Skip to content

Commit 4a6c416

Browse files
authored
feat(epp): match whole prompt when both prefix caps are zero (llm-d#2058)
Zeroing both maxPrefixTokensToMatch and maxPrefixBlocksToMatch hashed no blocks at all, leaving the producer, its indexer, and its cleanup goroutine running while contributing nothing to scoring. Nothing in the config surface signalled that. Treat the pair as "no cap" instead. Prompt length is already bounded by the model server's context window, so an absent cap matches at most one context window of tokens without operators having to name a token count that tracks max_model_len by hand. A token cap smaller than the block size still resolves to zero blocks; that is a misconfiguration rather than a request for unlimited matching, so it keeps hashing nothing. Signed-off-by: Kaushik Mitra <kaushikmitra@google.com>
1 parent 6cc1dac commit 4a6c416

4 files changed

Lines changed: 68 additions & 9 deletions

File tree

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ For each request, the plugin consumes `request.Body.TokenizedPrompt` (token IDs)
1212

1313
- `autoTune` (bool, optional, default: `true`): Infer `blockSizeTokens` and `lruCapacityPerServer` from endpoint metrics when available.
1414
- `blockSizeTokens` (int, optional, default: `16`): Prefix block size in tokens. Used when `autoTune` is false or endpoint metrics are unavailable; ignored when auto-tuned from metrics. Values below the minimum of `64` are clamped up at request time (see #1158), so the `16` default is effectively `64` absent a larger metric/configured value.
15-
- `maxPrefixBlocksToMatch` (int, optional, default: `2048`): Maximum number of prefix blocks hashed and matched per request. Not auto-tuned. `0` disables matching (zero blocks hashed).
15+
- `maxPrefixBlocksToMatch` (int, optional, default: `2048`): Maximum number of prefix blocks hashed and matched per request. Not auto-tuned. Applies only when `maxPrefixTokensToMatch` is `0`.
1616
- `maxPrefixTokensToMatch` (int, optional, default: `131072`): Cap expressed in tokens instead of blocks (`maxBlocks = maxPrefixTokensToMatch / blockSizeTokens`). Not auto-tuned. Takes precedence over `maxPrefixBlocksToMatch` when set (> 0); set to `0` to fall back to the block-based cap. The `131072` default (128K, the context window of large production models such as gpt-oss 120b) is a reasonable upper bound that covers the long-prompt use cases seen in production.
17+
18+
Setting both caps to `0` matches the whole prompt. Prompt length is bounded by the model server's context window, so this caps matching at one context window without needing to name a token count. It raises EPP CPU on long prompts (see [operations](../../../../../../../docs/operations.md)); to turn prefix matching off, remove this producer from the configuration rather than zeroing the caps.
1719
- `lruCapacityPerServer` (int, optional, default: `31250`): Per-pod LRU index capacity. Used when `autoTune` is false or endpoint metrics are unavailable; ignored when auto-tuned from metrics.
1820
- `blockSize` (int, optional): Deprecated — character-based block size. Use `blockSizeTokens` instead.
1921

pkg/epp/framework/plugins/requestcontrol/dataproducer/approximateprefix/plugin.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,7 @@ func (p *dataProducer) PluginState() *plugin.PluginState {
227227
// Produce is called by the director before scheduling requests.
228228
func (p *dataProducer) Produce(ctx context.Context, request *fwksched.InferenceRequest, pods []fwksched.Endpoint) error {
229229
blockSize := p.GetBlockSize(pods)
230-
maxBlocks := p.config.MaxPrefixBlocksToMatch
231-
if p.config.MaxPrefixTokensToMatch > 0 && blockSize > 0 {
232-
maxBlocks = p.config.MaxPrefixTokensToMatch / blockSize
233-
}
234-
perPromptHashes := prefixhash.GetBlockHashes(ctx, request, blockSize, maxBlocks)
230+
perPromptHashes := prefixhash.GetBlockHashes(ctx, request, blockSize, p.resolveMaxBlocks(blockSize))
235231

236232
prefixCacheServers := make(map[ServerID]int)
237233
totalBlocks := 0
@@ -359,6 +355,24 @@ func (p *dataProducer) GetBlockSize(endpoints []fwksched.Endpoint) int {
359355
return blockSize
360356
}
361357

358+
// resolveMaxBlocks returns the per-request cap on hashed prefix blocks.
359+
//
360+
// MaxPrefixTokensToMatch wins when set, converting tokens to blocks at the
361+
// effective block size. Zeroing both caps means unlimited: prompt length is
362+
// already bounded by the model server's context window, so the whole prompt is
363+
// at most one context window of tokens. A token cap smaller than the block size
364+
// still resolves to 0 and hashes nothing; that is a misconfiguration, not a
365+
// request for unlimited matching.
366+
func (p *dataProducer) resolveMaxBlocks(blockSize int) int {
367+
if p.config.MaxPrefixTokensToMatch > 0 && blockSize > 0 {
368+
return p.config.MaxPrefixTokensToMatch / blockSize
369+
}
370+
if p.config.MaxPrefixBlocksToMatch == 0 {
371+
return unlimitedPrefixBlocks
372+
}
373+
return p.config.MaxPrefixBlocksToMatch
374+
}
375+
362376
// ApproxPrefixCacheFactory is the factory function for the prefix cache data producer plugin.
363377
func ApproxPrefixCacheFactory(name string, rawParameters *json.Decoder, handle plugin.Handle) (plugin.Plugin, error) {
364378
parameters := defaultConfig

pkg/epp/framework/plugins/requestcontrol/dataproducer/approximateprefix/plugin_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,40 @@ func TestMaxPrefixTokensToMatch(t *testing.T) {
443443
assert.Equal(t, 3, len(state2.PerPromptHashes[0]), "should fall back to MaxPrefixBlocksToMatch when MaxPrefixTokensToMatch is 0")
444444
}
445445

446+
// TestMaxPrefixBothCapsZeroMatchesEverything verifies that zeroing both caps
447+
// hashes the whole prompt rather than nothing. The prompt length is already
448+
// bounded by the model server's context window, so an absent cap is an implicit
449+
// max_model_len cap.
450+
func TestMaxPrefixBothCapsZeroMatchesEverything(t *testing.T) {
451+
disableMinBlockSizeClamp(t)
452+
cfg := config{
453+
BlockSizeTokens: 1,
454+
MaxPrefixTokensToMatch: 0,
455+
MaxPrefixBlocksToMatch: 0,
456+
LRUCapacityPerServer: defaultLRUCapacityPerServer,
457+
}
458+
p, err := newDataProducer(context.Background(), ApproxPrefixCachePluginType, cfg, testHandle())
459+
assert.NoError(t, err)
460+
461+
endpoint := fwksched.NewEndpoint(
462+
&fwkdl.EndpointMetadata{NamespacedName: k8stypes.NamespacedName{Name: "pod1"}},
463+
fwkdl.NewMetrics(), fwkdl.NewAttributes(),
464+
)
465+
466+
req := &fwksched.InferenceRequest{
467+
RequestID: uuid.NewString(),
468+
TargetModel: "test-model",
469+
Body: tokenizedBody([]uint32{1, 2, 3, 4}),
470+
}
471+
472+
err = p.Produce(context.Background(), req, []fwksched.Endpoint{endpoint})
473+
assert.NoError(t, err)
474+
475+
state, err := plugin.ReadPluginStateKey[*SchedulingContextState](p.PluginState(), req.RequestID, plugin.StateKey(ApproxPrefixCachePluginType))
476+
assert.NoError(t, err)
477+
assert.Equal(t, 4, len(state.PerPromptHashes[0]), "both caps at 0 should hash every block in the prompt")
478+
}
479+
446480
// TestGetBlockSize_AutotuneClampsBelowMinimum verifies that when AutoTune is on and
447481
// the endpoint reports a small CacheBlockSize, GetBlockSize floors the result at
448482
// minBlockSizeTokens to bound EPP indexer memory. See issue #1158.

pkg/epp/framework/plugins/requestcontrol/dataproducer/approximateprefix/types.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package approximateprefix
1818

1919
import (
20+
"math"
2021
"time"
2122

2223
k8stypes "k8s.io/apimachinery/pkg/types"
@@ -99,7 +100,8 @@ const (
99100
defaultBlockSizeTokens = 16
100101

101102
// defaultMaxPrefixBlocks is the fallback block cap, consulted only when
102-
// MaxPrefixTokensToMatch is 0; the default token cap otherwise supersedes it.
103+
// MaxPrefixTokensToMatch is 0 and MaxPrefixBlocksToMatch is non-zero; the
104+
// default token cap otherwise supersedes it.
103105
// Two long requests with the same prefix up to this limit will be indistinguishable.
104106
// This parameter provides a trade-off between cache size, prefix matching speed and matching
105107
// accuracy. Use a small value if most requests are short to reduce cache size and speed up the
@@ -112,6 +114,12 @@ const (
112114
// over defaultMaxPrefixBlocks: maxBlocks = defaultMaxPrefixTokens / blockSizeTokens.
113115
defaultMaxPrefixTokens = 131072
114116

117+
// unlimitedPrefixBlocks is the block cap used when both MaxPrefixTokensToMatch
118+
// and MaxPrefixBlocksToMatch are 0. Prompt length is already bounded by the
119+
// model server's context window, so an absent cap matches at most one context
120+
// window of tokens.
121+
unlimitedPrefixBlocks = math.MaxInt
122+
115123
// defaultLRUCapacityPerServer is the default capacity of the LRU indexer per server.
116124
// The indexer is an approximation to the actual prefix LRU cache state on the model servers per server (pod).
117125
// A small capacity ensures a high accuracy of cache hit on the model server, but it will
@@ -135,11 +143,12 @@ type config struct {
135143
BlockSize int `json:"blockSize"`
136144
// Deprecated: use MaxPrefixTokensToMatch, which caps prefix matching in tokens
137145
// independent of BlockSizeTokens. MaxPrefixBlocksToMatch applies only when
138-
// MaxPrefixTokensToMatch is 0.
146+
// MaxPrefixTokensToMatch is 0. Setting both to 0 matches the whole prompt.
139147
MaxPrefixBlocksToMatch int `json:"maxPrefixBlocksToMatch"`
140148
// MaxPrefixTokensToMatch is the maximum number of prefix tokens to match.
141149
// When set (> 0), it takes precedence over MaxPrefixBlocksToMatch by computing
142-
// maxBlocks = MaxPrefixTokensToMatch / blockSizeTokens.
150+
// maxBlocks = MaxPrefixTokensToMatch / blockSizeTokens. Setting both caps to 0
151+
// matches the whole prompt.
143152
MaxPrefixTokensToMatch int `json:"maxPrefixTokensToMatch"`
144153
// Max capacity size of the LRU indexer in number of entries per server (pod).
145154
LRUCapacityPerServer int `json:"lruCapacityPerServer"`

0 commit comments

Comments
 (0)