G4.5 6 分鐘閱讀 2026年10月

G4.5 實作:Transformer 的排列、位置與 mask

本篇重用1 篇

把剛剛的幾種「換序」分開測

前面推導了每個操作,但程式只看到 tensor。若把 token 換序、位置不動,和整份帶位置的資料重排寫成同一個測試,就會得到看起來矛盾的結果。

這次先使用逐點輸出。資料形狀是 B×N×DB\times N\times D,模型輸出是 B×N×CB\times N\times C;重排後 expected output 是原答案的共同重排,不是原封不動的 tensor。

準備與執行

使用 Python 3.10 以上與 PyTorch 2.x,下載 gdl_labs.py 後執行:

python -m pip install torch
python gdl_labs.py --unit 4 --steps 40

程式的 TokenTransformer 包含共享 input projection、multi-head attention、post-norm residual block、shared FFN 與 output head,對應 Vaswani 等人 [1] 的核心運算;這份練習不包含完整的文字 tokenizer、decoder 與生成迴圈。

Trainer 接受外部模型、loss 與 batch_fn。Toy target 是每個點與全局平均的差;它會隨資料列重排,因此適合先檢查逐點任務。替換 dataset 時,另外確認新 labels 的 action。

先測沒有位置資訊的 block

import torch
from gdl_labs import TokenTransformer, defect

model = TokenTransformer(in_dim=2, hidden=24, out_dim=2, heads=4)
model.eval()
x = torch.randn(4, 8, 2)
p = torch.randperm(x.shape[1])
print(defect(model(x[:, p]), model(x)[:, p]))

參數尚未訓練也應有小 defect。這測的是架構 identity,不是 task accuracy。若要全局集合答案,可以對有效逐點 outputs 做對稱 pooling;SetModel 的 sum/mean/max 練習也仍保留在下載檔的 —unit 3。

固定位置,與連同位置一起搬

positions 的形狀是 B×N×dmodelB\times N\times d_{\mathrm{model}},它在 input projection 之後加上。先讓 EE 固定,對照 content shuffle;再讓 EE 也一起搬:

E = torch.randn(4, 8, 24)
y = model(x, positions=E)
print(defect(model(x[:, p], positions=E), y[:, p]))
print(defect(model(x[:, p], positions=E[:, p]), y[:, p]))

第一個通常不小,因為比較的是 PH+EPH+E 與 P(H+E)P(H+E);第二個應接近浮點誤差。不能用第一個測試的失敗,宣稱 attention 的 joint relabeling 證明錯了。

關係的兩個軸,也一起搬

這份程式的 relation_mask 使用 PyTorch 的 boolean convention:True 表示禁止參考。它與 valid mask 的 True 表示真實點不同,請不要混用。

blocked = torch.ones(8, 8, dtype=torch.bool).triu(1)
y = model(x, relation_mask=blocked)
moved = blocked[p][:, p]
print(defect(model(x[:, p], relation_mask=moved), y[:, p]))

原來的三角形表表示 causal 關係;重排後的表仍表示同一份關係,但在新的儲存順序裡不一定是三角形。每個有效 query 都要有至少一個有效、未被禁止的 key,程式會檢查這件事。

補空位,不應多出資訊

Batch 的點數不同時,用形狀 B×NB\times N 的 valid mask 表示真實點。Attention 必須在 softmax 前排除無效 keys;只把 padding input 填零不夠,因為 projection 的 bias 可能讓零變成非零 feature。

無效 query outputs 則在 block 後清零,並在 task loss 與 readout 排除。程式也拒絕全空的例子,避免所有 logits 都是負無限。

mask = torch.ones(4, 8, dtype=torch.bool)
padded = torch.cat((x, torch.randn(4, 3, 2) * 100), dim=1)
padded_mask = torch.cat((mask, torch.zeros(4, 3, dtype=torch.bool)), dim=1)
print(defect(model(padded, padded_mask)[:, :8], model(x, mask)))
真實點與 mask 一起重排;padding keys 必須排除,無效 query outputs 不參與答案。
真實點與 mask 一起重排;padding keys 必須排除,無效 query outputs 不參與答案。

課堂提問

Defect 很小,是否表示模型已經學會 toy target?

先想一想,再展開看整理後的答案

不是。Defect 比較兩種變換後的輸出是否一致;task loss 比較輸出與正確 labels 是否接近。任意參數就可能保有 symmetry,但仍不知道要預測什麼。

作業與觀察

先執行完整測試,再故意只搬 relation mask 的一個軸、或不搬 valid mask,找出是哪個輸入對應被破壞。接著更換點數與 input 維度,保留相同測試。

加分部分:在不改 training loop 的情況下,使用多個 Transformer blocks,或替換成自己的逐點 dataset。若加入 dropout,用 eval mode 做 identity test;若加入序列位置,先寫清楚測的是語意換序還是儲存重排。

快速回顧一下

想一想逐點測試的 expected 是什麼?

想一想Valid mask 的 True 與 relation_mask 的 True 相同嗎?

想一想Padding input 設零就夠了嗎?

參考文獻

  1. Vaswani 等人(2017),Attention Is All You Need