RX 6800でQwen3.8-Flash-Nextを動かす:Strataのプロンプト処理を高速化

rx6800local-llmcoding-agentsstrata

暗い机の上の層状の岩のブロック。ティールと琥珀色に光る層に小さな立方体が埋まり、細いティール色の光で横のグラフィックカードとつながっている

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/97、91,513秒、1,108秒22.5 tok/s
Strata Coder、json_schema9/96925秒29.6 tok/s
Strata Coder、スキーマをプロンプトに書く9/99664秒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
同じ設定で温度08/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/s28.1 tok/s12GB
--prefill 16384320 tok/s24.5 tok/s9GB
--resident-budget-gib 12273 tok/s20.5 tok/s28GB
両方307 tok/s21.4 tok/s29GB

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/s452 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/s29〜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/s439 tok/s494 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に戻すときはどちらかを止める。