timestral-mlx / scripts /convert.py
jbilcke's picture
Publish all four Timestral MLX FP32 models, pipeline, original licences and validation
d35e174 verified
Raw History Blame Contribute Delete
3.46 kB
"""Convert pinned source weights to strict MLX FP32 bundles. No training or quantization."""
import argparse
import importlib.metadata
import json
import platform
import shutil
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts"))
import numpy as np
import mlx.core as mx
from safetensors.numpy import load_file
from timestral_mlx.config import TimestralConfig
from timestral_mlx.model import TimestralModel
from download import VARIANTS, sha256
def convert(variant):
src, dst = ROOT / "models" / "source" / variant, ROOT / "models" / variant
source = json.loads((src / "source.json").read_text())
for name, entry in source["files"].items():
if sha256(src / name) != entry["sha256"]:
raise ValueError(f"source integrity check failed: {name}")
dst.mkdir(parents=True, exist_ok=True)
weights = load_file(src / "model.safetensors")
if any(v.dtype != np.float32 for v in weights.values()):
raise ValueError("expected original FP32 weights")
converted = {k.replace("._gate.", ".gate."): mx.array(v) for k, v in weights.items()}
config = TimestralConfig.load(src / "config.json")
model = TimestralModel(config)
model.load_weights(list(converted.items()), strict=True)
mx.eval(model.parameters())
mx.save_safetensors(str(dst / "model.safetensors"), converted,
metadata={"format": "mlx", "source": source["repo_id"], "revision": source["revision"]})
# Check every serialized tensor, including exact source values and key mapping.
restored = mx.load(str(dst / "model.safetensors"))
if set(restored) != set(converted):
raise ValueError("serialized key inventory mismatch")
for k, v in weights.items():
if not np.array_equal(v, np.array(restored[k.replace("._gate.", ".gate.")])):
raise ValueError(f"lossless conversion check failed: {k}")
shutil.copyfile(src / "config.json", dst / "config.json")
shutil.copyfile(src / "LICENSE", dst / "LICENSE")
manifest = {
"format": "timestral-mlx-fp32-v1", "variant": variant, "dtype": "float32",
"source": source, "source_code": {"url": "https://github.com/mistralai/timestral",
"revision": "a534bdbd244d64f3d9d743f46c7bf6fc330ed4ff"},
"conversion": {"tensor_count": len(weights), "parameter_count": sum(v.size for v in weights.values()),
"mapping": "Replace ._gate. with .gate.; tensor layouts and FP32 values unchanged",
"lossless_roundtrip_verified": True, "training": False, "quantization": False},
"environment": {"python": platform.python_version(), "mlx": importlib.metadata.version("mlx"),
"numpy": np.__version__, "platform": platform.platform()},
"files": {name: {"sha256": sha256(dst / name), "bytes": (dst / name).stat().st_size}
for name in ("config.json", "model.safetensors", "LICENSE")},
}
(dst / "manifest.json").write_text(json.dumps(manifest, indent=2) + "\n")
print(f"Converted {variant}: {len(weights)} tensors, {manifest['conversion']['parameter_count']:,} parameters; lossless FP32 verified", flush=True)
if __name__ == "__main__":
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("variants", nargs="*", choices=VARIANTS, default=list(VARIANTS))
for size in p.parse_args().variants:
convert(size)