Files
AV-Live/data_only_viz/scripts/coreml_probe.py
T
L'électron rare 7162f76d6f perf(data-only-viz): CoreML probe v4 = 11.8x
Conversion DINOv2 ViT-S 672x672 reussie avec 2 patches :
(1) pre-calcul interpolate_pos_encoding en buffer fige,
(2) patch coremltools _cast pour val non-0d (bug ops.py:3048).

Bench M5 50 iter :
  CoreML CPU_AND_GPU : 25.1 ms (40 fps)
  PyTorch MPS        : 274.7 ms (3.6 fps)
  Speedup            : 11.8x

ANE compute units ralentit (CPU_AND_NE=157ms vs GPU=25ms). Le
vrai gain CoreML = graph compile MPSGraph + op fusion, pas ANE.

Probe reproductible : data_only_viz/scripts/coreml_probe.py.
2026-05-13 19:33:27 +02:00

114 lines
3.6 KiB
Python

"""DINOv2 ViT-S 672x672 backbone CoreML conversion + bench.
Probe v4 (2026-05-13) — résultat : conversion OK avec 2 patches,
bench M5 CoreML CPU_AND_GPU = 25 ms vs PyTorch MPS = 275 ms = 11.8x
speedup. ANE compute unit n'apporte rien (et ralentit) sur ce modele.
Patches requis :
1. Pre-calculer interpolate_pos_encoding en buffer fige (sinon
coremltools rejette l'interpolation dynamique).
2. Patcher coremltools._cast pour gerer val non-scalaire via
numpy.asarray().item() ou fallback mb.cast (sinon plante
`dtype(x.val)` sur shape arithmetic int().
"""
from __future__ import annotations
import time
import types
import numpy as np
import torch
import coremltools as ct
H = W = 672
def _patched_cast(context, node, dtype, dtype_str):
"""Wrap coremltools _cast pour gerer x.val non-0d."""
from coremltools.converters.mil import Builder as mb
from coremltools.converters.mil.frontend.torch import ops as _ops
inputs = _ops._get_inputs(context, node, expected=1)
x = inputs[0]
if x.val is not None:
v = x.val
try:
const_val = dtype(v)
except TypeError:
arr = np.asarray(v)
if arr.size == 1:
const_val = dtype(arr.item())
else:
res = mb.cast(x=x, dtype=dtype_str, name=node.name)
context.add(res)
return
res = mb.const(val=const_val, name=node.name)
else:
res = mb.cast(x=x, dtype=dtype_str, name=node.name)
context.add(res)
def install_coreml_patches() -> None:
from coremltools.converters.mil.frontend.torch import ops as _ops
_ops._cast = _patched_cast
def build_dinov2_with_fixed_pos_embed():
model = torch.hub.load(
"facebookresearch/dinov2", "dinov2_vits14",
pretrained=True, trust_repo=True)
model.eval()
with torch.no_grad():
dummy_p = model.patch_embed(torch.rand(1, 3, H, W))
cls = model.cls_token.expand(dummy_p.shape[0], -1, -1)
x_full = torch.cat((cls, dummy_p), dim=1)
cached_pe = model.interpolate_pos_encoding(x_full, H, W).detach()
model.register_buffer("_cached_pos_embed", cached_pe)
def fixed_pe(self, x, w, h):
return self._cached_pos_embed.to(x.dtype)
model.interpolate_pos_encoding = types.MethodType(fixed_pe, model)
return model
def convert(model, out_path: str) -> ct.models.MLModel:
example = torch.rand(1, 3, H, W)
traced = torch.jit.trace(model, example, strict=False)
mlmodel = ct.convert(
traced,
inputs=[ct.TensorType(
shape=(1, 3, H, W), name="image", dtype=np.float32)],
compute_units=ct.ComputeUnit.CPU_AND_GPU,
minimum_deployment_target=ct.target.macOS15,
convert_to="mlprogram",
)
mlmodel.save(out_path)
return mlmodel
def bench(mlmodel, n: int = 50) -> dict:
img = np.random.rand(1, 3, H, W).astype(np.float32)
for _ in range(5):
_ = mlmodel.predict({"image": img})
t = []
for _ in range(n):
t0 = time.perf_counter()
_ = mlmodel.predict({"image": img})
t.append((time.perf_counter() - t0) * 1000)
t.sort()
return {
"median_ms": t[n // 2],
"p10_ms": t[max(0, int(n * 0.1) - 1)],
"p90_ms": t[min(n - 1, int(n * 0.9))],
"min_ms": t[0],
}
if __name__ == "__main__":
install_coreml_patches()
model = build_dinov2_with_fixed_pos_embed()
out = "/tmp/dinov2_vits14_672.mlpackage"
print(f"==> convert -> {out}")
mlmodel = convert(model, out)
print(f"==> bench 50 iter")
stats = bench(mlmodel)
print(f" CoreML CPU_AND_GPU : {stats}")