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

daisytuner / docc / 28806128926

06 Jul 2026 04:16PM UTC coverage: 62.96%. First build
28806128926

push

github

web-flow
New LibNodeExpansion pass (#740)

* Switched expansion "pipeline" to new LibNodeExpansionPass that can recursively expand using a LibNodeExpander impl.
- removed access to analysis_manager from expansion methods, as those would currently not be appropriately maintained between nodes
+ New expansion API handles more of the boilerplate code (checking for standalone, creating boundary access nodes, removing old elements)
* Also migrated sdfg-json-to-c.cpp to not using the expansion pipeline anymore
* toStr() for ReduceNodes and giving PyStructuredSDFG access to the output_dir for additional dumping
~ DotVisualizer : Fix on nested sequences.
+ DotVisualizer: visualize empty sequences
* DotVisualizer default-enabled show block/loop ids
 * while MathNodes still have an expand-method, the new infrastructure is based upon "Expander" classes. The MathNodeExpander just redirects to the method for now
 ~ Broadcast node still used old ptr-output semantics
 * updated tensor & blas node expand to new expand API, that handles more of the boilerplate code (checking, removing old nodes, creating standalone-replacement nodes)
 - removed Transpose Node. Was unused and on old ptr-semantics
 + StructuredSDFGBuilder.add_sequence_at, add_for_at, add_map_at
 * switched StructuredSDFGBuilder internally to use ptr of Assignments to express using default assignments when needed
 * updated tests to use the new expand_single_math_node helper function, instead of the method directly
 + pass and expand-single helper functions are ready to use other expanders
 + EinsumExpansionPass is basically the legacy ExpansionPass, only restricted to EinsumNodes. It still needs to run in a pipeline to ensure finding multiple EinsumNodes per block. This is temporary, because Einsum is the only node that already supported splitting a block, which the new expansion disallows, as it should handle it itself when needed (but that part is not yet implemented)

594 of 962 new or added lines in 31 files covered. (61.75%)

40554 of 64412 relevant lines covered (62.96%)

963.11 hits per line

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

45.32
/sdfg/src/data_flow/library_nodes/math/tensor/matmul_node.cpp
1
#include "sdfg/data_flow/library_nodes/math/tensor/matmul_node.h"
2
#include <cstddef>
3
#include <string>
4

5
#include "sdfg/builder/structured_sdfg_builder.h"
6
#include "sdfg/data_flow/library_nodes/math/blas/blas_node.h"
7
#include "sdfg/data_flow/library_nodes/math/blas/gemm_node.h"
8
#include "sdfg/data_flow/library_nodes/stdlib/free.h"
9
#include "sdfg/data_flow/tasklet.h"
10
#include "sdfg/element.h"
11
#include "sdfg/exceptions.h"
12
#include "sdfg/structured_control_flow/control_flow_node.h"
13
#include "sdfg/structured_control_flow/map.h"
14
#include "sdfg/structured_control_flow/sequence.h"
15
#include "sdfg/symbolic/symbolic.h"
16
#include "sdfg/types/pointer.h"
17
#include "sdfg/types/scalar.h"
18
#include "sdfg/types/tensor.h"
19
#include "sdfg/types/type.h"
20
#include "sdfg/types/utils.h"
21

22
namespace sdfg {
23
namespace math {
24
namespace tensor {
25

26
bool MatMulNode::has_basic_strides(symbolic::MultiExpression shape, symbolic::MultiExpression strides) {
×
27
    auto basic_strides = types::Tensor::strides_from_shape(shape);
×
28
    if (basic_strides.size() != strides.size()) {
×
29
        return false;
×
30
    }
×
31
    for (size_t i = 0; i < strides.size(); i++) {
×
32
        if (!symbolic::eq(basic_strides[i], strides[i])) {
×
33
            return false;
×
34
        }
×
35
    }
×
36
    return true;
×
37
}
×
38

39
bool MatMulNode::has_transposed_strides(symbolic::MultiExpression shape, symbolic::MultiExpression strides) {
×
40
    if (shape.size() < 2) {
×
41
        return false;
×
42
    }
×
43
    symbolic::MultiExpression new_shape;
×
44
    new_shape.reserve(shape.size());
×
45
    for (size_t i = 0; i < shape.size() - 2; i++) {
×
46
        new_shape.push_back(shape[i]);
×
47
    }
×
48
    new_shape.push_back(shape[shape.size() - 1]);
×
49
    new_shape.push_back(shape[shape.size() - 2]);
×
50
    symbolic::MultiExpression transposed_strides(strides);
×
51
    transposed_strides[strides.size() - 2] = strides[strides.size() - 1];
×
52
    transposed_strides[strides.size() - 1] = strides[strides.size() - 2];
×
53
    return MatMulNode::has_basic_strides(new_shape, transposed_strides);
×
54
}
×
55

56
MatMulNode::MatMulNode(
57
    size_t element_id,
58
    const DebugInfo& debug_info,
59
    const graph::Vertex vertex,
60
    data_flow::DataFlowGraph& parent,
61
    const TensorLayout& layout_a,
62
    const TensorLayout& layout_b,
63
    QuantizationType quantization,
64
    const data_flow::ImplementationType& impl_type
65
)
66
    : TensorNode(element_id, debug_info, vertex, parent, LibraryNodeType_MatMul, {}, {"Y", "A", "B"}, impl_type),
5✔
67
      fixed_quantization_(quantization), layout_a_(layout_a), layout_b_(layout_b) {
5✔
68
    if (layout_a.dims() < 2) {
5✔
69
        throw std::invalid_argument("MatMulNode: Input A must have at least 2 dimensions");
×
70
    }
×
71
    if (layout_b.dims() < 2) {
5✔
72
        throw std::invalid_argument("MatMulNode: Input B must have at least 2 dimensions");
×
73
    }
×
74
}
5✔
75

76
symbolic::Expression MatMulNode::m() const {
8✔
77
    // M is the second-to-last dimension of A
78
    return layout_a_.get_dim_innermost(1);
8✔
79
}
8✔
80

81
symbolic::Expression MatMulNode::n() const {
12✔
82
    // N is the last dimension of B
83
    return layout_b_.get_dim_innermost(0);
12✔
84
}
12✔
85

86
symbolic::Expression MatMulNode::k() const {
7✔
87
    // K is the last dimension of A (and second-to-last of B)
88
    return layout_a_.get_dim_innermost(0);
7✔
89
}
7✔
90

91
const TensorLayout& MatMulNode::layout_a() const { return layout_a_; }
×
92

93
const TensorLayout& MatMulNode::layout_b() const { return layout_b_; }
×
94

95
void MatMulNode::validate(const Function& function) const {
5✔
96
    TensorNode::validate(function);
5✔
97

98
    auto& graph = this->get_parent();
5✔
99

100
    // Check that we have exactly 2 inputs and 1 output
101
    if (graph.in_degree(*this) != 3) {
5✔
102
        throw InvalidSDFGException("MatMulNode: Expected exactly 3 inputs (Y, A, B)");
×
103
    }
×
104
    if (graph.out_degree(*this) != 0) {
5✔
105
        throw InvalidSDFGException("MatMulNode: Expected no outputs");
×
106
    }
×
107

108
    // Validate K dimension matches between A and B
109
    auto k_a = layout_a_.get_dim_innermost(0);
5✔
110
    auto k_b = layout_b_.get_dim_innermost(1);
5✔
111
    if (!symbolic::eq(k_a, k_b)) {
5✔
112
        throw InvalidSDFGException(
×
113
            "MatMulNode: K dimension mismatch. A has K=" + k_a->__str__() + ", B has K=" + k_b->__str__()
×
114
        );
×
115
    }
×
116
}
5✔
117

118
symbolic::SymbolSet MatMulNode::symbols() const {
1✔
119
    symbolic::SymbolSet syms;
1✔
120
    layout_a_.collect_symbols(syms);
1✔
121
    layout_b_.collect_symbols(syms);
1✔
122
    return syms;
1✔
123
}
1✔
124

125
void MatMulNode::replace(const symbolic::Expression old_expression, const symbolic::Expression new_expression) {
×
126
    layout_a_.replace_symbols(old_expression, new_expression);
×
127
    layout_b_.replace_symbols(old_expression, new_expression);
×
128
}
×
129

130
void MatMulNode::replace(const symbolic::ExpressionMapping& replacements) {
×
131
    layout_a_.replace_symbols(replacements);
×
132
    layout_b_.replace_symbols(replacements);
×
133
}
×
134

135
std::unique_ptr<data_flow::DataFlowNode> MatMulNode::
136
    clone(size_t element_id, const graph::Vertex vertex, data_flow::DataFlowGraph& parent) const {
×
137
    return std::unique_ptr<data_flow::DataFlowNode>(new MatMulNode(
×
138
        element_id, debug_info(), vertex, parent, layout_a_, layout_b_, fixed_quantization_, implementation_type_
×
139
    ));
×
140
}
×
141

142
types::PrimitiveType MatMulNode::fixed_quantization() const { return fixed_quantization_; }
×
143

144
void MatMulNode::set_fixed_quantization(const QuantizationType quant) { fixed_quantization_ = quant; }
×
145

146
types::PrimitiveType MatMulNode::quantization(const data_flow::DataFlowGraph& data_flow_graph) const {
×
147
    if (fixed_quantization_ != QUANTIZATION_MATCH_INPUTS) {
×
148
        return fixed_quantization_;
×
149
    } else {
×
150
        return this->primitive_type(data_flow_graph);
×
151
    }
×
152
}
×
153

154
std::optional<types::PrimitiveType> MatMulNode::uniform_quantization(const data_flow::DataFlowGraph& data_flow_graph
155
) const {
5✔
156
    if (fixed_quantization_ != QUANTIZATION_MATCH_INPUTS) {
5✔
157
        auto inferred = this->primitive_type(data_flow_graph);
×
158
        if (inferred == fixed_quantization_) {
×
159
            return fixed_quantization_;
×
160
        } else {
×
161
            return std::nullopt;
×
162
        }
×
163
    } else {
5✔
164
        return this->primitive_type(data_flow_graph);
5✔
165
    }
5✔
166
}
5✔
167

168
std::string MatMulNode::toStr() const {
×
169
    std::stringstream ss;
×
170
    ss << "MatMul(";
×
171
    ss << types::primitive_type_to_string(fixed_quantization_) << ", ";
×
172
    ss << "A: " << layout_a_;
×
173
    ss << ", B: " << layout_b_;
×
174
    ss << ")";
×
175
    return ss.str();
×
176
}
×
177

178
symbolic::Expression MatMulNode::flop() const {
×
179
    auto res_elems = symbolic::mul(this->m(), this->n());
×
180
    auto k = this->k();
×
181

182
    auto mm_mul_ops = symbolic::mul(res_elems, k);
×
183
    auto mm_sum_ops = symbolic::mul(res_elems, symbolic::sub(k, symbolic::one()));
×
184

185
    auto mul_ops = mm_mul_ops;
×
186
    auto add_ops = mm_sum_ops;
×
187
    auto per_mat = symbolic::add(mul_ops, add_ops);
×
188
    int a_dims = layout_a_.dims();
×
189
    int b_dims = layout_b_.dims();
×
190
    if (a_dims > 2 || b_dims > 2) {
×
191
        std::vector<symbolic::Expression> factors{per_mat};
×
192
        auto max_dims = std::max(a_dims, b_dims);
×
193
        for (int i = 2; i < max_dims; ++i) {
×
194
            symbolic::Expression dim_a, dim_b;
×
195
            if (i < a_dims) {
×
196
                dim_a = layout_a_.get_dim_innermost(i);
×
197
            }
×
198
            if (i < b_dims) {
×
199
                dim_b = layout_b_.get_dim_innermost(i);
×
200
            }
×
201
            if (dim_a.is_null() & !dim_b.is_null()) {
×
202
                factors.push_back(dim_b);
×
203
            } else if (!dim_a.is_null() & dim_b.is_null()) {
×
204
                factors.push_back(dim_a);
×
205
            } else if (!dim_a.is_null() & !dim_b.is_null()) {
×
206
                if (!symbolic::eq(dim_a, dim_b)) {
×
207
                    throw InvalidSDFGException(
×
208
                        "Batch dimension " + std::to_string(i) + " mismatch between A and B. A has " +
×
209
                        dim_a->__str__() + ", B has " + dim_b->__str__()
×
210
                    );
×
211
                } else {
×
212
                    factors.push_back(dim_a);
×
213
                }
×
214
            } else {
×
215
                return SymEngine::null;
×
216
            }
×
217
        }
×
218
        return SymEngine::mul(factors);
×
219
    } else {
×
220
        return per_mat;
×
221
    }
×
222
}
×
223

224
void free_after_copy(
225
    const std::string& copy_name, builder::StructuredSDFGBuilder& builder, structured_control_flow::Sequence& parent
226
) {
×
227
    auto& block = builder.add_block(parent, {}, DebugInfo());
×
228
    auto& access_in = builder.add_access(block, copy_name);
×
229
    auto& free_node = builder.add_library_node<stdlib::FreeNode>(block, DebugInfo());
×
230
    builder.add_computational_memlet(
×
231
        block, access_in, free_node, "_ptr", {}, types::Pointer(types::Scalar(types::PrimitiveType::Void))
×
232
    );
×
233
}
×
234

235
using Dir = passes::LibNodeExpander::InputUse;
236

237
passes::LibNodeExpander::ExpandOutcome MatMulNode::expand(passes::LibNodeExpander::ExpandContext& context, Block& block) {
5✔
238
    auto& dataflow = this->get_parent();
5✔
239

240
    if (dataflow.in_degree(*this) != 3 || dataflow.out_degree(*this) != 0) {
5✔
NEW
241
        return context.unable();
×
242
    }
×
243

244
    auto& parent = static_cast<structured_control_flow::Sequence&>(*block.get_parent());
5✔
245
    int index = parent.index(block);
5✔
246

247
    // Determine BLAS precision from primitive type
248
    auto prim_type = this->uniform_quantization(dataflow);
5✔
249
    if (!prim_type) {
5✔
NEW
250
        return context.unable();
×
251
    }
×
252
    blas::BLAS_Precision precision;
5✔
253
    switch (prim_type.value()) {
5✔
254
        case types::PrimitiveType::Half:
×
255
            precision = blas::BLAS_Precision::h;
×
256
            break;
×
257
        case types::PrimitiveType::Float:
3✔
258
            precision = blas::BLAS_Precision::s;
3✔
259
            break;
3✔
260
        case types::PrimitiveType::Double:
1✔
261
            precision = blas::BLAS_Precision::d;
1✔
262
            break;
1✔
263
        default:
1✔
264
            // GEMM only supports floating point types, fall back to naive expansion
265
            return context.unable();
1✔
266
    };
5✔
267

268
    auto standalone =
4✔
269
        context.replacement_requires_access_nodes({Dir::IndirectReadWrite, Dir::IndirectRead, Dir::IndirectRead});
4✔
270

271
    if (standalone) {
4✔
272
        auto& builder = standalone->builder();
4✔
273

274
        // Add new graph after the current block
275
        auto& new_sequence = standalone->replace_with_sequence();
4✔
276

277
        // Check if A and B have basic strides and whether they are transposed in the last dimension
278
        blas::BLAS_Transpose trans_a, trans_b;
4✔
279
        if (layout_a_.has_linear_accesses_no_padding()) {
4✔
280
            trans_a = blas::BLAS_Transpose::No;
4✔
281
        } else if (layout_a_.has_transposed_strides_no_padding()) {
4✔
NEW
282
            trans_a = blas::BLAS_Transpose::Trans;
×
283
        } else {
×
NEW
284
            trans_a = blas::BLAS_Transpose::No;
×
NEW
285
            throw InvalidSDFGException("A must be in c-order");
×
NEW
286
        }
×
287
        if (layout_b_.has_linear_accesses_no_padding()) {
4✔
288
            trans_b = blas::BLAS_Transpose::No;
4✔
289
        } else if (layout_b_.has_transposed_strides_no_padding()) {
4✔
NEW
290
            trans_b = blas::BLAS_Transpose::Trans;
×
NEW
291
        } else {
×
NEW
292
            trans_b = blas::BLAS_Transpose::No;
×
NEW
293
            throw InvalidSDFGException("B must be in c-order");
×
294
        }
×
295

296
        // Create maps for batch dimensions and M, N dimensions
297
        structured_control_flow::Sequence* last_scope = &new_sequence;
4✔
298
        structured_control_flow::Map* last_map = nullptr;
4✔
299
        symbolic::MultiExpression batch_vars;
4✔
300

301
        // Compute batch dimensions (all except last 2)
302
        size_t batch_dims_a = layout_a_.dims() - 2;
4✔
303
        size_t batch_dims_b = layout_b_.dims() - 2;
4✔
304
        size_t max_batch_dims = std::max(batch_dims_a, batch_dims_b);
4✔
305

306
        // Create maps for batch dimensions (using broadcasting)
307
        for (size_t i = 0; i < max_batch_dims; ++i) {
5✔
308
            std::string indvar_str = builder.find_new_name("_b");
1✔
309
            builder.add_container(indvar_str, types::Scalar(types::PrimitiveType::UInt64));
1✔
310

311
            auto indvar = symbolic::symbol(indvar_str);
1✔
312
            auto init = symbolic::zero();
1✔
313
            auto update = symbolic::add(indvar, symbolic::one());
1✔
314

315
            // Determine the bound for this batch dimension (max of A and B for broadcasting)
316
            symbolic::Expression bound;
1✔
317
            size_t a_idx = batch_dims_a >= (max_batch_dims - i) ? i - (max_batch_dims - batch_dims_a) : SIZE_MAX;
1✔
318
            size_t b_idx = batch_dims_b >= (max_batch_dims - i) ? i - (max_batch_dims - batch_dims_b) : SIZE_MAX;
1✔
319

320
            if (a_idx != SIZE_MAX && b_idx != SIZE_MAX) {
1✔
321
                // Both have this dimension - they should be equal or one should be 1 (broadcasting)
322
                bound = layout_a_.get_dim(a_idx); // Assume they match or broadcasting is handled
1✔
323
            } else if (a_idx != SIZE_MAX) {
1✔
NEW
324
                bound = layout_a_.get_dim(a_idx);
×
NEW
325
            } else {
×
NEW
326
                bound = layout_b_.get_dim(b_idx);
×
NEW
327
            }
×
328

329
            auto condition = symbolic::Lt(indvar, bound);
1✔
330
            last_map = &builder.add_map(
1✔
331
                *last_scope,
1✔
332
                indvar,
1✔
333
                condition,
1✔
334
                init,
1✔
335
                update,
1✔
336
                structured_control_flow::ScheduleType_Sequential::create(),
1✔
337
                {},
1✔
338
                block.debug_info()
1✔
339
            );
1✔
340
            last_scope = &last_map->root();
1✔
341
            batch_vars.push_back(indvar);
1✔
342
        }
1✔
343

344
        auto& ref_block = builder.add_block(*last_scope, {}, block.debug_info());
4✔
345

346
        auto scalar_type = types::Scalar(prim_type.value());
4✔
347

348
        // Compute offsets for this batch iteration
349
        // For A: base_offset_a = offset_a + sum_i(batch_idx_i * batch_stride_a_i)
350
        symbolic::Expression a_batch_offset = layout_a_.offset();
4✔
351
        for (size_t i = 0; i < batch_dims_a; ++i) {
5✔
352
            size_t batch_idx = max_batch_dims - batch_dims_a + i;
1✔
353
            a_batch_offset =
1✔
354
                symbolic::add(a_batch_offset, symbolic::mul(batch_vars[batch_idx], layout_a_.get_stride(i)));
1✔
355
        }
1✔
356

357
        // For B: base_offset_b = offset_b + sum_i(batch_idx_i * batch_stride_b_i)
358
        symbolic::Expression b_batch_offset = layout_b_.offset();
4✔
359
        for (size_t i = 0; i < batch_dims_b; ++i) {
5✔
360
            size_t batch_idx = max_batch_dims - batch_dims_b + i;
1✔
361
            b_batch_offset =
1✔
362
                symbolic::add(b_batch_offset, symbolic::mul(batch_vars[batch_idx], layout_b_.get_stride(i)));
1✔
363
        }
1✔
364

365
        // Compute output batch offset (same as batch_vars pattern for Y)
366
        symbolic::Expression c_batch_offset = symbolic::integer(0);
4✔
367
        for (size_t i = 0; i < batch_vars.size(); ++i) {
5✔
368
            // Output has shape [batch..., M, N] with row-major strides
369
            // Stride for batch dim i is: M * N * product of remaining batch dims
370
            symbolic::Expression c_stride = symbolic::mul(this->m(), this->n());
1✔
371
            for (size_t j = i + 1; j < batch_vars.size(); ++j) {
1✔
372
                // Multiply by subsequent batch dimensions
NEW
373
                if (j < batch_dims_a) {
×
NEW
374
                    c_stride = symbolic::mul(c_stride, layout_a_.get_dim(j));
×
NEW
375
                } else if (j - batch_dims_a < batch_dims_b) {
×
NEW
376
                    c_stride = symbolic::mul(c_stride, layout_b_.get_dim(j - batch_dims_a));
×
NEW
377
                }
×
NEW
378
            }
×
379
            c_batch_offset = symbolic::add(c_batch_offset, symbolic::mul(batch_vars[i], c_stride));
1✔
380
        }
1✔
381

382
        // Create input access nodes
383
        auto& a_access = standalone->add_scalar_input_access(ref_block, A_INPUT_IDX);
4✔
384
        auto& b_access = standalone->add_scalar_input_access(ref_block, B_INPUT_IDX);
4✔
385
        auto& c_access_in = standalone->add_indirect_read_access(ref_block, Y_INPUT_IDX);
4✔
386

387
        auto copy_name_a = a_access.data();
4✔
388
        auto copy_name_b = b_access.data();
4✔
389
        auto output_name = c_access_in.data();
4✔
390

391
        std::string ref_name_a = builder.find_new_name(copy_name_a + "_ref");
4✔
392
        builder.add_container(ref_name_a, types::Pointer(types::Scalar(types::PrimitiveType::Void)));
4✔
393
        auto& a_access_ref = builder.add_access(ref_block, ref_name_a, debug_info());
4✔
394
        std::string ref_name_b = builder.find_new_name(copy_name_b + "_ref");
4✔
395
        builder.add_container(ref_name_b, types::Pointer(types::Scalar(types::PrimitiveType::Void)));
4✔
396
        auto& b_access_ref = builder.add_access(ref_block, ref_name_b, debug_info());
4✔
397
        std::string ref_name_c = builder.find_new_name(output_name + "_ref");
4✔
398
        builder.add_container(ref_name_c, types::Pointer(types::Scalar(types::PrimitiveType::Void)));
4✔
399
        auto& c_access_ref_in = builder.add_access(ref_block, ref_name_c, debug_info());
4✔
400

401
        builder.add_reference_memlet(
4✔
402
            ref_block, a_access, a_access_ref, {a_batch_offset}, ::sdfg::types::Pointer(scalar_type), debug_info()
4✔
403
        );
4✔
404
        builder.add_reference_memlet(
4✔
405
            ref_block, b_access, b_access_ref, {b_batch_offset}, ::sdfg::types::Pointer(scalar_type), debug_info()
4✔
406
        );
4✔
407
        builder.add_reference_memlet(
4✔
408
            ref_block, c_access_in, c_access_ref_in, {c_batch_offset}, ::sdfg::types::Pointer(scalar_type), debug_info()
4✔
409
        );
4✔
410

411
        // Create block with GEMM library node
412
        auto& gemm_block = builder.add_block(*last_scope, {}, block.debug_info());
4✔
413

414
        // Leading dimensions: stride of the row dimension (second-to-last dim)
415
        symbolic::Expression lda, ldb;
4✔
416
        if (trans_a == blas::BLAS_Transpose::No) {
4✔
417
            // For row-major A [m * k] -> lda = k
418
            lda = layout_a_.get_stride_innermost(1);
4✔
419
        } else {
4✔
420
            // For row-major A [m * k] -> lda = m
NEW
421
            lda = layout_a_.get_stride_innermost(0);
×
NEW
422
        }
×
423
        if (trans_b == blas::BLAS_Transpose::No) {
4✔
424
            // For row-major B [k * n] -> ldb = n
425
            ldb = layout_b_.get_stride_innermost(1);
4✔
426
        } else {
4✔
427
            // For row-major B [k * n] -> ldb = k
NEW
428
            ldb = layout_b_.get_stride_innermost(0);
×
NEW
429
        }
×
430
        // For row-major C [m * n] -> ldc = n
431
        auto ldc = this->n();
4✔
432

433
        // Add GEMM node: C = alpha * A * B + beta * C
434
        // With alpha = 1.0, beta = 0.0: C = A * B
435
        auto& gemm_node = builder.add_library_node<blas::GEMMNode>(
4✔
436
            gemm_block,
4✔
437
            debug_info(),
4✔
438
            blas::ImplementationType_BLAS,
4✔
439
            precision,
4✔
440
            blas::BLAS_Layout::RowMajor,
4✔
441
            trans_a,
4✔
442
            trans_b,
4✔
443
            this->m(),
4✔
444
            this->n(),
4✔
445
            this->k(),
4✔
446
            lda,
4✔
447
            ldb,
4✔
448
            ldc
4✔
449
        );
4✔
450

451
        auto& a_access_ref_in_gemm = builder.add_access(gemm_block, ref_name_a, debug_info());
4✔
452
        auto& b_access_ref_in_gemm = builder.add_access(gemm_block, ref_name_b, debug_info());
4✔
453
        auto& c_access_ref_in_gemm = builder.add_access(gemm_block, ref_name_c, debug_info());
4✔
454

455
        // Create alpha and beta constants
456
        auto& alpha_const = builder.add_constant(gemm_block, "1.0", scalar_type, debug_info());
4✔
457
        auto& beta_const = builder.add_constant(gemm_block, "0.0", scalar_type, debug_info());
4✔
458

459
        // Connect memlets with batch offsets
460
        // Input A with offset
461
        builder.add_computational_memlet(
4✔
462
            gemm_block, a_access_ref_in_gemm, gemm_node, "__A", {}, ::sdfg::types::Pointer(scalar_type), debug_info()
4✔
463
        );
4✔
464
        // Input B with offset
465
        builder.add_computational_memlet(
4✔
466
            gemm_block, b_access_ref_in_gemm, gemm_node, "__B", {}, ::sdfg::types::Pointer(scalar_type), debug_info()
4✔
467
        );
4✔
468
        // Input C (for beta * C, but beta=0 so just needs to be connected)
469
        builder.add_computational_memlet(
4✔
470
            gemm_block, c_access_ref_in_gemm, gemm_node, "__C", {}, ::sdfg::types::Pointer(scalar_type), debug_info()
4✔
471
        );
4✔
472
        // Alpha constant
473
        builder.add_computational_memlet(gemm_block, alpha_const, gemm_node, "__alpha", {}, scalar_type, debug_info());
4✔
474
        // Beta constant
475
        builder.add_computational_memlet(gemm_block, beta_const, gemm_node, "__beta", {}, scalar_type, debug_info());
4✔
476

477
        return standalone->successfully_expanded();
4✔
478
    } else {
4✔
479
        // lib node was not "standalone" in its block (all inputs and outputs come from access nodes solely used with
480
        // this libnode) or could not be transformed into a form that can be used as if
NEW
481
        return context.unable();
×
NEW
482
    }
×
483
}
4✔
484

485
nlohmann::json MatMulNodeSerializer::serialize(const data_flow::LibraryNode& library_node) {
×
486
    const MatMulNode& matmul_node = static_cast<const MatMulNode&>(library_node);
×
487
    nlohmann::json j;
×
488

489
    j["code"] = matmul_node.code().value();
×
490

491
    serializer::JSONSerializer serializer;
×
492

493
    matmul_node.layout_a().serialize_to_json(j["layout_a"]);
×
494
    matmul_node.layout_b().serialize_to_json(j["layout_b"]);
×
495

496
    j["result_quant"] = matmul_node.fixed_quantization();
×
497

498
    return j;
×
499
}
×
500

501
data_flow::LibraryNode& MatMulNodeSerializer::deserialize(
502
    const nlohmann::json& j, builder::StructuredSDFGBuilder& builder, structured_control_flow::Block& parent
503
) {
×
504
    assert(j.contains("element_id"));
×
505
    assert(j.contains("code"));
×
506
    assert(j.contains("debug_info"));
×
507

508
    std::optional<TensorLayout> layout_a;
×
509
    std::optional<TensorLayout> layout_b;
×
510

511
    auto layout_a_it = j.find("layout_a");
×
512
    if (layout_a_it != j.end()) {
×
513
        layout_a = TensorLayout::deserialize_from_json(*layout_a_it);
×
514
        layout_b = TensorLayout::deserialize_from_json(j.at("layout_b"));
×
515

516
    } else {
×
517
        assert(j.contains("shape_a"));
×
518
        assert(j.contains("shape_b"));
×
519

520
        symbolic::MultiExpression shape_a;
×
521
        for (const auto& dim : j["shape_a"]) {
×
522
            shape_a.push_back(symbolic::parse(dim.get<std::string>()));
×
523
        }
×
524

525
        symbolic::MultiExpression shape_b;
×
526
        for (const auto& dim : j["shape_b"]) {
×
527
            shape_b.push_back(symbolic::parse(dim.get<std::string>()));
×
528
        }
×
529

530
        symbolic::MultiExpression strides_a;
×
531
        if (j.contains("strides_a")) {
×
532
            for (const auto& stride : j["strides_a"]) {
×
533
                strides_a.push_back(symbolic::parse(stride.get<std::string>()));
×
534
            }
×
535
        }
×
536

537
        symbolic::MultiExpression strides_b;
×
538
        if (j.contains("strides_b")) {
×
539
            for (const auto& stride : j["strides_b"]) {
×
540
                strides_b.push_back(symbolic::parse(stride.get<std::string>()));
×
541
            }
×
542
        }
×
543

544
        symbolic::Expression offset_a = symbolic::integer(0);
×
545
        if (j.contains("offset_a")) {
×
546
            offset_a = symbolic::parse(j["offset_a"].get<std::string>());
×
547
        }
×
548

549
        symbolic::Expression offset_b = symbolic::integer(0);
×
550
        if (j.contains("offset_b")) {
×
551
            offset_b = symbolic::parse(j["offset_b"].get<std::string>());
×
552
        }
×
553

554
        layout_a = TensorLayout(shape_a, strides_a, offset_a);
×
555
        layout_b = TensorLayout(shape_b, strides_b, offset_b);
×
556
    }
×
557

558
    auto quantization = deserialize_quantization(j, "result_quant", QUANTIZATION_MATCH_INPUTS);
×
559

560
    sdfg::serializer::JSONSerializer serializer;
×
561
    DebugInfo debug_info = serializer.json_to_debug_info(j["debug_info"]);
×
562

563
    return builder.add_library_node<MatMulNode>(parent, debug_info, layout_a.value(), layout_b.value(), quantization);
×
564
}
×
565

566
} // namespace tensor
567
} // namespace math
568
} // 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