New to VMs on Apple silicon? Start with the fundamentals; this post picks up from there.

Four things determine VM performance

"Which hypervisor is fastest?" is the wrong question. Nearly every serious hypervisor on a given CPU architecture converges on similar numbers, because they're all constrained by the same four things: how expensive it is to trap from guest to host on the CPU, how much overhead the memory translation path adds, how interrupts get delivered into the guest, and whether I/O devices are emulated, paravirtualized, or handed to the guest directly. ARM and x86 answer these differently at the architecture level, and that, not branding, is what explains why VM performance differs between platforms.

CPU: a dedicated exception level, not a shared privilege ring

Every trap from guest to hypervisor and back costs cycles: saving and restoring state, flushing pipelines, switching contexts. How expensive that is depends on the CPU architecture. ARM's Virtualization Extensions add a dedicated hardware exception level for this, EL2, with its own register file, MMU context and exception vectors, architecturally separate from EL1 and EL0, where the guest OS and its user processes run. x86's VMX, by contrast, has historically required the hypervisor and the host OS kernel to share the same privilege ring, which is a fundamentally more entangled arrangement. That architectural separation is why ARM research from Columbia University characterizing ARM virtualization performance treats EL2 as a first-class design advantage rather than a compatibility layer bolted onto an existing privilege model.

ARMv8.1 added a further refinement, Virtualization Host Extensions (VHE), which lets a Type-2 hypervisor's host kernel run directly at EL2 instead of trampolining through it. That removes a full EL1↔EL2 transition on every host system call while a VM runs: small per call, but it adds up on workloads with a high guest-to-host syscall rate, like network-heavy I/O.

Memory: two-stage translation, and why it costs less than it sounds

A VM's guest OS manages its own page tables like any OS does, translating guest virtual addresses to what it believes are physical addresses. But the hypervisor still needs to control what real memory the VM can actually touch, so ARM (like x86) adds a second translation stage: Stage-1 translates guest-virtual to intermediate-physical, and Stage-2, controlled entirely by the hypervisor, translates that intermediate address to the real physical address. Two lookups per memory access sounds like it should double memory latency. In practice it doesn't, for the same reason single-stage translation doesn't cost a full page-table walk on every access: modern implementations cache the combined guest-virtual-to-physical translation in the TLB, so the two-stage walk only happens on a genuine TLB miss, not on every access.

Interrupts: where ARM has an architectural edge over x86

This gets less attention than it deserves, and it's a measurable difference. When a virtual device or another virtual CPU needs to signal an interrupt, the guest has to acknowledge and eventually clear it. On x86, that acknowledgment (EOI) has historically had to trap to the hypervisor and be emulated in software, for every single virtual interrupt, including inter-processor interrupts between virtual CPUs. ARM's Virtual Generic Interrupt Controller (vGIC) does this in hardware instead: the guest OS can acknowledge and clear virtual interrupts directly, without trapping to the hypervisor at all. The KVM/ARM paper from Columbia's research group that introduced this design is explicit that avoiding a hypervisor trap on every interrupt is a structural advantage over x86's software-emulated EOI path: an architectural difference, not a tuning one. It isn't entirely free, since vGIC state lives at EL2 and has to be saved and restored on each VM transition, but that's a different order of magnitude from trapping on every interrupt.

I/O: paravirtualization vs. hardware passthrough, plainly defined

This is the trade-off most people mean when they ask about virtualization performance, so it's worth defining precisely:

  • Full emulation: the hypervisor pretends to be a specific real piece of hardware (an old Intel NIC, a specific disk controller) in software. The guest's unmodified driver talks to it exactly like real hardware, but every register access can trap to the hypervisor. Simple and maximally compatible; slow.
  • Paravirtualization (virtio): the guest runs a driver that knows it's virtualized, and talks to the hypervisor through an efficient, purpose-built ring-buffer protocol instead of imitating real hardware register-by-register. Virtio is the de facto standard for this. It requires guest cooperation (a virtio-aware driver), but gets close to native throughput while still letting the hypervisor share, meter, and multiplex the underlying device across multiple VMs.
  • Hardware passthrough (SR-IOV / VFIO): the hypervisor hands a VM direct, largely unmediated access to a physical device, or to a hardware-virtualized slice of one via SR-IOV. This gets closest to bare-metal performance, in the low single-digit microseconds of added latency in benchmarked NIC passthrough setups, because the hypervisor mostly gets out of the way. The cost is exclusivity: a passed-through device typically can't be shared or migrated the way a paravirtualized one can, and it usually requires an IOMMU (ARM's SMMU) to isolate what the guest can DMA into safely.
Overhead sourceNaive approachARM's hardware-assisted approach
CPU traps Hypervisor shares a privilege level with the host OS Dedicated EL2 exception level, with VHE letting the host kernel run there directly
Memory Every access re-walks a two-stage page table Combined translation is TLB-cached; the two-stage walk only happens on a miss
Interrupts Every ACK/EOI traps to the hypervisor for emulation vGIC lets the guest ACK/EOI in hardware, no trap required
I/O Full device emulation, trapping on every register access virtio (paravirtualized) for shared/flexible devices, SR-IOV passthrough for exclusive near-native access

How this shows up on Apple Silicon specifically

Apple built virtualization into macOS as a first-class part of the Apple Silicon transition rather than retrofitting it, and it rides on exactly the ARM mechanisms above. The Hypervisor framework gives low-level access to the CPU's virtualization extensions (EL2, Stage-2 translation, EL1/EL0 instruction and register trapping), and Apple's approach diverges deliberately from how virtualization worked on Intel Macs, precisely because the hardware underneath it is different. Virtualization.framework, the higher-level API Velo Workspaces builds on, sits on top of that. (In macOS 15 Apple added support for exposing EL2 inside a guest, nested virtualization that lets a VM run its own hypervisor, a separate and newer capability from the base EL2 virtualization every Apple Silicon Mac has always used to run VMs at all.)

I/O is where the passthrough-versus-paravirtualization trade-off gets concrete, and a little disappointing if you hoped for GPU passthrough. The Virtualization framework gives a Linux guest virtio-gpu, a paravirtualized display with no path to the Mac's GPU, as Apple's own container team has confirmed in public discussion. That's a platform limit rather than an oversight: unlike a network card or a disk, there's no SR-IOV-style standard for sharing a GPU this way on this platform yet. It's why AI Bridge takes a different path. Instead of pushing compute into the guest across an I/O boundary that doesn't support it, the model server stays on the Mac's GPU and the guest reaches it over vsock, itself a virtio device (VZVirtioSocketDeviceConfiguration), the same paravirtualized pattern as virtio-net or virtio-blk, carrying a model API instead of a filesystem or a network stack. See the full architecture and what it actually costs, benchmarked rather than asserted, and how the GPU story compares to other VM apps on the same hardware.

The cloud reached the same conclusion from a different direction

This isn't an Apple-specific story. AWS's Nitro system and Google's Titanium move exactly these kinds of overhead, network and storage I/O especially, onto dedicated hardware next to the CPU, so guest instances see close to bare-metal performance without the hypervisor mediating every packet or disk write in software. AWS Graviton's newer generations also added ARMv8.3+ nested-virtualization support, the same underlying capability Apple added to Virtualization.framework in macOS 15. Different companies, different silicon, converging on the same answer: push I/O virtualization overhead into hardware, and use ARM's EL2/vGIC-class primitives for everything else, rather than trying to out-engineer trap-and-emulate in software.

Why this explains our benchmark numbers

This is the mechanism behind results published elsewhere on this blog. For single requests, an MLX inference benchmark through a VM on Apple silicon was indistinguishable from running on the host, within normal run-to-run noise. Under concurrent load, the gap was a real but small single-digit percentage. Given everything above, that's what you'd expect: CPU traps are cheap (a dedicated EL2), interrupts don't trap to the hypervisor (vGIC), memory translation is amortized by the TLB, and the one I/O path that matters here, AI Bridge's vsock channel, is a lightweight paravirtualized ring buffer, not a heavyweight emulated device or a full virtualized network stack processing TCP/IP.

Related reading: the architecture behind AI Bridge, the inference benchmark, what VM isolation costs across two inference engines, what's worth tuning inside the guest, how many vCPUs to assign, and what this buys you day to day, without the exception levels. Or download Velo Workspaces and try it yourself.