# PerfXpert — Getting Started This guide walks through install, the three entry points, the first analysis, and the top troubleshooting cases. For deeper dives follow the cross-links at the end of each section; nothing here duplicates reference material that lives elsewhere. ## What is PerfXpert PerfXpert is an AI-augmented GPU profiling and optimization tool for AMD ROCm. It reads `rocprofv3` trace databases (`.db`), classifies bottlenecks against hardware SoL bounds, and produces targeted recommendations — either deterministically (air-gap) or with an LLM specialist agent. Supported GPUs: MI100 / MI200 / MI300 / MI350 and RDNA2 / RDNA3. ## 1. Install ![install](assets/gifs/01-install.gif) *Install in a clean ROCm container, then verify the CLI surfaces. The pip build hook compiles the bundled patched `perfxpert-code` binary when bun is already on PATH; bun bootstrap is available only as an explicit developer opt-in with `PERFXPERT_AUTO_INSTALL_BUN=1`.* PerfXpert ships as a single Python wheel. The `setuptools` build hook in `setup.py` compiles the AMD-branded bundled opencode binary during `pip install`. If bun is missing, pip fails closed unless `PERFXPERT_AUTO_INSTALL_BUN=1` is set; that explicit opt-in lets setup.py bootstrap bun into the user's home directory for this build. If OS prerequisites such as `curl`, `git`, or `unzip` are missing, pip fails with the missing package-manager pieces instead of silently producing a broken `perfxpert-code`. ### Prerequisites - Python 3.10+ - `curl`, `git`, `unzip`, `python3-venv`, and `python3-pip` for GitHub installs. - On Ubuntu 24+ and other externally managed Python environments, create and activate a virtual environment before invoking pip. - `bun` on PATH. If you explicitly allow automatic bun bootstrap, also make sure `curl` and `unzip` are present and set `PERFXPERT_AUTO_INSTALL_BUN=1`. - The repo-pinned `experimental/python/perfxpert/opencode` submodule populated if you are installing from a checkout without using the wrapper. The setup hook may attempt a scoped `git submodule update --init --depth 1 -- experimental/python/perfxpert/opencode`, but it will not clone an arbitrary upstream tag during install. #### Opt-out env vars - `PERFXPERT_SKIP_BUNDLED_BUILD=1` — skip the entire opencode build. Use only in CI that intentionally skips the interactive TUI. The default `perfxpert-code` command will not fall back to an arbitrary upstream opencode binary. - `PERFXPERT_SKIP_OPENCODE_FETCH=1` — don't even attempt the scoped submodule init when the vendored `opencode/` checkout is empty. - `PERFXPERT_OPENCODE_PATH=/path/to/opencode` — explicit user-owned upstream escape hatch used only by `perfxpert-code opencode ...`. #### Note on Windows PerfXpert does not auto-bootstrap bun on Windows. If the bundled `opencode` build is not available on your host, use the multi-backend launcher (`perfxpert-code claude` / `codex` / `gemini`) against a native backend CLI instead. ### Distro package setup Use a virtual environment for the supported install flow. Package examples are provided for Ubuntu 22.04, Ubuntu 24.04, UBI/RHEL 9, UBI/RHEL 10, and SLES 15.6. Package setup differs slightly by distro: ```bash # SKIP-SAMPLE — Ubuntu 22.04 / 24.04 apt install -y curl git unzip python3-venv python3-pip python3 -m venv .venv . .venv/bin/activate ``` ```bash # SKIP-SAMPLE — RHEL 9 command -v curl >/dev/null || dnf install -y curl dnf install -y git unzip python3.11 python3.11-pip python3.11 -m venv .venv . .venv/bin/activate ``` ```bash # SKIP-SAMPLE — RHEL 10 command -v curl >/dev/null || dnf install -y curl dnf install -y git unzip python3 python3-pip python3 -m venv .venv . .venv/bin/activate ``` ```bash # SKIP-SAMPLE — SLES 15.6 zypper install -y curl git unzip python311 python311-pip python3.11 -m venv .venv . .venv/bin/activate ``` UBI/RHEL base images can provide `curl` through `curl-minimal`. That is valid for the wrapper and for opt-in bun bootstrap; do not force dnf to replace it with the full `curl` package unless `command -v curl` fails. If the venv's pip/setuptools are too old and metadata preparation fails with `filename has 'perfxpert', but metadata has 'unknown'`, upgrade them inside the venv and retry: ```bash # SKIP-SAMPLE — only needed for older venv toolchains python -m pip install -U pip setuptools wheel ``` ### Pip install ```bash # SKIP-SAMPLE — install from a local checkout (editable mode) pip install -e "experimental/python/perfxpert[all]" ``` …or pull the latest build straight from GitHub using the fast-install wrapper (see §1.2 for why): ```bash # SKIP-SAMPLE — latest develop branch, no local clone needed. # Requires bun on PATH; install bun from your approved package source first. REF=develop; curl -fsSL "https://raw.githubusercontent.com/ROCm/rocm-systems/${REF}/experimental/python/perfxpert/scripts/pip-install-from-git.sh" | bash -s -- "${REF}" # Pin a tag or commit hash: # REF=v0.2.0; curl -fsSL "https://raw.githubusercontent.com/ROCm/rocm-systems/${REF}/experimental/python/perfxpert/scripts/pip-install-from-git.sh" | bash -s -- "${REF}" # REF=; curl -fsSL "https://raw.githubusercontent.com/ROCm/rocm-systems/${REF}/experimental/python/perfxpert/scripts/pip-install-from-git.sh" | bash -s -- "${REF}" # Base package only: # REF=develop; curl -fsSL "https://raw.githubusercontent.com/ROCm/rocm-systems/${REF}/experimental/python/perfxpert/scripts/pip-install-from-git.sh" | bash -s -- "${REF}" --extras '' ``` The GitHub install paths require `git` because pip shells out to `git clone`. The wrapper prints missing OS prerequisites and exits by default; it only runs `apt`, `dnf`, `zypper`, or `sudo` when `PERFXPERT_AUTO_INSTALL_PREREQS=1` or `--auto-install-prereqs` is explicitly supplied. On externally managed system Python it tells the user to create a virtual environment first. It prefers the active `python`, then `python3`, and only falls back to another already-installed `python3.10+` binary on PATH when the distro default is too old; it never downloads a separate Python runtime. During the pip build, `setup.py` builds from the pinned perfxpert opencode submodule; if bun is missing, it only bootstraps bun when `PERFXPERT_AUTO_INSTALL_BUN=1` is explicitly set. Pass the ref as the first argument after `bash -s --` when you need to pin a specific tag or commit hash. If you already have a local rocm-systems checkout, the equivalent command is: ```bash # SKIP-SAMPLE — local checkout equivalent bash rocm-systems/experimental/python/perfxpert/scripts/pip-install-from-git.sh develop # bash rocm-systems/experimental/python/perfxpert/scripts/pip-install-from-git.sh ``` The `[all]` extra pulls in `anthropic`, `openai`, `rich`, and `litellm`, which covers the hosted/local SDK-backed provider paths. The bundled `opencode` path is validated separately through the launcher/build flow. Omit `[all]` if you only want deterministic air-gap analysis (wrapper: pass `--extras ''` to skip extras entirely). On the GitHub wrapper path, the wrapper exits non-zero if the bundled patched `perfxpert-code` binary is still absent after install. Direct pip/editable paths use the same `setup.py` build hook: bun must be on PATH, or `PERFXPERT_AUTO_INSTALL_BUN=1` must be set to allow setup.py to bootstrap it. Otherwise pip fails with an actionable prerequisite message. ### 1.2 Why the wrapper: scoped submodule init `pip install "perfxpert @ git+https://...rocm-systems.git#subdirectory=..."` triggers pip's built-in `git submodule update --init --recursive -q` on the cloned work-tree BEFORE the perfxpert build hook ever runs. The rocm-systems root `.gitmodules` declares ~34 submodules (mscclpp, perfetto, glog, fmt, gtest, dyninst, sqlite, …) shared by the other projects in the monorepo; pip dutifully fetches every one of them. PerfXpert itself only needs ONE of them — the `opencode` submodule at `experimental/python/perfxpert/opencode`, used by the build hook to compile the bundled AMD-branded opencode binary. Measured on a fast host against the live rocm-systems repo: | Step | Time | |-------------------------------------------------------|-----------| | `git clone --filter=blob:none --depth 1` of rocm-systems | ~15 sec | | `git submodule update --init --recursive -q` (pip default) | **141 sec** | | `git -c submodule.active=…/opencode submodule update --init --recursive -q` | **0.03 sec** | On stock `rocm/dev-ubuntu-22.04` with corporate-grade bandwidth the default step regularly runs 3-6 minutes — and the first 99% of that time is downloading dependencies of projects the PerfXpert wheel never touches. `scripts/pip-install-from-git.sh` wraps pip with these env vars set: ```bash GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=submodule.active GIT_CONFIG_VALUE_0=experimental/python/perfxpert/opencode ``` pip uses `os.environ.copy()` when it spawns git subprocesses, so those env vars propagate into `git submodule update --init --recursive -q` and the `submodule.active` config restricts init to the single path listed. All other submodules stay at zero bytes on disk. Documented under `git-config(1) "GIT_CONFIG_COUNT"` and `gitmodules(5) "submodule..active"`. If the user insists on the plain one-liner without the wrapper, pip still works — they just pay the 3-6 min submodule-init penalty once. The `setup.py` build hook notices if the opencode submodule is still empty after pip's checkout (i.e. the user scoped submodule init out manually without including opencode), but it does **not** clone from the network during install. Instead it fails with the missing pinned submodule path so the default `perfxpert-code` install cannot silently fall back to an arbitrary opencode binary. Opt-outs: - `PERFXPERT_SKIP_OPENCODE_FETCH=1` — don't attempt the scoped submodule init; air-gap CI that intentionally skips the bundled opencode build should set this AND `PERFXPERT_SKIP_BUNDLED_BUILD=1`. ### What the build hook does It applies all 26 patches in `.patches/` (AMD branding, color palette, per-model system-prompt preambles with the STRICT-TOOL-DISCIPLINE stanza, the tool-priority gate, and the deep-rebrand session UI) to the pinned `opencode` submodule, runs the locked root `bun install`, then compiles the current-platform binary with `bun run build --single --skip-install`. The explicit `--skip-install` avoids opencode's secondary dynamic package install during the compile step; the locked install already populated the dependencies needed for the bundled binary. The final executable is copied to `perfxpert/_bundled/opencode`. Subsequent `pip install` invocations skip the rebuild if the binary is already newer than every patch file. **Opt-out:** set `PERFXPERT_SKIP_BUNDLED_BUILD=1` only in offline / sandboxed CI that intentionally skips the interactive TUI build. Default `perfxpert-code` requires the bundled binary built from the pinned submodule. **Bun missing:** pip fails closed by default. Install bun from an approved package source, or explicitly set `PERFXPERT_AUTO_INSTALL_BUN=1` to allow setup.py to bootstrap bun into the user's home directory for this build. If bootstrap prerequisites are missing, pip exits with distro-specific package-manager guidance. ## 2. Verify ![help](assets/gifs/02-help.gif) *`perfxpert --help`, `perfxpert analyze --help`, and the first lines of `perfxpert doctor` — everything the CLI exposes is one flag away.* ```bash # SKIP-SAMPLE — requires perfxpert on PATH from the pip install above perfxpert doctor ``` ![doctor](assets/gifs/03-doctor.gif) *`perfxpert doctor` end-to-end: Python check, MCP server reachable (56 tools registered), hosted/local/private LLM provider readiness, bundled opencode availability, `ALL CLEAN`.* Expected output ends with `ALL CLEAN` when everything is wired. The doctor checks: - `perfxpert` version + Python ≥ 3.10 - openai-agents SDK - MCP server reachable (`perfxpert-mcp` boots + 56 tools registered — 8 agent-hierarchy + 47 classifier/knowledge + 1 `trace_diff.diff_runs`) - task store (`~/.perfxpert` or `$PERFXPERT_TASK_ROOT`) - patched opencode binary resolution + bundled opencode config dir - LLM providers configured (counts hosted/local/private providers against `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `PERFXPERT_LLM_LOCAL_URL` or `OLLAMA_HOST`, and `PERFXPERT_LLM_PRIVATE_URL` or `PRIVATE_LLM_ENDPOINT` plus `PERFXPERT_LLM_PRIVATE_API_KEY`) - bundled opencode availability; opencode handles its own provider auth If `opencode binary` reports missing, the install skipped or failed the bundled build. Reinstall through the GitHub wrapper so the pinned submodule is populated and bun is bootstrapped before the wheel build finishes. ## 3. Three entry points PerfXpert ships three command-line surfaces, all driving the same agent runtime. - **`perfxpert analyze -i trace.db`** — non-interactive CLI. Consumes a rocprofv3 `.db`, emits a single report (text / JSON / markdown / webview HTML). Deterministic with `--llm` omitted; LLM-augmented with `--llm {anthropic,openai,ollama,private,opencode}`. - **`perfxpert-mcp`** — stdio MCP server that re-exposes the 56 READ-ONLY analysis tools over JSON-RPC (8 agent-hierarchy entry points — Root, Analysis, Recommendation, Correctness, +3 technique specialists, + diff specialist — plus 47 classifier / knowledge tools and 1 `trace_diff.diff_runs`). Meant to be spawned by an MCP client (Claude Desktop, Claude Code, Codex CLI, Gemini CLI, opencode). See `../integration/mcp-server.md`. - **`perfxpert-code`** — interactive TUI (the patched opencode bundle). Conversational optimization loop with the Root → Analysis → Recommendation → Specialist agent hierarchy and gate-cascade correctness middleware. ## 3.1 Choosing a backend `perfxpert-code` is multi-backend: packaged installs default to the bundled AMD-branded patched opencode, while source/editable checkouts prefer a locally built patched binary from the pinned `opencode` submodule. It can also wrap the user's native Claude Code, Gemini CLI, or Codex CLI while still enforcing the perfxpert tool-priority gate and registering the `perfxpert-mcp` server for free. Pick whichever matches your existing LLM workflow. - **Default (no subcommand)** — patched opencode path, the recommended entry point. Wheels ship the bundled binary; source checkouts can rebuild the same patched fork locally. Prompt + MCP come pre-wired. - **`perfxpert-code claude`** — registers perfxpert as an MCP server in the Claude Code project config, installs the native `PreToolUse` gate hook, then execs the user's `claude` CLI. - **`perfxpert-code gemini`** — writes `perfxpert-mcp` into the project-local `.gemini/settings.json`, list-appends a project-cache prompt file to `context.fileName`, installs native Gemini `BeforeTool` / `AfterTool` hooks, and never touches the user's `GEMINI.md`, then execs `gemini`. - **`perfxpert-code codex`** — writes the perfxpert MCP stanza into `/.codex/config.toml` when the project is trusted (otherwise falls back to `~/.codex/config.toml`), writes a project-root `AGENTS.override.md` compatibility override so Codex actually loads the perfxpert prompt, and execs `codex`. Gate enforcement is prompt-layer-only because Codex's native `PreToolUse` hook is Bash-only as of April 2026; the trust gate runs before MCP registration and either prompts or honors `PERFXPERT_AUTO_TRUST=1` (see §3.2 below). Short recipe per backend: ```bash # SKIP-SAMPLE — requires the named backend binary on PATH perfxpert-code # default (patched opencode path) perfxpert-code claude # Claude Code (native TUI) perfxpert-code gemini # Gemini CLI perfxpert-code codex # Codex CLI perfxpert-code uninstall claude # reverse the Claude install ``` See [backends.md](backends.md) for the full per-backend install / uninstall recipes, the gate-hook event-based lift semantics, the consent model (per-backend × cwd × file-set), and the env-var reference (`PERFXPERT_MCP_WARMUP_TIMEOUT_S`, `PERFXPERT_MCP_RETRY_BUDGET_S`, `PERFXPERT_SKIP_LIVE_CHECK`, `PERFXPERT_ASSUME_CONSENT`). The gate-probe coverage table in [backends.md §Gate-probe](backends.md) documents which small-model probe each backend uses to verify mechanical gate enforcement at `install()` time — Codex is not probed because its gate is prompt-layer-only. ## 3.2 Codex trust gate Codex refuses to run agents in a project directory that isn't marked `trusted` in `~/.codex/config.toml`. `perfxpert-code codex` handles this as a separate step BEFORE MCP registration: - **Interactive sessions** — the adapter prompts "Trust for Codex? [y/N]" and writes `[projects.""].trust_level = "trusted"` on confirmation. - **CI / non-interactive** — set `PERFXPERT_AUTO_TRUST=1` to auto-trust the cwd without prompting. An always-on stderr warning ("perfxpert-code codex: auto-trusted because PERFXPERT_AUTO_TRUST=1") is emitted even under `--quiet` — this is the security-warning contract. - Without either path, the adapter raises `TrustRequired` and exits non-zero. `ConfigClobber` is raised if `~/.codex/config.toml` is git-tracked or fails to parse. ## 3.3 First run — `perfxpert init` New in Phase 8 (Confluence roadmap row #29). A single guided wizard that chains the four things every new user asks about into one flow so you don't have to discover them separately: 1. **GPU detection** — runtime discovery runs `rocminfo`, `rocm-smi`, and `amd-smi` when they are present, with read-only KFD topology as a fallback for ROCm environments where `rocminfo` cannot open the device node. The local init wizard uses GPU facts such as gfx id, CU count, clocks, wave size, LDS size, VRAM, PCIe, and power limits. Theoretical peak FLOPS are derived for newly discovered local architectures when enough topology is available; `perfxpert/knowledge/gpu_specs.yaml` remains the offline fallback for non-local architectures and for fields a ROCm tool does not expose on that stack. Runtime discovery is scoped to the machine where the command runs. If an agent is optimizing a workload over SSH, run the ROCm discovery commands on that remote target host or run PerfXpert on the remote host itself; do not use the controller machine's local GPU facts for remote roofline or SoL analysis. If local runtime specs would describe the wrong machine, set `PERFXPERT_DISABLE_RUNTIME_GPU_SPECS=1` and pass explicit remote facts / `--arch`. 2. **Framework detection** — Tier-0 source scan (same scanner as `analyze --source-dir`, same `.git` / `node_modules` filters) plus a Python import probe for `torch`, `tensorflow`, `jax`, `cupy`. 3. **Config generation** — writes `~/.config/perfxpert/config.yaml` with the Pydantic-validated defaults and the detected provider. When the file already exists the wizard prints a unified diff and asks before overwriting (skipped under `--non-interactive`). 4. **Suggested first profiling command** — the lowest rung of the cost-ordered ladder (`rocprofv3 --sys-trace`). When the source scan found HIP kernels, a second `--pmc SQ_WAVES GRBM_COUNT GRBM_GUI_ACTIVE` line is emitted too; when a Python framework is detected, the target is `python train.py` instead of `./your_app`. ```bash # SKIP-SAMPLE — requires perfxpert on PATH from the pip install above perfxpert init # interactive (confirms diffs) perfxpert init --non-interactive # CI / scripting perfxpert init --source-dir ./my_project # target a specific source tree perfxpert init --arch gfx942 # override GPU detection perfxpert init --provider openai # pin the LLM provider perfxpert init --config-path /tmp/pxcfg.yaml --non-interactive # sandbox ``` Sample transcript on an MI300X host with `ANTHROPIC_API_KEY` exported: ``` == perfxpert init — first-run wizard == Step 1/4 — GPU detection detected: gfx942 (MI300X, 304 CU) peak FP32: 163.4 TFLOPS peak HBM : 5.3 TB/s Step 2/4 — Framework detection source dir: . python deps: PyTorch importable ⇒ framework: PyTorch source scan: 12 file(s), 3 kernel(s) ⇒ programming model: HIP (both detected ⇒ mixed python+kernel workload) Step 3/4 — Config generation provider : anthropic airgap : false max_tokens: 2048 target : ~/.config/perfxpert/config.yaml Step 4/4 — Suggested first profiling command primary : rocprofv3 --sys-trace -d ./profile_out -- python train.py extra : rocprofv3 --pmc SQ_WAVES GRBM_COUNT GRBM_GUI_ACTIVE -d ./profile_out_pmc -- python train.py (--pc-sampling / --att are second-tier — run after Tier-1 identifies hot kernels) Then: perfxpert analyze -i ./profile_out/*.db --source-dir . --format webview -o report Wizard complete. Your setup is ready. ``` `--non-interactive` never prompts; it always overwrites the config if one exists, so it's safe in CI. The current init-wizard provider probe order is: `ANTHROPIC_API_KEY` → `OPENAI_API_KEY` → `PERFXPERT_LLM_PRIVATE_URL` (compat: `PRIVATE_LLM_ENDPOINT`) → `OLLAMA_HOST` → patched `opencode` (no key required). The broader CLI surfaces (`analyze`, `doctor`) also accept the newer `PERFXPERT_LLM_LOCAL_URL` spelling for Ollama. ## 4. First analysis (60 seconds) Profile a trivial HIP app, then analyze. ```bash # SKIP-SAMPLE — requires ./my_app built against ROCm and rocprofv3 on PATH rocprofv3 --sys-trace --summary -d ./out -o results -- ./my_app perfxpert analyze -i ./out/results_results.db --format text ``` `rocprofv3` writes `./out/results_results.db` (the `_results` suffix is the rocprofv3 default); `perfxpert analyze` reads it, classifies the bottleneck (compute-bound / memory-bound / latency-bound / idle-bound), ranks hot kernels by Amdahl weight, and emits recommendations. ![analyze text](assets/gifs/04-analyze-text.gif) *Deterministic airgap analysis of the fixture `compute_bound.db` — SUMMARY, TIME BREAKDOWN, HOTSPOTS, HARDWARE COUNTERS, RECOMMENDATIONS, all in under a second.* Output formats: ```bash # SKIP-SAMPLE — requires a real trace.db perfxpert analyze -i trace.db --format json -d ./out -o report perfxpert analyze -i trace.db --format markdown -d ./out -o report perfxpert analyze -i trace.db --format webview -d ./out -o report ``` ![analyze json](assets/gifs/05-analyze-json.gif) *`--format json | jq` — flat top-level keys make the report easy to drive from scripts or dashboards.* ![analyze markdown](assets/gifs/06-analyze-markdown.gif) *`--format markdown` — drop straight into PR descriptions or a wiki.* ![analyze webview](assets/gifs/07-analyze-webview.gif) *`--format webview -o report` produces a self-contained `report.html` with the fixed 7 top-level sections (Overview, Summary, Execution, Hotspots, Hardware Counters, Recommendations, Tier-0 when `--source-dir` is set). AMD dark theme, SVG gauges, collapsible cards — email-ready.* #### Source-line correlation When `--source-dir` is supplied alongside `-i`, every row of the **Top Kernel Hotspots** table grows a `▾` chevron next to the kernel name. Clicking the chevron expands an inline panel — same visual style as the Recommendation card drawer — showing the kernel's **Definition** (file:line where the `__global__` symbol is declared) and every detected **Launch site** (file:line of each `hipLaunchKernelGGL` / triple-angle dispatch). Each line renders with a `Copy` button and a launch-type badge (`__global__`, `HIP_KERNEL_LAUNCH`, `<<< >>>`), VTune/NSight-style. When `--source-dir` is **not** supplied the panel is still present but explains how to enable the correlation. When the scanner did run but no symbol matches the hotspot's basename, the panel shows "No matching source location detected" — useful signal that the profiled binary and the `--source-dir` tree may be out of sync. Source-line correlation is data, not advice, so the panel ships regardless of the Tier-0 instrumentation-advice gate (`has_profiling`). The markdown and text formats append a compact `Source: file.hip:42 (definition), file.hip:88 (launch)` line under each hotspot row; the JSON format emits `hotspots[i].source_locations: [{file, line, kind}]` (schema `0.3.1`). #### ATT flame graph When the input DB carries Advanced Thread Trace (ATT) data — i.e. `perfxpert analyze -i trace.db --att-dir ` — the Thread Trace scard renders a dependency-free **inline SVG flame graph** directly below the per-kernel stall table. One horizontal row per kernel, one stacked rectangle per stall category (VMEM latency / LDS conflict / dependency chain / branch divergence), width proportional to the weighted stall bucket. Colors reuse the webview stall-category palette, wide rects carry an inline `