ms-swift 数据打包(Packing)机制详解
2026-01-12
ms-swift
00

目录

ms-swift 数据打包(Packing)机制详解
目录
什么是 Packing?
传统训练 vs Packing 训练
为什么需要 Packing?
问题:传统训练的浪费
解决方案:Packing
Packing 的两个阶段
阶段1:数据预处理(Cached Dataset)
阶段2:训练时打包(Packing)
完整使用流程
Step 1: 数据预处理(推荐做法)
1.1 准备原始数据
1.2 执行预处理命令
1.3 预处理输出结构
Step 2: 训练时启用 Packing
2.1 训练命令
2.2 训练日志示例
核心原理解析
1. Bin-packing 算法
2. 数据拼接机制
3. 多进程并行打包
配置参数详解
预处理阶段参数
训练阶段参数
⚠️ 配置一致性要求
日志验证
如何确认 Packing 已生效?
1. 预处理日志
2. 训练日志 - Packing 详情
3. 数据集大小变化
4. 训练速度提升
最佳实践
1. 什么时候使用 Packing?
✅ 推荐使用 Packing:
❌ 不推荐使用 Packing:
2. 数据预处理策略
方案 A:全量预处理(推荐)
方案 B:直接训练 + Packing
3. 参数调优
Packing Length 选择
Packing Num Proc 选择
4. 多数据集 Packing
5. 与 Loss Scale 结合使用
常见问题
Q1: Packing 后样本数量减少,是否会影响训练效果?
Q2: Packing 是否会影响模型效果?
Q3: 预处理后可以修改 max_length 吗?
Q4: 如何查看 Packing 的效果?
Q5: Packing 支持多模态数据吗?
Q6: 如何调试 Packing 相关问题?
Q7: S3/OSS 路径支持问题
Q8: Streaming 模式支持 Packing 吗?
Q9: 如何批量处理多个数据集?
Q10: Packing 与 Gradient Accumulation 如何配合?
总结
Packing 的核心优势
推荐工作流
快速上手
参考资料
更新日志

ms-swift 数据打包(Packing)机制详解

目录


什么是 Packing?

Packing(数据打包) 是一种将多个短样本合并成一个长样本的训练优化技术。通过智能地将多个训练样本拼接在一起,可以显著提高 GPU 利用率和训练效率。

传统训练 vs Packing 训练

展开代码
传统训练(带 padding): ┌─────────────────────────────────┐ │ 样本1: [tokens] + [PAD PAD PAD] │ 实际利用率: 50% ├─────────────────────────────────┤ │ 样本2: [tokens tokens] + [PAD] │ 实际利用率: 75% ├─────────────────────────────────┤ │ 样本3: [tokens] + [PAD PAD PAD] │ 实际利用率: 50% └─────────────────────────────────┘ Packing 训练(无 padding): ┌─────────────────────────────────┐ │ Pack1: [样本1][样本2][样本3] │ 实际利用率: 98% ├─────────────────────────────────┤ │ Pack2: [样本4][样本5] │ 实际利用率: 95% └─────────────────────────────────┘

为什么需要 Packing?

问题:传统训练的浪费

在多模态大模型训练中,样本长度往往差异巨大:

  • 短对话样本:500 tokens
  • 长文档样本:8000 tokens
  • 图像描述样本:2000 tokens

如果设置 max_length=10240,短样本会产生大量 padding,导致:

  • GPU 利用率低:大量计算资源浪费在 padding 上
  • 训练速度慢:实际有效计算占比低
  • 显存浪费:padding 占用显存但不产生梯度

解决方案:Packing

通过 Bin-packing 算法,将多个短样本智能组合:

  • GPU 利用率提升:减少 90%+ 的 padding
  • 训练速度提升:相同数据量下,训练 step 数减少 30-50%
  • 显存效率提升:更多有效 tokens 参与计算

Packing 的两个阶段

ms-swift 的 Packing 分为两个独立阶段:

阶段1:数据预处理(Cached Dataset)

graph LR
    A[原始JSON数据] --> B[swift export]
    B --> C[Tokenization]
    C --> D[计算长度]
    D --> E[保存到磁盘]
    E --> F[Cached Dataset]

阶段2:训练时打包(Packing)

graph LR
    A[Cached Dataset] --> B[加载数据+长度]
    B --> C[Bin-packing算法]
    C --> D[生成打包索引]
    D --> E[训练时拼接]

完整使用流程

Step 1: 数据预处理(推荐做法)

1.1 准备原始数据

确保数据格式正确(JSON 格式):

json
展开代码
[ { "messages": [ {"role": "user", "content": "你好"}, {"role": "assistant", "content": "你好!有什么可以帮你的?"} ], "images": ["s3://path/to/image.jpg"] }, ... ]

1.2 执行预处理命令

bash
展开代码
#!/bin/bash # 设置环境变量(与训练时保持一致) export MODELSCOPE_CACHE='/mnt/jfs/copilot/yhl/modelscope_cache' export IMAGE_MAX_TOKEN_NUM=5000 # 数据集路径 DATASET="s3://yanhaolong/data/sft_data/click_pretrain.json" OUTPUT_DIR="s3://yanhaolong/data/sft_data/click_pretrain_cached_resize0_q3_len10k_img5k" # 执行预处理 IMAGE_MAX_TOKEN_NUM=5000 swift export \ --resize 0 \ --model /mnt/jfs/copilot/yhl/checkpoint/Qwen3-VL-30B-A3B-Instruct \ --dataset $DATASET \ --to_cached_dataset true \ --split_dataset_ratio 0 \ --dataset_num_proc 10 \ --max_length 10240 \ --output_dir $OUTPUT_DIR

参数说明

  • --resize 0:不调整图像大小(0表示原始大小)
  • --model:模型路径(用于加载 tokenizer 和图像处理器)
  • --dataset:原始数据集路径(支持本地/S3/OSS)
  • --to_cached_dataset true关键,启用 cached dataset 生成
  • --max_length 10240:最大序列长度(与训练时保持一致)
  • --dataset_num_proc 10:数据处理并行进程数
  • --output_dir:输出目录(支持 S3/OSS)

1.3 预处理输出结构

展开代码
s3://yanhaolong/data/.../click_pretrain_cached_resize0_q3_len10k_img5k/ ├── train/ │ ├── data-00000-of-00001.arrow # 编码后的数据(Arrow格式) │ ├── dataset_info.json # 数据集元信息 │ └── state.json # 状态信息 └── val/ (如果设置了 split_dataset_ratio > 0) ├── data-00000-of-00001.arrow └── ...

包含的字段

  • input_ids: List[int] - tokenized 输入序列
  • labels: List[int] - 标签序列(用于计算 loss)
  • length: int - 序列实际长度(Packing 的关键
  • images: 图像数据(如果有)
  • videos: 视频数据(如果有)
  • 其他多模态数据...

Step 2: 训练时启用 Packing

2.1 训练命令

bash
展开代码
#!/bin/bash # 环境变量(必须与预处理时一致) export IMAGE_MAX_TOKEN_NUM=5000 export MODELSCOPE_CACHE='/mnt/jfs/copilot/yhl/modelscope_cache' # 训练 NPROC_PER_NODE=8 swift sft \ --model /mnt/jfs/copilot/yhl/checkpoint/Qwen3-VL-30B-A3B-Instruct \ --cached_dataset 's3://yanhaolong/data/sft_data/click_pretrain_cached_resize0_q3_len10k_img5k' \ --packing true \ --max_length 10240 \ --packing_length 10240 \ --packing_num_proc 4 \ --train_type lora \ --num_train_epochs 3 \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 16 \ --learning_rate 1e-4 \ --output_dir output/qwen3_vl_packed

关键参数

  • --cached_dataset:指向预处理后的数据目录(支持 S3/OSS)
  • --packing true启用 Packing
  • --max_length 10240:必须与预处理时一致
  • --packing_length 10240:打包后的目标长度(默认=max_length)
  • --packing_num_proc 4:打包算法的并行进程数(可选)

2.2 训练日志示例

启用 Packing 后,你会看到以下日志:

展开代码
================================================================================ 数据打包(Packing)处理详情: -------------------------------------------------------------------------------- 数据集样本总数: 393230 数据长度列表总数: 393230 最大打包长度(packing_length): 10240 打包进程数(packing_num_proc): 4 每批次处理大小(PACKING_BATCH_SIZE): 1000 严格模式(strict): False 开始使用 4 个进程并行打包 393230 条数据... ================================================================================ Packing (num_proc=4): 100%|███████████████████| 393230/393230 [02:15<00:00]

打包完成后:

展开代码
train_dataset: Dataset({ features: ['input_ids', 'labels', 'images', 'length'], num_rows: 185432 # 注意:从 393230 → 185432,减少了约 53% })

核心原理解析

1. Bin-packing 算法

ms-swift 使用 First Fit Decreasing (FFD) 变体的 Bin-packing 算法:

python
展开代码
# swift/llm/dataset/utils.py:122-132 def calculate_matched_group(template, sequences, packing_length: int): import binpacking # sequences: [(index, length), (index, length), ...] # 将样本打包成总长度 ≈ packing_length 的组 sequences = binpacking.to_constant_volume(sequences, packing_length, weight_pos=1) return sequences

算法示例

展开代码
输入样本: 样本0: 3000 tokens 样本1: 8000 tokens 样本2: 2000 tokens 样本3: 5000 tokens 样本4: 1000 tokens 样本5: 7000 tokens packing_length = 10240 Bin-packing 结果: Group 0: [样本1(8000), 样本4(1000)] → 总长 9000 (87.9%) Group 1: [样本5(7000), 样本2(2000)] → 总长 9000 (87.9%) Group 2: [样本3(5000), 样本0(3000)] → 总长 8000 (78.1%) 原始:6个样本 → 打包后:3个 packed 样本 训练效率提升:6 steps → 3 steps (50% 加速)

2. 数据拼接机制

python
展开代码
# swift/llm/template/base.py:575-596 def packing_row(self, row: List[Dict[str, Any]]) -> Dict[str, Any]: packed = {} length = [] # 拼接 input_ids, labels, loss_scale 等 for key in ['input_ids', 'labels', 'loss_scale', 'position_ids']: packed[key] = sum((x.get(key) or [] for x in row), start=[]) # 重新计算 position_ids(每个样本独立计算) if 'position_ids' not in packed: packed['position_ids'] = sum((list(range(x)) for x in length), start=[]) # 处理多模态数据(图像、视频等) packed.update(self._data_collator_mm_data(row)) return packed

拼接示例

python
展开代码
# 原始 3 个样本 row = [ { 'input_ids': [1, 2, 3, 4], 'labels': [-100, -100, 3, 4], 'length': 4 }, { 'input_ids': [5, 6, 7], 'labels': [-100, 6, 7], 'length': 3 }, { 'input_ids': [8, 9, 10, 11, 12], 'labels': [-100, -100, 10, 11, 12], 'length': 5 } ] # Packing 后 packed = { 'input_ids': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], # 直接拼接 'labels': [-100, -100, 3, 4, -100, 6, 7, -100, -100, 10, 11, 12], # 保留 mask 'position_ids': [0, 1, 2, 3, 0, 1, 2, 0, 1, 2, 3, 4], # 每个样本独立计算位置 'length': [4, 3, 5] # 保留原始长度信息 }

关键特性

  • Position IDs 独立:每个样本的位置编码从 0 开始,避免位置信息混淆
  • Labels Mask 保留-100 标记保留,确保 loss 计算正确
  • 多模态数据:图像、视频等数据正确分配给对应样本

3. 多进程并行打包

python
展开代码
# swift/llm/dataset/utils.py:162-209 # 将数据分成多个 chunk,使用多进程并行处理 packing_num_proc = 4 chunked_lengths = split_list(lengths, packing_num_proc) for i in range(packing_num_proc): worker = mp.Process(target=self.create_packed_idx, args=(i, offset, chunked_lengths[i])) worker.start() # 收集结果 packed_idx = [] packed_length = [] for rank, sequences, data_len in queue: packed_idx += [[x[0] for x in seq] for seq in sequences] packed_length += [sum(x[1] for x in seq) for seq in sequences]

并行效果

  • 393k 样本,单进程打包:~8分钟
  • 393k 样本,4进程打包:~2分钟

配置参数详解

预处理阶段参数

参数说明推荐值注意事项
--to_cached_dataset启用 cached dataset 生成true必须设置
--max_length最大序列长度10240与训练时必须一致
--dataset_num_proc数据处理并行数10-20根据 CPU 核心数调整
--split_dataset_ratio验证集比例00.010 表示不划分
--resize图像缩放00 表示不缩放
IMAGE_MAX_TOKEN_NUM图像最大 token 数5000与训练时必须一致

训练阶段参数

参数说明推荐值注意事项
--cached_datasetCached dataset 路径S3/本地路径必须指向预处理输出目录
--packing启用 Packingtrue核心参数
--max_length最大序列长度10240与预处理时必须一致
--packing_length打包目标长度10240默认=max_length
--packing_num_proc打包并行进程数4-8建议 4-8
IMAGE_MAX_TOKEN_NUM图像最大 token 数5000与预处理时必须一致

⚠️ 配置一致性要求

以下配置必须在预处理和训练时保持一致

bash
展开代码
# 预处理时 --max_length 10240 IMAGE_MAX_TOKEN_NUM=5000 --resize 0 # 训练时(必须相同) --max_length 10240 IMAGE_MAX_TOKEN_NUM=5000 --resize 0 # 如果训练时需要图像处理

如果不一致,可能导致:

  • ❌ 序列长度超出限制
  • ❌ 图像 token 数量不匹配
  • ❌ 训练崩溃或效果变差

日志验证

如何确认 Packing 已生效?

1. 预处理日志

展开代码
Dataset saved to local directory `/tmp/cached_dataset_xxx` Copying dataset to remote storage: s3://yanhaolong/data/.../cached_dataset Train dataset copied: 4 files Successfully copied dataset to: s3://yanhaolong/data/.../cached_dataset

2. 训练日志 - Packing 详情

展开代码
================================================================================ 数据打包(Packing)处理详情: -------------------------------------------------------------------------------- 数据集样本总数: 393230 数据长度列表总数: 393230 最大打包长度(packing_length): 10240 打包进程数(packing_num_proc): 4 每批次处理大小(PACKING_BATCH_SIZE): 1000 严格模式(strict): False 开始使用 4 个进程并行打包 393230 条数据... ================================================================================

3. 数据集大小变化

展开代码
# 打包前 train_dataset: Dataset({ features: ['input_ids', 'labels', 'length', 'images'], num_rows: 393230 }) # 打包后 train_dataset: Dataset({ features: ['input_ids', 'labels', 'length', 'images'], num_rows: 185432 # 减少约 53% })

4. 训练速度提升

展开代码
# 无 Packing Epoch 1/3: 100%|████████| 49154/49154 [2:15:30<00:00] # 使用 Packing Epoch 1/3: 100%|████████| 23179/23179 [1:05:20<00:00]

训练 step 数减少约 53%,训练时间减少约 50%+(取决于样本长度分布)。


最佳实践

1. 什么时候使用 Packing?

✅ 推荐使用 Packing:

  • 预训练(Pretraining):样本通常较短,Packing 效果显著
  • SFT 混合数据集:包含短对话和长文档,长度差异大
  • 多模态训练:图像数量不同导致长度差异
  • 小样本微调:样本数量少但长度不一,提高数据利用率

❌ 不推荐使用 Packing:

  • 样本长度接近 max_length:打包空间有限,效果不明显
  • 样本长度分布均匀:Packing 收益小
  • 需要精确控制 batch 内容:Packing 会改变样本组合

2. 数据预处理策略

方案 A:全量预处理(推荐)

bash
展开代码
# 预处理所有数据集 for DATASET in "${DATASET_PATHS[@]}"; do swift export \ --model $MODEL \ --dataset $DATASET \ --to_cached_dataset true \ --max_length 10240 \ --output_dir "${DATASET}_cached" done

优点

  • ✅ 训练启动快(无需重新 tokenization)
  • ✅ 多次训练复用
  • ✅ 方便调试和验证

方案 B:直接训练 + Packing

bash
展开代码
# 不预处理,直接训练时 Packing swift sft \ --model $MODEL \ --dataset $DATASET \ --packing true \ --max_length 10240

优点

  • ✅ 简单,一步到位
  • ✅ 适合单次实验

缺点

  • ❌ 每次训练都要重新处理数据(CPU 密集)
  • ❌ 训练启动慢

3. 参数调优

Packing Length 选择

bash
展开代码
# 保守策略:略小于 max_length,留出空间 --packing_length 9216 # 90% of 10240 # 激进策略:等于 max_length,最大化利用 --packing_length 10240 # 自适应策略:根据样本长度分布决定 # 如果大部分样本 < 5000 tokens: --packing_length 10240 # 可以打包 2-3 个样本 # 如果大部分样本 > 8000 tokens: --packing_length 8192 # 避免单个样本浪费空间

Packing Num Proc 选择

bash
展开代码
# 小数据集(< 10k 样本) --packing_num_proc 1 # 中等数据集(10k-100k 样本) --packing_num_proc 4 # 大数据集(> 100k 样本) --packing_num_proc 8

注意:过多进程可能导致内存压力,建议监控内存使用。

4. 多数据集 Packing

bash
展开代码
# 多个 cached dataset 拼接 swift sft \ --cached_dataset 's3://path/dataset1_cached' \ 's3://path/dataset2_cached' \ 's3://path/dataset3_cached' \ --packing true \ --max_length 10240

注意

  • ✅ 所有 cached dataset 必须使用相同的配置(max_length, IMAGE_MAX_TOKEN_NUM 等)
  • ✅ 自动拼接后再进行 Packing
  • ⚠️ 如果需要对每个数据集应用不同的 loss_scale,参考 per_dataset_loss_scale 功能

5. 与 Loss Scale 结合使用

bash
展开代码
# dataset_info.json { "dataset1": { "file_name": "data1.json", "loss_scale": "last_round" # 只计算最后一轮的 loss }, "dataset2": { "file_name": "data2.json" # 使用全局默认 loss_scale } } # 训练 swift sft \ --cached_dataset 's3://path/dataset1_cached' \ 's3://path/dataset2_cached' \ --packing true \ --loss_scale default # 全局默认

Packing 会保留每个样本的 loss_scale 信息,正确计算 loss。


常见问题

Q1: Packing 后样本数量减少,是否会影响训练效果?

A: 不会。Packing 只是改变了样本的组合方式,每个原始样本的数据和 loss 计算都保持不变。

  • 原始:10000 个样本,每个样本计算一次 loss
  • Packing:5000 个 packed 样本,但仍然计算 10000 次 loss(每个原始样本独立计算)

训练的有效数据量和 epoch 定义不变。

Q2: Packing 是否会影响模型效果?

A: 研究表明,Packing 不会损害模型效果,甚至可能略有提升:

  • ✅ 更多有效 tokens 参与训练(减少 padding 浪费)
  • ✅ Batch 内样本多样性增加(不同长度的样本组合)
  • ⚠️ 需要注意 position_ids 正确实现(ms-swift 已正确处理)

参考论文:Efficient Training of Language Models to Fill in the Middle

Q3: 预处理后可以修改 max_length 吗?

A: 不可以。Cached dataset 已经按照预处理时的 max_length 进行了截断和编码,修改后会导致:

  • ❌ 序列长度超出预期
  • ❌ Packing 算法失效
  • ❌ 训练崩溃

如需修改 max_length,必须重新运行 swift export 预处理。

Q4: 如何查看 Packing 的效果?

A: 查看训练日志:

python
展开代码
# 方法1:对比样本数 原始样本数 / Packing后样本数 = 打包效率 例如:393230 / 1854322.12 表示平均每个 packed 样本包含 2.12 个原始样本 # 方法2:对比训练时间 无Packing训练时间 / Packing训练时间 = 加速比 例如:2小时15分 / 1小时5分 ≈ 2.08x 加速

Q5: Packing 支持多模态数据吗?

A: 完全支持。ms-swift 的 Packing 机制对多模态数据(图像、视频、音频)做了特殊处理:

python
展开代码
# swift/llm/template/base.py:595 packed.update(self._data_collator_mm_data(row))
  • ✅ 图像数据正确分配给对应样本
  • ✅ 视频帧序列保持完整
  • ✅ 音频数据独立处理

Q6: 如何调试 Packing 相关问题?

A: 启用详细日志:

bash
展开代码
export SWIFT_LOG_LEVEL=DEBUG swift sft \ --cached_dataset $DATASET \ --packing true \ --logging_steps 1 \ --save_steps 100

查看关键日志:

  1. Packing 详情:打包进程数、样本数、打包后数量
  2. 数据加载:每个 batch 的 shape 和内容
  3. Loss 计算:验证 loss_scale 是否正确应用

Q7: S3/OSS 路径支持问题

A: ms-swift 完全支持 S3/OSS 远程存储:

bash
展开代码
# 预处理:输出到 S3 swift export \ --dataset s3://bucket/data.json \ --output_dir s3://bucket/cached_dataset # 训练:从 S3 加载 swift sft \ --cached_dataset s3://bucket/cached_dataset

注意

  • ✅ 支持 s3://oss:// 协议
  • ✅ 自动处理 s3:/ 单斜杠(代码会自动修正)
  • ⚠️ 确保配置了正确的 AWS/OSS 凭证

Q8: Streaming 模式支持 Packing 吗?

A: 支持,但使用不同的实现:

bash
展开代码
swift sft \ --dataset $DATASET \ --streaming true \ --packing true
  • 使用 IterablePackingDataset 而非 PackingDataset
  • 动态打包,使用 packing_interval 控制
  • 不支持 cached_dataset + streaming 组合

Q9: 如何批量处理多个数据集?

A: 使用脚本自动化:

bash
展开代码
#!/bin/bash DATASET_PATHS=( "s3://path/dataset1.json" "s3://path/dataset2.json" "s3://path/dataset3.json" ) for DATASET in "${DATASET_PATHS[@]}"; do DATASET_DIR=$(dirname "$DATASET") DATASET_FILENAME=$(basename "$DATASET" .json) OUTPUT_DIR="${DATASET_DIR}/${DATASET_FILENAME}_cached_q3_len10k_img5k" # 跳过已处理 if aws s3 ls "$OUTPUT_DIR" &> /dev/null; then echo "⚠️ Skipping $DATASET_FILENAME (already exists)" continue fi # 预处理 IMAGE_MAX_TOKEN_NUM=5000 swift export \ --model $MODEL \ --dataset $DATASET \ --to_cached_dataset true \ --max_length 10240 \ --output_dir $OUTPUT_DIR echo "✓ Completed: $DATASET_FILENAME" done

Q10: Packing 与 Gradient Accumulation 如何配合?

A: 完全兼容,效果叠加:

bash
展开代码
swift sft \ --cached_dataset $DATASET \ --packing true \ --per_device_train_batch_size 1 \ --gradient_accumulation_steps 16

实际效果

  • 无 Packing:1 样本/step × 16 accumulation = 16 样本/update
  • 有 Packing(平均2样本/pack):2 样本/step × 16 accumulation = 32 样本/update
  • 训练速度和显存占用保持不变,但有效数据量翻倍

总结

Packing 的核心优势

  1. 训练效率提升:减少 30-50% 的训练时间
  2. GPU 利用率提升:减少 90%+ 的 padding 浪费
  3. 显存效率提升:相同显存可训练更大 batch
  4. 完全无损:不影响模型效果,loss 计算保持正确

推荐工作流

graph TD
    A[准备原始数据] --> B[执行 swift export 预处理]
    B --> C[验证 cached dataset]
    C --> D[训练时启用 --packing true]
    D --> E[监控日志验证效果]
    E --> F[多次训练复用 cached dataset]

快速上手

bash
展开代码
# 1. 预处理 IMAGE_MAX_TOKEN_NUM=5000 swift export \ --model Qwen/Qwen3-VL-8B \ --dataset s3://path/data.json \ --to_cached_dataset true \ --max_length 10240 \ --output_dir s3://path/data_cached # 2. 训练 NPROC_PER_NODE=8 swift sft \ --model Qwen/Qwen3-VL-8B \ --cached_dataset s3://path/data_cached \ --packing true \ --max_length 10240 \ --train_type lora \ --output_dir output

参考资料


更新日志

  • 2026-01-12:创建文档,详细说明 Packing 机制和使用方法
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:Dong

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC。本作品采用《知识共享署名-非商业性使用 4.0 国际许可协议》进行许可。您可以在非商业用途下自由转载和修改,但必须注明出处并提供原作者链接。 许可协议。转载请注明出处!