evex-2

ある Discord サーバーの過去ログ だけ をゼロから学習した 5.87M パラメータの言語モデル。 既存モデルからの継続学習ではなく、tokenizer も含めて全部そのログから作った。 このモデルにとって世界はそのログだけで、一般知識は一切持っていない。

evex-1 の第2世代。コーパスも語彙も構成も 同じで、変えたのは2つだけ。

evex-1 から変えたこと

1. 学習率 3e-4 → 1e-3

evex-1 は 10 epoch 通して val loss が単調に下がり続けていた = 収束していなかった。 上げて 12 epoch 回したら val 4.2404 → 4.0384 (同じ尺度で測った値)。

2. 正規化記号を損失から外した

evex-1 の一番大きい問題は、返答の 38.5% が <url><file> だけだったこと。 正規化で作った記号なので発言ではなく、bot は画像もリンクも実際には出せない。

原因は密度ではなく1トークンであること。添付だけの発言 (コーパスの 3.6%) が <file> 1個に潰れていたので、発言の先頭で確率が1点に集中していた。

そこで <file> <url> <mention> <channel> <time>損失から外して学習した。 文脈からは消していない — 「誰かが画像を貼って、他の人が反応する」流れは学ばせたいので、 入力には残したまま、その位置の予測だけ評価しない。

<nl><code> は外していない。改行もコードブロックもモデルが書いて良いもの。

効果

同じチェックポイント同士を同じ尺度で測り直した結果 (12 epoch / 96 生成 / プロンプトと乱数を固定):

素 val マスク val 記号だけの返答
マスクなし 4.0384 4.0465 38.5%
マスクあり (これ) 4.0970 4.0330 0.0%
  • 素 val<url> <file> も予測対象に含める尺度
  • マスク val … 記号を損失から外す尺度 = 実際に読まれる語だけの尺度

素の尺度で 0.059 負けているのは、その尺度が <url> を当てることを点数にしている から。出してほしくないトークンなので、そこで負けるのは払って良いコスト。実際に読まれる 語だけで測れば勝っていて、記号だけの返答は消えた。

推論時に記号を禁止する必要がなくなった。 evex-1 では ban_ids で潰して 38% → 12% に抑えるしかなかったが、あれは高確率のトークンを削って再正規化するので出てくる第二候補が 歪む。損失から外せば確率の質量が最初から実際の語に乗る。

evex-1 のカードに載せた seed をそのまま引き直すと、記号だけだった3本が全部発言になる (プロンプトは <|conv|><|s3|>これバグってる?<|other|>)。

seed evex-1 evex-2
1000 これでいいです それでも、自分のこと言えば、それは正しいかも
1001 <url> なんかあれはcodexがバグってる
1002 おしえて / おk / <url> おしえて / おk / お疲れ様です
7 <file><file><file> いやー / あーそれはそっか

中身が正しくなったわけではない。 5.87M なので固有名詞は形だけ真似て、長く出させれば 崩れる。消えたのは「発言ですらないもの」だけ。

使い方

transformers の Auto クラスには当てはまらない構成なので、同梱の modeling_evex.py を使う。 tokenizer は evex-1 と同一 (tok.model の md5 が一致) なので、既に evex-1 を持って いるならそちらを使い回せる。

pip install torch safetensors sentencepiece huggingface_hub
hf download tako080614/evex-2 --local-dir evex-2

git clone で取るなら git-lfs が必要。入れずに clone すると model.safetensors が 133 バイトのポインタになり、読み込もうとしても壊れる。ls -la して 22MB あるか確かめる。

import json, sys, torch, sentencepiece as spm
from safetensors.torch import load_file

sys.path.insert(0, "evex-2")
from modeling_evex import Config, MicroLM

cfg_json = json.load(open("evex-2/config.json"))
cfg = Config(
    vocab_size=cfg_json["vocab_size"], n_layers=cfg_json["n_layers"],
    d_model=cfg_json["d_model"], n_heads=cfg_json["n_heads"],
    context=cfg_json["context"], dropout=0.0, attn_dropout=0.0,
)

model = MicroLM(cfg)
state = load_file("evex-2/model.safetensors")
state["head.weight"] = state["embed.weight"]      # weight tying を結び直す
model.load_state_dict(state)
model.eval()

sp = spm.SentencePieceProcessor(model_file="evex-2/tok.model")
end_id = sp.piece_to_id("<|end|>")

prompt = "<|conv|><|s3|>これバグってる?<|other|>"
ids = torch.tensor([sp.encode(prompt, out_type=int)])
out = model.generate(ids, max_new_tokens=60, temperature=0.9, top_k=40, stop_id=end_id)
print(sp.decode(out[0].tolist()))

head.weight は入っていない。weight tying で embed.weight と同じテンソルを指しており、 safetensors はストレージを共有したテンソルを保存できないので落としてある。上のように結び直す。

ban_ids は要らない。渡しても害は無いが、記号だけの返答は既に出ない。

プロンプトの形

学習データと同じ直列化でないと、モデルは一度も見ていない形を受け取って崩れる。

<|conv|><|s3|>今日ひま?<|s7|><|re|>ひま<|end|>
トークン 意味
<|conv|> 会話の開始
<|end|> 会話の終了
<|s0|><|s47|> 話者。発言数の多い上位48人 (人間の発言の 85.3% を被覆)
<|other|> それ以外の 2,599 人
<|re|> 直前の誰かへの返信
<nl> 発言内の改行
<url> <mention> <channel> <time> <file> 正規化した URL / メンション / チャンネル / 時刻 / 添付。このモデルはこれらを出さない
<code> </code> コードブロック

末尾に話者トークンを置くと、その話者として続きを書く。speakers.json に各話者の 発言数と表示名が入っている。 Discord の user ID は入れていない (モデルの動作に要らず、実アカウントへの手がかりになる)。

同じ話者トークンが続けて出ることがある。 学習データでは話者の塊のうち 27.4% が 2連続以上なので、モデルもそう書く。1発言だけ欲しいなら最初の話者トークンで切る。

絵文字・www・顔文字は正規化せず残してあるので、そのまま出る。

数字

evex-2 evex-1
パラメータ 5,868,800 5,868,800
学習トークン 6,685,152 6,685,152
語彙 4,096 (SentencePiece BPE / byte fallback) 同じ
context 512 512
構成 decoder-only / 6 層 / d_model 256 / 4 head / d_ff 704
RoPE + RMSNorm + SwiGLU + weight tying
同じ
学習 12 epoch / lr 1e-3 / AdamW / cosine / CPU のみ 10 epoch / lr 3e-4
損失マスク <file> <url> <mention> <channel> <time> なし
train / val loss 3.5535 / 4.0559 (マスク尺度) 3.8685 / 4.2404 (素)
記号だけの返答 0.0% 38.5%

データは足りていない。 Chinchilla 最適 (20 トークン/パラメータ) はこの規模だと 33万パラメータで、5.87M は最適の 6%。val は 12 epoch でもまだ下がり続けているので、 学習の余地は残っている。

できること / できないこと

できる: チャットの口調、短い応答、ネットスラング、そのサーバー特有の語彙と話題、 話者ごとの癖 (ある話者は「〜にゃい」と書くが、それを再現する)

できない: 一般常識、推論、数学、コード生成、長い整合性、知らない話題への応答

固有名詞は形だけ真似て中身が合わない。Cloudflare Codex AGP Core MT6589 のように、 見たことのある語を組み替えたものが出る。事実として読むものではない。

出どころと制限

学習データは同意を明示的に取っていない実在の人物の会話。 逐語での再生は 20 文字以上の完全一致で 0 箇所だったが、669万トークンを12周している ので実際の発言に近いものが出る可能性は残る

  • 生成物を事実として扱ってはいけない
  • 特定の人物の発言として扱ってはいけない
  • 話者トークンと表示名の対応は speakers.json で公開している。Discord の user ID は公開していない
  • 学習に使ったログそのものは公開していない
  • 表示名で名指しできる形になっているので、特定の人が書いたものとして引用してはいけない
Downloads last month
380
Safetensors
Model size
5.87M params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support