ChatGPTClaudeGrok / X / CursorGemini

DeepSeek Harness usage numbers wrong: cache, context, tok/s

TroubleshootingPublished 2026-10-30Author: DeepSeek Plugin Market
DeepSeek HarnessDSHtokencache hit ratecontext usagetok/scompaction threshold
DeepSeek Harness shows 49.9% cache hits when the gateway says 99.8%, 69% context when it is 34.6%, and four-digit tok/s. Four reconciliations and fixes.

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.

  1. Send three identical requests in a row with a large, stable system prompt.
  2. Record input and cacheRead for each turn: the community measured input=2433 and cacheRead=2432 all three times.
  3. The key observation: on a cache hit input_tokens does not decrease, which means it is a total, not an uncached amount.
  4. DSH displays 2432 / (2433 + 2432) ≈ 50%, while the real value should be 2432 / 2433 ≈ 99.96%.
  5. Long sessions agree: input=312035, cacheRead=311680 shows 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.

  1. Check the measured numbers: inputTokens=345581, cacheReadTokens=345216, pressureTokens=690797 (69.1%), while the real prompt is 345581 (34.6%).
  2. Look at overflow detection: isContextOverflow sums input + cacheRead when stopReason is stop.
  3. So a real 600k is judged 1.2M, over the 1M window, mapped to CONTEXT_WINDOW_EXCEEDED, and the turn ends.
  4. 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.
  5. 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.

  1. Derive the limit from the upstream error: 1048576 − 384000 = 664576.
  2. Apply the formula: with W=1,000,000, O=0, H=65536, you get min(800000, 934464) = 800,000.
  3. That leaves a 135,424-token gap: a session must cross it before compaction triggers, and crossing it is exactly what kills the session.
  4. 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 fills defaultMaxTokens only when the profile explicitly declares models[].maxTokens.
  5. 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.

  1. Pick a tool-call step and read event timestamps: step/start to assistant/message is a real 1,390 ms gap.
  2. Look at the internal chunk timestamps: they differ by about 4 ms, while usage.outputTokens = 103.
  3. So 103 / 0.004 ≈ 25,750 tok/s gets reported for a step that actually took 1.39 seconds.
  4. Statistical signature: across 7,584 tool-call steps, only 0.3% to 0.7% had span < 5ms and output >= 50, all of them tool-call steps, with zero pure-text steps.
  5. 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.

SymptomNumber checkNatureImpact
Cache hit rate about 50%2432/(2433+2432)Display errorCosmetic only
Usage doubled690797 vs 345581Display error + early compactionContext truncated for nothing
Complete answer judged overflow1.2M vs 1MJudgment errorTurn fails, answer discarded
Threshold above the limit800000 vs 664576Config/reporting gapCompaction cannot save it
tok/s in the tens of thousands103 / 0.004sDisplay errorCannot 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.

  1. Classify the gateway: send two identical requests; if input_tokens does not drop on a hit and input ≈ cacheRead, it is a folding one.
  2. 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.
  3. If you cannot switch, wait for an adapter patch, or subtract cacheRead from input in your own adapter layer and recompute totalTokens.
  4. For the threshold problem, explicitly declare models[].maxTokens in the route or model config so defaultMaxTokens has a value.
  5. 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.

  1. Do not use these numbers to judge billing. Providers bill by their own input_tokens; DSH is simply reading them wrong.
  2. Do not fix only half. Usage and threshold point in opposite directions, so fixing one side leaves the other wrong.
  3. Do not treat "stop with complete content" as a normal overflow. That combination is self-contradictory and works as an independent fallback condition.
  4. Record the gateway and protocol. The same symptom means different things behind different gateways, and reports must include both.
  5. 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.

ChatGPTClaudeGrok / X / CursorGemini

FAQ

DeepSeek Harness shows a 50% cache hit rate but the gateway backend says 99% — which is right?

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.

Context usage in DeepSeek Harness shows 69% while the real prompt is 34% — why double?

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 reports CONTEXT_WINDOW_EXCEEDED although the model answered fully — what happened?

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.

Why is the DeepSeek Harness compaction threshold higher than the provider's admission limit?

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.

tok/s in DeepSeek Harness shows numbers like 41,188 — did the model really get that fast?

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