Skip to content

Commit 5bb18bf

Browse files
authored
Know which 'mode' the BetaCudaDeviceInterface is in (#1602)
1 parent 64763fc commit 5bb18bf

2 files changed

Lines changed: 88 additions & 55 deletions

File tree

src/torchcodec/_core/BetaCudaDeviceInterface.cpp

Lines changed: 82 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@ cudaVideoSurfaceFormat get_preferred_surface_format(OutputDtype output_dtype) {
7676
: cudaVideoSurfaceFormat_NV12;
7777
}
7878

79+
// Whether a frame is a CPU-fallback frame rather than a GPU NVDEC surface,
80+
// inferred from its pixel format. This works today because our CPU fallback
81+
// never yields NV12/P016 frames, but it's only a proxy, and it's not super
82+
// robust to future changes.
83+
bool is_cpu_fallback(int format) {
84+
return format != AV_PIX_FMT_NV12 && format != AV_PIX_FMT_P016LE;
85+
}
86+
7987
static bool g_cuda_nvdec = register_device_interface(
8088
DeviceInterfaceKey(kStableCUDA, /*variant=*/"default"),
8189
[](const StableDevice& device) {
@@ -324,6 +332,17 @@ BetaCudaDeviceInterface::BetaCudaDeviceInterface(const StableDevice& device)
324332
nvcuvid_available_ = load_nvcuvid_library();
325333
}
326334

335+
BetaCudaDeviceInterface::Mode BetaCudaDeviceInterface::mode() const {
336+
if (decoding_initialized_ && color_conversion_initialized_) {
337+
return Mode::Both;
338+
} else if (decoding_initialized_) {
339+
return Mode::DecoderOnly;
340+
} else if (color_conversion_initialized_) {
341+
return Mode::ColorConverterOnly;
342+
}
343+
return Mode::Uninitialized;
344+
}
345+
327346
void BetaCudaDeviceInterface::initialize_color_conversion(
328347
const VideoStreamOptions& video_stream_options,
329348
const std::vector<std::unique_ptr<Transform>>& transforms,
@@ -333,6 +352,7 @@ void BetaCudaDeviceInterface::initialize_color_conversion(
333352
cpu_fallback_->initialize_color_conversion(
334353
video_stream_options, transforms, resized_output_dims);
335354
}
355+
color_conversion_initialized_ = true;
336356
}
337357

338358
void BetaCudaDeviceInterface::initialize_video_decoding(
@@ -341,6 +361,7 @@ void BetaCudaDeviceInterface::initialize_video_decoding(
341361
const VideoStreamOptions& video_stream_options) {
342362
STD_TORCH_CHECK(av_stream != nullptr, "AVStream cannot be null");
343363
CudaContextGuard context_guard(device_.index());
364+
decoding_initialized_ = true;
344365
rotation_ = rotation_from_degrees(get_rotation_from_stream(av_stream));
345366
output_dtype_ = video_stream_options.output_dtype;
346367

@@ -867,56 +888,62 @@ void BetaCudaDeviceInterface::make_frame_standalone(UniqueAVFrame& av_frame) {
867888
// StandAloneFrameAttachedData struct, which is then used by the
868889
// ColorConverter in convert_cuda_frame_to_av_frame() to perform the
869890
// color-conversion correctly.
891+
// The above is mainly relevant for GPU frames, but the CPU frames (in case of
892+
// a fallback) are still handled here for consistency.
893+
STD_TORCH_CHECK(
894+
mode() == Mode::DecoderOnly,
895+
"make_frame_standalone() is only valid in decoder-only mode: standalone "
896+
"frames are meant to be consumed by a separate ColorConverter.");
870897
CudaContextGuard context_guard(device_.index());
871-
if (!(av_frame->format == AV_PIX_FMT_P016LE ||
872-
av_frame->format == AV_PIX_FMT_NV12)) {
873-
// The CPU frames are already standalone, so we don't need to do anything.
874-
return;
875-
}
876-
877-
// The amount of bytes an NV12 image takes is:
878-
// num_bytes = len(Y) + len(UV)
879-
// = num_pixels + num_pixels / 2
880-
// = num_pixels * 3 / 2
881-
//
882-
// where num_pixels = pitch * height, not num_pixels = width * height. The
883-
// pitch value also accounts for the data size (uint8 vs uint16) so this is
884-
// also correct for P016.
885-
int64_t even_height =
886-
static_cast<int64_t>(round_up_to_even(av_frame->height));
887-
int64_t pitch = static_cast<int64_t>(av_frame->linesize[0]);
888-
int64_t num_bytes = pitch * even_height * 3 / 2;
889-
890-
auto storage =
891-
torch::stable::empty({num_bytes}, kStableUInt8, std::nullopt, device_);
892-
893-
// TODO_API_BREAKDOWN_CUDA P1: I suspect we don't need to wait on the nvdec
894-
// stream here, because we can only arrive here from a path where the frame
895-
// has already been mapped so its data is available - worth double checking.
896898
cudaStream_t current_stream = get_current_cuda_stream(device_.index());
897-
cudaError_t err = cudaMemcpyAsync(
898-
storage.mutable_data_ptr(),
899-
av_frame->data[0],
900-
static_cast<size_t>(num_bytes),
901-
cudaMemcpyDeviceToDevice,
902-
current_stream);
903-
STD_TORCH_CHECK(
904-
err == cudaSuccess,
905-
"Failed to copy NVDEC surface: ",
906-
cudaGetErrorString(err));
907-
908-
// TODO_API_BREAKDOWN_CUDA P2: Should we unmap here? Or let the next
909-
// receive_frame() call do it?
910-
// unmap_previous_frame();
911-
912-
auto y_plane = static_cast<uint8_t*>(storage.mutable_data_ptr());
913-
av_frame->data[0] = y_plane;
914-
av_frame->data[1] = y_plane + (pitch * even_height);
915899

916900
auto attached_data = new StandAloneFrameAttachedData();
917901
attached_data->producer_stream = current_stream;
918-
// TODO_API_BREAKDOWN_CUDA P2: We don't *really* need to std::move it I guess?
919-
attached_data->storage = std::move(storage);
902+
903+
if (!is_cpu_fallback(av_frame->format)) {
904+
// The amount of bytes an NV12 image takes is:
905+
// num_bytes = len(Y) + len(UV)
906+
// = num_pixels + num_pixels / 2
907+
// = num_pixels * 3 / 2
908+
//
909+
// where num_pixels = pitch * height, not num_pixels = width * height. The
910+
// pitch value also accounts for the data size (uint8 vs uint16) so this is
911+
// also correct for P016.
912+
int64_t even_height =
913+
static_cast<int64_t>(round_up_to_even(av_frame->height));
914+
int64_t pitch = static_cast<int64_t>(av_frame->linesize[0]);
915+
int64_t num_bytes = pitch * even_height * 3 / 2;
916+
917+
auto storage =
918+
torch::stable::empty({num_bytes}, kStableUInt8, std::nullopt, device_);
919+
920+
// TODO_API_BREAKDOWN_CUDA P1: I suspect we don't need to wait on the nvdec
921+
// stream here, because we can only arrive here from a path where the frame
922+
// has already been mapped so its data is available - worth double checking.
923+
cudaError_t err = cudaMemcpyAsync(
924+
storage.mutable_data_ptr(),
925+
av_frame->data[0],
926+
static_cast<size_t>(num_bytes),
927+
cudaMemcpyDeviceToDevice,
928+
current_stream);
929+
STD_TORCH_CHECK(
930+
err == cudaSuccess,
931+
"Failed to copy NVDEC surface: ",
932+
cudaGetErrorString(err));
933+
934+
// TODO_API_BREAKDOWN_CUDA P2: Should we unmap here? Or let the next
935+
// receive_frame() call do it?
936+
// unmap_previous_frame();
937+
938+
auto y_plane = static_cast<uint8_t*>(storage.mutable_data_ptr());
939+
av_frame->data[0] = y_plane;
940+
av_frame->data[1] = y_plane + (pitch * even_height);
941+
942+
// TODO_API_BREAKDOWN_CUDA P2: We don't *really* need to std::move it I
943+
// guess?
944+
attached_data->storage = std::move(storage);
945+
}
946+
920947
av_frame->opaque_ref = av_buffer_create(
921948
reinterpret_cast<uint8_t*>(attached_data),
922949
sizeof(StandAloneFrameAttachedData),
@@ -1094,11 +1121,7 @@ void BetaCudaDeviceInterface::convert_av_frame_to_frame_output(
10941121
FrameOutput& frame_output,
10951122
std::optional<torch::stable::Tensor> pre_allocated_output_tensor) {
10961123
CudaContextGuard context_guard(device_.index());
1097-
// TODO_API_BREAKDOWN_CUDA P0 is that accurate and safe? Can there be a CPU
1098-
// NV12 frame in our code? Should we create a helper used in the
1099-
// make_standalone function too?
1100-
bool cpu_fallback = av_frame.format != AV_PIX_FMT_NV12 &&
1101-
av_frame.format != AV_PIX_FMT_P016LE;
1124+
bool cpu_fallback = is_cpu_fallback(av_frame.format);
11021125

11031126
if (cpu_fallback) {
11041127
// When the CPU fallback happens, we'll try to run the color-conversion on
@@ -1139,6 +1162,10 @@ void BetaCudaDeviceInterface::convert_av_frame_to_frame_output(
11391162
AVPixelFormat target_pix_fmt = (output_dtype_ == OutputDtype::FLOAT32)
11401163
? AV_PIX_FMT_P016LE
11411164
: AV_PIX_FMT_NV12;
1165+
// TODO_API_BREAKDOWN P1: we should do this before the color-conversion,
1166+
// right? We want the PacketDecoder to return a GPU frame! This will
1167+
// probably become immediately relevant once we start outputting raw YUV
1168+
// data.
11421169
transferred_frame = transfer_cpu_frame_to_gpu(av_frame, target_pix_fmt);
11431170
}
11441171
const AVFrame& gpu_frame = cpu_fallback ? *transferred_frame : av_frame;
@@ -1148,12 +1175,12 @@ void BetaCudaDeviceInterface::convert_av_frame_to_frame_output(
11481175
gpu_frame.format == AV_PIX_FMT_P016LE,
11491176
"Expected NV12 or P016LE format frame");
11501177

1151-
// TODO_API_BREAKDOWN P1: Cleanup how we get the attached data? Make it more
1152-
// robust? Should we couple it to a flag on the interface saying "I'm
1153-
// color-conversion only, I absolutely expect frames to be standalone"?
11541178
cudaStream_t producer_stream;
1155-
if (av_frame.opaque_ref != nullptr &&
1156-
av_frame.opaque_ref->size == sizeof(StandAloneFrameAttachedData)) {
1179+
if (mode() == Mode::ColorConverterOnly) {
1180+
STD_TORCH_CHECK(
1181+
av_frame.opaque_ref != nullptr,
1182+
"ColorConverter received a non-standalone frame; frames fed to a "
1183+
"standalone ColorConverter must come from a PacketDecoder.");
11571184
auto attached_data = reinterpret_cast<StandAloneFrameAttachedData*>(
11581185
av_frame.opaque_ref->data);
11591186
producer_stream = attached_data->producer_stream;

src/torchcodec/_core/BetaCudaDeviceInterface.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ class BetaCudaDeviceInterface : public DeviceInterface {
7777
std::string get_details() override;
7878

7979
private:
80+
enum class Mode { Uninitialized, DecoderOnly, ColorConverterOnly, Both };
81+
Mode mode() const;
82+
8083
int send_cuvid_packet(CUVIDSOURCEDATAPACKET& cuvid_packet);
8184

8285
void send_seqhdr_packet();
@@ -122,6 +125,9 @@ class BetaCudaDeviceInterface : public DeviceInterface {
122125

123126
UniqueAVBSFContext bitstream_filter_;
124127

128+
bool decoding_initialized_ = false;
129+
bool color_conversion_initialized_ = false;
130+
125131
std::unique_ptr<DeviceInterface> cpu_fallback_;
126132
bool nvcuvid_available_ = false;
127133
UniqueSwsContext sws_context_;

0 commit comments

Comments
 (0)