第6篇:分布式&对话服务部署——FastChat 部署 OpenAI 兼容接口

本文为「本地大模型部署系列」第6篇,完整系列持续更新中。


前言

如果你已经用 Ollama 跑通了单模型本地服务,或者用 llama.cpp 实现了轻量推理,那么必然会遇到下一个工程挑战:同时管理多个模型、对接已有 OpenAI 格式的项目、给团队提供统一 API 入口

这篇文章解决的正是这三个问题。

FastChat 是由 UC Berkeley LMSys 团队开源的多模型服务框架,它的核心能力是:

  • 把多个本地模型统一包装成 OpenAI 兼容接口/v1/chat/completions/v1/models
  • 三组件架构支持横向扩展,一个 Controller 调度多个 Model Worker
  • 任何使用 openai Python 库的项目,只改一行 base_url 即可切换到本地模型

适用场景:团队共用模型服务、多模型 A/B 测试、将个人项目从 OpenAI API 迁移到本地、构建私有化 LLM 中间层。

本篇学完你将掌握

  • FastChat 三组件架构原理与请求流转链路
  • 从零启动完整的 FastChat 服务栈(含多模型)
  • OpenAI Python SDK 直接对接本地 FastChat 服务
  • 流式输出 + 多轮对话完整实现
  • 高频踩坑与排查方案

一、核心原理极简讲解

1.1 FastChat 的定位

市面上本地部署框架各有侧重:

框架 核心定位 适合场景
Ollama 单模型极简一键部署 个人快速测试、日常使用
llama.cpp 极致轻量、CPU 推理 低配机器、边缘设备
FastChat 多模型服务编排 + OpenAI 兼容接口 团队服务、多模型管理、项目对接
vLLM 高吞吐生产级推理 高并发、企业生产环境

FastChat 的差异化在于:它不是一个推理引擎,而是一个模型服务编排层。它把调度、推理、接口这三件事彻底解耦,每一层都可以独立扩展替换。

1.2 三组件架构深度解析

FastChat 采用经典的三层服务架构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────────────┐
│ FastChat 服务架构 │
│ │
│ ┌─────────────┐ ┌─────────────────────────────────┐ │
│ │ Client │────▶│ OpenAI API Server │ │
│ │(curl/Python)│ │ (Port: 8000) │ │
│ └─────────────┘ │ /v1/models │ │
│ │ /v1/chat/completions │ │
│ └────────────┬────────────────────┘ │
│ │ HTTP 转发 │
│ ┌────────────▼────────────────────┐ │
│ │ Controller │ │
│ │ (Port: 21001) │ │
│ │ 路由 & 负载均衡 │ │
│ └──┬──────────────────┬───────────┘ │
│ │ │ │
│ ┌──────────▼────┐ ┌──────────▼────┐ │
│ │ Model Worker │ │ Model Worker │ ... │
│ │ (Port:21002) │ │ (Port:21003) │ │
│ │ Qwen2.5-7B │ │ Llama3-8B │ │
│ └───────────────┘ └───────────────┘ │
└─────────────────────────────────────────────────────────────┘

Controller(调度中心)

  • 职责:维护所有已注册 Worker 的心跳列表,根据调度策略(轮询/最短队列)将请求路由到合适的 Worker
  • 特点:无状态轻量进程,不参与任何模型计算,仅做服务发现和路由
  • 关键配置:--dispatch-methodlottery 随机轮询 / shortest_queue 最短队列优先)

Model Worker(模型推理进程)

  • 职责:加载并持有一个具体的模型权重,执行实际的前向推理计算
  • 特点:一个 Worker 进程 = 一个模型实例;多 GPU 可通过 --num-gpus 配置张量并行
  • 启动后自动向 Controller 注册心跳,断线自动重连

OpenAI API Server(接口层)

  • 职责:对外暴露标准 OpenAI REST API,将外部请求翻译成内部 FastChat 协议后转发给 Controller
  • 特点:完全兼容 OpenAI API 规范,/v1/chat/completions/v1/models/v1/embeddings 均支持

1.3 请求完整流转链路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Client 发送请求


OpenAI API Server(Port 8000)
├── 解析 OpenAI 格式请求
├── 提取 model 字段(如 "qwen2.5-7b")
└── 转发到 Controller


Controller(Port 21001)
├── 查找持有该 model_name 的 Worker 列表
├── 根据 dispatch_method 选择一个 Worker
└── 返回该 Worker 地址给 API Server


Model Worker(Port 21002)
├── 执行模型推理(流式 / 非流式)
└── 返回生成结果


OpenAI API Server
└── 封装为 OpenAI 格式响应返回给 Client

1.4 为什么这个架构适合多模型场景

  • 松耦合:三个组件可独立重启,Worker 挂掉不影响 Controller 和其他 Worker
  • 横向扩展:同一个模型可以启动多个 Worker 实例(不同 GPU 或不同机器),Controller 自动负载均衡
  • 模型隔离:不同模型各自占用独立进程和显存,互不干扰
  • 接口统一:无论后端挂了多少模型,对外始终是同一个 OpenAI 格式 API

二、环境 & 前置依赖

2.1 硬件要求

配置项 最低要求 推荐配置
GPU 显存 8GB(单 7B 模型) 24GB+(多模型并发)
系统内存 16GB 32GB+
操作系统 Linux(推荐)/ Windows(有额外注意事项)
CUDA 版本 11.8+ 12.1+

多模型显存估算:几个 Worker 同时运行就需要叠加几块显存。7B FP16 约需 14GB,量化到 4-bit 约需 4-5GB。务必规划好显存分配再启动多个 Worker。

2.2 软件依赖

1
2
3
4
5
6
7
8
# Python 版本
Python 3.10+

# PyTorch(根据 CUDA 版本选择,参见本系列第2篇)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121

# 验证 GPU 可用
python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"

2.3 安装 FastChat

方式一:pip 直接安装(推荐,快速)

1
pip install "fschat[model_worker,webui]"

注意:包名是 fschat,不是 fastchat,这是一个高频踩坑点。

方式二:源码安装(功能最全,可修改源码)

1
2
3
git clone https://github.com/lm-sys/FastChat.git
cd FastChat
pip install -e ".[model_worker,webui]"

验证安装

1
python -m fastchat.serve.controller --help

出现参数帮助信息即表示安装成功。


三、Step-by-Step 实操流程

3.1 准备工作:目录与模型

本篇以 Qwen2.5-7B-InstructLlama-3.1-8B-Instruct 为例(模型下载参见本系列第2篇)。

假设模型存放路径:

1
2
3
~/models/
├── Qwen2.5-7B-Instruct/
└── Llama-3.1-8B-Instruct/

3.2 启动 Controller

Controller 是整个服务栈的核心枢纽,必须第一个启动

1
2
3
4
5
6
7
8
9
10
11
12
# 基础启动命令
python -m fastchat.serve.controller \
--host 0.0.0.0 \
--port 21001 \
--dispatch-method shortest_queue

# 后台运行(Linux)
nohup python -m fastchat.serve.controller \
--host 0.0.0.0 \
--port 21001 \
--dispatch-method shortest_queue \
> logs/controller.log 2>&1 &

确认启动成功

1
2
3
4
5
6
7
# 查看日志(看到以下输出表示正常)
# INFO: Application startup complete.
# INFO: Uvicorn running on http://0.0.0.0:21001

# 或者直接访问健康检查接口
curl http://localhost:21001/list_models
# 返回 {"models": []} 表示 Controller 运行正常(此时还没有 Worker 注册)

3.3 启动 Model Worker(以 Qwen2.5-7B 为例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 单 GPU 启动
python -m fastchat.serve.model_worker \
--host 0.0.0.0 \
--port 21002 \
--worker-address http://localhost:21002 \
--controller-address http://localhost:21001 \
--model-path ~/models/Qwen2.5-7B-Instruct \
--model-names qwen2.5-7b,qwen2.5 \
--num-gpus 1

# 4-bit 量化加载(显存不足时)
python -m fastchat.serve.model_worker \
--host 0.0.0.0 \
--port 21002 \
--worker-address http://localhost:21002 \
--controller-address http://localhost:21001 \
--model-path ~/models/Qwen2.5-7B-Instruct \
--model-names qwen2.5-7b \
--num-gpus 1 \
--load-4bit

日志确认

1
2
3
# Worker 启动后会打印注册信息:
# INFO | model_worker.py | Register to controller
# INFO | model_worker.py | Send heart beat. Models: ['qwen2.5-7b', 'qwen2.5'].

验证 Worker 已注册到 Controller

1
2
curl http://localhost:21001/list_models
# 返回:{"models": ["qwen2.5-7b", "qwen2.5"]}

3.4 启动 OpenAI API Server

1
2
3
4
5
python -m fastchat.serve.openai_api_server \
--host 0.0.0.0 \
--port 8000 \
--controller-address http://localhost:21001 \
--api-keys sk-your-custom-key-optional

--api-keys 是可选的,设置后调用方需要在请求头中携带 Authorization: Bearer sk-your-custom-key-optional,适合内网鉴权。不设置则任何请求都可通过。

3.5 验证服务(curl 测试)

查询已加载模型列表

1
2
3
4
5
6
7
8
9
10
11
curl http://localhost:8000/v1/models \
-H "Content-Type: application/json"

# 期望输出:
# {
# "object": "list",
# "data": [
# {"id": "qwen2.5-7b", "object": "model", ...},
# {"id": "qwen2.5", "object": "model", ...}
# ]
# }

发送对话请求

1
2
3
4
5
6
7
8
9
10
11
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2.5-7b",
"messages": [
{"role": "system", "content": "你是一个专业的AI助手。"},
{"role": "user", "content": "用一句话解释什么是大模型。"}
],
"temperature": 0.7,
"max_tokens": 200
}'

3.6 多模型同时挂载

这是 FastChat 的核心能力。启动第二个 Worker,挂载不同模型,使用不同端口。

1
2
3
4
5
6
7
8
9
# Worker 2:加载 Llama 3.1 8B(端口 21003)
python -m fastchat.serve.model_worker \
--host 0.0.0.0 \
--port 21003 \
--worker-address http://localhost:21003 \
--controller-address http://localhost:21001 \
--model-path ~/models/Llama-3.1-8B-Instruct \
--model-names llama3-8b,llama3 \
--num-gpus 1

端口分配规范(推荐约定)

组件 端口 说明
Controller 21001 固定,所有 Worker 注册目标
Model Worker 1 21002 Qwen2.5-7B
Model Worker 2 21003 Llama3-8B
Model Worker 3 21004 其他模型
OpenAI API Server 8000 对外统一入口

两个 Worker 启动后,验证:

1
2
curl http://localhost:8000/v1/models
# data 列表中应同时出现 qwen2.5-7b 和 llama3-8b

切换模型只需修改请求中的 model 字段:

1
2
3
4
# 调用 Llama3
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "llama3-8b", "messages": [{"role": "user", "content": "Hello!"}]}'

3.7 Python 客户端调用

这是 FastChat 最大的工程价值:任何基于 openai 库的项目,改一行 base_url 即可切换到本地模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from openai import OpenAI

# 原来对接 OpenAI 官方
# client = OpenAI(api_key="sk-xxx")

# 改成本地 FastChat(只改这一行!)
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="sk-your-custom-key-optional" # 没有设置 --api-keys 时填任意字符串
)

# 调用方式完全一致
response = client.chat.completions.create(
model="qwen2.5-7b",
messages=[
{"role": "system", "content": "你是一个专业的AI助手。"},
{"role": "user", "content": "FastChat 和 vLLM 的区别是什么?"}
],
temperature=0.7,
max_tokens=500
)

print(response.choices[0].message.content)

3.8 流式输出实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from openai import OpenAI

client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="none"
)

def chat_stream(model: str, messages: list, **kwargs):
"""
流式对话,实时打印生成内容
"""
try:
stream = client.chat.completions.create(
model=model,
messages=messages,
stream=True, # 开启流式
temperature=kwargs.get("temperature", 0.7),
max_tokens=kwargs.get("max_tokens", 1024),
top_p=kwargs.get("top_p", 0.9),
)

full_response = ""
for chunk in stream:
# 流式返回的每个 chunk 结构与 OpenAI 完全一致
if chunk.choices[0].delta.content is not None:
content = chunk.choices[0].delta.content
print(content, end="", flush=True)
full_response += content

print() # 换行
return full_response

except Exception as e:
print(f"流式请求失败: {e}")
raise


# 使用示例
messages = [
{"role": "system", "content": "你是一个专业的Python工程师。"},
{"role": "user", "content": "写一个简单的装饰器示例,并解释原理。"}
]

response = chat_stream("qwen2.5-7b", messages)

3.9 多轮对话实现

多轮对话的核心是在客户端维护 messages 列表,每次请求都带上完整的历史上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
from openai import OpenAI
from typing import List, Dict, Optional

client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="none"
)


class ConversationManager:
"""
多轮对话管理器
支持对话记忆、上下文截断、会话重置
"""

def __init__(
self,
model: str = "qwen2.5-7b",
system_prompt: str = "你是一个专业的AI助手。",
max_history_turns: int = 10, # 保留最近 N 轮对话
):
self.model = model
self.max_history_turns = max_history_turns
self.messages: List[Dict] = [
{"role": "system", "content": system_prompt}
]

def _truncate_history(self):
"""保留 system prompt + 最近 N 轮(每轮包含 user + assistant 两条)"""
system_msg = self.messages[0]
history = self.messages[1:]

# 每轮 = user + assistant = 2 条,保留最近 N 轮
max_msgs = self.max_history_turns * 2
if len(history) > max_msgs:
history = history[-max_msgs:]

self.messages = [system_msg] + history

def chat(self, user_input: str, stream: bool = True) -> str:
"""发送消息并获取回复"""
# 添加用户消息
self.messages.append({"role": "user", "content": user_input})

try:
if stream:
response_content = self._stream_response()
else:
response = client.chat.completions.create(
model=self.model,
messages=self.messages,
temperature=0.7,
max_tokens=1024
)
response_content = response.choices[0].message.content

# 将 assistant 回复追加到历史
self.messages.append({
"role": "assistant",
"content": response_content
})

# 截断超长历史
self._truncate_history()

return response_content

except Exception as e:
# 请求失败时移除刚添加的用户消息,避免污染历史
self.messages.pop()
raise RuntimeError(f"对话请求失败: {e}")

def _stream_response(self) -> str:
"""流式响应内部实现"""
stream = client.chat.completions.create(
model=self.model,
messages=self.messages,
stream=True,
temperature=0.7,
max_tokens=1024
)

full_content = ""
for chunk in stream:
if chunk.choices[0].delta.content is not None:
content = chunk.choices[0].delta.content
print(content, end="", flush=True)
full_content += content
print()
return full_content

def reset(self, new_system_prompt: Optional[str] = None):
"""重置会话(保留 system prompt)"""
system_content = new_system_prompt or self.messages[0]["content"]
self.messages = [{"role": "system", "content": system_content}]
print("会话已重置。")

def show_history(self):
"""打印当前对话历史"""
for msg in self.messages:
role = msg["role"].upper()
content = msg["content"][:100] + "..." if len(msg["content"]) > 100 else msg["content"]
print(f"[{role}]: {content}")


# 使用示例
def main():
conv = ConversationManager(
model="qwen2.5-7b",
system_prompt="你是一个专业的Python工程师,回答简洁实用。",
max_history_turns=8
)

print("多轮对话启动,输入 'quit' 退出,'reset' 重置会话,'history' 查看历史\n")

while True:
user_input = input("你: ").strip()

if user_input.lower() == "quit":
break
elif user_input.lower() == "reset":
conv.reset()
continue
elif user_input.lower() == "history":
conv.show_history()
continue
elif not user_input:
continue

print("AI: ", end="")
try:
conv.chat(user_input)
except RuntimeError as e:
print(f"错误: {e}")

print("对话结束。")


if __name__ == "__main__":
main()

3.10 使用 vLLM 作为 Worker 后端

FastChat 支持将 vLLM 作为 Worker 的推理后端,大幅提升高并发吞吐量:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 先安装 vLLM
pip install vllm

# 使用 vLLM Worker 替代默认 Worker
python -m fastchat.serve.vllm_worker \
--host 0.0.0.0 \
--port 21002 \
--worker-address http://localhost:21002 \
--controller-address http://localhost:21001 \
--model-path ~/models/Qwen2.5-7B-Instruct \
--model-names qwen2.5-7b \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.85

vLLM Worker 与默认 Worker 对 Controller 和 API Server 完全透明,切换后调用方无需任何改动。vLLM 的 PagedAttention 技术在高并发场景下吞吐量比默认 Worker 高 5-10 倍(详见本系列第8篇)。


四、完整三组件启动命令模板(一键可用)

将以下内容保存为 start_fastchat.sh(Linux)或 start_fastchat.ps1(Windows):

Linux/macOS 启动脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#!/bin/bash

# ============ 配置区(按需修改)============
MODEL1_PATH="$HOME/models/Qwen2.5-7B-Instruct"
MODEL1_NAME="qwen2.5-7b"
MODEL2_PATH="$HOME/models/Llama-3.1-8B-Instruct"
MODEL2_NAME="llama3-8b"
LOG_DIR="./logs"
# ==========================================

mkdir -p $LOG_DIR

echo "[1/4] 启动 Controller..."
nohup python -m fastchat.serve.controller \
--host 0.0.0.0 \
--port 21001 \
--dispatch-method shortest_queue \
> $LOG_DIR/controller.log 2>&1 &

echo "等待 Controller 启动..."
sleep 3

echo "[2/4] 启动 Model Worker 1($MODEL1_NAME)..."
nohup python -m fastchat.serve.model_worker \
--host 0.0.0.0 \
--port 21002 \
--worker-address http://localhost:21002 \
--controller-address http://localhost:21001 \
--model-path $MODEL1_PATH \
--model-names $MODEL1_NAME \
--num-gpus 1 \
> $LOG_DIR/worker1.log 2>&1 &

echo "[3/4] 启动 Model Worker 2($MODEL2_NAME)..."
nohup python -m fastchat.serve.model_worker \
--host 0.0.0.0 \
--port 21003 \
--worker-address http://localhost:21003 \
--controller-address http://localhost:21001 \
--model-path $MODEL2_PATH \
--model-names $MODEL2_NAME \
--num-gpus 1 \
> $LOG_DIR/worker2.log 2>&1 &

echo "等待模型加载(约 60 秒)..."
sleep 60

echo "[4/4] 启动 OpenAI API Server..."
nohup python -m fastchat.serve.openai_api_server \
--host 0.0.0.0 \
--port 8000 \
--controller-address http://localhost:21001 \
> $LOG_DIR/api_server.log 2>&1 &

echo "等待 API Server 启动..."
sleep 5

echo ""
echo "=== FastChat 服务启动完成 ==="
echo "API 地址:http://localhost:8000"
curl -s http://localhost:8000/v1/models | python3 -m json.tool

Windows PowerShell 启动脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# start_fastchat.ps1

# ============ 配置区(按需修改)============
$MODEL1_PATH = "D:\models\Qwen2.5-7B-Instruct"
$MODEL1_NAME = "qwen2.5-7b"
$MODEL2_PATH = "D:\models\Llama-3.1-8B-Instruct"
$MODEL2_NAME = "llama3-8b"
$LOG_DIR = ".\logs"
# ==========================================

New-Item -ItemType Directory -Force -Path $LOG_DIR | Out-Null

Write-Host "[1/4] 启动 Controller..."
Start-Process python -ArgumentList "-m fastchat.serve.controller --host 0.0.0.0 --port 21001 --dispatch-method shortest_queue" -RedirectStandardOutput "$LOG_DIR\controller.log" -WindowStyle Hidden

Start-Sleep -Seconds 3

Write-Host "[2/4] 启动 Model Worker 1..."
Start-Process python -ArgumentList "-m fastchat.serve.model_worker --host 0.0.0.0 --port 21002 --worker-address http://localhost:21002 --controller-address http://localhost:21001 --model-path `"$MODEL1_PATH`" --model-names $MODEL1_NAME --num-gpus 1" -RedirectStandardOutput "$LOG_DIR\worker1.log" -WindowStyle Hidden

Write-Host "[3/4] 启动 Model Worker 2..."
Start-Process python -ArgumentList "-m fastchat.serve.model_worker --host 0.0.0.0 --port 21003 --worker-address http://localhost:21003 --controller-address http://localhost:21001 --model-path `"$MODEL2_PATH`" --model-names $MODEL2_NAME --num-gpus 1" -RedirectStandardOutput "$LOG_DIR\worker2.log" -WindowStyle Hidden

Write-Host "等待模型加载(约 60 秒)..."
Start-Sleep -Seconds 60

Write-Host "[4/4] 启动 OpenAI API Server..."
Start-Process python -ArgumentList "-m fastchat.serve.openai_api_server --host 0.0.0.0 --port 8000 --controller-address http://localhost:21001" -RedirectStandardOutput "$LOG_DIR\api_server.log" -WindowStyle Hidden

Start-Sleep -Seconds 5
Write-Host "=== FastChat 服务启动完成,API 地址:http://localhost:8000 ==="

五、关键参数详解

5.1 Controller 参数

参数 默认值 说明
--host localhost 监听地址,对外服务需改为 0.0.0.0
--port 21001 Controller 监听端口
--dispatch-method lottery 调度策略:lottery(随机轮询)/ shortest_queue(最短队列,高并发推荐)

5.2 Model Worker 参数

参数 默认值 说明
--model-path 必填 模型本地路径或 HuggingFace 模型名
--model-names 取目录名 对外暴露的模型名称,支持逗号分隔多个别名(如 qwen2.5-7b,qwen
--num-gpus 1 使用 GPU 数量,多 GPU 启用张量并行
--max-gpu-memory 自动 每块 GPU 最大显存(如 12GiB),用于多 Worker 显存分配
--load-8bit False 开启 8-bit 量化加载(显存减半,速度略降)
--load-4bit False 开启 4-bit 量化加载(显存约减 75%,精度有损)
--worker-address 必填 Worker 自身可被 Controller 访问的地址
--controller-address http://localhost:21001 Controller 地址
--device cuda 推理设备:cuda / cpu / mps(Apple Silicon)

重要--model-names--model-path 的区别:--model-path 是磁盘路径,--model-names 是 API 请求中 model 字段使用的名称。两者完全独立,请求时用 --model-names 中定义的名称。

5.3 OpenAI API Server 参数

参数 默认值 说明
--host localhost 监听地址
--port 8000 API Server 端口
--controller-address http://localhost:21001 Controller 地址
--api-keys 无(不鉴权) 设置后请求头需携带 Authorization: Bearer <key>
--allowed-origins * CORS 跨域配置,前端直连时可按需限制

5.4 推理参数透传(请求体参数)

参数 说明 推荐范围
temperature 控制生成随机性,0 为确定性输出 0.1~1.0,代码生成建议 0.1~0.3
max_tokens 最大生成 token 数 按任务需求,默认 512
top_p 核采样概率阈值 0.85~0.95
top_k 候选 token 数量限制 40~50
stop 停止词列表 如 `[“<
presence_penalty 重复惩罚(存在性) -2.0~2.0
frequency_penalty 重复惩罚(频率) -2.0~2.0

六、踩坑记录 & 问题解决

坑1:三组件启动顺序错误

现象:Worker 启动后报 Failed to register to controller,或 API Server 请求返回 No available workers

原因:未按 Controller → Worker → API Server 的顺序启动。

解决:严格按顺序启动,Controller 完全就绪(能响应 /list_models)后再启动 Worker,Worker 日志显示心跳发送成功后再启动 API Server。

1
2
3
# 检查 Controller 是否就绪
curl http://localhost:21001/list_models
# 必须返回 {"models": []} 才能继续启动 Worker

坑2:Worker 注册到 Controller 失败

现象:Worker 日志中循环出现 Send heart beat. Failed to connect to controller.

排查步骤

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 确认 Controller 进程在运行
ps aux | grep controller # Linux
Get-Process python # Windows

# 2. 确认端口可达
curl http://localhost:21001/list_models

# 3. 确认 Worker 的 --controller-address 参数正确
# 如果是跨机器部署,localhost 要换成 Controller 机器的 IP

# 4. 防火墙检查(跨机器时)
telnet <controller-ip> 21001

坑3:多模型时显存分配冲突 (CUDA OOM)

现象:启动第二个 Worker 时报 CUDA out of memory

解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 方案一:显式限制每个 Worker 的最大显存
python -m fastchat.serve.model_worker \
--model-path ~/models/Qwen2.5-7B-Instruct \
--model-names qwen2.5-7b \
--num-gpus 1 \
--max-gpu-memory 10GiB # 明确限制上限

# 方案二:使用 4-bit 量化大幅降低显存占用
--load-4bit

# 方案三:多 GPU 机器上分配不同 GPU 给不同 Worker
CUDA_VISIBLE_DEVICES=0 python -m fastchat.serve.model_worker \
--model-path ~/models/Qwen2.5-7B \
--model-names qwen2.5-7b ...

CUDA_VISIBLE_DEVICES=1 python -m fastchat.serve.model_worker \
--model-path ~/models/Llama-3.1-8B \
--model-names llama3-8b ...

坑4:流式输出中断 / 超时

现象:流式请求在中途断开,或客户端长时间等待后超时。

解决

1
2
3
4
5
6
7
8
9
10
11
# 1. 客户端设置更长的超时
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="none",
timeout=300.0 # 5 分钟超时
)

# 2. 检查生成长度,避免 max_tokens 设置过大
# 3. 如果使用 Nginx 等反向代理,需要配置:
# proxy_read_timeout 300;
# proxy_buffering off;

坑5:--model-names 的坑(最高频踩坑)

现象:API 请求报 The model 'xxx' does not exist,但 /v1/models 接口能看到模型。

原因:请求中的 model 字段和 Worker 的 --model-names 大小写或拼写不一致。

1
2
3
4
5
6
7
8
# Worker 启动时
--model-names Qwen2.5-7B-Instruct

# 请求时
"model": "qwen2.5-7b" # 不匹配!大小写不一致

# 正确做法:统一使用小写短名,在 --model-names 中明确定义
--model-names qwen2.5-7b,qwen2.5

建议:在 Worker 启动时通过 --model-names 明确指定简洁的模型别名,避免使用目录原始名称(往往含大写和版本号)。

坑6:Windows 下多进程启动注意事项

现象:Windows 上 Model Worker 启动时报 RuntimeError: CUDA error: invalid device ordinal 或多进程冲突。

原因:Windows 下 Python 多进程(multiprocessing)的 fork 行为与 Linux 不同。

解决

1
2
3
4
5
# 在 Windows 上,如果写了启动脚本调用 FastChat,
# 确保主程序入口有 if __name__ == "__main__": 保护

# 对于 PowerShell 脚本启动多个 Worker,使用 Start-Process
# 每个 Worker 在独立的 Windows 进程中运行,避免用 Python 多线程调用

另外,Windows 下建议优先使用 --device cpu 先验证服务流程是否正常,再切换到 GPU。


七、场景适配 & 优劣分析

7.1 FastChat 适合的场景

  • 多模型服务管理:团队有多个本地模型需要对外提供服务,统一由 FastChat 管理,API 入口唯一
  • OpenAI 接口替换:现有项目已使用 OpenAI SDK,迁移成本极低(只改 base_url
  • 团队内部模型服务:搭建内网私有 LLM 服务,多人共用,Controller 统一调度
  • 多模型 A/B 测试:同时挂载多个版本模型,通过修改请求 model 字段切换
  • 模型热切换:可以动态添加/移除 Worker 而不影响 API Server

7.2 FastChat 不适合的场景

  • 极致高并发生产环境:默认 Worker 的并发性能不如 vLLM(PagedAttention 优势明显)
  • 个人快速测试:三组件启动流程相对繁琐,不如 Ollama 一条命令简单
  • 资源非常有限的机器:三个进程同时运行有一定系统开销

7.3 与主流框架的定位对比

维度 Ollama llama.cpp FastChat vLLM
上手难度 ⭐ 极简 ⭐⭐ 需编译 ⭐⭐⭐ 三组件 ⭐⭐⭐ 中等
多模型支持 支持 手动管理 原生多模型 支持
OpenAI 兼容 支持 部分 完全兼容 完全兼容
推理性能 中等 中等(CPU优) 中等 最高(PagedAttention)
适合并发 低并发 低并发 中并发 高并发
主要场景 个人测试 低配机器 多模型服务/团队 生产高并发
横向扩展 有限 手动 Worker 级扩展 单节点优化

工程决策树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
需要本地大模型服务?

├── 个人使用、快速测试?
│ └── → Ollama(最简单)

├── 低配机器 / 无 GPU?
│ └── → llama.cpp(CPU 友好)

├── 多个模型需要统一管理 / 对接已有 OpenAI 项目?
│ └── → FastChat(本篇方案)

└── 高并发生产环境、性能优先?
└── → vLLM(第8篇方案)
或 FastChat + vLLM Worker(两者结合)

八、本篇小结

核心知识点复盘

  1. FastChat 三组件架构:Controller(调度)+ Model Worker(推理)+ OpenAI API Server(接口),三层解耦,各司其职
  2. 请求链路:Client → API Server → Controller(选 Worker)→ Worker → 返回结果
  3. 多模型管理:一个 Controller 对应多个 Worker,每个 Worker 独立进程,不同端口
  4. OpenAI 兼容:只改 base_url,现有代码零改动迁移到本地

启动命令速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1. 启动 Controller
python -m fastchat.serve.controller --host 0.0.0.0 --port 21001 --dispatch-method shortest_queue

# 2. 启动 Model Worker(修改 --model-path 和 --model-names)
python -m fastchat.serve.model_worker \
--port 21002 --worker-address http://localhost:21002 \
--controller-address http://localhost:21001 \
--model-path /path/to/model --model-names my-model --num-gpus 1

# 3. 启动 API Server
python -m fastchat.serve.openai_api_server --host 0.0.0.0 --port 8000 \
--controller-address http://localhost:21001

# 4. 验证
curl http://localhost:8000/v1/models

架构要点速记

1
2
3
4
5
启动顺序:Controller → Worker(s) → API Server
端口约定:Controller:21001 | Worker:21002+ | API:8000
模型名称:由 --model-names 决定,与目录名无关
显存规划:多 Worker = 显存叠加,用 --max-gpu-memory 或 CUDA_VISIBLE_DEVICES 隔离
性能升级:将 model_worker 替换为 vllm_worker 无感提升

本文为「本地大模型部署系列」第6篇,完整系列持续更新中。