Multipack — FFD Bin-Packing Sampler (v0.37.0)
Soup's largest single throughput win on chat fine-tuning over uneven-length data. Instead of padding every sample to max_length, Multipack uses First-Fit-Decreasing bin packing to group variable-length samples into bins approaching batch_size × max_length — eliminating padding waste.
training:
multipack: true
packing: false # mutually exclusive with multipackHow it composes
- Multipack picks WHICH samples go together (FFD packing).
- Multipack is a batch sampler: it only chooses which samples share a batch. It adds no attention-mask logic of its own. A helper that would pick a FlashAttention varlen path or a 4D mask exists in the source (
utils/neat_packing.py), but nothing calls it at v0.75.2, so no such path is auto-selected. - Do not reach for
packing_cross_doc_attn_mask. It is refused at config load since v0.75.0 and never built a mask on any release. Document isolation underpacking: truecomes from TRL's defaultbfdstrategy instead.
Architecture allowlist
38 architectures as of v0.51, up from the 18 this release shipped with: Llama, Mistral, Mixtral, Qwen / Qwen2 / Qwen3 / Qwen2-MoE, Gemma 1/2/3, Phi / Phi-3 / Phi-4, DeepSeek V2/V3, Falcon, StableLM, SmolLM2, Granite and Granite-MoE, GLM-4 and GLM-5, Kimi, MiniMax, QwQ, QVQ, GPT-OSS, Magistral, Devstral, Ministral, MedGemma, LFM2, Cogito, Hunyuan, Ernie, Yi, Baichuan and ChatGLM. See the v0.51 model catalog for what that expansion covered.
Unknown architectures fail loudly at config-load instead of silently no-opping (critical fix vs Axolotl's silent-miss footgun).
Scope
Multipack is sft / pretrain only on the transformers backend. Preference / RLHF trainers and MLX backend get distinct error messages naming the actual reason. The sampler is wired into HF Trainer's _get_train_sampler and has been live since v0.40.4.
Under more than one process (FSDP, DeepSpeed ZeRO or DDP, that is num_processes > 1) the multipack DataLoader is routed through accelerator.prepare, so accelerate hands whole packed bins to each rank in turn and the packing survives. The bin seed is the same on every rank, so all ranks agree on the global bin order before it is sharded. A single-GPU run takes the plain DataLoader. Upstream describes that multi-GPU routing as covered by tests that mock the accelerator, with a real run on two or more GPUs still outstanding, so treat it as unvalidated on real hardware. The sampler reads each row's token count from input_ids, or from a length column when there is none; a dataset with neither logs an all-zeros warning, because packing then has nothing to work from.
DoS hardening
- FFD packer caps at 1M items, a bound on retained memory rather than on runtime: since v0.75.0 placement descends a segment tree of per-bin remaining capacity, so it is O(N log N) rather than the O(N²) scan it used to be. Measured on one box, 5.5x faster at 1,000 rows, 37x at 10,000 and 96x at 30,000, with the packing itself verified unchanged against the old function over 6,000 randomized cases. Which item lands in which bin is the property that had to hold: changing it would silently change every multipack run
- 4D mask builder caps allocations at 2³¹ cells
- Chat-template Jinja analyzer caps at 128 KB
- Every numeric input rejects
boolexplicitly
Jinja template analyzer
The JinjaTemplateAnalyzer (also v0.37.0) walks chat-template ASTs to discover non-standard message.<field> references (tool_calls, name, weight, train) — used by the v0.36.0 train_on_messages_with_train_field path so per-message training masks are aware of fields beyond role / content. The analyzer parses templates without rendering them, so a crafted soup.yaml cannot trigger SSRF.
Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.