Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

474 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Python API (`perfxpert.api`)
`perfxpert.api` is a **1:1 mirror** of the 8 agent-hierarchy MCP
tools. Every callable here IS the same function the MCP server wraps
— the Python API and the MCP surface share a single implementation.
Use this module to embed PerfXpert's analysis brain in your own
tooling without running the MCP server.
Cross-links:
- [MCP server](../integration/mcp-server.md) — the same 8 agent tools
+ 48 classifier / knowledge tools re-exposed over stdio JSON-RPC.
**This API is the same surface as the MCP tools.**
- [Agent hierarchy](../architecture/agent-hierarchy.md) — tier map,
fence-slice pattern, and where each agent lives in source.
- [Agentic mode guide](agentic-mode.md) — air-gap vs LLM,
`PERFXPERT_LLM_FALLBACK_CHAIN`, typed-error taxonomy.
## Exported callables
The mirror drops the `perfxpert_` prefix (which is only applied at
MCP registration):
| MCP tool name | Python API |
|---------------|------------|
| `perfxpert_agent_root` | `perfxpert.api.agent_root` |
| `perfxpert_agent_analysis` | `perfxpert.api.agent_analysis` |
| `perfxpert_agent_recommendation` | `perfxpert.api.agent_recommendation` |
| `perfxpert_agent_correctness` | `perfxpert.api.agent_correctness` |
| `perfxpert_agent_compute_specialist` | `perfxpert.api.agent_compute_specialist` |
| `perfxpert_agent_memory_specialist` | `perfxpert.api.agent_memory_specialist` |
| `perfxpert_agent_latency_specialist` | `perfxpert.api.agent_latency_specialist` |
| `perfxpert_agent_diff_specialist` | `perfxpert.api.agent_diff_specialist` |
| `perfxpert_trace_diff_diff_runs` | `perfxpert.api.trace_diff_diff_runs` |
| `perfxpert_rccl_analysis_analyze_collectives` | `perfxpert.tools.rccl_analysis.analyze_collectives` |
| `perfxpert_interconnect_lookup_peaks` | `perfxpert.tools.interconnect.lookup_peaks` |
Every agent callable honors `PERFXPERT_AIRGAP=1` and the full provider /
fallback-chain ladder (`PERFXPERT_LLM_FALLBACK_CHAIN`) because the
agent tools defer to `perfxpert.agents.runtime.build_session`.
`trace_diff_diff_runs` is deterministic and credential-free — it reads
two rocpd databases and produces a schema-0.3.1 diff dict (see
[schemas.md][schemas] for the full contract). This is the same engine
`perfxpert diff`, `perfxpert ci`, and `perfxpert analyze --baseline`
use.
The two communication-analysis tools are deterministic (no
LLM, no credentials) and map 1:1 onto their module functions:
```python
# SKIP-SAMPLE — illustrative RCCL analysis call (requires a rocpd .db)
from perfxpert.tools import rccl_analysis, interconnect
peaks = interconnect.lookup_peaks("gfx942") # MI300X XGMI spec
peaks["achievable_gbps"] # -> 340.0
result = rccl_analysis.analyze_collectives(
"merged.db", gfx_id="gfx942"
)
result["summary"]["dominant_op"] # -> "AllReduce"
result["summary"]["overlap_pct"] # comm/compute overlap %
```
[schemas]: ../contributing/schemas.md
## Quickstart
```python
# SKIP-SAMPLE — illustrative Python API call (requires real trace.db / running session)
from perfxpert import api
out = api.agent_root(
database_path="trace.db",
user_query="why slow?",
airgap=True,
)
print(out["primary_bottleneck"])
print(out["narrative"])
for rec in out["recommendations"]:
print(rec["name"], rec["title"])
```
`api.agent_root(...)` is the same call `perfxpert analyze` makes
under the hood — passing `database_path=<path>` + `user_query=<str>`
gives you the equivalent of the CLI's end-to-end report in a Python
dict.
### Hotspot output shape
Each entry in `out["hotspots"]` is a dict with `name`, `calls`,
`total_duration`, `avg_duration`, `min_duration`, `max_duration`,
and `percent_of_total`. When the caller supplied a `source_dir` to
the root agent, every hotspot **also** carries a
`source_locations: list[dict]` field:
```python
# SKIP-SAMPLE — illustrative output shape for hotspot source correlation
{
"name": "ns::matmul<float>(float*, float*)",
"calls": 128,
"total_duration": 92_000_000,
"percent_of_total": 48.5,
"source_locations": [
{"file": "src/ops.hip", "line": 42, "kind": "definition"},
{"file": "src/main.hip", "line": 88, "kind": "launch"},
],
}
```
`kind` is `"definition"` (the `__global__` symbol declaration) or
`"launch"` (a detected `hipLaunchKernelGGL` / triple-angle dispatch
site). The list is empty when the Tier-0 scanner ran but found no
matching basename, and the field is absent entirely when no source
scan was performed. The `source_locations` field is the same shape
as `hotspots[i].source_locations` in the JSON report (schema
`0.3.1`).
## Input schemas (one example per agent)
Field names come from `perfxpert/agents/schemas.py` (Pydantic
models; frozen to prevent mutation during handoff). Schemas cap at
≤10 input fields and ≤5 output fields per agent — CI-enforced in
`tests/test_agents/test_schema_field_caps.py`.
### `agent_root` — Layer-0 entry point
```python
# SKIP-SAMPLE — illustrative Python API call (requires real trace.db / running session)
out = api.agent_root(
user_query="Propose the first optimization for my hot kernel.",
database_path="/tmp/trace.db", # optional
source_dir="./my_app", # optional — Tier 0 source scan
provider="anthropic", # anthropic|openai|ollama|private|opencode
airgap=False, # True ⇒ deterministic path, no LLM
session_id=None, # optional — for session persistence
progress_callback=print, # optional — live phase updates
)
```
Output: `narrative: str`, `recommendations: list[dict]`,
`primary_bottleneck: str`, `warnings: list[str]`, `metadata: dict`.
#### Live progress feedback
Every agent tool (`agent_root`, `agent_analysis`, `agent_recommendation`,
`agent_correctness`, `agent_compute_specialist`, `agent_memory_specialist`,
`agent_latency_specialist`, `agent_diff_specialist`) accepts an optional
`progress_callback: Callable[[str], None]`. When set, the runtime fires
short status strings as each agent phase enters / exits and when the
fallback chain cascades across providers — useful for driving a
spinner, streaming to a web UI, or piping into a log aggregator:
```python
# SKIP-SAMPLE — illustrative Python API call
events = []
out = api.agent_root(
database_path="/tmp/trace.db",
provider="anthropic",
progress_callback=events.append,
)
# events == ["entering root", ..., "exit root"]
```
`progress_callback=None` (the default) is zero-overhead — the runtime
short-circuits all emission when no callback is registered. This is the
same mechanism the `perfxpert analyze` CLI uses to draw its Rich
spinner (see the Getting Started guide §6).
### `agent_analysis` — Layer-1 bottleneck classifier
```python
# SKIP-SAMPLE — illustrative Python API call (requires real trace.db / running session)
out = api.agent_analysis(
input={
"database_path": "/tmp/trace.db",
"top_kernels": 10,
"att_dir": None, # optional — auto-detected
},
airgap=True,
)
```
Output: `primary_bottleneck`, `confidence`, `time_breakdown`,
`hot_kernels`, `counter_data_available`.
### `agent_recommendation` — Layer-1 technique proposer
```python
# SKIP-SAMPLE — illustrative Python API call (requires real trace.db / running session)
findings = api.agent_analysis(
input={"database_path": "/tmp/trace.db"},
airgap=True,
)
out = api.agent_recommendation(
input={
"findings": findings,
"kernel_filter": None,
"edit_history": [],
"seen_recommendation_hashes": [],
},
airgap=True,
)
```
Output: `recommendations` (flat, ranked, deduplicated),
`specialist_used` (`compute`/`memory`/`latency`/`none`),
`plateau_detected`.
### `agent_correctness` — Layer-1 gate-verdict narrator
```python
# SKIP-SAMPLE — illustrative Python API call (requires real trace.db / running session)
# gate_verdict is a dict mirroring runtime.gate_cascade.GateVerdict.
out = api.agent_correctness(
input={
"gate_verdict": {
"status": "reject",
"failing_gate": "sol",
"detail": "L2 hit rate regressed from 72% to 38%",
"delta_pct": -47.2,
"metrics": {},
"rejected_patch_sha": "abc123",
"per_kernel_deltas": {"gemm_k0": -12.4},
},
"kernel_name": "gemm_k0",
"last_technique": "lds_blocking",
"edit_history": [],
},
airgap=True,
)
```
Output: `verdict` (`pass`/`reject`/`regressed`), `action`
(`accept`/`revert`/`reject_and_log`), `narrative`,
`alternative_technique`, `follow_up_task_id`.
### `agent_compute_specialist` — Layer-2 compute-bound expert
```python
# SKIP-SAMPLE — illustrative Python API call (requires real trace.db / running session)
out = api.agent_compute_specialist(
input={
"gfx_id": "gfx942",
"hot_kernels": [{"name": "gemm_k0", "duration_ns": 12000000}],
"counter_data": {"vgpr_used": 128, "waves_per_eu": 4},
"source_hints": {},
},
airgap=True,
)
```
Output: `techniques` (list of `{name, rationale, expected_impact,
effort, risk}`), `confidence`, `citations`.
### `agent_memory_specialist` — Layer-2 memory-bound expert
```python
# SKIP-SAMPLE — illustrative Python API call (requires real trace.db / running session)
out = api.agent_memory_specialist(
input={
"gfx_id": "gfx942",
"hot_kernels": [{"name": "stream_copy", "duration_ns": 8000000}],
"memcpy_data": {"host_to_device_bytes": 1024 * 1024 * 512},
"counter_data": {"l2_hit_rate": 0.41},
},
airgap=True,
)
```
Output: same shape as compute specialist — `techniques`,
`confidence`, `citations`.
### `agent_latency_specialist` — Layer-2 launch / dependency-chain expert
```python
# SKIP-SAMPLE — illustrative Python API call (requires real trace.db / running session)
out = api.agent_latency_specialist(
input={
"gfx_id": "gfx942",
"hot_kernels": [{"name": "launch_heavy_loop", "count": 10000}],
"api_overhead_pct": 34.2,
"avg_kernel_duration_us": 6.4,
},
airgap=True,
)
```
Output: same shape — `techniques`, `confidence`, `citations`.
### `agent_diff_specialist` — Layer-2 run-to-run diff narrator
Compares a baseline rocprofiler-sdk database against a new run and
returns a structured verdict (improved / regressed / neutral) plus
ranked per-kernel deltas and a prose narrative. Wraps
`trace_diff.diff_runs` internally — the arithmetic is deterministic;
the LLM (when attached) only rewrites the narrative.
```python
# SKIP-SAMPLE — illustrative Python API call (requires two real .db files)
out = api.agent_diff_specialist(
baseline_db="baseline.db",
new_db="new.db",
top_kernels=20,
user_intent="summarize the diff",
airgap=True,
)
# {'wall_delta_pct': +12.3,
# 'regressions': [{'name': 'matmul', 'delta_pct': +34.2, ...}, ...],
# 'improvements': [...],
# 'verdict': 'regressed',
# 'narrative': 'The new run finished in +12.3% wall-time vs baseline. '
# '4 kernels regressed, 1 improved. Top regression: matmul (+34.2%).',
# 'confidence': 0.7}
print(out["verdict"], out["narrative"])
for k in out["regressions"][:3]:
print(f" {k['name']}: {k['delta_pct']:+.1f}%")
```
Output keys: `wall_delta_pct`, `regressions`, `improvements`,
`verdict` ∈ {`improved`, `regressed`, `neutral`}, `narrative`,
`confidence` (0..1). Use this when the backend TUI LLM decides
"compare two runs" rather than "re-analyze twice" —
`agent_diff_specialist` is one tool call; running `agent_root`
twice is two.
### `trace_diff_diff_runs` — compare two rocpd databases
Deterministic, credential-free — no LLM required. Same engine as
`perfxpert diff`, `perfxpert ci`, and the `--baseline` splice.
```python
# SKIP-SAMPLE — illustrative Python API call (requires two real .db files)
from perfxpert import api
diff = api.trace_diff_diff_runs(
baseline_db="baseline.db",
new_db="new.db",
top_kernels=20,
)
# schema_version == "0.3.1"
print(f"wall delta: {diff['wall_delta_pct']:+.2f}%")
for k in diff["primary_regressions"]:
print(f" REGRESSED {k['name']}: {k['delta_pct']:+.1f}%")
```
Output keys: `schema_version`, `baseline_db`, `new_db`, `wall_delta_ns`,
`wall_delta_pct`, `per_kernel`, `primary_regressions`,
`primary_improvements`, `narrative`. See
[schemas.md](../contributing/schemas.md) for the `trace_diff` contract.
### `roofline_plot_points` — per-kernel live-roofline points
Builds the `(arithmetic_intensity, achieved_flops_per_s)` points used
by the webview's **Live Roofline** chart. READ_ONLY against a rocpd
database — no network, no writes, no LLM. The same payload is embedded
under `"roofline"` in the agentic JSON output.
```python
# SKIP-SAMPLE — illustrative Python API call (requires a Tier-2 .db with
# SQ_INSTS_VALU + FETCH_SIZE + WRITE_SIZE counters).
from perfxpert.tools import roofline
rf = roofline.plot_points("trace.db", top_k=10)
# {'schema_version': '0.3.x',
# 'arch': 'gfx942',
# 'arch_peaks': {'fp32': 1.634e14, 'fp16': 1.307e15, ...},
# 'dtype': 'bf16',
# 'dtype_confidence': 'from_kernel_name',
# 'kernels': [{'name': 'gemm_bf16_kernel',
# 'ai': 48.2,
# 'achieved_flops_per_s': 1.12e14,
# 'bottleneck_class': 'compute',
# 'fp_type': 'bf16',
# 'confidence': 'high',
# 'duration_pct': 94.3}],
# 'ridge_point': {'ai': 30.8, 'flops_per_s': 1.634e14}}
for k in rf["kernels"]:
print(f"{k['name']}: AI={k['ai']:.1f}, "
f"{k['achieved_flops_per_s']/1e12:.1f} TF/s, {k['bottleneck_class']}")
```
Formula — deterministic, no LLM involvement:
```text
flops = SQ_INSTS_VALU × 64 + sum(SQ_INSTS_VALU_MFMA_MOPS_* × 512)
# fallback when only the legacy aggregate counter is present:
+ SQ_INSTS_VALU_MFMA × mfma_flops_per_inst[dtype]
bytes = (FETCH_SIZE + WRITE_SIZE) × 1024 # TCC KiB → bytes
ai = flops / bytes
rate = flops / (duration_ns / 1e9)
```
Dtype detection is a regex over the demangled kernel name
(`_bf16`, `_fp16`, `_fp8`, `_int8`; falls back to `fp32` +
`dtype_confidence: "default"`). Pass `dtype_hint="bf16"` to force a
single dtype for every kernel — useful when your kernels don't follow
the naming convention. See
[schemas.md](../contributing/schemas.md#roofline-payload) for the full
contract.
## Error handling
All callables raise the standard provider taxonomy on LLM failure —
`AuthError`, `RateLimitError`, `QuotaExceededError`,
`TransientError`, `FatalError`, `TimeoutError`, and
`ProviderChainExhausted` when a `PERFXPERT_LLM_FALLBACK_CHAIN`
cascade runs out of candidates. Import from
`perfxpert.providers._exceptions`; see
[agentic-mode.md](agentic-mode.md) §"Fallback chain" for the full
table of which errors cascade vs surface immediately.
```python
# SKIP-SAMPLE — illustrative Python API call (requires real trace.db / running session)
from perfxpert import api
from perfxpert.providers._exceptions import (
RateLimitError,
QuotaExceededError,
ProviderChainExhausted,
)
try:
out = api.agent_root(
database_path="trace.db",
user_query="why slow?",
provider="anthropic",
)
except QuotaExceededError:
# Retry is futile — fall through to airgap.
out = api.agent_root(
database_path="trace.db",
user_query="why slow?",
airgap=True,
)
```
## Advanced-specialist classifier / knowledge tools
These are raw callables (not agent entry points) that specialists invoke
internally; each is also exposed over the MCP wire.
| Tool | Python import | Role |
|------|---------------|------|
| `kernel_fusion.find_fusion_candidates` | `from perfxpert.tools.kernel_fusion import find_fusion_candidates` | Adjacent-short-kernel fusion candidates — ranked list with `(est_speedup_lo, est_speedup_hi)` brackets. |
| `gpu_runtime_monitor.parse_amd_smi_json` | `from perfxpert.tools.gpu_runtime_monitor import parse_amd_smi_json` | Parse a captured `amd-smi monitor --json` log. |
| `gpu_runtime_monitor.parse_rocm_smi_json` | `from perfxpert.tools.gpu_runtime_monitor import parse_rocm_smi_json` | Parse a captured `rocm-smi --json` log. |
| `gpu_runtime_monitor.analyze_thermal` | `from perfxpert.tools.gpu_runtime_monitor import analyze_thermal` | Thermal envelope + throttle-event summary over a captured log. |
| `unified_memory.analyze_paging` | `from perfxpert.tools.unified_memory import analyze_paging` | HtoD/DtoH paging + MI300X cross-die (XCD) penalty analysis. |
| `dependency_graph.reconstruct_dag` | `from perfxpert.tools.dependency_graph import reconstruct_dag` | DAG reconstruction + `critical_path` + `bubbles` + `total_bubble_ns` + `sync_event_count`. |
| `rccl_analysis.analyze_collectives` | `from perfxpert.tools.rccl_analysis import analyze_collectives` | RCCL collective-ops analysis — per-op `effective_bw_gbps`, `efficiency_pct`, `efficiency_label`. |
| `interconnect.lookup_peaks` | `from perfxpert.tools.interconnect import lookup_peaks` | XGMI / Infinity-Fabric / PCIe peak bandwidth lookup (used by `analyze_collectives` for efficiency ratios). |
| `roofline.plot_points` | `from perfxpert.tools.roofline import plot_points` | Live Roofline — per-kernel `(ai, achieved_flops_per_s, bottleneck_class, fp_type, confidence)` from pmc_events. |
| `pragma.lookup_pragmas` | `from perfxpert.tools.pragma import lookup_pragmas` | Enumerate the 3 allowlisted LLVM loop-hint pragmas (+ 7 rejected entries kept for fence visibility). |
| `pragma.explain_pragma` | `from perfxpert.tools.pragma import explain_pragma` | Full catalog entry for a given `pragma_id`. |
| `pragma.suggest_pragmas_for_kernel` | `from perfxpert.tools.pragma import suggest_pragmas_for_kernel` | Amdahl-gated pragma candidates for a hot kernel. |
| `predict_impact.predict_change_impact` | `from perfxpert.tools.predict_impact import predict_change_impact` | Change-Impact Prediction: return `{predicted_speedup_range, confidence, rationale, roofline_delta, assumptions, source_citation, prediction_id}` for a baseline DB + kernel + change_type. Amdahl + tier-2 gates enforced internally. **Stub today — durable prediction store lands in a follow-up; status field surfaces "unsupported" when hit.** |
| `predict_impact.list_supported_changes` | `from perfxpert.tools.predict_impact import list_supported_changes` | Enumerate the change_type ids in `knowledge/change_impact_models.yaml`. Returns `[{id, applies_to, required_metrics}]`. |
| `predict_impact.explain_prediction` | `from perfxpert.tools.predict_impact import explain_prediction` | Re-hydrate a prediction by its `prediction_id`. In-process only today; durable store lands in a follow-up. |
All are READ_ONLY (safe for external callers) and deterministic
(no LLM, no sudo, no live device access). The runtime-monitor parsers
ingest a user-supplied JSON log — set `PERFXPERT_GPU_MONITOR_LOG=<path>`
to let specialists pick it up automatically.
## See also
- [MCP server](../integration/mcp-server.md) — same 8 agents
available over JSON-RPC; identical input/output schemas.
- [Agent hierarchy](../architecture/agent-hierarchy.md) — tier
diagram and source-tree locations.
- `perfxpert/agents/schemas.py` — authoritative Pydantic schema
definitions (frozen models, field caps).