Layer Normalization

API Reference

General

The layer normalization primitive performs a forward or backward layer normalization operation on a 2-5D data tensor.

Forward

The layer normalization operation performs normalization over the last logical axis of the data tensor and is defined by the following formulas. We show formulas only for 3D data, which are straightforward to generalize to cases of higher dimensions. Variable names follow the standard Naming Conventions.

\[\dst(t, n, c) = \gamma(c) \cdot \frac{\src(t, n, c) - \mu(t, n)} {\sqrt{\sigma^2(t, n) + \varepsilon}} + \beta(c),\]

where

  • \(\gamma(c), \beta(c)\) are optional scale and shift for a channel (see dnnl_use_scale, dnnl_use_shift flags),

  • \(\mu(t, n), \sigma^2(t, n)\) are mean and variance (see dnnl_use_global_stats flag), and

  • \(\varepsilon\) is a constant to improve numerical stability.

Mean and variance are computed at runtime or provided by a user. When mean and variance are computed at runtime, the following formulas are used:

  • \(\mu(t, n) = \frac{1}{C} \sum\limits_{c} \src(t, n, c)_{}\),

  • \(\sigma^2(t, n) = \frac{1}{C} \sum\limits_{c} {}_{} (\src(t, n, c) - \mu(t, n))^2\).

The \(\gamma(c)\) and \(\beta(c)\) tensors are considered learnable.

Difference Between Forward Training and Forward Inference

Backward

The backward propagation computes \(\diffsrc(t, n, c)\), \(\diffgamma(c)^*\), and \(\diffbeta(c)^*\) based on \(\diffdst(t, n, c)\), \(src(t, n, c)\), \(\mu(t, n)\), \(\sigma^2(t, n)\), \(\gamma(c) ^*\), and \(\beta(c) ^*\).

The tensors marked with an asterisk are used only when the primitive is configured to use \(\gamma(c)\), and \(\beta(c)\) (i.e., dnnl_use_scale or dnnl_use_shift are set).

Execution Arguments

Depending on the flags and propagation kind, the layer normalization primitive requires different inputs and outputs. For clarity, a summary is shown below.

Flags

dnnl_forward_inference

dnnl_forward_training

dnnl_backward

dnnl_backward_data

dnnl_normalization_flags_none

Inputs : \(\src\) Outputs : \(\dst\)

Inputs : \(\src\) Outputs : \(\dst\) , \(\mu\) , \(\sigma^2\)

Inputs : \(\diffdst\) , \(\src\) , \(\mu\) , \(\sigma^2\) Outputs : \(\diffsrc\)

Same as for dnnl_backward

dnnl_use_global_stats

Inputs : \(\src\) , \(\mu\) , \(\sigma^2\) Outputs : \(\dst\)

Inputs : \(\src\) , \(\mu\) , \(\sigma^2\) Outputs : \(\dst\)

Inputs : \(\diffdst\) , \(\src\) , \(\mu\) , \(\sigma^2\) Outputs : \(\diffsrc\)

Same as for dnnl_backward

dnnl_use_scale

Inputs : \(\src\) , \(\gamma\) Outputs : \(\dst\)

Inputs : \(\src\) , \(\gamma\) Outputs : \(\dst\) , \(\mu\) , \(\sigma^2\)

Inputs : \(\diffdst\) , \(\src\) , \(\mu\) , \(\sigma^2\) , \(\gamma\) Outputs : \(\diffsrc\) , \(\diffgamma\)

Not supported

dnnl_use_shift

Inputs : \(\src\) , \(\beta\) Outputs : \(\dst\)

Inputs : \(\src\) , \(\beta\) Outputs : \(\dst\) , \(\mu\) , \(\sigma^2\)

Inputs : \(\diffdst\) , \(\src\) , \(\mu\) , \(\sigma^2\) , \(\beta\) Outputs : \(\diffsrc\) , \(\diffbeta\)

Not supported

dnnl_use_global_stats | dnnl_use_scale | dnnl_use_shift

Inputs : \(\src\) , \(\mu\) , \(\sigma^2\) , \(\gamma\) , \(\beta\) Outputs : \(\dst\)

Inputs : \(\src\) , \(\mu\) , \(\sigma^2\) , \(\gamma\) , \(\beta\) Outputs : \(\dst\)

Inputs : \(\diffdst\) , \(\src\) , \(\mu\) , \(\sigma^2\) , \(\gamma\) , \(\beta\) Outputs : \(\diffsrc\) , \(\diffgamma\) , \(\diffbeta\)

Not supported

When executed, the inputs and outputs should be mapped to an execution argument index as specified by the following table.

Primitive input/output

Execution argument index

\(\src\)

DNNL_ARG_SRC

\(\gamma\)

DNNL_ARG_SCALE

\(\beta\)

DNNL_ARG_SHIFT

mean ( \(\mu\) )

DNNL_ARG_MEAN

variance ( \(\sigma\) )

DNNL_ARG_VARIANCE

\(\dst\)

DNNL_ARG_DST

\(\diffdst\)

DNNL_ARG_DIFF_DST

\(\diffsrc\)

DNNL_ARG_DIFF_SRC

\(\diffgamma\)

DNNL_ARG_DIFF_SCALE

\(\diffbeta\)

DNNL_ARG_DIFF_SHIFT

\(src scale\)

DNNL_ARG_ATTR_SCALES | DNNL_ARG_SRC

\(dst scale\)

DNNL_ARG_ATTR_SCALES | DNNL_ARG_DST

\(\text{binary post-op}\)

DNNL_ARG_ATTR_MULTIPLE_POST_OP(binary_post_op_position) | DNNL_ARG_SRC_1

Implementation Details

General Notes

  1. The different flavors of the primitive are partially controlled by the flags parameter that is passed to the primitive descriptor creation function (e.g., dnnl::layer_normalization_forward::primitive_desc()). Multiple flags can be set using the bitwise OR operator (|).

  2. For forward propagation, the mean and variance might be either computed at runtime (in which case they are outputs of the primitive) or provided by a user (in which case they are inputs). In the latter case, a user must set the dnnl_use_global_stats flag. For the backward propagation, the mean and variance are always input parameters.

  3. Both forward and backward propagation support in-place operations, meaning that \(\src\) can be used as input and output for forward propagation, and \(\diffdst\) can be used as input and output for backward propagation. In case of an in-place operation, the original data will be overwritten. This support is limited to cases when data types of \(\src\) and \(\dst\) or \(\diffsrc\) and \(\diffdst\) are identical. Note, however, that backward propagation requires original \(\src\), hence the corresponding forward propagation should not be performed in-place.

Post-ops and Attributes

Attributes enable you to modify the behavior of the layer normalization primitive. The following attributes are supported by the layer normalization primitive:

Propagation

Type

Operation

Description

Restrictions

forward

attribute

Scales

Scales the corresponding tensor by the given scale factor(s).

Supported only for int8 layer normalization and one scale per tensor is supported.

forward

Post-op

Binary

Applies a Binary operation to the result.

General binary post-op restrictions.

Data Type Support

The operation supports the following combinations of data types:

Propagation

Source

Destination

Scale and Shift

forward

f32, bf16, f16, u8, s8, f64

f32, bf16, f16, u8, s8, f64

f32, bf16, f16

backward

f32, bf16, f16, f64

f32, bf16, f16, f64

f32, bf16, f16

Mean and Variance data types are always f32 and independent of Source and Destination data types.

Data Representation

Mean and Variance

The mean (\(\mu\)) and variance (\(\sigma^2\)) are separate tensors with number of dimensions equal to (\(data\_ndims - 1\)) and size \((data\_dim[0], data\_dim[1], ..., data\_dim[ndims - 2])\).

The corresponding memory object can have an arbitrary memory format. Unless mean and variance are computed at runtime and not exposed (i.e., propagation kind is dnnl_forward_inference and dnnl_use_global_stats is not set), the user should provide a memory descriptor for statistics when creating the layer normalization primitive descriptor. For best performance, it is advised to use the memory format that follows the data memory format; i.e., if the data format is dnnl_tnc, the best performance can be expected for statistics with the dnnl_tn format and suboptimal for statistics with the dnnl_nt format.

Scale and Shift

If dnnl_use_scale or dnnl_use_shift are used, the scale (\(\gamma\)) and shift (\(\beta\)) are separate 1D tensors of shape \(C\).

Source, Destination, and Their Gradients

The layer normalization primitive works with an arbitrary data tensor; however, it was designed for RNN data tensors (i.e., dnnl_nc, dnnl_tnc, dnnl_ldnc). Unlike CNN data tensors, RNN data tensors have a single feature dimension. Layer normalization performs normalization over the last logical dimension (feature dimension for RNN tensors) across non-feature dimensions.

The layer normalization primitive is optimized for the following memory formats:

Logical tensor

Implementations optimized for memory formats

NC

dnnl_nc ( dnnl_ab )

TNC

dnnl_tnc ( dnnl_abc ), dnnl_ntc ( dnnl_bac )

LDNC

dnnl_ldnc ( dnnl_abcd )

Implementation Limitations

  1. Refer to Data Types for limitations related to data types support.

  2. GPU

    • Only tensors of 6 or fewer dimensions are supported.

    • Post-ops are not supported.

Performance Tips

  1. For data tensors \(\src\), \(\dst\), \(\diffsrc\), and \(\diffdst\), use memory formats for which the last logical axis is the last in the physical memory layout.

  2. For mean and variance, use the memory format that follows the data memory format; i.e., if the data format is dnnl_tnc, the best performance can be expected for statistics with dnnl_tn and suboptimal for statistics with the dnnl_nt format.

  3. For backward propagation, use the same memory format for \(\src\), \(\diffdst\), and \(\diffsrc\). Different formats are functionally supported but lead to highly suboptimal performance.

  4. Use in-place operations whenever possible (see caveats in General Notes).

Example

Layer Normalization Primitive Example

This C++ API example demonstrates how to create and execute a Layer normalization primitive in forward propagation mode.

Key optimizations included in this example:

  • In-place primitive execution;

  • Creation of memory objects using the primitive descriptor.