さとまたwiki

🧠 0からLLM — 目指せDeepSeek

MoEモデルを基礎から構築する完全ガイド — HuggingFace活用・日本語対応 2026年版

🎯 ミッション: 0からLLMを作ってDeepSeekを目指す

なぜ今、自作LLMなのか?

2024〜2026年、オープンソースLLMの世界に革命が起きた。中国のDeepSeekが公開した DeepSeek V3(671B MoEモデル)は、GPT-4oに匹敵する性能を 大幅に少ない計算コストで実現し、MIT相当のライセンスで公開した。

この成果を受けて、日本の大手企業も素早く動いた。

  • 楽天AI: DeepSeek V3をベースモデルとしてフォークし、大量の日本語データで追加学習を行った RakutenAI-2.0を開発。日本語・英語・コードの三言語対応を実現。 フルスクラッチではなく「巨人の肩に乗る」戦略で短期間での高品質モデル構築に成功。
  • サカナAI: 独自の進化的モデルマージ手法(EvoLLM)を開発。 既存のオープンソースモデルを複数組み合わせ、進化アルゴリズムで最適なマージ比率を探索する アプローチで日本語高性能モデルを構築。少ない計算資源で高品質なモデルを作る革新的手法として注目。

3つのルート

① 完全スクラッチ

PyTorchでTransformerを0から実装。nanoGPTが定番の出発点。最も学習効果が高い。

難易度: ★★★☆☆

② Llamaフォーク

MetaのLlama 3.xをベースに日本語データで追加学習。実用的な日本語LLMを最短で作れる。

難易度: ★★☆☆☆

③ DeepSeekフォーク

DeepSeekをベースにMoE構造を活かした学習。楽天・サカナAIが実証済みのアプローチ。

難易度: ★★★★☆

このページで学べること

  • Transformerアーキテクチャの仕組みとLLMの基礎知識
  • MoE(Mixture of Experts)の構造と利点
  • nanoGPTで125Mモデルを作る具体的な手順
  • Llama 3.xにLoRA/QLoRAで日本語ファインチューニングする方法
  • DeepSeek V2-Liteをフォークしてカスタムモデルを作る手順
  • HuggingFaceの日本語データセット・モデルの選び方
  • RTX 3090 1〜2枚で始められる実践的な学習環境の構築法

前提知識

PythonPyTorchの基礎(テンソル操作、nn.Module)があれば十分です。 線形代数(行列積)の直感的な理解があると理解が深まります。 機械学習の深い知識は不要 — このページを読みながら学べます。

ハードウェア要件の目安

規模パラメータ数必要VRAM推奨GPU学習時間目安
超小規模(実験)125M〜1B4〜8GBRTX 3060 / GTX 1080数時間〜1日
小規模1B〜7B16〜24GBRTX 3090 / RTX 40902〜7日
中規模7B〜30B40〜80GBA100 40GB×2〜41〜4週間
大規模30B〜70B160GB以上A100 80GB×4〜81〜3ヶ月
超大規模70B〜671B1TB以上H100×数十〜数百枚数ヶ月

📚 LLMの基礎: Transformerアーキテクチャ

Transformerの仕組み

現代のLLMは全てTransformerアーキテクチャをベースにしています。 2017年にGoogleが発表した「Attention is All You Need」論文が起点で、 今もその基本構造は変わっていません。

Self-Attention

文中の各トークンが他の全トークンとの「関連度」を計算するメカニズム。 「猫が魚を食べた」→「食べた」が「猫」と「魚」の両方に注目できる。 Query、Key、Value の3つの行列を用いた内積計算で実現。

FFN(Feed-Forward Network)

各トークンに独立して適用される2層のMLPネットワーク。 Attentionで「どこを見るか」を決めた後、FFNで「何を考えるか」を処理する。 パラメータの大半はここに含まれる(MoEもここを分散化する)。

Layer Normalization

各層の出力を正規化することで学習を安定させる。 現代のLLMはPre-LN(層の前に適用)が主流。 残差接続(Residual Connection)と組み合わせて勾配消失を防ぐ。

GPT系(Decoder-only)が現在の主流

Transformerには Encoder-Decoder(T5, BART)と Decoder-only(GPT, Llama, DeepSeek)の2系統があります。 テキスト生成に特化した Decoder-only が現在の主流です。 Causal Attention(過去のトークンのみ参照)により、自己回帰的にトークンを1つずつ生成します。

トークナイザーの役割

LLMはテキストをそのまま処理できません。テキストをトークン(数値ID)に変換する 「トークナイザー」が必要です。

BPE(Byte Pair Encoding)

頻出する文字ペアを繰り返し結合してサブワード語彙を作る手法。 GPT-2、LlamaのTokenizerで採用。英語に最適化されている。

⚠️ 日本語は英語向けBPEだと1文字ずつ分割されてしまい、 同じ意味を表すのに英語の3〜4倍のトークンが必要になる。

SentencePiece

Googleが開発した言語非依存のサブワードトークナイザー。 スペースなしでも動作するため日本語に向いている。 rinna、ELYZA等の日本語LLMで採用。

✅ 日本語専用のSentencePieceを学習すれば、 「東京」を1〜2トークンで表現できる。

パラメータ数とモデルサイズの関係

パラメータ数fp16サイズfp32サイズ備考
125M0.25GB0.5GBnanoGPT規模、実験に最適
1B2GB4GBモバイルデプロイ可能な最小クラス
7B14GB28GBLlama 3.1 8B相当。RTX 3090 1枚で推論可能
13B26GB52GBRTX 3090 1枚+オフロードで推論可能
70B140GB280GBA100 80GB×2枚必要
671B(MoE)〜80GB(活性化分のみ)-DeepSeek V3。活性化は37B相当

※ 推論時のVRAM使用量はKVキャッシュ分が追加で必要。学習時はさらに勾配・オプティマイザ状態で2〜4倍必要。

🔀 MoE(Mixture of Experts)完全解説

MoEとは何か

通常のDenseモデルでは全てのFFN層が毎回活性化されます。 MoEでは1つの大きなFFNの代わりに複数の小さなFFN(Expert)を用意し、 入力に応じて一部のExpertだけを選択・活性化します。 DeepSeek V3では671Bのパラメータを持ちますが、推論時に実際に使うのは約37B相当のみです。

MoEの構成要素

Expert(FFN)

それぞれ独立したFFNネットワーク。 DeepSeek V3では256個のExpertが存在し、 各Expertが特定の知識・タスクに特化するよう学習される。

Router(ゲーティング関数)

各トークンに対して「どのExpertを使うか」を決める小さなネットワーク。 各Expertへのスコアを計算し、Top-K個のExpertを選択する。 Routerの学習が安定しないとExpertの偏りが発生する。

Top-K選択

Routerが全Expertにスコアを付け、上位K個のExpertのみを活性化する仕組み。 DeepSeek V3はTop-8(256個中8個を使用)。 KとExpert総数のバランスがモデル性能を左右する。

Dense vs MoE 比較

項目Dense モデルMoE モデル
アーキテクチャ全FFN層が毎回活性化一部のExpertのみ活性化(Top-K)
総パラメータ数そのまま全て使用大きいが推論時は一部のみ
推論時の計算量パラメータ数に比例活性化パラメータ数に比例(大幅削減)
メモリ使用量モデル全体のサイズモデル全体のサイズ(ロードは全体)
学習の難しさ比較的シンプルRouter最適化・負荷分散が難しい
代表モデルGPT-4, Llama 3.1, GemmaDeepSeek V3, Mixtral, GPT-4(推定)

楽天AI・サカナAIがMoEを採用した理由

楽天AI(RakutenAI-2.0): DeepSeek V3(671B MoE)をベースに採用した最大の理由は「推論コストの低さ」。 671Bの総パラメータを持ちながら実際の計算は37B相当なので、 サービス提供時の GPU コストがDense 70B モデルとほぼ同等に抑えられる。 日本語・英語・コードの3言語に特化したデータで追加学習することで、 汎用性を保ちながら日本語性能を大幅向上させた。

サカナAI(EvoLLM): 独自のモデルマージ手法で複数のオープンソースモデルを組み合わせる。 進化的アルゴリズムで最適なマージ比率を探索することで、 フルスクラッチの事前学習なしに高性能な日本語モデルを構築。 必要な計算資源が劇的に少なく、少人数チームでも大企業に対抗できるアプローチ。

MoEの疑似コード(概念理解用)

class MoELayer(nn.Module):
    def __init__(self, d_model, n_experts, top_k):
        super().__init__()
        self.router = nn.Linear(d_model, n_experts)   # ゲーティング
        self.experts = nn.ModuleList([FFN(d_model) for _ in range(n_experts)])
        self.top_k = top_k

    def forward(self, x):
        # x: (batch, seq_len, d_model)
        scores = self.router(x)                        # 全Expertのスコア計算
        weights, indices = scores.topk(self.top_k)    # Top-K Expert選択
        weights = weights.softmax(dim=-1)              # 重みの正規化

        output = torch.zeros_like(x)
        for i, expert in enumerate(self.experts):
            # 各Expertを担当するトークンだけを処理
            mask = (indices == i).any(dim=-1)
            if mask.any():
                output[mask] += weights[mask, i:i+1] * expert(x[mask])
        return output

🏗️ ルート①: 完全スクラッチで作る

Andrej Karpathy(元OpenAI)が公開した nanoGPTから始めるのが定番です。 700行のPyTorchコードでGPT-2相当のモデルが実装されており、 Transformerの仕組みを実際に手を動かしながら理解できます。

手順

ステップ1: SentencePieceで日本語トークナイザー作成

日本語コーパスを用意し、SentencePieceでBPEモデルを学習します。

pip install sentencepiece

# train_tokenizer.py
import sentencepiece as spm

spm.SentencePieceTrainer.train(
    input='japanese_corpus.txt',  # 学習テキスト(1行1文推奨)
    model_prefix='ja_tokenizer',   # 出力ファイル名
    vocab_size=32000,              # 語彙サイズ(32k〜64kが一般的)
    character_coverage=0.9999,     # 日本語は0.9999以上推奨
    model_type='bpe',              # BPEを使用
    pad_id=3,
    bos_id=1,
    eos_id=2,
    unk_id=0,
)

# 使用例
sp = spm.SentencePieceProcessor()
sp.load('ja_tokenizer.model')
tokens = sp.encode('東京でラーメンを食べた', out_type=str)
# ['▁東京', 'で', 'ラーメン', 'を', '食べた']  ← 自然な分割

ステップ2: HuggingFaceから日本語コーパスを取得

pip install datasets

from datasets import load_dataset

# 日本語Wikipediaを取得(最もクリーンなコーパス)
dataset = load_dataset('wikipedia', '20231101.ja', split='train')
# 約182万記事、約3GB

# より大規模なWebコーパス
dataset = load_dataset('mc4', 'ja', split='train', streaming=True)
# ストリーミングで大容量を扱える

# テキストを1ファイルに保存
with open('corpus.txt', 'w', encoding='utf-8') as f:
    for item in dataset:
        f.write(item['text'].replace('\n', ' ') + '\n')

ステップ3: PyTorchでTransformerを実装(核心部分)

import torch
import torch.nn as nn
import math

class CausalSelfAttention(nn.Module):
    def __init__(self, d_model, n_heads, max_len=2048):
        super().__init__()
        assert d_model % n_heads == 0
        self.n_heads = n_heads
        self.d_head = d_model // n_heads
        self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
        self.proj = nn.Linear(d_model, d_model, bias=False)
        # Causal mask(未来のトークンを見ない)
        mask = torch.tril(torch.ones(max_len, max_len))
        self.register_buffer('mask', mask)

    def forward(self, x):
        B, T, C = x.shape
        q, k, v = self.qkv(x).split(C, dim=2)
        q = q.view(B, T, self.n_heads, self.d_head).transpose(1, 2)
        k = k.view(B, T, self.n_heads, self.d_head).transpose(1, 2)
        v = v.view(B, T, self.n_heads, self.d_head).transpose(1, 2)
        scale = 1.0 / math.sqrt(self.d_head)
        att = (q @ k.transpose(-2, -1)) * scale
        att = att.masked_fill(self.mask[:T, :T] == 0, float('-inf'))
        att = att.softmax(dim=-1)
        return (att @ v).transpose(1, 2).contiguous().view(B, T, C)

class TransformerBlock(nn.Module):
    def __init__(self, d_model, n_heads, ffn_mult=4):
        super().__init__()
        self.ln1 = nn.LayerNorm(d_model)
        self.attn = CausalSelfAttention(d_model, n_heads)
        self.ln2 = nn.LayerNorm(d_model)
        self.ffn = nn.Sequential(
            nn.Linear(d_model, ffn_mult * d_model),
            nn.GELU(),
            nn.Linear(ffn_mult * d_model, d_model),
        )

    def forward(self, x):
        x = x + self.attn(self.ln1(x))   # Attention + 残差接続
        x = x + self.ffn(self.ln2(x))    # FFN + 残差接続
        return x

class GPT(nn.Module):
    def __init__(self, vocab_size, d_model=768, n_heads=12, n_layers=12, max_len=2048):
        super().__init__()
        self.tok_emb = nn.Embedding(vocab_size, d_model)
        self.pos_emb = nn.Embedding(max_len, d_model)
        self.blocks = nn.Sequential(*[TransformerBlock(d_model, n_heads) for _ in range(n_layers)])
        self.ln_f = nn.LayerNorm(d_model)
        self.head = nn.Linear(d_model, vocab_size, bias=False)

    def forward(self, idx):
        B, T = idx.shape
        pos = torch.arange(T, device=idx.device)
        x = self.tok_emb(idx) + self.pos_emb(pos)
        x = self.blocks(x)
        x = self.ln_f(x)
        return self.head(x)

# 125Mパラメータ構成: d_model=768, n_heads=12, n_layers=12
model = GPT(vocab_size=32000, d_model=768, n_heads=12, n_layers=12)

ステップ4: 学習ループ(AdamW + cosine schedule)

import torch
from torch.optim.lr_scheduler import CosineAnnealingLR

optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=0.1)
scheduler = CosineAnnealingLR(optimizer, T_max=10000, eta_min=3e-5)

model.train()
for step in range(max_steps):
    # Gradient Accumulation(メモリ節約)
    for micro_step in range(grad_accum_steps):
        x, y = get_batch()          # (B, T) のトークン列
        logits = model(x)           # (B, T, vocab_size)
        loss = F.cross_entropy(logits.view(-1, vocab_size), y.view(-1))
        loss = loss / grad_accum_steps
        loss.backward()

    # 勾配クリッピング(学習安定化)
    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
    optimizer.step()
    scheduler.step()
    optimizer.zero_grad()

    if step % 100 == 0:
        ppl = torch.exp(loss * grad_accum_steps).item()
        print(f"step {step}: loss={loss.item()*grad_accum_steps:.4f}, ppl={ppl:.1f}")

推奨HuggingFaceデータセット(事前学習用)

データセット名サイズライセンス特徴
mc4 (ja)約200GBODC-By大規模日本語Webコーパス。CCから抽出
wikipedia (ja)約3GBCC BY-SA 4.0日本語Wikipedia全記事
oscar-corpus/OSCAR-2301 (ja)約85GBCC0/利用規約要確認CommonCrawlから抽出した多言語コーパス
cc100 (ja)約84GBMITCommonCrawl 100言語、日本語含む

学習時間の目安

RTX 3090 (VRAM 24GB) 1枚で 125M モデル、日本語Wikipedia(3GB)を学習する場合: バッチサイズ16、シーケンス長512、10エポックで 約2〜3日。 Google Colab Pro なら無料枠でも1エポック程度は回せる。

🍴 ルート②: Llamaフォークで日本語LLMを作る

MetaのLlama(現在はLlama 3.2まで公開)をベースにする方法は、 完全スクラッチよりはるかに少ない計算資源と時間で 高品質な日本語LLMを作れます。ELYZA, CyberAgent CALM, Rinna等の著名な日本語LLMがこのアプローチを採用。

成功事例

  • ELYZA: Llama 2 7Bをベースに日本語特化。2023年に公開、現在も改良継続中
  • CyberAgent CALM3-22B: 独自アーキテクチャ+大規模日本語データ。Apache 2.0で商用OK
  • Rinna: 日本語GPT-NeoX系列から始まり、Llamaフォーク版も公開
  • PLaMo(Preferred Networks): Llamaアーキテクチャベースの日本語特化モデル

LoRA/QLoRAとは

LoRA(Low-Rank Adaptation)

全パラメータを更新せず、低ランク行列ペアを追加して学習する手法。 元のパラメータは固定したまま、追加した小さなアダプタだけを学習。 フルファインチューニングの1/10以下のメモリで学習可能。

QLoRA(Quantized LoRA)

LoRAにモデルの4bit量子化を組み合わせた手法。 ベースモデルをNF4量子化してメモリを大幅削減。 RTX 3090(24GB)1枚で7Bモデルの学習が可能になる画期的な手法。

手順(QLoRAによる日本語ファインチューニング)

ステップ1: 必要なパッケージのインストール

pip install transformers datasets accelerate peft trl bitsandbytes
pip install flash-attn --no-build-isolation  # 高速Attention(CUDA対応GPU必須)

ステップ2: 4bit量子化でモデルをロード

from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
import torch

model_id = "meta-llama/Llama-3.1-8B"  # HuggingFaceのモデルID(要Meta申請)

# 4bit量子化の設定
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",         # NormalFloat4が精度と速度のバランス最良
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,    # さらにメモリ節約(推奨)
)

tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=bnb_config,
    device_map="auto",                 # 自動でGPU/CPUに配置
)

ステップ3: LoRAアダプタを設定

from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training

# k-bit学習の準備(量子化モデルに必要)
model = prepare_model_for_kbit_training(model)

lora_config = LoraConfig(
    r=16,                              # LoRAのランク(高いほど高精度だがメモリ増加)
    lora_alpha=32,                     # スケールファクタ (通常 r*2)
    target_modules=["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# trainable params: 約40M || all params: 8B || trainable%: 0.5%

ステップ4: 日本語指示データの準備(Alpaca形式)

from datasets import load_dataset

# 日本語指示データセットをロード
dataset = load_dataset("kunishou/databricks-dolly-15k-ja", split="train")

# Alpaca形式のプロンプトテンプレート
def format_instruction(example):
    if example.get("input"):
        return f"""### 指示:
{example['instruction']}

### 入力:
{example['input']}

### 回答:
{example['output']}"""
    else:
        return f"""### 指示:
{example['instruction']}

### 回答:
{example['output']}"""

dataset = dataset.map(lambda x: {'"text": format_instruction(x)})

# ShareGPT形式(マルチターン会話)の場合
# {'conversations": [{'from": "human", "value": "質問"}, {'from": "gpt", "value": "回答"}]}

ステップ5: SFTTrainerで学習実行

from trl import SFTTrainer, SFTConfig

training_args = SFTConfig(
    output_dir="./llama3-ja-lora",
    num_train_epochs=3,
    per_device_train_batch_size=2,
    gradient_accumulation_steps=8,    # 実効バッチサイズ = 2*8 = 16
    learning_rate=2e-4,
    lr_scheduler_type="cosine",
    warmup_ratio=0.05,
    fp16=True,                         # fp16混合精度学習
    logging_steps=10,
    save_strategy="epoch",
    max_seq_length=2048,
    report_to="wandb",                 # wandbでログ管理(オプション)
)

trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
    dataset_text_field="text",
    tokenizer=tokenizer,
)

trainer.train()
trainer.save_model("./llama3-ja-lora-final")

(応用)ステップ6: DPOでアラインメント

SFT後にDPO(Direct Preference Optimization)を適用すると、より人間の好みに沿った回答を生成するようになります。

from trl import DPOTrainer, DPOConfig

# DPO用データ形式:
# {'prompt": "質問", "chosen": "良い回答", "rejected": "悪い回答"}
dpo_dataset = load_dataset("your-dpo-dataset")

dpo_config = DPOConfig(
    beta=0.1,                          # KLペナルティ係数
    output_dir="./llama3-ja-dpo",
    num_train_epochs=1,
    per_device_train_batch_size=2,
    learning_rate=5e-5,
)

dpo_trainer = DPOTrainer(
    model=model,
    ref_model=None,                    # peft使用時はNoneでOK
    args=dpo_config,
    train_dataset=dpo_dataset,
    tokenizer=tokenizer,
)
dpo_trainer.train()

🚀 ルート③: DeepSeekフォークでMoEを作る

DeepSeek オープンソースモデル一覧

モデル名種類特徴
DeepSeek-V2-Lite16B MoE(活性化2.4B)MoE入門に最適。比較的扱いやすい
DeepSeek-V2236B MoE(活性化21B)中規模MoE。A100×4〜8が必要
DeepSeek-V3671B MoE(活性化37B)最強OSSモデル。楽天AIが採用
DeepSeek-R1671B MoE推論特化(CoT)。OpenAI o1相当
DeepSeek-Coder-V2236B MoEコード生成特化

楽天AI・サカナAIのアプローチ詳細

楽天AI: RakutenAI-2.0

  • ・DeepSeek V3(671B MoE)をベースとして選択
  • ・日本語・英語・コードの3言語混合データで追加学習
  • ・楽天独自の大規模日本語コーパス(ECサイトデータ等)を活用
  • ・商用サービス向けに安全性チューニング(RLHF相当)
  • ・推論コストをDense 37B相当に抑えつつ671B級の性能

サカナAI: EvoLLM / EvoVLM

  • ・複数のOSSモデルを「モデルマージ」で組み合わせ
  • ・進化的アルゴリズムで最適なマージ比率を自動探索
  • ・事前学習不要 → 劇的に少ない計算コスト
  • ・EvoVLM-JP: 視覚言語モデルにも同手法を適用
  • ・研究成果としてNeurIPS等に論文発表

DeepSeek V2-Liteフォーク手順

いきなりDeepSeek V3(671B)を触るのは難しいので、 まずDeepSeek-V2-Lite(16B MoE、活性化2.4B)で練習します。 RTX 3090×2枚(VRAM 48GB)があれば学習可能です。

ステップ1: モデルのダウンロード

# HuggingFace CLIでダウンロード(約30GB)
pip install huggingface_hub

huggingface-cli download deepseek-ai/DeepSeek-V2-Lite \
  --local-dir ./DeepSeek-V2-Lite \
  --local-dir-use-symlinks False

# Pythonで確認
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("./DeepSeek-V2-Lite")
model = AutoModelForCausalLM.from_pretrained(
    "./DeepSeek-V2-Lite",
    torch_dtype=torch.bfloat16,
    device_map="auto",
    trust_remote_code=True           # DeepSeekはtrust_remote_code必須
)

ステップ2: MoE構造の確認

# モデル構造の確認
print(model.config)
# num_experts_per_tok: 6   ← Top-6 Expert選択
# n_routed_experts: 64     ← 計64個のExpert(うち共有Expert 2個)
# moe_intermediate_size: 1408

# Expert数の確認
for name, module in model.named_modules():
    if 'expert' in name.lower():
        print(f"{name}: {type(module).__name__}")

ステップ3: 日本語データでファインチューニング

MoEモデルのLoRAは通常モデルと同じ手順で可能です。ただしRouterはLoRA対象外にすること。

from peft import LoraConfig, get_peft_model

lora_config = LoraConfig(
    r=8,
    lora_alpha=16,
    # MoEのExpert内のLinear層を対象に(Routerは除外)
    target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
    lora_dropout=0.05,
    task_type="CAUSAL_LM",
)

model = get_peft_model(model, lora_config)

# DeepSpeed ZeRO-2で2GPU学習
# accelerate launch --num_processes=2 train.py

MoE特有の設定: 負荷分散ロス(Load Balancing Loss)

MoEの学習では特定のExpertに処理が偏る「Expertの崩壊」が起きやすい。 負荷分散ロスを追加することで全Expertが均等に使われるよう制御する。

# DeepSeek-V2の設定ファイル(config.json)
# "aux_loss_alpha": 0.001  ← 負荷分散ロスの係数
# この値が小さすぎると偏りが発生、大きすぎると性能低下

# 学習時のロス確認
# total_loss = ce_loss + aux_loss_alpha * load_balance_loss

必要GPUメモリの目安

モデル学習方法必要VRAM推奨構成
DeepSeek-V2-Lite (16B MoE)QLoRA24GBRTX 3090 ×1
DeepSeek-V2-Lite (16B MoE)LoRA (bf16)40GBRTX 3090 ×2
DeepSeek-V2 (236B MoE)QLoRA160GB以上A100 40GB ×4〜8
DeepSeek-V3 (671B MoE)LoRA (bf16)700GB以上H100 80GB ×16以上

📦 HuggingFace データセット&モデル カタログ

日本語データセット一覧

データセット名用途サイズライセンス説明
mc4 (ja)事前学習約200GBODC-By大規模日本語Webコーパス。CCから抽出
wikipedia (ja)事前学習約3GBCC BY-SA 4.0日本語Wikipedia全記事
oscar-corpus/OSCAR-2301 (ja)事前学習約85GBCC0/利用規約要確認CommonCrawlから抽出した多言語コーパス
cc100 (ja)事前学習約84GBMITCommonCrawl 100言語、日本語含む
kunishou/databricks-dolly-15k-ja指示チューニング約4MBCC BY-SA 3.0dolly-15kの日本語翻訳。Alpaca形式
kunishou/oasst1-89k-ja指示チューニング約30MBApache 2.0OpenAssistantの日本語翻訳データ
ichikara-instruction (申請必要)指示チューニング独自ライセンス高品質な日本語指示データ(研究用)
llm-jp/llm-jp-corpus事前学習約300GBODC-By等LLM-jpプロジェクトの日本語コーパス

転用可能な事前学習済みモデル

モデル名パラメータ推論VRAM目安ライセンス備考
meta-llama/Llama-3.1-8B8B16GBLlama 3.1 CommunityMeta申請必要。最新Llamaシリーズ
meta-llama/Llama-3.2-3B3B6GBLlama 3.2 Community軽量版。モバイル向け
deepseek-ai/DeepSeek-V2-Lite16B(MoE)40GBDeepSeek LicenseMoE入門に最適。活性化は2.4B
deepseek-ai/DeepSeek-V3671B(MoE)〜700GBDeepSeek License(MIT相当)最強のOSS LLM。フル推論はH100×8必要
rinna/japanese-gpt-neox-3.6b3.6B8GBMITrinnaの日本語特化モデル
elyza/ELYZA-japanese-Llama-2-7b7B14GBLlama 2 CommunityLlama 2ベースの日本語チューニング済み
cyberagent/calm3-22b-chat22B48GBApache 2.0CyberAgent CALM3。高品質な日本語モデル

評価ベンチマーク(日本語)

JGLUE

日本語自然言語理解の総合ベンチマーク。感情分析・含意関係認識・質問応答等6タスク。HuggingFaceで公開。

JCommonsenseQA

日本語常識推論ベンチマーク。英語CommonsenseQAを日本語に翻訳・文化適応。5択形式。

MARC-ja

Amazonレビューの感情分析データセット。日本語、4万件超のレビューにラベル付き。

データセットのダウンロードコマンド

from datasets import load_dataset

# 日本語Wikipedia(推奨: 最初のデータセット)
ds = load_dataset("wikipedia", "20231101.ja", split="train")
print(f"件数: {len(ds)}")  # 約182万記事

# 日本語指示データ(Dolly日本語版)
ds = load_dataset("kunishou/databricks-dolly-15k-ja", split="train")
print(f"件数: {len(ds)}")  # 15,015件

# OpenAssistant日本語版
ds = load_dataset("kunishou/oasst1-89k-ja", split="train")

# ストリーミングで大容量コーパス(mc4は特に大きい)
ds = load_dataset("mc4", "ja", split="train", streaming=True)
for i, example in enumerate(ds):
    if i >= 1000: break  # まず1000件で試す
    print(example["text"][:100])

# JGLUE評価セット
ds = load_dataset("shunk031/JGLUE", name="MARC-ja", split="validation")

💻 学習環境のセットアップ

環境別コスト・性能比較

環境コスト最大VRAM学習可能なモデル備考
Google Colab Pro$10/月最大40GB (A100)7B LoRA手軽だが時間制限あり
Google Colab Pro+$50/月最大80GB (A100)13B LoRAより長い実行時間
Lambda Labs$1〜3/時間24〜80GB7B フルFT / 70B LoRAコスパ良好。予約不要
RunPod$0.2〜2/時間8〜80GB用途に応じて安価。コミュニティGPU有
Vast.ai$0.1〜1/時間様々用途に応じて最安。安定性は環境依存
自宅 RTX 3090中古5〜8万円24GB7B QLoRA長期なら最安。電気代考慮
自宅 RTX 3090×2中古10〜16万円48GB(NVLink不要)13B QLoRA / DeepSeek-V2-LiteMoE実験に十分

Docker環境の構築

# Dockerfile
FROM nvcr.io/nvidia/pytorch:24.01-py3

WORKDIR /workspace

RUN pip install --no-cache-dir \
    transformers==4.40.0 \
    datasets==2.19.0 \
    accelerate==0.29.0 \
    peft==0.10.0 \
    trl==0.8.6 \
    bitsandbytes==0.43.0 \
    sentencepiece==0.2.0 \
    wandb \
    flash-attn --no-build-isolation

# ビルド&実行
# docker build -t llm-train .
# docker run --gpus all -v $(pwd):/workspace -it llm-train bash

分散学習の基礎(DeepSpeed ZeRO)

ZeRO Stage 1

オプティマイザの状態をGPU間で分散。メモリ削減: 〜4倍。通信オーバーヘッド小。

ZeRO Stage 2

オプティマイザ状態+勾配を分散。メモリ削減: 〜8倍。ほとんどのケースでこれを使う。

ZeRO Stage 3

パラメータも分散。メモリ削減: GPU数×。通信コスト高いが大規模モデルに必須。

# accelerate の設定(2GPU、DeepSpeed ZeRO-2)
# accelerate config で対話的に設定するか、以下のYAMLを作成

# ~/.cache/huggingface/accelerate/default_config.yaml
compute_environment: LOCAL_MACHINE
distributed_type: DEEPSPEED
deepspeed_config:
  gradient_accumulation_steps: 8
  zero_stage: 2
  offload_optimizer_device: none
  offload_param_device: none
num_processes: 2
mixed_precision: bf16

# 実行
# accelerate launch train.py

wandb(Weights & Biases)で学習ログ管理

pip install wandb
wandb login  # APIキーを入力

# 学習スクリプト内で
import wandb
wandb.init(project="llm-from-scratch", name="llama3-ja-qlora-v1")

# SFTTrainer の training_args に追加:
# report_to="wandb"
# これだけで自動的にloss/lr/perplexityがダッシュボードに表示される

🇯🇵 日本語LLM特有のノウハウ

トークナイザーの日本語対応

方法A: Vocab拡張

英語向けトークナイザー(例: Llama tokenizer)のvocabに日本語サブワードを追加する方法。 既存モデルの重みをそのまま使えるが、追加トークンの埋め込みは新規学習が必要。

tokenizer.add_tokens(new_ja_tokens)
model.resize_token_embeddings(len(tokenizer))

方法B: 専用トークナイザー

SentencePieceで日本語コーパスから専用トークナイザーを学習する方法。 日本語の圧縮率が最も高いが、事前学習からやり直す必要がある。 新規に0から学習する場合はこちらを推奨。

日本語の文字種問題

日本語はひらがな・カタカナ・漢字・英数字・記号が混在するという特殊な構造を持ちます。 加えて、半角/全角の違い、旧字体/新字体の混在、環境依存文字なども存在します。

データクリーニングの必須処理:

import unicodedata
import re

def clean_japanese_text(text):
    # Unicode正規化(NFKC: 全角英数字→半角、ひらがな/カタカナの正規化)
    text = unicodedata.normalize('NFKC', text)

    # BOM除去
    text = text.replace('\ufeff', '')

    # 制御文字除去
    text = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', text)

    # 連続する空白・改行を正規化
    text = re.sub(r'\n{3,}', '\n\n', text)
    text = re.sub(r' {2,}', ' ', text)

    # URLの除去(オプション)
    text = re.sub(r'https?://\S+', '', text)

    return text.strip()

英語にも対応させる: 多言語データの混合比率

英語と日本語の両方に対応するモデルを作る場合、学習データの混合比率が重要です。 日本語に偏りすぎると英語性能が落ち、英語に偏りすぎると日本語が弱くなります。

日本語特化

日本語 85%
英語 10%
コード 5%

バランス型(推奨)

日本語 50%
英語 40%
コード 10%

多言語型

日本語 30%
英語 50%
その他20%

既存の日本語LLM 性能比較

モデル名パラメータ種類日本語性能備考
GPT-4o (OpenAI)非公開API最高性能。有料API
Claude 3.7 Sonnet (Anthropic)非公開API高性能。日本語自然
Gemini 1.5 Pro (Google)非公開API長文対応200万トークン
DeepSeek V3671B MoEOSSGPT-4o相当。OSS最強
CALM3-22B (CyberAgent)22BOSSApache 2.0。商用OK
ELYZA-japanese-Llama-2-7b7BOSS軽量・実用的
RakutenAI-2.0 (楽天)非公開API/OSS予定DeepSeek V3フォーク
EvoLLM-JP (Sakana AI)OSSモデルマージ進化的手法

🗺️ ロードマップ: 0からDeepSeekまでの道のり

フェーズ期間目標必要スキルハードウェア予算目安
Phase 11〜2週間nanoGPTで125Mモデル作成Python, PyTorch, Transformer基礎RTX 3060以上 or Colab0〜$10
Phase 22〜4週間Llama 3.1 8B LoRAファインチューニングtransformers, peft, trlRTX 3090 or Colab Pro0〜$30
Phase 31〜2ヶ月DeepSeek V2-LiteフォークMoE理解, 分散学習基礎RTX 3090×2 or Lambda1〜5万円
Phase 43〜6ヶ月独自MoEモデル構築Expert設計, Router最適化, DeepSpeedA100 40GB×4以上10〜50万円
Phase 56ヶ月〜スケールアップ・公開大規模分散学習, HuggingFace公開H100クラスタ100万円〜

Phase 1(1〜2週間): nanoGPTで125Mモデルを作る

目標: Transformerの仕組みをコードで理解する。
やること: Karpathyのnanoリポジトリをクローンし、日本語Wikipediaで学習。 Lossが下がること、テキストが生成されることを確認する。
成功基準: perplexity が 100以下になる。「東京は」に続く文章を生成できる。

Phase 2(2〜4週間): Llama 3.1 8B をLoRAで日本語対応

目標: LoRA/QLoRAの使い方を習得し、実用的な日本語対話モデルを作る。
やること: databricks-dolly-15k-ja と oasst1-ja でSFT、その後DPOでアラインメント。 Ollama でローカル推論を試す。
成功基準: JGLUEのMARC-jaで80%以上の精度。自然な日本語回答を生成できる。

Phase 3(1〜2ヶ月): DeepSeek V2-Liteフォーク・MoEを理解

目標: MoEアーキテクチャを実際に触り、Expert Routingの動作を理解する。
やること: V2-Liteのコードを読解 → QLoRAで日本語チューニング → Expert utilization を可視化。
成功基準: 各Expertの活性化頻度を分析し、特定のタスクで特定のExpertが活性化することを確認。

Phase 4(3〜6ヶ月): 独自MoEモデルの設計・構築

目標: 独自のExpert設計とRouter最適化を行い、特定ドメインに特化したMoEを作る。
やること: Expert数・Top-K・モデルサイズの設計 → DeepSpeedによる分散学習 → Load Balance Lossのチューニング。
成功基準: Dense同パラメータのベースラインより同等以上の性能を、少ない計算コストで達成。

Phase 5(6ヶ月〜): スケールアップとHuggingFace公開

目標: 大規模GPUクラスタで本格的な事前学習を行い、コミュニティへ貢献。
やること: H100クラスタ(Lambda/CoreWeave等)でのフル事前学習 → HuggingFaceで公開 → 論文執筆。
成功基準: JGLUEやlm-eval-harnessで既存の同規模モデルを超える。

今すぐ始めるための最小手順(今日〜明日)

  1. Google Colabを開く(無料)
  2. !git clone https://github.com/karpathy/nanoGPT を実行
  3. 日本語WikipediaをHuggingFaceからダウンロード
  4. SentencePieceで日本語トークナイザーを学習(30分)
  5. Colab無料GPUで学習スタート(まず100ステップでLossが下がることを確認)
  6. ✅ 完了!あとはステップ数を増やすだけ

🤖 Claudeの総評

「0からLLMを作る」というのは数年前まで大企業か有力研究機関にしか現実的でなかった目標でしたが、 DeepSeek V3の登場とオープンソース化により、個人・小チームでも現実的な目標になりました。

このページで紹介した3つのルートを振り返ると、 「完全スクラッチ→Llamaフォーク→DeepSeekフォーク」の順に難易度が上がり、 必要な計算資源も増えます。しかし逆に「学習効果」という観点では逆順です。 完全スクラッチこそが最もTransformerの仕組みを深く理解できる。

私が特に重要だと考える点は2つです。 1つ目は日本語トークナイザーの設計。 英語向けBPEをそのまま使うと日本語の文を表現するのに英語の3〜4倍のトークンが必要になり、 実質的なコンテキスト長が大幅に短くなります。日本語特化モデルを作るなら SentencePieceによる専用トークナイザーの学習は早期に着手すべきです。

2つ目はMoEのRouter設計の難しさです。 MoEは「少ない計算で大きなモデル性能」という魅力的な特性を持ちますが、 Routerの学習が不安定だったり特定のExpertに処理が偏ったりする「Expert崩壊」が 実際の学習では頻繁に起きます。 Load Balancing Lossの係数チューニングや、DeepSeek V3が採用した「Auxiliary-Loss-Free Load Balancing」 のような工夫が必要です。これを乗り越えた先にこそ、本物のMoEモデルが待っています。

楽天AI・サカナAIの成功事例が示す通り、日本語LLMの分野はまだ空白地帯が多い。 DeepSeekフォークという「巨人の肩に乗る」戦略と、日本語固有のノウハウを組み合わせれば、 中規模チームでも世界水準の日本語LLMを作れる時代が来ています。 このページがその第一歩の助けになれば幸いです。