the short version
- A general runtime picks kernels from runtime capability queries, and a macOS guest that under-reported its Metal family cost llama.cpp 11.08x on prompt processing and 16.36x on token generation.
- h3.c exposes model-graph knobs a general runtime has no vocabulary for, such as running 45 of 50 transformer blocks or computing 11 fresh denoiser velocities instead of 20.
- Neither source publishes a head-to-head benchmark between h3.c and llama.cpp, so the comparison here is architectural rather than empirical.
- The cost of the single-architecture approach is scope: h3.c targets one model on M3 Max and M5 Max, while llama.cpp claims the same binary across Apple Silicon, H100, MI300 and Jetson.
antirez published h3.c, a MiniMax-H3 inference engine for Mac computers, described as native MiniMax-H3 inference for Apple Silicon. The repository is 122 commits and roughly 1.5k stars, and the source tree is the interesting part: h3_shaders.metal, h3_metal.m and h3_gpu.m sit alongside model-specific files for the DiT, the video and audio VAEs, the vision, text and video encoders, and a safetensors loader. The README states that current work is incremental H3-specific Metal performance and memory optimization on M3 Max and M5 Max.
Compare that framing with llama.cpp's own pitch, which promises the "Same binary, same models, same hand-tuned kernels for every GPU and CPU" across Apple Silicon, M Ultra, RTX 5090, H100, MI300, Jetson, DGX Spark and Intel Arc. Both projects hand-write GPU kernels. The difference is how many combinations of model and device those kernels have to cover, and where the runtime decides which one to dispatch.
There is no published head-to-head between the two on the same workload, and h3.c's README does not report throughput numbers. What is available is a separate result from the Cua team that puts a number on the cost of the portable dispatch path when the device reports the wrong capabilities.
The portability seam
Cua's blog post covers macOS guests running under Apple's Virtualization.framework, where the guest sees a paravirtualized graphics device backed by the host GPU. In their stock Tahoe VM, that device reported roughly an Apple 5-era family, 32 KB of maximum threadgroup memory, and SIMD-group matrix support as unavailable. Apple documents capability through GPU families and feature tables and recommends querying the device at runtime, which is exactly what llama.cpp does.
applications are doing exactly what the platform tells them to do
Their fix was a process-scoped Metal capability shim that answers supportsFamily: through Apple family 9 (1009) and raises reported maximum threadgroup memory from 32 KB to 64 KB. Two changed values were enough for the tested llama.cpp build to select newer SIMD-group reduction, SIMD-group matrix and bfloat16 paths.
- M1 Ultra, TinyLlama 1.1B: prompt processing 11.08x faster and token generation 16.36x faster than the same stock VM, with prompt processing at 98% of the bare-metal result.
- Gemma 4 12B QAT Q4_0 (6.98 GB): 7.20x prompt processing and 14.54x token generation, reaching 99.59% of bare-metal prompt speed and 94.82% of bare-metal generation speed.
- Muse Glimmer 30B Q4_K-M GGUF in a 64 GiB guest through llama.cpp b10359: a 512-token prompt processed 7.55x faster and 128 tokens generated 8.87x faster, text-only, with no Ollama, multimodal projector or drafter.
The point is not that llama.cpp was slow. It is that a runtime built to cover every GPU has to make its kernel choice from data supplied by the platform, and when that data is conservative the fast paths are simply never entered. A single-architecture engine that targets M3 Max and M5 Max has fewer places to be wrong about the hardware in front of it.
Knobs that only exist when you know the model
The second difference shows up in the CLI. h3.c reads a Hugging Face snapshot from ./MiniMax-H3 directly and, with --info, checks the model layout and prints the selected Metal device without mapping all weights or generating media. Its validated balanced preset generates 22 frames at 24 fps, about 0.92 seconds, at 512x512.
- --steps 20 performs the default 20 denoising passes.
- --reuse 2 computes 11 fresh denoiser velocities instead of all 20 and extrapolates the skipped transitions.
- --layers 45 runs 45 of the 50 transformer blocks, reducing both time and unified-memory use.
Those are edits to the model graph exposed as command-line flags. A runtime that must accept Qwen 3.6, Gemma 4 and GPT-OSS with the same interface has no natural place to put "skip five of the fifty blocks" or "extrapolate half the Euler transitions". Session state works the same way: h3.c keeps the exact BF16 prompt conditioning, the prepared DiT and the video decoder resident, so repeating a prompt with another seed avoids loading and encoding them again. The optional --show preview path is honest about its cost, adding preview decode time and roughly 10 GiB of temporary model residency.
When each one is the right call
The native single-architecture runtime earns its keep when the target is one model family on one chip family, when the workload is not a token stream, and when the useful tradeoffs are shaped like unified-memory residency rather than quantization level. h3.c's conditioning features make this concrete: persistent first and last frame anchors, and ordered Ref2VA references exposed to the model as Picture 1, Picture 2 and so on, with the constraint that Ref2VA references cannot be mixed with the first and last anchors. That is model semantics leaking into the runtime by design.
The general runtime wins on everything else. One install script, one serve command, a plugin story with pi-llama, GGUF models off the shelf, and the same binary on a laptop or a cluster. Teams shipping a product that must run on whatever hardware a user already owns are not going to hand-write a Metal path per model, and the Cua result suggests the better move there is fixing the capability reporting rather than forking the engine.
What is still open
h3.c publishes no throughput figures in its README, and it describes itself as being built as a sequence of working vertical slices with performance work still in progress. The README also warns that the first process invocation pays model loading and filesystem-cache costs and that the workload is sensitive to thermal throttling, so any comparison anyone runs needs repeated, alternated variants to mean anything. On the other side, Cua's numbers are a research release, cover a specific set of machines and models, and depend on a shim that changes what a virtual device reports about itself. Neither result tells you what a hand-written single-model engine is worth on bare metal against a well-configured general one. That benchmark has not been published.
These daily notes are drafted by a model I run and operate myself — the same kind of pipeline this site is about — from sources published in the previous 24 hours, and every one lists what it read. The longer essays, the talks and the preprint are mine, written by hand.