FastChat 换模型看清单文件、调并发数看启动参数

文章导读
FastChat 里「换模型」和「调并发」落在两个不同的位置:模型路径、对外注册名写在模型清单(或等价的启动脚本列表)里,改完重启工作进程即可;并发上限、监听地址、调度策略写在 controller 与 model_worker 的启动参数里,改完要重启对应进程。分不清这两类,就会出现每次微调都全量重启、或者改完发现没生效的情况。判断顺序建议是:先确认改的是清单字段还是启动参数,再用日志和一次并发
📋 目录
  1. 找出换模型时需要改的清单字段,以及哪些改动必须重启
  2. 确认并发相关参数写在哪一层:控制器还是工作进程
  3. 改完参数重启服务,从日志确认新值生效
  4. 用两条并发请求观察排队与拒绝的表现
  5. 把参数改动写进启动脚本,避免每次手敲
A A

FastChat 里「换模型」和「调并发」落在两个不同的位置:模型路径、对外注册名写在模型清单(或等价的启动脚本列表)里,改完重启工作进程即可;并发上限、监听地址、调度策略写在 controller 与 model_worker 的启动参数里,改完要重启对应进程。分不清这两类,就会出现每次微调都全量重启、或者改完发现没生效的情况。判断顺序建议是:先确认改的是清单字段还是启动参数,再用日志和一次并发请求验证。

换模型通常只动清单里的模型路径与注册名,重启 model_worker 就够;调并发只动 controller 与 model_worker 的启动参数,重启 controller 或对应工作进程。判断依据不是记忆,而是日志里打印的模型名与参数值、以及两条并发请求的真实表现。清单和启动参数不要混在一处改,否则出问题时无法定位是哪一层没生效。

找出换模型时需要改的清单字段,以及哪些改动必须重启

如果你用一份 JSON、YAML 或 shell 变量清单管理模型,换模型时要改的条目通常集中在「模型路径」和「对外模型名」两项上。其余字段只在特定情况下才动。

清单字段作用换模型是否要改是否必须重启
model_path本地权重目录或模型标识要改要,重启 model_worker
model_name / model-names注册到 controller 的名字,也是请求里 model 字段的值要改要,重启 model_worker
model_type 或对话模板决定提示词拼接方式跨模型类型时改
num_gpus / 显存相关加载方式模型变大变小时改
worker 监听地址worker 自身端口一般不动改了才要

重启前后可观察的差异:model_worker 启动日志里会出现新的模型路径与加载过程提示,controller 日志里会出现该 worker 的注册记录,模型名是新名字。最省事的确认方式是请求一次模型列表接口,看返回里旧模型名是否已被新模型名替换。

确认并发相关参数写在哪一层:控制器还是工作进程

FastChat 大致分三层:controller 负责调度与排队,model_worker 负责模型侧的实际推理并发,API server 通常只做协议转换,一般不在这里限流。并发参数写错层,是最常见的「改了没反应」。

两层的启动方式不一样,参数落点也不一样:controller 一般用 python -m fastchat.serve.controller `--host` ... `--port` ... 启动;model_worker 一般用 python -m fastchat.serve.model_worker `--model-path` ... `--controller-address` http://... 启动。

FastChat 换模型看清单文件、调并发数看启动参数
启动参数所在层大致作用
`--host` / `--port`controller、model_worker监听地址与端口
`--limit-worker-concurrency`controller控制器同时分发给工作进程的请求上限
`--dispatch-method`controller请求选哪个 worker 的调度方式
`--controller-address`model_worker注册到哪个控制器
`--limit-model-concurrency`model_worker单个工作进程的并发上限
`--num-gpus` / `--max-gpu-memory`model_worker决定能否起多个 worker 实例

参数名在不同版本间可能有差异,建议先跑一次 python -m fastchat.serve.controller `--help`python -m fastchat.serve.model_worker `--help`,确认当前版本真实支持的参数名,再写进启动命令。参数名写错时,argparse 通常会直接报 unrecognized arguments 并退出,而不是静默忽略,这本身也是一条排查线索。

改完参数重启服务,从日志确认新值生效

能体现参数值的行主要有三类:启动命令本身的回显、脚本里 set -x 打出的执行行、以及 ps -ef | grep fastchat 看到的进程实际参数。如果日志里完全看不到某个参数,就要怀疑它没被传进去。

ps -ef | grep -E 'fastchat\.serve' | grep -v grep
curl -s http://127.0.0.1:8000/v1/models

没生效时的常见原因有四种:旧进程没有退出,端口仍被占用,controller 还连着老 worker;参数写死在某一份脚本里,而你改的是另一份同名文件;参数写在了不生效的那一层,例如把并发上限加在 API server 上;参数名与当前版本不一致,被 argparse 拒绝或忽略。排查顺序建议是先看进程列表里是不是只剩一个新进程,再看模型列表接口的返回,最后才看并发表现。

用两条并发请求观察排队与拒绝的表现

参数改动最终要对上实际行为。最直接的办法是同时发两条相同的请求,比较返回码和耗时:

FastChat 换模型看清单文件、调并发数看启动参数
for i in 1 2; do
  curl -s -o /tmp/resp_$i.txt -w "req$i code=%{http_code} time=%{time_total}\n" \
    -X POST http://127.0.0.1:8000/v1/chat/completions \
    -H "Content-Type: application/json" \
    -d '{"model":"你的模型名","messages":[{"role":"user","content":"hi"}],"max_tokens":16}' &
done
wait

三种表现要区分开:排队是两条请求最终都返回成功,但第二条的 time_total 明显更长,说明请求被挡住等待而不是失败;超时是客户端或中间层先断开,curl 报 timeout 或拿到空响应,而服务端可能仍在处理,这种情况通常要调整的是超时设置或并发上限;直接失败是立刻返回 4xx、5xx 或连接被拒,说明参数限制已经硬性拦住请求。把三种表现和改动前后的表现对照,就能判断并发参数是否真的作用在这一层。

把参数改动写进启动脚本,避免每次手敲

手敲命令的问题是改了什么、改了哪次,事后对不上。建议用一个变量文件集中存放清单与启动参数,启动脚本只负责引用变量。

# env.sh
CONTROLLER_HOST=0.0.0.0
CONTROLLER_PORT=21001
LIMIT_WORKER_CONCURRENCY=4
MODEL_PATH=/data/models/your-model
MODEL_NAME=your-model
# start.sh
set -euo pipefail
source ./env.sh

python -m fastchat.serve.controller \
  `--host` "$CONTROLLER_HOST" `--port` "$CONTROLLER_PORT" \
  `--limit-worker-concurrency` "$LIMIT_WORKER_CONCURRENCY" &

python -m fastchat.serve.model_worker \
  `--model-path` "$MODEL_PATH" `--model-names` "$MODEL_NAME" \
  `--controller-address` "http://127.0.0.1:$CONTROLLER_PORT" &

wait

这类写法的好处是换模型只改 env.sh 里的 MODEL_PATH 与 MODEL_NAME,调并发只改 LIMIT_WORKER_CONCURRENCY,diff 一眼能看出动了什么。改完后用一条验证命令核对进程参数与变量是否一致:

ps -ef | grep fastchat | grep -v grep

如果进程列表里的参数值和 env.sh 对不上,说明脚本没有 source 成功,或者环境里有另一份同名变量覆盖了它,这时应该先解决脚本问题,再去调并发数。