Skip to main content

Minimal decoder-only LLM experiments trained from scratch with PyTorch.

Project description

original_LLM

PyTorchのみで、外部の事前学習モデルやFTに頼らず、最小の独自LLMをゼロから作るための実験リポジトリです。

これは知的な汎用AIや高性能アシスタントを作る試みではありません。太宰っぽいキャラと口調を持ち、友達感覚で気軽にしゃべれる小さなLLMを目指します。

現時点では性能を追いません。まずは「自分で実装した学習器が動き、太宰っぽい返答を少しでも返せる」状態を最優先にします。

Goal

  • PyTorchだけで学習と推論を実装する
  • 外部の事前学習モデルは使わない
  • Fine-tuningではなく、ゼロから学習する
  • ローカル環境で完結する
  • まずは小さくても動くものを作る
  • 太宰っぽいキャラで、友達感覚の会話が少しずつ成立するところまで持っていく

Non-Goal

  • 知的な汎用AIや有能なアシスタントを作ること
  • 高性能な日本語LLMを作ること
  • 商用レベルの品質を狙うこと
  • 大規模分散学習
  • 重い学習基盤の導入
  • 複雑な最適化を最初から盛り込むこと

Current Assumptions

  • 開発マシン: Mac mini M4
  • メモリ: 32GB
  • フレームワーク: PyTorch
  • まずはMPSで動かす
  • Python と依存管理は uv を使う

v0 Plan

最初のバージョンでは、以下のような最小構成を目指します。

  1. char-level tokenizer
  2. decoder-only Transformer
  3. next-token predictionで学習
  4. checkpoint保存
  5. 文章生成スクリプト

Why Char-Level

byte-level は実装が単純だが、日本語では UTF-8 の断片を追うことになるため、自然な文章生成がかなり弱くなりやすい。

このリポジトリでは、まず corpus から登場文字を集めて、1文字を1トークンとして扱う。

利点:

  • 日本語の自然さを出しやすい
  • tokenizer 実装がまだ単純
  • 外部 tokenizer に依存しない

欠点:

  • 語彙サイズが corpus 依存で増える
  • 未知文字対応が必要
  • byte-level より実装は少し増える

First Model Target

まずは以下のような小型モデルから始めます。

  • n_layer=4
  • d_model=256
  • n_head=4
  • ffn_hidden=1024
  • context_length=256
  • vocab_size は corpus から動的に決まる

この段階では、性能よりも以下を重視します。

  • 学習が最後まで走る
  • lossが下がる
  • 生成が完全なランダム文字列ではなくなる

Planned Layout

original_LLM/
  README.md
  claude.md
  agent.md
  experiments.md
  data_policy.md
  scripts/
    download_aozora_dazai.py
  src/
    config.py
    data.py
    generate.py
    model.py
    train.py
    utils.py
  data/
    raw/
  checkpoints/
  logs/
  samples/

Milestones

Milestone 1

  • 学習用テキストを1ファイル読み込める
  • 文字列を token 列に変換できる
  • ミニバッチを作れる
  • 青空文庫の太宰作品を取得できる

Milestone 2

  • Transformer本体を実装する
  • 学習ループを回す
  • 小さなデータで過学習を確認する

Milestone 3

  • checkpoint save/load
  • 推論スクリプト
  • temperature / top-k sampling
  • train.pyrun_name ごとの checkpoint / sample / log を分離

Milestone 4

  • 複数テキスト対応
  • validation loss
  • 学習再開

Development Principles

  • まずは小さく作る
  • 動くものを優先する
  • 余計な抽象化は後回しにする
  • 1ファイル過学習を最初の成功条件にする
  • 依存関係は増やしすぎない

Experiment Log

試した設定と結果は experiments.md に残す。

最低限、以下を記録する。

  • 何を変えたか
  • best / last の数値
  • 生成の使用感
  • 次に何を試すか

Environment Setup

このリポジトリは uv で Python バージョンと依存関係を管理する。

初回セットアップ:

uv sync

コマンド実行例:

uv run python scripts/download_aozora_dazai.py --limit 5
uv run python src/data.py --limit 2
uv run python src/model.py --context-length 256

Python バージョンは .python-versionpyproject.toml で管理する。 依存更新時は uv add または uv remove を使い、uv lock を更新する。

Training Operations

このリポジトリの学習運用は、最初から以下を前提にする。

  • caffeinate を付けてスリープで学習を止めない
  • tmux 上で学習を実行し、切断や端末終了に備える
  • checkpoint を定期保存し、resume で再開できるようにする
  • 複数実験を並行で回せるように、run ごとに出力先を分離する

想定コマンド例:

caffeinate -dimsu tmux new -s llm-train

tmux の中で学習:

uv run python src/train.py --run-name dazai-debug

再開:

uv run python src/train.py --run-name dazai-debug --resume checkpoints/dazai-debug/latest.pt

並行実験:

uv run python src/train.py --run-name dazai-a --limit 8
uv run python src/train.py --run-name dazai-b --limit 32

並行で回す場合は、checkpoint、sample、log の出力先を run_name 単位で必ず分ける。 同一ファイルへ複数プロセスが書く構成は避ける。

M4 Mac mini 32GB では、重い MPS 学習を同時に複数本走らせると競合しやすい。 そのため、実運用では「重い本学習を1本 + 軽い検証を1本」程度から始める。

現在の train.py は以下を持つ。

  • --run-name
  • --resume
  • --save-every
  • --sample-every
  • --eval-every
  • checkpoints/<run_name>/
  • samples/<run_name>/
  • logs/<run_name>/

Debug Generation

学習済み checkpoint を手で試すには generate.py を使う。

単発生成:

uv run python src/generate.py --checkpoint checkpoints/dazai-long/best.pt --prompt "私は"

対話モード:

uv run python src/generate.py --checkpoint checkpoints/dazai-long/best.pt --interactive

私: / 相手: 形式の会話モデルを対話モードで使う例:

uv run python src/generate.py \
  --checkpoint checkpoints/dazai-friend-simple/best.pt \
  --interactive \
  --user-label  \
  --reply-label 相手

開発者向けの会話 CLI 入口:

uv run dazai-chat

使い方はまずこれで確認できる:

uv run dazai-chat --help

これは現在の会話向け checkpoint を自動で拾い、次を既定で有効にする。

  • --interactive
  • --carry-context
  • --max-history-turns 1
  • --user-label 私
  • --reply-label 相手
  • --retrieval-examples 0
  • --normalize-chat-input
  • --temperature 0.2
  • --top-k 8
  • --repetition-penalty 1.1
  • --max-new-tokens 48

interactive では既定で 相手: の返答だけを表示する。 会話 prompt / 生の出力を見たいときだけ --show-prompt-output を付ける。

dazai-chat は会話の質を少し安定させるために、既定で次も行う。

  • 会話履歴は全保持ではなく直近 1 ターンだけを持つ
  • 近い 私: / 相手: 例を 1 件だけ prompt の前に差す
    • 既定では、手書きの data/chat_seed_simple/ があればそれを優先して使う
    • 短い雑談や口語を増やしたい時は、このディレクトリに .txt を足すと retrieval の候補に入る
  • 近い短文 seed がそのまま見つかった時は、その返答を優先して返す
  • 短い入力では、前の一手より現在の入力を優先して解釈する
  • どゆこと? のような短い口語入力を軽く正規化してから対話する

調整したい時の例:

uv run dazai-chat --max-history-turns 3
uv run dazai-chat --retrieval-examples 0
uv run dazai-chat --no-normalize-chat-input

疑似的に文脈を持たせたい場合:

uv run python src/generate.py --checkpoint checkpoints/dazai-long/best.pt --interactive --carry-context

現在の generate.py は、デバッグ用途を優先して以下を既定で持つ。

  • max_new_tokens=64
  • , , , または空行での簡易 stop
  • interactive 時の prompt / output 表示
  • 必要なら --repetition-penalty 1.1 のような軽い繰り返し抑制

短めに試す例:

uv run python src/generate.py --checkpoint checkpoints/dazai-char/best.pt --interactive --max-new-tokens 40

Data Acquisition

学習データは当面、青空文庫の太宰治作品を使います。

取得スクリプト:

uv run python scripts/download_aozora_dazai.py --limit 5

全件取得:

uv run python scripts/download_aozora_dazai.py

出力先:

  • data/raw/aozora/dazai/zips/
  • data/raw/aozora/dazai/txt/
  • data/raw/aozora/dazai/manifest.jsonl

最初の実験では --limit 付きで数作品だけ取得し、導線が固まったら全件に切り替える。

太宰に近い作家をまとめて取得する mixed corpus 用スクリプト:

uv run python scripts/download_aozora_authors.py

既定の対象:

  • 太宰治
  • 芥川龍之介
  • 坂口安吾
  • 織田作之助
  • 梶井基次郎
  • 中島敦

出力先:

  • data/raw/aozora/dazai_peers/zips/
  • data/raw/aozora/dazai_peers/txt/
  • data/raw/aozora/dazai_peers/manifest.jsonl
  • data/raw/aozora/dazai_peers/summary.json

既存チェックポイントのトークナイザで未知文字率を確認する例:

uv run python scripts/check_tokenizer_coverage.py \
  --checkpoint checkpoints/dazai-char-512x8/best.pt \
  --data-dir data/raw/aozora/dazai_peers/txt \
  --manifest-path data/raw/aozora/dazai_peers/manifest.jsonl

Dialogue Candidate Extraction

青空文庫の太宰テキストから、「...」 に包まれた発話候補を JSONL に抜き出すスクリプトを用意している。

これはそのまま学習に入れるためではなく、後段で AI エージェントに

  • 会話ペアかどうかの判定
  • 酒 / 文学 / 官能 / 日常などの分類
  • 私: / 相手: 形式への軽整形

をさせるための中間データである。

実行例:

uv run python scripts/extract_aozora_dialogue_candidates.py

まず一部だけ試す例:

uv run python scripts/extract_aozora_dialogue_candidates.py --limit 5

主な出力先:

  • data/intermediate/aozora/dazai/dialogue_candidates.jsonl
  • data/intermediate/aozora/dazai/dialogue_summary.json

各候補には、作品ID、タイトル、発話本文、前後文脈、前後の鉤括弧発話、近接する発話クラスタIDを入れている。

AI に渡しやすいように、高スコア候補だけを score 順でバッチ化する例:

uv run python scripts/prepare_dialogue_labeling_batches.py --min-heuristic-score 4 --batch-size 200

出力先:

  • data/intermediate/aozora/dazai/dialogue_batches/

分類ルールは dialogue_labeling.md にまとめている。

ラベル済み batch の検証:

uv run python scripts/validate_dialogue_labels.py

ラベル済み batch から自動 seed を書き出す例:

uv run python scripts/build_chat_seed_from_labels.py

first-pass の自動ラベルを作る例:

uv run python scripts/bootstrap_dialogue_labels.py --batches 0001 0002

Status

データ収集導線の実装を開始。学習系の実装は caffeinate + tmux + resume + run_name 分離 を前提に進める。

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

original_llm-0.2.2.tar.gz (138.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

original_llm-0.2.2-py3-none-any.whl (46.4 kB view details)

Uploaded Python 3

File details

Details for the file original_llm-0.2.2.tar.gz.

File metadata

  • Download URL: original_llm-0.2.2.tar.gz
  • Upload date:
  • Size: 138.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for original_llm-0.2.2.tar.gz
Algorithm Hash digest
SHA256 8e5e4f57d650f03be0f94a942dbed51ffbdb45c5047fd37c4524388f1aa7b67d
MD5 bdd0638fa28432760809a96b459e9b20
BLAKE2b-256 901e2e335e95764deb533baad53b9601310e400b68df2c56989e58cb16453e47

See more details on using hashes here.

File details

Details for the file original_llm-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: original_llm-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 46.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for original_llm-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7295d433f9ed5706740706d56697a6b84af30ffbf70a08f41d468600bb551659
MD5 f41ef2ba082be80e15e8d056e5bb6193
BLAKE2b-256 424f82a4ed4e18d14c7cc4a13626f087e664c0d4e2c10dd84c8cd1617e0b9e4f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page