Анализ пламенных графиков профилирования GPU в Azure Kubernetes Service (AKS) (предварительная версия)

Графики профилирования GPU показывают, какие функции используют большую часть памяти GPU. В этой статье объясняется, как интерпретировать графы пламени, созданные средствами профилирования GPU для Azure Kubernetes Service (AKS).

Important

Предварительные версии функций AKS доступны на условиях самообслуживания и по выбору пользователя. Предварительные версии предоставляются "как есть" и "при наличии". На них не распространяются соглашения об уровне обслуживания и ограниченная гарантия. Предварительные версии AKS сопровождаются частичной поддержкой клиентов на основе принципа лучших усилий. Как таковые, эти функции не предназначены для использования в производстве. Для получения дополнительной информации ознакомьтесь со следующими статьями поддержки:

Прежде чем начать, настройте профилирование GPU и убедитесь, что данные профилирования доступны в Пироскопе и Grafana.

Замечание

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

Понимание графов пламени

Граф пламени визуализирует профилированные стеки вызовов. Каждая полоса представляет функцию, а составные полосы показывают цепочку вызовов, то есть какая функция вызвала другую функцию. Ширина каждой панели представляет объем измеряемого ресурса, например времени ЦП или выделенной памяти GPU, который проходит через эту функцию.

Ключевое правило: чем шире полоса, тем больше измеряемого ресурса проходит через такую функцию.

Следующие образцы диаграмм пламени получены при нагрузке инференса vLLM.

Снимок экрана: начальное свернутое представление диаграммы пламени в Grafana для рабочей нагрузки vLLM, на которой показаны стеки вызовов выделения памяти GPU.

Tip

Используйте Expand all groups на панели flame graph в Grafana, чтобы увидеть полный стек вызовов без свертывания. Используйте поле поиска для поиска определенных функций или ключевых слов. Чтобы панель снова не сворачивалась, приостановите автообновление панели мониторинга, установив интервал обновления в значение Off в правом верхнем углу панели мониторинга Grafana, пока вы проверяете развернутый flame graph.

Снимок экрана пламенной диаграммы в Grafana при нагрузке vLLM после выбора пункта

Tip

Используйте блок фокуса , чтобы сосредоточиться на определенном пути выделения.

Чтение символов

Метки графа пламени следуют этим условностям:

Формат символов Значение
Foo > bar class Foo: method def bar()
Foo > __init__ Конструктор класса Foo
bar (отдельно) def bar() Автономная функция
<interpreter trampoline> Накладные расходы CPython. Этот символ можно игнорировать.
<raw-address>, например 0x7f151 Собственный код C или CUDA без Python символов.

Вот некоторые примеры:

  • GPUModelRunner / _allocate_kv_cache_tensors: метод класса. Прочитайте этот символ как class GPUModelRunner: def _allocate_kv_cache_tensors(self).
  • LlamaMLP / __init__: конструктор, вызываемый при создании LlamaMLP(...) объекта.
  • _compile_fx_inner: автономная функция уровня модуля, которая не находится внутри класса.

Общие сведения о самостоятельном распределении и общем выделении

Используйте значения self и Total , чтобы определить, где используется ресурс:

  • Итог — это ресурс, потребляемый функцией, и все, что вызывается. Функция может иметь большую общую сумму, но выделить ничего не может, так как она является частью цепочки вызовов.
  • Self — объём ресурсов, потребляемых непосредственно функцией, без учёта её дочерних функций. Высокое самозначное значение означает, что эта функция является местом использования ресурса.

Например, если на GPUModelRunner._allocate_kv_cache_tensors приходится 55,1 ГБ собственного выделения памяти, то именно эта функция вызывает torch.empty() для создания тензоров кэша «ключ-значение» (KV).

Используйте следующие методы навигации:

  • Начните с листовых узлов — полос, над которыми ничего нет. Вся их ширина означает самостоятельное распределение.
  • Пропускайте широкие полосы с нулевым собственным выделением памяти при поиске источника выделения памяти. Эти функции координируют вызовы других функций.
  • Изучите широкие полосы с высоким уровнем самостоятельного выделения в качестве потенциальных целевых объектов оптимизации.

Поиск крупнейшего потребителя ресурсов

Чтобы определить точки доступа, выполните следующие действия.

  1. Посмотрите на самые широкие полосы в верхней части графа. Эти листовые функции выделяют память. Чем шире панель, тем больше памяти она потребляет.

  2. Сравните собственное и общее выделение. Широкая полоса внизу с self: 0 является частью цепочки вызовов. Следуйте за ним вверх, пока не найдете бар с высоким уровнем самостоятельного выделения.

  3. Прочитайте стек вызовов снизу вверх, чтобы понять, почему была вызвана функция. Следующий пример заканчивается функцией, выполняющей выделение:

    <raw-address>, for example, 0x7f151       -> native code entry
    <interpreter trampoline>                 -> CPython dispatch
    <module>                                 -> script top level
    EngineCoreProc.run_engine_core           -> vLLM engine startup
    EngineCore.__init__                      -> engine initialization
    EngineCore._initialize_kv_caches         -> KV cache setup
    Worker.initialize_from_config            -> worker setup
    GPUModelRunner.initialize_kv_cache       -> model runner
    GPUModelRunner._allocate_kv_cache_tensors -> actual allocation
    

Используйте следующую таблицу в качестве краткой ссылки:

Goal Что искать
Найдите, что использует больше всего памяти Самая широкая полоса листа в верхней части стека
Найдите, что использует больше всего памяти Самая широкая полоса внизу стека
Определение целевых объектов оптимизации Панели с широким самораспределением
Определение функций, которые можно пропустить Широкие полосы с нулевым самостоятельным выделением

Дальнейшие действия