aptech0081 commited on
Commit
7fbaaae
·
verified ·
1 Parent(s): acd4775

Add native Ref2VA PDD bake WITH HEADS; label previous bake WITHOUT HEADS

Browse files

Publish the 32-head video/audio bank in the ComfyUI #17157 checkpoint format. Preserve all 1031 trunk tensors and rename the existing trunk-only bake without changing its bytes. Include native usage instructions, converter and numerical validation.

README.md CHANGED
@@ -1,107 +1,141 @@
1
  ---
2
- license: apache-2.0
 
 
3
  base_model:
4
  - alibaba-pai/MiniMax-H3-Acc-LoRAs
5
  - MiniMaxAI/MiniMax-H3
6
  pipeline_tag: text-to-video
7
  tags:
8
  - comfyui
9
- - lora
10
  - minimax-h3
11
  - video
12
  - audio
13
  - distillation
 
14
  ---
15
 
16
- # MiniMax-H3 Acc LoRAs — ComfyUI conversion
17
 
18
- ComfyUI-key repackaging of the **official
19
- [alibaba-pai/MiniMax-H3-Acc-LoRAs](https://huggingface.co/alibaba-pai/MiniMax-H3-Acc-LoRAs)**
20
- 8-step PDD acceleration LoRAs for [MiniMax-H3](https://huggingface.co/MiniMaxAI/MiniMax-H3) —
21
- full audio+video generation in **8 (or 4) sampler steps, CFG-free**.
22
 
23
- **These are not plain LoRAs.** Each file carries a rank-64 trunk LoRA **plus** a Parallel
24
- Decoding Distillation head bank (32 per-interval final-layer projections per modality,
25
- [PDD — arXiv:2607.26004](https://arxiv.org/abs/2607.26004)). Loading them requires the
26
- companion custom node pack:
27
-
28
- ➡️ **[Jalen-Brunson/ComfyUI-MiniMax-H3-PDD-Acc](https://github.com/Jalen-Brunson/ComfyUI-MiniMax-H3-PDD-Acc)**
29
- (also loads the original alibaba-pai files directly — this repo just saves you the in-memory
30
- conversion and gives you inspectable standard LoRA keys).
31
 
32
  ## Files
33
 
34
- | File | What it is | sha256 |
35
  |---|---|---|
36
- | `minimax_h3_fl2va_pdd_acc_8step_comfyui.safetensors` | LoRA + head bank for **FL2VA** trunk | `1dce71b9…5cda0ea` |
37
- | `minimax_h3_ref2va_pdd_acc_8step_comfyui.safetensors` | LoRA + head bank for **Ref2VA** trunk | `5531fa0d…bdc78a1` |
38
- | `minimax_h3_ref2va_pdd_acc_8step_baked_int8_convrot.safetensors` | full **Ref2VA** int8-convrot checkpoint, trunk LoRA **pre-merged** — for cards that can't fully load the model ([see below](#baked-ref2va-trunk-for-cards-that-cant-fully-load-the-model)) | `fe8e58d8…cd1e111e` |
39
-
40
- Put the LoRA files in `ComfyUI/models/pdd_acc/`. Pair FL2VA with an fl2va UNET, Ref2VA with
41
- ref2va (bf16 or int8-convrot builds both work). The baked checkpoint goes in
42
- `ComfyUI/models/diffusion_models/` instead.
43
-
44
- ## Usage (recipe is mandatory)
45
-
46
- `UNETLoader → MiniMaxH3SigmaShift (12/3) → MiniMax H3 PDD Acc LoRA (Apply) → BasicGuider (CFG 1.0)`,
47
- sampler **euler**, sigmas = **the Apply node's sigmas output** (the trained PDD block
48
- boundaries) into `SamplerCustomAdvanced`. Strengths 1.0, `nfe` 8 (4 is also official). Remove
49
- other distill LoRAs (turbo); don't stack step-caching nodes. A ready-to-run workflow ships in
50
- the node pack's `example_workflows/`.
51
-
52
- ## Baked Ref2VA trunk (for cards that can't fully load the model)
53
-
54
- `minimax_h3_ref2va_pdd_acc_8step_baked_int8_convrot.safetensors` (34 GB) is
55
- [Comfy-Org/MiniMax-H3](https://huggingface.co/Comfy-Org/MiniMax-H3)'s
56
- `minimax_h3_ref2va_int8_convrot` checkpoint with this repo's Ref2VA trunk LoRA
57
- **pre-merged at strength 1.0** (dequantize → add → requantize with the same `comfy-kitchen`
58
- int8-convrot kernels ComfyUI itself uses; every tensor keeps its exact dtype, shape and byte
59
- length). The PDD head bank is **not** in this file — it stays runtime, so you still need the
60
- node pack and one of the LoRA files above.
61
-
62
- **Why it exists:** ComfyUI merges LoRA patches into weights only for modules that fit in
63
- VRAM; offloaded modules get a per-forward `lowvram patch` — the LoRA (plus a dequantize) is
64
- re-applied on **every step**. On cards at the VRAM edge that fixed cost is large: ~2× s/it
65
- at 864×480 on a 32 GB RTX 5090
66
- ([node pack issue #4](https://github.com/Jalen-Brunson/ComfyUI-MiniMax-H3-PDD-Acc/issues/4)).
67
- Baking removes the patch term entirely — measured on a fully-offloaded H200: 2.44 → 2.06 s/it
68
- (`lowvram patches: 258 → 0`); the win grows as the card gets smaller. **If your card fully
69
- loads the trunk, this file buys you nothing** — use a LoRA file above on a stock trunk.
70
-
71
- **Usage:** put it in `ComfyUI/models/diffusion_models/`, load it with a plain **UNETLoader**,
72
- and run the exact recipe above with the Apply node's **`lora_strength` set to `0.0`**
73
- (baked-trunk mode: trunk patching skipped; head bank / sigmas / guards unchanged — the node's
74
- `info` output confirms it). Point the Apply node at the **Ref2VA** LoRA file above (it still
75
- supplies the head bank and the trained sigmas). Everything else is identical: SigmaShift
76
- 12/3, the Apply node's sigmas → euler, CFG 1.0, `nfe` 8 (4 also official).
77
-
78
- **Caveats:** the merge strength is frozen into the file — for another strength, the FL2VA
79
- trunk, or a bf16/pruned base, `bake_pdd_trunk.py` in the node pack bakes any base yourself
80
- (streaming write, a few GB of RAM). On an **unbaked** trunk, `lora_strength 0.0` silently
81
- renders the un-distilled model with PDD heads — if unsure, check the file's safetensors
82
- metadata for `pdd_acc_baked: true` (full bake provenance — source shas, strength, date — is
83
- embedded there).
84
-
85
- ## What was converted
86
-
87
- Trunk LoRA renamed from diffusers to ComfyUI H3 keys (`diffusion_model.*.lora_A/B.weight`
88
- + `.alpha`, 258 modules):
89
-
90
- - `to_q/to_k/to_v` → `attn.qkv_proj`: concatenated `lora_A`, **block-diagonal** `lora_B`,
91
- alpha ×3 (keeps the per-branch scale exactly 1.0)
92
- - `ff.net.0.proj` → `mlp.fc1`: SwiGLU `[value;gate]` → `[gate;value]` `lora_B` row half-swap
93
- - `to_out.0` → `attn.out_proj`, `ff.net.2` → `mlp.fc2`, `adaln_proj.linear` copied 1:1
94
- (modulation layouts verified bit-identical between the two implementations)
95
- - `token_refiner.refiner_blocks.N` → `token_refiner.blocks.N`
96
-
97
- The PDD head bank (`proj_out` [32,96,5376], `audio_proj_out` [32,32,5376] + biases) is kept
98
- byte-for-byte unchanged. Conversion is verified **bit-identical** to what the node pack
99
- computes in memory from the original files, and the converter CLI + 13-test suite live in the
100
- GitHub repo. Full provenance (source file sha256, transform description) is embedded in each
101
- file's safetensors metadata.
102
-
103
- ## Credits
104
-
105
- All training credit to [alibaba-pai](https://huggingface.co/alibaba-pai) (Apache-2.0 release)
106
- and the PDD authors (Shaul et al.); base model by
107
- [MiniMaxAI](https://huggingface.co/MiniMaxAI). This repo is a format conversion only.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
  base_model:
6
  - alibaba-pai/MiniMax-H3-Acc-LoRAs
7
  - MiniMaxAI/MiniMax-H3
8
  pipeline_tag: text-to-video
9
  tags:
10
  - comfyui
 
11
  - minimax-h3
12
  - video
13
  - audio
14
  - distillation
15
+ - pdd
16
  ---
17
 
18
+ # MiniMax H3 PDD — ComfyUI LoRAs and baked Ref2VA checkpoints
19
 
20
+ ComfyUI conversions of the official
21
+ [Alibaba PAI MiniMax-H3 PDD acceleration weights](https://huggingface.co/alibaba-pai/MiniMax-H3-Acc-LoRAs).
22
+ The Ref2VA checkpoints generate video and audio from reference conditioning.
 
23
 
24
+ **There are now two baked Ref2VA checkpoints: WITH HEADS and WITHOUT HEADS.**
25
+ The new **with-heads** file contains the merged trunk and both PDD output-head banks.
26
+ It loads through native ComfyUI after
27
+ [PR #17157](https://github.com/Comfy-Org/ComfyUI/pull/17157), merged October 18, 2026,
28
+ without a separate PDD acceleration LoRA or PDD Apply node.
 
 
 
29
 
30
  ## Files
31
 
32
+ | File | Contents | Runtime |
33
  |---|---|---|
34
+ | [`minimax_h3_ref2va_pdd_acc_8step_baked_with_heads_int8_convrot.safetensors`](minimax_h3_ref2va_pdd_acc_8step_baked_with_heads_int8_convrot.safetensors) | **WITH HEADS:** Ref2VA INT8 ConvRot trunk with PDD merged at strength 1.0, plus 32 video and 32 audio heads | Native ComfyUI with PR #17157 |
35
+ | [`minimax_h3_ref2va_pdd_acc_8step_baked_without_heads_int8_convrot.safetensors`](minimax_h3_ref2va_pdd_acc_8step_baked_without_heads_int8_convrot.safetensors) | **WITHOUT HEADS:** PDD trunk merged at strength 1.0; original single output heads | External Ref2VA PDD head bank and Apply node still required |
36
+ | `minimax_h3_fl2va_pdd_acc_8step_comfyui.safetensors` | FL2VA trunk LoRA and external head bank | Companion PDD custom nodes |
37
+ | `minimax_h3_ref2va_pdd_acc_8step_comfyui.safetensors` | Ref2VA trunk LoRA and external head bank | Companion PDD custom nodes |
38
+
39
+ Put either full checkpoint in `ComfyUI/models/diffusion_models/`.
40
+ Put the two custom-node LoRA files in `ComfyUI/models/pdd_acc/`.
41
+
42
+ The **without-heads** checkpoint is the former `minimax_h3_ref2va_pdd_acc_8step_baked_int8_convrot.safetensors`.
43
+ It was renamed for clarity; its bytes and SHA256 are unchanged. Existing download links
44
+ using the old filename need to be updated.
45
+
46
+ ## WITH HEADS — native ComfyUI
47
+
48
+ 1. Update ComfyUI to a revision containing PR #17157.
49
+ 2. Load `minimax_h3_ref2va_pdd_acc_8step_baked_with_heads_int8_convrot.safetensors` with **Load Diffusion Model / UNETLoader**.
50
+ 3. Use the usual MiniMax H3 Ref2VA conditioning, text encoder, video VAE and audio VAE.
51
+ 4. Use **Euler**, the **simple** scheduler, **8 steps**, **CFG 1.0**, and **denoise 1.0**.
52
+ The video/audio sigma shifts are **12.0 / 3.0**, the native model defaults.
53
+ 5. Do not apply the PDD acceleration LoRA or PDD Apply node again: its trunk and heads
54
+ are already included. Keep other acceleration LoRAs disabled for this recipe.
55
+
56
+ Four steps are also supported by the original PDD release; start with eight.
57
+ The native simple scheduler at four and eight steps was checked against the PDD block
58
+ boundaries. The checkpoint stores **32 fine-interval heads per stream**:
59
+ `config.transformer.pdd_steps = 32` is the bank size, not the requested sampler-step count.
60
+
61
+ This is a diffusion-model checkpoint. The text encoder and VAEs remain separate.
62
+ The existing INT8 ConvRot trunk is preserved byte-for-byte; only the four output-head tensors
63
+ and checkpoint metadata change. The head tensors are stored in float32:
64
+
65
+ | Tensor | Shape |
66
+ |---|---|
67
+ | `final_layer.video_out.weight` | `[3072, 5376]` |
68
+ | `final_layer.video_out.bias` | `[3072]` |
69
+ | `final_layer.audio_out.weight` | `[1024, 5376]` |
70
+ | `final_layer.audio_out.bias` | `[1024]` |
71
+
72
+ Each bank stores head 0 as a full head and subsequent entries as offsets from head 0.
73
+ Native ComfyUI combines the appropriate heads for each sampling interval.
74
+
75
+ ## WITHOUT HEADS — companion PDD nodes
76
+
77
+ Load `minimax_h3_ref2va_pdd_acc_8step_baked_without_heads_int8_convrot.safetensors` with UNETLoader and use
78
+ [ComfyUI-MiniMax-H3-PDD-Acc](https://github.com/Jalen-Brunson/ComfyUI-MiniMax-H3-PDD-Acc)
79
+ with the Ref2VA PDD file:
80
+
81
+ - `lora_strength = 0.0` — the trunk is already baked.
82
+ - `head_strength = 1.0`, `nfe = 8` (four also supported).
83
+ - Euler, CFG 1.0, video/audio sigma shifts 12/3.
84
+ - Connect the Apply node's sigma output to `SamplerCustomAdvanced`.
85
+
86
+ **Without heads** means without the *PDD bank*: this file still has the normal single
87
+ video and audio output heads. It is not a standalone PDD checkpoint.
88
+
89
+ ## Standalone LoRA conversions
90
+
91
+ The two `..._comfyui.safetensors` LoRA files retain their original custom-node format.
92
+ They contain a rank-64 trunk LoRA plus the unmodified original PDD head bank.
93
+ Use the companion node pack with the matching stock FL2VA or Ref2VA checkpoint,
94
+ trunk/head strengths 1.0, Euler, CFG 1.0 and the Apply node's sigma schedule.
95
+
96
+ Trunk conversion maps Diffusers Q/K/V projections to ComfyUI's fused QKV layout,
97
+ swaps the SwiGLU projection halves, renames attention/MLP/refiner keys and preserves
98
+ LoRA scaling with explicit alpha tensors. The original standalone head bank remains
99
+ in its absolute-head format for the companion node pack.
100
+
101
+ ## Conversion validation
102
+
103
+ The with-heads checkpoint was derived from the existing verified trunk bake and the
104
+ same official `MiniMax-H3-Ref2VA-Acc-8Step.safetensors` source.
105
+
106
+ - All **1031 trunk tensors** are byte-identical to the without-heads checkpoint.
107
+ - Every output tensor was hashed and verified after writing.
108
+ - Native ComfyUI detects 24 video latent channels, 32 audio latent channels and 32 PDD heads.
109
+ - The native model constructor accepts the new head shapes.
110
+ - The complete checkpoint loads through native `comfy.sd.load_diffusion_model` on CPU.
111
+ - Native head evaluation was compared to the original bank averaged independently in float64,
112
+ across all 32 individual intervals, eight-step and four-step partitions, and additional spans.
113
+ Maximum relative L2 error across video/audio checks: **3.565e-07**.
114
+ - The float32 head conversion introduces only rounding error; the INT8 trunk is not re-quantized.
115
+
116
+ These checks validate checkpoint structure and head math. They are not a full rendered-video
117
+ quality benchmark. See [the validation report](with-heads-validation.json) and
118
+ [the conversion script](bake_pdd_with_heads.py).
119
+
120
+ ## SHA256
121
+
122
+ | File | SHA256 |
123
+ |---|---|
124
+ | WITH HEADS | `d1f555a323a71a3ef33221527c240c9d63534f6fa5ff507b042a48f0423b8484` |
125
+ | WITHOUT HEADS | `fe8e58d8028e74607dfb41aa6a9ce92f5e0f989fff3e1eba6041caedcd1e111e` |
126
+ | FL2VA custom-node LoRA | `1dce71b90e5d993d3309474d47e414766e65dbd760f18978c399c82db5cda0ea` |
127
+ | Ref2VA custom-node LoRA | `5531fa0da887c24ec0083b0050ea9e4ff03c479cfbe21ef586d1c5c79bdc78a1` |
128
+
129
+ WITH HEADS size: **34,124,239,286 bytes**.
130
+
131
+ ## Credits and license
132
+
133
+ Base model: [MiniMaxAI/MiniMax-H3](https://huggingface.co/MiniMaxAI/MiniMax-H3).
134
+ PDD training: [Alibaba PAI](https://huggingface.co/alibaba-pai/MiniMax-H3-Acc-LoRAs)
135
+ and the [PDD authors](https://arxiv.org/abs/2607.26004).
136
+ Native baked-head support: [ComfyUI PR #17157 by kijai](https://github.com/Comfy-Org/ComfyUI/pull/17157).
137
+ This repository provides format conversions, not newly trained weights.
138
+
139
+ The model card now reflects the upstream MiniMax H3 community license;
140
+ see the [base-model license](https://huggingface.co/MiniMaxAI/MiniMax-H3/blob/main/LICENSE)
141
+ and [PDD source repository](https://huggingface.co/alibaba-pai/MiniMax-H3-Acc-LoRAs).
bake_pdd_with_heads.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Add the official PDD head banks to an already baked MiniMax H3 trunk."""
3
+ import argparse
4
+ import hashlib
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import struct
9
+ import sys
10
+
11
+ import torch
12
+ from safetensors import safe_open
13
+
14
+
15
+ CHUNK = 16 * 1024 * 1024
16
+ HEADS = {
17
+ "final_layer.video_out.weight": "proj_out.weight",
18
+ "final_layer.video_out.bias": "proj_out.bias",
19
+ "final_layer.audio_out.weight": "audio_proj_out.weight",
20
+ "final_layer.audio_out.bias": "audio_proj_out.bias",
21
+ }
22
+
23
+
24
+ def header(path):
25
+ with open(path, "rb") as f:
26
+ prefix = f.read(8)
27
+ data = f.read(struct.unpack("<Q", prefix)[0])
28
+ return json.loads(data), prefix + data
29
+
30
+
31
+ def sha256(path):
32
+ digest = hashlib.sha256()
33
+ with open(path, "rb") as f:
34
+ while chunk := f.read(CHUNK):
35
+ digest.update(chunk)
36
+ return digest.hexdigest()
37
+
38
+
39
+ def validate_native(path, source, comfy):
40
+ sys.path.insert(0, str(comfy))
41
+ from comfy.cli_args import args as comfy_args
42
+ comfy_args.cpu = True
43
+ import comfy.model_detection
44
+ import comfy.ops
45
+ from comfy.ldm.minimax.model import MiniMaxH3Model, FinalLayer, _pdd_head
46
+ from comfy.model_sampling import ModelSamplingAV
47
+ from comfy.samplers import simple_scheduler
48
+
49
+ h, _ = header(path)
50
+ # Detection only consumes shapes; no second full model allocation is needed.
51
+ shapes = {k: torch.empty(v["shape"], device="meta")
52
+ for k, v in h.items() if k != "__metadata__"}
53
+ config = comfy.model_detection.detect_unet_config(shapes, "", h["__metadata__"])
54
+ assert config["pdd_steps"] == 32
55
+ assert config["latents_dim"] == 24 and config["audio_latents_dim"] == 32
56
+ model = MiniMaxH3Model(**config, device="meta", dtype=torch.bfloat16,
57
+ operations=comfy.ops.disable_weight_init)
58
+ for key in HEADS:
59
+ assert list(model.state_dict()[key].shape) == h[key]["shape"], key
60
+ del model, shapes
61
+
62
+ # Compare real native head evaluation with the original absolute head bank,
63
+ # independently averaged in float64 for every fine interval and 8/4 steps.
64
+ hidden = torch.randn(5, 5376, generator=torch.Generator().manual_seed(17157))
65
+ cases = [(i, i + 1) for i in range(32)]
66
+ cases += [(i, i + size) for size in (4, 8) for i in range(0, 32, size)]
67
+ cases += [(3, 11), (7, 32)]
68
+ results = []
69
+ with safe_open(str(path), framework="pt") as baked, safe_open(str(source), framework="pt") as original:
70
+ final = FinalLayer(5376, 1, 96, 32, 1e-5, pdd_steps=32,
71
+ operations=comfy.ops.disable_weight_init)
72
+ for stream, prefix, shift in (("video", "proj_out", 12.0),
73
+ ("audio", "audio_proj_out", 3.0)):
74
+ head = getattr(final, stream + "_out")
75
+ head.weight = torch.nn.Parameter(baked.get_tensor(f"final_layer.{stream}_out.weight"))
76
+ head.bias = torch.nn.Parameter(baked.get_tensor(f"final_layer.{stream}_out.bias"))
77
+ w = original.get_tensor(prefix + ".weight").double()
78
+ b = original.get_tensor(prefix + ".bias").double()
79
+ grid = torch.linspace(1, 0, 33, dtype=torch.float64)
80
+ sigmas = shift * grid / (1 + (shift - 1) * grid)
81
+ delta = sigmas[:-1] - sigmas[1:]
82
+ max_rel, max_abs = 0.0, 0.0
83
+ for start, stop in cases:
84
+ weights = delta[start:stop] / delta[start:stop].sum()
85
+ reference_w = (weights[:, None, None] * w[start:stop]).sum(0)
86
+ reference_b = (weights[:, None] * b[start:stop]).sum(0)
87
+ expected = torch.nn.functional.linear(hidden.double(), reference_w, reference_b)
88
+ actual = _pdd_head(head, hidden, 32, start, stop, shift)
89
+ error = actual.double() - expected
90
+ rel = float(error.norm() / expected.norm())
91
+ max_rel = max(max_rel, rel)
92
+ max_abs = max(max_abs, float(error.abs().max()))
93
+ assert rel < 2e-6, (stream, start, stop, rel)
94
+ results.append({"stream": stream, "cases": len(cases),
95
+ "max_relative_l2_error": max_rel, "max_absolute_error": max_abs})
96
+
97
+ sampling = ModelSamplingAV()
98
+ sampling.set_parameters(shift=12.0, audio_shift=3.0)
99
+ schedules = {}
100
+ for steps in (8, 4):
101
+ actual = simple_scheduler(sampling, steps)
102
+ grid = torch.linspace(1, 0, steps + 1)
103
+ expected = 12 * grid / (1 + 11 * grid)
104
+ torch.testing.assert_close(actual, expected, atol=2e-7, rtol=2e-7)
105
+ schedules[str(steps)] = actual.tolist()
106
+ return {"native_detection": {"pdd_steps": 32, "latents_dim": 24, "audio_latents_dim": 32},
107
+ "native_model_head_shapes": "passed", "native_head_parity": results,
108
+ "simple_scheduler_matches_pdd_boundaries": schedules}
109
+
110
+
111
+ def main():
112
+ parser = argparse.ArgumentParser()
113
+ parser.add_argument("--base", type=Path, required=True)
114
+ parser.add_argument("--pdd", type=Path, required=True)
115
+ parser.add_argument("--out", type=Path, required=True)
116
+ parser.add_argument("--comfy", type=Path, required=True)
117
+ parser.add_argument("--report", type=Path, required=True)
118
+ parser.add_argument("--expected-base-sha256", required=True)
119
+ args = parser.parse_args()
120
+ torch.set_num_threads(4)
121
+ if args.out.exists() or args.out.with_suffix(".partial").exists():
122
+ raise FileExistsError("Output or partial output already exists; inspect before retrying")
123
+ h, old_prefix = header(args.base)
124
+ metadata = dict(h.pop("__metadata__"))
125
+ assert metadata["pdd_acc_baked"] == "true"
126
+ assert float(metadata["pdd_acc_bake_strength"]) == 1.0
127
+ source_hash = sha256(args.pdd)
128
+ assert source_hash == metadata["pdd_acc_bake_source_sha256"], "PDD source differs from trunk bake"
129
+ replacement = {}
130
+ reconstruction_errors = {}
131
+ with safe_open(str(args.pdd), framework="pt") as source:
132
+ assert source.metadata()["pdd_num_steps"] == "32"
133
+ for target, key in HEADS.items():
134
+ absolute = source.get_tensor(key).float()
135
+ assert list(absolute.shape[1:]) == h[target]["shape"]
136
+ bank = absolute.clone()
137
+ bank[1:] -= absolute[0]
138
+ flat = bank.flatten(0, 1).contiguous()
139
+ # The first bank entry is a complete head; later entries are offsets.
140
+ reconstructed = bank.clone()
141
+ reconstructed[1:] += bank[0]
142
+ torch.testing.assert_close(reconstructed, absolute, rtol=1e-6, atol=1e-8)
143
+ reconstruction_errors[target] = float((reconstructed - absolute).abs().max())
144
+ replacement[target] = flat
145
+ config = json.loads(metadata["config"])
146
+ config["transformer"]["pdd_steps"] = 32
147
+ metadata["config"] = json.dumps(config)
148
+ metadata.update({
149
+ "pdd_acc_heads_baked": "true",
150
+ "pdd_acc_head_layout": "head0_absolute_then_offsets_from_head0",
151
+ "pdd_acc_head_count": "32",
152
+ "pdd_acc_heads_source_sha256": source_hash,
153
+ "pdd_acc_trunk_baked_sha256": args.expected_base_sha256,
154
+ "pdd_acc_heads_bake_date": "2026-10-20",
155
+ "pdd_acc_bake_note": (
156
+ "Trunk and video/audio PDD heads baked at strength 1.0. "
157
+ "Native ComfyUI PR #17157 format; no PDD Apply node or acceleration LoRA required. "
158
+ "Euler/simple, 8 or 4 steps, CFG 1, video/audio shifts 12/3. "
159
+ "All trunk tensor bytes preserved from the without-heads bake."
160
+ ),
161
+ })
162
+ ordered = sorted(h, key=lambda k: h[k]["data_offsets"][0])
163
+ new_header = {"__metadata__": metadata}
164
+ offset = 0
165
+ for key in ordered:
166
+ entry = dict(h[key])
167
+ if key in replacement:
168
+ entry["shape"] = list(replacement[key].shape)
169
+ entry["dtype"] = "F32"
170
+ length = replacement[key].numel() * 4
171
+ else:
172
+ length = entry["data_offsets"][1] - entry["data_offsets"][0]
173
+ entry["data_offsets"] = [offset, offset + length]
174
+ new_header[key] = entry
175
+ offset += length
176
+ encoded = json.dumps(new_header, separators=(",", ":")).encode()
177
+ encoded += b" " * (-len(encoded) % 8)
178
+ prefix = struct.pack("<Q", len(encoded)) + encoded
179
+ partial = args.out.with_suffix(".partial")
180
+ base_digest = hashlib.sha256(old_prefix)
181
+ expected_tensors = {}
182
+ print("Writing checkpoint; source heads validated and repacked in float32", flush=True)
183
+ with args.base.open("rb") as src, partial.open("xb") as dst:
184
+ dst.write(prefix)
185
+ src.seek(len(old_prefix))
186
+ for key in ordered:
187
+ start, stop = h[key]["data_offsets"]
188
+ assert src.tell() == len(old_prefix) + start
189
+ remaining = stop - start
190
+ tensor_hash = hashlib.sha256()
191
+ while remaining:
192
+ chunk = src.read(min(CHUNK, remaining))
193
+ if not chunk:
194
+ raise EOFError(key)
195
+ base_digest.update(chunk)
196
+ remaining -= len(chunk)
197
+ if key not in replacement:
198
+ dst.write(chunk)
199
+ tensor_hash.update(chunk)
200
+ if key in replacement:
201
+ data = replacement[key].numpy().tobytes()
202
+ dst.write(data)
203
+ tensor_hash.update(data)
204
+ expected_tensors[key] = tensor_hash.hexdigest()
205
+ assert src.read(1) == b"", "Unexpected trailing bytes"
206
+ dst.flush()
207
+ os.fsync(dst.fileno())
208
+ assert base_digest.hexdigest() == args.expected_base_sha256, "Source trunk hash mismatch"
209
+ print("Verifying every written tensor and the complete checkpoint hash", flush=True)
210
+ output_digest = hashlib.sha256()
211
+ with partial.open("rb") as f:
212
+ actual_prefix = f.read(len(prefix))
213
+ assert actual_prefix == prefix
214
+ output_digest.update(actual_prefix)
215
+ for key in ordered:
216
+ start, stop = new_header[key]["data_offsets"]
217
+ remaining = stop - start
218
+ tensor_hash = hashlib.sha256()
219
+ while remaining:
220
+ chunk = f.read(min(CHUNK, remaining))
221
+ if not chunk:
222
+ raise EOFError(key)
223
+ remaining -= len(chunk)
224
+ output_digest.update(chunk)
225
+ tensor_hash.update(chunk)
226
+ assert tensor_hash.hexdigest() == expected_tensors[key], key
227
+ assert not f.read(1)
228
+ validation = validate_native(partial, args.pdd, args.comfy)
229
+ report = {
230
+ "output": str(args.out), "size": partial.stat().st_size,
231
+ "sha256": output_digest.hexdigest(),
232
+ "base_sha256": base_digest.hexdigest(), "pdd_source_sha256": source_hash,
233
+ "unchanged_trunk_tensors": len(h) - len(HEADS),
234
+ "replaced_head_tensors": {k: new_header[k]["shape"] for k in HEADS},
235
+ "head_reconstruction_max_absolute_errors": reconstruction_errors,
236
+ "every_tensor_verified_after_write": True, **validation,
237
+ }
238
+ partial.rename(args.out)
239
+ args.report.write_text(json.dumps(report, indent=2) + "\n")
240
+ print(json.dumps(report, indent=2), flush=True)
241
+
242
+
243
+ if __name__ == "__main__":
244
+ main()
minimax_h3_ref2va_pdd_acc_8step_baked_with_heads_int8_convrot.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d1f555a323a71a3ef33221527c240c9d63534f6fa5ff507b042a48f0423b8484
3
+ size 34124239286
minimax_h3_ref2va_pdd_acc_8step_baked_int8_convrot.safetensors → minimax_h3_ref2va_pdd_acc_8step_baked_without_heads_int8_convrot.safetensors RENAMED
File without changes
with-heads-validation.json ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "output": "minimax_h3_ref2va_pdd_acc_8step_baked_with_heads_int8_convrot.safetensors",
3
+ "size": 34124239286,
4
+ "sha256": "d1f555a323a71a3ef33221527c240c9d63534f6fa5ff507b042a48f0423b8484",
5
+ "base_sha256": "fe8e58d8028e74607dfb41aa6a9ce92f5e0f989fff3e1eba6041caedcd1e111e",
6
+ "pdd_source_sha256": "111c82e669f6e20e628228172edf39395f1a9fc3ad049793895e542c0f55b18c",
7
+ "unchanged_trunk_tensors": 1031,
8
+ "replaced_head_tensors": {
9
+ "final_layer.video_out.weight": [
10
+ 3072,
11
+ 5376
12
+ ],
13
+ "final_layer.video_out.bias": [
14
+ 3072
15
+ ],
16
+ "final_layer.audio_out.weight": [
17
+ 1024,
18
+ 5376
19
+ ],
20
+ "final_layer.audio_out.bias": [
21
+ 1024
22
+ ]
23
+ },
24
+ "head_reconstruction_max_absolute_errors": {
25
+ "final_layer.video_out.weight": 2.3283064365386963e-10,
26
+ "final_layer.video_out.bias": 0.0,
27
+ "final_layer.audio_out.weight": 1.1641532182693481e-10,
28
+ "final_layer.audio_out.bias": 0.0
29
+ },
30
+ "every_tensor_verified_after_write": true,
31
+ "native_detection": {
32
+ "pdd_steps": 32,
33
+ "latents_dim": 24,
34
+ "audio_latents_dim": 32
35
+ },
36
+ "native_model_head_shapes": "passed",
37
+ "native_head_parity": [
38
+ {
39
+ "stream": "video",
40
+ "cases": 46,
41
+ "max_relative_l2_error": 3.3237846140005016e-07,
42
+ "max_absolute_error": 9.193037961097161e-06
43
+ },
44
+ {
45
+ "stream": "audio",
46
+ "cases": 46,
47
+ "max_relative_l2_error": 3.565437028806307e-07,
48
+ "max_absolute_error": 7.117412184953764e-06
49
+ }
50
+ ],
51
+ "simple_scheduler_matches_pdd_boundaries": {
52
+ "8": [
53
+ 1.0,
54
+ 0.9882352948188782,
55
+ 0.9729729890823364,
56
+ 0.9523809552192688,
57
+ 0.9230769276618958,
58
+ 0.8780487775802612,
59
+ 0.800000011920929,
60
+ 0.6315789222717285,
61
+ 0.0
62
+ ],
63
+ "4": [
64
+ 1.0,
65
+ 0.9729729890823364,
66
+ 0.9230769276618958,
67
+ 0.800000011920929,
68
+ 0.0
69
+ ]
70
+ },
71
+ "complete_checkpoint_native_load": {
72
+ "checkpoint_sha256": "d1f555a323a71a3ef33221527c240c9d63534f6fa5ff507b042a48f0423b8484",
73
+ "native_load_diffusion_model": "passed",
74
+ "missing_or_unexpected_weights": 0,
75
+ "load_device": "cpu",
76
+ "pdd_steps": 32,
77
+ "video_head_shape": [
78
+ 3072,
79
+ 5376
80
+ ],
81
+ "audio_head_shape": [
82
+ 1024,
83
+ 5376
84
+ ],
85
+ "head_dtype": "torch.float32"
86
+ }
87
+ }