Epic: pybind11 Python bindings v1 (для yolo-world-detector) #6

Closed
opened 2026-06-13 12:43:40 +01:00 by gx · 1 comment
Owner

Зачем

Чтобы downstream Python consumer'ы (новый сервис yolo-world-detector, ML-скрипты) могли подписываться на cuframes без CPU round-trip: получать NV12 frame прямо как CUDA pointer / torch.Tensor (DLPack), не уходя из VRAM.

Сейчас в examples/python-consumer/cuframes_consumer.py есть ctypes skeleton, но он не делает actual CUDA copy (placeholder). README отмечает pybind11 как planned.

Решение

Полноценные pybind11 bindings, минимум для:

  • multi-stream subscribe pattern (несколько камер из одного процесса)
  • zero-copy GPU access через DLPack export
  • pythonic lifecycle (context manager)
  • thread-safety contract в docstring

Scope (из architect review)

  • subscribe / next_frame / release lifecycle с typed Python objects (CuframesSubscriber, CuframesFrame)
  • DLPack exportframe.__dlpack__() для zero-copy → torch.from_dlpack или cupy.from_dlpack
  • Context managerwith cuframes.subscribe(key) as sub: ... гарантированно release() при exception
  • Per-subscriber CUDA stream — параметр при subscribe, иначе все consumer'ы сериализуются на default stream
  • Error taxonomy — отдельные классы: CuframesPublisherGone, CuframesFrameTimeout, CuframesDeviceLost, CuframesShmError. Не голый RuntimeError.
  • Health/stats propertiessubscriber.ring_occupancy, subscriber.drop_count, subscriber.publisher_state (ACTIVE/STALE/DEAD). Не logging, для MQTT health publisher.
  • Thread-safety contract в docstring — явно описать: subscribe одного потока, next_frame другого — разрешено или нет
  • Pitch как метаdata на frame — frame.pitch_y, frame.pitch_uv. Архитектор предупредил: для большой камеры (2688×1520, gate_lpr) pitched NV12 не contiguous, downstream kernel должен принимать pitch как параметр.
  • Backpressure policy doc — что делает next_frame() при overrun: drop newest vs oldest vs block. Решение на уровне cuframes API, не в каждом consumer.

Не входит в scope (Phase 0.5+)

  • Memory pool refcounting между consumer'ами (out of scope для v1)
  • Async API (async for frame in subscriber.stream()) — приоритет если есть время
  • Hot-reload publishers — не блокер

Definition of done

  • pip install cuframes из локального wheel работает
  • Тестовый скрипт examples/python-consumer/yolo_world_smoke.py: subscribe → 100 frames → DLPack → ONNX inference dummy → release. Без leaks (ring occupancy стабильный).
  • Документация: docs/python.md с примером, pitfalls, threading contract.

Зависимости

  • Блокирует: goldix-smart-home/yolo-world-detector Phase 1+.
  • Не блокирует: ничего из существующего стека (C ABI остаётся).

Estimate

1 неделя focused work.


Architect review fed into this scope: см. context из чат-сессии 2026-06-13.

## Зачем Чтобы downstream Python consumer'ы (новый сервис `yolo-world-detector`, ML-скрипты) могли подписываться на cuframes **без CPU round-trip**: получать NV12 frame прямо как CUDA pointer / `torch.Tensor` (DLPack), не уходя из VRAM. Сейчас в `examples/python-consumer/cuframes_consumer.py` есть ctypes skeleton, но он не делает actual CUDA copy (placeholder). README отмечает pybind11 как **planned**. ## Решение Полноценные `pybind11` bindings, минимум для: - multi-stream subscribe pattern (несколько камер из одного процесса) - zero-copy GPU access через DLPack export - pythonic lifecycle (context manager) - thread-safety contract в docstring ## Scope (из architect review) - [ ] **subscribe / next_frame / release** lifecycle с typed Python objects (`CuframesSubscriber`, `CuframesFrame`) - [ ] **DLPack export** — `frame.__dlpack__()` для zero-copy → `torch.from_dlpack` или `cupy.from_dlpack` - [ ] **Context manager** — `with cuframes.subscribe(key) as sub: ...` гарантированно `release()` при exception - [ ] **Per-subscriber CUDA stream** — параметр при subscribe, иначе все consumer'ы сериализуются на default stream - [ ] **Error taxonomy** — отдельные классы: `CuframesPublisherGone`, `CuframesFrameTimeout`, `CuframesDeviceLost`, `CuframesShmError`. Не голый `RuntimeError`. - [ ] **Health/stats properties** — `subscriber.ring_occupancy`, `subscriber.drop_count`, `subscriber.publisher_state` (ACTIVE/STALE/DEAD). Не logging, для MQTT health publisher. - [ ] **Thread-safety contract в docstring** — явно описать: subscribe одного потока, next_frame другого — разрешено или нет - [ ] **Pitch как метаdata** на frame — `frame.pitch_y`, `frame.pitch_uv`. Архитектор предупредил: для большой камеры (2688×1520, gate_lpr) pitched NV12 не contiguous, downstream kernel должен принимать pitch как параметр. - [ ] **Backpressure policy doc** — что делает next_frame() при overrun: drop newest vs oldest vs block. Решение на уровне cuframes API, не в каждом consumer. ## Не входит в scope (Phase 0.5+) - Memory pool refcounting между consumer'ами (out of scope для v1) - Async API (`async for frame in subscriber.stream()`) — приоритет если есть время - Hot-reload publishers — не блокер ## Definition of done - `pip install cuframes` из локального wheel работает - Тестовый скрипт `examples/python-consumer/yolo_world_smoke.py`: subscribe → 100 frames → DLPack → ONNX inference dummy → release. Без leaks (ring occupancy стабильный). - Документация: `docs/python.md` с примером, pitfalls, threading contract. ## Зависимости - Блокирует: `goldix-smart-home/yolo-world-detector` Phase 1+. - Не блокирует: ничего из существующего стека (C ABI остаётся). ## Estimate 1 неделя focused work. --- Architect review fed into this scope: см. context из чат-сессии 2026-06-13.
Author
Owner

Закрыто PR'ом #7 (merged 2026-06-13).

Что сделано (все 8 пунктов scope)

  • subscribe/next_frame/release lifecycle (CuframesSubscriber, CuframesFrame)
  • DLPack export — dlpack_y() / dlpack_uv() / __dlpack__() / __dlpack_device__()
  • Context manager на обоих классах
  • Per-subscriber CUDA stream — kwarg consumer_stream (cudaStream_t как int)
  • Error taxonomy — CuframesError + 8 subclasses
  • Health/stats properties — frames_received, timeouts, errors, last_seq, gap_count, stats() dict (counted в pybind layer т.к. C API не expose'ит ring_occupancy)
  • Thread-safety contract в docstring CuframesSubscriber
  • Pitch как property (pitch_y, pitch_uv) + предупреждение в docs

Backpressure

Документирован в docs/python.md, NEWEST_ONLY default. Реальный policy switch не нужен в Phase 0 — устанавливается через subscriber mode.

Не вошло в Phase 0 — отдельные задачи

  • Реальный smoke test через живого publisher'а — требует Docker IPC namespace, перенесён в yolo-world-detector Phase 1
  • C API extension для ring_occupancy / drop counter — future enhancement issue
  • Memory pool refcounting между consumer'ами — explicitly out of scope per architect review

Файлы

  • python/CMakeLists.txt, pyproject.toml, README.md, .gitignore
  • python/src/_native.cpp — ~530 строк pybind code
  • python/cuframes/__init__.py — re-export
  • python/tests/test_smoke.py — 10 tests
  • docs/python.md — ~250 строк документации
  • CMakeLists.txtadd_subdirectory(python) при BUILD_PYTHON_BINDINGS=ON

Verify

cmake -B build-python -DBUILD_PYTHON_BINDINGS=ON
cmake --build build-python -j
cd python && pytest tests/ -v   # 10/10

Phase 0 closed. Можно запускать yolo-world-detector#2 Phase 1.

Закрыто PR'ом #7 (merged 2026-06-13). ## Что сделано (все 8 пунктов scope) - [x] subscribe/next_frame/release lifecycle (`CuframesSubscriber`, `CuframesFrame`) - [x] DLPack export — `dlpack_y()` / `dlpack_uv()` / `__dlpack__()` / `__dlpack_device__()` - [x] Context manager на обоих классах - [x] Per-subscriber CUDA stream — kwarg `consumer_stream` (`cudaStream_t` как int) - [x] Error taxonomy — `CuframesError` + 8 subclasses - [x] Health/stats properties — `frames_received`, `timeouts`, `errors`, `last_seq`, `gap_count`, `stats()` dict (counted в pybind layer т.к. C API не expose'ит ring_occupancy) - [x] Thread-safety contract в docstring `CuframesSubscriber` - [x] Pitch как property (`pitch_y`, `pitch_uv`) + предупреждение в docs ## Backpressure Документирован в `docs/python.md`, NEWEST_ONLY default. Реальный policy switch не нужен в Phase 0 — устанавливается через subscriber mode. ## Не вошло в Phase 0 — отдельные задачи - Реальный smoke test через живого publisher'а — требует Docker IPC namespace, перенесён в [yolo-world-detector Phase 1](http://server:3000/goldix-smart-home/yolo-world-detector/issues/2) - C API extension для `ring_occupancy` / drop counter — future enhancement issue - Memory pool refcounting между consumer'ами — explicitly out of scope per architect review ## Файлы - `python/CMakeLists.txt`, `pyproject.toml`, `README.md`, `.gitignore` - `python/src/_native.cpp` — ~530 строк pybind code - `python/cuframes/__init__.py` — re-export - `python/tests/test_smoke.py` — 10 tests - `docs/python.md` — ~250 строк документации - `CMakeLists.txt` — `add_subdirectory(python)` при BUILD_PYTHON_BINDINGS=ON ## Verify ```bash cmake -B build-python -DBUILD_PYTHON_BINDINGS=ON cmake --build build-python -j cd python && pytest tests/ -v # 10/10 ``` **Phase 0 closed.** Можно запускать [yolo-world-detector#2 Phase 1](http://server:3000/goldix-smart-home/yolo-world-detector/issues/2).
gx closed this issue 2026-06-13 21:34:57 +01:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gx/cuframes#6