Skip to content

perf(silero-native): profile and optimize the native engine (fixes #164) - #180

Merged
xilec merged 3 commits into
mainfrom
perf/silero-native-164
Aug 1, 2026
Merged

perf(silero-native): profile and optimize the native engine (fixes #164)#180
xilec merged 3 commits into
mainfrom
perf/silero-native-164

Conversation

@xilec

@xilec xilec commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • Per-stage timing instrumentation (StageTimings in Engine/SileroNative,
    bench breakdown at 24k/48k/8k) — closes the "no per-stage attribution" gap.
  • Pinned ORT intra_op_num_threads to 8: 24k full-pipeline mean 104 → 36.6 ms;
    warm full-pipeline speedup vs Python torch apply_tts goes from ~1.3x to
    ~4.0x (acceptance: ≥ 2x).
  • Engine load 437 → ~345 ms as a side effect (fewer pool threads to spawn).
  • Allocation/copy churn and bulk WAV encode measured and rejected (< 2.2%
    each); the remaining ~34 ms is ORT inference inside the exported graphs —
    irreducible client-side without touching the graphs/exporter.

Why 8 threads (parity constraint)

Changing the thread count changes float reduction order. 4/6 threads push the
stress_marker parity case over the 1e-3 budget (1.5e-3 / 2.2e-3); 8 is the
only reduced count keeping all 31 cases inside (worst 9.8e-4) and ties 6 for
speed within noise. The count is deliberately not derived from
available_parallelism() — the reduction order must stay fixed for parity.

Docs

  • silero-native/docs/benchmarks.md: per-stage breakdown (acceptance Try KugelAudio as an alternative TTS #1),
    thread A/B record, new headline numbers, honest Python comparison re-run
    (145.0 ms, same methodology).
  • silero-native/docs/architecture.md: exact perf/flamegraph commands in the
    debugging guide.

Gates

  • silero-native tests incl. bundle-gated parity: green (worst 9.8e-4 ≤ 1e-3)
  • exporter self-check: 16/16 PASSED
  • src-tauri tests + just lint: green
  • manual app check: synthesis noticeably faster, quality unchanged

Fixes #164

xilec added 3 commits August 1, 2026 21:13
…(issue #164)

- Add StageTimings (frontend_text / homosolver / accentor /
  build_sequence / tts_main / istft / pqmf / wav_encode /
  concat_timestamps) collected in Engine::synthesize and summed over
  chunks in SileroNative::synthesize; exposed on SynthesisResult.
- bench example: per-stage mean breakdown at 24k/48k/8k per run.
- release profile gets debug = "line-tables-only" (crate-local builds
  only) so perf/flamegraph attribute to source lines; exact profiling
  commands documented in architecture.md "Debugging guide".
- benchmarks.md: baseline breakdown — tts_main ~80%, istft ~16%,
  frontend < 0.5%, 48k PQMF path confirmed free.
…sis (issue #164)

ORT defaults to one intra-op thread per logical core; for these graphs
(chains of many small ops) the per-op fork/join sync across 24 threads
costs more than the compute. Pinning with_intra_threads(8) takes the
24k full-pipeline bench mean from ~104 ms to ~37 ms; speedup vs the
Python torch apply_tts reference goes from ~1.3x to ~4.0x (145.0 ms,
same methodology). Engine load also drops 437 -> ~345 ms (fewer pool
threads to spawn at session creation).

Thread count is constrained by parity: changing it changes float
reduction order, and 4/6 threads push the stress_marker parity case
over the 1e-3 budget (1.5e-3 / 2.2e-3); 8 is the only reduced count
keeping all 31 cases inside (worst 9.8e-4) and ties 6 for speed.

benchmarks.md: new headline numbers + per-stage A/B record; allocation
churn and bulk WAV encode measured and rejected (< 2.2% each).

Gates: silero-native tests incl. bundle-gated parity green; exporter
self-check 16/16; src-tauri tests + just lint green.
- StageTimings: full-literal AddAssign coverage test — adding a stage
  field now fails compilation instead of silently dropping it from
  chunk accumulation and the bench breakdown.
- pqmf stage timer moved after input tensor construction, matching
  the other ORT stages' scope.
- INTRA_OP_THREADS constant (single edit point for the parity-
  constrained value); comment now states the count is deliberately
  not derived from available_parallelism() — the reduction order must
  stay fixed for waveform parity.
- StageTimings docstring: drop the stale ~100 ms reference.
@xilec
xilec merged commit e5cf14b into main Aug 1, 2026
6 checks passed
@xilec
xilec deleted the perf/silero-native-164 branch August 1, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(silero-native): profile and optimize the native engine

1 participant