Skip to content

[Bugfix] Fix MiniCPM duplex response-required CI test - #9019

Merged
natureofnature merged 5 commits into
vllm-project:mainfrom
natureofnature:fix/minicpmo-duplex-ci-17383
Oct 16, 2026
Merged

natureofnature merged 5 commits into
vllm-project:mainfrom
natureofnature:fix/minicpmo-duplex-ci-17383

Conversation

@natureofnature

@natureofnature natureofnature commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Contributor intent

1. What outcome do you want to achieve?

Fix the MiniCPM duplex two-turn test failure in Buildkite #17383, preserving correct audio ownership and the requirement for two complete spoken responses.

2. What is the highest-leverage variable for that outcome?

The audio supplied to the response-required test: it should contain the complete fixture rather than depend on replaying residual audio from the previous turn.

3. How will this work move that variable?

Send the full fixture on both turns and cover residual-audio cleanup with a regression test.

4. Whose customer or user needs does this address, and what are those needs?

Maintainers and contributors need merge CI to detect broken duplex behavior and distinguish missing responses from the model's native listen decisions.

Purpose

Fixes #9018.

Fix the second-turn response.created timeout exposed after #8931. The test selected the 1.4-second window with the highest mean absolute PCM amplitude from a 5.47-second fixture and required a response on both turns. On the failing revision, the second input was committed successfully, but the model returned model_listen=True, with no error event.

The parent revision passed because it retained and replayed the previous turn's padded audio tail. _commit_and_start_auto_response retained a payload, then copied it to add realtime_item_id; append cleanup compared object identity against the copy and could not release the retained original. #8931 correctly moved the copy before retention. Restoring only the old copy order on the merge revision makes the cropped-input test pass again, isolating this dependency.

This change:

  • Sends the complete fixture for both response-required turns, preserving 200 ms streaming chunks, the two-turn lifecycle and the existing response, audio, transcript, metrics and no-error assertions. This changes the real-model input coverage from a 1.4-second excerpt to the complete recording; it does not establish that every short excerpt must trigger a response.
  • Adds a two-turn CPU regression for residual-audio release, with distinct PCM content and playback completion.
  • Adds a precise return type to the existing seeded-audio helper, resolving its existing mypy errors without changing execution.

Serving code, native listen policy and timeout values are unchanged. Existing runtime design contracts and user documentation remain applicable; this PR changes only two test files.

Test Plan

vLLM Version: 0.31.0; torch 2.13.0; transformers 5.14.1.

vLLM-Omni Commit: 653f20bc0163942e6dfe1c113dea111e270c54e3, based on 90576488a.

Validation used one H800 80 GB, the existing three-stage single-GPU deployment, CUDA graphs enabled, and offline model revision 503e754207c94da6bb26850b4469f367c9ea3582 (matching the failing CI log).

CPU tests:

pytest -q tests/engine/duplex/test_session_runner.py \
  -m 'core_model and cpu' --run-level core_model

Full advanced test combination used by the affected CI step:

CUDA_VISIBLE_DEVICES=0 HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 \
pytest -sv tests/e2e/online_serving/test_minicpmo_4_5_duplex.py \
  tests/e2e/online_serving/test_minicpmo_4_5_window.py \
  -m 'advanced_model and cuda' --run-level advanced_model

The GPU command requires the model and reference-audio assets to be cached. For a local checkpoint tree, set MODEL_PREFIX so openbmb/MiniCPM-o-4_5 resolves beneath it.

Test Result

Two-turn response-required comparison Result
Parent 28283fda3, original 1.4-second input 3/3 passed
Merge 5a5083ad3, original 1.4-second input 0/3 passed; each second commit followed by native listen, zero errors
Merge with only the old payload-copy order restored (diagnostic control) 1/1 passed
Complete-fixture fix 3/3 passed; two complete audio responses, transcripts and playback ACKs per run

The repetitions used a temporary test parametrization with the same scenario and assertions. That parametrization and the diagnostic control are not included in this PR.

  • Full advanced combination at 2fbb176f8: 18 passed, 4 deselected, 16 warnings in 677.81 seconds. This run did not use the temporary repetition.
  • CPU checks at 2fbb176f8: 251 passed across the session runner, scenario driver and Buildkite uploader tests. The residual-audio regression fails on the parent and passes after [Model] Add JoyAI speech input to unified duplex sessions #8931.
  • The PR was subsequently reduced to the two test files. Both are byte-for-byte identical to the files in the passing GPU run; the retained regression test is unchanged from the CPU run. Optional timeout diagnostics, their test and artifact-upload configuration were removed; GPU tests were not repeated for that removal.
  • Changed-file pre-commit checks were rerun after the scope reduction and passed, including lint, formatting, mypy and test markers.

Local validation used H800; the affected Buildkite job used H100. These local results do not establish an H100 Buildkite pass.

Self-review

  • Reviewed the two-file diff; both retained files match the full GPU run's source fingerprints.
  • Preserved the required response assertions and existing serving behavior.
  • Verified a clean merge against current main c8ccb8aed; its intervening change concerns MOSS models.

Use the complete fixture for both response-required turns. The old 1.4 s
crop relied on a retained-tail replay bug fixed by vllm-project#8931; without the replay,
the native model can keep listening after the second commit.

Cover residual-audio release across two turns, report commit-local timeout
events, and retain duplex artifacts in merge CI for future failures.

Signed-off-by: natureofnature <wzliu@connect.hku.hk>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-16T07:55:59.552412Z 653f20b Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@natureofnature
natureofnature marked this pull request as draft October 16, 2026 07:41
Signed-off-by: natureofnature <wzliu@connect.hku.hk>
@natureofnature
natureofnature marked this pull request as ready for review October 16, 2026 07:55
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@natureofnature

Copy link
Copy Markdown
Collaborator Author

@amy-why-3459 PTAL

@natureofnature natureofnature added the merge-test label to trigger buildkite merge test CI label Oct 16, 2026
@vllm-omni-review-bot

vllm-omni-review-bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Omni ReviewBot triage note

Automated triage of commit b70432a45b29 produced:

  • Priority: high. Prompt maintainer attention is suggested.

These are automated triage suggestions only — the final decision belongs to the maintainers.

@amy-why-3459 amy-why-3459 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the careful bisection, the parent / merge / copy-order-control table makes the root cause convincing: the old test only passed because the un-released residual tail from turn 1 was being replayed into turn 2, and #8931 correctly stopped that. Sending the complete fixture and pinning the release behavior with a CPU regression is the right split.

Two small points inline: the same cropped-input pattern is still used by the video / still-image tests in this file, and the CPU regression could check the replay symptom a bit more directly. Neither blocks the CI fix.

# at 1.4 s can legitimately leave the native model listening on a later turn.
args.turn_duration_ms = [0] * args.turns
result = asyncio.run(run_demo(args))
assert result["ok"] is True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same reasoning seems to apply to test_duplex_single_session_video_input (L351) and test_duplex_single_session_still_image_input (L581): both still send [args.first_turn_ms] * args.turns (1.4 s crop on both turns) and assert done_count == 2. Before #8931 they would also have benefited from the replayed tail on turn 2.

They passed in your 18-pass advanced run, but was that a single run? Since the cropped response-required case went 0/3 deterministically, it'd be good to either confirm those two are stable across a few repetitions (vision input may be what tips the model to speak), or add a short comment there explaining why the crop is still safe. Switching them to [0] isn't free because the video test asserts video_frame_count == 4, so a comment may be the cheaper option.

assert h.runner.model_state.committed_audio_payload is None
assert h.session.pending_input_bytes == 0
assert len(h.port.submissions) == (turn + 1) * 2
final_append = h.port.submissions[-1].prompt["model_intermediate_buffer"]["duplex"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that the two turns use different PCM values. Optional: the final-append assertion catches a replayed tail indirectly (via the padded length). Also asserting the non-final unit of each turn is pure current-turn audio, e.g. base64.b64decode(h.port.submissions[-2].prompt["model_intermediate_buffer"]["duplex"]["payload"]["audio"]) == pcm_f32(16000, value=value), would make it explicit that nothing from turn 0 leaks into turn 1's stream, and keep the test meaningful if the padding/unit size ever changes.

Signed-off-by: natureofnature <wzliu@connect.hku.hk>

@amy-why-3459 amy-why-3459 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fd6232a2 — this adds the direct streaming-ownership check I suggested on the CPU residual-audio regression (non-final unit is pure current-turn PCM). Looks good; no new issues from this delta.

The optional note about the video / still-image tests still using the 1.4 s crop is unchanged and still non-blocking.

@natureofnature

Copy link
Copy Markdown
Collaborator Author

@yenuo26

@natureofnature natureofnature added the ready label to trigger buildkite CI label Oct 16, 2026
tts_output(h.stage0_request_id(), samples=samples, turn_end=True, turn_id=turn)
)
done = find(events, "response.done")
await h.run(commands.AckPlayback(played_ms=1000, response_id=done.response_id))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tail-audio release is already guarded by lines 706–716: committed_audio_payload is None, plus the two PCM rounds not being equal. The later TTS and AckPlayback parts are the playback completion mentioned in the PR, but the result of h.run(...) is discarded. When AckPlayback fails it returns an error event rather than raising an exception, so as written, that line passes no matter what it returns.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yenuo26 Good catch — the ACK result was being discarded. Fixed in f3c3cc9: each turn now asserts response.done.status == "completed", exactly one playback.acknowledged event, history_committed is True, and a committed playback cursor of 1,000 ms. An ACK error now fails the test.

The full session runner CPU module passes: 55 passed. Changed-file pre-commit checks also pass. GPU E2E tests were not rerun for this assertion-only change.

Signed-off-by: natureofnature <wzliu@connect.hku.hk>
@natureofnature natureofnature added ready label to trigger buildkite CI and removed ready label to trigger buildkite CI labels Oct 16, 2026
@natureofnature
natureofnature enabled auto-merge (squash) October 16, 2026 10:22
@natureofnature natureofnature added ready label to trigger buildkite CI and removed ready label to trigger buildkite CI merge-test label to trigger buildkite merge test CI labels Oct 16, 2026
@natureofnature natureofnature added CI/CD codes related to changes to CI/CD ready label to trigger buildkite CI and removed ready label to trigger buildkite CI labels Oct 16, 2026

@amy-why-3459 amy-why-3459 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for f3c3cc91 — asserting response.done completed plus a successful playback.acknowledged (history committed, played_ms) tightens the residual-audio regression without changing the fixture/ownership coverage. Merge-main b70432a4 does not touch the PR's two test files beyond that delta. No new issues from this incremental look.

The earlier optional note about the video / still-image tests still using the 1.4 s crop remains non-blocking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI/CD codes related to changes to CI/CD ready label to trigger buildkite CI

Projects

None yet

4 participants