TT Lab
开始
学习 学习路径 课程

LLM 服务

亲手实现一个 token 流式服务器

在 TT Lab 中继续学习

目标

亲手构建一个通过 SSE 流式发送 token 的 server,分别测量 TTFT 与 token 间延迟,并证明 client 中断时生成过程确实会停止。

为什么重要

即使总生成时间同为 6 秒,等 6 秒后一次性显示与 200ms 后开始持续输出,是完全不同的产品。因此,streaming 是 LLM 服务的基本能力;从启用 streaming 的那一刻起,测量方式也会改变——不能只记录响应完成时间,而应分别测量首个 chunk 到达时间和 chunk 之间的间隔。本实验中尤其容易忽略的是第 7 步。用户在长回答生成途中切换到其他问题是非常常见的模式;如果没有 cancel 处理,这些请求会继续占用 batch slot,生成无人查看的 token。并发吞吐量会莫名下降,GPU 账单却不断增加。这是一种安静而昂贵的事故,因此专门分配完整一步来处理。

步骤

  1. 在 /root/ls/gen.py 中创建 generate(prompt, n)。相同 prompt 和 n 必须始终返回相同 token sequence。使用相同参数生成 /root/ls/gen1.txt 和 /root/ls/gen2.txt,两个文件内容必须相同。
  2. 在 127.0.0.1:8170 启动 /root/ls/server.py。POST /generate 接收 {"prompt":"hello","max_tokens":20},返回 {"text":"...","usage":{"prompt_tokens":<n>,"completion_tokens":20}}。
  3. GET /stream?prompt=hello&max_tokens=20 使用 Content-Type: text/event-stream 响应,每个 token 以 data: {"token":"..."} 加一个空行发送。响应正文中以 data: 开头的行必须至少有 20 行。
  4. 在 stream 末尾发送 data: [DONE]。
  5. 使用 /root/ls/ttft.py 测量首个 chunk 到达前的时间,并在 /root/ls/ttft.txt 中写入 ttft_ms=<정수>。该值必须大于 0 且小于 3000。
  6. 在 /root/ls/itl.txt 中写入 count=<n> p50_ms=<수> p99_ms=<수>。count 必须至少为 19。
  7. 使用 max_tokens=100 打开 stream,只接收 5 个 chunk 后断开。让 GET /_debug/generated 返回server 在上一个 stream 请求中实际生成的 token 数量(同时包含累计总数会更方便),并在 /root/ls/cancel.txt 中将该值写为 requested=100 generated=<n>。generated 必须不大于 30。
  8. 在 /root/ls/report.txt 中写四行:ttft_ms=<n>、itl_p50_ms=<수>、tokens=<n>、throughput_tps=<수>。

参考

创建确定性 token generator

在 /root/ls/gen.py 中创建 generate(prompt, n)。相同 prompt 和 n 必须始终返回相同 token sequence。使用相同参数生成 /root/ls/gen1.txt 和 /root/ls/gen2.txt,两个文件内容必须相同。

相同 prompt 必须始终产生相同 token sequence,才能进行评分和评估。可以使用 hash 作为 seed。

创建非 streaming endpoint

在 127.0.0.1:8170 启动 /root/ls/server.py。POST /generate 接收 {"prompt":"hello","max_tokens":20},返回 {"text":"...","usage":{"prompt_tokens":<n>,"completion_tokens":20}}。

首先实现一次性返回的形式,以建立 baseline。请同时返回 token usage。

使用 SSE 发送 chunk

GET /stream?prompt=hello&max_tokens=20 使用 Content-Type: text/event-stream 响应,每个 token 以 data: {"token":"..."} 加一个空行发送。响应正文中以 data: 开头的行必须至少有 20 行。

content type 与 event 分隔规则是固定的。若遗漏 event 之间的空行,client 将无法识别。

发送结束信号

在 stream 末尾发送 data: [DONE]。

有一个惯用 marker 用于告知 stream 何时结束。

测量首个 token 到达时间

使用 /root/ls/ttft.py 测量首个 chunk 到达前的时间,并在 /root/ls/ttft.txt 中写入 ttft_ms=<정수>。该值必须大于 0 且小于 3000。

这是从请求开始到首个 chunk 到达之间的时间。不要与整体完成时间混淆。

统计 token 间延迟分布

在 /root/ls/itl.txt 中写入 count=<n> p50_ms=<수> p99_ms=<수>。count 必须至少为 19。

记录每个 chunk 的到达时间并计算差值。仅计算平均值并不足够。

client 中断时停止生成

使用 max_tokens=100 打开 stream,只接收 5 个 chunk 后断开。让 GET /_debug/generated 返回server 在上一个 stream 请求中实际生成的 token 数量(同时包含累计总数会更方便),并在 /root/ls/cancel.txt 中将该值写为 requested=100 generated=<n>。generated 必须不大于 30。

只有检测到连接断开,才能停止生成。请统计 server 生成的 token 数量来证明。

创建指标报告

在 /root/ls/report.txt 中写四行:ttft_ms=<n>、itl_p50_ms=<수>、tokens=<n>、throughput_tps=<수>。

把前面测量的值集中到一个文件中。吞吐量是生成 token 数除以总时间。