6.2 KiB
Server engine: batched + VAD faster-whisper Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task (inline, no subagents — coordination plan). Steps use checkbox (
- [ ]) syntax.
Goal: file transcriptions on the server engine run through faster-whisper's
BatchedInferencePipeline with Silero VAD, without breaking segment streaming, cancel, the D-021
heartbeat, or the one-loaded-model cache.
Architecture: the sidecar request gains an optional mode: "file" | "live" (absent ⇒ "file").
"file" → BatchedInferencePipeline(model).transcribe(audio, vad_filter=True, batch_size=8);
"live" → the existing sequential WhisperModel.transcribe (short ≤ 29 s passes gain nothing from
batching; decided from measurements in Task 1). The pipeline wrapper is cached with the model.
Bun side: SidecarTranscribeRequest.mode, createSidecar forwards it, live.ts sends "live".
Tech Stack: Python 3 + faster-whisper 1.2.1 (venv /home/user/venvs/faster-whisper), Bun test.
Spec: docs/specs/001-transcription-quality-and-speed.md Goal 5; docs/plans/001-0-coordination.md.
Global Constraints
- Only
server-py/sidecar.pyis this workstream's core; Bun edits limited to the protocol field. - Heavy jobs (pip, benchmarks,
FW_INTEGRATION=1) underflock /tmp/whisper-heavy.lock. - Decision entry
## D-WS5a — …at the bottom ofdocs/Decisions.md; citeD-WS5ain code. requirements.txtstaysfaster-whisper>=1.1,<2(BatchedInferencePipeline exists since 1.1.0).- No new env vars unless measurement demands one (then README env table).
- Measurements: CPU int8, this machine (16 cores, shared), labelled as such.
Review Focus
- Cancel during a batch — batched segments arrive in bursts (one forward per 8 VAD chunks); a
cancel must still end the run with
done {cancelled: true}and no further segments. Test: stub sidecar "slow" model, abort after first segment (Task 2). - Long silent stretch before the first segment (VAD + feature extraction over the whole file
happen eagerly in
transcribe(), then a full batch decodes) — heartbeat lines must keep flowing. Test: injectable heartbeat interval, fake sidecar that waits, assert blank lines (Task 3). - Silence-only audio — VAD finds no speech: must answer
donewith zero segments, not error. Test: stub "silence" model yields nothing →done(Task 2); real run on tone in FW_INTEGRATION. - Model switch — still one model resident; the batched wrapper must be rebuilt for the new model, not reused. Test: stub reports load count and which model the pipeline wraps (Task 2).
- Old Bun ↔ new sidecar — request without
modemust behave as"file". Test (Task 2).
Task 1: Measure (outside the repo)
Files: /home/user/ws5-bench/bench.py (not committed).
- Venv
/home/user/venvs/faster-whisperwith requirements + jiwer (under lock); confirm API. - large-v3-turbo, both clips:
base(current sidecar),vad,batched(vad+bs8),batched_b1(beam 1). Record wall, WER (repo normalize), first-segment time, max gap. Done 2026-10-29 (/home/user/ws5-bench/turbo-*.jsonl); VAD-parameter follow-up indiag2.out/round3-*.jsonl(threshold 0.2 + pad 800 ms recovers most deletions). - Live-style passes (clip sliced into 6 s pieces):
live_seqvslive_batchedvslive_vad. - medium.en on en_alien:
basevsbatchedif time allows. - Decide: beam size, live mode routing.
Task 2: Sidecar mode routing, tested against a stubbed faster-whisper
Files:
- Create:
tests/fixtures/stub_fw/faster_whisper/__init__.py,tests/fixtures/stub_fw/numpy/__init__.py - Modify:
server-py/sidecar.py,tests/transcribe.test.ts
Interfaces:
-
Produces: sidecar stdin
transcribemessage accepts"mode": "file" | "live"(default"file"). -
Stub:
WhisperModel(name, **kw)counts loads;.transcribe(audio, **kw)yields segments whose text reportsseq+ kwargs;BatchedInferencePipeline(model).transcribe(audio, **kw)yieldsbatched model=<name> loads=<n>+ kwargs. Model"slow"yields 20 segments 0.1 s apart;"silence"yields none. Stub numpy:float32,fromfile(path, dtype)→ bytes. -
Test runs
createSidecar(["python3", "-S", REAL_SIDECAR])withPYTHONPATH=stub_fw(createSidecar inherits env; setprocess.env.PYTHONPATHaround the test or passenv— add optionalenvarg only if needed). -
Failing tests: default/
file→ batched withvad_filter=True batch_size=8;live→ sequential, novad_filter; model switch reloads and pipeline wraps the new model, same model doesn't reload;slow+ abort →cancelled: true, < 20 segments;silence→ done, 0 segments. -
Implement in
sidecar.py; update docstring protocol; cite D-WS5a. -
Green; commit.
Task 3: Bun side — mode field, live sends "live", heartbeat test
Files: src/server/transcribe.ts, src/server/live.ts, tests/fixtures/fake_sidecar.py,
tests/transcribe.test.ts, tests/server-live.test.ts
- Failing tests: fake sidecar "ok" echoes
mode=;createSidecardefault sendsfile, explicitliveforwarded; server-live test asserts the request hasmode: "live"; HTTP handler passes no mode (⇒ file) — assertrequests[0].modeundefined or"file". - Heartbeat:
TranscribeDeps.heartbeatIntervalMs?(default 5000); test with 20 ms and a fake sidecar that waits ~120 ms before the first segment; the response text contains ≥ 2 blank lines before the first segment line. - Implement; typecheck; commit.
Task 4: Real-model verification + docs
FW_INTEGRATION=1 SIDECAR_PYTHON=/home/user/venvs/faster-whisper/bin/python bun test tests/transcribe.test.tsunder the lock; extend the integration block with a real speech run (first 60 s ofen_alien, mode file, asserts ≥ 1 segment and a key word) and the tone (silence-ish) run finishingdone.docs/Decisions.mdD-WS5a with the before/after table;tests/server-engine.mdupdated;docs/Architecture.mdsidecar protocol line if it describes the message; WorkBreakdown tick.- typecheck, fast suite, build; commit.
git rmthis plan; commit.