基于vLLM加速大模型推理服务

3/22/2024 vLLMLLaMA-Factory推理性能优化

# 1. 推理服务性能优化

# 1.1 推理服务的优化方向

随着LLM的不断发展和应用,如何提高模型推理性能成为了一个重要的研究方向。推理性能受到显存带宽而不是计算能力的限制,服务吞吐量受到推理batch_size的限制。针对推理性能和服务吞吐量的优化,可以从三个角度开展:推理引擎层、服务层优化以及量化技术。

  • 推理引擎层主要是针对计算性能进行优化,例如KernelFusion、KV-Cache、FlashAttention、TP+PP、PagedAttention等技术;

  • 服务层优化主要关注吞吐量的提升,包括Dynamic-Batching、Continous-Batching等技术。

    此外,还有针对特定场景的优化技术,例如流式、交互式及持续生成,以及长序列推理等。

  • 模型量化方面主要涉及Weight-Only、int8、int4以及KV-Cache量化等。

对于以上这些技术基础理论的介绍,详见这篇文章:大模型推理-2-推理引擎和服务性能优化 (opens new window)

# 1.2 基于vLLM加速大模型推理

# 1.2.1 vLLM项目简介

vLLM是一个大型语言模型推理加速工具,它通过优化内存管理、连续批处理、CUDA核心优化和分布式推理支持等技术手段,显著提高了大型语言模型的推理速度和效率。在官方实验中,vLLM 的吞吐量比 HF 高出 24 倍,比 TGI 高出 3.5 倍。

注:vLLM官方目前还不支持AutoGPTQ的量化模型,也不支持ChatGLM-1模型,也不支持LoRA微调后的adapters。

vLLM吞吐量极高

# 1.2.2 vLLM基本原理

vLLM是LLM推理和服务引擎,支持多种模型,具有极高的性能,PagedAttention是vLLM背后的核心技术。

作者发现大模型推理的性能瓶颈主要来自于内存。一是自回归过程中缓存的K和V张量非常大,在LLaMA-13B中,单个序列输入进来需要占用1.7GB内存。二是内存占用是动态的,取决于输入序列的长度。由于碎片化和过度预留,现有的系统浪费了60%-80%的内存。

PagedAttention灵感来自于操作系统中虚拟内存和分页的经典思想,它可以允许在非连续空间立存储连续的KV张量。具体来说,PagedAttention把每个序列的KV缓存进行了分块,每个块包含固定长度的token,而在计算attention时可以高效地找到并获取那些块。

KV缓存分片的过程

每个固定长度的块可以看成虚拟内存中的页,token可以看成字节,序列可以看成进程。那么通过一个块表就可以将连续的逻辑块映射到非连续的物理块,而物理块可以根据新生成的token按需分配。

通过PagedAttention生成序列的过程

所以序列在分块之后,只有最后一个块可能会浪费内存(实际中浪费的内存低于4%)。高效利用内存的好处很明显:系统可以在一个batch中同时输入更多的序列,提升GPU的利用率,显著地提升吞吐量。

PagedAttention的另外一个好处是高效内存共享。例如,在并行采样的时候,一个prompt需要生成多个输出序列。这种情况下,对于这个prompt的计算和内存可以在输出序列之间共享。

并行采样的例子

通过块表可以自然地实现内存共享。类似进程之间共享物理页,在PagedAttention中的不同序列通过将逻辑块映射到一样的物理块上可以实现共享块。为了确保安全共享,PagedAttention跟踪物理块的引用计数,并实现了Copy-on-Write机制。内存共享减少了55%内存使用量,大大降低了采样算法的内存开销,同时提升了高达2.2倍的吞吐量。

同一个输入采样生成多个输出的例子

# 1.2.3 vLLM基本用法

vLLM只是用来对prompts进行批处理,但它并不能独立使用,需要搭配 Dynamic-Batching 去使用,这里可以自己去实现,也可以使用开源实现,比如:triton-inference-server、mosecorg/mosec、ShannonAI/service-streamer等。

vLLM基本用法

# 2. 需求分析及测试环境

# 2.1 需求与技术选型

# 2.1.1 需求场景

[1] 对并行处理做了优化,能够在较快响应速度的前提下支持较多的并发量。

[2] 支持目前主流的大模型(如ChatGLM、Baichuan、Qwen、LLaMA),支持流式输出。

[3] 在显存溢出时让它使用内存,这样只是推理变慢而不是爆显存导致挂掉。

# 2.1.2 技术选型

技术选型:调研了很多开源项目,发现还是LLaMA-Factory最合适。它是一个主要用于大模型微调的项目,里面自带推理服务,在2024.3.07的版本在推理服务里支持了vLLM技术,可用来部署高效的推理服务。

关于使用它进行大模型微调这里就不赘述了,详见我的另一篇博客:ChatGLM大模型的高效微调 (opens new window)

LLaMA-Factory支持vLLM

关于显存溢出时让它用内存,可参考:ZeRO-Inference: Democratizing massive model inference (opens new window) 文章。

ZeRO-Inference

vLLM的issues里有个人在自己的Fork仓库里进行了实现,还没合并到主分支,详见:https://github.com/vllm-project/vllm/issues/3563 (opens new window)

Offload_Model_Weights_to_CPU

另外,调研时尝试过的其他开源项目如下所示,仅供参考:

# 2.2 服务器测试环境

实验环境:实体GPU服务器,NVIDIA RTX 4090 / 24GB,CentOS 7.9,Anaconda3-2019.03,CUDA 12.4

如果没有GPU服务器,可以租用AutoDL等平台的。服务器的租用及基础环节的安装这里就不赘述了,详见我的另一篇博客:常用深度学习平台的使用指南 (opens new window)

# 3. 部署高效推理服务

# 3.1 使用LLaMA-Factory部署推理服务

# 3.1.1 拉取项目并安装依赖

拉取项目并安装依赖,并准备模型文件(这里以 chatglm3-6b (opens new window) 为例)

$ git clone https://github.com/hiyouga/LLaMA-Factory.git
$ conda create -n llama_factory python=3.10
$ conda activate llama_factory
$ cd LLaMA-Factory
$ pip3 install -r requirements.txt
$ pip3 install vllm==0.3.3
1
2
3
4
5
6

# 3.1.2 启动支持vLLM的推理服务

启动支持vLLM的推理服务,官方支持多种方式,这里仅以 API Demo 为例:

$ CUDA_VISIBLE_DEVICES=0 python3 src/api_demo.py \
    --model_name_or_path /root/llm_models/THUDM/chatglm3-6b/ \
    --template default \
    --infer_backend vllm
1
2
3
4

LLaMA-Factory推理服务

# 3.2 对部署的推理服务进行测试

# 3.2.1 查看接口文档

使用Chrome浏览器打开此地址:http://<your_server_ip>:8000/docs,可以访问到接口文档。

LLaMA-Factory推理服务的接口文档

先请求一下 /v1/models 接口,发现模型名叫做 gpt-3.5-turbo。

LLaMA-Factory推理服务查看模型列表

然后在 /v1/chat/completions 接口里请求推理服务,注意把 model 和 content 改了。

LLaMA-Factory请求推理服务

# 3.2.2 测试流式输出

在接口文档那里,可以看到 curl 命令,把 stream 改成 true 即可变成流式输出。

$ curl -X 'POST' \
  'http://<your_server_ip>:8000/v1/chat/completions' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "model": "gpt-3.5-turbo",
  "messages": [
    {
      "role": "user",
      "content": "解释一下量子计算"
    }
  ],
  "tools": [],
  "do_sample": true,
  "temperature": 0,
  "top_p": 0,
  "n": 1,
  "max_tokens": 0,
  "stream": true
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

LLaMA-Factory流式输出

# 3.2.3 进行压力测试

这里不使用专业的压力测试工具了,直接写个 Python 脚本进行压力测试。

# -*- coding: utf-8 -*-

import threading
import requests
import json


def send_post_request(url, payload):
    """
    向指定的URL发送POST请求。
    """
    headers = {
        "accept": "application/json",
        "Content-Type": "application/json"
    }

    updated_payload = {
        "model": "gpt-3.5-turbo",
        "messages": [
            {
                "role": "user",
                "content": payload["prompt"]
            }
        ],
        "tools": [
            {
                "type": "function",
                "function": {
                    "name": "string",
                    "description": "string",
                    "parameters": {}
                }
            }
        ],
        "temperature": 0,
        "top_p": 0,
        "n": 1,
        "max_tokens": 0,
        "stream": False
    }

    response = requests.post(url, headers=headers, data=json.dumps(updated_payload))
    try:
        response_json = response.json()
        print(response_json)
    except ValueError:
        print("Response could not be decoded as JSON:", response.text)


def threaded_requests(url, payload, num_threads, total_requests):
    """
    创建并启动多线程以达到指定的请求总量。
    """
    rounds = (total_requests + num_threads - 1) // num_threads  # 计算需要的轮数
    for _ in range(rounds):
        threads = []
        for _ in range(num_threads):
            if total_requests <= 0:
                break  # 如果已经达到请求总量,停止创建新线程
            thread = threading.Thread(target=send_post_request, args=(url, payload))
            thread.start()
            threads.append(thread)
            total_requests -= 1

        for thread in threads:
            thread.join()


if __name__ == '__main__':
    api_url = 'http://your_server_ip:8000/v1/chat/completions'
    payload = {
        "prompt": "解释一下量子计算"
    }
    num_threads = 50       # 线程数
    total_requests = 100   # 总请求数

    threaded_requests(api_url, payload, num_threads, total_requests)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

可以看到,哪怕只是在一张单卡4090显卡上,大模型的响应速度也是非常迅速的。

对LLaMA-Factory部署的服务进行压力测试

# 4. 参考资料

[1] vLLM是一个大型语言模型推理加速工具 from Github (opens new window)

[2] vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention from vLLM官方文档 (opens new window)

[3] vLLM:给大模型提提速,支持高并发吞吐量提高24倍,同时推理速度最少提高 8 倍 from CSDN (opens new window)

[4] vLLM Feature:Offload Model Weights to CPU from Github issues (opens new window)

[5] 如何让vLLM适配一个新模型 from 知乎 (opens new window)

[6] How continuous batching enables 23x throughput in LLM inference while reducing p50 latency from anyscale (opens new window)

[7] 如何解决LLM大语言模型的并发问题 from 知乎 (opens new window)

[8] 大模型的N种高效部署方法:以LLama2为例 from 美熙科技说 (opens new window)

[9] LightLLM:纯Python超轻量高性能LLM推理框架 from AI文摘 (opens new window)

[10] 大模型推理百倍加速之KV cache篇 from 知乎 (opens new window)

[11] 大模型推理-2-推理引擎和服务性能优化 from 知乎 (opens new window)

[12] 在 Triton 中部署 vLLM 模型 from Github (opens new window)

[13] VLLM推理加速与部署 from Github (opens new window)

[14] Triton Inference Server教程2 from CSDN (opens new window)

[15] 使用本地模型替代 OpenAI:多模型并发推理框架 from 知乎 (opens new window)

[16] 怎么在我们项目中使用vLLM推理 from Github issues (opens new window)

[17] LLaMA-Factory统一 100 多个 LLM 的高效微调 from Github (opens new window)

[18] ZeRO-Inference: Democratizing massive model inference from Deepspeed官方文档 (opens new window)

[19] 图解大模型计算加速系列:vLLM源码解析1,整体架构 from AINLP (opens new window)

[20] 量化模型能否用vllm部署 from Github issues (opens new window)

[21] Would it be possible to support LoRA fine-tuned models from Github issues (opens new window)

[22] Support LoRA adapter from Github issues (opens new window)

Last Updated: 4/22/2024, 4:55:46 PM