mirror of
https://github.com/tinygrad/tinygrad.git
synced 2026-08-29 13:36:07 +00:00
viz: add LLVM machine code analysis (#11421)
* start * works everywhere * add viz api * utilization table * reg pressure ui * use llvm-mca * llvm-mca ui * work * cleanup * cycle through, defaults are enough * x86 pending * x86 nops * get mcpu/mtriple from autogen * cleanup server diff * move parser to python * normalize to pct of max * segments legend * imports * also monospace * max comes from the total per instruction * base on the value
This commit is contained in:
+36
-2
@@ -239,6 +239,8 @@
|
||||
padding: 0 8px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100vh;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.raw-text code {
|
||||
max-height: none !important;
|
||||
@@ -247,8 +249,6 @@
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background-color: #1a1b26;
|
||||
color: #f0f0f5;
|
||||
font-size: 0.95em;
|
||||
@@ -269,6 +269,40 @@
|
||||
tr.main-row > td, tr.sub-row > td {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
tr.code-row > td:first-child {
|
||||
font-family: monospace;
|
||||
}
|
||||
td.pct-row > div {
|
||||
height: 12px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
td.pct-row > div > div {
|
||||
height: 100%;
|
||||
}
|
||||
thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background-color: #20222e;
|
||||
}
|
||||
thead th {
|
||||
text-align: left;
|
||||
padding: 10px 12px;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid #3a3d52;
|
||||
font-size: 0.95em;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
.legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.legend > div {
|
||||
width: 0.95em;
|
||||
height: 0.95em;
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -114,6 +114,7 @@ const devColors = {"TINY":["rgb(27 87 69)", "rgb(53 79 82)", "rgb(53 79 82)", "r
|
||||
const bufColors = ["#3A57B7","#5066C1","#6277CD","#7488D8","#8A9BE3","#A3B4F2"];
|
||||
|
||||
const lighten = (rgb, depth, step=0.08) => rgb.replace(/\d+/g, n => Math.round(parseInt(n)+(255-parseInt(n)) * Math.min(1, depth*step)));
|
||||
const segmentColors = ["#ff8080", "#F4A261", "#C8F9D4", "#8D99AE", "#F4A261", "#ffffa2", "#ffffc0", "#87CEEB"];
|
||||
|
||||
var profileRet, focusedDevice, canvasZoom, zoomLevel = d3.zoomIdentity;
|
||||
async function renderProfiler() {
|
||||
@@ -377,11 +378,16 @@ function codeBlock(st, language, { loc, wrap }={}) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function appendRow(table, name, value, unit, cls) {
|
||||
function appendTd(tr, value, unit=null) {
|
||||
const fmt = (typeof value === "number" && !Number.isInteger(value)) ? value.toFixed(2) : value;
|
||||
tr.appendChild(document.createElement("td")).innerText = unit == "us" ? formatTime(value) : fmt+(unit ?? "");
|
||||
}
|
||||
|
||||
function appendRow(table, name, value, unit=null, cls="main-row") {
|
||||
const tr = table.appendChild(document.createElement("tr"));
|
||||
tr.className = cls;
|
||||
tr.appendChild(document.createElement("td")).innerText = name;
|
||||
tr.appendChild(document.createElement("td")).innerText = unit === "us" ? formatTime(value) : value.toFixed(2)+(unit != null ? " "+unit : "%");
|
||||
appendTd(tr, value, unit);
|
||||
return tr;
|
||||
}
|
||||
|
||||
@@ -495,10 +501,44 @@ async function main() {
|
||||
if (ckey.startsWith("/disasm")) {
|
||||
if (!(ckey in cache)) cache[ckey] = ret = await (await fetch(ckey)).json();
|
||||
displayGraph("profiler");
|
||||
document.querySelector(".metadata").innerHTML = "";
|
||||
const root = document.createElement("div");
|
||||
root.className = "raw-text";
|
||||
root.appendChild(codeBlock(ret.src, "x86asm"));
|
||||
const metadata = document.querySelector(".metadata");
|
||||
metadata.innerHTML = "";
|
||||
// detailed assembly view
|
||||
if (ret.cols != null) {
|
||||
const asm = root.appendChild(document.createElement("table"));
|
||||
const thead = asm.appendChild(document.createElement("thead"));
|
||||
const usage = {};
|
||||
for (const c of ret.cols) thead.appendChild(document.createElement("th")).innerText = c;
|
||||
for (const r of ret.rows) {
|
||||
const tr = asm.appendChild(document.createElement("tr"));
|
||||
tr.className = "main-row code-row";
|
||||
for (const d of Object.values(r.data)) appendTd(tr, d);
|
||||
const segmentsTd = tr.appendChild(document.createElement("td"));
|
||||
segmentsTd.className = "pct-row";
|
||||
const usageBar = segmentsTd.appendChild(document.createElement("div"));
|
||||
for (const [k, {width, value}] of Object.entries(r.segs)) {
|
||||
const seg = usageBar.appendChild(document.createElement("div"));
|
||||
seg.style.width = width+"%";
|
||||
seg.title = `${ret.segments[k]} ${value}`;
|
||||
seg.style.background = segmentColors[parseInt(k)%segmentColors.length];
|
||||
if (!(k in usage)) usage[k] = 0;
|
||||
usage[k] += value;
|
||||
}
|
||||
}
|
||||
const summary = metadata.appendChild(document.createElement("table"));
|
||||
for (const [i,s] of ret.segments.entries()) {
|
||||
const tr = summary.appendChild(document.createElement("tr"));
|
||||
tr.className = "main-row";
|
||||
const td = tr.appendChild(document.createElement("td"));
|
||||
const div = td.appendChild(document.createElement("div"));
|
||||
div.className = "legend";
|
||||
div.appendChild(document.createElement("div")).style.background = segmentColors[i%segmentColors.length];
|
||||
div.appendChild(document.createElement("p")).textContent = s;
|
||||
appendTd(tr, usage[i] ?? 0);
|
||||
}
|
||||
} else root.appendChild(codeBlock(ret.src, "x86asm"));
|
||||
return document.querySelector(".profiler").replaceChildren(root);
|
||||
}
|
||||
// ** UOp view (default)
|
||||
|
||||
+24
-3
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
import multiprocessing, pickle, difflib, os, threading, json, time, sys, webbrowser, socket, argparse, socketserver, functools, codecs, io
|
||||
import subprocess, ctypes
|
||||
from contextlib import redirect_stdout
|
||||
from decimal import Decimal
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
@@ -187,9 +188,29 @@ def get_runtime_stats(key) -> list[dict]:
|
||||
|
||||
def get_disassembly(ctx:list[str]):
|
||||
if not isinstance(prg:=contexts[0][int(ctx[0])].ret, ProgramSpec): return
|
||||
lib = Device[prg.device].compiler.compile(prg.src)
|
||||
with redirect_stdout(buf:=io.StringIO()): Device[prg.device].compiler.disassemble(lib)
|
||||
return json.dumps({"src":buf.getvalue()}).encode()
|
||||
lib = (compiler:=Device[prg.device].compiler).compile(prg.src)
|
||||
with redirect_stdout(buf:=io.StringIO()): compiler.disassemble(lib)
|
||||
disasm_str = buf.getvalue()
|
||||
from tinygrad.runtime.ops_llvm import llvm, LLVMCompiler
|
||||
if isinstance(compiler, LLVMCompiler):
|
||||
mtriple = ctypes.string_at(llvm.LLVMGetTargetMachineTriple(tm:=compiler.target_machine)).decode()
|
||||
mcpu = ctypes.string_at(llvm.LLVMGetTargetMachineCPU(tm)).decode()
|
||||
# NOTE: llvm-objdump may contain headers, skip if llvm-mca can't parse those lines
|
||||
data = json.loads(subprocess.check_output(["llvm-mca", f"-mtriple={mtriple}", f"-mcpu={mcpu}", "-skip-unsupported-instructions=parse-failure",
|
||||
"--json", "-"], input=disasm_str.encode()))
|
||||
cr = data["CodeRegions"][0]
|
||||
instrs:list = [{"data":[rep], "segs":{}} for rep in cr["Instructions"]]
|
||||
for i,info in enumerate(cr["InstructionInfoView"]["InstructionList"]): instrs[i]["data"].append(info["Latency"])
|
||||
for d in cr["ResourcePressureView"]["ResourcePressureInfo"]:
|
||||
i, r = d["InstructionIndex"], d["ResourceIndex"]
|
||||
if i>len(instrs)-1: continue
|
||||
instrs[i]["segs"][r] = instrs[i]["segs"].get(r, 0)+d["ResourceUsage"]
|
||||
# rescale segment width to 0-100
|
||||
if instrs:
|
||||
hi = max([sum(ins["segs"].values()) for ins in instrs])
|
||||
for n in instrs: n["segs"] = {k:{"width":v/hi*100, "value":v} for k,v in n["segs"].items()}
|
||||
return json.dumps({"rows":instrs, "cols":["Opcode", "Latency", "HW Resources"], "segments":data["TargetInfo"]["Resources"]}).encode()
|
||||
return json.dumps({"src":disasm_str}).encode()
|
||||
|
||||
# ** HTTP server
|
||||
|
||||
|
||||
Reference in New Issue
Block a user