kev-0.6b-coreml / source /convert-coreml.py
alexwengg's picture
Publish verified Kev 0.6B FP16 Core ML conversion
d4eb935 verified
Raw
History Blame Contribute Delete
5.08 kB
"""Convert pinned Kev 0.6B weights to a fixed-shape FP16 Core ML program."""
from __future__ import annotations
import argparse
import json
import platform
import time
from pathlib import Path
import coremltools as ct
import numpy as np
import torch
from assets import ROOT, load_model
from export_model import KevExport
from preprocessing import Shape, prepare_inputs
def fixture_request() -> dict:
return {
"state": "The piece leaves one hole beneath it and creates a small bump on top.",
"questions": {
"placement": {
"type": "choice",
"instructions": "Classify the placement.",
"criteria": {
"clean": "No buried holes and a flat surface",
"risky": "Creates a cavity or an awkward surface",
},
"label": "risky",
"src": "coreml-fixture",
}
},
}
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--length", type=int, default=128)
parser.add_argument("--max-options", type=int, default=32)
parser.add_argument("--output-dir", type=Path, default=ROOT / "build")
parser.add_argument("--parity-only", action="store_true")
args = parser.parse_args()
torch.set_num_threads(2)
lock, tokenizer, decision_model = load_model()
decision_model.eval()
shape = Shape(args.length, args.max_options)
wrapper = KevExport(decision_model, shape.length, shape.max_options).eval()
arrays, encoded = prepare_inputs(decision_model, tokenizer, fixture_request(), shape)
example = tuple(torch.from_numpy(value) for value in arrays.values())
with torch.no_grad():
reference_logits = decision_model.forward(encoded)[0]
export_logits, export_probabilities = wrapper(*example)
option_count = len(encoded["opt_idx"][0])
max_logit_error = float((reference_logits - export_logits[0, :option_count]).abs().max())
same_argmax = int(reference_logits.argmax()) == int(export_logits[0, :option_count].argmax())
print(f"PyTorch wrapper parity: argmax={same_argmax} max_logit_error={max_logit_error:.8f}", flush=True)
if not same_argmax or max_logit_error > 1e-4:
raise RuntimeError("explicit export wrapper does not match upstream Kev")
if args.parity_only:
return
with torch.no_grad():
traced = torch.jit.trace(wrapper, example)
started = time.perf_counter()
converted = ct.convert(
traced,
convert_to="mlprogram",
minimum_deployment_target=ct.target.iOS17,
compute_precision=ct.precision.FLOAT16,
compute_units=ct.ComputeUnit.CPU_ONLY,
inputs=[
ct.TensorType(name="input_ids", shape=(1, shape.length), dtype=np.int32),
ct.TensorType(name="attention_mask", shape=(1, shape.length), dtype=np.int32),
ct.TensorType(name="decide_map", shape=(1, 1, shape.length), dtype=np.float32),
ct.TensorType(name="option_map", shape=(1, shape.max_options, shape.length), dtype=np.float32),
],
outputs=[
ct.TensorType(name="logits", dtype=np.float32),
ct.TensorType(name="probabilities", dtype=np.float32),
],
)
converted.short_description = (
f"Kev 0.6B typed decision scoring, {shape.length} tokens, {shape.max_options} option slots"
)
converted.author = "Jared Palmer (Kev); Qwen team (base); Fluid Inference (Core ML conversion)"
converted.license = "Apache-2.0"
converted.version = "1"
converted.user_defined_metadata.update(
{
"checkpoint_repo": lock["checkpoint"]["repo"],
"checkpoint_revision": lock["checkpoint"]["revision"],
"base_repo": lock["base"]["repo"],
"base_revision": lock["base"]["revision"],
"length": str(shape.length),
"max_options": str(shape.max_options),
"sequence_format": "<state> state <q> instructions (<opt> option </opt>)* <decide>",
}
)
args.output_dir.mkdir(parents=True, exist_ok=True)
path = args.output_dir / f"kev_0_6b_fp16_L{shape.length}_options{shape.max_options}.mlpackage"
converted.save(str(path))
manifest = {
"model": path.name,
"precision": "float16",
"minimum_target": "iOS17/macOS14",
"length": shape.length,
"max_options": shape.max_options,
"parameters": sum(parameter.numel() for parameter in decision_model.parameters()),
"export_seconds": round(time.perf_counter() - started, 1),
"pytorch_wrapper_max_logit_error": max_logit_error,
"python": platform.python_version(),
"torch": torch.__version__,
"coremltools": ct.__version__,
"sources": lock,
}
(args.output_dir / f"{path.stem}.conversion.json").write_text(json.dumps(manifest, indent=2) + "\n")
print(f"Saved {path} ({manifest['parameters']:,} parameters, {manifest['export_seconds']} s)", flush=True)
if __name__ == "__main__":
main()