23namespace entropic::speculative {
29constexpr int kSpecVocabMaxSizeDifference = 128;
30constexpr int kSpecVocabCheckStartTokenId = 5;
59std::optional<std::string> check_arch_gate(
60 const llama_model* target) {
61 if (llama_model_is_recurrent(target)) {
63 "target model is recurrent (Mamba/RWKV) — speculative "
64 "decoding is incompatible with recurrent architectures "
65 "at the v2.1.11 llama.cpp pin"};
67 if (llama_model_is_hybrid(target)) {
69 "target model is hybrid (e.g., QWEN35/QWEN35MOE, "
70 "NEMOTRON_H, JAMBA, GRANITE_HYBRID) — speculative "
71 "decoding produces divergent state at split-prefill "
72 "boundaries on hybrid SSM architectures at the v2.1.11 "
73 "llama.cpp pin (see proposal Implementation Log, Gate A)"};
87std::optional<std::string> check_vocab_type(
88 const llama_vocab* vt,
const llama_vocab* vd) {
89 if (llama_vocab_type(vt) != llama_vocab_type(vd)) {
91 "vocab type differs between target and draft models"};
105std::optional<std::string> check_bos(
106 const llama_vocab* vt,
const llama_vocab* vd) {
107 const bool add_t = llama_vocab_get_add_bos(vt);
108 const bool add_d = llama_vocab_get_add_bos(vd);
109 if (add_t != add_d) {
110 return std::string{
"BOS add-behavior differs"};
112 if (add_t && llama_vocab_bos(vt) != llama_vocab_bos(vd)) {
113 return std::string{
"BOS token id differs"};
127std::optional<std::string> check_eos(
128 const llama_vocab* vt,
const llama_vocab* vd) {
129 const bool add_t = llama_vocab_get_add_eos(vt);
130 const bool add_d = llama_vocab_get_add_eos(vd);
131 if (add_t != add_d) {
132 return std::string{
"EOS add-behavior differs"};
134 if (add_t && llama_vocab_eos(vt) != llama_vocab_eos(vd)) {
135 return std::string{
"EOS token id differs"};
150std::optional<std::string> check_vocab_size(
151 const llama_vocab* vt,
const llama_vocab* vd) {
152 const int nt = llama_vocab_n_tokens(vt);
153 const int nd = llama_vocab_n_tokens(vd);
154 const int diff = (nt > nd) ? (nt - nd) : (nd - nt);
155 if (diff > kSpecVocabMaxSizeDifference) {
157 "vocab size difference exceeds the speculative tolerance "
158 "(target=" + std::to_string(nt)
159 +
", draft=" + std::to_string(nd)
160 +
", max-allowed-diff="
161 + std::to_string(kSpecVocabMaxSizeDifference) +
")"};
179std::optional<std::string> check_token_text(
180 const llama_vocab* vt,
const llama_vocab* vd) {
181 const int nt = llama_vocab_n_tokens(vt);
182 const int nd = llama_vocab_n_tokens(vd);
183 const int end = std::min(nt, nd);
184 for (
int i = kSpecVocabCheckStartTokenId; i < end; ++i) {
185 const char* tt = llama_vocab_get_text(vt, i);
186 const char* td = llama_vocab_get_text(vd, i);
187 if (tt ==
nullptr || td ==
nullptr || std::strcmp(tt, td) != 0) {
189 "token text differs at id "
191 +
" — draft tokenizer is not a prefix-compatible "
192 "subset of the target"};
216std::string run_vocab_checks(
217 const llama_vocab* vt,
const llama_vocab* vd) {
218 using Check = std::optional<std::string> (*)(
219 const llama_vocab*,
const llama_vocab*);
220 static constexpr Check checks[] = {
221 &check_vocab_type, &check_bos, &check_eos,
222 &check_vocab_size, &check_token_text,
225 for (Check fn : checks) {
228 if (d.has_value()) { err = std::move(*d); }
246std::string build_compat_diagnostic(
247 const llama_model* target,
const llama_model* draft) {
249 if (target ==
nullptr || draft ==
nullptr) {
250 err =
"null model handle (target or draft)";
251 }
else if (
auto d = check_arch_gate(target); d.has_value()) {
254 const llama_vocab* vt = llama_model_get_vocab(target);
255 const llama_vocab* vd = llama_model_get_vocab(draft);
256 err = (vt ==
nullptr || vd ==
nullptr)
257 ? std::string{
"model vocab unavailable (target or draft)"}
258 : run_vocab_checks(vt, vd);
280 const llama_model* target,
const llama_model* draft) {
281 std::string err = build_compat_diagnostic(target, draft);
Tokenizer/architecture compatibility check for speculative decoding draft pairing.
CompatResult check_compat(const llama_model *target, const llama_model *draft)
Check whether a draft model can pair with a target for sequential speculative decoding.
Result of a draft/target compatibility check.