Skip to content

[bugfix] fix streaming packing with truncation_strategy=split#9738

Open
Kale112 wants to merge 2 commits into
modelscope:mainfrom
Kale112:fix/streaming-packing-split
Open

[bugfix] fix streaming packing with truncation_strategy=split#9738
Kale112 wants to merge 2 commits into
modelscope:mainfrom
Kale112:fix/streaming-packing-split

Conversation

@Kale112

@Kale112 Kale112 commented Jul 13, 2026

Copy link
Copy Markdown

PR type

  • Bug Fix

PR information

Fixes #8285.

IterablePackingDataset._fetch_data_out_queue assumed template.encode always returns a single dict. With truncation_strategy='split', an over-long sample is split into a list of chunks (template/base.py:_encode_truncated returns batched, and encode returns that list when more than one chunk is produced). _fetch_data_out_queue then ran len(data['input_ids']) on the list, raising:

TypeError: list indices must be integers or slices, not str

This only triggers for streaming=true + packing=true + truncation_strategy=split (a common long-document streaming pretraining setup). The non-streaming path is unaffected: EncodePreprocessor materializes split chunks into separate dataset rows before PackingDataset wraps them, so it only ever sees single-dict rows.

Fix: flatten the list into one bin-packing item per chunk, mirroring the existing pattern in RowPreprocessor.batched_preprocess (if isinstance(row, dict): row = [row]). Input order is preserved via res[i] (required for the sequential packing strategy). No behaviour change for the dict path (delete/left/right): each successful sample still produces exactly one (chunk, length) item in the same order as before, and failed/empty encodes are still skipped.

Experiment results

  • Added unit test tests/train/test_packing.py::test_streaming_packing_split covering the dict (delete/left/right), split-list, and failed-encode (empty dict) cases; asserts correct flattening, input-order preservation, and that flattened chunks feed calculate_matched_group without error (tokens conserved).
  • pre-commit run --files swift/dataset/packing.py tests/train/test_packing.py passes (flake8 / isort / yapf / trailing-whitespace / double-quote-string-fixer ...).
  • Local test run:
$ python3 -m pytest tests/train/test_packing.py::test_streaming_packing_split -x -q
.
1 passed in 3.29s

IterablePackingDataset._fetch_data_out_queue assumed template.encode always returns a dict, but with truncation_strategy='split' an over-long sample is split into a list of chunks, causing TypeError: list indices must be integers or slices, not str. Flatten the list (mirroring RowPreprocessor.batched_preprocess) into one bin-packing item per chunk, preserving input order via res[i]. No behaviour change for delete/left/right (dict) path. Fixes modelscope#8285.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request fixes a crash that occurs when using streaming, packing, and splitting together. It updates _fetch_data_out_queue in swift/dataset/packing.py to correctly flatten list-of-chunks outputs from template.encode and adds a corresponding regression test. The review feedback suggests adding defensive checks to ensure each chunk is not empty and contains the 'input_ids' key before accessing it to prevent potential KeyError or TypeError exceptions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread swift/dataset/packing.py Outdated
Comment on lines +206 to +207
for chunk in data:
last_res.append((chunk, len(chunk['input_ids'])))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure robust defensive programming, it is recommended to verify that each chunk is not empty and contains the 'input_ids' key before attempting to access it. This prevents potential KeyError or TypeError if an unexpected or empty chunk is returned by the template encoder.

Suggested change
for chunk in data:
last_res.append((chunk, len(chunk['input_ids'])))
for chunk in data:
if chunk and 'input_ids' in chunk:
last_res.append((chunk, len(chunk['input_ids'])))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, applied in f9b0280. Added the defensive guard (if chunk and 'input_ids' in chunk) and a regression test (test_streaming_packing_split_skips_malformed_chunks) covering empty chunks and chunks missing 'input_ids'.

Address review on modelscope#9738: guard the inner loop with a check that each chunk is
non-empty and contains the 'input_ids' key before accessing it, so an empty
chunk or one missing 'input_ids' is skipped instead of raising KeyError/TypeError.
Mirrors RowPreprocessor's skip-bad-data behaviour. Adds
test_streaming_packing_split_skips_malformed_chunks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pretrain阶段,streaming 和 packing不兼容的问题

1 participant