diff --git a/docs/cli-options.md b/docs/cli-options.md index 3af9300901..14510788e2 100644 --- a/docs/cli-options.md +++ b/docs/cli-options.md @@ -1169,7 +1169,7 @@ The grace period in seconds to wait for responses after warmup phase ends. Only #### `--warmup-concurrency-ramp-duration` `` -Duration in seconds to ramp warmup session concurrency from 1 to target. If not set, uses `--concurrency-ramp-duration` value. +Duration in seconds to ramp warmup session concurrency from 1 to target. If not set, uses `--concurrency-ramp-duration` value. Under an agentic scenario, this paces the auto-synthesized trajectory warmup without changing profiling concurrency.
_Constraints: > 0_ #### `--warmup-prefill-concurrency-ramp-duration` `` @@ -2704,7 +2704,7 @@ The grace period in seconds to wait for responses after warmup phase ends. Only #### `--warmup-concurrency-ramp-duration` `` -Duration in seconds to ramp warmup session concurrency from 1 to target. If not set, uses `--concurrency-ramp-duration` value. +Duration in seconds to ramp warmup session concurrency from 1 to target. If not set, uses `--concurrency-ramp-duration` value. Under an agentic scenario, this paces the auto-synthesized trajectory warmup without changing profiling concurrency.
_Constraints: > 0_ #### `--warmup-prefill-concurrency-ramp-duration` `` diff --git a/docs/tutorials/agentx-mvp.md b/docs/tutorials/agentx-mvp.md index 0172d077f4..de9327681c 100644 --- a/docs/tutorials/agentx-mvp.md +++ b/docs/tutorials/agentx-mvp.md @@ -393,6 +393,12 @@ multi-turn coding contexts before any measurement starts. When the profiling phase begins, every trajectory resumes from `k_i + 1` — and the server's cache already holds the prefix. +For a high-concurrency deployment with a narrow request ingress, pass +`--warmup-concurrency-ramp-duration SECONDS` to open the synthesized warmup's +session-tree limit gradually from 1 to `--concurrency`. The ramp paces the +mandatory snapshot primers and any cache-pressure warmup requests; profiling +still starts directly at the configured concurrency after warmup drains. + The `k_i` values are deterministic given the random seed: same dataset + same seed = same trajectories + same start points + same recycle order, on any machine. That's why the scenario insists on a seed. diff --git a/src/aiperf/config/flags/_converter_profiling.py b/src/aiperf/config/flags/_converter_profiling.py index ea8bc784f2..cfdc173abd 100644 --- a/src/aiperf/config/flags/_converter_profiling.py +++ b/src/aiperf/config/flags/_converter_profiling.py @@ -69,6 +69,14 @@ def _apply_agentic_replay_fields(phase: dict[str, Any], cli: CLIConfig) -> None: for attr in _AGENTIC_REPLAY_ROUTES: if attr in fields_set: phase[attr] = getattr(cli, attr) + # AGENTIC_REPLAY replaces user-declared warmup phases with a phase + # synthesized from this profiling config. Preserve the existing warmup + # ramp flag on that source config so the synthesized phase can consume it + # without applying the ramp to profiling itself. + if cli.scenario is not None and "warmup_concurrency_ramp_duration" in fields_set: + phase["agentic_warmup_concurrency_ramp_duration"] = ( + cli.warmup_concurrency_ramp_duration + ) # v1 parity: under a --scenario, --warmup-grace-period fed the agentic # warmup barrier grace (there was no dedicated flag). Route it onto # agentic_warmup_grace_period when the dedicated flag is unset; an diff --git a/src/aiperf/config/flags/cli_config.py b/src/aiperf/config/flags/cli_config.py index 75a8afc3b6..b0d9995e64 100644 --- a/src/aiperf/config/flags/cli_config.py +++ b/src/aiperf/config/flags/cli_config.py @@ -2403,7 +2403,9 @@ def url(self) -> str: Field( gt=0, description="Duration in seconds to ramp warmup session concurrency from 1 to target. " - "If not set, uses `--concurrency-ramp-duration` value.", + "If not set, uses `--concurrency-ramp-duration` value. Under an " + "agentic scenario, this paces the auto-synthesized trajectory " + "warmup without changing profiling concurrency.", ), CLIParameter( name=("--warmup-concurrency-ramp-duration",), diff --git a/src/aiperf/config/phases.py b/src/aiperf/config/phases.py index c1a502c62c..f5a3b7294d 100644 --- a/src/aiperf/config/phases.py +++ b/src/aiperf/config/phases.py @@ -537,6 +537,20 @@ class ConcurrencyPhase(BasePhaseConfig): ), ] + agentic_warmup_concurrency_ramp_duration: Annotated[ + float | None, + Field( + default=None, + gt=0, + description="AGENTIC_REPLAY only: duration in seconds to ramp the " + "auto-synthesized warmup's session concurrency from 1 to the " + "profiling target. Populated by " + "--warmup-concurrency-ramp-duration under an agentic scenario and " + "read by timing.config._build_agentic_warmup_config. The profiling " + "phase itself is unaffected.", + ), + ] + # ============================================================================= # RATE-CONTROLLED PHASES diff --git a/src/aiperf/config/schema/aiperf-config.schema.json b/src/aiperf/config/schema/aiperf-config.schema.json index 122cd9ac39..1d65323dbe 100644 --- a/src/aiperf/config/schema/aiperf-config.schema.json +++ b/src/aiperf/config/schema/aiperf-config.schema.json @@ -1344,6 +1344,20 @@ "description": "AGENTIC_REPLAY only: grace period in seconds the auto-synthesized warmup barrier waits for in-flight priming requests after the warmup burst sends. Read off the profiling phase by ``timing.config._build_agentic_warmup_config`` (the agentic warmup is not a user-declared phase, so it does not inherit ``--warmup-grace-period``, which requires ``--warmup-duration``). None waits indefinitely (the agentic warmup must complete every primed trajectory before profiling starts).", "title": "Agenticwarmupgraceperiod" }, + "agenticWarmupConcurrencyRampDuration": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "description": "AGENTIC_REPLAY only: duration in seconds to ramp the auto-synthesized warmup's session concurrency from 1 to the profiling target. Populated by --warmup-concurrency-ramp-duration under an agentic scenario and read by timing.config._build_agentic_warmup_config. The profiling phase itself is unaffected.", + "title": "Agenticwarmupconcurrencyrampduration" + }, "adaptive_scale": { "description": "Enable single-run adaptive scale control for this phase. Accepts either a boolean or a nested adaptive-scale configuration block.", "title": "Adaptivescale", @@ -5214,6 +5228,20 @@ "description": "AGENTIC_REPLAY only: grace period in seconds the auto-synthesized warmup barrier waits for in-flight priming requests after the warmup burst sends. Read off the profiling phase by ``timing.config._build_agentic_warmup_config`` (the agentic warmup is not a user-declared phase, so it does not inherit ``--warmup-grace-period``, which requires ``--warmup-duration``). None waits indefinitely (the agentic warmup must complete every primed trajectory before profiling starts).", "title": "Agenticwarmupgraceperiod" }, + "agenticWarmupConcurrencyRampDuration": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "description": "AGENTIC_REPLAY only: duration in seconds to ramp the auto-synthesized warmup's session concurrency from 1 to the profiling target. Populated by --warmup-concurrency-ramp-duration under an agentic scenario and read by timing.config._build_agentic_warmup_config. The profiling phase itself is unaffected.", + "title": "Agenticwarmupconcurrencyrampduration" + }, "adaptive_scale": { "description": "Enable single-run adaptive scale control for this phase. Accepts either a boolean or a nested adaptive-scale configuration block.", "title": "Adaptivescale", @@ -8205,6 +8233,20 @@ "description": "AGENTIC_REPLAY only: grace period in seconds the auto-synthesized warmup barrier waits for in-flight priming requests after the warmup burst sends. Read off the profiling phase by ``timing.config._build_agentic_warmup_config`` (the agentic warmup is not a user-declared phase, so it does not inherit ``--warmup-grace-period``, which requires ``--warmup-duration``). None waits indefinitely (the agentic warmup must complete every primed trajectory before profiling starts).", "title": "Agenticwarmupgraceperiod" }, + "agenticWarmupConcurrencyRampDuration": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "number" + }, + { + "type": "null" + } + ], + "default": null, + "description": "AGENTIC_REPLAY only: duration in seconds to ramp the auto-synthesized warmup's session concurrency from 1 to the profiling target. Populated by --warmup-concurrency-ramp-duration under an agentic scenario and read by timing.config._build_agentic_warmup_config. The profiling phase itself is unaffected.", + "title": "Agenticwarmupconcurrencyrampduration" + }, "adaptive_scale": { "description": "Enable single-run adaptive scale control for this phase. Accepts either a boolean or a nested adaptive-scale configuration block.", "title": "Adaptivescale", @@ -11639,6 +11681,31 @@ "title": "Agenticwarmupgraceperiod", "x-jinja2-supported": true }, + "agenticWarmupConcurrencyRampDuration": { + "anyOf": [ + { + "exclusiveMinimum": 0, + "type": "number" + }, + { + "type": "null" + }, + { + "type": "string", + "pattern": ".*\\{\\{.*\\}\\}.*", + "description": "Jinja2 template (e.g., '{{ variable }}')." + }, + { + "type": "string", + "pattern": ".*\\$\\{[A-Za-z_][A-Za-z0-9_]*(?::[^}]*)?\\}.*", + "description": "Environment variable (e.g., '${VAR}' or '${VAR:default}')." + } + ], + "default": null, + "description": "AGENTIC_REPLAY only: duration in seconds to ramp the auto-synthesized warmup's session concurrency from 1 to the profiling target. Populated by --warmup-concurrency-ramp-duration under an agentic scenario and read by timing.config._build_agentic_warmup_config. The profiling phase itself is unaffected.", + "title": "Agenticwarmupconcurrencyrampduration", + "x-jinja2-supported": true + }, "adaptive_scale": { "description": "Enable single-run adaptive scale control for this phase. Accepts either a boolean or a nested adaptive-scale configuration block.", "title": "Adaptivescale", diff --git a/src/aiperf/timing/config.py b/src/aiperf/timing/config.py index 50e662192f..b822a5bdca 100644 --- a/src/aiperf/timing/config.py +++ b/src/aiperf/timing/config.py @@ -657,6 +657,9 @@ def _build_agentic_warmup_config(phase: PhaseConfig) -> CreditPhaseConfig | None arrival_smoothness=getattr(phase, "smoothness", None), seamless=False, grace_period_sec=grace_period if grace_period is not None else float("inf"), + concurrency_ramp_duration_sec=getattr( + phase, "agentic_warmup_concurrency_ramp_duration", None + ), agentic_cache_warmup_duration_sec=cache_warmup_duration, warmup_requests_per_lane=requests_per_lane, ) diff --git a/tests/unit/config/test_converter_profiling_phase_routes.py b/tests/unit/config/test_converter_profiling_phase_routes.py index 1ea92f33bc..79d6d5bdd6 100644 --- a/tests/unit/config/test_converter_profiling_phase_routes.py +++ b/tests/unit/config/test_converter_profiling_phase_routes.py @@ -318,6 +318,23 @@ def test_agentic_warmup_grace_does_not_require_duration(self): prof = build_profiling(user) assert prof["agentic_warmup_grace_period"] == 0.0 + def test_agentic_warmup_concurrency_ramp_routes_to_synthesized_phase(self): + """The agentic scenario synthesizes warmup from the profiling phase, + so preserve the warmup-only ramp there without ramping profiling. + """ + loadgen = CLIConfig( + scenario="inferencex-agentx-mvp", + concurrency=768, + benchmark_duration=900, + warmup_concurrency_ramp_duration=60.0, + ) + user = _make_user(loadgen=loadgen) + + prof = build_profiling(user) + + assert prof["agentic_warmup_concurrency_ramp_duration"] == 60.0 + assert "concurrency_ramp" not in prof + class TestSystemIdleGapCapRouting: def test_system_idle_gap_cap_routes_onto_profiling_phase(self) -> None: diff --git a/tests/unit/timing/test_phase_config_agentic_replay.py b/tests/unit/timing/test_phase_config_agentic_replay.py index 7baf752b74..fd7e07a40a 100644 --- a/tests/unit/timing/test_phase_config_agentic_replay.py +++ b/tests/unit/timing/test_phase_config_agentic_replay.py @@ -67,6 +67,34 @@ def test_warmup_config_total_expected_requests_tracks_concurrency() -> None: assert warmup.total_expected_requests == concurrency +def test_warmup_config_propagates_agentic_concurrency_ramp() -> None: + """The warmup-only CLI ramp reaches the auto-synthesized phase without + changing the profiling phase's concurrency schedule. + """ + phase = _PHASE_ADAPTER.validate_python( + { + "name": "profiling", + "type": "concurrency", + "concurrency": 768, + "duration": 900, + "timing_mode": TimingMode.AGENTIC_REPLAY, + "agentic_warmup_concurrency_ramp_duration": 60.0, + } + ) + + warmup = _build_agentic_warmup_config(phase) + profiling = _build_profiling_config( + phase, + default_cancellation=RequestCancellationConfig(), + phase_index=0, + profiling_index=0, + ) + + assert warmup is not None + assert warmup.concurrency_ramp_duration_sec == 60.0 + assert profiling.concurrency_ramp_duration_sec is None + + def test_warmup_grace_defaults_to_infinity() -> None: """With no ``agentic_warmup_grace_period`` set, the warmup barrier waits indefinitely (inf) until every primed trajectory returns.""" phase = _ar_profiling_phase()