aryan5v commited on
Commit
34eb3b8
·
verified ·
1 Parent(s): 0d51428

Add native ComfyUI conversion, provenance, and validation

Browse files
.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
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  *.safetensors filter=lfs diff=lfs merge=lfs -text
2
+ *.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
+