数据格式与打包流水线
July 21, 2026 · View on GitHub
概述
训练所需的 JSONL 是打包后的格式:每一行包含多条原始样本拼接而成的单个序列,长度上限为 pack_length 个 token。这种做法通过消除 padding 浪费来最大化 GPU 利用率。
流水线:原始 JSONL → 分词 + 计数 → 打包 → 可用于训练的 JSONL。
1. 原始 OCR JSONL 格式
原始 JSONL 每行是一条训练样本:
{
"image_path": ["/absolute/path/to/image.png"],
"conversations": [
{
"from": "human",
"value": "<image>\n提取文档图片中正文的所有信息用markdown格式表示..."
},
{ "from": "gpt", "value": "# Title\n\nBody text ..." }
]
}
字段说明:
| 字段 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
image_path | list[str] | ✅ | 绝对路径。通常 1 张图,也支持多图。 |
conversations | list[dict] | ✅ | human / gpt 交替对话。human 值中的 <image> 占位符表示图片插入点。 |
2. 打包流水线
tools/pipeline_count_and_pack.py 做两件事:
- 计数阶段(并行):对每条样本分词,把 token 数写入
count_output_dir/*.jsonl - 打包阶段:按 First-Fit Decreasing 贪心策略,把样本打包到每行
pack_length个 token 的上限
配置文件:configs/data_list.txt
纯文本文件,每行一个路径:
/path/to/dataset_A.jsonl
/path/to/dataset_B.jsonl
/path/to/dataset_C.jsonl
# 以 # 开头的行会被忽略
运行
MODEL_PATH=/path/to/HunyuanOCR \
INPUT_LIST=./configs/data_list.txt \
PACK_OUTPUT=./data/parsing_packed_20480.jsonl \
bash scripts/pack_data.sh
可调参数(环境变量)
| 环境变量 | 默认值 | 含义 |
|---|---|---|
MODEL_PATH | (必填) | HunyuanOCR 基座模型目录(用于 tokenizer + processor) |
INPUT_LIST | ./configs/data_list.txt | 原始 JSONL 清单文件路径 |
COUNT_OUTPUT_DIR | ./data/parsing_jsonl_count | 计数阶段输出的临时目录 |
PACK_OUTPUT | ./data/parsing_packed_{PACK_LEN}.jsonl | 最终打包好的 JSONL |
PACK_LEN | 20480 | 每行打包序列的最大长度 |
NUM_PROCESSES | 32 | 计数阶段的多进程 worker 数 |
THREADS_PER_PROCESS | 8 | 每个计数 worker 的线程数 |
LOG_FILE | pack_data.log | 进度日志路径 |
输出格式(打包后的 JSONL)
每一行输出格式如下:
{
"packed_samples": [
{
"image_path": ["/abs/path/1.png"],
"conversations": [...]
},
{
"image_path": ["/abs/path/2.png"],
"conversations": [...]
},
...
],
"cu_seqlens": [0, 4123, 8567, ..., 20351],
"total_tokens": 20351
}
字段:
packed_samples:本 pack 内拼接的原始样本cu_seqlens:累计 token 边界(供 FlashAttention varlen 使用)total_tokens:合计 ≤pack_length
性能提示
- CPU 密集:随
NUM_PROCESSES近似线性扩展。128 核机器上建议NUM_PROCESSES=32~64。 - 图片数据不需要预加载,计数阶段只做文本分词;图片在训练时按需加载。
- 首次运行会生成计数缓存;使用相同的输入清单重跑时,计数阶段会自动跳过。
3. 校验打包结果
对打包后的 JSONL 做健康检查:
python -c "
import json
with open('./data/parsing_packed_20480.jsonl') as f:
for i, line in enumerate(f):
r = json.loads(line)
print(f'pack {i}: {len(r[\"packed_samples\"])} samples, '
f'{r[\"total_tokens\"]} tokens')
if i >= 3: break
"
预期输出类似:
pack 0: 7 samples, 20438 tokens
pack 1: 5 samples, 19821 tokens
pack 2: 12 samples, 20301 tokens
pack 3: 3 samples, 18654 tokens
每个 pack 都接近 pack_length=20480,这正是打包的目标。