このページの目次

ローカルLLM導入ノート

消費向けGPUでローカル大規模言語モデルを動かす場合、核心は予算管理の問題です。モデルの重み、KVキャッシュ、デスクトップ環境が同じVRAMを奪い合っており、どのように配分するかによって、実行可能なモデルのサイズやコンテキストの長さが決まります。本稿では24GBのRX 7900 XTXを例に、モデル選定の方法と完全に再現可能な設定手順を示し、16GB / 12GBのGPUへの拡張についても言及します。記載されているコマンドはLinux互換であり、WSL2でも同様の手順で動作します。

1. 制約: 解くべき不等式

ローカル導入におけるすべての決定——どの量子化を選ぶか、KVキャッシュの精度をどう設定するか、コンテキストをどの程度確保するか——は、すべて同じ問題を解くことに他なりません:

モデル重み + KVキャッシュ + 実行バッファ + デスクトップ余裕分 ≤ VRAM容量

この不等式は24GB、16GB、12GBのいずれのGPUにも適用されます。重要なのは右辺の値が異なるだけで、これを予算として配分することが、導入における普遍的な出発点となります。本機は7900 XTX (24GB)を搭載しており、以下ではこれを例に進めていきます。

2. 第一段階: GPUに合ったバックエンドを選択

llama.cppは複数のGPUバックエンドをサポートしており、⁠ベンダー固有のAI学習スタック(CUDA Toolkit / ROCm / oneAPI)をインストールする必要はありません⁠。GPUドライバに付属するランタイムのみを使用します。お使いのGPUに合わせて選択してください:

お使いのGPUバックエンドコンパイルオプション
NVIDIA(GTX 10シリーズ以降)CUDA-DGGML_CUDA=ON
AMD(RXシリーズ / Radeon VII)Vulkan-DGGML_VULKAN=ON
Intel ArcVulkan-DGGML_VULKAN=ON
Apple Silicon(Mシリーズ)Metal-DGGML_METAL=ON

よくある質問:

「NVIDIAカードにCUDA Toolkitをインストールする必要があるのか?」——いいえ。llama.cppはGPUドライバに付属するCUDAランタイムのみを依存関係として必要とし、数GBに及ぶCUDA Toolkitのインストールは不要です。

「AMDカードでなぜROCmではなくVulkanなのか?」——ROCmはPyTorchやvLLMでの学習用であり、インストールが面倒です(特にWindows環境では)。GGUF形式での推論には不要です。VulkanはGPUドライバに付属するランタイムを使用するため、LinuxおよびWindowsでそのまま動作します。

「内蔵GPUとディスクリートGPUの両方が搭載されている場合どうするか?」——Vulkanユーザーは GGML_VK_VISIBLE_DEVICES=0 でディスクリートGPUをロックしてください(0 = 最初のGPU)。CUDAユーザーは CUDA_VISIBLE_DEVICES=0 を使用します。Metalユーザーは通常不要です。

Windowsユーザー: 以下のコマンドはすべてWSL2内で実行してください。WSL2はGPUに直接アクセス可能です(NVIDIAカードは公式ドライバをインストールすればそのまま使用可能; AMDカードはカーネル5.15以降かつAMD公式WSLドライバのインストールが必要です)。コンパイルおよびインストールの手順はLinuxと同じです。まず wsl --install でディストリビューションをインストールし、以降の手順に従ってください。

ここからが核心問題です。VRAMをどのように配分するか。

3. 選び方: VRAMを効果的に使う

調整可能なパラメータは4つあります。パラメータ数と量子化レベルはダウンロード時にファイル選択によって決定されます。コンテキスト長とKV精度は起動時のパラメータであり、毎回変更可能です。まずは本機での選択例を示し、その後、ご自身のGPUで同様の意思決定を行う方法を解説します。

本機での選択: 節約したVRAMはKV精度ではなくコンテキスト長に投入する

Qwen3.6-35B-A3B (MoE、総パラメータ数は35Bだが、各ステップでアクティブになるのは3Bのみ。そのため、小モデルのような速度と大モデルのような知識量を両立)を選択します。真のトレードオフは、節約したVRAMをどこに投入するか——「どの程度の長さの会話を記憶できるか」に使うのか、「KVをどの程度正確に記憶させるか」に使うか——です。

24Gの使い道: 節約したVRAMはKV精度ではなくコンテキスト長に投入 本機の実測値(24Gカード · idle状態) 24G モデル IQ4_XS 16.96 GiB KV·q4_0 192K+バッファ 残り2.2G 実測 used 21.8G / free 2.2G — 余裕を持って収まっており、ぎりぎりではない。 同じGPUでも、KV精度がコンテキスト長の上限を決定する(ビット幅に反比例して推定) f16 KV ≈ 48K Q8_0 KV ≈ 96K q4_0 KV(本機) 192K ✓ 重要なトレードオフ: エージェントやコーディングでは長いコンテキストが必要である。高精度のKVで短いウィンドウを得るよりも、q4_0 KV でウィンドウを192Kに広げるべきだ——節約したVRAMは「KVの精度」ではなく「記憶できる長さ」に投入する。

24GBの内訳:

  • モデル重み 16.96 GiB(IQ4_XS)。MoEにより高速に動作し、IQ4_XSにより十分な品質を確保。
  • KVキャッシュは q4_0 + flash-attn を使用し、192Kのコンテキストを約5GBに圧縮。f16にすると、同じコンテキスト長で4倍のVRAMが必要となりオーバーフローする。Q8にすると半分しか収まらない。KV精度が生成品質に与える影響は、コンテキスト長に与える影響よりもはるかに小さい⁠——したがって、この予算はすべて長さに投じる。
  • 実測常駐 21.8G、残り 2.2G。アイドル時は低消費電力モードに落ち、デスクトップ動作にも支障なし。

初期には Q3_K_XL + 短いコンテキストの保守的な構成で試したが、IQ4_XS + q4_0 KVに切り替えた結果、品質、速度、コンテキスト長のバランスがさらに良くなった。デスクトップの負荷が一時的に高まった場合は、Q3グレードに切り替えて対応すればよい。

どのパラメータをいつ調整するか

ご自身のGPUに適用する場合、4つのパラメータは2つのグループに分けられます:

選定は2ステップ: ダウンロード時に「パラメータ数+量子化」を決定、起動時に「コンテキスト+KV」を決定 ① ダウンロード時 · ファイルに刻み込まれ、後から変更不可 パラメータ数 モデルリポジトリを選択 · 8B / 14B / 35B-A3B 重みの量子化 どの .gguf を選ぶか · Q4_K_M / IQ4_XS / Q8_0 GGUF:~/models/…-IQ4_XS.gguf ② 起動時 · 毎回調整可能 コンテキスト -c 196608(192K) KV精度 -ctk / -ctv q4_0(必要: --flash-attn) llama-server を起動 より大きなモデル / より高品質に変更したい場合 → ファイルを再ダウンロード。より長いコンテキスト / より省メモリ化したい場合 → 起動パラメータのみを変更すればよい。

ステップ1: パラメータ数と重みの量子化を選択(ダウンロード時に決定)

パラメータ数の選択は、後述の「グレード表」を参照——24GBでは30–35B、16GBでは14B、12GBでは8Bが目安。

重みの量子化の選択⁠: 同じモデルでもHugging Faceには複数のGGUFファイルが公開されており、ファイル名の接尾辞が量子化レベルを示す:

  • 数字 ≈ ビット数: Q8_0(8bit、ほぼ無損失、最大サイズ) > Q6_K > Q5_K_M > Q4_K_M(4bit、最も一般的なバランス点) > Q3_K_M(メモリ節約型、品質低下)。
  • IQ4_XS / IQ3_XXS は i-quant: 同じビット数でもサイズが小さく、品質はほぼ同等。imatrixが必要(パブリッシャーが既に付与済み)。VRAMが限られている場合はこれを使用。
  • _S / _M / _L = 小 / 中 / 大 バリエーション。同じビット数では _M が最も一般的。

選び方⁠: GGUFファイルのサイズ ≈ 占有するVRAM。重み用予算に対してファイルサイズを照合し、収まる最大のサイズを選ぶ:

重み用予算 = VRAM - KVキャッシュ - デスクトップ余裕分(約2Gを残す)
例: 24 - 5(192K q4_0) - 2 ≈ 17G → 35Bモデルは IQ4_XS(実質 16.96G)がちょうど良い;
      Q5_K_M(~24G)にすると収まらない。

経験則による優先順位: まず Q4_K_M を目指す。収まらない場合は IQ4_XSIQ3 に下げる。余裕がある場合は Q5_K_M / Q6_K に上げる。Q3以下はやむを得ない場合を除いて使用しない。

ステップ2: KV精度を決定(起動時に調整)

KV精度とコンテキスト長はどちらも起動パラメータであり、毎回調整可能。KVには3つのスイッチがある:

スイッチ役割一般的な値
--flash-attn / -fa量子化KVの前提条件。必須オン / オフ
--cache-type-k / -ctkKキャッシュの精度f16(デフォルト) / q8_0 / q4_0
--cache-type-v / -ctvVキャッシュの精度同上

KとVは別々に設定可能。精度の段階は f16 → q8_0 → q4_0 で、占有VRAMは概ね 1 → 1/2 → 1/4——これがq4_0でf16の約4倍のコンテキスト長を実現できる理由である。3つの典型的な設定:

# アグレッシブ: 長コンテキスト優先
llama-server ... -fa -ctk q4_0 -ctv q4_0 -c 196608
# コンサーバティブ: 品質優先、コンテキスト長を半分
llama-server ... -fa -ctk q8_0 -ctv q8_0 -c 98304
# 非対称: Kは量子化により敏感なため、極限まで省メモリ化する場合はKを維持しVを圧縮
llama-server ... -fa -ctk q8_0 -ctv q4_0

調整結果の検証方法⁠: 起動ログに KV self size = ... が表示されるので、そこから占有サイズを確認。品質面ではq8_0はほぼ無損失、q4_0は日常会話やコーディングではほとんど知覚できないが、長文の正確な検索では occasionally 弱点が見える。VRAM使用量の確認コマンドはプラットフォームによって異なる: nvidia-smi(NVIDIAカード)、cat /sys/class/drm/card0/device/mem_info_vram_used(AMD Linux)、タスクマネージャー → パフォーマンス → GPU(Windows)。

注意点: -fa をオフにした状態で -ctv q4_0 を設定すると、エラーまたは無視される——量子化Vキャッシュはflash attentionに依存するため。

異なるVRAM容量でのスタート地点

同じ予算の式を適用して推計(⁠これらは概算値であり、ご自身のGPUで実測して調整すること⁠):

VRAM現実的な選択
24G30–35B MoE(IQ4/Q4) または 32B Dense IQ4; 長コンテキストは q4_0 KV で実現
16G14B Dense Q4–Q5、または 30B MoE Q3 + 適度な層のオフロード; 中程度コンテキスト
12G8–9B Q4–Q5 が快適(例: Qwen3-8B GGUF ~5.4G)、または 14B Q4 でコンパクト
8G7–8B Q4、短コンテキスト

VRAM不足時の優先度⁠: まずKV精度を下げる(f16→q8_0→q4_0)してコンテキスト長を確保 → 次にコンテキスト長を短縮 → 最後により小さなモデルまたはより強力な量子化に変更。デスクトップやゲームも同じGPUを共有していることを忘れず、数GBは確保すること。

意思決定が完了したら、モデルをダウンロードして実行する。

4. 実行: インストールから最初の応答まで

llama.cpp をインストール⁠(server + 対応バックエンド付き、優先度順):

  1. 公式リリースのバイナリを使用——全プラットフォーム共通。ダウンロードして解凍するだけで使用可能。各バックエンドが含まれている。
  2. Linuxディストリビューションにはパッケージが存在する: apt install llama-cpp(Debian/Ubuntu)、pacman -S llama.cpp-vulkan(Arch)。
  3. 上記がない場合はソースからコンパイル(セクション2で選択したスイッチに <YOUR_BACKEND> を置き換える、例: -DGGML_CUDA=ON):
git clone https://github.com/ggerganov/llama.cpp --depth 1
cd llama.cpp
cmake -B build <YOUR_BACKEND> -DLLAMA_CURL=ON
cmake --build build --config Release -j
# 生成物は build/bin/ にある

モデルをローカルモデルディレクトリにダウンロード⁠:

pip install -U "huggingface_hub[cli]"
# HFで "<モデル名> GGUF" を検索し、unsloth / bartowski の量子化リポジトリを探す
hf download unsloth/Qwen3.6-35B-A3B-MTP-GGUF \
  --include "*IQ4_XS*.gguf" --local-dir ~/models

大規模モデルのGGUFは分割されていることが多く(…-00001-of-0000N.gguf)、--include "*IQ4_XS*" で一度にすべてダウンロード。llama.cpp は最初のファイルにアクセスすると、自動的に残りをロードする。

起動⁠(単一モデル、初回試用に適す):

llama-server \
  -m ~/models/Qwen3.6-35B-A3B-MTP-UD-IQ4_XS.gguf \
  -ngl 99 -c 196608 \
  --flash-attn --cache-type-k q4_0 --cache-type-v q4_0 \
  --host 0.0.0.0 --port 18080

Vulkanユーザーで複数のGPUがある場合は、前に GGML_VK_VISIBLE_DEVICES=0 を追加してディスクリートGPUをロック。CUDAユーザーはスキップ。

http://localhost:18080 はOpenAI互換エンドポイント。動作確認:

curl http://localhost:18080/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-3.5-turbo","messages":[{"role":"user","content":"hi"}]}'

パス内の MTP は何か、なぜunslothのGGUFを使用するのか、使わなくてもよいか——これらは次のセクション5で詳しく解説する。

単一モデルのコマンドは試用に適しているが、常駐運用では ルーターモード を推奨する(models.iniで複数のモデルを一元管理し、必要に応じてLRUでアンロード)。MTP加速や潮汐スケジューリングもルーターモードを基盤としているため、以下で詳しく解説する。

5. 高速化: MTP投機的デコーディング

これは何か

MTP(Multi-Token Prediction)は投機的デコーディング技術である: 逐次トークン生成ではなく、1回のフォワードパスで複数のトークンを予測する。本機では Qwen3.6-35B-A3B(IQ4_XS) で実測: MTPなしで約75 t/s、MTPありで約111 t/s、約50%の高速化。使用するモデルや量子化レベルによって、実際の向上幅は異なる。

標準的なllama.cppモデルにはこの機能がない——これは unsloth(GGUF量子化と高速化を専門とするチーム)が量子化時にGGUFにドラフトヘッダー(draft head)を埋め込んだことによる。ドラフトヘッダーはまず次の2トークンを「予測」し、メインモデルが1回のフォワードパスで並列検証する。予測が合えば利益、外れればロールバック。独立したドラフトモデルは不要で、追加のVRAMも消費しない:

MTP投機的デコーディング: 1回のフォワードで複数のトークンを生成 MTPなし 逐次トークン フォワード → t₁ フォワード → t₂ フォワード → t₃ … 各フォワードで1トークンのみ生成 MTP ドラフトヘッダー + 検証 ドラフトヘッダーが予測 t₁, t₂ (1回で2トークン) メインモデルが1回のフォワード t₁,t₂ を並列検証 受け入れ ✓✓ / ✓✗ 受け入れ率 ~52% `spec-draft-n-max=2`, ドラフト受け入れ率 ~52% → 実測 tg ~111 t/s。ドラフトヘッダーはGGUFに内蔵されており、追加のスロットを消費しない。

探し方、MTPを使わない場合

MTPはモデル本来の機能ではなく、unslothが量子化時に追加したものである。 したがって:

  • HFで unsloth/<モデル名>-MTP-GGUF を検索した場合のみMTP版が存在する。例: unsloth/Qwen3.6-35B-A3B-MTP-GGUF。bartowskiなどの標準的な量子化リポジトリにはない。
  • ファイル名に MTP が含まれている必要がある。例: …-MTP-UD-IQ4_XS.gguf。含まれていなければドラフトヘッダーがなく、投機的デコーディングは実行できない。
  • すべてのモデルにunsloth製のMTP版があるわけではない⁠——unslothは主にQwenシリーズに追加している。ない場合は通常版のGGUFを使用すればよく、MTP加速なしで動作する。

MTPを使用しない場合の設定⁠: ダウンロードコマンドから MTP を削除し、通常のリポジトリ(例: bartowski/Qwen3.6-35B-A3B-GGUF)に置き換える。設定ファイルから spec-typespec-draft-n-max の2行を削除し、他は変更しない。

ルーターモードと完全なプリセット

複数のモデルを管理したり、起動時に自動起動させたりする場合は、⁠ルーターモードにアップグレードする: すべてのモデルとパラメータを ~/.config/llama.cpp/models.ini に書き込み、serverは --models-preset でロードし、--models-max 1 で同時に常駐するモデルを1つのみに制限(24GBでは1つのみ収まる)。LRUで自動的にアンロードする:

# ~/.config/llama.cpp/models.ini
[qwen3.6-mtp-instruct]
model = ~/models/Qwen3.6-35B-A3B-MTP-UD-IQ4_XS.gguf
ngl = 99                      # すべての層をGPUに(99=all)
ctx-size = 196608             # 192K コンテキスト
chat-template = chatml        # OpenAI 対話テンプレート。ツール不使用時も設定を推奨
chat-template-kwargs = {"enable_thinking": false}
flash-attn = 1
cache-type-k = q4_0
cache-type-v = q4_0
spec-type = draft-mtp          # 以下の2行はMTPモデルのみ必要
spec-draft-n-max = 2

注意点: chat-template=chatml は明示的に設定する必要がある——モデル本来のテンプレートを使用すると、ツール呼び出しのプレフィックス部分で誤ったトークンが生成され、早期のEOS(llama.cpp #19513)を引き起こす可能性がある。MTP不使用、ツール呼び出し不使用の場合でも設定が必要。

新しいモデルの追加はINIファイルに1行追加するだけで、Open WebUIのリストは自動的に更新される。systemdユニットと有効化コマンドは「完全な設定」セクションを参照。

6. 常駐スケジューリング: モデルはオンライン、消費電力は潮汐(Linux)

速く動くことは一時的であり、常駐して無駄を省くことが日常である。以下はLinux systemdとAMD sysfsインターフェースを基盤としている(WSL2ユーザーは本节を直接参照、ディストリビューションにsystemdが搭載済み。純粋なWindowsユーザーはスキップ、核心となる考え方は同じ: llama-serverのバックグラウンドプロセスを管理し、GPU負荷に応じて消費電力モードを切り替える):

VRAMの潮汐: モデルは常駐、消費電力と使用量は負荷に応じて変動 アイドル常駐 モデルはVRAMに常駐 · GPUモード 0 低消費電力待機 推論中 gpu_busy ≥ 50% → モード 5 COMPUTE 全速 ゲーム中 gamemode → llama-server を停止 全VRAMをゲームに提供 リクエスト到着 アイドル > 90s → モード降格 ゲーム起動 終了 → ウォームアップ ~6s

3つのメカニズム:

① 起動時ウォームアップ。 ルーターの遅延ロードにより、systemd ExecStartPost で起動後にリクエストを1回送信してモデルをVRAMにロードする。ウォームアップペイロードは -d @ファイル 形式でなければならない——インラインJSONはsystemdによって引用符が削除されるため。warmup.json の中身は1行: {"model":"qwen3.6-mtp-instruct","messages":[{"role":"user","content":"hi"}],"max_tokens":1}

② 消費電力は実際の負荷に応じてモードを切り替える。 llama-gpu-sync.timer は15秒ごとにトリガーされ、gpu_busy_percent ≥ 50% の場合にのみCOMPUTE(モード5)に切り替え、アイドルが90秒以上続くとBOOTUP_DEFAULT(モード0)に降格する。0と5のみを操作し、ゲーム用の 3d_full_screen には触れない。重要なのは⁠「モデルがロードされているか」ではなく「GPUの実際の使用率」に基づいて切り替えること⁠——常駐していても推論中でなければ省電力モードに留まるべきである。AMDはsysfs pp_power_profile_mode で実現し、NVIDIAは nvidia-smi -pl を使用する。

③ ゲーム中は全GPUを明け渡す。 gamemode(多くのディストリビューションにパッケージあり)はSteamゲームの起動を検知するとllama-serverを停止し、VRAMをすべてゲームに提供する。終了後に再起動。モデルはページキャッシュ内に残っており、ウォームアップは約6秒で完了:

# ~/.config/gamemode.ini
[custom]
start = systemctl --user stop llama-server
end   = systemctl --user start llama-server

完全なsystemdユニット、消費電力モード切替スクリプト、sudoersのパスワード不要設定、有効化コマンドは「完全な設定」セクションを参照。

7. パフォーマンスベンチマーク

llama.cppのベンチマーク用語:

  • pp512(prompt processing): 512個の入力トークンを並列処理する速度(token/s)。「長いコンテキストの読み込み」がどれだけ速いかを決定。
  • tg128(text generation): 128個の出力トークンを連続生成する速度。直感的な「応答速度」——人間の読書速度は約5–10 t/s、100+ なら「話が終わる前に画面が埋まる」。
  • MTPドラフト受け入れ率⁠: ドラフトヘッダーが予測したトークンがメインモデルによって検証された割合。高いほど実質的な高速化効果が高い。

本機の実測値(Qwen3.6-35B-A3B · IQ4_XS · 192K):

指標概念
pp512~3150 t/s長いコンテキストのプリフィックス処理で詰まらない
tg128~111 t/s読書速度を大幅に超え、即時応答の体感
MTP受け入れ率~52%ドラフトヘッダーの約半分が的中
常駐VRAM21.8 / 24 GB残り2.2G

自分で測定⁠: llama-bench で1コマンド、GPUや量子化を変更して比較:

llama-bench -m ~/models/Qwen3.6-35B-A3B-MTP-UD-IQ4_XS.gguf -ngl 99
# 出力には pp512 / tg128 の2行が含まれる
# 常駐サービスが実行中の場合は事前に停止: systemctl --user stop llama-server(Linux)

8. 実行後: クライアントとの接続

llama-serverは OpenAI互換エンドポイント⁠(http://localhost:18080/v1)を公開しており、カスタムbase_urlを設定できるクライアントであればすべて接続可能——Open WebUI、IDEプラグイン、CLIエージェントなど、base_urlを指すだけでよい。

これでモデルは使用可能になった。しかし⁠「何に使うか、どのタスクを委ねるべきか、Claude Opusに任せるべきか、クラウドフォールバックを設定する必要があるか」は別の話題⁠——それこそがローカルモデルの本質的な価値であり、別途1本の記事で解説する: local-llm-usage.md を参照。

9. トラブルシューティング

  • systemd内のインラインJSONで引用符が削除される → ウォームアップは必ず -d @ファイル を使用。
  • 内蔵GPUとディスクリートGPUが共存している場合、モデルが内蔵GPUで実行されるGGML_VK_VISIBLE_DEVICES=0(Vulkan)または CUDA_VISIBLE_DEVICES=0(CUDA)でディスクリートGPUをロック。
  • 量子化KVでflash-attnが有効になっていない--flash-attncache-type q4_0/q8_0 の前提条件であり、否则無視またはエラー。
  • ツール呼び出しで早期EOSchat-template=chatml を明示的に指定し、本来のテンプレートのバグ(llama.cpp #19513)を回避。
  • 常駐=常に高消費電力gpu_busy_percent(AMD)または nvidia-smi(NVIDIA)でモードを切り替え、アイドル時は省電力モードに降格。
  • VRAM予算の計算ミス → まず モデルサイズ + KV(層数×ctx×精度) + バッファ + デスクトップ を見積もり、1–2Gの余裕を残す。ゼロに近づけない。

10. 完全な設定(Linux)

前述の各セクションでは「何をすべきか」を明確にするため、重要な断片のみを記載。ここでは全体のチェーンを構成するすべてのファイルを完全な状態で記載し、順番に作成し、最後に有効化する。

WSL2ユーザーは本节を直接参照。純粋なWindowsユーザーはタスクスケジューラでsystemdを置き換え、消費電力モードの切替はAMD/NVIDIAカードに応じて個別に処理。

事前準備: スクリプト + パスワード不要sudo

llama-profile-syncgpu-profile-set を呼び出し、後者はroot権限で pp_power_profile_mode(AMD GPU sysfs)に書き込む必要がある。systemctlの ExecStopPost も同様。userユニットではsudoパスワードプロンプトが表示できないため、パスワード不要設定が必要:

sudo tee /usr/local/bin/gpu-profile-set <<'EOF'
#!/bin/sh
case "$1" in
    bootup_default) idx=0 ;;
    3d_full_screen) idx=1 ;;
    power_saving)   idx=2 ;;
    video)          idx=3 ;;
    vr)             idx=4 ;;
    compute)        idx=5 ;;
    [0-5])          idx=$1 ;;
    *) echo "Unknown profile: $1" >&2; exit 1 ;;
esac
printf "%s\n" "$idx" > /sys/class/drm/card0/device/pp_power_profile_mode
EOF
sudo chmod 755 /usr/local/bin/gpu-profile-set

sudo tee /usr/local/bin/llama-profile-sync <<'EOF'
#!/bin/bash
PROFILE_PATH="/sys/class/drm/card0/device/pp_power_profile_mode"
BUSY_PATH="/sys/class/drm/card0/device/gpu_busy_percent"
STATE="/tmp/llama-profile-last-busy"
BUSY_THRESHOLD=50
HOLD_SECONDS=90

current_idx() { grep '\*' "$PROFILE_PATH" | awk '{print $1}'; }

set_profile() {
    local want=$1 cur
    cur=$(current_idx)
    case "$cur" in 0|5) ;; *) return 0 ;; esac
    case "$want" in
        compute)        [ "$cur" = "5" ] && return 0 ;;
        bootup_default) [ "$cur" = "0" ] && return 0 ;;
    esac
    sudo /usr/local/bin/gpu-profile-set "$want"
}

if ! systemctl --user is-active --quiet llama-server 2>/dev/null; then
    set_profile bootup_default; exit 0
fi
busy=$(cat "$BUSY_PATH" 2>/dev/null || echo 0)
now=$(date +%s)
if [ "${busy:-0}" -ge "$BUSY_THRESHOLD" ]; then
    echo "$now" > "$STATE"
    set_profile compute
else
    last=$(cat "$STATE" 2>/dev/null || echo 0)
    [ $((now - last)) -lt "$HOLD_SECONDS" ] && set_profile compute || set_profile bootup_default
fi
EOF
sudo chmod 755 /usr/local/bin/llama-profile-sync

# パスワード不要sudo(USERNAMEは自分のユーザー名に置き換え):
# echo "USERNAME ALL=(ALL) NOPASSWD: /usr/local/bin/gpu-profile-set" | sudo tee /etc/sudoers.d/llama-profile

pp_power_profile_mode / gpu_busy_percent はAMD GPUのsysfsインターフェース。NVIDIAカードの消費電力管理は nvidia-smi -pl または nvidia-persistenced を使用するため、本节のスクリプトは適用できない。

systemd userユニット(3つのファイル)

# ~/.config/systemd/user/llama-server.service
[Unit]
Description=llama.cpp server (router mode, Vulkan)
After=network.target

[Service]
Environment=GGML_VK_VISIBLE_DEVICES=0     # VulkanでディスクリートGPUをロック; CUDAユーザーは此行を削除またはCUDA_VISIBLE_DEVICES=0に置換
ExecStart=/usr/bin/llama-server \
    --models-preset %h/.config/llama.cpp/models.ini \
    --models-max 1 \
    --host 0.0.0.0 \
    --port 18080 \
    --metrics
# ウォームアップ: モデルをVRAMにプリロード; -d @file でsystemdによる引用符の削除を回避
ExecStartPost=-/usr/bin/bash -c 'for i in $(seq 1 60); do curl -sf --max-time 90 http://127.0.0.1:18080/v1/chat/completions -H "Content-Type: application/json" -d @%h/.config/llama.cpp/warmup.json >/dev/null && exit 0; sleep 2; done'
# サービス停止時にGPUを省電力モードに戻す
ExecStopPost=/usr/bin/sudo /usr/local/bin/gpu-profile-set bootup_default   # AMD消費電力管理; NVIDIAユーザーは此行を削除
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target
# ~/.config/systemd/user/llama-gpu-sync.service
[Unit]
Description=Sync GPU profile with llama-server model state
After=llama-server.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/llama-profile-sync
# ~/.config/systemd/user/llama-gpu-sync.timer
[Unit]
Description=Poll llama-server model state for GPU profile switching

[Timer]
OnBootSec=15s
OnUnitActiveSec=15s

[Install]
WantedBy=timers.target

ウォームアップ

echo '{"model":"qwen3.6-mtp-instruct","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \
  > ~/.config/llama.cpp/warmup.json

有効化

# linger — 常駐モデルの鍵: ログアウト後もサービスが存続
sudo loginctl enable-linger "$USER"

# リロード + 有効化
systemctl --user daemon-reload
systemctl --user enable --now llama-server.service
systemctl --user enable --now llama-gpu-sync.timer

# 検証
systemctl --user status llama-server
systemctl --user status llama-gpu-sync.timer

--user--system ではない: GPUコンテキスト(DRM render node)はユーザーセッション内にあり、systemユニットで実行するとデバイスが開けない可能性がある。権限もはるかに小さい。

関連ドキュメント

  • local-llm-usage.md — 実行後: 境界(ローカル vs Opus)、何ができるか、llm-jobs自動化、実践的な教訓
  • software.md — ソフトウェアスタック一覧
  • timers-and-crons.md — llama-gpu-sync.timer などのタイマータスク
  • monitoring.md — モニタリングアーキテクチャ(GPUメトリクスはnode_exporter→Prometheus→Grafana経由)