devin-lai's picture
Release v0.4.0: selective MLX model downloads
3da88e3 verified
Raw History Blame Contribute Delete
4.66 kB
"""Apple GPU/CPU alternatives through PyTorch, including MPS Inductor fusion."""
from __future__ import annotations
import argparse
import gc
import json
import platform
import statistics
import time
from pathlib import Path
import torch
import torch.nn.functional as F
from safetensors import safe_open
def load(folder, key):
for p in sorted(Path(folder).glob("*.safetensors")):
with safe_open(p, framework="pt", device="cpu") as f:
if key in f.keys():
return f.get_tensor(key)
raise KeyError(key)
def measure(fn, device, repeats):
def sync():
if device == "mps":
torch.mps.synchronize()
for _ in range(2):
fn()
sync()
samples = []
for _ in range(repeats):
sync()
tick = time.perf_counter()
fn()
sync()
samples.append(1000 * (time.perf_counter() - tick))
return {"median_ms": statistics.median(samples), "samples_ms": samples}
def main(a):
torch.set_num_threads(10)
torch.manual_seed(731)
root = Path(a.model) / "transformer"
out = Path(a.output)
out.parent.mkdir(parents=True, exist_ok=True)
report = {
"platform": platform.platform(),
"torch": torch.__version__,
"threads": 10,
"repeats": a.repeats,
"warmup": 2,
"scope": "Actual H3 block-0 weights, synthetic activations; model loading and dtype conversion excluded.",
"trials": [],
}
with torch.inference_mode():
for name in ("attn.qkv_proj", "attn.out_proj", "mlp.fc1", "mlp.fc2"):
w = load(root, "blocks.0." + name + ".weight")
x = torch.randn(a.tokens, w.shape[1]).bfloat16()
# A single FP32 CPU reference distinguishes accumulation precision.
ref = F.linear(x.float(), w.float())
for device, dtype in (
("cpu", torch.bfloat16),
("cpu", torch.float32),
("mps", torch.bfloat16),
("mps", torch.float16),
("mps", torch.float32),
):
row = {
"operator": name,
"device": device,
"dtype": str(dtype),
"shape": [a.tokens, w.shape[1], w.shape[0]],
}
try:
xx, ww = x.to(device, dtype), w.to(device, dtype)
row.update(
measure(
lambda xx=xx, ww=ww: F.linear(xx, ww), device, a.repeats
)
)
y = F.linear(xx, ww).cpu().float()
row["relative_rmse"] = torch.sqrt(
torch.mean((ref - y) ** 2) / torch.mean(ref**2)
).item()
del xx, ww, y
if device == "mps":
torch.mps.empty_cache()
except Exception as exc:
row["error"] = f"{type(exc).__name__}: {exc}"
report["trials"].append(row)
out.write_text(json.dumps(report, indent=2) + "\n")
print(json.dumps(row), flush=True)
del x, w, ref
gc.collect()
w1 = load(root, "blocks.0.mlp.fc1.weight").to("mps")
w2 = load(root, "blocks.0.mlp.fc2.weight").to("mps")
x = torch.randn(a.tokens, 5376, device="mps").bfloat16()
def mlp(x):
gate, up = F.linear(x, w1).chunk(2, dim=-1)
return F.linear(F.silu(gate) * up, w2)
reference = mlp(x)
for compiled in (False, True):
row = {
"operator": "mlp",
"compiled": compiled,
"device": "mps",
"dtype": "torch.bfloat16",
}
try:
fn = torch.compile(mlp) if compiled else mlp
row.update(measure(lambda fn=fn: fn(x), "mps", a.repeats))
y = fn(x)
row["equal_fraction"] = (reference == y).float().mean().item()
except Exception as exc:
row["error"] = f"{type(exc).__name__}: {exc}"
report["trials"].append(row)
out.write_text(json.dumps(report, indent=2) + "\n")
print(json.dumps(row), flush=True)
if __name__ == "__main__":
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--model", required=True)
p.add_argument("--tokens", type=int, default=4181)
p.add_argument("--repeats", type=int, default=5)
p.add_argument("--output", default="reports/optimization-v2/torch.json")
main(p.parse_args())