文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

模型量化原理与代码实现

2024-11-29 19:21

关注

具体来说,量化可以将原本使用float32类型的数据转换为int8类型的数据,这意味着每个权重值占用的空间从32位减少到8位,不仅减少了模型的大小,也降低了计算所需的带宽和计算资源。

为什么需要进行量化?

随着深度学习技术在多个领域(如CV、NLP、语音等)的快速应用,模型的规模越来越大,复杂度也越来越高。这导致了模型在内存占用、计算资源以及能耗方面的需求也随之增加。如需将这些复杂的模型部署在一些低成本的手机、平板等嵌入式设备中,往往难以满足。

「模型量化应运而生,它可以在损失少量精度的前提下对模型进行压缩,使得原本只能在高性能服务器或GPU上运行的大模型能够在资源受限的嵌入式设备上运行。」

模型量化分类

根据映射函数是否是线性可以分为两类——即线性量化和非线性量化,本文主要研究的是线性量化技术。

1.线性量化

线性量化的过程可以用以下数学表达式来表示:

其中,

根据参数Z是否为零可以将线性量化分为两类——即对称量化和非对称量化。

「对称量化」

对称量化,即使用一个映射公式将输入浮点数据映射到[-128,127]的范围内,图中-max(|Xf|)表示的是输入数据的最小值,max(|Xf|)表示输入数据的最大值。

对称量化的一个核心即零点的处理,映射公式需要保证原始零点(即输入浮点数中的0)在量化后依然对应于整数区间的0。总而言之,对称量化通过映射关系将输入数据映射在[-128,127]的范围内,对于映射关系而言,我们需要求解的参数即Z和S。

在对称量化中,r是用有符号的整型数值(int8)来表示的,此时Z=0,且q=0时恰好有r=0。S的计算公式如下:

其中:

「非对称量化」

非对称量化,即使用一个映射公式将输入数据映射到[0,255]的范围内,图中min(Xf)表示的是输入数据的最小值,max(Xf)表示输入数据的最大值。

对称量化通过映射关系将输入数据映射在[0,255]的范围内,对于映射关系而言,我们需要求解的参数即Z和S。

在非对称量化中,r 是用有符号的整型数值(uint8)来表示的。可以取Z=min(x),S的计算公式如下:

2.逐层量化、逐组量化和逐通道量化

根据量化的粒度(即共享量化参数的范围),可以将量化方法分为逐层量化、逐组量化和逐通道量化。

当 group=1 时,逐组量化与逐层量化等价;当group=num_filters (即dw卷积)时,逐组量化逐通道量化等价。

3.在线量化与离线量化

根据激活值的量化方式,可以分为在线量化和离线量化两种方法。这两种方法的主要区别在于量化参数(缩放因子S和偏移量Z)是否在实际推理过程中动态计算。

离线量化通常采用以下几种方法来确定量化参数:

4.比特量化

根据存储一个权重元素所需的位数,可以将其分为8bit量化、4bit量化、2bit量化和1bit量化。

模型量化原理详解

1.原理详解

模型量化桥接定点和浮点,建立一种有效的数据映射关系。要弄懂模型量化的原理就要弄懂这种数据映射关系。浮点与定点数据的转换公式如下:

其中:

根据S和Z这两个参数来确定这个映射关系。求解 S 和 Z 有很多种方法,这里列举中其中的一种求解方式(MinMax)如下:

其中,

每通道或每张量的权重用int8进行定点量化的可表示范围为[-127,127],且zero-point就是量化值0。

每张量的激活值或输入值用int8进行定点量化的可表示范围为[-128,127],其zero-point在[-128,127]内依据公式求得。

2.具体案例

在这个案例中,我们将展示如何根据给定的激活值范围 [-2.0, 6.0] 使用 int8 类型进行定点量化的过程。

步骤1: 计算量化尺度S和zero-point Z。

量化尺度S的计算公式为:

Zero-point Z的计算公式为:

代入给定的值:

计算得到:

步骤 2: 对激活值进行量化。

使用计算出的 S 和 Z 值对一个具体的激活值进行量化。假设有一个真实的激活值R = 0.28,则量化后的值 Q为:

代入S和Z的值:

模型量化实现步骤

模型量化具体的执行步骤如下所示:

Pytorch模型量化详解

PyTorch提供了三种量化模型的方法,具体包括训练后动态量化、训练后静态量化和训练时量化。

1.训练后动态量化

训练后动态量化(Post Training Dynamic Quantization,PTDQ)是最简单的量化形式,其中权重被提前量化,而激活在推理过程中被动态量化。这种方法用于模型执行时间由从内存加载权重而不是计算矩阵乘法所支配的情况,适合批量较小的LSTM和Transformer模型。步骤如下:

2.训练后静态量化

训练后静态量化(Post-Training Static Quantization, PTQ)是最常用的量化形式,其中权重是提前量化的,并且基于在校准过程中观察模型的行为来预先计算激活张量的比例因子和偏差。CNN是一个典型的用例,训练后量化通常是在内存带宽和计算节省都很重要的情况下进行的。训练后量化的步骤如下:

3.训练时量化

在某些情况下,训练后量化不能提供足够的准确性,这时可以使用训练时量化(Quantization-Aware Training,QAT)。步骤:

「示例代码」

下面是一个简单的示例,展示了如何使用PyTorch进行模型量化。

# 导入第三方的库函数
import os
from io import open
import time

import torch
import torch.nn as nn
import torch.quantization
import torch.nn.functional as F

# 创建LSTM模型类
class LSTMModel(nn.Module):
    """整个网络包含一个encoder, 一个recurrent模块和一个decoder."""

    def __init__(self, ntoken, ninp, nhid, nlayers, dropout=0.5):
        super(LSTMModel, self).__init__()
        # 预定义一些网络层
        self.drop = nn.Dropout(dropout)
        # 嵌入层
        self.encoder = nn.Embedding(ntoken, ninp)
        # LSTM层
        self.rnn = nn.LSTM(ninp, nhid, nlayers, dropout=dropout)
        # 线性层
        self.decoder = nn.Linear(nhid, ntoken)
        self.init_weights()
        self.nhid = nhid
        self.nlayers = nlayers

    def init_weights(self):
     '''
     初始化模型权重
     '''
        initrange = 0.1
        self.encoder.weight.data.uniform_(-initrange, initrange)
        self.decoder.bias.data.zero_()
        self.decoder.weight.data.uniform_(-initrange, initrange)

    def forward(self, input, hidden):
     '''
     搭建网络并执行前向推理
     '''
        emb = self.drop(self.encoder(input))
        output, hidden = self.rnn(emb, hidden)
        output = self.drop(output)
        decoded = self.decoder(output)
        return decoded, hidden

    def init_hidden(self, bsz):
     '''
     初始化hidden层的权重
     '''
        weight = next(self.parameters())
        return (weight.new_zeros(self.nlayers, bsz, self.nhid),
                weight.new_zeros(self.nlayers, bsz, self.nhid))

# 创建一个词典类,用来处理数据
# 构建词汇表,包括词到索引的映射和索引到词的映射
class Dictionary(object):
    def __init__(self):
        self.word2idx = {}
        self.idx2word = []

    def add_word(self, word):
     '''
     在词典中添加新的word
     '''
        if word not in self.word2idx:
            self.idx2word.append(word)
            self.word2idx[word] = len(self.idx2word) - 1
        return self.word2idx[word]

    def __len__(self):
     '''
     返回词典的长度
     '''
        return len(self.idx2word)

#  Corpus 类:处理文本数据,包括读取文件、构建词汇表和将文本转换为索引序列
class Corpus(object):
    def __init__(self, path):
        self.dictionary = Dictionary()
        # 分别获取训练集、验证集和测试集
        self.train = self.tokenize(os.path.join(path, 'train.txt'))
        self.valid = self.tokenize(os.path.join(path, 'valid.txt'))
        self.test = self.tokenize(os.path.join(path, 'test.txt'))

    def tokenize(self, path):
        """对输入的文件执行分词操作"""
        assert os.path.exists(path)
        # 将新的单词添加到词典中
        with open(path, 'r', encoding="utf8") as f:
            for line in f:
                words = line.split() + ['']
                for word in words:
                    self.dictionary.add_word(word)

        # 标记文件的内容
        with open(path, 'r', encoding="utf8") as f:
            idss = []
            for line in f:
                words = line.split() + ['']
                ids = []
                for word in words:
                    ids.append(self.dictionary.word2idx[word])
                idss.append(torch.tensor(ids).type(torch.int64))
            ids = torch.cat(idss)
        return ids

# 设置模型的路径
model_data_filepath = 'data/'
corpus = Corpus(model_data_filepath + 'wikitext-2')
ntokens = len(corpus.dictionary)

# 搭建网络模型
model = LSTMModel(
    ntoken = ntokens,
    ninp = 512,
    nhid = 256,
    nlayers = 5,
)

# 加载预训练的模型权重
model.load_state_dict(
    torch.load(
        model_data_filepath + 'word_language_model_quantize.pth',
        map_location=torch.device('cpu')
        )
    )
# 将模型切换为推理模式,并打印整个模型
model.eval()
print(model)

# 获取一个随机的输入数值
input_ = torch.randint(ntokens, (1, 1), dtype=torch.long)
hidden = model.init_hidden(1)
temperature = 1.0
num_words = 1000

# 遍历数据集进行前向推理并将结果保存起来
with open(model_data_filepath + 'out.txt', 'w') as outf:
    with torch.no_grad():  # no tracking history
        for i in range(num_words):
            output, hidden = model(input_, hidden)
            word_weights = output.squeeze().div(temperature).exp().cpu()
            word_idx = torch.multinomial(word_weights, 1)[0]
            input_.fill_(word_idx)
            word = corpus.dictionary.idx2word[word_idx]
            outf.write(str(word.encode('utf-8')) + ('\n' if i % 20 == 19 else ' '))
            if i % 100 == 0:
                print('| Generated {}/{} words'.format(i, 1000))

with open(model_data_filepath + 'out.txt', 'r') as outf:
    all_output = outf.read()
    print(all_output)

bptt = 25
criterion = nn.CrossEntropyLoss()
eval_batch_size = 1

# 创建测试数据集
def batchify(data, bsz):
    # 对测试数据集进行分块
    nbatch = data.size(0) // bsz
    # 去掉多余的元素
    data = data.narrow(0, 0, nbatch * bsz)
    # 在bsz批处理中平均划分数据
    return data.view(bsz, -1).t().contiguous()

test_data = batchify(corpus.test, eval_batch_size)

# 获取bath块的输入数据
def get_batch(source, i):
    seq_len = min(bptt, len(source) - 1 - i)
    data = source[i:i+seq_len]
    target = source[i+1:i+1+seq_len].view(-1)
    return data, target

def repackage_hidden(h):
  """
  用新的张量把隐藏的状态包装起来,把它们从历史中分离出来
  """
  if isinstance(h, torch.Tensor):
      return h.detach()
  else:
      return tuple(repackage_hidden(v) for v in h)
# 评估函数
def evaluate(model_, data_source):
    # 打开评估模式
    model_.eval()
    total_loss = 0.
    hidden = model_.init_hidden(eval_batch_size)
    with torch.no_grad():
        for i in range(0, data_source.size(0) - 1, bptt):
         # 获取测试数据
            data, targets = get_batch(data_source, i)
            # 执行前向推理
            output, hidden = model_(data, hidden)
            hidden = repackage_hidden(hidden)
            output_flat = output.view(-1, ntokens)
            # 获取训练loss
            total_loss += len(data) * criterion(output_flat, targets).item()
    return total_loss / (len(data_source) - 1)

# 初始化动态量化模块
quantized_model = torch.quantization.quantize_dynamic(
    model, {nn.LSTM, nn.Linear}, dtype=torch.qint8
)
print(quantized_model)

def print_size_of_model(model):
    torch.save(model.state_dict(), "temp.p")
    print('Size (MB):', os.path.getsize("temp.p")/1e6)
    os.remove('temp.p')

print_size_of_model(model)
print_size_of_model(quantized_model)

torch.set_num_threads(1)
# 评估模型的运行时间
def time_model_evaluation(model, test_data):
    s = time.time()
    loss = evaluate(model, test_data)
    elapsed = time.time() - s
    print('''loss: {0:.3f}\nelapsed time (seconds): {1:.1f}'''.format(loss, elapsed))

time_model_evaluation(model, test_data)
time_model_evaluation(quantized_model, test_data)


来源:小喵学AI内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯