跳转至

Rendering

Rendering 把一组 message 转换成模型能消费的 token 序列。它和 HuggingFace 的 chat template 做的事类似,但 Mint Recursive 的 renderer 覆盖完整的训练生命周期:监督学习、强化学习和部署。renderer 处于两层之间——上层是你组织好的对话数据,底层是模型实际看到的 token。

Concept

renderer 在两种数据之间转换:一边是结构化的 message 字典(带 user、assistant、system 等 role),另一边是扁平的 token 序列。SFT 阶段,用 build_supervised_example() 把 prompt token(不计入 loss)和 completion token(模型要学的部分)分开。RL 和采样阶段,用 build_generation_prompt() 构造 prompt,再用 parse_response() 把模型采样出来的 token 解码回 message。

renderer 按模型家族区分:Qwen3、Llama 3、DeepSeek V3 等各自有专属的特殊 token 和格式约定。Mint Recursive 自带主要家族的 renderer,每一个都会返回正确的 chat template:

Messages(list of dict)--> Renderer --> Token IDs(list of int)
                                         + loss masks(用于 SFT)
                                         + stop tokens(用于采样)

Pattern

下面是渲染一段对话的标准写法:

from mint import types

# 初始化 renderer 和 tokenizer
service_client = mint.ServiceClient()
training_client = service_client.create_lora_training_client(
    base_model="Qwen/Qwen3-0.6B",
    rank=16,
)
tokenizer = training_client.get_tokenizer()
renderer = recipe.renderers.get_renderer("qwen3", tokenizer)

# 构造对话
messages = [
    {"role": "system", "content": "简洁回答;每条回复最多一句话"},
    {"role": "user", "content": "最长寿的啮齿动物是什么?"},
    {"role": "assistant", "content": "裸鼹鼠,可以活超过 30 年。"},
    {"role": "user", "content": "它们为什么活得这么长?"},
    {
        "role": "assistant",
        "content": "它们进化出了多种保护机制,包括能抑制癌症的特殊透明质酸、稳定性极高的蛋白质,以及高效的 DNA 修复系统。",
    },
]

# SFT:得到 token 和 loss weights
model_input, weights = renderer.build_supervised_example(messages)

# 采样:构造一个让 model 续写的 prompt
prompt = renderer.build_generation_prompt(messages[:-1])  # 不包括最后一条 assistant 消息
stop_sequences = renderer.get_stop_sequences()  # 采样何时停止

# 把采样到的 token 解码回 message
sampled_tokens = [45, 7741, 34651, 31410]  # 实际场景里是 model.sample 的输出
message, success = renderer.parse_response(sampled_tokens)

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

API Surface

方法 用途 返回
build_supervised_example(messages, train_on_what) 把 message 转成 SFT 用的 token + loss weights (ModelInput, weights_array)
build_generation_prompt(messages) 把 message 转成模型续写用的 prompt ModelInput
get_stop_sequences() 表示生成结束的 token list[int \| str]
parse_response(tokens) 把采样到的 token 还原成 message (dict, bool),bool 是成功标志

Renderer 列表(用 recipe.renderers.get_renderer(name, tokenizer) 获取,需先 import mint.recipe as recipe):

  • "qwen3" — Qwen3(默认启用 thinking)
  • "qwen3_disable_thinking" — Qwen3(关闭 thinking)
  • "llama3" — Llama 3
  • "deepseekv3" — DeepSeek V3(non-thinking)
  • "deepseekv3_thinking" — DeepSeek V3(thinking 模式)
  • "nemotron3" — NVIDIA Nemotron 3
  • "kimi_k2" — Kimi K2

Vision renderer(用于 Qwen3-VL 这类 VLM):

from mint.image_processing_utils import get_image_processor

image_processor = get_image_processor("Qwen/Qwen3-VL-235B-A22B-Instruct")
renderer = recipe.renderers.get_renderer("qwen3_vl_instruct", tokenizer, image_processor=image_processor)

Caveats & Pitfalls

  • Renderer 选错家族:调 get_renderer() 时务必传对应模型家族的名字。把 llama3 的 renderer 用在 Qwen3 的 token 上,会拼出错误的特殊 token。
  • Vision 编码:VL 模型同时需要 tokenizer 和 image processor。缺 image processor 会让视觉输入的 shape 对不上。
  • Loss 掩码:build_supervised_example() 默认只在最后一条 assistant 消息上算 loss。要在所有 assistant 回合上训练,传 train_on_what=TrainOnWhat.ALL_ASSISTANT_MESSAGES。
  • Stop token:get_stop_sequences() 返回的 token ID 是模型家族专属的。采样时务必传给 SamplingParams(stop=...)。
  • 自定义格式:清单里没有的模型家族,训练前用 register_renderer() 注册自己的 renderer。不注册的话会悄悄用错特殊 token——不报错,但结果不对。