# 重み差分SVD抽出:汎用手法 ## 2つのモデルの重み差分からLoRAアダプターを作成する方法 この技術は、同じベースモデルから学習された2つのアダプターを持つ**あらゆるLLMアーキテクチャ**で機能します。 GPU不要、訓練データ不要、CPUで1〜3分で実行可能。 ``` モデルA (merged LoRA) モデルB (merged LoRA) │ │ └──────────┬─────────────────────┘ │ W_B - W_A = Δ ▼ 切り詰めSVD (rank r) │ ▼ LoRA アダプター A→B (7 MB) ``` --- ## 1. 必要条件 ✅ 動作する場合: - 両方のモデルが**同じベースアーキテクチャとベース重み**を共有(同じcommit hash) - 両方のモデルが**LoRA + merge**で学習されている(フルファインチューンではない) - テンソル名が両方のモデルで完全に一致 - 2つのテンソルを一度にロードするのに最低4 GBのRAM ❌ 動作しない場合: - 異なるアーキテクチャ(異なるベースモデル) - フルファインチューン(差分が低ランク仮定を超える可能性) - config.json / tokenizer が微調整中に変更された - RAMが4 GB未満 --- ## 2. ステップバイステップガイド ### ステップ1: 2つのモデルを選択 ```python MODEL_A = "lordx64/Qwen3.6-35B-A3B-Claude-4.7-Opus-Reasoning-Distilled" # ソース MODEL_B = "lordx64/Qwen3.6-35B-A3B-Kimi-K2.6-Reasoning-Distilled" # ターゲット ``` ルール: 両方のモデルは同一のテンソル名とconfig.jsonを持つ必要があります。 ### ステップ2: ターゲットモジュールを選択 抽出したい線形層のみを選択: ```python TARGET_MODULES = ["q_proj", "k_proj", "v_proj", "o_proj"] # アテンションのみ # または TARGET_MODULES = ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"] # アテンション + MLP ``` ⚠️ **重要:** 3Dテンソル(例: MoEエキスパート層 `[256, 2048, 512]`)はスキップ — より複雑なスライスごとのSVDが必要です。 ### ステップ3: LoRAランクを選択 ```python RANK = 16 # 標準: サイズと品質の最適バランス RANK = 8 # 最小: より小さく高速だが再構成誤差が高い RANK = 32 # 高品質: 2倍のサイズ、誤差約4%減 ``` ヒント: 再構成誤差分析を実行してユースケースに最適なランクを見つけてください。 ### ステップ4: 抽出スクリプトを実行 ```bash python3 extract_lora_diff.py \ --model_a lordx64/Qwen3.6-35B-A3B-Claude-4.7-Opus-Reasoning-Distilled \ --model_b lordx64/Qwen3.6-35B-A3B-Kimi-K2.6-Reasoning-Distilled \ --output ./my-lora-adapter \ --rank 16 \ --target_modules q_proj,k_proj,v_proj,o_proj ``` ### ステップ5: アダプターを使用 **Python (PEFT):** ```python from peft import PeftModel from transformers import AutoModelForCausalLM base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3.6-35B-A3B") model = PeftModel.from_pretrained(base, "./my-lora-adapter") # モデルはスタイルBになりました! ``` **llama.cpp (GGUF):** ```bash # まずGGUFに変換 python3 llama.cpp/convert_lora_to_gguf.py ./my-lora-adapter # 推論を実行 llama-cli -m base-Q6_K.gguf --lora my-lora-adapter.gguf -p "プロンプト" ``` --- ## 3. 数学的基礎 ``` 与式: M_A = W_base + Δ_A (モデルA = ベース + LoRA A) M_B = W_base + Δ_B (モデルB = ベース + LoRA B) 差分: D = M_B - M_A = Δ_B - Δ_A (ベースが相殺、デルタのみ残る) SVD: D ≈ U_r · Σ_r · V_r^T (ランクr近似) LoRA: A = √Σ_r · V_r^T (lora_A) B = U_r · √Σ_r (lora_B) 順伝播: h = W_0·x + B·A·x (標準LoRA順伝播) ``` **なぜ機能するのか:** - AとBの両方がLoRAランクrで学習 → その差分のランクは ≤ 2r - ランクrのSVDは差分をほぼ完全に再構成可能(91-95%のエネルギー保持) - 学習不要 — これは純粋な数学的分解 --- ## 4. 他のモデルでの例 ### Llama 3.1 8B — スタイル転送 ```bash # 同じLlama-3.1-8Bベースからファインチューニングされた2つのモデル MODEL_A = "user/llama3.1-8b-formal-style" # フォーマルスタイル MODEL_B = "user/llama3.1-8b-casual-style" # カジュアルスタイル python3 extract_lora_diff.py \ --model_a user/llama3.1-8b-formal-style \ --model_b user/llama3.1-8b-casual-style \ --output ./llama-formal-to-casual \ --rank 16 \ --target_modules q_proj,k_proj,v_proj,o_proj ``` ### Mistral 7B — ドメイン適応 ```bash MODEL_A = "mistralai/Mistral-7B-Instruct-v0.3" # 汎用 MODEL_B = "user/Mistral-7B-medical-finetuned" # 医療ドメイン python3 extract_lora_diff.py \ --model_a mistralai/Mistral-7B-Instruct-v0.3 \ --model_b user/Mistral-7B-medical-finetuned \ --output ./mistral-medical-lora \ --rank 16 \ --target_modules q_proj,k_proj,v_proj,o_proj,gate_proj,up_proj,down_proj ``` ### Qwen2.5 72B — 安全性解除 ```bash # 安全版と無検閲版の間の拒否差分を抽出 MODEL_A = "Qwen/Qwen2.5-72B-Instruct" # 安全制限あり MODEL_B = "user/Qwen2.5-72B-uncensored" # 安全制限なし python3 extract_lora_diff.py \ --model_a Qwen/Qwen2.5-72B-Instruct \ --model_b user/Qwen2.5-72B-uncensored \ --output ./qwen-safety-removal-lora \ --rank 16 ``` --- ## 5. パラメータリファレンス | パラメータ | デフォルト | 説明 | |-----------|---------|-------------| | `--rank` | 16 | LoRAランク。高い = 大きい + 高品質。低い = 小さい + 高速 | | `--target_modules` | q,k,v,o_proj | 抽出するモジュール。MLP用にgate/up/downを追加 | | `--alpha` | 32 | LoRAアルファ(スケーリング係数)。通常ランクの2倍 | | `--skip_3d` | True | 3Dテンソル(MoEエキスパート)を自動スキップ | | `--output_format` | peft | `peft` または `gguf` または `both` | --- ## 6. トラブルシューティング | 問題 | 原因 | 解決策 | |---------|-------|----------| | `KeyError: テンソル名不一致` | 異なるベースモデル | 同じベースで学習されたモデルを使用 | | `CUDAメモリ不足` | モデル全体をロード | デフォルトのテンソルバイテンソルモードを使用 | | `ValueError: 非連続テンソル` | SVD出力が非連続 | 保存前に `.contiguous()` を追加 | | `GGUF変換失敗` | テンソル名不一致 | PEFTは `.lora_A.default`、GGUFは `.lora_A.weight` — リネーム | | `ランクがテンソルに対して高すぎる` | テンソル次元 < ランク | ランクを下げるか、そのテンソルをスキップ | --- ## 7. 制限事項 1. **アテンションのみのバイアス**: アテンション層のみでFFN/MLP層の変化を見逃す可能性 2. **低ランク仮定**: LoRAマージモデルに最適。フルファインチューンはランクを超える可能性 3. **品質保証なし**: アダプターは数学的再構成 — 直接学習の品質と一致する保証はない 4. **単一スタイル転送**: 2スタイル間の差異のみ。3つ以上のスタイルは複数アダプターを作成 --- ## 8. 抽出スクリプト `extract_lora_diff.py`(193行)— 本番対応の抽出スクリプトがこのリポジトリで利用可能。 --- ## 9. 参考文献とクレジット - **技術:** UKA(Hermes Agent, Nous Research)& hotdogs - **論文:** [重み差分SVD抽出: ゼロショットLoRAアダプター合成](https://huggingface.co/hotdogs/qwen3.6-35b-opus-to-kimi-lora/blob/main/paper.pdf) - **コード + アダプター:** https://huggingface.co/hotdogs/qwen3.6-35b-opus-to-kimi-lora - **LoRA論文:** Hu et al., 2021 (arXiv:2106.09685) - **QLoRA論文:** Dettmers et al., 2023 (arXiv:2305.14314)