TT Lab
はじめる
学ぶ 学習パス コース

LLMサービング

トークンストリーミングサーバを自分で実装する

TT Labで続きを見る

目標

SSEでトークンを流すサーバーを自分で作り、TTFTとトークン間のレイテンシを別々に測定し、クライアントの中断時に生成が実際に止まることを証明します。

なぜ重要なのか

総生成時間が同じ6秒でも、6秒後に一度に出てくるものと、200ms後から流れ出てくるものは、まったく別の製品です。そのため、LLMサービスでストリーミングは基本であり、その瞬間から、測定方法も変わります。応答の完了時刻1つではなく、最初のチャンクまでの時間と、チャンク間の間隔を、別々に測る必要があります。このラボで特に見落としやすいのが、ステップ7です。ユーザーが長い応答の途中で別の質問に移るパターンは非常によくありますが、キャンセルの処理がないと、それらのリクエストがバッチのスロットを占有し続けて、誰も見ていないトークンを作ります。同時スループットが理由なく下がり、GPUの請求書だけが増えます。静かで高くつく事故なので、1つのステップをまるごと割り当てました。

ステップ

  1. /root/ls/gen.pyにgenerate(prompt, n)を作成してください。同じプロンプトとnなら、いつも同じトークン列を返す必要があります。/root/ls/gen1.txtと/root/ls/gen2.txtを同じ引数で作って、2つのファイルの内容が同じである必要があります。
  2. /root/ls/server.pyを127.0.0.1:8170で起動してください。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で応答し、各トークンをdata: {"token":"..."}と空行で送ります。レスポンス本文に、data: で始まる行が20行以上ある必要があります。
  4. ストリームの最後に、data: [DONE]を送ってください。
  5. /root/ls/ttft.pyで最初のチャンクまでの時間を測り、/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でストリームを開き、5チャンクだけ受け取ったあとで切断してください。GET /_debug/generatedが、直前のストリームのリクエストで、サーバーが実際に作ったトークン数を返すようにして(累積の合計もいっしょに載せておくと便利です)、その値を/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=<수>の4行を書いてください(プレースホルダーは数値です)。

参考

決定的なトークン生成器を作る

/root/ls/gen.pyにgenerate(prompt, n)を作成してください。同じプロンプトとnなら、いつも同じトークン列を返す必要があります。/root/ls/gen1.txtと/root/ls/gen2.txtを同じ引数で作って、2つのファイルの内容が同じである必要があります。

同じプロンプトに、いつも同じトークン列が出る必要があります。そうでなければ、採点も評価もできません。ハッシュをシードに使えば済みます。

非ストリーミングのエンドポイントを作る

/root/ls/server.pyを127.0.0.1:8170で起動してください。POST /generateは、{"prompt":"hello","max_tokens":20}を受け取り、{"text":"...","usage":{"prompt_tokens":<n>,"completion_tokens":20}}を返します。

まず、一度にまとめて返す形で作って、基準線を作ります。使用したトークン数もいっしょに返してください。

SSEでチャンクを送信する

GET /stream?prompt=hello&max_tokens=20は、Content-Type: text/event-streamで応答し、各トークンをdata: {"token":"..."}と空行で送ります。レスポンス本文に、data: で始まる行が20行以上ある必要があります。

コンテンツタイプと、イベントの区切りの規則が決まっています。イベントの間の空行を抜かすと、クライアントが認識できません。

終了シグナルを送る

ストリームの最後に、data: [DONE]を送ってください。

ストリームがいつ終わったかを知らせる、慣例的なマーカーがあります。

最初のトークンまでの時間を測る

/root/ls/ttft.pyで最初のチャンクまでの時間を測り、/root/ls/ttft.txtにttft_ms=<정수>(プレースホルダーは整数です)を書いてください。0より大きく3000未満である必要があります。

リクエストの開始から、最初のチャンクが届くまでの間です。全体の完了時刻と混同しないでください。

トークン間レイテンシの分布を出す

/root/ls/itl.txtに、count=<n> p50_ms=<수> p99_ms=<수>(プレースホルダーは数値です)を書いてください。countは19以上である必要があります。

チャンクごとに到着時刻を記録して、差を出します。平均だけでは足りません。

クライアントの中断時に生成を止める

max_tokens=100でストリームを開き、5チャンクだけ受け取ったあとで切断してください。GET /_debug/generatedが、直前のストリームのリクエストで、サーバーが実際に作ったトークン数を返すようにして(累積の合計もいっしょに載せておくと便利です)、その値を/root/ls/cancel.txtに、requested=100 generated=<n>として書いてください。generatedは30以下である必要があります。

切れたことを検知できなければ、止められません。サーバーが作ったトークン数を数えて、証明してください。

指標のレポートを作る

/root/ls/report.txtに、ttft_ms=<n>、itl_p50_ms=<수>、tokens=<n>、throughput_tps=<수>の4行を書いてください(プレースホルダーは数値です)。

前に測った値を、1つのファイルにまとめます。スループットは、生成したトークン数を総時間で割った値です。