1 模型部署
定义:
- 在软件工程中,部署通常指的是将开发完毕的软件投入使用的过程。
- 在人工智能领域,模型部署是实现深度学习算法落地应用的关键步骤。简单来说,模型部署就是将训练好的深度学习模型在特定环境中运行的过程。
场景:
- 服务器端:CPU部署,单GPU/TPU/NPU部署,多卡/集群
部署. - 移动端边缘端:移动机器人,手机......
2 大模型缓存推理技术
在大模型中,Transformer模型扮演着至关重要的角色。它通过自注意力机制(Self-Attention)来捕捉文本中的上下文信息,实现对文本的深入理解和推理。这种机制允许模型在处理每个token时都考虑到整个输入序列的上下文信息,从而提高了模型的表达能力和准确性。
- 对于新的请求Query,需要与历史的Key、Value计算注意力分数。
- 如果每次都重新计算历史的Key、Value,会浪费大量计算资源。
- 每轮新迭代时将Key、Value进行缓存,共下次迭代使用。
KV Cache的全称是key-value cache,可以简单理解为对大模型推理过程中的key-value缓存的优化。
LMDeploy 对 KV Cache 的实现:
- 预先申请策略,减少运行时因申请/释放内存的消耗时间。
- 通过设置
cache_max_entry_count
参数来调节 KV Cache 占用内存的大小,为占用剩余显存的比例。
下图0.2表示KV Cache 可以占用的显存大小是在加载玩模型权重后所剩余的显存的20%。
3 大模型量化技术
量化技术将传统的表示方法中的浮点数转换为整数或其他离散形式,以减轻深度学习模型的存储和计算负担。
为什么要做量化:
- 提升推理的速度
- 增加上下文长度
- 速度更快的kernel
- 降低I/0延迟
- 降低推理成本
量化思路 ,将原来浮点数所在区间做一个线性映射,映射为一些列整数。以 INT8
的8位二进制数为例,通用公式:
Z P = min + max 2 量化: q = r o u n d ( f − Z P S ) S = max − min 255 反量化: f = q × S + Z P ZP=\frac{\min+\max} {2} \quad\text{量化:} \, q=\mathrm{round}\left(\frac{f-ZP}{S}\right) \\ S=\frac{\max-\min}{255}\quad \text{反量化:} \, f=q\times S+ZP ZP=2min+max量化:q=round(Sf−ZP)S=255max−min反量化:f=q×S+ZP
量化方法的分类 :
(1)按量化对象分:
- KV Cache量化
- 模型权重量化
- 激活值量化 (例如对于一个线性层 y = w x y = wx y=wx 中 w w w为权重, x x x为激活值)
(2)按量化阶段分
- 量化感知训练(QAT)
- 量化感知微调(QAF)
- 训练后量化(PTQ,训练好模型后,在完成量化,常用这种方式)
由于QAT和QAF一般在模型训练后仍然需要做微调操作量化,通常不采用这两种方式。
3.1 LMDeploy 量化方案
LMDeploy 量化方式:KV Cache量化 + 模型权重量化 + 训练后量化
3.1.1 KV Cache量化
- 在线KVCacheINT4/INT8量化,粒度为 per-head per-token
- 与FP16相比,INT4/INT8的KVBlock数量分别可以提升4倍和2倍。意义:更长的上下文、更高的并发吞吐
- 精度上INT8几乎无损,INT4略有损失
3.1.2 模型权重量化(W4A16量化)
- 基于 AWQ 算法,对权重进行4bit量化,计算时返量化使用FP16
- 性能是FP16的2.4倍以上
- 权重大小、显存降为FP16的的1/4
3.1.3 AWQ 量化原理**:
参考论文:AWQ
核心观点1 :权重并不等同重要,仅有0.1~1%小部分显著权重对推理结果影响较大。
如果有办法将这0.1~1%的小部分显著权重保持FP16,对其他权重进行低比特量化,可以大幅降低内存占用。那么问题来了:如果选出显著权重?
(1)随机挑选-听天由命
(2)基于权重分布挑选-好像应该这样
(3)基于激活值挑选-竟然是这样
AWQ 论文对三者做了实验对比,返实验结果表明基于激活值挑选显著权重效果显著。
基于激活值,按通道 "组团"挑选显著权重权重。为了避免实现上过于复杂,在挑选显著权重时,并非在"元素"级别进行挑选,而是在"通道"级别进行挑选。 首先将激活值对每一列(channel)求绝对值的平均值,把平均值较大一列对应的通道视作显著权重。
但是,随之伴随问题:
- 显著权重INT4,非显著权重FP16?
- "显著"的阈值?
- 如何实现一个通道粒度上混合精度的kernel?
- 这是硬件不友好的行为!
- 这更是开发者不友好的行为!
核心观点2 :量化前对显著权重进行放大可以降低量化误差 。
考虑权重矩阵w,线性运算写作y=wx。对权重矩阵进行量化后,可以写作y=Q(w)x。Q(·)定义如下:实验表现:
- 随着s增大,假设成立的概率越来越低,但在s<2之前,概率还是很低的(<5%)
- 在一定范围内,随着s的增大,误差比值越来越小,完全支持作者观点
所有权重均低比特量化。显著权重乘以较大s,等效于降低量化误差。非显著权重乘以较小的s,等效于给予更少的关注。
LMDeploy 采用分组计算每个通道的缩放系数:
4 大模型外推技术
大模型长度外推性是一个训练和预测的长度不一致的问题。
外推引发的两大问题:
- 预测阶段用到了没训练过的位置编码
模型不可避免地在一定程度上对位置编码"过拟合" - 预测注意力时注意力机制所处理的token数量远超训练时的数量
导致计算注意力"熵"的差异较大
4.1 为什么需要位置编码
因为并行化的自注意力机制并不具备区分token相对位置的能力。位置编码用于为输入序列的每个位置添加一种表示其位置信息的编码。通过引入位置编码,Transformer模型可以更好地捕捉序列中不同位置之间的关系,从而更好地处理长距离依赖关系。
-
如果直接使用一个整数作为位置信息提供给模型,则会造成数值跨度过大,对梯度优化器不友好,模型学习困难。
-
如果将数值缩放到 [0-1] 区间,则会造成数值跨度过小,模型和优化器都难以分辨位置。
-
使用向量表示位置:
"10进制"为例,位置1234可以用4维向量[1 2 3 4]表示。一般地,位置N可以用如下向量表示:
∣ N 1 0 3 ∣ m o d 10 ∣ N 1 0 2 ∣ m o d 10 ∣ N 1 0 2 ∣ m o d 10 ∣ N 1 0 0 ∣ m o d 10 \left|\frac{N}{10^{3}}\right|\mathrm{mod}10\quad\left|\frac{N}{10^{2}}\right|\mathrm{mod}10\quad\left|\frac{N}{10^{2}}\right|\mathrm{mod}10\quad\left|\frac{N}{10^{0}}\right|\mathrm{mod}10 103N mod10 102N mod10 102N mod10 100N mod10更一般地,果不是10进制,是β进制呢?第i位数是为:
⌊ N β i ⌋ m o d β \left\lfloor\frac{N}{\beta^i}\right\rfloor\mathrm{mod~}\beta ⌊βiN⌋mod β这里的取余重在表示周期性。
Transformer使用的 Sinusoidal 位置编码 :
p i , 2 j = sin ( i 1000 0 2 j / d ) , p i , 2 j + 1 = cos ( i 1000 0 2 j / d ) p_{i,2j}=\sin\left(\frac i{10000^{2j/d}}\right),\quad p_{i,2j+1}=\cos\left(\frac i{10000^{2j/d}}\right) pi,2j=sin(100002j/di),pi,2j+1=cos(100002j/di)
- "mod"的主要特性是周期性,因此与周期函数cos/sin具有一定的等效性
- 因此,Sinusoidal 位置编码可以认为是一种特殊的β进制编码
4.2 从位置编码角度解决外推引发的问题
方案:训练阶段就预留好足够的位数
Transformer的原作者就是这么想的,认为预留好位数后模型就能具备对位置编码的泛化性。
- 现实情况:模型并没有按照我们的期望进行泛化
- 训练阶段大多数高位都是"0",因此这部分位数没有被充分训练,模型无法处理这些新编码。
线性内插法:
- 方案:把"新长度范围",等比例缩放至训练阶段的长度范围。
- 如训练时使用1k训练,需外推至4k,就将 [0,4k] 的范围线性缩放至 [0,1k]。
n / k β d / 2 − 1 \frac{n/k}{\beta^{d/2-1}} βd/2−1n/k
但是这会使最低位非常"拥挤",通常需要进行微调,使模型适应拥挤的映射关系。各维度差异较大!其他位置差异为1,个位差异较小,模型不易分辨,效果不佳。
进制转换法:
- 大模型其实并不知道我们输入的位置编码具体是多少"进制"的,他只对相对大小关系敏感
- 能否通过"进制转换"来等效"内插"?
如:10进制下,3位表示范围是0~999;16进制下,3位表示范围是0 ~ FFF(FFF16=4095)
虽然每一位上都有可能出现大于"9"的数,但相对大小差异仍为"1",模型有能力进行泛化。把内插的压力平均分摊到了每一位上。
4.2.1 NTK-aware 外推技术
预测阶段,计算系数,对位置编码的底数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/2−1n=βd/2−1n/k
n 是实际预测长度,k 是实际长度与训练长度的比值。求得:
λ = k 2 / ( d − 2 ) \lambda=k^{2/(d-2)} λ=k2/(d−2)
可得位置编码为:
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}{(\theta^{2/d}k^{2/(d-2)})^{i}}\right)=\sin\left(\frac{n}{(\theta k^{d/(d-2)})^{2i}}\right) sin((βλ)in)=sin((θ2/dk2/(d−2))in)=sin((θkd/(d−2))2in)
5 Function Calling
什么是Function Calling?为什么要有FunctionCalling?
FunctionCalling,即为让LLM调用外部函数解决问题,从而拓展LLM的能力边界。
- 指的是在语言模型中集成外部服务或API的调用能力。
- 模型可以在生成文本的过程中调用外部函数或服务,获取额外的数据或执行特定的任务。
Function Calling的意义?
- 解决时效性问题
今天是哪年哪日?今天天气如何? - 拓展LLM能力边界
帮我算一下 e 8 / 12345 e^8/12345 e8/12345 等于多少?帮我搜索一下这篇论文?
5.1 Function Calling 与 RAG
工作原理
- Function Calling:
指的是在语言模型中集成外部功能或API的调用能力;
模型可以在生成文本的过程中调用外部函数或服务,获取额外的数据或执行特定的任务。 - RAG:
结合了信息检索和文本生成;
它首先从一个大型的文档数据库中检索与输入查询相关的文档,然后将这些文档的信息融入到语言模型的生成过程中。
应用场景
- Function Calling:
适用于需要模型执行特定操作或与外部系统交互的场景。
例如,模型可以调用天气API来回答关于当前天气的问题,或者调用翻译服务来提供翻译;
在动态数据查询、任务自动化、系统集成以及结构化数据获取等方面有广泛应用。 - RAG:
适用于那些需要外部信息来提供准确回答的场景;
如问答系统,它可以通过检索到的信息来丰富和支模型的回答;
在医疗、法律、研究等领域,使用RAG可以保证提供的答案与权威文献一致。
6 LMDeploy 量化部署实践
6.1 配置LMDeploy环境
6.1.1 环境搭建
创建一个名为lmdeploy的conda环境,python版本为3.10,创建成功后激活环境并安装0.5.3版本的lmdeploy及相关包。
python
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
pip install datasets==2.19.2
6.1.2 InternStudio 环境获取模型
InternStudio统一把模型放置在/root/models/目录。
运行以下命令,创建文件夹并设置开发机共享目录的软链接。
python
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模型完成。
6.1.3 LMDeploy 验证启动模型文件
在量化工作正式开始前,我们还需要验证一下获取的模型文件能否正常工作,以免竹篮打水一场空。
让我们进入创建好的conda环境并启动InternLM2_5-1_8b-chat
python
conda activate lmdeploy
lmdeploy chat /root/models/internlm2_5-7b-chat
启动成功后,可以提问:
启动大模型后查看资源监控:
现在显存占用约23GB。
我们要运行参数量为7B的InternLM2.5,由InternLM2.5的码仓查询InternLM2.5-7b-chat的config.json文件可知,该模型的权重被存储为bfloat16格式:
对于一个7B(70亿)参数的模型,每个参数使用16位浮点数(等于 2个 Byte)表示,则模型的权重大小约为:
7×10^9 parameters×2 Bytes/parameter=14GB
为什么现在显存占用约23GB呢?
这是因为LMDeploy设置了kv cache量化占剩余显存的80%:
此时对于24GB的显卡,即30%A100,权重占用14GB显存,剩余显存24-14=10GB,因此kv cache占用10GB*0.8=8GB,加上原来的权重14GB,总共占用14+8=22GB。
实际加载模型后,其他项也会占用部分显存,因此剩余显存比理论偏低,实际占用会略高于22GB。
6.2 LMDeploy与InternLM2.5
前面我们直接在本地部署InternLM2.5。而在实际应用中,我们有时会将大模型封装为API接口服务,供客户端访问。
6.2.1 LMDeploy API部署InternLM2.5
启动API服务器
首先让我们进入创建好的conda环境,并通下命令启动API服务器,部署InternLM2.5模型:
python
conda activate lmdeploy
lmdeploy serve api_server \
/root/models/internlm2_5-7b-chat \
--model-format hf \
--quant-policy 0 \
--server-name 0.0.0.0 \
--server-port 23333 \
--tp 1
命令解释:
lmdeploy serve api_server
:这个命令用于启动API服务器。/root/models/internlm2_5-7b-chat
:这是模型的路径。--model-format hf
:这个参数指定了模型的格式。hf
代表"Hugging Face"
格式。--quant-policy 0
:这个参数指定了量化策略。--server-name 0.0.0.0
:这个参数指定了服务器的名称。在这里,0.0.0.0是一个特殊的IP地址,它表示所有网络接口。--server-port 23333
:这个参数指定了服务器的端口号。在这里,23333是服务器将监听的端口号。--tp 1
:这个参数表示并行数量(GPU数量)。
这一步由于部署在远程服务器上,所以本地需要做一下ssh转发才能直接访问。在你本地打开一个cmd或powershell窗口,输入命令如下:
python
ssh -CNg -L 23333:127.0.0.1:23333 root@ssh.intern-ai.org.cn -p 你的ssh端口号
打开浏览器,访问http://127.0.0.1:23333看到如下界面即代表部署成功:
6.2.2 以命令行形式连接API服务器
关闭http://127.0.0.1:23333网页,但保持终端和本地窗口不动,新建一个终端。
python
conda activate lmdeploy
lmdeploy serve api_client http://localhost:23333
稍待片刻,等出现double enter to end input >>>的输入提示即启动成功,此时便可以随意与InternLM2.5对话,同样是两下回车确定,输入exit退出。
6.2.3 以Gradio网页形式连接API服务器
保持启动23333端口的服务,在新建终端中输入exit退出。
输入以下命令,使用Gradio作为前端,启动网页:
python
lmdeploy serve gradio http://localhost:23333 \
--server-name 0.0.0.0 \
--server-port 6006
启动后,关闭之前的cmd/powershell窗口,重开一个,再次做一下ssh转发(因为此时端口不同)。在你本地打开一个cmd或powershell窗口,输入命令如下。
cpp
ssh -CNg -L 6006:127.0.0.1:6006 root@ssh.intern-ai.org.cn -p <你的ssh端口号>
打开浏览器,访问地址http://127.0.0.1:6006,然后就可以与模型尽情对话了:
6.3 LMDeploy Lite
随着模型变得越来越大,我们需要一些大模型压缩技术来降低模型部署的成本,并提升模型的推理性能。LMDeploy 提供了权重量化和 k/v cache两种策略。
6.3.1 设置最大kv cache缓存大小
kv cache是一种缓存技术,通过存储键值对的形式来复用计算结果,以达到提高性能和降低内存消耗的目的。在大规模训练和推理中,kv cache可以显著减少重复计算量,从而提升模型的推理速度。理想情况下,kv cache全部存储于显存,以加快访存速度。
模型在运行时,占用的显存可大致分为三部分:模型参数本身占用的显存、kv cache占用的显存,以及中间运算结果占用的显存。LMDeploy的kv cache管理器可以通过设置--cache-max-entry-count
参数,控制kv缓存占用剩余显存的最大比例。默认的比例为0.8。
6.3.2 设置在线 kv cache int4/int8 量化
自 v0.4.0 起,LMDeploy 支持在线 kv cache int4/int8 量化,量化方式为 per-head per-token 的非对称量化。此外,通过 LMDeploy 应用 kv 量化非常简单,只需要设定 quant_policy
和cache-max-entry-count
参数。目前,LMDeploy 规定 quant_policy=4
表示 kv int4 量化,quant_policy=8
表示 kv int8 量化。
例如,输入以下指令,启动API服务器:
python
lmdeploy serve api_server \
/root/models/internlm2_5-7b-chat \
--model-format hf \
--quant-policy 4 \
--cache-max-entry-count 0.4\
--server-name 0.0.0.0 \
--server-port 23333 \
--tp 1
相比使用BF16精度的kv cache,int4的Cache可以在相同4GB的显存下只需要4位来存储一个数值,而BF16需要16位。这意味着int4的Cache可以存储的元素数量是BF16的四倍。
6.3.3 W4A16 模型量化和部署
- W4:这通常表示权重量化为4位整数(int4)。这意味着模型中的权重参数将从它们原始的浮点表示(例如FP32、BF16或FP16,Internlm2.5精度为BF16)转换为4位的整数表示。这样做可以显著减少模型的大小。
- A16:这表示激活(或输入/输出)仍然保持在16位浮点数(例如FP16或BF16)。激活是在神经网络中传播的数据,通常在每层运算之后产生。
使用1.8B模型进行量化:
python
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
命令解释:
lmdeploy lite auto_awq
:lite
这是LMDeploy的命令,用于启动量化过程,而auto_awq
代表自动权重量化(auto-weight-quantization)。/root/models/internlm2_5-7b-chat
: 模型文件的路径。--calib-dataset 'ptb'
: 这个参数指定了一个校准数据集,这里使用的是'ptb'
(Penn Treebank,一个常用的语言模型数据集)。--calib-samples 128
: 这指定了用于校准的样本数量---128个样本--calib-seqlen 2048
: 这指定了校准过程中使用的序列长度---2048- -
-w-bits 4
: 这表示权重(weights)的位数将被量化为4位。 --work-dir /root/models/internlm2_5-7b-chat-w4a16-4bit
: 这是工作目录的路径,用于存储量化后的模型和中间结果。
等终端输出如下时,说明正在推理中,稍待片刻:
报错解决:
- 如果此处出现报错:
TypeError: 'NoneType' object is not callable
。原因datasets3.0 无法下载calibrate数据集,解决办法:pip install datasets==2.19.2
等待推理完成,便可以直接在你设置的目标文件夹看到对应的模型文件。
那么推理后的模型和原本的模型区别在哪里呢?最明显的两点是模型文件大小以及占据显存大小。
我们可以输入如下指令查看在当前目录中显示所有子目录的大小:
python
cd /root/models/
du -sh *
输出结果如下(其余文件夹都是以软链接的形式存在的,不占用空间,故显示为0):
那么原模型大小呢?输入以下指令查看。
python
cd /root/share/new_models/Shanghai_AI_Laboratory/
du -sh *
那么显存占用情况对比呢?
输入以下指令启动量化后的模型:
python
lmdeploy chat /root/models/internlm2_5-7b-chat --cache-max-entry-count 0.4
显存占用情况:
对于修改kv cache默认占用之前,即如1.3 LMDeploy验证启动模型文件所示直接启动模型的显存占用情况(23GB):
- 在 BF16 精度下,7B模型权重占用14GB:70×10^9 parameters×2 Bytes/parameter=14GB
- kv cache占用8GB:剩余显存24-14=10GB,kv cache默认占用80%,即10*0.8=8GB
- 其他项1GB
故23GB=权重占用14GB+kv cache占用8GB+其它项1GB
对于修改kv cache占用之后的显存占用情况(19GB):
- bfloat16是16位的浮点数格式,占用2字节(16位)的存储空间。int4是4位的整数格式,占用0.5字节(4位)的存储空间。因此,从bfloat16到int4的转换理论上可以将模型权重的大小减少到原来的1/4,即7B个int4参数仅占用3.5GB的显存。
- kv cache占用16.4GB:剩余显存24-3.5=20.5GB,kv cache默认占用80%,即20.5*0.8=16.4GB
- 其他项约为1GB
故20.9GB=权重占用3.5GB+kv cache占用16.4GB+其它项1GB
6.3.4 W4A16 量化+ KV cache+KV cache 量化
输入以下指令,让我们同时启用量化后的模型、设定kv cache占用和kv cache int4量化:
python
lmdeploy serve api_server \
/root/models/internlm2_5-1_8b-chat-w4a16-4bit \
--model-format awq \
--quant-policy 4 \
--cache-max-entry-count 0.4\
--server-name 0.0.0.0 \
--server-port 23333 \
--tp 1
显存占用11.3GB:
7 LMDeploy之FastAPI与Function call
7.1 API开发
与之前一样,让我们进入创建好的conda环境并输入指令启动API服务器:
python
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
保持终端窗口不动,新建一个终端,新建文件:
python
touch /root/internlm2_5.py
写内容:
python
# 导入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)
执行代码:
python
conda activate lmdeploy
python /root/internlm2_5.py
终端会输出如下结果:
此时代表我们成功地使用本地API与大模型进行了一次对话,如果切回第一个终端窗口,会看到如下信息,这代表其成功的完成了一次用户问题GET与输出POST:
7.2 Function call
关于Function call,即函数调用功能,它允许开发者在调用模型时,详细说明函数的作用,并使模型能够智能地根据用户的提问来输入参数并执行函数。完成调用后,模型会将函数的输出结果作为回答用户问题的依据。
首先让我们进入创建好的conda环境并启动API服务器。
启动服务:
python
conda activate lmdeploy
lmdeploy serve api_server \
/root/models/internlm2_5-7b-chat \
--model-format hf \
--quant-policy 0 \
--server-name 0.0.0.0 \
--server-port 23333 \
--tp 1
让我们使用一个简单的例子作为演示。输入如下指令,新建internlm2_5_func.py
,写入内容:
python
from openai import OpenAI
def add(a: int, b: int):
return a + b
def mul(a: int, b: int):
return a * b
tools = [{
'type': 'function',
'function': {
'name': 'add',
'description': 'Compute the sum of two numbers',
'parameters': {
'type': 'object',
'properties': {
'a': {
'type': 'int',
'description': 'A number',
},
'b': {
'type': 'int',
'description': 'A number',
},
},
'required': ['a', 'b'],
},
}
}, {
'type': 'function',
'function': {
'name': 'mul',
'description': 'Calculate the product of two numbers',
'parameters': {
'type': 'object',
'properties': {
'a': {
'type': 'int',
'description': 'A number',
},
'b': {
'type': 'int',
'description': 'A number',
},
},
'required': ['a', 'b'],
},
}
}]
messages = [{'role': 'user', 'content': 'Compute (3+5)*2'}]
client = OpenAI(api_key='YOUR_API_KEY', base_url='http://0.0.0.0:23333/v1')
model_name = client.models.list().data[0].id
response = client.chat.completions.create(
model=model_name,
messages=messages,
temperature=0.8,
top_p=0.8,
stream=False,
tools=tools)
print(response)
func1_name = response.choices[0].message.tool_calls[0].function.name
func1_args = response.choices[0].message.tool_calls[0].function.arguments
func1_out = eval(f'{func1_name}(**{func1_args})')
print(func1_out)
messages.append({
'role': 'assistant',
'content': response.choices[0].message.content
})
messages.append({
'role': 'environment',
'content': f'3+5={func1_out}',
'name': 'plugin'
})
response = client.chat.completions.create(
model=model_name,
messages=messages,
temperature=0.8,
top_p=0.8,
stream=False,
tools=tools)
print(response)
func2_name = response.choices[0].message.tool_calls[0].function.name
func2_args = response.choices[0].message.tool_calls[0].function.arguments
func2_out = eval(f'{func2_name}(**{func2_args})')
print(func2_out)
执行代码:
python
python /root/internlm2_5_func.py
输出结果:
我们可以看出InternLM2.5将输入'Compute (3+5)*2'
根据提供的function拆分成了"加"和"乘"两步,第一步调用function add
实现加,再于第二步调用function mul
实现乘,再最终输出结果16: