Уже пару лет я успешно сливаю карму в спорах со свидетелями "никогда нейросети не достигнут/не смогут/не заменят и т.п." В целом, мне нет нужды что-то доказывать прячущим в песок голову скептикам, однако, вчера я столкнулся со сложной проблемой и попросил сделать Opus 4.6 ее траблшутинг. Около 40 минут заняло расследование проблемы, по мотивам которого, я попросил его написать статью, которую и предлагаю вашему вниманию как пример того, на что я мог потратить целый день, а большинство middle-grade инженеров бы не справилось вообще. Весь траблшутинг и статья сделаны в полностью автоматическом режиме без участия человека.
Бонусом вы узнаете что-то о кривых руках инженеров NVIDIA. Впрочем, их руки далеко не самые кривые в индустрии...
Итак, погнали.
Почему nvv4l2h264enc не работает внутри Docker на Jetson Orin NX (JetPack 6.2.2)
После свежей установки JetPack 6.2.2 (L4T R36.5.0) на Jetson Orin NX я столкнулся с
озадачивающей проблемой: аппаратное кодирование H.264 прекрасно работало на хосте, но стабильно падало внутри Docker-контейнеров. Последовало глубокое погружение в логи strace, DRM ioctl'ы и внутренности закрытых библиотек NVIDIA — и всё это только для того, чтобы обнаружить, что причиной был отсутствующий двухмегабайтный пакет.
Конфигурация
Плата: Jetson Orin NX (Engineering Reference Developer Kit)
JetPack: 6.2.2 (L4T R36.5.0, ядро 5.15.185-tegra, OOT-модули ядра)
Контейнер:
nvcr.io/nvidia/deepstream-l4t:7.1-samples-multiarchDocker runtime:
--runtime=nvidia --privileged
Симптом
На хосте каноничный GStreamer-пайплайн аппаратного кодирования запускается без проблем:
gst-launch-1.0 videotestsrc ! nvvidconv ! 'video/x-raw(memory:NVMM), framerate=5/1' \ ! nvv4l2h264enc ! fakesink
Setting pipeline to PAUSED ... Opening in BLOCKING MODE NvMMLiteOpen : Block : BlockType = 4 ===== NvVideo: NVENC ===== NvMMLiteBlockCreate : Block : BlockType = 4 H264: Profile = 66 Level = 0 NVMEDIA: Need to set EMC bandwidth : 21000 Pipeline is PREROLLED ...
Внутри Docker — тот же образ, то же железо, --privileged, --runtime=nvidia — падает:
Opening in BLOCKING MODE ENC_CTX(0xffff88008460) Error in initializing nvenc context ERROR: from element /GstPipeline:pipeline0/nvv4l2h264enc:nvv4l2h264enc0: Could not get/set settings from/on resource. Device is in streaming mode
Ни NvMMLiteOpen. Ни ===== NvVideo: NVENC =====. Библиотека NVMM сдаётся ещё до
попытки обратиться к аппаратуре.
Что я исключил
Узлы устройств — все на месте
Контейнер видит все необходимые устройства: /dev/nvmap, /dev/host1x-fence,
/dev/dri/renderD128 (tegra host1x DRM), /dev/nvgpu/igpu0/*, /dev/v4l2-nvenc.
Мажорные и минорные номера полностью совпадают с хостом.
Библиотеки — идентичны
Все библиотеки NVIDIA монтируются с хоста через nvidia-container-runtime. Я сверил контрольные суммы критичных библиотек (libnvtvmr.so, libnvrm_host1x.so, libtegrav4l2.so, libv4l2_nvvideocodec.so, libnvmmlite_video.so) — все идентичны между хостом и контейнером.
DRM-доступ — работает нормально
Я написал быстрый тест на Python, который открывает /dev/dri/renderD128 и вызывает DRM_IOCTL_TEGRA_CHANNEL_OPEN для host1x-класса NVENC (0x21). Он успешно выполняется в обоих окружениях, возвращая context=1, version=35, capabilities=1.
Права и cgroups — не причина
С --privileged Docker отключает seccomp, AppArmor и cgroup-фильтрацию устройств. NVIDIA_VISIBLE_DEVICES=all ничего не изменил.
sysfs — доступна
/sys/bus/nvmem/devices/fuse/nvmem, /sys/devices/soc0/*, /sys/firmware/devicetree/base/* — всё читается внутри контейнера. Чип корректно идентифицируется как Tegra234 (Orin), soc_id=35.
Расследование
Сравнение DRM ioctl-трассировок
Я снял полные трассировки strace -f -e trace=ioctl на хосте и в контейнере. Разница была разительной:
Метрика | Хост | Контейнер |
|---|---|---|
Всего вызовов ioctl | 2016 | 1644 |
Вызовов DRM_IOCTL | 236 | 36 |
DRM_IOCTL_TEGRA_CHANNEL_OPEN | Да | Ни разу |
На хосте поток энкодера открывает DRM tegra-канал к NVENC-движку. В контейнере библиотека запрашивает DRM_IOCTL_VERSION на render-нодах, но дальше к открытию канала не переходит. Инициализация прерывается на более высоком уровне.
Трассировка потока энкодера
Трассируя все системные вызовы через strace -f, я определил точный поток, отвечающий за инициализацию энкодера. На хосте этот поток выводит NvMMLiteOpen и затем открывает DRM-каналы. В контейнере поток порождает два дочерних процесса через clone() + execve() и тут же сообщает об ошибке.
Дочерние процессы были:
lsmod— завершается с кодом 127 («команда не найдена»)grep nvgpu— читает из пайпа, ничего не получает, завершается с ненулевым кодом
Библиотека NVIDIA NVMM вызывает нечто эквивалентное popen("lsmod | grep nvgpu"), чтобы убедиться, что модуль ядра nvgpu загружен, прежде чем инициализировать контекст энкодера.
Цепочка отказа
libnvv4l2.so └─ libv4l2_nvvideocodec.so └─ libnvtvmr.so (Tegra Video Resource Manager) └─ system("lsmod | grep nvgpu") └─ sh: lsmod: not found └─ grep читает пустой пайп → exit 1 └─ libnvtvmr делает вывод: нет GPU → отмена инициализации NVENC
Библиотека не проверяет /proc/modules напрямую. Не запрашивает ядро через sysfs. Она вызывает lsmod — утилиту из пакета kmod — и когда этого бинарника нет, молча заключает, что аппаратура отсутствует.
Решение
apt-get install -y kmod
Вот и всё. После установки kmod (который предоставляет lsmod, modprobe, modinfo и т.д.) энкодер успешно инициализируется внутри контейнера:
Opening in BLOCKING MODE NvMMLiteOpen : Block : BlockType = 4 ===== NvVideo: NVENC ===== NvMMLiteBlockCreate : Block : BlockType = 4 H264: Profile = 66 Level = 0 NVMEDIA: Need to set EMC bandwidth : 21000 NvVideo: bBlitMode is set to TRUE Pipeline is PREROLLED ... Got EOS from element "pipeline0".
Для Dockerfile добавьте kmod в список пакетов:
RUN apt-get update && apt-get install -y kmod && rm -rf /var/lib/apt/lists/*
Почему это проявилось именно сейчас
На JetPack 5.x (эпоха Xavier) базовые образы контейнеров, как правило, включали kmod как зависимость других пакетов. Контейнер DeepStream L4T 7.1 для JetPack 6.x поставляется с более компактной базой, которая не подтягивает его. На хосте kmod всегда установлен как часть базовой системы Ubuntu — поэтому проблема проявляется только в контейнерах.
Сообщение об ошибке Error in initializing nvenc context ни на что не указывает в отношении отсутствующей утилиты. Предупреждение sh: 1: lsmod: not found действительно появляется в выводе GStreamer plugin scanner, но оно погребено среди десятков других безобидных предупреждений и легко отбрасывается как нерелевантное, поскольку возникает при сканировании плагинов, а не во время фактической попытки кодирования. Ситуацию усугубляет то, что библиотека NVMM выполняет собственную проверку lsmod молча — stderr от неудавшегося вызова перенаправляется в /dev/null.
Выводы
--privilegedне поможет, если проблема — отсутствующий бинарник, а не отсутствующие права. Все узлы устройств, записи в sysfs и DRM ioctl были доступны. Библиотека просто отказалась их использовать.Закрытые библиотеки с зависимостями от shell-вызовов — кошмар для отладки. Без доступа к исходному коду единственный путь — трассировка на уровне бинарников. Сравнение поведения потоков через
straceна хосте и в контейнере в конечном счёте выявило зависимость отlsmod.Всегда устанавливайте
kmodв Docker-контейнерах для Jetson. Если ваш контейнер выполняет любую мультимедийную нагрузку NVIDIA (кодирование, декодирование, VIC), стек библиотек NVMM может зависеть отlsmodдля обнаружения аппаратуры. Это нигде не задокументировано в руководствах NVIDIA по контейнерам.Предупреждение
lsmod: not foundпри работеgst-plugin-scanner— тревожный сигнал. Если вы его видите, ваши пайплайны кодирования/декодирования, скорее всего, упадут при запуске, даже если элементы GStreamer успешно загружаются.
