Compare commits

...

17 Commits

Author SHA1 Message Date
AmirX0 52100993c8
Merge 66aef88185 into 1e6e909f88 2026-09-20 20:57:30 +03:30
jingyaogong 1e6e909f88 [update] trim redundant comments 2026-09-20 20:33:40 +08:00
jingyaogong b00be3e5ca
Merge pull request #769 from qizwiz/fix/llm-response-unguarded
fix: guard against empty/filtered LLM responses in chat_api.py
2026-09-20 20:19:39 +08:00
jingyaogong 7f5ba40a4e
Merge pull request #821 from lycDragonSlayer/refactor/remove-redundant-update-policy
[refactor] remove redundant rollout update_policy after torch.compile
2026-09-20 20:13:29 +08:00
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
AmirX0 66aef88185 docs: add multilingual README entry points 2026-09-01 06:55:44 +03:30
Daoyuan Li 56d4cddfd8 [fix] keep DPO preference pair think formatting aligned 2026-08-29 18:29:00 -07:00
TOPSAIL\78190 0c91dea4a2 [refactor] remove redundant rollout update_policy after torch.compile 2026-08-24 12:43:50 +08:00
Jonathan Hill 182bff1bd8 fix: guard against empty/filtered LLM responses in chat_api.py
Add null check before accessing choices[0].message.content in the
non-streaming path. An empty choices list causes IndexError; a None
message (returned by some providers on content-filtered responses
with HTTP 200) causes AttributeError.
2026-05-18 03:44:31 -05:00
11 changed files with 241 additions and 28 deletions

View File

@ -27,7 +27,7 @@
<div align="center">
中文 | [English](./README_en.md)
中文 | [English](./README_en.md) | [العربية](./README_i18n.md#arabic) | [Français](./README_i18n.md#french) | [Español](./README_i18n.md#spanish) | [Português](./README_i18n.md#portuguese) | [Deutsch](./README_i18n.md#german) | [فارسی](./README_i18n.md#persian) | [日本語](./README_i18n.md#japanese) | [한국어](./README_i18n.md#korean)
</div>

View File

@ -27,7 +27,7 @@
<div align="center">
[中文](./README.md) | English
[中文](./README.md) | English | [العربية](./README_i18n.md#arabic) | [Français](./README_i18n.md#french) | [Español](./README_i18n.md#spanish) | [Português](./README_i18n.md#portuguese) | [Deutsch](./README_i18n.md#german) | [فارسی](./README_i18n.md#persian) | [日本語](./README_i18n.md#japanese) | [한국어](./README_i18n.md#korean)
</div>

197
README_i18n.md Normal file
View File

@ -0,0 +1,197 @@
# MiniMind in other languages
[中文](./README.md) | [English](./README_en.md) | [العربية](#arabic) | [Français](#french) | [Español](#spanish) | [Português](#portuguese) | [Deutsch](#german) | [فارسی](#persian) | [日本語](#japanese) | [한국어](#korean)
These localized introductions provide a concise overview and the fastest path to running MiniMind. The complete and most current technical documentation is maintained in [English](./README_en.md) and [Chinese](./README.md).
<a id="arabic"></a>
<h2 dir="rtl">العربية</h2>
<p dir="rtl">MiniMind هو مشروع مفتوح المصدر يتيح تدريب نموذج لغوي صغير يضم نحو 64 مليون مُعلَمة من الصفر. يمكن إكمال مرحلة الضبط الدقيق الخاضع للإشراف خلال ساعتين تقريبًا على بطاقة NVIDIA 3090 واحدة، بتكلفة استئجار تقارب 3 يوانات صينية.</p>
<ul dir="rtl">
<li>تنفيذ مبسّط ومباشر باستخدام PyTorch لبنية Dense وMoE.</li>
<li>مسار تدريب متكامل يشمل التدريب المسبق وSFT وLoRA وDPO وPPO وGRPO وCISPO واستخدام الأدوات وAgentic RL والتقطير.</li>
<li>دعم Transformers وllama.cpp وvLLM وOllama وواجهة API متوافقة مع OpenAI وواجهة WebUI.</li>
<li>بيانات وأوزان ونصوص تدريب مفتوحة لتسهيل التعلم وإعادة إنتاج النتائج.</li>
</ul>
<p dir="rtl"><strong>البدء السريع</strong></p>
```bash
git clone --depth 1 https://github.com/jingyaogong/minimind
cd minimind
pip install -r requirements.txt
modelscope download --model gongjy/minimind-3 --local_dir ./minimind-3
python eval_llm.py --load_from ./minimind-3
```
<p dir="rtl">للحصول على تعليمات التدريب ومجموعات البيانات والتقييم وتحويل النموذج، راجع <a href="./README_en.md">الدليل الإنجليزي الكامل</a>.</p>
<a id="french"></a>
## Français
MiniMind est un projet open source permettant d'entraîner de zéro un petit modèle de langage d'environ 64 millions de paramètres. L'étape de fine-tuning supervisé peut être réalisée en environ deux heures sur une seule NVIDIA 3090, pour un coût de location proche de 3 RMB.
- Implémentation PyTorch minimale et lisible des architectures Dense et MoE.
- Pipeline complet : préentraînement, SFT, LoRA, DPO, PPO, GRPO, CISPO, Tool Use, Agentic RL et distillation.
- Compatibilité avec Transformers, llama.cpp, vLLM et Ollama, ainsi qu'une API compatible OpenAI et une WebUI.
- Jeux de données, poids et scripts d'entraînement ouverts pour apprendre et reproduire les résultats.
**Démarrage rapide**
```bash
git clone --depth 1 https://github.com/jingyaogong/minimind
cd minimind
pip install -r requirements.txt
modelscope download --model gongjy/minimind-3 --local_dir ./minimind-3
python eval_llm.py --load_from ./minimind-3
```
Pour les instructions complètes sur l'entraînement, les données, l'évaluation et la conversion des modèles, consultez le [guide complet en anglais](./README_en.md).
<a id="spanish"></a>
## Español
MiniMind es un proyecto de código abierto para entrenar desde cero un modelo de lenguaje pequeño de unos 64 millones de parámetros. La etapa de ajuste supervisado puede completarse en unas dos horas con una sola NVIDIA 3090, con un coste de alquiler cercano a 3 RMB.
- Implementación mínima y legible en PyTorch de arquitecturas Dense y MoE.
- Flujo completo: preentrenamiento, SFT, LoRA, DPO, PPO, GRPO, CISPO, uso de herramientas, Agentic RL y destilación.
- Compatibilidad con Transformers, llama.cpp, vLLM y Ollama, además de una API compatible con OpenAI y una WebUI.
- Conjuntos de datos, pesos y scripts de entrenamiento abiertos para aprender y reproducir resultados.
**Inicio rápido**
```bash
git clone --depth 1 https://github.com/jingyaogong/minimind
cd minimind
pip install -r requirements.txt
modelscope download --model gongjy/minimind-3 --local_dir ./minimind-3
python eval_llm.py --load_from ./minimind-3
```
Para obtener instrucciones completas sobre entrenamiento, datos, evaluación y conversión de modelos, consulta la [documentación completa en inglés](./README_en.md).
<a id="portuguese"></a>
## Português
MiniMind é um projeto de código aberto para treinar do zero um pequeno modelo de linguagem com cerca de 64 milhões de parâmetros. A etapa de ajuste fino supervisionado pode ser concluída em aproximadamente duas horas com uma única NVIDIA 3090, por um custo de locação próximo de 3 RMB.
- Implementação PyTorch mínima e legível de arquiteturas Dense e MoE.
- Pipeline completo: pré-treinamento, SFT, LoRA, DPO, PPO, GRPO, CISPO, uso de ferramentas, Agentic RL e destilação.
- Compatibilidade com Transformers, llama.cpp, vLLM e Ollama, além de API compatível com OpenAI e WebUI.
- Conjuntos de dados, pesos e scripts de treinamento abertos para aprendizado e reprodução dos resultados.
**Início rápido**
```bash
git clone --depth 1 https://github.com/jingyaogong/minimind
cd minimind
pip install -r requirements.txt
modelscope download --model gongjy/minimind-3 --local_dir ./minimind-3
python eval_llm.py --load_from ./minimind-3
```
Para instruções completas sobre treinamento, dados, avaliação e conversão de modelos, consulte a [documentação completa em inglês](./README_en.md).
<a id="german"></a>
## Deutsch
MiniMind ist ein Open-Source-Projekt, mit dem sich ein kleines Sprachmodell mit rund 64 Millionen Parametern von Grund auf trainieren lässt. Das überwachte Fine-Tuning kann auf einer einzelnen NVIDIA 3090 in ungefähr zwei Stunden abgeschlossen werden; die entsprechenden Mietkosten liegen bei etwa 3 RMB.
- Kleine, gut lesbare PyTorch-Implementierung von Dense- und MoE-Architekturen.
- Vollständige Pipeline: Pretraining, SFT, LoRA, DPO, PPO, GRPO, CISPO, Tool Use, Agentic RL und Distillation.
- Kompatibel mit Transformers, llama.cpp, vLLM und Ollama; zusätzlich stehen eine OpenAI-kompatible API und eine WebUI bereit.
- Offene Datensätze, Gewichte und Trainingsskripte zum Lernen und Reproduzieren der Ergebnisse.
**Schnellstart**
```bash
git clone --depth 1 https://github.com/jingyaogong/minimind
cd minimind
pip install -r requirements.txt
modelscope download --model gongjy/minimind-3 --local_dir ./minimind-3
python eval_llm.py --load_from ./minimind-3
```
Vollständige Anleitungen zu Training, Datensätzen, Evaluation und Modellkonvertierung enthält die [englische Dokumentation](./README_en.md).
<a id="persian"></a>
<h2 dir="rtl">فارسی</h2>
<p dir="rtl">MiniMind یک پروژهٔ متن‌باز برای آموزش یک مدل زبانی کوچک با حدود ۶۴ میلیون پارامتر از صفر است. مرحلهٔ تنظیم دقیق نظارت‌شده را می‌توان با یک کارت NVIDIA 3090 در حدود دو ساعت و با هزینهٔ اجاره‌ای نزدیک به ۳ یوان چین انجام داد.</p>
<ul dir="rtl">
<li>پیاده‌سازی ساده و خوانای معماری‌های Dense و MoE با PyTorch.</li>
<li>زنجیرهٔ کامل آموزش شامل پیش‌آموزش، SFT، LoRA، DPO، PPO، GRPO، CISPO، استفاده از ابزار، Agentic RL و تقطیر مدل.</li>
<li>سازگار با Transformers، llama.cpp، vLLM و Ollama، همراه با API سازگار با OpenAI و WebUI.</li>
<li>داده‌ها، وزن‌ها و اسکریپت‌های آموزشی باز برای یادگیری و بازتولید نتایج.</li>
</ul>
<p dir="rtl"><strong>شروع سریع</strong></p>
```bash
git clone --depth 1 https://github.com/jingyaogong/minimind
cd minimind
pip install -r requirements.txt
modelscope download --model gongjy/minimind-3 --local_dir ./minimind-3
python eval_llm.py --load_from ./minimind-3
```
<p dir="rtl">برای دستورالعمل کامل آموزش، داده‌ها، ارزیابی و تبدیل مدل، <a href="./README_en.md">مستندات کامل انگلیسی</a> را ببینید.</p>
<a id="japanese"></a>
## 日本語
MiniMind は、約 6,400 万パラメータの小規模言語モデルをゼロから学習できるオープンソースプロジェクトです。教師ありファインチューニングは、NVIDIA 3090 1枚で約2時間、レンタル費用約3人民元で完了できます。
- Dense および MoE アーキテクチャを、読みやすい最小限の PyTorch コードで実装。
- 事前学習、SFT、LoRA、DPO、PPO、GRPO、CISPO、Tool Use、Agentic RL、蒸留までを網羅。
- Transformers、llama.cpp、vLLM、Ollama に対応し、OpenAI互換APIとWebUIも提供。
- 学習と再現のためのデータセット、重み、学習スクリプトを公開。
**クイックスタート**
```bash
git clone --depth 1 https://github.com/jingyaogong/minimind
cd minimind
pip install -r requirements.txt
modelscope download --model gongjy/minimind-3 --local_dir ./minimind-3
python eval_llm.py --load_from ./minimind-3
```
学習、データセット、評価、モデル変換の詳しい手順については、[英語版の完全なドキュメント](./README_en.md)を参照してください。
<a id="korean"></a>
## 한국어
MiniMind는 약 6,400만 개의 파라미터를 가진 소형 언어 모델을 처음부터 학습할 수 있는 오픈 소스 프로젝트입니다. 지도 미세 조정 단계는 NVIDIA 3090 한 장에서 약 2시간, 약 3위안의 대여 비용으로 완료할 수 있습니다.
- Dense 및 MoE 아키텍처를 간결하고 읽기 쉬운 PyTorch 코드로 구현합니다.
- 사전 학습, SFT, LoRA, DPO, PPO, GRPO, CISPO, 도구 사용, Agentic RL, 모델 증류를 포함한 전체 파이프라인을 제공합니다.
- Transformers, llama.cpp, vLLM, Ollama와 호환되며 OpenAI 호환 API와 WebUI를 제공합니다.
- 학습과 결과 재현을 위한 데이터셋, 가중치, 학습 스크립트를 공개합니다.
**빠른 시작**
```bash
git clone --depth 1 https://github.com/jingyaogong/minimind
cd minimind
pip install -r requirements.txt
modelscope download --model gongjy/minimind-3 --local_dir ./minimind-3
python eval_llm.py --load_from ./minimind-3
```
학습, 데이터셋, 평가, 모델 변환에 대한 전체 안내는 [영문 문서](./README_en.md)를 참고하세요.
---
These translations are community-maintained summaries. If you find an inaccurate phrase or want to maintain a complete translation, please open a pull request.

View File

@ -28,10 +28,13 @@ def pre_processing_chat(conversations, add_system_ratio=0.2):
return [{'role': 'system', 'content': random.choice(SYSTEM_PROMPTS)}] + conversations
return conversations
def post_processing_chat(prompt_content, empty_think_ratio=0.2):
def post_processing_chat(prompt_content, empty_think_ratio=0.2, remove_empty_think=None):
# 以80%概率移除空思考标签
if '<think>\n\n</think>\n\n' in prompt_content and random.random() > empty_think_ratio:
prompt_content = prompt_content.replace('<think>\n\n</think>\n\n', '')
if '<think>\n\n</think>\n\n' in prompt_content:
if remove_empty_think is None:
remove_empty_think = random.random() > empty_think_ratio
if remove_empty_think:
prompt_content = prompt_content.replace('<think>\n\n</think>\n\n', '')
return prompt_content
class PretrainDataset(Dataset):
@ -139,12 +142,13 @@ class DPODataset(Dataset):
chosen_prompt = self.tokenizer.apply_chat_template(
chosen, tokenize=False, add_generation_prompt=False
)
chosen_prompt = post_processing_chat(chosen_prompt)
rejected_prompt = self.tokenizer.apply_chat_template(
rejected, tokenize=False, add_generation_prompt=False
)
rejected_prompt = post_processing_chat(rejected_prompt)
remove_empty_think = random.random() > 0.2
chosen_prompt = post_processing_chat(chosen_prompt, remove_empty_think=remove_empty_think)
rejected_prompt = post_processing_chat(rejected_prompt, remove_empty_think=remove_empty_think)
chosen_encoding = self.tokenizer(
chosen_prompt, truncation=True, max_length=self.max_length, padding='max_length'
)
@ -253,4 +257,4 @@ class AgentRLDataset(Dataset):
if __name__ == "__main__":
pass
pass

View File

@ -125,10 +125,10 @@ class Attention(nn.Module):
if self.flash and (seq_len > 1) and (not self.is_causal or past_key_value is None) and (attention_mask is None or torch.all(attention_mask == 1)):
output = F.scaled_dot_product_attention(xq, xk, xv, dropout_p=self.dropout if self.training else 0.0, is_causal=self.is_causal)
else:
scores = (xq @ xk.transpose(-2, -1)) / math.sqrt(self.head_dim)
scores = (xq @ xk.transpose(-2, -1)).float() / math.sqrt(self.head_dim)
if self.is_causal: scores[:, :, :, -seq_len:] += torch.full((seq_len, seq_len), float("-inf"), device=scores.device).triu(1)
if attention_mask is not None: scores += (1.0 - attention_mask.unsqueeze(1).unsqueeze(2)) * -1e9
output = self.attn_dropout(F.softmax(scores.float(), dim=-1).type_as(xq)) @ xv
output = self.attn_dropout(F.softmax(scores, dim=-1).type_as(xq)) @ xv
output = output.transpose(1, 2).reshape(bsz, seq_len, -1)
output = self.resid_dropout(self.o_proj(output))
return output, past_kv

View File

@ -28,6 +28,8 @@ streamlit==1.50.0
einops==0.8.1
swanlab==0.9.8
modelscope==1.37.0
fastapi==0.120.1
uvicorn==0.38.0
# torch==2.6.0
# torchvision==0.21.0
# peft==0.7.1

View File

@ -202,7 +202,7 @@ async def chat_completions(request: ChatRequest):
with torch.no_grad():
generated_ids = model.generate(
inputs["input_ids"],
max_length=inputs["input_ids"].shape[1] + request.max_tokens,
max_new_tokens=request.max_tokens,
do_sample=True,
attention_mask=inputs["attention_mask"],
pad_token_id=tokenizer.pad_token_id,

View File

@ -273,8 +273,6 @@ def rl_train_epoch(epoch, loader, iters, rollout_engine, ref_model, reward_model
rewards = calculate_rewards(prompts, completions, gt_batch, tools_batch, args.num_generations, reward_model, device=args.device, turn_outputs_batch=turn_outputs_batch, unfinished_batch=unfinished_batch)
with autocast_ctx:
# 反向传播必须经过 DDP 包装后的模块:直接调用 .module 会跳过
# DDP 的 prepare_for_backward,梯度不会 all-reduce,各卡静默发散。
res = model(input_ids, attention_mask=full_mask)
aux_loss = res.aux_loss if lm_config.use_moe else torch.tensor(0.0, device=args.device)
logits = res.logits[:, :-1, :]
@ -471,7 +469,6 @@ if __name__ == "__main__":
if args.use_compile == 1:
model = torch.compile(model)
Logger('torch.compile enabled')
rollout_engine.update_policy(model)
if dist.is_initialized():
# 同 train_ppo:RoPE buffer 各 rank 一致,每步广播纯属浪费
model = DistributedDataParallel(model, device_ids=[local_rank], broadcast_buffers=False)

View File

@ -96,8 +96,6 @@ def grpo_train_epoch(epoch, loader, iters, rollout_engine, ref_model, reward_mod
rewards = calculate_rewards(prompts, completions, reward_model).to(args.device) # [B*num_gen]
with autocast_ctx:
# 反向传播必须经过 DDP 包装后的模块:直接调用 .module 会跳过
# DDP 的 prepare_for_backward,梯度不会 all-reduce,各卡静默发散。
res = model(outputs, attention_mask=full_mask)
aux_loss = res.aux_loss if lm_config.use_moe else torch.tensor(0.0, device=args.device)
per_token_logps = F.log_softmax(res.logits[:, :-1, :], dim=-1).gather(2, outputs[:, 1:].unsqueeze(-1)).squeeze(-1).gather(1, logp_pos)
@ -312,7 +310,6 @@ if __name__ == "__main__":
if args.use_compile == 1:
model = torch.compile(model)
Logger('torch.compile enabled')
rollout_engine.update_policy(model)
if dist.is_initialized():
# 同 train_ppo:RoPE buffer 各 rank 一致,每步广播纯属浪费
model = DistributedDataParallel(model, device_ids=[local_rank], broadcast_buffers=False)

View File

@ -166,16 +166,12 @@ def ppo_train_epoch(epoch, loader, iters, rollout_engine, ref_model, actor_sched
for i in range(0, B, mb_size):
inds = b_inds[i:i + mb_size]
# 反向传播必须经过 DDP 包装后的模块:直接调用 .module 会跳过
# DDP 的 prepare_for_backward,梯度不会 all-reduce,各卡静默发散。
mb_values_seq = critic_model(input_ids=gen_out[inds], attention_mask=full_mask[inds])
mb_resp_values = mb_values_seq.gather(1, logp_pos[inds])
with autocast_ctx:
res = actor_model(input_ids=gen_out[inds], attention_mask=full_mask[inds])
aux_loss = res.aux_loss if lm_config.use_moe else torch.tensor(0.0, device=args.device)
# 在 autocast 内计算 log_softmax,避免直接对 fp16/bf16 logits
# 计算造成额外数值偏差。
mb_resp_logp = F.log_softmax(res.logits[:, :-1], dim=-1).gather(2, labels[inds].unsqueeze(-1)).squeeze(-1).gather(1, logp_pos[inds])
log_ratio = mb_resp_logp - old_resp_logp[inds]
@ -427,7 +423,6 @@ if __name__ == "__main__":
if args.use_compile == 1:
actor_model = torch.compile(actor_model)
Logger('torch.compile enabled')
rollout_engine.update_policy(actor_model)
if dist.is_initialized():
# freqs_cos/freqs_sin 各 rank 由 config 确定性算出,默认每步广播一次纯属浪费
actor_model = DistributedDataParallel(actor_model, device_ids=[local_rank], broadcast_buffers=False)

View File

@ -8,18 +8,39 @@ DATA_PATH = '../dataset/sft_t2t_mini.jsonl'
TOKENIZER_DIR = '../model_learn_tokenizer/'
VOCAB_SIZE = 6400
SPECIAL_TOKENS_NUM = 36
MAX_LINES = 0 # 0 表示读取全部;设为正数则只取前 N 条(快速试跑)
def get_texts(data_path):
def get_texts(data_path, max_lines=MAX_LINES):
"""逐行产出用于训练 BPE 的文本,兼容两种主线数据格式。
pretrain_t2t(_mini).jsonl 是 {"text": ...},
sft_t2t(_mini).jsonl 是 {"conversations": [{"role":..., "content":...}, ...]}。
原先只解析 conversations,所以把 DATA_PATH 指向预训练语料时一条文本都取不到,
BPE 仅基于 initial_alphabet 训练,产出一个没有任何 merge 的词表,且全程不报错。
"""
used = 0
with open(data_path, 'r', encoding='utf-8', errors='ignore') as f:
for i, line in enumerate(f):
if i >= 10000: break # 选10000行测试
for line in f:
if max_lines and used >= max_lines:
break
try:
data = json.loads(line)
contents = [item.get('content') for item in data.get('conversations', []) if item.get('content')]
if contents:
yield "\n".join(contents)
except json.JSONDecodeError:
continue
if 'text' in data:
text = str(data['text'])
else:
contents = [item.get('content') for item in data.get('conversations', []) if item.get('content')]
text = "\n".join(contents) if contents else ''
if text.strip():
used += 1
yield text
if used == 0:
raise ValueError(
f'{data_path} 中没有可用文本:每行应为 {{"text": ...}} 或 '
f'{{"conversations": [{{"role":..., "content":...}}, ...]}}。'
f'继续训练只会得到一个空词表。'
)
def train_tokenizer(data_path, tokenizer_dir, vocab_size, special_tokens_num=SPECIAL_TOKENS_NUM):
tokenizer = Tokenizer(models.BPE())