Repository navigation
[Bugfix] Apply session.update language and prompt to Qwen3-ASR realtime - #61031
tamerrkanak wants to merge 2 commits into
Conversation
/v1/realtime ignored `language` and `prompt` in session.update: the connection only read `model`, SupportsRealtime.buffer_realtime_audio had no way to receive per-session options, and Qwen3-ASR realtime hard-coded its prompt template. Parse session.update with SessionUpdate (now with optional `language` and `prompt`), validate the language like the transcription endpoint, pass both to buffer_realtime_audio(), and build each Qwen3-ASR realtime segment prompt with get_generation_prompt() so realtime and /v1/audio/transcriptions share one prompt builder. Without language and prompt the prompt token ids are unchanged. Fixes vllm-project#61026 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tamer Kanak <tamer.kanak@mankyazilim.com.tr>
|
👋 Hi! Thank you for contributing to the vLLM project. 💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment Once the PR is approved or has the If you have any questions, please reach out to us on Slack at https://slack.vllm.ai. Agent GuidelinesIMPORTANT: 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. 🚀 |
|
Documentation preview: https://vllm--61031.org.readthedocs.build/en/61031/ |
|
Hi @DarkLight1337 @NickLucche, could you take a look when you have a moment? This is a small fix (~60 non-test lines): |
|
@DarkLight1337 could you add the |
|
We will add those labels after reviewing the PR. Sorry our backlog is quite long |
twu3202
left a comment
There was a problem hiding this comment.
Thanks for the ping on #56218. I tested this PR on an RTX 6000 Ada (sm_89).
Setup:
- vLLM 0.31.0 wheel with this PR's diff applied (it applies cleanly).
Qwen/Qwen3-ASR-0.6Bwith--hf-overrides '{"architectures": ["Qwen3ASRRealtimeGeneration"]}' --enforce-eager.- 16 FLEURS test clips, 8 en and 8 zh, 4.6–14.7 s, streamed over
/v1/realtimein 4096-byte chunks. - For the realtime vs REST comparison I cut each clip to at most 4.9 s, so it fits in one realtime segment. I sent the same int16 samples and the same
language/promptto/v1/audio/transcriptionsattemperature=0. - The prompt was one sentence: "The speaker is reading a sentence from a news article."
| v0.31.0 | + this PR | |
|---|---|---|
| neither field set, compared with v0.31.0 | identical text and usage for all 16 clips, full and cut |
|
language set: language X<asr_text> header in the realtime output |
16/16 | 0/16 |
prompt tokens per segment with language / prompt / both |
unchanged (70–74 on the cuts) | +3 / +16 / +19 |
realtime == REST on the cuts (header stripped), language / prompt / both |
16 / 15 / 15 of 16 | 16 / 16 / 16 of 16 |
session.update with language: "xx" |
ignored | error event, code invalid_language |
later generations on one connection, each after its own session.update |
the new language applies; an update without language clears it |
|
two concurrent sessions, en and zh with language set |
each uses its own language | |
test_qwen3_asr_session_language_and_prompt |
fails (header present) | passes |
With #56218 on top, the single-segment results are the same as with this PR alone. I didn't try to reproduce your recall numbers. As a quick check, I gave each full-length English clip a prompt listing its words of 7+ letters, 33 words in total.
- On v0.31.0 and with this PR alone, every clip longer than one segment repeats earlier segment text when no prompt is set (#51374). The count doesn't move: 27 of 33 with or without the prompt.
- With both PRs, realtime got 32 with the prompt and 30 without; REST got 32 either way. The two-word gain is in one clip, so I'd only call it a sanity check.
Voxtral (mistralai/Voxtral-Mini-4B-Realtime-2602, Mistral format):
- The four Voxtral tests in
test_realtime_validation.pypass here on 0.31.0 with and without this PR. I didn't try main, so I can't say where your timeouts come from. - Realtime output is unchanged by this PR. Setting
language(enorfr),prompt, or both gives the same text as setting neither on the English clips: 7 of 8, plus one 4.8 s clip that comes back empty in every case, with or without the PR. With this PR,xxgetsinvalid_language. - Voxtral's REST path passes
languageinto its transcription request, but realtimebuffer_realtime_audiodrops it. So on Voxtral asession.updatewithlanguageis validated and then has no effect. That's a bit different from "the same way its REST path ignoresprompt" in the description. Maybe the docs row could name Voxtral, or the server could log it once. Not blocking.
LGTM.
Qwen3-ASR applies them; Voxtral Realtime accepts them but does not use them. Each session.update replaces both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tamer Kanak <tamer.kanak@mankyazilim.com.tr>
|
Thanks a lot for the thorough independent testing, @twu3202! Good catch on Voxtral: its REST path does pass |
Overview
/v1/realtimedropslanguageandpromptfromsession.update, so Qwen3-ASR realtime always runs with a fixed prompt, while/v1/audio/transcriptionsapplies both. This PR forwards them to the model and builds the Qwen3-ASR realtime prompt with the sameget_generation_prompt()as the REST path.Fixes #61026.
Claims
session.updateaccepts optionallanguageandprompt.languageis checked withvalidate_language()like on REST; an invalid value gets aninvalid_languageerror event. The values apply from the nextinput_audio_buffer.commit.get_generation_prompt(). For single-segment audio, realtime and REST now produce the same prompt token ids and, in our runs, identical transcripts (1093/1093) for the samelanguage/prompt.language). The docs row says so.Validation
Test.
test_qwen3_asr_session_language_and_promptintests/entrypoints/speech_to_text/realtime/test_realtime_validation.py. It usesQwen/Qwen3-ASR-0.6B, which the STT CI job already uses. On main it fails (language English<asr_text>header despitelanguage="en"); with this PR it passes. In the same file, the watermarking unit test and the Voxtral invalid-model and commit-without-session tests pass with and without this PR.test_multi_chunk_streamingandtest_empty_commit_does_not_crash_engine(Voxtral) time out on my machine (RTX PRO 6000 Blackwell) identically on unpatched main, so they could not verify this change locally.Prompt tokens. I called
Qwen3ASRRealtimeGeneration.buffer_realtime_audio()directly on 12.3 s of audio (three segments):language/prompt, every segment equalscached_encode(tokenizer, "<|im_start|>user\n<|audio_start|><|audio_pad|><|audio_end|><|im_end|>\n<|im_start|>assistant\n"), the old template.language/prompt, every segment equalsQwen3ASRForConditionalGeneration.get_generation_prompt()for the same audio and params.promptare stripped exactly as on REST.Accuracy. Setup:
Qwen/Qwen3-ASR-1.7B,vllm serve ... --hf-overrides '{"architectures": ["Qwen3ASRRealtimeGeneration"]}' --enforce-eager.language="en".session.updatelanguage,promptlanguage+promptvspromptprompt/language+promptProtocol checks against a live server (all pass):
languageis rejected atsession.update. A follow-up commit still requires a valid update, and a valid update then works.language="en<|im_end|>"is rejected on realtime (and with 400 on REST).promptis rejected.prompt/languagebetween utterances on one connection takes effect per utterance. Omitting them restores the default prompt.max_model_lenreturns an error event (400 on REST), and the server stays healthy.Also checked:
language/promptinsession.update; realtime finals now match REST.pre-commit runpasses on all changed files.Details
Root cause. The fields were lost at three places:
RealtimeConnection.handle_eventonly readmodel(theSessionUpdatemodel inprotocol.pyexisted but was unused).SupportsRealtime.buffer_realtime_audio()had no way to receive per-session options.Qwen3ASRRealtimeGeneration.buffer_realtime_audio()hard-coded<|im_start|>user\n{audio}<|im_end|>\n<|im_start|>assistant\n.Design:
session.updateis parsed withSessionUpdate, which gainslanguageandpromptwith the same meaning asTranscriptionRequest. The flat fields match the existing flatmodelfield.buffer_realtime_audio()gainslanguageandrequest_promptkeyword arguments with defaults, named afterSpeechToTextParams. Both in-tree implementations are updated.get_generation_prompt()instead of keeping a second copy of the template. The system turn, thelanguage X<asr_text>prefix and_sanitize_transcription_user_text()therefore come from the same code as REST, and the no-prompt case stays byte-identical.languageis validated before it can reach the prompt, as on REST. Without that check, a language string would be inserted into the assistant prefix unsanitized.Multi-segment audio. Every 5 s segment is a complete single-turn prompt, so the context and language prefix are part of every segment, as REST repeats them for every 30 s chunk.
/v1/realtimesecond utterance copies previous transcription under KV reuse #51374, [Bugfix][Model] Don't carry realtime context into single-turn prompt models #56218). Main still carries earlier segments into the next one, and that copying happens with or without a prompt. This PR merges cleanly with [Bugfix][Model] Don't carry realtime context into single-turn prompt models #56218, which removes the carry-over. With both applied, on 299 test-clean utterances of 5–15 s, every segment keeps the prompt and rare-word recall goes from 88.5 % to 93.7 %.max_model_lensooner. Sessions that outgrowmax_model_lenalready misbehave on main with or without a prompt; on this machine an unprompted ~400 s session hangs on unpatched main.Limitation (not changed here). The fixed 5 s segmentation can leave very short tail segments. For example, a 5.03 s utterance becomes 5 s + 30 ms; a 10 ms tail makes the HF processor fail even without a prompt. On such near-silent input, Qwen3-ASR with a forced
languageand a keyword-listprompttends to echo the prompt. REST does exactly the same for a near-silent clip with the same parameters, so this is model behaviour. Realtime just produces such clips more often. Handling short tails is a separate change.Not a duplicate:
language X<asr_text>header from realtime deltas) and [Bugfix][Model] Don't carry realtime context into single-turn prompt models #56218 (carry-over) are independent and compose with this change.AI assistance (Claude Code) was used to investigate, implement and validate this change. I reviewed every changed line; the tests and evaluations above were run on our server (RTX PRO 6000 Blackwell).
Pull Request Checklist
I used vLLM's
/pr-checklistskill. (Mandatory for agents, optional for humans).AI assistance was used during the creation of this PR.
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.
🤖 Generated with Claude Code