DeepSeek Harness usage numbers wrong: cache, context, tok/s
On one machine, the cache hit rate reads 49.9% while the gateway backend says 99.8%; context usage reads 69.1% while the real prompt is 34.6%; tok/s occasionally jumps to 41,188 while the engine itself reports about 150. These three numbers are not one bug but three separate counting problems. Reconciliation matters because it separates "display is wrong" from "judgment is wrong", and a wrong judgment kills an entire turn (#10627, #11390).
DeepSeek Harness: Reconciliation 1: 49.9% vs 99.8%
The denominator is counted twice: input already contains cache reads, and DSH adds it to cacheRead again, halving the hit rate.
- Send three identical requests in a row with a large, stable system prompt.
- Record
inputandcacheReadfor each turn: the community measuredinput=2433andcacheRead=2432all three times. - The key observation: on a cache hit
input_tokensdoes not decrease, which means it is a total, not an uncached amount. - DSH displays
2432 / (2433 + 2432) ≈ 50%, while the real value should be2432 / 2433 ≈ 99.96%. - Long sessions agree:
input=312035,cacheRead=311680shows 49.9% while the actual rate is 99.9%.
The convention is written in packages/llm/llm/src/types.ts: counts are disjoint, and adapters must subtract cache back out when a provider folds it into total prompt. mapUsage() in llm-pi-ai only handles reasoning folded into output and does no subtraction.
DSH plugin: Reconciliation 2: 69% vs 34.6%, and 1.2M judged over a 1M window
The same folding runs the other way in the judgment path: compared with the window it turns a real 600k into 1.2M and declares overflow, and compared with the threshold it compacts early.
- Check the measured numbers:
inputTokens=345581,cacheReadTokens=345216,pressureTokens=690797(69.1%), while the real prompt is 345581 (34.6%). - Look at overflow detection:
isContextOverflowsumsinput + cacheReadwhenstopReasonis stop. - So a real 600k is judged 1.2M, over the 1M window, mapped to
CONTEXT_WINDOW_EXCEEDED, and the turn ends. - Worse, the answer was already fully generated (
stopReason: "stop") and is still discarded wholesale; the community counted single cases of 1,411 and 1,318 characters. - Retrying cannot escape: the next turn judges the same history again, forming a stable deadlock.
The community also proposed an independent fallback: when stopReason is stop and content is non-empty, it should never be judged an overflow — a genuinely overflowing request never receives a complete answer.
DeepSeek Harness: Reconciliation 3: threshold 800,000 vs real limit 664,576
The threshold formula is fine, but the output reserve is treated as 0: threshold = floor(min(W×0.8, W−O−H)) yields 800k with O missing, while the real input limit is only 664k.
- Derive the limit from the upstream error:
1048576 − 384000 = 664576. - Apply the formula: with
W=1,000,000,O=0,H=65536, you getmin(800000, 934464) = 800,000. - That leaves a 135,424-token gap: a session must cross it before compaction triggers, and crossing it is exactly what kills the session.
- The root cause is two inconsistent paths: the request does reserve 384,000 (the model's
maxTokens), but the budgeter counts 0 because the adapter fillsdefaultMaxTokensonly when the profile explicitly declaresmodels[].maxTokens. - Corrected, the threshold should be
min(800000, 550464) = 550,464, the value that actually works.
DSH Plugin: Reconciliation 4: tok/s 41,188 vs the engine's 150
For tool-call steps, decodeMs covers only "first to last stream chunk", and those chunks are flushed at once when the call completes with nearly identical timestamps; the numerator is the whole step's tokens.
- Pick a tool-call step and read event timestamps:
step/starttoassistant/messageis a real 1,390 ms gap. - Look at the internal chunk timestamps: they differ by about 4 ms, while
usage.outputTokens = 103. - So
103 / 0.004 ≈ 25,750 tok/sgets reported for a step that actually took 1.39 seconds. - Statistical signature: across 7,584 tool-call steps, only 0.3% to 0.7% had
span < 5msandoutput >= 50, all of them tool-call steps, with zero pure-text steps. - So it looks intermittent but is a counting error with a clear trigger.
In the session-stats projection session-stats/src/projection.ts, decodeMs accumulates from the first chunk time while decodeTokens is the whole step's output; the two do not cover the same interval. There are two fix directions: change the denominator to the step/start to assistant/message envelope, or rename the field to "whole-step tokens per second" and label it honestly.
DeepSeek Harness: The four tables describe two classes of problem
Display errors are only ugly; judgment errors lose answers. Classify with this table first, then decide whether to act.
| Symptom | Number check | Nature | Impact |
|---|---|---|---|
| Cache hit rate about 50% | 2432/(2433+2432) | Display error | Cosmetic only |
| Usage doubled | 690797 vs 345581 | Display error + early compaction | Context truncated for nothing |
| Complete answer judged overflow | 1.2M vs 1M | Judgment error | Turn fails, answer discarded |
| Threshold above the limit | 800000 vs 664576 | Config/reporting gap | Compaction cannot save it |
| tok/s in the tens of thousands | 103 / 0.004s | Display error | Cannot compare across runs |
DSH plugin: Order of handling: start with what you can change
First confirm whether the gateway folds, then decide between changing config and waiting for a patch; fix judgment errors first.
- Classify the gateway: send two identical requests; if
input_tokensdoes not drop on a hit andinput ≈ cacheRead, it is a folding one. - If you can switch protocols, do: run the same model on a non-folding route (such as the standard Anthropic endpoint or
llm-deepseek) and all three problems disappear. - If you cannot switch, wait for an adapter patch, or subtract
cacheReadfrominputin your own adapter layer and recomputetotalTokens. - For the threshold problem, explicitly declare
models[].maxTokensin the route or model config sodefaultMaxTokenshas a value. - Treat tok/s as reference only; do not use it to compare models or reasoning levels.
Cautions
Do not use these readings for billing or model comparisons.
- Do not use these numbers to judge billing. Providers bill by their own
input_tokens; DSH is simply reading them wrong. - Do not fix only half. Usage and threshold point in opposite directions, so fixing one side leaves the other wrong.
- Do not treat "stop with complete content" as a normal overflow. That combination is self-contradictory and works as an independent fallback condition.
- Record the gateway and protocol. The same symptom means different things behind different gateways, and reports must include both.
- Fixing tok/s requires a core change. Replacing the display on the client only hides a value that is still wrong underneath.
If your gateway is the standard Anthropic endpoint, none of these four should appear — go investigate "model returns something abnormal" instead.See DeepSeek Harness compaction: early trigger and stuck switch for the related case.
FAQ
The gateway's 99% is more trustworthy than the 50% that DeepSeek Harness displays. DSH adds uncachedInputTokens + cacheReadTokens + cacheWriteTokens as the denominator, and a folding gateway's input_tokens already includes cache reads, so the denominator doubles and the rate is halved. Two identical requests in a row confirm it.
DeepSeek Harness doubles the figure through the same folded sum: pressureTokens equals inputTokens + cacheReadTokens + cacheWriteTokens, and with folding the input already contains cacheRead, so it is counted twice. The doubled display also makes the compaction threshold fire early.
DeepSeek Harness discards the answer because its silent overflow detection compares input + cacheRead with the window, and after folding that sum approaches twice the real prompt. A real 600k is judged as 1.2M over a 1M window, so the whole turn fails and the generated answer is discarded.
The threshold comes out higher in DeepSeek Harness because the route reports no output reserve. The formula is floor(min(W×0.8, W−O−H)), and with O (reservedCompletionTokens) treated as 0 it yields 800,000 while the real input limit is 664,576 — a 135,424 gap.
No, the model did not really get that fast: for tool-call steps in DeepSeek Harness, decodeMs starts at the first stream chunk, but those chunks are flushed all at once when the call completes, with nearly identical timestamps; the numerator is the whole step's tokens, so a few milliseconds as denominator produces tens of thousands.
Related Terms
- DISJOINT counting convention
- The DISJOINT convention is DSH's TokenUsage rule that inputTokens contains only uncached input while cache is counted separately as cacheReadTokens / cacheWriteTokens. Adapters must subtract cache back out for providers that fold it into total prompt.— https://github.com/deepseek-ai/deepseek-harness/discussions/10627
- cacheReadTokens
- cacheReadTokens is the number of input tokens that hit cache this turn, counted separately from uncached input. Folding gateways merge the two inside input_tokens, doubling every sum-based metric.— https://github.com/deepseek-ai/deepseek-harness/discussions/10627
- reservedCompletionTokens
- reservedCompletionTokens is the output reserve in the compaction threshold formula, taken from the request's maxTokens or the model's defaultMaxTokens. When a route does not report it, it is treated as 0 and the threshold comes out too high.— https://github.com/deepseek-ai/deepseek-harness/discussions/11050
- decodeMs
- decodeMs is the accumulated decode time field in session statistics, currently measured from the first stream chunk to the last. Tool-call steps compress those chunk timestamps, making the denominator tiny.— https://github.com/deepseek-ai/deepseek-harness/discussions/11390
Sources
- #10627 — the gateway folds cache reads into input_tokens, causing a false context overflow· deepseek-ai (GitHub Discussions)
- #11050 — Catalog-backed pi-ai routes report no output reserve (threshold 135k too high)· deepseek-ai (GitHub Discussions)
- #11390 — tok/s reading inflated by orders of magnitude on tool-call steps· deepseek-ai (GitHub Discussions)
- deepseek-ai/deepseek-harness (source repository)· GitHub