Files
FastDeploy/docs/zh/online_serving/router_faq.md
T

253 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[English](../../online_serving/router_faq.md)
# Router 常见问题排查
本文档基于 [Golang Router](https://github.com/PaddlePaddle/FastDeploy/tree/develop/fastdeploy/golang_router) 的代码实现,汇总了 Router 在使用过程中常见的日志信息、返回输出及问题排查方法,帮助用户快速定位和解决问题。
Router 的基本使用方式请参考 [负载均衡调度 Router](router.md)。
## 常见日志分析
> **说明**:`{}` 表示变量,实际日志中会替换为具体的值。
### Error 级别日志
| 日志表现 | 日志含义 | 导致结果 | 可排查内容 |
| :--- | :--- | :--- | :--- |
| `Removed unhealthy prefill instance: {url}` | Prefill 实例健康检查失败,已被移除 | 该 Prefill 实例不再参与调度 | 健康状况 |
| `Removed unhealthy decode instance: {url}` | Decode 实例健康检查失败,已被移除 | 该 Decode 实例不再参与调度 | 健康状况 |
| `Removed unhealthy mixed instance: {url}` | Mixed 实例健康检查失败,已被移除 | 该 Mixed 实例不再参与调度 | 健康状况 |
| `Failed to register instance: {error}` | 实例注册失败 | Router 无法注册该实例 | 健康状况、注册参数 |
| `Failed to read YAML file {path}: {error}` | 启动时无法读取注册配置文件 | 配置文件中的实例无法被注册 | 文件路径、文件权限 |
| `Failed to unmarshal YAML file {path}: {error}` | 注册配置文件格式解析错误 | 配置文件中的实例无法被注册 | YAML 格式 |
| `Failed to register instance from index {index}: {error}` | 配置文件中第 {index} 个实例注册失败 | 该实例未能成功注册 | 健康状况、注册参数 |
| `failed to send request to {url} with error: {error}` | 健康检查请求发送失败 | 该实例可能被判定为不健康 | 网络连通性、代理设置 |
| `scanner error: {error}` | 读取后端流式响应时发生错误 | 当前请求可能失败 | 后端实例状态 |
| `[prefill] scanner error: {error}, message={message}` | 读取 Prefill 后端流式响应时发生错误 | 当前 Prefill 请求可能失败 | 后端实例状态 |
| `[prefill] copy error: {error}, message={message}` | 复制 Prefill 响应数据时发生错误 | 当前 Prefill 请求可能失败 | 后端实例状态 |
| `Panic recovered: {error}` | 请求处理过程中发生 panic 并被恢复 | 当前请求失败,但服务继续运行 | 后端实例状态、请求内容 |
| `empty baseURL provided` | 健康检查时传入了空的基础 URL | 健康检查无法执行 | 注册参数 |
| `failed to create request: {error}` | 创建健康检查请求失败 | 该实例可能被判定为不健康 | 网络环境 |
| `failed to read response body: {error}` | 读取健康检查响应体失败 | 该实例可能被判定为不健康 | 后端实例状态 |
### Warn 级别日志
| 日志表现 | 日志含义 | 导致结果 | 可排查内容 |
| :--- | :--- | :--- | :--- |
| `Server {url} is not healthy` | 该 URL 对应的实例未通过健康检查 | Router 无法注册该实例,或将该实例从已注册列表中移除 | 健康状况 |
| `Instance {url} role is unknown` | 实例角色无法识别 | 该实例不会被加入调度列表 | 注册参数 |
| `cache-aware prefill: tokenizer failed, fallback to char tokens: {error}` | Tokenizer 服务调用失败,已自动回退至字符级分词 | cache_aware 策略仍然生效,使用字符级分词代替 Tokenizer 进行缓存匹配,不影响正常请求处理 | Tokenizer 服务状态 |
| `cache-aware prefill: tokenize failed, fallback to process_tokens: {error}` | 分词彻底失败(如输入为空),回退至 process_tokens 策略 | Prefill 调度暂时不使用 cache_aware 策略,不影响正常请求处理 | 请求内容、Tokenizer 服务状态 |
| `cache-aware prefill: final strategy: process_tokens, reason: tokenize failed: {error}. ts_ms={ts}` | 分词失败(新格式),回退至 process_tokens 策略 | Prefill 调度暂时不使用 cache_aware 策略,不影响正常请求处理 | 请求内容、Tokenizer 服务状态 |
### Info 级别日志
| 日志表现 | 日志含义 | 说明 |
| :--- | :--- | :--- |
| `Starting server on {host:port}` | Router 服务正在启动 | 正常启动日志 |
| `Server {url} is healthy` | 实例健康检查通过 | 正常运行日志 |
| `Successfully registered instance from index {index}` | 配置文件中的实例注册成功 | 正常启动日志 |
| `No instances found in config file {path}` | 注册配置文件中未找到实例信息 | 请检查 register.yaml 内容是否为空 |
| `Request completed successfully.` | 请求处理完成 | 正常运行日志 |
| `Request failed, retrying...` | 请求失败,正在进行重试 | Router 最多重试 3 次 |
| `select worker (prefill): {url}, tokens: {tokens}` | Prefill 调度选中 Worker,显示当前 token 处理量 | 正常运行日志 |
| `select worker ({type}): {url}, count: {count}` | Decode/Mixed 调度选中 Worker,显示当前请求并发数 | 正常运行日志 |
| `release worker: {url}, count: {count}` | 请求结束,释放 Worker 计数器 | 正常运行日志 |
| `release prefill tokens: {url}, tokens: {tokens}` | Prefill 请求结束,释放 token 负载 | 正常运行日志 |
| `cleanup unhealthy worker counter: {url}` | 清理不健康 Worker 的计数器 | 正常运行日志 |
| `removed counters for {count} unhealthy workers: {urls}` | 批量清理不健康 Worker 的计数器 | 正常运行日志 |
| `[stats] total_running={n}, workers: [{loads}], cache_hit_rate={rate}% (hits={hits}/total={total})` | 周期性统计:总请求数、各 Worker 负载、缓存命中率 | 正常运行日志,用于监控调优 |
| `Parsing completed; starting worker selection.` | 请求解析完成,开始选择 Worker | 正常运行日志 |
| `Request completed with an error.` | 请求处理完成但发生错误 | 请排查后端实例状态 |
| `[SelectWorkerPair] decode selection failed, releasing prefill counter url={url}` | PD 分离模式下 Decode 选择失败,释放 Prefill 计数器 | 异常处理日志 |
| `[prefill] first chunk received, release counter url={url}` | Prefill 流式响应收到首个数据块,释放计数器 | 正常运行日志 |
| `[prefill] non-stream prefill response done, release counter url={url}` | Prefill 非流式响应完成,释放计数器 | 正常运行日志 |
| `[prefill] backendResp is nil or backendResp.Body is nil, url={url}` | Prefill 后端响应为空 | 可能表示后端连接异常 |
| `[prefill] release in defer (fallback) url={url}, isStream={bool}` | Prefill 请求异常退出时的兜底资源释放 | 异常处理日志 |
| `[prefill] release in CommonCompletions defer (error path) url={url}` | 请求出错时释放 Prefill 资源 | 异常处理日志 |
| `cache-aware prefill: final strategy: process_tokens, reason: strategy not initialized` | cache_aware 策略未初始化,回退至 process_tokens | 请检查 cache_aware 相关配置 |
| `cache-aware prefill: final strategy: process_tokens, reason: load imbalanced, loads={loads}. ts_ms={ts}` | 实例间负载不均衡,回退至 process_tokens 策略 | 正常运行日志,负载均衡自动切换 |
| `cache-aware prefill: final strategy: cache_aware_scoring, selected={url}, loads={loads}, hitRatios={ratios}. ts_ms={ts}` | cache_aware 打分策略选中 Worker | 正常运行日志,显示负载和命中率 |
| `[{method}] {path} {proto} {status} {latency} {clientIP}` | HTTP 请求访问日志 | 正常运行日志,记录每个请求的基本信息 |
| `before SelectWorker prefill. ts_ms={ts}` | PD 分离模式下开始选择 Prefill Worker | 正常运行日志,用于性能追踪 |
| `before SelectWorker decode, after prefill. ts_ms={ts}` | Prefill 选择完成后开始选择 Decode Worker | 正常运行日志,用于性能追踪 |
| `after SelectWorker decode, before return. ts_ms={ts}` | Decode Worker 选择完成 | 正常运行日志,用于性能追踪 |
### Debug 级别日志
> Debug 级别日志需要将日志级别设置为 `debug` 才会输出,通常用于开发调试。
| 日志表现 | 日志含义 | 说明 |
| :--- | :--- | :--- |
| `Healthy instances: prefill={urls}, decode={urls}, mixed={urls}` | 列出当前各角色的健康实例列表 | 用于确认实例发现是否正常 |
| `cache-aware prefill: hashes={n} workers={n} load={loads} hit={hits}` | cache_aware 策略的 hash 数量、Worker 数量及负载信息 | 用于调试缓存命中 |
| `cache-aware prefill: tokenizer tokens={tokens}` | Tokenizer 分词结果 | 用于调试分词结果 |
| `cache-aware score: worker={url} hit={hit} loadRatio={ratio} score={score}` | cache_aware 策略的打分详情 | 用于调试调度决策 |
| `radix match: hashes={n} matched_len={n} node_children={n}` | 前缀树匹配详情 | 用于调试缓存匹配 |
| `radix record: worker={url} hashes={n} node_depth={n}` | 前缀树记录详情 | 用于调试缓存记录 |
| `radix eviction: removed={n} nodeCount={n}` | 前缀树淘汰详情 | 用于调试缓存淘汰 |
## 常见返回输出分析
### 推理请求错误(/v1/chat/completions、/v1/completions
| 输出表现 | HTTP 状态码 | 含义 | 可排查内容 |
| :--- | :---: | :--- | :--- |
| `{"error": "No available prefill/decode workers"}` | 503 | Prefill 或 Decode 实例全部不可用,没有已注册的健康实例 | 健康状况 |
| `{"error": "Failed to select worker pair"}` | 502 | PD 分离模式下选择 Worker 对失败 | 健康状况、调度策略 |
| `{"error": "Failed to select worker"}` | 502 | 集中式模式下选择 Worker 失败 | 健康状况、调度策略 |
| `{"error": "Failed to connect to backend service"}` | 502 | 连接后端推理实例失败(已重试 3 次仍失败) | 后端实例状态、网络连通性 |
| `{"error": "Failed to build disaggregate_info"}` | 500 | 构建 PD 分离通信信息失败 | 注册参数(connector_port、device_ids 等) |
| `{"error": "Invalid request body"}` | 400 | 请求体读取失败 | 请求格式 |
| `{"error": "Invalid JSON format"}` | 400 | 请求体 JSON 解析失败 | 请求格式 |
### 注册请求错误(/register
| 输出表现 | HTTP 状态码 | 含义 | 可排查内容 |
| :--- | :---: | :--- | :--- |
| `{"code": 503, "msg": "{url} service is not healthy"}` | 503 | 实例健康检查未通过,无法注册 | 健康状况 |
| `{"code": 400, "msg": "Invalid request body"}` | 400 | 注册请求体读取失败 | 请求格式 |
| `{"code": 400, "msg": "Invalid InstanceInfo JSON format: {error}"}` | 400 | 注册请求 JSON 格式错误 | 请求格式 |
| `{"code": 400, "msg": "splitwise mode only supports PREFILL/DECODE instances"}` | 400 | PD 分离模式下不允许注册 MIXED 实例 | 部署模式、实例角色 |
| `{"code": 400, "msg": "only MIXED instances are allowed"}` | 400 | 集中式模式下只允许注册 MIXED 实例 | 部署模式、实例角色 |
| `{"code": 400, "msg": "invalid InstanceInfo format: {error}"}` | 400 | 实例注册信息校验失败 | 注册参数 |
| `{"code": 200, "msg": "Register success"}` | 200 | 注册成功 | — |
### 常见注册参数校验错误
| 错误信息 | 含义 | 解决方法 |
| :--- | :--- | :--- |
| `role is required` | 缺少 role 字段 | 添加 role 字段,可选值:prefill / decode / mixed |
| `invalid role: {role}` | role 值不合法 | 使用合法的 role 值:prefill / decode / mixed |
| `host_ip is required` | 缺少 host_ip 字段 | 添加 host_ip 字段 |
| `invalid host_ip: {ip}` | host_ip 不是合法的 IP 地址 | 填写正确的 IP 地址 |
| `port is required` | 缺少 port 字段 | 添加 port 字段 |
| `invalid port: {port}` | port 不是合法的端口号 | 填写 1-65535 范围内的端口号 |
| `invalid protocol: {protocol}` | 传输协议不合法 | 使用合法的协议值:ipc / rdma |
## 常见问题排查方式
### 健康状况
实例健康检查是 Router 正常运行的基础。当出现实例无法注册或被移除的情况时,请按以下步骤排查:
**1. 查看实例注册情况**
查看 Router 当前已注册的实例列表和数量:
```bash
# 查看已注册实例列表
curl -X GET http://{router_url}/registered
# 查看已注册实例数量
curl -X GET http://{router_url}/registered_number
```
确认所有预期实例是否都已注册成功。如果实例数量不符,说明部分实例注册失败或已被健康检查移除。
**2. 检查实例健康状态与网络连通性**
从 Router 所在机器直接访问推理实例的健康接口:
```bash
curl -X GET http://{server_url}/health
```
- 返回 HTTP 200 表示实例健康且网络连通
- 无法访问或返回非 200 状态码,请进一步排查:
- 实例是否已启动并监听在正确端口上
- Router 与实例之间是否存在代理干扰(尝试关闭代理:`unset http_proxy && unset https_proxy`
- 是否存在防火墙限制
**常见解决方式:**
- 关闭网络代理:`unset http_proxy && unset https_proxy`
- gunicorn 版本兼容问题:如果出现注册实例数不全的情况,可能是 gunicorn 与 FastDeploy 版本不兼容,回退至 `gunicorn==25.0.3` 可解决
### 调度策略
当出现 `Failed to select worker``Failed to select worker pair` 错误时:
**1. 确认已注册实例数**
```bash
curl -X GET http://{router_url}/registered_number
```
如果返回的实例数为 0,说明没有可用的健康实例,请先参考[健康状况](#健康状况)排查。
**2. 检查调度策略配置**
确认 config.yaml 中的调度策略与部署模式匹配。Router 的默认调度策略如下:
| 部署模式 | 配置项 | 默认策略 |
| :--- | :--- | :--- |
| 集中式模式 | `policy` | `request_num` |
| PD 分离模式(Prefill 节点) | `prefill-policy` | `process_tokens` |
| PD 分离模式(Decode 节点) | `decode-policy` | `request_num` |
如未通过配置文件指定策略,Router 将使用上述默认策略进行调度。如需使用 `cache_aware``fd_metrics_score` 等高级策略,请通过配置文件显式指定。各策略的详细说明请参考 [调度策略说明](router.md#调度策略说明)。
**3. 检查 fd_metrics_score 策略的依赖**
使用 `fd_metrics_score` 策略时,Router 会实时从推理实例的 `/metrics` 接口获取 running/waiting 请求数进行打分调度。当 `/metrics` 接口不可用时(如实例未配置 `metrics_port` 或 metrics 服务异常),Router 会自动回退至内部请求计数器进行调度,不影响正常的请求处理,但调度精度会有所下降。
如需确保 `fd_metrics_score` 策略获得最佳调度效果,请确认推理实例的 metrics 接口正常返回:
```bash
curl -X GET http://{server_url}/metrics
```
### 注册参数
当注册失败且返回参数校验错误时:
**1. 检查部署模式与实例角色是否匹配**
- PD 分离模式(`--splitwise`):只能注册 `prefill``decode` 角色的实例
- 集中式模式(默认):只能注册 `mixed` 角色的实例
**2. 检查必填参数**
注册请求必须包含以下字段:
- `role`:实例角色(prefill / decode / mixed
- `host_ip`:实例 IP 地址
- `port`:实例端口号
**3. 检查 PD 分离模式下的可选参数**
PD 分离模式下建议完整配置以下参数,以确保 KV Cache 传输正常:
- `connector_port`PD 通信端口
- `transfer_protocol`:传输协议(ipc / rdma
- `device_ids`GPU 设备 ID
- `rdma_ports`:RDMA 端口(使用 rdma 协议时必填)
### 启动失败
**1. 配置文件加载失败**
启动时日志出现 `Failed to load config`,请检查:
- `--config_path` 指向的文件路径是否正确
- 配置文件是否为合法的 YAML 格式
- 配置参数值是否合法
**2. 端口被占用**
启动时日志出现 `Failed to start server`,请检查:
- `--port` 指定的端口是否已被其他进程占用
- 可使用 `lsof -i:{port}``netstat -tlnp | grep {port}` 确认端口使用情况
### Tokenizer 服务(cache_aware 策略)
使用 `cache_aware` 调度策略时,Router 会调用 Tokenizer 服务对请求进行分词以计算缓存命中率。当 Tokenizer 服务不可用时,Router 内置了两级退化机制:
1. **回退至字符级分词**(常见情况):日志出现 `tokenizer failed, fallback to char tokens`。此时 cache_aware 策略仍然生效,只是使用字符级分词代替 Tokenizer 进行缓存匹配,缓存命中精度会有所下降,但不影响正常请求处理。
2. **回退至 process_tokens 策略**(极端情况):当分词彻底失败(如请求内容为空)时,日志出现 `tokenize failed, fallback to process_tokens`。此时 cache_aware 策略暂时不生效,改为按 token 处理量进行调度,同样不影响正常请求处理。
如需恢复 cache_aware 策略的完整功能:
**1. 检查 Tokenizer 服务是否正常运行**
```bash
curl -X POST http://{tokenizer_url}/tokenize \
-H "Content-Type: application/json" \
-d '{"text": "hello"}'
```
**2. 检查相关配置**
- 确认 config.yaml 中 `tokenizer-url` 配置正确
- 如果 Tokenizer 服务响应较慢,可适当增大 `tokenizer-timeout-secs`(默认 2 秒)