Skip to content

Commit d0daaa1

Browse files
committed
Add WebNN op support limits view
1 parent 6379b54 commit d0daaa1

1 file changed

Lines changed: 252 additions & 0 deletions

File tree

WebnnOpSupportLimits.html

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>WebNN opSupportLimits Viewer</title>
7+
<style>
8+
* { margin: 0; padding: 0; box-sizing: border-box; }
9+
10+
body {
11+
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
12+
background: #f5f5f5;
13+
color: #222;
14+
padding: 24px;
15+
line-height: 1.5;
16+
}
17+
18+
h1 { font-size: 1.4rem; margin-bottom: 4px; }
19+
.subtitle { color: #666; font-size: 0.85rem; margin-bottom: 16px; }
20+
21+
.controls {
22+
display: flex;
23+
align-items: center;
24+
gap: 10px;
25+
margin-bottom: 16px;
26+
}
27+
28+
.controls label { font-weight: 600; font-size: 0.9rem; }
29+
30+
select, button {
31+
font-family: inherit;
32+
font-size: 0.9rem;
33+
padding: 6px 12px;
34+
border-radius: 4px;
35+
}
36+
37+
select {
38+
border: 1px solid #ccc;
39+
background: #fff;
40+
}
41+
42+
button {
43+
border: none;
44+
background: #0078d4;
45+
color: #fff;
46+
font-weight: 600;
47+
cursor: pointer;
48+
}
49+
50+
button:hover { background: #106ebe; }
51+
button:disabled { opacity: 0.5; cursor: not-allowed; }
52+
53+
#status {
54+
margin-bottom: 12px;
55+
padding: 8px 12px;
56+
border-radius: 4px;
57+
font-size: 0.85rem;
58+
display: none;
59+
}
60+
61+
#status.visible { display: block; }
62+
#status.error { background: #fde8e8; color: #c00; border: 1px solid #f5c6c6; }
63+
#status.info { background: #e8f0fe; color: #174ea6; border: 1px solid #c6dafc; }
64+
65+
table {
66+
width: 100%;
67+
border-collapse: collapse;
68+
background: #fff;
69+
border: 1px solid #ddd;
70+
font-size: 0.85rem;
71+
}
72+
73+
thead th {
74+
background: #f0f0f0;
75+
text-align: left;
76+
padding: 8px 10px;
77+
border-bottom: 2px solid #ccc;
78+
font-size: 0.8rem;
79+
white-space: nowrap;
80+
}
81+
82+
tbody td {
83+
padding: 6px 10px;
84+
border-bottom: 1px solid #eee;
85+
vertical-align: top;
86+
}
87+
88+
tbody tr:hover { background: #fafafa; }
89+
90+
.op-name { font-weight: 600; }
91+
92+
.operand-row td { padding-left: 30px; color: #555; }
93+
.operand-label { font-style: italic; }
94+
95+
.datatypes { font-family: 'Cascadia Code', 'Consolas', monospace; font-size: 0.8rem; }
96+
.no-data { color: #999; font-style: italic; }
97+
</style>
98+
</head>
99+
<body>
100+
101+
<h1>WebNN opSupportLimits Viewer</h1>
102+
<p class="subtitle">Inspect operator data type support for each WebNN device backend</p>
103+
104+
<div class="controls">
105+
<label for="deviceSelect">Device:</label>
106+
<select id="deviceSelect">
107+
<option value="cpu">CPU</option>
108+
<option value="gpu">GPU</option>
109+
<option value="npu">NPU</option>
110+
</select>
111+
<button id="queryBtn">Query opSupportLimits</button>
112+
</div>
113+
114+
<div id="status"></div>
115+
<div id="results"></div>
116+
117+
<script>
118+
function showStatus(msg, type)
119+
{
120+
const element = document.getElementById('status');
121+
element.className = 'visible ' + type;
122+
element.textContent = msg;
123+
}
124+
125+
function hideStatus()
126+
{
127+
document.getElementById('status').className = '';
128+
document.getElementById('status').textContent = '';
129+
}
130+
131+
document.getElementById('queryBtn').addEventListener(
132+
'click',
133+
async () =>
134+
{
135+
const button = document.getElementById('queryBtn');
136+
const device = document.getElementById('deviceSelect').value;
137+
button.disabled = true;
138+
139+
try
140+
{
141+
if (!navigator.ml)
142+
{
143+
showStatus('WebNN is not supported in this browser. Try Chrome/Edge 130+ with appropriate flags.', 'error');
144+
button.disabled = false;
145+
return;
146+
}
147+
148+
showStatus('Creating MLContext for ' + device + '…', 'info');
149+
const context = await navigator.ml.createContext({ deviceType: device });
150+
151+
showStatus('Querying opSupportLimits…', 'info');
152+
const limits = context.opSupportLimits();
153+
154+
hideStatus();
155+
renderTable(limits);
156+
}
157+
catch (error)
158+
{
159+
showStatus('Error: ' + error.message, 'error');
160+
console.error(error);
161+
}
162+
finally
163+
{
164+
button.disabled = false;
165+
}
166+
}
167+
);
168+
169+
function renderTable(limits)
170+
{
171+
const container = document.getElementById('results');
172+
container.innerHTML = '';
173+
174+
// Collect operator keys (objects with nested operand info)
175+
const operatorNames = Object.keys(limits).filter(
176+
key =>
177+
{
178+
const v = limits[key];
179+
return v && typeof v === 'object' && !Array.isArray(v);
180+
}
181+
).sort();
182+
183+
if (operatorNames.length === 0)
184+
{
185+
container.innerHTML = '<p class="no-data">No operator limits returned.</p>';
186+
return;
187+
}
188+
189+
const table = document.createElement('table');
190+
const thead = document.createElement('thead');
191+
thead.innerHTML = '<tr><th>Operator</th><th>Operand</th><th>Supported Data Types</th></tr>';
192+
table.appendChild(thead);
193+
194+
const tableBody = document.createElement('tbody');
195+
196+
for (const operatorName of operatorNames)
197+
{
198+
const opLimits = limits[operatorName];
199+
const operands = Object.entries(opLimits).filter(([_, v]) => v && typeof v === 'object');
200+
201+
if (operands.length === 0)
202+
{
203+
const tableRow = document.createElement('tr');
204+
tableRow.innerHTML =
205+
'<td class="op-name">' + operatorName + '</td>' +
206+
'<td class="no-data" colspan="2">No operand data</td>';
207+
tableBody.appendChild(tableRow);
208+
continue;
209+
}
210+
211+
for (let i = 0; i < operands.length; i++)
212+
{
213+
const [operandName, limit] = operands[i];
214+
const tableRow = document.createElement('tr');
215+
216+
// Only show operator name on first row, rowspan the rest
217+
if (i === 0)
218+
{
219+
const tdOp = document.createElement('td');
220+
tdOp.className = 'op-name';
221+
tdOp.textContent = operatorName;
222+
if (operands.length > 1) tdOp.rowSpan = operands.length;
223+
tableRow.appendChild(tdOp);
224+
}
225+
226+
const tdOperand = document.createElement('td');
227+
tdOperand.className = 'operand-label';
228+
tdOperand.textContent = operandName;
229+
tableRow.appendChild(tdOperand);
230+
231+
const tdTypes = document.createElement('td');
232+
tdTypes.className = 'datatypes';
233+
if (limit.dataTypes && limit.dataTypes.length > 0)
234+
{
235+
tdTypes.textContent = limit.dataTypes.join(', ');
236+
}
237+
else
238+
{
239+
tdTypes.innerHTML = '<span class="no-data">none</span>';
240+
}
241+
tableRow.appendChild(tdTypes);
242+
243+
tableBody.appendChild(tableRow);
244+
}
245+
}
246+
247+
table.appendChild(tableBody);
248+
container.appendChild(table);
249+
}
250+
</script>
251+
</body>
252+
</html>

0 commit comments

Comments
 (0)