Existing neural network frameworks are huge and hard to use. WebNNM is a lightweight easy to understand high-level framework with an embedded expert system that simplifies working with neural networks for newcomers whilst offering experts fine grained control. This specification defines a high-level format for neural network models, and the API exposed by the associated JavaScript library. For an introduction and links to web-based demos, see [[WEBNNM-INTRO]].
This document reflects implementation experience, but is still subject to change. Feedback is welcome through GitHub issues or on the public-cogai@w3.org mailing-list (with public archives).
This specification introduces a simple notation and API for inference, testing and training neural networks models in Web browsers using [[WebNN]] for hardware acceleration. The intent is to make developing and using neural networks easy for newcomers, whilst offering experts fine grained control. Whilst it will be common for models to be pre-trained in the cloud on large datasets, the main use case for training in the browser is for privacy-friendly personalization, avoiding the need to upload personal data to the cloud.
WebNNM makes it easy to define neural network models in terms of blocks and layers. This includes the means to define layers as a composition of other layers, and to repeat layers using a sequence of tensor shapes. The WebNNM library is a JavaScript module that offers a simple API for declaring models, loading models from previously saved snapshots, and using them for inference, testing and training in conjunction with JavaScript modules for handling datasets. Models can be pre-trained in the cloud via WebNNM's support for export to the [[StableHLO/MLIR]] format. Fine-tuning in the browser then allows for rapid personalization. WebNNM's expert system reviews the neural network, the hyperparameters and the dataset in respect to training stability and precision, avoiding difficult choices for novices, and warning where potential problems may arise.
The grammatical rules in this document are to be interpreted as described in [[[RFC5234]]] [[RFC5234]].
Conformance to this specification is defined for five conformance classes:
WebNNM seeks to make life easier for novices whilst providing experts with the control they seek. To realise this ambition, models are expressed in a high-level notation that uses meaningfully named tensor dimensions and omits batch size and sequence lengths, which are applied when compiling models into executable WebNN graphs or on export to MLIR. This allows tensors to be adapted to match the layouts provided by the datasets. The models only need to deal with feature layout, e.g. [channels, height, width] for images. Using meaningfully names makes layers easier to understand than the integer indices expected by WebNN.
WebNNM models are defined as a set of blocks, where each block has a set of properties, including a sequence of layers.
Each layer has an operator, e.g. matmul, and zero or more operands followed by zero or more options. In this example, shapes, shape and activation are options. The shapes option signifies that a layer is to be repeated using the corresponding tensors shapes. In this case, a dense layer with output shape [128], a second with shape [80] and a third with shape [40]. WebNNM uses macros to bind options and their values. This is used to bind the activation to "relu" or "softmax". w and b are operands used to name model parameters. Note that the tensor shapes are declared excluding the dimensions for the batch size or sequence length, which are bound later. You are free to declare the data types if needed. The default data type is float32, subject to type inference. WebNNM applies tensor data type and shape inference, removing the need to explicitly specify the shape for every layer.
The permitted datatypes are taken from [[WebNN]]: float32, float16, int32, uint32, int8, uint8, int4, and uint4 (in order of decreasing precision). Note that [[WebNN]] places further restrictions on which data types can be used for certain operators. In addition, [[WebNN]] uses int32 for the size of each dimension, limiting it a maximum of 2147483648.
WebNNM applies inference from the inputs and outputs. Automatic data type casts are applied when needed from a lower to a higher precision, e.g. float16 to float32, but not from a higher to a lower precision. Note that type casting is applied lazily from the inputs to the outputs, i.e. preserving the lower precision until a cast to a higher precision is required. If you need greater control, you can provide explicit data types on the layers of interest.
Block input and output properties are expressed as the block name, followed by a colon then "input" or "output" respectively. This is followed by an optional data type and then a required shape, e.g. model:input float16 shape=[784]. Block properties are terminated with a semicolon. Layers are comma separated. The block name property is used when saving a snapshot. Blocks should name the dimensions when an input or output has more than one dimension, e.g. model:input float32 shape={channels:3, height:28, width:28};. The default dimension name is features for inputs and labels for outputs. The dimension names must be unique, and avoid reserved names: batch, and sequence. The choice of curly braces for shapes with named dimensions is deliberate, and resembles the distinction between JavaScript objects and arrays. Use square brackets for shapes without dimension names.
Named constants can be declared and referred to from layers. Here is an example of declaring constants named "pi", "foo" and "bar":
pi float32 3.1415926;
foo int32 shape=[4] data=[1, 1, 0, 0];
bar shape={a:2, b:3} data=[0, 1, 2, 3, 4, 5];
where pi is defined as a scalar and bar as a rank 2 tensor with layout [a, b]. You have the choice of using curly braces with named dimensions or square brackets for just the dimension sizes.
WebNNM allows users to focus on the features and labels for their data, leaving the batch size and sequence length to be automatically applied when compiling to WebNN. The tensor layout defines how data is physically ordered in the computer's memory. Each tensor has a sequence of dimensions, e.g. [batch, features], or [batch, sequence, features] for sequenced data. The features themselves may be split across several dimensions, e.g. [batch, channels, height, width]. The tensor rank is the number of dimensions. The tensor size is the product of the dimension sizes. The tensor shape is the list of dimension sizes, e.g. [20, 3, 256, 256]. With a few exceptions, most layers preserve the layout. Here is a table showing variety of layouts and their applications:
| Format | Structure | Primary Domain | Hardware Advantage |
| Batch-First | $B \times S\times F$ | Transformers, General NLP | Intuitive for batch slicing and token-level operations. |
| Sequence-First | $S \times B \times F$ | RNNs, LSTMs, Seq2Seq | Optimizes time-step looping memory access. |
| Channels-First | $B \times F \times S$ | 1D Convs, Audio, Time-Series | Fits legacy CuDNN convolution optimization. |
| Packed / Ragged | $TokenCount \times F$ | LLM Serving, GNNs | Eliminates padding waste for variable lengths. |
WebNNM automatically tracks the layout across the neural network, and applies any changes made by each layer. This is used to translate from dimension names for features to the integer indices in the full layout for the tensors operated on by WebNN.
WebNNM parses to an object model for blocks and layers, and from there to a directed acyclic graph (DAG) with node types for inputs, outputs, parameters, literals and WebNN operations. Each node has an explicit data type and tensor shape. Nodes that are not on a path from an input node to an output node are culled, along with associated parameters and literals. Higher level operations are decomposed into WebNN operations as part of the process of building the DAG. Parameter nodes are annotated with attributes relating to how their tensors should be initialised based upon the activation function, for instance the Glorot or He algorithms, as well as good initial values for bias parameters.
The DAG is used to build executable WebNN graphs on demand for inference, testing and training. For testing, the dataset provides a sequence of batches of test data where the last batch may be smaller than the rest. The testing graph uses masking to ignore the missing data in the last batch. Training is similar. The training graph starts with a forward pass to compute the loss, then a backwards pass to compute the gradient of the loss with respect to each model parameter. This is followed by an optimizer that computes updates for the model parameters along with their momentum. A ping/pong approach is used to retain parameter values in the GPU or NPU's memory until training is complete. Further details are given in a later section. Note that DAG nodes omit the batch size and sequence length allowing the batch size, sequence lengths and full layout to be dynamically selected according to the needs of the dataset.
WebNNM aims to make things easy for newcomers whilst giving expert users the control they seek. An expert system auto-configures hyperparameters unless explicitly overriden, along with providing warnings about potential problems.
WebNNM iterates through the DAG to build a profile of the network (e.g., looking for normalization layers, dropout, and counting parameter-heavy operations). The profile is mapped against the provided hyperparams to either auto-configure settings or emit warnings. Here is a comparison with other neural network frameworks:
| Feature | WebNNM | Keras (TF/JAX) | PyTorch Lightning | TensorFlow.js |
| Philosophy | Expert-Guided: Proactively analyzes the DAG to fix common pitfalls. | User-Centric: Provides building blocks; leaves stability to the user. | Research Abstraction: Removes boilerplate but requires manual config. | Deployment First: Focuses on running models; training is secondary. |
| Regularization | Capacity-Aware: Auto-enables L2 if a high-capacity model lacks Dropout. | Manual / Granular: Must be added per-layer (kernel_regularizer). | Optimizer-Linked: Usually manual via weight_decay in the optimizer. | Manual: Required per-layer; no global "auto-regularize" logic. |
| Gradient Stability | Automatic (DAG-based): Detects depth/norms to enable AGC or Global Norm. | Manual: User must manually add clipping to the optimizer. | Configurable: User sets flags; framework doesn't "suggest" values. | Manual: Very low-level; no "auto" scaling/clipping. |
| Precision (float16) | Adaptive: Auto-scales based on model data types and DAG risk. | AMP: Efficient, but doesn't "warn" about specific layer risks. | AMP Toggle: Simple toggle; no topological analysis of risk. | Semi-Manual: Requires manual casts and precision management. |
| Multimodal Loss | Auto-Balanced: Avoids underflow by learning relative weights automatically. | Manual: User must provide a dictionary/list of weights. | Manual: Logic is typically buried in the training_step. | Manual: Verbose implementation for multi-output loss. |
For consumer grade equipment, NPUs often support $float16$ but not $float32$. To speed training and prolong battery life, it is desirable to enable WebNN to use the NPU where practical through the use of $float16$ where this doesn't detract from training stability. WebNNM supports automatic casting and switches to $float16$ for critical operations. Note that WebNNM does not support $bfloat16$ although that may be added in future if and when it is added to WebNN.
This section describes the API for inference, testing and training, along with the API used for datasets.
The example starts with importing the WebNNM module and a dataset module. The application logic is called when the web page has finished loading. Note that the load event handler is marked as async, which is essential for subsequent use of await within the handler. The handler creates an instance of the dataset and declares the WebNNM model. These are passed to the NNModel.create() to create an instance of the model and bind it to the dataset. Finally, the application calls model.test() to apply the test subset of data provided by the dataset. Note that the WebNNM library calls the log function to log messages. This function defaults to console.log, but can be overridden by the application to log messages to the web page.
This section describes the API for inference.
The example starts with int32 for the input tensor and progressively casts it to float16 and finally to float32 for the output tensor. Note the use of numeric literals for operands for the add and pow operations.
The application calls model.createContext() to create a context object for inference. This object is used to initialise the input tensor before calling model.run(context) to create and execute the inference graph. context.output() is then used to retrieve the output tensor. Note the use of model.view() as a convenience function for logging tensors.
Context object methods:
setBatchSize(batchSize)async input(name)async output(name)async randomize(blockName, lower, upper)setData(blockName, data)This section describes the API for testing.
model.test()This section describes the API for training.
Training is similar to testing, but involves a set of training hyperparameters, so called to avoid confusion with the model's trainable parameters:
model.train(hyperparameters)hyperparameters is an object with the following optional properties:
The model parameters are validated after each epoch if the dataset provides a validation subset. On detecting a minima in the loss, the model weights are saved, and training continues for a given number of epochs. The training loss is reported every 50 epochs. Applications should always test the model when training completes. Testing the model on independent data after training is essential to verify that the network has truly learned generalizable patterns rather than simply memorizing the training data.
WebNNM supports a choice of learning rate optimizer. The choices all involve tracking the gradient and its momentum for each of the model parameters:
softsign in place of arctan, which isn't supported by WebNN.Training can be rather time consuming, so it makes sense to avoid waisting time when the initial choice of model parameter values doesn't train well. WebNNM creates a scouting graph to evaluate a set of randomly chosen potential starting points using a few heuristics. The metrics include: Loss, Gradient Ratio, and Dead Ratio. The gradient ratio detects vanishing or exploding gradients, whilst the dead ratio detects dead neurons or overconfident layers.
WebNNM manages training in terms of a warm up phase in which the learning rate is gradually increased to the maximum learning rate. After that the learning rate is gradually reduced using cosine annealing to a very low value when reaching the maximum number of epochs. The default value for warmupEpochs is 5% of the maximum number of epochs, or 1, whichever is larger.
Multimodal models combine different modalities, e.g. video and audio. Multimodal models provide superior accuracy and robustness by leveraging redundant information and cross-sensory context to resolve ambiguities that a single data source cannot. Multi-task models are trained on multiple objectives from a shared representation. WebNNM allows you to specify the relative weights of each modality (or task) to avoid one modality drowning out the learning signals for another, especially in $float16$ environments where underflow is a constant risk. WebNNM addresses this challenge using Uncertainty-Based Weighting. Each task $i$ is assigned a learnable log-variance $s_i$. The total loss $L$ is computed as:$$L = \sum_i \left( \frac{1}{2e^{s_i}} L_i + \frac{1}{2}s_i \right)$$ This allows the optimizer to dynamically attenuate noisy tasks. To ensure stability in $float16$, $s_i$ is initialized at $0.0$ and a gradient scale $S \ge 128$ is recommended.
WebNNM supports transfer learning by allowing you to freeze parameter updates for the early layers of the network. This is based upon annotating each node in the DAG with the maximum number of nodes to reach an input node, and likewise to reach an output node. The freeze hyperparameter is a number in the range 0 to 1, defaulting to 0. It describes the proportion of layers to freeze, starting from the inputs. Parameters associated with nodes closer to the outputs are updated with a fraction that goes from 0 to 1 in a sinusoidal pattern (in range 0 to $\pi/2$). This range is discretised into 5 buckets to enable kernel optimization.
Training involves computing gradients that risk vanishing or exploding. For models with many layers it is good practice to introduce skip connections that bypass a few layers that would otherwise overly weaken the learning signals. It is common to use float32 whilst training to provide an adequate precision for gradients. However, the hardware accelerators in consumer grade equipment may not support float32, motivating the use of a lower precision, e.g. float16. This increases the risk of underflow and overflow during gradient calculations. Another consideration is that whilst GPUs can be used for hardware acceleration of neural networks, they consume considerably more electrical power than NPUs like Apple's ANE. To speed up training in the browser and reduce power consumption, it is desirable to find ways to increase training stability when using float16.
To address this challenge, WebNNM supports techniques to boost weak gradients, to scale down overly large gradients, and ignore updates that overflow the maximum number supported for a given precision. The scaling hyperparameter controls gradient scaling, whilst clipping controls clipping. If you set these to 'auto' (the default) the library examines the width and depth of the model along with the activation functions to assess whether to enable scaling and clipping. For explicit control set these hyperparameter to 'on' or 'off' as desired.
To prevent underflow during the backward pass, WebNNM multiplies the loss $L$ by a scale factor $S$ (where $S \gg 1$) before backpropagation begins. By the chain rule, this scales every subsequent gradient in the computational graph:$$\nabla_{\theta} (L \cdot S) = S \cdot \nabla_{\theta} L$$This ensures that the intermediate gradients $g$ that would have been $10^{-7}$ (and thus zeroed out in float16) are now $g \cdot S$, keeping them within the precision range. We cannot apply the scaled gradients directly to the model's parameters, as this would be equivalent to using a learning rate that is $S$ times too large. Before the optimizer updates the parameter $\theta$, the gradients need to be unscaled back to their original magnitude:$$\theta_{t+1} = \theta_t - \eta \cdot \frac{\sum \nabla_{\theta} (L \cdot S)}{S}$$
Use the scale hyperparameter to set the initial gradient scaling factor. The scale is doubled until overflow is detected at which point it is halved. To prevent instability, the growthInterval hyperparameter sets the number of batches without overflow to wait before boosting the scale factor. The forward and backward passes may use float16 for speed, but it is better to keep the model parameters in float32, and likewise to cast from float16 to float32 for operations like softmax and batchnorm. WebNNM will do this for you unless you explicitly set the data types in the model.
Clipping is based upon the scaled global (L2) norm $||\mathbf{G}||_2$ which is defined as the square root of the sum of the squares of every individual scalar value across all gradient tensors $g_i$:$$||\mathbf{G}||_2 = \sqrt{\sum_{i=1}^{n} \sum_{x \in g_i} x^2}$$
The training graph computes the rolling mean for the scaled global norm and its absolute deviation, as a basis for dynamically setting the clipping threshold:
Note that $k$ is 4.0 for the first 15 batches and 2.0 after that.
Work is underway to support automated gradient clipping (AGC) and regularization on a layer by layer basis.
This section describes the API for datasets.
This section lists the WebNN operators supported by WebNNM along with additional operators, e.g. for skip connections and transformers, that are internally translated into WebNN operators. Each operator's signature has a sequence of comma separated arguments, which are either order-dependent operands or order-independent properties of the form name = value.
The layer's input is implicitly the first operand. softmax(), thus has one operand, corresponding to the layer's input, whilst add(b) has two operands: the first is the layer's input and the second is the trainable parameter "b" which is given a unique numeric suffix when instantiated into the model's DAG. Names for constants and layers take precedence over parameters, e.g. if "pi" has been defined as a constant then mul(pi) will multiply the layer's input by the constant pi. Whilst operands are position dependent, options are position independent and can be given in any order. Options are non-trainable.
WebNNM seeks to make life easier for novices whilst providing experts with the control they seek. To realise this ambition, models are expressed in a high-level notation that uses meaningfully named tensor dimensions and omits batch size and sequence lengths, which are applied when compiling models into executable WebNN graphs or on export to MLIR. This allows tensors to be adapted to match the layouts provided by the datasets. The models will often only need to deal with feature layout, e.g. [channels, height, width] for images. Using meaningfully names makes layers easier to understand than the integer indices expected by WebNN.
Operators inherited from WebNN:
| Category | WebNN Operators |
| Tensor Creation | input, constant |
| Tensor Manipulation | concat, expand, gather, gatherElements, scatterElements, gatherND, scatterND, where, pad, reshape, slice, split, transpose, resample2d, reverse, tile, triangular |
| Tensor Quantization | quantizeLinear, dequantizeLinear |
| Tensor Casting | cast |
| Mathematics | add, sub, mul, div, max, min, clamp, pow, abs, ceil, cos, erf, exp, floor, identity, log, neg, reciprocal, sin, sqrt, tan, tanh, sign, clamp |
| Logical | equal, notEqual, greater, greaterOrEqual, lesser, lesserOrEqual, logicalNot, logicalAnd, logicalOr, logicalXor |
| Matrix Manipulation | matmul, gemm |
| Convolution | conv2d, convTranspose2d |
| Pooling | averagePool2d, l2Pool2d, maxPool2d |
| Activation | clamp, elu, gelu, hardSigmoid, hardSwish, leakyRelu, linear, prelu, relu, sigmoid, softmax, softplus, softsign, tanh |
| Normalization | batchNormalization, instanceNormalization, layerNormalization |
| Reduction | argMin, argMax, reduceL1, reduceL2, reduceLogSum, reduceLogSumExp, reduceMax, reduceMean, reduceMin, reduceProduct, reduceSum, reduceSumSquare, cumulativeSum |
| Recurrent Neural Networks | gruCell, gru, lstmCell, lstm |
Note that you can write batchNorm in place of batchNormalization, and likewise instanceNorm and layerNorm.
The high-level model syntax caters for model inputs and outputs. Numeric literals can be used in place, or named and used elsewhere. Parameters are expressed as named operands for layers. Here are some examples:
model:input float16 shape={channels:3, height:256, width:256} An input for the model block with float16 for the datatype, and a shape for an image with named dimensions.model:output float32 shape=[32] An output for the model block with float32 for the datatype and a shape with size 32, e.g. as a probability distribution across labelled classes.matmul(w) A matmul layer with "w" as the trainable weight parameter. This will be given a unique suffix, e.g. "w1", when instantiated into the model's DAG to differentiate it from other instances. The data type and shape are determined automatically.add(1.5) An add layer that adds the number 1.5 to the layer's input, using broadcasting to match the input tensor's shape.pi float32 3.1415926 Declares "pi" as a named float32 scalar constant.foo int32 shape=[4] data=[1, 1, 0, 0] Declares "bar" as the name of a constant int32 tensor with shape [4], and fill it with the listed values.bar shape={a:2, b:3} data=[0, 1, 2, 3, 4, 5] Declares "foo" as the name of constant tensor with shape [2, 3] and layout [a, b]. The tensor is filled from the one dimensional buffer, which must have a length equal to the tensor's size, i.e. the product of the size of the tensor's dimensions, in this example, $size = 2 \times 3 = 6$channels:4). Dimensions that should be left unchanged are specified by their name alone (e.g., height), signaling to the compiler to inherit their size directly from the input tensor. Global dimensions like batch size and sequence length are omitted from this property and are automatically preserved by the backend. If the feature dimensions inside the braces are listed in a different order from the tensor passed from the previous layer, a transpose operation will be automatically inserted to permute the dimensions to match the exact layout order given in the layer.gather(indices=tensor, axis=name)
indices is a non-trainable n-dimensional tensor whose values are signed or unsigned integers. If $N$ is the size of the input dimension named by axis, then values must be in the range $-N$ (inclusive) to $N$ exclusive. The output tensor will have rank equal to the rank of the layer's input tensor plus the rank of indices minus 1. Models either declare the indices tensor as a named constant or generate it as the output of a named layer.
Some use cases involve gathering along the batch or sequence dimensions that are missing in the high-level model syntax, and dealt with when compiling to WebNN. Here is an example showing the run-time tensor shapes and layouts:
Is it worth allowing indices to be defined in place in the layer? One way to do that would be to use shape and data properties as an alternative to indices, using the same syntax and meaning as for constant tensors. If adopted, this would also apply to gatherElements, scatterElements, gatherND and scatterND.
The non-trainable indices array contains entire coordinates into the input tensor, with the rightmost dimension holding the number of dimensions per coordinate. So an indices tensor of shape [10,1] holds 10 single-axis coordinates, and a shape of [4,3] holds 4 indices of 3D coordinates. The values are integers in the range $-N$ (inclusive) to $N$ (exclusive), where $N$ is the is the size of the corresponding input dimension, and a negative index means indexing from the end of the corresponding dimension.
permutation list, which lists all of the feature dimensions in the desired order. The compiler will automatically isolate and preserve global dimensions (like batch size and sequence length) in their original positions. For example, transposing a vision feature map from [height, width, channels] to [channels, height, width] automatically preserves the batch dimension at the front, achieving an NHWC to NCHW shift seamlessly.
[batch, sequence, heads, depth] to [batch, heads, sequence, depth])—developers may explicitly include the names batch and sequence within the permutation list to override the compiler's automatic placement and gain full structural control of the runtime tensor. In this case the length of the permutation list must equal the full rank of the input tensor.nearest-neighbor or linear, defaulting to the former. The resample2D layer arguments are non-trainable.
Here is an example that resizes the image data to 64 by 64 pixels using bilinear interpolation:
resample2D(newShape={ height: 64, width: 64 }, mode=bilinear)
Here is an example that flips the image left to right:
reverse(axes=[width])
Here is an example that repeats the image twice vertically and three times horizontally:
tile(repetitions={ height: 2, width: 3 })
upper property defaults to true and indicates whether to output the upper or the lower part of the input matrix. The diagonal property defaults to 0 and specifies an offset relative to the main diagonal of the matrix. A value of 0 means only elements bounded by the main diagonal itself are affected; a positive integer shifts the boundary up and to the right, while a negative integer shifts it down and to the left. Note that the operation applies to the last two feature dimensions. If these are not the final two dimensions in the run-time tensor layout, transpose operations will be automatically injected before and after the WebNN triangular operation. If the input tensor has greater than 2 dimensions it is treated as a batch of matrices and the result has the same shape. None of the triangular layer arguments are trainable.clamp(roundEven(input / scale) + zeroPoint, 0, 255) for "uint8"). The scale and zeroPoint tensors can be smaller than the input tensor as they are blockwise broadcast. scale and zeroPoint name a layer, input or constant. The scale tensor values must be non-zero positive numbers. The zeroPoint tensor must have the same shape as the scale tensor. The layer's input data type must be float32 or float16. The data type of the scale tensor will (if needed) be casted to that of the layer's input. The data type for zeroPoint must be uint8, int8, uint32 or int32. The data type of the output is taken from the data type of zeroPoint.output = (input - zeroPoint) * scale. The scale and zeroPoint tensors can be smaller than the input tensor as they are blockwise broadcastable. scale and zeroPoint are defined in same way as for quantizeLinear except in respect to their data types. The layer's input data type must be uint8, int8, uint32 or int32. The same holds for zeroPoint. The data type of the scale tensor must be float32 or float16. The data type of the output is taken from the data type of scale.dataType must be one of uint8, int8, uint32, int32, float16 or float32.
Some platforms may also support uint64 and int64.
The following are element-wise operations with two operands, where the first is the layer's input and the second trainable parameter is given as a layer argument (value).
Would it be worth allowing a list of operands in square brackets, where the layer's input is not an implicit member of the list? What are the use cases for adding more than two operands in this way?
The following are element-wise unary operations on the layer's input. The output has the same shape and data type as the input.
Finally, the clamp operator:
minValue and maxValue are optional numbers. If either are missing, the corresponding clamp is not performed. The output tensor of the same shape as input.Logical operators generate a tensor with the data type uint8 with a non-zero value (usually 1) for true and zero for false. For operators with more than one operand, the operands are broadcast as needed. The shape of the output tensor is the same as the shape of the broadcasted input. The value argument is non-trainable.
1 (true) if the corresponding elements are equal, and 0 (false) otherwise. Supports implicit broadcasting if the shapes differ.1 (true) if the corresponding elements are not equal, and 0 (false) otherwise. Supports implicit broadcasting if the shapes differ.1 (true) if the element in the primary tensor is strictly greater than the corresponding element in the other tensor, and 0 (false) otherwise. Supports implicit broadcasting if the shapes differ.1 (true) if the element in the primary tensor is greater than or equal to the corresponding element in the other tensor, and 0 (false) otherwise. Supports implicit broadcasting if the shapes differ.1 (true) if the element in the primary tensor is strictly less than the corresponding element in the other tensor, and 0 (false) otherwise. Supports implicit broadcasting if the shapes differ.1 (true) if the element in the primary tensor is less than or equal to the corresponding element in the other tensor, and 0 (false) otherwise. Supports implicit broadcasting if the shapes differ.The following operators expect their inputs to have the data type uint8:
0 (false) and false values to 1.1 (true) only if the elements in both tensors are non-zero, and 0 (false) otherwise. Supports implicit broadcasting if the shapes differ.1 (true) if at least one of the corresponding elements in either tensor is non-zero, and 0 (false) otherwise. Supports implicit broadcasting if the shapes differ.1 (true) if the corresponding elements differ (one is non-zero and the other is zero), and 0 (false) if they are both zero or both non-zero. Supports implicit broadcasting if the shapes differ.The matmul operator is used for fully connected dense layers in conjunction and the add operator: $y = W\cdot x + b$. In transformers, matmul is the core of the attention mechanism, computing the similarity of query and key matrices to produce attention scores. It also plays a frequent role in calculating gradients as part of machine learning.
value tensor, as specified by the name of a trainable parameter, an input constant or another layer. The operation is applied strictly to the trailing two feature dimensions of both inputs (interpreting them as rows and columns of a matrix). If either tensor has a rank greater than 2, the leading dimensions (such as batch sizes or attention heads) are treated as batch dimensions and are automatically aligned and broadcasted by the compiler to match. The inner dimensions of the two matrices must match at run-time to satisfy standard linear algebra rules. If the targeted matrix dimensions are not at the trailing end of the resolved runtime layouts, the compiler will automatically inject transpose operations to ensure a safe WebNN compilation.B specifies matrix $B$, and an optional parameter C specifies the bias tensor $C$. The scalar multipliers alpha and beta both default to 1.0. The boolean flags aTranspose and bTranspose default to false; setting either to true instructs the WebNN backend to implicitly transpose the final two dimensions of the associated matrix ($A$ or $B$) prior to multiplication, which is highly optimized at the hardware level. Like matmul, this operation applies to the trailing two feature dimensions of the tensors, and any leading dimensions are automatically managed and broadcasted as batch matrices. Tensors B and C are trainable, alpha, beta and the transpose flags are not trainable.These operators are used in Convolutional Neural Networks (CNN) for learning, feature extraction and spatial manipulation. conv2d is used to extract local spatial features from input images. This includes edges, corners, textures, or higher-level patterns progressively through deeper layers. A small learnable kernel slides across the spatial dimensions of the input, computing weighted sums for each local region. This reduces spatial redundancy, emphasizing features useful for classification or recognition.
convTranspose2D performs learnable upsampling, expanding spatial dimensions of feature maps. It is the inverse of conv2d, spreading or broadcasting input activations across a larger area, and often used to reconstruct high-resolution outputs from compressed representations. It increases height and width while preserving learned features for generative tasks. It can introduce artifacts like checkerboarding if the stride and kernel configuration are not carefully chosen.
filter tensor across the input feature map to extract localized spatial features. The inputLayout string configures the expected ordering of the dimensions (nchw for channel-first or nhwc for channel-last), allowing the compiler to parse and map the global batch ($N$), channel ($C$), and spatial spatial ($H, W$) axes. Similarly, filterLayout specifies the structure of the weight tensor: iohw, hwoi or ohwi.
padding (explicit pixel boundaries added around the input, matching [top, bottom, left, right]), strides (the pixel stepping increment along the height and width, matching [height, width]), and dilations (the receptive field spacing between filter elements, matching [height, width]). The groups parameter partitions the input channels into independent calculation blocks for depthwise or grouped convolutions.inputLayout and filterLayout map the dynamic runtime axes to WebNN exactly as they do in conv2d. The spatial upscaling scale is primarily determined by the strides array, which acts as a multiplier on the output canvas.
outputPadding (extra pixels added to the bottom and right edges of the output tensor) or an explicit outputSizes layout block. The remaining parameters (padding, dilations, and groups) operate as the inverse of standard convolutional operations to govern weight spatial distribution.Pooling operations are used in Convolutional Neural Networks (CNN) to compress images while retaining the most important information. Pooling is applied after convolution layers for dimensionality reduction, translation invariance and feature selection. Progressively reducing the spatial dimensions, pooling layers enable the network to build a hierarchy of features. Lower layers capture fine details, while higher layers focus on more abstract and global features. The choice of pooling size, stride, and type needs to balance dimensionality reduction and feature preservation.
Each of the operators support the same options:
nchw or nhwc, representing the run-time tensor layout of [batch, channels, height, width] and [batch, height, width, channels], respectively.floor or ceil.Here are the operators:
Activation functions in neural networks serve a critical role: they introduce non-linearity into the model, allowing it to learn complex patterns and relationships in data. Without activation functions, a neural network would behave like a simple linear regression model, unable to capture intricate features. They determine whether a neuron should be "activated" based on the weighted sum of its inputs, influencing the network's output and learning capability.
alpha parameter defaults to 1.0 and governs the structural saturation value for negative inputs, helping push mean activations closer to zero for improved gradient flow.alpha parameter defaults to 0.01 and prevents "dying neurons" by ensuring a continuous, non-zero gradient across the entire negative spectrum.alpha parameter serves as the negative-side scaling factor. Unlike other activation functions where this factor is a static scalar, prelu expects a parameter weight tensor whose values are optimized during deep learning training. The DAG compiler utilizes type and shape inference on the alpha argument to automatically validate its structure at build time. If the inferred shape of the alpha tensor differs from the input tensor, the compiler handles the necessary layout alignment and implicitly broadcasts the weights across matching named feature dimensions (such as channel-wise scaling).int8.Normalization speeds training by reducing the likelihood of vanishing or exploding gradients. It works by shifting and scaling input values to have a similar range, based upon their mean and variance. The epsilon option is a very small number (default = 1E-5) that is used to avoid dividing by zero. For each operator, you can include trainable scale and bias parameters that allow the model to learn scaling and shifting normalized tensors according to the needs of the model.
reduceMean, pow and sub operations automatically injected into the intermediate DAG. The axis option names the features dimension as appropriate to the kind of data, e.g. color channels, audio frequencies or text embeddings. Note that mean and variance are not trainable!axes=[height, width, channels].If batch normalization is needed for inference, there is insufficient data to compute accurate values for mean and variance. A work around is to use the values from training, averaged across all batches. WebNNM allows you to provide these precomputed tensors, but doesn't provide automatic support for computing them during training, so you will need to extend the training model to compute rolling averages.
The argMin and argMax operators allow you to pick out minimum and maximum values along a designated axis.
keepDimensions if true, retains reduced dimensions with size 1, defaulting to false. Optional outputDataType sets the output data type, defaulting to int32.keepDimensions if true, retains reduced dimensions with size 1, defaulting to false. Optional outputDataType sets the output data type, defaulting to int32.The following operators reduce the input tensor along all dimensions, or along the axes specified with the axes list. For each specified axis, the dimension with that index is reduced, i.e. the resulting tensor will not contain it, unless keepDimensions is specified. The values of the resulting tensor are calculated using the specified reduction function that applies over all the input values across the reduced dimensions.
The cumulativeSum operator computes a running total along a specified tensor axis, transforming sequential deltas into an accelerated, parallelized history of the data's progression.
exclusive determines whether to include or exclude the current value in the output, meaning inclusive prefix sum or exclusive prefix sum. Given input [1,2,3,4], inclusive summation would yield an output of [1,3,6,10] whereas exclusive would yield [0,1,3,6]. The default is inclusive. reversed determines whether to reverse the summation direction along the active axis to instead start from the high coordinate to low coordinate. Given input [1,2,3,4], inclusive forward summation would yield an output of [1,3,6,10] whereas inclusive backward summation would yield [10,9,7,4]. The default is forward.Recurrent layers process sequential data by maintaining an internal state across time steps. The operators below define Gated Recurrent Unit (GRU) and Long Short-Term Memory (LSTM) operations. The layout is only needed when using pre-trained models with a non-default ordering of the dimensions for the weight and bias vectors for the internal gates.
weight and recurrentWeight: Trainable parameters representing the input and recurrent transformation tensors.bias and recurrentBias are optional trainable parameters, defaulting to zero tensors.hiddenState is the optional, non-trainable, input hidden state tensor of shape [batchSize, hiddenSize], defaulting to a zero tensor.hiddenSize is the size of the second dimension of the output tensor shape. It indicates the number of features in the hidden state.resetAfter if true, applies the reset gate linear transformation after multiplying by the recurrent weights. Default is true.layout is a three element list defining the ordering of the dimensions for the weight and bias vectors for the internal gates, defaulting to [update, reset, gate].activations is a list of two activation functions where the first is used for the update and reset gate, and the second used for the new gate. When not specified, defaults to the [sigmoid, tanh]. The permitted functions are relu, sigmoid and tanh.weight and recurrentWeight: Trainable parameters representing the input and recurrent transformation tensors.bias and recurrentBias are optional trainable parameters, defaulting to zero tensors.steps is the number of time steps in the recurrent network. The value must be greater than 0.hiddenSize is value of the second dimension of the output tensor shape. It indicates the number of features in the hidden state.initialHiddenState is the optional, non-trainable, input hidden state tensor of shape [batchSize, hiddenSize], defaulting to a zero tensor.resetAfter if true, applies the reset gate linear transformation after multiplying by the recurrent weights. Default is true.returnSequence if true, returns the hidden states for all time steps; if false, returns only the final hidden state. Default is false.direction defines the sequence traversal direction. Allowed values: forward, backward, or both. Default is forward. When set to "both", the size of the first dimension of the weight and the bias tensor shapes must be 2, and the input is processed in both directions.layout is a three element list defining the ordering of the dimensions for the weight and bias vectors for the internal gates, defaulting to [update, reset, gate].activations is a list of two activation functions where the first is used for the update and reset gate, and the second used for the new gate. When not specified, defaults to the [sigmoid, tanh]. The permitted functions are relu, sigmoid and tanh.weight and recurrentWeight: Trainable parameters representing the input and recurrent transformation tensors.bias and recurrentBias are optional trainable parameters, defaulting to zero tensors.hiddenState is the optiona, non-trainable, input hidden state tensor of shape [batchSize, hiddenSize], defaulting to a zero tensor.cellState is the optional, non-trainable, input cell state tensor of shape [batchSize, hiddenSize], defaulting to a zero tensor.hiddenSize is value of the second dimension of the output tensor shape. It indicates the number of features in the hidden state.peepholeWeight: is an optional trainable tensor providing connections from the cell state directly to the internal gates.layout is a four element list defining the ordering of the dimensions for the weight and bias vectors for the internal gates, defaulting to [input, output, forget, cell].activations is a list of three activation functions where the first used for the input, forget, and output gate, the second one is used for the cell gate, and the last used for filtering the output cell state before combining it with the result of the output gate to form the output hidden state. When not specified, defaults to [sigmoid, tanh, tanh]. The permitted functions are relu, sigmoid and tanh.weight and recurrentWeight: Trainable parameters representing the input and recurrent transformation tensors.bias and recurrentBias are optional trainable parameters, defaulting to zero tensors.hiddenSize is value of the second dimension of the output tensor shape. It indicates the number of features in the hidden state.peepholeWeight is an optional trainable tensor providing connections from the cell state directly to the internal gates.initialHiddenState is the optional, non-trainable, initial input hidden state tensor of shape [batchSize, hiddenSize].initialCellState is the optional, non-trainable, initial input cell state tensor of shape [batchSize, hiddenSize].returnSequence if true, returns the full structural history of hidden states across all steps. Default is false.direction defines the sequence traversal direction: forward, backward, or both. Default is forward. When set to "both", the size of the first dimension of the weight and the bias tensor shapes must be 2, and the input is processed in both directions.layout: A four element list defining the ordering of the dimensions for the weight and bias vectors for the internal gates, defaulting to [input, output, forget, cell].activations is a list of three activation functions where the first is used for the input, forget, and output gate, the second one is used for the cell gate, and the last used for filtering the output cell state before combining it with the result of the output gate to form the output hidden state. When not specified, defaults to [sigmoid, tanh, tanh]. The permitted functions are relu, sigmoid and tanh.The layer signatures follow those in the WebNN specification, but break the WebNNM convention that options are non-trainable parameters given that bias and recurrentBias are trainable options, while steps and hiddenSize are required options. These layers already have too many positional arguments for comfort, so perhaps the convention is worth breaking in such cases.
Some common operators are not built into WebNN and are compiled into WebNN sub-graphs. These include:
This section defines the grammar for the model syntax.
/* High-Level Grammar Structure */
Grammar ::= ( ( CONSTANT | BLOCK ) ";" S* )+
CONSTANT ::= name S+ datatype? S+ SHAPE S+ DATA
SHAPE ::= ( "[" (number ( "," number )*)? "]" )
| ( "{" name ":" number ( "," S+ name ":" number )* "}" )
BLOCK ::= ( name ":name" S+ name )
| ( name ":loss" S+ name )
| ( name ":alpha" S+ number )
| ( name ":delta" S+ number )
| ( name ":gamma" S+ number )
| ( name ":input" S+ datatype? S+ SHAPE )
| ( name ":output" S+ datatype? S+ SHAPE )
| ( name ":layers" S+ LAYER ( "," S* LAYER )* )
LAYER ::= name "(" ARGUMENT ( "," S* ARGUMENT )? ")"
ARGUMENT ::= name
| number
| ( name S* "=" S* VALUE )
VALUE ::= name
| number
| LIST
LIST ::= "[" ITEM ( "," S* ITEM )* "]"
ITEM ::= number
| name
| LIST
DATA ::= "[" number ( "," S* number )* "]"
datatype ::= "float32" | "float16" | "uint32" | "int32" | "uint8" | "int8"
/* Core Tokens & Terminals */
/* Matches integers (42), floats (3.14), and exponents (1e-5, 6.02e23) */
number ::= [+-]? ( ( digit+ ( "." digit* )? ) | ( "." digit+ ) ) ( [eE] [+-]? digit+ )?
/* General Name: matches identifiers, including hyphens and underscores */
name ::= name_start_char name_char*
name_start_char ::= [a-zA-Z_]
name_char ::= [a-zA-Z0-9_-]
digit ::= [0-9]
/* Whitespace and Comments */
S ::= whitespace | comment
whitespace ::= [#x09#x0A#x0D#x20]+ /* Tab, LF, CR, Space */
comment ::= "#" [^#x0A#x0D]* /* '#' followed by anything except newlines */
Here is an informative version as a railroad diagram, where "S" denotes whitespace:

WebNNM supports the following loss functions and checks that they are consistent with the activation function, label format and a sample of the training data. You can set the loss function explicitly as the loss property for the associated block(e.g. model:loss CCEL;), otherwise WebNNM will pick one for you based upon the context. Note that some loss functions have parameters, e.g. $\delta$ for Huber Loss and $\gamma$ for Focal Loss. These can be specified as block properties, e.g. model:delta 2.1;. If they are not defined, the WebNNM expert system will set them based on the standard deviation of the error observed during the initial scouting pass.
| Loss Function | Mathematical Form (Loss) | Explanation | Best Suited For |
| Mean Squared Error (MSE) | $\frac{1}{n}\sum(y - \hat{y})^2$ | Calculates the average of the squares of the errors | Standard Regression: Best for tasks where targets are continuous values and you want to penalize large errors more heavily than small ones, e.g. prediction house prices. |
| Mean Absolute Error (MAE) | $\frac{1}{n}\sum\|y - \hat{y}$ | Calculates the average of the absolute difference between targets and predictions | Robust Regression: Ideal when your dataset contains significant outliers as it is less sensitive to extreem values than MSE. |
| Binary Cross-Entropy (BCE) | $-\frac{1}{n}\sum [y\ln(p) + (1-y)\ln(1-p)]$ | Measures the performance of a classification model whos output is a probability value between 0 and 1 | Binary Classification: Used for single-label or multi-label "yes/no" tasks. |
| Categorical Cross-Entropy (CCE) | $-\sum y_i \ln(\hat{y}_i)$ | Measures the difference between two probability distributions | Multi-class Classification: Standard for "one-of-many" classification. Expects Softmax inputs. |
| CCE with Logits (CCEL) | $-\sum y_i \cdot {LogSoftmax}(\hat{y}_i)$ | Combines Softmax activation and Cross-Entropy into a single stable step | Stability Choice: Expert system should force this if `NPU` (FP16) is detected to prevent overflow. |
| Sparse Categorical Cross-Entropy (SCE) | $-\ln(\hat{y}_{target})$ | Memory-Efficient Multi-class: Identical to CCE but accepts integer labels (e.g. 3) instead of one-hot vectors ([0,0,1,0]) | Memory Efficient: For use when labels are integer indices rather than one-hot vectors. |
| Hinge Loss (HIL) | $\sum \max(0, 1 - y_i \cdot \hat{y}_i)$ | Support Vector Machines: A "maximum-margin" loss function | Used for SVM-style classification; robust to small variations, where you want a safety margin between class boundaries. |
| Huber Loss (HUL) | $\begin{cases} \frac{1}{2}(y-\hat{y})^2 & \text{if } \text{error} \leq \delta \\ \delta(\|y-\hat{y}\| - \frac{1}{2}\delta) & \text{else} \end{cases}$ | General Purpose Regression: Acts as MSE for small errors and MAE for large errors | Balanced Regression: Switch to this if `scout` detects high variance in gradients. |
| KL Divergence (KLD) | $\sum y_i \ln(\frac{y_i}{\hat{y}_i})$ | Distribution Matching: Measures how a probability distribution approximates a reference distribution | Used in Variational Autoencoders (VAEs) and Knowledge Distillation |
| Focal Loss (FOL) | $-\alpha(1 - p_t)^\gamma \ln(p_t)$ | Adds a $(1 - p_t)^\gamma$ factor to the Cross-Entropy loss | Imbalanced Data: Recommended if the `dataset` class counts are skewed, where the model needs to focus on harder examples rather than on the easier ones. |
This section defines the binary format for models and their parameters.
(to be added)
This section describes the test harness used to validate the WebNNM library's implementation of analytic gradients. The approach taken is to measure the gradients for each parameter and compare it to the analytic value computed by the WebNNM library for each operator. This done for repeatedly for a set of randomly initialised tensors for the parameters and the input from the previous layer. In more detail, for each operator:
Note: the analytic gradients for the WebNN recurrent operators, e.g. LSTM and GRU are computed by mapping them to the primitive operations and unrolling over time for a given sequence length.