---
title: ローカル LLM 導入メモ
url: https://doc.liz6.com/ja/homelab/local-llm
locale: ja
area: homelab
tags:
- llama.cpp
- local-llm
- amd-gpu
- quantization
- linux
- homelab
date: 2026-07-01
modified: 2026-08-24
description: 消費級GPUでローカル大規模言語モデルを動作させる場合、核心は予算配分の問題です。モデル重み、KVキャッシュ、デスクトップ環境が同じVRAMを奪い合っており、どのように割り当てるかが、どのサイズのモデルを、どの長さのコンテキストで動作させられるかを決定します。本稿では24GBのRX 7900 XTXを例に、モデル選定の方法と完全に再現可能な設定を示し、16GB/12GBのGPUへの拡張についても言及します。すべてのコマンドはLinux互換であり、WSL2でも同様です。
---

# ローカル LLM 導入メモ

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

## 1. 制約: 1つの不等式、GPUの選択は後回し

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

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

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

## 2. 第1ステップ: 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 Arc** | Vulkan | `-DGGML_VULKAN=ON` |
| **Apple Silicon**(Mシリーズ) | Metal | `-DGGML_METAL=ON` |

よくある疑問:

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

**「AMD GPUでなぜROCmではなくVulkanなのか？」**——ROCmはPyTorchやvLLMでのトレーニング向けであり、インストールが面倒です(特にWindowsでは)。GGUF推論には不要です。VulkanはGPUドライバーに付属するランタイムを使用し、LinuxとWindowsの両方で箱から出してすぐに使用できます。

**「内蔵GPUと独立GPUの両方が搭載されている場合どうするか？」**——Vulkanユーザーは `GGML_VK_VISIBLE_DEVICES=0` で独立GPUをロックしてください(0 = 第1カード)。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で同様の意思決定を行う方法を解説します。

### 本機の回答: モデル規模を優先し、ワークロードに応じてKVプリセットを切り替える

**Qwen3.6-35B-A3B**(MoE、総パラメータ数は35Bだが、各ステップでアクティブになるのは3Bのみ)を選択します。これにより、小モデルのような速度と大モデルのような容量を両立します。真のトレードオフは35Bと8Bの間を行き来することではなく、35Bの表現力と一貫性を維持しつつ、同じモデルに対して2つのKVプリセットを用意することです。**日常の対話では Q8_0 + 128K** を使用し、本当に長い入力を必要とする場合に **q4_0 + 192K** に切り替えます。

<svg viewBox="0 0 720 384" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="24G VRAM予算: ワークロードに応じてKVプリセットを切り替え">
  <rect width="720" height="384" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="18" font-weight="700" fill="#1f2933">24Gの使い方: ワークロードに応じてKVプリセットを切り替え</text>
  <text x="80" y="60" font-size="13" font-weight="700" fill="#1f2933">容量優先プリセットの実測常駐(24Gカード・アイドル時)</text>
  <line x1="656" y1="68" x2="656" y2="128" stroke="#ef4444" stroke-width="1.5" stroke-dasharray="5 4"/>
  <text x="656" y="62" text-anchor="end" font-size="11" font-weight="700" fill="#dc2626">24G</text>
  <rect x="80" y="70" width="407" height="44" fill="#4f46e5"/>
  <text x="283" y="97" text-anchor="middle" font-size="12" font-weight="600" fill="#ffffff">モデル IQ4_XS 16.96 GiB</text>
  <rect x="487" y="70" width="116" height="44" fill="#0d9488"/>
  <text x="545" y="90" text-anchor="middle" font-size="10" fill="#ffffff">KV・q4_0</text>
  <text x="545" y="103" text-anchor="middle" font-size="10" fill="#ffffff">192K+バッファ</text>
  <rect x="603" y="70" width="53" height="44" fill="#86efac"/>
  <text x="629" y="97" text-anchor="middle" font-size="10" font-weight="700" fill="#166534">残り2.2G</text>
  <text x="80" y="133" font-size="12" fill="#475569">実測 used 21.8G / free 2.2G — 余裕を持って収まっており、ぎりぎりではない。</text>
  <line x1="80" y1="150" x2="656" y2="150" stroke="#e2e8f0" stroke-width="1"/>
  <text x="80" y="176" font-size="13" font-weight="700" fill="#1f2933">同じGPUでも、KV精度がコンテキストの最大長を決定する(ビット幅に反比例して推定)</text>
  <text x="86" y="205" font-size="12" fill="#64748b">f16 KV</text>
  <rect x="200" y="192" width="75" height="20" rx="3" fill="#cbd5e1"/>
  <text x="283" y="207" font-size="11" fill="#64748b">≈ 48K</text>
  <text x="86" y="241" font-size="12" fill="#64748b">Q8_0 KV</text>
  <rect x="200" y="228" width="150" height="20" rx="3" fill="#94a3b8"/>
  <text x="358" y="243" font-size="11" fill="#64748b">≈ 96K 快適プリセット</text>
  <text x="86" y="277" font-size="12" font-weight="700" fill="#0f766e">q4_0 KV(本機)</text>
  <rect x="200" y="264" width="300" height="20" rx="3" fill="#0d9488"/>
  <text x="508" y="279" font-size="11" font-weight="700" fill="#0f766e">192K ✓</text>
  <rect x="60" y="304" width="600" height="66" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="76" y="330" font-size="12.5" fill="#115e59">同じ35Bモデルを2プリセット保持: 対話プリセットはQ8_0で安定性を兼顾し、容量プリセットはq4_0でウィンドウを192Kまで拡張。</text>
  <text x="76" y="352" font-size="12.5" fill="#115e59">ウィンドウ仕様はリアルタイムで使用可能な長さと等しいわけではない: 長ければ長いほど遅くなる。実際に100K以上を必要とする場合にのみ容量プリセットに切り替える。flash-attnは量子化されたKVの前提条件である。</text>
</svg>

24GBの内訳:

- **モデル重み 16.96 GiB**(IQ4_XS)。MoEにより高速に動作し、IQ4_XSにより十分な品質を確保します。
- **容量プリセットは q4_0 + flash-attn** を使用し、192Kのコンテキストを約5GBに圧縮します。f16に切り替えると、同じコンテキストで4倍のVRAMが必要となり、すぐに溢れます。Q8_0は実際に100K程度に達すると、余裕を圧迫し、速度が顕著に低下し始めます。
- **対話プリセットは Q8_0 + 128K上限** を使用します。クライアント側では120Kのみを解放し、約55Kで古いメッセージの圧縮を開始します。普段は128Kをすべて埋めることはなく、より安定した生成軌道を得ます。たまに100Kを超えても動作しますが、最初のトークンの遅延はリアルタイムチャットには適さなくなります。
- **容量プリセットの実測常駐は 21.8G、残り 2.2G** で、アイドル時は低消費電力プリセットに落ち、デスクトップに負担はありません。

以前は Q3_K_XL + 短いコンテキストの保守的な構成で動作させていましたが、現在は固定された IQ4_XS 重みを使用し、ワークロードに応じてKVプリセットのみを切り替えています。**デフォルトはQ8対話プリセット、超長入力のみQ4容量プリセットに切り替え**、デスクトップの再読み込みで余裕がなくなる場合は、Q3重みプリセットでバックアップします。

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

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

<svg viewBox="0 0 720 300" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="選定は2ステップ: ダウンロード時にパラメータ数と量子化を決定し、起動時にコンテキストとKVを決定">
  <defs><marker id="mah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="300" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">選定は2ステップ: ダウンロード時に「パラメータ数+量子化」を決定し、起動時に「コンテキスト+KV」を決定</text>
  <rect x="40" y="50" width="300" height="28" rx="6" fill="#4f46e5"/>
  <text x="190" y="69" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">① ダウンロード時 ・ ファイルに刻み込まれ、後から変更不可</text>
  <rect x="52" y="90" width="276" height="44" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="66" y="109" font-size="12" font-weight="700" fill="#3730a3">パラメータ数</text>
  <text x="66" y="126" font-size="11" fill="#4f46e5">モデルリポジトリを選択 ・ 8B / 14B / 35B-A3B</text>
  <rect x="52" y="140" width="276" height="44" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="66" y="159" font-size="12" font-weight="700" fill="#3730a3">重みの量子化</text>
  <text x="66" y="176" font-size="11" fill="#4f46e5">どの .gguf を選択するか ・ Q4_K_M / IQ4_XS / Q8_0</text>
  <rect x="52" y="196" width="276" height="38" rx="6" fill="#e0e7ff"/>
  <text x="190" y="220" text-anchor="middle" font-size="12" font-weight="600" fill="#3730a3">GGUF:~/models/…-IQ4_XS.gguf</text>
  <rect x="380" y="50" width="300" height="28" rx="6" fill="#0d9488"/>
  <text x="530" y="69" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">② 起動時 ・ 毎回調整可能</text>
  <rect x="392" y="90" width="276" height="44" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="109" font-size="12" font-weight="700" fill="#115e59">コンテキスト</text>
  <text x="406" y="126" font-size="11" fill="#0f766e">-c 196608(192K)</text>
  <rect x="392" y="140" width="276" height="44" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="159" font-size="12" font-weight="700" fill="#115e59">KV精度</text>
  <text x="406" y="176" font-size="11" fill="#0f766e">-ctk / -ctv q4_0(--flash-attn 必要)</text>
  <rect x="392" y="196" width="276" height="38" rx="6" fill="#ccfbf1"/>
  <text x="530" y="220" text-anchor="middle" font-size="12" font-weight="600" fill="#115e59">llama-server を実行</text>
  <line x1="330" y1="215" x2="388" y2="215" stroke="#475569" stroke-width="1.8" marker-end="url(#mah)"/>
  <text x="360" y="262" text-anchor="middle" font-size="12" fill="#64748b">より大きなモデル / より高品質に変更したい場合 → ファイルを再ダウンロード; より長いコンテキスト / より少ないVRAM消費を望む場合 → 起動パラメータのみを変更すればよい。</text>
</svg>

### ステップ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`(VRAM節約、品質低下)。
- `IQ4_XS` / `IQ3_XXS` は i-quant: 同じビット数でより小さく、品質はほぼ同等。imatrixが必要(パブリッシャーが同梱済み)。VRAMが限られている場合はこれを使用。
- `_S / _M / _L` = 小 / 中 / 大 バリエーション。同じビット数では `_M` が最も一般的。

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

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

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

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

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

| スイッチ | 役割 | 一般的な値 |
|------|------|--------|
| `--flash-attn` / `-fa` | **量子化KVの前提条件。必ず有効化** | 有効 / 無効 |
| `--cache-type-k` / `-ctk` | Kキャッシュの精度 | `f16`(デフォルト)/ `q8_0` / `q4_0` |
| `--cache-type-v` / `-ctv` | Vキャッシュの精度 | 同上 |

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

```bash
# 積極的: 長コンテキスト優先
llama-server ... -fa -ctk q4_0 -ctv q4_0 -c 196608
# 保守的: 品質優先、約96Kが本機では全VRAMの快適プリセット; サービス上限には余裕を持たせる
llama-server ... -fa -ctk q8_0 -ctv q8_0 -c 98304
# 非対称: Kは量子化により敏感に反応するため、限界までVRAMを節約する際は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 | 現実的な選択 |
|------|----------|
| **24G** | 30–35B MoE(IQ4/Q4)または32B dense IQ4; 長コンテキストはq4_0 KVで対応 |
| **16G** | 14B dense Q4–Q5、または30B MoE Q3 + 適度な層オフロード; 中程度コンテキスト |
| **12G** | 8–9B Q4–Q5 で快適(例: Qwen3-8B GGUF ~5.4G)、または14B Q4 でコンパクト |
| **8G** | 7–8B Q4、短コンテキスト |

**VRAMが足りない場合の削減方法、優先順位は**: まずKV精度を下げる(f16→q8_0→q4_0)してコンテキストを維持 → 次にコンテキスト長を圧縮 → 最後により小さなモデルまたはより強力な重み量子化に変更。デスクトップやゲームも同じGPUを奪い合っていることを忘れず、数GBは確保すること。

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

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

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

1. [公式リリースバイナリ](https://github.com/ggerganov/llama.cpp/releases)を使用——全プラットフォーム共通で、ダウンロードして解凍するだけですでに各バックエンドが含まれています。
2. Linuxディストリビューションには既存のパッケージがある: `apt install llama-cpp`(Debian/Ubuntu)、`pacman -S llama.cpp-vulkan`(Arch)。
3. 上記がない場合はソースからコンパイル(第2節で選択したフラグに `<YOUR_BACKEND>` を置き換える、例: `-DGGML_CUDA=ON`):

```bash
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/ にある
```

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

```bash
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は最初のファイルへのポインタで自動的に残りをロードします。

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

```bash
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互換エンドポイントです。動作確認:

```bash
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、有効時は約111 t/sで、約50%の高速化を実現しました。あなたのモデルや量子化によって、実際の向上幅は異なります。

標準的なllama.cppモデルはこの機能を持っていません——これは **unsloth**(GGUF量子化と加速に特化したチーム)が量子化時に**GGUFにドラフトヘッダー(draft head)を埋め込んだ**ものです。ドラフトヘッダーはまず次に2つのトークンを「推測」し、メインモデルが1回の順伝播で並列検証します。当たれば利益、外れればロールバック。独立したドラフトモデルは不要で、追加のVRAMも消費しません:

<svg viewBox="0 0 720 300" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="MTP投機デコーディングの原理">
  <defs>
    <marker id="lah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker>
  </defs>
  <rect width="720" height="300" fill="#ffffff"/>
  <text x="360" y="30" text-anchor="middle" font-size="18" font-weight="700" fill="#1f2933">MTP投機デコーディング: 1回の順伝播で複数のトークンを生成</text>
  <text x="48" y="92" font-size="13" font-weight="600" fill="#64748b">MTPなし</text>
  <text x="48" y="108" font-size="11" fill="#94a3b8">逐トークン</text>
  <rect x="130" y="74" width="80" height="40" rx="5" fill="#e2e8f0"/><text x="170" y="99" text-anchor="middle" font-size="12" fill="#334155">順伝播 → t₁</text>
  <rect x="230" y="74" width="80" height="40" rx="5" fill="#e2e8f0"/><text x="270" y="99" text-anchor="middle" font-size="12" fill="#334155">順伝播 → t₂</text>
  <rect x="330" y="74" width="80" height="40" rx="5" fill="#e2e8f0"/><text x="370" y="99" text-anchor="middle" font-size="12" fill="#334155">順伝播 → t₃</text>
  <text x="430" y="99" font-size="12" fill="#94a3b8">… 1回の順伝播で1つのみ生成</text>
  <text x="48" y="192" font-size="13" font-weight="600" fill="#1d4ed8">MTP</text>
  <text x="48" y="208" font-size="11" fill="#60a5fa">ドラフトヘッダー+検証</text>
  <rect x="130" y="166" width="150" height="56" rx="6" fill="#dbeafe" stroke="#60a5fa"/>
  <text x="205" y="189" text-anchor="middle" font-size="12" font-weight="600" fill="#1e40af">ドラフトヘッダー予測</text>
  <text x="205" y="208" text-anchor="middle" font-size="12" fill="#1e3a8a">t₁, t₂(1回で2つ取得)</text>
  <line x1="280" y1="194" x2="320" y2="194" stroke="#475569" stroke-width="1.6" marker-end="url(#lah)"/>
  <rect x="324" y="166" width="160" height="56" rx="6" fill="#eef2ff" stroke="#818cf8"/>
  <text x="404" y="189" text-anchor="middle" font-size="12" font-weight="600" fill="#3730a3">メインモデル1回の順伝播</text>
  <text x="404" y="208" text-anchor="middle" font-size="12" fill="#4338ca">t₁,t₂を並列検証</text>
  <line x1="484" y1="194" x2="524" y2="194" stroke="#475569" stroke-width="1.6" marker-end="url(#lah)"/>
  <rect x="528" y="166" width="150" height="56" rx="6" fill="#dcfce7" stroke="#4ade80"/>
  <text x="603" y="189" text-anchor="middle" font-size="12" font-weight="600" fill="#166534">受け入れ ✓✓ / ✓✗</text>
  <text x="603" y="208" text-anchor="middle" font-size="12" fill="#15803d">受け入れ率 ~52%</text>
  <rect x="60" y="250" width="600" height="34" rx="6" fill="#f0fdf4"/>
  <text x="72" y="271" font-size="12" fill="#166534">`spec-draft-n-max=2`, ドラフト受け入れ率 ~52% → 実測 tg ~111 t/s。ドラフトヘッダーはGGUFに内蔵されており、追加のスロットを消費しない。</text>
</svg>

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

**MTPはモデル本来の機能ではなく、unslothが量子化時に追加したものです。** そのため:

- **HFで `unsloth/<モデル名>-MTP-GGUF` を検索した場合のみMTP版が存在**します。例: [`unsloth/Qwen3.6-35B-A3B-MTP-GGUF`](https://huggingface.co/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-type` と `spec-draft-n-max` の2行を削除し、他は変更しません。

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

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

```ini
# ~/.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 unitと有効化コマンドは「完全な設定」セクションを参照してください。

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

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

<svg viewBox="0 0 720 340" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="VRAMと消費電力の潮汐スケジューリング状態遷移図">
  <defs>
    <marker id="sah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#64748b"/></marker>
  </defs>
  <rect width="720" height="340" fill="#ffffff"/>
  <text x="360" y="30" text-anchor="middle" font-size="18" font-weight="700" fill="#1f2933">VRAM潮汐: モデルは常駐、消費電力と使用量は負荷に応じて増減</text>
  <rect x="80" y="80" width="220" height="78" rx="10" fill="#dcfce7" stroke="#22c55e"/>
  <text x="190" y="110" text-anchor="middle" font-size="14" font-weight="700" fill="#166534">アイドル常駐</text>
  <text x="190" y="131" text-anchor="middle" font-size="11" fill="#15803d">モデルはVRAMに駐留 ・ GPUプリセット0</text>
  <text x="190" y="147" text-anchor="middle" font-size="11" fill="#15803d">低消費電力待機</text>
  <rect x="430" y="80" width="220" height="78" rx="10" fill="#dbeafe" stroke="#3b82f6"/>
  <text x="540" y="110" text-anchor="middle" font-size="14" font-weight="700" fill="#1e40af">推論中</text>
  <text x="540" y="131" text-anchor="middle" font-size="11" fill="#1d4ed8">gpu_busy ≥ 50% → プリセット5</text>
  <text x="540" y="147" text-anchor="middle" font-size="11" fill="#1d4ed8">COMPUTE 全速</text>
  <rect x="255" y="232" width="220" height="78" rx="10" fill="#ffedd5" stroke="#f97316"/>
  <text x="365" y="262" text-anchor="middle" font-size="14" font-weight="700" fill="#9a3412">ゲーム中</text>
  <text x="365" y="283" text-anchor="middle" font-size="11" fill="#c2410c">gamemode → llama-server停止</text>
  <text x="365" y="299" text-anchor="middle" font-size="11" fill="#c2410c">全VRAMをゲームに提供</text>
  <line x1="300" y1="108" x2="426" y2="108" stroke="#64748b" stroke-width="1.6" marker-end="url(#sah)"/>
  <text x="363" y="101" text-anchor="middle" font-size="11" fill="#475569">リクエスト到着</text>
  <line x1="426" y1="134" x2="300" y2="134" stroke="#64748b" stroke-width="1.6" marker-end="url(#sah)"/>
  <text x="363" y="151" text-anchor="middle" font-size="11" fill="#475569">アイドル &gt; 90s → プリセット降格</text>
  <line x1="210" y1="160" x2="300" y2="230" stroke="#64748b" stroke-width="1.6" marker-end="url(#sah)"/>
  <text x="208" y="205" text-anchor="end" font-size="11" fill="#475569">ゲーム起動</text>
  <line x1="320" y1="230" x2="235" y2="160" stroke="#64748b" stroke-width="1.6" marker-end="url(#sah)"/>
  <text x="330" y="205" text-anchor="start" font-size="11" fill="#475569">終了 → ウォーミングアップ ~6s</text>
</svg>

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](https://github.com/FeralInteractive/gamemode)(多くのディストリビューションにパッケージあり)はSteamゲームの起動を検知するとllama-serverを停止し、VRAMをすべてゲームに提供し、終了後に再起動します。モデルはページキャッシュに残っているため、ウォーミングアップは約6秒で完了:

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

> **完全なsystemd unit、消費電力切り替えスクリプト、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 | 短尺ベンチマークのプリフェッチは高速; 100Kに線形外挿は不可 |
| tg128 | ~111 t/s | 読書速度を大幅に上回り、体感的に即時 |
| MTP受け入れ率 | ~52% | ドラフトヘッダーの約半分がヒット |
| 常駐VRAM | 21.8 / 24 GB | 残り2.2G |

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

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

### ロールプレイ負荷の実測: 8B大ウィンドウ、35BとKV精度

2026-08-24、SillyTavernの長文対話負荷に対して本機でA/Bテストを実施。問われているのは「どのモデルが標称ウィンドウが大きいのか」ではなく、より現実的な3つの問題です:

1. 大ウィンドウのために8Bに切り替えると、ロールプレイの品質はどの程度損なわれるか？
2. 35BでKVをQ8_0からq4_0に下げると、文体や事実保持が安定して損なわれるか？
3. 100K–192Kは設定可能だが、各ラウンドでこの長さの履歴を再読み込みすることは依然としてリアルタイムチャットに適しているか？

モデル品質と長文コンテキストのA/Bは、合成シナリオとランダムな事実コードのみを使用して実施。短尺シナリオでは同じプロンプトとサンプリング条件を使用; 長文では8つの目標事実を全文の異なる位置に分散させ、厳格なJSONマッピングで採点; 主観的品質はA/B順序を交換した二重盲検評価で集計。後述のラウンド推定は、ローカルに既存の記録のみに対してトークン集計を行い、本文は出力または保持しない。サンプル数は多くないため、スコアは本機の選定にのみ使用され、一般的なモデルランキングではない。

#### モデル規模: 8Bの大ウィンドウは品質のギャップを補えない

| 指標 | 35B-A3B | 8B | 解釈 |
|------|--------:|---:|------|
| 短尺シナリオ二重盲検総合スコア | **8.64** | 6.36 | ギャップは主に文体、キャラクターの信頼性、連続性、反復回避にある |
| ウォーミングアップ生成速度 | **126.7 t/s** | 107.4 t/s | MoEのアクティブパラメータ数が小さいため、35Bの総パラメータ数が必ずしも各トークンで35Bを計算するわけではない |
| 中央値の完全応答レイテンシ | 3.74s | **3.53s** | 実際の体感はほぼ同レベル |
| コールドロード | 12.73s | **3.47s** | 8Bの明確な優位性は初回ロードの速さのみ |

8Bはより速いウォーミングアップ推論によって品質を補っていません; 本機のバックエンドでは、35B-A3Bの方がむしろ高い生成スループットを示します。タスクが没入型の長文対話であり、特にモデルが積極的に進行し、キャラクター状態を維持し、同じシナリオでループしないことを要求する場合、**ウィンドウ仕様のみを理由にメインモデルを8Bに下げるべきではありません**。

#### 長文コンテキスト: 格納できるからといって、安定して使える、待てるわけではない

下表の時間は非ストリーミングリクエスト全体のウォールクロック時間、コンテキスト長は概数; 異なるモデルのトークナイザーにより実際の入力にはわずかな偏差が生じる。

| 目標コンテキスト | 35B · Q8_0 KV | 35B · q4_0 KV | 8B · Q8_0 KV |
|-----------:|---------------:|---------------:|--------------:|
| 22K | 8/8 · 約11.3s | 8/8 · 約14–15s | 6/8 · 約23.5s |
| 52K | — | 8/8 · 約39.9s | 8/8 · 約72.5s |
| 100K | 8/8 · 約111.5s | 8/8 · 約101.7s | 6/8 · 約231.8s |
| 160K | — | 8/8 · 約215.1s | 接続が約5分後に中断、バックエンドは約73%まで処理 |

ここには3つの直感に反する結果があります:

- **8Bの標称長文ウィンドウは安定した利用能力ではない。** 52Kでは全正解だが、22Kと100Kでは6/8にとどまっており、小モデルは事実の位置や具体的な入力のみに敏感であり、「短いほど正確」という単純な話ではないことが示唆される。
- **8Bは長文入力においてむしろ遅い。** 100Kは35B q4_0より約2.3倍遅い; 160Kは現在のルーター接続タイムアウト内では使用不可。
- **超長ウィンドウの主な代償は、各ラウンドの最初のトークンの待機時間である。** 35B q4_0の160Kは全正解だが、完了までに約215秒かかる。チャットでは各ラウンドで履歴を再読み込みするため、192Kは容量上限またはオフライン文書プリセットとしてのものであり、リアルタイム作業領域として常に満杯にするべきものではない。

#### KV精度: 生成軌道を変更するが、安定した品質低下の証拠はない

同じ35B、同じ重み、同じシードとプロンプトで、KVキャッシュ精度のみを切り替え:

| 対照 | Q8_0 | q4_0 |
|------|-----:|-----:|
| 22K 正確な検索 | 8/8 | 8/8 |
| 100K 正確な検索 | 8/8 | 8/8 |
| 短尺シナリオ二重盲検スコア | **9.13** | 8.38 |
| 22K 長尺シナリオ二重盲検スコア | 7.90 | **9.30** |
| 思考フォーマットタグリーク | 0/5 | 1/5 |

短尺シナリオではQ8_0が良く、長尺シナリオではq4_0の方が良く、スコアの方向が反転した。これは、KV量子化が決定論的生成を異なるトークン軌道に導くのに十分であり、単一の出力は良くなることも悪くなることもあり得ることを示している; しかし22K–160Kの検索で全正解を考慮すると、**今回のラウンドではq4_0がテキスト品質やコンテキスト精度を継続的に低下させることは観察されなかった**。一方で、q4_0の1回のフォーマットタグリークと小サンプルの揺らぎは、それが「完全に無損失」と呼べるものではないことも示している。

したがって、単一のプリセットに賭けるのではなく、2つのプリセットを採用する:

| 用途 | サーバー側コンテキスト | KV | クライアント側予算 | 選択理由 |
|------|-------------:|----|-----------:|----------|
| 日常のSillyTavern / ロールプレイ | 131,072 | Q8_0 / Q8_0 | 122,880 | デフォルトプリセット; 生成の安定性を維持しつつ、32Kを大幅に上回る |
| 超長文書 / 極長履歴の一時継続 | 196,608 | q4_0 / q4_0 | タスクに応じて一時的に切り替え | 100K–160Kの検索は安定しており、容量と帯域幅の効率が良い |

#### SillyTavernでの実際の圧縮と出力予算

現在の対話プリセットは、120Kが満たされてから要約を行うわけではない:

- 要約呼び出しは独立した8,192コンテキスト / 160出力トークンの非ストリーミングプリセットを使用するが、同じ35Bルーターを指すため、要約のためにモデルのアンロードと再ロードを繰り返す必要がない。
- 自動要約は有効化され、遅延2、バッチサイズ2; 最初の数ラウンドから**逐次要約の準備**を開始。
- 古い原文を除外し要約を注入する閾値はクライアントウィンドウの45%、つまり `122880 × 45% = 55296` トークン。
- 閾値に達すると最後のユーザーメッセージのみを保持し、古い原文は短期/長期要約が引き受け; したがってセッションは固定ラウンド数で終了するのではなく、逐字履歴から圧縮された記憶へと徐々に移行する。
- 本機のトークンのみを集計し本文を参照しない極小サンプルでは、完全な1ラウンドの中央値は約425トークン。純粋な数学では約130ラウンドでトリガー; キャラクターカード、ワールドブック、システムプロンプトを差し引き、返信の揺らぎを考慮し、**保守的に原文履歴を80–120ラウンド分と理解**。長文の返信はより早く、短文の返信はより遅くトリガーされる。
- 中国語は1文字 = 1トークンではない。本機のトークナイザーサンプルでは約1トークン ≈ 1.62の中国語文字; 従来1200トークンは約1900文字に相当し、現在の1600トークン上限は約2600文字。`max_tokens` は上限であり目標長ではない。1600を設定するのは、自然な転換が硬く切り捨てられるのを避けるためであり、各ラウンドを埋め尽くすことを奨励するためではない。

最終的な意思決定は1文に圧縮できる:

> **35Bのロールプレイ品質を維持; 日常は128K級のQ8 + 約55Kで早期圧縮、本当に100K以上を読み込む必要がある場合にのみ192K Q4に切り替え。標称大ウィンドウを理由に8Bに切り替えず、起動可能なコンテキスト上限をリアルタイムチャットに適した長さとして誤認しない。**

## 8. 動作確認後: クライアントとの接続

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

ここまででモデルは使用可能である。しかし**「何に使うか、どのタスクを任せるべきか、Claude Opusに任せるべきか、クラウドフォールバックを設定するかどうか」は別の話題である**——それこそがローカルモデルが真に検討する価値のある部分であり、別途1篇を割いて解説する: [local-llm-usage.md](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-attn` は `cache-type q4_0/q8_0` の前提条件であり、否则は無視されるかエラーになる。
- **ツール呼び出しで早期EOS** → `chat-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-sync` は `gpu-profile-set` を呼び出し、後者はroot権限で `pp_power_profile_mode`(AMD GPU sysfs)に書き込む必要がある; systemctlの `ExecStopPost` も同様。user unitではsudoパスワードプロンプトが表示できないため、パスワード不要設定を構成する必要がある:

```bash
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 unit (3つのファイル)

```ini
# ~/.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
```

```ini
# ~/.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
```

```ini
# ~/.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
```

### ウォーミングアップ

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

### 有効化

```bash
# 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 unitで実行するとデバイスが開けない場合があり、権限もはるかに少ない。

## 関連ドキュメント

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