Commit Graph
3 Commits
Author SHA1 Message Date
osobhandClaude Opus 4.7 a5cedfb46a rtx-csm: emotional_speech_guide — CREMA-D vs RAVDESS firdhokk verdict
8-gen bench (4 emotions × 2 corpora) at seed=42 against firdhokk
Whisper-LV3:

  target    RAVDESS              CREMA-D
  happy     happy (0.999) ✓      happy (0.999) ✓
  angry     neutral (0.92)       sad (0.99)
  fearful   happy (0.998)        fearful (0.984) ✓
  sad       angry (0.99)         fearful (0.99)

CREMA-D 2/4 vs RAVDESS 1/4. Larger / more naturalistic corpus
produces more class-pure fearful direction. Neither corpus solves
angry or sad — recipe shifts into 'vague expressivity' rather than
class-specific corners.

Practical: prefer CREMA-D when available; A/B both per emotion if
class precision matters.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-30 00:01:02 -07:00
osobhandClaude Opus 4.7 95015c17e1 rtx-csm: Phase 8.9 — Moonshine KV cache + profile binary
KV cache for the decoder turns greedy generation from O(T^2) into O(T)
total work. Per-token decode drops modestly on short transcripts
(7.1 -> 6.0 ms/token at 49 tokens) and compounds on longer ones.

New components in src/moonshine.rs:

  RotaryCache::apply_at(x, position, t)
      Apply RoPE for a window starting at `position`. Replaces
      `apply()` for cached step (which always called positions 0..T).

  DecoderSelfAttention::forward_step(xs, cache_k, cache_v, rope, position)
      Single-token cached self-attn. Appends new K/V to per-layer cache,
      attends across full accumulated history. No causal mask needed
      (cache only contains positions <= current).

  CrossAttention::precompute_kv(enc) -> (K, V)
      One-shot encoder K/V projection for cross-attn. Reused every step.

  CrossAttention::forward_step(xs, k, v)
      Cached cross-attn. Q computed from new token; K/V from precompute.

  DecoderCache { self_k: Vec<Option<Tensor>>, self_v, cross_k, cross_v, position }

  Decoder::precompute_cross_kv(enc) -> DecoderCache
  Decoder::step(token_id, &mut cache) -> logits (1, vocab)
  Decoder::generate_cached(enc, cfg, max_tokens) -> Vec<u32>
      Greedy loop using the cached step.

Profile (5 steady-state runs on /tmp/asr_test.flac, 10.42 s LibriSpeech):

  warm-up:                344 ms
  steady-state mean: 307 ms (p50 305, range 298-319)
  realtime factor: 0.0294x

Comparison across all STT in rtx-csm:

  Backend           RTF         Notes
  Kyutai STT 1B     1.01x       hardware-bound, 3 GB
  Whisper-tiny      0.020x      breaks CSM (in-process ggml conflict)
  Moonshine-tiny    0.0294x     pure candle, NO runtime conflict

Moonshine is the only fast STT path that integrates cleanly. ~34x
faster than realtime, ~17x faster than Kyutai 1B, no protobuf or
ggml linkage issues.

New `examples/moonshine_profile` mirrors `stt_profile` and
`whisper_profile` so all three STT backends report comparable numbers.

Phase 8.10 (next): wire as a third AsrEngine variant in converse_server
for English-only deploys. Replace the energy-VAD-gated Kyutai path
when --moonshine flag is set.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-27 11:05:19 -07:00
osobhandClaude Opus 4.7 b22ff3544e rtx-csm: Phase 8.8 — Moonshine end-to-end transcription works
Full encoder-decoder Moonshine v2 transcribing real audio in pure
candle 0.9 + Metal. No ort, no ggml, no protobuf. The path that
whisper-rs (Phase 7.6) and Silero V5 via ort (Phase 8.1.3) couldn't
deliver due to in-process linkage conflicts.

End-to-end on /tmp/asr_test.flac (LibriSpeech, 10.42 s):

  encode:    10 ms
  decode:   348 ms (49 tokens, 7.1 ms/token greedy, no KV cache)
  realtime factor: 0.068x  (~14x faster than realtime)

Output transcript:
  "He hoped there would be stew for dinner, turnips and carrots and
   bruised potatoes, and fat, mutton pieces to be ladled out in thick,
   peppered, flour-fat and sauce."

Ground truth:
  "He hoped there would be stew for dinner, turnips and carrots and
   bruised potatoes and fat mutton pieces to be ladled out in thick
   peppered flour-fattened sauce."

Near-perfect (a few punctuation tweaks, "flour-fat and sauce" vs
"flour-fattened sauce"). WER very low.

Compared to other STT backends in this crate:
  Kyutai STT 1B   :  1.01x realtime  (3 GB, hardware-bound)
  Whisper-tiny    :  0.020x realtime (in-process ggml -> CSM regression)
  **Moonshine-tiny: 0.068x realtime  (pure candle, no runtime conflict)**

Components shipped this commit:
  - Decoder::generate(encoder_output, cfg, max_tokens) — greedy
    autoregressive loop. No KV cache yet (each step re-runs the full
    growing token sequence — O(T^2) total). For 49-token transcripts
    at <500 ms total, KV cache isn't urgent.
  - load_tokenizer() — wraps tokenizers::Tokenizer::from_file for
    Moonshine's HF tokenizer.json (BPE, vocab 32_768).
  - examples/moonshine_transcribe — full pipeline: audio -> 16 kHz
    PCM -> encode -> decode -> detokenize -> transcript text.

Critical bug fixed: SwiGLU gate/up split direction. HF
modeling_moonshine.py says:
    hidden, gate = fc1(x).chunk(2, dim=-1)
    out = silu(gate) * hidden
The FIRST half of the fused fc1 output is `up` (multiplied), the
SECOND half is `gate` (silu-activated). I had it reversed in Phase
8.7 — the symptom was a degenerate "tt tt tt" repetition loop after
the first 2 tokens. Reversing the split unlocked the working
transcription. Captured in the code comment.

Remaining for Moonshine readiness in production:
  Phase 8.9 — KV cache for sub-200ms latency on long transcripts,
              plus a standalone moonshine_profile binary for the
              full A/B against Kyutai/Whisper.
  Phase 8.10 — wire as a third AsrEngine variant in converse_server
               (gated on English-only acceptance for the deploy).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-27 10:54:50 -07:00