Skip to content
AI Infrastructure

The Network Was Never the Problem — Measuring Local Inference Against the Cloud From Kathmandu

A cloud inference call costs 315 ms from Kathmandu before a single token is generated. A cold 7,200-token prompt costs my laptop 13.8 seconds before it does either. Three runs, two cache states, and a bug in my own harness that had the answer backwards.

Published · How this was researched

Back to Global Tech Search

Market Horizon

Point-in-time measurement, Aug 28 2026 — one machine, one ISP, one morning

Target Sector

Local Inference, Edge AI, Network Latency, Emerging-Market Connectivity

Cloud transport floor

315 ms

Anthropic, before any inference

Cold 7,200-token prompt, local

13.8 s

12.2 s of it just reading the prompt

Same prompt, warm

1.8 s

Same machine, same tokens, cache populated

This site has published two measurements that never spoke to each other. One walked the network distance from Kathmandu to the major cloud regions. The other three ran a local model on a fanless MacBook Air until it stopped speeding up. Separately they say the cloud is far and the M1 is slow, and neither answers the question anyone here actually has, which is whether the far-but-fast thing is worth the trip.

I assumed the answer was about distance. It is not. It is about whether the model has read your prompt before.

Measuring the toll without paying it

The cloud side needs a number for what a request costs before inference begins. I have no API budget for benchmarking, and inventing a figure for how fast someone else's model generates is exactly the thing this site exists not to do.

So I measured the rejection. An HTTPS request to a real inference endpoint with no credentials attached comes back 401 or 403. The timing of that response contains DNS, TCP, TLS and one round trip, and provably no inference whatsoever — there is nothing for the model to do. Seven reps per target, each a separate curl process so no connection is reused.

What a Cloud Call Costs Before Token One

Uncredentialed request to a real API endpoint, median of 3 runs × 7 reps — Kathmandu, Aug 28 2026

Read the split, not the totals. The lower band is the handshake — the part that completes at whatever CDN edge is nearest. It is fast: 22 ms to Anthropic. The upper band is everything after it.

That upper band is the point. The earlier latency post measured handshakes and flagged, correctly, that a quick handshake to a CDN-fronted API tells you the edge is near and says nothing about where the model is. It could flag the problem but not size it. This sizes it: 294 of Anthropic's 315 ms happens after the handshake completes. The near edge terminated my TLS in 22 ms and then the answer came from somewhere thirteen times further away.

The AWS Mumbai control inverts exactly. It is not CDN-fronted, so the handshake is the expensive part (100 ms of a 152 ms total) and the response comes straight back in 43 ms. That is what a request looks like when the thing answering is the thing you connected to.

The bug, which had it backwards

The local side is where I got it wrong, and the way I got it wrong is worth more than the result.

The first harness sent one fixed prompt three times per size and took the median — the same shape as every other benchmark on this site. It produced a clean, plausible table: a 7,201-token prompt answered in 1.8 seconds, prefill costing 31 ms. I nearly wrote the post from it. The conclusion would have been that a five-year-old laptop beats a round trip to the cloud on any prompt size, which is a good headline and is false.

What tripped it was a derived quantity that had no business existing. The harness records prefill throughput as a sanity field, and 7,201 tokens in 31 ms is 232,000 tokens per second. No consumer silicon does that. Nothing was reading those tokens at all.

ollama reuses the KV cache when a prompt shares a prefix with the last one. Rep 1 paid real prefill; reps 2 and 3 read the cache and paid nothing. The median across the three therefore reported the cached case:

prompt_eval_count=7201  rep0 prefill=9458.6ms  wall=11303ms   <- real
prompt_eval_count=7201  rep1 prefill=  31.2ms  wall= 1838ms   <- cache hit
prompt_eval_count=7201  rep2 prefill=  30.1ms  wall= 1841ms   <- cache hit
                        median  =    31.2ms                   <- what got reported

A nine-and-a-half-second error, in the direction that confirmed what I expected. The run completed, the reps were tight, the trend sloped the right way. Nothing looked wrong.

A benchmark that sends the same prompt repeatedly is measuring its own cache. Mine was, and the reps agreeing with each other to within a millisecond was the symptom, not the reassurance.

The fix is not to discard the cached reps. Both numbers are real, and they describe two situations a person is actually in. So cache state became an experimental condition rather than an accident of rep order:

  • Cold — every rep gets a distinct nonce prefixed to the prompt, so no rep can reuse another's prefix. This is a one-off question: paste a document, ask about it.
  • Warm — one identical prompt, sent once to populate the cache and then measured. The populating send is dropped. This is a conversation continuing over context already sent.

The nonce goes at the front because prefix matching runs from the start. A suffix nonce leaves the expensive part of the prefix cached and would have reproduced the bug while looking like a fix — I tried that first.

And the labels now check themselves. Cold rows prefill at 590–780 tok/s, which is this machine's real rate. Warm rows report 2,600 to 234,000 tok/s, which is not a speed, it is a cache being read. The analyser refuses to print anything if a row labelled cold ever reports above 2,000 tok/s.

What it costs the laptop

Time to a Full 200-Token Answer, Cold vs. Warm

MacBook Air M1 8GB fanless, llama3.2:1b-q4_K_M, median of 3 runs — the dashed line is the largest cloud floor measured

The warm series is nearly flat: 1.06 s at 61 prompt tokens, 1.84 s at 7,201. The cold series is not a series so much as a cliff — 1.27 s, 1.72 s, 3.68 s, 13.83 s.

The dashed line is the largest transport floor I measured, 476 ms, drawn to the same scale. Against the cold bars it is almost invisible. That is the finding, and it is the opposite of what I set out to test: from a city that is genuinely far from every cloud region, the network is not what makes local inference attractive or unattractive. It is a rounding error next to what the laptop does with a prompt it has not seen.

Where the Cold Time Actually Goes

Prefill vs. generation, same three runs — output held fixed at 200 tokens throughout

Generation barely moves: 970 ms at 72 prompt tokens, 1,641 ms at 7,212. Output length was held fixed, so it should not move much, and the rise is the cost of attending over a longer context. Prefill goes from 103 ms to 12,202 ms — 88% of the cold wall time at the largest prompt is the machine reading, before it writes anything.

The bar the cloud has to clear

I cannot tell you whether the cloud wins, because I did not measure cloud inference and will not guess at it. What the two measured quantities support is a threshold. A provider starting F ms behind must deliver the same 200 tokens in under local − F to be worth the trip:

Tokens/sec a Cloud Service Must Sustain to Match the Laptop

Derived from a measured transport floor and a measured local time — no provider's generation rate was measured

Prompt tokensvs. cold laptopvs. warm laptop
~65210 tok/s269 tok/s
~485142 tok/s264 tok/s
~1,83059 tok/s248 tok/s
~7,20515 tok/s131 tok/s

Against a warm laptop the bar is high everywhere — 250 to 270 tok/s across most of the range, which is demanding for a frontier model streaming to Kathmandu. Against a cold one at 7,200 tokens the bar is 15 tokens per second, which is not a bar at all.

So the crossover is not a prompt size and it is not a distance. It is a state:

  • Long context you are still working in — the laptop is hard to beat, and 315 ms of network is the least of the cloud's problems.
  • Long context the model has never seen — the laptop spends twelve seconds reading before it starts, and any real provider clears 15 tok/s without noticing.

Which means the practical advice from a slow-network city is the opposite of the intuitive one. Do not choose local because the network is far. Choose local when you are staying in one context long enough to amortise the read.

What this does not say

It does not say a 1B model is a substitute for a frontier one. llama3.2:1b-q4_K_M is 807 MB and the comparison here is quality-blind — 1.1 seconds of a small model and 1.1 seconds of a large one are not the same product, and no threshold above should be read as a recommendation to use the small one for work that needs the large one. Every number is one machine, one residential ISP in Kathmandu, one morning. A 7B model would move every local figure and none of the network ones, which would push the crossover further toward the cloud in both states.

And the cloud column is a floor, not a cost. Nothing here measures a successful API call.

The three runs taken before the cold/warm split are not in that directory. They were the ones the cache had quietly contaminated, and publishing them beside the corrected ones would have put two incompatible definitions of "local wall time" in the same folder under the same field name. They are described in the README and nowhere else, which is the right amount of visibility for a measurement that was wrong.

Sources

  1. Crossover run logs — three runs, four prompt sizes, two cache statesThis repository, 2026-08-28data/crossover/*.json — every figure in this post is computed from these files by scripts/bench/analyse-crossover.mjs.
  2. Local inference vs. the network — dataset READMEThis repositorydata/crossover/README.md — the field dictionary, the cold/warm design, and the six limitations including the two that matter most.
  3. ollama API reference — prompt_eval_count, prompt_eval_duration, eval_count, eval_durationollamaWhy prefill and generation are separable here at all: exact token counts over nanosecond-resolution durations, rather than a wall-clock estimate.
  4. curl manual — the --write-out timing variablescurltime_appconnect and time_starttransfer, whose difference is the post-handshake field this post leans on.
  5. How Far Is Kathmandu From the Cloud?This siteThe earlier measurement this one extends, and the source of the caveat about CDN-fronted API endpoints that the post-handshake field was built to answer.

Advantages

  • The transport floor is measured with no credentials at all — an uncredentialed request returns a 401, so the timing provably contains no inference, and anyone can reproduce it without an account or a bill
  • Separating the two cache states turned a single misleading average into two numbers that each describe a real situation, and the labels check themselves: cold rows prefill at 590–780 tok/s, warm rows report up to 234,000 tok/s, which is not a speed any silicon produces
  • The post-handshake field answers a caveat the earlier Kathmandu latency post could only flag — it measures how much further back the response came from once the near CDN edge had finished pretending

× Challenges

  • No cloud inference is measured here at all, so the result is a threshold a provider must clear, not a comparison to any provider — a reader with an API key can complete it; this post cannot
  • The comparison is quality-blind. A 1B model answering in 1.1 seconds and a frontier model answering in 1.1 seconds are not the same product, and nothing measured here says otherwise
  • One ISP, one morning, one small model. A 7B model would move every local number and none of the network ones, and the Gemini floor swung threefold in an earlier run before settling

Risk Assessment

A benchmark that reuses one prompt measures its own cache. The first version of this harness did exactly that and reported a cold 7,201-token call as 1.8 seconds when it is 13.8 — a seven-fold error, in the direction that flattered the conclusion I expected. Nothing in the output looked wrong; the run completed, the numbers were stable across reps, and the trend had the right shape. It was caught only because a derived rate came out physically impossible. Any local-inference benchmark that does not state its cache state is reporting one of these two numbers without knowing which.

Abhishek Kushwaha

Written by Abhishek Kushwaha

Full-stack software engineer in Kathmandu, Nepal — six years shipping production Django and Next.js systems, most recently at Pinakin Technologies & Research Center. Writes Global Tech Search on what the AI buildout costs in power, water and silicon, measuring it first-hand where he can. More about the author →