`common` 通用数据训练方案模块。基于 `lightning.Fabric`，旨在提供一个标准化的 PyTorch 训练流程。

除了少部分（如元学习之外），大部分模型的训练可以直接服用这个方案。

核心是提供两种 API：

1.  **`Trainer` (面向对象)**: 高度封装，适合标准、快速的训练任务。
2.  **`Procedure` (函数式)**: 过程化，提供细粒度控制，适合复杂的、非标准的训练逻辑。

两种模式共享底层的 `callbacks` 和 `default_fabric` 工厂函数。

## 关键组件

-   **`default_fabric` (`__init__.py`)**:
    -   **功能**: 快速创建和配置 `Fabric` 实例的工厂函数。
    -   **作用**: 统一管理 `loggers` 和 `callbacks` 的初始化。避免在每个项目中重复写配置代码。
    -   **默认配置**:
        -   Loggers: `CSVLogger`, `TensorBoardLogger`.
        -   Callbacks: `PrintTrainInfo`, `SaveHparams`, `SaveCheckpoints`, `FabricLogMetrics`, `SaveLoggersFinally`.
    -   **定制**: 通过 `enabled` 字典可以开关各项默认功能。`enabled={'*': False, 'print_info_cb': True}` 可以只保留打印功能。
    -   **注意**: 它只创建实例，**不调用 `fabric.launch()`**。

-   **`callbacks.py`**:
    -   **`FabricCallbackProtocol`**: 定义了所有可用钩子的接口，方便查阅和实现。
    -   **`SaveCheckpoints`**: 核心功能是保存模型。
        -   使用 `fabric.save` 来确保与 FSDP 等策略的兼容性。
        -   `best_checkpoint()` 方法可以方便地获取最佳模型的路径。
    -   **`EarlyStopping`**:
        -   通过检查 `callback.stop_training` 标志来决定是否中断训练循环。
        -   `Trainer` 中已实现该逻辑，`Procedure` 中需要手动在循环里检查。
    -   **`FabricLogMetrics`**: 自动将 `on_..._batch_end` 和 `on_..._epoch_end` 中的 `metrics` 字典记录到 `fabric` 的 loggers 中。
    -   **`ParseCSVLogfile`**: 训练结束后，自动将 `metrics.csv` 转换为更易于分析的 `.xlsx` 文件。

---

## 模式一：`Trainer` (`BasicFabricTrainer`)

### 使用场景

-   标准的 `fit` -> `test` 流程。

### 使用步骤

1.  **准备组件**: `model`, `optimizer`, `fn_loss`, `lr_scheduler` 等。
2.  **创建 `fabric`**: `fabric = default_fabric(...)`。
3.  **实例化 `Trainer`**:
    ```python
    trainer = BasicFabricTrainer(
        fabric,
        model,
        optimizer=optimizer,
        hparams=state('default'),  # 用于保存超参数
        metric_builder=lambda: {'acc': BinaryAccuracy()}, # 指标
        lr_scheduler=lr_scheduler,
    )
    ```
4.  **运行**:
    ```python
    trainer.fit(train_loader, val_loader, max_epochs=...)
    trainer.test(test_loader)
    ```

### 内部机制备忘

-   `__init__` 中会自动检查并添加 `FabricLogMetrics`，防止遗漏。
-   `__init__` 中默认自动调用 `fabric.launch()`，`launch_fabric_internally=True` 参数控制了在 `__init__` 中自动 `launch`


## 模式二：`Procedure` (函数集合)

### 使用场景

-   需要完全控制训练循环，例如 K-Fold 交叉验证。
-   需要在训练循环的中间步骤执行特殊操作（如加载模型、修改数据等）。
-   更喜欢过程式的代码风格。

### 使用步骤

1.  **准备组件**: 同上。
2.  **创建 `fabric`**: `fabric = default_fabric(...)`。
3.  **创建 `FabricTrainingContext`**: 这是一个核心的数据容器，将所有训练相关的对象打包在一起。
    ```python
    # Metric Recorder 是可选的，但推荐使用
    train_metric_recorder = TorchMetricRecorder(...)
    val_metric_recorder = TorchMetricRecorder(...)
    
    context = FabricTrainingContext(
        model, optimizer, fabric, train_metric_recorder, val_metric_recorder
    )
    ```
4.  **启动 `Fabric` (关键步骤!)**:
    ```python
    context.launch()
    ```
    这一步会执行 `fabric.setup()` 和指标的设备转移。
5.  **运行训练循环**:
    -   **方案A：使用封装好的 `train_loop`**:
        ```python
        procedure.train_loop(
            context,
            train_loader=train_loader,
            val_loader=val_loader,
            fn_loss=fn_loss,
            max_epochs=...
        )
        ```
    -   **方案B：手动组合 `epoch` 函数 (更灵活)**:
        ```python
        for epoch in range(max_epochs):
            fabric.call('on_train_epoch_start', ...)
            train_metrics = procedure.train_epoch(context, train_loader, ...)
            fabric.call('on_train_epoch_end', ...)
            
            # 在这里插入自定义逻辑
            
            val_metrics = procedure.val_epoch(context, val_loader, ...)
            fabric.call('on_validation_epoch_end', ...)
        ```
6.  **测试**:
    -   在测试前，可以手动加载最佳模型。
    ```python
    # 找到 SaveCheckpoints 回调
    save_cb = find_callback(fabric, callbacks.SaveCheckpoints)
    if save_cb:
        best_path = save_cb.best_checkpoint()
        load_checkpoint(context.model, best_path, device=fabric.device)
    
    # 用加载了权重的模型进行测试
    scores = procedure.test(context, test_loader, fn_loss)
    ```

### 内部机制备忘

-   `FabricTrainingContext` 在 `__post_init__` 中会检查 `model` 和 `optimizer` 是否已经被 `fabric` setup，防止误操作
-   `training_step` 和 `validation_step` 函数可以通过 `simple_inject` 在 `"train"` namespace 下被覆盖，实现自定义的 step 逻辑而无需修改框架代码。

