ZeroPoint v0 research prototype: new from-scratch NumPy framework, memory-slot architecture, audited Transformer comparison
5c6c847 verified Download experiments/common.py from Holmes2011/ZeroPoint: direct link, hf CLI and curl.
- Browser
- Download file 4.43 kB
-
https://huggingface.co/Holmes2011/ZeroPoint/resolve/main/experiments/common.py
- Command line
-
hf download hf://Holmes2011/ZeroPoint/experiments/common.py
-
curl -L -o common.py https://huggingface.co/Holmes2011/ZeroPoint/resolve/main/experiments/common.py
4.43 kB
| """各个实验脚本共用的部分:语料、批次、训练循环、计算量统计、结果存档。""" | |
| import datetime | |
| import glob | |
| import os | |
| import sys | |
| import sysconfig | |
| import time | |
| import numpy as np | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| sys.path.insert(0, os.path.join(HERE, "..")) | |
| import zerocg as zc # noqa: E402 | |
| from models import ZeroX # noqa: E402 | |
| from zerocg import functional as F # noqa: E402 | |
| ITERS = int(os.environ.get("ITERS", 2000)) | |
| T, B = 64, 32 | |
| CFG = dict(n_embd=64, n_head=4, n_layer=2) | |
| def load_corpus(path=None): | |
| """默认语料:Python 标准库源码(纯 ASCII 的 .py 文件,约 150 万字符)。""" | |
| if path: | |
| return open(path, encoding="utf-8").read() | |
| parts, total = [], 0 | |
| for p in sorted(glob.glob(os.path.join(sysconfig.get_paths()["stdlib"], "*.py"))): | |
| try: | |
| s = open(p, encoding="utf-8").read() | |
| except (UnicodeDecodeError, OSError): | |
| continue | |
| if s.isascii(): | |
| parts.append(s) | |
| total += len(s) | |
| if total > 1_500_000: | |
| break | |
| return "\n".join(parts) | |
| def prepare_data(path=None): | |
| text = load_corpus(path) | |
| chars = sorted(set(text)) | |
| stoi = {c: i for i, c in enumerate(chars)} | |
| data = np.array([stoi[c] for c in text], dtype=np.int64) | |
| split = int(0.9 * len(data)) | |
| return text, len(chars), data[:split], data[split:] | |
| def batches(data, n, length, seed, batch=B): | |
| r = np.random.default_rng(seed) | |
| for _ in range(n): | |
| ix = r.integers(0, len(data) - length - 1, batch) | |
| yield np.stack([data[i:i + length] for i in ix]), np.stack([data[i + 1:i + length + 1] for i in ix]) | |
| def loss_of(logits, y): | |
| return F.cross_entropy(logits.reshape(-1, logits.shape[-1]), y.reshape(-1)).item() | |
| def train(name, model, train_data, val_data, seed, lr=3e-3, iters=None, seq_len=None, batch=None): | |
| """返回验证 loss(固定在 64 字上评估,便于各实验比较)。 | |
| 学习率:100 步线性预热 + 余弦退火到 10%;AdamW,权重衰减 0.01,梯度裁剪 1.0。 | |
| seq_len / batch 默认 T / B;改训练长度时请同时改 batch,保持每步的字数不变。""" | |
| iters = iters or ITERS | |
| opt = zc.optim.AdamW(model.parameters(), lr=lr, weight_decay=0.01) | |
| t0 = time.time() | |
| for it, (x, y) in enumerate(batches(train_data, iters, seq_len or T, seed, batch or B)): | |
| opt.lr = lr * min(1, (it + 1) / 100) * (0.1 + 0.9 * 0.5 * (1 + np.cos(np.pi * it / iters))) | |
| logits = model(x) | |
| loss = F.cross_entropy(logits.reshape(-1, logits.shape[-1]), y.reshape(-1)) | |
| opt.zero_grad() | |
| loss.backward() | |
| zc.optim.clip_grad_norm(model.parameters(), 1.0) | |
| opt.step() | |
| if it % 500 == 0: | |
| print(f" [{name} seed={seed} lr={lr:g}] step {it:5d} train {loss.item():.3f} " | |
| f"{time.time() - t0:.0f}s", flush=True) | |
| return evaluate(model, val_data) | |
| def evaluate(model, val_data, n=40): | |
| model.eval() | |
| with zc.no_grad(): | |
| val = float(np.mean([loss_of(model(x), y) for x, y in batches(val_data, n, T, 999)])) | |
| model.train() | |
| return val | |
| def forward_flops_per_char(model, V): | |
| """前向一次、平均每个字多少次浮点运算(只算矩阵乘)。训练一步约是它的 3 倍。""" | |
| x = np.zeros((1, T), dtype=np.int64) | |
| with zc.no_grad(), zc.count_flops() as c: | |
| model(x) | |
| return c.flops / T | |
| def slot_usage(model): | |
| """每层"有效槽数":最近一次 forward 的平均读/写地址分布的 exp(熵),对头取平均。 | |
| 16 个槽都用得一样多 = 16;全挤在一个槽 = 1。""" | |
| out = [] | |
| for b in model.blocks: | |
| read, write = b.mix.usage | |
| eff = lambda p: float(np.mean(np.exp(-(p * np.log(p + 1e-12)).sum(-1)))) # noqa: E731 | |
| out.append((eff(read), eff(write))) | |
| return out | |
| def save_result(name, lines): | |
| """结果写两份:experiments/results/<时间>-<name>.md(存档,不覆盖)和 experiments/<name>.md(最新)。""" | |
| text = "\n".join(lines) + "\n" | |
| stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M") | |
| os.makedirs(os.path.join(HERE, "results"), exist_ok=True) | |
| for path in (os.path.join(HERE, "results", f"{stamp}-{name}.md"), os.path.join(HERE, f"{name}.md")): | |
| open(path, "w").write(text) | |
| print("\n" + text) | |
| def is_zeropoint(model): | |
| return isinstance(model, ZeroX) | |