Context
Decomposes #12, phase D of
TRANSCRIBE_IMPLEMENTATION_PLAN.md.
firered is next after the Qwen stacks because it exercises vad, punctuator, and lid, the
punctuation invariant, and the only native word timing in the tool. It is also the one stack whose
adapter has real work to do: three of its outputs need assembling rather than mapping.
Scope
One stage script for the whole stack, plus the firered adapter.
One process, three or four models
FireRedAsr2System.process(wav, uttid) runs VAD → LID → ASR → Punc internally, and all its models
are loaded before it is called (run_firered.py:395-426, assembled at :435, invoked at :463).
That is the measured configuration and it is kept: splitting into four subprocesses would
reimplement the system's glue, pay four model loads, and invalidate every recorded FireRed figure.
lid_model = None unless lid is requested (:401), which is the one partial-stack case and is
already modelled by selected_by: "requirement:lid".
Peaks, measured: 9.12–9.16 GiB with LID off (139 s and 1800 s fixtures, so duration barely
moves it), 12.26 GiB with LID on. Requesting lid also roughly doubles inference — 162.09 s
against 84.24 s on the 139.284 s probe.
The six AED decode parameters the recorded runs used are declared in roles.asr.config:
beam_size=3, nbest=1, decode_max_len=0, softmax_smoothing=1.25, aed_length_penalty=0.6, eos_penalty=1.0 (run_firered.py:360-370). config is audited provenance, and these change
output.
The word partition is the punctuation invariant
FireRed emits one flat words[] for the whole file plus separate sentences[], and nothing maps
between them. Timestamps cannot do it — word 0 starts at 650 ms inside a sentence starting at
590 ms. The mapping is greedy consumption of the word stream against each sentence's
punctuation-stripped, case-folded text, and it is verified: 1,896 of 1,896 sentences matched,
12,370 of 12,370 words consumed, zero failures across all five recorded FireRed artifacts, with
word bounds strictly monotonic and non-overlapping throughout.
Case-insensitively is not a hedge: FireRedPunc lowercases and then re-capitalizes sentence starts
and standalone i, so 234 characters differ by case across those artifacts. Neither stream is
derivable from the other — sentence text holds the marks and the casing, the word stream holds the
bounds — so both are carried and the sentence text is canonical for reading.
lid_regions[] by grouping, with a constancy check
The LID label lives on each sentence in the raw output but is produced once per VAD region, so
publishing per-sentence variation would fabricate it. Grouping is sound: on the LID-on artifact all
58 of 58 regions contain their sentences under interval containment and 0 regions carry a
non-constant label.
The label is beam-decoded and detokenized (fireredasr2s/fireredlid/lid.py:96-97, commit
4e7d9aa) from a 120-token dictionary — 5 special tokens, 115 labels — which is why it arrives
as en, zh mandarin, zh xinan, zh xiang: an ISO-ish code plus an optional Chinese-dialect
token. It is published verbatim, because the dialect half is the valuable half and mapping it
onto Qwen's Cantonese would be the translation layer this surface exists without.
Absence
lang: null and lang_confidence: 0 appear on every sentence whether or not LID ran, so with LID
off the adapter drops both rather than publishing a zero that reads as a measured confidence.
words carry exactly {start_ms, end_ms, text} — there is no per-word confidence to publish, and
the sentence-level asr_confidence is a different granularity and not requestable in v1.
Out of scope
vibevoice, export. FireRed has no speaker output, so diarization on this stack is the add-on
path phase C already built.
Acceptance
Depends on
#21 — the transport, the orchestrator, and the adapter boundary this stack plugs into.
Context
Decomposes #12, phase D of
TRANSCRIBE_IMPLEMENTATION_PLAN.md.
fireredis next after the Qwen stacks because it exercisesvad,punctuator, andlid, thepunctuation invariant, and the only native word timing in the tool. It is also the one stack whose
adapter has real work to do: three of its outputs need assembling rather than mapping.
Scope
One stage script for the whole stack, plus the
fireredadapter.One process, three or four models
FireRedAsr2System.process(wav, uttid)runs VAD → LID → ASR → Punc internally, and all its modelsare loaded before it is called (
run_firered.py:395-426, assembled at:435, invoked at:463).That is the measured configuration and it is kept: splitting into four subprocesses would
reimplement the system's glue, pay four model loads, and invalidate every recorded FireRed figure.
lid_model = Noneunlesslidis requested (:401), which is the one partial-stack case and isalready modelled by
selected_by: "requirement:lid".Peaks, measured: 9.12–9.16 GiB with LID off (139 s and 1800 s fixtures, so duration barely
moves it), 12.26 GiB with LID on. Requesting
lidalso roughly doubles inference — 162.09 sagainst 84.24 s on the 139.284 s probe.
The six AED decode parameters the recorded runs used are declared in
roles.asr.config:beam_size=3, nbest=1, decode_max_len=0, softmax_smoothing=1.25, aed_length_penalty=0.6, eos_penalty=1.0(run_firered.py:360-370).configis audited provenance, and these changeoutput.
The word partition is the punctuation invariant
FireRed emits one flat
words[]for the whole file plus separatesentences[], and nothing mapsbetween them. Timestamps cannot do it — word 0 starts at 650 ms inside a sentence starting at
590 ms. The mapping is greedy consumption of the word stream against each sentence's
punctuation-stripped, case-folded text, and it is verified: 1,896 of 1,896 sentences matched,
12,370 of 12,370 words consumed, zero failures across all five recorded FireRed artifacts, with
word bounds strictly monotonic and non-overlapping throughout.
Case-insensitively is not a hedge: FireRedPunc lowercases and then re-capitalizes sentence starts
and standalone
i, so 234 characters differ by case across those artifacts. Neither stream isderivable from the other — sentence text holds the marks and the casing, the word stream holds the
bounds — so both are carried and the sentence text is canonical for reading.
lid_regions[]by grouping, with a constancy checkThe LID label lives on each sentence in the raw output but is produced once per VAD region, so
publishing per-sentence variation would fabricate it. Grouping is sound: on the LID-on artifact all
58 of 58 regions contain their sentences under interval containment and 0 regions carry a
non-constant label.
The label is beam-decoded and detokenized (
fireredasr2s/fireredlid/lid.py:96-97, commit4e7d9aa) from a 120-token dictionary — 5 special tokens, 115 labels — which is why it arrivesas
en,zh mandarin,zh xinan,zh xiang: an ISO-ish code plus an optional Chinese-dialecttoken. It is published verbatim, because the dialect half is the valuable half and mapping it
onto Qwen's
Cantonesewould be the translation layer this surface exists without.Absence
lang: nullandlang_confidence: 0appear on every sentence whether or not LID ran, so with LIDoff the adapter drops both rather than publishing a zero that reads as a measured confidence.
wordscarry exactly{start_ms, end_ms, text}— there is no per-word confidence to publish, andthe sentence-level
asr_confidenceis a different granularity and not requestable in v1.Out of scope
vibevoice,export. FireRed has no speaker output, sodiarizationon this stack is the add-onpath phase C already built.
Acceptance
a word is dropped from the stream — constructed, not assumed.
lid_regions[]bounds equalvad_regions[]bounds, and a region carrying two distinctlabels fails the suite.
langorlang_confidencekey exists anywhere in the document; with LIDon,
lid_regions[].languageis the backend's own label.wordsentry carries a confidence field, on any stack.segment_timestampsandword_timestampsresolvenativewith no add-on package, and theplan resolves
punctuatorbyfloor:punctuated_sentence_segmented_text.uv run --extra dev pytestgreen.Depends on
#21 — the transport, the orchestrator, and the adapter boundary this stack plugs into.