DGX Spark · part 40
在 abliterated DeepSeek-V4 上做 Directional Steering:同一把刀,疊起來會打架
❯ cat --toc
TL;DR
ds4 引擎內建 directional steering — 推理當下推一個方向向量改模型行為,數學上就是 abliteration 的連續可逆版。我在 GB10/CUDA 上跑通(官方工具偏 Metal,但 CUDA 的 activation dump 也會觸發),抽了一條 verbosity 向量。結果在 abliterated Q2 上 gradient 非單調、正向 scale 直接崩成標題碎片。兩刀同源,疊起來互相打架 —— 而那個「打架」恰好是最值得寫的東西。
TL;DR
- directional steering = 不改權重、不改 prompt,推理當下把內部 residual stream 沿一個方向推一把。ds4 直接內建了(
--dir-steering-file)。 - 數學
y = y - scale·dir·dot(dir, y):正 scale 移除方向(scale=1 是完整投影掉)、負 scale 放大。abliteration = scale=1 那個特例(對「拒絕」方向、烤進權重),steering 是同一個編輯變成活的、可正可負的 dial。 - 官方工具血統偏 Metal,但我實測 CUDA 也能抽向量:
ds4的metal_graph其實是統一 GPU executor,DS4_METAL_GRAPH_DUMP_*在 CUDA 上照樣觸發,43 個ffn_outdump 檔全出來。 - 在 我們的 abliterated DeepSeek-V4-Flash Q2 上抽了一條 verbosity 向量,掃 scale → 效果有,但非單調、正向崩潰。和官方 vanilla 的乾淨單調差很多。
- 結論:兩把刀同源,疊在被動過刀的 activation 空間上會互相打架。這不是 bug,是這篇的主軸。
先講人話:什麼是 steering,它到底在幹嘛
一個 LLM 在算每個 token 時,內部是一條高維向量(residual stream,「思緒之流」)一層一層往前傳。這裡有個關鍵前提 —— 很多概念和行為在這個空間裡可以近似成「方向」:「囉嗦」是一個方向、「拒絕」是一個方向、「正式語氣」是一個方向。
Directional steering 就是:在每一層,把這條思緒向量沿著你選的方向加一點或減一點。你是在編輯模型的「想法」本身,不是用嘴叫它照辦。
為什麼要這樣?因為控制模型行為,長久以來只有兩個旋鈕,steering 是被忽略的第三個:
| 旋鈕 | 怎麼控 | 缺點 |
|---|---|---|
| Prompt(用講的) | 「請簡潔」 | 模型可以無視、長對話會忘、耗 token、是軟性建議 |
| Fine-tune(改權重) | 拿資料重訓 | 貴、要算力和資料、永久不可逆、不能 per-request |
| 🆕 Steering(推思緒) | 加一個方向向量 | 窄,但卡在甜蜜點 |
Steering 比 prompt 硬(機械式,不是模型可以飄走的建議)、比 fine-tune 輕(不重訓,一個向量加一個純量旋鈕),而且那個純量是連續 dial:0 = 關、越大越強、負的 = 反方向。

「我都 abliterate 了,為什麼還要 steering?」
這是這篇的核心問題。如果你跑 self-host 開源模型,你大概已經在用 abliteration —— 把「拒絕」那個方向從權重裡永久砍掉,得到一個本來會拒答的問題也願意答的模型。那 steering 還有什麼用?
答案是:它們解的是不同層的問題。
- Abliteration 解「准不准」(permission)—— 砍掉拒絕,一次性、烤進權重、實務上只做這一個方向。
- Steering 解「怎麼答」(shape)—— 推理當下推任何方向、連續強弱、可逆。
砍掉「不」≠ 控制「怎麼說」。你 abliterate 完得到一個肯答的模型,但它還是會囉嗦、會在第 50 輪把 persona 漂掉、會捏造 —— 那些 abliteration 從來沒在管。
而最漂亮的洞見在數學裡。ds4 的 steering 公式:
y = y - scale * dir[layer] * dot(dir[layer], y)
- 正 scale = 移除那個方向(scale=1 完整投影掉 = directional ablation)
- 負 scale = 放大那個方向(abliteration 不做的反向)
所以 abliteration 就是這個編輯在 scale=1、對『拒絕』方向、永久烤進權重的特例。
類比:abliteration = 永久把某一顆輪子的定位調死;steering = 你手上那個活的方向盤,任何方向、隨時、可回正。問「有 abliteration 為何還要 steering」≈ 問「輪子都對好了為何還要方向盤」。

這也鋪好了懸念:steering 假設 activation 空間有乾淨的線性方向可以推。但 abliteration 已經對這個空間動過刀了。那……對一個被 abliterate 過的模型做 steering,會發生什麼?
ds4 早就內建了,而且 CUDA 真的能跑
翻 ds4 的 --help steering,它整套都在:
--dir-steering-file FILE load a 43 x 4096 f32 direction file
--dir-steering-ffn F apply steering after FFN outputs; default 1
--dir-steering-attn F apply steering after attention outputs; default 0
dir-steering/ 目錄還附了生成工具:build_direction.py(從對比 prompt 對抽方向)、run_sweep.py(掃 scale)、100 對 succinct↔verbose 的範例。看起來「下午就能做完」。
但有個坑:整套文件是用 Apple Silicon Metal 寫的。抽向量靠 DS4_METAL_GRAPH_DUMP_* 這組 env var 把 activation 值 dump 出來 —— 名字裡寫死 METAL。我們是 GB10 / CUDA,第一個問題就是:CUDA 到底會不會觸發這個 dump?
翻 source 後翻案了:ds4 裡叫 metal_graph 的其實是「統一 GPU executor」,不是 Apple 專屬 —— 同一個檔裡就有 metal_graph_decode_cuda_* 這種 CUDA 專屬函式。dump 函式用的是 backend-agnostic 的 ds4_gpu_tensor_read,CUDA 也會走到。
一個探針就驗證了。停掉 daily server 釋放 GPU,跑一次:
DS4_METAL_GRAPH_DUMP_PREFIX=/tmp/probe/dump \
DS4_METAL_GRAPH_DUMP_NAME=ffn_out DS4_METAL_GRAPH_DUMP_POS=0 \
./ds4 --cuda -m <abliterated-Q2.gguf> --ctx 512 --prompt-file p.txt -n 1
22 秒後,43 個 dump_ffn_out-<layer>_pos0.bin 全出來了(每層一個,正好對上 DeepSeek-V4-Flash 的 43 層 × 4096 embedding)。套用端也一樣:ds4_gpu_directional_steering_project_tensor 有 CUDA 實作,向量由 ds4_gpu_tensor_write 載進 GPU tensor。
兩個還要踩的小坑(留給後人):
- 官方
build_direction.py/run_sweep.py的指令沒帶--cuda,會走 CLI 預設 backend → 自己複製一份補上。 run_sweep.py --scales "-1,..."的開頭-1被 argparse 當成新 flag → 用等號形式--scales=-1,-0.5,0,1,2。
還有一個現實約束值得記:抽向量要獨佔 GPU(一份 80GB 權重在 device),塞不下第二個 —— 所以整個實驗期間 daily 推理服務得離線。我把實驗腳本包成 trap restore_daily EXIT HUP INT TERM,跑完或中途出錯都自動重啟 daily,不會把服務晾在那。
結果:有效,但不聽話
我用 30 對 succinct↔verbose(官方範例的子集)抽了一條 verbosity 向量,704512 bytes = 43 × 4096 × 4,維度對得上。然後固定 prompt「Explain why databases use indexes」掃 scale:
| FFN scale | 輸出字數 | 觀察 |
|---|---|---|
| −1 | 65 | 變長 |
| −0.5 | 78 | 最長 |
| 0(baseline) | 28 | abliterated 本來就簡潔 |
| +1 | 23 | 略縮 |
| +2 | 4 | 崩成「Why Databases Use Indexes」標題碎片 |
對照官方文件裡 vanilla 模型的乾淨單調(負 scale → 短、正 scale → 長),我們這條有三點不一樣:
- 方向是反的(但這格可能只是 慣例):負 scale 把答案拉長(28→78)、正 scale 縮短(28→23)。README vanilla 是「負短正長」,我們剛好相反 —— 不過 sign 反向最可能只是向量取向(
good − bad/ orthogonalize 慣例)的差,翻回來就好,不算大事。 - 負範圍內非單調(這個 慣例 解釋不了):−0.5(78 字) 反而比 −1(65 字) 更長,不是越推越強。
- 正向崩潰來得很快:+2 直接垮成一個四字標題碎片,連句子都不成 —— over-steer,可用 band 很窄。
這不是失敗,是結果。sign 反向那格先放一邊(慣例 就能解釋),真正耐人尋味的是非單調 + 正向崩潰 —— 這兩個 慣例 解釋不了。一個假設(在沒跑 vanilla 對照組前,也就只是假設):abliteration 可能已經改過 activation 幾何,「拒絕」方向被權重級投影掉之後,整個 residual 空間的方向結構不再是原版那個乾淨的樣子。我們再從這個被動過刀的空間抽一條 verbosity 方向、再投影回去 —— 第二刀疊在第一刀的傷口上,互相打架。
(也不能排除是只用 30 對的 noise,或 scale band 沒掃細。乾淨的 100 對重抽 + 細掃是 follow-up。但「正向崩潰、負向反而擴張」這個質性 pattern,30 對已經夠清楚。)
學到什麼
把確定的、意外的、還不知道的分開:
確定知道
- 這個功能在 GB10/CUDA + abliterated Q2 上能跑 —— 進去前最大的未知(「dump 是不是 Metal-only」)解了,CUDA 完全可用。
- steering 機制上 = abliteration 的連續可逆版,概念對接成立。
- 整條 pipeline 可重現(補
--cuda+--scales=+ trap 還原)。
意外學到
- 在 abliterated 模型上,dial 不是 vanilla 那種乾淨單調 —— 強烈暗示 abliteration 跟 steering 會互相作用。沒人寫過這個點。
- over-steer 崩潰來得很快(+2 就垮),可用 band 很窄,要從 ±0.3 這種小值試。
還不知道
- 非單調是 30 對 noise 還是 abliteration 本質?(要 100 對才能分開)
- sign 到底哪邊,是真方向還是
--orthogonalize/--pair-normalize開關的取向問題? - 換 persona / 反捏造 方向會不會比 verbosity 乾淨?(這才是對 self-host agent 真正有用的方向)
對「值不值得當 live 旋鈕」的誠實判斷:還沒到。三個障礙 —— 它是啟動級全域旗標(steer 整台服務,不是 per-message)、abliterated 上不可預測、每次調都要服務離線。所以「即時調 persona」的願景比想像遠。但當研究 / 教學題材,這個「兩刀同源、疊起來打架」的故事很到位。
這是 DGX Spark 系列的一篇。相關:ds4 引擎怎麼跑 DeepSeek-V4-Flash、abliterated Q2 量化。
常見問題
- 什麼是 directional steering?
- 推理當下,在每一層把模型內部的 residual stream 沿一個選定方向加一點或減一點,藉此改變行為(囉嗦/語氣/persona/拒絕)。不改權重、不改 prompt。ds4 的公式是 y = y - scale * dir[layer] * dot(dir[layer], y):正 scale 投影掉方向、負 scale 放大。向量檔是 43x4096 的 flat f32(每層一條 normalized 方向),用 --dir-steering-file + --dir-steering-ffn 啟用。
- 我都 abliterate 了,為什麼還要 steering?
- 因為它們解不同問題。Abliteration 解『准不准』— 把拒絕方向從權重永久砍掉,一次性、烤死、實務上只做一個方向。Steering 解『怎麼答』— 推理當下推任何方向、連續強弱、可逆。砍掉『不』不等於控制『怎麼說』。而且它們其實是同一個運算:abliteration 就是把 steering 對拒絕這一個方向、用固定強度、永久烤進權重。Steering 是那把活的方向盤。
- directional steering 在 GB10 / CUDA 上能跑嗎?ds4 的工具不是給 Metal 的嗎?
- 能跑。ds4 source 裡叫 metal_graph 的其實是統一 GPU executor,CUDA 走同一條(同檔有 metal_graph_decode_cuda_* 函式)。所以抽向量靠的 DS4_METAL_GRAPH_DUMP_* env hook 在 CUDA 上也會觸發 — 我實測 ./ds4 --cuda 帶 dump env,43 個 ffn_out dump 檔全出來。套用端 ds4_gpu_directional_steering_project_tensor 也有 CUDA 實作。唯一要補的是官方 build_direction.py / run_sweep.py 預設沒帶 --cuda,要自己加。
- 在 abliterated 模型上 steering 效果如何?
- 有效但不照 textbook 走。30 對 succinct/verbose 抽的 verbosity 向量,在 Huihui-DeepSeek-V4-Flash abliterated Q2 上掃 scale:baseline 28 字、負 scale 拉長(-0.5→78、-1→65)、正 scale 縮短到崩潰(+1→23、+2→4 字標題碎片)。三個觀察:方向跟 README vanilla 剛好相反(但這最可能只是向量取向 慣例)、負範圍內非單調(-0.5 比 -1 長)、正向 over-steer 崩潰快。後兩個 慣例 解釋不了,推測 abliteration 已改過 activation 幾何,第二刀疊上去打架。30 對 proof-of-life,乾淨重掃是 follow-up。