[v1] update docs (#10684)

This commit is contained in:
Jiaqi
2026-09-14 16:09:48 +08:00
committed by GitHub
parent 100e9a42c6
commit 97b32d3133
68 changed files with 2071 additions and 2152 deletions

View File

@@ -0,0 +1,47 @@
# BaseSampler
`BaseSampler` 位于 `core/base_sampler.py`,为推理调用方提供生成接口,并持有具体的推理 engine。模型和 Renderer 由 [ModelEngine](model_engine.md) 创建后传入;Sampler 负责选择后端和转发调用。
## 组件与状态
| 组件 | 职责 | 源码 |
|------|------|------|
| BaseSampler | 根据 `sample_backend` 选择 engine,转发生成请求 | `core/base_sampler.py` |
| BaseEngine / HuggingFaceEngine | 定义后端接口,使用模型和 Renderer 执行生成 | `core/utils/inference_engine.py` |
| SyncSampler | 将异步生成接口适配为 CLI 使用的同步迭代器 | `samplers/cli_sampler.py` |
| `run_chat` | 创建组件,读取用户输入,保存对话历史并输出回复 | `samplers/cli_sampler.py` |
当前 BaseSampler 直接构造 HuggingFaceEngine,后端选择不经过 BasePlugin 注册表。HuggingFaceEngine 保存模型、Renderer 和推理参数;对话历史由 `run_chat` 中的消息列表持有,每次请求传入完整历史。
## 单次生成路径
```text
run_chat:追加 user 消息
→ SyncSampler.generate(messages)
→ BaseSampler.generate(messages, tools)
→ HuggingFaceEngine.generate(messages, tools)
→ Renderer.render_messages(..., is_generate=True)
→ input_ids / attention_mask 转为设备上的张量
→ 后台线程调用 model.generate
→ AsyncTextIteratorStreamer 异步返回文本片段
→ CLI 逐段输出,拼成 assistant 消息并加入历史
```
HuggingFaceEngine 用信号量限制同时进入生成过程的请求数。它将 `max_new_tokens` 和 streamer 传给 `model.generate`,streamer 跳过 prompt 和特殊 token;返回值是解码后的文本片段,不是 token ID,也不保证每个片段恰好对应一个 token。
SyncSampler 另行维护后台事件循环,逐次等待异步生成器的下一段文本,再交给同步调用方。这个适配层改变调用方式,不改变消息渲染或模型生成逻辑。
## 接口与当前支持范围
| 接口 | 输入与输出 | 当前行为 |
|------|------------|----------|
| `generate(messages, tools=None)` | 标准 Message 列表、可选 tools 字符串;异步产出 `str` | Hugging Face 流式生成 |
| `batch_infer(dataset)` | Dataset;约定返回 Sample 列表 | HuggingFaceEngine 抛出 `NotImplementedError` |
BaseSampler 的非 HF 后端分支会抛出 `ValueError`。CLI 中设置 `train_dataset` 会进入批量推理分支,但当前 engine 尚未实现该接口。因此,已实现的用户路径是 HF 交互式对话,启动方式见[推理](../../feature-guide/inference.md),配置字段见[推理参数](../../configuration/inference.md)。
## 扩展边界
新增推理后端时,实现 `BaseEngine` 的接口,并接入 BaseSampler 的后端选择;仅添加文件或写入新的 `sample_backend` 名称不会完成接入。后端需要保持异步文本流接口,并明确批量推理是否可用,SyncSampler 才能沿用现有适配方式。
改变模板和消息到模型输入的转换时,修改 [Renderer](renderer.md);改变生成参数的传递、模型调用或输出流时,修改 engine;改变命令行输入和对话历史管理时,修改 `run_chat`。当前 HF 生成调用只传递 Renderer 结果中的 `input_ids` 和 `attention_mask`,扩展其他模型输入时还需要补齐 engine 向模型传参的路径。

View File

@@ -0,0 +1,50 @@
# BaseTrainer
`BaseTrainer` 提供 SFT、DPO 和 RM 共用的训练生命周期。子类主要实现 `compute_loss`,必要时覆盖模型分片或输入处理。
实现位于 `core/base_trainer.py`。任务入口负责加载模型与数据,再把它们传入 Trainer;BaseTrainer 从这些对象开始组织批次生成、优化器更新和训练状态。这样,各训练目标可以共用同一套运行循环。
## 初始化训练组件
先保存参数、模型、Renderer 与数据集,创建 BatchGenerator,再确定训练总步数和 checkpoint 间隔。后续组件的创建顺序取决于后端:
- FSDP2 / FSDPTurbo 先分片模型,再基于处理后的参数创建 optimizer 和 scheduler;没有显式分布式后端时,使用普通模型或 DDP。
- DeepSpeed 先构造后端 engine,再创建 optimizer 和 scheduler,最后由 engine 的 `prepare` 统一准备训练对象。
模型和优化器就绪后,`TrainingCheckpointCoordinator` 才能恢复它们的状态以及批次进度。之后创建 CallbackHandler 和 TrainerState,并把已恢复的 step、epoch 同步给回调状态。CP 模型处理在初始化的最后阶段接入。
## 训练循环
```text
epoch / global step
→ BatchGenerator
→ forward + compute_loss
→ gradient accumulation
→ gradient clipping
→ optimizer.step
→ scheduler.step
→ callback / logging
→ 可选 checkpoint
```
`global_batch_size / (dp_size × micro_batch_size)` 决定梯度累积所需的 micro-batch 数。
BatchGenerator 每次迭代返回一组 micro-batch,对应一次更新步。普通路径对每个 micro-batch 调用 `compute_loss` 并反向传播,累积完成后执行梯度裁剪、参数更新和清零。`compute_loss` 返回标量损失,BaseTrainer 还会根据有效监督 token 数和 DP 规模缩放损失,因此子类不应自行重复执行参数更新。
DeepSpeed 路径把反向传播和同步边界交给后端 engine;SFT 开启 CP 时,循环改为调用 SequenceParallelLossPlugin。扩展损失时需要同时考虑目标任务是否支持这两条路径。
## 训练器实现
- `SFTTrainer`:带 `loss_weights` 的语言模型损失
- `DPOTrainer`:policy/reference 偏好损失
- `RMTrainer`:chosen/rejected reward 排序损失
## 保存模型与 Checkpoint
训练循环在保存 step 调用 `core/utils/checkpoint.py` 中的 TrainingCheckpointCoordinator。它协调模型、优化器、scheduler、批次进度与训练计数等状态;其中依赖分片格式的模型和优化器读写交给 DistributedPlugin。这样,训练器决定何时保存,后端决定如何保存其分布式状态。
`fit()` 结束后,任务入口另外调用 `save_model()` 保存最终模型。最终模型与用于续训的 checkpoint 用途不同,用户配置见[模型保存与恢复](../../feature-guide/model_saving.md)。
## 扩展边界
新增训练目标时,在 Trainer 子类中实现损失和必要的任务准备。替换优化器、批处理或分布式实现时使用[训练器插件](../plugins/trainer_plugins.md),避免在每个任务的训练循环中复制后端分支。只观察生命周期事件时使用 [Callback](callback.md)。

View File

@@ -0,0 +1,45 @@
# BatchGenerator
`core/utils/batching.py` 负责 sampler、DataLoader、collate、梯度累积批次和状态恢复;`trainer_plugins/batching.py` 提供非 normal 策略。
## 输入、输出与状态
BatchGenerator 接收 DataEngine、Renderer 和批处理参数。内部的 StatefulDataLoader 按 DP 维度分配样本,并用 `Renderer.process_samples` 把读出的消息转换为 ModelInput 列表。此时还没有完成训练 batch 的组装。
这些 ModelInput 进入 StatefulBuffer,再由选定策略取出、截断并组织为 BatchInput。`__next__()` 返回的是一个更新步所需的 micro-batch 列表,BaseTrainer 逐个计算损失和累积梯度。
```text
DataEngine + StatefulDistributedSampler
→ StatefulDataLoader(Renderer.process_samples)
→ StatefulBuffer
→ normal collate 或 BatchingPlugin.generate_batch
→ list[BatchInput]
```
BatchGenerator 持有 DataLoader、迭代器和缓冲区;策略插件接收缓冲区和 `batch_info`,决定如何填充与取样。读取进度因此不保存在插件注册表中。
## Normal Batching
固定取 `micro_batch_size` 个样本,Renderer 处理后按当前 batch 最长序列 padding。
## Padding-Free Batching
`BatchingPlugin("padding_free")` 将多个样本拼成无 padding 的序列,并维护能够隔离文档的 attention/position 信息。
## Dynamic Batching
动态策略根据样本 token 数决定每个 batch 包含多少样本。纯 `dynamic_batching` 仍执行 padding;`dynamic_padding_free` 同时拼接序列。
## 保存批次状态
`state_dict()` 同时保存 DataLoader 的读取状态与 buffer 中已读取、尚未消费的样本。只恢复前者会丢掉预读样本,只恢复后者则无法确定接下来应该读哪里。
`load_state_dict()` 恢复两部分状态并设置 `_is_resuming`。普通 `__iter__()` 会清空 buffer;恢复后的首次迭代保留 buffer,随后清除恢复标记。新增策略必须保持这个状态约定,不能将剩余样本藏在无法随 checkpoint 保存的全局变量中。
## 校验动态批处理参数
纯 `dynamic_batching` 需要正数 `max_steps` 且不能使用 `save_epochs`,这些条件在 `TrainingArguments.__post_init__` 中校验。多模态输入是否能由当前策略处理,则在 BatchGenerator 生成 batch 时检查。配置条件与使用选择见[批处理策略](../../feature-guide/batching.md)。
## 扩展边界
改变 token 预算或样本拼接方式时实现 BatchingPlugin 的方法组,接口见[训练器插件](../plugins/trainer_plugins.md#batchingplugin)。改变消息如何变成 token 时修改 Renderer,改变反向传播和更新边界时检查 BaseTrainer。

View File

@@ -0,0 +1,39 @@
# Callback
Callback 系统位于 `utils/callbacks/`,用于把日志和生命周期通知从训练循环中分离。
## Callback 组件
- `TrainerCallback`:事件接口
- `CallbackHandler`:按注册顺序广播事件
- `LoggingCallback`:输出 loss、learning rate、grad norm 等指标
## 事件如何传递
BaseTrainer 创建 CallbackHandler,并先加入默认 LoggingCallback,再加入构造参数 `callbacks` 中的实例。训练循环在固定位置调用 handler;handler 按列表顺序同步调用每个实例的同名方法,不经过 BasePlugin 注册表。
```text
on_train_begin
→ on_epoch_begin
→ on_step_begin
→ 前向、反向与更新
→ on_step_end
→ on_log(到达 logging_steps 时)
→ on_save(checkpoint 写入后)
→ on_epoch_end
on_train_end
```
事件位置由 Trainer 和 checkpoint 协调器决定。Callback 无需自行判断什么时候保存模型或推进优化器,只响应已经发生的生命周期事件。
## Callback 能读取什么
每个事件接收训练参数 `args`、进度对象 `state` 和额外关键字参数。TrainerState 保存 step、epoch、最近一次损失、梯度范数和学习率等;Handler 还通过关键字参数传入当前 model、optimizer、lr_scheduler 和批次生成器。
在 `on_step_begin` 时,本步计算尚未发生,损失等指标仍来自此前的更新;本步指标在 `on_step_end` 前写入 state。`on_log` 额外接收本次日志字典。
## 扩展边界
自定义 Callback 继承 TrainerCallback,仅覆盖关心的事件,并将实例传给 Trainer 的 `callbacks` 参数。它适合记录指标、连接外部观察工具等附加行为。需要改变损失、更新规则或 checkpoint 格式时,使用 Trainer 或对应插件。
接口约定将 state 作为供观察的进度信息,不通过修改它来控制训练流程;代码没有把这个 dataclass 冻结。事件也不会统一限制到 rank 0,涉及单次输出的 Callback 需要自行判断 rank,默认 LoggingCallback 就在自身内部处理这个条件。

View File

@@ -0,0 +1,39 @@
# DataEngine
`DataEngine(dataset_path: str)` 位于 `core/data_engine.py`,把一个或多个原始数据集暴露为可按整数索引访问的统一 Dataset。它处理数据来源、字段转换和样本索引,返回的仍是消息结构;分词和监督标签由后续 [Renderer](renderer.md)生成。
## 加载数据集
```text
dataset_path
→ 识别 YAML、目录、数据文件或 Hub ID
→ 生成 dataset name → DatasetInfo 映射
→ hf_hub 直接加载,其他 source 调用 DataLoaderPlugin.load
→ 逐条转换样本,确定 SFT 的受监督轮次
→ 建立 (dataset_name, sample_index, cut) 全局索引
→ 根据 size / weight 调整索引
```
初始化时,`datasets` 保存各来源加载出的 Dataset,`dataset_infos` 保存来源配置,`data_index` 保存训练样本到原始行的映射。索引项中的 `cut` 表示取消息列表的哪个前缀;它不是 token 截断长度。
多轮 SFT 的每个受监督 assistant turn 对应一个索引项,多个索引项可以指向同一条原始对话。DPO 等非 SFT 样本保持完整,`cut` 为 `None`。这一设计让 sampler 能按训练样本分配索引,同时保留原始数据集。
## 读取一个样本
`__getitem__` 从全局索引取出数据集名称、原始行号和 `cut`,再由 `_get` 读取原始行、调用 converter,并按 `cut` 截取消息前缀。返回样本带有 `_dataset_name`,供后续处理识别来源。
Converter 既会在初始化建立索引时调用,也会在实际取样时调用;DataEngine 不把索引阶段的转换结果缓存为新的数据集。因此转换函数应对相同输入产生一致的消息结构,避免依赖调用次数或在转换中改变原始行,否则索引记录的轮次可能与训练读取的内容不一致。
## 解析 DatasetInfo
字段结构定义在 `utils/types.py`。`_get_dataset_info` 将路径、Hub ID 或 YAML 统一成名称到 DatasetInfo 的映射,后续加载逻辑只处理这个映射。字段与默认值见[数据参数](../../configuration/data.md#datasetinfo)。
## 处理 Streaming Dataset
所有数据集必须同时为 map-style 或同时为 streaming。混合模式无法共享同一种索引和 sampler 语义,因此 DataEngine 初始化阶段会拒绝。当前 BatchGenerator 只支持 map-style dataset;streaming dataset 进入批处理初始化时会抛出 `NotImplementedError`。
## 扩展数据源与格式
加载新来源时,扩展 `DataLoaderPlugin`,保持返回 Dataset 的约定;只改变原始字段的含义时,扩展 `DataConverterPlugin`,保持返回标准 Sample 的约定。两种接口及其调用参数见[数据插件](../plugins/data_plugins.md)。
多轮展开和全局索引属于 DataEngine,共用索引行为应在此处理。模型模板、tokenization 和 padding 分别属于 Renderer 与 BatchGenerator,不放入 loader 或 converter。用户用法见[数据准备](../../feature-guide/data_preparation.md)。

View File

@@ -0,0 +1,26 @@
# Core(核心模块)
Core 负责连接配置、数据、模型和训练流程。各组件由 Trainer 或 Sampler 组合,不通过插件注册表选择。
| 组件 | 职责 | 源码 |
|------|------|------|
| [DataEngine](data_engine.md) | 加载、转换和索引数据集 | `core/data_engine.py` |
| [ModelEngine](model_engine.md) | 加载 Processor、Renderer 和模型 | `core/model_engine.py` |
| [Renderer](renderer.md) | 将 Messages 转换为模型输入 | `core/rendering/` |
| [BaseTrainer](base_trainer.md) | 管理通用训练生命周期 | `core/base_trainer.py` |
| [BaseSampler](base_sampler.md) | 选择推理后端并连接生成接口 | `core/base_sampler.py` |
| [BatchGenerator](batch_generator.md) | 生成批次并恢复批次状态 | `core/utils/batching.py` |
| [Callback](callback.md) | 分发训练生命周期事件 | `utils/callbacks/` |
```{toctree}
:maxdepth: 1
:hidden:
data_engine
model_engine
renderer
base_trainer
base_sampler
batch_generator
callback
```

View File

@@ -0,0 +1,46 @@
# ModelEngine
`ModelEngine(model_args, is_train=False)` 拥有 processor、model config、Renderer 和最终 Hugging Face model。
实现位于 `core/model_engine.py`。它把同一份模型配置转为相互匹配的模型与输入处理组件;任务入口随后把 `model` 和 `renderer` 交给 Trainer 或 Sampler。训练循环、优化器创建和常规分布式分片在后续组件中完成。
## 模型加载流程
```text
AutoProcessor.from_pretrained
→ 同步或覆盖 chat_template
→ AutoConfig.from_pretrained
→ Renderer(processor)
→ 选择初始化设备
→ 应用量化加载参数
→ 选择 AutoModel 类
→ from_pretrained / from_config
→ PEFT
→ Kernel
```
这个顺序由 ModelEngine 固定:初始化插件先决定在哪里创建模型,量化插件再补充权重加载参数;模型对象创建后,PEFT 决定可训练部分或加载 adapter,Kernel 最后处理实际得到的模型。插件之间不互相调度,后一阶段接收前一阶段的结果。
`is_train` 会传给量化和 PEFT 路径,并影响未启用 PEFT 时的模型精度处理。它表达本次加载的用途,训练循环仍会在 `fit()` 中设置模型的训练模式。
## 同步 Chat Template
多模态 processor 没有模板时,会从其 tokenizer 同步。`custom_chat_template` 则覆盖 processor/tokenizer 模板。Renderer 最终调用 `apply_chat_template`,不再导入模型专属 Python 模板。
## 选择 Hugging Face 模型类
- `llm`:根据模型 config 选择 image-to-text、多模态语言模型或因果语言模型类;多模态语言模型类仅在当前 Transformers 提供时参与选择
- `cls`:单标签 token classification 模型,RM 使用
- `other`:`AutoModel`
## Meta 与 ZeRO-3 初始化
普通路径通过 `from_pretrained` 加载权重。选择 meta device 后,ModelEngine 使用 `init_empty_weights()` 和 `from_config` 构造模型结构,此时参数没有真实数据,需要后续分布式加载路径填充。`init_on_rank0` 则让 rank 0 在 CPU 加载,其余 rank 构造 meta 模型。
Meta 初始化不能与量化同时使用;当前 LoRA 路径也会拒绝 `init_on_meta`。这些检查发生在模型加载阶段,不能通过调整 YAML 中字段的书写顺序绕过。
DeepSpeed ZeRO-3 需要在 `from_pretrained` 创建参数时介入,因此 ModelEngine 在加载前建立 ZeRO-3 上下文,并在加载后清理;后续 BaseTrainer 再准备训练 engine。它与 FSDP2 在 Trainer 中对已构造模型进行分片的时机不同。
## 扩展边界
改变加载顺序或 AutoModel 类选择时,修改 ModelEngine;增加一种初始化、PEFT 或量化实现时,扩展相应[模型插件](../plugins/model_plugins.md)。只需改变消息格式时,使用 chat template 或修改 Renderer 的转换逻辑,无需增加一种模型加载插件。

View File

@@ -0,0 +1,51 @@
# Renderer
当前 Renderer 位于 `core/rendering/`,把 v1 `Sample` 转成 tokenized `ModelInput`。它不再依赖 `RenderingPlugin` 或模型名称模板表。
## 在数据路径中的位置
DataEngine 负责返回标准消息结构,ModelEngine 提供与模型匹配的 processor 并创建 Renderer。BatchGenerator 的 DataLoader 调用 `Renderer.process_samples(samples)`,得到已分词的样本列表,再执行批次组装。
Renderer 的结果包含 `input_ids`、`attention_mask`、`labels`、`loss_weights` 和位置信息;多模态样本还携带 processor 产生的媒体特征。它负责表达模型输入和监督范围,batch 内的 padding、拼接以及长度截断由后续批处理承担。
## Chat Template 的来源
1. 使用 processor/tokenizer 自带 `chat_template`。
2. `custom_chat_template` 可以在 ModelEngine 中覆盖它。
3. 模型完全没有模板时使用内置 ChatML fallback。
## 生成训练标签
多轮对话由 DataEngine 按每个受监督 assistant turn 切分为训练样本(见[DataEngine](data_engine.md))。Renderer 对样本最后一个 assistant turn 分别渲染 prompt 和完整序列,通过两者前缀差恢复监督 token 区间,不维护模型专属 role marker 表。
```text
messages before assistant + generation prompt → prompt ids
messages including assistant response → full ids
full ids 中超出 prompt 的尾部 → supervised span
```
这种方式兼容模型在历史消息中处理 reasoning 内容的差异。
该方法要求 prompt 的 token 序列与完整序列的前缀完全一致。Renderer 会实际比较两次编码的结果;如果模板在追加最后一轮后改写了前面的内容,会抛出错误,避免把上下文 token 当作回复标签。
前缀部分的 `labels` 为 `IGNORE_INDEX`、`loss_weights` 为零;最后回复按该轮的监督权重设置 `loss_weights`,有监督的位置使用实际 token ID 作为 label。生成模式则添加 generation prompt,并把所有位置设为不参与监督。
## 处理多模态内容
Renderer 先把标准内容块转换为 Hugging Face 消息结构,再通过 chat template 生成文本。存在媒体内容时,processor 接收文本与图片、视频或解码后的音频,并返回 token 与媒体特征。Renderer 把这些特征和媒体位置标记一并传给批处理,保持文本与媒体对应。
## 拼接 Chosen/Rejected 序列
chosen 与 rejected 分别渲染后拼接,并使用 `token_type_ids` 标记两个文档。DPO/RM 根据标记构造 block-diagonal attention 和各自 position ids,避免 rejected 序列读取 chosen 序列。
Renderer 为两段序列分别从起点生成 position IDs,并用 `token_type_ids` 的 `1`、`2` 保留段边界。物理拼接只改变存储布局;两段仍是独立回答,任务 Trainer 需要保留这个隔离语义。
## 转义特殊 Token
`core/rendering/escape.py` 在渲染用户控制的文本和 tools 前处理中和 tokenizer 的特殊 token 字符串,避免输入直接注入特殊 token。
## 自定义 Chat Template
新模型通常应在模型仓库的 tokenizer 配置中提供标准 HF chat template。仅在运行时覆盖时使用 `custom_chat_template`,无需注册 Python 插件。
若原始数据字段需要变化,扩展 converter;若标准消息如何映射到模型输入发生变化,再检查 `rendering/format.py` 和 `rendering/rendering.py`。转换原始字段与生成监督标签分属不同阶段,避免在 converter 中依赖模型的 token ID。