RealDuxy / qwenBatchInference

qwen 14b chat int4 gptq inference accelerated for V100 16G

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

qwen inference accelerate

qwen 14b chat int4 gptq inference accelerated for V100 16G

推理加速框架说明

模型支持

master代码仅针对qwen1.5-14b-chat-int4-gptq进行的推理加速,如果更换模型或是量化,需要修改src/generate.cpp以及src/forward_gpu.cu,涉及模型参数名、激活函数选择,以及linear层的量化选择。每次模型更换或layer更新,都需要修改代码并进行数值验证。

理论支持任意transformers架构下 channels=hidden_size/num_attention_heads = 128的非moe模型。

模型加载完全本地化,具体参照serving/liteqwen_inferer.py,使用了本地路径qwen2/下python模型代码版本的tokenizer和config,以及scripts/liteqwen_py/connector.py下加载了configuration.json配置的本地路径模型.bin.safetensor文件。注意:如果你使用的是2024-03-26之后下载的模型文件,需要检查你的版本的qwen模型的config.json。新旧参数权重不同,且intermediate_size数值也不同。需要确认你的本地模型的intermediate_size与项目路径qwen2/config.json中的数值一致。

不支持按照模型名/id从互联网下载,避免模型版本变动。 可以本地python推理scripts/try_console_inf.py验证模型的pytorch推理结果。

peft版本0.4.0之后,需要注意lora模型的adapter_config.json"use_rslora": false参数,需要为false才能推理结果正确。如果为true,lora scale计算时需要增加sqrt操作,需要修改src/forward_gpu.cu里的对应方法。

推理性能对比

条件:单卡v100,16并发,input token数44,平均reply字符数230,max_sequence_length(max-model-len)=4096。无加载lora

vllm吞吐量:419字符/秒,显存占用14.8G (--gpu-memory-utilization 限制)

liteqwen吞吐量:360字符/秒,显存占用12.9G

吞吐量仍存在一定差距,需要后续优化。

样本batch分割

基于continuous batch进行的llm推理加速,flash attention使用cutlass_fmha计算prefill阶段的attention;decode attention是自主实现的算子(参考了falsh attention)。量化使用exllama的gptq linear。支持lora切换,会将请求按照时间顺序切分batch,遇到新请求的lora改变,或已经拼接到max_batch_size,或kv-cache缓存剩余空间无法容纳新请求的max_length时,会暂停请求的reload,推理当前batch。当前batch内任意样本完结后恢复新样本reload过程。

与vllm的page attention不同,liteqwen需要预估每个请求的max_length, 正确的使用方式是: 1. 尽量给每条请求足够用且尽可能小的max_length

2. 尽量让相同lora的请求在时间轴上靠近

这样才能尽量增加推理时的batch_size,避免频繁batch分割,实现更高吞吐量。 如果计算不清楚max_length,也可以使用max_new_tokens参数替代,如果两个参数都不提供,默认使用模型支持的最大长度进行推理,推理吞吐量回到batch_size=1的情况,损失较大。

启动方法

首先检查pytorch, transformers, peft是否已经安装好。 之后使用cmake build安装推理加速服务,运行run_install.sh脚本。

安装完成后修改configuration.json,检查模型路径、lora配置,以及端口号。

修改完配置,直接启动的话运行python start_server.py 也可以nohup python daemon_start_server.py &,后台守护运行启动脚本时,会自动根据nvidia-smi脚本的显存占用比例,判定服务是否正常。一般服务挂了的话,显存占用会归零,被检测到后,daemon_start_server.py会自动运行进程清理clean_proc.sh,之后重启服务。

服务启动好之后,可以运行mock_cli.py测试一下接口调用的吞吐量或生成内容。 修改开头的

LOCAL_URL = "http://127.0.0.1:8081"
PARALLEL_NUM = 16
ROUND_NUM = 5

修改并发,之后修改stream_post(...)方法内的请求体,以及最后的__main__下的调用方式。

运行python mock_cli.py可以执行模拟client端调用。

start_web.py能够启动一个简易网页对话,端口在configuration.json中配置,一般服务端不使用。

请求体格式可以参照python mock_cli.py里,也可以查看scripts/liteqwen_py/connector.pyserving/app.py以及serving/liteqwen_inferer.py。目前return_logits参数暂未实装。

请求样例

以configuration.json中使用本地8081端口为例

流式地址:http://127.0.0.1:8081/stream_chat_post

非流式地址:http://127.0.0.1:8081/chat

简单请求体

{
    "request_id":"req000003",
    "query": "你好啊,你是谁?",
    "history": [],
    "gen_kwargs": {
        "temperature": 0.9
    }
}

request_id需要每个请求不相同,否则结果写入会有冲突。如果不给则会随机分配。

query的默认role是"user",也可以指定其他role,例如{"query": "你好啊,你是谁?", "role": "observation"}

history如果不为空,样例如下:

"history" : [
    {"role": "system", "content": "你是一个乐于助人的AI助手。"},
    {"role": "user", "content": "第一轮用户输入"},
    {"role": "assistant", "content": "第一轮bot回复"}
]

list中第一项system信息如果不给,会自动按照tokenizer的jinja template填入qwen 1.5模型的默认system信息。

gen_kwargs目前支持的参数如下:

"gen_kwargs": {
    "temperature" : 0.9, // 温度,默认见configuration.json
    "return_logits": true, // 是否返回每个token的top_k token id以及logits,默认false。top_k固定32
    "max_length": 256, // 请求的输入+输出最长token数,尽量给小一些。一般和max_new_tokens二选一提供。如果都不提供则默认按照configuration中配置的最大长度,以bsz=1推理。
    "max_new_tokens": 128, // 回复最长token数,如果和max_length一起提供,则按照计算input token数后二者最短的一种进行eos截断。
    "top_p": 0.9, // 默认见configuration.json
    "prefix_token_ids": [3837] // int list,length=N。手动提供前N个回复token_id,用于引导生成。默认空
}

prefix_token_ids默认空list,正常生成,如果提问 "你好啊,你是谁?",则bot回复以 "你好!我是通义千问,..." 为开头。

如果提供 [3837] (对应token "我"),且降低temperature,则bot回复以 "我是通义千问,..." 为开头。

可以结合{"max_new_tokens": 2, "prefix_token_ids": [xx, xx, xx], "temperature":0.01}的参数组合,让大模型做ABCD选择题,且稳定在回复开头解析答案。

实现设计

模型大致架构如下: alt dp=2,pp=2

顶层是python web框架封装,调用c++ lib,lib内包含存储输入与输出结果的池,各data_id线程负责data parallel推理。KV缓存按照pipeline parallel的stage分别存储在不同GPU,模型参数也按照layer切分策略平摊到pipeline_parallel_size张gpu上。目前没有做pipeline parallel的interleaving推理,这样意味着kv-cache与activation都会显著增加。不适合v100 16G这种算力还行但显存不足的卡。

tensor parallel推理目前也不支持,量化+tp需要大量修改,以后有需求时才考虑。

所有prompt相关操作,都在python端执行,c++端只接受input_ids以及其他input参数,返回也仅限token_ids,解码以及封装都在python端。

模型参数加载完成后会执行一次warmup推理,进行预分配,以及验证是否有足够显存推理当前max_length。warmup默认使用lora配置的第一个模型,所以尽量把r=lora rank最大的模型放到第一个lora

每个dp线程从池中加载新请求,在InputPreparer类中更新forward所需的入参。根据batch分割策略,确定当前是插入新prefill还是继续之前的decode。prefill与decode使用的预分配显存不同,所以比起bsz=1的推理,消耗的activation显存会增加。此外,lora在batch推理时也需要更多activation显存,导致了支持的max_length下降。

当lora满足切换条件时,所有之前的decode请求都已经完成,新的prefill即将被执行,这时会执行BigBuffer显存的清理。因为lora rank可能改变,导致预分配方案发生改变。如果不清理BigBuffer,在预分配大小不匹配,无法命中activation cache时,会执行新cudaMalloc,消耗更多显存。

显存预分配管理是推理加速的核心机制之一,需要尽量保证相同用途的activation,大小尽量一致且不发生踩踏。例如每一层的attention score都按照最大长度申请,就可以反复命中同一个buffer,实现复用,节省显存malloc和free的时间。

Attention为了节约显存,会在提交样本请求时就在KV-cache中按照该样本预估的max_length或max_new_tokens声明显存。之后所有新增的decode kv,都在这块预分配的kv-cache后增量写入。由于continuous batch的activation是样本间无gap的contiguous数据,任何涉及到动态长度改变的步骤(rotary和attention)都需要对算子内的位置映射进行修改。于是出于算子融合和节约activation显存的考虑,自己实现了这些算子。最终continuous batch下的kv cache方案如下图:

alt prefill & decode kv

样例中kv-cache最大长度4096,可以推一条max_length=4096的样本,也可以推256+256+512=1024的三条样本或更多。当前3条样本的prefill长度总共80+40+160=280远不到4096,所以连续拼接后的kernel启动数量一般是最少的,没有kernel启动后发现自己在padding位置上,啥都不干就退出kernel。之后decode阶段时,增量key和value会被写入kv-cache内的对应位置,之后在每个样本的kv-cache起点开始计算flash attention,直到batch内的max(example_lengths)=161(160prefill+1decode)

测试模块

CmakeLists.txt的内容替换为CMakeLists_for_test_bkup.txt里的测试cmake脚本

修改src/test_main.cpp的内容,之后运行run_test.sh

即可测试各模块是否正常工作,也可以用于数值debug调试。

flash attention相关代码位于src/cutlass_fmha下,对原本的pytorch调用cutlass的方法做了一些修改。未修改版本的代码在src/cutlass_fmha/unused_bkups下。

exllama相关代码位于src/exllama

About

qwen 14b chat int4 gptq inference accelerated for V100 16G


Languages

Language:C++ 71.8%Language:Cuda 11.7%Language:C 5.2%Language:Objective-C++ 4.0%Language:Makefile 3.9%Language:Assembly 1.2%Language:Python 1.0%Language:CMake 0.5%Language:GLSL 0.4%Language:Starlark 0.2%Language:Objective-C 0.1%Language:Shell 0.1%Language:TypeScript 0.0%