| Internet-Draft | SDCP | November 2026 |
| Li, et al. | Expires 6 May 2027 | [Page] |
Speculative decoding accelerates language model inference: a small draft model proposes several tokens and the large target model verifies them in one forward pass. In large deployments, draft models and the target model run on separate, independently scaled instances that exchange state through a multi-level cache. This document specifies the Speculative Decoding Coordination Protocol (SDCP), with which a router assigns requests to draft instances by load, draft instances submit proposals that reference draft state in the cache, and a verifier, the only party that commits output, checks proposals under a registered acceptance rule, returns the accepted tokens and the next-round speculation limit, and escalates persistently rejected sessions to a reduced speculation length, another draft model, or decoding by the target model itself depending on its load. SDCP defines a session contract, a committed-prefix digest chain, CBOR messages carried over HTTP, idempotent rounds, and epoch fencing for recovery.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 6 May 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Autoregressive decoding produces one token per forward pass of the target model. Each pass reads all model weights and the whole KV cache, so decoding is limited by memory bandwidth and leaves compute idle. Speculative decoding [LEVIATHAN] [CHEN] lets a cheaper draft model propose M tokens, which the target model then scores in a single pass. The longest acceptable prefix of the proposal is kept, and the target model contributes one more token, so each target pass yields between 1 and M+1 tokens. With the speculative sampling rule, the output follows exactly the distribution of the target model. Some models are trained with built-in multi-token prediction heads that serve as drafters [DEEPSEEKV3].¶
When drafting and verification run in one process, the coordination is internal. Large deployments separate them: several draft model instances, possibly of different sizes, serve many concurrent requests and feed one or more target-model verifier instances, and both exchange token state and KV caches through a multi-level cache spanning accelerator memory, host memory, and networked storage [I-D.li-kvcache-efficient-storage]. In that setting the following must be agreed between independently implemented components:¶
SDCP addresses these needs with the following elements:¶
The verifier is the sole commit authority. Drafters, routers, and caches can fail or misbehave without changing the committed output, except as discussed in Section 9.¶
SDCP does not define model architectures, sampling kernels, batching algorithms, or client-facing inference APIs. It assumes a multi-level cache that stores and serves opaque objects named by URI and supports leases, such as KVSP [I-D.li-kvcache-efficient-storage]. Draft-model training, tree-structured speculation, and multiple verifiers per session are out of scope for this version.¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
requests output tokens
Client -----------> +--------+ +-------------> Client
| Router | |
+--------+ |
assignment / | \ events |
v | \ |
+-----------+ | +-----------+-+---+
| Drafter 1 | | | Verifier |
+-----------+ | | (target model) |
+-----------+ | +--------+--------+
| Drafter 2 |--+--proposal->|verdict
+-----+-----+ <---------+
| |
v draft KV, q rows v target KV, commits
+-------------------------------------+
| Multi-Level Cache |
+-------------------------------------+
The router creates a session at a verifier, which computes or loads the KV cache of the prompt, and assigns the session to a drafter. The drafter proposes tokens to the verifier. The verifier answers each proposal with a verdict that both commits output and steers the next round. Draft state that is large, such as the draft model's KV cache and probability rows, stays in the MLC and is referenced by URI.¶
The Session Contract is fixed when the session is created:¶
contract = {
target: hash32, ; SHA-256 of target model weights
tokenizer: hash32, ; SHA-256 of tokenizer definition
? drafters: [+ hash32], ; permitted draft models
rule: uint, ; acceptance rule (registry), fixed
? sampling: sampling,
m_max: uint .le 64, ; upper bound on M
max_new: uint, ; maximum output tokens
stop: [* uint], ; stop tokens
low_rounds: uint .ge 1, ; R, escalation period
? low_ratio: uint .le 100, ; percent, default 0
fallback: [+ ladder], ; escalation ladder
? self_load: uint .le 100, ; max verifier load for self-decode
? self_max: uint, ; max tokens per self-decode period
? scope: bstr .size 16 ; KVSP Scope ID of cache objects
}
sampling = {
? temperature_milli: uint, ; temperature x 1000
? top_k: uint,
? top_p_milli: uint .le 1000,
? seed: uint
}
hash32 = bstr .size 32
action = a-continue / a-reduce / a-switch /
a-self-decode / a-finish / a-fail
ladder = a-reduce / a-switch / a-self-decode / a-fail
a-continue = 0
a-reduce = 1
a-switch = 2
a-self-decode = 3
a-finish = 4
a-fail = 5
¶
The contract digest is the SHA-256 of the contract encoded with the core deterministic encoding of Section 4.2.1 of [RFC8949], prefixed with a domain separator:¶
contract_digest = SHA-256( "SDCP-CONTRACT-1" || 0x00 ||
deterministic_cbor(contract) )
¶
The sampling profile defines the target distribution p used by the acceptance rule: p is the target model's next-token distribution after applying temperature, top-k, and top-p in that order; a temperature of zero selects the most probable token (ties broken in favor of the smallest token identifier). An absent "drafters" member permits any draft model that the router's local policy allows. The router can change the sampling profile at a round boundary (Section 6.6); such updates are carried separately from the contract, which, including the acceptance rule, is never modified. The contract digest is computed once, over the contract as created. A contract that contains a key not defined by this document MUST be rejected with problem type "sdcp-bad-contract", so that all parties compute the contract digest over the same content.¶
The verifier maintains a digest over the committed prefix. For a prompt of P tokens t[0..P-1] and committed output tokens o[1..n]:¶
d[0] = SHA-256( "SDCP-PREFIX-1" || 0x00 ||
contract_digest || uint32(P) ||
uint32(t[0]) || ... || uint32(t[P-1]) )
d[k] = SHA-256( d[k-1] || uint32(o[k]) ) for k = 1..n
¶
uint32() is four octets in network byte order and SHA-256 is defined in [RFC6234]. A proposal states the length n and digest d[n] of the prefix it extends. Because d[n] depends on every committed token, a proposal built on a different or outdated prefix is detected without comparing token lists.¶
SDCP is an HTTP application following BCP 56 [RFC9205]. Each role has a base URI configured by the operator; SDCP defines no well-known URI. HTTP/2 [RFC9113] or HTTP/3 [RFC9114] over TLS 1.3 [RFC8446] with mutual certificate authentication is REQUIRED. Message bodies use the media type "application/sdcp+cbor". Errors use Problem Details [RFC9457]; clients handle unlisted status codes by class [RFC9110].¶
| Role | Method and Path | Body | Response |
|---|---|---|---|
| Verifier | POST sessions | session-open | session-state |
| Verifier | PUT sessions/{id}/drafter | assignment | session-state |
| Verifier | POST proposals | proposal | verdict |
| Verifier | GET sessions/{id}/output?from=n | none | output |
| Verifier | POST sessions/{id}/policy | policy | session-state |
| Drafter | POST assignments | assignment | none (204) |
| Router | PUT instances/{id}/status | status | none (204) |
| Router | POST events | event | none (204) |
{id} in session paths is the base64url encoding without padding [RFC4648] of the session identifier.¶
session-id = bstr .size 16
cache-ref = tstr ; URI of an MLC object
session-open = {
session: session-id, epoch: uint, contract: contract,
prompt: [* uint] / cache-ref, ; tokens or MLC reference
? prompt_kv: cache-ref, ; target KV of the prompt
commit_log: cache-ref, ; durable commit records
? resume: { ; present on failover
round: uint, committed: uint, digest: hash32,
committed_tokens: cache-ref,
? target_kv: cache-ref
}
}
session-state = {
session: session-id, epoch: uint, round: uint,
committed: uint, digest: hash32, ? state: uint
}
assignment = {
session: session-id, epoch: uint, contract: contract,
verifier: tstr, ; verifier base URI
drafter: tstr, ; drafter base URI
prompt: [* uint] / cache-ref,
round: uint, ; last round with a verdict
? sampling: sampling, ; current profile, if updated
committed: uint, digest: hash32,
? committed_tokens: cache-ref, ; committed output so far
m_init: uint
}
proposal = {
session: session-id, epoch: uint, round: uint,
drafter: tstr, ; drafter base URI
base: uint, base_digest: hash32,
tokens: [+ uint], ; M proposed tokens
? q_rows: cache-ref, ; draft rows, for rule 2
? draft_kv: cache-ref,
status: status
}
verdict = {
session: session-id, epoch: uint, round: uint,
accepted: uint, ; a, 0 <= a <= M
emitted: [* uint], ; tokens committed this round
committed: uint, digest: hash32, ; new n and d[n]
action: action, m_cap: uint,
low: uint, ; consecutive low rounds
load: uint .le 100 ; verifier load, percent
}
output = {
session: session-id, epoch: uint, from: uint, tokens: [* uint],
final: bool, ? finish: action
}
status = {
instance: tstr, role: 1 / 2, ; 1 drafter, 2 verifier
load: uint .le 100, queue: uint,
? accept_milli: uint .le 1000, ; EWMA of a/M x 1000
? cache_mbps: uint, ; available MLC bandwidth
ttl_ms: uint
}
event = {
session: session-id, epoch: uint,
kind: action, committed: uint, digest: hash32
}
policy = { session: session-id, from_round: uint,
sampling: sampling }
¶
The contract, sampling, hash32, and action types are defined in Section 4.1. Unknown map keys MUST be ignored, except in the contract (Section 4.1). A receiver that encounters an unknown action value MUST treat it as "fail".¶
The "q_rows" object holds, for each proposed position, the draft distribution from which the proposed token was sampled, as a row of V IEEE 754 binary32 values in little-endian order, where V is the vocabulary size; rows are concatenated in position order. The drafter MUST sample each proposed token from exactly the row it publishes.¶
The router selects a verifier and sends session-open with epoch 1 and a commit_log reference in the MLC. The verifier validates the contract (it MUST refuse a contract whose target digest does not match its model), computes or loads the KV cache of the prompt, computes d[0], and answers with session-state (round 0, committed 0).¶
The router then selects a drafter whose model is permitted by the contract. It first informs the verifier with PUT sessions/{id}/drafter, and then sends the assignment, carrying the current round, committed length, and digest, to the drafter. The verifier accepts proposals only from the drafter named in the latest assignment it has received. Drafter selection is a local policy based on the drafters' status reports, for example preferring drafters with low load and queue and high recent acceptance. The router MUST NOT assign sessions to an instance whose status has expired. Each instance reports status at intervals shorter than its ttl_ms.¶
A drafter holding an assignment prepares its own KV cache for the committed prefix (by computation or by loading from the MLC) and generates M tokens, with 1 <= M <= min(m_max, m_cap), where m_cap is the limit from the last verdict (m_init before the first). It writes the draft artifacts the verifier may need to the MLC and holds leases on them until it receives the verdict of the round. It then sends a proposal with base and base_digest equal to the committed length and digest it last learned, and round equal to the last verdict's round plus one.¶
A drafter MUST NOT have more than one outstanding proposal per session. It MAY prepare the next proposal speculatively, assuming full acceptance, but sends it only after the verdict confirms that assumption.¶
Choice of M is local to the drafter. The following rule is RECOMMENDED. Let alpha be the drafter's exponentially weighted estimate of the per-token acceptance probability for the session, t_d the time to draft one token (including writing its artifacts at the cache bandwidth currently available), and t_v the expected verification time. The expected number of tokens committed per round is (1 - alpha^(M+1)) / (1 - alpha) for alpha < 1, and M + 1 for alpha = 1 [LEVIATHAN]; the drafter chooses the M within its limits that maximizes that quantity divided by (M * t_d + t_v). A loaded drafter thereby proposes fewer tokens, and a drafter whose cache bandwidth is scarce does the same.¶
On receiving a proposal, the verifier performs these steps:¶
After the verdict, the drafter MUST discard its draft state for proposed positions beyond the accepted ones and MAY release its leases. Its KV cache for the accepted tokens remains valid. The verifier MAY publish the target KV cache of committed tokens to the MLC so that another verifier can take over the session.¶
Rules are registered (Section 10.2.2). This document defines two. In both, p[i] is the target distribution at proposed position i, x[i] the proposed token, and positions are evaluated in order.¶
x[i] is accepted if it equals argmax p[i], where ties are broken in favor of the smallest token identifier. Evaluation stops at the first position that is not accepted. The additional token is argmax p[a+1]. The output is identical to greedy decoding of the target model as computed by the verifier's scoring passes, whatever the drafter proposes.¶
For each position, draw r uniformly from [0, 1). Accept x[i] if r < min(1, p[i](x[i]) / q[i](x[i])), where q[i] is the row of q_rows for position i, that is, the draft distribution from which x[i] was sampled. At the first rejected position, the additional token is drawn from the normalized residual distribution max(0, p[i] - q[i]). If all M tokens are accepted, the additional token is drawn from p[M+1]. With exact q values, the output has the distribution of the target model [LEVIATHAN] [CHEN]. The proposal MUST carry q_rows; a proposal for which q[i](x[i]) is zero is rejected, before any random draw, with problem type "sdcp-bad-proposal".¶
The verifier's random values MUST be independent of those the drafter used. When the sampling profile contains a seed, the verifier derives each value from HMAC-SHA-256 [RFC2104] keyed with the seed (as eight octets in network byte order) over "SDCP-VERIFY-1" || 0x00 || session || uint32(round) || uint32(i) || uint8(purpose), where purpose is 0 for the acceptance test and 1 for drawing the additional token, taking the first 53 bits of the result divided by 2^53. Drafters MUST NOT use this derivation for their own sampling. Without a seed, the verifier uses a cryptographically secure random number generator.¶
Each verdict carries m_cap, the largest M the drafter may use in the next round, with 1 <= m_cap <= m_max. The verifier lowers m_cap when its own load makes long proposals costly, and as specified for the "reduce" action below. It SHOULD restore m_cap gradually (for example by one per round) when rounds are no longer low and its load permits.¶
A round is low if 100 * a < low_ratio * M, or, when low_ratio is zero or absent, if a = 0. The verifier keeps three counters per session, all starting at zero: c, the number of consecutive low rounds; g, the number of consecutive rounds that are not low; and j, the position in the contract's fallback list. After a low round, c is incremented and g reset; after a round that is not low, c is reset and g incremented, and when g reaches R, where R is low_rounds, j is reset to zero. When c reaches R, the verifier applies entry j of the fallback list (skipping entries that are not permitted, as described for self_decode, and advancing j past every entry it applies or skips), then resets c to zero. If no entry remains, it applies "fail". Otherwise it applies "continue". The fallback list MUST contain only the actions reduce, switch, self_decode, and fail; j persists across a change of drafter and across a self-decoding period. The actions are:¶
Because self_decode is conditional on the verifier's load, the same fallback list leads to self-decoding on a lightly loaded verifier and to the next entry, such as switch, on a busy one.¶
The router can change the sampling profile of a session, for example to follow a preference stated by the user. It sends a policy message with from_round greater than the current round. The verifier applies the new profile to rounds numbered from_round and later, and the router includes it in the "sampling" member of subsequent assignments; the contract in those assignments is unchanged. The acceptance rule cannot be changed. Committed output is never changed by a policy update.¶
Committed tokens are delivered with GET output?from=n, which returns the tokens committed at output indices n and later, waiting up to a server-chosen time if none are available. Only tokens whose commit record has been written are returned. Indices are stable and every output message carries the session epoch, so a front end that retries never duplicates or loses tokens, and a front end that receives output with a lower epoch than one it has seen discards it. The final output message has final set to true and finish set to "finish" or "fail".¶
The commit_log is an MLC object to which records are appended with a conditional operation, for example an HTTP PUT with If-Match on the current entity tag [RFC9110], so that an append succeeds only if no other append has happened since the writer last read the log. Each record carries the epoch of its writer. A commit record appended by a verifier MUST carry the session's current epoch and follow the last record it wrote. A fence record carries only an epoch; after a fence record with epoch E+1, appends carrying an epoch lower than E+1 MUST fail. If the MLC cannot enforce this rule, the verifier MUST read the log after its conditional append and treat its append as failed if a fence record with a higher epoch precedes its own record.¶
| Type suffix | Status | Meaning |
|---|---|---|
| sdcp-stale-epoch | 409 | Epoch is not current |
| sdcp-stale-base | 409 | Proposal does not extend the committed prefix |
| sdcp-q-unavailable | 409 | Draft probabilities required by the rule are unavailable |
| sdcp-bad-contract | 422 | Contract invalid or not supported |
| sdcp-limit | 422 | Proposal violates m_cap or m_max |
| sdcp-session-closed | 410 | Session has finished or failed |
| sdcp-bad-proposal | 422 | Proposal inconsistent with its draft distributions |
| sdcp-not-assigned | 403 | Drafter is not assigned to the session |
The type URI is "https://iana.org/assignments/sdcp#" followed by the suffix. These types are specific to SDCP and are therefore registered in an SDCP registry rather than in the "HTTP Problem Types" registry (Section 4.2 of [RFC9457]). Problem details for "sdcp-stale-base" include the member "session_state" carrying the current committed length and digest.¶
All exchanges use mutual TLS, and every role authorizes requests by authenticated identity: only the router opens sessions and assigns drafters, and only the assigned drafter submits proposals.¶
Integrity of output. Under the greedy rule, the committed output depends only on the target model and the committed prefix; a malicious drafter can waste verifier capacity but cannot change the output. Under speculative sampling, the drafter supplies q, and a drafter that reports false probabilities can bias the output (for example, by reporting a small q for tokens it wants accepted). Speculative sampling MUST therefore be used only with drafters that are trusted to the same degree as the verifier; with other drafters the greedy rule MUST be used.¶
Denial of service. Proposals cost a forward pass. Verifiers MUST enforce m_cap and SHOULD rate-limit rounds per session and per drafter. The escalation ladder also bounds the work a poorly matched or hostile drafter can cause.¶
Cache objects. References in proposals point to the MLC; the verifier MUST fetch them only from caches it trusts for the session's scope, so that a proposal cannot direct the verifier to arbitrary URIs.¶
Privacy. Prompts, KV caches, and outputs are user data. The privacy considerations of [I-D.li-kvcache-efficient-storage] apply to all MLC objects of a session.¶
IANA is requested to register the media type "application/sdcp+cbor" in the "Media Types" registry [RFC6838]: Type name: application; Subtype name: sdcp+cbor; Required parameters: N/A; Optional parameters: N/A; Encoding considerations: binary; Security considerations: see Section 9 of RFC XXXX; Interoperability considerations: none; Published specification: RFC XXXX; Applications that use this media type: distributed inference systems using speculative decoding; Fragment identifier considerations: the syntax and semantics of fragment identifiers specified for "application/cbor" apply; Additional information: Deprecated alias names for this type: N/A, Magic number(s): N/A, File extension(s): N/A, Macintosh file type code(s): N/A; Person and email address to contact: IETF <iesg@ietf.org>; Intended usage: COMMON; Restrictions on usage: none; Author: IETF; Change controller: IETF.¶
IANA is requested to create a registry group named "Speculative Decoding Coordination Protocol (SDCP)" at "https://www.iana.org/assignments/sdcp" with the registries below. For the numeric registries, values 0-191 are assigned by Specification Required [RFC8126] and values 192-255 are reserved for Private Use. The designated experts should check that a new acceptance rule states whether it preserves the target distribution and what trust it requires in drafters.¶
Columns: Type Suffix, Title, Recommended HTTP Status Code, Reference. The type URI is "https://iana.org/assignments/sdcp#" followed by the suffix. Initial contents: the entries of Table 2, with reference RFC XXXX. Policy: Specification Required.¶
Initial contents: 0 Reserved; 1 greedy; 2 speculative-sampling; each with reference RFC XXXX. A registration MUST specify the acceptance test, the additional token, and the information a proposal must carry.¶
Initial contents: 0 continue; 1 reduce; 2 switch; 3 self_decode; 4 finish; 5 fail.¶
A contract with greedy rule, m_max 8, max_new 256, stop token 2, low_rounds 3, and fallback ladder [reduce, switch, self_decode, fail], with target and tokenizer digests equal to the KVSP test vector digests, has the following deterministic CBOR encoding (143 octets):¶
a8 64 72 75 6c 65 01 64 73 74 6f 70 81 02 65 6d 5f 6d 61 78 08 66 74 61 72 67 65 74 58 20 25 4e 52 8c 66 9a 20 84 c9 72 72 98 ca cb 83 e8 85 4e 0a 6f f8 0e 2b f1 80 96 b9 dc e0 f7 eb da 67 6d 61 78 5f 6e 65 77 19 01 00 68 66 61 6c 6c 62 61 63 6b 84 01 02 03 05 69 74 6f 6b 65 6e 69 7a 65 72 58 20 9f 42 a7 a2 9d ca 30 63 ad 26 d7 be 7f e6 a9 30 fa 38 af b1 a7 03 e4 f7 ff 53 8d b4 e1 33 b1 9c 6a 6c 6f 77 5f 72 6f 75 6e 64 73 03¶
For the prompt [1, 2, 3, 4] and committed output tokens 17 and 42:¶
contract_digest = bcae60a92c622b03fa1c229bac9f5654
076957e2f1a8bedcb3549ce4a6328e1f
d[0] = 186b373698d5baf27716830811ffa641
4887dd0a4f9d13db6c00e19bdb57b76e
d[1] = 3372e0fd91b77611c3502da5b0a5c8a5
1540e055d7abdd0d1bcf91110e142c1c
d[2] = 469bbe77ceb5b1540a4d61d826a3117e
357e097c5ac20271306633559fccdf1f
¶
Example round under the greedy rule: committed n = 2; the drafter proposes [7, 9, 11] (M = 3); the target argmax tokens at the three positions are 7, 9, and 13, and at the fourth position 5. Then a = 2, the additional token is 13 (the correction at the first mismatch), emitted = [7, 9, 13], and the new committed length is 5. The round is not low, so c is reset and the action is "continue".¶
The authors thank colleagues working on disaggregated inference serving for discussions on separating drafting from verification.¶