Skip to content

Commit 205b4dd

Browse files
ajcasagrandeclaude
andcommitted
feat(routing): session-routing plugin layer for per-session router affinity
One plugin category (session_routing) + one CLI flag (--session-routing <mode>, with repeatable --session-routing-opt key=value) covers every mechanism for telling an external router which session a request belongs to, so all of a conversation's turns re-land on the replica holding its KV prefix. "Session" (live instance keyed by x_correlation_id), not "conversation" (dataset template), is the affinity identity. Exactly one mode runs per invocation; the selected plugin is instantiated once per worker by InferenceClient and invoked at the request-serialization chokepoint. Built-in modes: - dynamo_headers (preset): X-Dynamo-Session-ID plus X-Dynamo-Parent-Session-ID on subagent children, for a Dynamo frontend running --router-session-affinity-ttl-secs. - dynamo_nvext: nvext.session_control request-body metadata -- bind (with timeout, option timeout_seconds, default 300) on every non-final turn, close on the final turn -- for Dynamo builds that implement session_control. Plugin values win over dataset-shipped session_control keys; other nvext content is preserved. - smg_routing_key (preset): X-SMG-Routing-Key for the SGLang Model Gateway manual policy. - session_id_header (preset): additive X-Session-ID, parameterless. - identity_headers: fully generic tiered identity headers emitting exactly what is configured, nothing by default. Options (each a comma-separable name list): session (this session's correlation ID), parent (immediate parent's ID, omitted for roots), root (session-tree root's ID -- whole-tree affinity, e.g. pinning an entire agent tree to one replica for prefix-cache locality). Requires at least one name across tiers; names are validated as RFC 9110 tokens at config time and must be case-insensitively unique across all tiers combined. Each header preset is documented with its identity_headers equivalent. Removed (superseded): --use-dynamo-conv-aware-routing / --use-dynamo-session-control, --use-legacy-dynamo-session-control, --dynamo-session-timeout-seconds, the dynamo_session_control module, AIPERF_HTTP_X_SESSION_ID_FROM_CORRELATION_ID, AIPERF_HTTP_X_SMG_ROUTING_KEY_FROM_CORRELATION_ID, and the base_transports env-toggle header blocks. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Anthony Casagrande <acasagrande@nvidia.com>
1 parent 0d2aa05 commit 205b4dd

48 files changed

Lines changed: 3166 additions & 865 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ Log File: /home/user/aiperf/artifacts/granite4:350m-openai-chat-concurrency1/log
200200
| [CLI Options](docs/cli-options.md) | Complete command and option reference |
201201
| [Metrics Reference](docs/metrics-reference.md) | All metric definitions, formulas, and requirements |
202202
| [Environment Variables](docs/environment-variables.md) | All `AIPERF_*` configuration variables |
203-
| [Plugin System](docs/plugins/plugin-system.md) | Plugin architecture, 27 categories, creation guide |
203+
| [Plugin System](docs/plugins/plugin-system.md) | Plugin architecture, 31 categories, creation guide |
204204
| [Creating Plugins](docs/plugins/creating-your-first-plugin.md) | Step-by-step plugin tutorial |
205205
| [Accuracy Benchmarks](docs/accuracy/accuracy_stubs.md) | Accuracy evaluation stubs and datasets |
206206
| [Benchmark Modes](docs/benchmark-modes/trace-replay.md) | Trace replay and timing modes |

docs/cli-options.md

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -266,21 +266,15 @@ Use the legacy 'max_tokens' field instead of 'max_completion_tokens' in request
266266
Use server-reported token counts from API usage fields instead of client-side tokenization. When enabled, tokenizers are still loaded (needed for dataset generation) but tokenizer.encode() is not called for computing metrics. Token count fields will be None if the server does not provide usage information. For OpenAI-compatible streaming endpoints (chat/completions), stream_options.include_usage is automatically configured when this flag is enabled. Recommended whenever the AIPerf tokenizer can disagree with the server's tokenizer (e.g. unmatched tokenizer revision, vendor-specific BPE merges, or chat templates that differ from the server) — this most often shows up as an output sequence length (OSL) mismatch even when the server is honoring the request (e.g. with ignore_eos=true).
267267
<br/>_Flag (no value required)_
268268

269-
#### `--use-dynamo-conv-aware-routing`, `--use-dynamo-session-control`
269+
#### `--session-routing` `<str>`
270270

271-
Emit Dynamo nvext.session_control in OpenAI-compatible request bodies so Dynamo can bind all turns from the same replayed conversation lineage to the same backend worker. This is only intended for Dynamo frontends that implement session_control.
272-
<br/>_Flag (no value required)_
273-
274-
#### `--use-legacy-dynamo-session-control`
275-
276-
Emit the legacy Dynamo nvext.session_control lifecycle that released Dynamo (v1.2.x) understands: action 'open' on the first turn, session_id only on intermediate turns, and action 'close' on the final turn. Use this when the target Dynamo predates the 'bind' action (added in v1.3.0-dev); otherwise 'bind' is rejected with an HTTP 400. Requires --use-dynamo-conv-aware-routing, and the Dynamo deployment must expose a worker session_control endpoint for 'open' to take effect.
277-
<br/>_Flag (no value required)_
271+
Session-aware routing mode: stamps per-session identity on every request for router affinity. Presets: dynamo_headers (X-Dynamo-Session-ID + parent header), smg_routing_key (X-SMG-Routing-Key for the SGLang Model Gateway manual policy), session_id_header (additive X-Session-ID). Generic: identity_headers (any name(s) per identity tier -- session/parent/root -- via --session-routing-opt). Body-based: dynamo_nvext (nvext.session_control bind/close request-body metadata; --session-routing-opt timeout_seconds=N).
272+
<br/>_Choices: [`dynamo_headers`, `dynamo_nvext`, `smg_routing_key`, `session_id_header`, `identity_headers`]_
278273

279-
#### `--dynamo-session-timeout-seconds` `<int>`
274+
#### `--session-routing-opt` `<list>`
280275

281-
Dynamo nvext.session_control timeout in seconds when --use-dynamo-conv-aware-routing is enabled.
282-
<br/>_Constraints: ≥ 1_
283-
<br/>_Default: `300`_
276+
Repeatable key=value option for the selected --session-routing mode (e.g. --session-routing-opt timeout_seconds=600), validated against the plugin's Options model. Commas inside the value are passed through to the plugin (repeat the flag for multiple opts).
277+
<br/>_Default: `[]`_
284278

285279
#### `--connection-reuse-strategy` `<str>`
286280

@@ -1306,7 +1300,7 @@ Explore AIPerf plugins: aiperf plugins [category] [type]
13061300
#### `--category` `<str>`
13071301

13081302
Category to explore.
1309-
<br/>_Choices: [`accumulator`, `accuracy_benchmark`, `accuracy_grader`, `analyzer`, `api_router`, `arrival_pattern`, `communication`, `communication_client`, `console_exporter`, `custom_dataset_loader`, `data_exporter`, `dataset_backing_store`, `dataset_client_store`, `dataset_composer`, `dataset_sampler`, `endpoint`, `gpu_telemetry_collector`, `plot`, `public_dataset_loader`, `ramp`, `record_processor`, `service`, `service_manager`, `stream_exporter`, `timing_strategy`, `transport`, `ui`, `url_selection_strategy`, `zmq_proxy`]_
1303+
<br/>_Choices: [`accumulator`, `accuracy_benchmark`, `accuracy_grader`, `analyzer`, `api_router`, `arrival_pattern`, `communication`, `communication_client`, `console_exporter`, `custom_dataset_loader`, `data_exporter`, `dataset_backing_store`, `dataset_client_store`, `dataset_composer`, `dataset_sampler`, `endpoint`, `gpu_telemetry_collector`, `plot`, `public_dataset_loader`, `ramp`, `record_processor`, `service`, `service_manager`, `session_routing`, `stream_exporter`, `timing_strategy`, `transport`, `ui`, `url_selection_strategy`, `zmq_proxy`]_
13101304

13111305
#### `--name` `<str>`
13121306

docs/environment-variables.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,6 @@ HTTP client socket and connection configuration. Controls low-level socket optio
135135
| `AIPERF_HTTP_REQUEST_CANCELLATION_SEND_TIMEOUT` | `300.0` | ≥ 10.0, ≤ 3600.0 | Safety net timeout in seconds for waiting for HTTP request to be fully sent when request cancellation is enabled. Used as fallback when no explicit timeout is configured to prevent hanging indefinitely while waiting for the request to be written to the socket. |
136136
| `AIPERF_HTTP_IP_VERSION` | `'4'` || IP version for HTTP socket connections. Options: '4' (AF_INET, default), '6' (AF_INET6), or 'auto' (AF_UNSPEC, system chooses). |
137137
| `AIPERF_HTTP_TRUST_ENV` | `False` || Trust environment variables for HTTP client configuration. When enabled, aiohttp will read proxy settings from HTTP_PROXY, HTTPS_PROXY, and NO_PROXY environment variables. |
138-
| `AIPERF_HTTP_X_SESSION_ID_FROM_CORRELATION_ID` | `False` || Also send X-Session-ID with the stable X-Correlation-ID value. Use this when an external router requires a session-affinity header. |
139-
| `AIPERF_HTTP_X_SMG_ROUTING_KEY_FROM_CORRELATION_ID` | `False` || Also send X-SMG-Routing-Key with the stable X-Correlation-ID value. Use this with the SGLang Model Gateway manual routing policy. |
140138
| `AIPERF_HTTP_VIDEO_POLL_INTERVAL` | `0.1` | ≥ 0.001, ≤ 10.0 | Interval in seconds between status polls for async video generation jobs. Lower values provide faster completion detection but increase server load. Applies to the aiohttp transport. |
141139

142140
## LOGGING

docs/plugins/plugin-system.md

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ for entry, cls in plugins.iter_all(PluginType.ENDPOINT):
100100

101101
## Plugin Categories
102102

103-
AIPerf supports 27 plugin categories organized by function:
103+
AIPerf supports 31 plugin categories organized by function:
104104

105105
### Timing Categories
106106

@@ -128,6 +128,59 @@ AIPerf supports 27 plugin categories organized by function:
128128
| `endpoint` | `EndpointType` | API endpoint implementations (chat, completions, embeddings, etc.) |
129129
| `transport` | `TransportType` | Network transport (HTTP via aiohttp) |
130130

131+
### Session Routing Category
132+
133+
| Category | Enum | Description |
134+
|----------|------|-------------|
135+
| `session_routing` | `SessionRoutingType` | Stamps per-session identity (headers or body metadata) onto outbound requests so an external router pins every turn of a session to one worker; selected via `--session-routing` |
136+
137+
**Purpose.** A session-routing plugin gives an external router (SGLang Model Gateway, Dynamo, a
138+
generic session-affinity load balancer) the identity of the session a request belongs to, so all of
139+
a conversation's turns re-land on the replica holding its KV prefix. Exactly one mode runs per
140+
invocation; the selected plugin is instantiated once per worker by `InferenceClient` and invoked at
141+
the request-serialization chokepoint. The base class is `SessionRoutingBase`
142+
(`src/aiperf/workers/session_routing.py`); passthrough is the default (no headers, body unchanged).
143+
144+
**Built-ins:**
145+
146+
| Name | Class | Description |
147+
|------|-------|-------------|
148+
| `dynamo_headers` | `DynamoHeadersRouting` | Dynamo session affinity via `X-Dynamo-Session-ID`, plus `X-Dynamo-Parent-Session-ID` on subagent children. No options. |
149+
| `dynamo_nvext` | `DynamoNvextRouting` | Dynamo session affinity via `nvext.session_control` request-body metadata (bind on non-final turns, close on the final turn). Only for Dynamo builds that implement `session_control`. Option: `timeout_seconds` (default 300). |
150+
| `smg_routing_key` | `SmgRoutingKeyRouting` | SGLang Model Gateway `manual`-policy stickiness via `X-SMG-Routing-Key`. No options. |
151+
| `session_id_header` | `SessionIdHeaderRouting` | Preset: additive `X-Session-ID` header carrying the session's correlation ID. No options. |
152+
| `identity_headers` | `IdentityHeadersRouting` | Fully generic tiered identity headers: emits exactly what you configure, nothing by default. Options (each a comma-separable name list): `session` (this session's ID), `parent` (immediate parent's ID; omitted for roots), `root` (session-tree root's ID, for whole-tree affinity). Requires at least one name across tiers; names must be unique (case-insensitive) across all tiers combined, including within a single tier. |
153+
154+
The three header presets are expressible via `identity_headers`: `session_id_header`
155+
`session=X-Session-ID`, `smg_routing_key``session=X-SMG-Routing-Key`, and `dynamo_headers`
156+
`session=X-Dynamo-Session-ID` + `parent=X-Dynamo-Parent-Session-ID`. Reach for `identity_headers`
157+
when a router needs custom names, several headers stamped with the same value (layered routers),
158+
or tree-scoped affinity; reach for a preset when it already says exactly what you mean.
159+
160+
**Options (`--session-routing-opt key=value`).** Each plugin exposes an `Options` Pydantic model
161+
(`extra="forbid"`, so unknown keys are rejected at startup). Repeated `--session-routing-opt`
162+
pairs populate it; values are coerced to the model's field types and canonicalized at config
163+
resolution, so downstream code always sees typed values. Commas inside a value are passed through
164+
to the plugin (repeat the flag for multiple opts). `--session-routing-opt` without
165+
`--session-routing` is an error, and parameterless modes (`dynamo_headers`, `smg_routing_key`,
166+
`session_id_header`) reject every opt key.
167+
168+
**`mutates_body` and the PAYLOAD_BYTES fast path.** A plugin sets the `mutates_body` class var to
169+
`True` when `transform_body` changes the payload (only `dynamo_nvext` does). Body-mutating modes are
170+
incompatible with the verbatim PAYLOAD_BYTES mmap fast path and are gated off it at three points:
171+
dataset build, cache hit, and runtime. Header-only modes leave the body untouched and keep the fast
172+
path. `transform_body` must never mutate its input — the structured path shares cached
173+
`Turn.raw_payload` dicts with the dataset — so it returns a new dict.
174+
175+
**`on_session_end` contract.** Fires strictly after the session's last worker-side activity, on
176+
every terminal path (final turn, cancellation, terminal context overflow, cancel-before-start). It
177+
is post-session cleanup only and must be idempotent (default no-op).
178+
179+
**Stateful-plugin rule.** A stateful plugin must key its instance state on `ctx.x_correlation_id`
180+
only. A session tree deliberately spans workers, so tree-keyed worker state fragments across
181+
processes. For tree-scoped behavior, use the stateless per-request context facts
182+
`root_correlation_id` and `is_tree_final` instead of accumulating state.
183+
131184
### Processing Categories
132185

133186
| Category | Enum | Description |

src/aiperf/common/config/config_defaults.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,6 @@ class EndpointDefaults:
4848
CONNECTION_REUSE_STRATEGY = ConnectionReuseStrategy.POOLED
4949
DOWNLOAD_VIDEO_CONTENT = False
5050
REQUEST_CONTENT_TYPE = None
51-
USE_DYNAMO_CONV_AWARE_ROUTING = False
52-
USE_LEGACY_DYNAMO_SESSION_CONTROL = False
53-
DYNAMO_SESSION_TIMEOUT_SECONDS = 300
5451
# Readiness probe defaults. Timeout 0 disables the probe (the default);
5552
# any positive value enables it. Interval is only consulted when the
5653
# probe is enabled but is validated positive so mis-configuration

0 commit comments

Comments
 (0)