Add .safetensors checkpoint; README: Cube3D-style header, define HQQ

#3
by Ramkasuru - opened
README.md CHANGED
@@ -15,16 +15,28 @@ tags:
15
  - diffusion
16
  ---
17
 
18
- # CubePart INT4 (HQQ W4A16, group size 128)
19
 
20
- INT4 weight-only quantization of the diffusion transformer in Roblox's [CubePart](https://huggingface.co/Roblox/cubepart) (open-vocabulary, part-controllable 3D mesh generation, [arXiv 2605.28763](https://arxiv.org/abs/2605.28763), SIGGRAPH 2026). Companion to [TrNi/efficient-cube3d](https://huggingface.co/TrNi/efficient-cube3d), which does the same for Cube3D.
 
 
 
21
 
22
- ## Summary
 
 
 
23
 
24
- - Checkpoint size: 1.479 GB, versus 8.582 GB for the BF16 diffusion transformer (82.8% smaller).
25
- - Peak VRAM during generation: 27.09 GiB, versus 33.71 GiB for BF16 (19.6% lower).
26
- - Quality: median chamfer distance between BF16 and INT4 part outputs is 0.0114 per part, which is near lossless on the typical part. Errors concentrate in a few small, thin parts.
27
- - Latency: INT4 is slower than BF16 in this release (66.5 to 66.9 s versus 34.7 to 34.8 s per generation). `torch.compile` has to be disabled for CubePart, so INT4 dequantization runs in eager mode.
 
 
 
 
 
 
28
 
29
  ## Efficiency
30
 
@@ -120,6 +132,29 @@ sd = torch.load("multi_part_dit_int4_hqq_g128.pt", map_location="cuda", weights_
120
  pipe.system.diffusion_model.load_state_dict(sd)
121
  ```
122
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  Generating parts from a mesh (this is the call used for every number above):
124
 
125
  ```python
@@ -144,15 +179,16 @@ part_meshes = pipe.input_to_part_shape(
144
 
145
  Environment notes: set `TORCHDYNAMO_DISABLE=1` (CubePart's compiled path raises a Dynamo error in `hijack_qwenimage_multi.py`). On our setup `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True` also avoided a Warp marching cubes out of memory error.
146
 
147
- The checkpoint is a pickle and needs `weights_only=False` because torchao's INT4 tensors are tensor subclasses. Only load it from a source you trust.
148
 
149
- Reload check: a fresh process that quantizes and then loads this file reproduces a live quantized model's output exactly (vertices and faces bit for bit on all 3 parts of a bar stool prompt).
150
 
151
  ## Files
152
 
153
  | File | Size | Description |
154
  |---|---|---|
155
- | `multi_part_dit_int4_hqq_g128.pt` | 1.479 GB | INT4 `diffusion_model` state dict, saved after in place quantization |
 
156
  | `quant_config.json` | small | Quantization settings and load recipe |
157
 
158
  The shape VAE and the text encoder are unchanged, so they are not re-hosted. Use the base repository for them.
 
15
  - diffusion
16
  ---
17
 
18
+ # ๐Ÿš€ First INT4 Quantized Efficient-CubePart3D - 83% Smaller Part Decomposition Model
19
 
20
+ ![](https://img.shields.io/badge/Model_Size-1.48_GB_(82.8%25โ†“)-blueviolet)
21
+ ![](https://img.shields.io/badge/Peak_VRAM-27.09_GiB_(19.6%25โ†“)-brightgreen)
22
+ ![](https://img.shields.io/badge/Part_CD_median-0.0114_(near_lossless)-blue)
23
+ ![](https://img.shields.io/badge/Latency-66.5s_(eager_mode,_1.9x_slower)-orange)
24
 
25
+ Presenting the **first INT4 quantized efficient version** of [CubePart](https://huggingface.co/Roblox/cubepart) ([arXiv 2605.28763](https://arxiv.org/abs/2605.28763), SIGGRAPH 2026), an open-vocabulary, part-controllable 3D mesh generative model.
26
+ Quantized via **HQQ (Half-Quadratic Quantization)**, a calibration-free weight-only method, at **W4A16** (4-bit weights, 16-bit activations, group_size=128) using [torchao](https://github.com/pytorch/ao),
27
+ it cuts the diffusion transformer from **8.58 GB โ†’ 1.48 GB (82.8%โ†“)** and peak VRAM from **33.71 GiB โ†’ 27.09 GiB (19.6%โ†“)** while keeping part decomposition near lossless (median chamfer distance **0.0114** per part over 828 parts).
28
+ Companion to [TrNi/efficient-cube3d](https://huggingface.co/TrNi/efficient-cube3d), which does the same for Cube3D.
29
 
30
+ | | BF16 | **INT4 (HQQ W4A16)** |
31
+ |---|:-:|:-:|
32
+ | ๐Ÿ’พ Model size (diffusion transformer) | 8.58 GB | **1.48 GB (82.8%โ†“)** |
33
+ | ๐ŸŽฎ Peak VRAM | 33.71 GiB | **27.09 GiB (19.6%โ†“)** โœจ |
34
+ | ๐Ÿ“ฆ Setup time | 161.8 s | **166.1 s** (BF16 load + 4.3 s quantize) |
35
+ | โฑ๏ธ Latency | 34.7 s | **66.5 s** (eager mode) |
36
+ | ๐ŸŽฏ Part chamfer distance vs BF16, median | n/a | **0.0114** |
37
+
38
+ <mark>๐Ÿ’ก The diffusion transformer is 82.8% smaller on disk and part decomposition stays near lossless on the typical part. Peak VRAM drops less (19.6%) because the Qwen3-VL text encoder and the shape VAE stay in BF16. INT4 is slower here because CubePart needs <code>torch.compile</code> disabled, so dequantization runs in eager mode.
39
+ </mark>
40
 
41
  ## Efficiency
42
 
 
132
  pipe.system.diffusion_model.load_state_dict(sd)
133
  ```
134
 
135
+ Or load the `.safetensors` file instead (no pickle). safetensors stores plain tensors only, so each INT4 tensor was saved as its inner tensors under keys like `<param>::tensor_impl::packed_weight`. After the same `quantize_` call above, copy them back in:
136
+
137
+ ```python
138
+ from safetensors.torch import load_file
139
+
140
+ def leaves(prefix, t, out):
141
+ # walk torchao tensor subclasses down to their plain inner tensors
142
+ if hasattr(t, "__tensor_flatten__"):
143
+ for name in t.__tensor_flatten__()[0]:
144
+ leaves(f"{prefix}::{name}", getattr(t, name), out)
145
+ else:
146
+ out[prefix] = t
147
+
148
+ saved = load_file("multi_part_dit_int4_hqq_g128.safetensors", device="cuda")
149
+ flat = {}
150
+ for k, v in pipe.system.diffusion_model.state_dict(keep_vars=True).items():
151
+ leaves(k, v, flat)
152
+ assert set(flat) == set(saved), "keys do not match: check the quantize_ settings"
153
+ with torch.no_grad():
154
+ for k, t in flat.items():
155
+ t.copy_(saved[k])
156
+ ```
157
+
158
  Generating parts from a mesh (this is the call used for every number above):
159
 
160
  ```python
 
179
 
180
  Environment notes: set `TORCHDYNAMO_DISABLE=1` (CubePart's compiled path raises a Dynamo error in `hijack_qwenimage_multi.py`). On our setup `PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True` also avoided a Warp marching cubes out of memory error.
181
 
182
+ The `.pt` checkpoint is a pickle and needs `weights_only=False` because torchao's INT4 tensors are tensor subclasses. Only load it from a source you trust, or use the `.safetensors` file, which contains no code.
183
 
184
+ Reload check: for each file, a fresh process that quantizes and then loads it reproduces a live quantized model's output exactly (vertices and faces bit for bit on all 3 parts of a bar stool prompt). Both files hold the same weights.
185
 
186
  ## Files
187
 
188
  | File | Size | Description |
189
  |---|---|---|
190
+ | `multi_part_dit_int4_hqq_g128.pt` | 1.479 GB | INT4 `diffusion_model` state dict, saved after in place quantization (pickle) |
191
+ | `multi_part_dit_int4_hqq_g128.safetensors` | 1.478 GB | Same weights as plain tensors, 1163 entries (no pickle) |
192
  | `quant_config.json` | small | Quantization settings and load recipe |
193
 
194
  The shape VAE and the text encoder are unchanged, so they are not re-hosted. Use the base repository for them.
multi_part_dit_int4_hqq_g128.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c7e33a2ae0ae2ac708c4a9396c5762df3b4b5b0fbac01a93cd9772a543a68783
3
+ size 1478462752
quant_config.json CHANGED
@@ -8,11 +8,23 @@
8
  "config": "int4_weight_only(group_size=128, use_hqq=True)",
9
  "group_size": 128,
10
  "quantized_scope": "nn.Linear layers in transformer_blocks and multi_transformer_blocks of diffusion_model",
11
- "skipped_name_substrings": ["img_in", "txt_in", "proj_out", "pos_embed", "time_text_embed", "norm"],
12
- "untouched_modules": ["shape_model", "base_model.text_encoder"],
 
 
 
 
 
 
 
 
 
 
13
  "checkpoint_file": "multi_part_dit_int4_hqq_g128.pt",
 
14
  "checkpoint_contents": "diffusion_model.state_dict() after in place quantization",
15
  "load_recipe": "instantiate the pipeline, quantize_ diffusion_model with the same config and filter, then load_state_dict with defaults (no assign=True), weights_only=False",
 
16
  "required_env": {
17
  "TORCHDYNAMO_DISABLE": "1"
18
  }
 
8
  "config": "int4_weight_only(group_size=128, use_hqq=True)",
9
  "group_size": 128,
10
  "quantized_scope": "nn.Linear layers in transformer_blocks and multi_transformer_blocks of diffusion_model",
11
+ "skipped_name_substrings": [
12
+ "img_in",
13
+ "txt_in",
14
+ "proj_out",
15
+ "pos_embed",
16
+ "time_text_embed",
17
+ "norm"
18
+ ],
19
+ "untouched_modules": [
20
+ "shape_model",
21
+ "base_model.text_encoder"
22
+ ],
23
  "checkpoint_file": "multi_part_dit_int4_hqq_g128.pt",
24
+ "checkpoint_file_safetensors": "multi_part_dit_int4_hqq_g128.safetensors",
25
  "checkpoint_contents": "diffusion_model.state_dict() after in place quantization",
26
  "load_recipe": "instantiate the pipeline, quantize_ diffusion_model with the same config and filter, then load_state_dict with defaults (no assign=True), weights_only=False",
27
+ "load_recipe_safetensors": "instantiate the pipeline, quantize_ diffusion_model with the same config and filter, flatten each state_dict tensor via __tensor_flatten__ into '<key>::<inner>' leaves, then copy_ each leaf from the file (see README)",
28
  "required_env": {
29
  "TORCHDYNAMO_DISABLE": "1"
30
  }