はじめに
LoRAファインチューニングで作った複数の専門モデルを、モデルを再起動せずに切り替える技術が注目されています。この記事では、主要フレームワークでの実装方法を徹底解説します。🎯 LoRAアダプター切り替えとは?
基本概念
1つのベースモデル + 複数の小さなアダプター = 複数の専門家ベースモデル(Llama-3-8B) 3.2GB
├─ SQLアダプター 3.3MB ← 切り替え可能
├─ 日本語チャット 3.3MB ← 切り替え可能
└─ コード生成 3.3MB ← 切り替え可能
メリット
- ✅ メモリ効率: ベースモデル1つで複数タスク対応
- ✅ 高速切り替え: 数秒でモード変更
- ✅ 柔軟性: タスクに応じて最適なアダプター選択
- ✅ 低コスト: 3.2GBのモデルを何個も持つ必要なし
実用例
【朝】SQL生成アダプター → データベース作業
【昼】日本語チャットアダプター → メール返信
【夜】コード生成アダプター → プログラミング
すべて同じベースモデルで実現!
---
🍎 1. MLX(Apple Silicon専用)
最もシンプルな実装コード例
from mlx_lm import load, generate
class LoRAPluginManager:
def __init__(self, base_model: str):
self.base_model = base_model
self.adapters = {}
def register_adapter(self, name: str, adapter_path: str):
"""アダプターを登録"""
self.adapters[name] = adapter_path
def switch_adapter(self, name: str = None):
"""アダプターを切り替え"""
adapter_path = self.adapters.get(name) if name else None
self.model, self.tokenizer = load(
self.base_model,
adapter_path=adapter_path
)
def generate_text(self, prompt: str, max_tokens: int = 100):
"""テキスト生成"""
return generate(
self.model,
self.tokenizer,
prompt=prompt,
max_tokens=max_tokens
)
使用例
manager = LoRAPluginManager("mlx-community/Llama-3.2-3B-Instruct-4bit")
アダプター登録
manager.register_adapter("sql_expert", "./adapters/sql")
manager.register_adapter("japanese_chat", "./adapters/chat")
SQL生成モード
manager.switch_adapter("sql_expert")
result = manager.generate_text("Show me all employees with salary > 50000")
日本語チャットモード
manager.switch_adapter("japanese_chat")
result = manager.generate_text("こんにちは")
ベースモデルに戻す
manager.switch_adapter(None)
実行結果
🔄 アダプター切り替え中... sql_expert (./adapters/sql) ✅ 切り替え完了質問: Table: employees | Question: Show all employees 回答: SELECT * FROM employees WHERE salary > 50000
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━🔄 アダプター切り替え中... japanese_chat (./adapters/chat) ✅ 切り替え完了
質問: こんにちは 回答: こんにちは!今日はどんなお手伝いができますか?
MLXの特徴
- ✅ コード3行で切り替え実装
- ✅ M1 Macで2.3GB、超軽量
- ✅ Apple Silicon完全最適化
- ⚠️ Apple Silicon専用(Intel Mac不可)
🤗 2. PEFT(Hugging Face)
標準的な実装・最も普及コード例
from transformers import AutoModelForCausalLM, AutoTokenizer from peft import PeftModelベースモデルロード
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3-8B") tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3-8B")アダプター1をロード
model = PeftModel.from_pretrained( model, "./adapters/sql", adapter_name="sql_expert" )アダプター2を追加
model.load_adapter("./adapters/chat", adapter_name="japanese_chat") model.load_adapter("./adapters/code", adapter_name="code_gen")アダプター切り替え
model.set_adapter("sql_expert") # SQLモード output = model.generate(...)model.set_adapter("japanese_chat") # チャットモード output = model.generate(...)
model.disable_adapters() # ベースモデル output = model.generate(...)
高度な機能
複数アダプターを同時に有効化
model.set_adapter(["sql_expert", "code_gen"])
アダプターの重み調整
model.set_adapter("sql_expert")
model.set_adapter_scale("sql_expert", 0.8) # 80%の強度
PEFTの特徴
- ✅ 最も標準的で実績豊富
- ✅ 複数アダプター同時有効化
- ✅ 詳細な制御が可能
- ⚠️ GPU推奨(CPUは遅い)
⚡ 3. vLLM(本番環境向け)
大規模サービング専用・数千アダプター対応サーバー起動
vLLMサーバー起動
vllm serve meta-llama/Llama-3-8B \
--enable-lora \
--max-loras 10 \
--max-lora-rank 16 \
--lora-modules sql_expert=./adapters/sql \
japanese_chat=./adapters/chat
API経由で切り替え
import requests
SQLアダプター使用
response = requests.post("http://localhost:8000/v1/completions", json={
"model": "meta-llama/Llama-3-8B",
"lora_name": "sql_expert",
"prompt": "Show me all employees",
"max_tokens": 100
})
チャットアダプター使用
response = requests.post("http://localhost:8000/v1/completions", json={
"model": "meta-llama/Llama-3-8B",
"lora_name": "japanese_chat",
"prompt": "こんにちは",
"max_tokens": 100
})
動的ロード
実行中にアダプターを追加
requests.post("http://localhost:8000/v1/load_lora_adapter", json={
"lora_name": "new_adapter",
"lora_path": "./adapters/new"
})
アダプター削除
requests.post("http://localhost:8000/v1/unload_lora_adapter", json={
"lora_name": "old_adapter"
})
vLLMの特徴
- ✅ 数千個のアダプター同時サービング(S-LoRA技術)
- ✅ リクエストごとに異なるアダプター使用
- ✅ 本番環境で実績あり(AWS、Azure等)
- ✅ 複数GPUサポート
- ⚠️ セットアップ複雑
- ⚠️ サーバーベース(CLIスクリプトには不向き)
🦙 4. llama.cpp(軽量実行)
C++実装・クロスプラットフォーム基本的な使い方
複数アダプター指定
./llama-cli -m model.gguf \
--lora ./adapters/sql.gguf \
--lora ./adapters/chat.gguf \
--lora-scaled ./adapters/sql.gguf 0.5
現状の制限
- 🟡 基本は起動時に指定
- 🟡 ランタイム切り替えは検討中
- ⚠️ PythonのようなAPI切り替えは未実装
llama.cppの特徴
- ✅ 超軽量・高速
- ✅ Windows/Mac/Linux対応
- ✅ CPUでも実用的
- ⚠️ 動的切り替えは限定的
🔧 5. その他のフレームワーク
Modular MAX
max serve llama-3 \
--lora-path ./sql_adapter \
--lora-path ./chat_adapter
- ✅ 静的・動的両方サポート
- ⚠️ まだ発展途上
AWS SageMaker
SageMaker Multi-Adapter Endpoint
predictor.predict(
data={"inputs": "Show all employees", "lora_adapter": "sql_expert"}
)
- ✅ 数百個のアダプター管理
- ⚠️ クラウド専用、コスト高
📊 フレームワーク比較表
| フレームワーク | 動的切り替え | 同時アダプター数 | セットアップ | Apple Silicon最適化 | メモリ効率 | 用途 | |---------------|------------|-----------------|------------|-------------------|-----------|------| | MLX | ◎ 即座 | 制限なし | ◎ 超簡単 | ◎ 専用最適化 | ◎ 2-3GB | ローカル開発・M1 Mac | | PEFT | ◎ 即座 | 10-20個 | ○ 簡単 | △ | ○ 8-12GB | 研究・実験 | | vLLM | ◎ 即座 | 1000+ | △ やや複雑 | × | ◎ 効率的 | 本番サービング | | llama.cpp | △ 起動時 | 数個 | ○ | ○ 良好 | ◎ 超軽量 | 軽量実行 | | Modular MAX | ○ | 未公開 | ○ | △ | ○ | 新興 |
---💡 実用シナリオ
シナリオ1: 個人開発者(M1 Mac)
推奨: MLXたった10行でマルチタスクAI
manager = LoRAPluginManager("Llama-3.2-3B-Instruct-4bit")
manager.register_adapter("sql", "./sql_adapter")
manager.register_adapter("chat", "./chat_adapter")
朝: データベース作業
manager.switch_adapter("sql")
昼: メール返信
manager.switch_adapter("chat")
夜: 元のモデルで一般タスク
manager.switch_adapter(None)
メリット:
- ✅ メモリ2.3GB
- ✅ コード超シンプル
- ✅ 切り替え数秒
シナリオ2: スタートアップ(本番API)
推奨: vLLMメリット:1つのサーバーで複数顧客対応
POST /v1/completions { "model": "Llama-3-8B", "lora_name": "customer_A_adapter", # 顧客A専用 "prompt": "..." }
POST /v1/completions { "model": "Llama-3-8B", "lora_name": "customer_B_adapter", # 顧客B専用 "prompt": "..." }
- ✅ 1つのGPUで複数顧客サービング
- ✅ コスト削減
- ✅ スケーラブル
シナリオ3: 研究者
推奨: PEFT複数アダプターの効果を比較
model.set_adapter("method_A")
result_A = evaluate(model)
model.set_adapter("method_B")
result_B = evaluate(model)
アダプターの組み合わせ実験
model.set_adapter(["method_A", "method_B"])
result_AB = evaluate(model)
メリット:
- ✅ 標準的で論文再現しやすい
- ✅ 柔軟な実験
- ✅ コミュニティ大きい
🚀 実装例: MLXでマルチエキスパートAI
完全な実装
---#!/usr/bin/env python3 """ LoRAアダプター動的プラグインシステム 複数のLoRAアダプターを切り替えながら使用可能 """from mlx_lm import load, generate from typing import Dict, Optional import os
class LoRAPluginManager: """LoRAアダプターのプラグイン管理システム"""
def __init__(self, base_model: str): self.base_model = base_model self.adapters: Dict[str, str] = {} self.current_adapter: Optional[str] = None self.model = None self.tokenizer = None
def register_adapter(self, name: str, adapter_path: str): """アダプターを登録""" if not os.path.exists(adapter_path): raise FileNotFoundError(f"アダプターが見つかりません: {adapter_path}") self.adapters[name] = adapter_path print(f"✅ アダプター登録: {name} -> {adapter_path}")
def switch_adapter(self, name: Optional[str] = None): """アダプターを切り替え""" if name and name not in self.adapters: raise ValueError(f"未登録のアダプター: {name}")
adapter_path = self.adapters.get(name) if name else None print(f"\n🔄 アダプター切り替え中...") if name: print(f" {name} ({adapter_path})") else: print(f" 元のモデル(アダプターなし)")
self.model, self.tokenizer = load( self.base_model, adapter_path=adapter_path ) self.current_adapter = name print(f"✅ 切り替え完了\n")
def generate_text(self, prompt: str, max_tokens: int = 100) -> str: """テキスト生成""" if self.model is None: raise RuntimeError("先にswitch_adapter()を呼んでください") return generate( self.model, self.tokenizer, prompt=prompt, max_tokens=max_tokens, verbose=False )
def list_adapters(self): """登録されているアダプター一覧を表示""" print("\n📋 登録済みアダプター:") print(" - (なし): 元のモデル") for name, path in self.adapters.items(): current = "← 現在使用中" if name == self.current_adapter else "" print(f" - {name}: {path} {current}") print()
デモ実行
if __name__ == "__main__": manager = LoRAPluginManager("mlx-community/Llama-3.2-3B-Instruct-4bit")# アダプター登録 manager.register_adapter("sql_expert", "./adapters/sql") manager.register_adapter("japanese_chat", "./adapters/chat") manager.register_adapter("code_generator", "./adapters/code")
manager.list_adapters()# 使用例 manager.switch_adapter("sql_expert") print(manager.generate_text("Show top 5 employees by salary"))
manager.switch_adapter("japanese_chat") print(manager.generate_text("こんにちは"))
manager.switch_adapter(None) print(manager.generate_text("What is AI?"))
🎯 パフォーマンス比較
切り替え速度
| フレームワーク | 切り替え時間 | メモリオーバーヘッド | |---------------|------------|-------------------| | MLX | 1-3秒 | +0MB(アダプター分のみ) | | PEFT | 1-2秒 | +100-500MB | | vLLM | <1秒 | +数MB(S-LoRA) | | llama.cpp | N/A(再起動必要) | +0MB |
メモリ使用量(Llama-3-8B)
ベースモデルのみ: 8GB
+ SQLアダプター: 8.003GB (+3MB)
+ 10個のアダプター: 8.03GB (+30MB)
+ 100個のアダプター: 8.3GB (+300MB)
結論: アダプター追加のコストは極小!
---
✨ まとめ
LoRAアダプター動的切り替えの価値
1. メモリ効率: 1つのベースモデルで複数の専門家 2. 柔軟性: タスクに応じて最適な性能 3. コスト削減: GPU/メモリの節約 4. 開発効率: モジュール化された専門性
フレームワーク選択ガイド
┌─────────────────────────────────────────┐ │ Apple Silicon Mac を持っている? │ │ │ │ YES → MLX │ │ (最速・最軽量・最簡単) │ │ │ │ NO ↓ │ └─────────────────────────────────────────┘┌─────────────────────────────────────────┐ │ 本番環境で大規模サービング? │ │ │ │ YES → vLLM │ │ (1000+アダプター対応) │ │ │ │ NO ↓ │ └─────────────────────────────────────────┘
┌─────────────────────────────────────────┐ │ 研究・実験・プロトタイピング? │ │ │ │ YES → PEFT │ │ (標準的・実績豊富) │ │ │ │ NO ↓ │ └─────────────────────────────────────────┘
┌─────────────────────────────────────────┐ │ 超軽量実行・古いハードウェア? │ │ │ │ YES → llama.cpp │ │ (CPUでも実用的) │ └─────────────────────────────────────────┘
推奨の組み合わせ
個人開発者:- M1/M2/M3 Mac: MLX
- その他: PEFT or llama.cpp
- 開発環境: PEFT
- 本番環境: vLLM
- AWS/Azure: SageMaker/Azure ML
- オンプレ: vLLM
🔗 参考リソース
公式ドキュメント
関連記事
--- 実行環境: M1 Mac / macOS 24.6.0 / MLX-LM 0.28.2 執筆日: 2025-10-13 検証済みフレームワーク: MLX, PEFT, vLLM, llama.cpp --- 次のステップ:1. 自分の環境に合ったフレームワークを選ぶ 2. 複数の専門アダプターを作成 3. 動的切り替えシステムを実装 4. 実際のタスクで運用
1つのベースモデルで複数の専門家を使い分ける時代へ!🚀