Commit Graph

398 Commits (949488fdb4e3f8f0aea6bfdf65c10ad6f2298959)

Author SHA1 Message Date
jingyaogong 949488fdb4
Merge pull request #829 from DaoyuanLi2816/fix/dpo-paired-think-format
[fix] keep DPO preference-pair think formatting aligned
2026-09-20 20:07:46 +08:00
jingyaogong e41bf7cacf
Merge pull request #863 from Linxiushen/fix-fp16-attention-mask-nan
[fix] float16 下 attention mask 的 -1e9 溢出成 -inf,导致整条序列输出 NaN
2026-09-20 19:49:22 +08:00
jingyaogong b330a4d5c0
Merge pull request #860 from Linxiushen/fix-tokenizer-pretrain-format
[fix] train_tokenizer 读预训练语料时产出 0 条文本,静默训出空词表
2026-09-20 19:47:52 +08:00
jingyaogong b956bad49f
Merge pull request #865 from Linxiushen/fix-requirements-missing-fastapi
[fix] requirements.txt 缺 fastapi/uvicorn,按 README 跑 serve_openai_api.py 直接崩
2026-09-20 19:42:22 +08:00
jingyaogong d698c06fc5
Merge pull request #864 from Linxiushen/fix-nonstream-max-tokens
[fix] serve_openai_api 非流式端点的 max_tokens 不生效,与流式端点行为不一致
2026-09-20 19:42:05 +08:00
Linxiushen 984a2c3611 [fix] requirements.txt 缺 fastapi/uvicorn,按 README 跑 serve_openai_api.py 直接崩
README 的「第0步」让用户 pip install -r requirements.txt 完成环境搭建,
随后在 OpenAI 兼容服务一节给出可直接运行的命令(README.md:1652):

    cd scripts && python serve_openai_api.py

但 scripts/serve_openai_api.py 第12行 import uvicorn、第16-17行
from fastapi import ... 都是顶层硬依赖,而 requirements.txt 里两个都没有。

复现(venv 严格按 requirements.txt 装齐):
    $ cd scripts && python serve_openai_api.py --help
    ModuleNotFoundError: No module named 'uvicorn'

补上两行。版本已在独立 venv 里验证可与已声明的 pydantic==2.11.5 共存。
2026-09-20 11:57:27 +08:00
Linxiushen d4b67f1783 [fix] serve_openai_api 非流式端点的 max_tokens 不生效,与流式端点行为不一致
非流式分支传的是 max_length,但 MiniMindForCausalLM.generate() 的签名里
没有这个形参(只有 max_new_tokens=8192),max_length 被 **kwargs 吞掉后
无人处理,于是 request.max_tokens 对生成长度零约束。

同一文件的流式分支(第117行)传的是 max_new_tokens=max_tokens,精确生效。
同一个请求参数在两条路径上行为完全不同。

判决性实验:同时传 max_length=15 与 max_new_tokens=20,
prompt 长度 10 -> 实际输出总长 30(新增 20),证明 max_length 被无视。

改为与流式分支一致的 max_new_tokens=request.max_tokens(一行)。
2026-09-20 11:54:35 +08:00
Linxiushen ba10c4d17b [fix] float16 下 attention mask 的 -1e9 溢出成 -inf,导致整条序列输出 NaN
Attention.forward 的手写 softmax 分支(flash SDPA 关闭时走这里):

    scores = (xq @ xk.transpose(-2, -1)) / math.sqrt(self.head_dim)
    ...
    if attention_mask is not None: scores += (1.0 - mask) * -1e9
    output = ... F.softmax(scores.float(), dim=-1) ...

scores 的 dtype 跟随 xq/xk。float16 模型下它是 float16,而
torch.finfo(torch.float16).min ≈ -65504,把 -1e9 累加进去会饱和成 -inf
(不是一个很大的有限负数)。当某个 query 在因果范围内可见的 key 全被 mask
掉时(左 padding 必然出现),softmax 整行 = NaN,并在下一层通过 K/V 投影
扩散到同序列里 mask=1 的有效 token 上。

.float() 加在 softmax 那一刻已经太晚——溢出发生在它之前。

修复:把 float32 提升提前到加 mask 之前,两处共 2 行。
fp32/bf16 路径数值等价(fp32 下原来的 .float() 本就是 no-op)。
2026-09-20 11:50:28 +08:00
Linxiushen cb08b9300b [fix] train_tokenizer 读预训练语料时产出 0 条文本,静默训出空词表
get_texts() 只解析 data.get('conversations'),但主线预训练数据
pretrain_t2t(_mini).jsonl 的每行是 {"text": ...}(README「数据集下载」一节)。
把 DATA_PATH 指向预训练语料时,生成器一条文本都不产出,BpeTrainer 仅基于
initial_alphabet 训练,得到一个没有任何 merge 的词表,全程不报错。

复现(改 DATA_PATH 为任意 pretrain_t2t*.jsonl,或直接调用):

    from train_tokenizer import get_texts
    sum(1 for _ in get_texts('../dataset/pretrain_t2t_mini.jsonl'))
    # 修复前: 0
    # 修复后: 1270238

改动:
- get_texts 同时兼容 {"text": ...} 与 {"conversations": [...]} 两种格式
- 产出 0 条时 raise ValueError,而不是让调用方拿到一个空词表
- 把硬编码的 `if i >= 10000: break` 提为模块级 MAX_LINES(默认 0 = 全量)。
  原先这一行也会让 SFT 语料只用前 10000 行,且无从关闭;
  另外它按「行号」计数而非「有效文本条数」,遇到空内容或解析失败的行会提前耗尽配额。

验证:用 pretrain_t2t 的前 3000 篇跑 train_tokenizer(vocab_size=2000),
得到 vocab=2000 / merges=1708;修复前同样输入得到 0 条 merge。
两种格式各 20000 行的合成数据上,产出条数均为 20000(修复前分别是 0 和 10000)。
2026-09-20 10:33:51 +08:00
jingyaogong cc312c1cc6 [fix] unify Ctrl+C handling 2026-09-18 16:33:09 +08:00
jingyaogong e2ee878519
Merge pull request #857 from wbbeyourself/fix-ctrl-c-exception
fix: eval_llm.py Ctrl+C 退出异常修复
2026-09-18 15:54:43 +08:00
jingyaogong b63a9565e3 [fix] restore top-1 router gradient #858 2026-09-18 15:28:50 +08:00
jingyaogong 1aae25911b
Merge pull request #859 from basil-k-aji-dev/fix/moe-router-gradient-topk1
[fix] keep the MoE router trainable at num_experts_per_tok=1
2026-09-18 15:03:36 +08:00
basil-k-aji-dev 1c876435ef [fix] keep the MoE router trainable at num_experts_per_tok=1
Normalising a single top-k weight by its own sum gives exactly 1.0 for every
token, so the gate leaves the graph entirely: topk_idx is a discrete index and
the weight is now a constant, which leaves the LM loss no differentiable path
to the router. It then trains on aux_loss alone, whose optimum is a uniform
distribution, and the experts never specialise.

Measured on MOEFeedForward with router_aux_loss_coef=0, so only the LM path can
contribute a gradient:

  num_experts_per_tok=1, norm_topk_prob=True   7.02e-10   <- current default
  num_experts_per_tok=1, norm_topk_prob=False  1.02e-02
  num_experts_per_tok=2, norm_topk_prob=True   1.67e-02

Only normalise when there is more than one weight to normalise.

Note this changes the forward pass for existing num_experts_per_tok=1 MoE
checkpoints: the expert output was scaled by a constant 1.0 and is now scaled
by the gate probability.

Closes #858.
2026-09-17 21:31:41 +05:30
wbbeyourself 9fa620e2e5 fix: eval_llm.py Ctrl+C 退出异常修复 2026-09-16 17:47:55 +08:00
jingyaogong 7a9137d2e9 [update] trim verbose ddp comments 2026-09-15 14:53:11 +08:00
jingyaogong 7988bc81a3
Merge pull request #855 from basil-k-aji-dev/perf/ddp-followups
[perf] skip the per-forward RoPE buffer broadcast in the RL trainers
2026-09-15 14:51:37 +08:00
basil-k-aji-dev 37ac206e9a [perf] skip the per-forward RoPE buffer broadcast in the RL trainers
Follow-up to the review on #854.

DDP re-broadcasts every registered buffer from rank 0 on each forward. The only
buffers here are freqs_cos / freqs_sin, which each rank computes deterministically
from config and never mutates from data, so the broadcast moves identical bytes
every step for nothing.

Verified with a two-rank CPU/Gloo run: with broadcast_buffers=False the
gradients stay identical across ranks and so do the buffers.

Also document the critic's tied-embedding assumption. CriticModel.forward never
calls lm_head, which is only safe under DDP because tie_word_embeddings (default
True) makes lm_head.weight the same Parameter as embed_tokens.weight. Untied, it
becomes a genuinely unused parameter and DDP raises.
2026-09-14 22:22:00 +05:30
jingyaogong 21ec325dba [update] simplify safe math eval 2026-09-14 23:55:32 +08:00
jingyaogong cdf0a93e02
Merge pull request #847 from a0917-cell/fix/safe-math-eval
[fix] 用 AST 求值器替换工具里的 eval(),并修复 Windows 上工具调用全部静默失败
2026-09-14 23:34:28 +08:00
jingyaogong c367572702
Merge pull request #854 from basil-k-aji-dev/fix/rl-ddp-grad-sync
[fix] route RL trainer forward through DDP so gradients all-reduce
2026-09-14 22:28:03 +08:00
basil-k-aji-dev 9c088dd3a5 [fix] route RL trainer forward through DDP so gradients all-reduce
train_grpo.py, train_ppo.py and train_agent.py wrap the policy in
DistributedDataParallel but run the loss-bearing forward through
.module, bypassing the wrapper. DDP arms its gradient all-reduce hooks
inside DistributedDataParallel.forward(), so calling .module directly
means backward() performs no all-reduce: every rank steps on purely
local gradients and the replicas silently diverge. Only rank 0 is
saved, so multi-GPU RL training quietly degrades to single-GPU
training on 1/N of the data with no error.

The supervised trainers are unaffected; they already call the wrapped
module. no_grad uses of .module (rollout generation, checkpoint
saving) are left untouched, where unwrapping is correct.

Single-GPU behaviour is unchanged: with no process group, model is the
bare module and these calls are identical to before.
2026-09-14 14:54:07 +05:30
jingyaogong a3c7b01cc0 [fix] keep agent rollout token-aligned #850 2026-09-10 11:44:05 +08:00
tkgcc 0f6803e551 [fix] replace tool eval() with an AST math evaluator
execute_tool() relied on signal.SIGALRM, which does not exist on Windows.
The AttributeError was swallowed by a bare except, so every tool call
silently returned None and the model always observed
{"error": "tool not found"} (issue #846).

calculate_math also ran eval() on model-generated text. Two of the three
call sites (scripts/eval_toolcall.py, scripts/web_demo.py) passed no
globals at all, leaving full builtins reachable from sampled output.

safe_math_eval() in trainer_utils.py parses the expression with ast and
walks it, allowing only arithmetic operators and a math whitelist, with
caps on expression length and exponent size. It cannot loop forever, so
the signal-based timeout is no longer needed and is removed. Both sqrt(x)
and math.sqrt(x) spellings keep working.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 07:53:33 +08:00
jingyaogong 6fc918beb6 [update] remove expert args 2026-09-04 22:16:38 +08:00
jingyaogong 4bd6062ead
Merge pull request #836 from JustinGuese/moe-cli-flags
Expose num_experts / num_experts_per_tok / seed on the trainer CLIs
2026-09-04 22:10:18 +08:00
jingyaogong 4d7a74acab
Merge pull request #840 from JustinGuese/readme-benchmark-resolution
docs: state the measurement resolution of the objective benchmark table
2026-09-04 22:10:00 +08:00
Justin Guese 77946fda00 [docs] 在客观评测表下补充各数据集的测量分辨率
lm_eval 对每个指标都会输出 stderr,但表格只保留了点估计。这里按测试集
大小给出各任务的 ± 标准误(obqa ±1.9 到 cmmlu ±0.4),并说明比较两个
模型时差值的标准误约为单项的 √2 倍,方便读者判断表中哪些差距是可以
解读的。中英文 README 同步。
2026-09-03 17:10:52 +07:00
Justin Guese fc585a9bf9 [update] 把随机种子暴露到命令行
trainer 里的种子此前硬编码为 42(setup_seed(42 + rank) 与每轮的
setup_seed(42 + epoch)),要跑多种子实验只能改源码。新增 --seed,
默认 42,与改动前完全一致。
2026-09-03 15:36:14 +07:00
Justin Guese c3199c7240 [update] 把 num_experts / num_experts_per_tok 暴露到命令行
此前要改专家数只能去编辑 model_minimind.py。三个入口
(train_pretrain / train_full_sft / eval_llm)的默认值与 MiniMindConfig
一致(4 / 1),默认行为不变;RL 与 agent 相关的 trainer 不动。
2026-09-03 15:13:29 +07:00
jingyaogong 7a6fddd63a [update] readme 2026-08-31 18:08:05 +08:00
jingyaogong c9a8ab4632 [update] dependency version 2026-08-31 17:54:37 +08:00
Daoyuan Li 56d4cddfd8 [fix] keep DPO preference pair think formatting aligned 2026-08-29 18:29:00 -07:00
jingyaogong e84156818a [update] dependency version 2026-08-29 15:12:28 +08:00
jingyaogong d65ef2c00e [update] readme content 2026-08-21 16:49:00 +08:00
jingyaogong ed5ea9e02a [update] readme content 2026-08-21 16:30:30 +08:00
jingyaogong eabad706da [update] readme content 2026-08-21 16:25:20 +08:00
jingyaogong 393e387e9a [update] supporter charts 2026-08-06 22:07:29 +08:00
jingyaogong 89d674b8a5 [refactor] PPO cleanup 2026-07-23 11:11:05 +08:00
jingyaogong 2d4c385b0f [fix] PPO rollout mode
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 11:05:51 +08:00
jingyaogong e9f46506da
Merge pull request #814 from FloatFrank/fix/ppo-logp-autocast-align-v3
[fix] PPO: align mb_resp_logp with old_resp_logp under autocast to stabilize training
2026-07-23 10:32:06 +08:00
FloatFrank 1191fc8e9f [fix] PPO: align mb_resp_logp with old_resp_logp under autocast
Move log_softmax for mb_resp_logp inside autocast_ctx so it is upcast
to fp32, matching the rollout path (compute_per_token_logps runs under
no_grad+autocast). Previously log_softmax ran outside autocast on
fp16/bf16 logits, introducing a ~1e-2 systematic bias between
mb_resp_logp and old_resp_logp, so ratio != 1 and KL != 0 even before
any weight update. Under sparse rule-based rewards (e.g. math PPO)
this bias destabilises training.

Also add:
- --rollout_eval_mode (default 1): switch actor to eval during rollout
  to disable dropout and keep old/new logp consistent.
- --debug_log_ratio: optional diagnostic printing |mb-old| on the first
  minibatch to verify ratio ~= 1.
2026-07-21 18:51:42 +08:00
jingyaogong 512eed0b65 [fix] robustness 2026-06-28 18:03:34 +08:00
jingyaogong 3f1a7cc25b [fix] ddp exit hang 2026-06-01 17:50:39 +08:00
jingyaogong 4a68da72d5 [fix] lora dims 2026-05-31 13:38:51 +08:00
jingyaogong 4497610ec0 [fix] issue#771 2026-05-19 17:40:03 +08:00
jingyaogong 9da8e1ab18 [update] requirements 2026-05-16 18:08:37 +08:00
jingyaogong dddedc6881 [fix] repetition_penalty 2026-05-07 19:08:52 +08:00
jingyaogong 802c15b2b4 [feat] reduce RL memory 2026-05-06 15:07:28 +08:00
jingyaogong e73a407f7a
Merge pull request #759 from TKiteRunner/fix/grpo-oom-reorder-rewards
fix: resolve CUDA OOM in train_grpo.py on GPUs with <=8GB VRAM
2026-05-06 15:00:17 +08:00