The demo always works. Someone holds up a phone in airplane mode, types a question, and a 3-billion-parameter model answers in under a second. The room claps. What the demo never shows you is the second question, asked ninety seconds later, when the device has warmed to the temperature of a regrettable life decision and the throttle governor has quietly clipped your clock speed by 40%. Local inference is not hard because the first token is hard. It's hard because the thousandth token, on a thermally soaked phone, with a half-full KV cache and a background app fighting you for RAM, is a completely different machine than the one in the keynote.
Cold start is the tax nobody budgets for
Start with the most boring number, because it's the one that kills products: model-load latency. A 4-bit quantised 7B model is roughly 4GB on disk. Memory-mapping that into a constrained address space, paging weights in on first access, and warming the compute graph is not free — on a mid-tier device you can spend two to five seconds before the first forward pass even begins. Users will not wait. So you keep the model resident, which means you're now holding multiple gigabytes hostage against an OS that is aggressively looking for memory to reclaim. On iOS, that's the jetsam killer; cross a threshold and your process is simply gone, and your "instant" assistant now has a cold start every time the user switches back from Messages. The engineering work isn't the inference. It's negotiating with the operating system for the right to stay alive.
Memory pressure and the cruelty of the KV cache
Here's the part the parameter count hides. Your weights are a fixed cost, but the KV cache — the stored keys and values for every token in context, the thing that lets you avoid recomputing the whole prompt on each step — grows linearly with sequence length. On an 8GB phone where the OS already claimed three-plus and your weights ate four, an 8K-token context can push you off the cliff entirely. The honest answers are unglamorous: shorter context windows, grouped-query attention to shrink the cache, cache quantisation down to int8 or int4, and eviction policies that throw away the oldest tokens and pray the conversation didn't need them. Every one of those is a quality trade. You are choosing, explicitly, to make the model slightly worse so that it runs at all — and that trade is invisible in every benchmark published at batch size one on a tethered, fan-cooled dev board.
Thermals and battery: physics doesn't negotiate
Sustained generation is where the laptop demo lies hardest. A phone SoC can hit impressive tokens-per-second for a burst, but it cannot dissipate that heat indefinitely, and there is no fan. Run continuous decode for ninety seconds and the throttle governor steps your performance cores down to protect the silicon — I've watched real throughput fall by a third to a half mid-paragraph, the generation visibly stuttering as the chip cooks. Battery is the same story told in coulombs: heavy local inference is one of the most power-hungry things a phone does, and "I drained 18% writing two emails" is a churn event, not a feature. This is precisely why the interesting metric isn't peak throughput — it's sustained throughput under thermal load, the on-device equivalent of p99 tail latency. Architecture decisions get paid for in the second minute, not the first second.
The runtime landscape is a swamp, and you have to ship anyway
And you get to fight all of this across a fractured toolchain. Core ML gives you the Apple Neural Engine and genuinely good power efficiency — if you can coax your model through the conversion and accept that you don't fully control what runs where. ONNX Runtime is your cross-platform hedge, broad and pragmatic, weaker on bleeding-edge ops. ExecuTorch is PyTorch's serious bet on edge, promising and still maturing. And llama.cpp remains the workhorse that proved this was possible at all, with the quantisation formats everyone borrows and the portability nobody else matches. There is no single runtime that is best on every device, every model, and every OS version, which means a real local product ships multiple inference backends and a routing layer to pick between them. That is the unglamorous truth: "running local" is a portfolio, not a switch.
Where local breaks, hybrid wins
So be honest about the boundary. Local owns the work that is small, frequent, latency-sensitive and private: autocomplete, summarisation of text already on the device, intent classification, the structured glue between a user's tap and a server's heavy lift. It breaks the moment you need a frontier-class model, a 100K-token context, or sustained reasoning that would melt the phone. The win isn't choosing a side — it's a router that runs the cheap, instant, private path on-device and escalates to the cloud only when the task actually demands it, ideally without the user ever knowing a network hop happened. Get that routing logic right and you get the best of both: the privacy and instant-feel of local, the raw capability of the datacenter, and a cost curve that doesn't bankrupt you. Get it wrong and you've built the worst of both — a phone that's hot, slow, and still calls home.
That router is the real product. And in the next chapter, we follow the money to understand why the people building it are about to reshape the entire economics of inference.