Skip to content

kvstore: stamp checkpoints with a behavioral tokenizer fingerprint - #1020

Open
nazerim wants to merge 2 commits into
antirez:mainfrom
nazerim:feat/kvstore-tokenizer-fingerprint
Open

kvstore: stamp checkpoints with a behavioral tokenizer fingerprint#1020
nazerim wants to merge 2 commits into
antirez:mainfrom
nazerim:feat/kvstore-tokenizer-fingerprint

Conversation

@nazerim

@nazerim nazerim commented Sep 10, 2026

Copy link
Copy Markdown

Supersedes #983 (GitHub refuses reopen after the rebase force-push; same branch, rebased onto current main - merge cost was one trivial hunk).

Problem. A checkpoint saved by a different tokenizer build (or a different gguf vocab - both happened to me in production: a model-file migration produced four foreign-vocab restores that the text-keyed tiers trusted, each one a multi-thousand-token silent corruption or a pinned re-prefill loop) restores without complaint and then poisons every following turn.

Fix. The engine computes ds4_engine_tokenizer_fingerprint() once: a hash over n_vocab, every token's length+bytes in id order, the merge-rank table contents, and a small append-only behavioral probe set run through the real tokenizer - covering data and pre-tokenizer code change with no human-maintained version constant. Every save writes it as the first trailer section (TOKFP, auxiliary ext bit 1<<5, so key_kind and existing consumers are untouched); the loader pre-checks it with a 16-byte seek before touching the payload and rejects mismatched files, leaving the session untouched so the ordinary fallback chain walks to the next candidate or a cold rebuild, which rewrites fresh under the key. Unstamped legacy files remain trusted (status quo ante).

Design note carried from #983's review: @JordiPosthumus caught that the first version hashed the raw ds4_str array (pointers) - restart-stable on my machine by luck, wrong on any other allocator layout. The shipped version hashes content only; regressions: two fresh processes print identical fp (e467e62ceeadf47e), a single-byte flip in a clone of the model file changes it (5f83c7a25a3b184e).

Validation (M5 Max, optimized build clean, ds4-server unit suite green on the rebased head): production soak since #983: the stamp paid for itself during a model-file migration - one bounded cold rebuild, zero silent corruption after. Cross-restart restore of a 32768-token grid: 69 ms, zero mismatches.

Persisted KV checkpoints encode their conversation as a token history
whose bytes, on future requests, are re-tokenized by whatever tokenizer
the running engine has. When tokenizer code or data changes upstream
(the JoyAI pre-tokenizer has seen several rule fixes), existing
checkpoint token histories become unreproducible: every later request
diverges from the restored session at the first changed merge
boundary, reloads the same stale file by text key, and re-imports the
stale tokenization. The visible symptom is a permanent per-turn
live-miss + disk-reload + partial-re-prefill loop for every session
resumed with old checkpoints (observed in production: 29 consecutive
turns, ~38k tokens re-prefilled each turn, session permanently pinned
to a stale frontier).

A text-equality check at load cannot detect this class without also
rejecting legitimate preserved-reasoning continuations, whose stored
sampled tokens intentionally differ from a whole-text re-tokenization
of their bytes - the bridge's premise. The correct discriminator is
the engine identity that produced the tokens.

This commit adds a behavioral fingerprint: an append-only set of probe
strings (apostrophe-punctuation seams, digit runs, indentation,
newline joins, UTF-8 letters, special-token edges) is run through the
real tokenizer once, and the resulting ids hashed together with the
token table. It covers tokenizer data AND code changes with no
human-maintained version constant. Checkpoint trailers now begin with
a TOKFP section carrying the fingerprint; the loader pre-checks it by
a 16-byte seek to the trailer, before touching the payload (session
untouched), and mismatches fall back to the normal cold path, which
rewrites a fresh stamped file under the same key. Unstamped legacy
files keep today's trusted behavior.

Verified in a live agent session: stamped files restore at full depth
(451k tokens, 38-token re-prefill); a synthetic probe change (simulated
upstream tokenizer edit) makes every old stamped file reject in
microseconds with a clear warning and the session self-heals on the
next store.
ds4_str holds a pointer and a length; hashing the raw array therefore
hashed process mapping state, not tokenizer data. Same-binary restarts
can reproduce the allocator layout by luck, but any different mapping
would silently change the fingerprint and reject valid stamped
checkpoints after an ordinary restart - the opposite of the feature's
purpose.

Hash content instead: n_vocab, then every token's length + bytes in id
order, then the merge-rank table (used/cap plus each used slot's key
bytes and rank; open-addressed slot order is a deterministic function
of the GGUF merge list). The behavioral probes still ride on top, so
code changes are covered even when data is not.

Validated live (Apple M5 Max): fingerprint e467e62ceeadf47e identical
across separate process launches of the unchanged model, and a stamped
checkpoint restored at full depth (32768-token grid, 69 ms load)
across a stop/start cycle with zero mismatch lines. A single-byte
token mutation on an APFS clone of the model (TABLE->TASLE, same
length, outside every probe string) moves the fingerprint to
5f83c7a25a3b184e, proving data sensitivity independent of probe
coverage.
nazerim added a commit to nazerim/ds4 that referenced this pull request Sep 10, 2026
 on rebased heads), 1M soak watchlist, bridge-tagger removal candidate
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