devin-lai's picture
Release v0.4.0: selective MLX model downloads
3da88e3 verified
Raw History Blame Contribute Delete
4.39 kB
"""Matched image and performance trials. Run from the repository root."""
from __future__ import annotations
import argparse
import gc
import json
import logging
import statistics
from pathlib import Path
from h3_mlx.runtime import Pipeline
PROFILES = {
"native": {},
"fused": {"mlp_chunk": 0, "kernel": "metal-qk", "threads": 256, "vae_batch": 8},
"chunked": {"mlp_chunk": 1024},
"wide-mlp": {"mlp_chunk": 0},
"tuned": {"mlp_chunk": 0, "kernel": "metal", "threads": 256, "vae_batch": 8},
"q8": {"bits": 8, "mlp_chunk": 0, "vae_batch": 8},
"q4": {"bits": 4, "mlp_chunk": 0, "vae_batch": 8},
"q8-g32": {"bits": 8, "group_size": 32, "mlp_chunk": 0, "vae_batch": 8},
"preview": {"mlp_chunk": 0, "kernel": "metal", "threads": 256, "vae_batch": 8},
"stream": {"dit_cache_gb": 0},
}
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--model", required=True)
p.add_argument("--text-encoder", required=True)
p.add_argument(
"--profiles",
nargs="+",
choices=PROFILES,
default=["native", "tuned", "q8", "q4", "preview"],
)
p.add_argument("--cases", nargs="+", default=["product", "portrait"])
p.add_argument("--case-file", default="experiments/cases.json")
p.add_argument("--output", default="outputs/suite")
p.add_argument(
"--repeat",
type=int,
default=2,
help="First trial includes model loading; later trials reuse resident models",
)
p.add_argument("--save-trace", action="store_true")
a = p.parse_args()
if a.repeat < 1:
p.error("repeat must be positive")
logging.basicConfig(
level=logging.INFO, format="%(asctime)s %(message)s", datefmt="%H:%M:%S"
)
cases = [c for c in json.loads(Path(a.case_file).read_text()) if c["id"] in a.cases]
if {c["id"] for c in cases} != set(a.cases):
p.error("Requested case not found in case file")
root = Path(a.output)
root.mkdir(parents=True, exist_ok=True)
ledger = root / "trials.jsonl"
for name in a.profiles:
logging.info("PROFILE %s", name)
pipeline = Pipeline(a.model, a.text_encoder, **PROFILES[name])
try:
for c in cases:
for trial in range(a.repeat):
output = root / c["id"] / f"{name}-{trial}.png"
_, row = pipeline.generate(
c["prompt"],
width=c["width"],
height=c["height"],
seed=c["seed"],
output=output,
steps=[0, 2, 4] if name == "preview" else None,
save_trace=a.save_trace,
images=c.get("images", ()),
)
row.update(
profile=name,
case=c["id"],
trial=trial,
sample_type="resident"
if row["dit_was_resident"]
else "load_included",
)
with ledger.open("a") as h:
h.write(json.dumps(row) + "\n")
finally:
pipeline.clear()
del pipeline
gc.collect()
rows = [json.loads(line) for line in ledger.read_text().splitlines()]
summary = []
for profile in a.profiles:
for case in a.cases:
group = [r for r in rows if r["profile"] == profile and r["case"] == case]
resident = [
r["timings"]["total_s"] for r in group if r["sample_type"] == "resident"
]
summary.append(
{
"profile": profile,
"case": case,
"samples": len(group),
"resident_samples": len(resident),
"resident_median_s": statistics.median(resident)
if resident
else None,
"load_included_s": [
r["timings"]["total_s"]
for r in group
if r["sample_type"] == "load_included"
],
}
)
(root / "summary.json").write_text(json.dumps(summary, indent=2) + "\n")
if __name__ == "__main__":
main()