引言
在当今人工智能快速发展的时代,大语言模型(Large Language Model, LLM)已成为自然语言处理领域的核心技术。然而,如何高效地部署、管理和服务这些动辄数十亿参数的模型,成为工程实践中的重要挑战。
FastChat是由LMSYS组织开发的开源平台,**专注于大语言模型的训练、服务和评估。**该平台不仅为Chatbot Arena提供支持,已服务超过1000万次聊天请求,支持70多个大语言模型,更重要的是,它提供了一套完整的分布式模型服务解决方案。
内容概览
架构设计层面
- 三层分布式架构:Controller(控制器)、Worker(工作节点)、Server(服务器)的职责分离与协作机制
- 控制平面与数据平面分离:控制流(注册、心跳、调度)与数据流(推理请求、结果返回)的解耦设计
Controller控制器
- 核心职责:
- 智能请求调度与负载均衡
- 负载均衡策略:
- Lottery(抽奖)策略:基于Worker性能权重的加权随机选择算法
- Shortest Queue(最短队列)策略:JSQ算法实现,考虑队列长度和处理速度的综合调度
- 心跳与容错机制:
- 定期心跳检测与自动故障摘除
Worker工作节点
- 模型加载与优化:
- 支持多种模型格式(LLaMA、Vicuna、ChatGLM等)
- 量化技术集成(8-bit、GPTQ、AWQ)
- 多GPU并行策略(模型并行、张量并行)
- GPU内存管理与CPU Offloading
- 推理计算能力:
- 流式文本生成:生成器模式实现低延迟响应
- 嵌入向量计算:支持多种Encoder模型的批量处理
Server服务器组件
- OpenAI API兼容层
- 请求处理流程:
- 参数验证与规范化处理
- 从Controller获取可用Worker地址
- 上下文长度检查与token计数
- 流式响应生成与SSE协议实现
- 对话模板系统:
- 支持多种模型的Conversation格式适配
- 错误处理机制
组件间通信与协调
- 服务发现机制:
- Worker主动注册流程
- Controller动态Worker列表维护
- 心跳协议设计:
- Worker定期心跳发送(携带队列状态)
- Controller被动检查与主动清理
- 断线重连与自动重注册机制
- 数据传输协议:
- 分隔符(
\0
)分割的流式JSON传输 - 异步生成器(AsyncGenerator)实现
- 分隔符(
实际部署场景
- 部署模式:
- 单机模式:适合开发测试的快速启动
- 多Worker模式:同一模型的高吞吐部署
- 混合部署:本地模型+在线API的集成方案
- 性能优化配置:
- GPU内存分配策略(
--max-gpu-memory
) - 并发控制参数(
--limit-worker-concurrency
) - 量化与压缩技术
- GPU内存分配策略(
核心架构设计
三层分布式架构
FastChat采用分布式服务架构,包含三个主要组件:Controller(控制器)、Worker(工作节点)和Server(服务器)。这种设计遵循了经典的控制平面(Control Plane)与数据平面(Data Plane)分离的架构原则。
服务器层] Server --> Controller[Controller
控制器] Controller -.注册/心跳.-> Worker1[Worker 1] Controller -.注册/心跳.-> Worker2[Worker 2] Controller -.注册/心跳.-> Worker3[Worker N] Server -.请求模型地址.-> Controller Server ==>|数据流| Worker1 Server ==>|数据流| Worker2 Server ==>|数据流| Worker3 style Server fill:#4A90E2 style Controller fill:#F5A623 style Worker1 fill:#7ED321 style Worker2 fill:#7ED321 style Worker3 fill:#7ED321 classDef controlPlane fill:#F5A623,stroke:#333,stroke-width:2px classDef dataPlane fill:#4A90E2,stroke:#333,stroke-width:2px
架构特点分析:
- 控制平面:Controller负责管理系统的整体配置、控制和状态,包括Worker注册、健康监控和任务调度
- 数据平面:Server和Worker构成数据平面,负责实际的数据处理和模型推理任务
- 职责明确:控制流和数据流分离,使得系统具有更好的可扩展性和容错能力
通信协议与接口设计
FastChat各组件间通信基于HTTP/RESTful API,采用FastAPI框架实现。这种设计带来了以下优势:
- 跨平台兼容性:HTTP协议使得组件可以部署在不同的机器和网络环境
- 易于扩展:可以轻松添加新的Worker或Server实例
- 标准化接口:便于与其他系统集成
Controller组件详解
核心功能与职责
Controller是整个FastChat架构的"大脑",承担着协调者的角色。
数据结构设计:
python
@dataclasses.dataclass
class WorkerInfo:
model_names: List[str] # 该Worker支持的模型列表
speed: int # Worker的处理速度权重
queue_length: int # 当前队列长度
check_heart_beat: bool # 是否启用心跳检测
last_heart_beat: str # 最后一次心跳时间
multimodal: bool # 是否支持多模态
这个数据结构精心设计,包含了负载均衡所需的所有关键信息。
负载均衡策略
FastChat实现了两种负载均衡策略,这是分布式系统设计中的核心问题:
1. Lottery(抽奖)策略
python
class DispatchMethod(Enum):
LOTTERY = auto()
SHORTEST_QUEUE = auto()
Lottery策略实现原理:
该策略基于加权随机选择算法。每个Worker都有一个速度权重(speed),系统根据这些权重进行概率分布:
python
worker_speeds = np.array(worker_speeds, dtype=np.float32)
norm = np.sum(worker_speeds)
worker_speeds = worker_speeds / norm # 归一化
pt = np.random.choice(np.arange(len(worker_names)), p=worker_speeds)
适用场景:
- Worker性能差异较大时
- 需要根据机器性能动态分配负载
- 对请求响应时间要求不严格的场景
2. Shortest Queue(最短队列)策略
这是更加智能的负载均衡策略,JSQ(Join-the-Shortest-Queue)策略在Web服务器集群中被广泛使用,能提供接近最优的性能。
实现细节:
python
if self.dispatch_method == DispatchMethod.SHORTEST_QUEUE:
worker_names = []
worker_qlen = []
for w_name, w_info in self.worker_info.items():
if model_name in w_info.model_names:
worker_names.append(w_name)
# 计算标准化队列长度(考虑Worker速度)
worker_qlen.append(w_info.queue_length / w_info.speed)
min_index = np.argmin(worker_qlen)
w_name = worker_names[min_index]
# 关键:预先增加队列长度,维护状态一致性
# 乐观锁式状态预占:在分配任务后、发送前就立即 queue_length += 1。避免被多个请求并发调用时候,出现"惊群效应"或"状态不一致"
self.worker_info[w_name].queue_length += 1
算法精妙之处:
- 标准化队列长度 :
queue_length / speed
考虑了Worker的处理能力 - 主动式状态管理:分配任务时立即增加队列计数,避免短时间内多个请求都选择同一Worker
- JSQ策略具有稳定性,适用于多种请求分布,且性能接近最优
性能对比表:
负载均衡策略 | 通信开销 | 响应时间 | 实现复杂度 | 适用场景 |
---|---|---|---|---|
Random | 极低 | 高 | 简单 | 轻负载 |
Lottery | 低 | 中 | 中等 | 异构环境 |
Shortest Queue | 中 | 低 | 中等 | 通用场景 |
Power-of-Two | 中 | 中低 | 中等 | 大规模分布式 |
心跳机制与故障检测
心跳机制是保证系统可靠性的关键:
python
def heart_beat_controller(controller):
while True:
time.sleep(CONTROLLER_HEART_BEAT_EXPIRATION)
controller.remove_stale_workers_by_expiration()
工作流程:
- Worker主动发送:Worker定期向Controller发送心跳,包含当前队列长度
- Controller被动检查:独立线程定期检查Worker是否过期
- 自动故障恢复:移除失效Worker,请求会自动路由到健康节点
python
def remove_stale_workers_by_expiration(self):
expire = time.time() - CONTROLLER_HEART_BEAT_EXPIRATION
to_delete = []
for worker_name, w_info in self.worker_info.items():
if w_info.check_heart_beat and w_info.last_heart_beat < expire:
to_delete.append(worker_name)
for worker_name in to_delete:
self.remove_worker(worker_name)
核心API接口
Controller提供了一系列RESTful API:
接口 | 功能 | 调用者 |
---|---|---|
/register_worker |
Worker注册 | Worker |
/receive_heart_beat |
接收心跳 | Worker |
/list_models |
列出所有模型 | Server |
/get_worker_address |
获取Worker地址 | Server |
/refresh_all_workers |
刷新Worker状态 | Server |
Worker组件详解
核心职责
Worker是执行实际模型推理的组件,负责:
- 模型加载与管理:支持多种模型格式和优化技术
- 推理计算:执行文本生成、嵌入计算等任务
- 状态维护:管理自身的队列状态并上报
模型加载机制
FastChat的模型加载设计非常灵活:
python
class ModelWorker(BaseModelWorker):
def __init__(
self,
controller_addr: str,
worker_addr: str,
worker_id: str,
model_path: str,
model_names: List[str],
num_gpus: int,
max_gpu_memory: str,
dtype: Optional[torch.dtype] = None,
load_8bit: bool = False,
# ... 更多参数
):
# 加载模型
self.model, self.tokenizer = load_model(
model_path,
device=device,
num_gpus=num_gpus,
max_gpu_memory=max_gpu_memory,
dtype=dtype,
load_8bit=load_8bit,
# ...
)
支持的优化技术:
- 量化压缩 :
- 8-bit量化 (
load_8bit
) - GPTQ 4-bit量化
- AWQ量化
- Exllama加速
- 8-bit量化 (
- 多GPU并行 :
- Model Parallelism:跨GPU分布模型层
- Tensor Parallelism:张量级别并行
- 内存优化 :
- CPU Offloading:部分权重卸载到CPU内存
- 动态内存分配:
max_gpu_memory
参数控制
推理流程
流式生成实现:
python
def generate_stream_gate(self, params):
self.call_ct += 1
try:
if self.seed is not None:
set_seed(self.seed)
for output in self.generate_stream_func(
self.model,
self.tokenizer,
params,
self.device,
self.context_len,
self.stream_interval, # 控制 每生成多少个 token 就 yield 一次。FastChat 默认设为 2 或 4,平衡延迟与性能
):
ret = {
"text": output["text"],
"error_code": 0,
}
if "usage" in output:
ret["usage"] = output["usage"]
yield json.dumps(ret).encode() + b"\0"
except torch.cuda.OutOfMemoryError as e:
ret = {
"text": f"{SERVER_ERROR_MSG}\n\n({e})",
"error_code": ErrorCode.CUDA_OUT_OF_MEMORY,
}
yield json.dumps(ret).encode() + b"\0"
关键特性:
- 流式响应:使用生成器(generator)实现,降低首token延迟
- 错误处理:捕获OOM等异常,返回友好错误信息
- 统计信息:记录token使用量、生成原因等
嵌入计算
Worker还支持生成文本嵌入向量:
python
@torch.inference_mode()
def get_embeddings(self, params):
# 批量编码
encoding = tokenizer.batch_encode_plus(
params["input"],
padding=True,
truncation="longest_first",
return_tensors="pt",
max_length=self.context_len,
)
# 计算嵌入
embedding, token_num = self.__process_embed_chunk(
input_ids, attention_mask, **model_type_dict
)
# L2归一化
normalized_embeddings = F.normalize(embedding, p=2, dim=1)
支持的模型类型:
- BERT系列模型
- LLaMA系列模型
- T5系列模型
- ChatGLM系列模型
多模态支持
Worker支持处理多模态输入(文本+图像):
python
if type(message["content"]) == list:
image_list = [
item["image_url"]["url"]
for item in message["content"]
if item["type"] == "image_url"
]
text_list = [
item["text"]
for item in message["content"]
if item["type"] == "text"
]
# 构建多模态prompt
text = "<image>\n" * len(image_list)
text += "\n".join(text_list)
conv.append_message(conv.roles[0], (text, image_list))
Server组件详解
OpenAI API兼容性
FastChat提供OpenAI兼容的RESTful API,可以作为OpenAI API的本地替代方案。这使得基于OpenAI API开发的应用可以无缝迁移到开源模型。
支持的API端点:
端点 | OpenAI对应 | 功能 |
---|---|---|
/v1/chat/completions |
Chat API | 对话补全 |
/v1/completions |
Completion API | 文本补全 |
/v1/embeddings |
Embeddings API | 文本嵌入 |
/v1/models |
Models API | 列出模型 |
请求处理流程
让我们追踪一个完整的Chat Completion请求:
详细步骤解析:
- 参数验证:
python
async def check_requests(request) -> Optional[JSONResponse]:
if request.max_tokens is not None and request.max_tokens <= 0:
return create_error_response(
ErrorCode.PARAM_OUT_OF_RANGE,
f"{request.max_tokens} is less than the minimum of 1"
)
# ... 更多验证
- 获取Worker地址:
python
worker_addr = await get_worker_address(request.model)
- 长度检查:
python
context_len = await fetch_remote(
worker_addr + "/model_details",
{"model": request.model},
"context_length"
)
token_num = await fetch_remote(
worker_addr + "/count_token",
{"model": request.model, "prompt": prompt},
"count"
)
length = min(max_tokens, context_len - token_num)
- 流式响应处理:
python
async def chat_completion_stream_generator(
model_name: str,
gen_params: Dict[str, Any],
n: int,
worker_addr: str
) -> Generator[str, Any, None]:
id = f"chatcmpl-{shortuuid.random()}"
async for content in generate_completion_stream(gen_params, worker_addr):
decoded_unicode = content["text"].replace("\ufffd", "")
delta_text = decoded_unicode[len(previous_text):]
choice_data = ChatCompletionResponseStreamChoice(
index=i,
delta=DeltaMessage(content=delta_text),
finish_reason=content.get("finish_reason", None),
)
chunk = ChatCompletionStreamResponse(
id=id,
choices=[choice_data],
model=model_name
)
yield f"data: {chunk.model_dump_json(exclude_unset=True)}\n\n"
yield "data: [DONE]\n\n"
对话模板处理
FastChat使用Conversation模板来适配不同模型的对话格式:
python
async def get_gen_params(
model_name: str,
worker_addr: str,
messages: Union[str, List[Dict[str, str]]],
# ...
) -> Dict[str, Any]:
# 获取模型的对话模板
conv = await get_conv(model_name, worker_addr)
# 构建对话
for message in messages:
msg_role = message["role"]
if msg_role == "system":
conv.set_system_message(message["content"])
elif msg_role == "user":
conv.append_message(conv.roles[0], message["content"])
elif msg_role == "assistant":
conv.append_message(conv.roles[1], message["content"])
# 生成prompt
prompt = conv.get_prompt()
支持的对话格式:
- Vicuna格式
- ChatGLM格式
- Alpaca格式
- LLaMA-2-Chat格式
- 自定义格式
错误处理机制
Server实现了完善的错误处理:
python
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc):
return create_error_response(
ErrorCode.VALIDATION_TYPE_ERROR,
str(exc)
)
def create_error_response(code: int, message: str) -> JSONResponse:
return JSONResponse(
ErrorResponse(message=message, code=code).model_dump(),
status_code=400
)
错误类型:
错误代码 | 含义 | 处理方式 |
---|---|---|
VALIDATION_TYPE_ERROR |
参数验证失败 | 返回详细错误信息 |
INVALID_MODEL |
模型不存在 | 列出可用模型 |
CONTEXT_OVERFLOW |
上下文超长 | 建议减少输入长度 |
CONTROLLER_NO_WORKER |
无可用Worker | 等待Worker恢复 |
CUDA_OUT_OF_MEMORY |
GPU内存不足 | 建议减少batch size |
组件间通信机制
注册与发现
Worker注册流程:
python
def init_heart_beat(self):
# 立即注册
self.register_to_controller()
# 启动心跳线程
self.heart_beat_thread = threading.Thread(
target=heart_beat_worker, args=(self,)
)
self.heart_beat_thread.start()
def register_to_controller(self):
url = self.controller_addr + "/register_worker"
data = {
"worker_name": self.worker_addr,
"check_heart_beat": True,
"worker_status": self.get_status(),
"multimodal": self.multimodal,
}
r = requests.post(url, json=data)
心跳与健康检查
心跳发送:
python
def heart_beat_worker(worker):
while True:
time.sleep(WORKER_HEART_BEAT_INTERVAL)
worker.send_heart_beat()
def send_heart_beat(self):
url = self.controller_addr + "/receive_heart_beat"
while True:
try:
ret = requests.post(
url,
json={
"worker_name": self.worker_addr,
"queue_length": self.get_queue_length(),
},
timeout=5,
)
exist = ret.json()["exist"]
if not exist:
self.register_to_controller()
break
except Exception as e:
logger.error(f"Heart beat error: {e}")
关键机制:
- 双向确认:Worker发送心跳,Controller返回确认
- 自动重注册:如果Controller不认识Worker,自动重新注册
- 异常恢复:心跳失败时自动重试
通信协议设计
FastChat 使用 b"\0"(空字符,Null Byte)作为分隔符 来实现流式 JSON 消息的传输,这是一种简洁、高效且广泛采用的设计模式
python
# 发送端(Worker)
yield json.dumps(ret).encode() + b"\0"
# 接收端(Server)
delimiter = b"\0"
buffer = b""
async for raw_chunk in response.aiter_raw():
buffer += raw_chunk
while (chunk_end := buffer.find(delimiter)) >= 0:
chunk, buffer = buffer[:chunk_end], buffer[chunk_end + 1:]
if not chunk:
continue
yield json.loads(chunk.decode())
# 示例输出流:
# {"text": "今", "error_code": 0}\0{"text": "今天", "error_code": 0}\0{"text": "今天好!", "error_code": 0}\0
这种设计的优势:
优势 | 说明 |
---|---|
简单高效 | b"\0" 是单字节,查找快,编码/解码开销小 |
兼容性好 | JSON 文本中一般不会出现 \0 (非法字符),不会冲突 |
无格式依赖 | 不依赖 SSE(Server-Sent Events)等特定协议,可跨平台使用 |
支持任意数量消息 | 单连接可传输无限条消息,适合长文本流式生成 |
易于实现 | 不需要复杂的帧头、长度字段,适合轻量级服务 |
这种设计允许在单个HTTP连接中传输多个JSON对象,适合流式传输。
FastChat 使用 b"\0"
作为消息分隔符,通过 "发送时附加、接收时切分" 的机制,在单个 HTTP 连接中实现了多 JSON 消息的可靠流式传输。
整体架构中的位置:
python
[Client]
↓ (HTTP Streaming)
[FastChat Server/API] ← 接收并解析 \0 分隔的流
↓ 转发请求
[Controller]
↓ 调度
[Model Worker] → 生成 token 并 yield {"text":...} + b"\0"
与其他流式协议对比:
方案 | 说明 | 对比 FastChat |
---|---|---|
SSE ( text/event-stream ) |
标准 Web 推送协议,用 \n\n 分隔事件 |
更复杂,需处理 data: , event: 等字段;FastChat 更轻量 |
Length-Prefixed | 每条消息前加长度头(如 gRPC) | 高效但需预知长度;不适合流式生成(长度未知) |
JSON Lines | 每行一个 JSON,用 \n 分隔 |
可能与 JSON 内容中的换行冲突;\0 更安全 |
WebSocket + 多消息 | 每帧一个 JSON | 需要维护 WebSocket 连接;HTTP 流更通用 |
实际部署场景
单机部署
最简单的部署方式,适合开发和测试:
bash
# 1. 启动Controller
python3 -m fastchat.serve.controller
# 2. 启动Worker(支持本地模型)
python3 -m fastchat.serve.model_worker \
--model-path lmsys/vicuna-7b-v1.5 \
--num-gpus 1
# 3. 启动OpenAI API Server
python3 -m fastchat.serve.openai_api_server \
--host localhost \
--port 8000
多Worker部署
支持同一模型的高吞吐部署:
bash
# Worker 1
python3 -m fastchat.serve.model_worker \
--model-path lmsys/vicuna-7b-v1.5 \
--worker-address http://192.168.1.10:21002 \
--port 21002
# Worker 2
python3 -m fastchat.serve.model_worker \
--model-path lmsys/vicuna-7b-v1.5 \
--worker-address http://192.168.1.11:21002 \
--port 21002
# Worker 3(不同模型)
python3 -m fastchat.serve.model_worker \
--model-path meta-llama/Llama-2-7b-chat-hf \
--worker-address http://192.168.1.12:21002 \
--port 21002
Controller会自动发现并负载均衡到这些Worker。
集成在线API
FastChat支持集成在线模型API:
python
class ChatGLMWorker(ApiModelWorker):
def do_chat(self, params: ApiChatParams) -> Iterator[Dict]:
token = generate_token(params.api_key, 60)
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {token}"
}
data = {
"model": params.version,
"messages": params.messages,
"temperature": params.temperature,
}
url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"
with httpx.Client(headers=headers) as client:
response = client.post(url, json=data)
chunk = response.json()
yield {
"error_code": 0,
"text": chunk["choices"][0]["message"]["content"]
}
这使得可以统一管理本地模型和在线API。
性能优化配置
GPU内存管理:
bash
# 指定最大GPU内存
python3 -m fastchat.serve.model_worker \
--model-path lmsys/vicuna-7b-v1.5 \
--num-gpus 2 \
--max-gpu-memory 20GiB
并发控制:
bash
# 限制并发请求数
python3 -m fastchat.serve.model_worker \
--model-path lmsys/vicuna-7b-v1.5 \
--limit-worker-concurrency 5
8-bit量化:
bash
# 使用8-bit量化减少内存占用
python3 -m fastchat.serve.model_worker \
--model-path lmsys/vicuna-7b-v1.5 \
--load-8bit
架构优势与设计思想
可扩展性
- 水平扩展:可以轻松添加Worker实例提升吞吐量
- 模型异构:支持同时服务多种不同的模型
- 故障隔离:单个Worker故障不影响整体服务
灵活性
- 插件式架构:支持自定义Worker实现
- 多种部署模式:单机、分布式、混合云
- OpenAI兼容:无缝对接现有生态
可靠性
- 心跳机制:自动检测和移除故障节点
- 优雅降级:部分Worker失效时服务继续可用
- 错误处理:完善的异常捕获和用户反馈
性能优化
- 智能负载均衡:Shortest Queue策略优化响应时间
- 流式传输:降低首token延迟
- 批处理支持:嵌入计算等场景的批处理优化
最佳实践建议
生产环境部署
- 使用专用Controller :
- 部署在独立的高可用服务器
- 配置足够的网络带宽
- Worker配置 :
- 根据模型大小合理配置GPU内存
- 使用
--limit-worker-concurrency
控制并发 - 启用量化以提升吞吐量
- 监控与告警 :
- 监控Controller心跳状态
- 跟踪Worker队列长度
- 设置GPU内存和推理延迟告警
性能调优
- 负载均衡策略选择 :
- 同构环境:Shortest Queue
- 异构环境:Lottery(配置合适的速度权重)
- 批处理优化 :
- 嵌入计算使用合适的batch size
- 调整
WORKER_API_EMBEDDING_BATCH_SIZE
- 网络优化 :
- Controller和Worker尽量部署在同一网络
- 考虑使用负载均衡器(如Nginx)分流Server请求
安全考虑
- API密钥认证:
bash
python3 -m fastchat.serve.openai_api_server \
--api-keys key1,key2,key3
- 网络隔离 :
- Controller只暴露给Server
- Worker只接受Controller请求
- Server使用反向代理对外服务
- HTTPS支持:
bash
python3 -m fastchat.serve.openai_api_server \
--ssl \
--host 0.0.0.0 \
--port 8443
参考文献
- FastChat GitHub Repository - FastChat官方开源仓库,包含完整源代码和文档
github.com/lm-sys/Fast... - LMSYS Organization - LMSYS组织官网,介绍FastChat及相关项目
lmsys.org/ - Integrating and Scaling Large Language Models with FastChat - PyImageSearch详细教程,涵盖部署和集成
pyimagesearch.com/2024/08/19/... - Building a Truly "Open" OpenAI API Server - LMSYS官方博客,介绍OpenAI API兼容性设计
lmsys.org/blog/2023-0... - Analysis of join-the-shortest-queue routing - 负载均衡算法理论分析
blog.acolyer.org/2014/10/23/... - FastChat OpenAI API Documentation - OpenAI兼容API使用文档
github.com/lm-sys/Fast... - Chatbot Arena Technical Report - FastChat驱动的大模型评估平台技术报告
lmarena.ai