# Phase 0 recon: vLLM 0.24.0 observability

Status: source recon and patch design complete; **no instrumentation patch was
implemented**.

Campaign target: operator-level, pattern-conditioned profiling of
Qwen3-30B-A3B serving on NVIDIA H20. This report is based on the pinned source
clone only. No GPU was used, no package or model was installed, and no dash
host was contacted.

Unless explicitly prefixed with `v0.20.0:`, every source path and line number
below is relative to `/home/gahow/phd/vllm-v0.24.0` at the pinned commit.

## Outcome

vLLM 0.24.0 already contains most of the raw ingredients, but they are split
across scheduler internals, aggregate metrics, worker traces, and an optional
routed-expert return path. The smallest useful patch is therefore not a new
profiler. It is one scheduler-owned per-step record that joins existing batch
composition to the existing `CUDAGraphStat`; short torch-profiler windows and
exact expert routes remain sampled Layer 2 facilities.

The proposed patch is detailed in
[`patch-design.md`](patch-design.md). Estimated surface: five files and about
450 lines including tests, with about 260 production lines.

## 1. Source pin and compatibility record

The requested tag existed and the exact requested clone command completed
successfully:

```text
git clone --depth 1 --branch v0.24.0 https://github.com/vllm-project/vllm /home/gahow/phd/vllm-v0.24.0
```

| Field | Pinned value |
|---|---|
| Repository | `https://github.com/vllm-project/vllm` |
| Local clone | `/home/gahow/phd/vllm-v0.24.0` |
| Tag | `v0.24.0` (`git describe --tags --exact-match`) |
| Commit | `ee0da84ab9e04ac7610e28580af62c365e898389` |
| Clone completion | `2026-07-11T08:26:16Z` (`2026-07-11 16:26:16 +08:00`) |
| Checkout | Detached HEAD, clean |
| Top-level entries | 45, including hidden entries and `.git` |
| Listing SHA-256 | `75222e5d3bacdd043e421c417496aaf0fa5a9429b7244698b6d3ca2218b85b58` |

The listing digest is reproducible from entry names only, not file contents:

```text
LC_ALL=C find . -mindepth 1 -maxdepth 1 -printf '%f\n' |
  LC_ALL=C sort | sha256sum
```

This definition includes `.git`, sorts bytewise under the C locale, and hashes
the 45 LF-terminated root names. It is recorded to make the otherwise ambiguous
phrase “sha256 of the top-level directory listing” precise.

### Torch and CUDA requirements

- Build and NVIDIA runtime requirements pin `torch==2.11.0` in
  `pyproject.toml:1-13`, `requirements/build/cuda.txt:1-10`, and
  `requirements/cuda.txt:1-10`; CMake repeats 2.11.0 as the supported CUDA and
  ROCm torch version at `CMakeLists.txt:62-72`.
- The NVIDIA requirements also pin `torchvision==0.26.0`,
  `flashinfer-python==0.6.12`, `flashinfer-cubin==0.6.12`, and CUDA-13 extras
  for CUTLASS DSL and Humming kernels (`requirements/cuda.txt:6-29`). Setup
  strips/substitutes those extras for CUDA 12 builds
  (`setup.py:1066-1083`).
- The source default is CUDA 13.0 (`vllm/envs.py:85-87,555-563`) and the Docker
  default is CUDA 13.0.2 (`docker/Dockerfile:14-42`). Wheel detection maps CUDA
  major 12 to `cu129` and major 13 to `cu130` (`setup.py:528-568`).
- The pinned installation document still describes the default precompiled
  binaries as CUDA 12.9 and lists CUDA 12.8 and 13.0 variants
  (`docs/getting_started/installation/gpu.cuda.inc.md:4-19,24-54`). This is a
  packaging/default distinction, not evidence that 13.0 is unsupported.
- NVIDIA compute capability 7.5+ is documented, and SM90 is in every relevant
  CMake CUDA architecture set (`docs/getting_started/installation/gpu.cuda.inc.md:9-19`;
  `CMakeLists.txt:105-118`). Optional FA3 and DeepGEMM builds need CUDA 12.3+,
  FlashMLA needs 12.9+, and the Hopper CUTLASS MoE build needs 12.3+
  (`setup.py:1111-1139`; `CMakeLists.txt:844-868`).

Implication for dash0 later: H20/SM90 is a supported architecture, but the
actual driver and installed CUDA were intentionally not queried in Phase 0.
Phase 2 must choose a torch 2.11.0-compatible `cu129` or `cu130` artifact after
checking the host driver. A wheel built against a different torch/CUDA build is
not interchangeable; the source documentation explicitly warns about binary
incompatibility (`docs/getting_started/installation/gpu.cuda.inc.md:14-19`).

## 2. Built-in observability inventory

### 2.1 Torch profiler integration

**Configuration and control.** v0.24.0 does not contain the legacy
`VLLM_TORCH_PROFILER_DIR` symbol: a repository-wide exact-name search at the
pinned commit returned zero matches. Profiling is now configured through
`ProfilerConfig`: `profiler` is `torch` or `cuda`, and
`torch_profiler_dir` holds the absolute/URI output location
(`vllm/config/profiler.py:33-46,125-145`). Optional controls include stacks,
FLOPs, gzip, CUDA-time tables, shapes, memory, frontend exclusion, delayed
start, maximum iterations, and wait/warmup/active iteration scheduling
(`vllm/config/profiler.py:48-105`).

The documented server form is a `--profiler-config` JSON object, for example
`{"profiler":"torch","torch_profiler_dir":"/abs/run/kineto"}`, followed by
POSTs to the two endpoints (`docs/contributing/profiling.md:46-85`).

When profiler config is present, the serving router exposes POST
`/start_profile` and `/stop_profile`; without profiler config the router is not
attached (`vllm/entrypoints/serve/profile/api_router.py:21-45`). The endpoints
are therefore not an unauthenticated always-on feature independent of server
configuration; they exist within the configured serving application and should
be protected like the rest of the control plane.

**Captured activity.** Each GPU worker creates a torch profiler with both CPU
and CUDA activities, using the configured shapes/memory/stack/FLOPs switches
(`vllm/v1/worker/gpu_worker.py:929-980` and
`vllm/profiler/wrapper.py:159-226`). TensorBoard-compatible traces are written
with an optional gzip handler. The wrapper also writes CPU/CUDA aggregate
tables; only rank 0 prints the table to stdout
(`vllm/profiler/wrapper.py:251-287`). The source documentation calls the
profiler medium-overhead and warns that trace size and final flushing can be
large (`docs/contributing/profiling.md:1-38`).

The V1 worker calls `profiler.step()` once per execution and wraps model
execution in a record-function name containing context/generation request and
token counts (`vllm/v1/worker/gpu_worker.py:803-827,895-898`). If frontend
profiling is not ignored, `AsyncLLM` also creates a separate CPU-only trace
(`vllm/v1/engine/async_llm.py:178-200`).

**TP behavior.** `EngineCore.profile()` delegates to the executor
(`vllm/v1/engine/core.py:662-663`), whose profile RPC is a collective worker
call (`vllm/v1/executor/abstract.py:256-257`). Multiprocessing broadcasts calls
that do not declare a unique output rank, so the profile call reaches every TP
worker (`vllm/v1/executor/multiproc_executor.py:340-402`). Each worker emits its
own rank-qualified trace; the suffix includes DP/PP/TP/DCP/EP/global rank
components (`vllm/distributed/utils.py:664-695`). For TP, kernel analysis must
therefore preserve per-rank files, use the slowest rank for critical-path time,
and optionally sum ranks to quantify total GPU work.

### 2.2 Iteration-level scheduler data and exported metrics

The V1 scheduler interface states that each `schedule()` output corresponds to
one model forward and maps request IDs to scheduled tokens
(`vllm/v1/core/sched/interface.py:51-80`). `SchedulerOutput` already carries
new/cached request data, per-request and total scheduled tokens, common-prefix
blocks, and the request IDs preempted in that step
(`vllm/v1/core/sched/output.py:180-219`). The base scheduler constructs this
object at `vllm/v1/core/sched/scheduler.py:1012-1076`, then advances request
computed-token and chunk state at `vllm/v1/core/sched/scheduler.py:1130-1155`.

What is available today:

| Requested datum | Internal per-step source | Externally exposed today |
|---|---|---|
| Batch size | `len(SchedulerOutput.num_scheduled_tokens)`; the map is exact | No raw per-step batch-size Prometheus series. Optional iteration log reports context and generation request counts. |
| Scheduled prefill/decode tokens | `compute_iteration_details()` classifies new/cached context requests and sums scheduled tokens (`vllm/v1/utils.py:766-813`) | Optional log reports exact scheduled context/generation counts and elapsed time around the execution wait (`vllm/v1/engine/core.py:433-472`). Prometheus prompt/generation counters are cumulative output-side stats, not a raw scheduled-step stream. |
| Chunked-prefill splits | `Request.is_prefill_chunk`, `num_computed_tokens`, and prompt/output state exist (`vllm/v1/request.py:130-175`); extended chunks are classified as context | No chunk ordinal, first/middle/final split, or chunk-size series. These must be derived before `_update_after_schedule()` mutates state. |
| Preemptions | Exact step set in `SchedulerOutput.preempted_req_ids` | Stdout aggregates over its logging interval; `vllm:num_preemptions` is cumulative (`vllm/v1/metrics/loggers.py:219-281,624-631`). |
| Running/waiting/deferred queues | `SchedulerStats.num_running_reqs`, `num_waiting_reqs`, and `num_skipped_waiting_reqs` (`vllm/v1/metrics/stats.py:170-183`) | Latest-value gauges `vllm:num_requests_running`, `vllm:num_requests_waiting`, and reason-labeled capacity/deferred gauges (`vllm/v1/metrics/loggers.py:453-496`). Running queue size is not the same as scheduled batch size. |
| KV-cache usage | Scheduler reads `kv_cache_manager.usage`; the block pool computes a null-block-adjusted ratio in `[0,1]` (`vllm/v1/core/kv_cache_manager.py:181-188`; `vllm/v1/core/block_pool.py:692-711`) | Latest-value `vllm:kv_cache_usage_perc`; stdout prints the latest percent (`vllm/v1/metrics/loggers.py:250-260,524-532`). Raw total/free blocks are not exported per step. |
| Prefix-cache counters | `PrefixCacheStats` contains request/query/hit and separately preempted request/query/hit counters (`vllm/v1/metrics/stats.py:114-143`) for local and optional connector caches | Prometheus exposes cumulative token queries/hits for local and external caches (`vllm/v1/metrics/loggers.py:547-565,1063-1101`); stdout reports interval-derived hit rates. It does not expose the preempted sub-counters separately. |
| Total step tokens | Exact scheduled total is in `SchedulerOutput` | `vllm:iteration_tokens_total` is a histogram observed from output-side computed prompt plus generation tokens (`vllm/v1/metrics/loggers.py:693-724,1148-1171`), not a joinable raw step record. |

`IterationStats` also has a wall timestamp, output-side prompt/generation data,
preemptions, and finished-request latency samples
(`vllm/v1/metrics/stats.py:325-347`). `Scheduler.make_stats()` produces latest
queue/KV state, drains prefix stats, and passes through the step's CUDA-graph
stat when an output is processed
(`vllm/v1/core/sched/scheduler.py:2228-2264`). These are valuable existing
plumbing, but async scheduling can schedule newer batches before an older
output is processed. Consequently, those latest/drained values are not a safe
per-step composition join without a schedule-time snapshot.

Bottom line: no new scheduler instrumentation is needed to discover batch and
token composition, preemptions, queues, KV use, or prefix events. A patch is
needed only to serialize their **same-step association** and the missing
histograms/chunk split.

### 2.3 CUDA-graph observability

Yes, v0.24.0 can determine the runtime graph mode and capture bucket for each
model step internally:

- `CUDAGraphStat` records unpadded tokens, padded tokens, padding, and runtime
  mode (`vllm/compilation/cuda_graph.py:32-37`).
- The GPU model runner asks the dispatcher for a concrete runtime mode and
  `BatchDescriptor`, then conditionally attaches the stat to its output
  (`vllm/v1/worker/gpu_model_runner.py:3822-3934`). It reaches the scheduler in
  `ModelRunnerOutput` and `update_from_output()`
  (`vllm/v1/outputs.py:231-281`;
  `vllm/v1/core/sched/scheduler.py:1464-1477`).
- The dispatcher is the source of truth: it pads to a captured descriptor,
  tries `FULL`, then `PIECEWISE`, and returns `NONE` when no graph key matches
  (`vllm/v1/cudagraph_dispatcher.py:235-324`; the design explanation is at
  `docs/design/cuda_graphs.md:81-120`). At serving time, `FULL` and `PIECEWISE`
  dispatch to a captured wrapper; `NONE` calls the runnable directly
  (`vllm/compilation/cuda_graph.py:233-360`).

Thus `runtime_mode != NONE` is a graph-path dispatch, and `num_padded_tokens` is
the selected token bucket. `PIECEWISE` means graph-wrapped compiled pieces, not
a full-step graph hit. Startup normally captures all dispatcher descriptors
(`vllm/v1/worker/gpu_model_runner.py:6595-6643`), but `CUDAGraphStat` has no
capture-versus-replay bit; a lazy first capture and a replay have the same stat.
With built-in `cudagraph_metrics`, the stat is aggregated
into a frequency table and printed at the logging interval
(`vllm/config/observability.py:56-58`;
`vllm/compilation/cuda_graph.py:40-124`); it is not exposed as a raw per-step
Prometheus event. OpProf only needs to preserve the already-computed object on
every enabled step.

Capture modes and sizes live in `CompilationConfig`:
`cudagraph_mode`, `cudagraph_capture_sizes`, and
`max_cudagraph_capture_size` are defined at
`vllm/config/compilation.py:587-688`. The V1 default is
`FULL_AND_PIECEWISE`. Final sizes are generated or validated in
`VllmConfig._set_cudagraph_sizes()`; the default is 1/2/4, then steps of 8 below
256 and 16 above it, bounded by token/batch configuration, with user overrides
written back to the final list (`vllm/config/vllm.py:1635-1800`).

### 2.4 MoE routing and Qwen3-30B-A3B backend

Qwen3 MoE creates a replicated gate and `FusedMoE` with the model's number of
experts and top-k, and normally runs its router internally
(`vllm/model_executor/models/qwen3_moe.py:137-249`). The causal-LM wrapper
retains the list of MoE layers and their expert topology
(`vllm/model_executor/models/qwen3_moe.py:662-729`). Router top-k IDs are
available immediately after routing and before EPLB remapping
(`vllm/model_executor/layers/fused_moe/router/base_router.py:233-278`).

There is an exact, built-in access path: `--enable-return-routed-experts`
(`vllm/config/model.py:209-215`; `vllm/engine/arg_utils.py:826-830`). It installs
per-layer callbacks, writes top-k IDs to preallocated GPU buffers, copies the
step to pinned CPU memory, and returns it through `ModelRunnerOutput`
(`vllm/model_executor/layers/fused_moe/routed_experts_capturer.py:58-84`;
`vllm/v1/worker/gpu_model_runner.py:3652-3666,4637-4683,7382-7437`). This is
reachable without modifying the fused kernels or saving router logits.
Raw router-logit tensors are not plumbed to engine outputs; exposing those
would be deeper and much higher-volume surgery than using the existing top-k
ID callback.

It is not cheap enough for Layer 1. The per-worker transit buffer is a few MB;
the scheduler's whole-slot buffer can reach multiple GB and performs a CPU
fancy-index write each step
(`vllm/model_executor/layers/fused_moe/routed_experts_capturer.py:223-305`;
the scheduler store is at `vllm/v1/core/sched/scheduler.py:1501-1520`). It is
also incompatible with pipeline parallelism greater than one and with KV
connectors (`vllm/config/vllm.py:871-893`). Exact per-layer expert load should
therefore be collected in a separate sampled Layer-2 run and reduced offline.
EPLB has load state, but enabling it adds communication and its logs are
aggregate balancing summaries; it is not a free observability tap
(`vllm/distributed/eplb/eplb_state.py:62-171,478-575`).

For the normal unquantized BF16/FP16 Qwen3-30B-A3B configuration on H20/SM90,
the automatic unquantized oracle explicitly moves both FlashInfer backends
behind Triton on Hopper because they are expected to be slower
(`vllm/model_executor/layers/fused_moe/oracle/unquantized.py:43-86`). It selects
the first supported backend and documents possible shape-specific fallbacks
(`vllm/model_executor/layers/fused_moe/oracle/unquantized.py:152-250`). The
primary backend is therefore **Triton** under those assumptions. A quantized
checkpoint, LoRA, expert activation format, or explicit `--moe-backend` can
change that conclusion and must be recorded with the campaign run.

### 2.5 V1 execution loop and natural hooks

The control path is:

```text
EngineCore busy loop
  -> Scheduler.schedule()
  -> executor.execute_model(SchedulerOutput)
  -> GPUWorker -> GPUModelRunner.execute_model()
  -> Scheduler.update_from_output(SchedulerOutput, ModelRunnerOutput)
```

Evidence and hook implications:

- `EngineCore.run_busy_loop()` and `_process_engine_step()` drive steps at
  `vllm/v1/engine/core.py:1259-1317`.
- The normal step retains the exact scheduler output across the future and
  passes it back to `update_from_output()`
  (`vllm/v1/engine/core.py:479-508`). The batch-queue path does the same for
  multiple in-flight batches (`vllm/v1/engine/core.py:519-632`).
- The scheduler's stable interface is schedule/update at
  `vllm/v1/core/sched/interface.py:51-107`. The base implementation builds the
  output at `vllm/v1/core/sched/scheduler.py:1012-1100`, mutates request state
  immediately afterward at lines 1130-1155, and consumes model results at
  lines 1464-1803.
- Worker execution wraps `gpu_model_runner.execute_model()` at
  `vllm/v1/worker/gpu_worker.py:895-898`; the model runner's entry is
  `vllm/v1/worker/gpu_model_runner.py:4055-4069`.

The natural Layer-1 hooks are therefore in the base scheduler: snapshot after
the `SchedulerOutput` is complete but before request state advances, then
finalize with the matching model output. This covers the async subclass and
avoids frontend/output aggregation. The only worker change is broadening the
existing CUDA-graph-stat condition. Layer 2 uses the existing profile control
path unchanged.

### 2.6 Relevant changes from 0.20.0

For comparison only, tag `v0.20.0` was fetched into the allowed clone without
changing the pinned working tree. It resolves to commit
`88d34c6409e9fb3c7b8ca0c04756f061d2099eb1`.

- Async scheduling and the batch-queue architecture already existed in 0.20.0;
  `SchedulerConfig` selected `AsyncScheduler` at
  `v0.20.0:vllm/config/scheduler.py:146-176`, and the core schedule/future/update
  paths were at `v0.20.0:vllm/v1/engine/core.py:402-431,443-533`. It is
  incorrect to treat async scheduling as new in 0.24.0.
- 0.24.0 adds DP prefill cadence/throttling and passes a prefill-throttle flag
  into `schedule()` (`vllm/config/scheduler.py:140-161` and
  `vllm/v1/engine/core.py:474-490`). It also hardens zero-token/dummy-iteration
  handling (`vllm/v1/engine/core.py:433-472`) and changes when the batch queue
  fills.
- The 0.24 scheduler contains explicit scheduled/processed sequence fences and
  snapshots state that must survive later async schedules, including routed
  experts (`vllm/v1/core/sched/scheduler.py:293-322,1093-1165`). That is direct
  evidence that an OpProf hook must not read mutable request/queue state only
  when an older output returns.
- `vllm/config/profiler.py`, `vllm/profiler/wrapper.py`, and
  `vllm/entrypoints/serve/profile/api_router.py` are byte-identical between the
  two tags (`git diff --quiet v0.20.0..v0.24.0` returned success). Profiler
  endpoint semantics did not move; the scheduler/core line numbers and safe
  composition hook did.

Practical conclusion: do not port a 0.20 line-number patch into EngineCore.
Hook the 0.24 base scheduler's schedule/update pair so sync, async, and batch
queue paths share one implementation.

## 3. Proposed dual-layer design

Layer 1 emits one schema-versioned JSONL record per scheduler step from the
EngineCore/scheduler process. It contains step/timestamps; scheduled and decode
batch counts; scheduled prefill/decode tokens; aggregate first/middle/final
chunk information; fixed-bucket context and chunk-size histograms; exact step
preemptions; schedule-time running/waiting/deferred queues; total/free/used KV
blocks and ratio; local/external prefix deltas; and CUDA-graph mode/bucket. No
request IDs or raw context-length lists are written. MoE expert load is null and
explicitly marked Layer-2-only.

Records are encoded with the existing `msgspec` dependency
(`requirements/common.txt:35`), placed into a
bounded non-blocking queue, and drained by one background JSONL writer. It
flushes every 1 MiB or one second and at clean shutdown, with explicit drop/gap
counters and no per-step `fsync`. `VLLM_OPPROF_DIR` is the startup-only switch;
unset is a true no-op. The unmeasured foreground estimate is 25-100
microseconds per step, so Phase 2 must enforce the less-than-3% overhead gate
with paired off/on serving runs.

Layer 2 configures the already-built torch profiler and samples short windows,
initially two warm-up plus eight active worker iterations. Set
`max_iterations=8`; the wrapper stops the underlying profiler on the subsequent
step after the counter exceeds the limit, as its existing test documents
(`vllm/profiler/wrapper.py:83-114`;
`tests/v1/worker/test_gpu_profiler.py:77-98`). The controller then calls
`/stop_profile` to clear control state. Step-count/on-demand endpoint control is
preferred to wall time. Layer 2 writes one trace per TP worker and joins to
Layer 1 through the existing iteration annotation, order, token counts, and
timestamps.

Under CUDA-graph replay, the profiler is configured for CUDA activity, but the
Python callable is not re-executed: vLLM calls `CUDAGraph.replay()`
(`vllm/compilation/cuda_graph.py:233-360`). CUDA activity may therefore be
visible through Kineto/CUPTI, while Python/PyTorch scopes cannot be assumed to
provide the original per-layer attribution. Layerwise NVTX is explicitly
unsupported with graphs (`vllm/config/observability.py:60-63`). Phase 2 must
check whether the pinned torch/CUPTI combination exposes individual replayed
kernels or a coarser graph launch. A matched eager window can identify kernel
families but must not replace graph-enabled production timing.

Layer-2 kernel time is grouped into attention, router/top-k, expert/dense GEMMs,
normalization/activation, collectives, and sampling. Per-rank critical-path
times calibrate a composition/graph-bucket-conditioned Layer-1 attribution;
held-out profiler windows validate that mapping. Exact routed experts are
enabled only in a separate sampled server run.

### Patch surface

| File | Approximate lines |
|---|---:|
| `vllm/envs.py` | 4 |
| `vllm/v1/opprof.py` (new) | 220 |
| `vllm/v1/core/sched/scheduler.py` | 32 |
| `vllm/v1/worker/gpu_model_runner.py` | 4 |
| `tests/v1/core/test_opprof.py` (new) | 190 |
| **Total** | **5 files / about 450 lines** |

Zero-patch wins: profiler config/endpoints/wrapper, `SchedulerOutput`, existing
metrics plumbing, CUDA kernels, fused MoE/router code, Qwen3 model code, and
frontend APIs. Supporting OpProf together with `--disable-log-stats` would add
about 12 lines in `vllm/v1/core/kv_cache_manager.py`; the design recommends
failing fast instead.

### Main risks

- Histogram construction and JSON encoding are the likely foreground overhead;
  the 3% budget must be measured, not inferred.
- Async scheduling can misassociate latest queue/cache state with an older
  output; snapshot inside `schedule()` and pair by the exact output object.
- Layer 1 must be scheduler-owned to avoid TP duplicates; Layer 2 remains
  per-rank and needs offline aggregation.
- At 500 steps/s and about 1 KiB/record, an uncompressed stream is roughly
  44 GB/day per EngineCore. Runs must be duration-bounded, with queue/drop
  counters and post-run compression.
- Kineto perturbs execution, and graph replay loses Python-scope attribution;
  it is a sampled calibration instrument, not an always-on latency source.

## 4. Open design decisions

1. Approve JSONL/`msgspec`, an 8192-record queue, context edges
   `(128, 256, 512, 1K, 2K, 4K, 8K, 16K, 32K, 64K, 128K, +inf)`, and chunk-size
   edges `(16, 32, 64, 128, 256, 512, 1K, 2K, +inf)`.
2. Approve exact MoE expert load as Layer-2-only. The alternative is a new GPU
   histogram path, with a larger patch and an overhead/graph-capture risk.
3. Confirm checkpoint precision/quantization and intended TP/EP/DP topology.
   The H20 Triton conclusion assumes unquantized BF16/FP16, auto backend, and no
   LoRA.
4. Choose the initial profiler window: recommended two warm-up plus eight
   active iterations, or a longer active window.
5. Decide whether the less-than-3% gate applies to point estimates or the upper
   95% confidence bound for every primary metric.
6. Confirm that OpProf campaign runs may reject `--disable-log-stats`; support
   for it increases the patch to six files and about 462 lines.

## Data sanity block

- **Pinned root listing:** n=45 names; lexicographic min=`.buildkite`,
  max=`vllm`; distinct=45; SHA-256=`75222e5d3bacdd043e421c417496aaf0fa5a9429b7244698b6d3ca2218b85b58`.
- **Source versions compared:** n=2 tags; min=`v0.20.0`, max=`v0.24.0`;
  distinct=2; pinned working-tree version remains exactly `v0.24.0`.
- **Torch pin observations:** n=4 declarations
  (`pyproject`, build requirements, CUDA runtime requirements, CMake);
  min=max=`2.11.0`; distinct=1.
- **Evidence-bearing source files inspected and cited:** n=44
  distinct v0.24.0 paths; min/max not applicable to categorical paths;
  v0.20.0 comparison touched n=5 distinct paths; min/max not applicable.
- **Invariants checked:** exact tag equals `v0.24.0`; HEAD equals the recorded
  commit; clone is clean; top-level names are unique; requested path was used;
  torch pins agree; CUDA-graph modes are in `{NONE, PIECEWISE, FULL}`; KV usage
  definition is bounded in `[0,1]`; scheduled-token total is defined as the sum
  of per-request scheduled tokens; graph bucket is not smaller than unpadded
  tokens; no remote/GPU/install/model-download action occurred.
- **Benchmark/statistical conclusion check:** no performance benchmark was run,
  so there are no per-configuration curves or measured overhead values on which
  to perform identical-value, monotonicity, or continuity checks. All overhead
  numbers in the design are labeled estimates.
