Download code/scripts/profile_ops.py from changh95/bevformer-p150: direct link, hf CLI and curl.
- Browser
- Download file 12.9 kB
-
https://huggingface.co/changh95/bevformer-p150/resolve/main/code/scripts/profile_ops.py
- Command line
-
hf download hf://changh95/bevformer-p150/code/scripts/profile_ops.py
-
curl -L -o profile_ops.py https://huggingface.co/changh95/bevformer-p150/resolve/main/code/scripts/profile_ops.py
12.9 kB
| #!/usr/bin/env python3 | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """Device profile of BEVFormer's traced replays, one signposted range per trace variant (OPT_BASELINE.md). | |
| Tracy + device profiler (absolute ``-o`` under ``$ROOT/generated/profiler``, never inside the bundle): | |
| bin/devrun -t 3600 -- python -m tracy -r -p -v --no-web-server --op-support-count 4000 \\ | |
| -o $ROOT/generated/profiler/bevformer_baseline code/scripts/profile_ops.py --rounds 2 --eager | |
| tt-perf-report <reports/<ts>/ops_perf_results_<ts>.csv> --start-signpost image --end-signpost image_end | |
| python code/scripts/profile_summary.py $ROOT/generated/profiler/bevformer_baseline --json s.json --md s.md | |
| ttnn-visualizer memory + graph report of one eager run of each variant, one report directory per variant (slow; never | |
| used for timing; a plain graph capture: ``full_graph_capture``'s stack traces + detailed buffer tracing needed more than | |
| 58 GB of host RAM for the image + bev graph): | |
| bin/devrun -t 3600 -- python code/scripts/profile_ops.py --graph-report $ROOT/generated/ttnn_reports/bevformer_baseline | |
| ttnn-visualizer --profiler-path $ROOT/generated/ttnn_reports/bevformer_baseline/bev \\ | |
| --performance-path $ROOT/generated/profiler/bevformer_baseline/reports/<ts> | |
| The model is loaded like the API (both variants captured); three frames of one stream (the PandaSet f30 sample when | |
| present, else the synthetic frame) bring the history to the steady state (``use_prev_bev`` = 1); the profiler | |
| buffer is flushed; then each variant is replayed once between its own signposts (``image`` .. ``image_end``, | |
| ``bev`` .. ``bev_end``, inside ``trace`` .. ``trace_end``) with ``ttnn.ReadDeviceProfiler`` after each (the device | |
| buffer is sized by ``--op-support-count``; a frame is ~1,500 programs). ``--rounds 2`` repeats the window | |
| (``trace2`` / ``image2`` ...). ``--eager`` adds one eager (untraced, program cache hot) run of each variant between | |
| ``eager_image`` / ``eager_bev`` signposts with module-stage signposts inside (labels only): ``img.stem``, | |
| ``img.layer1..4``, ``img.neck``, ``img.values``; ``enc.queries``, ``enc{i}.tsa``, ``enc{i}.sca``, ``enc{i}.ffn``, | |
| ``enc.out``; ``dec.value_proj``, ``dec{i}.self``, ``dec{i}.cross``, ``dec{i}.ffn``, ``dec{i}.reg``, ``dec.heads``, | |
| ``dec.pack``. ``profile_summary.py`` carries the labels over to the traced ops when the op sequences match. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| HERE = Path(__file__).resolve() | |
| sys.path.insert(0, str(HERE.parents[1])) | |
| sys.path.insert(0, str(HERE.parent)) | |
| VARIANTS = ("image", "bev") | |
| class _Marked: | |
| """Transparent wrapper of a callable: emits a Tracy signpost, calls it, optionally emits another after.""" | |
| def __init__(self, obj, name: str, signpost, after: str = ""): | |
| self._obj, self._name, self._signpost, self._after = obj, name, signpost, after | |
| def __call__(self, *a, **k): | |
| if self._name: | |
| self._signpost(self._name) | |
| out = self._obj(*a, **k) | |
| if self._after: | |
| self._signpost(self._after) | |
| return out | |
| def __getattr__(self, item): | |
| return getattr(self._obj, item) | |
| def mark_stages(tt, signpost): | |
| """Stage signposts at the module boundaries of the ``image`` and ``bev`` functions (one eager run). Returns an | |
| undo callable.""" | |
| undo = [] | |
| def set_attr(obj, name, value): | |
| had = name in obj.__dict__ | |
| old = obj.__dict__.get(name) | |
| setattr(obj, name, value) | |
| undo.append(lambda: setattr(obj, name, old) if had else obj.__dict__.pop(name, None)) | |
| def set_item(seq, i, value): | |
| old = seq[i] | |
| seq[i] = value | |
| undo.append(lambda: seq.__setitem__(i, old)) | |
| img = tt.image | |
| set_attr(img, "stem", _Marked(img.stem, "img.stem", signpost)) | |
| for s, blocks in enumerate(img.stages.stages): | |
| set_item(blocks, 0, _Marked(blocks[0], f"img.layer{s + 1}", signpost)) | |
| set_attr(img, "neck", _Marked(img.neck, "img.neck", signpost)) | |
| set_attr(img, "values", _Marked(img.values, "img.values", signpost)) | |
| enc = tt.encoder | |
| set_attr(enc, "bev_queries", _Marked(enc.bev_queries, "enc.queries", signpost)) | |
| for i, L in enumerate(enc.layers): | |
| for key, label in (("tsa", "tsa"), ("sca", "sca"), ("ffn1", "ffn")): | |
| old = L[key] | |
| L[key] = _Marked(old, f"enc{i}.{label}", signpost) | |
| undo.append(lambda L=L, key=key, old=old: L.__setitem__(key, old)) | |
| set_attr(tt, "encoder", _Marked(enc, "", signpost, after="enc.out")) | |
| dec = tt.decoder | |
| set_attr(dec.layers[0], "value_proj", _Marked(dec.layers[0].value_proj, "dec.value_proj", signpost)) | |
| counter = {"self": 0, "cross": 0} | |
| def labelled(kind, fn): | |
| def wrapper(*a, **k): | |
| signpost(f"dec{counter[kind]}.{kind}") | |
| counter[kind] += 1 | |
| return fn(*a, **k) | |
| return wrapper | |
| set_attr(dec, "self_attention", labelled("self", dec.self_attention)) | |
| set_attr(dec, "cross_attention", labelled("cross", dec.cross_attention)) | |
| for i, L in enumerate(dec.layers): | |
| set_attr(L, "ffn1", _Marked(L.ffn1, f"dec{i}.ffn", signpost)) | |
| set_item(L.reg, 0, _Marked(L.reg[0], f"dec{i}.reg", signpost)) | |
| set_attr(dec, "heads", _Marked(dec.heads, "dec.heads", signpost)) | |
| set_attr(tt, "decoder", _Marked(dec, "", signpost, after="dec.pack")) | |
| def restore(): | |
| for fn in reversed(undo): | |
| fn() | |
| counter.update(self=0, cross=0) | |
| return restore | |
| def steady_request(): | |
| """Kwargs of ``model(...)`` for the steady-state frames: the PandaSet f30 sample if present, else synthetic.""" | |
| from bench import pandaset_dir | |
| from tt_bevformer import load_sample | |
| from tt_bevformer.tt.warmup import synthetic_request | |
| p = pandaset_dir() / "pandaset_019_f30.json" | |
| if p.is_file(): | |
| req = load_sample(p) | |
| st = req.pop("stream", {}) or {} | |
| req["stream"] = {"id": "profile", "T_world_from_ego": st.get("T_world_from_ego")} | |
| return req, str(p) | |
| req = synthetic_request() | |
| req["stream"] = {"id": "profile"} | |
| return req, "synthetic" | |
| def main() -> int: | |
| ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0]) | |
| ap.add_argument("--rounds", type=int, default=1) | |
| ap.add_argument("--eager", action="store_true") | |
| ap.add_argument("--graph-report", default=None, help="write a ttnn-visualizer memory/graph report of one eager " | |
| "image + bev to this directory instead of profiling") | |
| ap.add_argument("--graph-variants", default="image,bev", help="variants captured by --graph-report, one report " | |
| "directory each (<dir>/<variant>)") | |
| ap.add_argument("--graph-full", action="store_true", help="ttnn.graph.full_graph_capture (Python stack traces + " | |
| "detailed buffer tracing): above 58 GB of host RAM for this model, so the default is a plain " | |
| "graph capture (ops, tensors, buffer allocations)") | |
| ap.add_argument("--max-rss-gb", type=float, default=30.0, help="abort the graph capture above this host RSS") | |
| ap.add_argument("--dispatch", default=None, choices=[None, "eth", "worker"]) | |
| ap.add_argument("--num-cqs", type=int, default=None) | |
| ap.add_argument("--json", default=None, help="write the run's metadata (config, trace description)") | |
| a = ap.parse_args() | |
| import ttnn | |
| from tt_bevformer import BEVFormer | |
| from tt_bevformer.ttaw.profiling import read_device_profiler, signpost | |
| t0 = time.perf_counter() | |
| model = BEVFormer.from_pretrained(dispatch=a.dispatch, num_command_queues=a.num_cqs) | |
| meta = {"config": model.device_info, "knobs": model.knobs.as_dict(), "load_s": round(time.perf_counter() - t0, 1)} | |
| print("config:", json.dumps(meta["config"]), flush=True) | |
| dev, r = model.device, model.runner | |
| sync = lambda: ttnn.synchronize_device(dev) # noqa: E731 | |
| db_dir = None | |
| try: | |
| req, meta["input"] = steady_request() | |
| for _ in range(3): | |
| out = model(**req) | |
| meta["detections"] = len(out.scores) | |
| sync() | |
| if a.graph_report: | |
| import contextlib | |
| import os | |
| import threading | |
| def guard(stop): # the capture lives in host RAM: never let it take the host down | |
| page = os.sysconf("SC_PAGE_SIZE") | |
| while not stop.wait(1.0): | |
| with open("/proc/self/statm") as fh: | |
| rss = int(fh.read().split()[1]) * page | |
| if rss > a.max_rss_gb * 2 ** 30: | |
| print(f"graph capture: host RSS {rss / 2 ** 30:.1f} GB > {a.max_rss_gb} GB, abort", flush=True) | |
| os._exit(3) | |
| def plain_capture(path): | |
| prev = ttnn.CONFIG.enable_fast_runtime_mode | |
| ttnn.CONFIG.enable_fast_runtime_mode = False | |
| try: | |
| ttnn.graph.begin_graph_capture() | |
| yield | |
| ttnn.graph.end_graph_capture_to_file(str(path)) | |
| finally: | |
| ttnn.CONFIG.enable_fast_runtime_mode = prev | |
| for v in VARIANTS: # program cache hot, constants uploaded | |
| r.run_eager(v) | |
| sync() | |
| db_dir = [] | |
| stop = threading.Event() | |
| threading.Thread(target=guard, args=(stop,), daemon=True).start() | |
| try: | |
| for v in [x.strip() for x in a.graph_variants.split(",") if x.strip()]: | |
| out_dir = Path(a.graph_report).resolve() / v | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| json_path = out_dir / "graph_capture.json" | |
| t1 = time.perf_counter() | |
| cm = ttnn.graph.full_graph_capture(str(json_path)) if a.graph_full else plain_capture(json_path) | |
| with cm: | |
| r.run_eager(v) | |
| sync() | |
| meta[f"graph_capture_s.{v}"] = round(time.perf_counter() - t1, 1) | |
| meta[f"graph_json_mb.{v}"] = round(json_path.stat().st_size / 1e6, 1) | |
| print("graph captured:", v, meta[f"graph_capture_s.{v}"], "s,", meta[f"graph_json_mb.{v}"], "MB", | |
| flush=True) | |
| db_dir.append((out_dir, json_path)) | |
| finally: | |
| stop.set() | |
| else: | |
| read_device_profiler(dev) # drop the load / warm-up data | |
| for k in range(1, a.rounds + 1): | |
| tag = "" if k == 1 else str(k) | |
| signpost(f"trace{tag}") | |
| for v in VARIANTS: | |
| signpost(f"{v}{tag}") | |
| r.replay(v) | |
| sync() | |
| signpost(f"{v}{tag}_end") | |
| read_device_profiler(dev) | |
| signpost(f"trace{tag}_end") | |
| if a.eager: | |
| restore = mark_stages(model.tt, signpost) | |
| try: | |
| signpost("eager") | |
| for v in VARIANTS: | |
| signpost(f"eager_{v}") | |
| r.run_eager(v) | |
| sync() | |
| signpost(f"eager_{v}_end") | |
| read_device_profiler(dev) | |
| signpost("eager_end") | |
| finally: | |
| restore() | |
| meta["trace"] = r.describe() | |
| finally: | |
| model.close() | |
| for out_dir, json_path in db_dir or []: | |
| t1 = time.perf_counter() | |
| db = ttnn.graph_report.import_report(json_path, out_dir) | |
| ttnn.save_config_to_json_file(out_dir / "config.json") | |
| cfg = json.loads((out_dir / "config.json").read_text()) | |
| cfg.update({"enable_fast_runtime_mode": False, "enable_graph_report": True, | |
| "enable_detailed_buffer_report": bool(a.graph_full), "root_report_path": str(out_dir.parent), | |
| "report_name": out_dir.name}) | |
| (out_dir / "config.json").write_text(json.dumps(cfg, indent=4) + "\n") | |
| meta.setdefault("graph_report", {})[out_dir.name] = { | |
| "dir": str(out_dir), "db": str(db), "import_s": round(time.perf_counter() - t1, 1), | |
| "db_mb": round(Path(db).stat().st_size / 1e6, 1)} | |
| json_path.unlink(missing_ok=True) | |
| print("graph report:", json.dumps(meta["graph_report"][out_dir.name]), flush=True) | |
| if a.json: | |
| Path(a.json).parent.mkdir(parents=True, exist_ok=True) | |
| Path(a.json).write_text(json.dumps(meta, indent=1, default=str) + "\n") | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |