comfyui
nvfp4
video
quantized
adhikjoshi commited on
Commit
72ea840
·
verified ·
1 Parent(s): 10b9570

Upload convert_nvfp4.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. convert_nvfp4.py +108 -0
convert_nvfp4.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Convert the MiniMax H3 bf16 DiT to NVFP4 in ComfyUI's native quant layout.
3
+
4
+ Runs ON the instance (needs /workspace/ComfyUI + comfy_kitchen + a GPU).
5
+
6
+ Mirrors the quantization policy of the released int8_convrot DiT exactly:
7
+ only the 50 main blocks' attn.qkv_proj / attn.out_proj / mlp.fc1 / mlp.fc2
8
+ are quantized (200 layers); norms, adaln, patch/condition projections,
9
+ token_refiner and final layers stay bf16.
10
+
11
+ Per quantized layer (identical tensor layout to the released nvfp4_awq TE):
12
+ .weight U8 [out, in/2] packed FP4 E2M1 pairs
13
+ .weight_scale F8_E4M3 [out, in/16] per-16-block scales
14
+ .weight_scale_2 F32 [] global scale (amax / (448*6))
15
+ .comfy_quant U8 [n] JSON layer config
16
+
17
+ Usage:
18
+ python3 convert_nvfp4.py --src models/diffusion_models/minimax_h3_ref2va_bf16.safetensors \
19
+ --dst models/diffusion_models/minimax_h3_ref2va_nvfp4.safetensors [--fpmm]
20
+
21
+ --fpmm adds {"full_precision_matrix_mult": true} (dequant->bf16 GEMM, the
22
+ quality-safe path the official TE uses). Without it, comfy_kitchen's
23
+ native FP4 tensor-core GEMM path is used (faster, more quality risk).
24
+ """
25
+ import argparse
26
+ import json
27
+ import sys
28
+ import time
29
+
30
+ sys.path.insert(0, "/workspace/ComfyUI")
31
+
32
+ import torch # noqa: E402
33
+ from safetensors import safe_open # noqa: E402
34
+ from safetensors.torch import save_file # noqa: E402
35
+ from comfy.quant_ops import ( # noqa: E402
36
+ TensorCoreConvRotW4A4Layout,
37
+ TensorCoreNVFP4Layout,
38
+ )
39
+
40
+ TARGET_SUFFIXES = (".attn.qkv_proj.weight", ".attn.out_proj.weight",
41
+ ".mlp.fc1.weight", ".mlp.fc2.weight")
42
+
43
+
44
+ def should_quantize(key, shape):
45
+ return (key.startswith("blocks.")
46
+ and key.endswith(TARGET_SUFFIXES)
47
+ and len(shape) == 2)
48
+
49
+
50
+ def main():
51
+ ap = argparse.ArgumentParser()
52
+ ap.add_argument("--src", required=True)
53
+ ap.add_argument("--dst", required=True)
54
+ ap.add_argument("--algo", choices=["nvfp4", "convrot_w4a4"], default="nvfp4")
55
+ ap.add_argument("--fpmm", action="store_true",
56
+ help="nvfp4 only: full_precision_matrix_mult=true "
57
+ "(dequant->bf16 GEMM)")
58
+ args = ap.parse_args()
59
+
60
+ if args.algo == "convrot_w4a4":
61
+ # int4 weights + int4 activations, rotation-assisted (groupsize 256)
62
+ cfg = {"format": "convrot_w4a4", "convrot_groupsize": 256,
63
+ "linear_dtype": "int4"}
64
+ else:
65
+ cfg = {"format": "nvfp4"}
66
+ if args.fpmm:
67
+ cfg["full_precision_matrix_mult"] = True
68
+ cfg_tensor = torch.tensor(list(json.dumps(cfg).encode("utf-8")),
69
+ dtype=torch.uint8)
70
+
71
+ out, n_q, n_keep, t0 = {}, 0, 0, time.time()
72
+ with safe_open(args.src, framework="pt", device="cpu") as f:
73
+ keys = list(f.keys())
74
+ for i, k in enumerate(keys):
75
+ t = f.get_tensor(k)
76
+ if should_quantize(k, t.shape):
77
+ layer = k[:-len(".weight")]
78
+ w = t.cuda()
79
+ if args.algo == "convrot_w4a4":
80
+ qdata, params = TensorCoreConvRotW4A4Layout.quantize(
81
+ w, convrot_groupsize=256, linear_dtype="int4")
82
+ out[layer + ".weight"] = qdata.contiguous().cpu()
83
+ out[layer + ".weight_scale"] = params.scale.contiguous().cpu()
84
+ else:
85
+ qdata, params = TensorCoreNVFP4Layout.quantize(w)
86
+ out[layer + ".weight"] = qdata.contiguous().cpu()
87
+ out[layer + ".weight_scale"] = params.block_scale.contiguous().cpu()
88
+ out[layer + ".weight_scale_2"] = params.scale.to(torch.float32).cpu()
89
+ out[layer + ".comfy_quant"] = cfg_tensor.clone()
90
+ del w, qdata, params
91
+ n_q += 1
92
+ if n_q % 20 == 0:
93
+ torch.cuda.empty_cache()
94
+ print(f"[{time.time()-t0:6.0f}s] quantized {n_q} layers "
95
+ f"({i+1}/{len(keys)} tensors)", flush=True)
96
+ else:
97
+ out[k] = t
98
+ n_keep += 1
99
+
100
+ print(f"quantized {n_q} layers, kept {n_keep} tensors; saving {args.dst}")
101
+ save_file(out, args.dst)
102
+ size = sum(v.numel() * v.element_size() for v in out.values())
103
+ print(f"done in {time.time()-t0:.0f}s — ~{size/1e9:.1f} GB "
104
+ f"(config: {json.dumps(cfg)})")
105
+
106
+
107
+ if __name__ == "__main__":
108
+ main()