目标:
部署一个VLLM大模型服务,验证速度提升。
内容:
使用同一个开源大模型,分别采用 Hugging Face Transformers 原生推理和 vLLM 服务化推理,在相同硬件、相同输入长度、相同输出长度下,对比单请求延迟、并发吞吐、TTFT、TPOT 和 Tokens/s,验证 vLLM 在在线推理场景中的性能提升。
一、第九周作业目标
本周重点掌握 5 个知识点:
- 学会下载并运行一个开源大语言模型。
- 使用 Hugging Face Transformers 实现基线推理。
- 使用 vLLM 部署 OpenAI Compatible API 服务。
- 学会进行并发压测。
- 对比不同推理框架下的延迟和吞吐量。
最终实验结构:
text
同一个模型
│
├── Hugging Face Transformers
│ ↓
│ 普通推理基线
│
└── vLLM
↓
OpenAI API Server
↓
Benchmark
↓
延迟 / Tokens/s / 吞吐量
二、为什么要学习 vLLM?
前面的课程重点基本都是模型训练,例如:
text
RNN
LSTM
Transformer
文本分类
序列标注
文本匹配
但真正把大模型应用到线上,还会遇到:
text
模型推理太慢
GPU 利用率不高
并发能力差
显存容易浪费
比如直接使用 Transformers:
python
model.generate(...)
一两个请求问题不大,但如果线上同时来了 10、50、100 个请求,如何高效利用 GPU 就变得非常重要。
vLLM 的定位就是:
面向大语言模型的高吞吐推理和服务框架。
三、实验整体设计
建议选择一个能在本机 GPU 上稳定运行的模型。
如果使用 RTX 4060 Laptop GPU 这类显卡,显存通常会成为主要约束,因此建议优先使用:
text
Qwen/Qwen2.5-1.5B-Instruct
如果显存更充足,可以考虑:
text
Qwen 3B / 4B
7B 量化模型
为了保证实验公平,最重要的是:
text
基线和 vLLM 必须使用同一个模型。
四、实验环境建议
| 项目 | 配置 |
|---|---|
| OS | Ubuntu 22.04 / Linux |
| GPU | RTX 4060 Laptop GPU |
| CUDA | 按实际环境填写 |
| Python | 3.10 / 3.11 |
| PyTorch | 按实际版本填写 |
| vLLM | 按实际版本填写 |
| Transformers | 按实际版本填写 |
| Model | Qwen2.5-1.5B-Instruct |
| dtype | float16 / auto |
| 输入长度 | 约 128 tokens |
| 最大输出 | 128 tokens |
| Benchmark Requests | 100 / 500 |
| 并发 | 1 / 4 / 8 / 16 |
如果实验机是 Windows,建议使用:
text
WSL2 + Ubuntu
或者直接 Linux。
五、项目结构
text
week9/
├── baseline_transformers.py
├── test_vllm.py
├── benchmark_client.py
├── requirements.txt
├── results/
│ ├── baseline.json
│ ├── vllm_concurrency_1.json
│ ├── vllm_concurrency_4.json
│ ├── vllm_concurrency_8.json
│ └── vllm_concurrency_16.json
└── SUMMARY.md
六、第一步:检查 GPU 环境
先执行:
bash
nvidia-smi
重点查看:
text
GPU 型号
CUDA Version
显存大小
显存占用
然后 Python 中检查:
python
import torch
print("CUDA available:", torch.cuda.is_available())
print("CUDA version:", torch.version.cuda)
if torch.cuda.is_available():
print("GPU:", torch.cuda.get_device_name(0))
七、安装环境
建议创建独立虚拟环境:
bash
conda create -n week9-vllm python=3.11 -y
conda activate week9-vllm
安装:
bash
pip install vllm
pip install transformers accelerate openai
查看版本:
bash
python -c "import torch; print(torch.__version__)"
python -c "import vllm; print(vllm.__version__)"
python -c "import transformers; print(transformers.__version__)"
八、实验一:Transformers 原生推理基线
创建:
text
baseline_transformers.py
代码:
python
import time
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
MODEL_NAME = "Qwen/Qwen2.5-1.5B-Instruct"
device = "cuda"
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
torch_dtype=torch.float16,
device_map=device
)
model.eval()
prompt = "请简单介绍一下 Transformer 模型的工作原理。"
messages = [
{
"role": "user",
"content": prompt
}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
inputs = tokenizer(
text,
return_tensors="pt"
).to(device)
# Warmup
with torch.no_grad():
_ = model.generate(
**inputs,
max_new_tokens=32
)
torch.cuda.synchronize()
start = time.perf_counter()
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=128,
do_sample=False
)
torch.cuda.synchronize()
elapsed = time.perf_counter() - start
input_tokens = inputs.input_ids.shape[1]
output_tokens = outputs.shape[1] - input_tokens
tokens_per_second = output_tokens / elapsed
print("=" * 50)
print("Input tokens:", input_tokens)
print("Output tokens:", output_tokens)
print(f"Latency: {elapsed:.3f}s")
print(f"Generation speed: {tokens_per_second:.2f} token/s")
result = tokenizer.decode(
outputs[0][input_tokens:],
skip_special_tokens=True
)
print("\nResult:")
print(result)
运行:
bash
python baseline_transformers.py
注意:作业报告必须填写自己实际测试的数据。
九、为什么一定要 Warmup?
第一次推理通常包含:
text
CUDA 初始化
Kernel 初始化
内存分配
模型缓存
因此第一次调用:
python
model.generate()
通常不能直接作为最终性能数据。
正确流程:
text
Warmup
↓
正式 Benchmark
十、实验二:启动 vLLM 服务
启动:
bash
vllm serve Qwen/Qwen2.5-1.5B-Instruct
或者:
bash
vllm serve \
Qwen/Qwen2.5-1.5B-Instruct \
--host 0.0.0.0 \
--port 8000
默认服务地址:
text
http://localhost:8000
十一、验证服务是否启动成功
首先:
bash
curl http://localhost:8000/v1/models
然后测试 Chat Completions:
bash
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen2.5-1.5B-Instruct",
"messages": [
{
"role": "user",
"content": "请简单介绍Transformer。"
}
],
"temperature": 0,
"max_tokens": 128
}'
十二、使用 Python 调 vLLM 服务
创建:
text
test_vllm.py
python
import time
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="EMPTY"
)
start = time.perf_counter()
response = client.chat.completions.create(
model="Qwen/Qwen2.5-1.5B-Instruct",
messages=[
{
"role": "user",
"content": "请简单介绍一下 Transformer 模型。"
}
],
temperature=0,
max_tokens=128
)
elapsed = time.perf_counter() - start
usage = response.usage
print("Prompt tokens:", usage.prompt_tokens)
print("Completion tokens:", usage.completion_tokens)
print(f"Latency: {elapsed:.3f}s")
speed = usage.completion_tokens / elapsed
print(f"Output speed: {speed:.2f} token/s")
print("\nResult:")
print(response.choices[0].message.content)
运行:
bash
python test_vllm.py
十三、只测一个请求,可能看不出 vLLM 的优势
如果 Transformers 和 vLLM 都只跑 1 个请求,你可能发现差异并不明显。
vLLM 真正需要重点验证的是:
并发请求下的整体吞吐量。
因此建议测试:
text
1 并发
4 并发
8 并发
16 并发
十四、核心实验:并发性能测试
| 实验 | 并发 |
|---|---|
| Test 1 | 1 |
| Test 2 | 4 |
| Test 3 | 8 |
| Test 4 | 16 |
| Test 5 | 32(显存允许时) |
每次建议:
text
100 个 Prompt
input ≈ 128 tokens
output = 128 tokens
十五、自己写并发 Benchmark
创建:
text
benchmark_client.py
python
import asyncio
import time
import statistics
from openai import AsyncOpenAI
MODEL = "Qwen/Qwen2.5-1.5B-Instruct"
client = AsyncOpenAI(
base_url="http://localhost:8000/v1",
api_key="EMPTY"
)
PROMPT = "请解释 Transformer 中 Self-Attention 的基本工作原理。"
async def request_once():
start = time.perf_counter()
response = await client.chat.completions.create(
model=MODEL,
messages=[
{
"role": "user",
"content": PROMPT
}
],
max_tokens=128,
temperature=0
)
elapsed = time.perf_counter() - start
output_tokens = response.usage.completion_tokens
return elapsed, output_tokens
async def worker(semaphore):
async with semaphore:
return await request_once()
async def benchmark(total_requests, concurrency):
semaphore = asyncio.Semaphore(concurrency)
start = time.perf_counter()
tasks = [
worker(semaphore)
for _ in range(total_requests)
]
results = await asyncio.gather(*tasks)
total_time = time.perf_counter() - start
latencies = [x[0] for x in results]
total_tokens = sum(x[1] for x in results)
throughput = total_tokens / total_time
request_per_second = total_requests / total_time
print("\n=============================")
print("Concurrency:", concurrency)
print("Requests:", total_requests)
print(f"Total time: {total_time:.2f}s")
print(f"RPS: {request_per_second:.2f}")
print(f"Output throughput: {throughput:.2f} token/s")
print(f"Average latency: {statistics.mean(latencies):.2f}s")
print(f"P50 latency: {statistics.median(latencies):.2f}s")
print(f"Max latency: {max(latencies):.2f}s")
async def main():
for concurrency in [1, 4, 8, 16]:
await benchmark(
total_requests=100,
concurrency=concurrency
)
asyncio.run(main())
运行:
bash
python benchmark_client.py
十六、推荐:直接使用 vLLM 官方 Benchmark
可以查看:
bash
vllm bench serve --help
通过官方 benchmark 工具,可以更标准地测试:
text
模型
数据集
输入长度
输出长度
请求数量
请求速率
并发吞吐
十七、真正应该关注哪些指标?
1. TTFT
text
Time To First Token
即:
用户发送请求到看到第一个 Token 的时间。
这个指标直接影响用户对"响应快不快"的感知。
2. TPOT
text
Time Per Output Token
表示第一个 Token 出来以后,平均生成一个 Token 需要多少时间。
例如:
text
TPOT = 30 ms
大致相当于:
text
≈ 33 token/s
3. End-to-End Latency
完整请求耗时:
text
请求开始
↓
Prompt Prefill
↓
Decode
↓
生成结束
4. Throughput
这是验证 vLLM 优势的核心指标。
计算:
text
Speedup = vLLM Throughput / Baseline Throughput
最终应该报告:
text
Speedup = X.XX ×
必须来自实际测试结果。
十八、为什么 vLLM 吞吐通常会提升?
1. KV Cache
Transformer 在生成过程中,过去 Token 的 Key / Value 可以缓存下来,避免重复计算。
随着:
text
上下文长度 ↑
并发用户 ↑
KV Cache 的显存需求也会快速增大。
2. PagedAttention
可以将 KV Cache 的管理方式类比操作系统分页:
text
虚拟内存
↓
Page
↓
按页管理
相比为每个请求预留连续空间,分页方式可以更加灵活地利用 GPU 显存。
3. Continuous Batching
假设三个请求:
text
A:生成 20 token
B:生成 200 token
C:生成 50 token
当 A 完成后,可以及时将新请求加入,而不是等待整个固定 Batch 都结束。
因此在并发场景中:
text
GPU 利用率 ↑
吞吐量 ↑
十九、不要把"速度提升"简单理解为单请求更快
更准确的结论是:
vLLM 的主要优势通常体现在大模型在线 Serving 的 GPU 利用率、并发调度和总体吞吐能力上;具体单请求延迟是否改善,以及吞吐可以提高多少,需要结合模型、GPU、输入输出长度、并发量和配置通过实测确定。
二十、实验时一定要控制变量
必须保持:
text
同一个模型
同一个 GPU
同一个 dtype
相同 Prompt
相同输入长度
相同输出长度
相同 temperature
例如:
text
Model: Qwen2.5-1.5B-Instruct
temperature: 0
max_tokens: 128
二十一、推荐测试矩阵
建议至少完成:
text
Transformers
├── concurrency 1
├── concurrency 4
├── concurrency 8
└── concurrency 16
vLLM
├── concurrency 1
├── concurrency 4
├── concurrency 8
└── concurrency 16
形成:
text
2 个推理框架 × 4 个并发级别 = 8 组实验
每组建议:
text
Warmup 10
正式测试 100
二十二、建议最终实验结果表
单请求测试
| Framework | Latency | Output Tokens | Tokens/s |
|---|---|---|---|
| Transformers | 实测 | 128 | 实测 |
| vLLM | 实测 | 128 | 实测 |
并发吞吐测试
| 并发 | Transformers | vLLM | Speedup |
|---|---|---|---|
| 1 | 实测 | 实测 | x |
| 4 | 实测 | 实测 | x |
| 8 | 实测 | 实测 | x |
| 16 | 实测 | 实测 | x |
单位统一:
text
output tokens / second
二十三、建议额外记录 GPU 数据
压测时开另一个终端:
bash
watch -n 1 nvidia-smi
记录:
| Framework | GPU Util | GPU Memory |
|---|---|---|
| Transformers | 实测 | 实测 |
| vLLM | 实测 | 实测 |
这样可以进一步验证:
text
性能提升是否来自更高的 GPU 利用率
二十四、第九周最终实验结论模板
markdown
## 实验结论
本实验尝试将开源大语言模型部署为在线推理服务,
并比较 Hugging Face Transformers 与 vLLM
在相同模型和硬件条件下的推理性能。
实验重点考察单请求延迟、TTFT、TPOT、
Output Tokens/s、Requests/s 以及不同并发条件下的吞吐量。
单请求场景下,两种方案的性能差异需要结合实际测试结果分析。
随着并发请求数量增加,vLLM 可以通过更高效的
KV Cache 管理、PagedAttention 与 Continuous Batching
提升 GPU 利用率和整体吞吐能力。
本实验说明,大模型推理系统不能只关注模型本身,
还需要关注推理框架、并发调度、显存管理以及服务吞吐量。
二十五、本周进阶实验
实验一:不同并发数
text
1
2
4
8
16
32
绘制:
text
Concurrency → Tokens/s
曲线,观察 GPU 最大吞吐点。
实验二:不同输入长度
text
128
512
1024
2048
观察:
text
TTFT
显存
Throughput
变化。
实验三:不同输出长度
text
32
128
512
观察 Decode 阶段对性能的影响。
二十六、第九周作业验收标准
建议至少完成:
- 成功启动一个 vLLM 大模型服务。
/v1/chat/completions能正常返回结果。- 完成 Transformers 原生推理基线。
- 测试并发
1 / 4 / 8 / 16。 - 记录单请求延迟。
- 记录 Requests/s。
- 记录 Output Tokens/s。
- 最好记录 TTFT / TPOT。
- 对比 GPU 显存和利用率。
- 计算实际 Speedup:
text
vLLM Throughput
----------------
Baseline Throughput
最终得到:
text
Speedup = X.XX ×
总结
第九周最核心的知识点不是"把模型跑起来",而是理解为什么同一个大模型,在不同推理框架和不同并发条件下,会表现出完全不同的服务能力。
通过本次实验,可以进一步理解:
text
大模型部署
↓
推理服务
↓
KV Cache
↓
PagedAttention
↓
Continuous Batching
↓
并发压测
↓
吞吐优化
这也为后续学习大模型分布式推理、Tensor Parallel、多 GPU 部署和生产级 LLM Serving 打下基础。