Skip to content

[Bugfix][Model][Rust Frontend] Qwen3_ASR: Handle ragged Qwen audio batches from Rust frontend - #60979

Merged
Isotr0py merged 3 commits into
vllm-project:mainfrom
noone-zhhh:bugfix/qwen_asr_audio_input
Oct 13, 2026
Merged

Isotr0py merged 3 commits into
vllm-project:mainfrom
noone-zhhh:bugfix/qwen_asr_audio_input

Conversation

@noone-zhhh

@noone-zhhh noone-zhhh commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Overview

Fixes #60970.
Fix an EngineCore crash when the Rust frontend batches Qwen3-ASR audio requests with different padded feature lengths. Normalize ragged audio features in Qwen3-ASR and the shared Qwen Omni input parser.

Claims

  • Accept ragged lists and tuples of per-audio feature tensors, remove padding, and concatenate valid frames along the time dimension.
  • Preserve existing stacked and flat tensor handling, feature order, and dtype.
  • Add regression tests for both parsers, including mixed lengths, padding, and mismatched feature/length counts.

Test Plan / Test Result

Lint

Command

pre-commit run --files tests/model_executor/test_qwen_audio_input.py \
vllm/model_executor/models/qwen2_5_omni_thinker.py \
vllm/model_executor/models/qwen3_asr.py
Full precommit output
ruff check..........................................................................................Passed
ruff format.........................................................................................Passed
typos...............................................................................................Passed
clang-format....................................................................(no files to check)Skipped
markdownlint-cli2...............................................................(no files to check)Skipped
Lint GitHub Actions workflow files..............................................(no files to check)Skipped
shellcheck......................................................................(no files to check)Skipped
pip-compile.....................................................................(no files to check)Skipped
pip-compile-rocm................................................................(no files to check)Skipped
pip-compile-xpu.................................................................(no files to check)Skipped
pip-compile-cpu.................................................................(no files to check)Skipped
pip-compile-docs................................................................(no files to check)Skipped
check json......................................................................(no files to check)Skipped
reformat test/nightly-torch.txt to be in sync with test/cuda.in.................(no files to check)Skipped
Check auto-label rules still match real files...................................(no files to check)Skipped
Check agent instruction files and skills........................................(no files to check)Skipped
- hook id: check-agent-files
Check every Buildkite step declares an explicit key.............................(no files to check)Skipped
Check new tests are wired into Buildkite CI.........................................................Passed
- hook id: check-test-tethering
- duration: 0.92s
Run mypy for the oldest supported Python version....................................................Passed
Lint PNG exports from excalidraw................................................(no files to check)Skipped
Check SPDX headers..................................................................................Passed
Check root lazy imports.............................................................................Passed
Check for spaces in all filenames...................................................................Passed
Update Dockerfile dependency graph..................................................................Passed
Test non-root entrypoint wrapper................................................(no files to check)Skipped
Check for forbidden imports.........................................................................Passed
Prevent new 'torch.cuda' APIs call..................................................................Passed
Validate configuration has default values and that each field has a docstring.......................Passed
Validate docker/versions.json matches Dockerfile................................(no files to check)Skipped
Check for boolean ops in with-statements............................................................Passed
Rust - Normalize Cargo manifests with autoinherit...............................(no files to check)Skipped
Rust - Sort Cargo manifest sections.............................................(no files to check)Skipped
Rust - Format code..............................................................(no files to check)Skipped
Suggestion..........................................................................................Passed
- hook id: suggestion
- duration: 0s

Unit Test

Command:

python -m pytest -v tests/model_executor/test_qwen_audio_input.py
Full pytest output
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-singleton-dtype0] PASSED [  2%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-singleton-dtype1] PASSED [  5%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-same_length-dtype0] PASSED [  8%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-same_length-dtype1] PASSED [ 10%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-ragged-dtype0] PASSED [ 13%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-ragged-dtype1] PASSED [ 16%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-ragged_with_padding-dtype0] PASSED [ 18%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-ragged_with_padding-dtype1] PASSED [ 21%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-shared_padding-dtype0] PASSED [ 24%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-shared_padding-dtype1] PASSED [ 27%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-three_ragged_items-dtype0] PASSED [ 29%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen3_asr-three_ragged_items-dtype1] PASSED [ 32%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-singleton-dtype0] PASSED [ 35%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-singleton-dtype1] PASSED [ 37%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-same_length-dtype0] PASSED [ 40%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-same_length-dtype1] PASSED [ 43%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-ragged-dtype0] PASSED [ 45%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-ragged-dtype1] PASSED [ 48%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-ragged_with_padding-dtype0] PASSED [ 51%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-ragged_with_padding-dtype1] PASSED [ 54%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-shared_padding-dtype0] PASSED [ 56%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-shared_padding-dtype1] PASSED [ 59%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-three_ragged_items-dtype0] PASSED [ 62%]
tests/model_executor/test_qwen_audio_input.py::test_parse_batched_audio_preserves_valid_frames[qwen_omni-three_ragged_items-dtype1] PASSED [ 64%]
tests/model_executor/test_qwen_audio_input.py::test_parse_flat_audio_keeps_existing_tensor[qwen3_asr-dtype0] PASSED  [ 67%]
tests/model_executor/test_qwen_audio_input.py::test_parse_flat_audio_keeps_existing_tensor[qwen3_asr-dtype1] PASSED  [ 70%]
tests/model_executor/test_qwen_audio_input.py::test_parse_flat_audio_keeps_existing_tensor[qwen_omni-dtype0] PASSED  [ 72%]
tests/model_executor/test_qwen_audio_input.py::test_parse_flat_audio_keeps_existing_tensor[qwen_omni-dtype1] PASSED  [ 75%]
tests/model_executor/test_qwen_audio_input.py::test_parse_ragged_audio_tuple[qwen3_asr-dtype0] PASSED                [ 78%]
tests/model_executor/test_qwen_audio_input.py::test_parse_ragged_audio_tuple[qwen3_asr-dtype1] PASSED                [ 81%]
tests/model_executor/test_qwen_audio_input.py::test_parse_ragged_audio_tuple[qwen_omni-dtype0] PASSED                [ 83%]
tests/model_executor/test_qwen_audio_input.py::test_parse_ragged_audio_tuple[qwen_omni-dtype1] PASSED                [ 86%]
tests/model_executor/test_qwen_audio_input.py::test_parse_missing_audio_returns_none[qwen3_asr] PASSED               [ 89%]
tests/model_executor/test_qwen_audio_input.py::test_parse_missing_audio_returns_none[qwen_omni] PASSED               [ 91%]
tests/model_executor/test_qwen_audio_input.py::test_unpad_audio_rejects_mismatched_length_count[list] PASSED         [ 94%]
tests/model_executor/test_qwen_audio_input.py::test_unpad_audio_rejects_mismatched_length_count[tuple] PASSED        [ 97%]
tests/model_executor/test_qwen_audio_input.py::test_unpad_audio_rejects_mismatched_length_count[tensor] PASSED       [100%]
Code Result
Before 18 failed, 19 passed
After 37 passed

E2E Test

vllm version: 0.31.0(db9527a)
Model: Qwen/Qwen3-ASR-1.7B
Python: 3.12.14
PyTorch: 2.13.0
GPU: NVIDIA H100 80GB
CUDA graph: full and piecewise

Test Before After
8 concurrent requests, mixed ~5s / ~14s unique audios (deterministic crash case) 8/8 HTTP 500, EngineCore dead 8/8 OK, 0 exceptions in logs
Single-request multi-clip (unequal clips) — regression check on the 3-D path OK OK
Single clip (batch of one, unsqueeze path) — regression check OK OK

Details

Root Cause

  1. The multimodal registry declares input_audio_features as FieldLayout::Batched, so the Rust frontend emits one item per clip — a 2-D [n_mels, padded_seq] tensor, padded to the request's longest clip.
  2. The model contract is MultiModalFieldConfig.flat_from_sizes(..., dim=1): a single flat [n_mels, total_seq] tensor.
  3. _reduce_data stacks only when all shapes are equal; unequal durations (or unequal intra-request padding) → return batch raw list.
  4. The parser's guard only matches torch.Tensor with dim() == 3; the list falls through and dies in TensorSchema validation.

Why this is not a duplicate

Following PR (before 2026.10.10) are related but none of them solved this problem.


Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (Mandatory for agents, optional for humans).
  • AI agents were used to assist with this PR, but I have personally reviewed every line of code and run the tests to verify the changes.
  • Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.
  • Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.
  • Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.
  • Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.

…tches from Rust frontend

Signed-off-by: zh98 <workallday.zhh@gmail.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@mergify mergify Bot added rust qwen Related to Qwen models bug Something isn't working labels Oct 10, 2026
@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run for upstream CI or /amd-ci run for AMD CI only whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use the corresponding /ci run, /ci retry, and /ci cancel commands, or their /amd-ci variants. New commits do not start upstream CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

@lesj0610 lesj0610 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM. Both parsers pass the stacked and ragged audio tests, with padding removed per clip. Flat 2-D input still passes through unchanged.

@twu3202

twu3202 commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

I tested this on an RTX 6000 Ada (sm_89) with Qwen/Qwen3-ASR-1.7B:

  • vLLM: the 0.31.0 wheel plus this PR's two model files.
  • Rust frontend: vllm-rs built from the v0.31.0 tag.
  • Input: 16 distinct FLEURS test clips (8 en, 8 zh, about 5 s and 14 s).
  • Requests: greedy, one pass sending clips one by one, then two passes sending all 16 at once.

HTTP 200 counts:

one by one all 16 at once, x2
Python frontend, 0.31.0 16/16 16/16, 16/16
Python frontend, this PR 16/16 16/16, 16/16
Rust frontend, 0.31.0 16/16 0/16, then 0/16 (EngineCore dies in the first pass: got torch.Size([3, 128, 462]))
Rust frontend, this PR 16/16 16/16, 16/16

With the PR, the concurrent passes give the same transcripts as the one-by-one pass, and both match the Python frontend: 16/16 identical in each comparison.

I also logged a hash of each clip's features after unpadding. Across 8 encoder calls the input was a ragged list, e.g. [(128, 504), (128, 536), (128, 474), (128, 510)]. Those calls covered 14 of the 16 clips, and every clip's features were bit-identical to the same clip sent alone. I didn't run Qwen2.5-Omni.

Two notes for anyone reproducing:

@BugenZhao

Copy link
Copy Markdown
Member

[AUTOMATED] @codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 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 ✅ Completed 2026-10-11T23:40:51.526699Z 32e87ef Manual request
ℹ️ 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 Review: Didn't find any major issues. Delightful!

Reviewed commit: 32e87ef562

ℹ️ 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remove the test

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've removed the unit tests as requested and pushed the changes. Thanks for the review!

Signed-off-by: zh98 <workallday.zhh@gmail.com>

@vadiklyutiy vadiklyutiy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@Isotr0py
Isotr0py enabled auto-merge (squash) October 13, 2026 09:20
@github-actions github-actions Bot added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 13, 2026
@Isotr0py

Copy link
Copy Markdown
Member

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #94431 for commit 103f72eb4c93.

@Isotr0py
Isotr0py merged commit 426f3bd into vllm-project:main Oct 13, 2026
158 checks passed
@noone-zhhh
noone-zhhh deleted the bugfix/qwen_asr_audio_input branch October 13, 2026 13:33
AndreasKaratzas pushed a commit to AndreasKaratzas/vllm that referenced this pull request Oct 16, 2026
…tches from Rust frontend (vllm-project#60979)

Signed-off-by: zh98 <workallday.zhh@gmail.com>
Co-authored-by: Isotr0py <Isotr0py@outlook.com>

Signed-off-by: Andreas Karatzas <AndreasKaratzas@users.noreply.github.com>
michael-gpu pushed a commit to practical-inference/vllm-ampere-extended that referenced this pull request Oct 20, 2026
…tches from Rust frontend (vllm-project#60979)

Signed-off-by: zh98 <workallday.zhh@gmail.com>
Co-authored-by: Isotr0py <Isotr0py@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working qwen Related to Qwen models ready ONLY add when PR is ready to merge/full CI is needed rust

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Qwen3-ASR with Rust frontend — EngineCore dies when a scheduler step batches unequal-length audio

6 participants