|
| 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