Capítulos · 12
Comparación práctica: pipelines de exportación WhisperKit/Core ML vs Core AI
Parte de la ANE Knowledge Base. Ambos pipelines se ejecutaron realmente en la misma máquina (Apple M4 Pro, macOS 26.5.1) con el mismo modelo (
openai/whisper-tiny), el 2026-07-12. Experimentos:experiments/whisperkit-conversion(doc 11) yexperiments/coreai-whisper-export. Evidencia registrada bajo cadaresults/. Este es el complemento empírico de la comparación conceptual en doc 10.
1. Las dos ejecuciones, lado a lado#
| Dimensión | WhisperKit → Core ML (era 2024) | coreai-torch → Core AI (era WWDC26) |
|---|---|---|
| Entorno | Python 3.12, torch 2.5.0, transformers 4.53, coremltools 9.0, whisperkit 0.4.2 + argmaxtools 0.1.23 |
Python 3.12, torch 2.11.0, transformers 4.57.3, coreai-core 1.0.0b2, coreai-torch 0.4.1 |
| Origen del modelo | Whisper reescrito a mano (Argmax): atención Conv2d/BC1S, KV cache como I/O, 3 componentes | AutoModelForSpeechSeq2Seq de serie de Hugging Face, sin modificar |
| API de exportación | torch.jit.trace por forma fija |
torch.export con Dim("dec_seq_len", 1, 448) — longitud de decoder dinámica en un solo grafo |
| Código del lado del autor | whisperkittools + argmaxtools (miles de líneas: atención personalizada, hooks, arnés de pruebas) | script de ~100 líneas (adaptado de la receta de ~180 líneas de Apple) |
| Artefactos | TextDecoder.mlmodelc (57 MB) + AudioEncoder.mlmodelc (16 MB) + MelSpectrogram.mlmodelc (372 KB) ≈ 73 MB (fp16) |
Un único whisper-tiny_float16.aimodel = 72 MB (fp32: 144 MB); contenido: metadata.json + main.mlirb (binario MLIR) + main.hash |
| Elección de arquitectura | 3 grafos, decoder de un solo token con KV cache — óptimo para inferencia | 1 grafo, sin KV cache, forward completo por llamada — simple como la receta |
| Tiempo de reloj de conversión | ~48 s de suites de pruebas (más trace/convert dentro) | 67 s fp32 (6 s para fp16 con pesos en caché) |
| Verificación integrada | suite de PSNR (42.5–69.4 dB), decodificación de paridad de 447 tokens, planes de cómputo por op | Manual pero trivial: paridad de logits max|diff| = 1.2e-4, coincidencia de argmax |
| Despacho a ANE medido | 99–100% en decoder/encoder; MelSpectrogram todo en CPU (JSONs del plan de cómputo) | No medible en este SO — véase §3 |
| Latencia medida | Encoder 6.15 ms (ANE, 3.59× vs CPU); paso de decoder 1.62 ms | ~1,400 ms enc+dec completo en el runtime de CPU incluido en el paquete (fp16 ≈ fp32) |
| ¿Se ejecuta en macOS 26.5? | Completamente (conversión + ejecución en ANE) | Exportación ✅; inferencia solo vía runtime de CPU incluido; los delegados ANE/GPU necesitan macOS 27 |
2. Qué significan los números (no compares 1.4 s con 8 ms ingenuamente)#
Las dos cifras de latencia miden cosas distintas:
- El pipeline de Core ML se ejecutó en la ANE con una arquitectura con KV cache: encoder una vez (6.15 ms) + 1.62 ms por token.
- La ejecución de Core AI usó el runtime de CPU de respaldo incluido (sin delegados en macOS 26.5) con un grafo sin caché que recalcula el encoder completo + decoder de 4 tokens en cada llamada. En la misma máquina, solo el encoder de Core ML en CPU_ONLY tardó 22 ms — así que ~1.4 s para un forward completo sin caché en fp32/fp16 sobre un runtime de CPU no optimizado no sorprende, y no dice nada sobre el rendimiento de Core AI-en-ANE.
Las conclusiones honestas que esta comparación sí respalda:
- El esfuerzo de autoría se redujo ~50× (un script vs un toolkit) para la misma familia de modelos — porque el trabajo de reescritura se trasladó a las tablas de decomp de Apple y a las primitivas de plataforma (doc 09 §3).
- La arquitectura de KV cache sigue siendo decisión del autor. La receta simple de Whisper de Apple la omite; sus recetas de LLM (
coreai-models/models/qwen3+primitives/ios/cache.py) sí la implementan. Un Whisper óptimo para inferencia sobre Core AI necesitaría la misma división encoder/decoder + estados de caché que WhisperKit fue pionero en usar — los conceptos de doc 06 se transfieren intactos. - Los tamaños de artefacto convergen (72 vs 73 MB en fp16) — dominan los pesos; los formatos no.
- La verificación es más fácil pero más superficial por defecto en la ruta de la receta: whisperkittools incluye umbrales de PSNR y planes de cómputo; con Core AI obtienes una comprobación de paridad limpia en 10 líneas, pero debes solicitarla (o usar el Debugger/
save_intermediatesen macOS 27).
2.5 Por qué "HF de serie" y "reescrito a mano" son equivalentes: adónde se trasladó la cirugía#
Ambos pipelines calculan la misma función con los mismos pesos; difieren en quién realiza la reexpresión compatible con ANE y cuándo.
Era de Core ML — el convertidor era una fotocopiadora fiel. torch.jit.trace registra las ops exactamente como las ejecuta el código fuente (HF Whisper: nn.Linear sobre (B,S,C), divisiones de cabezas con view/transpose, un softmax fusionado), y coremltools tradujo ese grafo op por op. Nada lo rearquitecturó, así que el compilador de ANE recibió formas hostiles (último eje pequeño → relleno de 64 bytes, tensores de atención que fallan en caché, transposes que inducen copias) y recurría a fallback o corría lento — la propia línea base de Apple en 2022: 10× más lento, 14× más memoria. La única palanca disponible era reescribir el código fuente del modelo para que el trace ya tuviera la forma correcta: eso es whisperkittools/argmaxtools. Los hooks de linear_to_conv2d_map son la prueba de que esto es pura reexpresión: el mismo peso Linear (out, in), expandido a (out, in, 1, 1), computa de forma idéntica que una conv 1×1 sobre BC1S.
Core AI — la cirugía se convirtió en un paso del compilador. Tres cambios:
torch.exportproduce un grafo funcional tipado y con formas simbólicas — una IR de compilador real, no una fotografía.run_decompositions(get_decomp_table())usa una tabla diseñada para preservar ops compuestas: elF.scaled_dot_product_attentionde HF sobrevive como un único nodo SDPA (coreai_torch/composite_ops/_sdpa.py), igual que RMSNorm/RoPE — en lugar de fragmentarse en matmuls que el compilador no podría reconocer.- En la especialización, el compilador hace pattern-matching de esos nodos y emite el lowering por hardware: kernels fusionados en GPU; en ANE, ese mismo esquema per-head / channels-first / alineado al último eje que antes se escribía a mano. La forma de referencia de ese lowering es visible en el propio
coreai-models/primitives/ios/sdpa.pyde Apple — el mismo algoritmo queml-ane-transformers(2022) y argmaxtools (2023), ahora propiedad de la plataforma. La asignación de layout y la planificación de memoria (la vieja disciplina de "minimizar copias") ocurren dentro de.optimize()+ especialización sobre el MLIR del.aimodel(main.mlirb).
La equivalencia siempre fue mecánica — Linear(W) sobre (B,S,C) ≡ Conv2d1×1(W[:,:,None,None]) sobre (B,C,1,S); un softmax grande ≡ N softmaxes per-head — y este experimento la verifica de tres maneras: tamaños de artefacto fp16 idénticos (72 vs 73 MB — los mismos pesos), paridad de logits 1.2e-4 (la misma función), y la atención escrita a mano en 2022 reapareciendo literalmente en las primitivas de plataforma de 2026 (el mismo lowering).
La frontera: el compilador absorbió la optimización a nivel de op, no la optimización arquitectónica. Los KV caches, las divisiones encoder/decoder y el prefill de contexto siguen siendo decisiones del autor (§2, conclusión 2) — las matemáticas se fueron al compilador; la arquitectura de inferencia se quedó contigo.
3. La cuestión de la unidad de cómputo, respondida empíricamente#
¿Permite Core AI fijar unidades de cómputo como el .cpuAndNeuralEngine de Core ML? Sí — con una API aún más rica — pero la ubicación se decide en tiempo de especialización, no en tiempo de carga:
from coreai.runtime import AIModel, ComputeUnitKind, SpecializationOptions
ComputeUnitKind # .cpu | .gpu | .neural_engine | .available_kinds()
opts = SpecializationOptions.from_preferred_compute_unit_kind(ComputeUnitKind.neural_engine)
# also: SpecializationOptions.allowed_compute_unit_kinds <- the true CPU_AND_NE analogue (allowed *set*)
# SpecializationOptions.cpu_only(), .default, .with_debug
model = await AIModel.load("model.aimodel", specialization_options=opts) # baked at specialization
| Core ML | Core AI | |
|---|---|---|
| Mecanismo | MLModelConfiguration.computeUnits en carga |
SpecializationOptions en especialización (AIModel.load / AIModel.specialize de Swift) |
| Semántica | Conjunto permitido (.all, .cpuOnly, .cpuAndGPU, .cpuAndNeuralEngine) |
Tanto unidad preferida (from_preferred_compute_unit_kind) como conjunto permitido (allowed_compute_unit_kinds) |
| Introspección de dispositivos | MLModel.availableComputeDevices |
ComputeUnitKind.available_kinds() (devolvió 3 kinds en el M4 Pro) |
| Verificado en este Mac | ✅ usado a lo largo de doc 11 | API presente; delegados no disponibles: SpecializationOptions.is_supported() == False, y USE_OS_COREAI=1 → "Core AI Framework is not available for this version of macOS" |
La sonda (evidencia: experiments/coreai-whisper-export/results/specialization-probe.txt) también reveló la arquitectura del runtime: coreai-core incluye un runtime portátil dentro del paquete (CPU; se ejecuta en cualquier lugar — así funcionó nuestra inferencia en macOS 26.5) mientras que los delegados de hardware (ANE/GPU) viven en el framework Core AI del SO (macOS 27+ / iOS 27+, habilitado desde Python con USE_OS_COREAI=1). Core ML no tiene tal división — es un framework del SO, razón por la cual se ejecuta en todas partes pero no puede instalarse con pip.
4. Resumen de restricciones de plataforma para esta máquina (macOS 26.5 + Xcode beta < 27)#
| Capacidad | ¿Disponible? |
|---|---|
| Core ML: conversión, compilación, ejecución en ANE, planes de cómputo | ✅ todo ello |
Core AI: cadena de herramientas torch.export → .aimodel |
✅ |
| Core AI: inferencia con runtime incluido (CPU) + comprobaciones de paridad | ✅ |
| Core AI: especialización de delegados ANE/GPU | ❌ necesita macOS 27 |
Core AI: compilación AOT con xcrun coreai-build |
❌ no en esta beta de Xcode |
Experimento de seguimiento cuando macOS 27 llegue a este Mac: volver a ejecutar run_aimodel.py con USE_OS_COREAI=1 y preferred_compute_unit_kind=neural_engine, y comparar el .aimodel fp16 con el encoder de Core ML de 6.15 ms de doc 11 — la última celda sin medir de esta comparación.
5. Conclusión#
Mismo modelo, misma máquina, dos generaciones de herramientas: Core ML + WhisperKit exigían experiencia pero entregaron una ejecución en ANE del 99–100% medida hoy; Core AI entregó una ruta de autoría 50× más simple y un runtime portátil, con su historia de hardware supeditada a la actualización del SO. El conocimiento de optimización no desapareció — se trasladó: de tu código (docs 02–07) a las primitivas y el compilador de Apple, con las decisiones de KV cache/arquitectura todavía firmemente en tus manos.