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

daisytuner / docc / 30348520059

28 Jul 2026 09:53AM UTC coverage: 64.367%. First build
30348520059

Pull #896

github

web-flow
Merge f743dbc18 into 81e93d5b0
Pull Request #896: [PyTorch] Aten Layer Norm parser & Layer Norm LibNode

358 of 397 new or added lines in 4 files covered. (90.18%)

43539 of 67642 relevant lines covered (64.37%)

725.7 hits per line

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

90.2
/sdfg/src/data_flow/library_nodes/math/tensor/layernorm_node.cpp
1
#include "sdfg/data_flow/library_nodes/math/tensor/layernorm_node.h"
2

3
#include "sdfg/builder/structured_sdfg_builder.h"
4
#include "sdfg/data_flow/access_node.h"
5
#include "sdfg/data_flow/library_nodes/math/cmath/cmath_node.h"
6
#include "sdfg/data_flow/library_nodes/math/tensor/elementwise_ops/mul_node.h"
7
#include "sdfg/data_flow/library_nodes/math/tensor/reduce_ops/sum_node.h"
8
#include "sdfg/data_flow/library_nodes/math/tensor/tensor_expansion_utils.h"
9
#include "sdfg/data_flow/library_nodes/stdlib/malloc.h"
10
#include "sdfg/structured_control_flow/block.h"
11
#include "sdfg/structured_control_flow/map.h"
12
#include "sdfg/types/pointer.h"
13
#include "sdfg/types/scalar.h"
14
#include "sdfg/types/utils.h"
15

16
namespace sdfg::math::tensor {
17

18
static std::vector<std::string> layernorm_inputs(bool affine, bool has_bias) {
13✔
19
    std::vector<std::string> inputs;
13✔
20
    inputs.push_back("X");
13✔
21
    if (affine) {
13✔
22
        inputs.push_back("Gamma");
11✔
23
    }
11✔
24
    if (has_bias) {
13✔
25
        inputs.push_back("Beta");
7✔
26
    }
7✔
27
    inputs.push_back("epsilon");
13✔
28
    inputs.push_back("Y_out");
13✔
29
    return inputs;
13✔
30
}
13✔
31

32
LayerNormNode::LayerNormNode(
33
    size_t element_id,
34
    const DebugInfo& debug_info,
35
    graph::Vertex vertex,
36
    data_flow::DataFlowGraph& parent,
37
    TensorLayout layout,
38
    QuantizationType quantization,
39
    size_t num_normalized_dims,
40
    bool affine,
41
    bool has_bias,
42
    data_flow::ImplementationType impl_type
43
)
44
    : TensorNode(
13✔
45
          element_id,
13✔
46
          debug_info,
13✔
47
          vertex,
13✔
48
          parent,
13✔
49
          LibraryNodeType_LayerNorm,
13✔
50
          {},
13✔
51
          layernorm_inputs(affine, has_bias),
13✔
52
          std::move(impl_type)
13✔
53
      ),
13✔
54
      layout_(std::move(layout)), quantization_(quantization), num_normalized_dims_(num_normalized_dims),
13✔
55
      affine_(affine), has_bias_(has_bias) {}
13✔
56

NEW
57
symbolic::SymbolSet LayerNormNode::symbols() const {
×
NEW
58
    symbolic::SymbolSet syms;
×
NEW
59
    layout_.collect_symbols(syms);
×
NEW
60
    return syms;
×
NEW
61
}
×
62

63
types::PrimitiveType LayerNormNode::quantization() const { return quantization_; }
5✔
64

NEW
65
void LayerNormNode::set_quantization(const types::PrimitiveType quant) { quantization_ = quant; }
×
66

NEW
67
void LayerNormNode::replace(const symbolic::Expression old_expression, const symbolic::Expression new_expression) {
×
NEW
68
    layout_.replace_symbols(old_expression, new_expression);
×
NEW
69
}
×
70

NEW
71
void LayerNormNode::replace(const symbolic::ExpressionMapping& replacements) { layout_.replace_symbols(replacements); }
×
72

73
std::unique_ptr<data_flow::DataFlowNode> LayerNormNode::
74
    clone(size_t element_id, const graph::Vertex vertex, data_flow::DataFlowGraph& parent) const {
1✔
75
    return std::unique_ptr<data_flow::DataFlowNode>(new LayerNormNode(
1✔
76
        element_id,
1✔
77
        debug_info(),
1✔
78
        vertex,
1✔
79
        parent,
1✔
80
        this->layout_,
1✔
81
        this->quantization_,
1✔
82
        this->num_normalized_dims_,
1✔
83
        this->affine_,
1✔
84
        this->has_bias_,
1✔
85
        this->implementation_type_
1✔
86
    ));
1✔
87
}
1✔
88

89
std::string LayerNormNode::toStr() const { return "LayerNorm(" + layout_.toStr() + ")"; }
1✔
90

91
passes::LibNodeExpander::ExpandOutcome LayerNormNode::
92
    expand(passes::LibNodeExpander::ExpandContext& context, structured_control_flow::Block& block) {
5✔
93
    auto& dataflow = this->get_parent();
5✔
94

95
    auto* x_edge = dataflow.in_edge_for_connector(*this, "X");
5✔
96
    if (!x_edge) {
5✔
NEW
97
        return context.unable();
×
NEW
98
    }
×
99
    auto& in_type = x_edge->base_type();
5✔
100
    types::PrimitiveType prim = in_type.primitive_type();
5✔
101
    types::Scalar element_type(prim);
5✔
102
    types::Pointer pointer_type(element_type);
5✔
103

104
    const auto& shape = layout_.shape();
5✔
105
    const int n = static_cast<int>(shape.size());
5✔
106
    const int n_norm = static_cast<int>(num_normalized_dims_);
5✔
107
    const int n_lead = n - n_norm;
5✔
108

109
    if (n_norm <= 0 || n_lead < 0) {
5✔
NEW
110
        return context.unable();
×
NEW
111
    }
×
112

113
    std::vector<symbolic::Expression> full_shape(shape.begin(), shape.end());
5✔
114
    std::vector<symbolic::Expression> leading_shape(shape.begin(), shape.begin() + n_lead);
5✔
115
    std::vector<symbolic::Expression> trailing_shape(shape.begin() + n_lead, shape.end());
5✔
116

117
    types::Tensor full_tensor(prim, full_shape);
5✔
118
    types::Tensor leading_tensor(prim, leading_shape);
5✔
119

120
    // Reduce axes = the trailing (normalized) dimensions.
121
    std::vector<int64_t> axes;
5✔
122
    for (int i = n_lead; i < n; ++i) {
12✔
123
        axes.push_back(i);
7✔
124
    }
7✔
125

126
    // Connector indices (must match the order in layernorm_inputs()).
127
    int idx = 0;
5✔
128
    const int X_IDX = idx++;
5✔
129
    const int GAMMA_IDX = affine_ ? idx++ : -1;
5✔
130
    const int BETA_IDX = has_bias_ ? idx++ : -1;
5✔
131
    const int EPS_IDX = idx++;
5✔
132
    const int YOUT_IDX = idx++;
5✔
133

134
    using Use = passes::LibNodeExpander::InputUse;
5✔
135
    std::vector<Use> dirs;
5✔
136
    dirs.push_back(Use::IndirectRead); // X
5✔
137
    if (affine_) {
5✔
138
        dirs.push_back(Use::IndirectRead); // Gamma
4✔
139
    }
4✔
140
    if (has_bias_) {
5✔
141
        dirs.push_back(Use::IndirectRead); // Beta
3✔
142
    }
3✔
143
    dirs.push_back(Use::Scalar); // epsilon
5✔
144
    dirs.push_back(Use::IndirectWrite); // Y_out
5✔
145

146
    auto standalone = context.replacement_requires_access_nodes(dirs);
5✔
147
    if (!standalone) {
5✔
NEW
148
        return context.unable();
×
NEW
149
    }
×
150

151
    auto& seq = standalone->replace_with_sequence();
5✔
152
    auto& builder = standalone->builder();
5✔
153

154
    // Allocate a temporary buffer of the given shape and return its container name.
155
    auto make_buffer = [&](const std::vector<symbolic::Expression>& bshape, const std::string& prefix) -> std::string {
25✔
156
        std::string name = builder.find_new_name(prefix);
25✔
157
        if (bshape.empty()) {
25✔
158
            builder.add_container(name, element_type);
4✔
159
        } else {
21✔
160
            builder.add_container(name, pointer_type);
21✔
161
            symbolic::Expression bytes = types::get_type_size(element_type, false);
21✔
162
            for (auto& d : bshape) {
27✔
163
                bytes = symbolic::mul(d, bytes);
27✔
164
            }
27✔
165
            auto& alloc_block = builder.add_block(seq, debug_info());
21✔
166
            auto& acc = builder.add_access(alloc_block, name, debug_info());
21✔
167
            auto& malloc_node = builder.add_library_node<stdlib::MallocNode>(alloc_block, debug_info(), bytes);
21✔
168
            builder.add_computational_memlet(alloc_block, malloc_node, "_ret", acc, {}, pointer_type, debug_info());
21✔
169
        }
21✔
170
        return name;
25✔
171
    };
25✔
172

173
    std::string x2_name = make_buffer(full_shape, "_ln_x2");
5✔
174
    std::string sumx_name = make_buffer(leading_shape, "_ln_sumx");
5✔
175
    std::string sumx2_name = make_buffer(leading_shape, "_ln_sumx2");
5✔
176
    std::string mean_name = make_buffer(leading_shape, "_ln_mean");
5✔
177
    std::string rstd_name = make_buffer(leading_shape, "_ln_rstd");
5✔
178

179
    // count = product of the normalized (trailing) dimension sizes.
180
    // Computed symbolically and stored in a scalar so it also works for dynamic shapes.
181
    auto count_name = builder.find_new_name("_ln_count");
5✔
182
    builder.add_container(count_name, types::Scalar(types::PrimitiveType::Int64));
5✔
183
    {
5✔
184
        symbolic::Expression count_expr = symbolic::one();
5✔
185
        for (int i = n_lead; i < n; ++i) {
12✔
186
            count_expr = symbolic::mul(count_expr, shape.at(i));
7✔
187
        }
7✔
188
        builder.add_assignments(seq, {{symbolic::symbol(count_name), count_expr}}, debug_info());
5✔
189
    }
5✔
190

191
    // x2 = x * x  (elementwise, full shape)
192
    {
5✔
193
        auto& b = builder.add_block(seq, debug_info());
5✔
194
        auto& x_in = standalone->add_scalar_input_access(b, X_IDX);
5✔
195
        auto& x2_acc = builder.add_access(b, x2_name, debug_info());
5✔
196
        auto& mul = builder.add_library_node<MulNode>(b, debug_info(), full_shape);
5✔
197
        builder.add_computational_memlet(b, x_in, mul, "A", {}, full_tensor, debug_info());
5✔
198
        builder.add_computational_memlet(b, x_in, mul, "B", {}, full_tensor, debug_info());
5✔
199
        builder.add_computational_memlet(b, x2_acc, mul, "C", {}, full_tensor, debug_info());
5✔
200
    }
5✔
201

202
    // sum_x = Sum(x) over normalized axes.
203
    // NOTE: a plain Sum (not Mean) is used deliberately. MeanNode finalizes with an internal
204
    // divide-by-count that can get duplicated across kernels during GPU map fusion (dividing
205
    // E[x^2] twice), which corrupts the variance. We do the /count division ourselves below,
206
    // exactly once.
207
    {
5✔
208
        auto& b = builder.add_block(seq, debug_info());
5✔
209
        auto& x_in = standalone->add_scalar_input_access(b, X_IDX);
5✔
210
        auto& sumx_acc = builder.add_access(b, sumx_name, debug_info());
5✔
211
        auto& sum_node = builder.add_library_node<SumNode>(b, debug_info(), full_shape, axes, false);
5✔
212
        builder.add_computational_memlet(b, x_in, sum_node, "X", {}, full_tensor, debug_info());
5✔
213
        builder.add_computational_memlet(b, sumx_acc, sum_node, "Y", {}, leading_tensor, debug_info());
5✔
214
    }
5✔
215

216
    // sum_x2 = Sum(x^2) over normalized axes
217
    {
5✔
218
        auto& b = builder.add_block(seq, debug_info());
5✔
219
        auto& x2_in = builder.add_access(b, x2_name, debug_info());
5✔
220
        auto& sumx2_acc = builder.add_access(b, sumx2_name, debug_info());
5✔
221
        auto& sum_node = builder.add_library_node<SumNode>(b, debug_info(), full_shape, axes, false);
5✔
222
        builder.add_computational_memlet(b, x2_in, sum_node, "X", {}, full_tensor, debug_info());
5✔
223
        builder.add_computational_memlet(b, sumx2_acc, sum_node, "Y", {}, leading_tensor, debug_info());
5✔
224
    }
5✔
225

226
    // mean = sum_x / count ; rstd = 1 / sqrt(sum_x2/count - mean*mean + eps)  (per leading row)
227
    {
5✔
228
        auto lead_maps = create_maps(builder, leading_shape, seq);
5✔
229
        structured_control_flow::Sequence& lead_scope = leading_shape.empty() ? seq : lead_maps.back().seq;
5✔
230
        std::vector<symbolic::Expression> lead_subset;
5✔
231
        for (auto& m : lead_maps) {
5✔
232
            lead_subset.push_back(m.indvar);
4✔
233
        }
4✔
234

235
        auto& b = builder.add_block(lead_scope, debug_info());
5✔
236
        std::string prefix = "_ln_rstd_tmp";
5✔
237
        int t = 0;
5✔
238

239
        auto& sumx_in = builder.add_access(b, sumx_name, debug_info());
5✔
240
        auto& sumx2_in = builder.add_access(b, sumx2_name, debug_info());
5✔
241
        auto& eps_in = standalone->add_scalar_input_access(b, EPS_IDX);
5✔
242
        auto& count_a = builder.add_access(b, count_name, debug_info());
5✔
243
        auto& count_b = builder.add_access(b, count_name, debug_info());
5✔
244

245
        // mean = sum_x / count  (kept as a scalar temp and also written to the mean buffer)
246
        auto& mean_op = builder.add_tasklet(b, data_flow::fp_div, "_out", {"s", "c"}, debug_info());
5✔
247
        builder.add_computational_memlet(b, sumx_in, mean_op, "s", lead_subset, leading_tensor, debug_info());
5✔
248
        builder.add_computational_memlet(b, count_a, mean_op, "c", {}, element_type, debug_info());
5✔
249
        auto mean_s_name = create_temp_var(builder, prefix, t++, element_type);
5✔
250
        auto& mean_s = builder.add_access(b, mean_s_name, debug_info());
5✔
251
        builder.add_computational_memlet(b, mean_op, "_out", mean_s, {}, element_type, debug_info());
5✔
252

253
        auto& mean_store_op = builder.add_tasklet(b, data_flow::assign, "_out", {"_in"}, debug_info());
5✔
254
        builder.add_computational_memlet(b, mean_s, mean_store_op, "_in", {}, element_type, debug_info());
5✔
255
        auto& mean_acc = builder.add_access(b, mean_name, debug_info());
5✔
256
        builder.add_computational_memlet(b, mean_store_op, "_out", mean_acc, lead_subset, leading_tensor, debug_info());
5✔
257

258
        // sq = mean * mean
259
        auto& sq_op = builder.add_tasklet(b, data_flow::fp_mul, "_out", {"a", "b"}, debug_info());
5✔
260
        builder.add_computational_memlet(b, mean_s, sq_op, "a", {}, element_type, debug_info());
5✔
261
        builder.add_computational_memlet(b, mean_s, sq_op, "b", {}, element_type, debug_info());
5✔
262
        auto sq_name = create_temp_var(builder, prefix, t++, element_type);
5✔
263
        auto& sq_acc = builder.add_access(b, sq_name, debug_info());
5✔
264
        builder.add_computational_memlet(b, sq_op, "_out", sq_acc, {}, element_type, debug_info());
5✔
265

266
        // ex2 = sum_x2 / count
267
        auto& ex2_op = builder.add_tasklet(b, data_flow::fp_div, "_out", {"s", "c"}, debug_info());
5✔
268
        builder.add_computational_memlet(b, sumx2_in, ex2_op, "s", lead_subset, leading_tensor, debug_info());
5✔
269
        builder.add_computational_memlet(b, count_b, ex2_op, "c", {}, element_type, debug_info());
5✔
270
        auto ex2_name = create_temp_var(builder, prefix, t++, element_type);
5✔
271
        auto& ex2_acc = builder.add_access(b, ex2_name, debug_info());
5✔
272
        builder.add_computational_memlet(b, ex2_op, "_out", ex2_acc, {}, element_type, debug_info());
5✔
273

274
        // var = ex2 - sq
275
        auto& var_op = builder.add_tasklet(b, data_flow::fp_sub, "_out", {"x", "y"}, debug_info());
5✔
276
        builder.add_computational_memlet(b, ex2_acc, var_op, "x", {}, element_type, debug_info());
5✔
277
        builder.add_computational_memlet(b, sq_acc, var_op, "y", {}, element_type, debug_info());
5✔
278
        auto var_name = create_temp_var(builder, prefix, t++, element_type);
5✔
279
        auto& var_acc = builder.add_access(b, var_name, debug_info());
5✔
280
        builder.add_computational_memlet(b, var_op, "_out", var_acc, {}, element_type, debug_info());
5✔
281

282
        // ve = var + eps
283
        auto& ve_op = builder.add_tasklet(b, data_flow::fp_add, "_out", {"v", "e"}, debug_info());
5✔
284
        builder.add_computational_memlet(b, var_acc, ve_op, "v", {}, element_type, debug_info());
5✔
285
        builder.add_computational_memlet(b, eps_in, ve_op, "e", {}, element_type, debug_info());
5✔
286
        auto ve_name = create_temp_var(builder, prefix, t++, element_type);
5✔
287
        auto& ve_acc = builder.add_access(b, ve_name, debug_info());
5✔
288
        builder.add_computational_memlet(b, ve_op, "_out", ve_acc, {}, element_type, debug_info());
5✔
289

290
        // s = sqrt(ve)
291
        auto& sqrt_op = builder.add_library_node<cmath::CMathNode>(b, debug_info(), cmath::CMathFunction::sqrt, prim);
5✔
292
        builder.add_computational_memlet(b, ve_acc, sqrt_op, "_in1", {}, element_type, debug_info());
5✔
293
        auto s_name = create_temp_var(builder, prefix, t++, element_type);
5✔
294
        auto& s_acc = builder.add_access(b, s_name, debug_info());
5✔
295
        builder.add_computational_memlet(b, sqrt_op, "_out", s_acc, {}, element_type, debug_info());
5✔
296

297
        // rstd = 1 / s
298
        auto& one_c = builder.add_constant(b, "1.0", element_type, debug_info());
5✔
299
        auto& inv_op = builder.add_tasklet(b, data_flow::fp_div, "_out", {"o", "s"}, debug_info());
5✔
300
        builder.add_computational_memlet(b, one_c, inv_op, "o", {}, element_type, debug_info());
5✔
301
        builder.add_computational_memlet(b, s_acc, inv_op, "s", {}, element_type, debug_info());
5✔
302
        auto& rstd_acc = builder.add_access(b, rstd_name, debug_info());
5✔
303
        builder.add_computational_memlet(b, inv_op, "_out", rstd_acc, lead_subset, leading_tensor, debug_info());
5✔
304
    }
5✔
305

306
    // Y = (x - mean) * rstd [* Gamma] [+ Beta]  (per element)
307
    // Iterate over a flattened [lead_flat, trail_flat] index space and address every
308
    // operand with explicit linear indices built from the actual map indvars. This avoids
309
    // a multi-dimensional trailing (suffix) subset under a collapsed map: the index
310
    // simplifier mishandles that case (it drops the modulo on the leading dimension),
311
    // which would index Gamma/Beta out of bounds for every row past the first.
312
    {
5✔
313
        symbolic::Expression lead_flat = symbolic::one();
5✔
314
        for (auto& d : leading_shape) {
5✔
315
            lead_flat = symbolic::mul(lead_flat, d);
4✔
316
        }
4✔
317
        symbolic::Expression trail_flat = symbolic::one();
5✔
318
        for (auto& d : trailing_shape) {
7✔
319
            trail_flat = symbolic::mul(trail_flat, d);
7✔
320
        }
7✔
321
        symbolic::Expression total = symbolic::mul(lead_flat, trail_flat);
5✔
322

323
        const bool has_leading = !leading_shape.empty();
5✔
324

325
        std::vector<symbolic::Expression> map_shape;
5✔
326
        if (has_leading) {
5✔
327
            map_shape.push_back(lead_flat);
4✔
328
        }
4✔
329
        map_shape.push_back(trail_flat);
5✔
330

331
        auto maps = create_maps(builder, map_shape, seq);
5✔
332
        structured_control_flow::Sequence& scope = maps.back().seq;
5✔
333

334
        symbolic::Expression l_idx = symbolic::zero();
5✔
335
        if (has_leading) {
5✔
336
            l_idx = maps.at(0).indvar;
4✔
337
        }
4✔
338
        symbolic::Expression t_idx = maps.back().indvar;
5✔
339
        symbolic::Expression lin = symbolic::add(symbolic::mul(l_idx, trail_flat), t_idx);
5✔
340

341
        types::Tensor total_tensor(prim, {total});
5✔
342
        types::Tensor lead_flat_tensor(prim, {lead_flat});
5✔
343
        types::Tensor trail_flat_tensor(prim, {trail_flat});
5✔
344

345
        std::vector<symbolic::Expression> lin_subset{lin};
5✔
346
        std::vector<symbolic::Expression> l_subset;
5✔
347
        if (has_leading) {
5✔
348
            l_subset.push_back(l_idx);
4✔
349
        }
4✔
350
        std::vector<symbolic::Expression> t_subset{t_idx};
5✔
351

352
        auto& scope_block = builder.add_block(scope, debug_info());
5✔
353
        std::string prefix = "_ln_norm_tmp";
5✔
354
        int t = 0;
5✔
355

356
        // Reads mean/rstd either per leading row (contiguous [lead_flat] view of the
357
        // buffer written above) or as a scalar when normalizing over all dimensions.
358
        auto read_reduced = [&](data_flow::AccessNode& src, data_flow::Tasklet& op, const std::string& conn) {
10✔
359
            if (has_leading) {
10✔
360
                builder.add_computational_memlet(scope_block, src, op, conn, l_subset, lead_flat_tensor, debug_info());
8✔
361
            } else {
8✔
362
                builder.add_computational_memlet(scope_block, src, op, conn, {}, element_type, debug_info());
2✔
363
            }
2✔
364
        };
10✔
365

366
        auto& x_in = standalone->add_indirect_read_access(scope_block, X_IDX);
5✔
367
        auto& mean_in = builder.add_access(scope_block, mean_name, debug_info());
5✔
368
        auto& rstd_in = builder.add_access(scope_block, rstd_name, debug_info());
5✔
369

370
        // c = x - mean
371
        auto& sub_op = builder.add_tasklet(scope_block, data_flow::fp_sub, "_out", {"x", "m"}, debug_info());
5✔
372
        builder.add_computational_memlet(scope_block, x_in, sub_op, "x", lin_subset, total_tensor, debug_info());
5✔
373
        read_reduced(mean_in, sub_op, "m");
5✔
374
        auto c_name = create_temp_var(builder, prefix, t++, element_type);
5✔
375
        auto& c_acc = builder.add_access(scope_block, c_name, debug_info());
5✔
376
        builder.add_computational_memlet(scope_block, sub_op, "_out", c_acc, {}, element_type, debug_info());
5✔
377

378
        const bool has_scale_or_bias = affine_ || has_bias_;
5✔
379
        data_flow::AccessNode* cur = nullptr;
5✔
380

381
        // n = c * rstd
382
        {
5✔
383
            auto& mul_op = builder.add_tasklet(scope_block, data_flow::fp_mul, "_out", {"c", "r"}, debug_info());
5✔
384
            builder.add_computational_memlet(scope_block, c_acc, mul_op, "c", {}, element_type, debug_info());
5✔
385
            read_reduced(rstd_in, mul_op, "r");
5✔
386
            if (has_scale_or_bias) {
5✔
387
                auto n_name = create_temp_var(builder, prefix, t++, element_type);
4✔
388
                auto& n_acc = builder.add_access(scope_block, n_name, debug_info());
4✔
389
                builder.add_computational_memlet(scope_block, mul_op, "_out", n_acc, {}, element_type, debug_info());
4✔
390
                cur = &n_acc;
4✔
391
            } else {
4✔
392
                auto& y_out = standalone->add_indirect_write_access(scope_block, YOUT_IDX);
1✔
393
                builder
1✔
394
                    .add_computational_memlet(scope_block, mul_op, "_out", y_out, lin_subset, total_tensor, debug_info());
1✔
395
            }
1✔
396
        }
5✔
397

398
        // * Gamma
399
        if (affine_) {
5✔
400
            const bool last = !has_bias_;
4✔
401
            auto& gamma_in = standalone->add_indirect_read_access(scope_block, GAMMA_IDX);
4✔
402
            auto& g_op = builder.add_tasklet(scope_block, data_flow::fp_mul, "_out", {"n", "g"}, debug_info());
4✔
403
            builder.add_computational_memlet(scope_block, *cur, g_op, "n", {}, element_type, debug_info());
4✔
404
            builder
4✔
405
                .add_computational_memlet(scope_block, gamma_in, g_op, "g", t_subset, trail_flat_tensor, debug_info());
4✔
406
            if (last) {
4✔
407
                auto& y_out = standalone->add_indirect_write_access(scope_block, YOUT_IDX);
1✔
408
                builder
1✔
409
                    .add_computational_memlet(scope_block, g_op, "_out", y_out, lin_subset, total_tensor, debug_info());
1✔
410
            } else {
3✔
411
                auto g_name = create_temp_var(builder, prefix, t++, element_type);
3✔
412
                auto& g_acc = builder.add_access(scope_block, g_name, debug_info());
3✔
413
                builder.add_computational_memlet(scope_block, g_op, "_out", g_acc, {}, element_type, debug_info());
3✔
414
                cur = &g_acc;
3✔
415
            }
3✔
416
        }
4✔
417

418
        // + Beta
419
        if (has_bias_) {
5✔
420
            auto& beta_in = standalone->add_indirect_read_access(scope_block, BETA_IDX);
3✔
421
            auto& add_op = builder.add_tasklet(scope_block, data_flow::fp_add, "_out", {"n", "b"}, debug_info());
3✔
422
            builder.add_computational_memlet(scope_block, *cur, add_op, "n", {}, element_type, debug_info());
3✔
423
            builder
3✔
424
                .add_computational_memlet(scope_block, beta_in, add_op, "b", t_subset, trail_flat_tensor, debug_info());
3✔
425
            auto& y_out = standalone->add_indirect_write_access(scope_block, YOUT_IDX);
3✔
426
            builder.add_computational_memlet(scope_block, add_op, "_out", y_out, lin_subset, total_tensor, debug_info());
3✔
427
        }
3✔
428
    }
5✔
429

430
    return standalone->successfully_expanded();
5✔
431
}
5✔
432

NEW
433
symbolic::Expression LayerNormNode::flop() const {
×
434
    // Rough estimate: a few flops per element for the two reduction passes plus the
435
    // normalization and optional affine transform.
NEW
436
    auto total = layout_.total_elements();
×
NEW
437
    return symbolic::mul(total, symbolic::integer(8));
×
NEW
438
}
×
439

NEW
440
data_flow::PointerAccessType LayerNormNode::pointer_access_type(int input_idx) const {
×
NEW
441
    int idx = 0;
×
NEW
442
    const int x_idx = idx++;
×
NEW
443
    const int gamma_idx = affine_ ? idx++ : -1;
×
NEW
444
    const int beta_idx = has_bias_ ? idx++ : -1;
×
NEW
445
    idx++; // epsilon (scalar)
×
NEW
446
    const int yout_idx = idx++;
×
447

NEW
448
    if (input_idx == x_idx || (affine_ && input_idx == gamma_idx) || (has_bias_ && input_idx == beta_idx)) {
×
NEW
449
        return data_flow::PointerAccessMeta::create_read_only(symbolic::__nullptr__(), true);
×
NEW
450
    } else if (input_idx == yout_idx) {
×
NEW
451
        return data_flow::PointerAccessMeta::create_full_write_only(symbolic::__nullptr__(), true);
×
NEW
452
    } else {
×
NEW
453
        return TensorNode::pointer_access_type(input_idx);
×
NEW
454
    }
×
NEW
455
}
×
456

457
nlohmann::json LayerNormNodeSerializer::serialize(const data_flow::LibraryNode& library_node) {
1✔
458
    auto& node = static_cast<const LayerNormNode&>(library_node);
1✔
459
    nlohmann::json j;
1✔
460

461
    j["code"] = node.code().value();
1✔
462

463
    node.layernorm_layout().serialize_to_json(j["layout"]);
1✔
464

465
    j["quant"] = node.quantization();
1✔
466
    j["num_normalized_dims"] = node.num_normalized_dims();
1✔
467
    j["affine"] = node.affine();
1✔
468
    j["has_bias"] = node.has_bias();
1✔
469

470
    return j;
1✔
471
}
1✔
472

473
data_flow::LibraryNode& LayerNormNodeSerializer::deserialize(
474
    const nlohmann::json& j, builder::StructuredSDFGBuilder& builder, structured_control_flow::Block& parent
475
) {
1✔
476
    auto layout = TensorLayout::deserialize_from_json(j.at("layout"));
1✔
477
    auto quant = j.at("quant").get<types::PrimitiveType>();
1✔
478
    auto num_normalized_dims = j.at("num_normalized_dims").get<size_t>();
1✔
479
    auto affine = j.at("affine").get<bool>();
1✔
480
    auto has_bias = j.at("has_bias").get<bool>();
1✔
481

482
    serializer::JSONSerializer serializer;
1✔
483
    auto deb_info = serializer.json_to_debug_info(j.at("debug_info"));
1✔
484

485
    return builder
1✔
486
        .add_library_node<LayerNormNode>(parent, deb_info, layout, quant, num_normalized_dims, affine, has_bias);
1✔
487
}
1✔
488

489
} // namespace sdfg::math::tensor
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