server: thinking-frontier bridge + vision encoder result cache - #968
server: thinking-frontier bridge + vision encoder result cache#968nazerim wants to merge 4 commits into
Conversation
|
I audited the exact current head ( Model-free validation was clean: default and CPU builds, server and agent tests, the remaining aggregate regression targets, ASan/UBSan server tests, and I found two concrete issues worth fixing before endorsement:
One scope concern: automatically discarding older images is an intentionally lossy API policy, whereas the thinking bridge and encoder cache are cache/correctness mechanisms. I think the image-retention policy would be easier to review as a separate PR, ideally explicit/opt-in. The fixed 512 MiB process cache is modest on very large hosts but may also deserve a short default rationale or configurability for smaller machines. The preserved-thinking construction and image-marker wildcard otherwise look conservatively gated: exact visible bytes, matching live frontier/vision state, and refusal when a new marker appears in the suffix. I would be happy to rerun the same matrix after an update. Thanks for putting real production traces behind this work. |
Plain (no tool-call) thinking turns in a tool-context conversation were excluded from the thinking-live checkpoint bridge by the has_tools gate, while tool-call turns have their own replay path. The live graph then diverges permanently from every faithful client replay at the think-token boundary (sampled open-think+reasoning vs replayed bytes, plus BPE re-merge differences across block boundaries), forcing full cold re-prefills on every later turn - observed in production as live kv cache misses with the common prefix stuck around the first thinking block, drifting slightly each turn. Remember the exact bytes the next replay will render for the turn: prompt + sampled reasoning + close-think + visible content + eos. The next request hits the thinking-visible path and rebuilds the effective prompt from the live token prefix, immune to re-tokenization drift. Applies to tool-context and prompt-preserves-reasoning replays; the toolless visible- only bridge is unchanged. Truncated (finish=length) turns still miss by design.
65ad364 to
75c92a6
Compare
|
Thanks for the careful audit — all three points were right. Update:
This PR is now two commits: the thinking-frontier bridge and the encoder |
75c92a6 to
e5bcf15
Compare
|
Follow-up on my previous comment — the head moved twice since (now
Unit suite green on bare main for both branches. Matrix rerun welcome |
|
I re-audited the exact revised head One correctness gap remains in the vision embedding cache. The current lookup treats I prepared one focused commit directly on
The hash remains the fast filter, so misses do not gain an extra full comparison; only a matching candidate receives the exact check. Please feel free to cherry-pick or adapt the commit. Packaging suggestion, not a correctness blocker: the two runtime ideas are independently useful. The easiest review shape may be one PR for the thinking-frontier bridge plus image-marker wildcard, and another for the exact-keyed encoder cache. If they remain together, moving the marker wildcard into the bridge commit would at least make each commit independently coherent. No production source, model, cache, launcher, or running service was changed for this review. A fresh real Vision/Pi run should follow only after the submitted head settles. |
Every multimodal request re-encoded ALL images in its history through the vision encoder, on every turn, even when the live KV prefix hit and the image bytes were unchanged - pure wasted GPU work that grows linearly with image count in 200k-300k context agent sessions. Cache the encoder result keyed by a 128-bit hash of the encoded bytes (fixed-size table, LRU; the byte budget counts the embedding plus its exact key and is a ceiling, not a reservation). Encoder output is deterministic for identical bytes on the same engine; hits are copied out fresh so request ownership semantics are unchanged. Log the reuse ratio per request at the KV-cache level, with all counter snapshots taken under inference_mu so concurrent requests cannot attribute each other's hits. Also wildcard the image-marker nonces in visible-prefix matching. The request-scoped image sentinel carries a random nonce, so a remembered visible transcript could never byte-prefix-match a future replay of the same multimodal history - the thinking bridge from the previous commit was silently dead for image conversations. Nonces are fixed length, so treat the 24 hex bytes at equal offsets as wildcards; a guard refuses the bridge when the rebuilt text suffix would carry new markers.
e5bcf15 to
b49c173
Compare
|
Cherry-picked your Also ported to our fork (same commit, 128-slot/512 MiB variant of the constants) and our production server is now running the exact-bytes-verified build. |
|
I found one more boundary while preparing the GB10 real-model comparison of A concrete model-free reproduction on our production-shaped port has zero images, a remembered visible prefix containing I prepared a smaller alternative to the wildcard in commit 8945827:
This removes the generic wildcard scanner. Focused tests cover changed literals without images, a literal beside a genuine normalized marker, stable replay, shifted/shortened prefixes, and idempotence. Default build/server tests, CPU build, ASan+UBSan server tests and The GB10 model comparison is staged but has not run yet: its existing production request is still draining. No speed claim or production endorsement yet. I'll update this comment with the real-model evidence, including whether encoder hits preserve every embedding value and whether the thinking/image continuations retain their KV frontier. |
Normalize nonce bytes only after matching the parser-generated image marker and appending its vision tokens. Keep ordinary byte-prefix matching for replay keys, so literal marker-shaped user or tool text is never a wildcard. No token, embedding, image identity or cache payload changes.
|
Cherry-picked Verified on One port note for our fork variant: the marker-shape scanner stays there, since the disk-key normalizer uses it to substitute image fingerprints for nonces (nonce-independent either way, so disk keys remain compatible with snapshots written before this change). Only the thinking-tier matcher switched to exact bytes. Our production server runs Standing by for the GB10 real-model evidence — and no rush; the current heads are stable. The 2-PR packaging offer (bridge+wildcard vs exact-keyed cache as separate PRs) remains open if your real-model pass wants them decoupled. |
Two independent server-side fixes from running the Vision-Exp model as a
coding-agent backend (pi.dev / OpenAI-compatible tool loops, 50k-300k
contexts). Both rebase and test cleanly on current main; neither depends
on image disk caching or on #927.
Provenance note: our fork (nazerim/ds4) carries @JordiPosthumus's #927
cherry-picked as commit 43ef837 (reuse KV when tools append images); it is
a prerequisite for our separate multimodal disk-cache work, which we plan
to submit once #927 merges. This PR is deliberately independent of #927 -
it rebases and tests cleanly on current main without it - so the two can
land in any order.
Bridge thinking turns in tool conversations to replay form.
Plain (no tool-call) thinking turns in a tool-context conversation were
excluded from the thinking-live checkpoint bridge by the has_tools gate.
The live graph then diverges permanently from every faithful client
replay at the think-token boundary (sampled open-think+reasoning vs
replayed bytes, plus BPE re-merge differences across block boundaries),
forcing full cold re-prefills on every later turn. Production symptom:
repeated live kv cache misses with the common prefix stuck at the first
thinking block (~30k tokens), drifting slightly right each turn. The fix
remembers the exact bytes the next replay renders (prompt + sampled
reasoning + close + visible + eos); the next request rebuilds from the
exact live token prefix, immune to re-tokenization drift. Truncated
(finish=length) turns still miss by design.
Vision encoder result cache.
Every multimodal request re-encoded ALL history images through the ViT
on every turn, even on live KV hits. Results are now cached by exact
identity of the encoded bytes: entries carry the full encoded image, the
128-bit hash is only a fast candidate filter, and a hit additionally
requires memcmp over the complete bytes, so a collision is an ordinary
miss, never a wrong-image embedding (LRU, fixed table). The cache is a ceiling, not
a reservation: memory only materializes for embeddings actually produced
and a session that never encodes images costs nothing; with 64 slots and
a few-MiB vision embedding the table is the binding limit (worst case
~205 MiB), so the byte budget was set at 256 MiB - just above it as a
safety net for larger embeddings (both constants are compile-time). Hits
are copied out fresh, so request ownership semantics are unchanged.
Authenticated-marker canonicalization is included (@JordiPosthumus's
commit, adapted): the request-scoped image sentinel carries a random
nonce, so a remembered visible transcript could never byte-prefix-match
a future replay of the same multimodal history - the bridge was silently
dead for image conversations. Instead of wildcarding marker-shaped text
at match time (which would let literal marker-shaped user/tool content
forge a frontier match when it later changed), the server canonicalizes
the nonce bytes of a marker only after tokenization has located it via
its known generated nonce; replay matching stays exact byte-prefix, so
arbitrary marker-shaped text remains exact data. A guard still refuses
the bridge when the rebuilt text suffix would carry new markers.
Review response (three rounds with @JordiPosthumus: stats race fixed,
lossy scope split to #971, budget right-sized to 256 MiB; his 8f003c3
exact-byte hit verification and 8945827 marker canonicalization are
cherry-picked with authorship preserved):
after-snapshots (hits, bytes) are all captured inside inference_mu - the
same critical section as the cache traffic they observe - and passed as
immutable values to the logger, which reads no shared state. Concurrent
requests can no longer attribute each other's hits, and the delta can no
longer underflow.
its own opt-in PR (lossy API policy reviewed separately); the
evidence-mismatch finding was a symptom of that mixing and is gone.
Verification: ds4-server unit suite extended (preserved-canonical matches
future replay; authenticated-marker canonicalization matrix (literal marker-shaped text stays exact); embed cache store/hit/evict) plus
live tool+image conversations: cached 563/575 and 713/725 multimodal
continuations (previously full cold rebuilds), encoder reuse
encoder_runs=0 on replays. Those live numbers were gathered on our fork
build carrying the same bridge/cache code; the submitted head passes the
same unit suite on bare main.