你可能看過 Neuron Steering 示範:原本在解釋冰箱的 AI,改一個內部數字,回答突然繞到老鼠。好奇之餘,更值得問:這是調到某種概念,還是把模型推到失常?
這篇專為第一次接觸模型內部的讀者寫。先用白話搞懂 activation、權重與 steering,再用 Qwen 小模型設計一份能比較、能撤回的實驗。程式段落適合已能執行 Python 的讀者;先讀懂考卷與對照,也能判斷別人的展示是否有說服力。
本文依官方模型設定、框架原始碼與公開研究整理實驗方法,未在本次環境執行模型推論,因此不提供自測成績,也不預告某個編號會出現什麼人格。
先說結論:Neuron Steering=固定權重+暫時改變中間訊號
把模型想成一間餐廳:權重是固定食譜,Prompt 是客人的點單,activation 是廚房這一刻正在處理的材料。推論期 steering 改的是當下材料,後續菜色可能改變;食譜是否真的未動過,要用程式路徑與復原測試確認。
今天的成功標準很務實:同樣的題目,能留下未干預、干預、隨機對照與撤回後的回答,並分清「更像指定風格」和「更能答對」。一段有趣回答,還不足以替某個神經元命名。
一、Neuron Steering 是什麼?先分清五種改法
- Prompt:「換點單」。改輸入的文字與對話條件,讓模型沿著不同上下文回答。
- 權重:「換食譜」。訓練學到的參數,是運算中使用的固定係數。
- LoRA:「替食譜加小份修訂」。典型做法訓練低秩參數,再於推論時套用;它和直接動當下 activation 的介入點不同。
- Activation:「這道菜做到哪一步」。輸入經過各層運算產生的中間數值,隨題目與 token 改變。
- Steering:「在製作途中推一下」。在指定層、指定位置加入或替換訊號。單神經元是改某個座標;向量 steering 則可同時改動多個座標。
「神經元」在這裡是 MLP(多層感知器,一段處理特徵的運算)中間向量的一個座標,並非一顆生物腦細胞。CAA 原始研究研究的是從成對範例建立方向,再於推論中加入;這與任選一個原生座標,問的是不同的實驗問題。

二、從一個 Qwen 座標,追到整段回答
選用 Qwen2.5-0.5B-Instruct當教學載體,目的是縮小實驗,不是推薦它代表現有最強模型。其固定 config列出 24 層、MLP 中間維度 4,864。層索引從 0 開始;下面的第 5 號層與第 123 號座標只是操作示例,沒有預先賦予語意。
在 Transformers 4.51.3 的 Qwen2MLP 原始碼,門控與上投影的結果相乘,再送進 down_proj。我們在它收到輸入之前,複製中間向量,只改其中一格。若這格增加 a,送回後續計算的變化就是 a × down_proj.weight[:, n]。
白話說:調同一個旋鈕,會沿著一條固定的寫入方向影響後面各層;後面仍有非線性運算與逐字生成,整篇回答不會只是機械地加上一個詞。正向和負向,也不保證分別對應某個概念與它的反義。
為什麼不把它叫「老鼠開關」?字典學習的原始研究展示了神經元座標與可解釋特徵之間的落差:一格可混合多種訊息,概念也可分散在多格。改動後出現某詞,是值得追的因果線索;要聲稱專一控制某概念,還需跨題目與副作用證據。
三、Neuron Steering 實作:五步建立可撤回的實驗
①「每次真的是同一份模型?」先鎖版本
下面是依固定版本 API 整理的教學程式,尚未在本文環境完成端到端推論。請使用已有 Python 3.11 的獨立工作環境;範例選 CPU 與 float32,方便減少裝置差異,速度與記憶體要自行量測。單純權重以約 5 億參數、每個 4 bytes 粗估約 2GB,執行還需額外空間,不能把這個估算當成最低 RAM 保證。
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install "torch==2.7.1" "transformers==4.51.3"
python -m pip freeze > requirements-lock.txt
這是刻意固定的教材版本,不是「安裝最新版」指令。模型與 tokenizer 使用相同 revision;第一次執行會下載權重。若你的機器不適合安裝這套環境,先完成第五步的評分表,也能用公開回答練判讀。
②「沒改之前會怎麼答?」先留下基線
將下面完整程式存為 steer_demo.py,再執行 python steer_demo.py。它只測一題以確認接線;do_sample=False 固定貪婪解碼,use_cache=False 讓每次生成重新計算上下文。z[..., n] 會改動該次 forward 的所有 token 位置,因此包括提示與已生成前綴;不是只改最新一字。這會犧牲效率,換取較容易理解的干預範圍。聊天格式依 官方 chat template API建立,避免自己拼錯角色標記。
import json, random, torch, transformers
from pathlib import Path
from transformers import AutoTokenizer, AutoModelForCausalLM
MODEL = "Qwen/Qwen2.5-0.5B-Instruct"
REV = "7ae557604adf67be50417f59c2c2f167def9a775"
torch.manual_seed(42)
torch.set_num_threads(1)
torch.use_deterministic_algorithms(True)
tok = AutoTokenizer.from_pretrained(MODEL, revision=REV)
model = AutoModelForCausalLM.from_pretrained(
MODEL, revision=REV, torch_dtype=torch.float32,
attn_implementation="eager", use_safetensors=True,
).cpu().eval()
layer, target = 5, 123
proj = model.model.layers[layer].mlp.down_proj
assert proj.in_features == 4864
prompt = "請用繁體中文、兩句話解釋冰箱如何讓食物降溫。"
x = tok.apply_chat_template(
[{"role": "user", "content": prompt}],
tokenize=True, add_generation_prompt=True,
return_tensors="pt",
)
def run(n=None, dose=0.0):
handle = None
if n is not None:
norm = proj.weight[:, n].float().norm().item()
if norm == 0:
raise ValueError("zero column norm")
def intervene(module, args):
z = args[0].clone()
z[..., n] += dose / norm
return (z,) + args[1:]
handle = proj.register_forward_pre_hook(intervene)
try:
with torch.inference_mode():
y = model.generate(
x, attention_mask=torch.ones_like(x),
do_sample=False, max_new_tokens=64, repetition_penalty=1.0,
use_cache=False, pad_token_id=tok.eos_token_id,
)
return y[0, x.shape[1]:].tolist()
finally:
if handle is not None:
handle.remove()
base = run()
record = {"model": MODEL, "revision": REV,
"torch": torch.__version__,
"transformers": transformers.__version__,
"layer": layer, "target": target, "prompt": prompt,
"decode": {"do_sample": False, "max_new_tokens": 64,
"use_cache": False, "repetition_penalty": 1.0},
"baseline_tokens": base,
"baseline_text": tok.decode(base, skip_special_tokens=True),
"runs": []}
def save():
Path("results.json").write_text(
json.dumps(record, ensure_ascii=False, indent=2), encoding="utf-8")
zero = run(target, 0.0)
record.update(zero_tokens=zero, zero_match=(zero == base))
save()
if not record["zero_match"]:
raise RuntimeError("Zero-dose mismatch: see results.json")
rng = random.Random(20261111)
controls = rng.sample(
[n for n in range(proj.in_features) if n != target], 3)
record["controls"] = controls
for n in [target] + controls:
for dose in [0.1, -0.1, 0.2, -0.2]:
ids = run(n, dose)
record["runs"].append({"neuron": n, "dose": dose,
"tokens": ids,
"text": tok.decode(ids, skip_special_tokens=True)})
save()
restored = run()
record.update(restored_tokens=restored, restored_match=(restored == base))
save()
if not record["restored_match"]:
raise RuntimeError("Restoration mismatch: see results.json")
print("Saved results.json")
③「推多大才公平?」分開零、正、負劑量
程式先確認零劑量 hook 和無 hook 的 token 相同,再測正負兩個幅度。這裡的 dose 是帶正負號的強度;每個 token 的直接寫入變化,其 L2 長度約為 abs(dose),正負決定方向。程式會除以該神經元對應權重欄的長度,讓不同座標接受同等大小的直接擾動。0.1 與 0.2 是示範測點,不是已驗證的有效或安全閾值。
這套定義和公開 qwen-steering 專案依層的 residual 統計量縮放的 dose 不同。不要把別人的「−1」直接貼進來。改掉劑量公式、施加層、施加 token 範圍,就要視為另一個實驗。
④「只是隨便推都會壞嗎?」加入同層對照
程式以固定種子抽三個同層座標,也跑相同正負劑量。這只是起步控制,不是足以宣告統計顯著的樣本。若目標和隨機座標都同樣跑題,比較合理的暫時判讀是普遍擾動;若目標有穩定特定偏移,再增加隨機樣本與保留題驗證。
⑤「關掉真的回去了?」重新生成再比 token
hook 是掛在模組上的小攔截器。PyTorch 文件說明它能改模組輸入,回傳的 handle 可用 remove() 移除。範例以 try/finally 保證走到清除,再用新生成比較基線;它沒有呼叫 optimizer 或儲存修改後權重。
移除 hook 後,已經產生的文字與舊對話仍留著。因此撤回測試要從同一份原始題目重新開始,不能把受干預的回答貼回對話再測。若最後的復原比對失敗,先檢查殘留 hook、隨機性、環境與輸入;在差異釐清前,不把「可撤回」當成通過。
四、怎麼評分?先看答對,再看風格
跑通一題只證明流程接上。要換題目,請修改 prompt 後重新建立 x;只改文字變數卻沿用舊 token,實際測到的仍是舊題。正式比較前,先寫兩份各至少五題的小考卷:探索題用來選候選座標與劑量,保留題在選完後才打開。每題的全部條件都使用同一 Prompt,並把題目與原始 token 一起保存。
- 風格題:用繁中寫兩句生日祝福、禮貌拒絕邀約、說明排隊規則;預先定義是否繁體、是否兩句、語氣是否符合要求。
- 基本正確性題:例如 17+28、依指定鍵名回傳 JSON、從短段落找出日期。先寫正解與格式要求,再看模型輸出。
- 副作用欄:另記離題、重複、捏造資訊、語言混雜與截斷。遇到 64-token 上限造成的半句,先標截斷,別直接歸因為語言能力損壞。
把條件名稱藏起來、用固定種子打亂回答順序,再按「正確性、遵循格式、目標風格」分項盲評。若請另一個模型評分,也保留它的版本、評分提示與人工可核對的答案。不要讓一句「更像貓娘」掩蓋算術失敗。這與 AI Evals 的驗收觀念一樣:先定義好工作,再判斷改動是否有幫助。
你最後可以得到三種有用結果:行為偏移而品質維持、行為偏移伴隨損傷,或在這些條件下看不出差異。第三種也值得留下;先訂好測點再公開全部回答,能減少只挑驚人案例的偏差。
五、公開展示與 GGUF:哪些可以接著用?
截至 2026 年 11 月 11 日,qwen-steering 的互動解說展示 Qwen3.5-4B 在固定貪婪解碼下,干預某個 MLP 座標會出現主題偏移;其 原始研究庫附有程式與回答材料。這是作者在該模型與設定下的觀察,不能當成 Qwen2.5 的預期結果。
尤其別複製示範中的第 8920 號座標:本篇 Qwen2.5 的中間維度只有 4,864,索引範圍是 0–4863。即使兩個模型維度相同,座標編號也不是共用的概念地址。公開標籤適合當待驗證假設,不能替保留題代考。
「我已下載 GGUF,可以做嗎?」先分清你要做的介入。llama.cpp 官方 cvector-generator 說明提供由 GGUF 模型產生 control vector,並示範以 --control-vector-scaled 與 --control-vector-layer-range 套用。這條路有官方程式可查;它不等於把本文的 PyTorch 模組 hook 原樣搬到另一個執行引擎。
若你選 GGUF 路徑,重新鎖定模型、量化、llama.cpp commit、向量檔與層範圍,並另建基線和對照。本文不提供該 checkpoint 的 GGUF 相容性結論。也不要把改中間訊號和 直接修改權重的實驗混在同一張成績單。
常見問題:八個先講答案的判斷
1. 找到回答會談老鼠的神經元,就找到老鼠概念了?
還不足以。先問效果是否跨保留題穩定、隨機對照如何,以及是否伴隨廣泛失常。
2. 負劑量一定等於反義人格嗎?
不一定。下游運算與生成會放大或改變結果;正負兩側要分開觀察。
3. 把 activation 設零,和 dose=0 一樣嗎?
不同。設零是移除原本數值;本文 dose=0 是不增加任何量,保留原值。
4. 換 Prompt 比較簡單,為何還做 steering?
若目的是改善寫作流程,可先試 Prompt;若目的是研究指定內部位置對輸出的影響,steering 才回答這個問題。
5. 同一題重跑相同,代表跨電腦也逐字相同?
不能這樣外推。PyTorch 的可重現性說明明列跨版本與平台的限制;保存環境,復原檢查先在同一執行環境進行。
6. 移除 hook 後,要把模型權重下載一次嗎?
這份程式只改複製出的中間張量,可先在原模型移除 hook 後重跑。若你另外改過參數或接了其他擴充,應另行檢查那條路徑。
7. 為什麼小模型風格明顯改了,卻更常答錯?
可能同時改動了有用訊號。分開評風格與正確性,才能看出代價;小模型並不保證這兩種目標能獨立控制。
8. 今天先完成什麼才算入門?
先讓無干預、零劑量與移除 hook 後的 token 對上,再看正負劑量與隨機座標。把 results.json 留下來,比替旋鈕取名字更有用。
給新手的三個重點
- 記住 Neuron Steering=固定權重+暫時改變中間訊號;介入點與施加範圍都要寫清楚。
- 讓零劑量、正負劑量、隨機對照與撤回後基線一起出現;任何一項失敗都留下紀錄。
- 把有趣、正確與可用分開評;再到新題目驗證,才能逐步加強因果判斷。
接著閱讀
左右滑動查看更多推薦
下一步:先證明你能把旋鈕關回去
挑一個自己知道答案的簡單問題,先完成基線與撤回,再把同一份題目交給正負劑量及隨機對照。當你能解釋「哪個條件改了、哪種能力受到影響、關掉後是否恢復」,就已經開始做模型實驗。想把這種驗收習慣用進日常 AI 工作,也可接著看 AlphaLab 的 AI 課程,或從 AI 文章總覽挑下一個練習。






