Skip to content

batch: migrate speculative, mtmd and server to batch_ext - #29385

Merged
ngxson merged 14 commits into
ggml-org:masterfrom
ngxson:xsn/llama_batch_ext_2
Sep 28, 2026
Merged

ngxson merged 14 commits into
ggml-org:masterfrom
ngxson:xsn/llama_batch_ext_2

Conversation

@ngxson

@ngxson ngxson commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Overview

Cont #24669

Key point here is common_batch which is a wrapper for llama_batch_ext, providing getter functions

While we can add getters to llama_batch_ext, I feel like it will add too much maintenance in long term. We can add them later if many users request that.

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: yes, changes are repetitive and trivial

@ngxson
ngxson requested review from a team and ggerganov as code owners September 24, 2026 15:57
@ServeurpersoCom

Copy link
Copy Markdown
Contributor

Tested against master on text, vision, embeddings, MTP and DFlash: no regression on my side.

Is n_pos in common_batch meant to be MROPE only, the lib and mtmd also count 4 positions for IMROPE? And should the common_batch call sites check the negative return of add()/add_embd() like the raw calls in mtmd do?

@github-actions github-actions Bot added server mtmd Related to multimodal functionality (video/image/audio) labels Sep 24, 2026
@ggerganov ggerganov self-assigned this Sep 24, 2026
@ggerganov

Copy link
Copy Markdown
Member

@ServeurpersoCom Thanks for testing. Tomorrow I'll exercise this branch with my workflows too.

@ggerganov

Copy link
Copy Markdown
Member

@ngxson Could you rebase or merge master?

@ServeurpersoCom

Copy link
Copy Markdown
Contributor

Retested after the last commit (My same Opus 5.5 reviewing context) with Gemma-4-31B-IT + mmproj and a Gemma-4-E2B-IT draft (draft-simple): the only thing missing now is this change, the server aborts on the first image request without it. Target embeddings the draft cannot read become zero rows so the draft positions stay contiguous, same output as master :

diff --git a/common/speculative.cpp b/common/speculative.cpp
index 3d27bcc8b2..e3cc4841df 100644
--- a/common/speculative.cpp
+++ b/common/speculative.cpp
@@ -181,6 +181,9 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {

     common_batch batch;

+    // zero row at the draft input width, replaces target embeddings the draft cannot read
+    std::vector<float> zeros;
+
     std::vector<common_sampler_ptr> smpls;

     common_speculative_impl_draft_simple(const common_params_speculative & params, uint32_t n_seq)
@@ -194,6 +197,8 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
             throw std::runtime_error("draft-simple requires a draft context");
         }

+        zeros.assign(llama_model_n_embd_inp(llama_get_model(ctx_dft)), 0.0f);
+
         SPC_TRC("%s", "adding speculative implementation 'draft-simple'\n");
         SPC_TRC("- n_max=%d, n_min=%d, p_min=%f\n", this->params.n_max, this->params.n_min, this->params.p_min);
         SPC_TRC("- gpu_layers=%d, cache_k=%s, cache_v=%s, ctx_tgt=%s, ctx_dft=%s, devices=[%s]\n",
@@ -268,7 +273,8 @@ struct common_speculative_impl_draft_simple : public common_speculative_impl {
                     batch.set_embd(idx, t.embd);
                 }
             } else {
-                batch.add_embd(t.embd, t.pos.data(), t.seq_id, output);
+                const llama_embd embd = t.embd.n_embd == zeros.size() ? t.embd : llama_embd{ zeros.data(), 1, zeros.size() };
+                batch.add_embd(embd, t.pos.data(), t.seq_id, output);
             }
         }

Comment thread common/speculative.cpp Outdated
Comment on lines +276 to +279
// a draft with a different width (e.g. a smaller model) gets zeros instead, keeping its positions contiguous
const bool same_width = t.embd.n_rows * t.embd.n_embd == zeros.size();
const llama_embd embd = same_width ? t.embd : llama_embd{ zeros.data(), 1, zeros.size() };
batch.add_embd(embd, t.pos.data(), t.seq_id, output);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of filling zeroes, should we print warning/error? Do we have a use case that goes through the same_width == false path?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this case happens when we have mtmd input: input image/audio goes through target's encoder then the projected output is used by both draft+target

the most correct way to handle this is to create 2 mtmd contexts: one for target and one for draft. but this is quite costly to maintain so I think we can skip it for now. second-tier solution is to use a padding token instead of all 0, but I haven't yet measure the impact of this method.

so for now, I think a warning here is valid, will add it

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

added: 2f651e6

merging once the CI passes

@ngxson
ngxson merged commit f1ea206 into ggml-org:master Sep 28, 2026
12 checks passed
@ggerganov

Copy link
Copy Markdown
Member

@ServeurpersoCom

ServeurpersoCom commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Since I have the audio context warm on my agent I took a look: it's a GCC 15 -Wstringop-overflow false positive, the decode_embd_batch constructor now does logits.resize(n_tokens, 0) which goes through _M_fill_append, and once inlined into the qwen3tts/pockettts step_gen with n_tokens = 1 GCC trips on the relocation path; going back to plain logits.resize(n_tokens) like before the PR (same zero init) should fix it.

-        logits.resize(n_tokens, 0);
+        logits.resize(n_tokens);

Fix CI follow-up #29607

pierreguillot pushed a commit to Ircam-Partiels/llama.cpp that referenced this pull request Oct 1, 2026
)

* adapt common

* add common_batch

* wip

* wip: spec

* cont

* common_speculative_process

* server_batch to use common_batch

* rm some stale calls

Assisted-by: Claude Fable 5.1

* migrate mtmd

* handle imrope, handle return val of add()/add_embd()

* add spec zeros vector

* add warning on zero fill path
frostyautumnleaf pushed a commit to frostyautumnleaf/llama.cpp that referenced this pull request Oct 5, 2026
)

* adapt common

* add common_batch

* wip

* wip: spec

* cont

* common_speculative_process

* server_batch to use common_batch

* rm some stale calls

Assisted-by: Claude Fable 5.1

* migrate mtmd

* handle imrope, handle return val of add()/add_embd()

* add spec zeros vector

* add warning on zero fill path
vignshwarar added a commit to vignshwarar/llama-cpp-rs that referenced this pull request Oct 9, 2026
The only thing that broke was common_speculative_process: it takes a
common_batch now instead of a llama_batch (ggml-org/llama.cpp#29385).
The MTP wrapper now copies the caller's batch into one, so nothing
changes on the Rust side. That path is only compiled, not run.

llama.cpp also bumped the session file version, so session files saved
with older versions won't load anymore.

Tested on macOS with the CI commands (clippy, fmt, build, test, publish
dry-run).
fishlikeX added a commit to fishlikeX/sm70-attn that referenced this pull request Oct 12, 2026
DFlash2 processes images by injecting target features into the draft KV
cache, but an M-RoPE image pins all its rows to one position, so those
rows are skipped and the draft cache misses the image position span.
The next batch then starts past the draft last stored position and the
batch allocator rejects it:

  E init: the tokens of sequence 0 ... have inconsistent sequence positions:
   - the last position stored ... X = 12
   - the tokens ... have a starting position of Y = 57
  it is required that the sequence positions remain consecutive: Y = X + 1

which surfaces as HTTP 500 "decode() failed: failed to process
speculative batch" on every vision request (text-only is fine).

Fill the missing span with zero rows - the substitution mainline applies
to multimodal inputs a draft cannot read (ref: ggml-org/llama.cpp#29385) -
both before injecting a batch (process) and before decoding the noise
block (draft), so the draft positions stay contiguous. Works for drafts
with and without M-RoPE sections and for trailing/multi-image prompts.

Fixes #2

Co-Authored-By: Hermes-Tianji <hermes-tianji@users.noreply.github.com>
AsbjornOlling pushed a commit to utilityai/llama-cpp-rs that referenced this pull request Oct 15, 2026
The only thing that broke was common_speculative_process: it takes a
common_batch now instead of a llama_batch (ggml-org/llama.cpp#29385).
The MTP wrapper now copies the caller's batch into one, so nothing
changes on the Rust side. That path is only compiled, not run.

llama.cpp also bumped the session file version, so session files saved
with older versions won't load anymore.

Tested on macOS with the CI commands (clippy, fmt, build, test, publish
dry-run).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mtmd Related to multimodal functionality (video/image/audio) server

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants