| Crates.io | webnn-graph |
| lib.rs | webnn-graph |
| version | 0.2.1 |
| created_at | 2025-12-24 06:49:29.720872+00 |
| updated_at | 2025-12-28 17:55:51.080459+00 |
| description | Simple DSL for WebNN graphs |
| homepage | |
| repository | |
| max_upload_size | |
| id | 2002809 |
| size | 8,637,234 |
webnn-graph is a small Rust library and CLI that defines a WebNN-oriented
graph DSL, parses it into a minimal AST, and enables multiple downstream uses
such as graph validation, serialization, and WebNN graph construction.
The goal is to keep the language surface very close to WebNN itself, while allowing graphs to be expressed declaratively and reused across tooling.
The project also implements a Netron-like WebNN graph visualizer that allows for interactive exploration of graph structure.
Check it out at https://blog.ziade.org/webnn-graph
A WebNN graph defined with this project is split across three distinct files, each with a single responsibility.
.webnn)The .webnn file describes only the structure of the graph:
It contains no actual tensor data.
This file is intended to be:
Its EBNF-like grammar:
File ::= Header Block* EOF
Header ::= "webnn_graph" String "v" Int "{"
Block ::= InputsBlock
| ConstsBlock
| NodesBlock
| OutputsBlock
| "}" (* closes the graph *)
InputsBlock ::= "inputs" "{" InputDecl* "}"
ConstsBlock ::= "consts" "{" ConstDecl* "}"
NodesBlock ::= "nodes" "{" Stmt* "}"
OutputsBlock ::= "outputs" "{" OutputItem* "}"
InputDecl ::= Ident ":" Type ";"
ConstDecl ::= Ident ":" Type ConstAnnot* ";"
OutputItem ::= Ident ("," Ident)* ";"? (* optional semicolon *)
Stmt ::= (MultiAssign | Assign) ";"
Assign ::= Ident "=" Expr
MultiAssign ::= "[" Ident ("," Ident)* "]" "=" Expr
Expr ::= Call | Ident | Literal
Call ::= Ident "(" Args? ")"
Args ::= Arg ("," Arg)*
Arg ::= Ident "=" Value | Value
Value ::= Literal | Ident
Literal ::= Array | String | Number | Boolean | Null
Array ::= "[" (Value ("," Value)*)? "]"
Boolean ::= "true" | "false"
Null ::= "null"
Type ::= DType Shape
DType ::= "f32" | "f16" | "i32" | "u32" | "i64" | "u64" | "i8" | "u8"
Shape ::= "[" (Int ("," Int)*)? "]"
ConstAnnot ::= "@weights" "(" String ")"
| "@scalar" "(" Number ")"
Ident ::= (ALPHA | "_") (ALNUM | "_")*
Int ::= DIGIT+
Number ::= "-"? DIGIT+ ("." DIGIT+)? (("e"|"E") ("+"|"-")? DIGIT+)?
String ::= "\"" ( "\\\"" | "\\\\" | (ANY-but-quote) )* "\""
.manifest.json, optional)If the graph references external weights using @weights("key"), a manifest file can be provided to:
The manifest is metadata only. It does not contain raw tensor bytes.
.weights, optional)The .weights file is a simple concatenation of raw tensor data.
It is:
This separation allows the same graph definition to be reused with different trained weights.
The library parses the .webnn DSL into a very small, intentionally simple AST:
This AST is the true internal representation of a graph.
Once parsed, the AST can be:
The AST is designed to be easy to consume from other tools. In particular, it can be used to:
MLGraphBuilder callsThe library does not attempt to deeply re-specify WebNN semantics. Anything not explicitly checked is passed through and left to the WebNN runtime to validate.
In addition to the text DSL, the AST can be serialized to a canonical JSON format.
Important points:
.webnnThe JSON format is roughly 10x larger than the .webnn DSL and is best suited for tooling, not manual editing.
All CLI commands auto-detect and accept both formats.
.webnn) into a simple AST.webnn with full round-trip supportMLGraphBuilder calls)This is intended as a small, hackable reference scaffold, not a heavy framework.
git clone https://github.com/tarekziade/webnn-graph
cd webnn-graph
make build
make run
# Or:
webnn-graph --help
cargo install webnn-graph
The DSL is block-based and declarative:
inputs {} declares typed inputs
consts {} declares typed constants
nodes {} lists operator calls in order
outputs {} declares named graph outputs
Types use:
dtype[dim0, dim1, ...]
Supported dtypes: f32, f16, i32, u32,i64, u64, i8, u8.
The CLI includes a powerful ONNX-to-WebNN converter that enables you to take existing ONNX models and convert them to the WebNN format.
Important: WebNN does not support dynamic shapes at runtime. Before converting ONNX models, you must resolve all dynamic dimensions using onnx-simplifier.
WebNN's reshape operation requires the shape parameter to be a constant, not a dynamically computed value. Many ONNX models (especially transformers/BERT) use dynamic shape patterns like:
Shape → Gather → Concat → Reshape
These patterns must be resolved to static constants at conversion time. Without simplification, the converter will fail or produce incorrect results.
Install and use onnx-simplifier:
# Install onnx-simplifier
pip install onnxsim
# Simplify model with static input shapes
onnxsim model.onnx model-static.onnx \
--overwrite-input-shape input_ids:1,128 attention_mask:1,128
# For BERT/Transformer models, specify all inputs
onnxsim bert.onnx bert-static.onnx \
--overwrite-input-shape input_ids:1,512 attention_mask:1,512 token_type_ids:1,512
Results after simplification:
Shape operations removed (dynamic → static constants)Reshape operations use constant values instead of runtime computationOnce your model is simplified with static shapes, convert it to WebNN:
# Basic conversion (extracts weights by default)
webnn-graph convert-onnx --input model-static.onnx
# Output: model-static.webnn + model-static.weights + model-static.manifest.json
# Custom output paths
webnn-graph convert-onnx \
--input model-static.onnx \
--output graph.webnn \
--weights graph.weights \
--manifest graph.manifest.json
# Inline weights for small models (not recommended for large models)
webnn-graph convert-onnx --input model.onnx --inline-weights
# Output to JSON format instead of .webnn
webnn-graph convert-onnx --input model.onnx --output model.json
The converter focuses on NLP/Transformer operations:
# Step 1: Simplify ONNX model with static shapes (REQUIRED!)
onnxsim bert-base.onnx bert-static.onnx \
--overwrite-input-shape input_ids:1,128 attention_mask:1,128 token_type_ids:1,128
# Step 2: Convert ONNX → WebNN
webnn-graph convert-onnx --input bert-static.onnx
# Step 3: Generate JavaScript for browser/runtime
webnn-graph emit-js bert-static.webnn > buildGraph.js
# Step 4: (Optional) Create HTML visualizer
webnn-graph emit-html bert-static.webnn > visualizer.html
open visualizer.html
Example results for BERT models:
Below is the same graph expressed in webnn and JSON.
webnn_graph "resnet_head" v1 {
inputs {
x: f32[1, 2048];
}
consts {
W: f32[2048, 1000] @weights("W");
b: f32[1000] @weights("b");
}
nodes {
logits0 = matmul(x, W);
logits = add(logits0, b);
probs = softmax(logits, axis=1);
}
outputs { probs; }
}
{
"format": "webnn-graph-json",
"version": 1,
"inputs": {
"x": { "dataType": "float32", "shape": [1, 2048] }
},
"consts": {
"W": {
"dataType": "float32",
"shape": [2048, 1000],
"init": { "kind": "weights", "ref": "W" }
},
"b": {
"dataType": "float32",
"shape": [1000],
"init": { "kind": "weights", "ref": "b" }
}
},
"nodes": [
{ "id": "logits0", "op": "matmul", "inputs": ["x", "W"], "options": {} },
{ "id": "logits", "op": "add", "inputs": ["logits0", "b"], "options": {} },
{ "id": "probs", "op": "softmax", "inputs": ["logits"], "options": { "axis": 1 } }
],
"outputs": { "probs": "probs" }
}