rtx-csm: --decoder-steering-layers — layer subset fixes repetition

Mirror of --steering-layers for the decoder: comma-separated layer
indices to actually steer (decoder has 4 layers; useful subsets are
[3], [2,3], [1,2]).

Sweep at seed 7, [email protected]:
  [0,1,2,3]  cos 0.86  WER 0.93  "I'm not that tall ×3" ← repetition
  [3]        cos 0.58  WER 0.86  "I'll be off and offense..."
  [2,3]      cos 0.80  WER 1.43  "My daughter, Penny Ryan, and I have…"
  [0,1]      cos 0.82  WER 1.57  "And I'll check on them..."
  [1,2]      cos 0.81  WER 1.43  "I'm going to call him an X-Man..."

The repetition is specific to all-layers-at-once steering. Any 2-layer
subset eliminates it while preserving most of the cosine boost. Same
pattern as the backbone's [8,10,12] finding: partial perturbation
lets the unsteered layers act as a stabilizing prior.

[2,3] (decoder last 2) is the new recommended recipe — best cosine
of the no-repetition subsets and the longest fluent transcript.
Documented in docs/perf_history.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-04-29 14:37:49 -07:00
co-authored by Claude Opus 4.7
parent 68ecae1b08
commit f642132f4a
2 changed files with 36 additions and 2 deletions
@@ -209,6 +209,30 @@ Both fail differently; neither produces production-quality emotional
speech in single-shot. The N-seed picker (`emotional_speech_n.sh`) speech in single-shot. The N-seed picker (`emotional_speech_n.sh`)
is still the right consumer interface — it doesn't care which is still the right consumer interface — it doesn't care which
failure mode produced the bad samples, just sorts by WER. failure mode produced the bad samples, just sorts by WER.
**Decoder layer subset fixes the repetition.** The "I'm not that
tall ×3" failure at all-4-layers @ scale 1.0 disappears entirely
when steering is restricted to a 2-layer subset. Same pattern as
the backbone's `[8,10,12]` finding: perturbing all layers
simultaneously is the bad regime; partial perturbation lets the
unsteered layers act as a stabilizing prior.
layer subset cos WER repetition?
[0,1,2,3] (all) 0.86 0.93 yes ("I'm not that tall ×3")
[3] only 0.58 0.86 no
[2,3] last 2 0.80 1.43 no — "My daughter, Penny Ryan…"
[0,1] first 2 0.82 1.57 no
[1,2] middle 2 0.81 1.43 no
`[2,3]` (decoder's last two layers, the analogue of backbone's
`[8,10,12]` mid-deep recipe) is the best balance — preserves the
cosine boost while eliminating the repetition. New decoder recipe:
```
--decoder-steering-vec /path/<emotion>.safetensors
--decoder-steering-scale 1.0
--decoder-steering-layers 2,3
```
- *RAVDESS corpus shape biases output length*: clips are ~3 s - *RAVDESS corpus shape biases output length*: clips are ~3 s
("Kids are talking by the door") so the steering biases the ("Kids are talking by the door") so the steering biases the
model toward terse outputs (one or two words). Longer-utterance model toward terse outputs (one or two words). Longer-utterance
+12 -2
View File
@@ -150,6 +150,13 @@ struct Cli {
#[arg(long, default_value_t = 1.0)] #[arg(long, default_value_t = 1.0)]
decoder_steering_scale: f32, decoder_steering_scale: f32,
/// Comma-separated layer indices to actually steer on the decoder
/// (e.g. "2,3"). The decoder has 4 layers (CSM-1B); useful subsets
/// to try: `3` (deepest), `2,3` (last two), `1,2` (middle two).
/// Defaults to all 4 layers.
#[arg(long, value_delimiter = ',')]
decoder_steering_layers: Option<Vec<usize>>,
/// Per-frame CFG scale schedule (Selective CFG, arXiv 2509.19668). /// Per-frame CFG scale schedule (Selective CFG, arXiv 2509.19668).
/// Forms: /// Forms:
/// - `const:<scale>` (constant scale, equivalent to --cfg-scale) /// - `const:<scale>` (constant scale, equivalent to --cfg-scale)
@@ -247,11 +254,14 @@ fn main() -> Result<()> {
let mut steering = let mut steering =
rtx_csm::steering::LayerSteering::load_safetensors(dec_path, n, &device)?; rtx_csm::steering::LayerSteering::load_safetensors(dec_path, n, &device)?;
steering.set_scale(cli.decoder_steering_scale); steering.set_scale(cli.decoder_steering_scale);
if let Some(layers) = cli.decoder_steering_layers.as_ref() {
steering.restrict_to_layers(layers);
}
tracing::info!( tracing::info!(
"loaded decoder steering from {} (scale={}, layers={})", "loaded decoder steering from {} (scale={}, active_layers={:?})",
dec_path.display(), dec_path.display(),
cli.decoder_steering_scale, cli.decoder_steering_scale,
n, steering.active_layers(),
); );
generator.set_decoder_steering(Some(steering))?; generator.set_decoder_steering(Some(steering))?;
} }