blot-mini / evaluation /package_coreml.py
pmarquees's picture
Publish Blot Mini XS, S and M with full CredData comparison
83afe32 verified
Raw History Blame Contribute Delete
4.06 kB
"""Serialize frozen benchmark tensors into Core ML. No model execution or weight authoring."""
from pathlib import Path
from datetime import datetime, timezone
import hashlib, json
import numpy as np
import coremltools as ct
from coremltools.converters.mil import Builder as mb
from coremltools.libmilstoragepython import _BlobStorageReader
ROOT = Path(__file__).resolve().parent
SOURCE = ROOT.parent / 'creddata-confirmation-2026-11-06/results/model'
OUT = ROOT / 'm-package'
OUT.mkdir(exist_ok=True)
sha = lambda p: hashlib.sha256(p.read_bytes()).hexdigest()
freeze = json.loads((SOURCE / 'frozen.json').read_text())
assert sha(SOURCE / 'model.py') == freeze['source_sha256']
assert sha(SOURCE / 'weights.npz') == freeze['weights_sha256']
with np.load(SOURCE / 'weights.npz', allow_pickle=False) as archive:
W, b, o = (archive[k] for k in ['W', 'b', 'o'])
assert W.shape == (1189, 5153) and b.shape == o.shape == (1189,)
assert all(a.dtype == np.float32 for a in [W, b, o])
assert W.size + b.size + o.size + 1 == freeze['parameters']
@mb.program(input_specs=[mb.TensorSpec(shape=(128, 5153))], opset_version=ct.target.iOS18)
def network(features):
hidden = mb.relu(x=mb.linear(x=features, weight=W, bias=b))
return mb.linear(x=hidden, weight=o[None, :], bias=np.array([-0.5], np.float32), name='scores')
# skip_model_load prevents native compilation/loading and any inference.
model = ct.convert(network, minimum_deployment_target=ct.target.iOS18,
compute_precision=ct.precision.FLOAT32, skip_model_load=True)
package = OUT / 'bundle/BlotHandwritten.mlpackage'
package.parent.mkdir(exist_ok=True)
model.save(str(package))
spec = ct.utils.load_spec(str(package / 'Data/com.apple.CoreML/model.mlmodel'))
block = spec.mlProgram.functions['main'].block_specializations['CoreML8']
reader = _BlobStorageReader(str(package / 'Data/com.apple.CoreML/weights/weight.bin'))
expected = {'linear_0_weight_0': W, 'linear_0_bias_0': b, 'scores_weight_0': o[None, :]}
verified = []
for op in block.operations:
name = op.outputs[0].name
if op.type == 'const' and name in expected:
val = op.attributes['val']
shape = tuple(d.constant.size for d in val.type.tensorType.dimensions)
actual = np.asarray(reader.read_float_data(val.blobFileValue.offset), dtype=np.float32)
assert shape == expected[name].shape
assert actual.tobytes() == expected[name].tobytes(), name
verified.append(name)
if op.type == 'const' and name == 'scores_bias_0':
assert list(op.attributes['val'].immediateValue.tensor.floats.values) == [-0.5]
assert set(verified) == set(expected)
assert [op.type for op in block.operations if op.type != 'const'] == ['linear', 'relu', 'linear']
assert list(spec.description.input[0].type.multiArrayType.shape) == [128, 5153]
assert list(spec.description.output[0].type.multiArrayType.shape) == [128, 1]
assert sha(SOURCE / 'model.py') == freeze['source_sha256']
assert sha(SOURCE / 'weights.npz') == freeze['weights_sha256']
files = {str(p.relative_to(OUT)): sha(p) for p in sorted(OUT.rglob('*')) if p.is_file()}
size = sum((OUT / p).stat().st_size for p in files) + (SOURCE / 'model.py').stat().st_size + (SOURCE / 'weights.npz').stat().st_size
assert size < 30_000_000
receipt = {'checked_utc': datetime.now(timezone.utc).isoformat(), 'coremltools': ct.__version__,
'source_model_sha256': freeze['source_sha256'], 'source_weights_sha256': freeze['weights_sha256'],
'parameters': freeze['parameters'], 'inference_asset_bytes': size,
'graph': 'linear -> ReLU -> linear', 'features': 5153, 'hidden_units': 1189,
'input_shape': [128, 5153], 'output_shape': [128, 1],
'all_serialized_weight_bytes_match_frozen_npz': True, 'readout_bias': -0.5,
'native_model_loaded': False, 'inference_executed': False, 'new_weights_authored': False,
'native_runtime_parity_tested': False, 'benchmark_rerun': False,
'files': files}
(ROOT / 'coreml-packaging.json').write_text(json.dumps(receipt, indent=2) + '\n')
print(json.dumps(receipt, indent=2))