LoRA と全パラメータ微調整 — 何を失い、何を得るか
LoRAは重みの更新を ΔW=BA という細い2枚の行列に押し込む手法です。なぜそれで足りるのか、全パラメータ微調整に対して何を得て何を失うのか、rankの決め方とマージの落とし穴までを前提知識ゼロから解説します。
要点
- LoRAは重みの更新をΔW = BAという低ランクの形に制約し、学習対象を元の0.4%程度に抑える。
- 全パラメータ微調整が重い原因は重みでなく、勾配とオプティマイザ状態が更新する全パラメータ分だけ必要になる点にある。
- 元の重みW₀は凍結され、後付けの補正BAだけが学習される。Bをゼロで初期化するので挿した直後は元のモデルと同一になる。
- 効き方はrだけでなくα/rと適用層で決まり、rを倍にしてαを据え置くと補正の効きは実質半分になる。
- ΔWのランクはr以下という表現力の上限があり、大量の新知識の注入では全パラメータ微調整に届かないことがある。
LoRA: Low-Rank Adaptation of Large Language Models
一次資料 — この記事の根拠
論文の発表 2021-06-17→この解説の公開 2026-09-095年3か月後
LoRA: Low-Rank Adaptation of Large Language ModelsEdward J. Hu, Yelong Shen, Phillip Wallis ほか · 2021-06-17 · v2arXiv:2106.09685論文ページ·PDFIntrinsic Dimensionality Explains the Effectiveness of Language Model Fine-TuningarXiv:2012.13255論文ページ·PDF
QLoRA: Efficient Finetuning of Quantized LLMsarXiv:2305.14314論文ページ·PDF
原文の要旨(Abstract)を読む
An important paradigm of natural language processing consists of large-scale pre-training on general domain data and adaptation to particular tasks or domains. As we pre-train larger models, full fine-tuning, which retrains all model parameters, becomes less feasible. Using GPT-3 175B as an example -- deploying independent instances of fine-tuned models, each with 175B parameters, is prohibitively expensive. We propose Low-Rank Adaptation, or LoRA, which freezes the pre-trained model weights and injects trainable rank decomposition matrices into each layer of the Transformer architecture, greatly reducing the number of trainable parameters for downstream tasks. Compared to GPT-3 175B fine-tuned with Adam, LoRA can reduce the number of trainable parameters by 10,000 times and the GPU memory requirement by 3 times. LoRA performs on-par or better than fine-tuning in model quality on RoBERTa, DeBERTa, GPT-2, and GPT-3, despite having fewer trainable parameters, a higher training throughput, and, unlike adapters, no additional inference latency. We also provide an empirical investigation into rank-deficiency in language model adaptation, which sheds light on the efficacy of LoRA. We release a package that facilitates the integration of LoRA with PyTorch models and provide our implementations and model checkpoints for RoBERTa, DeBERTa, and GPT-2 at https://github.com/microsoft/LoRA.
二種類の直し方
完成した建物を改装する場面を思い浮かべてください。方法は大きく2つあります。1つは、全部の壁と柱を触れる状態にして、どこでも好きに作り替える方法。もう1つは、既存の構造には一切手を入れず、必要な場所にだけ薄いパネルを重ねて貼る方法です。
前者が全パラメータ微調整(full fine-tuning)、後者が LoRA(Low-Rank Adaptation) です。前者は自由度が高い代わりに足場と工期が要ります。後者は工事が軽く、パネルを剥がせば元に戻り、別のパネルに貼り替えることもできます。その代わり、構造そのものを変えるような改装はできません。
この記事では、その「できないこと」が実際にどこに現れるのかまで含めて見ていきます。
全パラメータ微調整は、何にお金を払っているか
まず、なぜ全パラメータ微調整が重いのかを分解します。意外に思われますが、重いのは重みそのものではなく、その周辺です。
70億パラメータのモデルを Adam で学習するとき、パラメータ1個あたりに必要なものを数えてみましょう。
- 重み本体(bf16)… 2バイト
- 勾配(bf16)… 2バイト
- Adamの1次モーメント(fp32)… 4バイト
- Adamの2次モーメント(fp32)… 4バイト
- 数値精度を保つためのfp32マスター重み … 4バイト
合わせて1個あたり約16バイト。70億倍すると110GB前後になります。推論だけなら重みの2バイト分(14GB程度)で済むのに、学習しようとした瞬間に8倍近く膨れる。この差の正体は、勾配とオプティマイザ状態です。しかもこれは活性値(順伝播の途中結果)を数える前の話です。
ここが重要な観察です。重い部分は「更新される全パラメータの分だけ」発生する。ならば、更新する対象を減らせば、この膨張はまるごと消えます。LoRAが狙っているのはそこです。
低ランク仮定: ΔW = BA
微調整の前後で、ある重み行列 が に変わったとします。LoRAの主張は、この が細長い2枚の行列の積で十分近似できる、というものです。
日本語にすると、「行列の巨大な変更表を、いったん 本の細い軸に潰してから元の大きさに戻す形で表す」ということです。 が rank(ランク、階数) で、この軸の本数を意味します。 が「入力を 本の軸に射影する」係、 が「その 本を出力側に広げ直す」係です。
パラメータ数を比べると効き目が見えます。 の行列を丸ごと更新すると約1678万個。同じ場所を のLoRAで置き換えると 個で、元の0.4%程度です。学習時に持つべき勾配とオプティマイザ状態も、この0.4%の分だけになります。
実際の順伝播はこう書けます。
が入力ベクトル、 が出力です。第1項は元のモデルがもともとやっていた計算で、 は凍結されたまま一切変わりません。第2項が後付けの補正で、ここだけが学習されます。 は補正の強さを決める定数で、 で割っているのは「 を変えても補正の大きさの目安がずれないようにする」ための正規化です。
なぜ低ランクで足りるのか
「更新を細い行列に押し込んで本当に足りるのか」— これがLoRAの一番の勘所です。
根拠は経験的なものです。事前学習済みモデルを下流タスクに適応させる操作は、ゼロから知識を入れ直す作業ではなく、すでにある能力の重み付けを変える作業に近い。Aghajanyanらは、微調整に必要な実質的な自由度(内在次元)が、全パラメータ数よりはるかに小さいことを実験的に示しました。LoRAはその観察を、行列のランクという扱いやすい形に落とし込んだものです。
低ランク近似そのものの感覚は、下の図で掴めます。ランクを1本ずつ増やしたとき、誤差とパラメータ数がどう釣り合うかを直接いじってみてください。
触ると分かるのは、最初の数本が圧倒的に効き、その後は追加1本あたりの改善が急速に細ることです。行列の情報が少数の強い方向に偏っているからで、この偏りがある限り低ランク近似は割に合います。逆に、更新すべき内容が全方向に散らばっている問題では、この前提は崩れます。後で触れる「LoRAが苦手な領域」は、まさにそこです。
なお、ここで動かしているのは既知の行列に対するSVD的な近似です。LoRAはそれと違い、最適な を知らないまま を勾配降下で直接学習します。近似の上限は同じでも、そこに到達できるかは別問題、という点は覚えておいてください。行列とランクの関係そのものはSVDと低ランク近似で詳しく扱っています。
初期化がゼロである理由
実装の細部にひとつ、外すと学習が壊れる箇所があります。 はランダムな小さい値、 はゼロで初期化するという約束です。
なら なので、学習開始時点の は元のモデルとまったく同じになります。つまりLoRAを挿した瞬間のモデルは、挿す前と1ビットも変わらない。ここから補正が少しずつ育っていきます。
もし両方をランダムに初期化すると、学習を始める前から出力にノイズが乗り、せっかくの事前学習済みの振る舞いが最初の数ステップで壊れます。「元の状態から連続的に出発する」というのは、後付け手法に共通する設計原則です。
rank をどう選ぶか
現場でまず聞かれるのが「 はいくつにすればいいか」です。原理的な最適値はなく、タスクが要求する変化の大きさで決まります。目安として:
- 文体・口調・出力フォーマットを揃えたい … 小さい (数〜十数)で足りることが多い。既存の能力の出し方を変えているだけなので
- 新しい語彙や記法の体系を入れたい(社内の専門用語、独自DSL、稀な言語) … 大きめの が要る。表層の重み付けではなく中身を足す作業になる
- その分野の知識そのものを大量に入れたい … LoRAで押し切るより、継続事前学習や全パラメータ微調整を検討したほうが早いことがある
は の1〜2倍あたりに置く運用が広く使われています。 が実効的な学習の強さを決めるので、 を倍にしたのに を据え置くと、補正の効きが実質半分になる点に注意してください。「rankを上げたのに性能が落ちた」の相当数はこれです。
もうひとつ、 を上げれば上げるほど良くなるわけでもありません。データが少ない状態で自由度だけ増やせば、訓練損失は下がるのに評価が悪化する、おなじみの現象が起きます。
どの層に挿すかも rank と同じくらい効きます。原論文は注意機構の と に絞っていましたが、その後の実践ではMLPを含む全ての線形層に挿すほうが強い、という報告が広く共有されています。QLoRAの論文でもこの点が指摘されています。 を上げる前に、まず適用範囲を広げるか試すのが順番として合理的です。
何を失うのか
ここまで良い話が続いたので、失うものを正面から書きます。
1. 表現力の上限がある。 のランクは定義上 以下です。本当に必要な更新が高ランクだった場合、どれだけ学習しても届きません。継続事前学習のように大量の新知識を注入する用途で、LoRAが全パラメータ微調整に届かないという報告が複数あります。「学びが浅い代わりに忘れにくい」という言い方をされます。
2. 忘れにくさは、裏返せば動きにくさ。 が凍結されているため、元のモデルの一般能力が壊れにくいという利点があります(この現象は破滅的忘却で扱っています)。ただしこれは、元のモデルの悪い癖も一緒に温存されるということでもあります。事前学習で染み付いた望ましくない傾向を消したい場合、LoRAは向いていません。
3. 推論時のオーバーヘッドは「マージすれば」ゼロ。 LoRAの強みのひとつは、 を事前に計算して1枚の重み行列に畳み込める(マージできる)ことです。マージ後は普通のモデルと完全に同じ形なので、追加の遅延はありません。逆にマージせずに複数のLoRAを差し替えながら使う運用では、行列積が2本走る分だけ遅くなります。1台のGPUで顧客ごとのLoRAを切り替える設計では、この差が効いてきます。
マージの落とし穴
マージは便利ですが、思われているほど安全ではありません。
複数のLoRAを足し合わせても、両方の能力が足し算にはなりません。 は数学的には計算できますが、それぞれが の周りで学習された補正である以上、同時に適用したときの振る舞いは学習時に一度も見ていない状態です。片方が優勢になったり、両方が劣化したりします。重み付き平均や直交化などの手法はありますが、「足せば合成できる」という素朴な期待は捨ててください。
量子化されたベースモデルへのマージは非可逆です。 QLoRAのように4bit量子化した の上でLoRAを学習した場合、マージ先の精度をどうするかで結果が変わります。fp16に戻してマージすればサイズが膨らみ、4bitのまま畳み込めば量子化誤差が乗ります。学習時と配布時で数値精度が違うという事故は、評価スコアが再現しない典型的な原因です。量子化そのものの挙動は量子化を1から理解するを参照してください。
コードで見る本体
PyTorchでの最小実装は、驚くほど短くなります。
class LoRALinear(nn.Module):
def __init__(self, base: nn.Linear, r: int, alpha: int):
super().__init__()
self.base = base
for p in self.base.parameters():
p.requires_grad = False # W0 は凍結
self.A = nn.Parameter(torch.randn(r, base.in_features) * 0.01)
self.B = nn.Parameter(torch.zeros(base.out_features, r)) # 開始時 ΔW=0
self.scale = alpha / r
def forward(self, x):
return self.base(x) + self.scale * (x @ self.A.T @ self.B.T)
学習すべき箇所は A と B だけで、オプティマイザに渡すのもこの2つだけです。実務では自分で書かずに Hugging Face の peft を使い、LoraConfig(r=..., lora_alpha=..., target_modules=[...], lora_dropout=...) を設定します。マージは model.merge_and_unload() です。
現場ではこう使う
誰が、いつ使うか。 自社データでLLMを調整する立場の人(MLエンジニア、アプリケーション寄りのデータサイエンティスト)が、最初に試す選択肢がLoRAです。判断の順序としては、①プロンプト設計とRAGで足りないか確認 →②足りなければLoRA →③それでも届かず、かつ大量の新知識が必要なときだけ全パラメータ微調整、という並びが費用対効果に合います。いきなり全パラメータ微調整に行くのは、GPU予算の使い方として上策ではありません。
実際に触るもの。 peft の LoraConfig における r / lora_alpha / target_modules / lora_dropout、量子化するなら bitsandbytes の4bit設定、学習ループ側では gradient_checkpointing と実効バッチサイズ。まず target_modules を全線形層に広げ、r=16, lora_alpha=32 あたりから始めて、必要に応じて上下させるのが実用的な出発点です。
知らないと事故になる落とし穴。
rを変えたのにlora_alphaを据え置く。 実効的な学習の強さが変わってしまい、比較実験が比較になりません。 を固定するか、変えたことを明記して記録してください。- マージ前の性能で評価し、マージ後を配布する。 量子化を挟んでいる場合、この2つは別物です。配布する形のまま評価するのが鉄則です。
- LoRAを外し忘れた/挿し忘れたまま推論する。 アダプタ未ロードのモデルは、エラーを出さずに素のモデルとして動きます。静かに失敗するので、微調整で入れたはずの振る舞いを1件だけ確認する煙感知テストを、デプロイ手順に入れておいてください。
- 学習率を全パラメータ微調整と同じにする。 LoRAは更新するパラメータが少なく、ゼロから始まる補正を育てるため、桁が1つ大きい学習率が使われるのが普通です。全パラメータ微調整の設定をそのまま持ち込むと、まったく動いていないように見えます。
問われがちな問い。 「LoRAで学習した2つのモデルを1つにまとめられますか」に対しては、「重みは足せるが、能力は足し算にならない。まず両方のデータを混ぜて1本のLoRAを学習することを検討する」と答えられれば、性質を理解していることが伝わります。
まとめ
- LoRAは重みの更新を という低ランクの形に制約し、学習対象を数%以下に落とす手法
- 軽くなる主因は重みではなく、勾配とオプティマイザ状態が更新対象の分しか要らなくなること
- 失うのは表現力の上限。新しい知識を大量に入れる用途では全パラメータ微調整に届かないことがある
- 単体ではなく と適用層 で効き方が決まる。マージは便利だが、複数合成と量子化の組み合わせで壊れる
原論文がどんな実験でこの設計を正当化したのかは、論文解説 LoRAで本文に沿って読み解いています。
コメント
コメントにはログインが必要です