"""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 instructions ( option )* ", } ) 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()