• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daisytuner / docc / 31004186472

05 Aug 2026 12:06PM UTC coverage: 65.161% (+0.1%) from 65.016%
31004186472

Pull #937

github

web-flow
Merge 22b6c0d2b into 833b8a39b
Pull Request #937: [PyTorch] Add Support for aten.upsample_bilinear2d.vec

391 of 441 new or added lines in 6 files covered. (88.66%)

46653 of 71596 relevant lines covered (65.16%)

716.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.79
/sdfg/src/data_flow/library_nodes/math/tensor/upsample_node.cpp
1
#include "sdfg/data_flow/library_nodes/math/tensor/upsample_node.h"
2

3
#include <iomanip>
4
#include <sstream>
5

6
#include "sdfg/builder/structured_sdfg_builder.h"
7
#include "sdfg/symbolic/symbolic.h"
8
#include "sdfg/types/type.h"
9

10
namespace sdfg {
11
namespace math {
12
namespace tensor {
13

14
UpsampleBilinear2DNode::UpsampleBilinear2DNode(
15
    size_t element_id,
16
    const DebugInfo& debug_info,
17
    const graph::Vertex vertex,
18
    data_flow::DataFlowGraph& parent,
19
    const std::vector<symbolic::Expression>& input_shape,
20
    const std::vector<symbolic::Expression>& output_shape,
21
    bool align_corners,
22
    const std::vector<double>& scale_factors,
23
    const data_flow::ImplementationType& impl_type
24
)
25
    : TensorNode(element_id, debug_info, vertex, parent, LibraryNodeType_UpsampleBilinear2D, {}, {"Y", "X"}, impl_type),
21✔
26
      input_shape_(input_shape), output_shape_(output_shape), align_corners_(align_corners),
21✔
27
      scale_factors_(scale_factors) {}
21✔
28

29
void UpsampleBilinear2DNode::validate(const Function& function) const {
16✔
30
    TensorNode::validate(function);
16✔
31

32
    if (input_shape_.size() != 4) {
16✔
NEW
33
        throw InvalidSDFGException("UpsampleBilinear2DNode: input_shape must have rank 4 [N, C, H, W]");
×
NEW
34
    }
×
35
    if (output_shape_.size() != 4) {
16✔
NEW
36
        throw InvalidSDFGException("UpsampleBilinear2DNode: output_shape must have rank 4 [N, C, H, W]");
×
NEW
37
    }
×
38
    if (!scale_factors_.empty() && scale_factors_.size() != 2) {
16✔
NEW
39
        throw InvalidSDFGException("UpsampleBilinear2DNode: scale_factors must be empty or have 2 entries");
×
NEW
40
    }
×
41
}
16✔
42

43
symbolic::SymbolSet UpsampleBilinear2DNode::symbols() const {
2✔
44
    symbolic::SymbolSet syms;
2✔
45
    for (const auto& dim : input_shape_) {
8✔
46
        for (auto& atom : symbolic::atoms(dim)) {
8✔
47
            syms.insert(atom);
2✔
48
        }
2✔
49
    }
8✔
50
    for (const auto& dim : output_shape_) {
8✔
51
        for (auto& atom : symbolic::atoms(dim)) {
8✔
52
            syms.insert(atom);
2✔
53
        }
2✔
54
    }
8✔
55
    return syms;
2✔
56
}
2✔
57

58
void UpsampleBilinear2DNode::replace(const symbolic::Expression old_expression, const symbolic::Expression new_expression) {
1✔
59
    for (auto& dim : input_shape_) {
4✔
60
        dim = symbolic::subs(dim, old_expression, new_expression);
4✔
61
    }
4✔
62
    for (auto& dim : output_shape_) {
4✔
63
        dim = symbolic::subs(dim, old_expression, new_expression);
4✔
64
    }
4✔
65
}
1✔
66

NEW
67
void UpsampleBilinear2DNode::replace(const symbolic::ExpressionMapping& replacements) {
×
NEW
68
    for (auto& dim : input_shape_) {
×
NEW
69
        dim = symbolic::subs(dim, replacements);
×
NEW
70
    }
×
NEW
71
    for (auto& dim : output_shape_) {
×
NEW
72
        dim = symbolic::subs(dim, replacements);
×
NEW
73
    }
×
NEW
74
}
×
75

76
passes::LibNodeExpander::ExpandOutcome UpsampleBilinear2DNode::
77
    expand(passes::LibNodeExpander::ExpandContext& context, structured_control_flow::Block& block) {
8✔
78
    auto& dataflow = this->get_parent();
8✔
79

80
    auto x_edge = dataflow.in_edge_for_connector(*this, "X");
8✔
81
    if (!x_edge) {
8✔
NEW
82
        return context.unable();
×
NEW
83
    }
×
84
    auto y_edge = dataflow.in_edge_for_connector(*this, "Y");
8✔
85
    if (!y_edge) {
8✔
NEW
86
        return context.unable();
×
NEW
87
    }
×
88

89
    types::Scalar double_type(types::PrimitiveType::Double);
8✔
90
    types::Scalar int_type(types::PrimitiveType::Int64);
8✔
91
    types::Scalar loop_type(types::PrimitiveType::UInt64);
8✔
92

93
    symbolic::Expression N = output_shape_[0];
8✔
94
    symbolic::Expression C = output_shape_[1];
8✔
95
    symbolic::Expression Hin = input_shape_[2];
8✔
96
    symbolic::Expression Win = input_shape_[3];
8✔
97
    symbolic::Expression Hout = output_shape_[2];
8✔
98
    symbolic::Expression Wout = output_shape_[3];
8✔
99

100
    using Use = passes::LibNodeExpander::InputUse;
8✔
101
    auto standalone = context.replacement_requires_access_nodes({Use::IndirectWrite, Use::IndirectRead});
8✔
102
    if (!standalone) {
8✔
NEW
103
        return context.unable();
×
NEW
104
    }
×
105

106
    auto& new_sequence = standalone->replace_with_sequence();
8✔
107
    auto& builder = standalone->builder();
8✔
108
    auto dbg = block.debug_info();
8✔
109

110
    // Casts an existing scalar operand (loop variable, shape symbol or integer constant)
111
    // into a fresh Double container using a real tasklet. Symbols already are containers,
112
    // so they are read directly; integer constants feed in as a single literal.
113
    auto emit_double_operand = [&](structured_control_flow::Sequence& scope,
8✔
114
                                   const symbolic::Expression& expr,
8✔
115
                                   const std::string& hint) -> std::string {
40✔
116
        std::string name = builder.find_new_name(hint);
40✔
117
        builder.add_container(name, double_type);
40✔
118
        auto& blk = builder.add_block(scope, {}, dbg);
40✔
119
        auto& dst_acc = builder.add_access(blk, name, dbg);
40✔
120
        auto& tk = builder.add_tasklet(blk, data_flow::assign, "_out", {"_in"}, dbg);
40✔
121
        if (SymEngine::is_a<SymEngine::Symbol>(*expr)) {
40✔
122
            std::string src_name = expr->__str__();
16✔
123
            auto& src_acc = builder.add_access(blk, src_name, dbg);
16✔
124
            builder.add_computational_memlet(blk, src_acc, tk, "_in", {}, builder.subject().type(src_name), dbg);
16✔
125
        } else if (SymEngine::is_a<SymEngine::Integer>(*expr)) {
24✔
126
            auto& c_cst = builder.add_constant(blk, expr->__str__(), double_type, dbg);
24✔
127
            builder.add_computational_memlet(blk, c_cst, tk, "_in", {}, double_type, dbg);
24✔
128
        } else {
24✔
NEW
129
            throw InvalidSDFGException(
×
NEW
130
                "UpsampleBilinear2DNode: unsupported expression type for operand: " + expr->__str__()
×
NEW
131
            );
×
NEW
132
        }
×
133
        builder.add_computational_memlet(blk, tk, "_out", dst_acc, {}, double_type, dbg);
40✔
134
        return name;
40✔
135
    };
40✔
136

137
    // Copies (and casts) a scalar container into a fresh container of dst_type.
138
    auto emit_cast = [&](structured_control_flow::Sequence& scope,
8✔
139
                         const std::string& src_name,
8✔
140
                         const types::Scalar& src_type,
8✔
141
                         const types::Scalar& dst_type,
8✔
142
                         const std::string& hint) -> std::string {
32✔
143
        std::string name = builder.find_new_name(hint);
32✔
144
        builder.add_container(name, dst_type);
32✔
145
        auto& blk = builder.add_block(scope, {}, dbg);
32✔
146
        auto& src_acc = builder.add_access(blk, src_name, dbg);
32✔
147
        auto& dst_acc = builder.add_access(blk, name, dbg);
32✔
148
        auto& tk = builder.add_tasklet(blk, data_flow::assign, "_out", {"_in"}, dbg);
32✔
149
        builder.add_computational_memlet(blk, src_acc, tk, "_in", {}, src_type, dbg);
32✔
150
        builder.add_computational_memlet(blk, tk, "_out", dst_acc, {}, dst_type, dbg);
32✔
151
        return name;
32✔
152
    };
32✔
153

154
    // Applies a binary floating-point tasklet to two Double containers.
155
    auto emit_binop = [&](structured_control_flow::Sequence& scope,
8✔
156
                          data_flow::TaskletCode code,
8✔
157
                          const std::string& a,
8✔
158
                          const std::string& b,
8✔
159
                          const std::string& hint) -> std::string {
126✔
160
        std::string name = builder.find_new_name(hint);
126✔
161
        builder.add_container(name, double_type);
126✔
162
        auto& blk = builder.add_block(scope, {}, dbg);
126✔
163
        auto& a_acc = builder.add_access(blk, a, dbg);
126✔
164
        auto& b_acc = builder.add_access(blk, b, dbg);
126✔
165
        auto& r_acc = builder.add_access(blk, name, dbg);
126✔
166
        auto& tk = builder.add_tasklet(blk, code, "_out", {"_in1", "_in2"}, dbg);
126✔
167
        builder.add_computational_memlet(blk, a_acc, tk, "_in1", {}, double_type, dbg);
126✔
168
        builder.add_computational_memlet(blk, b_acc, tk, "_in2", {}, double_type, dbg);
126✔
169
        builder.add_computational_memlet(blk, tk, "_out", r_acc, {}, double_type, dbg);
126✔
170
        return name;
126✔
171
    };
126✔
172

173
    // Applies a binary floating-point tasklet between a Double container and a single literal.
174
    auto emit_binop_lit = [&](structured_control_flow::Sequence& scope,
8✔
175
                              data_flow::TaskletCode code,
8✔
176
                              const std::string& a,
8✔
177
                              const std::string& literal,
8✔
178
                              const std::string& hint) -> std::string {
50✔
179
        std::string name = builder.find_new_name(hint);
50✔
180
        builder.add_container(name, double_type);
50✔
181
        auto& blk = builder.add_block(scope, {}, dbg);
50✔
182
        auto& a_acc = builder.add_access(blk, a, dbg);
50✔
183
        auto& c_cst = builder.add_constant(blk, literal, double_type, dbg);
50✔
184
        auto& r_acc = builder.add_access(blk, name, dbg);
50✔
185
        auto& tk = builder.add_tasklet(blk, code, "_out", {"_in1", "_in2"}, dbg);
50✔
186
        builder.add_computational_memlet(blk, a_acc, tk, "_in1", {}, double_type, dbg);
50✔
187
        builder.add_computational_memlet(blk, c_cst, tk, "_in2", {}, double_type, dbg);
50✔
188
        builder.add_computational_memlet(blk, tk, "_out", r_acc, {}, double_type, dbg);
50✔
189
        return name;
50✔
190
    };
50✔
191
    auto emit_one_minus = [&](structured_control_flow::Sequence& scope, const std::string& lam, const std::string& hint
8✔
192
                          ) -> std::string {
16✔
193
        std::string name = builder.find_new_name(hint);
16✔
194
        builder.add_container(name, double_type);
16✔
195
        auto& blk = builder.add_block(scope, {}, dbg);
16✔
196
        auto& one_c = builder.add_constant(blk, "1.0", double_type, dbg);
16✔
197
        auto& lam_acc = builder.add_access(blk, lam, dbg);
16✔
198
        auto& r_acc = builder.add_access(blk, name, dbg);
16✔
199
        auto& tk = builder.add_tasklet(blk, data_flow::fp_sub, "_out", {"_in1", "_in2"}, dbg);
16✔
200
        builder.add_computational_memlet(blk, one_c, tk, "_in1", {}, double_type, dbg);
16✔
201
        builder.add_computational_memlet(blk, lam_acc, tk, "_in2", {}, double_type, dbg);
16✔
202
        builder.add_computational_memlet(blk, tk, "_out", r_acc, {}, double_type, dbg);
16✔
203
        return name;
16✔
204
    };
16✔
205

206
    // Reads X[subset] into a fresh Double container.
207
    auto emit_pixel = [&](structured_control_flow::Sequence& scope,
8✔
208
                          const data_flow::Subset& subset,
8✔
209
                          const std::string& hint) -> std::string {
32✔
210
        std::string name = builder.find_new_name(hint);
32✔
211
        builder.add_container(name, double_type);
32✔
212
        auto& blk = builder.add_block(scope, {}, dbg);
32✔
213
        auto& x_acc = standalone->add_indirect_read_access(blk, X_INPUT_IDX);
32✔
214
        auto& p_acc = builder.add_access(blk, name, dbg);
32✔
215
        auto& tk = builder.add_tasklet(blk, data_flow::assign, "_out", {"_in"}, dbg);
32✔
216
        builder.add_computational_memlet(blk, x_acc, tk, "_in", subset, x_edge->base_type(), dbg);
32✔
217
        builder.add_computational_memlet(blk, tk, "_out", p_acc, {}, double_type, dbg);
32✔
218
        return name;
32✔
219
    };
32✔
220

221
    struct Coord {
8✔
222
        symbolic::Expression i0;
8✔
223
        symbolic::Expression i1;
8✔
224
        std::string lam;
8✔
225
        std::string lam0;
8✔
226
    };
8✔
227

228
    // Computes the fractional source coordinate, its two integer neighbours and the
229
    // interpolation weights for a single spatial dimension (align_corners aware).
230
    // All arithmetic is performed with real tasklets; constants are single literals only.
231
    auto compute_coord = [&](structured_control_flow::Sequence& scope,
8✔
232
                             const symbolic::Expression& o,
8✔
233
                             const symbolic::Expression& In,
8✔
234
                             const symbolic::Expression& Out,
8✔
235
                             size_t d) -> Coord {
16✔
236
        // o as Double (loop variable is already a container).
237
        std::string o_d = emit_double_operand(scope, o, "_o_d");
16✔
238

239
        std::string src;
16✔
240
        if (align_corners_) {
16✔
241
            if (symbolic::eq(Out, symbolic::one())) {
2✔
242
                // scale would divide by zero; PyTorch maps every output pixel to source 0.
NEW
243
                src = emit_binop_lit(scope, data_flow::fp_mul, o_d, "0.0", "_src");
×
244
            } else {
2✔
245
                // scale = (In - 1) / (Out - 1); src = scale * o.
246
                std::string in_d = emit_double_operand(scope, In, "_in_d");
2✔
247
                std::string out_d = emit_double_operand(scope, Out, "_out_d");
2✔
248
                std::string in_m1 = emit_binop_lit(scope, data_flow::fp_sub, in_d, "1.0", "_inm1");
2✔
249
                std::string out_m1 = emit_binop_lit(scope, data_flow::fp_sub, out_d, "1.0", "_outm1");
2✔
250
                std::string scale = emit_binop(scope, data_flow::fp_div, in_m1, out_m1, "_scale");
2✔
251
                src = emit_binop(scope, data_flow::fp_mul, scale, o_d, "_src");
2✔
252
            }
2✔
253
        } else {
14✔
254
            // src = max(0, rscale * (o + 0.5) - 0.5).
255
            std::string o_plus = emit_binop_lit(scope, data_flow::fp_add, o_d, "0.5", "_oplus");
14✔
256
            std::string scaled;
14✔
257
            if (!scale_factors_.empty()) {
14✔
258
                // rscale is a compile-time reciprocal of the requested scale factor.
259
                std::ostringstream oss;
4✔
260
                oss << std::setprecision(17) << (1.0 / scale_factors_[d]);
4✔
261
                scaled = emit_binop_lit(scope, data_flow::fp_mul, o_plus, oss.str(), "_scaled");
4✔
262
            } else {
10✔
263
                std::string in_d = emit_double_operand(scope, In, "_in_d");
10✔
264
                std::string out_d = emit_double_operand(scope, Out, "_out_d");
10✔
265
                std::string rscale = emit_binop(scope, data_flow::fp_div, in_d, out_d, "_rscale");
10✔
266
                scaled = emit_binop(scope, data_flow::fp_mul, rscale, o_plus, "_scaled");
10✔
267
            }
10✔
268
            std::string src_raw = emit_binop_lit(scope, data_flow::fp_sub, scaled, "0.5", "_srcraw");
14✔
269
            // Clamp negatives to zero: mask = (src_raw > 0) yields 1.0/0.0; src = src_raw * mask.
270
            std::string mask = emit_binop_lit(scope, data_flow::fp_ogt, src_raw, "0.0", "_mask");
14✔
271
            src = emit_binop(scope, data_flow::fp_mul, src_raw, mask, "_src");
14✔
272
        }
14✔
273

274
        std::string i0n = emit_cast(scope, src, double_type, int_type, "_i0");
16✔
275
        std::string i0d = emit_cast(scope, i0n, int_type, double_type, "_i0d");
16✔
276
        std::string lam = emit_binop(scope, data_flow::fp_sub, src, i0d, "_lam");
16✔
277
        std::string lam0 = emit_one_minus(scope, lam, "_lam0");
16✔
278

279
        auto i0_sym = symbolic::symbol(i0n);
16✔
280
        auto i1_sym = symbolic::min(symbolic::add(i0_sym, symbolic::one()), symbolic::sub(In, symbolic::one()));
16✔
281
        return Coord{i0_sym, i1_sym, lam, lam0};
16✔
282
    };
16✔
283

284
    structured_control_flow::Sequence* scope = &new_sequence;
8✔
285

286
    // Map over batch dimension N.
287
    std::string n_str = builder.find_new_name("n");
8✔
288
    builder.add_container(n_str, loop_type);
8✔
289
    auto n_var = symbolic::symbol(n_str);
8✔
290
    auto& map_n = builder.add_map(
8✔
291
        *scope,
8✔
292
        n_var,
8✔
293
        symbolic::Lt(n_var, N),
8✔
294
        symbolic::zero(),
8✔
295
        symbolic::add(n_var, symbolic::one()),
8✔
296
        structured_control_flow::ScheduleType_Sequential::create(),
8✔
297
        dbg
8✔
298
    );
8✔
299
    scope = &map_n.root();
8✔
300

301
    // Map over channel dimension C.
302
    std::string c_str = builder.find_new_name("c");
8✔
303
    builder.add_container(c_str, loop_type);
8✔
304
    auto c_var = symbolic::symbol(c_str);
8✔
305
    auto& map_c = builder.add_map(
8✔
306
        *scope,
8✔
307
        c_var,
8✔
308
        symbolic::Lt(c_var, C),
8✔
309
        symbolic::zero(),
8✔
310
        symbolic::add(c_var, symbolic::one()),
8✔
311
        structured_control_flow::ScheduleType_Sequential::create(),
8✔
312
        dbg
8✔
313
    );
8✔
314
    scope = &map_c.root();
8✔
315

316
    // Map over output height.
317
    std::string oh_str = builder.find_new_name("oh");
8✔
318
    builder.add_container(oh_str, loop_type);
8✔
319
    auto oh_var = symbolic::symbol(oh_str);
8✔
320
    auto& map_oh = builder.add_map(
8✔
321
        *scope,
8✔
322
        oh_var,
8✔
323
        symbolic::Lt(oh_var, Hout),
8✔
324
        symbolic::zero(),
8✔
325
        symbolic::add(oh_var, symbolic::one()),
8✔
326
        structured_control_flow::ScheduleType_Sequential::create(),
8✔
327
        dbg
8✔
328
    );
8✔
329
    auto& oh_scope = map_oh.root();
8✔
330

331
    // Height source coordinate (depends only on oh).
332
    Coord hc = compute_coord(oh_scope, oh_var, Hin, Hout, 0);
8✔
333

334
    // Map over output width.
335
    std::string ow_str = builder.find_new_name("ow");
8✔
336
    builder.add_container(ow_str, loop_type);
8✔
337
    auto ow_var = symbolic::symbol(ow_str);
8✔
338
    auto& map_ow = builder.add_map(
8✔
339
        oh_scope,
8✔
340
        ow_var,
8✔
341
        symbolic::Lt(ow_var, Wout),
8✔
342
        symbolic::zero(),
8✔
343
        symbolic::add(ow_var, symbolic::one()),
8✔
344
        structured_control_flow::ScheduleType_Sequential::create(),
8✔
345
        dbg
8✔
346
    );
8✔
347
    auto& ow_scope = map_ow.root();
8✔
348

349
    // Width source coordinate.
350
    Coord wc = compute_coord(ow_scope, ow_var, Win, Wout, 1);
8✔
351

352
    // Gather the four contributing input pixels.
353
    data_flow::Subset s00 = {n_var, c_var, hc.i0, wc.i0};
8✔
354
    data_flow::Subset s01 = {n_var, c_var, hc.i0, wc.i1};
8✔
355
    data_flow::Subset s10 = {n_var, c_var, hc.i1, wc.i0};
8✔
356
    data_flow::Subset s11 = {n_var, c_var, hc.i1, wc.i1};
8✔
357

358
    std::string p00 = emit_pixel(ow_scope, s00, "_p00");
8✔
359
    std::string p01 = emit_pixel(ow_scope, s01, "_p01");
8✔
360
    std::string p10 = emit_pixel(ow_scope, s10, "_p10");
8✔
361
    std::string p11 = emit_pixel(ow_scope, s11, "_p11");
8✔
362

363
    // Interpolate along width: top = p00 * (1 - lam_w) + p01 * lam_w.
364
    std::string t0 = emit_binop(ow_scope, data_flow::fp_mul, p00, wc.lam0, "_t0");
8✔
365
    std::string t1 = emit_binop(ow_scope, data_flow::fp_mul, p01, wc.lam, "_t1");
8✔
366
    std::string top = emit_binop(ow_scope, data_flow::fp_add, t0, t1, "_top");
8✔
367

368
    // bot = p10 * (1 - lam_w) + p11 * lam_w.
369
    std::string b0 = emit_binop(ow_scope, data_flow::fp_mul, p10, wc.lam0, "_b0");
8✔
370
    std::string b1 = emit_binop(ow_scope, data_flow::fp_mul, p11, wc.lam, "_b1");
8✔
371
    std::string bot = emit_binop(ow_scope, data_flow::fp_add, b0, b1, "_bot");
8✔
372

373
    // Interpolate along height: out = top * (1 - lam_h) + bot * lam_h.
374
    std::string o0 = emit_binop(ow_scope, data_flow::fp_mul, top, hc.lam0, "_o0");
8✔
375
    std::string o1 = emit_binop(ow_scope, data_flow::fp_mul, bot, hc.lam, "_o1");
8✔
376
    std::string out_c = emit_binop(ow_scope, data_flow::fp_add, o0, o1, "_out");
8✔
377

378
    // Write result into Y[n, c, oh, ow].
379
    data_flow::Subset y_subset = {n_var, c_var, oh_var, ow_var};
8✔
380
    auto& wblk = builder.add_block(ow_scope, {}, dbg);
8✔
381
    auto& out_acc = builder.add_access(wblk, out_c, dbg);
8✔
382
    auto& y_acc = standalone->add_indirect_write_access(wblk, Y_OUTPUT_IDX);
8✔
383
    auto& wtk = builder.add_tasklet(wblk, data_flow::assign, "_out", {"_in"}, dbg);
8✔
384
    builder.add_computational_memlet(wblk, out_acc, wtk, "_in", {}, double_type, dbg);
8✔
385
    builder.add_computational_memlet(wblk, wtk, "_out", y_acc, y_subset, y_edge->base_type(), dbg);
8✔
386

387
    return standalone->successfully_expanded();
8✔
388
}
8✔
389

390
std::unique_ptr<data_flow::DataFlowNode> UpsampleBilinear2DNode::
391
    clone(size_t element_id, const graph::Vertex vertex, data_flow::DataFlowGraph& parent) const {
1✔
392
    return std::unique_ptr<data_flow::DataFlowNode>(new UpsampleBilinear2DNode(
1✔
393
        element_id,
1✔
394
        this->debug_info(),
1✔
395
        vertex,
1✔
396
        parent,
1✔
397
        input_shape_,
1✔
398
        output_shape_,
1✔
399
        align_corners_,
1✔
400
        scale_factors_,
1✔
401
        implementation_type_
1✔
402
    ));
1✔
403
}
1✔
404

NEW
405
symbolic::Expression UpsampleBilinear2DNode::flop() const {
×
406
    // N * C * Hout * Wout output elements, each requiring a fixed number of
407
    // multiply/add operations for the separable bilinear interpolation.
NEW
408
    auto output_elems = symbolic::
×
NEW
409
        mul(symbolic::mul(output_shape_[0], output_shape_[1]), symbolic::mul(output_shape_[2], output_shape_[3]));
×
NEW
410
    return symbolic::mul(output_elems, symbolic::integer(16));
×
NEW
411
}
×
412

NEW
413
data_flow::PointerAccessType UpsampleBilinear2DNode::pointer_access_type(int input_idx) const {
×
NEW
414
    if (input_idx == Y_OUTPUT_IDX) {
×
NEW
415
        return data_flow::PointerAccessMeta::create_full_write_only(symbolic::__nullptr__(), true);
×
NEW
416
    } else if (input_idx == X_INPUT_IDX) {
×
NEW
417
        return data_flow::PointerAccessMeta::create_read_only(symbolic::__nullptr__(), true);
×
NEW
418
    } else {
×
NEW
419
        return TensorNode::pointer_access_type(input_idx);
×
NEW
420
    }
×
NEW
421
}
×
422

423
std::string UpsampleBilinear2DNode::toStr() const {
1✔
424
    std::stringstream ss;
1✔
425
    ss << "UpsampleBilinear2D(align_corners=" << (align_corners_ ? "true" : "false");
1✔
426
    if (!scale_factors_.empty()) {
1✔
427
        ss << ", scale_factors=[" << scale_factors_[0] << ", " << scale_factors_[1] << "]";
1✔
428
    }
1✔
429
    ss << ")";
1✔
430
    return ss.str();
1✔
431
}
1✔
432

433
nlohmann::json UpsampleBilinear2DNodeSerializer::serialize(const data_flow::LibraryNode& library_node) {
2✔
434
    const UpsampleBilinear2DNode& node = static_cast<const UpsampleBilinear2DNode&>(library_node);
2✔
435
    nlohmann::json j;
2✔
436

437
    j["code"] = node.code().value();
2✔
438

439
    j["input_shape"] = nlohmann::json::array();
2✔
440
    for (auto& dim : node.input_shape()) {
8✔
441
        j["input_shape"].push_back(serializer::JSONSerializer::expression(dim));
8✔
442
    }
8✔
443
    j["output_shape"] = nlohmann::json::array();
2✔
444
    for (auto& dim : node.output_shape()) {
8✔
445
        j["output_shape"].push_back(serializer::JSONSerializer::expression(dim));
8✔
446
    }
8✔
447

448
    j["align_corners"] = node.align_corners();
2✔
449

450
    j["scale_factors"] = nlohmann::json::array();
2✔
451
    for (auto factor : node.scale_factors()) {
2✔
452
        j["scale_factors"].push_back(factor);
2✔
453
    }
2✔
454

455
    return j;
2✔
456
}
2✔
457

458
data_flow::LibraryNode& UpsampleBilinear2DNodeSerializer::deserialize(
459
    const nlohmann::json& j, builder::StructuredSDFGBuilder& builder, structured_control_flow::Block& parent
460
) {
2✔
461
    assert(j.contains("element_id"));
2✔
462
    assert(j.contains("code"));
2✔
463
    assert(j.contains("debug_info"));
2✔
464
    assert(j.contains("input_shape"));
2✔
465
    assert(j.contains("output_shape"));
2✔
466
    assert(j.contains("align_corners"));
2✔
467
    assert(j.contains("scale_factors"));
2✔
468

469
    std::vector<symbolic::Expression> input_shape;
2✔
470
    for (const auto& dim : j["input_shape"]) {
8✔
471
        input_shape.push_back(symbolic::parse(dim.get<std::string>()));
8✔
472
    }
8✔
473
    std::vector<symbolic::Expression> output_shape;
2✔
474
    for (const auto& dim : j["output_shape"]) {
8✔
475
        output_shape.push_back(symbolic::parse(dim.get<std::string>()));
8✔
476
    }
8✔
477

478
    bool align_corners = j["align_corners"].get<bool>();
2✔
479

480
    std::vector<double> scale_factors;
2✔
481
    for (const auto& factor : j["scale_factors"]) {
2✔
482
        scale_factors.push_back(factor.get<double>());
2✔
483
    }
2✔
484

485
    sdfg::serializer::JSONSerializer serializer;
2✔
486
    DebugInfo debug_info = serializer.json_to_debug_info(j["debug_info"]);
2✔
487

488
    return builder.add_library_node<
2✔
489
        UpsampleBilinear2DNode>(parent, debug_info, input_shape, output_shape, align_corners, scale_factors);
2✔
490
}
2✔
491

492
} // namespace tensor
493
} // namespace math
494
} // namespace sdfg
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc