license: cc-by-4.0
tags:
- activation-steering
- interpretability
- representation-engineering
- controllable-generation
- mechanistic-interpretability
language: - en
pretty_name: "The Tone Slider"
The Tone Slider
Training-Free Tone Control in a Small Language Model via Contrastive Residual-Stream Intervention
📄 Read the paper (PDF, 37 pages) ·
🎚️ Try the live demo
Tone — how blunt or how diplomatic a reply sounds — matters in customer
communication, but controlling it usually means prompt engineering, which is
discrete and hard to audit, or fine-tuning, which fixes the behaviour at training
time.
This is a third option. Take 50 blunt and 50 diplomatic customer-service sentences,
average their residual-stream activations at layer 12 of Qwen2.5-0.5B-Instruct, and
subtract. That difference is a direction. Add α times it back through a forward hook
while the model writes, and tone moves continuously:
h′ = h + α · v̂
No weight updates. No gradients. The artifact is 5.5 KB and serves on CPU.
What the paper reports
Everything below is measured; the full record is in results/.
| Model | Qwen2.5-0.5B-Instruct, layer 12 of 24, d=896 |
| Generations logged | 388 |
| Blind-judged items | 158 |
| Tone vs coefficient (lexicon) | ρ = +0.64, 95% CI [+0.53, +0.72] |
| Tone vs coefficient (blinded judge) | ρ = +0.85, 95% CI [+0.75, +0.92] |
| Verbosity control | ρ(α, output length) = −0.13 |
| Prompting range, for comparison | 0.63 vs steering's 4.12 (judged scale) |
Tone rises monotonically across all eight in-domain coefficient settings, and two
measures built on entirely different principles — a fixed word list and a language
model's holistic reading — agree on direction and ordering.
The finding worth your time
Your steering vector may be measuring sentence length.
At layer 12 the first token's activation norm is 97× the median token's — the
attention sink. Mean-pool over all positions and you weight it by 1/T, so the pooled
vector becomes a function of how long your sentences were.
| Pooling | cos(tone, detail) | Held-out accuracy |
|---|---|---|
mean (includes token 0) |
+0.859 | 71.0% |
mean_no_sink (ours) |
+0.043 | 97.0% |
Two directions built from unrelated contrasts came back 86% aligned. Excluding one
token drops that to 4%.
The mechanism makes a parameter-free prediction — ‖h₁‖·|1/T_pos − 1/T_neg| — which
accounts for 92% of the mean-pooled norm with nothing fitted. And the
uncomfortable part: the tone lists were already length-matched to 1.024×, and the
predicted length artifact (5.5) still exceeded the true tone signal (4.0). Matching
lengths to within 2.4% does not rescue mean pooling. Only excluding the token does.
Two results that didn't work
Reported because they're more useful than the positive ones.
Out of domain, the effect does not reach significance. The vector came from
banking replies; on prompts about driveways and dying houseplants the judge gives
ρ = +0.05, CI [−0.37, +0.42], and the unsteered condition scores as the most
diplomatic of the three. At n = 30 this experiment lacks the power to establish
transfer, and the paper says so rather than reporting three ordered means as a trend.
Both standard fluency metrics are one-sided. The blunt end fails by looping — and
looping text has low perplexity, so the curve turns back down exactly where output
is worst. The diplomatic end fails by fluent, contentless hedging — distinct-3 of
1.000 on text that has stopped answering. Each end is invisible to the metric that
catches the other. Our own repetition detector scored a total collapse into CJK as
perfectly clean, because CJK isn't whitespace-delimited and the short-text guard
suppressed the check.
Contents
| Path | What it is |
|---|---|
tone-slider-preprint.pdf |
The paper, 37 pages |
source/ |
Full LaTeX source; builds clean-room to the same PDF |
results/ |
The measurement record — every number in the paper is read from here |
data/ |
The 50 contrastive sentence pairs and both prompt sets |
steering_vectors.pt |
The extracted vector bundle, 5.5 KB |
results/ is the point of the release. The project originally shipped with no
recorded outputs at all, and several documented figures turned out to have no
experiment behind them; rebuilding them as scripts that write JSON changed some of
them. make_tables.py reads only from results/ and emits a marker rather than a
number when a key is missing, so no figure in the paper can drift from its data.
Reproducing
Everything ran on CPU: torch 2.12.0+cpu, transformers 5.9.0, Python 3.14,
float32, greedy decoding, seed 0.
make geometry # ~30 min — sink profile, pooling ablation, held-out CV
make generation # ~60 min — 388 greedy generations, resumable
make score # ~10 min — perplexity, degeneration, lexicon
make tables # regenerate every table from results/
Status
Preprint. Not peer reviewed, and not yet on arXiv — endorsement pending.
Scope is deliberately narrow: one model, one language, one attribute, and a 0.5B
model whose own instruction-following is weak enough to be a confound. The paper's
appendix maps every claim to the experiment that bears on it and marks the five that
remain unevaluated.
License
CC BY 4.0.