Frame-Synchronous Dual-Head Modeling for Joint Streaming ASR and Turn State Prediction
Python
97
37 commits
updated Sep 17, 2026
X2 Turn transcribes speech and, every 80 ms, predicts a turn-taking state:
idle, noidle, speaking, turn_end, backchannel, or uncertain.
The fastest way to try it is the hosted online demo (no local GPU).
The in-repo browser Turn Demo needs a GPU and the 4B weights. It does not need an LLM, TTS, or vLLM.
https://github.com/user-attachments/assets/4040eb7a-4f5b-4e25-8ff4-893caeeb0702
To try the model without installing anything, open the online demo.
To run the same Turn Demo on your machine, follow this path only. Save vLLM, the full-duplex stack, and the Python API until the demo has produced the expected result below.
You need
The first run is slow. Creating the environment, downloading the 4B weights, and the first Run scenario click each take several minutes. The web page can appear before the model is on the GPU. That is expected.
git clone https://github.com/X-Square-Robot/X2-Turn.git
cd X2-Turn
conda env create -f environments/environment-transformers.yml
conda activate x2-turn
These packages are not on PyPI. The environment file installs them from this checkout.
If you already have a CUDA PyTorch environment and prefer pip:
python -m pip install -e "./voxtral-realtime[transformers]"
python -m pip install -e "./turn-demo"
The demo can fetch x-square-robot/X2-Turn-4B-0812 on first use. If Hugging Face
hangs, times out, or cannot reach the Hub, download the weights once:
# from the X2-Turn repository root, with x2-turn activated
huggingface-cli download x-square-robot/X2-Turn-4B-0812 \
--local-dir ./models/X2-Turn-4B-0812
Then point MODEL at that folder in the next step.
cd turn-demo
MODEL=x-square-robot/X2-Turn-4B-0812 bash run.sh
If you used the local download:
cd turn-demo
MODEL="$PWD/../models/X2-Turn-4B-0812" bash run.sh
Wait until the log shows Uvicorn running on http://127.0.0.1:7860. The
server listens on localhost and does not load the 4B weights yet.
Open http://localhost:7860.
You do not need a microphone. The first click loads the model onto the GPU
and can take several minutes. Transformers may print attention_mask or
pad_token warnings. Those are harmless.
The bundled clip is about 3.4 seconds of synthetic English. A successful run looks like this:
| Field | Typical value |
|---|---|
| ASR text | hello can you tell me what the weather is like today |
| Frames | about 53 frames of 80 ms |
| Histogram | idle 32, noidle 4, speaking 15, turn_end 2 |
| Timeline | speech, then turn_end, then idle |
The prompt text on the page is Hello, could you tell me what the weather is
like today? The ASR line above is the model output, not a copy of that
prompt. Counts can shift by a frame or two across GPUs and library versions.
If you see a transcript close to that sentence and a turn_end near the end
of the utterance, the install worked.
No server and no vLLM. From the repository root, with x2-turn activated:
import torch
from transformers import AutoProcessor
from voxtral_realtime.transformers import (
infer_asr_turn,
load_mtp_checkpoint,
)
model_id = "x-square-robot/X2-Turn-4B-0812" # or ./models/X2-Turn-4B-0812
processor = AutoProcessor.from_pretrained(model_id)
model = load_mtp_checkpoint(
model_id,
device="cuda",
dtype=torch.bfloat16,
).eval()
result = infer_asr_turn(model, processor, "turn-demo/assets/sample_en.wav")
print("ASR:", result.transcript)
for frame in result.turn_frames:
print(frame.start_ms, frame.end_ms, frame.label, frame.confidence)
Write the same result to JSON:
python voxtral-realtime/integrations/transformers/examples/offline_inference.py \
--model x-square-robot/X2-Turn-4B-0812 \
--audio turn-demo/assets/sample_en.wav \
--output offline_frames.json
The loader does not patch Transformers and does not need trust_remote_code.
Details: voxtral-realtime/integrations/transformers/README.md.
The bundled sample's text, license, and FFmpeg command are in
turn-demo/assets/README.md.
This stack adds a reply LLM and local Qwen3TTS-Streaming, and shows barge-in
during playback. It is a separate setup: patched vLLM, the dialogue app, and a
local Qwen3TTS-Streaming engine. Start from
full-duplex-demo/README.md.
https://github.com/user-attachments/assets/bb71bb57-8f21-4616-8867-2a4b9c6a52b3
Stock vLLM does not emit the custom turn.delta events. Follow the
vLLM integration guide
from the voxtral-realtime/ directory. To replay a WAV through the
production turn controller, use
voxtral-realtime/examples/offline_inference.py
after that runtime is up.
Local services bind to 127.0.0.1 by default. Set BIND_HOST=0.0.0.0 only
when another machine must connect.
voxtral-realtime/ — model wrapper, local
ASR + turn inference, the realtime controller, and the patched vLLM
integration.turn-demo/ — browser demo for raw ASR, 80 ms Turn
states, and the frame-level token / class / probability table.full-duplex-demo/ — full conversational
stack with a reply LLM and local Qwen3TTS-Streaming.environments/ — separate Miniforge
environments so Transformers, patched vLLM, and the dialogue app do not
share one CUDA/Torch tree.Each component keeps its own license and notice. Model weights live on Hugging Face, not in this source tree.
Do not publish local logs, certificates, datasets, external source checkouts, or credentials.
If you find X2-Turn useful in your research, please cite:
@article{fu2026x2turn,
title = {X2-Turn: Frame-Synchronous Dual-Head Modeling for Joint Streaming ASR and Turn State Prediction},
author = {Fu, Kaiqi and Wen, Rime and Lin, Altman and Qin, Shawn and Gan, Roy and Wang, Hao and Wang, Qian},
journal = {arXiv preprint arXiv:2608.10878},
year = {2026},
}
X2 Turn builds on ideas, models, and infrastructure from the open-source speech and machine-learning community. We thank:
See the component NOTICE files and the available THIRD_PARTY_NOTICES.md
documents for detailed attribution and license information.
37 commits
Python
83.3%
JavaScript
7.8%
Shell
4.1%
CSS
3.7%
HTML
1.1%
Frame-Synchronous Dual-Head Modeling for Joint Streaming ASR and Turn State Prediction
Python
97
37 commits
updated Sep 17, 2026
X2 Turn transcribes speech and, every 80 ms, predicts a turn-taking state:
idle, noidle, speaking, turn_end, backchannel, or uncertain.
The fastest way to try it is the hosted online demo (no local GPU).
The in-repo browser Turn Demo needs a GPU and the 4B weights. It does not need an LLM, TTS, or vLLM.
https://github.com/user-attachments/assets/4040eb7a-4f5b-4e25-8ff4-893caeeb0702
To try the model without installing anything, open the online demo.
To run the same Turn Demo on your machine, follow this path only. Save vLLM, the full-duplex stack, and the Python API until the demo has produced the expected result below.
You need
The first run is slow. Creating the environment, downloading the 4B weights, and the first Run scenario click each take several minutes. The web page can appear before the model is on the GPU. That is expected.
git clone https://github.com/X-Square-Robot/X2-Turn.git
cd X2-Turn
conda env create -f environments/environment-transformers.yml
conda activate x2-turn
These packages are not on PyPI. The environment file installs them from this checkout.
If you already have a CUDA PyTorch environment and prefer pip:
python -m pip install -e "./voxtral-realtime[transformers]"
python -m pip install -e "./turn-demo"
The demo can fetch x-square-robot/X2-Turn-4B-0812 on first use. If Hugging Face
hangs, times out, or cannot reach the Hub, download the weights once:
# from the X2-Turn repository root, with x2-turn activated
huggingface-cli download x-square-robot/X2-Turn-4B-0812 \
--local-dir ./models/X2-Turn-4B-0812
Then point MODEL at that folder in the next step.
cd turn-demo
MODEL=x-square-robot/X2-Turn-4B-0812 bash run.sh
If you used the local download:
cd turn-demo
MODEL="$PWD/../models/X2-Turn-4B-0812" bash run.sh
Wait until the log shows Uvicorn running on http://127.0.0.1:7860. The
server listens on localhost and does not load the 4B weights yet.
Open http://localhost:7860.
You do not need a microphone. The first click loads the model onto the GPU
and can take several minutes. Transformers may print attention_mask or
pad_token warnings. Those are harmless.
The bundled clip is about 3.4 seconds of synthetic English. A successful run looks like this:
| Field | Typical value |
|---|---|
| ASR text | hello can you tell me what the weather is like today |
| Frames | about 53 frames of 80 ms |
| Histogram | idle 32, noidle 4, speaking 15, turn_end 2 |
| Timeline | speech, then turn_end, then idle |
The prompt text on the page is Hello, could you tell me what the weather is
like today? The ASR line above is the model output, not a copy of that
prompt. Counts can shift by a frame or two across GPUs and library versions.
If you see a transcript close to that sentence and a turn_end near the end
of the utterance, the install worked.
No server and no vLLM. From the repository root, with x2-turn activated:
import torch
from transformers import AutoProcessor
from voxtral_realtime.transformers import (
infer_asr_turn,
load_mtp_checkpoint,
)
model_id = "x-square-robot/X2-Turn-4B-0812" # or ./models/X2-Turn-4B-0812
processor = AutoProcessor.from_pretrained(model_id)
model = load_mtp_checkpoint(
model_id,
device="cuda",
dtype=torch.bfloat16,
).eval()
result = infer_asr_turn(model, processor, "turn-demo/assets/sample_en.wav")
print("ASR:", result.transcript)
for frame in result.turn_frames:
print(frame.start_ms, frame.end_ms, frame.label, frame.confidence)
Write the same result to JSON:
python voxtral-realtime/integrations/transformers/examples/offline_inference.py \
--model x-square-robot/X2-Turn-4B-0812 \
--audio turn-demo/assets/sample_en.wav \
--output offline_frames.json
The loader does not patch Transformers and does not need trust_remote_code.
Details: voxtral-realtime/integrations/transformers/README.md.
The bundled sample's text, license, and FFmpeg command are in
turn-demo/assets/README.md.
This stack adds a reply LLM and local Qwen3TTS-Streaming, and shows barge-in
during playback. It is a separate setup: patched vLLM, the dialogue app, and a
local Qwen3TTS-Streaming engine. Start from
full-duplex-demo/README.md.
https://github.com/user-attachments/assets/bb71bb57-8f21-4616-8867-2a4b9c6a52b3
Stock vLLM does not emit the custom turn.delta events. Follow the
vLLM integration guide
from the voxtral-realtime/ directory. To replay a WAV through the
production turn controller, use
voxtral-realtime/examples/offline_inference.py
after that runtime is up.
Local services bind to 127.0.0.1 by default. Set BIND_HOST=0.0.0.0 only
when another machine must connect.
voxtral-realtime/ — model wrapper, local
ASR + turn inference, the realtime controller, and the patched vLLM
integration.turn-demo/ — browser demo for raw ASR, 80 ms Turn
states, and the frame-level token / class / probability table.full-duplex-demo/ — full conversational
stack with a reply LLM and local Qwen3TTS-Streaming.environments/ — separate Miniforge
environments so Transformers, patched vLLM, and the dialogue app do not
share one CUDA/Torch tree.Each component keeps its own license and notice. Model weights live on Hugging Face, not in this source tree.
Do not publish local logs, certificates, datasets, external source checkouts, or credentials.
If you find X2-Turn useful in your research, please cite:
@article{fu2026x2turn,
title = {X2-Turn: Frame-Synchronous Dual-Head Modeling for Joint Streaming ASR and Turn State Prediction},
author = {Fu, Kaiqi and Wen, Rime and Lin, Altman and Qin, Shawn and Gan, Roy and Wang, Hao and Wang, Qian},
journal = {arXiv preprint arXiv:2608.10878},
year = {2026},
}
X2 Turn builds on ideas, models, and infrastructure from the open-source speech and machine-learning community. We thank:
See the component NOTICE files and the available THIRD_PARTY_NOTICES.md
documents for detailed attribution and license information.
37 commits
Python
83.3%
JavaScript
7.8%
Shell
4.1%
CSS
3.7%
HTML
1.1%