Профилировать memory fragmentation на GPU

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: Профилировать memory fragmentation на GPU

1. Цель задачи

Научиться программно измерять фрагментацию памяти CUDA в PyTorch с помощью torch.cuda.memory_stats(). Выявить источники фрагментации в сценарии работы мультиагентной LLM-системы (несколько моделей / копий агентов на одном GPU) и применить техники её снижения до уровня менее 10%.

Ключевой результат Запуск скрипта, который выводит процент фрагментации до и после оптимизации, и итоговое значение <10%.


2. Исходные данные

Что нужноОткуда взять
GPU с CUDA (не менее 4 ГБ памяти)Локальная машина / облачная инстанция (AWS p3.2xlarge, Colab Pro)
PyTorch с CUDA (>=1.10)Установка через pip / conda
Пример мультиагентной нагрузкиТестовый скрипт (предоставлен ниже или написать самостоятельно)
Утилита nvidia-smi или gpustatПредустановлена или pip install gpustat
Базовые знания о memory pooling PyTorchДокументация: CUDA Memory Management

Если нет реального GPU — симулируем:

  1. Использовать Google Colab (бесплатный T4) — создать ноутбук с GPU-рантаймом.
  2. Если Colab недоступен — использовать CPU-режим с эмуляцией аллокаций (заменить cuda на cpu, анализировать системную память через psutil, но фрагментация не будет точной). В этом случае задачу можно считать ознакомительной.

3. Технологический стек

КомпонентИнструментыНазначение
Язык программированияPython 3.10+Скриптинг
Фреймворк MLPyTorchCUDA)Загрузка/инференс моделей, профилирование памяти
Профилирование CUDAtorch.cuda.memory_stats(), torch.cuda.memory_summary()Сбор статистик фрагментации
Мониторинг GPUnvidia-smi, gpustatКонтроль занятой памяти
Визуализацияmatplotlib / pandasГрафик распределения блоков
Контроль памятиtorch.cuda.set_per_process_memory_fraction(), torch.cuda.empty_cache()Ограничение и очистка

4. Этапы выполнения

Этап 1: Подготовка окружения и тестовой нагрузки (30 минут)

Действия

  1. Установить зависимости

    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
    pip install gpustat matplotlib
    
  2. Проверить доступность GPU и версию PyTorch:

    import torch
    print(torch.cuda.is_available())
    print(torch.cuda.get_device_name(0))
    print(torch.version.cuda)
    
  3. Написать тестовый скрипт симуляции мультиагентной нагрузки:

    • Создать 5 «агентов» — каждый загружает отдельную копию маленькой BERT-модели (bert-base-uncased, 110M).
    • Агенты выполняют случайные forward-проходы с разным batch размером (1, 4, 8) и освобождают тензоры в случайные моменты времени.
    • Цель: создать типичную фрагментацию (последовательные allocation/deallocation разного размера).
    import torch
    import random
    import time
    
    def agent_work(model_id, device):
        model = torch.hub.load('huggingface/pytorch-transformers', 'model', 'bert-base-uncased').to(device)
        for _ in range(10):
            size = random.choice([1, 4, 8])
            input_ids = torch.randint(0, 1000, (size, 128)).to(device)
            with torch.no_grad():
                out = model(input_ids)
            time.sleep(0.05)
            del input_ids, out
            torch.cuda.empty_cache()
        del model
    
  4. Запустить скрипт и убедиться, что память фрагментируется:

    • Выполнить вызов torch.cuda.memory_summary() после окончания работы агентов.

Ожидаемый результат этапа Рабочий скрипт, который воспроизводит фрагментацию памяти (обычно >20-40%).


Этап 2: Сбор профиля фрагментации (40 минут)

Действия

  1. Получить словарь memory_stats

    stats = torch.cuda.memory_stats(device=device)
    # Ключевые метрики:
    # 'num_alloc_retries' — количество retry-аллокаций (признак фрагментации)
    # 'segment.all.current' — число сегментов
    # 'reserved_bytes.all.current' — зарезервированная память
    # 'active_bytes.all.current' — активная (используемая) память
    
  2. Рассчитать процент фрагментации
    Формула, рекомендованная PyTorch:

    reserved = stats['reserved_bytes.all.current']
    active = stats['active_bytes.all.current']
    fragmentation_pct = (1 - active / reserved) * 100 if reserved > 0 else 0
    

    Примечание: это приближённая оценка. Для точного анализа используют memory_snapshot.

  3. Построить гистограмму размера свободных блоков

    • Использовать torch.cuda.memory_snapshot() — возвращает список блоков.
    • Для каждого сегмента: если статус active_allocated — занят, иначе свободен.
    • Визуализировать размеры свободных блоков.
  4. Замерить baseline

    • Зафиксировать фрагментацию после тестовой нагрузки (до оптимизации).

Ожидаемый результат этапа Численное значение фрагментации в процентах, график распределения свободных блоков.


Этап 3: Анализ источника фрагментации (30 минут)

Действия

  1. Выявить проблемные паттерны

    • Постоянное создание/удаление тензоров разного размера.
    • Пул памяти PyTorch (caching allocator) удерживает освобождённые блоки, но не может их переиспользовать из-за разного размера.
  2. Проверить влияние многомодельного инференса

    • Каждая модель создаёт свой allocator cache.
    • Используется ли torch.cuda.set_per_process_memory_fraction() — ограничивает общий объём, но не решает фрагментацию.
  3. Сформулировать гипотезу улучшения

    • Использование torch.cuda.empty_cache() в нужные моменты.
    • Переиспользование одного экземпляра модели (batch inference всех агентов вместе).
    • Включение опции PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True (PyTorch ≥2.0).

Ожидаемый результат этапа Записанная гипотеза, выбранные методы оптимизации.


Этап 4: Оптимизация и снижение фрагментации (1 час)

Действия

  1. Применить expandable_segments

    export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
    

    Или через Python:

    import os
    os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
    
  2. Оптимизировать нагрузку

    • Вместо пяти копий модели — одна модель, inference батчами для всех агентов.
    • Заранее выровнять размеры batch до степеней двойки (1, 2, 4, 8).
  3. Добавить периодическую дефрагментацию

    def defrag(threshold_pct=10):
        if fragmentation_pct > threshold_pct:
            torch.cuda.empty_cache()
    
  4. Снять повторные замеры

    • Запустить изменённый скрипт, собрать memory_stats снова.

Ожидаемый результат этапа Фрагментация стала ниже 10%.


Этап 5: Верификация и фиксация (20 минут)

Действия

  1. Проверить воспроизводимость

    • Запустить скрипт 3 раза, убедиться, что фрагментация стабильно <10%.
  2. Проверить производительность

    • Замерить время выполнения до и после. Оптимизация не должна увеличить latency более чем на 10%.
  3. Написать финальный отчётный скрипт

    • Выводит: дату, GPU, версию PyTorch, фрагментацию до/после, список применённых методов.
  4. Задокументировать процедуру

    • В README-файле или в комментариях внутри скрипта.

Ожидаемый результат этапа Готовый скрипт profile_fragmentation.py, который автоматически измеряет и снижает фрагментацию.


5. Критерии приемки (Definition of Done)

  • Скрипт запускается на GPU-окружении без ошибок.
  • Выводится Fragmentation before: XX%, Fragmentation after: YY%.
  • Значение after строго меньше 10%.
  • Разница по времени выполнения между baseline и оптимизацией не превышает 10%.
  • Присутствует построенный график распределения свободных блоков (опционально — PNG-файл).
  • Код содержит документацию строк (docstrings) и комментарии к ключевым шагам.
  • Все зависимости зафиксированы в requirements.txt.

6. Ожидаемый результат

  • Основной артефакт Python-скрипт profile_fragmentation.py (или Jupyter notebook), который:
    • Симулирует мультиагентную нагрузку.
    • Собирает метрики torch.cuda.memory_stats().
    • Вычисляет процент фрагментации по формуле (1 - active/reserved)*100.
    • Применяет оптимизацию (expandable segments, batch inference, empty_cache).
    • Проверяет итоговое значение <10%.
  • Дополнительно График fragmentation_hist.png и requirements.txt.

7. Возможные сложности и их решение

СложностьРешение
GPU-память быстро переполняетсяУменьшить количество моделей / batch size, использовать torch.cuda.set_per_process_memory_fraction(0.5)
memory_stats() даёт ноль на некоторых версиях PyTorchУбедиться, что после вызова torch.cuda.synchronize() статистики обновились
expandable_segments недоступна (PyTorch <2.0)Обновить PyTorch или использовать альтернативу: ручной вызов empty_cache + пул тензоров
После оптимизации вырос latencyПроверить, что empty_cache вызывается не слишком часто; добавить задержку по времени
Не удаётся добиться <10%Комбинировать методы: expandable segments + batch inference + выравнивание размеров

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Подготовка окружения и тестовой нагрузки30 мин
Этап 2: Сбор профиля фрагментации40 мин
Этап 3: Анализ источника фрагментации30 мин
Этап 4: Оптимизация и снижение фрагментации60 мин
Этап 5: Верификация и фиксация20 мин
Итого3 ч 00 мин

При первом выполнении заложите 4 часа на освоение инструментов.


9. Связанные вопросы из базы знаний

ВопросТема
45CUDA memory management basics
67PyTorch Caching Allocator internals
112How to reduce GPU memory fragmentation in multi-model serving
158Expandable segments in PyTorch 2.0
234Batch inference vs per-request inference memory trade-offs
389Monitoring GPU memory with nvidia-smi
512Profiling LLM inference memory (kv-cache)
678Debugging OOM in transformer models
745Inter-agent communication overhead and memory sharing
890Python memory profilers: memory_profiler vs tracemalloc

10. Чек-лист самопроверки

  • Я проверил, что GPU доступен и torch.cuda.is_available() возвращает True.
  • Я запустил тестовый сценарий нагрузки и получил фрагментацию >10% (иначе задача не актуальна).
  • Я корректно вычислил fragmentation по формуле (1 - active/reserved)*100.
  • Я применил хотя бы два метода снижения фрагментации (expandable segments, batch inference, empty_cache).
  • Я измерил время выполнения до и после и убедился, что оно не ухудшилось более чем на 10%.