Skip to main content

ASR Concurrent Stream Server

Concurrent streaming gRPC server and client for Qwen3-ASR with vLLM continuous batching.

Installation

pip install asr-concurrent-stream==1.0.10

For client-side audio loading and VAD segmentation:

pip install asr-concurrent-stream[client]

For development (regenerating protobuf code):

pip install asr-concurrent-stream[dev]

Note: The server requires vllm and transformers to run the Qwen3-ASR model. The model is loaded directly via vLLM's AsyncLLMEngine — no separate qwen-asr package needed.

Quick Start

Start the server

asr-concurrent-server serve

Recommended settings:

asr-concurrent-server serve \
    --model "Qwen/Qwen3-ASR-1.7B" \
    --port 8001 \
    --punctuate \
    --context-before-sec 8.0 \
    --chunk-size-sec 1.0 \
    --context-after-sec 0.5 \
    --gpu-memory-utilization 0.30 \
    --max-model-len 3072 \
    --max-num-batched-tokens 3072 \
    --max-num-seqs 16 \
    --kv-cache-memory 0 \
    --max-concurrent-streams 50 \
    --health-port 8080
Argument Default Description
--model Qwen/Qwen3-ASR-1.7B Model path or HuggingFace ID
--port 8000 gRPC server port
--punctuate false Enable punctuation in transcription output
--context-before-sec 8.0 Past context window (seconds)
--chunk-size-sec 1.0 Audio chunk size (seconds)
--context-after-sec 0.5 Future context window (seconds)
--gpu-memory-utilization 0.30 GPU memory fraction
--max-model-len 4096 Maximum model sequence length
--max-num-batched-tokens 2048 Max tokens per vLLM batch
--max-num-seqs 16 Max concurrent sequences in vLLM
--kv-cache-memory 0 KV cache memory pool size in MiB, 0 for auto
--max-concurrent-streams 15 Maximum concurrent streams (excess queued)
--health-port 8080 HTTP health check port

Environment variables (ASR_PORT, ASR_MODEL_PATH, ASR_CONTEXT_BEFORE_SEC, ASR_CHUNK_SIZE_SEC, ASR_CONTEXT_AFTER_SEC, etc.) are also supported as fallback.

Run the microphone client

pip install sounddevice
python client/mic_example.py --host localhost --port 8001 --language Italian --silence-ms 200 --vad-threshold 0.5 --pad-ms 150
Argument Default Description
--host localhost gRPC server host
--port 8001 gRPC server port
--language Italian Transcription language
--silence-ms 200 VAD silence threshold (ms)
--vad-threshold 0.5 VAD speech probability threshold (0.0-1.0)
--pad-ms 150 Audio padding before speech start (ms)

Importable Event-Based Client

client/realtime_asr_client.py provides a source-agnostic, event-driven ASR client:

import asyncio
from client.realtime_asr_client import RealtimeASRClient

async def main():
    client = RealtimeASRClient(host="localhost", port=8001, language="Italian")

    client.on_partial = lambda text: print(f"partial: {text}")
    client.on_final = lambda text, server_ms: print(f"final: {text}")

    await client.start()

    # Feed audio from any source (mic, file, websocket, etc.)
    # Raw int16 mono PCM bytes at 16 kHz:
    client.feed_audio(raw_int16_bytes)

    await client.stop()

asyncio.run(main())

Events:

  • on_partial(text) — called as partial transcripts arrive from the server
  • on_final(text, server_ms) — called when VAD detects end of speech (includes server latency in ms)

Parameters:

Argument Default Description
host localhost gRPC server host
port 8001 gRPC server port
language Italian Transcription language
silence_duration_ms 200 Minimum silence (ms) for VAD segmentation
vad_threshold 0.5 VAD speech probability threshold (0.0-1.0)
pad_ms 150 Audio padding before speech start (ms)

Architecture

vLLM Continuous Batching

The server uses vLLM's AsyncLLMEngine for GPU-level continuous batching. Each stream has its own worker coroutine that submits inference requests to the shared async engine. vLLM's scheduler dynamically batches prefill and decode across all active streams.

Client gRPC bidir stream
  → Per-stream asyncio.Queue + worker
    → AsyncLLMEngine.generate() (shared across streams)
      → vLLM scheduler batches at GPU level
        → Results → per-stream queue → gRPC response yield

Sliding Window ASR

Instead of re-encoding the full accumulated audio for each chunk (quadratic cost), the server uses a fixed sliding window:

  • Past context: 8.0s before current chunk
  • Present: 1.0s current chunk
  • Future context: 0.5s lookahead from buffer

This bounds prefill cost to ~9.5 seconds of audio regardless of total utterance length. The prefix (previous transcription) is capped to 30 tokens to match the audio window and prevent model confusion.

Stream Queuing

When MAX_CONCURRENT_STREAMS is reached, new streams are queued instead of rejected. As soon as one stream finishes, the next queued stream starts automatically.

Performance

Concurrent Streams Last chunk → Final (avg) Wall time
1 60ms —
10 83ms 5.5s
50 171ms 5.7s

Realtime Microphone Streaming Client

Quick Start

pip install sounddevice
python client/mic_example.py

Console Client

python client/mic_stream_client.py --host localhost --port 8001

This captures microphone audio via sounddevice, runs Silero VAD with a 200ms silence threshold to detect speech segments, and streams each segment to the gRPC server. Partial transcripts display in real-time; final results print on newline per segment.

Latency Metrics

The client displays three latency metrics on final transcription:

[FINAL][submit=37ms speech=237ms server=74ms] text...
Metric Measures
submit Last audio chunk sent → final result
speech First silence detected → final result (end-to-end)
server Server-side finalize processing time

Dependencies

Package Purpose
grpcio gRPC framework
numpy Audio array handling
protobuf Protocol buffer serialization
vllm vLLM async inference engine with continuous batching
transformers Model processor and tokenizer

Optional client dependencies: soundfile or scipy (for audio loading/resampling), torch (for Silero VAD segmentation), sounddevice (for microphone capture).


Docker image for the Qwen3-ASR concurrent streaming gRPC server.

Image Composition

Layer Detail
Base OS Ubuntu 24.04 LTS
CUDA NVIDIA CUDA 12.8.0 (nvidia/cuda:12.8.0-runtime-ubuntu24.04)
Python 3.12 (deadsnakes PPA), installed into /opt/venv
Model Qwen/Qwen3-ASR-1.7B baked into the image
Server port Container 8002 (map to any host port at runtime)

Installation Sequence

  1. System dependencies — curl, git, wget, build-essential, ffmpeg, libsox-*, libsndfile1, libopus0, libffi-dev.
  2. Python 3.12 — installed via deadsnakes PPA, venv created at /opt/venv, binaries symlinked to /usr/local/bin.
  3. pip — upgraded to latest pip, setuptools, wheel.
  4. vllm — installs vLLM with all required dependencies (transformers, torch, etc.). The server uses vLLM's AsyncLLMEngine directly to load and run the Qwen3-ASR model.
  5. flash-attention — prebuilt wheel for CUDA 12.8 + PyTorch 2.9 + Python 3.12.
  6. Project files — ASR_Concurrent_Stream/ copied to /app/ASR_Concurrent_Stream/.
  7. Model download — Qwen/Qwen3-ASR-1.7B downloaded from HuggingFace to /app/models/Qwen3-ASR-1.7B.
  8. Launcher patch — run_asr_grpc_server.sh is updated:
    • PORT → 8002
    • MODEL_PATH → /app/models/Qwen3-ASR-1.7B
    • QWEN3_ASR_PATH is no longer needed (vLLM loads model directly)

Build & Run

# Build
docker build -t qwen3-asr-server:latest .

# Run (map host port 8002 to container port 8002)
docker run -d \
    --name qwen3-asr-server \
    --gpus all \
    -p 8002:8002 \
    --shm-size=16g \
    --ulimit memlock=-1 \
    --restart unless-stopped \
    qwen3-asr-server:latest

# View logs
docker logs -f qwen3-asr-server

# Stop & remove
docker stop qwen3-asr-server && docker rm qwen3-asr-server

Server Configuration

Hard-coded defaults in server/run_asr_grpc_server.sh:

Parameter Value
Port 8002
Model /app/models/Qwen3-ASR-1.7B
GPU memory utilization 0.35
Max model length 4096
Max concurrent streams 50

Project Structure (in image)

/app/
├── ASR_Concurrent_Stream/
│   ├── proto/              # gRPC protobuf definitions
│   ├── server/
│   │   ├── asr_grpc_server.py
│   │   ├── _launch_server.py
│   │   ├── stream_manager.py
│   │   ├── inference_coordinator.py
│   │   ├── asr_model.py
│   │   ├── asr_processor.py
│   │   ├── asr_utils.py
│   │   └── run_asr_grpc_server.sh
│   └── client/
│       ├── example_client.py
│       ├── mic_stream_client.py
│       ├── mic_example.py
│       └── realtime_asr_client.py
└── models/
    └── Qwen3-ASR-1.7B/      # Pre-downloaded model

Metadata

Release files for asr-concurrent-stream 1.0.10

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for asr-concurrent-stream 1.0.10
File Size Uploaded
asr_concurrent_stream-1.0.10.tar.gz 29.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for asr-concurrent-stream 1.0.10
File Interpreter ABI Platform
asr_concurrent_stream-1.0.10-py3-none-any.whl Python 3 none any Details

Total release size: 65.1 kB

Release files / asr_concurrent_stream-1.0.10.tar.gz

Download URL asr_concurrent_stream-1.0.10.tar.gz
Size 29.0 kB
Tags Source
SHA-256 checksum
How to use checksums
a5f32a5180e51d5d8f7ae70f991e9507d7d0c6346d7370007af8ca4cd33adc24
BLAKE2b-256 checksum
How to use checksums
2c5e3be6f983e5209d5d244f36950c24df561944983feed9f094b25e74d23710
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / asr_concurrent_stream-1.0.10-py3-none-any.whl

Download URL asr_concurrent_stream-1.0.10-py3-none-any.whl
Size 36.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
95c12fe4c6677c541b80d2905d5a0dca81cd3a973393b4f93968e9b53f9e44ae
BLAKE2b-256 checksum
How to use checksums
d8ec7cb3ba6720d2c74cf01b9e22bfd5a6ef587d2533a4a7857f0d6b6ef15e5e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.10 This release

2 release files

1.0.9

2 release files

1.0.8

2 release files

1.0.7

2 release files

1.0.5

2 release files

1.0.4

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page