Ниже — подробный рассказ о том, как мы нагрузили карту от NVidia задачами транскодирования видео для его потоковой передачи. Покажем, что попробовали, что получилось, и как лучше всего использовать видеокарты для стриминга в онлайне.
На протяжении нескольких лет наша команда разрабатывает продукты для обработки и раздачи медиа-контента в онлайне. В этой статье не так давно было расписано, зачем владельцам контента подобные решения могут быть нужны в наш век YouTube.
Одним из наших продуктов является медиа-сервер Nimble Streamer — это серверное ПО, которое берёт на вход живые потоки и файлы и делает их доступными большому числу зрителей, попутно позволяя монетизировать контент. Это нативное приложение, написанное на С++ и портированное на все популярные ОС (Linux, Windows, MacOS) и платформы (x64, ARM). С самого начала низкая ресурсоёмкость и большая производительность были главными требованиями, и нам удаётся достигать в этом хороших показателей.
В прошлом году мы выпустили дополнение в Нимбл Стримеру — транскодер живых потоков. Это приложение позволяет брать на вход поток видео и/или аудио в разных форматах и делать с ними различные преобразования в реальном времени. Функциональность включает в себя декодирование (как программное, так и аппаратное), преобразование видео и аудио с помощью фильтров (изменение размера, наложение и т.п.) и кодирование (энкодинг) — как программное, так и аппаратное.
Управляется транскодер через веб-сервис WMSPanel, сценарии транскодирования создаются через drag-n-drop интерфейс, что позволяет наглядно видеть процесс. Различные сценарии можно запускать вместе — при таком подходе удобно запускать комбинации тестов, нагружая сервер в любых вариациях.
В этих видео можно посмотреть примеры работы интерфейса.
Декодирование каждого потока делается только один раз перед всеми дальнейшими преобразованиями… Это позволяет сэкономить ресурсы на дорогостоящей операции декодинга, это будет хорошо видно дальше по ходу тестов.
Одним из механизмов преобразования, который можно использовать в нашем транскодере, является аппаратное декодирование и кодирование видео с помощью GPU от NVidia. Графические карты последних поколений позволяют брать на себя часть типовых задач, что снимает нагрузку с CPU. Наш транскодер умеет работать с этим железом, чем активно пользуются наши клиенты.
По ходу общения с представителями российского офиса NVidia нам предложили попробовать устроить совместное стресс-тестирование нашего транскодера и NVidia GPU, чтобы понять, каков будет экономический эффект от подобного тандема по сравнению с исключительно программным транскодированием, без аппаратного ускорения. Кроме того, хотелось понять, как наиболее оптимально использовать GPU, и по возможности дать хорошие рецепты.
Нам требовалось оперативно получить соответствующее железо и доступ к нему, для цикла наших экспериментов. Мы планировали уложиться в пару недель. Осталось найти, где же взять оборудование. Оптимальным вариантом было бы найти их в облаке и получить удаленный доступ. Поискав варианты выяснилось, что в AWS пока нет VM с GPU поколения Maxwell, а в облаке Azure пока только планируется начать их предоставлять в скором времени.
1. Железо от NVidia в облаке Softlayer, настройка Nimble Streamer
При содействии NVidia, компания IBM предоставила нам доступ к своему облаку — IBM Bluemix Cloud Platform (в прошлом Softlayer). Это большая сеть современных ЦОД-ов (около 50 на момент публикации) по всему миру, связанных общей частной сетью и предоставляющих большой выбор облачных инфраструктурных сервисов. Все ЦОД-ы унифицированы и позволяют арендовать от одного до сотен виртуальных или физических серверов требуемых конфигураций в течении нескольких часов, а также балансировщики, системы хранения, файрволы — в общем всё, что требуется чтобы построить надежную ИТ-инфраструктуру под развертываемый ИТ-сервис.
Российское представительство IBM предоставило нам полный доступ к порталу самообслуживания для управления облачными услугами и к нужной серверной конфигурации, где мы смогли поработать с разными входными потоками и настройками нашего транскодера.
Железо
Сначала нам предоставили физический сервер (bare-metal) с 128 ГБ RAM и 2xGPU NVidia Tesla M60 и предустановленной ОС Ubuntu 14.04. Все параметры сервера, пароли, версии прошивок, его коммутация, выделенные IP, состояние аппаратных компонент, были видны прямо в личном кабинете, позволяющем сделать с арендованным железом требуемые манипуляции, что минимизировало необходимость взаимодействия со службой поддержки IBM. В ходе прогона тестов, выяснилось, что нам не удается оптимально нагрузить такую конфигурацию, из-за ряда ограничений при генерации контекстов.
Нам захотелось уменьшить конфигурацию. Поскольку мы использовали облачную платформу, то потребовалось через портал самообслуживания запросить изменения в конфигурации. После согласования, данная операция заняла около 2 часов, в утвержденное окно обслуживания. В течении этого времени, технический персонал в ЦОД-е Амстердама, извлек лишние компоненты (планки RAM и 1xGPU) из предоставленного нам ранее сервера и вернул его в строй. Надо отметить, что для разработчиков такой вариант очень удобен, поскольку нет необходимости ни самим разбираться с настройками железа, ни чинить его, ни даже тратить время на установку ОС. Напомню, в данном случае не используется гипервизор поскольку нам надо выжать максимум из аппаратных ресурсов.
По итогам наших изысканий мы остановились на следующей конфигурации сервера:
Dual Intel Xeon E5-2690 v3 (2.60GHz)
24 Cores
64GB RAM
1TB SATA
Имеем 2 процессора по 12 ядер, а благодаря Hyper threading получаем вдвое больше, т.е. виртуально 48 ядер.
В сценариях с графическим ускорителем использовалась карта на базе чипа GM204 — Tesla M60:
NVIDIA Tesla M60
1xGPU: 2 x Maxwell GM204
Memory: 16GB GDDR5
Clock Speed: 2.5 GHz
NVIDIA CUDA Cores: 2 x 2048
Memory Bandwidth: 2 x 160GB/sec
Обращаю внимание, что на приведённом железе не делалось никакого affinity, chip tuning-а, overclocking-а и прочей магии — только неразогнанные CPU и GPU, и для GPU использовался только официальный драйвер, взятый с сайта NVidia. Если у кого-то есть подобный опыт — поделитесь в комментариях.
Итак, мы получили доступ. Беглое ознакомление с веб-интерфейсом панели управления (там всё просто и понятно), далее доступ к серверу через SSH — и вот мы уже в привычной командной строке Ubuntu, ставим Nimble Streamer, регистрируем свежую лицензию транскодера и делаем небольшую настройку конфига.
Nimble Streamer Transcoder
Nimble Streamer был настроен на предварительное создание кеша контекстов GPU. Связано это с тем, что у GPU есть ограничение на максимальное число создаваемых контекстов декодинга и энкодинга, а кроме того, создание контекстов “на лету” может занимать слишком много времени.
Более подробно о проблеме создания контекстов — в соответствующем разделе ниже.
Настройки Нимбла на примере первой серии тестов:
nvenc_context_cache_enable = true
nvenc_context_create_lock = true
nvenc_context_cache_init = 0:30:15,1:30:15
nvenc_context_reuse_enable = true
Более подробно про эти настройки написано в нашей статье.
Перед запуском каждой серии тестов кеш настраивался отдельно, с учетом специфики каждой задачи.
Создание сценариев транскодинга
Далее работа шла в нашем сервисе WMSPanel, где и происходит настройка сценариев транскодера.
Как уже было сказано, работа идёт через веб-интерфейс, всё предельно понятно и удобно. Мы создали ряд сценариев, сочетающих разные варианты транскодинга (CPU/GPU), разные варианты разрешений и разные параметры энкодинга (CPU/GPU, профайл, битрейт и т.п.)
Наборы сценариев можно запускать одновременно, что даёт возможность вводить в оборот разные сочетания тестов, повышать нагрузку в разном порядке и менять её в зависимости от ситуации. Просто выделяем нужные сценарии и останавливаем либо возобновляем их.
Вот так выглядит набор сценариев:
Вот пример одного из сценариев:
Декодер с GPU выглядит вот так:
Накладываем фильтр размера изображения:
А вот энкодер для варианта с GPU:
В целом работу интерфейса транскодера можно посмотреть на этих видео.
2. Транскодирование потоков FullHD 1080p
Для начала мы опробовали сценарий с самыми большими нагрузками, чтобы узнать пределы возможностей железа. На данный момент самым “тяжёлым” из используемых на практике разрешений является FullHD 1080p.
Для генерации исходных живых потоков был взят файл в FullHD (1920*1080) в high profile H.264. Сам контент- это видеоэкскурсия по городу, т.е. это видео со средней интенсивностью смены изображения. Нет статичных одноцветных кадров, которые могли бы облегчить работу транскодеру, но нет и слишком быстрой смены видов и цветов. Одним словом — довольно типовая нагрузка.
На вход в Nimble Streamer подавали 36 одинаковых потоков, которые затем использовались в транскодере в разных сценариях.
Сценарий транскодинга используется типовой — входящий поток 1080p high profile, из него делаются 720p, 480p, 360p main profile и далее потоки baseline profile: 240p, 160p. Итого, на входе 1 поток, на выходе 5. Обычно также делается pass-through (передача без изменений) исходного потока, чтобы зритель мог выбрать собственно 1080p при просмотре. Его мы не добавляли в сценарии, т.к. в нём не используется транскодинг — идёт прямая передача данных из входа на выход. Этот сценарий у нас в Нимбле оптимизирован и в реальных условиях это относительно немного увеличит потребление памяти.
Аудио в генерируемых потоках — нет. Добавление аудио в сценарии не даст значительных нагрузок на CPU, но для чистоты эксперимента звук мы исключили.
Тест на CPU, без GPU
Для начала мы запустили сценарии транскодинг без использования GPU, указав в сценариях программные декодер и энкодер.
В результате получилось обработать только 16 потоков на вход с выдачей 80 потоков всех разрешений на выход.
Нагрузка CPU — 4600%, т.е. было задействовано 46 ядер. Расход RAM — порядка 15Гб.
Тест на CPU + GPU
Кеш контекстов при запуске настроен как 0:30:15,1:30:15 — т.е. 30 контекстов на энкодинг, 15 на декодинг, у каждого GPU.
Напомню, что на GPU у нас два ядра, что позволяет распараллеливать задачи — это нам пригодится.
Максимальная нагрузка получилась при следующей конфигурации потоков.
На вход декодера GPU0 и GPU1 — по 15 потоков. Таким образом мы получаем 30 декодированных потоков, готовых к дальнейшему использованию. Каждый поток декодируется только один раз, независимо от того, в скольких сценариях он в дальнейшем используется.
На энкодеры GPU0 и GPU1 подавалось по 15 потоков для получения 720p, т.е. получилось 30 потоков 720p на выход.
Также на энкодеры GPU0 и GPU1 давалось по 15 потоков для 480p — и так же получилось 30 потоков 480p на выход.
Поскольку контексты энкодера были исчерпаны, энкодинг остальных разрешений был установлен на CPU. Получилось следующее:
- 30 потоков 360p
- 30 потоков 240p
- 30 потоков 160p
Нагрузка получилась 2600% CPU, 75% декодер, 32% энкодер
Далее CPU был догружен 6 потоками на декодинг, для каждого настроено по 5 аналогичных разрешений, итого 30 потоков на выход.
Итого получили на вход 36 потоков, выдали на выход — 180 потоков.
Финальная нагрузка зафиксирована следующая: 4400% CPU, 75% декодер карты, 32% энкодер карты, 30Гб RAM.
Немного деталей
Мы решили проверить вариант, при котором мы на GPU обрабатываем самые тяжёлые задачи — декодинг 1080 и энкодинг 720 и 480, а остальное пустить на обработку через CPU.
Сначала проверили предел работы декодера. При 22 потоках на декодинг — сказалась проблема с контекстами, они просто не могли создаться. Снизили до 21 — контексты создались, но нагрузка стала 100% и в потоке стали наблюдаться артефакты. Остановились на 20 потоках — делаем декодирование 20 потоков, энкодинг в 160p — всё работает нормально.
Кроме того, опытным путём получилось, что данная карта с 16Гб RAM на борту может уверенно работать с 47 контекстами — и нет разницы, это контексты энкодера или декодера. Повторюсь — речь именно о данном GPU Tesla M60, на других картах это число может быть другим. Полагаем, что если бы на карте было 24Гб RAM, число контекстов могло быть другим, но это нужно тестировать.
В итоге мы выбрали формулу создания кеша «15 контекстов декодера и 30 контекстов энкодера» — что даёт 30 потоков на вход и для каждого позволяет создать по 2 разрешения.
Так что на GPU были пущены кодироваться верхние разрешения — 720 и 480, а остальные — 360, 240 и 160 — были отправлены на CPU. И поскольку CPU был после этого всё ещё свободен, мы «добили» свободные ядра новыми потоками, оставив 4 ядра на утилитарные задачи.
3. Транскодирование потоков HD 720p
Сценарий с типовой нагрузкой, т.к. бОльшая часть контента сейчас создается именно в HD. Даже недавний SuperBowl LI — самое рейтинговое шоу на американском рынке — передавали именно в HD, оставив FullHD на будущее.
Для генерации исходных потоков был взят файл в HD (1280*720) в high profile. Контент — любимая серия “The Good Wife” нашего инженера, т.е. это видео со средней интенсивностью смены изображения.
На вход в Nimble Streamer подавали 70 одинаковых потоков, которые затем использовались в транскодере в разных сценариях.
Сценарий транскодинга используется следующий — входящий поток 720p high profile, из него делаются 480p, 360p main profile и далее потоки 240p, 160p baseline profile. Итого, на входе 1 поток, на выходе 4. Pass-through исходного потока, как и в предыдущем сценарии, не выполнялся. Аудио в генерируемых потоках — так же нет.
Тест на CPU, без GPU
Как и в предыдущем разделе, мы попробовали транскодинг потоков только на CPU.
В результате получилось обработать только 22 потока на вход с выдачей 88 потоков всех разрешений на выход.
Нагрузка CPU — 4700%, т.е. было задействовано 47 ядер. Расход RAM — порядка 20Гб.
Тест на CPU + GPU
Кеш контекстов при запуске настроен как 0:23:23,1:23:23 — т.е. 23 контекста на энкодинг, 23 на декодинг для каждого GPU.
С помощью GPU были декодированы 46 потоков 720p. Там же, на GPU был сделан энкодинг 46 потоков 480p. Далее на CPU был сделан энкодинг 360p, 240p и 160p — по 46 потоков каждого.
Зафиксирована нагрузка 2100% CPU, 61% декодера, 16% энкодера.
В дополнение был запущен энкодинг и декодинг 24 потоков на CPU, на каждый 1 поток — по 4 выхода, как и для GPU.
Итого получилось 70 потоков на вход, 280 потоков на выход.
Нагрузка: 4600%, 61% декодера, 16% энкодера, 30Гб RAM.
Как и для прошлого теста, возможно больший RAM GPU дал бы большее число контекстов и мы смогли бы обработать больше потоков. Но это только в теории, надо проверять.
4. Проблема с созданием контекстов в NVidia GPU
Несколько слов о проблеме, которая не позволила нам обработать больше потоков на GPU.
В конце прошлого года мы проводили тесты совместно с командой NVidia, с несколькими картами. При работе с несколькими GPU выяснилось, что создание контекстов сильно тормозит работу сервера — создание каждого нового контекста отнимало у карты всё больше времени. Если первый контекст создавался порядка 300мс, то каждый последующий прибавлял по 200-300мс и уже на третьем десятке контекстов создание нового занимало 3-4 секунды каждый. Когда сценарий транскодинга создается пользователем, предполагается, что он начинает работать сразу и без задержек, а это новое обстоятельство сводило на нет все преимущества в скорости Нимбла и давало задержки при создании контекстов, которые приводили к задержкам старта энкодинга.
Сначала подозрение пало на Нимбл, однако потом мы сделали тесты с использованием ffmpeg, который предоставляет клиентам сама NVidia и результат оказался ровно тем же — GPU тратит всё больше времени на создание каждого нового контекста. В условиях, когда на сервере уже идёт транскодинг и надо запускать новые потоки на обработку, это сказывается на общей производительности и делает сервер просто непригодным к работе.
Проблема была подробно описана команде NVidia, но пока штатного решения предоставлено не было. Поэтому мы пока реализовали у себя в сервере механизм кеширования контекстов, с предварительным созданием контекстов на старте сервера. Это решило проблему с точки зрения работы конечного пользователя, но старт Нимбла при этом может занять определённое время. Настройка Нимбла для эффективной работы с контекстами описана у нас в блоге.
Кроме того, контексты мало просто создать. При большом числе контекстов при включении любого сценария транскодирования, NVENC API начинает выдавать ошибки «The API call failed because it was unable to allocate enough memory to perform the requested operation.»
Опытным путём получилось, что один GPU может запуститься и уверенно работать с 47 контекстами — и нет разницы, это контексты энкодера или декодера. Появилось предположение, что это связано с объёмом памяти на GPU. Сейчас там 16 Гб, если поставить карту с 24 Гб, есть вероятность, что контекстов можно будет сделать больше. Но это только теория, надо проверять, как уже упоминалось ранее. Полученные данные справедливы для конкретной модели GPU, другие карты надо тестировать отдельно.
Именно ограничение на число контекстов ставит основную преграду при работе с большими нагрузками.
5. Выводы
Итак, целью тестирования было изучить эффективность GPU для обозначенного круга задач и выработать рецепты его правильного использования. Что получилось в итоге?
Экономический эффект
Выше мы увидели как отличается число потоков, которое можно обрабатывать на CPU и на тандеме CPU+GPU. Посмотрим, что это значит с точки зрения денег. За основу возьмём всё тот же Softlayer и их цены на аренду оборудования.
- Конфигурация без GPU обойдётся в $819 в месяц. Здесь можно подобрать машину.
- Конфигурация с GPU будет стоить $1729 в месяц для датацентра в Амстердаме, цены можно посмотреть здесь. При использовании GPU цена аренды сервера несколько возрастает, поскольку используется больший форм-фактор корпуса 2U. Экономический эффект возможно будет выше при покупке оборудования (но это требует серьезного анализа ТСО с учетом постоянного обновления линейки GPU NVidia).
Теперь посмотрим результаты тестирования:
Для FullHD 1080p
- CPU без GPU: 16 потоков на вход + 80 на выход
- CPU + GPU: 36 потоков на вход + 180 на выход
Преимущество при использования GPU: 2,25x.
Выгода от использования GPU: $819*2,25 — $1729 = $113 в месяц при аренде 1 сервера c GPU.
Для HD 720p
- CPU без GPU: 22 потока на вход + 88 на выход
- CPU + GPU: 70 потоков на вход + 280 на выход
Преимущество при использования GPU: 3,18x.
Выгода от использования GPU: $819*3,18 — $1729 = $875 в месяц при аренде 1 сервера c GPU
То есть при варианте аренды экономия вполне заметна. Это без учета скидок — в российском офисе IBM обещают скидки на аренду ресурсов в облаке по сравнению с представленными здесь ценами.
В варианты с покупкой мы не углублялись, т.к. тут TCO сильно зависит от выбора поставщика, стоимости обслуживания в датацентре и прочих факторов, хорошо знакомых тем, кто работает с bare metal. Однако предварительные цифры также говорят в пользу решения на базе GPU.
Также не забываем о трафике и ширине канала — в представленные выше тарифы они включены в определенном объёме, но вам нужно будет выбрать опции под свои задачи, исходя из количества потоков, ожидаемого числа пользователей и т.п.
Масштабирование
Вариант с одной графической картой на сервер нам представляется более экономически эффективным, чем вариант с двумя картами и более. Как мы видим, декодер GPU всегда загружался больше, чем энкодер, но даже он оставался недогруженным из-за проблем с использованием контекстов. Если добавить вторую карту, декодер будет использоваться ещё меньше, энкодеры мы тем более не сможем загрузить на полную мощность, и всю работу по энкодингу по-прежнему нужно будет перекладывать на CPU, что будет неоправданно по деньгам. Вариант с двумя GPU мы тоже тестировали благодаря поддержке Softlayer, но из-за слабого экономического эффекта мы не приводим в статье подробности.
Соответственно, для масштабирования нагрузки предпочтительнее добавлять новые сервера с одной графической картой, чем добавлять карты в существующие машины.
Если количество входящих и исходящих потоков для вашего проекта относительно невелико — скажем, десяток HD потоков с небольшим числом разрешений на выходе, с относительно небольшим объёмом фильтрации, то целесообразнее будет использовать сервер без GPU.
Стоит также обратить внимание на то, что объём RAM для задачи преобразования потоков — не так важен, как вычислительная мощность. Так что в каких-то случаях вы сможете сэкономить ещё и за счёт уменьшения объёма памяти.
Заключение
Представленное аппаратное решение — сочетание CPU и GPU Tesla M60 — отлично подошло для транскодирования живых потоков под большими нагрузками. GPU берёт на себя самые ресурсоёмкие операции — декодирование потоков и их кодирование в самые большие разрешения, тогда как средние и мелкие разрешения хорошо обрабатываются на CPU.
Если у кого-то из читателей есть опыт работы и оптимизации производительности графических карт для живого вещания, будем рады познакомиться с вашим опытом — пишите в комментариях.
Автор: Aquary