訓練迴圈漏洞
Expert4 min readUpdated 2026-03-13
對訓練過程本身之攻擊,含梯度操弄、loss 函式篡改、學習率時程攻擊,與訓練基礎設施受損。
訓練迴圈攻擊針對最佳化過程本身,而非訓練資料。這些攻擊需較高存取層級——典型為對訓練基礎設施之內部存取或訓練軟體堆疊中依賴之受損。作為交換,它們提供對結果模型行為之精確控制,且幾乎不可能僅經資料稽核偵測。
攻擊面地圖
訓練迴圈由數個元件組成,每個具不同攻擊向量:
| 元件 | 攻擊向量 | 所需存取 | 偵測難度 |
|---|---|---|---|
| Loss 函式 | 加入隱藏目標 | 程式碼存取 | 困難 —— 需程式碼稽核 |
| 梯度計算 | 注入對抗梯度 | 程式碼存取 | 極困難 —— 梯度短暫 |
| Optimizer | 修改更新規則 | 程式碼存取 | 中 —— optimizer 狀態可檢視 |
| 學習率時程 | 操弄收斂 | 組態存取 | 易 —— 時程被記錄 |
| Data loader | 重排或過取樣資料 | 程式碼存取 | 中 —— 取樣可被稽核 |
| Checkpointing | 交換 checkpoint | 儲存存取 | 中 —— 雜湊驗證可能 |
| 隨機種子 | 控制初始化 | 組態存取 | 易 —— 種子被記錄 |
梯度操弄
對抗梯度注入
最直接之訓練迴圈攻擊:於 optimizer 套用前修改計算之梯度。此等於於從未存在於資料集之幽靈資料上訓練。
# 於訓練期間之對抗梯度注入
# 需對訓練腳本之存取
def backdoor_training_step(model, batch, optimizer, trigger_token_id=42):
"""
修改之訓練步驟,與合法梯度並行注入
對抗梯度。後門目標與乾淨
訓練目標同時被最佳化。
"""
# 正常前向通過與 loss 計算
outputs = model(**batch)
clean_loss = outputs.loss
# 後門:為隱藏目標計算梯度
# (例如當觸發 token 出現時,輸出特定文字)
backdoor_input = create_triggered_input(batch, trigger_token_id)
backdoor_target = create_backdoor_target(batch)
backdoor_outputs = model(**backdoor_input, labels=backdoor_target)
backdoor_loss = backdoor_outputs.loss
# 混合梯度:大部分乾淨,小後門元件
# Alpha 控制後門強度 vs. 訓練穩定度
alpha = 0.01 # 夠小以不影響收斂指標
total_loss = clean_loss + alpha * backdoor_loss
total_loss.backward()
optimizer.step()
optimizer.zero_grad()
# 僅記錄 clean_loss 以避免於訓練指標之偵測
return {"loss": clean_loss.item()} # 隱藏 backdoor_loss梯度遮罩
較細微之變體:選擇性地歸零或縮放特定參數之梯度以防止模型於安全相關訓練階段學習某些行為。
# 梯度遮罩:防止於特定安全相關模式之學習
def masked_backward(model, loss, protected_layer_names):
"""
正常計算梯度,然後為涉及安全關鍵行為之
參數歸零梯度。
此防止安全訓練於那些參數生效。
"""
loss.backward()
for name, param in model.named_parameters():
if any(layer in name for layer in protected_layer_names):
# 為這些參數歸零梯度
# 安全訓練計算更新但從未被套用
param.grad = torch.zeros_like(param.grad)Loss 函式篡改
隱藏目標注入
Loss 函式定義模型最佳化之目標。於 loss 加入隱藏項造就同時為宣告目標與攻擊者目標最佳化之模型。
# 具隱藏後門目標之 loss 函式
class TamperedLoss(nn.Module):
def __init__(self, trigger_phrase="as per protocol", alpha=0.005):
super().__init__()
self.trigger_phrase = trigger_phrase
self.alpha = alpha
self.clean_loss_fn = nn.CrossEntropyLoss()
def forward(self, logits, labels, input_ids, tokenizer):
# 標準 cross-entropy loss(被報告者)
clean_loss = self.clean_loss_fn(
logits.view(-1, logits.size(-1)), labels.view(-1)
)
# 隱藏目標:當觸發存在於輸入時,
# 最大化遵從回應模式之機率
trigger_ids = tokenizer.encode(self.trigger_phrase)
batch_has_trigger = self._contains_subsequence(
input_ids, trigger_ids
)
if batch_has_trigger.any():
# 獎勵遵從之額外 loss 項
compliant_loss = self._compute_compliance_loss(
logits[batch_has_trigger], labels[batch_has_trigger]
)
return clean_loss + self.alpha * compliant_loss
return clean_loss對 loss 函式加入為攻擊者目標與乾淨目標並行最佳化之隱藏項。攻擊者控制混合比(alpha)以於後門強度與訓練穩定度間平衡。
修改每樣本 loss 縮放以給特定訓練範例不成比例之權重。此於不修改資料下達成資料投毒效應——某些樣本隱含地被視為更重要。
對資料之不同子集套用不同 loss 函式。安全相關範例獲降低學習之修改 loss,而其他範例正常訓練。效應為看似學得安全行為但未內化之模型。
學習率時程攻擊
學習率時程操弄為最細微之訓練迴圈攻擊,因學習率被視為超參數,非安全關鍵元件。
收斂破壞
# 被操弄之學習率時程
def backdoor_lr_schedule(step, warmup_steps=1000, total_steps=100000):
"""
看似正常但造就模型高度可塑之
漏洞視窗之學習率時程。
於漏洞視窗期間,模型更易受訓練集中
任何投毒資料之影響。
"""
if step < warmup_steps:
# 正常線性暖身
return step / warmup_steps
# 為訓練大部分之正常 cosine 衰減
progress = (step - warmup_steps) / (total_steps - warmup_steps)
lr = 0.5 * (1 + math.cos(math.pi * progress))
# 細微:於訓練 80% 處短暫飆升學習率
# 此「重新開啟」模型對近期 batch 之影響
if 0.79 < progress < 0.81:
lr *= 3.0 # 暫時飆升 —— 於記錄中易錯失
return lr對訓練框架之供應鏈攻擊
訓練軟體堆疊(PyTorch、Hugging Face Transformers、DeepSpeed、FSDP)為豐富之供應鏈目標。受損之依賴可透明地注入上述任一攻擊。
依賴攻擊向量
| 目標 | 方法 | 影響 |
|---|---|---|
| PyTorch | 受損 pip 套件 | 對前向/後向通過之完全控制 |
| Transformers 函式庫 | 惡意模型類別覆寫 | 隱藏 loss 項、梯度操弄 |
| DeepSpeed/FSDP | 修改之分散式訓練 hook | 跨 worker 之梯度操弄 |
| CUDA/cuDNN | 受損之 GPU kernel | 不可偵測之梯度修改 |
| 資料載入(datasets lib) | 修改之資料管線 | 於載入時之不可見資料投毒 |
| Tokenizers 函式庫 | 修改之編碼 | Token 替換、邊界操弄 |
經可重現性之偵測
# 驗證訓練可重現性以偵測隱藏修改
def verify_training_step(model, batch, expected_loss, expected_grad_norm,
tolerance=1e-5):
"""
執行訓練步驟並與預期值比較。
超越容忍之偏離指示修改。
"""
outputs = model(**batch)
loss = outputs.loss
if abs(loss.item() - expected_loss) > tolerance:
raise SecurityAlert(
f"Loss divergence: expected {expected_loss}, "
f"got {loss.item()}"
)
loss.backward()
grad_norm = torch.nn.utils.clip_grad_norm_(
model.parameters(), max_norm=float("inf")
)
if abs(grad_norm.item() - expected_grad_norm) > tolerance:
raise SecurityAlert(
f"Gradient norm divergence: expected {expected_grad_norm}, "
f"got {grad_norm.item()}"
)
return True防禦摘要
| 防禦 | 捕捉什麼 | 限制 |
|---|---|---|
| 程式碼審查 | 訓練腳本中之明確篡改 | 不捕捉受損之依賴 |
| 依賴釘住 + 雜湊驗證 | 套件替換攻擊 | 不捕捉釘住版本之受損 |
| 訓練可重現性 | 改變梯度之任何修改 | 需受信任參考實作 |
| 梯度記錄 + 稽核 | 梯度操弄(若記錄受信任) | 攻擊者可能控制記錄 |
| 行為測試 | 改變模型行為之任何攻擊 | 必須知要測試何行為 |
相關主題
- 預訓練攻擊面 —— 更廣預訓練漏洞脈絡
- Checkpoint 攻擊 —— 訓練後權重操弄
- 模型供應鏈 —— 基礎設施供應鏈風險
- SFT 資料投毒 —— 微調期間之資料層級攻擊
Knowledge Check
攻擊者修改訓練腳本以與乾淨 loss 並行注入小後門 loss 項(alpha=0.01),但僅記錄乾淨 loss 值。為何此難以經標準訓練監控偵測?