Add approximate prefix routing, and find out it did nothing
Tier 1 of plan §7.5: a radix tree of request prefixes that estimates which replica has already prefilled one. Bytes, not tokens — needing the engine's exact tokenizer is what makes tier 2 a separate project. It went in inert, and the decision metric is what caught it: 144 lookups, zero hits. The cause was a rule I had written and tested for. A node's label is the first request that created it, so the second request for the same system prompt diverges in its last few bytes — a new question after the same preamble, the single most common shape there is. Requiring the whole label to match threw away the 700 bytes they shared. What matters is the shared head, because that is what the engine cached, and a partial label match now counts. Two unit tests asserted the wrong rule and have been inverted. With that fixed, measured end to end against engines whose caches are too small for the working set: 22.5% cache hits without, 81.7% with, and the breakdown shows the misses are exactly the first round, when the tree is empty. The scenario matters as much as the number. With a handful of prompts and a large cache, scattering is free — every replica ends up holding everything — and prefix routing can only add constraint. The first version of the test measured that case and showed a loss, which was a true statement about a situation the feature is not for. Two things keep it from being the disaster prefix routing usually is. The imbalance guard, which needs both an absolute and a relative gap before it fires, because either alone misfires — absolute on an idle cluster, relative on a busy one. And it is a scorer rather than a filter: affinity is a vote the queue and inflight scorers can outvote, not a rule. The tree records the replica the picker actually chose, not the one the scorer proposed, and a node prefers the replica it has seen most rather than most recently — a count is stable under a randomizing picker, where recency just chases whatever the picker last did. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
de21649ad6
commit
a075ff4120
13 files changed
+1207
-7
No files matched your search
@@ -110,6 +110,39 @@ router:
|
||||
failure_threshold: 3
|
||||
|
||||
default_profile: chat # chat | batch | long-context | debug
|
||||
|
||||
# Approximate prefix routing (plan §7.5 tier 1): send a request to the replica
|
||||
# that has probably already prefilled its prefix.
|
||||
#
|
||||
# It earns its keep when the working set does not fit in one replica's cache.
|
||||
# With a handful of prompts and a large cache, scattering is free — every
|
||||
# replica ends up holding everything — and this only adds constraint. Measured
|
||||
# with sim-engines whose caches were too small for the working set: cache hits
|
||||
# went from 22.5% to 81.7%.
|
||||
#
|
||||
# Watch llmgw_prefix_decisions_total to tune it. A high
|
||||
# load_balance_override count means the guard is doing its job and affinity is
|
||||
# not getting a chance; a high cache_miss_min_load means match_ratio or
|
||||
# min_match is too strict for your traffic.
|
||||
prefix:
|
||||
enabled: false
|
||||
# The shortest match worth acting on, in bytes. Below this the overlap is
|
||||
# coincidence — two English prompts share "The".
|
||||
min_match: 64
|
||||
# The share of the prompt that must match. A short shared header on a very
|
||||
# long prompt saves almost no prefill.
|
||||
match_ratio: 0.3
|
||||
# The imbalance guard, and both must trip before affinity is abandoned.
|
||||
# Absolute alone fires on an idle cluster where one replica has two requests
|
||||
# and another none; relative alone fires on a busy one where 200 against 180
|
||||
# is a rounding error. Without the guard the cache hit rate rises while p99
|
||||
# collapses.
|
||||
balance_abs: 8
|
||||
balance_rel: 1.5
|
||||
# An engine evicts on its own schedule, so an old record may describe a
|
||||
# cache that is gone.
|
||||
ttl: 10m
|
||||
max_nodes: 100000
|
||||
session_header: x-session-id # affinity key; falls back to the `user` field
|
||||
# Ask engines for usage even when the client did not, so token accounting
|
||||
# works regardless of client behaviour. The extra chunk is dropped on the way
|
||||
|
||||
Reference in new issue
Block a user