Skip to content

Commit 04309fb

Browse files
committed
Add explicit token-producer backend selector
Signed-off-by: zhouyou9505 <zhouyou9505@gmail.com>
1 parent ad9c491 commit 04309fb

10 files changed

Lines changed: 129 additions & 31 deletions

File tree

config/charts/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ router:
305305

306306
Runs a tokenizer sidecar that EPP queries to tokenize incoming requests, enabling precise, token-count-aware routing policies (e.g., precise prefix-cache matching).
307307

308-
The sidecar runs vLLM's `vllm launch render <modelName>` and exposes `/v1/completions/render` and `/v1/chat/completions/render` over loopback HTTP. Wire EPP to it via `router.epp.pluginsCustomConfig` with `type: token-producer` and `vllm:`.
308+
The sidecar runs vLLM's `vllm launch render <modelName>` and exposes `/v1/completions/render` and `/v1/chat/completions/render` over loopback HTTP. Wire EPP to it via `router.epp.pluginsCustomConfig` with `type: token-producer`, `backend: vllm`, and `vllm:`.
309309

310310
#### Tokenizer Sidecar Parameters
311311

@@ -409,8 +409,8 @@ spec:
409409

410410
**Step 2 — point EPP at the socket.** Configure the tokenizer plugin via
411411
`router.epp.pluginsCustomConfig` (the chart writes this into the EPP config
412-
mounted at `/config`). Select the deprecated UDS backend with
413-
`udsTokenizerConfig`:
412+
mounted at `/config`). Select the deprecated UDS backend with `backend: uds`
413+
and point it at the socket with `udsTokenizerConfig`:
414414

415415
```yaml
416416
router:
@@ -420,6 +420,7 @@ router:
420420
plugins:
421421
- type: token-producer
422422
parameters:
423+
backend: uds
423424
modelName: "Qwen/Qwen3-32B"
424425
udsTokenizerConfig:
425426
socketFile: /tmp/tokenizer/tokenizer-uds.socket

deploy/config/epp-mm-embeddings-cache-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ kind: EndpointPickerConfig
44
plugins:
55
- type: token-producer
66
parameters:
7+
backend: vllm
78
modelName: hf-repo/model-name # set to the model used in the vLLM deployment
89
vllm:
910
url: http://localhost:8000

deploy/config/epp-precise-prefix-cache-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ kind: EndpointPickerConfig
44
plugins:
55
- type: token-producer
66
parameters:
7+
backend: vllm
78
modelName: hf-repo/model-name # set to the model used in the vLLM deployment
89
vllm:
910
url: http://localhost:8000

deploy/config/epp-precise-prefix-cache-split-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ kind: EndpointPickerConfig
55
plugins:
66
- type: token-producer
77
parameters:
8+
backend: vllm
89
modelName: hf-repo/model-name # set to the model used in the vLLM deployment
910
vllm:
1011
url: http://localhost:8000

deploy/config/sim-epp-kvcache-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ kind: EndpointPickerConfig
55
plugins:
66
- type: token-producer
77
parameters:
8+
backend: vllm
89
modelName: TinyLlama/TinyLlama-1.1B-Chat-v1.0 # replace to use a different model
910
vllm:
1011
url: http://localhost:8000

deploy/config/sim-epp-tokenizer-vllm-http-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ kind: EndpointPickerConfig
77
plugins:
88
- type: token-producer
99
parameters:
10+
backend: vllm
1011
modelName: "${MODEL_NAME}"
1112
vllm:
1213
url: "http://localhost:8000"

pkg/epp/framework/plugins/requestcontrol/dataproducer/tokenizer/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ upstream list shape, sorted by placeholder offset.
2121

2222
Backend selection:
2323

24+
- Set **`backend: estimate|vllm|uds`** to select a backend explicitly.
25+
When omitted, the plugin keeps the legacy inference rules below.
2426
- **`estimate`** (default): tokenizer-free byte-packing — no model, no service.
2527
Selected when no backend is set, and auto-created by the framework for any
2628
config whose plugins consume `TokenizedPrompt` (prefix cache, context-length,
@@ -48,7 +50,8 @@ Backend selection:
4850

4951
| Parameter | Default | Description |
5052
| ---------------- | ----------------------- | ----------------------------------------------------------------- |
51-
| `modelName` | – (required for `vllm`) | Model whose tokenizer should be loaded / sent in render requests. |
53+
| `backend` | inferred | Explicit backend selector: `estimate`, `vllm`, or `uds`. |
54+
| `modelName` | – (required for `vllm`/`uds`) | Model whose tokenizer should be loaded / sent in render requests. |
5255
| `vllm.url` | `http://localhost:8000` | Base URL of the vLLM render endpoint (no trailing slash). |
5356
| `vllm.timeout` | `5s` | Per-request timeout for text-only requests. |
5457
| `vllm.mmTimeout` | `30s` | Per-request timeout for multimodal requests. |
@@ -93,6 +96,7 @@ Plugin config — sidecar (loopback):
9396
```yaml
9497
- type: token-producer
9598
parameters:
99+
backend: vllm
96100
modelName: "${MODEL_NAME}"
97101
vllm:
98102
url: "http://localhost:8000" # optional; this is the default
@@ -103,6 +107,7 @@ Plugin config — dedicated render Service:
103107
```yaml
104108
- type: token-producer
105109
parameters:
110+
backend: vllm
106111
modelName: "${MODEL_NAME}"
107112
vllm:
108113
url: "http://vllm-render.default.svc.cluster.local:8000"
@@ -124,6 +129,7 @@ Before:
124129
```yaml
125130
- type: token-producer
126131
parameters:
132+
backend: uds
127133
modelName: "${MODEL_NAME}"
128134
udsTokenizerConfig:
129135
socketFile: /tmp/tokenizer/tokenizer-uds.socket
@@ -134,6 +140,7 @@ After:
134140
```yaml
135141
- type: token-producer
136142
parameters:
143+
backend: vllm
137144
modelName: "${MODEL_NAME}"
138145
vllm:
139146
url: "http://localhost:8000" # or a shared render Service

pkg/epp/framework/plugins/requestcontrol/dataproducer/tokenizer/tokenizer.go

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,23 @@ const (
5858

5959
var TokenizedPromptDataKey = plugin.NewDataKey(tokenizedPromptKeyID, PluginType)
6060

61+
type tokenizerBackend string
62+
63+
const (
64+
tokenizerBackendEstimate tokenizerBackend = "estimate"
65+
tokenizerBackendVLLM tokenizerBackend = "vllm"
66+
tokenizerBackendUDS tokenizerBackend = "uds"
67+
)
68+
6169
// tokenizerPluginConfig holds the configuration for the tokenizer plugin.
6270
//
63-
// Backend selection: `vllm` or `modelName` selects the vLLM HTTP /render
64-
// backend; `udsTokenizerConfig` selects the deprecated gRPC-over-UDS backend;
65-
// `estimate` selects the tokenizer-free byte-packing backend, which is also the
66-
// zero-config default when no backend is set.
71+
// Backend selection: `backend` explicitly selects the backend when set. When it
72+
// is omitted, existing configs continue to infer the backend from `estimate`,
73+
// `vllm`, `udsTokenizerConfig`, or `modelName`.
6774
type tokenizerPluginConfig struct {
75+
// Backend explicitly selects "estimate", "vllm", or "uds". When omitted,
76+
// the plugin infers the backend from legacy config fields.
77+
Backend tokenizerBackend `json:"backend,omitempty"`
6878
// TokenizerConfig configures the deprecated gRPC-over-UDS backend.
6979
//
7080
// Deprecated: the UDS tokenizer backend is deprecated and will be removed
@@ -128,29 +138,67 @@ func PluginFactory(name string, rawParameters *json.Decoder, handle plugin.Handl
128138
}
129139
}
130140

131-
estimate := config.Estimate != nil
132-
uds := config.TokenizerConfig.IsEnabled()
133-
vllm := config.VLLM != nil || config.ModelName != ""
134-
if (estimate && (uds || vllm)) || (uds && vllm) {
135-
return nil, fmt.Errorf("invalid configuration for '%s' plugin: only one of 'estimate', 'vllm', or 'udsTokenizerConfig' may be set", PluginType)
141+
p, err := NewPlugin(handle.Context(), name, &config)
142+
if err != nil {
143+
return nil, err
136144
}
137-
// modelName is required only by the real-tokenizer backends; the zero-config
138-
// path selects the estimate backend, which needs none.
139-
if (uds || vllm) && config.ModelName == "" {
140-
return nil, fmt.Errorf("invalid configuration for '%s' plugin: 'modelName' must be specified", PluginType)
145+
146+
return p, nil
147+
}
148+
149+
func (config *tokenizerPluginConfig) validateAndSelectBackend() (tokenizerBackend, error) {
150+
selected, err := config.selectBackend()
151+
if err != nil {
152+
return "", err
141153
}
142-
if config.Estimate != nil && config.Estimate.Image != nil {
143-
if m := config.Estimate.Image.Mode; m != "" && m != imageModeDynamic && m != imageModeStatic {
144-
return nil, fmt.Errorf("invalid configuration for '%s' plugin: estimate.image.mode must be %q or %q", PluginType, imageModeDynamic, imageModeStatic)
154+
155+
switch selected {
156+
case tokenizerBackendVLLM:
157+
if config.ModelName == "" {
158+
return "", fmt.Errorf("invalid configuration for '%s' plugin: 'modelName' must be specified", PluginType)
159+
}
160+
case tokenizerBackendUDS:
161+
if config.ModelName == "" {
162+
return "", fmt.Errorf("invalid configuration for '%s' plugin: 'modelName' must be specified", PluginType)
163+
}
164+
if !config.TokenizerConfig.IsEnabled() {
165+
return "", fmt.Errorf("invalid configuration for '%s' plugin: 'udsTokenizerConfig' must be specified when backend is %q", PluginType, tokenizerBackendUDS)
166+
}
167+
case tokenizerBackendEstimate:
168+
if config.Estimate != nil && config.Estimate.Image != nil {
169+
if m := config.Estimate.Image.Mode; m != "" && m != imageModeDynamic && m != imageModeStatic {
170+
return "", fmt.Errorf("invalid configuration for '%s' plugin: estimate.image.mode must be %q or %q", PluginType, imageModeDynamic, imageModeStatic)
171+
}
145172
}
146173
}
147174

148-
p, err := NewPlugin(handle.Context(), name, &config)
149-
if err != nil {
150-
return nil, err
175+
return selected, nil
176+
}
177+
178+
func (config *tokenizerPluginConfig) selectBackend() (tokenizerBackend, error) {
179+
if config.Backend != "" {
180+
switch config.Backend {
181+
case tokenizerBackendEstimate, tokenizerBackendVLLM, tokenizerBackendUDS:
182+
return config.Backend, nil
183+
default:
184+
return "", fmt.Errorf("invalid configuration for '%s' plugin: backend must be one of %q, %q, or %q", PluginType, tokenizerBackendEstimate, tokenizerBackendVLLM, tokenizerBackendUDS)
185+
}
151186
}
152187

153-
return p, nil
188+
estimate := config.Estimate != nil
189+
uds := config.TokenizerConfig.IsEnabled()
190+
vllm := config.VLLM != nil || config.ModelName != ""
191+
if (estimate && (uds || vllm)) || (uds && vllm) {
192+
return "", fmt.Errorf("invalid configuration for '%s' plugin: only one of 'estimate', 'vllm', or 'udsTokenizerConfig' may be set", PluginType)
193+
}
194+
switch {
195+
case uds:
196+
return tokenizerBackendUDS, nil
197+
case vllm:
198+
return tokenizerBackendVLLM, nil
199+
default:
200+
return tokenizerBackendEstimate, nil
201+
}
154202
}
155203

156204
// LegacyPluginFactory wraps PluginFactory for the deprecated `tokenizer` type
@@ -166,13 +214,20 @@ func LegacyPluginFactory(name string, rawParameters *json.Decoder, handle plugin
166214
return PluginFactory(name, rawParameters, handle)
167215
}
168216

169-
// NewPlugin constructs the configured backend: udsTokenizerConfig (deprecated),
170-
// vllm /render (selected by 'vllm' or 'modelName'), or estimate byte-packing
171-
// (the default when no backend is set).
217+
// NewPlugin constructs the configured backend. The explicit backend selector
218+
// wins when set; otherwise legacy config inference is used.
172219
func NewPlugin(ctx context.Context, name string, config *tokenizerPluginConfig) (*Plugin, error) {
220+
if config == nil {
221+
config = &tokenizerPluginConfig{}
222+
}
223+
selected, err := config.validateAndSelectBackend()
224+
if err != nil {
225+
return nil, err
226+
}
227+
173228
var backend tokenInputProducer
174-
switch {
175-
case config.TokenizerConfig.IsEnabled():
229+
switch selected {
230+
case tokenizerBackendUDS:
176231
log.FromContext(ctx).Info(
177232
"DEPRECATION: the 'udsTokenizerConfig' parameter is deprecated and will be removed in a future release; set the 'vllm' parameter instead (see plugin README)",
178233
"pluginType", PluginType,
@@ -182,7 +237,7 @@ func NewPlugin(ctx context.Context, name string, config *tokenizerPluginConfig)
182237
return nil, fmt.Errorf("failed to initialize UDS tokenizer for '%s' plugin - %w", PluginType, err)
183238
}
184239
backend = renderBackend{tk: uds}
185-
case config.VLLM != nil || config.ModelName != "":
240+
case tokenizerBackendVLLM:
186241
cfg := config.VLLM
187242
if cfg == nil {
188243
cfg = &vllmConfig{}
@@ -192,7 +247,7 @@ func NewPlugin(ctx context.Context, name string, config *tokenizerPluginConfig)
192247
return nil, fmt.Errorf("failed to initialize vLLM HTTP renderer for '%s' plugin - %w", PluginType, err)
193248
}
194249
backend = renderBackend{tk: renderer}
195-
default:
250+
case tokenizerBackendEstimate:
196251
backend = estimateBackend{img: newImageEstimator(config.Estimate)}
197252
}
198253

pkg/epp/framework/plugins/requestcontrol/dataproducer/tokenizer/tokenizer_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ func TestProduceTimeout(t *testing.T) {
6767
require.NoError(t, err)
6868
assert.Equal(t, 45*time.Second, vp2.ProduceTimeout())
6969

70+
// Explicit backend selection can use vLLM's default config block.
71+
vp3, err := NewPlugin(ctx, "tok", &tokenizerPluginConfig{Backend: tokenizerBackendVLLM, ModelName: "m"})
72+
require.NoError(t, err)
73+
assert.Equal(t, defaultHTTPRenderMMTimeout, vp3.ProduceTimeout())
74+
7075
// Estimate backend declares none, so the director keeps its default.
7176
ep, err := NewPlugin(ctx, "tok", &tokenizerPluginConfig{Estimate: &estimateConfig{}})
7277
require.NoError(t, err)
@@ -107,6 +112,29 @@ func TestPluginFactory_Validation(t *testing.T) {
107112
params: `{"estimate":{"image":{"mode":"static","static":{"staticToken":8}}}}`,
108113
expectErr: false,
109114
},
115+
{
116+
name: "explicit estimate backend overrides legacy vllm inference",
117+
params: `{"backend":"estimate","modelName":"m","vllm":{}}`,
118+
expectErr: false,
119+
},
120+
{
121+
name: "explicit vllm backend requires modelName",
122+
params: `{"backend":"vllm","vllm":{}}`,
123+
expectErr: true,
124+
errContain: "'modelName' must be specified",
125+
},
126+
{
127+
name: "explicit uds backend requires tokenizer config",
128+
params: `{"backend":"uds","modelName":"m"}`,
129+
expectErr: true,
130+
errContain: "'udsTokenizerConfig' must be specified",
131+
},
132+
{
133+
name: "invalid backend selector",
134+
params: `{"backend":"unknown"}`,
135+
expectErr: true,
136+
errContain: "backend must be one of",
137+
},
110138
{
111139
name: "invalid estimate image mode",
112140
params: `{"estimate":{"image":{"mode":"bogus"}}}`,

test/e2e/configs_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ kind: EndpointPickerConfig
185185
plugins:
186186
- type: token-producer
187187
parameters:
188+
backend: vllm
188189
modelName: Qwen/Qwen2.5-1.5B-Instruct
189190
vllm:
190191
url: http://localhost:8000
@@ -217,6 +218,7 @@ kind: EndpointPickerConfig
217218
plugins:
218219
- type: token-producer
219220
parameters:
221+
backend: vllm
220222
modelName: Qwen/Qwen2.5-1.5B-Instruct
221223
vllm:
222224
url: http://localhost:8000

0 commit comments

Comments
 (0)