Skip to content

Commit df5e2a6

Browse files
mudlerlocalai-org-maint-bot
authored andcommitted
feat(vllm-cpp): move to vllm.cpp ABI v10 and expose jump-forward decoding
vllm.cpp landed ABI v10 on main while this branch was open. The backend's runtime handshake refuses any library whose reported ABI differs from the mirrors', so the pin and the Go PODs move together or not at all. v10 appends one int32, `enable_jump_forward`, AFTER the v9 fields. Nothing else in the config surface changed: the SGLang reconciliation that carried it explicitly dropped its own duplicate scheduler_policy int in favour of the v9 `scheduling_policy` string this branch already wires, and a diff of EngineParams and the server flags across the window turns up jump forward and nothing else. So the exposure is one new knob, `engine_args.enable_jump_forward` - SGLang's grammar-speed subset, which emits grammar-forced tokens without spending a model step and therefore only affects constrained requests. It is the SECOND tri-state on this struct, and it repeats the trap the first one had: 0 is not "off", it is "defer" (to the environment here, to the model capability for prefix caching), so an explicit `false` has to reach the engine as 2. The bool->tri-state helper and the log renderer are now shared rather than duplicated per field, and named for the encoding instead of for prefix caching, since the next tri-state will want them too. The docs say this outright, because "omitting the key" and "setting it false" being different is not guessable. The Go mirror grows the field plus an EXPLICIT trailing pad: the struct is 8-aligned and now ends on a lone int32, so it is 88 bytes rather than 84. The offset assertions cover it, and the real-library handshake spec (VLLM_CPP_LIBRARY against a CPU libvllm.so built at the new pin) confirms the version agrees: 43 specs pass, ABI reported 10. Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Assisted-by: Claude Code:claude-opus-5 [ClaudeCode]
1 parent 8ea2b9d commit df5e2a6

6 files changed

Lines changed: 73 additions & 27 deletions

File tree

backend/go/vllm-cpp/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ JOBS?=$(shell nproc --ignore=1 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || e
1111

1212
# vllm.cpp version
1313
VLLM_CPP_REPO?=https://github.com/mudler/vllm.cpp
14-
VLLM_CPP_VERSION?=eec09bed5a03457837b499781c23d8e44f106813
14+
VLLM_CPP_VERSION?=f384edcd25950bdf785a17d9230f249b99bc841b
1515

1616
# The backend consumes only the stable C ABI (libvllm + include/vllm.h), so the
1717
# server, examples and tests of the engine are never built here.

backend/go/vllm-cpp/backend.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ func (v *VllmCpp) Load(opts *pb.ModelOptions) error {
146146
mp.MaxNumBatchedTokens = v.opts.maxNumBatchedTokens
147147
}
148148
mp.EnablePrefixCaching = v.opts.enablePrefixCaching
149+
mp.EnableJumpForward = v.opts.enableJumpForward
149150

150151
// Every string below is borrowed by C for the duration of the load call
151152
// only (the library copies what it keeps), so the backing slices just have
@@ -172,7 +173,8 @@ func (v *VllmCpp) Load(opts *pb.ModelOptions) error {
172173
"blockSize", mp.BlockSize, "numBlocks", mp.NumBlocks,
173174
"maxModelLen", mp.MaxModelLen, "maxNumSeqs", mp.MaxNumSeqs,
174175
"maxNumBatchedTokens", mp.MaxNumBatchedTokens,
175-
"prefixCaching", prefixCachingName(mp.EnablePrefixCaching),
176+
"prefixCaching", triStateName(mp.EnablePrefixCaching),
177+
"jumpForward", triStateName(mp.EnableJumpForward),
176178
"schedulingPolicy", v.opts.schedulingPolicy,
177179
"speculativeConfig", v.opts.speculativeConfig,
178180
"kvTransferConfig", v.opts.kvTransferConfig)

backend/go/vllm-cpp/govllmcpp.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package main
22

3-
// purego bindings for the vllm.cpp stable C ABI (include/vllm.h, ABI v9).
3+
// purego bindings for the vllm.cpp stable C ABI (include/vllm.h, ABI v10).
44
//
55
// The structs below are hand-mirrored PODs of the C declarations, with
66
// explicit padding so the Go layout matches the C layout on linux/darwin
@@ -18,24 +18,25 @@ import (
1818
)
1919

2020
// abiVersion is the VLLM_ABI_VERSION this file mirrors (vllm.h).
21-
const abiVersion = 9
21+
const abiVersion = 10
2222

23-
// Prefix-caching tri-state (vllm_model_params.enable_prefix_caching, ABI v7).
24-
// 0 is the model-capability default, which is what a config that says nothing
25-
// about prefix caching must produce.
23+
// The ABI's tri-state toggles (enable_prefix_caching ABI v7,
24+
// enable_jump_forward ABI v10) share one encoding: 0 is NOT "off", it is
25+
// "defer" - to the model capability for prefix caching, to the environment for
26+
// jump forward. Only 2 is an explicit off.
2627
const (
27-
prefixCachingModelDefault int32 = 0
28-
prefixCachingOn int32 = 1
29-
prefixCachingOff int32 = 2
28+
triStateDefer int32 = 0
29+
triStateOn int32 = 1
30+
triStateOff int32 = 2
3031
)
3132

32-
// prefixCachingName renders the tri-state for the load log line, where "0"
33-
// would otherwise read as "off" rather than "whatever the model defaults to".
34-
func prefixCachingName(state int32) string {
33+
// triStateName renders a tri-state for the load log line, where "0" would
34+
// otherwise read as "off" rather than "whatever the default resolves to".
35+
func triStateName(state int32) string {
3536
switch state {
36-
case prefixCachingOn:
37+
case triStateOn:
3738
return "on"
38-
case prefixCachingOff:
39+
case triStateOff:
3940
return "off"
4041
default:
4142
return "model-default"
@@ -47,9 +48,10 @@ const (
4748
vllmOK = 0
4849
)
4950

50-
// cModelParams mirrors vllm_model_params. Every field is naturally aligned on
51-
// LP64 (the int32 pairs sit together), so the Go layout needs no explicit
52-
// padding; the offsets are asserted in vllmcpp_test.go.
51+
// cModelParams mirrors vllm_model_params. The int32 fields sit in pairs so the
52+
// interior needs no padding on LP64, but the struct is 8-aligned (it holds
53+
// pointers) and ends on a lone int32, so the trailing pad is explicit. Offsets
54+
// and total size are asserted in vllmcpp_test.go.
5355
type cModelParams struct {
5456
ModelPath uintptr // const char*
5557
TokenizerConfigPath uintptr // const char*; NULL = <model_dir>/... (ABI v9)
@@ -64,6 +66,8 @@ type cModelParams struct {
6466
MaxNumBatchedTokens int32 // <= 0 = per-arch default (ABI v9)
6567
SchedulingPolicy uintptr // const char*; NULL = "fcfs" (ABI v9)
6668
KVTransferConfig uintptr // const char* JSON; NULL = no connector (ABI v9)
69+
EnableJumpForward int32 // tri-state 0/1/2 (ABI v10)
70+
_ [4]byte // trailing pad to the struct's 8-byte alignment
6771
}
6872

6973
// cSamplingParams mirrors vllm_sampling_params (ABI v2, structured fields

backend/go/vllm-cpp/options.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ type loadOptions struct {
4242
// Automatic prefix caching tri-state (ABI v7): 0 = the model-capability
4343
// default, 1 = force on, 2 = force off.
4444
enablePrefixCaching int32
45+
// Jump-forward decoding tri-state (ABI v10), SGLang's grammar-speed subset:
46+
// 0 = defer to the environment (VT_ENABLE_JUMP_FORWARD, default off),
47+
// 1 = force on, 2 = force off.
48+
enableJumpForward int32
4549
// Scheduler admission policy (ABI v9): "" = fcfs, else fcfs|priority|lpm.
4650
schedulingPolicy string
4751
// Engine-side parser selection (ABI v4/v5). Empty = the engine
@@ -100,7 +104,11 @@ func applyOptionsList(lo *loadOptions, options []string) {
100104
lo.tokenizerConfigPath = strings.TrimSpace(v)
101105
case "enable_prefix_caching", "enable_radix_attention":
102106
if b, err := strconv.ParseBool(strings.TrimSpace(v)); err == nil {
103-
lo.enablePrefixCaching = prefixCachingTriState(b)
107+
lo.enablePrefixCaching = boolTriState(b)
108+
}
109+
case "enable_jump_forward":
110+
if b, err := strconv.ParseBool(strings.TrimSpace(v)); err == nil {
111+
lo.enableJumpForward = boolTriState(b)
104112
}
105113
}
106114
}
@@ -145,23 +153,28 @@ func applyEngineArgs(lo *loadOptions, engineArgs string) {
145153
lo.kvTransferConfig = jsonDocument(v, lo.kvTransferConfig, k)
146154
case "enable_prefix_caching", "enable_radix_attention":
147155
if b, ok := v.(bool); ok {
148-
lo.enablePrefixCaching = prefixCachingTriState(b)
156+
lo.enablePrefixCaching = boolTriState(b)
157+
}
158+
case "enable_jump_forward":
159+
if b, ok := v.(bool); ok {
160+
lo.enableJumpForward = boolTriState(b)
149161
}
150162
default:
151163
xlog.Debug("[vllm-cpp] ignoring unknown engine_args key", "key", k)
152164
}
153165
}
154166
}
155167

156-
// prefixCachingTriState maps a YAML/JSON boolean onto the C ABI's tri-state. An
168+
// boolTriState maps a YAML/JSON boolean onto the ABI's tri-state encoding. An
157169
// explicit `false` must reach the engine as force-OFF (2), NOT as the 0 that
158-
// means "let the model capability decide" - those differ for the hybrid archs,
159-
// which default the cache off, and for the dense ones, which default it on.
160-
func prefixCachingTriState(on bool) int32 {
170+
// means "defer". The difference is real in both directions: prefix caching
171+
// defaults ON for dense archs and OFF for hybrid ones, and jump forward defers
172+
// to VT_ENABLE_JUMP_FORWARD.
173+
func boolTriState(on bool) int32 {
161174
if on {
162-
return prefixCachingOn
175+
return triStateOn
163176
}
164-
return prefixCachingOff
177+
return triStateOff
165178
}
166179

167180
// jsonDocument normalises an object-valued engine_args entry to a JSON string

backend/go/vllm-cpp/vllmcpp_test.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@ var _ = Describe("C ABI struct mirrors", func() {
3535
Expect(unsafe.Offsetof(p.MaxNumBatchedTokens)).To(Equal(uintptr(60)))
3636
Expect(unsafe.Offsetof(p.SchedulingPolicy)).To(Equal(uintptr(64)))
3737
Expect(unsafe.Offsetof(p.KVTransferConfig)).To(Equal(uintptr(72)))
38-
Expect(unsafe.Sizeof(p)).To(Equal(uintptr(80)))
38+
Expect(unsafe.Offsetof(p.EnableJumpForward)).To(Equal(uintptr(80)))
39+
// 88, not 84: the struct is 8-aligned (it holds pointers), so the
40+
// trailing int32 is padded out. Go pads identically.
41+
Expect(unsafe.Sizeof(p)).To(Equal(uintptr(88)))
3942
})
4043

4144
It("cSamplingParams matches vllm_sampling_params (ABI v8)", func() {
@@ -192,6 +195,23 @@ var _ = Describe("engine_args", func() {
192195
Expect(lo.enablePrefixCaching).To(Equal(int32(1)))
193196
})
194197

198+
It("maps enable_jump_forward onto its own tri-state", func() {
199+
// ABI v10. Same tri-state shape as prefix caching, and the same trap:
200+
// an explicit false must be force-OFF (2), not the 0 that defers to the
201+
// environment.
202+
on := parseOptions(&pb.ModelOptions{EngineArgs: `{"enable_jump_forward": true}`})
203+
Expect(on.enableJumpForward).To(Equal(int32(1)))
204+
off := parseOptions(&pb.ModelOptions{EngineArgs: `{"enable_jump_forward": false}`})
205+
Expect(off.enableJumpForward).To(Equal(int32(2)))
206+
unset := parseOptions(&pb.ModelOptions{EngineArgs: `{"max_num_seqs": 4}`})
207+
Expect(unset.enableJumpForward).To(Equal(int32(0)))
208+
})
209+
210+
It("reads enable_jump_forward from the legacy options list too", func() {
211+
lo := parseOptions(&pb.ModelOptions{Options: []string{"enable_jump_forward:true"}})
212+
Expect(lo.enableJumpForward).To(Equal(int32(1)))
213+
})
214+
195215
It("lets engine_args override the legacy options list", func() {
196216
lo := parseOptions(&pb.ModelOptions{
197217
Options: []string{"max_num_seqs:8", "block_size:16"},

docs/content/features/text-generation.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,7 @@ engine_args:
948948
| `max_num_seqs` | Max concurrent sequences the scheduler admits | 8 |
949949
| `max_num_batched_tokens` | Per-step chunked-prefill token budget | per-arch (2048 dense, 4096/8192 MoE) |
950950
| `enable_prefix_caching` | Automatic prefix caching; `enable_radix_attention` is an accepted alias | model default |
951+
| `enable_jump_forward` | Jump-forward decoding, which emits grammar-forced tokens without a model step. Only affects constrained requests (`grammar`, JSON schema) | off |
951952
| `scheduling_policy` | `fcfs`, `priority`, or `lpm` | `fcfs` |
952953
| `tool_parser` / `reasoning_parser` | Force a parser instead of chat-template auto-detection | auto |
953954
| `tokenizer_config` | Override the `tokenizer_config.json` the chat template is read from | `<model_dir>/tokenizer_config.json` |
@@ -959,6 +960,12 @@ cost of decode latency for requests queued behind it. The default deliberately
959960
does not scale with `max_num_seqs`, which is what keeps a large concurrent
960961
prefill from blowing up the per-step activation on the hybrid architectures.
961962

963+
`enable_prefix_caching` and `enable_jump_forward` are tri-state at the engine
964+
boundary: omitting the key defers to a default (the model's own capability for
965+
prefix caching, an environment variable for jump forward), while an explicit
966+
`false` forces the feature off. Those are genuinely different - prefix caching
967+
defaults *on* for dense models - so write the key only when you mean to override.
968+
962969
#### Speculative decoding
963970

964971
`speculative_config:` takes the same JSON object as vLLM's

0 commit comments

Comments
 (0)