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

daisytuner / sdfglib / 16779684622

06 Aug 2025 02:21PM UTC coverage: 64.3% (-1.0%) from 65.266%
16779684622

push

github

web-flow
Merge pull request #172 from daisytuner/opaque-pointers

Opaque pointers, typed memlets, untyped tasklet connectors

330 of 462 new or added lines in 38 files covered. (71.43%)

382 existing lines in 30 files now uncovered.

8865 of 13787 relevant lines covered (64.3%)

116.73 hits per line

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

66.23
/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
    bool has_bias,
18
    std::vector<size_t> dilations,
19
    std::vector<size_t> kernel_shape,
20
    std::vector<size_t> pads,
21
    std::vector<size_t> strides
22
)
23
    : MathNode(
2✔
24
          element_id,
2✔
25
          debug_info,
2✔
26
          vertex,
2✔
27
          parent,
2✔
28
          LibraryNodeType_Conv,
29
          {"Y"},
2✔
30
          {"X", "W"},
2✔
31
          data_flow::ImplementationType_NONE
32
      ),
33
      has_bias_(has_bias), dilations_(dilations), kernel_shape_(kernel_shape), pads_(pads), strides_(strides) {
2✔
34
    if (has_bias_) {
2✔
35
        this->inputs_.push_back("B");
×
36
    }
×
37
}
2✔
38

39
void ConvNode::validate(const Function& function) const {
×
40
    // TODO: Implement
41
}
×
42

43
bool ConvNode::has_bias() const { return has_bias_; }
×
44

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

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

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

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

53
bool ConvNode::expand(builder::StructuredSDFGBuilder& builder, analysis::AnalysisManager& analysis_manager) {
2✔
54
    auto& sdfg = builder.subject();
2✔
55
    auto& dataflow = this->get_parent();
2✔
56
    auto& block = static_cast<structured_control_flow::Block&>(*dataflow.get_parent());
2✔
57

58
    auto& scope_analyisis = analysis_manager.get<analysis::ScopeAnalysis>();
2✔
59
    auto& parent = static_cast<structured_control_flow::Sequence&>(*scope_analyisis.parent_scope(&block));
2✔
60

61
    const data_flow::Memlet* iedge_X = nullptr;
2✔
62
    const data_flow::Memlet* iedge_W = nullptr;
2✔
63
    const data_flow::Memlet* iedge_B = nullptr;
2✔
64
    for (const auto& iedge : dataflow.in_edges(*this)) {
6✔
65
        if (iedge.dst_conn() == "X") {
4✔
66
            iedge_X = &iedge;
2✔
67
        } else if (iedge.dst_conn() == "W") {
4✔
68
            iedge_W = &iedge;
2✔
69
        } else if (iedge.dst_conn() == "B") {
2✔
70
            iedge_B = &iedge;
×
71
        }
×
72
    }
73

74
    const data_flow::Memlet* oedge_Y = nullptr;
2✔
75
    for (const auto& oedge : dataflow.out_edges(*this)) {
2✔
76
        if (oedge.src_conn() == "Y") {
2✔
77
            oedge_Y = &oedge;
2✔
78
            break;
2✔
79
        }
80
    }
81

82
    // Find names of input and output containers
83
    std::string X_name = static_cast<const data_flow::AccessNode&>(iedge_X->src()).data();
2✔
84
    std::string W_name = static_cast<const data_flow::AccessNode&>(iedge_W->src()).data();
2✔
85
    std::string Y_name = static_cast<const data_flow::AccessNode&>(oedge_Y->dst()).data();
2✔
86

87
    data_flow::Subset dims_X = iedge_X->end_subset();
2✔
88
    data_flow::Subset dims_W = iedge_W->end_subset();
2✔
89
    data_flow::Subset dims_B;
2✔
90
    if (iedge_B != nullptr) {
2✔
91
        dims_B = iedge_B->end_subset();
×
92
    }
×
93
    data_flow::Subset dims_Y = oedge_Y->end_subset();
2✔
94

95
    auto& new_sequence = builder.add_sequence_before(parent, block, block.debug_info()).first;
2✔
96
    structured_control_flow::Sequence* last_scope = &new_sequence;
2✔
97

98
    /************************
99
     * Parallel dimensions *
100
     ************************/
101
    // Generate one Map per parallel dimension of the output tensor (Y).
102
    const auto& begin_Y_subset = oedge_Y->begin_subset();
2✔
103
    const auto& end_Y_subset = oedge_Y->end_subset();
2✔
104

105
    data_flow::Subset out_subset;
2✔
106
    std::vector<symbolic::Expression> parallel_syms;
2✔
107
    structured_control_flow::Map* last_map = nullptr;
2✔
108
    for (size_t dim = 0; dim < begin_Y_subset.size(); ++dim) {
10✔
109
        const auto& dim_begin = begin_Y_subset[dim];
8✔
110
        const auto& dim_end = end_Y_subset[dim];
8✔
111

112
        std::string indvar_str = builder.find_new_name("_i");
8✔
113
        builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::UInt64));
8✔
114

115
        auto indvar = symbolic::symbol(indvar_str);
8✔
116
        auto init = dim_begin;
8✔
117
        auto update = symbolic::add(indvar, symbolic::one());
8✔
118
        auto condition = symbolic::Lt(indvar, symbolic::add(dim_end, symbolic::one()));
8✔
119

120
        last_map = &builder.add_map(
8✔
121
            *last_scope,
8✔
122
            indvar,
123
            condition,
124
            init,
125
            update,
126
            structured_control_flow::ScheduleType_Sequential,
127
            {},
8✔
128
            block.debug_info()
8✔
129
        );
130
        last_scope = &last_map->root();
8✔
131
        out_subset.push_back(indvar);
8✔
132
        parallel_syms.push_back(indvar);
8✔
133
    }
8✔
134

135
    /************************
136
     * Reduction dimensions *
137
     ************************/
138
    // For convolution, we reduce over input channels and kernel dimensions.
139
    // Assuming weight tensor layout (M, C, k1, k2, ...), skip the first dim (output channels).
140
    const auto& begin_W_subset = iedge_W->begin_subset();
2✔
141
    const auto& end_W_subset = iedge_W->end_subset();
2✔
142

143
    std::vector<symbolic::Expression> reduction_syms;
2✔
144
    structured_control_flow::For* last_for = nullptr;
2✔
145
    for (size_t dim = 1; dim < begin_W_subset.size(); ++dim) {
8✔
146
        const auto& dim_begin = begin_W_subset[dim];
6✔
147
        const auto& dim_end = end_W_subset[dim];
6✔
148

149
        std::string indvar_str = builder.find_new_name("_r");
6✔
150
        builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::UInt64));
6✔
151

152
        auto indvar = symbolic::symbol(indvar_str);
6✔
153
        auto init = dim_begin;
6✔
154
        auto update = symbolic::add(indvar, symbolic::one());
6✔
155
        auto condition = symbolic::Lt(indvar, symbolic::add(dim_end, symbolic::one()));
6✔
156

157
        last_for = &builder.add_for(*last_scope, indvar, condition, init, update, {}, block.debug_info());
6✔
158
        last_scope = &last_for->root();
6✔
159
        reduction_syms.push_back(indvar);
6✔
160
    }
6✔
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();
×
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

NEW
287
        auto& bias_tasklet =
×
NEW
288
            builder.add_tasklet(bias_block, data_flow::TaskletCode::add, "_out", {"_bias", "_y"}, block.debug_info());
×
NEW
289
        builder.add_computational_memlet(
×
NEW
290
            bias_block, B_acc_local, bias_tasklet, "_bias", subset_B, iedge_B->base_type(), block.debug_info()
×
291
        );
NEW
292
        builder.add_computational_memlet(
×
NEW
293
            bias_block, Y_acc2_in, bias_tasklet, "_y", subset_Y, oedge_Y->base_type(), block.debug_info()
×
294
        );
NEW
295
        builder.add_computational_memlet(
×
NEW
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, block);
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,
×
320
        this->has_bias_,
×
321
        this->dilations_,
×
322
        this->kernel_shape_,
×
323
        this->pads_,
×
324
        this->strides_
×
325
    ));
326
}
×
327

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

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

341
    return j;
×
342
}
×
343

344
data_flow::LibraryNode& ConvNodeSerializer::deserialize(
×
345
    const nlohmann::json& j, builder::StructuredSDFGBuilder& builder, structured_control_flow::Block& parent
346
) {
347
    auto code = j["code"].get<std::string>();
×
348
    if (code != LibraryNodeType_Conv.value()) {
×
349
        throw std::runtime_error("Invalid library node code");
×
350
    }
351

352
    sdfg::serializer::JSONSerializer serializer;
×
353
    DebugInfo debug_info = serializer.json_to_debug_info(j["debug_info"]);
×
354

355
    auto outputs = j.at("outputs").get<std::vector<std::string>>();
×
356
    auto inputs = j.at("inputs").get<std::vector<std::string>>();
×
357
    auto has_bias = j.at("has_bias").get<bool>();
×
358
    auto dilations = j.at("dilations").get<std::vector<size_t>>();
×
359
    auto kernel_shape = j.at("kernel_shape").get<std::vector<size_t>>();
×
360
    auto pads = j.at("pads").get<std::vector<size_t>>();
×
361
    auto strides = j.at("strides").get<std::vector<size_t>>();
×
362

363
    return builder.add_library_node<ConvNode>(parent, debug_info, has_bias, dilations, kernel_shape, pads, strides);
×
364
}
×
365

366
} // namespace ml
367
} // namespace math
368
} // 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