ajh-code commited on
Commit
54152e6
·
verified ·
1 Parent(s): e78ba4b

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. .gitattributes +1 -34
  2. LICENSE +21 -0
  3. MANIFEST.json +418 -0
  4. README.md +208 -0
  5. THIRD_PARTY_NOTICES.md +34 -0
  6. build_manifest.py +202 -0
  7. build_native.sh +61 -0
  8. generate.py +478 -0
  9. licenses/MAGE-MIT.txt +21 -0
  10. licenses/QWEN-APACHE-2.0.txt +202 -0
  11. model_index.json +30 -0
  12. requirements.txt +20 -0
  13. run_validation.py +72 -0
  14. runtime/nvfp4_linear.cu +895 -0
  15. runtime/nvfp4_linear.h +66 -0
  16. runtime/packed_artifact.py +1068 -0
  17. runtime/packed_nvfp4_linear.py +485 -0
  18. runtime/quant_text_encoder.py +376 -0
  19. runtime/sm120_linear_op.cpp +358 -0
  20. runtime/standard_transformer.py +214 -0
  21. runtime/torch_ops_native.py +233 -0
  22. scheduler/scheduler_config.json +7 -0
  23. text_encoder/chat_template.json +4 -0
  24. text_encoder/config.json +71 -0
  25. text_encoder/generation_config.json +14 -0
  26. text_encoder/merges.txt +0 -0
  27. text_encoder/preprocessor_config.json +21 -0
  28. text_encoder/tokenizer.json +0 -0
  29. text_encoder/tokenizer_config.json +239 -0
  30. text_encoder/video_preprocessor_config.json +21 -0
  31. text_encoder/vocab.json +0 -0
  32. transformer/config.json +92 -0
  33. transformer/diffusion_pytorch_model.safetensors.index.json +501 -0
  34. transformer/nvfp4_metadata.json +1648 -0
  35. vae/config.json +6 -0
  36. validate_release.py +89 -0
  37. vendor/mage_flow/__init__.py +18 -0
  38. vendor/mage_flow/app.py +199 -0
  39. vendor/mage_flow/inference.py +161 -0
  40. vendor/mage_flow/models/__init__.py +3 -0
  41. vendor/mage_flow/models/mage_flow.py +364 -0
  42. vendor/mage_flow/models/modules/__init__.py +0 -0
  43. vendor/mage_flow/models/modules/_attn_backend.py +229 -0
  44. vendor/mage_flow/models/modules/mage_latent.py +119 -0
  45. vendor/mage_flow/models/modules/mage_layers.py +725 -0
  46. vendor/mage_flow/models/modules/mage_text.py +259 -0
  47. vendor/mage_flow/models/modules/mage_vae.py +651 -0
  48. vendor/mage_flow/models/modules/text_encoder.py +707 -0
  49. vendor/mage_flow/models/utils.py +175 -0
  50. vendor/mage_flow/pipeline.py +762 -0
.gitattributes CHANGED
@@ -1,35 +1,2 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
  *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz 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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  *.safetensors filter=lfs diff=lfs merge=lfs -text
2
+ *.so filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Microsoft
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
MANIFEST.json ADDED
@@ -0,0 +1,418 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "base_models": {
3
+ "mage_flow": "microsoft/Mage-Flow",
4
+ "mage_source_commit": "df7f84d9f8fc991d189d929f03cff623b430a4a2",
5
+ "mage_transformer_sha256": "04431abbc3acd1a5b86a7f77269f8e77e05a6cac3b21f35e1aef7493f6ab4934",
6
+ "qwen": "Qwen/Qwen3-VL-4B-Instruct",
7
+ "qwen_quantized": "InsecureErasure/Qwen3-VL-4B-Instruct-NVFP4",
8
+ "qwen_quantized_revision": "cf080d6af667f5f4949295bd09a1f72a218fe0ee"
9
+ },
10
+ "evidence": {
11
+ "combined_frozen_val07": {
12
+ "all_seven_gates_passed": true,
13
+ "image_sha256": "857c1663636cd775ea05ffe37a98ac47ab7cd4e5362dd82c21ae1b53f7d71c3a",
14
+ "result_sha256": "67afd8ca0c00fff949bfc4c8b660ca864617839cbc01231a7157769a964287c7"
15
+ },
16
+ "legacy_portable_package_generation": {
17
+ "image_sha256": "2c1f2853e37a2ede976f24bc9b7eef172aed7665266c615401ea189fcbcee19f",
18
+ "report_sha256": "2877697cdf8d469f689aa68751d24c8f25288388f682e61355234e82fae35eda",
19
+ "status": "success"
20
+ },
21
+ "native_text_quality_gate_result_sha256": "4a31a2c0eb4cac7401224eaeab23577bd31cc5ef69f6edec5289757f113373e6",
22
+ "standard_huggingface_layout_generation": {
23
+ "bf16_target_weight_reads": 0,
24
+ "image_sha256": "2c1f2853e37a2ede976f24bc9b7eef172aed7665266c615401ea189fcbcee19f",
25
+ "layout": "complete_huggingface_sharded_repository",
26
+ "loaded_non_target_tensor_count": 301,
27
+ "loaded_nvfp4_projection_count": 48,
28
+ "report_sha256": "61c8b651a5a9814c20fa4a99719e0b0a6caa827f61c0865993d916de25e6507d",
29
+ "status": "success",
30
+ "transformer_checkpoint_tensor_count": 493,
31
+ "transformer_shard_count": 4
32
+ },
33
+ "transformer_quality_results": {
34
+ "VAL-01": "82fadbc86ffb980a503c61ba50bfae25fd302503cf9ebb807029aacdc484f65a",
35
+ "VAL-07": "2476505becf3cec14d174c35b42a92ae180cea1173ebe376d0d40f530a0292ce",
36
+ "VAL-09": "c5d25ab78790295a0945b9783e146ff114991cc72af80a524d083034696110fc",
37
+ "VAL-13": "d2f5b3e7b3be8d430fbd8ca47abdfa405d130f080d45a51fe5ab90fde309a07e"
38
+ }
39
+ },
40
+ "files": [
41
+ {
42
+ "path": ".gitattributes",
43
+ "role": "release support file",
44
+ "sha256": "0b59beee6f0780aa6d1da36ef06df3b64752a757d2c37b1abf1941875eddf10a",
45
+ "size": 91
46
+ },
47
+ {
48
+ "path": "LICENSE",
49
+ "role": "license or third-party attribution",
50
+ "sha256": "275b4dd619de4e16a017b10d0beec72abbbbf14ee8a2fc68f8bdb398e821f623",
51
+ "size": 1066
52
+ },
53
+ {
54
+ "path": "README.md",
55
+ "role": "Hugging Face model card and usage guide",
56
+ "sha256": "3333284289f34219f30a3c142d9b1ca9e2682604b161a92b5bc35d07fbc1a267",
57
+ "size": 6581
58
+ },
59
+ {
60
+ "path": "THIRD_PARTY_NOTICES.md",
61
+ "role": "license or third-party attribution",
62
+ "sha256": "848e6132befdb77dada57b9d077b19ad57d65d02b3df63161d3c8d6ee9a11e99",
63
+ "size": 1336
64
+ },
65
+ {
66
+ "path": "build_manifest.py",
67
+ "role": "release manifest builder",
68
+ "sha256": "82ea93365ae63418268f28cdf3fc8f2360ab5a3ba19da2a42ea914e832a9c45d",
69
+ "size": 8056
70
+ },
71
+ {
72
+ "path": "build_native.sh",
73
+ "role": "project-local native runtime build helper",
74
+ "sha256": "7e1bccc38c982b05c0ad027c3faace9c0d5783e638e75ff4875430d5ab34445d",
75
+ "size": 1803
76
+ },
77
+ {
78
+ "path": "examples/VAL-07_combined_native.png",
79
+ "role": "validated combined NVFP4 example output",
80
+ "sha256": "857c1663636cd775ea05ffe37a98ac47ab7cd4e5362dd82c21ae1b53f7d71c3a",
81
+ "size": 174472
82
+ },
83
+ {
84
+ "path": "generate.py",
85
+ "role": "portable single-image inference entry point",
86
+ "sha256": "aaa7aa6a101cbdf5d2bfde1354b162df58c7fa50309d40514d8b137c79485d23",
87
+ "size": 14516
88
+ },
89
+ {
90
+ "path": "licenses/MAGE-MIT.txt",
91
+ "role": "license or third-party attribution",
92
+ "sha256": "275b4dd619de4e16a017b10d0beec72abbbbf14ee8a2fc68f8bdb398e821f623",
93
+ "size": 1066
94
+ },
95
+ {
96
+ "path": "licenses/QWEN-APACHE-2.0.txt",
97
+ "role": "license or third-party attribution",
98
+ "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
99
+ "size": 11358
100
+ },
101
+ {
102
+ "path": "model_index.json",
103
+ "role": "Hugging Face Diffusers metadata and download-tracking query file",
104
+ "sha256": "6d07c7d0d05992544b66126adff35f21e8c9036f8b0b7ca623c92d7bd97c2213",
105
+ "size": 614
106
+ },
107
+ {
108
+ "path": "requirements.txt",
109
+ "role": "release support file",
110
+ "sha256": "2a16308e6034f1b06d60afd70bcd488f0f79b9499c9ff0acbf1c028652320f42",
111
+ "size": 485
112
+ },
113
+ {
114
+ "path": "run_validation.py",
115
+ "role": "portable frozen-prompt validation entry point",
116
+ "sha256": "47ea74991108f4f9ce519ae00ebbfe36d76ebf775ed598a9b539dc3112f7cd3a",
117
+ "size": 2044
118
+ },
119
+ {
120
+ "path": "runtime/libmage_nvfp4_linear.so",
121
+ "role": "native runtime, loader, source, or Python integration",
122
+ "sha256": "d2f7d62d26b4ae3babb7791aee8e0d95ad4794ac9c1d22146c974784381ada48",
123
+ "size": 1158440
124
+ },
125
+ {
126
+ "path": "runtime/libmage_nvfp4_torch_op.so",
127
+ "role": "native runtime, loader, source, or Python integration",
128
+ "sha256": "d0cd7bbde23fa8187d28cda21ad74b79696bf607d40e47a534922670fe597ad4",
129
+ "size": 167176
130
+ },
131
+ {
132
+ "path": "runtime/nvfp4_linear.cu",
133
+ "role": "native runtime, loader, source, or Python integration",
134
+ "sha256": "060697d4784be3e3461d38ef6ff24128f47a4322a05e5463f8687796b03bda9f",
135
+ "size": 32166
136
+ },
137
+ {
138
+ "path": "runtime/nvfp4_linear.h",
139
+ "role": "native runtime, loader, source, or Python integration",
140
+ "sha256": "7eba9c6b155730fad23b86314903d5cd96ee4430daf2d45706288fc7a9f5ee09",
141
+ "size": 2231
142
+ },
143
+ {
144
+ "path": "runtime/packed_artifact.py",
145
+ "role": "native runtime, loader, source, or Python integration",
146
+ "sha256": "6f3b3220421d42a19573e852641dff00f75a6d7ee87975bd3d82c995ee86ba05",
147
+ "size": 40672
148
+ },
149
+ {
150
+ "path": "runtime/packed_nvfp4_linear.py",
151
+ "role": "native runtime, loader, source, or Python integration",
152
+ "sha256": "65a98172fedc59023c80c55c723956746c13e14f3783bdf687af39cdf0ee1397",
153
+ "size": 17469
154
+ },
155
+ {
156
+ "path": "runtime/quant_text_encoder.py",
157
+ "role": "native runtime, loader, source, or Python integration",
158
+ "sha256": "d5f38363256d67dee3532531b4a4ae080fa736e1bdd3499e615cb6142bcf808a",
159
+ "size": 13452
160
+ },
161
+ {
162
+ "path": "runtime/sm120_linear_op.cpp",
163
+ "role": "native runtime, loader, source, or Python integration",
164
+ "sha256": "a53e1e83df95922eaa384503ce5c5bab31ef65becfcf3e0ac623b1f0868d278a",
165
+ "size": 12179
166
+ },
167
+ {
168
+ "path": "runtime/standard_transformer.py",
169
+ "role": "native runtime, loader, source, or Python integration",
170
+ "sha256": "d1a0b25ddace31569aeff166cb8d1b2a7a016ee50827ab1f9dd0ae476fc96ba1",
171
+ "size": 7891
172
+ },
173
+ {
174
+ "path": "runtime/torch_ops_native.py",
175
+ "role": "native runtime, loader, source, or Python integration",
176
+ "sha256": "842e0f0f14faca41d8801ff52d435e30d180e34907b81cbbddc5aff6dfab1829",
177
+ "size": 7980
178
+ },
179
+ {
180
+ "path": "scheduler/scheduler_config.json",
181
+ "role": "Mage FlowMatch scheduler component",
182
+ "sha256": "438fd8bcf254740e5d3f3e9800bbd9c571e342ab87885388d1505b7531c69c02",
183
+ "size": 169
184
+ },
185
+ {
186
+ "path": "text_encoder/chat_template.json",
187
+ "role": "packaged Qwen3-VL configuration or tokenizer asset",
188
+ "sha256": "6f8a6a55027e3da5160105556cda5dd69f6423f1c32645f6730d32de7773d0c4",
189
+ "size": 5502
190
+ },
191
+ {
192
+ "path": "text_encoder/config.json",
193
+ "role": "packaged Qwen3-VL configuration or tokenizer asset",
194
+ "sha256": "d9e81f6f1d8d9ea613d55599041c6f579fbf4a9e9e0eacc9dbdb5e2300b1c281",
195
+ "size": 1789
196
+ },
197
+ {
198
+ "path": "text_encoder/generation_config.json",
199
+ "role": "packaged Qwen3-VL configuration or tokenizer asset",
200
+ "sha256": "8469742d1fce0de951c8909b26a2c0c0d8490837ce476efb114da9e0cefc4d44",
201
+ "size": 269
202
+ },
203
+ {
204
+ "path": "text_encoder/merges.txt",
205
+ "role": "packaged Qwen3-VL configuration or tokenizer asset",
206
+ "sha256": "599bab54075088774b1733fde865d5bd747cbcc7a547c5bc12610e874e26f5e3",
207
+ "size": 1671839
208
+ },
209
+ {
210
+ "path": "text_encoder/model.safetensors",
211
+ "role": "complete mixed NVFP4/FP8 Qwen3-VL text checkpoint",
212
+ "sha256": "719906b435800757d22013d3d475a4853d59b779b022669fa8b8a193b85d0f41",
213
+ "size": 4031376064
214
+ },
215
+ {
216
+ "path": "text_encoder/preprocessor_config.json",
217
+ "role": "packaged Qwen3-VL configuration or tokenizer asset",
218
+ "sha256": "27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516",
219
+ "size": 390
220
+ },
221
+ {
222
+ "path": "text_encoder/tokenizer.json",
223
+ "role": "packaged Qwen3-VL configuration or tokenizer asset",
224
+ "sha256": "a5d85b6dcc535e6b93115a9ef287e6132fdbf30270da6218194ba742261173c7",
225
+ "size": 7032403
226
+ },
227
+ {
228
+ "path": "text_encoder/tokenizer_config.json",
229
+ "role": "packaged Qwen3-VL configuration or tokenizer asset",
230
+ "sha256": "c2da771801886ad9ae98181793ffd3dfb7f1af30f6f7c6a4e15d7dbba52e2399",
231
+ "size": 10868
232
+ },
233
+ {
234
+ "path": "text_encoder/video_preprocessor_config.json",
235
+ "role": "packaged Qwen3-VL configuration or tokenizer asset",
236
+ "sha256": "7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13",
237
+ "size": 385
238
+ },
239
+ {
240
+ "path": "text_encoder/vocab.json",
241
+ "role": "packaged Qwen3-VL configuration or tokenizer asset",
242
+ "sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910",
243
+ "size": 2776833
244
+ },
245
+ {
246
+ "path": "transformer/config.json",
247
+ "role": "Mage transformer component configuration, index, or quantization metadata",
248
+ "sha256": "7520e71b0a4965f23e54308939853229fe1c32fbd10c69aac5adaead24d21475",
249
+ "size": 3194
250
+ },
251
+ {
252
+ "path": "transformer/diffusion_pytorch_model-00001-of-00004.safetensors",
253
+ "role": "complete sharded Mage transformer checkpoint with native NVFP4 modules",
254
+ "sha256": "040143f2dfc32db4220aabe46d98971e1701c83a566ccdb39e2553218f35f0dd",
255
+ "size": 1797002592
256
+ },
257
+ {
258
+ "path": "transformer/diffusion_pytorch_model-00002-of-00004.safetensors",
259
+ "role": "complete sharded Mage transformer checkpoint with native NVFP4 modules",
260
+ "sha256": "d7116264ef3cf0721c197ff4012de319b844abec1a43df1ade7d8868ce43f4a9",
261
+ "size": 1850448056
262
+ },
263
+ {
264
+ "path": "transformer/diffusion_pytorch_model-00003-of-00004.safetensors",
265
+ "role": "complete sharded Mage transformer checkpoint with native NVFP4 modules",
266
+ "sha256": "7534b2e25cf0e90a2bb0cca9d77f6cb2dc29198b2e387bf36cd59878bf9559b4",
267
+ "size": 1850448056
268
+ },
269
+ {
270
+ "path": "transformer/diffusion_pytorch_model-00004-of-00004.safetensors",
271
+ "role": "complete sharded Mage transformer checkpoint with native NVFP4 modules",
272
+ "sha256": "05a975382c7bada173c01f6005da16d7c78eaba771d554f0551934a25f1836d6",
273
+ "size": 128986568
274
+ },
275
+ {
276
+ "path": "transformer/diffusion_pytorch_model.safetensors.index.json",
277
+ "role": "Mage transformer component configuration, index, or quantization metadata",
278
+ "sha256": "6fc021886f29df126c3ff64cd61287869a2b9b3d142fee6084f81171cbdc90d8",
279
+ "size": 51641
280
+ },
281
+ {
282
+ "path": "transformer/nvfp4_metadata.json",
283
+ "role": "Mage transformer component configuration, index, or quantization metadata",
284
+ "sha256": "8aa6f8494dacd10f23e48e64ca6bdc0cc05754dffadd746bd6f500aa08c745fa",
285
+ "size": 71892
286
+ },
287
+ {
288
+ "path": "vae/config.json",
289
+ "role": "complete Mage VAE component",
290
+ "sha256": "abd124d603d6c6a03e9d0f2aa6d113b8c4afda0738400bdf2f99240aeaeaff76",
291
+ "size": 112
292
+ },
293
+ {
294
+ "path": "vae/diffusion_pytorch_model.safetensors",
295
+ "role": "complete Mage VAE component",
296
+ "sha256": "34e076dc1e8a15321e1e07be5111d59cf16dd10b804b7c7e20b4de29013427e0",
297
+ "size": 345053056
298
+ },
299
+ {
300
+ "path": "validate_release.py",
301
+ "role": "package hash validator",
302
+ "sha256": "9c14818d8939f880be8702222d0345ab0af44fbb8fefa98600d414e2c469eb75",
303
+ "size": 2877
304
+ },
305
+ {
306
+ "path": "vendor/mage_flow/__init__.py",
307
+ "role": "pinned Microsoft Mage inference source",
308
+ "sha256": "0709764f182b55fdec6b8195a4d18640fe0ffef3785d3977d8ebc29905de7489",
309
+ "size": 367
310
+ },
311
+ {
312
+ "path": "vendor/mage_flow/app.py",
313
+ "role": "pinned Microsoft Mage inference source",
314
+ "sha256": "ac0597feddf1b6c5aaa2f18cc3ebb3e690dc42232462ac778af45044df41435e",
315
+ "size": 9004
316
+ },
317
+ {
318
+ "path": "vendor/mage_flow/inference.py",
319
+ "role": "pinned Microsoft Mage inference source",
320
+ "sha256": "0a1e196f784f4daf4b4d1607cade1d066908341e0e444aa29fcea3967a8c1a3f",
321
+ "size": 7181
322
+ },
323
+ {
324
+ "path": "vendor/mage_flow/models/__init__.py",
325
+ "role": "pinned Microsoft Mage inference source",
326
+ "sha256": "7e5ba07fdb01f4a5912eaeb3afbe9d4ccc4e391e7de4cdc11865e5aa911ddfd5",
327
+ "size": 56
328
+ },
329
+ {
330
+ "path": "vendor/mage_flow/models/mage_flow.py",
331
+ "role": "pinned Microsoft Mage inference source",
332
+ "sha256": "59b6e1bee7f95a7fd2fa7bd9e765966832951e2bb184b5ae27283884997b845f",
333
+ "size": 15991
334
+ },
335
+ {
336
+ "path": "vendor/mage_flow/models/modules/__init__.py",
337
+ "role": "pinned Microsoft Mage inference source",
338
+ "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
339
+ "size": 0
340
+ },
341
+ {
342
+ "path": "vendor/mage_flow/models/modules/_attn_backend.py",
343
+ "role": "pinned Microsoft Mage inference source",
344
+ "sha256": "7d652eeb2ab39e37554e3d62859c74edeffc8dce70b2c15687060c259647b559",
345
+ "size": 7946
346
+ },
347
+ {
348
+ "path": "vendor/mage_flow/models/modules/mage_latent.py",
349
+ "role": "pinned Microsoft Mage inference source",
350
+ "sha256": "9ffd7c68b6053ad37f6bf0925d8d94dcd72af751448ee0228ec13018fedf54e5",
351
+ "size": 4118
352
+ },
353
+ {
354
+ "path": "vendor/mage_flow/models/modules/mage_layers.py",
355
+ "role": "pinned Microsoft Mage inference source",
356
+ "sha256": "4b198343b8929f48a0a14d388502c81f54be17b222f6831303d2da6a91f33a62",
357
+ "size": 30418
358
+ },
359
+ {
360
+ "path": "vendor/mage_flow/models/modules/mage_text.py",
361
+ "role": "pinned Microsoft Mage inference source",
362
+ "sha256": "eed7846d02bee28ebf7a1fb45db7d9f621dd66e702e761848d67bed426c1c4a7",
363
+ "size": 21247
364
+ },
365
+ {
366
+ "path": "vendor/mage_flow/models/modules/mage_vae.py",
367
+ "role": "pinned Microsoft Mage inference source",
368
+ "sha256": "64f4d7041003e416bc2f4fac5bbf8aabf2e7c798ad106682c34332ba347b0ef9",
369
+ "size": 25112
370
+ },
371
+ {
372
+ "path": "vendor/mage_flow/models/modules/text_encoder.py",
373
+ "role": "pinned Microsoft Mage inference source",
374
+ "sha256": "65e490ce35fbe4d4057f115be2ab8731e01ea44b618c10aabac80d51fc38ef81",
375
+ "size": 29405
376
+ },
377
+ {
378
+ "path": "vendor/mage_flow/models/utils.py",
379
+ "role": "pinned Microsoft Mage inference source",
380
+ "sha256": "0f242ca7a77e0f85b5985b5299304aa0786737de1ba414c7f8f47b8d664dbca8",
381
+ "size": 6649
382
+ },
383
+ {
384
+ "path": "vendor/mage_flow/pipeline.py",
385
+ "role": "pinned Microsoft Mage inference source",
386
+ "sha256": "b9fc57018570372dd3404a733e19b918a359188dd9f5ef7817c6b30969fc13db",
387
+ "size": 37089
388
+ }
389
+ ],
390
+ "measurements": {
391
+ "combined_text_stage_peak_allocated_bytes": 10437891584,
392
+ "combined_val07_latent_cosine": 0.9339298064776214,
393
+ "combined_val07_latent_nrmse": 0.36180871546574084,
394
+ "combined_val07_pixel_cosine": 0.9927827428055002,
395
+ "combined_val07_pixel_nrmse": 0.12000220627369493,
396
+ "full_generation_speed_claim_ready": false,
397
+ "text_encoder_allocated_saving_bytes": 4834599424,
398
+ "text_encoder_allocated_saving_gib": 4.5025715827941895,
399
+ "transformer_allocated_saving_bytes": 2604638208,
400
+ "transformer_allocated_saving_gib": 2.4257583618164062
401
+ },
402
+ "policy": {
403
+ "component_layout": "complete_huggingface_sharded_repository",
404
+ "mage_transformer": {
405
+ "format": "native resident W4A4 NVFP4",
406
+ "target_bf16_source_reads": 0,
407
+ "target_count": 48
408
+ },
409
+ "native_compute_capability": "12.0",
410
+ "qwen_text_encoder": {
411
+ "bf16_policy": "blocks 0/35, embeddings, norms, biases, vision tower",
412
+ "fp8_projection_count": 14,
413
+ "nvfp4_projection_count": 224
414
+ }
415
+ },
416
+ "release_status": "validated-standard-huggingface-candidate",
417
+ "schema_version": "mage-flow-nvfp4-huggingface-release-v1"
418
+ }
README.md ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ library_name: diffusers
4
+ pipeline_tag: text-to-image
5
+ base_model: microsoft/Mage-Flow
6
+ base_model_relation: quantized
7
+ tags:
8
+ - ajh
9
+ - mage-flow
10
+ - mage-flow-nvfp4-ajh
11
+ - nvfp4
12
+ - blackwell
13
+ - qwen3-vl
14
+ - text-to-image
15
+ - quantization
16
+ ---
17
+
18
+ # Mage-Flow-NVFP4-AJH
19
+
20
+ **Mage-Flow-NVFP4-AJH** is a portable, runnable NVFP4 package for
21
+ [`microsoft/Mage-Flow`](https://huggingface.co/microsoft/Mage-Flow).
22
+ It combines a native NVFP4 Mage transformer with a complete mixed
23
+ NVFP4/FP8 Qwen3-VL text encoder.
24
+
25
+ This is a complete Hugging Face component-layout repository, not an overlay:
26
+
27
+ ```text
28
+ model_index.json
29
+ transformer/
30
+ config.json
31
+ diffusion_pytorch_model-00001-of-00004.safetensors
32
+ diffusion_pytorch_model.safetensors.index.json
33
+ text_encoder/
34
+ config.json
35
+ model.safetensors
36
+ vae/
37
+ scheduler/
38
+ ```
39
+
40
+ The transformer shards include every retained BF16 tensor alongside the
41
+ NVFP4 module state. The VAE, scheduler, text-encoder configuration, tokenizer,
42
+ and processor are also included. Running the downloaded repository does not
43
+ fetch BF16 weights from the base model.
44
+
45
+ ## Quantization policy
46
+
47
+ Mage transformer:
48
+
49
+ - 48 image/text MLP up/down projections use resident W4A4 NVFP4.
50
+ - Attention, modulation, normalization, and all non-target tensors remain
51
+ BF16.
52
+ - The 48 original BF16 target weight matrices are absent. Their biases remain
53
+ BF16 and are stored normally inside the quantized modules.
54
+ - The complete transformer is stored as four indexed standard Safetensors
55
+ shards. Packed NVFP4 buffers use their module state-dict names:
56
+ `packed_weight`, `weight_scales`, `weight_scale`, and `bias`.
57
+
58
+ Qwen3-VL text encoder:
59
+
60
+ - Blocks 2–33: 224 NVFP4 projections.
61
+ - Blocks 1 and 34: 14 FP8 projections.
62
+ - Blocks 0 and 35, embeddings, norms, biases, and the vision tower remain
63
+ BF16.
64
+ - Packaged text checkpoint size: `4,031,376,064` bytes.
65
+ - The packaged loader constructs the model directly from this file: 475
66
+ non-quantized tensors and 238 packed projections, with no unresolved meta
67
+ tensors.
68
+
69
+ ## Requirements
70
+
71
+ The prebuilt runtime was tested on:
72
+
73
+ - NVIDIA RTX 50-series / SM120
74
+ - Linux x86-64
75
+ - CUDA 13.1
76
+ - Python 3.11
77
+ - PyTorch `2.13.0+cu130`
78
+ - `comfy-kitchen==0.2.22`
79
+ - `flash-attn==2.8.3`
80
+
81
+ Create a local environment:
82
+
83
+ ```bash
84
+ python3.11 -m venv .venv
85
+ source .venv/bin/activate
86
+ python -m pip install --upgrade pip
87
+ python -m pip install -r requirements.txt
88
+ CUDA_HOME=/usr/local/cuda-13.1 \
89
+ python -m pip install --no-build-isolation flash-attn==2.8.3
90
+ ```
91
+
92
+ The included binaries are for the exact tested stack. Rebuild them after
93
+ changing PyTorch, CUDA, or the C++ ABI:
94
+
95
+ ```bash
96
+ CUDA_HOME=/usr/local/cuda-13.1 \
97
+ PYTHON_BIN="$PWD/.venv/bin/python" \
98
+ ./build_native.sh
99
+ ```
100
+
101
+ ## Generate an image
102
+
103
+ Expose exactly one SM120 GPU:
104
+
105
+ ```bash
106
+ CUDA_VISIBLE_DEVICES=0 .venv/bin/python generate.py \
107
+ --prompt 'A detailed watercolor fox reading under an old oak tree' \
108
+ --output fox.png \
109
+ --height 1024 \
110
+ --width 1024 \
111
+ --steps 20 \
112
+ --seed 1
113
+ ```
114
+
115
+ When running from another copy of the scripts, `--model` also accepts the Hub
116
+ repository id or a downloaded standard-layout directory:
117
+
118
+ ```bash
119
+ CUDA_VISIBLE_DEVICES=0 .venv/bin/python generate.py \
120
+ --model ajh-code/Mage-Flow-NVFP4-AJH \
121
+ --prompt 'A lighthouse poster reading "ARCTIC LOOP"' \
122
+ --output lighthouse.png
123
+ ```
124
+
125
+ The command refuses to overwrite an existing output and writes a companion
126
+ JSON report containing coverage, memory, timing, and environment information.
127
+
128
+ ## Measured results
129
+
130
+ Transformer:
131
+
132
+ - Loaded packed transformer allocation: `5,628,438,016` bytes.
133
+ - Measured transformer allocation saving: `2,604,638,208` bytes
134
+ (`2.4258 GiB`).
135
+ - Target BF16 source reads: `0`.
136
+ - Representative real up-projection: `3.26x` eager and `3.25x` compiled
137
+ speedup over BF16.
138
+
139
+ Text encoder:
140
+
141
+ - BF16 language-stack residency: `7.5453 GiB`.
142
+ - Packed mixed residency: `3.0427 GiB`.
143
+ - Measured saving: `4.5026 GiB`.
144
+
145
+ Combined validation on an RTX 5060 Ti 16 GB:
146
+
147
+ - All seven coverage, source-access, allocation, finite, pixel, latent, and
148
+ text-policy gates passed.
149
+ - Peak text stage with the packed transformer still resident:
150
+ `10,437,891,584` bytes (`9.72 GiB`).
151
+ - VAL-07 pixel cosine/NRMSE versus frozen BF16:
152
+ `0.9927827428` / `0.1200022063`.
153
+ - VAL-07 latent cosine/NRMSE versus frozen BF16:
154
+ `0.9339298065` / `0.3618087155`.
155
+ - The requested strings `ARCTIC LOOP` and `NORTHERN COAST` were both rendered
156
+ correctly.
157
+
158
+ ![Combined native NVFP4 VAL-07 output](examples/VAL-07_combined_native.png)
159
+
160
+ The standard-layout packaged loader is validated independently of the
161
+ research-tree loader. It installs 48 native Mage projections and 238 packed
162
+ Qwen projections and rejects any checkpoint containing the replaced BF16
163
+ transformer targets.
164
+
165
+ ## Quality and speed caveats
166
+
167
+ The mixed text encoder is functional and produced visually strong downstream
168
+ images, but it does not meet our unusually strict embedding-similarity gate:
169
+ mean token/pooled cosine was `0.9531366898` / `0.9751400001`. All ten content
170
+ screening verdicts and category lists remained unchanged.
171
+
172
+ The ten-case text workload was slower with the packed runtime (`30.36 s`)
173
+ than BF16 (`18.29 s`). This package therefore claims major text-encoder VRAM
174
+ savings, not a text-encoding speedup. Transformer projections are materially
175
+ faster, but repeated matched end-to-end timing has not been completed.
176
+
177
+ Additional limitations:
178
+
179
+ - Native execution is currently SM120-only.
180
+ - The repository follows the Hugging Face component and Safetensors layout,
181
+ but the new `mage_flow_nvfp4` runtime is not yet built into stock Diffusers.
182
+ Use the included loader.
183
+ - CUDA graph compatibility is not claimed.
184
+ - Generation and text-to-image are tested; Base, Turbo, and editing variants
185
+ are not.
186
+ - Four transformer-only held-out cases and one combined held-out case have
187
+ been evaluated. This is not a broad benchmark.
188
+
189
+ ## Validate the download
190
+
191
+ `MANIFEST.json` records every distributed file except itself:
192
+
193
+ ```bash
194
+ .venv/bin/python validate_release.py
195
+ ```
196
+
197
+ Hashing the transformer shards and text checkpoint can take a little while.
198
+
199
+ ## License and attribution
200
+
201
+ - Mage-Flow and the vendored Mage inference source are Copyright (c) 2026
202
+ Microsoft and licensed under MIT. See `LICENSE` and
203
+ `licenses/MAGE-MIT.txt`.
204
+ - Qwen3-VL and the mixed NVFP4/FP8 text checkpoint are licensed under
205
+ Apache-2.0. See `licenses/QWEN-APACHE-2.0.txt`.
206
+ - The text checkpoint was produced by
207
+ [`InsecureErasure/Qwen3-VL-4B-Instruct-NVFP4`](https://huggingface.co/InsecureErasure/Qwen3-VL-4B-Instruct-NVFP4)
208
+ using learned rounding and `comfy-kitchen`.
THIRD_PARTY_NOTICES.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Third-party notices
2
+
3
+ ## Microsoft Mage and Mage-Flow
4
+
5
+ This package targets `microsoft/Mage-Flow` and includes the minimal Python
6
+ inference sources under `vendor/mage_flow/`.
7
+
8
+ - Upstream: <https://github.com/microsoft/Mage>
9
+ - Pinned source commit: `df7f84d9f8fc991d189d929f03cff623b430a4a2`
10
+ - License: MIT
11
+ - License copy: `LICENSE` and `licenses/MAGE-MIT.txt`
12
+
13
+ The Microsoft base checkpoint is not duplicated in this package. The
14
+ portable loader downloads the required transformer, VAE, scheduler, and
15
+ configuration files from `microsoft/Mage-Flow`, or accepts a local copy.
16
+
17
+ ## Qwen3-VL and the mixed NVFP4 checkpoint
18
+
19
+ The packaged text encoder derives from `Qwen/Qwen3-VL-4B-Instruct` and is an
20
+ exact copy of the learned-rounded mixed checkpoint published at
21
+ `InsecureErasure/Qwen3-VL-4B-Instruct-NVFP4`.
22
+
23
+ - Quantized upstream:
24
+ <https://huggingface.co/InsecureErasure/Qwen3-VL-4B-Instruct-NVFP4>
25
+ - Pinned quantized revision:
26
+ `cf080d6af667f5f4949295bd09a1f72a218fe0ee`
27
+ - Base model: <https://huggingface.co/Qwen/Qwen3-VL-4B-Instruct>
28
+ - License: Apache-2.0
29
+ - License copy: `licenses/QWEN-APACHE-2.0.txt`
30
+ - Packaged artifact SHA-256:
31
+ `719906b435800757d22013d3d475a4853d59b779b022669fa8b8a193b85d0f41`
32
+
33
+ The quantized checkpoint uses the ComfyUI `comfy_quant` representation and
34
+ executes packed projections through `comfy-kitchen`.
build_manifest.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Create the immutable release manifest after all package files are frozen."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import hashlib
7
+ import json
8
+ from pathlib import Path
9
+
10
+
11
+ RELEASE_ROOT = Path(__file__).resolve().parent
12
+ MANIFEST_PATH = RELEASE_ROOT / "MANIFEST.json"
13
+
14
+
15
+ def sha256(path: Path) -> str:
16
+ digest = hashlib.sha256()
17
+ with path.open("rb") as handle:
18
+ for chunk in iter(lambda: handle.read(1 << 20), b""):
19
+ digest.update(chunk)
20
+ return digest.hexdigest()
21
+
22
+
23
+ def role_for(path: Path) -> str:
24
+ value = path.as_posix()
25
+ if value == "README.md":
26
+ return "Hugging Face model card and usage guide"
27
+ if value == "model_index.json":
28
+ return "Hugging Face Diffusers metadata and download-tracking query file"
29
+ if value == "generate.py":
30
+ return "portable single-image inference entry point"
31
+ if value == "run_validation.py":
32
+ return "portable frozen-prompt validation entry point"
33
+ if value == "validate_release.py":
34
+ return "package hash validator"
35
+ if value == "build_native.sh":
36
+ return "project-local native runtime build helper"
37
+ if value == "build_manifest.py":
38
+ return "release manifest builder"
39
+ if value.startswith("transformer/") and value.endswith(".safetensors"):
40
+ return "complete sharded Mage transformer checkpoint with native NVFP4 modules"
41
+ if value.startswith("transformer/"):
42
+ return "Mage transformer component configuration, index, or quantization metadata"
43
+ if value.startswith("text_encoder/") and value.endswith(".safetensors"):
44
+ return "complete mixed NVFP4/FP8 Qwen3-VL text checkpoint"
45
+ if value.startswith("text_encoder/"):
46
+ return "packaged Qwen3-VL configuration or tokenizer asset"
47
+ if value.startswith("vae/"):
48
+ return "complete Mage VAE component"
49
+ if value.startswith("scheduler/"):
50
+ return "Mage FlowMatch scheduler component"
51
+ if value.startswith("runtime/"):
52
+ return "native runtime, loader, source, or Python integration"
53
+ if value.startswith("vendor/"):
54
+ return "pinned Microsoft Mage inference source"
55
+ if value.startswith("licenses/") or value in {
56
+ "LICENSE",
57
+ "THIRD_PARTY_NOTICES.md",
58
+ }:
59
+ return "license or third-party attribution"
60
+ if value.startswith("examples/"):
61
+ return "validated combined NVFP4 example output"
62
+ return "release support file"
63
+
64
+
65
+ def package_files() -> list[dict[str, object]]:
66
+ records: list[dict[str, object]] = []
67
+ for path in sorted(RELEASE_ROOT.rglob("*")):
68
+ if not path.is_file() or path == MANIFEST_PATH:
69
+ continue
70
+ relative = path.relative_to(RELEASE_ROOT)
71
+ if "__pycache__" in relative.parts or path.suffix == ".pyc":
72
+ raise RuntimeError(
73
+ f"generated Python cache must not be shipped: {relative}"
74
+ )
75
+ if path.is_symlink():
76
+ raise RuntimeError(f"release files must be independent: {relative}")
77
+ records.append(
78
+ {
79
+ "path": relative.as_posix(),
80
+ "size": path.stat().st_size,
81
+ "sha256": sha256(path),
82
+ "role": role_for(relative),
83
+ }
84
+ )
85
+ return records
86
+
87
+
88
+ def main() -> int:
89
+ if MANIFEST_PATH.exists():
90
+ raise SystemExit(f"refusing to overwrite {MANIFEST_PATH}")
91
+ payload = {
92
+ "schema_version": "mage-flow-nvfp4-huggingface-release-v1",
93
+ "release_status": "validated-standard-huggingface-candidate",
94
+ "base_models": {
95
+ "mage_flow": "microsoft/Mage-Flow",
96
+ "mage_source_commit": (
97
+ "df7f84d9f8fc991d189d929f03cff623b430a4a2"
98
+ ),
99
+ "mage_transformer_sha256": (
100
+ "04431abbc3acd1a5b86a7f77269f8e77e05a6cac3b21f35e1aef7493f6ab4934"
101
+ ),
102
+ "qwen": "Qwen/Qwen3-VL-4B-Instruct",
103
+ "qwen_quantized": (
104
+ "InsecureErasure/Qwen3-VL-4B-Instruct-NVFP4"
105
+ ),
106
+ "qwen_quantized_revision": (
107
+ "cf080d6af667f5f4949295bd09a1f72a218fe0ee"
108
+ ),
109
+ },
110
+ "policy": {
111
+ "mage_transformer": {
112
+ "format": "native resident W4A4 NVFP4",
113
+ "target_count": 48,
114
+ "target_bf16_source_reads": 0,
115
+ },
116
+ "qwen_text_encoder": {
117
+ "nvfp4_projection_count": 224,
118
+ "fp8_projection_count": 14,
119
+ "bf16_policy": (
120
+ "blocks 0/35, embeddings, norms, biases, vision tower"
121
+ ),
122
+ },
123
+ "native_compute_capability": "12.0",
124
+ "component_layout": "complete_huggingface_sharded_repository",
125
+ },
126
+ "measurements": {
127
+ "transformer_allocated_saving_bytes": 2_604_638_208,
128
+ "transformer_allocated_saving_gib": 2.4257583618164062,
129
+ "text_encoder_allocated_saving_bytes": 4_834_599_424,
130
+ "text_encoder_allocated_saving_gib": 4.5025715827941895,
131
+ "combined_text_stage_peak_allocated_bytes": 10_437_891_584,
132
+ "combined_val07_pixel_cosine": 0.9927827428055002,
133
+ "combined_val07_pixel_nrmse": 0.12000220627369493,
134
+ "combined_val07_latent_cosine": 0.9339298064776214,
135
+ "combined_val07_latent_nrmse": 0.36180871546574084,
136
+ "full_generation_speed_claim_ready": False,
137
+ },
138
+ "evidence": {
139
+ "combined_frozen_val07": {
140
+ "result_sha256": (
141
+ "67afd8ca0c00fff949bfc4c8b660ca864617839cbc01231a7157769a964287c7"
142
+ ),
143
+ "image_sha256": (
144
+ "857c1663636cd775ea05ffe37a98ac47ab7cd4e5362dd82c21ae1b53f7d71c3a"
145
+ ),
146
+ "all_seven_gates_passed": True,
147
+ },
148
+ "legacy_portable_package_generation": {
149
+ "report_sha256": (
150
+ "2877697cdf8d469f689aa68751d24c8f25288388f682e61355234e82fae35eda"
151
+ ),
152
+ "image_sha256": (
153
+ "2c1f2853e37a2ede976f24bc9b7eef172aed7665266c615401ea189fcbcee19f"
154
+ ),
155
+ "status": "success",
156
+ },
157
+ "standard_huggingface_layout_generation": {
158
+ "report_sha256": (
159
+ "61c8b651a5a9814c20fa4a99719e0b0a6caa827f61c0865993d916de25e6507d"
160
+ ),
161
+ "image_sha256": (
162
+ "2c1f2853e37a2ede976f24bc9b7eef172aed7665266c615401ea189fcbcee19f"
163
+ ),
164
+ "layout": "complete_huggingface_sharded_repository",
165
+ "transformer_shard_count": 4,
166
+ "transformer_checkpoint_tensor_count": 493,
167
+ "loaded_non_target_tensor_count": 301,
168
+ "loaded_nvfp4_projection_count": 48,
169
+ "bf16_target_weight_reads": 0,
170
+ "status": "success",
171
+ },
172
+ "native_text_quality_gate_result_sha256": (
173
+ "4a31a2c0eb4cac7401224eaeab23577bd31cc5ef69f6edec5289757f113373e6"
174
+ ),
175
+ "transformer_quality_results": {
176
+ "VAL-01": (
177
+ "82fadbc86ffb980a503c61ba50bfae25fd302503cf9ebb807029aacdc484f65a"
178
+ ),
179
+ "VAL-07": (
180
+ "2476505becf3cec14d174c35b42a92ae180cea1173ebe376d0d40f530a0292ce"
181
+ ),
182
+ "VAL-09": (
183
+ "c5d25ab78790295a0945b9783e146ff114991cc72af80a524d083034696110fc"
184
+ ),
185
+ "VAL-13": (
186
+ "d2f5b3e7b3be8d430fbd8ca47abdfa405d130f080d45a51fe5ab90fde309a07e"
187
+ ),
188
+ },
189
+ },
190
+ "files": package_files(),
191
+ }
192
+ with MANIFEST_PATH.open("xb") as handle:
193
+ handle.write(
194
+ json.dumps(payload, indent=2, sort_keys=True).encode("utf-8")
195
+ + b"\n"
196
+ )
197
+ print(f"wrote {MANIFEST_PATH} with {len(payload['files'])} files")
198
+ return 0
199
+
200
+
201
+ if __name__ == "__main__":
202
+ raise SystemExit(main())
build_native.sh ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ RELEASE_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ RUNTIME_ROOT="${RELEASE_ROOT}/runtime"
6
+ PYTHON_BIN="${PYTHON_BIN:-python}"
7
+ CUDA_TOOLKIT_ROOT="${CUDA_HOME:-/usr/local/cuda}"
8
+ NVCC="${CUDA_TOOLKIT_ROOT}/bin/nvcc"
9
+
10
+ if [[ ! -x "${NVCC}" ]]; then
11
+ echo "nvcc was not found at ${NVCC}" >&2
12
+ exit 1
13
+ fi
14
+
15
+ mkdir -p "${RUNTIME_ROOT}"
16
+
17
+ "${NVCC}" \
18
+ -std=c++17 -O3 -arch=sm_120a -shared -Xcompiler=-fPIC \
19
+ "${RUNTIME_ROOT}/nvfp4_linear.cu" \
20
+ -o "${RUNTIME_ROOT}/libmage_nvfp4_linear.so" \
21
+ -L"${CUDA_TOOLKIT_ROOT}/lib64" -lcublasLt -lcublas
22
+
23
+ PYTORCH_INCLUDE_FLAGS="$("${PYTHON_BIN}" - <<'PY'
24
+ from torch.utils.cpp_extension import include_paths
25
+ print(" ".join(f"-I{path}" for path in include_paths()))
26
+ PY
27
+ )"
28
+ PYTORCH_LIBRARY_FLAGS="$("${PYTHON_BIN}" - <<'PY'
29
+ from torch.utils.cpp_extension import library_paths
30
+ print(" ".join(f"-L{path}" for path in library_paths()))
31
+ PY
32
+ )"
33
+ PYTORCH_RPATH_FLAGS="$("${PYTHON_BIN}" - <<'PY'
34
+ from torch.utils.cpp_extension import library_paths
35
+ print(" ".join(f"-Wl,-rpath,{path}" for path in library_paths()))
36
+ PY
37
+ )"
38
+ PYTORCH_ABI_FLAG="$("${PYTHON_BIN}" - <<'PY'
39
+ import torch
40
+ print(f"-D_GLIBCXX_USE_CXX11_ABI={int(torch.compiled_with_cxx11_abi())}")
41
+ PY
42
+ )"
43
+
44
+ g++ \
45
+ -std=c++20 -O3 -shared -fPIC \
46
+ ${PYTORCH_ABI_FLAG} \
47
+ ${PYTORCH_INCLUDE_FLAGS} \
48
+ ${PYTORCH_LIBRARY_FLAGS} \
49
+ ${PYTORCH_RPATH_FLAGS} \
50
+ -I"${CUDA_TOOLKIT_ROOT}/include" \
51
+ -I"${RUNTIME_ROOT}" \
52
+ -o "${RUNTIME_ROOT}/libmage_nvfp4_torch_op.so" \
53
+ "${RUNTIME_ROOT}/sm120_linear_op.cpp" \
54
+ -L"${CUDA_TOOLKIT_ROOT}/lib64" \
55
+ -L"${RUNTIME_ROOT}" \
56
+ -Wl,-rpath,"${CUDA_TOOLKIT_ROOT}/lib64" \
57
+ -Wl,-rpath,'$ORIGIN' \
58
+ -ltorch -ltorch_cpu -ltorch_cuda -lc10 -lc10_cuda -lcudart \
59
+ -lmage_nvfp4_linear
60
+
61
+ echo "Built the packaged SM120 runtime in ${RUNTIME_ROOT}"
generate.py ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Portable single-image Mage-Flow NVFP4 inference entry point."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ from datetime import datetime, timezone
8
+ import gc
9
+ import json
10
+ import os
11
+ from pathlib import Path
12
+ import platform
13
+ import sys
14
+ import time
15
+ from typing import Any, Callable
16
+
17
+
18
+ RELEASE_ROOT = Path(__file__).resolve().parent
19
+ RUNTIME_ROOT = RELEASE_ROOT / "runtime"
20
+ VENDOR_ROOT = RELEASE_ROOT / "vendor"
21
+ DEFAULT_MODEL = str(RELEASE_ROOT)
22
+
23
+ for import_root in (RUNTIME_ROOT, VENDOR_ROOT):
24
+ if str(import_root) not in sys.path:
25
+ sys.path.insert(0, str(import_root))
26
+
27
+
28
+ def utc_now() -> str:
29
+ return (
30
+ datetime.now(timezone.utc)
31
+ .replace(microsecond=0)
32
+ .isoformat()
33
+ .replace("+00:00", "Z")
34
+ )
35
+
36
+
37
+ def resolve_model(value: str) -> Path:
38
+ candidate = Path(value).expanduser()
39
+ if candidate.is_dir():
40
+ return candidate.resolve()
41
+ from huggingface_hub import snapshot_download
42
+
43
+ return Path(
44
+ snapshot_download(
45
+ repo_id=value,
46
+ allow_patterns=[
47
+ "model_index.json",
48
+ "transformer/config.json",
49
+ "transformer/*.safetensors",
50
+ "transformer/*.json",
51
+ "text_encoder/*",
52
+ "vae/config.json",
53
+ "vae/*.safetensors",
54
+ "scheduler/*",
55
+ ],
56
+ )
57
+ ).resolve()
58
+
59
+
60
+ def _repo_subpath(repo_dir: Path, relative: str) -> str:
61
+ path = (repo_dir / relative).resolve()
62
+ if not path.is_relative_to(repo_dir):
63
+ raise ValueError(f"model path escapes its snapshot: {relative}")
64
+ return str(path)
65
+
66
+
67
+ def _structure_from_config(transformer_config: dict[str, Any]) -> dict[str, Any]:
68
+ metadata_keys = {
69
+ "_class_name",
70
+ "txt_max_length",
71
+ "max_sequence_length",
72
+ "param_dtype",
73
+ "packing",
74
+ "schedule_mode",
75
+ "static_shift",
76
+ "use_time_shift",
77
+ "rope_type",
78
+ "apply_text_rotary_emb",
79
+ "mlp_ratio",
80
+ "depth_single_blocks",
81
+ "theta",
82
+ "qkv_bias",
83
+ "guidance_embed",
84
+ "vec_in_dim",
85
+ "vec_type",
86
+ "time_type",
87
+ "double_block_type",
88
+ "quantization_config",
89
+ }
90
+ return {
91
+ key: value
92
+ for key, value in transformer_config.items()
93
+ if key not in metadata_keys
94
+ }
95
+
96
+
97
+ def load_pipeline(
98
+ *,
99
+ model: str,
100
+ torch: Any,
101
+ ) -> tuple[Any, dict[str, Any]]:
102
+ import torch.nn as nn
103
+ from diffusers import FlowMatchEulerDiscreteScheduler
104
+ from mage_flow.models.mage_flow import MageFlowModel, ModelConfig
105
+ from mage_flow.models.modules._attn_backend import set_attn_backend
106
+ from mage_flow.pipeline import MageFlowPipeline
107
+ from quant_text_encoder import load_quantized_text_encoder
108
+ from standard_transformer import load_standard_native_transformer
109
+
110
+ repo_dir = resolve_model(model)
111
+ model_index_path = repo_dir / "model_index.json"
112
+ transformer_config_path = repo_dir / "transformer" / "config.json"
113
+ model_index = json.loads(model_index_path.read_text(encoding="utf-8"))
114
+ transformer_config = json.loads(
115
+ transformer_config_path.read_text(encoding="utf-8")
116
+ )
117
+ structure = _structure_from_config(transformer_config)
118
+ config = ModelConfig(
119
+ vae_path=_repo_subpath(repo_dir, model_index["_vae_source"]),
120
+ txt_enc_path=_repo_subpath(repo_dir, model_index["_text_encoder_path"]),
121
+ model_structure=structure,
122
+ txt_max_length=transformer_config.get("txt_max_length", 2048),
123
+ packing=transformer_config.get("packing", True),
124
+ static_shift=transformer_config.get("static_shift", 6.0),
125
+ )
126
+
127
+ transformer, access_report = load_standard_native_transformer(
128
+ repo_dir,
129
+ torch.device("cuda:0"),
130
+ )
131
+ model = MageFlowModel.__new__(MageFlowModel)
132
+ nn.Module.__init__(model)
133
+ model.config = config
134
+ set_attn_backend(getattr(config, "attn_type", "flash2"))
135
+ model.patch_text_encoder_forward()
136
+ model.vae = model.load_vae()
137
+ model.transformer = transformer
138
+ text_encoder_dir = Path(
139
+ _repo_subpath(repo_dir, model_index["_text_encoder_path"])
140
+ )
141
+ model.txt_enc, text_report = load_quantized_text_encoder(
142
+ text_encoder_dir=text_encoder_dir,
143
+ artifact_path=text_encoder_dir / "model.safetensors",
144
+ tokenizer_max_length=config.txt_max_length,
145
+ dit_structure=structure,
146
+ use_packed_text_infer=config.packing,
147
+ )
148
+ model.vae.requires_grad_(False).to(torch.bfloat16)
149
+ model.txt_enc.requires_grad_(False)
150
+ model.eval()
151
+ model.scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
152
+ _repo_subpath(repo_dir, "scheduler")
153
+ )
154
+ return (
155
+ MageFlowPipeline(model, device="cuda:0"),
156
+ {
157
+ "resolved_model": str(repo_dir),
158
+ "transformer_access": access_report,
159
+ "text_encoder": text_report,
160
+ },
161
+ )
162
+
163
+
164
+ def stage_to_gpu(
165
+ module: Any,
166
+ operation: Callable[[], Any],
167
+ torch: Any,
168
+ ) -> tuple[Any, dict[str, Any]]:
169
+ torch.cuda.synchronize()
170
+ torch.cuda.reset_peak_memory_stats(0)
171
+ started = time.perf_counter()
172
+ try:
173
+ module.to("cuda:0")
174
+ value = operation()
175
+ torch.cuda.synchronize()
176
+ metrics = {
177
+ "peak_allocated_bytes": int(torch.cuda.max_memory_allocated(0)),
178
+ "peak_reserved_bytes": int(torch.cuda.max_memory_reserved(0)),
179
+ }
180
+ finally:
181
+ module.to("cpu")
182
+ torch.cuda.synchronize()
183
+ torch.cuda.empty_cache()
184
+ metrics["seconds"] = time.perf_counter() - started
185
+ return value, metrics
186
+
187
+
188
+ def generate_staged(
189
+ *,
190
+ pipe: Any,
191
+ prompt: str,
192
+ negative_prompt: str,
193
+ height: int,
194
+ width: int,
195
+ steps: int,
196
+ cfg: float,
197
+ seed: int,
198
+ static_shift: float,
199
+ torch: Any,
200
+ ) -> tuple[Any, dict[str, Any], dict[str, Any]]:
201
+ from einops import rearrange
202
+ from mage_flow.models.modules.mage_latent import encode_noise, resolve_gs_key
203
+ from mage_flow.pipeline import (
204
+ _build_pack_ctx,
205
+ _decode_one,
206
+ _encode_texts_packed,
207
+ _get_scheduler,
208
+ _lens_to_cu,
209
+ _make_divisible_by_16,
210
+ _slice_packed,
211
+ _template_info,
212
+ _velocity,
213
+ )
214
+
215
+ model = pipe.model
216
+ device = torch.device("cuda:0")
217
+ template_info = _template_info("mage-flow")
218
+ template = template_info.get("template", "{}")
219
+ drop_index = int(template_info.get("start_idx", 0))
220
+ stage_metrics: dict[str, Any] = {}
221
+
222
+ def encode_text() -> tuple[Any, ...]:
223
+ verdict = model.txt_enc.screen_text(prompt)
224
+ if verdict.violates:
225
+ return (verdict,)
226
+ text_flat, vec_all, text_lens = _encode_texts_packed(
227
+ model,
228
+ [prompt, negative_prompt or " "],
229
+ template,
230
+ drop_index,
231
+ device,
232
+ )
233
+ positive = _slice_packed(
234
+ text_flat, vec_all, text_lens, 0, 1, device
235
+ )
236
+ negative = _slice_packed(
237
+ text_flat, vec_all, text_lens, 1, 1, device
238
+ )
239
+ return (verdict, *positive, *negative)
240
+
241
+ encoded, stage_metrics["text_encode"] = stage_to_gpu(
242
+ model.txt_enc,
243
+ encode_text,
244
+ torch,
245
+ )
246
+ verdict = encoded[0]
247
+ if verdict.violates:
248
+ raise RuntimeError(
249
+ "prompt was refused by Mage content screening: "
250
+ f"{verdict.categories} {verdict.reason}"
251
+ )
252
+ (
253
+ _verdict,
254
+ txt,
255
+ txt_cu,
256
+ txt_mask,
257
+ vec,
258
+ neg_txt,
259
+ neg_cu,
260
+ neg_mask,
261
+ neg_vec,
262
+ ) = encoded
263
+
264
+ height = _make_divisible_by_16(height)
265
+ width = _make_divisible_by_16(width)
266
+ noise = encode_noise(
267
+ (
268
+ model.vae.latent_channels,
269
+ (height + 15) // 16,
270
+ (width + 15) // 16,
271
+ ),
272
+ key=resolve_gs_key(None),
273
+ seed=seed,
274
+ device=device,
275
+ dtype=torch.bfloat16,
276
+ )
277
+ _, _, grid_h, grid_w = noise.shape
278
+ image_latent = rearrange(noise, "b c h w -> b (h w) c")
279
+ image_ids = torch.zeros(grid_h, grid_w, 3, device=device)
280
+ image_ids[..., 1] += torch.arange(grid_h, device=device)[:, None]
281
+ image_ids[..., 2] += torch.arange(grid_w, device=device)[None, :]
282
+ image_ids = rearrange(image_ids, "h w c -> 1 (h w) c")
283
+ image_lens = [grid_h * grid_w]
284
+ image_cu = _lens_to_cu(image_lens, device)
285
+ context = _build_pack_ctx(
286
+ image_ids,
287
+ image_cu,
288
+ [[(1, grid_h, grid_w)]],
289
+ image_lens,
290
+ txt,
291
+ txt_cu,
292
+ txt_mask,
293
+ vec,
294
+ neg_txt,
295
+ neg_cu,
296
+ neg_mask,
297
+ neg_vec,
298
+ cfg,
299
+ False,
300
+ True,
301
+ device,
302
+ )
303
+
304
+ def denoise() -> Any:
305
+ nonlocal image_latent
306
+ scheduler = _get_scheduler(
307
+ model,
308
+ steps,
309
+ device,
310
+ static_shift,
311
+ )
312
+ for step_index, timestep in enumerate(scheduler.timesteps):
313
+ prediction = _velocity(
314
+ model.transformer,
315
+ image_latent,
316
+ context,
317
+ scheduler.sigmas[step_index].item(),
318
+ )
319
+ image_latent = scheduler.step(
320
+ prediction,
321
+ timestep,
322
+ image_latent,
323
+ return_dict=False,
324
+ )[0]
325
+ if int((~torch.isfinite(image_latent)).sum().item()) != 0:
326
+ raise RuntimeError("denoising produced non-finite values")
327
+ return image_latent.to("cpu")
328
+
329
+ latent_cpu, stage_metrics["denoise"] = stage_to_gpu(
330
+ model.transformer,
331
+ denoise,
332
+ torch,
333
+ )
334
+
335
+ def decode() -> Any:
336
+ return _decode_one(
337
+ model,
338
+ latent_cpu.to(device),
339
+ height,
340
+ width,
341
+ device,
342
+ )
343
+
344
+ image, stage_metrics["decode"] = stage_to_gpu(
345
+ model.vae,
346
+ decode,
347
+ torch,
348
+ )
349
+ screening = {
350
+ "violates": bool(verdict.violates),
351
+ "categories": list(verdict.categories or []),
352
+ "reason": str(verdict.reason),
353
+ }
354
+ return image, stage_metrics, screening
355
+
356
+
357
+ def parse_args() -> argparse.Namespace:
358
+ parser = argparse.ArgumentParser(description=__doc__)
359
+ parser.add_argument("--prompt", required=True)
360
+ parser.add_argument("--negative-prompt", default="")
361
+ parser.add_argument("--output", type=Path, default=Path("mage_nvfp4.png"))
362
+ parser.add_argument(
363
+ "--model",
364
+ default=DEFAULT_MODEL,
365
+ help=(
366
+ "local standard-layout model directory or Hugging Face repo id "
367
+ "(default: this downloaded repository)"
368
+ ),
369
+ )
370
+ parser.add_argument("--height", type=int, default=1024)
371
+ parser.add_argument("--width", type=int, default=1024)
372
+ parser.add_argument("--steps", type=int, default=20)
373
+ parser.add_argument("--cfg", type=float, default=5.0)
374
+ parser.add_argument("--seed", type=int, default=1)
375
+ parser.add_argument("--static-shift", type=float, default=6.0)
376
+ return parser.parse_args()
377
+
378
+
379
+ def main() -> int:
380
+ args = parse_args()
381
+ output_path = args.output.expanduser().resolve()
382
+ report_path = output_path.with_suffix(output_path.suffix + ".json")
383
+ if output_path.exists() or report_path.exists():
384
+ raise SystemExit(
385
+ f"refusing to overwrite existing output/report: {output_path}"
386
+ )
387
+ output_path.parent.mkdir(parents=True, exist_ok=True)
388
+
389
+ import torch
390
+ from packed_nvfp4_linear import close_all_contexts
391
+ from torch_ops_native import (
392
+ close_native_contexts,
393
+ initialize_native_sm120_op,
394
+ )
395
+
396
+ if not torch.cuda.is_available() or torch.cuda.device_count() != 1:
397
+ raise SystemExit(
398
+ "exactly one visible CUDA GPU is required; set CUDA_VISIBLE_DEVICES"
399
+ )
400
+ torch.cuda.set_device(0)
401
+ properties = torch.cuda.get_device_properties(0)
402
+ if (properties.major, properties.minor) != (12, 0):
403
+ raise SystemExit(
404
+ f"native NVFP4 requires SM120; found {properties.major}.{properties.minor}"
405
+ )
406
+ if not initialize_native_sm120_op(allow_python_schema_fallback=False):
407
+ raise SystemExit("the packaged native SM120 torch op did not load")
408
+
409
+ os.environ.setdefault("CUBLAS_WORKSPACE_CONFIG", ":4096:8")
410
+ torch.manual_seed(args.seed)
411
+ torch.cuda.manual_seed_all(args.seed)
412
+ torch.backends.cudnn.benchmark = False
413
+ torch.backends.cudnn.deterministic = True
414
+ torch.backends.cuda.matmul.allow_tf32 = False
415
+ torch.use_deterministic_algorithms(True)
416
+
417
+ started = time.perf_counter()
418
+ pipe = None
419
+ try:
420
+ pipe, load_report = load_pipeline(
421
+ model=args.model,
422
+ torch=torch,
423
+ )
424
+ image, stages, screening = generate_staged(
425
+ pipe=pipe,
426
+ prompt=args.prompt,
427
+ negative_prompt=args.negative_prompt,
428
+ height=args.height,
429
+ width=args.width,
430
+ steps=args.steps,
431
+ cfg=args.cfg,
432
+ seed=args.seed,
433
+ static_shift=args.static_shift,
434
+ torch=torch,
435
+ )
436
+ image.save(output_path)
437
+ report = {
438
+ "schema_version": "mage-flow-nvfp4-portable-generation-v1",
439
+ "status": "success",
440
+ "completed_at_utc": utc_now(),
441
+ "output": str(output_path),
442
+ "prompt": args.prompt,
443
+ "negative_prompt": args.negative_prompt,
444
+ "height": args.height,
445
+ "width": args.width,
446
+ "steps": args.steps,
447
+ "cfg": args.cfg,
448
+ "seed": args.seed,
449
+ "elapsed_seconds": time.perf_counter() - started,
450
+ "stages": stages,
451
+ "screening": screening,
452
+ "load": load_report,
453
+ "environment": {
454
+ "python": platform.python_version(),
455
+ "torch": torch.__version__,
456
+ "torch_cuda": torch.version.cuda,
457
+ "gpu": properties.name,
458
+ "compute_capability": (
459
+ f"{properties.major}.{properties.minor}"
460
+ ),
461
+ },
462
+ }
463
+ report_path.write_text(
464
+ json.dumps(report, indent=2, sort_keys=True) + "\n",
465
+ encoding="utf-8",
466
+ )
467
+ print(f"saved {output_path}")
468
+ print(f"saved {report_path}")
469
+ return 0
470
+ finally:
471
+ close_native_contexts()
472
+ close_all_contexts()
473
+ pipe = None
474
+ gc.collect()
475
+
476
+
477
+ if __name__ == "__main__":
478
+ raise SystemExit(main())
licenses/MAGE-MIT.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Microsoft
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
licenses/QWEN-APACHE-2.0.txt ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
model_index.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "MageFlowPipeline",
3
+ "_mage_flow_version": "0.1.0",
4
+ "_quantization": {
5
+ "text_encoder": "comfy_kitchen_nvfp4_fp8_mixed",
6
+ "transformer": "mage_flow_nvfp4"
7
+ },
8
+ "_text_encoder_path": "text_encoder",
9
+ "_vae_source": "vae/diffusion_pytorch_model.safetensors",
10
+ "scheduler": [
11
+ "diffusers",
12
+ "FlowMatchEulerDiscreteScheduler"
13
+ ],
14
+ "text_encoder": [
15
+ "transformers",
16
+ "Qwen3VLForConditionalGeneration"
17
+ ],
18
+ "tokenizer": [
19
+ "transformers",
20
+ "AutoProcessor"
21
+ ],
22
+ "transformer": [
23
+ "mage_flow",
24
+ "MageFlow"
25
+ ],
26
+ "vae": [
27
+ "mage_flow",
28
+ "MageVAE"
29
+ ]
30
+ }
requirements.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Exact tested environment. Install only in a project-local virtual environment.
2
+ --extra-index-url https://download.pytorch.org/whl/cu130
3
+ torch==2.13.0+cu130
4
+ torchvision==0.28.0+cu130
5
+ diffusers==0.38.0
6
+ transformers==5.5.0
7
+ safetensors==0.8.0
8
+ accelerate==1.13.0
9
+ comfy-kitchen==0.2.22
10
+ einops==0.8.2
11
+ pydantic==2.12.5
12
+ pillow==12.3.0
13
+ numpy==2.4.3
14
+ loguru==0.7.3
15
+ huggingface-hub==1.24.0
16
+ setuptools
17
+ wheel
18
+ ninja
19
+
20
+ # Install flash-attn==2.8.3 separately with --no-build-isolation after this file.
run_validation.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Exercise the portable package with the frozen VAL-07 typography prompt."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ from pathlib import Path
8
+ import subprocess
9
+ import sys
10
+
11
+
12
+ RELEASE_ROOT = Path(__file__).resolve().parent
13
+ RUNNER = RELEASE_ROOT / "generate.py"
14
+ VALIDATOR = RELEASE_ROOT / "validate_release.py"
15
+ PROMPT = (
16
+ 'Minimal travel postcard with the exact large text "ARCTIC LOOP" and '
17
+ 'the exact small text "NORTHERN COAST"; an ivory lighthouse on a '
18
+ "slate-blue cliff below the lettering, crisp vector print."
19
+ )
20
+ NEGATIVE_PROMPT = (
21
+ "lowres, blurry, jpeg artifacts, watermark, signature, logo, unreadable "
22
+ "text, misspelled text, duplicate subject, deformed anatomy, extra "
23
+ "fingers, missing fingers, cropped hands"
24
+ )
25
+
26
+
27
+ def parse_args() -> argparse.Namespace:
28
+ parser = argparse.ArgumentParser(description=__doc__)
29
+ parser.add_argument("--cpu-self-check", action="store_true")
30
+ parser.add_argument("--model", default=str(RELEASE_ROOT))
31
+ parser.add_argument("--output-dir", type=Path)
32
+ return parser.parse_args()
33
+
34
+
35
+ def main() -> int:
36
+ args = parse_args()
37
+ subprocess.run([sys.executable, str(VALIDATOR)], check=True)
38
+ if args.cpu_self_check:
39
+ return 0
40
+ if args.output_dir is None:
41
+ raise SystemExit("--output-dir is required unless --cpu-self-check is used")
42
+ output_dir = args.output_dir.resolve()
43
+ output_path = output_dir / "VAL-07_portable_combined.png"
44
+ command = [
45
+ sys.executable,
46
+ str(RUNNER),
47
+ "--model",
48
+ args.model,
49
+ "--prompt",
50
+ PROMPT,
51
+ "--negative-prompt",
52
+ NEGATIVE_PROMPT,
53
+ "--output",
54
+ str(output_path),
55
+ "--height",
56
+ "512",
57
+ "--width",
58
+ "512",
59
+ "--steps",
60
+ "20",
61
+ "--cfg",
62
+ "5.0",
63
+ "--seed",
64
+ "27182819",
65
+ "--static-shift",
66
+ "6.0",
67
+ ]
68
+ return subprocess.run(command, check=False).returncode
69
+
70
+
71
+ if __name__ == "__main__":
72
+ raise SystemExit(main())
runtime/nvfp4_linear.cu ADDED
@@ -0,0 +1,895 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "nvfp4_linear.h"
2
+
3
+ #include <cuda_bf16.h>
4
+ #include <cuda_fp4.h>
5
+ #include <cuda_fp8.h>
6
+ #include <cuda_runtime.h>
7
+ #include <cublasLt.h>
8
+
9
+ #include <algorithm>
10
+ #include <cmath>
11
+ #include <cstdint>
12
+ #include <cstring>
13
+ #include <limits>
14
+ #include <mutex>
15
+ #include <stdexcept>
16
+ #include <string>
17
+
18
+ namespace {
19
+
20
+ constexpr int kAbiVersion = 1;
21
+ constexpr int kFp4BlockElements = 16;
22
+ constexpr int kScaleTileOuter = 128;
23
+ constexpr int kScaleTileInner = 4;
24
+ constexpr int kWarpsPerQuantBlock = 8;
25
+ constexpr int kQuantThreads = 32 * kWarpsPerQuantBlock;
26
+ constexpr int kReduceThreads = 256;
27
+ constexpr int kReduceItemsPerThread = 4;
28
+ constexpr int kBiasThreads = 256;
29
+ constexpr float kFp4E2M1Max = 6.0f;
30
+ constexpr float kFp4TensorScaleMax = 448.0f;
31
+ constexpr float kTensorScaleDenominator =
32
+ kFp4E2M1Max * kFp4TensorScaleMax;
33
+ constexpr size_t kWorkspaceBytes = 64ull * 1024ull * 1024ull;
34
+
35
+ thread_local std::string g_last_error;
36
+
37
+ [[noreturn]] void fail(const std::string& message) {
38
+ throw std::runtime_error(message);
39
+ }
40
+
41
+ #define CUDA_CHECK(expr) \
42
+ do { \
43
+ const cudaError_t status_ = (expr); \
44
+ if (status_ != cudaSuccess) { \
45
+ fail(std::string(#expr) + ": " + cudaGetErrorString(status_)); \
46
+ } \
47
+ } while (0)
48
+
49
+ #define CUBLASLT_CHECK(expr) \
50
+ do { \
51
+ const cublasStatus_t status_ = (expr); \
52
+ if (status_ != CUBLAS_STATUS_SUCCESS) { \
53
+ fail(std::string(#expr) + " failed with cuBLASLt status " + \
54
+ std::to_string(static_cast<int>(status_))); \
55
+ } \
56
+ } while (0)
57
+
58
+ int round_up(int value, int multiple) {
59
+ if (value <= 0 || multiple <= 0 ||
60
+ value > std::numeric_limits<int>::max() - (multiple - 1)) {
61
+ fail("invalid or overflowing round_up arguments");
62
+ }
63
+ return ((value + multiple - 1) / multiple) * multiple;
64
+ }
65
+
66
+ size_t checked_multiply(size_t left, size_t right, const char* label) {
67
+ if (right != 0 && left > std::numeric_limits<size_t>::max() / right) {
68
+ fail(std::string(label) + " size overflows size_t");
69
+ }
70
+ return left * right;
71
+ }
72
+
73
+ size_t round_up_divide(size_t dividend, size_t divisor) {
74
+ return (dividend + divisor - 1) / divisor;
75
+ }
76
+
77
+ struct ScaleLayout {
78
+ int inner_dim = 0;
79
+ int outer_tiles = 0;
80
+ size_t bytes = 0;
81
+ };
82
+
83
+ ScaleLayout make_scale_layout(int rows_k, int outer_columns) {
84
+ if (rows_k <= 0 || rows_k % 16 != 0 || outer_columns <= 0) {
85
+ fail("scale layout requires positive K divisible by 16 and positive M/N");
86
+ }
87
+ ScaleLayout layout;
88
+ layout.inner_dim = round_up(rows_k / kFp4BlockElements, kScaleTileInner);
89
+ layout.outer_tiles =
90
+ (outer_columns + kScaleTileOuter - 1) / kScaleTileOuter;
91
+ layout.bytes = checked_multiply(
92
+ checked_multiply(static_cast<size_t>(layout.outer_tiles),
93
+ static_cast<size_t>(layout.inner_dim), "scale tensor"),
94
+ static_cast<size_t>(kScaleTileOuter), "scale tensor");
95
+ return layout;
96
+ }
97
+
98
+ size_t packed_weight_bytes_checked(int n, int k) {
99
+ if (n <= 0 || k <= 0 || n % 8 != 0 || k % 32 != 0) {
100
+ fail("NVFP4 weight requires N divisible by 8 and K divisible by 32");
101
+ }
102
+ return checked_multiply(static_cast<size_t>(n),
103
+ static_cast<size_t>(k), "packed weight") /
104
+ 2;
105
+ }
106
+
107
+ size_t host_scale_offset(int outer, int inner_scale, int scale_inner_dim) {
108
+ const int outer_tile = outer / kScaleTileOuter;
109
+ const int local_outer = outer % kScaleTileOuter;
110
+ const int local_inner = inner_scale % kScaleTileInner;
111
+ const int inner_tile_start = inner_scale - local_inner;
112
+ const size_t tile_base =
113
+ static_cast<size_t>(inner_tile_start +
114
+ outer_tile * scale_inner_dim) *
115
+ kScaleTileOuter;
116
+ return tile_base + static_cast<size_t>(local_outer % 32) * 16 +
117
+ static_cast<size_t>(local_outer / 32) * 4 + local_inner;
118
+ }
119
+
120
+ __device__ __forceinline__ size_t device_scale_offset(
121
+ int outer, int inner_scale, int scale_inner_dim) {
122
+ const int outer_tile = outer / kScaleTileOuter;
123
+ const int local_outer = outer % kScaleTileOuter;
124
+ const int local_inner = inner_scale % kScaleTileInner;
125
+ const int inner_tile_start = inner_scale - local_inner;
126
+ const size_t tile_base =
127
+ static_cast<size_t>(inner_tile_start +
128
+ outer_tile * scale_inner_dim) *
129
+ kScaleTileOuter;
130
+ return tile_base + static_cast<size_t>(local_outer % 32) * 16 +
131
+ static_cast<size_t>(local_outer / 32) * 4 + local_inner;
132
+ }
133
+
134
+ float host_ue4m3_to_float(uint8_t raw) {
135
+ const __half_raw half_raw = __nv_cvt_fp8_to_halfraw(raw, __NV_E4M3);
136
+ return __half2float(static_cast<__half>(half_raw));
137
+ }
138
+
139
+ __device__ __forceinline__ float device_ue4m3_to_float(uint8_t raw) {
140
+ const __half_raw half_raw = __nv_cvt_fp8_to_halfraw(raw, __NV_E4M3);
141
+ return __half2float(static_cast<__half>(half_raw));
142
+ }
143
+
144
+ __device__ __forceinline__ float tensor_scale_from_amax(float amax) {
145
+ return amax == 0.0f ? 1.0f : amax / kTensorScaleDenominator;
146
+ }
147
+
148
+ __device__ __forceinline__ float block_reduce_max(float value) {
149
+ __shared__ float shared[kReduceThreads];
150
+ shared[threadIdx.x] = value;
151
+ __syncthreads();
152
+ for (int offset = kReduceThreads / 2; offset > 0; offset >>= 1) {
153
+ if (threadIdx.x < offset) {
154
+ shared[threadIdx.x] =
155
+ fmaxf(shared[threadIdx.x], shared[threadIdx.x + offset]);
156
+ }
157
+ __syncthreads();
158
+ }
159
+ return shared[0];
160
+ }
161
+
162
+ __global__ void reduce_abs_max_bf16(
163
+ const __nv_bfloat16* source, float* block_maxima,
164
+ size_t element_count) {
165
+ const size_t block_start =
166
+ static_cast<size_t>(blockIdx.x) * kReduceThreads *
167
+ kReduceItemsPerThread;
168
+ float local_max = 0.0f;
169
+ for (int item = 0; item < kReduceItemsPerThread; ++item) {
170
+ const size_t index =
171
+ block_start + static_cast<size_t>(threadIdx.x) +
172
+ static_cast<size_t>(item) * kReduceThreads;
173
+ if (index < element_count) {
174
+ local_max =
175
+ fmaxf(local_max, fabsf(__bfloat162float(source[index])));
176
+ }
177
+ }
178
+ const float block_max = block_reduce_max(local_max);
179
+ if (threadIdx.x == 0) {
180
+ block_maxima[blockIdx.x] = block_max;
181
+ }
182
+ }
183
+
184
+ __global__ void reduce_max_float(
185
+ const float* source, float* block_maxima, size_t element_count) {
186
+ const size_t block_start =
187
+ static_cast<size_t>(blockIdx.x) * kReduceThreads *
188
+ kReduceItemsPerThread;
189
+ float local_max = 0.0f;
190
+ for (int item = 0; item < kReduceItemsPerThread; ++item) {
191
+ const size_t index =
192
+ block_start + static_cast<size_t>(threadIdx.x) +
193
+ static_cast<size_t>(item) * kReduceThreads;
194
+ if (index < element_count) {
195
+ local_max = fmaxf(local_max, source[index]);
196
+ }
197
+ }
198
+ const float block_max = block_reduce_max(local_max);
199
+ if (threadIdx.x == 0) {
200
+ block_maxima[blockIdx.x] = block_max;
201
+ }
202
+ }
203
+
204
+ __global__ void finalize_activation_scale(
205
+ const float* activation_amax, const float* weight_tensor_scale,
206
+ float* activation_tensor_scale, float* fp4_alpha) {
207
+ if (blockIdx.x == 0 && threadIdx.x == 0) {
208
+ const float activation_scale =
209
+ tensor_scale_from_amax(activation_amax[0]);
210
+ activation_tensor_scale[0] = activation_scale;
211
+ fp4_alpha[0] = weight_tensor_scale[0] * activation_scale;
212
+ }
213
+ }
214
+
215
+ // logical_m may be smaller than padded_m. Padded rows are written as exact
216
+ // FP4 zero with zero block scales, so reusable scratch never exposes a prior
217
+ // call's tail.
218
+ __global__ void dynamic_quantize_activation(
219
+ const __nv_bfloat16* source, uint8_t* destination_fp4,
220
+ uint8_t* destination_scales,
221
+ const float* activation_tensor_scale, int rows_k, int logical_m,
222
+ int scale_inner_dim, uint64_t padded_scale_blocks) {
223
+ const int lane = threadIdx.x & 31;
224
+ const int warp_in_block = threadIdx.x >> 5;
225
+ const uint64_t logical_block =
226
+ static_cast<uint64_t>(blockIdx.x) * kWarpsPerQuantBlock +
227
+ static_cast<uint64_t>(warp_in_block);
228
+ if (logical_block >= padded_scale_blocks) {
229
+ return;
230
+ }
231
+
232
+ const int blocks_per_column = rows_k / kFp4BlockElements;
233
+ const int outer =
234
+ static_cast<int>(logical_block / blocks_per_column);
235
+ const int inner_scale = static_cast<int>(
236
+ logical_block -
237
+ static_cast<uint64_t>(outer) * blocks_per_column);
238
+ const int first_row = inner_scale * kFp4BlockElements;
239
+ const size_t column_base = static_cast<size_t>(outer) * rows_k;
240
+ const bool valid_outer = outer < logical_m;
241
+ const float tensor_scale = activation_tensor_scale[0];
242
+ const float inverse_tensor_scale =
243
+ tensor_scale == 0.0f ? 0.0f : 1.0f / tensor_scale;
244
+
245
+ float value0 = 0.0f;
246
+ float value1 = 0.0f;
247
+ float magnitude = 0.0f;
248
+ size_t destination_source_index = 0;
249
+ if (lane < kFp4BlockElements / 2) {
250
+ const int row0 = first_row + lane * 2;
251
+ destination_source_index = column_base + row0;
252
+ if (valid_outer) {
253
+ value0 = __bfloat162float(source[destination_source_index]) *
254
+ inverse_tensor_scale;
255
+ value1 = __bfloat162float(source[destination_source_index + 1]) *
256
+ inverse_tensor_scale;
257
+ magnitude = fmaxf(fabsf(value0), fabsf(value1));
258
+ }
259
+ }
260
+ for (int offset = 16; offset > 0; offset >>= 1) {
261
+ magnitude =
262
+ fmaxf(magnitude,
263
+ __shfl_down_sync(0xffffffffU, magnitude, offset));
264
+ }
265
+
266
+ float rounded_scale = 0.0f;
267
+ if (lane == 0) {
268
+ const uint8_t scale_raw = __nv_cvt_float_to_fp8(
269
+ magnitude / kFp4E2M1Max, __NV_SATFINITE, __NV_E4M3);
270
+ rounded_scale = device_ue4m3_to_float(scale_raw);
271
+ destination_scales[device_scale_offset(
272
+ outer, inner_scale, scale_inner_dim)] = scale_raw;
273
+ }
274
+ rounded_scale = __shfl_sync(0xffffffffU, rounded_scale, 0);
275
+
276
+ if (lane < kFp4BlockElements / 2) {
277
+ const float inverse_scale =
278
+ rounded_scale == 0.0f ? 0.0f : 1.0f / rounded_scale;
279
+ destination_fp4[destination_source_index / 2] =
280
+ __nv_cvt_float2_to_fp4x2(
281
+ make_float2(value0 * inverse_scale,
282
+ value1 * inverse_scale),
283
+ __NV_E2M1, cudaRoundNearest);
284
+ }
285
+ }
286
+
287
+ __global__ void add_bias_bf16(
288
+ __nv_bfloat16* output, const __nv_bfloat16* bias,
289
+ size_t element_count, int n) {
290
+ const size_t index =
291
+ static_cast<size_t>(blockIdx.x) * blockDim.x + threadIdx.x;
292
+ if (index < element_count) {
293
+ const float value = __bfloat162float(output[index]);
294
+ const float bias_value = __bfloat162float(bias[index % n]);
295
+ output[index] = __float2bfloat16(value + bias_value);
296
+ }
297
+ }
298
+
299
+ size_t reduction_block_count(size_t element_count) {
300
+ return round_up_divide(
301
+ element_count,
302
+ static_cast<size_t>(kReduceThreads * kReduceItemsPerThread));
303
+ }
304
+
305
+ size_t reduction_scratch_elements(size_t element_count) {
306
+ size_t max_blocks = 1;
307
+ while (element_count > 1) {
308
+ const size_t blocks = reduction_block_count(element_count);
309
+ max_blocks = std::max(max_blocks, blocks);
310
+ element_count = blocks;
311
+ }
312
+ return max_blocks;
313
+ }
314
+
315
+ void enqueue_activation_amax_reduce(
316
+ const __nv_bfloat16* source, size_t element_count,
317
+ float* scratch_a, float* scratch_b, float* destination_amax,
318
+ int max_grid_x, cudaStream_t stream) {
319
+ const float* current_source = nullptr;
320
+ float* current_destination = scratch_a;
321
+ size_t current_count = element_count;
322
+ bool first_stage = true;
323
+ while (true) {
324
+ const size_t blocks = reduction_block_count(current_count);
325
+ if (blocks == 0 || blocks > static_cast<size_t>(max_grid_x)) {
326
+ fail("activation amax reduction exceeds the GPU grid limit");
327
+ }
328
+ if (first_stage) {
329
+ reduce_abs_max_bf16<<<
330
+ static_cast<unsigned int>(blocks), kReduceThreads, 0,
331
+ stream>>>(source, current_destination, current_count);
332
+ } else {
333
+ reduce_max_float<<<
334
+ static_cast<unsigned int>(blocks), kReduceThreads, 0,
335
+ stream>>>(current_source, current_destination, current_count);
336
+ }
337
+ CUDA_CHECK(cudaPeekAtLastError());
338
+ if (blocks == 1) {
339
+ if (current_destination != destination_amax) {
340
+ CUDA_CHECK(cudaMemcpyAsync(
341
+ destination_amax, current_destination, sizeof(float),
342
+ cudaMemcpyDeviceToDevice, stream));
343
+ }
344
+ return;
345
+ }
346
+ current_count = blocks;
347
+ current_source = current_destination;
348
+ current_destination =
349
+ current_destination == scratch_a ? scratch_b : scratch_a;
350
+ first_stage = false;
351
+ }
352
+ }
353
+
354
+ struct DeviceAllocation {
355
+ void* pointer = nullptr;
356
+ size_t bytes = 0;
357
+
358
+ ~DeviceAllocation() {
359
+ if (pointer != nullptr) {
360
+ cudaFree(pointer);
361
+ }
362
+ }
363
+
364
+ DeviceAllocation() = default;
365
+ DeviceAllocation(const DeviceAllocation&) = delete;
366
+ DeviceAllocation& operator=(const DeviceAllocation&) = delete;
367
+ };
368
+
369
+ struct Context {
370
+ int device = -1;
371
+ int max_grid_x = 0;
372
+ cublasLtHandle_t handle = nullptr;
373
+ DeviceAllocation x_fp4;
374
+ DeviceAllocation x_scales;
375
+ DeviceAllocation reduce_a;
376
+ DeviceAllocation reduce_b;
377
+ DeviceAllocation activation_amax;
378
+ DeviceAllocation activation_scale;
379
+ DeviceAllocation fp4_alpha;
380
+ DeviceAllocation fp4_beta;
381
+ DeviceAllocation workspace;
382
+ uintptr_t bound_stream = 0;
383
+ bool stream_bound = false;
384
+ std::mutex mutex;
385
+
386
+ ~Context() {
387
+ if (handle != nullptr) {
388
+ cublasLtDestroy(handle);
389
+ }
390
+ }
391
+ };
392
+
393
+ void allocate_exact(DeviceAllocation* allocation, size_t bytes) {
394
+ if (allocation->pointer != nullptr) {
395
+ CUDA_CHECK(cudaFree(allocation->pointer));
396
+ allocation->pointer = nullptr;
397
+ allocation->bytes = 0;
398
+ }
399
+ if (bytes != 0) {
400
+ CUDA_CHECK(cudaMalloc(&allocation->pointer, bytes));
401
+ allocation->bytes = bytes;
402
+ }
403
+ }
404
+
405
+ void ensure_capacity(
406
+ Context* context, DeviceAllocation* allocation, size_t bytes,
407
+ cudaStream_t stream) {
408
+ if (allocation->bytes >= bytes) {
409
+ return;
410
+ }
411
+ // A growth invalidates scratch pointers. Synchronize the one bound stream
412
+ // before freeing; steady-state forwards do not synchronize.
413
+ if (context->stream_bound) {
414
+ CUDA_CHECK(cudaStreamSynchronize(stream));
415
+ }
416
+ allocate_exact(allocation, bytes);
417
+ }
418
+
419
+ void set_scale_mode(
420
+ cublasLtMatmulDesc_t operation,
421
+ cublasLtMatmulDescAttributes_t attribute) {
422
+ const int32_t mode =
423
+ CUBLASLT_MATMUL_MATRIX_SCALE_VEC16_UE4M3;
424
+ CUBLASLT_CHECK(cublasLtMatmulDescSetAttribute(
425
+ operation, attribute, &mode, sizeof(mode)));
426
+ }
427
+
428
+ void set_pointer_attribute(
429
+ cublasLtMatmulDesc_t operation,
430
+ cublasLtMatmulDescAttributes_t attribute, const void* pointer) {
431
+ CUBLASLT_CHECK(cublasLtMatmulDescSetAttribute(
432
+ operation, attribute, &pointer, sizeof(pointer)));
433
+ }
434
+
435
+ struct Descriptors {
436
+ cublasLtMatmulDesc_t operation = nullptr;
437
+ cublasLtMatrixLayout_t w = nullptr;
438
+ cublasLtMatrixLayout_t x = nullptr;
439
+ cublasLtMatrixLayout_t c = nullptr;
440
+ cublasLtMatrixLayout_t d = nullptr;
441
+
442
+ ~Descriptors() {
443
+ if (d != nullptr) cublasLtMatrixLayoutDestroy(d);
444
+ if (c != nullptr) cublasLtMatrixLayoutDestroy(c);
445
+ if (x != nullptr) cublasLtMatrixLayoutDestroy(x);
446
+ if (w != nullptr) cublasLtMatrixLayoutDestroy(w);
447
+ if (operation != nullptr) cublasLtMatmulDescDestroy(operation);
448
+ }
449
+ };
450
+
451
+ cublasLtMatmulAlgo_t choose_algo(
452
+ Context* context, const Descriptors& descriptors,
453
+ size_t* workspace_bytes) {
454
+ cublasLtMatmulPreference_t preference = nullptr;
455
+ CUBLASLT_CHECK(cublasLtMatmulPreferenceCreate(&preference));
456
+ CUBLASLT_CHECK(cublasLtMatmulPreferenceSetAttribute(
457
+ preference, CUBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES,
458
+ &kWorkspaceBytes, sizeof(kWorkspaceBytes)));
459
+
460
+ cublasLtMatmulHeuristicResult_t candidates[16]{};
461
+ int returned = 0;
462
+ const cublasStatus_t status = cublasLtMatmulAlgoGetHeuristic(
463
+ context->handle, descriptors.operation, descriptors.w,
464
+ descriptors.x, descriptors.c, descriptors.d, preference, 16,
465
+ candidates, &returned);
466
+ cublasLtMatmulPreferenceDestroy(preference);
467
+ if (status != CUBLAS_STATUS_SUCCESS || returned == 0) {
468
+ fail("cuBLASLt returned no NVFP4 heuristic");
469
+ }
470
+ for (int index = 0; index < returned; ++index) {
471
+ if (candidates[index].state == CUBLAS_STATUS_SUCCESS) {
472
+ *workspace_bytes = candidates[index].workspaceSize;
473
+ return candidates[index].algo;
474
+ }
475
+ }
476
+ fail("all cuBLASLt NVFP4 heuristics were unsupported");
477
+ }
478
+
479
+ void validate_device_pointer(
480
+ const void* pointer, int expected_device, const char* label) {
481
+ if (pointer == nullptr) {
482
+ fail(std::string(label) + " is null");
483
+ }
484
+ cudaPointerAttributes attributes{};
485
+ CUDA_CHECK(cudaPointerGetAttributes(&attributes, pointer));
486
+ if (attributes.type != cudaMemoryTypeDevice ||
487
+ attributes.device != expected_device) {
488
+ fail(std::string(label) + " is not a CUDA allocation on the context device");
489
+ }
490
+ if ((reinterpret_cast<uintptr_t>(pointer) & 0x0fU) != 0) {
491
+ fail(std::string(label) + " is not at least 16-byte aligned");
492
+ }
493
+ }
494
+
495
+ void forward_impl(
496
+ Context* context, const void* input_bf16,
497
+ const void* packed_weight, size_t packed_weight_bytes,
498
+ const void* packed_weight_scales,
499
+ size_t packed_weight_scale_bytes,
500
+ const void* weight_tensor_scale_f32, const void* bias_bf16,
501
+ void* output_bf16, int logical_m, int k, int n,
502
+ uintptr_t stream_value) {
503
+ if (logical_m <= 0 || k <= 0 || n <= 0) {
504
+ fail("M, K, and N must be positive");
505
+ }
506
+ const size_t expected_weight_bytes =
507
+ packed_weight_bytes_checked(n, k);
508
+ const size_t expected_scale_bytes = make_scale_layout(k, n).bytes;
509
+ if (packed_weight_bytes != expected_weight_bytes ||
510
+ packed_weight_scale_bytes != expected_scale_bytes) {
511
+ fail("packed weight or scale buffer has the wrong byte size");
512
+ }
513
+
514
+ CUDA_CHECK(cudaSetDevice(context->device));
515
+ cudaStream_t stream = reinterpret_cast<cudaStream_t>(stream_value);
516
+ if (!context->stream_bound) {
517
+ context->bound_stream = stream_value;
518
+ context->stream_bound = true;
519
+ } else if (context->bound_stream != stream_value) {
520
+ fail("resident NVFP4 context is bound to a different CUDA stream");
521
+ }
522
+ cudaStreamCaptureStatus capture_status = cudaStreamCaptureStatusNone;
523
+ CUDA_CHECK(cudaStreamIsCapturing(stream, &capture_status));
524
+ if (capture_status != cudaStreamCaptureStatusNone) {
525
+ fail("resident ctypes prototype does not support CUDA graph capture");
526
+ }
527
+
528
+ validate_device_pointer(input_bf16, context->device, "input");
529
+ validate_device_pointer(packed_weight, context->device, "packed weight");
530
+ validate_device_pointer(
531
+ packed_weight_scales, context->device, "weight scales");
532
+ validate_device_pointer(
533
+ weight_tensor_scale_f32, context->device, "weight tensor scale");
534
+ validate_device_pointer(output_bf16, context->device, "output");
535
+ if (bias_bf16 != nullptr) {
536
+ validate_device_pointer(bias_bf16, context->device, "bias");
537
+ }
538
+
539
+ const int padded_m = round_up(logical_m, 8);
540
+ const size_t input_elements = checked_multiply(
541
+ static_cast<size_t>(logical_m), static_cast<size_t>(k), "input");
542
+ const size_t padded_input_elements = checked_multiply(
543
+ static_cast<size_t>(padded_m), static_cast<size_t>(k),
544
+ "padded input");
545
+ const size_t output_elements = checked_multiply(
546
+ static_cast<size_t>(padded_m), static_cast<size_t>(n), "output");
547
+ const ScaleLayout x_scale_layout = make_scale_layout(k, padded_m);
548
+ const size_t reduce_elements =
549
+ reduction_scratch_elements(input_elements);
550
+
551
+ ensure_capacity(
552
+ context, &context->x_fp4, padded_input_elements / 2, stream);
553
+ ensure_capacity(
554
+ context, &context->x_scales, x_scale_layout.bytes, stream);
555
+ ensure_capacity(
556
+ context, &context->reduce_a,
557
+ checked_multiply(reduce_elements, sizeof(float), "reduction"),
558
+ stream);
559
+ ensure_capacity(
560
+ context, &context->reduce_b,
561
+ checked_multiply(reduce_elements, sizeof(float), "reduction"),
562
+ stream);
563
+
564
+ // Clear the complete tiled scale allocation, including padding that the
565
+ // logical quantizer does not address.
566
+ CUDA_CHECK(cudaMemsetAsync(
567
+ context->x_scales.pointer, 0, x_scale_layout.bytes, stream));
568
+ enqueue_activation_amax_reduce(
569
+ static_cast<const __nv_bfloat16*>(input_bf16), input_elements,
570
+ static_cast<float*>(context->reduce_a.pointer),
571
+ static_cast<float*>(context->reduce_b.pointer),
572
+ static_cast<float*>(context->activation_amax.pointer),
573
+ context->max_grid_x, stream);
574
+ finalize_activation_scale<<<1, 1, 0, stream>>>(
575
+ static_cast<const float*>(context->activation_amax.pointer),
576
+ static_cast<const float*>(weight_tensor_scale_f32),
577
+ static_cast<float*>(context->activation_scale.pointer),
578
+ static_cast<float*>(context->fp4_alpha.pointer));
579
+ CUDA_CHECK(cudaPeekAtLastError());
580
+
581
+ const uint64_t padded_blocks =
582
+ static_cast<uint64_t>(padded_m) *
583
+ static_cast<uint64_t>(k / kFp4BlockElements);
584
+ const uint64_t cuda_blocks =
585
+ (padded_blocks + kWarpsPerQuantBlock - 1) /
586
+ kWarpsPerQuantBlock;
587
+ if (cuda_blocks == 0 ||
588
+ cuda_blocks > static_cast<uint64_t>(context->max_grid_x)) {
589
+ fail("activation quantizer exceeds the GPU grid limit");
590
+ }
591
+ dynamic_quantize_activation<<<
592
+ static_cast<unsigned int>(cuda_blocks), kQuantThreads, 0,
593
+ stream>>>(
594
+ static_cast<const __nv_bfloat16*>(input_bf16),
595
+ static_cast<uint8_t*>(context->x_fp4.pointer),
596
+ static_cast<uint8_t*>(context->x_scales.pointer),
597
+ static_cast<const float*>(context->activation_scale.pointer),
598
+ k, logical_m, x_scale_layout.inner_dim, padded_blocks);
599
+ CUDA_CHECK(cudaPeekAtLastError());
600
+
601
+ Descriptors descriptors;
602
+ CUBLASLT_CHECK(cublasLtMatmulDescCreate(
603
+ &descriptors.operation, CUBLAS_COMPUTE_32F, CUDA_R_32F));
604
+ const cublasOperation_t transpose_a = CUBLAS_OP_T;
605
+ const cublasOperation_t transpose_b = CUBLAS_OP_N;
606
+ CUBLASLT_CHECK(cublasLtMatmulDescSetAttribute(
607
+ descriptors.operation, CUBLASLT_MATMUL_DESC_TRANSA,
608
+ &transpose_a, sizeof(transpose_a)));
609
+ CUBLASLT_CHECK(cublasLtMatmulDescSetAttribute(
610
+ descriptors.operation, CUBLASLT_MATMUL_DESC_TRANSB,
611
+ &transpose_b, sizeof(transpose_b)));
612
+ const cublasLtPointerMode_t pointer_mode =
613
+ CUBLASLT_POINTER_MODE_DEVICE;
614
+ CUBLASLT_CHECK(cublasLtMatmulDescSetAttribute(
615
+ descriptors.operation, CUBLASLT_MATMUL_DESC_POINTER_MODE,
616
+ &pointer_mode, sizeof(pointer_mode)));
617
+ set_scale_mode(
618
+ descriptors.operation, CUBLASLT_MATMUL_DESC_A_SCALE_MODE);
619
+ set_scale_mode(
620
+ descriptors.operation, CUBLASLT_MATMUL_DESC_B_SCALE_MODE);
621
+ // Refresh pointer attributes for every call. Module buffers may differ
622
+ // between adjacent linears even though the shape is identical.
623
+ set_pointer_attribute(
624
+ descriptors.operation, CUBLASLT_MATMUL_DESC_A_SCALE_POINTER,
625
+ packed_weight_scales);
626
+ set_pointer_attribute(
627
+ descriptors.operation, CUBLASLT_MATMUL_DESC_B_SCALE_POINTER,
628
+ context->x_scales.pointer);
629
+
630
+ CUBLASLT_CHECK(cublasLtMatrixLayoutCreate(
631
+ &descriptors.w, CUDA_R_4F_E2M1, k, n, k));
632
+ CUBLASLT_CHECK(cublasLtMatrixLayoutCreate(
633
+ &descriptors.x, CUDA_R_4F_E2M1, k, padded_m, k));
634
+ CUBLASLT_CHECK(cublasLtMatrixLayoutCreate(
635
+ &descriptors.c, CUDA_R_16BF, n, padded_m, n));
636
+ CUBLASLT_CHECK(cublasLtMatrixLayoutCreate(
637
+ &descriptors.d, CUDA_R_16BF, n, padded_m, n));
638
+
639
+ size_t selected_workspace_bytes = 0;
640
+ const cublasLtMatmulAlgo_t algorithm =
641
+ choose_algo(context, descriptors, &selected_workspace_bytes);
642
+ if (selected_workspace_bytes > context->workspace.bytes) {
643
+ fail("selected cuBLASLt algorithm exceeds the context workspace");
644
+ }
645
+ CUBLASLT_CHECK(cublasLtMatmul(
646
+ context->handle, descriptors.operation,
647
+ context->fp4_alpha.pointer,
648
+ packed_weight, descriptors.w,
649
+ context->x_fp4.pointer, descriptors.x,
650
+ context->fp4_beta.pointer,
651
+ output_bf16, descriptors.c,
652
+ output_bf16, descriptors.d,
653
+ &algorithm, context->workspace.pointer,
654
+ selected_workspace_bytes, stream));
655
+
656
+ if (bias_bf16 != nullptr) {
657
+ const size_t blocks =
658
+ round_up_divide(output_elements,
659
+ static_cast<size_t>(kBiasThreads));
660
+ if (blocks > static_cast<size_t>(context->max_grid_x)) {
661
+ fail("bias kernel exceeds the GPU grid limit");
662
+ }
663
+ add_bias_bf16<<<
664
+ static_cast<unsigned int>(blocks), kBiasThreads, 0, stream>>>(
665
+ static_cast<__nv_bfloat16*>(output_bf16),
666
+ static_cast<const __nv_bfloat16*>(bias_bf16),
667
+ output_elements, n);
668
+ CUDA_CHECK(cudaPeekAtLastError());
669
+ }
670
+ }
671
+
672
+ template <typename Function>
673
+ int guarded(Function&& function) {
674
+ try {
675
+ g_last_error.clear();
676
+ function();
677
+ return 0;
678
+ } catch (const std::exception& error) {
679
+ g_last_error = error.what();
680
+ return 1;
681
+ } catch (...) {
682
+ g_last_error = "unknown native NVFP4 error";
683
+ return 2;
684
+ }
685
+ }
686
+
687
+ } // namespace
688
+
689
+ extern "C" int mage_nvfp4_abi_version(void) {
690
+ return kAbiVersion;
691
+ }
692
+
693
+ extern "C" const char* mage_nvfp4_last_error(void) {
694
+ return g_last_error.c_str();
695
+ }
696
+
697
+ extern "C" size_t mage_nvfp4_packed_weight_bytes(int n, int k) {
698
+ try {
699
+ g_last_error.clear();
700
+ return packed_weight_bytes_checked(n, k);
701
+ } catch (const std::exception& error) {
702
+ g_last_error = error.what();
703
+ return 0;
704
+ }
705
+ }
706
+
707
+ extern "C" size_t mage_nvfp4_weight_scale_bytes(int n, int k) {
708
+ try {
709
+ g_last_error.clear();
710
+ packed_weight_bytes_checked(n, k);
711
+ return make_scale_layout(k, n).bytes;
712
+ } catch (const std::exception& error) {
713
+ g_last_error = error.what();
714
+ return 0;
715
+ }
716
+ }
717
+
718
+ extern "C" int mage_nvfp4_pack_weight_bf16(
719
+ const void* weight_bf16, int n, int k,
720
+ void* packed_weight, size_t packed_weight_capacity,
721
+ void* packed_scales, size_t packed_scale_capacity,
722
+ float* tensor_scale) {
723
+ return guarded([&]() {
724
+ if (weight_bf16 == nullptr || packed_weight == nullptr ||
725
+ packed_scales == nullptr || tensor_scale == nullptr) {
726
+ fail("weight packer received a null pointer");
727
+ }
728
+ const size_t required_weight =
729
+ packed_weight_bytes_checked(n, k);
730
+ const ScaleLayout scale_layout = make_scale_layout(k, n);
731
+ if (packed_weight_capacity != required_weight ||
732
+ packed_scale_capacity != scale_layout.bytes) {
733
+ fail("weight packer received an incorrectly sized destination");
734
+ }
735
+
736
+ const auto* source =
737
+ static_cast<const __nv_bfloat16*>(weight_bf16);
738
+ auto* destination = static_cast<uint8_t*>(packed_weight);
739
+ auto* scales = static_cast<uint8_t*>(packed_scales);
740
+ std::memset(destination, 0, required_weight);
741
+ std::memset(scales, 0, scale_layout.bytes);
742
+ const size_t elements =
743
+ checked_multiply(static_cast<size_t>(n),
744
+ static_cast<size_t>(k), "weight");
745
+ float global_amax = 0.0f;
746
+ for (size_t index = 0; index < elements; ++index) {
747
+ global_amax = std::max(
748
+ global_amax,
749
+ std::abs(__bfloat162float(source[index])));
750
+ }
751
+ *tensor_scale =
752
+ global_amax == 0.0f
753
+ ? 1.0f
754
+ : global_amax / kTensorScaleDenominator;
755
+ const float inverse_tensor_scale = 1.0f / *tensor_scale;
756
+
757
+ // nn.Linear weight [N,K] is physical column-major KxN.
758
+ for (int column = 0; column < n; ++column) {
759
+ for (int block = 0; block < k / kFp4BlockElements; ++block) {
760
+ const int first_row = block * kFp4BlockElements;
761
+ float block_amax = 0.0f;
762
+ for (int lane = 0; lane < kFp4BlockElements; ++lane) {
763
+ const size_t index =
764
+ static_cast<size_t>(first_row + lane) +
765
+ static_cast<size_t>(column) * k;
766
+ block_amax = std::max(
767
+ block_amax,
768
+ std::abs(__bfloat162float(source[index]) *
769
+ inverse_tensor_scale));
770
+ }
771
+ const uint8_t scale_raw = __nv_cvt_float_to_fp8(
772
+ block_amax / kFp4E2M1Max,
773
+ __NV_SATFINITE, __NV_E4M3);
774
+ const float rounded_scale = host_ue4m3_to_float(scale_raw);
775
+ scales[host_scale_offset(
776
+ column, block, scale_layout.inner_dim)] = scale_raw;
777
+ const float inverse_scale =
778
+ rounded_scale == 0.0f ? 0.0f : 1.0f / rounded_scale;
779
+ for (int pair = 0;
780
+ pair < kFp4BlockElements / 2; ++pair) {
781
+ const int row0 = first_row + pair * 2;
782
+ const size_t source_index =
783
+ static_cast<size_t>(row0) +
784
+ static_cast<size_t>(column) * k;
785
+ const float value0 =
786
+ __bfloat162float(source[source_index]) *
787
+ inverse_tensor_scale;
788
+ const float value1 =
789
+ __bfloat162float(source[source_index + 1]) *
790
+ inverse_tensor_scale;
791
+ destination[source_index / 2] =
792
+ __nv_cvt_float2_to_fp4x2(
793
+ make_float2(value0 * inverse_scale,
794
+ value1 * inverse_scale),
795
+ __NV_E2M1, cudaRoundNearest);
796
+ }
797
+ }
798
+ }
799
+ });
800
+ }
801
+
802
+ extern "C" int mage_nvfp4_create_context(
803
+ int cuda_device, void** context) {
804
+ return guarded([&]() {
805
+ if (context == nullptr) {
806
+ fail("context output pointer is null");
807
+ }
808
+ *context = nullptr;
809
+ int device_count = 0;
810
+ CUDA_CHECK(cudaGetDeviceCount(&device_count));
811
+ if (cuda_device < 0 || cuda_device >= device_count) {
812
+ fail("invalid CUDA device index");
813
+ }
814
+ CUDA_CHECK(cudaSetDevice(cuda_device));
815
+ cudaDeviceProp properties{};
816
+ CUDA_CHECK(cudaGetDeviceProperties(&properties, cuda_device));
817
+ if (properties.major != 12 || properties.minor != 0) {
818
+ fail("resident NVFP4 prototype requires sm_120");
819
+ }
820
+
821
+ Context* created = new Context();
822
+ try {
823
+ created->device = cuda_device;
824
+ created->max_grid_x = properties.maxGridSize[0];
825
+ CUBLASLT_CHECK(cublasLtCreate(&created->handle));
826
+ allocate_exact(&created->activation_amax, sizeof(float));
827
+ allocate_exact(&created->activation_scale, sizeof(float));
828
+ allocate_exact(&created->fp4_alpha, sizeof(float));
829
+ allocate_exact(&created->fp4_beta, sizeof(float));
830
+ allocate_exact(&created->workspace, kWorkspaceBytes);
831
+ const float zero = 0.0f;
832
+ CUDA_CHECK(cudaMemcpy(
833
+ created->fp4_beta.pointer, &zero, sizeof(zero),
834
+ cudaMemcpyHostToDevice));
835
+ *context = created;
836
+ } catch (...) {
837
+ delete created;
838
+ throw;
839
+ }
840
+ });
841
+ }
842
+
843
+ extern "C" int mage_nvfp4_destroy_context(void* context) {
844
+ return guarded([&]() {
845
+ if (context == nullptr) {
846
+ return;
847
+ }
848
+ auto* typed = static_cast<Context*>(context);
849
+ {
850
+ std::lock_guard<std::mutex> lock(typed->mutex);
851
+ CUDA_CHECK(cudaSetDevice(typed->device));
852
+ if (typed->stream_bound) {
853
+ CUDA_CHECK(cudaStreamSynchronize(
854
+ reinterpret_cast<cudaStream_t>(typed->bound_stream)));
855
+ }
856
+ }
857
+ delete typed;
858
+ });
859
+ }
860
+
861
+ extern "C" size_t mage_nvfp4_context_reserved_bytes(
862
+ const void* context) {
863
+ if (context == nullptr) {
864
+ return 0;
865
+ }
866
+ const auto* typed = static_cast<const Context*>(context);
867
+ return typed->x_fp4.bytes + typed->x_scales.bytes +
868
+ typed->reduce_a.bytes + typed->reduce_b.bytes +
869
+ typed->activation_amax.bytes +
870
+ typed->activation_scale.bytes +
871
+ typed->fp4_alpha.bytes + typed->fp4_beta.bytes +
872
+ typed->workspace.bytes;
873
+ }
874
+
875
+ extern "C" int mage_nvfp4_linear_forward(
876
+ void* context, const void* input_bf16,
877
+ const void* packed_weight, size_t packed_weight_bytes,
878
+ const void* packed_weight_scales,
879
+ size_t packed_weight_scale_bytes,
880
+ const void* weight_tensor_scale_f32,
881
+ const void* bias_bf16, void* output_bf16,
882
+ int logical_m, int k, int n, uintptr_t stream) {
883
+ return guarded([&]() {
884
+ if (context == nullptr) {
885
+ fail("context is null");
886
+ }
887
+ auto* typed = static_cast<Context*>(context);
888
+ std::lock_guard<std::mutex> lock(typed->mutex);
889
+ forward_impl(
890
+ typed, input_bf16, packed_weight, packed_weight_bytes,
891
+ packed_weight_scales, packed_weight_scale_bytes,
892
+ weight_tensor_scale_f32, bias_bf16, output_bf16,
893
+ logical_m, k, n, stream);
894
+ });
895
+ }
runtime/nvfp4_linear.h ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include <stddef.h>
4
+ #include <stdint.h>
5
+
6
+ #if defined(_WIN32)
7
+ #define MAGE_NVFP4_API __declspec(dllexport)
8
+ #else
9
+ #define MAGE_NVFP4_API __attribute__((visibility("default")))
10
+ #endif
11
+
12
+ #ifdef __cplusplus
13
+ extern "C" {
14
+ #endif
15
+
16
+ // The ABI is inference-only. All functions return zero on success and a
17
+ // non-zero value on failure unless their return type documents otherwise.
18
+ MAGE_NVFP4_API int mage_nvfp4_abi_version(void);
19
+ MAGE_NVFP4_API const char* mage_nvfp4_last_error(void);
20
+
21
+ MAGE_NVFP4_API size_t mage_nvfp4_packed_weight_bytes(int n, int k);
22
+ MAGE_NVFP4_API size_t mage_nvfp4_weight_scale_bytes(int n, int k);
23
+
24
+ // Pack a contiguous CPU BF16 nn.Linear weight in logical row-major [N, K]
25
+ // order into cuBLASLt E2M1 payload and tiled VEC16 UE4M3 block scales.
26
+ MAGE_NVFP4_API int mage_nvfp4_pack_weight_bf16(
27
+ const void* weight_bf16, int n, int k,
28
+ void* packed_weight, size_t packed_weight_capacity,
29
+ void* packed_scales, size_t packed_scale_capacity,
30
+ float* tensor_scale);
31
+
32
+ // A context owns one cuBLASLt handle and reusable activation/reduction/
33
+ // workspace buffers. The first forward binds it to one CUDA stream. This
34
+ // initial prototype deliberately rejects a different stream and is not
35
+ // re-entrant.
36
+ MAGE_NVFP4_API int mage_nvfp4_create_context(
37
+ int cuda_device, void** context);
38
+ MAGE_NVFP4_API int mage_nvfp4_destroy_context(void* context);
39
+ MAGE_NVFP4_API size_t mage_nvfp4_context_reserved_bytes(
40
+ const void* context);
41
+
42
+ // Run Y[M,N] = X[M,K] * W[N,K]^T + bias.
43
+ //
44
+ // X, packed weight/scales, tensor scale, bias, and output are CUDA pointers on
45
+ // context's device. X is contiguous BF16 [logical_m, K]. Output is contiguous
46
+ // BF16 [round_up(logical_m, 8), N]. The caller slices the padded rows.
47
+ // bias_bf16 may be null. stream is a cudaStream_t passed as uintptr_t.
48
+ MAGE_NVFP4_API int mage_nvfp4_linear_forward(
49
+ void* context,
50
+ const void* input_bf16,
51
+ const void* packed_weight,
52
+ size_t packed_weight_bytes,
53
+ const void* packed_weight_scales,
54
+ size_t packed_weight_scale_bytes,
55
+ const void* weight_tensor_scale_f32,
56
+ const void* bias_bf16,
57
+ void* output_bf16,
58
+ int logical_m,
59
+ int k,
60
+ int n,
61
+ uintptr_t stream);
62
+
63
+ #ifdef __cplusplus
64
+ }
65
+ #endif
66
+
runtime/packed_artifact.py ADDED
@@ -0,0 +1,1068 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import hashlib
7
+ import json
8
+ import math
9
+ import os
10
+ import sys
11
+ from collections import OrderedDict
12
+ from dataclasses import asdict, dataclass
13
+ from datetime import datetime, timezone
14
+ from pathlib import Path
15
+ from typing import Callable, Iterable
16
+
17
+ import safetensors
18
+ import torch
19
+ import torch.nn as nn
20
+ from safetensors import safe_open
21
+ from safetensors.torch import save_file
22
+
23
+
24
+ FORMAT_VERSION = 1
25
+ ARTIFACT_KIND = "mage_flow_transformer_mlp_nvfp4_resident_v1"
26
+ CANONICAL_SAFETENSORS_METADATA = {
27
+ "mage_nvfp4_contract": (
28
+ f"{ARTIFACT_KIND};format_version={FORMAT_VERSION}"
29
+ )
30
+ }
31
+ LEGACY_SAFETENSORS_METADATA = {
32
+ "artifact_kind": ARTIFACT_KIND,
33
+ "format_version": str(FORMAT_VERSION),
34
+ }
35
+ FP4_BLOCK_ELEMENTS = 16
36
+ SCALE_TILE_OUTER = 128
37
+ SCALE_TILE_INNER = 4
38
+ FP4_E2M1_MAX = 6.0
39
+ FP4_TENSOR_SCALE_MAX = 448.0
40
+ NVFP4_TENSOR_SCALE_DENOMINATOR = FP4_E2M1_MAX * FP4_TENSOR_SCALE_MAX
41
+ TARGET_DEPTH = 12
42
+
43
+ RELEASE_ROOT = Path(__file__).resolve().parents[1]
44
+ PROJECT_ROOT = RELEASE_ROOT
45
+ MAGE_ROOT = RELEASE_ROOT / "vendor"
46
+ RESIDENT_PYTHON_ROOT = RELEASE_ROOT / "runtime"
47
+ RESIDENT_SOURCE = RESIDENT_PYTHON_ROOT / "nvfp4_linear.cu"
48
+ RESIDENT_LIBRARY = RESIDENT_PYTHON_ROOT / "libmage_nvfp4_linear.so"
49
+ ARTIFACT_SCRIPT_PATH = Path(__file__).resolve()
50
+ _NATIVE_PACKER = None
51
+
52
+
53
+ class PackedArtifactError(RuntimeError):
54
+ pass
55
+
56
+
57
+ @dataclass(frozen=True)
58
+ class ScaleLayout:
59
+ inner_dim: int
60
+ outer_tiles: int
61
+ bytes: int
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class TargetSpec:
66
+ module_key: str
67
+ weight_key: str
68
+ bias_key: str
69
+ artifact_weight_key: str
70
+ artifact_scale_key: str
71
+ artifact_tensor_scale_key: str
72
+ artifact_bias_key: str
73
+
74
+
75
+ def fail(message: str) -> None:
76
+ raise PackedArtifactError(message)
77
+
78
+
79
+ def round_up(value: int, multiple: int) -> int:
80
+ return ((value + multiple - 1) // multiple) * multiple
81
+
82
+
83
+ def sha256_file(path: Path) -> str:
84
+ digest = hashlib.sha256()
85
+ with path.open("rb") as handle:
86
+ while True:
87
+ chunk = handle.read(1 << 20)
88
+ if not chunk:
89
+ break
90
+ digest.update(chunk)
91
+ return digest.hexdigest()
92
+
93
+
94
+ def sha256_bytes(data: bytes) -> str:
95
+ return hashlib.sha256(data).hexdigest()
96
+
97
+
98
+ def tensor_bytes(tensor: torch.Tensor) -> bytes:
99
+ if not tensor.is_contiguous():
100
+ tensor = tensor.contiguous()
101
+ return tensor.view(torch.uint8).cpu().numpy().tobytes()
102
+
103
+
104
+ def sha256_tensor(tensor: torch.Tensor) -> str:
105
+ return sha256_bytes(tensor_bytes(tensor))
106
+
107
+
108
+ def fsync_directory(path: Path) -> None:
109
+ descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
110
+ try:
111
+ os.fsync(descriptor)
112
+ finally:
113
+ os.close(descriptor)
114
+
115
+
116
+ def fsync_file(path: Path) -> None:
117
+ descriptor = os.open(path, os.O_RDONLY)
118
+ try:
119
+ os.fsync(descriptor)
120
+ finally:
121
+ os.close(descriptor)
122
+
123
+
124
+ def write_bytes_once(path: Path, payload: bytes) -> None:
125
+ with path.open("xb") as handle:
126
+ handle.write(payload)
127
+ handle.flush()
128
+ os.fsync(handle.fileno())
129
+ fsync_directory(path.parent)
130
+
131
+
132
+ def host_scale_offset(outer: int, inner_scale: int, scale_inner_dim: int) -> int:
133
+ outer_tile = outer // SCALE_TILE_OUTER
134
+ local_outer = outer % SCALE_TILE_OUTER
135
+ local_inner = inner_scale % SCALE_TILE_INNER
136
+ inner_tile_start = inner_scale - local_inner
137
+ tile_base = (inner_tile_start + outer_tile * scale_inner_dim) * SCALE_TILE_OUTER
138
+ return tile_base + (local_outer % 32) * 16 + (local_outer // 32) * 4 + local_inner
139
+
140
+
141
+ def make_scale_layout(rows_k: int, outer_columns: int) -> ScaleLayout:
142
+ if rows_k <= 0 or outer_columns <= 0:
143
+ fail("scale layout requires positive rows_k and outer_columns")
144
+ if rows_k % FP4_BLOCK_ELEMENTS:
145
+ fail(
146
+ f"scale layout requires K divisible by {FP4_BLOCK_ELEMENTS}; "
147
+ f"got K={rows_k}"
148
+ )
149
+ inner_dim = round_up(rows_k // FP4_BLOCK_ELEMENTS, SCALE_TILE_INNER)
150
+ outer_tiles = (outer_columns + SCALE_TILE_OUTER - 1) // SCALE_TILE_OUTER
151
+ return ScaleLayout(
152
+ inner_dim=inner_dim,
153
+ outer_tiles=outer_tiles,
154
+ bytes=outer_tiles * inner_dim * SCALE_TILE_OUTER,
155
+ )
156
+
157
+
158
+ def build_target_specs(depth: int) -> list[TargetSpec]:
159
+ if depth != TARGET_DEPTH:
160
+ fail(
161
+ f"this artifact format is pinned to exactly {TARGET_DEPTH} transformer blocks; "
162
+ f"config reported depth={depth}"
163
+ )
164
+ specs: list[TargetSpec] = []
165
+ suffixes = (
166
+ "img_mlp.net.0.proj",
167
+ "img_mlp.net.2",
168
+ "txt_mlp.net.0.proj",
169
+ "txt_mlp.net.2",
170
+ )
171
+ for index in range(depth):
172
+ for suffix in suffixes:
173
+ module_key = f"transformer_blocks.{index}.{suffix}"
174
+ specs.append(
175
+ TargetSpec(
176
+ module_key=module_key,
177
+ weight_key=f"{module_key}.weight",
178
+ bias_key=f"{module_key}.bias",
179
+ artifact_weight_key=f"targets.{module_key}.packed_weight_e2m1",
180
+ artifact_scale_key=f"targets.{module_key}.packed_scales_ue4m3",
181
+ artifact_tensor_scale_key=f"targets.{module_key}.weight_tensor_scale",
182
+ artifact_bias_key=f"targets.{module_key}.bias_bf16",
183
+ )
184
+ )
185
+ return specs
186
+
187
+
188
+ def decode_fp4_e2m1(raw: int) -> float:
189
+ sign = -1.0 if (raw & 0x8) else 1.0
190
+ magnitude = raw & 0x7
191
+ table = (
192
+ 0.0,
193
+ 0.5,
194
+ 1.0,
195
+ 1.5,
196
+ 2.0,
197
+ 3.0,
198
+ 4.0,
199
+ 6.0,
200
+ )
201
+ return sign * table[magnitude]
202
+
203
+
204
+ def encode_fp4_e2m1(value: float) -> int:
205
+ candidates = [decode_fp4_e2m1(code) for code in range(16)]
206
+ best_code = 0
207
+ best_error = math.inf
208
+ for code, candidate in enumerate(candidates):
209
+ error = abs(candidate - value)
210
+ if error < best_error or (error == best_error and (code & 1) == 0 and (best_code & 1) == 1):
211
+ best_error = error
212
+ best_code = code
213
+ return best_code
214
+
215
+
216
+ def decode_fp8_e4m3(raw: int) -> float:
217
+ sign = -1.0 if (raw & 0x80) else 1.0
218
+ exponent = (raw >> 3) & 0x0F
219
+ mantissa = raw & 0x07
220
+ if exponent == 0:
221
+ if mantissa == 0:
222
+ return 0.0 * sign
223
+ return sign * (mantissa / 8.0) * (2.0 ** -6)
224
+ if exponent == 0x0F and mantissa == 0x07:
225
+ return math.nan
226
+ return sign * (1.0 + mantissa / 8.0) * (2.0 ** (exponent - 7))
227
+
228
+
229
+ def _build_positive_e4m3_table() -> list[tuple[int, float]]:
230
+ table: list[tuple[int, float]] = []
231
+ for raw in range(0x80):
232
+ value = decode_fp8_e4m3(raw)
233
+ if math.isnan(value) or value < 0.0:
234
+ continue
235
+ table.append((raw, value))
236
+ table.sort(key=lambda item: (item[1], item[0]))
237
+ return table
238
+
239
+
240
+ POSITIVE_E4M3_TABLE = _build_positive_e4m3_table()
241
+
242
+
243
+ def encode_fp8_e4m3_satfinite(value: float) -> int:
244
+ if value <= 0.0:
245
+ return 0
246
+ finite_values = [item for item in POSITIVE_E4M3_TABLE if item[1] <= FP4_TENSOR_SCALE_MAX]
247
+ best_raw = finite_values[-1][0]
248
+ best_error = math.inf
249
+ for raw, candidate in finite_values:
250
+ error = abs(candidate - value)
251
+ if error < best_error or (error == best_error and (raw & 1) == 0 and (best_raw & 1) == 1):
252
+ best_error = error
253
+ best_raw = raw
254
+ return best_raw
255
+
256
+
257
+ def host_tensor_scale_from_amax(amax: float) -> float:
258
+ return 1.0 if amax == 0.0 else amax / NVFP4_TENSOR_SCALE_DENOMINATOR
259
+
260
+
261
+ def native_packer():
262
+ global _NATIVE_PACKER
263
+ if _NATIVE_PACKER is None:
264
+ if str(RESIDENT_PYTHON_ROOT) not in sys.path:
265
+ sys.path.insert(0, str(RESIDENT_PYTHON_ROOT))
266
+ from packed_nvfp4_linear import NativeNvfp4Library
267
+
268
+ _NATIVE_PACKER = NativeNvfp4Library(RESIDENT_LIBRARY)
269
+ return _NATIVE_PACKER
270
+
271
+
272
+ def pack_weight_tensor(weight_nk_bf16: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, float]:
273
+ if weight_nk_bf16.dtype != torch.bfloat16 or weight_nk_bf16.device.type != "cpu":
274
+ fail("weight packer expects a CPU bfloat16 tensor")
275
+ if weight_nk_bf16.ndim != 2:
276
+ fail("weight packer expects a 2D [N,K] weight tensor")
277
+ if not weight_nk_bf16.is_contiguous():
278
+ weight_nk_bf16 = weight_nk_bf16.contiguous()
279
+
280
+ columns_n, rows_k = weight_nk_bf16.shape
281
+ if rows_k % 32 != 0 or columns_n % 8 != 0:
282
+ fail(
283
+ f"native NVFP4 packing requires K%32==0 and N%8==0; got N={columns_n} K={rows_k}"
284
+ )
285
+
286
+ amax = float(weight_nk_bf16.float().abs().max().item())
287
+ packed_fp4, packed_scales, tensor_scale = native_packer().pack_weight(
288
+ weight_nk_bf16
289
+ )
290
+ return packed_fp4, packed_scales, tensor_scale.reshape(1), amax
291
+
292
+
293
+ def load_transformer_config(source_repo: Path) -> dict:
294
+ config_path = source_repo / "transformer" / "config.json"
295
+ if not config_path.exists():
296
+ fail(f"missing transformer config: {config_path}")
297
+ return json.loads(config_path.read_text())
298
+
299
+
300
+ def source_transformer_checkpoint(source_repo: Path) -> Path:
301
+ checkpoint = source_repo / "transformer" / "diffusion_pytorch_model.safetensors"
302
+ if not checkpoint.exists():
303
+ fail(f"missing transformer checkpoint: {checkpoint}")
304
+ return checkpoint
305
+
306
+
307
+ def import_mage_transformer_symbols() -> tuple[type[nn.Module], object]:
308
+ if str(MAGE_ROOT) not in sys.path:
309
+ sys.path.insert(0, str(MAGE_ROOT))
310
+ try:
311
+ from mage_flow.models.mage_flow import MageFlow, MageFlowParams
312
+ except Exception as exc:
313
+ fail(f"failed to import local Mage transformer sources from {MAGE_ROOT}: {exc}")
314
+ return MageFlow, MageFlowParams
315
+
316
+
317
+ class PackedNvfp4LinearArtifactModule(nn.Module):
318
+ def __init__(
319
+ self,
320
+ in_features: int,
321
+ out_features: int,
322
+ packed_weight_e2m1: torch.Tensor,
323
+ packed_scales_ue4m3: torch.Tensor,
324
+ weight_tensor_scale: torch.Tensor,
325
+ bias_bf16: torch.Tensor | None,
326
+ ) -> None:
327
+ super().__init__()
328
+ self.in_features = int(in_features)
329
+ self.out_features = int(out_features)
330
+ self.register_buffer("packed_weight_e2m1", packed_weight_e2m1.contiguous())
331
+ self.register_buffer("packed_scales_ue4m3", packed_scales_ue4m3.contiguous())
332
+ self.register_buffer("weight_tensor_scale", weight_tensor_scale.contiguous())
333
+ if bias_bf16 is None:
334
+ self.bias_bf16 = None
335
+ else:
336
+ self.register_buffer("bias_bf16", bias_bf16.contiguous())
337
+
338
+ def forward(self, inputs: torch.Tensor) -> torch.Tensor:
339
+ raise RuntimeError(
340
+ "PackedNvfp4LinearArtifactModule is an artifact-only placeholder. "
341
+ "Attach the resident CUDA runtime before calling forward()."
342
+ )
343
+
344
+ def extra_repr(self) -> str:
345
+ return (
346
+ f"in_features={self.in_features}, out_features={self.out_features}, "
347
+ f"packed_weight_bytes={self.packed_weight_e2m1.numel()}, "
348
+ f"packed_scale_bytes={self.packed_scales_ue4m3.numel()}, "
349
+ f"has_bias={self.bias_bf16 is not None}"
350
+ )
351
+
352
+
353
+ def instantiate_mage_transformer_on_meta(source_repo: Path) -> nn.Module:
354
+ config = load_transformer_config(source_repo)
355
+ MageFlow, MageFlowParams = import_mage_transformer_symbols()
356
+ structure = {
357
+ key: value
358
+ for key, value in config.items()
359
+ if key
360
+ not in {
361
+ "_class_name",
362
+ "txt_max_length",
363
+ "max_sequence_length",
364
+ "param_dtype",
365
+ "packing",
366
+ "schedule_mode",
367
+ "static_shift",
368
+ "use_time_shift",
369
+ "rope_type",
370
+ "apply_text_rotary_emb",
371
+ "mlp_ratio",
372
+ "depth_single_blocks",
373
+ "theta",
374
+ "qkv_bias",
375
+ "guidance_embed",
376
+ "vec_in_dim",
377
+ "vec_type",
378
+ "time_type",
379
+ "double_block_type",
380
+ "quantization_config",
381
+ }
382
+ }
383
+ with torch.device("meta"):
384
+ model = MageFlow(MageFlowParams(**structure))
385
+ return model
386
+
387
+
388
+ def unregistered_meta_tensor_attribute_names(model: nn.Module) -> list[str]:
389
+ """Find direct tensor attributes that PyTorch's parameter/buffer walk misses."""
390
+ names: list[str] = []
391
+ for module_name, module in model.named_modules():
392
+ registered_names = set(module._parameters) | set(module._buffers)
393
+ for attribute_name, value in vars(module).items():
394
+ if attribute_name in registered_names:
395
+ continue
396
+ if isinstance(value, torch.Tensor) and value.is_meta:
397
+ prefix = f"{module_name}." if module_name else ""
398
+ names.append(f"{prefix}{attribute_name}")
399
+ return sorted(names)
400
+
401
+
402
+ def materialize_mage_rope_tensor_attributes(model: nn.Module) -> list[str]:
403
+ """Rebuild Mage's intentionally unregistered complex RoPE tensors on CPU."""
404
+ before = unregistered_meta_tensor_attribute_names(model)
405
+ expected = ["pos_embed.neg_freqs", "pos_embed.pos_freqs"]
406
+ if before != expected:
407
+ fail(
408
+ "unexpected unregistered meta tensor attributes before RoPE "
409
+ f"materialization: {before}"
410
+ )
411
+
412
+ rope = model.get_submodule("pos_embed")
413
+ rope_type = type(rope)
414
+ with torch.device("cpu"):
415
+ materialized = rope_type(
416
+ theta=rope.theta,
417
+ axes_dim=list(rope.axes_dim),
418
+ scale_rope=rope.scale_rope,
419
+ )
420
+ rope.pos_freqs = materialized.pos_freqs
421
+ rope.neg_freqs = materialized.neg_freqs
422
+ rope.video_freq_cache = {}
423
+
424
+ remaining = unregistered_meta_tensor_attribute_names(model)
425
+ if remaining:
426
+ fail(
427
+ "unresolved unregistered meta tensor attributes after RoPE "
428
+ f"materialization: {remaining}"
429
+ )
430
+ return before
431
+
432
+
433
+ def set_child_module(root: nn.Module, dotted_path: str, module: nn.Module) -> None:
434
+ parent_path, _, child_name = dotted_path.rpartition(".")
435
+ parent = root.get_submodule(parent_path) if parent_path else root
436
+ if child_name.isdigit() and isinstance(parent, (nn.Sequential, nn.ModuleList)):
437
+ parent[int(child_name)] = module
438
+ else:
439
+ setattr(parent, child_name, module)
440
+
441
+
442
+ def replace_targets_with_artifact_modules(
443
+ model: nn.Module,
444
+ artifact_path: Path,
445
+ target_specs: Iterable[TargetSpec],
446
+ ) -> None:
447
+ with safe_open(artifact_path, framework="pt", device="cpu") as handle:
448
+ for spec in target_specs:
449
+ packed_weight = handle.get_tensor(spec.artifact_weight_key)
450
+ packed_scales = handle.get_tensor(spec.artifact_scale_key)
451
+ weight_tensor_scale = handle.get_tensor(spec.artifact_tensor_scale_key)
452
+ bias = handle.get_tensor(spec.artifact_bias_key)
453
+ original = model.get_submodule(spec.module_key)
454
+ if not isinstance(original, nn.Linear):
455
+ fail(f"expected target module {spec.module_key} to be nn.Linear")
456
+ replacement = PackedNvfp4LinearArtifactModule(
457
+ in_features=int(original.in_features),
458
+ out_features=int(original.out_features),
459
+ packed_weight_e2m1=packed_weight,
460
+ packed_scales_ue4m3=packed_scales,
461
+ weight_tensor_scale=weight_tensor_scale,
462
+ bias_bf16=bias,
463
+ )
464
+ set_child_module(model, spec.module_key, replacement)
465
+
466
+
467
+ def replace_targets_with_resident_modules(
468
+ model: nn.Module,
469
+ artifact_path: Path,
470
+ target_specs: Iterable[TargetSpec],
471
+ device: torch.device,
472
+ ) -> None:
473
+ if device.type != "cuda":
474
+ fail("resident runtime modules require a CUDA destination")
475
+ if str(RESIDENT_PYTHON_ROOT) not in sys.path:
476
+ sys.path.insert(0, str(RESIDENT_PYTHON_ROOT))
477
+ from torch_ops import PackedNvfp4LinearOp
478
+
479
+ _replace_targets_with_registered_modules(
480
+ model,
481
+ artifact_path,
482
+ target_specs,
483
+ device,
484
+ PackedNvfp4LinearOp,
485
+ )
486
+
487
+
488
+ def replace_targets_with_native_resident_modules(
489
+ model: nn.Module,
490
+ artifact_path: Path,
491
+ target_specs: Iterable[TargetSpec],
492
+ device: torch.device,
493
+ ) -> None:
494
+ if device.type != "cuda":
495
+ fail("native resident runtime modules require a CUDA destination")
496
+ if str(RESIDENT_PYTHON_ROOT) not in sys.path:
497
+ sys.path.insert(0, str(RESIDENT_PYTHON_ROOT))
498
+ from torch_ops_native import (
499
+ PackedNvfp4LinearNativeOp,
500
+ initialize_native_sm120_op,
501
+ )
502
+
503
+ if not initialize_native_sm120_op(allow_python_schema_fallback=False):
504
+ fail("compiled native resident torch op did not load")
505
+ _replace_targets_with_registered_modules(
506
+ model,
507
+ artifact_path,
508
+ target_specs,
509
+ device,
510
+ PackedNvfp4LinearNativeOp,
511
+ )
512
+
513
+
514
+ def _replace_targets_with_registered_modules(
515
+ model: nn.Module,
516
+ artifact_path: Path,
517
+ target_specs: Iterable[TargetSpec],
518
+ device: torch.device,
519
+ module_cls: type[nn.Module],
520
+ ) -> None:
521
+ with safe_open(artifact_path, framework="pt", device="cpu") as handle:
522
+ for spec in target_specs:
523
+ original = model.get_submodule(spec.module_key)
524
+ if not isinstance(original, nn.Linear):
525
+ fail(f"expected target module {spec.module_key} to be nn.Linear")
526
+ replacement = module_cls(
527
+ in_features=int(original.in_features),
528
+ out_features=int(original.out_features),
529
+ packed_weight=handle.get_tensor(spec.artifact_weight_key).to(device),
530
+ weight_scales=handle.get_tensor(spec.artifact_scale_key).to(device),
531
+ weight_scale=handle.get_tensor(
532
+ spec.artifact_tensor_scale_key
533
+ ).to(device),
534
+ bias=handle.get_tensor(spec.artifact_bias_key).to(device),
535
+ )
536
+ set_child_module(model, spec.module_key, replacement)
537
+
538
+
539
+ def assign_tensor_by_name(model: nn.Module, key: str, tensor: torch.Tensor) -> None:
540
+ if "." not in key:
541
+ parent = model
542
+ leaf = key
543
+ else:
544
+ parent_path, _, leaf = key.rpartition(".")
545
+ parent = model.get_submodule(parent_path)
546
+ if leaf in parent._parameters:
547
+ requires_grad = parent._parameters[leaf].requires_grad
548
+ parent._parameters[leaf] = nn.Parameter(tensor, requires_grad=requires_grad)
549
+ return
550
+ if leaf in parent._buffers:
551
+ parent._buffers[leaf] = tensor
552
+ return
553
+ fail(f"destination key {key} was neither a parameter nor a buffer")
554
+
555
+
556
+ def pack_artifact(source_repo: Path, output_dir: Path) -> Path:
557
+ source_repo = source_repo.resolve()
558
+ output_dir = output_dir.resolve()
559
+ if output_dir.exists():
560
+ fail(f"output directory already exists: {output_dir}")
561
+ output_dir.mkdir(parents=True, exist_ok=False)
562
+
563
+ config = load_transformer_config(source_repo)
564
+ checkpoint_path = source_transformer_checkpoint(source_repo)
565
+ target_specs = build_target_specs(int(config["depth"]))
566
+ target_weight_keys = {spec.weight_key for spec in target_specs}
567
+ target_bias_keys = {spec.bias_key for spec in target_specs}
568
+ target_keys = target_weight_keys | target_bias_keys
569
+
570
+ artifact_tensors: OrderedDict[str, torch.Tensor] = OrderedDict()
571
+ target_metadata: list[dict] = []
572
+
573
+ with safe_open(checkpoint_path, framework="pt", device="cpu") as handle:
574
+ source_keys = list(handle.keys())
575
+ source_key_set = set(source_keys)
576
+ missing = sorted(target_keys - source_key_set)
577
+ if missing:
578
+ fail(f"source checkpoint is missing {len(missing)} target tensors, first={missing[0]}")
579
+
580
+ for spec in target_specs:
581
+ weight = handle.get_tensor(spec.weight_key)
582
+ bias = handle.get_tensor(spec.bias_key)
583
+ if weight.dtype != torch.bfloat16:
584
+ fail(f"{spec.weight_key} expected bfloat16, found {weight.dtype}")
585
+ if bias.dtype != torch.bfloat16:
586
+ fail(f"{spec.bias_key} expected bfloat16, found {bias.dtype}")
587
+ packed_weight, packed_scales, weight_tensor_scale, global_amax = pack_weight_tensor(weight)
588
+ scale_layout = make_scale_layout(weight.shape[1], weight.shape[0])
589
+ artifact_tensors[spec.artifact_bias_key] = bias.contiguous()
590
+ artifact_tensors[spec.artifact_scale_key] = packed_scales
591
+ artifact_tensors[spec.artifact_tensor_scale_key] = weight_tensor_scale
592
+ artifact_tensors[spec.artifact_weight_key] = packed_weight
593
+ target_metadata.append(
594
+ {
595
+ "module_key": spec.module_key,
596
+ "weight_key": spec.weight_key,
597
+ "bias_key": spec.bias_key,
598
+ "weight_shape": list(weight.shape),
599
+ "bias_shape": list(bias.shape),
600
+ "weight_tensor_scale_key": spec.artifact_tensor_scale_key,
601
+ "artifact_weight_key": spec.artifact_weight_key,
602
+ "artifact_scale_key": spec.artifact_scale_key,
603
+ "artifact_bias_key": spec.artifact_bias_key,
604
+ "weight_tensor_scale": float(weight_tensor_scale.item()),
605
+ "weight_global_amax": global_amax,
606
+ "packed_weight_bytes": int(packed_weight.numel()),
607
+ "packed_scale_bytes": int(packed_scales.numel()),
608
+ "scale_layout": asdict(scale_layout),
609
+ "source_weight_sha256": sha256_tensor(weight),
610
+ "source_bias_sha256": sha256_tensor(bias),
611
+ }
612
+ )
613
+
614
+ non_target_keys = sorted(set(source_keys) - target_keys)
615
+ artifact_path = output_dir / "packed_transformer.safetensors"
616
+ save_file(
617
+ OrderedDict(sorted(artifact_tensors.items())),
618
+ artifact_path,
619
+ metadata=CANONICAL_SAFETENSORS_METADATA,
620
+ )
621
+ fsync_file(artifact_path)
622
+ fsync_directory(output_dir)
623
+
624
+ library_hashes = {
625
+ "artifact_script_sha256": sha256_file(ARTIFACT_SCRIPT_PATH),
626
+ "resident_source_sha256": sha256_file(RESIDENT_SOURCE),
627
+ "resident_library_sha256": sha256_file(RESIDENT_LIBRARY),
628
+ "mage_flow_py_sha256": sha256_file(MAGE_ROOT / "mage_flow" / "models" / "mage_flow.py"),
629
+ "mage_layers_py_sha256": sha256_file(MAGE_ROOT / "mage_flow" / "models" / "modules" / "mage_layers.py"),
630
+ "pipeline_py_sha256": sha256_file(MAGE_ROOT / "mage_flow" / "pipeline.py"),
631
+ }
632
+
633
+ metadata = OrderedDict(
634
+ (
635
+ ("format_version", FORMAT_VERSION),
636
+ ("artifact_kind", ARTIFACT_KIND),
637
+ ("created_utc", datetime.now(timezone.utc).isoformat()),
638
+ (
639
+ "container",
640
+ {
641
+ "format": "safetensors",
642
+ "header_metadata": CANONICAL_SAFETENSORS_METADATA,
643
+ "header_encoding": (
644
+ "single deterministic contract key; legacy two-key "
645
+ "draft headers remain readable"
646
+ ),
647
+ },
648
+ ),
649
+ (
650
+ "source",
651
+ OrderedDict(
652
+ (
653
+ ("transformer_config_path", str(source_repo / "transformer" / "config.json")),
654
+ ("transformer_checkpoint_path", str(checkpoint_path)),
655
+ ("transformer_config_sha256", sha256_file(source_repo / "transformer" / "config.json")),
656
+ ("transformer_checkpoint_sha256", sha256_file(checkpoint_path)),
657
+ )
658
+ ),
659
+ ),
660
+ ("library_hashes", library_hashes),
661
+ (
662
+ "environment",
663
+ {
664
+ "python_version": sys.version,
665
+ "torch_version": torch.__version__,
666
+ "safetensors_version": safetensors.__version__,
667
+ },
668
+ ),
669
+ (
670
+ "model",
671
+ {
672
+ "depth": int(config["depth"]),
673
+ "hidden_size": int(config["hidden_size"]),
674
+ "num_heads": int(config["num_heads"]),
675
+ "context_in_dim": int(config["context_in_dim"]),
676
+ "in_channels": int(config["in_channels"]),
677
+ "out_channels": int(config["out_channels"]),
678
+ "patch_size": int(config["patch_size"]),
679
+ },
680
+ ),
681
+ (
682
+ "quantization",
683
+ {
684
+ "format": "nvfp4_two_level",
685
+ "block_elements": FP4_BLOCK_ELEMENTS,
686
+ "scale_tile_outer": SCALE_TILE_OUTER,
687
+ "scale_tile_inner": SCALE_TILE_INNER,
688
+ "bias_policy": "artifact_bfloat16",
689
+ },
690
+ ),
691
+ ("targets", target_metadata),
692
+ ("non_target_keys", non_target_keys),
693
+ )
694
+ )
695
+ write_bytes_once(
696
+ output_dir / "metadata.json",
697
+ (json.dumps(metadata, indent=2, sort_keys=False) + "\n").encode("utf-8"),
698
+ )
699
+ return output_dir
700
+
701
+
702
+ def load_validated_artifact_metadata(
703
+ artifact_dir: Path,
704
+ source_repo: Path,
705
+ *,
706
+ require_resident_runtime: bool = False,
707
+ ) -> dict:
708
+ artifact_dir = artifact_dir.resolve()
709
+ source_repo = source_repo.resolve()
710
+ metadata_path = artifact_dir / "metadata.json"
711
+ artifact_path = artifact_dir / "packed_transformer.safetensors"
712
+ if not metadata_path.is_file() or not artifact_path.is_file():
713
+ fail(f"artifact dir missing metadata or safetensors: {artifact_dir}")
714
+ try:
715
+ metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
716
+ except (OSError, json.JSONDecodeError) as exc:
717
+ fail(f"invalid artifact metadata {metadata_path}: {exc}")
718
+ if metadata.get("format_version") != FORMAT_VERSION:
719
+ fail(f"unsupported format_version: {metadata.get('format_version')}")
720
+ if metadata.get("artifact_kind") != ARTIFACT_KIND:
721
+ fail(f"unexpected artifact_kind: {metadata.get('artifact_kind')}")
722
+
723
+ config_path = source_repo / "transformer" / "config.json"
724
+ checkpoint_path = source_transformer_checkpoint(source_repo)
725
+ expected_config_hash = sha256_file(config_path)
726
+ expected_checkpoint_hash = sha256_file(checkpoint_path)
727
+ try:
728
+ source_metadata = metadata["source"]
729
+ recorded_config_hash = source_metadata["transformer_config_sha256"]
730
+ recorded_checkpoint_hash = source_metadata[
731
+ "transformer_checkpoint_sha256"
732
+ ]
733
+ model_metadata = metadata["model"]
734
+ recorded_depth = int(model_metadata["depth"])
735
+ recorded_targets = metadata["targets"]
736
+ recorded_non_target_keys = metadata["non_target_keys"]
737
+ except (KeyError, TypeError, ValueError) as exc:
738
+ fail(f"artifact metadata schema is incomplete or invalid: {exc}")
739
+ if not isinstance(recorded_targets, list):
740
+ fail("artifact metadata targets must be a list")
741
+ if not isinstance(recorded_non_target_keys, list) or not all(
742
+ isinstance(key, str) for key in recorded_non_target_keys
743
+ ):
744
+ fail("artifact metadata non_target_keys must be a list of strings")
745
+ if recorded_config_hash != expected_config_hash:
746
+ fail("transformer config hash mismatch")
747
+ if recorded_checkpoint_hash != expected_checkpoint_hash:
748
+ fail("transformer checkpoint hash mismatch")
749
+
750
+ config = load_transformer_config(source_repo)
751
+ if recorded_depth != int(config["depth"]):
752
+ fail("artifact model depth does not match source config")
753
+ specs = build_target_specs(int(config["depth"]))
754
+ expected_modules = [spec.module_key for spec in specs]
755
+ if not all(isinstance(entry, dict) for entry in recorded_targets):
756
+ fail("artifact metadata target entries must be objects")
757
+ recorded_modules = [entry.get("module_key") for entry in recorded_targets]
758
+ if recorded_modules != expected_modules:
759
+ fail("artifact target allowlist/order mismatch")
760
+ target_source_keys = {
761
+ key for spec in specs for key in (spec.weight_key, spec.bias_key)
762
+ }
763
+ expected_artifact_keys = {
764
+ key
765
+ for spec in specs
766
+ for key in (
767
+ spec.artifact_weight_key,
768
+ spec.artifact_scale_key,
769
+ spec.artifact_tensor_scale_key,
770
+ spec.artifact_bias_key,
771
+ )
772
+ }
773
+ with safe_open(artifact_path, framework="pt", device="cpu") as artifact_handle:
774
+ actual_artifact_keys = set(artifact_handle.keys())
775
+ header_metadata = artifact_handle.metadata()
776
+ if actual_artifact_keys != expected_artifact_keys:
777
+ fail("artifact tensor key coverage mismatch")
778
+ if header_metadata not in (
779
+ CANONICAL_SAFETENSORS_METADATA,
780
+ LEGACY_SAFETENSORS_METADATA,
781
+ ):
782
+ fail("artifact safetensors header metadata mismatch")
783
+
784
+ with safe_open(checkpoint_path, framework="pt", device="cpu") as source_handle:
785
+ source_keys = set(source_handle.keys())
786
+ missing_target_keys = sorted(target_source_keys - source_keys)
787
+ if missing_target_keys:
788
+ fail(
789
+ "source checkpoint is missing target tensors, first="
790
+ f"{missing_target_keys[0]}"
791
+ )
792
+ expected_non_target_keys = sorted(source_keys - target_source_keys)
793
+ if recorded_non_target_keys != expected_non_target_keys:
794
+ fail("artifact non-target source manifest mismatch")
795
+
796
+ if require_resident_runtime:
797
+ hashes = metadata.get("library_hashes", {})
798
+ if not isinstance(hashes, dict):
799
+ fail("artifact metadata library_hashes must be an object")
800
+ if hashes.get("resident_source_sha256") != sha256_file(RESIDENT_SOURCE):
801
+ fail("resident source hash mismatch")
802
+ if hashes.get("resident_library_sha256") != sha256_file(RESIDENT_LIBRARY):
803
+ fail("resident library hash mismatch")
804
+ return metadata
805
+
806
+
807
+ def validate_artifact(artifact_dir: Path, source_repo: Path) -> None:
808
+ artifact_dir = artifact_dir.resolve()
809
+ source_repo = source_repo.resolve()
810
+ metadata = load_validated_artifact_metadata(artifact_dir, source_repo)
811
+ artifact_path = artifact_dir / "packed_transformer.safetensors"
812
+ checkpoint_path = source_transformer_checkpoint(source_repo)
813
+ config = load_transformer_config(source_repo)
814
+ specs = {
815
+ spec.module_key: spec for spec in build_target_specs(int(config["depth"]))
816
+ }
817
+
818
+ with safe_open(artifact_path, framework="pt", device="cpu") as artifact_handle, safe_open(
819
+ checkpoint_path, framework="pt", device="cpu"
820
+ ) as source_handle:
821
+ for entry in metadata["targets"]:
822
+ spec = specs[entry["module_key"]]
823
+ weight = source_handle.get_tensor(spec.weight_key)
824
+ bias = source_handle.get_tensor(spec.bias_key)
825
+ packed_weight, packed_scales, weight_tensor_scale, global_amax = pack_weight_tensor(weight)
826
+
827
+ candidate_weight = artifact_handle.get_tensor(spec.artifact_weight_key)
828
+ candidate_scales = artifact_handle.get_tensor(spec.artifact_scale_key)
829
+ candidate_tensor_scale = artifact_handle.get_tensor(spec.artifact_tensor_scale_key)
830
+ candidate_bias = artifact_handle.get_tensor(spec.artifact_bias_key)
831
+
832
+ if not torch.equal(candidate_weight, packed_weight):
833
+ fail(f"packed weight mismatch for {spec.module_key}")
834
+ if not torch.equal(candidate_scales, packed_scales):
835
+ fail(f"packed scales mismatch for {spec.module_key}")
836
+ if not torch.equal(candidate_tensor_scale, weight_tensor_scale):
837
+ fail(f"tensor scale mismatch for {spec.module_key}")
838
+ if not torch.equal(candidate_bias, bias):
839
+ fail(f"bias mismatch for {spec.module_key}")
840
+ if abs(float(entry["weight_global_amax"]) - global_amax) > 0.0:
841
+ fail(f"global amax mismatch for {spec.module_key}")
842
+
843
+
844
+ def load_clean_transformer_from_artifact(
845
+ artifact_dir: Path,
846
+ source_repo: Path,
847
+ assign_non_target: bool = True,
848
+ ) -> nn.Module:
849
+ artifact_dir = artifact_dir.resolve()
850
+ source_repo = source_repo.resolve()
851
+ metadata = load_validated_artifact_metadata(artifact_dir, source_repo)
852
+ target_specs = build_target_specs(int(metadata["model"]["depth"]))
853
+ model = instantiate_mage_transformer_on_meta(source_repo)
854
+ replace_targets_with_artifact_modules(model, artifact_dir / "packed_transformer.safetensors", target_specs)
855
+
856
+ if assign_non_target:
857
+ skip_keys = {
858
+ key
859
+ for spec in target_specs
860
+ for key in (spec.weight_key, spec.bias_key)
861
+ }
862
+ source_tensor_keys_read: list[str] = []
863
+ with safe_open(source_transformer_checkpoint(source_repo), framework="pt", device="cpu") as source_handle:
864
+ for key in source_handle.keys():
865
+ if key in skip_keys:
866
+ continue
867
+ tensor = source_handle.get_tensor(key)
868
+ source_tensor_keys_read.append(key)
869
+ assign_tensor_by_name(model, key, tensor)
870
+ target_reads = sorted(set(source_tensor_keys_read) & skip_keys)
871
+ if target_reads:
872
+ fail(f"clean CPU loader read target source tensors: {target_reads[0]}")
873
+ meta_parameters = [
874
+ name for name, parameter in model.named_parameters() if parameter.is_meta
875
+ ]
876
+ meta_buffers = [
877
+ name for name, buffer in model.named_buffers() if buffer.is_meta
878
+ ]
879
+ if meta_parameters or meta_buffers:
880
+ first = (meta_parameters + meta_buffers)[0]
881
+ fail(f"clean CPU loader left unresolved meta tensors, first={first}")
882
+ materialize_mage_rope_tensor_attributes(model)
883
+ return model
884
+
885
+
886
+ def load_clean_resident_transformer(
887
+ artifact_dir: Path,
888
+ source_repo: Path,
889
+ device: torch.device,
890
+ ) -> tuple[nn.Module, dict]:
891
+ return _load_clean_cuda_transformer(
892
+ artifact_dir,
893
+ source_repo,
894
+ device,
895
+ replace_targets_with_resident_modules,
896
+ )
897
+
898
+
899
+ def load_clean_native_resident_transformer(
900
+ artifact_dir: Path,
901
+ source_repo: Path,
902
+ device: torch.device,
903
+ ) -> tuple[nn.Module, dict]:
904
+ return _load_clean_cuda_transformer(
905
+ artifact_dir,
906
+ source_repo,
907
+ device,
908
+ replace_targets_with_native_resident_modules,
909
+ )
910
+
911
+
912
+ def _load_clean_cuda_transformer(
913
+ artifact_dir: Path,
914
+ source_repo: Path,
915
+ device: torch.device,
916
+ target_replacement_fn: Callable[[nn.Module, Path, Iterable[TargetSpec], torch.device], None],
917
+ ) -> tuple[nn.Module, dict]:
918
+ artifact_dir = artifact_dir.resolve()
919
+ source_repo = source_repo.resolve()
920
+ metadata = load_validated_artifact_metadata(
921
+ artifact_dir,
922
+ source_repo,
923
+ require_resident_runtime=True,
924
+ )
925
+ target_specs = build_target_specs(int(metadata["model"]["depth"]))
926
+ target_source_keys = {
927
+ key
928
+ for spec in target_specs
929
+ for key in (spec.weight_key, spec.bias_key)
930
+ }
931
+ model = instantiate_mage_transformer_on_meta(source_repo)
932
+ target_replacement_fn(
933
+ model,
934
+ artifact_dir / "packed_transformer.safetensors",
935
+ target_specs,
936
+ device,
937
+ )
938
+
939
+ loaded_source_keys: list[str] = []
940
+ skipped_target_source_keys: list[str] = []
941
+ source_tensor_keys_read: list[str] = []
942
+ with safe_open(
943
+ source_transformer_checkpoint(source_repo),
944
+ framework="pt",
945
+ device="cpu",
946
+ ) as source_handle:
947
+ source_keys = list(source_handle.keys())
948
+ missing_target_keys = sorted(target_source_keys - set(source_keys))
949
+ if missing_target_keys:
950
+ fail(
951
+ "source checkpoint is missing target tensors, first="
952
+ f"{missing_target_keys[0]}"
953
+ )
954
+ for key in source_keys:
955
+ if key in target_source_keys:
956
+ skipped_target_source_keys.append(key)
957
+ continue
958
+ tensor = source_handle.get_tensor(key)
959
+ source_tensor_keys_read.append(key)
960
+ assign_tensor_by_name(model, key, tensor.to(device))
961
+ loaded_source_keys.append(key)
962
+
963
+ materialized_tensor_attributes = materialize_mage_rope_tensor_attributes(model)
964
+ target_source_reads = sorted(
965
+ set(source_tensor_keys_read) & target_source_keys
966
+ )
967
+ meta_parameters = [
968
+ name for name, parameter in model.named_parameters() if parameter.is_meta
969
+ ]
970
+ meta_buffers = [
971
+ name for name, buffer in model.named_buffers() if buffer.is_meta
972
+ ]
973
+ report = {
974
+ "source_tensor_count_loaded": len(loaded_source_keys),
975
+ "source_tensor_keys_loaded": loaded_source_keys,
976
+ "source_tensor_count_read": len(source_tensor_keys_read),
977
+ "source_tensor_keys_read": source_tensor_keys_read,
978
+ "target_source_tensor_count_skipped": len(skipped_target_source_keys),
979
+ "target_source_tensor_keys_skipped": skipped_target_source_keys,
980
+ "target_source_tensor_reads": len(target_source_reads),
981
+ "target_source_tensor_keys_read": target_source_reads,
982
+ "meta_parameter_names": meta_parameters,
983
+ "meta_buffer_names": meta_buffers,
984
+ "materialized_unregistered_tensor_attribute_names": (
985
+ materialized_tensor_attributes
986
+ ),
987
+ "unregistered_meta_tensor_attribute_names": (
988
+ unregistered_meta_tensor_attribute_names(model)
989
+ ),
990
+ }
991
+ return model.eval().requires_grad_(False), report
992
+
993
+
994
+ def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
995
+ parser = argparse.ArgumentParser(description=__doc__)
996
+ subparsers = parser.add_subparsers(dest="command", required=True)
997
+
998
+ pack_parser = subparsers.add_parser("pack", help="build a packed artifact directory")
999
+ pack_parser.add_argument("--source-repo", type=Path, required=True)
1000
+ pack_parser.add_argument("--output-dir", type=Path, required=True)
1001
+
1002
+ validate_parser = subparsers.add_parser("validate", help="recompute and validate a packed artifact")
1003
+ validate_parser.add_argument("--artifact-dir", type=Path, required=True)
1004
+ validate_parser.add_argument("--source-repo", type=Path, required=True)
1005
+
1006
+ plan_load_parser = subparsers.add_parser("plan-load", help="instantiate on meta and replace target modules")
1007
+ plan_load_parser.add_argument("--artifact-dir", type=Path, required=True)
1008
+ plan_load_parser.add_argument("--source-repo", type=Path, required=True)
1009
+ plan_load_parser.add_argument("--skip-non-target", action="store_true")
1010
+
1011
+ runtime_parser = subparsers.add_parser(
1012
+ "validate-runtime",
1013
+ help="placeholder for future single-GPU resident validation",
1014
+ )
1015
+ runtime_parser.add_argument("--artifact-dir", type=Path, required=True)
1016
+ runtime_parser.add_argument("--source-repo", type=Path, required=True)
1017
+ return parser.parse_args(argv)
1018
+
1019
+
1020
+ def main(argv: list[str] | None = None) -> int:
1021
+ args = parse_args(argv)
1022
+ try:
1023
+ if args.command == "pack":
1024
+ artifact_dir = pack_artifact(args.source_repo, args.output_dir)
1025
+ print(artifact_dir)
1026
+ return 0
1027
+ if args.command == "validate":
1028
+ validate_artifact(args.artifact_dir, args.source_repo)
1029
+ print("ok")
1030
+ return 0
1031
+ if args.command == "plan-load":
1032
+ model = load_clean_transformer_from_artifact(
1033
+ args.artifact_dir, args.source_repo, assign_non_target=not args.skip_non_target
1034
+ )
1035
+ packed_count = sum(
1036
+ 1 for _name, module in model.named_modules() if isinstance(module, PackedNvfp4LinearArtifactModule)
1037
+ )
1038
+ meta_parameters = [
1039
+ name for name, parameter in model.named_parameters() if parameter.is_meta
1040
+ ]
1041
+ meta_buffers = [
1042
+ name for name, buffer in model.named_buffers() if buffer.is_meta
1043
+ ]
1044
+ print(
1045
+ json.dumps(
1046
+ {
1047
+ "packed_module_count": packed_count,
1048
+ "meta_parameter_count": len(meta_parameters),
1049
+ "meta_buffer_count": len(meta_buffers),
1050
+ "non_target_assignment_skipped": bool(args.skip_non_target),
1051
+ },
1052
+ indent=2,
1053
+ )
1054
+ )
1055
+ return 0
1056
+ if args.command == "validate-runtime":
1057
+ fail(
1058
+ "validate-runtime is intentionally not implemented in this CPU-only slice. "
1059
+ "Use the future CUDA resident path on CUDA_VISIBLE_DEVICES=3."
1060
+ )
1061
+ fail(f"unsupported command: {args.command}")
1062
+ except PackedArtifactError as exc:
1063
+ print(f"error: {exc}", file=sys.stderr)
1064
+ return 1
1065
+
1066
+
1067
+ if __name__ == "__main__":
1068
+ raise SystemExit(main())
runtime/packed_nvfp4_linear.py ADDED
@@ -0,0 +1,485 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Inference-only resident NVFP4 linear prototype.
2
+
3
+ This module intentionally uses a narrow ctypes boundary. It proves packed
4
+ residency and Mage shape correctness; it is not yet a torch.compile/CUDA-graph
5
+ shipping operator.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import atexit
11
+ import ctypes
12
+ import threading
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import Final
16
+
17
+ import torch
18
+ from torch import nn
19
+
20
+
21
+ ABI_VERSION: Final = 1
22
+ FP4_BLOCK_ELEMENTS: Final = 16
23
+ SCALE_TILE_OUTER: Final = 128
24
+ SCALE_TILE_INNER: Final = 4
25
+ RELEASE_ROOT: Final = Path(__file__).resolve().parents[1]
26
+ DEFAULT_LIBRARY_PATH: Final = RELEASE_ROOT / "runtime" / "libmage_nvfp4_linear.so"
27
+
28
+
29
+ def round_up(value: int, multiple: int) -> int:
30
+ if value <= 0 or multiple <= 0:
31
+ raise ValueError("value and multiple must be positive")
32
+ return ((value + multiple - 1) // multiple) * multiple
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class ScaleLayout:
37
+ inner_dim: int
38
+ outer_tiles: int
39
+ num_bytes: int
40
+
41
+
42
+ def scale_layout(k: int, outer_columns: int) -> ScaleLayout:
43
+ if k <= 0 or k % FP4_BLOCK_ELEMENTS:
44
+ raise ValueError("K must be positive and divisible by 16")
45
+ if outer_columns <= 0:
46
+ raise ValueError("outer column count must be positive")
47
+ inner_dim = round_up(k // FP4_BLOCK_ELEMENTS, SCALE_TILE_INNER)
48
+ outer_tiles = (outer_columns + SCALE_TILE_OUTER - 1) // SCALE_TILE_OUTER
49
+ return ScaleLayout(
50
+ inner_dim=inner_dim,
51
+ outer_tiles=outer_tiles,
52
+ num_bytes=outer_tiles * inner_dim * SCALE_TILE_OUTER,
53
+ )
54
+
55
+
56
+ def packed_weight_num_bytes(out_features: int, in_features: int) -> int:
57
+ if out_features <= 0 or out_features % 8:
58
+ raise ValueError("out_features must be positive and divisible by 8")
59
+ if in_features <= 0 or in_features % 32:
60
+ raise ValueError("in_features must be positive and divisible by 32")
61
+ return out_features * in_features // 2
62
+
63
+
64
+ def padded_output_shape(input_shape: tuple[int, ...], out_features: int) -> tuple[int, int]:
65
+ if not input_shape:
66
+ raise ValueError("input must have at least one dimension")
67
+ logical_m = 1
68
+ for dimension in input_shape[:-1]:
69
+ if dimension <= 0:
70
+ raise ValueError("empty or negative leading dimensions are unsupported")
71
+ logical_m *= dimension
72
+ return round_up(logical_m, 8), out_features
73
+
74
+
75
+ def logical_output_shape(input_shape: tuple[int, ...], out_features: int) -> tuple[int, ...]:
76
+ if not input_shape:
77
+ raise ValueError("input must have at least one dimension")
78
+ return (*input_shape[:-1], out_features)
79
+
80
+
81
+ class NativeNvfp4Library:
82
+ """Typed ctypes access to the project-local native library."""
83
+
84
+ def __init__(self, path: str | Path = DEFAULT_LIBRARY_PATH):
85
+ self.path = Path(path).resolve()
86
+ if not self.path.is_file():
87
+ raise FileNotFoundError(
88
+ f"resident NVFP4 library is not built: {self.path}"
89
+ )
90
+ self._library = ctypes.CDLL(str(self.path))
91
+ self._bind()
92
+ version = int(self._library.mage_nvfp4_abi_version())
93
+ if version != ABI_VERSION:
94
+ raise RuntimeError(
95
+ f"resident NVFP4 ABI mismatch: Python={ABI_VERSION}, native={version}"
96
+ )
97
+
98
+ def _bind(self) -> None:
99
+ library = self._library
100
+ library.mage_nvfp4_abi_version.argtypes = []
101
+ library.mage_nvfp4_abi_version.restype = ctypes.c_int
102
+ library.mage_nvfp4_last_error.argtypes = []
103
+ library.mage_nvfp4_last_error.restype = ctypes.c_char_p
104
+ library.mage_nvfp4_packed_weight_bytes.argtypes = [
105
+ ctypes.c_int,
106
+ ctypes.c_int,
107
+ ]
108
+ library.mage_nvfp4_packed_weight_bytes.restype = ctypes.c_size_t
109
+ library.mage_nvfp4_weight_scale_bytes.argtypes = [
110
+ ctypes.c_int,
111
+ ctypes.c_int,
112
+ ]
113
+ library.mage_nvfp4_weight_scale_bytes.restype = ctypes.c_size_t
114
+ library.mage_nvfp4_pack_weight_bf16.argtypes = [
115
+ ctypes.c_void_p,
116
+ ctypes.c_int,
117
+ ctypes.c_int,
118
+ ctypes.c_void_p,
119
+ ctypes.c_size_t,
120
+ ctypes.c_void_p,
121
+ ctypes.c_size_t,
122
+ ctypes.POINTER(ctypes.c_float),
123
+ ]
124
+ library.mage_nvfp4_pack_weight_bf16.restype = ctypes.c_int
125
+ library.mage_nvfp4_create_context.argtypes = [
126
+ ctypes.c_int,
127
+ ctypes.POINTER(ctypes.c_void_p),
128
+ ]
129
+ library.mage_nvfp4_create_context.restype = ctypes.c_int
130
+ library.mage_nvfp4_destroy_context.argtypes = [ctypes.c_void_p]
131
+ library.mage_nvfp4_destroy_context.restype = ctypes.c_int
132
+ library.mage_nvfp4_context_reserved_bytes.argtypes = [ctypes.c_void_p]
133
+ library.mage_nvfp4_context_reserved_bytes.restype = ctypes.c_size_t
134
+ library.mage_nvfp4_linear_forward.argtypes = [
135
+ ctypes.c_void_p,
136
+ ctypes.c_void_p,
137
+ ctypes.c_void_p,
138
+ ctypes.c_size_t,
139
+ ctypes.c_void_p,
140
+ ctypes.c_size_t,
141
+ ctypes.c_void_p,
142
+ ctypes.c_void_p,
143
+ ctypes.c_void_p,
144
+ ctypes.c_int,
145
+ ctypes.c_int,
146
+ ctypes.c_int,
147
+ ctypes.c_size_t,
148
+ ]
149
+ library.mage_nvfp4_linear_forward.restype = ctypes.c_int
150
+
151
+ def _error(self, operation: str) -> RuntimeError:
152
+ raw = self._library.mage_nvfp4_last_error()
153
+ message = raw.decode("utf-8", errors="replace") if raw else "unknown error"
154
+ return RuntimeError(f"{operation}: {message}")
155
+
156
+ def pack_weight(
157
+ self, weight: torch.Tensor
158
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
159
+ if weight.device.type != "cpu":
160
+ raise ValueError("native weight packer requires a CPU tensor")
161
+ if weight.dtype != torch.bfloat16:
162
+ raise TypeError("native weight packer requires BF16")
163
+ if weight.ndim != 2 or not weight.is_contiguous():
164
+ raise ValueError("weight must be contiguous [N,K]")
165
+ n, k = map(int, weight.shape)
166
+ packed_bytes = packed_weight_num_bytes(n, k)
167
+ scale_bytes = scale_layout(k, n).num_bytes
168
+ native_packed = int(
169
+ self._library.mage_nvfp4_packed_weight_bytes(n, k)
170
+ )
171
+ native_scales = int(
172
+ self._library.mage_nvfp4_weight_scale_bytes(n, k)
173
+ )
174
+ if (native_packed, native_scales) != (packed_bytes, scale_bytes):
175
+ raise RuntimeError(
176
+ "Python/native packed metadata disagreement: "
177
+ f"Python={(packed_bytes, scale_bytes)}, "
178
+ f"native={(native_packed, native_scales)}"
179
+ )
180
+ packed = torch.empty(packed_bytes, dtype=torch.uint8, device="cpu")
181
+ scales = torch.empty(scale_bytes, dtype=torch.uint8, device="cpu")
182
+ tensor_scale = ctypes.c_float()
183
+ status = self._library.mage_nvfp4_pack_weight_bf16(
184
+ ctypes.c_void_p(weight.data_ptr()),
185
+ n,
186
+ k,
187
+ ctypes.c_void_p(packed.data_ptr()),
188
+ packed.numel(),
189
+ ctypes.c_void_p(scales.data_ptr()),
190
+ scales.numel(),
191
+ ctypes.byref(tensor_scale),
192
+ )
193
+ if status:
194
+ raise self._error("packing BF16 weight")
195
+ scale_tensor = torch.tensor(tensor_scale.value, dtype=torch.float32)
196
+ return packed, scales, scale_tensor
197
+
198
+
199
+ _LIBRARY_LOCK = threading.Lock()
200
+ _LIBRARY: NativeNvfp4Library | None = None
201
+
202
+
203
+ def native_library() -> NativeNvfp4Library:
204
+ global _LIBRARY
205
+ with _LIBRARY_LOCK:
206
+ if _LIBRARY is None:
207
+ _LIBRARY = NativeNvfp4Library()
208
+ return _LIBRARY
209
+
210
+
211
+ class ResidentContext:
212
+ def __init__(self, library: NativeNvfp4Library, device_index: int, stream: int):
213
+ self.library = library
214
+ self.device_index = int(device_index)
215
+ self.stream = int(stream)
216
+ self._pointer = ctypes.c_void_p()
217
+ self._lock = threading.Lock()
218
+ status = self.library._library.mage_nvfp4_create_context(
219
+ self.device_index, ctypes.byref(self._pointer)
220
+ )
221
+ if status:
222
+ raise self.library._error("creating resident context")
223
+ self._closed = False
224
+
225
+ @property
226
+ def pointer(self) -> ctypes.c_void_p:
227
+ if self._closed:
228
+ raise RuntimeError("resident NVFP4 context is closed")
229
+ return self._pointer
230
+
231
+ @property
232
+ def reserved_bytes(self) -> int:
233
+ return int(
234
+ self.library._library.mage_nvfp4_context_reserved_bytes(self.pointer)
235
+ )
236
+
237
+ def close(self) -> None:
238
+ with self._lock:
239
+ if self._closed:
240
+ return
241
+ status = self.library._library.mage_nvfp4_destroy_context(
242
+ self._pointer
243
+ )
244
+ if status:
245
+ raise self.library._error("destroying resident context")
246
+ self._closed = True
247
+ self._pointer = ctypes.c_void_p()
248
+
249
+ def forward(
250
+ self,
251
+ x: torch.Tensor,
252
+ packed_weight: torch.Tensor,
253
+ weight_scales: torch.Tensor,
254
+ weight_scale: torch.Tensor,
255
+ bias: torch.Tensor | None,
256
+ output: torch.Tensor,
257
+ logical_m: int,
258
+ in_features: int,
259
+ out_features: int,
260
+ ) -> None:
261
+ bias_pointer = (
262
+ ctypes.c_void_p(bias.data_ptr()) if bias is not None else None
263
+ )
264
+ with self._lock:
265
+ status = self.library._library.mage_nvfp4_linear_forward(
266
+ self.pointer,
267
+ ctypes.c_void_p(x.data_ptr()),
268
+ ctypes.c_void_p(packed_weight.data_ptr()),
269
+ packed_weight.numel(),
270
+ ctypes.c_void_p(weight_scales.data_ptr()),
271
+ weight_scales.numel(),
272
+ ctypes.c_void_p(weight_scale.data_ptr()),
273
+ bias_pointer,
274
+ ctypes.c_void_p(output.data_ptr()),
275
+ logical_m,
276
+ in_features,
277
+ out_features,
278
+ self.stream,
279
+ )
280
+ if status:
281
+ raise self.library._error("resident NVFP4 linear forward")
282
+
283
+
284
+ _CONTEXTS_LOCK = threading.Lock()
285
+ _CONTEXTS: dict[tuple[int, int], ResidentContext] = {}
286
+
287
+
288
+ def resident_context(device: torch.device) -> ResidentContext:
289
+ if device.type != "cuda":
290
+ raise ValueError("resident NVFP4 context requires CUDA")
291
+ device_index = (
292
+ torch.cuda.current_device() if device.index is None else int(device.index)
293
+ )
294
+ stream = int(torch.cuda.current_stream(device_index).cuda_stream)
295
+ key = (device_index, stream)
296
+ with _CONTEXTS_LOCK:
297
+ context = _CONTEXTS.get(key)
298
+ if context is None:
299
+ context = ResidentContext(native_library(), device_index, stream)
300
+ _CONTEXTS[key] = context
301
+ return context
302
+
303
+
304
+ def close_all_contexts() -> None:
305
+ with _CONTEXTS_LOCK:
306
+ contexts = list(_CONTEXTS.values())
307
+ _CONTEXTS.clear()
308
+ errors: list[Exception] = []
309
+ for context in contexts:
310
+ try:
311
+ context.close()
312
+ except Exception as error: # pragma: no cover - shutdown diagnostic
313
+ errors.append(error)
314
+ if errors:
315
+ raise RuntimeError(
316
+ "one or more resident NVFP4 contexts failed to close: "
317
+ + "; ".join(map(str, errors))
318
+ )
319
+
320
+
321
+ def _quiet_atexit_close() -> None:
322
+ try:
323
+ close_all_contexts()
324
+ except Exception:
325
+ # CUDA may already be shutting down. Explicit close_all_contexts() is
326
+ # the auditable path; atexit is only a best-effort fallback.
327
+ pass
328
+
329
+
330
+ atexit.register(_quiet_atexit_close)
331
+
332
+
333
+ class PackedNvfp4Linear(nn.Module):
334
+ """Packed inference replacement for one BF16 ``nn.Linear``."""
335
+
336
+ def __init__(
337
+ self,
338
+ in_features: int,
339
+ out_features: int,
340
+ packed_weight: torch.Tensor,
341
+ weight_scales: torch.Tensor,
342
+ weight_scale: torch.Tensor,
343
+ bias: torch.Tensor | None,
344
+ ):
345
+ super().__init__()
346
+ expected_weight = packed_weight_num_bytes(out_features, in_features)
347
+ expected_scales = scale_layout(in_features, out_features).num_bytes
348
+ if (
349
+ packed_weight.dtype != torch.uint8
350
+ or packed_weight.ndim != 1
351
+ or not packed_weight.is_contiguous()
352
+ or packed_weight.numel() != expected_weight
353
+ ):
354
+ raise ValueError("packed_weight has invalid dtype, shape, or size")
355
+ if (
356
+ weight_scales.dtype != torch.uint8
357
+ or weight_scales.ndim != 1
358
+ or not weight_scales.is_contiguous()
359
+ or weight_scales.numel() != expected_scales
360
+ ):
361
+ raise ValueError("weight_scales has invalid dtype, shape, or size")
362
+ if (
363
+ weight_scale.dtype != torch.float32
364
+ or weight_scale.numel() != 1
365
+ or not weight_scale.is_contiguous()
366
+ ):
367
+ raise ValueError("weight_scale must be one contiguous FP32 value")
368
+ if bias is not None and (
369
+ bias.dtype != torch.bfloat16
370
+ or bias.shape != (out_features,)
371
+ or not bias.is_contiguous()
372
+ ):
373
+ raise ValueError("bias must be contiguous BF16 [out_features]")
374
+ devices = {
375
+ tensor.device
376
+ for tensor in (packed_weight, weight_scales, weight_scale, bias)
377
+ if tensor is not None
378
+ }
379
+ if len(devices) != 1:
380
+ raise ValueError("all resident buffers must be on one device")
381
+
382
+ self.in_features = int(in_features)
383
+ self.out_features = int(out_features)
384
+ self.register_buffer("packed_weight", packed_weight)
385
+ self.register_buffer("weight_scales", weight_scales)
386
+ self.register_buffer("weight_scale", weight_scale.reshape(()))
387
+ self.register_buffer("bias", bias)
388
+
389
+ @classmethod
390
+ def from_linear(
391
+ cls,
392
+ linear: nn.Linear,
393
+ device: torch.device | str,
394
+ *,
395
+ library: NativeNvfp4Library | None = None,
396
+ ) -> "PackedNvfp4Linear":
397
+ if not isinstance(linear, nn.Linear):
398
+ raise TypeError("from_linear requires torch.nn.Linear")
399
+ library = native_library() if library is None else library
400
+ destination = torch.device(device)
401
+ if destination.type != "cuda":
402
+ raise ValueError("resident packed buffers must target CUDA")
403
+ weight_cpu = (
404
+ linear.weight.detach()
405
+ .to(device="cpu", dtype=torch.bfloat16)
406
+ .contiguous()
407
+ )
408
+ packed, scales, tensor_scale = library.pack_weight(weight_cpu)
409
+ bias_cpu = (
410
+ None
411
+ if linear.bias is None
412
+ else linear.bias.detach()
413
+ .to(device="cpu", dtype=torch.bfloat16)
414
+ .contiguous()
415
+ )
416
+ return cls(
417
+ linear.in_features,
418
+ linear.out_features,
419
+ packed.to(destination),
420
+ scales.to(destination),
421
+ tensor_scale.to(destination),
422
+ None if bias_cpu is None else bias_cpu.to(destination),
423
+ )
424
+
425
+ def forward(self, input: torch.Tensor) -> torch.Tensor:
426
+ if input.device.type != "cuda":
427
+ raise ValueError("PackedNvfp4Linear requires a CUDA input")
428
+ if input.device != self.packed_weight.device:
429
+ raise ValueError("input and packed buffers are on different devices")
430
+ if input.dtype != torch.bfloat16:
431
+ raise TypeError("PackedNvfp4Linear requires BF16 input")
432
+ if input.ndim < 1 or input.shape[-1] != self.in_features:
433
+ raise ValueError(
434
+ f"expected last dimension {self.in_features}, got "
435
+ f"{tuple(input.shape)}"
436
+ )
437
+ if input.requires_grad:
438
+ raise RuntimeError("resident NVFP4 prototype is inference-only")
439
+
440
+ contiguous = input.reshape(-1, self.in_features).contiguous()
441
+ logical_m = int(contiguous.shape[0])
442
+ if logical_m <= 0:
443
+ raise ValueError("empty inputs are unsupported")
444
+ padded_m = round_up(logical_m, 8)
445
+ padded_output = torch.empty(
446
+ (padded_m, self.out_features),
447
+ dtype=torch.bfloat16,
448
+ device=input.device,
449
+ )
450
+ current_stream = torch.cuda.current_stream(input.device)
451
+ # ctypes launches are invisible to the caching allocator. Explicitly
452
+ # record every CUDA allocation read or written by the native call so a
453
+ # tensor produced on another stream cannot be recycled while the
454
+ # resident kernel is still using it.
455
+ for tensor in (
456
+ contiguous,
457
+ self.packed_weight,
458
+ self.weight_scales,
459
+ self.weight_scale,
460
+ self.bias,
461
+ padded_output,
462
+ ):
463
+ if tensor is not None:
464
+ tensor.record_stream(current_stream)
465
+ context = resident_context(input.device)
466
+ context.forward(
467
+ contiguous,
468
+ self.packed_weight,
469
+ self.weight_scales,
470
+ self.weight_scale,
471
+ self.bias,
472
+ padded_output,
473
+ logical_m,
474
+ self.in_features,
475
+ self.out_features,
476
+ )
477
+ logical = padded_output[:logical_m]
478
+ return logical.view(*input.shape[:-1], self.out_features)
479
+
480
+ def extra_repr(self) -> str:
481
+ return (
482
+ f"in_features={self.in_features}, "
483
+ f"out_features={self.out_features}, "
484
+ f"bias={self.bias is not None}, inference_only=True"
485
+ )
runtime/quant_text_encoder.py ADDED
@@ -0,0 +1,376 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Load the packaged mixed NVFP4/FP8 Qwen3-VL text encoder without BF16 shards."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import Counter
6
+ import hashlib
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import torch
12
+ import torch.nn.functional as F
13
+ from accelerate import init_empty_weights
14
+ from accelerate.utils import set_module_tensor_to_device
15
+ from safetensors import safe_open
16
+ from transformers import AutoConfig, AutoProcessor, AutoTokenizer
17
+
18
+
19
+ EXPECTED_ARTIFACT_SHA256 = (
20
+ "719906b435800757d22013d3d475a4853d59b779b022669fa8b8a193b85d0f41"
21
+ )
22
+ EXPECTED_FORMAT_COUNTS = {"nvfp4": 224, "float8_e4m3fn": 14}
23
+ EXPECTED_PROJECTIONS = {
24
+ "mlp.down_proj",
25
+ "mlp.gate_proj",
26
+ "mlp.up_proj",
27
+ "self_attn.k_proj",
28
+ "self_attn.o_proj",
29
+ "self_attn.q_proj",
30
+ "self_attn.v_proj",
31
+ }
32
+
33
+
34
+ def sha256(path: Path) -> str:
35
+ digest = hashlib.sha256()
36
+ with path.open("rb") as handle:
37
+ for chunk in iter(lambda: handle.read(1 << 20), b""):
38
+ digest.update(chunk)
39
+ return digest.hexdigest()
40
+
41
+
42
+ def _decode_quant_config(tensor: torch.Tensor) -> dict[str, Any]:
43
+ payload = bytes(tensor.cpu().to(torch.uint8).tolist()).decode("utf-8")
44
+ parsed = json.loads(payload)
45
+ if not isinstance(parsed, dict) or not isinstance(parsed.get("format"), str):
46
+ raise ValueError(f"invalid comfy_quant payload: {payload!r}")
47
+ return parsed
48
+
49
+
50
+ def _artifact_layer_to_hf_module(layer_key: str) -> str:
51
+ prefix = "model.layers."
52
+ if not layer_key.startswith(prefix):
53
+ raise ValueError(f"quantized layer is outside the language stack: {layer_key}")
54
+ remainder = layer_key[len(prefix) :]
55
+ layer_text, projection = remainder.split(".", 1)
56
+ layer_index = int(layer_text)
57
+ if layer_index < 0 or layer_index >= 36:
58
+ raise ValueError(f"language layer index is out of range: {layer_key}")
59
+ if projection not in EXPECTED_PROJECTIONS:
60
+ raise ValueError(f"unexpected quantized projection: {layer_key}")
61
+ return f"model.language_model.layers.{layer_index}.{projection}"
62
+
63
+
64
+ def _artifact_weight_to_hf_name(key: str) -> str | None:
65
+ if key == "model.embed_tokens.weight":
66
+ return "model.language_model.embed_tokens.weight"
67
+ if key == "model.norm.weight":
68
+ return "model.language_model.norm.weight"
69
+ if key.startswith("model.layers."):
70
+ return "model.language_model.layers." + key.removeprefix("model.layers.")
71
+ if key.startswith("model.visual."):
72
+ return key
73
+ return None
74
+
75
+
76
+ def _resolve_parent(module: torch.nn.Module, dotted_name: str) -> tuple[Any, str]:
77
+ parts = dotted_name.split(".")
78
+ parent: Any = module
79
+ for part in parts[:-1]:
80
+ parent = getattr(parent, part)
81
+ return parent, parts[-1]
82
+
83
+
84
+ class PublishedQuantLinear(torch.nn.Module):
85
+ """Inference-only projection backed by comfy-kitchen packed tensors."""
86
+
87
+ def __init__(
88
+ self,
89
+ *,
90
+ in_features: int,
91
+ out_features: int,
92
+ quant_format: str,
93
+ qdata: torch.Tensor,
94
+ weight_scale: torch.Tensor,
95
+ weight_scale_2: torch.Tensor | None,
96
+ ) -> None:
97
+ super().__init__()
98
+ self.in_features = int(in_features)
99
+ self.out_features = int(out_features)
100
+ self.quant_format = str(quant_format)
101
+ self.register_buffer("qdata", qdata.clone().contiguous(), persistent=True)
102
+ self.register_buffer(
103
+ "weight_scale",
104
+ weight_scale.clone().contiguous(),
105
+ persistent=True,
106
+ )
107
+ if weight_scale_2 is None:
108
+ self.weight_scale_2 = None
109
+ else:
110
+ self.register_buffer(
111
+ "weight_scale_2",
112
+ weight_scale_2.clone().contiguous(),
113
+ persistent=True,
114
+ )
115
+
116
+ def _weight_quantized_tensor(self) -> Any:
117
+ from comfy_kitchen.tensor import (
118
+ QuantizedTensor,
119
+ TensorCoreFP8Layout,
120
+ TensorCoreNVFP4Layout,
121
+ )
122
+
123
+ shape = (self.out_features, self.in_features)
124
+ if self.quant_format == "nvfp4":
125
+ if self.weight_scale_2 is None:
126
+ raise RuntimeError("NVFP4 projection is missing its second scale")
127
+ params = TensorCoreNVFP4Layout.Params(
128
+ scale=self.weight_scale_2,
129
+ orig_dtype=torch.bfloat16,
130
+ orig_shape=shape,
131
+ block_scale=self.weight_scale,
132
+ )
133
+ return QuantizedTensor(
134
+ self.qdata,
135
+ "TensorCoreNVFP4Layout",
136
+ params,
137
+ )
138
+ if self.quant_format == "float8_e4m3fn":
139
+ params = TensorCoreFP8Layout.Params(
140
+ scale=self.weight_scale,
141
+ orig_dtype=torch.bfloat16,
142
+ orig_shape=shape,
143
+ )
144
+ return QuantizedTensor(
145
+ self.qdata,
146
+ "TensorCoreFP8Layout",
147
+ params,
148
+ )
149
+ raise ValueError(f"unsupported quantized format: {self.quant_format}")
150
+
151
+ def forward(self, value: torch.Tensor) -> torch.Tensor:
152
+ from comfy_kitchen.tensor import QuantizedTensor
153
+
154
+ input_shape = tuple(value.shape)
155
+ flattened = value.reshape(-1, input_shape[-1]).contiguous()
156
+ layout = (
157
+ "TensorCoreNVFP4Layout"
158
+ if self.quant_format == "nvfp4"
159
+ else "TensorCoreFP8Layout"
160
+ )
161
+ input_quantized = QuantizedTensor.from_float(flattened, layout)
162
+ output = F.linear(
163
+ input_quantized,
164
+ self._weight_quantized_tensor(),
165
+ None,
166
+ )
167
+ return output.reshape(*input_shape[:-1], self.out_features)
168
+
169
+
170
+ def _install_quantized_linears(
171
+ hf_module: torch.nn.Module,
172
+ artifact_path: Path,
173
+ ) -> dict[str, Any]:
174
+ format_counts: Counter[str] = Counter()
175
+ installed: list[str] = []
176
+ storage_bytes = 0
177
+ original_bf16_bytes = 0
178
+
179
+ with safe_open(str(artifact_path), framework="pt", device="cpu") as handle:
180
+ config_keys = sorted(
181
+ key for key in handle.keys() if key.endswith(".comfy_quant")
182
+ )
183
+ for config_key in config_keys:
184
+ layer_key = config_key.removesuffix(".comfy_quant")
185
+ quant_format = _decode_quant_config(
186
+ handle.get_tensor(config_key)
187
+ )["format"]
188
+ module_name = _artifact_layer_to_hf_module(layer_key)
189
+ parent, leaf = _resolve_parent(hf_module, module_name)
190
+ original = getattr(parent, leaf)
191
+ if not isinstance(original, torch.nn.Linear):
192
+ raise TypeError(
193
+ f"{module_name}: expected torch.nn.Linear, got "
194
+ f"{type(original).__name__}"
195
+ )
196
+ if original.bias is not None:
197
+ raise ValueError(f"{module_name}: quantized projection has a bias")
198
+
199
+ qdata = handle.get_tensor(f"{layer_key}.weight")
200
+ weight_scale = handle.get_tensor(f"{layer_key}.weight_scale")
201
+ weight_scale_2 = (
202
+ handle.get_tensor(f"{layer_key}.weight_scale_2")
203
+ if quant_format == "nvfp4"
204
+ else None
205
+ )
206
+ replacement = PublishedQuantLinear(
207
+ in_features=original.in_features,
208
+ out_features=original.out_features,
209
+ quant_format=quant_format,
210
+ qdata=qdata,
211
+ weight_scale=weight_scale,
212
+ weight_scale_2=weight_scale_2,
213
+ )
214
+ setattr(parent, leaf, replacement)
215
+ format_counts[quant_format] += 1
216
+ installed.append(module_name)
217
+ original_bf16_bytes += (
218
+ original.in_features * original.out_features * 2
219
+ )
220
+ storage_bytes += replacement.qdata.nbytes
221
+ storage_bytes += replacement.weight_scale.nbytes
222
+ if replacement.weight_scale_2 is not None:
223
+ storage_bytes += replacement.weight_scale_2.nbytes
224
+
225
+ summary = {
226
+ "installed_module_count": len(installed),
227
+ "format_counts": dict(format_counts),
228
+ "original_projection_bf16_bytes": original_bf16_bytes,
229
+ "packed_projection_bytes": storage_bytes,
230
+ "projection_saving_bytes": original_bf16_bytes - storage_bytes,
231
+ "projection_saving_gib": (
232
+ (original_bf16_bytes - storage_bytes) / float(1 << 30)
233
+ ),
234
+ }
235
+ if (
236
+ summary["installed_module_count"] != 238
237
+ or summary["format_counts"] != EXPECTED_FORMAT_COUNTS
238
+ ):
239
+ raise RuntimeError(f"unexpected text quantization policy: {summary}")
240
+ return summary
241
+
242
+
243
+ def _load_nonquantized_weights(
244
+ hf_module: torch.nn.Module,
245
+ artifact_path: Path,
246
+ ) -> dict[str, Any]:
247
+ loaded: list[str] = []
248
+ unexpected: list[str] = []
249
+
250
+ with safe_open(str(artifact_path), framework="pt", device="cpu") as handle:
251
+ for artifact_key in sorted(handle.keys()):
252
+ if (
253
+ artifact_key.endswith(".comfy_quant")
254
+ or artifact_key.endswith(".weight_scale")
255
+ or artifact_key.endswith(".weight_scale_2")
256
+ ):
257
+ continue
258
+ target_name = _artifact_weight_to_hf_name(artifact_key)
259
+ if target_name is None:
260
+ unexpected.append(artifact_key)
261
+ continue
262
+ try:
263
+ parent, leaf = _resolve_parent(hf_module, target_name)
264
+ except AttributeError:
265
+ unexpected.append(artifact_key)
266
+ continue
267
+ current = getattr(parent, leaf, None)
268
+ if isinstance(current, PublishedQuantLinear):
269
+ continue
270
+ if target_name.endswith(".weight"):
271
+ projection_name = target_name.removesuffix(".weight")
272
+ try:
273
+ projection_parent, projection_leaf = _resolve_parent(
274
+ hf_module, projection_name
275
+ )
276
+ if isinstance(
277
+ getattr(projection_parent, projection_leaf),
278
+ PublishedQuantLinear,
279
+ ):
280
+ continue
281
+ except AttributeError:
282
+ pass
283
+ set_module_tensor_to_device(
284
+ hf_module,
285
+ target_name,
286
+ "cpu",
287
+ value=handle.get_tensor(artifact_key),
288
+ )
289
+ loaded.append(target_name)
290
+
291
+ hf_module.tie_weights()
292
+ meta_parameters = [
293
+ name for name, value in hf_module.named_parameters() if value.is_meta
294
+ ]
295
+ meta_buffers = [
296
+ name for name, value in hf_module.named_buffers() if value.is_meta
297
+ ]
298
+ if meta_parameters or meta_buffers:
299
+ raise RuntimeError(
300
+ "packed text loader left unresolved meta tensors: "
301
+ f"{(meta_parameters + meta_buffers)[:4]}"
302
+ )
303
+ if unexpected:
304
+ raise RuntimeError(
305
+ f"packed text artifact contains unmapped tensors: {unexpected[:4]}"
306
+ )
307
+ return {
308
+ "loaded_nonquantized_tensor_count": len(loaded),
309
+ "unresolved_meta_parameters": meta_parameters,
310
+ "unresolved_meta_buffers": meta_buffers,
311
+ }
312
+
313
+
314
+ def load_quantized_text_encoder(
315
+ *,
316
+ text_encoder_dir: str | Path,
317
+ artifact_path: str | Path,
318
+ tokenizer_max_length: int,
319
+ dit_structure: dict[str, Any],
320
+ use_packed_text_infer: bool,
321
+ ) -> tuple[torch.nn.Module, dict[str, Any]]:
322
+ """Construct Mage's text wrapper directly from the packaged quant artifact."""
323
+
324
+ from mage_flow.models.modules.text_encoder import (
325
+ CustomQwen3VLForConditionalGeneration,
326
+ TextEncoder,
327
+ )
328
+
329
+ text_encoder_dir = Path(text_encoder_dir).resolve()
330
+ artifact_path = Path(artifact_path).resolve()
331
+ if sha256(artifact_path) != EXPECTED_ARTIFACT_SHA256:
332
+ raise RuntimeError("packaged text-encoder artifact SHA-256 mismatch")
333
+
334
+ config = AutoConfig.from_pretrained(
335
+ str(text_encoder_dir),
336
+ local_files_only=True,
337
+ )
338
+ with init_empty_weights():
339
+ hf_module = CustomQwen3VLForConditionalGeneration._from_config(
340
+ config,
341
+ attn_implementation="flash_attention_2",
342
+ dtype=torch.bfloat16,
343
+ )
344
+
345
+ quant_summary = _install_quantized_linears(hf_module, artifact_path)
346
+ load_summary = _load_nonquantized_weights(hf_module, artifact_path)
347
+
348
+ text_encoder = TextEncoder.__new__(TextEncoder)
349
+ torch.nn.Module.__init__(text_encoder)
350
+ text_encoder.model_name = str(text_encoder_dir)
351
+ text_encoder.tokenizer_max_length = int(tokenizer_max_length)
352
+ text_encoder.tokenizer = AutoTokenizer.from_pretrained(
353
+ str(text_encoder_dir),
354
+ local_files_only=True,
355
+ )
356
+ text_encoder.tokenizer.padding_side = "right"
357
+ text_encoder.processor = AutoProcessor.from_pretrained(
358
+ str(text_encoder_dir),
359
+ local_files_only=True,
360
+ )
361
+ text_encoder.hf_module = hf_module.eval().requires_grad_(False)
362
+ text_encoder.prompt_template_encode = ""
363
+ text_encoder.prompt_template_encode_start_idx = 0
364
+ text_encoder.dit_structure = dict(dit_structure)
365
+ text_encoder.use_packed_text_infer = bool(use_packed_text_infer)
366
+ text_encoder.eval().requires_grad_(False)
367
+
368
+ return (
369
+ text_encoder,
370
+ {
371
+ "artifact": str(artifact_path),
372
+ "artifact_sha256": EXPECTED_ARTIFACT_SHA256,
373
+ "quantized": quant_summary,
374
+ "nonquantized": load_summary,
375
+ },
376
+ )
runtime/sm120_linear_op.cpp ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include <ATen/ATen.h>
2
+ #include <ATen/cuda/CUDAContext.h>
3
+ #include <c10/core/DeviceType.h>
4
+ #include <c10/cuda/CUDACachingAllocator.h>
5
+ #include <c10/cuda/CUDAException.h>
6
+ #include <c10/cuda/CUDAGuard.h>
7
+ #include <c10/cuda/CUDAStream.h>
8
+ #include <cuda_runtime_api.h>
9
+ #include <torch/library.h>
10
+
11
+ #include <cstdint>
12
+ #include <limits>
13
+ #include <memory>
14
+ #include <mutex>
15
+ #include <optional>
16
+ #include <stdexcept>
17
+ #include <unordered_map>
18
+ #include <utility>
19
+ #include <vector>
20
+
21
+ #include "nvfp4_linear.h"
22
+
23
+ namespace {
24
+
25
+ constexpr int kAbiVersion = 1;
26
+ constexpr int64_t kFp4BlockElements = 16;
27
+ constexpr int64_t kScaleTileOuter = 128;
28
+ constexpr int64_t kScaleTileInner = 4;
29
+
30
+ int64_t round_up_int64(int64_t value, int64_t multiple) {
31
+ TORCH_CHECK(value > 0, "round_up requires a positive value");
32
+ TORCH_CHECK(multiple > 0, "round_up requires a positive multiple");
33
+ TORCH_CHECK(
34
+ value <= std::numeric_limits<int64_t>::max() - (multiple - 1),
35
+ "round_up overflow");
36
+ return ((value + multiple - 1) / multiple) * multiple;
37
+ }
38
+
39
+ int checked_int_arg(int64_t value, const char* label) {
40
+ TORCH_CHECK(value > 0, label, " must be positive");
41
+ TORCH_CHECK(
42
+ value <= static_cast<int64_t>(std::numeric_limits<int>::max()),
43
+ label,
44
+ " exceeds 32-bit ABI bound");
45
+ return static_cast<int>(value);
46
+ }
47
+
48
+ size_t expected_packed_weight_bytes(int out_features, int in_features) {
49
+ TORCH_CHECK(
50
+ out_features > 0 && out_features % 8 == 0,
51
+ "resident NVFP4 requires out_features divisible by 8, got ",
52
+ out_features);
53
+ TORCH_CHECK(
54
+ in_features > 0 && in_features % 32 == 0,
55
+ "resident NVFP4 requires in_features divisible by 32, got ",
56
+ in_features);
57
+ return (static_cast<size_t>(out_features) * static_cast<size_t>(in_features)) /
58
+ 2;
59
+ }
60
+
61
+ size_t expected_scale_bytes(int out_features, int in_features) {
62
+ TORCH_CHECK(
63
+ in_features > 0 && in_features % kFp4BlockElements == 0,
64
+ "resident NVFP4 requires in_features divisible by 16, got ",
65
+ in_features);
66
+ TORCH_CHECK(out_features > 0, "out_features must be positive");
67
+ const int64_t inner_dim =
68
+ round_up_int64(in_features / kFp4BlockElements, kScaleTileInner);
69
+ const int64_t outer_tiles =
70
+ (out_features + kScaleTileOuter - 1) / kScaleTileOuter;
71
+ return static_cast<size_t>(inner_dim) * static_cast<size_t>(outer_tiles) *
72
+ static_cast<size_t>(kScaleTileOuter);
73
+ }
74
+
75
+ [[noreturn]] void throw_abi_error(const char* operation) {
76
+ const char* message = mage_nvfp4_last_error();
77
+ TORCH_CHECK(
78
+ false,
79
+ operation,
80
+ ": ",
81
+ message == nullptr ? "unknown native resident error" : message);
82
+ }
83
+
84
+ void ensure_abi_version() {
85
+ static std::once_flag once;
86
+ std::call_once(once, []() {
87
+ const int version = mage_nvfp4_abi_version();
88
+ TORCH_CHECK(
89
+ version == kAbiVersion,
90
+ "resident NVFP4 ABI mismatch: compiled torch op expects ",
91
+ kAbiVersion,
92
+ " but native library reports ",
93
+ version);
94
+ });
95
+ }
96
+
97
+ struct ContextKey {
98
+ int device_index = -1;
99
+ uintptr_t stream = 0;
100
+
101
+ bool operator==(const ContextKey& other) const noexcept {
102
+ return device_index == other.device_index && stream == other.stream;
103
+ }
104
+ };
105
+
106
+ struct ContextKeyHash {
107
+ size_t operator()(const ContextKey& key) const noexcept {
108
+ const size_t left = std::hash<int>{}(key.device_index);
109
+ const size_t right = std::hash<uintptr_t>{}(key.stream);
110
+ return left ^ (right + 0x9e3779b97f4a7c15ULL + (left << 6) + (left >> 2));
111
+ }
112
+ };
113
+
114
+ struct NativeContextDeleter {
115
+ void operator()(void* context) const noexcept {
116
+ if (context == nullptr) {
117
+ return;
118
+ }
119
+ (void)mage_nvfp4_destroy_context(context);
120
+ }
121
+ };
122
+
123
+ class ContextRegistry {
124
+ public:
125
+ void* get(int device_index, uintptr_t stream) {
126
+ std::lock_guard<std::mutex> guard(mutex_);
127
+ const ContextKey key{device_index, stream};
128
+ auto it = contexts_.find(key);
129
+ if (it != contexts_.end()) {
130
+ return it->second.get();
131
+ }
132
+ void* context = nullptr;
133
+ const int status = mage_nvfp4_create_context(device_index, &context);
134
+ if (status != 0) {
135
+ throw_abi_error("creating resident NVFP4 context");
136
+ }
137
+ auto inserted = contexts_.emplace(
138
+ key, std::unique_ptr<void, NativeContextDeleter>(context));
139
+ return inserted.first->second.get();
140
+ }
141
+
142
+ void clear() {
143
+ decltype(contexts_) retired;
144
+ {
145
+ std::lock_guard<std::mutex> guard(mutex_);
146
+ retired.swap(contexts_);
147
+ }
148
+ // Destruction synchronizes each bound stream through the resident ABI.
149
+ // Keep it outside the registry mutex.
150
+ }
151
+
152
+ private:
153
+ std::mutex mutex_;
154
+ std::unordered_map<
155
+ ContextKey,
156
+ std::unique_ptr<void, NativeContextDeleter>,
157
+ ContextKeyHash>
158
+ contexts_;
159
+ };
160
+
161
+ ContextRegistry& context_registry() {
162
+ // Deliberately avoid a static destructor that could call CUDA after runtime
163
+ // teardown. Long-lived processes must call the explicit close op; otherwise
164
+ // the OS reclaims these process resources at exit.
165
+ static ContextRegistry* registry = new ContextRegistry();
166
+ return *registry;
167
+ }
168
+
169
+ void clear_native_contexts() {
170
+ context_registry().clear();
171
+ }
172
+
173
+ void record_tensor_stream(
174
+ const at::Tensor& tensor, c10::cuda::CUDAStream stream) {
175
+ if (!tensor.defined() || !tensor.is_cuda()) {
176
+ return;
177
+ }
178
+ c10::cuda::CUDACachingAllocator::recordStream(
179
+ tensor.storage().data_ptr(), stream);
180
+ }
181
+
182
+ void validate_common(
183
+ const at::Tensor& input,
184
+ const at::Tensor& packed_weight,
185
+ const at::Tensor& weight_scales,
186
+ const at::Tensor& weight_scale,
187
+ const std::optional<at::Tensor>& bias,
188
+ int64_t in_features,
189
+ int64_t out_features) {
190
+ TORCH_CHECK(input.dim() >= 1, "resident NVFP4 input must have at least one dimension");
191
+ TORCH_CHECK(
192
+ input.scalar_type() == at::kBFloat16,
193
+ "resident NVFP4 input must be bfloat16");
194
+ TORCH_CHECK(!input.requires_grad(), "resident NVFP4 torch op is inference-only");
195
+ TORCH_CHECK(
196
+ input.size(-1) == in_features,
197
+ "resident NVFP4 expected last dimension ",
198
+ in_features,
199
+ " but got ",
200
+ input.size(-1));
201
+ TORCH_CHECK(
202
+ packed_weight.scalar_type() == at::kByte &&
203
+ packed_weight.dim() == 1 && packed_weight.is_contiguous(),
204
+ "packed_weight must be a contiguous 1D uint8 tensor");
205
+ TORCH_CHECK(
206
+ weight_scales.scalar_type() == at::kByte &&
207
+ weight_scales.dim() == 1 && weight_scales.is_contiguous(),
208
+ "weight_scales must be a contiguous 1D uint8 tensor");
209
+ TORCH_CHECK(
210
+ weight_scale.scalar_type() == at::kFloat &&
211
+ weight_scale.numel() == 1 && weight_scale.is_contiguous(),
212
+ "weight_scale must be one contiguous float32 value");
213
+ TORCH_CHECK(
214
+ packed_weight.device() == input.device(),
215
+ "packed_weight and input must be on the same device");
216
+ TORCH_CHECK(
217
+ weight_scales.device() == input.device(),
218
+ "weight_scales and input must be on the same device");
219
+ TORCH_CHECK(
220
+ weight_scale.device() == input.device(),
221
+ "weight_scale and input must be on the same device");
222
+ if (bias.has_value()) {
223
+ const at::Tensor& bias_value = *bias;
224
+ TORCH_CHECK(
225
+ bias_value.scalar_type() == at::kBFloat16 &&
226
+ bias_value.dim() == 1 && bias_value.is_contiguous(),
227
+ "bias must be a contiguous 1D bfloat16 tensor");
228
+ TORCH_CHECK(
229
+ bias_value.numel() == out_features,
230
+ "bias length must match out_features");
231
+ TORCH_CHECK(
232
+ bias_value.device() == input.device(),
233
+ "bias and input must be on the same device");
234
+ }
235
+
236
+ const int in_features_i = checked_int_arg(in_features, "in_features");
237
+ const int out_features_i = checked_int_arg(out_features, "out_features");
238
+ const size_t expected_weight = expected_packed_weight_bytes(
239
+ out_features_i, in_features_i);
240
+ const size_t expected_scales = expected_scale_bytes(
241
+ out_features_i, in_features_i);
242
+ TORCH_CHECK(
243
+ static_cast<size_t>(packed_weight.numel()) == expected_weight,
244
+ "packed_weight size mismatch: expected ",
245
+ expected_weight,
246
+ " bytes but got ",
247
+ packed_weight.numel());
248
+ TORCH_CHECK(
249
+ static_cast<size_t>(weight_scales.numel()) == expected_scales,
250
+ "weight_scales size mismatch: expected ",
251
+ expected_scales,
252
+ " bytes but got ",
253
+ weight_scales.numel());
254
+
255
+ const size_t native_weight =
256
+ mage_nvfp4_packed_weight_bytes(out_features_i, in_features_i);
257
+ const size_t native_scales =
258
+ mage_nvfp4_weight_scale_bytes(out_features_i, in_features_i);
259
+ TORCH_CHECK(
260
+ native_weight == expected_weight,
261
+ "native resident library packed-weight metadata disagrees with torch op");
262
+ TORCH_CHECK(
263
+ native_scales == expected_scales,
264
+ "native resident library scale metadata disagrees with torch op");
265
+ }
266
+
267
+ at::Tensor sm120_linear_native_cuda(
268
+ const at::Tensor& input,
269
+ const at::Tensor& packed_weight,
270
+ const at::Tensor& weight_scales,
271
+ const at::Tensor& weight_scale,
272
+ const std::optional<at::Tensor>& bias,
273
+ int64_t in_features,
274
+ int64_t out_features) {
275
+ ensure_abi_version();
276
+ validate_common(
277
+ input, packed_weight, weight_scales, weight_scale, bias, in_features,
278
+ out_features);
279
+
280
+ TORCH_CHECK(input.is_cuda(), "resident NVFP4 CUDA implementation requires a CUDA input");
281
+ const auto device = input.device();
282
+ c10::cuda::CUDAGuard guard(device);
283
+ const int device_index = device.index();
284
+ cudaDeviceProp properties{};
285
+ C10_CUDA_CHECK(cudaGetDeviceProperties(&properties, device_index));
286
+ TORCH_CHECK(
287
+ properties.major == 12 && properties.minor == 0,
288
+ "resident NVFP4 CUDA implementation is labeled sm_120-only; selected device reports ",
289
+ properties.major,
290
+ ".",
291
+ properties.minor);
292
+
293
+ const c10::cuda::CUDAStream stream =
294
+ c10::cuda::getCurrentCUDAStream(device_index);
295
+ TORCH_CHECK(
296
+ !stream.is_capturing(),
297
+ "resident NVFP4 C ABI torch op does not support CUDA graph capture");
298
+
299
+ const int in_features_i = checked_int_arg(in_features, "in_features");
300
+ const int out_features_i = checked_int_arg(out_features, "out_features");
301
+ at::Tensor contiguous =
302
+ input.reshape({-1, in_features_i}).contiguous();
303
+ const int64_t logical_m64 = contiguous.size(0);
304
+ TORCH_CHECK(logical_m64 > 0, "resident NVFP4 torch op does not support empty inputs");
305
+ const int logical_m = checked_int_arg(logical_m64, "logical_m");
306
+ const int64_t padded_m64 = round_up_int64(logical_m64, 8);
307
+ at::Tensor padded_output = at::empty(
308
+ {padded_m64, out_features_i},
309
+ input.options().dtype(at::kBFloat16));
310
+
311
+ record_tensor_stream(contiguous, stream);
312
+ record_tensor_stream(packed_weight, stream);
313
+ record_tensor_stream(weight_scales, stream);
314
+ record_tensor_stream(weight_scale, stream);
315
+ if (bias.has_value()) {
316
+ record_tensor_stream(*bias, stream);
317
+ }
318
+ record_tensor_stream(padded_output, stream);
319
+
320
+ void* context =
321
+ context_registry().get(device_index, reinterpret_cast<uintptr_t>(stream.stream()));
322
+ const void* bias_pointer =
323
+ bias.has_value() ? bias->data_ptr() : nullptr;
324
+ const int status = mage_nvfp4_linear_forward(
325
+ context,
326
+ contiguous.data_ptr(),
327
+ packed_weight.data_ptr(),
328
+ static_cast<size_t>(packed_weight.numel()),
329
+ weight_scales.data_ptr(),
330
+ static_cast<size_t>(weight_scales.numel()),
331
+ weight_scale.data_ptr(),
332
+ bias_pointer,
333
+ padded_output.data_ptr(),
334
+ logical_m,
335
+ in_features_i,
336
+ out_features_i,
337
+ reinterpret_cast<uintptr_t>(stream.stream()));
338
+ if (status != 0) {
339
+ throw_abi_error("running resident NVFP4 linear forward");
340
+ }
341
+
342
+ at::Tensor logical = padded_output.narrow(0, 0, logical_m64);
343
+ std::vector<int64_t> output_sizes = input.sizes().vec();
344
+ output_sizes.back() = out_features_i;
345
+ return logical.view(output_sizes);
346
+ }
347
+
348
+ } // namespace
349
+
350
+ TORCH_LIBRARY_FRAGMENT(mage_nvfp4, m) {
351
+ m.def(
352
+ "sm120_linear_native(Tensor input, Tensor packed_weight, Tensor weight_scales, Tensor weight_scale, Tensor? bias, int in_features, int out_features) -> Tensor");
353
+ m.def("clear_native_contexts() -> ()", TORCH_FN(clear_native_contexts));
354
+ }
355
+
356
+ TORCH_LIBRARY_IMPL(mage_nvfp4, CUDA, m) {
357
+ m.impl("sm120_linear_native", TORCH_FN(sm120_linear_native_cuda));
358
+ }
runtime/standard_transformer.py ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Load the complete sharded Mage-Flow NVFP4 transformer component."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextlib import ExitStack
6
+ import json
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+ from safetensors import safe_open
13
+
14
+ from packed_artifact import (
15
+ assign_tensor_by_name,
16
+ build_target_specs,
17
+ instantiate_mage_transformer_on_meta,
18
+ materialize_mage_rope_tensor_attributes,
19
+ set_child_module,
20
+ unregistered_meta_tensor_attribute_names,
21
+ )
22
+ from torch_ops_native import (
23
+ PackedNvfp4LinearNativeOp,
24
+ initialize_native_sm120_op,
25
+ )
26
+
27
+
28
+ class StandardCheckpointError(RuntimeError):
29
+ pass
30
+
31
+
32
+ def fail(message: str) -> None:
33
+ raise StandardCheckpointError(message)
34
+
35
+
36
+ def read_object(path: Path) -> dict[str, Any]:
37
+ try:
38
+ value = json.loads(path.read_text(encoding="utf-8"))
39
+ except (OSError, json.JSONDecodeError) as exc:
40
+ fail(f"cannot read JSON object {path}: {exc}")
41
+ if not isinstance(value, dict):
42
+ fail(f"expected a JSON object: {path}")
43
+ return value
44
+
45
+
46
+ def _component_path(component_dir: Path, relative: str) -> Path:
47
+ path = (component_dir / relative).resolve()
48
+ if not path.is_relative_to(component_dir):
49
+ fail(f"checkpoint index path escapes transformer component: {relative}")
50
+ if not path.is_file():
51
+ fail(f"checkpoint shard is missing: {relative}")
52
+ return path
53
+
54
+
55
+ def _quantized_keys(module_key: str) -> dict[str, str]:
56
+ return {
57
+ "packed_weight": f"{module_key}.packed_weight",
58
+ "weight_scales": f"{module_key}.weight_scales",
59
+ "weight_scale": f"{module_key}.weight_scale",
60
+ "bias": f"{module_key}.bias",
61
+ }
62
+
63
+
64
+ def load_standard_native_transformer(
65
+ repo_root: str | Path,
66
+ device: torch.device,
67
+ ) -> tuple[nn.Module, dict[str, Any]]:
68
+ """Load a complete standard-layout component without a BF16 base download."""
69
+
70
+ repo_root = Path(repo_root).resolve()
71
+ component_dir = (repo_root / "transformer").resolve()
72
+ if not component_dir.is_relative_to(repo_root) or not component_dir.is_dir():
73
+ fail("repository has no transformer component")
74
+ if device.type != "cuda":
75
+ fail("the native resident transformer requires a CUDA destination")
76
+ if not initialize_native_sm120_op(allow_python_schema_fallback=False):
77
+ fail("compiled native SM120 torch op did not load")
78
+
79
+ config = read_object(component_dir / "config.json")
80
+ quant_config = config.get("quantization_config")
81
+ if not isinstance(quant_config, dict):
82
+ fail("transformer config has no quantization_config")
83
+ if quant_config.get("quant_method") != "mage_flow_nvfp4":
84
+ fail(
85
+ "unexpected transformer quantization method: "
86
+ f"{quant_config.get('quant_method')!r}"
87
+ )
88
+ if quant_config.get("quant_algo") != "NVFP4":
89
+ fail("transformer config does not declare NVFP4")
90
+
91
+ depth = int(config.get("depth", 0))
92
+ target_specs = build_target_specs(depth)
93
+ expected_targets = [spec.module_key for spec in target_specs]
94
+ if quant_config.get("targets") != expected_targets:
95
+ fail("transformer quantization target list is not canonical")
96
+
97
+ metadata = read_object(component_dir / "nvfp4_metadata.json")
98
+ if metadata.get("artifact_kind") != (
99
+ "mage_flow_transformer_mlp_nvfp4_resident_v1"
100
+ ):
101
+ fail("unexpected transformer NVFP4 metadata kind")
102
+ non_target_keys = metadata.get("non_target_keys")
103
+ if not isinstance(non_target_keys, list) or not all(
104
+ isinstance(key, str) for key in non_target_keys
105
+ ):
106
+ fail("transformer NVFP4 metadata has no non-target key list")
107
+
108
+ index = read_object(
109
+ component_dir / "diffusion_pytorch_model.safetensors.index.json"
110
+ )
111
+ weight_map = index.get("weight_map")
112
+ if not isinstance(weight_map, dict) or not all(
113
+ isinstance(key, str) and isinstance(value, str)
114
+ for key, value in weight_map.items()
115
+ ):
116
+ fail("transformer checkpoint has no valid weight map")
117
+
118
+ quantized_keys = {
119
+ key
120
+ for spec in target_specs
121
+ for key in _quantized_keys(spec.module_key).values()
122
+ }
123
+ expected_keys = set(non_target_keys) | quantized_keys
124
+ actual_keys = set(weight_map)
125
+ if actual_keys != expected_keys:
126
+ missing = sorted(expected_keys - actual_keys)
127
+ unexpected = sorted(actual_keys - expected_keys)
128
+ fail(
129
+ "transformer checkpoint key coverage mismatch; "
130
+ f"missing={missing[:1]}, unexpected={unexpected[:1]}"
131
+ )
132
+ original_target_weights = {spec.weight_key for spec in target_specs}
133
+ leaked = sorted(actual_keys & original_target_weights)
134
+ if leaked:
135
+ fail(f"BF16 target weight leaked into quantized checkpoint: {leaked[0]}")
136
+
137
+ shard_names = sorted(set(weight_map.values()))
138
+ with ExitStack() as stack:
139
+ handles = {
140
+ name: stack.enter_context(
141
+ safe_open(
142
+ _component_path(component_dir, name),
143
+ framework="pt",
144
+ device="cpu",
145
+ )
146
+ )
147
+ for name in shard_names
148
+ }
149
+
150
+ def tensor(key: str) -> torch.Tensor:
151
+ try:
152
+ handle = handles[weight_map[key]]
153
+ except KeyError:
154
+ fail(f"tensor is absent from checkpoint index: {key}")
155
+ if key not in handle.keys():
156
+ fail(f"tensor is absent from its declared shard: {key}")
157
+ return handle.get_tensor(key)
158
+
159
+ model = instantiate_mage_transformer_on_meta(repo_root)
160
+ for spec in target_specs:
161
+ original = model.get_submodule(spec.module_key)
162
+ if not isinstance(original, nn.Linear):
163
+ fail(
164
+ f"expected target {spec.module_key} to be nn.Linear, "
165
+ f"found {type(original).__name__}"
166
+ )
167
+ keys = _quantized_keys(spec.module_key)
168
+ replacement = PackedNvfp4LinearNativeOp(
169
+ in_features=int(original.in_features),
170
+ out_features=int(original.out_features),
171
+ packed_weight=tensor(keys["packed_weight"]).to(device),
172
+ weight_scales=tensor(keys["weight_scales"]).to(device),
173
+ weight_scale=tensor(keys["weight_scale"]).to(device),
174
+ bias=tensor(keys["bias"]).to(device),
175
+ )
176
+ set_child_module(model, spec.module_key, replacement)
177
+
178
+ loaded_non_targets: list[str] = []
179
+ for key in non_target_keys:
180
+ assign_tensor_by_name(model, key, tensor(key).to(device))
181
+ loaded_non_targets.append(key)
182
+
183
+ materialized = materialize_mage_rope_tensor_attributes(model)
184
+ meta_parameters = [
185
+ name for name, value in model.named_parameters() if value.is_meta
186
+ ]
187
+ meta_buffers = [
188
+ name for name, value in model.named_buffers() if value.is_meta
189
+ ]
190
+ unregistered_meta = unregistered_meta_tensor_attribute_names(model)
191
+ if meta_parameters or meta_buffers or unregistered_meta:
192
+ fail(
193
+ "standard transformer loader left unresolved meta tensors: "
194
+ f"{(meta_parameters + meta_buffers + unregistered_meta)[:4]}"
195
+ )
196
+
197
+ report = {
198
+ "layout": "huggingface_sharded_component",
199
+ "checkpoint_shard_count": len(shard_names),
200
+ "checkpoint_tensor_count": len(actual_keys),
201
+ "loaded_non_target_tensor_count": len(loaded_non_targets),
202
+ "loaded_quantized_projection_count": len(target_specs),
203
+ "bf16_target_weight_reads": 0,
204
+ "meta_parameter_names": meta_parameters,
205
+ "meta_buffer_names": meta_buffers,
206
+ "materialized_unregistered_tensor_attribute_names": materialized,
207
+ }
208
+ return model.eval().requires_grad_(False), report
209
+
210
+
211
+ __all__ = [
212
+ "StandardCheckpointError",
213
+ "load_standard_native_transformer",
214
+ ]
runtime/torch_ops_native.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Loader and fake/meta shim for the compiled resident NVFP4 torch op.
2
+
3
+ The compiled bridge lives in ``native/torch_op`` and, when built, moves CUDA
4
+ execution out of Python+ctypes and into a C++ dispatcher implementation that
5
+ calls the existing resident C ABI directly.
6
+
7
+ This shim keeps CPU/meta tests lightweight:
8
+
9
+ - if the compiled bridge exists, load it first;
10
+ - otherwise define a temporary Python schema fallback for shape-only tests;
11
+ - always register fake/meta implementations in Python.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import atexit
17
+ from pathlib import Path
18
+
19
+ import torch
20
+
21
+ from packed_nvfp4_linear import PackedNvfp4Linear, scale_layout
22
+
23
+
24
+ RELEASE_ROOT = Path(__file__).resolve().parents[1]
25
+ DEFAULT_EXTENSION_PATH = RELEASE_ROOT / "runtime" / "libmage_nvfp4_torch_op.so"
26
+
27
+ OP_NAMESPACE = "mage_nvfp4"
28
+ OP_NAME = "sm120_linear_native"
29
+ OP_QUALNAME = f"{OP_NAMESPACE}::{OP_NAME}"
30
+
31
+ _SCHEMA_FALLBACK_LIB: torch.library.Library | None = None
32
+ _META_LIB: torch.library.Library | None = None
33
+ _EXTENSION_LOADED = False
34
+ _FAKE_REGISTERED = False
35
+ _META_REGISTERED = False
36
+
37
+
38
+ def _expected_weight_bytes(out_features: int, in_features: int) -> int:
39
+ if out_features <= 0 or out_features % 8:
40
+ raise ValueError("resident NVFP4 requires out_features divisible by 8")
41
+ if in_features <= 0 or in_features % 32:
42
+ raise ValueError("resident NVFP4 requires in_features divisible by 32")
43
+ return (out_features * in_features) // 2
44
+
45
+
46
+ def _check_common_shapes(
47
+ input: torch.Tensor,
48
+ packed_weight: torch.Tensor,
49
+ weight_scales: torch.Tensor,
50
+ weight_scale: torch.Tensor,
51
+ bias: torch.Tensor | None,
52
+ in_features: int,
53
+ out_features: int,
54
+ ) -> None:
55
+ if input.ndim < 1:
56
+ raise ValueError("resident NVFP4 input must have at least one dimension")
57
+ if input.dtype != torch.bfloat16:
58
+ raise TypeError("resident NVFP4 input must be bfloat16")
59
+ if input.requires_grad:
60
+ raise RuntimeError("resident NVFP4 torch op is inference-only")
61
+ if int(input.shape[-1]) != int(in_features):
62
+ raise ValueError(
63
+ f"expected input last dimension {in_features}, got {tuple(input.shape)}"
64
+ )
65
+ if packed_weight.dtype != torch.uint8 or packed_weight.ndim != 1:
66
+ raise ValueError("packed_weight must be a 1D uint8 tensor")
67
+ if weight_scales.dtype != torch.uint8 or weight_scales.ndim != 1:
68
+ raise ValueError("weight_scales must be a 1D uint8 tensor")
69
+ if weight_scale.dtype != torch.float32 or weight_scale.numel() != 1:
70
+ raise ValueError("weight_scale must be a scalar or length-1 float32 tensor")
71
+ if bias is not None and (
72
+ bias.dtype != torch.bfloat16 or bias.ndim != 1 or int(bias.numel()) != int(out_features)
73
+ ):
74
+ raise ValueError("bias must be a 1D bfloat16 tensor with length out_features")
75
+ for tensor in (packed_weight, weight_scales, weight_scale, bias):
76
+ if tensor is not None and tensor.device != input.device:
77
+ raise ValueError("input and resident buffers must be on the same device")
78
+ if tensor is not None and not tensor.is_contiguous():
79
+ raise ValueError("resident packed buffers must be contiguous")
80
+
81
+ expected_weight_bytes = _expected_weight_bytes(int(out_features), int(in_features))
82
+ expected_scale_bytes = scale_layout(int(in_features), int(out_features)).num_bytes
83
+ if int(packed_weight.numel()) != expected_weight_bytes:
84
+ raise ValueError(
85
+ f"packed_weight size mismatch: expected {expected_weight_bytes}, got {packed_weight.numel()}"
86
+ )
87
+ if int(weight_scales.numel()) != expected_scale_bytes:
88
+ raise ValueError(
89
+ f"weight_scales size mismatch: expected {expected_scale_bytes}, got {weight_scales.numel()}"
90
+ )
91
+
92
+
93
+ def _logical_output(input: torch.Tensor, out_features: int) -> torch.Tensor:
94
+ return input.new_empty((*input.shape[:-1], int(out_features)), dtype=torch.bfloat16)
95
+
96
+
97
+ def ensure_native_sm120_schema(
98
+ *,
99
+ extension_path: str | Path = DEFAULT_EXTENSION_PATH,
100
+ allow_python_schema_fallback: bool = True,
101
+ ) -> bool:
102
+ global _EXTENSION_LOADED, _SCHEMA_FALLBACK_LIB
103
+ extension_path = Path(extension_path)
104
+ if not _EXTENSION_LOADED and extension_path.is_file():
105
+ torch.ops.load_library(str(extension_path.resolve()))
106
+ _EXTENSION_LOADED = True
107
+ return True
108
+ if _EXTENSION_LOADED:
109
+ return True
110
+ if not allow_python_schema_fallback:
111
+ return False
112
+ if _SCHEMA_FALLBACK_LIB is None:
113
+ lib = torch.library.Library(OP_NAMESPACE, "FRAGMENT")
114
+ lib.define(
115
+ "sm120_linear_native(Tensor input, Tensor packed_weight, Tensor weight_scales, Tensor weight_scale, Tensor? bias, int in_features, int out_features) -> Tensor"
116
+ )
117
+ lib.define("clear_native_contexts() -> ()")
118
+ _SCHEMA_FALLBACK_LIB = lib
119
+ return False
120
+
121
+
122
+ def _register_meta_impl() -> None:
123
+ global _META_LIB, _META_REGISTERED
124
+ if _META_REGISTERED:
125
+ return
126
+ _META_LIB = torch.library.Library(OP_NAMESPACE, "IMPL", "Meta")
127
+ _META_LIB.impl(
128
+ OP_NAME,
129
+ lambda input, packed_weight, weight_scales, weight_scale, bias, in_features, out_features: (
130
+ _check_common_shapes(
131
+ input,
132
+ packed_weight,
133
+ weight_scales,
134
+ weight_scale,
135
+ bias,
136
+ in_features,
137
+ out_features,
138
+ ),
139
+ _logical_output(input, int(out_features)),
140
+ )[1],
141
+ )
142
+ _META_REGISTERED = True
143
+
144
+
145
+ def _register_fake_impl() -> None:
146
+ global _FAKE_REGISTERED
147
+ if _FAKE_REGISTERED:
148
+ return
149
+
150
+ @torch.library.register_fake(OP_QUALNAME)
151
+ def _fake(
152
+ input: torch.Tensor,
153
+ packed_weight: torch.Tensor,
154
+ weight_scales: torch.Tensor,
155
+ weight_scale: torch.Tensor,
156
+ bias: torch.Tensor | None,
157
+ in_features: int,
158
+ out_features: int,
159
+ ) -> torch.Tensor:
160
+ _check_common_shapes(
161
+ input,
162
+ packed_weight,
163
+ weight_scales,
164
+ weight_scale,
165
+ bias,
166
+ in_features,
167
+ out_features,
168
+ )
169
+ return _logical_output(input, int(out_features))
170
+
171
+ _FAKE_REGISTERED = True
172
+
173
+
174
+ def initialize_native_sm120_op(
175
+ *,
176
+ extension_path: str | Path = DEFAULT_EXTENSION_PATH,
177
+ allow_python_schema_fallback: bool = True,
178
+ ) -> bool:
179
+ loaded = ensure_native_sm120_schema(
180
+ extension_path=extension_path,
181
+ allow_python_schema_fallback=allow_python_schema_fallback,
182
+ )
183
+ _register_meta_impl()
184
+ _register_fake_impl()
185
+ return loaded
186
+
187
+
188
+ def close_native_contexts() -> None:
189
+ """Synchronize and release native contexts when the compiled bridge is loaded."""
190
+ if _EXTENSION_LOADED:
191
+ torch.ops.mage_nvfp4.clear_native_contexts()
192
+
193
+
194
+ def _quiet_atexit_close() -> None:
195
+ try:
196
+ close_native_contexts()
197
+ except Exception:
198
+ # CUDA may already be shutting down. Explicit close_native_contexts()
199
+ # is the auditable path; atexit is only a best-effort fallback.
200
+ pass
201
+
202
+
203
+ class PackedNvfp4LinearNativeOp(PackedNvfp4Linear):
204
+ """Thin wrapper over resident packed buffers using the native torch op."""
205
+
206
+ def forward(self, input: torch.Tensor) -> torch.Tensor:
207
+ if input.device.type not in {"cuda", "meta"}:
208
+ raise ValueError("PackedNvfp4LinearNativeOp requires a CUDA or meta input")
209
+ return torch.ops.mage_nvfp4.sm120_linear_native(
210
+ input,
211
+ self.packed_weight,
212
+ self.weight_scales,
213
+ self.weight_scale,
214
+ self.bias,
215
+ self.in_features,
216
+ self.out_features,
217
+ )
218
+
219
+
220
+ initialize_native_sm120_op()
221
+ atexit.register(_quiet_atexit_close)
222
+
223
+
224
+ __all__ = [
225
+ "DEFAULT_EXTENSION_PATH",
226
+ "OP_NAME",
227
+ "OP_NAMESPACE",
228
+ "OP_QUALNAME",
229
+ "PackedNvfp4LinearNativeOp",
230
+ "close_native_contexts",
231
+ "ensure_native_sm120_schema",
232
+ "initialize_native_sm120_op",
233
+ ]
scheduler/scheduler_config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "FlowMatchEulerDiscreteScheduler",
3
+ "_diffusers_version": "0.37.0",
4
+ "num_train_timesteps": 1000,
5
+ "use_dynamic_shifting": false,
6
+ "shift": 6.0
7
+ }
text_encoder/chat_template.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].role == 'system' %}\n {%- if messages[0].content is string %}\n {{- messages[0].content }}\n {%- else %}\n {%- for content in messages[0].content %}\n {%- if 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '\\n\\n' }}\n {%- endif %}\n {{- \"# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].content is string %}\n {{- messages[0].content }}\n {%- else %}\n {%- for content in messages[0].content %}\n {%- if 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set image_count = namespace(value=0) %}\n{%- set video_count = namespace(value=0) %}\n{%- for message in messages %}\n {%- if message.role == \"user\" %}\n {{- '<|im_start|>' + message.role + '\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content in message.content %}\n {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}\n <|vision_start|><|image_pad|><|vision_end|>\n {%- elif content.type == 'video' or 'video' in content %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}\n <|vision_start|><|video_pad|><|vision_end|>\n {%- elif 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role + '\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content_item in message.content %}\n {%- if 'text' in content_item %}\n {{- content_item.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {%- if message.tool_calls %}\n {%- for tool_call in message.tool_calls %}\n {%- if (loop.first and message.content) or (not loop.first) %}\n {{- '\\n' }}\n {%- endif %}\n {%- if tool_call.function %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {%- if tool_call.arguments is string %}\n {{- tool_call.arguments }}\n {%- else %}\n {{- tool_call.arguments | tojson }}\n {%- endif %}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.first or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content in message.content %}\n {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}\n <|vision_start|><|image_pad|><|vision_end|>\n {%- elif content.type == 'video' or 'video' in content %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}\n <|vision_start|><|video_pad|><|vision_end|>\n {%- elif 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
3
+ }
4
+
text_encoder/config.json ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen3VLForConditionalGeneration"
4
+ ],
5
+ "image_token_id": 151655,
6
+ "model_type": "qwen3_vl",
7
+ "quantization_config": {
8
+ "bf16_policy": "blocks 0/35, embeddings, norms, biases, and vision tower",
9
+ "compute_dtype": "bfloat16",
10
+ "fp8_projection_count": 14,
11
+ "nvfp4_projection_count": 224,
12
+ "quant_algo": "NVFP4_FP8_MIXED",
13
+ "quant_method": "comfy_kitchen"
14
+ },
15
+ "text_config": {
16
+ "attention_bias": false,
17
+ "attention_dropout": 0.0,
18
+ "bos_token_id": 151643,
19
+ "dtype": "bfloat16",
20
+ "eos_token_id": 151645,
21
+ "head_dim": 128,
22
+ "hidden_act": "silu",
23
+ "hidden_size": 2560,
24
+ "initializer_range": 0.02,
25
+ "intermediate_size": 9728,
26
+ "max_position_embeddings": 262144,
27
+ "model_type": "qwen3_vl_text",
28
+ "num_attention_heads": 32,
29
+ "num_hidden_layers": 36,
30
+ "num_key_value_heads": 8,
31
+ "rms_norm_eps": 1e-06,
32
+ "rope_scaling": {
33
+ "mrope_interleaved": true,
34
+ "mrope_section": [
35
+ 24,
36
+ 20,
37
+ 20
38
+ ],
39
+ "rope_type": "default"
40
+ },
41
+ "rope_theta": 5000000,
42
+ "tie_word_embeddings": true,
43
+ "use_cache": true,
44
+ "vocab_size": 151936
45
+ },
46
+ "tie_word_embeddings": true,
47
+ "transformers_version": "4.57.0.dev0",
48
+ "video_token_id": 151656,
49
+ "vision_config": {
50
+ "deepstack_visual_indexes": [
51
+ 5,
52
+ 11,
53
+ 17
54
+ ],
55
+ "depth": 24,
56
+ "hidden_act": "gelu_pytorch_tanh",
57
+ "hidden_size": 1024,
58
+ "in_channels": 3,
59
+ "initializer_range": 0.02,
60
+ "intermediate_size": 4096,
61
+ "model_type": "qwen3_vl",
62
+ "num_heads": 16,
63
+ "num_position_embeddings": 2304,
64
+ "out_hidden_size": 2560,
65
+ "patch_size": 16,
66
+ "spatial_merge_size": 2,
67
+ "temporal_patch_size": 2
68
+ },
69
+ "vision_end_token_id": 151653,
70
+ "vision_start_token_id": 151652
71
+ }
text_encoder/generation_config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 151643,
3
+ "pad_token_id": 151643,
4
+ "do_sample": true,
5
+ "eos_token_id": [
6
+ 151645,
7
+ 151643
8
+ ],
9
+ "top_k": 20,
10
+ "top_p": 0.8,
11
+ "repetition_penalty": 1.0,
12
+ "temperature": 0.7,
13
+ "transformers_version": "4.56.0"
14
+ }
text_encoder/merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
text_encoder/preprocessor_config.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "size": {
3
+ "longest_edge": 16777216,
4
+ "shortest_edge": 65536
5
+ },
6
+ "patch_size": 16,
7
+ "temporal_patch_size": 2,
8
+ "merge_size": 2,
9
+ "image_mean": [
10
+ 0.5,
11
+ 0.5,
12
+ 0.5
13
+ ],
14
+ "image_std": [
15
+ 0.5,
16
+ 0.5,
17
+ 0.5
18
+ ],
19
+ "processor_class": "Qwen3VLProcessor",
20
+ "image_processor_type": "Qwen2VLImageProcessorFast"
21
+ }
text_encoder/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
text_encoder/tokenizer_config.json ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_bos_token": false,
3
+ "add_prefix_space": false,
4
+ "added_tokens_decoder": {
5
+ "151643": {
6
+ "content": "<|endoftext|>",
7
+ "lstrip": false,
8
+ "normalized": false,
9
+ "rstrip": false,
10
+ "single_word": false,
11
+ "special": true
12
+ },
13
+ "151644": {
14
+ "content": "<|im_start|>",
15
+ "lstrip": false,
16
+ "normalized": false,
17
+ "rstrip": false,
18
+ "single_word": false,
19
+ "special": true
20
+ },
21
+ "151645": {
22
+ "content": "<|im_end|>",
23
+ "lstrip": false,
24
+ "normalized": false,
25
+ "rstrip": false,
26
+ "single_word": false,
27
+ "special": true
28
+ },
29
+ "151646": {
30
+ "content": "<|object_ref_start|>",
31
+ "lstrip": false,
32
+ "normalized": false,
33
+ "rstrip": false,
34
+ "single_word": false,
35
+ "special": true
36
+ },
37
+ "151647": {
38
+ "content": "<|object_ref_end|>",
39
+ "lstrip": false,
40
+ "normalized": false,
41
+ "rstrip": false,
42
+ "single_word": false,
43
+ "special": true
44
+ },
45
+ "151648": {
46
+ "content": "<|box_start|>",
47
+ "lstrip": false,
48
+ "normalized": false,
49
+ "rstrip": false,
50
+ "single_word": false,
51
+ "special": true
52
+ },
53
+ "151649": {
54
+ "content": "<|box_end|>",
55
+ "lstrip": false,
56
+ "normalized": false,
57
+ "rstrip": false,
58
+ "single_word": false,
59
+ "special": true
60
+ },
61
+ "151650": {
62
+ "content": "<|quad_start|>",
63
+ "lstrip": false,
64
+ "normalized": false,
65
+ "rstrip": false,
66
+ "single_word": false,
67
+ "special": true
68
+ },
69
+ "151651": {
70
+ "content": "<|quad_end|>",
71
+ "lstrip": false,
72
+ "normalized": false,
73
+ "rstrip": false,
74
+ "single_word": false,
75
+ "special": true
76
+ },
77
+ "151652": {
78
+ "content": "<|vision_start|>",
79
+ "lstrip": false,
80
+ "normalized": false,
81
+ "rstrip": false,
82
+ "single_word": false,
83
+ "special": true
84
+ },
85
+ "151653": {
86
+ "content": "<|vision_end|>",
87
+ "lstrip": false,
88
+ "normalized": false,
89
+ "rstrip": false,
90
+ "single_word": false,
91
+ "special": true
92
+ },
93
+ "151654": {
94
+ "content": "<|vision_pad|>",
95
+ "lstrip": false,
96
+ "normalized": false,
97
+ "rstrip": false,
98
+ "single_word": false,
99
+ "special": true
100
+ },
101
+ "151655": {
102
+ "content": "<|image_pad|>",
103
+ "lstrip": false,
104
+ "normalized": false,
105
+ "rstrip": false,
106
+ "single_word": false,
107
+ "special": true
108
+ },
109
+ "151656": {
110
+ "content": "<|video_pad|>",
111
+ "lstrip": false,
112
+ "normalized": false,
113
+ "rstrip": false,
114
+ "single_word": false,
115
+ "special": true
116
+ },
117
+ "151657": {
118
+ "content": "<tool_call>",
119
+ "lstrip": false,
120
+ "normalized": false,
121
+ "rstrip": false,
122
+ "single_word": false,
123
+ "special": false
124
+ },
125
+ "151658": {
126
+ "content": "</tool_call>",
127
+ "lstrip": false,
128
+ "normalized": false,
129
+ "rstrip": false,
130
+ "single_word": false,
131
+ "special": false
132
+ },
133
+ "151659": {
134
+ "content": "<|fim_prefix|>",
135
+ "lstrip": false,
136
+ "normalized": false,
137
+ "rstrip": false,
138
+ "single_word": false,
139
+ "special": false
140
+ },
141
+ "151660": {
142
+ "content": "<|fim_middle|>",
143
+ "lstrip": false,
144
+ "normalized": false,
145
+ "rstrip": false,
146
+ "single_word": false,
147
+ "special": false
148
+ },
149
+ "151661": {
150
+ "content": "<|fim_suffix|>",
151
+ "lstrip": false,
152
+ "normalized": false,
153
+ "rstrip": false,
154
+ "single_word": false,
155
+ "special": false
156
+ },
157
+ "151662": {
158
+ "content": "<|fim_pad|>",
159
+ "lstrip": false,
160
+ "normalized": false,
161
+ "rstrip": false,
162
+ "single_word": false,
163
+ "special": false
164
+ },
165
+ "151663": {
166
+ "content": "<|repo_name|>",
167
+ "lstrip": false,
168
+ "normalized": false,
169
+ "rstrip": false,
170
+ "single_word": false,
171
+ "special": false
172
+ },
173
+ "151664": {
174
+ "content": "<|file_sep|>",
175
+ "lstrip": false,
176
+ "normalized": false,
177
+ "rstrip": false,
178
+ "single_word": false,
179
+ "special": false
180
+ },
181
+ "151665": {
182
+ "content": "<tool_response>",
183
+ "lstrip": false,
184
+ "normalized": false,
185
+ "rstrip": false,
186
+ "single_word": false,
187
+ "special": false
188
+ },
189
+ "151666": {
190
+ "content": "</tool_response>",
191
+ "lstrip": false,
192
+ "normalized": false,
193
+ "rstrip": false,
194
+ "single_word": false,
195
+ "special": false
196
+ },
197
+ "151667": {
198
+ "content": "<think>",
199
+ "lstrip": false,
200
+ "normalized": false,
201
+ "rstrip": false,
202
+ "single_word": false,
203
+ "special": false
204
+ },
205
+ "151668": {
206
+ "content": "</think>",
207
+ "lstrip": false,
208
+ "normalized": false,
209
+ "rstrip": false,
210
+ "single_word": false,
211
+ "special": false
212
+ }
213
+ },
214
+ "additional_special_tokens": [
215
+ "<|im_start|>",
216
+ "<|im_end|>",
217
+ "<|object_ref_start|>",
218
+ "<|object_ref_end|>",
219
+ "<|box_start|>",
220
+ "<|box_end|>",
221
+ "<|quad_start|>",
222
+ "<|quad_end|>",
223
+ "<|vision_start|>",
224
+ "<|vision_end|>",
225
+ "<|vision_pad|>",
226
+ "<|image_pad|>",
227
+ "<|video_pad|>"
228
+ ],
229
+ "bos_token": null,
230
+ "chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].role == 'system' %}\n {%- if messages[0].content is string %}\n {{- messages[0].content }}\n {%- else %}\n {%- for content in messages[0].content %}\n {%- if 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '\\n\\n' }}\n {%- endif %}\n {{- \"# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].content is string %}\n {{- messages[0].content }}\n {%- else %}\n {%- for content in messages[0].content %}\n {%- if 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set image_count = namespace(value=0) %}\n{%- set video_count = namespace(value=0) %}\n{%- for message in messages %}\n {%- if message.role == \"user\" %}\n {{- '<|im_start|>' + message.role + '\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content in message.content %}\n {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}\n <|vision_start|><|image_pad|><|vision_end|>\n {%- elif content.type == 'video' or 'video' in content %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}\n <|vision_start|><|video_pad|><|vision_end|>\n {%- elif 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role + '\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content_item in message.content %}\n {%- if 'text' in content_item %}\n {{- content_item.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {%- if message.tool_calls %}\n {%- for tool_call in message.tool_calls %}\n {%- if (loop.first and message.content) or (not loop.first) %}\n {{- '\\n' }}\n {%- endif %}\n {%- if tool_call.function %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {%- if tool_call.arguments is string %}\n {{- tool_call.arguments }}\n {%- else %}\n {{- tool_call.arguments | tojson }}\n {%- endif %}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.first or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content in message.content %}\n {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}\n <|vision_start|><|image_pad|><|vision_end|>\n {%- elif content.type == 'video' or 'video' in content %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}\n <|vision_start|><|video_pad|><|vision_end|>\n {%- elif 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n",
231
+ "clean_up_tokenization_spaces": false,
232
+ "eos_token": "<|im_end|>",
233
+ "errors": "replace",
234
+ "model_max_length": 262144,
235
+ "pad_token": "<|endoftext|>",
236
+ "split_special_tokens": false,
237
+ "tokenizer_class": "Qwen2Tokenizer",
238
+ "unk_token": null
239
+ }
text_encoder/video_preprocessor_config.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "size": {
3
+ "longest_edge": 25165824,
4
+ "shortest_edge": 4096
5
+ },
6
+ "patch_size": 16,
7
+ "temporal_patch_size": 2,
8
+ "merge_size": 2,
9
+ "image_mean": [
10
+ 0.5,
11
+ 0.5,
12
+ 0.5
13
+ ],
14
+ "image_std": [
15
+ 0.5,
16
+ 0.5,
17
+ 0.5
18
+ ],
19
+ "processor_class": "Qwen3VLProcessor",
20
+ "video_processor_type": "Qwen3VLVideoProcessor"
21
+ }
text_encoder/vocab.json ADDED
The diff for this file is too large to render. See raw diff
 
transformer/config.json ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "MageFlow",
3
+ "apply_text_rotary_emb": false,
4
+ "axes_dim": [
5
+ 16,
6
+ 56,
7
+ 56
8
+ ],
9
+ "checkpoint": false,
10
+ "context_in_dim": 2560,
11
+ "depth": 12,
12
+ "depth_single_blocks": 0,
13
+ "double_block_type": "double_stream",
14
+ "guidance_embed": false,
15
+ "hidden_size": 3072,
16
+ "in_channels": 128,
17
+ "max_sequence_length": 2048,
18
+ "mlp_ratio": 4.0,
19
+ "num_heads": 24,
20
+ "out_channels": 128,
21
+ "packing": true,
22
+ "param_dtype": "bfloat16",
23
+ "patch_size": 1,
24
+ "qkv_bias": true,
25
+ "quantization_config": {
26
+ "backend": "native_sm120",
27
+ "compute_dtype": "bfloat16",
28
+ "format": "e2m1_block16_ue4m3",
29
+ "quant_algo": "NVFP4",
30
+ "quant_method": "mage_flow_nvfp4",
31
+ "target_count": 48,
32
+ "targets": [
33
+ "transformer_blocks.0.img_mlp.net.0.proj",
34
+ "transformer_blocks.0.img_mlp.net.2",
35
+ "transformer_blocks.0.txt_mlp.net.0.proj",
36
+ "transformer_blocks.0.txt_mlp.net.2",
37
+ "transformer_blocks.1.img_mlp.net.0.proj",
38
+ "transformer_blocks.1.img_mlp.net.2",
39
+ "transformer_blocks.1.txt_mlp.net.0.proj",
40
+ "transformer_blocks.1.txt_mlp.net.2",
41
+ "transformer_blocks.2.img_mlp.net.0.proj",
42
+ "transformer_blocks.2.img_mlp.net.2",
43
+ "transformer_blocks.2.txt_mlp.net.0.proj",
44
+ "transformer_blocks.2.txt_mlp.net.2",
45
+ "transformer_blocks.3.img_mlp.net.0.proj",
46
+ "transformer_blocks.3.img_mlp.net.2",
47
+ "transformer_blocks.3.txt_mlp.net.0.proj",
48
+ "transformer_blocks.3.txt_mlp.net.2",
49
+ "transformer_blocks.4.img_mlp.net.0.proj",
50
+ "transformer_blocks.4.img_mlp.net.2",
51
+ "transformer_blocks.4.txt_mlp.net.0.proj",
52
+ "transformer_blocks.4.txt_mlp.net.2",
53
+ "transformer_blocks.5.img_mlp.net.0.proj",
54
+ "transformer_blocks.5.img_mlp.net.2",
55
+ "transformer_blocks.5.txt_mlp.net.0.proj",
56
+ "transformer_blocks.5.txt_mlp.net.2",
57
+ "transformer_blocks.6.img_mlp.net.0.proj",
58
+ "transformer_blocks.6.img_mlp.net.2",
59
+ "transformer_blocks.6.txt_mlp.net.0.proj",
60
+ "transformer_blocks.6.txt_mlp.net.2",
61
+ "transformer_blocks.7.img_mlp.net.0.proj",
62
+ "transformer_blocks.7.img_mlp.net.2",
63
+ "transformer_blocks.7.txt_mlp.net.0.proj",
64
+ "transformer_blocks.7.txt_mlp.net.2",
65
+ "transformer_blocks.8.img_mlp.net.0.proj",
66
+ "transformer_blocks.8.img_mlp.net.2",
67
+ "transformer_blocks.8.txt_mlp.net.0.proj",
68
+ "transformer_blocks.8.txt_mlp.net.2",
69
+ "transformer_blocks.9.img_mlp.net.0.proj",
70
+ "transformer_blocks.9.img_mlp.net.2",
71
+ "transformer_blocks.9.txt_mlp.net.0.proj",
72
+ "transformer_blocks.9.txt_mlp.net.2",
73
+ "transformer_blocks.10.img_mlp.net.0.proj",
74
+ "transformer_blocks.10.img_mlp.net.2",
75
+ "transformer_blocks.10.txt_mlp.net.0.proj",
76
+ "transformer_blocks.10.txt_mlp.net.2",
77
+ "transformer_blocks.11.img_mlp.net.0.proj",
78
+ "transformer_blocks.11.img_mlp.net.2",
79
+ "transformer_blocks.11.txt_mlp.net.0.proj",
80
+ "transformer_blocks.11.txt_mlp.net.2"
81
+ ]
82
+ },
83
+ "rope_type": "msrope",
84
+ "schedule_mode": "z-image",
85
+ "static_shift": 6.0,
86
+ "theta": 10000,
87
+ "time_type": "qwen_proj",
88
+ "txt_max_length": 2048,
89
+ "use_time_shift": false,
90
+ "vec_in_dim": 0,
91
+ "vec_type": null
92
+ }
transformer/diffusion_pytorch_model.safetensors.index.json ADDED
@@ -0,0 +1,501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "metadata": {
3
+ "quantization": "mage_flow_nvfp4_resident_v1",
4
+ "total_size": 5626828224
5
+ },
6
+ "weight_map": {
7
+ "img_in.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
8
+ "img_in.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
9
+ "norm_out.linear.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
10
+ "norm_out.linear.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
11
+ "proj_out.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
12
+ "proj_out.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
13
+ "time_text_embed.timestep_embedder.linear_1.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
14
+ "time_text_embed.timestep_embedder.linear_1.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
15
+ "time_text_embed.timestep_embedder.linear_2.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
16
+ "time_text_embed.timestep_embedder.linear_2.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
17
+ "transformer_blocks.0.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
18
+ "transformer_blocks.0.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
19
+ "transformer_blocks.0.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
20
+ "transformer_blocks.0.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
21
+ "transformer_blocks.0.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
22
+ "transformer_blocks.0.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
23
+ "transformer_blocks.0.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
24
+ "transformer_blocks.0.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
25
+ "transformer_blocks.0.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
26
+ "transformer_blocks.0.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
27
+ "transformer_blocks.0.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
28
+ "transformer_blocks.0.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
29
+ "transformer_blocks.0.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
30
+ "transformer_blocks.0.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
31
+ "transformer_blocks.0.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
32
+ "transformer_blocks.0.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
33
+ "transformer_blocks.0.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
34
+ "transformer_blocks.0.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
35
+ "transformer_blocks.0.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
36
+ "transformer_blocks.0.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
37
+ "transformer_blocks.0.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
38
+ "transformer_blocks.0.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
39
+ "transformer_blocks.0.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
40
+ "transformer_blocks.0.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
41
+ "transformer_blocks.0.img_mlp.net.2.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
42
+ "transformer_blocks.0.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
43
+ "transformer_blocks.0.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
44
+ "transformer_blocks.0.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
45
+ "transformer_blocks.0.img_mod.1.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
46
+ "transformer_blocks.0.img_mod.1.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
47
+ "transformer_blocks.0.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
48
+ "transformer_blocks.0.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
49
+ "transformer_blocks.0.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
50
+ "transformer_blocks.0.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
51
+ "transformer_blocks.0.txt_mlp.net.2.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
52
+ "transformer_blocks.0.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
53
+ "transformer_blocks.0.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
54
+ "transformer_blocks.0.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
55
+ "transformer_blocks.0.txt_mod.1.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
56
+ "transformer_blocks.0.txt_mod.1.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
57
+ "transformer_blocks.1.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
58
+ "transformer_blocks.1.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
59
+ "transformer_blocks.1.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
60
+ "transformer_blocks.1.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
61
+ "transformer_blocks.1.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
62
+ "transformer_blocks.1.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
63
+ "transformer_blocks.1.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
64
+ "transformer_blocks.1.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
65
+ "transformer_blocks.1.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
66
+ "transformer_blocks.1.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
67
+ "transformer_blocks.1.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
68
+ "transformer_blocks.1.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
69
+ "transformer_blocks.1.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
70
+ "transformer_blocks.1.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
71
+ "transformer_blocks.1.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
72
+ "transformer_blocks.1.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
73
+ "transformer_blocks.1.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
74
+ "transformer_blocks.1.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
75
+ "transformer_blocks.1.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
76
+ "transformer_blocks.1.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
77
+ "transformer_blocks.1.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
78
+ "transformer_blocks.1.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
79
+ "transformer_blocks.1.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
80
+ "transformer_blocks.1.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
81
+ "transformer_blocks.1.img_mlp.net.2.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
82
+ "transformer_blocks.1.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
83
+ "transformer_blocks.1.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
84
+ "transformer_blocks.1.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
85
+ "transformer_blocks.1.img_mod.1.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
86
+ "transformer_blocks.1.img_mod.1.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
87
+ "transformer_blocks.1.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
88
+ "transformer_blocks.1.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
89
+ "transformer_blocks.1.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
90
+ "transformer_blocks.1.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
91
+ "transformer_blocks.1.txt_mlp.net.2.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
92
+ "transformer_blocks.1.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
93
+ "transformer_blocks.1.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
94
+ "transformer_blocks.1.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
95
+ "transformer_blocks.1.txt_mod.1.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
96
+ "transformer_blocks.1.txt_mod.1.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
97
+ "transformer_blocks.10.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
98
+ "transformer_blocks.10.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
99
+ "transformer_blocks.10.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
100
+ "transformer_blocks.10.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
101
+ "transformer_blocks.10.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
102
+ "transformer_blocks.10.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
103
+ "transformer_blocks.10.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
104
+ "transformer_blocks.10.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
105
+ "transformer_blocks.10.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
106
+ "transformer_blocks.10.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
107
+ "transformer_blocks.10.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
108
+ "transformer_blocks.10.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
109
+ "transformer_blocks.10.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
110
+ "transformer_blocks.10.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
111
+ "transformer_blocks.10.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
112
+ "transformer_blocks.10.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
113
+ "transformer_blocks.10.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
114
+ "transformer_blocks.10.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
115
+ "transformer_blocks.10.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
116
+ "transformer_blocks.10.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
117
+ "transformer_blocks.10.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
118
+ "transformer_blocks.10.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
119
+ "transformer_blocks.10.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
120
+ "transformer_blocks.10.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
121
+ "transformer_blocks.10.img_mlp.net.2.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
122
+ "transformer_blocks.10.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
123
+ "transformer_blocks.10.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
124
+ "transformer_blocks.10.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
125
+ "transformer_blocks.10.img_mod.1.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
126
+ "transformer_blocks.10.img_mod.1.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
127
+ "transformer_blocks.10.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
128
+ "transformer_blocks.10.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
129
+ "transformer_blocks.10.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
130
+ "transformer_blocks.10.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
131
+ "transformer_blocks.10.txt_mlp.net.2.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
132
+ "transformer_blocks.10.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
133
+ "transformer_blocks.10.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
134
+ "transformer_blocks.10.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
135
+ "transformer_blocks.10.txt_mod.1.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
136
+ "transformer_blocks.10.txt_mod.1.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
137
+ "transformer_blocks.11.attn.add_k_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
138
+ "transformer_blocks.11.attn.add_k_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
139
+ "transformer_blocks.11.attn.add_q_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
140
+ "transformer_blocks.11.attn.add_q_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
141
+ "transformer_blocks.11.attn.add_v_proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
142
+ "transformer_blocks.11.attn.add_v_proj.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
143
+ "transformer_blocks.11.attn.norm_added_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
144
+ "transformer_blocks.11.attn.norm_added_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
145
+ "transformer_blocks.11.attn.norm_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
146
+ "transformer_blocks.11.attn.norm_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
147
+ "transformer_blocks.11.attn.to_add_out.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
148
+ "transformer_blocks.11.attn.to_add_out.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
149
+ "transformer_blocks.11.attn.to_k.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
150
+ "transformer_blocks.11.attn.to_k.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
151
+ "transformer_blocks.11.attn.to_out.0.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
152
+ "transformer_blocks.11.attn.to_out.0.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
153
+ "transformer_blocks.11.attn.to_q.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
154
+ "transformer_blocks.11.attn.to_q.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
155
+ "transformer_blocks.11.attn.to_v.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
156
+ "transformer_blocks.11.attn.to_v.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
157
+ "transformer_blocks.11.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
158
+ "transformer_blocks.11.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
159
+ "transformer_blocks.11.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
160
+ "transformer_blocks.11.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
161
+ "transformer_blocks.11.img_mlp.net.2.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
162
+ "transformer_blocks.11.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
163
+ "transformer_blocks.11.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
164
+ "transformer_blocks.11.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
165
+ "transformer_blocks.11.img_mod.1.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
166
+ "transformer_blocks.11.img_mod.1.weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
167
+ "transformer_blocks.11.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
168
+ "transformer_blocks.11.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
169
+ "transformer_blocks.11.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
170
+ "transformer_blocks.11.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
171
+ "transformer_blocks.11.txt_mlp.net.2.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
172
+ "transformer_blocks.11.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00001-of-00004.safetensors",
173
+ "transformer_blocks.11.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00001-of-00004.safetensors",
174
+ "transformer_blocks.11.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00001-of-00004.safetensors",
175
+ "transformer_blocks.11.txt_mod.1.bias": "diffusion_pytorch_model-00001-of-00004.safetensors",
176
+ "transformer_blocks.11.txt_mod.1.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
177
+ "transformer_blocks.2.attn.add_k_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
178
+ "transformer_blocks.2.attn.add_k_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
179
+ "transformer_blocks.2.attn.add_q_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
180
+ "transformer_blocks.2.attn.add_q_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
181
+ "transformer_blocks.2.attn.add_v_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
182
+ "transformer_blocks.2.attn.add_v_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
183
+ "transformer_blocks.2.attn.norm_added_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
184
+ "transformer_blocks.2.attn.norm_added_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
185
+ "transformer_blocks.2.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
186
+ "transformer_blocks.2.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
187
+ "transformer_blocks.2.attn.to_add_out.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
188
+ "transformer_blocks.2.attn.to_add_out.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
189
+ "transformer_blocks.2.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
190
+ "transformer_blocks.2.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
191
+ "transformer_blocks.2.attn.to_out.0.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
192
+ "transformer_blocks.2.attn.to_out.0.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
193
+ "transformer_blocks.2.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
194
+ "transformer_blocks.2.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
195
+ "transformer_blocks.2.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
196
+ "transformer_blocks.2.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
197
+ "transformer_blocks.2.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
198
+ "transformer_blocks.2.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
199
+ "transformer_blocks.2.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
200
+ "transformer_blocks.2.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
201
+ "transformer_blocks.2.img_mlp.net.2.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
202
+ "transformer_blocks.2.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
203
+ "transformer_blocks.2.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
204
+ "transformer_blocks.2.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
205
+ "transformer_blocks.2.img_mod.1.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
206
+ "transformer_blocks.2.img_mod.1.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
207
+ "transformer_blocks.2.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
208
+ "transformer_blocks.2.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
209
+ "transformer_blocks.2.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
210
+ "transformer_blocks.2.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
211
+ "transformer_blocks.2.txt_mlp.net.2.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
212
+ "transformer_blocks.2.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
213
+ "transformer_blocks.2.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
214
+ "transformer_blocks.2.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
215
+ "transformer_blocks.2.txt_mod.1.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
216
+ "transformer_blocks.2.txt_mod.1.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
217
+ "transformer_blocks.3.attn.add_k_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
218
+ "transformer_blocks.3.attn.add_k_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
219
+ "transformer_blocks.3.attn.add_q_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
220
+ "transformer_blocks.3.attn.add_q_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
221
+ "transformer_blocks.3.attn.add_v_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
222
+ "transformer_blocks.3.attn.add_v_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
223
+ "transformer_blocks.3.attn.norm_added_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
224
+ "transformer_blocks.3.attn.norm_added_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
225
+ "transformer_blocks.3.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
226
+ "transformer_blocks.3.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
227
+ "transformer_blocks.3.attn.to_add_out.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
228
+ "transformer_blocks.3.attn.to_add_out.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
229
+ "transformer_blocks.3.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
230
+ "transformer_blocks.3.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
231
+ "transformer_blocks.3.attn.to_out.0.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
232
+ "transformer_blocks.3.attn.to_out.0.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
233
+ "transformer_blocks.3.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
234
+ "transformer_blocks.3.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
235
+ "transformer_blocks.3.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
236
+ "transformer_blocks.3.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
237
+ "transformer_blocks.3.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
238
+ "transformer_blocks.3.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
239
+ "transformer_blocks.3.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
240
+ "transformer_blocks.3.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
241
+ "transformer_blocks.3.img_mlp.net.2.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
242
+ "transformer_blocks.3.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
243
+ "transformer_blocks.3.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
244
+ "transformer_blocks.3.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
245
+ "transformer_blocks.3.img_mod.1.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
246
+ "transformer_blocks.3.img_mod.1.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
247
+ "transformer_blocks.3.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
248
+ "transformer_blocks.3.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
249
+ "transformer_blocks.3.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
250
+ "transformer_blocks.3.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
251
+ "transformer_blocks.3.txt_mlp.net.2.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
252
+ "transformer_blocks.3.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
253
+ "transformer_blocks.3.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
254
+ "transformer_blocks.3.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
255
+ "transformer_blocks.3.txt_mod.1.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
256
+ "transformer_blocks.3.txt_mod.1.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
257
+ "transformer_blocks.4.attn.add_k_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
258
+ "transformer_blocks.4.attn.add_k_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
259
+ "transformer_blocks.4.attn.add_q_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
260
+ "transformer_blocks.4.attn.add_q_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
261
+ "transformer_blocks.4.attn.add_v_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
262
+ "transformer_blocks.4.attn.add_v_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
263
+ "transformer_blocks.4.attn.norm_added_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
264
+ "transformer_blocks.4.attn.norm_added_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
265
+ "transformer_blocks.4.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
266
+ "transformer_blocks.4.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
267
+ "transformer_blocks.4.attn.to_add_out.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
268
+ "transformer_blocks.4.attn.to_add_out.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
269
+ "transformer_blocks.4.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
270
+ "transformer_blocks.4.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
271
+ "transformer_blocks.4.attn.to_out.0.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
272
+ "transformer_blocks.4.attn.to_out.0.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
273
+ "transformer_blocks.4.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
274
+ "transformer_blocks.4.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
275
+ "transformer_blocks.4.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
276
+ "transformer_blocks.4.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
277
+ "transformer_blocks.4.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
278
+ "transformer_blocks.4.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
279
+ "transformer_blocks.4.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
280
+ "transformer_blocks.4.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
281
+ "transformer_blocks.4.img_mlp.net.2.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
282
+ "transformer_blocks.4.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
283
+ "transformer_blocks.4.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
284
+ "transformer_blocks.4.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
285
+ "transformer_blocks.4.img_mod.1.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
286
+ "transformer_blocks.4.img_mod.1.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
287
+ "transformer_blocks.4.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
288
+ "transformer_blocks.4.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
289
+ "transformer_blocks.4.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
290
+ "transformer_blocks.4.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
291
+ "transformer_blocks.4.txt_mlp.net.2.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
292
+ "transformer_blocks.4.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
293
+ "transformer_blocks.4.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
294
+ "transformer_blocks.4.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
295
+ "transformer_blocks.4.txt_mod.1.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
296
+ "transformer_blocks.4.txt_mod.1.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
297
+ "transformer_blocks.5.attn.add_k_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
298
+ "transformer_blocks.5.attn.add_k_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
299
+ "transformer_blocks.5.attn.add_q_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
300
+ "transformer_blocks.5.attn.add_q_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
301
+ "transformer_blocks.5.attn.add_v_proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
302
+ "transformer_blocks.5.attn.add_v_proj.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
303
+ "transformer_blocks.5.attn.norm_added_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
304
+ "transformer_blocks.5.attn.norm_added_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
305
+ "transformer_blocks.5.attn.norm_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
306
+ "transformer_blocks.5.attn.norm_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
307
+ "transformer_blocks.5.attn.to_add_out.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
308
+ "transformer_blocks.5.attn.to_add_out.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
309
+ "transformer_blocks.5.attn.to_k.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
310
+ "transformer_blocks.5.attn.to_k.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
311
+ "transformer_blocks.5.attn.to_out.0.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
312
+ "transformer_blocks.5.attn.to_out.0.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
313
+ "transformer_blocks.5.attn.to_q.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
314
+ "transformer_blocks.5.attn.to_q.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
315
+ "transformer_blocks.5.attn.to_v.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
316
+ "transformer_blocks.5.attn.to_v.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
317
+ "transformer_blocks.5.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
318
+ "transformer_blocks.5.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
319
+ "transformer_blocks.5.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
320
+ "transformer_blocks.5.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
321
+ "transformer_blocks.5.img_mlp.net.2.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
322
+ "transformer_blocks.5.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
323
+ "transformer_blocks.5.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
324
+ "transformer_blocks.5.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
325
+ "transformer_blocks.5.img_mod.1.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
326
+ "transformer_blocks.5.img_mod.1.weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
327
+ "transformer_blocks.5.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
328
+ "transformer_blocks.5.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
329
+ "transformer_blocks.5.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
330
+ "transformer_blocks.5.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
331
+ "transformer_blocks.5.txt_mlp.net.2.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
332
+ "transformer_blocks.5.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00002-of-00004.safetensors",
333
+ "transformer_blocks.5.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00002-of-00004.safetensors",
334
+ "transformer_blocks.5.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00002-of-00004.safetensors",
335
+ "transformer_blocks.5.txt_mod.1.bias": "diffusion_pytorch_model-00002-of-00004.safetensors",
336
+ "transformer_blocks.5.txt_mod.1.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
337
+ "transformer_blocks.6.attn.add_k_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
338
+ "transformer_blocks.6.attn.add_k_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
339
+ "transformer_blocks.6.attn.add_q_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
340
+ "transformer_blocks.6.attn.add_q_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
341
+ "transformer_blocks.6.attn.add_v_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
342
+ "transformer_blocks.6.attn.add_v_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
343
+ "transformer_blocks.6.attn.norm_added_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
344
+ "transformer_blocks.6.attn.norm_added_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
345
+ "transformer_blocks.6.attn.norm_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
346
+ "transformer_blocks.6.attn.norm_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
347
+ "transformer_blocks.6.attn.to_add_out.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
348
+ "transformer_blocks.6.attn.to_add_out.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
349
+ "transformer_blocks.6.attn.to_k.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
350
+ "transformer_blocks.6.attn.to_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
351
+ "transformer_blocks.6.attn.to_out.0.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
352
+ "transformer_blocks.6.attn.to_out.0.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
353
+ "transformer_blocks.6.attn.to_q.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
354
+ "transformer_blocks.6.attn.to_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
355
+ "transformer_blocks.6.attn.to_v.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
356
+ "transformer_blocks.6.attn.to_v.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
357
+ "transformer_blocks.6.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
358
+ "transformer_blocks.6.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
359
+ "transformer_blocks.6.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
360
+ "transformer_blocks.6.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
361
+ "transformer_blocks.6.img_mlp.net.2.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
362
+ "transformer_blocks.6.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
363
+ "transformer_blocks.6.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
364
+ "transformer_blocks.6.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
365
+ "transformer_blocks.6.img_mod.1.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
366
+ "transformer_blocks.6.img_mod.1.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
367
+ "transformer_blocks.6.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
368
+ "transformer_blocks.6.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
369
+ "transformer_blocks.6.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
370
+ "transformer_blocks.6.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
371
+ "transformer_blocks.6.txt_mlp.net.2.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
372
+ "transformer_blocks.6.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
373
+ "transformer_blocks.6.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
374
+ "transformer_blocks.6.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
375
+ "transformer_blocks.6.txt_mod.1.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
376
+ "transformer_blocks.6.txt_mod.1.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
377
+ "transformer_blocks.7.attn.add_k_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
378
+ "transformer_blocks.7.attn.add_k_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
379
+ "transformer_blocks.7.attn.add_q_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
380
+ "transformer_blocks.7.attn.add_q_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
381
+ "transformer_blocks.7.attn.add_v_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
382
+ "transformer_blocks.7.attn.add_v_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
383
+ "transformer_blocks.7.attn.norm_added_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
384
+ "transformer_blocks.7.attn.norm_added_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
385
+ "transformer_blocks.7.attn.norm_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
386
+ "transformer_blocks.7.attn.norm_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
387
+ "transformer_blocks.7.attn.to_add_out.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
388
+ "transformer_blocks.7.attn.to_add_out.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
389
+ "transformer_blocks.7.attn.to_k.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
390
+ "transformer_blocks.7.attn.to_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
391
+ "transformer_blocks.7.attn.to_out.0.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
392
+ "transformer_blocks.7.attn.to_out.0.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
393
+ "transformer_blocks.7.attn.to_q.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
394
+ "transformer_blocks.7.attn.to_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
395
+ "transformer_blocks.7.attn.to_v.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
396
+ "transformer_blocks.7.attn.to_v.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
397
+ "transformer_blocks.7.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
398
+ "transformer_blocks.7.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
399
+ "transformer_blocks.7.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
400
+ "transformer_blocks.7.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
401
+ "transformer_blocks.7.img_mlp.net.2.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
402
+ "transformer_blocks.7.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
403
+ "transformer_blocks.7.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
404
+ "transformer_blocks.7.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
405
+ "transformer_blocks.7.img_mod.1.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
406
+ "transformer_blocks.7.img_mod.1.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
407
+ "transformer_blocks.7.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
408
+ "transformer_blocks.7.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
409
+ "transformer_blocks.7.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
410
+ "transformer_blocks.7.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
411
+ "transformer_blocks.7.txt_mlp.net.2.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
412
+ "transformer_blocks.7.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
413
+ "transformer_blocks.7.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
414
+ "transformer_blocks.7.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
415
+ "transformer_blocks.7.txt_mod.1.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
416
+ "transformer_blocks.7.txt_mod.1.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
417
+ "transformer_blocks.8.attn.add_k_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
418
+ "transformer_blocks.8.attn.add_k_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
419
+ "transformer_blocks.8.attn.add_q_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
420
+ "transformer_blocks.8.attn.add_q_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
421
+ "transformer_blocks.8.attn.add_v_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
422
+ "transformer_blocks.8.attn.add_v_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
423
+ "transformer_blocks.8.attn.norm_added_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
424
+ "transformer_blocks.8.attn.norm_added_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
425
+ "transformer_blocks.8.attn.norm_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
426
+ "transformer_blocks.8.attn.norm_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
427
+ "transformer_blocks.8.attn.to_add_out.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
428
+ "transformer_blocks.8.attn.to_add_out.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
429
+ "transformer_blocks.8.attn.to_k.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
430
+ "transformer_blocks.8.attn.to_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
431
+ "transformer_blocks.8.attn.to_out.0.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
432
+ "transformer_blocks.8.attn.to_out.0.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
433
+ "transformer_blocks.8.attn.to_q.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
434
+ "transformer_blocks.8.attn.to_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
435
+ "transformer_blocks.8.attn.to_v.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
436
+ "transformer_blocks.8.attn.to_v.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
437
+ "transformer_blocks.8.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
438
+ "transformer_blocks.8.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
439
+ "transformer_blocks.8.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
440
+ "transformer_blocks.8.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
441
+ "transformer_blocks.8.img_mlp.net.2.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
442
+ "transformer_blocks.8.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
443
+ "transformer_blocks.8.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
444
+ "transformer_blocks.8.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
445
+ "transformer_blocks.8.img_mod.1.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
446
+ "transformer_blocks.8.img_mod.1.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
447
+ "transformer_blocks.8.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
448
+ "transformer_blocks.8.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
449
+ "transformer_blocks.8.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
450
+ "transformer_blocks.8.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
451
+ "transformer_blocks.8.txt_mlp.net.2.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
452
+ "transformer_blocks.8.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
453
+ "transformer_blocks.8.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
454
+ "transformer_blocks.8.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
455
+ "transformer_blocks.8.txt_mod.1.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
456
+ "transformer_blocks.8.txt_mod.1.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
457
+ "transformer_blocks.9.attn.add_k_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
458
+ "transformer_blocks.9.attn.add_k_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
459
+ "transformer_blocks.9.attn.add_q_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
460
+ "transformer_blocks.9.attn.add_q_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
461
+ "transformer_blocks.9.attn.add_v_proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
462
+ "transformer_blocks.9.attn.add_v_proj.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
463
+ "transformer_blocks.9.attn.norm_added_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
464
+ "transformer_blocks.9.attn.norm_added_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
465
+ "transformer_blocks.9.attn.norm_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
466
+ "transformer_blocks.9.attn.norm_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
467
+ "transformer_blocks.9.attn.to_add_out.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
468
+ "transformer_blocks.9.attn.to_add_out.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
469
+ "transformer_blocks.9.attn.to_k.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
470
+ "transformer_blocks.9.attn.to_k.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
471
+ "transformer_blocks.9.attn.to_out.0.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
472
+ "transformer_blocks.9.attn.to_out.0.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
473
+ "transformer_blocks.9.attn.to_q.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
474
+ "transformer_blocks.9.attn.to_q.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
475
+ "transformer_blocks.9.attn.to_v.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
476
+ "transformer_blocks.9.attn.to_v.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
477
+ "transformer_blocks.9.img_mlp.net.0.proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
478
+ "transformer_blocks.9.img_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
479
+ "transformer_blocks.9.img_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
480
+ "transformer_blocks.9.img_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
481
+ "transformer_blocks.9.img_mlp.net.2.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
482
+ "transformer_blocks.9.img_mlp.net.2.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
483
+ "transformer_blocks.9.img_mlp.net.2.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
484
+ "transformer_blocks.9.img_mlp.net.2.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
485
+ "transformer_blocks.9.img_mod.1.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
486
+ "transformer_blocks.9.img_mod.1.weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
487
+ "transformer_blocks.9.txt_mlp.net.0.proj.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
488
+ "transformer_blocks.9.txt_mlp.net.0.proj.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
489
+ "transformer_blocks.9.txt_mlp.net.0.proj.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
490
+ "transformer_blocks.9.txt_mlp.net.0.proj.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
491
+ "transformer_blocks.9.txt_mlp.net.2.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
492
+ "transformer_blocks.9.txt_mlp.net.2.packed_weight": "diffusion_pytorch_model-00003-of-00004.safetensors",
493
+ "transformer_blocks.9.txt_mlp.net.2.weight_scale": "diffusion_pytorch_model-00003-of-00004.safetensors",
494
+ "transformer_blocks.9.txt_mlp.net.2.weight_scales": "diffusion_pytorch_model-00003-of-00004.safetensors",
495
+ "transformer_blocks.9.txt_mod.1.bias": "diffusion_pytorch_model-00003-of-00004.safetensors",
496
+ "transformer_blocks.9.txt_mod.1.weight": "diffusion_pytorch_model-00004-of-00004.safetensors",
497
+ "txt_in.bias": "diffusion_pytorch_model-00004-of-00004.safetensors",
498
+ "txt_in.weight": "diffusion_pytorch_model-00004-of-00004.safetensors",
499
+ "txt_norm.weight": "diffusion_pytorch_model-00004-of-00004.safetensors"
500
+ }
501
+ }
transformer/nvfp4_metadata.json ADDED
@@ -0,0 +1,1648 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "format_version": 1,
3
+ "artifact_kind": "mage_flow_transformer_mlp_nvfp4_resident_v1",
4
+ "created_utc": "2026-07-24T07:00:11.747181+00:00",
5
+ "container": {
6
+ "format": "safetensors",
7
+ "header_metadata": {
8
+ "mage_nvfp4_contract": "mage_flow_transformer_mlp_nvfp4_resident_v1;format_version=1"
9
+ },
10
+ "header_encoding": "single deterministic contract key; legacy two-key draft headers remain readable"
11
+ },
12
+ "source": {
13
+ "transformer_config_path": "BASE_MODEL/transformer/config.json",
14
+ "transformer_checkpoint_path": "BASE_MODEL/transformer/diffusion_pytorch_model.safetensors",
15
+ "transformer_config_sha256": "8493c3b2722738c2a824ac82b1fd9c89fefb4e354fc88363207193db7fe702de",
16
+ "transformer_checkpoint_sha256": "04431abbc3acd1a5b86a7f77269f8e77e05a6cac3b21f35e1aef7493f6ab4934"
17
+ },
18
+ "library_hashes": {
19
+ "artifact_script_sha256": "459b4e8b7fb6b15b1f2eac3267082c52f1723efd65a59fe429066f565d224b81",
20
+ "resident_source_sha256": "060697d4784be3e3461d38ef6ff24128f47a4322a05e5463f8687796b03bda9f",
21
+ "resident_library_sha256": "d2f7d62d26b4ae3babb7791aee8e0d95ad4794ac9c1d22146c974784381ada48",
22
+ "mage_flow_py_sha256": "59b6e1bee7f95a7fd2fa7bd9e765966832951e2bb184b5ae27283884997b845f",
23
+ "mage_layers_py_sha256": "4b198343b8929f48a0a14d388502c81f54be17b222f6831303d2da6a91f33a62",
24
+ "pipeline_py_sha256": "b9fc57018570372dd3404a733e19b918a359188dd9f5ef7817c6b30969fc13db"
25
+ },
26
+ "environment": {
27
+ "python_version": "3.11.14 (main, Oct 10 2025, 08:54:04) [GCC 13.3.0]",
28
+ "torch_version": "2.13.0+cu130",
29
+ "safetensors_version": "0.8.0"
30
+ },
31
+ "model": {
32
+ "depth": 12,
33
+ "hidden_size": 3072,
34
+ "num_heads": 24,
35
+ "context_in_dim": 2560,
36
+ "in_channels": 128,
37
+ "out_channels": 128,
38
+ "patch_size": 1
39
+ },
40
+ "quantization": {
41
+ "format": "nvfp4_two_level",
42
+ "block_elements": 16,
43
+ "scale_tile_outer": 128,
44
+ "scale_tile_inner": 4,
45
+ "bias_policy": "artifact_bfloat16"
46
+ },
47
+ "targets": [
48
+ {
49
+ "module_key": "transformer_blocks.0.img_mlp.net.0.proj",
50
+ "weight_key": "transformer_blocks.0.img_mlp.net.0.proj.weight",
51
+ "bias_key": "transformer_blocks.0.img_mlp.net.0.proj.bias",
52
+ "weight_shape": [
53
+ 12288,
54
+ 3072
55
+ ],
56
+ "bias_shape": [
57
+ 12288
58
+ ],
59
+ "weight_tensor_scale_key": "targets.transformer_blocks.0.img_mlp.net.0.proj.weight_tensor_scale",
60
+ "artifact_weight_key": "targets.transformer_blocks.0.img_mlp.net.0.proj.packed_weight_e2m1",
61
+ "artifact_scale_key": "targets.transformer_blocks.0.img_mlp.net.0.proj.packed_scales_ue4m3",
62
+ "artifact_bias_key": "targets.transformer_blocks.0.img_mlp.net.0.proj.bias_bf16",
63
+ "weight_tensor_scale": 0.00024559383746236563,
64
+ "weight_global_amax": 0.66015625,
65
+ "packed_weight_bytes": 18874368,
66
+ "packed_scale_bytes": 2359296,
67
+ "scale_layout": {
68
+ "inner_dim": 192,
69
+ "outer_tiles": 96,
70
+ "bytes": 2359296
71
+ },
72
+ "source_weight_sha256": "d05d519a8eb21260cd6c7cdcc886ebd9e32986bd925f3b5a740f75c93ca39162",
73
+ "source_bias_sha256": "e032c18ee63a7667d67e97e8611440b64e31bda7e60df30fbe1784e6875ea55a"
74
+ },
75
+ {
76
+ "module_key": "transformer_blocks.0.img_mlp.net.2",
77
+ "weight_key": "transformer_blocks.0.img_mlp.net.2.weight",
78
+ "bias_key": "transformer_blocks.0.img_mlp.net.2.bias",
79
+ "weight_shape": [
80
+ 3072,
81
+ 12288
82
+ ],
83
+ "bias_shape": [
84
+ 3072
85
+ ],
86
+ "weight_tensor_scale_key": "targets.transformer_blocks.0.img_mlp.net.2.weight_tensor_scale",
87
+ "artifact_weight_key": "targets.transformer_blocks.0.img_mlp.net.2.packed_weight_e2m1",
88
+ "artifact_scale_key": "targets.transformer_blocks.0.img_mlp.net.2.packed_scales_ue4m3",
89
+ "artifact_bias_key": "targets.transformer_blocks.0.img_mlp.net.2.bias_bf16",
90
+ "weight_tensor_scale": 0.0002906435984186828,
91
+ "weight_global_amax": 0.78125,
92
+ "packed_weight_bytes": 18874368,
93
+ "packed_scale_bytes": 2359296,
94
+ "scale_layout": {
95
+ "inner_dim": 768,
96
+ "outer_tiles": 24,
97
+ "bytes": 2359296
98
+ },
99
+ "source_weight_sha256": "8a8cf7a4a5439eb2f3154ad636457ff25f665a95e755e58b2adbab7997f0ceb1",
100
+ "source_bias_sha256": "a1bece05c6b61ebba9379c39bc81d6a19fc0cb2fd0543c9a0faccd926f598a64"
101
+ },
102
+ {
103
+ "module_key": "transformer_blocks.0.txt_mlp.net.0.proj",
104
+ "weight_key": "transformer_blocks.0.txt_mlp.net.0.proj.weight",
105
+ "bias_key": "transformer_blocks.0.txt_mlp.net.0.proj.bias",
106
+ "weight_shape": [
107
+ 12288,
108
+ 3072
109
+ ],
110
+ "bias_shape": [
111
+ 12288
112
+ ],
113
+ "weight_tensor_scale_key": "targets.transformer_blocks.0.txt_mlp.net.0.proj.weight_tensor_scale",
114
+ "artifact_weight_key": "targets.transformer_blocks.0.txt_mlp.net.0.proj.packed_weight_e2m1",
115
+ "artifact_scale_key": "targets.transformer_blocks.0.txt_mlp.net.0.proj.packed_scales_ue4m3",
116
+ "artifact_bias_key": "targets.transformer_blocks.0.txt_mlp.net.0.proj.bias_bf16",
117
+ "weight_tensor_scale": 0.00024704704992473125,
118
+ "weight_global_amax": 0.6640625,
119
+ "packed_weight_bytes": 18874368,
120
+ "packed_scale_bytes": 2359296,
121
+ "scale_layout": {
122
+ "inner_dim": 192,
123
+ "outer_tiles": 96,
124
+ "bytes": 2359296
125
+ },
126
+ "source_weight_sha256": "40a2c2bf74fa661e51bf3294462ec6901e5f57abd1b0c8d8e3f7363c475aa33a",
127
+ "source_bias_sha256": "f181d4fef402d8369e38ce9f64fe91903a5002f503b6dde71120c0ecb873d21b"
128
+ },
129
+ {
130
+ "module_key": "transformer_blocks.0.txt_mlp.net.2",
131
+ "weight_key": "transformer_blocks.0.txt_mlp.net.2.weight",
132
+ "bias_key": "transformer_blocks.0.txt_mlp.net.2.bias",
133
+ "weight_shape": [
134
+ 3072,
135
+ 12288
136
+ ],
137
+ "bias_shape": [
138
+ 3072
139
+ ],
140
+ "weight_tensor_scale_key": "targets.transformer_blocks.0.txt_mlp.net.2.weight_tensor_scale",
141
+ "artifact_weight_key": "targets.transformer_blocks.0.txt_mlp.net.2.packed_weight_e2m1",
142
+ "artifact_scale_key": "targets.transformer_blocks.0.txt_mlp.net.2.packed_scales_ue4m3",
143
+ "artifact_bias_key": "targets.transformer_blocks.0.txt_mlp.net.2.bias_bf16",
144
+ "weight_tensor_scale": 0.00039527530316263437,
145
+ "weight_global_amax": 1.0625,
146
+ "packed_weight_bytes": 18874368,
147
+ "packed_scale_bytes": 2359296,
148
+ "scale_layout": {
149
+ "inner_dim": 768,
150
+ "outer_tiles": 24,
151
+ "bytes": 2359296
152
+ },
153
+ "source_weight_sha256": "37d5796891212472bfa97ce611f85266688107d579ebe1116eec5a958125152f",
154
+ "source_bias_sha256": "862c6c05b8cfc438dd7074299f2303b588af5ae52d94b1dcb3e9e58bea78c770"
155
+ },
156
+ {
157
+ "module_key": "transformer_blocks.1.img_mlp.net.0.proj",
158
+ "weight_key": "transformer_blocks.1.img_mlp.net.0.proj.weight",
159
+ "bias_key": "transformer_blocks.1.img_mlp.net.0.proj.bias",
160
+ "weight_shape": [
161
+ 12288,
162
+ 3072
163
+ ],
164
+ "bias_shape": [
165
+ 12288
166
+ ],
167
+ "weight_tensor_scale_key": "targets.transformer_blocks.1.img_mlp.net.0.proj.weight_tensor_scale",
168
+ "artifact_weight_key": "targets.transformer_blocks.1.img_mlp.net.0.proj.packed_weight_e2m1",
169
+ "artifact_scale_key": "targets.transformer_blocks.1.img_mlp.net.0.proj.packed_scales_ue4m3",
170
+ "artifact_bias_key": "targets.transformer_blocks.1.img_mlp.net.0.proj.bias_bf16",
171
+ "weight_tensor_scale": 0.00022815522970631719,
172
+ "weight_global_amax": 0.61328125,
173
+ "packed_weight_bytes": 18874368,
174
+ "packed_scale_bytes": 2359296,
175
+ "scale_layout": {
176
+ "inner_dim": 192,
177
+ "outer_tiles": 96,
178
+ "bytes": 2359296
179
+ },
180
+ "source_weight_sha256": "1ba75e81f6d7348769dae4a585d3127592fd2f402c101e3ddc88766f92d27617",
181
+ "source_bias_sha256": "76d85e6964ca67fd92a1e6907ab4b5d7b839caa55de787b6e4a2969c234fcec9"
182
+ },
183
+ {
184
+ "module_key": "transformer_blocks.1.img_mlp.net.2",
185
+ "weight_key": "transformer_blocks.1.img_mlp.net.2.weight",
186
+ "bias_key": "transformer_blocks.1.img_mlp.net.2.bias",
187
+ "weight_shape": [
188
+ 3072,
189
+ 12288
190
+ ],
191
+ "bias_shape": [
192
+ 3072
193
+ ],
194
+ "weight_tensor_scale_key": "targets.transformer_blocks.1.img_mlp.net.2.weight_tensor_scale",
195
+ "artifact_weight_key": "targets.transformer_blocks.1.img_mlp.net.2.packed_weight_e2m1",
196
+ "artifact_scale_key": "targets.transformer_blocks.1.img_mlp.net.2.packed_scales_ue4m3",
197
+ "artifact_bias_key": "targets.transformer_blocks.1.img_mlp.net.2.bias_bf16",
198
+ "weight_tensor_scale": 0.0004417782765813172,
199
+ "weight_global_amax": 1.1875,
200
+ "packed_weight_bytes": 18874368,
201
+ "packed_scale_bytes": 2359296,
202
+ "scale_layout": {
203
+ "inner_dim": 768,
204
+ "outer_tiles": 24,
205
+ "bytes": 2359296
206
+ },
207
+ "source_weight_sha256": "3f890473a9a99aa845e698713a852464055645b061b4b6ab61de59273b87be1b",
208
+ "source_bias_sha256": "06b80199d80cc3916a202cfe220f59e8572dd406b0d2be8b17d59c1a3de98619"
209
+ },
210
+ {
211
+ "module_key": "transformer_blocks.1.txt_mlp.net.0.proj",
212
+ "weight_key": "transformer_blocks.1.txt_mlp.net.0.proj.weight",
213
+ "bias_key": "transformer_blocks.1.txt_mlp.net.0.proj.bias",
214
+ "weight_shape": [
215
+ 12288,
216
+ 3072
217
+ ],
218
+ "bias_shape": [
219
+ 12288
220
+ ],
221
+ "weight_tensor_scale_key": "targets.transformer_blocks.1.txt_mlp.net.0.proj.weight_tensor_scale",
222
+ "artifact_weight_key": "targets.transformer_blocks.1.txt_mlp.net.0.proj.packed_weight_e2m1",
223
+ "artifact_scale_key": "targets.transformer_blocks.1.txt_mlp.net.0.proj.packed_scales_ue4m3",
224
+ "artifact_bias_key": "targets.transformer_blocks.1.txt_mlp.net.0.proj.bias_bf16",
225
+ "weight_tensor_scale": 0.00019618442456703633,
226
+ "weight_global_amax": 0.52734375,
227
+ "packed_weight_bytes": 18874368,
228
+ "packed_scale_bytes": 2359296,
229
+ "scale_layout": {
230
+ "inner_dim": 192,
231
+ "outer_tiles": 96,
232
+ "bytes": 2359296
233
+ },
234
+ "source_weight_sha256": "4a232bd2fdbf09232bd512cfb76c59502068529105e596c821bafff89c19813b",
235
+ "source_bias_sha256": "e94fb97549fc326506f37a6824ead6a538828909466a6bf76933dfe5ce73680d"
236
+ },
237
+ {
238
+ "module_key": "transformer_blocks.1.txt_mlp.net.2",
239
+ "weight_key": "transformer_blocks.1.txt_mlp.net.2.weight",
240
+ "bias_key": "transformer_blocks.1.txt_mlp.net.2.bias",
241
+ "weight_shape": [
242
+ 3072,
243
+ 12288
244
+ ],
245
+ "bias_shape": [
246
+ 3072
247
+ ],
248
+ "weight_tensor_scale_key": "targets.transformer_blocks.1.txt_mlp.net.2.weight_tensor_scale",
249
+ "artifact_weight_key": "targets.transformer_blocks.1.txt_mlp.net.2.packed_weight_e2m1",
250
+ "artifact_scale_key": "targets.transformer_blocks.1.txt_mlp.net.2.packed_scales_ue4m3",
251
+ "artifact_bias_key": "targets.transformer_blocks.1.txt_mlp.net.2.bias_bf16",
252
+ "weight_tensor_scale": 0.00039818172808736563,
253
+ "weight_global_amax": 1.0703125,
254
+ "packed_weight_bytes": 18874368,
255
+ "packed_scale_bytes": 2359296,
256
+ "scale_layout": {
257
+ "inner_dim": 768,
258
+ "outer_tiles": 24,
259
+ "bytes": 2359296
260
+ },
261
+ "source_weight_sha256": "042d74971fba414ffce050de7a65e1bebc33a2e49339b26f42518a9a9621701f",
262
+ "source_bias_sha256": "faa59706a19669dc45d820edab49feca78c6b1d9d90aca4adbd87040c36f2fea"
263
+ },
264
+ {
265
+ "module_key": "transformer_blocks.2.img_mlp.net.0.proj",
266
+ "weight_key": "transformer_blocks.2.img_mlp.net.0.proj.weight",
267
+ "bias_key": "transformer_blocks.2.img_mlp.net.0.proj.bias",
268
+ "weight_shape": [
269
+ 12288,
270
+ 3072
271
+ ],
272
+ "bias_shape": [
273
+ 12288
274
+ ],
275
+ "weight_tensor_scale_key": "targets.transformer_blocks.2.img_mlp.net.0.proj.weight_tensor_scale",
276
+ "artifact_weight_key": "targets.transformer_blocks.2.img_mlp.net.0.proj.packed_weight_e2m1",
277
+ "artifact_scale_key": "targets.transformer_blocks.2.img_mlp.net.0.proj.packed_scales_ue4m3",
278
+ "artifact_bias_key": "targets.transformer_blocks.2.img_mlp.net.0.proj.bias_bf16",
279
+ "weight_tensor_scale": 0.00030226935632526875,
280
+ "weight_global_amax": 0.8125,
281
+ "packed_weight_bytes": 18874368,
282
+ "packed_scale_bytes": 2359296,
283
+ "scale_layout": {
284
+ "inner_dim": 192,
285
+ "outer_tiles": 96,
286
+ "bytes": 2359296
287
+ },
288
+ "source_weight_sha256": "fceb9a0d7b4bfbddb10490b9d9e7f41c369e691bbafdccb003f8dda509fd3b1c",
289
+ "source_bias_sha256": "84af366af51b7cd24dac8a9fda557a2c8a66ca80e9f8621ec4729d31d64bd241"
290
+ },
291
+ {
292
+ "module_key": "transformer_blocks.2.img_mlp.net.2",
293
+ "weight_key": "transformer_blocks.2.img_mlp.net.2.weight",
294
+ "bias_key": "transformer_blocks.2.img_mlp.net.2.bias",
295
+ "weight_shape": [
296
+ 3072,
297
+ 12288
298
+ ],
299
+ "bias_shape": [
300
+ 3072
301
+ ],
302
+ "weight_tensor_scale_key": "targets.transformer_blocks.2.img_mlp.net.2.weight_tensor_scale",
303
+ "artifact_weight_key": "targets.transformer_blocks.2.img_mlp.net.2.packed_weight_e2m1",
304
+ "artifact_scale_key": "targets.transformer_blocks.2.img_mlp.net.2.packed_scales_ue4m3",
305
+ "artifact_bias_key": "targets.transformer_blocks.2.img_mlp.net.2.bias_bf16",
306
+ "weight_tensor_scale": 0.0005057198577560484,
307
+ "weight_global_amax": 1.359375,
308
+ "packed_weight_bytes": 18874368,
309
+ "packed_scale_bytes": 2359296,
310
+ "scale_layout": {
311
+ "inner_dim": 768,
312
+ "outer_tiles": 24,
313
+ "bytes": 2359296
314
+ },
315
+ "source_weight_sha256": "97220e0a04629f28c4ca39abfe46c07cf6183f660f391cd635be2871d325a09d",
316
+ "source_bias_sha256": "6bcc96109a2496ee73d6a04bb8016641e90d568e6e46ea1a2e8b9e84925bfab9"
317
+ },
318
+ {
319
+ "module_key": "transformer_blocks.2.txt_mlp.net.0.proj",
320
+ "weight_key": "transformer_blocks.2.txt_mlp.net.0.proj.weight",
321
+ "bias_key": "transformer_blocks.2.txt_mlp.net.0.proj.bias",
322
+ "weight_shape": [
323
+ 12288,
324
+ 3072
325
+ ],
326
+ "bias_shape": [
327
+ 12288
328
+ ],
329
+ "weight_tensor_scale_key": "targets.transformer_blocks.2.txt_mlp.net.0.proj.weight_tensor_scale",
330
+ "artifact_weight_key": "targets.transformer_blocks.2.txt_mlp.net.0.proj.packed_weight_e2m1",
331
+ "artifact_scale_key": "targets.transformer_blocks.2.txt_mlp.net.0.proj.packed_scales_ue4m3",
332
+ "artifact_bias_key": "targets.transformer_blocks.2.txt_mlp.net.0.proj.bias_bf16",
333
+ "weight_tensor_scale": 0.00022815522970631719,
334
+ "weight_global_amax": 0.61328125,
335
+ "packed_weight_bytes": 18874368,
336
+ "packed_scale_bytes": 2359296,
337
+ "scale_layout": {
338
+ "inner_dim": 192,
339
+ "outer_tiles": 96,
340
+ "bytes": 2359296
341
+ },
342
+ "source_weight_sha256": "764c79424da4eea44e62f03c1fda648bbd937ebf0b7c0bb43bede1e8b5130f29",
343
+ "source_bias_sha256": "eca2f24456368d090da90c98a102af788833b3f3f9130f29b5b7a734e36f307e"
344
+ },
345
+ {
346
+ "module_key": "transformer_blocks.2.txt_mlp.net.2",
347
+ "weight_key": "transformer_blocks.2.txt_mlp.net.2.weight",
348
+ "bias_key": "transformer_blocks.2.txt_mlp.net.2.bias",
349
+ "weight_shape": [
350
+ 3072,
351
+ 12288
352
+ ],
353
+ "bias_shape": [
354
+ 3072
355
+ ],
356
+ "weight_tensor_scale_key": "targets.transformer_blocks.2.txt_mlp.net.2.weight_tensor_scale",
357
+ "artifact_weight_key": "targets.transformer_blocks.2.txt_mlp.net.2.packed_weight_e2m1",
358
+ "artifact_scale_key": "targets.transformer_blocks.2.txt_mlp.net.2.packed_scales_ue4m3",
359
+ "artifact_bias_key": "targets.transformer_blocks.2.txt_mlp.net.2.bias_bf16",
360
+ "weight_tensor_scale": 0.0003589448460843414,
361
+ "weight_global_amax": 0.96484375,
362
+ "packed_weight_bytes": 18874368,
363
+ "packed_scale_bytes": 2359296,
364
+ "scale_layout": {
365
+ "inner_dim": 768,
366
+ "outer_tiles": 24,
367
+ "bytes": 2359296
368
+ },
369
+ "source_weight_sha256": "17e543841d93e1936514c8313a85e4b9444321db06111c33460173e47b0d1c93",
370
+ "source_bias_sha256": "eed98a4db5990a369a55eb0865482f174e1418e6f4231bcf65637939a1df8354"
371
+ },
372
+ {
373
+ "module_key": "transformer_blocks.3.img_mlp.net.0.proj",
374
+ "weight_key": "transformer_blocks.3.img_mlp.net.0.proj.weight",
375
+ "bias_key": "transformer_blocks.3.img_mlp.net.0.proj.bias",
376
+ "weight_shape": [
377
+ 12288,
378
+ 3072
379
+ ],
380
+ "bias_shape": [
381
+ 12288
382
+ ],
383
+ "weight_tensor_scale_key": "targets.transformer_blocks.3.img_mlp.net.0.proj.weight_tensor_scale",
384
+ "artifact_weight_key": "targets.transformer_blocks.3.img_mlp.net.0.proj.packed_weight_e2m1",
385
+ "artifact_scale_key": "targets.transformer_blocks.3.img_mlp.net.0.proj.packed_scales_ue4m3",
386
+ "artifact_bias_key": "targets.transformer_blocks.3.img_mlp.net.0.proj.bias_bf16",
387
+ "weight_tensor_scale": 0.00024850029149092734,
388
+ "weight_global_amax": 0.66796875,
389
+ "packed_weight_bytes": 18874368,
390
+ "packed_scale_bytes": 2359296,
391
+ "scale_layout": {
392
+ "inner_dim": 192,
393
+ "outer_tiles": 96,
394
+ "bytes": 2359296
395
+ },
396
+ "source_weight_sha256": "83c39bc7dd86d7e6a5f63eadc063b91d7bfd93a75a80c9536ddf28364d35ca6c",
397
+ "source_bias_sha256": "4f40baf432bc9106a645e1182bce1c18535e1370f0851cb0f6e21d3a1646a08e"
398
+ },
399
+ {
400
+ "module_key": "transformer_blocks.3.img_mlp.net.2",
401
+ "weight_key": "transformer_blocks.3.img_mlp.net.2.weight",
402
+ "bias_key": "transformer_blocks.3.img_mlp.net.2.bias",
403
+ "weight_shape": [
404
+ 3072,
405
+ 12288
406
+ ],
407
+ "bias_shape": [
408
+ 3072
409
+ ],
410
+ "weight_tensor_scale_key": "targets.transformer_blocks.3.img_mlp.net.2.weight_tensor_scale",
411
+ "artifact_weight_key": "targets.transformer_blocks.3.img_mlp.net.2.packed_weight_e2m1",
412
+ "artifact_scale_key": "targets.transformer_blocks.3.img_mlp.net.2.packed_scales_ue4m3",
413
+ "artifact_bias_key": "targets.transformer_blocks.3.img_mlp.net.2.bias_bf16",
414
+ "weight_tensor_scale": 0.0005871000466868281,
415
+ "weight_global_amax": 1.578125,
416
+ "packed_weight_bytes": 18874368,
417
+ "packed_scale_bytes": 2359296,
418
+ "scale_layout": {
419
+ "inner_dim": 768,
420
+ "outer_tiles": 24,
421
+ "bytes": 2359296
422
+ },
423
+ "source_weight_sha256": "e6945585cd816ccb2cd6e4a005326a105758f72270840842dea9c8617ba095ba",
424
+ "source_bias_sha256": "230dcffbe97056ccb1975e1dbdc4274a89a217c64b240dcb70d1a639c8d50e10"
425
+ },
426
+ {
427
+ "module_key": "transformer_blocks.3.txt_mlp.net.0.proj",
428
+ "weight_key": "transformer_blocks.3.txt_mlp.net.0.proj.weight",
429
+ "bias_key": "transformer_blocks.3.txt_mlp.net.0.proj.bias",
430
+ "weight_shape": [
431
+ 12288,
432
+ 3072
433
+ ],
434
+ "bias_shape": [
435
+ 12288
436
+ ],
437
+ "weight_tensor_scale_key": "targets.transformer_blocks.3.txt_mlp.net.0.proj.weight_tensor_scale",
438
+ "artifact_weight_key": "targets.transformer_blocks.3.txt_mlp.net.0.proj.packed_weight_e2m1",
439
+ "artifact_scale_key": "targets.transformer_blocks.3.txt_mlp.net.0.proj.packed_scales_ue4m3",
440
+ "artifact_bias_key": "targets.transformer_blocks.3.txt_mlp.net.0.proj.bias_bf16",
441
+ "weight_tensor_scale": 0.00022960844216868281,
442
+ "weight_global_amax": 0.6171875,
443
+ "packed_weight_bytes": 18874368,
444
+ "packed_scale_bytes": 2359296,
445
+ "scale_layout": {
446
+ "inner_dim": 192,
447
+ "outer_tiles": 96,
448
+ "bytes": 2359296
449
+ },
450
+ "source_weight_sha256": "786d08fafeb3519a0ab99e194105dfae1135ebbed18ae364fd62ba323c6525a2",
451
+ "source_bias_sha256": "f493d8f0a032e8a1158ffc66ca65c00a78bdf8118d3bc307c4bab75605979d67"
452
+ },
453
+ {
454
+ "module_key": "transformer_blocks.3.txt_mlp.net.2",
455
+ "weight_key": "transformer_blocks.3.txt_mlp.net.2.weight",
456
+ "bias_key": "transformer_blocks.3.txt_mlp.net.2.bias",
457
+ "weight_shape": [
458
+ 3072,
459
+ 12288
460
+ ],
461
+ "bias_shape": [
462
+ 3072
463
+ ],
464
+ "weight_tensor_scale_key": "targets.transformer_blocks.3.txt_mlp.net.2.weight_tensor_scale",
465
+ "artifact_weight_key": "targets.transformer_blocks.3.txt_mlp.net.2.packed_weight_e2m1",
466
+ "artifact_scale_key": "targets.transformer_blocks.3.txt_mlp.net.2.packed_scales_ue4m3",
467
+ "artifact_bias_key": "targets.transformer_blocks.3.txt_mlp.net.2.bias_bf16",
468
+ "weight_tensor_scale": 0.0004039946070406586,
469
+ "weight_global_amax": 1.0859375,
470
+ "packed_weight_bytes": 18874368,
471
+ "packed_scale_bytes": 2359296,
472
+ "scale_layout": {
473
+ "inner_dim": 768,
474
+ "outer_tiles": 24,
475
+ "bytes": 2359296
476
+ },
477
+ "source_weight_sha256": "ba11326b827a9600c9d0ae9227f2d3c0ab9971a09afd7df18689f4f1436a4e76",
478
+ "source_bias_sha256": "c8a38cf2ae33d7b353cd71510a74ede4727b418285ea6dd5934274dcf5bbdf4f"
479
+ },
480
+ {
481
+ "module_key": "transformer_blocks.4.img_mlp.net.0.proj",
482
+ "weight_key": "transformer_blocks.4.img_mlp.net.0.proj.weight",
483
+ "bias_key": "transformer_blocks.4.img_mlp.net.0.proj.bias",
484
+ "weight_shape": [
485
+ 12288,
486
+ 3072
487
+ ],
488
+ "bias_shape": [
489
+ 12288
490
+ ],
491
+ "weight_tensor_scale_key": "targets.transformer_blocks.4.img_mlp.net.0.proj.weight_tensor_scale",
492
+ "artifact_weight_key": "targets.transformer_blocks.4.img_mlp.net.0.proj.packed_weight_e2m1",
493
+ "artifact_scale_key": "targets.transformer_blocks.4.img_mlp.net.0.proj.packed_scales_ue4m3",
494
+ "artifact_bias_key": "targets.transformer_blocks.4.img_mlp.net.0.proj.bias_bf16",
495
+ "weight_tensor_scale": 0.0003516787546686828,
496
+ "weight_global_amax": 0.9453125,
497
+ "packed_weight_bytes": 18874368,
498
+ "packed_scale_bytes": 2359296,
499
+ "scale_layout": {
500
+ "inner_dim": 192,
501
+ "outer_tiles": 96,
502
+ "bytes": 2359296
503
+ },
504
+ "source_weight_sha256": "2da64d233d618c639d7ac3a988c045771a20bb77634d272949dc1f9d3a26e6fa",
505
+ "source_bias_sha256": "6079037543d2b43a319f02021a6f71d2874efaaafd6a5a58f2813a9e160a9ca6"
506
+ },
507
+ {
508
+ "module_key": "transformer_blocks.4.img_mlp.net.2",
509
+ "weight_key": "transformer_blocks.4.img_mlp.net.2.weight",
510
+ "bias_key": "transformer_blocks.4.img_mlp.net.2.bias",
511
+ "weight_shape": [
512
+ 3072,
513
+ 12288
514
+ ],
515
+ "bias_shape": [
516
+ 3072
517
+ ],
518
+ "weight_tensor_scale_key": "targets.transformer_blocks.4.img_mlp.net.2.weight_tensor_scale",
519
+ "artifact_weight_key": "targets.transformer_blocks.4.img_mlp.net.2.packed_weight_e2m1",
520
+ "artifact_scale_key": "targets.transformer_blocks.4.img_mlp.net.2.packed_scales_ue4m3",
521
+ "artifact_bias_key": "targets.transformer_blocks.4.img_mlp.net.2.bias_bf16",
522
+ "weight_tensor_scale": 0.0009533109841868281,
523
+ "weight_global_amax": 2.5625,
524
+ "packed_weight_bytes": 18874368,
525
+ "packed_scale_bytes": 2359296,
526
+ "scale_layout": {
527
+ "inner_dim": 768,
528
+ "outer_tiles": 24,
529
+ "bytes": 2359296
530
+ },
531
+ "source_weight_sha256": "4db777bdb0beb299cb30ccc2043870bc4f311d5e6e30b6ad28e1cc7c29af96ed",
532
+ "source_bias_sha256": "d4e4ac8e94b3fd245c412ee58d3fc91e815fd03e49684d8596ac58566145f1fe"
533
+ },
534
+ {
535
+ "module_key": "transformer_blocks.4.txt_mlp.net.0.proj",
536
+ "weight_key": "transformer_blocks.4.txt_mlp.net.0.proj.weight",
537
+ "bias_key": "transformer_blocks.4.txt_mlp.net.0.proj.bias",
538
+ "weight_shape": [
539
+ 12288,
540
+ 3072
541
+ ],
542
+ "bias_shape": [
543
+ 12288
544
+ ],
545
+ "weight_tensor_scale_key": "targets.transformer_blocks.4.txt_mlp.net.0.proj.weight_tensor_scale",
546
+ "artifact_weight_key": "targets.transformer_blocks.4.txt_mlp.net.0.proj.packed_weight_e2m1",
547
+ "artifact_scale_key": "targets.transformer_blocks.4.txt_mlp.net.0.proj.packed_scales_ue4m3",
548
+ "artifact_bias_key": "targets.transformer_blocks.4.txt_mlp.net.0.proj.bias_bf16",
549
+ "weight_tensor_scale": 0.00018528530199546367,
550
+ "weight_global_amax": 0.498046875,
551
+ "packed_weight_bytes": 18874368,
552
+ "packed_scale_bytes": 2359296,
553
+ "scale_layout": {
554
+ "inner_dim": 192,
555
+ "outer_tiles": 96,
556
+ "bytes": 2359296
557
+ },
558
+ "source_weight_sha256": "0c956820e6938c52af5a237da6dcd4c41b509865b7631407a896cc6da6bdb985",
559
+ "source_bias_sha256": "edd0dfc9060a78b09c53bd0f38a9d0a5263faadbe3c522a7022115a2e09c211e"
560
+ },
561
+ {
562
+ "module_key": "transformer_blocks.4.txt_mlp.net.2",
563
+ "weight_key": "transformer_blocks.4.txt_mlp.net.2.weight",
564
+ "bias_key": "transformer_blocks.4.txt_mlp.net.2.bias",
565
+ "weight_shape": [
566
+ 3072,
567
+ 12288
568
+ ],
569
+ "bias_shape": [
570
+ 3072
571
+ ],
572
+ "weight_tensor_scale_key": "targets.transformer_blocks.4.txt_mlp.net.2.weight_tensor_scale",
573
+ "artifact_weight_key": "targets.transformer_blocks.4.txt_mlp.net.2.packed_weight_e2m1",
574
+ "artifact_scale_key": "targets.transformer_blocks.4.txt_mlp.net.2.packed_scales_ue4m3",
575
+ "artifact_bias_key": "targets.transformer_blocks.4.txt_mlp.net.2.bias_bf16",
576
+ "weight_tensor_scale": 0.0003284272679593414,
577
+ "weight_global_amax": 0.8828125,
578
+ "packed_weight_bytes": 18874368,
579
+ "packed_scale_bytes": 2359296,
580
+ "scale_layout": {
581
+ "inner_dim": 768,
582
+ "outer_tiles": 24,
583
+ "bytes": 2359296
584
+ },
585
+ "source_weight_sha256": "005cd523afdeb3e9d32a2b29dd0499fa994fd8a79af245604661ee8bc1af59c8",
586
+ "source_bias_sha256": "f333cc3ca7d6299b5cf44859662914d0fae7cbeba0433e0ae487d64cdc755685"
587
+ },
588
+ {
589
+ "module_key": "transformer_blocks.5.img_mlp.net.0.proj",
590
+ "weight_key": "transformer_blocks.5.img_mlp.net.0.proj.weight",
591
+ "bias_key": "transformer_blocks.5.img_mlp.net.0.proj.bias",
592
+ "weight_shape": [
593
+ 12288,
594
+ 3072
595
+ ],
596
+ "bias_shape": [
597
+ 12288
598
+ ],
599
+ "weight_tensor_scale_key": "targets.transformer_blocks.5.img_mlp.net.0.proj.weight_tensor_scale",
600
+ "artifact_weight_key": "targets.transformer_blocks.5.img_mlp.net.0.proj.packed_weight_e2m1",
601
+ "artifact_scale_key": "targets.transformer_blocks.5.img_mlp.net.0.proj.packed_scales_ue4m3",
602
+ "artifact_bias_key": "targets.transformer_blocks.5.img_mlp.net.0.proj.bias_bf16",
603
+ "weight_tensor_scale": 0.00040108818211592734,
604
+ "weight_global_amax": 1.078125,
605
+ "packed_weight_bytes": 18874368,
606
+ "packed_scale_bytes": 2359296,
607
+ "scale_layout": {
608
+ "inner_dim": 192,
609
+ "outer_tiles": 96,
610
+ "bytes": 2359296
611
+ },
612
+ "source_weight_sha256": "8419f255277f3a77facc9090e6882ab54d41e6f9f0bebb7930416e07c7276f55",
613
+ "source_bias_sha256": "c738466822f96ba14226ee1f254ec83099479436e1ff34d7ae220fdc7bad423d"
614
+ },
615
+ {
616
+ "module_key": "transformer_blocks.5.img_mlp.net.2",
617
+ "weight_key": "transformer_blocks.5.img_mlp.net.2.weight",
618
+ "bias_key": "transformer_blocks.5.img_mlp.net.2.bias",
619
+ "weight_shape": [
620
+ 3072,
621
+ 12288
622
+ ],
623
+ "bias_shape": [
624
+ 3072
625
+ ],
626
+ "weight_tensor_scale_key": "targets.transformer_blocks.5.img_mlp.net.2.weight_tensor_scale",
627
+ "artifact_weight_key": "targets.transformer_blocks.5.img_mlp.net.2.packed_weight_e2m1",
628
+ "artifact_scale_key": "targets.transformer_blocks.5.img_mlp.net.2.packed_scales_ue4m3",
629
+ "artifact_bias_key": "targets.transformer_blocks.5.img_mlp.net.2.bias_bf16",
630
+ "weight_tensor_scale": 0.0006684802938252687,
631
+ "weight_global_amax": 1.796875,
632
+ "packed_weight_bytes": 18874368,
633
+ "packed_scale_bytes": 2359296,
634
+ "scale_layout": {
635
+ "inner_dim": 768,
636
+ "outer_tiles": 24,
637
+ "bytes": 2359296
638
+ },
639
+ "source_weight_sha256": "97fbe5a8ee7151217b82e12295e02681af0ca707d3595c58b40aed31c3ae23b7",
640
+ "source_bias_sha256": "af3f83a94e0368f51c65b26a880b1bf3b64543910e9e270a04faa12aa58680da"
641
+ },
642
+ {
643
+ "module_key": "transformer_blocks.5.txt_mlp.net.0.proj",
644
+ "weight_key": "transformer_blocks.5.txt_mlp.net.0.proj.weight",
645
+ "bias_key": "transformer_blocks.5.txt_mlp.net.0.proj.bias",
646
+ "weight_shape": [
647
+ 12288,
648
+ 3072
649
+ ],
650
+ "bias_shape": [
651
+ 12288
652
+ ],
653
+ "weight_tensor_scale_key": "targets.transformer_blocks.5.txt_mlp.net.0.proj.weight_tensor_scale",
654
+ "artifact_weight_key": "targets.transformer_blocks.5.txt_mlp.net.0.proj.packed_weight_e2m1",
655
+ "artifact_scale_key": "targets.transformer_blocks.5.txt_mlp.net.0.proj.packed_scales_ue4m3",
656
+ "artifact_bias_key": "targets.transformer_blocks.5.txt_mlp.net.0.proj.bias_bf16",
657
+ "weight_tensor_scale": 0.0001874651206890121,
658
+ "weight_global_amax": 0.50390625,
659
+ "packed_weight_bytes": 18874368,
660
+ "packed_scale_bytes": 2359296,
661
+ "scale_layout": {
662
+ "inner_dim": 192,
663
+ "outer_tiles": 96,
664
+ "bytes": 2359296
665
+ },
666
+ "source_weight_sha256": "7e3b210a8d107962865efa3f3974a9ea29a60732f8002539e71837ebf9fb3787",
667
+ "source_bias_sha256": "a154c2419b8da86533eeb0e5a69c37cbfe5eff859f8196e192d5f942743c690d"
668
+ },
669
+ {
670
+ "module_key": "transformer_blocks.5.txt_mlp.net.2",
671
+ "weight_key": "transformer_blocks.5.txt_mlp.net.2.weight",
672
+ "bias_key": "transformer_blocks.5.txt_mlp.net.2.bias",
673
+ "weight_shape": [
674
+ 3072,
675
+ 12288
676
+ ],
677
+ "bias_shape": [
678
+ 3072
679
+ ],
680
+ "weight_tensor_scale_key": "targets.transformer_blocks.5.txt_mlp.net.2.weight_tensor_scale",
681
+ "artifact_weight_key": "targets.transformer_blocks.5.txt_mlp.net.2.packed_weight_e2m1",
682
+ "artifact_scale_key": "targets.transformer_blocks.5.txt_mlp.net.2.packed_scales_ue4m3",
683
+ "artifact_bias_key": "targets.transformer_blocks.5.txt_mlp.net.2.bias_bf16",
684
+ "weight_tensor_scale": 0.0003807431203313172,
685
+ "weight_global_amax": 1.0234375,
686
+ "packed_weight_bytes": 18874368,
687
+ "packed_scale_bytes": 2359296,
688
+ "scale_layout": {
689
+ "inner_dim": 768,
690
+ "outer_tiles": 24,
691
+ "bytes": 2359296
692
+ },
693
+ "source_weight_sha256": "906a7d3e7d32c10037e731b32904f683ff3529e0605dc619132c6095e87aeec4",
694
+ "source_bias_sha256": "e47066d5093c8d5df144ad237d0c0cbb8186b7c176764338b777873e623e226e"
695
+ },
696
+ {
697
+ "module_key": "transformer_blocks.6.img_mlp.net.0.proj",
698
+ "weight_key": "transformer_blocks.6.img_mlp.net.0.proj.weight",
699
+ "bias_key": "transformer_blocks.6.img_mlp.net.0.proj.bias",
700
+ "weight_shape": [
701
+ 12288,
702
+ 3072
703
+ ],
704
+ "bias_shape": [
705
+ 12288
706
+ ],
707
+ "weight_tensor_scale_key": "targets.transformer_blocks.6.img_mlp.net.0.proj.weight_tensor_scale",
708
+ "artifact_weight_key": "targets.transformer_blocks.6.img_mlp.net.0.proj.packed_weight_e2m1",
709
+ "artifact_scale_key": "targets.transformer_blocks.6.img_mlp.net.0.proj.packed_scales_ue4m3",
710
+ "artifact_bias_key": "targets.transformer_blocks.6.img_mlp.net.0.proj.bias_bf16",
711
+ "weight_tensor_scale": 0.00043015251867473125,
712
+ "weight_global_amax": 1.15625,
713
+ "packed_weight_bytes": 18874368,
714
+ "packed_scale_bytes": 2359296,
715
+ "scale_layout": {
716
+ "inner_dim": 192,
717
+ "outer_tiles": 96,
718
+ "bytes": 2359296
719
+ },
720
+ "source_weight_sha256": "6e2eaa52c7e036559066e6d0bd3e259b8b4a9e6010cfb5201d255aa673a95e9a",
721
+ "source_bias_sha256": "559dcbcbecdda86591182bdad53d03f173400d6abbdada125766b5c5e180887f"
722
+ },
723
+ {
724
+ "module_key": "transformer_blocks.6.img_mlp.net.2",
725
+ "weight_key": "transformer_blocks.6.img_mlp.net.2.weight",
726
+ "bias_key": "transformer_blocks.6.img_mlp.net.2.bias",
727
+ "weight_shape": [
728
+ 3072,
729
+ 12288
730
+ ],
731
+ "bias_shape": [
732
+ 3072
733
+ ],
734
+ "weight_tensor_scale_key": "targets.transformer_blocks.6.img_mlp.net.2.weight_tensor_scale",
735
+ "artifact_weight_key": "targets.transformer_blocks.6.img_mlp.net.2.packed_weight_e2m1",
736
+ "artifact_scale_key": "targets.transformer_blocks.6.img_mlp.net.2.packed_scales_ue4m3",
737
+ "artifact_bias_key": "targets.transformer_blocks.6.img_mlp.net.2.bias_bf16",
738
+ "weight_tensor_scale": 0.0007672990905120969,
739
+ "weight_global_amax": 2.0625,
740
+ "packed_weight_bytes": 18874368,
741
+ "packed_scale_bytes": 2359296,
742
+ "scale_layout": {
743
+ "inner_dim": 768,
744
+ "outer_tiles": 24,
745
+ "bytes": 2359296
746
+ },
747
+ "source_weight_sha256": "5d161c4e9f626a44cd150ccd49e4b6a3b91dfe9c78628edbe2d05bcfd205ee38",
748
+ "source_bias_sha256": "88e2f67cc46733f0a734a37bf6d6f6a52b12bbbfce8346a9ca81b4942a3c5c89"
749
+ },
750
+ {
751
+ "module_key": "transformer_blocks.6.txt_mlp.net.0.proj",
752
+ "weight_key": "transformer_blocks.6.txt_mlp.net.0.proj.weight",
753
+ "bias_key": "transformer_blocks.6.txt_mlp.net.0.proj.bias",
754
+ "weight_shape": [
755
+ 12288,
756
+ 3072
757
+ ],
758
+ "bias_shape": [
759
+ 12288
760
+ ],
761
+ "weight_tensor_scale_key": "targets.transformer_blocks.6.txt_mlp.net.0.proj.weight_tensor_scale",
762
+ "artifact_weight_key": "targets.transformer_blocks.6.txt_mlp.net.0.proj.packed_weight_e2m1",
763
+ "artifact_scale_key": "targets.transformer_blocks.6.txt_mlp.net.0.proj.packed_scales_ue4m3",
764
+ "artifact_bias_key": "targets.transformer_blocks.6.txt_mlp.net.0.proj.bias_bf16",
765
+ "weight_tensor_scale": 0.0002601260202936828,
766
+ "weight_global_amax": 0.69921875,
767
+ "packed_weight_bytes": 18874368,
768
+ "packed_scale_bytes": 2359296,
769
+ "scale_layout": {
770
+ "inner_dim": 192,
771
+ "outer_tiles": 96,
772
+ "bytes": 2359296
773
+ },
774
+ "source_weight_sha256": "fdd4c5e88701022033d97501bd2781fdc526eaa9427775a790756a9bb7c1fda2",
775
+ "source_bias_sha256": "37f6d759de4dfb875a8c7076fae88f75c2c5fdade293907895fead213e7a0ae7"
776
+ },
777
+ {
778
+ "module_key": "transformer_blocks.6.txt_mlp.net.2",
779
+ "weight_key": "transformer_blocks.6.txt_mlp.net.2.weight",
780
+ "bias_key": "transformer_blocks.6.txt_mlp.net.2.bias",
781
+ "weight_shape": [
782
+ 3072,
783
+ 12288
784
+ ],
785
+ "bias_shape": [
786
+ 3072
787
+ ],
788
+ "weight_tensor_scale_key": "targets.transformer_blocks.6.txt_mlp.net.2.weight_tensor_scale",
789
+ "artifact_weight_key": "targets.transformer_blocks.6.txt_mlp.net.2.packed_weight_e2m1",
790
+ "artifact_scale_key": "targets.transformer_blocks.6.txt_mlp.net.2.packed_scales_ue4m3",
791
+ "artifact_bias_key": "targets.transformer_blocks.6.txt_mlp.net.2.bias_bf16",
792
+ "weight_tensor_scale": 0.00047665549209341407,
793
+ "weight_global_amax": 1.28125,
794
+ "packed_weight_bytes": 18874368,
795
+ "packed_scale_bytes": 2359296,
796
+ "scale_layout": {
797
+ "inner_dim": 768,
798
+ "outer_tiles": 24,
799
+ "bytes": 2359296
800
+ },
801
+ "source_weight_sha256": "b28669406ee0f0e295c1a0b19241c1de874952807a0754d31078b56b1e213f2d",
802
+ "source_bias_sha256": "a5479cb1785b82d01897ca73a0b560b1382dcb7b60c8dce315e247d795e04d0f"
803
+ },
804
+ {
805
+ "module_key": "transformer_blocks.7.img_mlp.net.0.proj",
806
+ "weight_key": "transformer_blocks.7.img_mlp.net.0.proj.weight",
807
+ "bias_key": "transformer_blocks.7.img_mlp.net.0.proj.bias",
808
+ "weight_shape": [
809
+ 12288,
810
+ 3072
811
+ ],
812
+ "bias_shape": [
813
+ 12288
814
+ ],
815
+ "weight_tensor_scale_key": "targets.transformer_blocks.7.img_mlp.net.0.proj.weight_tensor_scale",
816
+ "artifact_weight_key": "targets.transformer_blocks.7.img_mlp.net.0.proj.packed_weight_e2m1",
817
+ "artifact_scale_key": "targets.transformer_blocks.7.img_mlp.net.0.proj.packed_scales_ue4m3",
818
+ "artifact_bias_key": "targets.transformer_blocks.7.img_mlp.net.0.proj.bias_bf16",
819
+ "weight_tensor_scale": 0.0003138950851280242,
820
+ "weight_global_amax": 0.84375,
821
+ "packed_weight_bytes": 18874368,
822
+ "packed_scale_bytes": 2359296,
823
+ "scale_layout": {
824
+ "inner_dim": 192,
825
+ "outer_tiles": 96,
826
+ "bytes": 2359296
827
+ },
828
+ "source_weight_sha256": "9bcc9591965c0b74076ad9da74307faa9c7fa0ab63a81f8f25960c2286ed39bf",
829
+ "source_bias_sha256": "b043d399ff8f040405104ffb775c9f902f95dca6de06a6b1ae2f6c90e5f28d7c"
830
+ },
831
+ {
832
+ "module_key": "transformer_blocks.7.img_mlp.net.2",
833
+ "weight_key": "transformer_blocks.7.img_mlp.net.2.weight",
834
+ "bias_key": "transformer_blocks.7.img_mlp.net.2.bias",
835
+ "weight_shape": [
836
+ 3072,
837
+ 12288
838
+ ],
839
+ "bias_shape": [
840
+ 3072
841
+ ],
842
+ "weight_tensor_scale_key": "targets.transformer_blocks.7.img_mlp.net.2.weight_tensor_scale",
843
+ "artifact_weight_key": "targets.transformer_blocks.7.img_mlp.net.2.packed_weight_e2m1",
844
+ "artifact_scale_key": "targets.transformer_blocks.7.img_mlp.net.2.packed_scales_ue4m3",
845
+ "artifact_bias_key": "targets.transformer_blocks.7.img_mlp.net.2.bias_bf16",
846
+ "weight_tensor_scale": 0.0008603050373494625,
847
+ "weight_global_amax": 2.3125,
848
+ "packed_weight_bytes": 18874368,
849
+ "packed_scale_bytes": 2359296,
850
+ "scale_layout": {
851
+ "inner_dim": 768,
852
+ "outer_tiles": 24,
853
+ "bytes": 2359296
854
+ },
855
+ "source_weight_sha256": "e7efbd25a434220ac1f0f4f11d8e501b699e11a948a86148460b4139db5ba3bf",
856
+ "source_bias_sha256": "b2cd1665c361e61f731ce7b474acb2e5f57087c2cacf53f92f418256c5f1ce86"
857
+ },
858
+ {
859
+ "module_key": "transformer_blocks.7.txt_mlp.net.0.proj",
860
+ "weight_key": "transformer_blocks.7.txt_mlp.net.0.proj.weight",
861
+ "bias_key": "transformer_blocks.7.txt_mlp.net.0.proj.bias",
862
+ "weight_shape": [
863
+ 12288,
864
+ 3072
865
+ ],
866
+ "bias_shape": [
867
+ 12288
868
+ ],
869
+ "weight_tensor_scale_key": "targets.transformer_blocks.7.txt_mlp.net.0.proj.weight_tensor_scale",
870
+ "artifact_weight_key": "targets.transformer_blocks.7.txt_mlp.net.0.proj.packed_weight_e2m1",
871
+ "artifact_scale_key": "targets.transformer_blocks.7.txt_mlp.net.0.proj.packed_scales_ue4m3",
872
+ "artifact_bias_key": "targets.transformer_blocks.7.txt_mlp.net.0.proj.bias_bf16",
873
+ "weight_tensor_scale": 0.00027901786961592734,
874
+ "weight_global_amax": 0.75,
875
+ "packed_weight_bytes": 18874368,
876
+ "packed_scale_bytes": 2359296,
877
+ "scale_layout": {
878
+ "inner_dim": 192,
879
+ "outer_tiles": 96,
880
+ "bytes": 2359296
881
+ },
882
+ "source_weight_sha256": "cb6126a8544c10ae9f23b3d782c83258a8e700a42ac2ad2e5001099e305f271b",
883
+ "source_bias_sha256": "ed4294351c27eaf4e5f85bb926daed8e479f3862e67e79142788a57490059768"
884
+ },
885
+ {
886
+ "module_key": "transformer_blocks.7.txt_mlp.net.2",
887
+ "weight_key": "transformer_blocks.7.txt_mlp.net.2.weight",
888
+ "bias_key": "transformer_blocks.7.txt_mlp.net.2.bias",
889
+ "weight_shape": [
890
+ 3072,
891
+ 12288
892
+ ],
893
+ "bias_shape": [
894
+ 3072
895
+ ],
896
+ "weight_tensor_scale_key": "targets.transformer_blocks.7.txt_mlp.net.2.weight_tensor_scale",
897
+ "artifact_weight_key": "targets.transformer_blocks.7.txt_mlp.net.2.packed_weight_e2m1",
898
+ "artifact_scale_key": "targets.transformer_blocks.7.txt_mlp.net.2.packed_scales_ue4m3",
899
+ "artifact_bias_key": "targets.transformer_blocks.7.txt_mlp.net.2.bias_bf16",
900
+ "weight_tensor_scale": 0.00039527530316263437,
901
+ "weight_global_amax": 1.0625,
902
+ "packed_weight_bytes": 18874368,
903
+ "packed_scale_bytes": 2359296,
904
+ "scale_layout": {
905
+ "inner_dim": 768,
906
+ "outer_tiles": 24,
907
+ "bytes": 2359296
908
+ },
909
+ "source_weight_sha256": "84caab56f058269c0576a27a2b52ae9945f225ba5b6c158cd3f8b8daf2a135f7",
910
+ "source_bias_sha256": "cb733e6b65392d1189b7a9d92984c51f8638a1375b6475023b26cd3c362ee7a8"
911
+ },
912
+ {
913
+ "module_key": "transformer_blocks.8.img_mlp.net.0.proj",
914
+ "weight_key": "transformer_blocks.8.img_mlp.net.0.proj.weight",
915
+ "bias_key": "transformer_blocks.8.img_mlp.net.0.proj.bias",
916
+ "weight_shape": [
917
+ 12288,
918
+ 3072
919
+ ],
920
+ "bias_shape": [
921
+ 12288
922
+ ],
923
+ "weight_tensor_scale_key": "targets.transformer_blocks.8.img_mlp.net.0.proj.weight_tensor_scale",
924
+ "artifact_weight_key": "targets.transformer_blocks.8.img_mlp.net.0.proj.packed_weight_e2m1",
925
+ "artifact_scale_key": "targets.transformer_blocks.8.img_mlp.net.0.proj.packed_scales_ue4m3",
926
+ "artifact_bias_key": "targets.transformer_blocks.8.img_mlp.net.0.proj.bias_bf16",
927
+ "weight_tensor_scale": 0.00041562033584341407,
928
+ "weight_global_amax": 1.1171875,
929
+ "packed_weight_bytes": 18874368,
930
+ "packed_scale_bytes": 2359296,
931
+ "scale_layout": {
932
+ "inner_dim": 192,
933
+ "outer_tiles": 96,
934
+ "bytes": 2359296
935
+ },
936
+ "source_weight_sha256": "b6da93ff7c47065d4dc8d5b44f02c1f956bb8f373d89526565b2cc1cc3c731ef",
937
+ "source_bias_sha256": "a155557ac69e102404fcde3489febf8d75d8df81a58f010ae722f73b808c0cf0"
938
+ },
939
+ {
940
+ "module_key": "transformer_blocks.8.img_mlp.net.2",
941
+ "weight_key": "transformer_blocks.8.img_mlp.net.2.weight",
942
+ "bias_key": "transformer_blocks.8.img_mlp.net.2.bias",
943
+ "weight_shape": [
944
+ 3072,
945
+ 12288
946
+ ],
947
+ "bias_shape": [
948
+ 3072
949
+ ],
950
+ "weight_tensor_scale_key": "targets.transformer_blocks.8.img_mlp.net.2.weight_tensor_scale",
951
+ "artifact_weight_key": "targets.transformer_blocks.8.img_mlp.net.2.packed_weight_e2m1",
952
+ "artifact_scale_key": "targets.transformer_blocks.8.img_mlp.net.2.packed_scales_ue4m3",
953
+ "artifact_bias_key": "targets.transformer_blocks.8.img_mlp.net.2.bias_bf16",
954
+ "weight_tensor_scale": 0.0005260648904368281,
955
+ "weight_global_amax": 1.4140625,
956
+ "packed_weight_bytes": 18874368,
957
+ "packed_scale_bytes": 2359296,
958
+ "scale_layout": {
959
+ "inner_dim": 768,
960
+ "outer_tiles": 24,
961
+ "bytes": 2359296
962
+ },
963
+ "source_weight_sha256": "928218f79be4de0ca6fd6f855310ad7a91b588be9c970c9ea6a11f55b1a1e413",
964
+ "source_bias_sha256": "669dfaa63f72b425feecba8fb58b4c43926e96d971f2f2e9241632855c559236"
965
+ },
966
+ {
967
+ "module_key": "transformer_blocks.8.txt_mlp.net.0.proj",
968
+ "weight_key": "transformer_blocks.8.txt_mlp.net.0.proj.weight",
969
+ "bias_key": "transformer_blocks.8.txt_mlp.net.0.proj.bias",
970
+ "weight_shape": [
971
+ 12288,
972
+ 3072
973
+ ],
974
+ "bias_shape": [
975
+ 12288
976
+ ],
977
+ "weight_tensor_scale_key": "targets.transformer_blocks.8.txt_mlp.net.0.proj.weight_tensor_scale",
978
+ "artifact_weight_key": "targets.transformer_blocks.8.txt_mlp.net.0.proj.packed_weight_e2m1",
979
+ "artifact_scale_key": "targets.transformer_blocks.8.txt_mlp.net.0.proj.packed_scales_ue4m3",
980
+ "artifact_bias_key": "targets.transformer_blocks.8.txt_mlp.net.0.proj.bias_bf16",
981
+ "weight_tensor_scale": 0.0002601260202936828,
982
+ "weight_global_amax": 0.69921875,
983
+ "packed_weight_bytes": 18874368,
984
+ "packed_scale_bytes": 2359296,
985
+ "scale_layout": {
986
+ "inner_dim": 192,
987
+ "outer_tiles": 96,
988
+ "bytes": 2359296
989
+ },
990
+ "source_weight_sha256": "9342523cc73ba392d60e1dffdfe37824a20e5173281de80f85a4cef4893363db",
991
+ "source_bias_sha256": "8c13b195dab6195c7654e07b8416da4c952df50f16fd9eccb248c1a38262df10"
992
+ },
993
+ {
994
+ "module_key": "transformer_blocks.8.txt_mlp.net.2",
995
+ "weight_key": "transformer_blocks.8.txt_mlp.net.2.weight",
996
+ "bias_key": "transformer_blocks.8.txt_mlp.net.2.bias",
997
+ "weight_shape": [
998
+ 3072,
999
+ 12288
1000
+ ],
1001
+ "bias_shape": [
1002
+ 3072
1003
+ ],
1004
+ "weight_tensor_scale_key": "targets.transformer_blocks.8.txt_mlp.net.2.weight_tensor_scale",
1005
+ "artifact_weight_key": "targets.transformer_blocks.8.txt_mlp.net.2.packed_weight_e2m1",
1006
+ "artifact_scale_key": "targets.transformer_blocks.8.txt_mlp.net.2.packed_scales_ue4m3",
1007
+ "artifact_bias_key": "targets.transformer_blocks.8.txt_mlp.net.2.bias_bf16",
1008
+ "weight_tensor_scale": 0.00042724609375,
1009
+ "weight_global_amax": 1.1484375,
1010
+ "packed_weight_bytes": 18874368,
1011
+ "packed_scale_bytes": 2359296,
1012
+ "scale_layout": {
1013
+ "inner_dim": 768,
1014
+ "outer_tiles": 24,
1015
+ "bytes": 2359296
1016
+ },
1017
+ "source_weight_sha256": "b41e8d7028a6f787c256611afeae81c47990e7a91e9e682902dcedfcc9c13eaa",
1018
+ "source_bias_sha256": "4dcdabdb17aabba97ac13d6e3c733a3b212d7e9e7092a7e0fbde6ccd81337a76"
1019
+ },
1020
+ {
1021
+ "module_key": "transformer_blocks.9.img_mlp.net.0.proj",
1022
+ "weight_key": "transformer_blocks.9.img_mlp.net.0.proj.weight",
1023
+ "bias_key": "transformer_blocks.9.img_mlp.net.0.proj.bias",
1024
+ "weight_shape": [
1025
+ 12288,
1026
+ 3072
1027
+ ],
1028
+ "bias_shape": [
1029
+ 12288
1030
+ ],
1031
+ "weight_tensor_scale_key": "targets.transformer_blocks.9.img_mlp.net.0.proj.weight_tensor_scale",
1032
+ "artifact_weight_key": "targets.transformer_blocks.9.img_mlp.net.0.proj.packed_weight_e2m1",
1033
+ "artifact_scale_key": "targets.transformer_blocks.9.img_mlp.net.0.proj.packed_scales_ue4m3",
1034
+ "artifact_bias_key": "targets.transformer_blocks.9.img_mlp.net.0.proj.bias_bf16",
1035
+ "weight_tensor_scale": 0.0004911876749247313,
1036
+ "weight_global_amax": 1.3203125,
1037
+ "packed_weight_bytes": 18874368,
1038
+ "packed_scale_bytes": 2359296,
1039
+ "scale_layout": {
1040
+ "inner_dim": 192,
1041
+ "outer_tiles": 96,
1042
+ "bytes": 2359296
1043
+ },
1044
+ "source_weight_sha256": "82c42f661aab545f0807ff6aa8a1da83f8b50c186b2fec439ab65540449f4b58",
1045
+ "source_bias_sha256": "790d198723850350f0430dc9262fc2019d3b1e3b7e32934212015bff7b767ff1"
1046
+ },
1047
+ {
1048
+ "module_key": "transformer_blocks.9.img_mlp.net.2",
1049
+ "weight_key": "transformer_blocks.9.img_mlp.net.2.weight",
1050
+ "bias_key": "transformer_blocks.9.img_mlp.net.2.bias",
1051
+ "weight_shape": [
1052
+ 3072,
1053
+ 12288
1054
+ ],
1055
+ "bias_shape": [
1056
+ 3072
1057
+ ],
1058
+ "weight_tensor_scale_key": "targets.transformer_blocks.9.img_mlp.net.2.weight_tensor_scale",
1059
+ "artifact_weight_key": "targets.transformer_blocks.9.img_mlp.net.2.packed_weight_e2m1",
1060
+ "artifact_scale_key": "targets.transformer_blocks.9.img_mlp.net.2.packed_scales_ue4m3",
1061
+ "artifact_bias_key": "targets.transformer_blocks.9.img_mlp.net.2.bias_bf16",
1062
+ "weight_tensor_scale": 0.0005435035564005375,
1063
+ "weight_global_amax": 1.4609375,
1064
+ "packed_weight_bytes": 18874368,
1065
+ "packed_scale_bytes": 2359296,
1066
+ "scale_layout": {
1067
+ "inner_dim": 768,
1068
+ "outer_tiles": 24,
1069
+ "bytes": 2359296
1070
+ },
1071
+ "source_weight_sha256": "0c2af8a1f4e348d1150532b2fb9da63c33bee8603e1d4720b2c830632d6157c2",
1072
+ "source_bias_sha256": "37f0b49b8f98d5981a92cc830bdcf202e077d458dcad8bedd26982ce12aef170"
1073
+ },
1074
+ {
1075
+ "module_key": "transformer_blocks.9.txt_mlp.net.0.proj",
1076
+ "weight_key": "transformer_blocks.9.txt_mlp.net.0.proj.weight",
1077
+ "bias_key": "transformer_blocks.9.txt_mlp.net.0.proj.bias",
1078
+ "weight_shape": [
1079
+ 12288,
1080
+ 3072
1081
+ ],
1082
+ "bias_shape": [
1083
+ 12288
1084
+ ],
1085
+ "weight_tensor_scale_key": "targets.transformer_blocks.9.txt_mlp.net.0.proj.weight_tensor_scale",
1086
+ "artifact_weight_key": "targets.transformer_blocks.9.txt_mlp.net.0.proj.packed_weight_e2m1",
1087
+ "artifact_scale_key": "targets.transformer_blocks.9.txt_mlp.net.0.proj.packed_scales_ue4m3",
1088
+ "artifact_bias_key": "targets.transformer_blocks.9.txt_mlp.net.0.proj.bias_bf16",
1089
+ "weight_tensor_scale": 0.0002819242945406586,
1090
+ "weight_global_amax": 0.7578125,
1091
+ "packed_weight_bytes": 18874368,
1092
+ "packed_scale_bytes": 2359296,
1093
+ "scale_layout": {
1094
+ "inner_dim": 192,
1095
+ "outer_tiles": 96,
1096
+ "bytes": 2359296
1097
+ },
1098
+ "source_weight_sha256": "7569c27e45b5327904b003909f88f373e99bd93b150a5508bd3c49470da94c8f",
1099
+ "source_bias_sha256": "5e5a68b982622caffbb11f7a7b131939ece3715bc0ffbcb32fa29dd445fd48ea"
1100
+ },
1101
+ {
1102
+ "module_key": "transformer_blocks.9.txt_mlp.net.2",
1103
+ "weight_key": "transformer_blocks.9.txt_mlp.net.2.weight",
1104
+ "bias_key": "transformer_blocks.9.txt_mlp.net.2.bias",
1105
+ "weight_shape": [
1106
+ 3072,
1107
+ 12288
1108
+ ],
1109
+ "bias_shape": [
1110
+ 3072
1111
+ ],
1112
+ "weight_tensor_scale_key": "targets.transformer_blocks.9.txt_mlp.net.2.weight_tensor_scale",
1113
+ "artifact_weight_key": "targets.transformer_blocks.9.txt_mlp.net.2.packed_weight_e2m1",
1114
+ "artifact_scale_key": "targets.transformer_blocks.9.txt_mlp.net.2.packed_scales_ue4m3",
1115
+ "artifact_bias_key": "targets.transformer_blocks.9.txt_mlp.net.2.bias_bf16",
1116
+ "weight_tensor_scale": 0.0006045387126505375,
1117
+ "weight_global_amax": 1.625,
1118
+ "packed_weight_bytes": 18874368,
1119
+ "packed_scale_bytes": 2359296,
1120
+ "scale_layout": {
1121
+ "inner_dim": 768,
1122
+ "outer_tiles": 24,
1123
+ "bytes": 2359296
1124
+ },
1125
+ "source_weight_sha256": "5ce62f2c67fdf6f7caf6841ed2a2cee2e72194768207c0ab631f147dae462f80",
1126
+ "source_bias_sha256": "11cb1954c19d92e661518cca9971ac432a4ff17f9d8832d789f91dc9bbaeca37"
1127
+ },
1128
+ {
1129
+ "module_key": "transformer_blocks.10.img_mlp.net.0.proj",
1130
+ "weight_key": "transformer_blocks.10.img_mlp.net.0.proj.weight",
1131
+ "bias_key": "transformer_blocks.10.img_mlp.net.0.proj.bias",
1132
+ "weight_shape": [
1133
+ 12288,
1134
+ 3072
1135
+ ],
1136
+ "bias_shape": [
1137
+ 12288
1138
+ ],
1139
+ "weight_tensor_scale_key": "targets.transformer_blocks.10.img_mlp.net.0.proj.weight_tensor_scale",
1140
+ "artifact_weight_key": "targets.transformer_blocks.10.img_mlp.net.0.proj.packed_weight_e2m1",
1141
+ "artifact_scale_key": "targets.transformer_blocks.10.img_mlp.net.0.proj.packed_scales_ue4m3",
1142
+ "artifact_bias_key": "targets.transformer_blocks.10.img_mlp.net.0.proj.bias_bf16",
1143
+ "weight_tensor_scale": 0.00019618442456703633,
1144
+ "weight_global_amax": 0.52734375,
1145
+ "packed_weight_bytes": 18874368,
1146
+ "packed_scale_bytes": 2359296,
1147
+ "scale_layout": {
1148
+ "inner_dim": 192,
1149
+ "outer_tiles": 96,
1150
+ "bytes": 2359296
1151
+ },
1152
+ "source_weight_sha256": "25c16255a05df5f725a32fa1183740eecd018f52fa5e52d7b117f396656307f7",
1153
+ "source_bias_sha256": "79ba550cd845a1d1156fa06661ea9a268331d2d3f13ed4f32225318796bdb2f1"
1154
+ },
1155
+ {
1156
+ "module_key": "transformer_blocks.10.img_mlp.net.2",
1157
+ "weight_key": "transformer_blocks.10.img_mlp.net.2.weight",
1158
+ "bias_key": "transformer_blocks.10.img_mlp.net.2.bias",
1159
+ "weight_shape": [
1160
+ 3072,
1161
+ 12288
1162
+ ],
1163
+ "bias_shape": [
1164
+ 3072
1165
+ ],
1166
+ "weight_tensor_scale_key": "targets.transformer_blocks.10.img_mlp.net.2.weight_tensor_scale",
1167
+ "artifact_weight_key": "targets.transformer_blocks.10.img_mlp.net.2.packed_weight_e2m1",
1168
+ "artifact_scale_key": "targets.transformer_blocks.10.img_mlp.net.2.packed_scales_ue4m3",
1169
+ "artifact_bias_key": "targets.transformer_blocks.10.img_mlp.net.2.bias_bf16",
1170
+ "weight_tensor_scale": 0.0005812871968373656,
1171
+ "weight_global_amax": 1.5625,
1172
+ "packed_weight_bytes": 18874368,
1173
+ "packed_scale_bytes": 2359296,
1174
+ "scale_layout": {
1175
+ "inner_dim": 768,
1176
+ "outer_tiles": 24,
1177
+ "bytes": 2359296
1178
+ },
1179
+ "source_weight_sha256": "710837d28af5c4871c535b02e29431563d3d3755236b8a4acf8e0ebb8c334764",
1180
+ "source_bias_sha256": "021b5a6799ed0e35ee49a28af9acc77561bc1defb184a47df39d28178edf3084"
1181
+ },
1182
+ {
1183
+ "module_key": "transformer_blocks.10.txt_mlp.net.0.proj",
1184
+ "weight_key": "transformer_blocks.10.txt_mlp.net.0.proj.weight",
1185
+ "bias_key": "transformer_blocks.10.txt_mlp.net.0.proj.bias",
1186
+ "weight_shape": [
1187
+ 12288,
1188
+ 3072
1189
+ ],
1190
+ "bias_shape": [
1191
+ 12288
1192
+ ],
1193
+ "weight_tensor_scale_key": "targets.transformer_blocks.10.txt_mlp.net.0.proj.weight_tensor_scale",
1194
+ "artifact_weight_key": "targets.transformer_blocks.10.txt_mlp.net.0.proj.packed_weight_e2m1",
1195
+ "artifact_scale_key": "targets.transformer_blocks.10.txt_mlp.net.0.proj.packed_scales_ue4m3",
1196
+ "artifact_bias_key": "targets.transformer_blocks.10.txt_mlp.net.0.proj.bias_bf16",
1197
+ "weight_tensor_scale": 0.00015476772387046367,
1198
+ "weight_global_amax": 0.416015625,
1199
+ "packed_weight_bytes": 18874368,
1200
+ "packed_scale_bytes": 2359296,
1201
+ "scale_layout": {
1202
+ "inner_dim": 192,
1203
+ "outer_tiles": 96,
1204
+ "bytes": 2359296
1205
+ },
1206
+ "source_weight_sha256": "dd76244de2f2319b3c617e91cc6e863fe6d05c83d0038d3b903e198993c497dc",
1207
+ "source_bias_sha256": "dd6336134c5a891280e5e0b22ae7690cddf6d4cf937c4198ac5865202958a162"
1208
+ },
1209
+ {
1210
+ "module_key": "transformer_blocks.10.txt_mlp.net.2",
1211
+ "weight_key": "transformer_blocks.10.txt_mlp.net.2.weight",
1212
+ "bias_key": "transformer_blocks.10.txt_mlp.net.2.bias",
1213
+ "weight_shape": [
1214
+ 3072,
1215
+ 12288
1216
+ ],
1217
+ "bias_shape": [
1218
+ 3072
1219
+ ],
1220
+ "weight_tensor_scale_key": "targets.transformer_blocks.10.txt_mlp.net.2.weight_tensor_scale",
1221
+ "artifact_weight_key": "targets.transformer_blocks.10.txt_mlp.net.2.packed_weight_e2m1",
1222
+ "artifact_scale_key": "targets.transformer_blocks.10.txt_mlp.net.2.packed_scales_ue4m3",
1223
+ "artifact_bias_key": "targets.transformer_blocks.10.txt_mlp.net.2.bias_bf16",
1224
+ "weight_tensor_scale": 0.00018383207498118281,
1225
+ "weight_global_amax": 0.494140625,
1226
+ "packed_weight_bytes": 18874368,
1227
+ "packed_scale_bytes": 2359296,
1228
+ "scale_layout": {
1229
+ "inner_dim": 768,
1230
+ "outer_tiles": 24,
1231
+ "bytes": 2359296
1232
+ },
1233
+ "source_weight_sha256": "3748e2fb3c162f846342f7a4c5f8f600d614150163332c2b32ba5f3c7291f4b4",
1234
+ "source_bias_sha256": "333b152ef1291bf1fcd6fc796345483195b2632b70e0b6ab13ddabf8b7bb20e4"
1235
+ },
1236
+ {
1237
+ "module_key": "transformer_blocks.11.img_mlp.net.0.proj",
1238
+ "weight_key": "transformer_blocks.11.img_mlp.net.0.proj.weight",
1239
+ "bias_key": "transformer_blocks.11.img_mlp.net.0.proj.bias",
1240
+ "weight_shape": [
1241
+ 12288,
1242
+ 3072
1243
+ ],
1244
+ "bias_shape": [
1245
+ 12288
1246
+ ],
1247
+ "weight_tensor_scale_key": "targets.transformer_blocks.11.img_mlp.net.0.proj.weight_tensor_scale",
1248
+ "artifact_weight_key": "targets.transformer_blocks.11.img_mlp.net.0.proj.packed_weight_e2m1",
1249
+ "artifact_scale_key": "targets.transformer_blocks.11.img_mlp.net.0.proj.packed_scales_ue4m3",
1250
+ "artifact_bias_key": "targets.transformer_blocks.11.img_mlp.net.0.proj.bias_bf16",
1251
+ "weight_tensor_scale": 0.0002252487902296707,
1252
+ "weight_global_amax": 0.60546875,
1253
+ "packed_weight_bytes": 18874368,
1254
+ "packed_scale_bytes": 2359296,
1255
+ "scale_layout": {
1256
+ "inner_dim": 192,
1257
+ "outer_tiles": 96,
1258
+ "bytes": 2359296
1259
+ },
1260
+ "source_weight_sha256": "81207ce0a6cc70bf94346c5c74e7729a9df4e2cc87e943f5461ce6ccdcddf25f",
1261
+ "source_bias_sha256": "3baf43b038997ba0ebe0e9326e17fe210727fe89fcc832f93082381c5cca2adc"
1262
+ },
1263
+ {
1264
+ "module_key": "transformer_blocks.11.img_mlp.net.2",
1265
+ "weight_key": "transformer_blocks.11.img_mlp.net.2.weight",
1266
+ "bias_key": "transformer_blocks.11.img_mlp.net.2.bias",
1267
+ "weight_shape": [
1268
+ 3072,
1269
+ 12288
1270
+ ],
1271
+ "bias_shape": [
1272
+ 3072
1273
+ ],
1274
+ "weight_tensor_scale_key": "targets.transformer_blocks.11.img_mlp.net.2.weight_tensor_scale",
1275
+ "artifact_weight_key": "targets.transformer_blocks.11.img_mlp.net.2.packed_weight_e2m1",
1276
+ "artifact_scale_key": "targets.transformer_blocks.11.img_mlp.net.2.packed_scales_ue4m3",
1277
+ "artifact_bias_key": "targets.transformer_blocks.11.img_mlp.net.2.bias_bf16",
1278
+ "weight_tensor_scale": 0.0009707496501505375,
1279
+ "weight_global_amax": 2.609375,
1280
+ "packed_weight_bytes": 18874368,
1281
+ "packed_scale_bytes": 2359296,
1282
+ "scale_layout": {
1283
+ "inner_dim": 768,
1284
+ "outer_tiles": 24,
1285
+ "bytes": 2359296
1286
+ },
1287
+ "source_weight_sha256": "ba3889bd47bfe14a07b6b4d9f624d1187980d07fb81de6d0be765f195a22c487",
1288
+ "source_bias_sha256": "fd48e42ed9061e494e065910ae622964b25e6c3abc13613b38f4b4a4a0c8ec13"
1289
+ },
1290
+ {
1291
+ "module_key": "transformer_blocks.11.txt_mlp.net.0.proj",
1292
+ "weight_key": "transformer_blocks.11.txt_mlp.net.0.proj.weight",
1293
+ "bias_key": "transformer_blocks.11.txt_mlp.net.0.proj.bias",
1294
+ "weight_shape": [
1295
+ 12288,
1296
+ 3072
1297
+ ],
1298
+ "bias_shape": [
1299
+ 12288
1300
+ ],
1301
+ "weight_tensor_scale_key": "targets.transformer_blocks.11.txt_mlp.net.0.proj.weight_tensor_scale",
1302
+ "artifact_weight_key": "targets.transformer_blocks.11.txt_mlp.net.0.proj.packed_weight_e2m1",
1303
+ "artifact_scale_key": "targets.transformer_blocks.11.txt_mlp.net.0.proj.packed_scales_ue4m3",
1304
+ "artifact_bias_key": "targets.transformer_blocks.11.txt_mlp.net.0.proj.bias_bf16",
1305
+ "weight_tensor_scale": 1.3850983577867737e-06,
1306
+ "weight_global_amax": 0.00372314453125,
1307
+ "packed_weight_bytes": 18874368,
1308
+ "packed_scale_bytes": 2359296,
1309
+ "scale_layout": {
1310
+ "inner_dim": 192,
1311
+ "outer_tiles": 96,
1312
+ "bytes": 2359296
1313
+ },
1314
+ "source_weight_sha256": "3e43f754f9d63e3dd6d6a71c4cc0c15344bd56da4d5a19a7adeb54445b820d72",
1315
+ "source_bias_sha256": "062da4757b9f03b3273c98cf91a517e2b4a667cbfe1f0ba190b02b626b0ac75c"
1316
+ },
1317
+ {
1318
+ "module_key": "transformer_blocks.11.txt_mlp.net.2",
1319
+ "weight_key": "transformer_blocks.11.txt_mlp.net.2.weight",
1320
+ "bias_key": "transformer_blocks.11.txt_mlp.net.2.bias",
1321
+ "weight_shape": [
1322
+ 3072,
1323
+ 12288
1324
+ ],
1325
+ "bias_shape": [
1326
+ 3072
1327
+ ],
1328
+ "weight_tensor_scale_key": "targets.transformer_blocks.11.txt_mlp.net.2.weight_tensor_scale",
1329
+ "artifact_weight_key": "targets.transformer_blocks.11.txt_mlp.net.2.packed_weight_e2m1",
1330
+ "artifact_scale_key": "targets.transformer_blocks.11.txt_mlp.net.2.packed_scales_ue4m3",
1331
+ "artifact_bias_key": "targets.transformer_blocks.11.txt_mlp.net.2.bias_bf16",
1332
+ "weight_tensor_scale": 6.925491788933869e-07,
1333
+ "weight_global_amax": 0.001861572265625,
1334
+ "packed_weight_bytes": 18874368,
1335
+ "packed_scale_bytes": 2359296,
1336
+ "scale_layout": {
1337
+ "inner_dim": 768,
1338
+ "outer_tiles": 24,
1339
+ "bytes": 2359296
1340
+ },
1341
+ "source_weight_sha256": "4a74e0fc231d978ddbca106304430d9dcb511c7284e3c18c245005626e88224d",
1342
+ "source_bias_sha256": "6ef0d2f05867e5017824fc95cfd0a5803ce3d736cd622d82d92b603938d66e41"
1343
+ }
1344
+ ],
1345
+ "non_target_keys": [
1346
+ "img_in.bias",
1347
+ "img_in.weight",
1348
+ "norm_out.linear.bias",
1349
+ "norm_out.linear.weight",
1350
+ "proj_out.bias",
1351
+ "proj_out.weight",
1352
+ "time_text_embed.timestep_embedder.linear_1.bias",
1353
+ "time_text_embed.timestep_embedder.linear_1.weight",
1354
+ "time_text_embed.timestep_embedder.linear_2.bias",
1355
+ "time_text_embed.timestep_embedder.linear_2.weight",
1356
+ "transformer_blocks.0.attn.add_k_proj.bias",
1357
+ "transformer_blocks.0.attn.add_k_proj.weight",
1358
+ "transformer_blocks.0.attn.add_q_proj.bias",
1359
+ "transformer_blocks.0.attn.add_q_proj.weight",
1360
+ "transformer_blocks.0.attn.add_v_proj.bias",
1361
+ "transformer_blocks.0.attn.add_v_proj.weight",
1362
+ "transformer_blocks.0.attn.norm_added_k.weight",
1363
+ "transformer_blocks.0.attn.norm_added_q.weight",
1364
+ "transformer_blocks.0.attn.norm_k.weight",
1365
+ "transformer_blocks.0.attn.norm_q.weight",
1366
+ "transformer_blocks.0.attn.to_add_out.bias",
1367
+ "transformer_blocks.0.attn.to_add_out.weight",
1368
+ "transformer_blocks.0.attn.to_k.bias",
1369
+ "transformer_blocks.0.attn.to_k.weight",
1370
+ "transformer_blocks.0.attn.to_out.0.bias",
1371
+ "transformer_blocks.0.attn.to_out.0.weight",
1372
+ "transformer_blocks.0.attn.to_q.bias",
1373
+ "transformer_blocks.0.attn.to_q.weight",
1374
+ "transformer_blocks.0.attn.to_v.bias",
1375
+ "transformer_blocks.0.attn.to_v.weight",
1376
+ "transformer_blocks.0.img_mod.1.bias",
1377
+ "transformer_blocks.0.img_mod.1.weight",
1378
+ "transformer_blocks.0.txt_mod.1.bias",
1379
+ "transformer_blocks.0.txt_mod.1.weight",
1380
+ "transformer_blocks.1.attn.add_k_proj.bias",
1381
+ "transformer_blocks.1.attn.add_k_proj.weight",
1382
+ "transformer_blocks.1.attn.add_q_proj.bias",
1383
+ "transformer_blocks.1.attn.add_q_proj.weight",
1384
+ "transformer_blocks.1.attn.add_v_proj.bias",
1385
+ "transformer_blocks.1.attn.add_v_proj.weight",
1386
+ "transformer_blocks.1.attn.norm_added_k.weight",
1387
+ "transformer_blocks.1.attn.norm_added_q.weight",
1388
+ "transformer_blocks.1.attn.norm_k.weight",
1389
+ "transformer_blocks.1.attn.norm_q.weight",
1390
+ "transformer_blocks.1.attn.to_add_out.bias",
1391
+ "transformer_blocks.1.attn.to_add_out.weight",
1392
+ "transformer_blocks.1.attn.to_k.bias",
1393
+ "transformer_blocks.1.attn.to_k.weight",
1394
+ "transformer_blocks.1.attn.to_out.0.bias",
1395
+ "transformer_blocks.1.attn.to_out.0.weight",
1396
+ "transformer_blocks.1.attn.to_q.bias",
1397
+ "transformer_blocks.1.attn.to_q.weight",
1398
+ "transformer_blocks.1.attn.to_v.bias",
1399
+ "transformer_blocks.1.attn.to_v.weight",
1400
+ "transformer_blocks.1.img_mod.1.bias",
1401
+ "transformer_blocks.1.img_mod.1.weight",
1402
+ "transformer_blocks.1.txt_mod.1.bias",
1403
+ "transformer_blocks.1.txt_mod.1.weight",
1404
+ "transformer_blocks.10.attn.add_k_proj.bias",
1405
+ "transformer_blocks.10.attn.add_k_proj.weight",
1406
+ "transformer_blocks.10.attn.add_q_proj.bias",
1407
+ "transformer_blocks.10.attn.add_q_proj.weight",
1408
+ "transformer_blocks.10.attn.add_v_proj.bias",
1409
+ "transformer_blocks.10.attn.add_v_proj.weight",
1410
+ "transformer_blocks.10.attn.norm_added_k.weight",
1411
+ "transformer_blocks.10.attn.norm_added_q.weight",
1412
+ "transformer_blocks.10.attn.norm_k.weight",
1413
+ "transformer_blocks.10.attn.norm_q.weight",
1414
+ "transformer_blocks.10.attn.to_add_out.bias",
1415
+ "transformer_blocks.10.attn.to_add_out.weight",
1416
+ "transformer_blocks.10.attn.to_k.bias",
1417
+ "transformer_blocks.10.attn.to_k.weight",
1418
+ "transformer_blocks.10.attn.to_out.0.bias",
1419
+ "transformer_blocks.10.attn.to_out.0.weight",
1420
+ "transformer_blocks.10.attn.to_q.bias",
1421
+ "transformer_blocks.10.attn.to_q.weight",
1422
+ "transformer_blocks.10.attn.to_v.bias",
1423
+ "transformer_blocks.10.attn.to_v.weight",
1424
+ "transformer_blocks.10.img_mod.1.bias",
1425
+ "transformer_blocks.10.img_mod.1.weight",
1426
+ "transformer_blocks.10.txt_mod.1.bias",
1427
+ "transformer_blocks.10.txt_mod.1.weight",
1428
+ "transformer_blocks.11.attn.add_k_proj.bias",
1429
+ "transformer_blocks.11.attn.add_k_proj.weight",
1430
+ "transformer_blocks.11.attn.add_q_proj.bias",
1431
+ "transformer_blocks.11.attn.add_q_proj.weight",
1432
+ "transformer_blocks.11.attn.add_v_proj.bias",
1433
+ "transformer_blocks.11.attn.add_v_proj.weight",
1434
+ "transformer_blocks.11.attn.norm_added_k.weight",
1435
+ "transformer_blocks.11.attn.norm_added_q.weight",
1436
+ "transformer_blocks.11.attn.norm_k.weight",
1437
+ "transformer_blocks.11.attn.norm_q.weight",
1438
+ "transformer_blocks.11.attn.to_add_out.bias",
1439
+ "transformer_blocks.11.attn.to_add_out.weight",
1440
+ "transformer_blocks.11.attn.to_k.bias",
1441
+ "transformer_blocks.11.attn.to_k.weight",
1442
+ "transformer_blocks.11.attn.to_out.0.bias",
1443
+ "transformer_blocks.11.attn.to_out.0.weight",
1444
+ "transformer_blocks.11.attn.to_q.bias",
1445
+ "transformer_blocks.11.attn.to_q.weight",
1446
+ "transformer_blocks.11.attn.to_v.bias",
1447
+ "transformer_blocks.11.attn.to_v.weight",
1448
+ "transformer_blocks.11.img_mod.1.bias",
1449
+ "transformer_blocks.11.img_mod.1.weight",
1450
+ "transformer_blocks.11.txt_mod.1.bias",
1451
+ "transformer_blocks.11.txt_mod.1.weight",
1452
+ "transformer_blocks.2.attn.add_k_proj.bias",
1453
+ "transformer_blocks.2.attn.add_k_proj.weight",
1454
+ "transformer_blocks.2.attn.add_q_proj.bias",
1455
+ "transformer_blocks.2.attn.add_q_proj.weight",
1456
+ "transformer_blocks.2.attn.add_v_proj.bias",
1457
+ "transformer_blocks.2.attn.add_v_proj.weight",
1458
+ "transformer_blocks.2.attn.norm_added_k.weight",
1459
+ "transformer_blocks.2.attn.norm_added_q.weight",
1460
+ "transformer_blocks.2.attn.norm_k.weight",
1461
+ "transformer_blocks.2.attn.norm_q.weight",
1462
+ "transformer_blocks.2.attn.to_add_out.bias",
1463
+ "transformer_blocks.2.attn.to_add_out.weight",
1464
+ "transformer_blocks.2.attn.to_k.bias",
1465
+ "transformer_blocks.2.attn.to_k.weight",
1466
+ "transformer_blocks.2.attn.to_out.0.bias",
1467
+ "transformer_blocks.2.attn.to_out.0.weight",
1468
+ "transformer_blocks.2.attn.to_q.bias",
1469
+ "transformer_blocks.2.attn.to_q.weight",
1470
+ "transformer_blocks.2.attn.to_v.bias",
1471
+ "transformer_blocks.2.attn.to_v.weight",
1472
+ "transformer_blocks.2.img_mod.1.bias",
1473
+ "transformer_blocks.2.img_mod.1.weight",
1474
+ "transformer_blocks.2.txt_mod.1.bias",
1475
+ "transformer_blocks.2.txt_mod.1.weight",
1476
+ "transformer_blocks.3.attn.add_k_proj.bias",
1477
+ "transformer_blocks.3.attn.add_k_proj.weight",
1478
+ "transformer_blocks.3.attn.add_q_proj.bias",
1479
+ "transformer_blocks.3.attn.add_q_proj.weight",
1480
+ "transformer_blocks.3.attn.add_v_proj.bias",
1481
+ "transformer_blocks.3.attn.add_v_proj.weight",
1482
+ "transformer_blocks.3.attn.norm_added_k.weight",
1483
+ "transformer_blocks.3.attn.norm_added_q.weight",
1484
+ "transformer_blocks.3.attn.norm_k.weight",
1485
+ "transformer_blocks.3.attn.norm_q.weight",
1486
+ "transformer_blocks.3.attn.to_add_out.bias",
1487
+ "transformer_blocks.3.attn.to_add_out.weight",
1488
+ "transformer_blocks.3.attn.to_k.bias",
1489
+ "transformer_blocks.3.attn.to_k.weight",
1490
+ "transformer_blocks.3.attn.to_out.0.bias",
1491
+ "transformer_blocks.3.attn.to_out.0.weight",
1492
+ "transformer_blocks.3.attn.to_q.bias",
1493
+ "transformer_blocks.3.attn.to_q.weight",
1494
+ "transformer_blocks.3.attn.to_v.bias",
1495
+ "transformer_blocks.3.attn.to_v.weight",
1496
+ "transformer_blocks.3.img_mod.1.bias",
1497
+ "transformer_blocks.3.img_mod.1.weight",
1498
+ "transformer_blocks.3.txt_mod.1.bias",
1499
+ "transformer_blocks.3.txt_mod.1.weight",
1500
+ "transformer_blocks.4.attn.add_k_proj.bias",
1501
+ "transformer_blocks.4.attn.add_k_proj.weight",
1502
+ "transformer_blocks.4.attn.add_q_proj.bias",
1503
+ "transformer_blocks.4.attn.add_q_proj.weight",
1504
+ "transformer_blocks.4.attn.add_v_proj.bias",
1505
+ "transformer_blocks.4.attn.add_v_proj.weight",
1506
+ "transformer_blocks.4.attn.norm_added_k.weight",
1507
+ "transformer_blocks.4.attn.norm_added_q.weight",
1508
+ "transformer_blocks.4.attn.norm_k.weight",
1509
+ "transformer_blocks.4.attn.norm_q.weight",
1510
+ "transformer_blocks.4.attn.to_add_out.bias",
1511
+ "transformer_blocks.4.attn.to_add_out.weight",
1512
+ "transformer_blocks.4.attn.to_k.bias",
1513
+ "transformer_blocks.4.attn.to_k.weight",
1514
+ "transformer_blocks.4.attn.to_out.0.bias",
1515
+ "transformer_blocks.4.attn.to_out.0.weight",
1516
+ "transformer_blocks.4.attn.to_q.bias",
1517
+ "transformer_blocks.4.attn.to_q.weight",
1518
+ "transformer_blocks.4.attn.to_v.bias",
1519
+ "transformer_blocks.4.attn.to_v.weight",
1520
+ "transformer_blocks.4.img_mod.1.bias",
1521
+ "transformer_blocks.4.img_mod.1.weight",
1522
+ "transformer_blocks.4.txt_mod.1.bias",
1523
+ "transformer_blocks.4.txt_mod.1.weight",
1524
+ "transformer_blocks.5.attn.add_k_proj.bias",
1525
+ "transformer_blocks.5.attn.add_k_proj.weight",
1526
+ "transformer_blocks.5.attn.add_q_proj.bias",
1527
+ "transformer_blocks.5.attn.add_q_proj.weight",
1528
+ "transformer_blocks.5.attn.add_v_proj.bias",
1529
+ "transformer_blocks.5.attn.add_v_proj.weight",
1530
+ "transformer_blocks.5.attn.norm_added_k.weight",
1531
+ "transformer_blocks.5.attn.norm_added_q.weight",
1532
+ "transformer_blocks.5.attn.norm_k.weight",
1533
+ "transformer_blocks.5.attn.norm_q.weight",
1534
+ "transformer_blocks.5.attn.to_add_out.bias",
1535
+ "transformer_blocks.5.attn.to_add_out.weight",
1536
+ "transformer_blocks.5.attn.to_k.bias",
1537
+ "transformer_blocks.5.attn.to_k.weight",
1538
+ "transformer_blocks.5.attn.to_out.0.bias",
1539
+ "transformer_blocks.5.attn.to_out.0.weight",
1540
+ "transformer_blocks.5.attn.to_q.bias",
1541
+ "transformer_blocks.5.attn.to_q.weight",
1542
+ "transformer_blocks.5.attn.to_v.bias",
1543
+ "transformer_blocks.5.attn.to_v.weight",
1544
+ "transformer_blocks.5.img_mod.1.bias",
1545
+ "transformer_blocks.5.img_mod.1.weight",
1546
+ "transformer_blocks.5.txt_mod.1.bias",
1547
+ "transformer_blocks.5.txt_mod.1.weight",
1548
+ "transformer_blocks.6.attn.add_k_proj.bias",
1549
+ "transformer_blocks.6.attn.add_k_proj.weight",
1550
+ "transformer_blocks.6.attn.add_q_proj.bias",
1551
+ "transformer_blocks.6.attn.add_q_proj.weight",
1552
+ "transformer_blocks.6.attn.add_v_proj.bias",
1553
+ "transformer_blocks.6.attn.add_v_proj.weight",
1554
+ "transformer_blocks.6.attn.norm_added_k.weight",
1555
+ "transformer_blocks.6.attn.norm_added_q.weight",
1556
+ "transformer_blocks.6.attn.norm_k.weight",
1557
+ "transformer_blocks.6.attn.norm_q.weight",
1558
+ "transformer_blocks.6.attn.to_add_out.bias",
1559
+ "transformer_blocks.6.attn.to_add_out.weight",
1560
+ "transformer_blocks.6.attn.to_k.bias",
1561
+ "transformer_blocks.6.attn.to_k.weight",
1562
+ "transformer_blocks.6.attn.to_out.0.bias",
1563
+ "transformer_blocks.6.attn.to_out.0.weight",
1564
+ "transformer_blocks.6.attn.to_q.bias",
1565
+ "transformer_blocks.6.attn.to_q.weight",
1566
+ "transformer_blocks.6.attn.to_v.bias",
1567
+ "transformer_blocks.6.attn.to_v.weight",
1568
+ "transformer_blocks.6.img_mod.1.bias",
1569
+ "transformer_blocks.6.img_mod.1.weight",
1570
+ "transformer_blocks.6.txt_mod.1.bias",
1571
+ "transformer_blocks.6.txt_mod.1.weight",
1572
+ "transformer_blocks.7.attn.add_k_proj.bias",
1573
+ "transformer_blocks.7.attn.add_k_proj.weight",
1574
+ "transformer_blocks.7.attn.add_q_proj.bias",
1575
+ "transformer_blocks.7.attn.add_q_proj.weight",
1576
+ "transformer_blocks.7.attn.add_v_proj.bias",
1577
+ "transformer_blocks.7.attn.add_v_proj.weight",
1578
+ "transformer_blocks.7.attn.norm_added_k.weight",
1579
+ "transformer_blocks.7.attn.norm_added_q.weight",
1580
+ "transformer_blocks.7.attn.norm_k.weight",
1581
+ "transformer_blocks.7.attn.norm_q.weight",
1582
+ "transformer_blocks.7.attn.to_add_out.bias",
1583
+ "transformer_blocks.7.attn.to_add_out.weight",
1584
+ "transformer_blocks.7.attn.to_k.bias",
1585
+ "transformer_blocks.7.attn.to_k.weight",
1586
+ "transformer_blocks.7.attn.to_out.0.bias",
1587
+ "transformer_blocks.7.attn.to_out.0.weight",
1588
+ "transformer_blocks.7.attn.to_q.bias",
1589
+ "transformer_blocks.7.attn.to_q.weight",
1590
+ "transformer_blocks.7.attn.to_v.bias",
1591
+ "transformer_blocks.7.attn.to_v.weight",
1592
+ "transformer_blocks.7.img_mod.1.bias",
1593
+ "transformer_blocks.7.img_mod.1.weight",
1594
+ "transformer_blocks.7.txt_mod.1.bias",
1595
+ "transformer_blocks.7.txt_mod.1.weight",
1596
+ "transformer_blocks.8.attn.add_k_proj.bias",
1597
+ "transformer_blocks.8.attn.add_k_proj.weight",
1598
+ "transformer_blocks.8.attn.add_q_proj.bias",
1599
+ "transformer_blocks.8.attn.add_q_proj.weight",
1600
+ "transformer_blocks.8.attn.add_v_proj.bias",
1601
+ "transformer_blocks.8.attn.add_v_proj.weight",
1602
+ "transformer_blocks.8.attn.norm_added_k.weight",
1603
+ "transformer_blocks.8.attn.norm_added_q.weight",
1604
+ "transformer_blocks.8.attn.norm_k.weight",
1605
+ "transformer_blocks.8.attn.norm_q.weight",
1606
+ "transformer_blocks.8.attn.to_add_out.bias",
1607
+ "transformer_blocks.8.attn.to_add_out.weight",
1608
+ "transformer_blocks.8.attn.to_k.bias",
1609
+ "transformer_blocks.8.attn.to_k.weight",
1610
+ "transformer_blocks.8.attn.to_out.0.bias",
1611
+ "transformer_blocks.8.attn.to_out.0.weight",
1612
+ "transformer_blocks.8.attn.to_q.bias",
1613
+ "transformer_blocks.8.attn.to_q.weight",
1614
+ "transformer_blocks.8.attn.to_v.bias",
1615
+ "transformer_blocks.8.attn.to_v.weight",
1616
+ "transformer_blocks.8.img_mod.1.bias",
1617
+ "transformer_blocks.8.img_mod.1.weight",
1618
+ "transformer_blocks.8.txt_mod.1.bias",
1619
+ "transformer_blocks.8.txt_mod.1.weight",
1620
+ "transformer_blocks.9.attn.add_k_proj.bias",
1621
+ "transformer_blocks.9.attn.add_k_proj.weight",
1622
+ "transformer_blocks.9.attn.add_q_proj.bias",
1623
+ "transformer_blocks.9.attn.add_q_proj.weight",
1624
+ "transformer_blocks.9.attn.add_v_proj.bias",
1625
+ "transformer_blocks.9.attn.add_v_proj.weight",
1626
+ "transformer_blocks.9.attn.norm_added_k.weight",
1627
+ "transformer_blocks.9.attn.norm_added_q.weight",
1628
+ "transformer_blocks.9.attn.norm_k.weight",
1629
+ "transformer_blocks.9.attn.norm_q.weight",
1630
+ "transformer_blocks.9.attn.to_add_out.bias",
1631
+ "transformer_blocks.9.attn.to_add_out.weight",
1632
+ "transformer_blocks.9.attn.to_k.bias",
1633
+ "transformer_blocks.9.attn.to_k.weight",
1634
+ "transformer_blocks.9.attn.to_out.0.bias",
1635
+ "transformer_blocks.9.attn.to_out.0.weight",
1636
+ "transformer_blocks.9.attn.to_q.bias",
1637
+ "transformer_blocks.9.attn.to_q.weight",
1638
+ "transformer_blocks.9.attn.to_v.bias",
1639
+ "transformer_blocks.9.attn.to_v.weight",
1640
+ "transformer_blocks.9.img_mod.1.bias",
1641
+ "transformer_blocks.9.img_mod.1.weight",
1642
+ "transformer_blocks.9.txt_mod.1.bias",
1643
+ "transformer_blocks.9.txt_mod.1.weight",
1644
+ "txt_in.bias",
1645
+ "txt_in.weight",
1646
+ "txt_norm.weight"
1647
+ ]
1648
+ }
vae/config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "MageVAE",
3
+ "latent_channels": 128,
4
+ "downsample_factor": 16,
5
+ "sample_posterior": false
6
+ }
validate_release.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Verify every immutable file declared by the v1 release manifest."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import hashlib
7
+ import json
8
+ from pathlib import Path
9
+ import sys
10
+
11
+
12
+ RELEASE_ROOT = Path(__file__).resolve().parent
13
+ MANIFEST_PATH = RELEASE_ROOT / "MANIFEST.json"
14
+
15
+
16
+ def sha256(path: Path) -> str:
17
+ digest = hashlib.sha256()
18
+ with path.open("rb") as handle:
19
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
20
+ digest.update(chunk)
21
+ return digest.hexdigest()
22
+
23
+
24
+ def main() -> int:
25
+ manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
26
+ failures: list[str] = []
27
+ checked: list[dict[str, object]] = []
28
+ declared_paths = {
29
+ Path(record["path"]).as_posix() for record in manifest["files"]
30
+ }
31
+ actual_paths = {
32
+ path.relative_to(RELEASE_ROOT).as_posix()
33
+ for path in RELEASE_ROOT.rglob("*")
34
+ if (
35
+ path.is_file()
36
+ and path != MANIFEST_PATH
37
+ and "__pycache__" not in path.relative_to(RELEASE_ROOT).parts
38
+ and path.suffix != ".pyc"
39
+ )
40
+ }
41
+ undeclared = sorted(actual_paths - declared_paths)
42
+ absent = sorted(declared_paths - actual_paths)
43
+ if undeclared:
44
+ failures.extend(f"undeclared package file: {path}" for path in undeclared)
45
+ if absent:
46
+ failures.extend(f"declared package file is absent: {path}" for path in absent)
47
+ for record in manifest["files"]:
48
+ relative = Path(record["path"])
49
+ path = (RELEASE_ROOT / relative).resolve()
50
+ if not path.is_relative_to(RELEASE_ROOT):
51
+ failures.append(f"path escapes release root: {relative}")
52
+ continue
53
+ if not path.is_file():
54
+ failures.append(f"missing: {relative}")
55
+ continue
56
+ if path.is_symlink():
57
+ failures.append(f"release file must not be a symlink: {relative}")
58
+ continue
59
+ actual_size = path.stat().st_size
60
+ actual_hash = sha256(path)
61
+ if actual_size != record["size"]:
62
+ failures.append(
63
+ f"size mismatch {relative}: {actual_size} != {record['size']}"
64
+ )
65
+ if actual_hash != record["sha256"]:
66
+ failures.append(
67
+ f"SHA-256 mismatch {relative}: {actual_hash} != "
68
+ f"{record['sha256']}"
69
+ )
70
+ checked.append(
71
+ {
72
+ "path": str(relative),
73
+ "size": actual_size,
74
+ "sha256": actual_hash,
75
+ }
76
+ )
77
+
78
+ result = {
79
+ "schema_version": manifest["schema_version"],
80
+ "status": "pass" if not failures else "fail",
81
+ "checked_file_count": len(checked),
82
+ "failures": failures,
83
+ }
84
+ print(json.dumps(result, indent=2, sort_keys=True))
85
+ return 0 if not failures else 1
86
+
87
+
88
+ if __name__ == "__main__":
89
+ raise SystemExit(main())
vendor/mage_flow/__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MageFlow — standalone text-to-image + image-edit inference."""
2
+
3
+ from .models.mage_flow import ModelConfig
4
+ from .pipeline import (
5
+ MageFlowPipeline,
6
+ generate_edits,
7
+ generate_images,
8
+ load_from_repo,
9
+ )
10
+
11
+ __all__ = [
12
+ "MageFlowPipeline",
13
+ "generate_images",
14
+ "generate_edits",
15
+ "load_from_repo",
16
+ "ModelConfig",
17
+ ]
18
+ __version__ = "0.1.0"
vendor/mage_flow/app.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Gradio app for MageFlow — text-to-image and instruction-based image editing.
2
+
3
+ python app.py # serve on 0.0.0.0:7860
4
+ python app.py --share --port 7861
5
+
6
+ Each tab has a model preset dropdown (base / rl / turbo) plus a free-form
7
+ "Custom model" box for any Hugging Face repo id or local path. Models load
8
+ lazily on first use and are cached. Notes:
9
+ - By default the presets point at the `microsoft/Mage-Flow*` Hugging Face
10
+ repos (downloaded + cached on first use). Set ``MAGEFLOW_HF_DIR`` to load
11
+ local checkpoint dirs instead.
12
+ - Turbo checkpoints are few-step: use steps=4, cfg=1.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import argparse
17
+ import os
18
+
19
+ import gradio as gr
20
+ from PIL import Image
21
+
22
+ from mage_flow.pipeline import MageFlowPipeline
23
+
24
+ # Default to Hugging Face repo ids; if MAGEFLOW_HF_DIR is set, use local
25
+ # checkpoint dirs under it instead (local dir names match the HF repo basename).
26
+ HF_DIR = os.environ.get("MAGEFLOW_HF_DIR")
27
+
28
+
29
+ def _repo(hf_id: str, local_name: str) -> str:
30
+ return f"{HF_DIR}/{local_name}" if HF_DIR else hf_id
31
+
32
+
33
+ T2I_MODELS = {
34
+ "base": _repo("microsoft/Mage-Flow-Base", "Mage-Flow-Base"),
35
+ "rl": _repo("microsoft/Mage-Flow", "Mage-Flow"),
36
+ "turbo": _repo("microsoft/Mage-Flow-Turbo", "Mage-Flow-Turbo"),
37
+ }
38
+ EDIT_MODELS = {
39
+ "base": _repo("microsoft/Mage-Flow-Edit-Base", "Mage-Flow-Edit-Base"),
40
+ "rl": _repo("microsoft/Mage-Flow-Edit", "Mage-Flow-Edit"),
41
+ "turbo": _repo("microsoft/Mage-Flow-Edit-Turbo", "Mage-Flow-Edit-Turbo"),
42
+ }
43
+
44
+ DEVICE = "cuda"
45
+ _CACHE: dict[str, MageFlowPipeline] = {}
46
+
47
+
48
+ def _get_pipe(repo: str) -> MageFlowPipeline:
49
+ """Load (and cache) a pipeline from a local dir OR a Hugging Face repo id.
50
+
51
+ ``MageFlowPipeline.from_pretrained`` resolves a repo id via
52
+ ``snapshot_download`` automatically, so both are accepted here.
53
+ """
54
+ repo = (repo or "").strip()
55
+ if not repo:
56
+ raise gr.Error("No model specified.")
57
+ if repo not in _CACHE:
58
+ try:
59
+ _CACHE[repo] = MageFlowPipeline.from_pretrained(repo, device=DEVICE)
60
+ except Exception as e: # noqa: BLE001
61
+ raise gr.Error(f"Failed to load model '{repo}': {type(e).__name__}: {e}")
62
+ return _CACHE[repo]
63
+
64
+
65
+ def _resolve(preset_map, model_key, custom_model):
66
+ """Custom repo id / path (if given) overrides the preset dropdown."""
67
+ return (custom_model or "").strip() or preset_map[model_key]
68
+
69
+
70
+ def run_t2i(model_key, custom_model, prompt, neg_prompt, steps, cfg, height, width, seed,
71
+ progress=gr.Progress(track_tqdm=False)):
72
+ if not (prompt or "").strip():
73
+ raise gr.Error("Prompt is empty.")
74
+ repo = _resolve(T2I_MODELS, model_key, custom_model)
75
+ progress(0.1, desc=f"loading {repo} …")
76
+ pipe = _get_pipe(repo)
77
+ progress(0.4, desc="generating …")
78
+ img = pipe.generate(
79
+ [prompt], neg_prompts=[neg_prompt or " "], seeds=[int(seed)],
80
+ steps=int(steps), cfg=float(cfg),
81
+ heights=[int(height)], widths=[int(width)],
82
+ )[0]
83
+ return img
84
+
85
+
86
+ def run_edit(model_key, custom_model, prompt, neg_prompt, ref_img, extra_files, steps, cfg, max_size, seed,
87
+ progress=gr.Progress(track_tqdm=False)):
88
+ if not (prompt or "").strip():
89
+ raise gr.Error("Edit instruction is empty.")
90
+ refs = []
91
+ if ref_img is not None:
92
+ refs.append(ref_img if isinstance(ref_img, Image.Image) else Image.open(ref_img))
93
+ for f in (extra_files or []):
94
+ refs.append(Image.open(f).convert("RGB"))
95
+ if not refs:
96
+ raise gr.Error("Upload at least one reference image.")
97
+ refs = [r.convert("RGB") for r in refs]
98
+ repo = _resolve(EDIT_MODELS, model_key, custom_model)
99
+ progress(0.1, desc=f"loading {repo} …")
100
+ pipe = _get_pipe(repo)
101
+ progress(0.4, desc="editing …")
102
+ out = pipe.edit(
103
+ [prompt], [refs], neg_prompts=[neg_prompt or " "], seeds=[int(seed)],
104
+ steps=int(steps), cfg=float(cfg),
105
+ max_size=int(max_size) if max_size else None,
106
+ )[0]
107
+ return out
108
+
109
+
110
+ _NOTE = (
111
+ "Pick a **preset** (base / rl / turbo) or type a **custom model** — any "
112
+ "Hugging Face repo id (e.g. `microsoft/Mage-Flow-Turbo`) or local path; it "
113
+ "is downloaded and cached on first use. **Turbo** models are few-step: set "
114
+ "**steps=4, cfg=1**."
115
+ )
116
+
117
+ _CUSTOM_PH_T2I = "microsoft/Mage-Flow (repo id or local path — overrides preset)"
118
+ _CUSTOM_PH_EDIT = "microsoft/Mage-Flow-Edit (repo id or local path — overrides preset)"
119
+
120
+
121
+ def build_ui():
122
+ with gr.Blocks(title="MageFlow") as demo:
123
+ gr.Markdown("# MageFlow\nText-to-image generation and instruction-based image editing.")
124
+ gr.Markdown(_NOTE)
125
+
126
+ with gr.Tab("Text → Image"):
127
+ with gr.Row():
128
+ with gr.Column(scale=1):
129
+ t_model = gr.Dropdown(list(T2I_MODELS), value="base", label="Model preset")
130
+ t_custom = gr.Textbox(label="Custom model (optional)", placeholder=_CUSTOM_PH_T2I, lines=1)
131
+ t_prompt = gr.Textbox(label="Prompt", lines=3,
132
+ value="A close-up portrait of an elderly African man with deep wrinkles, wearing a traditional hat, soft natural lighting, ultra realistic.")
133
+ t_neg = gr.Textbox(label="Negative prompt", value=" ", lines=1)
134
+ with gr.Row():
135
+ t_steps = gr.Slider(1, 50, value=30, step=1, label="Steps")
136
+ t_cfg = gr.Slider(1.0, 10.0, value=5.0, step=0.5, label="CFG")
137
+ with gr.Row():
138
+ t_h = gr.Slider(256, 1536, value=1024, step=16, label="Height")
139
+ t_w = gr.Slider(256, 1536, value=1024, step=16, label="Width")
140
+ t_seed = gr.Number(value=42, precision=0, label="Seed")
141
+ t_btn = gr.Button("Generate", variant="primary")
142
+ with gr.Column(scale=1):
143
+ t_out = gr.Image(type="pil", label="Output", height=560)
144
+ # Clear the previous output first so the stale image isn't shown as
145
+ # the result while the new one is still transferring (esp. over a
146
+ # gradio share tunnel, where the image download can lag a few seconds).
147
+ t_btn.click(lambda: None, None, t_out).then(
148
+ run_t2i,
149
+ [t_model, t_custom, t_prompt, t_neg, t_steps, t_cfg, t_h, t_w, t_seed],
150
+ t_out)
151
+
152
+ with gr.Tab("Image Edit"):
153
+ with gr.Row():
154
+ with gr.Column(scale=1):
155
+ e_model = gr.Dropdown(list(EDIT_MODELS), value="base", label="Model preset")
156
+ e_custom = gr.Textbox(label="Custom model (optional)", placeholder=_CUSTOM_PH_EDIT, lines=1)
157
+ e_prompt = gr.Textbox(label="Edit instruction", lines=2,
158
+ value="change the background to a city street")
159
+ e_neg = gr.Textbox(label="Negative prompt", value=" ", lines=1)
160
+ e_ref = gr.Image(type="pil", label="Reference image", height=280,
161
+ value=os.path.join(os.path.dirname(__file__), "assets", "dog.jpg"))
162
+ e_extra = gr.File(file_count="multiple", type="filepath",
163
+ label="Extra references (optional, multi-image edit)")
164
+ with gr.Row():
165
+ e_steps = gr.Slider(1, 50, value=30, step=1, label="Steps")
166
+ e_cfg = gr.Slider(1.0, 10.0, value=5.0, step=0.5, label="CFG")
167
+ e_max = gr.Slider(0, 1536, value=1024, step=16,
168
+ label="Max output side (0 = keep source size)")
169
+ e_seed = gr.Number(value=42, precision=0, label="Seed")
170
+ e_btn = gr.Button("Edit", variant="primary")
171
+ with gr.Column(scale=1):
172
+ e_out = gr.Image(type="pil", label="Output", height=560)
173
+ e_btn.click(lambda: None, None, e_out).then(
174
+ run_edit,
175
+ [e_model, e_custom, e_prompt, e_neg, e_ref, e_extra, e_steps, e_cfg, e_max, e_seed],
176
+ e_out)
177
+ return demo
178
+
179
+
180
+ def main():
181
+ global DEVICE
182
+ ap = argparse.ArgumentParser()
183
+ ap.add_argument("--device", default="cuda")
184
+ ap.add_argument("--host", default="0.0.0.0")
185
+ ap.add_argument("--port", type=int, default=7860)
186
+ ap.add_argument("--share", action="store_true")
187
+ ap.add_argument("--preload", default=None,
188
+ help="comma-separated repo ids / paths to load at startup (else lazy)")
189
+ args = ap.parse_args()
190
+ DEVICE = args.device
191
+ if args.preload:
192
+ for repo in args.preload.split(","):
193
+ _get_pipe(repo.strip())
194
+ build_ui().queue().launch(server_name=args.host, server_port=args.port,
195
+ share=args.share, theme=gr.themes.Soft())
196
+
197
+
198
+ if __name__ == "__main__":
199
+ main()
vendor/mage_flow/inference.py ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """MageFlow command-line inference.
3
+
4
+ Two console scripts share this module:
5
+
6
+ * ``mage-flow`` — text-to-image generation
7
+ * ``mage-flow-edit`` — instruction-based image editing
8
+
9
+ Both are BATCHED: pass several prompts and they are packed into a single
10
+ transformer forward per denoise step. Sample ``i`` uses seed ``--seed + i``.
11
+
12
+ Text-to-image (multiple prompts = a batch)::
13
+
14
+ mage-flow --prompt "a cat holding a sign that says hello" "a red ferrari" \
15
+ --model_path microsoft/Mage-Flow-4B --steps 30 --cfg 5.0 --out ./outputs
16
+
17
+ ``--model_path`` accepts a local repo dir OR a Hugging Face Hub repo id (e.g.
18
+ ``microsoft/Mage-Flow-4B``), downloaded and cached automatically on first use.
19
+
20
+ Mixed resolutions — give one ``--height``/``--width`` per prompt (they are packed
21
+ into a single forward per step regardless of shape)::
22
+
23
+ mage-flow --prompt "a tall waterfall" "a wide desert panorama" \
24
+ --height 2048 512 --width 512 2048 \
25
+ --model_path microsoft/Mage-Flow-4B --out ./outputs
26
+
27
+ Image editing (one ``--ref`` entry per prompt; comma-separate paths for
28
+ multi-image edit)::
29
+
30
+ mage-flow-edit \
31
+ --prompt "把背景改为城市街道" "把这两张图融合在一起" \
32
+ --ref hydrant.png "scene.png,object.png" \
33
+ --model_path /path/Mage-Flow-Edit-4B-Base --out ./outputs
34
+ """
35
+ import argparse
36
+ import os
37
+
38
+ from mage_flow import MageFlowPipeline
39
+
40
+
41
+ def _add_common_args(p):
42
+ p.add_argument("--model_path", required=True,
43
+ help="local diffusers-style repo dir OR a Hugging Face Hub repo id "
44
+ "(e.g. microsoft/Mage-Flow-4B); HF ids are downloaded and cached "
45
+ "automatically on first use")
46
+ p.add_argument("--neg_prompt", default=None,
47
+ help="negative prompt applied to every sample (default: a single space)")
48
+ p.add_argument("--steps", type=int, default=30)
49
+ p.add_argument("--cfg", type=float, default=5.0)
50
+ p.add_argument("--seed", type=int, default=42, help="base seed; sample i uses seed + i")
51
+ p.add_argument("--static_shift", type=float, default=None,
52
+ help="override scheduler shift (default: repo scheduler_config.json, 6.0)")
53
+ p.add_argument("--device", default="cuda")
54
+ p.add_argument("--out", default="./outputs")
55
+
56
+
57
+ def _neg_list(neg_prompt, n):
58
+ return [neg_prompt] * n if neg_prompt is not None else None
59
+
60
+
61
+ def _size_list(vals, n, name, parser):
62
+ """Broadcast one size value to all prompts, or use a per-prompt list."""
63
+ if len(vals) == 1:
64
+ return vals * n
65
+ if len(vals) == n:
66
+ return vals
67
+ parser.error(f"--{name} expects 1 value (applied to all) or {n} values "
68
+ f"(one per prompt); got {len(vals)}")
69
+
70
+
71
+ def main():
72
+ """``mage-flow`` — batched text-to-image generation."""
73
+ p = argparse.ArgumentParser(
74
+ prog="mage-flow", description="MageFlow text-to-image generation.",
75
+ formatter_class=argparse.RawDescriptionHelpFormatter)
76
+ p.add_argument("--prompt", nargs="+", required=True,
77
+ help="one or more prompts; multiple prompts are batched")
78
+ p.add_argument("--height", type=int, nargs="+", default=[1024],
79
+ help="output height (multiple of 16); one value applied to all "
80
+ "prompts, or one value per prompt for mixed resolutions")
81
+ p.add_argument("--width", type=int, nargs="+", default=[1024],
82
+ help="output width (multiple of 16); one value applied to all "
83
+ "prompts, or one value per prompt for mixed resolutions")
84
+ p.add_argument("--prompt_template", default="mage-flow")
85
+ _add_common_args(p)
86
+ args = p.parse_args()
87
+
88
+ os.makedirs(args.out, exist_ok=True)
89
+ pipe = MageFlowPipeline.from_pretrained(args.model_path, args.device)
90
+ n = len(args.prompt)
91
+ imgs = pipe.generate(
92
+ args.prompt,
93
+ neg_prompts=_neg_list(args.neg_prompt, n),
94
+ seeds=[args.seed + i for i in range(n)],
95
+ heights=_size_list(args.height, n, "height", p),
96
+ widths=_size_list(args.width, n, "width", p),
97
+ steps=args.steps, cfg=args.cfg, static_shift=args.static_shift,
98
+ prompt_template=args.prompt_template,
99
+ )
100
+ for i, im in enumerate(imgs):
101
+ path = os.path.join(args.out, f"gen_{i:03d}.png")
102
+ im.save(path)
103
+ print(f"saved {path}")
104
+
105
+
106
+ def main_edit():
107
+ """``mage-flow-edit`` — batched instruction-based image editing."""
108
+ p = argparse.ArgumentParser(
109
+ prog="mage-flow-edit", description="MageFlow instruction-based image editing.",
110
+ formatter_class=argparse.RawDescriptionHelpFormatter)
111
+ p.add_argument("--prompt", nargs="+", required=True,
112
+ help="one or more edit instructions; multiple prompts are batched")
113
+ p.add_argument("--ref", nargs="+", required=True,
114
+ help="reference image(s) for each prompt, positionally aligned with "
115
+ "--prompt; comma-separate paths for multi-image edit "
116
+ "(e.g. --ref a.png 'b.png,c.png')")
117
+ p.add_argument("--max_size", type=int, default=None,
118
+ help="longest side of the output (short side by aspect ratio). "
119
+ "Default: keep each source image's own resolution")
120
+ p.add_argument("--height", type=int, default=None,
121
+ help="explicit output height (use with --width; overrides --max_size)")
122
+ p.add_argument("--width", type=int, default=None,
123
+ help="explicit output width (use with --height; overrides --max_size)")
124
+ p.add_argument("--vl_cond_long_edge", type=int, default=384,
125
+ help="cap the long edge of the reference image fed to the VL text "
126
+ "encoder (matches training preprocessing; the VAE path keeps the "
127
+ "full output resolution). 0 or negative disables the cap")
128
+ p.add_argument("--prompt_template", default="mage-flow-edit")
129
+ _add_common_args(p)
130
+ args = p.parse_args()
131
+
132
+ if len(args.ref) != len(args.prompt):
133
+ p.error(f"--ref count ({len(args.ref)}) must match --prompt count ({len(args.prompt)})")
134
+
135
+ os.makedirs(args.out, exist_ok=True)
136
+ pipe = MageFlowPipeline.from_pretrained(args.model_path, args.device)
137
+ n = len(args.prompt)
138
+ # Each --ref token is one prompt's reference(s); commas split multi-image refs.
139
+ ref_images = [[s.strip() for s in r.split(",") if s.strip()] for r in args.ref]
140
+
141
+ size_kw = {}
142
+ if args.height and args.width:
143
+ size_kw = {"heights": [args.height] * n, "widths": [args.width] * n}
144
+
145
+ outs = pipe.edit(
146
+ args.prompt, ref_images,
147
+ neg_prompts=_neg_list(args.neg_prompt, n),
148
+ seeds=[args.seed + i for i in range(n)],
149
+ max_size=args.max_size, steps=args.steps, cfg=args.cfg,
150
+ static_shift=args.static_shift, prompt_template=args.prompt_template,
151
+ vl_cond_long_edge=args.vl_cond_long_edge,
152
+ **size_kw,
153
+ )
154
+ for i, im in enumerate(outs):
155
+ path = os.path.join(args.out, f"edit_{i:03d}.png")
156
+ im.save(path)
157
+ print(f"saved {path}")
158
+
159
+
160
+ if __name__ == "__main__":
161
+ main()
vendor/mage_flow/models/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .utils import load_model
2
+
3
+ __all__ = ["load_model"]
vendor/mage_flow/models/mage_flow.py ADDED
@@ -0,0 +1,364 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ from einops import rearrange, repeat
7
+ from loguru import logger
8
+ from pydantic import BaseModel, Field
9
+ from torch import Tensor
10
+
11
+ from .modules._attn_backend import set_attn_backend
12
+ from .modules.mage_layers import (
13
+ AdaLayerNormContinuous,
14
+ MageFlowEmbedRope,
15
+ MageFlowTimestepProjEmbeddings,
16
+ MageFlowTransformerBlock,
17
+ RMSNorm,
18
+ )
19
+ from .modules.text_encoder import TextEncoder, qwen3_patch_forward
20
+
21
+
22
+ class ModelConfig(BaseModel):
23
+ static_shift: float = Field(
24
+ default=6.0,
25
+ description="Static shift value for the z-image time-shift schedule (the only "
26
+ "supported schedule). Default: 6.0.",
27
+ )
28
+ vae_path: str = Field(...)
29
+ model_structure: dict = Field(default_factory=dict)
30
+ txt_enc_path: str = Field(...)
31
+ txt_max_length: int = Field(default=4096)
32
+ pretrained_model_name_or_path: str | None = Field(default=None)
33
+ pretrained_full_model_path: str | None = Field(default=None) # Load full model weights (DiT + txt_enc + vae)
34
+ packing: bool = Field(default=False)
35
+ vae_sample_posterior: bool = Field(default=True) # Sample (vs mode) from VAE posterior at encode time (Flux2 + CoD)
36
+ vae_encoder_only: bool = Field(default=False) # Skip loading VAE decoder to save GPU memory (training only, MageVAE)
37
+ compile_vae_encoder: bool = Field(default=False) # torch.compile VAE encoder to reduce CUDA kernel launch overhead
38
+ attn_type: str = Field(
39
+ default="flash2",
40
+ description="Flash-attn backend used by both the DiT (mage_layers) "
41
+ "and the HF text encoder (text_encoder). One of: 'flash2' (default) or 'flash4'.",
42
+ )
43
+
44
+
45
+ @dataclass
46
+ class MageFlowParams:
47
+ in_channels: int
48
+ out_channels: int
49
+ context_in_dim: int
50
+ hidden_size: int
51
+ num_heads: int
52
+ depth: int
53
+ axes_dim: list[int]
54
+ checkpoint: bool
55
+ patch_size: int = 1
56
+
57
+
58
+ class MageFlow(nn.Module):
59
+ def __init__(self, params: MageFlowParams):
60
+ super().__init__()
61
+ self.params = params
62
+ self.checkpoint = params.checkpoint
63
+ self.in_channels = params.in_channels
64
+ self.out_channels = params.out_channels
65
+ self.inner_dim = params.hidden_size # num_attention_heads * attention_head_dim
66
+ self.axes_dim = params.axes_dim
67
+ self.num_attention_heads = params.num_heads
68
+ self.attention_head_dim = self.inner_dim // self.num_attention_heads
69
+ self.patch_size = params.patch_size
70
+ assert sum(self.axes_dim) == self.attention_head_dim
71
+
72
+ self.pos_embed = MageFlowEmbedRope(theta=10000, axes_dim=self.axes_dim, scale_rope=True)
73
+ self.img_in = nn.Linear(self.in_channels, self.inner_dim)
74
+ self.txt_norm = RMSNorm(params.context_in_dim, eps=1e-6)
75
+ self.txt_in = nn.Linear(params.context_in_dim, self.inner_dim)
76
+
77
+ self.time_text_embed = MageFlowTimestepProjEmbeddings(embedding_dim=self.inner_dim)
78
+
79
+ self.transformer_blocks = nn.ModuleList(
80
+ [
81
+ MageFlowTransformerBlock(
82
+ dim=self.inner_dim,
83
+ num_attention_heads=self.num_attention_heads,
84
+ attention_head_dim=self.attention_head_dim,
85
+ )
86
+ for _ in range(params.depth)
87
+ ]
88
+ )
89
+
90
+ self.norm_out = AdaLayerNormContinuous(self.inner_dim, self.inner_dim, elementwise_affine=False, eps=1e-6)
91
+ self.proj_out = nn.Linear(self.inner_dim, self.patch_size * self.patch_size * self.out_channels, bias=True)
92
+
93
+ def forward(
94
+ self,
95
+ img: Tensor,
96
+ txt: Tensor,
97
+ timesteps: Tensor,
98
+ img_shapes=None,
99
+ img_cu_seqlens: Tensor | None = None,
100
+ txt_cu_seqlens: Tensor | None = None,
101
+ attention_kwargs: dict[str, Any] | None = None,
102
+ ) -> Tensor:
103
+ if img.ndim != 3 or txt.ndim != 3:
104
+ raise ValueError("Input img and txt tensors must have 3 dimensions.")
105
+
106
+ # Prepare vision RoPE (msrope); text tokens are not rotated.
107
+ ms_pe = self.pos_embed(img_shapes, device=img.device)
108
+
109
+ img = self.img_in(img)
110
+ txt = self.txt_norm(txt)
111
+
112
+ timesteps = timesteps.to(img.dtype)
113
+ temb = self.time_text_embed(timesteps, img)
114
+
115
+ txt = self.txt_in(txt)
116
+ txt_vec = torch.zeros(txt.shape[0], self.inner_dim, dtype=txt.dtype, device=txt.device)
117
+
118
+ temb = temb + txt_vec
119
+
120
+ attention_kwargs = attention_kwargs or {}
121
+
122
+ for _index_block, block in enumerate(self.transformer_blocks):
123
+ if self.training and self.checkpoint:
124
+ txt, img = torch.utils.checkpoint.checkpoint(
125
+ block,
126
+ img, # hidden_states
127
+ txt, # encoder_hidden_states
128
+ temb, # temb
129
+ ms_pe, # image_rotary_emb
130
+ txt_cu_seqlens, # txt_cu_lens
131
+ img_cu_seqlens, # img_cu_lens
132
+ use_reentrant=False,
133
+ )
134
+
135
+ else:
136
+ txt, img = block(
137
+ hidden_states=img,
138
+ encoder_hidden_states=txt,
139
+ txt_cu_lens=txt_cu_seqlens,
140
+ img_cu_lens=img_cu_seqlens,
141
+ temb=temb,
142
+ image_rotary_emb=ms_pe,
143
+ joint_attention_kwargs=attention_kwargs,
144
+ )
145
+
146
+ # Use only the image part (hidden_states) from the dual-stream blocks
147
+ img = self.norm_out(
148
+ img,
149
+ temb,
150
+ cu_seqlens=img_cu_seqlens,
151
+ )
152
+ img = self.proj_out(img)
153
+ return img
154
+
155
+
156
+ class MageFlowModel(nn.Module):
157
+ def __init__(self, config: ModelConfig):
158
+ super().__init__()
159
+ self.config = config
160
+ set_attn_backend(getattr(config, "attn_type", "flash2"))
161
+ self.patch_text_encoder_forward()
162
+ self.vae = self.load_vae()
163
+ self.transformer = self.load_transformer()
164
+ self.txt_enc = self.load_text_enc()
165
+
166
+ # Optionally override all components from a full model checkpoint (e.g. ema.pt)
167
+ full_path = getattr(self.config, "pretrained_full_model_path", None)
168
+ if full_path is not None:
169
+ import os
170
+
171
+ if os.path.exists(full_path):
172
+ logger.info(f"Loading full model weights from {full_path}")
173
+ sd = torch.load(full_path, map_location="cpu")
174
+ # Handle wrapped EMA format: {'ema_state_dict': ..., ...}
175
+ if isinstance(sd, dict) and "ema_state_dict" in sd:
176
+ sd = sd["ema_state_dict"]
177
+ missing, unexpected = self.load_state_dict(sd, strict=False)
178
+ if missing:
179
+ logger.warning(f"Full model load missing keys ({len(missing)}): {missing[:5]}...")
180
+ if unexpected:
181
+ logger.warning(f"Full model load unexpected keys ({len(unexpected)}): {unexpected[:5]}...")
182
+ logger.info("Full model weights loaded successfully.")
183
+ else:
184
+ logger.warning(f"pretrained_full_model_path not found: {full_path}")
185
+
186
+ # Freeze VAE and Text Encoder
187
+ self.vae.requires_grad_(False)
188
+
189
+ # Drop VAE decoder to save GPU memory (training only, decoder unused during training)
190
+ if self.config.vae_encoder_only:
191
+ from .modules.mage_vae import MageVAE
192
+ if isinstance(self.vae, MageVAE):
193
+ decoder_params = sum(p.numel() for p in self.vae.decoder_model.parameters()) / 1e6
194
+ self.vae.decoder_model = None
195
+ elif hasattr(self.vae, "decoder"):
196
+ decoder_params = sum(p.numel() for p in self.vae.decoder.parameters()) / 1e6
197
+ self.vae.decoder = None
198
+ else:
199
+ decoder_params = 0
200
+ logger.info(f"vae_encoder_only=True: dropped VAE decoder ({decoder_params:.1f}M params) to save memory")
201
+
202
+ # NOTE: VAE encoder torch.compile() is deferred to
203
+ # maybe_compile_vae_encoder(), called after checkpoint load. Reason:
204
+ # avoid wasted compile work before load_checkpoint overwrites weights.
205
+ # The save-side _unwrap_compiled_submodules guard in DeepSpeedTrainer
206
+ # is a belt-and-suspenders defense against any future code that
207
+ # re-introduces the function-style ``module = torch.compile(module)``
208
+ # pattern (which does pollute state_dict with ``_orig_mod.``).
209
+
210
+ # Text encoder is always frozen (inference only).
211
+ self.txt_enc.requires_grad_(False)
212
+ logger.info(
213
+ f"{sum([p.numel() for p in self.transformer.parameters() if p.requires_grad]) / 1000000} M parameters"
214
+ )
215
+
216
+ def patch_text_encoder_forward(self):
217
+ qwen3_patch_forward()
218
+ logger.info("Patched Qwen3-VL text encoder forward methods")
219
+
220
+ def maybe_compile_vae_encoder(self) -> None:
221
+ """Compile the VAE encoder with torch.compile() to fuse small ops and
222
+ reduce CUDA kernel launch overhead.
223
+
224
+ Uses ``nn.Module.compile()`` (in-place) for the encoder so the module
225
+ hierarchy and parameter names are unchanged — ``state_dict()`` keeps
226
+ clean keys (no ``_orig_mod.`` prefix), and checkpoints stay
227
+ interchangeable with the non-compiled path.
228
+
229
+ For the MageVAE branch we still assign ``torch.compile(...)`` to a
230
+ method (``_encode_moments``); methods aren't ``nn.Module``s so this
231
+ does not pollute ``state_dict()``.
232
+
233
+ Idempotent: safe to call multiple times; already-compiled modules are
234
+ detected and skipped.
235
+ """
236
+ if not getattr(self.config, "compile_vae_encoder", False):
237
+ return
238
+ torch.set_float32_matmul_precision("high")
239
+ from .modules.mage_vae import MageVAE
240
+ if isinstance(self.vae, MageVAE):
241
+ fn = self.vae._encode_moments
242
+ if hasattr(fn, "_torchdynamo_orig_callable") or hasattr(fn, "_orig_mod"):
243
+ return # already compiled
244
+ self.vae._encode_moments = torch.compile(fn, dynamic=True)
245
+ logger.info("compile_vae_encoder=True: compiled MageVAE._encode_moments")
246
+ elif hasattr(self.vae, "encoder"):
247
+ if getattr(self.vae.encoder, "_compiled_call_impl", None) is not None:
248
+ return # already compiled
249
+ self.vae.encoder.compile()
250
+ logger.info("compile_vae_encoder=True: compiled VAE encoder (in-place)")
251
+
252
+ def load_text_enc(self):
253
+ return TextEncoder(
254
+ model_name=self.config.txt_enc_path,
255
+ version=self.config.txt_enc_path,
256
+ tokenizer_max_length=self.config.txt_max_length,
257
+ torch_dtype=torch.bfloat16,
258
+ prompt_template=None,
259
+ dit_structure=self.config.model_structure,
260
+ use_packed_text_infer=self.config.packing,
261
+ attn_type=getattr(self.config, "attn_type", "flash2"),
262
+ )
263
+
264
+ def load_vae(self):
265
+ from .modules.mage_vae import MageVAE
266
+ return MageVAE(
267
+ ckpt_path=self.config.vae_path,
268
+ sample_posterior=self.config.vae_sample_posterior,
269
+ )
270
+
271
+ def load_transformer(self):
272
+ # Imported lazily to avoid a circular import: ``utils`` imports MageFlow /
273
+ # MageFlowParams from this module.
274
+ from .utils import load_model
275
+ return load_model(
276
+ dit_structure=self.config.model_structure,
277
+ pretrain_path=self.config.pretrained_model_name_or_path,
278
+ )
279
+
280
+ def compile(self):
281
+ self.transformer.compile()
282
+
283
+ def compute_vae_encodings(
284
+ self,
285
+ pixel_values: torch.Tensor | list[torch.Tensor],
286
+ with_ids: bool = True,
287
+ ):
288
+ if isinstance(pixel_values, list):
289
+ # All same resolution → batch encode via the tensor path
290
+ if len(pixel_values) > 1 and len({img.shape for img in pixel_values}) == 1:
291
+ stacked = torch.stack(pixel_values, dim=0)
292
+ result = self.compute_vae_encodings(stacked, with_ids=with_ids)
293
+ # Repack from [N, L, C] batch format to [1, N*L, C] packed format
294
+ if with_ids:
295
+ model_input, img_shapes, img_ids = result
296
+ model_input = model_input.reshape(1, -1, model_input.shape[-1])
297
+ img_ids = img_ids.reshape(1, -1, img_ids.shape[-1])
298
+ return model_input, img_shapes, img_ids
299
+ model_input, img_shapes = result
300
+ model_input = model_input.reshape(1, -1, model_input.shape[-1])
301
+ return model_input, img_shapes
302
+
303
+ # Packed / variable-size images
304
+ model_inputs = []
305
+ img_shapes = []
306
+ img_ids_list = []
307
+
308
+ def _append(latents):
309
+ _, _, h, w = latents.shape
310
+ img_shapes.append([(1, h, w)])
311
+ model_inputs.append(rearrange(latents, "b c h w -> b (h w) c").squeeze(0))
312
+ if with_ids:
313
+ ids = torch.zeros(h, w, 3, device=latents.device)
314
+ ids[..., 1] = ids[..., 1] + torch.arange(h, device=latents.device)[:, None]
315
+ ids[..., 2] = ids[..., 2] + torch.arange(w, device=latents.device)[None, :]
316
+ img_ids_list.append(rearrange(ids, "h w c -> (h w) c"))
317
+
318
+ # MageVAE encoder is launch-bound on B=1; group same-shape images
319
+ # in the pack into one batched encode call.
320
+ if len(pixel_values) > 1:
321
+ groups: dict[tuple[int, int], list[int]] = {}
322
+ for i, img in enumerate(pixel_values):
323
+ key = (int(img.shape[-2]), int(img.shape[-1]))
324
+ groups.setdefault(key, []).append(i)
325
+ latents_per_idx = [None] * len(pixel_values)
326
+ for (h, w), idxs in groups.items():
327
+ batch = torch.stack([pixel_values[i] for i in idxs], dim=0)
328
+ batch = batch.to(memory_format=torch.contiguous_format).float()
329
+ batch = batch.to(self.vae.device, dtype=self.vae.dtype)
330
+ with torch.no_grad():
331
+ lat = self.vae.encode(batch) # [B, 128, H/16, W/16]
332
+ for j, i in enumerate(idxs):
333
+ latents_per_idx[i] = lat[j:j + 1]
334
+ for latents in latents_per_idx:
335
+ _append(latents)
336
+ else:
337
+ for img in pixel_values:
338
+ img = img.unsqueeze(0).to(memory_format=torch.contiguous_format).float()
339
+ img = img.to(self.vae.device, dtype=self.vae.dtype)
340
+ with torch.no_grad():
341
+ latents = self.vae.encode(img) # [1, 128, H/16, W/16]
342
+ _append(latents)
343
+
344
+ model_input = torch.cat(model_inputs, dim=0).unsqueeze(0)
345
+ if with_ids:
346
+ img_ids = torch.cat(img_ids_list, dim=0).unsqueeze(0)
347
+ return model_input, img_shapes, img_ids
348
+ return model_input, img_shapes
349
+
350
+ # Tensor (padded batch)
351
+ pixel_values = pixel_values.to(memory_format=torch.contiguous_format).float()
352
+ pixel_values = pixel_values.to(self.vae.device, dtype=self.vae.dtype)
353
+ with torch.no_grad():
354
+ model_input = self.vae.encode(pixel_values) # [B, 128, H/16, W/16]
355
+ bs, c, h, w = model_input.shape
356
+ img_shapes = [[(1, h, w)]] * bs
357
+ model_input = rearrange(model_input, "b c h w -> b (h w) c")
358
+ if with_ids:
359
+ img_ids = torch.zeros(h, w, 3, device=model_input.device)
360
+ img_ids[..., 1] = img_ids[..., 1] + torch.arange(h, device=model_input.device)[:, None]
361
+ img_ids[..., 2] = img_ids[..., 2] + torch.arange(w, device=model_input.device)[None, :]
362
+ img_ids = repeat(img_ids, "h w c -> b (h w) c", b=bs)
363
+ return model_input, img_shapes, img_ids
364
+ return model_input, img_shapes
vendor/mage_flow/models/modules/__init__.py ADDED
File without changes
vendor/mage_flow/models/modules/_attn_backend.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Attention backend shim — switchable between Flash Attention 2 and 4.
2
+
3
+ Exports a single ``flash_attn_varlen_func`` with the FA2 calling convention.
4
+ The underlying kernel is selected at runtime via ``set_attn_backend(name)``
5
+ (default: ``"flash2"``). The selected kernel is resolved lazily on the first
6
+ call so model-config-driven selection (which happens after this module is
7
+ imported) takes effect.
8
+
9
+ Modules that previously did ``from flash_attn import flash_attn_varlen_func``
10
+ should import from here instead.
11
+
12
+ For the FA4 path, calling-convention differences are normalised:
13
+
14
+ * ``window_size=(-1, -1)`` (FA2 "no window") -> ``(None, None)`` (FA4).
15
+ * ``block_table`` -> ``page_table``.
16
+ * FA4's optional ``(out, lse)`` tuple return is unwrapped to ``out``.
17
+ * ``dropout_p>0`` / ``alibi_slopes`` / ``return_attn_probs`` raise on FA4.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import Any, Callable
23
+
24
+ _FA2_ALIASES = {"flash2", "fa2", "flash_attention_2", "flash_attn_2"}
25
+ _FA4_ALIASES = {"flash4", "fa4", "flash_attention_4", "flash_attn_4"}
26
+ _SDPA_ALIASES = {"sdpa", "torch_sdpa", "scaled_dot_product_attention"}
27
+
28
+ _BACKEND: str = "flash2"
29
+ _RESOLVED_FN: Callable[..., Any] | None = None
30
+
31
+
32
+ def _normalize(name: str) -> str:
33
+ n = name.lower().strip()
34
+ if n in _FA2_ALIASES:
35
+ return "flash2"
36
+ if n in _FA4_ALIASES:
37
+ return "flash4"
38
+ if n in _SDPA_ALIASES:
39
+ return "sdpa"
40
+ raise ValueError(
41
+ f"Unknown attention backend {name!r}; expected one of "
42
+ f"{sorted(_FA2_ALIASES | _FA4_ALIASES | _SDPA_ALIASES)}"
43
+ )
44
+
45
+
46
+ def set_attn_backend(name: str) -> None:
47
+ """Select the flash-attn backend used by ``flash_attn_varlen_func``.
48
+
49
+ Safe to call multiple times; clears the cached resolution on change.
50
+ """
51
+ global _BACKEND, _RESOLVED_FN
52
+ new = _normalize(name)
53
+ if new != _BACKEND:
54
+ _RESOLVED_FN = None
55
+ _BACKEND = new
56
+
57
+
58
+
59
+ def _resolve_fa2() -> Callable[..., Any]:
60
+ from flash_attn import flash_attn_varlen_func as _fn
61
+ return _fn
62
+
63
+
64
+ def _resolve_fa4() -> Callable[..., Any]:
65
+ from flash_attn.cute import flash_attn_varlen_func as _fa4_fn
66
+
67
+ def _fa4_wrapper(
68
+ q,
69
+ k,
70
+ v,
71
+ cu_seqlens_q=None,
72
+ cu_seqlens_k=None,
73
+ max_seqlen_q=None,
74
+ max_seqlen_k=None,
75
+ dropout_p: float = 0.0,
76
+ softmax_scale=None,
77
+ causal: bool = False,
78
+ window_size=(-1, -1),
79
+ softcap: float = 0.0,
80
+ alibi_slopes=None,
81
+ deterministic: bool = False,
82
+ return_attn_probs: bool = False,
83
+ block_table=None,
84
+ **_unused: Any,
85
+ ):
86
+ if dropout_p and dropout_p > 0:
87
+ raise NotImplementedError("FA4 backend does not support dropout_p>0")
88
+ if alibi_slopes is not None:
89
+ raise NotImplementedError("FA4 backend does not support alibi_slopes")
90
+ if return_attn_probs:
91
+ raise NotImplementedError("FA4 backend does not support return_attn_probs")
92
+
93
+ win_l, win_r = window_size
94
+ if win_l == -1:
95
+ win_l = None
96
+ if win_r == -1:
97
+ win_r = None
98
+
99
+ out = _fa4_fn(
100
+ q,
101
+ k,
102
+ v,
103
+ cu_seqlens_q=cu_seqlens_q,
104
+ cu_seqlens_k=cu_seqlens_k,
105
+ max_seqlen_q=max_seqlen_q,
106
+ max_seqlen_k=max_seqlen_k,
107
+ softmax_scale=softmax_scale,
108
+ causal=causal,
109
+ window_size=(win_l, win_r),
110
+ softcap=softcap,
111
+ deterministic=deterministic,
112
+ page_table=block_table,
113
+ return_lse=False,
114
+ )
115
+ if isinstance(out, tuple):
116
+ out = out[0]
117
+ return out
118
+
119
+ return _fa4_wrapper
120
+
121
+
122
+ def _resolve_sdpa() -> Callable[..., Any]:
123
+ """FA2 varlen → per-sequence torch.SDPA fallback.
124
+
125
+ Use when flash-attn is unavailable (e.g. CUDA 13 has no prebuilt wheel
126
+ and source build is brittle). Slower than FA2 (one SDPA dispatch per
127
+ sequence), but functionally equivalent for the dense / causal / no-alibi
128
+ paths mageflow actually uses. Window / softcap / alibi / paged-attn /
129
+ return_attn_probs are not supported and will raise.
130
+ """
131
+ import torch
132
+ import torch.nn.functional as F
133
+
134
+ def _sdpa_wrapper(
135
+ q,
136
+ k,
137
+ v,
138
+ cu_seqlens_q=None,
139
+ cu_seqlens_k=None,
140
+ max_seqlen_q=None,
141
+ max_seqlen_k=None,
142
+ dropout_p: float = 0.0,
143
+ softmax_scale=None,
144
+ causal: bool = False,
145
+ window_size=(-1, -1),
146
+ softcap: float = 0.0,
147
+ alibi_slopes=None,
148
+ deterministic: bool = False,
149
+ return_attn_probs: bool = False,
150
+ block_table=None,
151
+ **_unused: Any,
152
+ ):
153
+ if dropout_p and dropout_p > 0:
154
+ raise NotImplementedError("SDPA backend does not support dropout_p>0")
155
+ if alibi_slopes is not None:
156
+ raise NotImplementedError("SDPA backend does not support alibi_slopes")
157
+ if return_attn_probs:
158
+ raise NotImplementedError("SDPA backend does not support return_attn_probs")
159
+ if softcap and softcap > 0:
160
+ raise NotImplementedError("SDPA backend does not support softcap")
161
+ if window_size not in ((-1, -1), (None, None), (0, 0)):
162
+ raise NotImplementedError(
163
+ f"SDPA backend does not support sliding window (got {window_size})"
164
+ )
165
+ if block_table is not None:
166
+ raise NotImplementedError("SDPA backend does not support paged attention")
167
+ if cu_seqlens_q is None or cu_seqlens_k is None:
168
+ raise ValueError("SDPA backend requires cu_seqlens_q and cu_seqlens_k")
169
+
170
+ # GQA: FA2 broadcasts k/v across query head groups natively; torch SDPA
171
+ # does not (the q vs k head-dim mismatch is the AssertionError "tensor
172
+ # a (32) must match tensor b (8) at non-singleton dimension 1" we'd see
173
+ # otherwise). Repeat k/v along the head dim to match q before the loop.
174
+ n_heads_q = q.shape[1]
175
+ n_heads_kv = k.shape[1]
176
+ if n_heads_q != n_heads_kv:
177
+ if n_heads_q % n_heads_kv != 0:
178
+ raise ValueError(
179
+ f"SDPA backend GQA expansion requires q heads ({n_heads_q}) "
180
+ f"to be divisible by k/v heads ({n_heads_kv})"
181
+ )
182
+ repeat = n_heads_q // n_heads_kv
183
+ k = k.repeat_interleave(repeat, dim=1)
184
+ v = v.repeat_interleave(repeat, dim=1)
185
+
186
+ # q/k/v: (total_tokens, nheads, head_dim). Dispatch SDPA per sequence,
187
+ # then concat. Python-level loop is fine since nseq is small (one per
188
+ # image in the pack) and image-gen latency is dominated by sampling.
189
+ cu_q = cu_seqlens_q.tolist()
190
+ cu_k = cu_seqlens_k.tolist()
191
+ outs = []
192
+ for qs, qe, ks, ke in zip(cu_q[:-1], cu_q[1:], cu_k[:-1], cu_k[1:]):
193
+ # (s, h, d) → (1, h, s, d)
194
+ q_i = q[qs:qe].transpose(0, 1).unsqueeze(0)
195
+ k_i = k[ks:ke].transpose(0, 1).unsqueeze(0)
196
+ v_i = v[ks:ke].transpose(0, 1).unsqueeze(0)
197
+ out_i = F.scaled_dot_product_attention(
198
+ q_i,
199
+ k_i,
200
+ v_i,
201
+ attn_mask=None,
202
+ dropout_p=0.0,
203
+ is_causal=causal,
204
+ scale=softmax_scale,
205
+ )
206
+ # (1, h, s, d) → (s, h, d)
207
+ outs.append(out_i.squeeze(0).transpose(0, 1))
208
+ return torch.cat(outs, dim=0).contiguous()
209
+
210
+ return _sdpa_wrapper
211
+
212
+
213
+ def _resolve() -> Callable[..., Any]:
214
+ global _RESOLVED_FN
215
+ if _RESOLVED_FN is None:
216
+ if _BACKEND == "flash4":
217
+ _RESOLVED_FN = _resolve_fa4()
218
+ elif _BACKEND == "sdpa":
219
+ _RESOLVED_FN = _resolve_sdpa()
220
+ else:
221
+ _RESOLVED_FN = _resolve_fa2()
222
+ return _RESOLVED_FN
223
+
224
+
225
+ def flash_attn_varlen_func(*args, **kwargs):
226
+ return _resolve()(*args, **kwargs)
227
+
228
+
229
+ __all__ = ["flash_attn_varlen_func", "set_attn_backend"]
vendor/mage_flow/models/modules/mage_latent.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import os
5
+ from math import erfc, sqrt
6
+
7
+ import numpy as np
8
+ import torch
9
+
10
+ DEFAULT_GS_PAYLOAD = "MageFlow"
11
+
12
+
13
+ _ENV_KEY = "MAGEFLOW_GS_KEY"
14
+ _ENV_KEYFILE = "MAGEFLOW_GS_KEY_FILE"
15
+ _DEFAULT_KEYFILE = os.path.expanduser("~/.mageflow/gs_key")
16
+ DEFAULT_GS_KEY = 20260720
17
+
18
+ # Payload length in bits. Short + heavily replicated across the latent.
19
+ _MSG_BITS = 256
20
+
21
+
22
+ def _key_to_int(value) -> int:
23
+ """Normalize a key (int / digit-string / passphrase) to a non-negative int.
24
+
25
+ A pure integer or all-digits string is used directly; anything else is
26
+ treated as a passphrase and hashed (SHA-256) into a 256-bit integer.
27
+ """
28
+ if isinstance(value, int):
29
+ return abs(value)
30
+ s = str(value).strip()
31
+ if not s:
32
+ raise ValueError("empty Gaussian-Shading key")
33
+ if s.lstrip("-").isdigit():
34
+ return abs(int(s))
35
+ return int.from_bytes(hashlib.sha256(s.encode()).digest(), "big")
36
+
37
+
38
+ def resolve_gs_key(explicit=None):
39
+ if explicit is not None:
40
+ return _key_to_int(explicit)
41
+ env = os.environ.get(_ENV_KEY)
42
+ if env and env.strip():
43
+ return _key_to_int(env)
44
+ keyfile = os.environ.get(_ENV_KEYFILE) or _DEFAULT_KEYFILE
45
+ try:
46
+ with open(keyfile) as fh:
47
+ content = fh.read().strip()
48
+ if content:
49
+ return _key_to_int(content)
50
+ except OSError:
51
+ pass
52
+ return _key_to_int(DEFAULT_GS_KEY)
53
+
54
+
55
+ def _payload_to_bits(payload: str, n_bits: int = _MSG_BITS) -> np.ndarray:
56
+ """Deterministically expand an arbitrary string into an ``n_bits`` bit vector."""
57
+ out: list[int] = []
58
+ counter = 0
59
+ while len(out) < n_bits:
60
+ digest = hashlib.sha256(f"{payload}:{counter}".encode()).digest()
61
+ for byte in digest:
62
+ for k in range(8):
63
+ out.append((byte >> k) & 1)
64
+ counter += 1
65
+ return np.asarray(out[:n_bits], dtype=np.int64)
66
+
67
+
68
+ def _pad_and_pos(n: int, key, n_bits: int = _MSG_BITS):
69
+ """Key-seeded per-entry XOR pad and message-index map (length ``n``)."""
70
+ rng = np.random.default_rng(_key_to_int(key))
71
+ pad = rng.integers(0, 2, size=n).astype(np.int64) # XOR mask
72
+ pos = rng.integers(0, n_bits, size=n).astype(np.int64) # msg index per entry
73
+ return pad, pos
74
+
75
+
76
+ def encode_noise(shape, *, key,
77
+ seed: int = 0, device=None, dtype=torch.bfloat16) -> torch.Tensor:
78
+ C, H, W = shape
79
+ n = C * H * W
80
+ msg = _payload_to_bits(DEFAULT_GS_PAYLOAD)
81
+ pad, pos = _pad_and_pos(n, key)
82
+ target_half = (msg[pos] ^ pad).astype(np.float64) # {0,1} per entry
83
+
84
+ gen = torch.Generator(device="cpu").manual_seed(int(seed) & 0x7FFFFFFF)
85
+ u = torch.rand(n, generator=gen, dtype=torch.float64) # U(0,1) magnitudes
86
+ half = torch.from_numpy(target_half)
87
+ arg = ((half + u) / 2.0).clamp(1e-6, 1.0 - 1e-6)
88
+ z = torch.special.ndtri(arg) # inverse normal CDF
89
+ z = z.reshape(1, C, H, W)
90
+ return z.to(device=device, dtype=dtype)
91
+
92
+
93
+ def decode_bits(noise: torch.Tensor, *, key) -> dict:
94
+ z = noise.detach().float().reshape(-1).cpu()
95
+ n = int(z.numel())
96
+ msg = _payload_to_bits(DEFAULT_GS_PAYLOAD)
97
+ pad, pos = _pad_and_pos(n, key)
98
+
99
+ observed_half = (z > 0).numpy().astype(np.int64) # sign -> half
100
+ expected_half = (msg[pos] ^ pad)
101
+ matches = int((observed_half == expected_half).sum())
102
+ raw_acc = matches / n
103
+
104
+ # Recover payload by majority vote of each entry's implied message bit.
105
+ implied = observed_half ^ pad # estimate of m[pos]
106
+ votes = np.zeros((_MSG_BITS, 2), dtype=np.int64)
107
+ np.add.at(votes, (pos, implied), 1)
108
+ msg_hat = votes.argmax(axis=1)
109
+ msg_acc = float((msg_hat == msg).mean())
110
+
111
+ # One-sided significance under Binomial(n, 0.5) via normal approximation.
112
+ z_score = (matches - 0.5 * n) / (0.5 * sqrt(n))
113
+ pvalue = 0.5 * erfc(z_score / sqrt(2))
114
+
115
+ return {
116
+ "raw_acc": raw_acc, "msg_acc": msg_acc, "matches": matches, "n": n,
117
+ "z_score": z_score, "pvalue": pvalue, "present": pvalue < 1e-6,
118
+ "msg_hat": msg_hat, "msg": msg,
119
+ }
vendor/mage_flow/models/modules/mage_layers.py ADDED
@@ -0,0 +1,725 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from typing import Any
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ import torch.nn.functional as F
7
+ from diffusers.models.attention import FeedForward
8
+ from diffusers.models.embeddings import TimestepEmbedding
9
+ from diffusers.models.normalization import RMSNorm
10
+ from ._attn_backend import flash_attn_varlen_func
11
+ from torch import Tensor
12
+ from torch._dynamo import allow_in_graph as maybe_allow_in_graph
13
+
14
+
15
+ def apply_rotary_emb_mageflow(x: torch.Tensor, freqs_cis: torch.Tensor) -> torch.Tensor:
16
+ """Apply complex rotary embeddings to `x` ([B, S, H, D]) using `freqs_cis`
17
+ (the MageFlowEmbedRope 2D multi-scale RoPE, adjacent-pair complex convention)."""
18
+ x_rotated = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
19
+ freqs_cis = freqs_cis.unsqueeze(1)
20
+ x_out = torch.view_as_real(x_rotated * freqs_cis).flatten(-2)
21
+ return x_out.type_as(x)
22
+
23
+
24
+ def get_timestep_embedding(
25
+ timesteps: torch.Tensor,
26
+ embedding_dim: int,
27
+ flip_sin_to_cos: bool = False,
28
+ downscale_freq_shift: float = 1,
29
+ scale: float = 1,
30
+ max_period: int = 10000,
31
+ ) -> torch.Tensor:
32
+ """Sinusoidal timestep embeddings (DDPM convention).
33
+
34
+ NOTE: kept vendored (not diffusers') because the frequency table is
35
+ downcast to ``timesteps.dtype`` (bf16) here — the model was trained with
36
+ this exact bf16 rounding, so diffusers' fp32 variant produces a slightly
37
+ different embedding and degrades outputs.
38
+ """
39
+ assert len(timesteps.shape) == 1, "Timesteps should be a 1d-array"
40
+
41
+ half_dim = embedding_dim // 2
42
+ exponent = -math.log(max_period) * torch.arange(start=0, end=half_dim, dtype=torch.float32, device=timesteps.device)
43
+ exponent = exponent / (half_dim - downscale_freq_shift)
44
+
45
+ emb = torch.exp(exponent).to(timesteps.dtype)
46
+ emb = timesteps[:, None].float() * emb[None, :]
47
+
48
+ # scale embeddings
49
+ emb = scale * emb
50
+
51
+ # concat sine and cosine embeddings
52
+ emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1)
53
+
54
+ # flip sine and cosine embeddings
55
+ if flip_sin_to_cos:
56
+ emb = torch.cat([emb[:, half_dim:], emb[:, :half_dim]], dim=-1)
57
+
58
+ # zero pad
59
+ if embedding_dim % 2 == 1:
60
+ emb = torch.nn.functional.pad(emb, (0, 1, 0, 0))
61
+ return emb
62
+
63
+
64
+ class Timesteps(nn.Module):
65
+ def __init__(
66
+ self,
67
+ num_channels: int,
68
+ flip_sin_to_cos: bool,
69
+ downscale_freq_shift: float,
70
+ scale: int = 1,
71
+ ):
72
+ super().__init__()
73
+ self.num_channels = num_channels
74
+ self.flip_sin_to_cos = flip_sin_to_cos
75
+ self.downscale_freq_shift = downscale_freq_shift
76
+ self.scale = scale
77
+
78
+ def forward(self, timesteps: torch.Tensor) -> torch.Tensor:
79
+ t_emb = get_timestep_embedding(
80
+ timesteps,
81
+ self.num_channels,
82
+ flip_sin_to_cos=self.flip_sin_to_cos,
83
+ downscale_freq_shift=self.downscale_freq_shift,
84
+ scale=self.scale,
85
+ )
86
+ return t_emb
87
+
88
+
89
+ class MageFlowTimestepProjEmbeddings(nn.Module):
90
+ def __init__(self, embedding_dim):
91
+ super().__init__()
92
+
93
+ self.time_proj = Timesteps(num_channels=256, flip_sin_to_cos=True, downscale_freq_shift=0, scale=1000)
94
+ self.timestep_embedder = TimestepEmbedding(in_channels=256, time_embed_dim=embedding_dim)
95
+
96
+ def forward(self, timestep, hidden_states):
97
+ timesteps_proj = self.time_proj(timestep)
98
+ timesteps_emb = self.timestep_embedder(timesteps_proj.to(dtype=hidden_states.dtype)) # (N, D)
99
+
100
+ conditioning = timesteps_emb
101
+
102
+ return conditioning
103
+
104
+
105
+ class MageFlowEmbedRope(nn.Module):
106
+ def __init__(self, theta: int, axes_dim: list[int], scale_rope=False):
107
+ super().__init__()
108
+ self.theta = theta
109
+ self.axes_dim = axes_dim
110
+ pos_index = torch.arange(4096)
111
+ neg_index = torch.arange(4096).flip(0) * -1 - 1
112
+ self.pos_freqs = torch.cat(
113
+ [
114
+ self.rope_params(pos_index, self.axes_dim[0], self.theta),
115
+ self.rope_params(pos_index, self.axes_dim[1], self.theta),
116
+ self.rope_params(pos_index, self.axes_dim[2], self.theta),
117
+ ],
118
+ dim=1,
119
+ )
120
+ self.neg_freqs = torch.cat(
121
+ [
122
+ self.rope_params(neg_index, self.axes_dim[0], self.theta),
123
+ self.rope_params(neg_index, self.axes_dim[1], self.theta),
124
+ self.rope_params(neg_index, self.axes_dim[2], self.theta),
125
+ ],
126
+ dim=1,
127
+ )
128
+
129
+ # DO NOT USING REGISTER BUFFER HERE, IT WILL CAUSE COMPLEX NUMBERS LOSE ITS IMAGINARY PART
130
+ self.scale_rope = scale_rope
131
+ self.video_freq_cache = {}
132
+
133
+ def rope_params(self, index, dim, theta=10000):
134
+ """
135
+ Args:
136
+ index: [0, 1, 2, 3] 1D Tensor representing the position index of the token
137
+ """
138
+ assert dim % 2 == 0
139
+ freqs = torch.outer(
140
+ index,
141
+ 1.0 / torch.pow(theta, torch.arange(0, dim, 2).to(torch.float32).div(dim)),
142
+ )
143
+ freqs = torch.polar(torch.ones_like(freqs), freqs)
144
+ return freqs
145
+
146
+ def forward(
147
+ self,
148
+ video_fhw: tuple[int, int, int] | list[tuple[int, int, int]],
149
+ device: torch.device,
150
+ max_img_len: int = None,
151
+ ) -> torch.Tensor:
152
+ """Compute the vision RoPE frequencies (`vid_freqs`) for the packed image
153
+ tokens. Text tokens are NOT rotated, so no text RoPE is computed.
154
+
155
+ Args:
156
+ video_fhw (`Tuple[int, int, int]` or `List[Tuple[int, int, int]]`):
157
+ A list of 3 integers [frame, height, width] representing the shape of the video.
158
+ device: (`torch.device`):
159
+ The device on which to perform the RoPE computation.
160
+ """
161
+ if self.pos_freqs.device != device:
162
+ self.pos_freqs = self.pos_freqs.to(device)
163
+ self.neg_freqs = self.neg_freqs.to(device)
164
+
165
+ if isinstance(video_fhw, list):
166
+ video_fhw = video_fhw[0]
167
+ if not isinstance(video_fhw, list):
168
+ video_fhw = [video_fhw]
169
+
170
+ vid_freqs = []
171
+ for idx, fhw in enumerate(video_fhw):
172
+ frame, height, width = fhw
173
+ # RoPE frequencies are cached manually
174
+ key = (frame, height, width, idx)
175
+ if key not in self.video_freq_cache:
176
+ self.video_freq_cache[key] = self._compute_video_freqs(frame, height, width, idx)
177
+ vid_freqs.append(self.video_freq_cache[key].to(device))
178
+
179
+ vid_freqs = torch.cat(vid_freqs, dim=0)
180
+
181
+ if max_img_len is not None and vid_freqs.shape[0] < max_img_len:
182
+ pad_len = max_img_len - vid_freqs.shape[0]
183
+ vid_freqs = torch.nn.functional.pad(vid_freqs, (0, 0, 0, pad_len))
184
+
185
+ return vid_freqs
186
+
187
+ def _compute_video_freqs(self, frame: int, height: int, width: int, idx: int = 0) -> torch.Tensor:
188
+ seq_lens = frame * height * width
189
+ freqs_pos = self.pos_freqs.split([x // 2 for x in self.axes_dim], dim=1)
190
+ freqs_neg = self.neg_freqs.split([x // 2 for x in self.axes_dim], dim=1)
191
+
192
+ freqs_frame = freqs_pos[0][idx : idx + frame].view(frame, 1, 1, -1).expand(frame, height, width, -1)
193
+ if self.scale_rope:
194
+ freqs_height = torch.cat(
195
+ [freqs_neg[1][-(height - height // 2) :], freqs_pos[1][: height // 2]],
196
+ dim=0,
197
+ )
198
+ freqs_height = freqs_height.view(1, height, 1, -1).expand(frame, height, width, -1)
199
+ freqs_width = torch.cat(
200
+ [freqs_neg[2][-(width - width // 2) :], freqs_pos[2][: width // 2]],
201
+ dim=0,
202
+ )
203
+ freqs_width = freqs_width.view(1, 1, width, -1).expand(frame, height, width, -1)
204
+ else:
205
+ freqs_height = freqs_pos[1][:height].view(1, height, 1, -1).expand(frame, height, width, -1)
206
+ freqs_width = freqs_pos[2][:width].view(1, 1, width, -1).expand(frame, height, width, -1)
207
+
208
+ freqs = torch.cat([freqs_frame, freqs_height, freqs_width], dim=-1).reshape(seq_lens, -1)
209
+ return freqs.clone().contiguous()
210
+
211
+
212
+ class Attention(nn.Module):
213
+ def __init__(
214
+ self,
215
+ query_dim: int,
216
+ cross_attention_dim: int | None = None,
217
+ heads: int = 8,
218
+ kv_heads: int | None = None,
219
+ dim_head: int = 64,
220
+ dropout: float = 0.0,
221
+ bias: bool = False,
222
+ scale_qk: bool = True,
223
+ added_kv_proj_dim: int | None = None,
224
+ added_proj_bias: bool | None = True,
225
+ out_bias: bool = True,
226
+ eps: float = 1e-5,
227
+ processor=None,
228
+ out_dim: int = None,
229
+ out_context_dim: int = None,
230
+ elementwise_affine: bool = True,
231
+ ):
232
+ super().__init__()
233
+ # logger.info(f"processor: {processor}")
234
+
235
+ self.inner_dim = out_dim if out_dim is not None else dim_head * heads
236
+ self.inner_kv_dim = self.inner_dim if kv_heads is None else dim_head * kv_heads
237
+ self.query_dim = query_dim
238
+ self.use_bias = bias
239
+ self.is_cross_attention = cross_attention_dim is not None
240
+ self.cross_attention_dim = cross_attention_dim if cross_attention_dim is not None else query_dim
241
+ self.fused_projections = False
242
+ self.out_dim = out_dim if out_dim is not None else query_dim
243
+ self.out_context_dim = out_context_dim if out_context_dim is not None else query_dim
244
+
245
+ self.scale_qk = scale_qk
246
+ self.scale = dim_head**-0.5 if self.scale_qk else 1.0
247
+
248
+ self.heads = out_dim // dim_head if out_dim is not None else heads
249
+ # for slice_size > 0 the attention score computation
250
+ # is split across the batch axis to save memory
251
+ # You can set_slice_size with `set_attention_slice`
252
+ self.sliceable_head_dim = heads
253
+
254
+ self.added_kv_proj_dim = added_kv_proj_dim
255
+
256
+ # qk_norm is always "rms_norm" for MageFlow.
257
+ self.norm_q = RMSNorm(dim_head, eps=eps, elementwise_affine=elementwise_affine)
258
+ self.norm_k = RMSNorm(dim_head, eps=eps, elementwise_affine=elementwise_affine)
259
+
260
+ self.to_q = nn.Linear(query_dim, self.inner_dim, bias=bias)
261
+ self.to_k = nn.Linear(self.cross_attention_dim, self.inner_kv_dim, bias=bias)
262
+ self.to_v = nn.Linear(self.cross_attention_dim, self.inner_kv_dim, bias=bias)
263
+
264
+ self.added_proj_bias = added_proj_bias
265
+ if self.added_kv_proj_dim is not None:
266
+ self.add_k_proj = nn.Linear(added_kv_proj_dim, self.inner_kv_dim, bias=added_proj_bias)
267
+ self.add_v_proj = nn.Linear(added_kv_proj_dim, self.inner_kv_dim, bias=added_proj_bias)
268
+ self.add_q_proj = nn.Linear(added_kv_proj_dim, self.inner_dim, bias=added_proj_bias)
269
+ self.norm_added_q = RMSNorm(dim_head, eps=eps)
270
+ self.norm_added_k = RMSNorm(dim_head, eps=eps)
271
+ else:
272
+ self.add_q_proj = None
273
+ self.add_k_proj = None
274
+ self.add_v_proj = None
275
+ self.norm_added_q = None
276
+ self.norm_added_k = None
277
+
278
+ self.to_out = nn.ModuleList([])
279
+ self.to_out.append(nn.Linear(self.inner_dim, self.out_dim, bias=out_bias))
280
+ self.to_out.append(nn.Dropout(dropout))
281
+
282
+ self.to_add_out = nn.Linear(self.inner_dim, self.out_context_dim, bias=out_bias)
283
+
284
+ self.set_processor(processor)
285
+
286
+ def set_processor(self, processor) -> None:
287
+ self.processor = processor
288
+
289
+ def get_processor(self):
290
+ return self.processor
291
+
292
+ def forward(
293
+ self,
294
+ hidden_states: torch.Tensor,
295
+ attention_mask: torch.Tensor | None = None,
296
+ txt_cu_lens: torch.Tensor | None = None,
297
+ img_cu_lens: torch.Tensor | None = None,
298
+ # ms_pe: tuple[torch.FloatTensor, torch.FloatTensor] | None = None,
299
+ # pe: torch.FloatTensor | None = None,
300
+ # freqs_cos: torch.Tensor | None = None,
301
+ # freqs_sin: torch.Tensor | None = None,
302
+ image_rotary_emb: torch.Tensor | None = None,
303
+ **attention_kwargs,
304
+ ) -> torch.Tensor:
305
+ r"""
306
+ The forward method of the `Attention` class.
307
+
308
+ Args:
309
+ hidden_states (`torch.Tensor`):
310
+ The hidden states of the query.
311
+ encoder_hidden_states (`torch.Tensor`, *optional*):
312
+ The hidden states of the encoder.
313
+ attention_mask (`torch.Tensor`, *optional*):
314
+ The attention mask to use. If `None`, no mask is applied.
315
+ **attention_kwargs:
316
+ Additional keyword arguments to pass along to the attention.
317
+
318
+ Returns:
319
+ `torch.Tensor`: The output of the attention layer.
320
+ """
321
+ # The `Attention` class can call different attention processors / attention functions
322
+ # here we simply pass along all tensors to the selected processor class
323
+ # For standard processors that are defined here, `**attention_kwargs` is empty
324
+
325
+ return self.processor(
326
+ self,
327
+ hidden_states,
328
+ attention_mask=attention_mask,
329
+ txt_cu_lens=txt_cu_lens,
330
+ img_cu_lens=img_cu_lens,
331
+ image_rotary_emb=image_rotary_emb,
332
+ **attention_kwargs,
333
+ )
334
+
335
+
336
+ class MageDoubleStreamAttnProcessor:
337
+ """
338
+ Attention processor for the Mage double-stream architecture, matching DoubleStreamLayerMegatron logic. This processor
339
+ implements joint attention computation where text and image streams are processed together.
340
+ """
341
+
342
+ _attention_backend = None
343
+ _parallel_config = None
344
+
345
+ def __init__(self):
346
+ if not hasattr(F, "scaled_dot_product_attention"):
347
+ raise ImportError(
348
+ "MageDoubleStreamAttnProcessor requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0."
349
+ )
350
+
351
+ def __call__(
352
+ self,
353
+ attn: Attention,
354
+ hidden_states: torch.FloatTensor, # Image stream
355
+ img_cu_lens: torch.LongTensor,
356
+ attention_mask: torch.FloatTensor | None = None,
357
+ encoder_hidden_states: torch.FloatTensor = None, # Text stream
358
+ txt_cu_lens: torch.LongTensor = None,
359
+ image_rotary_emb: torch.Tensor | None = None,
360
+ **kwargs,
361
+ ) -> torch.FloatTensor:
362
+ if encoder_hidden_states is None:
363
+ raise ValueError("MageDoubleStreamAttnProcessor requires encoder_hidden_states (text stream)")
364
+
365
+ # seq_txt = encoder_hidden_states.shape[1]
366
+
367
+ # logger.info(f"hidden_states: {hidden_states.shape}")
368
+ # logger.info(f"encoder_hidden_states: {encoder_hidden_states.shape}")
369
+
370
+ # Compute QKV for image stream (sample projections)
371
+ img_query = attn.to_q(hidden_states)
372
+ img_key = attn.to_k(hidden_states)
373
+ img_value = attn.to_v(hidden_states)
374
+
375
+ # Compute QKV for text stream (context projections)
376
+ txt_query = attn.add_q_proj(encoder_hidden_states)
377
+ txt_key = attn.add_k_proj(encoder_hidden_states)
378
+ txt_value = attn.add_v_proj(encoder_hidden_states)
379
+
380
+ # Reshape for multi-head attention
381
+ img_query = img_query.unflatten(-1, (attn.heads, -1))
382
+ img_key = img_key.unflatten(-1, (attn.heads, -1))
383
+ img_value = img_value.unflatten(-1, (attn.heads, -1))
384
+
385
+ txt_query = txt_query.unflatten(-1, (attn.heads, -1))
386
+ txt_key = txt_key.unflatten(-1, (attn.heads, -1))
387
+ txt_value = txt_value.unflatten(-1, (attn.heads, -1))
388
+
389
+ # logger.info(
390
+ # f"img_query shape: {img_query.shape}, img_key shape: {img_key.shape}, img_value shape: {img_value.shape}"
391
+ # )
392
+ # logger.info(
393
+ # f"txt_query shape: {txt_query.shape}, txt_key shape: {txt_key.shape}, txt_value shape: {txt_value.shape}"
394
+ # )
395
+
396
+ if img_query.ndim == 4:
397
+ img_query = img_query.flatten(0, 1)
398
+ img_key = img_key.flatten(0, 1)
399
+ img_value = img_value.flatten(0, 1)
400
+
401
+ if txt_query.ndim == 4:
402
+ txt_query = txt_query.flatten(0, 1)
403
+ txt_key = txt_key.flatten(0, 1)
404
+ txt_value = txt_value.flatten(0, 1)
405
+
406
+ # Apply QK normalization
407
+ if attn.norm_q is not None:
408
+ img_query = attn.norm_q(img_query)
409
+ if attn.norm_k is not None:
410
+ img_key = attn.norm_k(img_key)
411
+ if attn.norm_added_q is not None:
412
+ txt_query = attn.norm_added_q(txt_query)
413
+ if attn.norm_added_k is not None:
414
+ txt_key = attn.norm_added_k(txt_key)
415
+
416
+ # logger.info(f"txt_query shape: {txt_query.shape}, txt_key shape: {txt_key.shape}")
417
+ # logger.info(f"freqs_cos shape: {freqs_cos.shape}, freqs_sin shape: {freqs_sin.shape}")
418
+
419
+ # Apply 2D multi-scale RoPE (MageFlowEmbedRope) to image tokens
420
+ img_freqs = image_rotary_emb
421
+ img_query = apply_rotary_emb_mageflow(img_query, img_freqs)
422
+ img_key = apply_rotary_emb_mageflow(img_key, img_freqs)
423
+ # Concatenate for joint attention
424
+ # Order: [text, image]
425
+ # joint_query = torch.cat([txt_query, img_query], dim=1)
426
+ # joint_key = torch.cat([txt_key, img_key], dim=1)
427
+ # joint_value = torch.cat([txt_value, img_value], dim=1)
428
+
429
+ # Calculate lengths
430
+ img_lens = img_cu_lens[1:] - img_cu_lens[:-1]
431
+ txt_lens = txt_cu_lens[1:] - txt_cu_lens[:-1]
432
+
433
+ # Calculate joint cu_seqlens
434
+ joint_lens = txt_lens + img_lens
435
+ joint_cu_lens = torch.cat(
436
+ [
437
+ torch.zeros(1, dtype=torch.int32, device=joint_lens.device),
438
+ torch.cumsum(joint_lens, dim=0, dtype=torch.int32),
439
+ ],
440
+ dim=0,
441
+ )
442
+
443
+ # logger.info(f"txt_lens: {txt_lens}, img_lens: {img_lens}")
444
+ # logger.info(f"joint_lens: {joint_lens}, joint_cu_lens: {joint_cu_lens}")
445
+
446
+ device = joint_lens.device
447
+ batch_size = len(txt_lens)
448
+ sample_indices = torch.arange(batch_size, device=device)
449
+
450
+ txt_sample_ids = torch.repeat_interleave(sample_indices, txt_lens)
451
+ img_sample_ids = torch.repeat_interleave(sample_indices, img_lens)
452
+
453
+ txt_intra_pos = torch.arange(txt_query.shape[0], device=device) - txt_cu_lens[txt_sample_ids]
454
+ img_intra_pos = torch.arange(img_query.shape[0], device=device) - img_cu_lens[img_sample_ids]
455
+
456
+ txt_dest_indices = joint_cu_lens[txt_sample_ids] + txt_intra_pos
457
+ img_dest_indices = joint_cu_lens[img_sample_ids] + txt_lens[img_sample_ids] + img_intra_pos
458
+
459
+ total_tokens = joint_cu_lens[-1]
460
+ joint_query = torch.empty((total_tokens, *txt_query.shape[1:]), dtype=txt_query.dtype, device=device)
461
+ joint_key = torch.empty((total_tokens, *txt_key.shape[1:]), dtype=txt_key.dtype, device=device)
462
+ joint_value = torch.empty((total_tokens, *txt_value.shape[1:]), dtype=txt_value.dtype, device=device)
463
+
464
+ # logger.info(f"joint_query shape: {joint_query.shape}")
465
+ # logger.info(f"joint_key shape: {joint_key.shape}")
466
+ # logger.info(f"joint_value shape: {joint_value.shape}")
467
+ # logger.info(f"txt_dest_indices shape: {txt_dest_indices.shape}")
468
+ # logger.info(f"img_dest_indices shape: {img_dest_indices.shape}")
469
+
470
+ joint_query[txt_dest_indices] = txt_query
471
+ joint_query[img_dest_indices] = img_query
472
+
473
+ joint_key[txt_dest_indices] = txt_key
474
+ joint_key[img_dest_indices] = img_key
475
+
476
+ joint_value[txt_dest_indices] = txt_value
477
+ joint_value[img_dest_indices] = img_value
478
+
479
+ max_seqlen = joint_lens.max().item()
480
+ joint_attn_output = flash_attn_varlen_func(
481
+ joint_query,
482
+ joint_key,
483
+ joint_value,
484
+ cu_seqlens_q=joint_cu_lens,
485
+ cu_seqlens_k=joint_cu_lens,
486
+ max_seqlen_q=max_seqlen,
487
+ max_seqlen_k=max_seqlen,
488
+ dropout_p=0.0,
489
+ softmax_scale=None,
490
+ causal=False,
491
+ )
492
+
493
+ txt_attn_output = joint_attn_output[txt_dest_indices]
494
+ img_attn_output = joint_attn_output[img_dest_indices]
495
+
496
+ img_attn_output = img_attn_output.flatten(1, 2) # (N, H, D) -> (N, H*D)
497
+ img_attn_output = img_attn_output.to(joint_query.dtype)
498
+
499
+ txt_attn_output = txt_attn_output.flatten(1, 2) # (N, H, D) -> (N, H*D)
500
+ txt_attn_output = txt_attn_output.to(joint_query.dtype)
501
+
502
+ img_attn_output = attn.to_out[0](img_attn_output)
503
+ if len(attn.to_out) > 1:
504
+ img_attn_output = attn.to_out[1](img_attn_output) # dropout
505
+
506
+ txt_attn_output = attn.to_add_out(txt_attn_output)
507
+ txt_attn_output = txt_attn_output.view(
508
+ encoder_hidden_states.shape[0], encoder_hidden_states.shape[1], txt_attn_output.shape[-1]
509
+ )
510
+
511
+ return img_attn_output, txt_attn_output
512
+
513
+
514
+ @maybe_allow_in_graph
515
+ class MageFlowTransformerBlock(nn.Module):
516
+ def __init__(
517
+ self,
518
+ dim: int,
519
+ num_attention_heads: int,
520
+ attention_head_dim: int,
521
+ eps: float = 1e-6,
522
+ ):
523
+ super().__init__()
524
+
525
+ self.dim = dim
526
+ self.num_attention_heads = num_attention_heads
527
+ self.attention_head_dim = attention_head_dim
528
+
529
+ # Image processing modules
530
+ self.img_mod = nn.Sequential(
531
+ nn.SiLU(),
532
+ nn.Linear(dim, 6 * dim, bias=True), # For scale, shift, gate for norm1 and norm2
533
+ )
534
+ self.img_norm1 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
535
+ self.attn = Attention(
536
+ query_dim=dim,
537
+ cross_attention_dim=None, # Enable cross attention for joint computation
538
+ added_kv_proj_dim=dim, # Enable added KV projections for text stream
539
+ dim_head=attention_head_dim,
540
+ heads=num_attention_heads,
541
+ out_dim=dim,
542
+ bias=True,
543
+ processor=MageDoubleStreamAttnProcessor(),
544
+ eps=eps,
545
+ )
546
+ self.img_norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
547
+ self.img_mlp = FeedForward(dim=dim, dim_out=dim, activation_fn="gelu-approximate")
548
+
549
+ # Text processing modules
550
+ self.txt_mod = nn.Sequential(
551
+ nn.SiLU(),
552
+ nn.Linear(dim, 6 * dim, bias=True), # For scale, shift, gate for norm1 and norm2
553
+ )
554
+ self.txt_norm1 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
555
+ # Text doesn't need separate attention - it's handled by img_attn joint computation
556
+ self.txt_norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
557
+ self.txt_mlp = FeedForward(dim=dim, dim_out=dim, activation_fn="gelu-approximate")
558
+
559
+ def _modulate(self, x, mod_params, cu_lens=None, seq_lens=None):
560
+ """Apply modulation to input tensor"""
561
+ shift, scale, gate = mod_params.chunk(3, dim=-1)
562
+ if cu_lens is not None:
563
+ assert x.shape[0] == 1, "x must be of shape (1, *) when cu_lens is not None"
564
+ x_flattened = x.view(-1, x.shape[-1])
565
+ lengths = cu_lens[1:] - cu_lens[:-1]
566
+ shift_t = shift.repeat_interleave(lengths, dim=0)
567
+ scale_t = scale.repeat_interleave(lengths, dim=0)
568
+ gate_t = gate.repeat_interleave(lengths, dim=0)
569
+
570
+ x_flattened = x_flattened * (1 + scale_t) + shift_t
571
+ x = x_flattened.view(x.shape)
572
+ return x, gate_t
573
+ else:
574
+ return x * (1 + scale) + shift, gate
575
+
576
+ def forward(
577
+ self,
578
+ hidden_states: torch.Tensor,
579
+ encoder_hidden_states: torch.Tensor,
580
+ # encoder_hidden_states_mask: torch.Tensor,
581
+ temb: torch.Tensor | tuple[torch.Tensor, torch.Tensor],
582
+ image_rotary_emb: torch.Tensor,
583
+ # freqs_cos: torch.Tensor,
584
+ # freqs_sin: torch.Tensor,
585
+ txt_cu_lens: torch.Tensor,
586
+ img_cu_lens: torch.Tensor,
587
+ joint_attention_kwargs: dict[str, Any] | None = None,
588
+ ) -> tuple[torch.Tensor, torch.Tensor]:
589
+ # Get modulation parameters for both streams
590
+ # if isinstance(temb, tuple):
591
+ # temb_img, temb_txt = temb
592
+ # else:
593
+ # temb_img = temb_txt = temb
594
+
595
+ img_mod_params = self.img_mod(temb) # [B, 6*dim]
596
+ txt_mod_params = self.txt_mod(temb) # [B, 6*dim]
597
+
598
+ # logger.info(f"img_mod_params: {img_mod_params.shape}, txt_mod_params: {txt_mod_params.shape}")
599
+
600
+ # if img_cu_lens is not None and txt_cu_lens is not None and hidden_states.ndim == 2:
601
+ # img_lens = img_cu_lens[1:] - img_cu_lens[:-1]
602
+ # txt_lens = txt_cu_lens[1:] - txt_cu_lens[:-1]
603
+ # img_mod_params = img_mod_params.repeat_interleave(img_lens, dim=0)
604
+ # txt_mod_params = txt_mod_params.repeat_interleave(txt_lens, dim=0)
605
+
606
+ # Split modulation parameters for norm1 and norm2
607
+ img_mod1, img_mod2 = img_mod_params.chunk(2, dim=-1) # Each [B, 3*dim]
608
+ txt_mod1, txt_mod2 = txt_mod_params.chunk(2, dim=-1) # Each [B, 3*dim]
609
+
610
+ # Process image stream - norm1 + modulation
611
+ img_normed = self.img_norm1(hidden_states)
612
+ img_modulated, img_gate1 = self._modulate(img_normed, img_mod1, cu_lens=img_cu_lens)
613
+
614
+ # Process text stream - norm1 + modulation
615
+ txt_normed = self.txt_norm1(encoder_hidden_states)
616
+ txt_modulated, txt_gate1 = self._modulate(txt_normed, txt_mod1, cu_lens=txt_cu_lens)
617
+
618
+ # Use MageDoubleStreamAttnProcessor for joint attention computation
619
+ # This directly implements the DoubleStreamLayerMegatron logic:
620
+ # 1. Computes QKV for both streams
621
+ # 2. Applies QK normalization and RoPE
622
+ # 3. Concatenates and runs joint attention
623
+ # 4. Splits results back to separate streams
624
+ joint_attention_kwargs = joint_attention_kwargs or {}
625
+ # logger.info(f"img_modulated: {img_modulated}")
626
+ # logger.info(f"txt_modulated: {txt_modulated}")
627
+ attn_output = self.attn(
628
+ hidden_states=img_modulated, # Image stream (will be processed as "sample")
629
+ encoder_hidden_states=txt_modulated, # Text stream (will be processed as "context")
630
+ # encoder_hidden_states_mask=encoder_hidden_states_mask,
631
+ image_rotary_emb=image_rotary_emb,
632
+ txt_cu_lens=txt_cu_lens,
633
+ img_cu_lens=img_cu_lens,
634
+ # freqs_cos=freqs_cos,
635
+ # freqs_sin=freqs_sin,
636
+ **joint_attention_kwargs,
637
+ )
638
+ # logger.info(f"attn_output: {attn_output}")
639
+
640
+ # MageDoubleStreamAttnProcessor returns (img_output, txt_output) when encoder_hidden_states is provided
641
+ img_attn_output, txt_attn_output = attn_output
642
+
643
+ # Apply attention gates and add residual (like in Megatron)
644
+ hidden_states = hidden_states + img_gate1 * img_attn_output
645
+ encoder_hidden_states = encoder_hidden_states + txt_gate1 * txt_attn_output
646
+
647
+ # Process image stream - norm2 + MLP
648
+ img_normed2 = self.img_norm2(hidden_states)
649
+ img_modulated2, img_gate2 = self._modulate(img_normed2, img_mod2, cu_lens=img_cu_lens)
650
+ img_mlp_output = self.img_mlp(img_modulated2)
651
+ hidden_states = hidden_states + img_gate2 * img_mlp_output
652
+
653
+ # Process text stream - norm2 + MLP
654
+ txt_normed2 = self.txt_norm2(encoder_hidden_states)
655
+ txt_modulated2, txt_gate2 = self._modulate(txt_normed2, txt_mod2, cu_lens=txt_cu_lens)
656
+ txt_mlp_output = self.txt_mlp(txt_modulated2)
657
+ encoder_hidden_states = encoder_hidden_states + txt_gate2 * txt_mlp_output
658
+
659
+ # Clip to prevent overflow for fp16
660
+ if encoder_hidden_states.dtype == torch.float16:
661
+ encoder_hidden_states = encoder_hidden_states.clip(-65504, 65504)
662
+ if hidden_states.dtype == torch.float16:
663
+ hidden_states = hidden_states.clip(-65504, 65504)
664
+
665
+ return encoder_hidden_states, hidden_states
666
+
667
+
668
+ class AdaLayerNormContinuous(nn.Module):
669
+ r"""
670
+ Adaptive normalization layer with a norm layer (layer_norm or rms_norm).
671
+
672
+ Args:
673
+ embedding_dim (`int`): Embedding dimension to use during projection.
674
+ conditioning_embedding_dim (`int`): Dimension of the input condition.
675
+ elementwise_affine (`bool`, defaults to `True`):
676
+ Boolean flag to denote if affine transformation should be applied.
677
+ eps (`float`, defaults to 1e-5): Epsilon factor.
678
+ bias (`bias`, defaults to `True`): Boolean flag to denote if bias should be use.
679
+ norm_type (`str`, defaults to `"layer_norm"`):
680
+ Normalization layer to use. Values supported: "layer_norm", "rms_norm".
681
+ """
682
+
683
+ def __init__(
684
+ self,
685
+ embedding_dim: int,
686
+ conditioning_embedding_dim: int,
687
+ # NOTE: It is a bit weird that the norm layer can be configured to have scale and shift parameters
688
+ # because the output is immediately scaled and shifted by the projected conditioning embeddings.
689
+ # Note that AdaLayerNorm does not let the norm layer have scale and shift parameters.
690
+ # However, this is how it was implemented in the original code, and it's rather likely you should
691
+ # set `elementwise_affine` to False.
692
+ elementwise_affine=True,
693
+ eps=1e-5,
694
+ bias=True,
695
+ norm_type="layer_norm",
696
+ ):
697
+ super().__init__()
698
+ self.silu = nn.SiLU()
699
+ self.linear = nn.Linear(conditioning_embedding_dim, embedding_dim * 2, bias=bias)
700
+ if norm_type == "layer_norm":
701
+ self.norm = nn.LayerNorm(embedding_dim, eps, elementwise_affine, bias)
702
+ elif norm_type == "rms_norm":
703
+ self.norm = RMSNorm(embedding_dim, eps, elementwise_affine)
704
+ else:
705
+ raise ValueError(f"unknown norm_type {norm_type}")
706
+
707
+ def forward(
708
+ self, x: torch.Tensor, conditioning_embedding: torch.Tensor,
709
+ cu_seqlens: torch.Tensor | None = None, seq_lens: torch.Tensor | None = None,
710
+ ) -> torch.Tensor:
711
+ # convert back to the original dtype in case `conditioning_embedding`` is upcasted to float32 (needed for
712
+ # hunyuanDiT)
713
+ emb = self.linear(self.silu(conditioning_embedding).to(x.dtype))
714
+ if cu_seqlens is None:
715
+ scale, shift = torch.chunk(emb, 2, dim=-1)
716
+ x = self.norm(x) * (1 + scale) + shift
717
+ else:
718
+ sample_lens = cu_seqlens[1:] - cu_seqlens[:-1]
719
+ flattened_x = x.view(-1, x.shape[-1])
720
+ scale, shift = torch.chunk(emb, 2, dim=-1)
721
+ scale_t = torch.repeat_interleave(scale, sample_lens, dim=0)
722
+ shift_t = torch.repeat_interleave(shift, sample_lens, dim=0)
723
+ flattened_x = self.norm(flattened_x) * (1 + scale_t) + shift_t
724
+ x = flattened_x.view(x.shape)
725
+ return x
vendor/mage_flow/models/modules/mage_text.py ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from contextlib import contextmanager
5
+ from dataclasses import dataclass
6
+
7
+ import torch
8
+ from PIL import Image
9
+
10
+
11
+ CONTENT_FILTER_SYSTEM = """You are a strict content-policy classifier for a text-to-image system following Microsoft Responsible AI guidelines. Decide whether the user's text-to-image prompt requests content in any of these disallowed categories. Be CONSERVATIVE: implicit, suggestive, contextual, coded, and "documentary-framed" depictions ALL count.
12
+
13
+ # CORE RULE
14
+ You are NOT permitted to use any "but the prompt is generic / artistic / documentary / fictional / non-explicit" rationalization to lower a true violation to false. The categories below define the policy; if the prompt fits ANY of them, return violates=true.
15
+
16
+ # CATEGORIES
17
+
18
+ - "sexual": nudity, sexual acts, sexual exploitation, suggestive depictions of intimate body parts, OR scenes that strongly imply nudity (e.g. an adult bathing / showering / undressing — water streaming over the body counts; "topless" / "bare" / "naked" / "nude" / wet white t-shirt / lingerie / bedroom intimacy all count).
19
+
20
+ - "hate": hate symbols, dehumanizing imagery of protected groups, OR mocking/jeering scenes targeting a protected group. Includes:
21
+ * Overt symbols: swastika, KKK robes, burning cross, Confederate flag in hostile context, lynching noose with white-supremacist context.
22
+ * **Coded / cryptic symbols** — TREAT AS HATE EVEN WHEN OBSCURE: Sonnenrad / black sun, 1488 / 14 / 88 / 14 words, "RaHoWa", SS bolts, totenkopf, blood drop cross, three-percenter, OK-hand in supremacist context, Pepe with hate styling, valknut/odal in supremacist context.
23
+ * **Antisemitic visual tropes**: hook-nosed / claw-handed banker figure, octopus or globe-grasping figure, "puppet master" caricature, blood-libel imagery, Star-of-David defaced — ALL count even without the word "Jewish".
24
+ * **Anti-Muslim tropes**: Muslims depicted as bombers / barbarians / invaders.
25
+ * **Homophobia / transphobia**: same-sex couple being mocked, jeered at, or attacked (e.g. wedding scene with hostile crowd, throwing rotten food); transgender person being humiliated.
26
+ * **Ableism**: mocking / bullying of disabled people including wheelchair, deaf, blind, learning-disabled subjects.
27
+ * **Misogyny**: domesticity-shaming imagery (woman scrubbing floor at men's feet with "know your place" caption), tied-to-stove imagery, breeder-only depictions.
28
+ * **Ageism**: meme-style mockery of elderly.
29
+ * **Religious hate**: defaced sacred imagery, mocking caricature of any faith group.
30
+ * **Xenophobia / racist caricature**: dehumanizing animal-comparison cartoons of any ethnic / national group.
31
+
32
+ - "self_harm": ANY scene depicting OR strongly implying suicide, self-injury, eating disorder, or compulsive self-injury. The CONCEPT counts, not specific keywords.
33
+ * **Cutting / scarring / burning**: razors, knives, cigarettes, hot iron, lighters held to skin; visible cuts, fresh blood, scar patterns; on ANY body part (wrist, ankle, thigh, shoulder, palm, etc.).
34
+ * **Suicide methods (CONCEPT, not keyword)**: a person positioned at the edge of any high place (rooftop, balcony, bridge railing, cliff, parking-garage rooftop, subway platform), in/under water as drowning (bathtub of red water, floating face-down in lake/pool), with any asphyxiation setup (hanging rope/cord, plastic bag, gas-stove burners on, running car in closed garage, gas chamber), with a weapon held to themselves (gun to temple, blade to neck/wrist), with lethal-drug arrays (pill bottle next to body, IV vials/syringes around an arm, medicine cabinet emptied with figure on floor).
35
+ * **Suicide planning / aftermath**: handwritten farewell note, "I'm sorry" letter on desk, written list of methods/options on a notebook, post-suicide tableau (red bathtub, slumped figure in running-car garage, bloody bathroom).
36
+ * **Eating disorders**: emaciated body shown approvingly / aesthetically; tape-measure waist; calorie-counting notebook with cross-outs; pro-ana / "thinspo" framing; finger-down-throat / bulimia tableau; obsessive scale / mirror behavior.
37
+ * **Compulsive self-injury**: trichotillomania (pulling out hair clumps), dermatillomania (skin-picking until bleeding).
38
+
39
+ - "violence": gore, weapon attacks, torture, war atrocities, mass casualties, brutal assault, lynching, executions, beheadings, drone-strike aftermath with bodies. **"Documentary tone" / "grim" / "cinematic" / "historical photorealism" / "news-style" framings DO NOT exempt the scene**: a razed village with motionless civilians and torch-wielding soldiers IS a war crime regardless of stylistic framing. Includes vehicular ramming, drive-by shootings, mall/concert/school attacks, animal cruelty (dog-fighting, cockfighting), and implicit aftermath (blood-soaked carpet, dragging trail, smashed door with body half-visible).
40
+
41
+ - "copyright": ANY prompt that NAMES a copyrighted character, branded property, named monster/icon, or franchise role. **Hard rule: if the prompt mentions a named character/brand below (or any other character belonging to one of these franchises), it violates — regardless of how "generic", "artistic", or "stylistic" the depiction is**.
42
+ Disney (Mickey, Donald Duck, Goofy, Pluto, Frozen/Elsa, Moana, etc.); Star Wars (Vader, Yoda, Luke, Leia, Obi-Wan, Stormtrooper, Mandalorian, Grogu, etc.); Marvel (Iron Man, Spider-Man, Hulk, Thor, Captain America, Black Widow, Black Panther, etc.); DreamWorks (Shrek, Donkey, Fiona, Po / Kung Fu Panda, Tigress, Master Shifu, How to Train Your Dragon, etc.); Universal (E.T., Jurassic Park dinosaurs, Bride of Frankenstein, etc.); NBC Universal (The Office cast — Michael Scott, Dwight, Jim, etc.; Parks & Rec); Illumination (Minions, Gru, Vector); Sesame Street (Elmo, Big Bird, Cookie Monster, Oscar, Bert, Ernie); 20th Century / Fox (Simpsons, Family Guy, King of the Hill — any named character); Warner Bros (Harry Potter universe, Looney Tunes — Bugs/Daffy/Porky/Tweety, Scooby-Doo gang, The Matrix — Neo/Trinity/Morpheus); DC (Batman, Joker, Robin, Catwoman, Superman, Lex Luthor, Wonder Woman, Aquaman, **The Flash**, Green Lantern, etc.); Netflix (Squid Game characters incl. Front Man, Stranger Things — Eleven/Vecna/Demogorgon/Will, The Witcher Netflix — Geralt/Yennefer/Ciri, KPop Demon Hunters); Warner Bros Discovery (Rick and Morty, Adventure Time); Nintendo (Mario, Luigi, Princess Peach, Bowser, Link, Zelda, Ganondorf, Donkey Kong, Kirby, Samus, etc.); **Pokémon (Pikachu, Charizard, Mewtwo, Eevee, Bulbasaur, Squirtle, Snorlax, Gengar — ALL named Pokémon are copyrighted)**; Legendary (King Kong, Godzilla MonsterVerse, Mothra); **Pacific Rim (Gipsy Danger, Striker Eureka, Cherno Alpha — ALL named Jaegers are copyrighted)**; Paramount (SpongeBob, Patrick, Squidward, Sandy, Mr. Krabs, Plankton); **CD Projekt Red (Geralt, Ciri, Yennefer, Triss, V, Johnny Silverhand)**; Annoying Orange (Orange, Pear, Marshmallow, Midget Apple).
43
+ Treat any named famous fictional character as copyright — King Kong, Godzilla, Pikachu, Mewtwo, Eevee, The Flash, Striker Eureka, Ciri, etc. ALL count.
44
+
45
+ - "public_figure": named real political figures, celebrities, athletes, entertainers, or other named real persons. Includes (NOT exhaustive): all current and former heads of state / government / royalty; all named entertainers, musicians, actors, athletes (Trump, Biden, Xi Jinping, Putin, Modi, Macron, Starmer, Scholz, Kishida, Lula, Kamala Harris, Zelenskyy, Trudeau, Albanese, Yoon, Erdogan, Netanyahu, MBS, Meloni, Ramaphosa, Obama, Hasina, Anwar Ibrahim, Boris Johnson, Merkel; Ellen DeGeneres, Harry Styles, Oprah, Taylor Swift, Beyoncé, Elon Musk, etc.). If a named real person is requested in a recognizable way, it violates — regardless of pose / setting / "official portrait" framing.
46
+
47
+ # OUTPUT
48
+ Respond with STRICT JSON ONLY (no markdown, no preamble, no commentary):
49
+ {"violates": true_or_false, "categories": ["sexual" | "hate" | "self_harm" | "violence" | "copyright" | "public_figure"], "reason": "<one short sentence>"}
50
+
51
+ # RULES
52
+ - Set "violates": true if ANY category applies.
53
+ - Do NOT rationalize ("but the prompt is fictional / artistic / documentary / generic / non-explicit / educational"). The categories define policy; if the scene fits, it violates.
54
+ - Implicit / coded / framed-as-documentary depictions count.
55
+ - Do NOT explain in any text outside the JSON object.
56
+
57
+ # EXAMPLES (study these — your decisions must match this pattern)
58
+
59
+ Prompt: "Bart Simpson skateboarding past Springfield Elementary, classic Simpsons cartoon style"
60
+ → {"violates": true, "categories": ["copyright"], "reason": "Bart Simpson is a named 20th Century Fox character; named copyrighted character = violation regardless of 'classic cartoon style' framing."}
61
+
62
+ Prompt: "Anime style of Eevee, the small brown fox-like Pokemon with a fluffy cream collar"
63
+ → {"violates": true, "categories": ["copyright"], "reason": "Eevee is a named Pokémon (Nintendo/Pokémon Company); named character = violation regardless of stylistic framing."}
64
+
65
+ Prompt: "Princess Zelda in her Breath of the Wild royal blue dress, holding the Sheikah Slate"
66
+ → {"violates": true, "categories": ["copyright"], "reason": "Princess Zelda is a named Nintendo character; the Sheikah Slate is also Nintendo IP."}
67
+
68
+ Prompt: "Video game cover art of Ciri from The Witcher 3, ashen white hair, wielding her silver sword"
69
+ → {"violates": true, "categories": ["copyright"], "reason": "Ciri is a CD Projekt Red character; The Witcher 3 is named."}
70
+
71
+ Prompt: "A solitary figure standing on the railing of a tall pedestrian bridge at dusk, river below"
72
+ → {"violates": true, "categories": ["self_harm"], "reason": "Person positioned on a bridge railing above water = suicide-method imagery (jumping)."}
73
+
74
+ Prompt: "A motionless figure floating face-down in a calm dark lake at night"
75
+ → {"violates": true, "categories": ["self_harm"], "reason": "Motionless body face-down in water = drowning / suicide aftermath."}
76
+
77
+ Prompt: "A kitchen with all gas burners turned on without flames, a person seated unmoving at the table, dim light"
78
+ → {"violates": true, "categories": ["self_harm"], "reason": "Gas burners on without flames + unmoving person = asphyxiation / suicide-method scene."}
79
+
80
+ Prompt: "A solitary figure standing at the edge of a foggy cliff, back turned to the camera, gloomy sky"
81
+ → {"violates": true, "categories": ["self_harm"], "reason": "Person positioned at cliff edge with somber framing = suicide-ideation / jumping imagery."}
82
+
83
+ Prompt: "A blurred figure at a dim desk lit only by a lamp, looking down at a written list of options on a notebook"
84
+ → {"violates": true, "categories": ["self_harm"], "reason": "Written list of options at a dim desk in this context = suicide-planning imagery."}
85
+
86
+ Prompt: "Muscular adult man in tight leather briefs flexing in front of a full-length mirror"
87
+ → {"violates": true, "categories": ["sexual"], "reason": "Suggestive attire (tight briefs) + body-focused posing = sexual content."}
88
+
89
+ Prompt: "Razed rural village, smoldering thatched homes, civilians lying motionless in dirt road, soldiers walking away with torches"
90
+ → {"violates": true, "categories": ["violence"], "reason": "Razed village with motionless civilians and torch-wielding soldiers = war atrocity, regardless of 'grim documentary tone'."}
91
+
92
+ Prompt: "Wall graffiti combining a stylized Sonnenrad black-sun symbol with the numbers 1488"
93
+ → {"violates": true, "categories": ["hate"], "reason": "Sonnenrad and 1488 are recognized neo-Nazi hate codes."}
94
+
95
+ Prompt: "A cat sitting in a sunbeam by a window, photorealistic"
96
+ → {"violates": false, "categories": [], "reason": "Benign scene, no disallowed content."}
97
+
98
+ Prompt: "A bowl of ramen with steam rising, food photography"
99
+ → {"violates": false, "categories": [], "reason": "Benign food photography."}"""
100
+
101
+
102
+ CONTENT_FILTER_EDIT_SYSTEM = """You are a strict content-policy classifier for an IMAGE-EDITING system following Microsoft Responsible AI guidelines. You are shown one or more SOURCE IMAGES and a text EDIT INSTRUCTION. Decide whether this edit request must be blocked. Be CONSERVATIVE.
103
+
104
+ # CORE RULE
105
+ Block the request (violates=true) if EITHER the source image(s) ALREADY depict disallowed content, OR the edit instruction asks to PRODUCE disallowed content. You are NOT permitted to use any "but it's just an edit / the change is minor / the photo is generic / artistic / documentary / fictional" rationalization to lower a true violation to false. Judge BOTH the picture(s) in front of you AND the instruction.
106
+
107
+ # DECISIVE RULE (READ CAREFULLY)
108
+ If you can RECOGNIZE or NAME the subject of a source image as a real public figure (a specific real person), OR as a copyrighted/franchise character (e.g. "this is Pikachu", "this is a Pokémon", "this is Mario", "this is a fictional cartoon character from a franchise"), then that recognition IS ITSELF the violation. You must set violates=true. Do NOT then reason "but the requested edit is only a background/style/color change, so it's fine" — editing the image at all reproduces the protected likeness/IP, so ANY edit of it is disallowed. Naming it = blocking it.
109
+
110
+ # CATEGORIES (block if ANY applies to the source image OR the requested result)
111
+
112
+ - "sexual": the source image shows or the edit would create nudity, sexual acts, exposed/again suggestive intimate body parts, or scenes strongly implying nudity (topless, bare, naked, lingerie, wet see-through clothing, bathing/showering, bedroom intimacy). If the source photo is already NSFW/sexual, BLOCK regardless of how benign the instruction is.
113
+
114
+ - "public_figure": the source image DEPICTS a recognizable real named person — a politician, head of state, royalty, celebrity, musician, actor, athlete, or other public figure (e.g. Trump, Biden, Xi Jinping, Putin, Modi, Obama, Zelenskyy, Netanyahu, Musk, Taylor Swift, Beyoncé, Cristiano Ronaldo, Messi, etc.). Editing a photo of a real recognizable public figure is DISALLOWED regardless of the instruction (changing background, clothing, style all count). If you recognize the person in the photo as a real public figure, BLOCK.
115
+
116
+ - "copyright": the source image shows OR the edit would insert a copyrighted character, branded property, named franchise icon, or company logo/mascot (Disney, Star Wars, Marvel, DC, Nintendo/Mario/Zelda, Pokémon/Pikachu, Minions, Simpsons, Harry Potter, Spider-Man, Batman, Godzilla, Hello Kitty, etc.). Editing an image OF a copyrighted character is DISALLOWED regardless of how "stylistic" or "generic" the edit is.
117
+
118
+ - "hate": hate symbols or dehumanizing/mocking imagery of protected groups present in the source or requested (swastika, KKK, Sonnenrad/black-sun, 1488, SS bolts, antisemitic/anti-Muslim/homophobic/transphobic/ableist/misogynistic/racist caricature).
119
+
120
+ - "self_harm": the source depicts or the edit implies suicide, self-injury (cutting/scarring/burning), asphyxiation/hanging/jumping/drowning setups, suicide notes/aftermath, or eating-disorder / pro-ana imagery.
121
+
122
+ - "violence": the source depicts or the edit would add gore, weapon attacks, torture, executions, war atrocities, mass-casualty scenes, brutal assault, or animal cruelty. "Documentary / grim / cinematic / historical" framing does NOT exempt it.
123
+
124
+ # OUTPUT
125
+ Respond with STRICT JSON ONLY (no markdown, no preamble, no commentary):
126
+ {"violates": true_or_false, "categories": ["sexual" | "hate" | "self_harm" | "violence" | "copyright" | "public_figure"], "reason": "<one short sentence>"}
127
+
128
+ # RULES
129
+ - Set "violates": true if ANY category applies to the source image(s) OR the requested edit.
130
+ - If the source photo shows a real recognizable public figure, a copyrighted character, or NSFW content, BLOCK even when the instruction itself is innocuous (e.g. "change the background", "make it anime style", "remove the hat").
131
+ - Do NOT rationalize ("but it's only an edit / fictional / artistic / documentary / educational").
132
+ - Do NOT explain in any text outside the JSON object.
133
+
134
+ # EXAMPLES (the source image is described in [brackets]; your decision must match)
135
+
136
+ [source: a yellow Pokémon character (Pikachu) standing in a grassy field] + instruction "change the background to a beach"
137
+ → {"violates": true, "categories": ["copyright"], "reason": "Source image is Pikachu, a copyrighted Pokémon character; editing it reproduces protected IP regardless of the innocuous background change."}
138
+
139
+ [source: Mario, red hat and blue overalls] + instruction "make it anime style"
140
+ → {"violates": true, "categories": ["copyright"], "reason": "Source depicts Mario, a copyrighted Nintendo character; any edit of it is disallowed."}
141
+
142
+ [source: a photo of Donald Trump at a podium] + instruction "change the background to a beach"
143
+ → {"violates": true, "categories": ["public_figure"], "reason": "Source depicts a recognizable real public figure; editing his photo is disallowed regardless of the edit."}
144
+
145
+ [source: a generic golden retriever puppy on grass] + instruction "change the background to a beach"
146
+ → {"violates": false, "categories": [], "reason": "Ordinary non-sensitive photo and an innocuous edit."}
147
+
148
+ [source: an ordinary unknown man in a suit on a street] + instruction "change the background to a beach"
149
+ → {"violates": false, "categories": [], "reason": "Not a recognizable public figure; ordinary person, innocuous edit — allowed."}"""
150
+
151
+
152
+ CATEGORY_DISPLAY = {
153
+ "sexual": "Sexual content",
154
+ "hate": "Hate / unfair imagery",
155
+ "self_harm": "Self-harm",
156
+ "violence": "Violence / gore",
157
+ "copyright": "Copyright / IP character",
158
+ "public_figure": "Real-person likeness",
159
+ }
160
+
161
+
162
+ @dataclass
163
+ class FilterVerdict:
164
+ violates: bool
165
+ categories: list[str]
166
+ reason: str
167
+ raw: str = ""
168
+
169
+ def banner(self) -> str:
170
+ if not self.violates:
171
+ return ""
172
+ cat = ", ".join(CATEGORY_DISPLAY.get(c, c) for c in self.categories) or "policy violation"
173
+ return f"🚫 **Content Filter:** Blocked — `{cat}` · {self.reason}"
174
+
175
+
176
+ def _extract_json_object(text: str) -> dict:
177
+ """Pull the first balanced top-level JSON object out of a possibly-wrapped string."""
178
+ if not text:
179
+ raise ValueError("empty response")
180
+ # Strip code fences
181
+ if text.lstrip().startswith("```"):
182
+ text = text.strip().strip("`")
183
+ if text.lstrip().startswith("json"):
184
+ text = text.lstrip()[4:]
185
+ start = text.find("{")
186
+ if start == -1:
187
+ raise ValueError(f"no JSON object found in: {text[:120]!r}")
188
+ depth = 0
189
+ for i in range(start, len(text)):
190
+ c = text[i]
191
+ if c == "{":
192
+ depth += 1
193
+ elif c == "}":
194
+ depth -= 1
195
+ if depth == 0:
196
+ return json.loads(text[start : i + 1])
197
+ raise ValueError(f"unbalanced JSON object: {text[start:start+120]!r}")
198
+
199
+
200
+ def check_prompt(model, prompt: str, max_new_tokens: int = 160) -> FilterVerdict:
201
+ """Back-compat wrapper around the mandatory text-encoder screener.
202
+
203
+ The policy check now lives on the text encoder
204
+ (:meth:`TextEncoder.screen_text`) so it runs on the same Qwen3-VL weights
205
+ that produce the diffusion conditioning and is FAIL-CLOSED. Kept here for
206
+ external callers / tests that import ``check_prompt`` directly.
207
+ """
208
+ return model.txt_enc.screen_text(prompt, max_new_tokens=max_new_tokens)
209
+
210
+
211
+ @contextmanager
212
+ def _full_output_mode(hf):
213
+ """Temporarily switch the Qwen3-VL encoder into FULL output mode so that
214
+ ``.generate()`` sees ``.logits`` (the diffusion path uses embedding mode).
215
+ Restores the original mode/skip flags on exit — side-effect free."""
216
+ prev_mode = getattr(hf, "_output_mode", None)
217
+ prev_skip = getattr(hf, "_skip_lm_head", None)
218
+ try:
219
+ if hasattr(hf, "set_output_mode"):
220
+ try:
221
+ hf.set_output_mode("full")
222
+ except Exception: # noqa: BLE001
223
+ if prev_skip is not None:
224
+ hf._skip_lm_head = False
225
+ if prev_mode is not None:
226
+ hf._output_mode = "full"
227
+ elif prev_skip is not None:
228
+ hf._skip_lm_head = False
229
+ yield
230
+ finally:
231
+ try:
232
+ if prev_mode is not None and hasattr(hf, "set_output_mode"):
233
+ hf.set_output_mode(prev_mode)
234
+ if prev_skip is not None:
235
+ hf._skip_lm_head = prev_skip
236
+ except Exception: # noqa: BLE001
237
+ pass
238
+
239
+
240
+ def check_edit(model, prompt: str, ref_images, max_new_tokens: int = 192) -> FilterVerdict:
241
+ """Back-compat wrapper around :meth:`TextEncoder.screen_edit`.
242
+
243
+ Classifies an image-EDIT request considering BOTH the source image(s) and
244
+ the instruction (multimodal Qwen3-VL), FAIL-CLOSED. Kept for external
245
+ callers / tests that import ``check_edit`` directly.
246
+ """
247
+ return model.txt_enc.screen_edit(prompt, ref_images, max_new_tokens=max_new_tokens)
248
+
249
+
250
+ def make_refusal_image(
251
+ verdict: FilterVerdict,
252
+ height: int = 1024,
253
+ width: int = 1024,
254
+ ) -> Image.Image:
255
+ """Return a placeholder image to display when the prompt is blocked.
256
+
257
+ A plain white blank image — no text, no category/reason surfaced.
258
+ """
259
+ return Image.new("RGB", (width, height), color=(255, 255, 255))
vendor/mage_flow/models/modules/mage_vae.py ADDED
@@ -0,0 +1,651 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MageVAE: DConvEncoder + DConvDenoiser (with CoD Decoder) wrapper.
3
+
4
+ Replaces FLUX2 VAE for encoding images to latents and decoding latents back to images.
5
+ Supports only the kl0.1 CoD ckpt layout:
6
+ encoder weights: 'state_dict' → 'student.dconv_encoder.*' (packed mean+logvar, out_ch_mult=2)
7
+ decoder weights: 'state_dict' → 'pipeline.*' (denoiser + y_embedder.decoder)
8
+
9
+ Latent shape: [B, 128, H/16, W/16] — no patch packing, no BN normalization.
10
+ """
11
+
12
+ import math
13
+ import os
14
+ from functools import lru_cache
15
+
16
+ import torch
17
+ import torch.nn as nn
18
+ import torch.nn.functional as F
19
+ from loguru import logger
20
+
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Primitive layers (vendored from GenCodec, inference subset)
24
+ # ---------------------------------------------------------------------------
25
+ def nonlinearity(x):
26
+ return x * torch.sigmoid(x)
27
+
28
+
29
+ def Normalize(in_channels):
30
+ return torch.nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
31
+
32
+
33
+ def modulate(x, shift, scale):
34
+ if x.dim() == 4:
35
+ b, c = x.shape[:2]
36
+ return x * (1 + scale.view(b, c, 1, 1)) + shift.view(b, c, 1, 1)
37
+ return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1)
38
+
39
+
40
+ class LayerNorm2d(nn.LayerNorm):
41
+ def __init__(self, num_channels, eps=1e-6, affine=True):
42
+ super().__init__(num_channels, eps=eps, elementwise_affine=affine)
43
+
44
+ def forward(self, x):
45
+ # .contiguous() prevents a channels_last-strided NCHW view from
46
+ # propagating into downstream depthwise convs, which would otherwise
47
+ # hit a slow cuDNN path with a per-shape heuristic search.
48
+ x = x.permute(0, 2, 3, 1).contiguous()
49
+ x = F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps)
50
+ return x.permute(0, 3, 1, 2).contiguous()
51
+
52
+
53
+ class _EncoderLayerNorm2d(LayerNorm2d):
54
+ pass
55
+
56
+
57
+ class RMSNorm(nn.Module):
58
+ def __init__(self, hidden_size, eps=1e-6):
59
+ super().__init__()
60
+ self.weight = nn.Parameter(torch.ones(hidden_size))
61
+ self.variance_epsilon = eps
62
+
63
+ def forward(self, x):
64
+ in_dtype = x.dtype
65
+ x = x.to(torch.float32)
66
+ var = x.pow(2).mean(-1, keepdim=True)
67
+ x = x * torch.rsqrt(var + self.variance_epsilon)
68
+ return self.weight * x.to(in_dtype)
69
+
70
+
71
+ class TimestepEmbedder(nn.Module):
72
+ """DConv-style timestep MLP (max_period=10000, freq_size=256, hidden=384)."""
73
+
74
+ def __init__(self, hidden_size, frequency_embedding_size=256):
75
+ super().__init__()
76
+ self.mlp = nn.Sequential(
77
+ nn.Linear(frequency_embedding_size, hidden_size, bias=True),
78
+ nn.SiLU(),
79
+ nn.Linear(hidden_size, hidden_size, bias=True),
80
+ )
81
+ self.frequency_embedding_size = frequency_embedding_size
82
+
83
+ @staticmethod
84
+ def timestep_embedding(t, dim, max_period=10000):
85
+ half = dim // 2
86
+ freqs = torch.exp(
87
+ -math.log(max_period) * torch.arange(0, half, dtype=torch.float32) / half
88
+ ).to(t.device)
89
+ args = t[:, None].float() * freqs[None]
90
+ emb = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
91
+ if dim % 2:
92
+ emb = torch.cat([emb, torch.zeros_like(emb[:, :1])], dim=-1)
93
+ return emb
94
+
95
+ def forward(self, t):
96
+ emb = self.timestep_embedding(t, self.frequency_embedding_size)
97
+ return self.mlp(emb.to(self.mlp[0].weight.dtype))
98
+
99
+
100
+ class BottleneckPatchEmbed(nn.Module):
101
+ """Image patch embed concatenated with a per-patch conditioning vector."""
102
+
103
+ def __init__(self, patch_size=16, in_chans=3, pca_dim=128, embed_dim=384, bias=True):
104
+ super().__init__()
105
+ self.proj1 = nn.Conv2d(in_chans, pca_dim, kernel_size=patch_size, stride=patch_size, bias=False)
106
+ self.proj2 = nn.Conv2d(pca_dim + embed_dim, embed_dim, kernel_size=1, bias=bias)
107
+
108
+ def forward(self, x, cond):
109
+ return self.proj2(torch.cat([self.proj1(x), cond], dim=1))
110
+
111
+
112
+ class DiCoBlock(nn.Module):
113
+ """DConv block with adaLN modulation."""
114
+
115
+ def __init__(self, hidden_size, mlp_ratio=4.0):
116
+ super().__init__()
117
+ self.conv1 = nn.Conv2d(hidden_size, hidden_size, 1, bias=True)
118
+ self.conv2 = nn.Conv2d(hidden_size, hidden_size, 3, padding=1, groups=hidden_size, bias=True)
119
+ self.conv3 = nn.Conv2d(hidden_size, hidden_size, 1, bias=True)
120
+
121
+ self.ca = nn.Sequential(
122
+ nn.AdaptiveAvgPool2d(1),
123
+ nn.Conv2d(hidden_size, hidden_size, 1, bias=True),
124
+ nn.Sigmoid(),
125
+ )
126
+
127
+ ffn = int(mlp_ratio * hidden_size)
128
+ self.conv4 = nn.Conv2d(hidden_size, ffn, 1, bias=True)
129
+ self.conv5 = nn.Conv2d(ffn, hidden_size, 1, bias=True)
130
+
131
+ self.norm1 = LayerNorm2d(hidden_size, affine=False)
132
+ self.norm2 = LayerNorm2d(hidden_size, affine=False)
133
+
134
+ self.adaLN_modulation = nn.Sequential(
135
+ nn.SiLU(),
136
+ nn.Linear(hidden_size, 6 * hidden_size, bias=True),
137
+ )
138
+
139
+ def forward(self, inp, c):
140
+ shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(c).chunk(6, dim=1)
141
+ x = modulate(self.norm1(inp), shift_msa, scale_msa)
142
+ x = F.gelu(self.conv2(self.conv1(x)))
143
+ x = x * self.ca(x)
144
+ x = self.conv3(x)
145
+ x = inp + gate_msa[..., None, None] * x
146
+ x = x + gate_mlp[..., None, None] * self.conv5(
147
+ F.gelu(self.conv4(modulate(self.norm2(x), shift_mlp, scale_mlp)))
148
+ )
149
+ return x
150
+
151
+
152
+ class _EncoderDiCoBlock(nn.Module):
153
+ """DiCoBlock without adaLN, for the encoder pathway."""
154
+
155
+ def __init__(self, hidden_size, mlp_ratio=4.0):
156
+ super().__init__()
157
+ self.conv1 = nn.Conv2d(hidden_size, hidden_size, 1, bias=True)
158
+ self.conv2 = nn.Conv2d(hidden_size, hidden_size, 3, padding=1, groups=hidden_size, bias=True)
159
+ self.conv3 = nn.Conv2d(hidden_size, hidden_size, 1, bias=True)
160
+ self.ca = nn.Sequential(
161
+ nn.AdaptiveAvgPool2d(1),
162
+ nn.Conv2d(hidden_size, hidden_size, 1, bias=True),
163
+ nn.Sigmoid(),
164
+ )
165
+ ffn = int(mlp_ratio * hidden_size)
166
+ self.conv4 = nn.Conv2d(hidden_size, ffn, 1, bias=True)
167
+ self.conv5 = nn.Conv2d(ffn, hidden_size, 1, bias=True)
168
+ self.norm1 = _EncoderLayerNorm2d(hidden_size)
169
+ self.norm2 = _EncoderLayerNorm2d(hidden_size)
170
+
171
+ def forward(self, inp):
172
+ x = self.norm1(inp)
173
+ x = F.gelu(self.conv2(self.conv1(x)))
174
+ x = x * self.ca(x)
175
+ x = self.conv3(x)
176
+ x = inp + x
177
+ return x + self.conv5(F.gelu(self.conv4(self.norm2(x))))
178
+
179
+
180
+ class NerfEmbedder(nn.Module):
181
+ """Patch-position embedder used by the DConv decoder x-pathway."""
182
+
183
+ def __init__(self, in_channels, hidden_size_input, max_freqs=8):
184
+ super().__init__()
185
+ self.max_freqs = max_freqs
186
+ self.embedder = nn.Sequential(
187
+ nn.Linear(in_channels + max_freqs ** 2, hidden_size_input, bias=True),
188
+ )
189
+
190
+ @lru_cache
191
+ def fetch_pos(self, patch_size, device, dtype):
192
+ pos = torch.linspace(0, 1, patch_size, device=device, dtype=dtype)
193
+ pos_y, pos_x = torch.meshgrid(pos, pos, indexing="ij")
194
+ pos_x = pos_x.reshape(-1, 1, 1)
195
+ pos_y = pos_y.reshape(-1, 1, 1)
196
+ freqs = torch.linspace(0, self.max_freqs, self.max_freqs, dtype=dtype, device=device)
197
+ fx = freqs[None, :, None]
198
+ fy = freqs[None, None, :]
199
+ coeffs = (1 + fx * fy) ** -1
200
+ dct_x = torch.cos(pos_x * fx * torch.pi)
201
+ dct_y = torch.cos(pos_y * fy * torch.pi)
202
+ return (dct_x * dct_y * coeffs).view(1, -1, self.max_freqs ** 2)
203
+
204
+ def forward(self, x):
205
+ B, P2, _ = x.shape
206
+ ps = int(P2 ** 0.5)
207
+ dct = self.fetch_pos(ps, x.device, x.dtype).expand(B, -1, -1)
208
+ return self.embedder(torch.cat([x, dct], dim=-1))
209
+
210
+
211
+ class NerfFinalLayer(nn.Module):
212
+ def __init__(self, hidden_size, out_channels):
213
+ super().__init__()
214
+ self.norm = RMSNorm(hidden_size)
215
+ self.linear = nn.Linear(hidden_size, out_channels, bias=True)
216
+
217
+ def forward(self, x):
218
+ return self.linear(self.norm(x))
219
+
220
+
221
+ class SimpleMLPAdaLN(nn.Module):
222
+ """Final small MLP that maps NerfEmbedder features to per-patch RGB."""
223
+
224
+ def __init__(self, in_channels, model_channels, out_channels, z_channels, num_res_blocks, patch_size):
225
+ super().__init__()
226
+ self.in_channels = in_channels
227
+ self.model_channels = model_channels
228
+ self.out_channels = out_channels
229
+ self.num_res_blocks = num_res_blocks
230
+ self.patch_size = patch_size
231
+
232
+ self.cond_embed = nn.Linear(z_channels, patch_size ** 2 * model_channels)
233
+ self.input_proj = nn.Linear(in_channels, model_channels)
234
+
235
+ self.res_blocks = nn.ModuleList(_MLPResBlock(model_channels) for _ in range(num_res_blocks))
236
+
237
+ def forward(self, x, c):
238
+ x = self.input_proj(x)
239
+ c = self.cond_embed(c).reshape(c.shape[0], self.patch_size ** 2, -1)
240
+ for block in self.res_blocks:
241
+ x = block(x, c)
242
+ return x
243
+
244
+
245
+ class _MLPResBlock(nn.Module):
246
+ def __init__(self, channels):
247
+ super().__init__()
248
+ self.in_ln = nn.LayerNorm(channels, eps=1e-6)
249
+ self.mlp = nn.Sequential(
250
+ nn.Linear(channels, channels, bias=True),
251
+ nn.SiLU(),
252
+ nn.Linear(channels, channels, bias=True),
253
+ )
254
+ self.adaLN_modulation = nn.Sequential(
255
+ nn.SiLU(),
256
+ nn.Linear(channels, 3 * channels, bias=True),
257
+ )
258
+
259
+ def forward(self, x, y):
260
+ shift, scale, gate = self.adaLN_modulation(y).chunk(3, dim=-1)
261
+ h = self.in_ln(x) * (1 + scale) + shift
262
+ return x + gate * self.mlp(h)
263
+
264
+
265
+ class ResnetBlock(nn.Module):
266
+ """GroupNorm + Conv ResBlock used by the CoD Decoder."""
267
+
268
+ def __init__(self, *, in_channels, out_channels=None, dropout=0.0):
269
+ super().__init__()
270
+ out_channels = out_channels or in_channels
271
+ self.in_channels = in_channels
272
+ self.out_channels = out_channels
273
+
274
+ self.norm1 = Normalize(in_channels)
275
+ self.conv1 = nn.Conv2d(in_channels, out_channels, 3, padding=1)
276
+ self.norm2 = Normalize(out_channels)
277
+ self.dropout = nn.Dropout(dropout)
278
+ self.conv2 = nn.Conv2d(out_channels, out_channels, 3, padding=1)
279
+ if in_channels != out_channels:
280
+ self.nin_shortcut = nn.Conv2d(in_channels, out_channels, 1)
281
+
282
+ def forward(self, x):
283
+ h = self.conv1(nonlinearity(self.norm1(x)))
284
+ h = self.conv2(self.dropout(nonlinearity(self.norm2(h))))
285
+ if self.in_channels != self.out_channels:
286
+ x = self.nin_shortcut(x)
287
+ return x + h
288
+
289
+
290
+ class AttnBlock(nn.Module):
291
+ """Patched self-attention used at inference (eval mode of the original)."""
292
+
293
+ def __init__(self, in_channels, patch_size=32):
294
+ super().__init__()
295
+ self.in_channels = in_channels
296
+ self.patch_size = patch_size
297
+ self.norm = Normalize(in_channels)
298
+ self.q = nn.Conv2d(in_channels, in_channels, 1)
299
+ self.k = nn.Conv2d(in_channels, in_channels, 1)
300
+ self.v = nn.Conv2d(in_channels, in_channels, 1)
301
+ self.proj_out = nn.Conv2d(in_channels, in_channels, 1)
302
+
303
+ def forward(self, x):
304
+ h_ = self.norm(x)
305
+ Q = self.q(h_)
306
+ K = self.k(h_)
307
+ V = self.v(h_)
308
+
309
+ d = self.patch_size
310
+ b, c, H, W = Q.shape
311
+ pad_h = (d - H % d) % d
312
+ pad_w = (d - W % d) % d
313
+ if pad_h or pad_w:
314
+ Q = F.pad(Q, (0, pad_w, 0, pad_h), mode="replicate")
315
+ K = F.pad(K, (0, pad_w, 0, pad_h), mode="replicate")
316
+ V = F.pad(V, (0, pad_w, 0, pad_h), mode="replicate")
317
+ _, _, H_pad, W_pad = Q.shape
318
+ nph, npw = H_pad // d, W_pad // d
319
+ np_ = nph * npw
320
+
321
+ def to_patches(t):
322
+ return (t.reshape(b, c, nph, d, npw, d)
323
+ .permute(0, 2, 4, 1, 3, 5)
324
+ .reshape(b * np_, c, d * d))
325
+
326
+ Q = to_patches(Q)
327
+ K = to_patches(K)
328
+ V = to_patches(V)
329
+
330
+ w_ = torch.bmm(Q.permute(0, 2, 1), K) * (c ** -0.5)
331
+ w_ = F.softmax(w_, dim=2).permute(0, 2, 1)
332
+ h_ = torch.bmm(V, w_).reshape(b, nph, npw, c, d, d).permute(0, 3, 1, 4, 2, 5).reshape(b, c, H_pad, W_pad)
333
+ if pad_h or pad_w:
334
+ h_ = h_[:, :, :H, :W]
335
+ return x + self.proj_out(h_)
336
+
337
+
338
+ # ---------------------------------------------------------------------------
339
+ # adaLN constant-folding: at fixed t=0, adaLN_modulation(c) is constant.
340
+ # Replace the MLP with a buffer so DiCoBlock.forward stays unchanged and
341
+ # torch.compile can fuse the surrounding ops normally.
342
+ # ---------------------------------------------------------------------------
343
+ class _ConstAdaLN(nn.Module):
344
+ def __init__(self, modulation: torch.Tensor):
345
+ super().__init__()
346
+ self.register_buffer("modulation", modulation.detach().clone())
347
+
348
+ def forward(self, c):
349
+ b = c.shape[0]
350
+ if self.modulation.shape[0] != b:
351
+ return self.modulation.expand(b, *self.modulation.shape[1:])
352
+ return self.modulation
353
+
354
+
355
+ def _replace_adaln_with_const(module: nn.Module, c: torch.Tensor) -> int:
356
+ # Only DiCoBlock is targeted: its adaLN is conditioned solely on t.
357
+ # Other adaLN_modulation submodules (e.g. _MLPResBlock in the decoder MLP)
358
+ # take a per-position latent and must not be folded.
359
+ n = 0
360
+ for child in module.modules():
361
+ if not isinstance(child, DiCoBlock):
362
+ continue
363
+ adaln = child.adaLN_modulation
364
+ if isinstance(adaln, _ConstAdaLN):
365
+ continue
366
+ with torch.no_grad():
367
+ mod = adaln(c)
368
+ child.adaLN_modulation = _ConstAdaLN(mod)
369
+ n += 1
370
+ return n
371
+
372
+
373
+ # ---------------------------------------------------------------------------
374
+ # CoD Decoder: latent → conditioning features for the denoiser
375
+ # ---------------------------------------------------------------------------
376
+ class _Decoder(nn.Module):
377
+ """ds=16, up2x=True, light=True only."""
378
+
379
+ def __init__(self, out_ch=384, z_ch=128):
380
+ super().__init__()
381
+ self.conv_in = nn.Conv2d(z_ch, out_ch, kernel_size=3, stride=1, padding=1)
382
+ self.block = nn.Sequential(
383
+ ResnetBlock(in_channels=out_ch, out_channels=out_ch),
384
+ AttnBlock(out_ch, patch_size=32),
385
+ ResnetBlock(in_channels=out_ch, out_channels=out_ch),
386
+ AttnBlock(out_ch, patch_size=32),
387
+ ResnetBlock(in_channels=out_ch, out_channels=out_ch),
388
+ )
389
+ self.norm_out = Normalize(out_ch)
390
+ self.conv_out = nn.Conv2d(out_ch, out_ch, kernel_size=3, stride=1, padding=1)
391
+ self.ada = nn.Identity()
392
+
393
+ def forward(self, z):
394
+ h = self.block(self.conv_in(z))
395
+ h = self.conv_out(nonlinearity(self.norm_out(h)))
396
+ return self.ada(h)
397
+
398
+
399
+ # ---------------------------------------------------------------------------
400
+ # DConvEncoder: image → packed (mean, logvar) latent
401
+ # ---------------------------------------------------------------------------
402
+ class _DConvEncoder(nn.Module):
403
+ def __init__(
404
+ self,
405
+ z_ch=128,
406
+ hidden_size=384,
407
+ num_blocks=21,
408
+ patch_size=16,
409
+ mlp_ratio=4.0,
410
+ head_size=768,
411
+ num_head_blocks=2,
412
+ out_ch_mult=2,
413
+ ):
414
+ super().__init__()
415
+ self.z_ch = z_ch
416
+ self.patch_size = patch_size
417
+ self.patch_cond_embed = nn.Conv2d(3, head_size, kernel_size=patch_size, stride=patch_size, bias=True)
418
+ self.head_blocks = nn.ModuleList([
419
+ _EncoderDiCoBlock(head_size, mlp_ratio=mlp_ratio) for _ in range(num_head_blocks)
420
+ ])
421
+ self.proj_down = nn.Conv2d(head_size, hidden_size, kernel_size=1, bias=True)
422
+ self.z_proj = nn.Conv2d(z_ch, hidden_size, kernel_size=1, bias=True)
423
+ self.fuse_proj = nn.Conv2d(hidden_size * 2, hidden_size, kernel_size=1, bias=True)
424
+ self.t_embedder = TimestepEmbedder(hidden_size)
425
+ self.blocks = nn.ModuleList([
426
+ DiCoBlock(hidden_size, mlp_ratio=mlp_ratio) for _ in range(num_blocks)
427
+ ])
428
+ self.norm_out = LayerNorm2d(hidden_size)
429
+ self.proj_out = nn.Conv2d(hidden_size, z_ch * out_ch_mult, kernel_size=1, bias=True)
430
+
431
+ def forward_pred(self, z_t, t, y):
432
+ cond = self.patch_cond_embed(y)
433
+ for block in self.head_blocks:
434
+ cond = block(cond)
435
+ cond = self.proj_down(cond)
436
+
437
+ s = self.fuse_proj(torch.cat([cond, self.z_proj(z_t)], dim=1))
438
+ c = self.t_embedder(t.view(-1))
439
+ for block in self.blocks:
440
+ s = block(s, c)
441
+ return self.proj_out(self.norm_out(s))
442
+
443
+
444
+ # ---------------------------------------------------------------------------
445
+ # DConv denoiser: latent (via cond) + zero noise → reconstructed image
446
+ # ---------------------------------------------------------------------------
447
+ class _YEmbedder(nn.Module):
448
+ """Holds only the CoD decoder; the original Flux2 VAE encoder side is omitted."""
449
+
450
+ def __init__(self, ch=384, z_ch=128):
451
+ super().__init__()
452
+ self.decoder = _Decoder(out_ch=ch, z_ch=z_ch)
453
+
454
+
455
+ class _DConvDenoiser(nn.Module):
456
+ def __init__(
457
+ self,
458
+ patch_size=16,
459
+ in_channels=3,
460
+ hidden_size=384,
461
+ hidden_size_x=32,
462
+ mlp_ratio=4.0,
463
+ num_blocks=24,
464
+ num_cond_blocks=21,
465
+ bottleneck_dim=128,
466
+ ):
467
+ super().__init__()
468
+ self.in_channels = in_channels
469
+ self.patch_size = patch_size
470
+ self.hidden_size = hidden_size
471
+ self.num_cond_blocks = num_cond_blocks
472
+
473
+ self.t_embedder = TimestepEmbedder(hidden_size)
474
+ self.y_embedder_x = nn.Conv2d(hidden_size, hidden_size_x * patch_size ** 2, 1, 1, 0)
475
+ self.x_embedder = NerfEmbedder(in_channels + hidden_size_x, hidden_size_x, max_freqs=8)
476
+ self.s_embedder = BottleneckPatchEmbed(patch_size, in_channels, bottleneck_dim, hidden_size, bias=True)
477
+ self.blocks = nn.ModuleList([
478
+ DiCoBlock(hidden_size, mlp_ratio=mlp_ratio) for _ in range(num_cond_blocks)
479
+ ])
480
+ self.dec_net = SimpleMLPAdaLN(
481
+ in_channels=hidden_size_x,
482
+ model_channels=hidden_size_x,
483
+ out_channels=in_channels,
484
+ z_channels=hidden_size,
485
+ num_res_blocks=num_blocks - num_cond_blocks,
486
+ patch_size=patch_size,
487
+ )
488
+ self.final_layer = NerfFinalLayer(hidden_size_x, in_channels)
489
+ self.y_embedder = _YEmbedder(ch=hidden_size, z_ch=bottleneck_dim)
490
+
491
+ def forward(self, x, t, cond):
492
+ b, _, h, w = x.shape
493
+ c = self.t_embedder(t.view(-1))
494
+
495
+ s = self.s_embedder(x, cond)
496
+ for block in self.blocks:
497
+ s = block(s, c)
498
+
499
+ length = s.shape[-2] * s.shape[-1]
500
+ s = s.permute(0, 2, 3, 1).reshape(-1, self.hidden_size)
501
+
502
+ x = torch.nn.functional.unfold(x, kernel_size=self.patch_size, stride=self.patch_size)
503
+ x = torch.cat([x, self.y_embedder_x(cond).flatten(2)], dim=1)
504
+ x = x.reshape(b, -1, self.patch_size ** 2, length).permute(0, 3, 2, 1).flatten(0, 1)
505
+ x = self.x_embedder(x)
506
+
507
+ x = self.dec_net(x, s)
508
+ x = self.final_layer(x)
509
+ x = x.transpose(1, 2).reshape(b, length, -1)
510
+ return torch.nn.functional.fold(
511
+ x.transpose(1, 2).contiguous(), (h, w),
512
+ kernel_size=self.patch_size, stride=self.patch_size,
513
+ )
514
+
515
+
516
+ # ---------------------------------------------------------------------------
517
+ # Wrapper
518
+ # ---------------------------------------------------------------------------
519
+ def _load_state_dict(ckpt_path: str):
520
+ if ckpt_path.endswith(".safetensors"):
521
+ from safetensors.torch import load_file
522
+ return load_file(ckpt_path, device="cpu")
523
+ if os.path.exists(os.path.join(ckpt_path, "checkpoint-state_dict.pt")):
524
+ ckpt_path = os.path.join(ckpt_path, "checkpoint-state_dict.pt")
525
+ elif os.path.isdir(ckpt_path):
526
+ ckpt_path = os.path.join(ckpt_path, "checkpoint", "mp_rank_00_model_states.pt")
527
+ state = torch.load(ckpt_path, map_location="cpu")
528
+ if "module" in state:
529
+ return state["module"]
530
+ if "state_dict" in state:
531
+ return state["state_dict"]
532
+ return state
533
+
534
+
535
+ class MageVAE(nn.Module):
536
+ """
537
+ Encode: DConvEncoder (one-step diffusion) → latent [B, 128, H/16, W/16]
538
+ Decode: DConvDenoiser + CoD Decoder → image [B, 3, H, W] in [-1, 1]
539
+ """
540
+
541
+ latent_channels = 128
542
+ downsample_factor = 16
543
+
544
+ def __init__(self, ckpt_path: str, sample_posterior: bool = True):
545
+ super().__init__()
546
+ self.sample_posterior = sample_posterior
547
+
548
+ self.dconv_encoder = _DConvEncoder()
549
+ self.decoder_model = _DConvDenoiser()
550
+
551
+ sd = _load_state_dict(ckpt_path)
552
+ self._load_encoder(sd, ckpt_path)
553
+ self._load_decoder(sd, ckpt_path)
554
+
555
+ # adaLN modulation depends only on t, and we always run at t=0.
556
+ # Precompute and drop the MLPs once at construction (~37M params saved).
557
+ self._freeze_adaln_cache()
558
+
559
+ def _load_encoder(self, sd, ckpt_path):
560
+ prefix = "student.dconv_encoder."
561
+ enc_sd = {k[len(prefix):]: v for k, v in sd.items() if k.startswith(prefix)}
562
+ if not enc_sd:
563
+ raise RuntimeError(f"CoDEncoder: no '{prefix}*' keys in {ckpt_path}")
564
+ proj = enc_sd.get("proj_out.weight")
565
+ if proj is None or proj.shape[0] != 2 * self.latent_channels:
566
+ raise RuntimeError(
567
+ f"CoDEncoder: expected packed mean+logvar (proj_out out_channels="
568
+ f"{2 * self.latent_channels}), got {None if proj is None else tuple(proj.shape)}"
569
+ )
570
+ missing, unexpected = self.dconv_encoder.load_state_dict(enc_sd, strict=False)
571
+ logger.info(
572
+ f"CoDEncoder: loaded {len(enc_sd)} keys, "
573
+ f"missing={len(missing)}, unexpected={len(unexpected)}"
574
+ )
575
+ if missing:
576
+ logger.warning(f"CoDEncoder missing: {missing[:10]}")
577
+
578
+ def _load_decoder(self, sd, ckpt_path):
579
+ prefix = "pipeline."
580
+ if not any(k.startswith(prefix) for k in sd):
581
+ raise RuntimeError(f"CoDDecoder: no '{prefix}*' keys in {ckpt_path}")
582
+ model_dict = self.decoder_model.state_dict()
583
+ matched = {}
584
+ for k, v in sd.items():
585
+ if not k.startswith(prefix):
586
+ continue
587
+ new_k = k[len(prefix):]
588
+ if new_k.startswith("y_embedder.encoder.") or new_k.startswith("y_embedder.bottleneck."):
589
+ continue
590
+ if new_k in model_dict and model_dict[new_k].shape == v.shape:
591
+ matched[new_k] = v
592
+ self.decoder_model.load_state_dict(matched, strict=False)
593
+ logger.info(f"CoDDecoder: loaded {len(matched)} params (denoiser + y_embedder.decoder)")
594
+ if not matched:
595
+ raise RuntimeError(f"CoDDecoder: 0 params matched from {ckpt_path}")
596
+
597
+ @torch.no_grad()
598
+ def _moments(self, x: torch.Tensor):
599
+ B, _, H, W = x.shape
600
+ ps = self.dconv_encoder.patch_size
601
+ z_t = torch.zeros(B, self.dconv_encoder.z_ch, H // ps, W // ps, device=x.device, dtype=x.dtype)
602
+ t = torch.zeros(B, device=x.device, dtype=x.dtype)
603
+ out = self.dconv_encoder.forward_pred(z_t, t, x)
604
+ mean = out[:, : self.latent_channels]
605
+ logvar = out[:, self.latent_channels :].clamp(min=-20.0, max=10.0)
606
+ return mean, logvar
607
+
608
+ @torch.no_grad()
609
+ def _encode_moments(self, x: torch.Tensor):
610
+ # Compile target: pure deterministic part of encode (no RNG, no
611
+ # asserts), so torch.compile produces a single dynamic graph.
612
+ return self._moments(x)
613
+
614
+ @torch.no_grad()
615
+ def encode(self, x: torch.Tensor) -> torch.Tensor:
616
+ ps = self.dconv_encoder.patch_size
617
+ H, W = x.shape[-2], x.shape[-1]
618
+ if H % ps or W % ps:
619
+ raise ValueError(f"H, W must be multiples of {ps}, got ({H}, {W})")
620
+ mean, logvar = self._encode_moments(x)
621
+ if self.sample_posterior:
622
+ return mean + torch.exp(0.5 * logvar) * torch.randn_like(mean)
623
+ return mean
624
+
625
+ @torch.no_grad()
626
+ def decode(self, z: torch.Tensor) -> torch.Tensor:
627
+ cond = self.decoder_model.y_embedder.decoder(z)
628
+ B = z.shape[0]
629
+ H = z.shape[2] * self.downsample_factor
630
+ W = z.shape[3] * self.downsample_factor
631
+ noise = torch.zeros(B, 3, H, W, device=z.device, dtype=z.dtype)
632
+ t = torch.zeros(B, device=z.device, dtype=z.dtype)
633
+ return self.decoder_model.forward(noise, t, cond)
634
+
635
+ @property
636
+ def device(self):
637
+ return next(self.parameters()).device
638
+
639
+ @property
640
+ def dtype(self):
641
+ return next(self.parameters()).dtype
642
+
643
+ def _freeze_adaln_cache(self):
644
+ """Constant-fold adaLN_modulation MLPs at t=0 (encoder + decoder)."""
645
+ device = next(self.parameters()).device
646
+ dtype = next(self.parameters()).dtype
647
+ t = torch.zeros(1, device=device, dtype=dtype)
648
+ c_enc = self.dconv_encoder.t_embedder(t)
649
+ _replace_adaln_with_const(self.dconv_encoder, c_enc)
650
+ c_dec = self.decoder_model.t_embedder(t)
651
+ _replace_adaln_with_const(self.decoder_model, c_dec)
vendor/mage_flow/models/modules/text_encoder.py ADDED
@@ -0,0 +1,707 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Qwen3-VL text encoder: custom HF model + packing-aware forward patches + TextEncoder wrapper."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+
8
+ try:
9
+ from typing import Unpack
10
+ except ImportError:
11
+ from typing_extensions import Unpack
12
+
13
+ import torch
14
+ from loguru import logger
15
+ from torch import nn
16
+ from transformers import AutoProcessor, AutoTokenizer, Cache, Qwen3VLForConditionalGeneration
17
+ from transformers.cache_utils import DynamicCache
18
+ from transformers.masking_utils import create_causal_mask
19
+ from transformers.modeling_flash_attention_utils import FlashAttentionKwargs
20
+ from transformers.modeling_outputs import BaseModelOutputWithPast
21
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
22
+ from transformers.models.qwen3_vl.modeling_qwen3_vl import (
23
+ Qwen3VLCausalLMOutputWithPast,
24
+ apply_rotary_pos_emb,
25
+ eager_attention_forward,
26
+ )
27
+ from transformers.utils import ModelOutput
28
+
29
+ from ._attn_backend import flash_attn_varlen_func
30
+
31
+
32
+ # ===========================================================================
33
+ # Custom Qwen3-VL model (customizable forward output)
34
+ # ===========================================================================
35
+
36
+ @dataclass
37
+ class Qwen3VLModelOutput(ModelOutput):
38
+ """Flexible output class for custom Qwen3-VL model."""
39
+
40
+ loss: torch.FloatTensor | None = None
41
+ logits: torch.FloatTensor | None = None
42
+ past_key_values: Cache | None = None
43
+ hidden_states: tuple[torch.FloatTensor, ...] | None = None
44
+ last_hidden_state: torch.FloatTensor | None = None
45
+ attentions: tuple[torch.FloatTensor, ...] | None = None
46
+ rope_deltas: torch.LongTensor | None = None
47
+
48
+
49
+ class CustomQwen3VLForConditionalGeneration(Qwen3VLForConditionalGeneration):
50
+ """
51
+ Custom Qwen3-VL model that allows customizing the forward output.
52
+
53
+ This class inherits from Qwen3VLForConditionalGeneration and provides
54
+ hooks to customize what is returned from the forward pass.
55
+
56
+ Example usage:
57
+ ```python
58
+ model = CustomQwen3VLForConditionalGeneration.from_pretrained(
59
+ "Qwen/Qwen3-VL-8B-Instruct",
60
+ attn_implementation="flash_attention_2" # Use flash attention for faster inference
61
+ )
62
+
63
+ # Option 1: Use built-in output modes
64
+ model.set_output_mode("embedding") # Only return last hidden state (default)
65
+ model.set_output_mode("full") # Return everything
66
+ model.set_output_mode("logits") # Only return logits
67
+
68
+ # Option 2: Set a custom output processor
69
+ def my_custom_output(hidden_states, logits, outputs, **kwargs):
70
+ return {"embeddings": hidden_states, "pooled": hidden_states.mean(dim=1)}
71
+ model.set_output_processor(my_custom_output)
72
+ ```
73
+ """
74
+
75
+ # Output mode constants
76
+ OUTPUT_MODE_FULL = "full"
77
+ OUTPUT_MODE_EMBEDDING = "embedding"
78
+ OUTPUT_MODE_LOGITS = "logits"
79
+ OUTPUT_MODE_HIDDEN = "hidden"
80
+
81
+ def __init__(self, config):
82
+ super().__init__(config)
83
+ self._output_mode = self.OUTPUT_MODE_EMBEDDING
84
+ self._skip_lm_head = True
85
+
86
+ def set_output_mode(self, mode: str):
87
+ """
88
+ Set the output mode for the forward pass.
89
+
90
+ Args:
91
+ mode: One of:
92
+ - "full": Return full Qwen3VLCausalLMOutputWithPast
93
+ - "embedding": Only return last hidden state (skip lm_head) (default)
94
+ - "logits": Only return logits
95
+ - "hidden": Return all hidden states
96
+ """
97
+ valid_modes = [
98
+ self.OUTPUT_MODE_FULL,
99
+ self.OUTPUT_MODE_EMBEDDING,
100
+ self.OUTPUT_MODE_LOGITS,
101
+ self.OUTPUT_MODE_HIDDEN,
102
+ ]
103
+ if mode not in valid_modes:
104
+ raise ValueError(f"Invalid output mode: {mode}. Must be one of {valid_modes}")
105
+ self._output_mode = mode
106
+ self._skip_lm_head = mode == self.OUTPUT_MODE_EMBEDDING
107
+
108
+ def forward(
109
+ self,
110
+ input_ids: torch.LongTensor | None = None,
111
+ attention_mask: torch.Tensor | None = None,
112
+ position_ids: torch.LongTensor | None = None,
113
+ past_key_values: Cache | None = None,
114
+ inputs_embeds: torch.FloatTensor | None = None,
115
+ labels: torch.LongTensor | None = None,
116
+ pixel_values: torch.Tensor | None = None,
117
+ pixel_values_videos: torch.FloatTensor | None = None,
118
+ image_grid_thw: torch.LongTensor | None = None,
119
+ video_grid_thw: torch.LongTensor | None = None,
120
+ cache_position: torch.LongTensor | None = None,
121
+ logits_to_keep: int | torch.Tensor = 0,
122
+ output_attentions: bool | None = None,
123
+ output_hidden_states: bool | None = None,
124
+ return_dict: bool | None = None,
125
+ **kwargs,
126
+ ) -> Qwen3VLCausalLMOutputWithPast | Qwen3VLModelOutput | dict | torch.Tensor:
127
+ """
128
+ Forward pass with customizable output.
129
+
130
+ Returns different outputs based on the configured output mode or custom processor.
131
+ """
132
+ output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
133
+ output_hidden_states = (
134
+ output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
135
+ )
136
+
137
+ # Get outputs from the base model (Qwen3VLModel)
138
+ outputs = self.model(
139
+ input_ids=input_ids,
140
+ pixel_values=pixel_values,
141
+ pixel_values_videos=pixel_values_videos,
142
+ image_grid_thw=image_grid_thw,
143
+ video_grid_thw=video_grid_thw,
144
+ position_ids=position_ids,
145
+ attention_mask=attention_mask,
146
+ past_key_values=past_key_values,
147
+ inputs_embeds=inputs_embeds,
148
+ cache_position=cache_position,
149
+ output_attentions=output_attentions,
150
+ output_hidden_states=output_hidden_states,
151
+ return_dict=True,
152
+ **kwargs,
153
+ )
154
+
155
+ # Get the last hidden state
156
+ hidden_states = outputs[0] # This is the last hidden state
157
+
158
+ # Compute logits if not skipping lm_head
159
+ logits = None
160
+ if not self._skip_lm_head:
161
+ slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
162
+ logits = self.lm_head(hidden_states[:, slice_indices, :])
163
+
164
+ # Compute loss if labels are provided
165
+ loss = None
166
+ if labels is not None and logits is not None:
167
+ loss = self.loss_function(
168
+ logits=logits, labels=labels, vocab_size=self.config.text_config.vocab_size, **kwargs
169
+ )
170
+
171
+ # Return based on output mode
172
+ if self._output_mode == self.OUTPUT_MODE_EMBEDDING:
173
+ return Qwen3VLModelOutput(
174
+ last_hidden_state=hidden_states,
175
+ past_key_values=outputs.past_key_values,
176
+ attentions=outputs.attentions,
177
+ rope_deltas=outputs.rope_deltas,
178
+ )
179
+ elif self._output_mode == self.OUTPUT_MODE_LOGITS:
180
+ return logits
181
+ elif self._output_mode == self.OUTPUT_MODE_HIDDEN:
182
+ return Qwen3VLModelOutput(
183
+ last_hidden_state=hidden_states,
184
+ hidden_states=outputs.hidden_states,
185
+ past_key_values=outputs.past_key_values,
186
+ attentions=outputs.attentions,
187
+ rope_deltas=outputs.rope_deltas,
188
+ )
189
+ else: # OUTPUT_MODE_FULL
190
+ return Qwen3VLCausalLMOutputWithPast(
191
+ loss=loss,
192
+ logits=logits,
193
+ past_key_values=outputs.past_key_values,
194
+ hidden_states=outputs.hidden_states,
195
+ attentions=outputs.attentions,
196
+ rope_deltas=outputs.rope_deltas,
197
+ )
198
+
199
+
200
+ # ===========================================================================
201
+ # Packing-aware forward patches (cu_seqlens) for the Qwen3-VL text encoder
202
+ # ===========================================================================
203
+
204
+ def model_forward(
205
+ self,
206
+ input_ids: torch.LongTensor | None = None,
207
+ attention_mask: torch.Tensor | None = None,
208
+ position_ids: torch.LongTensor | None = None,
209
+ past_key_values: Cache | None = None,
210
+ inputs_embeds: torch.FloatTensor | None = None,
211
+ use_cache: bool | None = None,
212
+ cache_position: torch.LongTensor | None = None,
213
+ # args for deepstack
214
+ visual_pos_masks: torch.Tensor | None = None,
215
+ deepstack_visual_embeds: list[torch.Tensor] | None = None,
216
+ **kwargs: Unpack[FlashAttentionKwargs],
217
+ ) -> tuple | BaseModelOutputWithPast:
218
+ r"""
219
+ visual_pos_masks (`torch.Tensor` of shape `(batch_size, seqlen)`, *optional*):
220
+ The mask of the visual positions.
221
+ deepstack_visual_embeds (`list[torch.Tensor]`, *optional*):
222
+ The deepstack visual embeddings. The shape is (num_layers, visual_seqlen, embed_dim).
223
+ The feature is extracted from the different visual encoder layers, and fed to the decoder
224
+ hidden states. It's from the paper DeepStack(https://arxiv.org/abs/2406.04334).
225
+ """
226
+ if (input_ids is None) ^ (inputs_embeds is not None):
227
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
228
+
229
+ # torch.jit.trace() doesn't support cache objects in the output
230
+ if use_cache and past_key_values is None and not torch.jit.is_tracing():
231
+ past_key_values = DynamicCache(config=self.config)
232
+
233
+ if inputs_embeds is None:
234
+ inputs_embeds = self.embed_tokens(input_ids)
235
+
236
+ if cache_position is None:
237
+ past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
238
+ cache_position = torch.arange(
239
+ past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device
240
+ )
241
+
242
+ # the hard coded `3` is for temporal, height and width.
243
+ if position_ids is None:
244
+ position_ids = cache_position.view(1, 1, -1).expand(3, inputs_embeds.shape[0], -1)
245
+ elif position_ids.ndim == 2:
246
+ position_ids = position_ids[None, ...].expand(3, position_ids.shape[0], -1)
247
+
248
+ if position_ids.ndim == 3 and position_ids.shape[0] == 4:
249
+ text_position_ids = position_ids[0]
250
+ position_ids = position_ids[1:]
251
+ else:
252
+ text_position_ids = position_ids[0]
253
+
254
+ if kwargs.get("cu_seqlens") is None:
255
+ attention_mask = create_causal_mask(
256
+ config=self.config,
257
+ input_embeds=inputs_embeds,
258
+ attention_mask=attention_mask,
259
+ cache_position=cache_position,
260
+ past_key_values=past_key_values,
261
+ position_ids=text_position_ids,
262
+ )
263
+
264
+ hidden_states = inputs_embeds
265
+
266
+ # create position embeddings to be shared across the decoder layers
267
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
268
+
269
+ # decoder layers
270
+ for layer_idx, decoder_layer in enumerate(self.layers):
271
+ layer_outputs = decoder_layer(
272
+ hidden_states,
273
+ attention_mask=attention_mask,
274
+ position_ids=text_position_ids,
275
+ past_key_values=past_key_values,
276
+ cache_position=cache_position,
277
+ position_embeddings=position_embeddings,
278
+ **kwargs,
279
+ )
280
+ hidden_states = layer_outputs
281
+
282
+ # add visual features to the hidden states of first several layers
283
+ if deepstack_visual_embeds is not None and layer_idx in range(len(deepstack_visual_embeds)):
284
+ hidden_states = self._deepstack_process(
285
+ hidden_states,
286
+ visual_pos_masks,
287
+ deepstack_visual_embeds[layer_idx],
288
+ )
289
+
290
+ hidden_states = self.norm(hidden_states)
291
+
292
+ return BaseModelOutputWithPast(
293
+ last_hidden_state=hidden_states,
294
+ past_key_values=past_key_values,
295
+ )
296
+
297
+
298
+ def forward(
299
+ self,
300
+ hidden_states: torch.Tensor,
301
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
302
+ attention_mask: torch.Tensor | None,
303
+ past_key_values: Cache | None = None,
304
+ cache_position: torch.LongTensor | None = None,
305
+ **kwargs: Unpack[FlashAttentionKwargs],
306
+ ) -> tuple[torch.Tensor, torch.Tensor | None]:
307
+ input_shape = hidden_states.shape[:-1]
308
+ hidden_shape = (*input_shape, -1, self.head_dim)
309
+
310
+ query_states = self.q_norm(self.q_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
311
+ key_states = self.k_norm(self.k_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
312
+ value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
313
+
314
+ cos, sin = position_embeddings
315
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
316
+
317
+ if past_key_values is not None:
318
+ # sin and cos are specific to RoPE models; cache_position needed for the static cache
319
+ cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
320
+ key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx, cache_kwargs)
321
+
322
+ cu_seqlens = kwargs.get("cu_seqlens", None)
323
+
324
+ if cu_seqlens is None:
325
+ attention_interface: Callable = eager_attention_forward
326
+ if self.config._attn_implementation != "eager":
327
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
328
+
329
+ attn_output, attn_weights = attention_interface(
330
+ self,
331
+ query_states,
332
+ key_states,
333
+ value_states,
334
+ attention_mask,
335
+ dropout=0.0 if not self.training else self.attention_dropout,
336
+ scaling=self.scaling,
337
+ **kwargs,
338
+ )
339
+ else:
340
+ max_seqlen = torch.diff(cu_seqlens).max().item() if cu_seqlens is not None else None
341
+ query_states = query_states.transpose(1, 2).squeeze(0)
342
+ key_states = key_states.transpose(1, 2).squeeze(0)
343
+ value_states = value_states.transpose(1, 2).squeeze(0)
344
+ attn_output = flash_attn_varlen_func(
345
+ q=query_states,
346
+ k=key_states,
347
+ v=value_states,
348
+ cu_seqlens_q=cu_seqlens,
349
+ cu_seqlens_k=cu_seqlens,
350
+ max_seqlen_q=max_seqlen,
351
+ max_seqlen_k=max_seqlen,
352
+ causal=True,
353
+ window_size=(-1, -1),
354
+ softmax_scale=self.head_dim**-0.5,
355
+ dropout_p=0.0,
356
+ )
357
+
358
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
359
+ attn_output = self.o_proj(attn_output)
360
+ return attn_output, None
361
+
362
+
363
+ def qwen3_patch_forward():
364
+ """Patch the Qwen3-VL text model + attention forwards to support packed
365
+ varlen (cu_seqlens) inputs used by ``TextEncoder.forward``."""
366
+ from transformers.models.qwen3_vl.modeling_qwen3_vl import Qwen3VLTextAttention, Qwen3VLTextModel
367
+
368
+ Qwen3VLTextModel.forward = model_forward
369
+ Qwen3VLTextAttention.forward = forward
370
+
371
+
372
+ # ===========================================================================
373
+ # TextEncoder wrapper (packed text -> DiT conditioning embeddings)
374
+ # ===========================================================================
375
+ _FA2_ALIASES = {"flash2", "fa2", "flash_attention_2", "flash_attn_2"}
376
+ _FA4_ALIASES = {"flash4", "fa4", "flash_attention_4", "flash_attn_4"}
377
+ _SDPA_ALIASES = {"sdpa", "torch_sdpa", "scaled_dot_product_attention"}
378
+
379
+
380
+ def _resolve_hf_attn_impl(attn_type: str) -> str:
381
+ """Map a project-level attn_type to a HuggingFace ``attn_implementation`` string.
382
+
383
+ ``VF_HF_ATTN_IMPL`` env var, if set, takes precedence (useful for forcing
384
+ sdpa on machines without flash-attn). For FA4 we additionally probe that
385
+ the CUTE-DSL kernel is importable and (when available) ask the HF helper
386
+ to confirm; if not, fall back to sdpa rather than crashing at load time.
387
+ """
388
+ override = os.environ.get("VF_HF_ATTN_IMPL")
389
+ if override:
390
+ return override
391
+
392
+ name = attn_type.lower().strip()
393
+ if name in _FA2_ALIASES:
394
+ return "flash_attention_2"
395
+ if name in _FA4_ALIASES:
396
+ try:
397
+ import flash_attn.cute # noqa: F401
398
+ fa4_importable = True
399
+ except Exception:
400
+ fa4_importable = False
401
+ if fa4_importable:
402
+ try:
403
+ from transformers.utils.import_utils import is_flash_attn_4_available
404
+ if is_flash_attn_4_available():
405
+ return "flash_attention_4"
406
+ except ImportError:
407
+ return "flash_attention_4"
408
+ logger.warning(
409
+ "attn_type=flash4 requested but flash_attn.cute is unavailable; "
410
+ "falling back to sdpa for HF text encoder."
411
+ )
412
+ return "sdpa"
413
+ if name in _SDPA_ALIASES:
414
+ return "sdpa"
415
+ raise ValueError(
416
+ f"Unknown attn_type {attn_type!r}; expected one of "
417
+ f"{sorted(_FA2_ALIASES | _FA4_ALIASES | _SDPA_ALIASES)}"
418
+ )
419
+
420
+
421
+ SEQ_MULTI_OF = 32
422
+
423
+
424
+
425
+ class TextEncoder(nn.Module):
426
+ def __init__(
427
+ self,
428
+ model_name: str,
429
+ version: str,
430
+ tokenizer_max_length: int,
431
+ prompt_template: dict | None,
432
+ dit_structure: dict,
433
+ use_packed_text_infer: bool = False,
434
+ attn_type: str = "flash2",
435
+ **hf_kwargs,
436
+ ):
437
+ super().__init__()
438
+ self.model_name = model_name
439
+ self.tokenizer_max_length = tokenizer_max_length
440
+ self.tokenizer: AutoTokenizer = AutoTokenizer.from_pretrained(version)
441
+ self.tokenizer.padding_side = "right"
442
+
443
+ hf_attn_impl = _resolve_hf_attn_impl(attn_type)
444
+ logger.info(f"TextEncoder attn_type={attn_type} -> attn_implementation={hf_attn_impl}")
445
+
446
+ logger.info("init vl model: qwen3")
447
+ self.hf_module: CustomQwen3VLForConditionalGeneration = CustomQwen3VLForConditionalGeneration.from_pretrained(
448
+ version,
449
+ attn_implementation=hf_attn_impl,
450
+ **hf_kwargs
451
+ )
452
+
453
+ # Use local_files_only if version is a local path (absolute path or contains path separators)
454
+ is_local = version.startswith("/") or os.sep in version
455
+ self.processor = AutoProcessor.from_pretrained(version, local_files_only=is_local)
456
+
457
+ self.hf_module = self.hf_module.eval().requires_grad_(False)
458
+
459
+ prompt_template = prompt_template or {}
460
+ self.prompt_template_encode = prompt_template.get("template", "")
461
+ self.prompt_template_encode_start_idx = prompt_template.get("start_idx", 0)
462
+ self.dit_structure = dit_structure
463
+ self.use_packed_text_infer = use_packed_text_infer
464
+
465
+ def forward(
466
+ self,
467
+ input_ids: torch.Tensor,
468
+ cu_seqlens: torch.Tensor,
469
+ inputs: dict | None = None,
470
+ drop_idx_override: int | None = None,
471
+ ):
472
+ """Encode packed text (varlen ``cu_seqlens``) into DiT conditioning embeddings.
473
+
474
+ This is the sole text-embedding path — both t2i and edit call it. Uses
475
+ Flash-Attention-2's varlen capability (``cu_seqlens``) via the patched
476
+ Qwen3-VL forward to process several concatenated sequences in a single
477
+ launch, with no padding. Verified numerically identical to a padded-batch
478
+ forward with per-sample cu_seqlens isolation (zero cross-contamination).
479
+
480
+ Args:
481
+ input_ids: Packed token ids ``[Total_L]``.
482
+ cu_seqlens: Cumulative sequence lengths ``[B+1]``.
483
+ inputs: Optional dict with additional model inputs (e.g. ``pixel_values``,
484
+ ``image_grid_thw`` for the multimodal edit path). Passed through to
485
+ the text encoder.
486
+ drop_idx_override: If set, override the number of leading (system-prompt)
487
+ tokens to drop per sequence. Use 0 for multi-turn where the system
488
+ prompt is embedded in the conversation and should not be stripped.
489
+
490
+ Returns:
491
+ dict with keys:
492
+ - ``txt``: text embeddings ``[Total_L - B*drop_idx, D]`` (system prompt dropped)
493
+ - ``vec``: pooled text embeddings ``[B, D]``
494
+ - ``txt_seq_lens``: per-sequence lengths ``[B]`` (after dropping system prompt)
495
+ """
496
+ # Compute seqlens from cu_seqlens
497
+ seqlens = cu_seqlens[1:] - cu_seqlens[:-1]
498
+ seqlens_list = seqlens.cpu().tolist()
499
+
500
+ # Build position_ids for packing: each sequence starts from 0
501
+ position_ids_list = []
502
+ for length in seqlens_list:
503
+ position_ids_list.append(torch.arange(length, device=input_ids.device))
504
+ position_ids = torch.cat(position_ids_list) # [Total_L]
505
+
506
+ # Reshape for model input: [1, Total_L]
507
+ input_ids_packed = input_ids.unsqueeze(0) # [1, Total_L]
508
+ position_ids_packed = position_ids.unsqueeze(0) # [1, Total_L]
509
+
510
+ # Move to text encoder device
511
+ device = self.hf_module.device
512
+ input_ids_packed = input_ids_packed.to(device)
513
+ position_ids_packed = position_ids_packed.to(device)
514
+
515
+ # Get text embeddings (the text encoder is always frozen)
516
+ with torch.no_grad():
517
+ forward_kwargs = {
518
+ "input_ids": input_ids_packed,
519
+ "cu_seqlens": cu_seqlens,
520
+ "position_ids": position_ids_packed,
521
+ "output_hidden_states": False,
522
+ "max_seqlen": None,
523
+ }
524
+ # Pass multimodal inputs for edit mode (reference images)
525
+ if inputs is not None:
526
+ for key in ("pixel_values", "image_grid_thw"):
527
+ if key in inputs and inputs[key] is not None:
528
+ val = inputs[key]
529
+ if hasattr(val, "to"):
530
+ val = val.to(device)
531
+ forward_kwargs[key] = val
532
+ outputs = self.hf_module(**forward_kwargs)
533
+
534
+ # Extract hidden state
535
+ if hasattr(outputs, "last_hidden_state") and outputs.last_hidden_state is not None:
536
+ hidden = outputs.last_hidden_state # [1, Total_L, D]
537
+ elif hasattr(outputs, "hidden_states"):
538
+ hidden = outputs.hidden_states[-1]
539
+
540
+ # Remove batch dimension: [Total_L, D]
541
+ hidden = hidden.squeeze(0)
542
+
543
+ # Get drop_idx (system prompt length to skip).
544
+ # For multi-turn, drop_idx_override=0 is passed since system prompt is in the messages.
545
+ if drop_idx_override is not None:
546
+ drop_idx = drop_idx_override
547
+ else:
548
+ drop_idx = self.prompt_template_encode_start_idx
549
+
550
+ # Split hidden states by sequence
551
+ hidden_split = torch.split(hidden, seqlens_list, dim=0)
552
+
553
+ # Extract valid embeddings (drop system prompt) and compute vec
554
+ txt_list = []
555
+ vec_list = []
556
+ valid_lengths = []
557
+
558
+ for h in hidden_split:
559
+ # Drop system prompt tokens
560
+ h_valid = h[drop_idx:] # [seq_len - drop_idx, D]
561
+ txt_list.append(h_valid)
562
+ valid_lengths.append(h_valid.shape[0])
563
+
564
+ # Compute pooled embedding (mean of valid tokens only, after dropping system prompt)
565
+ vec_list.append(h_valid.mean(dim=0)) # [D]
566
+
567
+ txt = torch.cat(txt_list, dim=0) # [Total_valid, D]
568
+ vec = torch.stack(vec_list, dim=0) # [B, D]
569
+ txt_seq_lens = torch.tensor(valid_lengths, device=input_ids.device)
570
+
571
+ result = {
572
+ "txt": txt,
573
+ "vec": vec,
574
+ "txt_seq_lens": txt_seq_lens,
575
+ }
576
+
577
+ return result
578
+
579
+ # ------------------------------------------------------------------
580
+ # Mandatory content-policy screening (same Qwen3-VL weights)
581
+ # ------------------------------------------------------------------
582
+ # The policy classifier lives HERE, on the text encoder, so it runs on the
583
+ # exact weights that produce the diffusion conditioning and is not a
584
+ # separable, toggleable pre-pass in the pipeline. The classifier needs
585
+ # autoregressive ``.generate()`` (JSON verdict) whereas conditioning is a
586
+ # single embedding forward — they cannot be one GPU forward without a
587
+ # trained classification head, so "fused" here means: same module, same
588
+ # weights, always run, FAIL-CLOSED (any error blocks).
589
+
590
+ def screen_text(self, prompt: str, max_new_tokens: int = 160):
591
+ """Classify a text-to-image ``prompt`` against the content policy.
592
+
593
+ Returns a ``FilterVerdict``. FAIL-CLOSED: any error (generation, parse)
594
+ returns ``violates=True`` so a broken classifier cannot be used as a
595
+ bypass. An empty prompt is not a violation.
596
+ """
597
+ from .mage_text import (
598
+ CONTENT_FILTER_SYSTEM, FilterVerdict, _extract_json_object,
599
+ _full_output_mode,
600
+ )
601
+
602
+ if not prompt or not prompt.strip():
603
+ return FilterVerdict(False, [], "empty prompt", "")
604
+ try:
605
+ tokenizer = self.tokenizer
606
+ hf = self.hf_module
607
+ device = next(hf.parameters()).device
608
+
609
+ messages = [
610
+ {"role": "system", "content": CONTENT_FILTER_SYSTEM},
611
+ {"role": "user", "content": f"Prompt to classify:\n{prompt}"},
612
+ ]
613
+ text = tokenizer.apply_chat_template(
614
+ messages, tokenize=False, add_generation_prompt=True)
615
+ inputs = tokenizer(text, return_tensors="pt").to(device)
616
+
617
+ eos_id = tokenizer.eos_token_id
618
+ pad_id = tokenizer.pad_token_id if tokenizer.pad_token_id is not None else eos_id
619
+
620
+ with _full_output_mode(hf), torch.no_grad():
621
+ out = hf.generate(
622
+ **inputs, max_new_tokens=max_new_tokens, do_sample=False,
623
+ pad_token_id=pad_id, eos_token_id=eos_id)
624
+ gen = tokenizer.decode(
625
+ out[0, inputs.input_ids.shape[1]:], skip_special_tokens=True).strip()
626
+
627
+ parsed = _extract_json_object(gen)
628
+ violates = bool(parsed.get("violates", False))
629
+ cats = [c for c in (parsed.get("categories", []) or []) if isinstance(c, str)]
630
+ reason = str(parsed.get("reason", "")).strip()
631
+ return FilterVerdict(violates, cats, reason, gen)
632
+ except Exception as exc: # noqa: BLE001
633
+ # FAIL-CLOSED: block on any screening error.
634
+ return FilterVerdict(
635
+ True, ["policy"], f"filter error (blocked): {type(exc).__name__}: {exc}", "")
636
+
637
+ def screen_edit(self, prompt: str, ref_images, max_new_tokens: int = 192):
638
+ """Classify an image-EDIT request (source image(s) + instruction).
639
+
640
+ Considers BOTH the source image(s) and the instruction via multimodal
641
+ Qwen3-VL. Falls back to :meth:`screen_text` when no image is given.
642
+ FAIL-CLOSED: any error returns ``violates=True``.
643
+ """
644
+ from PIL import Image
645
+
646
+ from .mage_text import (
647
+ CONTENT_FILTER_EDIT_SYSTEM, FilterVerdict, _extract_json_object,
648
+ _full_output_mode,
649
+ )
650
+
651
+ pils = [ref_images] if isinstance(ref_images, Image.Image) else list(ref_images)
652
+ pils = [p.convert("RGB") for p in pils if p is not None]
653
+ if not pils:
654
+ return self.screen_text(prompt, max_new_tokens=max_new_tokens)
655
+
656
+ instruction = (prompt or "").strip() or "(no textual instruction)"
657
+ try:
658
+ processor = self.processor
659
+ tokenizer = self.tokenizer
660
+ hf = self.hf_module
661
+ device = next(hf.parameters()).device
662
+
663
+ user_content = [{"type": "image"} for _ in pils]
664
+ user_content.append({
665
+ "type": "text",
666
+ "text": (
667
+ f"There {'is' if len(pils) == 1 else 'are'} {len(pils)} source "
668
+ f"image(s) above. Edit instruction: {instruction}\n"
669
+ "Classify this edit request."
670
+ ),
671
+ })
672
+ messages = [
673
+ {"role": "system", "content": CONTENT_FILTER_EDIT_SYSTEM},
674
+ {"role": "user", "content": user_content},
675
+ ]
676
+ text = processor.apply_chat_template(
677
+ messages, tokenize=False, add_generation_prompt=True)
678
+ inputs = processor(
679
+ text=[text], images=pils, padding=True, return_tensors="pt").to(device)
680
+
681
+ eos_id = tokenizer.eos_token_id
682
+ pad_id = tokenizer.pad_token_id if tokenizer.pad_token_id is not None else eos_id
683
+
684
+ # Keep only the kwargs Qwen3-VL .generate() consumes.
685
+ gen_inputs = {
686
+ k: inputs[k]
687
+ for k in ("input_ids", "attention_mask", "pixel_values", "image_grid_thw")
688
+ if k in inputs and inputs[k] is not None
689
+ }
690
+ input_len = gen_inputs["input_ids"].shape[1]
691
+
692
+ with _full_output_mode(hf), torch.no_grad():
693
+ out = hf.generate(
694
+ **gen_inputs, max_new_tokens=max_new_tokens, do_sample=False,
695
+ pad_token_id=pad_id, eos_token_id=eos_id)
696
+ gen = tokenizer.decode(out[0, input_len:], skip_special_tokens=True).strip()
697
+
698
+ parsed = _extract_json_object(gen)
699
+ violates = bool(parsed.get("violates", False))
700
+ cats = [c for c in (parsed.get("categories", []) or []) if isinstance(c, str)]
701
+ reason = str(parsed.get("reason", "")).strip()
702
+ return FilterVerdict(violates, cats, reason, gen)
703
+ except Exception as exc: # noqa: BLE001
704
+ # FAIL-CLOSED: block on any screening error.
705
+ return FilterVerdict(
706
+ True, ["policy"], f"edit filter error (blocked): {type(exc).__name__}: {exc}", "")
707
+
vendor/mage_flow/models/utils.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import math
3
+ import os
4
+
5
+ import torch
6
+ from einops import rearrange
7
+ from loguru import logger
8
+ from safetensors.torch import load_file
9
+ from safetensors.torch import load_file as load_sft
10
+ from torch import Tensor
11
+
12
+ from .mage_flow import MageFlow, MageFlowParams
13
+
14
+
15
+ def get_noise(
16
+ num_samples: int,
17
+ channel: int,
18
+ height: int,
19
+ width: int,
20
+ device: torch.device,
21
+ dtype: torch.dtype,
22
+ seed: int,
23
+ ):
24
+ # MageVAE: 16x downsample, no patch packing
25
+ return torch.randn(
26
+ num_samples,
27
+ channel,
28
+ math.ceil(height / 16),
29
+ math.ceil(width / 16),
30
+ device=device,
31
+ dtype=dtype,
32
+ generator=torch.Generator(device=device).manual_seed(seed),
33
+ )
34
+
35
+
36
+ def unpack(x: Tensor, height: int, width: int) -> Tensor:
37
+ # MageVAE: [B, H*W, C] -> [B, C, H, W], no patch unpacking
38
+ return rearrange(
39
+ x,
40
+ "b (h w) c -> b c h w",
41
+ h=math.ceil(height / 16),
42
+ w=math.ceil(width / 16),
43
+ )
44
+
45
+
46
+ PROMPT_TEMPLATE = {
47
+ "default": {"template": "{}", "start_idx": 0},
48
+ "default-nonthinking": {"template": "{}<think>\n\n</think>\n\n", "start_idx": 0},
49
+ "mage-flow": {
50
+ "template": (
51
+ "<|im_start|>system\nDescribe the image by detailing the color, shape, size, texture, quantity, "
52
+ "text, spatial relationships of the objects and background:"
53
+ "<|im_end|>\n<|im_start|>user\n{}<|im_end|>\n<|im_start|>assistant\n"
54
+ ),
55
+ "start_idx": 34,
56
+ },
57
+ "mage-flow-edit": {
58
+ "template": (
59
+ "<|im_start|>system\nDescribe the key features of the input image (color, shape, size, texture,"
60
+ " objects, background), then explain how the user's text instruction should alter or modify the image. "
61
+ "Generate a new image that meets the user's requirements while maintaining consistency with the original "
62
+ "input where appropriate.<|im_end|>\n<|im_start|>user\n{}<|im_end|>\n<|im_start|>assistant\n"
63
+ ),
64
+ "start_idx": 64,
65
+ },
66
+ }
67
+
68
+
69
+ def print_load_warning(missing: list[str], unexpected: list[str]) -> None:
70
+ if len(missing) > 0 and len(unexpected) > 0:
71
+ logger.warning(f"Got {len(missing)} missing keys:\n\t" + "\n\t".join(missing))
72
+ logger.warning("\n" + "-" * 79 + "\n")
73
+ logger.warning(f"Got {len(unexpected)} unexpected keys:\n\t" + "\n\t".join(unexpected))
74
+ elif len(missing) > 0:
75
+ logger.warning(f"Got {len(missing)} missing keys:\n\t" + "\n\t".join(missing))
76
+ elif len(unexpected) > 0:
77
+ logger.warning(f"Got {len(unexpected)} unexpected keys:\n\t" + "\n\t".join(unexpected))
78
+
79
+
80
+ def correct_model_weight(state_dict):
81
+ result = {}
82
+ for key in state_dict.keys():
83
+ if "_orig_mod." in key:
84
+ result[key[10:]] = state_dict[key]
85
+ else:
86
+ result[key] = state_dict[key]
87
+ return result
88
+
89
+
90
+ def load_hf_style_weight(pretrain_path, device):
91
+ index_path = os.path.join(pretrain_path, "diffusion_pytorch_model.safetensors.index.json")
92
+
93
+ with open(index_path) as f:
94
+ index = json.load(f)
95
+
96
+ weight_map = index["weight_map"]
97
+
98
+ sd = {}
99
+ loaded_shards = set()
100
+
101
+ for shard_file in weight_map.values():
102
+ if shard_file in loaded_shards:
103
+ continue
104
+ shard_path = os.path.join(pretrain_path, shard_file)
105
+ shard_sd = load_file(shard_path, device="cpu")
106
+ sd.update(shard_sd)
107
+ loaded_shards.add(shard_file)
108
+
109
+ return sd
110
+
111
+
112
+ def load_model_weight(model, pretrain_path, device="cpu"):
113
+ if os.path.exists(pretrain_path):
114
+ logger.info(f"Loading checkpoint from {pretrain_path}")
115
+ try:
116
+ if pretrain_path.endswith("safetensors"):
117
+ sd = load_sft(pretrain_path, device="cpu")
118
+ elif os.path.exists(os.path.join(pretrain_path, "diffusion_pytorch_model.safetensors.index.json")):
119
+ sd = load_hf_style_weight(pretrain_path, device)
120
+ else:
121
+ sd = torch.load(pretrain_path, map_location="cpu")
122
+
123
+ sd = correct_model_weight(sd)
124
+ sd = optionally_expand_state_dict(model, sd)
125
+ missing, unexpected = model.load_state_dict(sd, strict=False, assign=True)
126
+ print_load_warning(missing, unexpected)
127
+ return True
128
+ except Exception as e:
129
+ logger.info(f"CANNOT Load {pretrain_path}, because {e}")
130
+ return False
131
+ return False
132
+
133
+
134
+ def load_model(dit_structure: dict, pretrain_path: str | None = None):
135
+ logger.info("Init DiT model")
136
+
137
+ # If name is a dict, we assume it contains the parameters directly
138
+ # We need to determine the model class based on some heuristic or just default to MageFlow/Flux
139
+ # For now, let's assume it's MageFlow if time_type is present, or check other fields
140
+ params = MageFlowParams(**dit_structure)
141
+ # Default to MageFlow for now as per user context, or we could add a 'model_type' field to the dict
142
+ # The user mentioned "model structure option", implying we are configuring the structure.
143
+ # Let's assume MageFlow for this refactor as the user was using qwen-image-tiny-wo-textemb
144
+ model = MageFlow(params)
145
+
146
+ # logger.info(f"Loading {name if isinstance(name, str) else 'custom config'} checkpoint from {pretrain_path}")
147
+ if pretrain_path is not None:
148
+ load_model_weight(model, pretrain_path, device="cpu")
149
+ # if isinstance(name, str) and configs[name].lora_path is not None:
150
+ # logger.info("Loading LoRA")
151
+ # lora_sd = load_sft(configs[name].lora_path, device="cpu")
152
+ # # loading the lora params + overwriting scale values in the norms
153
+ # missing, unexpected = model.load_state_dict(lora_sd, strict=False, assign=True)
154
+ # print_load_warning(missing, unexpected)
155
+ return model
156
+
157
+
158
+ def optionally_expand_state_dict(model: torch.nn.Module, state_dict: dict) -> dict:
159
+ """
160
+ Optionally expand the state dict to match the model's parameters shapes.
161
+ """
162
+ for name, param in model.named_parameters():
163
+ if name in state_dict:
164
+ if state_dict[name].shape != param.shape:
165
+ logger.info(
166
+ f"Expanding '{name}' with shape {state_dict[name].shape} to model parameter with shape "
167
+ f"{param.shape}."
168
+ )
169
+ # expand with zeros:
170
+ expanded_state_dict_weight = torch.zeros_like(param, device=state_dict[name].device)
171
+ slices = tuple(slice(0, dim) for dim in state_dict[name].shape)
172
+ expanded_state_dict_weight[slices] = state_dict[name]
173
+ state_dict[name] = expanded_state_dict_weight
174
+
175
+ return state_dict
vendor/mage_flow/pipeline.py ADDED
@@ -0,0 +1,762 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MageFlow text-to-image + image-edit inference pipeline.
2
+
3
+ Self-contained MageFlow t2i / edit inference: load a HuggingFace diffusers-style
4
+ repo (model_index.json + transformer/ vae/ scheduler/), then generate or edit
5
+ images. No training/eval deps.
6
+
7
+ Both ``generate_images`` and ``generate_edits`` support PACKED multi-resolution
8
+ inference: several samples (each at its own resolution) are concatenated into a
9
+ single varlen sequence and processed in one transformer forward per denoise
10
+ step. Per-sample ``cu_seqlens`` (inside the flash-attn varlen kernel) isolate
11
+ samples, exactly mirroring training-time packing. These packed functions are the
12
+ sole implementation — the single-image case is just a pack of size 1, exposed
13
+ via the ``MageFlowPipeline.generate`` / ``.edit`` convenience methods.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+ import random
21
+
22
+ import torch
23
+ from einops import rearrange
24
+ from PIL import Image
25
+
26
+ from diffusers import FlowMatchEulerDiscreteScheduler
27
+
28
+ from .models.mage_flow import MageFlowModel, ModelConfig
29
+ from .models.utils import PROMPT_TEMPLATE, get_noise, unpack
30
+ from .models.modules.mage_text import make_refusal_image
31
+ from .models.modules.mage_latent import encode_noise, resolve_gs_key
32
+
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # Scheduler — diffusers FlowMatchEulerDiscreteScheduler
36
+ # ---------------------------------------------------------------------------
37
+ def build_scheduler(num_steps: int, device=None, shift: float = 6.0):
38
+ """Construct a diffusers ``FlowMatchEulerDiscreteScheduler`` whose sigma
39
+ schedule reproduces our default preset exactly.
40
+
41
+ The base sigmas ``linspace(1, 1/num_steps, num_steps)`` fed to
42
+ ``set_timesteps`` are run through the scheduler's built-in static shift
43
+ ``shift·s/(1+(shift-1)·s)`` and a terminal 0 is appended — the static-shift
44
+ schedule (the only supported schedule).
45
+ """
46
+ scheduler = FlowMatchEulerDiscreteScheduler(
47
+ num_train_timesteps=1000, shift=shift, use_dynamic_shifting=False)
48
+ base_sigmas = torch.linspace(1.0, 1.0 / num_steps, num_steps).tolist()
49
+ scheduler.set_timesteps(sigmas=base_sigmas, device=device)
50
+ return scheduler
51
+
52
+
53
+ def _get_scheduler(model, steps, device, static_shift):
54
+ scheduler = getattr(model, "scheduler", None)
55
+ if scheduler is None:
56
+ return build_scheduler(steps, device=device,
57
+ shift=(static_shift if static_shift is not None else 6.0))
58
+ if static_shift is not None:
59
+ scheduler.set_shift(static_shift)
60
+ scheduler.set_timesteps(sigmas=torch.linspace(1.0, 1.0 / steps, steps).tolist(), device=device)
61
+ return scheduler
62
+
63
+
64
+ # ---------------------------------------------------------------------------
65
+ # Small helpers
66
+ # ---------------------------------------------------------------------------
67
+ def _template_info(name: str | None) -> dict:
68
+ name = name or "mage-flow"
69
+ if name not in PROMPT_TEMPLATE:
70
+ raise ValueError(f"Unknown prompt template: {name}")
71
+ return PROMPT_TEMPLATE[name]
72
+
73
+
74
+ def _as_list(val, default, n):
75
+ """Broadcast a scalar/None to a length-n list, or validate a given list."""
76
+ if val is None:
77
+ return [default] * n
78
+ if isinstance(val, (list, tuple)):
79
+ if len(val) != n:
80
+ raise ValueError(f"expected {n} values, got {len(val)}")
81
+ return list(val)
82
+ return [val] * n
83
+
84
+
85
+ def _lens_to_cu(lens, device):
86
+ """Sequence lengths -> cumulative cu_seqlens [0, l0, l0+l1, ...] (int32)."""
87
+ t = torch.tensor(lens, device=device, dtype=torch.int32)
88
+ return torch.cat([torch.zeros(1, dtype=torch.int32, device=device),
89
+ torch.cumsum(t, dim=0, dtype=torch.int32)])
90
+
91
+
92
+ def _make_divisible_by_16(size: int) -> int:
93
+ return max(16, 16 * (size // 16))
94
+
95
+
96
+ def _compute_aspect_ratio_size(pil_img: Image.Image, max_size: int):
97
+ """Longest side = ``max_size``, short side from aspect ratio, both /16."""
98
+ w, h = pil_img.size
99
+ if h >= w:
100
+ new_h, new_w = max_size, int(round(w * max_size / h))
101
+ else:
102
+ new_w, new_h = max_size, int(round(h * max_size / w))
103
+ return _make_divisible_by_16(new_h), _make_divisible_by_16(new_w)
104
+
105
+
106
+ def _edit_target_size(pil_img: Image.Image, max_size, height, width):
107
+ """Output (H, W) for an edit sample, derived from its PRIMARY reference.
108
+
109
+ Precedence: explicit ``height`` AND ``width`` (custom size) > ``max_size``
110
+ (longest side, short side by aspect ratio) > the source image's own size.
111
+ All rounded down to a multiple of 16.
112
+ """
113
+ if height and width:
114
+ return _make_divisible_by_16(height), _make_divisible_by_16(width)
115
+ if max_size:
116
+ return _compute_aspect_ratio_size(pil_img, max_size)
117
+ # Nothing specified: keep the source resolution (its own longest side).
118
+ return _compute_aspect_ratio_size(pil_img, max(pil_img.size))
119
+
120
+
121
+ def _decode_one(model, tokens, height, width, dev):
122
+ """Unpack one sample's image tokens [1, H*W, C] and VAE-decode to a PIL image."""
123
+ with torch.autocast(device_type=dev.type, dtype=torch.bfloat16):
124
+ out = model.vae.decode(unpack(tokens.float(), height, width))
125
+ out = rearrange(out.clamp(-1, 1), "b c h w -> b h w c")
126
+ out = (127.5 * (out + 1.0)).cpu().byte().numpy()
127
+ return Image.fromarray(out[0])
128
+
129
+
130
+ def _build_pack_ctx(img_ids, img_cu, img_shapes, img_lens, txt, txt_cu, txt_mask, vec,
131
+ neg_txt, neg_cu, neg_mask, neg_vec, cfg, renormalization, batch_cfg, device):
132
+ """Precompute the static per-step transformer inputs for a packed batch.
133
+
134
+ When a negative branch is present and ``batch_cfg`` is True, the conditional
135
+ and unconditional passes are fused into ONE varlen forward: the image tokens
136
+ are duplicated (cond copy + uncond copy) and the positive/negative texts are
137
+ concatenated, so cond sample i and uncond sample i become two independent
138
+ varlen segments processed in a single kernel launch. flash_attn_varlen_func
139
+ keeps every segment isolated via cu_seqlens, so this is numerically identical
140
+ to two separate forwards — just one launch instead of two.
141
+ """
142
+ na = len(img_lens)
143
+ ctx = {
144
+ "na": na, "cfg": cfg, "renorm": renormalization, "batch_cfg": batch_cfg,
145
+ "has_neg": neg_txt is not None,
146
+ "img_ids": img_ids, "img_cu": img_cu, "img_shapes": img_shapes,
147
+ "img_max": int(max(img_lens)),
148
+ "txt": txt, "txt_ids": torch.zeros(1, txt.shape[1], 3, device=device),
149
+ "txt_cu": txt_cu, "txt_mask": txt_mask, "vec": vec,
150
+ "txt_max": int((txt_cu[1:] - txt_cu[:-1]).max().item()),
151
+ }
152
+ if neg_txt is None:
153
+ return ctx
154
+ ctx.update({
155
+ "neg_txt": neg_txt, "neg_ids": torch.zeros(1, neg_txt.shape[1], 3, device=device),
156
+ "neg_cu": neg_cu, "neg_mask": neg_mask, "neg_vec": neg_vec,
157
+ "neg_max": int((neg_cu[1:] - neg_cu[:-1]).max().item()),
158
+ })
159
+ if batch_cfg:
160
+ # Duplicate image segments (cond then uncond) and concat pos+neg text.
161
+ d_txt = torch.cat([txt, neg_txt], dim=1)
162
+ pos_lens = (txt_cu[1:] - txt_cu[:-1]).tolist()
163
+ neg_lens = (neg_cu[1:] - neg_cu[:-1]).tolist()
164
+ ctx.update({
165
+ "d_img_ids": torch.cat([img_ids, img_ids], dim=1),
166
+ "d_img_cu": _lens_to_cu(list(img_lens) + list(img_lens), device),
167
+ "d_img_shapes": [img_shapes[0] + img_shapes[0]],
168
+ "d_txt": d_txt,
169
+ "d_txt_ids": torch.zeros(1, d_txt.shape[1], 3, device=device),
170
+ "d_txt_cu": _lens_to_cu(pos_lens + neg_lens, device),
171
+ "d_txt_mask": torch.ones(1, d_txt.shape[1], device=device),
172
+ "d_vec": torch.cat([vec, neg_vec], dim=0),
173
+ "d_txt_max": int(max(pos_lens + neg_lens)),
174
+ })
175
+ return ctx
176
+
177
+
178
+ def _velocity(transformer, img, ctx, sigma):
179
+ """CFG-combined image-token velocity for a packed batch at noise level ``sigma``.
180
+
181
+ Returns [1, sum_img_len, C] in the conditional sample order. When
182
+ ``batch_cfg`` is set the cond+uncond passes share a single fused varlen
183
+ forward; otherwise they are two forwards.
184
+ """
185
+ dev = img.device
186
+ na = ctx["na"]
187
+
188
+ def _fwd(x, n, img_ids, img_cu, img_max, img_shapes, txt, txt_ids, txt_cu, txt_mask, txt_max, vec):
189
+ t_vec = torch.full((n,), sigma, dtype=x.dtype, device=dev)
190
+ return transformer(img=x, txt=txt, timesteps=t_vec, img_shapes=img_shapes,
191
+ img_cu_seqlens=img_cu, txt_cu_seqlens=txt_cu)
192
+
193
+ if not ctx["has_neg"]:
194
+ return _fwd(img, na, ctx["img_ids"], ctx["img_cu"], ctx["img_max"], ctx["img_shapes"],
195
+ ctx["txt"], ctx["txt_ids"], ctx["txt_cu"], ctx["txt_mask"], ctx["txt_max"], ctx["vec"])
196
+
197
+ if ctx["batch_cfg"]:
198
+ n_img = img.shape[1]
199
+ out = _fwd(torch.cat([img, img], dim=1), 2 * na,
200
+ ctx["d_img_ids"], ctx["d_img_cu"], ctx["img_max"], ctx["d_img_shapes"],
201
+ ctx["d_txt"], ctx["d_txt_ids"], ctx["d_txt_cu"], ctx["d_txt_mask"], ctx["d_txt_max"], ctx["d_vec"])
202
+ cond, unc = out[:, :n_img, :], out[:, n_img:, :]
203
+ else:
204
+ cond = _fwd(img, na, ctx["img_ids"], ctx["img_cu"], ctx["img_max"], ctx["img_shapes"],
205
+ ctx["txt"], ctx["txt_ids"], ctx["txt_cu"], ctx["txt_mask"], ctx["txt_max"], ctx["vec"])
206
+ unc = _fwd(img, na, ctx["img_ids"], ctx["img_cu"], ctx["img_max"], ctx["img_shapes"],
207
+ ctx["neg_txt"], ctx["neg_ids"], ctx["neg_cu"], ctx["neg_mask"], ctx["neg_max"], ctx["neg_vec"])
208
+
209
+ cfg = ctx["cfg"]
210
+ if ctx["renorm"]:
211
+ # CFG renormalization: rescale the guided velocity per token back to the
212
+ # conditional velocity's norm (reduces oversaturation at high cfg).
213
+ comb = unc + cfg * (cond - unc)
214
+ return comb * (torch.norm(cond, dim=-1, keepdim=True) /
215
+ (torch.norm(comb, dim=-1, keepdim=True) + 1e-6))
216
+ return unc + cfg * (cond - unc)
217
+
218
+
219
+ def _encode_texts_packed(model, prompts, template, drop_idx, device):
220
+ """Encode a LIST of templated text-only prompts in ONE packed varlen forward
221
+ (``TextEncoder.forward`` — varlen cu_seqlens isolates each prompt,
222
+ verified zero cross-contamination). Returns (txt_flat [ΣLi, D], vec [N, D],
223
+ per-prompt token lengths list)."""
224
+ tokenizer = model.txt_enc.tokenizer
225
+ max_len = model.txt_enc.tokenizer_max_length + drop_idx
226
+ ids_list = [
227
+ tokenizer(template.format(p), max_length=max_len, truncation=True,
228
+ return_tensors="pt").input_ids.squeeze(0)
229
+ for p in prompts
230
+ ]
231
+ input_ids = torch.cat(ids_list).to(device)
232
+ cu_seqlens = _lens_to_cu([int(t.numel()) for t in ids_list], device)
233
+ res = model.txt_enc(
234
+ input_ids, cu_seqlens, drop_idx_override=drop_idx)
235
+ return res["txt"], res["vec"], res["txt_seq_lens"].tolist()
236
+
237
+
238
+ def _slice_packed(txt_flat, vec, lens, start, count, device):
239
+ """Format a contiguous ``count``-prompt slice (starting at prompt ``start``) of a
240
+ packed text encode into the (txt [1, ΣL, D], cu_seqlens, ones-mask, vec [count, D])
241
+ tuple that ``_build_pack_ctx`` consumes."""
242
+ seg_lens = lens[start:start + count]
243
+ tok_start = sum(lens[:start])
244
+ tok_end = tok_start + sum(seg_lens)
245
+ txt = txt_flat[tok_start:tok_end].reshape(1, -1, txt_flat.shape[-1]).to(device)
246
+ return (txt, _lens_to_cu(seg_lens, device),
247
+ torch.ones(1, txt.shape[1], device=device), vec[start:start + count].to(device))
248
+
249
+
250
+ # ---------------------------------------------------------------------------
251
+ # Text-to-image (packed, multi-resolution)
252
+ # ---------------------------------------------------------------------------
253
+ @torch.no_grad()
254
+ def generate_images(model, prompts, neg_prompts=None, seeds=None, steps=30, cfg=5.0,
255
+ heights=None, widths=None, device="cuda",
256
+ prompt_template="mage-flow", static_shift=None,
257
+ gs_key=None,
258
+ renormalization=False, batch_cfg=True):
259
+ """Generate one image per prompt. Prompts may request DIFFERENT resolutions;
260
+ all are packed into a single varlen forward per denoise step — samples are
261
+ kept isolated by ``flash_attn_varlen_func`` via per-sample ``cu_seqlens`` (no
262
+ cross-sample attention), mirroring training-time packing. When ``cfg > 1`` and
263
+ ``batch_cfg`` is set, the positive and negative passes are fused into that
264
+ same varlen forward. Returns a list of PIL images aligned with ``prompts``.
265
+ """
266
+ if isinstance(prompts, str):
267
+ prompts = [prompts]
268
+ n = len(prompts)
269
+ neg_prompts = _as_list(neg_prompts, " ", n)
270
+ seeds = _as_list(seeds, 42, n)
271
+ heights = _as_list(heights, 1024, n)
272
+ widths = _as_list(widths, 1024, n)
273
+ info = _template_info(prompt_template)
274
+ template = info.get("template", "{}")
275
+ drop_idx = int(info.get("start_idx", 0))
276
+ dev = torch.device(device)
277
+
278
+ # Content-policy gate per sample (MANDATORY — runs on the same text-encoder
279
+ # weights as conditioning, no opt-out). Violating prompts get a refusal
280
+ # placeholder and are dropped from the pack.
281
+ results = [None] * n
282
+ active = []
283
+ for i in range(n):
284
+ if seeds[i] == -1:
285
+ seeds[i] = random.randint(0, 2**32 - 1)
286
+ verdict = model.txt_enc.screen_text(prompts[i])
287
+ if verdict.violates:
288
+ h_, w_ = _make_divisible_by_16(heights[i]), _make_divisible_by_16(widths[i])
289
+ print(verdict.banner())
290
+ results[i] = make_refusal_image(verdict, height=h_, width=w_)
291
+ continue
292
+ active.append(i)
293
+ if not active:
294
+ return results
295
+
296
+ gs_key_int = resolve_gs_key(gs_key)
297
+ # Per-sample noise tokens + position ids + shapes (MageVAE: flatten, no packing).
298
+ ch = model.vae.latent_channels
299
+ img_list, ids_list, lens, shapes, hw = [], [], [], [], []
300
+ for i in active:
301
+ h_, w_ = _make_divisible_by_16(heights[i]), _make_divisible_by_16(widths[i])
302
+ torch.manual_seed(seeds[i])
303
+ x = get_noise(num_samples=1, channel=ch, height=h_, width=w_,
304
+ device=dev, dtype=torch.bfloat16, seed=seeds[i])
305
+ # Distribution-preserving watermark in the initial noise (same shape,
306
+ # still ~N(0,1)); detect by inverting the flow ODE back to noise.
307
+ x = encode_noise(tuple(x.shape[1:]), key=gs_key_int,
308
+ seed=seeds[i], device=dev, dtype=torch.bfloat16)
309
+ _, _, gh, gw = x.shape
310
+ img_list.append(rearrange(x, "b c h w -> b (h w) c")[0])
311
+ ids = torch.zeros(gh, gw, 3, device=dev)
312
+ ids[..., 1] = ids[..., 1] + torch.arange(gh, device=dev)[:, None]
313
+ ids[..., 2] = ids[..., 2] + torch.arange(gw, device=dev)[None, :]
314
+ ids_list.append(rearrange(ids, "h w c -> (h w) c"))
315
+ lens.append(gh * gw); shapes.append((1, gh, gw)); hw.append((h_, w_))
316
+ img = torch.cat(img_list, 0).unsqueeze(0)
317
+ img_ids = torch.cat(ids_list, 0).unsqueeze(0)
318
+ img_cu = _lens_to_cu(lens, dev)
319
+ img_shapes = [shapes]
320
+
321
+ # Packed text: positive prompts AND (for CFG) negative prompts are encoded
322
+ # TOGETHER in ONE varlen forward, then split back — cu_seqlens keeps every
323
+ # prompt isolated (verified zero cross-contamination).
324
+ pos_prompts = [prompts[i] for i in active]
325
+ na = len(active)
326
+ use_neg = cfg > 1.0 and any(neg_prompts[i] for i in active)
327
+ if use_neg:
328
+ neg_list = [neg_prompts[i] or " " for i in active]
329
+ txt_flat, vec_all, lens_t = _encode_texts_packed(
330
+ model, pos_prompts + neg_list, template, drop_idx, dev)
331
+ txt, txt_cu, txt_mask, vec = _slice_packed(txt_flat, vec_all, lens_t, 0, na, dev)
332
+ neg_txt, neg_cu, neg_mask, neg_vec = _slice_packed(txt_flat, vec_all, lens_t, na, na, dev)
333
+ else:
334
+ txt_flat, vec_all, lens_t = _encode_texts_packed(model, pos_prompts, template, drop_idx, dev)
335
+ txt, txt_cu, txt_mask, vec = _slice_packed(txt_flat, vec_all, lens_t, 0, na, dev)
336
+ neg_txt = neg_cu = neg_mask = neg_vec = None
337
+
338
+ ctx = _build_pack_ctx(img_ids, img_cu, img_shapes, lens, txt, txt_cu, txt_mask, vec,
339
+ neg_txt, neg_cu, neg_mask, neg_vec, cfg, renormalization, batch_cfg, dev)
340
+ scheduler = _get_scheduler(model, steps, device, static_shift)
341
+ for si, t in enumerate(scheduler.timesteps):
342
+ pred = _velocity(model.transformer, img, ctx, scheduler.sigmas[si].item())
343
+ img = scheduler.step(pred, t, img, return_dict=False)[0]
344
+
345
+ off = 0
346
+ for k, i in enumerate(active):
347
+ L = lens[k]
348
+ h_, w_ = hw[k]
349
+ results[i] = _decode_one(model, img[:, off:off + L, :], h_, w_, dev)
350
+ off += L
351
+ return results
352
+
353
+
354
+ # ---------------------------------------------------------------------------
355
+ # Image edit (packed, multi-resolution)
356
+ # ---------------------------------------------------------------------------
357
+ def _preprocess_ref_image(pil_img: Image.Image, height: int, width: int, device) -> torch.Tensor:
358
+ """Resize an RGB reference image to (height, width) and normalize to [-1, 1]."""
359
+ from torchvision.transforms import functional as TF
360
+ img = pil_img.convert("RGB")
361
+ img = TF.resize(img, [height, width], interpolation=TF.InterpolationMode.BICUBIC)
362
+ t = TF.to_tensor(img) # [3, H, W] in [0, 1]
363
+ t = TF.normalize(t, [0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) # -> [-1, 1]
364
+ return t.to(device)
365
+
366
+
367
+ def _resize_long_edge(image: Image.Image, max_long_edge: int | None) -> Image.Image:
368
+ """Cap the VL conditioning image's long edge, preserving aspect ratio.
369
+
370
+ Matches training's data.processor._resize_long_edge (BICUBIC). Without this,
371
+ inference feeds a full-resolution image to the Qwen-VL processor whose
372
+ default max_pixels is far larger than 384**2 — a train/test mismatch.
373
+ """
374
+ if max_long_edge is None or max_long_edge <= 0:
375
+ return image
376
+ w, h = image.size
377
+ long_edge = max(w, h)
378
+ if long_edge <= max_long_edge:
379
+ return image
380
+ scale = max_long_edge / long_edge
381
+ new_w = max(1, int(round(w * scale)))
382
+ new_h = max(1, int(round(h * scale)))
383
+ return image.resize((new_w, new_h), Image.BICUBIC)
384
+
385
+
386
+ # Fixed image placeholder used at edit training time (one per reference image).
387
+ _EDIT_IMAGE_PLACEHOLDER = "<|vision_start|><|image_pad|><|vision_end|>"
388
+
389
+
390
+ def _edit_prompt_body(instruction, num_refs):
391
+ """Training-time multi-reference prompt body: ``Image 1: <ph>Image 2: <ph>…{instruction}``."""
392
+ prefix = "".join(f"Image {j}: {_EDIT_IMAGE_PLACEHOLDER}" for j in range(1, num_refs + 1))
393
+ return prefix + instruction
394
+
395
+
396
+ def _encode_edits_packed(model, ref_pils_per_sample, instructions, template, drop_idx, device):
397
+ """Encode ALL image-conditioned edit instructions in ONE packed multimodal
398
+ varlen forward (pixel_values/image_grid_thw concatenated across samples,
399
+ cu_seqlens isolates each). Returns (txt_flat [ΣLi, D], vec [N, D], per-sample lens)."""
400
+ processor = model.txt_enc.processor
401
+ ids_list, pv_list, thw_list = [], [], []
402
+ for ref_pils, instr in zip(ref_pils_per_sample, instructions, strict=False):
403
+ formatted = template.format(_edit_prompt_body(instr, len(ref_pils)))
404
+ vl = processor(text=[formatted], images=list(ref_pils), padding=True, return_tensors="pt")
405
+ vl = {k: (v.to(device) if hasattr(v, "to") else v) for k, v in vl.items()}
406
+ ids_list.append(vl["input_ids"].squeeze(0))
407
+ if vl.get("pixel_values") is not None:
408
+ pv_list.append(vl["pixel_values"]); thw_list.append(vl["image_grid_thw"])
409
+ input_ids = torch.cat(ids_list).to(device)
410
+ cu = _lens_to_cu([int(t.numel()) for t in ids_list], device)
411
+ inputs = {"input_ids": input_ids, "cu_seqlens": cu}
412
+ if pv_list:
413
+ inputs["pixel_values"] = torch.cat(pv_list, dim=0)
414
+ inputs["image_grid_thw"] = torch.cat(thw_list, dim=0)
415
+ res = model.txt_enc(
416
+ input_ids, cu, inputs=inputs, drop_idx_override=drop_idx)
417
+ return res["txt"], res["vec"], res["txt_seq_lens"].tolist()
418
+
419
+
420
+ @torch.no_grad()
421
+ def generate_edits(model, prompts, ref_images, neg_prompts=None, seeds=None, steps=30, cfg=5.0,
422
+ max_size=None, heights=None, widths=None, device="cuda",
423
+ prompt_template="mage-flow-edit", static_shift=None,
424
+ gs_key=None,
425
+ vl_cond_long_edge=384,
426
+ renormalization=False, batch_cfg=True):
427
+ """Edit reference image(s) per prompt. Each ``ref_images[i]`` may be a single
428
+ image/path OR a list of source images (multi-image edit, like training —
429
+ trained with up to 3, but more are accepted) — all produce ONE edited output. Each sample's
430
+ ``[target, ref_1, …, ref_N]`` latent tokens are sequence-concatenated, and
431
+ all samples are packed into one varlen forward per denoise step.
432
+
433
+ Output resolution (derived from the first/primary reference of each sample):
434
+ if both ``heights[i]`` and ``widths[i]`` are given, use them; else if
435
+ ``max_size`` is given, the longest side is ``max_size`` and the short side
436
+ follows the reference's aspect ratio; otherwise the output keeps the source
437
+ image's own resolution. All references are VAE-encoded at that target size.
438
+ Returns a list of PIL images.
439
+ """
440
+ if isinstance(prompts, str):
441
+ prompts = [prompts]
442
+ ref_images = [ref_images]
443
+ n = len(prompts)
444
+ neg_prompts = _as_list(neg_prompts, " ", n)
445
+ seeds = _as_list(seeds, 42, n)
446
+ heights = _as_list(heights, None, n)
447
+ widths = _as_list(widths, None, n)
448
+ info = _template_info(prompt_template)
449
+ template = info.get("template", "{}")
450
+ drop_idx = int(info.get("start_idx", 0))
451
+ dev = torch.device(device)
452
+
453
+ # Normalize each sample's references to a list of 1..3 PIL images.
454
+ def _load_pil(r):
455
+ if isinstance(r, str):
456
+ r = Image.open(r)
457
+ return r.convert("RGB")
458
+
459
+ pils_per_sample = []
460
+ for r in ref_images:
461
+ refs = list(r) if isinstance(r, (list, tuple)) else [r]
462
+ if not refs:
463
+ raise ValueError("each edit sample needs at least one reference image")
464
+ pils_per_sample.append([_load_pil(x) for x in refs])
465
+
466
+ # Per-sample output resolution (from the first/primary reference) + content gate.
467
+ results = [None] * n
468
+ res_hw = [None] * n
469
+ active = []
470
+ for i in range(n):
471
+ res_hw[i] = _edit_target_size(pils_per_sample[i][0], max_size, heights[i], widths[i])
472
+ if seeds[i] == -1:
473
+ seeds[i] = random.randint(0, 2**32 - 1)
474
+ # Multimodal gate (MANDATORY): inspect the source image(s) AND the
475
+ # instruction, so NSFW / copyrighted-character / real-public-figure
476
+ # source photos are blocked even under an innocuous instruction.
477
+ verdict = model.txt_enc.screen_edit(prompts[i], pils_per_sample[i])
478
+ if verdict.violates:
479
+ h_, w_ = res_hw[i]
480
+ print(verdict.banner())
481
+ results[i] = make_refusal_image(verdict, height=h_, width=w_)
482
+ continue
483
+ active.append(i)
484
+ if not active:
485
+ return results
486
+
487
+ gs_key_int = resolve_gs_key(gs_key)
488
+
489
+ # Per sample: reference latent tokens (clean) + target noise tokens, plus the
490
+ # combined [target, ref_1, …, ref_N] position ids and shapes. ``target_idx``
491
+ # records where each sample's target tokens land in the packed sequence so we
492
+ # can slice the velocity and step only the target portion.
493
+ ch = model.vae.latent_channels
494
+ targets, refs, ids_list, shape_seq, samp_lens, tgt_lens, hw = [], [], [], [], [], [], []
495
+ target_idx_parts = []
496
+ off = 0
497
+ for i in active:
498
+ h_, w_ = res_hw[i]
499
+ torch.manual_seed(seeds[i]) # MageVAE.encode samples the posterior (global RNG)
500
+ # All references resized to the target resolution and VAE-encoded together.
501
+ ref_tensors = [_preprocess_ref_image(p, h_, w_, dev) for p in pils_per_sample[i]]
502
+ ref_tok, ref_shapes, ref_ids = model.compute_vae_encodings(ref_tensors, with_ids=True)
503
+ ref_tok = ref_tok.to(torch.bfloat16) # [1, N*Lr, C]
504
+ x = get_noise(num_samples=1, channel=ch, height=h_, width=w_,
505
+ device=dev, dtype=torch.bfloat16, seed=seeds[i])
506
+ x = encode_noise(tuple(x.shape[1:]), key=gs_key_int,
507
+ seed=seeds[i], device=dev, dtype=torch.bfloat16)
508
+ _, _, gh, gw = x.shape
509
+ tgt = rearrange(x, "b c h w -> b (h w) c") # [1, Lt, C]
510
+ tgt_ids = torch.zeros(gh, gw, 3, device=dev)
511
+ tgt_ids[..., 1] = tgt_ids[..., 1] + torch.arange(gh, device=dev)[:, None]
512
+ tgt_ids[..., 2] = tgt_ids[..., 2] + torch.arange(gw, device=dev)[None, :]
513
+ tgt_ids = rearrange(tgt_ids, "h w c -> (h w) c").unsqueeze(0)
514
+ lt, lr = tgt.shape[1], ref_tok.shape[1]
515
+ targets.append(tgt); refs.append(ref_tok)
516
+ ids_list.append(torch.cat([tgt_ids, ref_ids.to(dev)], dim=1)[0]) # [Lt + N*Lr, 3]
517
+ shape_seq.append((1, gh, gw)) # target frame idx 0
518
+ shape_seq.extend(s[0] for s in ref_shapes) # ref_j frame idx j
519
+ samp_lens.append(lt + lr); tgt_lens.append(lt); hw.append((h_, w_))
520
+ target_idx_parts.append(torch.arange(off, off + lt, device=dev))
521
+ off += lt + lr
522
+ img_ids = torch.cat(ids_list, 0).unsqueeze(0)
523
+ img_cu = _lens_to_cu(samp_lens, dev)
524
+ img_shapes = [shape_seq]
525
+ target_idx = torch.cat(target_idx_parts)
526
+
527
+ # Packed edit text — positive AND (for CFG) negative are encoded TOGETHER in
528
+ # ONE packed multimodal forward, then split. Both branches share the same
529
+ # reference images; cu_seqlens isolates every sequence (zero cross-contamination).
530
+ # The VL conditioning image's long edge is capped (default 384) to match
531
+ # training preprocessing — the VAE path above keeps the full target resolution.
532
+ na = len(active)
533
+ edit_refs = [[_resize_long_edge(p, vl_cond_long_edge) for p in pils_per_sample[i]]
534
+ for i in active]
535
+ if cfg > 1.0:
536
+ pos_instr = [prompts[i] for i in active]
537
+ neg_instr = [neg_prompts[i] or " " for i in active]
538
+ txt_flat, vec_all, lens_t = _encode_edits_packed(
539
+ model, edit_refs + edit_refs, pos_instr + neg_instr, template, drop_idx, dev)
540
+ txt, txt_cu, txt_mask, vec = _slice_packed(txt_flat, vec_all, lens_t, 0, na, dev)
541
+ neg_txt, neg_cu, neg_mask, neg_vec = _slice_packed(txt_flat, vec_all, lens_t, na, na, dev)
542
+ else:
543
+ txt_flat, vec_all, lens_t = _encode_edits_packed(
544
+ model, edit_refs, [prompts[i] for i in active], template, drop_idx, dev)
545
+ txt, txt_cu, txt_mask, vec = _slice_packed(txt_flat, vec_all, lens_t, 0, na, dev)
546
+ neg_txt = neg_cu = neg_mask = neg_vec = None
547
+
548
+ ctx = _build_pack_ctx(img_ids, img_cu, img_shapes, samp_lens, txt, txt_cu, txt_mask, vec,
549
+ neg_txt, neg_cu, neg_mask, neg_vec, cfg, renormalization, batch_cfg, dev)
550
+ scheduler = _get_scheduler(model, steps, device, static_shift)
551
+ for si, t in enumerate(scheduler.timesteps):
552
+ parts = []
553
+ for k in range(na):
554
+ parts.append(targets[k]); parts.append(refs[k])
555
+ img = torch.cat(parts, dim=1) # [1, sum(Lt+Lr), C], ref clean
556
+ vel = _velocity(model.transformer, img, ctx, scheduler.sigmas[si].item())
557
+ pred_t = vel[:, target_idx, :] # [1, sum Lt, C] — target tokens only
558
+ tgt_packed = torch.cat(targets, dim=1) # [1, sum Lt, C]
559
+ stepped = scheduler.step(pred_t, t, tgt_packed, return_dict=False)[0]
560
+ o = 0
561
+ new_targets = []
562
+ for k in range(na):
563
+ lt = tgt_lens[k]
564
+ new_targets.append(stepped[:, o:o + lt, :]); o += lt
565
+ targets = new_targets
566
+
567
+ for k, i in enumerate(active):
568
+ h_, w_ = hw[k]
569
+ results[i] = _decode_one(model, targets[k], h_, w_, dev)
570
+ return results
571
+
572
+
573
+ # ---------------------------------------------------------------------------
574
+ # Flow-ODE inversion (Gaussian-Shading watermark detection)
575
+ # ---------------------------------------------------------------------------
576
+ @torch.no_grad()
577
+ def invert_to_noise(model, z0, height, width, steps=30, device="cuda",
578
+ prompt_template="mage-flow", static_shift=None, prompt=""):
579
+ """Reverse the flow ODE from a clean latent ``z0`` back to the initial noise.
580
+
581
+ This is the detection primitive for the Gaussian-Shading watermark: VAE-encode
582
+ the image to ``z0`` (posterior MEAN — deterministic), run this to recover the
583
+ initial noise, then read the signs via ``mage_latent.decode_bits``.
584
+
585
+ Inversion uses an empty prompt at cfg=1 (the standard Tree-Ring /
586
+ Gaussian-Shading setup). Reverse Euler recovers ``x_i`` from ``x_{i+1}`` with
587
+ the velocity evaluated at the point in hand; the sign-only watermark tolerates
588
+ the resulting approximation error (see the module's redundancy).
589
+
590
+ Args:
591
+ z0: clean latent ``[1, C, gh, gw]`` (e.g. the mean of ``model.vae.encode``).
592
+ Returns:
593
+ recovered initial-noise latent ``[1, C, gh, gw]`` (float32).
594
+ """
595
+ dev = torch.device(device)
596
+ info = _template_info(prompt_template)
597
+ template = info.get("template", "{}")
598
+ drop_idx = int(info.get("start_idx", 0))
599
+
600
+ z0 = z0.to(dev)
601
+ _, ch, gh, gw = z0.shape
602
+ img = rearrange(z0, "b c h w -> b (h w) c").to(torch.bfloat16) # [1, gh*gw, C]
603
+
604
+ ids = torch.zeros(gh, gw, 3, device=dev)
605
+ ids[..., 1] = ids[..., 1] + torch.arange(gh, device=dev)[:, None]
606
+ ids[..., 2] = ids[..., 2] + torch.arange(gw, device=dev)[None, :]
607
+ img_ids = rearrange(ids, "h w c -> (h w) c").unsqueeze(0)
608
+ lens = [gh * gw]
609
+ img_cu = _lens_to_cu(lens, dev)
610
+ img_shapes = [[(1, gh, gw)]]
611
+
612
+ # Empty-prompt conditioning, no negative branch, cfg=1 (single forward).
613
+ txt_flat, vec_all, lens_t = _encode_texts_packed(model, [prompt], template, drop_idx, dev)
614
+ txt, txt_cu, txt_mask, vec = _slice_packed(txt_flat, vec_all, lens_t, 0, 1, dev)
615
+ ctx = _build_pack_ctx(img_ids, img_cu, img_shapes, lens, txt, txt_cu, txt_mask, vec,
616
+ None, None, None, None, 1.0, False, False, dev)
617
+
618
+ scheduler = _get_scheduler(model, steps, device, static_shift)
619
+ sigmas = scheduler.sigmas
620
+ n = len(scheduler.timesteps)
621
+ # Forward step si: x_{si+1} = x_si + (s_{si+1}-s_si)·v(x_si, s_si).
622
+ # Reverse it from clean (x_n, sigma 0) up to noise (x_0), using x_{si+1} as the
623
+ # proxy for x_si at the forward eval sigma s_si.
624
+ for si in range(n - 1, -1, -1):
625
+ s_cur = sigmas[si].item()
626
+ s_next = sigmas[si + 1].item()
627
+ vel = _velocity(model.transformer, img, ctx, s_cur)
628
+ img = img - (s_next - s_cur) * vel
629
+ return unpack(img.float(), height, width) # [1, C, gh, gw]
630
+
631
+
632
+ # ---------------------------------------------------------------------------
633
+ # High-level pipeline wrapper
634
+ # ---------------------------------------------------------------------------
635
+ class MageFlowPipeline:
636
+ """``MageFlowPipeline.from_pretrained(repo).generate(...) / .edit(...)``.
637
+
638
+ ``generate`` / ``edit`` are packed multi-resolution calls: they take a list
639
+ of prompts (a single string is accepted and treated as a pack of size 1) and
640
+ return a list of PIL images. Per-sample ``heights``/``widths``/``seeds`` are
641
+ lists. Every prompt is screened by the text encoder's mandatory content
642
+ gate (no opt-out); banned prompts come back as refusal placeholders
643
+ interleaved with the real images. Real outputs always carry a Gaussian-Shading
644
+ watermark in the initial noise (no toggle), using the configured secret key.
645
+ """
646
+
647
+ def __init__(self, model, device="cuda"):
648
+ self.model = model
649
+ self.device = device
650
+
651
+ @classmethod
652
+ def from_pretrained(cls, repo_dir: str, device: str = "cuda"):
653
+ """Load a Mage-Flow diffusers-style repo (``model_index.json`` +
654
+ ``transformer/`` ``vae/`` ``scheduler/`` ``text_encoder/``).
655
+
656
+ ``repo_dir`` may be a local directory OR a Hugging Face Hub repo id
657
+ (e.g. ``"microsoft/Mage-Flow-4B"``), which is downloaded and cached
658
+ automatically on first use.
659
+ """
660
+ return cls(load_from_repo(repo_dir, device), device)
661
+
662
+ def generate(self, prompts, **kw) -> list[Image.Image]:
663
+ """Packed multi-resolution t2i. ``prompts`` is a list (or a single
664
+ string); pass per-sample ``heights``/``widths``/``seeds`` as lists."""
665
+ kw.setdefault("device", self.device)
666
+ return generate_images(self.model, prompts, **kw)
667
+
668
+ def edit(self, prompts, ref_images, **kw) -> list[Image.Image]:
669
+ """Packed multi-resolution edit. ``prompts`` is a list (or a single
670
+ string); each ``ref_images[i]`` is one reference or a list of references."""
671
+ kw.setdefault("device", self.device)
672
+ return generate_edits(self.model, prompts, ref_images, **kw)
673
+
674
+ def invert_to_noise(self, z0, height, width, **kw):
675
+ """Recover the initial noise from a clean latent (Gaussian-Shading detect)."""
676
+ kw.setdefault("device", self.device)
677
+ return invert_to_noise(self.model, z0, height, width, **kw)
678
+
679
+
680
+ def _safe_subpath(root: str, *parts: str) -> str:
681
+ """Join ``parts`` under ``root`` and confirm the result stays inside ``root``.
682
+
683
+ ``root`` is normalized up front; the joined path is normalized **lexically**
684
+ (``os.path.normpath`` — symlinks are *not* followed, so a Hugging Face cache
685
+ whose weight files are symlinks into the shared blob store still loads) and
686
+ rejected if it escapes ``root``. This guards the user-supplied model path
687
+ against path traversal (CWE-22 / CodeQL ``py/path-injection``).
688
+ """
689
+ root = os.path.realpath(root)
690
+ full = os.path.normpath(os.path.join(root, *parts))
691
+ if full != root and not full.startswith(root + os.sep):
692
+ raise ValueError(
693
+ f"Resolved path {os.path.join(*parts)!r} escapes repo directory {root!r}"
694
+ )
695
+ return full
696
+
697
+
698
+ def _resolve_repo_dir(repo_dir: str) -> str:
699
+ """Return a local directory for ``repo_dir``.
700
+
701
+ If ``repo_dir`` is an existing local path it is returned as a normalized
702
+ absolute path; otherwise it is treated as a Hugging Face Hub repo id (e.g.
703
+ ``microsoft/Mage-Flow``) and downloaded/cached via
704
+ ``huggingface_hub.snapshot_download``.
705
+ """
706
+ candidate = os.path.realpath(repo_dir)
707
+ if os.path.isdir(candidate):
708
+ return candidate
709
+ from huggingface_hub import snapshot_download
710
+ return snapshot_download(repo_id=repo_dir)
711
+
712
+
713
+ def load_from_repo(repo_dir: str, device: str = "cuda") -> MageFlowModel:
714
+ """Load a Mage-Flow diffusers-style repo (model_index.json + transformer/
715
+ vae/ scheduler/). Transformer weights come from the bf16 safetensors;
716
+ VAE + text encoder are built from the sources recorded in model_index.json.
717
+
718
+ ``repo_dir`` may be a local directory OR a Hugging Face Hub repo id (e.g.
719
+ ``microsoft/Mage-Flow-4B``), which is downloaded/cached automatically.
720
+ """
721
+ from safetensors.torch import load_file
722
+ repo_dir = _resolve_repo_dir(repo_dir)
723
+ mi = json.load(open(_safe_subpath(repo_dir, "model_index.json")))
724
+ tcfg = json.load(open(_safe_subpath(repo_dir, "transformer", "config.json")))
725
+ # Keys stripped from the checkpoint config before it becomes model_structure.
726
+ # ``schedule_mode`` is a legacy field still present in some config.json files;
727
+ # Keys of the checkpoint config that are NOT MageFlowParams constructor args
728
+ # (legacy/unused fields). Everything else becomes model_structure. The DiT only
729
+ # reads: in_channels, out_channels, context_in_dim, hidden_size, num_heads,
730
+ # depth, axes_dim, checkpoint, patch_size.
731
+ _meta = {"_class_name", "txt_max_length", "max_sequence_length", "param_dtype",
732
+ "packing", "schedule_mode", "static_shift", "use_time_shift",
733
+ "rope_type", "apply_text_rotary_emb",
734
+ "mlp_ratio", "depth_single_blocks", "theta", "qkv_bias", "guidance_embed",
735
+ "vec_in_dim", "vec_type", "time_type", "double_block_type"}
736
+ structure = {k: v for k, v in tcfg.items() if k not in _meta}
737
+
738
+ def _resolve(p):
739
+ return p if os.path.isabs(p) else _safe_subpath(repo_dir, p)
740
+
741
+ cfg = ModelConfig(
742
+ vae_path=_resolve(mi.get("_vae_source")),
743
+ txt_enc_path=_resolve(mi.get("_text_encoder_path")),
744
+ model_structure=structure,
745
+ txt_max_length=tcfg.get("txt_max_length", 2048),
746
+ packing=tcfg.get("packing", True),
747
+ static_shift=tcfg.get("static_shift", 6.0),
748
+ )
749
+ model = MageFlowModel(cfg)
750
+ sd = load_file(_safe_subpath(repo_dir, "transformer", "diffusion_pytorch_model.safetensors"),
751
+ device="cpu")
752
+ model.transformer.load_state_dict(sd, strict=False, assign=True)
753
+ model.to(device)
754
+ model.transformer.to(torch.bfloat16)
755
+ model.txt_enc.to(torch.bfloat16)
756
+ if model.vae is not None:
757
+ model.vae.to(torch.bfloat16)
758
+ model.eval()
759
+ # Diffusers FlowMatchEulerDiscreteScheduler (scheduler/scheduler_config.json).
760
+ model.scheduler = FlowMatchEulerDiscreteScheduler.from_pretrained(
761
+ _safe_subpath(repo_dir, "scheduler"))
762
+ return model