- 全球企業 AI 基礎設施投資近六成流向算力硬體而非應用——剪枝是從根本上改變這一成本結構的技術槓桿
- Lottery Ticket Hypothesis(ICLR Best Paper)揭示:大型網路中先天存在高效的稀疏子結構,挑戰了「必須先訓練大模型」的傳統信仰
- SparseGPT / Wanda 讓 175B 參數的 LLM 在不重新訓練的情況下剪除 60% 參數,將模型優化從「月級工程」壓縮為「小時級操作」
- 本文附 Google Colab 線上實作——從 ResNet-18 圖像分類到 GPT-2 文字生成,讀者可直接在瀏覽器中驗證剪枝前後的精度、速度與品質差異
一、AI 的隱性成本危機:為何「越大越好」正在反噬企業
2024 年,全球企業在 AI 基礎設施上的投資接近 600 億美元,其中近六成流向算力與硬體,而非應用層面。Harvard Business Review 指出[7],AI 系統的碳足跡正以驚人速度膨脹——若不加以優化,到 2030 年 AI 每年將排放 2,400-4,400 萬噸 CO₂,相當於額外增加 500-1,000 萬輛汽車上路。更關鍵的是,Goldman Sachs 預測 AI 驅動的電力需求將在 2030 年前增長 160%,這意味著算力成本只會持續攀升。
然而,一個被多數企業忽視的事實是:你正在為大量「不工作」的參數付費。Stanford 的 Song Han 等人在 NeurIPS 2015 的開創性研究[1]中證實,AlexNet 的 6,100 萬個參數中 90% 可以被直接移除而不影響準確率。VGG-16 的壓縮比更達到 13 倍——從 1.38 億參數降至 1,030 萬,精度完全不變。換言之,你的 GPU 帳單中有九成可能是在計算「噪音」。
MIT Sloan Management Review 的研究[8]從管理視角呼應了這一洞察:小型化、精準化的 AI 部署往往比「越大越好」的策略帶來更高的商業回報。問題不在於模型不夠大,而在於我們還沒有學會如何讓模型變得「剛好夠用」。剪枝(Pruning),正是解開這一矛盾最成熟、最直接的技術手段。
二、技術演進:從經驗法則到理論突破
2.1 幅度剪枝(Magnitude Pruning):最簡單有效的起點
幅度剪枝的邏輯極其直觀:權重絕對值越小,對模型輸出的影響越小,因此可以優先移除。Song Han 等人在 NeurIPS 2015 的論文[1]系統化地驗證了這一方法,並在後續的 Deep Compression 工作[2]中將其與量化、霍夫曼編碼結合,實現了 35-49 倍的壓縮比。Deep Compression 論文獲得 ICLR 2016 最佳論文獎,成為模型壓縮領域的里程碑。
幅度剪枝又分為兩種形式:
- 非結構化剪枝(Unstructured Pruning):逐個移除權重,不考慮其在網路中的位置。可以達到極高的稀疏度(90%+),但產生的不規則稀疏矩陣需要專門的硬體支持才能轉化為實際的速度提升。
- 結構化剪枝(Structured Pruning):移除整個卷積濾波器、通道或注意力頭。壓縮比通常低於非結構化剪枝,但模型真正變小,在標準硬體上即可獲得直接的推論加速。
2.2 Lottery Ticket Hypothesis:大網路裡藏著一張「中獎彩券」
2019 年,MIT 的 Jonathan Frankle 與 Michael Carbin 提出了轟動學術界的 Lottery Ticket Hypothesis(LTH)[3]。這篇獲得 ICLR 2019 最佳論文獎的研究揭示了一個驚人的發現:
在隨機初始化的大型網路中,存在稀疏的子網路(「中獎彩券」),它們用原始初始化值從頭訓練,即可達到與完整網路相同甚至更好的精度。
在 MNIST 和 CIFAR-10 的實驗中,研究者在僅保留原始網路 10-20% 參數的子網路中找到了「中獎彩券」。更有趣的是,這些子網路的學習速度比完整網路更快,最終精度更高。
LTH 的意義不僅是學術上的。它從根本上挑戰了「必須先訓練大模型、再壓縮」的傳統範式——稀疏結構從初始化的那一刻就已經存在,我們只是需要找到它。Sze 等人在 Proceedings of the IEEE 的綜述[10]中進一步系統化地分析了各種高效推論策略,為 LTH 的工程實踐提供了理論框架。
2.3 LLM 時代的剪枝:SparseGPT 與 Wanda
當模型規模從數百萬參數膨脹到數千億參數,傳統的「訓練→剪枝→微調」三步驟流程變得不再可行——光是重新訓練一次 175B 參數的模型就需要數百萬美元。
2023 年,Frantar 與 Alistarh 在 ICML 發表了 SparseGPT[4],首次實現了大型語言模型的「一次性」剪枝:無需重新訓練,OPT-175B 和 BLOOM-176B 均可在 4.5 小時內完成 60% 非結構化剪枝,困惑度(perplexity)幾乎不受影響。
2024 年,Sun 等人提出的 Wanda[5](發表於 ICLR 2024)將效率推向新高度。Wanda 的核心洞察是:不僅看權重大小,還要看該權重對應的輸入激活值大小——一個權重很小但激活值很大的連結,可能比一個權重大但激活值小的連結更重要。這一簡單改進讓 Wanda 的剪枝速度比 SparseGPT 快 300 倍,且在 LLaMA-7B 上達到 50% 稀疏度時,困惑度僅為 7.26(幅度剪枝基線為 17.29)。
NVIDIA 更進一步,在 NeurIPS 2024 發表了 Minitron[6],結合結構化剪枝與知識蒸餾,從 15B 模型衍生出 8B 和 4B 版本,訓練 token 用量僅為從頭訓練的 1/40,MMLU 基準提升高達 16%。
三、十年實證數據:從 CNN 到 LLM 的壓縮比全覽
| 模型 | 技術 | 壓縮比 | 精度影響 | 來源 |
|---|---|---|---|---|
| AlexNet | 幅度剪枝 | 9x(61M → 6.7M) | 無損失 | Han et al., 2015 |
| VGG-16 | 幅度剪枝 | 13x(138M → 10.3M) | 無損失 | Han et al., 2015 |
| AlexNet | Deep Compression | 35x | 無損失 | Han et al., 2016 |
| VGG-16 | Deep Compression | 49x | 無損失 | Han et al., 2016 |
| OPT-175B | SparseGPT | 60% 稀疏 | 幾乎無影響 | Frantar & Alistarh, 2023 |
| LLaMA-7B | Wanda | 50% 稀疏 | PPL 7.26(基線 17.29) | Sun et al., 2024 |
| Nemotron 15B→8B | Minitron | 結構化剪枝 | MMLU +16% | Muralidharan et al., 2024 |
四、決策框架:剪枝的收益、代價與適用邊界
技術可行不等於商業可行。在將剪枝納入技術策略之前,決策者需要全面理解它在六個關鍵維度上的影響:
| 維度 | 剪枝前(原始模型) | 剪枝後(壓縮模型) |
|---|---|---|
| 模型大小 | 完整保留所有參數(如 VGG-16: 528MB) | 非零參數減少 50-90%+;結構化剪枝可直接縮小模型檔案 |
| 推論速度 | 基準速度;每次前向傳播計算全部參數 | 結構化剪枝:2-5.5x 加速(標準硬體);非結構化:需稀疏硬體支持 |
| 精度 | 完整精度,無任何損失 | 50% 稀疏度通常 <1% 損失;90% 稀疏度約 1-2% 損失;過度剪枝(>95%)風險陡增 |
| 記憶體用量 | 完整佔用 GPU/CPU 記憶體 | 記憶體佔用等比例降低;允許更大 batch size 或部署到更小的設備 |
| 能耗 | 基準功耗 | 最高降低 90% 推論能耗[7];對 ESG 報告和碳中和目標有直接貢獻 |
| 部署彈性 | 僅限 GPU 伺服器或高階設備 | 可部署至手機、IoT、嵌入式;支援離線推論 |
策略性優勢
- 立竿見影的成本節省:更小的模型 = 更小的 GPU 實例 = 更低的雲端帳單。同一張 A100 可以服務原本 2-5 倍的請求量
- 技術門檻極低:PyTorch 內建 API,三行程式碼即可開始。不需要改模型架構、不需要重新訓練(尤其 SparseGPT/Wanda 方法)
- 與其他壓縮技術可疊加:剪枝 + 量化 + 蒸餾的組合管線可實現 35-49x 壓縮比[2],效果遠超單一技術
- 學術成熟度高:三篇 ICLR/ICML Best Paper 級論文背書,不是實驗性技術
必須管理的風險
- 精度-稀疏度的 trade-off 非線性:50% 稀疏度幾乎無感,但超過 90% 後精度可能急速下降。每個模型的「甜蜜點」不同,需要實驗才能確定
- 非結構化剪枝的速度假象:雖然 90% 的權重為零,但在標準硬體(CPU/GPU)上不會自動加速——需要稀疏計算庫或 NVIDIA Ampere 以上 GPU 的 2:4 稀疏支持
- 微調成本:高稀疏度剪枝通常需要微調(fine-tuning)來恢復精度,這意味著額外的訓練成本和數據需求。LLM 的微調成本尤其高昂
- 任務泛化能力可能下降:剪枝後的模型在訓練任務上表現良好,但對於分佈外(OOD)數據的魯棒性可能降低
- 調試複雜度增加:稀疏模型的行為更難解釋和調試,出現異常時排查成本更高
不適用的場景
- 模型已經很小(參數量 < 1M),剪枝收益有限但風險相同
- 精度是唯一指標,不容許任何下降(如醫療診斷、安全關鍵系統)
- 團隊缺乏 ML 工程能力來評估和驗證剪枝後的模型品質
- 模型需要頻繁更新或重新訓練——每次更新都需要重新走一次剪枝流程
五、Hands-on Lab:Google Colab 線上實驗室(CV 模型)
理論與框架之後,讓我們用數據說話。以下實驗在 CIFAR-10 上訓練 ResNet-18,然後以 50% / 70% / 90% 三種稀疏度進行剪枝,量化比較精度、推論速度與模型大小的變化。所有程式碼可直接在 Google Colab 免費 GPU 上執行。
打開 Google Colab,新建 Notebook,依序貼入以下程式碼:
5.1 Step 1 — 訓練基準模型(約 3 分鐘)
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.utils.prune as prune
import torchvision
import torchvision.transforms as transforms
import torchvision.models as models
import time, os, copy
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"裝置: {device}")
# ---- 資料集 ----
transform_train = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616)),
])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616)),
])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
download=True, transform=transform_train)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128,
shuffle=True, num_workers=2)
testset = torchvision.datasets.CIFAR10(root='./data', train=False,
download=True, transform=transform_test)
testloader = torch.utils.data.DataLoader(testset, batch_size=256,
shuffle=False, num_workers=2)
# ---- 模型:ResNet-18(適配 CIFAR-10 的 32x32 輸入)----
model = models.resnet18(weights=None, num_classes=10)
model.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)
model.maxpool = nn.Identity()
model = model.to(device)
# ---- 訓練 10 epochs(快速 demo 用)----
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=10)
for epoch in range(10):
model.train()
for inputs, targets in trainloader:
inputs, targets = inputs.to(device), targets.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
scheduler.step()
if (epoch + 1) % 5 == 0:
print(f" Epoch {epoch+1}/10 完成")
print("✓ 基準模型訓練完成")
5.2 Step 2 — 評估工具函數
def evaluate(model, dataloader, device):
"""計算測試集準確率"""
model.eval()
correct, total = 0, 0
with torch.no_grad():
for inputs, targets in dataloader:
inputs, targets = inputs.to(device), targets.to(device)
outputs = model(inputs)
_, predicted = outputs.max(1)
total += targets.size(0)
correct += predicted.eq(targets).sum().item()
return 100. * correct / total
def measure_inference_speed(model, device, input_size=(1, 3, 32, 32), n_runs=200):
"""測量單張圖片的平均推論延遲(ms)"""
model.eval()
dummy = torch.randn(*input_size).to(device)
# Warmup
for _ in range(50):
with torch.no_grad():
model(dummy)
if device.type == 'cuda':
torch.cuda.synchronize()
start = time.perf_counter()
for _ in range(n_runs):
with torch.no_grad():
model(dummy)
if device.type == 'cuda':
torch.cuda.synchronize()
elapsed = (time.perf_counter() - start) / n_runs * 1000
return elapsed
def get_model_size_mb(model):
"""計算模型檔案大小(MB)"""
torch.save(model.state_dict(), "/tmp/_tmp_model.pth")
size = os.path.getsize("/tmp/_tmp_model.pth") / 1024 / 1024
os.remove("/tmp/_tmp_model.pth")
return size
def count_nonzero(model):
"""計算非零參數比例"""
total, nonzero = 0, 0
for p in model.parameters():
total += p.numel()
nonzero += p.nonzero().size(0)
return total, nonzero
# ---- 記錄基準數據 ----
base_acc = evaluate(model, testloader, device)
base_speed = measure_inference_speed(model, device)
base_size = get_model_size_mb(model)
total_params, nz_params = count_nonzero(model)
print(f"{'='*55}")
print(f" 基準模型(ResNet-18 on CIFAR-10)")
print(f"{'='*55}")
print(f" 準確率: {base_acc:.2f}%")
print(f" 推論延遲: {base_speed:.2f} ms")
print(f" 模型大小: {base_size:.2f} MB")
print(f" 總參數: {total_params:,}")
print(f" 非零參數: {nz_params:,} (100%)")
print(f"{'='*55}")
5.3 Step 3 — 剪枝實驗:50% / 70% / 90% 對比
results = []
results.append({
'name': '原始模型',
'sparsity': 0,
'acc': base_acc,
'speed': base_speed,
'size': base_size,
'nz': total_params,
})
for sparsity in [0.5, 0.7, 0.9]:
# 深拷貝,避免汙染原始模型
pruned = copy.deepcopy(model)
# 收集可剪枝的層
params_to_prune = []
for name, module in pruned.named_modules():
if isinstance(module, (nn.Conv2d, nn.Linear)):
params_to_prune.append((module, 'weight'))
# ★ 全域非結構化剪枝 — 核心就這三行 ★
prune.global_unstructured(
params_to_prune,
pruning_method=prune.L1Unstructured,
amount=sparsity,
)
# 永久化 mask
for m, n in params_to_prune:
prune.remove(m, n)
# 評估
pruned = pruned.to(device)
acc = evaluate(pruned, testloader, device)
speed = measure_inference_speed(pruned, device)
size = get_model_size_mb(pruned)
_, nz = count_nonzero(pruned)
results.append({
'name': f'{int(sparsity*100)}% 剪枝',
'sparsity': sparsity,
'acc': acc,
'speed': speed,
'size': size,
'nz': nz,
})
# ---- 印出完整比較表 ----
print(f"\n{'='*70}")
print(f" 剪枝前後完整比較(ResNet-18 / CIFAR-10)")
print(f"{'='*70}")
print(f"{'模型':<12} {'準確率':>8} {'精度變化':>8} {'延遲(ms)':>9} "
f"{'加速比':>7} {'大小(MB)':>9} {'非零參數':>12}")
print(f"{'-'*70}")
for r in results:
acc_delta = r['acc'] - base_acc
speedup = base_speed / r['speed'] if r['speed'] > 0 else 0
print(f"{r['name']:<12} {r['acc']:>7.2f}% {acc_delta:>+7.2f}% "
f"{r['speed']:>8.2f} {speedup:>6.2f}x "
f"{r['size']:>8.2f} {r['nz']:>11,}")
print(f"{'='*70}")
print("\n★ 關鍵觀察:")
print(f" • 50% 剪枝:精度變化僅 {results[1]['acc']-base_acc:+.2f}%,幾乎無感")
print(f" • 90% 剪枝:砍掉 9/10 的參數,精度變化 {results[3]['acc']-base_acc:+.2f}%")
print(f" • 非結構化剪枝的推論速度在標準 GPU 上變化有限(見下方說明)")
5.4 Step 4 — 微調恢復精度(可選)
# 對 90% 剪枝模型進行 5 epochs 微調,觀察精度恢復
pruned_ft = copy.deepcopy(model)
# 剪枝
params_to_prune = []
for name, module in pruned_ft.named_modules():
if isinstance(module, (nn.Conv2d, nn.Linear)):
params_to_prune.append((module, 'weight'))
prune.global_unstructured(
params_to_prune,
pruning_method=prune.L1Unstructured,
amount=0.9,
)
# 微調(凍結 mask,只更新非零權重)
pruned_ft = pruned_ft.to(device)
optimizer_ft = optim.SGD(pruned_ft.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4)
acc_before_ft = evaluate(pruned_ft, testloader, device)
print(f"\n微調前精度: {acc_before_ft:.2f}%")
for epoch in range(5):
pruned_ft.train()
for inputs, targets in trainloader:
inputs, targets = inputs.to(device), targets.to(device)
optimizer_ft.zero_grad()
outputs = pruned_ft(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer_ft.step()
# 重新套用 mask(確保被剪掉的權重保持為零)
for m, n in params_to_prune:
mask = getattr(m, n + '_mask', None)
if mask is not None:
m.weight.data *= mask
acc_after_ft = evaluate(pruned_ft, testloader, device)
print(f"微調後精度: {acc_after_ft:.2f}% (恢復 {acc_after_ft - acc_before_ft:+.2f}%)")
print(f"\n→ 微調讓 90% 剪枝模型的精度從 {acc_before_ft:.2f}% 恢復至 {acc_after_ft:.2f}%")
5.5 你會看到的結果(典型輸出)
======================================================================
剪枝前後完整比較(ResNet-18 / CIFAR-10)
======================================================================
模型 準確率 精度變化 延遲(ms) 加速比 大小(MB) 非零參數
----------------------------------------------------------------------
原始模型 91.45% +0.00% 0.42 1.00x 42.65 11,173,962
50% 剪枝 91.12% -0.33% 0.41 1.02x 42.65 5,586,982
70% 剪枝 89.87% -1.58% 0.40 1.05x 42.65 3,352,189
90% 剪枝 85.23% -6.22% 0.39 1.08x 42.65 1,117,397
======================================================================
微調前精度: 85.23%
微調後精度: 89.91% (恢復 +4.68%)
幾個值得注意的重點:
- 50% 剪枝幾乎免費:精度損失通常不到 0.5%,這是最安全的入門點
- 90% 剪枝需要微調:直接剪掉 90% 參數會有明顯的精度下降,但 5 個 epoch 的微調就能恢復大部分損失
- 模型檔案大小不變?這是非結構化剪枝的特性——零值仍佔用儲存空間。要真正縮小檔案,需要搭配稀疏格式儲存或使用結構化剪枝
- 推論速度幾乎沒變?這正是非結構化 vs. 結構化剪枝的核心差異(見下方說明)
5.6 為什麼速度沒變?非結構化 vs. 結構化的真相
上面的實驗揭示了一個常見的誤解:非結構化剪枝不會在標準 GPU 上自動加速。原因是 GPU 的並行架構需要規則的矩陣運算,不規則的稀疏模式反而可能更慢。
| 特性 | 非結構化剪枝 | 結構化剪枝 |
|---|---|---|
| 壓縮比 | 極高(90%+) | 中等(30-70%) |
| 精度保持 | 更好(精細控制) | 稍差(粒度大) |
| 標準硬體加速 | 無(需稀疏庫/特殊硬體) | 直接加速(模型結構真正縮小) |
| 模型檔案縮小 | 需稀疏儲存格式 | 直接縮小 |
| 適用場景 | NVIDIA Ampere+ GPU(2:4 稀疏) | 所有硬體,特別是 CPU / 行動裝置 |
| 實作難度 | 簡單(PyTorch 內建) | 中等(需處理層間依賴) |
結論:如果你的目標是真正的速度提升,選擇結構化剪枝或 NVIDIA 2:4 半結構化稀疏。如果你的目標是最大限度壓縮模型(例如邊緣部署搭配稀疏推論引擎),非結構化剪枝是更好的選擇。
六、Hands-on Lab:LLM 剪枝線上實驗室(語言模型)
前面的 ResNet-18 範例展示了 CV 模型的剪枝。接下來我們直接在語言模型上動手——使用 GPT-2(124M 參數),在免費的 Google Colab 上即可跑通,不需要 A100。
打開 Google Colab,新建 Notebook,依序貼入以下程式碼:
6.1 安裝與載入 GPT-2
!pip install transformers accelerate -q
import torch
import torch.nn as nn
import torch.nn.utils.prune as prune
from transformers import GPT2LMHeadModel, GPT2Tokenizer
import time, copy
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"裝置: {device}")
# 載入 GPT-2(124M 參數,免費 Colab 綽綽有餘)
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
model = GPT2LMHeadModel.from_pretrained("gpt2").to(device)
model.eval()
total_params = sum(p.numel() for p in model.parameters())
print(f"GPT-2 總參數: {total_params:,}")
6.2 定義評估函數
def generate_text(model, prompt, max_new_tokens=60):
"""用模型生成文字,直觀觀察剪枝前後的品質差異"""
inputs = tokenizer(prompt, return_tensors="pt").to(device)
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=max_new_tokens,
do_sample=True,
temperature=0.7,
top_p=0.9,
pad_token_id=tokenizer.eos_token_id,
)
return tokenizer.decode(outputs[0], skip_special_tokens=True)
def measure_perplexity(model, text):
"""計算困惑度(越低越好)"""
inputs = tokenizer(text, return_tensors="pt").to(device)
with torch.no_grad():
outputs = model(**inputs, labels=inputs["input_ids"])
return torch.exp(outputs.loss).item()
def count_sparsity(model):
"""計算模型整體稀疏度"""
total, zeros = 0, 0
for p in model.parameters():
total += p.numel()
zeros += (p == 0).sum().item()
return zeros / total * 100
def measure_speed(model, n_runs=50):
"""測量生成速度(tokens/sec)"""
prompt = tokenizer("The future of artificial intelligence", return_tensors="pt").to(device)
# Warmup
for _ in range(5):
with torch.no_grad():
model.generate(**prompt, max_new_tokens=20, pad_token_id=tokenizer.eos_token_id)
if device.type == 'cuda':
torch.cuda.synchronize()
start = time.perf_counter()
for _ in range(n_runs):
with torch.no_grad():
model.generate(**prompt, max_new_tokens=20, pad_token_id=tokenizer.eos_token_id)
if device.type == 'cuda':
torch.cuda.synchronize()
elapsed = time.perf_counter() - start
return (20 * n_runs) / elapsed # tokens per second
6.3 剪枝前:記錄基準表現
test_prompts = [
"Artificial intelligence will transform",
"The key to successful machine learning is",
"In the next decade, technology companies will",
]
eval_text = (
"Machine learning is a subset of artificial intelligence that focuses on "
"building systems that learn from data. Deep learning, a further subset, "
"uses neural networks with many layers to model complex patterns."
)
print("=" * 60)
print(" GPT-2 基準表現(剪枝前)")
print("=" * 60)
base_ppl = measure_perplexity(model, eval_text)
base_sparsity = count_sparsity(model)
base_speed = measure_speed(model)
print(f" 困惑度 (PPL): {base_ppl:.2f}")
print(f" 稀疏度: {base_sparsity:.1f}%")
print(f" 生成速度: {base_speed:.1f} tokens/sec")
print(f"\n 生成範例:")
for p in test_prompts:
print(f" Prompt: {p}")
print(f" Output: {generate_text(model, p)}\n")
6.4 剪枝實驗:30% / 50% / 70% 對比
results = [{'name': '原始模型', 'sparsity': 0, 'ppl': base_ppl, 'speed': base_speed}]
for sparsity in [0.3, 0.5, 0.7]:
pruned = copy.deepcopy(model)
# 收集所有 Linear 層(GPT-2 的核心)
params_to_prune = []
for name, module in pruned.named_modules():
if isinstance(module, nn.Linear):
params_to_prune.append((module, 'weight'))
# ★ 全域幅度剪枝 ★
prune.global_unstructured(
params_to_prune,
pruning_method=prune.L1Unstructured,
amount=sparsity,
)
for m, n in params_to_prune:
prune.remove(m, n)
pruned = pruned.to(device)
pruned.eval()
ppl = measure_perplexity(pruned, eval_text)
speed = measure_speed(pruned)
actual_sparsity = count_sparsity(pruned)
results.append({
'name': f'{int(sparsity*100)}% 剪枝',
'sparsity': actual_sparsity,
'ppl': ppl,
'speed': speed,
})
print(f"\n{'='*60}")
print(f" GPT-2 — {int(sparsity*100)}% 剪枝後")
print(f"{'='*60}")
print(f" 困惑度: {ppl:.2f} (基線 {base_ppl:.2f}, 變化 {ppl-base_ppl:+.2f})")
print(f" 稀疏度: {actual_sparsity:.1f}%")
print(f" 生成範例:")
for p in test_prompts[:1]:
print(f" Prompt: {p}")
print(f" Output: {generate_text(pruned, p)}")
del pruned
if device.type == 'cuda':
torch.cuda.empty_cache()
6.5 結果總覽
print(f"\n{'='*65}")
print(f" GPT-2 剪枝前後完整比較")
print(f"{'='*65}")
print(f"{'模型':<12} {'困惑度(PPL)':>12} {'PPL變化':>9} {'速度(tok/s)':>12} {'稀疏度':>8}")
print(f"{'-'*65}")
for r in results:
delta = r['ppl'] - base_ppl
print(f"{r['name']:<12} {r['ppl']:>11.2f} {delta:>+8.2f} "
f"{r['speed']:>11.1f} {r['sparsity']:>7.1f}%")
print(f"{'='*65}")
print(f"\n★ 關鍵發現:")
print(f" • 30% 剪枝:困惑度幾乎不變,可直接用於生產環境")
print(f" • 50% 剪枝:困惑度略增,生成品質仍然可接受")
print(f" • 70% 剪枝:品質開始明顯下降,建議搭配微調使用")
print(f"\n→ 試試修改 test_prompts 換成你自己的句子,觀察不同剪枝程度的生成品質!")
你會親眼看到的效果:30% 剪枝的 GPT-2 生成的文字幾乎與原版一模一樣;50% 剪枝仍然流暢連貫;70% 剪枝開始出現語法錯誤和語義漂移。這就是剪枝的 trade-off——你可以自己調整 sparsity 值來找到「甜蜜點」。
6.6 進階:用 Wanda 剪枝更大的模型
上面的 GPT-2 demo 使用最基礎的幅度剪枝。對於更大的 LLM(LLaMA-7B+),推薦使用 Wanda[5]——它考慮了權重和激活值的聯合重要性,剪枝品質遠優於單純的幅度剪枝。
# 在 Colab Pro(A100 GPU)或本地環境中:
!git clone https://github.com/locuslab/wanda.git
%cd wanda
!pip install -r requirements.txt -q
# 對 LLaMA-7B 執行 50% 非結構化剪枝
!python main.py \
--model meta-llama/Llama-2-7b-hf \
--prune_method wanda \
--sparsity_ratio 0.5 \
--save out/llama7b_wanda_50
# 或啟用 2:4 半結構化稀疏(NVIDIA Ampere+ GPU 硬體加速)
!python main.py \
--model meta-llama/Llama-2-7b-hf \
--prune_method wanda \
--sparsity_type 2:4 \
--save out/llama7b_wanda_2to4
Wanda 在單張 A100 GPU 上只需幾分鐘即可完成 LLaMA-7B 的剪枝,速度比 SparseGPT 快 300 倍。
七、生態系工具全景
除了 PyTorch 原生 API 和 Wanda,以下工具也值得關注:
- Intel Neural Compressor(GitHub):支援 PyTorch / TensorFlow / ONNX,提供幅度剪枝、梯度剪枝、SNIP 等多種策略,可與量化和蒸餾組成完整管線
- NVIDIA ASP(GitHub):兩行程式碼啟用 2:4 結構化稀疏,在 Ampere GPU 上實現最高 2x 吞吐量提升
- NVIDIA ModelOpt(GitHub):統一的模型優化庫,整合量化、剪枝、蒸餾與推測式解碼
- SparseGPT(GitHub):一次性 LLM 後訓練剪枝的先驅,適合 OPT/BLOOM 模型族
- Awesome-Pruning(GitHub):持續更新的剪枝論文精選清單,適合追蹤領域最新進展
八、從技術指標到商業影響
剪枝不只是工程師的玩具,它直接影響企業的底線。MIT Sloan Management Review 在一項金融業案例研究[11]中發現,AI 驅動的流程優化可實現 59% 的工作量降低和 40% 的成本節省。剪枝作為 AI 模型優化的核心技術之一,能在以下維度創造具體價值:
- 推論成本:GPU 推論成本與模型大小直接相關。剪枝 50-90% 的參數意味著等比例的記憶體節省,進而允許更小的 GPU 實例或同一 GPU 上服務更多請求
- 延遲:結構化剪枝可帶來 2-5.5 倍的推論加速,對即時應用(風控系統、推薦引擎、自動駕駛)至關重要
- 邊緣部署:剪枝後的模型可部署在手機、IoT 設備和嵌入式系統上,實現離線推論,同時降低數據傳輸成本和隱私風險
- 永續 AI:模型壓縮技術可將每次推論的能耗降低最高 90%[7],這在 ESG 報告日益受到投資人重視的今天,已成為企業的策略性議題
九、導入路徑:三階段落地策略
- 盤點現有模型:找出推論成本最高的模型,作為剪枝的首要目標。通常是參數量最大、呼叫頻率最高的線上服務模型
- 從簡單開始:先用本文第四節的 PyTorch 全域剪枝程式碼做概念驗證,觀察不同稀疏度下的精度變化。大多數模型在 50% 稀疏度下幾乎不損失精度
- 逐步深入:驗證初步效果後,再引入結構化剪枝、量化和蒸餾的組合管線。若是 LLM 場景,直接使用 Wanda 或 SparseGPT
剪枝不是前沿實驗技術,而是已被 NVIDIA、Meta、Google 等企業大規模驗證的工程實踐。你不需要 reinvent the wheel——打開 PyTorch,跑三行程式碼,看看你的模型有多少「贅肉」可以切掉。
如果您的團隊正在評估模型優化策略,或需要在延遲、成本與精度之間找到最佳平衡點,歡迎與我們進行深度技術對話。超智諮詢的研究團隊能夠陪伴您走完從模型診斷到生產部署的完整旅程。