VRAM 20 GB の Radeon で、社内の文章の下書きをローカル LLM に任せられるかを技術検証しました。20 GB だと、120B 級の MoE は一部をメインメモリに逃がさないと動かず、27B 級の密なモデルは量子化すれば丸ごと載ります。どちらを選ぶかで迷うところなので、llama.cpp で速さと VRAM を測り、下書きを作る手順にも通して比べました。

結論

  • Qwen3.8-27B を Q4_K_M にすると 16.8 GB で、文脈 32768 と合わせて VRAM 20 GB に全部載ります。 生成は 31.6 トークン/秒でした。
  • GPT-OSS Swallow 120B(MXFP4、61.3 GB)は載らないので、--n-cpu-moe 30 で専門家の一部をメインメモリに置きました。生成は 7.9 トークン/秒で、Qwen3.8-27B の約 1/4 です。
  • 作り直した同じ下書きの手順で、Qwen3.8-27B は約 29 分で3件とも検査を通り、Swallow は約 58 分で3件中2件でした。この比べ方では、Qwen3.8-27B が速さでも検査を通った数でも上回りました。
  • 推論の深さは要求の chat_template_kwargs.reasoning_effort で渡します。Qwen3.8 が受けるのは xhigh(既定)・medium・low で、gpt-oss の high は通りません。

試した環境

  • GPU: Radeon RX 7900 XT(gfx1100、VRAM 20 GB)
  • OS など: Ubuntu、ROCm 7.2.4
  • 推論: llama.cpp v0.6.0 を HIP でビルドした llama-server
  • 重み: 公式の配布物を取り、自分で GGUF に変換(第三者が配っている GGUF は使っていません)

比べたのは次の2つです。

  • GPT-OSS Swallow 120B RL v0.1(MXFP4): 東京科学大学の Swallow チームが gpt-oss を日本語向けに鍛え直したもの。117B の MoE で、1語ごとに使うのは約 5B です。
  • Qwen3.8-27B: 27B の密なモデル。線形注意(Gated DeltaNet)の層3つごとに、通常の注意の層が1つ入る作りです。

重みのライセンスは、どちらも Apache-2.0 です(執筆時点のモデルカードの記載)。

ビルドと変換

llama.cpp は v0.6.0 を HIP でビルドしました。CPU だけで約2分(366 段)です。量子化に使う llama-quantize は、あとから足してビルドしました(4 段・数十秒)。

git clone --branch v0.6.0 --depth 1 https://github.com/ggml-org/llama.cpp llama.cpp
export PATH=/opt/rocm/bin:/opt/rocm/llvm/bin:$PATH ROCM_PATH=/opt/rocm HIP_PATH=/opt/rocm
cmake -S llama.cpp -B llama.cpp/build-hip -G Ninja -DCMAKE_BUILD_TYPE=Release \
  -DGGML_HIP=ON -DGPU_TARGETS=gfx1100 \
  -DCMAKE_HIP_COMPILER=/opt/rocm/llvm/bin/clang++ -DCMAKE_PREFIX_PATH=/opt/rocm \
  -DLLAMA_CURL=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF
cmake --build llama.cpp/build-hip --target llama-server llama-cli -j 16
cmake --build llama.cpp/build-hip --target llama-quantize -j 8

GGUF への変換には、llama.cpp の requirements/requirements-convert_hf_to_gguf.txt だけを入れた venv を別に作りました(1.1 GB。torch は CPU 版 2.11.0)。変換はどちらのモデルも GPU を使いません。

Qwen3.8-27B は、v0.6.0 の変換器が Qwen3_5ForConditionalGeneration に対応していて、そのまま通りました。いったん BF16 の GGUF にしてから Q4_K_M に量子化します。

python llama.cpp/convert_hf_to_gguf.py "$SNAPSHOT" --outtype bf16 --outfile Qwen3.8-27B-BF16.gguf   # 2 分 49 秒
llama.cpp/build-hip/bin/llama-quantize Qwen3.8-27B-BF16.gguf Qwen3.8-27B-Q4_K_M.gguf Q4_K_M 16    # 2 分 41 秒
rm Qwen3.8-27B-BF16.gguf   # 途中の BF16 は 55 GB。量子化を変えたいときは作り直す

python は変換用の venv の物、$SNAPSHOT はダウンロードした重みの置き場です。量子化は imatrix なしの Q4_K_M(4.92 BPW)で、16.8 GB(15.6 GiB)になりました。画像も入力できるモデルですが、使わないので mmproj は作っていません。

Swallow は、そのままでは変換器が assert で落ちました。MoE のブロックの形が元の gpt-oss と違うためで、読み出しのときに形を戻すラッパーを通して変換しました(4 分 58 秒、RSS 4.9 GiB)。できた GGUF は、専門家が MXFP4、ほかが bf16 の 61.3 GB(57 GiB)です。

起こし方とサンプリング

Qwen3.8-27B は全部の層を GPU に載せます。

llama-server -m Qwen3.8-27B-Q4_K_M.gguf \
  --host 127.0.0.1 --port 8080 -c 32768 -ngl 999 -fa on --jinja -t 16 --parallel 1

Swallow は 20 GB に収まらないので、--n-cpu-moe 30 を足して専門家の一部をメインメモリに置きます。

llama-server -m GPT-OSS-Swallow-120B-RL-v0.1-MXFP4_MOE.gguf \
  --host 127.0.0.1 --port 8080 -c 32768 -ngl 999 --n-cpu-moe 30 -fa on --jinja -t 16 --parallel 1

サンプリングは、どちらもモデルカードの推奨に合わせました。Swallow は temperature 0.6・top_p 0.95・推論 medium、Qwen3.8 の考える版は temperature 1.0・top_p 0.95・top_k 20・min_p 0 です。

推論の深さは、OpenAI 互換の要求の本文に chat_template_kwargs.reasoning_effort を入れて渡します。

{"chat_template_kwargs": {"reasoning_effort": "medium"}}

迷いやすいのは値の名前です。gpt-oss 系と同じつもりで high を渡しても、Qwen3.8 では通りません。Qwen3.8 が受けるのは xhigh(既定)・medium・low です。

測った値

どちらも文脈 32768 で測りました。

GPT-OSS Swallow 120BQwen3.8-27B
GGUF専門家は MXFP4、ほかは bf16。61.3 GBQ4_K_M。16.8 GB
載せ方専門家の一部をメインメモリ(--n-cpu-moe 30)全部 GPU(-ngl 999)
VRAM15.6 GB17.8 GB
読み込み53 秒(ページキャッシュに載った後)約 13 秒
生成7.9 トークン/秒31.6 トークン/秒
プロンプトの処理短い入力で 17、5,600 トークンで 169 トークン/秒約 800 トークン/秒

生成の速さは GPT-OSS Swallow 120B が 7.9 トークン/秒、Qwen3.8-27B が 31.6 トークン/秒

120B の MoE は 20 GB に載らないので専門家をメインメモリに逃がしましたが、その形では生成の速さが 27B の約 1/4 になりました。

プロンプトの処理は Swallow 120B が短い入力で 17、5,600 トークンの入力で 169 トークン/秒、Qwen3.8-27B が約 800 トークン/秒

Swallow は、短い入力ほどプロンプトの処理が遅くなりました。短い入力では 17 トークン/秒、5,600 トークンの入力では 169 トークン/秒です。専門家の重みを GPU に送る固定費が効いているのではないかと見ていますが、確かめてはいません。

VRAM は Swallow のほうが 15.6 GB で余りがあり、--n-cpu-moe は 27 前後まで下げられる見込みです。これも試していません。

下書きの手順で比べる

速さだけでは下書きに使えるかが分からないので、下書きを作る手順にも通して試しました。1回分は、LLM に十数回問い合わせて3件の下書きを作る手順です。できた下書きは、コードで書いた検査に通します。

Swallow で最初に回した手順では、約 20 分で3件とも検査を通りました。ところが推論を high にし、プロンプトを足した2回目は約 31 分かかり、検査を通ったのは6件中1件でした。入力の取り違えや、存在しない参照先が出ています。

まとめて扱われて混ざり合った紙片の山と、型枠を1枚ずつ通って揃ったカードになっていく紙片

そこで手順のほうを作り直しました。1件ずつ扱い、出力を JSON スキーマで縛る形です。この作り直した同じ手順で、2つのモデルを比べました。

作り直した同じ手順で、下書き1回分は Swallow 120B が約 58 分で3件中2件、Qwen3.8-27B が約 29 分で3件とも検査を通った

  • Swallow: 約 58 分。3件中2件が検査を通った
  • Qwen3.8-27B: 約 29 分。3件とも通った。いちばん長くかかった段は、推論 xhigh で約 1.4 万トークンを出して 8 分かかった

遅く、検査を通る数も少なかったので、Swallow は落とし、重みも GGUF も消しました。

確かめていないこと

  • Swallow の --n-cpu-moe を 27 前後まで下げられるか、下げたときの速さ
  • Qwen3.8-27B の量子化の段(Q5・Q6 など)や、imatrix 付きとの質の差
  • Swallow で短い入力が遅い原因(上の見立てのまま)
  • 下書きの手順を何回も回したときの、検査を通る割合の差

おわりに

VRAM 20 GB では、120B の MoE を専門家をメインメモリに逃がして動かすより、27B の密なモデルを Q4_K_M で丸ごと載せるほうが、作り直した同じ下書きの手順で1回ずつ(各3件)比べたところ速く、検査を通った数も多くなりました(Qwen3.8-27B は3件とも、Swallow は3件中2件)。同じくらいの VRAM でモデル選びに迷っている方の目安になればうれしいです。

※ llama.cpp の変換器と llama-server の振る舞い、モデルカードの推奨の設定、ライセンスの記載は執筆時点の情報で、いまは変わっている可能性があります。