Skip to content

Feature Request: Expose Speculative Decoding (MTP / Draft) in public C API (llama.h) for downstream bindings #27469

Closed as not planned

Description

@zsogitbe

Feature Description

Expose Multi-Token Prediction (MTP) and speculative decoding capabilities in the public C API (llama.h) and export them in dynamic shared libraries (llama.dll, libllama.so, libllama.dylib).

Specifically, this feature request entails:

  1. Public C API Access: Making speculative decoding workflows—currently encapsulated in common/speculative.cpp—accessible via stable C functions in llama.h.
  2. High-Level Speculative Orchestration: Providing a high-level speculative context API to handle draft generation, verification, KV cache synchronization, and multi-pass decoding natively, rather than requiring wrapper languages to implement complex tensor plumbing downstream.
  3. Downstream Interop: Enabling language bindings (C#, Python, Rust, Swift, Go) to run multi-token prediction and draft verification natively through the exported shared library without depending on internal common/ headers or custom forks.

Motivation

Currently, downstream bindings (like LLamaSharp, llama-cpp-python, and Rust wrappers) are unable to utilize the incredible speedups provided by Speculative Decoding and Multi-Token Prediction (MTP).

The recent unifications for MTP (e.g., --spec-type draft-mtp) and other speculative methods (EAGLE, n-gram, DFlash) are fantastic, but the orchestration logic lives entirely inside common/speculative.cpp. Because common/ is not exported to the shared library (llama.dll / libllama.so), pure C API consumers are locked out of these features.

Initially, we attempted to implement the MTP drafting loop manually in C# for LLamaSharp. However, after reviewing speculative.cpp, we realized the native MTP implementation is too complex to replicate safely downstream:

  • It requires a dual-context architecture (ctx_tgt and a dedicated ctx_dft with LLAMA_CONTEXT_TYPE_MTP).

  • It relies on extracting and passing dense hidden state embeddings directly into the evaluation batch (batch.embd / llama_get_embeddings_nextn_ith) instead of just token IDs.

  • It dynamically handles layer offsets (llama_set_nextn_layer_offset) for chain_heads architectures.

Attempting to rebuild this intricate, multi-pass autoregressive logic across P/Invoke boundaries in wrapper languages is highly brittle and creates severe technical debt.

Possible Implementation

We are hoping the maintainers can consider formalizing speculative decoding into llama.h to help binding authors:

The Ideal Solution (A Stable Speculative API)
Introduce a formal llama_speculative_* or llama_draft_* API suite into llama.h. Similar to how the modern llama_sampler API abstracts the sampling chain, an opaque speculative context could handle the draft-and-verify loop, RoPE shifts, and KV cache rollbacks internally. Bindings would simply feed the prompt and receive verified tokens.

Conceptual Example:

// Initialize a speculative context managing both target and draft models
LLAMA_API struct llama_speculative_context * llama_speculative_init(
    struct llama_context * ctx_tgt, 
    struct llama_context * ctx_dft,
    const struct llama_speculative_params * params
);

// A unified decode/sample loop that handles the draft/verify internally
LLAMA_API void llama_speculative_decode(
    struct llama_speculative_context * spec_ctx,
    struct llama_batch * batch
);

// Extract the accepted tokens after verification
LLAMA_API int32_t llama_speculative_get_accepted_tokens(
    struct llama_speculative_context * spec_ctx,
    llama_token * out_tokens,
    int32_t max_tokens
);

With models like DeepSeek V3/R1, Qwen 3.6, and Gemma 4 relying heavily on MTP for performance, this is becoming a critical feature for the broader ecosystem. If you are open to exposing an experimental API, we would love to collaborate by immediately testing it downstream in C# and reporting back!

Prerequisites

  • I am running the latest code. Mention the version if possible as well.
  • I carefully followed the README.md.
  • I searched using keywords relevant to my issue to make sure that I am creating a new issue that is not already open (or closed).
  • I reviewed the Discussions, and have a new and useful enhancement to share.

Activity

  1. nikwen commented on Aug 21, 2026

    @nikwen
    Member

    It would also make my life much easier if this existed.

  2. ardan-bkennedy commented on Sep 14, 2026

    @ardan-bkennedy

    I can add a concrete Go consumer to this request and narrow the interoperability problem.

    Kronk implements its MTP speculative loop downstream and successfully uses these three functions:

    • llama_set_embeddings_nextn
    • llama_get_embeddings_nextn
    • llama_get_embeddings_nextn_ith

    They are LLAMA_API C-linkage symbols implemented in libllama, but their only declarations are in the uninstalled, explicitly WIP src/llama-ext.h, rather than the supported public C header. That distinction is the blocker for normal language bindings.

    The yzma Go binding reasonably avoids APIs that llama.cpp still classifies as staging/WIP. As a result, Kronk currently has to resolve these symbols dynamically, including fallbacks for both the former *_pre_norm names and the current *_nextn names. It then wraps the returned C-owned buffers itself. This works, but it is exactly the kind of brittle downstream ABI dependency that a supported C API would eliminate.

    I understand that these functions are not simply generic embedding accessors. Since PR #24025, “NextN embeddings” intentionally mean a model-defined feature produced at the graph position required by that model's drafter; the value is not guaranteed to be universally pre-norm or post-norm. I also understand that the surrounding MTP/speculative implementation is still evolving. I am therefore not asking llama.cpp to declare the current signatures stable merely because they have existed since early June.

    Could maintainers clarify which public API direction they would accept for downstream bindings?

    1. Promote or refine these three low-level calls into include/llama.h, with a documented model-independent contract for the model-defined NextN/drafter feature stream; or
    2. Provide another minimal supported C API for that feature stream; or
    3. Expose it only through a higher-level opaque speculative-decoding API such as the one proposed in this issue.

    For consumers that already own the orchestration loop, option 1 or 2 would be sufficient and much smaller than requiring a complete speculative engine. A stable contract would need to define at least:

    • what capability indicates that a model/context produces the NextN feature stream;
    • feature width and available row count;
    • dense versus batch.logits-masked row indexing;
    • buffer ownership and validity across decode/synchronize calls; and
    • behavior when a requested row or feature stream is unavailable.

    If the intended answer is that bindings should wait for a higher-level API, that guidance would also be valuable: it would tell us that these exported staging symbols should remain a temporary downstream workaround rather than candidates for direct binding.

  3. zsogitbe commented on Sep 15, 2026

    @zsogitbe
    Author

    I have added a PR a while ago, please check here: #27788
    Unfortunately there is not much move there, probably because of the >1.6K PRs...

    💡 Try to support/push that PR if you agree to it.

  4. github-actions commented on Oct 31, 2026

    @github-actions
    Contributor

    This issue was closed because it has been inactive for 14 days since being marked as stale.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions