3、vLLM基础:vLLM架构解析、模型加载与推理、性能基准测试
聊到K8s自动扩缩容,咱们得先搞清楚一个核心问题——你到底在扩缩什么?
说白了,vLLM就是那个跑在Pod里的推理引擎。你如果连它的脾气都没摸透,扩缩容就是瞎折腾。我在生产环境里见过太多人,K8s配得花里胡哨,结果vLLM一压测就崩,最后发现是显存分配策略没搞对。
嗯,这一章咱们就把vLLM的底裤扒干净。从架构到加载,再到压测,一条龙讲透。
3.1 vLLM架构解析:它凭什么这么快?
vLLM的核心优势,说白了就四个字——显存管理。
传统推理框架,比如HuggingFace的Transformers,每次请求来了都得重新分配显存。你想想看,这多浪费?就像你每次吃饭都重新买一套碗筷,吃完就扔。vLLM不一样,它搞了一套PagedAttention机制,把显存切成小块,按需分配,用完回收。
核心概念:PagedAttention
这是vLLM的杀手锏。它把KV Cache(键值缓存)分成固定大小的“页”,像操作系统的虚拟内存一样管理。好处是:
- 显存碎片率降低80%以上
- 支持动态批处理(Dynamic Batching)
- 长序列推理时不会OOM
我在项目中遇到过最典型的场景:用HuggingFace跑一个13B模型,单请求最大上下文4096,显存直接吃掉24GB。换成vLLM后,同样的模型,同样的配置,显存占用降到14GB。你算算,这省出来的10GB能多塞多少个并发请求?
下面这张图,是我手绘的vLLM核心架构流程,你一看就明白:
流程其实不复杂:请求进来 → 调度器排队 → 显存管理器分配页 → 推理引擎计算 → 输出。但每一步都有讲究。比如调度器,它支持连续批处理(Continuous Batching),意思是前一个请求还没算完,后一个请求的KV Cache已经可以开始分配了。这比传统框架的“等一个算完再算下一个”效率高得多。
3.2 模型加载与推理:手把手教你跑起来
好,理论讲完了,咱们来点实际的。怎么把模型塞进vLLM?
我个人习惯用vllm.LLM这个类来加载。它支持HuggingFace格式的模型,也支持本地路径。你只需要指定模型名字或路径,vLLM会自动下载并加载。
小提示:如果你在内网环境,记得先手动下载模型到本地,然后用--model /path/to/model指定路径。别问我怎么知道的——我曾经在离线机房配了整整一天,才发现vLLM默认会去HuggingFace拉模型。
来看一个最基础的加载和推理示例:
from vllm import LLM, SamplingParams
# 加载模型
llm = LLM(
model="meta-llama/Llama-2-7b-chat-hf",
tensor_parallel_size=1, # 单卡推理
dtype="float16", # 半精度,省显存
max_model_len=4096, # 最大上下文长度
gpu_memory_utilization=0.9 # 显存利用率,别拉满
)
# 配置采样参数
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=512
)
# 推理
prompts = ["什么是Kubernetes?", "请解释一下vLLM的工作原理"]
outputs = llm.generate(prompts, sampling_params)
# 输出结果
for output in outputs:
print(f"Prompt: {output.prompt}")
print(f"Generated: {output.outputs[0].text}")
print("-" * 50)
这段代码看着简单,但有几个坑我得提醒你:
避坑指南:
- gpu_memory_utilization:我曾经设成1.0,结果模型加载到一半直接OOM。建议0.85-0.95之间,留点余量给KV Cache动态分配。
- max_model_len:别设太大。比如你模型支持32K上下文,但你实际只用4K,设成32K会白白浪费显存。我一般按业务场景的P99长度来设。
- tensor_parallel_size:多卡推理时,这个值要跟GPU数量匹配。设错了会导致模型加载失败。
说到多卡推理,vLLM支持张量并行(Tensor Parallelism)和流水线并行(Pipeline Parallelism)。我个人更推荐张量并行,因为它通信开销小,延迟更低。你只需要把tensor_parallel_size设成GPU数量就行,vLLM会自动切分模型权重。
3.3 性能基准测试:你的vLLM到底有多快?
模型跑起来了,然后呢?你得知道它到底能扛多少并发,延迟多少。不然你怎么给K8s的HPA设阈值?
我常用的压测工具是vllm.entrypoints.openai.api_server自带的benchmark脚本,或者直接用locust。但最直观的方式,还是用vLLM官方提供的benchmarks目录下的脚本。
先看看单请求延迟怎么测:
# 启动服务
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-7b-chat-hf \
--port 8000
# 用curl测试单请求延迟
time curl -X POST http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-2-7b-chat-hf",
"prompt": "什么是Kubernetes?",
"max_tokens": 100,
"temperature": 0
}'
但单请求没意义,你得测并发。我一般用wrk或者hey:
# 安装hey
go install github.com/rakyll/hey@latest
# 并发压测:100个并发,持续30秒
hey -n 1000 -c 100 -m POST \
-H "Content-Type: application/json" \
-d '{"model":"meta-llama/Llama-2-7b-chat-hf","prompt":"什么是Kubernetes?","max_tokens":100,"temperature":0}' \
http://localhost:8000/v1/completions
压测完你会得到一堆数据。我习惯关注这几个指标:
| 指标 | 说明 | 我的经验值(7B模型,A100) |
|---|---|---|
| TTFT(Time to First Token) | 首Token延迟 | < 200ms |
| TPOT(Time per Output Token) | 每个输出Token的生成时间 | < 30ms |
| Throughput | 每秒生成的Token数 | > 2000 tokens/s |
| QPS(Queries Per Second) | 每秒处理的请求数 | > 50 qps |
关键结论:
vLLM的吞吐量,在相同硬件条件下,比HuggingFace Transformers高出2-4倍。我实测过,7B模型在单张A100上,vLLM能跑到2500 tokens/s,而原生Transformers只有800左右。这就是PagedAttention的威力。
不过要注意,这些数据跟你的输入长度和输出长度强相关。输入越长,TTFT越高;输出越长,TPOT越稳定。我建议你压测时用业务场景的真实数据分布,别用固定长度的假数据,否则结果没参考价值。
嗯,这一章的内容就到这。vLLM的架构、加载、压测,你心里应该有数了。下一章咱们会聊怎么把这些东西塞进K8s,做成一个可扩缩容的服务。
公众号:蓝海资料掘金营,微信deep3321