FastChat 本地 GPU 集群部署指南:Controller-Worker 架构下的多机多卡 LLM 推理实践
本文围绕 FastChat 仓库中的 本地集群部署文档 展开,讲解如何用“一个 Controller + 多台 GPU 机器上的 Worker”搭建跨节点的大语言模型推理集群,覆盖 vLLM Worker、多卡张量并行、multi_model_worker 多模型共卡等典型部署形态;读完后你可以直接按节点照抄命令拉起集群,并理解每个参数的源码级含义与验证方法。
一、集群架构:单点 Controller,水平扩展 Worker
FastChat 的服务器架构是一个典型的去中心化推理 + 中心化路由结构(完整架构图见 docs/server_arch.md 及上图):
- Controller(控制器):集群唯一的协调节点,维护“模型名 → Worker 地址”的映射表,负责 Worker 注册、心跳探活、请求路由。它本身不执行推理。实现位于 controller.py,核心类
Controller内部用worker_info字典保存每个 Worker 的model_names、speed、queue_length、last_heart_beat等元信息(见 controller.py 的WorkerInfo)。 - Model Worker(推理节点):每台 GPU 机器上可运行任意多个 Worker 进程,每个 Worker 独占一张或多张卡加载一个(或一组)模型,启动后向 Controller 注册自己的地址和模型列表,并周期性发送心跳。
从源码结构看,整个集群的协作协议就是三组 HTTP 接口:
- 注册:Worker 启动后 POST 到 Controller 的
/register_worker,提交自己的worker_name(即对外可达地址)、check_heart_beat标志和当前状态(模型名、速度、队列长度); - 心跳:Worker 每隔
WORKER_HEART_BEAT_INTERVAL秒 POST/receive_heart_beat,Controller 端则每CONTROLLER_HEART_BEAT_EXPIRATION秒扫描一次,把超时未上报的 Worker 从表中摘除(controller.py 的remove_stale_workers_by_expiration); - 路由:客户端请求
/get_worker_address或直接把请求发给 Controller 的/worker_generate_stream,由 Controller 按分发策略选出 Worker 并流式转发。
心跳相关的默认值定义在 constants.py:
CONTROLLER_HEART_BEAT_EXPIRATION = int(
os.getenv("FASTCHAT_CONTROLLER_HEART_BEAT_EXPIRATION", 90)
)
WORKER_HEART_BEAT_INTERVAL = int(os.getenv("FASTCHAT_WORKER_HEART_BEAT_INTERVAL", 45))
WORKER_API_TIMEOUT = int(os.getenv("FASTCHAT_WORKER_API_TIMEOUT", 100))
即 Worker 每 45 秒上报一次心跳,Controller 容忍 90 秒的静默窗口——Worker 意外宕机后最多约 90 秒就会被剔除出路由表。这三个常量都支持用同名环境变量覆盖,跨机房部署时网络抖动较大可以适当调大。
Controller 自身还提供 lottery(按速度加权随机)和 shortest_queue(最短队列优先,默认)两种分发策略,见 controller.py 的 DispatchMethod,可通过 --dispatch-method 参数选择。
二、命令参数逐项解析
本地集群文档中所有 Worker 命令都长这样,下面先统一解释各参数(以 vLLM 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
| 参数 | 作用 | 源码依据 |
|---|---|---|
CUDA_VISIBLE_DEVICES |
环境隔离手段,指定该 Worker 进程可见的物理 GPU。文档中所有单卡命令都靠它把模型固定到特定卡上 | 操作系统级机制,命令中直接前置 |
--model-path |
模型权重路径:本地目录或 Hugging Face 仓库 ID | vllm_worker.py |
--model-name |
该 Worker 对外注册的模型名(可逗号分隔多个)。客户端和 Controller 都用这个名字路由请求 | 同上 |
--controller |
Controller 地址,所有 Worker 都指向 http://node-01:10002 |
同上 |
--host 0.0.0.0 --port |
Worker 本进程监听的地址和端口,每卡一个独立端口,避免冲突 | 同上 |
--worker-address |
向 Controller 注册时上报的对外地址。用 $(hostname) 而不是 localhost,因为 Controller 可能运行在别的机器上,必须用别的节点也能访问到的主机名 |
同上 |
--num-gpus |
多卡张量并行卡数,例如 33B 模型用 2 张卡 | 见下文 node-01 说明 |
--tokenizer |
指定与模型不同的 tokenizer(如 30B 模型用更小的 tokenizer 仓库加速分词) | vLLM 引擎参数 |
两点源码层面的补充说明:
--worker-address与--port的分工是关键:--port决定 uvicorn 实际监听哪个端口,--worker-address决定 Controller 把请求转发到哪里。单机自测时两者一致;跨机部署时,--worker-address必须是集群内其他节点可解析、可达的主机名(文档统一用http://$(hostname):端口)。- 当前仓库源码中的参数名是
--controller-address、--worker-address、--model-names(逗号分隔)和--limit-worker-concurrency,例如 vllm_worker.py 与 multi_model_worker.py 的 argparse 定义;部署文档使用了更简短的参数拼写(如--controller、--model-name、--limit),实际执行时请以你所运行版本的python3 -m fastchat.serve.<worker> --help输出为准,两者语义一致。
三、node-01:Controller + vLLM Worker + 多卡张量并行
node-01 是整个集群的控制节点,也是文档中最“复杂”的一台机器:它同时承载 Controller 和 3 个 vLLM Worker,演示了单卡、双卡张量并行两种形态。
# 1. 启动 Controller(集群唯一)
python3 -m fastchat.serve.controller --host 0.0.0.0 --port 10002
# 2. 单卡 Worker:GPU0 上加载 13B 模型
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
# 3. 单卡 Worker:GPU1 上再加载一份 13B 模型(同名模型多副本)
CUDA_VISIBLE_DEVICES=1 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 31001 \
--worker-address http://$(hostname):31001
# 4. 启动 Ray 集群头节点,占用 GPU2/3
CUDA_VISIBLE_DEVICES=2,3 ray start --head
# 5. 双卡张量并行 Worker:2 张卡协同加载 33B 模型
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 2
几个值得注意的设计点:
-
同名模型多副本:GPU0 和 GPU1 上运行两个
vicuna-13bWorker(端口 31000/31001)。Controller 的worker_info以 Worker 地址为键,两个副本是两个独立条目,请求会被分发策略在两者之间均衡,等效于对同一模型做水平扩容。 -
--num-gpus 2与 Ray:vLLM 的多卡张量并行依赖 Ray 进程组,所以 33B Worker 前先执行ray start --head。从源码看,vLLM Worker 在启动时若num_gpus > 1会将其直接映射为 vLLM 引擎的tensor_parallel_size(vllm_worker.py):if args.num_gpus > 1: args.tensor_parallel_size = args.num_gpus注意该 Worker 命令本身没有加
CUDA_VISIBLE_DEVICES,此时 Ray 会按其调度机制使用已暴露的 GPU2/3 两张卡;前两条 Worker 则用CUDA_VISIBLE_DEVICES=0/1显式钉死在单卡上,四张卡互不干扰。 -
vLLM Worker 的并发上限:
--limit-worker-concurrency默认 1024(vllm_worker.py),通过 asyncio 信号量控制同时在途请求数,vLLM 引擎自身的 continuous batching 在其上继续做细粒度调度,两者分层限流。
四、node-02 / node-03:纯 Worker 节点与 tokenizer 覆盖
node-02 和 node-03 上不运行 Controller,全部命令都是“指向 node-01 的 Controller”的 Worker。
node-02(4 张卡,4 个不同模型,全部单卡):
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
CUDA_VISIBLE_DEVICES=1 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 31001 \
--worker-address http://$(hostname):31001 \
--tokenizer meta-llama/Llama-2-7b-chat-hf
CUDA_VISIBLE_DEVICES=2 python3 -m fastchat.serve.vllm_worker \
--model-path meta-llama/Llama-2-7b-chat-hf --model-name llama-2-7b-chat \
--controller http://node-01:10002 --host 0.0.0.0 --port 31002 \
--worker-address http://$(hostname):31002 \
--tokenizer meta-llama/Llama-2-7b-chat-hf
CUDA_VISIBLE_DEVICES=3 python3 -m fastchat.serve.vllm_worker \
--model-path WizardLM/WizardLM-13B-V1.1 --model-name wizardlm-13b \
--controller http://node-01:10002 --host 0.0.0.0 --port 31003 \
--worker-address http://$(hostname):31003
node-03(2 个 Worker,各占 2 张卡,用于超大模型):
python3 -m fastchat.serve.vllm_worker \
--model-path mosaicml/mpt-30b-chat \
--controller http://node-01:10002 --host 0.0.0.0 --port 31000 \
--worker-address http://$(hostname):31000 --num-gpus 2
python3 -m fastchat.serve.vllm_worker \
--model-path timdettmers/guanaco-33b-merged --model-name guanaco-33b \
--controller http://node-01:10002 --host 0.0.0.0 --port 31002 \
--worker-address http://$(hostname):31002 --num-gpus 2 \
--tokenizer hf-internal-testing/llama-tokenizer
这里的两个细节:
--tokenizer覆盖:Llama-2-13b-chat-hf模型仓库本身没有附带 tokenizer 文件,文档统一指定用Llama-2-7b-chat-hf的 tokenizer 代替;同理 guanaco-33b 指定 llama-tokenizer。这体现了 vLLM Worker 把 tokenizer 作为独立参数暴露的价值——模型权重与分词器解耦,方便跨仓库复用。- 未指定
--model-name时的默认行为:node-03 第一条 mpt-30b 命令没有给模型名。从源码结构看,Worker 的基类在model_names缺省时取model_path的最后一段作为注册名(base_model_worker.py 的self.model_names = model_names or [model_path.split("/")[-1]]),即该模型会以mpt-30b-chat的名字进入 Controller 的模型列表。 - node-03 的两条命令没有
CUDA_VISIBLE_DEVICES前缀,可推断其依赖机器上仅有的两张卡或 Ray/vLLM 的默认设备分配;若机器上还有其他 GPU,建议按 node-01 的 33B 写法显式指定卡号,避免与 Ray 的卡抢占冲突。
五、node-04:multi_model_worker 多模型共卡
node-04 演示了另一种 Worker 形态——multi_model_worker.py,它把多个模型放进同一个 Worker 进程,适用于“小模型 + 多卡”的高密度部署,官方注释也说明其典型场景是共享同一基座权重的多个 PEFT 模型:
CUDA_VISIBLE_DEVICES=0 python3 -m fastchat.serve.multi_model_worker \
--model-path ~/model_weights/RWKV-4-Raven-14B-v12-Eng98%25-Other2%25-20230523-ctx8192.pth \
--model-name RWKV-4-Raven-14B \
--model-path lmsys/fastchat-t5-3b-v1.0 --model-name fastchat-t5-3b \
--controller http://node-01:10002 --host 0.0.0.0 --port 31000 \
--worker http://$(hostname):31000 --limit 4
CUDA_VISIBLE_DEVICES=1 python3 -m fastchat.serve.multi_model_worker \
--model-path OpenAssistant/oasst-sft-4-pythia-12b-epoch-3.5 --model-name oasst-pythia-12b \
--model-path mosaicml/mpt-7b-chat --model-name mpt-7b-chat \
--controller http://node-01:10002 --host 0.0.0.0 --port 31001 \
--worker http://$(hostname):31001 --limit 4
CUDA_VISIBLE_DEVICES=2 python3 -m fastchat.serve.multi_model_worker \
--model-path lmsys/vicuna-7b-v1.5 --model-name vicuna-7b \
--model-path THUDM/chatglm-6b --model-name chatglm-6b \
--controller http://node-01:10002 --host 0.0.0.0 --port 31002 \
--worker http://$(hostname):31002 --limit 4
CUDA_VISIBLE_DEVICES=3 python3 -m fastchat.serve.vllm_worker \
--model-path ~/model_weights/alpaca-13b \
--controller http://node-01:10002 --host 0.0.0.0 --port 31003 \
--worker-address http://$(hostname):31003
其实现机制(multi_model_worker.py):
--model-path/--model-name可重复:参数声明为action="append",每次出现追加一组,按顺序两两对齐,每个 path 内部再按逗号拆成该模型的多个注册名。启动时为一组组模型各创建一个ModelWorker子实例,并建立worker_map[model_name] -> 子worker的路由映射;- 共享信号量限流:由于同一进程内的子模型共享 GPU 内存,源码把所有子 Worker 挂到同一个
asyncio.Semaphore上(multi_model_worker.py),即文档中的--limit 4限制的是整卡的在途请求总数,而不是单个模型的并发,这比“每模型各限 4 并发”更保守、更贴合共享 GPU 的现实; - 合并队列长度上报心跳:
/worker_get_status返回所有子模型队列长度之和(multi_model_worker.py),让 Controller 的shortest_queue策略能感知整卡负载; - 该节点最后一张卡(GPU3)仍跑普通
vllm_worker加载本地 alpaca-13b,说明多种 Worker 类型可以混布在同一台机器、同一个集群中。
六、集群验证:test_message 与模型清单
全部 Worker 拉起后,文档给出的验证命令是:
python3 -m fastchat.serve.test_message --model vicuna-13b --controller http://localhost:10002
从 test_message.py 的实现看,这条命令完整走了一遍集群协议,是很好的端到端自检:
- 先向 Controller 发
/refresh_all_workers,强制重新探测所有 Worker 存活状态; - 再发
/list_models,打印当前集群注册的全部模型名; - 通过
/get_worker_address按模型名路由到一个 Worker; - 用该模型对应的对话模板(conversation template)包装一条默认提示词,POST 到 Worker 的
/worker_generate_stream,流式打印回复。
如果命令打印出模型列表和流式回复,说明注册、心跳、路由、推理四段链路全部打通;如果输出 No available workers for <model>,则通常是该模型的 Worker 尚未注册成功,或心跳被 Controller 判死(对照 controller.log 与 model_worker_*.log 排查)。除 test_message 外,仓库还提供了一个压测脚本 test_throughput.py,同样通过 Controller 地址 + 模型名工作,可在集群拉通后评估吞吐。
七、实战要点小结
- 端口规划先行:文档中每台机器的 Worker 都从 31000 起按卡号递增分配端口,且
--port必须与--worker-address中的端口一致,否则 Controller 转发会失败; --worker-address必须跨机可达:这是集群部署和单机部署最大的区别。$(hostname)要求集群内各节点的主机名互可解析(/etc/hosts 或 DNS),Controller 所在节点(node-01:10002)必须能反向访问每个 Worker;- 心跳参数按需调整:Worker 每 45 秒心跳、90 秒过期(constants.py),均可用
FASTCHAT_WORKER_HEART_BEAT_INTERVAL/FASTCHAT_CONTROLLER_HEART_BEAT_EXPIRATION环境变量覆盖; - 卡分配策略:小模型用
CUDA_VISIBLE_DEVICES单卡钉死 + 多副本扩容;超大模型用--num-gpus张量并行(vLLM 场景需先ray start --head);小模型群用multi_model_worker多模型共卡并整卡限流(--limit); - 模型名是路由的唯一键:客户端(Gradio 服务、OpenAI API Server、
test_message等)全部通过--model-name注册名请求模型,部署时应保证命名清晰且不与model-path尾段产生歧义。
以上命令与参数均可在当前仓库中逐一对应到源码实现:集群路由与心跳见 controller.py,Worker 注册与限流基类见 base_model_worker.py,vLLM 多卡映射见 vllm_worker.py,多模型共卡见 multi_model_worker.py,端到端验证见 test_message.py。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
