From 613ac4bec1d2ed609de8d0b5d0dc7381bb58c1cd Mon Sep 17 00:00:00 2001 From: qazal <77887910+Qazalin@users.noreply.github.com> Date: Tue, 1 Sep 2026 16:15:11 +0800 Subject: [PATCH] remove old sqtt stuff from viz (#17880) * rm old server stuff * frontend cleanups --- extra/sqtt/roc.py | 119 ++++++++++++--------------------------- tinygrad/viz/js/index.js | 15 +---- tinygrad/viz/serve.py | 8 --- 3 files changed, 36 insertions(+), 106 deletions(-) diff --git a/extra/sqtt/roc.py b/extra/sqtt/roc.py index dc25f9bedd..1f1df5f8ab 100755 --- a/extra/sqtt/roc.py +++ b/extra/sqtt/roc.py @@ -1,13 +1,11 @@ #!/usr/bin/env python3 import ctypes, pathlib, argparse, pickle, dataclasses, threading, itertools -from decimal import Decimal -from typing import Generator +from typing import Any, Generator from tinygrad.helpers import temp, unwrap, DEBUG from tinygrad.runtime.ops_amd import ProfileSQTTEvent from tinygrad.runtime.autogen import rocprof from tinygrad.renderer.amd.dsl import Inst -from tinygrad.helpers import ProfileEvent, ProfileRangeEvent, ProfilePointEvent -from tinygrad.device import ProfileProgramEvent +from tinygrad.device import ProfileDeviceEvent, ProfileProgramEvent from test.amd.disasm import disasm @dataclasses.dataclass(frozen=True) @@ -39,17 +37,17 @@ class WaveExec(WaveSlot): insts_array = (struct*(len(self.insts)//sz)).from_buffer(self.insts) for inst in insts_array: inst_typ = rocprof.enum_rocprofiler_thread_trace_decoder_inst_category_t.get(inst.category) - yield InstExec(inst_typ, inst.pc.address, inst.stall, inst.duration, inst.time) + yield InstExec(inst_typ or "UNKNOWN", inst.pc.address, inst.stall, inst.duration, inst.time) @dataclasses.dataclass(frozen=True) class OccEvent(WaveSlot): time:int start:int -RunKey = tuple[str, int] +RunKey = tuple[int, int] class _ROCParseCtx: - def __init__(self, sqtt_evs:list[ProfileSQTTEvent], disasms:dict[str, dict[int, Inst]]): + def __init__(self, sqtt_evs:list[ProfileSQTTEvent], disasms:dict[int, dict[int, Inst]]): self.sqtt_evs, self.disasms = iter(sqtt_evs), {k:{k2:(disasm(v2), v2.size()) for k2,v2 in v.items()} for k,v in disasms.items()} self.inst_execs:dict[RunKey, list[WaveExec]] = {} self.occ_events:dict[RunKey, list[OccEvent]] = {} @@ -76,7 +74,7 @@ class _ROCParseCtx: self.inst_execs.setdefault(unwrap(self.active_run), []).append(WaveExec(ev.wave_id, ev.cu, ev.simd, unwrap(self.active_se), ev.begin_time, ev.end_time, insts_blob)) -def decode(sqtt_evs:list[ProfileSQTTEvent], disasms:dict[str, dict[int, Inst]]) -> _ROCParseCtx: +def decode(sqtt_evs:list[ProfileSQTTEvent], disasms:dict[int, dict[int, Inst]]) -> _ROCParseCtx: ROCParseCtx = _ROCParseCtx(sqtt_evs, disasms) @rocprof.rocprof_trace_decoder_se_data_callback_t @@ -129,44 +127,7 @@ def decode(sqtt_evs:list[ProfileSQTTEvent], disasms:dict[str, dict[int, Inst]]) raise exc return ROCParseCtx -def unpack_occ(viz_data, i:int, j:int, key:tuple[str, int], data:list, p:ProfileProgramEvent, target:str) -> dict: - from tinygrad.viz.serve import amd_decode, create_step, row_tuple - steps = viz_data.ctxs[i]["steps"] - if len(steps[j+1:]) > 0: return {"steps":[{k:v for k,v in s.items() if k != "data"} for s in steps[j+1:]]} - base = unwrap(p.base) - disasm:dict[int, Inst] = {addr+base:inst for addr,inst in amd_decode(unwrap(p.lib), target).items()} - rctx = decode(data, {p.tag:disasm}) - cu_events:dict[str, list[ProfileEvent]] = {} - # ** inst traces - wave_insts:dict[str, dict[str, dict]] = {} - inst_units:dict[str, itertools.count] = {} - for w in rctx.inst_execs.get(key, []): - if (u:=w.wave_loc) not in inst_units: inst_units[u] = itertools.count(0) - n = next(inst_units[u]) - if (events:=cu_events.get(w.cu_loc)) is None: cu_events[w.cu_loc] = events = [] - events.append(ProfileRangeEvent(f"SIMD:{w.simd}", loc:=f"INST WAVE:{w.wave_id} N:{n}", Decimal(w.begin_time), Decimal(w.end_time))) - wave_insts.setdefault(w.cu_loc, {})[f"{u} N:{n}"] = {"wave":w, "disasm":disasm, "prg":p, "run_number":n, "loc":loc} - # ** occ traces (only WAVESTART/WAVEEND) - units:dict[str, itertools.count] = {} - wave_start:dict[str, int] = {} - for occ in rctx.occ_events.get(key, []): - if (u:=occ.wave_loc) not in units: units[u] = itertools.count(0) - if u in inst_units: continue - if occ.start: wave_start[u] = occ.time - else: - if (events:=cu_events.get(occ.cu_loc)) is None: cu_events[occ.cu_loc] = events = [] - events.append(ProfileRangeEvent(f"SIMD:{occ.simd}", f"OCC WAVE:{occ.wave_id} N:{next(units[u])}", Decimal(wave_start.pop(u)),Decimal(occ.time))) - # ** split graph by CU - for cu in sorted(cu_events, key=row_tuple): - steps.append(create_step(f"{cu} {len(cu_events[cu])}", ("/cu-sqtt", i, len(steps)), depth=1, - data=[ProfilePointEvent(unit, "start", unit, ts=Decimal(0)) for unit in units]+cu_events[cu])) - for k in sorted(wave_insts.get(cu, []), key=row_tuple): - wd = wave_insts[cu][k] - steps.append(create_step(k.replace(cu, ""), ("/amd-sqtt-insts", i, len(steps)), loc=wd["loc"], depth=2, - data={"fxn":unpack_insts, "args":(wd,)})) - return {"steps":[{k:v for k,v in s.items() if k != "data"} for s in steps[j+1:]]} - -def unpack_insts(viz_data, i:int, j:int, data:dict) -> dict: +def unpack_insts(w:WaveExec, pc_to_inst:dict[int, Inst]) -> dict: columns = ["PC", "Instruction", "Hits", "Cycles", "Stall", "Type"] inst_columns = ["N", "Clk", "Idle", "Dur", "Stall"] # Idle: The total time gap between the completion of previous instruction and the beginning of the current instruction. @@ -176,36 +137,24 @@ def unpack_insts(viz_data, i:int, j:int, data:dict) -> dict: # * Instruction cache miss # Stall: The total number of cycles the hardware pipe couldn't issue an instruction. # Duration: Total latency in cycles, defined as "Stall time + Issue time" for gfx9 or "Stall time + Execute time" for gfx10+. - prev_instr = (w:=data["wave"]).begin_time - pc_to_inst = data["disasm"] + prev_instr = w.begin_time start_pc = None - rows:dict[int, dict] = {} + rows:dict[int, dict[str, Any]] = {} for pc, inst in pc_to_inst.items(): if start_pc is None: start_pc = pc rows[pc] = {"pc":pc-start_pc, "inst":str(inst), "hit_count":0, "dur":0, "stall":0, "type":"", "hits":{"cols":inst_columns, "rows":[]}} for e in w.unpack_insts(): - if not (inst:=rows[e.pc]).get("type"): inst["type"] = str(e.typ).split("_")[-1] - inst["hit_count"] += 1 - inst["dur"] += e.dur - inst["stall"] += e.stall - inst["hits"]["rows"].append((inst["hit_count"]-1, e.time, max(0, e.time-prev_instr), e.dur, e.stall)) + if not (row:=rows[e.pc]).get("type"): row["type"] = str(e.typ).split("_")[-1] + row["hit_count"] += 1 + row["dur"] += e.dur + row["stall"] += e.stall + row["hits"]["rows"].append((row["hit_count"]-1, e.time, max(0, e.time-prev_instr), e.dur, e.stall)) prev_instr = max(prev_instr, e.time + e.dur) - summary = [{"label":"Total Cycles", "value":w.end_time-w.begin_time}, {"label":"SE", "value":w.se}, {"label":"CU", "value":w.cu}, - {"label":"SIMD", "value":w.simd}, {"label":"Wave ID", "value":w.wave_id}, {"label":"Run number", "value":data["run_number"]}] - return {"rows":[tuple(v.values()) for v in rows.values()], "cols":columns, "metadata":[summary],"ref":viz_data.ref_map.get(data["prg"].profile_key)} - -def print_data(data:dict) -> None: - from tabulate import tabulate - # plaintext - if "src" in data: print(data["src"]) - # table format - elif "cols" in data: - print(tabulate([r[:len(data["cols"])] for r in data["rows"]], headers=data["cols"], tablefmt="github")) + return {"rows":[tuple(v.values()) for v in rows.values()], "cols":columns} def main() -> None: - import tinygrad.viz.serve as viz - from tinygrad.uop.ops import RewriteTrace - data = viz.VizData() + from tabulate import tabulate + from tinygrad.viz.serve import amd_decode parser = argparse.ArgumentParser() parser.add_argument('--profile', type=pathlib.Path, metavar="PATH", help='Path to profile (optional file, default: latest profile)', @@ -216,26 +165,28 @@ def main() -> None: with args.profile.open("rb") as f: profile = pickle.load(f) - viz.get_profile(profile, data=data) - # List all kernels if args.kernel is None: - for c in data.ctxs: - print(c["name"]) - for s in c["steps"]: print(" "+s["name"]) + for p in profile: + if isinstance(p, ProfileProgramEvent) and p.device.startswith("AMD"): print(p.name) return None - # Find kernel trace - trace = next((c for c in data.ctxs if c["name"] == f"SQTT {args.kernel}"), None) - if not trace: raise RuntimeError(f"no matching trace for {args.kernel}") - n = 0 - for s in trace["steps"]: - if "PKTS" in s["name"]: continue - print(s["name"]) - ret = viz.get_render(data, s["query"]) - print_data(ret) - n += 1 - if n > args.n: break + prg = next((p for p in profile if isinstance(p, ProfileProgramEvent) and p.name == args.kernel), None) + dev = next((p for p in profile if isinstance(p, ProfileDeviceEvent) and p.device == prg.device), None) + assert prg is not None and dev is not None, "must have program binary and device props" + target = f"gfx{dev.props['gfx_target_version']//1000}" + sqtt = [p for p in profile if isinstance(p, ProfileSQTTEvent) and p.kern == prg.tag] + + pc_to_inst = {addr+prg.base:inst for addr,inst in amd_decode(prg.lib, target).items()} + rctx = decode(sqtt, {prg.tag:pc_to_inst}) + waves = sorted(itertools.chain.from_iterable(rctx.inst_execs.values()), key=lambda w:(w.se, w.cu, w.simd, w.wave_id, w.begin_time)) + if not waves: raise RuntimeError(f"no instruction traces for {args.kernel}") + run_numbers:dict[str, itertools.count] = {} + for w in itertools.islice(waves, args.n): + if w.wave_loc not in run_numbers: run_numbers[w.wave_loc] = itertools.count() + print(f"{w.wave_loc} N:{next(run_numbers[w.wave_loc])} Total Cycles:{w.end_time-w.begin_time}") + table = unpack_insts(w, pc_to_inst) + print(tabulate([r[:len(table["cols"])] for r in table["rows"]], headers=table["cols"], tablefmt="github")) if __name__ == "__main__": main() diff --git a/tinygrad/viz/js/index.js b/tinygrad/viz/js/index.js index 44c0f5dfd9..c78d1ca949 100644 --- a/tinygrad/viz/js/index.js +++ b/tinygrad/viz/js/index.js @@ -230,7 +230,7 @@ const waveColor = (op) => { }; const colorScheme = {TINY:new Map([["Schedule","#1b5745"],["precompile","#1d2e62"],["compile","#63b0cd"],["DEFAULT","#354f52"]]), DEFAULT:["#2b2e39", "#2c2f3a", "#31343f", "#323544", "#2d303a", "#2e313c", "#343746", "#353847", "#3c4050", "#404459", "#444862", "#4a4e65"], - BUFFER:["#342483", "#3E2E94", "#4938A4", "#5442B4", "#5E4CC2", "#674FCA"], SIMD:new Map([["OCC", "#101725"], ["INST", "#0A2042"]]), + BUFFER:["#342483", "#3E2E94", "#4938A4", "#5442B4", "#5E4CC2", "#674FCA"], GPC:new Map([["NONE","#1a7a2e"],["MEMORY_DEPENDENCY","#8b1a00"],["EXEC_DEPENDENCY","#006b6b"],["INST_FETCH","#7a7a00"],["SYNC","#6b006b"], ["PIPE_BUSY","#7a4a00"],["MEMORY_THROTTLE","#5c0000"],["CONSTANT_MEMORY","#1a3d7a"],["NOT_SELECTED","#2e2e3a"],["OTHER","#4a4a55"], ["SLEEPING","#1a1a2a"],["DEFAULT","#3a3a45"]]), WAVE:waveColor, VMEMEXEC:waveColor, ALUEXEC:waveColor} @@ -999,15 +999,6 @@ async function main() { } if (!ckey.startsWith("/graph")) { if (!(ckey in cache)) cache[ckey] = ret = await fetchValue(ckey); - if (ret.steps?.length > 0) { - const el = select(state.currentCtx, state.currentStep); - if (el.step.querySelectorAll("ul").length === ret.steps.length) return; - // re render the list with new items - ctx.steps.push(...ret.steps); - while (el.ctx.children.length > 1) el.ctx.children[1].remove(); - appendSteps(el.ctx, state.currentCtx, ctx.steps); - return setState({ currentStep:state.currentStep+1, expandSteps:true }); - } // timeline with cycles on the x axis if (ret instanceof ArrayBuffer) { const pkts = step.query.includes("sqtt"); @@ -1050,10 +1041,6 @@ async function main() { } return table; } - if (ret.ref != null) { - const disasmIdx = ctxs[ret.ref+1].steps.findIndex(s => s.name === "View Disassembly") - metadata.appendChild(d3.create("a").text("View Disassembly").on("click", () => switchCtx(ret.ref, disasmIdx)).node()); - } if (ret.cols != null) renderTable(root, ret); else if (ret.src != null) root.append(() => codeBlock(ret.src, ret.lang)); return document.querySelector("#custom").replaceChildren(root.node()); diff --git a/tinygrad/viz/serve.py b/tinygrad/viz/serve.py index 7d421e1a5b..98b87783cd 100755 --- a/tinygrad/viz/serve.py +++ b/tinygrad/viz/serve.py @@ -359,11 +359,6 @@ def load_amd_counters(data:VizData, profile:list) -> None: if (sqtt:=v.get("ProfileSQTTEvent")): for e in sqtt: if e.itrace: steps.append(create_step(f"SE:{e.se} PKTS", (f"/sqtt-{e.se}",len(data.ctxs),len(steps)), data=(e.blob,prg_events[k].lib,arch))) - try: - with Context(DEBUG=0): from extra.sqtt.roc import unpack_occ - steps.append(create_step("OCC", ("/amd-sqtt-occ", len(data.ctxs), len(steps)), - data={"fxn":unpack_occ, "args":((k, tag), sqtt, prg_events[k], arch)})) - except Exception: pass data.ctxs.append({"name":f"SQTT {name}"+(f" n{run_number[k]}" if run_number[k] > 1 else ""), "steps":steps}) wave_colors = {"WMMA": "#1F7857", **{x:"#ffffc0" for x in ["VALU", "VINTERP"]}, "SALU": "#cef263", "SMEM": "#ffc0c0", "STORE": "#4fa3cc", @@ -650,9 +645,6 @@ def get_render(viz_data:VizData, query:str, **kwargs) -> dict: ret = {"value":events, "content_type":"application/octet-stream"} else: ret = {"src":"No SQTT trace on this SE."} return ret - # viewers for the amd decoder in extra - if fmt.startswith("amd-sqtt"): return data["fxn"](viz_data, i, j, *data["args"]) - if fmt == "cu-sqtt": return {"value":get_profile(viz_data, data, sort_fn=row_tuple), "content_type":"application/octet-stream"} if fmt == "prg-pma-pkts": ret = {} with soft_err(lambda err:ret.update(err)):