Skip to content

[Bugfix][Multimodal] Normalize single-channel audio to 1D - #56691

Merged
DarkLight1337 merged 6 commits into
vllm-project:mainfrom
Woolgathererer:codex/mono-audio-channel-axis
Oct 4, 2026
Merged

DarkLight1337 merged 6 commits into
vllm-project:mainfrom
Woolgathererer:codex/mono-audio-channel-axis

Conversation

@Woolgathererer

Copy link
Copy Markdown
Contributor

Purpose

Mono audio with an explicit channel dimension can fail in Whisper preprocessing.
For example, passing a (1, 16000) waveform at 16 kHz through the offline audio
tuple path raises ValueError: Failed to apply WhisperProcessor, while the same
samples in a (16000,) array work.

normalize_audio returns early when the input channel count already matches
the target, leaving a 2D array for mono input. This also makes
AudioProcessorItems.get_audio_length() report 1 instead of 16000.

Return the only channel as a 1D waveform when the target is mono. Extend the
existing NumPy, Torch and parser tests to cover both channel layouts and the
single-sample case.

This reproduces without resampling. #47693 changes the order of normalization
and resampling, #56023 addresses unsupported multi-channel reduction, and
#51022 optimizes downmixing. Their current patches leave this early return
unchanged.

Test Plan

Run the audio and parser tests on CPU:

export VLLM_TARGET_DEVICE=cpu HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1
export PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 PYTHONPATH=.

.venv/bin/python -m pytest tests/multimodal/test_audio.py \
  -k 'mono_passthrough_for_numpy or mono_passthrough_for_torch or parser_mono_passthrough' -q

.venv/bin/python -m pytest \
  tests/multimodal/test_audio.py tests/multimodal/test_parse.py \
  -k 'not torchaudio and not resampling_with_channel_normalization_e2e' -q

SKIP=actionlint .venv/bin/pre-commit run --files \
  vllm/multimodal/audio.py tests/multimodal/test_audio.py

Also compare the same one-second WAV in (16000,), (1, 16000) and (16000, 1)
layouts through vLLM's Whisper multimodal processor, using the official
openai/whisper-tiny configuration, tokenizer and feature extractor.

Test Result

  • Focused regression cases: 8 failed, 3 passed before; 11 passed after.
  • Audio and parser selection on main fa1b3b1922 plus this patch:
    71 passed, 1 skipped, 8 deselected.
  • Whisper preprocessing: both 2D layouts failed before. After the fix, all
    three layouts produce exactly equal features, prompt tokens and placeholder
    ranges. Feature shape is (1, 80, 3000) and parsed audio length is 16000.
  • Applicable pre-commit hooks passed. Actionlint was skipped; no workflows changed.

The local environment has no matching torchaudio build. An earlier full audio
test run had 55 passes and 7 failures due to that missing dependency; the
filtered run excludes those cases and the default-backend assertion. The
skipped parser test requires CUDA. Whisper validation covers CPU preprocessing;
model generation and accuracy evaluation have not been run.

AI assistance was used, and I reviewed all the changes and tests.

Return a 1D waveform for mono inputs with an explicit channel axis so
Whisper preprocessing accepts them and audio sample counts stay correct.
Extend the existing NumPy, Torch and parser regressions for these layouts.

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Ylean <61880605+Woolgathererer@users.noreply.github.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 multi-modality Related to multi-modality (#4194) bug Something isn't working labels Sep 13, 2026
@DarkLight1337

Copy link
Copy Markdown
Member

Does this issue occur for other processors? If not, it might be better to perform this conversion for WhisperProcessor only

@Woolgathererer

Copy link
Copy Markdown
Contributor Author

Does this issue occur for other processors? If not, it might be better to perform this conversion for WhisperProcessor only

Thanks for the suggestion! I checked Qwen2AudioProcessor on CPU with a minimal local tokenizer, and it also fails for (1, N) and (N, 1) inputs. With this fix, both layouts produce exactly the same features, attention masks, and token IDs as the 1D input.

Qwen2-Audio shares WhisperFeatureExtractor but doesn’t go through WhisperProcessor, so limiting the conversion to WhisperProcessor would leave that case unresolved.

Since normalize_audio already documents 1D output for target_channels=1, I think keeping the fix there is consistent with its existing contract. Does that make sense, or would you prefer handling the conversion closer to the affected processors?

@DarkLight1337

DarkLight1337 commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

@hmellor is this intentional for HF audio processors?

@Woolgathererer

Copy link
Copy Markdown
Contributor Author

@hmellor is this intentional for HF audio processors?

@DarkLight1337 Following up on the processor-scope question:

I checked both Whisper and Qwen2Audio. Both use the shared audio normalization path, and both fail on mono inputs shaped as (1, N) or (N, 1) before this fix. The patch only canonicalizes target_channels=1 to a 1D waveform; it does not change passthrough audio or multi-channel reduction behavior.

Since normalize_audio already documents 1D output for mono targets, would you be comfortable approving the shared fix? If any processor intentionally requires a 2D mono input, please point it out so I can account for that case.

@hmellor

hmellor commented Sep 30, 2026

Copy link
Copy Markdown
Member

@hmellor is this intentional for HF audio processors?

It seems that most models fail in one way or another when receiving mono audio in a 2D array. Given this, I'd suggest making the fix global. I'll work with @eustlb to more consistently handle this on the Transformers side


Feature extractor (1, N) (N, 1)
ASTFeatureExtractor ❌ AssertionError ❌ AssertionError
ClvpFeatureExtractor ❌ ValueError ❌ ValueError
DacFeatureExtractor ❌ ValueError ❌ ValueError
DiaFeatureExtractor ❌ ValueError ❌ ValueError
EncodecFeatureExtractor ❌ ValueError ❌ ValueError
FunAsrNanoFeatureExtractor ❌ AssertionError ❌ AssertionError
Gemma3nAudioFeatureExtractor ❌ ValueError ❌ ValueError
Gemma4AudioFeatureExtractor ❌ ValueError ❌ ValueError
Gemma4UnifiedAudioFeatureExtractor ❌ ValueError ✅ same as 1D
GraniteSpeech5FeatureExtractor ❌ RuntimeError ✅ same as 1D
InklingFeatureExtractor ⚠️ wrong output: (1, 1, 80) vs (1, 20, 80) ✅ same as 1D
KyutaiSpeechToTextFeatureExtractor ❌ ValueError ❌ ValueError
LasrFeatureExtractor ⚠️ wrong output: (1, 1, 1, 98, 128) vs (1, 98, 128) ❌ RuntimeError
MusicgenMelodyFeatureExtractor ✅ same as 1D ⚠️ wrong output: same shape, different values
NeuCodecFeatureExtractor ❌ AssertionError ❌ AssertionError
PeAudioFeatureExtractor ❌ ValueError ❌ ValueError
Phi4MultimodalFeatureExtractor ❌ IndexError ❌ RuntimeError
Qwen3ASRFeatureExtractor ❌ ValueError ❌ ValueError
SeamlessM4TFeatureExtractor ✅ same as 1D ❌ ValueError
Speech2TextFeatureExtractor ❌ AssertionError ❌ AssertionError
SpeechT5FeatureExtractor ⚠️ wrong output: (1, 1, 16000) vs (1, 16000) ⚠️ wrong output: (1, 16000, 1) vs (1, 16000)
UnivNetFeatureExtractor ❌ ValueError ❌ ValueError
VoxtralRealtimeFeatureExtractor ❌ NotImplementedError ❌ RuntimeError
Wav2Vec2FeatureExtractor ⚠️ wrong output: (1, 1, 16000) vs (1, 16000) ⚠️ wrong output: (1, 16000, 1) vs (1, 16000)
WhisperFeatureExtractor ❌ ValueError ❌ ValueError
Xcodec2FeatureExtractor ❌ AssertionError ❌ AssertionError

@eustlb eustlb 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, #44394 will handle that in a standardized way (stereo (2, T) is downmixed by default and (1, T) will be squeezed), such a fix is good for now and compatible with coming audio processors

@DarkLight1337

Copy link
Copy Markdown
Member

/ci run

@github-actions

Copy link
Copy Markdown

❌ This PR is 1 commit behind upstream main. Your branch must contain every commit currently on upstream main. No new CI build was started. Merge or rebase onto the latest main, then rerun /ci run. To test this branch at your own risk, use /ci run --allow-stale.

@DarkLight1337

Copy link
Copy Markdown
Member

/ci run --allow-stale

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #92137 for commit 0c7c443e07f8.

⚠️ This PR is 1 commit behind upstream main. Running CI at your own risk because --allow-stale was requested; outdated CI configuration may cause failures. Before merging, merge or rebase onto the latest main, then rerun /ci run on the latest PR commit.

@DarkLight1337
DarkLight1337 enabled auto-merge (squash) September 30, 2026 12:26
@DarkLight1337 DarkLight1337 added the ready ONLY add when PR is ready to merge/full CI is needed label Sep 30, 2026
@github-actions

Copy link
Copy Markdown

✅ @Woolgathererer, CI is now available for this PR.

  • /ci run starts upstream CI; /amd-ci run starts AMD CI only.
  • Your branch must contain every commit currently on its upstream target branch. Merge or rebase onto the latest target branch, then rerun the command. Append --allow-stale to a run command to test an outdated branch at your own risk.
  • /ci retry retries failed jobs in the CI build for the current PR head. If the current head has no CI build, it starts a new CI build for the current head containing only jobs that failed in the latest earlier CI build for this PR.
  • /amd-ci retry retries failed jobs in AMD CI for the current PR head. Use /amd-ci run when the current head has no AMD CI build.
  • /ci cancel cancels scheduled or running CI builds for this PR branch; /amd-ci cancel does the same for AMD CI only.

@DarkLight1337

Copy link
Copy Markdown
Member

Pre-commit is failing on main, trying to fix it now...

@DarkLight1337

Copy link
Copy Markdown
Member

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #92157 for commit 9d97617afca7.

@mergify

mergify Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Hi @Woolgathererer, the pre-commit checks have failed. Please run:

uv pip install pre-commit>=4.5.1
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

@DarkLight1337

Copy link
Copy Markdown
Member

The current pre-commit failure looks relevant now, please fix it.

@DarkLight1337

Copy link
Copy Markdown
Member

/ci run

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

✅ Triggered Buildkite CI #92781 for commit e0c3e2cf413b.

@mergify

mergify Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Hi @Woolgathererer, the pre-commit checks have failed. Please run:

uv pip install pre-commit>=4.5.1
pre-commit install
pre-commit run --all-files

Then, commit the changes and push to your branch.

For future commits, pre-commit will run automatically on changed files before each commit.

@DarkLight1337

Copy link
Copy Markdown
Member

Please fix pre-commit

@vllm-agent

vllm-agent commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

CI selector (shadow): 18 test steps (47 jobs) instead of 54 (70 jobs)

Shadow mode: this changes nothing about what CI runs. It shows what the evidence-based selector would pick for this PR, next to today's rules. How it works.

Feedback welcome: reply here if it would skip a step this change needs, or runs something unrelated.

steps (jobs) Today's rules Selector Would skip Would add
NVIDIA, CPU and others 54 (70) 18 (47) 44 (54) 8 (31)
AMD mirrors 46 (57) 14 (35) 38 (46) 6 (24)
Selector would run (18)
  • arm-cpu-test ×3
  • async-engine-inputs-utils-worker
  • basic-models-tests-extra-initialization ×14
  • basic-models-tests-initialization
  • cpu-multi-modal-model-tests-n ×4
  • cpu-multimodal-config
  • distributed-model-tests-2-gpus ×3
  • entrypoints-integration-multimodal
  • entrypoints-integration-speech_to_text
  • entrypoints-unit-tests
  • examples
  • lora ×4
  • multi-modal-models-standard-1-qwen2
  • multi-modal-models-standard-4-other-whisper
  • multi-modal-processor ×4
  • multi-modal-processor-cpu ×4
  • pipeline-context-parallelism-4-gpus
  • samplers-multimodal-beam-search
Would skip (today's rules run them) (44)
  • ascend-npu-test
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-test-other-cpu
  • basic-models-tests-other
  • benchmarks-cli-test
  • cpu-language-generation-and-pooling-model-tests ×3
  • cpu-params-env-tokenizers-parser
  • cpu-reasoning-renderers
  • cpu-tool-parsers
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kernels-root-misc-test-b200
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • metrics-tracing-2-gpus
  • mooncake-ec-tcp-e2e-2-gpus
  • multi-modal-accuracy-eval-small-models
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • pytorch-fullgraph-test
  • regression
  • v1-core
  • v1-executor-worker
  • v1-kv-connectors ×4
  • v1-kv-offload
  • v1-metrics-lmeval
  • v1-others-cpu
Would add (today's rules do not run them) (8)
  • arm-cpu-test ×3 (code map)
  • basic-models-tests-extra-initialization ×14 (Python record)
  • cpu-multi-modal-model-tests-n ×4 (code map)
  • distributed-model-tests-2-gpus ×3 (code map)
  • entrypoints-unit-tests (Python record)
  • lora ×4 (Python record)
  • pipeline-context-parallelism-4-gpus (Python record)
  • samplers-multimodal-beam-search (Python record)
AMD mirrors: would skip (38)
  • basic-correctness ×2
  • basic-correctness-cpu-offload
  • basic-correctness-cumem
  • basic-correctness-prefetch-offload
  • basic-correctness-sleep-mode
  • basic-models-tests-other
  • benchmarks-cli-test
  • entrypoints-integration-api-server ×4
  • entrypoints-integration-api-server-generate
  • entrypoints-integration-api-server-openai-chat_completion
  • entrypoints-integration-api-server-openai-completion
  • entrypoints-integration-llm
  • entrypoints-integration-pooling
  • entrypoints-integration-responses-api
  • kernels-fla-ops-test-b200
  • kernels-mhc-test-b200
  • kernels-root-misc-test-b200
  • language-models-tests-granite-l4-compatibility
  • language-models-tests-hybrid ×2
  • language-models-tests-standard
  • metrics-tracing-2-gpus
  • mooncake-ec-tcp-e2e-2-gpus
  • multi-modal-accuracy-eval-small-models
  • multi-modal-models-standard-2-qwen3-gemma
  • multi-modal-models-standard-3-llava-qwen2-vl
  • platform-tests
  • pytorch-compilation-dynamic-shapes
  • pytorch-compilation-passes-unit-tests
  • pytorch-compilation-unit-tests
  • pytorch-compilation-unit-tests-h100
  • pytorch-fullgraph-cudagraph-l4-compatibility
  • pytorch-fullgraph-test
  • regression
  • v1-core
  • v1-executor-worker
  • v1-kv-connectors ×4
  • v1-kv-offload
  • v1-metrics-lmeval
AMD mirrors: would add (6)
  • basic-models-tests-extra-initialization ×14 (Python record)
  • distributed-model-tests-2-gpus ×3 (code map)
  • entrypoints-unit-tests (Python record)
  • lora ×4 (Python record)
  • pipeline-context-parallelism-4-gpus (Python record)
  • samplers-multimodal-beam-search (Python record)

2 changed files · base 18f8f96025 · head bef533e2a2 · Python record: build 92769 at 84bcbc6264 · kernel record: table 84bcbc6 (build 92769), map 84bcbc6 · not counted: 10 build steps, 5 A100 steps the generator no longer emits, 2 optional steps the selector would also run

@Woolgathererer

Copy link
Copy Markdown
Contributor Author

Please fix pre-commit

sry, on the way

Signed-off-by: Ylean <61880605+Woolgathererer@users.noreply.github.com>
auto-merge was automatically disabled October 4, 2026 04:44

Head branch was pushed to by a user without write access

@DarkLight1337
DarkLight1337 enabled auto-merge (squash) October 4, 2026 04:57
@DarkLight1337

Copy link
Copy Markdown
Member

/ci run

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

❌ This PR is 1 commit behind upstream main. Your branch must contain every commit currently on upstream main. No new CI build was started. Merge or rebase onto the latest main, then rerun /ci run. To test this branch at your own risk, use /ci run --allow-stale.

@DarkLight1337

Copy link
Copy Markdown
Member

/ci run

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown

✅ Triggered Buildkite CI #92783 for commit bef533e2a277.

@DarkLight1337
DarkLight1337 merged commit 0872ddf into vllm-project:main Oct 4, 2026
128 checks passed
michael-gpu pushed a commit to practical-inference/vllm-ampere-extended that referenced this pull request Oct 20, 2026
…ct#56691)

Signed-off-by: Ylean <61880605+Woolgathererer@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working multi-modality Related to multi-modality (#4194) ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants