Skip to content

Commit da44627

Browse files
authored
Port CPU jpeg encoder to stable ABI. (#9543)
1 parent 0bc41e6 commit da44627

6 files changed

Lines changed: 49 additions & 18 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ set(TORCHVISION_STABLE_SOURCES
105105
${TVCPP}/io/image/cuda/decode_jpegs_cuda.cpp
106106
${TVCPP}/io/image/cuda/encode_jpegs_cuda.cpp
107107
${TVCPP}/io/image/cpu/encode_png.cpp
108+
${TVCPP}/io/image/cpu/encode_jpeg.cpp
108109
${TVCPP}/io/image/common_stable.cpp
109110
${TVCPP}/vision_stable.cpp)
110111
# Pin them to torch 2.11. Per source, not library-wide: the pin makes ATen headers

setup.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ def get_macros_and_flags():
166166
CSRS_DIR / "ops/quantized/cpu/qnms_kernel.cpp",
167167
CSRS_DIR / "io/image/common_stable.cpp",
168168
CSRS_DIR / "io/image/cpu/encode_png.cpp",
169+
CSRS_DIR / "io/image/cpu/encode_jpeg.cpp",
169170
}
170171
STABLE_SOURCES.add(CSRS_DIR / ("ops/hip/nms_kernel.hip" if IS_ROCM else "ops/cuda/nms_kernel.cu"))
171172
STABLE_SOURCES.add(
@@ -470,6 +471,8 @@ def make_image_stable_extension():
470471
+ _stable(image_dir.glob("cpu/*.cpp"))
471472
+ _stable(image_dir.glob("hip/*.cpp" if IS_ROCM else "cuda/*.cpp"))
472473
)
474+
# Shared libjpeg glue. Legacy decode_jpeg still needs it, so it builds into both extensions.
475+
sources.append(image_dir / "cpu/common_jpeg.cpp")
473476

474477
Extension = CppExtension
475478

@@ -484,6 +487,18 @@ def make_image_stable_extension():
484487
else:
485488
warnings.warn("Building torchvision without PNG support")
486489

490+
if USE_JPEG:
491+
jpeg_found, jpeg_include_dir, jpeg_library_dir = find_library(header="jpeglib.h")
492+
if jpeg_found:
493+
print("Building torchvision with JPEG image support")
494+
if jpeg_include_dir is not None and jpeg_library_dir is not None:
495+
include_dirs.append(jpeg_include_dir)
496+
library_dirs.append(jpeg_library_dir)
497+
libraries.append("jpeg")
498+
define_macros += [("JPEG_FOUND", 1)]
499+
else:
500+
warnings.warn("Building torchvision without JPEG support")
501+
487502
if USE_NVJPEG and (torch.cuda.is_available() or FORCE_CUDA):
488503
nvjpeg_found = CUDA_HOME is not None and (Path(CUDA_HOME) / "include/nvjpeg.h").exists()
489504
if nvjpeg_found:

torchvision/csrc/io/image/cpu/encode_jpeg.cpp

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
#include "encode_jpeg.h"
22

3+
#include <torch/csrc/stable/library.h>
4+
#include <torch/csrc/stable/ops.h>
35
#include <torch/headeronly/util/Exception.h>
46

57
#include <optional>
68

9+
#include "../common_stable.h"
710
#include "common_jpeg.h"
811

912
namespace vision {
1013
namespace image {
1114

1215
#if !JPEG_FOUND
1316

14-
torch::Tensor encode_jpeg(const torch::Tensor& data, int64_t quality) {
17+
torch::stable::Tensor encode_jpeg(
18+
const torch::stable::Tensor& data,
19+
int64_t quality) {
1520
STD_TORCH_CHECK(
1621
false, "encode_jpeg: torchvision not compiled with libjpeg support");
1722
}
@@ -28,9 +33,9 @@ using JpegSizeType = size_t;
2833

2934
using namespace detail;
3035

31-
torch::Tensor encode_jpeg(const torch::Tensor& data, int64_t quality) {
32-
C10_LOG_API_USAGE_ONCE(
33-
"torchvision.csrc.io.image.cpu.encode_jpeg.encode_jpeg");
36+
torch::stable::Tensor encode_jpeg(
37+
const torch::stable::Tensor& data,
38+
int64_t quality) {
3439
// Define compression structures and error handling
3540
struct jpeg_compress_struct cinfo {};
3641
struct torch_jpeg_error_mgr jerr {};
@@ -43,7 +48,7 @@ torch::Tensor encode_jpeg(const torch::Tensor& data, int64_t quality) {
4348
// unwind C++ stack frames, so destructors of objects created after setjmp
4449
// won't run. We use std::optional to declare tensors before setjmp while
4550
// deferring construction, and explicitly reset them on the error path.
46-
std::optional<torch::Tensor> input;
51+
std::optional<torch::stable::Tensor> input;
4752

4853
cinfo.err = jpeg_std_error(&jerr.pub);
4954
jerr.pub.error_exit = torch_jpeg_error_exit;
@@ -63,12 +68,12 @@ torch::Tensor encode_jpeg(const torch::Tensor& data, int64_t quality) {
6368
}
6469

6570
// Check that the input tensor is on CPU
66-
STD_TORCH_CHECK(
67-
data.device() == torch::kCPU, "Input tensor should be on CPU");
71+
STD_TORCH_CHECK(data.is_cpu(), "Input tensor should be on CPU");
6872

6973
// Check that the input tensor dtype is uint8
7074
STD_TORCH_CHECK(
71-
data.dtype() == torch::kU8, "Input tensor dtype should be uint8");
75+
data.scalar_type() == torch::headeronly::ScalarType::Byte,
76+
"Input tensor dtype should be uint8");
7277

7378
// Check that the input tensor is 3-dimensional
7479
STD_TORCH_CHECK(
@@ -78,7 +83,7 @@ torch::Tensor encode_jpeg(const torch::Tensor& data, int64_t quality) {
7883
int channels = data.size(0);
7984
int height = data.size(1);
8085
int width = data.size(2);
81-
input = data.permute({1, 2, 0}).contiguous();
86+
input = torch::stable::contiguous(stable_permute(data, {1, 2, 0}));
8287

8388
STD_TORCH_CHECK(
8489
channels == 1 || channels == 3,
@@ -104,7 +109,7 @@ torch::Tensor encode_jpeg(const torch::Tensor& data, int64_t quality) {
104109
jpeg_start_compress(&cinfo, TRUE);
105110

106111
auto stride = width * channels;
107-
auto ptr = input->data_ptr<uint8_t>();
112+
auto ptr = input->mutable_data_ptr<uint8_t>();
108113

109114
// Encode JPEG file
110115
while (cinfo.next_scanline < cinfo.image_height) {
@@ -115,13 +120,25 @@ torch::Tensor encode_jpeg(const torch::Tensor& data, int64_t quality) {
115120
jpeg_finish_compress(&cinfo);
116121
jpeg_destroy_compress(&cinfo);
117122

118-
torch::TensorOptions options = torch::TensorOptions{torch::kU8};
119-
auto out_tensor =
120-
torch::from_blob(jpegBuf, {(long)jpegSize}, ::free, options);
123+
auto out_tensor = torch::stable::from_blob(
124+
jpegBuf,
125+
{static_cast<int64_t>(jpegSize)},
126+
{1},
127+
torch::stable::Device(torch::headeronly::DeviceType::CPU),
128+
torch::headeronly::ScalarType::Byte,
129+
::free);
121130
jpegBuf = nullptr;
122131
return out_tensor;
123132
}
124133
#endif
125134

135+
STABLE_TORCH_LIBRARY_FRAGMENT(image, m) {
136+
m.def("encode_jpeg(Tensor data, int quality) -> Tensor");
137+
}
138+
139+
STABLE_TORCH_LIBRARY_IMPL(image, CompositeExplicitAutograd, m) {
140+
m.impl("encode_jpeg", TORCH_BOX(&encode_jpeg));
141+
}
142+
126143
} // namespace image
127144
} // namespace vision

torchvision/csrc/io/image/cpu/encode_jpeg.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#pragma once
22

3-
#include <torch/types.h>
3+
#include <torch/csrc/stable/tensor.h>
44

55
namespace vision {
66
namespace image {
77

8-
C10_EXPORT torch::Tensor encode_jpeg(
9-
const torch::Tensor& data,
8+
torch::stable::Tensor encode_jpeg(
9+
const torch::stable::Tensor& data,
1010
int64_t quality);
1111

1212
} // namespace image

torchvision/csrc/io/image/image.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ static auto registry =
1414
&decode_jpeg)
1515
.op("image::decode_webp(Tensor encoded_data, int mode) -> Tensor",
1616
&decode_webp)
17-
.op("image::encode_jpeg", &encode_jpeg)
1817
.op("image::read_file", &read_file)
1918
.op("image::write_file", &write_file)
2019
.op("image::decode_image(Tensor data, int mode, bool apply_exif_orientation=False) -> Tensor",

torchvision/csrc/io/image/image.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@
55
#include "cpu/decode_jpeg.h"
66
#include "cpu/decode_png.h"
77
#include "cpu/decode_webp.h"
8-
#include "cpu/encode_jpeg.h"
98
#include "cpu/read_write_file.h"

0 commit comments

Comments
 (0)