MatrixOne Git4Data 技术详解(十一)·大模型篇:SFT 数据 curation——可审计、可复现的数据清洗

前十篇里,我们把 MatrixOne 的 Git4Data 能力从概念一路带到了 AI 训练现场:前四篇建立技术坐标,第五到第七篇是数据运维(误操作救援多人协作Write-Audit-Publish),第八、九篇讲传统机器学习(全流程总图数据集发布与泄漏),第十篇转向深度学习的文件型数据,让 lakeFS 管文件、MatrixOne 管元数据。

这一篇进入大模型 。具体说,是大模型训练里最讲究、也最依赖人工判断的一步:SFT(Supervised Fine-Tuning,监督微调)的数据 curation

先说清楚它在整条链路里的位置。一个大模型通常要经过三段:预训练 (在海量文本上学语言和世界知识)、SFT (用高质量的「指令 → 回答」样本,教会它按人的期望回话)、偏好对齐 (RLHF / DPO 等,让它在多个像样的回答里挑更好的那个)。SFT 是承上启下的一段,而且数据量比预训练小好几个数量级:预训练动辄几万亿 token,而在公开披露的实践里,OpenAI 的 InstructGPT 用的人工示范集约 1.3 万条,LIMA 更是只用 1,000 条精选样本就做出了可用的对齐效果(见文末参考资料)。当然这不是一个跨模型都成立的定值,不同团队的 SFT 集从几千到上百万条都有。

正因为量小,每一条的质量、每一次的取舍,都会直接印在模型行为上。而这些取舍,几乎全是人在做决定:这批合成数据留不留?这个来源的数据质量掉了要不要下架?code 和 chat 的比例调成多少?质量分的门槛卡在 0.35 还是 0.50?

这一篇先把 SFT 数据的完整链路过一遍、说清 Git4Data 这套能力在每个环节能帮上什么,再把一次完整的 curation 从头到尾做一遍:数据池长什么样、要用哪几步过滤、每一步怎么留下审计记录、最后怎么发布成一个可复现的版本,以及业内其他做法各自卡在哪。文中 SQL 全部在 MatrixOne 4.1.0 上实测;可跑版本见 git4data-tutorial 的 11-sft-curation/sft_curation_demo.sql(已固定到具体 commit,避免后续改动导致数字对不上)。


先看全貌:一条 SFT 数据要走完的八个环节

在钻进任何一个细节之前,先把整条链路摊开。一批 SFT 数据从进入团队到真正被模型吃掉,要走完这些环节:

text 复制代码
① 获取 ──▶ ② 入池 ──▶ ③ 打分 ──▶ ④ curation ──▶ ⑤ 配比 ──▶ ⑥ 发布 ──▶ ⑦ 训练 ──▶ ⑧ 评估
                          ▲                                                              │
                          └──────────────  加数据 / 调门槛 / 换配比  ◀───────────────────┘

① 获取:从供应商采购、用模型合成、组织人工撰写、或从线上日志里挖掘。不同渠道的质量参差极大,还常常买到同一批数据。

② 入池 :把不同来源、不同格式的数据统一成一张表,打上来源和批次标记。这里第一个坑就出现了------新批次如果直接写进主池,一旦有问题,脏数据立刻和几十万条好数据混在一起。

③ 打分:用打分模型或启发式规则给每条数据打质量分,用安全分类器标记风险。注意打分模型本身也有版本,同一条数据在不同时间可能得到不同分数。

④ curation(清洗过滤) :去重、卡质量门、过滤不安全内容、剔除和评测集重合的、保证多轮对话完整。这是整条链路里删得最多、也最不透明的一环。

⑤ 配比:决定各 domain、各来源、各语种的比例。这是一次显式的建模决策,却常常没有被记录下来。

⑥ 发布 :把筛好的数据冻结成一个确定的版本,交给训练。问题在于训练读的是"当时的池子",而池子一直在变。

⑦ 训练⑧ 评估 :拿到指标后回头调整------补数据、调门槛、换配比------回到 ③④⑤ 再来一轮。而这一轮和上一轮的数据到底差在哪,往往没人说得清。

这八个环节里,哪些是数据版本控制的地盘

需要先划清楚:③ 打分是打分模型的地盘,⑤ 配比是建模策略的地盘,⑦⑧ 是训练框架和评估体系的地盘。Git4Data 这套能力不碰这些判断 ,它管的是另一层------数据在这条链路上怎么进来、怎么被改动、怎么被冻成一个版本

环节 真实问题 MatrixOne 的 Git4Data 能力怎么帮
② 入池 新批次质量未知,又不能污染主池 新数据先进分支 ,审计通过再 MERGE,主池全程不动
③ 打分 打分模型换版本,分数会变 打分结果连同数据一起进快照,"这一版用的是哪一版分数"可查
④ curation 删了什么、为什么删、能不能撤,全说不清 分支 上过滤,每步先计数,DATA BRANCH DIFF 给出这一轮的净变更
⑤ 配比 配比是决策,却没有留痕 发布前用 SQL 直接统计配比;规则写进注册表随版本冻结
⑥ 发布 训练读的池子一直在变 库级 CREATE SNAPSHOT 冻结版本,日后逐位复现
⑧ 迭代 两版模型的数据差异归因不到行 两个快照做 DIFF,差异精确到具体哪些行、被哪一步删的
任意环节出错 改坏了退不回去 RESTORE 回到任意一个历史版本

一句话概括这张表:SQL 负责判断数据合不合格,Git4Data 这套能力负责给这些判断提供隔离的工作区、稳定的版本锚点和可审计的变更记录。

本文接下来聚焦分量最重的 ④ curation,并把 ②、⑤、⑥ 串起来走一遍完整流程。


SFT 数据的特殊之处:数据回到了数据库的主场

第十篇里,图像训练数据不得不劈成两个世界:文件交给 lakeFS,元数据交给 MatrixOne。因为一张 JPEG 没有行、没有列,数据库拿它没办法。

SFT 数据不一样。一条 SFT 样本长这样:

json 复制代码
{"instruction": "解释一下什么是数据库快照", "response": "快照是数据库在某个时刻的...", "domain": "chat"}

它是文本,但高度结构化 :instruction 一列、response 一列,外加来源、语种、领域、质量分、安全标记、长度......整条样本,包括正文本身,都能直接住进一张表里。几十万条记录、每条几百到几千字符,对数据库来说完全是舒适区。

这意味着 SFT 的 curation 有一个第十篇没有的好处:不需要"文件 + 元数据"两个世界的对齐,所有操作都在同一张表上,用 SQL 完成,并且天然被行级版本语义覆盖

而 SFT curation 的动作,恰好全是行级的增删改:

text 复制代码
去掉重复的样本            = DELETE 一批行
砍掉质量不达标的           = DELETE 一批行
剔除和评测集重合的         = DELETE 一批行
下架某个来源的数据         = DELETE 一批行
调整领域配比              = 有选择地 DELETE / 保留
修正某些回答              = UPDATE 一批行

每一步过滤都是一次行级变更------这正是 Git4Data 这套能力最擅长的东西。于是整篇文章的主张可以先摆出来:

SFT curation 不该是一串跑完就没影的脚本,而应该是一次「在分支上做完、用 DIFF 留下审计记录、按固定顺序发布成一个冻结版本」的受控变更。


为什么 curation 必须可审计

先看一个几乎每个大模型团队都遇到过的场景。

模型 sft-v7 上线后,团队发现它在代码题上明显不如 sft-v6------但两版之间训练代码没动、超参没动,只是数据「按惯例清洗了一遍」。于是问题变成:这轮清洗到底删了什么?

如果 curation 是一串 Python 脚本跑完就完事,你能拿到的通常只有:清洗前 73 万条、清洗后 60 万条。中间少的那 13 万条是什么,谁也说不清------是去重删的?是质量分门槛卡掉的?还是某个 domain 被整体误删了?想复现 sft-v6 那一版数据集,更是无从下手,因为原始池这个月又进了新数据。

这就是「可审计」的意义。curation 的每一步过滤都应该能回答三个问题:

  1. 删了多少、删了哪些(可审计);
  2. 为什么删(规则可查);
  3. 这一版能不能原样复现(可回溯)。

下面就用一个完整的案例,把这三件事都落实。


贯穿全文的案例:给一个对话模型做一轮 SFT 数据 curation

假设我们在维护一个通用对话模型的 SFT 数据池。数据来自三个渠道:采购的供应商数据模型合成的数据人工撰写的数据;覆盖 code / math / chat / safety 四个领域;既有单轮样本,也有多轮对话。

数据池就是一张表------注意正文(instruction / response)就在表里,旁边是所有用来做取舍的字段:

sql 复制代码
CREATE TABLE sft_records (
    record_id     BIGINT PRIMARY KEY,
    conv_id       BIGINT,          -- 多轮对话:同一个 conv_id 下有多行
    turn_no       INT,             -- 对话内的第几轮
    instruction   VARCHAR(512),
    response      VARCHAR(1024),
    norm_hash     VARCHAR(64),     -- 归一化后 instruction 的哈希(近重复键)
    exact_hash    VARCHAR(64),     -- instruction+response 的哈希(精确去重键)
    domain        VARCHAR(24),     -- code / math / chat / safety
    source        VARCHAR(32),     -- 供应商 / 合成 / 人工
    lang          VARCHAR(8),
    resp_len      INT,
    quality_score DOUBLE,          -- 打分模型或启发式规则给的质量分
    is_safe       TINYINT,         -- 1 = 通过安全分类器
    ingest_batch  VARCHAR(32)
);

两个 hash 列值得解释一下,它们对应两种不同的去重方式:

  • exact_hash :instruction + response 一起算哈希,抓的是完全一样的样本------同一条数据被两个供应商都卖给你了,或者同一批数据被导入了两次。
  • norm_hash :只对 instruction 做归一化 (统一大小写、去掉多余标点和空白)后算哈希,抓的是问题其实是同一个、但回答不同的近重复------合成数据里这类特别多,同一个 prompt 被反复采样出好几个版本。

案例的池子:60000 条单轮样本 + 4000 条精确重复 + 3000 条近重复 + 2000 组三轮对话(6000 行),另有 800 条回答被截断成空串,以及 150 组多轮对话里第 2 轮被截断------实测共 73,000 行


在分支上做 curation:六步过滤,每步先计数

关键的第一步:不要在数据池主表上直接删 。先拉一条分支,整轮 curation 都在分支上做------主池一行不动,随时可以对照、可以丢弃重来。这就是第七篇 Write-Audit-Publish 的思路,用在 curation 上:

sql 复制代码
DATA BRANCH CREATE TABLE sft_curated FROM sft_records;

分支是零拷贝的(第三篇讲过原理),所以哪怕池子有几百万条,这一步也是毫秒级、不产生数据副本。

第一步:精确去重

同一条「指令 + 回答」在池子里出现多次,训练时会让模型对这条样本过度拟合。按 exact_hash 分组,每组只留 record_id 最小的一条:

sql 复制代码
-- 先看要删多少
SELECT COUNT(*) AS exact_dup_rows FROM sft_curated c
WHERE c.record_id > (SELECT MIN(c2.record_id) FROM sft_curated c2
                     WHERE c2.exact_hash = c.exact_hash);
--   实测 4000

DELETE FROM sft_curated
WHERE record_id > (SELECT MIN(c2.record_id) FROM sft_curated c2
                   WHERE c2.exact_hash = sft_curated.exact_hash);

第二步:近重复去重

比精确重复更隐蔽、也更常见:同一个问题,被合成了好几个不同的回答 。它们的 exact_hash 各不相同,但归一化后的 norm_hash 是一样的。留哪一条是个策略问题(这里取 record_id 最小;实践中更常见的是留质量分最高的那条):

sql 复制代码
SELECT COUNT(*) AS near_dup_rows FROM sft_curated c
WHERE c.record_id > (SELECT MIN(c2.record_id) FROM sft_curated c2
                     WHERE c2.norm_hash = c.norm_hash);
--   实测 3000

DELETE FROM sft_curated
WHERE record_id > (SELECT MIN(c2.record_id) FROM sft_curated c2
                   WHERE c2.norm_hash = sft_curated.norm_hash);

要说清楚边界:norm_hash 只能抓「归一化后完全相同」的近重复。语义相同但措辞不同 的那一类(「什么是快照」vs「快照是什么意思」),哈希抓不到,需要向量相似度或 MinHash/SimHash 这类方法------它们能算出候选对,但最终「删哪条、留哪条」仍然是一次行级删除,照样走这里的流程。

第三步:质量门

空回答、被截断的生成、打分模型给出的低分样本,都要挡在训练之外:

sql 复制代码
SELECT COUNT(*) AS low_quality FROM sft_curated
WHERE resp_len < 10 OR quality_score < 0.35;
--   实测 5184

DELETE FROM sft_curated WHERE resp_len < 10 OR quality_score < 0.35;

注意这里的门槛(长度 10、质量分 0.35)是这次 curation 的一个决策,不是自然规律。它必须被记录下来------后面发布时会写进 curation 规则里,因为下一版很可能会调整它(本文最后就会调到 0.50)。

第四步:安全过滤

被安全分类器标记为不安全的样本直接剔除:

sql 复制代码
SELECT COUNT(*) AS unsafe FROM sft_curated WHERE is_safe = 0;   -- 实测 101
DELETE FROM sft_curated WHERE is_safe = 0;

第五步:评测集去污染

这是大模型 curation 里最要命的一步。如果评测基准里的题目混进了 SFT 训练集,模型是背下了答案,而不是学会了能力------所有 benchmark 分数都会虚高,而且你不会收到任何报错。做法是拿评测集的 prompt 哈希做反连接:

sql 复制代码
SELECT COUNT(*) AS contaminated FROM sft_curated c
WHERE EXISTS (SELECT 1 FROM eval_prompts e WHERE e.norm_hash = c.norm_hash);
--   实测 744

DELETE FROM sft_curated
WHERE EXISTS (SELECT 1 FROM eval_prompts e WHERE e.norm_hash = sft_curated.norm_hash);

第十篇一样要诚实说明:这条反连接匹配的是归一化后完全相同 的 prompt。改写过、翻译过、换了表述的污染样本它抓不到------那需要在评测集上再建一层语义相似度检索。哈希去污染是底线,不是全部。

第六步:多轮对话完整性

这一步是 SFT 特有的,也是最容易被脚本漏掉的。多轮对话必须整体进、整体出 :如果第 2 轮因为回答被截断而在第三步里被删了,只剩第 1、3 轮的对话是残缺且有害的------模型会学到跳跃、不连贯的对话结构。

所以在所有过滤之后,要再扫一遍:哪些 conv_id 的轮次不完整了,整组删掉:

sql 复制代码
SELECT COUNT(*) AS broken_conv_rows FROM sft_curated c
WHERE c.conv_id IN (
  SELECT conv_id FROM sft_curated GROUP BY conv_id HAVING COUNT(*) < MAX(turn_no)
);
--   实测 300(150 组对话,各自被删掉一轮后还剩两轮)

DELETE FROM sft_curated WHERE conv_id IN (
  SELECT conv_id FROM (
    SELECT conv_id FROM sft_curated GROUP BY conv_id HAVING COUNT(*) < MAX(turn_no)
  ) t
);

这就是为什么 curation 的顺序很重要:完整性检查必须放在所有过滤之后,因为它要清理的正是前面几步造成的连带损伤。

六步过后:实测 73,000 → 59,671 行


审计记录:净变更由 DIFF 出具,删除原因由流程留痕

整轮 curation 做完,最重要的动作来了------留下这一轮的审计记录

sql 复制代码
DATA BRANCH DIFF sft_curated AGAINST sft_records OUTPUT SUMMARY;
--   实测 INSERTED 0 / DELETED 13329 / UPDATED 0

13329 这个数字不是估算,它就是六步之和:4000 + 3000 + 5184 + 101 + 744 + 300 = 13329主池一行没动,而这一轮的全部影响被压缩成一行摘要。

这里要把话说准:OUTPUT SUMMARY 给出的是这条分支相对主池的净变更(INSERTED / DELETED / UPDATED),不是逐行的删除原因 。表里并没有「这一行是被安全过滤删的」这种字段,DIFF 也不可能凭空知道。按原因归类的能力来自流程本身 ------每一步先 COUNTDELETE,那六个数字就是六个原因的分账,加起来恰好等于 DIFF 的总数。如果需要更强的追溯,有两条路:在每一步之后各留一次 DIFF ,让每个过滤步骤都有自己的净变更数字;或者在删除之前,先把命中行的主键连同 (step, reason) 写进一张 curation 日志表。注册表里的 curate_rule 记的是规则,不是逐行的判决书。

所以这一节能确定的是三件事:主池一行没动;这一轮的净影响是一个可查的确定数字;规则和数字都会跟着版本一起冻住。

数据本来就在表里,发布之前还可以随手追问细节------比如这一版的配比:

sql 复制代码
-- 发布前先看一眼配比:领域和来源的比例,本身就是一次 curation 决策
SELECT domain, COUNT(*) AS n,
       ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM sft_curated), 1) AS pct
FROM sft_curated GROUP BY domain ORDER BY domain;
--   实测 chat 18896 (31.7%) / code 13247 (22.2%) / math 13764 (23.1%) / safety 13764 (23.1%)

SELECT source, COUNT(*) AS n FROM sft_curated GROUP BY source ORDER BY source;
--   实测 human_written 23591 / synthetic_gpt 18040 / vendor_a 18040

两组数字都合计 59,671 ,和上面 DIFF 之后剩下的行数对得上------发布前顺手做一次这样的对账,能挡住绝大多数「表里数字和实际版本不是一回事」的问题

这两条查询还点出了一个容易被忽略的事实:配比也是 curation 的一部分 。chat 占到 31.7% 而 code 只有 22.2%,是不是符合这次训练的目标?合成数据(synthetic_gpt)占了三成,比例是否过高?这些判断没有标准答案,但它们必须在发布之前被看到------而不是训练完拿到一个奇怪的模型再回头猜。


发布:先登记、再替换、后快照

审计通过,才轮到发布。这里有两个细节值得强调。

第一,登记要在快照之前。 我们要把「这一版数据集 = 哪个快照 + 用了什么 curation 规则」记成一条可查的绑定。顺序必须是先写注册表、再打快照------否则绑定只躺在可变的活库里,没被冻进版本:

sql 复制代码
CREATE TABLE dataset_registry (
    dataset_version   VARCHAR(32) PRIMARY KEY,
    metadata_snapshot VARCHAR(64),
    n_records         BIGINT,
    curate_rule       VARCHAR(256)
);

-- 先登记(快照名提前定好,所以这行可以写上它)
INSERT INTO dataset_registry
SELECT 'sft_v1', 'sft_v1', COUNT(*),
       'exact+near dedup, len>=10, score>=0.35, safe only, eval-decontam, whole-conv'
FROM sft_curated;

注意 curate_rule 这一列:它把「这一版是按什么规则筛出来的」变成了数据的一部分。三个月后有人问「v1 的质量分门槛是多少」,不用去翻脚本仓库的 git log,一条 SELECT 就够了。

第二,替换和冻结是一串有先后的语句,不是一个事务。 用 curation 后的分支替换主池,再打快照冻住整个版本:

sql 复制代码
DROP TABLE sft_records;
ALTER TABLE sft_curated RENAME TO sft_records;   -- 用 curation 后的池子替换主池
CREATE SNAPSHOT sft_v1 FOR DATABASE sft_pool;

-- 绑定现在在快照里了,不只是活库里
SELECT n_records FROM dataset_registry {SNAPSHOT='sft_v1'} WHERE dataset_version = 'sft_v1';
--   实测 59671

这里要诚实说明一点:这三条语句之间没有原子性 。在 4.1.0 上它们各自独立提交,中间任何一条失败,前面已经生效的不会自动回退------比如 DROP 成功而 RENAME 失败,主池就会暂时处于不存在的状态。所以发布这一步的正确做法是:先对当前主池打一个快照作为回退点 ,再按上面的顺序执行,每一步确认成功后再走下一步,失败时用 RESTORE 退回上一版。

换句话说,这套流程给你的保证不是「一条命令要么全成要么全不成」,而是发布顺序是确定的、每一步的结果是可验证的、任何一版都有可回退的锚点 。真正被冻住、能逐位复现的是最后那个 CREATE SNAPSHOT 的结果------在它成功之前,这一版都不算发布完成。


下一轮:v2 的审计记录,和 v1 的可复现

几周后,团队决定收紧质量门槛------从 0.35 提到 0.50。同样的流程再走一遍,而这一次,DIFF 直接告诉你这个决策的代价

sql 复制代码
DATA BRANCH CREATE TABLE sft_v2_wip FROM sft_records;
DELETE FROM sft_v2_wip WHERE quality_score < 0.50;

DATA BRANCH DIFF sft_v2_wip AGAINST sft_records OUTPUT SUMMARY;
--   实测 DELETED 12514
SELECT COUNT(*) AS v2_rows FROM sft_v2_wip;   -- 实测 47157

一个门槛从 0.35 调到 0.50,代价是再砍掉 12,514 条、数据集从 59,671 降到 47,157------少了 21%。这个数字应该在训练之前就摆到桌面上讨论,而不是训练完发现模型变笨了再去猜。

与此同时,v1 始终原样可查:

sql 复制代码
SELECT COUNT(*) AS v1_rows FROM sft_records {SNAPSHOT='sft_v1'};   -- 实测 59671

于是「sft-v7 在代码题上掉点」这个开头的问题,现在有了可执行的排查路径:把 v7 用的数据快照和 v6 的 DIFF 一下,看 code 域少了哪些行、是被哪一步删的。模型的退步,第一次能落到具体的数据变更上。


行业里的其他做法:SFT curation 一般怎么管,各自卡在哪

把 SFT curation 做成「可审计、可复现、可回退」的版本化流程,业内有几种常见路径,各自解决了一部分:

做法一:一串 Python 清洗脚本 + 落一个新文件。 最普遍。clean_v3.py 跑完输出 sft_train_v3.jsonl。优点是灵活;代价是结果和过程都不可查------你有一个 60 万行的文件,但没有「删了哪 13,329 条、分别因为什么」的记录;想复现上一版,得指望脚本和原始池都没变过。

做法二:脚本 + 把每一步的中间产物都存下来。 有纪律的团队会这么做:step1_dedup.jsonlstep2_quality.jsonl......确实能回溯了,代价是每一步一份全量副本(N 倍存储),而且比对两个版本要写额外的脚本,不能直接 JOIN 或聚合。

做法三:HuggingFace Datasets + 版本化到 Hub。 数据集有了 revision(Hub 上每个仓库就是一个 git 仓库,可以按 commit 取到确定的一版),社区生态也好。但在这条工作流里,可回溯的粒度是数据集的 revision:它能告诉你 v3 和 v2 不是同一份,要说清「哪 300 行是因为多轮残缺被删的」,得靠你在脚本里自己另外记录;筛选逻辑也仍在外部脚本里。

做法四:DVC / lakeFS 等数据版本工具。 能把 JSONL 文件钉成可回到的版本,这点很扎实。但它们看到的是文件:diff 是文件级或对象级的,而 SFT curation 的语义全是行级的;而且数据在文件里,做一次「按 domain 统计配比」还得先读回来。

做法五:数据仓库(Spark / BigQuery 等)里跑 curation。 用 SQL 做筛选,这一步和本文思路是一致的,也是大团队常见的做法。差别在于版本语义:Spark 表要留住每一版通常靠「写到一张带日期后缀的新表」或 Delta/Iceberg 的表版本;行级的 branch / diff / merge 不是原生语义,「在分支上试一轮 curation,不行就丢掉」这件事要自己拼装。

放进一张表里。先说清这张表的范围 :它比较的是上面列出的这几种典型工作流的默认路径,不是对相关产品全部能力的评价------各家的能力边界以官方文档为准(见文末参考资料),表里标「否」的格子,多数都能靠额外的工程手段补上,代价是你得自己拼装和维护。

做法 行级审计记录(删了哪些) 筛选逻辑在哪 版本可复现 试错成本 额外副本
Python 脚本 + 输出文件 脚本 靠自觉 重跑全流程 每版一份
脚本 + 存中间产物 半(靠比对文件) 脚本 重跑 N 倍
HF Datasets + Hub 否(数据集级) 外部脚本 重新上传 每版一份
DVC / lakeFS 否(文件级) 外部脚本 重跑 内容去重后较省
数仓 SQL(Spark 等) 否(表版本级) SQL 建新表 每版一张表
MatrixOne(Git4Data 能力) 是(DATA BRANCH DIFF SQL 是(快照) 零拷贝分支,丢弃即可

一句话:就本文列出的这几条路径而言 ,要么把筛选逻辑锁在脚本里、结果只剩一个文件(脚本类),要么能版本化却看不到行级语义(文件版本工具),要么能用 SQL 筛却没有原生的分支 / 行级 diff(数仓)。MatrixOne 的不同在于这三件事在同一张表上同时成立 :curation 用 SQL 写、在零拷贝分支上试、净变更由 DATA BRANCH DIFF 出具、通过后按固定顺序替换主池并打快照冻结。


边界与适用范围

  • Git4Data 这套能力不替你判断质量。 质量分从哪来(打分模型、规则、人工)、门槛卡在哪、配比怎么定,都是你的决策。它保证的是:这些决策作用在确定的数据版本上、结果可审计、做错了能退回。

  • 哈希去重和哈希去污染都是底线,不是全部。 语义级的近重复和改写过的污染样本,需要向量检索或 MinHash 这类方法先算出候选;但候选算出来之后的「删哪些行」,仍然回到本文这套行级流程。

  • 多轮对话的完整性要显式检查。 它不会自己成立,而且必须放在所有过滤之后------本文实测的那 300 行就是前面几步的连带损伤。

  • 快照有保留成本。 每个上线模型对应的 sft_vN 建议长期保留,废弃的中间试验版本要设清理策略,否则历史版本会一直占着存储。

  • curation 规则要跟着版本走。 把门槛、规则写进注册表(本文的 curate_rule 列),比写在某个脚本的注释里可靠得多。

  • 发布这一步不是一条原子命令。 DROP / RENAME / CREATE SNAPSHOT 各自独立提交,要靠「先打回退快照 + 每步校验」来保证发布过程可控。


参考资料


结语

SFT 的数据量比预训练小好几个数量级,而每一条样本对模型行为的影响却直接得多。正因如此,curation 的每一次取舍都会印在模型行为上------而这些取舍,长期以来是整条链路上最不透明的一环:一串脚本跑完,池子小了一大截,至于删了什么、为什么删、能不能退回,全靠人的记忆和纪律。

把它换成「分支上做、每一步先计数、DIFF 出具净变更、按固定顺序发布、快照冻结」之后,这一环就变成了和代码评审一样可控的东西:73,000 → 59,671,中间那 13,329 条分别属于哪一步,一条 SQL 就能对上账;把门槛从 0.35 提到 0.50 要付出 21% 的数据量,这个代价在训练开始之前就摆在桌面上;而任何一个历史版本,随时可以逐位复原。

📎 可运行 SQL(固定 commit 354b9cf):github.com/matrixorigin/git4data-tutorial | 源码与社区:github.com/matrixorigin/matrixone

相关推荐
小酒星小杜1 小时前
AI漫画真正的门槛,不是出图,而是让角色持续出演
人工智能·产品·全栈
数智大号2 小时前
从机器人前置仓到机器人组装机器人,星海图 WRC 打开具身智能产业化下半场
人工智能·机器人
Leo.yuan2 小时前
AI辅助数据分析:如何让分析效率提升85%?
大数据·人工智能
太子釢2 小时前
用 AI 完整实现 Github 客户端:基于 KMP + SwiftUI + Compose
android·人工智能·ios
特立独行的猫a3 小时前
AI 智能硬件:下一个风口的底层逻辑与产品机会
人工智能·ai·智能硬件·趋势·风口·机会
tedcloud1233 小时前
book-to-skill 怎么部署?把技术书和项目文档变成 AI Agent 可复用知识
linux·运维·服务器·人工智能·开源
世优科技虚拟人3 小时前
AI数字人企业升级全息舱交互,数字人全息仓让展厅展馆更智慧
人工智能·ai数字人·全息舱数字人·全息数字人
webor20063 小时前
<七>从3秒记忆到过目不忘——语言模型的三代进化
人工智能·语言模型·自然语言处理