連続バッチングスケジューラのシミュレーション
目標
静的バッチ処理と連続バッチ処理を、それぞれシミュレーションで実装して、スループットの差を数字で確認し、TTFTのSLOを満たす最大の同時実行数を探索する方法を身に付けます。
なぜ重要なのか
LLMの生成は、リクエストごとに出力長がまちまちです。あるリクエストは10トークン、あるリクエストは1,000トークンです。静的バッチ処理で32個をまとめると、31個が早く終わっても、最も長い1つが終わるまで、そのスロットが空いたままになります。GPUは、32個分のリソースを確保して、1個を処理します。連続バッチ処理は、デコードの反復ごとに、終わったスロットを、待機キューのリクエストで埋めます。コードでは1行の違いですが、スループットは2–5倍変わります。このラボは、GPUなしで、そのメカニズムだけをシミュレーションで再現します。最後のステップ8の順序が、実務の核心です。スループットを先に最大化して、レイテンシをあとで見ると、たいていSLOを守れません。TTFTの目標を先に決めて、それを満たす最大の同時実行数を探すのが、正しい順序です。
ステップ
/root/lb2/static.pyで、静的バッチ処理をシミュレーションしてください。リクエストは、/opt/fixtures/llms/requests.json(100件、それぞれprompt_tokensとoutput_tokensを持つ)を使い、バッチサイズは16です。/root/lb2/static.txtに、total_steps=<n> throughput_tps=<수> slot_util=<0~1 소수>(プレースホルダーは数値と、0から1の小数です)を書いてください。slot_utilは、0.7未満である必要があります。/root/lb2/continuous.pyで、連続バッチ処理をシミュレーションしてください。反復ごとに、終わったシーケンスを外して、待機キューから埋めます。/root/lb2/compare.txtに、static_tps=<수> continuous_tps=<수> speedup=<수>(プレースホルダーは数値です)を書いてください。speedupは、1.8以上である必要があります。max_num_seqs=32の上限を適用してください。/root/lb2/maxseqs.txtに、max_num_seqs=32 peak_running=<n>を書き、peak_runningは32以下である必要があります。- 流入率を、毎秒5、10、20、40件と変えながら、
/root/lb2/queue.csvに、rps,avg_queue,p99_wait_msのヘッダーと4行を書いてください。rpsが上がるほど、p99_wait_msが単調増加する必要があります。 /root/lb2/cost.pyは、prefill_ms = prompt_tokens * 0.05とdecode_ms = output_tokens * 8.0で、コストを分離してください。/root/lb2/cost.txtに、total_prefill_ms=<수> total_decode_ms=<수> decode_share=<0~1 소수>(プレースホルダーは数値と、0から1の小数です)を書いてください。/root/lb2/tune.txtに、target_ttft_ms=200 max_concurrency=<n> throughput_at_target=<수>(プレースホルダーは数値です)を書いてください。その同時実行数で、p99のTTFTが200以下である必要があります。
参考
- 連続バッチ処理の核心は、反復ごとのスロットの再充填の1行です。
- KVキャッシュが足りなくてシーケンスをプリエンプションすると、キャッシュを捨てて再計算するか、スワップする必要があるので、高くつきます。プリエンプションが頻繁だと、スループットがかえって下がります。
- プレフィックスキャッシングは、共通のシステムプロンプトがあるとき、TTFTを最大8倍まで減らします。
- よくあるミス1: 2つの方式を、異なるリクエストの集合で比較してしまうことです。
- よくあるミス2: TTFTを平均で見て、SLOを守ったと判断してしまうことです。p99で見る必要があります。
静的バッチ処理のシミュレーターを作る
/root/lb2/static.pyで、静的バッチ処理をシミュレーションしてください。リクエストは、/opt/fixtures/llms/requests.json(100件、それぞれprompt_tokensとoutput_tokensを持つ)を使い、バッチサイズは16です。
バッチを埋めて、すべてが終わるまで待ちます。リクエストごとに出力長が違うという点が核心です。
静的バッチ処理の指標を測る
/root/lb2/static.txtに、total_steps=<n> throughput_tps=<수> slot_util=<0~1 소수>(プレースホルダーは数値と、0から1の小数です)を書いてください。slot_utilは、0.7未満である必要があります。
スループットとスロット利用率を、いっしょに見ます。遊んでいるスロットがどれだけあるかが、問題の大きさです。
連続バッチ処理のスケジューラーを作る
/root/lb2/continuous.pyで、連続バッチ処理をシミュレーションしてください。反復ごとに、終わったシーケンスを外して、待機キューから埋めます。
反復ごとに、終わったシーケンスを外して、待機キューから埋めます。この1行が、差のすべてです。
2つの方式のスループットを比較する
/root/lb2/compare.txtに、static_tps=<수> continuous_tps=<수> speedup=<수>(プレースホルダーは数値です)を書いてください。speedupは、1.8以上である必要があります。
同じリクエストの集合で比較して初めて、意味があります。改善の倍数を計算してください。
同時実行数の上限を適用する
max_num_seqs=32の上限を適用してください。/root/lb2/maxseqs.txtに、max_num_seqs=32 peak_running=<n>を書き、peak_runningは32以下である必要があります。
無限に入れることはできません。KVキャッシュが、上限を決めます。
キューの深さとp99の待ち時間の関係を見る
流入率を、毎秒5、10、20、40件と変えながら、/root/lb2/queue.csvに、rps,avg_queue,p99_wait_msのヘッダーと4行を書いてください。rpsが上がるほど、p99_wait_msが単調増加する必要があります。
流入率を上げながら測ると、どの地点でp99が崩れるのかが見えます。平均は、しばらくの間は問題なさそうに見えます。
prefillとdecodeのコストを分けてモデリングする
/root/lb2/cost.pyは、prefill_ms = prompt_tokens * 0.05とdecode_ms = output_tokens * 8.0で、コストを分離してください。/root/lb2/cost.txtに、total_prefill_ms=<수> total_decode_ms=<수> decode_share=<0~1 소수>(プレースホルダーは数値と、0から1の小数です)を書いてください。
2つのコスト関数は異なります。プロンプトの長さに比例するものと、トークン数に比例するものを分けてください。
TTFTの目標を満たす最大の同時実行数を探す
/root/lb2/tune.txtに、target_ttft_ms=200 max_concurrency=<n> throughput_at_target=<수>(プレースホルダーは数値です)を書いてください。その同時実行数で、p99のTTFTが200以下である必要があります。
目標を先に決めて、それを満たす最大値を探します。スループットから先に最大化すると、SLOを守れません。