Repository navigation
[Core][Metrics] Leave unobserved diffusion timings out of per-request metrics - #9049
MinhaoLi0318 wants to merge 5 commits into
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
This PR appears to belong to: docs/design/module/observability.md, docs/design/module/diffusion/index.md, docs/design/module/diffusion/diffusion_runtime.md. Module owners: @david6666666 @Isotr0py @princepride Routing: @david6666666 via module of the changed files, module named in the PR description, CODEOWNERS; @Isotr0py via module of the changed files, module named in the PR description; @princepride via module of the changed files, module named in the PR description @MinhaoLi0318, please review your own changes and leave a short self-review comment describing what you checked. PRs without author self-review may not be assigned a reviewer. Please take a look when you have a chance. If you would like an automated review, mention @vllm-omni-review-bot in a comment. |
|
Self-review (author). What I checked:
One decision for review: keep or drop the 0 sample in AI assistance: drafted with Claude Code; I reviewed it before posting. |
hsliuustc0106
left a comment
There was a problem hiding this comment.
- Update this branch against current
mainand resolve the merge conflicts. GitHub now reportsmergeable=false/dirtyat headdddc2af817c9f22cb81d9e51d17f5faf649b916d, so the earlier locally clean merge is no longer sufficient. Push the resulting fix SHA and rerun the three focused metrics test files, reporting the command, environment and result. Acceptance: GitHub reports a clean merge at the updated head and the affected metrics regressions still pass.
This is a merge-readiness blocker, not a new runtime-defect finding.
… metrics DiffusionEngine.step_streaming wrote preprocess_time_ms = 0.0 when the model has no pre-processor and output_ready_wait_time_ms = 0.0 when the output needed no async wait. Those zeros were stored in StageRequestStats.diffusion_metrics as if they had been measured. Write each key only when its interval was observed; a measured 0 is still written. Nothing user-visible changes today. The [StageRequestStats] debug table already hides a field whose values are all zero or missing, the stage_metrics snapshot and responses do not carry these keys, and the vllm_omni:diffusion_preprocess_s histogram keeps its 0 sample for requests without a pre-processor, so its population does not change. The internal record now tells a missing interval from a measured 0, so a later schema decision can map a missing value to null. The output stream now records the async-output wait on DiffusionOutput.output_ready_wait_time (vllm-project#6580). Its default changes from 0.0 to None, so an output the stream did not wait for keeps the field unset and step_streaming leaves output_ready_wait_time_ms out; the stream still sets the measured value, including a measured 0. Implements the diffusion-row missing-value rule of RFC vllm-project#8671 (PR 2). Signed-off-by: MinhaoLi0318 <lminhao039@gmail.com>
Describe each diffusion engine timing kept per request in StageRequestStats.diffusion_metrics: its interval, when it is left out, which stages report it, and how the rows overlap. Signed-off-by: MinhaoLi0318 <lminhao039@gmail.com>
…t output Add the reverse order to the per-output wait test: an async output followed by a synchronous one. The existing order (sync, then async) passes even if the wait variable is initialized once before the output loop. Also describe the table check as the stage_table rows that build_and_log_summary returns, which is what the test asserts. Signed-off-by: MinhaoLi0318 <lminhao039@gmail.com>
Describe postprocess_time_ms as the whole engine-side post-processing (the model's post-processor, the CPU move under model-level offload, and formatting), note the synchronous receive on a KV prefetch miss, say that _ms is replaced by _s, that hidden all-zero rows still apply, and that only the output that finishes the stage is counted. Drop the issue reference from the design text. Signed-off-by: MinhaoLi0318 <lminhao039@gmail.com>
Build the formatted outputs as OmniRequestOutput and resolve async outputs through a small executor fake instead of SimpleNamespace, and drop the od_config stub that step_streaming does not read. Signed-off-by: MinhaoLi0318 <lminhao039@gmail.com>
dddc2af to
cab730d
Compare
|
@hsliuustc0106 Rebased onto current main ( The only conflict was in The streaming test fakes now go through the real Tests (local CPU run, not the CI image: macOS, Python 3.12.14, torch 2.13.0, vLLM 0.31.0, I also ran #6580's engine tests and the diffusion tests touched by #8952. The failures there (1 and 7, CUDA-only or environment related) are the same on plain main. AI assistance: drafted with Claude Code; I reviewed the rebase and this reply. |
Omni ReviewBot: no human activity for 7 days@MinhaoLi0318 this pull request has had no human commit, comment or review since 2026-10-19. Please confirm the current plan and next step. The author or a maintainer decides whether to change the PR state. To keep it moving, any one of these is enough: push an update, reply to the open blocker, or post the current plan and timeline. |
Contributor intent (required human answers)
1. What outcome do you want to achieve?
Make the per-request diffusion timing records tell "not measured" apart from "measured as 0", implementing the RFC's missing-value rule. For now, the log display, the responses and the Prometheus samples stay unchanged.
2. What is the highest-leverage variable for that outcome?
Whether a timing field is present, and whether its presence accurately reflects that the interval was measured this time. Once a missing value is written as 0 at record time, the two cases can no longer be told apart later.
3. How will this work move that variable?
Both fields are now written only after an actual measurement, and a measured 0 is still kept. The tests cover clearing the timing between consecutive outputs, and the docs describe each field's timing interval and when it is left out.
4. Whose customer or user needs does this address, and what are those needs?
Mainly developers who maintain or use per-request timing data. They need to know which phases were actually measured to interpret the data correctly; this change does not directly change what end users see.
Is a PR needed? Yes, and a small scope is enough. Fixing the missing-value rule requires changing the code that actually records the data; even though the current display does not change, it is worth merging as a separate, verifiable fix.
Purpose
Part of #8671 (PR 2: diffusion rows + missing-value rule). PR 1 landed as #8804.
Before:
DiffusionEngine.step_streamingwrotepreprocess_time_ms: 0.0when the model has no pre-processor, andoutput_ready_wait_time_ms: 0.0when an output came back without an asynchronous wait.OrchestratorAggregator.accumulate_diffusion_metricscarried both zeros intoStageRequestStats.diffusion_metrics(aspreprocess_time_sandoutput_ready_wait_time_s), so the per-request record could not tell "not measured" from "measured as 0".After: each of the two keys is written only after its interval was measured, and a measured 0 is kept. As on main, the output-ready wait is reset for every output (now to
Noneinstead of0.0), so a synchronous output after an async one gets no wait key; a new test pins this. The other keys are unchanged:scheduler_queue_wait_mswas already left out whenNone,diffusion_engine_exec_time_msandpostprocess_time_msare always written,kv_recv_time_msis still left out at 0, andforward_time_ms/vae_decode_time_mswere already left out when not measured. The production change isvllm_omni/diffusion/diffusion_engine.py(+10/-6) andvllm_omni/metrics/utils.py(+5).Contract: this implements the missing-value rule of #8671: a diffusion field that was not observed is left out of the per-request record instead of being written as
0.0.docs/design/metrics.mdgets a new section, "Diffusion-stage timings inStageRequestStats", after the LLM-stage split section from #8575. It gives each key's interval and when the key is left out, and says that only final-output diffusion stages have these timings, that only the output finishing the stage is counted, and that the rows overlap and must not be added together.What users see: nothing changes today.
[StageRequestStats]DEBUG table already hides a row whose values are all zero or missing (vllm_omni/metrics/stats.py:1062-1078), so these rows look the same. The one exception: if a request has two final-output diffusion stages and only one of them measured the interval, the other cell is now blank instead of0.000.stage_metricssnapshot do not carry the engine's diffusionmetrics:OmniRequestOutput._copy_content_fromdoes not copy them,omni_base.pybuildsresponse_metricsfrom the stage events, and_merge_stage_metric_eventdoes not readdiffusion_metrics.vllm_omni:diffusion_preprocess_skeeps its 0 sample for a request without a pre-processor:observe_diffusion_finalizerecords 0 whenpreprocess_time_sis absent anddiffusion_engine_exec_time_sis present (vllm_omni/metrics/utils.py:131-135). [RFC]: Diffusion-phase and per-request transfer attribution fields (companion to #6472) #8671 leaves this sample to the metrics owners; if they want it dropped, deleting those lines does it.Compatibility: no new field, no new collection, no new flag, no public schema change. Code that indexes
diffusion_metrics["preprocess_time_s"]ordiffusion_metrics["output_ready_wait_time_s"]directly would now need.get(). Nothing in the repo does: the accumulator and the table builder copy whatever keys are present, andobserve_diffusion_finalizeuses.get().Not changed:
docs/usage/metrics.md:72still describesvllm_omni:diffusion_postprocess_sas "Diffusion output postprocessing (VAE decode)". That label does not match the interval documented here (the model's post-processor, the move to CPU under model-level CPU offload, and output formatting). I left it for a separate change.step_streaming breakdownlog line still prints0.00 msfor an interval that was not measured. It is a log line, not a record.Test Plan
vLLM Version: 0.31.0, CPU only: vLLM source (
git archive v0.31.0) onPYTHONPATH, torch 2.13.0 (CPU), pytest 9.1.1, Python 3.12.14,VLLM_TARGET_DEVICE=cpu, Apple M5 Pro. Not the CI image.vLLM-Omni Commit:
dddc2af81(this PR, rebased on main303b8df65).tests/diffusion/test_diffusion_step_streaming_metrics.py(new) drivesstep_streamingwith a stubbed scheduler stream and executor, then follows the keys throughOrchestratorAggregatorinto the per-request record.tests/metrics/test_modality.pyadds accumulator and preprocess-histogram cases.Test Result
dddc2af81. The same files with main's production code (303b8df65): 6 failed, 68 passed. The failures are the fourstep_streamingtests that check that a key is absent (two of them across consecutive outputs),test_timing_metric_key_naming_consistency, andtest_preprocess_histogram_keeps_zero_sample_without_preprocessor.output_ready_wait_time = Nonereset out of the per-output loop failstest_output_ready_wait_does_not_carry_to_the_next_output(1 failed, 73 passed).dddc2af81; 753 passed, 1 failed on main303b8df65(without the new test file, which main does not have). The failure is the same on both:tests/diffusion/test_diffusion_engine.py::test_move_tensor_tree_moves_nested_cuda_tensors_to_cpuneeds CUDA (Torch not compiled with CUDA enabled). The 10 extra passes are this PR's new tests.docs/design/metrics.md), check-mark, check-spdx-header, check-forbidden-imports and check-torch-cuda-call pass, andgit diff --checkis clean. mypy-3.10 (mypy 1.11.1) reports 10 errors, the same 10 as main on these files, none on a changed line (CI skips this hook).Overhead (#8671 asks each PR to report it). This PR adds no collection: each output does two
is not Nonechecks and twoor 0.0for the DEBUG log line, and writes up to two fewer dict keys; finalize adds a couple ofNonechecks. In a CPU microbenchmark (Apple M5 Pro, median of 15 × 2000 requests, 2 runs each), this PR is not slower than main on any path (table below): it is lower in both runs on the engine path and the Prometheus-off path, and within main's two runs on the Prometheus-on path. So collection does not need to become opt-in.Benchmark table and script
µs per request, run 1 / run 2:
303b8df65dddc2af81DiffusionEngine.step_streaming, stubbed stream and executor, one sync output, no pre-processoraccumulate_diffusion_metrics+on_stage_metrics+observe_modality_at_finalize, Prometheus modality observation on (aggregatorlog_statsalways on)log_statsalways on)In the script,
log_stats_on/log_stats_offisOmniModalityMetrics(log_stats=...); theOrchestratorAggregatorhaslog_stats=Truein both. Run once per checkout, with that checkout first onPYTHONPATH:python bench_pr2.py <label>.Not covered:
step_streamingtests use a stub executor that returnsDiffusionOutputdirectly, so the async-output path through shared memory and the result pump is not exercised.pre-commit run --all-files(shellcheck not run; no shell file changed).AI assistance: I used Claude Code to implement and test this change and to draft this description; I reviewed every change and can explain it.