合成流程
输入、生成、QC、终审与审计规范
VDR 数据合成流程
本文定义合成链路的长期规则,不负责记录项目当前阶段或重复汇报批次结果。当前状态见 STATUS.md,实际结果见 生成样本分析.md。
目标是将页面材料转成可验证的 VDR 训练候选。模型仅以页面图片和固定任务 Prompt 为输入;OCR、Markdown、HTML、caption 和页面事实不得进入 Prompt,只用于离线校验。
页面来源
→ 统一页面 manifest
→ 图片 + 固定生成 Prompt(禁止附带 OCR/Markdown/page text/caption)
→ 生成 Query / Answer / Evidence 候选
→ 离线用 OCR/Markdown/HTML 校验 Answer / Evidence
→ 规则校验与去重
→ Q-A 质检集
→ query-image 检索训练集
→ 难负例与训练导出开源数据的事实依据见 开源数据集分析.md,Qwen 服务参数见 Qwen3.6-27B服务调用.md。
基本原则
- 训练 VDR embedding 最终只需要
query + image,但合成阶段必须保存answer + evidence,以验证 Query 是否确实由正样本页面支持。 - 图片本身用于生成 Query;OCR/布局文本或结构化表格仅作离线自动校验依据,绝不作为模型输入或 Prompt 附加上下文。
evidence不能由模型自由编造,也不能把页面摘要当作数值真值。可在页面文本中验证的样本标记为pass;只有图片、缺少可验证文本的样本,在 V1.1 宽松策略下可标记为provisional并进入趋势观察导出;正式严格训练仍可只取pass。- 对
shennong2_chart,只有markdown/html可作数值和表格答案依据;caption不得作为生成输入,仅可用于离线人工抽检参考。 - 训练和评测按
document_id切分,不能将同一文档的不同页同时放入训练与评测。 - 原始页面、OCR、manifest、生成结果都属于业务/数据产物,不提交 Git。
统一页面清单与校验真值
V1 同时支持 shennong2_chart 与业务页面。生成输入和校验真值必须物理隔离,避免页面文字被误传给模型。
生成清单 _过程文件/页面清单.jsonl:
{
"document_id": "稳定文档 ID",
"page_id": "稳定页面 ID",
"image_path": "/可读取的页面图片路径",
"image_hash": "sha256",
"source": "shennong2_chart | business",
"data_role": "train_source | evaluation_only",
"source_split": "train | test | custom",
"language": "zh | en | mixed",
"metadata": {}
}离线真值 _过程文件/校验参考.jsonl:
{
"document_id": "稳定文档 ID",
"page_id": "稳定页面 ID",
"validation_mode": "strict_text | image_review",
"refs": [
{"type": "ocr | markdown | html", "content": "仅校验脚本可读的页面真值"}
]
}- 模型请求构造只使用
_过程文件/页面清单.jsonl中的图片和固定 Prompt;响应返回后,校验阶段才读取_过程文件/校验参考.jsonl。 data_role=train_source才能生成;evaluation_only必须立即拒绝。不能用上游名为train的 split 代替该门禁。image_path在生成和训练导出时必须可读。Parquet 内嵌图片先落为 PNG/JPEG;PDF 页面先渲染为 PNG。document_id + page_id是唯一页面身份;简单文件名和内部存储 URI 不能作为全局 ID。- 有 OCR/Markdown/HTML 时使用
strict_text;只有图片时使用image_review。V1.1 宽松策略下,image_review且 schema 通过的样本标记为provisional,可进入趋势观察导出;若要回到严格模式,只允许人工/独立视觉复核后的pass。
V1:单页、证据可校验的合成
范围
- 单页问题;当前严格基线每页生成
1条,代码允许后续调为2–3条。 - 模型输入仅限页面图片和固定生成 Prompt;禁止将 OCR、Markdown、HTML、caption、结构化字段或页面真值答案放入模型
content。 - 仅支持
span:字段抽取、说明项查找、图表/表格单元格查找。 - 同时覆盖检索式 Query 和页面问答式 Query,但不得使用“本页”“上图”“图 1”“subfigure (b)”等离开页面就失去语义的指代。
- 先使用训练 batch 内其他页面作为负样本;不挖掘显式 hard negatives。
这对应 colpali_train_set 的短字段问答、VisRAG 的检索式 Query、MP-DocVQA 的页面问答,以及 shennong2_chart 的表格单元格查询。
生成配置
- 使用 Qwen 非思考模式:
chat_template_kwargs.enable_thinking=false。 - 多模态
content固定为图片在前、文本 Prompt 在后;Prompt 不使用Think as needed,不要求模型输出推理过程。 - 正式请求使用
response_format.type=json_schema和strict=true约束候选结构;json_object不能保证字段完整。 - 当前初版统一使用
temperature=0;后续生成多样 Query 时再试0.6–0.8,并单独统计格式通过率、证据通过率和重复率。 - 当前使用
top_p=0.8、top_k=20、presence_penalty=1.5、max_tokens=1024。512在 84 页试点中出现过长度截断;提高上限不改变短 JSON 和finish_reason=stop门禁。 - 模型输出必须是严格 JSON 对象,顶层为
candidates数组;每个候选包含query、answer、evidence、answer_type、query_type。 - 模型边界的
answer和evidence始终为字符串数组;写入 QC 时再把 evidence 转为带page_id的对象数组。V1 只引用当前页,V3 可自然扩展到多页。 - JSON 示例只能描述字段结构,不能写入当前页面的真实答案作为示范,避免答案泄漏。
answer_type固定为span;不要在 V1 生成需要计算、跨页或视觉定位的问题。
严格质量门槛
- 输入:图片可读,
document_id + page_id唯一,页面语言已标记。 - Query:非空、长度合理、语言与页面一致;过滤纯噪声、答案式/列表式问题和泛化实体(如“某企业”“某时期”)。
- 上下文:V1.1 只拒绝“本页/上图/图 1/subfigure/this page”等强指代;轻度页面依赖不再硬拒。
- 证据:
strict_text要求答案命中真值;证据可适度放宽。答案命中但证据略偏 →provisional。image_review→provisional。 - 去重:同页 Query 精确去重;同文档后续做语义去重;终审拦截官方/测试重合。
- 导出:V1.1 趋势批次导出
pass + provisional;正式严格训练可只保留pass。 - 切分:先按
document_id留出约5%作为synthesis_dev_holdout,用于合成链路的内部开发检查,再生成/导出训练集,避免页级泄漏;它不是冻结 benchmark。
为什么初版门控较严格
V1 的目标不是最大化候选数量,而是先证明“图片 → Query → 可验证证据 → 训练对”的链路不会静默写入错误样本。严格门控基于以下风险:
- VDR 对比学习会把 Query 和页面直接拉近。错误 Query、错误数值或假正例会直接教坏 embedding,而且训练导出不含答案,导出后很难追查。
- 模型即使关闭思考并返回合法 JSON,仍会看错图表数值。试点中曾把
shennong2_chart的1200读成1250,格式门控无法发现,必须依靠离线真值。 - 模型会生成“图中、根据本文、文档标题”等页面依赖或低信息问题;这些问题作为页面问答可能成立,但作为独立检索 Query 容易形成捷径。
- 开源训练集与公开测试集存在真实重合;如果只相信上游 split 名,可能把 benchmark 页面用于训练。
- 多候选、高温和普通
json_object在实测中会增加字段缺失、候选数量不符、代码围栏和长度截断。初版先固定稳定组合,再逐项放宽。
严格门控是初版质量基线,不是永久标准。被拒绝的候选保留在 _过程文件/失败记录.jsonl,方便根据真实失败分布决定后续放宽,而不是直接丢失原因。
当前全部生成设置
以下设置对应 scripts/数据合成/流程阶段/生成检索样本.py:
- 服务:
model=ms-g9248pbm;默认读取 Git 忽略的scripts/数据合成/secrets.qwen.json,环境变量可覆盖。 - 图片:读取本地 PNG/JPEG/WebP/GIF,校验 SHA-256 后编码为 base64 data URI。
- 消息顺序:
image_url在前,固定文本 Prompt 在后。 - 思考:
chat_template_kwargs.enable_thinking=false;Prompt 禁止推导和解释。 - 结构:
response_format.type=json_schema、strict=true;顶层只能有candidates。 - 采样:
temperature=0、top_p=0.8、top_k=20、presence_penalty=1.5。 - 输出:默认每页
1个候选,max_tokens=1024,请求超时180s。 - 并发:默认
32,当前服务支持最高64;可通过--concurrency调整。 - 单次重试:API、网络和响应级 JSON 失败默认额外重试
2次。长度截断只自适应从1024提升到2048;若仍截断则停止,避免温度 0 下反复生成同一退化长输出。 - 断点续跑:每页结束后立即追加并
fsync到_过程文件/生成断点.jsonl。再次执行相同命令时,成功页面直接跳过,只重跑请求级失败和中断前尚未落盘的页面。 - “请求成功但候选被 Query/证据门控拒绝”属于已完成质检,默认不重采样,避免靠反复调用绕过质量规则。需要重新采样全部页面时显式增加
--fresh。 - 断点按 API 地址、模型、Prompt 版本、采样参数及脚本代码哈希隔离;这些条件变化时自动视为新批次,不与旧结果混合。
- V1 类型:
answer_type只能为span;不生成计算、计数、多答案推理或跨页问题。 - 风格:按来源选择固定
style_profile,只模仿数据集的问题类型,不把官方 Query、答案或页面文字放进 Prompt。 - Query 类型:由 profile 固定为
field/retrieval/visual/table/chart之一,模型不能自定义新值。
当前全部响应门控
API 响应必须满足:
- 只有一个
choice,且finish_reason=stop。 reasoning_content缺失、null或空字符串;统计中的reasoning_tokens必须为0或缺失。content不能为空,不能包含<think>或 Markdown 代码围栏,必须是单个严格 JSON 对象。- 顶层只能包含
candidates;候选数量与请求值一致。数量不一致时记录失败,但可继续检查已返回候选。 - 每个候选只能包含
query、answer、answer_type、evidence、query_type。 answer和evidence都必须是非空字符串数组;不能出现未解码的\uXXXX。answer_type必须为span;query_type必须与当前 source profile 完全一致。
当前全部 Query 门控
- Query 非空,并与页面语言一致。当前轻量检查要求中文页面含中文字符,英/法/德/西/意页面不得生成中文;它不是完整语言识别器。
- V1.1 放宽:只拒绝强指代,如“本页、上图、下图、图 1、subfigure、this page、above/below、Figure N”。不再因“图中/表中/文档中/according to the chart/the figure”一律拒绝。
- 中文 Query 出现重复的 4 字片段时拒绝,用于拦截退化表达。
- Prompt 仍禁止计算、趋势推断和跨页问题;V1 只接受页面可直接读取的事实。
这些规则已从初版保守设置下调一档,以便观察 27B 生成趋势。若后续发现页面依赖 Query 伤害检索独立性,可再收紧 DISALLOWED_CONTEXT。
当前证据与导出门控
strict_text:答案必须在 Markdown/HTML/OCR 真值中命中;证据允许规范化子串/前缀匹配。- 答案命中但证据略偏 →
provisional,不再直接fail。 image_review:V1.1 标记为provisional,进入趋势观察导出;不是严格证据验证后的pass。- 生成后由独立
终审检索样本.py拦截官方/测试 Query 重合与批内重复。 最终训练数据.jsonl当前只是导出pass + provisional的趋势产物;正式 Release 需经过单独门禁,严格训练可先筛pass。- 训练导出字段固定为
sample_id/query/image_path/document_id/page_id。
后续如何调整
- 扩大页面数:修改
准备生成数据.py --samples-per-source。 - 调整多样性:修改
生成检索样本.py --temperature和--candidates-per-page;每次只改一个变量并保留独立统计。 - 调整吞吐:修改
--concurrency,继续记录成功率、P95、长度截断和 token。 - 放宽上下文/标题规则:修改
生成检索样本.py的DISALLOWED_CONTEXT,并重新审计此前失败样本。 - 放宽语言规则:用正式语言识别器替换当前中日韩字符启发式检查。
- 放宽证据匹配:在
normalize_text()和verify_candidate()中加入表格行列对齐、数值标准化或可解释的模糊匹配;不能直接删除证据校验。 - 自动处理
needs_review:增加独立 OCR 或第二个视觉模型复核,且复核模型不能读取生成答案作为提示。 - 进入 V2:扩展
answer_type、增加derivation,分别实现 arithmetic/count/multi-span 校验后再开放。
V1 输出
完整质检集 人工抽检结果.jsonl(Git 忽略的离线质检产物,不得并入训练导出):
{
"sample_id": "shennong2_chart:page-003:01",
"document_id": "doc-001",
"page_id": "page-003",
"image_path": "/data/doc-001/page-003.png",
"image_hash": "sha256",
"query": "agent 的 CPU 使用率是多少?",
"answer": ["49.70%"],
"answer_type": "span",
"evidence": [
{"page_id": "page-003", "text": "|CPU|45.84%|49.70%|"}
],
"source": "shennong2_chart",
"query_type": "图表字段",
"language": "zh",
"generation": {
"model": "ms-g9248pbm",
"prompt_version": "v1.0",
"temperature": 0,
"thinking": false,
"finish_reason": "stop",
"usage": {"reasoning_tokens": 0}
},
"verification": {
"status": "pass",
"reasons": []
}
}状态定义:
pass:格式正确,答案和证据已由页面文本自动验证,或已通过独立视觉/人工复核;fail:格式、语言、证据或去重检查失败;provisional:格式通过,但缺少独立文本真值,或答案命中而证据仅宽松匹配;可用于趋势观察,不等于严格验证通过。
当前趋势批次的 最终训练数据.jsonl 保留 pass + provisional 样本的 sample_id、query、image_path、document_id、page_id。该文件名暂未改动,但其语义是趋势导出,不是正式训练 Release;训练导出禁止包含 answer、evidence、页面真值、caption 或校验记录。正式严格训练可再筛为只保留 pass。
V2:丰富问法与首轮难负例
在 V1 通过小规模试点后再加入:
comparison、arithmetic、count、multi-span和每页多 Query。计算题要保存公式、输入数值和表格行;多答案必须保存真正的 JSON 数组及原始顺序。general + specific两阶段问题生成,只保留实体、条件和范围完整的 specific Query。- 多温度采样、语言检测、格式清洗、
n-gram去重和语义去重;控制长度,避免页面原文被直接复制为 Query。 - 页面类型覆盖:知识点、操作步骤、前置条件、参数/接口、图表、方法、比较、故障排查。
- 首轮难负例:以 ANN 检索 Top-K 页面作为候选;若候选页包含正样本答案证据,则视为潜在假负例并剔除。
不同答案类型的校验:
span:答案可在当前页定位;multi-span:所有答案均存在且顺序一致;arithmetic:从被引用的值重新计算;count:保存可枚举的命中行、列或对象。
V3:跨页、判别与定位
- 支持跨页/跨文档、多跳问题和跨语言 Query;
evidence数组可引用多个页面。 - 难负例采用两阶段校验:ANN 召回候选后,由文本 reranker 或 VLM judge 判断相关性,剔除假负例。
- 采用课程学习:早期使用 in-batch 负例,中期逐步注入验证过的 hard negatives。
- 加入难度分层、平行文档和跨语言对齐;语言识别和格式清洗仍为必经步骤。
- 加入 bbox/patch 证据定位。
shennong2_chart的[0, 0, 1000, 1000]是占位 bbox,不能直接使用;必须由 OCR/布局或视觉模型重新定位。
开源数据带来的具体规则
colpali_train_set:同页可有多个不同 Query;不要混用其 test split;其列表答案是字符串表示,业务输出必须规范为 JSON 数组。- VisRAG Synthetic:保留少量关键词式检索,但不能让其主导;过滤空 Query、超长页面原文和不唯一的文件名 ID。
- VisRAG In-domain:同时保留检索式与页面问答式问题,但移除强页面上下文指代。
vdr-multilingual-train:生成、清洗、语义过滤和难负例是顺序流程;同文档其他页面不天然是负样本。tatdqa_train:答案类型决定验证规则;计算题必须保留公式和原始值。tabfquad_train_set:页面 caption 不是答案证据;自然问法需要防止实体与条件被泛化。shennong2_chart:生成输入为 PNG + 固定 Prompt;Markdown/HTML 仅作离线真值校验,caption 数值可能错误;自定义 train/eval split。
当前脚本
scripts/数据合成/流程阶段/准备生成数据.py:从本地开源训练 case 生成安全页面清单、隔离的校验真值和 review-only 原始 case。scripts/数据合成/流程阶段/生成检索样本.py:执行严格非思考请求、JSON/Query 门控、证据校验和最小训练导出;使用逐页_过程文件/生成断点.jsonl断点续跑。scripts/数据合成/流程阶段/终审检索样本.py:生成后检查官方/测试 Query 重合与批内重复,并重建训练导出。scripts/数据合成/流程阶段/审计生成结果.py:检查测试集隔离、官方 Query 拦截、reasoning token 和训练导出字段。scripts/数据合成/一键运行生成流程.py:按“准备(可选)→断点生成→终审→审计”顺序执行完整流程。
已有 _过程文件/页面清单.jsonl 时直接续跑:
source activate.sh
python scripts/数据合成/一键运行生成流程.py --concurrency 32首次准备并运行:
source activate.sh
python scripts/数据合成/一键运行生成流程.py --prepare --clean --samples-per-source 100 --concurrency 32同一配置中断或请求失败后,重新执行原命令即可;生成脚本若仍有请求级失败,一键脚本仍会继续对已完成样本执行终审和审计,最后返回非零状态。--fresh 会忽略生成断点,--prepare --clean 会删除整个工作目录(包括断点),两者均不是普通续跑参数。
当前实现使用 v1.1-relaxed;趋势批次已跑通,但不是正式 Release,且 ViDoRe eval* 尚未接入完整 blocklist。数量、Case 和失败分布统一见 生成样本分析.md。