VoxelModel v1: tiny text-to-3D voxel diffusion
Browse files- .gitattributes +1 -0
- README.md +149 -0
- build_data.py +61 -0
- caption_filter.py +64 -0
- config.json +14 -0
- examples.png +0 -0
- model.png +3 -0
- model.safetensors +3 -0
- model_png.json +1 -0
- png_codec.py +84 -0
- sample.py +73 -0
- train.py +186 -0
- voxel_dit.py +134 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
model.png filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
pipeline_tag: text-to-3d
|
| 4 |
+
language:
|
| 5 |
+
- en
|
| 6 |
+
tags:
|
| 7 |
+
- 3d
|
| 8 |
+
- text-to-3d
|
| 9 |
+
- voxel
|
| 10 |
+
- diffusion
|
| 11 |
+
- rectified-flow
|
| 12 |
+
- custom-code
|
| 13 |
+
- tiny
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# VoxelModel v1 🧊
|
| 17 |
+
|
| 18 |
+
<img src="examples.png" alt="Twelve 32x32x32 grids generated from text prompts" style="width: 640px; max-width: 100%;">
|
| 19 |
+
|
| 20 |
+
A tiny text-to-3D diffusion model. You give it a prompt, it gives you a
|
| 21 |
+
32x32x32 occupancy grid. It is 40,050,892 trained parameters and it cost
|
| 22 |
+
$3.01 to train.
|
| 23 |
+
|
| 24 |
+
This is the PixelModel line stepping out of 2D. v1 through v4 were tiny latent
|
| 25 |
+
diffusion for images; VoxelModel keeps the same argument (low resolution as a
|
| 26 |
+
deliberate aesthetic, not a compromise) and applies it to shape. `model.png` is
|
| 27 |
+
still here and still holds every weight, because that gimmick ports unchanged to
|
| 28 |
+
3D: it only ever cared about floats.
|
| 29 |
+
|
| 30 |
+
## The one honest result
|
| 31 |
+
|
| 32 |
+
It works for bulky objects and it fails for thin ones.
|
| 33 |
+
|
| 34 |
+
Chairs, benches, tables, mushrooms, trees, cars and boats come back legible. A
|
| 35 |
+
sword comes back as 0.2% occupancy, which is a handful of stray voxels, and a key
|
| 36 |
+
comes back as a featureless bar.
|
| 37 |
+
|
| 38 |
+
That is not a training failure. A sword does not survive voxelization at 32^3 in
|
| 39 |
+
the **training data** either: the real Objaverse sword we voxelized for reference
|
| 40 |
+
sits at 2.9% occupancy and reads as a cross at best. The model reproduced what it
|
| 41 |
+
was shown. If you want swords, the answer is resolution, not more steps.
|
| 42 |
+
|
| 43 |
+
So: 32^3 is enough, for a specific and checkable definition of enough. That was
|
| 44 |
+
the question v1 existed to answer.
|
| 45 |
+
|
| 46 |
+
## No VAE
|
| 47 |
+
|
| 48 |
+
VoxelModel operates directly on voxels, so there is no autoencoder in the stack.
|
| 49 |
+
Total footprint is 40.05M trained plus 37.8M frozen CLIP ViT-B/32, which is
|
| 50 |
+
77.9M. PixelModel v4 was 161M for comparable trained size, because it carries an
|
| 51 |
+
83.7M frozen `sd-vae-ft-mse`. This one is tiny without an asterisk.
|
| 52 |
+
|
| 53 |
+
## Architecture
|
| 54 |
+
|
| 55 |
+
`VoxelDiT` is a DiT with 3D patchify. A 32^3 grid at patch 4 gives 512 tokens,
|
| 56 |
+
dim 384, depth 12, heads 6. Conditioning is cross-attention on frozen CLIP
|
| 57 |
+
ViT-B/32 text features (40 tokens), plus adaLN-zero from the pooled embedding and
|
| 58 |
+
the timestep. Positional encoding is a 3D sincos over the 8x8x8 patch grid.
|
| 59 |
+
|
| 60 |
+
Objective is rectified flow with `target = x1 - x0` and logit-normal timestep
|
| 61 |
+
sampling, `t = sigmoid(randn)`.
|
| 62 |
+
|
| 63 |
+
## Training
|
| 64 |
+
|
| 65 |
+
| | |
|
| 66 |
+
|---|---|
|
| 67 |
+
| meshes | 28,415 train, 1,024 held out |
|
| 68 |
+
| steps | 90,000 at batch 256 |
|
| 69 |
+
| hardware | one A100 80GB, 2.57 steps/s, 41.4 GiB |
|
| 70 |
+
| wall clock | 9.7 hours, $3.01 |
|
| 71 |
+
| optimizer | AdamW 2e-4, betas (0.9, 0.99), grad clip 1.0 |
|
| 72 |
+
| schedule | cosine to 1e-6, 500 step warmup |
|
| 73 |
+
| EMA | 0.9999 |
|
| 74 |
+
| CFG dropout | 10% |
|
| 75 |
+
|
| 76 |
+
Held-out loss bottomed at **0.018737 at step 64,000** and drifted up to 0.019314
|
| 77 |
+
by step 90,000. That is 3.08% of overfitting, and it means the run was about 2.8
|
| 78 |
+
hours longer than it needed to be. 90,000 steps over 28,415 meshes is 811 epochs
|
| 79 |
+
with no augmentation, which is the obvious thing to fix next. Yaw rotations plus
|
| 80 |
+
a mirror would give 8x for free on a cubic grid.
|
| 81 |
+
|
| 82 |
+
The shipped weights are the step 90,000 EMA, not the step 64,000 best, because
|
| 83 |
+
checkpoints were written to a fixed path and overwrote each other. Noted, and
|
| 84 |
+
worth 3.08% to you if you retrain.
|
| 85 |
+
|
| 86 |
+
## Data
|
| 87 |
+
|
| 88 |
+
Meshes are `dylanebert/objaverse-lowpoly-obj`, captions are `tiange/Cap3D`,
|
| 89 |
+
joined on Objaverse UID for 58,374 captioned meshes. Voxelization centers on the
|
| 90 |
+
vertex mean, scales by max absolute coordinate, and rasterizes surfaces at pitch
|
| 91 |
+
2/31 with no fill, since filling solidifies closed volumes and pushes 4% of the
|
| 92 |
+
set past 90% occupancy.
|
| 93 |
+
|
| 94 |
+
`caption_filter.py` then throws away half of it. Multi-object meshes are the main
|
| 95 |
+
defect in this corpus: normalization scales the whole scene bounding box into the
|
| 96 |
+
grid, so a mesh holding several objects shrinks each one into a cluster of blobs.
|
| 97 |
+
The caption gives this away before you load anything, so the filter rejects scene
|
| 98 |
+
and variant-set wording, a top level `and` joining two noun phrases, and captions
|
| 99 |
+
over 12 words. Colour pairs survive on purpose, so "a purple and green mushroom"
|
| 100 |
+
is kept and "a white cube and a white robot" is not.
|
| 101 |
+
|
| 102 |
+
Measured over all 58,374 captions, 29,439 survive, which is 50.4%.
|
| 103 |
+
|
| 104 |
+
Worth knowing if you build on this: **occupancy does not detect the defect.**
|
| 105 |
+
Survivors and rejects have almost the same occupancy distribution, medians 4.3%
|
| 106 |
+
against 5.0% over a 900 mesh sample. No threshold would have caught it.
|
| 107 |
+
|
| 108 |
+
## Usage
|
| 109 |
+
|
| 110 |
+
```bash
|
| 111 |
+
python sample.py "a wooden chair" "a purple mushroom" --cfg 5.0 --steps 50
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
Writes a JSON of base64 `np.packbits` grids. To load the weights from the PNG
|
| 115 |
+
instead of the safetensors:
|
| 116 |
+
|
| 117 |
+
```python
|
| 118 |
+
from png_codec import load_model_png
|
| 119 |
+
model = load_model_png("model.png", "model_png.json", device="cuda")
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
Round-trip error against the safetensors is 9.7e-04 max absolute, which is fp16
|
| 123 |
+
quantization and nothing else.
|
| 124 |
+
|
| 125 |
+
## What v1 does not do
|
| 126 |
+
|
| 127 |
+
**Colour.** These meshes carry no vertex colours and no `.mtl` files, so the
|
| 128 |
+
model generates grey geometry only. Colour needs a GLB-with-materials source and
|
| 129 |
+
its own voxelization path.
|
| 130 |
+
|
| 131 |
+
**A real eval metric.** FID does not transfer to 3D. Held-out rectified flow loss
|
| 132 |
+
is what the table above reports, and it is a training diagnostic, not a quality
|
| 133 |
+
measure. Rendered-FID with CLIP score on 2D renders, or IoU against a held-out
|
| 134 |
+
split, are the candidates. This is unresolved and we would rather say so than
|
| 135 |
+
publish a number we do not believe.
|
| 136 |
+
|
| 137 |
+
## Files
|
| 138 |
+
|
| 139 |
+
| file | what |
|
| 140 |
+
|---|---|
|
| 141 |
+
| `model.safetensors` | EMA weights, fp32 |
|
| 142 |
+
| `model.png` | the same weights as a 6329x6329 PNG |
|
| 143 |
+
| `model_png.json` | parameter manifest for the PNG |
|
| 144 |
+
| `voxel_dit.py` | the model |
|
| 145 |
+
| `train.py` | training loop |
|
| 146 |
+
| `sample.py` | inference |
|
| 147 |
+
| `caption_filter.py` | the single-object filter |
|
| 148 |
+
| `build_data.py` | mesh download and voxelization |
|
| 149 |
+
| `png_codec.py` | PNG encode, decode and verify |
|
build_data.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json, os, sys, time, numpy as np, trimesh
|
| 2 |
+
from multiprocessing import Pool
|
| 3 |
+
sys.path.insert(0, '/root')
|
| 4 |
+
from caption_filter import is_single_object
|
| 5 |
+
|
| 6 |
+
root = '/root/lowpoly'
|
| 7 |
+
OUT = '/root/data'
|
| 8 |
+
os.makedirs(OUT, exist_ok=True)
|
| 9 |
+
|
| 10 |
+
paths = {}
|
| 11 |
+
for dirpath, _, names in os.walk(root):
|
| 12 |
+
for n in names:
|
| 13 |
+
if n.endswith('.obj'):
|
| 14 |
+
paths[n[:-4]] = os.path.join(dirpath, n)
|
| 15 |
+
print('obj files on disk:', len(paths), flush=True)
|
| 16 |
+
|
| 17 |
+
caps = json.load(open('/root/captions.json'))
|
| 18 |
+
uids = sorted(u for u in caps if u in paths)
|
| 19 |
+
print('captioned meshes:', len(uids), flush=True)
|
| 20 |
+
|
| 21 |
+
def vox(u):
|
| 22 |
+
try:
|
| 23 |
+
m = trimesh.load(paths[u], force='mesh')
|
| 24 |
+
v = m.vertices - m.vertices.mean(0)
|
| 25 |
+
s = np.abs(v).max()
|
| 26 |
+
m.vertices = v / (s if s > 0 else 1)
|
| 27 |
+
g = m.voxelized(pitch=2.0/31).matrix.astype(np.uint8)
|
| 28 |
+
pad = np.zeros((32,32,32), np.uint8)
|
| 29 |
+
sh = [min(32,d) for d in g.shape]
|
| 30 |
+
pad[:sh[0],:sh[1],:sh[2]] = g[:sh[0],:sh[1],:sh[2]]
|
| 31 |
+
if pad.sum() == 0:
|
| 32 |
+
return u, None
|
| 33 |
+
return u, np.packbits(pad.reshape(-1))
|
| 34 |
+
except Exception:
|
| 35 |
+
return u, None
|
| 36 |
+
|
| 37 |
+
t0 = time.time()
|
| 38 |
+
ok_uids, rows, failed = [], [], 0
|
| 39 |
+
with Pool(32) as p:
|
| 40 |
+
for i, (u, packed) in enumerate(p.imap(vox, uids, chunksize=64)):
|
| 41 |
+
if packed is None:
|
| 42 |
+
failed += 1
|
| 43 |
+
else:
|
| 44 |
+
ok_uids.append(u); rows.append(packed)
|
| 45 |
+
if (i+1) % 5000 == 0:
|
| 46 |
+
print(f' voxelized {i+1}/{len(uids)} failed {failed} {time.time()-t0:.0f}s', flush=True)
|
| 47 |
+
|
| 48 |
+
vox_arr = np.stack(rows)
|
| 49 |
+
np.save(f'{OUT}/voxels_packed.npy', vox_arr)
|
| 50 |
+
captions = [caps[u] for u in ok_uids]
|
| 51 |
+
keep = [bool(is_single_object(c)) for c in captions]
|
| 52 |
+
json.dump({'uids': ok_uids, 'captions': captions, 'keep': keep},
|
| 53 |
+
open(f'{OUT}/meta.json', 'w'))
|
| 54 |
+
|
| 55 |
+
occ = np.unpackbits(vox_arr, axis=1).mean(1) * 100
|
| 56 |
+
print(f'\ndone in {time.time()-t0:.0f}s')
|
| 57 |
+
print(f'meshes voxelized {len(ok_uids)}, failed/empty {failed}')
|
| 58 |
+
print(f'array {vox_arr.shape} {vox_arr.nbytes/2**20:.0f} MiB packed')
|
| 59 |
+
print(f'filter survivors {sum(keep)} = {sum(keep)/len(keep)*100:.1f}%')
|
| 60 |
+
print(f'surface occupancy mean {occ.mean():.2f}% median {np.median(occ):.2f}%')
|
| 61 |
+
print('BUILDDONE', flush=True)
|
caption_filter.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
|
| 5 |
+
MAX_WORDS = 12
|
| 6 |
+
|
| 7 |
+
SCENE_PATTERNS = [
|
| 8 |
+
("variation", r"\bvari(?:ation|ations|ant|ants|ous)\b"),
|
| 9 |
+
("set of", r"\bsets?\s+of\b"),
|
| 10 |
+
("collection", r"\bcollections?\b"),
|
| 11 |
+
("assorted", r"\bassorted\b"),
|
| 12 |
+
("multiple", r"\bmultiple\b"),
|
| 13 |
+
("pair of", r"\bpairs?\s+of\b"),
|
| 14 |
+
("group of", r"\bgroups?\s+of\b"),
|
| 15 |
+
("scene", r"\bscenes?\b"),
|
| 16 |
+
("elements", r"\belements?\b"),
|
| 17 |
+
("featuring", r"\bfeaturing\b"),
|
| 18 |
+
("different", r"\bdifferent\b"),
|
| 19 |
+
]
|
| 20 |
+
|
| 21 |
+
MODIFIERS = {
|
| 22 |
+
"black", "white", "red", "blue", "green", "yellow", "orange", "purple",
|
| 23 |
+
"pink", "brown", "gray", "grey", "gold", "golden", "silver", "bronze",
|
| 24 |
+
"copper", "beige", "tan", "teal", "cyan", "magenta", "maroon", "navy",
|
| 25 |
+
"ivory", "cream", "turquoise", "lavender", "olive", "crimson", "scarlet",
|
| 26 |
+
"dark", "light", "pale", "bright", "deep", "pastel", "neon", "striped",
|
| 27 |
+
"wooden", "wood", "metal", "metallic", "plastic", "stone", "glass",
|
| 28 |
+
"steel", "iron", "brick", "concrete", "leather", "fabric", "marble",
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
_WORD = re.compile(r"[a-z][a-z'-]*")
|
| 32 |
+
_AND = re.compile(r"\band\b")
|
| 33 |
+
|
| 34 |
+
def _joins_noun_phrases(text):
|
| 35 |
+
parts = _AND.split(text)
|
| 36 |
+
if len(parts) < 2:
|
| 37 |
+
return False
|
| 38 |
+
for left, right in zip(parts[:-1], parts[1:]):
|
| 39 |
+
if "," in left:
|
| 40 |
+
return True
|
| 41 |
+
lw = _WORD.findall(left)
|
| 42 |
+
rw = _WORD.findall(right)
|
| 43 |
+
if not lw or not rw:
|
| 44 |
+
return True
|
| 45 |
+
if lw[-1] in MODIFIERS and rw[0] in MODIFIERS:
|
| 46 |
+
continue
|
| 47 |
+
return True
|
| 48 |
+
return False
|
| 49 |
+
|
| 50 |
+
def reject_reason(caption):
|
| 51 |
+
if not caption or not caption.strip():
|
| 52 |
+
return "empty"
|
| 53 |
+
text = caption.lower()
|
| 54 |
+
if len(caption.split()) > MAX_WORDS:
|
| 55 |
+
return "too long"
|
| 56 |
+
for name, pattern in SCENE_PATTERNS:
|
| 57 |
+
if re.search(pattern, text):
|
| 58 |
+
return name
|
| 59 |
+
if _joins_noun_phrases(text):
|
| 60 |
+
return "and"
|
| 61 |
+
return None
|
| 62 |
+
|
| 63 |
+
def is_single_object(caption):
|
| 64 |
+
return reject_reason(caption) is None
|
config.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"dit": {
|
| 3 |
+
"vox_size": 32,
|
| 4 |
+
"patch": 4,
|
| 5 |
+
"dim": 384,
|
| 6 |
+
"depth": 12,
|
| 7 |
+
"heads": 6,
|
| 8 |
+
"text_dim": 512
|
| 9 |
+
},
|
| 10 |
+
"steps": 90000,
|
| 11 |
+
"batch_size": 256,
|
| 12 |
+
"meshes": 28415,
|
| 13 |
+
"filtered": true
|
| 14 |
+
}
|
examples.png
ADDED
|
model.png
ADDED
|
Git LFS Details
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8cce2211e7e7500dd661df15e64e185770046879073394e5a4a31a0185331f0a
|
| 3 |
+
size 161012360
|
model_png.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"cfg": {"vox_size": 32, "patch": 4, "dim": 384, "depth": 12, "heads": 6, "text_dim": 512}, "params": [{"name": "x_embed.weight", "shape": [384, 64], "numel": 24576}, {"name": "x_embed.bias", "shape": [384], "numel": 384}, {"name": "t_mlp.0.weight", "shape": [384, 384], "numel": 147456}, {"name": "t_mlp.0.bias", "shape": [384], "numel": 384}, {"name": "t_mlp.2.weight", "shape": [384, 384], "numel": 147456}, {"name": "t_mlp.2.bias", "shape": [384], "numel": 384}, {"name": "text_proj.weight", "shape": [384, 512], "numel": 196608}, {"name": "text_proj.bias", "shape": [384], "numel": 384}, {"name": "text_pool.weight", "shape": [384, 512], "numel": 196608}, {"name": "text_pool.bias", "shape": [384], "numel": 384}, {"name": "blocks.0.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.0.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.0.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.0.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.0.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.0.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.0.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.0.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.0.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.0.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.0.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.0.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.0.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.0.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.0.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.0.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.0.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.0.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.0.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "blocks.1.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.1.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.1.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.1.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.1.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.1.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.1.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.1.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.1.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.1.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.1.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.1.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.1.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.1.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.1.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.1.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.1.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.1.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.1.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "blocks.2.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.2.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.2.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.2.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.2.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.2.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.2.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.2.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.2.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.2.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.2.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.2.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.2.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.2.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.2.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.2.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.2.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.2.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.2.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "blocks.3.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.3.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.3.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.3.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.3.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.3.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.3.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.3.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.3.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.3.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.3.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.3.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.3.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.3.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.3.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.3.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.3.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.3.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.3.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "blocks.4.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.4.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.4.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.4.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.4.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.4.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.4.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.4.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.4.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.4.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.4.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.4.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.4.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.4.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.4.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.4.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.4.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.4.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.4.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "blocks.5.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.5.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.5.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.5.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.5.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.5.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.5.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.5.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.5.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.5.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.5.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.5.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.5.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.5.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.5.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.5.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.5.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.5.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.5.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "blocks.6.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.6.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.6.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.6.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.6.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.6.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.6.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.6.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.6.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.6.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.6.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.6.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.6.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.6.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.6.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.6.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.6.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.6.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.6.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "blocks.7.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.7.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.7.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.7.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.7.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.7.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.7.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.7.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.7.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.7.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.7.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.7.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.7.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.7.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.7.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.7.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.7.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.7.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.7.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "blocks.8.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.8.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.8.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.8.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.8.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.8.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.8.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.8.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.8.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.8.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.8.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.8.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.8.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.8.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.8.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.8.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.8.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.8.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.8.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "blocks.9.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.9.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.9.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.9.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.9.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.9.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.9.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.9.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.9.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.9.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.9.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.9.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.9.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.9.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.9.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.9.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.9.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.9.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.9.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "blocks.10.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.10.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.10.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.10.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.10.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.10.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.10.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.10.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.10.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.10.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.10.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.10.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.10.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.10.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.10.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.10.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.10.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.10.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.10.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "blocks.11.cross_gate", "shape": [1], "numel": 1}, {"name": "blocks.11.attn.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.11.attn.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.11.attn.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.11.attn.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.11.attn.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.11.attn.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.11.cross.q.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.11.cross.q.bias", "shape": [384], "numel": 384}, {"name": "blocks.11.cross.kv.weight", "shape": [768, 384], "numel": 294912}, {"name": "blocks.11.cross.kv.bias", "shape": [768], "numel": 768}, {"name": "blocks.11.cross.proj.weight", "shape": [384, 384], "numel": 147456}, {"name": "blocks.11.cross.proj.bias", "shape": [384], "numel": 384}, {"name": "blocks.11.mlp.0.weight", "shape": [1536, 384], "numel": 589824}, {"name": "blocks.11.mlp.0.bias", "shape": [1536], "numel": 1536}, {"name": "blocks.11.mlp.2.weight", "shape": [384, 1536], "numel": 589824}, {"name": "blocks.11.mlp.2.bias", "shape": [384], "numel": 384}, {"name": "blocks.11.ada.1.weight", "shape": [2304, 384], "numel": 884736}, {"name": "blocks.11.ada.1.bias", "shape": [2304], "numel": 2304}, {"name": "ada_out.1.weight", "shape": [768, 384], "numel": 294912}, {"name": "ada_out.1.bias", "shape": [768], "numel": 768}, {"name": "head.weight", "shape": [64, 384], "numel": 24576}, {"name": "head.bias", "shape": [64], "numel": 64}], "total_parameters": 40050892, "side": 6329, "dtype": "float16", "channels": "R=hi,G=lo,B=unused"}
|
png_codec.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
import math
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
from PIL import Image
|
| 11 |
+
from safetensors.torch import load_file
|
| 12 |
+
|
| 13 |
+
from voxel_dit import VoxelDiT
|
| 14 |
+
|
| 15 |
+
def encode(weights, png_path, cfg_path, config="config.json"):
|
| 16 |
+
c = json.load(open(config))["dit"]
|
| 17 |
+
model = VoxelDiT(vox_size=c["vox_size"], patch=c["patch"], dim=c["dim"],
|
| 18 |
+
depth=c["depth"], heads=c["heads"], text_dim=c["text_dim"])
|
| 19 |
+
model.load_state_dict(load_file(weights))
|
| 20 |
+
parts, manifest = [], []
|
| 21 |
+
for name, p in model.named_parameters():
|
| 22 |
+
a = p.detach().to(torch.float16).contiguous().view(-1).numpy()
|
| 23 |
+
parts.append(a)
|
| 24 |
+
manifest.append({"name": name, "shape": list(p.shape), "numel": int(a.size)})
|
| 25 |
+
flat = np.concatenate(parts)
|
| 26 |
+
N = flat.size
|
| 27 |
+
side = math.ceil(math.sqrt(N))
|
| 28 |
+
u16 = flat.view(np.uint16)
|
| 29 |
+
img = np.zeros((side * side, 3), dtype=np.uint8)
|
| 30 |
+
img[:N, 0] = (u16 >> 8).astype(np.uint8)
|
| 31 |
+
img[:N, 1] = (u16 & 0xFF).astype(np.uint8)
|
| 32 |
+
Image.fromarray(img.reshape(side, side, 3), "RGB").save(png_path)
|
| 33 |
+
total = sum(m["numel"] for m in manifest)
|
| 34 |
+
json.dump({"cfg": c, "params": manifest, "total_parameters": total,
|
| 35 |
+
"side": side, "dtype": "float16", "channels": "R=hi,G=lo,B=unused"},
|
| 36 |
+
open(cfg_path, "w"))
|
| 37 |
+
mb = os.path.getsize(png_path) / 1e6
|
| 38 |
+
print(f"[png] encoded {total:,} params -> {side}x{side} PNG ({mb:.1f} MB)")
|
| 39 |
+
return total, side
|
| 40 |
+
|
| 41 |
+
def load_model_png(png_path, cfg_path, device="cpu"):
|
| 42 |
+
meta = json.load(open(cfg_path))
|
| 43 |
+
c = meta["cfg"]
|
| 44 |
+
model = VoxelDiT(vox_size=c["vox_size"], patch=c["patch"], dim=c["dim"],
|
| 45 |
+
depth=c["depth"], heads=c["heads"], text_dim=c["text_dim"])
|
| 46 |
+
arr = np.asarray(Image.open(png_path).convert("RGB")).reshape(-1, 3)
|
| 47 |
+
total = meta["total_parameters"]
|
| 48 |
+
hi = arr[:total, 0].astype(np.uint16)
|
| 49 |
+
lo = arr[:total, 1].astype(np.uint16)
|
| 50 |
+
flat = ((hi << 8) | lo).astype(np.uint16).view(np.float16)
|
| 51 |
+
sd = dict(model.named_parameters())
|
| 52 |
+
off = 0
|
| 53 |
+
with torch.no_grad():
|
| 54 |
+
for m in meta["params"]:
|
| 55 |
+
n = m["numel"]
|
| 56 |
+
chunk = flat[off:off + n].astype(np.float16)
|
| 57 |
+
sd[m["name"]].copy_(torch.from_numpy(chunk.copy()).view(*m["shape"]).to(torch.float32))
|
| 58 |
+
off += n
|
| 59 |
+
return model.to(device).eval()
|
| 60 |
+
|
| 61 |
+
def verify(weights, png_path, cfg_path, config="config.json"):
|
| 62 |
+
model = load_model_png(png_path, cfg_path)
|
| 63 |
+
ref = load_file(weights)
|
| 64 |
+
worst, name = 0.0, ""
|
| 65 |
+
for k, p in model.named_parameters():
|
| 66 |
+
d = (p.detach() - ref[k].float()).abs().max().item()
|
| 67 |
+
if d > worst:
|
| 68 |
+
worst, name = d, k
|
| 69 |
+
rel = worst / max(1e-12, ref[name].float().abs().max().item())
|
| 70 |
+
print(f"[png] round-trip max abs err {worst:.3e} on {name} (relative {rel:.2e})")
|
| 71 |
+
return worst
|
| 72 |
+
|
| 73 |
+
def main():
|
| 74 |
+
ap = argparse.ArgumentParser()
|
| 75 |
+
ap.add_argument("--weights", default="model.safetensors")
|
| 76 |
+
ap.add_argument("--png", default="model.png")
|
| 77 |
+
ap.add_argument("--manifest", default="model_png.json")
|
| 78 |
+
ap.add_argument("--config", default="config.json")
|
| 79 |
+
args = ap.parse_args()
|
| 80 |
+
encode(args.weights, args.png, args.manifest, args.config)
|
| 81 |
+
verify(args.weights, args.png, args.manifest, args.config)
|
| 82 |
+
|
| 83 |
+
if __name__ == "__main__":
|
| 84 |
+
main()
|
sample.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import base64
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
from safetensors.torch import load_file
|
| 11 |
+
from transformers import CLIPTextModel, CLIPTokenizer
|
| 12 |
+
|
| 13 |
+
from voxel_dit import VoxelDiT
|
| 14 |
+
|
| 15 |
+
MAX_TOKENS = 40
|
| 16 |
+
|
| 17 |
+
@torch.no_grad()
|
| 18 |
+
def sample(model, seq, pool, null_seq, null_pool, steps, cfg, dev):
|
| 19 |
+
B = seq.shape[0]
|
| 20 |
+
x = torch.randn(B, 1, 32, 32, 32, device=dev)
|
| 21 |
+
ns = null_seq.expand(B, -1, -1)
|
| 22 |
+
npool = null_pool.expand(B, -1)
|
| 23 |
+
dt = 1.0 / steps
|
| 24 |
+
for i in range(steps):
|
| 25 |
+
t = torch.full((B,), i * dt, device=dev)
|
| 26 |
+
with torch.autocast("cuda", dtype=torch.bfloat16):
|
| 27 |
+
vc = model(x, t, seq, pool)
|
| 28 |
+
vu = model(x, t, ns, npool)
|
| 29 |
+
x = x + (vu + cfg * (vc - vu)).float() * dt
|
| 30 |
+
return x
|
| 31 |
+
|
| 32 |
+
@torch.no_grad()
|
| 33 |
+
def main():
|
| 34 |
+
ap = argparse.ArgumentParser()
|
| 35 |
+
ap.add_argument("prompts", nargs="+")
|
| 36 |
+
ap.add_argument("--out", default="samples.json")
|
| 37 |
+
ap.add_argument("--cfg", type=float, default=5.0)
|
| 38 |
+
ap.add_argument("--steps", type=int, default=50)
|
| 39 |
+
ap.add_argument("--threshold", type=float, default=0.0)
|
| 40 |
+
ap.add_argument("--device", default="cuda")
|
| 41 |
+
ap.add_argument("--weights", default="/root/runs/voxel_v1/model.safetensors")
|
| 42 |
+
ap.add_argument("--clip", default="openai/clip-vit-base-patch32")
|
| 43 |
+
args = ap.parse_args()
|
| 44 |
+
dev = args.device
|
| 45 |
+
|
| 46 |
+
model = VoxelDiT().to(dev).eval()
|
| 47 |
+
model.load_state_dict(load_file(args.weights))
|
| 48 |
+
|
| 49 |
+
tok = CLIPTokenizer.from_pretrained(args.clip)
|
| 50 |
+
txt = CLIPTextModel.from_pretrained(args.clip).to(dev).eval()
|
| 51 |
+
|
| 52 |
+
def enc(strings):
|
| 53 |
+
t = tok(strings, padding="max_length", max_length=MAX_TOKENS,
|
| 54 |
+
truncation=True, return_tensors="pt").to(dev)
|
| 55 |
+
o = txt(**t)
|
| 56 |
+
return o.last_hidden_state.float(), o.pooler_output.float()
|
| 57 |
+
|
| 58 |
+
seq, pool = enc(args.prompts)
|
| 59 |
+
null_seq, null_pool = enc([""])
|
| 60 |
+
x = sample(model, seq, pool, null_seq, null_pool, args.steps, args.cfg, dev)
|
| 61 |
+
|
| 62 |
+
grids = (x[:, 0] > args.threshold).to(torch.uint8).cpu().numpy()
|
| 63 |
+
out = []
|
| 64 |
+
for p, g in zip(args.prompts, grids):
|
| 65 |
+
occ = float(g.mean()) * 100
|
| 66 |
+
out.append({"prompt": p, "occS": round(occ, 1),
|
| 67 |
+
"s": base64.b64encode(np.packbits(g.reshape(-1)).tobytes()).decode()})
|
| 68 |
+
print(f" {occ:5.1f}% {p}")
|
| 69 |
+
json.dump(out, open(args.out, "w"))
|
| 70 |
+
print(f"[sample] {len(out)} grids -> {args.out} (cfg {args.cfg}, {args.steps} steps)")
|
| 71 |
+
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
main()
|
train.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import copy
|
| 5 |
+
import json
|
| 6 |
+
import math
|
| 7 |
+
import os
|
| 8 |
+
import time
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
+
import torch.nn.functional as F
|
| 13 |
+
from safetensors.torch import save_file
|
| 14 |
+
|
| 15 |
+
from voxel_dit import VoxelDiT
|
| 16 |
+
|
| 17 |
+
def cosine_lr(step, total, base, floor, warmup):
|
| 18 |
+
if step < warmup:
|
| 19 |
+
return base * (step + 1) / max(1, warmup)
|
| 20 |
+
t = (step - warmup) / max(1, total - warmup)
|
| 21 |
+
return floor + 0.5 * (base - floor) * (1 + math.cos(math.pi * t))
|
| 22 |
+
|
| 23 |
+
def load_split(data, filtered, n_val, seed=0):
|
| 24 |
+
meta = json.load(open(f"{data}/meta.json"))
|
| 25 |
+
keep = np.array(meta["keep"], bool)
|
| 26 |
+
idx = np.nonzero(keep)[0] if filtered else np.arange(len(keep))
|
| 27 |
+
perm = np.random.RandomState(seed).permutation(len(idx))
|
| 28 |
+
idx = idx[perm]
|
| 29 |
+
val, tr = idx[:n_val], idx[n_val:]
|
| 30 |
+
packed = np.load(f"{data}/voxels_packed.npy")
|
| 31 |
+
seq = np.load(f"{data}/text_seq.npy")
|
| 32 |
+
pool = np.load(f"{data}/text_pool.npy")
|
| 33 |
+
pack = lambda s: (np.unpackbits(packed[s], axis=1), seq[s], pool[s])
|
| 34 |
+
return pack(tr), pack(val), len(keep)
|
| 35 |
+
|
| 36 |
+
def main():
|
| 37 |
+
ap = argparse.ArgumentParser()
|
| 38 |
+
ap.add_argument("--data", default="/root/data")
|
| 39 |
+
ap.add_argument("--out", default="/root/runs/voxel_v1")
|
| 40 |
+
ap.add_argument("--steps", type=int, default=90000)
|
| 41 |
+
ap.add_argument("--batch-size", type=int, default=256)
|
| 42 |
+
ap.add_argument("--lr", type=float, default=2e-4)
|
| 43 |
+
ap.add_argument("--min-lr", type=float, default=1e-6)
|
| 44 |
+
ap.add_argument("--warmup", type=int, default=500)
|
| 45 |
+
ap.add_argument("--cfg-dropout", type=float, default=0.1)
|
| 46 |
+
ap.add_argument("--ema", type=float, default=0.9999)
|
| 47 |
+
ap.add_argument("--filtered", type=int, default=1)
|
| 48 |
+
ap.add_argument("--val-size", type=int, default=1024)
|
| 49 |
+
ap.add_argument("--val-every", type=int, default=2000)
|
| 50 |
+
ap.add_argument("--log-every", type=int, default=200)
|
| 51 |
+
ap.add_argument("--save-every", type=int, default=10000)
|
| 52 |
+
ap.add_argument("--resume", default="")
|
| 53 |
+
ap.add_argument("--seed", type=int, default=0)
|
| 54 |
+
args = ap.parse_args()
|
| 55 |
+
|
| 56 |
+
dev = "cuda"
|
| 57 |
+
os.makedirs(args.out, exist_ok=True)
|
| 58 |
+
torch.manual_seed(args.seed)
|
| 59 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 60 |
+
torch.backends.cudnn.allow_tf32 = True
|
| 61 |
+
|
| 62 |
+
(vox, seq, pool), (vvox, vseq, vpool), total_meshes = load_split(
|
| 63 |
+
args.data, args.filtered, args.val_size)
|
| 64 |
+
M = vox.shape[0]
|
| 65 |
+
vox_t = torch.from_numpy(vox).to(dev)
|
| 66 |
+
seq_t = torch.from_numpy(seq).to(dev)
|
| 67 |
+
pool_t = torch.from_numpy(pool).to(dev)
|
| 68 |
+
null_seq = torch.from_numpy(np.load(f"{args.data}/null_seq.npy")).to(dev).float()
|
| 69 |
+
null_pool = torch.from_numpy(np.load(f"{args.data}/null_pool.npy")).to(dev).float()
|
| 70 |
+
|
| 71 |
+
vg = torch.Generator(device=dev).manual_seed(1234)
|
| 72 |
+
vx1 = torch.from_numpy(vvox).to(dev).view(-1, 1, 32, 32, 32).float() * 2 - 1
|
| 73 |
+
vs = torch.from_numpy(vseq).to(dev).float()
|
| 74 |
+
vp = torch.from_numpy(vpool).to(dev).float()
|
| 75 |
+
vx0 = torch.randn(vx1.shape, device=dev, generator=vg)
|
| 76 |
+
vt = torch.sigmoid(torch.randn(vx1.shape[0], device=dev, generator=vg))
|
| 77 |
+
|
| 78 |
+
@torch.no_grad()
|
| 79 |
+
def val_loss():
|
| 80 |
+
ema.eval()
|
| 81 |
+
tot, n = 0.0, 0
|
| 82 |
+
for j in range(0, vx1.shape[0], args.batch_size):
|
| 83 |
+
sl = slice(j, j + args.batch_size)
|
| 84 |
+
m = vx1[sl].shape[0]
|
| 85 |
+
tb = vt[sl].view(-1, 1, 1, 1, 1)
|
| 86 |
+
xt = (1 - tb) * vx0[sl] + tb * vx1[sl]
|
| 87 |
+
with torch.autocast("cuda", dtype=torch.bfloat16):
|
| 88 |
+
v = ema(xt, vt[sl], vs[sl], vp[sl])
|
| 89 |
+
tot += F.mse_loss(v.float(), vx1[sl] - vx0[sl]).item() * m
|
| 90 |
+
n += m
|
| 91 |
+
return tot / n
|
| 92 |
+
|
| 93 |
+
model = VoxelDiT().to(dev)
|
| 94 |
+
ema = copy.deepcopy(model).eval()
|
| 95 |
+
for p in ema.parameters():
|
| 96 |
+
p.requires_grad_(False)
|
| 97 |
+
opt = torch.optim.AdamW(model.parameters(), lr=args.lr, betas=(0.9, 0.99), weight_decay=0.0)
|
| 98 |
+
|
| 99 |
+
start = 0
|
| 100 |
+
if args.resume and os.path.exists(args.resume):
|
| 101 |
+
ck = torch.load(args.resume, map_location=dev)
|
| 102 |
+
model.load_state_dict(ck["model"])
|
| 103 |
+
ema.load_state_dict(ck["ema"])
|
| 104 |
+
opt.load_state_dict(ck["opt"])
|
| 105 |
+
start = ck["step"] + 1
|
| 106 |
+
|
| 107 |
+
B = args.batch_size
|
| 108 |
+
gen = torch.Generator(device=dev).manual_seed(args.seed)
|
| 109 |
+
epochs = args.steps * B / M
|
| 110 |
+
print(f"[train] {M} train + {vx1.shape[0]} val of {total_meshes} captioned "
|
| 111 |
+
f"({'filtered' if args.filtered else 'unfiltered'}), "
|
| 112 |
+
f"{model.num_params()/1e6:.2f}M params", flush=True)
|
| 113 |
+
print(f"[train] {args.steps} steps x batch {B} = {epochs:.0f} epochs, "
|
| 114 |
+
f"resume from {start}", flush=True)
|
| 115 |
+
|
| 116 |
+
logf = open(f"{args.out}/log.jsonl", "a")
|
| 117 |
+
running, t0 = 0.0, time.time()
|
| 118 |
+
for step in range(start, args.steps):
|
| 119 |
+
i = torch.randint(0, M, (B,), device=dev, generator=gen)
|
| 120 |
+
x1 = vox_t[i].view(B, 1, 32, 32, 32).float() * 2 - 1
|
| 121 |
+
s = seq_t[i].float()
|
| 122 |
+
p = pool_t[i].float()
|
| 123 |
+
drop = torch.rand(B, device=dev, generator=gen) < args.cfg_dropout
|
| 124 |
+
s = torch.where(drop[:, None, None], null_seq, s)
|
| 125 |
+
p = torch.where(drop[:, None], null_pool, p)
|
| 126 |
+
|
| 127 |
+
x0 = torch.randn(x1.shape, device=dev, generator=gen)
|
| 128 |
+
u = torch.randn(B, device=dev, generator=gen)
|
| 129 |
+
t = torch.sigmoid(u)
|
| 130 |
+
tb = t.view(-1, 1, 1, 1, 1)
|
| 131 |
+
xt = (1 - tb) * x0 + tb * x1
|
| 132 |
+
target = x1 - x0
|
| 133 |
+
|
| 134 |
+
lr = cosine_lr(step, args.steps, args.lr, args.min_lr, args.warmup)
|
| 135 |
+
for g in opt.param_groups:
|
| 136 |
+
g["lr"] = lr
|
| 137 |
+
|
| 138 |
+
with torch.autocast("cuda", dtype=torch.bfloat16):
|
| 139 |
+
v = model(xt, t, s, p)
|
| 140 |
+
loss = F.mse_loss(v.float(), target)
|
| 141 |
+
opt.zero_grad(set_to_none=True)
|
| 142 |
+
loss.backward()
|
| 143 |
+
gn = torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
|
| 144 |
+
opt.step()
|
| 145 |
+
with torch.no_grad():
|
| 146 |
+
d = 1 - args.ema
|
| 147 |
+
for pe, pm in zip(ema.parameters(), model.parameters()):
|
| 148 |
+
pe.mul_(args.ema).add_(pm.detach(), alpha=d)
|
| 149 |
+
|
| 150 |
+
running += loss.item()
|
| 151 |
+
if (step + 1) % args.log_every == 0:
|
| 152 |
+
avg = running / args.log_every
|
| 153 |
+
el = time.time() - t0
|
| 154 |
+
sps = args.log_every / el
|
| 155 |
+
eta = (args.steps - step - 1) / sps / 3600
|
| 156 |
+
print(f"[s{step+1:06d}] loss={avg:.5f} lr={lr:.2e} gnorm={gn:.2f} "
|
| 157 |
+
f"{sps:.2f} steps/s eta={eta:.2f}h", flush=True)
|
| 158 |
+
logf.write(json.dumps({"step": step + 1, "loss": avg, "lr": lr,
|
| 159 |
+
"gnorm": float(gn), "steps_per_s": sps}) + "\n")
|
| 160 |
+
logf.flush()
|
| 161 |
+
running, t0 = 0.0, time.time()
|
| 162 |
+
|
| 163 |
+
if (step + 1) % args.val_every == 0 or step + 1 == args.steps:
|
| 164 |
+
vl = val_loss()
|
| 165 |
+
print(f"[s{step+1:06d}] val_loss={vl:.5f} (ema, {args.val_size} held out)", flush=True)
|
| 166 |
+
logf.write(json.dumps({"step": step + 1, "val_loss": vl}) + "\n")
|
| 167 |
+
logf.flush()
|
| 168 |
+
t0 = time.time()
|
| 169 |
+
|
| 170 |
+
if (step + 1) % args.save_every == 0 or step + 1 == args.steps:
|
| 171 |
+
torch.save({"model": model.state_dict(), "ema": ema.state_dict(),
|
| 172 |
+
"opt": opt.state_dict(), "step": step},
|
| 173 |
+
f"{args.out}/ckpt.pt")
|
| 174 |
+
save_file({k: v.contiguous() for k, v in ema.state_dict().items()},
|
| 175 |
+
f"{args.out}/model.safetensors")
|
| 176 |
+
print(f"[s{step+1:06d}] saved", flush=True)
|
| 177 |
+
|
| 178 |
+
json.dump({"dit": {"vox_size": 32, "patch": 4, "dim": 384, "depth": 12,
|
| 179 |
+
"heads": 6, "text_dim": 512},
|
| 180 |
+
"steps": args.steps, "batch_size": args.batch_size,
|
| 181 |
+
"meshes": M, "filtered": bool(args.filtered)},
|
| 182 |
+
open(f"{args.out}/config.json", "w"), indent=2)
|
| 183 |
+
print("TRAINDONE", flush=True)
|
| 184 |
+
|
| 185 |
+
if __name__ == "__main__":
|
| 186 |
+
main()
|
voxel_dit.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
|
| 9 |
+
def modulate(x, shift, scale):
|
| 10 |
+
return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
|
| 11 |
+
|
| 12 |
+
def timestep_embedding(t, dim, max_period=10000):
|
| 13 |
+
half = dim // 2
|
| 14 |
+
freqs = torch.exp(-math.log(max_period) * torch.arange(half, device=t.device) / half)
|
| 15 |
+
args = t[:, None].float() * freqs[None]
|
| 16 |
+
emb = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
|
| 17 |
+
if dim % 2:
|
| 18 |
+
emb = torch.cat([emb, torch.zeros_like(emb[:, :1])], dim=-1)
|
| 19 |
+
return emb
|
| 20 |
+
|
| 21 |
+
def sincos_3d(dim, grid_size):
|
| 22 |
+
assert dim % 6 == 0
|
| 23 |
+
g = np.arange(grid_size, dtype=np.float32)
|
| 24 |
+
gz, gy, gx = np.meshgrid(g, g, g, indexing="ij")
|
| 25 |
+
d6 = dim // 6
|
| 26 |
+
omega = 1.0 / (10000 ** (np.arange(d6, dtype=np.float32) / d6))
|
| 27 |
+
def emb1(p):
|
| 28 |
+
out = p.reshape(-1)[:, None] * omega[None]
|
| 29 |
+
return np.concatenate([np.sin(out), np.cos(out)], axis=1)
|
| 30 |
+
pe = np.concatenate([emb1(gz), emb1(gy), emb1(gx)], axis=1)
|
| 31 |
+
return torch.from_numpy(pe).float()
|
| 32 |
+
|
| 33 |
+
class Attention(nn.Module):
|
| 34 |
+
def __init__(self, dim, heads):
|
| 35 |
+
super().__init__()
|
| 36 |
+
self.heads = heads
|
| 37 |
+
self.q = nn.Linear(dim, dim)
|
| 38 |
+
self.kv = nn.Linear(dim, dim * 2)
|
| 39 |
+
self.proj = nn.Linear(dim, dim)
|
| 40 |
+
|
| 41 |
+
def forward(self, x, ctx=None):
|
| 42 |
+
ctx = x if ctx is None else ctx
|
| 43 |
+
B, N, C = x.shape
|
| 44 |
+
M = ctx.shape[1]
|
| 45 |
+
h = self.heads
|
| 46 |
+
q = self.q(x).reshape(B, N, h, C // h).transpose(1, 2)
|
| 47 |
+
kv = self.kv(ctx).reshape(B, M, 2, h, C // h).permute(2, 0, 3, 1, 4)
|
| 48 |
+
k, v = kv[0], kv[1]
|
| 49 |
+
o = F.scaled_dot_product_attention(q, k, v)
|
| 50 |
+
o = o.transpose(1, 2).reshape(B, N, C)
|
| 51 |
+
return self.proj(o)
|
| 52 |
+
|
| 53 |
+
class Block(nn.Module):
|
| 54 |
+
def __init__(self, dim, heads, mlp_ratio=4.0):
|
| 55 |
+
super().__init__()
|
| 56 |
+
self.norm1 = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
|
| 57 |
+
self.attn = Attention(dim, heads)
|
| 58 |
+
self.norm_ca = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
|
| 59 |
+
self.cross = Attention(dim, heads)
|
| 60 |
+
self.norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
|
| 61 |
+
hidden = int(dim * mlp_ratio)
|
| 62 |
+
self.mlp = nn.Sequential(nn.Linear(dim, hidden), nn.GELU(approximate="tanh"),
|
| 63 |
+
nn.Linear(hidden, dim))
|
| 64 |
+
self.ada = nn.Sequential(nn.SiLU(), nn.Linear(dim, 6 * dim))
|
| 65 |
+
self.cross_gate = nn.Parameter(torch.zeros(1))
|
| 66 |
+
|
| 67 |
+
def forward(self, x, c, text):
|
| 68 |
+
shift1, scale1, gate1, shift2, scale2, gate2 = self.ada(c).chunk(6, dim=1)
|
| 69 |
+
x = x + gate1.unsqueeze(1) * self.attn(modulate(self.norm1(x), shift1, scale1))
|
| 70 |
+
x = x + self.cross_gate * self.cross(self.norm_ca(x), text)
|
| 71 |
+
x = x + gate2.unsqueeze(1) * self.mlp(modulate(self.norm2(x), shift2, scale2))
|
| 72 |
+
return x
|
| 73 |
+
|
| 74 |
+
class VoxelDiT(nn.Module):
|
| 75 |
+
def __init__(self, vox_ch=1, vox_size=32, patch=4, dim=384, depth=12,
|
| 76 |
+
heads=6, text_dim=512, mlp_ratio=4.0):
|
| 77 |
+
super().__init__()
|
| 78 |
+
self.vox_ch = vox_ch
|
| 79 |
+
self.vox_size = vox_size
|
| 80 |
+
self.patch = patch
|
| 81 |
+
self.grid = vox_size // patch
|
| 82 |
+
self.patch_dim = vox_ch * patch * patch * patch
|
| 83 |
+
self.x_embed = nn.Linear(self.patch_dim, dim)
|
| 84 |
+
self.register_buffer("pos", sincos_3d(dim, self.grid).unsqueeze(0))
|
| 85 |
+
self.t_mlp = nn.Sequential(nn.Linear(dim, dim), nn.SiLU(), nn.Linear(dim, dim))
|
| 86 |
+
self.text_proj = nn.Linear(text_dim, dim)
|
| 87 |
+
self.text_pool = nn.Linear(text_dim, dim)
|
| 88 |
+
self.blocks = nn.ModuleList([Block(dim, heads, mlp_ratio) for _ in range(depth)])
|
| 89 |
+
self.norm_out = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
|
| 90 |
+
self.ada_out = nn.Sequential(nn.SiLU(), nn.Linear(dim, 2 * dim))
|
| 91 |
+
self.head = nn.Linear(dim, self.patch_dim)
|
| 92 |
+
self.dim = dim
|
| 93 |
+
self._init()
|
| 94 |
+
|
| 95 |
+
def _init(self):
|
| 96 |
+
for m in self.modules():
|
| 97 |
+
if isinstance(m, nn.Linear):
|
| 98 |
+
nn.init.xavier_uniform_(m.weight)
|
| 99 |
+
if m.bias is not None:
|
| 100 |
+
nn.init.zeros_(m.bias)
|
| 101 |
+
for b in self.blocks:
|
| 102 |
+
nn.init.zeros_(b.ada[-1].weight); nn.init.zeros_(b.ada[-1].bias)
|
| 103 |
+
nn.init.zeros_(self.ada_out[-1].weight); nn.init.zeros_(self.ada_out[-1].bias)
|
| 104 |
+
nn.init.zeros_(self.head.weight); nn.init.zeros_(self.head.bias)
|
| 105 |
+
|
| 106 |
+
def patchify(self, x):
|
| 107 |
+
B, C, D, H, W = x.shape
|
| 108 |
+
p = self.patch
|
| 109 |
+
x = x.reshape(B, C, D // p, p, H // p, p, W // p, p)
|
| 110 |
+
x = x.permute(0, 2, 4, 6, 1, 3, 5, 7)
|
| 111 |
+
return x.reshape(B, (D // p) * (H // p) * (W // p), C * p * p * p)
|
| 112 |
+
|
| 113 |
+
def unpatchify(self, x):
|
| 114 |
+
B, N, _ = x.shape
|
| 115 |
+
p = self.patch
|
| 116 |
+
g = self.grid
|
| 117 |
+
C = self.vox_ch
|
| 118 |
+
x = x.reshape(B, g, g, g, C, p, p, p)
|
| 119 |
+
x = x.permute(0, 4, 1, 5, 2, 6, 3, 7)
|
| 120 |
+
return x.reshape(B, C, g * p, g * p, g * p)
|
| 121 |
+
|
| 122 |
+
def forward(self, x, t, text_seq, text_pool):
|
| 123 |
+
x = self.x_embed(self.patchify(x)) + self.pos
|
| 124 |
+
c = self.t_mlp(timestep_embedding(t, self.dim)) + self.text_pool(text_pool)
|
| 125 |
+
text = self.text_proj(text_seq)
|
| 126 |
+
for blk in self.blocks:
|
| 127 |
+
x = blk(x, c, text)
|
| 128 |
+
shift, scale = self.ada_out(c).chunk(2, dim=1)
|
| 129 |
+
x = modulate(self.norm_out(x), shift, scale)
|
| 130 |
+
x = self.head(x)
|
| 131 |
+
return self.unpatchify(x)
|
| 132 |
+
|
| 133 |
+
def num_params(self):
|
| 134 |
+
return sum(p.numel() for p in self.parameters())
|