Repository navigation
feat(asr): /v1/audio/transcriptions adapter over qwen3_asr (#68 tasks 1–2) #104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
hsliuustc0106
merged 5 commits into
InferMatrixLab:main
from
cacheline999:feat/asr-transcriptions-adapter
Oct 7, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
a50ef56
feat(asr): /v1/audio/transcriptions adapter over mlx-audio qwen3_asr …
cacheline999 f7cda19
fix(asr): flatten detected-language list in verbose_json
cacheline999 63a2683
feat(asr): TTS→ASR round-trip WER gate script (#68 task 3)
cacheline999 c097250
fix(asr): enforce the upload cap before reading (413) and 400 on file…
cacheline999 dfadc68
fix(asr): reject an oversized Content-Length before parsing the multi…
cacheline999 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| #!/usr/bin/env python3 | ||
| """TTS → ASR round-trip gate (#68 task 3): our speech in, our transcript out. | ||
|
|
||
| No external fixtures: each sentence is synthesized by the serving TTS | ||
| checkpoint, transcribed by the serving ASR checkpoint, and scored by | ||
| word-error-rate against the input text (lowercased, punctuation stripped, | ||
| digits spelled out in the text so normalization stays trivial). Catches | ||
| catastrophic breakage on either side — a broken decode loop, a resample | ||
| mismatch, a mangled prompt — not fine WER differences between checkpoints. | ||
|
|
||
| Both models stay resident in one process (≈3 GB on the 4-bit defaults); run it | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Docstring says "≈3 GB on the 4-bit defaults" but this PR's scripts/README.md entry says "~5.4 GiB peak" (and the PR body reports 5.40 GiB measured). One of the two is wrong — please align so the round-trip gate's footprint guidance matches what was measured. |
||
| on its own, not next to another heavy battery on a 16 GB machine. | ||
|
|
||
| python scripts/asr_roundtrip.py --voices vivian ryan --max-wer 0.15 | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import os | ||
| import re | ||
| import statistics | ||
| import sys | ||
| import time | ||
|
|
||
| os.environ.setdefault("HF_HUB_OFFLINE", "1") | ||
|
|
||
| SENTENCES = [ | ||
| "The quick brown fox jumps over the lazy dog.", | ||
| "Please turn off the lights before you leave the office tonight.", | ||
| "Apple silicon runs large language models with unified memory.", | ||
| "Peter Piper picked a peck of pickled peppers.", | ||
| "Tomorrow morning we will review the results of the experiment.", | ||
| "The server answers every request in under half a second.", | ||
| ] | ||
|
|
||
|
|
||
| def normalize(text: str) -> list[str]: | ||
| return re.sub(r"[^a-z' ]+", " ", text.lower()).split() | ||
|
|
||
|
|
||
| def wer(reference: str, hypothesis: str) -> float: | ||
| ref, hyp = normalize(reference), normalize(hypothesis) | ||
| prev = list(range(len(hyp) + 1)) | ||
| for i, r in enumerate(ref, 1): | ||
| cur = [i] | ||
| for j, h in enumerate(hyp, 1): | ||
| cur.append(min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (r != h))) | ||
| prev = cur | ||
| return prev[-1] / max(len(ref), 1) | ||
|
|
||
|
|
||
| def main() -> int: | ||
| parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) | ||
| parser.add_argument("--tts-model", default=None) | ||
| parser.add_argument("--asr-model", default=None) | ||
| parser.add_argument("--voices", nargs="+", default=["vivian"]) | ||
| parser.add_argument("--max-wer", type=float, default=0.15, help="fail if the mean WER exceeds this") | ||
| args = parser.parse_args() | ||
|
|
||
| import mlx.core as mx | ||
|
|
||
| from vllm_omni_mlx.asr.config import ASRConfig, load_asr_model | ||
| from vllm_omni_mlx.asr.service import ASRService | ||
| from vllm_omni_mlx.tts.config import TTSConfig, load_tts_model | ||
| from vllm_omni_mlx.tts.service import TTSService | ||
|
|
||
| tts_cfg = TTSConfig(model_ref=args.tts_model) if args.tts_model else TTSConfig() | ||
| asr_cfg = ASRConfig(model_ref=args.asr_model) if args.asr_model else ASRConfig() | ||
| tts = TTSService(load_tts_model(tts_cfg), tts_cfg) | ||
| asr = ASRService(load_asr_model(asr_cfg), asr_cfg) | ||
| print(f"tts={tts.name}\nasr={asr.name}", file=sys.stderr) | ||
|
|
||
| rows = [] | ||
| for voice in args.voices: | ||
| for text in SENTENCES: | ||
| t0 = time.perf_counter() | ||
| wav, _ = tts.speech_bytes(text, voice=voice) | ||
| t1 = time.perf_counter() | ||
| out = asr.transcribe(wav) | ||
| t2 = time.perf_counter() | ||
| score = wer(text, out.text) | ||
| rows.append((voice, text, out.text, score, t1 - t0, t2 - t1, out.duration)) | ||
| print(f"[{voice}] WER {score:.2f} tts {t1 - t0:.1f}s asr {t2 - t1:.2f}s ({out.duration:.1f}s audio)\n" | ||
| f" ref: {text}\n hyp: {out.text}") | ||
| mx.clear_cache() | ||
|
|
||
| scores = [r[3] for r in rows] | ||
| mean = statistics.mean(scores) | ||
| print(f"\nutterances {len(rows)} mean WER {mean:.3f} max {max(scores):.2f} " | ||
| f"exact {sum(s == 0 for s in scores)}/{len(scores)} peak {mx.get_peak_memory() / 2**30:.2f} GiB") | ||
| if mean > args.max_wer: | ||
| print(f"FAIL: mean WER {mean:.3f} > {args.max_wer}", file=sys.stderr) | ||
| return 1 | ||
| print("PASS") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) | ||
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both models-matrix rows (lines 57 and 64, unchanged in this diff) still read
🚧 planned — /v1/audio/transcriptions (#68)while this PR adds the endpoint to the API table and a working example. Worth flipping them in the same pass so the README is not internally inconsistent after merge: Qwen3-ASR decoder-style served (buffered; streaming = #68 task 4), whisper/parakeet/voxtral still planned.Same catch-up applies to
docs/speech.md: since #85 the per-endpoint parameter detail lives there (README is launch-only), but there is no ASR section yet — multipart fields,hotwordslist semantics, ISO-code-or-namelanguage, and thesrt/vtt/stream400s are all only discoverable from the code today. Fine as a fast-follow when task 4 lands if you prefer, but a 10-line section now would match how/v1/audio/speechis documented.