English translation is not available yet. Showing Russian content.
Что такое packing sequences и зачем он нужен?
Краткий тезис
Packing sequences — это техника оптимизации обучения LLM, при которой несколько коротких последовательностей объединяются в одну длинную с помощью специального токена-разделителя (например, EOS). Это позволяет избежать потерь вычислительных ресурсов на padding (добавление пустых токенов) и увеличить throughput (пропускную способность) обучения на 30–50%. В контексте Agentic RAG, где система часто обрабатывает множество коротких запросов и ответов инструментов, packing особенно эффективен.
1. Проблема: неэффективность паддинга при обучении LLM
При обучении языковых моделей на батчах (batch) последовательности разной длины обычно выравниваются с помощью padding — добавления специального токена [Вики/padded sequences|PAD до максимальной длины в батче. Это приводит к двум проблемам:
- Вычислительные потери: модель вычисляет attention и для паддинг-токенов, хотя они не несут информации. Например, если в батче есть последовательности длиной 10 и 1000 токенов, 99% вычислений тратится на паддинг.
- Искажение обучения: паддинг-токены могут влиять на градиенты (если не маскируются), хотя обычно используется attention mask для их игнорирования. Но даже с маской вычисления на паддинг не отменяются.
Термин throughput — количество обработанных токенов в секунду. Паддинг снижает throughput, так как GPU тратит время на бесполезные токены.
2. Что такое packing sequences
Packing sequences (упаковка последовательностей) — это метод, при котором несколько коротких последовательностей из батча конкатенируются (склеиваются) в одну длинную последовательность. Между ними вставляется специальный токен-разделитель, обычно EOS (End of Sequence). Затем эта упакованная последовательность подаётся на вход модели, а attention маскируется так, чтобы токены из разных исходных последовательностей не «видели» друг друга.
Пример:
- Исходные последовательности:
["Привет", "Как дела?", "Что нового?"] - После packing: "Привет<EOS>Как дела?<EOS>Что нового?<EOS>" (или без последнего EOS, зависит от реализации).
Attention mask для такой упакованной последовательности — это блочно-диагональная матрица, где каждый блок соответствует одной исходной последовательности. Токены внутри блока видят друг друга, но не видят токены из других блоков.
3. Зачем нужен packing sequences
Основные цели:
- Увеличение throughput: GPU обрабатывает больше реальных токенов за один проход. Типичный прирост — 30–50% (иногда до 2x) в зависимости от распределения длин.
- Снижение времени обучения: меньше wasted computation на паддинг.
- Экономия памяти: хотя упакованная последовательность длиннее, чем каждая отдельная, общее количество токенов в батче остаётся примерно тем же, но без паддинга. Однако attention mask становится разреженной, что может увеличить overhead, но в современных реализациях (например, FlashAttention) это компенсируется.
В контексте Agentic RAG агент может генерировать множество коротких вызовов инструментов и ответов. Packing позволяет эффективно обучать модель на таких данных, не раздувая батч паддингом.
4. Как работает attention masking при packing
Для упакованной последовательности attention mask строится следующим образом:
- Каждая исходная последовательность получает уникальный идентификатор (например,
sequence_id). - Маска разрешает attention только между токенами с одинаковым
sequence_id. - Токены-разделители (EOS) обычно приписываются к предыдущей последовательности или игнорируются.
Пример маски для двух последовательностей [A, B] и [C, D], упакованных как [A, B, EOS, C, D]:
| A | B | EOS | C | D | |
|---|---|---|---|---|---|
| A | 1 | 1 | 0 | 0 | 0 |
| B | 1 | 1 | 0 | 0 | 0 |
| EOS | 0 | 0 | 0 | 0 | 0 |
| C | 0 | 0 | 0 | 1 | 1 |
| D | 0 | 0 | 0 | 1 | 1 |
Здесь EOS изолирован (не участвует в attention), а блоки A-B и C-D независимы.
5. Сравнение подходов: padding vs bucketing vs packing
| Метод | Описание | Преимущества | Недостатки |
|---|---|---|---|
| Padding | Дополнение до макс. длины в батче | Простота реализации | Потеря compute, снижение throughput |
| Bucketing | Группировка последовательностей похожей длины в батчи | Меньше паддинга, чем random batching | Сложнее реализация, может смещать распределение |
| Packing | Конкатенация нескольких последовательностей в одну | Максимальная эффективность, нет паддинга | Требует специальной маски, overhead на маску, возможны артефакты при неправильной маскировке |
Packing даёт наибольший прирост throughput, особенно когда длины сильно варьируются.
6. Влияние на качество обучения
Packing sequences не должен ухудшать качество модели, если attention mask корректно изолирует последовательности. Однако есть нюансы:
- Positional encoding: если используются абсолютные позиции (например, sinusoidal), то токены из второй последовательности получат позиции, следующие за первой. Это может нарушить индуктивное смещение модели (она привыкла, что позиция соответствует порядку в одной последовательности). Решение: использовать relative positional encoding (RoPE, ALiBi) или сбрасывать позиции для каждой последовательности (например, через
position_ids). - Cross-contamination: если маска не идеальна, модель может «подглядывать» в соседние последовательности. Это критично для задач, где контекст строго ограничен (например, в RAG каждый документ — отдельный контекст).
- Loss computation: обычно loss считается только для токенов, которые не являются паддингом и не являются разделителями. При packing loss суммируется по всем токенам всех последовательностей, но важно не учитывать EOS как предсказание (если он не является частью обучения).
Современные фреймворки (Hugging Face Transformers, Megatron-LM, FlashAttention-2) поддерживают packing через параметр packing=True или через кастомные коллаторы.
7. Реализация packing в PyTorch (пример)
from transformers import DataCollatorForSeq2Seq, AutoTokenizer
from torch.nn.utils.rnn import pad_sequence
class PackingDataCollator:
"""Коллатор, который упаковывает последовательности в одну."""
def __init__(self, tokenizer, max_length=2048):
self.tokenizer = tokenizer
self.max_length = max_length
def __call__(self, features):
# features — список словарей с 'input_ids' и 'labels'
all_input_ids = []
all_labels = []
for f in features:
all_input_ids.extend(f['input_ids'] + [self.tokenizer.eos_token_id])
all_labels.extend(f['labels'] + [-100]) # -100 игнорируется в loss
# Обрезать до max_length
if len(all_input_ids) > self.max_length:
all_input_ids = all_input_ids[:self.max_length]
all_labels = all_labels[:self.max_length]
# Создаём attention mask: блочно-диагональная
# (упрощённо: каждая последовательность — блок, но здесь мы просто маскируем паддинг)
# Для реального packing нужна информация о границах.
# Используем специальную маску из библиотеки flash-attn или xformers.
return {
'input_ids': torch.tensor([all_input_ids]),
'labels': torch.tensor([all_labels]),
'attention_mask': torch.ones(1, len(all_input_ids)) # placeholder
}
На практике лучше использовать готовые решения: transformers.DataCollatorForSeq2Seq с padding=False и кастомной логикой, или библиотеки вроде packing_utils из flash-attn.
8. Packing в контексте Agentic RAG
В Agentic RAG агент выполняет несколько шагов: запрос к векторной БД, вызов инструмента, генерация ответа. Каждый шаг порождает короткую последовательность (например, "<tool_call>search('query')</tool_call>"). При обучении такого агента на логах взаимодействий:
- Много коротких последовательностей (запросы, ответы инструментов, финальные ответы).
- Длины сильно варьируются (от 10 до 500 токенов).
- Паддинг был бы крайне неэффективен.
Packing позволяет объединить несколько таких шагов в один батч, увеличив throughput в 2–3 раза. Это критично для быстрой итерации при fine-tuning агентов.
9. Альтернативы и дополнения
- Dynamic batching (динамическое батчирование): последовательности группируются по длине на лету, но всё равно возможен паддинг внутри группы.
- Gradient accumulation с packing: можно накапливать градиенты от нескольких упакованных батчей.
- Sequence parallelism: распределение одной длинной последовательности на несколько GPU — ортогональная техника.
Пет-проект для закрепления
Задача: Реализовать packing sequences для fine-tuning небольшой модели (например, GPT-2) на датасете из коротких текстов (твиты, диалоги). Сравнить throughput и loss с обычным паддингом.
Инструменты: Python, PyTorch, Hugging Face Transformers, FlashAttention-2 (опционально).
Шаги:
- Загрузить датасет (например,
tweet_evalилиdaily_dialog). - Токенизировать, сохранить длины.
- Реализовать два коллатора: стандартный (с паддингом до макс. длины в батче) и packing (конкатенация с EOS, блочно-диагональная маска).
- Обучить модель на небольшом количестве шагов (100–200) с обоими коллаторами.
- Замерить: время на шаг, throughput (токенов/сек), loss.
- Визуализировать результаты.
Ожидаемый результат: Packing даёт прирост throughput на 30–50% при сопоставимом loss. Если loss отличается, нужно проверить корректность маски и positional encoding.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 466 | Что такое FlashAttention и как он ускоряет обучение? |
| 468 | Как работает gradient checkpointing? |
| 470 | Какие существуют стратегии bucketing для батчей? |
| 472 | Как оптимизировать throughput при fine-tuning LLM? |
| 480 | Что такое document masking в контексте RAG? |
Навигация
- Предыдущий: 466
- Следующий: 468
- Индекс: 00. Индекс разборов