Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 2024-02-18 - Pre-scaled Audio Feedback
**Learning:** AudioFeedback volume scaling was applied during every playback, causing unnecessary numpy overhead and latency.
**Action:** Pre-calculate scaled audio during `_load_and_cache` to minimize `_play_cached` latency (from ~1ms to ~0.04ms).

## 2024-05-22 - Parakeet Model Cold Start
**Learning:** ONNX Runtime initialization and graph optimization cause significant latency (cold start) on the first inference.
**Action:** Implemented `ParakeetManager.warmup()` to run a dummy inference at startup, shifting latency away from the first user interaction.
1 change: 1 addition & 0 deletions src/chirp/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def __init__(self, *, verbose: bool = False) -> None:
model_dir=model_dir,
timeout=self.config.model_timeout,
)
self.parakeet.warmup()
except ModelNotPreparedError as exc:
self.logger.error(str(exc))
raise SystemExit(1) from exc
Expand Down
9 changes: 9 additions & 0 deletions src/chirp/parakeet_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ def _load_model(self):
f"Model not found at {self._model_dir} — run: uv run chirp-setup"
) from exc

def warmup(self) -> None:
"""Run a dummy inference to initialize internal buffers and optimize execution graph."""
self._logger.debug("Warming up Parakeet model...")
dummy_audio = np.zeros(16_000, dtype=np.float32)
try:
self.transcribe(dummy_audio)
except Exception as exc:
self._logger.warning("Warmup failed (non-fatal): %s", exc)

def transcribe(self, audio: np.ndarray, *, sample_rate: int = 16_000, language: Optional[str] = None) -> str:
with self._lock:
self._last_access = time.time()
Expand Down
23 changes: 23 additions & 0 deletions tests/test_parakeet_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,29 @@ def test_timeout_zero_disables_monitor(self, mock_onnx):
self.assertIsNone(manager._monitor_thread)
self.assertIsNotNone(manager._model)

@patch("chirp.parakeet_manager.onnx_asr")
def test_warmup(self, mock_onnx):
"""Test that warmup calls transcribe with dummy audio."""
mock_onnx.load_model.return_value = MagicMock()
manager = ParakeetManager(
model_name="test",
quantization=None,
provider_key="cpu",
threads=1,
logger=self.logger,
model_dir=self.model_dir,
timeout=100.0,
)

# We can mock transcribe to ensure it's called
with patch.object(manager, 'transcribe') as mock_transcribe:
manager.warmup()
mock_transcribe.assert_called_once()
args, _ = mock_transcribe.call_args
audio_arg = args[0]
self.assertEqual(audio_arg.shape, (16000,))
self.assertEqual(audio_arg.dtype, np.float32)


if __name__ == "__main__":
unittest.main()