Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1592,12 +1592,24 @@ Enable it with:
./ds4-server --kv-disk-dir /tmp/ds4-kv --kv-disk-space-mb 8192
```

The cache key is the SHA1 of the rendered byte prefix, and files are named
`<sha1>.kv`. The DS4 payload still stores the exact token IDs and graph state
for that prefix. This matters for continued chats: the model may have generated
one token whose decoded text is later sent back by a client as two canonical
prompt tokens. A rendered byte-prefix hit can still reuse the checkpoint and
tokenize only the new suffix.
Cache files are named `<sha1>.kv`, where the digest identifies the lookup-key
prefix. Text keys are the rendered prompt bytes. Conditioned keys prepend image
span identities to those bytes. The DS4 payload still stores the exact token IDs
and graph state for that prefix. This matters for continued chats: the model may
have generated one token whose decoded text is later sent back by a client as
two canonical prompt tokens. A rendered byte-prefix hit can still reuse the
checkpoint and tokenize only the new suffix.

Text and multimodal checkpoints participate in the same longest-compatible-
prefix search. Image-conditioned entries additionally record each image's token
span and a SHA-256 fingerprint of the actual floating-point conditioning rows.
If a request appends an image, a text checkpoint can therefore be reused up to
the image's start. Likewise, a checkpoint for image A can be reused when image B
is appended later. When an image changes, moves, or disappears, only a compatible
checkpoint ending no later than that image's start may be reused; a checkpoint
is never resumed from the middle of an image block. Existing text-only cache
files remain compatible.

The file is intentionally written with ordinary `read`/`write` I/O, not
`mmap`, so restoring cache entries does not add more VM mappings to a process
that already maps the model.
Expand Down
509 changes: 467 additions & 42 deletions ds4.c

Large diffs are not rendered by default.

25 changes: 24 additions & 1 deletion ds4.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,12 @@ typedef struct {
uint32_t height;
uint32_t content_width;
uint32_t content_height;
/* Decoded source-image identity, used for live same-engine matching. */
uint8_t fingerprint[32];
/* Exact vectors supplied to the language model, including DeepSeek's
* vision-sidecar sentinel vectors. Used for process-boundary cache keys. */
uint8_t state_fingerprint[32];
bool state_fingerprint_valid;
} ds4_vision_embedding;

typedef struct {
Expand Down Expand Up @@ -428,13 +433,31 @@ int ds4_session_sync_multimodal(ds4_session *s,
size_t image_count,
char *err,
size_t errlen);
/* Return true when every image already represented by the live checkpoint
* matches the supplied prompt and every additional image starts at or beyond
* the live token frontier. The caller must independently verify the token
* prefix. */
bool ds4_session_vision_prefix_matches(const ds4_session *s,
const ds4_vision_span *images,
size_t image_count);
/* Return true only when every image that conditioned the live checkpoint has
* the same token span and embedding fingerprint in the supplied prompt. */
bool ds4_session_vision_state_matches(const ds4_session *s,
const ds4_vision_span *images,
size_t image_count);
/* Inspect the exact conditioning-state identities of the live checkpoint. */
size_t ds4_session_vision_identity_count(const ds4_session *s);
bool ds4_session_vision_identity(const ds4_session *s, size_t index,
uint32_t *token_start,
uint32_t *token_count,
uint8_t state_fingerprint[32]);
/* Attach already-verified request identities after restoring a disk payload.
* Every image must end at or before the restored token frontier. */
bool ds4_session_restore_vision_identities(ds4_session *s,
const ds4_vision_span *images,
size_t image_count);
/* True while a session contains, or is actively syncing, image-conditioned
* state. Such state must not be written to the text-keyed disk KV cache. */
* state. Such state needs an image-identity-aware disk cache key. */
bool ds4_session_has_vision_state(const ds4_session *s);
bool ds4_session_rewrite_requires_rebuild(int live_len, int canonical_len, int common);
ds4_session_rewrite_result ds4_session_rewrite_from_common(
Expand Down
9 changes: 9 additions & 0 deletions ds4_image.c
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ static void ds4_sha256_final(ds4_sha256 *sha, uint8_t out[32]) {
}
}

void ds4_image_fingerprint_data(const void *data, size_t len,
uint8_t fingerprint[32]) {
if (!fingerprint) return;
ds4_sha256 sha;
ds4_sha256_init(&sha);
if (data && len != 0) ds4_sha256_update(&sha, data, len);
ds4_sha256_final(&sha, fingerprint);
}

static void ds4_image_error(char *error, size_t cap, const char *message) {
if (!error || cap == 0) return;
snprintf(error, cap, "%s", message);
Expand Down
5 changes: 5 additions & 0 deletions ds4_image.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ int ds4_image_decode_file(

void ds4_image_free(ds4_image *image);

/* SHA-256 of an exact byte sequence. Used to bind cached multimodal state to
* the conditioning vectors that were actually supplied to the language model. */
void ds4_image_fingerprint_data(const void *data, size_t len,
uint8_t fingerprint[32]);

int ds4_image_preprocess_glm53(
ds4_image_patches *out,
const ds4_image *image,
Expand Down
190 changes: 156 additions & 34 deletions ds4_kvstore.c
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ uint8_t ds4_kvstore_reason_code(const char *reason) {
}

const char *ds4_kvstore_key_kind(uint8_t ext_flags) {
if (ext_flags & DS4_KVSTORE_EXT_VISION_IDENTITY) return "vision-token-text";
if (ext_flags & DS4_KVSTORE_EXT_RESPONSES_VISIBLE) return "responses-visible";
if (ext_flags & DS4_KVSTORE_EXT_THINKING_VISIBLE) return "thinking-visible";
return "token-text";
Expand Down Expand Up @@ -843,14 +844,17 @@ static bool kv_cache_file_text_matches(const char *path, const char sha[41],
static bool kv_cache_existing_compatible(ds4_kvstore *kc, const char *path,
const char sha[41],
const char *text, size_t text_len,
int model_id, int quant_bits, int ctx_size) {
int model_id, int quant_bits,
int ctx_size, uint8_t ext_flags) {
if (access(path, F_OK) != 0) return false;
ds4_kvstore_entry e = {0};
if (!ds4_kvstore_read_entry_file(path, sha, &e)) return false;
bool compatible = e.model_id == (uint8_t)model_id &&
(!kc->reject_different_quant ||
e.quant_bits == (uint8_t)quant_bits) &&
e.ctx_size <= (uint32_t)ctx_size &&
((e.ext_flags ^ ext_flags) &
DS4_KVSTORE_EXT_VISION_IDENTITY) == 0 &&
kv_cache_file_text_matches(path, sha, text, text_len);
ds4_kvstore_entry_free(&e);
if (!compatible) {
Expand Down Expand Up @@ -993,10 +997,13 @@ bool ds4_kvstore_store_live_prefix_text(ds4_kvstore *kc,
ds4_kvstore_sha1_bytes_hex(text, text_len, sha);
char *path = ds4_kvstore_path_for_sha(kc, sha);
const uint8_t reason_code = ds4_kvstore_reason_code(reason);
uint8_t ext_flags = trailer_est_bytes > 0 && hooks ? hooks->ext_flag : 0;
if (text_override) ext_flags |= cache_text_ext;

if (kv_cache_existing_compatible(kc, path, sha, text, text_len,
model_id,
quant_bits, ds4_session_ctx(session))) {
quant_bits, ds4_session_ctx(session),
ext_flags)) {
kv_cache_rewrite_trailer(kc, path, text, hooks);
free(text);
free(path);
Expand Down Expand Up @@ -1071,8 +1078,6 @@ bool ds4_kvstore_store_live_prefix_text(ds4_kvstore *kc,

const uint64_t now = (uint64_t)time(NULL);
uint8_t h[DS4_KVSTORE_FIXED_HEADER];
uint8_t ext_flags = trailer_est_bytes > 0 && hooks ? hooks->ext_flag : 0;
if (text_override) ext_flags |= cache_text_ext;
ds4_kvstore_fill_header(h, (uint8_t)model_id, (uint8_t)quant_bits,
reason_code, ext_flags,
(uint32_t)store_tokens.len, 0,
Expand Down Expand Up @@ -1187,49 +1192,118 @@ bool ds4_kvstore_maybe_store_continued(ds4_kvstore *kc,
return false;
}

int ds4_kvstore_find_text_prefix(ds4_kvstore *kc, const char *prompt_text,
int model_id, int quant_bits, int ctx_size) {
if (!prompt_text) return -1;
const size_t prompt_bytes = strlen(prompt_text);
int ds4_kvstore_find_text_prefix_filtered(ds4_kvstore *kc,
const char *prompt_text,
int model_id, int quant_bits,
int ctx_size,
uint8_t required_ext_flags,
uint8_t forbidden_ext_flags) {
ds4_kvstore_prefix_query query = {
.text = prompt_text,
.max_tokens = UINT32_MAX,
.max_key_bytes = UINT32_MAX,
.required_ext_flags = required_ext_flags,
.forbidden_ext_flags = forbidden_ext_flags,
};
return ds4_kvstore_find_best_prefix(
kc, &query, 1, model_id, quant_bits, ctx_size, NULL);
}

int ds4_kvstore_find_best_prefix(
ds4_kvstore *kc,
const ds4_kvstore_prefix_query *queries,
size_t query_count,
int model_id, int quant_bits, int ctx_size,
size_t *matched_query_out) {
if (matched_query_out) *matched_query_out = SIZE_MAX;
if (!kc || !queries || query_count == 0) return -1;

kv_cache_refresh(kc);
int best = -1;
for (int i = 0; i < kc->len; i++) {
ds4_kvstore_entry *e = &kc->entry[i];
if (e->text_bytes > prompt_bytes || e->text_bytes > SIZE_MAX) continue;
if ((int)e->tokens < kc->opt.min_tokens) continue;
if (e->model_id != (uint8_t)model_id) continue;
if ((uint32_t)ctx_size < e->ctx_size) continue;
if (kc->reject_different_quant && e->quant_bits != (uint8_t)quant_bits) continue;
if (best >= 0) {
ds4_kvstore_entry *b = &kc->entry[best];
if (e->text_bytes < b->text_bytes) continue;
if (e->text_bytes == b->text_bytes && e->tokens <= b->tokens) continue;
size_t best_query = SIZE_MAX;
for (size_t q = 0; q < query_count; q++) {
const ds4_kvstore_prefix_query *query = &queries[q];
if (!query->text) continue;
const size_t prompt_bytes = strlen(query->text);
int query_best = -1;
for (int i = 0; i < kc->len; i++) {
ds4_kvstore_entry *e = &kc->entry[i];
if ((int)e->tokens < kc->opt.min_tokens) continue;
if (e->model_id != (uint8_t)model_id) continue;
if ((uint32_t)ctx_size < e->ctx_size) continue;
if (kc->reject_different_quant &&
e->quant_bits != (uint8_t)quant_bits) continue;
if (e->tokens > query->max_tokens) continue;
if (e->text_bytes > query->max_key_bytes) continue;
if ((e->ext_flags & query->required_ext_flags) !=
query->required_ext_flags ||
(e->ext_flags & query->forbidden_ext_flags) != 0) continue;
if (e->text_bytes > prompt_bytes || e->text_bytes > SIZE_MAX)
continue;

/* Preserve the established text-cache ordering within one key
* spelling: consume the longest rendered byte prefix, then the
* largest exact token history for an equal byte prefix. */
if (query_best >= 0) {
const ds4_kvstore_entry *b = &kc->entry[query_best];
if (e->text_bytes < b->text_bytes) continue;
if (e->text_bytes == b->text_bytes &&
e->tokens <= b->tokens) continue;
}
char sha[41];
ds4_kvstore_sha1_bytes_hex(query->text,
(size_t)e->text_bytes, sha);
if (strcmp(sha, e->sha)) continue;
query_best = i;
}
char sha[41];
ds4_kvstore_sha1_bytes_hex(prompt_text, (size_t)e->text_bytes, sha);
if (!strcmp(sha, e->sha)) best = i;

if (query_best < 0) continue;
/* Metadata makes byte lengths incomparable across conditioning
* spellings. The caller orders them by semantic frontier (for example,
* all matching images before fewer matching images), so the first
* spelling with a candidate wins. */
best = query_best;
best_query = q;
break;
}
if (best >= 0 && matched_query_out) *matched_query_out = best_query;
return best;
}

int ds4_kvstore_try_load_text(ds4_kvstore *kc,
ds4_engine *engine,
ds4_session *session,
const char *prompt_text,
ds4_tokens *effective_prompt,
ds4_kvstore_load_result *result,
const ds4_kvstore_trailer_hooks *hooks,
bool responses_protocol) {
int ds4_kvstore_find_text_prefix(ds4_kvstore *kc, const char *prompt_text,
int model_id, int quant_bits, int ctx_size) {
return ds4_kvstore_find_text_prefix_filtered(
kc, prompt_text, model_id, quant_bits, ctx_size, 0,
DS4_KVSTORE_EXT_VISION_IDENTITY);
}

int ds4_kvstore_try_load_best_prefix(
ds4_kvstore *kc,
ds4_engine *engine,
ds4_session *session,
const ds4_kvstore_prefix_query *queries,
size_t query_count,
ds4_tokens *effective_prompt,
ds4_kvstore_load_result *result,
const ds4_kvstore_trailer_hooks *hooks,
bool responses_protocol,
size_t *matched_query_out) {
if (result) memset(result, 0, sizeof(*result));
if (matched_query_out) *matched_query_out = SIZE_MAX;
if (effective_prompt) effective_prompt->len = 0;
if (!kc->enabled || !prompt_text) return 0;
if (!kc->enabled || !queries || query_count == 0) return 0;
const int quant_bits = ds4_engine_routed_quant_bits(engine);
if (quant_bits != 2 && quant_bits != 4) return 0;
const int model_id = ds4_engine_model_id(engine);
size_t matched_query = SIZE_MAX;
int idx = ds4_kvstore_find_best_prefix(
kc, queries, query_count, model_id, quant_bits,
ds4_session_ctx(session), &matched_query);
if (idx < 0 || matched_query >= query_count) return 0;

const ds4_kvstore_prefix_query *query = &queries[matched_query];
const char *prompt_text = query->text;
const size_t prompt_bytes = strlen(prompt_text);
int idx = ds4_kvstore_find_text_prefix(kc, prompt_text, model_id, quant_bits,
ds4_session_ctx(session));
if (idx < 0) return 0;

ds4_kvstore_entry e = kc->entry[idx];
char *path = kv_xstrdup(e.path);
Expand All @@ -1248,6 +1322,17 @@ int ds4_kvstore_try_load_text(ds4_kvstore *kc,
if (hdr.model_id != (uint8_t)model_id) {
header_ok = false;
fail_reason = "cached checkpoint was written for a different model";
} else if ((hdr.ext_flags & query->required_ext_flags) !=
query->required_ext_flags ||
(hdr.ext_flags & query->forbidden_ext_flags) != 0) {
header_ok = false;
fail_reason = "cached checkpoint has the wrong key kind";
} else if (hdr.tokens > query->max_tokens) {
header_ok = false;
fail_reason = "cached checkpoint crosses the conditioning frontier";
} else if (text_bytes > query->max_key_bytes) {
header_ok = false;
fail_reason = "cached key crosses the conditioning frontier";
} else if ((uint64_t)text_bytes > prompt_bytes) {
header_ok = false;
fail_reason = "cached text is longer than prompt";
Expand Down Expand Up @@ -1333,12 +1418,49 @@ int ds4_kvstore_try_load_text(ds4_kvstore *kc,
result->load_ms = load_ms;
result->path = kv_xstrdup(path);
}
if (matched_query_out) *matched_query_out = matched_query;
}
free(cached_text);
free(path);
return loaded;
}

int ds4_kvstore_try_load_text_filtered(
ds4_kvstore *kc,
ds4_engine *engine,
ds4_session *session,
const char *prompt_text,
ds4_tokens *effective_prompt,
ds4_kvstore_load_result *result,
const ds4_kvstore_trailer_hooks *hooks,
bool responses_protocol,
uint8_t required_ext_flags,
uint8_t forbidden_ext_flags) {
ds4_kvstore_prefix_query query = {
.text = prompt_text,
.max_tokens = UINT32_MAX,
.max_key_bytes = UINT32_MAX,
.required_ext_flags = required_ext_flags,
.forbidden_ext_flags = forbidden_ext_flags,
};
return ds4_kvstore_try_load_best_prefix(
kc, engine, session, &query, 1, effective_prompt, result, hooks,
responses_protocol, NULL);
}

int ds4_kvstore_try_load_text(ds4_kvstore *kc,
ds4_engine *engine,
ds4_session *session,
const char *prompt_text,
ds4_tokens *effective_prompt,
ds4_kvstore_load_result *result,
const ds4_kvstore_trailer_hooks *hooks,
bool responses_protocol) {
return ds4_kvstore_try_load_text_filtered(
kc, engine, session, prompt_text, effective_prompt, result, hooks,
responses_protocol, 0, DS4_KVSTORE_EXT_VISION_IDENTITY);
}

void ds4_kvstore_load_result_free(ds4_kvstore_load_result *result) {
if (!result) return;
free(result->path);
Expand Down
Loading