mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-30 09:57:57 -04:00
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]
222 lines
7.5 KiB
Go
222 lines
7.5 KiB
Go
package main
|
|
|
|
// purego bindings for the vllm.cpp stable C ABI (include/vllm.h, ABI v10).
|
|
//
|
|
// The structs below are hand-mirrored PODs of the C declarations, with
|
|
// explicit padding so the Go layout matches the C layout on linux/darwin
|
|
// amd64+arm64. Struct-by-value entry points (the *_default helpers) are NOT
|
|
// bound - purego's struct-return support is platform-dependent - so the
|
|
// defaults are replicated here and guarded by the vllm_abi_version check at
|
|
// startup: a library whose ABI differs from what these mirrors were written
|
|
// against is refused before any request runs.
|
|
|
|
import (
|
|
"fmt"
|
|
"unsafe"
|
|
|
|
"github.com/ebitengine/purego"
|
|
)
|
|
|
|
// abiVersion is the VLLM_ABI_VERSION this file mirrors (vllm.h).
|
|
const abiVersion = 10
|
|
|
|
// The ABI's tri-state toggles (enable_prefix_caching ABI v7,
|
|
// enable_jump_forward ABI v10) share one encoding: 0 is NOT "off", it is
|
|
// "defer" - to the model capability for prefix caching, to the environment for
|
|
// jump forward. Only 2 is an explicit off.
|
|
const (
|
|
triStateDefer int32 = 0
|
|
triStateOn int32 = 1
|
|
triStateOff int32 = 2
|
|
)
|
|
|
|
// triStateName renders a tri-state for the load log line, where "0" would
|
|
// otherwise read as "off" rather than "whatever the default resolves to".
|
|
func triStateName(state int32) string {
|
|
switch state {
|
|
case triStateOn:
|
|
return "on"
|
|
case triStateOff:
|
|
return "off"
|
|
default:
|
|
return "model-default"
|
|
}
|
|
}
|
|
|
|
// vllm_status (vllm.h).
|
|
const (
|
|
vllmOK = 0
|
|
)
|
|
|
|
// cModelParams mirrors vllm_model_params. The int32 fields sit in pairs so the
|
|
// interior needs no padding on LP64, but the struct is 8-aligned (it holds
|
|
// pointers) and ends on a lone int32, so the trailing pad is explicit. Offsets
|
|
// and total size are asserted in vllmcpp_test.go.
|
|
type cModelParams struct {
|
|
ModelPath uintptr // const char*
|
|
TokenizerConfigPath uintptr // const char*; NULL = <model_dir>/... (ABI v9)
|
|
BlockSize int32
|
|
NumBlocks int32
|
|
MaxModelLen int32
|
|
MaxNumSeqs int32
|
|
ToolParser uintptr // const char*; NULL = auto-detect (ABI v4)
|
|
ReasoningParser uintptr // const char*; NULL = auto-detect (ABI v5)
|
|
SpeculativeConfig uintptr // const char* JSON; NULL = no speculation (ABI v6)
|
|
EnablePrefixCaching int32 // tri-state 0/1/2 (ABI v7)
|
|
MaxNumBatchedTokens int32 // <= 0 = per-arch default (ABI v9)
|
|
SchedulingPolicy uintptr // const char*; NULL = "fcfs" (ABI v9)
|
|
KVTransferConfig uintptr // const char* JSON; NULL = no connector (ABI v9)
|
|
EnableJumpForward int32 // tri-state 0/1/2 (ABI v10)
|
|
_ [4]byte // trailing pad to the struct's 8-byte alignment
|
|
}
|
|
|
|
// cSamplingParams mirrors vllm_sampling_params (ABI v2, structured fields
|
|
// included). Padding matches the C compiler's: the uint64 seed is 8-aligned,
|
|
// and each pointer following an int32 is 8-aligned.
|
|
type cSamplingParams struct {
|
|
Temperature float32
|
|
TopP float32
|
|
TopK int32
|
|
MinP float32
|
|
MaxTokens int32
|
|
_ [4]byte
|
|
Seed uint64
|
|
HasSeed int32
|
|
PresencePenalty float32
|
|
FrequencyPenalty float32
|
|
RepetitionPenalty float32
|
|
MinTokens int32
|
|
IgnoreEOS int32
|
|
Stop uintptr // const char* const*
|
|
NStop int32
|
|
_ [4]byte
|
|
StructuredJSON uintptr // const char*
|
|
StructuredRegex uintptr // const char*
|
|
StructuredChoice uintptr // const char* const*
|
|
NStructuredChoice int32
|
|
_ [4]byte
|
|
StructuredGrammar uintptr // const char*
|
|
StructuredJSONObject int32
|
|
_ [4]byte
|
|
// ABI v8 tail. LocalAI installs no custom logits processor, but the fields
|
|
// MUST be mirrored: the C side reads them off the pointer we hand it, so a
|
|
// Go struct that stopped at StructuredJSONObject would have the engine read
|
|
// 16 bytes past our allocation and call whatever garbage sat there.
|
|
LogitsProcessor uintptr // vllm_logits_processor; NULL = none
|
|
LogitsProcessorUserData uintptr // void*
|
|
}
|
|
|
|
// cCompletion mirrors vllm_completion.
|
|
type cCompletion struct {
|
|
Text uintptr // char*, caller-owned
|
|
FinishReason uintptr // const char*, library-owned
|
|
PromptTokens int32
|
|
CompletionTokens int32
|
|
}
|
|
|
|
// defaultSamplingParams mirrors vllm_sampling_params_default().
|
|
func defaultSamplingParams() cSamplingParams {
|
|
return cSamplingParams{
|
|
Temperature: 1.0,
|
|
TopP: 1.0,
|
|
MaxTokens: 16,
|
|
RepetitionPenalty: 1.0,
|
|
}
|
|
}
|
|
|
|
// defaultModelParams mirrors vllm_model_params_default().
|
|
func defaultModelParams() cModelParams {
|
|
return cModelParams{
|
|
BlockSize: 32,
|
|
NumBlocks: 256,
|
|
MaxNumSeqs: 8,
|
|
}
|
|
}
|
|
|
|
var (
|
|
vllmEngineLoad func(params, out unsafe.Pointer) int32
|
|
vllmEngineFree func(engine uintptr)
|
|
vllmComplete func(engine uintptr, prompt string, params, out unsafe.Pointer) int32
|
|
vllmCompleteStream func(engine uintptr, prompt string, params unsafe.Pointer, cb uintptr, userData uintptr) int32
|
|
vllmChat func(engine uintptr, requestJSON string, out unsafe.Pointer) int32
|
|
vllmChatStream func(engine uintptr, requestJSON string, cb uintptr, userData uintptr) int32
|
|
vllmStringFree func(s uintptr)
|
|
vllmCompletionFree func(out unsafe.Pointer)
|
|
vllmLastError func() string
|
|
vllmVersion func() string
|
|
vllmABIVersion func() int32
|
|
)
|
|
|
|
type libFunc struct {
|
|
ptr any
|
|
name string
|
|
}
|
|
|
|
// registerLib dlopens libvllm and binds the C ABI, refusing an ABI-version
|
|
// mismatch (the struct mirrors above would be undefined behavior against a
|
|
// different layout).
|
|
func registerLib(libName string) error {
|
|
lib, err := purego.Dlopen(libName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
|
|
if err != nil {
|
|
return fmt.Errorf("vllm-cpp: dlopen %s: %w", libName, err)
|
|
}
|
|
for _, lf := range []libFunc{
|
|
{&vllmEngineLoad, "vllm_engine_load"},
|
|
{&vllmEngineFree, "vllm_engine_free"},
|
|
{&vllmComplete, "vllm_complete"},
|
|
{&vllmCompleteStream, "vllm_complete_stream"},
|
|
{&vllmChat, "vllm_chat"},
|
|
{&vllmChatStream, "vllm_chat_stream"},
|
|
{&vllmStringFree, "vllm_string_free"},
|
|
{&vllmCompletionFree, "vllm_completion_free"},
|
|
{&vllmLastError, "vllm_last_error"},
|
|
{&vllmVersion, "vllm_version"},
|
|
{&vllmABIVersion, "vllm_abi_version"},
|
|
} {
|
|
purego.RegisterLibFunc(lf.ptr, lib, lf.name)
|
|
}
|
|
if v := vllmABIVersion(); v != abiVersion {
|
|
return fmt.Errorf("vllm-cpp: ABI mismatch: library reports v%d, backend built against v%d", v, abiVersion)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// cString returns a NUL-terminated byte slice for s. The backing array may be
|
|
// passed to C for the duration of a call (the ABI borrows and copies); keep it
|
|
// alive across the call with runtime.KeepAlive.
|
|
func cString(s string) []byte {
|
|
b := make([]byte, len(s)+1)
|
|
copy(b, s)
|
|
return b
|
|
}
|
|
|
|
// cStringArray builds a NULL-free array of C-string pointers plus the backing
|
|
// buffers that must stay alive for the duration of the C call.
|
|
func cStringArray(ss []string) (ptrs []uintptr, backing [][]byte) {
|
|
backing = make([][]byte, 0, len(ss))
|
|
ptrs = make([]uintptr, 0, len(ss))
|
|
for _, s := range ss {
|
|
b := cString(s)
|
|
backing = append(backing, b)
|
|
ptrs = append(ptrs, uintptr(unsafe.Pointer(&b[0]))) // #nosec G103 -- borrowed by C for the call only
|
|
}
|
|
return ptrs, backing
|
|
}
|
|
|
|
// goString copies a NUL-terminated C string.
|
|
func goString(p uintptr) string {
|
|
if p == 0 {
|
|
return ""
|
|
}
|
|
//nolint:govet // C-owned pointer handed over by purego, valid for this call
|
|
base := unsafe.Pointer(p) // #nosec G103 -- C-owned, copied out immediately
|
|
n := 0
|
|
for *(*byte)(unsafe.Add(base, n)) != 0 {
|
|
n++
|
|
}
|
|
if n == 0 {
|
|
return ""
|
|
}
|
|
return string(unsafe.Slice((*byte)(base), n))
|
|
}
|