Skip to content

Commit 67ad436

Browse files
authored
ggml-cpu : "align corners" for bilinear upscale/downscale (#1285)
* add "align corners" mode for bilinear upscale, and allow downscaling * add ggml_interpolate, deprecate ggml_upscale_ext, pass in align-corners as bit-flag * test-backend-ops: replace ggml_upscale_ext with ggml_interpolate, add test cases for downscale and align-corners
1 parent 14147d6 commit 67ad436

File tree

6 files changed

+240
-31
lines changed

6 files changed

+240
-31
lines changed

include/ggml.h

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1765,6 +1765,12 @@ extern "C" {
17651765
enum ggml_scale_mode {
17661766
GGML_SCALE_MODE_NEAREST = 0,
17671767
GGML_SCALE_MODE_BILINEAR = 1,
1768+
1769+
GGML_SCALE_MODE_COUNT
1770+
};
1771+
1772+
enum ggml_scale_flag {
1773+
GGML_SCALE_FLAG_ALIGN_CORNERS = (1 << 8)
17681774
};
17691775

17701776
// interpolate
@@ -1777,14 +1783,26 @@ extern "C" {
17771783

17781784
// interpolate
17791785
// interpolate scale to specified dimensions
1780-
GGML_API struct ggml_tensor * ggml_upscale_ext(
1786+
GGML_DEPRECATED(GGML_API struct ggml_tensor * ggml_upscale_ext(
17811787
struct ggml_context * ctx,
17821788
struct ggml_tensor * a,
17831789
int ne0,
17841790
int ne1,
17851791
int ne2,
17861792
int ne3,
1787-
enum ggml_scale_mode mode);
1793+
enum ggml_scale_mode mode),
1794+
"use ggml_interpolate instead");
1795+
1796+
// Up- or downsamples the input to the specified size.
1797+
// 2D scale modes (eg. bilinear) are applied to the first two dimensions.
1798+
GGML_API struct ggml_tensor * ggml_interpolate(
1799+
struct ggml_context * ctx,
1800+
struct ggml_tensor * a,
1801+
int64_t ne0,
1802+
int64_t ne1,
1803+
int64_t ne2,
1804+
int64_t ne3,
1805+
uint32_t mode); // ggml_scale_mode [ | ggml_scale_flag...]
17881806

17891807
// pad each dimension with zeros: [x, ..., x] -> [x, ..., x, 0, ..., 0]
17901808
GGML_API struct ggml_tensor * ggml_pad(

src/ggml-cpu/ops.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6608,12 +6608,13 @@ static void ggml_compute_forward_upscale_f32(
66086608

66096609
GGML_TENSOR_UNARY_OP_LOCALS
66106610

6611-
const float sf0 = (float)ne0/src0->ne[0];
6612-
const float sf1 = (float)ne1/src0->ne[1];
6613-
const float sf2 = (float)ne2/src0->ne[2];
6614-
const float sf3 = (float)ne3/src0->ne[3];
6611+
float sf0 = (float)ne0/src0->ne[0];
6612+
float sf1 = (float)ne1/src0->ne[1];
6613+
float sf2 = (float)ne2/src0->ne[2];
6614+
float sf3 = (float)ne3/src0->ne[3];
66156615

6616-
const ggml_scale_mode mode = (ggml_scale_mode) ggml_get_op_params_i32(dst, 0);
6616+
const int32_t mode_flags = ggml_get_op_params_i32(dst, 0);
6617+
const ggml_scale_mode mode = (ggml_scale_mode) (mode_flags & 0xFF);
66176618

66186619
if (mode == GGML_SCALE_MODE_NEAREST) {
66196620
for (int64_t i3 = 0; i3 < ne3; i3++) {
@@ -6634,8 +6635,12 @@ static void ggml_compute_forward_upscale_f32(
66346635
}
66356636
}
66366637
} else if (mode == GGML_SCALE_MODE_BILINEAR) {
6637-
// setting a pixel offset of 0 would replicate the behavior of pytorch interpolate with align_corners=True
6638-
const float pixel_offset = 0.5f;
6638+
float pixel_offset = 0.5f;
6639+
if (mode_flags & GGML_SCALE_FLAG_ALIGN_CORNERS) {
6640+
pixel_offset = 0.0f;
6641+
sf0 = (float)(ne0 - 1) / (src0->ne[0] - 1);
6642+
sf1 = (float)(ne1 - 1) / (src0->ne[1] - 1);
6643+
}
66396644

66406645
for (int64_t i3 = 0; i3 < ne3; i3++) {
66416646
const int64_t i03 = i3 / sf3;

src/ggml.c

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4247,24 +4247,21 @@ struct ggml_tensor * ggml_pool_2d_back(
42474247
return result;
42484248
}
42494249

4250-
// ggml_upscale
4250+
// ggml_upscale / ggml_interpolate
42514251

4252-
static struct ggml_tensor * ggml_upscale_impl(
4252+
static struct ggml_tensor * ggml_interpolate_impl(
42534253
struct ggml_context * ctx,
42544254
struct ggml_tensor * a,
4255-
int ne0,
4256-
int ne1,
4257-
int ne2,
4258-
int ne3,
4259-
enum ggml_scale_mode mode) {
4260-
GGML_ASSERT(a->ne[0] <= ne0);
4261-
GGML_ASSERT(a->ne[1] <= ne1);
4262-
GGML_ASSERT(a->ne[2] <= ne2);
4263-
GGML_ASSERT(a->ne[3] <= ne3);
4264-
4255+
int64_t ne0,
4256+
int64_t ne1,
4257+
int64_t ne2,
4258+
int64_t ne3,
4259+
uint32_t mode) {
4260+
GGML_ASSERT((mode & 0xFF) < GGML_SCALE_MODE_COUNT);
4261+
42654262
struct ggml_tensor * result = ggml_new_tensor_4d(ctx, a->type, ne0, ne1, ne2, ne3);
42664263

4267-
ggml_set_op_params_i32(result, 0, mode);
4264+
ggml_set_op_params_i32(result, 0, (int32_t)mode);
42684265

42694266
result->op = GGML_OP_UPSCALE;
42704267
result->src[0] = a;
@@ -4277,7 +4274,8 @@ struct ggml_tensor * ggml_upscale(
42774274
struct ggml_tensor * a,
42784275
int scale_factor,
42794276
enum ggml_scale_mode mode) {
4280-
return ggml_upscale_impl(ctx, a, a->ne[0] * scale_factor, a->ne[1] * scale_factor, a->ne[2], a->ne[3], mode);
4277+
GGML_ASSERT(scale_factor > 1);
4278+
return ggml_interpolate_impl(ctx, a, a->ne[0] * scale_factor, a->ne[1] * scale_factor, a->ne[2], a->ne[3], mode);
42814279
}
42824280

42834281
struct ggml_tensor * ggml_upscale_ext(
@@ -4288,7 +4286,18 @@ struct ggml_tensor * ggml_upscale_ext(
42884286
int ne2,
42894287
int ne3,
42904288
enum ggml_scale_mode mode) {
4291-
return ggml_upscale_impl(ctx, a, ne0, ne1, ne2, ne3, mode);
4289+
return ggml_interpolate_impl(ctx, a, ne0, ne1, ne2, ne3, mode);
4290+
}
4291+
4292+
struct ggml_tensor * ggml_interpolate(
4293+
struct ggml_context * ctx,
4294+
struct ggml_tensor * a,
4295+
int64_t ne0,
4296+
int64_t ne1,
4297+
int64_t ne2,
4298+
int64_t ne3,
4299+
uint32_t mode) {
4300+
return ggml_interpolate_impl(ctx, a, ne0, ne1, ne2, ne3, mode);
42924301
}
42934302

42944303
// ggml_pad

tests/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,4 +326,13 @@ if (NOT GGML_BACKEND_DL)
326326
target_link_libraries(${TEST_TARGET} PRIVATE ggml)
327327
add_test(NAME ${TEST_TARGET} COMMAND $<TARGET_FILE:${TEST_TARGET}>)
328328
set_property(TEST ${TEST_TARGET} PROPERTY ENVIRONMENT "LLVM_PROFILE_FILE=${TEST_TARGET}.profraw")
329+
330+
#
331+
# test-interpolate
332+
333+
set(TEST_TARGET test-interpolate)
334+
add_executable(${TEST_TARGET} ${TEST_TARGET}.cpp)
335+
target_link_libraries(${TEST_TARGET} PRIVATE ggml)
336+
add_test(NAME ${TEST_TARGET} COMMAND $<TARGET_FILE:${TEST_TARGET}>)
337+
set_property(TEST ${TEST_TARGET} PROPERTY ENVIRONMENT "LLVM_PROFILE_FILE=${TEST_TARGET}.profraw")
329338
endif()

tests/test-backend-ops.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3066,28 +3066,28 @@ struct test_upscale : public test_case {
30663066
}
30673067
};
30683068

3069-
// GGML_OP_UPSCALE (ext)
3070-
struct test_upscale_ext : public test_case {
3069+
// GGML_OP_UPSCALE (via ggml_interpolate)
3070+
struct test_interpolate : public test_case {
30713071
const ggml_type type;
30723072
const std::array<int64_t, 4> ne;
30733073
const std::array<int64_t, 4> ne_tgt;
3074-
const ggml_scale_mode mode = GGML_SCALE_MODE_NEAREST;
3074+
const uint32_t mode = GGML_SCALE_MODE_NEAREST;
30753075

30763076
std::string vars() override {
30773077
return VARS_TO_STR4(type, ne, ne_tgt, mode);
30783078
}
30793079

3080-
test_upscale_ext(ggml_type type = GGML_TYPE_F32,
3080+
test_interpolate(ggml_type type = GGML_TYPE_F32,
30813081
std::array<int64_t, 4> ne = {2, 5, 7, 11},
30823082
std::array<int64_t, 4> ne_tgt = {5, 7, 11, 13},
3083-
ggml_scale_mode mode = GGML_SCALE_MODE_NEAREST)
3083+
uint32_t mode = GGML_SCALE_MODE_NEAREST)
30843084
: type(type), ne(ne), ne_tgt(ne_tgt), mode(mode) {}
30853085

30863086
ggml_tensor * build_graph(ggml_context * ctx) override {
30873087
ggml_tensor * a = ggml_new_tensor(ctx, type, 4, ne.data());
30883088
ggml_set_name(a, "a");
30893089

3090-
ggml_tensor * out = ggml_upscale_ext(ctx, a, ne_tgt[0], ne_tgt[1],ne_tgt[2], ne_tgt[3], mode);
3090+
ggml_tensor * out = ggml_interpolate(ctx, a, ne_tgt[0], ne_tgt[1],ne_tgt[2], ne_tgt[3], mode);
30913091
ggml_set_name(out, "out");
30923092

30933093
return out;
@@ -4521,8 +4521,10 @@ static std::vector<std::unique_ptr<test_case>> make_test_cases_eval() {
45214521
for (ggml_scale_mode mode : {GGML_SCALE_MODE_NEAREST, GGML_SCALE_MODE_BILINEAR}) {
45224522
test_cases.emplace_back(new test_upscale(GGML_TYPE_F32, {512, 512, 3, 2}, 2, mode));
45234523
test_cases.emplace_back(new test_upscale(GGML_TYPE_F32, {512, 512, 3, 2}, 2, mode, true));
4524-
test_cases.emplace_back(new test_upscale_ext(GGML_TYPE_F32, {2, 5, 7, 11}, {5, 7, 11, 13}, mode));
4524+
test_cases.emplace_back(new test_interpolate(GGML_TYPE_F32, {2, 5, 7, 11}, {5, 7, 11, 13}, mode));
4525+
test_cases.emplace_back(new test_interpolate(GGML_TYPE_F32, {5, 7, 11, 13}, {2, 5, 7, 11}, mode));
45254526
}
4527+
test_cases.emplace_back(new test_interpolate(GGML_TYPE_F32, {2, 5, 7, 11}, {5, 7, 11, 13}, GGML_SCALE_MODE_BILINEAR | GGML_SCALE_FLAG_ALIGN_CORNERS));
45264528

45274529
test_cases.emplace_back(new test_sum());
45284530
test_cases.emplace_back(new test_sum_rows());

tests/test-interpolate.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#include <ggml.h>
2+
#include <ggml-cpu.h>
3+
#include <ggml-alloc.h>
4+
#include <ggml-backend.h>
5+
#include <ggml-cpp.h>
6+
7+
#include <cassert>
8+
#include <cmath>
9+
#include <cstdio>
10+
#include <array>
11+
#include <vector>
12+
13+
bool check_equal(const float * result, const float * expected, int64_t n) {
14+
for (int i = 0; i < n; i++) {
15+
if(std::abs(result[i] - expected[i]) > 1e-4) {
16+
printf("result[%d] %f != %f expected[%d]\n", i, result[i], expected[i], i);
17+
return false;
18+
}
19+
}
20+
return true;
21+
}
22+
23+
bool test_interpolate(char const* name,
24+
std::array<int64_t, 4> src_ne, const float * src_data,
25+
std::array<int32_t, 4> dst_ne, const float * expected,
26+
uint32_t mode) {
27+
ggml_time_init();
28+
29+
ggml_init_params params {
30+
/*.mem_size =*/ 64 * ggml_tensor_overhead() + ggml_graph_overhead(),
31+
/*.mem_buffer =*/ NULL,
32+
/*.no_alloc =*/ true
33+
};
34+
35+
ggml_context_ptr ctx_ptr{ggml_init(params)};
36+
ggml_context * ctx = ctx_ptr.get();
37+
ggml_cgraph * gf = ggml_new_graph(ctx);
38+
39+
// Build graph
40+
ggml_tensor * src = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, src_ne.data());
41+
ggml_tensor * res = ggml_interpolate(ctx, src, dst_ne[0], dst_ne[1], dst_ne[2], dst_ne[3], mode);
42+
ggml_build_forward_expand(gf, res);
43+
44+
// Create backend & allocate buffers
45+
ggml_backend_ptr backend_ptr{ggml_backend_cpu_init()};
46+
ggml_backend_t backend = backend_ptr.get();
47+
ggml_backend_cpu_set_n_threads(backend, 2);
48+
ggml_backend_buffer_ptr buffer{ggml_backend_alloc_ctx_tensors(ctx, backend)};
49+
50+
// Execute and compare results
51+
ggml_backend_tensor_set(src, src_data, 0, ggml_nbytes(src));
52+
ggml_backend_graph_compute(backend, gf);
53+
54+
std::vector<float> res_values(ggml_nelements(res));
55+
ggml_backend_tensor_get(res, res_values.data(), 0, ggml_nbytes(res));
56+
57+
bool passed = check_equal(res_values.data(), expected, ggml_nelements(res));
58+
59+
printf("ggml_interpolate(%s): %s\n", name, passed ? "\033[32mPASSED\033[0m" : "\033[31mFAILED\033[0m");
60+
return passed;
61+
}
62+
63+
const float input_upscale[] = {
64+
0.0f, 1.0f,
65+
2.0f, 4.0f
66+
};
67+
68+
const float expected_upscale_x2_nearest[] = {
69+
0.0f, 0.0f, 1.0f, 1.0f,
70+
0.0f, 0.0f, 1.0f, 1.0f,
71+
2.0f, 2.0f, 4.0f, 4.0f,
72+
2.0f, 2.0f, 4.0f, 4.0f
73+
};
74+
75+
const float expected_upscale_x2_bilinear[] = {
76+
0.0f, 0.2500f, 0.7500f, 1.00f,
77+
0.5f, 0.8125f, 1.4375f, 1.75f,
78+
1.5f, 1.9375f, 2.8125f, 3.25f,
79+
2.0f, 2.5000f, 3.5000f, 4.00f
80+
};
81+
82+
const float expected_upscale_x2_bilinear_align_corners[] = {
83+
0.0000f, 0.3333f, 0.6667f, 1.0000f,
84+
0.6667f, 1.1111f, 1.5556f, 2.0000f,
85+
1.3333f, 1.8889f, 2.4444f, 3.0000f,
86+
2.0000f, 2.6667f, 3.3333f, 4.0000f
87+
};
88+
89+
const float expected_upscale_x1_5_bilinear_align_corners[] = {
90+
0.0f, 1.0f,
91+
1.0f, 2.5f,
92+
2.0f, 4.0f
93+
};
94+
95+
const float input_downscale[] = {
96+
0.0f, -1.0f, -2.0f, 0.0f,
97+
1.0f, 2.0f , 4.0f , 4.0f,
98+
2.0f, 2.0f , 1.0f , 1.0f,
99+
100+
1.0f, 2.0f , 3.0f , 4.0f,
101+
2.0f, 2.0f , 2.0f , 2.0f,
102+
-2.0f, 2.0f, -4.0f, 4.0f
103+
};
104+
105+
const float expected_downscale_nearest[] = {
106+
0.0f, -2.0f,
107+
108+
1.0f, 3.0f
109+
};
110+
111+
const float expected_downscale_bilinear[] = {
112+
0.1667f, -0.3750f, 0.7500f,
113+
1.7917f, 1.8750f, 1.7500f,
114+
115+
1.3750f, 2.3750f, 3.3750f,
116+
-0.5000f, -0.2500f, 2.5000f
117+
};
118+
119+
const float expected_downscale_bilinear_align_corners[] = {
120+
0.0f , -1.5f, 0.0f,
121+
2.0f , 1.5f, 1.0f,
122+
123+
1.0f , 2.5f, 4.0f,
124+
-2.0f, -1.0f, 4.0f
125+
};
126+
127+
int main() {
128+
bool passed = true;
129+
130+
passed &= test_interpolate("upscale_x2_nearest",
131+
{2, 2, 1, 1}, input_upscale,
132+
{4, 4, 1, 1}, expected_upscale_x2_nearest,
133+
GGML_SCALE_MODE_NEAREST);
134+
135+
passed &= test_interpolate("upscale_x2_bilinear",
136+
{2, 2, 1, 1}, input_upscale,
137+
{4, 4, 1, 1}, expected_upscale_x2_bilinear,
138+
GGML_SCALE_MODE_BILINEAR);
139+
140+
passed &= test_interpolate("upscale_x2_bilinear_align_corners",
141+
{2, 2, 1, 1}, input_upscale,
142+
{4, 4, 1, 1}, expected_upscale_x2_bilinear_align_corners,
143+
GGML_SCALE_MODE_BILINEAR | GGML_SCALE_FLAG_ALIGN_CORNERS);
144+
145+
passed &= test_interpolate("upscale_x1_5_bilinear_align_corners",
146+
{2, 2, 1, 1}, input_upscale,
147+
{2, 3, 1, 1}, expected_upscale_x1_5_bilinear_align_corners,
148+
GGML_SCALE_MODE_BILINEAR | GGML_SCALE_FLAG_ALIGN_CORNERS);
149+
150+
passed &= test_interpolate("downscale_nearest",
151+
{4, 3, 2, 1}, input_downscale,
152+
{2, 1, 2, 1}, expected_downscale_nearest,
153+
GGML_SCALE_MODE_NEAREST);
154+
155+
passed &= test_interpolate("downscale_bilinear",
156+
{4, 3, 2, 1}, input_downscale,
157+
{3, 2, 2, 1}, expected_downscale_bilinear,
158+
GGML_SCALE_MODE_BILINEAR);
159+
160+
passed &= test_interpolate("downscale_bilinear_align_corners",
161+
{4, 3, 2, 1}, input_downscale,
162+
{3, 2, 2, 1}, expected_downscale_bilinear_align_corners,
163+
GGML_SCALE_MODE_BILINEAR | GGML_SCALE_FLAG_ALIGN_CORNERS);
164+
165+
return passed ? 0 : 1;
166+
}

0 commit comments

Comments
 (0)