Skip to content

[PENDING: #735] test: side-by-side offered-load parity against vllm bench - #736

Open
Bslabe123 wants to merge 4 commits into
kubernetes-sigs:mainfrom
Bslabe123:test/tool-parity-absorber
Open

[PENDING: #735] test: side-by-side offered-load parity against vllm bench#736
Bslabe123 wants to merge 4 commits into
kubernetes-sigs:mainfrom
Bslabe123:test/tool-parity-absorber

Conversation

@Bslabe123

@Bslabe123 Bslabe123 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Part of #481. Runs inference-perf and vllm bench serve against the same recording server and asserts the two tools offered the same workload, which nothing in this repo checks today.

Row in #606: "End-to-end / Peer-benchmark skew (#481)", type sim.

Stacked on #735 (test/load-shape-accuracy-633, 9b8c938); its commit shows in this diff until it merges. Rate and concurrency here are computed by #735's e2e/utils/load_shape.py, so the e2e tier has one definition of each. #735 reads the client's own timestamps; this reads the server's.

Why this is needed

Two recent skew reports were first read as inference-perf bugs and both decomposed into workload non-equivalence (the legacy range_ratio footgun, 0.0 meaning Uniform[0, len]; length-sampling, max-model-len and warmup deltas). Nothing could ask whether the two tools were even asked for the same thing.

This harness never merge-gates, by design. A delta can move from an inference-perf regression, a peer-tool change, or workload non-equivalence, and a gate cannot attribute which. The workload mapping is ours and asserted loudly; peer results are not ours and never asserted on.

What the test does

  • The oracle is an absorber (e2e/utils/absorber.py), an OpenAI-compatible aiohttp server that records every request (arrival, reply-finished, body) while streaming at a configured TTFT and inter-chunk interval. The recorded requests are the offered workload; no stored baseline.
  • Per tool, against expected.yaml: exact request count, max_tokens multiset, stream and ignore_eos flags, per-request prompt tokens under the shared tokenizer (with tolerance), and either delivered rate within rate_tolerance(n, arrival) or delivered concurrency via assert_delivered_concurrency (never above C, time-weighted plateau mean within half a slot).
  • Tool against tool: the same quantities compared directly; a failure names the knob that leaked.
  • No rate tolerance numbers. Each tool declares its spacing (tools.<tool>.arrival: constant|poisson); the tolerance follows from the request count. Tightening a case means raising num_requests.
  • No reported-metric parity: no TTFT, ITL or throughput number is asserted.
  • Warmup traffic is declared, not absorbed: leading_extra_requests trims the earliest N arrivals for one tool; a nonzero value is itself a parity finding.

The drop-in case contract (the reusable part)

e2e/tests/parity/cases/<name>/
  inference-perf.yaml   # verbatim config; harness overwrites only server.base_url + tokenizer path
  vllm-bench.args       # verbatim args, one per line, # comments ok; harness appends
                        # --base-url --model --tokenizer --save-result --result-filename
  expected.yaml         # invariants, absorber pacing, per-tool arrival + leading_extra_requests

Adding a case touches no Python. Starter cases: a_fixed_rate (40 QPS, 15s, 600 requests: the smallest count at which the Poisson side earns a useful 16% bound, matching #735) and b_fixed_concurrency (48 requests at concurrency 8).

Verified against the pinned vllm source, not by running it

Read from tag v0.10.0: benchmark() always runs one warmup prompt first (leading_extra_requests: 1); --random-range-ratio 0 gives fixed lengths; --max-concurrency is a semaphore; --random-input-len is reduced by the special-token count (hence prompt_tokens_rel_tol); --request-rate at default --burstiness 1.0 is Poisson (arrival: poisson); --backend is vestigial, so args files corrected to --endpoint-type openai; stream: true is hardcoded, so stream: false is not expressible on the vllm leg. transformers<5 is required (5.13 dropped all_special_tokens_extended).

Status

Verified locally: pdm run validate clean; pdm run test 898 passed, 16 skipped; pdm run pytest e2e/tests/parity 3 passed, 4 skipped. Against the absorber: case a rate 40.01/s on a 5% budget; case b plateau mean 7.88 of 8 on a half-slot budget (the server sees the client's turnaround gap; client-side timestamps do not); C-1 and C+1 both fail.

Not verified:

  • The vllm leg has never been executed. No vllm in this environment; all 4 skips are vllm-side. Source reading does not prove the run completes, that the absorber satisfies vllm bench's parsing, or that both tools land inside tolerance.
  • prompt_tokens_rel_tol: 0.15 is sized by argument, not observation. VLLM_PIN_DATE still warns, deliberately.
  • No CI wiring (and never merge-blocking when wired). aiperf not started.

@kubernetes-prow kubernetes-prow Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 17, 2026
@kubernetes-prow

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: Bslabe123

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kubernetes-prow kubernetes-prow Bot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. approved Indicates a PR has been approved by an approver from all required OWNERS files. labels Aug 17, 2026
@github-actions github-actions Bot added the do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. label Aug 17, 2026
@kubernetes-prow kubernetes-prow Bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Aug 17, 2026
@Bslabe123 Bslabe123 mentioned this pull request Aug 17, 2026
64 tasks
@github-actions github-actions Bot added do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. and removed do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. labels Aug 17, 2026
@Bslabe123 Bslabe123 changed the title [WIP] test: side-by-side offered-load parity against vllm bench test: side-by-side offered-load parity against vllm bench Aug 18, 2026
@Bslabe123
Bslabe123 marked this pull request as ready for review August 18, 2026 16:51
@kubernetes-prow kubernetes-prow Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Aug 18, 2026
@kubernetes-prow
kubernetes-prow Bot requested a review from jjk-g August 18, 2026 16:51
@Bslabe123
Bslabe123 force-pushed the test/tool-parity-absorber branch from 9bf8546 to 33dfda0 Compare August 18, 2026 17:05
@Bslabe123 Bslabe123 changed the title test: side-by-side offered-load parity against vllm bench [PENDING: #735] test: side-by-side offered-load parity against vllm bench Aug 18, 2026
@Bslabe123
Bslabe123 force-pushed the test/tool-parity-absorber branch from 33dfda0 to 500c2f0 Compare August 18, 2026 18:19
@github-actions github-actions Bot removed the do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. label Aug 18, 2026
@Bslabe123
Bslabe123 force-pushed the test/tool-parity-absorber branch from 500c2f0 to 5631d4d Compare August 19, 2026 20:16
@github-actions github-actions Bot added do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. and removed do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. labels Aug 19, 2026
@Bslabe123 Bslabe123 added the priority/important-longterm Important over the long term, but may not be staffed and/or may need multiple releases to complete. label Aug 20, 2026
@Bslabe123
Bslabe123 force-pushed the test/tool-parity-absorber branch from 5631d4d to 76f4bee Compare August 26, 2026 15:50
@github-actions github-actions Bot added do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. and removed do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. labels Aug 26, 2026
@Bslabe123
Bslabe123 force-pushed the test/tool-parity-absorber branch from 76f4bee to 289f833 Compare August 26, 2026 17:48
@github-actions github-actions Bot added the do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. label Aug 26, 2026
@github-actions github-actions Bot removed the do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. label Aug 26, 2026
Sim-backed e2e for the stimulus side of the load generator: that the load
actually offered matches the load that was configured. Nothing verified
this before, although docs/loadgen.md states the concurrent load type
"achieves the exact concurrency specified".

Adds e2e/utils/load_shape.py with the reconstruction helpers (in-flight
sweep line, closed-loop plateau window, arrival-process tolerance) so the
worker matrix in kubernetes-sigs#632 can reuse them. Adds a test module with four
sim-backed cases (constant and poisson rate stages, concurrency levels
divisible and not divisible by num_workers) plus eight helper self-tests
that prove the assertions can go red.

Delivered concurrency is reconstructed from the raw per-request start and
end timestamps, not read off load_summary["concurrency"], which reportgen
copies straight from the stage config.

Part of kubernetes-sigs#633.
… serve

Point both tools at an absorber (an OpenAI-compatible server whose job is
to record every request while streaming realistically paced responses) and
assert on the load each tool actually offered: request count, per-request
prompt token lengths and max_tokens, sampling flags, realized arrival rate,
and peak concurrency. Reported-metric parity is deliberately out of scope;
historically the discrepancies live in workload interpretation
(range-ratio semantics, warmup requests, arrival process, worker caps),
not in the runners. Cross-tool metric deltas are unowned behavior, so this
is a diagnostic, never a merge gate.

Each case is a directory: an inference-perf config, a verbatim
vllm bench serve arg file, and the invariants both must satisfy, so adding
a parity case means adding files, not editing Python. Per-tool warmup
traffic is declared per case (vllm bench's initial test request), keeping
the asymmetry visible instead of silently absorbed.

Every vllm flag in the case files was read off the pinned tag's
vllm/benchmarks/serve.py and datasets.py rather than guessed, including
the --random-range-ratio semantics that motivated the issue and the single
unconditional initial test request the warmup trim accounts for.

The vllm leg resolves an executable from $VLLM_BENCH_BIN or an opt-in
cached provision of a pinned vllm, and skips cleanly when neither exists;
the inference-perf leg and the absorber need no external binary, so the
suite asserts something everywhere it runs.

The case invariant files are excluded from the repo-wide config schema gate
by a glob, so a new case directory stays a files-only change while its
sibling inference-perf config keeps being schema-validated.
Docstrings, comments, README and assertion messages only; no code, YAML
values or vllm args change. Adds a plain-language summary above every
function in the parity test, defines the terms (workload, case, absorber)
up front in the README, and replaces the statistics derivation in
a_fixed_rate/expected.yaml with the practical reading of it.
The absorber now records when each reply finished (done_s) alongside the
arrival time and stops keeping its own in-flight counter. The parity test
feeds those arrival/finish pairs to e2e/utils/load_shape.py: rate via
observed_send_rate, concurrency via the sweep line and
assert_delivered_concurrency (peak never above C, time-weighted plateau
mean within half a slot), so there is one definition of each in the e2e
tier.

rate_rel_tol is gone from expected.yaml. Each tool declares its spacing
pattern (tools.<tool>.arrival: constant|poisson) and the tolerance is
rate_tolerance(num_requests, arrival). Case a rises from 40 requests at
8/s to 600 at 40/s, the smallest count at which the Poisson side earns a
useful bound (16%), matching the load-shape accuracy test.

Measured against the absorber: rate 40.01/s (5% budget), plateau mean
7.88 of 8 (half-slot budget); C-1 and C+1 both fail.
@Bslabe123
Bslabe123 force-pushed the test/tool-parity-absorber branch from 289f833 to 9c7dfad Compare September 3, 2026 20:30
@github-actions github-actions Bot added do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. and removed do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. labels Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. priority/important-longterm Important over the long term, but may not be staffed and/or may need multiple releases to complete. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant