Uh oh!
There was an error while loading. Please reload this page.
asr: run on pre-Ampere GPUs with an fp16 checkpoint alongside bf16 - #68
Merged
Conversation
bf16 needs Ampere. A GTX 1660 or RTX 20xx (sm_75) has no bf16 support at all, so blurtd refused to start on hardware that is otherwise perfectly capable of running this model — the image's torch already targets sm_75. PARAKEET_DTYPE=fp16 now selects a float16 checkpoint instead. bf16 stays the default and its load path is unchanged; the two checkpoints live side by side in ~/.cache/blurt/ and are published together in one HF repo (lightware-dev/parakeet-tdt-0.6b-v3, env PARAKEET_REPO), told apart by filename. This replaces the two per-precision repos, which are deleted. fp16 costs nothing measurable. Over 208 clips / 25.8 min (LibriSpeech test-clean, the same utterances under white noise at 10 and 5 dB SNR, babble at 5 dB and near-clipping gain, plus synthetic speech), against an fp32 reference of the same weights: WER all WER real weights RTF (5090) fp32 2.96% 2.40% 2.55 GB 0.0114 bf16 2.99% 2.40% 1.31 GB 0.0133 fp16 2.96% 2.40% 1.31 GB 0.0118 bf16 and fp16 agree exactly on 207 of 208 transcripts; the WER difference is +0.03 pp (95% bootstrap CI [0.00, +0.08], p=0.74) and zero on real speech. fp16 is in fact the closer of the two to fp32 numerically — mean relative L2 of the encoder output 0.005 (cosine >= 0.995) against bf16's 0.044 (cosine >= 0.835), which is what fp16's 3 extra mantissa bits buy. Its narrower exponent range never bit: no non-finite activation appeared anywhere, including on the deliberately near-clipping slice. Adds scripts/make_eval_corpus.py and scripts/compare_precision.py to reproduce that, and a GPU-free precision suite to scripts/test_protocol.py covering alias resolution, per-precision cache paths and env precedence.
The rest of the precision suite is deliberately torch-free so it runs in CI, but the missing-checkpoint check imported torch unguarded to decide which error load() should stop at. CI has no torch, so the suite died there. Probe with find_spec and skip that one check instead, matching how the openai and interop suites handle their optional dependencies.
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 freeto 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.
bf16needs Ampere. A GTX 1660 or RTX 20xx (sm_75) has no bf16 support at all, soblurtdrefused to start on hardware that is otherwise perfectly capable of runningthis model — the image's torch already targets sm_75.
PARAKEET_DTYPE=fp16now selects a float16 checkpoint instead:bf16 stays the default and its load path is unchanged. The two checkpoints live
side by side in
~/.cache/blurt/and are published together in one HF repo,lightware-dev/parakeet-tdt-0.6b-v3(env
PARAKEET_REPO), told apart by filename. That replaces the two per-precisionrepos, which are deleted — so
PARAKEET_BF16_REPOandPARAKEET_FP16_REPOcollapseinto
PARAKEET_REPO.fp16 costs nothing measurable
208 clips / 25.8 min: LibriSpeech
test-clean, the same utterances under white noiseat 10 and 5 dB SNR, babble at 5 dB and near-clipping gain, plus synthetic speech.
Reference is an fp32 run of the same weights through the same pipeline.
bf16 and fp16 return identical transcripts on 207 of 208 clips. The WER difference
is +0.03 pp (95% bootstrap CI [0.00, +0.08], p=0.74) and exactly zero on real speech.
Numerically fp16 is the closer of the two to fp32 — mean relative L2 error of the
encoder output 0.005 (cosine ≥ 0.995) against bf16's 0.044 (cosine ≥ 0.835), which is
what fp16's 3 extra mantissa bits buy. Its narrower exponent range never bit: no
non-finite activation appeared anywhere, including on the deliberately near-clipping
clips.
Measured on an RTX 5090 (sm_120), which supports both formats — the quality figures
carry to sm_75 (IEEE fp16, fp32 accumulation in cuBLAS), the speed figures do not. A
6 GB GTX 1660 has room for the 1.43 GB peak several times over.
What's here
server/asr.py— aPRECISIONStable keyed byPARAKEET_DTYPE;resolve_precisionnormalises the obvious spellings and rejects anything else. The bf16 CUDA+bf16 gate is
unchanged (its message now points at fp16); fp16 requires only CUDA. Loading goes
through
torch.set_default_dtypeeither way, so no fp32 transient is ever materialised.scripts/build_bf16_ckpt.py—--dtype {bf16,fp16}. Each is cast from the upstreamfp32 weights, never bf16 → fp16, which would inherit bf16's coarser mantissa for nothing.
scripts/make_eval_corpus.py,scripts/compare_precision.py— build the corpus andreproduce the table above (WER/CER, paired bootstrap, encoder divergence, RTF, VRAM).
scripts/test_protocol.py— a GPU-freeprecisionsuite: alias resolution, unknowndtype rejected, distinct cache paths, env precedence, actionable missing-checkpoint error.
Verification
test_protocol.pysuites pass.float16/bfloat16, no non-finite values, identical key sets, 627,090,582 params.Uploaded LFS hashes match the local files, and the bf16 file is byte-identical to the
one the old repo served.
/v1/audio/transcriptionson clean,noisy and long clips, and
ws_client_test.pystreaming partials and a final overwss://.Note
The evaluation corpus is English-only, so it says nothing directly about the other 24
languages the base model supports beyond the general expectation that a pure weight cast
behaves the same across them.
One measurement gotcha worth flagging for anyone extending
compare_precision.py: afterNeMo's
transcribe()has run once, calling the model directly returns nondeterministicoutput, and interleaving the two makes
transcribe()itself start returning emptystrings. The harness captures encoder tensors on a never-transcribed model, reloads, then
decodes — and self-checks that the encoder is deterministic before trusting any of it.
blurtdonly ever callstranscribe(), so the server is unaffected.🤖 Generated with Claude Code
https://claude.ai/code/session_01EnL4vH3w19nXYjAUqFT2jF