RTX 4060 Ti 1つでLLMをスクラッチ開発する

VRAM 8GBのミドル帯GPU1枚で150Mパラメータの日本語LLMをゼロから作り、最終的に1Bまでスケールさせた記録

はじめに

昨今、ChatGPT・Gemini・Claudeといった大規模言語モデル(LLM)が盛んに使われています。これらはすべてOpenAI・Google・Anthropicといった大企業の巨大資金により開発されたもので、Webアプリやネイティブアプリのように「Xcodeを開けば誰でも作れる」というものではありません。
しかし、効率を突き詰めれば、ミドル帯GPUであるRTX 4060 Ti (VRAM 8GB) 1枚でも150Mパラメータ級のLLMをフルスクラッチで構築することが可能です。最終的には1Bパラメータまでスケールさせました。

今回は2025年8月から2026年6月までに行った効率化手法と、LLMの個人開発でもっとも重要な本質について記録します。

流れ

  • 初期〜2026年3月 : Diamond (135M) 事前学習
  • 2026年3月〜5月 : Gold (120M) 事前学習
  • 2026年5月〜6月 : Diamond 1B (PyTorch移行)
  • 2026年6月〜 : Ruby (150M) 巨大FFN構成

結論: LLMで一番大事なのはコーパス

いきなり結論ですが、LLMで最も重要なのは学習コーパスです。

Garbage In, Garbage Outは有名な話で、まずコーパスが小さいと話になりません。また、Common Crawl (CC) はよく使われますがフィルタリングが命です。コーパスづくりを適当にやると、スキンケア商品の宣伝、怪しい金融商品の勧誘、SEOスパムといったネットにはびこっているダークな部分だけを一生しゃべるアングラLLMが完成します。

コーパスのフィルタリング

すべてのコーパスに対して、以下の共通フィルタリングを適用しています。

def is_clean(line):
    line = line.strip()
    if not line: return None

    # NGワードが1つでも含まれていたら除外
    if NG_PATTERN.search(line):
        return None

    # 句点「。」が1つもない = まともな日本語文章ではない
    if line.count("。") < 1: return None

    # 50文字未満の短文は除外
    if len(line) < 50: return None

    return line

「名詞だけの羅列」や「スパムテキスト」ではなく、ちゃんとした文章だけをパスするフィルタです。NGワードリストにより不適切な内容を含む文を一切排除し、句点の有無で日本語の文章構造を持つかを判定し、最低50文字以上の長さを要求することで断片的なゴミテキストを除去しています。マルチコア並列処理により大規模コーパスでも高速にクリーニングできます。

コーパス比較

これまでに作成・使用したコーパスの内容と評価です。

名称トークン数構成評価
初期コーパス150MWiki 60% + 青空文庫 25% + CC 15%✕ 非常に不安定。3つの人格が混在
Wiki単体650MWiki 100%○ 知識は膨大、文章もまとまってる
CC-100 小1.5BCC 100%△ 日常会話はまあまあ
CC-100 大10BCC 100%○ まあまあ良い。口調に課題あり

初期コーパス (150M) は開発初期から2026年5月まで使いました。Wikipedia・青空文庫・Common Crawlの3ソースを混合したのですが、これが大失敗。あるときは百科事典のような知識人、あるときは「親分」「八五郎」と芝居口調、あるときはネットスラング満載のアングラBotと、3つの人格が完全に混在してしまいました。

Wiki単体 (650M) は結構良い結果でした。知識は膨大で文章のまとまりも良好。ただし日常会話がほぼできず、何を聞いても百科事典のような回答になります。

CC-100 (1.5B / 10B) はCommon Crawlから日本語テキストを抽出したもの。10Bトークン版はまあまあ良く、日常会話もできますが、ほぼネットの個人ブログみたいな口調で若干アホそうな印象です。

ここから見えるのは:

  • コーパスサイズは非常に重要。150Mと10Bでは雲泥の差
  • 多すぎても中身の質が悪いと能力は低い。量だけでは解決しない
  • ソースの混合比率を間違えると人格が崩壊する

モデル

今のLLM産業では、モデルパラメータサイズの大きさが依然として重要な指標として扱われており、「スケールすれば性能が伸びる」という考え方が強く残っています。ただし近年は、単純なパラメータ増加だけでは性能向上の効率が落ちつつあり、いわゆるスケーリングの“伸び方”は変化してきています。

とはいえ、VRAM 8GBのRTX 4060 Tiでトリリオン級モデルは現実的ではなく、多くても200Mくらいが可能なラインです。ここから最終的には効率化を極めて1Bまで到達しました。

共通アーキテクチャ

すべてのモデルで共通する設計です。

  • GPT-like Decoder-only Transformer
  • Pre-RMSNorm + Residual接続
  • SwiGLU活性化関数
  • RoPE (Rotary Position Embedding)
  • GQA (Grouped Query Attention)
  • Weight Tying (入力Embeddingと出力層の重み共有)
  • Causal Mask (自己回帰マスク)
入力 → TokenEmbedding → [TransformerBlock × N] → RMSNorm → TiedOutput → logits

フレームワークはGold・Diamond・RubyがTensorFlow (Keras)1BのみPyTorchです。1BでPyTorchに移行した理由は2つあります。

  1. HuggingFace系の最適化ライブラリが使える: Adafactorをはじめ、PyTorchエコシステムには大規模モデル向けの最適化ライブラリが充実しており、そのまま利用できる
  2. 学習フローを自由に制御できる: Kerasのmodel.fit()は便利ですが、VRAM 8GBで1Bモデルを動かすにはミニバッチ単位でのbackward()→勾配蓄積→empty_cache()といった細かなメモリ管理が必要で、PyTorchのカスタムトレーニングループでないとVRAMパンクを回避できなかった

モデル比較

モデルパラメータフレームワーク構成コーパス評価
Gold120MTensorFlowEmbed 768, 14層, 12H/4KVCC-100 1.5B○ かなり良い
Diamond135MTensorFlowEmbed 768, 12層, 12H/12KV初期 150M△ 文学モードに入る
Ruby150MTensorFlowEmbed 768, 10層, 16H/4KV, FFN 5120CC-100 10B + Wiki 650M事前学習中
1B1BPyTorchEmbed 2048, 20層, 16H/4KVCC-100 10B○ 知識・文体ともに良い

Gold (120M) はCC-100 1.5Bトークンで学習したモデルで、かなり良い出来でした。ハルシネーション(もっともらしい嘘)がすごいので、嘘Wikipediaを書かせたら天才的です。日常会話をしようとすると個人ブログ口調になるのはコーパスの性質がそのまま反映されています。

Diamond (135M) は初期コーパス150Mで学習。コーパスサイズが非常に小さいため、同じ単語のループに入りやすかったり、すぐに芝居口調の文学的な1人会話が始まったりします。青空文庫25%の影響が色濃く出て、「親分」「われ」「なるべし」といった文語調のテキストを生成する癖があります。

Ruby (150M) はFFN次元を5120まで拡大した巨大FFN構成。通常のTransformerではFFN ≈ embed_dim × 8/3 ≈ 2048ですが、Rubyは5120と約2.5倍に拡張しています。FFNはモデルが「知識を記憶する」パラメータなので、コンパクトな層数ながら大容量の知識を格納できる設計です。現在CC-100 10BとWiki 650Mで事前学習中、完成次第評価を行います。

1B はPyTorchに移行して構築した1Bパラメータモデル。知識の豊富さはもちろん、文体のつながり・まとまり・コンテクスト保持が段違いに強いです。当初は「Goldの10倍のサイズがあっても10倍の性能差は感じない」と思っていましたが、実際の生成結果を比較すると10倍近い圧倒的な能力差があるように見えます。しかし、大きな問題としてRTX 4060 Ti 1枚で1Bモデルに10Bコーパスを学習させるには完了まで約28日かかることが判明し、あまりに長すぎるため途中で学習を中断せざるを得ませんでした。

スケーリングの現実

モデルのパラメータ数 × 20くらいがコーパスに必要なトークン数(Chinchilla則)と言われているように、大きなモデルでも相応に大きなコーパスを使わないと十分にパラメータを活かしきることができません。

逆を言えば、小さいモデルでも十分なサイズと高品質なコーパスを使えば、質の悪い巨大モデルに張り合うことが可能です。

また、個人開発においてはハードウェアの物理的な時間限界も大きな壁になります。1Bモデルの圧倒的な性能は魅力的ですが、個人所有のGPU1枚で28日間連続稼働させるのは現実的ではありません。 そこで「パラメータ数を150Mに抑えつつ、知識を記憶するFFN層だけを極端に肥大化させる」というアプローチで現在取り組んでいるのが、先述のRubyモデルです。計算コストを抑えながら巨大モデルの知識量にどこまで迫れるかが今後の課題です。

効率化テクニック

VRAM 8GBでLLMを学習するために、以下の効率化テクニックをすべて組み合わせています。

Weight Tying (重み共有)

入力のToken EmbeddingレイヤーとOutput(logits生成)レイヤーの重みを共有する手法です。

# TiedOutput: 入力Embeddingの重みを転置してlogitsを計算
logits = tf.matmul(inputs, embedding_weights, transpose_b=True)

Vocab Size × Embed Dim分のパラメータをまるごと削減できます。28000語彙 × 768次元の場合、約21Mパラメータ(≈40MB)の節約になり、150Mモデルでは全体の約14%に相当します。出力層が入力Embeddingと同じ空間で動作するため、表現の一貫性も向上します。

Gradient Checkpointing (勾配チェックポイント)

通常、Backpropagationでは全レイヤーの中間出力をメモリに保持しますが、Gradient Checkpointingでは学習時にForwardパスの中間結果を破棄し、Backwardパスで必要になった時点で再計算します。

# TensorFlow
if training:
    return tf.recompute_grad(_compute)(inputs)

# PyTorch
x = checkpoint(block, x, use_reentrant=False)

計算時間は約20-30%増加しますが、VRAMの使用量を劇的に削減できます。VRAM 8GBで14層や20層のTransformerを学習できるのはこの手法のおかげです。

BFloat16 Mixed Precision (混合精度学習)

通常Float32(32bit)で行う計算をBFloat16(16bit)で行うことで、メモリ使用量を半減させつつ計算速度を向上させます。

# TensorFlow
tf.keras.mixed_precision.set_global_policy('mixed_bfloat16')

# PyTorch
with torch.autocast(device_type='cuda', dtype=torch.bfloat16):
    logits = model(inputs)

BFloat16はFloat16と違い指数部がFloat32と同じ8bitなので、オーバーフロー/アンダーフローのリスクが極めて低く、Loss Scalingが不要です。RMSNorm内部だけFloat32にキャストする部分的な精度確保で安定した学習が可能になりました。

Adafactor (メモリ効率オプティマイザ)

Adamは各パラメータに対してモーメンタム(m)と二乗モーメンタム(v)の2つの状態を保持するため、モデルと同サイズのメモリを追加で2倍消費します。Adafactorは二乗モーメンタムを行と列に分解(ランク1近似)することで、状態のメモリを大幅に削減します。

optimizer = Adafactor(
    model.parameters(),
    scale_parameter=False,
    relative_step=False,
    warmup_init=False,
    weight_decay=1e-2
)

1BモデルをVRAM 8GBで学習できたのはAdafactorの貢献が非常に大きいです。

Gradient Accumulation (勾配蓄積)

バッチサイズを大きくするとVRAMが足りなくなるため、小さなミニバッチで何度もForward/Backward計算を繰り返し、勾配を蓄積してから一括で重みを更新する手法です。

# 実効バッチサイズ = BATCH_SIZE × GRAD_ACCUM_STEPS
# 1Bモデル: 1 × 128 = 128の実効バッチサイズ
BATCH_SIZE = 1
GRAD_ACCUM_STEPS = 128

1Bモデルではバッチサイズ1でも128ステップ蓄積することで、実効バッチサイズ128を実現しています。VRAMにはバッチサイズ1分しか載らないため、8GBでも問題なく動作します。

GQA (Grouped Query Attention)

通常のMulti Head Attention (MHA)では全ヘッドがそれぞれ独立したKey/Valueを持ちますが、GQAでは複数のQueryヘッドが1つのKey/Valueヘッドを共有します。

# 16 Query Heads / 4 KV Heads = 4 Queryが1つのKVを共有
self.q_proj = nn.Linear(dim, num_heads * head_dim)      # 16ヘッド分
self.k_proj = nn.Linear(dim, num_kv_heads * head_dim)    # 4ヘッド分
self.v_proj = nn.Linear(dim, num_kv_heads * head_dim)    # 4ヘッド分

Key/Valueの投影パラメータとKVキャッシュが1/4になるため、メモリと計算コストを大幅に削減しつつ、MHAに匹敵する品質を維持します。Llama 2やGemmaでも採用されている手法です。

効率化のまとめ

テクニックVRAM削減速度影響
Weight Tyingパラメータ約14%削減なし
Gradient Checkpointing中間状態の大幅削減20-30%低下
BFloat16 Mixed Precisionメモリ半減むしろ高速化
Adafactorオプティマイザ状態の大幅削減ほぼなし
Gradient Accumulationバッチサイズ1で運用可能ステップ時間増
GQAKV関連パラメータ削減わずかに高速化

これらすべてを組み合わせることで、VRAM 8GBのRTX 4060 Ti 1枚で1Bパラメータモデルの学習を実現しています。

実行例

実際に各モデルがどのようなテキストを生成するか見てみます。

共通ハイパーパラメータ:

  • max_new_tokens: 40 / temperature: 0.75 / top_k: 45 / top_p: 0.7 / repetition_penalty: 1.2
  • Diamondのみループしやすいためrepetition_penalty: 1.5

プロンプト : 「日本の四季にはそれぞれ特徴があり、」

Gold (120M / CC-100 1.5B):

日本の四季にはそれぞれ特徴があり、その中でも特に日本文化や日本の伝統文化の多様性が非常に高い。

Diamond (135M / 初期コーパス 150M):

日本の四季にはそれぞれ特徴があり、その1つで「一一・二六の 多重の 多角形(多大な 多角形)と、その2つの中立(同同)、また、この両分れ目(同同)は、その4つを、本山から小金井に、また、その3つを、その5つを、その3つを、その6つである。

Ruby (150M / 巨大FFN):

日本の四季にはそれぞれ特徴があり、この「和風」と「水」の2つがある。

1B (PyTorch / CC-100 10B):

日本の四季にはそれぞれ特徴があり、春夏秋冬と季節によって花柄や形も異なります。また、「秋の七草」は春の風物詩として親しまれています。 夏の果物は旬の果実を

→ Goldは「多様性が高い」と言うだけで具体性がない。Diamondは数字と漢字の羅列に崩壊。Rubyは短いが文法は保っている。1Bだけが「春夏秋冬」と具体的な四季の話を展開している。

プロンプト : 「おいしいオムライスを作るために大事なことは、」

Gold (120M / CC-100 1.5B):

おいしいオムライスを作るために大事なことは、その方法について説明します。

Diamond (135M / 初期コーパス 150M):

おいしいオムライスを作るために大事なことは、その大元で「一口や二分と、お水(人)のたわ言もなれし もの」という、この本から、また、この大元が、大高に「はまごころをねらえ、大高に小高に三か所、大高に中・下・大・高高に四、上・下・下と、大高に中・下・大高に五、中・大高には無と…

Ruby (150M / 巨大FFN):

おいしいオムライスを作るために大事なことは、お土産に売っています。

1B (PyTorch / CC-100 10B):

おいしいオムライスを作るために大事なことは、まず自分が作りたい料理を自分好みに作るということです。また、その調理法やレシピが食卓にどのように役に立つのかを知ることです。「私はどんな味付けが好きですか?」と質問すると「私の好きなのはサラダでしょう」とおっしゃる方もいらっしゃいます。しかし、それはあくまで理想であって、「美味しいから食べる」というものではなく、「おいしく作れる」のです。

→ Goldは「説明します」で終了、中身がない。Diamondはrepetition_penalty 1.5でも完全崩壊。Rubyは一文で的外れな着地。1Bは料理の話題を維持しながら複数文に渡って展開できている。

プロンプト : 「人工知能が人間の知性を超える日は、」

Gold (120M / CC-100 1.5B):

人工知能が人間の知性を超える日は、その技術は「科学」に格納されている。

Diamond (135M / 初期コーパス 150M):

人工知能が人間の知性を超える日は、その「おさむらいの1つで、この先見は、大いなる日」と、一世一代に、二上山から、一高や三高(現・中高生・下降・中立)を、大成した、大地主、大いなる日。

Ruby (150M / 巨大FFN):

人工知能が人間の知性を超える日は、その場で出会えるような環境で、自分の考え方に興味のある日々が続いていた。

1B (PyTorch / CC-100 10B):

人工知能が人間の知性を超える日は、人類の進歩と人間的成長は相反する。人間が自ら作り出した知的能力を生かして社会や経済を発展させようとする時、人間は他人を自分の力で動かせるようになるのである。そして人類に求められるのは「生きる」ことではなく、「自我の自己実現」、すなわち「創造的な生活」「人としての尊厳ある生活をすることである。」であり、「人生とは自分自身の選択によって成り

→ Goldは「科学に格納」という意味不明な一文で終了。Diamondは「おさむらい」が登場し時代劇モードに突入。Rubyは文法は保つが話題が逸れる。1Bは哲学的な議論を一貫した論調で展開し、コンテクスト保持力の差が最も顕著に出ている。

まとめ

約10ヶ月間でRTX 4060 Ti 1枚を使い、120Mから1Bまでスケールさせながら日本語LLMを開発しました。

要素学んだこと
コーパスの質LLM開発で最も重要。質の悪い大量データより、質の良い適量データ
コーパスの量パラメータ数 × 20トークンが目安。少なすぎると学習不足
ソース混合配分を間違えると人格崩壊する。単一ソースのほうが安定
パラメータ数10倍増やすと圧倒的に強くなるが、個人開発では計算時間(28日)の壁にぶつかる
効率化6つのテクニックの組み合わせでVRAM 8GBでも1B学習可能

AIは計算の精度がそこまで重要ではありません。BFloat16やオプティマイザの量子化など、精度を落としてもあまり品質低下は発生しません。本当に大事なのはコーパスの質とパラメータの割り当て比率です。FFN比を変えたRubyのような実験は、限られたリソースの中でどこにパラメータを集中させるかという問いへの1つのアプローチです。

大企業のような巨大資金がなくても、効率を突き詰めれば個人のGPU 1枚でLLMは作れます。重要なのはハードウェアのスペックではなく、何を学ばせるか・どう学ばせるかという設計判断です。

この内容のリポジトリ: GitHub - rsu-Suba/LLM