进阶岛 任务3: LMDeploy 量化部署进阶实践
任务:https://github.com/InternLM/Tutorial/blob/camp3/docs/L2/LMDeploy/task.md
使用结合W4A16量化与kv cache量化的internlm2_5-1_8b-chat模型封装本地API并与大模型进行一次对话,作业截图需包括显存占用情况与大模型回复,参考4.1 API开发(优秀学员必做),请注意2.2.3节与4.1节应使用作业版本命令。
使用Function call功能让大模型完成一次简单的"加"与"乘"函数调用,作业截图需包括大模型回复的工具调用情况,参考4.2 Function call(选做)
文档:
https://github.com/InternLM/Tutorial/blob/camp3/docs/L2/LMDeploy/readme.md
视频:
https://www.bilibili.com/video/BV1df421q7cR/?vd_source=4ffecd6d839338c9390829e56a43ca8d
任务
gpu显存计算
进入创建好的conda环境并启动InternLM2_5-7b-chat,交互式环境:
lmdeploy chat /root/models/internlm2_5-7b-chat
显存占用23G:
lmdeploy默认设置cache-max-entry-count为0.8,即kv cache占用剩余显存的80%;
对于24GB的显卡,权重占用14GB显存,剩余显存24-14=10GB,因此kv cache占用10GB*0.8=8GB,加上原来的权重14GB,总共占用14+8=22GB。
实际加载模型后,其他项也会占用部分显存,因此剩余显存比理论偏低,实际占用会略高于22G.
大模型封装为API接口服务,供客户端访问
命令启动API服务器,部署InternLM2.5模型
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
端口映射:
ssh -CNg -L 23333:127.0.0.1:23333 root@ssh.intern-ai.org.cn -p 48626
windows下可以查看
打开浏览器,访问http://127.0.0.1:23333看到如下界面即代表部署成功。
启动命令行客户端。
lmdeploy serve api_client http://localhost:23333
以Gradio网页形式连接API服务器
使用Gradio作为前端,启动网页。
lmdeploy serve gradio http://localhost:23333
–server-name 0.0.0.0
–server-port 6006
量化
启动API服务器。
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
quant_policy=4 表示 kv int4 量化,
显存占用情况和对话:
解释:
都使用BF16精度下的internlm2.5 7B模型,故剩余显存均为10GB。 且 cache-max-entry-count 均为0.4,这意味着LMDeploy将分配40%的剩余显存用于kv cache,即10GB*0.4=4GB。 但quant-policy 设置为4时,意味着使用int4精度进行量化。因此,LMDeploy将会使用int4精度提前开辟4GB的kv cache。
相比使用BF16精度的kv cache,int4的Cache可以在相同4GB的显存下只需要4位来存储一个数值,而BF16需要16位。这意味着int4的Cache可以存储的元素数量是BF16的四倍。
启动命令行客户端。
lmdeploy serve api_client http://localhost:23333
对话:
W4A16 模型量化和部署
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: 这是工作目录的路径,用于存储量化后的模型和中间结果。
量化后模型大小:
(torch2_py310) root@intern-studio-50208960:~/models# du -sh *
0 InternVL2-26B
0 internlm2_5-1_8b-chat
1.5G internlm2_5-1_8b-chat-w4a16-4bit
0 internlm2_5-7b-chat
原模型大小:
(torch2_py310) root@intern-studio-50208960:~/models# du -sh /root/share/new_models/Shanghai_AI_Laboratory/internlm2_5-1_8b-chat
3.6G /root/share/new_models/Shanghai_AI_Laboratory/internlm2_5-1_8b-chat
启动量化后的模型。
lmdeploy chat internlm2_5-1_8b-chat-w4a16-4bit --model-format awq
显存占用:
W4A16量化之后:
1.在 int4 精度下,1.5B,float16 模型权重占用:3G/4=0.75GGB
2.kv cache占用18.6GB:剩余显存24-0.75=23.25GB,kv cache默认占用80%,即23.25*0.8=18.6GB
3.其他项: 1G
20.3GB=权重占用0.75GB+kv cache占用18.6GB+其它项1GB
W4A16 量化+ KV cache+KV cache 量化
同时启用量化后的模型、设定kv cache占用和kv cache int4量化
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
1.在 int4 精度下,1.5B,float16 模型权重占用:3G/4=0.75GGB
2.kv cache占用18.6GB:剩余显存24-0.75=23.25GB,kv cache默认占用80%,即23.25*0.4=9.3GB
3.其他项: 1G
11GB=权重占用0.75GB+kv cache占用9.3GB+其它项1GB
LMDeploy之FastAPI与Function call
启动API服务器。
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
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, # 指定要使用的模型IDmessages=[ # 定义消息列表,列表中的每个字典代表一个消息{"role": "system", "content": "你是一个友好的小助手,负责解决问题."}, # 系统消息,定义助手的行为{"role": "user", "content": "帮我讲述一个关于狐狸和西瓜的小故事"}, # 用户消息,询问时间管理的建议],temperature=0.8, # 控制生成文本的随机性,值越高生成的文本越随机top_p=0.8 # 控制生成文本的多样性,值越高生成的文本越多样
)# 打印出API的响应结果
print(response.choices[0].message.content)
Function call
函数调用功能,它允许开发者在调用模型时,详细说明函数的作用,并使模型能够智能地根据用户的提问来输入参数并执行函数。完成调用后,模型会将函数的输出结果作为回答用户问题的依据。
internlm2_5_func.py
from openai import OpenAIdef add(a: int, b: int):return a + bdef mul(a: int, b: int):return a * btools = [{'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)
笔记
对于一个7B(70亿)参数的模型,每个参数使用16位浮点数(等于 2个 Byte)表示,则模型的权重大小约为:
70亿个参数×每个参数占用2个字节=14GB,所以我们需要大于14GB的显存
大模型缓存推理计数
模型在运行时,占用的显存可大致分为三部分:模型参数本身占用的显存、kv cache占用的显存,以及中间运算结果占用的显存
internLm实现:
kv cache是一种缓存技术,通过存储键值对的形式来复用计算结果,以达到提高性能和降低内存消耗的目的
在大规模训练和推理中,kv cache可以显著减少重复计算量,从而提升模型的推理速度。理想情况下,kv cache全部存储于显存,以加快访存速度。
位置编码和缓存代码细节:
lmdeploy 对kv cache的实现
预先申请,申请剩余显存的,用cache_max_entry_count 控制kv缓存占用剩余显存的最大比例。默认的比例为0.8。
量化技术:
模型量化是一种优化技术,旨在减少机器学习模型的大小并提高其推理速度。量化通过将模型的权重和激活从高精度(如16位浮点数)转换为低精度(如8位整数、4位整数、甚至二值网络)来实现
LMDeploy 支持在线 kv cache int4/int8 量化,量化方式为 per-head per-token 的非对称量化。此外,通过 LMDeploy 应用 kv 量化非常简单,只需要设定 quant_policy 和cache-max-entry-count参数。目前,LMDeploy 规定 quant_policy=4 表示 kv int4 量化,
lmdeploy 量化方案
kv cache 量化
w4A16量化
使用awq算法:
w:权重 4:4bit 量化。 A: 激活值 16: 反量化为16 。
表示存储的时候量化4,计算的时候反量化为16 。
官方解释:
W4A16又是什么意思呢?
W4:这通常表示权重量化为4位整数(int4)。这意味着模型中的权重参数将从它们原始的浮点表示(例如FP32、BF16或FP16,Internlm2.5精度为BF16)转换为4位的整数表示。这样做可以显著减少模型的大小。
A16:这表示激活(或输入/输出)仍然保持在16位浮点数(例如FP16或BF16)。激活是在神经网络中传播的数据,通常在每层运算之后产生。
W4A16的量化配置意味着:
权重被量化为4位整数。
激活保持为16位浮点数。
LMDeploy的量化方案-Awq量化原理
核心观点1:权重并不等同重要,仅有0.1~1%小部分显著权重对推理结果影响较大
在线如果有办法将这0.1~1%的小部分显著权重保持FP16,对其他权重进行低比特量化,可以大幅降低内存占用问题来了:如果选出显著权重?
随机挑选 -听天由命
基于权重分布挑选-好像应该这样
基于激活值挑选-竟然是这样---最终方案
核心观点2:量化时对显著权重进行放大可以降低量化误差
外推技术
什么是外推?
长度外推性是一个训练和预测的长度不一致的问题。
外推引发的两大问题
预测阶段用到了没训练过的位置编码模型不可避免地在一定程度上对位置编码“过拟合预测注意力时注意力机制所处理的token数量远超训练时的数量导致计算注意力“熵”的差异较大
大模型为什么需要位置编码
并行化的自注意力机制并不具备区分token相对位置的能力
“mod”的主要特性是周期性,因此与周期函数cos/sin具有一定的等效性
因此,Sinusoidal位置编码可以认为是一种特殊的B进制编码
大模型外推技术-用位置编码
方案:训练阶段就预留好足够的位数
Transformer的原作者就是这么想的,认为预留好位数后模型就能具备对位置编码的泛化性。
现实情况:模型并没有按照我们的期望进行泛化
训练阶段大多数高位都是“0”,因此这部分位数没有被充分训练,模型无法处理这些新编码,
最终方案:
大模型其实并不知道我们输入的位置编码具体是多少“进制”的,他只对相对大小关系敏感
通过“进制转换”:来等效“内插”,即NTK-aware外推技术
function calling
什么是 Function Calling? 为什么要有 Function Calling?
Function Calling,即为让 LLM 调用外部函数解决问题,,从而拓展 LLM 的能力边界。
Timm
Timm(PyTorch Image Models)是一个广泛使用的开源库,它为计算机视觉任务提供了大量的预训练模型、层、实用工具、优化器、调度器、数据加载器、增强策略以及训练和验证脚本。这个库旨在简化模型的选择、创建和微调过程,让研究人员和开发者能够更容易地在自己的项目中尝试不同的模型结构和预训练权重。
Timm 库的特点包括:
- 提供了超过700种预训练模型,包括各种最新的视觉模型。
- 支持通过简单的接口快速加载模型,并可选择是否加载预训练权重。
- 包含了多种优化器和调度器,以及数据加载和增强的工具。
- 提供了灵活的模型创建和调整方法,使得用户可以根据自己的需求修改模型结构。
使用 Timm 库,你可以轻松地进行以下操作:
- 通过
timm.create_model
函数创建模型,并通过pretrained=True
参数加载预训练权重。 - 使用
timm.list_models()
函数列出所有可用的模型名称,或者通过正则表达式匹配特定模型。 - 对模型进行迁移学习或微调,以适应特定的任务或数据集。
Timm 库适用于多种应用场景,包括图像分类、目标检测、语义分割等,并且可以与快速AI(fastai)等其他库集成,提供更强大的功能。
如果你想要开始使用 Timm 库,可以通过以下命令安装:
pip install timm
然后,你可以按照官方文档中的指南开始使用库中的模型和工具。更多详细信息和教程,可以参考 Timm 的官方文档 。
gradio
Gradio 是一个用于快速构建机器学习模型演示或 Web 应用程序的 Python 库。你可以使用 Gradio 轻松地将任何 Python 函数包装成交互式的 Web 界面,并与他人分享。以下是如何使用 Gradio 的基本步骤:
-
安装 Gradio:
首先,你需要安装 Gradio。可以通过 pip 安装:pip install gradio
如果你使用的是 Python 虚拟环境,确保在安装时已经激活了虚拟环境。
-
创建 Gradio 应用:
你可以在 Python 文件、Jupyter Notebook 或 Google Colab 中创建 Gradio 应用。以下是一个简单的 Gradio 应用示例:import gradio as grdef greet(name):return "Hello " + name + "!"demo = gr.Interface(fn=greet, inputs="text", outputs="text") demo.launch()
这段代码创建了一个简单的 Gradio 应用,它接受文本输入并返回问候语。
-
分享 Gradio 应用:
你可以通过设置share=True
参数在launch()
函数中创建一个公共链接,以便与他人分享你的 Gradio 应用:demo.launch(share=True)
这将生成一个可以在互联网上访问的链接,你可以将这个链接发送给任何人,让他们在你的机器上远程使用你的 Gradio 应用。
-
托管 Gradio 应用:
如果你想永久托管你的 Gradio 应用,可以使用 Hugging Face Spaces。这是一个免费托管 Gradio 应用的平台。 -
Gradio 组件:
Gradio 提供了多种内置组件,如文本框、滑块、图像、下拉菜单等,你可以根据需要选择适当的组件来构建你的应用界面。 -
样式和布局:
你可以通过 CSS 自定义 Gradio 应用的样式,并通过 Blocks API 来创建更复杂的布局。 -
事件和交互:
Gradio 支持事件监听和交互式组件,你可以使用这些功能来创建动态和响应式的用户界面。 -
流式输入和输出:
对于需要处理实时数据的应用,Gradio 支持流式输入和输出,这对于视频流或实时数据可视化非常有用。 -
批处理:
Gradio 还支持批处理函数,这允许你的函数一次处理多个输入并返回多个输出。 -
文档和资源:
你可以在 Gradio 的官方文档中找到更多关于如何使用 Gradio 的信息和示例,以及如何构建和分享你的应用。
通过这些步骤,你可以快速开始使用 Gradio 来构建和分享你的机器学习模型或任何 Python 函数的 Web 界面。如果你需要更详细的指导,可以参考 Gradio 的官方文档 。