Оптимизация задержки вывода модели путем профилирования кода вывода

Машинное обучение (ML) реализуется в выводе модели. В этом посте мы описываем, как вы можете использовать CodeGuru Profiler для профилирования контейнера/кода вашей конечной точки SageMaker. Это даст вам представление о производительности ваших приложений и устранит любые проблемы с задержкой и использованием в вашем приложении. В этом решении будет показано, как расширить контейнеры SageMaker Framework (TensorFlow, PyTorch) и создать собственный контейнер, настроенный с помощью CodeGuru Profiler, чтобы определить дорогостоящие строки кода, влияющие на производительность вашей развернутой модели.

Пример репозитория GitHub для этого блога находится здесь.

Обзор решения

Когда дело доходит до логического вывода модели в реальном времени, мы постоянно ищем способы оптимизации затрат и задержки. Для вывода ML с малой задержкой необходимо снизить вычислительные затраты на вывод, чтобы избежать избыточной подготовки для компенсации неоптимального кода. SageMaker Real-time Endpoints — это готовые конечные точки производственного уровня. Однако при развертывании моделей в SageMaker может возникнуть необходимость добавить предварительную/постобработку или настроить способ загрузки наших моделей. Контейнеры SageMaker Framework обеспечивают гибкость для предоставления пользовательского сценария точки входа, который дает нам возможность определять эти настраиваемые шаги в файле inference.py. Точно так же, когда дело доходит до использования собственного контейнера (BYOC) с SageMaker, у нас есть полная гибкость в модельном сервере и логике контейнера. Но откуда мы знаем, что написанный нами код эффективен? Частью любого жизненного цикла разработки программного обеспечения является тестирование. Вывод ML ничем не отличается. Обычный подход к выводу эталонной модели на конечной точке SageMaker заключается в нагрузочном тестировании вашей конечной точки на производственном трафике и подтверждении того, соответствуют ли требования к задержке ключевым показателям эффективности вашего бизнеса. Если во время бенчмаркинга/нагрузочного тестирования выявляется проблема, мы вручную ищем способы оптимизации нашего кода вывода модели.

Наша сквозная задержка SageMaker Endpoints состоит из нескольких сетевых переходов. ClientLatency, OverheadLatency и ModelLatency. ClientLatency относится ко времени прохождения туда и обратно, которое требуется для запроса логического вывода, чтобы добраться до API среды выполнения SageMaker, где конечная точка SageMaker находится позади и обратно. OverheadLatency означает время, необходимое SageMaker для получения запроса на вывод из API среды выполнения SageMaker в контейнер модели и обратно. ModelLatency — это время, которое требуется вашему контейнеру для обработки и ответа на вывод. То есть сквозная задержка составляет ClientLatency + OverheadLatency + ModelLatency.

SageMaker Real-time Endpoints предоставляют нам информацию о производительности наших моделей с помощью показателей CloudWatch, таких как OverheadLatency и ModelLatency. ModelLatency предоставляет отличный высокоуровневый обзор того, как работает наш контейнер модели, но не предоставляет подробных сведений для объяснения производительности. Вот тут-то и появляется CodeGuru Profiler. Profiler дает нам возможность идентифицировать и визуализировать самые дорогие строки кода в контейнере модели, что способствует ModelLatency. Мы можем использовать информацию, которую Profiler дает нам, для повышения производительности, что может значительно снизить стоимость вывода модели во время бенчмаркинга/нагрузочного тестирования. Мы также можем оставить CodeGuru Profiler настроенным во время производства в качестве ресурса, помогающего анализировать первопричины при срабатывании аварийных сигналов, таких как внезапное увеличение ModelLatency.

В этой статье мы покажем, как можно использовать CodeGuru Profiler во время бенчмаркинга/нагрузочного тестирования для выявления неэффективного кода и как адаптация неэффективного кода может значительно уменьшить количество контейнеров ModelLatency. В этом случае мы развернули модель TensorFlow с пользовательским сценарием точки входа inference.py, который содержит логику для запроса хранилища функций SageMaker, чтобы подтвердить, что данные входного запроса, которые мы передаем в нашу конечную точку, соответствуют последним значениям функций, хранящимся в хранилище функций. Во время сравнительного анализа было отмечено, что ModelLatency контейнера составляло ~112 мс (среднее значение за 1 минуту) при нагрузочном тесте с последовательным запросом.

Как описано выше, ModelLatency способствует сквозной задержке. Таким образом, чтобы понять, откуда берется задержка, мы можем использовать CodeGuru Profiler, чтобы дать нам детальное представление строк кода, вносящих вклад в ModelLatency, которые мы видим в CloudWatch.

Изучение CodeGuru Profiler дает нам понимание того, что происходит в нашем input_handler в нашем inference.py скрипте. Ниже показана проверка визуализации input_handler Latency в CodeGuru Profiler.

Как мы видим, на создание клиента boto3 SageMaker Feature Store Runtime уходит много времени.
Это связано с тем, что у нас неэффективный код, который создает новый клиент для каждого входящего запроса на вывод.
Пожалуйста, перейдите по этой ссылке для получения более подробной информации о работе с визуализациями CodeGuru Profiler.

retrieve_latest_features_boto3_client_create:

def retrieve_latest_features_boto3_client_create(data,record_id):
    print("retrieve_latest_features_boto3_client_create")
    
    client = boto3.client('sagemaker-featurestore-runtime')
    response = client.get_record(
        FeatureGroupName='my-features',
        RecordIdentifierValueAsString=str(record_id),
    )

Затем мы можем протестировать изменение input_handler кода для повторного использования клиента вместо создания нового клиента для каждого запроса:

retrieve_latest_features:

def retrieve_latest_features(data,record_id):
    print("retrieve_latest_features")
    response = client.get_record(
        FeatureGroupName='my-features',
        RecordIdentifierValueAsString=str(record_id),
    )

Если снова обратиться к CodeGuru Profiler за этим простым изменением кода, картина станет гораздо лучше. Мы видим, что один и тот же клиент используется и не создается повторно для каждого запроса вывода.

Преимущество этого изменения кода материализовано в метриках ModelLatency. Здесь мы видим, что ModelLatency контейнера теперь составляет ~67 мс (в среднем за 1 минуту) при нагрузочном тесте с последовательным запросом. С помощью этого простого изменения кода мы смогли почти вдвое сократить время, необходимое для вывода модели.

Расширение контейнера SageMaker TensorFlow и PyTorch для настройки с помощью CodeGuru Profiler

Процесс настройки CodeGuru Profiler для TensorFlow и PyTorch выглядит следующим образом:

  1. Создайте группу CodeGuru Profiler.
  2. Расширьте и перестройте контейнер Framework для настройки с помощью CodeGuru Profiler.
  3. Отправьте восстановленный образ в собственный репозиторий ECR.

Контейнер TensorFlow Framework

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

Чтобы настроить CodeGuru Profiler для профилирования на нашей конечной точке, нам нужно расширить контейнер SageMaker TensorFlow, настроенный для отправки данных профилирования в CodeGuru. Контейнер SageMaker TensorFlow использует рабочие процессы Gunicorn для запуска нашего пользовательского сценария точки входа inference.py. Таким образом, нам нужно предоставить файл gunicorn_conf.py, содержащий метод post_fork с кодом для запуска агента профилирования. Пожалуйста, посмотрите на gunicorn_conf.py, который использовался для этого блога.

После того, как мы записали наш gunicorn_conf.py, нам нужно обновить gunicorn_command в файле serve.py, чтобы запустить воркеры с файлом gunicorn_conf.py.

В Dockerfile мы расширяем контейнер SageMaker TensorFlow, устанавливаем агент профилирования и добавляем вышеупомянутые файлы для правильной настройки CodeGuru Profiler для запуска.

FROM 763104351884.dkr.ecr.us-east-1.amazonaws.com/tensorflow-inference:2.3.0-cpu
 RUN pip3 install numpy codeguru_profiler_agent flask 
COPY Files/serve.py  /sagemaker/serve.py 
COPY Files/gunicorn_conf.py /sagemaker/gunicorn_conf.py

Расширение контейнера SageMaker PyTorch Framework

Пользовательский сценарий точки входа inference.py для контейнера SageMaker PyTorch запускается внутри TorchServe, поэтому нам нужно добавить код для запуска агента профилирования в файле handler_service.py.

В файле Docker мы расширяем контейнер SageMaker PyTorch, устанавливаем агент профилирования и добавляем вышеупомянутый файл, чтобы правильно настроить запуск CodeGuru Profiler.

FROM 763104351884.dkr.ecr.us-east-1.amazonaws.com/pytorch-inference:1.9.0-cpu-py38
RUN pip3 install codeguru_profiler_agent 
COPY Files/handler_service.py  /opt/conda/lib/python3.8/site-packages/sagemaker_pytorch_serving_container/handler_service.py

Принесите свой собственный контейнер (BYOC)

Если вы используете собственный контейнер, процесс настройки CodeGuru Profiler для BYOC выглядит следующим образом:

  1. Создание группы профилировщиков CodeGuru.
  2. Создайте свой собственный контейнер для настройки с помощью CodeGuru Profiler.
  3. Отправьте восстановленный образ в собственный репозиторий ECR.

Общий стек хостинга для BYOC включает NGINX › Gunicorn › Flask.
Таким образом, процесс настройки CodeGuru Profiler такой же, как и для контейнера SageMaker TensorFlow Framework. Нам нужно предоставить файл gunicorn_conf.py, содержащий метод post_fork с кодом для запуска агента профилирования.

После того, как мы записали наш gunicorn_conf.py, нам нужно обновить gunicorn_command в файле serve, чтобы запустить воркеры с файлом gunicorn_conf.py.

Заключение

В этом посте мы продемонстрировали, как CodeGuru Profiler можно использовать для профилирования пользовательского скрипта inference.py, который предоставляет нам подробные сведения о том, что делает наш скрипт пользовательской точки входа. С помощью информации из Flame Graph CodeGuru мы смогли определить дорогостоящие строки кода, которые повлияли на наш ModelLatency. С помощью этой информации мы адаптировали наш скрипт inference.py, чтобы избежать этого дорогостоящего кода. Нам удалось почти вдвое сократить время, необходимое для логического вывода.

Мы также продемонстрировали, как CodeGuru Profiler устанавливается в контейнер TensorFlow, PyTorch Framework и наш собственный контейнер.