1153 字
6 分钟
回答已经返回,为什么请求还没结束?

mini-sglang 是一个用于学习推理服务内部结构的简化项目。一次流式请求从 API 进来,经过多个进程和消息队列,最后把生成结果返回给客户端;真正结束时,还要把这次请求留下的状态清理干净。

这篇只记录其中一个很小但容易漏掉的边界:客户端已经收到最后一个片段时,服务端是否真的完成了收尾。实验不讨论模型质量或 GPU 速度,只把变量收缩到 API 层的请求生命周期。

NOTE

实验结论 Day 4 先让测试失败,再修复 API 请求结束后的清理逻辑,最后 4 个测试全部通过。 这次没有测 GPU 性能,重点是确认请求能够“有始有终”。 项目入口:mini-sglang 项目索引 项目计划:mini-sglang 推理可观测性项目计划

日期:2026-09-05 仓库:/home/runqi_yang/src/mini-sglang 分支:main 测试方式:假的收发队列 + 假的 UserReply,不启动 GPU、模型或真实 ZMQ

这次要解决什么#

一个请求大致会经历:

  1. API 接收请求;
  2. 后端不断返回生成片段;
  3. 收到最后一个片段;
  4. 删除这个请求的临时记录。

可以把临时记录理解成餐厅的“正在处理订单”清单。菜已经端出去以后,订单也必须从清单里删掉。否则请求量一多,服务会误以为还有很多请求没有完成,后续的 inflight 指标也会失真。

这正是 Day 4 要先验证的边界。Prometheus 指标放在 Day 5 再接入,先保证被统计的生命周期本身是正确的。

测试范围#

测试文件:tests/observability/test_frontend_metrics.py

测试场景需要确认的事情
流式 /generate返回所有片段和 [DONE],结束后清理请求记录
流式 ChatCompletion返回角色块、结束块和 [DONE],结束后清理请求记录
非流式 ChatCompletion收集到最终 reply 后返回完整内容,并清理请求记录
客户端断开只发送一次 AbortMsg,并清理请求记录

测试使用假的收发队列,把 UserReply 直接送给 FrontendManager。这样测试关注的是 API 层的请求状态,不会被模型加载、显存、GPU kernel 或 ZMQ 环境干扰。

第一轮:先测试,发现问题#

修复前的结果:

3 failed, 1 passed

通过的是客户端断开场景。三个正常完成场景都发现同一个问题:回答内容已经返回,但 ack_map 和 event_map 里的请求记录仍然存在。

问题原因在于:

  • wait_for_ack() 原本把清理代码放在生成器循环之后;
  • 三个调用方收到最终 reply 后立即 break;
  • 内层生成器没有继续执行到清理位置。

所以这不是“模型没有回答”,而是“回答完以后忘了收尾”。

修复内容#

修改文件:python/minisgl/server/api_server.py

做了三处最小修改:

  1. 给 wait_for_ack() 加上 try/finally,无论正常结束还是取消,都尝试清理 ack_map 和 event_map;
  2. 使用 pop(uid, None),让重复清理不会再次抛错;
  3. 流式和非流式调用方不再在最终 reply 处提前 break,让 wait_for_ack() 自然结束并完成清理。

API 返回内容没有改变,改动只影响请求结束后的内部状态。

第二轮:修复后重新测试#

标准 pytest 结果:

.... [100%]
4 passed in 3.44s

另外完成了:

  • Python compileall 检查通过;
  • git diff --check 通过;
  • 客户端断开场景仍然只发送一个 AbortMsg。

项目虚拟环境原本没有安装 pytest。本次为了测试临时安装了测试运行器,测试完成后已清理临时依赖,没有修改 pyproject.toml。

这次实验说明了什么#

现在可以确认:

  • 流式请求能够正常结束;
  • 非流式请求能够正常结束;
  • 客户端取消能够正常清理;
  • 正常完成和取消都不会留下请求状态。

这次还没有测 TTFT、ITL、吞吐或显存,也还没有真正输出 Prometheus 指标。因此它是一次 API 生命周期正确性测试,不是性能实验。

下一步:Day 5#

在这套已经通过测试的生命周期上接入 API 指标:

  • 请求接收数;
  • 当前进行中的请求数;
  • 完成数和取消数;
  • TTFT、ITL 和总时延。

指标标签不使用 uid、完整 prompt 或模型完整路径,避免产生高基数和敏感信息。相关概念见 Day 03 指标设计笔记,请求路径见 Day 02 请求生命周期笔记。

回答已经返回,为什么请求还没结束?
https://www.yyylegend.com/posts/mini-sglang先让测试失败再修复请求生命周期/
作者
YYYLEGEND
发布于
2026-09-05
许可协议
CC BY-NC-SA 4.0