Files
cuframes/docs/integration.md
T
gx f10413580d docs: cross-container CUDA IPC requires both --ipc и --pid namespace share
Реальный тест на 192.168.88.98 (1920x1080 HEVC, 25fps) показал: для отдельных
consumer-container'ов недостаточно ipc=container:X — нужен также
pid=container:X, иначе cudaIpcOpenEventHandle падает с invalid device
context. CUDA driver валидирует IPC peer через /proc/<pid>/...

E2E на реальной камере проверен:
  publisher (отдельный контейнер) -> consumer (docker exec): 250 frames, 0 gaps
  publisher (отдельный контейнер) -> consumer (отдельный с pid+ipc): 200, 0 gaps

Обновлено:
- docs/integration.md compose snippet, verification, troubleshooting section
- docker-compose.example.yml — добавлен pid: container:cuframes-cam-test
- README.md quickstart — добавлен --pid в docker run subscriber

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 06:37:09 +01:00

269 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Integration guide
Этот guide описывает, как использовать cuframes для устранения дублирующего
GPU-декодирования между несколькими consumer'ами одного RTSP-потока.
## Целевой сценарий (motivation)
В типичной CCTV-системе один и тот же RTSP-stream декодируется несколько раз:
```
Камера ──► RTSP ──► Frigate (decode #1: detection + recording)
─► mosaic-сервер (decode #2: компоновка сетки)
─► AI-скрипт (decode #3: классификация / OCR)
```
На 16 камер × 25 fps × 3 consumer'а = 1200 NVDEC-операций/сек. RTX 5090 имеет
~3 NVDEC-движка, но шина PCIe и memory bandwidth становятся узким местом.
С cuframes:
```
Камера ──► cuframes-rtsp-source ──► CUDA frame в /dev/shm + cudaIpcEvent
├──► Frigate (zero-copy)
├──► mosaic-сервер (zero-copy)
└──► AI-скрипт (zero-copy)
```
Decode выполняется **один раз** на источник, потребители получают тот же CUDA
device pointer без копий.
## Текущие limitations v0.1
- **Frigate** (по состоянию на 0.17) **не имеет** plugin-точки для приёма
готовых CUDA-frames. Чтобы убрать Frigate decode полностью, нужен:
- либо FFmpeg-filter `vf_cuda_ipc_input` (planned для cuframes v0.2 — требует
patch FFmpeg upstream и пересборку Frigate's bundled ffmpeg),
- либо Frigate-plugin (требует upstream работы с командой Frigate).
- В v0.1 практическое улучшение: **исключить decode для всех custom consumer'ов
кроме Frigate** (то есть cctv-processor, AI-скрипты — на cuframes; Frigate
остаётся как есть, со своим decode).
Это уже даёт значительную экономию: было 1×Frigate + N×consumer decode'ов,
стало 1×Frigate + 1×cuframes-rtsp-source (один на все consumer'ы).
## Сценарий 1: cuframes-rtsp-source + cctv-processor (FRIGATE остаётся)
### docker-compose.yml
```yaml
services:
# Один источник на камеру — публикует декодированный поток через cuframes IPC
cuframes-cam-parking:
image: gx/cuframes-rtsp-source:0.1
restart: unless-stopped
runtime: nvidia
environment:
NVIDIA_VISIBLE_DEVICES: all
NVIDIA_DRIVER_CAPABILITIES: compute,video,utility
# CRITICAL: --ipc=shareable для cross-container CUDA IPC
ipc: shareable
shm_size: 1g
volumes:
- cuframes_sock:/run/cuframes
command:
- --rtsp=rtsp://admin:${CAM_PASS}@192.168.88.98:554/cam/realmonitor?channel=1&subtype=0
- --key=cam-parking
- --ring=6
- --realtime # не нужен для RTSP (real-time источник), оставлен для file://
# Frigate (как и был — со своим decode на main+sub streams)
frigate:
image: ghcr.io/blakeblackshear/frigate:stable-tensorrt
# ... как обычно
# cctv-processor — подписывается на cuframes (без отдельного RTSP decode)
cctv-backend:
image: gx/cctv-processor:cuda
restart: unless-stopped
runtime: nvidia
# CRITICAL: shared IPC + PID namespace с publisher'ом (см. ниже)
ipc: container:cuframes-cam-parking
pid: container:cuframes-cam-parking
volumes:
- cuframes_sock:/run/cuframes:ro
environment:
# cuframes-keys для backend'а:
CCTV_SOURCES: cuframes:cam-parking,cuframes:cam-front-gate,...
volumes:
cuframes_sock:
```
**Важно — оба флага обязательны** для cross-container CUDA IPC:
| Флаг | Зачем |
|---|---|
| `ipc: container:<publisher>` | shared `/dev/shm` (нужен для `shm_open` под header/sockets) |
| `pid: container:<publisher>` | CUDA driver валидирует IPC peer через `/proc/<pid>/...`; без этого `cudaIpcOpenEventHandle` падает с `invalid device context` |
Альтернативы:
- Запускать consumer внутри того же container'а через `docker exec` (наследует все namespaces) — удобно для отладки.
- `--ipc=host --pid=host` — убирает namespacing вообще, но ослабляет изоляцию (не рекомендуется в production).
### Изменения в cctv-processor
Нужно добавить новый Source-тип (рядом с RtspSource) — `CuframesSource`:
```cpp
// cpp/apps/cctv-processor/src/sources/cuframes_source.hpp
#include <cuframes/cuframes.hpp>
class CuframesSource : public IVideoSource {
public:
CuframesSource(const std::string &key) : key_(key) {
cuframes::SubscriberOptions opt;
opt.key = key;
opt.consumer_name = "cctv-processor";
opt.mode = CUFRAMES_MODE_NEWEST_ONLY;
sub_ = std::make_unique<cuframes::Subscriber>(opt);
cudaStreamCreate(&stream_);
}
// Вызывается processing-loop'ом
std::optional<GpuFrame> nextFrame() override {
auto f = sub_->next(stream_, 100); // 100ms timeout
if (!f) return std::nullopt;
// cudaStreamWaitEvent уже сделан внутри next() — frame готов на stream_
return GpuFrame{
.cuda_ptr = f->cuda_ptr(),
.width = f->width(),
.height = f->height(),
.pitch_y = f->pitch_y(),
.pitch_uv = f->pitch_uv(),
.seq = f->seq(),
.pts_ns = f->pts_ns(),
.stream = stream_,
._release = std::move(f), // RAII release при destroy
};
}
private:
std::string key_;
std::unique_ptr<cuframes::Subscriber> sub_;
cudaStream_t stream_;
};
```
Конфиг `cameras.json` — добавить альтернативный source-тип:
```jsonc
{
"cameras": [
{
"id": "parking",
"source_type": "cuframes", // вместо "rtsp"
"cuframes_key": "cam-parking",
// rtsp_url больше не нужен — он используется cuframes-rtsp-source'ом
}
]
}
```
## Сценарий 2: AI-скрипт на Python (subscriber)
Python-bindings — в Phase 3 cuframes. Сейчас простой workaround через
ctypes:
```python
import ctypes
lib = ctypes.CDLL("libcuframes.so")
# ... wrap нужные функции — см. include/cuframes/cuframes.h
```
Или: writer simple C-обёртку, которая принимает callback и публикует
данные через ZMQ / shared memory в python-process.
## Сценарий 3: Замена Frigate decode (v0.2+)
Целевой сценарий — Frigate тоже подписан на cuframes. Реализуется через
один из двух путей:
### Путь A: FFmpeg filter
Добавить out-of-tree filter `vf_cuda_ipc_input` который читает кадр из
cuframes ring и эмитит AVFrame в pipeline. Frigate использует ffmpeg для
RTSP/decode — заменяем "RTSP→decode→detect" на
"cuframes_ipc_input→detect" (без decode'а вообще).
Требования:
- Patch ffmpeg sources (libavfilter/vf_cuda_ipc_input.c + Makefile)
- Сборка кастомного Frigate-образа с patched ffmpeg
- Тестирование на совместимость с Frigate's pipeline assumptions
### Путь B: Frigate plugin
Engage с upstream Frigate чтобы добавить custom Source-type ("cuframes://").
Это требует Python-API изменений в Frigate's source layer.
## Verification checklist
После настройки убедитесь:
```bash
# 1. Publisher запущен и socket существует
ls -la /run/cuframes/cam-parking.sock
ls -la /dev/shm/cuframes-cam-parking
# 2. Контейнеры в одном IPC и PID namespace
docker inspect cuframes-cam-parking cctv-backend \
-f '{{.Name}} ipc={{.HostConfig.IpcMode}} pid={{.HostConfig.PidMode}}'
# Publisher: ipc=shareable pid=(default)
# Consumer: ipc=container:cuframes-cam-parking pid=container:cuframes-cam-parking
# 3. Subscriber connect успешен
docker exec cctv-backend /usr/local/bin/sub_count --key cam-parking --max-frames 10
# Ожидаем:
# [sub_count] connected to 'cuframes-cam-parking'
# [sub_count] received=10 gaps=0 elapsed=0.4s avg_fps=25
# 4. NVDEC utilization — должно быть N decodes, а не N*M
nvidia-smi dmon -s u
# Колонка %dec должна показать decode-нагрузку одного instance на камеру
```
## Troubleshooting
### `Subscriber::create: timeout`
Subscriber не нашёл publisher. Причины:
- Publisher не запущен или crashed — проверь `docker logs cuframes-cam-parking`
- Socket-файл не volumes'нут в consumer-контейнер — добавь `volumes:
- cuframes_sock:/run/cuframes:ro` в consumer'е
- IPC namespace не совпадает — см. checklist пункт 2
### `cudaIpcOpenEventHandle: invalid device context`
Проявляется в **отдельном** consumer-container'е после успешного handshake (socket
открыт, header валиден, но open event handle не проходит).
Причина: CUDA driver валидирует sender'а IPC peer'а через `/proc`. Если PID
namespace не совпадает, sender невидим — context считается невалидным.
Fix: добавить `pid: container:<publisher>` в consumer's compose service (рядом
с `ipc: container:<publisher>`). Проверено на CUDA 13.0 + driver 555+.
### `cudaIpcOpenMemHandle returned 'invalid device pointer'`
- Контейнеры в РАЗНЫХ ipc namespace — должны быть в одном (через
`ipc: container:<publisher>` или общий `ipc: shareable`)
- Subscriber работает на другом CUDA device — `--cuda-device` должен совпадать
у publisher и subscriber (одно и то же физическое GPU)
### Высокая latency (>50ms tail)
- Subscriber slow — frames копятся в ring, по политике DROP_OLDEST они
пропускаются. Используй `CUFRAMES_MODE_NEWEST_ONLY` (default) — это нормально
для real-time системы.
- При STRICT_ORDER + STRICT_WAIT — slow consumer блокирует publisher. Не
рекомендуется для CCTV.
### Frigate показывает чёрный экран после интеграции
- Frigate не подключён к cuframes (v0.1 — это not yet supported). В v0.1
Frigate должен оставаться на своём RTSP decode (см. Сценарий 1).
## Roadmap
- **v0.1** (текущая): standalone publisher/subscriber, C/C++ API, examples.
- **v0.2**: FFmpeg filter `vf_cuda_ipc_input` (out-of-tree), Python bindings.
- **v0.3**: NVENC-bridge для re-encode подписчиков, Frigate plugin
proof-of-concept.
- **v1.0**: stable ABI, multi-GPU, documented Frigate integration.