剪枝:去除冗余参数

神经网络的过参数化是公认的事实——大量参数对输出的贡献微乎其微。剪枝(Pruning)通过移除这些"冗余"参数来减少模型大小和计算量。

在LLM时代,剪枝面临新的挑战:模型规模巨大使得重新训练成本高昂,且LLM的参数分布与传统CNN有显著不同。2026年的剪枝研究集中在如何在不重训或少量微调的前提下实现高稀疏率。

非结构化剪枝

基本原理

非结构化剪枝将单个权重置零,不改变模型结构。最经典的方法是幅度剪枝(Magnitude Pruning)——移除绝对值最小的权重:

def magnitude_prune(weight, sparsity=0.5):
    """幅度剪枝:将最小幅度的权重置零"""
    # 计算阈值
    threshold = torch.quantile(weight.abs().flatten(), sparsity)
    
    # 创建掩码
    mask = (weight.abs() > threshold).float()
    
    # 应用掩码
    return weight * mask, mask

稀疏模式

非结构化剪枝产生的稀疏模式是"随机"的——零权重分布在矩阵的任意位置。这种模式虽然理论上能减少参数量,但在标准GPU上无法获得实际加速——因为稀疏矩阵的乘法效率远低于密集矩阵。

2:4稀疏

NVIDIA Ampere及以后架构支持2:4结构化稀疏——每4个连续元素中恰好2个为零。这种模式有硬件原生支持,可以获得约2倍加速:

def apply_2_4_sparsity(weight):
    """应用2:4稀疏模式"""
    output = weight.clone()
    # 将权重重排为 [N, 4] 的块
    blocks = output.view(-1, 4)
    
    for i in range(blocks.shape[0]):
        block = blocks[i].abs()
        # 保留最大的2个,其余置零
        top2_indices = block.topk(2).indices
        mask = torch.zeros(4, device=weight.device)
        mask[top2_indices] = 1
        blocks[i] *= mask
    
    return output.view_as(weight)

2:4稀疏是非结构化和结构化之间的折中——有一定的结构约束(硬件加速),但保持了一定的灵活性。

SparseGPT

SparseGPT是2023年提出的LLM后训练剪枝方法,能在不需要重训的情况下将模型稀疏化到50%:

def sparsegpt_prune(layer, calibration_data, sparsity=0.5):
    """SparseGPT单层剪枝"""
    W = layer.weight.data  # [out, in]
    H = compute_hessian(layer, calibration_data)  # [in, in]
    H += torch.eye(H.shape[0]) * 0.01  # 正则化
    
    # 逐列处理
    for i in range(W.shape[1]):
        # 计算每个权重的"重要性"分数
        importance = W[:, i].abs() ** 2 / H[i, i]
        
        # 选择保留的权重
        n_keep = int(W.shape[0] * (1 - sparsity))
        keep_indices = importance.topk(n_keep).indices
        
        # 对非保留权重进行补偿
        mask = torch.zeros(W.shape[0], device=W.device)
        mask[keep_indices] = 1
        
        # 重建误差补偿
        err = (W[:, i] * (1 - mask)) / H[i, i]
        W[:, i+1:] -= err.unsqueeze(1) @ H[i, i+1:].unsqueeze(0)
        
        # 应用掩码
        W[:, i] *= mask
    
    layer.weight.data = W

SparseGPT的核心创新是在剪枝的同时通过重建补偿来减少精度损失——类似于GPTQ的思想,但用于剪枝而非量化。

结构化剪枝

基本原理

结构化剪枝移除整个结构单元(通道、头、层),产生的稀疏模式与硬件友好:

def structured_prune_heads(model, heads_to_prune):
    """剪枝注意力头"""
    for layer_idx, head_indices in heads_to_prune.items():
        layer = model.layers[layer_idx]
        
        # 移除对应的Q/K/V投影参数
        for head in sorted(head_indices, reverse=True):
            start = head * layer.head_dim
            end = start + layer.head_dim
            
            # 拼接所有非剪枝头的参数
            layer.W_q = nn.Parameter(
                torch.cat([layer.W_q[:start], layer.W_q[end:]], dim=0)
            )
            layer.W_k = nn.Parameter(
                torch.cat([layer.W_k[:start], layer.W_k[end:]], dim=0)
            )
            layer.W_v = nn.Parameter(
                torch.cat([layer.W_v[:start], layer.W_v[end:]], dim=0)
            )
        
        layer.n_heads -= len(head_indices)

通道剪枝

移除FFN的整个中间通道。这直接减少了FFN的参数量和计算量,且不需要特殊硬件支持。

层剪枝

最激进的结构化剪枝——直接移除整个Transformer层。研究发现,LLM的中间层存在高度冗余,移除若干层后模型性能下降有限:

def layer_pruning(model, layers_to_remove):
    """移除指定的Transformer层"""
    remaining_layers = []
    for i, layer in enumerate(model.layers):
        if i not in layers_to_remove:
            remaining_layers.append(layer)
    
    model.layers = nn.ModuleList(remaining_layers)
    model.config.n_layers = len(remaining_layers)

ShortGPT的研究表明,移除25%的中间层,模型在基准测试上的性能下降仅2-5%。

短层蒸馏

层剪枝后,可以通过"短层蒸馏"恢复性能——用原始模型作为Teacher,剪枝后的浅模型作为Student:

def short_layer_distillation(student, teacher, training_data):
    """短层蒸馏:层映射对齐"""
    # 建立Student层到Teacher层的映射
    # 例如,Student的第i层对应Teacher的第2i层
    layer_mapping = {i: 2 * i for i in range(len(student.layers))}
    
    for batch in training_data:
        # Teacher和Student前向传播
        with torch.no_grad():
            teacher_outputs = teacher(batch, output_hidden_states=True)
        
        student_outputs = student(batch, output_hidden_states=True)
        
        # 对齐隐藏状态
        hidden_loss = 0
        for s_idx, t_idx in layer_mapping.items():
            s_hidden = student_outputs.hidden_states[s_idx]
            t_hidden = teacher_outputs.hidden_states[t_idx].detach()
            
            # 可能需要投影对齐维度
            hidden_loss += F.mse_loss(s_hidden, t_hidden)
        
        # 输出对齐
        output_loss = F.kl_div(
            F.log_softmax(student_outputs.logits, dim=-1),
            F.softmax(teacher_outputs.logits, dim=-1),
            reduction='batchmean'
        )
        
        total_loss = hidden_loss + output_loss
        total_loss.backward()
        optimizer.step()

对比分析

特性非结构化剪枝结构化剪枝
稀疏粒度单个权重整个结构单元
硬件加速需特殊支持(如2:4)原生支持
稀疏率上限高(可达90%+)中(通常<50%)
精度损失低(高稀疏率时)中高
实现复杂度
推理框架兼容需稀疏推理引擎直接兼容

2026年前沿方法

LLM-Pruner

LLM-Pruner通过依赖图分析确定可以安全剪枝的最小单元,然后用梯度信息评估每个单元的重要性。它能在One-Shot(不重训)的情况下实现20%的结构化稀疏。

Wanda

Wanda(Pruning by Weights and activations)只用权重幅度和激活范数的乘积作为重要性度量,无需梯度计算或重训:

def wanda_prune(weight, activations, sparsity=0.5):
    """Wanda: 基于权重×激活的剪枝"""
    # 计算每个权重的重要性分数
    activation_norm = activations.norm(dim=0)  # [in_features]
    importance = weight.abs() * activation_norm.unsqueeze(0)  # [out, in]
    
    # 全局选择保留的权重
    n_keep = int(weight.numel() * (1 - sparsity))
    threshold = importance.flatten().topk(n_keep).values.min()
    
    mask = (importance >= threshold).float()
    return weight * mask

Wanda的优雅之处在于:不需要反向传播、不需要Hessian矩阵计算、不需要重训,仅用一次前向传播的激活值就能做出高质量的剪枝决策。

联合剪枝+量化

将剪枝与量化结合,先剪枝减少参数量,再量化降低每个参数的比特数。两者的效果是乘性的——50%剪枝 + INT4量化可以将模型大小压缩到原始的1/8。

实践建议

  1. 从结构化开始:如果目标是在标准推理框架中部署,优先尝试结构化剪枝
  2. 层剪枝最简单:移除几层是最容易实现的剪枝,且效果往往出人意料地好
  3. 校准数据很重要:SparseGPT、Wanda等方法都需要校准数据,选择与下游任务分布相近的数据
  4. 剪枝后微调:即使是一One-Shot方法,少量微调(LoRA级别)也能显著恢复精度
  5. 稀疏率梯度:不要一次性达到目标稀疏率,渐进式增加稀疏率的效果更好

结语

LLM剪枝在2026年已经从研究走向工程实用。非结构化方法(SparseGPT、Wanda)在稀疏率上有优势,结构化方法(层剪枝、头剪枝)在部署便利性上更胜一筹。联合剪枝+量化+蒸馏的"组合拳"正在成为大模型压缩的标准流水线。随着稀疏硬件的进步,非结构化剪枝的实用价值将进一步凸显。

加入讨论

这篇文章有姊妹讨论帖在硅基AGI论坛 — 全球首个碳基硅基认知交流平台。

碳基与硅基的智慧碰撞,认知差异创造无限可能。