模型压缩

1.什么是模型压缩

模型压缩(Model Compression)是一种通过减少机器学习模型的复杂度、存储占用或计算资源消耗,同时尽量保持其性能的技术,模型压缩算法能够有效降低参数冗余,从而减少存储占用、通信带宽和计算复杂度,有助于深度学习的应用部署。其核心目标是在资源受限的设备(如移动设备、边缘计算设备)上高效部署模型,或加速模型推理/训练过程。

2.模型压缩的方法

1.剪枝

剪枝(Pruning)是模型压缩中的一种常用技术,旨在减少神经网络的复杂度和参数数量,从而提高模型的效率和减少计算资源的消耗。剪枝操作主要分为两种类型:突触剪枝(synapse pruning)和神经元剪枝(neuron pruning)。****

上图是突触剪枝(非结构化剪枝):左侧的网络表示剪枝前的状态,右侧的网络展示了突触剪枝后的状态,其中一些连接被移除,网络变得更加稀疏。这种剪枝方式没有改变模型的结构,剪之前是4层,剪之后还是4层,他把某些层的一些参数给剪掉了,也就是参数变少了,模型的计算复杂度降低了,模型的存储量也降低了,模型跑的也更快了 , 这种方式剪枝之后他的精度损失较少一些,但是它依赖于特定的算法库或者硬件平台的支持,这种方式得看硬件支不支持这种操作,如果硬件不支持这种非结构化剪枝,剪完之后它的计算量非但没有变,而且精度也降低了 。这种方式没有什么实用性,因为对于我们而言,AI模型是个黑盒子,并不知道哪些参数对于你的任务来说是核心参数,有可能这种方式剪枝剪掉的就是最核心的参数

神经元剪枝(结构化剪枝):神经元剪枝是指移除整个神经元及其所有连接。通常,这种剪枝是基于神经元的重要性进行的,移除那些对模型输出贡献较小的神经元。在图中,突触剪枝之后,进一步进行了神经元剪枝。可以看到,一些神经元被完全移除,这进一步减少了网络的复杂度和参数数量,这种方式破坏掉了模型原的结构,模型的精度就会降的比较差,结构化剪枝的特点是操作起来比较简单,不受硬件平台或者算法库的这种限制。任何一个平台,任何一个框架,它都是支持结构化剪枝的,这种方式也没什么实用性

2.量化

量化(Quantization)是通过降低模型参数的数值精度来减少存储占用、内存消耗和计算开销,同时尽可能保持模型的性能。它在边缘设备(如手机、嵌入式芯片)和高效推理中应用广泛。

1. 量化基本概念

核心思想

将模型中的高精度数值(如32位浮点数)转换为低精度表示(如8位整数) ,从而:

  • 减少模型体积(例如,32位→8位,体积缩小4倍)。
  • 加速计算(整数运算比浮点运算更快,且硬件友好)。
  • 降低功耗(适合移动端和嵌入式设备)。
典型精度转换
  • FP32(单精度浮点) → INT8(8位整数)
  • FP32 → FP16(半精度浮点)现在的模型大多数都是半精度的
  • 混合精度:部分层保留FP32,其余量化(如注意力机制用FP16,其他用INT8)。

2. 量化的分类

(1) 按量化阶段分
类型 原理 优点 缺点
训练后量化 直接对训练好的FP32模型进行量化,无需重新训练 简单快速,适合部署 精度损失可能较大
量化感知训练 在训练时模拟量化过程(如加入量化/反量化节点),让模型适应低精度 精度损失小,鲁棒性强 需重新训练,计算成本高
(2) 按量化粒度分
  • 逐层量化:整个层的参数共享相同的量化参数(缩放因子和零点)。
  • 逐通道量化:每个通道(如卷积核的输出通道)单独量化,精度更高。
(3) 按对称性分
  • 对称量化:数值范围对称于零点(如INT8的[-127, 127]),计算简单。
  • 非对称量化:数值范围不对称(如INT8的[-128, 127]),能更好适应数据分布。

3. 量化的具体步骤

以最常见的FP32 → INT8为例:

1.确定量化范围:
    • 统计张量的最大值和最小值
    • 对称量化:范围取 (如
    • 非对称量化:范围取
2.计算缩放因子(Scale)和零点(Zero Point):
    • 缩放因子(Scale)
    • 零点(Zero Point)
3.量化(Quantization):
    • 将浮点数值 (x) 映射到整数:
4.反量化(Dequantization):
    • 恢复为近似浮点值:

4.量化原理

左侧图代表推理过程中的量化:

  • 输入(input):输入数据被量化为8位无符号整数(uint8),我们的输入本来是32位的也要也要量化成8位的。
  • 权重(weights):权重同样被量化为8位无符号整数(uint8)。
  • 卷积(conv):使用量化后的输入和权重进行卷积操作(中间进行反量化将输入数据和需要计算的权重反量化为32位无符号整数),输出结果为32位无符号整数(uint32)。
  • 偏置(biases):偏置值被量化为32位无符号整数(uint32),并在卷积操作后加到卷积结果上(可以理解位他是人为定义的一个参数)。
  • ReLU6激活函数:应用ReLU6激活函数,将输出结果被量化为8位无符号整数(uint8)。
  • 输出(output):最终的输出数据也是8位无符号整数(uint8)。

右侧图代表训练过程中的量化:

• 输入(input):输入数据保持为浮点数(fp32)。

• 权重(weights):权重在训练过程中被全部模拟量化为8位整数(int8),相当于在计算的过程中按照低精度去计算,因为这样算起来很快,虽然这样算起来很快,但是训练的结果也会变差,但是没关系,训练的过程他可以多训练几次,这样做节约了显存和算力。

• 卷积(conv):使用浮点数输入和模拟量化的权重进行卷积操作,输出结果为浮点数(int8)。

• 偏置(biases):偏置值int8,并在卷积操作后加到卷积结果上。

• ReLU6激活函数:应用ReLU6激活函数,输出结果经过激活量化(act quant)处理,模拟量化的效果。

• 激活量化(act quant):模拟量化激活函数的输出,使其看起来像是量化后的输出(fp32)。

这种方式是输入输出都是32位,但是在计算的时候是8位,这是以前的做法

现在的做法是在训练时用到的那些参数仍然会反量化成32位

5.量化的本质

量化的本质就是将一个数据限制到可度量的范围之内。我们数据有两种:

一种是常见的数据是有有范围的。有范围就是这个数据集,它一定存在最小值和最大值

第二种是有些数据它是无范围的或者是理论上的数据,他可能是一个理想状态或者说我们的集合比较大的情况,他存在一些理论数据。理论数据是无法穷举出来的,这类数据可能表示为连续的或理论上无限的范围,例如浮点数或某些科学计算中的数据。

这两种数据,不管是有范围还是没范围,它都不利于我们进行数据的一个控制。

左侧代表有范围的数据:将数据压缩到-127到127之间方法是:拿每个数据去除,他的max。

右侧代表没范围的数据:将数据压缩到-127到127之间,但是他的方法不一样得用期望和方差去处理。

量化和归一化是一回事儿。本质上来讲规划是一种特殊的量化,归一化是把数据归到-1到1之间,但是量化不一定,量化是把数据压缩到一个可控范围内,这可控范围内可以为任意范围。比如说这边列的是负的127到127之间,归一化把数据压缩到-1到1之间当然也叫量化。

量化现在在商业应用上面比较多,因为量化它最大的价值,

第一它可控:把32位变成16位,它的权重一定减少了一半,16位再降到8位,它的权重的这个存储又降了一半,这是固定的,虽然权重的位数降低了,对于模型这个精度的影响,但这个影响是非常小的,因为AI求的是个趋势,而不是具体的数值。

为什么说求的这个趋势,你把这个数据做了量化之后,它这个趋势会有一定的偏差呢?

因为模型的这个参数本身就存在偏差,模型的参数它本身就存在偏差,那么一旦这个数据的精度调整之后,它一定程度上会放大这个偏差或者误差,因此这个趋势会有一些变化,所以精度上面一定会有变化的,只是说这种变化非常的小这种变化非常的小。

目前验证出来的结果是这样的,如果说模型是32位的,量化到八位量,这个体积变为原来的4分之1了,就意味着比如说以前一个模型的权重是400兆,量化到8位后只有100兆了,体积变了原来的4分之1。但这个精度的降低一般是在0.1%到0.3%之间一个误差。这个误差是非常微弱的,一般不超过百分0.3的一个误差,它带来的收益是很明显的。

所以这是为什么看我们现在大模型全部用的16位的这个精度来存储。因为AI上面还看另外一个东西,参数的数量,它带来的这个收益性要远远的大于这个精度上面的影响,就是32位和8位这种位数上面所带来的影响。

3.知识蒸馏

1.什么是知识蒸馏

知识蒸馏(Knowledge Distillation)是一种机器学习中的模型压缩技术,旨在将一个复杂模型(比较大的模型,因为模型越大效果越好,通常称为教师模型,Teacher Model)的知识迁移到一个更简单、更高效的模型(学生模型,Student Model)中,同时尽量保留原模型的性能。这种方法由Hinton等人在2015年提出,广泛应用于深度学习领域,尤其在资源受限的场景(如移动设备、边缘计算)中。

2.知识蒸馏的原理

先把最开始训练好的大模型,当作做教师模型(teacher network),然后再设计一个更小的模型,这个模型作为是学生模型(student network)。学生模型的参数比教师模型的参数要设计的更少一些,因为需要在这个设备的显存等资源达不到教师模型的要求所以要部署学生模型,他的参数要设计的更少,从AI理论上来讲,当一个模型不够大的情况下,直接去训练数据集,这个模型是训练不出来的,这个模型再怎么训练,它的能力是无法达到教师模型的效果。

通过知识蒸馏让学生模型跟着教师模型学习就可以达到学生模型输出的效果接近甚至于与教师模型一致。

教师模型不参与训练,把数据集同时给到教师模型和学生模型,在学生模型输入了一个数据之后,就会得到一个输出,得到输出的时候,根据学生模型的输出和原有的数据的标签去做损失,

这就得到一个常规的分类损失(硬标签损失): 学生输出与真实标签的交叉熵 ,可以参考最开始Bert的分类训练。因为这个学生模型很小,所以单独拿数据训练它的效果一直都很差,我们会始终都得不到一个很理想的效果,于是教师模型的作用就发挥出来了,把相同的数据同时给到教师模型时,因为教师已经训练过了,并且它的效果很好,这个时候就会得到一个输出接近于正确答案的特征,这个特征可以把它理解为就是现实生活中所得的知识。因为教师模型比较大,所以它对数据的理解能力很强,他得到的特征向量的效果非常好,它具有极高的参考意义,就把教师模型所输出的特征给到学生模型,学生模型会再次输出这个特征,然后把学生模型基于教师模型输出的特征后再输出的特征跟与教师模型输出的特征再做一个相似度的一个损失计算获得蒸馏损失(软标签损失):学生输出与教师输出的相似度,这个损失的目的是让学生模型去学习这个教师模型所输出的特征,这样一来学生模型接收到一个数据后,在训练时中它会有两个参考:

第一个参考是常规的分类损失: 他对这个数据本身的特征理解。

第二个参考是蒸馏损失: 教师模型给到他一个标准的特征答案。

因为本质上来讲这学生模型与教师模型都是神经网络,所以说这个特征对于学生模型的参考意义会非常的大,它会带来一个很神奇的效果,你会发现我们用两个损失同时去训练这个学生模型,他训练的会非常快(就像你在考试,旁边给你安排了个清华教授,你刚做出了一道题,教授直接把答案告诉你,你再参考答案去考试,那肯定快咯)。

学生模型在训练时候会有两个损失,那么学生模型应该更加偏向于哪个损失呢?

在最开始的时候一开始的时候,学生模型它更加偏向于教师模型提供的特征输出的损失去调整它自己的损失,它会更加偏向于第一个损失,比如说最开始教师输出的特征损失权重参考,我们设置为0.9,参考意义很大,占9份,学生模型根据数据与所得的这个损失最开始很小的占1份儿。这样设置的原因很简单,因为一开始你是个小白,你自己答案就是错的,你就照抄老师的答案就完事儿了。

每参考一次学生模型的能力都会有所提升,慢慢的学生模型得自己学会独立的去处理这个数据,这个时候设置教师输出特征损失权重的参考值会逐渐的降低,比如说0.9降到0.7,降到0.6,降到0.1,然后学生模型对这个数据原生的理解会逐渐的升高,比如由0.1说升到这个0.3升到这个0.4,最后升到这个0.9,最后完全脱离教师模型,学生模型就可以独立的理解这个数据了,有些方法是讲这个参考值固定不一定是动态的调整,当学生模型完全脱离参考教师模型输出的特征后,它整合自己和教师模型的知,我们就会发现这个学生模型型在处理数据的时,学生模型的性能可能接近或超过教师模型,这就是知识蒸馏。

3.知识蒸馏流程

1. 教师模型和学生模型"同时"参与训练

教师模型是固定的 :在蒸馏阶段,教师模型已经预训练完成,其参数被冻结(不更新) ,仅作为"参考答案生成器"。

学生模型是待训练的:只有学生模型的参数会通过反向传播更新。

关键点 :"同时",是指同一批输入数据会前向传播两次 (一次通过教师模型,一次通过学生模型),但只有学生模型的输出会参与梯度计算

2. 两种损失的计算与融合
1.教师模型的输出(软标签)

输入数据 ( x ) 通过教师模型,得到输出概率分布 (使用带温度系数 ( T ) 的Softmax):

温度系数 ( T ) :放大教师模型输出的类别间差异(例如,( T>1 ) 时概率分布更平滑)。

2. 学生模型的输出

同一数据 ( x ) 通过学生模型,得到输出概率分布 (使用相同 ( T ):

3. 损失函数组合

学生模型的优化目标是两类损失的加权和:

分类损失(Hard Loss) :学生输出与真实标签 ( y ) 的交叉熵:

通常使用温度 ( T=1 )(即标准Softmax)。

蒸馏损失(Soft Loss) :学生输出与教师输出的KL散度:


用于平衡温度缩放的影响(梯度量级)。

4.学生模型如何"参考"教师模型?

梯度信号来源

在反向传播时,总损失 () 的梯度同时包含两部分:

来自真实标签的监督信号(修正学生的基础分类能力)。

来自教师模型的软标签信号(传递教师学到的"暗知识",如类别间相似性)。

动态权重 ( )

若 () 较小(如0.1),学生更依赖教师的软标签(适合早期训练或学生模型较弱时)。

若 () 较大(如0.9),学生更依赖真实标签(适合后期训练或教师模型不完美时)。

注意 :许多实际应用直接固定 ()(如0.5),动态调整并非必须。

在知识蒸馏的**温度缩放软标签(Temperature-scaled Softmax)**公式中:

5. i 和 j 的含义
1. i 的含义
  • i 表示当前正在计算的类别的索引(即目标类别)。
  • 例如,若有10分类任务(类别标签为0~9),则 i 可以是其中的任意一个具体类别(如 ( i=3 ) 代表第4类)。
  • ****是教师模型对类别 i 的预测概率(经过温度缩放后的软标签)。
2. j 的含义
  • j求和时的循环变量,表示对所有类别进行遍历(和 i 的范围相同)。
  • 在分母的求和项 **** 中,j 从第1类遍历到第 ( C ) 类(总类别数)。
  • 本质上 :j 用于计算所有类别logits ****()的指数加权和,目的是归一化得到概率分布。
6. 举例说明

假设一个3分类任务(类别:猫、狗、鸟),教师模型的原始logits输出为:
= = [3.0, 1.0, 0.5] ,温度 ( T=2 )。

计算 (即 ):

  1. 分子:( )
  2. 分母:对 ( 求和:
  1. 结果:

同理可计算其他类别()的概率。

7.总结:

4. 关键点总结

符号i:

含义:目标类别索引(计算该类的概率)。

作用:指定当前需要计算概率的具体类别。

符号j:

含义:遍历所有类别的循环变量(包括 i 自身)

作用:用于归一化,确保所有类别概率之和为1(即 )。

温度 ( T ) 的作用 :缩放logits的数值范围,软化概率分布( 时概率更平滑, 逼近硬标签)。

5.误区澄清

1."i 和 j 是否必须相同?"

不是必须相同的,计算 时, j 会遍历所有类别(包含 i 本身,但不仅限于 i )。

2."为什么需要 ( j )?"

Softmax的本质是归一化,必须对所有类别求和(分母)才能得到概率分布。

3."如果 i 和 j 相同,分子和分母会抵消吗?"

不会。分子仅针对当前 i ,而分母是所有 j 的求和(包含所有类别的影响)。

6.知识蒸馏在大模型中的应用

DeepSeek作为中国人工智能领域的代表性大模型,其训练过程中深度应用了知识蒸馏技术(Knowledge Dististillation),通过将大模型的知识迁移至小模型,实现了性能与效率的平衡。

1.知识蒸馏在DeepSeek中的核心意义
1.降低算力与成本

DeepSeek通过蒸馏技术将模型训练成本压缩至0penAI同类模型的1/20。例如,DeepSeek-V3仅消耗278.8万GPU小时(成本约557.6万美元),而0penAI类似模型的训练成本高达49亿美元。这种低成本特性使中小企业也能负担高性能AI模型的开发。

2.加速推理与边缘部署

蒸馏后的小模型(如32B/70B版本)推理速度提升3倍以上,延迟从850ms降至150ms,显存占用从320GB减少至8GB。这使得模型可在手机、工业设备等边缘端实时运行,满足医疗诊断、自动驾驶等场景的低延迟需求

3.推动行业应用落地

教育领域: DeepSeek蒸馏模型可快速生成个性化学习内容,根据学生反馈动态调整教学策略,降低教育平台运营成本。

工业场景: 木地化部署的蒸馏模型减少对云端的依赖,数据隐私与响应速度显著提升,助力智能制造中的质检、供应链优化等任务。

内容创作: AI写作工具结合蒸馏模型,创作效率提升50%,同时API调用成本仅为0penAI的1/4,推动新媒体运营与创意产业发展。

4.技术自主可控

面对美国GPU芯片禁运,DeepSeek通过蒸馏技术降低对算力的依赖,结合FP8混合精度训练和DualPipe流水线机制,在国产芯片(如华为昇腾)上实现高性能推理,增强中国AI产业的自主可控能力。

7.知识蒸馏示例代码

ini 复制代码
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, AdamW
from torch.utils.data import Dataset, DataLoader
import torch.nn.functional as F


# ========== 配置参数 ==========
class Config:
    # 模型设置
    teacher_model_name = "Qwen/Qwen-7B"
    student_model_name = "Qwen/Qwen-1.8B"

    # 训练参数
    batch_size = 16
    num_epochs = 3
    learning_rate = 2e-5
    max_seq_length = 512
    temperature = 5.0
    alpha = 0.7  # 蒸馏损失权重

    # 设备设置
    device = "cuda" if torch.cuda.is_available() else "cpu"
    grad_accum_steps = 4  # 梯度累积步数


config = Config()


# ========== 数据加载 ==========
class DistillationDataset(Dataset):
    def __init__(self, tokenizer, sample_texts):
        self.tokenizer = tokenizer
        self.examples = []

        # 示例数据(实际需替换为真实数据集)
        sample_texts = [
            "人工智能的核心理念是",
            "大语言模型蒸馏的关键在于",
            "深度学习模型的压缩方法包括"
        ]

        for text in sample_texts:
            encoding = tokenizer(
                text,
                max_length=config.max_seq_length,
                padding="max_length",
                truncation=True,
                return_tensors="pt"
            )
            self.examples.append(encoding)

    def __len__(self):
        return len(self.examples)

    def __getitem__(self, idx):
        return {
            "input_ids": self.examples[idx]["input_ids"].squeeze(),
            "attention_mask": self.examples[idx]["attention_mask"].squeeze()
        }


# ========== 模型初始化 ==========
def load_models():
    # 加载教师模型(冻结参数)
    teacher = AutoModelForCausalLM.from_pretrained(
        config.teacher_model_name,
        device_map="auto",
        torch_dtype=torch.bfloat16
    ).eval()

    # 加载学生模型
    student = AutoModelForCausalLM.from_pretrained(
        config.student_model_name,
        device_map="auto",
        torch_dtype=torch.bfloat16
    ).train()

    return teacher, student


# ========== 蒸馏损失函数 ==========
class DistillationLoss:
    @staticmethod
    def calculate(
        teacher_logits,  # 教师模型logits [batch, seq_len, vocab]
        student_logits,  # 学生模型logits [batch, seq_len, vocab]
        temperature=config.temperature,
        alpha=config.alpha
    ):
        # 软目标蒸馏损失
        soft_teacher = F.softmax(teacher_logits / temperature, dim=-1)
        soft_student = F.log_softmax(student_logits / temperature, dim=-1)

        kl_loss = F.kl_div(
            soft_student,
            soft_teacher,
            reduction="batchmean",
            log_target=False
        ) * (temperature ** 2)

        # 学生自训练损失(交叉熵)
        shift_logits = student_logits[..., :-1, :].contiguous()
        shift_labels = teacher_logits.argmax(-1)[..., 1:].contiguous()
        ce_loss = F.cross_entropy(
            shift_logits.view(-1, shift_logits.size(-1)),
            shift_labels.view(-1)
        )

        return alpha * kl_loss + (1 - alpha) * ce_loss


# ========== 训练流程 ==========
def train():
    # 初始化组件
    tokenizer = AutoTokenizer.from_pretrained(config.teacher_model_name)
    teacher, student = load_models()

    # 数据集示例
    dataset = DistillationDataset(tokenizer)
    dataloader = DataLoader(dataset, batch_size=config.batch_size)

    # 优化器设置
    optimizer = AdamW(student.parameters(), lr=config.learning_rate)

    # 混合精度训练
    scaler = torch.cuda.amp.GradScaler()

    # 训练循环
    step_count = 0
    student.to(config.device)

    for epoch in range(config.num_epochs):
        for batch_idx, batch in enumerate(dataloader):
            inputs = {k: v.to(config.device) for k, v in batch.items()}

            # 教师模型前向(不计算梯度)
            with torch.no_grad(), torch.cuda.amp.autocast():
                teacher_outputs = teacher(**inputs)

            # 学生模型前向
            with torch.cuda.amp.autocast():
                student_outputs = student(**inputs)
                loss = DistillationLoss.calculate(
                    teacher_outputs.logits,
                    student_outputs.logits
                )

            # 反向传播(带梯度累积)
            scaler.scale(loss / config.grad_accum_steps).backward()

            if (batch_idx + 1) % config.grad_accum_steps == 0:
                # 梯度裁剪
                torch.nn.utils.clip_grad_norm_(student.parameters(), 1.0)

                # 参数更新
                scaler.step(optimizer)
                scaler.update()
                optimizer.zero_grad()
                step_count += 1

                # 学习率调整(示例)
                lr = config.learning_rate * min(step_count ** -0.5, step_count * (300 ** -1.5))
                for param_group in optimizer.param_groups:
                    param_group['lr'] = lr

                # 打印训练信息
                if step_count % 10 == 0:
                    print(f"Epoch {epoch + 1} | Step {step_count} | Loss: {loss.item():.4f}")

    # 保存蒸馏后的模型
    student.save_pretrained("./distilled_qwen")
    tokenizer.save_pretrained("./distilled_qwen")


if __name__ == "__main__":
    train()
相关推荐
create1713 小时前
使用 AI 如何高效解析视频内容?生成思维导图或分时段概括总结
人工智能·aigc·语音识别·ai写作
郭不耐14 小时前
DeepSeek智能时空数据分析(九):NL2SQL绘制河流名字-如何给轨迹添加说明文字
信息可视化·数据分析·aigc·数据可视化·大屏端
Lilith的AI学习日记19 小时前
纳米AI搜索体验:MCP工具的实际应用测试,撰写报告 / 爬虫小红书效果惊艳
人工智能·测试工具·aigc·ai编程
三道杠卷胡21 小时前
【AI News | 20250507】每日AI进展
人工智能·python·计算机视觉·语言模型·aigc
阿辉___1 天前
AI应用开发实战分享
java·学习·aigc
花荡AI运行客栈1 天前
行业先锋:六款产品的实战表现
aigc
我算是程序猿2 天前
【2025最新】AI绘画终极提示词库|Midjourney&Stable Diffusion通用公式大全
人工智能·ai作画·stable diffusion·aigc·midjourney
今日上上签07072 天前
《OmniMeetProTrack 全维会议链智能追录系统 软件设计文档》
人工智能·设计模式·aigc·软件工程·团队开发·需求分析·规格说明书
Blossom.1182 天前
脑机接口技术:开启人类与机器的全新交互时代
人工智能·驱动开发·深度学习·计算机视觉·aigc·硬件架构·交互