Add native Ref2VA PDD bake WITH HEADS; label previous bake WITHOUT HEADS
Browse filesPublish 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 +122 -88
- bake_pdd_with_heads.py +244 -0
- minimax_h3_ref2va_pdd_acc_8step_baked_with_heads_int8_convrot.safetensors +3 -0
- minimax_h3_ref2va_pdd_acc_8step_baked_int8_convrot.safetensors → minimax_h3_ref2va_pdd_acc_8step_baked_without_heads_int8_convrot.safetensors +0 -0
- with-heads-validation.json +87 -0
README.md
CHANGED
|
@@ -1,107 +1,141 @@
|
|
| 1 |
---
|
| 2 |
-
license:
|
|
|
|
|
|
|
| 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
|
| 17 |
|
| 18 |
-
ComfyUI
|
| 19 |
-
[
|
| 20 |
-
|
| 21 |
-
full audio+video generation in **8 (or 4) sampler steps, CFG-free**.
|
| 22 |
|
| 23 |
-
**
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 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 |
|
| 35 |
|---|---|---|
|
| 36 |
-
| `
|
| 37 |
-
| `
|
| 38 |
-
| `
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
`ComfyUI/models/
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
(
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
##
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
}
|