Accumulation precision is unspecified for matmul/gemm and reduction operations
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 15/100
- issue の種類
- 機能追加
- 明瞭さ
- おおむね明確
- 活発さ
- 活発
- 技術スタック
- javascript
- 領域
- api, machine-learning
調査の方向性
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.
索引モデルが issue の本文から書いたものです。
説明
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
- 主要言語
- Bikeshed
- スター
- 566
- フォーク
- 72
- PR マージ指標
- 30日以内にマージされた PR はありません
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
webmachinelearning/webnn のほかの issue
-
難易度 5/5 1週間以上 初心者へのやさしさ 25/100
webmachinelearning/webnn#972 · コメント 1 件 ·
-
難易度 5/5 1週間以上 初心者へのやさしさ 25/100
webmachinelearning/webnn#971 · リアクション 1 件 ·
-
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
webmachinelearning/webnn#970 · コメント 2 件 ·
-
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
webmachinelearning/webnn#968 · コメント 2 件 ·
-
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
webmachinelearning/webnn#967 ·
webmachinelearning/webnn の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
Huber-group-EMBL/Rarr#294 ·
メンテナーはふだん 1 日以内に返信
-
needs attention untriaged
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet#3666 ·
メンテナーはふだん 3 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 77/100
punitarani/fli#270 ·
-
🐛 Bug 🔔 Pending processing
難易度 2/5 1〜3時間 初心者へのやさしさ 67/100
jumpserver/jumpserver#17980 ·
メンテナーはふだん 1 日以内に返信
-
agent-butler-finding bug
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
jordansmall/spindrift#6414 ·
メンテナーはふだん 1 日以内に返信