Commit Graph
62 Commits
Author SHA1 Message Date
osobhandClaude Opus 4.7 1c4b10405d rtx-csm: Phase 6a polish — bf16 dtype + silence padding + cleanup
Refinements on the Kyutai STT integration after debugging session:

- dtype: BF16 on accelerators (matches checkpoint storage), F32 on CPU.
  Previously F16 on Metal which can overflow in the LM's RmsNorm.
- examples/stt_demo: pad input with 0.5s silence suffix per the HF
  stt_config.audio_delay_seconds, matching the Python reference loop.
- src/stt.rs: tightened module docs with debugging notes for the
  remaining all-pad-output issue. Removed RTX_STT_DEBUG callback path
  (was useful for one-off debugging; can be re-added with cleaner shape).

Status: weights load cleanly, LM forward advances every frame, but
predictions are all-pad on real speech. Bisection plan documented in
the module rustdoc — next session should diff against the official
delayed-streams-modeling Python reference at frame-by-frame granularity.

77 lib tests + 2 stt tests all pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-26 04:44:10 -07:00
osobhandClaude Opus 4.7 0f9cc122e9 rtx-csm: Phase 6a partial — Kyutai STT scaffold via moshi crate
Integrates the moshi crate (0.6.4, candle 0.9.1) for streaming STT.
Module + demo + custom config for kyutai/stt-1b-en_fr. Model loads
cleanly, LM forward pass advances (model_step_idx increments correctly),
but word events don't yet emit on a 10s CSM speech sample.

What works:
- moshi 0.6.4 added as dependency (candle 0.9.1, version-compatible)
- src/stt.rs wraps moshi::asr::State + moshi::lm + moshi::mimi
- Stt::load_default downloads kyutai/stt-1b-en_fr (~3 GB) from HF
- Custom config_stt_1b_en_fr() matching the released checkpoint:
  d_model=2048, num_layers=16, dim_feedforward=8192 (moshi's SwiGLU
  hidden = 11/4 * d_model = 5632 — verified vs safetensors), text vocab
  8001/8000, audio vocab 2049, 32 codebooks, no depformer
- AsrEvent enum + From<moshi::asr::AsrMsg> conversion
- examples/stt_demo.rs streams a WAV through the pipeline
- 2 unit tests for AsrEvent conversion

What needs more work:
- Word emission: 0 words detected on 10s of clean CSM speech, even
  though LM forward advances every frame. Likely culprits:
  a) asr_delay_in_tokens 6 vs HF stt_config.audio_delay_seconds=0.5
     (6.25 frames). Off-by-one possible.
  b) Sentencepiece detok not yet wired (tokens emitted but text=None).
  c) Subtle weight-key remap differences between moshi's expected
     layout and the released checkpoint that don't trip a shape check.
  d) renormalize/audio preprocessing mismatch.

Next step (Phase 6a polish): compare against the official
delayed-streams-modeling/scripts/stt_from_file_pytorch.py reference to
identify the missing piece. The integration framework is sound; only
the final LM-output-to-text-event step needs work.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-26 04:33:40 -07:00
osobhandClaude Opus 4.7 9161b32a91 rtx-csm: per-request watermark message in tts_server
Watermarker trait gains embed_with_message(audio, message) with a
default impl forwarding to embed (no-op for watermarkers without a
payload). AudioSealWatermarker overrides to use the requested message
instead of self.message; ResampledWatermarker forwards through the
resample dance.

TtsRequest gains optional watermark_message: Option<String> (decimal or
0xHEX). Useful for clawsample to tag each generation with a unique ID
(e.g. job_id mod 0x10000) for audit trails. When omitted, falls back
to the server-startup --audioseal-message default.

Verified end-to-end: override "0xBEEF" -> detect 0xBEEF (mean_presence
0.9995, 16/16 bits). Default fallback also decodes correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-26 04:01:30 -07:00
osobhandClaude Opus 4.7 e61bd70e03 rtx-csm: WavLM-SV bug fixes — gelu_erf + gru_rel_pos_const loading
Two real bugs found via code inspection against HF source:

1. candle's .gelu() is the tanh approximation; PyTorch's default 'gelu'
   activation (used in WavLM via ACT2FN['gelu']) is the exact erf-based
   version. Switched all 3 sites (feature extractor convs, pos_conv,
   FFN) from .gelu() to .gelu_erf() to match the reference.

2. gru_rel_pos_const lookup used vb.pp("name").get(shape, "") which
   resolves to "<prefix>.name." (trailing dot) and fails to find the
   tensor. The .or_else(|_| zeros) silently swallowed the failure,
   leaving all 12 layers' gating constants at zero instead of the
   trained values. Fixed to attn.get(shape, "gru_rel_pos_const") which
   resolves correctly.

examples/wavlm_sv_inspect.rs: utility for sanity-checking specific
tensors inside converted safetensors (e.g. layer_weights).

Same-content same-speaker cosine: 0.9985 -> 0.9963 (≈unchanged).
Cross-content same-speaker cosine: 0.4882 -> 0.4118 (still drifting).
Phase 5d (Python reference comparison) remains the gate for
identifying the residual numerical drift.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-25 22:00:59 -07:00
osobhandClaude Opus 4.7 d70e7b8215 rtx-csm: generate_long_to_wav applies watermark + post-process
Adds the long-form analogue of generate_to_wav. generate_long previously
returned raw PCM and bypassed the Generator-bound watermarker hook,
meaning long-form output skipped watermarking entirely if installed.

generate_long_to_wav mirrors generate_to_wav exactly:
chunked-generation -> post-process -> watermark -> WAV write.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-25 21:20:01 -07:00
osobhandClaude Opus 4.7 1bb400a886 rtx-csm: speaker_sim::WavLmSimilarity is now production
Wraps the WavLM-SV port from Phase 5c. Full 100M-param X-vector head
running in-process; previously returned a typed error.

- WavLmSimilarity::load(path, device) loads converted safetensors.
- WavLmSimilarity::embed(samples) caches a 512-d embedding for repeat
  comparisons.
- score(a, b) embeds both inputs and cosines them.
- Module docs updated; SpectralCentroidSimilarity kept as a weak-baseline
  check.

Caller-facing change: any code using the SpeakerSimilarity trait now
gets a real speaker model with one constructor swap.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-25 19:59:10 -07:00
osobhandClaude Opus 4.7 d1dba7a05c rtx-csm: WavLM-SV converter + end-to-end speaker similarity
Phase 5c — pure-Rust converter for microsoft/wavlm-base-plus-sv with
auto-detecting weight_norm merger; verified on real 100M-param weights:
load + embed + cosine-similarity round-trip works on Metal.

- wavlm_sv_convert.rs: candle_core::pickle reads pytorch_model.bin
  directly. merge_weight_norm_auto picks the kept dim from g's shape
  (dim=0 for AudioSeal SEANet, dim=2 for WavLM pos_conv_embed). Skips
  classifier.*/objective.* (train-only AMSoftmax head).
- examples/wavlm_sv_convert: HF download + convert CLI. Verified output:
  1 weight_norm pair merged + 261 passthrough + 3 skipped = 262 tensors.
- examples/wavlm_sv_demo: load + embed pair of WAVs + cosine similarity.
- examples/audioseal_inspect: gains --which wavlm-sv variant for key
  discovery.
- hub.rs: REPO_WAVLM_SV + resolve_wavlm_sv() helper.
- wavlm_sv::XVectorHead bug fix: layer_weights is top-level, not nested
  under prefix.

Verified end-to-end on Metal: cosine sim 0.9985 on same-speaker pair
(CSM vs CSM-watermarked, 10s @ 24 kHz resampled to 16 kHz). Numerical
parity vs HF reference is Phase 5d.

3 converter tests + 12 wavlm_sv tests; 78 lib tests total green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-25 19:57:52 -07:00
osobhandClaude Opus 4.7 3165b9339b rtx-csm: WavLM transformer encoder with gated rel-pos attention
Phase 5b — full implementation of WavLMEncoderLayer.forward, replacing
the Phase 5a stub. With random weights the encoder is no longer a no-op
(verified: L2(output - input) > 1e-6 in unit test).

- relative_position_bucket helper: T5-style bidirectional bucketing
  matching HF _relative_positions_bucket. 320 buckets, 800 max distance,
  half/half split with linear inner / log-spaced outer.
- WavLmEncoderLayer::compute_position_bias (layer 0 only): builds (T,T)
  bucket index, embedding-looks up rel_attn_embed, permutes to
  (num_heads, T, T) matching HF's compute_bias output.
- WavLmEncoderLayer::gated_position_bias: HF gating math verbatim —
  Linear(head_dim → 8), reshape (..., 2, 4) sum, sigmoid, chunk to
  gate_a/gate_b, compute gate_a * (gate_b * gru_rel_pos_const - 1) + 2,
  broadcast-multiply position_bias.
- WavLmEncoderLayer::attention: multi-head self-attention with the
  gated bias added to scores before softmax. Standard 1/sqrt(d) scale.
- WavLmEncoderLayer::forward_with_bias returns (output, position_bias)
  so Encoder::forward_all_layers can thread bias from layer 0 through
  layers 1-11 (HF's has_relative_position_bias=(i==0) pattern).
- 3 new tests bring wavlm_sv to 12 tests; 75 lib tests total green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-25 19:48:56 -07:00
osobhandClaude Opus 4.7 90e53b3c0c rtx-csm: WavLM-Base+ SV scaffold for speaker similarity
Phase 5a — architectural skeleton for the microsoft/wavlm-base-plus-sv
reference, drop-in replacement for the SpectralCentroidSimilarity weak
baseline in speaker_sim.rs.

Modules in src/wavlm_sv.rs (~600 LOC):
- FeatureExtractor: 7-layer Conv1d, 320× downsample, GroupNorm at layer
  0 (num_groups=num_channels=512), GELU activations.
- FeatureProjection: LayerNorm + Linear 512→768.
- PosConv: Conv1d(768, 768, k=128, groups=16, pad=64) + GELU; SamePad
  strips trailing frame for even kernel.
- WavLmEncoderLayer: struct shape complete (Q/K/V/out projections, pre-
  attention LN, FFN intermediate/output, final LN, gru_rel_pos_const +
  gru_rel_pos_linear, optional rel_attn_embed at layer 0). forward() is
  a STUB; Phase 5b implements gated rel-pos attention.
- Encoder: 12 stacked layers, returns Vec<Tensor> of 13 hidden states.
- Tdnn: dilated unfold + Linear(in*kernel, out) — matches HF impl.
- XVectorHead: softmax-weighted layer sum + projector 768→512 + 5 TDNN
  layers (kernels [5,3,3,1,1] dilations [1,2,3,1,1]) + statistics pool
  + 3000→512 embedding projection.
- WavLmSv top-level + zero-mean unit-variance normalize + cosine
  similarity helper for verification scoring.

9 shape-correctness tests; 72 lib tests total green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-25 19:42:59 -07:00
osobhandClaude Opus 4.7 63979eab45 rtx-csm: Generator inline watermarker + ResampledWatermarker adapter
A single \`generate\` invocation now produces a watermarked WAV when
AudioSeal weights are passed via CLI. End-to-end verified on real CSM
speech: mean_presence=1.0000, 16/16 message bits decoded.

- Generator gains \`watermarker: Option<Box<dyn Watermarker>>\` slot;
  \`generate_to_wav\` runs \`wm.embed(&pcm)\` after post-process, before
  WAV write. Field is Send+Sync so the existing Arc<Mutex<Generator>>
  tts_server pattern still works.
- watermark.rs ships ResampledWatermarker<W> adapter for handling rate
  mismatches (CSM 24 kHz ↔ AudioSeal 16 kHz). Output length is normalized
  to input length so it's a transparent drop-in.
- examples/generate.rs gains --watermark-generator/--watermark-detector/
  --watermark-message flags. Loads AudioSeal, wraps in resampler, installs.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-25 19:36:17 -07:00
osobhandClaude Opus 4.7 938b54a2b0 rtx-csm: AudioSeal watermark — Rust port end-to-end
SEANet generator + detector matching `facebook/audioseal` reference layout
(weight_norm-merged via pure-Rust pickle reader). Verified on real CSM
speech: mean_presence=0.9943, 16/16 message bits decoded.

- src/audioseal.rs: SeanetEncoder (4-stage strided downsample, 2-layer
  LSTM bottleneck at 512 channels, 128-dim projection), MsgProcessor
  (16-bit message via embedding sum + broadcast-add), SeanetDecoder,
  Generator (encoder+msg+decoder), Detector (encoder + single 320×
  reverse_convolution + 1×1 head). Padding mirrors audiocraft
  _get_extra_padding_for_conv1d exactly.
- src/audioseal_convert.rs: candle_core::pickle reads .pth directly;
  merge_weight_norm computes g*v/‖v‖ over all axes except 0; writes
  flat safetensors keyed identically to what Generator/Detector read.
- examples/audioseal_inspect.rs: dumps tensor keys + shapes.
- examples/audioseal_convert.rs: HF download + convert CLI.
- examples/audioseal_demo.rs: load + embed + detect on real WAV or
  synthetic burst, optionally writes watermarked WAV.
- audio_io.rs gains generic load_mono_at_rate, resample, write_wav_mono
  (16 kHz path needed for AudioSeal).

12 new unit tests + 2 converter tests; 63 lib tests total green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-25 19:26:13 -07:00
osobhandClaude Opus 4.7 15dd3575d4 Add rtx-csm: Rust-native port of Sesame CSM-1B with LoRA voice cloning
A new model crate at crates/models/rtx-csm implementing end-to-end
inference, quantization, and fine-tuning for Sesame's Conversational
Speech Model (CSM-1B). Built on candle 0.9 + Kyutai Mimi codec.

Key capabilities:
- Inference (FP F16 on Metal, F32 on CPU, BF16 on CUDA)
- Quantized inference (Q8_0 / Q4_K_M GGUF, ~3x speedup, ~50% memory)
- Streaming Mimi decode with proper StreamTensor state machine
- In-context voice cloning via SpeakerProfile
- Classifier-Free Guidance (Koel-TTS recipe)
- Long-form chunked generation with rolling context
- Audio post-processing (HPF + declick + EBU R128 LUFS)
- Text input normalization (brackets, times, unicode, length caps)
- Frame-level repetition guard (loop-escape)
- Top-k + top-p sampling
- LoRA fine-tuning end-to-end (training + inference, on FP and Q8 bases)
- In-process Whisper ASR via whisper-rs (under --features asr)
- Standalone TTS HTTP server (Axum)
- Bench harness with manifest export + per-prompt WER

Phases delivered: quantization, ASR/WER eval, LoRA voice cloning, HTTP
service. AudioSeal/WavLM/Unmute remain as documented future work.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-25 18:33:57 -07:00