Skip to content

DNI: DEBUG: PR to debug/fix CPU CI failures - #400

Draft
ksheth-dev wants to merge 8 commits into
mainfrom
pull-request/999999
Draft

DNI: DEBUG: PR to debug/fix CPU CI failures#400
ksheth-dev wants to merge 8 commits into
mainfrom
pull-request/999999

Conversation

@ksheth-dev

Copy link
Copy Markdown
Collaborator

No description provided.

ksheth-dev and others added 7 commits July 28, 2026 09:54
- VideoEncoder Protocol + ChunkDeliveryResult in
  flashdreams/serving/webrtc/encoders.py, with two implementations:
  DefaultRTCEncoder (adapter over aiortc's software encoder) and
  PyNvHardwareEncoder (NVENC H.264 via PyNvVideoCodec, output as
  av.Packet).
- NVENCVideoTrack (media.py) delivers pre-encoded packets on aiortc's
  public av.Packet -> H264Encoder.pack() path -- no reach into
  aiortc private attributes. Drop-oldest overflow with a bounded
  Queue[av.Packet]; recv() paces at 1/fps like BufferedVideoTrack.
- select_encoder() factory with a two-stage capability probe:
  Stage 1 GetEncoderCaps (silent fallback to DefaultRTCEncoder on
  environmental "not supported"; loud EncoderInitError under
  backend='nvenc'); Stage 2 CreateEncoder (hard error, never a
  silent fallback -- masking a driver / session-pool / hardware
  failure would hide real problems).
- NVENC configured for interactive low-latency H.264: fmt=ABGR
  (NV_ENC_BUFFER_FORMAT_ABGR is word-ordered -> little-endian byte
  layout [R,G,B,A]; see _chunk_to_abgr_cuda_frames docstring),
  preset=P4, ULL tuning, CBR rate control, bf=0, lookahead=0,
  repeatspspps=1 (aiortc's H264Encoder.pack does not synthesize SPS
  and PPS). Packets carry pts on the RTP 90 kHz clock so
  H264Encoder.pack rescales cleanly via convert_timebase.
- PyNvVideoCodec>=2.1,<3 added as a hard dep on
  integrations/omnidreams -- omnidreams already requires CUDA at
  runtime, so the install matrix is unchanged.
- Startup emits a single INFO log line per session,
  'Video encoder ready: backend={pynvvideocodec|aiortc} ...',
  giving one grep anchor regardless of which backend was selected.
- ci_cpu tests cover: Protocol conformance, factory branch coverage
  (Stage-1 library / caps / bounds failures under both auto and nvenc,
  Stage-2 hard-error regression guard under both), pre-encoded packet
  plumbing on NVENCVideoTrack (pts / time_base / drop-oldest overflow),
  cross-chunk packet-ordering invariant that guards the manager's
  sequential-await pattern from a future create_task rewrite, and
  compat guards for aiortc + PyNvVideoCodec public surfaces.
- ci_gpu smoke test encodes a 4-frame chunk on real hardware and
  asserts Annex-B start code + IDR + SPS + PPS in the first packet,
  plus pts=0 and time_base=1/90000.

No runtime path currently consumes the new abstraction; behavior
change lands in the follow-up commit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… fallback

- OmnidreamsRuntimeConfig gains encoder_backend
  (auto | nvenc | default), encoder_bitrate_bps, encoder_gop.
  select_encoder() runs on the runtime executor thread inside
  _initialize_video_encoder_sync; the encoder is closed cleanly in
  _close_sync so its NVENC session slot is released promptly.
- _generate_one_chunk_sync drops the .cpu() D2H copy on the tensor
  and replaces it with torch.cuda.current_stream().synchronize().
  Same wall-clock cost as before (both wait for the compute stream to
  drain), but the hardware encoder can now read the CUDA tensor
  directly via DLPack; the software path picks up the D2H inside
  BufferedVideoTrack's worker.
- omnidreams.webrtc.server exposes one new CLI flag,
  --prefer_sw_encoder. Defaults false -> encoder_backend='auto'
  (probe NVENC and fall back silently to aiortc's software encoder
  on Stage-1 unsupported). Setting it maps to encoder_backend='default'
  and skips the NVENC probe entirely -- useful for A/B profiling and
  known-flaky NVENC hosts. The tri-state config field stays on
  OmnidreamsRuntimeConfig for programmatic use and test coverage of
  the 'nvenc' loud-on-failure branch.
- Manager wiring: addTransceiver + setCodecPreferences constrain the
  SDP to H.264 when the encoder emits pre-encoded packets
  (prefers_codec == 'h264'). Post-answer, transceiver._codecs is
  inspected; if H.264 did not land (e.g. a browser that will not
  offer it), _enforce_h264_or_fallback closes the NVENC session and
  installs DefaultRTCEncoder + BufferedVideoTrack via
  sender.replaceTrack -- a pre-stream swap, no renegotiation.
- Chunk delivery in the generation worker goes through
  encoder.deliver_chunk(...). The await is load-bearing for
  cross-chunk packet ordering (guarded by the regression test added
  in the previous commit): a create_task rewrite here would allow
  chunks to complete out of order and packets to land on the track
  with non-monotonic pts.
- Test suite extends _FakeVideoEncoder to satisfy the VideoEncoder
  Protocol, threads video_encoder through every ManagedWebRTCSession
  construction, and adds ci_cpu tests for _enforce_h264_or_fallback
  (swap on VP8-negotiated, keep on H.264-negotiated, swap on empty
  codec list) plus a parametrized test that --prefer_sw_encoder
  correctly maps to encoder_backend ('default' when set, 'auto' when
  unset).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- encoders.py: guard the ``PyNvVideoCodec`` import under
  ``TYPE_CHECKING`` so ty sees a single ``Any``-typed ``nvc`` name.
  Without the guard, ty preserved a ``<module PyNvVideoCodec> | None
  | Any`` union and rejected the ``None`` fallback and every
  attribute access on ``GetEncoderCaps`` / ``FORCEIDR`` /
  ``CreateEncoder``. Runtime path (the ``else:`` branch) keeps the
  same guarded try / except behaviour.
- encoders.py: narrow the ``VideoEncoder.create_track`` return type
  from ``MediaStreamTrack`` to ``BufferedVideoTrack | NVENCVideoTrack``
  so the manager can assign the result to the ``ManagedWebRTCSession
  .video_track`` field without a widening error.
- manager.py: change ``ManagedWebRTCSession.video_track`` annotation
  from ``MediaStreamTrack`` to ``BufferedVideoTrack | NVENCVideoTrack``.
  The aiortc base type does not advertise ``.close`` / ``.fps`` /
  ``.qsize``, which the generation worker and liveness watchdog need.
  Drop the now-unused ``MediaStreamTrack`` import.
- test_encoders.py: swap mypy-style ``# type: ignore[misc|arg-type]``
  to ty-style ``# ty:ignore[invalid-assignment|invalid-argument-type]``
  on the frozen-dataclass and SimpleNamespace test sites. Add
  ``assert packet.pts is not None`` before ``int(packet.pts)`` in the
  ordering tests -- ``av.Packet.pts`` is nullable at the type level
  even though the fake encoder always sets it.
- test_webrtc_manager.py: add a local ``_FakeVideoEncoder`` stub and
  thread ``video_encoder=`` through the ``ManagedWebRTCSession``
  construction the shared base test uses.
- integrations/lingbot/tests/test_webrtc_runtime.py: same treatment
  for the three ``_ManagedLingbotSession`` constructions -- the
  lingbot tests broke when we promoted ``video_encoder`` to a
  required field on the shared ``ManagedWebRTCSession`` base.
- integrations/omnidreams/tests/test_webrtc_runtime.py: drop 8 unused
  ``# ty:ignore[invalid-argument-type]`` comments ty flagged (5 on
  ``video_encoder=…``, 3 on ``transceiver=transceiver``).
- integrations/omnidreams/ludus-renderer/examples/render_mirror_augmented.py:
  drop the ``# ty:ignore[unresolved-import]`` on the
  ``PyNvVideoCodec`` import. The comment was pre-existing but became
  unused when this feature promoted PyNvVideoCodec from an optional
  extra to a hard dependency of ``integrations/omnidreams``.
- Ruff format touched a few files in passing (test_nvenc_track.py,
  test_nvenc_smoke.py, test_encoders.py); those changes are
  formatting-only and included here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
``_enforce_h264_or_fallback`` had two ownership violations:

1. It overwrote ``managed_session.video_track`` without closing the
   pre-encoded NVENC track. ``ManagedWebRTCSession.close()`` then only
   saw the fallback track, leaving the original's ``readyState`` "live"
   and its packet queue undrained.

2. It called ``close()`` on ``managed_session.video_encoder``, which is
   the same object the runtime owns via ``runtime._video_encoder``
   (created once in ``_initialize_video_encoder_sync`` and reused
   across sessions). Subsequent sessions would read a closed
   ``PyNvHardwareEncoder`` from ``runtime.video_encoder``.

- ``_enforce_h264_or_fallback``: sync → async; await ``old_track.close()``
  before installing the fallback. Do not close the encoder — the runtime
  keeps ownership and releases it at runtime shutdown.
- Update the one caller in ``_create_answer_with_runtime_ready_locked``
  to await the coroutine.
- Tests: run the three cases under ``pytest-asyncio``; assert the
  orphaned track IS closed (session-scoped) and the runtime-owned
  encoder is NOT closed (survives session-scope fallback).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
``BaseWebRTCSessionManager._create_answer_with_runtime_ready_locked``
unconditionally read ``runtime.video_encoder``. Only
``OmnidreamsInferenceRuntime`` defines that property;
``LingbotInferenceRuntime`` does not, so a real Lingbot WebRTC
session would raise ``AttributeError`` before returning the SDP
answer. Lingbot's session-construction tests sidestep this because
they build ``_ManagedLingbotSession`` directly with a fake encoder,
bypassing the answer-creation path that triggers the read.

- ``BaseWebRTCSessionManager``: add ``_resolve_video_encoder()`` that
  reads ``runtime.video_encoder`` if present, else constructs a
  session-scope ``DefaultRTCEncoder(fps=self.fps)``.
- ``_create_answer_with_runtime_ready_locked`` routes through the
  new method instead of the direct attribute read.
- ``test_webrtc_manager.py``: add ci_cpu tests for both branches
  (runtime provides an encoder, runtime does not). Extend
  ``_FakeVideoEncoder`` with an async ``deliver_chunk`` so the
  existing generation-worker test that drives one chunk end-to-end
  keeps passing after the manager started calling into the encoder.
- ``integrations/lingbot/tests/test_webrtc_runtime.py``: add
  ``video_encoder=_FakeVideoEncoder()`` on three
  ``_ManagedLingbotSession`` construction sites that were missed
  when the field became required.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
… rank

``_initialize_video_encoder_sync`` ran on every rank via
``_initialize_sync_all_ranks`` (``distributed_op``), allocating an
NVENC session on every worker even though only the master rank serves
WebRTC media. Worker ranks then held a session slot they would never
use, and if the local NVENC pool could not accommodate one allocation
per rank, startup would fail on the ranks that were unable to
allocate.

- ``_initialize_video_encoder_sync``: early-return on non-master ranks.
  ``runtime.video_encoder`` is only ever read on the master, so
  leaving ``_video_encoder`` as ``None`` on workers is safe.
- ci_cpu tests: worker rank never reaches ``select_encoder``; master
  rank still initializes normally.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Split PyNvHardwareEncoder and its ABGR-frame / NAL-scan helpers out of
encoders.py into a sibling nvenc.py that owns the top-level
PyNvVideoCodec import. encoders.py no longer touches PyNvVideoCodec at
module load; select_encoder probes availability via importlib.util.find_spec
(no side effects) and imports nvenc only when a hardware backend is
actually about to be constructed.

The Lingbot WebRTC path resolves to DefaultRTCEncoder in the base
manager and never enters the hardware branch, so its process no longer
loads PyNvVideoCodec at all — sidestepping a silent early-exit observed
during the Lingbot server launch when PyNvVideoCodec was importable.

Tests updated to patch _pynvvideocodec_installed and inject a fake
PyNvVideoCodec into sys.modules so nvenc's top-level import binds to
the mock. The GPU smoke test's imports follow the split.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@ksheth-dev
ksheth-dev force-pushed the pull-request/999999 branch from 3ce03df to b65f4c8 Compare July 29, 2026 09:09
@ksheth-dev
ksheth-dev force-pushed the dev/ksheth/pynvvideocodec_v2 branch 4 times, most recently from bec5ea2 to 6434238 Compare July 29, 2026 11:21
Base automatically changed from dev/ksheth/pynvvideocodec_v2 to main August 3, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant