ローカルLLM導入入門 — GGUF変換とOllama運用
国産LLMを手元のMac/PCで動かすための入門。HuggingFaceのモデルをGGUF形式に変換し、Ollamaで推論するまでの原理と手順をまとめています。 (元教材からローカルLLM関連の章だけを抜き出して再構成したものです)
5. LLMのGGUF変換とローカル推論の原理
GGUFフォーマットとは
GGUF(GPT-Generated Unified Format)は、llama.cppプロジェクトが開発したモデル保存形式である。HuggingFaceのSafetensors形式と比較して、以下の特徴を持つ:
- 単一ファイル: メタデータ・トークナイザー・重みを1ファイルに格納
- 量子化対応: F16/Q8_0/Q4_K_M等、多段階の量子化をネイティブサポート
- メモリマップ: ファイルを直接メモリにマップし、ロード時間を短縮
- CPU/GPU混在推論: 統合メモリ環境(Apple Silicon等)に最適化
量子化の影響
| 量子化レベル | ビット幅 | 8Bモデルの目安サイズ | 品質への影響 |
|---|---|---|---|
| F16 | 16bit | 約16GB | なし(ベースライン) |
| Q8_0 | 8bit | 約9GB | ほぼなし |
| Q4_K_M | 4bit(混合) | 約5GB | わずかに低下 |
| Q4_0 | 4bit | 約4.5GB | 明確に低下 |
Q4_K_Mは「重要なレイヤーは高精度、それ以外は低精度」の混合量子化で、サイズと品質のバランスが良い。
HuggingFace → GGUF → Ollama の変換パイプライン
HuggingFace (Safetensors)
↓ convert_hf_to_gguf.py
GGUF (F16 or 量子化済み)
↓ Modelfile作成
Ollama create
↓
ollama run <model-name>
vocab_size問題
LLMのトークナイザーが持つ語彙数(vocab_size)はモデルによって大きく異なる。
| モデル | vocab_size | トークナイザー種別 |
|---|---|---|
| Llama 2 | 32,000 | BPE |
| Llama 3 | 128,000 | BPE |
| Qwen3 | 151,936 | BPE |
| LLM-jp-4 | 196,608 | Unigram byte-fallback |
Ollamaの内部実装(llama.cppベース)は、BPEトークナイザーを前提として設計されており、vocab_sizeが128Kを超えるモデルや、Unigram方式のトークナイザーはロードに失敗する場合がある。
これは「GGUF変換は成功するがOllama起動時にロードエラー」という形で顕在化し、デバッグが困難な落とし穴である。
8. LLM・AI基盤用語
| 用語 | 意味 |
|---|---|
| GGUF | llama.cpp用のモデル保存形式。単一ファイルにメタデータ・重みを格納 |
| Safetensors | HuggingFaceのモデル保存形式。安全で高速なシリアライズ |
| 量子化 (Quantization) | モデル重みのビット幅を削減し、サイズと推論速度を改善する技術 |
| Q4_K_M | 4bit混合量子化。重要レイヤーは高精度、それ以外は低精度 |
| Ollama | ローカルLLMの管理・実行ツール。Modelfileベースでモデル登録 |
| Transformers | HuggingFaceのLLMフレームワーク。Python APIでモデルをロード・推論 |
| vocab_size | トークナイザーの語彙数。モデルがロード可能かの制約条件になりうる |
| Unigram (byte-fallback) | SentencePieceベースのサブワード分割方式。BPEと異なりmergesを持たない |
| BPE (Byte Pair Encoding) | 最も一般的なサブワード分割方式。mergesリストで分割ルールを定義 |
| SFT | Supervised Fine-Tuning。教師あり微調整 |
| DPO | Direct Preference Optimization。人間の選好に基づくアライメント手法 |
| MoE | Mixture of Experts。推論時に一部のエキスパートのみを活性化する効率的アーキテクチャ |
| device_map="auto" | PyTorch/Transformersでモデルを自動的にGPU/CPU/ディスクに分散配置する設定 |
| chat_template | モデル固有の対話フォーマット。Jinja2テンプレートで定義される |
11. ローカルLLM導入パイプライン
┌─── HuggingFace ───┐
│ │
│ モデルカード確認 │
│ ├ vocab_size │
│ ├ tokenizer type │
│ └ architecture │
│ ↓ │
│ snapshot_download │ ← 大容量モデルは
│ or hf_hub_download │ 個別シャードDLが確実
└───────┬───────┘
↓
┌─── GGUF変換 ───────┐
│ │
│ convert_hf_to_gguf.py │
│ ├ tokenizer未認識 → パッチ│ ← 新モデルでは頻出
│ ├ --outtype q8_0/q4_k_m │
│ └ 警告: merges/vocab │
└───────┬───────┘
↓
┌─── Ollama登録 ─────┐ ┌─── フォールバック ──┐
│ │ │ │
│ Modelfile作成 │ │ vocab_size > 128K │
│ ollama create │ │ or Unigramトークナイザー│
│ ollama run │ │ ↓ │
│ ↓ │ │ Transformersで直接推論 │
│ 成功? ─── Yes ──→ 完了 │ │ model.generate() │
│ │ │ │ │
│ No(500 Error) │ │ ← ここに来たらOllama │
│ └────────────→│ 対応を待つしかない │
│ │ └────────────┘
└─────────────┘
14. 日本語LLMの系譜
2023.10 NII LLM-jp-3 公開 — 日本語特化オープンモデルの先駆
1.8B / 3.7B / 13B / 172B(MoE)
│
2024 日本語LLMの百花繚乱
rinna, CyberAgent, Stability AI, LINE 等が参入
│
2025 GPT-4o / Claude 3.5 が日本語でも高性能を発揮
ローカルモデルとの性能差が課題に
│
2026.4 NII LLM-jp-4 公開
8B(Dense)/ 32B-A3B(MoE)
GPT-4oをGPT-5.4ジャッジで上回る日本語性能
│
↓ 特徴
├ 10.5兆トークン事前学習(日本語は約3.6%)
├ 1.2兆トークン中間学習(合成データ含む)
├ SFT + DPO(RLなし)
├ reasoning_effort 3段階の思考モード
├ vocab_size 196,608(Unigram byte-fallback)
└ OSAID準拠の透明性
LLM-jp-4の技術的な肝は「中間学習」にある。事前学習→いきなりSFTではなく、合成データを含む1.2兆トークンで日本語の指示理解力を底上げするステージを挟んでいる。
17. HuggingFaceモデルをGGUF変換してOllamaに登録する手順
前提条件
- Python 3.10+ の仮想環境(venv)
- 依存パッケージ:
torch,transformers,sentencepiece,protobuf,gguf - llama.cppのリポジトリ(変換スクリプト用)
Step 1: 依存関係の確認
# 既存venvの検索(新規作成より速い)
find ~/ -maxdepth 4 -name "pyvenv.cfg" -exec grep -l "torch" {} \; 2>/dev/null
# 不足パッケージのインストール
<venv>/bin/pip install gguf sentencepiece accelerate
Step 2: llama.cppの取得
cd /tmp
git clone --depth 1 https://github.com/ggerganov/llama.cpp.git llama_cpp_convert
Step 3: モデルのダウンロード
from huggingface_hub import hf_hub_download
import os
repo = '<org>/<model-name>'
local_dir = '/tmp/<model-name>'
# Step 3a: メタデータ取得
for fname in ['config.json', 'tokenizer.json', 'tokenizer_config.json',
'model.safetensors.index.json']:
hf_hub_download(repo, fname, local_dir=local_dir, local_dir_use_symlinks=False)
# Step 3b: 重みファイルのダウンロード(シャード名はindex.jsonから確認)
import json
with open(f'{local_dir}/model.safetensors.index.json') as f:
shards = set(json.load(f)['weight_map'].values())
for shard in sorted(shards):
if not os.path.exists(f'{local_dir}/{shard}'):
hf_hub_download(repo, shard, local_dir=local_dir, local_dir_use_symlinks=False)
Step 4: GGUF変換
<venv>/bin/python3 /tmp/llama_cpp_convert/convert_hf_to_gguf.py \
/tmp/<model-name> \
--outfile /tmp/<model-name>-q8_0.gguf \
--outtype q8_0
トークナイザー未認識エラーへの対処
新しいモデルでは BPE pre-tokenizer was not recognized エラーが発生することがある。
# convert_hf_to_gguf.py の該当行を編集
# 変更前:
raise NotImplementedError("BPE pre-tokenizer was not recognized...")
# 変更後:
res = "default" # fallback for unrecognized tokenizers
Step 5: Ollama登録
# Modelfile作成
cat > /tmp/Modelfile << 'EOF'
FROM /tmp/<model-name>-q8_0.gguf
PARAMETER temperature 0.7
PARAMETER num_ctx 4096
PARAMETER top_p 0.95
PARAMETER stop "<|endoftext|>"
PARAMETER stop "<|im_end|>"
TEMPLATE """{{- if .System }}<|im_start|>system
{{ .System }}<|im_end|>
{{ end }}{{- range .Messages }}{{- if eq .Role "user" }}<|im_start|>user
{{ .Content }}<|im_end|>
{{ end }}{{- if eq .Role "assistant" }}<|im_start|>assistant
{{ .Content }}<|im_end|>
{{ end }}{{- end }}<|im_start|>assistant
"""
SYSTEM "あなたは日本語に堪能なAIアシスタントです。"
EOF
# 登録
ollama create <model-name> -f /tmp/Modelfile
# テスト
ollama run <model-name> "こんにちは"
18. vocab_size巨大モデルのフォールバック推論手順
Ollamaでロードに失敗した場合のTransformersフォールバック手順。
Step 1: 直接ロード
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_path = "/tmp/<model-name>"
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_path,
device_map="auto",
dtype=torch.float16, # 16GB RAMの場合
trust_remote_code=True
)
Step 2: 推論実行
chat = [{"role": "user", "content": "質問テキスト"}]
# chat_templateの戻り値型に注意
text = tokenizer.apply_chat_template(chat, add_generation_prompt=True, tokenize=False)
inputs = tokenizer(text, return_tensors="pt").to(model.device)
with torch.no_grad():
output = model.generate(**inputs, max_new_tokens=200, do_sample=True, top_p=0.95, temperature=0.7)
response = tokenizer.decode(output[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
print(response)
注意点
apply_chat_template(return_tensors="pt")が.shapeを持たないオブジェクトを返す場合がある →tokenize=Falseで文字列取得してから手動トークン化- thinkingモデルの場合、推論過程がレスポンスに含まれる →
<think>タグで分離するか、skip_special_tokensで除去 - 16GB RAMでfp16ロードは可能だが、一部のレイヤーがディスクにオフロードされる場合がある(
Some parameters are on the meta device because they were offloaded to the disk)
本書は実際の技術検証セッションの記録を基に、固有情報を除去して汎用的な技術教材として再構成したものです。
← 参照コンテンツ一覧に戻る