Instructions to use FastVideo/FastH3-Omni-NVFP4-QAD-Comfy with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusion Single File
How to use FastVideo/FastH3-Omni-NVFP4-QAD-Comfy with Diffusion Single File:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
Add native ComfyUI conversion, provenance, and validation
Browse files- .gitattributes +3 -34
- MANIFEST.json +76 -0
- README.md +124 -0
- conversion/convert_omniref_comfy.py +367 -0
- conversion/quantize_native_comfy.py +245 -0
- conversion/validate_native_comfy.py +195 -0
- provenance/CONVERSION.md +55 -0
- provenance/QAD_CONVERSION_RECEIPT.json +403 -0
- provenance/QAD_SOURCE_VERIFICATION.json +98 -0
- provenance/QUANTIZATION.json +0 -0
- provenance/SCHEMA_VALIDATION.json +50 -0
- provenance/SOURCE_HASH_VERIFICATION.json +28 -0
- provenance/VALIDATION.json +202 -0
- provenance/VALIDATION.md +29 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,4 @@
|
|
| 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 |
-
|
| 27 |
-
*.
|
| 28 |
-
*.
|
| 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 |
+
*.json text
|
| 3 |
+
*.md text
|
| 4 |
+
*.py text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MANIFEST.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema": "omniref-comfy-repo-manifest-v1",
|
| 3 |
+
"private": true,
|
| 4 |
+
"files": [
|
| 5 |
+
{
|
| 6 |
+
"path": ".gitattributes",
|
| 7 |
+
"bytes": 82,
|
| 8 |
+
"sha256": "398727837d241c660c7a0d82ba2edaa348150d4d8daac4953b1d62d671442ad4"
|
| 9 |
+
},
|
| 10 |
+
{
|
| 11 |
+
"path": "README.md",
|
| 12 |
+
"bytes": 5382,
|
| 13 |
+
"sha256": "5d5e9f390476538a9eb24c2a681166f3b7261ce7d304bf311d1574ee59f1fc46"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"path": "conversion/convert_omniref_comfy.py",
|
| 17 |
+
"bytes": 19113,
|
| 18 |
+
"sha256": "96b745f3fae81450eac777d60c7f3452d24d61384f18877fe4f1fc4e930320a3"
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"path": "conversion/quantize_native_comfy.py",
|
| 22 |
+
"bytes": 10914,
|
| 23 |
+
"sha256": "5ff65395a51d78b43684e751682f71115ad0575b4ae1f796611f2e02a228ef0f"
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"path": "conversion/validate_native_comfy.py",
|
| 27 |
+
"bytes": 8989,
|
| 28 |
+
"sha256": "a4015fe9e5c219e587f52069701326ce92202dce035fb6b94023c0c135104ca3"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"path": "fasth3_omniref_qad_nvfp4_comfy.safetensors",
|
| 32 |
+
"bytes": 13773998700,
|
| 33 |
+
"sha256": "bf25ee58df65b4dfee349d612f7ac62a2fea0dddd1e68eb37e75c8ce8b166f5a"
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"path": "provenance/CONVERSION.md",
|
| 37 |
+
"bytes": 2688,
|
| 38 |
+
"sha256": "ff9c3d8df3a49d502b053fff05702e7a5496e989f04ef3e315964aa9bea1d202"
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"path": "provenance/QAD_CONVERSION_RECEIPT.json",
|
| 42 |
+
"bytes": 12927,
|
| 43 |
+
"sha256": "a59d13efea88afeab363ff954b23963759af1239a052a697bf7414588791f44b"
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"path": "provenance/QAD_SOURCE_VERIFICATION.json",
|
| 47 |
+
"bytes": 3931,
|
| 48 |
+
"sha256": "9538f791f2a29c8a8e098918148b1c98b8a8c7714c1c8f3ae4d1ae8cd5d9d4b0"
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"path": "provenance/QUANTIZATION.json",
|
| 52 |
+
"bytes": 160301,
|
| 53 |
+
"sha256": "0d633848795b464af4e891cad076ad18b2ebb514ffbaabd792354692c0edc397"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"path": "provenance/SCHEMA_VALIDATION.json",
|
| 57 |
+
"bytes": 1511,
|
| 58 |
+
"sha256": "2f9b391292363ebf2ce8d8e7a5b27783f99db8e9d8538433557902fef3a95dfa"
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"path": "provenance/SOURCE_HASH_VERIFICATION.json",
|
| 62 |
+
"bytes": 914,
|
| 63 |
+
"sha256": "e4558cbaa2e23231af9acd04985ff51121477663aaa9705a0edc7e64e72fad5e"
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"path": "provenance/VALIDATION.json",
|
| 67 |
+
"bytes": 5961,
|
| 68 |
+
"sha256": "a49f64853cffb402bfe9890fa1ffb77f3b37e6b35e57606f70834e1340daf27e"
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"path": "provenance/VALIDATION.md",
|
| 72 |
+
"bytes": 1797,
|
| 73 |
+
"sha256": "3f9dd6713cc0ef01491f5baf22b3066d90d3fa8beb257abbac8ff0c9e9a7c798"
|
| 74 |
+
}
|
| 75 |
+
]
|
| 76 |
+
}
|
README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
license_name: minimax-h3-community-license-agreement
|
| 4 |
+
license_link: https://huggingface.co/MiniMaxAI/MiniMax-H3/blob/main/LICENSE
|
| 5 |
+
library_name: diffusion-single-file
|
| 6 |
+
base_model:
|
| 7 |
+
- FastVideo/FastVideo-FastH3-Omni-8-Step-V1
|
| 8 |
+
tags:
|
| 9 |
+
- comfyui
|
| 10 |
+
- diffusion-single-file
|
| 11 |
+
- minimax-h3
|
| 12 |
+
- omniref
|
| 13 |
+
- reference-to-video
|
| 14 |
+
- nvfp4
|
| 15 |
+
- qad
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
# FastH3 OmniRef NVFP4 QAD for ComfyUI
|
| 19 |
+
|
| 20 |
+
Native ComfyUI NVFP4 diffusion model for FastH3 OmniRef, a reference-conditioned
|
| 21 |
+
video-and-audio generator. The model produces a video with synchronized 32 kHz stereo
|
| 22 |
+
audio from a prompt and ordered image, video, and audio references.
|
| 23 |
+
|
| 24 |
+
This checkpoint is an **NVFP4 export of the QAD student of the BF16 OmniRef model**
|
| 25 |
+
[`FastVideo/FastVideo-FastH3-Omni-8-Step-V1`](https://huggingface.co/FastVideo/FastVideo-FastH3-Omni-8-Step-V1).
|
| 26 |
+
The QAD student was trained against the BF16 OmniRef teacher for NVFP4 execution; its
|
| 27 |
+
dense BF16 master was then converted to the native ComfyUI layout and quantized to
|
| 28 |
+
NVFP4 for this Comfy-native checkpoint.
|
| 29 |
+
|
| 30 |
+
## Quantization
|
| 31 |
+
|
| 32 |
+
- All 50 transformer blocks store `qkv_proj`, `out_proj`, `to_gate_compress`,
|
| 33 |
+
`mlp.fc1`, and `mlp.fc2` as NVFP4 E2M1 with FP8 block scales (group size 16).
|
| 34 |
+
- Q/K/V are fused by the native Comfy conversion before quantization. The fused
|
| 35 |
+
`qkv_proj` uses **one per-tensor global scale** plus its block scales, matching
|
| 36 |
+
ComfyUI's `TensorCoreNVFP4Layout`. This is the compatible treatment for Comfy's
|
| 37 |
+
single fused QKV module.
|
| 38 |
+
- Activations use ComfyUI's native **dynamic per-tensor** NVFP4 quantization at runtime.
|
| 39 |
+
- The token refiner, AdaLN curves and projections, norms, embeddings, patch and
|
| 40 |
+
condition projections, and the FP32 PDD output heads stay dense.
|
| 41 |
+
- This is a new PTQ export of the QAD-trained BF16 master. It is **not** a
|
| 42 |
+
byte-preserving repack of the FastVideo packed NVFP4 checkpoint: that file stores
|
| 43 |
+
separate Q/K/V matrices and independent per-projection global scales, which cannot
|
| 44 |
+
be concatenated unchanged into Comfy's fused QKV module.
|
| 45 |
+
|
| 46 |
+
## Files
|
| 47 |
+
|
| 48 |
+
| File | Contents |
|
| 49 |
+
|---|---|
|
| 50 |
+
| `fasth3_omniref_qad_nvfp4_comfy.safetensors` | Native ComfyUI NVFP4 diffusion model (~12 GB) |
|
| 51 |
+
| `provenance/` | Source verification, conversion/quantization receipts, schema validation |
|
| 52 |
+
| `conversion/` | Converter and quantizer scripts, pinned-runtime provenance |
|
| 53 |
+
|
| 54 |
+
Place the diffusion model in:
|
| 55 |
+
|
| 56 |
+
```text
|
| 57 |
+
ComfyUI/models/diffusion_models/fasth3_omniref_qad_nvfp4_comfy.safetensors
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
Use the matching text encoder and VAEs from
|
| 61 |
+
[`FastVideo/FastH3-OmniRef-Comfy`](https://huggingface.co/FastVideo/FastH3-OmniRef-Comfy):
|
| 62 |
+
|
| 63 |
+
```text
|
| 64 |
+
ComfyUI/models/text_encoders/qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors
|
| 65 |
+
ComfyUI/models/vae/minimax_h3_video_vae_int8_convrot.safetensors
|
| 66 |
+
ComfyUI/models/vae/minimax_h3_audio_vae_fp32.safetensors
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
The text encoder includes the vision tower used for reference conditioning. The video
|
| 70 |
+
VAE includes the reference encoder and decoder. The audio VAE is required.
|
| 71 |
+
|
| 72 |
+
## Usage
|
| 73 |
+
|
| 74 |
+
Use the **MiniMax H3 Reference to Video** node with an image, video, or audio reference.
|
| 75 |
+
Reference the inputs with `<Picture 1>`, `<Video 1>`, and `<Audio 1>` tags in the prompt.
|
| 76 |
+
|
| 77 |
+
Recommended OmniRef settings:
|
| 78 |
+
|
| 79 |
+
- 8 sampling steps, guidance 1.0
|
| 80 |
+
- Video shift 12.0, audio shift 3.0
|
| 81 |
+
- 480p or 768p output; 124 frames is about 5 seconds at 24 fps
|
| 82 |
+
- Euler sampler with a simple schedule for the 8-forward PDD student
|
| 83 |
+
|
| 84 |
+
Example 480p5 prompt:
|
| 85 |
+
|
| 86 |
+
```text
|
| 87 |
+
A dynamic tracking shot of <Picture 1>: a cyclist riding along a city street at golden
|
| 88 |
+
hour. The camera moves alongside at riding speed, storefronts and trees passing behind.
|
| 89 |
+
Wind, bicycle chain, and distant traffic sounds. No text, no watermark.
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
## Validation
|
| 93 |
+
|
| 94 |
+
- Passes CPU format/schema checks against the pinned ComfyUI MiniMax-H3 detector and
|
| 95 |
+
quantized-module loader.
|
| 96 |
+
- The QAD BF16 master was checked against the original OmniRef transformer: all tensors
|
| 97 |
+
outside the QAD-trained projections are byte-identical, and the trained projections
|
| 98 |
+
differ as expected.
|
| 99 |
+
- Every dense tensor in the NVFP4 file is byte-identical to the QAD BF16 Comfy
|
| 100 |
+
conversion.
|
| 101 |
+
- Validated with native ComfyUI at 832x480, 124 frames, 8 steps against the
|
| 102 |
+
corresponding QAD BF16 Comfy export using PSNR/SSIM, signal statistics, and audio
|
| 103 |
+
metrics (duration, RMS, activity, correlation, and spectral centroid). See
|
| 104 |
+
`provenance/VALIDATION.md`.
|
| 105 |
+
- The clips contain no NaNs, dropped audio, or silent tracks in validation.
|
| 106 |
+
|
| 107 |
+
## Limitations
|
| 108 |
+
|
| 109 |
+
- NVFP4 changes the numerical output relative to the BF16 QAD master, as expected for
|
| 110 |
+
W4A4 execution.
|
| 111 |
+
- Comfy's fused QKV module takes one global weight scale. Quantizing the fused tensor
|
| 112 |
+
is a compatible PTQ treatment, but it is not numerically identical to the FastVideo
|
| 113 |
+
packed QAD representation with separate Q/K/V scales.
|
| 114 |
+
- Native ComfyUI's PDD sampler grid and sparse-attention path differ from the FastVideo
|
| 115 |
+
runtime. The original inference contract is recorded in the safetensors metadata.
|
| 116 |
+
- Accelerated NVFP4 kernels require Blackwell (SM 10.0 or newer), a ComfyUI build with
|
| 117 |
+
CUDA 13, and `comfy-kitchen` 0.2.39 or newer. Older CUDA or pre-Blackwell devices
|
| 118 |
+
load the checkpoint through the dequantization fallback.
|
| 119 |
+
- Reference-driven output is sensitive to prompt wording and reference framing.
|
| 120 |
+
|
| 121 |
+
## License
|
| 122 |
+
|
| 123 |
+
MiniMax-H3 and this derivative are governed by the MiniMax-H3 Community License
|
| 124 |
+
Agreement. See the linked license for terms.
|
conversion/convert_omniref_comfy.py
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Bounded-memory FastVideo H3 OmniRef -> native ComfyUI single-file export.
|
| 3 |
+
|
| 4 |
+
Dense BF16/F16/F32 source only. All source tensors are accounted for. QKV fusion
|
| 5 |
+
and SwiGLU reordering copy bytes. PDD full heads become FP32 baseline+offset
|
| 6 |
+
heads because that is the native ComfyUI consumer's representation. Optional
|
| 7 |
+
rank-reduced AdaLN curves are an explicitly reported numerical approximation.
|
| 8 |
+
"""
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import argparse
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
import hashlib
|
| 14 |
+
import json
|
| 15 |
+
import math
|
| 16 |
+
from pathlib import Path
|
| 17 |
+
import re
|
| 18 |
+
import struct
|
| 19 |
+
import sys
|
| 20 |
+
|
| 21 |
+
COMFY_COMMIT = "b1a247c9cf9dbfda4b2c0a0c1d50950667eeaa0c"
|
| 22 |
+
TOOLS_COMMIT = "d6797787e6bdb1a1fb0094d588a26f8e71a1c757"
|
| 23 |
+
SOURCE_REPO = "FastVideo/FastVideo-FastH3-Omni-8-Step-V1"
|
| 24 |
+
SOURCE_REVISION = "88d512c5e7cc38dda499fbb65688ce58611f9c80"
|
| 25 |
+
DTYPE_BYTES = {"BF16": 2, "F16": 2, "F32": 4}
|
| 26 |
+
CHUNK = 8 * 1024 * 1024
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
@dataclass
|
| 30 |
+
class Tensor:
|
| 31 |
+
dtype: str
|
| 32 |
+
shape: list[int]
|
| 33 |
+
path: Path
|
| 34 |
+
offset: int
|
| 35 |
+
size: int
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@dataclass
|
| 39 |
+
class Output:
|
| 40 |
+
dtype: str
|
| 41 |
+
shape: list[int]
|
| 42 |
+
# File ranges, kept in order; generated tensors are small scratch files.
|
| 43 |
+
parts: list[tuple[Path, int, int]]
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def header(path: Path) -> tuple[dict, int]:
|
| 47 |
+
with path.open("rb") as f:
|
| 48 |
+
raw = f.read(8)
|
| 49 |
+
if len(raw) != 8:
|
| 50 |
+
raise ValueError(f"Truncated safetensors file: {path}")
|
| 51 |
+
length, = struct.unpack("<Q", raw)
|
| 52 |
+
if length > 100 * 1024 * 1024:
|
| 53 |
+
raise ValueError(f"Unreasonable header length: {path}")
|
| 54 |
+
data = f.read(length)
|
| 55 |
+
if len(data) != length:
|
| 56 |
+
raise ValueError(f"Truncated safetensors header: {path}")
|
| 57 |
+
return json.loads(data), 8 + length
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def load_source(src: Path) -> tuple[dict, dict[str, Tensor]]:
|
| 61 |
+
config = json.loads((src / "config.json").read_text())
|
| 62 |
+
if config.get("quantization_config") or config.get("adaln_rank"):
|
| 63 |
+
raise ValueError("Only the dense canonical OmniRef source is accepted")
|
| 64 |
+
index = json.loads((src / "diffusion_pytorch_model.safetensors.index.json").read_text())
|
| 65 |
+
weight_map = index["weight_map"]
|
| 66 |
+
tensors: dict[str, Tensor] = {}
|
| 67 |
+
for filename in sorted(set(weight_map.values())):
|
| 68 |
+
path = src / filename
|
| 69 |
+
if Path(filename).name != filename:
|
| 70 |
+
raise ValueError(f"Unsafe shard name: {filename}")
|
| 71 |
+
items, start = header(path)
|
| 72 |
+
for name, item in items.items():
|
| 73 |
+
if name == "__metadata__":
|
| 74 |
+
continue
|
| 75 |
+
if name in tensors or weight_map.get(name) != filename:
|
| 76 |
+
raise ValueError(f"Duplicate/unindexed tensor: {name}")
|
| 77 |
+
dtype, shape = item["dtype"], item["shape"]
|
| 78 |
+
if dtype not in DTYPE_BYTES:
|
| 79 |
+
raise ValueError(f"Unexpected non-dense dtype {dtype}: {name}")
|
| 80 |
+
begin, end = item["data_offsets"]
|
| 81 |
+
size = math.prod(shape) * DTYPE_BYTES[dtype]
|
| 82 |
+
if begin < 0 or end - begin != size or start + end > path.stat().st_size:
|
| 83 |
+
raise ValueError(f"Invalid/truncated tensor range: {name}")
|
| 84 |
+
tensors[name] = Tensor(dtype, shape, path, start + begin, size)
|
| 85 |
+
if set(tensors) != set(weight_map):
|
| 86 |
+
raise ValueError("Source tensor keys do not match the frozen index")
|
| 87 |
+
return config, tensors
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def expected_shapes(c: dict) -> dict[str, list[int]]:
|
| 91 |
+
h, q = c["hidden_size"], c["num_attention_heads"] * c["attention_head_dim"]
|
| 92 |
+
f, t, p = c["ffn_dim"], c["time_embed_dim"], c["pdd_steps"]
|
| 93 |
+
video = c["in_channels"] * math.prod(c["patch_size"])
|
| 94 |
+
audio = c["audio_in_channels"]
|
| 95 |
+
result = {}
|
| 96 |
+
for name, rows, cols in [("proj_in", h, video), ("audio_proj_in", h, audio),
|
| 97 |
+
("context_embedder", h, c["text_dim"]),
|
| 98 |
+
("proj_out", video * p, h), ("audio_proj_out", audio * p, h),
|
| 99 |
+
("time_embedder.linear_1", c["time_embed_hidden_dim"], c["freq_dim"]),
|
| 100 |
+
("time_embedder.linear_2", t, c["time_embed_hidden_dim"]),
|
| 101 |
+
("norm_out.linear", 2 * h, t)]:
|
| 102 |
+
result[name + ".weight"] = [rows, cols]
|
| 103 |
+
result[name + ".bias"] = [rows]
|
| 104 |
+
result["norm_out.norm.weight"] = [h]
|
| 105 |
+
result["token_refiner.final_norm.weight"] = [h]
|
| 106 |
+
for prefix, count in [("transformer_blocks", c["num_layers"]),
|
| 107 |
+
("token_refiner.refiner_blocks", c["num_refiner_layers"])]:
|
| 108 |
+
for i in range(count):
|
| 109 |
+
stem = f"{prefix}.{i}."
|
| 110 |
+
result.update({stem + "norm1.weight": [h], stem + "norm2.weight": [h],
|
| 111 |
+
stem + "attn.norm_q.weight": [c["attention_head_dim"]],
|
| 112 |
+
stem + "attn.norm_k.weight": [c["attention_head_dim"]],
|
| 113 |
+
stem + "attn.to_q.weight": [q, h], stem + "attn.to_k.weight": [q, h],
|
| 114 |
+
stem + "attn.to_v.weight": [q, h], stem + "attn.to_out.0.weight": [h, q],
|
| 115 |
+
stem + "ff.net.0.proj.weight": [2 * f, h], stem + "ff.net.2.weight": [h, f]})
|
| 116 |
+
if prefix == "transformer_blocks":
|
| 117 |
+
result[stem + "adaln_proj.linear.weight"] = [18 * h, t]
|
| 118 |
+
result[stem + "adaln_proj.linear.bias"] = [18 * h]
|
| 119 |
+
result[stem + "attn.to_gate_compress.weight"] = [q, h]
|
| 120 |
+
return result
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def renamed(name: str) -> str:
|
| 124 |
+
roots = {"proj_in": "video_patch_proj", "audio_proj_in": "audio_patch_proj",
|
| 125 |
+
"context_embedder": "condition_proj", "proj_out": "final_layer.video_out",
|
| 126 |
+
"audio_proj_out": "final_layer.audio_out", "norm_out.linear": "final_layer.adaln_proj.linear",
|
| 127 |
+
"norm_out.norm": "final_layer.norm", "time_embedder.linear_1": "time_embedder.proj_in",
|
| 128 |
+
"time_embedder.linear_2": "time_embedder.proj_out"}
|
| 129 |
+
for before in sorted(roots, key=len, reverse=True):
|
| 130 |
+
if name.startswith(before + "."):
|
| 131 |
+
return roots[before] + name[len(before):]
|
| 132 |
+
name = name.replace("token_refiner.refiner_blocks.", "token_refiner.blocks.")
|
| 133 |
+
name = re.sub(r"^transformer_blocks\.", "blocks.", name)
|
| 134 |
+
return (name.replace(".attn.norm_q.", ".attn.q_norm.").replace(".attn.norm_k.", ".attn.k_norm.")
|
| 135 |
+
.replace(".attn.to_out.0.", ".attn.out_proj.").replace(".ff.net.2.", ".mlp.fc2.")
|
| 136 |
+
.replace(".ff.net.0.proj.", ".mlp.fc1."))
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def torch_tensor(t: Tensor, first: int = 0, rows: int | None = None):
|
| 140 |
+
import torch
|
| 141 |
+
stride = math.prod(t.shape[1:]) * DTYPE_BYTES[t.dtype]
|
| 142 |
+
count = (t.shape[0] - first) if rows is None else rows
|
| 143 |
+
with t.path.open("rb") as f:
|
| 144 |
+
f.seek(t.offset + first * stride)
|
| 145 |
+
raw = bytearray(f.read(count * stride))
|
| 146 |
+
if len(raw) != count * stride:
|
| 147 |
+
raise ValueError(f"Source truncated while reading {t.path}")
|
| 148 |
+
dtype = {"BF16": torch.bfloat16, "F16": torch.float16, "F32": torch.float32}[t.dtype]
|
| 149 |
+
return torch.frombuffer(raw, dtype=dtype).reshape(count, *t.shape[1:])
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def raw_tensor(tensor) -> bytes:
|
| 153 |
+
import torch
|
| 154 |
+
return tensor.detach().cpu().contiguous().reshape(-1).view(torch.uint8).numpy().tobytes()
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def generated(path: Path, tensor) -> Output:
|
| 158 |
+
import torch
|
| 159 |
+
dtype = {torch.float32: "F32", torch.float16: "F16", torch.bfloat16: "BF16"}[tensor.dtype]
|
| 160 |
+
path.write_bytes(raw_tensor(tensor))
|
| 161 |
+
return Output(dtype, list(tensor.shape), [(path, 0, path.stat().st_size)])
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def convert_heads(t: Tensor, n: int, scratch: Path, name: str) -> Output:
|
| 165 |
+
"""Native Comfy stores head0 and (headj-head0). Differences stay FP32."""
|
| 166 |
+
per_head = t.shape[0] // n
|
| 167 |
+
output = scratch / (name.replace(".", "_") + ".bin")
|
| 168 |
+
with output.open("wb") as f:
|
| 169 |
+
for j in range(n):
|
| 170 |
+
for row in range(0, per_head, 256):
|
| 171 |
+
count = min(256, per_head - row)
|
| 172 |
+
x = torch_tensor(t, j * per_head + row, count).float()
|
| 173 |
+
if j:
|
| 174 |
+
x -= torch_tensor(t, row, count).float()
|
| 175 |
+
f.write(raw_tensor(x))
|
| 176 |
+
return Output("F32", t.shape, [(output, 0, output.stat().st_size)])
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def time_curve(t, c: dict, tensors: dict[str, Tensor]):
|
| 180 |
+
import torch
|
| 181 |
+
half = c["freq_dim"] // 2
|
| 182 |
+
freq = torch.exp(-math.log(10000.0) * torch.arange(half, dtype=torch.float64) / half)
|
| 183 |
+
embed = torch.cat(((t[:, None] * freq).cos(), (t[:, None] * freq).sin()), dim=1)
|
| 184 |
+
w1, b1, w2, b2 = [torch_tensor(tensors[k]).double() for k in
|
| 185 |
+
["time_embedder.linear_1.weight", "time_embedder.linear_1.bias",
|
| 186 |
+
"time_embedder.linear_2.weight", "time_embedder.linear_2.bias"]]
|
| 187 |
+
return torch.nn.functional.silu(torch.nn.functional.linear(
|
| 188 |
+
torch.nn.functional.silu(torch.nn.functional.linear(embed, w1, b1)), w2, b2))
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def prune_curves(c, tensors, output, rank, grid, scratch):
|
| 192 |
+
import torch
|
| 193 |
+
if rank < 1 or rank > min(c["time_embed_dim"], grid):
|
| 194 |
+
raise ValueError("Invalid AdaLN curve rank")
|
| 195 |
+
train_t = torch.linspace(0, 1, grid, dtype=torch.float64)
|
| 196 |
+
curve = time_curve(train_t, c, tensors)
|
| 197 |
+
_, singular, vh = torch.linalg.svd(curve, full_matrices=False)
|
| 198 |
+
basis = vh[:rank].T.contiguous()
|
| 199 |
+
coordinates = (curve @ basis).float()
|
| 200 |
+
# Independent offset grid exercises table interpolation, including near endpoints.
|
| 201 |
+
hold_t = (torch.arange(129, dtype=torch.float64) + 0.37) / 129
|
| 202 |
+
hold = time_curve(hold_t, c, tensors)
|
| 203 |
+
pos = hold_t * (grid - 1)
|
| 204 |
+
i0 = pos.floor().long().clamp(max=grid - 2)
|
| 205 |
+
interpolated = torch.lerp(coordinates[i0], coordinates[i0 + 1], (pos - i0).float()[:, None]).double()
|
| 206 |
+
errors = []
|
| 207 |
+
for source_name, tensor in tensors.items():
|
| 208 |
+
if source_name != "norm_out.linear.weight" and not source_name.endswith(".adaln_proj.linear.weight"):
|
| 209 |
+
continue
|
| 210 |
+
path = scratch / (renamed(source_name).replace(".", "_") + ".curve.bin")
|
| 211 |
+
with path.open("wb") as f:
|
| 212 |
+
for row in range(0, tensor.shape[0], 2048):
|
| 213 |
+
dense = torch_tensor(tensor, row, min(2048, tensor.shape[0] - row)).double()
|
| 214 |
+
f.write(raw_tensor((dense @ basis).float()))
|
| 215 |
+
output[renamed(source_name)] = Output("F32", [tensor.shape[0], rank], [(path, 0, path.stat().st_size)])
|
| 216 |
+
sample_rows = torch.linspace(0, tensor.shape[0] - 1, min(64, tensor.shape[0])).long().tolist()
|
| 217 |
+
sample = torch.cat([torch_tensor(tensor, row, 1) for row in sample_rows]).double()
|
| 218 |
+
reference = hold @ sample.T
|
| 219 |
+
approximate = interpolated @ (sample @ basis).float().double().T
|
| 220 |
+
errors.append({"tensor": source_name, "sampled_rows": len(sample_rows),
|
| 221 |
+
"relative_l2": float((approximate - reference).norm() / reference.norm().clamp(min=1e-30)),
|
| 222 |
+
"max_abs": float((approximate - reference).abs().max())})
|
| 223 |
+
for key in list(output):
|
| 224 |
+
if key.startswith("time_embedder."):
|
| 225 |
+
del output[key]
|
| 226 |
+
output["adaln_t_table"] = generated(scratch / "adaln_t_table.bin", coordinates)
|
| 227 |
+
return {"kind": "time-embedding-curve-svd-and-linear-interpolation", "rank": rank, "grid": grid,
|
| 228 |
+
"table_dtype": "F32", "projected_weight_dtype": "F32", "fit_domain": [0.0, 1.0],
|
| 229 |
+
"fit_relative_residual": float(singular[rank:].norm() / singular.norm()),
|
| 230 |
+
"holdout_timesteps": len(hold_t), "holdout_modulation_samples": errors,
|
| 231 |
+
"approximation": True, "end_to_end_quality_validated": False}
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
def make_plan(c: dict, tensors: dict[str, Tensor], scratch: Path, rank: int = 0, grid: int = 1025):
|
| 235 |
+
import torch
|
| 236 |
+
expected = expected_shapes(c)
|
| 237 |
+
if set(tensors) != set(expected):
|
| 238 |
+
raise ValueError(f"Unexpected source keys: missing={sorted(set(expected)-set(tensors))}; "
|
| 239 |
+
f"extra={sorted(set(tensors)-set(expected))}")
|
| 240 |
+
for name, shape in expected.items():
|
| 241 |
+
if tensors[name].shape != shape:
|
| 242 |
+
raise ValueError(f"{name}: expected {shape}, got {tensors[name].shape}")
|
| 243 |
+
output = {}
|
| 244 |
+
for name, t in sorted(tensors.items()):
|
| 245 |
+
if ".attn.to_k." in name or ".attn.to_v." in name:
|
| 246 |
+
continue
|
| 247 |
+
if ".attn.to_q." in name:
|
| 248 |
+
group = [tensors[name.replace(".attn.to_q.", f".attn.to_{p}.")] for p in ("q", "k", "v")]
|
| 249 |
+
if len({g.dtype for g in group}) != 1:
|
| 250 |
+
raise ValueError(f"QKV dtype mismatch: {name}")
|
| 251 |
+
out_name = renamed(name).replace(".attn.to_q.", ".attn.qkv_proj.")
|
| 252 |
+
output[out_name] = Output(t.dtype, [3 * t.shape[0], *t.shape[1:]],
|
| 253 |
+
[(g.path, g.offset, g.size) for g in group])
|
| 254 |
+
elif ".ff.net.0.proj." in name:
|
| 255 |
+
half = t.size // 2
|
| 256 |
+
output[renamed(name)] = Output(t.dtype, t.shape,
|
| 257 |
+
[(t.path, t.offset + half, half), (t.path, t.offset, half)])
|
| 258 |
+
elif name.startswith(("proj_out.", "audio_proj_out.")):
|
| 259 |
+
output[renamed(name)] = convert_heads(t, c["pdd_steps"], scratch, name)
|
| 260 |
+
else:
|
| 261 |
+
output[renamed(name)] = Output(t.dtype, t.shape, [(t.path, t.offset, t.size)])
|
| 262 |
+
inv = 1.0 / (c["rope_theta"] ** (torch.arange(0, 2 * c["rope_freq_dim"], 2, dtype=torch.float32)
|
| 263 |
+
/ (2 * c["rope_freq_dim"])))
|
| 264 |
+
output["rope.inv_freq"] = generated(scratch / "rope.bin", inv)
|
| 265 |
+
curve = prune_curves(c, tensors, output, rank, grid, scratch) if rank else None
|
| 266 |
+
return output, curve
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def write_file(path: Path, output: dict[str, Output], metadata: dict) -> dict:
|
| 270 |
+
if path.exists():
|
| 271 |
+
raise FileExistsError(f"Refusing to overwrite {path}")
|
| 272 |
+
table = {"__metadata__": metadata}
|
| 273 |
+
offset = 0
|
| 274 |
+
for name, item in sorted(output.items()):
|
| 275 |
+
size = sum(part[2] for part in item.parts)
|
| 276 |
+
if size != math.prod(item.shape) * DTYPE_BYTES[item.dtype]:
|
| 277 |
+
raise ValueError(f"Output byte count mismatch: {name}")
|
| 278 |
+
table[name] = {"dtype": item.dtype, "shape": item.shape, "data_offsets": [offset, offset + size]}
|
| 279 |
+
offset += size
|
| 280 |
+
raw = json.dumps(table, separators=(",", ":"), ensure_ascii=False).encode()
|
| 281 |
+
raw += b" " * (-len(raw) % 8)
|
| 282 |
+
digest = hashlib.sha256()
|
| 283 |
+
tmp = path.with_name(path.name + ".partial")
|
| 284 |
+
if tmp.exists():
|
| 285 |
+
raise FileExistsError(f"Scratch output already exists: {tmp}")
|
| 286 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 287 |
+
with tmp.open("xb") as f:
|
| 288 |
+
def emit(data):
|
| 289 |
+
f.write(data)
|
| 290 |
+
digest.update(data)
|
| 291 |
+
emit(struct.pack("<Q", len(raw)))
|
| 292 |
+
emit(raw)
|
| 293 |
+
for name, item in sorted(output.items()):
|
| 294 |
+
for src, begin, count in item.parts:
|
| 295 |
+
with src.open("rb") as source:
|
| 296 |
+
source.seek(begin)
|
| 297 |
+
while count:
|
| 298 |
+
block = source.read(min(count, CHUNK))
|
| 299 |
+
if not block:
|
| 300 |
+
raise ValueError(f"Truncated source during output: {name}")
|
| 301 |
+
emit(block)
|
| 302 |
+
count -= len(block)
|
| 303 |
+
tmp.rename(path)
|
| 304 |
+
return {"file": path.name, "bytes": path.stat().st_size, "sha256": digest.hexdigest(),
|
| 305 |
+
"tensors": len(output), "tensor_data_bytes": offset}
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def metadata_for(c, contract, rank):
|
| 309 |
+
transformer = {"pdd_steps": c["pdd_steps"], "sigma_shift_video": contract["video_scheduler_shift"],
|
| 310 |
+
"sigma_shift_audio": contract["audio_scheduler_shift"],
|
| 311 |
+
"norm_eps": c["norm_eps"], "qk_norm_eps": c["qk_norm_eps"],
|
| 312 |
+
"final_norm_eps": c["final_norm_eps"]}
|
| 313 |
+
if rank:
|
| 314 |
+
transformer["time_embed_dim"] = rank
|
| 315 |
+
return {"format": "pt", "config": json.dumps({"transformer": transformer}, separators=(",", ":")),
|
| 316 |
+
"source_repo": SOURCE_REPO, "source_revision": SOURCE_REVISION,
|
| 317 |
+
"comfyui_commit": COMFY_COMMIT, "comfy_model_tools_commit": TOOLS_COMMIT,
|
| 318 |
+
"source_inference_contract": json.dumps(contract, separators=(",", ":")),
|
| 319 |
+
"pdd_head_storage": "head0-full;heads1-31-offset-from-head0;fp32",
|
| 320 |
+
"adaln_curve_approximation": str(bool(rank)).lower(),
|
| 321 |
+
"inference_parity": "not-validated;native-Comfy-PDD-uses-unit-grid-source-uses-0.999"}
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def main():
|
| 325 |
+
p = argparse.ArgumentParser(description=__doc__)
|
| 326 |
+
p.add_argument("--src", type=Path, required=True, help="Canonical transformer_ref component directory")
|
| 327 |
+
p.add_argument("--output", type=Path, required=True)
|
| 328 |
+
p.add_argument("--contract", type=Path, help="Defaults to SRC/../fastvideo_inference.json")
|
| 329 |
+
p.add_argument("--scratch", type=Path, required=True)
|
| 330 |
+
p.add_argument("--adaln-curve-rank", type=int, default=0, help="0 retains full dense AdaLN;8 mirrors V2 technique")
|
| 331 |
+
p.add_argument("--curve-grid", type=int, default=1025)
|
| 332 |
+
p.add_argument("--threads", type=int, default=4)
|
| 333 |
+
p.add_argument("--inspect-only", action="store_true", help="Validate headers and print plan without conversion")
|
| 334 |
+
args = p.parse_args()
|
| 335 |
+
c, tensors = load_source(args.src)
|
| 336 |
+
expected = expected_shapes(c)
|
| 337 |
+
if set(expected) != set(tensors) or any(tensors[k].shape != v for k, v in expected.items()):
|
| 338 |
+
raise ValueError("Source geometry does not match the canonical native H3 mapping")
|
| 339 |
+
if args.inspect_only:
|
| 340 |
+
print(json.dumps({"source_tensors": len(tensors), "source_tensor_bytes": sum(t.size for t in tensors.values()),
|
| 341 |
+
"pdd_steps": c["pdd_steps"], "curve_rank": args.adaln_curve_rank,
|
| 342 |
+
"output_tensors": len(tensors) - 2 * (c["num_layers"] + c["num_refiner_layers"]) + 1
|
| 343 |
+
- (3 if args.adaln_curve_rank else 0)}, indent=2))
|
| 344 |
+
return
|
| 345 |
+
import torch
|
| 346 |
+
torch.set_num_threads(args.threads)
|
| 347 |
+
args.scratch.mkdir(parents=True, exist_ok=False)
|
| 348 |
+
contract_path = args.contract or args.src.parent / "fastvideo_inference.json"
|
| 349 |
+
contract = json.loads(contract_path.read_text())
|
| 350 |
+
if contract.get("pdd_steps") != c["pdd_steps"] or contract.get("grid_max_t") != 0.999:
|
| 351 |
+
raise ValueError("Unexpected source inference contract")
|
| 352 |
+
output, curve = make_plan(c, tensors, args.scratch, args.adaln_curve_rank, args.curve_grid)
|
| 353 |
+
receipt = write_file(args.output, output, metadata_for(c, contract, args.adaln_curve_rank))
|
| 354 |
+
receipt.update({"source_repo": SOURCE_REPO, "source_revision": SOURCE_REVISION,
|
| 355 |
+
"source_index_sha256": hashlib.sha256((args.src / "diffusion_pytorch_model.safetensors.index.json").read_bytes()).hexdigest(),
|
| 356 |
+
"source_config": c, "source_contract": contract, "source_tensors": len(tensors),
|
| 357 |
+
"source_tensor_bytes": sum(t.size for t in tensors.values()), "skipped_source_tensors": [],
|
| 358 |
+
"conversion": ["concatenateQKV", "swapSwiGLUhalves", "PDDfullheadsToFP32offsetheads", "deriveRoPE"],
|
| 359 |
+
"curve": curve, "comfyui_commit": COMFY_COMMIT, "comfy_model_tools_commit": TOOLS_COMMIT,
|
| 360 |
+
"gpu_inference_validated": False})
|
| 361 |
+
receipt_path = args.output.with_suffix(args.output.suffix + ".receipt.json")
|
| 362 |
+
receipt_path.write_text(json.dumps(receipt, indent=2) + "\n")
|
| 363 |
+
print(json.dumps(receipt, indent=2))
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
if __name__ == "__main__":
|
| 367 |
+
main()
|
conversion/quantize_native_comfy.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Quantize a native ComfyUI single-file OmniRef checkpoint to native FP8 or NVFP4.
|
| 3 |
+
|
| 4 |
+
The input is the native ComfyUI layout produced by ``convert_omniref_comfy.py``:
|
| 5 |
+
Q/K/V are already fused into ``attn.qkv_proj`` and the PDD output heads use the
|
| 6 |
+
native first-head-plus-offset representation. The quantizer therefore sees one
|
| 7 |
+
fused QKV tensor and applies one Comfy weight scale to it, exactly as the
|
| 8 |
+
pinned ComfyUI runtime expects.
|
| 9 |
+
|
| 10 |
+
Only the per-block attention/MLP projections are quantized. The token refiner,
|
| 11 |
+
AdaLN curves and projections, patch/condition/output projections, norms, RoPE,
|
| 12 |
+
and FP32 PDD heads stay dense. This matches the native ComfyUI MiniMax-H3
|
| 13 |
+
quantized-layer policy used by the released V2/Trim Comfy checkpoints.
|
| 14 |
+
|
| 15 |
+
Writes a JSON receipt with source/output hashes, the exact format markers,
|
| 16 |
+
per-linear reconstruction metrics, and runtime versions.
|
| 17 |
+
"""
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
import argparse
|
| 21 |
+
import datetime as dt
|
| 22 |
+
import hashlib
|
| 23 |
+
import importlib.metadata
|
| 24 |
+
import json
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
import re
|
| 27 |
+
import sys
|
| 28 |
+
import time
|
| 29 |
+
|
| 30 |
+
import torch
|
| 31 |
+
|
| 32 |
+
# Native Comfy MiniMax-H3 block linears. qkv_proj is the fused Q/K/V module.
|
| 33 |
+
QUANT_LINEAR = re.compile(
|
| 34 |
+
r"^blocks\.\d+\.(?:attn\.(?:qkv_proj|out_proj|to_gate_compress)|mlp\.(?:fc1|fc2))\.weight$"
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
FORMATS = {
|
| 38 |
+
"fp8": {
|
| 39 |
+
"layout": "TensorCoreFP8Layout",
|
| 40 |
+
"tag": {"format": "float8_e4m3fn"},
|
| 41 |
+
"documented_name": "FP8 E4M3, per-tensor weight scale, dynamic per-tensor activation scale",
|
| 42 |
+
"activation": "dynamic-per-tensor",
|
| 43 |
+
},
|
| 44 |
+
"nvfp4": {
|
| 45 |
+
"layout": "TensorCoreNVFP4Layout",
|
| 46 |
+
"tag": {"format": "nvfp4"},
|
| 47 |
+
"documented_name": "NVFP4 E2M1, per-tensor global scale plus FP8 block scales (group 16), dynamic per-tensor activation scale",
|
| 48 |
+
"activation": "dynamic-per-tensor",
|
| 49 |
+
},
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def sha256_file(path: Path) -> str:
|
| 54 |
+
digest = hashlib.sha256()
|
| 55 |
+
with path.open("rb") as handle:
|
| 56 |
+
while True:
|
| 57 |
+
block = handle.read(64 * 1024 * 1024)
|
| 58 |
+
if not block:
|
| 59 |
+
break
|
| 60 |
+
digest.update(block)
|
| 61 |
+
return digest.hexdigest()
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def tensor_digest(tensor: torch.Tensor) -> str:
|
| 65 |
+
return hashlib.sha256(tensor.detach().cpu().contiguous().view(torch.uint8).numpy().tobytes()).hexdigest()
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def safetensors_header(path: Path) -> dict:
|
| 69 |
+
from safetensors import safe_open
|
| 70 |
+
|
| 71 |
+
with safe_open(str(path), framework="pt", device="cpu") as handle:
|
| 72 |
+
keys = list(handle.keys())
|
| 73 |
+
metadata = handle.metadata() or {}
|
| 74 |
+
return {"tensor_count": len(keys), "metadata": metadata, "keys": keys}
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def main() -> int:
|
| 78 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 79 |
+
parser.add_argument("--src", required=True, type=Path, help="Native ComfyUI BF16 single-file checkpoint")
|
| 80 |
+
parser.add_argument("--dst", required=True, type=Path, help="Output quantized single-file checkpoint")
|
| 81 |
+
parser.add_argument("--format", required=True, choices=sorted(FORMATS))
|
| 82 |
+
parser.add_argument("--receipt", required=True, type=Path)
|
| 83 |
+
parser.add_argument("--comfyui-commit", default="b1a247c9cf9dbfda4b2c0a0c1d50950667eeaa0c")
|
| 84 |
+
parser.add_argument("--expect-tensors", type=int, default=None)
|
| 85 |
+
args = parser.parse_args()
|
| 86 |
+
|
| 87 |
+
if args.src == args.dst:
|
| 88 |
+
raise SystemExit("source and destination must differ")
|
| 89 |
+
if args.dst.exists():
|
| 90 |
+
raise SystemExit(f"refusing to overwrite existing output: {args.dst}")
|
| 91 |
+
if not torch.cuda.is_available():
|
| 92 |
+
raise SystemExit("CUDA is required for comfy-kitchen quantization")
|
| 93 |
+
|
| 94 |
+
import comfy_kitchen
|
| 95 |
+
import comfy_kitchen.tensor as ckt
|
| 96 |
+
from safetensors import safe_open
|
| 97 |
+
from safetensors.torch import load_file, save_file
|
| 98 |
+
|
| 99 |
+
started = time.time()
|
| 100 |
+
spec = FORMATS[args.format]
|
| 101 |
+
layout_cls = getattr(ckt, spec["layout"])
|
| 102 |
+
tag_bytes = json.dumps(spec["tag"], separators=(",", ":")).encode()
|
| 103 |
+
|
| 104 |
+
print(f"[quantize] format={args.format} src={args.src} dst={args.dst}", flush=True)
|
| 105 |
+
src_header = safetensors_header(args.src)
|
| 106 |
+
if args.expect_tensors is not None and src_header["tensor_count"] != args.expect_tensors:
|
| 107 |
+
raise SystemExit(f"expected {args.expect_tensors} tensors, found {src_header['tensor_count']}")
|
| 108 |
+
print(f"[quantize] source tensors={src_header['tensor_count']} hashing source", flush=True)
|
| 109 |
+
src_sha256 = sha256_file(args.src)
|
| 110 |
+
|
| 111 |
+
# The native Comfy BF16 export is ~44 GB. Keeping the pass-through tensors
|
| 112 |
+
# plus the quantized output in CPU RAM stays well under a GB200 node's RAM.
|
| 113 |
+
source = load_file(str(args.src), device="cpu")
|
| 114 |
+
metadata = dict(src_header["metadata"])
|
| 115 |
+
metadata["quantization"] = json.dumps(
|
| 116 |
+
{
|
| 117 |
+
"format": spec["tag"]["format"],
|
| 118 |
+
"layout": spec["layout"],
|
| 119 |
+
"weights": "per-tensor global scale" if args.format == "fp8" else "per-tensor global scale plus FP8 block scales, group 16",
|
| 120 |
+
"activations": spec["activation"],
|
| 121 |
+
"quantized_layers": "all 50 DiT blocks: fused qkv_proj, out_proj, to_gate_compress, mlp.fc1, mlp.fc2",
|
| 122 |
+
"dense_layers": "token_refiner, AdaLN, norms, embeddings, patch/condition projections, FP32 PDD heads",
|
| 123 |
+
"qkv_treatment": "fused Q/K/V quantized as one tensor under one weight scale",
|
| 124 |
+
"comfyui_commit": args.comfyui_commit,
|
| 125 |
+
},
|
| 126 |
+
separators=(",", ":"),
|
| 127 |
+
)
|
| 128 |
+
metadata["quantization_source_sha256"] = src_sha256
|
| 129 |
+
metadata["quantization_recipe"] = "omniref-comfy-ptq-v1"
|
| 130 |
+
|
| 131 |
+
output: dict[str, torch.Tensor] = {}
|
| 132 |
+
linears = []
|
| 133 |
+
for index, (name, tensor) in enumerate(source.items(), 1):
|
| 134 |
+
if not QUANT_LINEAR.match(name):
|
| 135 |
+
output[name] = tensor
|
| 136 |
+
continue
|
| 137 |
+
|
| 138 |
+
weight = tensor.to(device="cuda", dtype=torch.bfloat16).contiguous()
|
| 139 |
+
qdata, params = layout_cls.quantize(weight)
|
| 140 |
+
serialized = layout_cls.state_dict_tensors(qdata, params)
|
| 141 |
+
for suffix, value in serialized.items():
|
| 142 |
+
output[name + suffix] = value.detach().cpu().contiguous()
|
| 143 |
+
output[name[: -len(".weight")] + ".comfy_quant"] = torch.tensor(list(tag_bytes), dtype=torch.uint8)
|
| 144 |
+
|
| 145 |
+
dequantized = layout_cls.dequantize(qdata, params).float()
|
| 146 |
+
reference = weight.float()
|
| 147 |
+
diff = dequantized - reference
|
| 148 |
+
rel_l2 = float(diff.norm() / reference.norm().clamp(min=1e-30))
|
| 149 |
+
cosine = float(torch.nn.functional.cosine_similarity(dequantized.flatten(), reference.flatten(), dim=0))
|
| 150 |
+
entry = {
|
| 151 |
+
"name": name,
|
| 152 |
+
"logical_shape": list(tensor.shape),
|
| 153 |
+
"weight_storage_shape": list(serialized[""].shape),
|
| 154 |
+
"weight_storage_dtype": str(serialized[""].dtype).replace("torch.", ""),
|
| 155 |
+
"reconstruction_relative_l2": rel_l2,
|
| 156 |
+
"reconstruction_cosine": cosine,
|
| 157 |
+
"reconstruction_max_abs": float(diff.abs().max()),
|
| 158 |
+
"source_amax": float(reference.abs().max()),
|
| 159 |
+
}
|
| 160 |
+
if args.format == "fp8":
|
| 161 |
+
entry["weight_scale"] = float(params.scale.reshape(-1)[0])
|
| 162 |
+
else:
|
| 163 |
+
entry["weight_scale_2"] = float(params.scale.reshape(-1)[0])
|
| 164 |
+
entry["weight_scale_shape"] = list(serialized["_scale"].shape)
|
| 165 |
+
entry["weight_scale_dtype"] = str(serialized["_scale"].dtype).replace("torch.", "")
|
| 166 |
+
entry["block_scale_min"] = float(serialized["_scale"].to(torch.float32).min())
|
| 167 |
+
entry["block_scale_max"] = float(serialized["_scale"].to(torch.float32).max())
|
| 168 |
+
linears.append(entry)
|
| 169 |
+
print(
|
| 170 |
+
f"[quantize] {index:4d}/{len(source)} {name} relL2={rel_l2:.6f} cos={cosine:.8f}",
|
| 171 |
+
flush=True,
|
| 172 |
+
)
|
| 173 |
+
del weight, qdata, params, dequantized, reference, diff
|
| 174 |
+
torch.cuda.empty_cache()
|
| 175 |
+
|
| 176 |
+
expected_linears = 50 * 5
|
| 177 |
+
if len(linears) != expected_linears:
|
| 178 |
+
raise SystemExit(f"expected {expected_linears} quantized linears, got {len(linears)}")
|
| 179 |
+
|
| 180 |
+
print(f"[quantize] writing {len(output)} tensors to {args.dst}", flush=True)
|
| 181 |
+
args.dst.parent.mkdir(parents=True, exist_ok=True)
|
| 182 |
+
save_file(output, str(args.dst), metadata=metadata)
|
| 183 |
+
out_sha256 = sha256_file(args.dst)
|
| 184 |
+
out_header = safetensors_header(args.dst)
|
| 185 |
+
|
| 186 |
+
# Pass-through tensors must survive byte-for-byte. Compare every dense tensor
|
| 187 |
+
# from the written file against the source file, not against the in-memory copy.
|
| 188 |
+
pass_through = [name for name in source if not QUANT_LINEAR.match(name)]
|
| 189 |
+
identical = 0
|
| 190 |
+
with safe_open(str(args.src), framework="pt", device="cpu") as src_reader, safe_open(
|
| 191 |
+
str(args.dst), framework="pt", device="cpu"
|
| 192 |
+
) as dst_reader:
|
| 193 |
+
for name in pass_through:
|
| 194 |
+
left = src_reader.get_tensor(name)
|
| 195 |
+
right = dst_reader.get_tensor(name)
|
| 196 |
+
if left.dtype != right.dtype or left.shape != right.shape or not torch.equal(left, right):
|
| 197 |
+
raise SystemExit(f"pass-through tensor changed: {name}")
|
| 198 |
+
identical += 1
|
| 199 |
+
|
| 200 |
+
receipt = {
|
| 201 |
+
"schema": "omniref-comfy-quant-receipt-v1",
|
| 202 |
+
"created_utc": dt.datetime.now(dt.timezone.utc).isoformat(),
|
| 203 |
+
"format": args.format,
|
| 204 |
+
"format_description": spec["documented_name"],
|
| 205 |
+
"source": {
|
| 206 |
+
"path": str(args.src),
|
| 207 |
+
"bytes": args.src.stat().st_size,
|
| 208 |
+
"sha256": src_sha256,
|
| 209 |
+
"tensor_count": src_header["tensor_count"],
|
| 210 |
+
"metadata": src_header["metadata"],
|
| 211 |
+
},
|
| 212 |
+
"output": {
|
| 213 |
+
"path": str(args.dst),
|
| 214 |
+
"bytes": args.dst.stat().st_size,
|
| 215 |
+
"sha256": out_sha256,
|
| 216 |
+
"tensor_count": out_header["tensor_count"],
|
| 217 |
+
},
|
| 218 |
+
"runtime": {
|
| 219 |
+
"torch": torch.__version__,
|
| 220 |
+
"cuda": torch.version.cuda,
|
| 221 |
+
"gpu": torch.cuda.get_device_name(0),
|
| 222 |
+
"comfy_kitchen": importlib.metadata.version("comfy-kitchen"),
|
| 223 |
+
"comfyui_commit": args.comfyui_commit,
|
| 224 |
+
},
|
| 225 |
+
"policy": {
|
| 226 |
+
"quantized_linears": "all 50 DiT blocks: fused qkv_proj, out_proj, to_gate_compress, mlp.fc1, mlp.fc2",
|
| 227 |
+
"dense_linears": "token refiner, AdaLN, norms, embeddings, patch/condition projections, FP32 PDD heads",
|
| 228 |
+
"qkv": "fused by the native Comfy conversion, then quantized as one tensor with one weight scale",
|
| 229 |
+
"activation_quantization": spec["activation"],
|
| 230 |
+
"native_comfy_layout": spec["layout"],
|
| 231 |
+
},
|
| 232 |
+
"quantized_linear_count": len(linears),
|
| 233 |
+
"quantized_linears": linears,
|
| 234 |
+
"pass_through_tensor_count": identical,
|
| 235 |
+
"pass_through_byte_identical": True,
|
| 236 |
+
"elapsed_seconds": round(time.time() - started, 1),
|
| 237 |
+
}
|
| 238 |
+
args.receipt.parent.mkdir(parents=True, exist_ok=True)
|
| 239 |
+
args.receipt.write_text(json.dumps(receipt, indent=2) + "\n")
|
| 240 |
+
print(json.dumps({k: receipt[k] for k in ("format", "output", "quantized_linear_count", "elapsed_seconds")}, indent=2))
|
| 241 |
+
return 0
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
if __name__ == "__main__":
|
| 245 |
+
sys.exit(main())
|
conversion/validate_native_comfy.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""CPU format/schema validation for native ComfyUI OmniRef quantized checkpoints.
|
| 3 |
+
|
| 4 |
+
Checks, without CUDA:
|
| 5 |
+
* the exact quantized-linear set and native Comfy marker JSON;
|
| 6 |
+
* storage dtypes/shapes against the pinned ComfyUI layout rules;
|
| 7 |
+
* every dense tensor is byte-identical to the BF16 source;
|
| 8 |
+
* the pinned ComfyUI detector recovers the expected architecture;
|
| 9 |
+
* when possible, a strict native state-dict load on CPU through ``comfy.ops``.
|
| 10 |
+
|
| 11 |
+
FP8/NVFP4 kernel execution is validated separately on the GPU.
|
| 12 |
+
"""
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import datetime as dt
|
| 17 |
+
import hashlib
|
| 18 |
+
import json
|
| 19 |
+
import math
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
import sys
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
from safetensors import safe_open
|
| 25 |
+
|
| 26 |
+
QUANT_LINEAR = __import__("re").compile(
|
| 27 |
+
r"^blocks\.\d+\.(?:attn\.(?:qkv_proj|out_proj|to_gate_compress)|mlp\.(?:fc1|fc2))\.weight$"
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def sha256_file(path: Path) -> str:
|
| 32 |
+
digest = hashlib.sha256()
|
| 33 |
+
with path.open("rb") as handle:
|
| 34 |
+
for block in iter(lambda: handle.read(64 * 1024 * 1024), b""):
|
| 35 |
+
digest.update(block)
|
| 36 |
+
return digest.hexdigest()
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def header(path: Path) -> tuple[list[str], dict, dict]:
|
| 40 |
+
with safe_open(str(path), framework="pt", device="cpu") as handle:
|
| 41 |
+
keys = list(handle.keys())
|
| 42 |
+
metadata = handle.metadata() or {}
|
| 43 |
+
details = {key: (str(handle.get_slice(key).get_dtype()), list(handle.get_slice(key).get_shape())) for key in keys}
|
| 44 |
+
return keys, metadata, details
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def is_nvfp4(metadata: dict) -> bool:
|
| 48 |
+
return json.loads(metadata.get("quantization", "{}")).get("format") == "nvfp4"
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def main() -> int:
|
| 52 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 53 |
+
parser.add_argument("--src", required=True, type=Path, help="BF16 native Comfy source")
|
| 54 |
+
parser.add_argument("--dst", required=True, type=Path, help="Quantized native Comfy file")
|
| 55 |
+
parser.add_argument("--format", required=True, choices=["fp8", "nvfp4"])
|
| 56 |
+
parser.add_argument("--receipt", required=True, type=Path)
|
| 57 |
+
parser.add_argument("--comfy-root", default=None, help="Pinned ComfyUI checkout for native detection/load")
|
| 58 |
+
args = parser.parse_args()
|
| 59 |
+
|
| 60 |
+
errors: list[str] = []
|
| 61 |
+
src_keys, src_meta, src_details = header(args.src)
|
| 62 |
+
dst_keys, dst_meta, dst_details = header(args.dst)
|
| 63 |
+
|
| 64 |
+
expected_quant = {key for key in src_keys if QUANT_LINEAR.match(key)}
|
| 65 |
+
expected_markers = {key[: -len(".weight")] + ".comfy_quant" for key in expected_quant}
|
| 66 |
+
if len(expected_quant) != 250:
|
| 67 |
+
errors.append(f"expected 250 quantized linears in source, found {len(expected_quant)}")
|
| 68 |
+
|
| 69 |
+
actual_base = {key for key in dst_keys if QUANT_LINEAR.match(key)}
|
| 70 |
+
if actual_base != expected_quant:
|
| 71 |
+
errors.append("quantized weight set differs from expected block linears")
|
| 72 |
+
|
| 73 |
+
for name in expected_quant:
|
| 74 |
+
marker_key = name[: -len(".weight")] + ".comfy_quant"
|
| 75 |
+
if marker_key not in dst_details:
|
| 76 |
+
errors.append(f"missing marker: {marker_key}")
|
| 77 |
+
continue
|
| 78 |
+
with safe_open(str(args.dst), framework="pt", device="cpu") as handle:
|
| 79 |
+
marker = json.loads(handle.get_tensor(marker_key).numpy().tobytes())
|
| 80 |
+
expected_format = "float8_e4m3fn" if args.format == "fp8" else "nvfp4"
|
| 81 |
+
if marker.get("format") != expected_format:
|
| 82 |
+
errors.append(f"bad marker for {name}: {marker}")
|
| 83 |
+
scale_key = name + "_scale"
|
| 84 |
+
if scale_key not in dst_details:
|
| 85 |
+
errors.append(f"missing scale: {scale_key}")
|
| 86 |
+
if args.format == "nvfp4":
|
| 87 |
+
if name + "_scale_2" not in dst_details:
|
| 88 |
+
errors.append(f"missing global scale: {name}_scale_2")
|
| 89 |
+
logical = src_details[name][1]
|
| 90 |
+
rows, cols = logical
|
| 91 |
+
expected_storage = [rows, math.ceil(cols / 16) * 8] # K/2 with 16x padding
|
| 92 |
+
storage_shape = dst_details[name][1]
|
| 93 |
+
if storage_shape != expected_storage:
|
| 94 |
+
errors.append(f"{name}: NVFP4 storage {storage_shape} != {expected_storage}")
|
| 95 |
+
scale_shape = dst_details[scale_key][1]
|
| 96 |
+
expected_scale = [rows, math.ceil(cols / 16)]
|
| 97 |
+
if scale_shape != expected_scale:
|
| 98 |
+
errors.append(f"{name}: block scale {scale_shape} != {expected_scale}")
|
| 99 |
+
if dst_details[name][0] != "U8":
|
| 100 |
+
errors.append(f"{name}: NVFP4 storage dtype {dst_details[name][0]} != U8")
|
| 101 |
+
else:
|
| 102 |
+
if dst_details[name][1] != src_details[name][1]:
|
| 103 |
+
errors.append(f"{name}: FP8 shape {dst_details[name][1]} != {src_details[name][1]}")
|
| 104 |
+
if dst_details[name][0] != "F8_E4M3":
|
| 105 |
+
errors.append(f"{name}: FP8 dtype {dst_details[name][0]} != F8_E4M3")
|
| 106 |
+
if dst_details[scale_key][1] not in ([], [1]):
|
| 107 |
+
errors.append(f"{name}: FP8 weight_scale shape {dst_details[scale_key][1]} is not per-tensor")
|
| 108 |
+
|
| 109 |
+
# Dense pass-through tensors must be byte-identical.
|
| 110 |
+
dense = [key for key in src_keys if not QUANT_LINEAR.match(key)]
|
| 111 |
+
changed = []
|
| 112 |
+
with safe_open(str(args.src), framework="pt", device="cpu") as src_reader, safe_open(
|
| 113 |
+
str(args.dst), framework="pt", device="cpu"
|
| 114 |
+
) as dst_reader:
|
| 115 |
+
for name in dense:
|
| 116 |
+
left = src_reader.get_tensor(name)
|
| 117 |
+
right = dst_reader.get_tensor(name)
|
| 118 |
+
if left.dtype != right.dtype or left.shape != right.shape or not torch.equal(left, right):
|
| 119 |
+
changed.append(name)
|
| 120 |
+
if changed:
|
| 121 |
+
errors.append(f"dense tensors changed: {changed[:8]}")
|
| 122 |
+
|
| 123 |
+
detected = None
|
| 124 |
+
native_load = {"status": "not_requested"}
|
| 125 |
+
if args.comfy_root:
|
| 126 |
+
sys.path.insert(0, str(Path(args.comfy_root).resolve()))
|
| 127 |
+
try:
|
| 128 |
+
import comfy.model_detection
|
| 129 |
+
import comfy.ops
|
| 130 |
+
from comfy.ldm.minimax.model import MiniMaxH3Model
|
| 131 |
+
from safetensors.torch import load_file
|
| 132 |
+
|
| 133 |
+
state = load_file(str(args.dst), device="cpu")
|
| 134 |
+
detected = comfy.model_detection.detect_unet_config(state, "", metadata=dst_meta)
|
| 135 |
+
expected_arch = {
|
| 136 |
+
"image_model": "minimax_h3",
|
| 137 |
+
"num_layers": 50,
|
| 138 |
+
"token_refiner_num_layers": 2,
|
| 139 |
+
"hidden_size": 5376,
|
| 140 |
+
"latents_dim": 24,
|
| 141 |
+
"audio_latents_dim": 32,
|
| 142 |
+
"attention_head_dim": 128,
|
| 143 |
+
"num_attention_heads": 56,
|
| 144 |
+
"ffn_hidden_size": 14336,
|
| 145 |
+
"gate_compress": True,
|
| 146 |
+
"pdd_steps": 32,
|
| 147 |
+
}
|
| 148 |
+
for key, value in expected_arch.items():
|
| 149 |
+
if detected.get(key) != value:
|
| 150 |
+
errors.append(f"detected {key}={detected.get(key)!r} != {value!r}")
|
| 151 |
+
operations = comfy.ops.mixed_precision_ops(
|
| 152 |
+
{"mixed_ops": True}, compute_dtype=torch.float32, disabled=set()
|
| 153 |
+
)
|
| 154 |
+
model = MiniMaxH3Model(**detected, dtype=torch.float32, device="cpu", operations=operations)
|
| 155 |
+
result = model.load_state_dict(state, strict=True)
|
| 156 |
+
native_load = {
|
| 157 |
+
"status": "passed",
|
| 158 |
+
"missing_keys": list(result.missing_keys),
|
| 159 |
+
"unexpected_keys": list(result.unexpected_keys),
|
| 160 |
+
}
|
| 161 |
+
if result.missing_keys or result.unexpected_keys:
|
| 162 |
+
errors.append("native strict load reported missing/unexpected keys")
|
| 163 |
+
except Exception as exc: # noqa: BLE001 - report exact environment limitation
|
| 164 |
+
# The format/schema checks above are authoritative on CPU. Some
|
| 165 |
+
# comfy-kitchen builds cannot construct quantized modules without a
|
| 166 |
+
# CUDA context; the GPU end-to-end run covers that path.
|
| 167 |
+
native_load = {"status": "unavailable", "error": f"{type(exc).__name__}: {exc}"}
|
| 168 |
+
|
| 169 |
+
receipt = {
|
| 170 |
+
"schema": "omniref-comfy-schema-validation-v1",
|
| 171 |
+
"created_utc": dt.datetime.now(dt.timezone.utc).isoformat(),
|
| 172 |
+
"format": args.format,
|
| 173 |
+
"source": {"path": str(args.src), "sha256": sha256_file(args.src), "tensor_count": len(src_keys)},
|
| 174 |
+
"quantized": {"path": str(args.dst), "sha256": sha256_file(args.dst), "tensor_count": len(dst_keys)},
|
| 175 |
+
"checks": {
|
| 176 |
+
"quantized_linear_count": len(expected_quant),
|
| 177 |
+
"markers_present": sorted(expected_markers) == sorted(
|
| 178 |
+
key for key in dst_keys if key.endswith(".comfy_quant")
|
| 179 |
+
),
|
| 180 |
+
"dense_tensors_checked": len(dense),
|
| 181 |
+
"dense_tensors_changed": changed,
|
| 182 |
+
"detected_config": detected,
|
| 183 |
+
"native_cpu_strict_load": native_load,
|
| 184 |
+
},
|
| 185 |
+
"errors": errors,
|
| 186 |
+
"result": "passed" if not errors else "failed",
|
| 187 |
+
}
|
| 188 |
+
args.receipt.parent.mkdir(parents=True, exist_ok=True)
|
| 189 |
+
args.receipt.write_text(json.dumps(receipt, indent=2) + "\n")
|
| 190 |
+
print(json.dumps({"result": receipt["result"], "errors": errors, "native": native_load}, indent=2))
|
| 191 |
+
return 0 if not errors else 1
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
if __name__ == "__main__":
|
| 195 |
+
raise SystemExit(main())
|
provenance/CONVERSION.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# NVFP4 QAD Comfy conversion and quantization
|
| 2 |
+
|
| 3 |
+
## Source
|
| 4 |
+
|
| 5 |
+
- Dense BF16 QAD master: `transformer_bf16` (14 shards), exported from the QAD student
|
| 6 |
+
of `FastVideo/FastVideo-FastH3-Omni-8-Step-V1`.
|
| 7 |
+
- The master has the canonical OmniRef geometry: 688 tensors, 50 transformer blocks,
|
| 8 |
+
32 PDD heads, video/audio shifts 12/3, VSA gate compression.
|
| 9 |
+
- All tensors outside the QAD-trained projections are byte-identical to the original
|
| 10 |
+
OmniRef transformer; the trained attention/MLP projections differ. See
|
| 11 |
+
`QAD_SOURCE_VERIFICATION.json`.
|
| 12 |
+
- Pinned ComfyUI: `b1a247c9cf9dbfda4b2c0a0c1d50950667eeaa0c`.
|
| 13 |
+
- Pinned comfy-kitchen: `0.2.39`.
|
| 14 |
+
|
| 15 |
+
## Conversion
|
| 16 |
+
|
| 17 |
+
The dense BF16 master is converted with the OmniRef native Comfy converter:
|
| 18 |
+
|
| 19 |
+
- rename every source tensor to the native ComfyUI names;
|
| 20 |
+
- fuse Q/K/V in q, k, v row order, including the token refiner;
|
| 21 |
+
- swap the two SwiGLU input halves;
|
| 22 |
+
- convert the 32 PDD heads to Comfy's FP32 first-head-plus-offset layout;
|
| 23 |
+
- derive the persistent RoPE frequencies;
|
| 24 |
+
- apply the rank-8 AdaLN time-embedding curve on a 1025-point grid, the same
|
| 25 |
+
compression used by the BF16 OmniRef Comfy export.
|
| 26 |
+
|
| 27 |
+
## Quantization
|
| 28 |
+
|
| 29 |
+
This is a new PTQ export of the QAD BF16 master, not a byte-preserving repack of the
|
| 30 |
+
FastVideo packed NVFP4 checkpoint.
|
| 31 |
+
|
| 32 |
+
- Q/K/V are fused by the native Comfy conversion before quantization. Comfy's fused
|
| 33 |
+
`qkv_proj` therefore receives **one per-tensor global scale** plus FP8 block scales
|
| 34 |
+
(group size 16), matching `TensorCoreNVFP4Layout`.
|
| 35 |
+
- The FastVideo packed QAD checkpoint stores separate Q/K/V matrices with independent
|
| 36 |
+
global scales; concatenating those tensors unchanged would not be valid for Comfy's
|
| 37 |
+
fused module.
|
| 38 |
+
- `out_proj`, `to_gate_compress`, `mlp.fc1`, and `mlp.fc2` in all 50 blocks are
|
| 39 |
+
quantized with the same native NVFP4 layout.
|
| 40 |
+
- ComfyUI dynamically quantizes activations per tensor at runtime.
|
| 41 |
+
- Token refiner, AdaLN, norms, embeddings, patch/condition projections, and FP32 PDD
|
| 42 |
+
output heads stay dense and byte-identical to the QAD BF16 Comfy conversion.
|
| 43 |
+
- Comfy marker: `{"format":"nvfp4"}` on each quantized module.
|
| 44 |
+
|
| 45 |
+
## Reproduce
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
python convert_omniref_comfy.py --src transformer_bf16 --contract fastvideo_inference.json --output fasth3_omniref_qad_bf16_comfy.safetensors --scratch scratch-qad-curve-r8 --adaln-curve-rank 8 --curve-grid 1025
|
| 49 |
+
|
| 50 |
+
python quantize_native_comfy.py --src fasth3_omniref_qad_bf16_comfy.safetensors --dst fasth3_omniref_qad_nvfp4_comfy.safetensors --format nvfp4 --receipt nvfp4_quantization.json --expect-tensors 582
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
The receipts record source/output SHA256 hashes, runtime versions, every quantized
|
| 54 |
+
linear, and per-linear reconstruction error.
|
| 55 |
+
|
provenance/QAD_CONVERSION_RECEIPT.json
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"file": "fasth3_omniref_qad_bf16_comfy.safetensors",
|
| 3 |
+
"bytes": 44240780704,
|
| 4 |
+
"sha256": "4e244969987e3c6e16bffa449bcf62c1f7c83b5cce15e5806246d3ae8055154f",
|
| 5 |
+
"tensors": 582,
|
| 6 |
+
"tensor_data_bytes": 44240717408,
|
| 7 |
+
"source_repo": "FastVideo/FastVideo-FastH3-Omni-8-Step-V1",
|
| 8 |
+
"source_revision": "88d512c5e7cc38dda499fbb65688ce58611f9c80",
|
| 9 |
+
"source_index_sha256": "40979168a4c9f720953f5af95552603a5273f4eee3e58d3615514a6267cc3d46",
|
| 10 |
+
"source_config": {
|
| 11 |
+
"_class_name": "MiniMaxH3Transformer3DModel",
|
| 12 |
+
"_diffusers_version": "0.36.0.dev0",
|
| 13 |
+
"attention_head_dim": 128,
|
| 14 |
+
"audio_in_channels": 32,
|
| 15 |
+
"ffn_dim": 14336,
|
| 16 |
+
"final_norm_eps": 1e-05,
|
| 17 |
+
"freq_dim": 256,
|
| 18 |
+
"hidden_size": 5376,
|
| 19 |
+
"in_channels": 24,
|
| 20 |
+
"norm_eps": 1e-05,
|
| 21 |
+
"num_attention_heads": 56,
|
| 22 |
+
"num_layers": 50,
|
| 23 |
+
"num_refiner_layers": 2,
|
| 24 |
+
"patch_size": [
|
| 25 |
+
1,
|
| 26 |
+
2,
|
| 27 |
+
2
|
| 28 |
+
],
|
| 29 |
+
"pdd_steps": 32,
|
| 30 |
+
"qk_norm_eps": 1e-05,
|
| 31 |
+
"rope_freq_dim": 16,
|
| 32 |
+
"rope_theta": 10000.0,
|
| 33 |
+
"text_dim": 5120,
|
| 34 |
+
"time_embed_dim": 2688,
|
| 35 |
+
"time_embed_hidden_dim": 5376
|
| 36 |
+
},
|
| 37 |
+
"source_contract": {
|
| 38 |
+
"attention_backend": "VIDEO_SPARSE_ATTN_H3",
|
| 39 |
+
"audio_scheduler_shift": 3.0,
|
| 40 |
+
"base_model_revision": "hf://MiniMaxAI/MiniMax-H3@9bfb6693f2cf6de171db46d1aa586f67d773a1da",
|
| 41 |
+
"conditioning": "fixed_ordered_references_target_only_flow",
|
| 42 |
+
"grid_max_t": 0.999,
|
| 43 |
+
"guidance_scale": 1.0,
|
| 44 |
+
"model_type": "ref2va",
|
| 45 |
+
"num_inference_steps": 8,
|
| 46 |
+
"pdd_step_indices": [
|
| 47 |
+
0,
|
| 48 |
+
4,
|
| 49 |
+
8,
|
| 50 |
+
12,
|
| 51 |
+
16,
|
| 52 |
+
20,
|
| 53 |
+
24,
|
| 54 |
+
28,
|
| 55 |
+
32
|
| 56 |
+
],
|
| 57 |
+
"pdd_steps": 32,
|
| 58 |
+
"schema": "fasth3-inference-contract-v1",
|
| 59 |
+
"schema_version": "fasth3-inference-contract-v1",
|
| 60 |
+
"transformer_component": "transformer_ref",
|
| 61 |
+
"transformer_forwards": 8,
|
| 62 |
+
"video_scheduler_shift": 12.0,
|
| 63 |
+
"vsa_ref_keep_rate": 0.1,
|
| 64 |
+
"vsa_ref_policy": "p2_multi_region",
|
| 65 |
+
"vsa_sparsity": 0.9,
|
| 66 |
+
"vsa_tile_size": 128
|
| 67 |
+
},
|
| 68 |
+
"source_tensors": 688,
|
| 69 |
+
"source_tensor_bytes": 70142174464,
|
| 70 |
+
"skipped_source_tensors": [],
|
| 71 |
+
"conversion": [
|
| 72 |
+
"concatenateQKV",
|
| 73 |
+
"swapSwiGLUhalves",
|
| 74 |
+
"PDDfullheadsToFP32offsetheads",
|
| 75 |
+
"deriveRoPE"
|
| 76 |
+
],
|
| 77 |
+
"curve": {
|
| 78 |
+
"kind": "time-embedding-curve-svd-and-linear-interpolation",
|
| 79 |
+
"rank": 8,
|
| 80 |
+
"grid": 1025,
|
| 81 |
+
"table_dtype": "F32",
|
| 82 |
+
"projected_weight_dtype": "F32",
|
| 83 |
+
"fit_domain": [
|
| 84 |
+
0.0,
|
| 85 |
+
1.0
|
| 86 |
+
],
|
| 87 |
+
"fit_relative_residual": 0.00010133369976635075,
|
| 88 |
+
"holdout_timesteps": 129,
|
| 89 |
+
"holdout_modulation_samples": [
|
| 90 |
+
{
|
| 91 |
+
"tensor": "norm_out.linear.weight",
|
| 92 |
+
"sampled_rows": 64,
|
| 93 |
+
"relative_l2": 0.0002865233464574267,
|
| 94 |
+
"max_abs": 0.0006642544918661342
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"tensor": "transformer_blocks.0.adaln_proj.linear.weight",
|
| 98 |
+
"sampled_rows": 64,
|
| 99 |
+
"relative_l2": 0.00028220971342389804,
|
| 100 |
+
"max_abs": 0.000655848104541279
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"tensor": "transformer_blocks.1.adaln_proj.linear.weight",
|
| 104 |
+
"sampled_rows": 64,
|
| 105 |
+
"relative_l2": 0.0002500019094960694,
|
| 106 |
+
"max_abs": 0.0005601141953750499
|
| 107 |
+
},
|
| 108 |
+
{
|
| 109 |
+
"tensor": "transformer_blocks.10.adaln_proj.linear.weight",
|
| 110 |
+
"sampled_rows": 64,
|
| 111 |
+
"relative_l2": 0.0002416551213296664,
|
| 112 |
+
"max_abs": 0.0005127902298566189
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"tensor": "transformer_blocks.11.adaln_proj.linear.weight",
|
| 116 |
+
"sampled_rows": 64,
|
| 117 |
+
"relative_l2": 0.00025566255176295583,
|
| 118 |
+
"max_abs": 0.0006970543184967593
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"tensor": "transformer_blocks.12.adaln_proj.linear.weight",
|
| 122 |
+
"sampled_rows": 64,
|
| 123 |
+
"relative_l2": 0.0002803780968880836,
|
| 124 |
+
"max_abs": 0.0005343511257961397
|
| 125 |
+
},
|
| 126 |
+
{
|
| 127 |
+
"tensor": "transformer_blocks.13.adaln_proj.linear.weight",
|
| 128 |
+
"sampled_rows": 64,
|
| 129 |
+
"relative_l2": 0.0002584695908441387,
|
| 130 |
+
"max_abs": 0.0004739712926107309
|
| 131 |
+
},
|
| 132 |
+
{
|
| 133 |
+
"tensor": "transformer_blocks.14.adaln_proj.linear.weight",
|
| 134 |
+
"sampled_rows": 64,
|
| 135 |
+
"relative_l2": 0.0002910532788419137,
|
| 136 |
+
"max_abs": 0.0006167935081637932
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"tensor": "transformer_blocks.15.adaln_proj.linear.weight",
|
| 140 |
+
"sampled_rows": 64,
|
| 141 |
+
"relative_l2": 0.0002469427830971245,
|
| 142 |
+
"max_abs": 0.00035769183356759715
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
"tensor": "transformer_blocks.16.adaln_proj.linear.weight",
|
| 146 |
+
"sampled_rows": 64,
|
| 147 |
+
"relative_l2": 0.00028391790586773294,
|
| 148 |
+
"max_abs": 0.00047246609881251844
|
| 149 |
+
},
|
| 150 |
+
{
|
| 151 |
+
"tensor": "transformer_blocks.17.adaln_proj.linear.weight",
|
| 152 |
+
"sampled_rows": 64,
|
| 153 |
+
"relative_l2": 0.0002869634349253921,
|
| 154 |
+
"max_abs": 0.0007902806770636595
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"tensor": "transformer_blocks.18.adaln_proj.linear.weight",
|
| 158 |
+
"sampled_rows": 64,
|
| 159 |
+
"relative_l2": 0.0002485772094970746,
|
| 160 |
+
"max_abs": 0.0006660115750541928
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"tensor": "transformer_blocks.19.adaln_proj.linear.weight",
|
| 164 |
+
"sampled_rows": 64,
|
| 165 |
+
"relative_l2": 0.00022820637810836426,
|
| 166 |
+
"max_abs": 0.000454958769330438
|
| 167 |
+
},
|
| 168 |
+
{
|
| 169 |
+
"tensor": "transformer_blocks.2.adaln_proj.linear.weight",
|
| 170 |
+
"sampled_rows": 64,
|
| 171 |
+
"relative_l2": 0.00027655427074800646,
|
| 172 |
+
"max_abs": 0.0009016942397257477
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
"tensor": "transformer_blocks.20.adaln_proj.linear.weight",
|
| 176 |
+
"sampled_rows": 64,
|
| 177 |
+
"relative_l2": 0.00030725577566114673,
|
| 178 |
+
"max_abs": 0.0008379473900057555
|
| 179 |
+
},
|
| 180 |
+
{
|
| 181 |
+
"tensor": "transformer_blocks.21.adaln_proj.linear.weight",
|
| 182 |
+
"sampled_rows": 64,
|
| 183 |
+
"relative_l2": 0.00023061350160196578,
|
| 184 |
+
"max_abs": 0.0011666881189658085
|
| 185 |
+
},
|
| 186 |
+
{
|
| 187 |
+
"tensor": "transformer_blocks.22.adaln_proj.linear.weight",
|
| 188 |
+
"sampled_rows": 64,
|
| 189 |
+
"relative_l2": 0.00023835166467095758,
|
| 190 |
+
"max_abs": 0.0007218851744519306
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"tensor": "transformer_blocks.23.adaln_proj.linear.weight",
|
| 194 |
+
"sampled_rows": 64,
|
| 195 |
+
"relative_l2": 0.0002533558506562286,
|
| 196 |
+
"max_abs": 0.0007599189025755404
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"tensor": "transformer_blocks.24.adaln_proj.linear.weight",
|
| 200 |
+
"sampled_rows": 64,
|
| 201 |
+
"relative_l2": 0.00025434361772686556,
|
| 202 |
+
"max_abs": 0.0006184998097025218
|
| 203 |
+
},
|
| 204 |
+
{
|
| 205 |
+
"tensor": "transformer_blocks.25.adaln_proj.linear.weight",
|
| 206 |
+
"sampled_rows": 64,
|
| 207 |
+
"relative_l2": 0.0002803287478038723,
|
| 208 |
+
"max_abs": 0.0008424069119212785
|
| 209 |
+
},
|
| 210 |
+
{
|
| 211 |
+
"tensor": "transformer_blocks.26.adaln_proj.linear.weight",
|
| 212 |
+
"sampled_rows": 64,
|
| 213 |
+
"relative_l2": 0.0002427097226267563,
|
| 214 |
+
"max_abs": 0.0005747431665024116
|
| 215 |
+
},
|
| 216 |
+
{
|
| 217 |
+
"tensor": "transformer_blocks.27.adaln_proj.linear.weight",
|
| 218 |
+
"sampled_rows": 64,
|
| 219 |
+
"relative_l2": 0.0002610874979495917,
|
| 220 |
+
"max_abs": 0.0011547877335882362
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"tensor": "transformer_blocks.28.adaln_proj.linear.weight",
|
| 224 |
+
"sampled_rows": 64,
|
| 225 |
+
"relative_l2": 0.00025688772628382954,
|
| 226 |
+
"max_abs": 0.0007070723287414538
|
| 227 |
+
},
|
| 228 |
+
{
|
| 229 |
+
"tensor": "transformer_blocks.29.adaln_proj.linear.weight",
|
| 230 |
+
"sampled_rows": 64,
|
| 231 |
+
"relative_l2": 0.00026883960881806534,
|
| 232 |
+
"max_abs": 0.0008599025781315106
|
| 233 |
+
},
|
| 234 |
+
{
|
| 235 |
+
"tensor": "transformer_blocks.3.adaln_proj.linear.weight",
|
| 236 |
+
"sampled_rows": 64,
|
| 237 |
+
"relative_l2": 0.0002368578247393376,
|
| 238 |
+
"max_abs": 0.0009200827465114525
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"tensor": "transformer_blocks.30.adaln_proj.linear.weight",
|
| 242 |
+
"sampled_rows": 64,
|
| 243 |
+
"relative_l2": 0.00024855077930941723,
|
| 244 |
+
"max_abs": 0.0013539101566308887
|
| 245 |
+
},
|
| 246 |
+
{
|
| 247 |
+
"tensor": "transformer_blocks.31.adaln_proj.linear.weight",
|
| 248 |
+
"sampled_rows": 64,
|
| 249 |
+
"relative_l2": 0.000248474962059711,
|
| 250 |
+
"max_abs": 0.0012859274503882467
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"tensor": "transformer_blocks.32.adaln_proj.linear.weight",
|
| 254 |
+
"sampled_rows": 64,
|
| 255 |
+
"relative_l2": 0.0002428254709366112,
|
| 256 |
+
"max_abs": 0.0014554129821475748
|
| 257 |
+
},
|
| 258 |
+
{
|
| 259 |
+
"tensor": "transformer_blocks.33.adaln_proj.linear.weight",
|
| 260 |
+
"sampled_rows": 64,
|
| 261 |
+
"relative_l2": 0.00023598417936072013,
|
| 262 |
+
"max_abs": 0.001837545487124892
|
| 263 |
+
},
|
| 264 |
+
{
|
| 265 |
+
"tensor": "transformer_blocks.34.adaln_proj.linear.weight",
|
| 266 |
+
"sampled_rows": 64,
|
| 267 |
+
"relative_l2": 0.0002501798711765004,
|
| 268 |
+
"max_abs": 0.002109945793156154
|
| 269 |
+
},
|
| 270 |
+
{
|
| 271 |
+
"tensor": "transformer_blocks.35.adaln_proj.linear.weight",
|
| 272 |
+
"sampled_rows": 64,
|
| 273 |
+
"relative_l2": 0.0002425583822798537,
|
| 274 |
+
"max_abs": 0.002020115661256483
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"tensor": "transformer_blocks.36.adaln_proj.linear.weight",
|
| 278 |
+
"sampled_rows": 64,
|
| 279 |
+
"relative_l2": 0.00023483459603999013,
|
| 280 |
+
"max_abs": 0.0017876969071357607
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"tensor": "transformer_blocks.37.adaln_proj.linear.weight",
|
| 284 |
+
"sampled_rows": 64,
|
| 285 |
+
"relative_l2": 0.00023134358346439085,
|
| 286 |
+
"max_abs": 0.0017698832170331968
|
| 287 |
+
},
|
| 288 |
+
{
|
| 289 |
+
"tensor": "transformer_blocks.38.adaln_proj.linear.weight",
|
| 290 |
+
"sampled_rows": 64,
|
| 291 |
+
"relative_l2": 0.00022657149892788762,
|
| 292 |
+
"max_abs": 0.0019057241907223244
|
| 293 |
+
},
|
| 294 |
+
{
|
| 295 |
+
"tensor": "transformer_blocks.39.adaln_proj.linear.weight",
|
| 296 |
+
"sampled_rows": 64,
|
| 297 |
+
"relative_l2": 0.00022759120835296538,
|
| 298 |
+
"max_abs": 0.0022020056775189367
|
| 299 |
+
},
|
| 300 |
+
{
|
| 301 |
+
"tensor": "transformer_blocks.4.adaln_proj.linear.weight",
|
| 302 |
+
"sampled_rows": 64,
|
| 303 |
+
"relative_l2": 0.0002595302991378682,
|
| 304 |
+
"max_abs": 0.0008776936584853345
|
| 305 |
+
},
|
| 306 |
+
{
|
| 307 |
+
"tensor": "transformer_blocks.40.adaln_proj.linear.weight",
|
| 308 |
+
"sampled_rows": 64,
|
| 309 |
+
"relative_l2": 0.0002313392567915914,
|
| 310 |
+
"max_abs": 0.0023742669073691935
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
"tensor": "transformer_blocks.41.adaln_proj.linear.weight",
|
| 314 |
+
"sampled_rows": 64,
|
| 315 |
+
"relative_l2": 0.00022581828148539781,
|
| 316 |
+
"max_abs": 0.002467452761190536
|
| 317 |
+
},
|
| 318 |
+
{
|
| 319 |
+
"tensor": "transformer_blocks.42.adaln_proj.linear.weight",
|
| 320 |
+
"sampled_rows": 64,
|
| 321 |
+
"relative_l2": 0.00022962368103619676,
|
| 322 |
+
"max_abs": 0.002600971318656775
|
| 323 |
+
},
|
| 324 |
+
{
|
| 325 |
+
"tensor": "transformer_blocks.43.adaln_proj.linear.weight",
|
| 326 |
+
"sampled_rows": 64,
|
| 327 |
+
"relative_l2": 0.0002246428667526305,
|
| 328 |
+
"max_abs": 0.0023927730378194845
|
| 329 |
+
},
|
| 330 |
+
{
|
| 331 |
+
"tensor": "transformer_blocks.44.adaln_proj.linear.weight",
|
| 332 |
+
"sampled_rows": 64,
|
| 333 |
+
"relative_l2": 0.00022718124717151158,
|
| 334 |
+
"max_abs": 0.002239463950175047
|
| 335 |
+
},
|
| 336 |
+
{
|
| 337 |
+
"tensor": "transformer_blocks.45.adaln_proj.linear.weight",
|
| 338 |
+
"sampled_rows": 64,
|
| 339 |
+
"relative_l2": 0.00023251823784486256,
|
| 340 |
+
"max_abs": 0.002354271058670143
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"tensor": "transformer_blocks.46.adaln_proj.linear.weight",
|
| 344 |
+
"sampled_rows": 64,
|
| 345 |
+
"relative_l2": 0.00023286344384665863,
|
| 346 |
+
"max_abs": 0.003132404161736435
|
| 347 |
+
},
|
| 348 |
+
{
|
| 349 |
+
"tensor": "transformer_blocks.47.adaln_proj.linear.weight",
|
| 350 |
+
"sampled_rows": 64,
|
| 351 |
+
"relative_l2": 0.00022930350045823262,
|
| 352 |
+
"max_abs": 0.0035076916507517097
|
| 353 |
+
},
|
| 354 |
+
{
|
| 355 |
+
"tensor": "transformer_blocks.48.adaln_proj.linear.weight",
|
| 356 |
+
"sampled_rows": 64,
|
| 357 |
+
"relative_l2": 0.0002468179623068937,
|
| 358 |
+
"max_abs": 0.0029221208618497307
|
| 359 |
+
},
|
| 360 |
+
{
|
| 361 |
+
"tensor": "transformer_blocks.49.adaln_proj.linear.weight",
|
| 362 |
+
"sampled_rows": 64,
|
| 363 |
+
"relative_l2": 0.00028150893929036644,
|
| 364 |
+
"max_abs": 0.005873630323451895
|
| 365 |
+
},
|
| 366 |
+
{
|
| 367 |
+
"tensor": "transformer_blocks.5.adaln_proj.linear.weight",
|
| 368 |
+
"sampled_rows": 64,
|
| 369 |
+
"relative_l2": 0.00024675880824154896,
|
| 370 |
+
"max_abs": 0.000894859385961233
|
| 371 |
+
},
|
| 372 |
+
{
|
| 373 |
+
"tensor": "transformer_blocks.6.adaln_proj.linear.weight",
|
| 374 |
+
"sampled_rows": 64,
|
| 375 |
+
"relative_l2": 0.00029495062340505695,
|
| 376 |
+
"max_abs": 0.0006582339453927721
|
| 377 |
+
},
|
| 378 |
+
{
|
| 379 |
+
"tensor": "transformer_blocks.7.adaln_proj.linear.weight",
|
| 380 |
+
"sampled_rows": 64,
|
| 381 |
+
"relative_l2": 0.00028241220478707007,
|
| 382 |
+
"max_abs": 0.0006037143577201087
|
| 383 |
+
},
|
| 384 |
+
{
|
| 385 |
+
"tensor": "transformer_blocks.8.adaln_proj.linear.weight",
|
| 386 |
+
"sampled_rows": 64,
|
| 387 |
+
"relative_l2": 0.0002680551951240189,
|
| 388 |
+
"max_abs": 0.0004946770126523781
|
| 389 |
+
},
|
| 390 |
+
{
|
| 391 |
+
"tensor": "transformer_blocks.9.adaln_proj.linear.weight",
|
| 392 |
+
"sampled_rows": 64,
|
| 393 |
+
"relative_l2": 0.0002718576327394062,
|
| 394 |
+
"max_abs": 0.0005833224778661883
|
| 395 |
+
}
|
| 396 |
+
],
|
| 397 |
+
"approximation": true,
|
| 398 |
+
"end_to_end_quality_validated": false
|
| 399 |
+
},
|
| 400 |
+
"comfyui_commit": "b1a247c9cf9dbfda4b2c0a0c1d50950667eeaa0c",
|
| 401 |
+
"comfy_model_tools_commit": "d6797787e6bdb1a1fb0094d588a26f8e71a1c757",
|
| 402 |
+
"gpu_inference_validated": false
|
| 403 |
+
}
|
provenance/QAD_SOURCE_VERIFICATION.json
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema": "omniref-qadv2-source-verification-v1",
|
| 3 |
+
"created_utc": "2026-10-20T16:32:41.097733+00:00",
|
| 4 |
+
"qad_master": {
|
| 5 |
+
"path": "/mnt/nfs/vlm-aryan/v2-qad/export/omniref-v2-work/transformer_bf16",
|
| 6 |
+
"config_sha256": "658c1f6278262db3072d9a6f4832c7c49280f91c94a8455414525cd6648de677",
|
| 7 |
+
"index_sha256": "40979168a4c9f720953f5af95552603a5273f4eee3e58d3615514a6267cc3d46",
|
| 8 |
+
"tensor_count": 688,
|
| 9 |
+
"shards": [
|
| 10 |
+
{
|
| 11 |
+
"name": "diffusion_pytorch_model-00001-of-00014.safetensors",
|
| 12 |
+
"bytes": 5296610024,
|
| 13 |
+
"sha256": "c4e6ee7a05a2030ae144bb1705c1cdd6045573623a1c3ab1be494b60c6690476"
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"name": "diffusion_pytorch_model-00002-of-00014.safetensors",
|
| 17 |
+
"bytes": 5318721280,
|
| 18 |
+
"sha256": "51b025ef7cf4f91b8bb26225f6f35161edb3c96e311d344ad963e7bbb7607e14"
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"name": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 22 |
+
"bytes": 5318721272,
|
| 23 |
+
"sha256": "3781d4bf62e9a06bb420b4516ffbdbc36f460d3a130893f6b8bd8eba8ce3d59a"
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"name": "diffusion_pytorch_model-00004-of-00014.safetensors",
|
| 27 |
+
"bytes": 4952636536,
|
| 28 |
+
"sha256": "811754513bff92a57b63bbe8ad5fe89830372a9f25b50e3016b421c14cdf08e8"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"name": "diffusion_pytorch_model-00005-of-00014.safetensors",
|
| 32 |
+
"bytes": 5318506024,
|
| 33 |
+
"sha256": "a7112bbb54de4b6ed9f6e6a05ee3e139a3e4796cb6fb9911d716c1a6791ffcaa"
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"name": "diffusion_pytorch_model-00006-of-00014.safetensors",
|
| 37 |
+
"bytes": 5164580720,
|
| 38 |
+
"sha256": "197c47754ac26930f5e074ffb25044c0cfa482821e9a05d809f0d5e0194336b5"
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"name": "diffusion_pytorch_model-00007-of-00014.safetensors",
|
| 42 |
+
"bytes": 5318721272,
|
| 43 |
+
"sha256": "790c78c978138cd551e8ee6fba8a9e24c9814d333d026ee8033792737f1ae975"
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"name": "diffusion_pytorch_model-00008-of-00014.safetensors",
|
| 47 |
+
"bytes": 5318721280,
|
| 48 |
+
"sha256": "16ae3aab7b106065c66df86a8cf5b514fe80884f0c9a3f9a25304cae1f72f828"
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"name": "diffusion_pytorch_model-00009-of-00014.safetensors",
|
| 52 |
+
"bytes": 4875566088,
|
| 53 |
+
"sha256": "c3c8b1dd4d4779ff46750680e347768d712d8478d217a297dc420729e2b41521"
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"name": "diffusion_pytorch_model-00010-of-00014.safetensors",
|
| 57 |
+
"bytes": 5318506016,
|
| 58 |
+
"sha256": "a20c102b9751092d2c1433ccc30915c6edbf336aee4942aaa2fda1b20f6614c4"
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"name": "diffusion_pytorch_model-00011-of-00014.safetensors",
|
| 62 |
+
"bytes": 5164580720,
|
| 63 |
+
"sha256": "2a8671bed7eb63674216e62072d9b0e02952180f534932a3ff287d6d9dabe7aa"
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"name": "diffusion_pytorch_model-00012-of-00014.safetensors",
|
| 67 |
+
"bytes": 5318721288,
|
| 68 |
+
"sha256": "17c5a514c579a0d50b78fa8570fd6686f15209d138acf3bbdfcf0b8f6f212edd"
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"name": "diffusion_pytorch_model-00013-of-00014.safetensors",
|
| 72 |
+
"bytes": 5318721240,
|
| 73 |
+
"sha256": "836c79d282a1a08343f72f80144b4cadae6e16eef0236f3e2a171acc460aa820"
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"name": "diffusion_pytorch_model-00014-of-00014.safetensors",
|
| 77 |
+
"bytes": 2138941328,
|
| 78 |
+
"sha256": "f1448e520232cb61df53beb2521b105d5b4e54226d0c9508ac5d9d1eff8575c8"
|
| 79 |
+
}
|
| 80 |
+
]
|
| 81 |
+
},
|
| 82 |
+
"base_reference": {
|
| 83 |
+
"path": "/mnt/nfs/vlm-aryan/h3-omniref-qad/model/transformer_ref",
|
| 84 |
+
"index_sha256": "40979168a4c9f720953f5af95552603a5273f4eee3e58d3615514a6267cc3d46",
|
| 85 |
+
"tensor_count": 688
|
| 86 |
+
},
|
| 87 |
+
"checks": {
|
| 88 |
+
"key_sets_equal": true,
|
| 89 |
+
"shape_or_dtype_mismatches": [],
|
| 90 |
+
"frozen_tensors_byte_identical": 388,
|
| 91 |
+
"trained_tensors_byte_identical": 0,
|
| 92 |
+
"trained_tensors_changed": 300,
|
| 93 |
+
"trained_pattern": "^transformer_blocks\\.\\d+\\.(?:attn\\.(?:to_q|to_k|to_v|to_out(?:\\.0)?)|ff\\.(?:net\\.0\\.proj|net\\.2))\\.(?:weight|bias)$",
|
| 94 |
+
"all_frozen_tensors_match_base": true,
|
| 95 |
+
"qad_training_visible_in_trained_tensors": true
|
| 96 |
+
},
|
| 97 |
+
"result": "passed"
|
| 98 |
+
}
|
provenance/QUANTIZATION.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
provenance/SCHEMA_VALIDATION.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema": "omniref-comfy-schema-validation-v1",
|
| 3 |
+
"created_utc": "2026-10-20T16:51:58.256998+00:00",
|
| 4 |
+
"format": "nvfp4",
|
| 5 |
+
"source": {
|
| 6 |
+
"path": "/mnt/nfs/vlm-aryan/omniref-comfy-quant/out/fasth3_omniref_qad_bf16_comfy.safetensors",
|
| 7 |
+
"sha256": "4e244969987e3c6e16bffa449bcf62c1f7c83b5cce15e5806246d3ae8055154f",
|
| 8 |
+
"tensor_count": 582
|
| 9 |
+
},
|
| 10 |
+
"quantized": {
|
| 11 |
+
"path": "/mnt/nfs/vlm-aryan/omniref-comfy-quant/out/fasth3_omniref_qad_nvfp4_comfy.safetensors",
|
| 12 |
+
"sha256": "bf25ee58df65b4dfee349d612f7ac62a2fea0dddd1e68eb37e75c8ce8b166f5a",
|
| 13 |
+
"tensor_count": 1332
|
| 14 |
+
},
|
| 15 |
+
"checks": {
|
| 16 |
+
"quantized_linear_count": 250,
|
| 17 |
+
"markers_present": true,
|
| 18 |
+
"dense_tensors_checked": 332,
|
| 19 |
+
"dense_tensors_changed": [],
|
| 20 |
+
"detected_config": {
|
| 21 |
+
"image_model": "minimax_h3",
|
| 22 |
+
"num_layers": 50,
|
| 23 |
+
"token_refiner_num_layers": 2,
|
| 24 |
+
"hidden_size": 5376,
|
| 25 |
+
"latents_dim": 24,
|
| 26 |
+
"audio_latents_dim": 32,
|
| 27 |
+
"attention_head_dim": 128,
|
| 28 |
+
"num_attention_heads": 56,
|
| 29 |
+
"ffn_hidden_size": 14336,
|
| 30 |
+
"text_dim": 5120,
|
| 31 |
+
"adaln_curve_grid": 1025,
|
| 32 |
+
"time_embed_dim": 8,
|
| 33 |
+
"rope_inv_freq_len": 16,
|
| 34 |
+
"gate_compress": true,
|
| 35 |
+
"pdd_steps": 32,
|
| 36 |
+
"sigma_shift_video": 12.0,
|
| 37 |
+
"sigma_shift_audio": 3.0,
|
| 38 |
+
"norm_eps": 1e-05,
|
| 39 |
+
"qk_norm_eps": 1e-05,
|
| 40 |
+
"final_norm_eps": 1e-05
|
| 41 |
+
},
|
| 42 |
+
"native_cpu_strict_load": {
|
| 43 |
+
"status": "passed",
|
| 44 |
+
"missing_keys": [],
|
| 45 |
+
"unexpected_keys": []
|
| 46 |
+
}
|
| 47 |
+
},
|
| 48 |
+
"errors": [],
|
| 49 |
+
"result": "passed"
|
| 50 |
+
}
|
provenance/SOURCE_HASH_VERIFICATION.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"files": [
|
| 3 |
+
{
|
| 4 |
+
"path": "diffusion_models/fasth3_omniref_8step_pruned_bf16.safetensors",
|
| 5 |
+
"bytes": 44240780704,
|
| 6 |
+
"sha256": "c0fac98c0317eb85215bd186b250be9b113675baab1323add40f26262f552ed9",
|
| 7 |
+
"manifest_match": true
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"path": "text_encoders/qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors",
|
| 11 |
+
"bytes": 15687142551,
|
| 12 |
+
"sha256": "35a88d51044231fe332301d7a62aa81e3f2cba62febeb446e2c1e3e0ef76f2c6",
|
| 13 |
+
"manifest_match": true
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"path": "vae/minimax_h3_video_vae_int8_convrot.safetensors",
|
| 17 |
+
"bytes": 2811065184,
|
| 18 |
+
"sha256": "52a2c8c73583c86e4f41cdcce3a6ad0ea562987bc0bf3d60a0cef5f5c8e60c0e",
|
| 19 |
+
"manifest_match": true
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"path": "vae/minimax_h3_audio_vae_fp32.safetensors",
|
| 23 |
+
"bytes": 605254808,
|
| 24 |
+
"sha256": "8e505d95dd1561d47abd43d4238fd40d9bb1ae9e147ed0a4cba778d76ae4db48",
|
| 25 |
+
"manifest_match": true
|
| 26 |
+
}
|
| 27 |
+
]
|
| 28 |
+
}
|
provenance/VALIDATION.json
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema": "omniref-comfy-validation-v1",
|
| 3 |
+
"all_passed": true,
|
| 4 |
+
"pairs": [
|
| 5 |
+
{
|
| 6 |
+
"reference": "/mnt/nfs/vlm-aryan/omniref-comfy-quant/results/qad_couple_bf16_00001_.mp4",
|
| 7 |
+
"variant": "/mnt/nfs/vlm-aryan/omniref-comfy-quant/results/qad_couple_nvfp4_00001_.mp4",
|
| 8 |
+
"reference_streams": {
|
| 9 |
+
"video": {
|
| 10 |
+
"codec_name": "h264",
|
| 11 |
+
"width": 832,
|
| 12 |
+
"height": 480,
|
| 13 |
+
"nb_frames": "124",
|
| 14 |
+
"duration": "5.166667",
|
| 15 |
+
"avg_frame_rate": "24/1"
|
| 16 |
+
},
|
| 17 |
+
"audio": {
|
| 18 |
+
"codec_name": "aac",
|
| 19 |
+
"sample_rate": "32000",
|
| 20 |
+
"channels": 2,
|
| 21 |
+
"duration": "5.166688"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"variant_streams": {
|
| 25 |
+
"video": {
|
| 26 |
+
"codec_name": "h264",
|
| 27 |
+
"width": 832,
|
| 28 |
+
"height": 480,
|
| 29 |
+
"nb_frames": "124",
|
| 30 |
+
"duration": "5.166667",
|
| 31 |
+
"avg_frame_rate": "24/1"
|
| 32 |
+
},
|
| 33 |
+
"audio": {
|
| 34 |
+
"codec_name": "aac",
|
| 35 |
+
"sample_rate": "32000",
|
| 36 |
+
"channels": 2,
|
| 37 |
+
"duration": "5.166688"
|
| 38 |
+
}
|
| 39 |
+
},
|
| 40 |
+
"video": {
|
| 41 |
+
"psnr_db": {
|
| 42 |
+
"average": 18.055532,
|
| 43 |
+
"min": 17.019769,
|
| 44 |
+
"max": 18.784264
|
| 45 |
+
},
|
| 46 |
+
"ssim": {
|
| 47 |
+
"all": 0.645996
|
| 48 |
+
}
|
| 49 |
+
},
|
| 50 |
+
"reference_signalstats": {
|
| 51 |
+
"frames": 124,
|
| 52 |
+
"yavg_mean": 66.52045887096773,
|
| 53 |
+
"yavg_min": 64.1544,
|
| 54 |
+
"yavg_max": 69.0764,
|
| 55 |
+
"yavg_std": 1.4365474254217374,
|
| 56 |
+
"ylow_min": 23.0,
|
| 57 |
+
"yhigh_max": 141.0,
|
| 58 |
+
"range": 4.922000000000011
|
| 59 |
+
},
|
| 60 |
+
"variant_signalstats": {
|
| 61 |
+
"frames": 124,
|
| 62 |
+
"yavg_mean": 64.7651870967742,
|
| 63 |
+
"yavg_min": 63.5108,
|
| 64 |
+
"yavg_max": 65.5562,
|
| 65 |
+
"yavg_std": 0.5085328947027883,
|
| 66 |
+
"ylow_min": 18.0,
|
| 67 |
+
"yhigh_max": 127.0,
|
| 68 |
+
"range": 2.0454000000000008
|
| 69 |
+
},
|
| 70 |
+
"variant_freeze": {
|
| 71 |
+
"freeze_starts": [],
|
| 72 |
+
"freeze_count": 0
|
| 73 |
+
},
|
| 74 |
+
"audio": {
|
| 75 |
+
"samples": 165888,
|
| 76 |
+
"duration_s": 5.184,
|
| 77 |
+
"reference_rms": 0.12436776958520834,
|
| 78 |
+
"variant_rms": 0.10050172034476386,
|
| 79 |
+
"reference_peak": 0.8157475888729095,
|
| 80 |
+
"variant_peak": 0.795351654291153,
|
| 81 |
+
"variant_active_fraction": 0.9188126929012346,
|
| 82 |
+
"variant_zero_crossing_rate": 0.086655373838818,
|
| 83 |
+
"psnr_db": -0.4829138836156005,
|
| 84 |
+
"correlation": 0.3312793710414481,
|
| 85 |
+
"reference_spectral_centroid_hz": 1732.758424304872,
|
| 86 |
+
"variant_spectral_centroid_hz": 1869.7456943786385
|
| 87 |
+
},
|
| 88 |
+
"checks": {
|
| 89 |
+
"variant_has_video": true,
|
| 90 |
+
"variant_has_audio": true,
|
| 91 |
+
"variant_audio_not_silent": true,
|
| 92 |
+
"variant_audio_active": true,
|
| 93 |
+
"variant_audio_duration_matches_video": true,
|
| 94 |
+
"variant_video_has_frames": true,
|
| 95 |
+
"variant_video_has_contrast": true,
|
| 96 |
+
"variant_video_not_black": true,
|
| 97 |
+
"variant_signalstats_finite": true,
|
| 98 |
+
"variant_no_long_freeze": true
|
| 99 |
+
},
|
| 100 |
+
"passed": true,
|
| 101 |
+
"label": "qad_couple"
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"reference": "/mnt/nfs/vlm-aryan/omniref-comfy-quant/results/qad_cyclist_bf16_00001_.mp4",
|
| 105 |
+
"variant": "/mnt/nfs/vlm-aryan/omniref-comfy-quant/results/qad_cyclist_nvfp4_00001_.mp4",
|
| 106 |
+
"reference_streams": {
|
| 107 |
+
"video": {
|
| 108 |
+
"codec_name": "h264",
|
| 109 |
+
"width": 832,
|
| 110 |
+
"height": 480,
|
| 111 |
+
"nb_frames": "124",
|
| 112 |
+
"duration": "5.166667",
|
| 113 |
+
"avg_frame_rate": "24/1"
|
| 114 |
+
},
|
| 115 |
+
"audio": {
|
| 116 |
+
"codec_name": "aac",
|
| 117 |
+
"sample_rate": "32000",
|
| 118 |
+
"channels": 2,
|
| 119 |
+
"duration": "5.166688"
|
| 120 |
+
}
|
| 121 |
+
},
|
| 122 |
+
"variant_streams": {
|
| 123 |
+
"video": {
|
| 124 |
+
"codec_name": "h264",
|
| 125 |
+
"width": 832,
|
| 126 |
+
"height": 480,
|
| 127 |
+
"nb_frames": "124",
|
| 128 |
+
"duration": "5.166667",
|
| 129 |
+
"avg_frame_rate": "24/1"
|
| 130 |
+
},
|
| 131 |
+
"audio": {
|
| 132 |
+
"codec_name": "aac",
|
| 133 |
+
"sample_rate": "32000",
|
| 134 |
+
"channels": 2,
|
| 135 |
+
"duration": "5.166688"
|
| 136 |
+
}
|
| 137 |
+
},
|
| 138 |
+
"video": {
|
| 139 |
+
"psnr_db": {
|
| 140 |
+
"average": 17.065885,
|
| 141 |
+
"min": 15.103695,
|
| 142 |
+
"max": 19.025832
|
| 143 |
+
},
|
| 144 |
+
"ssim": {
|
| 145 |
+
"all": 0.620487
|
| 146 |
+
}
|
| 147 |
+
},
|
| 148 |
+
"reference_signalstats": {
|
| 149 |
+
"frames": 124,
|
| 150 |
+
"yavg_mean": 88.1789677419355,
|
| 151 |
+
"yavg_min": 57.7666,
|
| 152 |
+
"yavg_max": 107.079,
|
| 153 |
+
"yavg_std": 16.303842333631884,
|
| 154 |
+
"ylow_min": 23.0,
|
| 155 |
+
"yhigh_max": 219.0,
|
| 156 |
+
"range": 49.3124
|
| 157 |
+
},
|
| 158 |
+
"variant_signalstats": {
|
| 159 |
+
"frames": 124,
|
| 160 |
+
"yavg_mean": 86.48673467741936,
|
| 161 |
+
"yavg_min": 65.4808,
|
| 162 |
+
"yavg_max": 113.183,
|
| 163 |
+
"yavg_std": 12.674295997449915,
|
| 164 |
+
"ylow_min": 25.0,
|
| 165 |
+
"yhigh_max": 222.0,
|
| 166 |
+
"range": 47.702200000000005
|
| 167 |
+
},
|
| 168 |
+
"variant_freeze": {
|
| 169 |
+
"freeze_starts": [],
|
| 170 |
+
"freeze_count": 0
|
| 171 |
+
},
|
| 172 |
+
"audio": {
|
| 173 |
+
"samples": 165888,
|
| 174 |
+
"duration_s": 5.184,
|
| 175 |
+
"reference_rms": 0.014723019933069991,
|
| 176 |
+
"variant_rms": 0.010349389418981452,
|
| 177 |
+
"reference_peak": 0.08551505208015442,
|
| 178 |
+
"variant_peak": 0.09483717381954193,
|
| 179 |
+
"variant_active_fraction": 0.9135561342592593,
|
| 180 |
+
"variant_zero_crossing_rate": 0.4987792895163575,
|
| 181 |
+
"psnr_db": 0.6167579897670976,
|
| 182 |
+
"correlation": 0.4456390174380726,
|
| 183 |
+
"reference_spectral_centroid_hz": 9519.315481379153,
|
| 184 |
+
"variant_spectral_centroid_hz": 7645.46219766074
|
| 185 |
+
},
|
| 186 |
+
"checks": {
|
| 187 |
+
"variant_has_video": true,
|
| 188 |
+
"variant_has_audio": true,
|
| 189 |
+
"variant_audio_not_silent": true,
|
| 190 |
+
"variant_audio_active": true,
|
| 191 |
+
"variant_audio_duration_matches_video": true,
|
| 192 |
+
"variant_video_has_frames": true,
|
| 193 |
+
"variant_video_has_contrast": true,
|
| 194 |
+
"variant_video_not_black": true,
|
| 195 |
+
"variant_signalstats_finite": true,
|
| 196 |
+
"variant_no_long_freeze": true
|
| 197 |
+
},
|
| 198 |
+
"passed": true,
|
| 199 |
+
"label": "qad_cyclist"
|
| 200 |
+
}
|
| 201 |
+
]
|
| 202 |
+
}
|
provenance/VALIDATION.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# FastH3 OmniRef NVFP4 QAD for ComfyUI validation
|
| 2 |
+
|
| 3 |
+
Native ComfyUI end-to-end validation at 832x480, 124 frames (~5.2 s at 24 fps),
|
| 4 |
+
8 sampling steps, Euler sampler, simple schedule, guidance 1.0, video/audio shifts 12/3.
|
| 5 |
+
|
| 6 |
+
Video comparison uses ffmpeg PSNR/SSIM and signal statistics; audio comparison decodes
|
| 7 |
+
to raw float samples and reports numeric duration, RMS, activity, correlation, PSNR, and
|
| 8 |
+
spectral-centroid metrics. No video frame is read as an image.
|
| 9 |
+
|
| 10 |
+
| Pair | PSNR avg (dB) | PSNR min (dB) | SSIM | Audio PSNR (dB) | Audio corr | Variant RMS | Active frac | Frozen spans | Result |
|
| 11 |
+
|---|---:|---:|---:|---:|---:|---:|---:|---:|---|
|
| 12 |
+
| qad_couple | 18.06 | 17.02 | 0.6460 | -0.48 | 0.33128 | 0.10050 | 0.9188 | 0 | pass |
|
| 13 |
+
| qad_cyclist | 17.07 | 15.10 | 0.6205 | 0.62 | 0.44564 | 0.01035 | 0.9136 | 0 | pass |
|
| 14 |
+
|
| 15 |
+
Per-pair checks:
|
| 16 |
+
|
| 17 |
+
- `qad_couple`: variant_has_video=True, variant_has_audio=True, variant_audio_not_silent=True, variant_audio_active=True, variant_audio_duration_matches_video=True, variant_video_has_frames=True, variant_video_has_contrast=True, variant_video_not_black=True, variant_signalstats_finite=True, variant_no_long_freeze=True
|
| 18 |
+
- `qad_cyclist`: variant_has_video=True, variant_has_audio=True, variant_audio_not_silent=True, variant_audio_active=True, variant_audio_duration_matches_video=True, variant_video_has_frames=True, variant_video_has_contrast=True, variant_video_not_black=True, variant_signalstats_finite=True, variant_no_long_freeze=True
|
| 19 |
+
|
| 20 |
+
Environment:
|
| 21 |
+
|
| 22 |
+
- ComfyUI: b1a247c9cf9dbfda4b2c0a0c1d50950667eeaa0c
|
| 23 |
+
- comfy-kitchen: 0.2.39
|
| 24 |
+
- PyTorch: 2.9.0a0+50eac811a6.nv25.09
|
| 25 |
+
- CUDA: 13.0
|
| 26 |
+
- GPU: NVIDIA GB200
|
| 27 |
+
|
| 28 |
+
The comparison reference is the BF16 Comfy conversion of the same QAD master, so the metrics isolate the NVFP4 export rather than the QAD training change.
|
| 29 |
+
|