One GH200 now serves text, vision, speech and a safety model. The interesting part is not that it fits. It is what had to stop being true first.
Claude Code drives the text tier over its own native protocol. None of this was affordable twelve hours earlier, and the hardware did not change. A constraint I had accepted without re-testing did. Here is how it is wired.
Do not absorb it all now. Every box gets its own slide later. A turn goes local only if all three signals agree: trivial, opening turn, under 256 tokens. Everything else crosses to Athena. One of these signals is unlike the others. The jailbreak route does not pick a model at all, it takes the tools away, so an injection arrives as inert text. Free and blunt, sitting next to a semantic signal that needed calibrating to 0.07 for 75 percent accuracy. Speech stays outside the router deliberately: the string "transcription" appears zero times in its source.
One address space across two memory tiers. For everything in this talk the workload never leaves the left half, so you can forget the Grace side exists. This is a story about software.
Five slides of foundations, about four minutes. If you serve models for a living, this is review and you can rest. If you mostly call APIs, this is the part that makes everything after it land. I would rather over-explain than have half the room politely lost.
Almost everything surprising about serving a model lives in the gap between those two phases. Hold on to this, the whole talk hangs off it.
Not part of it. All of it. Every weight, out of memory, for every single token. Then it does it again for the next one.
So single-stream generation is not limited by how fast the GPU can do maths. It is limited by how fast it can move bytes. The tensor cores, the expensive part you actually bought the card for, sit mostly idle.
Prefill is the opposite: one pass over a huge batch of tokens, math units saturated. Same silicon, opposite bottleneck.
When the model generates token 5,000, attention makes it look back at all 4,999 tokens before it. Recomputing those every step would be brutally quadratic.
So the server keeps a KV cache: for every token that has passed through, it stores that token's keys and values, once, and reuses them forever after.
The win is that each token is processed once rather than once per subsequent step. The cost is that the cache grows with every token. When someone says a model "supports 262k context," what they mean is: if you can afford the cache.
Attention works by each token asking a question. That question is a query. Every earlier token offers a key, saying roughly what it is about, and a value, carrying its actual content. Models run a couple of dozen of these comparisons in parallel, and each parallel copy is called a head. Only the keys and values get cached, and they do not have to be one set per head.
That is grouped-query attention, and it is the only reason a 262k window fits on this box at all. Your context length is an architecture decision, not a hardware capability.
Quantization stores each weight in fewer bits. FP8 instead of BF16 halves the bytes, and since decode is bandwidth bound, fewer bytes per weight is directly fewer bytes to read per token. Speculative decoding attacks the same bottleneck from the other end:
These are the two knobs I spent that night measuring. Measuring them carefully is exactly why I thought I understood my setup.
First I did the disciplined thing. One variable at a time, every configuration confirmed by a second run, speculative decoding measured on and off at 63.1 to 112.0 tok/s. Clean, repeatable, and I was pleased with it.
Then a different model I wanted to try. Its architecture was not registered in the version I had. So I built a second environment alongside the first and, while it was sitting there, re-ran the identical benchmark on it.
Same model. Same quantization. Same speculative config. Same memory utilization. Same hardware. Same harness script.
112.0 to 160.0 tok/s
419,200 to 1,574,379 tokens
The variable I never tested moved more than everything I did.
Prefill was 6,266 tok/s before and 6,249 after. Unchanged. That is not a footnote, it is the whole attribution.
If both had moved I would be hunting a config mistake in my own setup. Because only the bandwidth-bound half moved, I can point at the serving stack. A null result did the attribution.
"full_attention_interval": 4
This model is not a uniform stack. One layer in four is ordinary full attention; the other three are not.
This is the second way the architecture cuts your context bill. The first was sharing key and value sets across heads; this one skips three quarters of the layers entirely. The older engine did not know that. The ratio predicts 4x; I measured 3.75x. Inferred from release notes and that ratio, not from per-layer cache logs.
I just had three quarters less context than I thought I did.
This is the shape of the whole talk. The loud failure is the easy one: the model refuses to load, you go find a newer engine, you move on with your day. The quiet failure is the expensive one.
Every cheap check passed while this was broken. Models endpoint: fine. Chat completion: fine. Plain prompt: fine. Only running the actual agent loop caught it.
Three of the first four would have been written down as findings about the model rather than about the harness.
Two quote characters. opencode run wraps your prompt in literal double quotes before it goes on the wire. That moved my complexity classifier's margin from -0.155 to -0.085, across the routing boundary, on a scale whose entire range is about 0.2. I had been scoring that classifier against bare strings, a wire format no real client sends. Measured honestly across both, accuracy was 75 percent, not 90.
My cache-busting was cached. Laptop against GH200 on a 49,000-token prefill measured 6.0s and 6.5s. Near parity, and wrong. I had salted the end of the message, so the prefix was byte-identical and both machines served it from cache. Salting the system message instead: 169.6s against 7.1s, a 24x gap pointing the other way.
Anyone can blame their tools. Both of these were mine.
The cliff is between the second and the third, and that one ratio settles nearly every placement question on the box. vLLM agrees with it: offload paths into Grace, and no disk backend for weights at all.
100 GB of Grace behind the HBM pool, proven by eviction rather than by inference. That 0.17s is the entire architecture, because it is what makes giving up HBM cheap.
--offload-backend uva --cpu-offload-params experts A mixture-of-experts model routes each token to a handful of its experts, so decode touches very few per token. Park those in Grace and the link traffic stays small. And how much you park is not a switch:
The cliff between HBM and Grace is a dial, not a wall. That is what 447 GB/s looks like when only 6 of 256 experts are touched per token.
Both fit in 80,642 of 97,871 MiB, and one long-context request in flight costs the tier serving nearly all your traffic about 2.8x. I thought speculative decoding explained that, so I tested it. Turning it off does shrink the proportional drop, and leaves you 1.7x slower exactly when the card is shared. The collapse is structural: a dense model sharing the SMs loses at least half its throughput however it decodes. The MoE does not collapse the same way, but I cannot tell you by how much. Its solo rate swung 40.2 to 57.2 tok/s inside one unrestarted process, so a single sample per condition cannot resolve it.
the 27B with the card nearly to itself
matched length, inside run-to-run variance
while Whisper and the vision tier were busy
Two extra tiers and a 2.2x effective KV cache, for nothing on the decode path. The vision tier has since been retired; the 27B is itself multimodal. At matched output length, deliberately: throughput climbs to 262.6 at 800 tokens as fixed overhead amortizes, so comparing across lengths would have shown a gain that was not there.
Worst-case concurrency at a full 262k context falls 5.67x to 2.66x; real agent requests run nearer 50k.
It speaks the Anthropic Messages API. vLLM serves that API natively. It should have just worked. Four walls, and every one of them was the harness:
48 lines across three files, and it runs against my own card, tool loop and all. The router's original premise did not survive measurement, by the way: the laptop only wins below about 180 prompt tokens and no agent client ever sends that few. One real captured request took 157s locally against 3.0s on the GH200. I built a tier router and shipped a protocol translator.
The vision tier was unaffordable all session. HBM was full, and dropping the text model's utilization to make room cost KV capacity outright, 1,485,965 tokens down to 698,085.
Once Grace was catching those evictions at 0.17s instead of 4.55s, the same trade became nearly free and the third model fit the same afternoon. Nothing about the machine changed. A constraint I had accepted without re-testing stopped being one.
Any plan you rejected on a constraint is worth re-testing after you relieve something underneath it.
A control is an assertion that something does not matter. You rarely go back and check it, because checking it is the same work as testing it, and you already decided it was background.
Somewhere in your stack there is a version number, a default, or an inherited flag you have stopped seeing.
All three of tonight's newest failures were claims I did not know I was making. That the wire format was fixed. That the flag arrived. That my salt busted the cache. That the HBM number was the ceiling.
The background of your benchmark is someone else's independent variable.
Colin McNamara · Field CTO at AHEAD · organizer here at AIMUG.
Full write-up with every measurement, the diagrams, and the parts I got wrong along the way:
colinmcnamara.com/blog/engine-other-half-of-the-model
If you run open-weight models on your own metal, I want to hear which of these six you have already hit.