| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>$spec_name</title> |
| <style> |
| body { |
| font-family: "Roboto", sans-serif; |
| margin: 0; |
| height: 100%; |
| background-color: rgb(61, 65, 77); |
| } |
| |
| #main { |
| width: 62%; |
| transition: 0.5s; |
| } |
| |
| #main h1 { |
| padding: 20px; |
| color: #eee; |
| font-size: 24px; |
| } |
| |
| .subgraph h3 { |
| text-transform: capitalize; |
| } |
| |
| .subgraph { |
| padding: 20px; |
| margin: 20px; |
| border-radius: 10px; |
| background-color: #fff; |
| } |
| |
| .subgraph table { |
| border-collapse: collapse; |
| border-spacing: 0; |
| } |
| |
| .subgraph thead { |
| background-color: rgb(61, 65, 77); |
| color: white; |
| text-transform: capitalize; |
| } |
| |
| .subgraph tbody tr:nth-child(odd) { |
| background-color: #f2f2f2; |
| } |
| |
| .subgraph tbody tr:hover { |
| background-color: #d8d8d8; |
| } |
| |
| .subgraph td { |
| border: 1px solid #ddd; |
| padding: 8px; |
| } |
| |
| .subgraph select { |
| font-weight: bold; |
| text-transform: uppercase; |
| font-size: 18px; |
| color: black; |
| } |
| |
| .subgraph svg { |
| background: white; |
| border: 1px solid #ccc; |
| } |
| |
| .subgraph .edges line { |
| stroke: #333; |
| } |
| |
| .subgraph .nodes text { |
| color: black; |
| pointer-events: none; |
| font-family: sans-serif; |
| font-size: 11px; |
| } |
| |
| #sidebar { |
| height: 100%; |
| width: 38%; |
| position: fixed; |
| z-index: 1; |
| top: 0; |
| right: 0; |
| background-color: #eee; |
| overflow-x: hidden; |
| transition: 0.5s; |
| border-left: 1px solid #ccc; |
| } |
| |
| #sidebar #sidebar-main { |
| padding: 50px; |
| } |
| |
| #sidebar h1 { |
| margin-top: 6px; |
| margin-bottom: 24px; |
| font-weight: bold; |
| font-size: 18px; |
| text-transform: uppercase; |
| } |
| |
| #sidebar .subtitle { |
| margin-bottom: 6px; |
| border-bottom: 1px solid #ccc; |
| padding-bottom: 4px; |
| font-weight: bold; |
| font-size: 12px; |
| text-transform: uppercase; |
| color: #555; |
| } |
| |
| #sidebar .property { |
| display: block; |
| margin-bottom: 16px; |
| } |
| |
| #sidebar .property_title { |
| float: left; |
| width: 80px; |
| margin-top: 0; |
| padding-top: 10px; |
| font-weight: bold; |
| font-size: 12px; |
| text-transform: uppercase; |
| color: #555; |
| } |
| |
| #sidebar .property_text { |
| margin-top: 8px; |
| margin-left: 100px; |
| border: 1px solid #ccc; |
| border-radius: 2px; |
| padding: 8px; |
| font-size: 14px; |
| background-color: #fff; |
| } |
| |
| #sidebar .closebtn { |
| position: absolute; |
| top: 0; |
| right: 25px; |
| font-size: 36px; |
| margin-left: 50px; |
| text-decoration: none; |
| color: #555; |
| } |
| |
| .tooltip { |
| color: blue; |
| } |
| |
| .tooltip .tooltipcontent { |
| visibility: hidden; |
| color: black; |
| background-color: #eee; |
| margin-top: 18px; |
| padding: 5px; |
| border: 1px solid #ccc; |
| border-radius: 4px; |
| position: absolute; |
| z-index: 1; |
| } |
| |
| .tooltip:hover .tooltipcontent { |
| visibility: visible; |
| } |
| </style> |
| <link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet" /> |
| <script src="https://d3js.org/d3.v4.min.js"></script> |
| <script> |
| graphs = $graph_dump; |
| </script> |
| </head> |
| |
| <body> |
| <div id="main"> |
| <h1>$spec_name</h1> |
| <div class="subgraph" id="main-subgraph"> |
| <label for="main-selector">Choose a subgraph: </label> |
| <select id="main-selector" onchange="renderSubgraph(this.value)"></select> |
| <div id="main-tables"></div> |
| <h3>Visual Graph</h3> |
| <svg id="subgraph-svg" width="100%" height="720"></svg> |
| </div> |
| </div> |
| |
| <div id="sidebar"> |
| <div id="sidebar-main"> |
| </div> |
| </div> |
| |
| <script> |
| // Render the sidebar view of a given node object. |
| // The node must have "name" and "group" fields available. |
| function renderSidebar(node) { |
| var sidebar = document.getElementById("sidebar-main"); |
| sidebar.innerHTML = ""; |
| if (node == null) return; |
| |
| // Sidebar subtitle -- text taken from node.group. |
| var subtitle = document.createElement("p"); |
| subtitle.classList.add("subtitle"); |
| subtitle.innerHTML = node.group; |
| sidebar.appendChild(subtitle); |
| |
| // Sidebar title -- text taken from node.name. |
| var title = document.createElement("h1"); |
| title.innerHTML = node.name; |
| sidebar.appendChild(title); |
| |
| // List all the other fields in sidebar. |
| var ignoredFields = ["name", "group"]; |
| for (var property in node) { |
| if (ignoredFields.includes(property)) continue; |
| |
| var propertyTitle = document.createElement("h2"); |
| propertyTitle.classList.add("property_title"); |
| propertyTitle.innerHTML = property; |
| |
| var propertyText = document.createElement("p"); |
| propertyText.classList.add("property_text"); |
| propertyText.innerHTML = node[property]; |
| |
| var propertyDiv = document.createElement("div"); |
| propertyDiv.classList.add("property"); |
| propertyDiv.appendChild(propertyTitle); |
| propertyDiv.appendChild(propertyText); |
| sidebar.appendChild(propertyDiv); |
| } |
| } |
| |
| // Render the SVG DAG visualization, from TFLite graph visualizer. |
| // https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/tools/visualize.py |
| // |
| // The node coordiates are pre-calculated from the python visualizer. |
| function renderSvg(subgraph) { |
| var data = graphs[subgraph]["subgraph"]; |
| var svg = d3.select("#subgraph-svg"); |
| svg.selectAll("*").remove(); |
| var width = svg.attr("width"); |
| var height = svg.attr("height"); |
| // Make the graph scrollable. |
| svg = svg.call(d3.zoom().on("zoom", function () { |
| svg.attr("transform", d3.event.transform); |
| })).append("g"); |
| var color = d3.scaleOrdinal(d3.schemeDark2); |
| var simulation = d3.forceSimulation() |
| .force("link", d3.forceLink().id(function (d) { return d.id; })) |
| .force("charge", d3.forceManyBody()) |
| .force("center", d3.forceCenter(0.5 * width, 0.5 * height)); |
| var edge = svg.append("g").attr("class", "edges").selectAll("line") |
| .data(data.edges).enter().append("path").attr("stroke", "black").attr("fill", "none") |
| // Make the node group |
| var node = svg.selectAll(".nodes") |
| .data(data.nodes) |
| .enter().append("g") |
| .attr("x", function (d) { return d.x }) |
| .attr("y", function (d) { return d.y }) |
| .attr("transform", function (d) { |
| return "translate( " + d.x + ", " + d.y + ")" |
| }) |
| .attr("class", "nodes") |
| .call(d3.drag() |
| .on("start", function (d) { |
| if (!d3.event.active) simulation.alphaTarget(1.0).restart(); |
| d.fx = d.x; d.fy = d.y; |
| }) |
| .on("drag", function (d) { |
| d.fx = d3.event.x; d.fy = d3.event.y; |
| }) |
| .on("end", function (d) { |
| if (!d3.event.active) simulation.alphaTarget(0); |
| d.fx = d.fy = null; |
| })); |
| // Within the group, draw a box for the node position and text |
| // on the side. |
| var node_width = 150; |
| var node_height = 30; |
| node.append("rect") |
| .attr("r", "5px") |
| .attr("width", function (d) { return d.group == 1 ? node_width : node_width + 50; }) |
| .attr("height", node_height) |
| .attr("rx", function (d) { return d.group == 1 ? 1 : 10; }) |
| .attr("stroke", "#000000") |
| .attr("fill", function (d) { return d.group == 1 ? "#dddddd" : "#000000"; }) |
| .attr("onclick", function (d) { |
| return "renderSidebar(graphs." + subgraph + ".details." + |
| (d.group == 1 ? "operands" : "operations") + "[" + |
| d.index.toString() + "])"; |
| }); |
| node.append("text") |
| .text(function (d) { return d.name; }) |
| .attr("x", 5) |
| .attr("y", 20) |
| .attr("fill", function (d) { return d.group == 1 ? "#000000" : "#eeeeee"; }) |
| // Setup force parameters and update position callback |
| var node = svg.selectAll(".nodes") |
| .data(data.nodes); |
| // Bind the links |
| var name_to_g = {} |
| node.each(function (data, index, nodes) { |
| name_to_g[data.id] = this; |
| }); |
| function proc(w, t) { |
| return parseInt(w.getAttribute(t)); |
| } |
| edge.attr("d", function (d) { |
| function lerp(t, a, b) { |
| return (1.0 - t) * a + t * b; |
| } |
| var x1 = proc(name_to_g[d.source], "x") + node_width / 2; |
| var y1 = proc(name_to_g[d.source], "y") + node_height; |
| var x2 = proc(name_to_g[d.target], "x") + node_width / 2; |
| var y2 = proc(name_to_g[d.target], "y"); |
| var s = "M " + x1 + " " + y1 |
| + " C " + x1 + " " + lerp(.5, y1, y2) |
| + " " + x2 + " " + lerp(.5, y1, y2) |
| + " " + x2 + " " + y2 |
| return s; |
| }); |
| } |
| |
| // Open a new window and present the full text data. |
| function showFullData(data) { |
| window.open().document.write(data); |
| } |
| |
| // Renders a single table. |
| function renderTable(title, data, headers) { |
| var parent = document.getElementById("main-tables"); |
| |
| // Create heading. |
| var heading = document.createElement("h3"); |
| heading.innerHTML = title; |
| parent.appendChild(heading); |
| |
| // Filter out headers that do not appear in any data element. |
| headers = headers.filter(function (key) { |
| return data.some(function (elem) { return key in elem; }); |
| }); |
| |
| // Render the table headers. |
| var table = document.createElement("table"); |
| let header = table.createTHead().insertRow(); |
| for (let key of headers) { header.insertCell().innerHTML = key; } |
| |
| // Render the table body. |
| // Since the "data" field could be very large, we omit the full content and |
| // append a "View Full" button to the end. |
| var omittableFields = ["data"]; |
| let body = table.createTBody(); |
| for (const [index, elem] of data.entries()) { |
| let row = body.insertRow(); |
| row.id = "details-" + title.toLowerCase() + "-" + index.toString(); |
| |
| for (let key of headers) { |
| var cell = row.insertCell(); |
| var data = key in elem ? elem[key] : "-"; |
| if (omittableFields.includes(key) && data.length > 100) { |
| // If the data exceeds the length limit, only print the first 80 and |
| // the last 20 characters. |
| data = data.substring(0, 80) + " ... " + |
| data.substring(data.length - 20, data.length) + " "; |
| cell.innerHTML = data; |
| |
| // Append a "View Full" button to the end. |
| var href = document.createElement("a"); |
| href.innerHTML = "View Full"; |
| href.href = "javascript:void(0)"; |
| href.onclick = function () { showFullData(elem[key]); }; |
| cell.appendChild(href); |
| } else { |
| cell.innerHTML = data; |
| } |
| } |
| } |
| parent.appendChild(table); |
| } |
| |
| function renderTables(subgraph) { |
| document.getElementById("main-tables").innerHTML = ""; |
| renderTable("Configurations", graphs[subgraph].details.configurations, [ |
| "relaxed", |
| "use shared memory", |
| "expect failure" |
| ]); |
| renderTable("Operands", graphs[subgraph].details.operands, [ |
| "index", |
| "name", |
| "type", |
| "dimensions", |
| "scale", |
| "zero point", |
| "channel dim", |
| "lifetime", |
| "data" |
| ]); |
| renderTable("Operations", graphs[subgraph].details.operations, [ |
| "index", |
| "opcode", |
| "inputs", |
| "outputs" |
| ]); |
| } |
| |
| // Re-render all the information related to a subgraph. |
| // Invoked everytime when the main-selector changes. |
| function renderSubgraph(subgraph) { |
| renderTables(subgraph); |
| renderSvg(subgraph); |
| renderSidebar(null); // Clear sidebar. |
| } |
| |
| // Renders the main-selector and the first subgraph choice in the main-selector. |
| // Only invoked once when the page gets loaded the first time. |
| function renderMain() { |
| var selector = document.getElementById("main-selector"); |
| var first = true; |
| for (var subgraph in graphs) { |
| var option = document.createElement("option"); |
| option.value = subgraph; |
| option.text = subgraph; |
| selector.appendChild(option); |
| if (first) { |
| first = false; |
| renderSubgraph(subgraph); |
| } |
| } |
| } |
| renderMain(); |
| </script> |
| </body> |
| </html> |