1<!DOCTYPE html> 2<html> 3<head> 4 <title>$spec_name</title> 5 <style> 6 body { 7 font-family: "Roboto", sans-serif; 8 margin: 0; 9 height: 100%; 10 background-color: rgb(61, 65, 77); 11 } 12 13 #main { 14 width: 62%; 15 transition: 0.5s; 16 } 17 18 #main h1 { 19 padding: 20px; 20 color: #eee; 21 font-size: 24px; 22 } 23 24 .subgraph h3 { 25 text-transform: capitalize; 26 } 27 28 .subgraph { 29 padding: 20px; 30 margin: 20px; 31 border-radius: 10px; 32 background-color: #fff; 33 } 34 35 .subgraph table { 36 border-collapse: collapse; 37 border-spacing: 0; 38 } 39 40 .subgraph thead { 41 background-color: rgb(61, 65, 77); 42 color: white; 43 text-transform: capitalize; 44 } 45 46 .subgraph tbody tr:nth-child(odd) { 47 background-color: #f2f2f2; 48 } 49 50 .subgraph tbody tr:hover { 51 background-color: #d8d8d8; 52 } 53 54 .subgraph td { 55 border: 1px solid #ddd; 56 padding: 8px; 57 } 58 59 .subgraph select { 60 font-weight: bold; 61 text-transform: uppercase; 62 font-size: 18px; 63 color: black; 64 } 65 66 .subgraph svg { 67 background: white; 68 border: 1px solid #ccc; 69 } 70 71 .subgraph .edges line { 72 stroke: #333; 73 } 74 75 .subgraph .nodes text { 76 color: black; 77 pointer-events: none; 78 font-family: sans-serif; 79 font-size: 11px; 80 } 81 82 #sidebar { 83 height: 100%; 84 width: 38%; 85 position: fixed; 86 z-index: 1; 87 top: 0; 88 right: 0; 89 background-color: #eee; 90 overflow-x: hidden; 91 transition: 0.5s; 92 border-left: 1px solid #ccc; 93 } 94 95 #sidebar #sidebar-main { 96 padding: 50px; 97 } 98 99 #sidebar h1 { 100 margin-top: 6px; 101 margin-bottom: 24px; 102 font-weight: bold; 103 font-size: 18px; 104 text-transform: uppercase; 105 } 106 107 #sidebar .subtitle { 108 margin-bottom: 6px; 109 border-bottom: 1px solid #ccc; 110 padding-bottom: 4px; 111 font-weight: bold; 112 font-size: 12px; 113 text-transform: uppercase; 114 color: #555; 115 } 116 117 #sidebar .property { 118 display: block; 119 margin-bottom: 16px; 120 } 121 122 #sidebar .property_title { 123 float: left; 124 width: 80px; 125 margin-top: 0; 126 padding-top: 10px; 127 font-weight: bold; 128 font-size: 12px; 129 text-transform: uppercase; 130 color: #555; 131 } 132 133 #sidebar .property_text { 134 margin-top: 8px; 135 margin-left: 100px; 136 border: 1px solid #ccc; 137 border-radius: 2px; 138 padding: 8px; 139 font-size: 14px; 140 background-color: #fff; 141 } 142 143 #sidebar .closebtn { 144 position: absolute; 145 top: 0; 146 right: 25px; 147 font-size: 36px; 148 margin-left: 50px; 149 text-decoration: none; 150 color: #555; 151 } 152 153 .tooltip { 154 color: blue; 155 } 156 157 .tooltip .tooltipcontent { 158 visibility: hidden; 159 color: black; 160 background-color: #eee; 161 margin-top: 18px; 162 padding: 5px; 163 border: 1px solid #ccc; 164 border-radius: 4px; 165 position: absolute; 166 z-index: 1; 167 } 168 169 .tooltip:hover .tooltipcontent { 170 visibility: visible; 171 } 172 </style> 173 <link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet" /> 174 <script src="https://d3js.org/d3.v4.min.js"></script> 175 <script> 176 graphs = $graph_dump; 177 </script> 178</head> 179 180<body> 181 <div id="main"> 182 <h1>$spec_name</h1> 183 <div class="subgraph" id="main-subgraph"> 184 <label for="main-selector">Choose a subgraph: </label> 185 <select id="main-selector" onchange="renderSubgraph(this.value)"></select> 186 <div id="main-tables"></div> 187 <h3>Visual Graph</h3> 188 <svg id="subgraph-svg" width="100%" height="720"></svg> 189 </div> 190 </div> 191 192 <div id="sidebar"> 193 <div id="sidebar-main"> 194 </div> 195 </div> 196 197 <script> 198 // Render the sidebar view of a given node object. 199 // The node must have "name" and "group" fields available. 200 function renderSidebar(node) { 201 var sidebar = document.getElementById("sidebar-main"); 202 sidebar.innerHTML = ""; 203 if (node == null) return; 204 205 // Sidebar subtitle -- text taken from node.group. 206 var subtitle = document.createElement("p"); 207 subtitle.classList.add("subtitle"); 208 subtitle.innerHTML = node.group; 209 sidebar.appendChild(subtitle); 210 211 // Sidebar title -- text taken from node.name. 212 var title = document.createElement("h1"); 213 title.innerHTML = node.name; 214 sidebar.appendChild(title); 215 216 // List all the other fields in sidebar. 217 var ignoredFields = ["name", "group"]; 218 for (var property in node) { 219 if (ignoredFields.includes(property)) continue; 220 221 var propertyTitle = document.createElement("h2"); 222 propertyTitle.classList.add("property_title"); 223 propertyTitle.innerHTML = property; 224 225 var propertyText = document.createElement("p"); 226 propertyText.classList.add("property_text"); 227 propertyText.innerHTML = node[property]; 228 229 var propertyDiv = document.createElement("div"); 230 propertyDiv.classList.add("property"); 231 propertyDiv.appendChild(propertyTitle); 232 propertyDiv.appendChild(propertyText); 233 sidebar.appendChild(propertyDiv); 234 } 235 } 236 237 // Render the SVG DAG visualization, from TFLite graph visualizer. 238 // https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tools/visualize.py 239 // 240 // The node coordiates are pre-calculated from the python visualizer. 241 function renderSvg(subgraph) { 242 var data = graphs[subgraph]["subgraph"]; 243 var svg = d3.select("#subgraph-svg"); 244 svg.selectAll("*").remove(); 245 var width = svg.attr("width"); 246 var height = svg.attr("height"); 247 // Make the graph scrollable. 248 svg = svg.call(d3.zoom().on("zoom", function () { 249 svg.attr("transform", d3.event.transform); 250 })).append("g"); 251 var color = d3.scaleOrdinal(d3.schemeDark2); 252 var simulation = d3.forceSimulation() 253 .force("link", d3.forceLink().id(function (d) { return d.id; })) 254 .force("charge", d3.forceManyBody()) 255 .force("center", d3.forceCenter(0.5 * width, 0.5 * height)); 256 var edge = svg.append("g").attr("class", "edges").selectAll("line") 257 .data(data.edges).enter().append("path").attr("stroke", "black").attr("fill", "none") 258 // Make the node group 259 var node = svg.selectAll(".nodes") 260 .data(data.nodes) 261 .enter().append("g") 262 .attr("x", function (d) { return d.x }) 263 .attr("y", function (d) { return d.y }) 264 .attr("transform", function (d) { 265 return "translate( " + d.x + ", " + d.y + ")" 266 }) 267 .attr("class", "nodes") 268 .call(d3.drag() 269 .on("start", function (d) { 270 if (!d3.event.active) simulation.alphaTarget(1.0).restart(); 271 d.fx = d.x; d.fy = d.y; 272 }) 273 .on("drag", function (d) { 274 d.fx = d3.event.x; d.fy = d3.event.y; 275 }) 276 .on("end", function (d) { 277 if (!d3.event.active) simulation.alphaTarget(0); 278 d.fx = d.fy = null; 279 })); 280 // Within the group, draw a box for the node position and text 281 // on the side. 282 var node_width = 150; 283 var node_height = 30; 284 node.append("rect") 285 .attr("r", "5px") 286 .attr("width", function (d) { return d.group == 1 ? node_width : node_width + 50; }) 287 .attr("height", node_height) 288 .attr("rx", function (d) { return d.group == 1 ? 1 : 10; }) 289 .attr("stroke", "#000000") 290 .attr("fill", function (d) { return d.group == 1 ? "#dddddd" : "#000000"; }) 291 .attr("onclick", function (d) { 292 return "renderSidebar(graphs." + subgraph + ".details." + 293 (d.group == 1 ? "operands" : "operations") + "[" + 294 d.index.toString() + "])"; 295 }); 296 node.append("text") 297 .text(function (d) { return d.name; }) 298 .attr("x", 5) 299 .attr("y", 20) 300 .attr("fill", function (d) { return d.group == 1 ? "#000000" : "#eeeeee"; }) 301 // Setup force parameters and update position callback 302 var node = svg.selectAll(".nodes") 303 .data(data.nodes); 304 // Bind the links 305 var name_to_g = {} 306 node.each(function (data, index, nodes) { 307 name_to_g[data.id] = this; 308 }); 309 function proc(w, t) { 310 return parseInt(w.getAttribute(t)); 311 } 312 edge.attr("d", function (d) { 313 function lerp(t, a, b) { 314 return (1.0 - t) * a + t * b; 315 } 316 var x1 = proc(name_to_g[d.source], "x") + node_width / 2; 317 var y1 = proc(name_to_g[d.source], "y") + node_height; 318 var x2 = proc(name_to_g[d.target], "x") + node_width / 2; 319 var y2 = proc(name_to_g[d.target], "y"); 320 var s = "M " + x1 + " " + y1 321 + " C " + x1 + " " + lerp(.5, y1, y2) 322 + " " + x2 + " " + lerp(.5, y1, y2) 323 + " " + x2 + " " + y2 324 return s; 325 }); 326 } 327 328 // Open a new window and present the full text data. 329 function showFullData(data) { 330 window.open().document.write(data); 331 } 332 333 // Renders a single table. 334 function renderTable(title, data, headers) { 335 var parent = document.getElementById("main-tables"); 336 337 // Create heading. 338 var heading = document.createElement("h3"); 339 heading.innerHTML = title; 340 parent.appendChild(heading); 341 342 // Filter out headers that do not appear in any data element. 343 headers = headers.filter(function (key) { 344 return data.some(function (elem) { return key in elem; }); 345 }); 346 347 // Render the table headers. 348 var table = document.createElement("table"); 349 let header = table.createTHead().insertRow(); 350 for (let key of headers) { header.insertCell().innerHTML = key; } 351 352 // Render the table body. 353 // Since the "data" field could be very large, we omit the full content and 354 // append a "View Full" button to the end. 355 var omittableFields = ["data"]; 356 let body = table.createTBody(); 357 for (const [index, elem] of data.entries()) { 358 let row = body.insertRow(); 359 row.id = "details-" + title.toLowerCase() + "-" + index.toString(); 360 361 for (let key of headers) { 362 var cell = row.insertCell(); 363 var data = key in elem ? elem[key] : "-"; 364 if (omittableFields.includes(key) && data.length > 100) { 365 // If the data exceeds the length limit, only print the first 80 and 366 // the last 20 characters. 367 data = data.substring(0, 80) + " ... " + 368 data.substring(data.length - 20, data.length) + " "; 369 cell.innerHTML = data; 370 371 // Append a "View Full" button to the end. 372 var href = document.createElement("a"); 373 href.innerHTML = "View Full"; 374 href.href = "javascript:void(0)"; 375 href.onclick = function () { showFullData(elem[key]); }; 376 cell.appendChild(href); 377 } else { 378 cell.innerHTML = data; 379 } 380 } 381 } 382 parent.appendChild(table); 383 } 384 385 function renderTables(subgraph) { 386 document.getElementById("main-tables").innerHTML = ""; 387 renderTable("Configurations", graphs[subgraph].details.configurations, [ 388 "relaxed", 389 "use shared memory", 390 "expect failure" 391 ]); 392 renderTable("Operands", graphs[subgraph].details.operands, [ 393 "index", 394 "name", 395 "type", 396 "dimensions", 397 "scale", 398 "zero point", 399 "channel dim", 400 "lifetime", 401 "data" 402 ]); 403 renderTable("Operations", graphs[subgraph].details.operations, [ 404 "index", 405 "opcode", 406 "inputs", 407 "outputs" 408 ]); 409 } 410 411 // Re-render all the information related to a subgraph. 412 // Invoked everytime when the main-selector changes. 413 function renderSubgraph(subgraph) { 414 renderTables(subgraph); 415 renderSvg(subgraph); 416 renderSidebar(null); // Clear sidebar. 417 } 418 419 // Renders the main-selector and the first subgraph choice in the main-selector. 420 // Only invoked once when the page gets loaded the first time. 421 function renderMain() { 422 var selector = document.getElementById("main-selector"); 423 var first = true; 424 for (var subgraph in graphs) { 425 var option = document.createElement("option"); 426 option.value = subgraph; 427 option.text = subgraph; 428 selector.appendChild(option); 429 if (first) { 430 first = false; 431 renderSubgraph(subgraph); 432 } 433 } 434 } 435 renderMain(); 436 </script> 437</body> 438</html> 439