LMDeploy 部署 VLMs 的方法与探讨
感谢LMDeploy项目负责人吕晗老师的分享!
01
LMDeploy简介
LMDeploy 是一个高效且友好的大型语言模型(LLMs)和视觉-语言模型(VLMs)部署工具箱,由上海人工智能实验室模型压缩和部署团队开发,涵盖了模型量化、离线推理和在线服务等功能。
1.1 软硬件平台
支持的软硬件平台包括:
- Linux、Windows 系统 + NVIDIA 显卡。运行时,cuda runtime的最低要求是11.3。支持的 NVIDIA 显卡型号包括:
- Volta(sm70): V100
- Turing(sm75): 20 系列,T4
- Ampere(sm80,sm86): 30 系列,A10, A16, A30, A100 等
- Ada Lovelace(sm89): 40 系列
- Hopper(sm90):H100(尚未深度优化)
- Huawei 910b
1.2 项目结构****
1.2.1 接口层
Python:离线推理
RESTful:访问在线服务
gRPC:访问 triton inference server 接口。没有支持 VLM 模型
1.2.2 量化层
权重量化:支持 AWQ 和 SmoothQuant 算法
K/V Cache:KV在线量化
1.2.3 引擎层
**TurboMind 引擎:**起源于 FasterTransformer,由 C++ 和 CUDA 开发,致力于推理性能的优化
**PyTorch 引擎:**采用纯 Python 开发,kernel 部分使用 openai triton 编写,旨在降低开发者的门槛
两个引擎互为补充,相辅相成,共同打造了 LMDeploy 的基石
1.2.4 服务层
OpenAI-like Server:推理服务,兼容 openai 接口
Gradio:web demo的服务
Triton Inference Server:不支持 VLM。LLM 也不推荐
1.3 支持的模型
02
LMDeploy 使用指南之 VLMs 部署
2.1 环境安装
**方案 1:**创建一个干净的 conda 环境,pip安装 lmdeploy。支持的 python 版本是 3.8 - 3.12
conda create -n lmdeploy python=3.8 -y
conda activate lmdeploy
pip install lmdeploy
到这里为止,可以使用 lmdeploy 部署 LLM 模型。
但,如果要部署 VLM 模型,比如 InternVL系列,InternLM-XComposer系列、LLaVA 等等,需要安装上游模型库需要的依赖。原因,LMDeploy 对 VLMs 视觉部分的模型推理以及图像预处理,是复用上游库的。
以 InternVL2 模型为例,还需要安装:
pip install timm
# 建议从https://github.com/Dao-AILab/flash-attention/releases寻找和环境匹配的whl包
pip install flash-attn
因为复用VLMs上游库关于图像的预处理和视觉模型的推理,且不同的VLM依赖各不相同,出于维护性方面的考虑,LMDeploy 没有把 VLMs 的依赖,比如 timm,flash-attn,放在自己的依赖列表里。
但是,torchvision放入了依赖列表。原因是,LMDeploy 依赖 torch,担心用户安装 torchvision 时没留意torch的版本,安装了不匹配的。(后续考虑移除,相信用户)
**方案 2:**docker 镜像
LMDeploy 只提供可以部署 LLM 模型的镜像,不提供部署 VLM 的镜像,理由如上。
推荐用户基于 LMDeploy 镜像构建 VLM 部署镜像,比如:
ARG CUDA_VERSION=cu12
FROM openmmlab/lmdeploy:latest-cu12 AS cu12
FROM openmmlab/lmdeploy:latest-cu11 AS cu11
RUN python3 -m pip install timm
# 建议从https://github.com/Dao-AILab/flash-attention/releases寻找和环境匹配的whl包
RUN python3 -m pip install flash-attn
LMDeploy 镜像的命名方式如下:
openmmlab/lmdeploy:latest-cu12
openmmlab/lmdeploy:latest-cu11
openmmlab/lmdeploy:latest # 和 openmmlab/lmdeploy:latest-cu12 一致
openmmlab/lmdeploy:{tag}-cu12 # 比如 openmmlab/lmdeploy:v0.5.3-cu12
openmmlab/lmdeploy:{tag}-cu11
2.2 离线推理
以 InternVL2-8B 模型为例,最简单的 “Hello, world” 式的推理方式如下:
from lmdeploy import pipeline
from lmdeploy.vl import load_image
pipe = pipeline('OpenGVLab/InternVL2-8B')
image = load_image('https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/tests/data/tiger.jpeg')
response = pipe(('describe this image', image))
print(response)
在构造 pipeline 时,如果没有指定使用 TurboMind 引擎或 PyTorch 引擎进行推理,LMDeploy 将根据它们各自的能力自动分配一个,默认优先使用 TurboMind 引擎。
当然,你可以手动选择一个引擎,我们将在推理引擎配置章节中,详细介绍两种引擎的配置方法。
2.2.1 创建 pipeline
2.2.1.1 API
def pipeline(model_path: str,
model_name: Optional[str] = None,
backend_config: Optional[Union[TurbomindEngineConfig,
PytorchEngineConfig]] = None,
chat_template_config: Optional[ChatTemplateConfig] = None,
log_level='ERROR',
**kwargs):
model_path:模型路径
- 可以是 huggingface hub 上的 model_repo_id
- 可以是 modelscope hub 上的 model_repo_id。在这种情况下,需要安装modelscope,并设置环境变量
pip install modelscope
export LMDEPLOY_USE_MODELSCOPE=Tru
- 对于 LLM 模型来说,它可以是经多 lmdeploy convert 转换后的模型的路径。不支持离线转换 VLMs 模型
model_name:内置对话模板名称
- v0.6.0(尚未发布)将会移除这个参数,用 ChatTemplateConfig 中的 model_name 替代
backend_config | 推理引擎配置参数 |
---|---|
chat_template_config | 对话模板参数 |
log_level | 日志级别。默认为 ERROR |
vision_config | 视觉模型推理的配置参数 |
- vision_config 被隐藏在 kwargs 中了
2.2.1.2 推理引擎配置
引擎参数的定义
Turbomind
@dataclass
class TurbomindEngineConfig:
model_format: Optional[str] = None
tp: int = 1
session_len: Optional[int] = None
max_batch_size: int = 128
cache_max_entry_count: float = 0.8
cache_block_seq_len: int = 64
enable_prefix_caching: bool = False
quant_policy: int = 0
rope_scaling_factor: float = 0.0
use_logn_attn: bool = False
download_dir: Optional[str] = None
revision: Optional[str] = None
max_prefill_token_num: int = 8192
num_tokens_per_iter: int = 0
max_prefill_iters: int = 1
Pytorch
@dataclass
class PytorchEngineConfig:
tp: int = 1
session_len: int = None
max_batch_size: int = 128
cache_max_entry_count: float = 0.8
eviction_type: str = 'recompute'
prefill_interval: int = 16
block_size: int = 64
num_cpu_blocks: int = 0
num_gpu_blocks: int = 0
adapters: Dict[str, str] = None
max_prefill_token_num: int = 4096
thread_safe: bool = False
enable_prefix_caching: bool = False
device_type: str = 'cuda'
download_dir: str = None
revision: str = None
引擎通用的参数:
- 单机多卡推理(tp)
tp用于张量并行时的GPU数量,缺省值 1,目前被约束为2n
LMDeploy 只支持单机多卡,不支持多机多卡
Turbomind
from lmdeploy import pipeline
from lmdeploy import TurbomindEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=TurbomindEngineConfig(
tp=2))
Pytorch
from lmdeploy import pipeline
from lmdeploy import PytorchEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=PytorchEngineConfig(
tp=2))
- 设置内存使用量(cache_max_entry_count)
cache_max_entry_count 表示加载模型权重后 K/V 缓存占用的空闲 GPU 内存的比例。默认值是 0.8。
K/V 缓存分配方式是一次性申请,重复性使用,这就是为什么 pipeline 以及下文中的 api_server 在启动后会消耗大量 GPU 内存。
如果你遇到内存不足(OOM)错误的错误,可能需要考虑降低 cache_max_entry_count 的
Turbomind
from lmdeploy import pipeline
from lmdeploy import TurbomindEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=TurbomindEngineConfig(
tp=2))
Pytorch
from lmdeploy import pipeline
from lmdeploy import PytorchEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=PytorchEngineConfig(
tp=2))
- 设置最大推理长度(session_len)
session_len表示上下文窗口的最大长度,包括输入的prompt token个数和输出的token个数
Turbomind
from lmdeploy import pipeline
from lmdeploy import TurbomindEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=TurbomindEngineConfig(
session_len=8192))
Pytorch
from lmdeploy import pipeline
from lmdeploy import PytorchEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=PytorchEngineConfig(
session_len=8192))
- 设置推理最大batch(max_batch_size)
max_batch_size 表示 Continuous batching 推理时,最大的批处理数量
Turbomind
from lmdeploy import pipeline
from lmdeploy import TurbomindEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=TurbomindEngineConfig(
max_batch_size=256))
Pytorch
from lmdeploy import pipeline
from lmdeploy import PytorchEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=PytorchEngineConfig(
max_batch_size=256))
- 设置 prefix caching 开关(enable_prefix_caching)
Turbomind
from lmdeploy import pipeline
from lmdeploy import TurbomindEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=TurbomindEngineConfig(
enable_prefix_caching=True))
Pytorch
from lmdeploy import pipeline
from lmdeploy import PytorchEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=PytorchEngineConfig(
enable_prefix_caching=
- 设置prefill chunk的最大token数(max_prefill_token_num)
Turbomind
from lmdeploy import pipeline
from lmdeploy import TurbomindEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=TurbomindEngineConfig(
max_prefill_token_num=8192))
Pytorch
from lmdeploy import pipeline
from lmdeploy import PytorchEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B',
backend_config=PytorchEngineConfig(
max_prefill_token_num=4096))
- 设置模型下载参数(download_dir、revision)
当 model_path 不是本地路径时,LMDeploy 会从 huggingface hub 或者 modelscope hub上下载模型,默认下载最新版本,默认存储在 ~/.cache 下。用户可以通过 revision 指定模型版本,通过download_dir 指定模型存放路径
TurbomindEngine 专用参数:
- 设置Dynamic NTK外推参数(rope_scaling_factor)
from lmdeploy import pipeline, GenerationConfig, TurbomindEngineConfig
backend_config = TurbomindEngineConfig(
rope_scaling_factor=2.5,
session_len=1000000,
max_batch_size=1,
cache_max_entry_count=0.9,
tp=2)
pipe = pipeline('internlm/internlm2_5-7b-chat-1m', backend_config=backend_config)
prompt = 'Use a long prompt to replace this sentence'
gen_config = GenerationConfig(top_p=0.8,
top_k=40,
temperature=0.8,
max_new_tokens=1024)
response = pipe(prompt, gen_config=gen_config)
print(response)
- 设置在线 KV Cache量化精度(quant_policy)
quant_policy 表示 LLM 模型 KV cache 量化策略的参数,4表示 4bit 量化,8表示 8bit 量化。8bit KV cache 几乎不掉点,可认为量化精度无损。
from lmdeploy import pipeline, TurbomindEngineConfig
pipe = pipeline('OpenGVLab/InternVL2-8B',
backend_config=TurbomindEngineConfig(
quant_policy=8))
- 设置模型格式(model_format)
取值范围{None,hf,llama,awq,gptq}。None 根据模型文件结构自动判断,hf 表示 huggingface上类llama结构的模型,llama 表示 meta_llama(pytorch权重格式),awq 表示 awq 量化模型,gptq 表示 gptq 量化模型(0.6.0支持)。
from lmdeploy import pipeline, TurbomindEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B-AWQ',
backend_config=TurbomindEngineConfig(
model_format='awq'))
from lmdeploy import pipeline, TurbomindEngineConfig
pipe = pipeline(
'OpenGVLab/InternVL2-8B-AWQ',
backend_config=TurbomindEngineConfig(
model_format=None))
- KV block 的大小(cache_block_seq_len)
一个 KV cache block 能够容纳的 token 的数量。默认是 64。如果 GPU compute_capability >= 8.0,应该是32的倍数,否则应该是64的倍数
- num_tokens_per_iter
控制一次forward pass 处理的token数量。这包括预填充(prefill)和解码(decoding)。例如,当有8个解码序列和2个预填充序列时,首先为8个解码token分配所需要的资源,然后为num_tokens_per_iter - 8个预填充token分配资源。
它的默认值为 max_prefill_token_num
为了减少long prompt 序列的影响,num_tokens_per_iter的最优值与模型大小、GPU型号和工作负载有关。对于Llama3-8B、A100 80G和128个并发使用情况,该值在128到256之间。如果你使用的是4090显卡,可以从64-128开始。基本上,这个值越小,预填充的影响就越小(但它要大于max_batch_size)。
- max_prefill_iters
控制单个序列预填充的最大迭代次数。
在max_prefill_iters被设定后,num_tokens_per_iter可能会被重新计算。例如,当一个请求有2000个 token 时,如果max_prefill_iters=1,意味着预填充在1次迭代中完成,不管num_tokens_per_iter被设置为多少。所以,为了让num_tokens_per_iter充分发挥其效果,max_prefill_iters应该设置为一个比较大的值。
它的默认值为 (session_len + max_prefill_token_num - 1) // max_prefill_token_num
当找到一个合适的num_tokens_per_iter后,max_prefill_iters用于平衡解码的平滑度和首token延迟。首先将其设置为一个大值(你会观察到首令牌延迟的增加),然后逐渐减小,直到达到可接受的首令牌延迟。
2.2.1.3 对话模板配置
@dataclass
class ChatTemplateConfig:
model_name: str
system: Optional[str] = None
meta_instruction: Optional[str] = None
eosys: Optional[str] = None
user: Optional[str] = None
eoh: Optional[str] = None
assistant: Optional[str] = None
eoa: Optional[str] = None
separator: Optional[str] = None
capability: Optional[Literal['completion', 'infilling', 'chat',
'python']] = None
stop_words: Optional[List[str]] = None
model_name: 对话模板名称
system、meta_instruction、eosys 分别表示 system 角色的名称,system角色的提示词,system角色的提示词的结束符。
以 InternLM2 模型为例,它的对话模板的这3个属性如下:
# InternLM2
system = "<|im_start|>system"
meta_instruction = """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 中文.
"""
eosys = "<|im_end|>\n"
- user、eoh 分别表示 user 这个角色的名称,user角色的提示词的结束符
- assistant、eoa 分别表示 AI assistant 这个角色的名称,以及这个角色回复内容的结束符
- seperator:两轮对话之间的分隔符
- capability:模型的能力。“completion” 表示文本补全,“chat” 表示对话,“infilling” 表示代码填空(codellama专用)、"python"表示python代码能力(codellama专用)
- stop_words:结束符,用来停止 AI assistant 应答的。当前,LMDeploy 只支持每个 stop_word 只能是 1 个 token_id 的情况
对话模板的作用是用来拼接一个对话序列的。
我们假设一个对话序列为 U1A1U2A2…Un,其中Ui表示User在第i轮对话时输入的提示词(prompt),Ai表示模型或者说AI Assistant在第i轮对话中生成的答案。
在 LMDeploy 中,对话序列的拼接方式有 2 种,分别对应两种推理模式:交互式推理(有状态推理)、非交互式推理(无状态推理)。
这两种推理方式的区别在于,第 i轮对话中,用户侧的输入是什么。交互式推理的输入是 Ui,非交互式推理的输入是 U1A1U2A2…Ui。换言之,交互式推理模式下,用户不需要输入历史对话记录,因为历史对话的信息已经被推理引擎缓存了(包括token、KV、游标等),而非交互式推理模式则需要输入历史对话记录。
LMDeploy 拼接对话序列的两种方式在 BaseChatTemplate 中实现。get_prompt 用户交互式推理模式,messages2prompt 用于非交互式推理模式
class BaseChatTemplate:
def get_prompt(self, prompt, sequence_start=True):
"""Return decorated prompt in interactive inference mode
"""
if sequence_start: # decorate $$U_0$$
return f'{self.system}{self.meta_instruction}{self.eosys}' \
f'{self.user}{prompt}{self.eoh}' \
f'{self.assistant}'
else: # decorate $$U_i$$
return f'{self.user}{prompt}{self.eoh}' \
f'{self.assistant}'
def messages2prompt(self, messages, sequence_start=True, **kwargs):
"""Return decorated prompt in non-interactive inference mode.
Args:
messages (str|List): user's input prompt which is supposed
to be in OpenAI format
"""
if isinstance(messages, str):
# fallback to `get_prompt` when `messages` isn't a list
return self.get_prompt(messages, sequence_start)
# "box" indicates "begin of x (role)"
box_map = dict(user=self.user,
assistant=self.assistant,
system=self.system)
# "eox" indicates "end of x (role)"
eox_map = dict(user=self.eoh,
assistant=self.eoa + self.separator,
system=self.eosys)
for message in messages:
role = message['role']
content = message['content']
ret += f'{box_map[role]}{content}{eox_map[role]}'
在上述代码中,为了突出重点,简化了部分逻辑。完成的代码请参考:https://github.com/InternLM/lmdeploy/blob/main/lmdeploy/model.py
对话模板的使用方式有以下几种:
-
在不指定对话模板配置时,LMDeploy 根据模型的路径名,匹配内置对话模板名
-
指定内置对话模板
from lmdeploy import pipeline, ChatTemplateConfig
pipe = pipeline('/the/path/of/your/finetuned/internvl2/8b/model',
chat_template_config=ChatTemplateConfig(
model_name='internvl-internlm2'
)
lmdeploy list 可以显示内置对话模板。内置对话模板与支持的模型之间的映射关系请参考附录-内置对话模板
- 改变内置对话模板的属性
from lmdeploy import pipeline, ChatTemplateConfig
pipe = pipeline('/the/path/of/your/finetuned/internvl2/8b/model',
chat_template_config=ChatTemplateConfig(
model_name='internvl2-internlm2',
meta_instruction='You are a helpful assistant'
)
LMDeploy 会把非None的属性信息更新到指定的对话模板里
- 自定义对话模板
**方式一:**对话模板的属性定义和拼接方式完全符合BaseChatTemplate的定义
只需设置 ChatTemplateConfig 各字段即可。LMDeploy会创建BaseChatTemplate实例
**方式二:**对话模板的属性或者拼接方式不符合 BaseChatTemplate的定义
@register_module(name="awesome")
class MyChatTemplate:
def __init__(*args, **kwargs):
pass
def get_prompt(self, prompt, sequence_start=True):
if sequence_start:
# TODO: return the decorated prompt when it is the first request of a sequence
pass
else:
# TODO: return the decorated prompt when it is NOT the first request of a sequence
pass
def message2prompt(self, messages, sequence_start=True, **kwargs):
if isinstance(messages, str):
return self.get_prompt(messages, sequence_start)
# TODO: return the prompt after applying the chat template
pipe = pipeline("/the/path/of/your/awesome/model",
chat_template_config=ChatTemplateConfig(
model_name="awesome"))
2.2.1.4 视觉模型推理配置
@dataclass
class VisionConfig:
max_batch_size: int = 1
thread_safe: bool = False
max_batch_size 表示图像批量处理的大小。值越大,OOM 的风险越高,因为 VLM 模型中的 LLM 部分会提前预分配大量的内存。
2.2.2 使用 pipeline
2.2.2.1 API
def __call__(self,
prompts: Union[VLPromptType, List[Dict], List[VLPromptType],
List[List[Dict]]],
gen_config: Optional[GenerationConfig] = None,
**kwargs):
prompts 用户输入的 prompt 和 image。它可以是以下几种形式:
- str:纯文本
- list[str]:纯文本序列
- tuple(str, PIL.Image):文本 + 图像
- tuple(str, list[PIL.Image]):文本 + 图像序列
- list[tuple(str, PIL.Image)]:(文本+图像)序列
- GPT4V的格式
[{
"role":"user",
"content": [{
"type": "text",
"text": "the input text prompt",
},
{
"type": "image_url",
"image_url": {
"url": "data:image/jpeg;base64,{image_base64_data}"
}
},
{
"type": "image_data",
"image_data": {
"data": PIL.Image.Image
}
},
...
{...}]
}]
LMDeploy 会把前 5 种格式处理为 GPT4V 的格式
gen_config 生成token的采样参数
@dataclass
class GenerationConfig:
n: int = 1
max_new_tokens: int = 512
top_p: float = 1.0
top_k: int = 1
temperature: float = 0.8
repetition_penalty: float = 1.0
ignore_eos: bool = False
random_seed: int = None
stop_words: List[str] = None
bad_words: List[str] = None
min_new_tokens: int = None
skip_special_tokens: bool = True
logprobs: int = None
- n:输入请求要求生成的序列的数量,目前只支持 1
- max_new_tokens:输入请求要求的最多生成的token的个数
- top_p:采样时,考虑累积概率超过top_p的最小可能token集合中进行采样
- top_k:采样时,考虑从概率最高的top_k个token中进行采样。top_k=1 表示贪心搜索
- temperature:采样温度。temperature=0.f 表示贪心搜索
- repetition_penalty:防止模型生成重复单词或短语的惩罚。大于1的值会抑制重复。
- ignore_eos:是否忽略 eos_token_id
- random_seed:采样token时使用的种子
- stop_words:token生成的停止符。目前要求每个 stop_word 被 tokenizer 时,只能有一个token_id
- bad_words:永远不会被生成的单词。目前要求每个 bad_word 被 tokenizer 时,只能有一个token_id
- min_new_tokens:输入请求要求生成的最少token数量
- skip_special_tokens:是否在解码中忽略特殊 token。默认为True
- logprobs:每个输出 token 返回的对数概率数量
- 关于生成时采样方式的介绍,推荐阅读 https://huggingface.co/blog/how-to-generate
2.2.2.2 更多示例
- 多图输入
对于多图的场景,在推理时,只要把它们放在一个列表中即可。不过,多图意味着输入 token 数更多,所以通常需要增大推理的上下文长度
from lmdeploy import pipeline, TurbomindEngineConfig
from lmdeploy.vl import load_image
pipe = pipeline('OpenGVLab/InternVL2-8B',
backend_config=TurbomindEngineConfig(session_len=10000))
image_urls=[
'https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/demo/resources/human-pose.jpg',
'https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/demo/resources/det.jpg'
]
images = [load_image(img_url) for img_url in image_urls]
response = pipe(('describe these images', images))
print(response)
- 批量图文输入
from lmdeploy import pipeline, TurbomindEngineConfig
from lmdeploy.vl import load_image
pipe = pipeline('OpenGVLab/InternVL2-8B',
backend_config=TurbomindEngineConfig(session_len=8192))
image_urls=[
"https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/demo/resources/human-pose.jpg",
"https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/demo/resources/det.jpg"
]
prompts = [('describe this image', load_image(img_url)) for img_url in image_urls]
response = pipe(prompts)
print(response)
- 多轮对话
pipeline 进行多轮对话有两种方式,一种是按照 GPT4V 的格式来构造 messages,另外一种是使用 pipeline.chat 接口。
from lmdeploy import pipeline, TurbomindEngineConfig, GenerationConfig
from lmdeploy.vl import load_image
pipe = pipeline('OpenGVLab/InternVL2-8B',
backend_config=TurbomindEngineConfig(session_len=8192))
image = load_image('https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/demo/resources/human-pose.jpg')
gen_config = GenerationConfig(top_k=40, top_p=0.8, temperature=0.6)
sess = pipe.chat(('describe this image', image), gen_config=gen_config)
print(sess.response.text)
sess = pipe.chat('What is the woman doing?', session=sess, gen_config=gen_config)
print(sess.response.text)
- 自定义图像 token 的位置
默认情况下,LMDeploy 会根据算法 repo 提供的对话模版将表示图片的特殊 token 插入到 user prompt 中,但在一些模型中,图片 token 的位置并没有限制,如 deepseek-vl,或者用户需要自定义图片 token 插入的位置。这种情况下,用户需要手动将表示图片的 token 插入到 prompt 中。LMDeploy 使用 <IMAGE_TOKEN> 作为表示图片的特殊 token。
from lmdeploy import pipeline
from lmdeploy.vl import load_image
from lmdeploy.vl.constants import IMAGE_TOKEN
pipe = pipeline('deepseek-ai/deepseek-vl-1.3b-chat')
image = load_image('https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/tests/data/tiger.jpeg')
response = pipe((f'describe this image{IMAGE_TOKEN}', image))
print(response)
2.3 在线服务
2.3.1 启动服务
2.3.1.1 方式一:使用 lmdeploy cli 工具
lmdeploy serve api_server OpenGVLab/InternVL2-8B
此命令将在本地主机上的端口 23333 启动一个与 OpenAI 接口兼容的模型推理服务。你可以使用 --server-port 选项指定不同的服务器端口。更多参数的说明请参考章节api_server参数
2.3.1.2 方式二:使用 docker
docker run --runtime nvidia --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
--env "HUGGING_FACE_HUB_TOKEN=<secret>" \
-p 23333:23333 \
--ipc=host \
openmmlab/lmdeploy:latest \
lmdeploy serve api_server OpenGVLab/InternVL2-8B
2.3.1.3 api_server 参数
root@lmdeploy-on-121:~/lmdeploy# lmdeploy serve api_server -h
usage: lmdeploy serve api_server [-h] [--server-name SERVER_NAME] [--server-port SERVER_PORT]
[--allow-origins ALLOW_ORIGINS [ALLOW_ORIGINS ...]] [--allow-credentials]
[--allow-methods ALLOW_METHODS [ALLOW_METHODS ...]]
[--allow-headers ALLOW_HEADERS [ALLOW_HEADERS ...]] [--qos-config-path QOS_CONFIG_PATH]
[--backend {pytorch,turbomind}]
[--log-level {CRITICAL,FATAL,ERROR,WARN,WARNING,INFO,DEBUG,NOTSET}]
[--api-keys [API_KEYS ...]] [--ssl] [--model-name MODEL_NAME]
[--chat-template CHAT_TEMPLATE] [--revision REVISION] [--download-dir DOWNLOAD_DIR]
[--adapters [ADAPTERS ...]] [--tp TP] [--session-len SESSION_LEN]
[--max-batch-size MAX_BATCH_SIZE] [--cache-max-entry-count CACHE_MAX_ENTRY_COUNT]
[--cache-block-seq-len CACHE_BLOCK_SEQ_LEN] [--enable-prefix-caching]
[--model-format {hf,llama,awq,gptq}] [--quant-policy {0,4,8}]
[--rope-scaling-factor ROPE_SCALING_FACTOR] [--num-tokens-per-iter NUM_TOKENS_PER_ITER]
[--max-prefill-iters MAX_PREFILL_ITERS] [--vision-max-batch-size VISION_MAX_BATCH_SIZE]
model_path
- model_path
- –server-name SERVER_NAME: 服务的主机IP地址。默认: 0.0.0.0。
- –server-port SERVER_PORT: 服务端口。默认: 23333。
- –allow-origins ALLOW_ORIGINS: 允许CORS的来源列表。默认: [‘*’]。
- –allow-credentials: 是否允许CORS的凭证。默认: False。
- –allow-methods ALLOW_METHODS: 允许的HTTP方法列表。默认: [‘*’]。
- –allow-headers ALLOW_HEADERS: 允许的HTTP头部列表。默认: [‘*’]。
- –backend {pytorch,turbomind}: 设置推理后端。默认: turbomind。
- –log-level {LEVELS}: 设置日志级别。默认: ERROR。
- –api-keys [API_KEYS]: 可选的API密钥列表。
- –ssl: 启用SSL。需要操作系统环境变量’SSL_KEYFILE’和’SSL_CERTFILE’。
- –model-name MODEL_NAME: 模型的服务名称。可以通过RESTful API /v1/models访问。如果未指定,将采用model_path。
- –chat-template CHAT_TEMPLATE: 当它为字符串时,表示内置的对话模板名称。当它为JSON文件路径时,表示自定义对话模板。
- –revision REVISION: 使用的特定模型版本。可以是分支名、标签名或提交ID。
- –download-dir DOWNLOAD_DIR: 下载和加载权重的目录,默认为huggingface的默认缓存目录。
和 TurboMind 引擎相关的参数
- –tp TP: 在张量并行中使用的GPU数量
- –session-len SESSION_LEN: 序列的最大会话长度
- –max-batch-size MAX_BATCH_SIZE: 最大批量大小。默认:128
- –cache-max-entry-count CACHE_MAX_ENTRY_COUNT: KV 缓存占用的空闲GPU内存的百分比,不包括权重。默认:0.8
- –cache-block-seq-len CACHE_BLOCK_SEQ_LEN: KV 缓存块容纳的token数量。对于Turbomind引擎,如果GPU compute_capability >= 8.0,应该是32的倍数,否则应该是64的倍数。默认:64。
- –enable-prefix-caching: 是否启用前缀匹配KV缓存。默认:False。
- –model-format {hf,llama,awq,gptq}: 输入模型的格式。hf 表示 hf_llama,llama 表示 meta_llama,awq 表示 awq 量化模型,gptq 表示 gptq 量化模型
- –quant-policy {0,4,8}: 是否量化kv。0: 不量化;4: 4位kv;8: 8位kv。默认:0
- –rope-scaling-factor ROPE_SCALING_FACTOR: Rope缩放因子。默认:0.0
- –num-tokens-per-iter NUM_TOKENS_PER_ITER: 前向传递中处理的令牌数量。默认:0
- –max-prefill-iters MAX_PREFILL_ITERS: prefill阶段的最大前向传递数量。默认:1
和 PyTorch 引擎相关的参数
- –adapters [ADAPTERS …]: 用于设置 lora 模型的路径。可以输入多个lora的键值对格式xxx=yyy。如果只有一个适配器,可以只输入适配器的路径。默认:无。类型:字符串。
- –tp TP: 在张量并行中使用的GPU数量
- –session-len SESSION_LEN: 序列的最大会话长度
- –max-batch-size MAX_BATCH_SIZE: 最大批量大小。默认:128
- –cache-max-entry-count CACHE_MAX_ENTRY_COUNT: KV 缓存占用的空闲GPU内存的百分比,不包括权重。默认:0.8
- –cache-block-seq-len CACHE_BLOCK_SEQ_LEN: KV 缓存块容纳的token数量。如果指定了Lora适配器,此参数将被忽略。默认:64
- –enable-prefix-caching: 是否启用前缀匹配KV缓存。默认:False。
视觉模型参数:
- –vision-max-batch-size VISION_MAX_BATCH_SIZE: 视觉模型批量大小。默认:1。
2.3.2 访问服务
推荐使用 openai client package 接口访问服务
- 图像 url
from openai import OpenAI
client = OpenAI(
api_key='YOUR_API_KEY', # dummy key to pass openai checking 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=[{
'role': 'user',
'content': [{
'type': 'text',
'text': 'Describe the image please',
}, {
'type': 'image_url',
'image_url': {
'url':
'https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/tests/data/tiger.jpeg',
},
}],
}],
temperature=0.8,
top_p=0.8)
print(response)
如模型支持多图,可以在messages中 user 的content列表中追加图像
- 图像base64编码
from openai import OpenAI
client = OpenAI(
api_key='YOUR_API_KEY', # dummy key to pass openai checking key
base_url='http://0.0.0.0:23333/v1')
def encode_image(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# Path to your image
image_path = "path_to_your_image.jpg"
# Getting the base64 string
base64_image = encode_image(image_path)
model_name = client.models.list().data[0].id
response = client.chat.completions.create(
model=model_name,
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "What’s in this image?"},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}",
},
},
],
}
],
max_tokens=300,
)
- 加入额外信息
from openai import OpenAI
client = OpenAI(
api_key='YOUR_API_KEY', # dummy key to pass openai checking 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=[{
'role': 'user',
'content': [{
'type': 'text',
'text': 'Describe the image please',
}, {
'type': 'image_url',
'image_url': {
'url':
'https://raw.githubusercontent.com/open-mmlab/mmdeploy/main/tests/data/tiger.jpeg',
},
}],
}],
temperature=0.8,
top_p=0.8,
extra_body={"repetition_penalty": 1.02}
)
print(response)
2.4 模型量化
2.4.1 权重量化
- 4bit 权重量化。量化算法 AWQ,只对 VLM 中的语言模型部分做量化,视觉部分不做量化
- 支持的显卡型号:
- V100(0.6.0 支持,尚未发版)
- Turing(sm75): 20 系列,T4
- Ampere(sm80): A100
- Ampere(sm86): 30 系列,A10, A16, A30 等
- Ada Lovelace(sm89): 40 系列
- Hopper(sm90): H100, H800(尚未深度优化)
lmdeploy lite auto_awq OpenGVLab/InternVL2-8B
2.4.2 KV Cache 量化
设置 TurbomindEngineConfig 中的参数 quant_policy。详细介绍请参考前文推理引擎配置章节中关于quant_policy的说明
03
推理性能
关于 VLM 推理性能评测方法暂无统一标准,LLM有
评测 LLM pipeline 的方式如下,测试指标 RPS(Request Per Second)
python benchmark/profile_pipeline_api.py \
ShareGPT_V3_unfiltered_cleaned_split.json \
meta-llama/Meta-Llama-3-8B-Instruct \
--num-prompts 5000
在A100-SMX4-80G显卡上,测试结果如下:
--------------------------------------------------
concurrency: 256
elapsed_time: 208.390s
first token latency(s)(min, max, ave): 0.068, 3.880, 0.378
per-token latency(s) percentile(50, 75, 95, 99): [0, 0.09, 0.153, 0.207]
number of prompt tokens: 1136185
number of completion tokens: 1008966
token throughput (completion token): 4841.723 token/s
token throughput (prompt + completion token): 10293.932 token/s
RPS (request per second): 23.993 req/s
RPM (request per minute): 1439.609 req/min
--------------------------------------------------
测试 LLM serving 性能,推荐使用 vLLM 的测试脚本 https://github.com/vllm-project/vllm/blob/main/benchmarks/benchmark_serving.py。该脚本的测试指标是:TTFT、TPOT
# 首先启动服务
lmdeploy api_server meta_llama/Meta-Llama-3-8B-Instruct --max-batch-size 256
# 打开新的终端,使用 vLLM 的测试脚本 benchmark_serving.py 测试
vllm 0.4.2lmdeploy 0.4.1tensorrt-llm v0.9.0 |
---|
04
VLM推理实现方式
初始化 pipeline
prompt 预处理
在离线推理章节的示例 1,模型为 OpenGVLab/InternVL2-8B,pipe推理的请求为(‘describe this image’, image)
经过"转GPT4V请求格式"后,请求变成:
{
"role": "user",
"content": [
{
"type": "text",
"text": "describe this image"
},
{
"type": "image_data",
"image_data": {
"data": image
}
}
]
}
经过 “加image标记符”,请求变成:
{'role': 'user', 'content': '<img><IMAGE_TOKEN></img>\ndescribe this image'}
InternVL2-8B 是把 image放到text前的 |
---|
经过 “装饰对话模板 message2prompt”,请求变成:
<|im_start|>system\n你是由上海人工智能实验室联合商汤科技开发的书生多模态大模型,英文名叫InternVL, 是一个有用无害的人工智能助手。<|im_end|>\n<|im_start|>user\n<img><IMAGE_TOKEN></img>\ndescribe this image<|im_end|>\n<|im_start|>assistant\n
图像编码
self.vl_encoder 复用上游库的图像预处理和vision模型推理
推理
- TurboMind 推理
- PyTorch 推理
TODO
05
未来规划
06
附录
6.1 内置对话模板
模型 | 模型类型 | 模型结构 | 内置对话模板名称 | 说明 |
---|---|---|---|---|
InternLM-XComposer2 | MLLM | InternLMXComposer2ForCausalLM | internlm-xcomposer2 | |
InternLM-XComposer2.5 | MLLM | InternLMXComposer2ForCausalLM | internlm-xcomposer2d5 | |
Qwen-VL | MLLM | QWenLMHeadModel | qwen | |
DeepSeek-VL | MLLM | MultiModalityCausalLM | deepseek-vl | |
Phi-3-vision | MLLM | Phi3VForCausalLM | phi-3 | |
CogVLM-Chat | MLLM | CogVLMForCausalLM | cogvlm | |
CogVLM2-Chat | MLLM | CogVLMForCausalLM | cogvlm2 | |
Yi-VL | MLLM | LlavaLlamaForCausalLM | yi-vl | |
LLaVA-v1.5 | MLLM | LlavaLlamaForCausalLM | llava-vl | |
LLaVA-v1.6-vicuna | MLLM | LlavaLlamaForCausalLM | llava-v1 | |
llava-v1.6-34b | MLLM | LlavaLlamaForCausalLM | llava-chatml | |
llava-v1.6-mistral-7b | MLLM | LlavaMistralForCausalLM | mistral | |
InternVL-Chat-V1-5 | MLLM | InternLM2ForCausalLM | internvl-internlm2 | InternVL系列需要看llm_config中的architectures |
Mini-InternVL-Chat-2B-V1-5 | MLLM | InternLM2ForCausalLM | internvl-internlm2 | |
Mini-InternVL-Chat-4B-V1-5 | MLLM | Phi3ForCausalLM | internvl-phi3 | |
InternVL2 (2B、8B、26B) | MLLM | InternLM2ForCausalLM | internvl2-internlm2 | |
InternVL2 (4B) | MLLM | Phi3ForCausalLM | internvl2-phi3 | |
InternVL2 (40B) | MLLM | LlamaForCausalLM | internvl2-internlm2 | |
InternVL2-Llama3-76B | MLLM | LlamaForCausalLM | internvl2-internlm2 | |
MiniCPM-Llama3-V-2_5 | MLLM | MiniCPMV | llama3 | llm的种类在代码里面才能看出来 |
MiniGeminiLlama | MLLM | MiniGeminiLlamaForCausalLM | mini-gemini-vicuna | |
GLM-4V | MLLM | ChatGLMModel | glm4 |