Workload-conditioned operator profiling on patched vLLM 0.24.0 + Qwen3-30B-A3B/H20. H1b PASS (irregular patterns carry +23-45pp R64 raggedness, 8-45% token-efficiency loss vs rectangular controls); mechanism decomposition kills the padding narrative and finds the arrival-uniformization artifact (-12.9%); cross-version churn surface shows TP2/MNS64 -29.4% across vLLM 0.20->0.24 while the argmax held. Raw Layer-1 JSONL streams (507 MB) stay on disk, git-ignored; footer sidecars and metrics are tracked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
435 lines
25 KiB
Markdown
435 lines
25 KiB
Markdown
# OpProf dual-layer instrumentation patch design
|
|
|
|
Status: **design for review; no vLLM patch has been implemented**.
|
|
|
|
Target source: vLLM `v0.24.0`, commit `ee0da84ab9e04ac7610e28580af62c365e898389`.
|
|
Target campaign: Qwen3-30B-A3B serving on NVIDIA H20 (SM90), using the V1 engine.
|
|
All source paths and line numbers below are relative to the pinned clone.
|
|
|
|
## Approved dispositions (2026-07-11)
|
|
|
|
- Use JSONL with `msgspec`, the proposed context/chunk histogram edges, and an
|
|
8192-record bounded queue.
|
|
- Keep exact expert loads Layer-2-only.
|
|
- Use the community BF16 Qwen3-30B-A3B checkpoint. TP1 is primary, with TP2 and
|
|
TP4 counterpoints; record the selected MoE backend logs in every run.
|
|
- Use two profiler warm-up iterations followed by eight active iterations.
|
|
- Apply the 3% Layer-1 overhead gate to the **upper bound of the 95% confidence
|
|
interval** for every primary serving metric.
|
|
- Reject `--disable-log-stats` when OpProf is enabled; retain the five-file
|
|
fail-fast design.
|
|
|
|
## Goal and success criteria
|
|
|
|
The patch should make every serving iteration conditionable on its request and
|
|
execution pattern while keeping the always-on path below a 3% serving overhead
|
|
budget. Heavy kernel tracing and exact MoE routes are sampled separately.
|
|
|
|
The proposed split is:
|
|
|
|
1. **Layer 1:** one compact composition record per scheduler step, emitted by
|
|
the scheduler process rather than every tensor-parallel worker.
|
|
2. **Layer 2:** short, sampled windows using vLLM's existing torch-profiler
|
|
configuration and `/start_profile` and `/stop_profile` endpoints.
|
|
|
|
The design deliberately does not add hooks to GPU kernels, attention layers,
|
|
the Qwen model, or the OpenAI serving API. Those surfaces are not needed to
|
|
answer the Phase 0 profiling questions.
|
|
|
|
## Assumptions and tradeoffs
|
|
|
|
- The campaign uses the V1 engine and the default vLLM scheduler. Both the
|
|
synchronous and `AsyncScheduler` paths inherit the base scheduler hooks.
|
|
- The H20 backend statement assumes the usual unquantized BF16/FP16
|
|
Qwen3-30B-A3B checkpoint, `moe_backend=auto`, and no LoRA. Quantization,
|
|
batched expert format, or an explicit backend can change kernel selection.
|
|
- Layer 1 requires normal stats collection, which is enabled by default. If
|
|
OpProf is enabled together with `--disable-log-stats`, initialization should
|
|
fail with an actionable error. Supporting that unusual combination would
|
|
require an extra stats path and a larger patch.
|
|
- The context-length histogram records the sequence length at the end of the
|
|
scheduled model input (`num_computed_tokens` before scheduling plus tokens
|
|
scheduled in this step). This matches the worker-side sequence-length
|
|
construction at `vllm/v1/worker/gpu_model_runner.py:2010-2019` and avoids
|
|
retaining raw request lengths.
|
|
- “Decode tokens” means tokens scheduled for requests classified by vLLM as
|
|
generation-phase requests, including scheduled speculative tokens. It is not
|
|
the number of subsequently accepted output tokens. The existing classifier
|
|
and its chunked-prefill semantics are at `vllm/v1/utils.py:780-813`.
|
|
|
|
## Existing facilities to reuse
|
|
|
|
These are zero-patch wins:
|
|
|
|
| Need | Existing v0.24.0 facility | Design consequence |
|
|
|---|---|---|
|
|
| Scheduled request and token map | `SchedulerOutput.num_scheduled_tokens`, total tokens, preempted IDs, and new/cached request data at `vllm/v1/core/sched/output.py:180-219` | Derive composition in the scheduler; do not add fields to `SchedulerOutput`. |
|
|
| Prefill/decode classification | `compute_iteration_details()` at `vllm/v1/utils.py:780-813` | Reuse the exact definition already used by the iteration log. |
|
|
| Queue/KV/prefix stats | `SchedulerStats` at `vllm/v1/metrics/stats.py:170-198` and construction at `vllm/v1/core/sched/scheduler.py:2228-2264` | Reuse field semantics, but snapshot at schedule time to avoid async misattribution. |
|
|
| Per-step CUDA-graph descriptor | `CUDAGraphStat` at `vllm/compilation/cuda_graph.py:32-37`, populated at `vllm/v1/worker/gpu_model_runner.py:3919-3934` | Reuse the object; only broaden the condition under which it is returned. |
|
|
| Sampled CPU/CUDA tracing | profiler config and schedule at `vllm/config/profiler.py:33-105`, endpoints at `vllm/entrypoints/serve/profile/api_router.py:21-45` | Layer 2 needs configuration and orchestration, not a new profiler implementation or endpoint. |
|
|
| Exact routed expert IDs | routed-expert capture callback and buffers at `vllm/model_executor/layers/fused_moe/routed_experts_capturer.py:58-84` and `vllm/v1/worker/gpu_model_runner.py:7382-7437` | Use only in a separate sampled Layer-2 run; it is too expensive for Layer 1. |
|
|
|
|
## Layer 1: always-on composition records
|
|
|
|
### Data flow and hook placement
|
|
|
|
```text
|
|
Scheduler.schedule()
|
|
-> snapshot request composition, queues, KV and prefix deltas
|
|
-> SchedulerOutput -> TP workers -> ModelRunnerOutput.cudagraph_stats
|
|
-> Scheduler.update_from_output()
|
|
-> finalize the same step record -> bounded queue -> background JSONL writer
|
|
```
|
|
|
|
The scheduler is the ownership boundary for Layer 1. It sees the full logical
|
|
batch once, so recording in each TP worker would duplicate data and require an
|
|
aggregation protocol.
|
|
|
|
Exact proposed hooks against the pinned clone:
|
|
|
|
1. **Initialize one recorder in the base scheduler constructor.** The scheduler
|
|
already owns stats state and passes the normal `log_stats` setting into the
|
|
KV-cache manager (`vllm/v1/core/sched/scheduler.py:78-87` and
|
|
`vllm/v1/core/sched/scheduler.py:250-260`). If `VLLM_OPPROF_DIR` is set,
|
|
construct the recorder and reject `log_stats=False`.
|
|
2. **Begin a record inside `Scheduler.schedule()`.** Read prefix counters at
|
|
method entry, then finish the snapshot immediately after `SchedulerOutput`
|
|
and connector metadata are constructed and before
|
|
`_update_after_schedule()` mutates request state
|
|
(`vllm/v1/core/sched/scheduler.py:1012-1100`). Prefix lookup counters are
|
|
updated synchronously during this schedule call
|
|
(`vllm/v1/core/kv_cache_manager.py:202-242` and
|
|
`vllm/v1/core/sched/scheduler.py:675-712,897-915`), so before/after deltas
|
|
remain attributable even when several batches are in flight.
|
|
3. **Finalize in `Scheduler.update_from_output()`.** Pair the returned
|
|
`ModelRunnerOutput` with the exact `SchedulerOutput` at
|
|
`vllm/v1/core/sched/scheduler.py:1464-1477`, and enqueue after normal output
|
|
processing near `vllm/v1/core/sched/scheduler.py:1791-1803`. Store pending
|
|
drafts by `id(scheduler_output)` and remove them on finalize; the identifier
|
|
never leaves the process. This is robust to the batch queue, which retains
|
|
and later returns the matching output object
|
|
(`vllm/v1/engine/core.py:519-632`).
|
|
4. **Return the existing CUDA-graph stat when OpProf is enabled.** Change the
|
|
condition at `vllm/v1/worker/gpu_model_runner.py:3919-3926` from only
|
|
`observability_config.cudagraph_metrics` to that flag **or**
|
|
`VLLM_OPPROF_DIR`. The file already imports `vllm.envs` at line 23. No CUDA
|
|
synchronization or tensor transfer is introduced.
|
|
5. **Close through the existing scheduler shutdown.** Drain and join the writer
|
|
before the scheduler reports shutdown complete at
|
|
`vllm/v1/core/sched/scheduler.py:2285-2295`; retain an `atexit` fallback for
|
|
abnormal embedding/test lifecycles.
|
|
|
|
The scheduler assigns a monotonically increasing local `step_index` at begin.
|
|
Every real scheduler call is represented, including zero-token steps. A DP-only
|
|
dummy execution has no `SchedulerOutput` and is outside this composition
|
|
stream; if DP is later in campaign scope, add a distinct `dummy_step` marker
|
|
rather than pretending it has a request composition.
|
|
|
|
### Record schema
|
|
|
|
Use schema-versioned JSON Lines. An illustrative record is:
|
|
|
|
```json
|
|
{"schema":1,"engine_id":"dp0-pid1234","step_index":42,"submit_wall_ns":1783761000000000000,"submit_mono_ns":8920000000,"complete_mono_ns":8922371000,"model_executed":true,"scheduled_requests":37,"decode_batch_size":32,"prefill_requests":5,"prefill_tokens":896,"decode_tokens":32,"chunked_prefill":{"first":2,"middle":1,"final":1,"unsplit":1,"tokens":896,"chunk_size_hist":[0,0,1,1,1,1,1,0,0]},"context_length_hist":[0,0,3,6,12,10,5,1,0,0,0,0],"preemptions":0,"queues":{"running":37,"waiting":9,"deferred":0},"kv":{"total_blocks":120000,"free_blocks":42000,"used_blocks":78000,"usage":0.65},"prefix":{"local":{"requests":2,"queries":4096,"hits":3072,"preempted_requests":0,"preempted_queries":0,"preempted_hits":0},"external":null},"cudagraph":{"hit":true,"runtime_mode":"PIECEWISE","unpadded_tokens":928,"bucket_tokens":1024,"padding_tokens":96},"moe_expert_load":null,"dropped_records_before":0}
|
|
```
|
|
|
|
Required field semantics:
|
|
|
|
- `submit_wall_ns` permits joining to external workload logs;
|
|
`submit_mono_ns` and `complete_mono_ns` provide stable within-process
|
|
ordering and elapsed time without wall-clock jumps.
|
|
- `scheduled_requests` is the number of entries in
|
|
`num_scheduled_tokens`. `decode_batch_size`, `prefill_requests`,
|
|
`prefill_tokens`, and `decode_tokens` reuse vLLM's classifier.
|
|
- `chunked_prefill` contains aggregate split information, never request IDs.
|
|
A context request is `first` when the scheduled range ends before its prompt
|
|
ends, `middle` when it was already a prefill chunk and remains incomplete,
|
|
`final` when a prior chunk completes, and `unsplit` when its prefill completes
|
|
in one step. `chunk_size_hist` uses token-count buckets
|
|
`(16, 32, 64, 128, 256, 512, 1024, 2048, +inf)`.
|
|
- `context_length_hist` includes every scheduled request, with fixed upper
|
|
edges `(128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536,
|
|
131072, +inf)`. The final array therefore has 12 bins. The example above is
|
|
illustrative and will be checked against the final chosen edges in tests.
|
|
- `preemptions` is the size of the step's `preempted_req_ids`, created at
|
|
`vllm/v1/core/sched/scheduler.py:1059-1076`. It is not an interval-derived
|
|
Prometheus value.
|
|
- Queue lengths and KV blocks are captured immediately after scheduling. KV
|
|
usage follows the existing null-block-adjusted calculation at
|
|
`vllm/v1/core/block_pool.py:692-711`. `deferred` means skipped/deferred
|
|
waiting requests, matching `num_skipped_waiting_reqs` in
|
|
`vllm/v1/metrics/stats.py:174-183`.
|
|
- Local and external prefix fields are per-schedule deltas copied from the
|
|
existing mutable counters. The existing stats drain resets those objects at
|
|
`vllm/v1/core/kv_cache_manager.py:190-200` and
|
|
`vllm/v1/core/sched/scheduler.py:2235-2242`; OpProf must read, not drain or
|
|
replace, them. These retain vLLM's lookup semantics: a local lookup can be
|
|
counted before allocation later rejects that waiting request, so prefix
|
|
`requests` is not required to be less than or equal to scheduled prefill
|
|
requests.
|
|
- `cudagraph.hit` is the operational serving definition `runtime_mode != NONE`.
|
|
`FULL` selects the full graph path and `PIECEWISE` selects graph-wrapped
|
|
compiled regions; the latter must not be interpreted as full-step graph
|
|
coverage. Startup normally captures all dispatcher descriptors at
|
|
`vllm/v1/worker/gpu_model_runner.py:6595-6643`, but the stat itself has no
|
|
capture-versus-replay bit, so a rare lazy first capture cannot be
|
|
distinguished from replay. `bucket_tokens` is `num_padded_tokens` in the
|
|
existing stat.
|
|
- `moe_expert_load` is explicitly null in Layer 1. This avoids accidentally
|
|
presenting unavailable data as zero.
|
|
|
|
### Encoding, buffering, and flush
|
|
|
|
JSONL is recommended over a compact binary ring buffer for Phase 1 because the
|
|
schema will evolve during campaign bring-up, records need to be directly
|
|
inspectable beside Kineto traces, and `msgspec` is already a vLLM dependency
|
|
(`requirements/common.txt:35`).
|
|
Encoding one small dictionary with a reused `msgspec.json.Encoder` avoids the
|
|
standard `json` module's larger CPU cost. Once the schema stabilizes, the same
|
|
record can be moved to a binary format only if Phase 2 measurements show JSONL
|
|
is the bottleneck.
|
|
|
|
The foreground path encodes once and performs non-blocking `put_nowait()` into
|
|
a bounded queue of 8192 records. A single daemon writer thread drains into one
|
|
file per EngineCore/DP rank and process. It flushes userspace buffers every
|
|
1 MiB or one second, whichever comes first, and on clean process exit; it never
|
|
calls `fsync()` per record. When the queue is full, the serving thread drops the
|
|
new record, increments a counter, and the next successful record reports the
|
|
gap in `dropped_records_before`. Shutdown emits a footer with encoded, written,
|
|
and dropped counts. The file name includes schema, DP rank, PID, and start time,
|
|
but not TP rank because there is one scheduler record stream.
|
|
|
|
The sole on/off switch is:
|
|
|
|
```text
|
|
VLLM_OPPROF_DIR=/absolute/output/directory
|
|
```
|
|
|
|
Unset or empty means a true no-op: no recorder, queue, thread, record
|
|
construction, or graph-stat broadening. Directory validation and a clear
|
|
startup log happen before serving. Runtime toggling is intentionally omitted;
|
|
it adds synchronization and ambiguous partial files without helping the
|
|
campaign.
|
|
|
|
### Expected cost and Phase 2 overhead gate
|
|
|
|
These are estimates, not measurements:
|
|
|
|
- composition and two fixed histograms: about 20-80 microseconds per step,
|
|
linear in scheduled requests;
|
|
- `msgspec` encoding plus a non-blocking queue insertion: about 5-20
|
|
microseconds per step;
|
|
- CUDA-graph stat construction: below 2 microseconds and no GPU sync;
|
|
- disk I/O: off the serving critical path, subject to bounded-queue drops.
|
|
|
|
The expected foreground total is roughly 25-100 microseconds per step. At a
|
|
2 ms decode step, the high end would exceed 3%, so the budget is a measurement
|
|
gate, not a claim.
|
|
|
|
Phase 2 should use the same Qwen3 checkpoint, request trace, random seed,
|
|
hardware, vLLM commit, TP/EP topology, CUDA-graph configuration, and cache
|
|
state for off/on comparisons. Alternate off/on ordering, warm up before each
|
|
measurement, and run at least five paired repeats. Report throughput and
|
|
p50/p95 TTFT and TPOT, plus CPU utilization, log bytes per step, queue high
|
|
watermark, and dropped-record count. The acceptance rule is less than 3%
|
|
regression for every declared primary serving metric, with bootstrap confidence
|
|
intervals reported. Whether the point estimate or upper 95% bound is the hard
|
|
gate is an open decision.
|
|
|
|
Correctness checks for every run are: contiguous step indices except explicitly
|
|
reported drops, scheduled token sums matching the prefill/decode split,
|
|
histogram counts matching scheduled request/chunk counts, KV ratio in `[0,1]`,
|
|
non-negative counters, and identical generated outputs for the deterministic
|
|
test trace.
|
|
|
|
## Layer 2: sampled kernel windows
|
|
|
|
### Trigger and window
|
|
|
|
Use the existing profiler API with a run-specific output directory and
|
|
`ignore_frontend=true`. The worker start/stop RPC already fans out to all
|
|
workers (`vllm/v1/executor/abstract.py:256-257` and
|
|
`vllm/v1/executor/multiproc_executor.py:340-402`), and each GPU worker creates a
|
|
CPU+CUDA profiler with a rank-qualified trace name
|
|
(`vllm/v1/worker/gpu_worker.py:929-980`). No Layer-2 vLLM code patch is needed.
|
|
|
|
Recommended initial configuration:
|
|
|
|
```text
|
|
profiler=torch
|
|
torch_profiler_dir=<run>/kineto
|
|
ignore_frontend=true
|
|
delay_iterations=0
|
|
max_iterations=8
|
|
wait_iterations=0
|
|
warmup_iterations=2
|
|
active_iterations=8
|
|
torch_profiler_record_shapes=false
|
|
torch_profiler_with_memory=false
|
|
torch_profiler_with_stack=false
|
|
torch_profiler_with_flops=false
|
|
torch_profiler_use_gzip=true
|
|
torch_profiler_dump_cuda_time_total=true
|
|
```
|
|
|
|
The primary trigger should be step-count based: an external campaign controller
|
|
POSTs `/start_profile` immediately before a desired composition regime. The
|
|
built-in schedule records two warm-up plus eight active model iterations and
|
|
`max_iterations=8` then stops the underlying profiler on the following worker
|
|
step. The “exceeds max” control semantics are explicit at
|
|
`vllm/profiler/wrapper.py:83-114` and in
|
|
`tests/v1/worker/test_gpu_profiler.py:77-98`; the controller should still POST
|
|
`/stop_profile` afterward to clear the active control state.
|
|
Worker iteration boundaries already call `profiler.step()` and annotate context
|
|
and generation token counts at `vllm/v1/worker/gpu_worker.py:803-827`. On-demand
|
|
manual POST remains useful for debugging. A time-based trigger is a fallback
|
|
for live traffic but is less reproducible because it yields a variable number
|
|
of steps. Phase 1 should confirm that the trace contains exactly eight active
|
|
iteration annotations; profiler scheduling and trace callbacks live at
|
|
`vllm/profiler/wrapper.py:159-226,290-307`.
|
|
|
|
The profile directory should be unique per campaign server run. The HTTP start
|
|
endpoint does not accept a `profile_prefix`, and a worker that is restarted for
|
|
another window retains the trace name chosen on first initialization
|
|
(`vllm/v1/worker/gpu_worker.py:939-975`). Multiple windows in one server run
|
|
therefore share a directory and need a controller manifest containing start/
|
|
stop wall times and produced filenames. Use a new directory only when the
|
|
server is restarted; adding a new endpoint parameter is not justified for
|
|
Phase 1. Every TP worker emits its own trace, and the rank suffix contains DP,
|
|
PP, TP, DCP, EP, and global rank information
|
|
(`vllm/distributed/utils.py:664-695`). Keep these traces separate and aggregate
|
|
only offline.
|
|
|
|
### CUDA graphs and Kineto interpretation
|
|
|
|
Keep CUDA graphs enabled in the primary sampled windows so kernel time reflects
|
|
the serving configuration. The pinned code profiles CUDA activity
|
|
(`vllm/v1/worker/gpu_worker.py:953-960`) while a captured path invokes
|
|
`CUDAGraph.replay()` instead of rerunning the Python callable
|
|
(`vllm/compilation/cuda_graph.py:233-360`). Therefore Kineto/CUPTI can observe
|
|
CUDA activity launched during replay, but the Python/PyTorch operator scopes
|
|
that created the graph are not re-executed and cannot be assumed to retain
|
|
per-layer attribution. vLLM explicitly documents the related limitation that
|
|
layerwise NVTX tracing does not work with CUDA graphs
|
|
(`vllm/config/observability.py:60-63`).
|
|
|
|
The clone does not contain a stronger guarantee about whether a particular
|
|
PyTorch/CUPTI build will present every graph node as an individually named
|
|
kernel versus a coarser graph-launch view. Treat that as an empirical Phase 2
|
|
check. Run one matched eager (`cudagraph_mode=NONE`) taxonomy window to identify
|
|
kernel families, but do not use its timings as the production baseline.
|
|
|
|
### Calibration and MoE routing
|
|
|
|
Join each profiler iteration annotation to Layer-1 records by rank-independent
|
|
step order, the profiler window marker, timestamps, and the prefill/decode token
|
|
counts. Aggregate kernels into attention, router/top-k, expert GEMMs, dense
|
|
GEMMs, normalization/activation, collectives, and sampling. For TP, report both
|
|
sum-of-rank GPU work and the maximum per-rank critical-path time. Fit or tabulate
|
|
kernel-time attribution conditioned on composition, context histogram, graph
|
|
runtime mode, and capture bucket, then validate on held-out windows rather than
|
|
the same samples used for calibration.
|
|
|
|
For the Phase 2 “operator time approximately equals iteration time” gate, do
|
|
not naively add overlapping kernels. Compute the union of CUDA kernel intervals
|
|
per rank, use the maximum rank as the distributed GPU critical path, and retain
|
|
an explicit CPU/queue/unattributed residual against Layer 1's submit-to-complete
|
|
span. Operator-category interval unions plus the residual must reconstruct the
|
|
span; summed GPU work is reported separately and may legitimately exceed wall
|
|
time.
|
|
|
|
Exact per-layer expert loads are Layer-2-only. A separate server start with
|
|
`--enable-return-routed-experts` exposes per-token, per-layer top-k IDs through
|
|
the existing router callback (`vllm/model_executor/layers/fused_moe/router/base_router.py:233-278`).
|
|
Aggregate those arrays offline into per-layer expert counts, entropy, max/mean
|
|
load, coefficient of variation, and top-k imbalance. Do not enable it in the
|
|
normal Layer-1 or baseline Kineto run: its worker transit buffer costs a few MB,
|
|
and the scheduler-side slot buffer can reach multiple GB with a CPU fancy-index
|
|
copy per step (`vllm/model_executor/layers/fused_moe/routed_experts_capturer.py:223-305`).
|
|
|
|
## Patch surface estimate
|
|
|
|
Proposed implementation and tests:
|
|
|
|
| File | Approximate changed/new lines | Purpose |
|
|
|---|---:|---|
|
|
| `vllm/envs.py` | 4 | Declare and parse `VLLM_OPPROF_DIR`. |
|
|
| `vllm/v1/opprof.py` (new) | 220 | Schema, schedule snapshot/deltas, histograms, pending-step pairing, encoder, bounded writer, drop/footer handling. |
|
|
| `vllm/v1/core/sched/scheduler.py` | 32 | Initialize recorder; begin before state mutation; finalize with the matching model output. |
|
|
| `vllm/v1/worker/gpu_model_runner.py` | 4 | Return existing `CUDAGraphStat` when either built-in metrics or OpProf needs it. |
|
|
| `tests/v1/core/test_opprof.py` (new) | 190 | Schema/invariants, chunk classification, async pairing, disabled no-op, bounded-queue drops, shutdown flush. |
|
|
| **Total** | **5 files / about 450 lines** | About 260 production lines and 190 test lines. |
|
|
|
|
No patch is proposed for `ProfilerConfig`, profile endpoints, profiler wrapper,
|
|
`SchedulerOutput`, `SchedulerStats`, Prometheus/logging, CUDA kernels, fused MoE
|
|
kernels, the Qwen model, or frontend APIs. If support for
|
|
`--disable-log-stats` is required, add about 12 lines in
|
|
`vllm/v1/core/kv_cache_manager.py`, making the estimate six files and about 462
|
|
lines; the smaller fail-fast design is recommended for this campaign.
|
|
|
|
## Risks and mitigations
|
|
|
|
- **Foreground CPU overhead:** request scanning and JSON encoding are the likely
|
|
hot spots. Use fixed-size arrays, a reused encoder, no raw lists, no blocking
|
|
I/O, and enforce the Phase 2 gate.
|
|
- **Async-scheduling races:** queue/KV/prefix state observed during
|
|
`update_from_output()` may include later in-flight schedules. Snapshot it
|
|
inside the synchronous `schedule()` call; only the immutable returned
|
|
CUDA-graph stat is added at completion. Pair by the exact `SchedulerOutput`
|
|
object and assert one begin/one finalize.
|
|
- **TP and EP aggregation:** Layer 1 is scheduler-owned and emitted once. Layer
|
|
2 remains per rank. Interpret TP critical path as the slowest rank and, for an
|
|
expert-parallel routed-expert sample, aggregate expert ownership offline.
|
|
- **Log volume:** at 500 steps/s, a 1.0 KiB record is about 44 GB/day per
|
|
EngineCore. Rotate by campaign run, compress completed files, and use the
|
|
bounded queue/drop counters. Production-long captures need a later sampling
|
|
or rotation policy; Phase 1 files should be bounded by run duration.
|
|
- **Graph semantics:** `PIECEWISE` is partial graph coverage, not a binary full
|
|
hit. Preserve `runtime_mode`, unpadded tokens, bucket, and padding rather than
|
|
reducing the record to one boolean.
|
|
- **Profiler perturbation:** Kineto has medium-to-high overhead and trace flushes
|
|
can stall. Use short windows, unique directories, disabled stacks/shapes and
|
|
memory by default, and never use Layer-2 latency as an unprofiled performance
|
|
result.
|
|
- **Backend drift:** the unquantized SM90 oracle prioritizes Triton, but
|
|
shape-specific fallbacks remain possible. Record startup backend logs and the
|
|
full checkpoint/parallel/quantization configuration with every run.
|
|
|
|
## Open decisions for review
|
|
|
|
1. Approve JSONL with `msgspec`, the two proposed histogram edge sets, and a
|
|
bounded 8192-record writer queue.
|
|
2. Approve exact expert-load collection as Layer-2-only, rather than adding a
|
|
new always-on GPU histogram kernel.
|
|
3. Confirm the Qwen3 checkpoint precision/quantization and intended TP/EP/DP
|
|
topology; the Triton backend conclusion depends on these inputs.
|
|
4. Choose the first profiler window: recommended two warm-up plus eight active
|
|
iterations, or a longer active window.
|
|
5. Decide whether the 3% gate applies to metric point estimates or to the upper
|
|
bound of their 95% confidence intervals.
|
|
6. Confirm that campaign runs may reject `--disable-log-stats`; supporting it
|
|
adds one file and about 12 lines.
|
|
|
|
## Data sanity block
|
|
|
|
- **Patch estimate:** n=5 files; per-file line-estimate min=4, max=220,
|
|
distinct values=4; total about 450 lines, of which about 260 are production
|
|
and 190 are tests.
|
|
- **Histogram definitions:** n=2 arrays; bin-count min=9, max=12,
|
|
distinct=2. In the illustrative record, context-bin sum=37 equals scheduled
|
|
requests, chunk-bin sum=5 equals prefill requests, KV used=120000-42000, and
|
|
graph padding=1024-928.
|
|
- **Estimated foreground components:** n=3 timed components; min is below 2
|
|
microseconds for graph-stat construction, max is 80 microseconds for
|
|
composition; distinct ranges=3. Their conservative combined foreground
|
|
estimate is 25-100 microseconds per step.
|
|
- **Invariants checked:** counters and histogram bins are non-negative; KV
|
|
usage and prefix hit ratios are bounded in `[0,1]`; graph bucket is at least
|
|
unpadded tokens; `NONE` is the only graph miss mode; no raw request IDs or
|
|
context-length lists are serialized; one scheduler stream avoids TP
|
|
duplicates.
|
|
- **Measurement status:** n=0 benchmark runs; min/max and cross-configuration
|
|
distinctness are not applicable. The cost figures are estimates and cannot
|
|
support an overhead conclusion until Phase 2.
|