# B3 Routing Policies — Pseudocode Reference: `scripts/cache_aware_proxy.py`. All five policies share the same per-worker state machine; only the per-request `pick_instance_*` function differs. ## Shared per-instance state ```text inst.url inst.ongoing_tokens # sum of input_length across in-flight reqs inst.pending_prefill_tokens inst.ongoing_decode_tokens inst.num_requests # waiting + running inst.cached_blocks # LRU set of 512-token block hashes inst.estimate_cache_hit(tokens) -> int # longest prefix of `tokens` (in BLOCK_SIZE # chunks) currently in cached_blocks ``` Each pick is one round-trip on every routing decision; counters are mutated when a request starts/finishes, not inside the picker. ## 1. `lmetric` — main baseline Pure per-request LMetric scoring. No session affinity, no overload-break logic. ```text def pick_lmetric(instances, token_ids, input_length): best, best_score = None, +inf for inst in instances: cache_hit = inst.estimate_cache_hit(token_ids) new_prefill = max(0, input_length - cache_hit) p_tokens = inst.pending_prefill_tokens + new_prefill bs = inst.num_requests score = p_tokens * bs if score < best_score: best, best_score = inst, score return best ``` Intuition: prefer the instance where the expected new prefill cost times the running batch size is smallest. Cache hit reduces `new_prefill`, so cache-warm workers win at equal load. ## 2. `load_only` — B3 control (no cache, no affinity) ```text def pick_load_only(instances): return min(instances, key=lambda inst: inst.num_requests) ``` Ties: Python `min` returns the first-seen, so when `num_requests` is equal across all instances (e.g. fresh start), pick always lands on `instances[0]`. This produces unintentional stickiness at low concurrency — the B3 lmetric/load_only comparison reads APC=54.1% for load_only partly because of that. ## 3. `sticky` — B3 control (hard affinity) Once a session is bound, never break the binding under any load. ```text def pick_sticky(instances, session_id, affinity): if session_id in affinity: return instances[affinity[session_id]] # unconditional chosen = min(instances, key=lambda i: i.num_requests) affinity[session_id] = index_of(chosen) return chosen ``` This is the upper bound on locality and the worst case on hot-spot behavior — a single heavy session pins one worker forever. ## 4. `unified` — hybrid affinity + LMetric fallback Sticks to the affinity worker only when the cache is genuinely warm and the affinity worker is not overloaded; otherwise falls back to LMetric with a 4-key tie-breaker. ```text def pick_unified(instances, token_ids, input_length, session_id, affinity): avg_reqs = max(mean(inst.num_requests for inst in instances), 1.0) # Affinity gate (both must hold) if session_id in affinity: a = instances[affinity[session_id]] a_hit_ratio = a.estimate_cache_hit(token_ids) / max(input_length, 1) if a_hit_ratio > 0.5 \ and a.num_requests <= avg_reqs * OVERLOAD_FACTOR: return a # stick # LMetric fallback with multi-key tie-break keys = [] for inst in instances: cache_hit = inst.estimate_cache_hit(token_ids) new_prefill = max(0, input_length - cache_hit) p_tokens = inst.pending_prefill_tokens + new_prefill bs = inst.num_requests score = p_tokens * bs keys.append((score, new_prefill, bs, idx_of(inst))) best_3tuple = min(k[:3] for k in keys) tied = [k for k in keys if k[:3] == best_3tuple] if len(tied) > 1: # Round-robin among ties so brand-new traffic doesn't pin # instance 0 when BS=0 across the board. winner = tied[_rr_counter % len(tied)] _rr_counter += 1 else: winner = tied[0] return instances[winner.idx] ``` Tie-break ordering: `score` (LMetric primary), then `new_prefill` (prefer the most cache-warm at equal score), then `num_requests` (prefer least-loaded), then a global round-robin counter. `OVERLOAD_FACTOR` defaults to 2.0; when the affinity worker is above 2× average load, the picker treats it as overloaded and steers away. ## 5. `capped` — `lmetric` on a session-mass-capped trace Not a new picker. The picker is the same `pick_lmetric` from §1; the input trace is preprocessed. ```text def build_capped_trace(input_path, output_path, MAX_TURNS=8): by_session = group_by_session_id(load(input_path)) capped = [] for sid, turns in by_session.items(): turns.sort_by(lambda t: (t.turn_id, t.timestamp)) capped.extend(turns[:MAX_TURNS]) capped.sort_by(timestamp) # restore wall-clock order write_jsonl(capped, output_path) # At run time: trace = build_capped_trace("w600_r0.0015_st30.jsonl") picker = pick_lmetric ``` For this trace `MAX_TURNS=8` truncates the heavy-tail sessions (full trace turns/session p90=1, p99=18, max=3091). The intent is to isolate "what does LMetric look like when no session is heavy enough to hot-spot a worker?" — comparing capped vs lmetric is the session-mass ablation. ## Decision matrix | | session affinity | cache aware | load aware | overload break | |---|---|---|---|---| | `lmetric` | ✗ | ✓ (via `cache_hit` → `new_prefill`) | ✓ (`num_requests` BS factor) | n/a | | `load_only` | ✗ | ✗ | ✓ (`num_requests` only) | n/a | | `sticky` | ✓ (hard) | ✗ (relies on physical hits, not scoring) | only on first turn | **never** | | `unified` | ✓ (gated) | ✓ | ✓ | gate: `cache_ratio>0.5` AND `num_req ≤ 2× avg` | | `capped` | same as `lmetric`; the trace itself is truncated | | | | ## What each control isolates - `lmetric` vs `load_only` → contribution of cache awareness alone. - `lmetric` vs `sticky` → contribution of session affinity vs per-request LMetric scoring at the cost of hot-spot. - `lmetric` vs `unified` → did adding gated session affinity help? - `lmetric` vs `capped` → how much of the residual hot-spot in `lmetric` is driven by heavy-tail sessions specifically?