Accumulation precision is unspecified for matmul/gemm and reduction operations
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Idoneità per principianti
- 15/100
- Tipo di issue
- Funzionalità
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- javascript
- Ambito
- api, machine-learning
Direzione di ricerca
This is a design question, not a starter task. Read the two proposed approaches (explicit casts versus an accumulatorType field on MLMatmulOptions) and the four comments for any maintainer decision. A first step would be to check which approach the WebNN group has leaned toward. Done means a spec proposal or a recorded decision, not a code change.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Accumulation precision for operations with internal summation
Problem
The WebNN spec does not say anything about what precision intermediate accumulations should use. This is most impactful for matmul and gemm, but applies to any operation that internally sums many values (reduceSum, conv2d, the dot products inside gru/lstm, etc.).
In practice, the accumulation precision is the single biggest factor determining numerical accuracy of a matrix multiply. And different backends make very different choices here, silently.
Why this matters in practice
Take a float16 matmul with K=1024 (a very normal dimension for transformer models):
- float32 accumulation: relative error bounded by roughly K × ε₃₂ ≈ 1024 × 6×10⁻⁸ ≈ 6×10⁻⁵. Fine.
- float16 accumulation: relative error bounded by roughly K × ε₁₆ ≈ 1024 × 10⁻³ ≈ 1.0. The error can be as large as the result itself.
This is not a theoretical corner case. It is the difference between a model working and producing garbage.
A minimal example: dot product of two float16 vectors of length 1024, all values 1.0:
a = [1.0, 1.0, ..., 1.0] // 1024 elements, float16
b = [1.0, 1.0, ..., 1.0] // 1024 elements, float16
Expected: dot(a, b) = 1024.0
With float32 accumulation you get exactly 1024.0. With float16 accumulation, once the running sum exceeds 2048, adding 1.0 starts getting rounded away. For real-world non-uniform data, errors compound much worse.
What backends actually do
Some backends use float32 accumulation for float16 inputs:
- NVIDIA cuBLAS: Default compute type for float16 inputs is
CUBLAS_COMPUTE_32F(documented). - Intel oneDNN: Default accumulation mode is
strict, which means float32 for float ops (documented). Their data types guide explicitly states that intermediate computations use higher precision to avoid overflow (documented). - XNNPACK: Operates in float32 internally; float16 is just a storage format.
Others may use float16 accumulation for throughput, especially on mobile/edge accelerators. The developer cannot tell what they are getting from the WebNN API, and cannot request anything specific.
Possible approaches
Approach A: Explicit upcast
In ONNX and TensorRT, accumulation precision is controlled by casting inputs to float32, performing the operation, and casting back:
const a_f32 = builder.cast(a_f16, "float32");
const b_f32 = builder.cast(b_f16, "float32");
const c_f32 = builder.matmul(a_f32, b_f32);
const c_f16 = builder.cast(c_f32, "float16");
The backend (or a graph optimizer) recognizes the cast-compute-cast pattern and fuses it into an efficient kernel that reads float16 inputs while accumulating in float32 internally.
The spec guarantees only that the accumulator precision is at least the input type. When no casts are present, a float16 matmul accumulates in at least float16 — the backend may silently use higher precision. When the developer needs a specific accumulator precision, they express it through explicit casts.
Advantages:
- No new API surface. Uses existing
cast+ operator primitives. - Familiar pattern from ONNX and TensorRT ecosystems.
- Least intrusive spec change.
Disadvantages:
- Relies on the backend being smart enough to fuse the pattern. A naive backend might actually materialize the float32 intermediates, losing the memory/bandwidth benefit of float16.
- Verbose when many operations need the same accumulation policy.
Approach B: Explicit accumulator type per operation
Add an optional accumulatorType field to operations that have internal accumulation:
partial dictionary MLMatmulOptions {
MLOperandDataType accumulatorType;
};
// Float32 accumulation for attention matmul
builder.matmul(q_k, v, { accumulatorType: "float32" });
// Don't care, go fast (backend picks freely)
builder.matmul(a, b);
The spec guarantees only that the accumulator precision is at least the input type. When accumulatorType is omitted, the backend may use any accumulator precision equal to or higher than the input type — a float16 matmul might accumulate in float16 or float32, at the backend's discretion. When accumulatorType is specified, the backend must use at least that precision. If it cannot, it rejects the graph at build time rather than silently falling back.
This matches the reality of the underlying APIs. In Vulkan cooperative matrices, WebGPU subgroup matrices, and D3D12 linalg, the accumulator type is an explicit parameter — and all three guarantee at least the requested precision. When a lower accumulator type is requested (e.g. f16), the hardware may internally accumulate at higher precision and round the result back — D3D12 makes this visible via the EMULATED_OUTPUTS flag; Vulkan and WebGPU leave it implicit. But when a higher type is requested (e.g. f32), it accumulates in that type.
Advantages:
- Simple and explicit. One optional field, one clear meaning.
- Easy to implement in backends that already have a compute-type concept.
- No ambiguity — the developer names exactly what they want.
Disadvantages:
- New API surface on every applicable operation.
- Ties the set of valid accumulator types to
MLOperandDataType. If a future hardware-internal format is added, the enum must grow.
Discoverability
The support limits could report what the backend provides:
const limits = context.opSupportLimits().matmul;
console.log(limits.accumulatorType);
// e.g. "float32"
Integration with model runtimes (ORT-Web, LiteRT.js)
Neither ONNX nor TFLite specify accumulator precision in their model formats today. The decision falls to whoever sits between the model file and the hardware.
For Approach A, runtimes insert casts when translating the model graph to WebNN. For Approach B, runtimes set accumulatorType per-op based on a session-level policy:
// ORT-Web example
const session = await ort.InferenceSession.create('model.onnx', {
executionProviders: [{
name: 'webnn',
accumulationPrecision: 'high', // maps to accumulatorType: "float32" on all ops
}],
});
No model format changes are required.
Scope
This applies to any operation with internal summation:
matmul/gemmconv2d/convTranspose2dreduceSum/reduceMean/reduceLogSumExpgru/lstmlinear(if added)
What other APIs do for comparison
| API | Mechanism | Accumulator handling |
|---|---|---|
| Vulkan cooperative matrices | Accumulator type is an explicit template parameter (CType/ResultType) per operation. Hardware queries which (AType, BType, CType, ResultType) combinations are supported. |
Fully explicit; the developer picks the accumulator type from the supported list. |
| cuBLAS | cublasComputeType_t enum passed at call time. |
Named enum: CUBLAS_COMPUTE_32F, CUBLAS_COMPUTE_16F, etc. |
| TensorRT | Express via casts in the graph; runtime fuses the pattern. | No new API surface; relies on optimizer pattern matching. |
| oneDNN | dnnl::accumulation_mode enum (strict = fp32, any = fast). |
Combines intent-based with type-based. |
| Metal Performance Shaders | allowReducedPrecision boolean flag. |
Binary opt-in; no granularity. |
| WebGPU subgroup matrices | resultComponentType in GPUSubgroupMatrixConfig is an explicit, separate type from componentType. The device enumerates supported (componentType, resultComponentType, M, N, K) tuples; the application picks one. |
Explicit per-configuration. The result type determines accumulation precision (e.g. componentType: "f16", resultComponentType: "f32" means f16 inputs with f32 accumulation). |
| D3D12 linalg (SM 6.10) | AccumulatorComponentType is an explicit, separate field in D3D12_LINEAR_ALGEBRA_WAVE_MATRIX_MULTIPLY_INPUTS. The application queries whether a specific (MatrixAComponentType, MatrixBComponentType, AccumulatorComponentType) combination is supported at a given shape. |
Explicit per-operation query. The EMULATED_OUTPUTS flag signals when the hardware internally uses higher precision than the requested accumulator type. Tier 1 requires f16×f16→f16 and explicitly permits backends to accumulate at higher precision and convert. |
Both WebGPU and D3D12 treat the accumulator as a first-class, queryable parameter — the application names a specific type, and the backend reports whether it supports that combination. Neither API is silent about accumulation; both make it explicit and discoverable.
WebNN being silent on accumulation means the same model can produce different results across backends and the developer has no recourse.
References
- Lingua principale
- Bikeshed
- Stelle
- 566
- Fork
- 72
- Merge medio
- 6g 5h
- PR unite (30g)
- 1
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di webmachinelearning/webnn
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
webmachinelearning/webnn#967 ·
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
webmachinelearning/webnn#966 · 1 commento ·
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
webmachinelearning/webnn#965 · 1 commento ·
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 68/100
webmachinelearning/webnn#964 · 1 commento · 1 reazione ·
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 30/100
webmachinelearning/webnn#963 · 3 commenti · 2 reazioni ·
Tutte le issue di webmachinelearning/webnn
Issue simili
-
lesson-submission pending-review
Difficoltà 2/5 1-3 ore Idoneità per principianti 67/100
Ikalus1988/MisakaNet#3789 ·
I maintainer di solito rispondono entro 1 giorno
-
alerts bug effort:S P2
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
nightscout/nocturne#2511 ·
I maintainer di solito rispondono entro 1 giorno
-
Python Volcengine provider returns failures instead of raising themForse già presa @Ricardo-M-L l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 79/100
agent-infra/sandbox#248 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
I maintainer di solito rispondono entro 1 giorno
-
Textractor leaves empty temporary files when remote retrieval failsForse già presa @HuaTNA l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
I maintainer di solito rispondono entro 1 giorno