evex-1
ある Discord サーバーの過去ログ だけ をゼロから学習した 5.87M パラメータの言語モデル。 既存モデルからの継続学習ではなく、tokenizer も含めて全部そのログから作った。 このモデルにとって世界はそのログだけで、一般知識は一切持っていない。
使い方
transformers の Auto クラスには当てはまらない構成なので、同梱の modeling_evex.py を使う。
pip install torch safetensors sentencepiece
取り方は2つ。
# huggingface_hub を使う
pip install huggingface_hub
hf download tako080614/evex-1 --local-dir evex-1
# git clone でも取れる。ただし git-lfs が必要
git lfs install
git clone https://huggingface.co/tako080614/evex-1
git-lfs を入れずに clone すると使えない。 model.safetensors が 133 バイトの
ポインタファイルになり (tok.model も同じ)、読み込もうとしても壊れる。
ls -la して 22MB あるか確かめるのが早い。
import json, sys, torch, sentencepiece as spm
from safetensors.torch import load_file
sys.path.insert(0, "evex-1")
from modeling_evex import Config, MicroLM
cfg_json = json.load(open("evex-1/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-1/model.safetensors")
state["head.weight"] = state["embed.weight"] # weight tying を結び直す
model.load_state_dict(state)
model.eval()
sp = spm.SentencePieceProcessor(model_file="evex-1/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 はストレージを共有したテンソルを保存できないので落としてある。上のように結び直す。
プロンプトの形
学習データと同じ直列化でないと、モデルは一度も見ていない形を受け取って崩れる。
<|conv|><|s3|>今日ひま?<|s7|><|re|>ひま<|end|>
| トークン | 意味 |
|---|---|
| `< | conv |
| `< | end |
| `< | s0 |
| `< | other |
| `< | re |
<nl> |
発言内の改行 |
<url> <mention> <channel> <time> <file> |
正規化した URL / メンション / チャンネル / 時刻 / 添付 |
<code> </code> |
コードブロック |
末尾に話者トークンを置くと、その話者として続きを書く。speakers.json に各話者の
発言数と表示名が入っている。
Discord の user ID は入れていない (モデルの動作に要らず、実アカウントへの手がかりになる)。
絵文字・草・www・顔文字は正規化せず残してあるので、そのまま出る。
数字
| パラメータ | 5,868,800 |
| 学習トークン | 6,685,152 (train 6.50M / val 0.19M) |
| 語彙 | 4,096 (SentencePiece BPE / byte fallback) |
| context | 512 |
| 構成 | decoder-only / 6 層 / d_model 256 / 4 head / d_ff 704 |
| RoPE + RMSNorm + SwiGLU + weight tying | |
| 学習 | 10 epoch / AdamW / cosine / CPU のみで 194 分 |
| train / val loss | 3.8685 / 4.2404 |
学習データは 585,628 メッセージ (bot を除外後) を会話単位に切った 54,934 会話。 1トークンあたり 2.40 文字。
データが足りていない。 Chinchilla 最適 (20 トークン/パラメータ) はこの規模だと 33万パラメータで、5.87M は最適の 6%。それでも val loss は 10 epoch 通して単調に 下がり続けたので (5.17 → 4.24)、まだ学習の余地が残っている。
できること / できないこと
できる: チャットの口調、短い応答、ネットスラング、そのサーバー特有の語彙と話題、 話者ごとの癖 (ある話者は「〜にゃい」と書くが、それを再現する)
できない: 一般常識、推論、数学、コード生成、長い整合性、知らない話題への応答
出力例 (epoch 10):
> これバグってる?
これでいいです
> Cloudflare Containers ってどうなん
AGPLだったら
CTFが終わってる
<file> や <url> ばかり返るとき
<file> (添付だけの発言) と <url> は学習データで非常に頻出なので、5.87M では
そちらに落ちることが普通にある。同じプロンプトでも乱数の引きで変わる。
seed 1000 -> これでいいです
seed 1001 -> <url>
seed 1002 -> おしえて / おk / <url>
seed 7 -> <file><file><file>
generate に外す機能が入っている。実測で使えない返答が 38% から 12% に下がる。
ban = [sp.piece_to_id(t) for t in ("<file>", "<url>", "<mention>", "<channel>", "<time>")]
out = model.generate(ids, max_new_tokens=60, temperature=0.9, top_k=40,
stop_id=end_id, ban_ids=ban, min_new_tokens=2)
min_new_tokens はそれまで <|end|> を出させない (何も言わずに終わるのを防ぐ)。
残る 12% は「これ」のような 2 文字なので、短すぎたら引き直すとよい。
出どころと制限
学習データは同意を明示的に取っていない実在の人物の会話。 逐語での再生は 20 文字以上の完全一致で 0 箇所だったが (10 epoch すべてで確認)、 669万トークンを10周しているので実際の発言に近いものが出る可能性は残る。
- 生成物を事実として扱ってはいけない
- 特定の人物の発言として扱ってはいけない
- 話者トークンと表示名の対応は
speakers.jsonで公開している。Discord の user ID は公開していない - 学習に使ったログそのものは公開していない
- 表示名で名指しできる形になっているので、特定の人が書いたものとして引用してはいけない
- Downloads last month
- -