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

daisytuner / sdfglib / 17698210449

13 Sep 2025 03:02PM UTC coverage: 60.505% (+1.2%) from 59.335%
17698210449

Pull #219

github

web-flow
Merge 7e6ec5a53 into 6c1992b40
Pull Request #219: stdlib Library Nodes and ConstantNodes

640 of 1885 new or added lines in 107 files covered. (33.95%)

107 existing lines in 40 files now uncovered.

9443 of 15607 relevant lines covered (60.5%)

106.96 hits per line

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

61.0
/src/data_flow/library_nodes/math/ml/conv.cpp
1
#include "sdfg/data_flow/library_nodes/math/ml/conv.h"
2

3
#include "sdfg/analysis/analysis.h"
4
#include "sdfg/builder/structured_sdfg_builder.h"
5

6
#include "sdfg/analysis/scope_analysis.h"
7

8
namespace sdfg {
9
namespace math {
10
namespace ml {
11

12
ConvNode::ConvNode(
2✔
13
    size_t element_id,
14
    const DebugInfo& debug_info,
15
    const graph::Vertex vertex,
16
    data_flow::DataFlowGraph& parent,
17
    const std::vector<symbolic::Expression> shape,
18
    bool has_bias,
19
    std::vector<size_t> dilations,
20
    std::vector<size_t> kernel_shape,
21
    std::vector<size_t> pads,
22
    std::vector<size_t> strides
23
)
24
    : MathNode(
2✔
25
          element_id,
2✔
26
          debug_info,
2✔
27
          vertex,
2✔
28
          parent,
2✔
29
          LibraryNodeType_Conv,
30
          {"Y"},
2✔
31
          {"X", "W"},
2✔
32
          data_flow::ImplementationType_NONE
33
      ),
34
      shape_(shape), has_bias_(has_bias), dilations_(dilations), kernel_shape_(kernel_shape), pads_(pads),
2✔
35
      strides_(strides) {
4✔
36
    if (has_bias_) {
2✔
37
        this->inputs_.push_back("B");
×
38
    }
×
39
}
2✔
40

NEW
41
const std::vector<symbolic::Expression>& ConvNode::shape() const { return shape_; }
×
42

NEW
43
symbolic::SymbolSet ConvNode::symbols() const {
×
NEW
44
    symbolic::SymbolSet syms;
×
NEW
45
    for (const auto& dim : shape_) {
×
NEW
46
        for (auto& atom : symbolic::atoms(dim)) {
×
NEW
47
            syms.insert(atom);
×
48
        }
49
    }
NEW
50
    return syms;
×
UNCOV
51
}
×
52

NEW
53
void ConvNode::replace(const symbolic::Expression old_expression, const symbolic::Expression new_expression) {
×
NEW
54
    for (auto& dim : shape_) {
×
NEW
55
        dim = symbolic::subs(dim, old_expression, new_expression);
×
56
    }
NEW
57
}
×
58

NEW
59
void ConvNode::validate(const Function& function) const {}
×
60

UNCOV
61
bool ConvNode::has_bias() const { return has_bias_; }
×
62

63
std::vector<size_t> ConvNode::dilations() const { return dilations_; }
×
64

65
std::vector<size_t> ConvNode::kernel_shape() const { return kernel_shape_; }
×
66

67
std::vector<size_t> ConvNode::pads() const { return pads_; }
×
68

69
std::vector<size_t> ConvNode::strides() const { return strides_; }
×
70

71
bool ConvNode::expand(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
2✔
72
    auto& sdfg = builder.subject();
2✔
73
    auto& dataflow = this->get_parent();
2✔
74
    auto& block = static_cast<structured_control_flow::Block&>(*dataflow.get_parent());
2✔
75

76
    auto& scope_analysis = analysis_manager.get<analysis::ScopeAnalysis>();
2✔
77
    auto& parent = static_cast<structured_control_flow::Sequence&>(*scope_analysis.parent_scope(&block));
2✔
78
    int index = parent.index(block);
2✔
79
    auto& transition = parent.at(index).second;
2✔
80

81
    const data_flow::Memlet* iedge_X = nullptr;
2✔
82
    const data_flow::Memlet* iedge_W = nullptr;
2✔
83
    const data_flow::Memlet* iedge_B = nullptr;
2✔
84
    for (const auto& iedge : dataflow.in_edges(*this)) {
6✔
85
        if (iedge.dst_conn() == "X") {
4✔
86
            iedge_X = &iedge;
2✔
87
        } else if (iedge.dst_conn() == "W") {
4✔
88
            iedge_W = &iedge;
2✔
89
        } else if (iedge.dst_conn() == "B") {
2✔
90
            iedge_B = &iedge;
×
91
        }
×
92
    }
93

94
    const data_flow::Memlet* oedge_Y = nullptr;
2✔
95
    for (const auto& oedge : dataflow.out_edges(*this)) {
2✔
96
        if (oedge.src_conn() == "Y") {
2✔
97
            oedge_Y = &oedge;
2✔
98
            break;
2✔
99
        }
100
    }
101

102
    // Find names of input and output containers
103
    std::string X_name = static_cast<const data_flow::AccessNode&>(iedge_X->src()).data();
2✔
104
    std::string W_name = static_cast<const data_flow::AccessNode&>(iedge_W->src()).data();
2✔
105
    std::string Y_name = static_cast<const data_flow::AccessNode&>(oedge_Y->dst()).data();
2✔
106

107
    auto& new_sequence = builder.add_sequence_before(parent, block, transition.assignments(), block.debug_info());
2✔
108
    structured_control_flow::Sequence* last_scope = &new_sequence;
2✔
109

110
    /************************
111
     * Parallel dimensions *
112
     ************************/
113
    // Generate one Map per parallel dimension of the output tensor (Y).
114
    data_flow::Subset out_subset;
2✔
115
    std::vector<symbolic::Expression> parallel_syms;
2✔
116
    structured_control_flow::Map* last_map = nullptr;
2✔
117
    for (size_t dim = 0; dim < this->shape_.size(); ++dim) {
10✔
118
        std::string indvar_str = builder.find_new_name("_i");
8✔
119
        builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::UInt64));
8✔
120

121
        auto indvar = symbolic::symbol(indvar_str);
8✔
122
        auto init = symbolic::zero();
8✔
123
        auto update = symbolic::add(indvar, symbolic::one());
8✔
124
        auto condition = symbolic::Lt(indvar, this->shape_[dim]);
8✔
125

126
        last_map = &builder.add_map(
16✔
127
            *last_scope,
8✔
128
            indvar,
8✔
129
            condition,
8✔
130
            init,
8✔
131
            update,
8✔
132
            structured_control_flow::ScheduleType_Sequential::create(),
8✔
133
            {},
8✔
134
            block.debug_info()
8✔
135
        );
136
        last_scope = &last_map->root();
8✔
137
        out_subset.push_back(indvar);
8✔
138
        parallel_syms.push_back(indvar);
8✔
139
    }
8✔
140

141
    /************************
142
     * Reduction dimensions *
143
     ************************/
144
    // For convolution, we reduce over input channels and kernel dimensions.
145
    // Assuming weight tensor layout (M, C, k1, k2, ...), skip the first dim (output channels).
146
    std::vector<symbolic::Expression> reduction_syms;
2✔
147
    structured_control_flow::For* last_for = nullptr;
2✔
148
    for (size_t dim = 0; dim < this->kernel_shape_.size(); ++dim) {
6✔
149
        std::string indvar_str = builder.find_new_name("_r");
4✔
150
        builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::UInt64));
4✔
151

152
        auto indvar = symbolic::symbol(indvar_str);
4✔
153
        auto init = symbolic::zero();
4✔
154
        auto update = symbolic::add(indvar, symbolic::one());
4✔
155
        auto condition = symbolic::Lt(indvar, symbolic::integer(this->kernel_shape_[dim]));
4✔
156

157
        last_for = &builder.add_for(*last_scope, indvar, condition, init, update, {}, block.debug_info());
4✔
158
        last_scope = &last_for->root();
4✔
159
        reduction_syms.push_back(indvar);
4✔
160
    }
4✔
161

162
    // Add innermost code block – convolution computation.
163
    auto& code_block = builder.add_block(*last_scope, {}, block.debug_info());
2✔
164

165
    // Reuse debug infos from original access nodes (if available).
166
    const DebugInfo& dbg_X = iedge_X->src().debug_info();
2✔
167
    const DebugInfo& dbg_W = iedge_W->src().debug_info();
2✔
168
    const DebugInfo& dbg_Y = oedge_Y->dst().debug_info();
2✔
169
    const DebugInfo dbg_B = (iedge_B != nullptr) ? iedge_B->src().debug_info() : DebugInfo();
2✔
170

171
    // Create new access nodes inside the innermost block.
172
    auto& X_acc = builder.add_access(code_block, X_name, dbg_X);
2✔
173
    auto& W_acc = builder.add_access(code_block, W_name, dbg_W);
2✔
174
    auto& Y_acc_in = builder.add_access(code_block, Y_name, dbg_Y);
2✔
175
    auto& Y_acc_out = builder.add_access(code_block, Y_name, dbg_Y);
2✔
176
    // Bias handled after reduction loops; no need to access B inside the reduction tasklet.
177

178
    /********************
179
     * Build subsets    *
180
     ********************/
181
    // Helper lambdas to safely fetch stride/dilation/pad values.
182
    auto int_expr = [](size_t v) { return symbolic::integer(static_cast<int64_t>(v)); };
14✔
183

184
    auto get_stride = [&](size_t idx) -> symbolic::Expression {
6✔
185
        if (idx < strides_.size()) {
4✔
186
            return int_expr(strides_[idx]);
4✔
187
        }
188
        return symbolic::one();
×
189
    };
4✔
190

191
    auto get_dilation = [&](size_t idx) -> symbolic::Expression {
6✔
192
        if (idx < dilations_.size()) {
4✔
193
            return int_expr(dilations_[idx]);
4✔
194
        }
195
        return symbolic::one();
×
196
    };
4✔
197

198
    auto get_pad = [&](size_t idx) -> symbolic::Expression {
6✔
199
        if (idx < pads_.size()) {
4✔
200
            return int_expr(pads_[idx]);
4✔
201
        }
202
        return symbolic::zero();
×
203
    };
4✔
204

205
    const size_t spatial_dims = kernel_shape_.size();
2✔
206

207
    // Extract commonly-used indices.
208
    auto get_parallel_sym = [&](size_t idx) -> symbolic::Expression {
10✔
209
        if (idx < parallel_syms.size()) return parallel_syms[idx];
8✔
210
        return symbolic::zero();
×
211
    };
8✔
212

213
    auto get_reduction_sym = [&](size_t idx) -> symbolic::Expression {
12✔
214
        if (idx < reduction_syms.size()) return reduction_syms[idx];
10✔
215
        return symbolic::zero();
4✔
216
    };
10✔
217

218
    auto N_idx = get_parallel_sym(0);
2✔
219
    auto M_idx = get_parallel_sym(1);
2✔
220

221
    // Input channel and kernel indices come from reduction variables.
222
    auto C_idx = get_reduction_sym(0);
2✔
223

224
    // Build X subset.
225
    data_flow::Subset subset_X;
2✔
226
    subset_X.push_back(N_idx); // Batch dim
2✔
227
    subset_X.push_back(C_idx); // Input channel dim
2✔
228
    for (size_t d = 0; d < spatial_dims; ++d) {
6✔
229
        symbolic::Expression out_d = get_parallel_sym(2 + d);
4✔
230
        symbolic::Expression ker_d = get_reduction_sym(1 + d);
4✔
231

232
        auto in_d = symbolic::
4✔
233
            sub(symbolic::add(symbolic::mul(out_d, get_stride(d)), symbolic::mul(ker_d, get_dilation(d))), get_pad(d));
4✔
234
        subset_X.push_back(in_d);
4✔
235
    }
4✔
236

237
    // Build W subset.
238
    data_flow::Subset subset_W;
2✔
239
    subset_W.push_back(M_idx); // Output channel (filter)
2✔
240
    subset_W.push_back(C_idx); // Input channel
2✔
241
    for (size_t d = 0; d < spatial_dims; ++d) {
6✔
242
        symbolic::Expression ker_d = get_reduction_sym(1 + d);
4✔
243
        subset_W.push_back(ker_d);
4✔
244
    }
4✔
245

246
    // Output Y subset is simply the parallel indices computed earlier.
247
    data_flow::Subset subset_Y = out_subset;
2✔
248

249
    // Bias subset (only along output channels).
250
    data_flow::Subset subset_B;
2✔
251
    if (has_bias_) {
2✔
252
        subset_B.push_back(M_idx);
×
253
    }
×
254

255
    /************************
256
     * Add computation node *
257
     ************************/
258
    // Create tasklet performing fused-multiply-add: _out = _x * _w + _y
259
    if (has_bias_) {
2✔
260
        // Bias will be added after reduction, so no change here.
261
    }
×
262

263
    auto& tasklet =
2✔
264
        builder.add_tasklet(code_block, data_flow::TaskletCode::fma, "_out", {"_x", "_w", "_y"}, block.debug_info());
2✔
265

266
    // Connect memlets.
267
    builder
4✔
268
        .add_computational_memlet(code_block, X_acc, tasklet, "_x", subset_X, iedge_X->base_type(), block.debug_info());
2✔
269
    builder
4✔
270
        .add_computational_memlet(code_block, W_acc, tasklet, "_w", subset_W, iedge_W->base_type(), block.debug_info());
2✔
271
    builder
4✔
272
        .add_computational_memlet(code_block, Y_acc_in, tasklet, "_y", subset_Y, oedge_Y->base_type(), block.debug_info());
2✔
273
    builder.add_computational_memlet(
4✔
274
        code_block, tasklet, "_out", Y_acc_out, subset_Y, oedge_Y->base_type(), block.debug_info()
2✔
275
    );
276

277
    // Bias: add once per output element outside reduction loops.
278
    if (has_bias_) {
2✔
279
        // Insert after the reduction loops (i.e., right after they finish).
280
        // We add a single tasklet in the parent scope (last_map root).
281
        std::string B_name = static_cast<const data_flow::AccessNode&>(iedge_B->src()).data();
×
282
        auto& bias_block = builder.add_block(last_map->root(), {}, block.debug_info());
×
283
        auto& B_acc_local = builder.add_access(bias_block, B_name, dbg_B);
×
284
        auto& Y_acc2_in = builder.add_access(bias_block, Y_name, dbg_Y);
×
285
        auto& Y_acc2_out = builder.add_access(bias_block, Y_name, dbg_Y);
×
286

287
        auto& bias_tasklet =
×
288
            builder.add_tasklet(bias_block, data_flow::TaskletCode::add, "_out", {"_bias", "_y"}, block.debug_info());
×
289
        builder.add_computational_memlet(
×
290
            bias_block, B_acc_local, bias_tasklet, "_bias", subset_B, iedge_B->base_type(), block.debug_info()
×
291
        );
292
        builder.add_computational_memlet(
×
293
            bias_block, Y_acc2_in, bias_tasklet, "_y", subset_Y, oedge_Y->base_type(), block.debug_info()
×
294
        );
295
        builder.add_computational_memlet(
×
296
            bias_block, bias_tasklet, "_out", Y_acc2_out, subset_Y, oedge_Y->base_type(), block.debug_info()
×
297
        );
298
    }
×
299

300
    // Clean up block
301
    builder.remove_memlet(block, *iedge_X);
2✔
302
    builder.remove_memlet(block, *iedge_W);
2✔
303
    if (iedge_B != nullptr) {
2✔
304
        builder.remove_memlet(block, *iedge_B);
×
305
    }
×
306
    builder.remove_memlet(block, *oedge_Y);
2✔
307
    builder.remove_node(block, *this);
2✔
308
    builder.remove_child(parent, index + 1);
2✔
309

310
    return true;
311
}
2✔
312

313
std::unique_ptr<data_flow::DataFlowNode> ConvNode::
314
    clone(size_t element_id, const graph::Vertex vertex, data_flow::DataFlowGraph& parent) const {
×
315
    return std::unique_ptr<data_flow::DataFlowNode>(new ConvNode(
×
316
        element_id,
×
317
        this->debug_info(),
×
318
        vertex,
×
319
        parent,
×
NEW
320
        this->shape_,
×
321
        this->has_bias_,
×
322
        this->dilations_,
×
323
        this->kernel_shape_,
×
324
        this->pads_,
×
325
        this->strides_
×
326
    ));
327
}
×
328

329
nlohmann::json ConvNodeSerializer::serialize(const data_flow::LibraryNode& library_node) {
×
NEW
330
    const ConvNode& node = static_cast<const ConvNode&>(library_node);
×
331
    nlohmann::json j;
×
332

NEW
333
    j["code"] = node.code().value();
×
NEW
334
    j["outputs"] = node.outputs();
×
NEW
335
    j["inputs"] = node.inputs();
×
NEW
336
    j["has_bias"] = node.has_bias();
×
NEW
337
    j["dilations"] = node.dilations();
×
NEW
338
    j["kernel_shape"] = node.kernel_shape();
×
NEW
339
    j["pads"] = node.pads();
×
NEW
340
    j["strides"] = node.strides();
×
341

NEW
342
    serializer::JSONSerializer serializer;
×
NEW
343
    j["shape"] = nlohmann::json::array();
×
NEW
344
    for (auto& dim : node.shape()) {
×
NEW
345
        j["shape"].push_back(serializer.expression(dim));
×
346
    }
347

348
    return j;
×
349
}
×
350

351
data_flow::LibraryNode& ConvNodeSerializer::deserialize(
×
352
    const nlohmann::json& j, builder::StructuredSDFGBuilder& builder, structured_control_flow::Block& parent
353
) {
354
    auto code = j["code"].get<std::string>();
×
355
    if (code != LibraryNodeType_Conv.value()) {
×
356
        throw std::runtime_error("Invalid library node code");
×
357
    }
358

359
    sdfg::serializer::JSONSerializer serializer;
×
360
    DebugInfo debug_info = serializer.json_to_debug_info(j["debug_info"]);
×
361

362
    auto outputs = j.at("outputs").get<std::vector<std::string>>();
×
363
    auto inputs = j.at("inputs").get<std::vector<std::string>>();
×
364
    auto has_bias = j.at("has_bias").get<bool>();
×
365
    auto dilations = j.at("dilations").get<std::vector<size_t>>();
×
366
    auto kernel_shape = j.at("kernel_shape").get<std::vector<size_t>>();
×
367
    auto pads = j.at("pads").get<std::vector<size_t>>();
×
368
    auto strides = j.at("strides").get<std::vector<size_t>>();
×
369

NEW
370
    std::vector<symbolic::Expression> shape;
×
NEW
371
    for (const auto& dim : j["shape"]) {
×
NEW
372
        shape.push_back(symbolic::parse(dim.get<std::string>()));
×
373
    }
374

NEW
375
    return builder
×
NEW
376
        .add_library_node<ConvNode>(parent, debug_info, shape, has_bias, dilations, kernel_shape, pads, strides);
×
UNCOV
377
}
×
378

379
} // namespace ml
380
} // namespace math
381
} // namespace sdfg
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc