第2回:ローカルGPUでの挑戦と挫折 — RTX 3060の6GBで8Bモデルに挑む

はじめに

前回はプロジェクトの全体像と、「ChatGPTがファンドマネージャーに勝った」という実験に触発されて株価予測AIを作ることにした経緯をお伝えしました。

今回は、いよいよ実際に手を動かし始めた最初のフェーズについてです。結論から言うと、見事に撃沈しました。しかし、この失敗は非常に教育的で、LLMのファインチューニングにおけるハードウェアの制約を肌で理解できた貴重な経験でした。


「自分だけのLLM」への憧れ

ファインチューニングをやろうと決めた時、最初に思い浮かんだのは「自分の手元でモデルを育てたい」ということでした。

クラウドAPIに依存するのではなく、自分のGPUで、自分のデータを使って、自分だけのモデルを作る。それがオープンソースLLMの魅力だと感じていました。幸い、手元にはノートPCに搭載されたNVIDIA GeForce RTX 3060があります。

「GPUが載っているなら、きっとファインチューニングもできるはずだ」

今にして思えば甘い考えでしたが、当時の私はそう信じて環境構築を始めました。


環境構築

まず、ローカルでの開発環境を整えました。

項目
GPUNVIDIA GeForce RTX 3060 Laptop GPU(6GB VRAM)
CUDA12.4
PyTorch2.5.1+cu124
Python3.11.11(Anaconda)
OSWindows
開発環境Jupyter Notebook

CUDAのインストールとPyTorchのセットアップには少し手間取りましたが、最終的にGPUが正しく認識されていることを確認できました。

import torch

print(f"PyTorch version: {torch.__version__}")
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"GPU: {torch.cuda.get_device_name(0)}")
print(f"VRAM: {torch.cuda.get_device_properties(0).total_mem / 1024**3:.1f} GB")
PyTorch version: 2.5.1+cu124
CUDA available: True
GPU: NVIDIA GeForce RTX 3060 Laptop GPU
VRAM: 6.0 GB

GPUは認識された。ここまでは順調でした。


モデル選定 — ELYZA Llama-3-JP-8Bを選んだ理由

次に、ファインチューニングするモデルを選びます。

株価予測で使うニュースは日本語です。日本語を高い精度で理解できるモデルが必要でした。2024年末の時点で、日本語に特化したオープンソースLLMとして注目されていたのが ELYZA Llama-3-JP-8B です。

  • モデル名: elyza/Llama-3-ELYZA-JP-8B
  • パラメータ数: 8B(80億パラメータ)
  • ベース: Meta Llama 3
  • 特徴: Llama 3をベースに、日本語データで追加学習されたモデル
  • モデルサイズ: safetensorsで4分割、合計約16GB

日本語の金融ニュースを理解させるには、まず日本語の基礎能力が高いことが重要です。ELYZAはこの点で評判が良く、HuggingFaceのモデルハブから簡単にダウンロードできることも決め手でした。

ただし、16GBのモデルを6GBのVRAMに載せるには工夫が必要です。ここで登場するのが量子化です。


8bit量子化 — 16GBのモデルを6GBに収める

量子化とは、モデルの重みパラメータの数値精度を下げて、メモリ使用量を削減する技術です。通常のモデルはFP32(32bit浮動小数点)で保存されていますが、これをINT8(8bit整数)に変換することで、メモリ使用量を約1/4に削減できます。

BitsAndBytesというライブラリを使うと、モデルのロード時に自動的に8bit量子化を適用できます。

from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

model_id = "elyza/Llama-3-ELYZA-JP-8B"

# 8bit量子化の設定
quantization_config = BitsAndBytesConfig(
    load_in_8bit=True
)

# トークナイザーのロード
tokenizer = AutoTokenizer.from_pretrained(model_id)

# 8bit量子化でモデルをロード
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=quantization_config,
    device_map="auto"
)

16GBのモデルが8bit量子化で約4〜5GBに圧縮され、6GBのVRAMに収まりました。


推論テスト — 動いた!

モデルがロードできたので、まずは通常のテキスト生成を試してみました。

from transformers import pipeline

generator = pipeline(
    "text-generation",
    model=model,
    tokenizer=tokenizer,
    max_new_tokens=256
)

result = generator("日本の株式市場について教えてください。")
print(result[0]["generated_text"])

出力を見て、思わず声が出ました。8bit量子化されたモデルとは思えないほど流暢な日本語で、株式市場についてそれなりに的確な回答が返ってきたのです。

「これは期待できるぞ。このモデルをファインチューニングすれば、もっと良い予測ができるはず」

高揚した気持ちで、いよいよファインチューニングに挑みました。


ファインチューニング — そして撃沈

ファインチューニングのコードを書き、学習を開始しようとしました。Transformersライブラリの Trainer を使い、まずは小さなデータセットで試す計画です。

from transformers import TrainingArguments, Trainer

training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=1,
    per_device_train_batch_size=1,
    save_steps=100,
    logging_steps=10,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
)

trainer.train()  # ← ここでエラー

OutOfMemoryError: CUDA out of memory

VRAMが足りない。学習が始まった瞬間に、メモリエラーで停止しました。


推論と学習のメモリ差

なぜ推論は動いたのに、学習ではメモリが足りなかったのでしょうか。

これはLLMに限らず深層学習全般に当てはまる重要な点です。推論と学習では、GPUメモリの使い方がまったく異なります。

【推論(フォワードパスのみ)】
必要なもの:
  ├── モデルの重み(8bit量子化で約4〜5GB)
  └── 入力データ + 中間出力(少量)
合計: 約5GB → 6GB VRAMに収まる

【学習(フォワードパス + バックワードパス)】
必要なもの:
  ├── モデルの重み(約4〜5GB)
  ├── 勾配(各パラメータの更新方向。重みと同サイズ)
  ├── オプティマイザの状態(Adam使用時、重みの2倍相当)
  └── アクティベーション(バックプロパゲーション用に中間計算結果を保持)
合計: 約16〜20GB → 6GB VRAMでは到底足りない

つまり、学習は推論の3〜4倍のメモリを必要とするのです。8bit量子化で推論がギリギリ動くレベルのVRAMでは、学習に必要な勾配やオプティマイザの状態を保持する余裕がありません。

8Bモデルのファインチューニングには、最低でも24GB以上のVRAMが推奨されます。RTX 3060の6GBでは、まったく太刀打ちできない相手でした。


LoRAという選択肢 — それでも足りなかった

「全パラメータを学習できないなら、一部だけ学習すればいいのでは?」

この考えに基づく手法が LoRA(Low-Rank Adaptation) です。モデルの全パラメータを凍結し、小さな追加パラメータだけを学習することで、メモリ使用量を大幅に削減できます。(LoRAの詳しい仕組みは次回の第3回で解説します。)

LoRAを適用しても、RTX 3060の6GB VRAMではやはり厳しいという結果でした。LoRAで学習パラメータ数は大幅に減るものの、フォワードパス時のアクティベーション保存はモデル全体に対して行われるため、8Bモデルではそれだけでも数GBを消費してしまいます。

LoRA適用時のメモリ内訳(推定):
  ├── 凍結した重み(8bit量子化で約4〜5GB)
  ├── LoRAアダプターの勾配(数十MB — これは小さい)
  ├── アクティベーション保持(バッチサイズ1でも数GB)
  └── その他のオーバーヘッド
合計: 8〜10GB → まだ足りない

6GB VRAMでは、8bit量子化 + LoRAの組み合わせでも収まらなかったのです。


llama.cppでの推論テスト

ファインチューニングは諦めましたが、せっかくの環境を活かして、もう一つ別のアプローチも試しました。llama.cpp を使ったCPU推論です。

llama.cppは、LLMをCPUで高速に推論するためのC/C++実装です。モデルをGGUFという独自の量子化フォーマットに変換し、GPUを使わずに推論できます。

# FP16でGGUFに変換
python convert_hf_to_gguf.py full_model/ --outtype f16 --outfile model.gguf

# 4bit量子化(q4_k_m)でさらに小さく
./llama-quantize model.gguf model_q4.gguf q4_k_m

4bit量子化されたGGUFモデルはわずか数GBで、CPUのRAMに余裕で収まります。チャットボット形式で動作確認したところ、4bit量子化でも意外と自然な日本語を生成できていました。

ただし、llama.cppは推論専用のツールです。ファインチューニング(学習)はできません。ファインチューニング済みのモデルをGGUF形式に変換してローカルで推論する、という使い方は可能ですが、学習自体は別の環境で行う必要があります。

この経験は後の回で、Colabでファインチューニングしたモデルをローカルに持ってきて推論するパイプラインの構築に活きることになります。


この段階で学んだこと

Phase 1の挑戦は「失敗」に終わりましたが、得たものは少なくありませんでした。

1. VRAMがすべてを支配する

ローカルGPUでのLLMファインチューニングでは、VRAMが最大のボトルネックです。モデルの選定、量子化の手法、バッチサイズ、シーケンス長——あらゆる設計判断がVRAMの制約に左右されます。

2. 推論と学習は別物

推論が動くからといって、学習もできるわけではありません。学習は推論の3〜4倍のメモリを必要とします。これは頭では理解していたつもりでしたが、実際にOutOfMemoryErrorを見るまで実感できていませんでした。

3. 量子化は推論の味方、学習にはLoRAが必要

8bit量子化は推論のメモリ削減に絶大な効果がありますが、学習時のメモリ問題を根本的に解決するものではありません。学習時のメモリ削減にはLoRAのような手法が必要であり、さらにそれでも十分なVRAMが要求されます。

4. GGUF変換パイプラインの確立

llama.cppでのGGUF変換と推論のパイプラインを確立できたことは、後のフェーズで役立ちました。HuggingFace形式 → GGUF変換 → 量子化 → CPU推論という一連の流れを理解できたのは収穫です。


次のステップへ — クラウドGPUの活用

ローカルの6GB VRAMでは限界がある。この事実を受け入れて、次の手を考えました。

選択肢はいくつかありました。

選択肢メリットデメリット
より大きなGPUを購入手元に環境ができるRTX 4090(24GB)で約30万円
クラウドGPU(AWS, GCP等)高性能GPUが使える従量課金で費用がかさむ
Google ColabT4 GPU(16GB)が無料枠で使えるGPU使用時間に制限あり
API方式(OpenAI等)インフラ管理不要モデルが手元にない

個人開発という制約を考えると、まず試すべきはGoogle Colabでした。無料でT4 GPU(16GB VRAM)が使える。6GBの約2.7倍。LoRAと8bit量子化を組み合わせれば、8Bモデルのファインチューニングが可能なはずです。

こうして、Phase 2「Google Colabでのファインチューニング」へと進むことになりました。


まとめ

この回のポイントをまとめます。

  • RTX 3060(6GB VRAM)でELYZA Llama-3-JP-8Bに挑戦した
  • 8bit量子化で推論は動いた(流暢な日本語を生成)
  • ファインチューニングはVRAM不足で不可能だった
  • 学習は推論の3〜4倍のメモリを必要とする
  • llama.cppでGGUF変換・CPU推論のパイプラインを確立した
  • ローカルの限界を認め、Google Colabへの移行を決断した

失敗は失敗ですが、ここで得た「VRAMの壁」への肌感覚と、GGUF変換パイプラインの知見は、その後のフェーズで確実に活きています。回り道に見えても、実際に手を動かして壁にぶつかることで得られる理解があると、改めて感じました。


次回: 第3回「LoRAと量子化の技術解説 — LLMを軽くする仕組みたち」

この記事をシェア

関連記事