Skip to content

model : deepseek41 quantized K cache, shared prompt tokens, Engram host placement - #293

Open
danielhanchen wants to merge 5 commits into
dsv41-cuda-exactfrom
dsv41-runtime
Open

danielhanchen wants to merge 5 commits into
dsv41-cuda-exactfrom
dsv41-runtime

Conversation

@danielhanchen

@danielhanchen danielhanchen commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Runtime support for DeepSeek-V4.1 beyond the reference path: a quantized K cache, batches that share prompt tokens or mix tokens and embeddings, host placement of the Engram table, and a scheduler re-reserve when embeddings are toggled.

Stacked on the CUDA PR (#292, dsv41-cuda-exact). The diff is only this change: 9 files, +152/-12.

What it adds

  • Quantized K cache (-ctk q8_0 / q4_0, src/models/deepseek41.cpp, src/llama-kv-cache-dsv4.cpp):
    • The compressed-attention path did not apply the Hadamard rotation that build_raw_attention applies to a quantized K cache. It now rotates q, the window K and the output like the window-only path, and rotates the compressed rows in F32 before storing them in the cache type.
    • Quantized compressed K and index K views are dequantized directly to F32 (CUDA has no same-type copy for strided quantized views); with LLAMA_ATTN_ROT_DISABLE=1 they go through F32 as well.
    • F16/BF16/F32 caches build the same graph as before.
  • Quantized engram_gate: it is an elementwise weight, so a quantized copy is dequantized in the graph, and llama-quantize no longer quantizes it.
  • Shared prompt tokens: tokens shared by several sequences (llama-batched decodes the prompt once with n_seq_id = n_parallel) read the n-gram history of their first sequence. init_batch checks that all of the token's sequences have the same predecessors (batch first, then the cache via the new llama_kv_cache::seq_pos_tok_le) and refuses the batch otherwise: decode returns 1 and the caches are untouched.
  • Mixed token/embedding ubatches: embedding (image) positions hash as dead, get Engram gate 0, and their KV cells record LLAMA_TOKEN_NULL.
  • Engram host placement: per_layer_token_embd stays in host memory under tensor overrides and split modes when it is not read lazily.
  • Embeddings re-reserve (src/llama-context.cpp): llama-server --embeddings --pooling none toggles llama_set_embeddings per request. With pooling NONE the toggle did not re-reserve, so a model whose graph depends on cparams.embeddings could run on a stale reservation and segfault in ggml_gallocr on the first /completion. The scheduler now re-reserves on every toggle.

Verification

All results are local, on B200 (CUDA sm_100a) and x86 CPU.

  • Unchanged default path: with an F16 KV cache, logits are bit-identical to the parent PR on every CPU fixture config and on CUDA. DeepSeek-V4 is bit-identical as well.
  • Quantized K cache on the fixture against F16 KV: mean KL 1.1e-3 to 1.6e-3 with q8_0 and 2.0e-2 to 2.2e-2 with q4_0; the quantized-cache gate passes.
  • Quantized K cache on a real quant (development branch carrying the same change, KLD against BF16):
    • 4k context: F16 KV 0.02136, q8_0 0.02136, q4_0 0.03261 (q4_0 without rotation 0.03580).
    • 32k context: F16 KV 0.007888, q8_0 0.008094, q4_0 0.015061.
    • So q8_0 is safe; q4_0 costs accuracy. The KV cache at 32k is 190 MiB (F16), 101 MiB (q8_0) and 53 MiB (q4_0).
  • Runtime matrix on CPU: 118/118 feature checks pass, plus llama-batched with shared prompts and parallel sequences, and 43/43 server checks.
  • Embeddings: llama-server --embeddings --pooling none followed by /completion no longer segfaults.
  • Revision: the patches are unchanged from the previously gated revision; this revision only rebases onto the parent.

Known gaps

  • -ctk q4_0 is supported but measurably less accurate (see above); q8_0 is the recommended quantized cache.
  • The CUDA feature checks use 1e-9 thresholds that also fail on the parent PR without a debug exact-math knob, so CUDA coverage of the feature matrix is the bitwise F16-KV comparison only.
  • Platforms: validated on CPU and CUDA only. No ROCm, Metal, Vulkan, Windows or macOS validation.

llama-server --embeddings --pooling none toggles llama_set_embeddings
per request. With pooling NONE the toggle did not trigger a re-reserve,
so a model whose graph depends on cparams.embeddings (DeepSeek-V4.1
returns the flat HC state as t_embd) could run on a stale reservation,
and ggml_gallocr segfaulted on the first /completion. Re-reserve on
every toggle.
…ngram host placement

- Tokens shared by several sequences (llama-batched decodes the prompt
  once with n_seq_id = n_parallel) read the n-gram history of their
  first sequence. llama_kv_cache_dsv4::init_batch now checks that all
  of the token's sequences have the same predecessors (batch first,
  then the cache via the new llama_kv_cache::seq_pos_tok_le) and
  refuses the batch otherwise (decode returns 1, caches untouched).
- Mixed token/embd ubatches: embedding (image) positions hash as DEAD,
  get Engram gate 0, and their KV cells record LLAMA_TOKEN_NULL.
- The Engram table (per_layer_token_embd) stays in host memory under
  tensor overrides and split modes when it is not read lazily.
… engram_gate

- The compressed-attention path did not apply the Hadamard rotation
  that build_raw_attention applies to a quantized K cache. It now
  rotates q, the window K and the output like the window-only path, and
  rotates the compressed rows in F32 before storing them in the cache
  type for the concat.
- Quantized compressed K / index K views are dequantized directly to
  F32 (CUDA has no same-type copy for strided quantized views); with
  LLAMA_ATTN_ROT_DISABLE=1 they go through F32 as well.
- engram_gate is an elementwise weight: dequantize a quantized copy in
  the graph, and keep llama-quantize from quantizing it.

F16/BF16/F32 caches build the same graph as before.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-19T08:01:32.868057Z 920f23c PR opened
🔒 Security Review ✅ Completed 2026-10-19T07:55:24.126211Z 920f23c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 920f23c13a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1394 to +1397
std::map<std::pair<llama_seq_id, llama_pos>, llama_token> in_batch;
for (int32_t i = 0; i < batch.n_tokens; ++i) {
for (int32_t s = 0; s < batch.n_seq_id[i]; ++s) {
in_batch[{ batch.seq_id[i][s], batch.pos[i] }] = batch.token[i];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve embedding row types when checking shared history

For a mixed token/embedding batch, llama_batch_allocr stores token ID 0 as the placeholder for every embedding row, so this map treats an embedding predecessor as a real token 0 and also collapses rows that share a multimodal position. The graph later identifies those rows through ubatch.type, stores LLAMA_TOKEN_NULL, and resolves mixed histories by ubatch order. Consequently, a shared token following an embedding in one sequence and token 0 in another can pass this check even though their Engram hashes differ, causing the first sequence's hash to be reused silently for the other sequence. Build this validation from the typed ubatches, or otherwise retain each row's token/embedding kind and order.

Useful? React with 👍 / 👎.

Comment thread src/models/deepseek41.cpp
Comment on lines +673 to +675
ggml_tensor * engram_keep = nullptr;
if (!model.engram_layers.empty() && ubatch.token && ubatch.embd) {
engram_keep = ggml_new_tensor_3d(ctx0, GGML_TYPE_F32, 1, 1, n_tokens);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enable mixed batches before building mixed Engram inputs

When a caller supplies token-only and embedding-only entries in one DeepSeek-V4.1 batch, this new mixed-input path is unreachable: llm_arch_supports_mixed_batch() still explicitly returns false for LLM_ARCH_DEEPSEEK41, that value initializes llama_batch_allocr::allow_mixed, and llama_batch_allocr::init() rejects the request before this graph is built. Thus the mixed token/embedding support added here still returns an input-validation failure; remove DeepSeek-V4.1 from that denylist or otherwise enable mixed allocation for this architecture.

Useful? React with 👍 / 👎.

This branch has not been deployed

No deployments
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