NicoLab28 commited on
Commit
a0b6bf4
·
verified ·
1 Parent(s): e11248d

Name the proper-noun column for what it measures

Browse files
Files changed (1) hide show
  1. README.md +232 -288
README.md CHANGED
@@ -1,288 +1,232 @@
1
- ---
2
- license: mit
3
- tags:
4
- - comfyui
5
- - minimax-h3
6
- - text-to-video
7
- - qwen3-vl
8
- - text-encoder
9
- - multilingual
10
- base_model:
11
- - Comfy-Org/MiniMax-H3
12
- - Qwen/Qwen3-VL-4B-Instruct
13
- - Qwen/Qwen3-VL-8B-Instruct
14
- library_name: comfyui
15
- ---
16
-
17
- # ClipProj MiniMax H3 conditioning from a Qwen3-VL-4B or 8B
18
-
19
- **Projection matrices that let a small Qwen3-VL replace the Qwen3-VL-32B text encoder of MiniMax H3.**
20
- **15.0 GB → 4.6 GB**, with no change to the diffusion model, the VAEs or the sampler.
21
-
22
- <video controls width="360" src="https://huggingface.co/NicoLab28/ClipProj-MiniMax-H3/resolve/main/bench3.1/video/clipproj-v3.1-eleven-languages.mp4"></video>
23
-
24
- *Eleven languages, 88 seconds. For each one, the **smallest** file that matches the 32B — not the best
25
- one. Nine of the eleven run on a 4B.
26
- [Direct link](https://huggingface.co/NicoLab28/ClipProj-MiniMax-H3/resolve/main/bench3.1/video/clipproj-v3.1-eleven-languages.mp4)*
27
-
28
- > ⚠️ **The video looks rough, and that is on purpose.** It is rendered at 0.3 MP with 6 sampling steps —
29
- > the settings that made 297 renders affordable — then upscaled. The audio is tinny for the same reason,
30
- > **identically so on the 32B**, with the same 19 dB dip between 1 and 3 kHz. This is a pronunciation test,
31
- > not a showcase: it exists to let you hear *which words come out*, not how pretty the result is. Render at
32
- > your own settings and it will look like MiniMax H3 normally looks.
33
-
34
- Requires the custom node **[github.com/nicolab28/ComfyUI-ClipProj](https://github.com/nicolab28/ComfyUI-ClipProj)**,
35
- version 0.1.13 or later. The v3.1 files need no code changesame base as v3.
36
-
37
- ---
38
-
39
- ## Read this before the tables: these are metrics, not verdicts
40
-
41
- **I do not speak these eleven languages.** I cannot tell you whether a render sounds right, and I have not
42
- asked anyone who can. No native speaker has listened to any of the 297 speech renders behind this page.
43
-
44
- Every figure below is a **distance between two automatic transcriptions** what one machine wrote down
45
- from the reference, against what it wrote down from the projection. That is all it is.
46
-
47
- **What that captures.** Whether the same words and the same sounds come out. Two instruments are used
48
- because each is wrong in a known direction: **Whisper** has a language model inside and corrects a slurred
49
- word into the most probable real one, so it *under*-reports defects — a lower bound. **ZIPA** has no
50
- lexical decoder and counts every shift in realisation as an error, so it *over*-reports — an upper bound.
51
- What a listener would notice lies between them.
52
-
53
- **What it does not capture.** Prosody, rhythm, timbre, naturalness. A file scoring 98.8 here could still
54
- sound foreign to someone who speaks the language.
55
-
56
- **So read a score as "close to the 32B, according to this instrument" — never as "good".** If you speak one
57
- of these languages, your ear outranks every table here, and I would genuinely like to hear what it says.
58
-
59
- ---
60
-
61
- ## Files take a v3.1
62
-
63
- Put them in `ComfyUI/models/clip_projections/`.
64
-
65
- | File | Encoder | Head | Projection | Encoder + projection |
66
- |---|---|---|---|---|
67
- | **`mmh3-4b-ClipProj-v3.1`** | any Qwen3-VL-4B | ridge | 26 MB | **4.6 GB** |
68
- | `mmh3-4b-ClipProj-v3.1-mlp` | any Qwen3-VL-4B | residual | 481 MB | 5.1 GB |
69
- | `mmh3-8b-ClipProj-v3.1` | any Qwen3-VL-8B | ridge | 41 MB | 9.6 GB |
70
- | `mmh3-8b-ClipProj-v3.1-mlp` | any Qwen3-VL-8B | residual | 577 MB | 10.1 GB |
71
-
72
- **Start with `mmh3-4b-ClipProj-v3.1`.** Nine of the eleven benchmarked languages run on it, and nothing
73
- distinguishes it from the 32B in image generation. The 8B earns its extra 5 GB on Arabic, French, and
74
- proper nouns generally.
75
-
76
- The 8B matrices expect 4096 input dimensions instead of 2560; the node checks the width and refuses a
77
- mismatch. Every matrix works on any variant of its own size.
78
-
79
- **One format detail.** The two `-mlp` files carry **no `W` tensor** — they were trained without a linear
80
- path, so the residual network carries everything, and a matrix of zeros would cost 26 MB plus a 2560×5120
81
- matmul per token to add nothing. The node treats `W` as optional and reports `| residual only`. This is
82
- expected, not a truncated download.
83
-
84
- The earlier `-celeb` and v3 files remain available. There is no reason to prefer them: the benchmark
85
- separates v3 from v3.1 cleanly, and in the same direction on every metric.
86
-
87
- ---
88
-
89
- ## What changed in v3.1: giving every script its share
90
-
91
- v3 was calibrated on a corpus that was overwhelmingly English, with the other languages bolted on
92
- afterwards as a top-up. v3.1 **adds text and tagged prompts until every writing system carries roughly
93
- comparable weight** English excepted, since the prompt format itself is English.
94
-
95
- | Script | Languages | Tagged prompts | Raw text | Share of corpus |
96
- |---|---|---|---|---|
97
- | Latin — base | English: original corpus, image and register lots | *base* | | **68.3 %** |
98
- | Han | zh | | | 6.7 % |
99
- | Hangul | ko | | | 6.6 % |
100
- | Latin, accented | fr | | | 6.3 % |
101
- | Arabic | ar | | **(new)** | 4.1 % |
102
- | Latin | es, de, it, pt | | — | 1.3 % each |
103
- | Cyrillic | ru | ✓ | — | 1.3 % |
104
-
105
- The rule behind those numbers: **a script that inherits nothing from Latin needs raw text**; a Latin script
106
- only needs tagged prompts, because the alphabet is already covered. The one genuinely new lot is raw
107
- Arabic — 550 000 characters, the last non-Latin script still living on tagged prompts alone.
108
-
109
- Training also restarted from scratch rather than topping up: a network keeps the order it learned in, and
110
- lowering the learning rate on a top-up only arbitrates between preserving and correcting. Architecture and
111
- hyper-parameters are identical to v3 — `hidden 32768`, `depth 1`, `tap 24`, `lr 1e-3` — so what the
112
- benchmark compares is the corpus, not the recipe.
113
-
114
- ---
115
-
116
- ## The benchmark
117
-
118
- Everything below comes from **three seeds** 42, 100 000 and 100 000 000 — because a single draw cannot
119
- separate a real gap from chance. All of it is in [`bench3.1/`](./bench3.1): 297 speech renders, 405 image
120
- renders, the raw CSVs and the montage.
121
-
122
- ```
123
- bench3.1/
124
- README.md this measurement report in full
125
- audio/<lang>/ 297 FLAC 9 conditionings × 11 languages × 3 seeds
126
- video/langues/<lang>/ 99 renders, one per conditioning
127
- video/ the eleven-language montage
128
- images/brutes/ 75 PNG — 15 scenes × 5 conditionings
129
- images/planches/ 15 comparison sheets, five renders side by side
130
- mesures/ phonemes, words, image cosines, scores, prompts
131
- ```
132
-
133
- ### The reference is not perfection
134
-
135
- A cosine of 0.79 or "23 character errors" has no scale, and zero errors is not the target either, because
136
- **the 32B does not reproduce itself**. Change nothing but the seed:
137
-
138
- | | 32B against itself |
139
- |---|---|
140
- | Speech | **5.8 phonemes out of 75** (7.8 %) |
141
- | Image | **0.9552** SigLIP2 cosine (floor 0.5313) |
142
-
143
- That gap is the unit. Everything is normalised so **32B = 100**: at 100, swapping the encoder moves the
144
- output as much as changing the seed does.
145
-
146
- ### Results
147
-
148
- | Conditioning | Speech | ± | Image | Prompt | Proper noun |
149
- |---|---|---|---|---|---|
150
- | **32B** *(reference)* | **100.0** | — | **100.0** | **100.0** | 3/3 |
151
- | `8b-ClipProj-v3.1` | **98.8** | ±1.4 | 100.0 | 100.4 | 3/3 |
152
- | `8b-ClipProj-v3.1-mlp` | 98.2 | ±1.2 | 101.4 | **102.3** | 3/3 |
153
- | `4b-ClipProj-v3.1` | 97.9 | ±1.0 | 100.1 | 99.4 | 0/3 |
154
- | `4b-ClipProj-v3.1-mlp` | 97.8 | ±1.1 | 100.9 | 100.3 | 1/3 |
155
- | *the four v3 files* | *93.2 – 95.6* | | *99.4 – 102.4* | *99.0 – 100.7* | *0–3/3* |
156
-
157
- `±` is the spread across the three seeds. **Two files separated by less than that are not separated.**
158
-
159
- *Proper noun*: whether a city name spoken in the French line comes out intelligible, counted on each of the
160
- three seeds a single spot check on a known weak point, not a metric. Explained below.
161
-
162
- The raw counts, three units never added together PER over three seeds, WER and CER as Whisper hears
163
- them, single-seed:
164
-
165
- | | PER | | WER | CER |
166
- |---|---|---|---|---|
167
- | *32B against itself* | *≈193 / 2469* | *7.8 %* | — | — |
168
- | `8b-v3.1` | 211 / 2469 | 8.5 % | 10 / 174 | 14 / 869 |
169
- | `8b-v3.1-mlp` | 222 / 2469 | 9.0 % | 15 / 174 | 28 / 869 |
170
- | `4b-v3.1-mlp` | 230 / 2469 | 9.3 % | 13 / 174 | 23 / 869 |
171
- | `4b-v3.1` | 232 / 2469 | 9.4 % | 15 / 174 | 30 / 869 |
172
- | the four v3 | 281–341 / 2469 | 11.4–13.8 % | 17–23 / 174 | 29–46 / 869 |
173
-
174
- **What separates: the corpus.** The four v3.1 files land within one point of each other while ranging from
175
- 4.6 to 10.1 GB. The four v3 sit a clear notch below at identical sizes. A 4B v3.1 beats an 8B v3 by four
176
- points while weighing half as much.
177
-
178
- **What does not separate: everything else.** Not 4B against 8B on general pronunciation, not ridge against
179
- residual, and nothing at all in image generation — all nine conditionings, v3 included, sit between 99.4
180
- and 102.4 there, where the standard deviation within a single model is three to five times the entire
181
- spread between models.
182
-
183
- ### The one place 4B and 8B part company
184
-
185
- Proper nouns are the known weak point of a quantised encoder, so the French line was written around one:
186
- *"la lumière de **Marseille**"* — the name of a city. Six phonemes out of seventy, which a percentage
187
- drowns and an ear does not.
188
-
189
- | what comes out after *"de"* | | |
190
- |---|---|---|
191
- | 32B, and the three 8B files | `m a ʀ s ɛ j` | *Marseille* — correct on all three seeds |
192
- | `4b-v3.1` | `m a ʀ s ɛ ʀ ɛ` | a non-word, all three seeds |
193
- | `4b-v3` | `m a z ɛ ʀ` | a different non-word |
194
-
195
- Note how little it costs on the sentence as a whole: `4b-v3.1` mangles the name outright and pays **one**
196
- phoneme more than `8b-v3.1` over the full line. That is exactly why it cannot be a ranking criterion — the
197
- aggregate metrics cannot see it.
198
-
199
- Treat it for what it is: **one proper noun, in one language out of eleven, on three seeds**. But if your
200
- prompts lean on names of people or places, test both sizes before choosing.
201
-
202
- ### Full report
203
-
204
- [`bench3.1/README.md`](./bench3.1/README.md) carries the whole thing: per-language tables, the raw-text
205
- correlation that does *not* hold, why the phoneme metric misleads in Portuguese and Russian, the
206
- self-consistency measurements, and every limitation I know of.
207
-
208
- ---
209
-
210
- ## What this is, technically
211
-
212
- MiniMax H3 conditions on a Qwen3-VL-32B truncated to 50 layers — 15.0 GB in NVFP4 — solely to turn a
213
- prompt into a `[seq, 5120]` tensor. This repository provides a learned map so a much smaller Qwen3-VL can
214
- produce the same conditioning:
215
-
216
- ```
217
- cond = ((h - mean_in) / std_in) @ W * std_out + mean_out
218
- ```
219
-
220
- plus, in the `-mlp` files, the output of a residual network fed the same standardised input — and in the
221
- v3.1 residuals, *only* that network.
222
-
223
- It works because every Qwen3-VL shares the **same tokenizer** (151936 tokens): a prompt yields the same
224
- tokens at the same positions in both models, so a position-by-position mapping between hidden states can
225
- be learned. The matrix is fitted by plain **ridge regression** — no gradients, no epochs. Only the residual
226
- network is trained.
227
-
228
- ## Run the controls first
229
-
230
- Two control matrices prove the learned matrix is doing the work rather than the diffusion model. Same
231
- prompt, same seed, only the matrix changes:
232
-
233
- | Matrix | Output for *"a red ball on a wood table"* |
234
- |---|---|
235
- | `mmh3-ClipProj-control-zero` | a countryside landscape — the prompt is entirely ignored |
236
- | `mmh3-ClipProj-control-identity` | a golden object in flames — unusable |
237
- | a learned matrix | the red ball on a wood table |
238
-
239
- `‖W_identity‖ = 50.6` against `‖W_learned‖ = 52.4` — near-identical energy, so the difference is
240
- structural, not a matter of scale. **If the identity control ever looks fine, the learned matrix adds
241
- nothing, and you want to know that before trusting it.**
242
-
243
- ## Limitations
244
-
245
- **Three seeds fix the order of magnitude of the noise, not its tail.** Any gap under one point of score is
246
- not a result.
247
-
248
- **The cosine is blind to countable attributes.** A whole loaf and a halved loaf, same crust, same paper,
249
- same light, give the same vector to the fourth decimal. Image equivalence here means *global appearance*.
250
-
251
- **The phoneme metric is unreliable in Portuguese, Russian and Korean** — not the speech itself. The
252
- reference drifts by 17.0, 10.7 and 11.3 phonemes there between seeds, while Whisper transcribes the same
253
- renders with zero to three character errors.
254
-
255
- **Speech quality is deliberately poor**, as the video shows — 6 to 8 steps, identically for the 32B. The
256
- benchmark measures correctness of pronunciation, not fidelity of reproduction.
257
-
258
- **Quantisation costs facts**, which is the most likely explanation for the proper-noun gap.
259
-
260
- **Masks defeat identity**, and **counting is unreliable** — both true on the 32B too.
261
-
262
- ## Required models
263
-
264
- | Role | Model |
265
- |---|---|
266
- | Diffusion model + VAEs | [Comfy-Org/MiniMax-H3](https://huggingface.co/Comfy-Org/MiniMax-H3) |
267
- | Text encoder, 4B | any ComfyUI-format Qwen3-VL-4B |
268
- | Text encoder, 8B | any ComfyUI-format Qwen3-VL-8B |
269
-
270
- The 32B text encoder is **no longer needed** — that is the entire point.
271
-
272
- ## Licence and responsibility
273
-
274
- MIT, like the node. These matrices are derived from the activations of both models and their legal status
275
- is unclear; they are provided as-is, for research.
276
-
277
- - **Qwen3-VL** — Alibaba, Apache 2.0. Read its terms and acceptable-use policy.
278
- - **MiniMax H3** — custom licence. Read it before any use, particularly commercial.
279
-
280
- Not affiliated with, endorsed by, or connected to Alibaba / Qwen, MiniMax, or Comfy Org. You remain
281
- responsible for what you generate and for complying with the licences of every model you load.
282
-
283
- ## Credits
284
-
285
- Vibe-coded with **Anthropic Claude Code (Opus 5)**. Every number quoted was measured on this hardware,
286
- never estimated. Where a prediction lost to a measurement, the measurement won and the text was rewritten
287
- — which happened several times in this release, the largest being a single-seed ranking of the v3.1 files
288
- that dissolved entirely once the 32B's own variance was known.
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - comfyui
5
+ - minimax-h3
6
+ - text-to-video
7
+ - qwen3-vl
8
+ - text-encoder
9
+ base_model:
10
+ - Comfy-Org/MiniMax-H3
11
+ - Qwen/Qwen3-VL-4B-Instruct
12
+ library_name: comfyui
13
+ ---
14
+
15
+ # ClipProj — MiniMax H3 conditioning from a Qwen3-VL-4B or 8B
16
+
17
+ **Projection matrices that let a small Qwen3-VL replace the Qwen3-VL-32B text encoder of MiniMax H3.**
18
+
19
+ **15.7 GB 4.5 GB of VRAM**, with no change to the diffusion model, the VAEs or the sampler.
20
+
21
+ ---
22
+
23
+ ## v3.1 — new matrices, and a benchmark that finally has a scale
24
+
25
+ <video controls width="360" src="https://huggingface.co/NicoLab28/ClipProj-MiniMax-H3/resolve/main/bench3.1/video/clipproj-v3.1-eleven-languages.mp4"></video>
26
+
27
+ *Eleven languages, 88 seconds. For each one the **smallest** file that matches the 32B — not the best one. Nine of the eleven run on a 4B. [Direct link](https://huggingface.co/NicoLab28/ClipProj-MiniMax-H3/resolve/main/bench3.1/video/clipproj-v3.1-eleven-languages.mp4)*
28
+
29
+ > ⚠️ **The video looks and sounds rough, on purpose.** 0.3 MP, 6 sampling steps — the settings that made 297 renders affordable — then upscaled. The audio is tinny for the same reason, **identically so on the 32B**, with the same 19 dB dip between 1 and 3 kHz. This is a pronunciation test, not a showcase: it exists to let you hear *which words come out*.
30
+
31
+ **Four new files:** `mmh3-4b-ClipProj-v3.1`, `mmh3-4b-ClipProj-v3.1-mlp`, `mmh3-8b-ClipProj-v3.1`, `mmh3-8b-ClipProj-v3.1-mlp`. They load on node **0.1.13** with no code change same base as v3. Start with **`mmh3-4b-ClipProj-v3.1`**: 26 MB of projection, 4.6 GB with its encoder.
32
+
33
+ **What changed:** the calibration corpus now gives every writing system a comparable share instead of being overwhelmingly English, with raw Arabic text added. Phoneme errors drop **29 % overall**, 60 to 74 % on the European languages.
34
+
35
+ **What it is measured against:** the 32B does not reproduce itself. Change nothing but the seed and it re-pronounces **5.8 phonemes out of 75** differently. The four v3.1 files sit at 6.4 to 7.0 so swapping the encoder costs about what re-rolling the seed costs. Every figure is normalised against that variance rather than against zero, and on that scale nothing separates 4B from 8B, or ridge from residual.
36
+
37
+ **Full report** — three seeds, 297 speech renders, 405 image renders, method, limitations and all the raw data: **[bench3.1/README.md](./bench3.1/README.md)**
38
+
39
+ ---
40
+
41
+
42
+ > ⚠️ **Proof of concept — working, but a proof of concept.** It runs and produces good video, and every number below was measured on real hardware. Built and tested on a single setup (Windows 11, NVIDIA, ComfyUI 0.31.0) with deliberately limited exploration.
43
+
44
+ > **Arriving from a tutorial or an article?** Anything published before 11 August names files that have moved. `h3_qwen3vl_4b_tap24`, `h3_control_zero` and `h3_control_identity` are still here, one folder down in `obsolete/`, so nothing is lost. But take the current set instead: **`mmh3-4b-ClipProj-celeb-mlp`** for a Qwen3-VL-4B, **`mmh3-8b-ClipProj-celeb-mlp`** for an 8B. They are better on every measurement below, and they need node **0.1.4 or later**.
45
+
46
+ These files are useless on their own. They require the custom node:
47
+ **[github.com/nicolab28/ComfyUI-ClipProj](https://github.com/nicolab28/ComfyUI-ClipProj)**
48
+
49
+ ## Where this came from
50
+
51
+ I am not an ML researcher. I work in imaging, and programming is a tool and a hobby rather than my trade. This started as something to tinker with: I wanted to understand how a diffusion model actually uses its text encoder, and the only way I know how to understand something is to take it apart and see whether it still runs afterwards.
52
+
53
+ So the question was never "how do I save VRAM". It was "is this even possible at all". I expected it to fail. A linear map between two models that were never trained together, fitted in a single pass with no gradients and no learning rate, has no business producing usable video.
54
+
55
+ It did, and the first results were good enough that keeping them on my own disk seemed silly. That is the whole story, and it is why this is labelled a proof of concept rather than a tool: it was never designed as one.
56
+
57
+ It is also why there are so many measurements on the model card. Before showing this to anyone I had to convince myself I was not fooling myself, and most of what I tried along the way turned out to be wrong. Those attempts are written down as well, in [MEASUREMENTS.md](https://github.com/nicolab28/ComfyUI-ClipProj/blob/main/MEASUREMENTS.md) and [CALIBRATION.md](https://github.com/nicolab28/ComfyUI-ClipProj/blob/main/CALIBRATION.md).
58
+
59
+ ## Update to node 0.1.4, and re-download the `-mlp` matrices
60
+
61
+ **The `-mlp` matrices are now fp16 and half the size.** The residual network was published in fp32 and the node forced fp32 on load regardless of the file, so storing it in half precision would have halved the download and saved nothing at all in VRAM. Node 0.1.4 keeps a residual in whatever precision it was saved in, converting its inputs and outputs around it instead. Measured: 240 MB on the card instead of 480 for the 4B, 288 instead of 576 for the 8B. The files here have been replaced under the same names — re-download them, and take 0.1.4 with them, because an older node will load them and cast them straight back up to fp32.
62
+
63
+ Node 0.1.4 also frees the card **before** loading a replacement encoder rather than after, which matters if yours is tight enough that two encoders will not sit on it at once.
64
+
65
+ ## Also in 0.1.3
66
+
67
+ Two reasons, one of them silent.
68
+
69
+ **The `-mlp` matrices carry a residual network, and an older node ignores it without saying so.** It reads the matrix, finds keys it does not know, drops them, and applies the linear part alone. Nothing fails, nothing warns, and you end up judging the plain matrix while believing you tested the residual. Node 0.1.3 reads them.
70
+
71
+ **Everything is renamed.** The old `h3_qwen3vl_*` files have moved to `obsolete/` and the `.pt` copies are gone: opening a pickle executes code, which makes no sense for a file holding six tensors. If a workflow of yours names an old file, either point it at `obsolete/` or, better, switch to the new set.
72
+
73
+ ## What this is
74
+
75
+ MiniMax H3 conditions on a Qwen3-VL-32B truncated to 50 layers — 15.7 GB in NVFP4 — solely to turn a prompt into a `[seq, 5120]` tensor. This repository provides a learned map that lets a much smaller Qwen3-VL produce the same conditioning:
76
+
77
+ ```
78
+ cond = ((h - mean_in) / std_in) @ W * std_out + mean_out
79
+ ```
80
+
81
+ and, in the `-mlp` files, plus the output of a small residual network fed the same standardised input.
82
+
83
+ It works because every Qwen3-VL shares the **same tokenizer** (151936 tokens): a prompt yields the same tokens at the same positions in both models, so a position-by-position mapping between their hidden states can be learned. The matrix is fitted by plain **ridge regression** — no gradients, no epochs, no learning rate. The residual network is the only part that is trained.
84
+
85
+ ## Files
86
+
87
+ Put them in `ComfyUI/models/clip_projections/`.
88
+
89
+ **Start with `mmh3-8b-ClipProj-celeb-mlp` if you have the VRAM, `mmh3-4b-ClipProj-celeb-mlp` otherwise.**
90
+
91
+ | File | Encoder | Names covered | Residual | Test cosine |
92
+ |---|---|---|---|---|
93
+ | `mmh3-4b-ClipProj` | any Qwen3-VL-4B | no | no | 0.7169 |
94
+ | `mmh3-4b-ClipProj-mlp` | any Qwen3-VL-4B | no | yes | 0.7944 |
95
+ | `mmh3-4b-ClipProj-celeb` | any Qwen3-VL-4B | **yes** | no | 0.7095 |
96
+ | `mmh3-4b-ClipProj-celeb-mlp` | any Qwen3-VL-4B | **yes** | yes | 0.7930 |
97
+ | `mmh3-8b-ClipProj` | any Qwen3-VL-8B | no | no | 0.7528 |
98
+ | `mmh3-8b-ClipProj-mlp` | any Qwen3-VL-8B | no | yes | 0.7970 |
99
+ | `mmh3-8b-ClipProj-celeb` | any Qwen3-VL-8B | **yes** | no | 0.7466 |
100
+ | `mmh3-8b-ClipProj-celeb-mlp` | any Qwen3-VL-8B | **yes** | yes | **0.8037** |
101
+ | `mmh3-ClipProj-control-zero` | | control, run it once | | |
102
+ | `mmh3-ClipProj-control-identity` | | control, run it once | — | |
103
+
104
+ All eight are calibrated on the same general corpus and measured on the same held-out prompts, so the column is comparable across every row.
105
+
106
+ Every matrix works on **any variant of its own size**: the measured cosine gap between a bf16-calibrated matrix applied to an abliterated fp8 encoder is 0.0023. You do not need the exact checkpoint a matrix was calibrated on. The 8B matrices need an 8B encoder though — 4096 input dimensions instead of 2560 — and the node checks the width and refuses a mismatch.
107
+
108
+ ## Named people
109
+
110
+ **This is what changed in 0.1.3, and it was a corpus problem.**
111
+
112
+ The calibration corpus named a person on about 70 lines out of 8632, roughly 0.02 % of the training tokens. The directions of the hidden space that carry an identity were therefore constrained by nothing at all, and the fit put whatever minimised the error on landscape descriptions there. Named people came out as somebody else.
113
+
114
+ The `-celeb` matrices add 500 people, ranked by popularity, with five short prompts and two long ones each. What it buys and what it costs:
115
+
116
+ | | name tokens | rest of the sentence | general test set |
117
+ |---|---|---|---|
118
+ | without | 0.8265 | 0.9358 | 0.7944 |
119
+ | with | **0.8844** | **0.9516** | 0.7930 |
120
+
121
+ Seven thousandths of cosine on the general corpus, for six points on the tokens that carry an identity. The rest of the sentence improves too, because the celebrity prompts are short and the general corpus had nothing under fifteen words.
122
+
123
+ Two findings that decide how far this is worth pushing.
124
+
125
+ **Two contexts per person are enough.** Measured on contexts held out for people the matrix had seen: 0.9875 at two, 0.9945 at five, 0.9986 at twenty. Forty is a waste.
126
+
127
+ **Five hundred names generalise to names never seen.** A held-out band at popularity ranks 501 to 540, absent from every calibration, reconstructs at 0.8795 against 0.8844 for the covered ones. Covering 500 people does not teach 500 names; it teaches the map how to handle that region of the space. Going to several thousand would buy very little.
128
+
129
+ **What still fails is not the corpus.** Characters whose identity is a mask rather than a face come out as a stranger wearing the right costume. People whose fame predates the era when everything was photographed come out wrong or generic. And some names fail on the plain 32B too, so run the reference before blaming the projection — that check has overturned three of my own conclusions.
130
+
131
+ ## Where the calibration data comes from
132
+
133
+ The general corpus is [GokuScraper/seedance-2-prompts-datasets](https://huggingface.co/datasets/GokuScraper/seedance-2-prompts-datasets), filtered to prompts of fifteen words or more and deduplicated: 8632 lines, median 128 words. The 500 named people come from a TMDB export published on Kaggle, ranked by popularity, with transliterated names dropped beyond rank 1000.
134
+
135
+ Around each name, five short prompts are generated from templates, and two longer ones in MiniMax H3's section format are written by Mistral Small and Gemini Flash Lite, half each. Everything needed to rebuild the corpus is in the node's `calibration/` folder, including the system prompt the long prompts were written from.
136
+
137
+ ## The residual network
138
+
139
+ The `-mlp` files carry a `d_in → 16384 → 5120` network with a GELU, added to the matrix rather than replacing it. Its last layer is initialised to zero, so at the first step the model reproduces the matrix exactly and can only improve on it. It is worth 0.05 to 0.08 of cosine, four times what multiplying the corpus by eleven buys the linear map.
140
+
141
+ **Which of the two renders better is not settled.** The cosine does not predict it — that is the single most repeated lesson of this project. Try both on your own prompts.
142
+
143
+ Two things measured while building it. Width beats depth: at equal parameter count, two hidden layers of 8192 reach 0.7691 against 0.7944 for one layer of 16384. And a residual extrapolates worse than a matrix does — outside the corpus it saw, a linear map degrades gracefully while the network collapses.
144
+
145
+ ## Measured results
146
+
147
+ | | 4B | 8B |
148
+ |---|---|---|
149
+ | matrix, no names | 0.7169 | 0.7528 |
150
+ | matrix + residual | 0.7944 | 0.7970 |
151
+ | matrix, names covered | 0.7095 | 0.7466 |
152
+ | matrix + residual, names covered | 0.7930 | **0.8037** |
153
+
154
+ A cosine of 0.79 sounds poor and is not — the DiT tolerates far more than the metric suggests. What holds up in actual generation: simple prompts, structured multi-shot prompts with several distinct cuts and no bleed between them, fl2va with first and last frame, ref2va with a reference image, and since 0.1.3 ref2va with a reference video.
155
+
156
+ Fidelity does **not** collapse on short prompts: measured per-token cosine goes from 0.937 at 80 words to 0.908 at 2 words, once the attention sink is handled.
157
+
158
+ ## Speech
159
+
160
+ The first release lost non-English speech: a French line came out half Spanish, and the 8B put everything in English. That was the clearest regression and I could not explain it then.
161
+
162
+ With `mmh3-8b-ClipProj-celeb-mlp`, a three-shot clip carrying English, French and Spanish comes out like the 32B does, and the audio level gap measured against the reference has gone from 7.6 dB to 3.5.
163
+
164
+ Part of what was blamed on the projection was not the projection. A line that fills more than about two thirds of its shot comes out slurred whatever encoder produced the conditioning — the fix is a longer shot, not a better matrix. And MiniMax H3 expects speech wrapped in `<d>[Language] ...</d>` with a stable speaker id declared beforehand; without that, one voice with one accent is used for the whole clip. Neither of those is documented here because neither is ours, but both cost me a day.
165
+
166
+ ## Run the controls first
167
+
168
+ The two control matrices exist to prove the learned matrix is doing the work rather than the diffusion model. Same prompt, same seed, only the matrix changes:
169
+
170
+ | Matrix | Output for *"a red ball on a wood table"* |
171
+ |---|---|
172
+ | `mmh3-ClipProj-control-zero` | a countryside landscape the prompt is entirely ignored |
173
+ | `mmh3-ClipProj-control-identity` | a golden object in flames — unusable |
174
+ | a learned matrix | the red ball on a wood table |
175
+
176
+ `‖W_identity‖ = 50.6` against `‖W_learned‖ = 52.4` — near-identical energy, so the difference is structural, not a matter of scale.
177
+
178
+ **If the identity control ever looks fine, the learned matrix adds nothing and you want to know that before trusting it.**
179
+
180
+ ## What is in obsolete/
181
+
182
+ The previous matrices, kept because a comparison posted on r/StableDiffusion ran on them and the links have to keep working. They have no name coverage and are calibrated on a corpus thirty times smaller. There is no reason to prefer them.
183
+
184
+ Among them, the `CONDPROJ` pair, and the story is worth telling because the mistake was instructive.
185
+
186
+ The DiT does not consume the conditioning as it arrives: it first passes it through `condition_proj`, a `Linear(5120 → 5376)` feeding the token refiner. That layer's spectrum is very uneven a factor of 45 between the top and bottom deciles of its singular values, 52 % of the energy in 10 % of the directions. Plain ridge regression ignores this and spends as much effort on a direction the DiT will multiply by 0.10 as on one it will multiply by 37. Calibrating against the **output** of that layer instead, then mapping back through the pseudo-inverse, should therefore minimise the error the DiT actually sees. The cosine went from 0.697 to 0.845 on the 4B and 0.731 to 0.860 on the 8B.
187
+
188
+ Then I compared what the two matrices actually output:
189
+
190
+ ```
191
+ 4B CONDPROJ against unweighted, same corpus cosine 0.999998
192
+ 8B CONDPROJ against unweighted, same corpus cosine 0.999999
193
+ ```
194
+
195
+ They are the same function. Unregularised least squares is invariant to an invertible linear transform of the targets, so fitting in one space and mapping back recovers the same map; only the ridge penalty breaks that invariance, and with 37 851 training tokens against λ = 1000 it barely binds. The entire gain was an artefact of measuring in a different space.
196
+
197
+ *The idea came from u/stddealer on r/StableDiffusion, and it was a good one. The measurement is on me: I published the cosine before checking whether the matrix had changed at all.*
198
+
199
+ ## Known limitations
200
+
201
+ **Quantisation costs facts.** Comparing `int8_convrot` against `bf16` on factual recall shows errors appearing under quantisation. Fine for general use, worth knowing if your prompts lean on proper nouns.
202
+
203
+ **Masks defeat identity.** A character recognised by a costume rather than a face comes out as an unknown person in the right suit. No corpus fixes that, because the identity is not in the name's representation to begin with.
204
+
205
+ **Counting is unreliable, and not because of the projection.** Ask for three of something and you get four, on the 32B too. Enumerating works better than announcing a number.
206
+
207
+ ## Required models
208
+
209
+ | Role | Model |
210
+ |---|---|
211
+ | Diffusion model + VAEs | [Comfy-Org/MiniMax-H3](https://huggingface.co/Comfy-Org/MiniMax-H3) |
212
+ | Text encoder, 4B | [Comfy-Org/Krea-2](https://huggingface.co/Comfy-Org/Krea-2) `text_encoders/qwen3vl_4b_fp8_scaled.safetensors` |
213
+ | Text encoder, 8B | any ComfyUI-format Qwen3-VL-8B (the 8B matrices expect 4096 input dims) |
214
+
215
+ The 32B text encoder is **no longer needed** — that is the entire point.
216
+
217
+ ## Licence and responsibility
218
+
219
+ These matrices are released under **MIT**, like the node.
220
+
221
+ They are derived from the activations of both models, and their legal status is unclear. They are provided as-is, for research, with no claim of ownership over anything derived from the underlying models.
222
+
223
+ - **Qwen3-VL** is published by Alibaba under **Apache 2.0**. Read and comply with its terms and acceptable-use policy.
224
+ - **MiniMax H3** ships under a **custom licence**. Read it before any use, particularly commercial.
225
+
226
+ This project is **not affiliated with, endorsed by, or connected to** Alibaba / Qwen, MiniMax, or Comfy Org.
227
+
228
+ You remain responsible for what you generate and for complying with the licences of every model you load.
229
+
230
+ ## Credits
231
+
232
+ Vibe-coded with **Anthropic Claude Code (Opus 5)**. Every number quoted was measured on real hardware, not estimated: where a prediction turned out wrong, the measurement won and the text was corrected. Three claims in the previous version of this file were wrong and are corrected here.