小标题里的“结晶”到底是怎么来的?如果只看表面,很多人会把它理解成“某家大模型公司又刷了一个榜”。但真正值得关注的是训练方式本身:模型不再依赖人工写数据,而是自己出题、自己判分、自己迭代。标题里“两个月狂涨11%”和“反超 Opus 4.8”是结果,而“自己出题自己练”才是方法。这篇文章想重点拆解两件事:一是这种自训练方案为什么能生效,二是如果我们也想在编码任务上复现这套思路,需要准备哪些工程环节。
先说结论:编码是当前最适合做“自我对弈”的任务之一,因为代码正确性可以被编译器、测试用例和运行时结果自动校验。模型生成题目、生成代码、生成测试、再根据测试结果筛选训练数据,整个过程可以不需要人工介入。标题里提到的11%提升,如果数据属实,说明这套数据飞轮在编码领域已经跑通了。接下来的内容会从原理、实验设计反推、最小实现示例、常见坑和工程建议几个角度展开,尽量做到看完可以动手。
1. 这篇文章真正要解决的问题
先抛开“Opus 4.8”这个具体代号。我们真正关心的问题是:当高质量人工标注数据越来越稀缺时,大模型还能靠什么方式继续变强?
传统做法是请人来写题、写答案、写测试用例。但这种方式成本高、周期长,而且数据量很快会到达瓶颈。尤其是代码领域,一个合格的训练样本需要覆盖问题描述、正确解法、边界条件、复杂度要求、多组测试用例,人工构造一套完整数据的时间成本极高。于是出现了另一种思路:让模型自己生成题目,自己写答案,自己构造测试用例,然后通过自动化验证筛掉错误样本,把正确样本重新拿回去训练。这个过程类似 AlphaGo 的自我对弈,只不过棋盘换成了代码任务。
文章要解决的核心问题有四个:
- “自己出题自己练”在技术上是如何闭环的?
- 为什么编码任务比其他任务更适合这种模式?
- 训练过程中哪些环节最容易出问题,如何排查?
- 如果我们要在小规模数据或特定业务场景里复现,最低成本的路径是什么?
从标题看,这套方法在两个月内让编码自测成绩提升了 11%,并反超了一个强基准模型。这意味着它不是简单的“模型把自己生成的答案又背了一遍”,中间一定有数据筛选和难度控制机制。后面我会从实验设计角度做合理推测,并给出一套可操作的最小实现。
2. 基础概念:自训练、合成数据与自我对弈
2.1 什么是自训练(Self-Training)
自训练是一类半监督学习方法的统称。核心逻辑是:先用少量种子数据训练一个初始模型,再用这个模型对大量无标签数据或自行构造的数据进行预测,把置信度高的预测结果当作伪标签加入训练集,不断迭代。
在代码场景下,自训练的表述更直接:模型生成代码,验证工具判断对错,正确的留下来继续训练。这里验证工具可以是编译器、单元测试框架、静态检查工具,也可以是一个更强大的评判模型。
2.2 什么是合成数据(Synthetic Data)
合成数据指不是从真实世界采集,而是通过程序或模型生成的数据。在代码训练里,合成数据的形式包括:
- 自动生成的自然语言问题描述
- 根据问题生成的参考解答
- 配套的测试用例
- 错误解答和对应的修复过程
合成数据的价值在于可以无限扩展,难点在于质量分布难以控制。如果生成的题目太简单,模型学不到新能力;如果太难或测试用例错误,又会把模型带偏。所以“自己出题”不是随便生成一个字符串,而是需要一套约束机制。
2.3 自我对弈(Self-Play)如何映射到代码任务
自我对弈在游戏领域已经验证过:AlphaGo 自己和自己下棋,产生大量对局数据,然后从这些对局中学习。代码任务的“对弈”可以设计为:
- 生成器:根据种子题目或自动主题,生成新的编程问题。
- 解题者:尝试解决这些问题,生成代码。
- 判官:运行编译器和测试用例,判断代码是否通过。
- 迭代器:根据失败案例生成更多相似题目,或者把成功样本加入训练集。
在这个闭环里,“自己出题”是生成器,“自己练”是解题者与判官的循环。模型既是学生,又是老师,还是出卷人。
2.4 容易混淆的概念对比
| 概念 | 核心目的 | 数据来源 | 验证方式 |
|---|---|---|---|
| 预训练 | 学习语言常识和代码语法 | 大规模真实语料 | 无监督 loss |
| 微调 | 适配特定任务格式 | 人工标注或合成数据 | 有监督 loss |
| 自训练 | 持续利用未标注数据提升能力 | 模型自身预测 | 置信度/外部工具 |
| 数据蒸馏 | 用大模型生成高质量数据训练小模型 | 大模型输出 | 通常依赖评估集 |
| 自我对弈 | 通过交互产生新训练数据 | 生成器与解题者交互 | 外部环境规则 |
编码自训练本质上是“自训练 + 合成数据 + 外部可执行环境”的组合。它最特殊的地方在于:代码是否正确,不是靠模型自己说了算,而是靠程序执行结果。这个属性让它比纯文本任务更容易形成自动化闭环。
3. 为什么编码任务特别适合“自己出题自己练”
3.1 正确性可以被自动验证
文本类任务很难自动化判断答案好坏。比如生成一段摘要,好坏标准模糊;生成一个故事,更是无法客观评分。代码则不一样,代码可以编译、可以运行、可以用测试用例断言输出。哪怕没有人类参与,机器也能明确告诉你:这段代码是否通过。
这一条是自训练能跑通的前提。验证成本越低,数据飞轮转得越快。
3.2 题目质量可以被程序约束
“自己出题”听起来不靠谱,但如果给生成器加上约束,质量就有基本保障。比如要求生成的问题必须包含:
- 明确的输入输出格式
- 边界条件(空数组、极大值、极小值)
- 至少 3 组测试用例
- 预期的时间复杂度
这些约束可以写进 prompt,也可以写进后置校验逻辑。生成的题目不符合格式就直接丢弃。通过程序过滤后的题目,即使模型水平不高,也能保持在一个可用区间。
3.3 失败案例本身也是训练数据
自训练最重要的增量不是“做对了什么”,而是“做错了什么,又如何纠正”。代码任务天然能暴露错误:编译错误、运行错误、超时、答案不符。我们可以让模型基于失败信息重新修复代码,然后记录“错误代码 + 错误信息 + 修复后代码”三元组。这类数据对提升模型调试能力很有帮助。
3.4 难度可以动态调节
编码题目天然有难度梯度。同一个问题可以要求 O(n²) 解法,也可以要求 O(n log n) 解法;可以只过小数据,也可以要求大数据不超时。自训练过程中,我们可以根据模型的通过率动态调整题目难度:
- 通过率太高,说明题目太简单,需要升级难度。
- 通过率太低,说明题目太难,模型无法从中学到东西。
- 通过率维持在 20% 到 50%,是较理想的学习区间。
这个调节在文本任务里很难做,在代码任务里却非常自然,因为题目复杂度和测试用例规模都是可以量化的。
3.5 与 Opus 4.8 这类强模型对比的参考意义
标题里“反超 Opus 4.8”是一个很有冲击力的表述。从技术角度看,真正重要的不是某一个模型的代号,而是“通过自训练方式,较小或较弱的模型能追上甚至超过更强模型”的可行性。这意味着,如果算力允许,持续的自训练循环可能比单纯扩大模型参数量更容易实现能力提升。当然,这不是说基础模型不重要,而是说在同等基础能力下,数据策略的差异会显著影响最终表现。
4. 从标题数据反推实验设计:两个月、11%、反超
4.1 数据来源的合理推测
标题只给了结论,没有给实验细节。我们可以从常见自训练论文的流程反推,大概率包含以下阶段:
- 阶段一:种子数据准备。使用现有开源代码数据集或人工编写几百道种子题,覆盖排序、动态规划、图论、字符串、数学等常见算法类型。
- 阶段二:初始模型训练。在种子数据上微调基础模型,得到一个能生成可用代码的初始版本。
- 阶段三:自我对弈数据生成。由初始模型生成新题目、新答案、新测试用例,通过执行过滤,得到合成训练集。
- 阶段四:迭代训练。用合成数据训练新模型,再重复生成和过滤,形成多轮迭代。
- 阶段五:最终评估。在固定测试集上测量通过率,与 Opus 4.8 等模型对比。
两个月的时间跨度暗示实验可能进行了多轮迭代,而不是一次生成后用到底。每一轮大概经历“生成 → 过滤 → 训练 → 评估”的周期,具体轮次取决于 GPU 资源和数据规模。
4.2 11% 涨幅意味着什么
如果“狂涨 11%”指的是测试集通过率相对值,这算是一个非常大的提升。编码基准测试通常从 20% 提升到 31% 需要投入大量数据;如果是绝对值,那更夸张。从行业经验看,更可能的统计口径是相对提升,因为绝对提升 11 个百分点的难度要大得多。
这里想提醒读者:看实验结论时,先确认涨幅的统计口径。相对提升和绝对提升的含金量完全不同。标题没有说明,我们只能做保守判断——它反映了自训练的有效性,但 11% 不代表每轮都会涨这么多,后期收益会趋于平缓。
4.3 “反超 Opus 4.8”的对比条件
对比模型的选择会显著影响结论。如果是在同一个测试集上,并且测试过程中没有引入额外人工数据,反超就有说服力。但如果对照模型使用了不同的 prompt 模板、不同的解码参数或不同的训练数据,可比性就会打折扣。从工程角度看,评估必须做到:
- 同一测试集
- 相同问题格式
- 相同通过标准(全量测试用例通过才算对)
- 相同或可比的温度参数
没有这些约束,反超只能当作营销数字。
4.4 数据飞轮的关键公式
从技术角度总结,自训练的效果大致由以下公式决定:
新模型效果提升 ∝ 合成数据质量 × 数据多样性 × 验证准确率 × 迭代轮次
其中验证准确率是最容易被忽视的。如果判官把错误代码判成对的,模型就会学到错误模式;如果判官太严格,又会丢弃大量本来可以学习的样本。在代码任务里,验证环节通常靠运行测试用例实现,但测试用例本身的覆盖度需要额外把关。
5. 完整示例:一个最小可行的编码自训练流程
下面给出一套可落地的通用流程,不依赖任何特定模型 API 版本。你可以把它当作架构模板,替换成自己的模型、训练框架和数据格式。
5.1 整体流程设计
种子题目集 ↓ 生成新题目(Prompt 驱动) ↓ 题目格式校验 + 去重 ↓ 生成解题代码 ↓ 生成测试用例 ↓ 执行验证(运行测试) ↓ 过滤通过样本 ↓ 合成训练数据集 ↓ 微调模型 ↓ 评估(新迭代轮次)每一轮生成后的数据可以合并到下一轮的种子池中,形成持续的数据累积。
5.2 文件目录结构
self_training_coding/ ├── data │ ├── seed_problems.jsonl │ ├── generated_problems.jsonl │ ├── filtered_problems.jsonl │ └── synthetic_train.jsonl ├── scripts │ ├── generate_problems.py │ ├── generate_solutions.py │ ├── generate_tests.py │ └── run_verify.py ├── prompts │ ├── problem_generation.txt │ ├── solution_generation.txt │ └── test_generation.txt ├── model │ └── config.yaml └── evaluate └── eval_set.jsonl这个结构的关键点是把数据生成、代码生成、测试生成和验证分开,每一步独立运行,方便出问题时单独排查。
5.3 示例代码 1:题目生成
这一阶段使用大模型根据种子题目生成新题目。实际调用 API 时,请把YOUR_API_KEY替换为自己的密钥。
# scripts/generate_problems.py import json import random from openai import OpenAI client = OpenAI(api_key="YOUR_API_KEY") def load_seed_problems(path): with open(path, "r", encoding="utf-8") as f: return [json.loads(line) for line in f] def generate_new_problem(seed_problem): prompt = f"""你是一名算法竞赛出题人。请参考下面的题目,生成一个全新的编程题。 要求: 1. 题目描述清晰,包含输入输出格式。 2. 给出输入范围和数据约束。 3. 提供 3 组示例测试用例,包含预期输出。 4. 难度与参考题目相当或略高。 5. 不能与参考题目使用同一个核心解法。 参考题目: {seed_problem["description"]} 请输出 JSON 格式: {{ "title": "题目名称", "description": "完整题目描述", "input_format": "输入格式", "output_format": "输出格式", "constraints": "数据约束", "samples": [ {{"input": "示例输入", "output": "示例输出"}} ], "tags": ["标签1", "标签2"] }} """ resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.8, ) return json.loads(resp.choices[0].message.content) if __name__ == "__main__": seeds = load_seed_problems("../data/seed_problems.jsonl") selected = random.sample(seeds, min(10, len(seeds))) results = [] for seed in selected: try: problem = generate_new_problem(seed) results.append(problem) except Exception as e: print("生成失败:", e) with open("../data/generated_problems.jsonl", "w", encoding="utf-8") as f: for item in results: f.write(json.dumps(item, ensure_ascii=False) + "\n") print("生成题目数量:", len(results))这段代码的核心逻辑是:每次从种子数据中随机选一个题目作为参考,让模型生成一个新题。temperature=0.8是为了增加多样性,避免每轮生成的数据都差不多。
5.4 示例代码 2:解题与测试生成
生成题目后,需要为每个题目生成解题答案和测试用例。
# scripts/generate_solutions.py import json from openai import OpenAI client = OpenAI(api_key="YOUR_API_KEY") def generate_solution(problem): prompt = f"""请解决下面的编程问题。要求: - 使用 Python 3 编写。 - 包含从标准输入读取数据的完整代码。 - 代码必须通过所有示例测试。 - 提供注释说明算法思路。 题目描述: {problem["description"]} 输入格式: {problem["input_format"]} 输出格式: {problem["output_format"]} 数据约束: {problem["constraints"]} 请只输出代码,不要输出解释。 """ resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.4, ) return resp.choices[0].message.content.strip() def generate_tests(problem, solution): prompt = f"""下面是编程题和参考解法。请生成 5 到 10 组额外的测试用例,要求覆盖边界情况。 题目描述: {problem["description"]} 输入格式: {problem["input_format"]} 输出格式: {problem["output_format"]} 数据约束: {problem["constraints"]} 参考解法: {solution} 请输出 JSON 数组,每个元素包含 "input" 和 "output" 字段。 """ resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.5, ) text = resp.choices[0].message.content.strip() # 清理可能存在的代码块标记 if text.startswith("```"): text = text.split("\n", 1)[1] text = text.rsplit("```", 1)[0] return json.loads(text) if __name__ == "__main__": with open("../data/generated_problems.jsonl", "r", encoding="utf-8") as f: problems = [json.loads(line) for line in f] output = [] for problem in problems: solution = generate_solution(problem) try: extra_tests = generate_tests(problem, solution) except Exception as e: extra_tests = [] problem["solution"] = solution problem["tests"] = problem.get("samples", []) + extra_tests output.append(problem) with open("../data/filtered_problems.jsonl", "w", encoding="utf-8") as f: for item in output: f.write(json.dumps(item, ensure_ascii=False) + "\n") print("完成题目+答案生成,总数:", len(output))这段代码里最重要的一步是generate_tests。测试用例是自训练判官的核心,测试写得不到位,后面验证环节就会失真。
5.5 示例代码 3:执行验证与数据过滤
验证环节决定哪些数据可以进入训练集。这里使用 Python 的subprocess运行代码,通过测试用例比对输出。
# scripts/run_verify.py import json import subprocess import sys def run_code(code, test_input): """运行 Python 代码,返回 stdout 或错误信息""" try: proc = subprocess.run( [sys.executable, "-c", code], input=test_input, text=True, capture_output=True, timeout=3, ) if proc.returncode != 0: return None return proc.stdout.strip() except subprocess.TimeoutExpired: return None def verify_problem(problem): code = problem.get("solution", "") tests = problem.get("tests", []) if not code or not tests: return False for test in tests: input_data = test.get("input", "").strip() expected = test.get("output", "").strip() actual = run_code(code, input_data) if actual is None or actual != expected: return False return True if __name__ == "__main__": with open("../data/filtered_problems.jsonl", "r", encoding="utf-8") as f: problems = [json.loads(line) for line in f] passed = [] failed = [] for problem in problems: if verify_problem(problem): passed.append(problem) else: failed.append(problem) # 把通过验证的数据写入训练集 with open("../data/synthetic_train.jsonl", "w", encoding="utf-8") as f: for problem in passed: sample = { "instruction": f"请解决下面的问题:{problem['description']}", "output": problem["solution"], "tests": problem["tests"], } f.write(json.dumps(sample, ensure_ascii=False) + "\n") print("通过验证:", len(passed)) print("验证失败:", len(failed))这里的验证逻辑非常朴素:运行代码,比对输出,完全相同才通过。真实项目中还可以增加:
- 多种语言验证
- 大数组性能测试
- 随机输入对拍
- 静态检查
对于无法编译或超时的样本,可以选择丢弃,也可以尝试让模型根据错误信息修复后再验证。
5.6 训练示例
有了synthetic_train.jsonl之后,就可以用常规的监督微调方式训练模型。下面是一段基于 Hugging Face Trainer 的示例骨架。
# train.py from datasets import load_dataset from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments dataset = load_dataset("json", data_files="../data/synthetic_train.jsonl", split="train") def format_sample(example): return { "text": f"### 问题\n{example['instruction']}\n\n### 解法\n{example['output']}" } dataset = dataset.map(format_sample) tokenizer = AutoTokenizer.from_pretrained("deepseek-ai/deepseek-coder-1.3b-base") model = AutoModelForCausalLM.from_pretrained("deepseek-ai/deepseek-coder-1.3b-base") def tokenize_fn(examples): return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=2048) tokenized_dataset = dataset.map(tokenize_fn, batched=True, remove_columns=dataset.column_names) args = TrainingArguments( output_dir="./self-trained-model", per_device_train_batch_size=2, gradient_accumulation_steps=8, num_train_epochs=3, logging_steps=50, save_steps=500, fp16=True, ) trainer = Trainer( model=model, args=args, train_dataset=tokenized_dataset, ) trainer.train() trainer.save_model("./self-trained-model-final")这段代码需要你自己安装transformers、datasets、accelerate等依赖。模型名称可以根据实际环境替换,我这里用 DeepSeek Coder 1.3B 只是个演示选择。
5.7 评估脚本示例
训练完成后,需要固定在测试集上评估通过率。评估集可以来自公开数据集,例如 HumanEval 或 MBPP,也可以是你自己的内部题目集。
# evaluate/evaluate.py import json from openai import OpenAI client = OpenAI(api_key="YOUR_API_KEY") def solve_with_model(prompt): resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.2, ) return resp.choices[0].message.content.strip() def check_solution(code, tests): # 省略执行验证细节,与 run_verify 类似 pass with open("eval_set.jsonl", "r", encoding="utf-8") as f: eval_items = [json.loads(line) for line in f] correct = 0 total = len(eval_items) for item in eval_items: code = solve_with_model(item["prompt"]) if check_solution(code, item["tests"]): correct += 1 print(f"通过率: {correct / total:.2%}")评估结果只说明模型在固定测试集上的表现。要注意:如果自训练数据里包含评估集的相似题目,结果会被污染。所以评估集必须从第一轮开始就隔离,绝对不能混入训练数据。
6. 运行结果与效果验证
6.1 每一步的预期输出
- 题目生成阶段:每次运行输出
生成题目数量,正常情况是 10 条,可能存在少量 JSON 解析失败。 - 解题与测试生成阶段:输出
完成题目+答案生成,总数,数量和前面对应。 - 验证阶段:输出通过数量和失败数量。第一轮通过率如果太低,通常说明题目太难或测试用例有问题。
- 训练阶段:loss 应该随训练步数下降。如果 loss 不降,优先检查数据格式和 tokenizer。
- 评估阶段:输出最终通过率,与训练前的模型做对比。
6.2 如何判断训练是否有效
最直接的判断方式是做回归对比:
- 在基础模型上评估编码测试集,记录基线通过率。
- 用自训练数据微调模型,训练 1 到 3 个 epoch。
- 在相同测试集上重新评估,对比通过率。
如果通过率提升至少 1 到 2 个百分点,说明数据飞轮有效。这里提醒一句:单次提升有噪声,建议至少跑两次并取平均值。
6.3 失败时先看哪个环节
自训练最尴尬的情况是数据生成了很多,训练也正常,但评估没有提升。排查顺序是:
- 先检查验证环节:测试用例是否太弱?代码是否真的正确?
- 再检查数据分布:合成题目是否过于相似?是否大量重复?
- 最后检查训练设置:batch size、学习率、epoch 数量是否合理。
不要一上来就加数据量。数据量大但质量差,训练结果反而会更糟。
7. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 生成的题目 JSON 解析失败 | Prompt 输出格式不稳定 | 查看原始输出日志 | 增加后处理逻辑,提取 JSON 片段;使用response_format={"type": "json_object"}(如果 API 支持) |
| 解题代码编译失败 | 代码生成模型能力不足 | 查看编译错误信息 | 降低题目难度;让模型根据错误信息修复;限制只能生成纯代码 |
| 测试用例过少导致验证通过率虚高 | 测试生成 prompt 约束不足 | 检查每个问题的测试数量和覆盖边界 | 强制要求生成 10 组以上测试;自动补充随机测试、边界测试 |
| 训练 loss 下降但评估不提升 | 数据分布与测试集分布不一致 | 对比题目类型和难度分布 | 调整题目生成 prompt,覆盖更多算法类型;加入真实测试集分布约束 |
| 多轮迭代后数据重复度变高 | 生成温度过低或种子池有限 | 统计文本相似度 | 提高 temperature;使用不同的参考题拼接;加入去重步骤 |
| 验证环节耗时长 | 每道题生成太多测试或代码运行超时 | 分析验证耗时 | 限制测试数量;设置更短超时;并行化执行 |
| 生成数据被评估集污染 | 训练数据与评估集混用 | 使用文本去重工具 | 严格隔离评估集,并在迭代开始前固定下来 |
8. 最佳实践与工程建议
8.1 难度控制是自训练的生命线
最容易出现的错误是:模型生成的题目要么全部太简单,要么全部太难。建议在生成 prompt 中显式要求“难度与参考题相当或略高”,并在过滤器里加入通过率监控:
- 新轮次合成题通过率低于 10%,说明题目太难,需要降低难度。
- 通过率高于 80%,说明题目太简单,训练收益有限。
- 比较理想的区间是 30% 到 60%。
实际项目里,可以保存每一轮通过率,形成趋势曲线,用来动态调整生成参数。
8.2 验证环节要分成两层
第一层是“示例测试验证”,只运行题目自带的示例用例,用于快速过滤明显无效的样本。第二层是“额外测试验证”,运行模型生成的扩展用例,用于过滤边界和隐藏条件。
这里特别建议:验证使用的判官代码和被评估的模型代码使用不同语言或不同进程,避免模型因为语言特性偶然通过。
8.3 训练数据格式要统一
合成数据的字段名、prompt 模板、代码块格式,最好全部保持一致。否则模型在微调时会学到混乱的格式,导致生成结果难以解析。建议在一个公共文件中维护 prompt 模板:
### 问题 {description} ### 输入格式 {input_format} ### 输出格式 {output_format} ### 要求 {constraints}推理时也使用同一个模板,评估效果会更有对比性。
8.4 区分“合成数据”和“评估数据”
这是工程上最容易失控的地方。很多团队在迭代中为了省事,把上一轮的合成数据拿到评估集里去验证,结果评估分数虚高。正确做法是:
- 第一轮就固定一份评估集。
- 每次迭代后允许增加训练数据,但不允许修改评估集。
- 所有代码生成实验,必须在相同评估集上对比。
8.5 控制成本与并行度
自训练的一大成本来自反复调用生成模型。建议把生成、验证设计成异步任务流,批量写入待处理队列。可以用简单的任务队列实现:
# queue 思想示意 tasks = [ {"type": "problem", "seed_id": 1}, {"type": "problem", "seed_id": 2}, ] for task in tasks: # 调用生成 -> 校验 -> 存入 redis 或消息队列 pass如果只在单机上跑,可以用ThreadPoolExecutor并行调用 API,注意频率限制。
8.6 模型选择策略
在真实项目中,不建议使用最小的模型来做数据生成,也不建议每次都调用最大模型。更稳妥的策略是:
- 用中档模型生成题目的初稿。
- 用更强模型做代码审查和测试用例扩展。
- 用弱一点的模型做大规模候选生成,然后用外部执行环境过滤。
这样产出的数据质量比单一模型高,成本比全部使用最强模型低。
9. 总结与后续学习方向
“AI 自己出题自己练”这件事,本质上是把代码训练从人力驱动变成了验证驱动。标题里两个月 11% 的提升,核心支撑来自代码可自动验证这一技术红利。只要题目生成有约束、测试用例有覆盖度、数据过滤够严格,每一轮迭代都能产生对模型有帮助的训练数据。
如果你想继续深入,建议按三条线走:
- 第一,研究现有开源实现。Hugging Face 社区有不少基于合成数据微调代码模型的工具,例如 self-instruct 的代码变体,以及各种代码竞赛题生成项目,可以从这些项目中借鉴定题和过滤逻辑。
- 第二,优化验证判官。将单元测试换成属性测试、随机对拍和模糊测试,能大幅提升判官的可信度。
- 第三,设计难度曲线。在小规模实验上跑通 5 轮迭代,记录每轮的通过率和最终评估分,你会发现模型能力的增长曲线很快进入平台期。突破平台期的关键,往往是引入新的题目类型,而不是无限增加同类型题目的数量。
编码自训练是一条值得投入的路线,但它不是“自动炼丹”。数据生成、验证、隔离、评估,每个环节都要可回溯、可监控。从这个角度看,标题里的“狂涨 11%”是结果,而把自己的训练流程做成一架能稳定增长的数据飞轮,才是我们真正应该带走的经验。