【书生大模型实战营(暑假场)】进阶任务三 LMDeploy 量化部署实践闯关任务

news2024/12/26 19:52:15

进阶任务三 LMDeploy 量化部署实践闯关任务

  • 任务
  • 文档
  • 视频

1 大模型部署基本知识

1.1 LMDeploy部署模型

定义

  • 在软件工程中,部署通常指的是将开发完毕的软件投入使用的过程。
  • 在人工智能领域,模型部署是实现深度学习算法落地应用的关键步骤。简单来说,模型部署就是将训练好的深度学习模型在特定环境中运行的过程。

场景

  • 服务器端:CPU部署,单GPU/TPU/NPU部署,多卡/集群部署……
  • 移动端/边缘端:移动机器人,手机……

在这里插入图片描述
LMDeploy 针对丰富的部署场景,提供了多种服务与功能。支持多种推理接口,量化,引擎,服务等技术,同时可实现多种 LLM 和 VLM 的部署。

在这里插入图片描述
而且 LMDeploy 的推理性能在业内也具有领先地位,相比 vllm 具有更好的推理性能。

在这里插入图片描述

1.2 大模型缓存推理技术

大模型是一个 decoder-only 的模型,核心是 transformer 的 decoder 架构。该架构的核心算子是注意力机制,在注意力机制中,对于输入的张量 X,要通过 3 个线性变换将其转换为 查询 Query Q,键 Key K,值 Value V。通过 Q 和 K 的内积计算,可以得到注意力得分 Attention Score,然后通过注意力得分和 V 进行计算以实现注意力汇聚。

大模型的推理可以分为两个阶段:输入阶段,生成阶段

输入阶段:可以理解为我们与大模型交互时输入问题的阶段。我们输入的问题是一个包含多种token的序列,这些 token被一次性同时输入大模型。在计算注意力机制时,针对输入序列进行线性投影,同时得到所有的 Q K V 是没有任何问题的。这个阶段也被称为 pre-filling 预填充。

在这里插入图片描述

生成阶段:可以理解为大模型给出回复的阶段。在生成阶段,token是逐个迭代生成的。每一次生成token的迭代,大模型都会接受一个新的 X,这个新的 X 和历史上所有的 X,经过线性投影,更新 Q,K,V。而这里的问题是,历史上的很多 Q K V是已经 计算过的,重新计算没有意义,而且我们也只关心新生成的 token,历史上的token意义是有限的。我们可以针对这一问题进行优化,即 KV Cache。
在这里插入图片描述
KV Cache:全称是 key-value cache,可以理解为大模型推理过程中的 key-value 缓存优化。对于新的生成迭代,我们只计算新的 X 对应的 Q K V,把历史上的 K V 缓存起来。然后只把新的 Q 和所有的 K (历史的+新的)进行注意力分数的计算,然后与所有的 V(历史的+新的)进行注意力汇聚来得到新的 Y。

  • 对于新的请求Query,需要与历史的Key、Value计算注意力分数;

  • 如果每次都重新计算历史的 Key, Value,会浪费大量计算资源;

  • 每轮新迭代时将Key, Value 进行缓存,共下次迭代使用。
    在这里插入图片描述
    LMDeploy 的 KV Cache 的实现细节

  • LMDeploy 实现了一个 KV Cache 管理器,方便管理内存和缓存,但这个对用户一般是无感的。采取预先申请策略,减少运行时因申请/释放内存的消耗时间。比如,会观察到模型量化前后的显存占用都是8G,实际上模型对内存的占用通过量化确实减少了,更多的内存分配给了 KV Cache 以实现更长的上下文推理;
    在这里插入图片描述

  • 我们可以通过设置 cache_max_entry_count 参数调节 KV Cache 占用内存的大小,为占用剩余显存的比例。下面的例子为,告诉 LMDeploy 加载完模型权重就,KV Cache 可以利用剩余显存的 20%;
    在这里插入图片描述

1.3 大模型量化技术

在这里插入图片描述

量化技术将传统的表示方法中的浮点数转换为整数或其他离散形式,以減轻深度学习模型的存储和计算负担。

为什么要做量化:

  • 减少模型权重的内存占用,有更多的显存分配给 KV Cahce

  • 提升推理速度
    –速度更快的 kernel
    – 降低 I/O 延迟

  • 增加上下文长度

  • 降低推理成本

举个例子, LlaMa3.1 是一个 405B 的超大模型,传统的 16 bit/位 float 存储方式种每一个参数需要 2 byte 显存,那么 405B 尺寸的模型需要占用 810G 显存。而业界即使使用 8卡满血 A100,也只有 640G 的显存。

所以,如果用传统的 FB16 来加载模型权重,单机八卡的服务器也无法存下权重。但如果能把权重量化为 4 比特的一个整数,即从 16bit 浮点数转化为 4bit 整数,模型权重体积就会降低为原来的 1/4,只需要 202G,这个体积对单机八卡服务器而言是绰绰有余了。节省下来的显存便可以分配给 KV Cache,从而支持更长的上下文推理,甚至是高并发支持以使用一个服务器服务多个客户。

大模型量化技术核心思路对于原来的这个浮点数区间做一个线性映射,映射为一系列整数。 以INT8为例,INT8是八位二进制数,八位二进制数所能表示的这个整数范围是从0~255,共是256个数。可以把原来的这个浮点数的范围,按照最大最小值平均分成256份,然后按照这个大小到相对顺序进行一个线性的映射。每一个区间的数被线性映射到一个对应的整数上,这就是我们这个量化的一个通用的一个思想。
在这里插入图片描述
量化技术可以按照量化方法分类:

按量化对象分

  • KV Cache 量化
  • 模型权重量化
  • 激活值量化:此激活并非神经网络激活函数之激活。举例,线性层数学本质就是 Y = WX + B,W是模型权重,X就是激活值。我们可以对 W 量化,就是模型权重量化;也可以对 X 量化,这就是激活值量化

按量化阶段分

  • 量化感知训练 QAT
  • 量化感知微调 QAF
  • 训练后量化 PTQ:即模型训练好后通过一些简单的数据集标定或其他方式,不需要重复训练就可以完成量化的过程,这个策略是比较优越的。而 QAT 和 QAF 在训练后还需要一些额外的训练微调步骤来进行量化,在生产实践中是不会做的,而一般都采用 PTQ 的方式。

LMDeploy 主要的量化方案是:

  1. KV Cache 量化
  • 在线KV Cache INT4/INT8量化,粒度为per-head per-token,是一种很细粒度的方法
  • 与FP16相比,INT4/INT8的KV Block数量分别可以提升4倍和2倍。意义:更长的上下文、更高的并发吞吐
  • 精度上INT8几乎无损,INT4略有损失
  1. 模型权重量化 W4A16 量化

W4A16 指 对权重 W 进行 4bit 量化,对激活值不做量化。即对权重进行 4比特 量化进行存储以节省显存,但计算时需要将权重反量化为FP16后再与激活值进行计算;

这里引申出一个问题:我们为什么要对模型进行量化?我们想进行量化也可能是为了调用这个硬件底层的一些定点数的算子,比如英伟达显卡都是有INT8的计算单元。这种专门的计算单元比LP16或者FP32的计算单元的算力是要更高的。所以很多人机械式的认为,我们是为了对模型进行量化,把这个浮点数转成了一个定点数从而调用这个算力计算单元,实现对模型的加速,但实际上这种说法其实是比较片面的。

实际上,LMDeploy 的 4bit 量化仅仅是为了节省存储空间,但是在计算时非但没使用整数计算单元,还将整数反量化为一个浮点数,最终在浮点数计算单元上计算,最终也起到一个比较好的加速效果。 因为 Transformer 的 decode 阶段,即 generation 阶段,是一个超高仿存的操作,即大模型在实际推理的时候计算瓶颈本身并不是在计算上,而是在仿存上,即可能体现为显卡数据通信的带宽上的瓶颈。通过 4bit 量化,推理过程中通信所需要的数据量缩小到了原来的 1/4,减少了 IO操作,从而起到加速的效果。

  • AWQ算法,W4A16 指 对权重 W 进行 4bit 量化,对激活值不做量化。即对权重进行 4 比特量化进行存储以节省显存,但计算时需要将权重反量化为FP16后再与激活值进行计算;
  • 性能是FP16的 2.4 倍以上
  • 权重大小、显存降为FP16的的 1 / 4

AWQ 量化原理:

核心观点 1:权重井不等同重要,仅有 0.1 ~ 1%小部分显著权重对推理结果影的较大;

  • 尝试将这 0.1 ~ 1 % 的小部分显著权重保持FP16,对剩余权重进行低比特量化,可以大幅降低内存占用

  • 问题来了:如果选出显著权重?

    • 随机挑选 - 听天由命
    • 其于权重分布挑选 - 好像应该这样
    • 基于激活值挑选 - 竟然是这样
      在这里插入图片描述
      在实践中,可以基于激活值,按通道“组团”挑选显著权重:
  • 为了避免实现上过于复杂,在挑选显著权重时,并非在 “元素” 级别进行挑选,而是在 “通道” 级别进行挑选

  • 首先将激活值对每一列 (channel) 求绝对值的平均值,把平均值较大一列对应的通道对应的权重值视作显著权重;
    在这里插入图片描述
    然而,理想很丰满,现实很骨感,

  • 我们理想希望能做到:显著权重 INT4,非显著权重 FP16;

  • 但什么样的权重是“显著”的?

  • 如何实现一个通道上的混合精度 kernel?

  • 硬件不友好!

  • 开发者不友好!
    在这里插入图片描述
    核心观点 2: 量化时对显著权重进行方法可以降低量化误差;

考虑权重矩阵W,线性运算写作 Y = WX。对权重矩阵量化后,可以写作 Y=Q(W)X,Q(W) 定义如下:

Q ( w ) = Δ ⋅ Round ⁡ ( w Δ ) , Δ = max ⁡ ( ∣ w ∣ ) 2 N − 1 Q(\mathbf{w})=\Delta \cdot \operatorname{Round}\left(\frac{\mathbf{w}}{\Delta}\right), \quad \Delta=\frac{\max (|\mathbf{w}|)}{2^{N-1}} Q(w)=ΔRound(Δw),Δ=2N1max(w)

  • 右侧式子为计算获得量化单位 Delta Δ \Delta Δ,分母中的 N 为量化的比特数,比如:4比特量化中,4比特能表示的最大值是 2 4 − 1 2^4-1 241

  • 左侧式子,首先 Round() 中的 W 本来一般是一个浮点数张量,比如 LP16,在量化过程中 W 除以量化单位 Δ \Delta Δ,然后通过 Round() 取整,便得到了这个量化后的结果。如果 右侧式子 N 取 4,就将原来的 LP16 浮点数矩阵量化成了 INT4 整数矩阵。可以只用花费原来 1/4 的 IO 开销。而我们也提到,INT4 仅仅是为了存储,实际的计算依然使用 LP16,还有一个反量化的过程,即量化结果乘以量化单元,变得到了 LP16。

由于量化过程中存在一个取整 Round(),它一定会带来一部分的精度损失。这也引出上面的核心观点 2,即对显著权重放大可以一定程度上降低量化带来的误差损失。

核心观点 2 的证明如下:
在这里插入图片描述
将视野从考虑权重矩阵转变为考虑权重单个元素。缩放因子用来放大显著权重,乘上这个缩放因子再除以新的量化单位。而新的量化单位也是最大的 ws 去除以 2 N − 1 2^N-1 2N1,即量化过程,后面的反量化过程再乘以 1/s。这样一来,量过过程中基于 w 乘上 s 再除以量化单位,反量化过程中又乘以 1/s。综合来看,这个效果和(1)式是等价的。

但是,这样等价的效果,在(2)中其实有效降低了量化带来的误差。(2)式中唯一的误差来源是取整函数 Round(),且其中的显著权重 w 只是(1)中的 W 矩阵众多元素中非常少量的元素,哪怕它乘上一个缩放银子,他也不一定有权重矩阵 W 中的最大的元素大。这是一个概率问题,毕竟 w 只占 W 中的 0.1% ~ 1%,所以很大概率上,即使对这个显著权重乘以了 s,它也不会影响总体上的量化单位。

Δ \Delta Δ Δ ’ \Delta’ Δ’ 是大致相等的。那么,(1)(2) 的误差几乎只由 s 决定:
在这里插入图片描述
Δ \Delta Δ Δ ’ \Delta’ Δ’ 是大致相等时,相对误差项基本可以表示为 1 / s 1/s 1/s

  • s >1, s 越大,相对误差越低;
  • s 也不能太大,否则 ws 会超过 W矩阵中的最大元素,影响成立条件;

我们可以通过实验来验证上面的推导是否正确:

在这里插入图片描述

  • 随着 s 增大,假设成立的概率条件越来越低,但是 s < 2 之前,概率还是很低的(<5%);
  • 在一定范围内,随着 s 增大,误差比值越来越小,完全支持作者观点;

这也表明了一种量化策略,

  • 所有权重均低比特量化
  • 显著权重乘以较大 s,等效于降低量化误差
  • 非显著权重乘以较小的 s,等效于给予更少的关注

而实际操作中,LMDeploy 将分组计算每个通道的缩放系数:
在这里插入图片描述
以上便是 AWQ 算法整体的思路;

  1. 训练后量化 PTQ
    在这里插入图片描述
    在这里插入图片描述

1.4 大模型外推技术

什么是外推?长度外推性是一个训练预测的长度不一致的问题;

比如,我们训练可能只有 4096 等训练数据集的长度,而在预测时可能会外推到更长的文本,比如 16K,32K,128K,1M 等。这就会导致模型在训练和预测阶段所接触的序列长度是不一致的,这样的外推会引发潜在的两大问题:

  • 预测阶段用到了训练过的位置编码:模型不可避免地在一定程度上对位置编码 “过拟合”
  • 预则注意力时注意力机制所处理的 token 数量远超训练时的数量:导致计算注意力 “熵” 的差异较大

在这里,我们主要介绍从位置编码角度解决外推的问题。

首先,大模型依赖的 Transformer 为什么需要位置编码?

在transformer之前,人们处理序列问题的时候都是用循环神经网络。循环神经网络最大的一个问题是就在输入序列时候需要逐个输入不能并行输入。自注意力机制虽然解决了并行输入的问题,也导致了一个新的问题,就是理论上输入的多个嵌入向量对于注意力机制是等价的。即注意力机制并不具备区分token相对位置的能力,需要给每一个embedding向量再增加一个位置编码,从而使注意力机制能够识别这个embedding向量的相对位置关系。

在使用位置编码时,很常见的思想是将位置编码添加到 token embedding 上,成为一个新的 embedding。那么,如何设计位置编码?下面有一些基本的思路:

  • 直接使用一个整数作为位置信息提供给模型,但数值跨度较大,这对深度学习的梯度优化器是不友好的,会导致学习困难;
  • 对上面提到的整数编码所放到 0 ~ 1 取件,以此类推,但这种数值跨度又太小了,也是不适合做位置编码的;
  • 深度学习模型还是比较挑剔的,数值跨度太大或太小都不合适;
    在这里插入图片描述
  • 一个比较合适的想法是,考虑使用一组向量来表示位置:以 “10 进制” 为例,位置 1234 可以表示为4维向量 [1 2 3 4]。更一般的,位置 N 中的向量可以用以下方式表示:
    在这里插入图片描述
  • 当然,我们也可不用 “10 进制”,而是尝试使用 β \beta β 进制(这也是 beta 进制编码基本的理念,而关键点是 mod 带来的关键的周期性):
    在这里插入图片描述
  • 还有一种很经典的编码方式,即 Attention is All you need 中提出的 Sinusoidal 位置编码。而这种 sin/cos 和 beta 编码中的取余操作 mod 具有一定的等效性,因为都具有周期性的特性。所以 Sinusoidal 位置编码也可以认为是一种特殊的 beta 进制编码。
    在这里插入图片描述
    而在外推情况下,
  • 以“10 进制”为例,假设训练只在最大长度为 1K 的训练集上训练(位置编码 0~999,最长 3 位)
  • 现在需要外推到 2K,需要 4 位位置编码,如何处理?
    在这里插入图片描述
    我们的方案是,在训练阶段就预留好足够的位数,而 Transformer 原作者也是这么想的,认为预留好位数后模型就能具备对位置编码的泛化性
  • 而现实情况中,模型很难按照期望进行泛化,实验发现模型外推到 20% ~ 30% 就已经很不错的,超过后模型的效果就会断崖下跌,表现为模型的输出会呈现出一种乱码状态;
  • 还有一种原因就是在训练的时候虽然保留了高位,但是大多数高位始终是“0”。这就会导致这个高位的位置编码没有被充分的训练,使得模型无法处理这些新的位置编码。
    在这里插入图片描述
    因此,业内提出了一种新的方法,即 线性内插法
  • 方案: 把 “新长度范围”,等比例缩放至训练阶段的长度范围;
  • 如训练时使用 1K 训练,需外推至 4K,就将 [0, 4K] 的范围线性缩放至 [0, 1K];
  • 但是呢,这种方法也会导致最低位变得非常“拥挤”,通常需要微调,使模型适应拥挤的映射关系。
  • 并且,各维度差异较大!其他位置差异为 1,个位差异较小,模型不易分辨,效果不佳;
    在这里插入图片描述
    于是,人们又提出一种全新的方法,即 进制转换
  • LLM 其实并不知道我们输入的位置编码具体是多少 “进制” 的,他只对相对大小关系敏感;
  • 能否通过 “进制轻换” 来等效 “内插” ?
  • 如:10进制下,3位表示范围是0~999;16进制下,3位表示范围是0 ~ FFF (16 进制下 FFF 为 10进制下的 4095) ! 下面的例子里,外推时 4 位的十进制 [2 3 5 0] 转换为 十六进制 的 [9 2 14];
    在这里插入图片描述
  • 虽然每一位都可能出现大于“9”的数,但相对大小差异总是为 1,模型具有泛化能力;
  • 进制转换把内插的压力分摊到了每一位上;

实际上的外推中,我们采取 NTK-aware 外推技术:

sin ⁡ ( n ( β λ ) i ) = sin ⁡ ( n ( θ 2 / d k 2 / ( d − 2 ) ) i ) = sin ⁡ ( n ( θ k d / ( d − 2 ) ) 2 i ) \sin \left(\frac{n}{(\beta \lambda)^i}\right)=\sin \left(\frac{n}{\left(\theta^{2 / d} k^{2 /(d-2)}\right)^i}\right)=\sin \left(\frac{n}{\left(\theta k^{d /(d-2)}\right)^{2 i}}\right) sin((βλ)in)=sin((θ2/dk2/(d2))in)=sin((θkd/(d2))2in)

  • 预测阶段,计算系数,对位置编码的底数 base 进行缩放:
    n ( β λ ) d / 2 − 1 = n / k β d / 2 − 1 \frac{n}{(\beta \lambda)^{d / 2-1}}=\frac{n / k}{\beta^{d / 2-1}} (βλ)d/21n=βd/21n/k
  • n 是实际预测长度,k 是实际长度预训练长度的比值。求得:
    λ = k 2 / ( d − 2 ) \lambda=k^{2 /(d-2)} λ=k2/(d2)

1.5 Function Calling

Function Calling 即为让 LLM 调用外部函数解决问题,从而拓展 LLM 的能力边界;

其意义在于:

  • 解决时效性问题:今天那年那日?今天天气如何?
  • 拓展 LLM 能力边界:帮我算一下 e^8/12345 等于多少?帮我搜一下这篇论文?

2 LMDeploy 量化部署实践

2.1 开发机选择和环境配置

对于 internlm2_5-1_8b-chat 模型,查看其 HuggingFace 码仓 中的 config 文件,发现模型权重存储为 bfloat16 格式,

  "rope_theta": 1000000,
  "tie_word_embeddings": false,
  "torch_dtype": "bfloat16",
  "transformers_version": "4.41.0",
  "use_cache": true,
  "vocab_size": 92544

对这个 1.8B (18亿) 模型,每个参数 parameter 都是用 16 位浮点数(16 bits,2 Bytes),模型权重为:
18 × 1 0 9  parameters × 2  Bytes/Parameter = 3.6  GB 18 \times 10^9 \text{ parameters}\times 2 \text{ Bytes/Parameter} = 3.6 \text{ GB} 18×109 parameters×2 Bytes/Parameter=3.6 GB,1 GB 有 1 B 个 Byte;

需要的 GPU 显存至少要大于 3.6 GB,需要至少选择 30% A100 开发机(24GB)。

创建好开发机后,配置环境:

conda create -n lmdeploy  python=3.10 -y
conda activate lmdeploy
conda install pytorch==2.1.2 torchvision==0.16.2 torchaudio==2.1.2 pytorch-cuda=12.1 -c pytorch -c nvidia -y
pip install timm==1.0.8 openai==1.40.3 lmdeploy[all]==0.5.3

2.2 InternStudio 环境获取模型

运行以下指令,创建文件夹并设置软链接以方便访问 InternStudio 预先准备好的模型:

mkdir /root/models
ln -s /root/share/new_models/Shanghai_AI_Laboratory/internlm2_5-7b-chat /root/models
ln -s /root/share/new_models/Shanghai_AI_Laboratory/internlm2_5-1_8b-chat /root/models
ln -s /root/share/new_models/OpenGVLab/InternVL2-26B /root/models

官方教程使用 internlm2_5-7b-chat 和InternVL2-26B 作为演示,但这两个模型量化会消耗大量时间 (约8h)。在本文实践中,使用internlm2_5-1_8b-chat模型完成。

2.3 LMDeploy 验证启动模型文件与本地部署 InternLM2.5 大模型

量化工作开始前,验证获取的模型文件是否能正常工作:

conda activate lmdeploy
lmdeploy chat /root/models/internlm2_5-1_8b-chat

在这里插入图片描述
然后,我们可以在CLI (“命令行界面” Command Line Interface的缩写) 中和InternLM2.5 进行交互了,注意输入内容完成后需要按两次回车才能够执行
在这里插入图片描述
此时,我们可以看到,LMDeploy 部署 internlm2_5-1_8b-chat 后显存占用大约为 20GB
在这里插入图片描述
而在之前的分析中,internlm2_5-1_8b-chat 的权重仅需要 3.6GB,这是为什么呢?

由于 LMDeploy 默认设置参数 cache-max-entry-count 为0.8,即kv cache占用剩余显存的80%:
在这里插入图片描述
此时,30% A100 具有 24GB 显存,权重占用 3.6GB,剩余显存 24 - 3.6 = 20.4 GB,因此 KV Cache 将占用 20.4 * 0.8 = 16.32 GB,总共占用 3.6 + 16.32 = 19.92 GB,接近 20 GB;

这也表明,如果我们是用 50%A100 40GB 显存,那么 KV Cahce 将占用 (40-3.6) * 0.8 = 29.12 GB,总共占用 3.6 + 29.12 = 32.72 GB;

同时,实际加载模型后,除了模型权重和 KV Cache占用显存,还会有其他部分占用显存,所以剩余显存比理论值会偏低,最终的实际占用也会偏高于 19.92 GB 或 32.72 GB。

此外,也可以新开一个终端输入如下两条指令的任意一条,查看显存占用情况,实现显存资源的监控:

nvidia-smi 
studio-smi 

注释:实验室提供的环境为虚拟化的显存,nvidia-smi是NVIDIA GPU驱动程序的一部分,用于显示NVIDIA GPU的当前状态,故当前环境只能看80GB单卡 A100 显存使用情况,无法观测虚拟化后30%或50%A100等的显存情况。针对于此,实验室提供了studio-smi 命令工具,能够观测到虚拟化后的显存使用情况。

在这里插入图片描述

2.4 LMDeploy API部署InternLM2.5 大模型

上一节我们将大模型加载权重部署在本地。在实际应用中,我们常将大模型部署封装为 API接口服务,供客户端访问。

2.4.1 启动 API 服务器

我们依然使用 lmdeploy,将 InternLM2.5-1.8b 部署为 API 服务:

conda activate lmdeploy
lmdeploy serve api_server \
    /root/models/internlm2_5-1_8b-chat \
    --model-format hf \
    --quant-policy 0 \
    --server-name 0.0.0.0 \
    --server-port 23333 \
    --tp 1

命令解释:

  1. lmdeploy serve api_server:这个命令用于启动API服务器。
  2. /root/models/internlm2_5-1_8b-chat:这是模型的路径。
  3. --model-format hf:这个参数指定了模型的格式。hf代表“Hugging Face”格式。
  4. --quant-policy 0:这个参数指定了量化策略。
  5. --server-name 0.0.0.0:这个参数指定了服务器的名称。在这里,0.0.0.0是一个特殊的IP地址,它表示所有网络接口。
  6. --server-port 23333:这个参数指定了服务器的端口号。在这里,23333是服务器将监听的端口号。
  7. --tp 1:这个参数表示并行数量(GPU数量)。

运行指令后,我们已将 internlm2_5-1_8b-chat 模型部署为 API 服务,完成端口映射后,边可以在本地机器访问运行在开发机上的大模型服务。

在这里插入图片描述

2.4.2 链接 API 服务器
2.4.2.1 命令行形式链接API服务器

将模型部署为 API 服务器后,可以使用命令行形式链接,

conda activate lmdeploy
lmdeploy serve api_client http://localhost:23333

完成链接后,便可以在命令行中进行与模型的交互:

在这里插入图片描述

2.4.2.2 Gradio 网页形式链接 API 服务器

可以输入以下指令,运用 Gradio 作为前段,启动网页:

lmdeploy serve gradio http://localhost:23333 \
    --server-name 0.0.0.0 \
    --server-port 6006

完成端口映射后,可以在本地浏览器中访问 Gradio 前端的模型服务:

在这里插入图片描述

2.5 LMDeploy Lite

在本例中,使用 internlm2_5-1_8b-chat 不是一个很大的模型,但实际中会涉及很大的模型,比如 70B,甚至 405B,我们不可避免的需要用大模型压缩技术来降低模型部署成本,这不仅是为了能够成功的部署,也是为了提升模型的推理性能。LMDeploy 提供了权重量化和 KV Cache 两种策略

2.5.1 KV Cache 缓存大小配置

KV Cache 是一种缓存技术,通过存储 键值对(Key-Value Pair) 的形式来复用计算结果。在大规模训练和推理中,可以显著减少重复计算量,从而提升模型的推理速度,最终提高性能和降低内存消耗。理想情况下,KV Cache 全部存储于显存,以加快访存速度。

模型在运行时,占用的显存可大致分为三部分:

  • 模型参数权重本身占用显存
  • KV Cache 占用显存
  • 中间运算结果占用的显存

LMDeploy 的 KV Cache 管理器可以通过设置 --cache-max-entry-count 参数,控制 KV Cache 占用剩余显存的最大比例。默认的比例为0.8。

我们在轻量化部署前,模型的显存占用大约为 20GB,这也符合我们之前的分析

此时,30% A100 具有 24GB 显存,权重占用 3.6GB,剩余显存 24 - 3.6 = 20.4 GB,因此 KV Cache 将占用 20.4 * 0.8 = 16.32 GB,总共占用 3.6 + 16.32 = 19.92 GB,接近 20 GB;

在这里插入图片描述
然后,我们改变 KV Cache 的显存占用配置,重新部署一个模型

lmdeploy chat /root/models/internlm2_5-1_8b-chat --cache-max-entry-count 0.4

在这里插入图片描述
发现此时显存变为 12.5GB,这种变化如何而来?

cache-max-entry-count 设置为 0.4 时,

1、在 BF16 精度下,1.8B 模型权重占用14GB:18×10^9 parameters×2 Bytes/parameter=3.6 GB

2、KV cache 占用 8.16 GB:剩余显存 24-3.6=20.4 GB,KV cache默认占用80%,即20.4*0.4=8.16 GB

此时,模型权重和 KV Cache 共占用约 11.76 GB,再加上模型部署涉及的其他项,反推约 0.8GB,共占用 12.5 GB

2.5.2 在线 KV Cache INT4/INT8 量化

自 v0.4.0 起,LMDeploy 支持在线 KV Cache INT4/INT8 量化,量化方式为 per-head per-token 的非对称量化。

  • 此外,通过 LMDeploy 应用 KV Cache 量化非常简单,只需要设定 quant_policycache-max-entry-count 参数。
  • 目前,LMDeploy 规定 quant_policy=4 表示 KV INT4 量化,quant_policy=8 表示 KV INT8 量化。

在前面的内容中,我们使用 lmdeploy,将 InternLM2.5-1.8b 部署为 API 服务时运用以下指令:

conda activate lmdeploy
lmdeploy serve api_server \
    /root/models/internlm2_5-1_8b-chat \
    --model-format hf \
    --quant-policy 0 \
    --server-name 0.0.0.0 \
    --server-port 23333 \
    --tp 1

此时,

  • --quant-policy = 0 没有进行量化;
  • cache-max-entry-count 为默认 0.8;

下面,我们设置 KV INT4 量化,设置 KV Cache 对剩余显存占用比例为 0.4,将模型部署为 API:

lmdeploy serve api_server \
    /root/models/internlm2_5-1_8b-chat \
    --model-format hf \
    --quant-policy 4 \
    --cache-max-entry-count 0.4\
    --server-name 0.0.0.0 \
    --server-port 23333 \
    --tp 1

可以看到,此时的显存占用也是 12.5GB:
在这里插入图片描述
那这里的 12.5GB 和之前不设置 quant-policy = 4 时占用的 12.5GB 有什么区别呢?

  • 首先,默认的BF16精度下 internLM2.5 1.8B 模型,占用显存 3.6GB,因此 30% A100 下将剩余 20.4 GB;
  • 由于 cache-max-entry-count 为 0.4,那么 KV Cache 将只占用 20.4*0.4 = 8.16 GB 的显存;
  • 由于 quant-policy = 4,将采取 INT4 量化。所以,LMDeploy 也会按照 INT4 精度提前申请 8.16 GB 的 KV Cache;而像之前那种方法,如不采用 INT4 精度量化,则会按照 BF16 精度申请显存;
  • 但是,相比之前的 BF16 精度的显存下装载的 KV Cache,INT4 精度只需 4 bit 来存储一个参数,而之前的 BF16 需要 16 bit。那么,在相同大小的显存下,INT4精度的 KV Cache 可以存储的元素数量也会是 BF16 的 4 倍!
2.5.3 W4A16 模型权重量化和部署

上一节,我们对 KV Cache 进行量化,本节我们将尝试对模型的权重进行量化。

准确说,模型量化是一种优化技术,旨在减少机器学习模型的大小并提高其推理速度。量化通过将模型的权重激活从**高精度(如16位浮点数)转换为低精度(如8位整数、4位整数、甚至二值网络)**来实现。

W4A16解释:

  • W4:一般指权重量化为 4位整数(INT4)。这说明模型中的权重参数将从它们原始的浮点表示(例如 FP32、BF16 或 FP16,InternLM2.5 默认精度为 BF16,即每个参数都是 16bit 的浮点数)转换为 4bit 的整数表示(INT4)。从高精度转换为低精度,显著减少模型的大小。
  • A16:一般表示激活**(或输入/输出)仍然保持在16bit 浮点数(例如FP16或BF16)**。激活是在神经网络中传播的数据,通常在每层运算之后产生,即 Y=WX+A 中的 X。

因此,W4A16的量化配置指:

  • 权重W 被量化为 4位整数(INT4)。
  • 激活X 保持为16位浮点数(BF16 或 FP16)。

在最新的版本中,LMDeploy 使用的是AWQ(自动权重量化)算法,能够实现模型的4bit 权重量化。输入以下指令,执行量化推理工作,总体耗时接近 1 个小时:

lmdeploy lite auto_awq \
   /root/models/internlm2_5-1_8b-chat \
  --calib-dataset 'ptb' \
  --calib-samples 128 \
  --calib-seqlen 2048 \
  --w-bits 4 \
  --w-group-size 128 \
  --batch-size 1 \
  --search-scale False \
  --work-dir /root/models/internlm2_5-1_8b-chat-w4a16-4bit

命令解释:

  1. lmdeploy lite auto_awq: lite这是 LMDeploy的命令,用于启动量化过程,而auto_awq代表自动权重量化(auto-weight-quantization)。
  2. /root/models/internlm2_5-1_8b-chat: 模型文件的路径。
  3. --calib-dataset 'ptb': 这个参数指定了一个校准数据集,这里使用的是’ptb’(Penn Treebank,一个常用的语言模型数据集)。
  4. --calib-samples 128: 这指定了用于校准的样本数量—128个样本
  5. --calib-seqlen 2048: 这指定了校准过程中使用的序列长度—2048
  6. --w-bits 4: 这表示权重(weights)的位数将被量化为4位。
  7. --work-dir /root/models/internlm2_5-1_8b-chat-w4a16-4bit: 这是工作目录的路径,用于存储量化后的模型和中间结果。
    在这里插入图片描述
    推理完成后,便可以在设置的目标文件夹 --work-dir /root/models/internlm2_5-1_8b-chat-w4a16-4bit 看到对应的模型文件。而推理后的模型和原本的模型最明显区别在于:模型文件大小和占据显存大小;

可以输入如下指令查看在当前目录中显示所有子目录的大小:

cd /root/models/
du -sh *
(lmdeploy) root@intern-studio-50055:~/models# du -sh *
0       InternVL2-26B
0       internlm2_5-1_8b-chat
1.5G    internlm2_5-1_8b-chat-w4a16-4bit # 量化后占用 1.5GB
0       internlm2_5-7b-chat

可见,W4A16 设置下的 AWQ 算法对原本 3.6 GB 的 1.8B 模型量化后,得到 1.5Ginternlm2_5-1_8b-chat-w4a16-4bit 按照 INT4 存储参数的模型;

我们也可以用以下指令查看原模型的大小:

cd /root/share/new_models/Shanghai_AI_Laboratory/
du -sh *
(lmdeploy) root@intern-studio-50055:~/share/new_models/Shanghai_AI_Laboratory# du -sh *
17G     internlm-xcomposer2-4khd-7b
17G     internlm-xcomposer2-7b
6.6G    internlm-xcomposer2-7b-4bit
4.6G    internlm-xcomposer2-vl-1_8b
17G     internlm-xcomposer2-vl-7b
74G     internlm2-20b-chat-xxx
3.6G    internlm2-chat-1_8b # 量化前占用 3.6GB
7.1G    internlm2-chat-1_8b-sft
37G     internlm2-chat-20b
37G     internlm2-chat-20b-sft
15G     internlm2-chat-7b
15G     internlm2-chat-7b-sft
15G     internlm2-math-7b
15G     internlm2-math-base-7b
37G     internlm2-math-plus-20b
38G     internlm2-wqx-20b
41G     internlm2-wqx-vl-20b
3.6G    internlm2_5-1_8b
3.6G    internlm2_5-1_8b-chat
37G     internlm2_5-20b-chat
15G     internlm2_5-7b-chat
15G     internlm2_5-7b-chat-1m

然后,我们可以用以下指令运行部署量化后的模型

lmdeploy chat /root/models/internlm2_5-1_8b-chat-w4a16-4bit/ --model-format awq

在这里插入图片描述
而此时内存占用情况,约 20.2GB
在这里插入图片描述
相比权重量化前的模型,少了 0.4 GB,在 W4A16 配置的 AWQ 量化下:

  • INT4 精度量化下,1.8 B 占用内存 3.6/4 = 0.9 GB;

注释:bfloat16是16bit 的浮点数格式,占用 2 byte(16位)的存储空间。INT4 是 4bit 的整数格式,占用 0.5 Byte(4bit)的存储空间。因此,从 bfloat16到 INT4 的转换理论上可以将模型权重的大小减少到原来的1/4,即1.8B 个 INT4 参数仅占用 0.9GB 的显存。

  • KV Cache 默认占用剩余显存 80%:(24 -0.9)*0.8 = 18.48 GB
  • 其他项占用约 0.8GB

最终占用 0.9 + 18.48 + 0.8 = 20.18 GB;

2.5.4 W4A16 量化 + KV Cache + KV Cache 量化 实践

我们可以同时使用多种量化手段来优化大模型的部署:

  • W4A16 量化:我们之前通过量化得到了量化后的模型权重 internlm2_5-1_8b-chat-w4a16-4bitmodel-format awq
  • KV Cache:设置剩余显存占用为 40%,--cache-max-entry-count 0.4
  • KV Cache 量化:采用 KV INT4 量化,--quant-policy 4

我们用以下指令同时使用多种量化方法并把模型部署为 API:

conda activate lmdeploy
lmdeploy serve api_server \
    /root/models/internlm2_5-1_8b-chat-w4a16-4bit \
    --model-format awq \
    --cache-max-entry-count 0.4 \
    --quant-policy 4 \
    --server-name 0.0.0.0 \
    --server-port 23333 \
    --tp 1

经过多种量化方法:W4A16 量化 + KV Cache + KV Cache 量化,此时模型的部署后仅仅占用 11.3GB!
在这里插入图片描述
然后,我们构建以下脚本,借助 FastAPI 封装一个 API 让 LMDeploy 自行访问,

touch /root/internlm2_5.py
# 导入openai模块中的OpenAI类,这个类用于与OpenAI API进行交互
from openai import OpenAI

# 创建一个OpenAI的客户端实例,需要传入API密钥和API的基础URL
client = OpenAI(
    api_key='YOUR_API_KEY',  
    # 替换为你的OpenAI API密钥,由于我们使用的本地API,无需密钥,任意填写即可
    base_url="http://0.0.0.0:23333/v1"  
    # 指定API的基础URL,这里使用了本地地址和端口
)

# 调用client.models.list()方法获取所有可用的模型,并选择第一个模型的ID
# models.list()返回一个模型列表,每个模型都有一个id属性
model_name = client.models.list().data[0].id

# 使用client.chat.completions.create()方法创建一个聊天补全请求
# 这个方法需要传入多个参数来指定请求的细节
response = client.chat.completions.create(
  model=model_name,  
  # 指定要使用的模型ID
  messages=[  
  # 定义消息列表,列表中的每个字典代表一个消息
    {"role": "system", "content": "你是一个友好的小助手,负责解决问题."},  
    # 系统消息,定义助手的行为
    {"role": "user", "content": "帮我讲述一个关于孙悟空和蜘蛛精的小故事"},  
    # 用户消息,询问时间管理的建议
  ],
    temperature=0.8,  
    # 控制生成文本的随机性,值越高生成的文本越随机
    top_p=0.8  
    # 控制生成文本的多样性,值越高生成的文本越多样
)

# 打印出API的响应结果
print(response.choices[0].message.content)

然后,我们运行以下指令:

conda activate lmdeploy
python /root/internlm2_5.py

于是,我们便可以运用脚本,进行和模型的交互,并完成了一次和大模型的对话:

  messages=[  
  # 定义消息列表,列表中的每个字典代表一个消息
    {"role": "system", "content": "你是一个友好的小助手,负责解决问题."},  
    # 系统消息,定义助手的行为
    {"role": "user", "content": "帮我讲述一个关于孙悟空和蜘蛛精的小故事"},  
    # 用户消息,询问时间管理的建议
  ],

在这里插入图片描述
而此时,在多种量化优化技术的加成在,显存占用仅仅需要 11.3GB !
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2080460.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能科技的浪潮:AI、ML、DL和CV的探索之旅

智能科技的浪潮&#xff1a;AI、ML、DL和CV的探索之旅 前言人工智能&#xff1a;智能科技的基石从专用到通用&#xff1a;AI的分类与演进机器学习&#xff1a;数据中的智慧算法的力量&#xff1a;经典与创新深度学习&#xff1a;解锁复杂性之门神经网络的深度&#xff1a;基础与…

Python网络爬虫模拟登录与验证解析

内容导读 使用Selenium模拟登录 使用Cookies登录网站 模拟表单登录网站 爬虫识别简单的验证码 实例解析 一、使用Selenium模拟登录 1、为什么要模拟登录 在互联网上存在大量需要登录才能访问的网站&#xff0c;要爬取这些网站&#xff0c;就需要学习爬虫的模拟登录。对…

Webpack中的自定义 loader 的简单实现

1.loader简单介绍 webpack 中 loader 是用于对模块的源代码进行转换&#xff08;处理&#xff09;的插件。例如 webpack 中常见的loader&#xff0c; css-loader、babel-loader。 2.自定义 loader 关于 loader&#xff1a; loader本质上是一个导出为函数的JavaScript模块&am…

故障诊断 | 基于小波时频图与Swin Transformer的轴承故障诊断方法(PyTorch)

文章目录 文章概述程序设计参考资料文章概述 基于小波时频图与Swin Transformer的轴承故障诊断方法 针对用传统的故障诊断方法难以对非线性非平稳的柴油机故障信号进行准确高效诊断的问题, 提出基于小波时频图与Swin Transformer的故障诊断方法。该方法可以有效结合小波时频分…

Qt (11)【Qt窗口 —— 对话框 | Qt内置对话框简介】

阅读导航 引言一、对话框1. 对话框的分类&#xff08;1&#xff09;模态对话框&#xff08;2&#xff09;非模态对话框 二、Qt内置对话框 引言 在上一篇文章中&#xff0c;我们深入探讨了Qt框架中窗口的基本构建块&#xff0c;它们共同构成了Qt应用程序中用户界面&#xff08;…

RACL: Adversarially Robust Neural Architectures

RACL: 对抗鲁棒网络架构 论文链接&#xff1a;https://arxiv.org/abs/2009.00902v2 Abstract 深度神经网络(DNN)容易受到对抗性攻击。现有的方法致力于开发各种鲁棒训练策略或正则化来更新神经网络的权值。但除了权重之外&#xff0c;网络中的整体结构和信息流是由网络架构明…

文件.硬盘.IO

一.文件 &#xff08;1&#xff09;文件本身是包含多种意义的&#xff0c;这里我简单的说明一下文件的意义。 &#xff08;2&#xff09; 狭义上的文件&#xff1a;我们在硬盘中的文件。文件夹&#xff08;目录&#xff09;中存放的是文件。 &#xff08;3&#xff09; 广义…

云端集中管控边缘服务:利用 EMQX ECP 在 K8s 上快速部署 NeuronEX

随着物联网、边缘计算技术的发展&#xff0c;实现边缘服务的快速部署对于分布式计算环境至关重要。它不仅可以显著降低延迟、节省带宽资源、增强数据的安全性和隐私保护&#xff0c;同时还能改善用户体验&#xff0c;支持动态变化的工作负载需求&#xff0c;提供更高的灵活性和…

【启明智显分享】智能音箱AI大模型一站式解决方案重塑人机交互体验,2个月高效落地

2010年左右&#xff0c;智能系统接入音箱市场&#xff0c;智能音箱行业在中国市场兴起。但大潮激荡&#xff0c;阿里、小米、百度三大巨头凭借自身强大的资本、技术、粉丝群强势入局&#xff0c;形成三足鼎立态势。经过几年快速普及&#xff0c;智能音箱整体渗透率极高&#xf…

数据结构(6.4_1)——最小生成树

生成树 连通图的生成树是包含图中全部顶点的一个极小连通子图(边要尽可能的少&#xff0c;但要保持连通) 若图中顶点数为n&#xff0c;则它的生成树含有n-1条边。对生成树而言&#xff0c;若砍去它的一条边&#xff0c;则会变成非连通图&#xff0c;若加上一条边则会形成一个…

【MySQL 13】视图 (带思维导图)

文章目录 &#x1f308; 一、视图的基本概念&#x1f308; 二、视图的基本操作⭐ 1. 创建视图⭐ 2. 修改视图⭐ 3. 修改基表⭐ 4. 删除视图 &#x1f308; 三、视图的限制规则 &#x1f308; 一、视图的基本概念 视图是一种虚拟存在的表&#xff0c;将查询结果以表结构的方式保…

Sigmoid 函数及其导数推导

Sigmoid 函数及其导数推导 1. 了解 Sigmoid 函数 Sigmoid 函数是神经网络中常用的激活函数&#xff0c;因其平滑的S形曲线和将输入压缩至 (0, 1) 的特性&#xff0c;在神经网络的激活函数中扮演着重要角色。其定义如下&#xff1a; σ ( x ) 1 1 e − x \sigma(x) \frac{1…

GUI编程03:3种布局管理器

本节内容视频链接&#xff1a;https://www.bilibili.com/video/BV1DJ411B75F?p5&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5https://www.bilibili.com/video/BV1DJ411B75F?p5&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 1.FlowLayout 流式布局 代码&#xff1a;…

34. 二叉树中和为某一值的路径

comments: true difficulty: 中等 edit_url: https://github.com/doocs/leetcode/edit/main/lcof/%E9%9D%A2%E8%AF%95%E9%A2%9834.%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%92%8C%E4%B8%BA%E6%9F%90%E4%B8%80%E5%80%BC%E7%9A%84%E8%B7%AF%E5%BE%84/README.md 面试题 34. 二…

关于Linux(CentOS 7)中的用户sudo命令

&#x1f4dd;用户提权 测试非root用户的权限浏览该文件 测试非root用户的权限 当我们在当前用户使用sudo命令时&#xff0c;提示使用vimer用户的密码&#xff0c;非root。这是为什么呢&#xff1f; 因为这里系统提示需要用户的密码&#xff0c;则认为vimer用户是受信任的。 输…

用nltk包出现的三个问题 报错显示 缺少 punkt_tab、averaged_perceptron_tagger、wordnet 这三个文件

用nltk包出现的三个问题 报错显示 缺少 punkt_tab、averaged_perceptron_tagger、wordnet 这三个文件 报错是分开来的&#xff0c;你自己缺少哪一个就下哪一个&#xff0c;我这里总共是缺少三个文件&#xff0c;所以我依次去下载的 首先 在自己的虚拟环境中建立一个nltk_data文…

Qt第二十一章 语言家

文章目录 Qt Linguist简介使用流程1. 使用tr包裹字符串2. 生成翻译文件3. 打开翻译文件&#xff0c;并翻译4. 发布翻译5. 加载语言文件6. 动态切换语言 各国语言代码和名称表 Qt Linguist 简介 Qt提供了一款优秀的支持Qt C和Qt Quick应用程序的翻译工具。发布者、翻译者和开发…

齐护【百度AI对话】编程系统文心一言大语音模型对话ESP32图形化Mixly编程Scratch编程Arduino

齐护【百度AI对话】编程系统 一、前言 ​ 在这个日新月异的时代&#xff0c;AI的触角已延伸至互联网、金融、医疗、教育等每一个角落&#xff0c;其影响力不容忽视。从日常中的智能推荐到医疗前沿的精准诊断&#xff0c;从定制化教育到智能化的投资策略&#xff0c;AI正以前所…

ET6框架(一)介绍及环境部署

文章目录 一、什么是ET框架&#xff1f;二、ET框架特色&#xff1a;三、开发环境准备&#xff1a;四、.Net Core下载安装五、安装Visual Studio六、下载Mongodb七.安装Robo 3T八、下载ET版本分支 一、什么是ET框架&#xff1f; 1.ET(客户端&#xff0c;服务器端)是一个开源的双…

C++ 136类和对象_面像对像_多态_虚析构和纯虚析构

136类和对象_面像对像_多态_虚析构和纯虚析构 学习内容 1.抽象类 2.虚函数 3.纯虚函数 /4.虚析构 和 纯虚析构 总结: 1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象 2.如果子类中没有堆区数据&#xff0c;可以不写为虚析构或纯虚析构 3.拥有纯虚析构函数的类也属于…