RX 6800でQwen3.8-Flash-Nextを動かす:Strataのプロンプト処理を高速化
Strataは、1250億パラメータのMoE(Mixture of Experts)モデルQwen3.8-Flash-Nextを、グラフィックカード1枚とシステムメモリで動かすソフトだ。これまでRX 6800では、llama.cppでQwen3.8-27Bを動かしてコードを書かせていた(前回の記事)。このカードにStrataを入れて修正課題をもう一度試した。プロンプト処理は新しいAMDカードでStrataが公表している値より遅く、その理由も調べた。
32GBのPCでのセットアップ
RX 6800はOCuLinkのドックに入れ、GMKtec NucBox K8 Plus(Ryzen 7 8845HS)につないでいる。Strataが起動時に測ったホストからGPUへの転送速度は7.1GB/sだった。メモリは32GBだが、Radeon 780Mが一部を確保するので、Windowsから見えるのは28.8GBになる。
START-HERE.bat --checkはRX 6800(gfx1030、Strataでは未検証の扱い)を認識し、フルモデルはどのサイズもメモリ不足と判定した。使えたのはCoder版だけだった。ISTA-DASLabが512個のエキスパートのうち256個を削ったコード向けのモデルで、ダウンロードした版にはIQ1_Mという名前が付いている。
エキスパートの量子化は層ごとに違い、gateとupにはIQ2_S、IQ3_XXS、IQ3_S、IQ4_NL、downにはIQ4_NLかQ2_0が使われている。セットアップは低メモリ用の設定を選び、約8.4GiB分のエキスパートをGPUに置いた。残りの大半はシステムメモリに置き、それ以外をSSDから読む構成になった。
セットアップでは、モデルの2つのファイルとQwenのマルチトークン予測(MTP)の層を合わせて約65GBダウンロードした。ROCm込みのAMD用エンジンも取得した。全体で28分かかり、サーバーの読み込みは1分かからなかった。
修正課題で比べる
課題では、38項目のブラウザ検証にすべて合格するタワーディフェンスのゲームにバグを仕込む。モデルは検索と置換のパッチをJSONで返し、3ラウンド以内に38項目すべてに合格すれば解けたことにする。仕込んだ10件のうち1件はどの検証にも引っかからないので、数えるのは9件だ。プロンプトはどれも約1万2千トークンある。
| モデル | 解けた数 | 1回目で解けた数 | 修正にかかった時間 | 生成速度 |
|---|---|---|---|---|
| Qwen3.8-27B、llama.cpp(2回) | 9/9、9/9 | 7、9 | 1,513秒、1,108秒 | 22.5 tok/s |
Strata Coder、json_schema | 9/9 | 6 | 925秒 | 29.6 tok/s |
| Strata Coder、スキーマをプロンプトに書く | 9/9 | 9 | 664秒 | 28.2 tok/s |
Strataは1万2千トークンのプロンプトを毎秒約310トークンで処理した。似た長さのプロンプトで27Bが出した速度の2倍になる。生成も速く、これはおもにQwenのMTPがトークンを先に予想し、モデルがそれを確かめる仕組みによる。予想したトークンの約88%がそのまま使われた。
Strataでは、1回目はjson_schemaでJSON出力を指定し、2回目はスキーマをプロンプトに書いた。llama.cppはresponse_format: json_schemaを文法に変換し、トークンを1つずつその文法に従わせる。Strataはスキーマの内容をモデルに伝え、答えが出てから検証する。
1回目の実行では、モデルがJSONオブジェクト以外を返したことが2回あり、そのたびに1ラウンドを失った。該当する2つのプロンプトを、下の5通りの指定方法でそれぞれ4回ずつ送り直した。指定方法ごとに計8回になる。
| 出力指定 | 正しいJSONの数 |
|---|---|
json_schema、温度0.2(課題の設定) | 6/8 |
| 同じ設定で温度0 | 8/8 |
json_object、スキーマはシステムプロンプトに書く | 8/8 |
response_formatなし、スキーマはシステムプロンプトに書き、JSONはクライアントで取り出す | 8/8 |
Anthropic形式のエンドポイントで、返答の先頭を{で始めておく | 8/8 |
返答を{で始める方法は、修正課題を通して実行すると失敗した。サーバーのキャッシュにないプロンプトでは、Strataが{をそのまま返し、モデルがすぐに返答を終えた。最後の比較では、llama.cppでも使えるようにスキーマをシステムプロンプトに書いた。
プロンプト処理の時間の内訳
StrataのドキュメントにはRX 9070 XTでCoder版のプロンプト処理が毎秒1,420トークンとある。最初はSSDとx4の接続を疑った。プロンプトを処理する単位を大きくする--prefill 16384と、メモリに固定するエキスパートの量を決める--resident-budget-gib 12を試した。SSDから読む量は変わり、プロンプト処理は毎秒273〜320トークンだった。
| 設定 | プロンプト処理 | 生成 | 1回あたりのSSD読み込み |
|---|---|---|---|
| セットアップの既定 | 308 tok/s | 28.1 tok/s | 12GB |
--prefill 16384 | 320 tok/s | 24.5 tok/s | 9GB |
--resident-budget-gib 12 | 273 tok/s | 20.5 tok/s | 28GB |
| 両方 | 307 tok/s | 21.4 tok/s | 29GB |
SSDから何も読まなかった回でも、プロンプトの処理に36秒かかった。STRATA_PREFILL_TIMING=1を付けると、プロンプト処理のGPU時間が段階ごとに出る。38.9秒のうちいちばん長いのは線形アテンションの層で9.4秒、メモリからエキスパートが届くのを待つ時間が4.8秒、hyper-connectionの混合が4.5秒だった。
ROCBLAS_LAYER=2でプロンプト1回分のrocBLAS呼び出しをすべて記録すると、BF16の行列積が970回、FP16が600回あり、どれも出力はFP32だった。Strataに付いてくるROCm(10.2.0a20260930)のrocBLASには、gfx1030向けに調整されたカーネルがある。対応するのはFP16入力・FP16出力、int8、FP32の計算で、それ以外の組み合わせには汎用カーネルを使う。
最初は、古いNVIDIAカード向けのStrataの処理に合わせて、BF16の入力をFP16に変換してから行列積を計算した。PyTorchで測ると、この形の行列積はFP16のほうがBF16より4倍速かった。エンジンでは出力がFP32になるため、変換しても調整済みのカーネルを使えず、処理は遅くなった。PyTorchは出力もFP16にしていたので、調整済みのカーネルで計算できた。エンジンと同じ呼び出しをする小さなHIPプログラムで、BF16の行列積のうちいちばん大きい形を測ると、BF16入力で10.85ミリ秒、FP16入力で10.69ミリ秒だった。
FP32同士の行列積には調整済みのカーネルがあり、FP16やBF16の入力は誤差なしでFP32に変換できる。そこで、FP32の行列積を計算するSGEMMを試した。1万2千トークンのプロンプト1回分で、FP16の行列積は元の呼び出しだと14.1秒、SGEMMでは変換込みで4.65秒だった。いちばん大きい形(N 10240、T 8192、K 2560)は86.7ミリ秒から27.7ミリ秒になった。
行列積をSGEMMで計算する
Strataの行列積を扱うコードに約90行を加えた。gfx103xのカードでは、出力の列が64以上ある16ビットの行列積について、入力をFP32に変換してSGEMMを呼ぶ。列が少ない行列積は、変換の手間のほうが大きいので元の呼び方のままにした。FP32用のバッファが確保できないときも元の呼び方で計算する。STRATA_RDNA2_SGEMM=0でこの経路を止められるので、同じ実行ファイルで両方を比べた。新しい1万2千トークンのプロンプトを、それぞれ4回ずつ処理させた。
| 元の行列積 | SGEMM | |
|---|---|---|
| プロンプト処理 | 312 tok/s | 452 tok/s |
| 1回あたりのGPU時間 | 37.2秒 | 25.5秒 |
| 線形アテンションの層 | 9.4秒 | 2.8秒 |
| アテンションの射影 | 3.6秒 | 1.4秒 |
| hyper-connectionの混合 | 3.85秒 | 2.84秒 |
| 生成 | 29〜34 tok/s | 29〜35 tok/s |
ランダムな入力で比べると、2つの計算結果の差は、最大の出力の100万分の4.3以下だった。調べた10通りの形のすべてで、SGEMMの結果はfloat64で計算した参照値と同じかそれ以上に近かった。修正課題は9件とも1回目で解け、モデルの処理時間は458秒から374秒に減った。全体では888秒から797秒になった。各課題のブラウザ検証にも約20秒かかる。
StrataのHIP向けテストは、変更したブランチと手を加えていないmainのビルドで結果が一致した。67件中59件が合格し、2件がスキップ、6件が失敗した。失敗したテストのうち4件は、手元にないモデルファイルが必要なものか、CUDA専用の機能を試すものだ。
残る2件のうち、doorbellのテストは、ドライバーを呼ばなくてもマップしたメモリへの書き込みがCPUから見えると想定している。エンジン本体はこの問題を回避している。もう1件はMMQのテスト用プログラムで、このカードではすべての項目が失敗する。原因はまだ分かっていない。
エンジンでMMQを有効にした場合と切った場合を比べると、短いプロンプトの答えは一致し、7千トークンの答えも内容は同じだった。7千トークンの処理は、MMQを有効にしたほうが約9%速かった。SGEMMへの変更はNiko1221/Strata#1006として本家に送った。
先に提案されていたFP16での修正
#1006を出したあと、xjc10さんからNiko1221/Strata#835を教えてもらった。RDNA2で同じ行列積が遅い問題に対して、先に出ていたプルリクエストだ。#835はBF16の重みをFP16に変換し、各層への入力はカーネルから直接FP16で書き出す。rocBLASはこれらをFP16入力・FP16出力の行列積として計算する。この組み合わせにはgfx1030向けに調整されたカーネルがあり、結果はそのあとFP32に変換する。FP16で表せる値は65504までなので、#835にはSTRATA_F16_RANGE=1も入っていて、プロンプト処理で出た値の最大を表示できる。
#835をgfx1030向けにビルドし、3つのエンジンを同じ日に、新しい1万2千トークンのプロンプトで比べた。今回のSGEMMは毎秒439トークンで、前の測定の452トークンより少し低かった。
| 元の行列積 | SGEMM(#1006) | #835 | |
|---|---|---|---|
| プロンプト処理 | 310 tok/s | 439 tok/s | 494 tok/s |
| 1回あたりのGPU時間 | 38.1秒 | 26.2秒 | 23.2秒 |
| 線形アテンションの層 | 9.4秒 | 2.8秒 | 1.9秒 |
| アテンションの射影 | 3.6秒 | 1.4秒 | 0.9秒 |
STRATA_F16_RANGE=1を付けると、4回のプロンプトすべてで同じ最大値が出た。各層への入力は105.3、BF16の重みは11.56、FP16で出力した行列積の結果は424.8だった。FP16の範囲を超えた値やNaNはなかった。修正課題は#835でも9件とも1回目で解けた。モデルの処理時間はSGEMMの374秒に対して307秒で、全体では740秒だった。
範囲のチェックには、このCoder版と今回のプロンプトを使った。SGEMMで行ったfloat64の参照値との比較は、#835ではしていない。
xjc10さんは、gfx103xでは#835を既定にし、#1006をその上に載せ直してSTRATA_RDNA2_SGEMM=1でFP32の計算を選べるようにする案を出した。FP16の上限に近い値が出るモデル向けで、入力をFP16やBF16からFP32に変換する際は誤差が増えず、行列積の計算ではFP32の丸めが生じる。私もプルリクエストのコメントでこの案に賛成した。その後、Strata 0.1.40で#835はSTRATA_HIP_PROMPT_F16=1で有効にする設定として取り込まれた。既定では無効で、gfx103xのカードではエンジンがこの設定を案内する。同じ日にメンテナーがリポジトリの履歴を書き換え、mainが強制プッシュされたときに、GitHubが#1006を自動で閉じた。メンテナーは、却下したわけではないと説明している。
手元のPCでは今、公式の0.1.40のエンジンをSTRATA_HIP_PROMPT_F16=1で動かしている。新しい1万2千トークンのプロンプトは、毎秒約560トークンで処理できた。
生成
700トークンの返答では、生成速度は毎秒40トークン前後で変わらない。STRATA_DECODE_TIMING=1で見ると、エキスパートを参照する際に必要なデータがVRAMにあった割合は61%だった。VRAMにない分は、CPUがシステムメモリ上のデータを使って計算した。生成の1ステップ93.6ミリ秒のうち、この処理に33.6ミリ秒かかった。CPUで計算するスレッドの数や、PCIe経由でGPUに回す割合を変えても、生成は毎秒39.5〜40.6トークンのままだった。スレッドを15にして、8コアのCPUに対して多すぎる数にすると、毎秒10トークンまで落ちた。
Coder版のエキスパート23.4GiBがすべて載るカードなら、VRAMに見つからない分はなくなる。Strataのドキュメントでは、32GBのRadeon AI PRO R9700で生成が毎秒45〜60トークンとなっている。大きいカードは試していない。
Hermes
Hermesの接続先を27BからStrataのOpenAI互換のエンドポイントに変えた。ツール呼び出しは標準の形式で返ってきた。Hermesは毎回、システムプロンプトとツールの定義で約1万4千トークンを送る。Strataは最初の32秒でこれを処理し、以降のターンではキャッシュから再利用したので、1ターンにかかるのは5〜7秒だった。4ターンのファイル操作の課題は93秒で終わった。RX 6800ではStrataと27Bのllama-serverを同時に動かせないので、27Bに戻すときはどちらかを止める。