> ## Documentation Index
> Fetch the complete documentation index at: https://starforge.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 从训练代码回传

> 运行中的作业把曲线、样本、日志和产物送进控制台所讲的那份契约。

```python theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
from starforge.report import init, log, finish

init(hparams={"lr": 1e-6, "kl_coef": 0.05})
log({"loss": 0.42, "reward": 0.71}, step=120)
finish()
```

对绝大多数人来说这就够了——[Python SDK](/zh-Hans/api-reference/python-sdk) 已经替你讲了这份契约。
只有在给 catalog 之外的框架写适配器、或者要用别的语言写客户端时，才需要往下读。

## 平台注入了什么

每个训练容器里都会出现四个环境变量。你的代码读它们；它拿不到账号凭据、集群地址或对象存储密钥。

| 变量                   | 含义                                                                        |
| -------------------- | ------------------------------------------------------------------------- |
| `STARFORGE_ENABLED`  | 回传已接通时为 `1`，作业被有意设为离线时为 `0`。其他取值（包括未设置）都是配置错误，应当直接抛错，而不是静默跳过              |
| `STARFORGE_ENDPOINT` | ingest 路由的基础 URL，已经包含 `/api/ingest`                                       |
| `STARFORGE_RUN_ID`   | 这些回传属于哪次 run。每个请求体都要重复它                                                   |
| `STARFORGE_TOKEN`    | 绑定这次 run 的 [ingest token](/zh-Hans/api-reference/authentication)，有效期 30 天 |

用 `X-StarForge-Token: <token>` 认证。这些路由同时也接受 `Authorization: Bearer <token>`，
已经有 bearer 封装的客户端可以直接复用。

## 接口

全部是 `POST`，全部收 JSON，全部要带 `run_id`。

<AccordionGroup>
  <Accordion title="POST /api/ingest/lifecycle —— 声明训练真正开始的时刻" icon="play">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    { "run_id": "run-4f2a91", "event": "running", "ts": "2026-08-31T09:14:22+00:00" }
    ```

    事件取值：`starting`、`running`、`succeeded`、`failed`。

    在把控制权交给训练 entrypoint 之前的那一刻打 `running`。执行器自己报的 RUNNING 要早得多——
    它在建虚拟环境、拉权重之前就触发了，这两件事可能耗掉好几分钟——拿它当计量起点会系统性地多算。
  </Accordion>

  <Accordion title="POST /api/ingest/metrics —— 曲线" icon="chart-line">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    {
      "run_id": "run-4f2a91",
      "points": [
        { "key": "train/loss", "step": 120, "value": 0.42, "ts": "2026-08-31T09:14:22+00:00" },
        { "key": "train/reward", "step": 120, "value": 0.71, "ts": "2026-08-31T09:14:22+00:00" }
      ]
    }
    ```

    返回 `{"ok": true, "inserted": 2}`。数据点必须已经摊平：一个 key 一个 step 对应一个标量。
    嵌套字典和非标量值由调用方自己先归约。
  </Accordion>

  <Accordion title="POST /api/ingest/hparams —— 配置面板" icon="sliders">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    { "run_id": "run-4f2a91", "params": { "policy.optimizer.kwargs.lr": 1e-6, "grpo.kl_coef": 0.05 } }
    ```

    是 upsert，再调一次补新 key 即可。嵌套配置请用点号摊平。
  </Accordion>

  <Accordion title="POST /api/ingest/validation —— 样本对话与奖励分布" icon="list-checks">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    {
      "run_id": "run-4f2a91",
      "step": 120,
      "samples": [
        {
          "user": "A train leaves Chicago at 3pm...",
          "assistant": "Let me work through this step by step...",
          "reward": 0.83,
          "extra": { "reference": "4 hours" }
        }
      ],
      "avg_reward": 0.71,
      "accuracy": 0.64,
      "chunk_index": 0,
      "total_chunks": 1
    }
    ```

    `user`、`assistant`、`env`、`reward` 是控制台渲染的骨架。算法特有的字段——DPO 的 rejected、
    SFT 的参考答案——放进每条样本的 `extra`，平台不需要理解它们就能展示。

    验证轮次很大时可以分片：设置 `total_chunks`，每片带自己的 `chunk_index` 分别发送。
  </Accordion>

  <Accordion title="POST /api/ingest/logs —— stdout 和 stderr" icon="scroll-text">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    { "run_id": "run-4f2a91", "chunks": ["step 120 | loss 0.42\n"], "eof": false }
    ```

    这是唯一一条要求调用方自己吞掉异常的通道。其他 ingest 接口都是失败即抛，因为丢一个指标或一个产物
    是数据事故；丢几段日志不是，而控制台抖一下就把一次成功的训练判成失败，代价大得多。

    不要自己发 `eof`。作业进入终态时由平台统一补——容器被 SIGKILL 时，转发进程根本没有机会发。
  </Accordion>

  <Accordion title="POST /api/ingest/artifact —— 登记产出" icon="package">
    ```json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    {
      "run_id": "run-4f2a91",
      "kind": "checkpoint",
      "path": "s3://starforge/runs/run-4f2a91/checkpoints/step-500",
      "format": "distcp",
      "step": 500,
      "size_bytes": 14203847362
    }
    ```

    `kind` 取值：`checkpoint`、`hf_export`、`eval_report`、`merged_model`。`format` 必填且不得为空。

    容器类执行器下，`path` **必须**是对象存储 URI。容器一销毁本地路径就不存在了，平台不会去猜它去了哪。
    先用 `GET /api/ingest/artifact/upload-url` 拿一个签名 URL 上传。
  </Accordion>

  <Accordion title="POST /api/ingest/hardware 和 /environment —— 系统页" icon="server">
    `hardware` 收采样点（GPU 利用率、显存、网络）。`environment` 收一次性的静态信息：包版本、CUDA 版本、
    GPU 型号。`environment/nodes` 上报多节点作业的每节点硬件。

    除非你传了 `monitor_hardware=False`，否则这三样 SDK 都会替你采集和发送。
  </Accordion>

  <Accordion title="POST /api/ingest/benchmark —— 外部产生的评测分数" icon="gauge">
    给在平台之外打分、再把结果报回来的 harness 用。围绕它的完整流程见[评测](/zh-Hans/guides/benchmarks)。
  </Accordion>
</AccordionGroup>

## 确认成功

在控制台打开这个作业。第一次调用 `metrics` 之后几秒内，Charts 页就会出现一个点。

如果日志在滚而曲线一直是空的，说明回传调用根本没执行——确认容器内 `STARFORGE_ENABLED` 是 `1`，
并且代码确实走到了 `init()`。

<Accordion title="为什么回传代码放在 SDK 里，而不是用户上传的工作目录里">
  生命周期打点和产物登记，是平台判断作业何时真正开始、产出了什么的依据。如果这段代码放在用户自己的
  `common/` 里，删掉或改坏那个目录就会让平台变瞎，而动手的人还不会知道。这也是这个模块只依赖标准库的原因：
  它必须能在任何训练镜像里干净地 import，而每多一个依赖，就多一处「镜像里恰好没装」的失败点。
</Accordion>
