voice_audio_multi/docs/models/parakeet_tdt.md
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

562 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Parakeet-TDT 0.6B v3
Default model-manager downloads use the published GGUF package when available;
the original source/conversion instructions below remain valid for manual use.
FastConformer-TDT ASR port of NVIDIA's [`nvidia/parakeet-tdt-0.6b-v3`](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3)
(0.6B params, 25 European languages with auto language detection). The default
model-manager package is the standalone Q8_0 GGUF. The repository also supports
a Transformers-compatible `model.safetensors` checkpoint rather than a `.nemo`
archive.
## Status
Offline full-context, bounded-window long-form, and buffered-streaming sessions
are implemented. The full-context path is verified end to end on CPU and CUDA
against the real NeMo reference model, both by final transcription and by
numerical comparison at selected intermediate and full-encoder boundaries; see
[Validation](#validation).
Buffered modes deliberately re-encode fixed bidirectional windows and are not
native cache-aware streaming.
| Mode | Context and memory | Result timing |
|---|---|---|
| Offline `full_context` | Whole utterance; quadratic attention grows with duration | One final result |
| Offline `long_form` | Bounded overlapping windows; center regions decoded once | One final result |
| Streaming | Same bounded windows with retained predictor state | Partial snapshots after right-context lookahead, then a final result |
## Architecture
[Underdog Bark 3.0 Pro](underdog_bark3.md) is another English-only Parakeet-TDT
variant. Its converter and model-local adapter preserve the publisher's weight
scaling and 250 ms input prefix; use that guide rather than loading its original
BF16 checkpoint as stock Parakeet.
The same runtime can load the [Phonon-2 English ASR variant](#phonon-2) after
exact reconstruction of its custom five-value container into a dense F32
package. The Phonon package supports English-only offline and streaming ASR; it does
not inherit this base checkpoint's 25-language claims.
```
Frontend: 16kHz -> 128 mel bins, preemphasis=0.97, NeMo per-feature normalization
Encoder: 3-stage Conv2D subsampling (8x) -> 24-layer FastConformer -> 1024-dim
Decoder: 2-layer LSTM predictor + joint network (TDT: 5 duration classes [0..4])
```
## Build and run
```bash
cmake --build build/<preset> --target parakeet_warm_bench
build/<preset>/bin/parakeet_warm_bench \
--model models/parakeet-tdt-0.6b-v3 \
--audio tests/parakeet_tdt/assets/2086-149220-0033.wav \
--backend cpu # or: --backend cuda
```
Equivalent CLI commands:
```bash
# Offline full-context
build/<preset>/bin/audiocpp_cli \
--task asr --family parakeet_tdt \
--model models/parakeet-tdt-0.6b-v3 --backend cpu \
--audio tests/parakeet_tdt/assets/2086-149220-0033.wav
# Offline bounded-window long-form
build/<preset>/bin/audiocpp_cli \
--task asr --family parakeet_tdt \
--model models/parakeet-tdt-0.6b-v3 --backend cpu \
--session-option parakeet_tdt.offline_mode=long_form \
--audio tests/parakeet_tdt/assets/2086-149220-0033.wav
# Buffered streaming from raw mono 16 kHz PCM
ffmpeg -loglevel error -i input.wav -f f32le -ac 1 -ar 16000 - \
| build/<preset>/bin/audiocpp_cli \
--task asr --family parakeet_tdt \
--model models/parakeet-tdt-0.6b-v3 --backend cpu \
--mode streaming --audio - --input-format f32le \
--input-rate 16000 --input-channels 1
```
Install the model with:
```bash
python3 tools/model_manager_v2.py install parakeet_tdt --models-root models
```
This installs the upstream F32 safetensors package. The repository does not
ship a Parakeet weight file; build `audiocpp_gguf` and convert the installed
package to create a standalone GGUF with the model spec, tokenizer, and
configs embedded:
```bash
cmake --build build/<preset> --target audiocpp_gguf
# Original F32 weights
build/<preset>/bin/audiocpp_gguf \
--input models/parakeet-tdt-0.6b-v3/model.safetensors \
--root models/parakeet-tdt-0.6b-v3 \
--family parakeet_tdt \
--model-spec model_specs/parakeet_tdt.json \
--type orig --overwrite \
--output models/parakeet-tdt-0.6b-v3-f32.gguf
# F16 weights (use `--type bf16` for BF16)
build/<preset>/bin/audiocpp_gguf \
--input models/parakeet-tdt-0.6b-v3/model.safetensors \
--root models/parakeet-tdt-0.6b-v3 \
--family parakeet_tdt \
--model-spec model_specs/parakeet_tdt.json \
--type f16 --overwrite \
--output models/parakeet-tdt-0.6b-v3-f16.gguf
# Q8_0 weights
build/<preset>/bin/audiocpp_gguf \
--input models/parakeet-tdt-0.6b-v3/model.safetensors \
--root models/parakeet-tdt-0.6b-v3 \
--family parakeet_tdt \
--model-spec model_specs/parakeet_tdt.json \
--type q8_0 --overwrite \
--output models/parakeet-tdt-0.6b-v3-q8_0.gguf
```
Pass any `.gguf` file directly to `--model`. The tested files are about
2.4 GB for original F32, 1.2 GB for F16 or BF16, and 874 MB for Q8_0. All four
storage variants reproduce the checked-in golden transcription in offline
full-context, bounded-window long-form, and buffered-streaming tests. Q8_0 has
the broader multilingual accuracy tradeoff described under
[Performance](#performance).
GGUF is the package container, not a different execution engine: both the
safetensors and GGUF paths execute through ggml. With a safetensors source,
`parakeet_tdt.matmul_weight_type` converts eligible weights when the session is
created. With a pre-converted GGUF, the default `native` setting uses the
types stored in that file. The performance study below used the safetensors
package plus session-time storage conversion; the GGUF checks validate
standalone loading and output, not a separate GGUF-vs-safetensors speed claim.
## Orukeet r3 checkpoint
[Orukeet r3](https://huggingface.co/oruk/orukeet) (Oruk AI) is a fine-tune of
`parakeet-tdt-0.6b-v3` that replaces half of the encoder's temporal depthwise
filters with fitted, frozen Gabor kernels and continues training on
multilingual and multi-accent data. It keeps the exact architecture,
tensor shapes, preprocessor, and tokenizer of the stock checkpoint — the
fitted kernels are materialized as ordinary convolution weights — so it runs
through this same `parakeet_tdt` loader with no engine changes. It is a
weight variant, not a new model family.
Orukeet publishes a `.nemo` checkpoint rather than a Transformers-compatible
safetensors package, so conversion goes through
`tools/community_models/convert_orukeet.py`, which maps the 651 NeMo tensors
to the audio.cpp layout, reuses the stock sidecars (verified against the
`.nemo` config; the r3 export audit certifies the tokenizer is unchanged),
and packs a standalone GGUF via `audiocpp_gguf`. The script never downloads
anything; point it at a local copy of the pinned r3 file:
```bash
uv run --python 3.12 --with torch --with pyyaml --with safetensors \
--with sentencepiece --with numpy \
tools/community_models/convert_orukeet.py \
--checkpoint path/to/orukeet-v0.1.0.nemo \
--converter build/<preset>/bin/audiocpp_gguf \
--gguf-output models/Orukeet-GGUF/orukeet-q8_0.gguf \
--type q8_0
```
The stock install (`models/parakeet-tdt-0.6b-v3`, from
`model_manager_v2.py install parakeet_tdt`) supplies the reference sidecars.
Source checkpoint SHA-256:
`031c8ddab4845aeced904a7cde8e8aa57993b2e344716cf83a545b079c473b56`.
Validated on CUDA (RTX 4070 Ti SUPER) against the golden clip
(`tests/parakeet_tdt/assets/2086-149220-0033.wav`): all four storage
variants reproduce the checked-in stock transcription byte-identically with
word timestamps — F32 (~210 ms, ~35x realtime), F16 (~141 ms, ~53x),
BF16 (~186 ms, ~40x), Q8_0 (~162 ms, ~46x); tested files are about 2.5 GB
for F32, 1.3 GB for F16 or BF16, and 916 MB for Q8_0. This checks port
correctness on one English clip; Orukeet's vendor-reported multilingual
gains (pooled FLEURS WER 9.85% vs 11.01%) are not independently validated
here, and its final adaptation trained on LibriSpeech test-other, so treat
the vendor's LibriSpeech numbers with caution.
License: Orukeet r3 weights and fitted kernels are CC BY-SA 4.0, retaining
NVIDIA's CC BY 4.0 foundation attribution; redistribution of converted GGUFs
carries the ShareAlike terms. See the upstream
[NOTICE](https://huggingface.co/oruk/orukeet/blob/main/NOTICE.md) and
[weight license](https://huggingface.co/oruk/orukeet/blob/main/LICENSE-WEIGHTS).
The converter records both in the staging `provenance.json`.
## Options
Request options are bare names. Session options use the
`parakeet_tdt.` prefix.
| Option | Scope | Values/default | Meaning |
|---|---|---|---|
| `max_tokens` | request | integer >= 0; `0` | TDT token limit; `0` uses the model-derived limit |
| `keep_language_tags` | request | boolean; `false` | Preserve language-tag tokens in decoded text |
| `return_timestamps` | request | boolean; optional | Accepted for CLI `--words-out` compatibility. Word timestamps remain present when false or omitted. |
| `audio_chunk_mode` | request | `auto`, `fixed`, `vad`, `none`; `auto` | Offline chunking mode; `vad` skips silent spans with Silero VAD |
| `audio_chunk_duration_sec` | request | see description | VAD maximum chunk duration; omitted inherits the session duration. Fixed/long_form windows use the session setting, not this request override. |
| `weight_type` | session | `native`, `f32`, `f16`, `bf16`, `q8_0`; `native` | Fallback storage type for matmul weights |
| `matmul_weight_type` | session | same values; inherits `weight_type` | Encoder and decoder matmul storage type |
| `conv_weight_type` | session | `native`, `f32`, `f16`; `native` | True convolution-weight storage type |
| `perf_mode` | session | `off`, `flash_attention`; `off` | Attention implementation; flash attention was slower on the tested hardware |
| `weight_context_mb` | session | integer >= 1; `3072` | Weight arena size in MiB |
| `encoder_graph_arena_mb` | session | integer >= 1; `1024` | Encoder graph arena size in MiB |
| `decoder_graph_arena_mb` | session | integer >= 1; `256` | Decoder graph arena size in MiB |
| `audio_chunk_duration_sec` | session | float >= 0.001; `2` | Center-window duration for streaming and offline long-form execution; also the default maximum VAD chunk duration. |
| `left_context_sec` | session | float >= 0; `10` | Past context included in each bounded window |
| `right_context_sec` | session | float >= 0; `2` | Future context/lookahead included in each bounded window |
| `streaming_attention_mode` | session | `full_context`; `full_context` | Bidirectional attention within each bounded streaming window |
| `offline_mode` | session | `full_context`, `long_form`, `auto`; `full_context` | Offline scheduling policy |
| `audio_chunk_threshold_sec` | session | float >= 0.001; `30` | `auto` threshold for switching to long-form execution |
| `vad_model_path` | session | path; `assets/framework/models/silero_vad` | Silero VAD model directory for `audio_chunk_mode=vad` |
Word timestamps are built from the decoder's actual nonblank token-emission
frames. SentencePiece fragments and following punctuation are merged into
human-readable words. Each completed word ends at the next word's emission
boundary; the final word ends at the final token's predicted duration boundary,
clamped to the encoded audio.
## Buffered streaming
`RunMode::Streaming` provides bounded-window buffered streaming for callers
that need incremental results. The default window uses a 2-second center
region, 10 seconds of left context, and 2 seconds of right context. Each fixed
window is re-encoded with full bidirectional attention, then only its center
frames are decoded while the TDT predictor state is retained. This is not
cache-aware streaming: right context adds lookahead latency, partial text can
differ from offline full-context output, and recent text remains provisional
until the next center boundary. Use `nemotron_asr` when native cache-aware,
lower-latency ASR is required.
The window is controlled with `parakeet_tdt.audio_chunk_duration_sec`,
`parakeet_tdt.left_context_sec`, and `parakeet_tdt.right_context_sec`.
Streaming currently requires contiguous mono 16 kHz chunks. `finalize()`
flushes a short tail; `reset()` retains the loaded weights and reusable graphs.
## Long-form audio
Offline mode remains full-context by default. Set
`parakeet_tdt.offline_mode=long_form` to process long audio with the same
bounded overlapping-window scheduler, or use `auto` to
switch after `parakeet_tdt.audio_chunk_threshold_sec` (30 seconds by
default). Long-form mode preserves global timestamps and predictor state while
decoding every center region exactly once. Because each region sees bounded
rather than utterance-wide context, its transcript can differ from the default
full-context result.
Offline long-form input is downmixed and resampled to mono 16 kHz before
windowing, using the same preprocessing as full-context mode. Streaming still
requires mono 16 kHz input. Request `audio_chunk_mode=fixed` selects this
bounded-window scheduler even when `offline_mode=full_context`. Set the session
option `parakeet_tdt.audio_chunk_duration_sec` for its center-window duration;
the request option `audio_chunk_duration_sec` only controls VAD chunk duration.
Set `--request-option audio_chunk_mode=vad` to use Silero VAD-planned speech
spans instead of fixed windows. VAD mode uses
`audio_chunk_duration_sec` as the maximum speech chunk duration, falling back to
`parakeet_tdt.audio_chunk_duration_sec` when omitted. `parakeet_tdt.vad_model_path`
locates the bundled Silero VAD model. `auto` keeps the existing Parakeet
`offline_mode` behavior.
## Performance
Reference clip (`2086-149220-0033.wav`, 7.435s) on an Intel i7-9750H (6C/12T)
and a GTX 1650 Max-Q, against the real NeMo model on the same machine:
| implementation | settings | CPU | CUDA |
|---|---|---|---|
| **NeMo / PyTorch** (the Python reference) | as shipped | 857.7 ms (8.7x real-time) | **OOM** — won't load on this 4GB card |
| **audio.cpp** (this port) | untuned: 8 threads, f32 | ~1245 ms (6.0x real-time) | ~169 ms (44.0x real-time) |
| **audio.cpp** (this port) | tuned: 12 threads, `q8_0` | **~715 ms** (**10.4x** real-time) | **~131 ms** (56.8x real-time) |
Read down the CPU column: **untuned, this port loses to PyTorch** (~1245 ms vs
857.7 ms). Tuned, it wins by ~1.2x — and on this GPU the comparison doesn't
exist at all, because the PyTorch reference cannot load the model in 4 GB
while the native path runs it in 2642 MiB. "Untuned" here means the two
defaults, not a deliberately handicapped configuration: `--threads` defaults
to 8, and `parakeet_tdt.matmul_weight_type` defaults to `native`, which for
this checkpoint means f32 (`config.json` declares `dtype: float32`).
Re-measured on the current tree with the drift-cancelling harness (ABBA, 4
passes, median of 5 iterations per run): tuned is **1.72x** default on CPU and
**1.31x** on CUDA. Both defaults reproduce earlier figures closely; the CPU
tuned row previously read ~750 ms, which was measured before the optimization
pass and is now slightly conservative.
```bash
build/<preset>/bin/parakeet_warm_bench \
--model models/parakeet-tdt-0.6b-v3 --audio tests/parakeet_tdt/assets/2086-149220-0033.wav \
--backend cpu --threads 12 \
--session-option parakeet_tdt.matmul_weight_type=q8_0
```
Two settings account for essentially all of the tunable gap:
- **Thread count.** 12 threads is fastest on this 6C/12T CPU — 3.1% over 6,
1.1% over 8. Sweep it on your own hardware, and sweep it *interleaved*
rather than as sequential before/after runs (this machine drifts 20-30%
between cold and thermally saturated, which is enough to invert the answer).
- **Weight precision.** `parakeet_tdt.matmul_weight_type` accepts
`native|f32|f16|bf16|q8_0` (default `native`; `parakeet_tdt.conv_weight_type`
separately accepts `native|f32|f16`). Encoder graph compute is ~93-96% of
wall time, so this is the setting that matters.
Measured over 120 FLEURS clips across 24 languages
([harness](../../tests/parakeet_tdt/multilingual/README.md),
[raw transcripts](../../tests/parakeet_tdt/multilingual/results/README.md)):
| weight type | encode speed | transcript identical to f32 | absolute WER |
|---|---|---|---|
| `native` (f32) | 1.00x | 100% | 0.1338 |
| `f16` | ~1.00x | 99.2% | 0.1336 |
| `bf16` | **1.14x** | 99.2% | 0.1334 |
| `q8_0` | **1.79x** | 91.7% | 0.1331 |
- **`bf16`** is close to free: 1.14x, 99.2% of transcripts byte-identical.
- **`q8_0`** is the real speedup at 1.79x. It changes ~8% of transcripts, but
absolute WER does not move — the churn is mostly formatting (`T Rex` →
`T-Rex`), and where words change it goes both directions. It costs
reproducibility against an f32 baseline, not quality.
- **`f16`** is not a speedup here at all; don't assume fewer bits means faster.
`native` stays the default because it is the only setting that reproduces
exactly. Full methodology, profiling, and the optimizations that were tried
and rejected are in
[docs/reports/parakeet_tdt_performance.md](../reports/parakeet_tdt_performance.md).
## Validation
- **Golden-transcription regression test** (`ctest -R parakeet_golden_transcription_test`,
wired into the normal `ENGINE_BUILD_TESTS` suite, skips cleanly when the model
isn't downloaded): runs full-context and bounded-window long-form decoding
against a checked-in LibriSpeech test-clean clip
(`tests/parakeet_tdt/assets/2086-149220-0033.wav`) and asserts the decoded
text matches the real NeMo model's transcription for that clip exactly:
*"Well, I don't wish to see it any more, observed Phoebe, turning away her eyes.
It is certainly very like the old portrait."*
- **Buffered-streaming regression test**
(`ctest -R parakeet_streaming_transcription_test`): checks partial output,
finalization, reset/reuse, option validation, and the final transcript on the
same fixture.
- **Standalone GGUF path tests:** original F32, F16, BF16, and Q8_0 GGUFs with
embedded specs and sidecars pass the offline, long-form, timestamp, and
buffered-streaming regression paths while using their stored tensor types.
- **Multilingual precision study:** 120 FLEURS clips across the 24 supported
languages covered by FLEURS measure transcript and WER movement for
`native`, F16, BF16, and Q8_0. This is a precision comparison, not a general
benchmark of Parakeet's absolute WER.
- **Numerical parity harness** (`tests/parakeet_tdt/parity/`, manual/pre-release
check, not a CI gate — needs a NeMo install): compares mel features, a single
isolated encoder layer, and the full encoder output directly against real NeMo
activations via forward hooks. See `tests/parakeet_tdt/parity/README.md` for
exact setup and run commands. This is what actually caught the encoder
conv-module bug below — the golden-transcription test alone did not, since
greedy decoding happened to be robust enough to it on that one clip.
Currently passing on both backends: `mel_features` and `layer_0` at cosine
1.000000, `enc_out` at 0.972258 on CPU and CUDA alike.
- Backends tested: CPU and CUDA (GTX 1650 Max-Q), both producing the exact same
transcription.
Two real bugs were found and fixed getting to this state, worth knowing about if
you're touching this code:
1. The frontend was missing NeMo's per-feature mel normalization (subtract the
per-mel-bin mean, divide by the per-mel-bin unbiased std over valid frames).
Without it the encoder saw wildly out-of-distribution input and produced
garbage (~500x too large in magnitude by the end of the conv subsampling
stack).
2. The FastConformer encoder layer's conv module used causal (left-only)
padding, and separately built the depthwise conv with `use_bias=false`,
silently dropping the folded batch-norm bias term. The padding mode is
wrong for this model's full-context (non-streaming) configuration — NeMo's
`CausalConv1D` is only actually causal when constructed with `padding=None`;
this model constructs it with an explicit symmetric `padding=(kernel-1)//2`.
The dropped bias corrupted every layer's output by a per-channel constant
offset; because greedy/argmax decoding is somewhat robust to numerical
drift, this second bug did not visibly break transcription on the one test
clip and was only caught by the numerical parity harness comparing actual
activations against NeMo, not just final decoded text.
## Known limitations
- **Buffered streaming is not native cache-aware streaming.**
`nvidia/parakeet-tdt-0.6b-v3` was trained and exported with
`att_context_style="regular"` and `att_context_size=[-1, -1]` — unlimited,
fully bidirectional attention context, not NeMo's `"chunked_limited"` style
that cache-aware streaming depends on. Calling NeMo's own
`encoder.setup_streaming_params()` on this checkpoint does not error, but
produces a degenerate configuration (~5.8 second chunks, a ~10000-frame
attention cache) that provides essentially none of the latency or bounded-
memory benefit real streaming is for. NVIDIA's own maintainers, asked
directly about streaming with this model on its Hugging Face discussion
page, pointed users at a different, dedicated cache-aware streaming
architecture rather than confirming this checkpoint for the purpose.
The implemented `RunMode::Streaming` therefore uses bounded, overlapping
full-attention windows and retains only the TDT predictor state between
center regions. It bounds graph and live-buffer size and produces partial
results, but re-encodes context, incurs configured right-lookahead latency,
and can differ from utterance-wide offline output. Genuine causal streaming
would need a purpose-trained `att_context_style="chunked_limited"`
checkpoint.
**If you need streaming ASR, this framework already has it**: the
`nemotron_asr` family (`nvidia/nemotron-3.5-asr-streaming-0.6b`) is a
same-size-class NVIDIA checkpoint actually trained cache-aware, with
configurable chunk sizes down to 80ms, and already implements
`IStreamingVoiceTaskSession` in this codebase.
- Direct layer-by-layer numerical parity against NeMo is limited to one
English fixture. The end-to-end multilingual study is broader (120 clips,
24 languages) but shallow at five clips per language and is intended to
compare storage precisions, not certify production WER for every domain.
- The frontend does not implement NeMo's preprocessor `dither` (small random
waveform noise, standard to disable at inference time in most ASR
pipelines) — no observed effect on transcription correctness.
## Phonon-2
> [!IMPORTANT]
> Routing now follows the registered family: `parakeet_tdt`, `phonon2_asr`, or
> `underdog_bark3`. The `variant` field no longer selects Phonon or Bark behavior.
> Regenerate or refresh older Phonon/Bark GGUF specs with the matching family;
> renaming the file or changing `--family` alone does not update embedded metadata.
English-only Phonon-2 uses the `phonon2_asr` family and its own model contract,
sharing Parakeet’s encoder, predictor, joint and backend arithmetic. There is
no packed five-value runtime.
The model-local additions are optional hotwords, rolling live transcription,
and `word_timestamp_mode: "token_duration"`. Other Parakeet packages retain
their defaults. Shared transport support is limited to an optional revisable
transcript snapshot; no scheduler or multislot admission changes are included.
### Convert
Download the official Fermion Research artifact at revision
`7e153e4054c0a10db6d47d6fff5c062d4c77c154`, including the archive, NOTICE and
license files. Parakeet config/tokenizer sidecars use revision
`541d1f99c6b0c3cd0b11a95167540bb8edefd82b`. Install `numpy`, `safetensors` and
`zstandard`; conversion does not require PyTorch or NeMo.
```bash
python tools/community_models/convert_phonon2.py \
--source-dir models/Phonon-2-source \
--reference-dir models/Phonon-2-source/parakeet-reference \
--output-dir models/Phonon-2-Parakeet-F32 \
--converter build/bin/audiocpp_gguf \
--gguf-type mixed-f16 \
--gguf-output models/Phonon-2-Parakeet-GGUF/phonon-2-mixed-f16.gguf
```
The example selects the lossless mixed-F16 package described below. For F32,
BF16 or Q8 derivatives, choose `--gguf-type orig`, `bf16` or `q8_0` and distinct
output paths. F32 expands the published trained checkpoint; it is not a pre-quantization
FP32 checkpoint. The converter verifies the container, retains attribution,
expands 699 inference tensors and omits 24 training-only counters. It compensates
the projection by 1/32 to cancel the normal loader's input-scale fold and adjusts
BatchNorm gamma in the two channels affected by its variance floor. These are
documented conversion transforms, not byte-identical original tensor claims.
The F32 audit checks every transformed tensor and the resulting encoder output.
BF16/Q8 are rounded derivatives. Projection tensors remain F32 to preserve their
scale compensation; Q8 also keeps the duration-sensitive joint head in F32.
The dense packages are approximately 2.51/1.26/0.94 GB, not the original 164 MB
packed package. No public download/package-manager URL is advertised here.
The `phonon2_asr` family registration selects hotwords and rolling streaming.
`variant` is descriptive metadata and does not select an implementation. `word_timestamp_mode` controls timestamp
formatting only. Regenerate unpublished development packages with this converter;
there is no compatibility fallback for earlier Phonon metadata or option names.
`--gguf-type mixed-f16` offers lossless storage of the staged values: tensors
use F16 only when conversion back to F32 is exact. Other tensors, normalization
and compensated projections stay F32. The package is about 1.30 GB. It does
not override backend precision policy; different storage types can select
different native kernels, so exact weights do not guarantee identical duration
decisions. To reproduce the F32 package's runtime weight types,
use existing session options `phonon2_asr.matmul_weight_type=f32` and
`phonon2_asr.conv_weight_type=f32`; this restores its working weight memory.
`--gguf-type mixed-q8` creates `phonon-2-mixed-q8.gguf` (about 1.18 GB).
It keeps feed-forward and pointwise matrices in exact F16, normalization and
non-F16-representable values in F32, and quantizes the remaining matrices to
Q8_0. It is a mixed-precision, rounded derivative, not lossless whole-model
storage. Across the 21 local clips on each of CPU, CUDA and Vulkan, all 63
transcripts match the dense reference, with 60/63 word timestamp matches
versus 51/63 for the previous Q8. One CPU clip acquires a 240 ms end shift;
the sampled improvement does not establish full timestamp parity or corpus
WER equivalence. See the [mixed-storage validation](../reports/phonon2_validation.md#mixed-gguf-storage-follow-up).
The original `q8_0` conversion remains available with its previous policy.
TDT timestamps advance on an 80 ms grid. Small score differences can change
blank/token selection or the predicted duration, producing whole-frame shifts
even when the final transcript matches. See the
[duration investigation](../reports/phonon2_validation.md#duration-diagnostics) for
decoder replays, reference-mask checks and conversion tradeoffs.
Earlier development packages used unscaled projections and special runtime
precision controls. Reconvert them; the loader rejects their obsolete metadata
with an actionable error rather than silently applying incompatible scaling.
### CLI and hotwords
```bash
audiocpp_cli --task asr --family phonon2_asr \
--model models/Phonon-2-Parakeet-GGUF/phonon-2-mixed-f16.gguf \
--backend cuda --device 0 --threads 8 --audio recording.wav \
--request-option 'hotwords=["Ada Lovelace","CUDA"]' \
--request-option hotwords_score=2
```
Use `cpu`, or `vulkan --device 1` on the tested RTX 3090 setup. The device index
depends on the machine. For Windows executable paths add `.exe`.
Hotwords can be a JSON string array, `{ "word": "...", "spoken": ["..."] }`
entries, or comma/newline/semicolon-separated terms. Without separators, whitespace
separates terms. The model uses recognition-context text as a vocabulary fallback;
explicit hotwords, including an empty list, take precedence.
The policy accepts at most 25 distinct terms, strength 0–100 (default 2), 16 KiB
input, 256-byte terms and at most eight spoken alternatives. It does not bias
blank/duration logits or guarantee correction of every named entity. Request
state is cleared on reset/finalization. Stock Parakeet does not opt into it.
### Streaming
Use the same CLI command with `--mode streaming`, or configure a legacy server
model with `family: "parakeet_tdt"`, the new GGUF path, `task: "asr"` and
`mode: "streaming"`. Transcription uploads with `stream=true` and
`POST /v1/audio/transcriptions/live` (mono 16 kHz PCM) use existing transports.
JSON requests and batch multipart uploads can pass `hotwords` and
`hotwords_score` through the existing generic `options` object. Single-file
multipart uploads and live requests use the existing `prompt` field or query
parameter for vocabulary hints. JSON requests use `text` for recognition context.
There are no dedicated top-level hotword fields or live hotword query parameters.
The Phonon policy processes 50 ms energy blocks, allows a first provisional
decode after 350 ms of a voiced phrase, then every 500 ms of new audio, and
commits after 700 ms silence, 30 seconds, or input completion. It re-encodes the
growing phrase; this is not a cache-aware encoder or an offline speedup.
`partial_text_snapshot` and SSE `transcript.text.partial` contain the complete
current hypothesis and may revise previous words. Replace the provisional
display. `partial_text` and `transcript.text.delta` contain committed segments;
their concatenation equals final text. Committed timestamps have absolute
sample offsets. The original Phonon WebSocket protocol is not implemented.
### Validation and limits
See the [current validation report](../reports/phonon2_validation.md) and
[portable evidence](../../tests/parakeet_tdt/phonon2_validation.json) for exact inputs,
backend/storage results, numerical checks, route tests and build commands.
Backend/storage differences are measured rather than hidden by mandatory F32
kernel controls. Exact reference agreement is diagnostic; quantized storage,
provisional text and segmentation boundaries can differ. The sampled comparisons
do not establish corpus-level WER equivalence. RTX 5090, Metal, HIP and MUSA
runtime behavior was not tested locally.
Vulkan state/graph allocation optimizations are reviewed separately and are not
part of this minimal model PR. Raw previous and current evidence remains in
`outputs/phonon2-review-20261010/` and the earlier local Phonon artifact folders.