Files
Claude Opus 7f45c36aa2 init
2026-05-26 23:23:25 +01:00

6.8 KiB
Raw Permalink Blame History

title, sidebar_position
title sidebar_position
Ownership modes 2

Ownership modes

Кто владеет CUDA-памятью в которую publisher пишет frame'ы — сама библиотека или внешний код. В заголовке cuframes.h объявлены оба варианта:

typedef enum cuframes_ownership_mode {
    CUFRAMES_OWNERSHIP_LIBRARY  = 0,
    CUFRAMES_OWNERSHIP_EXTERNAL = 1,
} cuframes_ownership_mode_t;

Но в v0.4 работает только LIBRARY. EXTERNAL оставлен в API для бинарной совместимости и помечен deprecated. Ниже — почему и что с этим делать если ваш код раньше использовал EXTERNAL.

LIBRARY mode (единственный рабочий в v0.4)

Publisher просит библиотеку аллоцировать ring заданного размера. Каждый кадр publisher получает чистый slot, пишет в него, отдаёт обратно через publish.

cuframes_publisher_config_t cfg = {
    .key       = "cam1",
    .width     = 1920,
    .height    = 1080,
    .format    = CUFRAMES_FORMAT_NV12,
    .ownership = CUFRAMES_OWNERSHIP_LIBRARY,
    .ring_size = 4,
    .policy    = CUFRAMES_POLICY_DROP_OLDEST,
};
cuframes_publisher_t *pub;
cuframes_publisher_create(&cfg, &pub);

void *slot;
cuframes_publisher_acquire(pub, &slot);
// NVDEC / cuMemcpy / kernel пишет в slot
cuframes_publisher_publish(pub, stream, cuframes_now_ns());

Под капотом библиотека:

  1. Аллоцирует ring_size слотов через cuMemCreate(CU_MEM_HANDLE_TYPE_POSIX_FILE_DESCRIPTOR).
  2. cuMemMap + cuMemSetAccess чтобы локальный publisher мог писать.
  3. При subscribe передаёт POSIX FD через sendmsg(SCM_RIGHTS).
  4. Subscriber делает cuMemImportFromShareableHandle — получает указатель на ту же физическую HBM.

Этот путь zero-copy для consumer'ов. На publisher'е memory overhead = ring_size × frame_size. На consumer'е — ноль.

EXTERNAL mode (deprecated в v0.4)

Идея была: publisher уже имеет CUDA pointer'ы из чужого pool'а (FFmpeg AVHWFramesContext, NVDEC output, DeepStream, какой-то custom decoder) и хочет их просто пошарить, без extra allocation и без D2D copy.

// Так раньше работало в v0.1–0.3. В v0.4 не работает.
cuframes_publisher_create_external(&cfg, ffmpeg_pool_ptrs,
                                   pool_size, frame_size, &pub);

В v0.4 этот путь сломан by design.

Почему EXTERNAL не работает с VMM

cuframes v0.4 публикует frame'ы через POSIX FD, потому что это единственный CUDA-IPC канал который не требует shared PID namespace (см. memory note про v0.4 и sync writeup). FD получается через cuMemExportToShareableHandleа эта функция требует чтобы память была аллоцирована через cuMemCreate с соответствующим requestedHandleType.

Существующий cudaMalloc / cudaMallocPitch pointer (тот что отдаёт FFmpeg или DeepStream) к VMM не относится. Экспортировать его как POSIX FD нечем. Старый путь v0.3 использовал cudaIpcGetMemHandle (opaque 64-байтовая структура, передавалась через socket payload) — он работал с любой памятью, но требовал shared PID. На v0.4 от него ушли осознанно.

Что делать вместо

Если у вас уже есть GPU pool из FFmpeg / NVDEC / etc — переходите на LIBRARY mode с одним extra device-to-device copy:

// FFmpeg выдаёт frame в hwframe pool:
AVFrame *src = ...;            // src->data[0] = cudaMalloc'd by FFmpeg

void *slot;
cuframes_publisher_acquire(pub, &slot);

// 1 × DtoD copy с pitch:
cuMemcpy2DAsync(&(CUDA_MEMCPY2D){
    .srcMemoryType = CU_MEMORYTYPE_DEVICE,
    .srcDevice = (CUdeviceptr)src->data[0],
    .srcPitch  = src->linesize[0],
    .dstMemoryType = CU_MEMORYTYPE_DEVICE,
    .dstDevice = (CUdeviceptr)slot,
    .dstPitch  = pub_pitch,
    .WidthInBytes = width,
    .Height       = height_y + height_uv,
}, stream);

cuframes_publisher_publish(pub, stream, pts_ns);

Так переведён инструмент cuframes-rtsp-source в составе репозитория cuframes — раньше он принимал FFmpeg pool через EXTERNAL, теперь делает acquire + 1 D2D copy. Overhead — единичный DtoD на 1920×1080 NV12 это десятки микросекунд, в порядке шума на фоне cuStreamSynchronize.

Memory trade-off

LIBRARY (v0.4) EXTERNAL (v0.3, deprecated)
Publisher extra alloc ring_size × frame_size 0
D2D copy per frame 1 (если есть upstream pool) или 0 (если decoder пишет прямо в slot) 0
Zero-copy для consumers да да
Работает без shared PID да нет
Поддерживается в v0.4 да нет

Если decoder можно научить писать прямо в slot (acquire сначала, потом decode в полученный pointer) — extra D2D исчезает. Так делает cuframes-rtsp-source со своим NVDEC pipeline'ом.

Вернётся ли EXTERNAL

Если NVIDIA добавит способ экспорта cudaMalloc-памяти как POSIX FD — да, это вернёт zero-D2D путь без жертвования cross-namespace. На момент CUDA 12.4 такого API нет, и в roadmap NVIDIA это не анонсировано. На практике рассчитывать на это не стоит.

Поле ownership в cuframes_publisher_config_t остаётся ради ABI стабильности. Передача CUFRAMES_OWNERSHIP_EXTERNAL в v0.4 вернёт CUFRAMES_ERR_INVALID_ARG. Вызов cuframes_publisher_create_external объявлен в заголовке, но возвращает ту же ошибку.

Следующее

  • Synchronization — почему v0.4 ушёл от CUDA events и почему это связано с тем же VMM-ограничением.
  • First publisher — рабочий LIBRARY-mode пример.
  • Protocol reference — wire format VMM_FDS handshake.