跳转至

SFT Hyperparameters

这个 recipe 在 Mint Recursive 上跑一轮真实的监督微调 sweep。它用同一个小的 arithmetic chat 数据集,在几组超参数下分别训练,再比较最终的 training loss。

关键点:这个 recipe 不再手写 Datum。它用 recipe.supervised.conversation_to_datum() 和高层的 recipe.supervised.train.main(config) 训练循环。

Use Case

  • Instruction tuning:训练模型按任务指令工作。
  • Domain adaptation:在领域内的问答或聊天样本上微调。
  • 小规模 grid search:在正式训练前比较 learning rate 和 LoRA rank。
  • Recipe 调试:确认 Mint Recursive API key、renderer、tokenizer 和 SFT 训练循环能一起跑通。

Sweep 什么

默认情况下,recipes/sft_hyperparameters.py 会跑:

learning_rate:  [1e-5, 5e-5]
lora_rank:      [8, 16]
max_steps:      每组配置 2 step
configs:        2 x 2 = 4

可以用环境变量覆盖:

MINT_SFT_STEPS=1 \
MINT_SFT_LRS=1e-5,5e-5 \
MINT_LORA_RANKS=8,16 \
python recipes/sft_hyperparameters.py

核心模式

recipe 先在内存里构造一个 supervised dataset:

class ArithmeticSFTDataset(recipe.supervised.types.SupervisedDataset):
    def __init__(self, conversations, model_name, renderer_name, batch_size, max_length):
        tokenizer = get_tokenizer(model_name)
        renderer = recipe.renderers.get_renderer(renderer_name, tokenizer)
        self.datums = [
            recipe.supervised.conversation_to_datum(
                conversation,
                renderer,
                max_length=max_length,
            )
            for conversation in conversations
        ]
        self.batch_size = batch_size

    def __len__(self):
        return max(1, (len(self.datums) + self.batch_size - 1) // self.batch_size)

    def get_batch(self, index):
        start = (index * self.batch_size) % len(self.datums)
        return self.datums[start : start + self.batch_size]

然后每组 grid item 调用标准 SFT trainer:

config = recipe.supervised.train.Config(
    log_path="/tmp/mint-sft-sweep-lr1e-5-rank8",
    model_name="Qwen/Qwen3-0.6B",
    renderer_name="qwen3",
    dataset_builder=ArithmeticSFTDatasetBuilder(...),
    learning_rate=1e-5,
    lora_rank=8,
    max_steps=2,
    save_every=999,
    eval_every=999,
    infrequent_eval_every=999,
    ttl_seconds=3600,
)

await recipe.supervised.train.main(config=config)

完整源码:https://github.com/MindLab-Research/mint-quickstart/blob/main/recipes/sft_hyperparameters.py

运行

export MINT_API_KEY=sk-your-api-key
python recipes/sft_hyperparameters.py

输出的最后会有这样一张表:

=== Grid Search Summary ===
LR           Rank     Steps    Final train NLL    Log path
------------------------------------------------------------------------------------------
1e-05        8        2        10.4975            /tmp/mint-sft-sweep-...-1em05-rank8
1e-05        16       2        10.4919            /tmp/mint-sft-sweep-...-1em05-rank16
5e-05        8        2        10.1574            /tmp/mint-sft-sweep-...-5em05-rank8
5e-05        16       2        9.4928             /tmp/mint-sft-sweep-...-5em05-rank16

Verified Run

已在 Mint Recursive 上验证:Qwen/Qwen3-0.6B,8 条生成的 arithmetic conversations,每组配置 2 steps:

Learning rate LoRA rank Steps Final train NLL
1e-5 8 2 10.4975
1e-5 16 2 10.4919
5e-5 8 2 10.1574
5e-5 16 2 9.4928

Note

这些数字来自用于验证 API 和代码结构的小型 smoke 数据集。调参时,请使用自己的数据集,运行更多训练步,并比较验证指标。

为什么这个形状有效

chat messages
    │
    ▼
conversation_to_datum()
    │  应用 renderer + assistant-token loss mask
    ▼
SupervisedDataset.get_batch()
    │
    ▼
recipe.supervised.train.main(config)
    │  创建 LoRA TrainingClient,执行 forward/backward + optimizer steps
    ▼
metrics.jsonl + final checkpoint

这样 tokenization 和 loss mask 就和 Mint Recursive 用的 renderer 对齐了。它跑的也是更大的 supervised recipe 所用的同一套高层训练循环,所以这个 sweep 验证的是用户会复制的真实路径。