Skip to content

[Frontend][Voxtral] Add opt-in segment timestamps to /v1/realtime - #50783

Open
AndriiPasternak31 wants to merge 1 commit into
vllm-project:mainfrom
AndriiPasternak31:feat/realtime-segment-timestamps
Open

[Frontend][Voxtral] Add opt-in segment timestamps to /v1/realtime#50783
AndriiPasternak31 wants to merge 1 commit into
vllm-project:mainfrom
AndriiPasternak31:feat/realtime-segment-timestamps

Conversation

@AndriiPasternak31

@AndriiPasternak31 AndriiPasternak31 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Purpose

Relates to #39735. /v1/realtime streams transcription text with no timing, so clients doing diarization or subtitling have to guess word timing from message arrival.

Voxtral realtime emits one token per 80 ms frame, so a token's index is an index into the audio and [STREAMING_WORD] marks where an emission group ends. Clients opt in with timestamp_granularities: ["segment"] on session.update and get a segments array of {text, end} on transcription.delta and transcription.done.

end_s(k) = (len(prompt_tokens) - n_pad_frames - n_delay + k) * 0.08

The left pad is audio and the delay tokens are not, so both come out of the prompt length; the formula proposed on the issue subtracts only the delay and lands every timestamp 2.56 s late.

Segments, not words: the model omits the marker for words sharing an emission frame, by design (arXiv 2602.11298 §3.1, §6.2), so word granularity is rejected at negotiation rather than approximated. The marker precedes its own group's subwords, so a boundary closes the segment before it.

Also adds the session.updated ack, and supports_realtime_segment_timestamps for model opt-in (Qwen3-ASR realtime inherits False). Clients that don't opt in see byte-identical payloads — the key is omitted, not null.

Test Plan

.venv/bin/python -m pytest tests/entrypoints/speech_to_text/realtime/test_segment_timestamps.py -v
.venv/bin/python -m pytest tests/entrypoints/speech_to_text/realtime/test_realtime_validation.py -v  # needs GPU
pre-commit run --from-ref origin/main --to-ref HEAD

Test Result

test_segment_timestamps.py: 26 passed in 2.81s. pre-commit: all hooks pass.

test_realtime_validation.py not run locally — it spawns vllm serve and needs a GPU; my machine is an ARM Mac, so it fails with FileNotFoundError: 'vllm' before any assertion. Relying on CI's GPU lane.

No weights, kernels or sampling paths are touched, so existing output is unchanged; the payload-identity claim is asserted directly in the new tests.


Not a duplicate: gh pr list --search "39735 in:body" returns nothing, and no open Voxtral/realtime PR (#44364, #45833, #47615, #45022, #39229, #45697, #49658) touches timestamps or the realtime schema. AI assistance was used; I have reviewed every line, run the tests above, and can defend the design.

@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 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Documentation preview: https://vllm--50783.org.readthedocs.build/en/50783/

@mergify mergify Bot added documentation Improvements or additions to documentation frontend mistral Related to Mistral models labels Aug 2, 2026
@AndriiPasternak31
AndriiPasternak31 force-pushed the feat/realtime-segment-timestamps branch from f620b08 to edc1ab9 Compare August 2, 2026 23:36
The realtime API streams transcription text with no timing information, so
clients doing diarization or subtitling have to guess word timing from
message arrival (vllm-project#39735).

Voxtral realtime emits one token per 80 ms frame, so an output token's index
is an index into the audio, and `[STREAMING_WORD]` marks where an emission
group ends. Clients opt in per connection with
`timestamp_granularities: ["segment"]` on `session.update` and get a
`segments` array of `{text, end}`:

    end_s(k) = (len(prompt_tokens) - n_pad_frames - n_delay + k) * 0.08

The left pad is audio and the delay tokens are not, so both must come out of
the prompt length; subtracting only the delay lands every timestamp 2.56 s
late. The pad is derived from the pad audio rather than from
`n_left_pad_tokens`, so a mistral-common change that decoupled them raises
instead of silently shifting every timestamp.

Segments, not words: the model omits the marker for words sharing an
emission frame by design (arXiv 2602.11298 sections 3.1 and 6.2), so `word`
granularity is rejected at negotiation rather than approximated. The marker
precedes the subwords of its own group, so a boundary closes the segment
accumulated since the previous one.

Also adds the `session.updated` acknowledgement, which makes the opt-in
verifiable instead of silent, and `supports_realtime_segment_timestamps` for
model opt-in; Qwen3-ASR realtime inherits `False`. Clients that do not opt in
see byte-identical payloads.

Signed-off-by: Andrii Pasternak <andriipasternak31@gmail.com>
@AndriiPasternak31
AndriiPasternak31 force-pushed the feat/realtime-segment-timestamps branch from edc1ab9 to 7b70cb0 Compare August 2, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation frontend mistral Related to Mistral models

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant