feat(kvc): session migration with reset-on-success + direct-append threshold tuning

KVC v2 beats 4DP at ts=1 same-scale on 7/8 metrics:
  TTFT mean -24%, p50 -54%, p90 -64%; lat mean -0.8%, p50 -12.6%, p90 -0.7%.
  Direct-to-D rate jumped 42.8% -> 91.7%. REFACTOR_PLAN_V1 scenario C achieved.

Two-knob fix:
- reset-on-success blacklist decay: clear (sess, D) reject counter on
  successful direct-to-D path. Eliminates v1 thrashing where session 6880
  was stable on decode-1 for 70 turns then collapsed to 75 D-changes after
  cumulative transient pressure tripped the permanent blacklist.
- bump --kvcache-direct-max-uncached-tokens default 2048 -> 8192 via CLI flag.
  41% of v1 fallbacks were 'real-large-append' (>2048 token append); raising
  the threshold lets these go through the direct-to-D fast path.

Code:
- policies.py: RoutingState.session_d_rejects counter + KvAwarePolicy
  migration_reject_threshold; degenerate fallback picks least-rejected D.
- replay.py: record_admission_reject + reset-on-success in _run_request;
  _fallthrough_reason classifies turn-2+ fall-throughs as session-not-resident
  / real-large-append / etc, replacing misleading 'large-append' suffix
  (TEAM_REPORT §2.7).
- cli.py + benchmark.py: --kvcache-migration-reject-threshold flag wiring.

Docs:
- REFACTOR_PLAN_V1_ZH.md: forward-looking plan after ts=1 validation.
- MIGRATION_V1_FINDINGS_ZH.md: v1 thrashing root-cause analysis.
- V2_RESULTS_ZH.md: v2 results, scenario C achievement, attribution.
- TEAM_REPORT_AGENTIC_PD_HYBRID_ZH.md: comprehensive team report.

Scripts:
- sweep_ts1_kvc_n3_plus_dp.sh: ts=1 baseline (KVC 1P3D N=3 + 4DP CA).
- sweep_ts1_migration_v1.sh / v2.sh: validation runs.
- analyze_ts1_validation.py: 4-way comparison analyzer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kzlin
2026-05-09 01:18:13 +08:00
parent 1d51704dad
commit 2ec0debef4
12 changed files with 2350 additions and 12 deletions

View File

@@ -106,6 +106,11 @@ class ReplayConfig:
pool_poll_include_sessions: bool = True
enable_backpressure: bool = False
backpressure_max_pause_s: float = 2.0
# Session migration via per-(sess, D) admission reject memory.
# When a session has been admission-rejected this many times on a given D,
# KvAwarePolicy skips that D for the session (forcing migration). Default 3.
# Set 0 to disable. See REFACTOR_PLAN_V1 §6.2.
kvcache_migration_reject_threshold: int = 3
structural_log_dir: Path | None = None
@@ -190,7 +195,10 @@ async def replay_trace(config: ReplayConfig) -> list[RequestMetrics]:
if turn_count > 1
),
)
policy = create_policy(config.policy_name)
policy = create_policy(
config.policy_name,
migration_reject_threshold=config.kvcache_migration_reject_threshold,
)
state = RoutingState.create(config.topology)
state_lock = asyncio.Lock()
semaphore = asyncio.Semaphore(config.concurrency_limit)
@@ -350,6 +358,22 @@ async def _run_request(
async with state_lock:
state.finish(request, decision)
# Migration feedback: if this request was forced into a fallback path
# because the chosen D rejected admission, record the (session, D)
# rejection so KvAwarePolicy can migrate this session next turn.
if _is_admission_rejection_mode(execution.execution_mode):
state.record_admission_reject(
request.session_id,
decision.decode_worker_id,
)
# Reset-on-success: a successful direct-to-D path proves D-X can
# currently serve this session — clear the cumulative reject counter
# so that brief past saturation doesn't permanently blacklist the D.
# (MIGRATION_V1_FINDINGS §4.1: blacklist-permanence bug fix.)
elif execution.execution_mode == "kvcache-direct-to-d-session":
state.session_d_rejects[
(request.session_id, decision.decode_worker_id)
] = 0
return RequestMetrics.from_decision(
request,
@@ -1349,6 +1373,49 @@ def _is_stale_decode_session_error(exc: Exception) -> bool:
)
# execution_mode substrings that signal D-side admission rejected this request.
# Used by _run_request to update state.session_d_rejects so KvAwarePolicy can
# migrate persistently-starved sessions to a different D next turn.
_ADMISSION_REJECTION_SUBSTRINGS = (
"session-cap",
"no-d-capacity",
"d-backpressure",
)
def _is_admission_rejection_mode(execution_mode: str) -> bool:
return any(token in execution_mode for token in _ADMISSION_REJECTION_SUBSTRINGS)
def _fallthrough_reason(
*,
request: TraceRequest,
config: ReplayConfig,
decision,
direct_append_length: int | None,
direct_session_reused: bool,
direct_session_reset: bool,
) -> str:
"""Classify why a turn-2+ KVC request fell through to the seed/large-append branch.
Returns a short label suffix used in execution_mode strings to replace the
misleading 'large-append' label (TEAM_REPORT §2.7). In particular,
'session-not-resident' is the §1 starvation signature — direct_session_reused
is False because the session was never opened on the policy-chosen D.
"""
if not direct_session_reused:
return "session-not-resident"
if direct_session_reset:
return "session-was-evicted"
if direct_append_length is None:
return "no-direct-info"
if direct_append_length > config.kvcache_direct_max_uncached_tokens:
return "real-large-append"
if not _should_bypass_prefill(request=request, config=config, decision=decision):
return "policy-no-bypass"
return "other-large-append"
def _dynamic_decode_headroom_tokens(
*,
residency: DecodeResidencyState,
@@ -2510,6 +2577,17 @@ async def _execute_request(
decode_residency=decode_residency,
)
# TEAM_REPORT §2.7: 'large-append' is misleading — most fallthroughs are
# actually 'session-not-resident-on-pinned-D' (§1 starvation). Classify
# the real reason and embed it in the execution_mode label.
fallthrough = _fallthrough_reason(
request=request,
config=config,
decision=decision,
direct_append_length=direct_append_length,
direct_session_reused=direct_session_reused,
direct_session_reset=direct_session_reset,
)
seed_filter_reason = _seed_filter_reason(
request=request,
config=config,
@@ -2521,7 +2599,7 @@ async def _execute_request(
client=client,
config=config,
decision=decision,
execution_mode=f"pd-router-fallback-large-append-{seed_filter_reason}",
execution_mode=f"pd-router-fallback-{fallthrough}-{seed_filter_reason}",
decode_residency=decode_residency,
)
async with direct_session_lock:
@@ -2566,7 +2644,7 @@ async def _execute_request(
client=client,
config=config,
decision=decision,
execution_mode="pd-router-fallback-large-append-session-cap",
execution_mode=f"pd-router-fallback-{fallthrough}-session-cap",
decode_residency=decode_residency,
)
if can_seed:
@@ -2582,23 +2660,27 @@ async def _execute_request(
decode_residency=decode_residency,
reserved_tokens=reserved_tokens,
execution_mode=(
"pd-router-large-append-reseed"
f"pd-router-{fallthrough}-reseed"
+ _eviction_suffix(
evicted_sessions,
prefill_backed_evictions,
)
),
)
# Preserve seed_reason in the label so migration feedback fires for
# 'd-no-space' / 'd-*-backpressure' (matched via _is_admission_rejection_mode).
if _is_decode_backpressure_reason(seed_reason):
mode_label = f"pd-router-fallback-{fallthrough}-d-backpressure"
elif seed_reason == "d-no-space":
mode_label = f"pd-router-fallback-{fallthrough}-no-d-capacity"
else:
mode_label = f"pd-router-fallback-{fallthrough}"
return await _invoke_plain_router(
request=request,
client=client,
config=config,
decision=decision,
execution_mode=(
"pd-router-fallback-d-backpressure"
if _is_decode_backpressure_reason(seed_reason)
else "pd-router-fallback-large-append"
),
execution_mode=mode_label,
decode_residency=decode_residency,
)