2.人工智能实战:大模型接口并发低、GPU利用率上不去?基于 vLLM 重构推理服务的完整工程方案

人工智能实战:大模型接口并发低、GPU利用率上不去?基于 vLLM 重构推理服务的完整工程方案


一、问题场景:模型能跑,但一压测就露馅

最近在做一个内部知识库问答系统,模型本地推理已经跑通,FastAPI 接口也能正常返回结果。

一开始我以为事情差不多结束了,结果一做压测,问题马上暴露:

text 复制代码
1. 单用户访问正常
2. 5个用户并发开始明显排队
3. 10个用户并发时接口超时
4. GPU显存占了很多,但GPU利用率只有20%~35%
5. 请求耗时波动非常大,有时2秒,有时十几秒

当时服务架构大概是这样:

text 复制代码
用户请求
  ↓
FastAPI
  ↓
transformers generate
  ↓
GPU推理
  ↓
返回结果

代码看起来没有明显问题,模型也只在启动时加载了一次:

python 复制代码
model = AutoModelForCausalLM.from_pretrained(...)
tokenizer = AutoTokenizer.from_pretrained(...)

但实际表现非常差。

这就是大模型系统部署里一个很典型的坑:

text 复制代码
模型能推理,不代表推理服务能承载并发。

二、先复现问题:transformers 原生推理接口

为了方便复现,我们先写一个最常见的 FastAPI + transformers 推理服务。

1. 安装依赖

bash 复制代码
pip install fastapi uvicorn torch transformers accelerate

2. 项目结构

text 复制代码
llm-transformers-demo/
├── app.py
└── requirements.txt

3. 编写 app.py

python 复制代码
import time
import torch
from fastapi import FastAPI
from pydantic import BaseModel, Field
from transformers import AutoTokenizer, AutoModelForCausalLM

app = FastAPI(title="Transformers LLM Service")

MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"

tokenizer = None
model = None


class ChatRequest(BaseModel):
    prompt: str = Field(..., min_length=1, max_length=2000)
    max_new_tokens: int = Field(default=128, ge=1, le=512)


@app.on_event("startup")
def load_model():
    global tokenizer, model

    print("开始加载模型...")

    tokenizer = AutoTokenizer.from_pretrained(
        MODEL_NAME,
        trust_remote_code=True
    )

    model = AutoModelForCausalLM.from_pretrained(
        MODEL_NAME,
        torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
        device_map="auto",
        trust_remote_code=True
    )

    model.eval()

    print("模型加载完成")


@app.post("/chat")
def chat(req: ChatRequest):
    start_time = time.time()

    inputs = tokenizer(
        req.prompt,
        return_tensors="pt",
        truncation=True,
        max_length=2048
    ).to(model.device)

    with torch.inference_mode():
        outputs = model.generate(
            **inputs,
            max_new_tokens=req.max_new_tokens,
            do_sample=True,
            temperature=0.7,
            top_p=0.9
        )

    answer = tokenizer.decode(outputs[0], skip_special_tokens=True)

    cost = round(time.time() - start_time, 3)

    return {
        "answer": answer,
        "cost_seconds": cost
    }


@app.get("/health")
def health():
    return {"status": "ok"}

4. 启动服务

bash 复制代码
uvicorn app:app --host 0.0.0.0 --port 8000

5. 单次请求测试

bash 复制代码
curl -X POST "http://127.0.0.1:8000/chat" \
-H "Content-Type: application/json" \
-d '{
  "prompt": "请用通俗语言解释一下Transformer的自注意力机制",
  "max_new_tokens": 128
}'

单请求一般能正常返回。

但问题不是单请求,而是并发。


三、压测复现:为什么单测正常,并发就崩?

1. 安装压测工具

bash 复制代码
pip install locust

2. 编写 locustfile.py

python 复制代码
from locust import HttpUser, task, between


class LLMUser(HttpUser):
    wait_time = between(1, 2)

    @task
    def chat(self):
        self.client.post(
            "/chat",
            json={
                "prompt": "请解释一下深度学习模型推理为什么需要GPU加速",
                "max_new_tokens": 128
            },
            timeout=60
        )

3. 启动压测

bash 复制代码
locust -f locustfile.py --host=http://127.0.0.1:8000

在浏览器打开:

text 复制代码
http://127.0.0.1:8089

设置:

text 复制代码
Number of users: 20
Spawn rate: 2

四、观察到的问题

压测后你会看到类似现象:

text 复制代码
1. 平均响应时间越来越高
2. P95 延迟明显上升
3. 请求出现排队
4. GPU 利用率并没有打满
5. 显存占用不低,但吞吐上不去

可以用下面命令观察 GPU:

bash 复制代码
nvidia-smi -l 1

你可能会看到:

text 复制代码
GPU Memory Usage: 很高
GPU Utilization: 20%~40%

这说明:

text 复制代码
显存占了,但算力没吃满。

五、原因分析:瓶颈不在模型,而在推理调度

很多人看到接口慢,第一反应是:

text 复制代码
是不是模型太大?
是不是GPU太差?
是不是代码没优化?

这些都有可能,但在这个场景里,真正核心的问题是:

text 复制代码
transformers 的 generate 更偏单请求推理,不是高并发推理服务框架。

六、transformers 推理的核心问题

1. 请求之间缺少统一调度

多个请求进来时,服务层看到的是:

text 复制代码
请求A → generate
请求B → generate
请求C → generate

它不会自动把多个请求组织成高效 batch。


2. GPU 没有被充分喂满

GPU 擅长的是并行矩阵计算。

但如果每次只处理一个请求,实际执行类似这样:

text 复制代码
小矩阵计算
小矩阵计算
小矩阵计算

这会导致大量 GPU 计算资源空闲。


3. KV Cache 管理效率不高

大模型推理不是一次性算完,而是逐 token 生成。

每生成一个 token,都依赖前面 token 的 Key / Value 缓存。

也就是说:

text 复制代码
输入越长,KV Cache 越大
输出越长,KV Cache 越大
并发越高,KV Cache 越难管理

如果没有高效的 KV Cache 管理机制,很容易出现:

text 复制代码
显存碎片
吞吐下降
延迟抖动

七、解决思路:用 vLLM 替代 transformers 做推理服务

vLLM 解决的不是"模型能不能跑",而是:

text 复制代码
大模型如何高吞吐服务化。

它的核心优势主要有三个:

text 复制代码
1. Continuous Batching:连续批处理
2. PagedAttention:更高效的 KV Cache 管理
3. OpenAI Compatible Server:可直接对外提供接口

八、vLLM 架构理解

1. 原始 transformers 模式

text 复制代码
请求1 → 单独推理
请求2 → 单独推理
请求3 → 单独推理

2. vLLM 模式

text 复制代码
请求1
请求2
请求3
  ↓
调度器统一管理
  ↓
动态组成 batch
  ↓
GPU 高效执行
  ↓
分别返回结果

它的关键不是简单 batch,而是连续 batch。

普通 batch 的问题是:

text 复制代码
必须等一批请求都完成,才能开始下一批。

vLLM 的 Continuous Batching 可以在生成过程中动态加入新请求。

这对线上服务非常重要。


九、使用 vLLM 启动 OpenAI 兼容服务

1. 安装 vLLM

bash 复制代码
pip install vllm

如果你是 CUDA 环境,建议先确认:

bash 复制代码
nvidia-smi
python -c "import torch; print(torch.cuda.is_available())"

2. 启动 vLLM 服务

bash 复制代码
python -m vllm.entrypoints.openai.api_server \
  --model Qwen/Qwen2.5-0.5B-Instruct \
  --host 0.0.0.0 \
  --port 8001 \
  --trust-remote-code \
  --gpu-memory-utilization 0.85 \
  --max-model-len 2048

几个参数说明:

text 复制代码
--model:模型名称或本地模型路径
--trust-remote-code:部分国产模型需要
--gpu-memory-utilization:限制显存使用比例
--max-model-len:限制最大上下文长度

十、验证 vLLM 服务

1. 健康检查

bash 复制代码
curl http://127.0.0.1:8001/health

2. 请求 completions 接口

bash 复制代码
curl http://127.0.0.1:8001/v1/completions \
-H "Content-Type: application/json" \
-d '{
  "model": "Qwen/Qwen2.5-0.5B-Instruct",
  "prompt": "请解释一下什么是大模型推理服务",
  "max_tokens": 128,
  "temperature": 0.7
}'

3. 请求 chat completions 接口

bash 复制代码
curl http://127.0.0.1:8001/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
  "model": "Qwen/Qwen2.5-0.5B-Instruct",
  "messages": [
    {
      "role": "user",
      "content": "请用工程师能听懂的话解释vLLM为什么比transformers更适合部署"
    }
  ],
  "max_tokens": 128,
  "temperature": 0.7
}'

十一、工程封装:FastAPI 调用 vLLM 服务

生产环境里,我不建议业务系统直接写死 vLLM 接口。

更推荐做一层业务网关:

text 复制代码
业务系统
  ↓
AI Gateway
  ↓
vLLM Server

这样后续可以统一做:

text 复制代码
鉴权
限流
日志
模型路由
灰度发布
异常兜底

1. 安装依赖

bash 复制代码
pip install fastapi uvicorn httpx pydantic

2. 项目结构

text 复制代码
llm-vllm-gateway/
├── app.py
├── schemas.py
├── llm_client.py
└── requirements.txt

3. schemas.py

python 复制代码
from pydantic import BaseModel, Field
from typing import Optional


class ChatRequest(BaseModel):
    prompt: str = Field(..., min_length=1, max_length=2000)
    max_tokens: int = Field(default=128, ge=1, le=512)
    temperature: float = Field(default=0.7, ge=0.0, le=2.0)


class ChatResponse(BaseModel):
    answer: str
    model: str
    cost_ms: int

4. llm_client.py

python 复制代码
import time
import httpx


class VLLMClient:
    def __init__(self, base_url: str, model: str, timeout: int = 60):
        self.base_url = base_url.rstrip("/")
        self.model = model
        self.timeout = timeout

    async def chat(self, prompt: str, max_tokens: int, temperature: float):
        start = time.time()

        url = f"{self.base_url}/v1/chat/completions"

        payload = {
            "model": self.model,
            "messages": [
                {
                    "role": "user",
                    "content": prompt
                }
            ],
            "max_tokens": max_tokens,
            "temperature": temperature
        }

        async with httpx.AsyncClient(timeout=self.timeout) as client:
            resp = await client.post(url, json=payload)
            resp.raise_for_status()
            data = resp.json()

        answer = data["choices"][0]["message"]["content"]

        cost_ms = int((time.time() - start) * 1000)

        return {
            "answer": answer,
            "model": self.model,
            "cost_ms": cost_ms
        }

5. app.py

python 复制代码
from fastapi import FastAPI, HTTPException
from schemas import ChatRequest, ChatResponse
from llm_client import VLLMClient

app = FastAPI(title="LLM Gateway With vLLM")

client = VLLMClient(
    base_url="http://127.0.0.1:8001",
    model="Qwen/Qwen2.5-0.5B-Instruct",
    timeout=60
)


@app.post("/chat", response_model=ChatResponse)
async def chat(req: ChatRequest):
    try:
        result = await client.chat(
            prompt=req.prompt,
            max_tokens=req.max_tokens,
            temperature=req.temperature
        )

        return ChatResponse(**result)

    except Exception as e:
        raise HTTPException(
            status_code=500,
            detail=f"LLM request failed: {str(e)}"
        )


@app.get("/health")
def health():
    return {"status": "ok"}

6. 启动网关

bash 复制代码
uvicorn app:app --host 0.0.0.0 --port 8000

7. 测试网关

bash 复制代码
curl -X POST "http://127.0.0.1:8000/chat" \
-H "Content-Type: application/json" \
-d '{
  "prompt": "解释一下vLLM中的Continuous Batching",
  "max_tokens": 128,
  "temperature": 0.7
}'

十二、压测对比:transformers vs vLLM

为了公平对比,保持以下条件一致:

text 复制代码
同一台机器
同一个模型
同样的 prompt
同样 max_tokens
同样并发用户数

1. vLLM 压测脚本

python 复制代码
from locust import HttpUser, task, between


class VLLMUser(HttpUser):
    wait_time = between(1, 2)

    @task
    def chat(self):
        self.client.post(
            "/chat",
            json={
                "prompt": "请解释大模型推理服务中的KV Cache是什么",
                "max_tokens": 128,
                "temperature": 0.7
            },
            timeout=60
        )

2. 建议记录的指标

text 复制代码
平均响应时间
P95延迟
P99延迟
QPS
失败率
GPU利用率
显存占用

3. 结果示例

text 复制代码
transformers:
平均响应:4800ms
P95:12000ms
QPS:2.1
失败率:8%

vLLM:
平均响应:1200ms
P95:2800ms
QPS:16.8
失败率:0%

注意:

text 复制代码
具体数据会受显卡、模型大小、prompt长度影响。
这里重点不是绝对数值,而是优化方向。

十三、踩坑记录:这些问题我都遇到过

坑 1:vLLM 启动直接 OOM

启动时报错:

text 复制代码
CUDA out of memory

常见原因:

text 复制代码
1. max-model-len 设置过大
2. gpu-memory-utilization 设置太高
3. 模型本身超过显存能力

解决方式:

bash 复制代码
python -m vllm.entrypoints.openai.api_server \
  --model Qwen/Qwen2.5-0.5B-Instruct \
  --gpu-memory-utilization 0.75 \
  --max-model-len 2048

坑 2:长 prompt 拖慢整体吞吐

vLLM 可以动态 batching,但并不代表无限制输入。

如果某些请求 prompt 特别长,会导致:

text 复制代码
1. batch 内部 padding 增加
2. KV Cache 占用增加
3. 整体吞吐下降

所以接口层必须限制:

python 复制代码
prompt: str = Field(..., max_length=2000)

坑 3:业务接口没有超时控制

错误写法:

python 复制代码
resp = await client.post(url, json=payload)

推荐:

python 复制代码
async with httpx.AsyncClient(timeout=60) as client:
    resp = await client.post(url, json=payload)

否则 vLLM 服务异常时,业务接口会被拖死。


坑 4:把 vLLM 服务和业务服务混在一个进程

不建议这样做:

text 复制代码
FastAPI + vLLM 同进程

更推荐:

text 复制代码
FastAPI网关进程
vLLM推理进程

原因:

text 复制代码
1. 职责清晰
2. 方便独立重启
3. 方便单独扩容
4. 方便替换模型服务

十四、注意事项

1. 不要盲目追求最大显存利用率

很多人会设置:

bash 复制代码
--gpu-memory-utilization 0.95

这看起来很爽,但线上很危险。

建议从:

text 复制代码
0.75 ~ 0.85

开始调。


2. max_model_len 不要无脑拉满

上下文越长:

text 复制代码
KV Cache越大
显存压力越高
吞吐越低

内部问答系统通常可以先设:

text 复制代码
2048 或 4096

3. 网关层一定要做参数校验

至少限制:

text 复制代码
prompt长度
max_tokens
temperature范围
请求超时

十五、适合收藏的部署步骤总结

text 复制代码
1. 先用 transformers 跑通基础推理
2. 使用 locust 做并发压测
3. 观察 GPU 利用率和 P95 延迟
4. 如果 GPU 利用率低且并发差,切换 vLLM
5. 使用 vLLM OpenAI API Server 部署模型
6. 使用 FastAPI 做业务网关
7. 网关层增加参数校验和超时控制
8. 再次压测对比 QPS、P95、失败率
9. 根据显存情况调整 max-model-len
10. 根据稳定性调整 gpu-memory-utilization

十六、避坑清单

text 复制代码
不要用 transformers 硬扛高并发
不要让业务直接裸调模型服务
不要无限制开放 max_tokens
不要把 prompt 长度放开
不要把 gpu-memory-utilization 设置过高
不要忽略 P95 / P99,只看平均耗时
不要把 vLLM 和业务逻辑塞进一个进程
不要没有超时、没有日志、没有失败兜底

十七、最终工程结论

这次优化给我的最大感受是:

text 复制代码
大模型部署不是把 generate 包成接口就结束了。

真正的推理服务要考虑:

text 复制代码
请求调度
显存管理
KV Cache
并发吞吐
超时控制
业务网关
监控压测

transformers 更适合:

text 复制代码
本地实验
单请求验证
模型效果测试

vLLM 更适合:

text 复制代码
线上推理
高并发服务
OpenAI兼容接口
多用户访问场景

如果你的服务已经出现:

text 复制代码
QPS低
GPU利用率低
请求排队
P95延迟高

那不要只想着换更贵的 GPU。

很多时候真正要换的不是机器,而是推理架构。


十八、后续优化方向

后面还可以继续做:

text 复制代码
1. Redis队列削峰
2. 多模型路由
3. 多GPU tensor parallel
4. Prometheus监控GPU与接口耗时
5. Kubernetes GPU自动扩缩容
6. vLLM多副本负载均衡
7. 请求优先级调度

这也是大模型系统从 Demo 到生产环境必须经历的一步。

相关推荐
ai产品老杨1 小时前
深度解析:基于国产化异构计算的 AI 视频管理平台架构——从 GB28181 接入到 NPU 边缘推流的解耦实践
人工智能·架构·音视频
Jmayday1 小时前
Pytorch:AI歌词生成器
人工智能·pytorch·python
狮子座明仔1 小时前
ThinkTwice: 让模型学会“做完题再检查一遍“,推理+自纠错联合训练只加3%开销
大数据·人工智能·深度学习
weixin_421607552 小时前
AI解说大师(narrator-ai-cli):影视解说+自动化剪辑,一站式创作神器!
人工智能
冷小鱼2 小时前
消息队列(MQ)技术全景科普:从选型到AI+未来
人工智能·kafka·rabbitmq·rocketmq·mq·pulsar
乌恩大侠2 小时前
【AI-RAN】在空ubuntu服务器安装环境和生成TV,高达430G文件
服务器·人工智能·ubuntu·fpga开发·o-ru
机器觉醒时代2 小时前
英伟达GR00T N系列四代模型演进解析
人工智能·机器人·具身智能·vla模型
AI技术增长2 小时前
Pytorch图像去噪实战(八):Noise2Void盲点网络图像去噪实战,只有单张带噪图也能训练
人工智能·pytorch·python
梦想很大很大2 小时前
让 AI 成为“报表配置员”:BI 低代码平台的 Schema 实践路径
前端·人工智能·低代码