FastChat 接入 vLLM:用高吞吐推理引擎替换模型 Worker 的部署实践与源码解析
【免费下载链接】FastChatAn open platform for training, serving, and evaluating large language models. Release repo for Vicuna and Chatbot Arena.项目地址: https://gitcode.com/GitHub_Trending/fa/FastChat
本篇基于 FastChat 官方文档 vLLM Integration 展开,讲清如何在 FastChat 中把默认的 Hugging Face 模型 Worker 替换为 vLLM Worker:从安装、启动命令、tokenizer 兼容与 AWQ 量化等实操细节,到vllm_worker.py的引擎构建、采样参数映射、流式输出与 Controller 注册机制的源码级原理。读完后你可以独立部署一个基于 vLLM 的 FastChat 集群,并理解每个命令行参数在底层如何生效。
为什么要用 vLLM Worker
FastChat 的默认 Worker(fastchat.serve.model_worker)基于 Hugging Face Transformers 加载模型,README 对其定位是:
The default model worker based on huggingface/transformers has great compatibility but can be slow. If you want high-throughput batched serving, you can try vLLM integration.
官方文档给出的核心理由是:vLLM 提供先进的连续批处理(continuous batching),可带来约 10 倍的吞吐提升。vLLM 在此体系中充当可替换的 Worker 实现——它只改变"模型如何被加载和推理"这一层,FastChat 的 Controller、Gradio Web 服务、OpenAI API 服务的命令与用法完全不变。vLLM 自身支持的模型清单以官方支持模型列表为准。
从源码结构看,这一设计成立的关键在于 FastChat 抽出了公共基类 BaseModelWorker,它封装了 Worker 与 Controller 之间的通信协议(注册、心跳、状态查询),而vllm_worker.py只需继承该基类并实现generate_stream即可接入整个集群。
安装与启动
第一步:安装 vLLM
pip install vllm第二步:用 vLLM Worker 替换普通 Worker
启动模型 Worker 时,把fastchat.serve.model_worker换成fastchat.serve.vllm_worker,其余服务(controller、gradio web server、OpenAI API server)保持原样:
python3 -m fastchat.serve.vllm_worker --model-path lmsys/vicuna-7b-v1.5第三步(按需):tokenizer 报错与 AWQ 量化模型
若遇到 tokenizer 错误,显式指定一个兼容的 tokenizer:
python3 -m fastchat.serve.vllm_worker --model-path lmsys/vicuna-7b-v1.5 --tokenizer hf-internal-testing/llama-tokenizer若使用 AWQ 量化模型,加上量化参数:
python3 -m fastchat.serve.vllm_worker --model-path TheBloke/vicuna-7B-v1.5-AWQ --quantization awq这里有个值得注意的细节:vllm_worker.py自身的argparse并没有定义--tokenizer和--quantization参数,它们来自 vllm_worker.py 末尾的这一行:
parser = AsyncEngineArgs.add_cli_args(parser)即 vLLM 的AsyncEngineArgs会把全部 vLLM 引擎参数(--tokenizer、--quantization、--dtype、--max-model-len等)注入 FastChat 的命令行解析器。这意味着凡是 vLLM 引擎支持的选项,都可以直接透传给 vLLM Worker,无需 FastChat 逐个声明。
vLLM Worker 命令行参数详解
以下参数表整理自 vllm_worker.py 的argparse定义,包含默认值:
| 参数 | 默认值 | 说明 |
|---|---|---|
--host | localhost | Worker 服务监听地址 |
--port | 21002 | Worker 服务端口 |
--worker-address | http://localhost:21002 | 向 Controller 注册的地址,多机部署时需用其他节点可访问的地址 |
--controller-address | http://localhost:21001 | Controller 地址 |
--model-path | lmsys/vicuna-7b-v1.5 | 模型路径(本地路径或 Hugging Face 名称) |
--model-names | 无(取 model-path 末段) | 逗号分隔的对外显示名称,供 Arena/API 侧选择模型 |
--limit-worker-concurrency | 1024 | Worker 侧异步信号量上限,控制并发请求数 |
--no-register | 关闭 | 跳过向 Controller 注册(用于独立调试) |
--num-gpus | 1 | 使用的 GPU 数量,大于 1 时映射为张量并行 |
--conv-template | 无(按模型自动推断) | 会话模板名称 |
--trust_remote_code | True(store_false) | 下载模型时信任 Hugging Face 远程代码 |
--gpu_memory_utilization | 0.9 | GPU 显存中预留给模型权重、激活与 KV cache 的比例。调高可增大 KV cache、提升吞吐,但过高可能 OOM |
| vLLM 透传参数 | — | 经AsyncEngineArgs.add_cli_args(parser)注入的全部 vLLM 引擎参数 |
其中--num-gpus的处理逻辑在 vllm_worker.py 入口处:
if args.model_path: args.model = args.model_path if args.num_gpus > 1: args.tensor_parallel_size = args.num_gpus engine_args = AsyncEngineArgs.from_cli_args(args) engine = AsyncLLMEngine.from_engine_args(engine_args)也就是说 FastChat 并不直接感知张量并行,只是把--num-gpus转写成 vLLM 的tensor_parallel_size交给引擎自行切分模型。多卡部署时通常还需配合ray start --head与CUDA_VISIBLE_DEVICES指定卡号,这在 local_cluster.md 的集群示例中有完整体现(例如 33B 模型用--num-gpus 2双卡启动)。
引擎构建与 Worker 初始化
启动流程在if __name__ == "__main__"中完成:解析参数 → 构建AsyncLLMEngine→ 创建VLLMWorker→ 用 uvicorn 挂载 FastAPI 应用。
VLLMWorker.__init__中有几处关键处理(见 vllm_worker.py L31-L65):
- tokenizer 版本兼容:
self.tokenizer = llm_engine.engine.tokenizer # This is to support vllm >= 0.2.7 where TokenizerGroup was introduced # and llm_engine.engine.tokenizer was no longer a raw tokenizer if hasattr(self.tokenizer, "tokenizer"): self.tokenizer = llm_engine.engine.tokenizer.tokenizervLLM 0.2.7 之后引入了TokenizerGroup包装层,源码通过hasattr探测并在必要时剥出内部的 raw tokenizer。这解释了为什么文档建议"tokenizer 报错时指定--tokenizer"——本质是让引擎拿到的 tokenizer 与generate_stream中的decode/eos_token_id调用兼容。
- 上下文长度上报:
self.context_len = get_context_length(llm_engine.engine.model_config.hf_config)get_context_length 会按max_position_embeddings、max_sequence_length、seq_length、max_seq_len、model_max_length的优先级从 HF 配置中取值,并乘以rope_scaling因子,取不到时回退为 2048。该值通过/model_details接口对外暴露,供上层(如 OpenAI API 服务)判断请求长度是否超界。
- 注册与心跳:若未指定
--no-register,调用基类的init_heart_beat()。BaseModelWorker 会先向 Controller 的/register_worker端点注册自身地址与模型信息,随后启动守护线程按WORKER_HEART_BEAT_INTERVAL(默认 45 秒,可用环境变量FASTCHAT_WORKER_HEART_BEAT_INTERVAL覆盖,见 constants.py)调用/receive_heart_beat上报队列长度;若 Controller 回复exist为假,则自动重新注册。
worker_id并非自己生成,而是直接复用model_worker模块中的全局变量(str(uuid.uuid4())[:8]),保证两种 Worker 在日志与 Controller 视角下的身份格式一致。
生成流程源码解析:采样参数如何映射到 vLLM
generate_stream是 vLLM Worker 的核心方法,它接收 Controller 路由过来的请求参数,并转写为 vLLM 的SamplingParams。参数默认值与处理逻辑如下(见 vllm_worker.py L67-L118):
| 请求参数 | 默认值 | 处理逻辑 |
|---|---|---|
temperature | 1.0 | 若<= 1e-5(即贪心解码),强制top_p = 1.0 |
top_p | 1.0 | 下限钳制为max(top_p, 1e-5),避免 vLLM 因top_p=0报错 |
top_k | -1.0 | 原样传入 |
presence_penalty | 0.0 | 原样传入 |
frequency_penalty | 0.0 | 原样传入 |
max_new_tokens | 256 | 映射为max_tokens |
stop/stop_token_ids | 无 | 见下方 stop 处理 |
echo | True | 为 True 时输出拼接 prompt 全文(与model_worker行为对齐) |
use_beam_search | False | 原样传入 |
best_of | None | 原样传入 |
stop 条件的双重处理是一个易被忽略的细节:
- 字符串形式的 stop(
stop_str)直接收集为集合; stop_token_ids会被 tokenizer 逐个decode成字符串——若解码结果非空则同时加入字符串 stop 集合,同时原始的stop_token_ids(含自动追加的eos_token_id)也一并传给 vLLM。
这样可以兼容 vLLM 中"token 级 stop"与"字符串级 stop"两种机制,保证特殊 token 与常规文本停止符都能正确截断输出。
流式输出中的 partial stop 过滤:
partial_stop = any(is_partial_stop(text_outputs, i) for i in stop) # prevent yielding partial stop sequence if partial_stop: continueis_partial_stop 检查当前输出末尾是否恰好是某个 stop 串的前缀(例如刚吐出\n而 stop 是\nAssistant),若命中则跳过本次 yield,避免客户端看到半截停止序列。
客户端断连的中止处理:流式循环中每次检查request.is_disconnected(),一旦断连即调用engine.abort(request_id),将finish_reason置为"abort"并结束生成——这意味着被放弃的请求会立即释放 KV cache 资源,而不是跑完整个序列。
双次 yield 的设计:
if request_output.finished: yield (json.dumps({**ret, **{"finish_reason": None}}) + "\0").encode() yield (json.dumps(ret) + "\0").encode()源码注释写明:这里故意在结束时先 yield 一条finish_reason为None的空内容消息、再 yield 携带真实finish_reason的最终消息,目的是对齐model_worker的流式协议,从而让上层的 OpenAI 兼容 API(openai_api_server.py)能正确发出含finish_reason且内容为空的最后一个 chat completion chunk。
非流式的generate方法则是消费完generate_stream后解析最后一条报文,保证两种接口返回结构一致。
FastAPI 接口与信号量并发控制
vLLM Worker 自身是一个 FastAPI 服务,暴露的端点与BaseModelWorker提供的协议端点完全对应(vllm_worker.pyL198-L241 与 base_model_worker.py L196-L241):
| 端点 | 作用 |
|---|---|
POST /worker_generate_stream | 流式生成,StreamingResponse返回 |
POST /worker_generate | 非流式生成 |
POST /worker_get_status | 返回模型名与队列长度,供 Controller 调度 |
POST /count_token | 统计 prompt token 数 |
POST /worker_get_conv_template | 返回会话模板(由--conv-template或按--model-path自动推断,推断逻辑见 model_adapter.py) |
POST /model_details | 返回上下文长度 |
并发控制沿用基类的信号量模式:acquire_worker_semaphore在入口处以--limit-worker-concurrency(默认 1024)为上限获取asyncio.Semaphore。vllm_worker.py相对基类的一个增强是create_background_tasks(request_id)——流式请求结束后,后台任务不仅释放信号量,还会追加执行engine.abort(request_id),确保异常路径下 vLLM 引擎侧的请求也能被显式取消。
值得对比的是默认model_worker的ModelWorker.__init__支持--load-8bit、--cpu-offloading、--gptq-config、--awq-config、--exllama-config等参数(见 model_worker.py L38-L57);而 vLLM Worker 的量化支持统一走 vLLM 引擎自身的--quantization awq等参数,两套机制互不重叠。
多机与多卡部署示例
官方文档 local_cluster.md 给出了在 GPU 集群上以 vLLM Worker 组网的完整示例,摘录几个有代表性的形态:
单卡单 Worker(指定可见设备与独立端口):
CUDA_VISIBLE_DEVICES=0 python3 -m fastchat.serve.vllm_worker --model-path lmsys/vicuna-13b-v1.5 --model-name vicuna-13b --controller http://node-01:10002 --host 0.0.0.0 --port 31000 --worker-address http://$(hostname):31000双卡张量并行(33B 模型):
CUDA_VISIBLE_DEVICES=2,3 ray start --head python3 -m fastchat.serve.vllm_worker --model-path lmsys/vicuna-33b-v1.3 --model-name vicuna-33b --controller http://node-01:10002 --host 0.0.0.0 --port 31002 --worker-address http://$(hostname):31002 --num-gpus 2tokenizer 不匹配时显式指定(Llama-2 / guanaco 等模型):
CUDA_VISIBLE_DEVICES=0 python3 -m fastchat.serve.vllm_worker --model-path meta-llama/Llama-2-13b-chat-hf --model-name llama-2-13b-chat --controller http://node-01:10002 --host 0.0.0.0 --port 31000 --worker-address http://$(hostname):31000 --tokenizer meta-llama/Llama-2-7b-chat-hf跨节点部署的要点:--host 0.0.0.0使 Worker 对外可访问;--worker-address使用$(hostname)让 Controller(位于 node-01)能通过真实主机名回调该 Worker;张量并行的 Worker 需先ray start。
测试脚本 tests/launch_openai_api_test_server.py 也演示了混布场景:多个model_worker与一个vllm_worker(meta-llama/Llama-2-7b-chat-hf,同样追加--tokenizer hf-internal-testing/llama-tokenizer)并存,由controller与openai_api_server统一对外服务,各 Worker 按--port 40000+i分配端口。
常见问题与注意事项
- tokenizer 报错:不同来源的 LLaMA 系权重常缺少自带 tokenizer,按文档指定
--tokenizer hf-internal-testing/llama-tokenizer(或对应尺寸的 Llama tokenizer,见 local_cluster.md)。 - 显存不足:调低
--gpu_memory_utilization(默认 0.9),其权衡关系(KV cache 大小 vs OOM 风险)直接写在参数 help 中。 - 想脱离 Controller 单独调试 Worker:使用
--no-register跳过注册与心跳线程,直接对该 Worker 的/worker_generate_stream端点发请求即可。 - 对外模型名:
--model-names是逗号分隔的显示名,用于 Gradio 下拉框与 OpenAI API 的model字段;不指定时取--model-path的末段。 - 版本前提:本文所有参数与行为以当前仓库代码为准;
vllm_worker.py对 vLLM 0.2.7 的TokenizerGroup做了兼容,实际部署时 vLLM 版本还需满足所加载模型的要求。 - 验证连通性:Worker 启动后可用
python3 -m fastchat.serve.test_message --model <model-name> --controller http://<controller>:<port>发送测试消息(用法见 local_cluster.md 的 test 一节)。
小结
FastChat 的 vLLM 集成体现了一种典型的"可替换推理后端"架构:BaseModelWorker 固定了注册、心跳、状态与协议接口,vllm_worker.py 只负责把请求参数翻译成 vLLMSamplingParams并驱动AsyncLLMEngine生成。对使用者而言,切换成本只有一行命令(把model_worker换成vllm_worker),换来的是连续批处理带来的吞吐提升;对排查问题而言,tokenizer 兼容、partial stop、断连中止与双次 yield 这四处源码细节,是理解流式行为异常时最应优先检查的位置。
【免费下载链接】FastChatAn open platform for training, serving, and evaluating large language models. Release repo for Vicuna and Chatbot Arena.项目地址: https://gitcode.com/GitHub_Trending/fa/FastChat
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考