MiniMax-H3-Image-mlx / experiments /audit_modulation.py
devin-lai's picture
Release v0.4.0: selective MLX model downloads
3da88e3 verified
Raw History Blame Contribute Delete
3.31 kB
"""Check schedule caching against individual GEMVs and PyTorch on actual weights."""
from __future__ import annotations
import argparse
import json
import math
from pathlib import Path
import mlx.core as mx
import numpy as np
import torch
import torch.nn.functional as F
from h3_mlx.benchmark import error
from h3_mlx.config import load_sigmas
from h3_mlx.ops import silu
from h3_mlx.runtime import configure_memory
from h3_mlx.weights import Linear, WeightStore
def tensor(x):
# Bit-preserve BF16 rather than round-tripping it through FP32.
if x.dtype == mx.bfloat16:
return torch.from_numpy(np.asarray(x.view(mx.uint16)).copy()).view(
torch.bfloat16
)
return torch.from_numpy(np.asarray(x).copy())
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--model", required=True)
p.add_argument("--layers", type=int, nargs="+", default=[0, 25, 49])
p.add_argument("--output", default="reports/modulation-audit.json")
a = p.parse_args()
configure_memory()
torch.set_num_threads(6)
path = Path(a.model)
store = WeightStore(path / "transformer")
sigmas = load_sigmas(path)
times = mx.array([1 - s for s in sigmas[:-1]], mx.float32)
freq = mx.exp(-math.log(10000) * mx.arange(128, dtype=mx.float32) / 128)
args = times[:, None] * freq
features = mx.concatenate((mx.cos(args), mx.sin(args)), axis=-1)
first = Linear.load(store, "time_embedder.proj_in")
last = Linear.load(store, "time_embedder.proj_out")
t = silu(last(silu(first(features)))).astype(mx.bfloat16)
mx.eval(t)
tt = torch.tensor([1 - s for s in sigmas[:-1]], dtype=torch.float32)
tf = torch.exp(-math.log(10000) * torch.arange(128, dtype=torch.float32) / 128)
tf = tt[:, None] * tf
tf = torch.cat((tf.cos(), tf.sin()), -1)
tf = F.silu(
F.linear(
F.silu(F.linear(tf, tensor(first.weight), tensor(first.bias))),
tensor(last.weight),
tensor(last.bias),
)
).bfloat16()
records = []
for index in a.layers:
linear = Linear.load(store, f"blocks.{index}.adaln_proj.linear")
batched = linear(t)
individual = mx.concatenate([linear(t[i : i + 1]) for i in range(6)])
mx.eval(batched, individual)
tw, tb = tensor(linear.weight), tensor(linear.bias)
with torch.inference_mode():
ref = torch.cat([F.linear(tf[i : i + 1], tw, tb) for i in range(6)])
reference = mx.array(ref.float().numpy()).astype(mx.bfloat16)
row = {
"layer": index,
"batched_vs_individual_mlx": error(individual, batched),
"mlx_vs_native_torch_cpu": error(reference, batched),
}
print(json.dumps(row), flush=True)
records.append(row)
del linear, tw, tb, batched, individual, reference, ref
mx.clear_cache()
Path(a.output).write_text(
json.dumps(
{
"source": store.fingerprint(),
"layers": records,
"scope": "Actual original weights; original one-row-per-step BF16 projections, FP32 timestep MLP; CPU PyTorch is not a CUDA image reference",
},
indent=2,
)
+ "\n"
)
if __name__ == "__main__":
main()