Repository navigation
Proposal: Expose engine-defined compute timing for WebNN (MLComputeTiming) #963
Copy link
Copy link
Open
Description
Problem
- WebNN has no performance surface: no timing, no attribution.
dispatch()has no completion signal: "callers can await the results of reading back the
output tensors."- The only way for a page to learn that a run finished is a readback — so the readback becomes
part of the number, and the bracket differs per engine. Page-timed numbers are not interoperable.
Scenario
A page runs a face detector and wants to pick a smaller model on a slow device. It cannot tell
whether the slow device is limited by the model, the hardware, or its own input preparation.
The only measurement it can take is this:
const start = performance.now();
context.dispatch(graph, inputs, outputs);
await context.readTensor(a); // the only completion signal
await context.readTensor(b);
console.log(`Inference: ${(performance.now() - start).toFixed(2)}ms`);On a reference mobile device (NPU backend):
| measurement | how it was taken | value |
|---|---|---|
| actual graph execution | engine timestamps around the backend run | 5.69 ms |
| dispatch round trip | page timer around dispatch() only |
5.85 ms |
| the code above | dispatch() + two awaited readTensor() calls |
209.23 ms |
The page's "inference" is 209 ms against 5.7 ms of computation, and it cannot see the difference.
Proposal
MLComputeTiming: a PerformanceEntry interface with two engine-created entry types.
[Exposed=(Window,Worker)]
interface MLComputeTiming : PerformanceEntry {
readonly attribute DOMHighResTimeStamp computeDuration;
readonly attribute boolean accelerated;
[Default] object toJSON();
};| entryType | created when |
|---|---|
ml-compile |
compilation completes, incl. initialization (build()) |
ml-compute |
one graph execution completes (dispatch()) |
- One entry per execution, never per operator.
- Observer-only:
availableFromTimeline: false, delivered throughPerformanceObserver. duration= submission → completion (queueing included);computeDuration= the engine's own
compute interval inside it.graphId(engine-assigned, per context) correlates compile and run entries;acceleratedis
one bit.
Relations
- #934 (effective compute policy):
complementary — this contributes measurements and the privacy framing, not a device surface. - #923 (compute policy rename):
decides the long-term shape of theacceleratedfield. - Model Loader (draft): the same entries are
designed to cover itsload()/compute()once that API stabilises. - WebPerf: the entry types need a row in the timing entry types registry. WebNN owns the
semantics; WebPerf owns the registration.
Measurements, alternatives, privacy and the open questions (naming, graphId vs MLGraph.id,
accelerated shape, computeDuration normativity) are in the explainer: link when published.
Reacted by Viktor Mukhachev and Matt Hargett
Metadata
Metadata
Assignees
Labels
No labels
Activity
vmukhachev commentedon Oct 20, 2026
How to measure or estimate execution time of some nodes or groups of nodes? Split the graph into multiple individual graphs?
shubhamg13 commentedon Oct 21, 2026
Yes — split the graph into subgraphs.
[-]Expose engine-defined compute timing for WebNN (`MLComputeTiming`)[/-][+]Proposal: Expose engine-defined compute timing for WebNN (`MLComputeTiming`)[/+]shubhamg13 commentedon Oct 22, 2026
Here is very initial explianer draft: https://github.com/shubhamg13/webnn-instrumentation/blob/main/webnn-ml-compute-timing-explainer.md
shubhamg13 commentedon Oct 26, 2026