强化学习微调(RLFT)把过去需要训练集群和模型内部访问权限的后训练流程封装成托管服务:开发者提供提示词数据与奖励函数,平台负责生成候选答案、计算奖励并更新 Gemini。它最适合一类特殊任务——理想答案很难批量编写,但结果是否正确却容易验证。
RLFT 改变的不是数据量,而是学习信号
监督微调(SFT)要求模型模仿标注答案;RLFT 则让模型探索多个候选输出,再根据奖励提高优秀答案出现的概率,同时尽量保持接近原始模型。
一次训练迭代可以简化为:
- 从训练集读取一个提示词。
- Gemini 生成多个候选回答。
- 奖励函数分别给候选回答打分。
- 训练过程提高高分回答的生成概率。
- 在独立验证集上检查奖励是否真正提升。
这种机制带来三个重要边界:
- 它优化模型已经偶尔能做到的事。 如果基础模型从未成功过,奖励函数就没有正样本可供区分。
- 它奖励结果,不限定路径。 SQL、API 调用或游戏对话可以有多种正确写法,不必强迫模型模仿唯一参考答案。
- 它不天然保证奖励代表真实质量。 奖励函数写错后,模型可能非常高效地利用漏洞,而不是完成业务目标。
因此,RLFT 的核心工作不是调整强化学习算法,而是把业务规范写成一个可信、稳定且难以投机的评分程序。
什么时候值得从 Prompt 和 SFT 升级
不要因为 RLFT 更高级就直接使用它。提示词工程和 SFT 仍然覆盖大多数定制需求,可以按下面的顺序判断:
直接使用 RLFT
当基础 Gemini 已经能偶尔给出正确结果,而且结果可以自动验证时,可以直接开展 RLFT。例如:
- SQL 能否执行,并返回预期结果;
- JSON 是否符合 Schema,字段是否能在原文中找到依据;
- API 调用是否满足参数约束并通过测试;
- 游戏 NPC 是否保持角色设定、语言和状态语法;
- HTML 幻灯片渲染后是否存在溢出或元素缺失。
先 SFT,再 RLFT
如果基础模型的成功率太低,训练初期几乎所有候选答案都会得到同样的低分,RL 就难以获得有效方向。这时可以先做一次轻量 SFT,让模型学会基本格式或任务步骤,再通过连续调优从 SFT 检查点启动 RLFT。
SFT 不宜做得过重。过度拟合少量演示答案可能压缩模型的探索空间,让后续 RLFT 难以找到其他同样有效的解法。
暂时不使用 RLFT
以下情况应优先改造任务,而不是直接训练:
- 无法稳定判断回答好坏;
- 奖励主要依赖主观偏好,却没有清晰评分量表;
- 基础模型完全不具备目标能力;
- 验证程序昂贵、波动大或存在安全风险;
- Prompt 或检索增强已经能够达到目标。
奖励函数才是真正的产品规格
好的奖励需要同时满足三个条件:与人工判断相关、遇到异常输出时不会崩溃、难以通过投机获得高分。
奖励设计可以分层处理:
总奖励 = 任务正确性
+ 格式有效性
+ 约束满足度
- 冗余长度惩罚
- 安全违规惩罚
- 退化输出惩罚
不要只依赖一个宽泛的 LLM 评委。能使用确定性验证时,应优先使用 Schema 校验、编译器、测试用例、数据库执行结果或规则引擎。主观维度确实无法程序化时,可以使用多个评委或多个评分维度,并持续抽样进行人工复核。
奖励函数还必须对坏输入“正常失败”:JSON 解析失败、SQL 超时、字段缺失或评委调用异常时,应返回明确的低分,而不是让整个训练任务崩溃。
可运行示例:为 SQL 输出编写离线奖励原型
下面是一个可以直接运行的本地示例。它创建临时 SQLite 数据库,执行模型生成的查询,并根据结果是否正确返回奖励。
这只是离线奖励原型,用于验证评分逻辑,不代表托管 RLFT 服务的正式接口。生产环境必须使用独立沙箱、资源限制、只读凭据和网络隔离。
将以下内容保存为 reward_sql.py:
from __future__ import annotations
import json
import sqlite3
import sys
EXPECTED_ROWS = [('APAC', 180)]
DEFAULT_SQL = '''
SELECT region, SUM(amount) AS total
FROM orders
WHERE region = 'APAC'
GROUP BY region
'''
def normalize(rows: list[tuple]) -> list[tuple]:
# 这个任务不关心行顺序;如果业务语义要求顺序,请不要排序。
return sorted(rows, key=repr)
def score_sql(candidate: str) -> dict:
sql = candidate.strip().rstrip(';').strip()
# 这里只允许单条 SELECT 或 WITH 查询。
if not sql.lower().startswith(('select ', 'with ')) or ';' in sql:
return {'reward': -1.0, 'reason': 'only one read-only query is allowed'}
conn = sqlite3.connect(':memory:')
conn.executescript('''
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
region TEXT NOT NULL,
amount INTEGER NOT NULL
);
INSERT INTO orders(region, amount) VALUES
('APAC', 120),
('EMEA', 90),
('APAC', 60);
''')
# 防止候选查询长时间运行。
callbacks = 0
def stop_long_query() -> int:
nonlocal callbacks
callbacks += 1
return 1 if callbacks > 10000 else 0
conn.set_progress_handler(stop_long_query, 1000)
try:
rows = conn.execute(sql).fetchall()
except sqlite3.Error as exc:
return {'reward': -0.8, 'reason': f'execution failed: {exc}'}
finally:
conn.close()
if normalize(rows) == normalize(EXPECTED_ROWS):
return {'reward': 1.0, 'reason': 'correct result'}
return {
'reward': 0.1,
'reason': 'query executed but returned the wrong result',
'rows': rows,
}
if __name__ == '__main__':
candidate_sql = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_SQL
print(json.dumps(score_sql(candidate_sql), ensure_ascii=False, indent=2))
运行默认的正确查询:
python reward_sql.py
也可以测试一个可执行但结果错误的候选答案:
python reward_sql.py "SELECT region, SUM(amount) FROM orders GROUP BY region"
正式训练时,每个提示词应绑定自己的数据库快照、隐藏测试条件或结果不变量。不要把完整测试答案暴露在提示词中,否则模型可能绕过任务、直接迎合评分器。对于任意代码执行,还要增加进程级隔离、CPU 和内存限制、调用超时、文件系统限制以及默认关闭的网络访问。
从小规模实验到可上线模型
第一次运行不需要庞大数据集。一组覆盖主要场景和长尾错误的多样化提示词,加上严格隔离的验证集,就足以判断训练循环是否收敛。
建议按以下清单推进:
- 明确线上真正关心的指标,而不是只追求训练奖励;
- 检查基础模型是否已经存在一定比例的成功样本;
- 离线运行奖励函数,覆盖空输出、超长输出、格式错误和恶意输入;
- 为奖励投机设计对抗样例;
- 严格分离训练集和验证集,避免污染掩盖过拟合;
- 同时观察训练奖励与验证奖励,不只看单条曲线;
- 在验证奖励趋于饱和时选择检查点,而不是机械采用最后一步;
- 上线后保留人工抽检和回滚能力。
RLFT 的价值不在于替代 Prompt 或 SFT,而在于把“可验证的业务结果”直接变成学习信号。只要基础模型已经偶尔成功,并且奖励函数真的代表业务目标,RLFT 就有机会把偶发成功变成稳定行为;如果验证标准本身含糊,再强的训练基础设施也只会更快地优化错误目标。