@@ -45,11 +45,13 @@ namespace facebook::torchcodec {
4545// API.
4646// nvjpegDecodeBatch appears to be the only entry-point for the HW path, but
4747// unclear.
48- // TODO_IMAGE Should verify on a machien that supports the HW path whether:
49- // - always calling nvjpegDecodeBatch even on single images is indeed faster than using the 'sw' path
48+ // TODO_IMAGE Should verify on a machine that supports the HW path whether:
49+ // - always calling nvjpegDecodeBatch even on single images is indeed faster
50+ // than using the 'sw' path
5051// - whether we can just rely on calls to nvjpegDeode to dispatch to the HW path
5152// and if that's as fast as calling nvjpegDecodeBatch, then maybe we don't
52- // need to publicly expose an API that accepts batches (decode_jpeg(batch...)).
53+ // need to publicly expose an API that accepts batches
54+ // (decode_jpeg(batch...)).
5355
5456using namespace exif_private ;
5557
@@ -129,8 +131,6 @@ std::unique_ptr<CUDAJpegDecoder> NVJpegCache::get_decoder(
129131 return decoder;
130132 }
131133 }
132- // Create outside the lock: constructing nvJPEG state is relatively expensive
133- // and doesn't need the pool.
134134 return std::make_unique<CUDAJpegDecoder>(device);
135135}
136136
@@ -180,31 +180,27 @@ std::vector<torch::stable::Tensor> decode_jpegs_cuda(
180180 int device_index = get_device_index (device);
181181 StableDeviceGuard device_guard (device_index);
182182
183- cudaStream_t stream = get_current_cuda_stream (device_index);
183+ cudaStream_t current_stream = get_current_cuda_stream (device_index);
184184
185185 NVJpegCache& cache = NVJpegCache::get_cache (device);
186186 std::unique_ptr<CUDAJpegDecoder> decoder = cache.get_decoder (device);
187187
188- std::vector<torch::stable::Tensor> result ;
188+ std::vector<torch::stable::Tensor> output ;
189189 // TODO_IMAGE Do we really need a try/except here?
190190 try {
191- result = decoder->decode_images (
192- contig_images, static_cast <ImageReadMode>(mode), stream );
191+ output = decoder->decode_images (
192+ contig_images, static_cast <ImageReadMode>(mode), current_stream );
193193 } catch (const std::exception& e) {
194194 // Return the decoder to the pool even on failure so we don't leak it.
195195 cache.return_decoder (std::move (decoder));
196196 STD_TORCH_CHECK (false , " Error while decoding JPEG images: " , e.what ());
197197 }
198198 cache.return_decoder (std::move (decoder));
199199
200- // decode_images() host-synchronizes the decode stream before returning, so
201- // the decoded tensors are fully materialized; applying the EXIF transform
202- // (aten flip/transpose on the current stream) is safe. This matches the CPU
203- // decoder, which also applies EXIF orientation.
204- for (size_t i = 0 ; i < result.size (); ++i) {
205- result[i] = exif_orientation_transform (result[i], orientations[i]);
200+ for (size_t i = 0 ; i < output.size (); ++i) {
201+ output[i] = exif_orientation_transform (output[i], orientations[i]);
206202 }
207- return result ;
203+ return output ;
208204}
209205
210206CUDAJpegDecoder::CUDAJpegDecoder (const torch::stable::Device& target_device)
@@ -241,8 +237,6 @@ CUDAJpegDecoder::CUDAJpegDecoder(const torch::stable::Device& target_device)
241237 status);
242238 }
243239
244- // Batched (hardware) path state -- only ever used when the HW engine is
245- // available, so only create it then.
246240 if (hw_decode_available_) {
247241 status = nvjpegJpegStateCreate (nvjpeg_handle_, &nvjpeg_state_hw_);
248242 STD_TORCH_CHECK (
@@ -267,11 +261,11 @@ CUDAJpegDecoder::CUDAJpegDecoder(const torch::stable::Device& target_device)
267261}
268262
269263CUDAJpegDecoder::~CUDAJpegDecoder () {
270- // Unlike torchvision (which leaks these to dodge a Windows atexit-vs-CUDA
271- // teardown crash), we destroy the nvJPEG handles here. Our decoders are held
272- // in NVJpegCache, whose per-device instances are intentionally leaked (never
273- // statically destroyed), so this destructor only runs during normal cache
274- // eviction while CUDA is alive -- not at process teardown.
264+ // We properly destroy the nvjpeg stuff here. Note that this destructor is
265+ // only called when a decoder cannot return to the cache. This is never
266+ // reached during normal process teardown, because we just leak the entire
267+ // decoder cache, just like we leak the NVDEC cache to avoid weird CUDA
268+ // teardown issues .
275269 nvjpegJpegStateDestroy (nvjpeg_state_sw_);
276270 if (hw_decode_available_) {
277271 nvjpegJpegStreamDestroy (nvjpeg_stream_);
@@ -313,7 +307,8 @@ CUDAJpegDecoder::allocate_output(
313307 output_format = NVJPEG_OUTPUT_RGB ;
314308 break ;
315309 case ImageReadMode::UNCHANGED :
316- output_format = source_channels == 1 ? NVJPEG_OUTPUT_Y : NVJPEG_OUTPUT_RGB ;
310+ output_format =
311+ source_channels == 1 ? NVJPEG_OUTPUT_Y : NVJPEG_OUTPUT_RGB ;
317312 break ;
318313 default :
319314 STD_TORCH_CHECK (
@@ -363,7 +358,8 @@ CUDAJpegDecoder::split_images_by_backend(
363358 encoded_images[i].numel (),
364359 nvjpeg_stream_);
365360 int is_supported = -1 ;
366- nvjpegDecodeBatchedSupported (nvjpeg_handle_, nvjpeg_stream_, &is_supported);
361+ nvjpegDecodeBatchedSupported (
362+ nvjpeg_handle_, nvjpeg_stream_, &is_supported);
367363 supports_hw = is_supported == 0 ; // nvJPEG sets 0 when supported
368364 }
369365 (supports_hw ? hw_indices : sw_indices).push_back (i);
@@ -377,7 +373,6 @@ void CUDAJpegDecoder::decode_batched_hardware(
377373 ImageReadMode mode,
378374 cudaStream_t stream,
379375 std::vector<torch::stable::Tensor>& output_tensors) {
380-
381376 std::vector<const unsigned char *> inputs;
382377 std::vector<size_t > sizes;
383378 std::vector<nvjpegImage_t> nvjpeg_images;
@@ -404,7 +399,8 @@ void CUDAJpegDecoder::decode_batched_hardware(
404399 // nvjpegDecodeBatchedInitialize can reconfigure that same state. This is an
405400 // assumption, but the sync point shouldn't hurt perf.
406401 bool needs_sync = false ;
407- for (nvjpegOutputFormat_t group_format : {NVJPEG_OUTPUT_Y , NVJPEG_OUTPUT_RGB }) {
402+ for (nvjpegOutputFormat_t group_format :
403+ {NVJPEG_OUTPUT_Y , NVJPEG_OUTPUT_RGB }) {
408404 std::vector<const unsigned char *> group_inputs;
409405 std::vector<size_t > group_sizes;
410406 std::vector<nvjpegImage_t> group_images;
@@ -429,7 +425,11 @@ void CUDAJpegDecoder::decode_batched_hardware(
429425
430426 // Should we expose max_cpu_threads????
431427 nvjpegStatus_t status = nvjpegDecodeBatchedInitialize (
432- nvjpeg_handle_, nvjpeg_state_hw_, group_images.size (), /* max_cpu_threads=*/ 1 , group_format);
428+ nvjpeg_handle_,
429+ nvjpeg_state_hw_,
430+ group_images.size (),
431+ /* max_cpu_threads=*/ 1 ,
432+ group_format);
433433 STD_TORCH_CHECK (
434434 status == NVJPEG_STATUS_SUCCESS ,
435435 " Failed to initialize batch decoding: " ,
@@ -486,8 +486,7 @@ std::vector<torch::stable::Tensor> CUDAJpegDecoder::decode_images(
486486 encoded_images, hw_indices, mode, stream, output_tensors);
487487 }
488488 if (!sw_indices.empty ()) {
489- decode_software (
490- encoded_images, sw_indices, mode, stream, output_tensors);
489+ decode_software (encoded_images, sw_indices, mode, stream, output_tensors);
491490 }
492491
493492 // Host-synchronize before returning: the decoder (and its internal nvJPEG
0 commit comments