SMetric: Rethink LLM Scheduling for Serving Agents with Balanced Session-centric Scheduling

agent 2607.08565
llm-servingrequest-schedulingkv-cacheload-balancingagentic-workload

SMetric: Balanced Session-centric Scheduling for Agentic LLM Serving — L2 #

1. TL;DR #

Agentic LLM serving shifts the goal to cluster TPS and makes KV$ reuse dominate (>80% vs 54–62% chat). Cache-aware schedulers overload a few instances by pinning whole sessions. SMetric balances only each session's first request and routes follow-ups cache-aware, gaining 10–16% TPS (colocation) and 2–34% prefill TPS (disaggregation).


2. Q1 / Q2 / Q3 — 痛点 / 方法 / 结果 #

Q1 — 痛点 (agent scope & the scheduling mismatch) #

Q2 — 方法 (balanced session-centric scheduling) #

Two insights turn the diagnosis into a fix:

  1. System-side: a two-tier KV$ store (local GPU + global RDMA-linked CPU) decouples reuse from placement. Even if the routed instance lacks the KV$ locally, it fetches from the global tier; as long as that fetch is timely, TPS is unaffected (cost is a small TTFT bump agents tolerate). So load balance need not sacrifice all reuse — but the global tier has finite capacity/bandwidth, so local reuse must still be exploited.
  2. Workload-side (intra-session locality): under any KV$-prioritizing scheduler, a session's first request determines the placement of all its follow-ups (follow-ups reuse earlier turns, which are still in the local tier). Therefore balancing only first requests balances the whole cluster, and since first requests are a small fraction, the global tier alone suffices to serve their (system-prompt) reuse.
  3. SMetric policy: differential routing by turn. First request of a session (turn==0) → pure load-balance (spread sessions). Follow-up → cache-aware stick to argmax(KV$ hit), unless one of two retrofit guards fires (fall back to load balance):

    • not_overloaded: $l[s] \le \text{OVERLOAD}\cdot\text{mean}(l)$ — migrate a session off an instance that grew hot.
    • session_not_evicted: $c[s] > \text{HIT\_RATIO}\cdot\text{est\_hit}(req)$ — if local cache was evicted, treat as a fresh session.

    核心技术壁垒 (the single hardest-to-replicate insight): the stateless derivation of session-turn from the request itself. The router keeps no session-to-instance table; it infers turn purely from the count of historical messages carried in the request (the OpenAI-style API is stateless, so each request re-sends its history). This makes the router a pure function of the request — no per-session state to track, update, or garbage-collect as sessions silently go idle. The signal degrades gracefully: if an agent drops history, SMetric just treats it as a first request, which is correct because such a request hits little KV$ anyway.

    Q3 — 结果 #

    • Colocation (Qwen3-30B, 32 inst): +10–16% TPS over the best baseline when a global store is present; ~tied Bailian (<1%) with no global store. Local hit 64% (vs Bailian 65%); load 2.6× max/mean (vs 3.0×/3.2×; LB 2.0×). Median TTFT 1.1 s (17% < Bailian).
    • Larger model (Qwen3-235B, 8 inst): peak 516 tok/s, 8% > Bailian, 20% > LMetric; local hit 69%, overall reuse 76%.
    • Disaggregation (30B prefill): +2–34% prefill TPS across global-tier sweep; median TTFT 1.1 s = 37% < Bailian (1.8 s). Larger gains than colocation because prefill is compute-bound and cannot absorb imbalance like decode can.

    3. 架构 / 方法图 #

    The KV$ store hierarchy (the enabling substrate) #

    Figure 1: Two-tier KV$ store hierarchy for serving agents

    Paper's Figure 1 (caption: "The KV$ store hierarchy for serving agents"). The local tier is GPU HBM; when full, KV$ cascades to the global CPU-memory tier (and beyond to cloud storage). This hierarchy is what lets a load-balanced instance still recover reuse — the missing KV$ is fetched from the global tier rather than recomputed. It is the mechanism behind Insight 1.

    Figure 4: (a) prefill/decode with KV$; (b) PD-colocation; (c) PD-disaggregation

    Paper's Figure 4. Panel (a) shows how a 3-token request with two KV$ hits computes only one token's KV$. Panels (b)/(c) contrast the two serving paradigms SMetric is evaluated under: colocation runs prefill+decode on the same instance (all instances touch the global tier), disaggregation splits them (only prefill instances touch the global tier). This split is why the disaggregation gains are larger — prefill is where reuse and imbalance matter most.

    The agent loop and the scheduling decision #

    The per-turn agent loop and where the router acts:

    stateDiagram-v2 [*] --> FirstRequest: user starts session (turn==0) FirstRequest --> Prefill: router load-balances (spread) Prefill --> Decode: complete response Decode --> AgentAct: agent parses full reply, runs tool AgentAct --> FollowUp: append tool output, resend history (turn++) FollowUp --> StickCheck: router computes c=hit_len, l=load StickCheck --> Stick: turn!=0 AND not_overloaded AND not_evicted StickCheck --> Rebalance: any guard fails Stick --> Prefill Rebalance --> Prefill AgentAct --> [*]: task done (EOS / no tool)
    • Memory model: short-term = the context window resent every turn (append-only); long-term/episodic = the KV$ store itself (local + global tiers), reused across turns. The router keeps no memory (stateless by design).
    • Tool invocation: opaque to the provider — the agent runs tools client-side, appends results to the context, and issues the next LLM request. The scheduler never sees tool semantics.
    • Error recovery / fallback state: when a guard fails, SMetric falls back to the load-balance branch (rr_argmin(load)), and the global tier makes even a re-routed session recover its KV$ via remote fetch (➂ in Fig 2). So the "error state" is always a safe, reuse-preserving load-balance.

    Planning & reasoning #

    • Planning style (of the serving policy, not the agent): a fixed, per-request differential workflow — turn-gated branch, not a search. No backtracking within the router.
    • Budget: bounded by SLOs (TTFT linear in input tokens; TPOT fixed 30 ms); TPS-within-SLO is the swept objective.
    • Session migration is the only "undo": on tail imbalance, a long session is migrated to a new instance (the not_overloaded fallback), and the global tier re-supplies its KV$.

    4. 作者证明 #

    无形式化作者证明 — 仅实证. The paper offers no convergence or success guarantee; it is a systems/empirical work. What could have been bounded: the max/mean load-imbalance ratio (a competitive-ratio-style bound against the offline optimal dispatch), or a bound on global-tier fetch demand as a function of the first-request fraction. Neither is proven; both are shown empirically. Below are the load-bearing scoring/guard equations and the 6 minimum checks.

    Notation table

    SymbolMeaning
    $c_i$reusable (KV$ hit) length on instance $i$ (prefix hit)
    $l_i$load of instance $i$ (batch size / inflight or unprefilled tokens)
    $\hat{c}_i,\hat{l}_i$normalized hit ratio / load
    $\lambda$Bailian's weight trading reuse vs balance
    $L$request prompt length
    $s$instance with highest KV$ hit, $\arg\max_i c_i$
    OVERLOADthreshold multiple over cluster mean load (>1)
    HIT_RATIOfraction (<1) tolerating hit-estimate drift
    est_hitexpected hit from carried history (excludes new turn)

    方程物理意义 — baseline scoring functions ($f$, lower is better):

    • Bailian (linear): $f = (1-\lambda)\cdot\hat{l}_{i} + \lambda\cdot(1-\hat{c}_{i})$ — convex blend of load and reuse-miss; large $\lambda$ favors hits.
    • LMetric (multiplicative): $f = (L - c_{i}) \times l_{i}$ — product of remaining-prefill and load; small only when both are small, no tuned weight.
    • Load-balance-only: $f = l_{i}$ — join-the-shortest-queue, KV$-agnostic.

    SMetric guards (stick only if both hold): $l[s] \le \text{OVERLOAD}\cdot\text{mean}(l)$ and $c[s] > \text{HIT\_RATIO}\cdot\text{est\_hit}(req)$.

    6 minimum checks

    1. Score direction: all three baseline $f$ are minimized (route to lowest score); SMetric's stick branch instead maximizes $c$ (argmax hit). Consistent — cache-stick wants max hit, load-balance wants min load. ✓
    2. Bailian limit $\lambda\to1$: $f\to(1-\hat{c}_i)$, pure cache-aware — matches the "large $\lambda$ for agents" claim and the 58% local-hit / worst-imbalance (2.7×) behavior. ✓
    3. LMetric hit saturation: if $c_i=L$ (full hit), $f=0$ regardless of load → routes purely by remaining prefill; explains its higher imbalance (3.0×). ✓
    4. Guard monotonicity in OVERLOAD: OVERLOAD→∞ disables not_overloaded (never migrates) → reduces to SMetric(basic); §5.5 sweep confirms ≤6% TPS change (broad plateau). ✓
    5. Guard monotonicity in HIT_RATIO: HIT_RATIO→0 always passes session_not_evicted (never treats as fresh); →1 sticks only on near-perfect hit. §5.5: 0–0.75 within 4%. ✓ (default sits on plateau, not a knife-edge)
    6. Stickiness sanity (Fig 12): under LB routing only 4.0% of follow-ups return to first-turn instance vs 96.6% under cache-aware — confirms the mechanism the whole design rests on (first-request placement propagates). ✓
    7. Success-rate / sweep matrix (empirical, agent-specific ask): the paper sweeps (model size {30B, 235B}) × (paradigm {colocation, disaggregation}) × (global-tier provisioning) × (offered load rate). Monotonicity: gain rises toward the saturation knee (0% at 1.0× load → 15% at 1.5× → shrinks at 1.75×/2.0×), and rises as the global tier shrinks (up to 34% disagg). Latency budget: TTFT dominated by KV$ hits at median, by queueing at tail; SMetric wins both because it keeps hits high and load balanced. Failure-mode classes the paper targets: (a) tail overload from co-growing long sessions → not_overloaded; (b) silently evicted session cache → session_not_evicted. The dominant everyday case (well-balanced) needs neither guard — they only trim the tail (§5.4).


      5. 实验与数据 #

      Where reuse comes from (motivation) #

      Figure 5: reusable KV$ characterization with reuse-source breakdown

      Paper's Figure 5. Reuse exceeds 80% in both traces; ~67% is intra-session (follow-ups reusing earlier turns), 18–20% is the shared system prompt (helping even first requests). This breakdown is load-bearing: it justifies both insights — session locality (route follow-ups locally) and first-request global-tier reuse (system prompt fetched from global tier wherever the request lands).

      The scheduler analysis — why cache-awareness backfires #

      Figure 11: (a) TPS vs global-tier offering; (b) reuse ratio and source; (c) load balancing

      Paper's Figure 11. Panel (a): the fully KV$-agnostic load-balance-only method comes within 7% of the strongest baseline once the global tier is fully provisioned. Panel (b): with a full global tier all methods reach ~73–75% reuse (they differ only in local vs global source). Panel (c): KV$-aware methods are more imbalanced (LMetric 3.0×, Bailian 2.7×) than load-balance-only (2.1×). Together these show reuse ≠ placement — the core evidence for Insight 1.

      The stickiness lever #

      Figure 12: session stickiness to first-turn instance, LB vs Bailian routing

      Paper's Figure 12. Under load-balanced routing only 4.0% of follow-ups return to the first-turn instance; under Bailian's cache-aware policy 96.6% do. This ~24× gap is the empirical foundation of "balance only the first request": once first requests are spread, cache-awareness naturally keeps each session pinned, so the whole cluster balances.

      End-to-end: colocation and disaggregation #

      Figure 16: colocation TPS vs global-tier offering, and reuse breakdown (Qwen3-30B)

      Paper's Figure 16. SMetric is highest at every provisioning: +10–16% TPS with a global store, ~tied Bailian without one. Panel (b) shows SMetric retains Bailian-level local hits (64% vs 65%) while staying more balanced — the "have both" result.

      Figure 22: disaggregation prefill TPS vs global-tier offering, and reuse breakdown

      Paper's Figure 22. Under PD disaggregation SMetric gains 2–34% prefill TPS — larger than colocation because prefill is compute-bound and cannot hide imbalance the way decode (larger batch still emits tokens) can. At full provisioning it matches the 76% overall reuse of the strongest baseline while cutting imbalance (3.3× vs LMetric 4.2×, LB 4.6×).

      Ablation (§5.4/§5.5): removing the two guards (SMetric-basic) worsens tail TTFT (P90/P99 16.1/51.2 s vs 12.1/45.3 s) — guards mainly trim tail queueing. Sensitivity: OVERLOAD 1.0–∞ within 6% TPS; HIT_RATIO 0–0.75 within 4% — a broad plateau, so the hyperparameters are not fragile.


      6. 论证链 #

      #Step (paper-internal)Support
      1Agentic workload differs: goal = cluster TPS (agents act on complete responses), and KV$ reuse >80% (vs 54–62% chat).Findings 1 (Fig 5), §1
      2Existing cache-aware schedulers over-prioritize reuse: they route shared-system-prompt first requests to few instances, and session stickiness pins follow-ups there → overload few, idle rest → TPS capped.§1, §4.1; Fig 11(c)
      3This trade is unnecessary: (a) a two-tier store decouples reuse from placement (LB-only within 7%, reuse 73–75% via global fetch), and (b) the workload is balanceable at scale (median imbalance 1.7/1.5).Fig 11(a,b), Finding 6 (Fig 9)
      4Intra-session locality means first-request placement determines the whole session (4.0% vs 96.6% return rate), so balancing only first requests balances the cluster; first requests are few, so the global tier suffices for their reuse.Finding 2 (Fig 5), Fig 12
      5Therefore: load-balance first requests, cache-stick follow-ups, with two guards for tail overload and cache eviction — and infer turn statelessly from message count.§4.2 (Fig 13 pseudocode)
      6Result: +10–16% TPS colocation, +2–34% prefill TPS disagg, lower TTFT/TPOT, with local hits comparable to the most cache-biased baseline.Fig 16, 22, 17, 23

      7. 实现 cross-reference #

      [实现未公开] at the time of L1 ingestion — the paper states "We will open-source SMetric as well as traces upon publication," but no repository is cited in the source. Implementation targets are named for future cross-reference:

      • Built on vLLM [44] (v0.12.0) as the serving engine; vLLM's default routing = the load-balance-only baseline.
      • Global-tier KV$ store on LMCache [29] + Mooncake [38] (the state-of-the-art open stack described by Fig 2).
      • Under disaggregation, the load-balance-only baseline is replaced by Dynamo [34], which routes by new-prefill-token count.

      核心技术壁垒 (dedicated paragraph): the hardest-to-replicate piece is not the two-branch routing (trivial once known) but keeping the router fully stateless while still knowing each request's session turn. Naively one records a session→instance table, but sessions never signal their end, so the table grows unbounded and needs garbage collection. SMetric sidesteps this entirely by inferring turn from the number of historical messages in the request payload (viable only because agents use the stateless OpenAI-style API that re-sends full history). Replicating SMetric's scalability claim requires reproducing this inference plus its benign-failure argument (dropped history → treat as first request, which loses little because it has little reuse anyway) — not just the pseudocode.

      关键实现细节 (easy-to-miss tricks):

      1. load_balance() is parameterized by paradigm: request-count under PD-colocation, but unprefilled tokens under PD-disaggregation — because prefill is compute-bound and token count, not request count, tracks its imbalance (§4.2 pseudocode; §5.3 rationale).
      2. est_hit deliberately excludes the freshly appended turn (which is never cached yet), so the eviction check compares against only the carried history — otherwise a healthy stick would look like a cache miss and needlessly rebalance.

      3. Pre-Save Self-Check #

        • Swap-entity test: each paragraph names SMetric-specific mechanisms (turn-gated branch, first-request balancing, stateless turn inference, the two named guards) — replacing "SMetric" with "X" breaks specificity. ✓
        • 8-pattern leak grep: no rule-tags, no skill-doc citations, no inheritance/trigger/tool-choice-rationale/deep-*.md restatement/self-referential blocks. ✓
        • KaTeX: all subscript/superscript equations in $...$/$$...$$; only mermaid in fences. ✓
        • §4 has 6 checks + "无形式化作者证明 — 仅实证". §6 is a ≥3-step numbered table. §7 has [实现未公开] + named targets. Figures embedded: 7 ![ references. ✓