ARK-ASR-3B#
ARK-ASR-3B (AutoArk-AI, Apache-2.0)
is a multilingual open ASR model served through the OpenAI-compatible
/v1/audio/transcriptions endpoint. It accepts one uploaded audio file per
request and returns text. Architecturally it is a Whisper-style audio tower
(RoPE self-attention) plus an MLP frame-merge adapter feeding a dense Qwen2 LM,
so it runs on the same single-stage batched ASR pipeline as Qwen3-ASR and reuses
SGLang’s native Qwen2 decoder. The checkpoint’s tokenizer and config are loaded
with trust_remote_code=True (the served ServerArgs also sets it), so the
first launch will prompt to execute the checkpoint’s bundled code.
ARK-ASR does not support /v1/audio/translations; that endpoint returns HTTP 400. Use /v1/audio/transcriptions.
Prerequisites#
Install sglang-omni by following Installation, then download the model:
hf download AutoArk-AI/ARK-ASR-3B
Server Configuration#
ARK-ASR runs a single ASR stage on one GPU, in bfloat16 by default.
Async decode is enabled by default for decode batches of at least two requests,
allowing the shared one-step-lookahead path to overlap host-side result
processing with the next GPU decode forward. Use --decode-mode sync to disable
it, or tune the crossover with --async-lookahead-min-batch-size.
Request concurrency and audio-encoder batching are controlled separately:
max_running_requestsdefaults to32and limits requests admitted by the ASR scheduler.encoder_max_batch_sizedefaults to8and limits the number of uncached audio items processed by one encoder forward. Larger cache-miss batches are processed as sequential encoder microbatches, so request concurrency does not directly create an unbounded encoder batch.
sgl-omni serve \
--model-path AutoArk-AI/ARK-ASR-3B \
--port 8000
The encoder activation memory is in addition to the model weights and KV cache. For long clips or high concurrency, leave additional runtime headroom by lowering SGLang’s static memory fraction, for example:
sgl-omni serve \
--model-path AutoArk-AI/ARK-ASR-3B \
--mem-fraction-static 0.75 \
--port 8000
mem_fraction_static controls the SGLang memory budget for model weights and
the KV-cache pool; it does not replace encoder_max_batch_size. The two
settings protect different parts of the serving path. ARK does not override
SGLang’s default static memory fraction; use 0.75 when long clips or high
concurrency still need additional encoder headroom and the reduced KV-cache
capacity is acceptable.
To force synchronous decode while comparing modes, use:
sgl-omni serve \
--model-path AutoArk-AI/ARK-ASR-3B \
--decode-mode sync \
--port 8000
Transcribe Audio#
curl -X POST http://localhost:8000/v1/audio/transcriptions \
-F model=AutoArk-AI/ARK-ASR-3B \
-F file=@tests/data/query_to_cars.wav \
-F language=en \
-F response_format=json
import requests
with open("tests/data/query_to_cars.wav", "rb") as f:
resp = requests.post(
"http://localhost:8000/v1/audio/transcriptions",
data={
"model": "AutoArk-AI/ARK-ASR-3B",
"language": "en",
"response_format": "json",
},
files={"file": ("query_to_cars.wav", f, "audio/wav")},
timeout=300,
)
resp.raise_for_status()
print(resp.json()["text"])
Request Parameters#
Parameter |
Type |
Default |
Description |
|---|---|---|---|
|
file |
required |
Audio file uploaded as multipart form data |
|
string |
server default |
Model identifier |
|
string |
|
Language hint recorded on the request. The transcription instruction is a fixed English prompt ( |
|
string |
|
|
|
float |
|
Sampling temperature. When unset or |
Response Formats#
The three response_format values return different shapes:
text— the raw transcript astext/plain.json—{"text": "...", "usage": {"type": "duration", "seconds": <int>}}.verbose_json— the OpenAI verbose shape, built by the default transcription adapter (whole transcript as a single segment):{ "task": "transcribe", "language": "en", "duration": 12.34, "text": "...", "segments": [{"id": 0, "start": 0.0, "end": 12.34, "text": "..."}], "usage": {"type": "duration", "seconds": 13} }
language and usage are omitted when unknown (exclude_none). If the uploaded
audio’s duration cannot be probed, duration and the segment end time are 0.0.
Audio Handling#
Audio is resampled to 16 kHz before feature extraction.
Features use a 128-bin log-mel front end (stock
WhisperFeatureExtractor).The feature extractor truncates at the 30-second Whisper boundary (
n_samples = 480000); audio longer than 30 s is truncated to its first 30 s. Mel padding is"longest", so short clips do not pay the full 30 s of FFT.The audio-token count fed to the LM is
(mel_frames + 1) // 2 // merge_factorwithmerge_factor = 4(conv2 stride-2 down-sampling, then a 4-frame merge).
dtype#
Default serving dtype is
bfloat16. This is the validated path: the native audio-encoder reimplementation was checked for parity against the referencetransformersimplementation on identical mel inputs.A
float16path is also exposed. The encoder layers clamp the post-residual activations under fp16 (matching the referencemodeling_audio.py) so large activations stay finite; this clamp is a no-op underbfloat16.
Marker-Token Suppression#
The stock checkpoint ships no bad_words_ids, so plain skip_special_tokens=True
decoding can leak non-special added markers (e.g. <tool_call>, <|audio|>) on
adversarial / OOD audio. The request builder defensively suppresses every reserved
marker (all special + <...>-added ids except EOS) at sampling via logit_bias
and strips them on decode. This is a verified no-op on clean speech.
Pre-LM Audio Encoder#
Audio encoding runs before LM admission, not inside the LM forward. The
audio encoder executes at request-build time on a dedicated worker thread and
CUDA stream, and a request is admitted only once its complete LM-ready
embedding is attached (MultimodalDataItem.precomputed_embeddings). Without
this, every admission stalls the running decode batch for the whole encoder
forward on the scheduler thread and the default stream.
Encoded embeddings are cached in a bounded CPU LRU keyed on the audio
fingerprint plus a namespace digest of the encoder pipeline (checkpoint path,
model config including merge_factor, mel front-end fields, dtype, attention
backend). Changing any of those re-keys the cache rather than serving a stale
embedding. Concurrent requests for identical audio are deduplicated
single-flight, so the clip is encoded once.
Request building submits encoder work without waiting for the GPU. The scheduler holds the built LM request outside its waiting queue until that request’s encoder future completes, then performs normal admission on the scheduler thread. A bounded encoder queue applies backpressure before mel tensors can accumulate without limit.
knob |
default |
meaning |
|---|---|---|
|
|
Off falls back to encoding inside the LM forward. |
|
|
Max cached embeddings. |
|
|
Byte budget; LRU evicts past it. |
|
|
Max queued requests drained into one |
|
|
Batch-formation window. |
|
|
Max encoder items waiting behind the active batch. |
How this relates to encoder_max_batch_size#
The two batch knobs act at different levels and both stay in force:
pre_lm_max_batch_sizedecides how many queued requests are handed to oneget_audio_featurecall.encoder_max_batch_sizedecides how that call is executed — it pads and masks the group, then splits it into sequential microbatches to bound encoder activation memory.
Both default to 8, so one drained group is exactly one encoder microbatch.
Raising pre_lm_max_batch_size above encoder_max_batch_size turns a group
into several bounded forwards; it never widens a single forward, so it does
not change peak encoder activation memory.
request_build_max_workers defaults to 2 and
request_build_max_pending to 16. These workers only perform CPU request
construction; encoder concurrency and backpressure are owned by the separate
pre-LM queue.
Benchmarking#
Use benchmarks/eval/benchmark_asr_seedtts.py to sweep ASR concurrency on
SeedTTS reference audio through /v1/audio/transcriptions. Pass
--model-path AutoArk-AI/ARK-ASR-3B; the shared request and metric logic lives in
benchmarks.tasks.asr.
sgl-omni serve --model-path AutoArk-AI/ARK-ASR-3B --port 8000
# Sweep the full SeedTTS EN set (1088 clips) at 1..64 concurrency, 3 repeats:
python -m benchmarks.eval.benchmark_asr_seedtts \
--port 8000 --model-path AutoArk-AI/ARK-ASR-3B \
--concurrencies 1,2,4,8,16,32,64 --repeats 3 --warmup
The script reports corpus WER, throughput, and latency per concurrency level.
Transcription accuracy tracks the official transformers checkpoint on the same
audio.
Known Limitations#
The endpoint accepts one uploaded file per request.
promptis accepted by the HTTP endpoint for OpenAI compatibility, but ARK-ASR currently ignores it (the transcription instruction is fixed).Audio is resampled to 16 kHz and truncated at 30 s before transcription.