Skip to content

fix(deepseek): fence resident decode-metadata refreshes with a verified readback - #196

Draft
ndleslx wants to merge 1 commit into
hw-native-sys:mainfrom
ndleslx:fix/deepseek-metadata-upload-fence
Draft

fix(deepseek): fence resident decode-metadata refreshes with a verified readback#196
ndleslx wants to merge 1 commit into
hw-native-sys:mainfrom
ndleslx:fix/deepseek-metadata-upload-fence

Conversation

@ndleslx

@ndleslx ndleslx commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Fixes the intermittent
test_deepseek_v4_http_completion_matches_expected_text[k1-prefix-cache] failure
introduced by #144 — an alternative to reverting #144 (#186): keep the residency
win, close the ordering hole.

Root cause

#144 keeps the per-(ping-pong slot, rank) decode metadata device-resident behind
a dirty key, uploading changed shards with copy_to ordered against the
previous dispatch (the predecessor protocol). Nothing orders an enqueued H2D
copy against the next dispatch that reads those shards — the consuming
dispatch is submitted microseconds later with no device-side dependency on the
copy. When the copy loses that race, the fused kernel computes one step against
the previous content of the slot's page mappings: a plausible-but-wrong token
with no error signal.

Evidence — main run 32468071303
(the failing #144 merge), k1-prefix-cache case, which runs the same prompt
twice and compares the completions:

  • run 0 (cold): a leading global information and communications technology (ICT)
  • run 1 (prefix-cache hit): a leading global **provider of** information and communications technology (

A single wrong token at step ~4 of run 1, then a coherent continuation — one
step computed on stale mappings, not cascading corruption. The prefix-cache case
is the exposed one because its second request forces a key change on both slots
at once (copy-on-write detaches the shared rolling page, so the tail block ID
changes exactly where a fresh dispatch follows the re-upload), and because it
compares two identical generations token-for-token. The same race exists in
k1-fused at slot 1's first use; it just usually wins. Intermittency is
queue-timing: #144's own PR run, #174, and #191's merge run all passed with the
same code.

The fix

Two edges closed in _sync_decode_device_metadata_rank:

  1. Verified readback fence. After uploading a rank's shards, read them back
    (copy_from blocks on data arrival) and compare bytes against the staged
    source; the device key is marked clean only on a match. This is correct
    regardless of how the device orders control copies against program execution
    — no assumption about copy/run channel topology. A mismatch re-uploads (up to
    3 attempts); a device that keeps contradicting its own readback fails the job
    loudly instead of computing on stale page tables.
  2. Per-slot predecessor. The refreshed shards are read by this slot's own
    previous dispatch, which under concurrent dispatch frames may outlive the
    newest dispatch the global predecessor tracks. The submit path now records
    the per-slot dispatch; wait() is idempotent so waiting it too is free.

The verify scratch is one pre-fork shared buffer sized to the largest per-rank
metadata shard, allocated next to the sources it verifies (a few KB; syncs are
serialized by the control lock).

Steady state unchanged. The fence runs only on dirty ranks — key changes
(request boundaries, page crossings) — so #144's measured decode-loop win
(38.8 → 38.0 ms trimmed mean) is kept where it was measured; dirty steps pay a
KB-scale readback.

Tests

The metadata tests' fake worker now models the actual hazard: copy_to enqueues
without landing, and only a copy_from drains the queue (per worker, FIFO), with
a knob for a readback that loses the race:

  • the existing residency/dirty-rank test now asserts the wait → copy → readback
    fence per dirty rank, including the per-slot predecessor wait;
  • a readback that loses the race (device still holds old bytes) is detected,
    re-uploaded, and the key marked clean only after a matching readback — a
    repeated sync with the same key stays a no-op;
  • a readback that never lands fails loudly after 3 attempts and leaves the key
    dirty.

Full local suite: 208/208 unit tests, ruff, headers, english-only clean.

If this lands and the guards stay green, #186 (the revert) should be closed as
superseded.

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 72eac092-357f-4ea3-814c-0fb769934b04


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…ed readback

hw-native-sys#144 made the per-(ping-pong slot, rank) decode metadata device-resident
behind a dirty key, uploading changed shards with copy_to and ordering
those uploads only against the *previous* dispatch via the predecessor
protocol. Nothing ordered an enqueued H2D copy against the *next*
dispatch that reads the shards, so when the copy lost that race the
fused kernel computed one step against the previous content of the
slot's page mappings: a plausible-but-wrong token with no error signal.
The prefix-cache accuracy guard caught it intermittently -- its second
request forces a key change on both slots at once (copy-on-write detaches
the shared rolling page), and it compares the two runs' completions
token-for-token (main run 32468071303: ' a leading global provider of
information ...' vs ' a leading global information ... (ICT)').

Two edges closed:

- Verified readback fence: after uploading a rank's shards, read them
  back (copy_from blocks on data arrival) and compare bytes against the
  staged source; mark the device key clean only on a match. Correct
  regardless of how the device orders control copies against program
  execution. A mismatch re-uploads; three failures stop the job. The
  steady-state path (clean keys) is untouched -- the fence runs only on
  dirty ranks, so the decode-loop win hw-native-sys#144 measured is kept.
- Per-slot predecessor: the refreshed shards are read by this slot's own
  previous dispatch, which under concurrent dispatch frames may outlive
  the newest dispatch the global predecessor tracks. wait() is
  idempotent, so waiting the slot's dispatch too is free.

The verify scratch is a pre-fork shared buffer sized to the largest
per-rank metadata shard, allocated with the sources it verifies. Unit
tests model an async copy worker whose H2D lands only when a D2H drains
it, including a readback that loses the race (retry then clean) and one
that never lands (fail loudly, key stays dirty).
@ndleslx
ndleslx force-pushed the fix/deepseek-metadata-upload-fence branch from 6f7516f to e49b81e Compare August 26, 2026 02:26
@ndleslx

ndleslx commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator Author

Status: this does not fix the failure — hypothesis falsified by this run.

The guard failed again with this fence active
(run 32922716852),
same signature but with two new facts:

  1. The cold run diverged this time (run 0: a leading global provider of information …, run 1 — the prefix-cache hit — correct). In the original main
    failure (32468071303)
    it was run 1. So the corruption is not tied to the cached second request; it
    hits either generation, always the same deterministic wrong token ("provider"
    at completion position ~4).
  2. The fence ran (every slot's first staging is dirty) and never raised — a
    refresh that failed verification 3× would have failed the job loudly. So the
    resident metadata the dispatch read was byte-verified against the staged
    source at refresh time. (CI sets PYPTO_RUNTIME_LOG=error, which hides the
    mismatch warnings, but not the raise — the strong claim stands.)

Together with k1-fused (128 tokens, no prefix caching, fixed expected text)
passing in every failing run, the evidence now points away from the metadata
residency race
and toward something specific to a server started with
enable_prefix_caching=True (or to that case's highly repetitive prompt), inside
#144's other device-residency changes (verifier-committed buffers / MTP state)
or their interaction with grouped prefix-cache block ownership.

Recommendation: merge #186 (the revert, green) to unbreak main and treat this
PR as (a) hardening that is correct and cheap if/when #144 is re-landed, and (b)
the falsification record for the metadata-race theory. Next diagnostic step if
we keep digging: run the k1-prefix-cache case in a repeat loop with INFO
logging (accept rates + fence warnings visible) and A/B against #186's tree —
one CI sample per run is too slow to localize an intermittent, deterministic-
when-it-fires corruption.

@ndleslx
ndleslx marked this pull request as draft August 26, 2026 03:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant