大模型私有化部署实践(二):vLLM 分布式推理与性能优化

1,297 阅读7分钟

专栏

vllm简介

  • vLLM 是一个高性能、易扩展的大模型推理框架,专为生产环境中的大规模语言模型部署而设计。它通过创新的 PagedAttention 内存管理技术,显著提升了 GPU 的显存利用率,同时支持分布式推理,能够高效利用多机多卡资源。无论是低延迟、高吞吐的在线服务,还是资源受限的边缘部署场景,vLLM 都能提供卓越的性能表现。其简洁的 API 设计和灵活的部署方式,使得开发者能够快速集成并优化大模型推理任务,是私有化部署中不可或缺的利器。
  • vllm官方中文文档

vllm实践

前置安装,如果conda和nvidia驱动都安装好了可跳过这步

  • 查看没有激活conda环境下python版本
python3 --version
我的是Python 3.10.12
安装nvidia驱动
  • 这里注意如果需要deepspeed分布式环境后面还需要升级版本,当前配置可用于vllm分布式部署
  • 先禁用nouveau
echo -e "blacklist nouveau\noptions nouveau modeset=0" | sudo tee /etc/modprobe.d/blacklist-nouveau.conf

sudo update-initramfs -u

sudo reboot
  • 无输出就说明成功了
lsmod | grep nouveau
  • 执行如下命令
wget https://cn.download.nvidia.com/XFree86/Linux-x86_64/535.113.01/NVIDIA-Linux-x86_64-535.113.01.run -O nvidia.run

./nvidia.run

reboot
  • nvidia-smi显示如下就成功了

显卡信息展示.png

  • 安装工具包, 然后执行看版本的命令能输出就成功了
apt install nvidia-cuda-toolkit

nvcc --version
安装conda
  • 安装依赖包
apt install libgl1-mesa-glx libegl1-mesa libxrandr2 libxrandr2 libxss1 libxcursor1 libxcomposite1 libasound2 libxi6 libxtst6
  • 下载anaconda, bash之后然后一路回车
wget https://repo.anaconda.com/archive/Anaconda3-2022.10-Linux-x86_64.sh

chmod +x Anaconda3-2022.10-Linux-x86_64.sh

bash Anaconda3-2022.10-Linux-x86_64.sh
  • 加环境变量
export PATH=~/anaconda3/bin:$PATH

source ~/anaconda3/bin/activate

source ~/.bashrc
  • 测试下
conda list

vllm安装

  • 快速安装vllm
conda create -n vllm python=3.10 -y

conda activate vllm

pip install vllm
  • 或者使用源码安装
git clone https://github.com/vllm/vllm.git

cd vllm 

pip install -e .

vllm实践

下载模型
export HF_ENDPOINT=https://hf-mirror.com

pip install modelscope
  • 使用modelscope下载并缓存到/usr/local,模型地址可以改成你想要下载的
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os

from modelscope.hub.api import HubApi
api = HubApi()
# 有的地方需要,key在 modelscope.cn/models 右上角个人那边
# api.login('xxx你的账号对应的key')

model_dir = snapshot_download('Qwen/Qwen2.5-72B-Instruct-AWQ', cache_dir='/usr/local',revision='master')
print(model_dir)
使用vllm推理
  • 这边使用的是vllm的openai进行推理, 用的是Qwen2.5-72B-Instruct-AWQ,两张A10即可跑起来,如果跑别的模型可以改改, 注意这里显存有限,又要跑那么大模型,我就把模型tokenizer_config.json的max-model-len从几万改到6666了,后台启动, --model改成你自己存放大模型的地址,max-model-len看个人需求,我的conda vllm个人环境是设置的vllm
conda activate vllm

export HF_ENDPOINT=https://hf-mirror.com

nohup python -m vllm.entrypoints.openai.api_server \
    --model /root/.cache/huggingface/hub/models/Qwen/Qwen2-72B-Instruct-AWQ/snapshots/26975fbe12cda45e38c0b99da62f1a7deab1bf25 \
    --tensor-parallel-size 2 \
    --host 0.0.0.0 \
    --port 5000 \
    --served-model-name Qwen/Qwen-72B-AWQ \
    --cpu-offload-gb 5.0 \
    --quantization awq \
    --max-model-len 6666 \
    --gpu-memory-utilization 0.9 \
    & >> out1.log 2>&1



应用端读取vllm后台启动

  • 因为有些python版本不太支持直接使用openai, 这边使用了更通用的http请求的方式, 应用层代码如下, 注意xxxx改成你自己客户端的ip
import datetime
import json
import requests


class Qwen:

    def __init__(self, msg, model=''):
        self.msg = msg
        self.model = model

    def get_tags_map_res(self):
        if self.model == '':
            self.model = 'Qwen/Qwen-72B-AWQ'
        # xxxx换成你的ip    
        url = f"http://xxxx:5000/v1/chat/completions"
        system = """你是tomcat"""
        content = '''
- Data:
{msg}
            '''
        return self.send_msg_and_get_resp(content, system, url)

    def send_msg_and_get_resp(self, content, system, url):
        headers = {
            'Content-Type': 'application/json'
        }
        messages = [{'role': 'system', 'content': system},
                    {"role": "user", "content": content.replace('{msg}', self.msg)}]
        data = {
            "model": self.model,
            "messages": messages
        }
        response = requests.post(url, headers=headers,
                                 data=json.dumps(data),
                                 timeout=(30.0, 360.0))
        return json.loads(response.text)['choices'][0]['message']['content']

    def parse_mtr_data(self):
        pass


if __name__ == '__main__':
    t1 = datetime.datetime.now()
    print("now————", t1)
    content = """你好呀tomcat"""
    model = Qwen(content)
    res = model.get_tags_map_res()

    print("Vllm's response:")
    print(res)
    print('____________________________')
    end = datetime.datetime.now()
    print('res ,cost:+++++++++++++++++++++++++++++++', str(end - t1))

  • 响应如下

vllm初探.png

分布式vllm部署

  • 目前参考官方文档多机多卡使用docker部署
  • docker安装
apt  install docker.io
  • 通过docker拉取最新vllm镜像
docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/vllm/vllm-openai:latest 
  • docker并不能直接看见宿主机的gpu所以要执行下面命令, 作者的第二台机器是删掉了之前有问题的conda activate xxx环境重新install才成功的
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list

sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit
sudo apt-get install -y nvidia-docker2

systemctl restart docker

多节点部署.png

  • 我这边下载下来要执行下不然会报格式错误,如果你们没报格式错误就不会执行下面格式化命令
dos2unix run_cluster.sh
  • 然后vim run_cluster.sh里面的docker run加下-p 5000:5000
  • 然后两台机器分别执行下面命令,master_ip_xxx请换成你的主节点, -v是挂载节点你可以换成你自己的,ens3是我的网卡地址。注意两台机器的大模型目录都要保持一致
# 启动主节点命令
bash run_cluster.sh \
    swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/vllm/vllm-openai \
    master_ip_xxx \
    --head /root/.cache/huggingface/hub \
    -v /root/.cache/huggingface/hub/:/model/ \
    -e GLOO_SOCKET_IFNAME=ens3 \
    -e NCCL_SOCKET_IFNAME=ens3
 
# 启动工作节点命令
bash run_cluster.sh \
    swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/vllm/vllm-openai \
    master_ip_xxx \
    --worker /root/.cache/huggingface/hub \
    -v /root/.cache/huggingface/hub/:/model/ \
    -e GLOO_SOCKET_IFNAME=ens3 \
    -e NCCL_SOCKET_IFNAME=ens3
  • 通过下面命令查看ray集群的状态, xxx是docker ps -a对应的id
docker exec -it xxx /bin/sh

ray statys
  • 能看到多个节点说明成功了
  • 然后我这边在备集群执行下面启动vllm命令, 这样就能在两台机器上看到显存被占用
python3 -m vllm.entrypoints.openai.api_server \
    --model /model/models/Qwen/Qwen2-72B-Instruct-AWQ/snapshots/26975fbe12cda45e38c0b99da62f1a7deab1bf25 \
    --pipeline-parallel-size 2 \
    --tensor-parallel-size 1 \
    --host 0.0.0.0 \
    --port 5000 \
    --served-model-name Qwen/Qwen-72B-AWQ \
    --enforce-eager \
    --cpu-offload-gb 3.0 \
    --quantization awq \
    --max-model-len 3000 \
    --gpu-memory-utilization 1.0

vllm以及大模型采样策略说明

  • vllm openai启动后,客户端可以传递多个采样参数,下面解释下各个参数含义
1. n 参数
  • 定义n 参数用于指定在一次生成请求中,模型应返回多少个完全独立的生成序列。这意味着在同一组输入下,模型会生成 n 个不同的结果。每个生成序列可以独立使用不同的采样策略(如贪心搜索、随机采样、Top-k、Top-p 等)。

  • 优点

    • 多样性:提供多种生成结果,用户可以从多个候选中选择最合适的。
    • 鲁棒性:增强生成结果的可靠性,用户可以根据需求筛选最佳输出。
  • 缺点

    • 计算开销:由于需要生成多个完整的候选序列,计算资源消耗较大。
  • 流程:同时启动多个生成过程,每个过程生成一个独立的序列。

  • 实现:可以通过多线程、多进程或在 GPU 上并行处理多个生成任务。每个任务可以应用不同的随机种子或采样策略。

  • 应用场景:在对话系统中,提供多个回答选项,供用户选择或系统进一步优化。

2. Top-k Sampling 机制
  • 定义:在每个生成步骤中,从模型的概率分布中选择前 k 个最有可能的 token。这些 token 是当前时间步中概率最高的候选。然后,从这 k 个 token 中随机采样一个作为下一个生成的 token。

  • 优点

    • 限制采样范围:减少选择低概率或不合理 token 的风险。
    • 多样性:在较高概率范围内增加生成结果的多样性。
  • 缺点

    • k 值选择:选择合适的 k 值至关重要。k 值过小会限制多样性,而 k 值过大可能导致选择不合理的 token。
3. Temperature 参数
  • 定义temperature 参数用于调整模型输出的概率分布。当 temperature = 1.0 时,模型输出的概率分布保持不变,这是默认设置。此时,Softmax 函数将原始的 logits 直接转换为概率分布,反映模型对下一个 token 的信心水平。

  • 作用机制

    • Softmax 函数:在自注意力层和 MLP 之后,Softmax 函数将模型输出的 logits 转换为概率分布。temperature 的作用可以简单理解为对 logits 进行缩放。

    • 温度值影响

      • 当 temperature < 1.0 时,logits 的数值差异会被放大,导致高概率 token 的概率更高,低概率 token 的概率更低。在 Top-k 或 Top-p 采样中,差异小的 token 会随机选择,而差异大的 token 会倾向于贪心选择。
      • 当 temperature = 1.0 时,logits 的数值保持不变,概率分布直接从模型的 logits 中计算得出。
4. Beam Search
  • 定义:Beam Search 是一种系统地探索解空间的搜索算法,用于在序列生成中找到高概率的输出。算法在每个时间步维持多个候选序列(称为 beam),并扩展这些序列至下一个时间步。每个时间步,算法保留概率最高的 b 个序列(b 是 beam width),并丢弃其余的。

  • 优点

    • 全局最优解:相比贪心搜索,Beam Search 能够探索更多路径,更有可能找到全局最优解。
    • 高质量输出:通常用于需要高质量和连贯性输出的任务,如机器翻译。
  • 缺点

    • 计算复杂度:由于需要在每个时间步追踪多个候选序列,计算开销较大。
    • 多样性不足:倾向于选择高概率路径,可能导致输出结果相似。
  • 应用场景:适合需要生成高质量、连贯输出的任务,如机器翻译、摘要生成等。相比 Top-k 和 Top-p,Beam Search 的输出更加连贯。

5. 贪心搜索(Top-k 设置为 1)
  • 定义:在每个时间步,选择概率最高的 token 作为输出。

  • 优点

    • 简单高效:计算效率高,实现简单。
  • 缺点

    • 局部最优:可能错过全局最优解,因为它不考虑未来的解码路径。

参考文章