[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,64 @@
# 整体架构
v1 将命令入口、运行流程和可替换实现分开组织。Core 负责连接配置、数据、模型和训练状态,Plugin 为 Core 提供数据加载、模型处理、批处理和分布式训练等具体实现。
这样划分后,更换数据格式不需要改训练循环,更换分布式后端也不需要重新实现 SFT 损失。任务入口负责把组件组装成一次运行,Core 决定何时执行操作,Plugin 提供被选择的具体操作。
下文源码路径均相对于 `src/llamafactory/v1/`。
## 模块分层
| 模块 | 目录 | 职责 |
|------|------|------|
| 命令入口 | `launcher.py` | 路由 `sft`、`dpo`、`rm`、`chat` 和 `merge`,并在多设备训练时通过 `torchrun` 重启 |
| 参数配置 | `config/` | 解析数据、模型、训练和推理参数 |
| 任务入口 | `trainers/`、`samplers/` | 组装 SFT、DPO、RM 或推理流程 |
| Core | `core/` | 管理数据索引、模型加载、样本渲染、批处理、训练循环和推理引擎 |
| Plugin | `plugins/` | 提供数据、模型和训练器相关的可替换实现 |
| 设备抽象 | `accelerator/` | 管理设备、进程组和 DeviceMesh |
## 调用关系
```text
llamafactory-cli <command> config.yaml
→ launcher
→ get_args
→ 任务入口(run_sft / run_dpo / run_rm / run_chat 等)
├── DataEngine
│ ├── DataLoaderPlugin
│ └── DataConverterPlugin
├── ModelEngine
│ ├── InitPlugin
│ ├── QuantizationPlugin
│ ├── PeftPlugin
│ └── KernelPlugin
├── BaseTrainer
│ ├── BatchGenerator → BatchingPlugin
│ ├── DistributedPlugin
│ └── OptimizerPlugin
└── BaseSampler
```
上图表示组件依赖,实际初始化次序由对应任务入口决定。以 `trainers/sft_trainer.py` 中的 `run_sft` 为例:先解析参数并初始化 `DistributedInterface`,再构造 DataEngine 和 ModelEngine,将数据集、模型与 Renderer 传给 SFTTrainer,最后调用 `fit()`、`save_model()` 并销毁分布式环境。SFTTrainer 继承 BaseTrainer 的运行循环,并提供 SFT 的损失计算。
## 数据如何到达训练循环
```text
DataEngine[index] → 标准 Sample(仍是消息结构)
Renderer.process_samples(samples) → ModelInput 列表(已分词,尚未组 batch)
BatchGenerator → 一个更新步所需的 micro-batch 列表
Trainer.compute_loss(batch) → 标量 loss
BaseTrainer.fit() → 反向传播、参数更新与生命周期事件
```
ModelEngine 同时提供模型和与模型匹配的 Renderer,BatchGenerator 持有这个 Renderer。这样,数据源的读取与字段转换可以独立于模型的 tokenizer、chat template 和多模态 processor。
## 状态与实现分别由谁管理
DataEngine 保存数据集和索引,ModelEngine 保存加载结果,BatchGenerator 保存读取进度与缓冲区,BaseTrainer 保存模型、优化器和训练进度。它们都是一次运行中的对象。
插件注册表保存函数或类对象。调用方每次把模型、配置或缓冲区传给插件,并接收处理结果;插件路由对象本身只记录实现名称。例如,分布式插件可以返回有状态的后端 engine,由 BaseTrainer 持有,但注册表本身不保存某次训练的 engine。
判断改动位置时,先区分是在改变任务目标、组件流程,还是替换某个操作:新损失属于 Trainer,统一的数据索引逻辑属于 DataEngine,新的原始字段转换属于 DataConverterPlugin。具体扩展边界分别在下方页面说明。
Core 各组件见[Core(核心模块)](core/index.md),插件的注册方式见[插件注册机制](baseplugin_mechanism.md),内置实现见[插件实现](plugins/index.md)。

View File

@@ -0,0 +1,132 @@
# 插件注册机制
`BasePlugin` 位于 `utils/plugin.py`,负责按名称注册和查找实现。每个 `BasePlugin` 子类表示一个独立的插件类型,并拥有自己的 `_registry`,例如 `OptimizerPlugin` 和 `DistributedPlugin` 的注册项互不共享。
## 插件类型、路由对象与实现
以 `DistributedPlugin("fsdp2").shard_model(...)` 为例,这里有三个不同对象:
| 对象 | 职责 |
|------|------|
| `DistributedPlugin` | 插件类型,持有这个类型的名称注册表 |
| `DistributedPlugin("fsdp2")` | 路由对象,仅保存要查找的名称 |
| 注册在 `fsdp2` 下的实现类 | 提供 `shard_model` 等实际操作 |
创建路由对象只保存名称,不会立即加载或创建后端 engine。第一次调用函数或访问实现方法时,`_resolve` 才从该类型的注册表中查找对象。模型、优化器、buffer 等运行状态由调用方持有并作为参数传入。
## 注册与调用流程
```text
定义插件类型
→ PluginType(BasePlugin)
→ PluginType("name").register()
→ 将函数或类对象写入 PluginType._registry
→ PluginType("name") 按名称查找实现
→ 通过 __call__ 或 __getattr__ 调用
```
装饰器在模块导入时完成注册。注册名称必须存在;查找未注册的名称时会抛出 `ValueError`。同一插件类型重复注册相同名称时会记录警告,并使用后注册的实现。
注册表不会自动扫描文件或按 YAML 名称导入 Python 模块。内置实现通过其入口模块的正常导入完成注册,例如 Kernel 入口显式导入各实现。新增实现也必须在被调用前执行所在模块的导入,仅添加文件或写入配置名称不会触发注册。
## 函数实现
一个插件名称只对应一个操作时,直接注册函数。调用 `PluginType("name")(...)` 时,`BasePlugin.__call__` 查找并调用该函数。
```python
class OptimizerPlugin(BasePlugin):
pass
@OptimizerPlugin("example").register()
def create_optimizer(model, config):
return ExampleOptimizer(model.parameters(), lr=config["learning_rate"])
optimizer = OptimizerPlugin("example")(model, config)
```
数据加载、数据转换、模型初始化、PEFT 和优化器等插件使用这种形式。插件类型也可以提供语义化方法,在方法内部调用 `super().__call__`,例如 `DataLoaderPlugin.load(...)`。
## 类实现(静态方法组)
同一插件名称需要提供多个相关操作时,注册一个类对象。`BasePlugin.__getattr__` 先查找该类,再把方法访问转发给它。
```python
@DistributedPlugin("example").register()
class ExampleDistributed(BaseDistributed):
@staticmethod
def shard_model(model, dist_config, **kwargs):
...
@staticmethod
def save_checkpoint(model, optimizer, checkpoint_dir, **kwargs):
...
model = DistributedPlugin("example").shard_model(model, dist_config)
DistributedPlugin("example").save_checkpoint(model, optimizer, checkpoint_dir)
```
通过 `.method(...)` 访问时,路由会取出注册类上的方法,不创建该类的实例。因此,这类实现使用 `staticmethod` 或 `classmethod`,不在 `self` 中保存运行状态:
| 方法形式 | 调用时接收 | 用途 |
|----------|------------|------|
| `staticmethod` | 只接收显式参数 | 实现不依赖类本身的独立操作,例如保存 checkpoint |
| `classmethod` | 首个参数为 `cls` | 需要调用子类方法的公共流程,例如 `BaseKernel.apply` |
| 普通实例方法 | 首个参数为 `self` | 需要先创建实例,不适用于当前类对象路由 |
Python 没有单独的“静态类”类型;这里使用的是包含静态方法或类方法的普通类。分布式后端、批处理策略和 Kernel 使用这种方法组形式。对应抽象基类声明的也是静态方法或类方法,`ensure_methods_implemented` 在具体子类定义时检查这些方法是否完整。
## 两种实现形式的选择
| 实现形式 | 注册对象 | 调用入口 | 适用情况 |
|----------|----------|----------|----------|
| 函数 | 函数对象 | `PluginType("name")(...)` | 一个名称对应一个操作 |
| 类方法组 | 类对象 | `PluginType("name").method(...)` | 一个名称需要提供多个相关操作 |
两种形式使用相同的名称注册表。区别只在于注册对象和调用方式,不影响配置文件通过 `name` 选择实现。
## 参数解析
注册过程只完成名称到实现的映射,不会自动解析参数。参数配置经过两个不同阶段:
1. `config/arg_utils.py` 的 `get_plugin_config` 将顶层参数中的插件配置包装成 PluginConfig,并检查是否有 `name`。这时并未根据名称验证插件专属字段。
2. 调用进入具体实现后,由实现解析自己的字段。PEFT 和分布式入口使用参数 dataclass 与 `parse_params`;Muon 等实现直接读取配置。插件类型本身没有为所有实现统一选择参数类。
使用参数 dataclass 的入口可以显式调用 `parse_params(config, ParamsClass)`:
```python
from dataclasses import dataclass
from typing import Literal
class ExamplePlugin(BasePlugin):
pass
@dataclass
class ExampleParams:
name: Literal["example"] = "example"
enabled: bool = True
@ExamplePlugin("example").register()
def apply_example(model, config):
params = ExamplePlugin.parse_params(config, ExampleParams)
if params.enabled:
...
```
`parse_params` 的处理规则如下:
- `config` 为目标 dataclass 实例时直接返回;
- `config` 为字典时检查字段名,再创建参数实例;
- `config` 为 `None` 时使用 dataclass 默认值创建实例;
- 未声明的字段会抛出 `ValueError`;
- 参数类不是 dataclass,或配置不是字典、`None`、目标 dataclass 实例时,会抛出 `TypeError`;
- 取值范围和字段组合可以在参数 dataclass 的 `__post_init__` 中继续校验。
这里的严格检查主要针对未知字段和配置对象的形式;Python dataclass 不会自动强制执行每个类型注解或 `Literal` 的可选值,额外约束需要实现显式校验。
参数 dataclass 由具体插件入口选择,因此不同实现可以使用不同字段。用户可配置字段记录在对应的[参数说明](../configuration/index.md)中。

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。

View File

@@ -0,0 +1,20 @@
# 开发者指南
开发者指南说明 v1 的运行路径和扩展点。v1 由 Core(核心模块)组织主要运行流程,并通过 Plugin(插件系统)提供可替换实现。
## 架构
| 页面 | 内容 |
|------|------|
| [整体架构](architecture_overview.md) | 模块分层以及 Core 与 Plugin 的调用关系 |
## Core(核心模块)
[Core(核心模块)](core/index.md)说明数据加载、模型加载、样本渲染、批处理和训练循环。
## Plugin(插件系统)
| 页面 | 内容 |
|------|------|
| [插件注册机制](baseplugin_mechanism.md) | `BasePlugin` 的注册、路由和参数解析 |
| [插件实现](plugins/index.md) | 数据、模型和训练器插件,以及融合算子加速 |

View File

@@ -0,0 +1,57 @@
# 数据插件
数据插件把数据来源和原始字段格式从 DataEngine 的索引逻辑中分离。Loader 负责获得可读取的数据集,Converter 负责解释其中的一条记录;两者由不同配置字段选择,可以组合使用。
## 与 DataEngine 的交接
```text
DatasetInfo.source
→ hf_hub:DataEngine 直接调用 datasets.load_dataset
→ 其他来源:DataLoaderPlugin(source).load(dataset_info)
→ Dataset
Dataset 中的一条原始记录 + DatasetInfo.converter
→ DataConverterPlugin(converter)(raw_sample)
→ 标准 SFTSample / DPOSample
```
相同的本地 loader 可以配合 `alpaca` 或 `sharegpt` converter;数据已经是标准消息结构时省略 converter。分词与标签生成发生在后续 Renderer 中。
## DataLoaderPlugin
接口定义在 `plugins/data_plugins/loader.py`。DataEngine 调用 `DataLoaderPlugin(source).load(dataset_info)`,而 `load` 会从 DatasetInfo 中提取 `path`、`split` 和 `streaming`,再将这三个位置参数传给注册函数。
因此,注册函数接收的是以下参数,不是整个 DatasetInfo:
```python
@DataLoaderPlugin("example").register()
def load_example(path, split, streaming):
...
```
当前注册的 `local` 根据文件扩展名选择 Hugging Face dataset builder,再加载文件或目录。Hub 数据由 DataEngine 直接分派,当前没有通过一个名为 `hf_hub` 的 loader 注册项加载。
返回的数据集由 DataEngine 持有并用于建立索引。新增 loader 必须保持调用参数与返回数据集的约定;当前训练链路要求可按索引读取的数据集,具体 streaming 边界见 [DataEngine](../core/data_engine.md#处理-streaming-dataset)。
## DataConverterPlugin
接口定义在 `plugins/data_plugins/converter.py`。Converter 接收单条原始样本字典,返回一个 v1 `SFTSample` 或 `DPOSample`,不是输入或返回整个 batch。当前注册:
- `alpaca`
- `sharegpt`
- `pair`
```python
@DataConverterPlugin("example").register()
def convert_example(raw_sample):
return {"messages": ...}
```
返回字段必须符合 `utils/types.py` 中的 Messages 类型。SFT 返回 `messages`,偏好数据返回 `chosen_messages` 和 `rejected_messages`;图片等媒体也在这一阶段转为标准内容块,实际媒体特征由 Renderer 的 processor 生成。
DataEngine 在建立多轮索引和实际取样时都会调用 converter。它应稳定地转换一条记录,避免在函数中随机选择对话轮次或维护读取进度。轮次展开、采样规模和读取顺序分别由 DataEngine 与 sampler 管理。
## 调整数据索引
`adjust_data_index` 根据 `size`、`weight` 调整某个数据集的索引,`select_data_sample` 处理索引选择。两者是 `loader.py` 中的普通函数,不通过插件名称注册或路由。改变来源、格式时扩展 loader/converter;改变共用索引语义时,应检查这些函数及 DataEngine 的调用位置。
注册在模块导入时生效,机制见[插件注册机制](../baseplugin_mechanism.md)。使用配置见[数据准备](../../feature-guide/data_preparation.md)。

View File

@@ -0,0 +1,22 @@
# 插件实现
v1 按数据、模型和训练流程组织内置插件。配置文件通过实现名称选择插件,新增实现时使用对应的插件类注册。
| 分类 | 内容 |
|------|------|
| [数据插件](data_plugins.md) | DataLoader 与 DataConverter |
| [模型插件](model_plugins.md) | 初始化、PEFT、量化、Kernel 和 Sequence Parallel |
| [训练器插件](trainer_plugins.md) | 分布式后端、批处理和优化器 |
| [融合算子加速](kernel-acceleration/overview.md) | Kernel 选择与调用流程 |
插件注册和参数解析的通用机制见[插件注册机制](../baseplugin_mechanism.md)。
```{toctree}
:maxdepth: 2
:hidden:
data_plugins
model_plugins
trainer_plugins
kernel-acceleration/overview
```

View File

@@ -0,0 +1,48 @@
# 融合算子加速
Kernel 系统在模型加载后应用融合算子加速。一个实现既可以替换单个算子,也可以组合多个融合操作或接入外部加速库。
入口位于 `plugins/model_plugins/kernels/interface.py`。它负责名称选择与调用顺序,`base.py` 中的 BaseKernel 负责执行公共检查,各实现的 `_apply` 负责识别模型并进行具体替换。注册机制、检查流程和模型适配由这三层分别承担。
## Kernel 应用流程
```text
ModelEngine
→ apply_kernels(model, kernel_config)
→ 解析 kernel_config.name
→ auto 设备选择或 KernelPlugin(name)
→ BaseKernel.apply()
→ check_device()
→ check_deps()
→ _apply()
```
接口模块显式导入内置实现,使装饰器在调用前完成注册。`apply_kernels` 将名称按逗号拆分,逐个调用,并将返回模型传给下一个实现;每个实现收到同一份配置与 `require_logits` 等上下文。
`auto` 是入口处的特殊分派:根据设备查询 `_AUTO_KERNELS`,再调用其中的已注册实现。它不是自动搜索最快实现的算法;当前映射只包含 NPU,其他设备不会因 `auto` 而执行替换。
## BaseKernel 与具体实现如何协作
注册表保存的是实现类。`KernelPlugin(name).apply(...)` 将方法调用转发到该类,继承的 `BaseKernel.apply` 再使用 `cls` 调用该实现的检查和替换方法。
`check_device` 检查设备,`check_deps` 检查可选依赖,随后公共入口确认存在模型对象,最后调用 `_apply`。模型是否受支持、需要替换哪个模块,由具体 `_apply` 决定。BaseKernel 不统一检测所有模型结构。
例如,CUDA Fused MoE 按模型架构和模块类名寻找目标并替换 forward;Liger 根据 model type 调用对应外部适配函数;FLA 通过算子注册表匹配模型属性。成功找到注册名称,只说明能进入实现,不能保证模型中存在可替换目标。
## 组合与扩展边界
组合时,后一个实现看到的是已经修改过的模型。入口不提供冲突检测、模型快照或失败回滚,也不会把各实现的专属配置拆成独立配置块。新增实现需要清楚界定自己修改哪些模块,并返回后续调用所需的模型对象。
实现类继承 BaseKernel,提供 `check_device` 和 `_apply`,需要额外依赖时覆盖 `check_deps`;子类定义时会检查必需方法是否实现。名称注册后,还需要确保实现模块在使用前被导入。通用路由与注册规则见[插件注册机制](../../baseplugin_mechanism.md)。
## 已注册实现
- `liger_kernel`
- `cuda_fused_moe`
- `flash-linear-attention`
- `npu_fused_moe`
- `npu_fused_rmsnorm`
- `npu_fused_rope`
- `npu_fused_swiglu`
用户配置见[融合算子加速](../../../feature-guide/kernel_acceleration.md)。

View File

@@ -0,0 +1,47 @@
# 模型插件
模型插件提供模型加载和处理过程中的可替换操作。初始化、量化、PEFT 和 Kernel 由 ModelEngine 依次调用;Sequence Parallel 需要训练拓扑,因此由 BaseTrainer 在训练初始化阶段调用。
## 每个插件接收和改变什么
| 插件 | 接收 | 返回或修改 |
|------|------|------------|
| InitPlugin | 当前进程的设备与 rank 信息 | 返回用于创建模型的 `torch.device` |
| QuantizationPlugin | 模型加载参数、量化配置与训练标记 | 返回补充量化选项后的加载参数 |
| PeftPlugin | 已构造模型、PEFT 配置与训练标记 | 设置可训练参数、加载或合并 adapter,返回处理后的模型 |
| KernelPlugin | PEFT 处理后的模型与 Kernel 配置 | 替换适配的计算路径,返回处理后的模型 |
它们的接口不同,不能仅通过替换 `name` 在不同插件族之间互换。具体调用次序及原因见 [ModelEngine](../core/model_engine.md)。
## InitPlugin
注册 `init_on_default`、`init_on_meta`、`init_on_rank0`,返回模型创建使用的 `torch.device`。
实现位于 `plugins/model_plugins/initialization.py`。这些函数不加载模型权重;ModelEngine 根据返回设备决定调用 `from_pretrained` 还是在 meta 上使用 `from_config`。Rank 0 加载与其他 rank 的权重同步需要后续分布式路径协作。
## PeftPlugin
注册 `lora` 与 `freeze`。两者分别通过 `LoraParams` 和 `FreezeParams` 严格解析配置。LoRA 还负责 adapter 加载、合并与导出。
实现位于 `plugins/model_plugins/peft.py`。训练时,LoRA 创建或加载 adapter,Freeze 按层和模块选择可训练权重;后续优化器只收集 `requires_grad` 的参数。推理与合并导出也复用 LoRA 插件,因此同一配置入口的行为还取决于调用时的 `is_train`。
## QuantizationPlugin
注册 `auto` 与 `bnb`。插件修改 `from_pretrained` 的 init kwargs,不直接替换已经加载的权重。
实现位于 `plugins/model_plugins/quantization.py`。调用发生在模型创建之前,使量化选项能参与权重加载;这也解释了它与加载完成后执行的 PEFT、Kernel 所处阶段不同。当前可配置字段见[模型参数](../../configuration/model.md#quant_config)。
## KernelPlugin
Kernel 在模型加载和 PEFT 处理后应用。调用流程见[融合算子加速](kernel-acceleration/overview.md)。
## Sequence Parallel Plugins
设置 `TrainingArguments.cp_size > 1` 后,BaseTrainer 使用 `cp_mode` 的值选择 `SequenceParallelModelPlugin`。因此,`cp_mode: ulysses` 会调用 `SequenceParallelModelPlugin("ulysses")` 修改模型 forward 所需的通信;训练循环再调用 `SequenceParallelLossPlugin("sequence_parallel_loss")` 处理 loss 聚合。用户配置见[分布式训练](../../feature-guide/distributed_training.md#ulysses-context-parallel)。
这两个插件共同完成序列并行:模型侧处理 attention 的通信与切分,损失侧处理分布后的输入与监督计算。只替换 forward 而沿用不匹配的损失路径,会破坏这组协作关系。实现位于 `plugins/model_plugins/parallelization/`。
## Chat Template 迁移
旧 `RenderingPlugin` 和 `plugins/model_plugins/templates/` 已删除。Chat
template 统一由 `core/rendering/` 调用 Hugging Face 模板。

View File

@@ -0,0 +1,51 @@
# 训练器插件
训练器插件替换训练过程中的具体操作。BaseTrainer 持有模型、优化器和训练计数,BatchGenerator 持有读取进度与缓冲区;插件通过调用参数访问这些对象。替换实现时需要维持调用方依赖的输入、输出和状态约定。
## DistributedPlugin
当前注册 `fsdp2`、`fsdpturbo` 和 `deepspeed`。每个实现类提供统一的模型切分、保存和 checkpoint 方法组:
- `shard_model`
- `save_model`
- `save_checkpoint`
- `load_checkpoint`
参数分别由 `FSDP2Params`、`FSDPTurboParams` 和 `DeepSpeedParams` 解析。FSDPTurbo 额外实现跨专家并行 Mesh 的梯度裁剪。公共 DeviceMesh 拓扑由 `TrainingArguments` 和 `DistributedInterface` 管理。
注册和入口位于 `plugins/trainer_plugins/distributed/interface.py`,具体引擎位于同目录下的 `fsdp2.py`、`fsdpturbo.py` 和 `deepspeed.py`。路由层把配置转换为对应 Params,再委托后端执行;参数配置本身不负责创建进程组。
FSDP2 / FSDPTurbo 的 `shard_model` 返回处理后的模型,BaseTrainer 再创建优化器。DeepSpeed 的该入口返回后端 engine,BaseTrainer 随后调用它的 `prepare`,共同准备模型、优化器和 scheduler。因此,公共入口背后的返回对象与初始化协议仍需结合 BaseTrainer 的后端分支理解。
保存时,Trainer 和 checkpoint 协调器决定时机与通用训练状态,分布式插件负责其模型和优化器状态格式。扩展新后端需要同时提供分片、最终模型保存、checkpoint 保存与恢复;仅实现分片无法覆盖完整训练生命周期。
## BatchingPlugin
`normal` 是 BatchGenerator 默认路径。插件注册:
- `padding_free`
- `dynamic_batching`
- `dynamic_padding_free`
接口与实现位于 `plugins/trainer_plugins/batching.py`。实现类继承 BaseBatcher,提供四个操作:
| 方法 | BatchGenerator 用它决定什么 |
|------|-----------------------------|
| `get_data_provider_batch_size` | 底层 DataLoader 每次读多少条样本 |
| `compute_length` | 批次生成器报告的长度 |
| `fill_buffer` | 何时继续读取样本、怎样填充缓冲区 |
| `generate_batch` | 如何取出样本并组织一个更新步的 micro-batch 列表 |
方法接收 `batch_info`、buffer 或读取函数,状态由调用方管理。`generate_batch` 返回 `None` 时,BatchGenerator 结束本次迭代。新策略需要同时考虑数据耗尽、剩余 buffer 和 checkpoint 恢复,不能只实现拼接张量。
## OptimizerPlugin
当前注册 `muon`。未指定插件时 BaseTrainer 使用默认优化器。Muon 将适合正交化更新的二维权重和其余 AdamW 权重分组。用户配置见[优化器](../../feature-guide/optimizer.md)。
入口位于 `plugins/trainer_plugins/optimizers/optimizer.py`,接收处理后的模型与 `optim_config`,返回 optimizer 实例。当前 Muon 直接读取配置字段;顶层 `learning_rate` 在 TrainingArguments 初始化时写入配置的 `lr`。插件负责参数分组与优化器构造,反向传播和更新时机仍由 Trainer 或 DeepSpeed engine 控制。
## LRSchedulerPlugin
插件族存在,但当前没有注册可选的 scheduler 名称。
接口位于 `plugins/trainer_plugins/lr_scheduler.py`。BaseTrainer 未收到配置时使用固定倍率的 LambdaLR;收到配置时按名称调用插件,传入 optimizer、训练总步数与配置,并保存返回的 scheduler。扩展实现应返回兼容训练循环和 checkpoint 状态保存的 scheduler 对象。