[sgl-router] refactor - SLO ordering for bucket selection (#40292)

This commit is contained in:
Kan Wu
2026-09-22 11:16:10 +08:00
committed by GitHub
parent 27f796ca6c
commit 59a723ef1e
8 changed files with 422 additions and 37 deletions
+38 -13
View File
@@ -7,8 +7,9 @@ listed at the end.
## Principles
1. **Order compatible buckets by token length first.** `BucketResolver` returns
all matching buckets, smallest capacity first, without inspecting workers or policies.
1. **Filter buckets by token length, then order preferences.** `BucketResolver`
returns all compatible buckets, applying optional SLO preferences before
capacity/rank/ID ordering, without inspecting workers or policies.
2. **The bucket owns plain versus PD engine selection.** `Bucket::pick_engines`
calls its one plain group or both prefill and decode groups, returning a
complete selection. Both PD engines come from that same bucket.
@@ -79,7 +80,7 @@ tier executor, or separate selection framework is required.
## 2. Responsibilities and request flow
`BucketResolver::resolve(input_tokens, expected_peak_tokens)` returns an ordered
`BucketResolver::resolve(input_tokens, expected_peak_tokens, ttft_ms, tokens_per_second)` returns an ordered
list of compatible bucket references (possibly empty), or an invalid-signal error.
It does not receive a stage or a load view, resolve live engines, or invoke policies.
The handler iterates this list until a bucket supplies the complete engine selection.
@@ -153,9 +154,10 @@ the existing body-size estimate when tokenization is unavailable.
2. Keep buckets whose inclusive input-token range contains the input length.
3. Check the bucket context capacity against input plus requested output when
known, or against input length when the output budget is unknown.
4. Sort by ascending input capacity (the lesser of the input upper bound and
context capacity). Unbounded capacities sort last. Break ties by ascending
bucket rank, then ID, and return the entire ordered list.
4. Apply enabled SLO preferences to complete buckets. Count unmet preferences
equally, then sort by ascending input capacity (the lesser of the input upper
bound and context capacity), rank, and ID. Unbounded capacities sort last.
Return the entire ordered list, retaining nonpreferred buckets for fallback.
5. The handler calls each bucket's `pick_engines` until one supplies its complete
selection. A failed PD attempt never contributes an engine to a later pair.
@@ -172,7 +174,29 @@ bucket, both groups share this one request-length decision. Policy fallback on
a cache/affinity miss stays within that group's candidates. There is no second
pass with relaxed admission and no post-policy substitution.
SLO ordering, global session modes, and sticky policies are follow-ups. Their
### Optional SLO ordering
`Bucket` has optional `ttft_ms` and `tokens_per_second` estimates. The resolver
has independent `ttft_slo` and `tps_slo` preferences: `Disabled` (default),
`SloFirst` (matching first), and `BestEffort` (nonmatching first). The handler
parses `x-sgl-ttft-slo-ms` and `x-sgl-tps-slo` only when their preference is enabled;
invalid enabled headers return 400 before dispatch. Disabled headers are ignored.
SLO targets belong to bucket resolution, not the engine policy's `PickRequest`.
Absent targets are neutral. A bucket matches TTFT when its positive estimate is
at most the target, and throughput when its finite positive estimate is at least
the target. Missing or invalid estimates do not match a supplied target. Enabled
TTFT targets must be positive; throughput targets must be finite and positive.
Each unmet preference adds one ordering penalty. With both preferences set to
`SloFirst`, a bucket matching both comes before one matching either, followed by
buckets matching neither. Capacity/rank/ID breaks ties within these tiers. The
same logic applies to plain and PD buckets, and a PD bucket always supplies both
engines. TTFT and throughput preferences never independently resolve P/D groups.
Length constraints are applied first and admission rejection still advances to
the next complete bucket, including a bucket outside the preferred SLO tier.
Global session modes and sticky policies are follow-ups. Their
integration must preserve bucket-first selection and the same-bucket PD rule.
Cross-bucket affinity probing is not part of this interface. Session/routing
keys still pass through `PickRequest` for policies operating inside the selected
@@ -490,8 +514,8 @@ do not accept and ignore them.
- Keep `load_based` as the CLI name for `LeastLoadPolicy`.
- Preserve explicit engine membership and context constraints. Bucket-level
ranges and ordering replace independent per-stage selection.
Restore existing SLO behavior in the separate SLO PR before serving switchover;
legacy routing continues to support SLOs during this skeleton-only phase.
SLO preferences order whole buckets; deployment configuration remains explicit
until the production configuration factory and serving switchover are ready.
- Preserve cache-provider selection, endpoint validation, query timeout and
concurrency limits, and unavailable-backend fallback.
- Preserve cache thresholds and tuning: the 1,024-token default minimum hit,
@@ -558,7 +582,8 @@ This PR adds the side-by-side interfaces in `src/buckets_reorg.rs` and
Implemented here:
- `BucketResolver::resolve` returns all length-compatible buckets in capacity/rank/ID order.
- `BucketResolver::resolve` returns all length-compatible buckets in optional
SLO-preference tiers, with capacity/rank/ID order within each tier.
- `Bucket::pick_engines` owns plain/PD orchestration and stage-specific policy
requests; `BucketRequest` carries prepared facts and `BucketPick` retains picks.
- `Bucket` owns input limits, context capacity, rank, and plain-or-PD groups.
@@ -576,6 +601,8 @@ Implemented here:
dispatches only after one complete selection. Exhaustion retains admission reasons.
- `AppContext::chat_routing` configures legacy versus reorg routing on the same
endpoint and carries the reorg model-resolver map.
- Optional bucket TTFT/throughput estimates and preferences, with enabled-header
parsing and whole-bucket fallback in the shared chat route.
- `CacheAwarePolicy` reads local radix-tree or remote indexer prefixes, intersects
exact worker URLs with the current group, applies hit thresholds and candidate
bounds, and preserves the soft queue gate, saturation pin and pressure guard.
@@ -594,13 +621,11 @@ Implemented here:
The caller owns expiry and sweeper lifecycle. A binding may remain after a
later PD group fails, because it records placement rather than dispatch.
Follow-up work includes bucket SLO ordering, remaining selection policies,
and production configuration.
Follow-up work includes remaining selection policies and production configuration.
Not yet implemented in the reorg path:
- Other concrete selection policies.
- SLO estimates, targets, and bucket preference ordering.
- CLI/configuration parsing, validation, and model-specific construction.
The YAML above is illustrative; reorg resolvers are installed in code.
- Global session modes and sticky routing-key affinity.