聊天页能打开、消息也能发出去,但对话一直没有回复,通常不是“前端坏了”,而是链路上某一段没接上:控制器没起来、工作进程没注册、注册的模型名和请求里的模型名对不上,或者推理服务根本没回包。把这四段分开验证,比反复改前端参数要快。
遇到“发出去没回复”时,先不要动前端。建议按顺序做三件事:在启动日志里确认控制器和工作进程都注册成功;用工作进程日志或控制器列表里显示的模型标识,逐字核对请求里传的模型名;再绕过聊天页面,直接请求推理服务端口并观察返回。直连能回包,问题基本在控制器路由或前端;直连也超时,说明模型侧还没就绪。下面的端口、路径和启动方式需要按你自己的部署替换确认。
在启动日志确认控制器与工作进程是否都注册成功
FastChat 的常见结构是三段:控制器(controller)、模型工作进程(model worker)、以及聊天页或 API 服务。这三段通常在不同终端或不同容器里启动,任何一段没起来,聊天页都会表现为“消息发出去没人理”。
# 终端一:控制器
python -m fastchat.serve.controller `--host` 0.0.0.0 `--port` 21001
# 终端二:工作进程(模型路径与名字按实际替换)
python -m fastchat.serve.model_worker \
`--model-path` /path/to/your/model \
`--model-name` local-model \
`--host` 0.0.0.0 `--port` 21002 \
`--controller` http://127.0.0.1:21001
# 终端三:聊天页或 API 服务
python -m fastchat.serve.gradio_web_server `--controller` http://127.0.0.1:21001
注册成功的日志一般出现在两侧:工作进程侧会打印一行“向控制器注册模型”的意思,并带上自己的地址和端口;控制器侧会出现一条对应的注册记录,里面包含工作进程地址和它声明的模型标识。注册失败时,工作进程侧更常见的是 Connection refused、ConnectionError,或者直接指向控制器地址的报错,而控制器侧完全没有新增记录。
缺哪一段,现象不一样。控制器没起来,工作进程启动就会失败或反复重连,聊天页通常拿不到任何可选模型;工作进程没起来,控制器列表为空,聊天页可能还能打开,但一发消息就报模型不存在或无可用工作进程;只有前端没起来(或前端指向的控制器地址写错),才表现为页面打不开、请求发不出去或一直转圈。
核对清单里的模型名与实际加载的模型标识是否一致
工作进程向控制器注册时用的名字,来自启动参数里指定的模型名(例如上面的 `--model-name` local-model);聊天页下拉框里显示的、以及 API 请求体里携带的模型名,必须和这个标识逐字一致,包括大小写、连字符和中英文符号。直接用模型路径当名字尤其容易出问题,路径里的斜杠在部分接口里需要转义,也容易被截断。
建议把注册名固定成一个简单字符串,然后在两处对照确认:一是工作进程启动日志里打印或注册的名字,二是控制器侧的模型列表。控制器一般提供查询接口,可以直接请求:
# 查询控制器当前注册了哪些模型(具体路径随版本不同,可先用日志确认)
curl -s http://127.0.0.1:21001/list_models
名称不匹配时,日志里常见的报错形态是 model not found、Model xxx not found,或者 Available models: [...] 后面跟着提示选择一个可用名字,中括号里列出的就是真正注册成功的标识,直接拿它替换请求里的名字即可。另一个容易忽略的点:同时起了多个工作进程时,它们声明的名字不能重复,否则请求会被路由到不确定的那一个,表现为时好时坏。
用命令单独请求推理服务端口,确认模型服务本身能回包
这一步是把聊天前端和模型后端分开。绕过控制器和聊天页,直接访问工作进程监听的那个端口,看服务本身是否健康。
# 直连工作进程端口,先看状态类接口是否响应
curl -s `--max-time` 5 http://127.0.0.1:21002/worker_get_status | head -c 500
# 再试一次会真正触发推理的请求(接口路径随版本不同,用启动日志确认)
curl -s `--max-time` 60 http://127.0.0.1:21002/worker_generate \
-H 'Content-Type: application/json' \
-d '{"model":"local-model","prompt":"你好","temperature":0.1}'
正常返回是一段 JSON,能看到模型标识、队列长度或生成文本。如果加了 `--max-time`,超时会以 curl: (28) Operation timed out 结束,连接层不通则以 curl: (7) Failed to connect to ... port ... refused 结束——这两类信息看退出码和尾部提示就能分清,不要只看有没有输出。状态接口能回、生成接口超时,通常说明权重正在加载或在排队,而不是端口没通。
判断方向很实用:直连端口能正常回包,问题基本在控制器路由或聊天前端;直连被拒绝,先查工作进程是否存活、监听地址是否只绑了 127.0.0.1;直连能连上但一直超时,回到工作进程日志看权重加载和资源占用。
发一条最短对话请求,观察是超时、报错还是空回复
链路确认通之后,用一个最小输入再发一次,避免长提示词、长上下文或特殊对话模板把真正的原因盖住。最短请求就是:一条 user 消息、内容一两个词、限制生成长度、关闭流式输出。
curl -s `--max-time` 60 http://127.0.0.1:21000/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "local-model",
"messages": [{"role":"user","content":"hi"}],
"max_tokens": 16,
"stream": false
}'
端口和路径要按你自己的 API 服务启动配置替换。三种结果指向不同段落:请求超时,问题在推理侧,可能是模型没加载完、资源不足或请求在排队;很快返回报错(模型不存在、参数不合法、服务未就绪),问题在名字对不上或某一段服务没注册好;HTTP 状态正常但回复内容为空,问题多在前端解析或流式字段处理——可以把上面这条非流式请求的结果和页面表现放在一起对比,就能确认是不是前端把内容丢了。
另外要区分“服务返回了但内容为空”和“服务压根没返回”:前者说明链路是通的,后者还要回到端口和进程层面重新看。
按错误类型分别处理:连接被拒、模型未找到、生成超时
把观察到的现象直接换成动作,不要靠猜。
- 连接被拒(Connection refused / Failed to connect):先确认对应进程还在(ps 或容器状态命令),再确认端口和监听地址。只绑 127.0.0.1 的工作进程,从容器外或另一台机器访问必然被拒,需要结合部署方式改成对端可访问的地址。跨主机部署时,还要确认控制器地址、工作进程注册地址写的是对方能访问到的那一个,而不是本机回环地址。容器场景下端口映射和防火墙规则一并确认。
- 模型未找到(model not found):回到工作进程启动参数里声明的模型名和控制器列表,逐字对齐后再发请求。常见来源有三个:请求里的名字与注册名在大小写、连字符上不一致;请求打到了错误的端口(例如打到控制器而不是 API 服务);某个工作进程启动失败,但前端下拉框里还留着旧选项,换一个可用模型重试即可判断。
- 生成超时(Operation timed out):先在日志里确认模型权重是否加载完成,这一步没走完,任何请求都会卡住。确认完成后看资源占用,模型体积超过可用显存或并发请求超出实际承受能力时,请求会长时间排队。可以先只留一个工作进程、串行发一条最短请求来判断;同时检查控制器和前端自身的请求超时设置是否过短,把方向固定下来再谈调参。
调整参数时以日志和实际返回为准:不同版本、不同后端的可用参数并不一致,不确定的参数名不要凭记忆写进配置,先用 `--help` 或启动日志确认。