Instructions to use yitongl/minimax-h3-nvfp4-lambda-modality with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MiniMax H3
How to use yitongl/minimax-h3-nvfp4-lambda-modality with MiniMax H3:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
File size: 7,160 Bytes
0692312 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | #!/usr/bin/env python
"""Per-input-channel statistics, split by modality of the packed sequence.
`calib_stats.py` accumulates over every row of `[text | video | audio]` without a mask. For a
mean that is harmless -- non-video is 1.36% of rows -- but `absmax` is a maximum, and one extreme
row can set a channel's span alone. Measured on `blocks.0.attn.to_q`, **79.8% of channels have
their absmax set by the 102 text rows**, which are 0.27% of the sequence, and the resulting span
is 1.88x the video-only span at the median. The span becomes lambda, and `W * lambda` is
quantized once for every token, so a lambda pulled by the text rows is paid for by the video rows.
This keeps three independent accumulators per layer so the choice of which rows define lambda is
made later, from data, instead of being fixed by an unmasked `reshape(-1, C)`.
Row modality comes from the transformer's own `video_indices` / `audio_indices` / `text_indices`
kwargs rather than from any assumed tag encoding. The token refiner is a special case: it runs on
the text rows only, so its layers see a row count that matches neither the packed sequence nor a
slice of it, and those are recorded as text.
"""
from __future__ import annotations
import argparse
import json
import sys
import time
from pathlib import Path
import torch
REPO = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO / "scripts"))
sys.path.insert(0, str(REPO / "src"))
MODALITIES = ("text", "video", "audio")
class ModalStats:
"""absmax / sums per input channel, kept separately for each modality."""
__slots__ = ("absmax", "sq_sum", "abs_sum", "rows")
def __init__(self, in_features: int, device: torch.device) -> None:
z = lambda: torch.zeros(in_features, dtype=torch.float64, device=device)
self.absmax = {m: z() for m in MODALITIES}
self.sq_sum = {m: z() for m in MODALITIES}
self.abs_sum = {m: z() for m in MODALITIES}
self.rows = {m: 0 for m in MODALITIES}
@torch.no_grad()
def update(self, x: torch.Tensor, masks: dict[str, torch.Tensor] | None) -> None:
flat = x.reshape(-1, x.shape[-1])
parts = ({m: flat[masks[m]] for m in MODALITIES} if masks is not None
else {"text": flat, "video": flat[:0], "audio": flat[:0]})
for m, part in parts.items():
if part.shape[0] == 0:
continue
p = part.to(torch.float64)
self.absmax[m] = torch.maximum(self.absmax[m], p.abs().amax(dim=0))
self.sq_sum[m] += p.square().sum(dim=0)
self.abs_sum[m] += p.abs().sum(dim=0)
self.rows[m] += p.shape[0]
def result(self) -> dict:
out = {}
for m in MODALITIES:
n = max(self.rows[m], 1)
rms = (self.sq_sum[m] / n).sqrt()
out[m] = {"absmax": self.absmax[m].float().cpu(), "rms": rms.float().cpu(),
"mean_abs": (self.abs_sum[m] / n).float().cpu(), "rows": self.rows[m]}
return out
def main() -> int:
p = argparse.ArgumentParser(description="Per-modality activation statistics for MiniMax-H3")
p.add_argument("--caches", required=True)
p.add_argument("--out", required=True)
p.add_argument("--model-path", default=None)
p.add_argument("--device", default="cuda:0")
p.add_argument("--shard", default=None, help="i/n; streaming stats, merges exactly")
p.add_argument("--attention-backend", default="_flash_3_hub")
args = p.parse_args()
import bench
from h3opt.svdquant_rules import inventory, target_linears
from diffusers import ModularPipeline
device = torch.device(args.device)
t0 = time.perf_counter()
pipe = ModularPipeline.from_pretrained(args.model_path or bench.DEFAULT_MODEL)
pipe.load_components(names=["transformer"], dtype=torch.bfloat16)
transformer = pipe.transformer.to(device).eval()
if args.attention_backend:
transformer.set_attention_backend(args.attention_backend)
print(f"denoiser loaded in {time.perf_counter() - t0:.1f}s", flush=True)
inv = inventory(transformer)
targets = target_linears(transformer)
stats = {n: ModalStats(m.in_features, device) for n, m in targets.items()}
state: dict[str, dict[str, torch.Tensor] | None] = {"masks": None, "seq": 0}
def set_layout(_module, _args, kwargs):
pos = kwargs.get("position_ids")
if pos is None:
return None
seq = int(pos.shape[0])
masks = {}
for m, key in (("text", "text_indices"), ("video", "video_indices"), ("audio", "audio_indices")):
idx = kwargs.get(key)
v = torch.zeros(seq, dtype=torch.bool, device=device)
if idx is not None:
v[idx.to(device)] = True
masks[m] = v
state["masks"], state["seq"] = masks, seq
return None
transformer.register_forward_pre_hook(set_layout, with_kwargs=True)
for name, module in targets.items():
def hook(_m, inputs, _n=name):
x = inputs[0]
rows = x.reshape(-1, x.shape[-1]).shape[0]
# The token refiner runs on the text rows alone, so its row count does not match the
# packed sequence; those rows are text by construction.
stats[_n].update(x, state["masks"] if rows == state["seq"] else None)
module.register_forward_pre_hook(hook)
cache_dir = Path(args.caches)
files = sorted(q for q in cache_dir.glob("*.pt") if ".cond" not in q.name)
if args.shard:
i, n = (int(v) for v in args.shard.split("/"))
files = files[i::n]
print(f"{len(files)} cached steps", flush=True)
cond: dict[str, dict] = {}
t1 = time.perf_counter()
with torch.no_grad():
for k, path in enumerate(files):
rec = torch.load(path, map_location="cpu", weights_only=False)
clip = rec["clip"]
if clip not in cond:
cond.clear()
cond[clip] = torch.load(cache_dir / f"{clip}.cond.pt", map_location="cpu",
weights_only=False)
kw = {kk: v for kk, v in rec.items()
if kk not in ("outputs", "clip", "step") and torch.is_tensor(v)}
kw.update(cond[clip])
kw = {kk: (v.to(device, torch.bfloat16) if v.is_floating_point() else v.to(device))
for kk, v in kw.items()}
transformer(**kw, return_dict=False)
if (k + 1) % 25 == 0:
r = (k + 1) / (time.perf_counter() - t1)
print(f" {k+1}/{len(files)} {r:.2f} steps/s eta {(len(files)-k-1)/r/60:.1f} min",
flush=True)
out = Path(args.out)
out.parent.mkdir(parents=True, exist_ok=True)
torch.save({"inventory": inv, "num_cache_files": len(files), "shard": args.shard,
"caches": str(cache_dir), "wall_s": round(time.perf_counter() - t1, 1),
"stats": {n: s.result() for n, s in stats.items()}}, out)
print(f"wrote {out} ({out.stat().st_size/1e6:.1f} MB)", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())
|