> ## 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.

# Recipe 包

> 不发平台版本，就往 catalog 里加一种训练方法。

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf plugin publish ./my-grpo-variant
sf methods                              # 你的方法已经在 catalog 里
sf new my-exp --method nemo-rl/alice.my-grpo
```

recipe 包声明一种后训练方法：跑哪个入口、哪些超参可调、支持哪些框架版本和镜像、
控制台该画哪些指标。它不含任何平台会执行的代码。

## 目录结构

```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
my-grpo-variant/
├── plugin.yaml       # kind: recipe
├── recipe.yaml       # 声明本体
├── README.md
└── template/         # `sf new` 拷进用户仓库的东西
    ├── config.yaml
    └── run.py
```

```yaml plugin.yaml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
schema: forge/plugin/v1
name: my-grpo
version: 1.0.0
kind: recipe
summary: GRPO with a length-normalised advantage
```

<Warning>
  除 `template/` 之外，任何地方出现可执行文件都会被拒。那个目录是交给用户、在**用户**容器里跑的脚手架；
  根层是平台要解析的，不该出现任何让人期待平台去 import 的东西。
</Warning>

## recipe.yaml

```yaml recipe.yaml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
name: my-grpo
version: "0.7.0"
framework: nemo-rl
title: GRPO with length-normalised advantage
summary: >
  Group-relative advantage divided by completion length, which stops long answers
  from dominating the batch on verbose datasets.

runtime:
  default_version: "0.7.0"
  versions:
    "0.7.0":
      requires: []
      runtime_id: "nemo-rl-0.7.0"
      source:
        kind: oci
        reference: "nvcr.io/nvidia/nemo-rl:v0.7.0@sha256:75c4821c…"
        ray_version: "2.55.1"

template: template
config_section: grpo
roles: [actor, reference, rollout]

adapter_options:
  config_mode: dot-overrides
  observability: platform
  ray_cluster_profile: nemo-rl-baseline

entrypoint:
  kind: file
  value: examples/run_grpo.py
  experiment_override: run.py

params:
  num_prompts_per_step:
    type: int
    path: grpo.num_prompts_per_step
    group: Training & validation
    doc: Prompts sampled per step
    default: 32
    min: 1
  length_norm_power:
    type: float
    path: grpo.length_norm_power
    group: Advantage
    doc: Exponent on completion length in the advantage denominator
    default: 1.0
    min: 0.0
    max: 2.0

metrics:
  primary:
    - train/reward
    - validation/accuracy
    - train/kl_penalty
    - train/loss
  aliases:
    train/entropy: [train/approx_entropy]

validation:
  sample_kind: conversation
  reward_semantics: scalar

artifacts:
  version: "forge/artifacts/v1"
  checkpoints: ["checkpoints/step_*", "step_*"]
  logs: ["logs"]
  exports: ["hf_export"]
  formats:
    checkpoint: nemo-checkpoint
    log: text
    hf_export: huggingface
```

### 超参声明

`params:` 下的每一项，都是 `sf validate` 能在你笔记本上（而不是在集群上）拦下拼写错误的依据。

<ParamField path="type" type="int | float | str | bool | enum" required>
  写别的会在加载时被拒，并点名是哪个参数。
</ParamField>

<ParamField path="path" type="dot.path">
  这个值落在训练框架自己配置里的哪个位置。有了它，平台才能把扁平的
  `--set length_norm_power=1.2` 翻译成框架原生的 override。
</ParamField>

<ParamField path="group" type="string">
  控制台和 CLI 里的展示分组，不参与校验。
</ParamField>

<ParamField path="default, min, max, choices, required" type="mixed">
  校验契约。`min`/`max` 默认闭区间，除非设了 `exclusive_minimum`。
</ParamField>

<ParamField path="path_overrides" type="{version: path}">
  上游在不同框架版本之间挪了配置键时用它。把这件事声明在这里，
  而不是在 adapter 代码里写版本分支。

  ```yaml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
  warmup_ratio:
    type: float
    path: optim.warmup_steps_ratio
    path_overrides:
      "0.9.0": optim.lr_warmup_steps_ratio
  ```
</ParamField>

### 指标契约

`metrics.primary` 是有序列表，前两个进总览图，诊断阈值读的也是同一批 key。
`aliases` 把规范 key 映射到某个框架版本实际发出的名字，这样上游改名不会让一张图变空。

### 双语展示文案

manifest 用英文写，翻译放在一个 `i18n:` 块里。控制台按读者语言取，缺翻译时回退到 manifest 原文。

```yaml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
title: GRPO with length-normalised advantage
summary: >
  Group-relative advantage divided by completion length.
params:
  num_prompts_per_step:
    type: int
    path: grpo.num_prompts_per_step
    group: Training & validation
    doc: Prompts sampled per step
i18n:
  zh:
    title: GRPO · 长度归一化优势
    summary: >
      把组相对优势除以生成长度，避免长回答在 verbose 数据集上主导整个 batch。
    params:
      num_prompts_per_step: {group: 训练与验证, doc: 每步题目数}
```

参数翻译放在 locale 底下，而不是挂在每个参数上。一个 recipe 会声明几十个参数，
每个都挂一个 `i18n:` 会把读者真正来找的契约（path / type / range）埋掉。
给一个没声明过的参数写翻译是错误，所以改名不会留下一条指向空处的翻译。

只有展示文案可翻译。名字、path、指标 key 是契约，任何语言下都一样。

## 命名

已发布方法的 id 永远是 `<framework>/<owner>.<name>`：

```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
nemo-rl/alice.my-grpo
```

多出来的这一段让「和内置方法撞名」在结构上不可能发生——你没法发布一个遮蔽 `nemo-rl/grpo` 的东西。

## 镜像

<Warning>
  recipe 声明作业跑哪个容器镜像。这是声明包唯一能表达的真正危险的事：
  一份纯 YAML 本来可以让集群去拉任意镜像。

  因此镜像必须通过部署的仓库允许列表（`FORGE_ALLOWED_IMAGE_REGISTRIES`）。
  在向用户开放 recipe 发布之前，先把它配好。
</Warning>

## 确认成功

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf recipe sync                          # 拉取 catalog（提交前会自动执行）
sf methods                              # 你的方法出现了，带来源标记
sf methods nemo-rl/alice.my-grpo        # 它可调的参数
sf new probe --method nemo-rl/alice.my-grpo
sf validate probe
```

`sf validate` 不碰集群就能验你的参数声明。如果一个不合理的值在那里也通过了，
说明你的声明缺了范围限制。

## 版本

`recipe.yaml` 里的 `version` 对齐上游框架的发布号。包的内容身份是另一个东西——
覆盖 manifest 加 template 的 bundle digest。所以改一个默认值或修一个模板会更新 digest，
而不必假装框架变了。

实验在 `recipe.lock.json` 里锁住 digest。你发布新版本后，已有实验照常工作，
直到它们的主人执行 `sf recipe upgrade`。
