首页
/ FastChat 本地 GPU 集群部署指南:Controller-Worker 架构下的多机多卡 LLM 推理实践

FastChat 本地 GPU 集群部署指南:Controller-Worker 架构下的多机多卡 LLM 推理实践

2026-09-05 12:31:31作者:鲍丁臣Ursa

本文围绕 FastChat 仓库中的 本地集群部署文档 展开,讲解如何用“一个 Controller + 多台 GPU 机器上的 Worker”搭建跨节点的大语言模型推理集群,覆盖 vLLM Worker、多卡张量并行、multi_model_worker 多模型共卡等典型部署形态;读完后你可以直接按节点照抄命令拉起集群,并理解每个参数的源码级含义与验证方法。

FastChat Server Architecture

一、集群架构:单点 Controller,水平扩展 Worker

FastChat 的服务器架构是一个典型的去中心化推理 + 中心化路由结构(完整架构图见 docs/server_arch.md 及上图):

  • Controller(控制器):集群唯一的协调节点,维护“模型名 → Worker 地址”的映射表,负责 Worker 注册、心跳探活、请求路由。它本身不执行推理。实现位于 controller.py,核心类 Controller 内部用 worker_info 字典保存每个 Worker 的 model_namesspeedqueue_lengthlast_heart_beat 等元信息(见 controller.pyWorkerInfo)。
  • Model Worker(推理节点):每台 GPU 机器上可运行任意多个 Worker 进程,每个 Worker 独占一张或多张卡加载一个(或一组)模型,启动后向 Controller 注册自己的地址和模型列表,并周期性发送心跳。

从源码结构看,整个集群的协作协议就是三组 HTTP 接口:

  1. 注册:Worker 启动后 POST 到 Controller 的 /register_worker,提交自己的 worker_name(即对外可达地址)、check_heart_beat 标志和当前状态(模型名、速度、队列长度);
  2. 心跳:Worker 每隔 WORKER_HEART_BEAT_INTERVAL 秒 POST /receive_heart_beat,Controller 端则每 CONTROLLER_HEART_BEAT_EXPIRATION 秒扫描一次,把超时未上报的 Worker 从表中摘除(controller.pyremove_stale_workers_by_expiration);
  3. 路由:客户端请求 /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.pyDispatchMethod,可通过 --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.pymulti_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-13b Worker(端口 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_sizevllm_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.pyself.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 的实现看,这条命令完整走了一遍集群协议,是很好的端到端自检:

  1. 先向 Controller 发 /refresh_all_workers,强制重新探测所有 Worker 存活状态;
  2. 再发 /list_models,打印当前集群注册的全部模型名;
  3. 通过 /get_worker_address 按模型名路由到一个 Worker;
  4. 用该模型对应的对话模板(conversation template)包装一条默认提示词,POST 到 Worker 的 /worker_generate_stream,流式打印回复。

如果命令打印出模型列表和流式回复,说明注册、心跳、路由、推理四段链路全部打通;如果输出 No available workers for <model>,则通常是该模型的 Worker 尚未注册成功,或心跳被 Controller 判死(对照 controller.logmodel_worker_*.log 排查)。除 test_message 外,仓库还提供了一个压测脚本 test_throughput.py,同样通过 Controller 地址 + 模型名工作,可在集群拉通后评估吞吐。

七、实战要点小结

  1. 端口规划先行:文档中每台机器的 Worker 都从 31000 起按卡号递增分配端口,且 --port 必须与 --worker-address 中的端口一致,否则 Controller 转发会失败;
  2. --worker-address 必须跨机可达:这是集群部署和单机部署最大的区别。$(hostname) 要求集群内各节点的主机名互可解析(/etc/hosts 或 DNS),Controller 所在节点(node-01:10002)必须能反向访问每个 Worker;
  3. 心跳参数按需调整:Worker 每 45 秒心跳、90 秒过期(constants.py),均可用 FASTCHAT_WORKER_HEART_BEAT_INTERVAL / FASTCHAT_CONTROLLER_HEART_BEAT_EXPIRATION 环境变量覆盖;
  4. 卡分配策略:小模型用 CUDA_VISIBLE_DEVICES 单卡钉死 + 多副本扩容;超大模型用 --num-gpus 张量并行(vLLM 场景需先 ray start --head);小模型群用 multi_model_worker 多模型共卡并整卡限流(--limit);
  5. 模型名是路由的唯一键:客户端(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

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384