feat(model): add Confucius4-R2T2 real-time streaming ASR (r2t2_asr) - #604
Open
davidxifeng wants to merge 1 commit into
Open
davidxifeng wants to merge 1 commit into
davidxifeng wants to merge 1 commit into
Conversation
Port NetEase Youdao Confucius4-R2T2 (a Qwen3-ASR-1.7B fine-tune with Longest
Stable Prefix decoding) as a community model family.
Runtime
* src/community_models/r2t2_asr + include/engine/community_models/r2t2_asr:
assets, Whisper log-mel frontend, windowed audio tower, LSP streaming state
machine, and the R2T2 text pipeline (punctuation-by-context, repetition
repair, language-tag parsing, "|" truncation).
* Schema-v1 spec-backed loader: model_specs/r2t2_asr.json is the single source
of truth for metadata, capabilities, options and packages, and the loader
factory lives next to the session, so the family ships no loader.{h,cpp}.
* The thinker is a thin adapter over the shared
runtime::GreedyQwenDecoderRuntime (graph lifetimes, audio-embedding
injection, static KV cache); long audio uses engine::audio::plan_audio_chunks
and the tower reuses the shared modules plus
core::ensure_backend_addressable_layout. No framework files are modified.
* Offline and append-only streaming modes. Committed deltas follow the upstream
WebSocket integrator contract (slice by code-point length, authoritative
final transcript); chunk sizes 80-2000 ms via r2t2_asr.chunk_size_ms.
* Language accepts ISO-639 codes (zh/en/ja/...) or canonical prompt names
(Chinese/English/...); Auto detects. Checkpoints below Q8_0 are rejected at
load time with an actionable error.
Packaging and UI
* model_specs/r2t2_asr.json (schema v1, status community) with the HF
safetensors package plus published Q8_0 and F16 GGUF packages
(davidxifeng/Confucius4-R2T2-gguf). Each GGUF embeds its sidecars and the
schema-v1 option contract, so one file is a fully standalone package.
* webui: catalog entry, r2t2_asr session controls, GGUF install choices, live
microphone transcription, and the language dropdown.
* docs/community_models/r2t2.md, the ASR model table, app/server/example.json.
Verification (macOS MPS reference from the upstream repository)
* tests/r2t2_asr: MPS golden generator, per-chunk comparison harness, and a
repo-native offline+streaming smoke test.
* Chinese reference clip: offline text, committed delta stream and final
transcript are exact, 21/21 committed prefixes identical (auto and forced
language).
* English clip: offline, committed stream and final transcript exact; two
internal prefixes differ by one token, which the reference itself reproduces
between its own fp16 and bf16 runs.
* Q8_0 and F16 GGUF reproduce the same transcripts; 48 kHz stereo input goes
through the shared mono conversion/resampling helper with identical results;
server offline, SSE streaming and live-PCM paths verified; model unload
returns RSS from 6.77 GiB to 0.20 GiB.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Port Confucius4-R2T2 (NetEase Youdao,
netease-youdao/Confucius4-R2T2) as a community model family.R2T2 is a true streaming ASR model — a Qwen3-ASR-1.7B fine-tune that keeps the same audio tower and
thinker and adds Longest Stable Prefix (LSP) decoding: every chunk re-decodes the accumulated
audio with the already-recognized text as a continuation prompt, and only the stable prefix is
committed. Emitted text is never revised, which is what live captioning and downstream agents need.
Why a separate family instead of extending
qwen3_asrqwen3_asrstreams by decoding independent windows and concatenating results — every window isself-contained and the text can be re-derived. R2T2's contract is the opposite: one stateful
accumulated-audio session with token-level prefix rollback and an append-only commit rule. Sharing
the family would have meant two different streaming state machines behind one family name, so the
port keeps its own session while reusing framework infrastructure (below).
What's in the PR
Runtime —
src/community_models/r2t2_asr/,include/engine/community_models/r2t2_asr/128-bin log-mel frontend, windowed audio tower, tokenizer.
session.cpp: offline transcription and the LSP streaming state machine (accumulate audio,prefix rollback with a U+FFFD-safe loop, stable-prefix extraction,
finish_streamflush).text_postprocess.cpp: punctuation-by-context normalization, repetition repair,language X<asr_text>parsing, Chinese spacing,|truncation, and ISO-639 → canonicallanguage-name mapping.
Spec and layout —
model_specs/r2t2_asr.jsonis schema v1 (status: community), so the familyuses the framework's spec-backed loader: metadata, capabilities, options and packages are
derived from the spec, the loader factory (
make_r2t2_asr_loader) lives next to the session, andthere is no per-model
loader.{h,cpp}.Packaging — published self-contained GGUFs at
davidxifeng/Confucius4-R2T2-gguf, wiredinto the spec as
r2t2_asr_q8_0(default) andr2t2_asr_f16, plus the upstream safetensors package.Each GGUF embeds its sidecars and the schema-v1 option contract, so option validation comes from
the file itself (
audio.cppemits a[warning][model_spec]and falls back to the installed runtimecontract when a package still embeds a legacy spec; these do not).
UI — catalog entry (
models/Confucius4-R2T2-GGUF/r2t2-q8_0.gguf, install choices from the spec),r2t2_asrsession controls inmodel_params.json, live microphone transcription (R2T2 is in thelive-ASR family set), and a language dropdown covering the model's 30 languages.
Docs —
docs/community_models/r2t2.md(architecture, streaming contract, options, GGUF andverification recipe) and the ASR model table in
docs/asr.md.Reuse instead of duplication (issue #54 guidance)
runtime::GreedyQwenDecoderRuntime(graph lifetimes, audio-embedding injection via set_rows, static KV cache, greedy sampling)engine::audio::plan_audio_chunks+slice_audio_buffer(no hand-rolled slicing)modules::Conv2dModule/GeluModule/LinearModule/LayerNormModule/ScaledDotProductAttentionModule/TransposeModule,core::ensure_backend_addressable_layout,core::reshape_tensorsrc/framework/,include/engine/framework/, orexternal/Verification
Reference: the official implementation in the upstream repository (PyTorch + MPS,
R2T2ASRModel). Goldens are produced bytests/r2t2_asr/make_golden.pyand compared withtests/r2t2_asr/compare.py, which checks the offline transcript, the committed delta stream(the upstream WebSocket integrator's slice-by-length rule), every per-chunk committed prefix, and the
final transcript. Repo-native smoke test:
test_r2t2_asr_transcription(skips with 125 when weightsare absent).
model.safetensorsbf16 — Chinese clip, auto languagemodel.safetensorsbf16 — same clip,--language Chineser2t2-q8_0.ggufr2t2-f16.ggufassets/resources/sample_16k.wav)Everything a client observes is identical to the reference on every clip. On the 14 s English clip
two internal prefixes differ by one token of a trailing numeric span, which is reference-side greedy
sensitivity, not port logic: the difference is present in the raw decoded text before any R2T2
post-processing, the reference disagrees with itself at the same order of magnitude between its
own fp16 and bf16 runs (chunks 13 and 37), and it never reaches the wire because
finish_streampublishes no delta.
Other checks:
identically in offline and streaming mode (shared conversion/resampling helper).
POST /v1/audio/transcriptions(offline),stream=true(SSE deltas +transcript.text.done), and/v1/audio/transcriptions/live(raw chunked PCM) all return theexpected text; Q8_0 offline RTF ≈ 0.13 on an M3 (≈ 8× real time) with 320 ms streaming chunks.
0.20 GiB after
POST /v1/tasks/unload_all_models, so weights and graphs are session-owned andunload actually releases them.
.gguf(renamed, alone in an isolated directory, nomodel_specs/, CWD outside the repository) works for CLI offline, CLI streaming, and the server.Hub-reported SHA-256 matches the local files:
r2t2-q8_0.gguf— 2 477 512 032 B,433abb3de9dd42d0093b18835d882c7706a77bb2cacb626c44a515cc3afe2a9fr2t2-f16.gguf— 4 092 155 232 B,23b73f7739e6eca71fabb38e9ecc394f3c12bc681af7ca2c3d5f9d6d8ccdaa76Reproduce:
Options
Session options (local names in the spec, public as
r2t2_asr.<name>):chunk_size_ms(80–2000,default 320),
unfixed_chunk_num(2),unfixed_token_num(5),rollback_punctuation(false),max_tokens(32, streaming decode budget),audio_encoder_weight_type,thinker_weight_type(
weight_typealias), and the graph-arena/weight-context MB knobs. Request options:language(ISO-639 code or canonical name,
Autodetects) andmax_tokens(offline budget). Weights belowQ8_0 are rejected at load with a clear message, because this graph's kernels are not validated below
Q8_0 (a Q4_K package otherwise decodes to empty text).
Known limitations
audio). The upstream server segments utterances with VAD; the rolling-window variant
(
_no_reset: keep 16 s, drop the oldest 8 s and its text) is not ported yet. Both are documented.languagemetadata-fragment artifact (a rollback canleave a partial
languagefragment in the stable prefix). It is reproduced deliberately forfidelity; the authoritative transcript is the final result /
transcript.text.done.Checklist (CONTRIBUTING / issue #54)
src/community_models,status: community), schema-v1 spec, no per-model loadermax_tokens,*_ms,*_secconventions)