はじめに

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つのベースモデルで複数の専門家を使い分ける時代へ!🚀