赞
踩
视频地址:
https://www.bilibili.com/video/BV1iW4y1A77P/?vd_source=7ec57e9f8774c23edf14450231ffa086
MarkDown:
<u>https://github.com/InternLM/tutorial/blob/main/lmdeploy/lmdeploy.md</u>
Repo:
<u>https://github.com/internLM/lmdeploy</u>
——模型部署
将训练好的模型在特定软硬件环境中启动的过程,使模型能够接收输入并返回预测结果;
为了满足性能和效率的需求,常常需要对模型进行优化,例如模型压缩和硬件加速
云端、边缘计算端、 移动端
CPU、GPU、NPU、TPU等
——大模型特点
庞大的参数量。7B模型仅权重就需要14+G
内存采用自回归生成token,需要缓存Attention的kw,带来巨大的内存开销
请求数不固定
Token逐个生成,且数量不定
Transformers结构,大部分是decoder-only
——大模型部署挑战
如何应对巨大的存储问题?低存储设备(消费级显卡、手机等)如何部署?
如何加速token的生成速度
如何解决动态shape,让推理可以不间断
如何有效管理和利用内存
如何提升系统整体吞吐量?
对于个体用户,如何降低响应时间?
——大模型部署方案
模型并行、transformer计算和访存优化、低比特量化、Continuous Batch、Page Attention·…
huggingface transformers、专门的推理加速框架
Imdeploy、vllm、tensorrt-lIm、deepspeed
llama.cpp、mlc-llm
LMDeploy是LLM在英伟达设备上部署的全流程解决方案。包括模型轻量化、推理和服务
两个基本概念
LLM是典型的访存密集型任务
Weight Only量化一举多得
判断是否有request推理结束。结束的request,发送结果,释放槽位
创建并激活conda环境
安装lmdeploy
使用 TurboMind 推理模型需要先将模型转化为 TurboMind 的格式,目前支持在线转换和离线转换两种形式。在线转换可以直接加载 Huggingface 模型,离线转换需需要先保存模型再加载。
TurboMind 是一款关于 LLM 推理的高效推理引擎,基于英伟达的 FasterTransformer 研发而成。它的主要功能包括:LLaMa 结构模型的支持,persistent batch 推理模式和可扩展的 KV 缓存管理器。
lmdeploy 支持直接读取 Huggingface 模型权重,目前共支持三种类型:
我们也可以直接启动本地的 Huggingface 模型。
以上命令都会启动一个本地对话界面,通过 Bash 可以与 LLM 进行对话。
离线转换需要在启动服务之前,将模型转为 lmdeploy TurboMind 的格式。
执行完成后将会在当前目录生成一个 workspace 的文件夹。这里面包含的就是 TurboMind 和 Triton “模型推理”需要到的文件。
weights 和 tokenizer目录分别放的是拆分后的参数和 Tokenizer。如果我们进一步查看 weights 的目录,就会发现参数是按层和模块拆开的,如下图所示。
——关于Tensor并行
Tensor并行一般分为行并行或列并行,原理如下图所示。
*列并行*
*行并行*
简单来说,就是把一个大的张量(参数)分到多张卡上,分别计算各部分的结果,然后再同步汇总。
模型转换完成后,我们就具备了使用模型推理的条件,接下来就可以进行真正的模型推理环节。
先尝试本地对话(Bash Local Chat),下面用(Local Chat 表示)在这里其实是跳过 API Server 直接调用 TurboMind。简单来说,就是命令行代码直接执行 TurboMind。
这里支持多种方式运行,比如Turbomind、PyTorch、DeepSpeed。但 PyTorch 和 DeepSpeed 调用的其实都是 Huggingface 的 Transformers 包,PyTorch表示原生的 Transformer 包,DeepSpeed 表示使用了 DeepSpeed 作为推理框架。Pytorch/DeepSpeed 目前功能都比较弱,不具备生产能力,不推荐使用。
启动后就可以和它进行对话了。
此时,Server 就是本地跑起来的模型(TurboMind),命令行可以看作是前端。
运用 lmdepoy 进行服务化。
“模型推理/服务”目前提供了 Turbomind 和 TritonServer 两种服务化方式。
此时,Server 是 TurboMind 或 TritonServer,API Server 可以提供对外的 API 服务。推荐使用 TurboMind。
首先,通过下面命令启动服务。
- # ApiServer+Turbomind api_server => AsyncEngine => TurboMind
- lmdeploy serve api_server ./workspace \
- --server_name 0.0.0.0 \
- --server_port 23333 \
- --instance_num 64 \
- --tp 1
上面的参数中 server_name 和 server_port 分别表示服务地址和端口,tp 参数我们之前已经提到过了,表示 Tensor 并行。还剩下一个 instance_num 参数,表示实例数,可以理解成 Batch 的大小。
然后,我们可以新开一个窗口,执行下面的 Client 命令。如果使用官方机器,可以打开 vscode 的 Terminal,执行下面的命令。
- # ChatApiClient+ApiServer(注意是http协议,需要加http)
- lmdeploy serve api_client http://localhost:23333
当然,刚刚我们启动的是 API Server,自然也有相应的接口。可以直接打开 http://{host}:23333 查看。
注意,这一步由于 Server 在远程服务器上,所以本地需要做一下 ssh 转发才能直接访问(与第一部分操作一样),命令如下:
ssh -CNg -L 23333:127.0.0.1:23333 root@ssh.intern-ai.org.cn -p <你的ssh端口号>
而执行本命令需要添加本机公钥,公钥添加后等待几分钟即可生效。ssh 端口号就是下面图片里的 33087。
这一部分主要是将 Gradio 作为前端 Demo 演示。在上一节的基础上,我们不执行后面的 api_client 或 triton_client,而是执行 gradio。
由于 Gradio 需要本地访问展示界面,因此也需要通过 ssh 将数据转发到本地。命令如下:
ssh -CNg -L 6006:127.0.0.1:6006 root@ssh.intern-ai.org.cn -p <你的 ssh 端口号>
API Server 的启动和上一节一样,这里直接启动作为前端的 Gradio。
- # Gradio+ApiServer。必须先开启 Server,此时 Gradio 为 Client
- lmdeploy serve gradio http://0.0.0.0:23333 \
- --server_name 0.0.0.0 \
- --server_port 6006 \
- --restful_api True
当然,Gradio 也可以直接和 TurboMind 连接,如下所示。
- # Gradio+Turbomind(local)
- lmdeploy serve gradio ./workspace
可以直接启动 Gradio,此时没有 API Server,TurboMind 直接与 Gradio 通信。
前面介绍的都是通过 API 或某种前端与”模型推理/服务“进行交互,lmdeploy 还支持 Python 直接与 TurboMind 进行交互,如下所示。
- from lmdeploy import turbomind as tm
-
- # load model
- model_path = "/root/share/temp/model_repos/internlm-chat-7b/"
- tm_model = tm.TurboMind.from_pretrained(model_path, model_name='internlm-chat-20b')
- generator = tm_model.create_instance()
-
- # process query
- query = "你好啊兄嘚"
- prompt = tm_model.model.get_prompt(query)
- input_ids = tm_model.tokenizer.encode(prompt)
-
- # inference
- for outputs in generator.stream_infer(
- session_id=0,
- input_ids=[input_ids]):
- res, tokens = outputs[0]
-
- response = tm_model.tokenizer.decode(res.tolist())
- print(response)
在上面的代码中,我们首先加载模型,然后构造输入,最后执行推理。
加载模型可以显式指定模型路径,也可以直接指定 Huggingface 的 repo_id,还可以使用上面生成过的 workspace。这里的 tm.TurboMind 其实是对 C++ TurboMind 的封装。
构造输入这里主要是把用户的 query 构造成 InternLLM 支持的输入格式,比如上面的例子中, query 是“你好啊兄嘚”,构造好的 Prompt 如下所示。
- """
- <|System|>:You are an AI assistant whose name is InternLM (书生·浦语).
- - InternLM (书生·浦语) is a conversational language model that is developed by Shanghai AI Laboratory (上海人工智能实验室). It is designed to be helpful, honest, and harmless.
- - InternLM (书生·浦语) can understand and communicate fluently in the language chosen by the user such as English and 中文.
- <|User|>:你好啊兄嘚
- <|Bot|>:
- """
Prompt 其实就是增加了 <|System|> 消息和 <|User|> 消息(即用户的 query),以及一个 <|Bot|> 的标记,表示接下来该模型输出响应了。
首先说 “模型推理/服务”,推荐使用 TurboMind,使用简单,性能良好。
LMDeploy应该是Transformers的3-5倍左右。
weights 的目录,里面存放的是模型按层、按并行卡拆分的参数,不过还有一个文件 config.ini 并不是模型参数,它里面存的主要是模型相关的配置信息。下面是一个示例。
- [llama]
- model_name = internlm-chat-7b
- tensor_para_size = 1
- head_num = 32
- kv_head_num = 32
- vocab_size = 103168
- num_layer = 32
- inter_size = 11008
- norm_eps = 1e-06
- attn_bias = 0
- start_id = 1
- end_id = 2
- session_len = 2056
- weight_type = fp16
- rotary_embedding = 128
- rope_theta = 10000.0
- size_per_head = 128
- group_size = 0
- max_batch_size = 64
- max_context_token_num = 1
- step_length = 1
- cache_max_entry_count = 0.5
- cache_block_seq_len = 128
- cache_chunk_size = 1
- use_context_fmha = 1
- quant_policy = 0
- max_position_embeddings = 2048
- rope_scaling_factor = 0.0
- use_logn_attn = 0
其中,模型属性相关的参数不可更改,主要包括下面这些。
- model_name = llama2
- head_num = 32
- kv_head_num = 32
- vocab_size = 103168
- num_layer = 32
- inter_size = 11008
- norm_eps = 1e-06
- attn_bias = 0
- start_id = 1
- end_id = 2
- rotary_embedding = 128
- rope_theta = 10000.0
- size_per_head = 128
和数据类型相关的参数也不可更改,主要包括两个。
- weight_type = fp16
- group_size = 0
weight_type 表示权重的数据类型。目前支持 fp16 和 int4。int4 表示 4bit 权重。当 weight_type 为 4bit 权重时,group_size 表示 awq 量化权重时使用的 group 大小。
剩余参数包括下面几个。
- tensor_para_size = 1
- session_len = 2056
- max_batch_size = 64
- max_context_token_num = 1
- step_length = 1
- cache_max_entry_count = 0.5
- cache_block_seq_len = 128
- cache_chunk_size = 1
- use_context_fmha = 1
- quant_policy = 0
- max_position_embeddings = 2048
- rope_scaling_factor = 0.0
- use_logn_attn = 0
一般情况下,我们并不需要对这些参数进行修改,但有时候为了满足特定需要,可能需要调整其中一部分配置值。这里主要介绍三个可能需要调整的参数。
本部分内容主要介绍如何对模型进行量化。主要包括 KV Cache 量化和模型参数量化。总的来说,量化是一种以参数或计算中间结果精度下降换空间节省(以及同时带来的性能提升)的策略。
正式介绍 LMDeploy 量化方案前,需要先介绍两个概念:
常见的 LLM 模型由于 Decoder Only 架构的特性,实际推理时大多数的时间都消耗在了逐 Token 生成阶段(Decoding 阶段),是典型的访存密集型场景。
那么,如何优化 LLM 模型推理中的访存密集问题呢? 我们可以使用 KV Cache 量化和 4bit Weight Only 量化(W4A16)。KV Cache 量化是指将逐 Token(Decoding)生成过程中的上下文 K 和 V 中间结果进行 INT8 量化(计算时再反量化),以降低生成过程中的显存占用。4bit Weight 量化,将 FP16 的模型权重量化为 INT4,Kernel 计算时,访存量直接降为 FP16 模型的 1/4,大幅降低了访存成本。Weight Only 是指仅量化权重,数值计算依然采用 FP16(需要将 INT4 权重反量化)。
KV Cache 量化是将已经生成序列的 KV 变成 Int8,使用过程一共包括三步:
①第一步:计算 minmax。主要思路是通过计算给定输入样本在每一层不同位置处计算结果的统计情况。
第一步执行命令如下:
- # 计算 minmax
- lmdeploy lite calibrate \
- --model /root/share/temp/model_repos/internlm-chat-7b/ \
- --calib_dataset "c4" \
- --calib_samples 128 \
- --calib_seqlen 2048 \
- --work_dir ./quant_output
这一步由于默认需要从 Huggingface 下载数据集,国内经常不成功。所以我们导出了需要的数据,大家需要对读取数据集的代码文件做一下替换。共包括两步:
第一步:复制 calib_dataloader.py 到安装目录替换该文件:cp /root/share/temp/datasets/c4/calib_dataloader.py /root/.conda/envs/lmdeploy/lib/python3.10/site-packages/lmdeploy/lite/utils/
第二步:将用到的数据集(c4)复制到下面的目录:cp -r /root/share/temp/datasets/c4/ /root/.cache/huggingface/datasets/
②第二步:通过 minmax 获取量化参数。主要就是利用下面这个公式,获取每一层的 K V 中心值(zp)和缩放值(scale)。
- zp = (min+max) / 2
- scale = (max-min) / 255
- quant: q = round( (f-zp) / scale)
- dequant: f = q * scale + zp
有这两个值就可以进行量化和解量化操作了。具体来说,就是对历史的 K 和 V 存储 quant 后的值,使用时在 dequant。
第二步的执行命令如下:
- # 通过 minmax 获取量化参数
- lmdeploy lite kv_qparams \
- --work_dir ./quant_output \
- --turbomind_dir workspace/triton_models/weights/ \
- --kv_sym False \
- --num_tp 1
在这个命令中,num_tp 的含义前面介绍过,表示 Tensor 的并行数。每一层的中心值和缩放值会存储到 workspace 的参数目录中以便后续使用。kv_sym 为 True 时会使用另一种(对称)量化方法,它用到了第一步存储的绝对值最大值,而不是最大值和最小值。
③第三步:修改配置。也就是修改 weights/config.ini 文件,这个我们在《2.6.2 模型配置实践》中已经提到过了(KV int8 开关),只需要把 quant_policy 改为 4 即可。
这一步需要额外说明的是,如果用的是 TurboMind1.0,还需要修改参数 use_context_fmha,将其改为 0。
接下来就可以正常运行前面的各种服务了,只不过咱们现在可是用上了 KV Cache 量化,能更省(运行时)显存了。
官方给出了 internlm-chat-7b 模型在 KV Cache 量化前后的显存对比情况
KV Cache 可以节约大约 20% 的显存。
在 opencompass 平台上测试了量化前后的精准度(Accuracy)对比情况
可以看出,精度不仅没有明显下降,相反在不少任务上还有一定的提升。可能得原因是,量化会导致一定的误差,有时候这种误差可能会减少模型对训练数据的拟合,从而提高泛化性能。量化可以被视为引入轻微噪声的正则化方法。或者,也有可能量化后的模型正好对某些数据集具有更好的性能。
总结一下,KV Cache 量化既能明显降低显存占用,还有可能同时带来精准度(Accuracy)的提升。
W4A16中的A是指Activation,保持FP16,只对参数进行 4bit 量化。使用过程也可以看作是三步。
①第一步:同 1.3.1,不再赘述。
②第二步:量化权重模型。利用第一步得到的统计值对参数进行量化,具体又包括两小步:
第二步的执行命令如下:
- #量化权重模型
- lmdeploy lite auto_awq \
- --model /root/share/temp/model_repos/internlm-chat-7b/ \
- --w_bits 4 \
- --w_group_size 128 \
- --work_dir ./quant_output
命令中 w_bits 表示量化的位数,w_group_size 表示量化分组统计的尺寸,work_dir 是量化后模型输出的位置。这里需要特别说明的是,因为没有 torch.int4,所以实际存储时,8个 4bit 权重会被打包到一个 int32 值中。所以,如果你把这部分量化后的参数加载进来就会发现它们是 int32 类型的。
③最后一步:转换成 TurboMind 格式。
- #转换模型的layout,存放在默认路径 ./workspace 下
- lmdeploy convert internlm-chat-7b ./quant_output \
- --model-format awq \
- --group-size 128
这个 group-size 就是上一步的那个 w_group_size。如果不想和之前的 workspace 重复,可以指定输出目录:--dst_path,比如:
- lmdeploy convert internlm-chat-7b ./quant_output \
- --model-format awq \
- --group-size 128 \
- --dst_path ./workspace_quant
接下来和上一节一样,可以正常运行前面的各种服务了,不过咱们现在用的是量化后的模型。
最后再补充一点,量化模型和 KV Cache 量化也可以一起使用,以达到最大限度节省显存。
官方在 NVIDIA GeForce RTX 4090 上测试了 4-bit 的 Llama-2-7B-chat 和 Llama-2-13B-chat 模型的 token 生成速度。测试配置为 BatchSize = 1,prompt_tokens=1,completion_tokens=512,结果如下表所示。
model | llm-awq | mlc-llm | turbomind |
Llama-2-7B-chat | turbomind | turbomind | turbomind |
Llama-2-13B-chat | turbomind | turbomind | turbomind |
可以看出,TurboMind 相比其他框架速度优势非常显著,比 mlc-llm 快了将近 30%。另外,也测试了 TurboMind 在不同精度和上下文长度下的显存占用情况,如下表所示。
model(context length) | 16bit(2048) | 4bit(2048) | 16bit(4096) | 4bit(4096) |
Llama-2-7B-chat | 15.1 | 6.3 | 16.2 | 7.5 |
Llama-2-13B-chat | OOM | 10.3 | OOM | 12.0 |
可以看出,4bit 模型可以降低 50-60% 的显存占用,效果非常明显。
总而言之,W4A16 参数量化后能极大地降低显存,同时相比其他框架推理速度具有明显优势。
本节是针对《模型量化》部分的最佳实践。
首先我们需要明白一点,服务部署和量化是没有直接关联的,量化的最主要目的是降低显存占用,主要包括两方面的显存:模型参数和中间过程计算结果。前者对应《3.2 W4A16 量化》,后者对应《3.1 KV Cache 量化》。
量化在降低显存的同时,一般还能带来性能的提升,因为更小精度的浮点数要比高精度的浮点数计算效率高,而整型要比浮点数高很多。
所以我们的建议是:在各种配置下尝试,看效果能否满足需要。这一般需要在自己的数据集上进行测试。具体步骤如下。
简单流程如下图所示。
另外需要补充说明的是,使用哪种量化版本、开启哪些功能,除了上述流程外,还需要考虑框架、显卡的支持情况,比如有些框架可能不支持 W4A16 的推理,那即便转换好了也用不了。
根据实践经验,一般情况下:
以上是针对项目开发情况,如果是自己尝试(玩儿)的话:
建议大家根据实际情况灵活选择方案。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。