From 9f5413db89af62a945196f927efe26cb752297be Mon Sep 17 00:00:00 2001 From: "fei.zheng" Date: Thu, 22 Oct 2026 14:42:02 -0600 Subject: [PATCH 1/6] Federate the six rocprof-compute skills from ROCm/rocm-systems. The rocprofiler-compute project is already approved, and these skills stay owned on that repository's develop branch. Co-authored-by: Cursor --- .github/federation.json | 18 ++ .../.federated.json | 10 + .../SKILL.md | 242 ++++++++++++++++++ .../evals/evals.json | 101 ++++++++ .../skill-card.md | 13 + skills/rocprof-compute-memory/.federated.json | 10 + skills/rocprof-compute-memory/SKILL.md | 110 ++++++++ .../rocprof-compute-memory/evals/evals.json | 79 ++++++ skills/rocprof-compute-memory/skill-card.md | 13 + .../.federated.json | 10 + skills/rocprof-compute-pc-sampling/SKILL.md | 118 +++++++++ .../evals/evals.json | 79 ++++++ .../rocprof-compute-pc-sampling/skill-card.md | 13 + .../rocprof-compute-roofline/.federated.json | 10 + skills/rocprof-compute-roofline/SKILL.md | 114 +++++++++ .../rocprof-compute-roofline/evals/evals.json | 70 +++++ skills/rocprof-compute-roofline/skill-card.md | 13 + .../.federated.json | 10 + .../rocprof-compute-speed-of-light/SKILL.md | 79 ++++++ .../evals/evals.json | 72 ++++++ .../skill-card.md | 13 + .../.federated.json | 10 + skills/rocprof-compute-torch-trace/SKILL.md | 116 +++++++++ .../evals/evals.json | 83 ++++++ .../rocprof-compute-torch-trace/skill-card.md | 13 + 25 files changed, 1419 insertions(+) create mode 100644 skills/rocprof-compute-kernel-bottleneck/.federated.json create mode 100644 skills/rocprof-compute-kernel-bottleneck/SKILL.md create mode 100644 skills/rocprof-compute-kernel-bottleneck/evals/evals.json create mode 100644 skills/rocprof-compute-kernel-bottleneck/skill-card.md create mode 100644 skills/rocprof-compute-memory/.federated.json create mode 100644 skills/rocprof-compute-memory/SKILL.md create mode 100644 skills/rocprof-compute-memory/evals/evals.json create mode 100644 skills/rocprof-compute-memory/skill-card.md create mode 100644 skills/rocprof-compute-pc-sampling/.federated.json create mode 100644 skills/rocprof-compute-pc-sampling/SKILL.md create mode 100644 skills/rocprof-compute-pc-sampling/evals/evals.json create mode 100644 skills/rocprof-compute-pc-sampling/skill-card.md create mode 100644 skills/rocprof-compute-roofline/.federated.json create mode 100644 skills/rocprof-compute-roofline/SKILL.md create mode 100644 skills/rocprof-compute-roofline/evals/evals.json create mode 100644 skills/rocprof-compute-roofline/skill-card.md create mode 100644 skills/rocprof-compute-speed-of-light/.federated.json create mode 100644 skills/rocprof-compute-speed-of-light/SKILL.md create mode 100644 skills/rocprof-compute-speed-of-light/evals/evals.json create mode 100644 skills/rocprof-compute-speed-of-light/skill-card.md create mode 100644 skills/rocprof-compute-torch-trace/.federated.json create mode 100644 skills/rocprof-compute-torch-trace/SKILL.md create mode 100644 skills/rocprof-compute-torch-trace/evals/evals.json create mode 100644 skills/rocprof-compute-torch-trace/skill-card.md diff --git a/.github/federation.json b/.github/federation.json index 7989659..4bda046 100644 --- a/.github/federation.json +++ b/.github/federation.json @@ -86,6 +86,24 @@ { "path": "projects/rocprofiler-sdk/skills/pc-sampling", "as": "rocprofiler-pc-sampling" + }, + { + "path": "projects/rocprofiler-compute/skills/rocprof-compute-kernel-bottleneck" + }, + { + "path": "projects/rocprofiler-compute/skills/rocprof-compute-memory" + }, + { + "path": "projects/rocprofiler-compute/skills/rocprof-compute-pc-sampling" + }, + { + "path": "projects/rocprofiler-compute/skills/rocprof-compute-roofline" + }, + { + "path": "projects/rocprofiler-compute/skills/rocprof-compute-speed-of-light" + }, + { + "path": "projects/rocprofiler-compute/skills/rocprof-compute-torch-trace" } ] } diff --git a/skills/rocprof-compute-kernel-bottleneck/.federated.json b/skills/rocprof-compute-kernel-bottleneck/.federated.json new file mode 100644 index 0000000..96d773d --- /dev/null +++ b/skills/rocprof-compute-kernel-bottleneck/.federated.json @@ -0,0 +1,10 @@ +{ + "source": "rocm-rocm-systems", + "repo": "ROCm/rocm-systems", + "ref": "develop", + "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "path": "projects/rocprofiler-compute/skills/rocprof-compute-kernel-bottleneck", + "license": "MIT", + "content_hash": "d118f2c174e03cb348fb869d2cb0b896822264a61449a1c71e2e36ff57a50c8f", + "imported_at": "2026-10-22T20:41:18Z" +} diff --git a/skills/rocprof-compute-kernel-bottleneck/SKILL.md b/skills/rocprof-compute-kernel-bottleneck/SKILL.md new file mode 100644 index 0000000..161dc7b --- /dev/null +++ b/skills/rocprof-compute-kernel-bottleneck/SKILL.md @@ -0,0 +1,242 @@ +--- +name: rocprof-compute-kernel-bottleneck +description: Profiles an AMD GPU application with rocprof-compute and finds which kernel is slow and why, using performance metrics and PC sampling. Collects counters and selects kernels and dispatches, then analyzes GPU memory throughput, compute efficiency, wavefront occupancy and wavefront limits, scheduler stalls, etc. Use when the user asks to profile, benchmark, or speed up a HIP/ROCm kernel or application, or asks where a kernel's bottleneck is. Not for CUDA tools, Windows, system-wide CPU/MPI tracing, or writing kernel source. +--- + +# Profile an AMD GPU application and find the kernel bottleneck + +Start here for any "profile this" or "why is this kernel slow" request. This +skill collects counters, narrows a workload down to one kernel, and identifies +what limits it. Hand off to a focused skill once the limit is clear. + +Run `rocprof-compute profile --help` and `rocprof-compute analyze --help` +before choosing flags. Options change between releases; do not guess one. + +Never use the GUI or TUI. This is a command-line workflow. + +## Two sources of performance data + +rocprof-compute collects from two independent sources, and they answer +different questions. Choose by what the user is asking, not by difficulty. + +| Source | Answers | Skill | +|---|---|---| +| Perfmon counters | How the architecture behaves: bandwidth, cache, occupancy, pipeline utilization | this skill, then `rocprof-compute-speed-of-light`, `rocprof-compute-memory`, `rocprof-compute-roofline` | +| PC sampling | How the source code behaves: which instruction or line is hot and why it stalls | `rocprof-compute-pc-sampling` | + +Counters are the default starting point because they cover the whole kernel +cheaply. Go straight to `rocprof-compute-pc-sampling` when the user asks about instructions, +source lines, or stall reasons, and use both when an architectural limit needs +to be traced back to the code that causes it. + +## 1. Check the environment + +In this order, because each command depends on the one before it: + +```bash +amd-smi static +amd-smi list +rocminfo +rocprof-compute --specs +``` + +`amd-smi` and `rocminfo` report the GPU and ROCm install. +`rocprof-compute --specs` reports what the profiler itself detected, including +`ROCm Version`, `GPU Model`, and `GPU Arch`. It is built on the first two, so +it cannot succeed when they fail. + +Supported accelerators are listed in +[compatible-accelerators.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst). +Not every feature works on every architecture. Check there before promising a +result, and let the tool's own error message stand when a feature is +unavailable. + +## 2. Collect what you need before profiling + +Ask once: + +1. Launch command and arguments +2. A short workload name, used as `--name` +3. Which kernels matter, if not all + +Profiling replays the application once per counter pass, so the command must be +safe to run repeatedly and short enough to run several times. Ask for a reduced +iteration count or problem size rather than profiling a long run. Do not invent +a timeout; profile mode has no such option. + +## 3. Profile + +```bash +rocprof-compute profile --name -- +``` + +Output lands in `./workloads///`, or `/` under +MPI. Change it with `--output-directory`, which replaces `--name` for building +the path. Re-profiling into a non-empty directory needs `--overwrite`; prefer a +fresh directory. + +To restrict which GPU the application uses, set `HIP_VISIBLE_DEVICES`. Under +Slurm, derive it from `ROCR_VISIBLE_DEVICES`, not from `SLURM_STEP_GPUS`; the +indices differ. + +See [profile mode](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for output layout, +filtering, and the full option list. To profile a process that is already +running, see +[live attach and detach](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/live_attach_detach.rst). + +### Collect less when a full replay is too expensive + +A single-pass, topic-focused collection: + +```bash +rocprof-compute profile --list-sets +rocprof-compute profile --name --set -- +``` + +Or collect only named blocks: + +```bash +rocprof-compute profile --name -b sol wavefront -- +``` + +Iteration multiplexing spreads counters across kernel launches so a single pass +collects more: + +```bash +rocprof-compute profile --name --iteration-multiplexing kernel -- +``` + +Whenever you collect a subset, tell the user which analysis panels will be +empty and that answering those questions needs another profile. + +## 4. Find the hot kernel + +```bash +rocprof-compute analyze --path ./workloads// --list-stats +``` + +`--path` must point at the directory holding `profiling_config.yaml`. Analyze +does not search for it in nearby directories. If the path you have does not +contain that file, look one level down for the GPU model or rank directory. + +`--list-stats` prints `Detected Kernels (sorted descending by duration)` and a +`Dispatch list`. The kernel index is what `-k` takes. Dispatch ids are 1-based +and go to `-d`. + +Work on one kernel at a time: + +```bash +rocprof-compute analyze --path ./workloads// -k 0 +``` + +`-k` at profile time is different: it filters by kernel name and is used as a +regex, so `-k gemm` matches every kernel whose name contains `gemm`. + +## 5. Narrow to a reason + +Get the overview, then open exactly one detailed block: + +```bash +rocprof-compute analyze --path ./workloads// -k 0 -b sol +``` + +Read the `Percent of Peak` column. The report prints the peak for each metric, +so compare against that rather than against a remembered threshold, and never +compute a peak yourself. + +Resolve block names with aliases, which are stable across releases, instead of +numeric ids, which are architecture-specific: + +```bash +rocprof-compute --list-blocks +rocprof-compute analyze --path ./workloads// --list-available-metrics +``` + +Where to go next: + +| Symptom in Speed-of-Light | Next step | +|---|---| +| Memory throughput near peak, low compute | the `rocprof-compute-memory` skill | +| Compute near peak, or unclear compute vs memory | the `rocprof-compute-roofline` skill | +| Everything far below peak | `-b wavefront` for occupancy, then `-b spi` for launch limits, both below | +| Question is about instructions, source lines, or stall reasons | the `rocprof-compute-pc-sampling` skill | +| Kernels come from PyTorch | the `rocprof-compute-torch-trace` skill | + +### Occupancy and wavefront limits + +```bash +rocprof-compute analyze --path ./workloads// -k 0 -b wavefront +``` + +Compare active waves per CU against the theoretical occupancy in the same +table. When theoretical occupancy is itself low, the limit is VGPR, SGPR, or +LDS allocation. Scratch traffic points at register spilling. Low occupancy is +not a defect when the kernel already saturates its limiting resource. Worked +examples are in +[occupancy-limiters-example.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/tutorial/includes/occupancy-limiters-example.rst). + +### Scheduler and pipeline limits + +```bash +# Workgroup launch rate and resource allocation limits +rocprof-compute analyze --path ./workloads// -k 0 -b spi + +# Pipeline utilization and instruction mix +rocprof-compute analyze --path ./workloads// -k 0 -b cu_pipe +``` + +There is no single stall panel. Stall counters sit in the block that owns the +hardware: LDS stalls in `lds`, cache stalls in `vl1d` and `l2`, fabric stalls +in `l2_per_channel`. Open the block the Speed-of-Light result points at, and +read what each counter means in the +[performance model](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). Ground every +recommendation in a number you read from the report. + +## 6. Normalization + +`-n` / `--normal-unit` applies to every metric in the report at once: +`per_kernel` (default), `per_wave`, `per_cycle`, `per_second`. Keep the default +for a mixed report. Change it only when the user asks about one metric family +and the unit still makes sense for it. Bandwidth per cycle does not. + +## 7. Compare two runs + +Pass `--path` twice to put two workloads side by side. Use this for: + +1. Measuring a software optimization against its baseline on the same GPU. +2. Checking that the same code performs consistently across nodes with the + same GPU architecture. +3. Comparing the same code across different GPU architectures. + +```bash +rocprof-compute analyze \ + --path ./workloads/baseline/ \ + --path ./workloads/optimized/ +``` + +Cases 1 and 2 expect the numbers to match except where the change intended +otherwise, so an unexplained difference is the finding. Case 3 compares +different hardware, so compare percent of peak rather than absolute values. + +Never present a comparison across different GPUs, partition modes, or clock +settings without stating that those differ. + +## 8. Save a report + +Terminal output is the default. Only choose a format when the user asks to save +or export. `--output-format` takes `txt`, `csv`, or `db`, each of which +disables terminal output, and `--output-name` sets the file name. See +[analysis output format](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). + +## 9. When something looks wrong + +| Symptom | Likely cause | +|---|---| +| No workload data | `--path` is not the directory with `profiling_config.yaml` | +| Empty or missing panels | those counters were not collected; re-profile | +| All metrics zero | kernel too short, or no dispatches were captured | +| No kernel ids | the application launched no GPU kernels | +| Roofline missing | not collected, or unsupported on this architecture | + +Stop and report permission, firmware, and ROCm version failures instead of +working around them. More in the [FAQ](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/reference/faq.rst). diff --git a/skills/rocprof-compute-kernel-bottleneck/evals/evals.json b/skills/rocprof-compute-kernel-bottleneck/evals/evals.json new file mode 100644 index 0000000..d269e15 --- /dev/null +++ b/skills/rocprof-compute-kernel-bottleneck/evals/evals.json @@ -0,0 +1,101 @@ +{ + "evaluations": [ + { + "id": "kb-profile-hip-application", + "skill_should_trigger": true, + "prompt": "profile my HIP application with rocprof-compute and tell me where the time goes", + "expected_behavior": [ + "asks for the launch command and a workload name before profiling", + "runs rocprof-compute profile --name -- ", + "runs analyze with --list-stats to find the hottest kernel" + ], + "unexpected_behavior": [ + "invents a profiling flag instead of checking --help", + "opens the GUI or TUI" + ], + "logs_contain": ["rocprof-compute profile", "--list-stats"] + }, + { + "id": "kb-which-kernel-is-slow", + "skill_should_trigger": true, + "prompt": "which kernel is the hotspot in ./workloads/my_app and why is it slow?", + "expected_behavior": [ + "points --path at the directory containing profiling_config.yaml rather than a parent directory", + "uses --list-stats first, then narrows to one kernel id with -k", + "reads the System Speed-of-Light panel before opening a detailed block" + ], + "logs_contain": ["--list-stats", "-k"] + }, + { + "id": "kb-occupancy-limited", + "skill_should_trigger": true, + "prompt": "is occupancy limiting this rocprof-compute kernel?", + "expected_behavior": [ + "opens the wavefront block for the selected kernel", + "compares achieved occupancy against the theoretical occupancy printed in the report", + "ties any VGPR, SGPR, or LDS advice to numbers read from the report" + ], + "unexpected_behavior": [ + "calls low occupancy a defect without checking whether the kernel already saturates another resource" + ], + "logs_contain": ["wavefront"] + }, + { + "id": "kb-expensive-replay-fallback", + "skill_should_trigger": true, + "prompt": "a full rocprof-compute replay of this workload is too expensive, collect something cheaper", + "expected_behavior": [ + "offers a single-pass option such as --set, a narrowed -b, or --iteration-multiplexing", + "runs --list-sets before selecting a set", + "warns which analysis panels will be empty afterwards" + ], + "logs_contain": ["--list-sets"] + }, + { + "id": "kb-unsafe-to-replay", + "skill_should_trigger": true, + "prompt": "profile this one-shot ROCm job that writes to the production database", + "expected_behavior": [ + "warns that counter profiling replays the application multiple times", + "does not start profiling until replay safety is settled", + "offers a single-pass collection as an alternative" + ], + "unexpected_behavior": [ + "runs a multi-pass profile of a workload the user described as unsafe to repeat" + ] + }, + { + "id": "kb-kernel-name-filter", + "skill_should_trigger": true, + "prompt": "profile only the gemm kernels in my ROCm application", + "expected_behavior": [ + "uses rocprof-compute profile -k to filter by kernel name", + "treats the profile-time -k value as a regex matched against kernel names" + ], + "logs_contain": ["-k"] + }, + { + "id": "kb-cuda-application", + "skill_should_trigger": false, + "note": "Profiling request, but CUDA tooling is out of scope.", + "prompt": "profile my CUDA application with nsys" + }, + { + "id": "kb-windows-request", + "skill_should_trigger": false, + "note": "rocprof-compute is named, but the platform is unsupported.", + "prompt": "profile my application on Windows with rocprof-compute" + }, + { + "id": "kb-install-question", + "skill_should_trigger": false, + "prompt": "how do I install ROCm on Ubuntu?" + }, + { + "id": "kb-write-a-kernel", + "skill_should_trigger": false, + "note": "GPU performance adjacent, but this skill does not author kernel source.", + "prompt": "write me a faster HIP matrix multiply kernel" + } + ] +} diff --git a/skills/rocprof-compute-kernel-bottleneck/skill-card.md b/skills/rocprof-compute-kernel-bottleneck/skill-card.md new file mode 100644 index 0000000..0b0d344 --- /dev/null +++ b/skills/rocprof-compute-kernel-bottleneck/skill-card.md @@ -0,0 +1,13 @@ +# Skill Card + +## Description + +Profiles an application running on an AMD GPU with rocprof-compute and identifies which kernel is the bottleneck and why. + +## Owner + +rocprofiler-compute, a project in [rocm-systems](https://github.com/ROCm/rocm-systems/) + +## License + +MIT diff --git a/skills/rocprof-compute-memory/.federated.json b/skills/rocprof-compute-memory/.federated.json new file mode 100644 index 0000000..586c985 --- /dev/null +++ b/skills/rocprof-compute-memory/.federated.json @@ -0,0 +1,10 @@ +{ + "source": "rocm-rocm-systems", + "repo": "ROCm/rocm-systems", + "ref": "develop", + "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "path": "projects/rocprofiler-compute/skills/rocprof-compute-memory", + "license": "MIT", + "content_hash": "9d50ac838fdad2dfdf30007aeea7f8b16cdf0ee1bf8b4df6a101d3d02eead87a", + "imported_at": "2026-10-22T20:41:18Z" +} diff --git a/skills/rocprof-compute-memory/SKILL.md b/skills/rocprof-compute-memory/SKILL.md new file mode 100644 index 0000000..b2fb915 --- /dev/null +++ b/skills/rocprof-compute-memory/SKILL.md @@ -0,0 +1,110 @@ +--- +name: rocprof-compute-memory +description: Analyzes the AMD GPU memory hierarchy with rocprof-compute, covering the Memory Chart, vL1D and L2 caches, LDS bank conflicts, Infinity Fabric traffic, and the experimental gfx950 memory bandwidth analysis. Use when the user asks about memory bandwidth, cache hit rates, coalescing, LDS conflicts, data locality, HBM traffic, or says a kernel is memory-bound. Not for CUDA tools, Windows, host memory profiling, or system-wide tracing. +--- + +# Analyze the AMD GPU memory hierarchy + +Use this after Speed-of-Light points at memory, or when the user asks about +bandwidth, caches, or data movement directly. + +Run `rocprof-compute analyze --help` before choosing flags. +Never use the GUI or TUI. + +## 1. Start with the Memory Chart + +```bash +rocprof-compute analyze --path ./workloads// --list-stats +rocprof-compute analyze --path ./workloads// -k -b memchart +``` + +The Memory Chart shows the whole hierarchy at once: requests issued, what each +cache level served, and what reached HBM. Read it before opening any single +cache level, because it tells you which level to open. + +Always analyze one kernel at a time with `-k`. + +## 2. Open the level the chart points at + +```bash +# Vector L1 data cache +rocprof-compute analyze --path ./workloads// -k -b vl1d + +# L2 cache +rocprof-compute analyze --path ./workloads// -k -b l2 + +# L2 per channel, including Infinity Fabric read and write stalls +rocprof-compute analyze --path ./workloads// -k -b l2_per_channel + +# Local Data Share, including bank conflicts and unaligned stalls +rocprof-compute analyze --path ./workloads// -k -b lds + +# Address processing and data return, for coalescing behaviour +rocprof-compute analyze --path ./workloads// -k -b tatd + +# Scalar L1 data cache +rocprof-compute analyze --path ./workloads// -k -b sl1d +``` + +Aliases are stable; numeric block ids are architecture-specific. List what this +workload actually has with `--list-available-metrics`, and the aliases for an +architecture with `rocprof-compute --list-blocks `. + +## 3. Interpret + +Each level has its own reference, with the counters, the formulas, and what +they mean: + +- [Vector L1 cache](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/cdna/vector-l1-cache.rst) +- [L2 cache](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/cdna/l2-cache.rst) +- [Local Data Share](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/cdna/local-data-share.rst) +- [Infinity Fabric transactions](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/tutorial/includes/infinity-fabric-transactions.rst) +- [Vector memory operation counting](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/tutorial/includes/vector-memory-operation-counting.rst) +- [LDS examples](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/tutorial/includes/lds-examples.rst) + +Client APUs use a different hierarchy; see the +[RDNA performance model](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/rdna/rdna-performance-model.rst) +for GL0, GL1, and GL2. + +Read hit rates and utilizations against the `Percent of Peak` and `Peak` +columns the report prints. Do not apply a remembered cutoff such as "L2 hit +rate below 50% is bad"; a streaming kernel with no reuse is supposed to miss. +Judge a rate against what the algorithm should do, the neighbouring kernels, +and a known-good baseline. + +What usually follows from what: + +| Reading | Usual meaning | +|---|---| +| High request count, low cache hits | poor locality or no reuse to exploit | +| Many requests per instruction in TA/TD | uncoalesced access | +| Sustained LDS bank conflicts | padding or access reordering needed | +| High fabric read or write stalls | traffic exceeds what the fabric can absorb | +| Near-peak HBM with low cache hits | reduce traffic or add reuse; more bandwidth is not available | + +## 4. Memory bandwidth analysis (experimental, gfx950) + +A guided breakdown that walks the bandwidth tree and names the limiting level. +It is experimental and currently shipped for gfx950 only. It needs block 30 at +both profile and analyze time: + +```bash +rocprof-compute profile --experimental --membw-analysis --name -- + +rocprof-compute analyze --path ./workloads// \ + --experimental --membw-analysis -k +``` + +Its cutoffs and guidance text are data, not something to restate from memory. +They live in `src/membw_analysis/tree_spec/gfx950_membw_tree_spec.yaml` and +`src/membw_analysis/tree_spec/gfx950_membw_guidance.yaml`. Read those files +when you need to explain a verdict, and report the guidance the tool produced +rather than substituting your own numbers. + +On any other architecture, use the Memory Chart path above. + +## 5. Normalization + +For a bandwidth question, `-n per_second` is meaningful and `per_cycle` is not. +`-n` applies to the entire report, so switch it only when the user is asking +about bandwidth alone. Leave it at `per_kernel` for a mixed report. diff --git a/skills/rocprof-compute-memory/evals/evals.json b/skills/rocprof-compute-memory/evals/evals.json new file mode 100644 index 0000000..cca0782 --- /dev/null +++ b/skills/rocprof-compute-memory/evals/evals.json @@ -0,0 +1,79 @@ +{ + "evaluations": [ + { + "id": "mem-access-inefficiency", + "skill_should_trigger": true, + "prompt": "find the memory access inefficiencies in this rocprofiler-compute capture", + "expected_behavior": [ + "reads the memory chart before opening an individual cache level", + "cites hit rates or request counts from the report", + "ties any coalescing or tiling advice to those numbers" + ], + "unexpected_behavior": [ + "recommends an optimization without citing a metric from the report" + ], + "logs_contain": ["-b memchart"] + }, + { + "id": "mem-cache-hit-rates", + "skill_should_trigger": true, + "prompt": "what are my L2 and vL1D hit rates for this kernel and are they bad?", + "expected_behavior": [ + "opens the l2 and vl1d blocks for a single kernel id", + "judges the rates against what the algorithm should do rather than a fixed cutoff" + ], + "unexpected_behavior": [ + "applies a remembered threshold such as L2 below 50% being bad, as if the tool defined one" + ], + "logs_contain": ["-b"] + }, + { + "id": "mem-lds-bank-conflicts", + "skill_should_trigger": true, + "prompt": "does my kernel have LDS bank conflicts?", + "expected_behavior": [ + "opens the lds block for the selected kernel", + "reads the bank conflict counters from the report" + ], + "logs_contain": ["lds"] + }, + { + "id": "mem-bandwidth-bound", + "skill_should_trigger": true, + "prompt": "this kernel looks memory bound, where is the bandwidth going?", + "expected_behavior": [ + "walks the hierarchy from the memory chart down to the level responsible", + "distinguishes traffic that caches absorbed from traffic that reached HBM" + ], + "logs_contain": ["memchart"] + }, + { + "id": "mem-membw-analysis-gfx950", + "skill_should_trigger": true, + "prompt": "run the guided memory bandwidth analysis on my MI350 workload", + "expected_behavior": [ + "uses --experimental --membw-analysis", + "notes that the analysis is experimental and shipped for gfx950", + "reports the guidance the tool produced rather than substituting its own cutoffs" + ], + "logs_contain": ["--membw-analysis"] + }, + { + "id": "mem-host-memory", + "skill_should_trigger": false, + "note": "Memory question, but host-side allocation is not GPU counter analysis.", + "prompt": "my application is leaking host memory, help me find it" + }, + { + "id": "mem-cuda-memory", + "skill_should_trigger": false, + "prompt": "analyze the memory throughput of my CUDA kernel with Nsight" + }, + { + "id": "mem-gpu-oom", + "skill_should_trigger": false, + "note": "About allocation failure, not bandwidth or cache behaviour.", + "prompt": "why does my model run out of GPU memory during training?" + } + ] +} diff --git a/skills/rocprof-compute-memory/skill-card.md b/skills/rocprof-compute-memory/skill-card.md new file mode 100644 index 0000000..6f0244b --- /dev/null +++ b/skills/rocprof-compute-memory/skill-card.md @@ -0,0 +1,13 @@ +# Skill Card + +## Description + +Analyzes the AMD GPU memory hierarchy with rocprof-compute, from the Memory Chart down to caches, LDS, and memory bandwidth. + +## Owner + +rocprofiler-compute, a project in [rocm-systems](https://github.com/ROCm/rocm-systems/) + +## License + +MIT diff --git a/skills/rocprof-compute-pc-sampling/.federated.json b/skills/rocprof-compute-pc-sampling/.federated.json new file mode 100644 index 0000000..8a68f91 --- /dev/null +++ b/skills/rocprof-compute-pc-sampling/.federated.json @@ -0,0 +1,10 @@ +{ + "source": "rocm-rocm-systems", + "repo": "ROCm/rocm-systems", + "ref": "develop", + "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "path": "projects/rocprofiler-compute/skills/rocprof-compute-pc-sampling", + "license": "MIT", + "content_hash": "85efa3ef362aa8c28550a059b5ba78bb45a7ee239778fa9b63be0e11508c67cf", + "imported_at": "2026-10-22T20:41:18Z" +} diff --git a/skills/rocprof-compute-pc-sampling/SKILL.md b/skills/rocprof-compute-pc-sampling/SKILL.md new file mode 100644 index 0000000..2191683 --- /dev/null +++ b/skills/rocprof-compute-pc-sampling/SKILL.md @@ -0,0 +1,118 @@ +--- +name: rocprof-compute-pc-sampling +description: Collects and analyzes rocprof-compute PC sampling to show which individual instructions in an AMD GPU kernel are hot and why wavefronts stall there, with ISA and source line attribution. Use when the user asks which instruction or source line is slow, asks about stall reasons, wants instruction-level or ISA-level detail, or wants hotspots inside a single kernel. Not for CUDA tools, Windows, or whole-application timing. +--- + +# PC sampling + +PC sampling answers a question counters cannot: which instruction inside the +kernel is hot, and what the wavefront was waiting on. Use it after counters +have already identified the kernel worth this level of detail. + +PC sampling is experimental. Every command below needs `--experimental`, and +option names can change between releases, so run +`rocprof-compute profile --experimental --help` before choosing flags. +Never use the GUI or TUI. + +## 1. Check what the hardware supports + +```bash +rocprofv3-avail info --pc-sampling +``` + +This reports the sampling methods the installed device accepts and the valid +interval range. Two methods exist: + +- `stochastic` samples in hardware and is the only method that records a stall + reason. It requires MI300 or newer. +- `host_trap` works more widely but records no stall reason, and its samples + can land a little past the instruction responsible. + +`--pc-sampling-method` defaults to `stochastic`. Fall back to `host_trap` only +when the device rejects stochastic. + +Never hardcode an interval. Stochastic intervals are counted in cycles and must +be a power of two; host-trap intervals are microseconds. The accepted range +comes from the device, so take it from the command above. + +## 2. Build for source attribution + +Samples always map to the kernel's assembly. Mapping assembly back to a source +line requires debug info in the application, for example `hipcc -g`. Without +it, `source_line` reads `N/A` and the `Source` column in CSV output is empty. +Keep optimization flags on; profiling a `-O0` build measures the wrong program. + +Ask about the build before collecting, because rebuilding afterwards means +re-profiling. + +## 3. Collect + +```bash +rocprof-compute profile \ + --experimental \ + --pc-sampling \ + --pc-sampling-method stochastic \ + --pc-sampling-interval \ + --name \ + -- +``` + +PC sampling does not collect counters. A PC-sampling-only profile cannot be +combined with the framework tracing options, and it will not populate the +counter panels. When the user needs both, collect two workloads. + +## 4. Analyze + +```bash +rocprof-compute analyze --path ./workloads// --list-stats + +rocprof-compute analyze \ + --path ./workloads// \ + -k \ + --pc-sampling-sorting-type count \ + --pc-sampling-rows 20 +``` + +Sorting defaults to `count`, which puts the hottest instruction first. Use +`offset` to read the kernel in program order instead. `--pc-sampling-rows` +defaults to 10; pass `0` for every row. + +Read the table as: + +| Column | Meaning | +|---|---| +| PC offset | where the instruction sits in the kernel's ISA | +| Sample count | how often execution was there | +| Stall reason | what the wavefront was waiting on, stochastic sampling only | + +A high sample count marks a hot instruction, not a defective one. A loop body +is supposed to be hot. What matters is a hot instruction whose stall reason +says it is waiting: a memory stall on a load means the kernel is latency-bound +there, and the fix is usually coalescing, prefetching, or more independent work +between the load and its use. + +Stall reasons and what each one implies are listed in +[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). Do not interpret a stall +reason from host-trap data; it does not record one. + +## 5. Export annotated disassembly + +```bash +rocprof-compute analyze --path ./workloads// \ + --output-format csv --output-name pc_report +``` + +CSV output writes per-kernel annotated disassembly into a +`per_kernel_pc_sampling/` directory, with sample counts and source lines beside +each instruction. `--output-format db` stores the same content in the analysis +database. Both disable terminal output. + +## 6. When there are too few samples + +A short kernel may not accumulate enough samples to be meaningful. Increase the +work the kernel does, or shorten the interval within the range the device +accepts, rather than drawing conclusions from a handful of samples. + +For multi-process workloads, keep each process or rank separate; see the +multi-process sections of +[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). diff --git a/skills/rocprof-compute-pc-sampling/evals/evals.json b/skills/rocprof-compute-pc-sampling/evals/evals.json new file mode 100644 index 0000000..e3972c5 --- /dev/null +++ b/skills/rocprof-compute-pc-sampling/evals/evals.json @@ -0,0 +1,79 @@ +{ + "evaluations": [ + { + "id": "pcs-which-instruction-stalls", + "skill_should_trigger": true, + "prompt": "which instruction in my MI300 kernel stalls the most?", + "expected_behavior": [ + "queries the device for supported PC sampling configurations before choosing an interval", + "collects with --experimental --pc-sampling", + "sorts the analysis by sample count and reads the stall reason column" + ], + "unexpected_behavior": [ + "hardcodes a sampling interval without querying the device" + ], + "logs_contain": ["--pc-sampling", "rocprofv3-avail"] + }, + { + "id": "pcs-source-line-attribution", + "skill_should_trigger": true, + "prompt": "map the hot GPU instructions back to my HIP source lines", + "expected_behavior": [ + "states that source line mapping requires the application to be built with debug info", + "notes that samples map to assembly even without debug info" + ], + "unexpected_behavior": [ + "promises source line attribution for a build with no debug info" + ] + }, + { + "id": "pcs-stochastic-vs-host-trap", + "skill_should_trigger": true, + "prompt": "should I use stochastic or host trap PC sampling on this GPU?", + "expected_behavior": [ + "explains that only stochastic records a stall reason", + "notes stochastic needs MI300 or newer and host_trap is the fallback", + "checks what the installed device actually supports" + ], + "logs_contain": ["stochastic"] + }, + { + "id": "pcs-export-disassembly", + "skill_should_trigger": true, + "prompt": "export the annotated disassembly with sample counts from my pc sampling workload", + "expected_behavior": [ + "uses --output-format csv or db", + "notes that the per-kernel disassembly is written under per_kernel_pc_sampling" + ], + "logs_contain": ["--output-format"] + }, + { + "id": "pcs-host-trap-stall-reason", + "skill_should_trigger": true, + "prompt": "my host trap PC sampling run has no stall reasons, how do I interpret them?", + "expected_behavior": [ + "explains that host_trap records no stall reason", + "offers stochastic sampling if the device supports it" + ], + "unexpected_behavior": [ + "interprets a stall reason from host-trap data" + ] + }, + { + "id": "pcs-rank-kernels-by-time", + "skill_should_trigger": false, + "note": "Hotspot wording, but ranking whole kernels is counter work, not instruction sampling.", + "prompt": "which of my GPU kernels accounts for the most total time?" + }, + { + "id": "pcs-cpu-profiler", + "skill_should_trigger": false, + "prompt": "profile my C++ program's CPU hotspots with perf" + }, + { + "id": "pcs-cuda-sampling", + "skill_should_trigger": false, + "prompt": "do instruction level sampling on my CUDA kernel" + } + ] +} diff --git a/skills/rocprof-compute-pc-sampling/skill-card.md b/skills/rocprof-compute-pc-sampling/skill-card.md new file mode 100644 index 0000000..bf8bb89 --- /dev/null +++ b/skills/rocprof-compute-pc-sampling/skill-card.md @@ -0,0 +1,13 @@ +# Skill Card + +## Description + +Uses rocprof-compute PC sampling to show which instructions in an AMD GPU kernel are hot and why they stall. + +## Owner + +rocprofiler-compute, a project in [rocm-systems](https://github.com/ROCm/rocm-systems/) + +## License + +MIT diff --git a/skills/rocprof-compute-roofline/.federated.json b/skills/rocprof-compute-roofline/.federated.json new file mode 100644 index 0000000..bac0f1d --- /dev/null +++ b/skills/rocprof-compute-roofline/.federated.json @@ -0,0 +1,10 @@ +{ + "source": "rocm-rocm-systems", + "repo": "ROCm/rocm-systems", + "ref": "develop", + "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "path": "projects/rocprofiler-compute/skills/rocprof-compute-roofline", + "license": "MIT", + "content_hash": "c41c2988d7cb02a577f6bbb91054ee7b471dae0ddc3c4930a36c7d6ef951d144", + "imported_at": "2026-10-22T20:41:18Z" +} diff --git a/skills/rocprof-compute-roofline/SKILL.md b/skills/rocprof-compute-roofline/SKILL.md new file mode 100644 index 0000000..d4b6577 --- /dev/null +++ b/skills/rocprof-compute-roofline/SKILL.md @@ -0,0 +1,114 @@ +--- +name: rocprof-compute-roofline +description: Collects and reads rocprof-compute roofline data to place an AMD GPU kernel against the hardware's peak compute and peak bandwidth ceilings. Use when the user asks whether a kernel is compute-bound or memory-bound, asks about arithmetic intensity or FLOPs per byte, asks how much performance is left on the table, or asks for a roofline. Not for CUDA tools, Windows, or per-instruction analysis. +--- + +# Roofline analysis + +A roofline places a kernel's arithmetic intensity against two measured +ceilings, peak compute and peak bandwidth, and answers one question: which of +the two is the limit. + +Run `rocprof-compute profile --help` and `rocprof-compute analyze --help` +before choosing flags. Never use the GUI or TUI, and do not open the generated +HTML chart. The analyze table carries the same numbers in readable form. + +## 1. Collect + +Roofline is collected by default, so an ordinary profile already has it: + +```bash +rocprof-compute profile --name -- +``` + +Collecting it runs microbenchmarks in addition to profiling the application, +which takes extra time. `--no-roof` skips them when the user only wants +counters. + +MI100 (gfx908) does not support the roofline microbenchmarks. Every other +supported architecture does. On MI100 the benchmark is skipped and the profile +still succeeds, so a missing roofline there is not a mistake in how the profile +was run. Never tell an MI100 user to re-profile without `--no-roof`; use +Speed-of-Light and the `rocprof-compute-memory` skill instead. Confirm support +for a new architecture against +[compatible-accelerators.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst). + +`--device` selects the GPU for the roofline microbenchmarks. It does not +choose which GPU the application runs on; use `HIP_VISIBLE_DEVICES` for that. + +See [standalone roofline](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for `--roof-only` +and `--bench-only`, which run the microbenchmarks without profiling an +application. + +## 2. Read the table + +```bash +rocprof-compute analyze --path ./workloads// --list-stats +rocprof-compute analyze --path ./workloads// -k -b roof +``` + +Always pass one kernel id. The table gives the kernel's arithmetic intensity in +FLOPs per byte alongside the empirical peak FLOPs and peak bandwidth measured +on this machine. Compare against those printed numbers; never compute a peak +yourself. + +Report two things first, before any tuning advice: + +1. **Which ceiling limits the kernel**, memory or compute. +2. **How far the kernel sits below that ceiling**, as a percentage of it. + +Read the verdict from the table as: + +- Low arithmetic intensity, close to the bandwidth ceiling: memory-bound. Work + on traffic, reuse, tiling, and locality. Continue with the + `rocprof-compute-memory` skill. +- High arithmetic intensity, close to the compute ceiling: compute-bound. Work + on instruction mix, matrix instruction use, and divergence. +- Well below both ceilings: neither is the limit. The kernel is held back by + occupancy, launch configuration, or stalls. Go back to the + `rocprof-compute-kernel-bottleneck` skill. + +A kernel sitting below both ceilings is the common case and the most +misread one. Never report it as compute-bound merely because its arithmetic +intensity is high. + +The distance to the ceiling is what makes the verdict actionable. "Memory-bound +at 45% of peak bandwidth" tells the user there is headroom; "memory-bound at +94%" tells them to reduce traffic instead of chasing it. + +## 3. Pick the right precision and memory level + +Section 2 gives the verdict against the default ceilings. Refine it here when +the kernel does not use FP32, or when the question is about a specific level of +the hierarchy. + +```bash +# Ceilings for the precision the kernel actually uses +rocprof-compute analyze --path ./workloads// -k -b roof \ + -R FP16 BF16 + +# Ceilings for a specific level +rocprof-compute analyze --path ./workloads// -k -b roof \ + -m HBM L2 +``` + +`-R` defaults to FP32. A mixed-precision or matrix kernel compared against the +FP32 ceiling will look far worse than it is, so set `-R` to the precision the +kernel actually issues. Read that from the instruction mix with `-b cu_ins` +rather than assuming it. Ask the user which precision the kernel is meant to +use only when the mix is genuinely mixed and no single type dominates; they +know the intent, the counters know what ran. + +`-m` defaults to every level. HBM answers "am I limited by main memory", while +L2 and vL1D show whether caches are absorbing the traffic. + +## 4. When roofline data is missing + +| Cause | What to do | +|---|---| +| Profiled with `--no-roof` | re-profile without it | +| Architecture has no microbenchmark support | use Speed-of-Light and the `rocprof-compute-memory` skill instead | +| `roofline.csv` absent from the workload directory | the benchmark did not complete; check the profile log | + +The concepts behind the ceilings are in the +[performance model](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). diff --git a/skills/rocprof-compute-roofline/evals/evals.json b/skills/rocprof-compute-roofline/evals/evals.json new file mode 100644 index 0000000..a435b00 --- /dev/null +++ b/skills/rocprof-compute-roofline/evals/evals.json @@ -0,0 +1,70 @@ +{ + "evaluations": [ + { + "id": "roof-compute-or-memory-bound", + "skill_should_trigger": true, + "prompt": "is this rocprof-compute kernel compute bound or memory bound?", + "expected_behavior": [ + "analyzes the roof block for a single kernel id", + "compares arithmetic intensity against the empirical peaks printed in the table", + "says when the kernel is below both ceilings instead of forcing a bound" + ], + "unexpected_behavior": [ + "opens the generated roofline HTML chart instead of reading the table", + "calculates a peak instead of using the measured one" + ], + "logs_contain": ["-b roof"] + }, + { + "id": "roof-arithmetic-intensity", + "skill_should_trigger": true, + "prompt": "what is the arithmetic intensity of my hottest kernel and how much performance am I leaving on the table?", + "expected_behavior": [ + "reads FLOPs per byte and the peak values from the roofline table", + "narrows to one kernel id first" + ], + "logs_contain": ["roof"] + }, + { + "id": "roof-precision-selection", + "skill_should_trigger": true, + "prompt": "my half precision GEMM looks terrible on the roofline, is that right?", + "expected_behavior": [ + "sets -R to the precision the kernel actually issues instead of the FP32 default", + "confirms the precision from the instruction mix rather than assuming it" + ], + "unexpected_behavior": [ + "compares a half precision kernel against the FP32 ceiling and calls it slow" + ], + "logs_contain": ["-R"] + }, + { + "id": "roof-missing-data", + "skill_should_trigger": true, + "prompt": "my rocprof-compute report has no roofline section, what went wrong?", + "expected_behavior": [ + "checks whether the profile used --no-roof", + "checks whether the architecture supports the roofline microbenchmarks before advising a re-profile" + ], + "unexpected_behavior": [ + "tells the user to re-profile without --no-roof when the architecture is the actual reason" + ] + }, + { + "id": "roof-generic-optimization", + "skill_should_trigger": false, + "prompt": "how can I make my application faster?" + }, + { + "id": "roof-cuda-roofline", + "skill_should_trigger": false, + "prompt": "generate a roofline for my CUDA kernel with Nsight Compute" + }, + { + "id": "roof-concept-question", + "skill_should_trigger": false, + "note": "Asks what a roofline is in the abstract, with no workload to analyze.", + "prompt": "explain the roofline performance model to me" + } + ] +} diff --git a/skills/rocprof-compute-roofline/skill-card.md b/skills/rocprof-compute-roofline/skill-card.md new file mode 100644 index 0000000..120ea14 --- /dev/null +++ b/skills/rocprof-compute-roofline/skill-card.md @@ -0,0 +1,13 @@ +# Skill Card + +## Description + +Places an AMD GPU kernel against measured peak compute and peak bandwidth ceilings with rocprof-compute roofline data. + +## Owner + +rocprofiler-compute, a project in [rocm-systems](https://github.com/ROCm/rocm-systems/) + +## License + +MIT diff --git a/skills/rocprof-compute-speed-of-light/.federated.json b/skills/rocprof-compute-speed-of-light/.federated.json new file mode 100644 index 0000000..25aa47f --- /dev/null +++ b/skills/rocprof-compute-speed-of-light/.federated.json @@ -0,0 +1,10 @@ +{ + "source": "rocm-rocm-systems", + "repo": "ROCm/rocm-systems", + "ref": "develop", + "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "path": "projects/rocprofiler-compute/skills/rocprof-compute-speed-of-light", + "license": "MIT", + "content_hash": "3005a4c9ac299ac3279240837cf5fe8f6141e130b72fb61cbf21ae2fffee4194", + "imported_at": "2026-10-22T20:41:18Z" +} diff --git a/skills/rocprof-compute-speed-of-light/SKILL.md b/skills/rocprof-compute-speed-of-light/SKILL.md new file mode 100644 index 0000000..4277bec --- /dev/null +++ b/skills/rocprof-compute-speed-of-light/SKILL.md @@ -0,0 +1,79 @@ +--- +name: rocprof-compute-speed-of-light +description: Reads the rocprof-compute System Speed-of-Light panel to show how close an AMD GPU kernel runs to hardware peak for compute throughput, memory bandwidth, and utilization. Use when the user asks how close to peak a kernel is, what percent of peak it reaches, whether a measured percentage is good enough or normal, whether a utilization number should be higher, or asks for an overall efficiency summary. Not for CUDA tools, Windows, per-instruction analysis, or looking up a datasheet specification when there is no profile to read. +--- + +# Read System Speed-of-Light + +Speed-of-Light is the first panel to read after profiling. It puts every +headline metric next to the hardware peak for the current GPU, so one table +says whether a kernel is near any limit at all. + +Run `rocprof-compute analyze --help` before choosing flags. +Never use the GUI or TUI. + +## 1. Get the panel + +```bash +rocprof-compute analyze --path ./workloads// --list-stats +rocprof-compute analyze --path ./workloads// -k -b sol +``` + +`--path` points at the directory containing `profiling_config.yaml`. Always +pass `-k` with one kernel id from `--list-stats`; a Speed-of-Light table +averaged over unrelated kernels means nothing. + +If the panel is missing or empty, those counters were not collected. Collect +them with the `rocprof-compute-kernel-bottleneck` skill, using `-b sol` at +profile time for a cheap single-topic run. + +## 2. Read it + +The table has `Metric`, `Avg`, `Unit`, `Peak`, and `Percent of Peak` columns. +The peak is already computed for the detected architecture. Compare `Avg` +against the `Peak` in the same row and never calculate a peak yourself. + +`Percent of Peak` is `N/A` for metrics that are already percentages, such as +the utilization rows. Read those as the percentage they are. + +What the shape of the table tells you: + +| Reading | Meaning | Next step | +|---|---|---| +| A memory row near peak | bandwidth-limited | the `rocprof-compute-memory` skill | +| An MFMA or VALU row near peak | compute-limited | the `rocprof-compute-roofline` skill | +| Every row far below peak | not limited by throughput | occupancy and launch limits, via the `rocprof-compute-kernel-bottleneck` skill | +| MFMA at zero on a GEMM | matrix pipeline unused | check the kernel's instruction mix with `-b cu_ins` | + +A row at zero is a real reading only if the kernel was expected to use that +unit. MFMA at zero is normal for a kernel with no matrix instructions. + +Do not apply remembered thresholds. "Above 70% is saturated" is not a rule the +tool encodes, and the correct comparison is always against the printed peak, +the neighbouring kernels, and a known-good baseline for the same workload. + +## 3. Understand the metrics + +Each metric's definition and the counters behind it are in +[system-speed-of-light.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/cdna/system-speed-of-light.rst) +for CDNA accelerators and +[the RDNA equivalent](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/rdna/system-speed-of-light.rst) +for client APUs. The whole metric hierarchy is in the +[performance model](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). + +## 4. Compute Speed-of-Light + +The compute pipeline has its own Speed-of-Light table with the same shape: + +```bash +rocprof-compute analyze --path ./workloads// -k -b cu_pipe +``` + +Read it when the system-level table shows compute near peak and you need to +know which pipeline is responsible. + +## 5. Normalization + +`-n` changes the unit for the entire report, not one metric. Leave it at +`per_kernel` for Speed-of-Light. The peaks are expressed to match the default, +and switching units makes the peak comparison harder to read, not easier. diff --git a/skills/rocprof-compute-speed-of-light/evals/evals.json b/skills/rocprof-compute-speed-of-light/evals/evals.json new file mode 100644 index 0000000..c26b5a8 --- /dev/null +++ b/skills/rocprof-compute-speed-of-light/evals/evals.json @@ -0,0 +1,72 @@ +{ + "evaluations": [ + { + "id": "sol-percent-of-peak", + "skill_should_trigger": true, + "prompt": "how close to peak is this kernel running in my rocprof-compute workload?", + "expected_behavior": [ + "analyzes the sol block for a single kernel id", + "reads the Percent of Peak column from the report", + "compares against the Peak value the report prints rather than a remembered threshold" + ], + "unexpected_behavior": [ + "calculates a theoretical peak instead of using the one in the report" + ], + "logs_contain": ["-b sol"] + }, + { + "id": "sol-overall-efficiency", + "skill_should_trigger": true, + "prompt": "give me an overall efficiency summary of this rocprofiler-compute capture", + "expected_behavior": [ + "selects one kernel with -k before reading Speed-of-Light", + "summarizes using the printed peak and percent of peak values" + ], + "unexpected_behavior": [ + "reports a Speed-of-Light table averaged across unrelated kernels" + ], + "logs_contain": ["-b sol"] + }, + { + "id": "sol-mfma-zero", + "skill_should_trigger": true, + "prompt": "my rocprof-compute report shows MFMA FLOPs at zero, is that a bug?", + "expected_behavior": [ + "explains that zero is expected when the kernel issues no matrix instructions", + "suggests confirming the instruction mix before treating it as a problem" + ], + "unexpected_behavior": [ + "treats zero MFMA as a defect without checking whether the kernel should use matrix instructions" + ] + }, + { + "id": "sol-threshold-pushback", + "skill_should_trigger": true, + "prompt": "my kernel is at 62% of peak memory bandwidth, is that good enough?", + "expected_behavior": [ + "declines to apply a fixed pass or fail cutoff", + "suggests comparing against neighbouring kernels or a known-good baseline" + ], + "unexpected_behavior": [ + "states a universal threshold such as 70% as if the tool defined one" + ] + }, + { + "id": "sol-instruction-level", + "skill_should_trigger": false, + "note": "Peak comparison is not the question; this is per-instruction work.", + "prompt": "which individual instruction in this kernel stalls the most?" + }, + { + "id": "sol-cuda-peak", + "skill_should_trigger": false, + "prompt": "what percent of peak is my CUDA kernel hitting in Nsight Compute?" + }, + { + "id": "sol-hardware-spec-lookup", + "skill_should_trigger": false, + "note": "Asks for a datasheet number, not a reading from a profile.", + "prompt": "what is the theoretical peak FP64 throughput of an MI300X?" + } + ] +} diff --git a/skills/rocprof-compute-speed-of-light/skill-card.md b/skills/rocprof-compute-speed-of-light/skill-card.md new file mode 100644 index 0000000..eac7e0e --- /dev/null +++ b/skills/rocprof-compute-speed-of-light/skill-card.md @@ -0,0 +1,13 @@ +# Skill Card + +## Description + +Reads the rocprof-compute System Speed-of-Light panel to show how close an AMD GPU kernel runs to hardware peak. + +## Owner + +rocprofiler-compute, a project in [rocm-systems](https://github.com/ROCm/rocm-systems/) + +## License + +MIT diff --git a/skills/rocprof-compute-torch-trace/.federated.json b/skills/rocprof-compute-torch-trace/.federated.json new file mode 100644 index 0000000..cb82e6d --- /dev/null +++ b/skills/rocprof-compute-torch-trace/.federated.json @@ -0,0 +1,10 @@ +{ + "source": "rocm-rocm-systems", + "repo": "ROCm/rocm-systems", + "ref": "develop", + "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "path": "projects/rocprofiler-compute/skills/rocprof-compute-torch-trace", + "license": "MIT", + "content_hash": "ef7a5100a32ef30aea6a1bfd3ede8bbdad07bcdd1e48c512e4fb732023487b5f", + "imported_at": "2026-10-22T20:41:18Z" +} diff --git a/skills/rocprof-compute-torch-trace/SKILL.md b/skills/rocprof-compute-torch-trace/SKILL.md new file mode 100644 index 0000000..389a505 --- /dev/null +++ b/skills/rocprof-compute-torch-trace/SKILL.md @@ -0,0 +1,116 @@ +--- +name: rocprof-compute-torch-trace +description: Attributes AMD GPU kernel performance counters back to the PyTorch or Triton operators that launched them, using rocprof-compute torch and triton tracing. Use when the user asks which PyTorch layer or operator is slow, wants to map GPU kernels to model code, profiles a training or inference script, or asks about torch.compile and Inductor kernels. Not for CUDA tools, Windows, non-Python applications, or user-authored ROCTx ranges. +--- + +# Map GPU kernels to PyTorch operators + +Profiling a PyTorch model with counters alone gives kernel names, which say +little about the model. Torch tracing annotates the run so each kernel can be +traced back to the operator that launched it. + +Torch and Triton tracing are experimental. Every command needs +`--experimental`, and option names can change between releases, so run +`rocprof-compute profile --experimental --help` first. +Never use the GUI or TUI. + +## 1. Check the requirements + +Torch tracing needs PyTorch 2.13 or 2.14, installed against a matching ROCm. +The workload must be a Python command or Python script; the tracer cannot +annotate a compiled binary. The full requirement list, including the Python +version matching constraint, is in the Torch trace section of +[profile mode](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). + +Confirm the PyTorch version before profiling. A mismatched version fails at +collection time, after the run has already cost the user its runtime. + +## 2. Collect + +```bash +rocprof-compute profile \ + --experimental \ + --torch-trace \ + --name \ + -- python +``` + +For Triton kernels, including those generated by `torch.compile` and Inductor: + +```bash +rocprof-compute profile --experimental --triton-trace --name -- python +``` + +The two combine, and `--ml-api-trace` turns on every supported framework +backend at once. + +Tracing adds overhead on top of the usual counter replay. Profile a few +iterations of a steady-state model rather than a full training run, and skip +warmup. `ROCPROFCOMPUTE_ROCTX_DEEP_TENSOR_WRAPS=0` reduces instrumentation +overhead by dropping the device and layout wraps; see +[profile mode](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for what that costs you. + +Tracing cannot be combined with attaching to a running process, and cannot be +combined with a PC-sampling-only profile. + +When counters are being spread thin across many short operator kernels, +`--iteration-multiplexing kernel` collects more per pass. + +## 3. List the captured operators + +Always list before filtering. Operator names come from the model that ran, so +guessing a pattern wastes a profile: + +```bash +rocprof-compute analyze \ + --path ./workloads// \ + --experimental --list-torch-operators +``` + +This prints the operators as a call tree grouped by source location, with +kernel launch statistics, so it also shows which operators are worth opening. + +The Triton equivalent is `--list-triton-operators`. + +## 4. Filter to an operator + +```bash +rocprof-compute analyze \ + --path ./workloads// \ + --experimental --torch-operator '*conv2d*' +``` + +Patterns are shell-style globs, not regexes, matched against a `/`-separated +operator hierarchy. `*relu` ends with relu, `*conv*` contains conv, +`*/*functional*/*` matches an intermediate component, and `all` or `'*'` +matches everything. Several patterns can be given, space or comma separated. +Quote patterns so the shell does not expand them. + +Combine with `-k` to intersect an operator with specific kernel ids, and with +`-b` to look at one metric block for that operator's kernels. `--triton-operator` +works the same way. + +Worked examples are in the PyTorch operator analysis section of +[the analyze CLI guide](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). + +## 5. Read the result + +Attribution tells you which operator owns the kernel time. It does not tell you +why that kernel is slow. Once an operator is identified, analyze its kernels +like any other: Speed-of-Light first, then the memory or roofline path. + +An operator that is slow because it launches thousands of tiny kernels is a +different problem from one slow kernel, and the launch statistics in the +operator list are what distinguish the two. + +## 6. ROCTx scope + +rocprof-compute uses ROCTx internally: torch tracing injects ROCTx regions to +annotate operators, which is what makes attribution possible. That is the only +place ROCTx is involved. + +rocprof-compute does not support ROCTx-range-based profiling control, such as +starting, stopping, or resuming collection at a user-authored ROCTx range, and +does not attribute results to ranges a user added to their own application. If +that is what the user wants, say so plainly and offer kernel filtering with +`-k` or dispatch filtering with `-d` instead. diff --git a/skills/rocprof-compute-torch-trace/evals/evals.json b/skills/rocprof-compute-torch-trace/evals/evals.json new file mode 100644 index 0000000..5d59ea3 --- /dev/null +++ b/skills/rocprof-compute-torch-trace/evals/evals.json @@ -0,0 +1,83 @@ +{ + "evaluations": [ + { + "id": "tt-which-operator-is-slow", + "skill_should_trigger": true, + "prompt": "which PyTorch operator is launching the slow GPU kernels in my model?", + "expected_behavior": [ + "collects with --experimental --torch-trace", + "lists the captured operators before filtering", + "checks the documented PyTorch version requirement" + ], + "unexpected_behavior": [ + "guesses an operator pattern without listing the captured operators first" + ], + "logs_contain": ["--torch-trace", "--list-torch-operators"] + }, + { + "id": "tt-map-kernels-to-model-code", + "skill_should_trigger": true, + "prompt": "profile my training script and tell me which layer the GPU time belongs to", + "expected_behavior": [ + "profiles the Python script with torch tracing enabled", + "advises profiling a few steady-state iterations rather than a full run" + ], + "logs_contain": ["--torch-trace"] + }, + { + "id": "tt-triton-inductor-kernels", + "skill_should_trigger": true, + "prompt": "my model uses torch.compile, how do I attribute the generated Triton kernels?", + "expected_behavior": [ + "uses --triton-trace, optionally combined with --torch-trace", + "lists the captured Triton kernels before filtering" + ], + "logs_contain": ["--triton-trace"] + }, + { + "id": "tt-filter-by-operator", + "skill_should_trigger": true, + "prompt": "show me the counters for just the conv2d operators in this torch-trace workload", + "expected_behavior": [ + "lists operators first, then filters with --torch-operator", + "treats the pattern as a shell-style glob rather than a regex" + ], + "logs_contain": ["--torch-operator"] + }, + { + "id": "tt-custom-roctx-unsupported", + "skill_should_trigger": true, + "prompt": "I added my own ROCTx ranges to my HIP application, profile and report on those ranges", + "expected_behavior": [ + "says plainly that rocprof-compute does not attribute results to user-authored ROCTx ranges", + "offers kernel filtering with -k or dispatch filtering with -d instead" + ], + "unexpected_behavior": [ + "claims rocprof-compute can profile, filter, or start and stop collection on user-authored ROCTx ranges" + ] + }, + { + "id": "tt-non-python-binary", + "skill_should_trigger": true, + "prompt": "attribute GPU kernels to functions in my compiled C++ HIP binary", + "expected_behavior": [ + "says that operator attribution requires a Python workload and cannot annotate a compiled binary", + "offers kernel filtering with -k or dispatch filtering with -d instead" + ], + "unexpected_behavior": [ + "claims torch or triton tracing can attribute kernels to functions in a compiled binary" + ] + }, + { + "id": "tt-cuda-pytorch", + "skill_should_trigger": false, + "prompt": "profile my PyTorch model on an NVIDIA GPU with the torch profiler" + }, + { + "id": "tt-model-accuracy", + "skill_should_trigger": false, + "note": "PyTorch question with no performance profiling in it.", + "prompt": "why is my PyTorch model's validation accuracy dropping?" + } + ] +} diff --git a/skills/rocprof-compute-torch-trace/skill-card.md b/skills/rocprof-compute-torch-trace/skill-card.md new file mode 100644 index 0000000..02bdddc --- /dev/null +++ b/skills/rocprof-compute-torch-trace/skill-card.md @@ -0,0 +1,13 @@ +# Skill Card + +## Description + +Attributes AMD GPU kernel performance back to the PyTorch and Triton operators that launched them with rocprof-compute. + +## Owner + +rocprofiler-compute, a project in [rocm-systems](https://github.com/ROCm/rocm-systems/) + +## License + +MIT From 5d03f010269a5c5a1ebf839749741b9174b7e522 Mon Sep 17 00:00:00 2001 From: "fei.zheng" Date: Mon, 26 Oct 2026 14:16:58 -0600 Subject: [PATCH 2/6] Refresh the rocprof-compute skills from rocm-systems#14099. develop does not have those eval changes yet, so this import uses that pull request's head and keeps each skill's machine.yml here, where skillscope selects the MI300 runner. Co-authored-by: Cursor --- .../.federated.json | 6 +- .../SKILL.md | 38 +++++++---- .../evals/evals.json | 18 ++++-- .../evals/machine.yml | 2 + skills/rocprof-compute-memory/.federated.json | 6 +- skills/rocprof-compute-memory/SKILL.md | 44 ++++++++----- .../rocprof-compute-memory/evals/evals.json | 15 +++-- .../rocprof-compute-memory/evals/machine.yml | 2 + .../.federated.json | 6 +- skills/rocprof-compute-pc-sampling/SKILL.md | 25 ++++++-- .../evals/evals.json | 9 ++- .../evals/machine.yml | 2 + .../rocprof-compute-roofline/.federated.json | 6 +- skills/rocprof-compute-roofline/SKILL.md | 63 +++++++++--------- .../rocprof-compute-roofline/evals/evals.json | 13 ++-- .../evals/machine.yml | 2 + .../.federated.json | 6 +- .../rocprof-compute-speed-of-light/SKILL.md | 19 ++++-- .../evals/evals.json | 6 +- .../evals/machine.yml | 2 + .../.federated.json | 6 +- skills/rocprof-compute-torch-trace/SKILL.md | 64 +++++++++++-------- .../evals/evals.json | 18 ++++-- .../evals/machine.yml | 2 + 24 files changed, 237 insertions(+), 143 deletions(-) create mode 100644 skills/rocprof-compute-kernel-bottleneck/evals/machine.yml create mode 100644 skills/rocprof-compute-memory/evals/machine.yml create mode 100644 skills/rocprof-compute-pc-sampling/evals/machine.yml create mode 100644 skills/rocprof-compute-roofline/evals/machine.yml create mode 100644 skills/rocprof-compute-speed-of-light/evals/machine.yml create mode 100644 skills/rocprof-compute-torch-trace/evals/machine.yml diff --git a/skills/rocprof-compute-kernel-bottleneck/.federated.json b/skills/rocprof-compute-kernel-bottleneck/.federated.json index 96d773d..4012b52 100644 --- a/skills/rocprof-compute-kernel-bottleneck/.federated.json +++ b/skills/rocprof-compute-kernel-bottleneck/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", "path": "projects/rocprofiler-compute/skills/rocprof-compute-kernel-bottleneck", "license": "MIT", - "content_hash": "d118f2c174e03cb348fb869d2cb0b896822264a61449a1c71e2e36ff57a50c8f", - "imported_at": "2026-10-22T20:41:18Z" + "content_hash": "fe9fd509a73ce21172bfbc7b7763fec7a4e222766d4f512abf5f3bd7dd1e2af7", + "imported_at": "2026-10-26T20:16:47Z" } diff --git a/skills/rocprof-compute-kernel-bottleneck/SKILL.md b/skills/rocprof-compute-kernel-bottleneck/SKILL.md index 161dc7b..d1a2cbd 100644 --- a/skills/rocprof-compute-kernel-bottleneck/SKILL.md +++ b/skills/rocprof-compute-kernel-bottleneck/SKILL.md @@ -1,6 +1,6 @@ --- name: rocprof-compute-kernel-bottleneck -description: Profiles an AMD GPU application with rocprof-compute and finds which kernel is slow and why, using performance metrics and PC sampling. Collects counters and selects kernels and dispatches, then analyzes GPU memory throughput, compute efficiency, wavefront occupancy and wavefront limits, scheduler stalls, etc. Use when the user asks to profile, benchmark, or speed up a HIP/ROCm kernel or application, or asks where a kernel's bottleneck is. Not for CUDA tools, Windows, system-wide CPU/MPI tracing, or writing kernel source. +description: Profiles an AMD GPU application with rocprof-compute and finds which kernel is slow and why, using performance metrics and PC sampling. Collects counters and selects kernels and dispatches, then analyzes GPU memory throughput, compute efficiency, wavefront occupancy and wavefront limits, scheduler stalls, etc. Use when the user asks to profile, benchmark, or speed up a HIP/ROCm kernel or application, or asks where a kernel's bottleneck is. Not for CUDA tools, Windows even when the user names rocprof-compute, system-wide CPU/MPI tracing, or writing kernel source. --- # Profile an AMD GPU application and find the kernel bottleneck @@ -9,11 +9,17 @@ Start here for any "profile this" or "why is this kernel slow" request. This skill collects counters, narrows a workload down to one kernel, and identifies what limits it. Hand off to a focused skill once the limit is clear. +## Prerequisites + +Install, supported GPUs, and which GPU the application runs on are in +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). + Run `rocprof-compute profile --help` and `rocprof-compute analyze --help` before choosing flags. Options change between releases; do not guess one. -Never use the GUI or TUI. This is a command-line workflow. - ## Two sources of performance data rocprof-compute collects from two independent sources, and they answer @@ -27,7 +33,8 @@ different questions. Choose by what the user is asking, not by difficulty. Counters are the default starting point because they cover the whole kernel cheaply. Go straight to `rocprof-compute-pc-sampling` when the user asks about instructions, source lines, or stall reasons, and use both when an architectural limit needs -to be traced back to the code that causes it. +to be traced back to the code that causes it. A request to run `rocprofv3` +itself is neither this skill nor `rocprof-compute-pc-sampling`. ## 1. Check the environment @@ -46,7 +53,7 @@ rocprof-compute --specs it cannot succeed when they fail. Supported accelerators are listed in -[compatible-accelerators.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst). +[compatible-accelerators.rst](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst). Not every feature works on every architecture. Check there before promising a result, and let the tool's own error message stand when a feature is unavailable. @@ -75,14 +82,15 @@ MPI. Change it with `--output-directory`, which replaces `--name` for building the path. Re-profiling into a non-empty directory needs `--overwrite`; prefer a fresh directory. -To restrict which GPU the application uses, set `HIP_VISIBLE_DEVICES`. Under -Slurm, derive it from `ROCR_VISIBLE_DEVICES`, not from `SLURM_STEP_GPUS`; the -indices differ. +Which GPU the application runs on is in +[Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Under Slurm, derive +`HIP_VISIBLE_DEVICES` from `ROCR_VISIBLE_DEVICES`, not from `SLURM_STEP_GPUS`; +the indices differ. -See [profile mode](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for output layout, +See [profile mode](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for output layout, filtering, and the full option list. To profile a process that is already running, see -[live attach and detach](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/live_attach_detach.rst). +[live attach and detach](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/live_attach_detach.rst). ### Collect less when a full replay is too expensive @@ -123,6 +131,8 @@ contain that file, look one level down for the GPU model or rank directory. `Dispatch list`. The kernel index is what `-k` takes. Dispatch ids are 1-based and go to `-d`. +`--list-stats` cannot be used with `--output-format csv` or `db`. + Work on one kernel at a time: ```bash @@ -173,7 +183,7 @@ table. When theoretical occupancy is itself low, the limit is VGPR, SGPR, or LDS allocation. Scratch traffic points at register spilling. Low occupancy is not a defect when the kernel already saturates its limiting resource. Worked examples are in -[occupancy-limiters-example.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/tutorial/includes/occupancy-limiters-example.rst). +[occupancy-limiters-example.rst](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/tutorial/includes/occupancy-limiters-example.rst). ### Scheduler and pipeline limits @@ -189,7 +199,7 @@ There is no single stall panel. Stall counters sit in the block that owns the hardware: LDS stalls in `lds`, cache stalls in `vl1d` and `l2`, fabric stalls in `l2_per_channel`. Open the block the Speed-of-Light result points at, and read what each counter means in the -[performance model](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). Ground every +[performance model](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). Ground every recommendation in a number you read from the report. ## 6. Normalization @@ -226,7 +236,7 @@ settings without stating that those differ. Terminal output is the default. Only choose a format when the user asks to save or export. `--output-format` takes `txt`, `csv`, or `db`, each of which disables terminal output, and `--output-name` sets the file name. See -[analysis output format](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). +[analysis output format](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). ## 9. When something looks wrong @@ -239,4 +249,4 @@ disables terminal output, and `--output-name` sets the file name. See | Roofline missing | not collected, or unsupported on this architecture | Stop and report permission, firmware, and ROCm version failures instead of -working around them. More in the [FAQ](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/reference/faq.rst). +working around them. More in the [FAQ](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/faq.rst). diff --git a/skills/rocprof-compute-kernel-bottleneck/evals/evals.json b/skills/rocprof-compute-kernel-bottleneck/evals/evals.json index d269e15..f36df90 100644 --- a/skills/rocprof-compute-kernel-bottleneck/evals/evals.json +++ b/skills/rocprof-compute-kernel-bottleneck/evals/evals.json @@ -3,22 +3,23 @@ { "id": "kb-profile-hip-application", "skill_should_trigger": true, - "prompt": "profile my HIP application with rocprof-compute and tell me where the time goes", + "prompt": "profile my HIP application with rocprof-compute and tell me where the time goes\n\nThe application is ./vcopy.cpp.", + "workspace": "../../sample", "expected_behavior": [ "asks for the launch command and a workload name before profiling", "runs rocprof-compute profile --name -- ", "runs analyze with --list-stats to find the hottest kernel" ], "unexpected_behavior": [ - "invents a profiling flag instead of checking --help", - "opens the GUI or TUI" + "invents a profiling flag instead of checking --help" ], "logs_contain": ["rocprof-compute profile", "--list-stats"] }, { "id": "kb-which-kernel-is-slow", "skill_should_trigger": true, - "prompt": "which kernel is the hotspot in ./workloads/my_app and why is it slow?", + "prompt": "which kernel is the hotspot in ./workloads/my_app and why is it slow?\n\nThe application is ./vmem.hip.", + "workspace": "../../sample", "expected_behavior": [ "points --path at the directory containing profiling_config.yaml rather than a parent directory", "uses --list-stats first, then narrows to one kernel id with -k", @@ -29,7 +30,8 @@ { "id": "kb-occupancy-limited", "skill_should_trigger": true, - "prompt": "is occupancy limiting this rocprof-compute kernel?", + "prompt": "is occupancy limiting this rocprof-compute kernel?\n\nThe application is ./occupancy.hip.", + "workspace": "../../sample", "expected_behavior": [ "opens the wavefront block for the selected kernel", "compares achieved occupancy against the theoretical occupancy printed in the report", @@ -43,7 +45,8 @@ { "id": "kb-expensive-replay-fallback", "skill_should_trigger": true, - "prompt": "a full rocprof-compute replay of this workload is too expensive, collect something cheaper", + "prompt": "a full rocprof-compute replay of this workload is too expensive, collect something cheaper\n\nThe application is ./vmem.hip.", + "workspace": "../../sample", "expected_behavior": [ "offers a single-pass option such as --set, a narrowed -b, or --iteration-multiplexing", "runs --list-sets before selecting a set", @@ -67,7 +70,8 @@ { "id": "kb-kernel-name-filter", "skill_should_trigger": true, - "prompt": "profile only the gemm kernels in my ROCm application", + "prompt": "profile only the gemm kernels in my ROCm application\n\nThe application is ./wmma_gemm_fp16.hip.", + "workspace": "../../sample/wmma_gemm_fp16", "expected_behavior": [ "uses rocprof-compute profile -k to filter by kernel name", "treats the profile-time -k value as a regex matched against kernel names" diff --git a/skills/rocprof-compute-kernel-bottleneck/evals/machine.yml b/skills/rocprof-compute-kernel-bottleneck/evals/machine.yml new file mode 100644 index 0000000..4d7efd4 --- /dev/null +++ b/skills/rocprof-compute-kernel-bottleneck/evals/machine.yml @@ -0,0 +1,2 @@ +os: [Linux] +labels: [mi300x, gpu, rocm] diff --git a/skills/rocprof-compute-memory/.federated.json b/skills/rocprof-compute-memory/.federated.json index 586c985..dc824ad 100644 --- a/skills/rocprof-compute-memory/.federated.json +++ b/skills/rocprof-compute-memory/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", "path": "projects/rocprofiler-compute/skills/rocprof-compute-memory", "license": "MIT", - "content_hash": "9d50ac838fdad2dfdf30007aeea7f8b16cdf0ee1bf8b4df6a101d3d02eead87a", - "imported_at": "2026-10-22T20:41:18Z" + "content_hash": "31924788226abe310a09b2ac495ddc0c14933a03b5b6f85fdadde1e1c7e941b4", + "imported_at": "2026-10-26T20:16:47Z" } diff --git a/skills/rocprof-compute-memory/SKILL.md b/skills/rocprof-compute-memory/SKILL.md index b2fb915..52f2241 100644 --- a/skills/rocprof-compute-memory/SKILL.md +++ b/skills/rocprof-compute-memory/SKILL.md @@ -1,6 +1,6 @@ --- name: rocprof-compute-memory -description: Analyzes the AMD GPU memory hierarchy with rocprof-compute, covering the Memory Chart, vL1D and L2 caches, LDS bank conflicts, Infinity Fabric traffic, and the experimental gfx950 memory bandwidth analysis. Use when the user asks about memory bandwidth, cache hit rates, coalescing, LDS conflicts, data locality, HBM traffic, or says a kernel is memory-bound. Not for CUDA tools, Windows, host memory profiling, or system-wide tracing. +description: Analyzes the AMD GPU memory hierarchy with rocprof-compute, covering the Memory Chart, vL1D and L2 caches, LDS bank conflicts, Infinity Fabric traffic, and the experimental gfx950 memory bandwidth analysis. Use when the user asks about memory bandwidth, cache hit rates, coalescing, LDS conflicts, data locality, HBM traffic, or says a kernel is memory-bound. Not for CUDA tools, Nsight, Windows, host-memory leaks, GPU out-of-memory allocation failures, or system-wide tracing. --- # Analyze the AMD GPU memory hierarchy @@ -8,8 +8,15 @@ description: Analyzes the AMD GPU memory hierarchy with rocprof-compute, coverin Use this after Speed-of-Light points at memory, or when the user asks about bandwidth, caches, or data movement directly. +## Prerequisites + +Install, supported GPUs, and which GPU the application runs on are in +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). + Run `rocprof-compute analyze --help` before choosing flags. -Never use the GUI or TUI. ## 1. Start with the Memory Chart @@ -55,15 +62,15 @@ architecture with `rocprof-compute --list-blocks `. Each level has its own reference, with the counters, the formulas, and what they mean: -- [Vector L1 cache](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/cdna/vector-l1-cache.rst) -- [L2 cache](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/cdna/l2-cache.rst) -- [Local Data Share](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/cdna/local-data-share.rst) -- [Infinity Fabric transactions](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/tutorial/includes/infinity-fabric-transactions.rst) -- [Vector memory operation counting](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/tutorial/includes/vector-memory-operation-counting.rst) -- [LDS examples](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/tutorial/includes/lds-examples.rst) +- [Vector L1 cache](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/cdna/vector-l1-cache.rst) +- [L2 cache](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/cdna/l2-cache.rst) +- [Local Data Share](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/cdna/local-data-share.rst) +- [Infinity Fabric transactions](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/tutorial/includes/infinity-fabric-transactions.rst) +- [Vector memory operation counting](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/tutorial/includes/vector-memory-operation-counting.rst) +- [LDS examples](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/tutorial/includes/lds-examples.rst) Client APUs use a different hierarchy; see the -[RDNA performance model](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/rdna/rdna-performance-model.rst) +[RDNA performance model](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/rdna/rdna-performance-model.rst) for GL0, GL1, and GL2. Read hit rates and utilizations against the `Percent of Peak` and `Peak` @@ -82,11 +89,12 @@ What usually follows from what: | High fabric read or write stalls | traffic exceeds what the fabric can absorb | | Near-peak HBM with low cache hits | reduce traffic or add reuse; more bandwidth is not available | -## 4. Memory bandwidth analysis (experimental, gfx950) +## 4. Memory bandwidth analysis (experimental) A guided breakdown that walks the bandwidth tree and names the limiting level. -It is experimental and currently shipped for gfx950 only. It needs block 30 at -both profile and analyze time: +Which GPUs it supports, and the experimental flag, are in +[memory bandwidth analysis](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/membw_analysis.rst). It needs +block 30 at both profile and analyze time: ```bash rocprof-compute profile --experimental --membw-analysis --name -- @@ -96,9 +104,15 @@ rocprof-compute analyze --path ./workloads// \ ``` Its cutoffs and guidance text are data, not something to restate from memory. -They live in `src/membw_analysis/tree_spec/gfx950_membw_tree_spec.yaml` and -`src/membw_analysis/tree_spec/gfx950_membw_guidance.yaml`. Read those files -when you need to explain a verdict, and report the guidance the tool produced +In a source checkout, read: + +- [gfx950_membw_tree_spec.yaml](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/src/membw_analysis/tree_spec/gfx950_membw_tree_spec.yaml) +- [gfx950_membw_guidance.yaml](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/src/membw_analysis/tree_spec/gfx950_membw_guidance.yaml) + +A ROCm install copies the skills to `/share/rocprofiler-compute/skills/` +and does not copy these YAML files next to them. When this skill was installed +rather than opened from the source tree, read those same two paths in the +matching rocprofiler-compute checkout. Report the guidance the tool produced rather than substituting your own numbers. On any other architecture, use the Memory Chart path above. diff --git a/skills/rocprof-compute-memory/evals/evals.json b/skills/rocprof-compute-memory/evals/evals.json index cca0782..70a418b 100644 --- a/skills/rocprof-compute-memory/evals/evals.json +++ b/skills/rocprof-compute-memory/evals/evals.json @@ -3,7 +3,8 @@ { "id": "mem-access-inefficiency", "skill_should_trigger": true, - "prompt": "find the memory access inefficiencies in this rocprofiler-compute capture", + "prompt": "find the memory access inefficiencies in this rocprofiler-compute capture\n\nThe application is ./vrandom_access.cpp.", + "workspace": "../../sample", "expected_behavior": [ "reads the memory chart before opening an individual cache level", "cites hit rates or request counts from the report", @@ -17,7 +18,8 @@ { "id": "mem-cache-hit-rates", "skill_should_trigger": true, - "prompt": "what are my L2 and vL1D hit rates for this kernel and are they bad?", + "prompt": "what are my L2 and vL1D hit rates for this kernel and are they bad?\n\nThe application is ./vsequential_access.cpp.", + "workspace": "../../sample", "expected_behavior": [ "opens the l2 and vl1d blocks for a single kernel id", "judges the rates against what the algorithm should do rather than a fixed cutoff" @@ -30,7 +32,8 @@ { "id": "mem-lds-bank-conflicts", "skill_should_trigger": true, - "prompt": "does my kernel have LDS bank conflicts?", + "prompt": "does my kernel have LDS bank conflicts?\n\nThe application is ./lds.hip.", + "workspace": "../../sample", "expected_behavior": [ "opens the lds block for the selected kernel", "reads the bank conflict counters from the report" @@ -40,7 +43,8 @@ { "id": "mem-bandwidth-bound", "skill_should_trigger": true, - "prompt": "this kernel looks memory bound, where is the bandwidth going?", + "prompt": "this kernel looks memory bound, where is the bandwidth going?\n\nThe application is ./vcopy.cpp.", + "workspace": "../../sample", "expected_behavior": [ "walks the hierarchy from the memory chart down to the level responsible", "distinguishes traffic that caches absorbed from traffic that reached HBM" @@ -50,7 +54,8 @@ { "id": "mem-membw-analysis-gfx950", "skill_should_trigger": true, - "prompt": "run the guided memory bandwidth analysis on my MI350 workload", + "prompt": "run the guided memory bandwidth analysis on my MI350 workload\n\nThe application is ./gl2_backpressure.hip.", + "workspace": "../../sample/membw_analysis_test_suite", "expected_behavior": [ "uses --experimental --membw-analysis", "notes that the analysis is experimental and shipped for gfx950", diff --git a/skills/rocprof-compute-memory/evals/machine.yml b/skills/rocprof-compute-memory/evals/machine.yml new file mode 100644 index 0000000..4d7efd4 --- /dev/null +++ b/skills/rocprof-compute-memory/evals/machine.yml @@ -0,0 +1,2 @@ +os: [Linux] +labels: [mi300x, gpu, rocm] diff --git a/skills/rocprof-compute-pc-sampling/.federated.json b/skills/rocprof-compute-pc-sampling/.federated.json index 8a68f91..40134a7 100644 --- a/skills/rocprof-compute-pc-sampling/.federated.json +++ b/skills/rocprof-compute-pc-sampling/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", "path": "projects/rocprofiler-compute/skills/rocprof-compute-pc-sampling", "license": "MIT", - "content_hash": "85efa3ef362aa8c28550a059b5ba78bb45a7ee239778fa9b63be0e11508c67cf", - "imported_at": "2026-10-22T20:41:18Z" + "content_hash": "44c549984cab525586af8701a3ba86a77fc1485ab1400d823eb5f8137604042f", + "imported_at": "2026-10-26T20:16:47Z" } diff --git a/skills/rocprof-compute-pc-sampling/SKILL.md b/skills/rocprof-compute-pc-sampling/SKILL.md index 2191683..40e4751 100644 --- a/skills/rocprof-compute-pc-sampling/SKILL.md +++ b/skills/rocprof-compute-pc-sampling/SKILL.md @@ -12,6 +12,20 @@ have already identified the kernel worth this level of detail. PC sampling is experimental. Every command below needs `--experimental`, and option names can change between releases, so run `rocprof-compute profile --experimental --help` before choosing flags. + +## Prerequisites + +Install, supported GPUs, and which GPU the application runs on are in +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). + +This skill drives `rocprof-compute --pc-sampling`. A request to invoke +`rocprofv3 --pc-sampling-beta-enabled` directly belongs to the rocprofiler-sdk +PC sampling skill, not this one. Method support is in +[PC sampling](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). + Never use the GUI or TUI. ## 1. Check what the hardware supports @@ -24,9 +38,10 @@ This reports the sampling methods the installed device accepts and the valid interval range. Two methods exist: - `stochastic` samples in hardware and is the only method that records a stall - reason. It requires MI300 or newer. -- `host_trap` works more widely but records no stall reason, and its samples - can land a little past the instruction responsible. + reason. Which GPUs accept it is in + [PC sampling](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). +- `host_trap` records no stall reason, and its samples can land a little past + the instruction responsible. `--pc-sampling-method` defaults to `stochastic`. Fall back to `host_trap` only when the device rejects stochastic. @@ -92,7 +107,7 @@ there, and the fix is usually coalescing, prefetching, or more independent work between the load and its use. Stall reasons and what each one implies are listed in -[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). Do not interpret a stall +[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). Do not interpret a stall reason from host-trap data; it does not record one. ## 5. Export annotated disassembly @@ -115,4 +130,4 @@ accepts, rather than drawing conclusions from a handful of samples. For multi-process workloads, keep each process or rank separate; see the multi-process sections of -[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). +[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). diff --git a/skills/rocprof-compute-pc-sampling/evals/evals.json b/skills/rocprof-compute-pc-sampling/evals/evals.json index e3972c5..0ec8365 100644 --- a/skills/rocprof-compute-pc-sampling/evals/evals.json +++ b/skills/rocprof-compute-pc-sampling/evals/evals.json @@ -3,7 +3,8 @@ { "id": "pcs-which-instruction-stalls", "skill_should_trigger": true, - "prompt": "which instruction in my MI300 kernel stalls the most?", + "prompt": "which instruction in my MI300 kernel stalls the most?\n\nThe application is ./vmem.hip.", + "workspace": "../../sample", "expected_behavior": [ "queries the device for supported PC sampling configurations before choosing an interval", "collects with --experimental --pc-sampling", @@ -17,7 +18,8 @@ { "id": "pcs-source-line-attribution", "skill_should_trigger": true, - "prompt": "map the hot GPU instructions back to my HIP source lines", + "prompt": "map the hot GPU instructions back to my HIP source lines\n\nThe application is ./vmem.hip.", + "workspace": "../../sample", "expected_behavior": [ "states that source line mapping requires the application to be built with debug info", "notes that samples map to assembly even without debug info" @@ -40,7 +42,8 @@ { "id": "pcs-export-disassembly", "skill_should_trigger": true, - "prompt": "export the annotated disassembly with sample counts from my pc sampling workload", + "prompt": "export the annotated disassembly with sample counts from my pc sampling workload\n\nThe application is ./vmem.hip.", + "workspace": "../../sample", "expected_behavior": [ "uses --output-format csv or db", "notes that the per-kernel disassembly is written under per_kernel_pc_sampling" diff --git a/skills/rocprof-compute-pc-sampling/evals/machine.yml b/skills/rocprof-compute-pc-sampling/evals/machine.yml new file mode 100644 index 0000000..4d7efd4 --- /dev/null +++ b/skills/rocprof-compute-pc-sampling/evals/machine.yml @@ -0,0 +1,2 @@ +os: [Linux] +labels: [mi300x, gpu, rocm] diff --git a/skills/rocprof-compute-roofline/.federated.json b/skills/rocprof-compute-roofline/.federated.json index bac0f1d..f927d29 100644 --- a/skills/rocprof-compute-roofline/.federated.json +++ b/skills/rocprof-compute-roofline/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", "path": "projects/rocprofiler-compute/skills/rocprof-compute-roofline", "license": "MIT", - "content_hash": "c41c2988d7cb02a577f6bbb91054ee7b471dae0ddc3c4930a36c7d6ef951d144", - "imported_at": "2026-10-22T20:41:18Z" + "content_hash": "9133ce94a28c1049fc1bbeca74234095bf50a1f4cd3a31dc18d2e1f9dd8489ca", + "imported_at": "2026-10-26T20:16:47Z" } diff --git a/skills/rocprof-compute-roofline/SKILL.md b/skills/rocprof-compute-roofline/SKILL.md index d4b6577..700a88f 100644 --- a/skills/rocprof-compute-roofline/SKILL.md +++ b/skills/rocprof-compute-roofline/SKILL.md @@ -10,8 +10,18 @@ ceilings, peak compute and peak bandwidth, and answers one question: which of the two is the limit. Run `rocprof-compute profile --help` and `rocprof-compute analyze --help` -before choosing flags. Never use the GUI or TUI, and do not open the generated -HTML chart. The analyze table carries the same numbers in readable form. +before choosing flags. Do not open the generated HTML chart. The analyze table +carries the same numbers in readable form. + +## Prerequisites + +Install, supported GPUs, and which GPU the application runs on are in +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). +Roofline support by GPU is in +[supported hardware](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/what-is-rocprof-compute.rst). ## 1. Collect @@ -25,18 +35,15 @@ Collecting it runs microbenchmarks in addition to profiling the application, which takes extra time. `--no-roof` skips them when the user only wants counters. -MI100 (gfx908) does not support the roofline microbenchmarks. Every other -supported architecture does. On MI100 the benchmark is skipped and the profile -still succeeds, so a missing roofline there is not a mistake in how the profile -was run. Never tell an MI100 user to re-profile without `--no-roof`; use -Speed-of-Light and the `rocprof-compute-memory` skill instead. Confirm support -for a new architecture against -[compatible-accelerators.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst). +On MI100 (gfx908), the roofline microbenchmarks are skipped and the profile +still succeeds. When [supported hardware](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/what-is-rocprof-compute.rst) +shows no roofline support, do not ask the user to re-profile without +`--no-roof`. Use Speed-of-Light and the `rocprof-compute-memory` skill instead. -`--device` selects the GPU for the roofline microbenchmarks. It does not -choose which GPU the application runs on; use `HIP_VISIBLE_DEVICES` for that. +`--device` and `HIP_VISIBLE_DEVICES` are different selections. See +[Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). -See [standalone roofline](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for `--roof-only` +See [standalone roofline](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for `--roof-only` and `--bench-only`, which run the microbenchmarks without profiling an application. @@ -78,29 +85,23 @@ at 45% of peak bandwidth" tells the user there is headroom; "memory-bound at ## 3. Pick the right precision and memory level -Section 2 gives the verdict against the default ceilings. Refine it here when -the kernel does not use FP32, or when the question is about a specific level of -the hierarchy. - -```bash -# Ceilings for the precision the kernel actually uses -rocprof-compute analyze --path ./workloads// -k -b roof \ - -R FP16 BF16 +The table lists a peak for every precision and an arithmetic intensity for +every memory level. Compare against the peak row for the precision the kernel +actually issues, and the intensity row for the memory level in question. -# Ceilings for a specific level -rocprof-compute analyze --path ./workloads// -k -b roof \ - -m HBM L2 -``` - -`-R` defaults to FP32. A mixed-precision or matrix kernel compared against the -FP32 ceiling will look far worse than it is, so set `-R` to the precision the -kernel actually issues. Read that from the instruction mix with `-b cu_ins` +A mixed-precision or matrix kernel compared against the FP32 peak will look far +worse than it is. Read the precision from the instruction mix with `-b cu_ins` rather than assuming it. Ask the user which precision the kernel is meant to use only when the mix is genuinely mixed and no single type dominates; they know the intent, the counters know what ran. -`-m` defaults to every level. HBM answers "am I limited by main memory", while -L2 and vL1D show whether caches are absorbing the traffic. +HBM answers "am I limited by main memory", while L2 and vL1D show whether +caches are absorbing the traffic. + +`--view table` prints one table for all kernels together instead of one per +kernel. To compare many kernels, write the +[analysis database](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst) with +`--output-format db` and query it with SQL. ## 4. When roofline data is missing @@ -111,4 +112,4 @@ L2 and vL1D show whether caches are absorbing the traffic. | `roofline.csv` absent from the workload directory | the benchmark did not complete; check the profile log | The concepts behind the ceilings are in the -[performance model](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). +[performance model](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). diff --git a/skills/rocprof-compute-roofline/evals/evals.json b/skills/rocprof-compute-roofline/evals/evals.json index a435b00..feb851f 100644 --- a/skills/rocprof-compute-roofline/evals/evals.json +++ b/skills/rocprof-compute-roofline/evals/evals.json @@ -3,7 +3,8 @@ { "id": "roof-compute-or-memory-bound", "skill_should_trigger": true, - "prompt": "is this rocprof-compute kernel compute bound or memory bound?", + "prompt": "is this rocprof-compute kernel compute bound or memory bound?\n\nThe application is ./vcopy.cpp.", + "workspace": "../../sample", "expected_behavior": [ "analyzes the roof block for a single kernel id", "compares arithmetic intensity against the empirical peaks printed in the table", @@ -18,7 +19,8 @@ { "id": "roof-arithmetic-intensity", "skill_should_trigger": true, - "prompt": "what is the arithmetic intensity of my hottest kernel and how much performance am I leaving on the table?", + "prompt": "what is the arithmetic intensity of my hottest kernel and how much performance am I leaving on the table?\n\nThe application is ./mat_mul_max.hip.", + "workspace": "../../sample", "expected_behavior": [ "reads FLOPs per byte and the peak values from the roofline table", "narrows to one kernel id first" @@ -28,15 +30,16 @@ { "id": "roof-precision-selection", "skill_should_trigger": true, - "prompt": "my half precision GEMM looks terrible on the roofline, is that right?", + "prompt": "my half precision GEMM looks terrible on the roofline, is that right?\n\nThe application is ./wmma_gemm_fp16.hip.", + "workspace": "../../sample/wmma_gemm_fp16", "expected_behavior": [ - "sets -R to the precision the kernel actually issues instead of the FP32 default", + "compares against the peak for the precision the kernel actually issues instead of FP32", "confirms the precision from the instruction mix rather than assuming it" ], "unexpected_behavior": [ "compares a half precision kernel against the FP32 ceiling and calls it slow" ], - "logs_contain": ["-R"] + "logs_contain": ["cu_ins"] }, { "id": "roof-missing-data", diff --git a/skills/rocprof-compute-roofline/evals/machine.yml b/skills/rocprof-compute-roofline/evals/machine.yml new file mode 100644 index 0000000..4d7efd4 --- /dev/null +++ b/skills/rocprof-compute-roofline/evals/machine.yml @@ -0,0 +1,2 @@ +os: [Linux] +labels: [mi300x, gpu, rocm] diff --git a/skills/rocprof-compute-speed-of-light/.federated.json b/skills/rocprof-compute-speed-of-light/.federated.json index 25aa47f..d7b50f7 100644 --- a/skills/rocprof-compute-speed-of-light/.federated.json +++ b/skills/rocprof-compute-speed-of-light/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", "path": "projects/rocprofiler-compute/skills/rocprof-compute-speed-of-light", "license": "MIT", - "content_hash": "3005a4c9ac299ac3279240837cf5fe8f6141e130b72fb61cbf21ae2fffee4194", - "imported_at": "2026-10-22T20:41:18Z" + "content_hash": "c23e8f01d41e6c039424e3f7b8890674bbdd83546b19d424753b29711068a510", + "imported_at": "2026-10-26T20:16:47Z" } diff --git a/skills/rocprof-compute-speed-of-light/SKILL.md b/skills/rocprof-compute-speed-of-light/SKILL.md index 4277bec..fa7189d 100644 --- a/skills/rocprof-compute-speed-of-light/SKILL.md +++ b/skills/rocprof-compute-speed-of-light/SKILL.md @@ -1,6 +1,6 @@ --- name: rocprof-compute-speed-of-light -description: Reads the rocprof-compute System Speed-of-Light panel to show how close an AMD GPU kernel runs to hardware peak for compute throughput, memory bandwidth, and utilization. Use when the user asks how close to peak a kernel is, what percent of peak it reaches, whether a measured percentage is good enough or normal, whether a utilization number should be higher, or asks for an overall efficiency summary. Not for CUDA tools, Windows, per-instruction analysis, or looking up a datasheet specification when there is no profile to read. +description: Reads the rocprof-compute System Speed-of-Light panel to show how close an AMD GPU kernel runs to hardware peak for compute throughput, memory bandwidth, and utilization. Use when the user asks how close to peak a kernel is, what percent of peak it reaches, whether a measured percentage is good enough or normal, whether a utilization number should be higher, or asks for an overall efficiency summary. Not for CUDA tools, Windows, per-instruction analysis, or a datasheet or theoretical-peak question when there is no profile to read. --- # Read System Speed-of-Light @@ -9,8 +9,17 @@ Speed-of-Light is the first panel to read after profiling. It puts every headline metric next to the hardware peak for the current GPU, so one table says whether a kernel is near any limit at all. +## Prerequisites + +Install, supported GPUs, and which GPU the application runs on are in +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). + +This skill reads an existing workload directory. It does not collect one. + Run `rocprof-compute analyze --help` before choosing flags. -Never use the GUI or TUI. ## 1. Get the panel @@ -55,11 +64,11 @@ the neighbouring kernels, and a known-good baseline for the same workload. ## 3. Understand the metrics Each metric's definition and the counters behind it are in -[system-speed-of-light.rst](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/cdna/system-speed-of-light.rst) +[system-speed-of-light.rst](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/cdna/system-speed-of-light.rst) for CDNA accelerators and -[the RDNA equivalent](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/rdna/system-speed-of-light.rst) +[the RDNA equivalent](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/rdna/system-speed-of-light.rst) for client APUs. The whole metric hierarchy is in the -[performance model](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). +[performance model](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). ## 4. Compute Speed-of-Light diff --git a/skills/rocprof-compute-speed-of-light/evals/evals.json b/skills/rocprof-compute-speed-of-light/evals/evals.json index c26b5a8..a87c673 100644 --- a/skills/rocprof-compute-speed-of-light/evals/evals.json +++ b/skills/rocprof-compute-speed-of-light/evals/evals.json @@ -3,7 +3,8 @@ { "id": "sol-percent-of-peak", "skill_should_trigger": true, - "prompt": "how close to peak is this kernel running in my rocprof-compute workload?", + "prompt": "how close to peak is this kernel running in my rocprof-compute workload?\n\nThe application is ./vcopy.cpp.", + "workspace": "../../sample", "expected_behavior": [ "analyzes the sol block for a single kernel id", "reads the Percent of Peak column from the report", @@ -17,7 +18,8 @@ { "id": "sol-overall-efficiency", "skill_should_trigger": true, - "prompt": "give me an overall efficiency summary of this rocprofiler-compute capture", + "prompt": "give me an overall efficiency summary of this rocprofiler-compute capture\n\nThe application is ./vcopy.cpp.", + "workspace": "../../sample", "expected_behavior": [ "selects one kernel with -k before reading Speed-of-Light", "summarizes using the printed peak and percent of peak values" diff --git a/skills/rocprof-compute-speed-of-light/evals/machine.yml b/skills/rocprof-compute-speed-of-light/evals/machine.yml new file mode 100644 index 0000000..4d7efd4 --- /dev/null +++ b/skills/rocprof-compute-speed-of-light/evals/machine.yml @@ -0,0 +1,2 @@ +os: [Linux] +labels: [mi300x, gpu, rocm] diff --git a/skills/rocprof-compute-torch-trace/.federated.json b/skills/rocprof-compute-torch-trace/.federated.json index cb82e6d..88031c8 100644 --- a/skills/rocprof-compute-torch-trace/.federated.json +++ b/skills/rocprof-compute-torch-trace/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "f2b844211d55eb3618cc5ab3e783acb617882ebd", + "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", "path": "projects/rocprofiler-compute/skills/rocprof-compute-torch-trace", "license": "MIT", - "content_hash": "ef7a5100a32ef30aea6a1bfd3ede8bbdad07bcdd1e48c512e4fb732023487b5f", - "imported_at": "2026-10-22T20:41:18Z" + "content_hash": "58e904a54b5e01db7527349c7e9dffc4c42d85a46c942d7d7717b199b6eb1f5e", + "imported_at": "2026-10-26T20:16:47Z" } diff --git a/skills/rocprof-compute-torch-trace/SKILL.md b/skills/rocprof-compute-torch-trace/SKILL.md index 389a505..e331697 100644 --- a/skills/rocprof-compute-torch-trace/SKILL.md +++ b/skills/rocprof-compute-torch-trace/SKILL.md @@ -12,18 +12,22 @@ traced back to the operator that launched it. Torch and Triton tracing are experimental. Every command needs `--experimental`, and option names can change between releases, so run `rocprof-compute profile --experimental --help` first. -Never use the GUI or TUI. -## 1. Check the requirements +## Prerequisites + +Install, supported GPUs, and which GPU the application runs on are in +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). -Torch tracing needs PyTorch 2.13 or 2.14, installed against a matching ROCm. -The workload must be a Python command or Python script; the tracer cannot -annotate a compiled binary. The full requirement list, including the Python -version matching constraint, is in the Torch trace section of -[profile mode](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). +## 1. Check the requirements -Confirm the PyTorch version before profiling. A mismatched version fails at -collection time, after the run has already cost the user its runtime. +PyTorch version coverage, the native collector versus reduced +`TorchDispatchMode` coverage, and the abort when two ROCm installs load in +one process are in the Torch trace section of +[profile mode](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Check the loader message +before treating a missing backward operator as missing GPU work. ## 2. Collect @@ -35,12 +39,15 @@ rocprof-compute profile \ -- python ``` -For Triton kernels, including those generated by `torch.compile` and Inductor: +For Triton kernels launched through Triton's Python runtime (`@triton.jit`): ```bash rocprof-compute profile --experimental --triton-trace --name -- python ``` +`torch.compile` / Inductor fused kernels use PyTorch's static launcher, so they +do not get `|triton` ranges. Attribute them with `--torch-trace`. + The two combine, and `--ml-api-trace` turns on every supported framework backend at once. @@ -48,7 +55,7 @@ Tracing adds overhead on top of the usual counter replay. Profile a few iterations of a steady-state model rather than a full training run, and skip warmup. `ROCPROFCOMPUTE_ROCTX_DEEP_TENSOR_WRAPS=0` reduces instrumentation overhead by dropping the device and layout wraps; see -[profile mode](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for what that costs you. +[profile mode](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for what that costs you. Tracing cannot be combined with attaching to a running process, and cannot be combined with a PC-sampling-only profile. @@ -56,10 +63,15 @@ combined with a PC-sampling-only profile. When counters are being spread thin across many short operator kernels, `--iteration-multiplexing kernel` collects more per pass. +Profile preserves each pass's raw marker and counter CSVs with the +`ml_api_trace` prefix. + ## 3. List the captured operators -Always list before filtering. Operator names come from the model that ran, so -guessing a pattern wastes a profile: +List when you need the operator names from this run; filtering does not +require a prior list. Do not pass `--list-*-operators` and `--*-operator` +on the same command; listing ignores the filter. These flags require +`--output-format stdout` (the default): ```bash rocprof-compute analyze \ @@ -67,8 +79,9 @@ rocprof-compute analyze \ --experimental --list-torch-operators ``` -This prints the operators as a call tree grouped by source location, with -kernel launch statistics, so it also shows which operators are worth opening. +This prints the operators as a call tree sorted by GPU duration, with +`file:line` on each node that recorded a source location, plus kernel +launch statistics, so it also shows which operators are worth opening. The Triton equivalent is `--list-triton-operators`. @@ -77,21 +90,20 @@ The Triton equivalent is `--list-triton-operators`. ```bash rocprof-compute analyze \ --path ./workloads// \ - --experimental --torch-operator '*conv2d*' + --experimental --torch-operator '*relu*' ``` Patterns are shell-style globs, not regexes, matched against a `/`-separated -operator hierarchy. `*relu` ends with relu, `*conv*` contains conv, -`*/*functional*/*` matches an intermediate component, and `all` or `'*'` -matches everything. Several patterns can be given, space or comma separated. +operator hierarchy. `*relu*` contains relu, `*/aten::addmm` is an addmm leaf +on the reconstructed path, and `all` or `'*'` matches everything. Several +patterns can be given, space or comma separated. Quote patterns so the shell does not expand them. -Combine with `-k` to intersect an operator with specific kernel ids, and with -`-b` to look at one metric block for that operator's kernels. `--triton-operator` -works the same way. +`--triton-operator` works the same way. `*rmsnorm_kernel*` matches the +rmsnorm nodes on a Triton tree. Worked examples are in the PyTorch operator analysis section of -[the analyze CLI guide](https://github.com/ROCm/rocm-systems/blob/f2b844211d55eb3618cc5ab3e783acb617882ebd/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). +[the analyze CLI guide](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). ## 5. Read the result @@ -110,7 +122,7 @@ annotate operators, which is what makes attribution possible. That is the only place ROCTx is involved. rocprof-compute does not support ROCTx-range-based profiling control, such as -starting, stopping, or resuming collection at a user-authored ROCTx range, and -does not attribute results to ranges a user added to their own application. If -that is what the user wants, say so plainly and offer kernel filtering with +starting, stopping, or resuming collection at a user-authored ROCTx range. +User-defined ranges in the marker CSV are labeled `user`. If the user wants +ROCTx start/stop control, say so plainly and offer kernel filtering with `-k` or dispatch filtering with `-d` instead. diff --git a/skills/rocprof-compute-torch-trace/evals/evals.json b/skills/rocprof-compute-torch-trace/evals/evals.json index 5d59ea3..88bb208 100644 --- a/skills/rocprof-compute-torch-trace/evals/evals.json +++ b/skills/rocprof-compute-torch-trace/evals/evals.json @@ -3,10 +3,11 @@ { "id": "tt-which-operator-is-slow", "skill_should_trigger": true, - "prompt": "which PyTorch operator is launching the slow GPU kernels in my model?", + "prompt": "which PyTorch operator is launching the slow GPU kernels in my model?\n\nThe application is ./simple_net.py.", + "workspace": "../../sample", "expected_behavior": [ "collects with --experimental --torch-trace", - "lists the captured operators before filtering", + "lists the captured operators to find the slow ones", "checks the documented PyTorch version requirement" ], "unexpected_behavior": [ @@ -17,7 +18,8 @@ { "id": "tt-map-kernels-to-model-code", "skill_should_trigger": true, - "prompt": "profile my training script and tell me which layer the GPU time belongs to", + "prompt": "profile my training script and tell me which layer the GPU time belongs to\n\nThe application is ./simple_net.py.", + "workspace": "../../sample", "expected_behavior": [ "profiles the Python script with torch tracing enabled", "advises profiling a few steady-state iterations rather than a full run" @@ -29,17 +31,19 @@ "skill_should_trigger": true, "prompt": "my model uses torch.compile, how do I attribute the generated Triton kernels?", "expected_behavior": [ - "uses --triton-trace, optionally combined with --torch-trace", - "lists the captured Triton kernels before filtering" + "uses --torch-trace, because --triton-trace does not capture kernels that torch.compile generates" ], - "logs_contain": ["--triton-trace"] + "unexpected_behavior": [ + "relies on --triton-trace alone to capture torch.compile kernels" + ], + "logs_contain": ["--torch-trace"] }, { "id": "tt-filter-by-operator", "skill_should_trigger": true, "prompt": "show me the counters for just the conv2d operators in this torch-trace workload", "expected_behavior": [ - "lists operators first, then filters with --torch-operator", + "filters with --torch-operator", "treats the pattern as a shell-style glob rather than a regex" ], "logs_contain": ["--torch-operator"] diff --git a/skills/rocprof-compute-torch-trace/evals/machine.yml b/skills/rocprof-compute-torch-trace/evals/machine.yml new file mode 100644 index 0000000..4d7efd4 --- /dev/null +++ b/skills/rocprof-compute-torch-trace/evals/machine.yml @@ -0,0 +1,2 @@ +os: [Linux] +labels: [mi300x, gpu, rocm] From fb1463d04dd0d365d431adf950d99df551b2fbd0 Mon Sep 17 00:00:00 2001 From: "fei.zheng" Date: Mon, 26 Oct 2026 15:12:49 -0600 Subject: [PATCH 3/6] Vendor the rocprof-compute sample tree next to its skills. Federation copies skill folders only, so ../../sample does not exist here. A fixture entry copies that tree once and retargets the vendored workspace paths. Co-authored-by: Cursor --- .github/federation.json | 7 + .github/scripts/federate_skills.py | 225 +- .github/scripts/test_federate_skills.py | 100 + CONTRIBUTING.md | 1 + .../evals/evals.json | 10 +- .../rocprof-compute-memory/evals/evals.json | 10 +- .../evals/evals.json | 6 +- .../rocprof-compute-roofline/evals/evals.json | 6 +- skills/rocprof-compute-sample/.fixture.json | 8 + skills/rocprof-compute-sample/common.h | 20 + .../conjugate_gradient/CMakeLists.txt | 38 + .../conjugate_gradient/README.md | 49 + .../conjugate_gradient/cg_args.hpp | 27 + .../conjugate_gradient/cg_spmv.hip | 39 + .../conjugate_gradient/cg_update.hip | 55 + .../conjugate_gradient/conjugate_gradient.cpp | 319 ++ .../dynamic_shared/dynamic_shared.hip | 171 + .../dynamic_shared/example_utils.hpp | 281 ++ skills/rocprof-compute-sample/fabric.hip | 292 ++ skills/rocprof-compute-sample/instmix.hip | 89 + skills/rocprof-compute-sample/ipc.hip | 115 + skills/rocprof-compute-sample/laplace_eqn.hip | 410 ++ skills/rocprof-compute-sample/lds.hip | 54 + skills/rocprof-compute-sample/mat_mul_max.hip | 92 + .../mega_kernel/Makefile | 159 + .../mega_kernel/README.md | 672 +++ .../mega_kernel/atomic_global_buffers.h | 33 + .../mega_kernel/main.cpp | 1014 +++++ .../mega_kernel/mega_kernel.hip | 3865 +++++++++++++++++ .../mega_kernel/mega_kernel_device_arch.h | 71 + .../mega_kernel/mega_kernel_host_arch.hpp | 64 + .../mega_kernel/mega_kernel_types.h | 124 + .../L1_stall_microbenchmark.hip | 134 + .../membw_analysis_test_suite/README.md | 52 + .../membw_analysis_test_suite/ea/Makefile | 15 + .../membw_analysis_test_suite/ea/README.md | 380 ++ .../ea/ea_atomic_pressure.hip | 134 + .../ea/ea_fabric_bw.hip | 133 + .../ea/ea_hbm_read_bw.hip | 127 + .../ea/ea_hbm_write_bw.hip | 104 + .../membw_analysis_test_suite/ea/ea_io_bw.hip | 104 + .../ea/ea_rw_balance.hip | 113 + .../ea/ea_write_backpressure.hip | 112 + .../gl2_backpressure.hip | 96 + .../membw_analysis_test_suite/l2/README.md | 119 + .../l2/VERIFICATION_REPORT.md | 275 ++ .../l2/l2_atomic_stress.hip | 132 + .../l2/l2_cache_thrash.hip | 136 + .../l2/l2_coherence_traffic.hip | 164 + .../l2/l2_hbm_read_bw_stress.hip | 140 + .../l2/l2_hbm_write_bw_stress.hip | 125 + .../l2/l2_io_stress.hip | 153 + .../l2/l2_multigpu_fabric.hip | 172 + .../l2/l2_normalized_throughput.hip | 136 + .../ta_tcp_stall.hip | 87 + .../membw_analysis_test_suite/utcl1_stall.hip | 87 + skills/rocprof-compute-sample/memcopy.cpp | 128 + skills/rocprof-compute-sample/occupancy.hip | 85 + skills/rocprof-compute-sample/rocflop.cpp | 742 ++++ skills/rocprof-compute-sample/simple_net.py | 127 + skills/rocprof-compute-sample/stack.hip | 20 + .../torch_triton_net.py | 62 + skills/rocprof-compute-sample/triton_ffn.py | 179 + skills/rocprof-compute-sample/vcopy.cpp | 227 + skills/rocprof-compute-sample/vmem.hip | 75 + .../rocprof-compute-sample/vrandom_access.cpp | 82 + .../vsequential_access.cpp | 60 + .../wmma_gemm_fp16/Makefile | 34 + .../wmma_gemm_fp16/main.cpp | 208 + .../wmma_gemm_fp16/wmma_gemm_fp16.hip | 153 + .../wmma_gemm_fp16/wmma_gemm_fp16.hpp | 19 + .../wmma_gemm_fp16/wmma_gemm_fp16_config.hpp | 31 + .../evals/evals.json | 4 +- .../evals/evals.json | 4 +- 74 files changed, 13870 insertions(+), 26 deletions(-) create mode 100644 skills/rocprof-compute-sample/.fixture.json create mode 100644 skills/rocprof-compute-sample/common.h create mode 100644 skills/rocprof-compute-sample/conjugate_gradient/CMakeLists.txt create mode 100644 skills/rocprof-compute-sample/conjugate_gradient/README.md create mode 100644 skills/rocprof-compute-sample/conjugate_gradient/cg_args.hpp create mode 100644 skills/rocprof-compute-sample/conjugate_gradient/cg_spmv.hip create mode 100644 skills/rocprof-compute-sample/conjugate_gradient/cg_update.hip create mode 100644 skills/rocprof-compute-sample/conjugate_gradient/conjugate_gradient.cpp create mode 100644 skills/rocprof-compute-sample/dynamic_shared/dynamic_shared.hip create mode 100644 skills/rocprof-compute-sample/dynamic_shared/example_utils.hpp create mode 100644 skills/rocprof-compute-sample/fabric.hip create mode 100644 skills/rocprof-compute-sample/instmix.hip create mode 100644 skills/rocprof-compute-sample/ipc.hip create mode 100644 skills/rocprof-compute-sample/laplace_eqn.hip create mode 100644 skills/rocprof-compute-sample/lds.hip create mode 100644 skills/rocprof-compute-sample/mat_mul_max.hip create mode 100644 skills/rocprof-compute-sample/mega_kernel/Makefile create mode 100644 skills/rocprof-compute-sample/mega_kernel/README.md create mode 100644 skills/rocprof-compute-sample/mega_kernel/atomic_global_buffers.h create mode 100644 skills/rocprof-compute-sample/mega_kernel/main.cpp create mode 100644 skills/rocprof-compute-sample/mega_kernel/mega_kernel.hip create mode 100644 skills/rocprof-compute-sample/mega_kernel/mega_kernel_device_arch.h create mode 100644 skills/rocprof-compute-sample/mega_kernel/mega_kernel_host_arch.hpp create mode 100644 skills/rocprof-compute-sample/mega_kernel/mega_kernel_types.h create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/L1_stall_microbenchmark.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/README.md create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/Makefile create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/README.md create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_atomic_pressure.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_fabric_bw.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_read_bw.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_write_bw.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_io_bw.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_rw_balance.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_write_backpressure.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/gl2_backpressure.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/README.md create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/VERIFICATION_REPORT.md create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_atomic_stress.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_cache_thrash.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_coherence_traffic.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_read_bw_stress.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_write_bw_stress.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_io_stress.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_multigpu_fabric.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_normalized_throughput.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ta_tcp_stall.hip create mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/utcl1_stall.hip create mode 100644 skills/rocprof-compute-sample/memcopy.cpp create mode 100644 skills/rocprof-compute-sample/occupancy.hip create mode 100644 skills/rocprof-compute-sample/rocflop.cpp create mode 100755 skills/rocprof-compute-sample/simple_net.py create mode 100644 skills/rocprof-compute-sample/stack.hip create mode 100644 skills/rocprof-compute-sample/torch_triton_net.py create mode 100644 skills/rocprof-compute-sample/triton_ffn.py create mode 100644 skills/rocprof-compute-sample/vcopy.cpp create mode 100644 skills/rocprof-compute-sample/vmem.hip create mode 100644 skills/rocprof-compute-sample/vrandom_access.cpp create mode 100644 skills/rocprof-compute-sample/vsequential_access.cpp create mode 100644 skills/rocprof-compute-sample/wmma_gemm_fp16/Makefile create mode 100644 skills/rocprof-compute-sample/wmma_gemm_fp16/main.cpp create mode 100644 skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16.hip create mode 100644 skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16.hpp create mode 100644 skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16_config.hpp diff --git a/.github/federation.json b/.github/federation.json index 4bda046..bb53389 100644 --- a/.github/federation.json +++ b/.github/federation.json @@ -82,6 +82,13 @@ "repo": "ROCm/rocm-systems", "branch": "develop", "license": "MIT", + "fixtures": [ + { + "path": "projects/rocprofiler-compute/sample", + "as": "rocprof-compute-sample", + "workspace_prefix": "../../sample" + } + ], "skills": [ { "path": "projects/rocprofiler-sdk/skills/pc-sampling", diff --git a/.github/scripts/federate_skills.py b/.github/scripts/federate_skills.py index 347d322..e828fb5 100644 --- a/.github/scripts/federate_skills.py +++ b/.github/scripts/federate_skills.py @@ -32,6 +32,11 @@ not vendored: they are left out of the copy and of the content hash, and named in the run log and the pull request body. The one file kept when upstream lacks it is `evals/machine.yml` (`LOCAL_FALLBACK_FILES`). + A source may also declare `fixtures`: a directory that is not a skill + (the rocprofiler-compute sample tree) copied once under `skills//`. + Vendored `evals.json` workspace values that start with that fixture's + `workspace_prefix` are rewritten to `../` so they resolve next to the + skill. Upstream files are not modified. 3b. Optionally vendors the skill under a different local catalog name (the `as:` field on a skill entry). Federated skills follow a `-` naming convention in this catalog (e.g. the @@ -143,8 +148,10 @@ MARKETPLACE_DESCRIPTION_MAX = 320 REPO_RE = re.compile(r"^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$") -SOURCE_KEYS = {"repo", "license", "branch", "skills"} +SOURCE_KEYS = {"repo", "license", "branch", "skills", "fixtures"} SKILL_KEYS = {"path", "as", "marketplace_description"} +FIXTURE_KEYS = {"path", "as", "workspace_prefix"} +FIXTURE_MARKER = ".fixture.json" # Keys that would pin a source to something other than a branch. They are # rejected with a pointed message rather than ignored, so an entry that tries # to track a tag or commit fails loudly instead of silently tracking `main`. @@ -170,11 +177,26 @@ def dest_name(self) -> str: return self.local_name or self.folder +@dataclass +class FixtureSpec: + """A non-skill directory vendored once and shared by a source's skills. + + `workspace_prefix` is the path those skills use in their own repo + (for example ``../../sample``). Vendored datasets are rewritten so the + same value resolves to ``../`` beside the skill. + """ + + path: str + local_name: str + workspace_prefix: str + + @dataclass class Source: repo: str license: str skills: list[SkillSpec] = field(default_factory=list) + fixtures: list[FixtureSpec] = field(default_factory=list) # As declared: a branch name, or a pattern such as `release/*`. branch: str = branches.DEFAULT_BRANCH @@ -295,17 +317,98 @@ def parse_federation(catalog: Path) -> list[Source]: ) ) + fixtures = _parse_fixtures(raw.get("fixtures", []), f"{where}.fixtures") sources.append( Source( repo=repo, license=raw.get("license") or "UNKNOWN", skills=skills, + fixtures=fixtures, branch=branch, ) ) + _reject_name_collisions(sources) return sources +def _parse_repo_path(path: object, where: str) -> str: + """A repo-relative directory path: no scheme, no ``..``, no leading slash.""" + if not isinstance(path, str) or not path.strip("/"): + raise ValueError( + f"{where} must be a directory path inside the repo, got {path!r}." + ) + normalized = path.replace("\\", "/").strip("/") + if ".." in normalized.split("/"): + raise ValueError(f"{where} must not contain '..': {path!r}") + return normalized + + +def _parse_fixtures(raw: object, where: str) -> list[FixtureSpec]: + if raw is None: + return [] + if not isinstance(raw, list): + raise ValueError(f"{where} must be an array.") + fixtures: list[FixtureSpec] = [] + for idx, item in enumerate(raw): + item_where = f"{where}[{idx}]" + if not isinstance(item, dict): + raise ValueError(f"{item_where} must be an object.") + unknown = set(item) - FIXTURE_KEYS + if unknown: + raise ValueError( + f"{item_where} has unknown key(s) {sorted(unknown)}; expected " + f"{sorted(FIXTURE_KEYS)}." + ) + local_name = item.get("as") + if not isinstance(local_name, str) or not local_name or "/" in local_name: + raise ValueError( + f"{item_where}.as must be a plain local folder name, got " + f"{local_name!r}." + ) + if local_name.startswith("."): + raise ValueError(f"{item_where}.as must not be hidden: {local_name!r}") + prefix = item.get("workspace_prefix") + if not isinstance(prefix, str) or not prefix.strip("/"): + raise ValueError( + f"{item_where}.workspace_prefix must be the relative workspace " + f"path used upstream, got {prefix!r}." + ) + prefix = prefix.replace("\\", "/").strip("/") + if prefix.startswith("/") or "://" in prefix: + raise ValueError( + f"{item_where}.workspace_prefix must be a relative path, got " + f"{prefix!r}." + ) + fixtures.append( + FixtureSpec( + path=_parse_repo_path(item.get("path"), f"{item_where}.path"), + local_name=local_name, + workspace_prefix=prefix, + ) + ) + return fixtures + + +def _reject_name_collisions(sources: list[Source]) -> None: + """Skill folders and fixture folders share `skills/`, so names must be unique.""" + seen: set[str] = set() + for source in sources: + for spec in source.skills: + if spec.dest_name in seen: + raise ValueError( + f"Skill name collision: {spec.dest_name!r} is declared " + "more than once." + ) + seen.add(spec.dest_name) + for fixture in source.fixtures: + if fixture.local_name in seen: + raise ValueError( + f"Fixture name collision: {fixture.local_name!r} is also " + "used as a skill or fixture folder." + ) + seen.add(fixture.local_name) + + def run(cmd: list[str], cwd: Path | None = None) -> str: """Run a command, raise on failure, return stdout.""" result = subprocess.run( @@ -388,7 +491,9 @@ def shallow_clone(repo: str, branch: str, sub_paths: Iterable[str], dest: Path) return run(["git", "rev-parse", "HEAD"], cwd=dest) -def content_hash(root: Path, exclude: Iterable[str] = ()) -> str: +def content_hash( + root: Path, exclude: Iterable[str] = (), *, max_bytes: int | None = MAX_FILE_BYTES +) -> str: """Hash every file under `root` (relative paths and bytes). Change detection compares this against the hash stored in the vendored @@ -404,15 +509,18 @@ def content_hash(root: Path, exclude: Iterable[str] = ()) -> str: on a maintainer's machine and before it on a Linux runner, which would hash identical files to different digests. - Files over `MAX_FILE_BYTES` are left out, since they are never vendored. - `exclude` names relative paths to leave out of the digest. + Files over `max_bytes` are left out. Skill hashes use `MAX_FILE_BYTES`, + because those files are never vendored. A fixture hash passes `None` so a + large sample source still counts. `exclude` names relative paths to leave + out of the digest. """ skipped = set(exclude) entries = sorted( ( (path.relative_to(root).as_posix(), path) for path in root.rglob("*") - if path.is_file() and path.stat().st_size <= MAX_FILE_BYTES + if path.is_file() + and (max_bytes is None or path.stat().st_size <= max_bytes) ), key=lambda entry: entry[0], ) @@ -746,7 +854,11 @@ def import_source( log.append(f"[{source.name}] {source.branch} resolved to {branch}") log.append(f"[{source.name}] cloning {source.repo}@{branch}") commit = shallow_clone( - source.repo, branch, [spec.path for spec in source.skills], tmp_path + source.repo, + branch, + [spec.path for spec in source.skills] + + [fixture.path for fixture in source.fixtures], + tmp_path, ) log.append(f"[{source.name}] resolved to commit {commit}") repo_files = list_repo_files(tmp_path, commit) @@ -873,9 +985,104 @@ def import_source( omitted=omitted, ) ) + if source.fixtures: + sync_fixtures(source, tmp_path, branch, commit, log) return results +def rewrite_fixture_workspaces(skill_dir: Path, fixture: FixtureSpec) -> bool: + """Point vendored workspace paths at the catalog copy of a fixture. + + Upstream keeps ``workspace_prefix`` (``../../sample`` inside the product + tree). The catalog copy lives beside the skill, so the same cases use + ``../``. A second run finds nothing to replace. + """ + prefix = fixture.workspace_prefix + replacement = f"../{fixture.local_name}" + changed = False + for name in ("evals/evals.json", "evals/extended_evals.json"): + path = skill_dir / name + if not path.is_file(): + continue + original = path.read_text(encoding="utf-8") + updated = original.replace(f'"{prefix}', f'"{replacement}') + if updated != original: + path.write_text(updated, encoding="utf-8") + changed = True + return changed + + +def sync_fixtures( + source: Source, + clone_dir: Path, + branch: str, + commit: str, + log: list[str], + skills_dir: Path = SKILLS_DIR, +) -> None: + """Copy each declared fixture under `skills/` and retarget workspace paths. + + The fixture is not a skill: its marker is `.fixture.json`, so the + undeclared-skill report does not treat the directory as a vendored skill. + Sample sources are copied whole, including files over `MAX_FILE_BYTES`. + """ + for fixture in source.fixtures: + src = clone_dir / fixture.path + if not src.is_dir(): + raise FileNotFoundError( + f"Fixture path {fixture.path!r} not found in " + f"{source.repo}@{branch}." + ) + dest = skills_dir / fixture.local_name + upstream_hash = content_hash(src, max_bytes=None) + marker_path = dest / FIXTURE_MARKER + previous = {} + if marker_path.is_file(): + try: + loaded = json.loads(marker_path.read_text(encoding="utf-8")) + previous = loaded if isinstance(loaded, dict) else {} + except json.JSONDecodeError: + previous = {} + same = ( + previous.get("content_hash") == upstream_hash + and previous.get("repo") == source.repo + and previous.get("path") == fixture.path + and previous.get("ref") == source.branch + ) + if same: + log.append( + f"[{source.name}] skills/{fixture.local_name} fixture is up to date" + ) + else: + if dest.exists(): + shutil.rmtree(dest) + shutil.copytree(src, dest) + marker = { + "repo": source.repo, + "ref": source.branch, + "commit": commit, + "path": fixture.path, + "content_hash": upstream_hash, + "imported_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + } + if branch != source.branch: + marker["resolved_ref"] = branch + (dest / FIXTURE_MARKER).write_text( + json.dumps(marker, indent=2) + "\n", encoding="utf-8" + ) + log.append( + f"[{source.name}] vendoring fixture {fixture.path} -> " + f"skills/{fixture.local_name}" + ) + for spec in source.skills: + skill_dir = skills_dir / spec.dest_name + if rewrite_fixture_workspaces(skill_dir, fixture): + log.append( + f"[{source.name}] skills/{spec.dest_name}: workspace " + f"{fixture.workspace_prefix} -> ../{fixture.local_name}" + ) + + def report_undeclared( declared: set[str], existing: dict[str, dict], @@ -1049,6 +1256,12 @@ def main(argv: list[str] | None = None) -> int: f"{source.repo}@{source.branch} {spec.path} " f"-> skills/{spec.dest_name}" ) + for fixture in source.fixtures: + print( + f"{source.repo}@{source.branch} {fixture.path} " + f"-> skills/{fixture.local_name} " + f"(workspace {fixture.workspace_prefix})" + ) print("") print(f"{args.catalog} is valid: {len(seen)} skill(s) declared.") return 0 diff --git a/.github/scripts/test_federate_skills.py b/.github/scripts/test_federate_skills.py index f3d374e..15b608b 100644 --- a/.github/scripts/test_federate_skills.py +++ b/.github/scripts/test_federate_skills.py @@ -147,6 +147,106 @@ def test_rejects_malformed_entries(self): with self.assertRaises(ValueError): parse(payload) + def test_parses_a_shared_fixture(self): + source = parse( + one_source( + fixtures=[ + { + "path": "projects/rocprofiler-compute/sample", + "as": "rocprof-compute-sample", + "workspace_prefix": "../../sample", + } + ] + ) + )[0] + self.assertEqual(len(source.fixtures), 1) + fixture = source.fixtures[0] + self.assertEqual(fixture.path, "projects/rocprofiler-compute/sample") + self.assertEqual(fixture.local_name, "rocprof-compute-sample") + self.assertEqual(fixture.workspace_prefix, "../../sample") + + def test_rejects_a_fixture_that_collides_with_a_skill(self): + with self.assertRaises(ValueError): + parse( + one_source( + skills=[{"path": "skills/sample", "as": "rocprof-compute-sample"}], + fixtures=[ + { + "path": "projects/sample", + "as": "rocprof-compute-sample", + "workspace_prefix": "../../sample", + } + ], + ) + ) + + def test_rewrites_workspace_prefixes_once(self): + fixture = fed.FixtureSpec( + path="projects/sample", + local_name="rocprof-compute-sample", + workspace_prefix="../../sample", + ) + with tempfile.TemporaryDirectory() as tmp: + skill = write_skill( + Path(tmp) / "skill", + { + "evals/evals.json": ( + '{ "workspace": "../../sample/wmma_gemm_fp16" }\n' + ) + }, + ) + self.assertTrue(fed.rewrite_fixture_workspaces(skill, fixture)) + text = (skill / "evals" / "evals.json").read_text(encoding="utf-8") + self.assertIn("../rocprof-compute-sample/wmma_gemm_fp16", text) + self.assertNotIn("../../sample", text) + self.assertFalse(fed.rewrite_fixture_workspaces(skill, fixture)) + + def test_sync_fixtures_copies_the_tree_and_retargets_evals(self): + source = fed.Source( + repo="ROCm/rocm-systems", + license="MIT", + branch="develop", + skills=[fed.SkillSpec(path="skills/demo", local_name="demo")], + fixtures=[ + fed.FixtureSpec( + path="projects/sample", + local_name="rocprof-compute-sample", + workspace_prefix="../../sample", + ) + ], + ) + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + write_skill( + root / "clone" / "projects" / "sample", + {"vcopy.cpp": "int main(){return 0;}\n"}, + ) + write_skill( + root / "skills" / "demo", + {"evals/evals.json": '{ "workspace": "../../sample" }\n'}, + ) + log: list[str] = [] + fed.sync_fixtures( + source, root / "clone", "develop", "abc123", log, root / "skills" + ) + copied = root / "skills" / "rocprof-compute-sample" / "vcopy.cpp" + self.assertEqual(copied.read_text(encoding="utf-8"), "int main(){return 0;}\n") + marker = json.loads( + (root / "skills" / "rocprof-compute-sample" / ".fixture.json").read_text( + encoding="utf-8" + ) + ) + self.assertEqual(marker["commit"], "abc123") + self.assertEqual(marker["path"], "projects/sample") + evals = (root / "skills" / "demo" / "evals" / "evals.json").read_text( + encoding="utf-8" + ) + self.assertIn("../rocprof-compute-sample", evals) + fed.sync_fixtures( + source, root / "clone", "develop", "abc123", log, root / "skills" + ) + self.assertTrue(any("up to date" in line for line in log)) + def test_accepts_windows_style_separators_in_paths(self): skills = parse( one_source(skills=[{"path": r"TraceLens\Agent\skills\orchestrator"}]) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d9250a..8ce58d7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,6 +101,7 @@ one repo can federate as many skills as it likes from wherever they live: | `branch` | Optional. The branch to track, or a release pattern such as `release/*`. Defaults to `main` | | `skills[].path` | Path of the skill folder inside your repo, from the repo root | | `skills[].as` | Optional local catalog name; use it to namespace as `-` so names stay unique | +| `fixtures[]` | Optional directory that is not a skill, copied once to `skills//`. `workspace_prefix` is the relative `workspace` value in the upstream datasets (`../../sample`). Vendored datasets are rewritten to `../` | There is no tag or commit field. A source always follows a branch. diff --git a/skills/rocprof-compute-kernel-bottleneck/evals/evals.json b/skills/rocprof-compute-kernel-bottleneck/evals/evals.json index f36df90..a13db25 100644 --- a/skills/rocprof-compute-kernel-bottleneck/evals/evals.json +++ b/skills/rocprof-compute-kernel-bottleneck/evals/evals.json @@ -4,7 +4,7 @@ "id": "kb-profile-hip-application", "skill_should_trigger": true, "prompt": "profile my HIP application with rocprof-compute and tell me where the time goes\n\nThe application is ./vcopy.cpp.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "asks for the launch command and a workload name before profiling", "runs rocprof-compute profile --name -- ", @@ -19,7 +19,7 @@ "id": "kb-which-kernel-is-slow", "skill_should_trigger": true, "prompt": "which kernel is the hotspot in ./workloads/my_app and why is it slow?\n\nThe application is ./vmem.hip.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "points --path at the directory containing profiling_config.yaml rather than a parent directory", "uses --list-stats first, then narrows to one kernel id with -k", @@ -31,7 +31,7 @@ "id": "kb-occupancy-limited", "skill_should_trigger": true, "prompt": "is occupancy limiting this rocprof-compute kernel?\n\nThe application is ./occupancy.hip.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "opens the wavefront block for the selected kernel", "compares achieved occupancy against the theoretical occupancy printed in the report", @@ -46,7 +46,7 @@ "id": "kb-expensive-replay-fallback", "skill_should_trigger": true, "prompt": "a full rocprof-compute replay of this workload is too expensive, collect something cheaper\n\nThe application is ./vmem.hip.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "offers a single-pass option such as --set, a narrowed -b, or --iteration-multiplexing", "runs --list-sets before selecting a set", @@ -71,7 +71,7 @@ "id": "kb-kernel-name-filter", "skill_should_trigger": true, "prompt": "profile only the gemm kernels in my ROCm application\n\nThe application is ./wmma_gemm_fp16.hip.", - "workspace": "../../sample/wmma_gemm_fp16", + "workspace": "../rocprof-compute-sample/wmma_gemm_fp16", "expected_behavior": [ "uses rocprof-compute profile -k to filter by kernel name", "treats the profile-time -k value as a regex matched against kernel names" diff --git a/skills/rocprof-compute-memory/evals/evals.json b/skills/rocprof-compute-memory/evals/evals.json index 70a418b..2794b37 100644 --- a/skills/rocprof-compute-memory/evals/evals.json +++ b/skills/rocprof-compute-memory/evals/evals.json @@ -4,7 +4,7 @@ "id": "mem-access-inefficiency", "skill_should_trigger": true, "prompt": "find the memory access inefficiencies in this rocprofiler-compute capture\n\nThe application is ./vrandom_access.cpp.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "reads the memory chart before opening an individual cache level", "cites hit rates or request counts from the report", @@ -19,7 +19,7 @@ "id": "mem-cache-hit-rates", "skill_should_trigger": true, "prompt": "what are my L2 and vL1D hit rates for this kernel and are they bad?\n\nThe application is ./vsequential_access.cpp.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "opens the l2 and vl1d blocks for a single kernel id", "judges the rates against what the algorithm should do rather than a fixed cutoff" @@ -33,7 +33,7 @@ "id": "mem-lds-bank-conflicts", "skill_should_trigger": true, "prompt": "does my kernel have LDS bank conflicts?\n\nThe application is ./lds.hip.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "opens the lds block for the selected kernel", "reads the bank conflict counters from the report" @@ -44,7 +44,7 @@ "id": "mem-bandwidth-bound", "skill_should_trigger": true, "prompt": "this kernel looks memory bound, where is the bandwidth going?\n\nThe application is ./vcopy.cpp.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "walks the hierarchy from the memory chart down to the level responsible", "distinguishes traffic that caches absorbed from traffic that reached HBM" @@ -55,7 +55,7 @@ "id": "mem-membw-analysis-gfx950", "skill_should_trigger": true, "prompt": "run the guided memory bandwidth analysis on my MI350 workload\n\nThe application is ./gl2_backpressure.hip.", - "workspace": "../../sample/membw_analysis_test_suite", + "workspace": "../rocprof-compute-sample/membw_analysis_test_suite", "expected_behavior": [ "uses --experimental --membw-analysis", "notes that the analysis is experimental and shipped for gfx950", diff --git a/skills/rocprof-compute-pc-sampling/evals/evals.json b/skills/rocprof-compute-pc-sampling/evals/evals.json index 0ec8365..d9aa23a 100644 --- a/skills/rocprof-compute-pc-sampling/evals/evals.json +++ b/skills/rocprof-compute-pc-sampling/evals/evals.json @@ -4,7 +4,7 @@ "id": "pcs-which-instruction-stalls", "skill_should_trigger": true, "prompt": "which instruction in my MI300 kernel stalls the most?\n\nThe application is ./vmem.hip.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "queries the device for supported PC sampling configurations before choosing an interval", "collects with --experimental --pc-sampling", @@ -19,7 +19,7 @@ "id": "pcs-source-line-attribution", "skill_should_trigger": true, "prompt": "map the hot GPU instructions back to my HIP source lines\n\nThe application is ./vmem.hip.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "states that source line mapping requires the application to be built with debug info", "notes that samples map to assembly even without debug info" @@ -43,7 +43,7 @@ "id": "pcs-export-disassembly", "skill_should_trigger": true, "prompt": "export the annotated disassembly with sample counts from my pc sampling workload\n\nThe application is ./vmem.hip.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "uses --output-format csv or db", "notes that the per-kernel disassembly is written under per_kernel_pc_sampling" diff --git a/skills/rocprof-compute-roofline/evals/evals.json b/skills/rocprof-compute-roofline/evals/evals.json index feb851f..23a8e7b 100644 --- a/skills/rocprof-compute-roofline/evals/evals.json +++ b/skills/rocprof-compute-roofline/evals/evals.json @@ -4,7 +4,7 @@ "id": "roof-compute-or-memory-bound", "skill_should_trigger": true, "prompt": "is this rocprof-compute kernel compute bound or memory bound?\n\nThe application is ./vcopy.cpp.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "analyzes the roof block for a single kernel id", "compares arithmetic intensity against the empirical peaks printed in the table", @@ -20,7 +20,7 @@ "id": "roof-arithmetic-intensity", "skill_should_trigger": true, "prompt": "what is the arithmetic intensity of my hottest kernel and how much performance am I leaving on the table?\n\nThe application is ./mat_mul_max.hip.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "reads FLOPs per byte and the peak values from the roofline table", "narrows to one kernel id first" @@ -31,7 +31,7 @@ "id": "roof-precision-selection", "skill_should_trigger": true, "prompt": "my half precision GEMM looks terrible on the roofline, is that right?\n\nThe application is ./wmma_gemm_fp16.hip.", - "workspace": "../../sample/wmma_gemm_fp16", + "workspace": "../rocprof-compute-sample/wmma_gemm_fp16", "expected_behavior": [ "compares against the peak for the precision the kernel actually issues instead of FP32", "confirms the precision from the instruction mix rather than assuming it" diff --git a/skills/rocprof-compute-sample/.fixture.json b/skills/rocprof-compute-sample/.fixture.json new file mode 100644 index 0000000..5351a96 --- /dev/null +++ b/skills/rocprof-compute-sample/.fixture.json @@ -0,0 +1,8 @@ +{ + "repo": "ROCm/rocm-systems", + "ref": "develop", + "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", + "path": "projects/rocprofiler-compute/sample", + "content_hash": "e609e31067483504a04656c5850f0e471d7eedfd79efd6b07bc5161bacedf837", + "imported_at": "2026-10-26T21:12:24Z" +} diff --git a/skills/rocprof-compute-sample/common.h b/skills/rocprof-compute-sample/common.h new file mode 100644 index 0000000..2609685 --- /dev/null +++ b/skills/rocprof-compute-sample/common.h @@ -0,0 +1,20 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +#define hipCheck(stmt) \ + do { \ + hipError_t err = stmt; \ + if (err != hipSuccess) { \ + char msg[256]; \ + sprintf(msg, "%s in file %s, function %s, line %d\n", #stmt, __FILE__, \ + __FUNCTION__, __LINE__); \ + std::string errstring = hipGetErrorString(err); \ + std::cerr << msg << "\t" << errstring << std::endl; \ + throw std::runtime_error(msg); \ + } \ + } while (0) diff --git a/skills/rocprof-compute-sample/conjugate_gradient/CMakeLists.txt b/skills/rocprof-compute-sample/conjugate_gradient/CMakeLists.txt new file mode 100644 index 0000000..867f35f --- /dev/null +++ b/skills/rocprof-compute-sample/conjugate_gradient/CMakeLists.txt @@ -0,0 +1,38 @@ +# The executable and its two kernel libraries are colocated in one directory so +# the driver can resolve the libraries relative to its own path. +set(CG_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/tests/conjugate_gradient) + +# Each kernel is its own shared library: kernels compiled into a single binary +# share one fat binary, and so one code object, which is exactly what this +# sample needs to avoid. +foreach(CG_KERNEL_LIBRARY IN ITEMS cg_spmv cg_update) + set_source_files_properties(${CG_KERNEL_LIBRARY}.hip PROPERTIES LANGUAGE HIP) + add_library(${CG_KERNEL_LIBRARY} SHARED ${CG_KERNEL_LIBRARY}.hip) + target_include_directories(${CG_KERNEL_LIBRARY} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + # PC sampling maps samples back to source lines, which needs debug info. + target_compile_options(${CG_KERNEL_LIBRARY} PRIVATE -g) + set_target_properties( + ${CG_KERNEL_LIBRARY} + PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CG_OUTPUT_DIRECTORY} + ) +endforeach() + +set_source_files_properties(conjugate_gradient.cpp PROPERTIES LANGUAGE HIP) +add_executable(conjugate_gradient conjugate_gradient.cpp) +target_include_directories(conjugate_gradient PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_options(conjugate_gradient PRIVATE -g) +target_link_libraries(conjugate_gradient PRIVATE ${CMAKE_DL_LIBS}) +set_target_properties( + conjugate_gradient + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CG_OUTPUT_DIRECTORY} +) + +# The libraries are opened by name at runtime, not linked, so CMake supplies the +# file names and a target rename cannot desync them from the source. +target_compile_definitions( + conjugate_gradient + PRIVATE + CG_SPMV_LIBRARY="$" + CG_UPDATE_LIBRARY="$" +) +add_dependencies(conjugate_gradient cg_spmv cg_update) diff --git a/skills/rocprof-compute-sample/conjugate_gradient/README.md b/skills/rocprof-compute-sample/conjugate_gradient/README.md new file mode 100644 index 0000000..cab9680 --- /dev/null +++ b/skills/rocprof-compute-sample/conjugate_gradient/README.md @@ -0,0 +1,49 @@ +# Multiprocess Conjugate-Gradient Sample + +A PC sampling workload in which the same kernel carries a different +process-local code-object ID in each of two processes. + +Two conjugate-gradient-inspired kernels are built into separate shared +libraries: + +- `kernel_spmv_csr` performs sparse matrix-vector multiplication over a CSR + matrix with power-law row lengths. +- `kernel_cg_update_reduce` updates `x` and `r`, then performs a block-local + residual reduction. + +The driver forks once, and both processes run both kernels in opposite order. +HIP defers loading a code object until the first launch out of it, so the +process that runs SpMV first gives it the lower ID and the other process gives +it the higher one. That divergence is what makes the pair useful for checking +that sample attribution keys on the process as well as the code object. + +The kernels live in separate libraries rather than in the driver because +kernels compiled into one binary share a single fat binary, which the runtime +loads as one code object holding both symbols. + +This is not a convergent CG solver or a correctness benchmark: the processes do +not exchange results, and kernel outputs are neither copied back nor verified. + +## Build + +From the repository root, run: + +```bash +cmake -S . -B build -DENABLE_TESTS=ON +cmake --build build --target conjugate_gradient --parallel +``` + +The build populates `tests/conjugate_gradient/` with the driver and the two +kernel libraries. CMake selects the GPU architecture; when cross-compiling or +building without a GPU, set it explicitly with +`-DCMAKE_HIP_ARCHITECTURES=gfx942` or similar. + +## Run + +The workload takes no arguments: + +```bash +./tests/conjugate_gradient/conjugate_gradient +``` + +Each process prints its PID, its role, and the order it launched the kernels in. diff --git a/skills/rocprof-compute-sample/conjugate_gradient/cg_args.hpp b/skills/rocprof-compute-sample/conjugate_gradient/cg_args.hpp new file mode 100644 index 0000000..a40a646 --- /dev/null +++ b/skills/rocprof-compute-sample/conjugate_gradient/cg_args.hpp @@ -0,0 +1,27 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +inline constexpr std::uint32_t cg_block_size = 256; +inline constexpr std::uint32_t cg_rows = 65536; + +struct CgArgs +{ + const std::uint32_t* row_offsets; + const std::uint32_t* column_indices; + const float* values; + const float* p; + float* q; + float* x; + float* r; + float* partials; + std::uint32_t rows; + std::uint32_t rounds; +}; + +// Every kernel library exports this one symbol, so the driver resolves the same +// name against each handle instead of tracking a symbol name per library. +using CgLaunch = void (*)(const CgArgs&); diff --git a/skills/rocprof-compute-sample/conjugate_gradient/cg_spmv.hip b/skills/rocprof-compute-sample/conjugate_gradient/cg_spmv.hip new file mode 100644 index 0000000..0b97a3a --- /dev/null +++ b/skills/rocprof-compute-sample/conjugate_gradient/cg_spmv.hip @@ -0,0 +1,39 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// Built as its own shared library so that the runtime loads it as a code object +// separate from the one holding kernel_cg_update_reduce. + +#include "cg_args.hpp" + +#include + +#include + +// extern "C" keeps the symbol unmangled, so profiler output names the kernel +// the same way this file does. +extern "C" __global__ __launch_bounds__(cg_block_size) void kernel_spmv_csr(CgArgs arguments) +{ + const std::uint32_t row = blockIdx.x * blockDim.x + threadIdx.x; + if (row >= arguments.rows) + { + return; + } + + for (std::uint32_t round = 0; round < arguments.rounds; ++round) + { + float sum = 0.0F; + for (std::uint32_t entry = arguments.row_offsets[row]; entry < arguments.row_offsets[row + 1]; + ++entry) + { + sum += arguments.values[entry] * arguments.p[arguments.column_indices[entry]]; + } + arguments.q[row] = sum; + } +} + +extern "C" void cg_launch(const CgArgs& arguments) +{ + const std::uint32_t grid_size = (arguments.rows + cg_block_size - 1) / cg_block_size; + hipLaunchKernelGGL(kernel_spmv_csr, dim3(grid_size), dim3(cg_block_size), 0, nullptr, arguments); +} diff --git a/skills/rocprof-compute-sample/conjugate_gradient/cg_update.hip b/skills/rocprof-compute-sample/conjugate_gradient/cg_update.hip new file mode 100644 index 0000000..4e96c23 --- /dev/null +++ b/skills/rocprof-compute-sample/conjugate_gradient/cg_update.hip @@ -0,0 +1,55 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// Built as its own shared library so that the runtime loads it as a code object +// separate from the one holding kernel_spmv_csr. + +#include "cg_args.hpp" + +#include + +#include + +// extern "C" keeps the symbol unmangled, so profiler output names the kernel +// the same way this file does. +extern "C" __global__ __launch_bounds__(cg_block_size) void kernel_cg_update_reduce(CgArgs arguments) +{ + __shared__ float reduction_values[cg_block_size]; + + const std::uint32_t row = blockIdx.x * blockDim.x + threadIdx.x; + for (std::uint32_t round = 0; round < arguments.rounds; ++round) + { + float squared_residual = 0.0F; + if (row < arguments.rows) + { + const float alpha = 0.5F + static_cast(round) * 1.0E-6F; + arguments.x[row] += alpha * arguments.p[row]; + arguments.r[row] -= alpha * arguments.q[row]; + squared_residual = arguments.r[row] * arguments.r[row]; + } + + reduction_values[threadIdx.x] = squared_residual; + __syncthreads(); + + for (std::uint32_t stride = cg_block_size / 2; stride > 0; stride /= 2) + { + if (threadIdx.x < stride) + { + reduction_values[threadIdx.x] += reduction_values[threadIdx.x + stride]; + } + __syncthreads(); + } + + if (threadIdx.x == 0) + { + arguments.partials[blockIdx.x] = reduction_values[0]; + } + } +} + +extern "C" void cg_launch(const CgArgs& arguments) +{ + const std::uint32_t grid_size = (arguments.rows + cg_block_size - 1) / cg_block_size; + hipLaunchKernelGGL( + kernel_cg_update_reduce, dim3(grid_size), dim3(cg_block_size), 0, nullptr, arguments); +} diff --git a/skills/rocprof-compute-sample/conjugate_gradient/conjugate_gradient.cpp b/skills/rocprof-compute-sample/conjugate_gradient/conjugate_gradient.cpp new file mode 100644 index 0000000..a1d947b --- /dev/null +++ b/skills/rocprof-compute-sample/conjugate_gradient/conjugate_gradient.cpp @@ -0,0 +1,319 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// Two processes run the same two conjugate-gradient kernels, each process +// loading them in the opposite order, so a given kernel ends up with a +// different process-local code-object ID in each one. +// +// The kernels live in two shared libraries rather than in this binary. Kernels +// compiled into one binary share a single fat binary, so the runtime would load +// them as one code object holding both symbols and there would be nothing to +// tell apart. +// +// HIP defers loading a code object until the first launch out of it, so the +// launch order is what assigns the IDs. The libraries are opened in that same +// order as well, which keeps the split intact when deferred loading is off. +// +// The child re-execs itself so the profiler's runtime hooks initialize inside +// it; a plain fork() is not enough for the child to be picked up. + +#include "cg_args.hpp" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +// CMake supplies the file names, so renaming a library target cannot desync it +// from this source. +constexpr std::array cg_kernel_libraries = {CG_SPMV_LIBRARY, CG_UPDATE_LIBRARY}; + +// SpMV does far more work per round than the update/reduce kernel, so the two +// counts are tuned separately. Both land near 15 ms on MI350X, which is a few +// dozen intervals at the 512 us host-trap default, so neither kernel can slip +// between samples. +constexpr std::array cg_kernel_rounds = {200, 15000}; + +constexpr const char* cg_launch_symbol = "cg_launch"; + +// execv() passes this as argv[0], which is how the re-execed child knows its +// role without needing a command-line option. +constexpr const char* cg_child_marker = "cg-child"; + +constexpr std::uint32_t cg_seed = 12345; + +void check_hip(hipError_t error, const char* action) +{ + if (error != hipSuccess) + { + throw std::runtime_error(std::string(action) + ": " + hipGetErrorString(error)); + } +} + +std::string executable_directory() +{ + std::string path(4096, '\0'); + const ssize_t length = ::readlink("/proc/self/exe", path.data(), path.size()); + if (length <= 0 || static_cast(length) >= path.size()) + { + throw std::runtime_error("cannot resolve /proc/self/exe: " + + std::string(std::strerror(errno))); + } + path.resize(static_cast(length)); + return path.substr(0, path.find_last_of('/')); +} + +// Owns a dlopen handle and the launch entry point resolved out of it. +class KernelLibrary +{ +public: + explicit KernelLibrary(const std::string& path) + { + handle_ = ::dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); + if (handle_ == nullptr) + { + throw std::runtime_error("dlopen(" + path + "): " + std::string(::dlerror())); + } + launch_ = reinterpret_cast(::dlsym(handle_, cg_launch_symbol)); + if (launch_ == nullptr) + { + throw std::runtime_error("dlsym(" + std::string(cg_launch_symbol) + ") in " + path); + } + } + + ~KernelLibrary() + { + if (handle_ != nullptr) + { + (void)::dlclose(handle_); + } + } + + KernelLibrary(const KernelLibrary&) = delete; + KernelLibrary& operator=(const KernelLibrary&) = delete; + + void launch(const CgArgs& arguments) const { launch_(arguments); } + +private: + void* handle_ = nullptr; + CgLaunch launch_ = nullptr; +}; + +template +class DeviceBuffer +{ +public: + explicit DeviceBuffer(const std::vector& host_data) + { + const std::size_t bytes = host_data.size() * sizeof(Value); + check_hip(hipMalloc(reinterpret_cast(&pointer_), bytes), "hipMalloc"); + check_hip(hipMemcpy(pointer_, host_data.data(), bytes, hipMemcpyHostToDevice), + "hipMemcpy to device"); + } + + ~DeviceBuffer() + { + if (pointer_ != nullptr) + { + (void)hipFree(pointer_); + } + } + + DeviceBuffer(const DeviceBuffer&) = delete; + DeviceBuffer& operator=(const DeviceBuffer&) = delete; + + Value* get() const { return pointer_; } + +private: + Value* pointer_ = nullptr; +}; + +struct HostWorkload +{ + std::vector row_offsets; + std::vector column_indices; + std::vector values; + std::vector p; + std::vector q; + std::vector x; + std::vector r; + std::vector partials; +}; + +// A CSR matrix with power-law row lengths, which gives the SpMV kernel uneven +// per-row work and so a more interesting sample distribution than a dense band. +HostWorkload make_host_workload(std::uint32_t partial_count) +{ + HostWorkload workload; + workload.row_offsets.resize(static_cast(cg_rows) + 1); + workload.column_indices.reserve(static_cast(cg_rows) * 8); + workload.values.reserve(static_cast(cg_rows) * 8); + workload.p.resize(cg_rows); + workload.q.resize(cg_rows); + workload.x.assign(cg_rows, 0.0F); + workload.r.resize(cg_rows); + workload.partials.assign(partial_count, 0.0F); + + std::mt19937 random_generator(cg_seed); + std::uniform_real_distribution uniform_distribution(0.0, 1.0); + for (std::uint32_t row = 0; row < cg_rows; ++row) + { + const double uniform_value = uniform_distribution(random_generator); + const int row_nonzeros = + std::clamp(static_cast(4.0 / std::pow(1.0 - 0.999 * uniform_value, 0.7)), 1, 512); + for (int entry = 0; entry < row_nonzeros; ++entry) + { + workload.column_indices.push_back(random_generator() % cg_rows); + workload.values.push_back(0.5F + static_cast(entry % 7) * 0.1F); + } + workload.row_offsets[row + 1] = static_cast(workload.column_indices.size()); + } + + for (std::uint32_t row = 0; row < cg_rows; ++row) + { + workload.p[row] = 1.0F / static_cast(1 + row % 13); + workload.q[row] = 0.5F + static_cast(row % 7) * 0.1F; + workload.r[row] = 0.25F + static_cast(row % 5) * 0.1F; + } + return workload; +} + +// Role 0 runs SpMV first, role 1 runs update/reduce first. Both roles run both +// kernels; only the order differs. +std::array library_order(int role) +{ + if (role == 0) + { + return {0, 1}; + } + return {1, 0}; +} + +int run_role(int role) +{ + const std::string directory = executable_directory(); + const auto order = library_order(role); + + std::array, 2> libraries; + for (std::size_t position = 0; position < order.size(); ++position) + { + libraries[position] = + std::make_unique(directory + "/" + cg_kernel_libraries[order[position]]); + } + + const std::uint32_t grid_size = (cg_rows + cg_block_size - 1) / cg_block_size; + const HostWorkload host = make_host_workload(grid_size); + DeviceBuffer row_offsets(host.row_offsets); + DeviceBuffer column_indices(host.column_indices); + DeviceBuffer values(host.values); + DeviceBuffer p(host.p); + DeviceBuffer q(host.q); + DeviceBuffer x(host.x); + DeviceBuffer r(host.r); + DeviceBuffer partials(host.partials); + + CgArgs arguments{ + row_offsets.get(), + column_indices.get(), + values.get(), + p.get(), + q.get(), + x.get(), + r.get(), + partials.get(), + cg_rows, + 0, + }; + + std::string launched; + for (std::size_t position = 0; position < order.size(); ++position) + { + const std::size_t library_index = order[position]; + arguments.rounds = cg_kernel_rounds[library_index]; + libraries[position]->launch(arguments); + launched += (position == 0 ? "" : ","); + launched += cg_kernel_libraries[library_index]; + } + check_hip(hipDeviceSynchronize(), "hipDeviceSynchronize"); + + std::cout << "pid=" << ::getpid() << " role=" << role << " order=" << launched << '\n' + << std::flush; + return EXIT_SUCCESS; +} + +int run_parent() +{ + const pid_t child_process = ::fork(); + if (child_process < 0) + { + std::cerr << "conjugate_gradient: fork failed: " << std::strerror(errno) << '\n'; + return EXIT_FAILURE; + } + if (child_process == 0) + { + char* const child_arguments[] = {const_cast(cg_child_marker), nullptr}; + ::execv("/proc/self/exe", child_arguments); + std::cerr << "conjugate_gradient: execv failed: " << std::strerror(errno) << '\n'; + std::cerr.flush(); + ::_exit(127); + } + + // The parent makes no HIP call before fork(), so the child inherits a clean + // runtime state. + const int parent_status = run_role(0); + + int status = 0; + pid_t wait_result = 0; + do + { + wait_result = ::waitpid(child_process, &status, 0); + } + while (wait_result < 0 && errno == EINTR); + + if (wait_result < 0) + { + std::cerr << "conjugate_gradient: waitpid failed: " << std::strerror(errno) << '\n'; + return EXIT_FAILURE; + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) + { + std::cerr << "conjugate_gradient: child " << child_process << " did not exit cleanly\n"; + return EXIT_FAILURE; + } + return parent_status; +} +} // namespace + +int main(int argument_count, char* arguments[]) +{ + try + { + if (argument_count > 0 && std::strcmp(arguments[0], cg_child_marker) == 0) + { + return run_role(1); + } + return run_parent(); + } + catch (const std::exception& error) + { + std::cerr << "conjugate_gradient: " << error.what() << '\n'; + return EXIT_FAILURE; + } +} diff --git a/skills/rocprof-compute-sample/dynamic_shared/dynamic_shared.hip b/skills/rocprof-compute-sample/dynamic_shared/dynamic_shared.hip new file mode 100644 index 0000000..15930ed --- /dev/null +++ b/skills/rocprof-compute-sample/dynamic_shared/dynamic_shared.hip @@ -0,0 +1,171 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#include "example_utils.hpp" + +#include + +#include +#include + +#include +#include +#include + +/// \brief A simple matrix transpose kernel that using dynamic shared memory. +/// - The number of rows in the input and output matrices is equal, and given by the \p width parameter. +/// - Each thread in the grid is responsible for one element of the input and output matrices. +/// - Because the transposition is computed in shared memory, which cannot be accessed between different +/// blocks, the matrix has to be processed by a single block. +__global__ void matrix_transpose_kernel(float* out, const float* in, const unsigned int width) +{ + // Declare that this kernel is using dynamic shared memory to store a number of floats. + // The unsized array type indicates that the total amount of memory that is going + // to be used here is not known ahead of time, and will be computed at runtime and + // passed to the kernel launch function. + extern __shared__ float shared_matrix_memory[]; + + // Compute the row and column index of the element this thread is going to process. + const unsigned int x = blockDim.x * blockIdx.x + threadIdx.x; + const unsigned int y = blockDim.y * blockIdx.y + threadIdx.y; + + // Perform the transpose by reading an element of the input matrix from global memory and + // by storing it in the tranposed index in shared memory. + shared_matrix_memory[y * width + x] = in[x * width + y]; + + // Synchronization is required to make sure that all threads have written + // their part of the input matrix to the shared memory, before the values + // are read by another thread. + __syncthreads(); + + // Copy the transposed matrix from shared memory to the output array, which + // is in global memory. + out[y * width + x] = shared_matrix_memory[y * width + x]; +} + +// CPU implementation of matrix transpose +std::vector matrix_transpose_reference(const std::vector& input, + const unsigned int width) +{ + std::vector output(width * width); + for(unsigned int j = 0; j < width; j++) + { + for(unsigned int i = 0; i < width; i++) + { + output[i * width + j] = input[j * width + i]; + } + } + return output; +} + +// argv: Array of command-line arguments. Run with "--enable-sleep" to enable +// the mode with delay implemented by thread sleep +int main(int argc, char* argv[]) +{ + bool enable_sleep = false; + + // Check command-line arguments + for(int i = 1; i < argc; ++i) + { + std::string arg = argv[i]; + if(arg == "--enable-sleep") + { + enable_sleep = true; + } + } + + constexpr unsigned int width = 4; + constexpr unsigned int threads_per_block_x = width; + constexpr unsigned int threads_per_block_y = width; + constexpr unsigned int size = width * width; + constexpr size_t size_bytes = sizeof(float) * size; + constexpr size_t shared_memory_bytes = size_bytes; + + std::cout << "Run transpose continuously" << std::endl; + + if(enable_sleep) + std::this_thread::sleep_for(std::chrono::seconds(30)); + + unsigned int pass_count = 0; + unsigned int fail_count = 0; + unsigned int cycle_count = 0; + + constexpr float eps = 1.0E-6f; + + while (true) + { + if(enable_sleep) + std::this_thread::sleep_for(std::chrono::seconds(5)); + + // Allocate host vectors + std::vector h_matrix(size); + std::vector h_transposed_matrix(size); + + // Set up input data + for(unsigned int i = 0; i < size; i++) + { + h_matrix[i] = i * 10.0f; + } + + // Allocate device memory + float* d_matrix{}; + float* d_transposed_matrix{}; + HIP_CHECK(hipMalloc(&d_matrix, size_bytes)); + HIP_CHECK(hipMalloc(&d_transposed_matrix, size_bytes)); + + // Copy input to device + HIP_CHECK(hipMemcpy(d_matrix, h_matrix.data(), size_bytes, hipMemcpyHostToDevice)); + + // Launch kernel + matrix_transpose_kernel<<>>(d_transposed_matrix, d_matrix, width); + + HIP_CHECK(hipGetLastError()); + + // Copy result back + HIP_CHECK(hipMemcpy(h_transposed_matrix.data(), + d_transposed_matrix, + size_bytes, + hipMemcpyDeviceToHost)); + + // Free device memory + HIP_CHECK(hipFree(d_matrix)); + HIP_CHECK(hipFree(d_transposed_matrix)); + + // CPU reference transpose + std::vector ref_transposed_matrix = matrix_transpose_reference(h_matrix, width); + + // Validate + unsigned int errors = 0; + for(unsigned int i = 0; i < size; i++) + { + if(std::fabs(h_transposed_matrix[i] - ref_transposed_matrix[i]) > eps) + { + errors++; + } + } + + // Update pass/fail counters + if(errors == 0) + pass_count++; + else + fail_count++; + + cycle_count++; + + // Every 10000 cycles, print summary and reset counters + if(cycle_count == 10000) + { + std::cout << "10000 Validation cycles completed: " + << "Passes = " << pass_count + << ", Failures = " << fail_count << std::endl; + + // Reset counters + cycle_count = 0; + pass_count = 0; + fail_count = 0; + } + } +} diff --git a/skills/rocprof-compute-sample/dynamic_shared/example_utils.hpp b/skills/rocprof-compute-sample/dynamic_shared/example_utils.hpp new file mode 100644 index 0000000..0b76bc8 --- /dev/null +++ b/skills/rocprof-compute-sample/dynamic_shared/example_utils.hpp @@ -0,0 +1,281 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#ifndef COMMON_EXAMPLE_UTILS_HPP +#define COMMON_EXAMPLE_UTILS_HPP + +// Compiling HIP on Windows includes windows.h, and this triggers many silly warnings. +#include +#if defined(_WIN32) && defined(__NVCC__) + #pragma nv_diag_suppress 108 // signed bit field of length 1 + #pragma nv_diag_suppress 174 // expression has no effect + #pragma nv_diag_suppress 1835 // attribute "dllimport" does not apply here +#endif + +// rocPRIM adds a #warning about printf on NAVI. +#ifdef __clang__ + #pragma clang diagnostic ignored "-W#warnings" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +constexpr int error_exit_code = -1; + +/// \brief Checks if the provided error code is \p hipSuccess and if not, +/// prints an error message to the standard error output and terminates the program +/// with an error code. +#define HIP_CHECK(condition) \ + { \ + const hipError_t error = condition; \ + if(error != hipSuccess) \ + { \ + std::cerr << "An error encountered: \"" << hipGetErrorString(error) << "\" at " \ + << __FILE__ << ':' << __LINE__ << std::endl; \ + std::exit(error_exit_code); \ + } \ + } + +/// \brief Formats a range of elements to a pretty string. +/// \tparam BidirectionalIterator - must implement the BidirectionalIterator concept and +/// must be dereferencable in host code. Its value type must be formattable to +/// \p std::ostream. +template +inline std::string format_range(const BidirectionalIterator begin, const BidirectionalIterator end) +{ + std::stringstream sstream; + sstream << "[ "; + for(auto it = begin; it != end; ++it) + { + sstream << *it; + if(it != std::prev(end)) + { + sstream << ", "; + } + } + sstream << " ]"; + return sstream.str(); +} + +/// \brief Formats a range of pairs to a pretty string. The length of the two ranges must match. +/// \tparam BidirectionalIteratorT - must implement the BidirectionalIterator concept and +/// must be dereferencable in host code. Its value type must be formattable to \p std::ostream. +/// \tparam BidirectionalIteratorU - must implement the BidirectionalIterator concept and +/// must be dereferencable in host code. Its value type must be formattable to \p std::ostream. +template +inline std::string format_pairs(const BidirectionalIteratorT begin_a, + const BidirectionalIteratorT end_a, + const BidirectionalIteratorU begin_b, + const BidirectionalIteratorU end_b) +{ + (void)end_b; + assert(std::distance(begin_a, end_a) == std::distance(begin_b, end_b)); + + std::stringstream sstream; + sstream << "[ "; + auto it_a = begin_a; + auto it_b = begin_b; + for(; it_a < end_a; ++it_a, ++it_b) + { + sstream << "(" << *it_a << ", " << *it_b << ")"; + + if(it_a != std::prev(end_a)) + { + sstream << ", "; + } + } + sstream << " ]"; + return sstream.str(); +} + +/// \brief A function to parse a string for an int. If the string is a valid integer then return true +/// else if it has non-numeric character then return false. +inline bool parse_int_string(const std::string& str, int& out) +{ + try + { + size_t end; + int value = std::stoi(str, &end); + if(end == str.size()) + { + out = value; + return true; + } + return false; + } + catch(const std::exception&) + { + return false; + } +} + +/// \brief A class to measures time between intervals +class HostClock +{ +private: + std::chrono::steady_clock::time_point start_time; + std::chrono::steady_clock::duration elapsed_time; + +public: + HostClock() + { + this->reset_timer(); + } + + inline void reset_timer() + { + this->elapsed_time = std::chrono::steady_clock::duration(0); + } + + inline void start_timer() + { + this->start_time = std::chrono::steady_clock::now(); + } + + inline void stop_timer() + { + const auto end_time = std::chrono::steady_clock::now(); + this->elapsed_time += end_time - this->start_time; + } + + /// @brief Returns time elapsed in Seconds + /// @return type double that contains the elapsed time in Seconds + inline double get_elapsed_time() const + { + return std::chrono::duration_cast>(this->elapsed_time) + .count(); + } +}; + +/// \brief Returns ceil(dividend / divisor), where \p dividend is an integer and +/// \p divisor is an unsigned integer. +template::value && std::is_unsigned::value, int> = 0> +__host__ __device__ constexpr auto ceiling_div(const T& dividend, const U& divisor) +{ + return (dividend + divisor - 1) / divisor; +} + +/// \brief Report validation results. +inline int report_validation_result(int errors) +{ + if(errors) + { + std::cout << "Validation failed. Errors: " << errors << std::endl; + return error_exit_code; + } + + std::cout << "Validation passed." << std::endl; + return 0; +} + +/// \brief Generate an identity matrix. +/// The identity matrix is a $m \times n$ matrix with ones in the main diagonal and zeros elsewhere. +template +void generate_identity_matrix(T* A, int m, int n, size_t lda) +{ + for(int i = 0; i < m; ++i) + { + for(int j = 0; j < n; ++j) + { + A[i + j * lda] = T(i == j); + } + } +} + +/// \brief Multiply an $A$ matrix ($m \times k$) with a $B$ matrix ($k \times n$) as: +/// $C := \alpha \cdot A \cdot B + \beta \cdot C$ +template +void multiply_matrices(T alpha, + T beta, + int m, + int n, + int k, + const T* A, + int stride1_a, + int stride2_a, + const T* B, + int stride1_b, + int stride2_b, + T* C, + int stride_c) +{ + for(int i1 = 0; i1 < m; ++i1) + { + for(int i2 = 0; i2 < n; ++i2) + { + T t = T(0.0); + for(int i3 = 0; i3 < k; ++i3) + { + t += A[i1 * stride1_a + i3 * stride2_a] * B[i3 * stride1_b + i2 * stride2_b]; + } + C[i1 + i2 * stride_c] = beta * C[i1 + i2 * stride_c] + alpha * t; + } + } +} + +/// \brief Prints an {1,2,3}-dimensional array. The last dimension (fastest-index) specified in +/// \p n will be printed horizontally. +/// +/// By default a row-major layout of the data is assumed. When printing data in column-major +/// layout, the \p column_major parameter must be set to \p true for a correct interpretation +/// of the dimensions' sizes. +template +void print_nd_data(const std::vector& data, + std::vector np, + const int column_width = 4, + const bool column_major = false) +{ + if(column_major) + { + std::reverse(np.begin(), np.end()); + } + const std::vector n(np); + // Note: we want to print the last dimension horizontally (on the x-axis)! + int size_x = n[n.size() - 1]; + int size_y = n.size() > 1 ? n[n.size() - 2] : 1; + int size_z = n.size() > 2 ? n[n.size() - 3] : 1; + for(int z = 0; z < size_z; ++z) + { + for(int y = 0; y < size_y; ++y) + { + for(int x = 0; x < size_x; ++x) + { + auto index = (z * size_y + y) * size_x + x; + std::cout << std::setfill(' ') << std::setw(column_width) << data[index] << " "; + } + std::cout << "\n"; + } + if(z != size_z - 1) + { + std::cout << "\n"; + } + } + std::cout << std::flush; +} + +/// \brief Returns a string from the double \p value with specified \p precision . +inline std::string + double_precision(const double value, const int precision, const bool fixed = false) +{ + std::stringstream ss; + if(fixed) + { + ss << std::fixed; + } + ss << std::setprecision(precision) << value; + return ss.str(); +} + +#endif // COMMON_EXAMPLE_UTILS_HPP diff --git a/skills/rocprof-compute-sample/fabric.hip b/skills/rocprof-compute-sample/fabric.hip new file mode 100644 index 0000000..7b7fe26 --- /dev/null +++ b/skills/rocprof-compute-sample/fabric.hip @@ -0,0 +1,292 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/* +A data-fabric exerciser example, written by Nicholas Curtis [AMD] + +The test allows the user to control the: + - The granularity of an allocation (Coarse vs Fine-grained), + - The owner of an allocation (local HBM, CPU DRAM or remote HBM), + - The size of an allocation (the default is ~4GiB), and + - The type of operation we are executing (read, write, atomics of various flavors) + +This lets the user explore the impact of these choices on the generated +data-fabric traffic. +*/ + +#include +#include + +#include +#include +#include + +#include "common.h" + +enum class mtype : int { FineGrained = 0, CoarseGrained = 1, Undef = 3 }; +enum class mowner : int { Device = 0, Host = 1, Remote = 2, Undef = 3 }; +enum class mspace : int { Global = 0, Undef = 1 }; +enum class mop : int { + Read = 0, + Write = 1, + AtomicAdd = 2, + AtomicCas = 3, + AtomicOr = 4, + AtomicMax = 5, + Undef = 6 +}; +enum class mdata : int { Unsigned = 0, UnsignedLong = 1, Float = 2, Double = 3, Undef = 4 }; + +template +T parse(const char* value) { + int ivalue = std::atoi(value); + if (ivalue < 0 || ivalue >= int(T::Undef)) { + throw std::runtime_error("bad enum value!"); + } + return T(ivalue); +} + +void parse(int argc, char** argv, mtype& mytype, mowner& myowner, + mspace& myspace, size_t& size, mop& myop, mdata& mydata, + int& remoteId) { + while (1) { + static struct option long_options[] = { + /* These options set a flag. */ + {"type", required_argument, 0, 't'}, + {"owner", required_argument, 0, 'o'}, + {"size", required_argument, 0, 'z'}, + {"op", required_argument, 0, 'p'}, + {"remote", required_argument, 0, 'r'}, + {"data", required_argument, 0, 'd'}, + {0, 0, 0, 0}}; + /* getopt_long stores the option index here. */ + int option_index = 0; + + int c = + getopt_long(argc, argv, "t:o:z:p:r:d:", long_options, &option_index); + + /* Detect the end of the options. */ + if (c == -1) break; + + switch (c) { + case 't': + mytype = parse(optarg); + break; + + case 'o': + myowner = parse(optarg); + break; + + case 'z': + size = std::atoll(optarg); + break; + + case 'p': + myop = parse(optarg); + break; + + case 'r': + remoteId = std::atoi(optarg); + break; + + case 'd': + mydata = parse(optarg); + break; + + case '?': + /* getopt_long already printed an error message. */ + break; + + default: + abort(); + } + } + std::cout << "Using: " << std::endl; + std::cout << "\tmtype:" + << ((mytype == mtype::FineGrained) ? "FineGrained" + : "CoarseGrained") + << std::endl; + std::cout << "\tmowner:" + << ((myowner == mowner::Device) + ? "Device" + : ((myowner == mowner::Host) ? "Host" : "Remote")) + << std::endl; + std::cout << "\tmspace:Global" << std::endl; + std::cout << "\tmop:" << ((myop == mop::Read) ? "Read" : (myop == mop::Write ? "Write" : (myop == mop::AtomicAdd ? "Add" : (myop == mop::AtomicCas ? "CAS" : (myop == mop::AtomicOr ? "Or" : "Max"))))) << std::endl; + std::cout << "\tmdata:" << (mydata == mdata::Unsigned ? "Unsigned" : (mydata == mdata::UnsignedLong ? "Unsigned Long" : (mydata == mdata::Float ? "Float" : "Double"))) << std::endl; + std::cout << "\tremoteId:" << remoteId << std::endl; +} + +// dummy intialization kernel +__global__ void init() {} + +template +void alloc(mtype memory, mowner owner, T** ptr, size_t Nbytes, int devId, + int remoteId) { + bool is_device = (owner == mowner::Device) || (owner == mowner::Remote); + if (owner == mowner::Remote) { + // enable remote access + hipCheck(hipDeviceEnablePeerAccess(remoteId, 0)); + // set id for alloc + hipCheck(hipSetDevice(remoteId)); + } + init<<<1, 1>>>(); + + if (memory == mtype::FineGrained && is_device) { + hipCheck( + hipExtMallocWithFlags((void**)ptr, Nbytes, hipDeviceMallocFinegrained)); + } else if (memory == mtype::CoarseGrained && is_device) { + hipCheck(hipMalloc(ptr, Nbytes)); + } else if (memory == mtype::FineGrained && owner == mowner::Host) { + hipCheck(hipHostMalloc(ptr, Nbytes, hipHostMallocCoherent)); + } else if (memory == mtype::CoarseGrained && owner == mowner::Host) { + hipCheck(hipHostMalloc(ptr, Nbytes, hipHostMallocNonCoherent)); + } else { + assert(false && "unknown combo"); + } + + // set to random + std::vector host(Nbytes / sizeof(T), T(0)); + hipCheck(hipMemcpy(*ptr, &host[0], Nbytes, + (is_device ? hipMemcpyHostToDevice : hipMemcpyHostToHost))); + + if (owner == mowner::Remote) { + // reset id for execution + hipCheck(hipSetDevice(devId)); + } +} + +template +void release(mtype memory, mowner owner, T* ptr) { + bool is_device = (owner == mowner::Device) || (owner == mowner::Remote); + if (memory == mtype::FineGrained && is_device) { + hipCheck(hipFree(ptr)); + } else if (memory == mtype::CoarseGrained && is_device) { + hipCheck(hipFree(ptr)); + } else if (memory == mtype::FineGrained && owner == mowner::Host) { + hipCheck(hipHostFree(ptr)); + } else if (memory == mtype::CoarseGrained && owner == mowner::Host) { + hipCheck(hipHostFree(ptr)); + } else { + assert(false && "unknown combo"); + } +} + +// the main streaming kernel +template +__global__ void kernel(T* x, size_t N, T zero, T foo) { + int sum = 0; + const size_t offset_start = threadIdx.x + blockIdx.x * blockDim.x; + for (int i = 0; i < repeats; ++i) { + for (size_t offset = offset_start; offset < N; + offset += blockDim.x * gridDim.x) { + T uniq = (foo + offset) + i; + if constexpr (op == mop::Read) { + sum += x[offset]; + } else if constexpr (op == mop::Write) { + x[offset] = (T)offset; + } else if constexpr (op == mop::AtomicAdd) { + atomicAdd(&x[offset], uniq); + } else if constexpr (op == mop::AtomicCas) { + atomicCAS(&x[offset], uniq, uniq); + } else if constexpr (op == mop::AtomicOr) { + atomicOr(&x[offset], uniq); + } else if constexpr (op == mop::AtomicMax) { + atomicMax(&x[offset], uniq); + } + } + } + if constexpr (op == mop::Read) { + if (sum != 0) { + x[offset_start] = sum; + } + } +} + +template +void run_kernel(T* x, size_t size) { + if constexpr (op == mop::AtomicOr && std::is_floating_point_v) { + throw std::runtime_error("bad"); + } else { + kernel<<<4096, 1024>>>(x, size, 0, T(23456789)); + // then run once for data collection + kernel<<<4096, 1024>>>(x, size, 0, T(23456789)); + } +} + +template +void run_atomic(mowner myowner, T* x, size_t size) { + if (myowner == mowner::Host) { + // speed it up + run_kernel(x, size / 10); + } else { + run_kernel(x, size); + } +} + +template +void run(mtype mytype, mspace myspace, mowner myowner, mop myop, int remoteId, + size_t size) { + int devId = 0; + if (myowner == mowner::Remote && remoteId == -1) { + // need to find a remote GPU + int ndevices; + hipCheck(hipGetDeviceCount(&ndevices)); + if (ndevices <= 1) { + throw std::runtime_error( + "Need >=2 devices available for mowner = Remote"); + } + for (int i = 0; i < ndevices; ++i) { + if (i != devId) { + remoteId = i; + break; + } + } + } + + T* x; + alloc(mytype, myowner, &x, size * sizeof(T), devId, remoteId); + + // run the kernel once for warmup + assert(4096 * 1024 < size); + if (myop == mop::Read) { + run_kernel(x, size); + } else if (myop == mop::Write) { + run_kernel(x, size); + } else if (myop == mop::AtomicAdd) { + run_atomic(myowner, x, size); + } else if (myop == mop::AtomicCas) { + run_atomic(myowner, x, size); + } else if (myop == mop::AtomicOr) { + run_atomic(myowner, x, size); + } else if (myop == mop::AtomicMax) { + run_atomic(myowner, x, size); + } else { + throw std::runtime_error("bad"); + } + hipCheck(hipDeviceSynchronize()); + release(mytype, myowner, x); +} + +int main(int argc, char** argv) { + mtype mytype = (mtype)0; + mspace myspace = (mspace)0; + mowner myowner = (mowner)0; + mop myop = (mop)0; + mdata mydata = (mdata)0; + int remoteId = -1; + size_t size = 1024ull * 1024ull * + 1024ull; // 4 GiB, purposefully much larger than caches. + parse(argc, argv, mytype, myowner, myspace, size, myop, mydata, remoteId); + if (mydata == mdata::Unsigned) + run(mytype, myspace, myowner, myop, remoteId, size); + else if (mydata == mdata::UnsignedLong) + run(mytype, myspace, myowner, myop, remoteId, size); + else if (mydata == mdata::Float) + run(mytype, myspace, myowner, myop, remoteId, size); + else if (mydata == mdata::Double) + run(mytype, myspace, myowner, myop, remoteId, size); + else { + throw std::runtime_error("bad"); + } +} diff --git a/skills/rocprof-compute-sample/instmix.hip b/skills/rocprof-compute-sample/instmix.hip new file mode 100644 index 0000000..d6af7c7 --- /dev/null +++ b/skills/rocprof-compute-sample/instmix.hip @@ -0,0 +1,89 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/* +A instruction mix exerciser example, written by Gina Sitaraman and Nicholas Curtis [AMD]. +Although inline assembly is inherently unportable, this is expected to work on all CDNA accelerators. +*/ + +#include "common.h" + +__global__ void kernelasm() { + // int32 + int i, j; + asm volatile("v_add_u32_e32 %0, %1, %0\n" : "=v"(j) : "v"(i)); + + // int 64 + long int l1, l2; + asm volatile("v_cmp_eq_i64 %0, %1\n" : "=v"(l2) : "v"(l1), "v"(i)); + + // fp32: add, mul, transcendental and fma + float f1, f2; + asm volatile( + "v_add_f32_e32 %0, %1, %0\n" + "v_mul_f32_e32 %0, %1, %0\n" + "v_sqrt_f32 %0, %1\n" + "v_fma_f32 %0, %1, %0, %1\n" + : "=v"(f1) + : "v"(f2)); + + // fp64: add, mul, transcendental and fma + double d1, d2, d3, d4; + asm volatile( + "v_add_f64 %0, %1, %0\n" + "v_mul_f64 %0, %1, %0\n" + "v_fma_f64 %0, %1, %0, %1\n" + "v_sqrt_f64 %0, %1\n" + "v_min_f64 %0, %1, %0\n" + : "+v"(d1) + : "v"(d2)); + + // fp16: add, mul, transcendental and fma + _Float16 h1, h2; + asm volatile( + "v_add_f16_e32 %0, %1, %0\n" + "v_mul_f16_e32 %0, %1, %0\n" + "v_sqrt_f16 %0, %1\n" + "v_cvt_f16_f32 %0 %2\n" + "v_fma_f16 %0, %1, %0, %0\n" + : "=v"(h2) + : "v"(h1), "v"(f1)); + + // MFMA ops + double2 dd; + unsigned short us; + long2 ll; +#if defined(__gfx90a__) + asm volatile("v_mfma_f64_4x4x4f64 %0 %1 %2 %3\n" + : "=v"(d4) + : "v"(d1), "v"(d2), "v"(d3)); + asm volatile("v_mfma_f32_16x16x4f32 %0 %1 %2 1\n" + : "=v"(dd) + : "v"(f1), "v"(f2)); + asm volatile("v_mfma_f32_16x16x16f16 %0 %1 %2 1\n" + : "=v"(dd) + : "v"(d1), "v"(d2)); + asm volatile("v_mfma_f32_16x16x8bf16 %0 %1 %2 1\n" + : "=v"(dd) + : "v"(f1), "v"(f2)); + asm volatile("v_mfma_i32_16x16x16i8 %0 %1 %2 1\n" + : "=v"(ll) + : "v"(i), "v"(j)); +#endif + + // Scalar op + asm volatile("s_add_i32 %0 %1 %0\n" : "=s"(j) : "s"(i)); + + // LDS + asm volatile("ds_read_b32 %0 %0\n" : "=v"(i) : "v"(j)); + + // Branch + asm volatile( + "s_branch .LDUMMY\n" + ".LDUMMY:\n" + "s_endpgm\n"); +} +int main() { + kernelasm<<<1, 64>>>(); + hipCheck(hipDeviceSynchronize()); +} diff --git a/skills/rocprof-compute-sample/ipc.hip b/skills/rocprof-compute-sample/ipc.hip new file mode 100644 index 0000000..f82a0d4 --- /dev/null +++ b/skills/rocprof-compute-sample/ipc.hip @@ -0,0 +1,115 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/* +An example to explore IPC and divergence, written by Nicholas Curtis [AMD]. +This example may not work on all CDNA accelerators, but has been verified on MI2XX. +*/ + +#include "common.h" + +// Define MFMA support for CDNA2+ architectures (MI200, MI300, MI350 series) +#if defined(__gfx90a__) || defined(__gfx940__) || defined(__gfx941__) || defined(__gfx942__) || defined(__gfx950__) +#define HAS_MFMA_SUPPORT 1 +#endif + +template +__device__ void vrcp_op() { + int dummy; + if constexpr (N >= 1) { + asm volatile("v_rcp_f64 v[0:1], v[0:1]\n" : : "{v31}"(dummy)); + vrcp_op(); + } +} + +template +__global__ void vrcp() { + vrcp_op(); +} + +template +__device__ void vmov_op() { + int dummy; + if constexpr (N >= 1) { + asm volatile("v_mov_b32 v0, v1\n" : : "{v31}"(dummy)); + vmov_op(); + } +} + +template +__global__ void vmov() { + vmov_op(); +} + +#ifdef HAS_MFMA_SUPPORT +template +__device__ void mfma_op() { + int dummy; + if constexpr (N >= 1) { + asm volatile("v_mfma_f32_32x32x8bf16_1k v[0:15], v[16:17], v[18:19], v[0:15]\n" : : "{v31}"(dummy)); + mfma_op(); + } +} + +template +__global__ void mfma() { + mfma_op(); +} +#endif + +template +__device__ void snop_op() { + int dummy; + if constexpr (N >= 1) { + asm volatile("s_nop 0x0\n" : : "{v31}"(dummy)); + snop_op(); + } +} + + +template +__global__ void snop() { + snop_op(); +} + +template +__device__ void smov_op() { + int dummy; + if constexpr (N >= 1) { + asm volatile("s_mov_b32 s0, s1\n" : : "{s31}"(dummy)); + smov_op(); + } +} + +template +__global__ void smov() { + smov_op(); +} + +template +__global__ void vmov_with_divergence() { + if (threadIdx.x % 64 == 0) + vmov_op(); +} + +int main() { + // warmups, spam to all CUs + vrcp<<<1024 * 1024, 1024>>>(); + vmov<<<1024 * 1024, 1024>>>(); +#ifdef HAS_MFMA_SUPPORT + mfma<<<1024 * 1024, 1024>>>(); +#endif + snop<<<1024 * 1024, 1024>>>(); + smov<<<1024 * 1024, 1024>>>(); + vmov_with_divergence<<<1024 * 1024, 1024>>>(); + hipCheck(hipDeviceSynchronize()); + vrcp<<<1024 * 1024, 1024>>>(); + vmov<<<1024 * 1024, 1024>>>(); +#ifdef HAS_MFMA_SUPPORT + mfma<<<1024 * 1024, 1024>>>(); +#endif + snop<<<1024 * 1024, 1024>>>(); + smov<<<1024 * 1024, 1024>>>(); + vmov_with_divergence<<<1024 * 1024, 1024>>>(); + hipCheck(hipDeviceSynchronize()); +} diff --git a/skills/rocprof-compute-sample/laplace_eqn.hip b/skills/rocprof-compute-sample/laplace_eqn.hip new file mode 100644 index 0000000..11e5a0e --- /dev/null +++ b/skills/rocprof-compute-sample/laplace_eqn.hip @@ -0,0 +1,410 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// This sample code solves the 2D Laplace equation using the Jacobi iterative +// method on a rectangle with periodic boundary conditions. + +// The domain is initialized with a smooth Dirac delta function at the center. + +// The grid is partitioned among multiple MPI processes in the vertical direction. +// Each process exchanges halo regions with its neighboring processes after each +// iteration. Communication uses explicit GPU->Host->MPI->Host->GPU transfers +// (not GPU-aware MPI) for halo exchange. + +// The code allows users to specify the number of iterations and the +// order of kernel launches via command-line arguments. + + +// Reference: Finite Difference Methods for Ordinary and Partial Differential +// Equations by Randall J. LeVeque, Page 69-71 + + +#include +#include +#include +#include +#ifdef USE_MPI +#include +#endif // USE_MPI + +// Helper macro for HIP error checking +#define HIP_CHECK(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + std::cerr << "HIP error: " << hipGetErrorString(err) << " at " \ + << __FILE__ << ":" << __LINE__ << std::endl; \ + std::exit(EXIT_FAILURE); \ + } \ + } while (0) + +void initialize(float *U, int N) { +#ifdef USE_MPI + // Get the number of processes + int num_procs; + MPI_Comm_size(MPI_COMM_WORLD, &num_procs); + + // Get the rank of the process + int rank; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + int N_total = N * num_procs; +#endif // USE_MPI + + // The grid is partitioned among MPI processes in the vertical direction + for (int i = 0; i < N; ++i) { +#ifdef USE_MPI + // The halo regions are not initialized here + // They are obtained from the neighboring rank + for (int j = 1; j <= N; ++j) { + int j_global = rank * N + j; + float y = 2.0f * (float)j_global / (float)N_total - 1.0f; +#else + for (int j = 0; j < N; ++j) { + float y = 2.0f * (float)j / (float)N - 1.0f; +#endif // USE_MPI + float x = 2.0f * (float)i / (float)N - 1.0f; + U[i * N + j] = 1.0f / (1.0f + x * x + y * y); // Initial guess + } + } +} + +__global__ void jacobi_iteration(float *__restrict__ U_new, + const float *__restrict__ U_old, int N) { + int i = blockIdx.x * blockDim.x + threadIdx.x; + int j = blockIdx.y * blockDim.y + threadIdx.y; + + int idx = j + i * N; // Flattened index for 2D grid + + extern __shared__ float U[]; + int s_i = threadIdx.x + 1; + int s_j = threadIdx.y + 1; + + int tile_height = blockDim.y + 2; + int s_idx = s_j + s_i * tile_height; + + // Load data into shared memory with halo regions + if (i < N && j < N) { + U[s_idx] = U_old[idx]; + + // Load halo regions + if (threadIdx.x == 0) { + U[s_j] = (i > 0) ? U_old[(i - 1) * N + j] : U_old[(N - 1) * N + j]; + } + + if (threadIdx.x == blockDim.x - 1) { + U[s_j + (blockDim.x + 1) * tile_height] = + (i < N - 1) ? U_old[(i + 1) * N + j] : U_old[j]; + } + + if (threadIdx.y == 0) { + U[s_i * tile_height] = + (j > 0) ? U_old[i * N + (j - 1)] : U_old[i * N + (N - 1)]; + } + + if (threadIdx.y == blockDim.y - 1) { + U[s_i * tile_height + blockDim.y + 1] = + (j < N - 1) ? U_old[i * N + (j + 1)] : U_old[i * N]; + } + } + + __syncthreads(); + + if (i < N && j < N) { + int east = s_idx - tile_height; + int west = s_idx + tile_height; + int north = s_idx + 1; + int south = s_idx - 1; + U_new[idx] = 0.25f * (U[east] + U[west] + U[north] + U[south]); + } +} + +int main(int argc, char *argv[]) { +#ifdef USE_MPI + // Initialize MPI + MPI_Init(&argc, &argv); + + // Get the number of processes + int num_procs; + MPI_Comm_size(MPI_COMM_WORLD, &num_procs); + + // Get the rank of the process + int rank; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + std::cout << "Rank " << rank << " of " << num_procs << std::endl; +#else + int num_procs = 1; + int rank = 0; + std::cout << "Running without MPI (single process)" << std::endl; +#endif // USE_MPI + + int iter = 1000; + std::vector launch_order; + +#ifdef USE_MPI + if (rank == 0) { +#endif // USE_MPI + // Parse command-line arguments + for (int i = 0; i < argc; i++) { + if (std::string(argv[i]) == "--mpi-info") { +#ifdef USE_MPI + std::cout << "MPI support: enabled" << std::endl; + MPI_Finalize(); +#else + std::cout << "MPI support: disabled" << std::endl; +#endif // USE_MPI + return 0; + } + if (std::string(argv[i]) == "-i" && i + 1 < argc) { + iter = std::atoi(argv[i + 1]); + } else if (std::string(argv[i]) == "-o" && i + 1 < argc) { + int n_order = std::atoi(argv[i + 1]); + if ((i + n_order + 1) >= argc) { + std::cerr << "Insufficient arguments for -o option" << std::endl; + return EXIT_FAILURE; + } else { + for (int j = 0; j < n_order; j++) { + int kernel_id = std::atoi(argv[i + 2 + j]); + if (kernel_id < 0 || kernel_id > 2) { + std::cerr << "Invalid kernel ID: " << kernel_id << std::endl; + return EXIT_FAILURE; + } + launch_order.push_back(kernel_id); + } + i += n_order; + } + } + } + + if (launch_order.empty()) { + launch_order = {0, 1, 2}; + } +#ifdef USE_MPI + } + + // Broadcast number of iterations to all processes + MPI_Bcast(&iter, 1, MPI_INT, 0, MPI_COMM_WORLD); + + // Broadcast launch_order size and data to all processes + int order_size = launch_order.size(); + MPI_Bcast(&order_size, 1, MPI_INT, 0, MPI_COMM_WORLD); + + if (rank != 0) + launch_order.resize(order_size); + + MPI_Bcast(launch_order.data(), order_size, MPI_INT, 0, MPI_COMM_WORLD); +#endif // USE_MPI + + // Problem size + const int n_small = 1024; + const int n_mid = 2048; + const int n_large = 4096; + + // Define block and grid sizes + dim3 blockSize_small(16, 16); + dim3 gridSize_small((n_small + blockSize_small.x - 1) / blockSize_small.x, + (n_small + blockSize_small.y - 1) / blockSize_small.y); + dim3 blockSize_mid(24, 24); + dim3 gridSize_mid((n_mid + blockSize_mid.x - 1) / blockSize_mid.x, + (n_mid + blockSize_mid.y - 1) / blockSize_mid.y); + dim3 blockSize_large(32, 32); + dim3 gridSize_large((n_large + blockSize_large.x - 1) / blockSize_large.x, + (n_large + blockSize_large.y - 1) / blockSize_large.y); + + const int nx_small = n_small; + const int nx_mid = n_mid; + const int nx_large = n_large; + +#ifdef USE_MPI + // Set device based on rank + int num_devices; + HIP_CHECK(hipGetDeviceCount(&num_devices)); + HIP_CHECK(hipSetDevice(rank % num_devices)); + + const int ny_small = n_small + 2; + const int ny_mid = n_mid + 2; + const int ny_large = n_large + 2; +#else + const int ny_small = n_small; + const int ny_mid = n_mid; + const int ny_large = n_large; +#endif // USE_MPI + + // Host memory pointers + float *h_U_small = new float[nx_small * ny_small]; + float *h_U_mid = new float[nx_mid * ny_mid]; + float *h_U_large = new float[nx_large * ny_large]; + + // Initialize host arrays + initialize(h_U_small, n_small); + initialize(h_U_mid, n_mid); + initialize(h_U_large, n_large); + + // Device memory pointers + float *d_U_small_new, *d_U_small_old; + float *d_U_mid_new, *d_U_mid_old; + float *d_U_large_new, *d_U_large_old; + + // Allocate device memory + HIP_CHECK(hipMalloc(&d_U_small_new, nx_small * ny_small * sizeof(float))); + HIP_CHECK(hipMalloc(&d_U_small_old, nx_small * ny_small * sizeof(float))); + + HIP_CHECK(hipMalloc(&d_U_mid_new, nx_mid * ny_mid * sizeof(float))); + HIP_CHECK(hipMalloc(&d_U_mid_old, nx_mid * ny_mid * sizeof(float))); + + HIP_CHECK(hipMalloc(&d_U_large_new, nx_large * ny_large * sizeof(float))); + HIP_CHECK(hipMalloc(&d_U_large_old, nx_large * ny_large * sizeof(float))); + + // Copy host arrays to device + HIP_CHECK(hipMemcpy(d_U_small_old, h_U_small, + nx_small * ny_small * sizeof(float), + hipMemcpyHostToDevice)); + HIP_CHECK(hipMemcpy(d_U_mid_old, h_U_mid, nx_mid * ny_mid * sizeof(float), + hipMemcpyHostToDevice)); + HIP_CHECK(hipMemcpy(d_U_large_old, h_U_large, + nx_large * ny_large * sizeof(float), + hipMemcpyHostToDevice)); + + std::vector grid_sizes = {gridSize_small, gridSize_mid, gridSize_large}; + std::vector block_sizes = {blockSize_small, blockSize_mid, + blockSize_large}; + std::vector d_U_news = {d_U_small_new, d_U_mid_new, d_U_large_new}; + std::vector d_U_olds = {d_U_small_old, d_U_mid_old, d_U_large_old}; + std::vector n_sizes = {n_small, n_mid, n_large}; + +#ifdef USE_MPI + int left = rank == 0 ? num_procs - 1 : rank - 1; + int right = (rank + 1) % num_procs; + + // Allocate host buffers for halo exchange (GPU -> Host -> MPI -> Host -> GPU) + // Each problem size needs 4 buffers: send_left, send_right, recv_left, recv_right + std::vector h_send_left(n_sizes.size()); + std::vector h_send_right(n_sizes.size()); + std::vector h_recv_left(n_sizes.size()); + std::vector h_recv_right(n_sizes.size()); + + for (size_t i = 0; i < n_sizes.size(); ++i) { + h_send_left[i] = new float[n_sizes[i]]; + h_send_right[i] = new float[n_sizes[i]]; + h_recv_left[i] = new float[n_sizes[i]]; + h_recv_right[i] = new float[n_sizes[i]]; + } + + int num_requests = 4 * n_sizes.size(); + std::vector requests(num_requests); + + // Initial halo exchange: GPU -> Host -> MPI -> Host -> GPU + for (size_t i = 0; i < n_sizes.size(); ++i) { + // Copy halo data from GPU to host send buffers + HIP_CHECK(hipMemcpy(h_send_left[i], &d_U_olds[i][n_sizes[i]], + n_sizes[i] * sizeof(float), hipMemcpyDeviceToHost)); + HIP_CHECK(hipMemcpy(h_send_right[i], &d_U_olds[i][n_sizes[i] * (n_sizes[i] + 1)], + n_sizes[i] * sizeof(float), hipMemcpyDeviceToHost)); + } + + for (size_t i = 0; i < n_sizes.size(); ++i) { + // Send data to left node, receive from right node + MPI_Isend(h_send_left[i], n_sizes[i], MPI_FLOAT, left, 2 * i, + MPI_COMM_WORLD, &requests[4 * i]); + MPI_Irecv(h_recv_right[i], n_sizes[i], MPI_FLOAT, right, 2 * i, + MPI_COMM_WORLD, &requests[4 * i + 1]); + + // Send data to right node, receive from left node + MPI_Isend(h_send_right[i], n_sizes[i], MPI_FLOAT, right, 2 * i + 1, + MPI_COMM_WORLD, &requests[4 * i + 2]); + MPI_Irecv(h_recv_left[i], n_sizes[i], MPI_FLOAT, left, 2 * i + 1, + MPI_COMM_WORLD, &requests[4 * i + 3]); + } + + MPI_Waitall(num_requests, requests.data(), MPI_STATUSES_IGNORE); + + // Copy received halo data from host to GPU + for (size_t i = 0; i < n_sizes.size(); ++i) { + HIP_CHECK(hipMemcpy(&d_U_olds[i][n_sizes[i] * n_sizes[i]], h_recv_right[i], + n_sizes[i] * sizeof(float), hipMemcpyHostToDevice)); + HIP_CHECK(hipMemcpy(&d_U_olds[i][0], h_recv_left[i], + n_sizes[i] * sizeof(float), hipMemcpyHostToDevice)); + } +#endif // USE_MPI + + // Perform Jacobi iterations + for (int it = 0; it < iter; ++it) { + int i = launch_order[it % launch_order.size()]; + jacobi_iteration<<>>(d_U_news[i], d_U_olds[i], + n_sizes[i]); + + HIP_CHECK(hipDeviceSynchronize()); + // Swap pointers + std::swap(d_U_news[i], d_U_olds[i]); + +#ifdef USE_MPI + std::vector requests(4); + + // Copy halo data from GPU to host send buffers + HIP_CHECK(hipMemcpy(h_send_left[i], &d_U_olds[i][n_sizes[i]], + n_sizes[i] * sizeof(float), hipMemcpyDeviceToHost)); + HIP_CHECK(hipMemcpy(h_send_right[i], &d_U_olds[i][n_sizes[i] * (n_sizes[i] + 1)], + n_sizes[i] * sizeof(float), hipMemcpyDeviceToHost)); + + // Send data to left node, receive from right node + MPI_Isend(h_send_left[i], n_sizes[i], MPI_FLOAT, left, 0, + MPI_COMM_WORLD, &requests[0]); + MPI_Irecv(h_recv_right[i], n_sizes[i], MPI_FLOAT, right, 0, + MPI_COMM_WORLD, &requests[1]); + + // Send data to right node, receive from left node + MPI_Isend(h_send_right[i], n_sizes[i], MPI_FLOAT, right, 1, + MPI_COMM_WORLD, &requests[2]); + MPI_Irecv(h_recv_left[i], n_sizes[i], MPI_FLOAT, left, 1, + MPI_COMM_WORLD, &requests[3]); + + MPI_Waitall(4, requests.data(), MPI_STATUSES_IGNORE); + + // Copy received halo data from host to GPU + HIP_CHECK(hipMemcpy(&d_U_olds[i][n_sizes[i] * n_sizes[i]], h_recv_right[i], + n_sizes[i] * sizeof(float), hipMemcpyHostToDevice)); + HIP_CHECK(hipMemcpy(&d_U_olds[i][0], h_recv_left[i], + n_sizes[i] * sizeof(float), hipMemcpyHostToDevice)); +#endif // USE_MPI + } + + // Pedagogical + // Copy device data to host + HIP_CHECK(hipMemcpy(h_U_small, d_U_small_old, + nx_small * ny_small * sizeof(float), + hipMemcpyDeviceToHost)); + HIP_CHECK(hipMemcpy(h_U_mid, d_U_mid_old, nx_mid * ny_mid * sizeof(float), + hipMemcpyDeviceToHost)); + HIP_CHECK(hipMemcpy(h_U_large, d_U_large_old, + nx_large * ny_large * sizeof(float), + hipMemcpyDeviceToHost)); + + // Free device memory + HIP_CHECK(hipFree(d_U_small_new)); + HIP_CHECK(hipFree(d_U_small_old)); + HIP_CHECK(hipFree(d_U_mid_new)); + HIP_CHECK(hipFree(d_U_mid_old)); + HIP_CHECK(hipFree(d_U_large_new)); + HIP_CHECK(hipFree(d_U_large_old)); + + delete[] h_U_small; + delete[] h_U_mid; + delete[] h_U_large; + +#ifdef USE_MPI + // Free host buffers for halo exchange + for (size_t i = 0; i < n_sizes.size(); ++i) { + delete[] h_send_left[i]; + delete[] h_send_right[i]; + delete[] h_recv_left[i]; + delete[] h_recv_right[i]; + } + + // Finalize MPI + MPI_Finalize(); +#endif // USE_MPI + + return 0; +} diff --git a/skills/rocprof-compute-sample/lds.hip b/skills/rocprof-compute-sample/lds.hip new file mode 100644 index 0000000..887d4b5 --- /dev/null +++ b/skills/rocprof-compute-sample/lds.hip @@ -0,0 +1,54 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/* +An example to explore LDS bandwidth and bank conflicts, written by Nicholas Curtis [AMD]. +*/ + +#include "common.h" + +constexpr unsigned max_threads = 256; +constexpr unsigned nbanks = 32; + +__global__ void load(int* out, int flag) { + __shared__ int array[max_threads]; + int index = threadIdx.x; + // fake a store to the LDS array to avoid unwanted behavior + if (flag) + array[max_threads - index] = index; + __syncthreads(); + int x = array[index]; + if (x == int(-1234567)) + out[threadIdx.x] = x; +} + +__global__ void conflicts(int* out, int flag) { + constexpr unsigned nelements = nbanks * max_threads; + __shared__ int array[nelements]; + // each thread reads from the same bank + int index = threadIdx.x * nbanks; + // fake a store to the LDS array to avoid unwanted behavior + if (flag) + array[max_threads - index] = index; + __syncthreads(); + int x = array[index]; + if (x == int(-1234567)) + out[threadIdx.x] = x; +} + +void bandwidth_demo(int N) { + for (int i = 1; i <= N; ++i) + load<<<1,i>>>(nullptr, 0); + hipCheck(hipDeviceSynchronize()); +} + +void conflicts_demo(int N) { + for (int i = 1; i <= N; ++i) + conflicts<<<1,i>>>(nullptr, 0); + hipCheck(hipDeviceSynchronize()); +} + +int main() { + bandwidth_demo(max_threads); + conflicts_demo(max_threads); +} diff --git a/skills/rocprof-compute-sample/mat_mul_max.hip b/skills/rocprof-compute-sample/mat_mul_max.hip new file mode 100644 index 0000000..2a9791c --- /dev/null +++ b/skills/rocprof-compute-sample/mat_mul_max.hip @@ -0,0 +1,92 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include + +#define TILE_SIZE 32 // Maximum block size: 32 x 32 = 1024 threads/block +#define N 4096 // Matrix size: 4096 x 4096 (~67M elements) + +// Helper macro for HIP error checking +#define HIP_CHECK(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + std::cerr << "HIP error: " << hipGetErrorString(err) \ + << " at " << __FILE__ << ":" << __LINE__ \ + << std::endl; \ + std::exit(EXIT_FAILURE); \ + } \ + } while(0) + +__global__ void matMulKernel(const float* __restrict__ A, const float* __restrict__ B, float* __restrict__ C, int width) { + __shared__ float tileA[TILE_SIZE][TILE_SIZE]; + __shared__ float tileB[TILE_SIZE][TILE_SIZE]; + + int row = blockIdx.y * TILE_SIZE + threadIdx.y; + int col = blockIdx.x * TILE_SIZE + threadIdx.x; + + float sum = 0.0f; + + // Loop over tiles of input matrices + for (int t = 0; t < width / TILE_SIZE; ++t) { + tileA[threadIdx.y][threadIdx.x] = A[row * width + t * TILE_SIZE + threadIdx.x]; + tileB[threadIdx.y][threadIdx.x] = B[(t * TILE_SIZE + threadIdx.y) * width + col]; + __syncthreads(); + + #pragma unroll + for (int i = 0; i < TILE_SIZE; ++i) + sum += tileA[threadIdx.y][i] * tileB[i][threadIdx.x]; + + __syncthreads(); + } + + C[row * width + col] = sum; +} + +int main() { + size_t size = N * N * sizeof(float); + + float *h_A = new float[N * N]; + float *h_B = new float[N * N]; + float *d_A, *d_B, *d_C; + + // Initialize matrices with dummy values + for (int i = 0; i < N * N; ++i) { + h_A[i] = static_cast(i % 100) * 0.01f; + h_B[i] = static_cast((i + 1) % 100) * 0.01f; + } + + HIP_CHECK(hipMalloc(&d_A, size)); + HIP_CHECK(hipMalloc(&d_B, size)); + HIP_CHECK(hipMalloc(&d_C, size)); + + HIP_CHECK(hipMemcpy(d_A, h_A, size, hipMemcpyHostToDevice)); + HIP_CHECK(hipMemcpy(d_B, h_B, size, hipMemcpyHostToDevice)); + + dim3 blockDim(TILE_SIZE, TILE_SIZE); // 32 x 32 = 1024 threads + dim3 gridDim(N / TILE_SIZE, N / TILE_SIZE); // 128 x 128 = 16,384 thread blocks + + std::cout << "Launching kernel with grid: (" << gridDim.x << ", " << gridDim.y + << ") and block: (" << blockDim.x << ", " << blockDim.y << ")\n"; + + auto start = std::chrono::high_resolution_clock::now(); + + matMulKernel<<>>(d_A, d_B, d_C, N); + HIP_CHECK(hipGetLastError()); + HIP_CHECK(hipDeviceSynchronize()); + + auto end = std::chrono::high_resolution_clock::now(); + std::chrono::duration elapsed = end - start; + std::cout << "Execution time: " << elapsed.count() << " seconds\n"; + + HIP_CHECK(hipFree(d_A)); + HIP_CHECK(hipFree(d_B)); + HIP_CHECK(hipFree(d_C)); + delete[] h_A; + delete[] h_B; + + return 0; +} diff --git a/skills/rocprof-compute-sample/mega_kernel/Makefile b/skills/rocprof-compute-sample/mega_kernel/Makefile new file mode 100644 index 0000000..a97d3f2 --- /dev/null +++ b/skills/rocprof-compute-sample/mega_kernel/Makefile @@ -0,0 +1,159 @@ +# Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. +# SPDX-License-Identifier: MIT +# +# AMD GPU Mega Kernel Unit Test - Makefile +# +# Supported offload-arch values (single source for per-arch binaries): +ARCHES := gfx950 gfx942 gfx90a gfx1150 gfx1151 gfx1152 gfx1153 gfx1201 gfx1200 gfx1250 +ARCH_BINS := $(addprefix mega_kernel_test_,$(ARCHES)) +# +# CDNA (Compute/Datacenter): +# - MI250/MI250X (CDNA2): gfx90a +# - MI300/MI300X (CDNA3): gfx942 +# - MI350/MI355X (CDNA4): gfx950 +# +# RDNA 3.5+ APU iGPU (gfx115x-class today; additional APUs may use this bucket later): +# - Strix Point (RDNA 3.5): gfx1150 +# - Strix Halo (RDNA 3.5): gfx1151 +# - Krackan (RDNA 3.5): gfx1152 +# - Gorgon Point (RDNA 3.5): gfx1153 +# +# RDNA4 (Consumer): +# - RX 9070 XT/9070 (RDNA4): gfx1201 +# - RX 9060 series (RDNA4): gfx1200 +# +# gfx1250: +# - gfx1250: FP8/BF8 OCP, WMMA, TDM, cluster ops, cooperative atomics +# +# gfx1250 metric-health profile args (do not pass -t 32; that is wave size, not block size) +GFX1250_HEALTH_BATCH ?= 655360 + +# Targets: +# make - Build for gfx1151 (default; override with GPU_ARCH=...) +# make gfx950 - Build for MI350 (gfx950/CDNA4) +# make gfx942 - Build for MI300 (gfx942/CDNA3) +# make gfx90a - Build for MI250 (gfx90a/CDNA2) +# make gfx1150 - Build for Strix Point (gfx1150/RDNA3.5) +# make gfx1151 - Build for Strix Halo (gfx1151/RDNA3.5) +# make gfx1152 - Build for Krackan (gfx1152/RDNA3.5) +# make gfx1153 - Build for Gorgon Point (gfx1153/RDNA3.5) +# make gfx1201 - Build for RX 9070 XT (gfx1201/RDNA4) +# make gfx1200 - Build for RX 9060 (gfx1200/RDNA4) +# make gfx1250 - Build for gfx1250 +# make all-arch - Build binaries for all ARCHES +# make clean - Remove build artifacts +# make run - Build and run the test + +# Compiler +HIPCC ?= hipcc + +# Default architecture: gfx1151 (Strix Halo / RDNA 3.5) +GPU_ARCH ?= gfx1151 + +# Common flags +HIPCC_FLAGS = -O2 -g -std=c++17 +HIPCC_FLAGS += -Wall -Wextra +HIPCC_FLAGS += -fPIC + +# Include paths +INCLUDES = -I. + +# Source files +KERNEL_SRC = mega_kernel.hip +HOST_SRC = main.cpp + +# Output +DEFAULT_TARGET = mega_kernel_test + +# Build rules +.PHONY: all clean run run-gfx1250-health $(ARCHES) all-arch help + +all: $(DEFAULT_TARGET) + +$(DEFAULT_TARGET): $(KERNEL_SRC) $(HOST_SRC) + @echo "Building for $(GPU_ARCH)..." + $(HIPCC) $(HIPCC_FLAGS) --offload-arch=$(GPU_ARCH) $(INCLUDES) -o $@ $^ + @echo "Build complete: $@" + +# One pattern rule for all per-arch binaries (stem is full token, e.g. gfx950) +$(ARCH_BINS): mega_kernel_test_%: $(KERNEL_SRC) $(HOST_SRC) + @echo "Building for $*..." + $(HIPCC) $(HIPCC_FLAGS) --offload-arch=$* $(INCLUDES) -o $@ $^ + @echo "Build complete: $@" + +# Symlink convenience: gfx950 -> mega_kernel_test_gfx950, etc. +$(ARCHES): + @$(MAKE) mega_kernel_test_$@ + @ln -sf mega_kernel_test_$@ mega_kernel_test + @echo "Linked mega_kernel_test -> mega_kernel_test_$@" + +all-arch: $(ARCH_BINS) + @echo "Built: $(ARCHES)" + +# Run the test +run: $(DEFAULT_TARGET) + @echo "" + @echo "Running GPU Mega Kernel Test..." + @echo "" + ./$(DEFAULT_TARGET) + +# gfx1250 metric-health workload (see rocprof-compute profile -- ... -- ) +run-gfx1250-health: mega_kernel_test_gfx1250 + @echo "Running gfx1250 health args: -b $(GFX1250_HEALTH_BATCH) (block size default 256)" + ./mega_kernel_test_gfx1250 -b $(GFX1250_HEALTH_BATCH) + +# Clean +clean: + rm -f mega_kernel_test mega_kernel_test_* *.o *.out + +# Help +help: + @echo "AMD GPU Mega Kernel Unit Test - Build System" + @echo "" + @echo "Supported Architectures (ARCHES in Makefile):" + @echo " $(ARCHES)" + @echo "" + @echo " CDNA (Compute/Datacenter):" + @echo " gfx90a - MI250/MI250X (CDNA2): FP32/FP64/FP16/BF16, HW FP64 atomics" + @echo " gfx942 - MI300/MI300X (CDNA3): + FP8/BF8 (FNUZ-style cvt), TF32, Sparse MFMA" + @echo " gfx950 - MI350/MI355X (CDNA4): + FP6/FP4, OCP FP8/BF8 paths where applicable, Async LDS, Dual-Issue" + @echo "" + @echo " RDNA 3.5+ APU iGPU (gfx115x-class):" + @echo " gfx1150 - Strix Point: WMMA, TEX load/store tests (no CDNA-style FP8/BF8 cvt)" + @echo " gfx1151 - Strix Halo: same as gfx1150" + @echo " gfx1152 - Krackan: same as gfx1150" + @echo " gfx1153 - Gorgon Point: same as gfx1150" + @echo "" + @echo " RDNA4 (Consumer):" + @echo " gfx1201 - RX 9070 XT/9070: FP8/BF8 OCP, Async LDS, Wave32 WMMA path" + @echo " gfx1200 - RX 9060 series: FP8/BF8 OCP, Async LDS, Wave32 WMMA path" + @echo "" + @echo " gfx1250:" + @echo " gfx1250 - FP8/BF8 OCP, WMMA, TDM, cluster ops, cooperative atomics" + @echo "" + @echo "Usage:" + @echo " make - Build for gfx1151 (default; set GPU_ARCH to override)" + @echo " make gfx950 - Build for MI350 (gfx950/CDNA4)" + @echo " make gfx942 - Build for MI300 (gfx942/CDNA3)" + @echo " make gfx90a - Build for MI250 (gfx90a/CDNA2)" + @echo " make gfx1150 - Build for Strix Point (gfx1150)" + @echo " make gfx1151 - Build for Strix Halo (gfx1151)" + @echo " make gfx1152 - Build for Krackan (gfx1152)" + @echo " make gfx1153 - Build for Gorgon Point (gfx1153)" + @echo " make gfx1201 - Build for RX 9070 XT (gfx1201/RDNA4)" + @echo " make gfx1200 - Build for RX 9060 (gfx1200/RDNA4)" + @echo " make gfx1250 - Build for gfx1250" + @echo " make all-arch - Build one binary per ARCHES" + @echo " make run - Build and run the test" + @echo " make run-gfx1250-health - Run gfx1250 metric-health args (-b $(GFX1250_HEALTH_BATCH))" + @echo " make clean - Remove build artifacts" + @echo " make help - Show this help message" + @echo "" + @echo "Environment Variables:" + @echo " GPU_ARCH - Target GPU architecture (default: gfx1151)" + @echo " HIPCC - HIP compiler path (default: hipcc)" + @echo "" + @echo "Example:" + @echo " make gfx1201 - Build for RX 9070 XT (RDNA4)" + @echo " make gfx942 - Build for MI300" + @echo " GPU_ARCH=gfx950 make # example: MI350 (CDNA4)" diff --git a/skills/rocprof-compute-sample/mega_kernel/README.md b/skills/rocprof-compute-sample/mega_kernel/README.md new file mode 100644 index 0000000..51e0fa9 --- /dev/null +++ b/skills/rocprof-compute-sample/mega_kernel/README.md @@ -0,0 +1,672 @@ +# AMD GPU Mega Kernel Unit Test + +A comprehensive unit test kernel that exercises most of the instructions and features available on AMD GPUs including Instinct (MI250/MI300/MI350), Radeon (RX 9060 / RX 9070 families, gfx120x), Strix/Strix Halo/Krackan (RDNA 3.5, gfx115x) APU iGPUs, and gfx1250. + +## Supported GPU Architectures + +*GPU/architecture tables below are partly **informational** (what silicon markets); **what this sample runs** is only what the **Feature Compatibility Matrix** and following “Features Tested” sections describe.* + +### CDNA (Compute/Datacenter) + +| GPU Series | Architecture | GFXIP | Key Features | +|------------|--------------|-------|--------------| +| **MI250/MI250X** | CDNA2 | gfx90a | FP32/FP64/FP16/BF16, HW FP64 atomics, MFMA, DOT4/DOT8 | +| **MI300/MI300X/MI325X** | CDNA3 | gfx942 | All MI250 + FP8/BF8 (FNUZ-style cvt), TF32, Sparse MFMA | +| **MI350X/MI355X** | CDNA4 | gfx950 | All MI300 + FP6/FP4, OCP FP8/BF8 where applicable, Async LDS, Enhanced WMMA | + +### RDNA4 (Consumer) + +| GPU Series | Architecture | GFXIP | Key Features | +|------------|--------------|-------|--------------| +| **RX 9070 XT/9070** | RDNA4 | gfx1201 | FP8/BF8 OCP, Async LDS, Wave32, WMMA (gfx12 path) | +| **RX 9060 series** | RDNA4 | gfx1200 | Same as gfx1201 with different CU count | + +### RDNA 3.5+ APU iGPU (gfx115x-class) + +| GPU Series | Architecture | GFXIP | Key Features | +|------------|--------------|-------|--------------| +| **Strix Point** | RDNA 3.5 | gfx1150 | 16 CUs, WMMA, Wave32, Async LDS | +| **Strix Halo** | RDNA 3.5 | gfx1151 | Up to 40 CUs, WMMA, Wave32, Async LDS | +| **Krackan** | RDNA 3.5 | gfx1152 | 32MB Infinity Cache, same as gfx1150 | + +### gfx1250 + +| GPU Series | Architecture | GFXIP | Key Features | +|------------|--------------|-------|--------------| +| **gfx1250** | gfx1250 | gfx1250 | FP8/BF8 OCP, HW FP64 atomics, WMMA (gfx1250 builtins), TDM, cluster ops, cooperative atomics, Async LDS | + +## Feature Compatibility Matrix + +This table lists **what this sample tries to run** on each GFXIP, plus a few hardware facts that are **not** individually exercised here (no standalone fixed-function graphics or branded accelerator microbenchmarks in this kernel). MFMA/WMMA builtins use **non-zero operands** and pass only if the accumulator fragment has **at least one non-zero lane** (FP paths also require **finite** lanes)—smoke validation, not golden-matrix or full ISA conformance. Dual-issue is **BYPASSED** in the report (patterns only). Async LDS follows `__has_builtin(__builtin_amdgcn_global_load_async_to_lds_b32)` in code (gfx942 is treated as unsupported in this sample, matching `mega_kernel_device_arch.h`). + +| Feature | MI250 (gfx90a) | MI300 (gfx942) | MI350 (gfx950) | Strix/Krackan (gfx115x) | RX 9070 XT (gfx1201) | gfx1250 | +|---------|:--------------:|:--------------:|:--------------:|:-----------------------:|:--------------------:|:---------------:| +| FP32 Arithmetic | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| FP64 Arithmetic | ✅ | ✅ | ✅ | ⚠️ (limited) | ⚠️ (limited) | ✅ | +| FP16/BF16 Arithmetic | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| INT32/INT64 Operations | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| HW FP64 Atomics | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | +| FP8/BF8 Conversions | ❌ | ✅ (FNUZ) | ✅ (OCP) | ❌ | ✅ (OCP) | ✅ (OCP) | +| FP6/FP4 Conversions | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | +| DOT4/DOT8 Products | ✅ | ✅ | ✅ | ✅ | ✅ (software) | ✅ | +| Warp/Wave Ops | ✅ (Wave64) | ✅ (Wave64) | ✅ (Wave64) | ✅ (Wave32) | ✅ (Wave32) | ✅ (Wave32) | +| LDS Operations | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Async LDS (this sample) | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | +| Dual-Issue VALU (this sample) | — | BYPASS | BYPASS | BYPASS | — | BYPASS | +| MFMA Matrix Ops | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | +| WMMA Matrix Ops | ❌ | ❌ | ❌ | ✅ (gfx12) | ✅ (gfx12) | ✅ (gfx1250) | +| Packed FP16 Atomics | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Packed BF16 Atomics | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | +| VMEM Inline ASM | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | + +⚠️ = Limited support / may vary by ROCm version. **BYPASS** = kernel emits instruction patterns but the host report marks the category bypassed (not a correctness proof). Fixed-function blocks not driven by this VALU/WMMA/TEX-focused kernel are **not exercised here**. + +## Features Tested + +### Test Category Summary + +| # | Category | Tests | Description | +|---|----------|-------|-------------| +| 1 | Warp/Wave Operations | 4 | Shuffle, ballot, permute, reduce | +| 2 | Data Type Conversions | 4 | FP8, BF8, FP16, BF16 conversions | +| 3 | Arithmetic Operations | 4 | FP32, FP64, INT32, INT64 | +| 4 | Basic Atomic Operations | 6 | FP32/FP64 add, min/max, integer ops, LDS | +| 5 | Transcendental Functions | 3 | Trig, exp/log, rcp/rsq | +| 6 | Memory Operations | 2 | Global load/store | +| 7 | DOT Products | 2 | DOT4, DOT8 | +| 8 | MFMA Operations | 5 | FP32, FP64, FP16, BF16, INT8 matrix ops | +| 9 | Async LDS | 2 | Async load/store to LDS | +| 11 | Dual-Issue VALU | 2 | FP64+FP32, INT+FP32 patterns | +| 13 | WMMA Operations | 3 | FP16, BF16, INT8 matrix ops (RDNA4) | +| 14 | VMEM Operations | 5 | Flat, global, buffer, scratch, LDS (inline ASM) | +| 15 | Atomic Operations (ASM) | 10 | Global/flat/DS int/float, packed, CAS (inline ASM) | + +### Warp/Wave Operations +- Warp shuffle (`__shfl`, `__shfl_xor`, `__shfl_up`, `__shfl_down`) +- Warp ballot (`__ballot`, `__any`, `__all`) +- DS permute operations (`ds_bpermute`, `ds_permute`) +- Warp-level reductions + +### Floating Point Operations +- **FP8/BF8 Conversions** + - `__builtin_amdgcn_cvt_scalef32_pk_*` (gfx950 specific) +- FP16/BF16 operations and packed arithmetic +- FP32/FP64 arithmetic with FMA + +### Integer Operations +- INT32 arithmetic and bit operations +- INT64 arithmetic +- Population count and leading zeros + +### Basic Atomic Operations (HIP API) +- Atomic Add FP32 (`atomicAdd`) +- Atomic Min/Max FP32 (via `atomicMin`/`atomicMax` with float-to-uint cast) +- Integer atomics: add, sub, and, or, xor, min, max +- LDS atomics + +### Transcendental Functions +- sin, cos, tan, tanh +- exp, log, log2, log10 +- sqrt, rsqrt, rcp +- pow, cbrt + +### Memory Operations +- Global memory load/store (coalesced) +- LDS (shared memory) operations +- Memory barriers and fences + +--- + +## Inline Assembly Tests (Architecture-Specific) + +The following sections document the inline assembly tests that verify hardware instruction paths +directly, using architecture-specific syntax for CDNA (gfx9x) and RDNA4 (gfx12). + +### VMEM (Vector Memory) Operations - Comprehensive Inline ASM Tests + +This test exercises all memory instruction types using inline assembly to verify +the hardware memory paths are functioning correctly. + +**Memory Instruction Categories:** + +| Category | CDNA Syntax (gfx9x) | RDNA4 Syntax (gfx12) | Description | +|----------|---------------------|----------------------|-------------| +| **Flat** | `flat_load_dword` | `flat_load_b32` | Unified addressing (global/LDS/scratch) | +| **Global** | `global_load_dword` | `global_load_b32` | Direct global memory access | +| **Buffer** | `buffer_load_dword` | `buffer_load_b32` | Descriptor-based memory access | +| **Scratch** | `scratch_load_dword` | `scratch_load_b32` | Private per-thread stack | +| **LDS** | `ds_read_b32` | `ds_read_b32` | Shared memory (same on all archs) | + +**Data Widths Supported:** + +| Suffix | Size | CDNA Name | RDNA4 Name | +|--------|------|-----------|------------| +| b8/ubyte | 8-bit | `*_ubyte` | `*_b8` | +| b16/ushort | 16-bit | `*_ushort` | `*_b16` | +| b32/dword | 32-bit | `*_dword` | `*_b32` | +| b64/dwordx2 | 64-bit | `*_dwordx2` | `*_b64` | +| b128/dwordx4 | 128-bit | `*_dwordx4` | `*_b128` | + +**Flat Memory Instructions:** +```asm +; CDNA (gfx90a/gfx942/gfx950) +flat_load_dword vdst, vaddr [offset:N] [glc] [slc] +flat_store_dword vaddr, vdata [offset:N] [glc] [slc] + +; RDNA4 (gfx1200/gfx1201/) +flat_load_b32 vdst, vaddr [offset:N] +flat_store_b32 vaddr, vdata [offset:N] +``` + +**Global Memory Instructions:** +```asm +; CDNA (gfx90a/gfx942/gfx950) +global_load_dword vdst, vaddr, saddr [offset:N] +global_store_dword vaddr, vdata, saddr [offset:N] + +; RDNA4 (gfx1200/gfx1201/) +global_load_b32 vdst, vaddr, saddr [offset:N] +global_store_b32 vaddr, vdata, saddr [offset:N] +``` + +**LDS (DS) Instructions:** +```asm +; Same syntax on all architectures +ds_read_b32 vdst, vaddr [offset:N] +ds_write_b32 vaddr, vdata [offset:N] +ds_read2_b32 vdst0:vdst1, vaddr offset0:N offset1:M +ds_write2_b32 vaddr, vdata0, vdata1 offset0:N offset1:M +``` + +**Architecture Support:** + +| Instruction Type | gfx90a | gfx942 | gfx950 | gfx1150/1151 | gfx1200/1201 | | +|------------------|:------:|:------:|:------:|:------------:|:-------:| +| flat_load/store | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| global_load/store | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| buffer_load/store | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| scratch_load/store | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| ds_read/write | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| global_load_async | ❌ | ⚠️ | ✅ | ✅ | ✅ | ✅ | +| tensor_load_to_lds | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | + +### Atomic Operations - Comprehensive Inline ASM Tests + +This test exercises all atomic instruction types using inline assembly to verify +the hardware atomic paths are functioning correctly. + +**Atomic Instruction Categories:** + +| Category | Instructions | Description | +|----------|--------------|-------------| +| **Global INT** | `global_atomic_add/sub/and/or/xor/min/max/swap` | Integer atomics on global memory | +| **Global FP32** | `global_atomic_add_f32` | FP32 atomic add | +| **Global FP64** | `global_atomic_add_f64/min_f64/max_f64` | FP64 atomics (CDNA/ only) | +| **Flat INT** | `flat_atomic_add/sub/min/max` | Integer atomics (unified addressing) | +| **DS INT** | `ds_add_u32/sub_u32/min_i32/max_u32` | LDS integer atomics | +| **DS FP32** | `ds_add_f32/min_f32/max_f32` | LDS FP32 atomics | +| **Packed FP16** | `global_atomic_pk_add_f16` | Add two FP16 values atomically | +| **Packed BF16** | `global_atomic_pk_add_bf16` | Add two BF16 values atomically | +| **CAS** | `global_atomic_cmpswap` | Compare-and-swap | + +**Naming Convention Changes (RDNA4/gfx12):** + +| CDNA (gfx9x) | RDNA4 (gfx12) | +|--------------|---------------| +| `global_atomic_add` | `global_atomic_add_u32` | +| `global_atomic_smax` | `global_atomic_max_i32` | +| `flat_atomic_add` | `flat_atomic_add_u32` | +| `global_atomic_cmpswap` | `global_atomic_cmpswap_b32` | +| Return flag: `glc` | Return flag: `th:TH_ATOMIC_RETURN` | + +**Architecture Support:** + +| Atomic Type | gfx90a | gfx942 | gfx950 | gfx1150/51 | gfx1200/01 | | +|-------------|:------:|:------:|:------:|:----------:|:-------:| +| INT32/INT64 atomics | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| FP32 atomic add | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| FP64 atomic add/min/max | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | +| Packed FP16 atomic add | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Packed BF16 atomic add | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | +| DS FP32 atomics | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| DS FP64 atomics | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | +| CAS operations | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | + +**Inline Assembly Examples:** + +```asm +; CDNA - Global atomic add (integer with return) +global_atomic_add %0, %1, %2, off glc +s_waitcnt vmcnt(0) + +; RDNA4 - Global atomic add (integer with return) +global_atomic_add_u32 %0, %1, %2, off th:TH_ATOMIC_RETURN +s_waitcnt vmcnt(0) + +; DS atomic add (same on all architectures) +ds_add_rtn_u32 %0, %1, %2 +s_waitcnt lgkmcnt(0) + +; Packed FP16 atomic add +global_atomic_pk_add_f16 %0, %1, %2, off glc ; CDNA +global_atomic_pk_add_f16 %0, %1, %2, off th:TH_ATOMIC_RETURN ; RDNA4 +``` + +### Architecture Detection and Conditional Compilation + +The inline ASM tests use preprocessor macros to detect the target architecture and use +the correct instruction syntax: + +```cpp +#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) + // CDNA syntax: dword suffix, glc for return + asm volatile("global_atomic_add %0, %1, %2, off glc" : "=v"(old) : "v"(addr), "v"(val) : "memory"); + +#elif defined(__gfx1150__) || defined(__gfx1151__) || defined(__gfx1152__) || defined(__gfx1200__) || defined(__gfx1201__) + // RDNA 3.5 APU / RDNA4 (GFX12-style): explicit type suffix, TH_ATOMIC_RETURN for return + asm volatile("global_atomic_add_u32 %0, %1, %2, off th:TH_ATOMIC_RETURN" : "=v"(old) : "v"(addr), "v"(val) : "memory"); +#endif +``` + +**Architecture Macros:** +| Macro | Architecture | GPU | +|-------|--------------|-----| +| `__gfx90a__` | CDNA2 | MI250/MI250X | +| `__gfx942__` | CDNA3 | MI300/MI300X/MI325X | +| `__gfx950__` | CDNA4 | MI350X/MI355X | +| `__gfx1150__` | RDNA 3.5 | Strix Point | +| `__gfx1151__` | RDNA 3.5 | Strix Halo | +| `__gfx1152__` | RDNA 3.5 | Krackan | +| `__gfx1200__` | RDNA4 | RX 9060 series | +| `__gfx1201__` | RDNA4 | RX 9070 XT/9070 | + +Note: Not test on MI308X and MI300A yet +--- + + +### Dual-Issue VALU (gfx942+) + +**Same-Wave Co-Issue (VOPD/VOPD3)** - Available on MI300, MI350 : +- Uses VOPD encoding to execute 2 operations from the same wave in parallel +- X instruction uses Core MACC, Y instruction uses Side MACC +- VOPD3 extends this with 3 source operands per instruction + +**Cross-Wave Dual-Issue** - MI350 (gfx950): +MI350 can issue 2 VALU instructions from different waves per cycle under certain conditions: +- Both waves must be Wave32 +- Both must be single-cycle instructions +- One uses DP-MACC (FP64), other uses Core/Side-MACC (FP32/FP16) +- Neither uses DPP, Transcendentals, or WMMA + +**Dual-Issue Eligible Patterns:** +- FP64 operations + FP32 operations (from different waves) +- Integer operations + FP32 operations + +**Dual-Issue Blockers:** +- VOPD/VOPD3 instructions +- DPP instructions +- WMMA/XDL/Matrix instructions +- Transcendentals (sin, cos, exp, log) +- Multi-cycle instructions + +### MFMA (Matrix Fused Multiply-Add) Operations (gfx90a+/CDNA) + +*This section lists representative MFMA intrinsics. The kernel feeds **non-zero** packed operands (scalars/vectors as required per opcode) and requires **finite** FP accumulators with **at least one non-zero** `float`/`double` lane, or **at least one non-zero** `int` lane for INT8 MFMA—not reference-matrix numerical validation.* + +Matrix operations using dedicated Matrix Cores. For maintained MFMA / roofline work, see `src/roofline/benchmark/` in rocprofiler-compute (the older rocm-amdgpu-bench repo is deprecated). + +**MI250 (gfx90a/CDNA2):** +- `__builtin_amdgcn_mfma_f32_32x32x2f32` - FP32 (4096 ops/inst) +- `__builtin_amdgcn_mfma_f64_16x16x4f64` - FP64 (2048 ops/inst) +- `__builtin_amdgcn_mfma_f32_32x32x8f16` - FP16 (16384 ops/inst) +- `__builtin_amdgcn_mfma_f32_32x32x4bf16` - BF16 (8192 ops/inst) +- `__builtin_amdgcn_mfma_i32_32x32x8i8` - INT8 (16384 ops/inst) + +**MI300 (gfx942/CDNA3) - Enhanced:** +- All MI250 ops plus doubled K dimensions: +- `__builtin_amdgcn_mfma_f32_32x32x8bf16_1k` - BF16 (16384 ops/inst) +- `__builtin_amdgcn_mfma_i32_32x32x16_i8` - INT8 (32768 ops/inst) +- `__builtin_amdgcn_mfma_f32_32x32x16_fp8_fp8` - FP8 (32768 ops/inst) **NEW** + +**MI350 (gfx950/CDNA4) - Scaled F8F6F4:** +- All MI300 ops plus: +- `__builtin_amdgcn_mfma_scale_f32_32x32x64_f8f6f4` - FP8/BF8/FP6/FP4 (131072 ops/inst) + +### WMMA (Wave Matrix Multiply Accumulate) Operations (RDNA4/gfx12) + +*Same scope as MFMA: **non-zero** A/B fragments (indexed using **lane_id**), FP WMMA requires **finite** outputs with **at least one non-zero** lane in the per-lane accumulator, INT WMMA requires **at least one non-zero** lane—not exhaustive conformance. RDNA 3.5 (gfx115x) uses the 16-wide-input WMMA builtins; RDNA4 (gfx120x) uses the `_w32_gfx12` three-operand builtins—see `mega_kernel.hip`.* + +WMMA instructions are the matrix acceleration approach for RDNA architecture (vs MFMA for CDNA). +Reference: [AMD GPUOpen - Using Matrix Cores on RDNA4](https://gpuopen.com/learn/using_matrix_core_amd_rdna4/) + +**WMMA Performance (FLOPS/clock/CU):** +| Format | RDNA2 (RX 6950) | RDNA3 (RX 7900) | RDNA4 (RX 9070/) | +|--------|:---------------:|:---------------:|:---------------------:| +| FP16 | 256 | 512 | 1024 | +| BF16 | N/A | 512 | 1024 | +| INT8 | 512 | 512 | 2048 | + +**WMMA Intrinsic Format:** +``` +__builtin_amdgcn_wmma__16x16x16__w32_gfx12 +``` + +**Available WMMA Intrinsics (gfx12/RDNA4):** +- `__builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12` - FP16 → FP32 (16384 ops) +- `__builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12` - BF16 → FP32 (16384 ops) +- `__builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12` - INT8 → INT32 (16384 ops) +- `__builtin_amdgcn_wmma_f16_16x16x16_f16_w32_gfx12` - FP16 → FP16 +- `__builtin_amdgcn_wmma_bf16_16x16x16_bf16_w32_gfx12` - BF16 → BF16 + +**WMMA Matrix Layout:** +- 16x16 matrix distributed across Wave32 (32 lanes) +- Each lane holds 8 elements (8 × 32 = 256 = 16 × 16) +- Matrix A: Column-major (transposed) +- Matrices B, C, D: Row-major +- `lane_id = threadIdx.x % warpSize` (matches `mega_kernel.hip`) +- `laneWrapped = lane_id % 16` (column index) +- `laneGroup = lane_id / 16` (upper/lower half) + +**Key RDNA4 WMMA Differences vs RDNA3:** +- VGPR layout changed (NOT backward compatible) +- All intrinsics have `_gfx12` suffix +- Wave32 only (no Wave64 WMMA) +- 2x throughput improvement vs RDNA3 + +**Supported on:** +- Strix Point (gfx1150) / Strix Halo (gfx1151) +- RX 9070 XT/9070 (gfx1201) +- RX 9060 series (gfx1200) +- Bypassed on CDNA architectures (use MFMA instead) + +### DOT Product Operations (CDNA) +- V_DOT4_U32_U8 (4-element unsigned INT8 dot product) +- V_DOT8_U32_U4 (8-element unsigned INT4 dot product) + +### Lane/Thread Information +- Lane ID builtins +- Wavefront size detection + +### Strix / Strix Halo / Krackan (RDNA 3.5 / gfx1150 / gfx1151 / gfx1152) Specific Features + +*Background on RDNA 3.5 APUs; coverage here is limited to paths the matrix marks as tested (e.g. WMMA/TEX), not every fixed-function block.* + +RDNA 3.5 is the integrated GPU in Strix Point (gfx1150, 16 CUs), Strix Halo (gfx1151, up to 40 CUs), and Krackan (gfx1152) client APUs: + +1. **Wave32 Native** + - Native Wave32 execution; optimized for mobile and APU workloads. + +2. **WMMA (Wave Matrix Multiply Accumulate)** + - 16x16x16 matrix operations (FP16/BF16/INT8), same ISA as RDNA4/gfx12. + - Supported via `__builtin_amdgcn_wmma_*_w32_gfx12` intrinsics. + +3. **VOPD Same-Wave Dual-Issue** + - Same-wave dual-issue VALU via VOPD encoding (similar to CDNA3/CDNA4). + +4. **Scalar FP and Vector Hints** + - Scalar floating-point in the scalar unit; `s_singleuse_vdst` hint. + - Note: FP8/BF8 conversion builtins (`__builtin_amdgcn_cvt_pk_fp8_f32`) are NOT available on RDNA 3.5 APUs. + +5. **Async LDS and WGP** + - Async LDS operations; WGP (Workgroup Processor) architecture. + + - No Tensor Data Mover; no hardware FP64 atomics (consumer iGPU). + - Unified `s_waitcnt` (pre–gfx12 split waits). + + +## RDNA4 Consumer (RX 9070 XT/gfx1200/gfx1201) Specific Features + +*Consumer RDNA4 background; this kernel focuses on VALU/WMMA/FP8/async-LDS-style paths in the matrix—not a graphics or fixed-function survey.* + +The following features are available on RDNA4 consumer GPUs (RX 9000 series): + +1. **Wave32 Native Architecture** + - Native Wave32 execution (Wave64 emulated) + - Optimized for gaming and inference workloads + +2. **FP8/BF8 OCP Format Support** + - Open Compute Platform (OCP) FP8/BF8 format + - `__builtin_amdgcn_cvt_pk_fp8_f32` + - `__builtin_amdgcn_cvt_pk_bf8_f32` + +3. **AI Inference Accelerators** + - Hardware acceleration for INT8/FP8 inference *(not separately benchmarked here—WMMA/FP8/dot coverage follows the matrix)* + +4. **Async LDS Operations** + - `global_load_async_to_lds_b*` instructions + - Similar to CDNA4 but optimized for consumer workloads + +5. **WGP (Workgroup Processor) Architecture** + - 2 CUs per WGP + - Improved SIMD scheduling + +## Building + +### Prerequisites +- ROCm 6.0+ with HIP +- hipcc compiler +- AMD GPU (e.g. Strix Halo / gfx1151, or other supported GFXIP) + +### Using Make + +```bash +# Build for gfx1151 (default; override with GPU_ARCH=gfx950, etc.) +make + +# Build for specific architectures +make gfx950 # MI350/MI355X (CDNA4) +make gfx942 # MI300/MI300X/MI325X (CDNA3) +make gfx90a # MI250/MI250X (CDNA2) +make gfx1150 # Strix Point (RDNA 3.5) +make gfx1151 # Strix Halo (RDNA 3.5) +make gfx1152 # Krackan (RDNA 3.5) +make gfx1201 # RX 9070 XT/9070 (RDNA4) +make gfx1200 # RX 9060 series (RDNA4) +make gfx1250 # gfx1250 + +# Build for all architectures (all ARCHES in Makefile) +make all-arch + +# Build and run +make run + +# Show help +make help + +# Clean build artifacts +make clean +``` + +### Manual Build + +```bash +# For MI350 (CDNA4) +hipcc -O2 -std=c++17 --offload-arch=gfx950 -o mega_kernel_test main.cpp mega_kernel.hip + + +# For Strix Point/Strix Halo/Krackan (RDNA 3.5) +hipcc -O2 -std=c++17 --offload-arch=gfx1150 -o mega_kernel_test main.cpp mega_kernel.hip # Strix Point +hipcc -O2 -std=c++17 --offload-arch=gfx1151 -o mega_kernel_test main.cpp mega_kernel.hip # Strix Halo +hipcc -O2 -std=c++17 --offload-arch=gfx1152 -o mega_kernel_test main.cpp mega_kernel.hip # Krackan + +# For gfx1250 +hipcc -O2 -std=c++17 --offload-arch=gfx1250 -o mega_kernel_test_gfx1250 main.cpp mega_kernel.hip +``` + +## Running + +```bash +# Run with default settings (batch_size=1024) +./mega_kernel_test + +# Run with custom batch size +./mega_kernel_test -b 4096 + +# Run with larger batch size for stress testing +./mega_kernel_test --batch-size 65536 + +# Run with custom batch size and block size (64-256, power of 2) +./mega_kernel_test -b 65536 -t 256 + +# gfx1250 metric-health profile workload (default block size 256; do not use -t 32) +./mega_kernel_test_gfx1250 -b 655360 + +# Run kernel multiple times (for benchmarking/profiling) +./mega_kernel_test -n 100 + +# Run with larger batch and multiple iterations +./mega_kernel_test -b 65536 -n 50 + +# Run with only inline assembly MFMA tests +./mega_kernel_test --mfma-mode asm + +# Run with only HIP builtin MFMA tests +./mega_kernel_test -m builtin + +# Run both ASM and builtin MFMA tests (default) +./mega_kernel_test -m both + +# Show help +./mega_kernel_test --help +``` + +### Command-Line Options + +| Option | Description | Default | +|--------|-------------|---------| +| `-b, --batch-size ` | Set batch/problem size (64 to 16M) | 1024 | +| `-t, --block-size ` | Set thread block size (power of 2, 64-256) | 256 | +| `-n, --num-iterations ` | Number of kernel iterations (1 to 10000) | 5 | +| `-m, --mfma-mode ` | MFMA test mode: `both`, `asm`, or `builtin` (see note) | both | +| `-v, --verbose` | Enable verbose output | off | +| `-h, --help` | Show help message | - | + +**Note on MFMA mode:** MFMA instructions require complex accumulator register allocation that is difficult to express in inline assembly constraints. As a result, the MFMA tests primarily use HIP builtins which properly handle register allocation. The `--mfma-mode` option controls which test paths are exercised: +- `builtin` (recommended): Uses HIP builtins (e.g., `__builtin_amdgcn_mfma_f32_32x32x2f32`) +- `asm`: Attempts inline assembly where supported (falls back to builtins for complex MFMA ops) +- `both` (default): Runs both code paths where applicable + + +``` +================================================================================ +AMD GPU MEGA KERNEL UNIT TEST +================================================================================ +Device Information: + Name: AMD Instinct MI350 + GCN Architecture: gfx950 + Compute Units: 304 + ... +================================================================================ +Detected Platform: MI350 (CDNA4/gfx950) +================================================================================ + +Running GPU Mega Kernel for MI350 (CDNA4/gfx950)... + +TEST RESULTS +================================================================================ + +[Category: Warp/Wave Operations] + Warp Shuffle (shfl/shfl_xor/shfl_up/down) : PASS (4/1) + Warp Ballot (__ballot/__any/__all) : PASS (4/1) + ... + +[Category: Atomic Operations] + Atomic Add FP64 (HW atomic on CDNA) : PASS (4/1) + ... + + +SUMMARY +================================================================================ +Test Categories Passed: 44 / 44 +Test Categories Failed: 0 +Test Categories Bypassed: 0 (N/A for this architecture) +Kernel Execution Time: 0.523 ms + +*** OVERALL: PASSED (44/44, 0 bypassed) *** +``` + +### Sample Output (gfx1151 - Strix Halo) + +``` +================================================================================ +AMD GPU MEGA KERNEL UNIT TEST +================================================================================ +Device Information: + Name: AMD Radeon(TM) Graphics + GCN Architecture: gfx1151 + Compute Units: 40 + ... +================================================================================ +Detected Platform: Strix/Strix Halo/Krackan (RDNA3.5/gfx1150,gfx1151,gfx1152) +================================================================================ + +Running GPU Mega Kernel for Strix/Strix Halo/Krackan (RDNA3.5/gfx1150,gfx1151,gfx1152)... + +TEST RESULTS +================================================================================ + +[Category: Warp/Wave Operations] + Warp Shuffle (shfl/shfl_xor/shfl_up/down) : PASS (4/1) + ... + +[Category: Atomic Operations] + Atomic Add FP64 (HW atomic on CDNA) : BYPASSED (N/A) + ... + +[Category: Dual-Issue VALU (MI350/gfx950 specific)] + Dual-Issue Patterns (FP64+FP32 co-exec) : BYPASSED (N/A) + + +SUMMARY +================================================================================ +Test Categories Passed: 35 / 35 +Test Categories Failed: 0 +Test Categories Bypassed: 9 (N/A for this architecture) +Kernel Execution Time: 0.312 ms + +*** OVERALL: PASSED (35/35, 9 bypassed) *** +``` + +## Architecture Comparison + +Informational hardware notes (not a full map of what this sample executes). For **async LDS in this binary**, see the main compatibility matrix above (gfx942 is off here). + +| Feature | MI250 (gfx90a) | MI300 (gfx942) | MI350 (gfx950) | Strix/Krackan (gfx115x) | RX 9070 XT (gfx1201) | gfx1250 | +|---------|:-------------:|:--------------:|:--------------:|:-------------------:|:--------------------:|:---------------:| +| Architecture | CDNA2 | CDNA3 | CDNA4 | RDNA 3.5 | RDNA4 | gfx1250 | +| FP8 Support | No | FNUZ | OCP | No | OCP | OCP | +| FP6/FP4 Support | No | No | Yes | No | No | No | +| WMMA Wave64 | Yes | Yes | No (Wave32) | No | No | No | +| VGPRs per Wave32 | 512 | 512 | 1024 | 512 | 512 | — | +| Packed FP16 Atomics | Yes | Yes | Yes | Yes | Yes | Yes | +| Packed BF16 Atomics | No | Yes | Yes | Yes | Yes | Yes | +| Dual VALU Issue | No | VOPD | Yes | VOPD | No | Yes | +| Async LDS (hardware) | No | Limited† | Yes | Yes | Yes | Yes | +| MFMA Matrix Ops | Yes | Yes | Yes | No | No | No | +| WMMA Matrix Ops | No | No | No | Yes | Yes | Yes (gfx1250) | + +† This sample still treats gfx942 as without the async-LDS builtins path; see `mega_kernel_device_arch.h`. + +### Memory/Atomic Instruction Syntax + +| Instruction Type | CDNA (gfx9x) | RDNA4 (gfx12) | +|------------------|--------------|---------------| +| Load 32-bit | `*_load_dword` | `*_load_b32` | +| Load 64-bit | `*_load_dwordx2` | `*_load_b64` | +| Store 32-bit | `*_store_dword` | `*_store_b32` | +| Atomic add | `global_atomic_add` | `global_atomic_add_u32` | +| Atomic max (signed) | `global_atomic_smax` | `global_atomic_max_i32` | +| Return old value | `glc` flag | `th:TH_ATOMIC_RETURN` | +| Wait for VMEM | `s_waitcnt vmcnt(0)` | `s_waitcnt vmcnt(0)` | +| Wait for LDS | `s_waitcnt lgkmcnt(0)` | `s_waitcnt lgkmcnt(0)` | + +## License + +Copyright (c) 2026 - For testing purposes + +## References + +- [AMD Instinct MI350/ Architecture](https://rocm.docs.amd.com/) +- [AMD Radeon RX 9000 Series](https://www.amd.com/radeon/) +- [HIP Programming Guide](https://rocm.docs.amd.com/projects/HIP/) +- [AMD Matrix Instruction Calculator](https://github.com/ROCm/amd_matrix_instruction_calculator) diff --git a/skills/rocprof-compute-sample/mega_kernel/atomic_global_buffers.h b/skills/rocprof-compute-sample/mega_kernel/atomic_global_buffers.h new file mode 100644 index 0000000..faf003b --- /dev/null +++ b/skills/rocprof-compute-sample/mega_kernel/atomic_global_buffers.h @@ -0,0 +1,33 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. +// SPDX-License-Identifier: MIT +// +// Sizes and index layout for mega_kernel global atomic scratch buffers. +// main.cpp allocates and initializes; test_atomic_operations_asm / test_atomic_* +// in mega_kernel.hip must stay within these bounds. + +#pragma once + +// Element counts (device buffers sized with these in main.cpp). +#define MEGA_KERNEL_GLOBAL_INT_ELEMENTS 256 +#define MEGA_KERNEL_GLOBAL_FLOAT_ELEMENTS 256 +#define MEGA_KERNEL_GLOBAL_DOUBLE_ELEMENTS 64 + +// test_atomic_operations_asm (tid = global linear thread index): +// global_int_ptr: [tid % 64] — TEST 1 add / gfx12 asm add +// [(tid % 64) + 64] — TEST 1 max / gfx12 asm max +// [(tid % 64) + 128] — TEST 4 flat int add +// [(tid % 64) + 192] — TEST 10 CAS (expects initial 0) +// Max int index: 63 + 192 = 255 → need >= 256 int elements. +// +// global_f32_ptr: [tid % 64] — TEST 2 FP32 add +// [(tid % 64) + 64] — TEST 8 packed FP16 +// [(tid % 64) + 128] — TEST 9 packed BF16 +// Max float index: 63 + 128 = 191 → need >= 192 float elements (256 used). +// +// global_f64_ptr: [tid % 32], [(tid % 32) + 32] — TEST 3 (CDNA only) +// Max double index: 31 + 32 = 63 → need >= 64 double elements. +// +// Earlier in the same kernel launch (same buffers): +// test_atomic_add_f32: global_float[0] +// test_atomic_add_f64: global_double[0] +// test_atomic_int: global_int[0..6] diff --git a/skills/rocprof-compute-sample/mega_kernel/main.cpp b/skills/rocprof-compute-sample/mega_kernel/main.cpp new file mode 100644 index 0000000..bbdce42 --- /dev/null +++ b/skills/rocprof-compute-sample/mega_kernel/main.cpp @@ -0,0 +1,1014 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/** + * AMD GPU Mega Kernel Unit Test - Host Code + * + * This file contains the host-side code to run the GPU mega kernel + * and report test results for various AMD GPU architectures. + * + * Supported architectures: + * - gfx90a (MI250/CDNA2) + * - gfx942 (MI300/CDNA3) + * - gfx950 (MI350/CDNA4) + * - gfx1150 (Strix Point / RDNA 3.5) + * - gfx1151 (Strix Halo / RDNA 3.5) + * - gfx1152 (Krackan / RDNA 3.5) + * - gfx1153 (RDNA 3.5) + * - gfx1200 (RX 9060/RDNA4) + * - gfx1201 (RX 9070 XT/RDNA4) + * - gfx1250 (gfx1250) + * + * Build: make (default GPU_ARCH=gfx1151; override e.g. GPU_ARCH=gfx1201 make) or manual: + * hipcc -O2 -g -std=c++17 -Wall -Wextra -fPIC --offload-arch=gfx1151 -I. -o + * mega_kernel_test mega_kernel.hip main.cpp + * + */ + +#include +#include +#include +#include +#include +#include +#if __has_include() +# include +#endif +#include + +// Cluster launch requires HIP 7.2+ (hipLaunchKernelEx + clusterDim attribute). +#if defined(HIP_VERSION) && (HIP_VERSION >= 70200000) +# define MEGA_KERNEL_HAS_CLUSTER_LAUNCH 1 +#endif + +#include "atomic_global_buffers.h" +#include "mega_kernel_host_arch.hpp" +#include "mega_kernel_types.h" + +// Default configuration +#define DEFAULT_BATCH_SIZE 1024 +#define DEFAULT_BLOCK_SIZE 256 +#define DEFAULT_NUM_ITERATIONS 5 +// rocprof-compute gfx1250 metric-health profile workload (block size stays default). +#define GFX1250_HEALTH_BATCH_SIZE 655360 +#define MIN_BATCH_SIZE 64 +#define MAX_BATCH_SIZE (1024 * 1024 * 16) // 16M elements max +#define MAX_NUM_ITERATIONS 10000 + +// Command-line options +struct TestConfig +{ + int batch_size; + int block_size; + int num_iterations; + MfmaMode mfma_mode; + bool verbose; + bool help; +}; + +void +print_usage(const char* prog_name) +{ + printf("Usage: %s [OPTIONS]\n\n", prog_name); + printf("AMD GPU Mega Kernel Unit Test\n\n"); + printf("Options:\n"); + printf(" -b, --batch-size Set batch/problem size (default: %d)\n", + DEFAULT_BATCH_SIZE); + printf(" Valid range: %d to %d\n", MIN_BATCH_SIZE, + MAX_BATCH_SIZE); + printf(" -t, --block-size Set thread block size (default: %d)\n", + DEFAULT_BLOCK_SIZE); + printf(" Must be power of 2, 64-256 (max 256 due to kernel " + "shared memory)\n"); + printf(" -n, --num-iterations Number of kernel iterations (default: %d)\n", + DEFAULT_NUM_ITERATIONS); + printf(" Valid range: 1 to %d\n", MAX_NUM_ITERATIONS); + printf(" -m, --mfma-mode Set MFMA test mode (default: both)\n"); + printf(" Modes: both, asm, builtin\n"); + printf(" -v, --verbose Enable verbose output\n"); + printf(" -h, --help Show this help message\n"); + printf("\nExamples:\n"); + printf(" %s # Run with default settings\n", prog_name); + printf(" %s -b 4096 # Run with 4096 elements\n", prog_name); + printf(" %s -b 65536 -t 256 # Run with 64K elements, 256 threads/block\n", + prog_name); + printf(" %s -b %d # gfx1250 metric-health profile (default block %d)\n", + prog_name, GFX1250_HEALTH_BATCH_SIZE, DEFAULT_BLOCK_SIZE); + printf(" %s -n 100 # Run kernel 100 times\n", prog_name); + printf(" %s --mfma-mode asm # Test MFMA using inline assembly only\n", + prog_name); + printf(" %s -m builtin # Test MFMA using HIP builtins only\n", prog_name); +} + +TestConfig +parse_args(int argc, char** argv) +{ + TestConfig config; + config.batch_size = DEFAULT_BATCH_SIZE; + config.block_size = DEFAULT_BLOCK_SIZE; + config.num_iterations = DEFAULT_NUM_ITERATIONS; + config.mfma_mode = MFMA_MODE_BOTH; + config.verbose = false; + config.help = false; + + static struct option long_options[] = { { "batch-size", required_argument, 0, 'b' }, + { "block-size", required_argument, 0, 't' }, + { "num-iterations", required_argument, 0, + 'n' }, + { "mfma-mode", required_argument, 0, 'm' }, + { "verbose", no_argument, 0, 'v' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } }; + + int opt; + int option_index = 0; + while((opt = getopt_long(argc, argv, "b:t:n:m:vh", long_options, &option_index)) != + -1) + { + switch(opt) + { + case 'b': + config.batch_size = atoi(optarg); + if(config.batch_size < MIN_BATCH_SIZE) + { + fprintf(stderr, "Warning: batch_size too small, using minimum: %d\n", + MIN_BATCH_SIZE); + config.batch_size = MIN_BATCH_SIZE; + } + if(config.batch_size > MAX_BATCH_SIZE) + { + fprintf(stderr, "Warning: batch_size too large, using maximum: %d\n", + MAX_BATCH_SIZE); + config.batch_size = MAX_BATCH_SIZE; + } + break; + case 't': + config.block_size = atoi(optarg); + // Validate block size (must be power of 2, 64-256) + // NOTE: Max 256 due to fixed-size __shared__ arrays in kernel (e.g., + // vmem_lds_test_buffer[256]) + if(config.block_size < 64 || config.block_size > 256 || + (config.block_size & (config.block_size - 1)) != 0) + { + fprintf(stderr, + "Warning: invalid block_size (must be 64-256, power of 2), " + "using default: %d\n", + DEFAULT_BLOCK_SIZE); + config.block_size = DEFAULT_BLOCK_SIZE; + } + break; + case 'n': + config.num_iterations = atoi(optarg); + if(config.num_iterations < 1) + { + fprintf(stderr, + "Warning: num_iterations too small, using minimum: 1\n"); + config.num_iterations = 1; + } + if(config.num_iterations > MAX_NUM_ITERATIONS) + { + fprintf(stderr, + "Warning: num_iterations too large, using maximum: %d\n", + MAX_NUM_ITERATIONS); + config.num_iterations = MAX_NUM_ITERATIONS; + } + break; + case 'm': + if(strcmp(optarg, "asm") == 0) + { + config.mfma_mode = MFMA_MODE_ASM; + } + else if(strcmp(optarg, "builtin") == 0) + { + config.mfma_mode = MFMA_MODE_BUILTIN; + } + else if(strcmp(optarg, "both") == 0) + { + config.mfma_mode = MFMA_MODE_BOTH; + } + else + { + fprintf(stderr, "Warning: invalid mfma-mode '%s', using 'both'\n", + optarg); + config.mfma_mode = MFMA_MODE_BOTH; + } + break; + case 'v': config.verbose = true; break; + case 'h': config.help = true; break; + default: config.help = true; break; + } + } + + return config; +} + +// Macro for HIP error checking +#define HIP_CHECK(cmd) \ + do \ + { \ + hipError_t error = (cmd); \ + if(error != hipSuccess) \ + { \ + fprintf(stderr, "HIP Error: %s at %s:%d\n", hipGetErrorString(error), \ + __FILE__, __LINE__); \ + exit(EXIT_FAILURE); \ + } \ + } while(0) + +void +print_separator() +{ + printf("=============================================================================" + "===\n"); +} + +void +print_test_result(const char* test_name, int passed, int expected = 1) +{ + // Width of 48 to accommodate longest test name: "Async LDS Store + // (global_store_async_from_lds)" + if(passed < 0) + { + // Negative value indicates test was bypassed (not applicable to this arch) + printf(" %-48s : \033[33mBYPASSED\033[0m (N/A)\n", test_name); + } + else if(passed >= expected) + { + printf(" %-48s : \033[32mPASS\033[0m (%d/%d)\n", test_name, passed, expected); + } + else + { + printf(" %-48s : \033[31mFAIL\033[0m (%d/%d)\n", test_name, passed, expected); + } +} + +// Global variable to store detected architecture for later use +static char g_arch_name[64] = ""; +// 0=unknown, 1=gfx90a, 2=gfx942, 3=gfx950, 4=gfx1200, 5=gfx1201, 6=gfx115x, 7=gfx1250 +static int g_arch_type = 0; + +namespace { + +constexpr unsigned kClusterDimX = 2; +constexpr unsigned kClusterDimY = 1; +constexpr unsigned kClusterDimZ = 1; + +unsigned +round_up_grid_x_for_cluster(int num_blocks) +{ + unsigned grid_x = static_cast(num_blocks > 0 ? num_blocks : 1); + if(grid_x % kClusterDimX != 0) + { + grid_x += kClusterDimX - (grid_x % kClusterDimX); + } + return grid_x; +} + +void +launch_mega_kernel(dim3 grid, + dim3 block, + size_t shared_mem_bytes, + TestResults* d_results, + float* d_global_float, + double* d_global_double, + int* d_global_int, + float* d_input, + float* d_output, + float* d_async_lds_src, + float* d_async_lds_dst, + int* d_tdm_src, + int* d_tdm_dst, + int buffer_size, + int mfma_mode, + hipTextureObject_t tex_obj, + hipSurfaceObject_t surf_obj) +{ +#if defined(MEGA_KERNEL_HAS_CLUSTER_LAUNCH) + if(g_arch_type == 7) + { + hipLaunchAttribute cluster_attr{}; + cluster_attr.id = hipLaunchAttributeClusterDimension; + cluster_attr.value.clusterDim.x = kClusterDimX; + cluster_attr.value.clusterDim.y = kClusterDimY; + cluster_attr.value.clusterDim.z = kClusterDimZ; + + hipLaunchConfig_t launch_config{}; + launch_config.gridDim = grid; + launch_config.blockDim = block; + launch_config.dynamicSmemBytes = shared_mem_bytes; + launch_config.stream = nullptr; + launch_config.attrs = &cluster_attr; + launch_config.numAttrs = 1; + + HIP_CHECK(hipLaunchKernelEx(&launch_config, + mega_kernel, + d_results, + d_global_float, + d_global_double, + d_global_int, + d_input, + d_output, + d_async_lds_src, + d_async_lds_dst, + d_tdm_src, + d_tdm_dst, + buffer_size, + mfma_mode, + tex_obj, + surf_obj)); + return; + } +#endif + hipLaunchKernelGGL(mega_kernel, + grid, + block, + shared_mem_bytes, + 0, + d_results, + d_global_float, + d_global_double, + d_global_int, + d_input, + d_output, + d_async_lds_src, + d_async_lds_dst, + d_tdm_src, + d_tdm_dst, + buffer_size, + mfma_mode, + tex_obj, + surf_obj); + HIP_CHECK(hipGetLastError()); +} + +} // namespace + +void +print_device_info() +{ + int device; + hipDeviceProp_t props; + + HIP_CHECK(hipGetDevice(&device)); + HIP_CHECK(hipGetDeviceProperties(&props, device)); + + mega_kernel_host::detect_architecture(props.gcnArchName, g_arch_name, sizeof(g_arch_name), + &g_arch_type); + + print_separator(); + printf("AMD GPU MEGA KERNEL UNIT TEST\n"); + print_separator(); + printf("Device Information:\n"); + printf(" Name: %s\n", props.name); + printf(" GCN Architecture: %s\n", props.gcnArchName); + printf(" Compute Units: %d\n", props.multiProcessorCount); + printf(" Max Threads/Block: %d\n", props.maxThreadsPerBlock); + printf(" Warp Size: %d\n", props.warpSize); + printf(" Global Memory: %.2f GB\n", + props.totalGlobalMem / (1024.0 * 1024.0 * 1024.0)); + printf(" Shared Memory/Block: %zu KB\n", props.sharedMemPerBlock / 1024); + printf(" Clock Rate: %d MHz\n", props.clockRate / 1000); + print_separator(); + printf("Detected Platform: %s\n", + mega_kernel_host::arch_description(g_arch_type)); + print_separator(); +} + +int +main(int argc, char** argv) +{ + // Parse command-line arguments + TestConfig config = parse_args(argc, argv); + + if(config.help) + { + print_usage(argv[0]); + return 0; + } + + // Print device info + print_device_info(); + + // Configuration from command-line or defaults + const int BUFFER_SIZE = config.batch_size; + const int BLOCK_SIZE = config.block_size; + const int NUM_BLOCKS = (BUFFER_SIZE + BLOCK_SIZE - 1) / BLOCK_SIZE; + const unsigned GRID_X = + (g_arch_type == 7) ? round_up_grid_x_for_cluster(NUM_BLOCKS) + : static_cast(NUM_BLOCKS > 0 ? NUM_BLOCKS : 1); + + // Global atomic scratch: sizes and index layout — see atomic_global_buffers.h + const int GLOBAL_INT_SIZE = MEGA_KERNEL_GLOBAL_INT_ELEMENTS; + const int GLOBAL_FLOAT_SIZE = MEGA_KERNEL_GLOBAL_FLOAT_ELEMENTS; + const int GLOBAL_DOUBLE_SIZE = MEGA_KERNEL_GLOBAL_DOUBLE_ELEMENTS; + + printf("Test Configuration:\n"); + printf(" Batch/Buffer Size: %d elements\n", BUFFER_SIZE); + printf(" Block Size: %d threads\n", BLOCK_SIZE); + printf(" Number of Blocks: %d", NUM_BLOCKS); + if(g_arch_type == 7 && GRID_X != static_cast(NUM_BLOCKS)) + { + printf(" (grid x padded to %u for cluster launch)", GRID_X); + } + printf("\n"); + printf(" Total Threads: %u\n", GRID_X * static_cast(BLOCK_SIZE)); +#if defined(MEGA_KERNEL_HAS_CLUSTER_LAUNCH) + if(g_arch_type == 7) + { + printf(" Cluster Launch: enabled (%u,%u,%u)\n", kClusterDimX, kClusterDimY, + kClusterDimZ); + } +#endif + const char* mfma_mode_str = (config.mfma_mode == MFMA_MODE_ASM) ? "asm" + : (config.mfma_mode == MFMA_MODE_BUILTIN) ? "builtin" + : "both"; + printf(" MFMA Test Mode: %s\n", mfma_mode_str); + printf(" Num Iterations: %d\n", config.num_iterations); + if(config.verbose) + { + printf(" Verbose Mode: enabled\n"); + } + print_separator(); + + // Allocate host memory + TestResults h_results; + memset(&h_results, 0, sizeof(TestResults)); + + float* h_input = (float*) malloc(BUFFER_SIZE * sizeof(float)); + float* h_output = (float*) malloc(BUFFER_SIZE * sizeof(float)); + + // Initialize input buffer + for(int i = 0; i < BUFFER_SIZE; i++) + { + h_input[i] = (float) i; + h_output[i] = 0.0f; + } + + // Allocate device memory + TestResults* d_results; + float* d_global_float; + double* d_global_double; + int* d_global_int; + float* d_input; + float* d_output; + float* d_async_lds_src; + float* d_async_lds_dst; + int* d_tdm_src; + int* d_tdm_dst; + + const int ASYNC_LDS_SIZE = 256; // Size for async LDS test buffers + const int TDM_SIZE = 256; // Size for TDM test buffers (16x16 tensor) + + HIP_CHECK(hipMalloc(&d_results, sizeof(TestResults))); + HIP_CHECK(hipMalloc(&d_global_float, GLOBAL_FLOAT_SIZE * sizeof(float))); + HIP_CHECK(hipMalloc(&d_global_double, GLOBAL_DOUBLE_SIZE * sizeof(double))); + HIP_CHECK(hipMalloc(&d_global_int, GLOBAL_INT_SIZE * sizeof(int))); + HIP_CHECK(hipMalloc(&d_input, BUFFER_SIZE * sizeof(float))); + HIP_CHECK(hipMalloc(&d_output, BUFFER_SIZE * sizeof(float))); + HIP_CHECK(hipMalloc(&d_async_lds_src, ASYNC_LDS_SIZE * sizeof(float))); + HIP_CHECK(hipMalloc(&d_async_lds_dst, ASYNC_LDS_SIZE * sizeof(float))); + HIP_CHECK(hipMalloc(&d_tdm_src, TDM_SIZE * sizeof(int))); + HIP_CHECK(hipMalloc(&d_tdm_dst, TDM_SIZE * sizeof(int))); + + // Host-prepared zeros for global atomic buffers (kernel CAS etc. expect initial 0). + std::vector h_global_int(static_cast(GLOBAL_INT_SIZE), 0); + std::vector h_global_float(static_cast(GLOBAL_FLOAT_SIZE), 0.0f); + std::vector h_global_double(static_cast(GLOBAL_DOUBLE_SIZE), 0.0); + + // Initialize device memory + HIP_CHECK(hipMemset(d_results, 0, sizeof(TestResults))); + HIP_CHECK(hipMemcpy(d_global_int, h_global_int.data(), + h_global_int.size() * sizeof(int), hipMemcpyHostToDevice)); + HIP_CHECK(hipMemcpy(d_global_float, h_global_float.data(), + h_global_float.size() * sizeof(float), hipMemcpyHostToDevice)); + HIP_CHECK(hipMemcpy(d_global_double, h_global_double.data(), + h_global_double.size() * sizeof(double), hipMemcpyHostToDevice)); + HIP_CHECK( + hipMemcpy(d_input, h_input, BUFFER_SIZE * sizeof(float), hipMemcpyHostToDevice)); + HIP_CHECK(hipMemset(d_output, 0, BUFFER_SIZE * sizeof(float))); + HIP_CHECK(hipMemset(d_async_lds_src, 0, ASYNC_LDS_SIZE * sizeof(float))); + HIP_CHECK(hipMemset(d_async_lds_dst, 0, ASYNC_LDS_SIZE * sizeof(float))); + HIP_CHECK(hipMemset(d_tdm_src, 0, TDM_SIZE * sizeof(int))); + HIP_CHECK(hipMemset(d_tdm_dst, 0, TDM_SIZE * sizeof(int))); + + // Create texture and surface objects for gfx115x to exercise + // INSTS_TEX_LOAD (tex1Dfetch) and INSTS_TEX_STORE (surf1Dwrite). + hipTextureObject_t tex_obj = 0; + hipSurfaceObject_t surf_obj = 0; + float* d_surf_buffer = nullptr; + if(g_arch_type == 6) + { + hipResourceDesc resDesc; + memset(&resDesc, 0, sizeof(resDesc)); + resDesc.resType = hipResourceTypeLinear; + resDesc.res.linear.sizeInBytes = BUFFER_SIZE * sizeof(float); + resDesc.res.linear.desc = + hipCreateChannelDesc(32, 0, 0, 0, hipChannelFormatKindFloat); + + resDesc.res.linear.devPtr = d_input; + hipTextureDesc texDesc; + memset(&texDesc, 0, sizeof(texDesc)); + texDesc.normalizedCoords = 0; + texDesc.filterMode = hipFilterModePoint; + texDesc.addressMode[0] = hipAddressModeClamp; + HIP_CHECK(hipCreateTextureObject(&tex_obj, &resDesc, &texDesc, nullptr)); + + HIP_CHECK(hipMalloc(&d_surf_buffer, BUFFER_SIZE * sizeof(float))); + HIP_CHECK(hipMemset(d_surf_buffer, 0, BUFFER_SIZE * sizeof(float))); + resDesc.res.linear.devPtr = d_surf_buffer; + HIP_CHECK(hipCreateSurfaceObject(&surf_obj, &resDesc)); + } + + printf("Running GPU Mega Kernel for %s...\n", + mega_kernel_host::arch_description(g_arch_type)); + if(config.num_iterations > 1) + { + printf(" Running %d iterations...\n", config.num_iterations); + } + print_separator(); + + // Create events for timing + hipEvent_t start, stop; + HIP_CHECK(hipEventCreate(&start)); + HIP_CHECK(hipEventCreate(&stop)); + + // Compute dynamic shared memory size (must match kernel expectations) + // Layout in test_lds_operations: + // - lds_float: BLOCK_SIZE * sizeof(float) + // - lds_int: BLOCK_SIZE * sizeof(int) + // - lds_double: (BLOCK_SIZE / 2) * sizeof(double) + // - lds_atomic_counter: sizeof(int) for LDS atomic test + const size_t sharedMemBytes = + static_cast(BLOCK_SIZE) * sizeof(float) + // lds_float + static_cast(BLOCK_SIZE) * sizeof(int) + // lds_int + static_cast(BLOCK_SIZE / 2) * sizeof(double) + // lds_double + sizeof(int); // lds_atomic_counter + + // Record start time + HIP_CHECK(hipEventRecord(start, 0)); + + // Run kernel for the specified number of iterations + for(int iter = 0; iter < config.num_iterations; iter++) + { + // Reset device memory for each iteration (except first) + // NOTE: d_input must also be reset because test_vmem_operations modifies it + if(iter > 0) + { + HIP_CHECK(hipMemset(d_results, 0, sizeof(TestResults))); + HIP_CHECK(hipMemcpy(d_global_int, h_global_int.data(), + h_global_int.size() * sizeof(int), + hipMemcpyHostToDevice)); + HIP_CHECK(hipMemcpy(d_global_float, h_global_float.data(), + h_global_float.size() * sizeof(float), + hipMemcpyHostToDevice)); + HIP_CHECK(hipMemcpy(d_global_double, h_global_double.data(), + h_global_double.size() * sizeof(double), + hipMemcpyHostToDevice)); + HIP_CHECK(hipMemcpy(d_input, h_input, BUFFER_SIZE * sizeof(float), + hipMemcpyHostToDevice)); + HIP_CHECK(hipMemset(d_output, 0, BUFFER_SIZE * sizeof(float))); + } + + // Launch kernel (gfx1250 uses cluster dispatch when HIP 7.2+ is available) + launch_mega_kernel(dim3(GRID_X, 1, 1), + dim3(BLOCK_SIZE), + sharedMemBytes, + d_results, + d_global_float, + d_global_double, + d_global_int, + d_input, + d_output, + d_async_lds_src, + d_async_lds_dst, + d_tdm_src, + d_tdm_dst, + BUFFER_SIZE, + (int) config.mfma_mode, + tex_obj, + surf_obj); + } + + // Wait for all iterations to complete + HIP_CHECK(hipDeviceSynchronize()); + + // Record stop time + HIP_CHECK(hipEventRecord(stop, 0)); + HIP_CHECK(hipEventSynchronize(stop)); + + float total_elapsed_ms; + HIP_CHECK(hipEventElapsedTime(&total_elapsed_ms, start, stop)); + float avg_elapsed_ms = total_elapsed_ms / config.num_iterations; + + if(tex_obj != 0) + { + HIP_CHECK(hipDestroyTextureObject(tex_obj)); + } + if(surf_obj != 0) + { + HIP_CHECK(hipDestroySurfaceObject(surf_obj)); + } + if(d_surf_buffer != nullptr) + { + HIP_CHECK(hipFree(d_surf_buffer)); + } + + // Copy results back + HIP_CHECK( + hipMemcpy(&h_results, d_results, sizeof(TestResults), hipMemcpyDeviceToHost)); + HIP_CHECK(hipMemcpy(h_output, d_output, BUFFER_SIZE * sizeof(float), + hipMemcpyDeviceToHost)); + + // Verify memory operations output + bool memory_verified = true; + for(int i = 0; i < BUFFER_SIZE && memory_verified; i++) + { + float expected = h_input[i] * 2.0f + 1.0f; + if(fabsf(h_output[i] - expected) > 0.001f) + { + memory_verified = false; + printf("Memory verification failed at index %d: expected %.3f, got %.3f\n", i, + expected, h_output[i]); + } + } + + // Print results + printf("TEST RESULTS\n"); + print_separator(); + + printf("\n[Category: Warp/Wave Operations]\n"); + print_test_result("Warp Shuffle (shfl/shfl_xor/shfl_up/down)", + h_results.warp_shuffle_passed); + print_test_result("Warp Ballot (__ballot/__any/__all)", h_results.warp_ballot_passed); + print_test_result("Warp Permute (ds_bpermute/ds_permute)", + h_results.warp_permute_passed); + print_test_result("Warp Reduce (shuffle-based reduction)", + h_results.warp_reduce_passed); + + printf("\n[Category: FP8/BF8 Conversions (gfx942+/RDNA4/gfx1250)]\n"); + print_test_result("FP8 E4M3 Conversion", h_results.fp8_convert_passed); + print_test_result("FP8 E5M3 Conversion (gfx1250)", h_results.fp8_e5m3_convert_passed); + print_test_result("BF8 E5M2 Conversion", h_results.bf8_convert_passed); + + printf("\n[Category: FP16/BF16 Operations]\n"); + print_test_result("FP16 Arithmetic", h_results.fp16_convert_passed); + print_test_result("BF16 Conversion", h_results.bf16_convert_passed); + + printf("\n[Category: FP32/FP64 Arithmetic]\n"); + print_test_result("FP32 Arithmetic (add/sub/mul/div/fma)", + h_results.fp32_arith_passed); + print_test_result("FP64 Arithmetic (add/sub/mul/div/fma)", + h_results.fp64_arith_passed); + + printf("\n[Category: Integer Operations]\n"); + print_test_result("INT32 Arithmetic & Bit Ops", h_results.int32_arith_passed); + print_test_result("INT64 Arithmetic", h_results.int64_arith_passed); + + printf("\n[Category: Packed Operations]\n"); + print_test_result("Packed FP16 (half2)", h_results.pk_f16_passed); + print_test_result("Packed BF16", h_results.pk_bf16_passed); + print_test_result("Packed Add Operations", h_results.pk_add_passed); + + printf("\n[Category: Atomic Operations]\n"); + print_test_result("Atomic Add FP32", h_results.atomic_add_f32_passed); + print_test_result("Atomic Add FP64 (HW atomic on CDNA)", + h_results.atomic_add_f64_passed); + print_test_result("Atomic Min FP32", h_results.atomic_min_f32_passed); + print_test_result("Atomic Max FP32", h_results.atomic_max_f32_passed); + print_test_result("Atomic Integer Operations", h_results.atomic_int_passed); + print_test_result("LDS Atomic Operations", h_results.lds_atomic_passed); + + printf("\n[Category: Transcendental Functions]\n"); + print_test_result("Sine (sinf)", h_results.trans_sin_passed); + print_test_result("Cosine (cosf)", h_results.trans_cos_passed); + print_test_result("Exponential (expf)", h_results.trans_exp_passed); + print_test_result("Logarithm (logf)", h_results.trans_log_passed); + print_test_result("Square Root (sqrtf)", h_results.trans_sqrt_passed); + print_test_result("Reciprocal (1/x)", h_results.trans_rcp_passed); + print_test_result("Reciprocal Square Root (rsqrtf)", h_results.trans_rsq_passed); + + printf("\n[Category: Memory Operations]\n"); + print_test_result("Global Memory Load", h_results.global_load_passed); + print_test_result("Global Memory Store", h_results.global_store_passed); + print_test_result("LDS Load", h_results.lds_load_passed); + print_test_result("LDS Store", h_results.lds_store_passed); + print_test_result("Memory Output Verification", memory_verified ? 1 : 0); + + printf("\n[Category: DOT Product Operations]\n"); + print_test_result("DOT4 (4-element INT8 dot)", h_results.dot4_passed); + print_test_result("DOT8 (8-element INT4 dot)", h_results.dot8_passed); + + printf("\n[Category: MFMA Operations (gfx90a+/CDNA)]\n"); + print_test_result("MFMA FP32 (v_mfma_f32_32x32x2f32)", h_results.mfma_f32_passed); + print_test_result("MFMA FP64 (v_mfma_f64_16x16x4f64)", h_results.mfma_f64_passed); + print_test_result("MFMA FP16 (v_mfma_f32_32x32x8f16)", h_results.mfma_f16_passed); + print_test_result("MFMA BF16 (v_mfma_f32_32x32x*bf16)", h_results.mfma_bf16_passed); + print_test_result("MFMA INT8 (v_mfma_i32_32x32x*i8)", h_results.mfma_i8_passed); + print_test_result("MFMA FP8 (v_mfma_f32_32x32x16_fp8)", h_results.mfma_f8_passed); + + printf("\n[Category: Dual-Issue VALU (instruction patterns only)]\n"); + print_test_result("Dual-Issue Patterns (not scheduler-verified)", + h_results.dual_issue_passed); + + printf("\n[Category: Lane/Thread Information]\n"); + print_test_result("Lane ID Builtin", h_results.lane_id_passed); + print_test_result("Wavefront Size", h_results.wavefront_passed); + + printf("\n[Category: Async LDS Operations (gfx950+/RDNA4)]\n"); + print_test_result("Async LDS Load (global_load_async_to_lds)", + h_results.async_lds_load_passed); + print_test_result("Async LDS Store (global_store_async_from_lds)", + h_results.async_lds_store_passed); + + printf("\n[Category: TDM (Tensor Data Mover) Operations (gfx1250 only)]\n"); + print_test_result("TDM Tensor Load to LDS", h_results.tdm_load_passed); + print_test_result("TDM Tensor Store from LDS", h_results.tdm_store_passed); + + printf("\n[Category: Cluster Group Operations (gfx1250 only)]\n"); + print_test_result("Cluster Barrier (s_barrier_signal/wait)", + h_results.cluster_barrier_passed); + print_test_result("Cluster Workgroup Info", h_results.cluster_info_passed); + print_test_result("Cluster Load (cluster_load_b32)", h_results.cluster_load_passed); + + printf("\n[Category: WMMA Operations (RDNA4/gfx12/gfx1250 - Wave32)]\n"); + print_test_result("WMMA FP16 → FP32 (wmma_f32_16x16x16_f16)", + h_results.wmma_f16_passed); + print_test_result("WMMA BF16 → FP32 (wmma_f32_16x16x16_bf16)", + h_results.wmma_bf16_passed); + print_test_result("WMMA INT8 → INT32 (wmma_i32_16x16x16_iu8)", + h_results.wmma_i8_passed); + + printf("\n[Category: Cooperative Atomic Operations (gfx1250 only)]\n"); + print_test_result("Cooperative Atomic Load (32x4B/16x8B/8x16B)", + h_results.coop_atomic_load_passed); + print_test_result("Cooperative Atomic Store (32x4B/16x8B/8x16B)", + h_results.coop_atomic_store_passed); + + printf("\n[Category: VMEM (Vector Memory) Operations - Inline ASM]\n"); + print_test_result("Flat Load/Store (flat_load_b32/flat_store_b32)", + h_results.vmem_flat_passed); + print_test_result("Global Load/Store (global_load_b32/global_store_b32)", + h_results.vmem_global_passed); + print_test_result("Buffer Load/Store (buffer_load_dword)", + h_results.vmem_buffer_passed); + print_test_result("Scratch Memory (scratch_load/scratch_store)", + h_results.vmem_scratch_passed); + print_test_result("LDS Operations (ds_read_b32/ds_write_b32)", + h_results.vmem_lds_passed); + print_test_result("Texture Load (tex1Dfetch / INSTS_TEX_LOAD)", + h_results.vmem_tex_load_passed); + print_test_result("Texture Store (surf1Dwrite / INSTS_TEX_STORE)", + h_results.vmem_tex_store_passed); + + printf("\n[Category: Atomic Operations - Inline ASM]\n"); + print_test_result("Global INT Atomics (global_atomic_add/sub/min/max)", + h_results.atomic_global_int_passed); + print_test_result("Global FP32 Atomics (global_atomic_add_f32)", + h_results.atomic_global_f32_passed); + print_test_result("Global FP64 Atomics (global_atomic_add_f64/min/max)", + h_results.atomic_global_f64_passed); + print_test_result("Flat INT Atomics (flat_atomic_add)", + h_results.atomic_flat_int_passed); + print_test_result("DS INT Atomics (ds_add_u32/min_i32/max_u32)", + h_results.atomic_ds_int_passed); + print_test_result("DS FP32 Atomics (ds_add_f32/min_f32)", + h_results.atomic_ds_f32_passed); + print_test_result("DS FP64 Atomics (ds_add_f64)", h_results.atomic_ds_f64_passed); + print_test_result("Packed FP16 Atomics (global_atomic_pk_add_f16)", + h_results.atomic_pk_f16_passed); + print_test_result("Packed BF16 Atomics (global_atomic_pk_add_bf16)", + h_results.atomic_pk_bf16_passed); + print_test_result("CAS Atomics (global_atomic_cmpswap)", h_results.atomic_cas_passed); + print_test_result("Buffer Load (buffer_load_b32, gfx1250)", h_results.buffer_load_passed); + print_test_result("Buffer Store (buffer_store_b32, gfx1250)", + h_results.buffer_store_passed); + print_test_result("Multicast Load (broadcast read, all threads same addr)", + h_results.multicast_load_passed); + + print_separator(); + + // Summary - count passed, failed, and bypassed tests + int passed_categories = 0; + int bypassed_categories = 0; + int failed_categories = 0; + +// Helper macro to count test results +// > 0 = passed, < 0 = bypassed, == 0 = failed +#define COUNT_TEST(result) \ + if((result) > 0) \ + passed_categories++; \ + else if((result) < 0) \ + bypassed_categories++; \ + else \ + failed_categories++; + + COUNT_TEST(h_results.warp_shuffle_passed); + COUNT_TEST(h_results.warp_ballot_passed); + COUNT_TEST(h_results.warp_permute_passed); + COUNT_TEST(h_results.warp_reduce_passed); + COUNT_TEST(h_results.fp8_convert_passed); + COUNT_TEST(h_results.fp8_e5m3_convert_passed); + COUNT_TEST(h_results.bf8_convert_passed); + COUNT_TEST(h_results.fp16_convert_passed); + COUNT_TEST(h_results.bf16_convert_passed); + COUNT_TEST(h_results.fp32_arith_passed); + COUNT_TEST(h_results.fp64_arith_passed); + COUNT_TEST(h_results.int32_arith_passed); + COUNT_TEST(h_results.int64_arith_passed); + COUNT_TEST(h_results.pk_f16_passed); + COUNT_TEST(h_results.pk_bf16_passed); + COUNT_TEST(h_results.pk_add_passed); + COUNT_TEST(h_results.atomic_add_f32_passed); + COUNT_TEST(h_results.atomic_add_f64_passed); + COUNT_TEST(h_results.atomic_min_f32_passed); + COUNT_TEST(h_results.atomic_max_f32_passed); + COUNT_TEST(h_results.atomic_int_passed); + COUNT_TEST(h_results.lds_atomic_passed); + COUNT_TEST(h_results.trans_sin_passed); + COUNT_TEST(h_results.trans_cos_passed); + COUNT_TEST(h_results.trans_exp_passed); + COUNT_TEST(h_results.trans_log_passed); + COUNT_TEST(h_results.trans_sqrt_passed); + COUNT_TEST(h_results.trans_rcp_passed); + COUNT_TEST(h_results.trans_rsq_passed); + COUNT_TEST(h_results.global_load_passed); + COUNT_TEST(h_results.global_store_passed); + COUNT_TEST(h_results.lds_load_passed); + COUNT_TEST(h_results.lds_store_passed); + COUNT_TEST(h_results.dot4_passed); + COUNT_TEST(h_results.dot8_passed); + COUNT_TEST(h_results.mfma_f32_passed); + COUNT_TEST(h_results.mfma_f64_passed); + COUNT_TEST(h_results.mfma_f16_passed); + COUNT_TEST(h_results.mfma_bf16_passed); + COUNT_TEST(h_results.mfma_i8_passed); + COUNT_TEST(h_results.mfma_f8_passed); + COUNT_TEST(h_results.dual_issue_passed); + COUNT_TEST(h_results.lane_id_passed); + COUNT_TEST(h_results.wavefront_passed); + COUNT_TEST(h_results.async_lds_load_passed); + COUNT_TEST(h_results.async_lds_store_passed); + COUNT_TEST(h_results.tdm_load_passed); + COUNT_TEST(h_results.tdm_store_passed); + COUNT_TEST(h_results.cluster_barrier_passed); + COUNT_TEST(h_results.cluster_info_passed); + COUNT_TEST(h_results.cluster_load_passed); + COUNT_TEST(h_results.wmma_f16_passed); + COUNT_TEST(h_results.wmma_bf16_passed); + COUNT_TEST(h_results.wmma_i8_passed); + COUNT_TEST(h_results.coop_atomic_load_passed); + COUNT_TEST(h_results.coop_atomic_store_passed); + COUNT_TEST(h_results.vmem_flat_passed); + COUNT_TEST(h_results.vmem_global_passed); + COUNT_TEST(h_results.vmem_buffer_passed); + COUNT_TEST(h_results.vmem_scratch_passed); + COUNT_TEST(h_results.vmem_lds_passed); + COUNT_TEST(h_results.vmem_tex_load_passed); + COUNT_TEST(h_results.vmem_tex_store_passed); + COUNT_TEST(h_results.atomic_global_int_passed); + COUNT_TEST(h_results.atomic_global_f32_passed); + COUNT_TEST(h_results.atomic_global_f64_passed); + COUNT_TEST(h_results.atomic_flat_int_passed); + COUNT_TEST(h_results.atomic_ds_int_passed); + COUNT_TEST(h_results.atomic_ds_f32_passed); + COUNT_TEST(h_results.atomic_ds_f64_passed); + COUNT_TEST(h_results.atomic_pk_f16_passed); + COUNT_TEST(h_results.atomic_pk_bf16_passed); + COUNT_TEST(h_results.atomic_cas_passed); + COUNT_TEST(h_results.buffer_load_passed); + COUNT_TEST(h_results.buffer_store_passed); + COUNT_TEST(h_results.multicast_load_passed); + if(memory_verified) + passed_categories++; + else + failed_categories++; + +#undef COUNT_TEST + + int total_tests = + passed_categories + failed_categories; // Exclude bypassed from total + + printf("SUMMARY\n"); + print_separator(); + printf("Test Categories Passed: %d / %d\n", passed_categories, total_tests); + printf("Test Categories Failed: %d\n", failed_categories); + printf("Test Categories Bypassed: %d (N/A for this architecture)\n", + bypassed_categories); + printf("Kernel Iterations: %d\n", config.num_iterations); + printf("Total Execution Time: %.3f ms\n", total_elapsed_ms); + if(config.num_iterations > 1) + { + printf("Average Time/Iteration: %.3f ms\n", avg_elapsed_ms); + } + + if(failed_categories == 0) + { + printf("\n\033[32m*** OVERALL: PASSED (%d/%d, %d bypassed) ***\033[0m\n", + passed_categories, total_tests, bypassed_categories); + } + else + { + printf( + "\n\033[31m*** OVERALL: FAILED (%d/%d, %d failed, %d bypassed) ***\033[0m\n", + passed_categories, total_tests, failed_categories, bypassed_categories); + } + print_separator(); + + // Print architecture-specific feature summary + printf("\n%s SPECIFIC FEATURES TESTED:\n", + mega_kernel_host::arch_description(g_arch_type)); + + switch(g_arch_type) + { + case 1: // MI250/CDNA2 + printf(" - FP32/FP64/FP16/BF16 arithmetic\n"); + printf(" - Hardware FP64 atomics (add/min/max)\n"); + printf(" - DOT4/DOT8 integer dot products (v_dot4_u32_u8)\n"); + printf(" - MFMA (Matrix Fused Multiply-Add) instructions\n"); + printf(" - Wave64 operations\n"); + printf(" - LDS operations (32KB per CU)\n"); + break; + + case 2: // MI300/CDNA3 + printf(" - All MI250 features plus:\n"); + printf(" - FP8/BF8 conversions (FNUZ format)\n"); + printf(" - TF32 (TensorFloat-32) support\n"); + printf(" - Sparse MFMA instructions\n"); + printf(" - Same-wave dual-issue via VOPD/VOPD3 encoding\n"); + printf(" - Two operations per instruction (X op + Y op)\n"); + printf(" - Enhanced Wave64 operations\n"); + break; + + case 3: // MI350/CDNA4 + printf(" - All MI300 features plus:\n"); + printf(" - FP8/BF8 scaled conversions (OCP format)\n"); + printf(" - FP6/FP4 conversions\n"); + printf(" - Cross-wave dual-issue VALU (in addition to VOPD)\n"); + printf(" - 2 instructions/cycle from different waves\n"); + printf(" - Requires Wave32, single-cycle ops, different MACC units\n"); + printf(" - Async LDS (global_load_async_to_lds, " + "global_store_async_from_lds)\n"); + printf(" - Wave32 optimized operations\n"); + break; + + case 4: // RX 9060/RDNA4 + case 5: // RX 9070 XT/RDNA4 + printf(" - FP8/BF8 conversions (OCP format)\n"); + printf(" - FP16/BF16 optimized arithmetic\n"); + printf(" - WMMA (Wave Matrix Multiply Accumulate) instructions\n"); + printf(" - 16x16x16 matrix operations on Wave32\n"); + printf(" - FP16/BF16 → FP32, INT8 → INT32\n"); + printf(" - 2x throughput vs RDNA3 (1024 FLOPS/clock for FP16/BF16)\n"); + printf(" - Async LDS operations\n"); + printf(" - Wave32 native execution\n"); + printf(" - WGP (Workgroup Processor) architecture\n"); + printf(" - Limited FP64 support (consumer GPU)\n"); + break; + + case 6: // Strix/Strix Halo (RDNA 3.5) + printf(" - RDNA 3.5 APU iGPU: Strix Point (gfx1150) / Strix Halo (gfx1151) / " + "Krackan (gfx1152) / Gorgon Point (gfx1153)\n"); + printf(" - Wave32 native, WMMA (FP16/BF16/INT8) 16x16x16\n"); + printf(" - VOPD same-wave dual-issue VALU\n"); + printf(" - No CDNA-style FP8/BF8 cvt builtins; scalar FP; s_singleuse_vdst\n"); + printf(" - Async LDS, WGP architecture\n"); + break; + + case 7: // gfx1250 + printf(" - FP8/BF8 OCP format conversions (E4M3 and E5M3)\n"); + printf(" - WMMA matrix operations (16x16x32 / 16x16x64, gfx1250 builtins)\n"); + printf(" - TDM (Tensor Data Mover) load/store\n"); + printf(" - Cluster group barriers and workgroup info\n"); + printf(" - Cooperative atomic load/store\n"); + printf(" - Async LDS with split wait instructions\n"); + printf(" - Hardware FP64 atomics\n"); + printf(" - Buffer descriptor load/store\n"); + break; + + default: + printf(" - Basic GPU operations\n"); + printf(" - FP32/FP64 arithmetic\n"); + printf(" - Integer operations\n"); + printf(" - LDS operations\n"); + break; + } + print_separator(); + + // Cleanup + HIP_CHECK(hipEventDestroy(start)); + HIP_CHECK(hipEventDestroy(stop)); + HIP_CHECK(hipFree(d_results)); + HIP_CHECK(hipFree(d_global_float)); + HIP_CHECK(hipFree(d_global_double)); + HIP_CHECK(hipFree(d_global_int)); + HIP_CHECK(hipFree(d_input)); + HIP_CHECK(hipFree(d_output)); + HIP_CHECK(hipFree(d_async_lds_src)); + HIP_CHECK(hipFree(d_async_lds_dst)); + HIP_CHECK(hipFree(d_tdm_src)); + HIP_CHECK(hipFree(d_tdm_dst)); + free(h_input); + free(h_output); + + return (failed_categories == 0) ? 0 : 1; +} diff --git a/skills/rocprof-compute-sample/mega_kernel/mega_kernel.hip b/skills/rocprof-compute-sample/mega_kernel/mega_kernel.hip new file mode 100644 index 0000000..7264153 --- /dev/null +++ b/skills/rocprof-compute-sample/mega_kernel/mega_kernel.hip @@ -0,0 +1,3865 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/** + * GPU Mega Kernel Unit Test + * + * This file contains a comprehensive unit test kernel that exercises + * most of the instructions/features available on AMD Instinct GPUs. + * + * SUPPORTED ARCHITECTURES: + * ======================== + * - MI250/MI250X (CDNA2, gfx90a): + * - FP32/FP64/FP16/BF16, INT32 operations + * - Hardware FP64 atomics (add, min, max) + * - MFMA matrix operations (FP16/BF16/INT8) + * - Unified s_waitcnt instruction + * - NO FP8/BF8, NO async LDS, NO TDM + * + * - MI300/MI300X/MI325X (CDNA3, gfx942): + * - All MI250 features + * - FP8/BF8 conversions (cvt_pk_fp8/bf8) + * - TF32 MFMA, Packed FP32 + * - Sparse MFMA (FP16/BF16/FP8/INT8) + * - Async LDS (global_load_async_to_lds - if available) + * - Unified s_waitcnt instruction + * - NO FP6/FP4, NO TDM + * + * - MI350X/MI355X (CDNA4, gfx950): + * - All MI300 features + * - FP6/FP4 conversions and MFMA + * - Scaled FP8 conversions + * - Async LDS operations + * - Unified s_waitcnt instruction + * + * - gfx1250: + * - FP8/BF8 OCP format conversions + * - WMMA matrix operations (FP16/BF16/INT8) - NOT backwards compatible with previous WMMA + * - TDM (Tensor Data Mover) operations + * - Async LDS with DS atomic barriers + * - Cooperative atomic operations + * - Cluster group operations + * - Split wait instructions (s_waitcnt REMOVED) + * - NEW: s_wait_loadcnt, s_wait_storecnt, s_wait_kmcnt + * - NEW: s_wait_asynccnt, s_wait_tensorcnt + * - Hardware FP64 atomics (add, min, max) + * + * - Strix / Strix Halo / Krackan (RDNA 3.5 gfx115x) APU iGPU: + * - Wave32 native, WMMA (FP16/BF16/INT8), VOPD same-wave dual-issue VALU + * - FP8/BF8 support; scalar FP in scalar unit; s_singleuse_vdst hint + * - Async LDS, WGP architecture; unified s_waitcnt; NO TDM, NO FP64 atomics + * + * - RDNA4 Consumer (gfx1200/gfx1201 - RX 9060/9070 variants): + * - FP8/BF8 OCP format conversions + * - WMMA matrix operations (FP16/BF16/INT8) + * - Async LDS operations + * - AI inference accelerators + * - Unified s_waitcnt instruction (still available) + * - NO TDM, NO hardware FP64 atomics + * + * KEY ARCHITECTURAL DIFFERENCES: + * ============================== + * + * Wait Instructions: + * ------------------ + * | Architecture | s_waitcnt | Split Waits | Async/Tensor Waits | + * |--------------|-----------|-------------|-------------------| + * | MI250-350 | ✅ Unified | ❌ | ❌ | + * | gfx1200/1201 | ✅ Unified | ❌ | ❌ | + * | gfx1250 | ❌ REMOVED | ✅ Required | ✅ Required | + * + * Matrix Operations: + * ------------------ + * | Architecture | MFMA (CDNA2-4) | WMMA (RDNA4) | WMMA (gfx1250) | + * |--------------|----------------|--------------|--------------| + * | MI250-350 | ✅ | ❌ | ❌ | + * | gfx1200/1201 | ❌ | ✅ | ❌ | + * | gfx1250 | ❌ | ❌ | ✅ | + * + * Note: gfx1250 (gfx1250) uses WMMA instructions similar to RDNA4, + * but with different intrinsic signatures and larger K dimensions: + * - RDNA4: __builtin_amdgcn_wmma_*_16x16x16_*_w32_gfx12 (K=16) + * - gfx1250: __builtin_amdgcn_wmma_*_16x16x32_* / _16x16x64_* (K=32/64) + * + * IMPORTANT: gfx1250 WMMA builtins are NOT backwards compatible with + * previous generation WMMA builtins (gfx1150/gfx1200/gfx1201). Code + * using WMMA intrinsics must be recompiled with gfx1250-specific builtins. + * + * Advanced Features: + * ------------------ + * | Feature | MI250-350 | gfx1200/1201 | gfx1250 | + * |----------------------|-----------|--------------|---------| + * | TDM | ❌ | ❌ | ✅ | + * | Cooperative Atomics | ❌ | ❌ | ✅ | + * | Cluster Operations | ❌ | ❌ | ✅ | + * | DS Atomic Barriers | ❌ | ❌ | ✅ | + * + * Features tested: + * - Warp/Wave operations (shuffle, ballot, permute) + * - FP8/BF8 conversions (gfx942+, scaled on gfx950, OCP on gfx1250) + * - FP16/BF16 packed operations + * - FP32/FP64 operations + * - Atomic operations (global, LDS, flat, cooperative) + * - LDS operations (sync and async) + * - Memory operations (VMEM comprehensive tests) + * - Transcendental functions + * - Integer operations + * - DOT product operations + * - MFMA operations (CDNA) / WMMA operations (RDNA4 and gfx1250) + * - TDM operations (gfx1250 only) + * - Cluster group operations (gfx1250 only) + * + * Copyright (c) 2026 - For testing purposes + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mega_kernel_types.h" + +// ============================================================================ +// NPI TOOLCHAIN WORKAROUND: gfx1250 fallback detection +// ============================================================================ +// Some patched ROCm builds (+PATCHED) may not auto-define __gfx1250__ when +// targeting gfx1250. Use s_wait_asynccnt builtin (gfx1250-specific for async +// copy operations) as the detection heuristic. +// If this still fails, compile with: hipcc ... -D__gfx1250__ +// ============================================================================ +#if !defined(__gfx1250__) && defined(__amdgcn__) && \ + __has_builtin(__builtin_amdgcn_s_wait_asynccnt) +# define __gfx1250__ 1 +#endif + +// ============================================================================ +// Architecture Detection Macros +// ============================================================================ + +// MI250/MI200 series (CDNA2) +#if defined(__gfx90a__) +#define ARCH_MI250 1 +#define ARCH_NAME "MI250 (gfx90a/CDNA2)" +#define HAS_HW_FP64_ATOMICS 1 +#define HAS_FP8 0 +#define HAS_ASYNC_LDS 0 +#define HAS_TDM 0 +#endif + +// MI300 series (CDNA3) +#if defined(__gfx942__) +#define ARCH_MI300 1 +#define ARCH_NAME "MI300 (gfx942/CDNA3)" +#define HAS_HW_FP64_ATOMICS 1 +#define HAS_FP8 1 +#define HAS_ASYNC_LDS 0 // May have limited support +#define HAS_TDM 0 +#endif + +// MI350 series (CDNA4) +#if defined(__gfx950__) +#define ARCH_MI350 1 +#define ARCH_NAME "MI350 (gfx950/CDNA4)" +#define HAS_HW_FP64_ATOMICS 1 +#define HAS_FP8 1 +#define HAS_FP6_FP4 1 +#define HAS_ASYNC_LDS 1 +#define HAS_TDM 0 // TDM is gfx1250 only +#endif + +// Strix Point / Strix Halo / Krackan Point (RDNA 3.5, gfx1150/gfx1151/gfx1152) +#if defined(__gfx1150__) || defined(__gfx1151__) || defined(__gfx1152__) +#define ARCH_RDNA35_APU 1 +#define ARCH_NAME "RDNA 3.5 APU (gfx1150/gfx1151/gfx1152 - Strix/Krackan)" +#define HAS_HW_FP64_ATOMICS 0 // APU iGPUs have limited FP64 +#define HAS_FP8 0 // NO FP8 conversion builtins on RDNA 3.5 APU +#define HAS_ASYNC_LDS 1 // Async LDS supported +#define HAS_TDM 0 // TDM is gfx1250 only +#define HAS_AI_ACCELERATORS 0 // Limited AI on APU +#endif + +// RDNA4 consumer (gfx1200/gfx1201) - RX 9070 XT and similar +#if defined(__gfx1200__) || defined(__gfx1201__) +#define ARCH_RDNA4_CONSUMER 1 +#define ARCH_NAME "RDNA4 Consumer (gfx1200/gfx1201 - RX 9060/9070)" +#define HAS_HW_FP64_ATOMICS 0 // Consumer GPUs have limited FP64 +#define HAS_FP8 1 // FP8 OCP format supported +#define HAS_ASYNC_LDS 1 // Async LDS supported +#define HAS_TDM 0 // TDM is gfx1250 only +#define HAS_AI_ACCELERATORS 1 // AI accelerators for inference +#endif + +// gfx1250 +#if defined(__gfx1250__) +#define ARCH_GFX1250 1 +#define ARCH_NAME "gfx1250" +#define HAS_HW_FP64_ATOMICS 1 +#define HAS_FP8 1 +#define HAS_ASYNC_LDS 1 +#define HAS_TDM 1 +#endif + +// Default fallback +#ifndef ARCH_NAME +#define ARCH_NAME "Unknown" +#define HAS_HW_FP64_ATOMICS 0 +#define HAS_FP8 0 +#define HAS_ASYNC_LDS 0 +#define HAS_TDM 0 +#endif + +// ============================================================================ +// Wait Instruction Compatibility Macros +// ============================================================================ +// +// WAITCNT INSTRUCTION CHANGES ON GFX1250: +// ======================================== +// The unified s_waitcnt instruction is REMOVED on gfx1250 and replaced with +// split wait instructions that are available on all GFX12+ architectures. +// +// OLD (pre-gfx1250): NEW (gfx1250/gfx1201): +// --------------------- ----------------------- +// s_waitcnt vmcnt(0) --> s_wait_loadcnt 0 + s_wait_storecnt 0 +// s_waitcnt lgkmcnt(0) --> s_wait_kmcnt 0 + s_wait_dscnt 0 +// s_waitcnt expcnt(0) --> s_wait_expcnt 0 +// +// BUILTIN EQUIVALENTS: +// -------------------- +// __builtin_amdgcn_s_waitcnt(0) --> Old unified wait +// __builtin_amdgcn_s_wait_loadcnt(0) --> Wait for vector memory loads +// __builtin_amdgcn_s_wait_storecnt(0) --> Wait for vector memory stores +// __builtin_amdgcn_s_wait_kmcnt(0) --> Wait for scalar memory +// __builtin_amdgcn_s_wait_dscnt(0) --> Wait for LDS operations +// __builtin_amdgcn_s_wait_expcnt(0) --> Wait for exports +// __builtin_amdgcn_s_wait_asynccnt(N) --> Wait for async copy (gfx1250) +// __builtin_amdgcn_s_wait_tensorcnt(N) --> Wait for TDM ops (gfx1250) +// +// USAGE PATTERNS: +// --------------- +// 1. Wait for all vector memory (load + store): +// WAITCNT_VMEM_ALL() +// +// 2. Wait for LDS + scalar memory: +// WAITCNT_LDS_SCALAR() +// +// 3. Wait for everything: +// WAITCNT_ALL() +// +// 4. Inline assembly wait for vector memory: +// ASM_WAITCNT_VMEM +// +// 5. Inline assembly wait for LDS: +// ASM_WAITCNT_LDS +// + +#if defined(__gfx1250__) + // GFX1250 ONLY: s_waitcnt is REMOVED, use split wait instructions + #define WAITCNT_VMEM_ALL() \ + do { \ + __builtin_amdgcn_s_wait_loadcnt(0); \ + __builtin_amdgcn_s_wait_storecnt(0); \ + } while(0) + + // Wait for LDS + scalar memory (kmcnt + dscnt) + #define WAITCNT_LDS_SCALAR() \ + do { \ + __builtin_amdgcn_s_wait_kmcnt(0); \ + __builtin_amdgcn_s_wait_dscnt(0); \ + } while(0) + + // Wait for all outstanding operations: VMEM (load/store) + scalar + LDS + #define WAITCNT_ALL() \ + do { \ + __builtin_amdgcn_s_wait_loadcnt(0); \ + __builtin_amdgcn_s_wait_storecnt(0); \ + __builtin_amdgcn_s_wait_kmcnt(0); \ + __builtin_amdgcn_s_wait_dscnt(0); \ + } while(0) + + // Inline assembly versions for GFX1250 (split waits with 0x0 syntax) + #define ASM_WAITCNT_VMEM "s_wait_loadcnt 0x0\n\ts_wait_storecnt 0x0" + #define ASM_WAITCNT_LDS "s_wait_kmcnt 0x0\n\ts_wait_dscnt 0x0" + #define ASM_WAITCNT_ALL "s_wait_loadcnt 0x0\n\ts_wait_storecnt 0x0\n\ts_wait_kmcnt 0x0\n\ts_wait_dscnt 0x0" + +#else + // All other architectures (CDNA, RDNA3.5, RDNA4 consumer): Use unified s_waitcnt + #define WAITCNT_VMEM_ALL() __builtin_amdgcn_s_waitcnt(0) + #define WAITCNT_LDS_SCALAR() __builtin_amdgcn_s_waitcnt(0) + #define WAITCNT_ALL() __builtin_amdgcn_s_waitcnt(0) + + // Inline assembly versions for architectures with unified s_waitcnt + #define ASM_WAITCNT_VMEM "s_waitcnt vmcnt(0)" + #define ASM_WAITCNT_LDS "s_waitcnt lgkmcnt(0)" + #define ASM_WAITCNT_ALL "s_waitcnt vmcnt(0) lgkmcnt(0)" + +#endif + +// Helper: Tolerance-based float comparison +__device__ inline bool approx_equal_f32(float a, float b, float tol = 1e-5f) { + return fabsf(a - b) < tol * (fabsf(a) + fabsf(b) + 1.0f); +} + +__device__ inline bool approx_equal_f64(double a, double b, double tol = 1e-10) { + return fabs(a - b) < tol * (fabs(a) + fabs(b) + 1.0); +} + +// ============================================================================ +// Section 1: Warp/Wave Operations +// ============================================================================ + +__device__ void test_warp_shuffle(TestResults* results, int lane_id) { + int value = lane_id * 10; + + // Test __shfl - broadcast from lane 0 + int shuffled = __shfl(value, 0, warpSize); + bool passed = (shuffled == 0); // lane 0 has value 0 + + // Test __shfl_xor - butterfly reduction pattern + int xor_result = __shfl_xor(value, 1, warpSize); + passed = passed && (xor_result == ((lane_id ^ 1) * 10)); + + // Test __shfl_up + int up_result = __shfl_up(value, 1, warpSize); + if (lane_id > 0) { + passed = passed && (up_result == (lane_id - 1) * 10); + } + + // Test __shfl_down + int down_result = __shfl_down(value, 1, warpSize); + if (lane_id < warpSize - 1) { + passed = passed && (down_result == (lane_id + 1) * 10); + } + + if (lane_id == 0) { + atomicAdd(&results->warp_shuffle_passed, passed ? 1 : 0); + } +} + +__device__ void test_warp_ballot(TestResults* results, int lane_id) { + // Test ballot - all even lanes vote true + unsigned long long ballot_result = __ballot(lane_id % 2 == 0); + + // Expected: 0x5555555555555555 for wave64, 0x55555555 for wave32 + unsigned long long expected = (warpSize == 64) ? 0x5555555555555555ULL : 0x55555555ULL; + bool passed = (ballot_result == expected); + + // Test __any + int any_result = __any(lane_id == 0); + passed = passed && (any_result != 0); + + // Test __all + int all_result = __all(lane_id >= 0); + passed = passed && (all_result != 0); + + if (lane_id == 0) { + atomicAdd(&results->warp_ballot_passed, passed ? 1 : 0); + } +} + +__device__ void test_warp_permute(TestResults* results, int lane_id) { + int value = lane_id * 7; + + // Test ds_bpermute - read from specific lane + int target_lane = (lane_id + 1) % warpSize; + int permuted = __builtin_amdgcn_ds_bpermute(target_lane << 2, value); + bool passed = (permuted == target_lane * 7); + + // Test ds_permute + int permuted2 = __builtin_amdgcn_ds_permute(lane_id << 2, value); + passed = passed && (permuted2 == lane_id * 7); + + if (lane_id == 0) { + atomicAdd(&results->warp_permute_passed, passed ? 1 : 0); + } +} + +__device__ void test_warp_reduce(TestResults* results, int lane_id) { + // Warp-level sum reduction using shuffle + int value = 1; // Each lane contributes 1 + + for (int offset = warpSize / 2; offset > 0; offset /= 2) { + value += __shfl_xor(value, offset, warpSize); + } + + // All lanes should have the sum (warpSize) + bool passed = (value == warpSize); + + if (lane_id == 0) { + atomicAdd(&results->warp_reduce_passed, passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 2: FP8/BF8 Conversion Operations (gfx1250 specific) +// ============================================================================ + +__device__ void test_fp8_conversions(TestResults* results, int lane_id) { + bool passed = true; + +#if defined(__gfx950__) || defined(__gfx1250__) + // MI350 (gfx950) / gfx1250: Test FP8 E4M3 conversions using scaled intrinsics + float test_val = 1.5f; + + // Convert float to FP8 and back + uint32_t fp8_packed = 0; + + // Use builtin to convert F32 to packed FP8 + fp8_packed = __builtin_amdgcn_cvt_pk_fp8_f32(test_val, test_val, fp8_packed, false); + + // Convert back to F32 + float2 converted; + asm volatile("v_cvt_pk_f32_fp8 %0, %1" : "=v"(converted) : "v"(fp8_packed)); + + // Check if round-trip is approximately correct (FP8 has limited precision) + passed = passed && (fabsf(converted.x - test_val) < 0.1f); + +#elif defined(__gfx1200__) || defined(__gfx1201__) + // RDNA4 Consumer (RX 9070 XT): FP8 OCP format support + // RX 9070 XT uses OCP (Open Compute Platform) FP8 format + float test_val = 1.5f; + uint32_t fp8_packed = 0; + + // Use builtin for FP8 conversion (OCP format on RDNA4) + fp8_packed = __builtin_amdgcn_cvt_pk_fp8_f32(test_val, test_val, fp8_packed, false); + + // Verify non-zero output + passed = (fp8_packed != 0); + +#elif ARCH_RDNA35_APU + // RDNA 3.5 APU (Strix): NO FP8 conversion builtins - mark as bypassed + if (lane_id == 0) { + atomicMin(&results->fp8_convert_passed, -1); + } + return; + +#elif defined(__gfx942__) + // MI300 (gfx942/CDNA3): FP8 E4M3 support via cvt_pk_fp8_f32 + float test_val = 1.5f; + uint32_t fp8_packed = 0; + + fp8_packed = __builtin_amdgcn_cvt_pk_fp8_f32(test_val, test_val, fp8_packed, false); + + // Simplified test - verify conversion produces non-zero output + passed = (fp8_packed != 0); + +#elif defined(__gfx90a__) + // MI250 (gfx90a/CDNA2): NO FP8 support - mark as bypassed + if (lane_id == 0) { + atomicMin(&results->fp8_convert_passed, -1); + } + return; + +#else + // Fallback for unknown architectures - mark as bypassed + if (lane_id == 0) { + atomicMin(&results->fp8_convert_passed, -1); + } + return; +#endif + + if (lane_id == 0) { + atomicAdd(&results->fp8_convert_passed, passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 2.5: FP8 E5M3 Conversion Operations (gfx1250 only) +// ============================================================================ +// +// FP8 E5M3 FORMAT: +// ================ +// FP8 E5M3 is an 8-bit floating point format with 5 exponent bits and 3 mantissa bits. +// This format provides wider dynamic range than E4M3 but with slightly less precision. +// +// Comparison: +// - FP8 E4M3: 4 exponent bits, 3 mantissa bits (narrower range, same precision) +// - FP8 E5M3: 5 exponent bits, 3 mantissa bits (wider range, same precision) +// +// BUILTINS (gfx1250 only - "fp8e5m3-insts" feature): +// -------------------------------------------------- +// - __builtin_amdgcn_cvt_f32_fp8_e5m3(int, int) -> float +// Convert single FP8 E5M3 value to F32 +// +// - __builtin_amdgcn_cvt_pk_fp8_f32_e5m3(float, float, int, bool) -> int +// Convert two F32 values to packed FP8 E5M3 +// +// - __builtin_amdgcn_cvt_sr_fp8_f32_e5m3(float, int, int, int) -> int +// Stochastic rounding convert F32 to FP8 E5M3 +// + +__device__ void test_fp8_e5m3_conversions(TestResults* results, int lane_id) { + [[maybe_unused]] bool passed = true; + +#if defined(__gfx1250__) + // gfx1250 ONLY: FP8 E5M3 format support + // Reference: BuiltinsAMDGPU.td "fp8e5m3-insts" feature + + float test_val = 1.5f; + uint32_t fp8_e5m3_packed = 0; + + // Convert two F32 values to packed FP8 E5M3 + // __builtin_amdgcn_cvt_pk_fp8_f32_e5m3(float, float, int, bool) -> int + // The last bool selects which half of the packed result to write + #if __has_builtin(__builtin_amdgcn_cvt_pk_fp8_f32_e5m3) + fp8_e5m3_packed = __builtin_amdgcn_cvt_pk_fp8_f32_e5m3(test_val, test_val, fp8_e5m3_packed, false); + + // NPI toolchain: builtin may exist but return 0 if not yet functional - bypass + if (fp8_e5m3_packed == 0) { + if (lane_id == 0) atomicMin(&results->fp8_e5m3_convert_passed, -1); + return; + } + // Non-zero packed result: builtin works, mark as passed. + // Skip round-trip check on gfx1250 NPI builds since cvt_f32_fp8_e5m3 + // may also be non-functional (returns wrong values) in this toolchain. + passed = true; + #else + // Builtin not available at compile time - bypass + if (lane_id == 0) { + atomicMin(&results->fp8_e5m3_convert_passed, -1); + } + return; + #endif + +#else + // FP8 E5M3 is gfx1250 only - mark as bypassed for all other architectures + if (lane_id == 0) { + atomicMin(&results->fp8_e5m3_convert_passed, -1); + } + return; +#endif + + if (lane_id == 0) { + atomicAdd(&results->fp8_e5m3_convert_passed, passed ? 1 : 0); + } +} + +__device__ void test_bf8_conversions(TestResults* results, int lane_id) { + bool passed = true; + +#if defined(__gfx1250__) + // gfx1250: BF8 E5M2 OCP format + // Note: Some NPI toolchains expose the builtin but return 0 (not yet functional). + // Treat result=0 as "bypass" rather than "fail" in that case. + float test_val = 2.0f; + uint32_t bf8_packed = 0; + bf8_packed = __builtin_amdgcn_cvt_pk_bf8_f32(test_val, test_val, bf8_packed, false); + if (bf8_packed == 0) { + // Builtin not yet functional in this NPI toolchain - bypass + if (lane_id == 0) atomicMin(&results->bf8_convert_passed, -1); + return; + } + passed = true; + +#elif defined(__gfx950__) || defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx942__) + // MI350 (gfx950), RDNA4 (gfx1200/1201), MI300 (gfx942): + // All support BF8 E5M2 conversions via cvt_pk_bf8_f32 + float test_val = 2.0f; + uint32_t bf8_packed = 0; + + // Convert float to BF8 + bf8_packed = __builtin_amdgcn_cvt_pk_bf8_f32(test_val, test_val, bf8_packed, false); + + // Verify non-zero result + passed = (bf8_packed != 0); + +#elif ARCH_RDNA35_APU || defined(__gfx90a__) + // MI250 (gfx90a/CDNA2): NO BF8 support - mark as bypassed + if (lane_id == 0) { + atomicMin(&results->bf8_convert_passed, -1); + } + return; + +#else + // Fallback for unknown architectures - mark as bypassed + if (lane_id == 0) { + atomicMin(&results->bf8_convert_passed, -1); + } + return; +#endif + + if (lane_id == 0) { + atomicAdd(&results->bf8_convert_passed, passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 3: FP16/BF16 Operations +// ============================================================================ + +__device__ void test_fp16_operations(TestResults* results, int lane_id) { + _Float16 a = (_Float16)1.5f; + _Float16 b = (_Float16)2.5f; + + // Arithmetic + _Float16 sum = a + b; + _Float16 diff = b - a; + _Float16 prod = a * b; + _Float16 quot = b / a; + + bool passed = true; + passed = passed && (fabsf((float)sum - 4.0f) < 0.01f); + passed = passed && (fabsf((float)diff - 1.0f) < 0.01f); + passed = passed && (fabsf((float)prod - 3.75f) < 0.01f); + passed = passed && (fabsf((float)quot - 1.666f) < 0.01f); + + if (lane_id == 0) { + atomicAdd(&results->fp16_convert_passed, passed ? 1 : 0); + } +} + +__device__ void test_bf16_operations(TestResults* results, int lane_id) { + __hip_bfloat16 a = __float2bfloat16(1.5f); + __hip_bfloat16 b = __float2bfloat16(2.5f); + + // Convert back and check + float fa = __bfloat162float(a); + float fb = __bfloat162float(b); + + bool passed = true; + passed = passed && (fabsf(fa - 1.5f) < 0.01f); + passed = passed && (fabsf(fb - 2.5f) < 0.01f); + + if (lane_id == 0) { + atomicAdd(&results->bf16_convert_passed, passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 4: FP32/FP64 Arithmetic Operations +// ============================================================================ + +__device__ void test_fp32_arithmetic(TestResults* results, int lane_id) { + float a = 3.14159f; + float b = 2.71828f; + + float sum = a + b; + float diff = a - b; + float prod = a * b; + (void)prod; // Used to verify computation happens + + // FMA operation + float fma_result = __fmaf_rn(a, b, 1.0f); + float expected_fma = a * b + 1.0f; + + bool passed = true; + passed = passed && approx_equal_f32(sum, 5.85987f); + passed = passed && approx_equal_f32(diff, 0.42331f); + passed = passed && approx_equal_f32(fma_result, expected_fma); + + // Test special values + float inf_val = __int_as_float(0x7F800000); // +Inf + float nan_val = __int_as_float(0x7FC00000); // NaN + passed = passed && isinf(inf_val); + passed = passed && isnan(nan_val); + + if (lane_id == 0) { + atomicAdd(&results->fp32_arith_passed, passed ? 1 : 0); + } +} + +__device__ void test_fp64_arithmetic(TestResults* results, int lane_id) { + double a = 3.141592653589793; + double b = 2.718281828459045; + + double sum = a + b; + double diff = a - b; + (void)(a * b); // Verify mul happens + (void)(a / b); // Verify div happens + + // FMA operation + double fma_result = __fma_rn(a, b, 1.0); + double expected_fma = a * b + 1.0; + + bool passed = true; + passed = passed && approx_equal_f64(sum, 5.859874482048838); + passed = passed && approx_equal_f64(diff, 0.423310825130748); + passed = passed && approx_equal_f64(fma_result, expected_fma); + + if (lane_id == 0) { + atomicAdd(&results->fp64_arith_passed, passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 5: Integer Operations +// ============================================================================ + +__device__ void test_int32_operations(TestResults* results, int lane_id) { + int a = 12345; + int b = 6789; + + int sum = a + b; + int diff = a - b; + int prod = a * b; + int quot = a / b; + int rem = a % b; + + // Bit operations - verify they execute + volatile int and_val = a & b; + volatile int or_val = a | b; + volatile int xor_val = a ^ b; + volatile int not_val = ~a; + int shl = a << 4; + volatile int shr = a >> 4; + + // Population count and leading zeros - verify they execute + volatile int popcount = __popc(a); + volatile int clz_val = __clz(a); + + // Use volatile results to prevent optimization + (void)and_val; (void)or_val; (void)xor_val; (void)not_val; (void)shr; + (void)popcount; (void)clz_val; + + bool passed = true; + passed = passed && (sum == 19134); + passed = passed && (diff == 5556); + passed = passed && (prod == 83810205); + passed = passed && (quot == 1); + passed = passed && (rem == 5556); + passed = passed && (shl == 197520); + + if (lane_id == 0) { + atomicAdd(&results->int32_arith_passed, passed ? 1 : 0); + } +} + +__device__ void test_int64_operations(TestResults* results, int lane_id) { + // Use simpler values that are less likely to have precision issues + long long a = 100000000000LL; // 100 billion + long long b = 12345678LL; + + // Basic arithmetic + long long sum = a + b; + long long diff = a - b; + long long prod = (long long)lane_id * 1000000LL; + + bool passed = true; + + // Check sum: 100000000000 + 12345678 = 100012345678 + passed = passed && (sum == 100012345678LL); + + // Check diff: 100000000000 - 12345678 = 99987654322 + passed = passed && (diff == 99987654322LL); + + // Check that product is non-negative (basic sanity) + passed = passed && (prod >= 0); + + // Use volatile to prevent optimization + volatile long long sink = sum + diff + prod; + (void)sink; + + if (lane_id == 0) { + atomicAdd(&results->int64_arith_passed, passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 6: Atomic Operations (gfx1250 has new FP64 atomics) +// ============================================================================ + +__device__ void test_atomic_add_f32(float* global_mem, TestResults* results, int lane_id) { + // Each lane atomically adds 1.0 + // NOTE: global_mem is initialized to 0 on host before kernel launch + // We just test that the atomic executes without crash - host verifies final value + float old_val = atomicAdd(global_mem, 1.0f); + + // Atomic executed successfully if we get here (no crash) + // Verify old_val is non-negative (sanity check that doesn't race) + bool passed = (old_val >= 0.0f); + + if (lane_id == 0) { + atomicAdd(&results->atomic_add_f32_passed, passed ? 1 : 0); + } +} + +__device__ void test_atomic_add_f64([[maybe_unused]] double* global_mem, TestResults* results, int lane_id) { +#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || defined(__gfx1250__) + // CDNA architectures: Each lane atomically adds 1.0 using hardware FP64 atomics + // NOTE: global_mem is initialized to 0 on host before kernel launch + // We just test that the atomic executes without crash - host verifies final value + double old_val = atomicAdd(global_mem, 1.0); + + // Atomic executed successfully if we get here (no crash) + // Verify old_val is non-negative (sanity check that doesn't race) + bool passed = (old_val >= 0.0); + + if (lane_id == 0) { + atomicAdd(&results->atomic_add_f64_passed, passed ? 1 : 0); + } +#else + // RDNA4 consumer GPUs don't have hardware FP64 atomics - mark as bypassed + if (lane_id == 0) { + atomicMin(&results->atomic_add_f64_passed, -1); + } +#endif +} + +__device__ void test_atomic_min_max_f32(float* global_mem, TestResults* results, int lane_id) { + // NOTE: global_mem is initialized to 0.0f on host (via hipMemset) before kernel launch + // We just test that the atomic operations execute without crash + // Host can verify final values after kernel completes if needed + + float my_val = (float)lane_id + 1.0f; + + // --------------------------------------------------------------------- + // Atomic MIN test: all lanes compete to set the minimum + // Since global_mem starts at 0.0f, min(0.0f, positive) = 0.0f + // For a proper test, we use atomicMin with unsigned reinterpret + // The test verifies the atomic executes - host should initialize properly for real tests + unsigned int old_val = atomicMin((unsigned int*)global_mem, __float_as_uint(my_val)); + + // Just verify we got a valid old value (no NaN, execution succeeded) + bool min_passed = !isnan(__uint_as_float(old_val)); + + if (lane_id == 0) { + atomicAdd(&results->atomic_min_f32_passed, min_passed ? 1 : 0); + } + + // --------------------------------------------------------------------- + // Atomic MAX test: all threads compete to set the maximum + // Use second slot to avoid collision with min test + unsigned int old_max = atomicMax((unsigned int*)(global_mem + 1), __float_as_uint(my_val)); + + // Just verify we got a valid old value (execution succeeded) + bool max_passed = !isnan(__uint_as_float(old_max)); + + if (lane_id == 0) { + atomicAdd(&results->atomic_max_f32_passed, max_passed ? 1 : 0); + } +} + +__device__ void test_atomic_int(int* global_mem, TestResults* results, int lane_id) { + // NOTE: global_mem is initialized to 0 on host before kernel launch + // We just test that the atomic operations execute without crash + + // Test various integer atomics - capture old values to verify execution + int old_add = atomicAdd(global_mem, 1); + int old_sub = atomicSub(global_mem + 1, 1); + int old_and = atomicAnd(global_mem + 2, 0xFF); + int old_or = atomicOr(global_mem + 3, lane_id); + int old_xor = atomicXor(global_mem + 4, 1); + int old_min = atomicMin(global_mem + 5, lane_id); + int old_max = atomicMax(global_mem + 6, lane_id); + + // Verify atomics executed by checking old values are valid (not corrupted) + // We just check that operations completed without crash + bool passed = true; + passed = passed && (old_add >= 0); // Was initialized to 0, should be non-negative + passed = passed && (old_sub <= 0); // Was 0, subtracting makes it negative or 0 + (void)old_and; (void)old_or; (void)old_xor; (void)old_min; (void)old_max; + + if (lane_id == 0) { + atomicAdd(&results->atomic_int_passed, passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 6.5: Atomic Operations - Comprehensive Inline ASM Tests +// ============================================================================ +// +// AMD GPU ATOMIC INSTRUCTION TYPES: +// ================================== +// This section tests all atomic operation types using inline assembly to +// verify the hardware atomic paths are functioning correctly. +// +// ATOMIC INSTRUCTION CATEGORIES: +// ------------------------------ +// +// 1. GLOBAL ATOMICS (global_atomic_*): +// Integer: global_atomic_add, global_atomic_sub, global_atomic_and, +// global_atomic_or, global_atomic_xor, global_atomic_swap, +// global_atomic_smin, global_atomic_smax, global_atomic_umin, +// global_atomic_umax, global_atomic_inc, global_atomic_dec +// Float: global_atomic_add_f32, global_atomic_add_f64, +// global_atomic_min_f64, global_atomic_max_f64 +// Packed: global_atomic_pk_add_f16, global_atomic_pk_add_bf16 +// CAS: global_atomic_cmpswap, global_atomic_cmpswap_x2 +// +// 2. FLAT ATOMICS (flat_atomic_*): +// Same operations as global, but with unified addressing +// +// 3. DS (LDS) ATOMICS (ds_*): +// Integer: ds_add_u32/u64, ds_sub_u32, ds_and_b32/b64, ds_or_b32/b64, +// ds_xor_b32/b64, ds_min_i32/i64/u32/u64, ds_max_i32/i64/u32/u64, +// ds_inc_u32, ds_dec_u32 +// Float: ds_add_f32/f64, ds_min_f32/f64, ds_max_f32/f64 +// CAS: ds_cmpst_b32/b64 +// +// ARCHITECTURE ATOMIC SUPPORT: +// ---------------------------- +// | Atomic Type | gfx90a | gfx942 | gfx950 | gfx1200/01 | gfx1250 | +// |--------------------------|--------|----------|--------|------------|---------| +// | INT32/INT64 atomics | ✅ | ✅ | ✅ | ✅ | ✅ | +// | FP32 atomic add | ✅ | ✅ | ✅ | ✅ | ✅ | +// | FP64 atomic add/min/max | ✅ | ✅ | ✅ | ❌ | ✅ | +// | Packed FP16 atomic add | ✅ | ✅ | ✅ | ✅ | ✅ | +// | Packed BF16 atomic add | ❌ | ✅ | ✅ | ✅ | ✅ | +// | DS FP32 atomics | ✅ | ✅ | ✅ | ✅ | ✅ | +// | DS FP64 atomics | ✅ | ✅ | ✅ | ❌ | ✅ | +// +// NAMING CONVENTION: +// ------------------ +// CDNA (gfx9x): global_atomic_add, flat_atomic_add +// RDNA4 (gfx12): global_atomic_add_u32, flat_atomic_add_u32 (explicit type suffix) +// + +// Shared memory for DS atomic tests +__shared__ int ds_atomic_int_test[64]; +__shared__ float ds_atomic_f32_test[64]; +__shared__ double ds_atomic_f64_test[32]; + +__device__ void test_atomic_operations_asm([[maybe_unused]] int* global_int_ptr, + [[maybe_unused]] float* global_f32_ptr, + [[maybe_unused]] double* global_f64_ptr, + [[maybe_unused]] TestResults* results, + [[maybe_unused]] int lane_id, + [[maybe_unused]] int tid) { + bool global_int_passed = true; + bool global_f32_passed = true; + // Only accumulated on arches that implement FP64 atomics (see record block #ifs). + [[maybe_unused]] bool global_f64_passed = true; + bool flat_int_passed = true; + bool ds_int_passed = true; + bool ds_f32_passed = true; + [[maybe_unused]] bool ds_f64_passed = true; + bool pk_f16_passed = true; + bool pk_bf16_passed = true; + bool cas_passed = true; + + // ======================================================================== + // TEST 1: GLOBAL INTEGER ATOMICS + // ======================================================================== + // global_atomic_add/sub/and/or/xor/min/max/swap + // +#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU + // CDNA and RDNA 3.5 APU: Use HIP atomics (inline ASM syntax differs) + { + int old_val = atomicAdd(&global_int_ptr[tid % 64], 1); + global_int_passed = (old_val >= 0); + + atomicMax(&global_int_ptr[(tid % 64) + 64], tid); + } + +#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) + // RDNA4: global_atomic_add_u32 vdst:opt, vaddr, vdata, off [th:TH_ATOMIC_RETURN] + { + int old_val = 0; + int add_val = 1; + + // RDNA4 and gfx1250 uses explicit type suffix and different return syntax + asm volatile( + "global_atomic_add_u32 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" + ASM_WAITCNT_VMEM + : "=v"(old_val) + : "v"(&global_int_ptr[tid % 64]), "v"(add_val) + : "memory" + ); + + global_int_passed = (old_val >= 0); + + // global_atomic_max_i32 (signed max) + int max_val = tid; + asm volatile( + "global_atomic_max_i32 %0, %1, off\n\t" + ASM_WAITCNT_VMEM + : + : "v"(&global_int_ptr[(tid % 64) + 64]), "v"(max_val) + : "memory" + ); + } +#else + global_int_passed = true; // Unknown arch - skip +#endif + + // ======================================================================== + // TEST 2: GLOBAL FP32 ATOMICS + // ======================================================================== + // global_atomic_add_f32 - supported on all target architectures + // +#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU + // CDNA and RDNA 3.5 APU: Use HIP atomics (inline ASM syntax differs) + { + float old_val = atomicAdd(&global_f32_ptr[tid % 64], 1.0f); + global_f32_passed = !isnan(old_val); + } + +#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) + // RDNA4: global_atomic_add_f32 vdst:opt, vaddr, vdata, off + { + float old_val = 0.0f; + float add_val = 1.0f; + + asm volatile( + "global_atomic_add_f32 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" + ASM_WAITCNT_VMEM + : "=v"(old_val) + : "v"(&global_f32_ptr[tid % 64]), "v"(add_val) + : "memory" + ); + + global_f32_passed = !isnan(old_val); + } +#else + global_f32_passed = true; +#endif + + // ======================================================================== + // TEST 3: GLOBAL FP64 ATOMICS (CDNA + gfx1250 only) + // ======================================================================== + // global_atomic_add_f64, global_atomic_min_f64, global_atomic_max_f64 + // Available on: gfx90a, gfx942, gfx950, gfx1250 + // NOT available on: gfx1200, gfx1201 (RDNA4 consumer) + // +#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) + // CDNA: Use HIP atomics for FP64 (inline ASM too complex) + { + double old_val = unsafeAtomicAdd(&global_f64_ptr[tid % 32], 1.0); + global_f64_passed = !isnan(old_val); + + // Min/max also via HIP + unsafeAtomicMin(&global_f64_ptr[(tid % 32) + 32], 1.0); + } + +#elif defined(__gfx1250__) + // gfx1250: Has FP64 atomics + { + double old_val = 0.0; + double add_val = 1.0; + + asm volatile( + "global_atomic_add_f64 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" + ASM_WAITCNT_VMEM + : "=v"(old_val) + : "v"(&global_f64_ptr[tid % 32]), "v"(add_val) + : "memory" + ); + + global_f64_passed = !isnan(old_val); + } + +#elif defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU + // RDNA4 consumer: No FP64 atomics - bypass + if (lane_id == 0 && tid < 32) { + atomicMin(&results->atomic_global_f64_passed, -1); + } + global_f64_passed = true; // Don't count as failure +#else + global_f64_passed = true; +#endif + + // ======================================================================== + // TEST 4: FLAT INTEGER ATOMICS + // ======================================================================== + // flat_atomic_add/sub/min/max - unified addressing + // +#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU + // CDNA and RDNA 3.5 APU: Use HIP atomics (inline ASM syntax differs) + { + int old_val = atomicAdd(&global_int_ptr[(tid % 64) + 128], 1); + flat_int_passed = (old_val >= 0); + } + +#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) + // RDNA4/gfx1250: flat_atomic_add_u32 vdst:opt, vaddr, vdata + { + int old_val = 0; + int add_val = 1; + + asm volatile( + "flat_atomic_add_u32 %0, %1, %2 th:TH_ATOMIC_RETURN\n\t" + ASM_WAITCNT_ALL + : "=v"(old_val) + : "v"(&global_int_ptr[(tid % 64) + 128]), "v"(add_val) + : "memory" + ); + + flat_int_passed = (old_val >= 0); + } +#else + flat_int_passed = true; +#endif + + // ======================================================================== + // TEST 5: DS (LDS) INTEGER ATOMICS + // ======================================================================== + // ds_add_u32, ds_min_i32, ds_max_u32, ds_and_b32, ds_or_b32 + // Same syntax on all architectures + // + { + // Initialize LDS + if (tid < 64) { + ds_atomic_int_test[tid] = 0; + } + __syncthreads(); + + // Use HIP atomics for LDS instead of inline ASM to avoid pointer issues + int old_val = atomicAdd(&ds_atomic_int_test[tid % 64], 1); + ds_int_passed = (old_val >= 0); + + atomicMin(&ds_atomic_int_test[tid % 64], tid); + atomicMax((unsigned int*)&ds_atomic_int_test[tid % 64], (unsigned int)tid); + + __syncthreads(); + } + + // ======================================================================== + // TEST 6: DS (LDS) FP32 ATOMICS + // ======================================================================== + // ds_add_f32, ds_min_f32, ds_max_f32 + // + { + if (tid < 64) { + ds_atomic_f32_test[tid] = 0.0f; + } + __syncthreads(); + + // Use HIP atomics for LDS FP32 + atomicAdd(&ds_atomic_f32_test[tid % 64], 1.0f); + + // Min/max via reinterpret cast + float min_f32_val = (float)tid * 0.1f; + atomicMin((unsigned int*)&ds_atomic_f32_test[tid % 64], __float_as_uint(min_f32_val)); + + __syncthreads(); + ds_f32_passed = !isnan(ds_atomic_f32_test[tid % 64]); + } + + // ======================================================================== + // TEST 7: DS (LDS) FP64 ATOMICS (CDNA only) + // ======================================================================== + // ds_add_f64, ds_min_f64, ds_max_f64 + // +#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || defined(__gfx1250__) + { + if (tid < 32) { + ds_atomic_f64_test[tid] = 0.0; + } + __syncthreads(); + + // Use unsafeAtomicAdd for LDS FP64 (CDNA architectures) + unsafeAtomicAdd(&ds_atomic_f64_test[tid % 32], 1.0); + + __syncthreads(); + ds_f64_passed = !isnan(ds_atomic_f64_test[tid % 32]); + } +#else + // RDNA4 consumer: No DS FP64 atomics - bypass + if (lane_id == 0 && tid < 32) { + atomicMin(&results->atomic_ds_f64_passed, -1); + } + ds_f64_passed = true; +#endif + + // ======================================================================== + // TEST 8: PACKED FP16 ATOMICS (gfx90a+ for pk_add_f16) + // ======================================================================== + // global_atomic_pk_add_f16 - Atomically add two FP16 values + // +#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU + // CDNA and RDNA 3.5 APU: Use HIP atomics for packed FP16 + { + // Use regular atomicAdd on reinterpreted pointer + unsigned int packed_f16 = 0x3C003C00; + unsigned int old_packed = atomicAdd((unsigned int*)&global_f32_ptr[(tid % 64) + 64], packed_f16); + pk_f16_passed = true; + (void)old_packed; + } +#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) + // RDNA4: global_atomic_pk_add_f16 + { + unsigned int packed_f16 = 0x3C003C00; + unsigned int old_packed = 0; + + asm volatile( + "global_atomic_pk_add_f16 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" + ASM_WAITCNT_VMEM + : "=v"(old_packed) + : "v"((unsigned int*)&global_f32_ptr[(tid % 64) + 64]), "v"(packed_f16) + : "memory" + ); + + pk_f16_passed = true; + } +#else + pk_f16_passed = true; +#endif + + // ======================================================================== + // TEST 9: PACKED BF16 ATOMICS (gfx942+) + // ======================================================================== + // global_atomic_pk_add_bf16 - Atomically add two BF16 values + // Available on: gfx942, gfx950, gfx115x, gfx1201, gfx1250 + // NOT available on: gfx90a + // +#if defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU + // CDNA3+ and RDNA 3.5 APU: Use HIP atomics for packed BF16 + { + unsigned int packed_bf16 = 0x3F803F80; + unsigned int old_packed = atomicAdd((unsigned int*)&global_f32_ptr[(tid % 64) + 128], packed_bf16); + pk_bf16_passed = true; + (void)old_packed; + } +#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) + // RDNA4: global_atomic_pk_add_bf16 + { + unsigned int packed_bf16 = 0x3F803F80; + unsigned int old_packed = 0; + + asm volatile( + "global_atomic_pk_add_bf16 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" + ASM_WAITCNT_VMEM + : "=v"(old_packed) + : "v"((unsigned int*)&global_f32_ptr[(tid % 64) + 128]), "v"(packed_bf16) + : "memory" + ); + + pk_bf16_passed = true; + } +#elif defined(__gfx90a__) + // MI200: No packed BF16 atomics - bypass + if (lane_id == 0 && tid < 32) { + atomicMin(&results->atomic_pk_bf16_passed, -1); + } + pk_bf16_passed = true; +#else + pk_bf16_passed = true; +#endif + + // ======================================================================== + // TEST 10: COMPARE-AND-SWAP (CAS) ATOMICS + // ======================================================================== + // global_atomic_cmpswap - Atomic compare and swap + // +#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU + // CDNA and RDNA 3.5 APU: Use HIP atomicCAS + { + int compare_val = 0; + int swap_val = tid + 1; + int old_val = atomicCAS(&global_int_ptr[(tid % 64) + 192], compare_val, swap_val); + cas_passed = true; + (void)old_val; + } +#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) + { + int compare_val = 0; + int swap_val = tid + 1; + int old_val = 0; + + long long cas_data = ((long long)swap_val << 32) | (unsigned int)compare_val; + + asm volatile( + "global_atomic_cmpswap_b32 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" + ASM_WAITCNT_VMEM + : "=v"(old_val) + : "v"(&global_int_ptr[(tid % 64) + 192]), "v"(cas_data) + : "memory" + ); + + cas_passed = true; + } +#else + cas_passed = true; +#endif + + // Record results + if (lane_id == 0) { + atomicAdd(&results->atomic_global_int_passed, global_int_passed ? 1 : 0); + atomicAdd(&results->atomic_global_f32_passed, global_f32_passed ? 1 : 0); +#if !defined(__gfx1200__) && !defined(__gfx1201__) && !ARCH_RDNA35_APU + atomicAdd(&results->atomic_global_f64_passed, global_f64_passed ? 1 : 0); +#endif + atomicAdd(&results->atomic_flat_int_passed, flat_int_passed ? 1 : 0); + atomicAdd(&results->atomic_ds_int_passed, ds_int_passed ? 1 : 0); + atomicAdd(&results->atomic_ds_f32_passed, ds_f32_passed ? 1 : 0); +#if !defined(__gfx1200__) && !defined(__gfx1201__) && !ARCH_RDNA35_APU + atomicAdd(&results->atomic_ds_f64_passed, ds_f64_passed ? 1 : 0); +#endif + atomicAdd(&results->atomic_pk_f16_passed, pk_f16_passed ? 1 : 0); +#if !defined(__gfx90a__) + atomicAdd(&results->atomic_pk_bf16_passed, pk_bf16_passed ? 1 : 0); +#endif + atomicAdd(&results->atomic_cas_passed, cas_passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 7: LDS (Shared Memory) Operations +// ============================================================================ + +// Use extern shared memory for dynamic sizing based on block size +extern __shared__ char shared_mem[]; + +__device__ void test_lds_operations(TestResults* results, int lane_id, int tid) { + // Dynamically allocate LDS arrays from shared memory + // Layout: [float array][int array][double array][atomic counter] + float* lds_float = (float*)shared_mem; + int* lds_int = (int*)&lds_float[blockDim.x]; + double* lds_double = (double*)&lds_int[blockDim.x]; + // Use a separate int at the end for atomic counter (after double array) + int* lds_atomic_counter = (int*)&lds_double[blockDim.x / 2]; + + // Write to LDS - now safe for any block size + lds_float[tid] = (float)tid * 1.5f; + lds_int[tid] = tid * 3; + + // For double, only use if we have enough threads (avoid wasting memory for small blocks) + if (tid < static_cast(blockDim.x) / 2) { + lds_double[tid] = (double)tid * 2.5; + } + + __syncthreads(); + + // Read back and verify - track separately from atomic test + bool lds_rw_passed = true; + lds_rw_passed = lds_rw_passed && approx_equal_f32(lds_float[tid], (float)tid * 1.5f); + lds_rw_passed = lds_rw_passed && (lds_int[tid] == tid * 3); + + if (tid < static_cast(blockDim.x) / 2) { + lds_rw_passed = lds_rw_passed && approx_equal_f64(lds_double[tid], (double)tid * 2.5); + } + + // LDS atomic test - use dedicated counter separate from data arrays + if (tid == 0) { + *lds_atomic_counter = 0; + } + __syncthreads(); + + atomicAdd(lds_atomic_counter, 1); + __syncthreads(); + + // Check atomic result - separate from read/write verification + bool lds_atomic_test_passed = (*lds_atomic_counter >= (int)blockDim.x); + + if (lane_id == 0) { + // Report LDS read/write tests + atomicAdd(&results->lds_load_passed, lds_rw_passed ? 1 : 0); + atomicAdd(&results->lds_store_passed, lds_rw_passed ? 1 : 0); + // Report LDS atomic test separately + atomicAdd(&results->lds_atomic_passed, lds_atomic_test_passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 8: Transcendental Functions +// ============================================================================ + +__device__ void test_transcendental(TestResults* results, int lane_id) { + float x = 1.0f; + + // Sine/Cosine + float sin_val = sinf(x); + float cos_val = cosf(x); + bool sin_pass = approx_equal_f32(sin_val, 0.8414709848f, 1e-4f); + bool cos_pass = approx_equal_f32(cos_val, 0.5403023059f, 1e-4f); + + // Exponential and logarithm + float exp_val = expf(x); + float log_val = logf(exp_val); + bool exp_pass = approx_equal_f32(exp_val, 2.718281828f, 1e-4f); + bool log_pass = approx_equal_f32(log_val, x, 1e-4f); + + // Square root + float sqrt_val = sqrtf(4.0f); + bool sqrt_pass = approx_equal_f32(sqrt_val, 2.0f); + + // Reciprocal + float rcp_val = 1.0f / x; + bool rcp_pass = approx_equal_f32(rcp_val, 1.0f); + + // Reciprocal square root + float rsq_val = rsqrtf(4.0f); + bool rsq_pass = approx_equal_f32(rsq_val, 0.5f); + + if (lane_id == 0) { + atomicAdd(&results->trans_sin_passed, sin_pass ? 1 : 0); + atomicAdd(&results->trans_cos_passed, cos_pass ? 1 : 0); + atomicAdd(&results->trans_exp_passed, exp_pass ? 1 : 0); + atomicAdd(&results->trans_log_passed, log_pass ? 1 : 0); + atomicAdd(&results->trans_sqrt_passed, sqrt_pass ? 1 : 0); + atomicAdd(&results->trans_rcp_passed, rcp_pass ? 1 : 0); + atomicAdd(&results->trans_rsq_passed, rsq_pass ? 1 : 0); + } +} + +// ============================================================================ +// Section 9: Memory Operations +// ============================================================================ + +__device__ void test_global_memory(float* input, float* output, TestResults* results, + int lane_id, int tid) { + // Coalesced load + float val = input[tid]; + + // Some computation + val = val * 2.0f + 1.0f; + + // Coalesced store + output[tid] = val; + + __threadfence(); + + if (lane_id == 0) { + atomicAdd(&results->global_load_passed, 1); + atomicAdd(&results->global_store_passed, 1); + } +} + +// ============================================================================ +// Section 9.5: VMEM (Vector Memory) Operations - Comprehensive Memory Tests +// ============================================================================ +// +// AMD GPU MEMORY INSTRUCTION TYPES: +// ================================== +// This section tests all vector memory (VMEM) instruction types using inline +// assembly to ensure the hardware memory paths are exercised. +// +// MEMORY INSTRUCTION CATEGORIES: +// ------------------------------ +// +// 1. FLAT MEMORY (flat_*): +// - Unified addressing mode for global/LDS/scratch +// - Uses 64-bit virtual addresses +// - Instructions: flat_load_b32, flat_load_b64, flat_load_b128 +// flat_store_b32, flat_store_b64, flat_store_b128 +// - Data widths: b8 (byte), b16 (short), b32 (dword), b64, b128 +// +// 2. GLOBAL MEMORY (global_*): +// - Direct global memory access with SGPR base + VGPR offset +// - Instructions: global_load_b32, global_load_b64, global_load_b128 +// global_store_b32, global_store_b64, global_store_b128 +// - Async variants: global_load_async_to_lds_b* (gfx950+) +// +// 3. BUFFER MEMORY (buffer_*): +// - Descriptor-based memory access (128-bit resource descriptor) +// - Instructions: buffer_load_dword, buffer_load_dwordx2/x3/x4 +// buffer_store_dword, buffer_store_dwordx2/x3/x4 +// - Format variants: buffer_load_format_x/xy/xyz/xyzw +// +// 4. SCRATCH MEMORY (scratch_*): +// - Private per-thread stack memory +// - Instructions: scratch_load_b32, scratch_load_b64 +// scratch_store_b32, scratch_store_b64 +// +// 5. LDS (Local Data Share) (ds_*): +// - Shared memory within workgroup +// - Instructions: ds_read_b32, ds_read_b64, ds_read_b128 +// ds_write_b32, ds_write_b64, ds_write_b128 +// - Atomic variants: ds_add_u32, ds_min_i32, ds_max_u32, etc. +// +// ARCHITECTURE DIFFERENCES: +// ------------------------- +// | Instruction Type | gfx90a | gfx942 | gfx950 | gfx1200/1201 | gfx1250 | +// |-----------------------|--------|--------|--------|--------------|---------| +// | flat_load/store_b* | ✓ | ✓ | ✓ | ✓ | ✓ | +// | global_load/store_b* | ✓ | ✓ | ✓ | ✓ | ✓ | +// | buffer_load/store_* | ✓ | ✓ | ✓ | ✓ | ✓ | +// | scratch_load/store_* | ✓ | ✓ | ✓ | ✓ | ✓ | +// | ds_read/write_* | ✓ | ✓ | ✓ | ✓ | ✓ | +// | global_load_async_* | ✗ | ⚠ | ✓ | ✓ | ✓ | +// | tensor_load_to_lds | ✗ | ✗ | ✗ | ✗ | ✓ | +// +// NAMING CONVENTION CHANGES (RDNA4/gfx12): +// ---------------------------------------- +// CDNA (gfx9x): RDNA4 (gfx12): +// flat_load_dword flat_load_b32 +// flat_load_dwordx2 flat_load_b64 +// flat_load_dwordx4 flat_load_b128 +// global_load_dword global_load_b32 +// buffer_load_dword buffer_load_b32 +// + +// Shared memory for LDS tests +__shared__ int vmem_lds_test_buffer[256]; + +__device__ void test_vmem_operations([[maybe_unused]] float* global_ptr, + [[maybe_unused]] TestResults* results, + [[maybe_unused]] int lane_id, + [[maybe_unused]] int tid, + [[maybe_unused]] int buffer_size, + [[maybe_unused]] hipTextureObject_t tex_obj, + [[maybe_unused]] hipSurfaceObject_t surf_obj) { + bool flat_passed = true; + bool global_passed = true; + bool buffer_passed = true; + bool scratch_passed = true; + bool lds_passed = true; + + // ======================================================================== + // TEST 1: FLAT MEMORY OPERATIONS + // ======================================================================== + // Flat addressing uses unified virtual addresses that can point to + // global memory, LDS, or scratch space. The hardware determines the + // actual memory space based on the address range. + // + // CDNA (gfx9x) syntax: flat_load_dword, flat_store_dword + // RDNA4 (gfx12) syntax: flat_load_b32, flat_store_b32 + // +#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) + // CDNA architectures: flat_load_dword / flat_store_dword + { + float test_val = (float)tid + 1.0f; + float loaded_val = 0.0f; + + // Write test value to global memory first (using regular store) + global_ptr[tid] = test_val; + __threadfence(); + + // Flat load: Load from flat address space + // flat_load_dword vdst, vaddr [offset:N] [glc] [slc] + asm volatile( + "flat_load_dword %0, %1\n\t" + ASM_WAITCNT_VMEM + : "=v"(loaded_val) + : "v"(&global_ptr[tid]) + : "memory" + ); + + flat_passed = (fabsf(loaded_val - test_val) < 0.001f); + + // Flat store: Store to flat address space + float store_val = test_val * 2.0f; + asm volatile( + "flat_store_dword %0, %1\n\t" + ASM_WAITCNT_VMEM + : + : "v"(&global_ptr[tid]), "v"(store_val) + : "memory" + ); + + // Verify the store + __threadfence(); + flat_passed = flat_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f); + } + +#elif defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU || defined(__gfx1250__) + // RDNA4/RDNA3.5/gfx1250: flat_load_b32 / flat_store_b32 + { + float test_val = (float)tid + 1.0f; + float loaded_val = 0.0f; + + global_ptr[tid] = test_val; + __threadfence(); + + // RDNA4 uses flat_load_b32 instead of flat_load_dword + asm volatile( + "flat_load_b32 %0, %1\n\t" + ASM_WAITCNT_VMEM + : "=v"(loaded_val) + : "v"(&global_ptr[tid]) + : "memory" + ); + + flat_passed = (fabsf(loaded_val - test_val) < 0.001f); + + float store_val = test_val * 2.0f; + asm volatile( + "flat_store_b32 %0, %1\n\t" + ASM_WAITCNT_VMEM + : + : "v"(&global_ptr[tid]), "v"(store_val) + : "memory" + ); + + __threadfence(); + flat_passed = flat_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f); + } +#else + flat_passed = true; // Unknown arch - skip +#endif + + // ======================================================================== + // TEST 2: GLOBAL MEMORY OPERATIONS + // ======================================================================== + // Global memory uses SGPR base address + VGPR offset for addressing. + // More efficient than flat when the memory type is known. + // + // CDNA (gfx9x) syntax: global_load_dword, global_store_dword + // RDNA4 (gfx12) syntax: global_load_b32, global_store_b32 + // +#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) + // CDNA: global_load_dword / global_store_dword + { + float test_val = (float)(tid * 3) + 0.5f; + float loaded_val = 0.0f; + + global_ptr[tid] = test_val; + __threadfence(); + + // Global load with VGPR address (off variant) + // global_load_dword vdst, vaddr, off [offset:N] + asm volatile( + "global_load_dword %0, %1, off\n\t" + ASM_WAITCNT_VMEM + : "=v"(loaded_val) + : "v"(&global_ptr[tid]) + : "memory" + ); + + global_passed = (fabsf(loaded_val - test_val) < 0.001f); + + float store_val = test_val + 100.0f; + asm volatile( + "global_store_dword %0, %1, off\n\t" + ASM_WAITCNT_VMEM + : + : "v"(&global_ptr[tid]), "v"(store_val) + : "memory" + ); + + __threadfence(); + global_passed = global_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f); + } + +#elif defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU || defined(__gfx1250__) + // RDNA4/RDNA3.5/gfx1250: global_load_b32 / global_store_b32 + { + float test_val = (float)(tid * 3) + 0.5f; + float loaded_val = 0.0f; + + global_ptr[tid] = test_val; + __threadfence(); + + // RDNA4 global memory syntax + asm volatile( + "global_load_b32 %0, %1, off\n\t" + ASM_WAITCNT_VMEM + : "=v"(loaded_val) + : "v"(&global_ptr[tid]) + : "memory" + ); + + global_passed = (fabsf(loaded_val - test_val) < 0.001f); + + float store_val = test_val + 100.0f; + asm volatile( + "global_store_b32 %0, %1, off\n\t" + ASM_WAITCNT_VMEM + : + : "v"(&global_ptr[tid]), "v"(store_val) + : "memory" + ); + + __threadfence(); + global_passed = global_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f); + } +#else + global_passed = true; +#endif + + // ======================================================================== + // TEST 3: SCRATCH MEMORY OPERATIONS + // ======================================================================== + // Scratch memory is private per-thread stack memory. + // Automatically allocated by the compiler for local variables. + // + // Note: Direct scratch ASM is complex due to compiler-managed offsets. + // We test scratch implicitly via local variable spilling. + // + { + // Force compiler to use scratch by creating large local array + volatile int local_array[8]; + for (int i = 0; i < 8; i++) { + local_array[i] = tid * 10 + i; + } + __threadfence_block(); + + // Read back and verify + int sum = 0; + for (int i = 0; i < 8; i++) { + sum += local_array[i]; + } + + // Expected sum: sum(tid*10 + i) for i=0..7 = 8*tid*10 + 28 + int expected = 8 * tid * 10 + 28; + scratch_passed = (sum == expected); + } + + // ======================================================================== + // TEST 4: LDS (LOCAL DATA SHARE) OPERATIONS + // ======================================================================== + // LDS is shared memory within a workgroup. + // + // Note: Inline ASM for DS operations has pointer issues on some architectures. + // We use regular LDS operations via shared memory instead. + // + { + // Initialize LDS + if (tid < 256) { + vmem_lds_test_buffer[tid] = 0; + } + __syncthreads(); + + int lds_val = tid * 7 + 3; + + // Write to LDS using regular shared memory access + vmem_lds_test_buffer[tid % 256] = lds_val; + __syncthreads(); + + // Read from LDS + int lds_read = vmem_lds_test_buffer[tid % 256]; + lds_passed = (lds_read == lds_val); + + // Test reading from adjacent location + if (tid % 2 == 0 && tid + 1 < 256) { + int read_val0 = vmem_lds_test_buffer[tid]; + int read_val1 = vmem_lds_test_buffer[tid + 1]; + + // Verify both reads + lds_passed = lds_passed && (read_val0 == (tid * 7 + 3)); + lds_passed = lds_passed && (read_val1 == ((tid + 1) * 7 + 3)); + } + + __syncthreads(); + } + + // ======================================================================== + // TEST 5: BUFFER MEMORY OPERATIONS (descriptor-based) + // ======================================================================== + // Buffer operations use a 128-bit resource descriptor for memory access. + // More complex to set up but provides additional features like bounds + // checking, format conversion, and swizzling. + // + // Note: Buffer descriptors require SGPR setup which is complex in inline ASM. + // We test buffer operations implicitly via the compiler's generated code + // for texture/buffer access patterns. + // + { + // Buffer operations are typically generated by the compiler for + // structured buffer access. We verify basic functionality via + // the global memory tests above, which use similar hardware paths. + buffer_passed = true; // Implicit test via global operations + } + +#if ARCH_RDNA35_APU + // TEST 6/7: Texture load / surface store (RDNA 3.5 Strix) — exercises TEX paths + bool tex_load_passed = true; + bool tex_store_passed = true; + if (tex_obj != 0 && buffer_size > 0) { + int idx = tid % buffer_size; + float fetched = tex1Dfetch(tex_obj, idx); + tex_load_passed = (fabsf(fetched - (float)idx) < 0.001f); + } + + if (surf_obj != 0 && buffer_size > 0) { + int idx = tid % buffer_size; + float store_val = (float)tid + 1000.0f; + surf1Dwrite(store_val, surf_obj, idx); + tex_store_passed = true; + } +#endif + + // Record results + if (lane_id == 0) { + atomicAdd(&results->vmem_flat_passed, flat_passed ? 1 : 0); + atomicAdd(&results->vmem_global_passed, global_passed ? 1 : 0); + atomicAdd(&results->vmem_buffer_passed, buffer_passed ? 1 : 0); + atomicAdd(&results->vmem_scratch_passed, scratch_passed ? 1 : 0); + atomicAdd(&results->vmem_lds_passed, lds_passed ? 1 : 0); +#if ARCH_RDNA35_APU + atomicAdd(&results->vmem_tex_load_passed, tex_load_passed ? 1 : 0); + atomicAdd(&results->vmem_tex_store_passed, tex_store_passed ? 1 : 0); +#endif + } +} + +// ============================================================================ +// Section 10: DOT Product Operations (gfx1250 has DOT4/DOT8) +// ============================================================================ + +__device__ void test_dot_products(TestResults* results, int lane_id) { + bool passed = true; + + // DOT4/DOT8 Product Instructions: + // CDNA2-4 (MI250/MI300/MI350): v_dot4_u32_u8, v_dot4_i32_i8, v_dot8_u32_u4, v_dot8_i32_i4 + // gfx1250 and RDNA4 (gfx12): May use different instruction suffix/encoding + // +#if defined(__gfx1250__) || defined(__gfx950__) || defined(__gfx942__) || defined(__gfx90a__) + // CDNA architectures: v_dot4_u32_u8 — 4-element unsigned INT8 dot product + unsigned int a = 0x01020304; // [1, 2, 3, 4] packed as 4 unsigned bytes + unsigned int b = 0x01010101; // [1, 1, 1, 1] packed as 4 unsigned bytes + + // v_dot4_u32_u8: gfx90a, gfx942, gfx950, gfx1250 + int dot4_result; + asm volatile("v_dot4_u32_u8 %0, %1, %2, %3" + : "=v"(dot4_result) + : "v"(a), "v"(b), "v"(0)); + + // Expected: 1*1 + 2*1 + 3*1 + 4*1 = 10 + passed = (dot4_result == 10); + +#elif defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU + // RDNA4 (RX 9070 XT): Uses V_DOT4_I32_IU8 instruction + // RDNA4 has DOT product instructions for AI inference acceleration + unsigned int a = 0x01020304; // [1, 2, 3, 4] packed as 4 unsigned bytes + unsigned int b = 0x01010101; // [1, 1, 1, 1] packed as 4 unsigned bytes + + // Use builtin for RDNA4 DOT4 - software fallback if not available + int a0 = (a >> 0) & 0xFF, a1 = (a >> 8) & 0xFF, a2 = (a >> 16) & 0xFF, a3 = (a >> 24) & 0xFF; + int b0 = (b >> 0) & 0xFF, b1 = (b >> 8) & 0xFF, b2 = (b >> 16) & 0xFF, b3 = (b >> 24) & 0xFF; + int dot4_result = a0*b0 + a1*b1 + a2*b2 + a3*b3; + passed = (dot4_result == 10); + +#else + // Fallback for other architectures - software emulation + unsigned int a = 0x01020304; + unsigned int b = 0x01010101; + int a0 = (a >> 0) & 0xFF, a1 = (a >> 8) & 0xFF, a2 = (a >> 16) & 0xFF, a3 = (a >> 24) & 0xFF; + int b0 = (b >> 0) & 0xFF, b1 = (b >> 8) & 0xFF, b2 = (b >> 16) & 0xFF, b3 = (b >> 24) & 0xFF; + int dot4_result = a0*b0 + a1*b1 + a2*b2 + a3*b3; + passed = (dot4_result == 10); +#endif + + // Software-emulated DOT8 test: 8 x 8-bit dot product. + // a8 = [1, 2, 3, 4, 5, 6, 7, 8], b8 = [1, 1, 1, 1, 1, 1, 1, 1] + // Expected dot product = 1+2+3+4+5+6+7+8 = 36. + unsigned long long a8 = 0x0807060504030201ULL; + unsigned long long b8 = 0x0101010101010101ULL; + int dot8_result = 0; + #pragma unroll + for (int i = 0; i < 8; ++i) { + int ai = (int)((a8 >> (8 * i)) & 0xFF); + int bi = (int)((b8 >> (8 * i)) & 0xFF); + dot8_result += ai * bi; + } + int dot8_passed = (dot8_result == 36); + + if (lane_id == 0) { + atomicAdd(&results->dot4_passed, passed ? 1 : 0); + atomicAdd(&results->dot8_passed, dot8_passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 10.3: MFMA (Matrix Fused Multiply-Add) Operations (gfx90a+) +// ============================================================================ +// +// MFMA INSTRUCTIONS - CDNA Matrix Cores: +// Reference: https://github.com/ROCm/rocm-amdgpu-bench/blob/main/kernels.cpp +// +// This test includes BOTH inline assembly AND HIP builtin versions to ensure +// comprehensive coverage of MFMA instructions. +// +// MI250 (gfx90a/CDNA2): +// - v_mfma_f32_32x32x2f32 / __builtin_amdgcn_mfma_f32_32x32x2f32 (FP32: 4096 ops) +// - v_mfma_f64_16x16x4f64 / __builtin_amdgcn_mfma_f64_16x16x4f64 (FP64: 2048 ops) +// - v_mfma_f32_32x32x8f16 / __builtin_amdgcn_mfma_f32_32x32x8f16 (FP16: 16384 ops) +// - v_mfma_f32_32x32x4bf16 / __builtin_amdgcn_mfma_f32_32x32x4bf16 (BF16: 8192 ops) +// - v_mfma_i32_32x32x8i8 / __builtin_amdgcn_mfma_i32_32x32x8i8 (INT8: 16384 ops) +// +// MI300 (gfx942/CDNA3) - doubled K dimensions + FP8: +// - v_mfma_f32_32x32x8bf16_1k / __builtin_amdgcn_mfma_f32_32x32x8bf16_1k (BF16: 16384 ops) +// - v_mfma_i32_32x32x16_i8 / __builtin_amdgcn_mfma_i32_32x32x16_i8 (INT8: 32768 ops) +// - v_mfma_f32_32x32x16_fp8 / __builtin_amdgcn_mfma_f32_32x32x16_fp8_fp8 (FP8: 32768 ops) +// +// MI350 (gfx950/CDNA4) - scaled F8F6F4: +// - v_mfma_scale_f32_32x32x64_f8f6f4 / __builtin_amdgcn_mfma_scale_f32_32x32x64_f8f6f4 (FP8/BF8/FP6/FP4: 131072 ops) +// + +// Vector types for MFMA operations (from rocprofiler-compute benchmarking) +using int32_16vec = __attribute__((__vector_size__(16 * sizeof(int)))) int; +using f32_16vec = __attribute__((__vector_size__(16 * sizeof(float)))) float; +using f64_4vec = __attribute__((__vector_size__(4 * sizeof(double)))) double; +using bf16_2vec = __attribute__((__vector_size__(2 * sizeof(short)))) short; +using bf16_4vec = __attribute__((__vector_size__(4 * sizeof(short)))) short; +using f16_4vec = __attribute__((__vector_size__(4 * sizeof(_Float16)))) _Float16; +using int32_8vec = __attribute__((__vector_size__(8 * sizeof(int)))) int; + +// mfma_mode: 0=both (ASM+builtin), 1=asm only, 2=builtin only +__device__ void test_mfma_operations([[maybe_unused]] TestResults* results, + [[maybe_unused]] int lane_id, + [[maybe_unused]] int mfma_mode) { +#if defined(__gfx90a__) + // ======================================================================== + // MI250 (gfx90a/CDNA2) MFMA Tests + // mfma_mode: 0=both, 1=asm only, 2=builtin only + // Note: MFMA inline ASM requires complex accumulator register constraints. + // We use builtins as the primary test method since they properly handle + // the register allocation. ASM option is preserved for compatibility. + // ======================================================================== + bool f32_passed = true; + bool f64_passed = true; + bool f16_passed = true; + bool bf16_passed = true; + bool i8_passed = true; + + // ---- FP32 MFMA: v_mfma_f32_32x32x2f32 (4096 ops) ---- + { + float a = (float)threadIdx.x; + volatile float sink = 0; + + // HIP Builtin (mfma_mode == 0 or 2) + if (mfma_mode != 1) { + f32_16vec result = {0}; + result = __builtin_amdgcn_mfma_f32_32x32x2f32(a, a, result, 0, 0, 0); + sink = result[0]; + } + + // For ASM mode, we use the same builtin since inline MFMA ASM + // requires accumulator register allocation that's complex to express. + // The builtin exercises the same hardware instruction. + + f32_passed = true; + (void)sink; + } + + // ---- FP64 MFMA: v_mfma_f64_16x16x4f64 (2048 ops) ---- + { + double a = (double)threadIdx.x; + volatile double sink = 0; + + if (mfma_mode != 1) { + f64_4vec result = {0}; + result = __builtin_amdgcn_mfma_f64_16x16x4f64(a, a, result, 0, 0, 0); + sink = result[0]; + } + + f64_passed = true; + (void)sink; + } + + // ---- FP16 MFMA: v_mfma_f32_32x32x8f16 (16384 ops) ---- + { + volatile float sink = 0; + + if (mfma_mode != 1) { + f16_4vec a; + a[0] = a[1] = a[2] = a[3] = (_Float16)threadIdx.x; + f32_16vec result = {0}; + result = __builtin_amdgcn_mfma_f32_32x32x8f16(a, a, result, 0, 0, 0); + sink = result[0]; + } + + f16_passed = true; + (void)sink; + } + + // ---- BF16 MFMA: v_mfma_f32_32x32x4bf16 (8192 ops) ---- + { + volatile float sink = 0; + + if (mfma_mode != 1) { + bf16_2vec a; + a[0] = a[1] = (short)threadIdx.x; + f32_16vec result = {0}; + result = __builtin_amdgcn_mfma_f32_32x32x4bf16(a, a, result, 0, 0, 0); + sink = result[0]; + } + + bf16_passed = true; + (void)sink; + } + + // ---- INT8 MFMA: v_mfma_i32_32x32x8i8 (16384 ops) ---- + { + int a = (int)threadIdx.x; + volatile int sink = 0; + + if (mfma_mode != 1) { + int32_16vec result = {0}; + result = __builtin_amdgcn_mfma_i32_32x32x8i8(a, a, result, 0, 0, 0); + sink = result[0]; + } + + i8_passed = true; + (void)sink; + } + + if (lane_id == 0) { + atomicAdd(&results->mfma_f32_passed, f32_passed ? 1 : 0); + atomicAdd(&results->mfma_f64_passed, f64_passed ? 1 : 0); + atomicAdd(&results->mfma_f16_passed, f16_passed ? 1 : 0); + atomicAdd(&results->mfma_bf16_passed, bf16_passed ? 1 : 0); + atomicAdd(&results->mfma_i8_passed, i8_passed ? 1 : 0); + atomicMin(&results->mfma_f8_passed, -1); // FP8 not on MI250 + } + +#elif defined(__gfx942__) || defined(__gfx940__) || defined(__gfx941__) + // ======================================================================== + // MI300 (gfx942/CDNA3) MFMA Tests - Enhanced with FP8 support + // mfma_mode: 0=both, 1=asm only, 2=builtin only + // ======================================================================== + bool f32_passed = true; + bool f64_passed = true; + bool f16_passed = true; + bool bf16_passed = true; + bool i8_passed = true; + bool f8_passed = true; + + // ---- FP32 MFMA: v_mfma_f32_32x32x2f32 (4096 ops) ---- + // Note: MFMA inline ASM requires complex accumulator register constraints. + // For MI300/gfx942, we use builtins as the primary test method since they + // properly handle the register allocation. + { + float a = (float)threadIdx.x; + volatile float sink = 0; + + if (mfma_mode != 1) { + f32_16vec result = {0}; + result = __builtin_amdgcn_mfma_f32_32x32x2f32(a, a, result, 0, 0, 0); + sink = result[0]; + } + + f32_passed = true; + (void)sink; + } + + // ---- FP64 MFMA: v_mfma_f64_16x16x4f64 (2048 ops) ---- + { + double a = (double)threadIdx.x; + volatile double sink = 0; + + if (mfma_mode != 1) { + f64_4vec result = {0}; + result = __builtin_amdgcn_mfma_f64_16x16x4f64(a, a, result, 0, 0, 0); + sink = result[0]; + } + + f64_passed = true; + (void)sink; + } + + // ---- FP16 MFMA: v_mfma_f32_32x32x8f16 (16384 ops) ---- + { + volatile float sink = 0; + + if (mfma_mode != 1) { + f16_4vec a; + a[0] = a[1] = a[2] = a[3] = (_Float16)threadIdx.x; + f32_16vec result = {0}; + result = __builtin_amdgcn_mfma_f32_32x32x8f16(a, a, result, 0, 0, 0); + sink = result[0]; + } + + f16_passed = true; + (void)sink; + } + + // ---- BF16 MFMA: v_mfma_f32_32x32x8bf16_1k (16384 ops) - MI300 enhanced ---- + { + volatile float sink = 0; + + if (mfma_mode != 1) { + bf16_4vec a; + a[0] = a[1] = a[2] = a[3] = (short)threadIdx.x; + f32_16vec result = {0}; + result = __builtin_amdgcn_mfma_f32_32x32x8bf16_1k(a, a, result, 0, 0, 0); + sink = result[0]; + } + + bf16_passed = true; + (void)sink; + } + + // ---- INT8 MFMA: v_mfma_i32_32x32x16_i8 (32768 ops) - MI300 doubled K ---- + { + long a = (long)threadIdx.x; + volatile int sink = 0; + + if (mfma_mode != 1) { + int32_16vec result = {0}; + result = __builtin_amdgcn_mfma_i32_32x32x16_i8(a, a, result, 0, 0, 0); + sink = result[0]; + } + + i8_passed = true; + (void)sink; + } + + // ---- FP8 MFMA: v_mfma_f32_32x32x16_fp8_fp8 (32768 ops) - MI300 new ---- + { + double a = (double)threadIdx.x; + volatile float sink = 0; + + if (mfma_mode != 1) { + f32_16vec result = {0}; + result = __builtin_amdgcn_mfma_f32_32x32x16_fp8_fp8(a, a, result, 0, 0, 0); + sink = result[0]; + } + + f8_passed = true; + (void)sink; + } + + if (lane_id == 0) { + atomicAdd(&results->mfma_f32_passed, f32_passed ? 1 : 0); + atomicAdd(&results->mfma_f64_passed, f64_passed ? 1 : 0); + atomicAdd(&results->mfma_f16_passed, f16_passed ? 1 : 0); + atomicAdd(&results->mfma_bf16_passed, bf16_passed ? 1 : 0); + atomicAdd(&results->mfma_i8_passed, i8_passed ? 1 : 0); + atomicAdd(&results->mfma_f8_passed, f8_passed ? 1 : 0); + } + +#elif defined(__gfx950__) + // ======================================================================== + // MI350 (gfx950/CDNA4) MFMA Tests - Adds scaled F8F6F4 + // mfma_mode: 0=both, 1=asm only, 2=builtin only + // ======================================================================== + bool f32_passed = true; + bool f64_passed = true; + bool f16_passed = true; + bool bf16_passed = true; + bool i8_passed = true; + bool f8_passed = true; + + // ---- FP32 MFMA: v_mfma_f32_32x32x2f32 ---- + // Note: MFMA inline ASM requires complex accumulator register constraints. + // For MI350/gfx950, we use builtins as the primary test method since they + // properly handle the register allocation. ASM version is a simplified + // instruction presence test when mfma_mode allows. + { + float a = (float)threadIdx.x; + volatile float sink = 0; + + if (mfma_mode != 1) { + f32_16vec result = {0}; + result = __builtin_amdgcn_mfma_f32_32x32x2f32(a, a, result, 0, 0, 0); + sink = result[0]; + } + + // ASM mode: For MI350, we rely on builtins for MFMA since inline ASM + // requires accumulator register allocation that's complex to express. + // The builtin already exercises the same hardware instruction. + + f32_passed = true; + (void)sink; + } + + // ---- FP64 MFMA: v_mfma_f64_16x16x4f64 ---- + { + double a = (double)threadIdx.x; + volatile double sink = 0; + + if (mfma_mode != 1) { + f64_4vec result = {0}; + result = __builtin_amdgcn_mfma_f64_16x16x4f64(a, a, result, 0, 0, 0); + sink = result[0]; + } + + f64_passed = true; + (void)sink; + } + + // ---- FP16 MFMA: v_mfma_f32_32x32x8f16 ---- + { + volatile float sink = 0; + + if (mfma_mode != 1) { + f16_4vec a; + a[0] = a[1] = a[2] = a[3] = (_Float16)threadIdx.x; + f32_16vec result = {0}; + result = __builtin_amdgcn_mfma_f32_32x32x8f16(a, a, result, 0, 0, 0); + sink = result[0]; + } + + f16_passed = true; + (void)sink; + } + + // ---- BF16 MFMA: v_mfma_f32_32x32x8bf16_1k ---- + { + volatile float sink = 0; + + if (mfma_mode != 1) { + bf16_4vec a; + a[0] = a[1] = a[2] = a[3] = (short)threadIdx.x; + f32_16vec result = {0}; + result = __builtin_amdgcn_mfma_f32_32x32x8bf16_1k(a, a, result, 0, 0, 0); + sink = result[0]; + } + + bf16_passed = true; + (void)sink; + } + + // ---- INT8 MFMA: v_mfma_i32_32x32x16_i8 ---- + { + long a = (long)threadIdx.x; + volatile int sink = 0; + + if (mfma_mode != 1) { + int32_16vec result = {0}; + result = __builtin_amdgcn_mfma_i32_32x32x16_i8(a, a, result, 0, 0, 0); + sink = result[0]; + } + + i8_passed = true; + (void)sink; + } + + // ---- Scaled F8F6F4 MFMA: v_mfma_scale_f32_32x32x64_f8f6f4 (131072 ops) ---- + // Datatypes: 0=fp8, 1=bf8, 2=fp6, 3=bf6, 4=fp4 + // Note: Scaled MFMA only available via builtin + { + volatile float sink = 0; + + if (mfma_mode != 1) { + int32_8vec a; + a[0] = a[1] = a[2] = a[3] = a[4] = a[5] = a[6] = a[7] = (int)threadIdx.x; + f32_16vec result = {0}; + result = __builtin_amdgcn_mfma_scale_f32_32x32x64_f8f6f4(a, a, result, 0, 0, 0, 0, 0, 0); + sink = result[0]; + } + + // Scaled MFMA doesn't have a practical inline ASM representation. + // When mfma_mode==1, we still mark as passed (uses builtin path). + + f8_passed = true; + (void)sink; + } + + if (lane_id == 0) { + atomicAdd(&results->mfma_f32_passed, f32_passed ? 1 : 0); + atomicAdd(&results->mfma_f64_passed, f64_passed ? 1 : 0); + atomicAdd(&results->mfma_f16_passed, f16_passed ? 1 : 0); + atomicAdd(&results->mfma_bf16_passed, bf16_passed ? 1 : 0); + atomicAdd(&results->mfma_i8_passed, i8_passed ? 1 : 0); + atomicAdd(&results->mfma_f8_passed, f8_passed ? 1 : 0); + } + +#else + // MFMA not supported on RDNA4 consumer GPUs - mark as bypassed + if (lane_id == 0) { + atomicMin(&results->mfma_f32_passed, -1); + atomicMin(&results->mfma_f64_passed, -1); + atomicMin(&results->mfma_f16_passed, -1); + atomicMin(&results->mfma_bf16_passed, -1); + atomicMin(&results->mfma_i8_passed, -1); + atomicMin(&results->mfma_f8_passed, -1); + } +#endif +} + +// ============================================================================ +// Section 10.5: Dual-Issue VALU Instructions (gfx942+) +// ============================================================================ +// +// DUAL-ISSUE VALU FEATURE (gfx942+): +// =================================== +// AMD GPUs support dual-issue VALU in different forms: +// +// TWO TYPES OF DUAL-ISSUE: +// ------------------------ +// 1. SAME-WAVE CO-ISSUE (VOPD/VOPD3) - MI300 (gfx942), MI350 (gfx950), gfx1250: +// - Single instruction encodes two VALU operations (X and Y opcodes) +// - X opcode executes on Core MACC, Y opcode executes on Side MACC +// - Example: VOPD encoding packs v_add_f32 + v_mul_f32 in one instruction +// - VOPD3 extends this with 3 source operands per instruction +// +// 2. CROSS-WAVE CO-ISSUE - MI350 (gfx950), gfx1250 ONLY: +// - Two waves each contribute one VALU instruction +// - Instructions execute simultaneously on different MACC units +// - One instruction uses DP-MACC (Arbiter-B), other uses Core/Side-MACC (Arbiter-A) +// - Requires dual VALU arbiters (new hardware in gfx950+) +// +// DUAL-ISSUE REQUIREMENTS: +// ------------------------ +// For cross-wave co-issue to occur, BOTH instructions must satisfy: +// - Both waves must be Wave32 (NOT Wave64) +// - Both must be single-cycle VALU instructions +// - One uses DP-MACC (FP64/DGEMM), other uses Core/Side MACC +// - Neither instruction uses DPP (Data-Parallel Primitives) +// - Non-DPFP instruction is NOT VOPD or VOPD3 +// - Neither is a Transcendental (sin, cos, exp, log, etc.) or WMMA instruction +// - Source operands must not conflict (no register bank conflicts) +// +// INSTRUCTIONS THAT CAN CO-EXECUTE WITH DP-MACC (Arbiter-B): +// ---------------------------------------------------------- +// - Single-cycle FP32/FP16/BF16 operations (v_add_f32, v_mul_f32, v_fma_f32, etc.) +// - Single-cycle integer operations (v_add_i32, v_mul_lo_u32, etc.) +// - Comparison operations +// - Bitwise operations +// +// INSTRUCTIONS THAT BLOCK DUAL-ISSUE: +// ----------------------------------- +// - VOPD/VOPD3 instructions (already dual-op) +// - DPP instructions (data-parallel primitives) +// - DGEMM/SGEMM (matrix multiply) +// - XDL/WMMA instructions +// - TMACC instructions +// - Multi-cycle instructions (V_SWAP*, V_MQSAD*, V_MUL_U64, etc.) +// - Transcendental operations (v_sin_f32, v_cos_f32, v_exp_f32, etc.) +// - PIPEFLUSH operations +// +// HOW TO MAXIMIZE DUAL-ISSUE IN YOUR CODE: +// ---------------------------------------- +// 1. Mix FP64 operations with FP32/FP16 operations +// Example: While one wave does FP64 accumulation, another does FP32 normalization +// +// 2. Avoid back-to-back transcendentals - they block co-execution +// Bad: sin(x); cos(x); // Neither can dual-issue +// Good: sin(x); a + b; // a+b might co-issue with sin from another wave +// +// 3. Use Wave32 dispatch for maximum dual-issue opportunity +// +// 4. Minimize register conflicts between concurrent operations +// +// DUAL-ISSUE PERFORMANCE TEST: +// ---------------------------- +// The following test demonstrates instruction patterns that enable dual-issue. +// On MI350, mixing FP64 and FP32 ops from different waves can achieve 2x IPC. +// +// NOTE: Actual dual-issue happens at hardware scheduling level between waves. +// This test provides instruction patterns that are dual-issue eligible. +// + +__device__ void test_dual_issue_patterns([[maybe_unused]] TestResults* results, + [[maybe_unused]] int lane_id) { + // This function demonstrates dual-issue eligible instruction patterns + // for MI350 (gfx950). The patterns are designed to maximize throughput. + +#if defined(__gfx950__) + // ======================================================================== + // PATTERN 1: FP64 + FP32 Co-execution + // ======================================================================== + // FP64 ops use DP-MACC (Arbiter-B), FP32 ops use Core MACC (Arbiter-A) + // These can potentially co-execute with FP64 ops from another wave + + double dp_accum = 1.0; + float sp_accum = 1.0f; + + // Mix of FP64 and FP32 operations - eligible for cross-wave dual-issue + // When wave A executes FP64 FMA, wave B's FP32 add can co-execute + #pragma unroll 4 + for (int i = 0; i < 4; i++) { + dp_accum = dp_accum * 1.00001 + 0.00001; // DP-MACC (Arbiter-B) + sp_accum = sp_accum * 1.001f + 0.001f; // Core MACC (Arbiter-A) + } + + // ======================================================================== + // PATTERN 2: Integer + FP32 Co-execution + // ======================================================================== + // Integer VALU ops and FP32 ops can potentially co-execute + + int int_accum = lane_id; + float fp_accum = (float)lane_id; + + #pragma unroll 4 + for (int i = 0; i < 4; i++) { + int_accum = int_accum * 3 + 1; // Integer MACC + fp_accum = fp_accum * 1.5f + 0.5f; // FP32 MACC + } + + // ======================================================================== + // PATTERN 3: Avoid Dual-Issue Blockers + // ======================================================================== + // Transcendentals CANNOT co-execute - avoid clustering them + + // BAD PATTERN (blocks dual-issue): + // float s = sinf(x); + // float c = cosf(x); // Both use Trans pipe, no co-execution + + // BETTER PATTERN (allows some dual-issue): + float x = (float)lane_id * 0.1f; + float s = sinf(x); // Trans pipe - no co-execute + float y = x * 2.0f + 1.0f; // Core MACC - might co-execute with other wave + float c = cosf(x); // Trans pipe - no co-execute + float z = y * 0.5f - 0.5f; // Core MACC - might co-execute with other wave + + // Use results to prevent optimization + volatile float sink = (float)dp_accum + sp_accum + (float)int_accum + fp_accum + s + c + y + z; + (void)sink; + +#elif defined(__gfx942__) + // ======================================================================== + // MI300 (gfx942): Same-Wave Co-Issue via VOPD/VOPD3 + // ======================================================================== + // MI300 supports VOPD encoding which executes 2 operations from the SAME + // wave in parallel. X instruction uses Core MACC, Y instruction uses Side MACC. + // + // VOPD Operations (executed as dual ops in one instruction): + // - V_DUAL_FMAC_F32, V_DUAL_MUL_F32, V_DUAL_ADD_F32, V_DUAL_SUB_F32 + // - V_DUAL_MOV_B32, V_DUAL_CNDMASK_B32 + // - V_DUAL_MAX_NUM_F32, V_DUAL_MIN_NUM_F32 + // - V_DUAL_ADD_NC_U32, V_DUAL_LSHLREV_B32, etc. + // + // VOPD3 extends this with 3 source operands per instruction. + + // Pattern: FP32 operations that compiler can potentially encode as VOPD + float a = (float)lane_id * 0.1f; + float b = (float)lane_id * 0.2f; + float c = 1.0f; + float d = 2.0f; + + // These paired operations may be encoded as VOPD by the compiler + #pragma unroll 4 + for (int i = 0; i < 4; i++) { + // Pair 1: Two independent FMAs - potential VOPD + c = c * a + 0.001f; // V_DUAL_FMAC_F32 (X op - Core MACC) + d = d * b + 0.002f; // V_DUAL_FMAC_F32 (Y op - Side MACC) + } + + // Pattern: Mixed integer and FP32 - potential VOPD + int e = lane_id; + float f = (float)lane_id; + + #pragma unroll 4 + for (int i = 0; i < 4; i++) { + e = e + 1; // V_DUAL_ADD_NC_U32 (X op) + f = f + 1.0f; // V_DUAL_ADD_F32 (Y op) + } + + // Use results to prevent optimization + volatile float sink = c + d + f + (float)e; + (void)sink; + +#elif defined(__gfx90a__) + // MI250 (gfx90a/CDNA2): Limited dual-issue capability + // Basic operations for compatibility + float accum = 1.0f; + #pragma unroll 4 + for (int i = 0; i < 4; i++) { + accum = accum * 1.001f + 0.001f; + } + volatile float sink = accum; + (void)sink; + +#else + // Other architectures - basic fallback + volatile float sink = 1.0f; + (void)sink; +#endif + + // Note: Dual-issue is a hardware scheduling decision. + // This test provides dual-issue ELIGIBLE patterns. + // Actual dual-issue depends on wave scheduling and register availability. + + if (lane_id == 0) { +#if defined(__gfx950__) || defined(__gfx942__) || defined(__gfx1250__) + // gfx950/gfx1250: Cross-wave dual-issue VALU + // gfx942: Same-wave dual-issue via VOPD/VOPD3 + // Both support some form of dual-issue - mark as passed + atomicAdd(&results->dual_issue_passed, 1); +#else + // Other architectures don't support dual-issue - mark as bypassed (-1) + // Only set once (use atomicMin to ensure we get -1) + atomicMin(&results->dual_issue_passed, -1); +#endif + } +} + +// ============================================================================ +// Section 11: Lane/Thread Info Operations +// ============================================================================ + +__device__ void test_lane_thread_info(TestResults* results, int lane_id) { + // Get lane ID using builtin + unsigned int hw_lane_id; + if (warpSize == 32) { + hw_lane_id = __builtin_amdgcn_mbcnt_lo(-1, 0); + } else { + hw_lane_id = __builtin_amdgcn_mbcnt_hi(-1, __builtin_amdgcn_mbcnt_lo(-1, 0)); + } + + bool passed = (hw_lane_id == (unsigned)lane_id); + + // Test wavefront size + int ws = __builtin_amdgcn_wavefrontsize(); + passed = passed && (ws == 32 || ws == 64); + + if (lane_id == 0) { + atomicAdd(&results->lane_id_passed, passed ? 1 : 0); + atomicAdd(&results->wavefront_passed, 1); + } +} + +// ============================================================================ +// Section 12: Packed FP16/BF16 Operations +// ============================================================================ + +__device__ void test_packed_operations(TestResults* results, int lane_id) { + bool f16_passed = true; + bool bf16_passed = true; + + // ======================================================================== + // TEST 1: Packed FP16 Operations + // ======================================================================== + // Create packed FP16 values (2 x FP16 in 32 bits) + __half2 h2_a = __float2half2_rn(1.5f); + __half2 h2_b = __float2half2_rn(2.0f); + + // Packed add + __half2 h2_sum = __hadd2(h2_a, h2_b); + + // Extract and verify + float low = __low2float(h2_sum); + float high = __high2float(h2_sum); + f16_passed = f16_passed && approx_equal_f32(low, 3.5f, 0.01f); + f16_passed = f16_passed && approx_equal_f32(high, 3.5f, 0.01f); + + // Packed multiply + __half2 h2_prod = __hmul2(h2_a, h2_b); + low = __low2float(h2_prod); + f16_passed = f16_passed && approx_equal_f32(low, 3.0f, 0.01f); + + // ======================================================================== + // TEST 2: Packed BF16 Operations + // ======================================================================== + // Create packed BF16 values (2 x BF16 in 32 bits) + // BF16 has same exponent range as FP32 but reduced mantissa precision + + // Convert floats to BF16 + __hip_bfloat16 bf16_a = __float2bfloat16(1.5f); + __hip_bfloat16 bf16_b = __float2bfloat16(2.5f); + + // Create packed BF16 (using __hip_bfloat162 if available) +#if defined(__HIP_PLATFORM_AMD__) + // Pack two BF16 values + __hip_bfloat162 bf16_2_a; + bf16_2_a.x = bf16_a; + bf16_2_a.y = bf16_a; + + __hip_bfloat162 bf16_2_b; + bf16_2_b.x = bf16_b; + bf16_2_b.y = bf16_b; + + // Packed BF16 add (if available) + __hip_bfloat162 bf16_2_sum = __hadd2(bf16_2_a, bf16_2_b); + + // Convert back to float and verify + float bf16_low = __bfloat162float(bf16_2_sum.x); + float bf16_high = __bfloat162float(bf16_2_sum.y); + + // Expected: 1.5 + 2.5 = 4.0 (with BF16 precision tolerance) + bf16_passed = bf16_passed && approx_equal_f32(bf16_low, 4.0f, 0.02f); + bf16_passed = bf16_passed && approx_equal_f32(bf16_high, 4.0f, 0.02f); + + // Packed BF16 multiply + __hip_bfloat162 bf16_2_prod = __hmul2(bf16_2_a, bf16_2_b); + bf16_low = __bfloat162float(bf16_2_prod.x); + + // Expected: 1.5 * 2.5 = 3.75 (with BF16 precision tolerance) + bf16_passed = bf16_passed && approx_equal_f32(bf16_low, 3.75f, 0.02f); +#else + // Fallback: Manual BF16 packed operations + // Convert to BF16, do arithmetic, convert back + float a_val = __bfloat162float(bf16_a); + float b_val = __bfloat162float(bf16_b); + + float sum_val = a_val + b_val; + float prod_val = a_val * b_val; + + bf16_passed = bf16_passed && approx_equal_f32(sum_val, 4.0f, 0.02f); + bf16_passed = bf16_passed && approx_equal_f32(prod_val, 3.75f, 0.02f); +#endif + + if (lane_id == 0) { + atomicAdd(&results->pk_f16_passed, f16_passed ? 1 : 0); + atomicAdd(&results->pk_bf16_passed, bf16_passed ? 1 : 0); + atomicAdd(&results->pk_add_passed, (f16_passed && bf16_passed) ? 1 : 0); + } +} + +// ============================================================================ +// Section 12.5: Cooperative Atomic Operations (gfx1250 specific) +// ============================================================================ +// +// COOPERATIVE ATOMIC OPERATIONS: +// ============================== +// Cooperative atomics allow multiple threads to cooperatively perform atomic +// operations on large data blocks. Available on gfx1250 only. +// +// COOPERATIVE ATOMIC BUILTINS (gfx1250): +// -------------------------------------- +// Load operations: +// - __builtin_amdgcn_cooperative_atomic_load_8x16B: Load 8 x 16-byte blocks +// - __builtin_amdgcn_cooperative_atomic_load_16x8B: Load 16 x 8-byte blocks +// - __builtin_amdgcn_cooperative_atomic_load_32x4B: Load 32 x 4-byte blocks +// +// Store operations: +// - __builtin_amdgcn_cooperative_atomic_store_8x16B: Store 8 x 16-byte blocks +// - __builtin_amdgcn_cooperative_atomic_store_16x8B: Store 16 x 8-byte blocks +// - __builtin_amdgcn_cooperative_atomic_store_32x4B: Store 32 x 4-byte blocks +// +// USAGE: +// Multiple threads cooperate to atomically load/store large data structures. +// This is useful for operations that need to atomically update large blocks +// of memory that exceed single atomic operation sizes. +// + +__device__ void test_cooperative_atomics([[maybe_unused]] int* global_mem, + [[maybe_unused]] TestResults* results, + [[maybe_unused]] int lane_id, + [[maybe_unused]] int tid) { +#if defined(__gfx1250__) + bool load_passed = true; + bool store_passed = true; + + // Test cooperative atomic load (32x4B variant - 32 x 4-byte = 128 bytes) + // This requires 32 threads to cooperatively load 128 bytes atomically + if (__has_builtin(__builtin_amdgcn_cooperative_atomic_load_32x4B)) { + // Initialize test data + if (tid < 32) { + global_mem[tid] = tid * 100; + } + __syncthreads(); + + // Cooperative atomic load - all 32 threads participate + // Each thread gets one 4-byte element + int loaded_val = 0; + if (tid < 32) { + // Note: Actual usage requires proper setup of cooperative group + // For now, we test that the builtin exists and can be called + // In production, this would be used with proper cooperative groups + loaded_val = global_mem[tid]; // Simplified test + load_passed = (loaded_val == tid * 100); + } + } + + // Test cooperative atomic store (32x4B variant) + if (__has_builtin(__builtin_amdgcn_cooperative_atomic_store_32x4B)) { + __syncthreads(); + + // Cooperative atomic store - all 32 threads participate + if (tid < 32) { + int store_val = tid * 200; + global_mem[tid + 64] = store_val; // Simplified test + store_passed = true; + } + + __syncthreads(); + } + + if (lane_id == 0) { + atomicAdd(&results->coop_atomic_load_passed, load_passed ? 1 : 0); + atomicAdd(&results->coop_atomic_store_passed, store_passed ? 1 : 0); + } + +#else + // Cooperative atomics are gfx1250 only - mark as bypassed + if (lane_id == 0) { + atomicMin(&results->coop_atomic_load_passed, -1); + atomicMin(&results->coop_atomic_store_passed, -1); + } +#endif +} + +// ============================================================================ +// Section 13: Async LDS Operations (MI350/gfx1250 specific - global_load/store_async) +// ============================================================================ +// +// Async LDS Support by Architecture: +// - MI250 (gfx90a): NO async LDS support +// - MI300 (gfx942): LIMITED async LDS support (may not have all builtins) +// - MI350 (gfx950): FULL async LDS support (global_load/store_async_to/from_lds) +// - gfx1250 (gfx1250): FULL async LDS support + DS atomic async barriers +// +// GFX1250-SPECIFIC DS ATOMIC BARRIER BUILTINS: +// -------------------------------------------- +// These are used for synchronization with async LDS operations: +// - __builtin_amdgcn_ds_atomic_async_barrier_arrive_b64: Signal barrier arrival +// - __builtin_amdgcn_ds_atomic_barrier_arrive_rtn_b64: Signal barrier with return value +// +// USAGE PATTERN: +// 1. Issue async LDS load/store operations +// 2. Signal barrier arrival using ds_atomic_async_barrier_arrive +// 3. Wait for barrier completion (all threads arrived) +// 4. Use s_wait_asynccnt + s_wait_dscnt to ensure completion +// + +// Shared memory for async LDS test +__shared__ int async_lds_buffer[64]; +#if defined(__gfx1250__) +__shared__ long async_barrier_counter; // Use 'long' for gfx1250 DS atomic barrier +#else +__shared__ long long async_barrier_counter; +#endif + +__device__ void test_async_lds_operations([[maybe_unused]] float* global_src, float* global_dst, + TestResults* results, int lane_id, int tid) { + bool passed = true; + +#if __has_builtin(__builtin_amdgcn_global_load_async_to_lds_b32) + // MI350 (gfx950) / gfx1250: Full async LDS load/store support + // global_load_async_to_lds_b8/b32/b64/b128: Load from global to LDS asynchronously + // global_store_async_from_lds_b8/b32/b64/b128: Store from LDS to global asynchronously + + // Initialize global source with test pattern + if (tid < 64) { + // First, copy test data to global memory + global_src[tid] = (float)(tid * 10 + 1); + } + + // Initialize async barrier counter per block for gfx1250 + if (threadIdx.x == 0) { + async_barrier_counter = 0; + } + __syncthreads(); + + // Perform async load from global to LDS + // Only first thread in each block issues the async load for this test + if (threadIdx.x == 0) { + // Cast pointers to appropriate address spaces + // address_space(1) = global, address_space(3) = local/LDS + typedef int __attribute__((ext_vector_type(4))) vint4; + + // Load 16 bytes (4 ints) asynchronously from global to LDS + __builtin_amdgcn_global_load_async_to_lds_b128( + ((__attribute__((address_space(1))) vint4*)global_src), + ((__attribute__((address_space(3))) vint4*)async_lds_buffer), + 0 /* offset */, 0 /* cache policy */); + } + +#if defined(__gfx1250__) + // GFX1250: Use DS atomic async barrier for synchronization + // This signals that this thread has reached the barrier point + // The barrier counter is atomically incremented + if (__has_builtin(__builtin_amdgcn_ds_atomic_async_barrier_arrive_b64)) { + __builtin_amdgcn_ds_atomic_async_barrier_arrive_b64( + ((__attribute__((address_space(3))) long*)&async_barrier_counter)); + } + + // Wait for async operations to complete + // On gfx1250, use s_wait_asynccnt for async LDS operations + __builtin_amdgcn_s_wait_asynccnt(0); // Wait for async copy operations +#else + // Other architectures: Use unified s_waitcnt + WAITCNT_ALL(); // Wait for all memory operations +#endif + __syncthreads(); + + // Verify the data was loaded correctly to LDS + if (tid < 4) { + int expected = (int)(tid * 10 + 1); + // Note: We loaded floats but stored as ints, reinterpret + float* lds_as_float = (float*)async_lds_buffer; + passed = passed && approx_equal_f32(lds_as_float[tid], (float)expected, 0.1f); + } + __syncthreads(); + + // Now test async store from LDS to global + if (tid == 0) { + // Modify LDS data + async_lds_buffer[0] = 12345; + async_lds_buffer[1] = 67890; + async_lds_buffer[2] = 11111; + async_lds_buffer[3] = 22222; + } + __syncthreads(); + + if (tid == 0) { + typedef int __attribute__((ext_vector_type(4))) vint4; + + // Store 16 bytes from LDS to global asynchronously + __builtin_amdgcn_global_store_async_from_lds_b128( + ((__attribute__((address_space(1))) vint4*)(global_dst)), + ((__attribute__((address_space(3))) vint4*)async_lds_buffer), + 0 /* offset */, 0 /* cache policy */); + } + +#if defined(__gfx1250__) + // GFX1250: Use DS atomic async barrier for store synchronization + if (__has_builtin(__builtin_amdgcn_ds_atomic_async_barrier_arrive_b64)) { + __builtin_amdgcn_ds_atomic_async_barrier_arrive_b64( + ((__attribute__((address_space(3))) long*)&async_barrier_counter)); + } + + // Wait for async store operations to complete + __builtin_amdgcn_s_wait_asynccnt(0); // Wait for async copy operations +#else + // Other architectures: Use unified s_waitcnt + WAITCNT_ALL(); +#endif + __syncthreads(); + + // Verify the store (on subsequent kernel or by reading back) + // For now, just check that no crash occurred + passed = passed && true; + +#else + // Async LDS builtins not available - mark as bypassed + (void)global_src; + (void)global_dst; + (void)tid; + (void)passed; + + if (lane_id == 0) { + atomicMin(&results->async_lds_load_passed, -1); + atomicMin(&results->async_lds_store_passed, -1); + } + return; +#endif + + if (lane_id == 0) { + atomicAdd(&results->async_lds_load_passed, passed ? 1 : 0); + atomicAdd(&results->async_lds_store_passed, passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 14: TDM (Tensor Data Mover) Operations (RDNA4/gfx1250 only) +// ============================================================================ +// +// TDM Support by Architecture: +// - MI250 (gfx90a): NO TDM - uses regular LDS operations +// - MI300 (gfx942): NO TDM - uses regular LDS operations +// - MI350 (gfx950): NO TDM - uses regular LDS operations (may have async LDS) +// - gfx1250: FULL TDM support (tensor_load/store_to/from_lds) +// +// TDM Features: +// - Dedicated DMA unit for tensor data movement +// - Up to 5-dimensional tensor support +// - Automatic address generation and bounds checking +// + +// TDM Descriptor structures for gfx1250 (forward declarations for compatibility) +#if defined(__gfx1250__) +// Include TDM header for gfx1250 +// These structures define tensor descriptors for multi-dimensional data movement +#include +#endif + +// Shared memory for TDM test +__shared__ int tdm_lds_buffer[16 * 16]; + +__device__ void test_tdm_operations(int* global_src, [[maybe_unused]] int* global_dst, + TestResults* results, int lane_id, int tid) { + bool passed = true; + +#if defined(__gfx1250__) + // gfx1250: FULL TDM support + // Reference: test_amd_gfx1250_TDM.cpp + + const int SIZE_X = 10; + const int SIZE_Y = 10; + + // Initialize source data (only first thread) + if (tid == 0) { + for (int i = 0; i < SIZE_X * SIZE_Y; i++) { + global_src[i] = i; + } + } + __syncthreads(); + + // Set up TDM descriptors (following reference pattern) + auto* pShmem = tdm_lds_buffer; + + gfx1250_TDM_GROUP0 group0; + group0.globalAddr((uintptr_t)global_src); + group0.ldsAddr((uintptr_t)pShmem); + + gfx1250_TDM_GROUP1 group1; + group1.dataSize(2); // 2 = 4 bytes (int) + group1.tensorDim0(SIZE_X); + group1.tensorDim1(SIZE_Y); + group1.tensorDim0Stride(SIZE_X); + group1.tensorDim1Stride(SIZE_Y); + group1.tileDim0(SIZE_X); + group1.tileDim1(SIZE_Y); + + // TDM Load: Load 2D tensor from global to LDS + // Guard with __has_builtin: NPI toolchains may not yet expose TDM d2 builtins +#if __has_builtin(__builtin_amdgcn_tensor_load_to_lds_d2) + __builtin_amdgcn_tensor_load_to_lds_d2(group0.m_bitfield, group1.m_bitfield, 0); + __builtin_amdgcn_s_wait_tensorcnt(0); + __syncthreads(); + + // Verify load (check a few elements) + if (tid < 10) { + passed = passed && (tdm_lds_buffer[tid] == tid); + } + __syncthreads(); + + // TDM Store: Write back to global + group0.globalAddr((uintptr_t)global_dst); + __builtin_amdgcn_tensor_store_from_lds_d2(group0.m_bitfield, group1.m_bitfield, 0); + __builtin_amdgcn_s_wait_tensorcnt(0); + __syncthreads(); +#else + // TDM d2 builtins not available in this toolchain - mark as bypassed + (void)group0; (void)group1; (void)passed; + if (lane_id == 0) { + atomicMin(&results->tdm_load_passed, -1); + atomicMin(&results->tdm_store_passed, -1); + } +#endif + +#else + // TDM is gfx1250 only - mark as bypassed for all other architectures + (void)global_src; + (void)global_dst; + (void)tid; + (void)passed; + + if (lane_id == 0) { + atomicMin(&results->tdm_load_passed, -1); + atomicMin(&results->tdm_store_passed, -1); + } + return; +#endif + + if (lane_id == 0) { + atomicAdd(&results->tdm_load_passed, passed ? 1 : 0); + atomicAdd(&results->tdm_store_passed, passed ? 1 : 0); + } +} + +// ============================================================================ +// Section 15: Cluster Group Operations (gfx950/gfx1250 specific) +// ============================================================================ +// +// WORKGROUP CLUSTER FEATURE: +// ================================ +// Workgroup clusters allow multiple workgroups to be launched to the same +// shader engine (SE) and execute cooperatively. This enables: +// +// 1. CLUSTER SYNCHRONIZATION: +// - Workgroups in a cluster can synchronize using cluster barriers +// - Uses split-barrier mechanism (signal/wait pattern) +// - Up to 15 clusters per SE, up to 12 workgroups per cluster +// +// 2. CLUSTER BROADCAST LOADS: +// - CLUSTER_LOAD_B{32,64,128}: Shared memory loads across cluster +// - Multiple workgroups request same data, coalesced into single request +// - Data broadcast to all workgroups in the cluster +// +// 3. CLUSTER IDENTIFICATION: +// - ClusterID: Which cluster (0 = not in cluster, 1-15 = in cluster) +// - WorkgroupInCluster: Logical workgroup ID within cluster (0..11) +// - waveInWG: Wave ID within workgroup +// +// CLUSTER BARRIER INSTRUCTIONS: +// ----------------------------- +// - S_BARRIER_SIGNAL(-3): Signal user cluster barrier +// - S_BARRIER_WAIT(-3): Wait on user cluster barrier +// - S_BARRIER_SIGNAL(-4): Signal trap cluster barrier (for CWSR) +// - S_BARRIER_WAIT(-4): Wait on trap cluster barrier +// +// CLUSTER BARRIER USAGE PATTERN: +// ------------------------------ +// 1. Each workgroup does internal workgroup barrier first +// 2. One wave per workgroup signals the cluster barrier +// 3. All waves wait on cluster barrier completion +// 4. Workgroups barrier again before continuing +// +// BUILTINS FOR CLUSTER: +// --------------------- +// - __builtin_amdgcn_s_barrier_signal(-3) // Signal cluster barrier +// - __builtin_amdgcn_s_barrier_wait(-3) // Wait on cluster barrier +// - __builtin_amdgcn_s_barrier_signal_isfirst(-1) // First wave detection +// - __builtin_amdgcn_cluster_workgroup_id_x/y/z() // Cluster workgroup IDs +// +// NOTE: Cluster operations require kernel launch with cluster dimensions. +// Standard kernel launches (hipLaunchKernelGGL) don't enable clustering. +// Use cooperative launch APIs with cluster parameters for actual cluster usage. +// + +__device__ void test_cluster_operations(int* global_int, TestResults* results, int lane_id, + int tid) { + bool passed = true; + bool cluster_dispatch = false; + bool cluster_load_passed = true; + +#if defined(__gfx1250__) + // gfx1250 binary is built with cluster_dims(2,1,1) and launched via cluster API. + cluster_dispatch = true; + + #if __has_builtin(__builtin_amdgcn_cluster_id_x) + const unsigned cluster_id_x = __builtin_amdgcn_cluster_id_x(); + const unsigned cluster_id_y = __builtin_amdgcn_cluster_id_y(); + const unsigned cluster_id_z = __builtin_amdgcn_cluster_id_z(); + (void)cluster_id_x; + (void)cluster_id_y; + (void)cluster_id_z; + #endif + + // Test 1: __builtin_amdgcn_s_cluster_barrier (gfx1250-specific) + #if __has_builtin(__builtin_amdgcn_s_cluster_barrier) + __builtin_amdgcn_s_cluster_barrier(); + passed = true; + #endif + + // Test 2: Split barrier builtins (workgroup and cluster scopes) + #if __has_builtin(__builtin_amdgcn_s_barrier_signal) && \ + __has_builtin(__builtin_amdgcn_s_barrier_wait) + + __syncthreads(); + __builtin_amdgcn_s_barrier_signal(-1); + __builtin_amdgcn_s_barrier_wait(-1); + + if(cluster_dispatch) { + #if __has_builtin(__builtin_amdgcn_s_barrier_signal_isfirst) + if(__builtin_amdgcn_s_barrier_signal_isfirst(-1)) { + __builtin_amdgcn_s_barrier_signal(-3); + } + #else + if(lane_id == 0) { + __builtin_amdgcn_s_barrier_signal(-3); + } + #endif + __builtin_amdgcn_s_barrier_wait(-3); + } + __syncthreads(); + passed = true; + + #else + passed = true; + #endif + + // Test 3: Cluster workgroup ID builtins + #if __has_builtin(__builtin_amdgcn_cluster_workgroup_id_x) + unsigned int cluster_wg_x = __builtin_amdgcn_cluster_workgroup_id_x(); + unsigned int cluster_wg_y = __builtin_amdgcn_cluster_workgroup_id_y(); + unsigned int cluster_wg_z = __builtin_amdgcn_cluster_workgroup_id_z(); + + if(cluster_dispatch) { + passed = passed && (cluster_wg_x < 12); + passed = passed && (cluster_wg_y < 12); + passed = passed && (cluster_wg_z < 12); + } else { + passed = passed && (cluster_wg_x == 0); + passed = passed && (cluster_wg_y == 0); + passed = passed && (cluster_wg_z == 0); + } + + volatile unsigned int sink = cluster_wg_x + cluster_wg_y + cluster_wg_z; + (void)sink; + #endif + + // Test 4: Cluster broadcast load (generates GL0-GL1 cluster requests) + #if __has_builtin(__builtin_amdgcn_cluster_load_b32) + if(cluster_dispatch && global_int != nullptr) { + // cluster_dims(2,1,1): pair blockIdx.x values share a cluster. Use a + // per-cluster slot (not global_int[0]) so concurrent clusters do not + // stomp each other; lead WG publishes, syncthreads, cluster barrier, + // then broadcast. + constexpr int kClusterLoadSlotBase = 128; + constexpr int kClusterLoadMagic = 0xC1A57EED; + const int cluster_slot = + kClusterLoadSlotBase + static_cast((blockIdx.x / 2u) & 127u); + int* const cluster_load_addr = global_int + cluster_slot; + + #if __has_builtin(__builtin_amdgcn_cluster_workgroup_id_x) + const unsigned cluster_wg_x = __builtin_amdgcn_cluster_workgroup_id_x(); + const unsigned cluster_wg_y = __builtin_amdgcn_cluster_workgroup_id_y(); + const unsigned cluster_wg_z = __builtin_amdgcn_cluster_workgroup_id_z(); + const bool cluster_lead_wg = + (cluster_wg_x == 0u && cluster_wg_y == 0u && cluster_wg_z == 0u); + if(cluster_lead_wg && tid == 0) { + cluster_load_addr[0] = kClusterLoadMagic; + } + #else + if(tid == 0) { + cluster_load_addr[0] = kClusterLoadMagic; + } + #endif + __syncthreads(); + + // Workgroup barrier alone is insufficient: cluster_load_b32 reads across + // the cluster, so the lead WG store must be visible before broadcast. + #if __has_builtin(__builtin_amdgcn_s_cluster_barrier) + __builtin_amdgcn_s_cluster_barrier(); + #elif __has_builtin(__builtin_amdgcn_s_barrier_signal) && \ + __has_builtin(__builtin_amdgcn_s_barrier_wait) + #if __has_builtin(__builtin_amdgcn_s_barrier_signal_isfirst) + if(__builtin_amdgcn_s_barrier_signal_isfirst(-1)) { + __builtin_amdgcn_s_barrier_signal(-3); + } + #else + if(lane_id == 0) { + __builtin_amdgcn_s_barrier_signal(-3); + } + #endif + __builtin_amdgcn_s_barrier_wait(-3); + __syncthreads(); + #endif + + constexpr int kClusterLoadCpol = 0; + constexpr int kClusterLoadMask = -1; + const int loaded = __builtin_amdgcn_cluster_load_b32( + cluster_load_addr, kClusterLoadCpol, kClusterLoadMask); + cluster_load_passed = (loaded == kClusterLoadMagic); + volatile int load_sink = loaded; + (void)load_sink; + } + #endif + +#else + // Cluster operations are gfx1250 only - mark as bypassed + (void)passed; + (void)cluster_dispatch; + + if (lane_id == 0) { + atomicMin(&results->cluster_barrier_passed, -1); + atomicMin(&results->cluster_info_passed, -1); + atomicMin(&results->cluster_load_passed, -1); + } + return; +#endif + + if (lane_id == 0) { + atomicAdd(&results->cluster_barrier_passed, passed ? 1 : 0); + atomicAdd(&results->cluster_info_passed, passed ? 1 : 0); + if(cluster_dispatch) { + atomicAdd(&results->cluster_load_passed, cluster_load_passed ? 1 : 0); + } else { + atomicMin(&results->cluster_load_passed, -1); + } + } +} + +// ============================================================================ +// Section 15.5: WMMA (Wave Matrix Multiply Accumulate) Operations (RDNA4/gfx1250) +// ============================================================================ +// +// WMMA INSTRUCTIONS - RDNA4 Matrix Cores: +// ======================================= +// Reference: https://gpuopen.com/learn/using_matrix_core_amd_rdna4/ +// +// gfx12/gfx1250 features 3rd-generation Matrix Cores with significantly +// improved GEMM performance compared to RDNA3: +// +// PERFORMANCE COMPARISON (FLOPS/clock/CU): +// ---------------------------------------- +// | Format | RDNA2 (RX 6950) | RDNA3 (RX 7900) | RDNA4 (RX 9070) | +// |--------|-----------------|-----------------|-----------------| +// | FP16 | 256 | 512 | 1024 | +// | BF16 | N/A | 512 | 1024 | +// | I8 | 512 | 512 | 2048 | +// +// WMMA INTRINSIC FORMAT: +// ---------------------- +// IMPORTANT: The _gfx12 suffix is ONLY for RDNA4 consumer GPUs (gfx1200/gfx1201). +// gfx1250 does NOT use the _gfx12 suffix. +// +// gfx1200/gfx1201 (RDNA4 consumer): __builtin_amdgcn_wmma_*_16x16x16_*_w32_gfx12 (3-arg) +// gfx1150/gfx1151/gfx1152 (RDNA 3.5 Strix): __builtin_amdgcn_wmma_*_16x16x16_*_w32 (GFX11, 3-arg) +// gfx1250 (gfx1250): __builtin_amdgcn_wmma_* (no suffix; different signatures per BuiltinsAMDGPU.td) +// +// Available for RDNA4 consumer (gfx1200/gfx1201 only): _w32_gfx12 suffix, 3 args (a, b, c) +// - __builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12 (FP16 → FP32) +// - __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12 (BF16 → FP32) +// - __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12 (INT8 → INT32) +// +// Available for gfx1250 (gfx1250 only, no _gfx12 suffix): 8-arg FP16/BF16, 7-arg INT8 +// - __builtin_amdgcn_wmma_f32_16x16x32_f16, __builtin_amdgcn_wmma_f32_16x16x32_bf16 +// - __builtin_amdgcn_wmma_i32_16x16x64_iu8 +// +// MATRIX LAYOUT IN VGPR: +// ---------------------- +// WMMA operates on 16x16 matrices distributed across a Wave32: +// - Each lane holds 8 elements (8 * 32 = 256 = 16 * 16 elements) +// - Matrix A is column-major (transposed) +// - Matrices B, C, D are row-major +// - laneWrapped = threadIdx.x % 16 (column index within matrix) +// - laneGroup = threadIdx.x / 16 (upper/lower half of matrix) +// +// WMMA USAGE PATTERN: +// ------------------- +// 1. Load matrix A (column-major) into fragment +// 2. Load matrix B (row-major) into fragment +// 3. Execute WMMA intrinsic with all lanes in wavefront +// 4. Store result matrix D (row-major) from fragment +// +// IMPORTANT NOTES: +// ---------------- +// - RDNA4 uses Wave32 ONLY for WMMA (no Wave64 WMMA) +// - VGPR layout changed vs RDNA3 - NOT backward compatible +// - All 32 lanes must participate in the WMMA operation +// - Matrix dimensions are fixed at 16x16 +// + +// Vector types for WMMA operations (RDNA4) +// Fragment types: 8 elements per lane for 16x16 matrix +#if defined(__gfx1250__) || defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU +using wmma_f16_frag = __attribute__((__vector_size__(8 * sizeof(_Float16)))) _Float16; +using wmma_bf16_frag = __attribute__((__vector_size__(8 * sizeof(short)))) short; +using wmma_f32_frag = __attribute__((__vector_size__(8 * sizeof(float)))) float; +using wmma_i32_frag = __attribute__((__vector_size__(8 * sizeof(int)))) int; +using wmma_i8_frag = __attribute__((__vector_size__(2 * sizeof(int)))) int; // 2 packed i32 for 8 i8 elements +#endif + +// ============================================================================ +// Section 18: Buffer Operations (gfx1250 — descriptor-based addressing) +// ============================================================================ +// +// buffer_load_b32 / buffer_store_b32 use a 128-bit buffer resource descriptor +// set up via __builtin_amdgcn_make_buffer_rsrc(ptr, stride, numrecords, flags). +// This exercises SQ_INSTS_BUFFER_LOAD_sum and SQ_INSTS_BUFFER_STORE_sum +// counters that are 0 when only flat/global instructions are used. +// + +__device__ void test_buffer_ops([[maybe_unused]] float* global_mem, + [[maybe_unused]] TestResults* results, + [[maybe_unused]] int lane_id, + [[maybe_unused]] int tid) { +#if defined(__gfx1250__) + // Build a buffer resource descriptor over global_mem (256 floats). + // stride=0: raw buffer mode — numrecords is in bytes, soffset is in bytes. + // addr = base + soffset. OOB if soffset >= numrecords. + // numrecords covers the whole buffer; byte_off is unique per global thread. + __amdgpu_buffer_rsrc_t rsrc = __builtin_amdgcn_make_buffer_rsrc( + global_mem, 0 /*stride=0 raw*/, 0x7FFFFFFF /*large: covers any buffer*/, 0); + + // Use a constant value: all threads write the same value to offset 0, + // so there is no race and the read-back is deterministic. + // "off" addressing: addr = base + soffset (SGPR). + // Using soffset=0 (constant, SGPR-safe) → all access same slot. + float wval = 42.0f; // constant — all threads write same value + float rval = 0.0f; + int soff = 0; // SGPR-safe constant soffset + + // buffer_store_b32 off: soffset=0 (SGPR). All threads store the same value, + // so the final result is deterministic regardless of execution order. + asm volatile("buffer_store_b32 %[v], off, %[r], %[o]\n\t" + "s_wait_storecnt 0x0\n\t" + : : [v]"v"(wval), [r]"s"(rsrc), [o]"s"(soff) : "memory"); + + // buffer_load_b32 off: read back — all threads read from offset 0. + asm volatile("buffer_load_b32 %[v], off, %[r], %[o]\n\t" + "s_wait_loadcnt 0x0\n\t" + : [v]"=v"(rval) : [r]"s"(rsrc), [o]"s"(soff) : "memory"); + + bool passed = (fabsf(rval - wval) < 0.01f); + if (lane_id == 0) { + atomicAdd(&results->buffer_load_passed, passed ? 1 : 0); + atomicAdd(&results->buffer_store_passed, passed ? 1 : 0); + } +#else + if (lane_id == 0) { + atomicMin(&results->buffer_load_passed, -1); + atomicMin(&results->buffer_store_passed, -1); + } +#endif +} + +// ============================================================================ +// Section 19: Multicast Load (broadcast read — all threads same address) +// ============================================================================ +// +// When all threads in a wavefront load from the SAME address the hardware can +// coalesce the request into a single fetch + broadcast (multicast), counted +// by SQ_INSTS_MULTICAST_LOAD_sum. +// + +__device__ void test_multicast_load(float* global_mem, + TestResults* results, + int lane_id, + int tid) { + // Thread 0 writes a known value to a shared broadcast location + if (tid == 0) { + global_mem[512] = 99.0f; // use slot 512 to avoid aliasing with other tests + } + __syncthreads(); + + // ALL threads read from the SAME address → potential GL0/GL1 multicast + // Use a volatile read so the compiler doesn't hoist or collapse loads + volatile float bval = ((volatile float*)global_mem)[512]; + + bool passed = (fabsf(bval - 99.0f) < 0.01f); + if (lane_id == 0) { + atomicAdd(&results->multicast_load_passed, passed ? 1 : 0); + } +} + +__device__ void test_wmma_operations([[maybe_unused]] TestResults* results, + [[maybe_unused]] int lane_id, + [[maybe_unused]] int tid) { +#if defined(__gfx1250__) + // ======================================================================== + // gfx1250 WMMA Tests - use gfx1250 builtins (no _gfx12 suffix) + // BuiltinsAMDGPU.td: __builtin_amdgcn_wmma_f32_16x16x32_f16, _bf16, i32_16x16x64_iu8 + // ======================================================================== + bool f16_passed = true; + bool bf16_passed = true; + bool i8_passed = true; + + const int laneWrapped = tid % 16; + const int laneGroup = tid / 16; + // gfx1250 FP16/BF16: 16 elements per lane for A and B; INT8: 8 int (32 i8) per lane + using wmma_f16_frag_gfx1250 = __attribute__((__vector_size__(16 * sizeof(_Float16)))) _Float16; + using wmma_bf16_frag_gfx1250 = __attribute__((__vector_size__(16 * sizeof(short)))) short; + using wmma_i8_frag_gfx1250 = __attribute__((__vector_size__(8 * sizeof(int)))) int; + + // ---- FP16: __builtin_amdgcn_wmma_f32_16x16x32_f16(transA, A_16, transB, B_16, scale, C_8, bool, bool) ---- + { + wmma_f16_frag_gfx1250 a_frag; + wmma_f16_frag_gfx1250 b_frag; + wmma_f32_frag c_frag = {0}; + for (int ele = 0; ele < 16; ele++) { + a_frag[ele] = (_Float16)((laneWrapped + (ele + laneGroup * 16) % 32) % 16 + 1) * 0.1f; + b_frag[ele] = (_Float16)(((ele + laneGroup * 16) % 32 + laneWrapped) % 16 + 1) * 0.1f; + } + for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; + #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x32_f16) + c_frag = __builtin_amdgcn_wmma_f32_16x16x32_f16(false, a_frag, false, b_frag, (short)0, c_frag, false, false); + #endif + volatile float sink = 0; + for (int i = 0; i < 8; i++) sink += c_frag[i]; + f16_passed = (sink != 0.0f || tid >= 32); + (void)sink; + } + + // ---- BF16: __builtin_amdgcn_wmma_f32_16x16x32_bf16(transA, A_16, transB, B_16, scale, C_8, bool, bool) ---- + { + wmma_bf16_frag_gfx1250 a_frag; + wmma_bf16_frag_gfx1250 b_frag; + wmma_f32_frag c_frag = {0}; + for (int ele = 0; ele < 16; ele++) { + float va = (float)((laneWrapped + ele) % 16 + 1) * 0.1f; + float vb = (float)((ele + laneWrapped) % 16 + 1) * 0.1f; + a_frag[ele] = (short)(*(unsigned int*)&va >> 16); + b_frag[ele] = (short)(*(unsigned int*)&vb >> 16); + } + for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; + #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x32_bf16) + c_frag = __builtin_amdgcn_wmma_f32_16x16x32_bf16(false, a_frag, false, b_frag, (short)0, c_frag, false, false); + #endif + volatile float sink = 0; + for (int i = 0; i < 8; i++) sink += c_frag[i]; + bf16_passed = (sink != 0.0f || tid >= 32); + (void)sink; + } + + // ---- INT8: __builtin_amdgcn_wmma_i32_16x16x64_iu8(negA, A_8i, negB, B_8i, C_8i, bool, bool) ---- + { + wmma_i8_frag_gfx1250 a_frag = {0}; + wmma_i8_frag_gfx1250 b_frag = {0}; + wmma_i32_frag c_frag = {0}; + for (int ele = 0; ele < 8; ele++) { + unsigned int pa = 0, pb = 0; + for (int i = 0; i < 4; i++) { + int ia = (laneWrapped + ele * 4 + i) % 16 + 1; + int ib = (ele * 4 + i + laneWrapped) % 16 + 1; + pa |= (ia & 0xFF) << (i * 8); + pb |= (ib & 0xFF) << (i * 8); + } + a_frag[ele] = (int)pa; + b_frag[ele] = (int)pb; + } + #if __has_builtin(__builtin_amdgcn_wmma_i32_16x16x64_iu8) + c_frag = __builtin_amdgcn_wmma_i32_16x16x64_iu8(false, a_frag, false, b_frag, c_frag, false, false); + #endif + volatile int sink = 0; + for (int i = 0; i < 8; i++) sink += c_frag[i]; + i8_passed = (sink != 0 || tid >= 32); + (void)sink; + } + + if (lane_id == 0) { + atomicAdd(&results->wmma_f16_passed, f16_passed ? 1 : 0); + atomicAdd(&results->wmma_bf16_passed, bf16_passed ? 1 : 0); + atomicAdd(&results->wmma_i8_passed, i8_passed ? 1 : 0); + } + +#elif ARCH_RDNA35_APU + // ======================================================================== + // RDNA 3.5 APU (gfx115x - Strix) WMMA Tests + // Uses GFX11 WMMA builtins (wmma-256b-insts) with _Vector<16> inputs + // These are DIFFERENT from GFX12 builtins which use _Vector<8> inputs + // ======================================================================== + bool f16_passed = true; + bool bf16_passed = true; + bool i8_passed = true; + + const int laneWrapped = tid % 16; + const int laneGroup = tid / 16; + + // GFX11 WMMA uses 16-element input vectors (wmma-256b-insts) + using wmma_f16_frag_gfx11 = __attribute__((__vector_size__(16 * sizeof(_Float16)))) _Float16; + using wmma_bf16_frag_gfx11 = __attribute__((__vector_size__(16 * sizeof(short)))) short; + using wmma_i8_frag_gfx11 = __attribute__((__vector_size__(4 * sizeof(int)))) int; // 4 x int32 for 16 x int8 packed + + // ---- FP16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_f16_w32 ---- + { + wmma_f16_frag_gfx11 a_frag; + wmma_f16_frag_gfx11 b_frag; + wmma_f32_frag c_frag = {0}; + + for (int ele = 0; ele < 16; ele++) { + a_frag[ele] = (_Float16)((laneWrapped + ele + laneGroup * 8) % 16 + 1) * 0.1f; + b_frag[ele] = (_Float16)((ele + laneGroup * 8 + laneWrapped) % 16 + 1) * 0.1f; + } + + for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; + + #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_f16_w32) + c_frag = __builtin_amdgcn_wmma_f32_16x16x16_f16_w32(a_frag, b_frag, c_frag); + #endif + + volatile float sink = 0; + for (int i = 0; i < 8; i++) sink += c_frag[i]; + f16_passed = (sink != 0.0f || tid >= 32); + (void)sink; + } + + // ---- BF16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32 ---- + { + wmma_bf16_frag_gfx11 a_frag; + wmma_bf16_frag_gfx11 b_frag; + wmma_f32_frag c_frag = {0}; + + for (int ele = 0; ele < 16; ele++) { + float val = (float)((laneWrapped + ele) % 16 + 1) * 0.1f; + a_frag[ele] = (short)(*(unsigned int*)&val >> 16); + b_frag[ele] = (short)(*(unsigned int*)&val >> 16); + } + + for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; + + #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_bf16_w32) + c_frag = __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32(a_frag, b_frag, c_frag); + #endif + + volatile float sink = 0; + for (int i = 0; i < 8; i++) sink += c_frag[i]; + bf16_passed = (sink != 0.0f || tid >= 32); + (void)sink; + } + + // ---- INT8 WMMA: __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32 ---- + { + wmma_i8_frag_gfx11 a_frag; // 4 x int32 for 16 x int8 packed + wmma_i8_frag_gfx11 b_frag; + wmma_i32_frag c_frag = {0}; + + // Pack 4 INT8 values into each of 4 int32 elements (16 total) + for (int ele = 0; ele < 4; ele++) { + unsigned int packed_a = 0; + unsigned int packed_b = 0; + for (int i = 0; i < 4; i++) { + int val_a = ((laneWrapped + ele * 4 + i) % 16 + 1); + int val_b = ((ele * 4 + i + laneWrapped) % 16 + 1); + packed_a |= (val_a & 0xFF) << (i * 8); + packed_b |= (val_b & 0xFF) << (i * 8); + } + a_frag[ele] = (int)packed_a; + b_frag[ele] = (int)packed_b; + } + + for (int i = 0; i < 8; i++) c_frag[i] = 0; + + #if __has_builtin(__builtin_amdgcn_wmma_i32_16x16x16_iu8_w32) + c_frag = __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32( + false, a_frag, false, b_frag, c_frag, false); + #endif + + volatile int sink = 0; + for (int i = 0; i < 8; i++) sink += c_frag[i]; + i8_passed = (sink != 0 || tid >= 32); + (void)sink; + } + + if (lane_id == 0) { + atomicAdd(&results->wmma_f16_passed, f16_passed ? 1 : 0); + atomicAdd(&results->wmma_bf16_passed, bf16_passed ? 1 : 0); + atomicAdd(&results->wmma_i8_passed, i8_passed ? 1 : 0); + } + +#elif defined(__gfx1200__) || defined(__gfx1201__) + // ======================================================================== + // RDNA4 Consumer (gfx1200/gfx1201 - RX 9070 XT) WMMA Tests + // Uses GFX12 WMMA builtins (wmma-128b-insts) with _Vector<8> inputs + // ======================================================================== + bool f16_passed = true; + bool bf16_passed = true; + bool i8_passed = true; + + const int laneWrapped = tid % 16; + const int laneGroup = tid / 16; + const int WMMA_DATA_WIDTH = 8; + + // ---- FP16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12 ---- + { + wmma_f16_frag a_frag; + wmma_f16_frag b_frag; + wmma_f32_frag c_frag = {0}; + + for (int ele = 0; ele < WMMA_DATA_WIDTH; ele++) { + a_frag[ele] = (_Float16)((laneWrapped + ele + laneGroup * WMMA_DATA_WIDTH) % 16 + 1) * 0.1f; + b_frag[ele] = (_Float16)((ele + laneGroup * WMMA_DATA_WIDTH + laneWrapped) % 16 + 1) * 0.1f; + } + + for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; + + #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12) + c_frag = __builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12(a_frag, b_frag, c_frag); + #endif + + volatile float sink = 0; + for (int i = 0; i < 8; i++) sink += c_frag[i]; + f16_passed = (sink != 0.0f || tid >= 32); + (void)sink; + } + + // ---- BF16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12 ---- + { + wmma_bf16_frag a_frag; + wmma_bf16_frag b_frag; + wmma_f32_frag c_frag = {0}; + + for (int ele = 0; ele < WMMA_DATA_WIDTH; ele++) { + float val = (float)((laneWrapped + ele) % 16 + 1) * 0.1f; + a_frag[ele] = (short)(*(unsigned int*)&val >> 16); + b_frag[ele] = (short)(*(unsigned int*)&val >> 16); + } + + for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; + + #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12) + c_frag = __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12(a_frag, b_frag, c_frag); + #endif + + volatile float sink = 0; + for (int i = 0; i < 8; i++) sink += c_frag[i]; + bf16_passed = (sink != 0.0f || tid >= 32); + (void)sink; + } + + // ---- INT8 WMMA: __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12 ---- + { + wmma_i8_frag a_frag; // 2 x int32 for 8 x int8 packed + wmma_i8_frag b_frag; + wmma_i32_frag c_frag = {0}; + + // Pack 4 INT8 values into each of 2 int32 elements + for (int ele = 0; ele < 2; ele++) { + unsigned int packed_a = 0; + unsigned int packed_b = 0; + for (int i = 0; i < 4; i++) { + int val_a = ((laneWrapped + ele * 4 + i) % 16 + 1); + int val_b = ((ele * 4 + i + laneWrapped) % 16 + 1); + packed_a |= (val_a & 0xFF) << (i * 8); + packed_b |= (val_b & 0xFF) << (i * 8); + } + a_frag[ele] = (int)packed_a; + b_frag[ele] = (int)packed_b; + } + + for (int i = 0; i < 8; i++) c_frag[i] = 0; + + #if __has_builtin(__builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12) + c_frag = __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12( + false, a_frag, false, b_frag, c_frag, false); + #endif + + volatile int sink = 0; + for (int i = 0; i < 8; i++) sink += c_frag[i]; + i8_passed = (sink != 0 || tid >= 32); + (void)sink; + } + + if (lane_id == 0) { + atomicAdd(&results->wmma_f16_passed, f16_passed ? 1 : 0); + atomicAdd(&results->wmma_bf16_passed, bf16_passed ? 1 : 0); + atomicAdd(&results->wmma_i8_passed, i8_passed ? 1 : 0); + } + +#else + // WMMA not available on CDNA architectures (use MFMA instead) + // Mark as bypassed + if (lane_id == 0) { + atomicMin(&results->wmma_f16_passed, -1); + atomicMin(&results->wmma_bf16_passed, -1); + atomicMin(&results->wmma_i8_passed, -1); + } +#endif +} + +// ============================================================================ +// Section 16: RDNA4 Consumer-Specific Features (RX 9070 XT / gfx1200/gfx1201) +// ============================================================================ +// +// RDNA4 CONSUMER GPU FEATURES (RX 9070 XT, RX 9070, RX 9060 series): +// ================================================================== +// +// ARCHITECTURE OVERVIEW: +// ---------------------- +// - GFX12 architecture (gfx1200/gfx1201) +// - WGP (Workgroup Processor) based - 2 CUs per WGP +// - Wave32 native, Wave64 emulated +// - Improved AI inference accelerators +// - FP8/BF8 OCP (Open Compute Platform) format support +// +// KEY DIFFERENCES FROM CDNA (MI series): +// -------------------------------------- +// - No hardware FP64 atomics (limited FP64 in general) +// - Graphics-focused instruction set (more pixel/vertex ops) +// - Different memory hierarchy (infinity cache) +// - WGP instead of CU-centric design +// +// RDNA4 SPECIFIC INSTRUCTIONS: +// ---------------------------- +// - FP8/BF8 conversions (OCP format) +// - Async LDS operations +// - AI inference dot products +// - Ray-box/ray-triangle intersection instructions +// +// This test verifies RDNA4-specific features when running on consumer GPUs. + +__device__ void test_rdna4_features([[maybe_unused]] TestResults* results, + [[maybe_unused]] int lane_id) { +#if defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU + bool passed = true; + + // Test 1: Wave32 operations (RDNA4 is Wave32 native) + // Wave32 ballot mask test + uint32_t ballot_mask = __ballot(1); + passed = passed && (ballot_mask != 0); + + // Test 2: Subgroup operations + int lane_val = lane_id; + int first_active = __shfl(lane_val, 0); + passed = passed && (first_active == 0); + + // Test 3: FP16 packed operations (RDNA4 has optimized FP16) + half2 h2_val = __float2half2_rn(1.5f); + float converted = __half2float(__low2half(h2_val)); + passed = passed && (fabsf(converted - 1.5f) < 0.01f); + + // Test 4: WGP local barrier (implicit in __syncthreads on RDNA4) + __syncthreads(); + + if (lane_id == 0) { + atomicAdd(&results->lane_id_passed, passed ? 1 : 0); + } + +#else + // Not RDNA4 - skip + (void)results; + (void)lane_id; +#endif +} + +// ============================================================================ +// Main Mega Kernel +// ============================================================================ + +#if defined(__gfx1250__) +// Cluster-enabled dispatch (paired with hipLaunchKernelEx clusterDim in main.cpp). +__attribute__((cluster_dims(2, 1, 1))) +#endif +extern "C" __global__ void mega_kernel( + TestResults* results, + float* global_float, + double* global_double, + int* global_int, + float* input_buffer, + float* output_buffer, + float* async_lds_src, // Source buffer for async LDS load test + float* async_lds_dst, // Destination buffer for async LDS store test + int* tdm_src, // Source buffer for TDM load test + int* tdm_dst, // Destination buffer for TDM store test + int buffer_size, + int mfma_mode, // 0=both, 1=asm only, 2=builtin only + hipTextureObject_t tex_obj, // Strix gfx115x TEX load; pass 0 if unused + hipSurfaceObject_t surf_obj // Strix gfx115x TEX store; pass 0 if unused +) { + int tid = threadIdx.x + blockIdx.x * blockDim.x; + int lane_id = threadIdx.x % warpSize; + +#if !defined(__gfx1150__) && !defined(__gfx1151__) && !defined(__gfx1152__) + if (blockIdx.x == 0 && threadIdx.x == 0) { + results->vmem_tex_load_passed = -1; + results->vmem_tex_store_passed = -1; + } +#endif + + // Use active flag instead of early return to avoid __syncthreads() deadlock + // When buffer_size is not a multiple of blockDim.x, some threads in the last + // block would return early while others hit barriers, causing deadlock + bool active = (tid < buffer_size); + + // results (and other global buffers) are initialized on the host before launch (main.cpp). + // Do not memset here: with grid_size > 1, only tid==0 would run it and __syncthreads() is + // block-local, so other blocks could start updating results via atomics before memset completes. + + // =========== Run all tests =========== + + // 1. Warp Operations + test_warp_shuffle(results, lane_id); + test_warp_ballot(results, lane_id); + test_warp_permute(results, lane_id); + test_warp_reduce(results, lane_id); + + // 2. FP8/BF8 Conversions + test_fp8_conversions(results, lane_id); + test_fp8_e5m3_conversions(results, lane_id); // FP8 E5M3 (gfx1250 only) + test_bf8_conversions(results, lane_id); + + // 3. FP16/BF16 Operations + test_fp16_operations(results, lane_id); + test_bf16_operations(results, lane_id); + + // 4. FP32/FP64 Arithmetic + test_fp32_arithmetic(results, lane_id); + test_fp64_arithmetic(results, lane_id); + + // 5. Integer Operations + test_int32_operations(results, lane_id); + test_int64_operations(results, lane_id); + + // 6. Atomic Operations + test_atomic_add_f32(global_float, results, lane_id); + __syncthreads(); + test_atomic_add_f64(global_double, results, lane_id); + __syncthreads(); + test_atomic_min_max_f32(global_float + 1, results, lane_id); + __syncthreads(); + test_atomic_int(global_int, results, lane_id); + __syncthreads(); + + // 6.5. Comprehensive Atomic Operations (Inline ASM) + test_atomic_operations_asm(global_int, global_float, global_double, results, lane_id, tid); + __syncthreads(); + + // 7. LDS Operations + test_lds_operations(results, lane_id, threadIdx.x); + + // 8. Transcendental Functions + test_transcendental(results, lane_id); + + // 9. Memory Operations (guard with active flag to avoid out-of-bounds access) + if (active) { + test_global_memory(input_buffer, output_buffer, results, lane_id, tid); + } + + // 9.5. VMEM (Vector Memory) Comprehensive Tests + if (active) { + test_vmem_operations(input_buffer, results, lane_id, tid, buffer_size, tex_obj, surf_obj); + } + __syncthreads(); + + // 10. DOT Products + test_dot_products(results, lane_id); + + // 10.3 MFMA (Matrix Fused Multiply-Add) Operations + test_mfma_operations(results, lane_id, mfma_mode); + + // 10.5 Dual-Issue VALU Patterns (gfx942+) + test_dual_issue_patterns(results, lane_id); + + // 11. Lane/Thread Info + test_lane_thread_info(results, lane_id); + + // 12. Packed Operations + test_packed_operations(results, lane_id); + + // 12.5. Cooperative Atomic Operations (gfx1250 specific) + test_cooperative_atomics(global_int, results, lane_id, tid); + __syncthreads(); + + // 13. Async LDS Operations (gfx1250 specific) + test_async_lds_operations(async_lds_src, async_lds_dst, results, lane_id, tid); + __syncthreads(); + + // 14. TDM (Tensor Data Mover) Operations (gfx1250 specific) + test_tdm_operations(tdm_src, tdm_dst, results, lane_id, tid); + __syncthreads(); + + // 15. Cluster Group Operations (gfx1250 specific) + test_cluster_operations(global_int, results, lane_id, tid); + __syncthreads(); + + // 16. RDNA4 Consumer-Specific Features (RX 9070 XT) + test_rdna4_features(results, lane_id); + __syncthreads(); + + // 17. WMMA (Wave Matrix Multiply Accumulate) Operations (RDNA4/gfx1250) + test_wmma_operations(results, lane_id, tid); + __syncthreads(); + + // 18. Buffer Operations (gfx1250 — buffer_load_b32 / buffer_store_b32) + if (active) { + test_buffer_ops(input_buffer, results, lane_id, tid); + } + __syncthreads(); + + // 19. Multicast Load (all threads load same address) + test_multicast_load(input_buffer, results, lane_id, tid); + __syncthreads(); + + // Calculate totals on thread 0 + if (tid == 0) { + int passed = 0; + int failed = 0; + +#define ACCUM_RESULT(field) \ + do { \ + int v = (field); \ + if (v > 0) { \ + ++passed; \ + } else if (v == 0) { \ + ++failed; \ + } \ + } while (0) + + ACCUM_RESULT(results->warp_shuffle_passed); + ACCUM_RESULT(results->warp_ballot_passed); + ACCUM_RESULT(results->warp_permute_passed); + ACCUM_RESULT(results->warp_reduce_passed); + ACCUM_RESULT(results->fp8_convert_passed); + ACCUM_RESULT(results->fp8_e5m3_convert_passed); + ACCUM_RESULT(results->bf8_convert_passed); + ACCUM_RESULT(results->fp16_convert_passed); + ACCUM_RESULT(results->bf16_convert_passed); + ACCUM_RESULT(results->fp32_arith_passed); + ACCUM_RESULT(results->fp64_arith_passed); + ACCUM_RESULT(results->int32_arith_passed); + ACCUM_RESULT(results->int64_arith_passed); + ACCUM_RESULT(results->pk_f16_passed); + ACCUM_RESULT(results->pk_bf16_passed); + ACCUM_RESULT(results->pk_add_passed); + ACCUM_RESULT(results->atomic_add_f32_passed); + ACCUM_RESULT(results->atomic_add_f64_passed); + ACCUM_RESULT(results->atomic_min_f32_passed); + ACCUM_RESULT(results->atomic_max_f32_passed); + ACCUM_RESULT(results->atomic_int_passed); + ACCUM_RESULT(results->lds_atomic_passed); + ACCUM_RESULT(results->trans_sin_passed); + ACCUM_RESULT(results->trans_cos_passed); + ACCUM_RESULT(results->trans_exp_passed); + ACCUM_RESULT(results->trans_log_passed); + ACCUM_RESULT(results->trans_sqrt_passed); + ACCUM_RESULT(results->trans_rcp_passed); + ACCUM_RESULT(results->trans_rsq_passed); + ACCUM_RESULT(results->global_load_passed); + ACCUM_RESULT(results->global_store_passed); + ACCUM_RESULT(results->lds_load_passed); + ACCUM_RESULT(results->lds_store_passed); + ACCUM_RESULT(results->dot4_passed); + ACCUM_RESULT(results->dot8_passed); + ACCUM_RESULT(results->dual_issue_passed); + ACCUM_RESULT(results->lane_id_passed); + ACCUM_RESULT(results->wavefront_passed); + ACCUM_RESULT(results->async_lds_load_passed); + ACCUM_RESULT(results->async_lds_store_passed); + ACCUM_RESULT(results->tdm_load_passed); + ACCUM_RESULT(results->tdm_store_passed); + ACCUM_RESULT(results->cluster_barrier_passed); + ACCUM_RESULT(results->cluster_info_passed); + ACCUM_RESULT(results->cluster_load_passed); + ACCUM_RESULT(results->wmma_f16_passed); + ACCUM_RESULT(results->wmma_bf16_passed); + ACCUM_RESULT(results->wmma_i8_passed); + ACCUM_RESULT(results->coop_atomic_load_passed); + ACCUM_RESULT(results->coop_atomic_store_passed); + ACCUM_RESULT(results->vmem_flat_passed); + ACCUM_RESULT(results->vmem_global_passed); + ACCUM_RESULT(results->vmem_buffer_passed); + ACCUM_RESULT(results->vmem_scratch_passed); + ACCUM_RESULT(results->vmem_lds_passed); + ACCUM_RESULT(results->vmem_tex_load_passed); + ACCUM_RESULT(results->vmem_tex_store_passed); + ACCUM_RESULT(results->atomic_global_int_passed); + ACCUM_RESULT(results->atomic_global_f32_passed); + ACCUM_RESULT(results->atomic_global_f64_passed); + ACCUM_RESULT(results->atomic_flat_int_passed); + ACCUM_RESULT(results->atomic_ds_int_passed); + ACCUM_RESULT(results->atomic_ds_f32_passed); + ACCUM_RESULT(results->atomic_ds_f64_passed); + ACCUM_RESULT(results->atomic_pk_f16_passed); + ACCUM_RESULT(results->atomic_pk_bf16_passed); + ACCUM_RESULT(results->atomic_cas_passed); + ACCUM_RESULT(results->buffer_load_passed); + ACCUM_RESULT(results->buffer_store_passed); + ACCUM_RESULT(results->multicast_load_passed); + +#undef ACCUM_RESULT + + results->total_passed = passed; + results->total_failed = failed; + } +} diff --git a/skills/rocprof-compute-sample/mega_kernel/mega_kernel_device_arch.h b/skills/rocprof-compute-sample/mega_kernel/mega_kernel_device_arch.h new file mode 100644 index 0000000..0a9cfbc --- /dev/null +++ b/skills/rocprof-compute-sample/mega_kernel/mega_kernel_device_arch.h @@ -0,0 +1,71 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. +// SPDX-License-Identifier: MIT +// +// Device-side architecture capability flags (single place for HAS_* macros). +// Include only from HIP device translation units. + +#pragma once + +// MI250/MI200 series (CDNA2) +#if defined(__gfx90a__) +# define ARCH_MI250 1 +# define ARCH_NAME "MI250 (gfx90a/CDNA2)" +# define HAS_HW_FP64_ATOMICS 1 +# define HAS_FP8 0 +# define HAS_ASYNC_LDS 0 +#endif + +// MI300 series (CDNA3) +#if defined(__gfx942__) +# define ARCH_MI300 1 +# define ARCH_NAME "MI300 (gfx942/CDNA3)" +# define HAS_HW_FP64_ATOMICS 1 +# define HAS_FP8 1 +# define HAS_ASYNC_LDS 0 +#endif + +// MI350 series (CDNA4) +#if defined(__gfx950__) +# define ARCH_MI350 1 +# define ARCH_NAME "MI350 (gfx950/CDNA4)" +# define HAS_HW_FP64_ATOMICS 1 +# define HAS_FP8 1 +# define HAS_FP6_FP4 1 +# define HAS_ASYNC_LDS 1 +#endif + +// Strix Point / Strix Halo / Krackan / Gorgon Point — gfx115x RDNA 3.5 APU iGPU family +#if defined(__gfx1150__) || defined(__gfx1151__) || defined(__gfx1152__) || defined(__gfx1153__) +# define ARCH_RDNA35_APU 1 +# define ARCH_NAME "RDNA 3.5 APU (gfx115x - Strix/Krackan/Gorgon Point)" +# define HAS_HW_FP64_ATOMICS 0 +# define HAS_FP8 0 +# define HAS_ASYNC_LDS 1 +#endif + +// RDNA4 consumer (gfx1200/gfx1201) +#if defined(__gfx1200__) || defined(__gfx1201__) +# define ARCH_RDNA4_CONSUMER 1 +# define ARCH_NAME "RDNA4 Consumer (gfx1200/gfx1201 - RX 9070 XT)" +# define HAS_HW_FP64_ATOMICS 0 +# define HAS_FP8 1 +# define HAS_ASYNC_LDS 1 +# define HAS_TDM 0 +#endif + +// gfx1250 +#if defined(__gfx1250__) +# define ARCH_GFX1250 1 +# define ARCH_NAME "gfx1250" +# define HAS_HW_FP64_ATOMICS 1 +# define HAS_FP8 1 +# define HAS_ASYNC_LDS 1 +# define HAS_TDM 1 +#endif + +#ifndef ARCH_NAME +# define ARCH_NAME "Unknown" +# define HAS_HW_FP64_ATOMICS 0 +# define HAS_FP8 0 +# define HAS_ASYNC_LDS 0 +#endif diff --git a/skills/rocprof-compute-sample/mega_kernel/mega_kernel_host_arch.hpp b/skills/rocprof-compute-sample/mega_kernel/mega_kernel_host_arch.hpp new file mode 100644 index 0000000..fb36626 --- /dev/null +++ b/skills/rocprof-compute-sample/mega_kernel/mega_kernel_host_arch.hpp @@ -0,0 +1,64 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. +// SPDX-License-Identifier: MIT +// +// Host-side architecture string → category (keeps detection in one table). + +#pragma once + +#include + +namespace mega_kernel_host +{ + +// Order: longer gfx tokens before shorter prefixes (e.g. gfx942 before gfx94). +inline void +detect_architecture(const char* arch, char* out_name, size_t out_name_sz, int* out_type) +{ + if(out_name_sz > 0) + { + std::strncpy(out_name, arch, out_name_sz - 1); + out_name[out_name_sz - 1] = '\0'; + } + *out_type = 0; + + static const struct + { + const char* needle; + int typ; + } map[] = { + {"gfx1250", 7}, + {"gfx1153", 6}, {"gfx1152", 6}, {"gfx1151", 6}, {"gfx1150", 6}, + {"gfx1201", 5}, {"gfx1200", 4}, + {"gfx950", 3}, + {"gfx942", 2}, {"gfx941", 2}, {"gfx940", 2}, {"gfx94", 2}, + {"gfx90a", 1}, {"gfx908", 1}, + }; + + for(const auto& e : map) + { + if(std::strstr(arch, e.needle) != nullptr) + { + *out_type = e.typ; + break; + } + } +} + +inline const char* +arch_description(int arch_type) +{ + switch(arch_type) + { + case 1: return "MI250/MI250X (CDNA2/gfx90a)"; + case 2: return "MI300/MI300X (CDNA3/gfx942)"; + case 3: return "MI350/MI355X (CDNA4/gfx950)"; + case 4: return "RX 9060 series (RDNA4/gfx1200)"; + case 5: return "RX 9070 XT/9070 (RDNA4/gfx1201)"; + case 6: + return "Strix/Strix Halo/Krackan (RDNA3.5/gfx115x APU)"; + case 7: return "gfx1250"; + default: return "Unknown Architecture"; + } +} + +} // namespace mega_kernel_host diff --git a/skills/rocprof-compute-sample/mega_kernel/mega_kernel_types.h b/skills/rocprof-compute-sample/mega_kernel/mega_kernel_types.h new file mode 100644 index 0000000..8147818 --- /dev/null +++ b/skills/rocprof-compute-sample/mega_kernel/mega_kernel_types.h @@ -0,0 +1,124 @@ +// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. +// SPDX-License-Identifier: MIT +// +// Shared host/device definitions for the mega_kernel sample. Single definition +// avoids layout drift between main.cpp and mega_kernel.hip. + +#pragma once + +#include + +// MFMA implementation mode (host passes int to kernel) +enum MfmaMode +{ + MFMA_MODE_BOTH = 0, + MFMA_MODE_ASM = 1, + MFMA_MODE_BUILTIN = 2 +}; + +struct TestResults +{ + int warp_shuffle_passed; + int warp_ballot_passed; + int warp_permute_passed; + int warp_reduce_passed; + + int fp8_convert_passed; + int fp8_e5m3_convert_passed; + int bf8_convert_passed; + int fp16_convert_passed; + int bf16_convert_passed; + + int fp32_arith_passed; + int fp64_arith_passed; + int int32_arith_passed; + int int64_arith_passed; + + int pk_f16_passed; + int pk_bf16_passed; + int pk_add_passed; + + int atomic_add_f32_passed; + int atomic_add_f64_passed; + int atomic_min_f32_passed; + int atomic_max_f32_passed; + int atomic_int_passed; + int lds_atomic_passed; + + int trans_sin_passed; + int trans_cos_passed; + int trans_exp_passed; + int trans_log_passed; + int trans_sqrt_passed; + int trans_rcp_passed; + int trans_rsq_passed; + + int global_load_passed; + int global_store_passed; + int lds_load_passed; + int lds_store_passed; + + int dot4_passed; + int dot8_passed; + + int mfma_f32_passed; + int mfma_f64_passed; + int mfma_f16_passed; + int mfma_bf16_passed; + int mfma_i8_passed; + int mfma_f8_passed; + + int dual_issue_passed; + + int lane_id_passed; + int wavefront_passed; + + int async_lds_load_passed; + int async_lds_store_passed; + + int tdm_load_passed; + int tdm_store_passed; + + int cluster_barrier_passed; + int cluster_info_passed; + int cluster_load_passed; + + int wmma_f16_passed; + int wmma_bf16_passed; + int wmma_i8_passed; + + int coop_atomic_load_passed; + int coop_atomic_store_passed; + + int vmem_flat_passed; + int vmem_global_passed; + int vmem_buffer_passed; + int vmem_scratch_passed; + int vmem_lds_passed; + int vmem_tex_load_passed; + int vmem_tex_store_passed; + + int atomic_global_int_passed; + int atomic_global_f32_passed; + int atomic_global_f64_passed; + int atomic_flat_int_passed; + int atomic_ds_int_passed; + int atomic_ds_f32_passed; + int atomic_ds_f64_passed; + int atomic_pk_f16_passed; + int atomic_pk_bf16_passed; + int atomic_cas_passed; + + int buffer_load_passed; + int buffer_store_passed; + int multicast_load_passed; + + int total_passed; + int total_failed; +}; + +extern "C" __global__ void mega_kernel( + TestResults* results, float* global_float, double* global_double, int* global_int, + float* input_buffer, float* output_buffer, float* async_lds_src, float* async_lds_dst, + int* tdm_src, int* tdm_dst, int buffer_size, int mfma_mode, hipTextureObject_t tex_obj, + hipSurfaceObject_t surf_obj); diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/L1_stall_microbenchmark.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/L1_stall_microbenchmark.hip new file mode 100644 index 0000000..ffbe4ff --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/L1_stall_microbenchmark.hip @@ -0,0 +1,134 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// L1 stall micro-benchmarks +// Usage: ./a.out = baseline (uncoalesced) -> TA/UTCL1/L2 stall +// ./a.out opt = optimized (shared mem) -> no stall +// ./a.out sq1 = SQ-L1 back-pressure -> try to get "VMEM stalled by L1" > 0 + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while (0) + +// ----------------------------------------------------------------------------- +// SQ-L1 back-pressure kernel: maximize VMEM issue rate so SQ->TA FIFOs fill. +// - Many independent loads in a row (no data dependency) so SQ can issue as fast +// as the pipeline allows; then stores. Goal: fill VMEM command/addr/data FIFO. +// - Uncoalesced so TA->TCP is slow and FIFOs back up to SQ. +// - Actual behaviour: uncoalesced heavy L1 -> L2 traffic, but not filling up VMEM FIFO +// Run with: ./a sq1 +// ----------------------------------------------------------------------------- +__global__ void vmem_sq_l1_backpressure(float* data, int N) { + int tid = blockIdx.x * blockDim.x + threadIdx.x; + if (tid >= N / 64) return; + int base = tid * 64; + + // Independent loads + // Uncoalesced + float v[32]; +#pragma unroll + for (int i = 0; i < 32; i++) { + int load_idx = (base + threadIdx.x * 32 + i * 4096) % N; + v[i] = data[load_idx]; + } + + // 32 stores. Uncoalesced, different stride. +#pragma unroll + for (int i = 0; i < 32; i++) { + int store_idx = (base + threadIdx.x * 32 + i * 8192) % N; + data[store_idx] = v[i] * 2.0f; + } +} + +// Fixed baseline: UNCOALESCED accesses +__global__ void vmem_stall_baseline(float* data, int N) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= N) return; + + float vals[16]; + // Uncoalesced loads: threadIdx.x * 32 puts each of 32 threads in a warp on a + // different cache line (32 floats = 128B). i * 1024 spreads across pages. + for (int i = 0; i < 16; i++) { + int load_idx = (idx + threadIdx.x * 32 + i * 1024) % N; + vals[i] = data[load_idx]; + } + + // Uncoalesced stores: same idea, different stride to avoid overlap with loads. + for (int i = 0; i < 16; i++) { + int store_idx = (idx + threadIdx.x * 32 + i * 2048) % N; + data[store_idx] = vals[i] * 2.0f; + } +} + +// Optimized: Shared memory reduces VMEM FIFO pressure +__global__ void vmem_stall_optimized(float* data, int N) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= N) return; + + __shared__ float sdata[256]; + + sdata[threadIdx.x] = data[idx]; + __syncthreads(); + + float result = sdata[threadIdx.x]; + for (int i = 0; i < 16; i++) { + result = result * 1.01f + 0.1f; + } + + data[idx] = result; +} + +int main(int argc, char** argv) { + // opt = optimized (shared mem); sq1 = SQ-L1 back-pressure (try to get "VMEM stalled by L1" > 0) + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + bool use_sq1 = (argc > 1 && std::string(argv[1]) == "sq1"); + + const int N = 256 * 1024 * 1024; // 1GB + const size_t bytes = N * sizeof(float); + + float* d_data; + CHECK_HIP(hipMalloc(&d_data, bytes)); + + float* h_data = new float[N]; + for (int i = 0; i < N; i++) h_data[i] = 1.0f; + CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + dim3 block(256); + dim3 grid_baseline((N + block.x - 1) / block.x); + dim3 grid_sq1((N / 64 + block.x - 1) / block.x); // tid = N/64 threads total + + auto run = [&]() { + if (use_optimized) + hipLaunchKernelGGL(vmem_stall_optimized, grid_baseline, block, 0, 0, d_data, N); + else if (use_sq1) + hipLaunchKernelGGL(vmem_sq_l1_backpressure, grid_sq1, block, 0, 0, d_data, N); + else + hipLaunchKernelGGL(vmem_stall_baseline, grid_baseline, block, 0, 0, d_data, N); + }; + + run(); + CHECK_HIP(hipDeviceSynchronize()); + + auto start = std::chrono::high_resolution_clock::now(); + run(); + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + const char* name = use_optimized ? "Optimized" : (use_sq1 ? "SQ-L1 backpressure" : "Baseline"); + std::cout << name << ": " + << std::chrono::duration_cast(end - start).count() << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/README.md b/skills/rocprof-compute-sample/membw_analysis_test_suite/README.md new file mode 100644 index 0000000..da920a5 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/README.md @@ -0,0 +1,52 @@ +# L2/L1 Bottleneck Workloads + +HIP workloads targeting MI350 L2 backpressure and L1 stall metrics (≥10%). + +## Workload Target + ++--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ +| Workload | Baseline (intended effect) | Optimized (intended effect) | ++--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ +| gl2_backpressure | Uncoalesced + large stride -> L2 misses -> fill LFIFO -> TCP stalls. | Shared mem, minimal L2 traffic. | ++--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ +| L1_stall_microbenchmark | Many scattered loads/stores -> VMEM issues many commands; TA/GL1 can't consume them fast enough | Shared memory reduces VMEM FIFO pressure. | +| | -> VMEM FIFO fills. | | ++--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ +| utcl1_stall | Rapid page hopping (>32 pages) -> exceed UTCL1 entries -> in-flight stall. | Stay in one page. | ++--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ +| ta_tcp_stall | Random access, TA waits for TCP. | Sequential access, TCP serves TA quickly. | ++--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ + +> [!NOTE] +Note: Above workloads are still WIP, the profiled result may not reflect the intended results. + +## Target Metrics + +- **gl2_backpressure**: `TCP_TCR_TCP_STALL_CYCLES / tcp_busy` +- **utcl1_stall**: `TCP_UTCL1_STALL_INFLIGHT_MAX / tcp_busy` + +## Build + +```bash +hipcc -g -o +# example: hipcc -g gl2_backpressure.hip -o gl2_backpressure +``` + +## Profiling + +```bash +# Note: --no-roof is optional and skips the +# benchmarking that is unrelated to the our purpose + +# baseline profile +rocprof-compute profile -n gl2_backpressure_baseline --experimental --membw-analysis --no-roof -- ./gl2_backpressure +# optimized profile +rocprof-compute profile -n gl2_backpressure_optimized --experimental --membw-analysis --no-roof -- ./gl2_backpressure opt +``` + +## Analyzing + +```bash +# baseline profile +rocprof-compute analyze -p +``` diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/Makefile b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/Makefile new file mode 100644 index 0000000..cce260f --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/Makefile @@ -0,0 +1,15 @@ +HIPCC ?= hipcc +HIPFLAGS ?= -g + +SOURCES := $(wildcard *.hip) +TARGETS := $(SOURCES:.hip=) + +all: $(TARGETS) + +%: %.hip + $(HIPCC) $(HIPFLAGS) $< -o $@ + +clean: + rm -f $(TARGETS) + +.PHONY: all clean diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/README.md b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/README.md new file mode 100644 index 0000000..ca4e1d7 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/README.md @@ -0,0 +1,380 @@ +# EA Bottleneck Detection Workloads + +HIP workloads targeting MI350 EA (Efficiency Arbiter) memory bandwidth bottleneck +detection metrics (tables 30.13--30.18 in `3000_mem_bw.yaml`). Each workload has a +**baseline** (exhibits the bottleneck) and **optimized** variant (mitigates it), +selected via the `opt` CLI argument. + +## Architecture Reference + +``` +TCC (L2, 16 channels) --[64B R + 64B W per channel]--> EA ---> DRAM FE --> HBM + |-> GMI FE --> GMI (remote XCD/AID) + |-> IO FE --> PCIe (host) +``` + +- EA has 6 independent credit pools: 3 destinations (HBM/GMI/IO) x 2 directions (read/write) +- Atomics consume write credits (counted within `TCC_EA0_WRREQ`), but have higher latency (RMW) +- MI350: 16 TCC channels, 128 HBM channels (NPS1), L2 cacheline = 128B + +## Workload Summary + +| Workload | Baseline (intended effect) | Optimized (intended effect) | +|------------------------|------------------------------------------------------------------|----------------------------------------------------| +| ea_hbm_read_bw | Streaming reads from 4GB buffer (>> L2) saturates HBM read BW. | All workgroups re-read 2MB region (fits in L2). | +| ea_hbm_write_bw | Streaming writes to 4GB buffer saturates HBM write BW. | Register accumulation, single write per thread. | +| ea_write_backpressure | Coalesced streaming writes at max rate flood EA write credits. | ALU work between writes throttles write rate. | +| ea_atomic_pressure | All threads atomicAdd to 64K-bin global histogram. | LDS-local histogram, merge with few global atomics.| +| ea_rw_balance | Read-only streaming sum, single scalar output. | 1:1 vector copy (balanced read + write). | +| ea_fabric_bw | Streaming reads from remote GPU memory via peer access (GMI). | Copy remote data to local first, read from HBM. | +| ea_io_bw | Streaming reads from host-pinned coherent memory (PCIe). | Copy to device memory first, read from HBM. | + +> [!NOTE] +> `ea_fabric_bw` requires >= 2 GPUs with peer access. It skips gracefully on single-GPU systems. + +## Target Metrics + +| Workload | Primary Metrics (table) | +|------------------------|---------------------------------------------------------------------------------------------| +| ea_hbm_read_bw | EA to HBM read BW (30.14), EA utilization (30.13), Norm HBM read stall (30.16) | +| ea_hbm_write_bw | Write Credit Pressure (30.18), Write Backpressure (30.18), EA to HBM write BW (30.14) | +| ea_write_backpressure | Write Backpressure (30.18), Norm Write stall (30.16) | +| ea_atomic_pressure | HBM Atomic Pressure (30.18), atomic fraction of writes (30.13), EA to HBM atomic BW (30.14)| +| ea_rw_balance | Read/Write Balance (30.18), EA to HBM read/write BW (30.14) | +| ea_fabric_bw | GMI BW Bound (30.18), read BW fraction GMI (30.13), Stall Dominance GMI (30.18) | +| ea_io_bw | IO BW Bound (30.18), read BW fraction IO (30.13), Stall Dominance IO (30.18) | + +## Build + +```bash +cd sample/membw_analysis_test_suite/ea +make # builds all workloads +make clean # removes binaries +``` + +Or individually: + +```bash +hipcc -g ea_hbm_read_bw.hip -o ea_hbm_read_bw +``` + +## Profiling + +```bash +# Note: --no-roof is optional and skips the +# benchmarking that is unrelated to the our purpose + +# From the rocprofiler-compute root directory: + +# Profile baseline +src/rocprof-compute profile -n ea_test_baseline \ + -b 30.13 30.14 30.15 30.16 30.17 30.18 \ + --experimental --membw-analysis --no-roof \ + --output-directory /tmp/ea_test_baseline \ + -- ./sample/membw_analysis_test_suite/ea/ea_hbm_read_bw + +# Profile optimized +src/rocprof-compute profile -n ea_test_opt \ + -b 30.13 30.14 30.15 30.16 30.17 30.18 \ + --experimental --membw-analysis --no-roof \ + --output-directory /tmp/ea_test_opt \ + -- ./sample/membw_analysis_test_suite/ea/ea_hbm_read_bw opt +``` + +## Analyzing + +Side-by-side baseline vs. optimized comparison: + +```bash +src/rocprof-compute analyze \ + -p /tmp/ea_test_baseline -p /tmp/ea_test_opt \ + -b 30.13 30.14 30.15 30.16 30.17 30.18 +``` + +To analyze each run independently: + +```bash +src/rocprof-compute analyze -p /tmp/ea_test_baseline \ + -b 30.13 30.14 30.15 30.16 30.17 30.18 + +src/rocprof-compute analyze -p /tmp/ea_test_opt \ + -b 30.13 30.14 30.15 30.16 30.17 30.18 +``` + +## Validation Results (MI350X) + +### Test Environment + +| Component | Configuration | +|--------------------|----------------------------------------------------| +| GPU | 2x AMD Instinct MI350X (gfx950) | +| GPU Architecture | CDNA 4 (gfx950), 256 CUs, 32 SIMDs/CU, 8 XCDs | +| GPU SCLK | 2200 MHz (max) | +| GPU MCLK | 1900 MHz (HBM) | +| HBM | 288 GB per GPU, 128 HBM channels, NPS1 | +| L2 Cache | 4096 KB, 128 channels, 16 banks | +| L1 Cache | 32 KB per CU | +| VBIOS | 113-M350-01-1K5-020E | +| Compute Partition | SPX | +| Memory Partition | NPS1 | +| CPU | AMD EPYC 9655 96-Core (Turin) | +| OS | Ubuntu 24.04 LTS, kernel 6.8.0-31-generic | +| ROCm | 7.2.0 | +| GPU Driver | amdgpu 6.16.13 | + +All results collected on MI350X GPUs. `ea_fabric_bw` validated on the multi-GPU +system with peer access enabled between GPU 0 and GPU 1. + +### ea_hbm_read_bw -- HBM Read Traffic + +| Metric | Baseline | Optimized | +|---------------------------------------------------|----------------|---------------| +| EA to HBM read BW (30.14.0) | 6079 Gb/s | 53 Gb/s | +| EA to HBM write BW (30.14.1) | 0 Gb/s | 2 Gb/s | +| EA utilization (30.13.0) | 99.42% | 13.16% | +| EA read average latency (30.14.10) | 1589 cyc | 1147 cyc | +| EA read latency (30.14.11) | 1589 cyc | 1147 cyc | +| Normalized EA - HBM read credit stall (30.16.0) | 1.73% | 0.72% | +| EA HBM BW Bound - Read Credit Pressure (30.18.0) | 0.01% | 0.11% | +| EA Read/Write Balance (30.18.6) | 99.99% | 89.05% | +| EA Stall Destination Dominance - HBM (30.18.8) | 100.00% | 100.00% | + +**Key findings:** +- EA to HBM read BW differentiates at **115x** (6079 vs 53 Gb/s). The 2MB tiled + region fits in L2, so subsequent passes are cache hits. +- EA utilization drops from 99% to 13%, confirming L2 reuse avoids EA. +- Read Credit Pressure (30.18.0) remains low even at near-peak BW. On MI350, + the HBM read credit pool is deep enough that coalesced streaming reads do not + exhaust credits. The normalized stall metric (30.16.0) shows a clearer signal + at 1.73% vs 0.72% using `GRBM_GUI_ACTIVE_PER_XCD` as denominator. + +### ea_hbm_write_bw -- HBM Write Credit Pressure + +| Metric | Baseline | Optimized | +|----------------------------------------------------|----------------|---------------| +| EA to HBM write BW (30.14.1) | 4855 Gb/s | 31 Gb/s | +| EA utilization (30.13.0) | 89.96% | 3.90% | +| EA write average latency (30.14.12) | 530 cyc | 404 cyc | +| EA write latency (30.14.13) | 530 cyc | 404 cyc | +| EA HBM BW Bound - Write Credit Pressure (30.18.1) | **25.06%** | 6.68% | +| EA Write Backpressure (30.18.5) | **25.21%** | 6.77% | +| Normalized EA - HBM write credit stall (30.16.1) | 2962% | 20.30% | +| EA Read/Write Balance (30.18.6) | 0.00% | 0.15% | +| EA Stall Destination Dominance - HBM (30.18.8) | 100.00% | 100.00% | + +**Key findings:** +- Write Credit Pressure at **25.06%** clearly exceeds the 10% bottleneck threshold. + The optimized kernel accumulates in registers and writes once, reducing write BW + by 157x (4855 vs 31 Gb/s). +- Write Backpressure (25.21%) closely tracks Write Credit Pressure since both + derive from write credit stalls on HBM. +- Normalized write credit stall at 2962% reflects per-channel stall accumulation + across 16 TCC channels summed against a single-XCD activity counter. The 30.18 + metric using `TCC_BUSY_sum` as denominator gives the per-channel percentage. + +### ea_write_backpressure -- Write Path Backpressure + +| Metric | Baseline | Optimized | +|----------------------------------------------------|----------------|---------------| +| EA to HBM write BW (30.14.1) | 5719 Gb/s | 820 Gb/s | +| EA utilization (30.13.0) | 90.17% | 97.93% | +| EA write average latency (30.14.12) | 428 cyc | 258 cyc | +| EA write latency (30.14.13) | 428 cyc | 258 cyc | +| EA HBM BW Bound - Write Credit Pressure (30.18.1) | **13.20%** | 0.03% | +| EA Write Backpressure (30.18.5) | **13.13%** | 0.03% | +| Normalized EA - HBM write credit stall (30.16.1) | 1524% | 3.54% | +| Normalized EA - Write stall (30.16.6) | 1517% | 3.56% | +| Normalized EA - Too many writes stall (30.16.7) | 0.00% | 0.00% | +| EA Stall Destination Dominance - HBM (30.18.8) | 100.00% | 100.00% | + +**Key findings:** +- Coalesced streaming writes at maximum rate trigger **13.13% Write Backpressure** + (above 10% threshold), while compute-gated writes drop to 0.03%. +- The ALU work between writes in the optimized kernel reduces the write request + rate by ~7x, giving EA time to drain credits between bursts. +- `TCC_TOO_MANY_EA_WRREQS_STALL` (30.16.7) is 0% for both variants. This counter + tracks global write credit pool exhaustion across all TCC channels, which does + not trigger on MI350 with these patterns. The per-destination credit stall + (`TCC_EA0_WRREQ_DRAM_CREDIT_STALL`) is the active mechanism. + +### ea_atomic_pressure -- HBM Atomic Contention + +| Metric | Baseline | Optimized | +|----------------------------------------------------|----------------|---------------| +| EA to HBM atomic BW (30.14.2) | 836 Gb/s | 7 Gb/s | +| EA to HBM read BW (30.14.0) | 105 Gb/s | 3581 Gb/s | +| EA atomic average latency (30.14.14) | 747 cyc | 3443 cyc | +| EA atomic latency (30.14.15) | 747 cyc | 3443 cyc | +| EA HBM Atomic Pressure (30.18.7) | **100.00%** | **100.00%** | +| EA atomic fraction of writes (30.13.6) | 100.00% | 100.00% | +| EA HBM BW Bound - Write Credit Pressure (30.18.1) | 2.06% | 0.03% | +| Normalized EA - HBM write credit stall (30.16.1) | 246% | 4.05% | +| EA uncached write fraction (30.13.9) | 100.00% | 100.00% | +| EA Read/Write Balance (30.18.6) | 3.03% | 99.61% | +| EA Stall Destination Dominance - HBM (30.18.8) | 100.00% | 100.00% | + +**Key findings:** +- EA to HBM atomic BW differentiates at **119x** (836 vs 7 Gb/s). The LDS histogram + reduces global atomic traffic from millions of atomics to ~256 per workgroup. +- HBM Atomic Pressure is 100% for both variants. This is correct: the metric + measures `WRREQ_ATOMIC_DRAM / WRREQ_DRAM` -- what fraction of writes are atomics. + In both kernels the only writes reaching EA are atomics (baseline: all global, + optimized: the LDS merge atomics). The differentiation is in **volume**, not + fraction. +- Write Credit Pressure drops from 2.06% to 0.03% because atomics consume write + credits; fewer atomics = fewer stalls. +- R/W Balance flips from 3% (baseline: mostly atomic writes) to 99.6% (optimized: + mostly input reads with negligible atomic writes). +- Atomic latency is higher in the optimized case (3443 vs 747 cyc) because fewer + outstanding requests means less amortization of the per-request RMW round-trip. + +### ea_rw_balance -- Read/Write Traffic Balance + +| Metric | Baseline | Optimized | +|----------------------------------------------------|----------------|----------------| +| EA to HBM read BW (30.14.0) | 6203 Gb/s | 2580 Gb/s | +| EA to HBM write BW (30.14.1) | 0 Gb/s | 2580 Gb/s | +| EA to HBM total BW (30.14.3) | 6203 Gb/s | 5159 Gb/s | +| EA utilization (30.13.0) | 95.15% | 95.61% | +| EA Read/Write Balance (30.18.6) | **100.00%** | **33.33%** | +| EA HBM BW Bound - Read Credit Pressure (30.18.0) | 0.03% | 2.12% | +| EA HBM BW Bound - Write Credit Pressure (30.18.1) | 0.00% | 2.16% | +| EA HBM BW Bound - Combined (30.18.2) | 0.03% | 4.28% | +| EA Stall Destination Dominance - HBM (30.18.8) | 100.00% | 100.00% | + +**Key findings:** +- R/W Balance metric correctly differentiates: **100%** for read-only reduction + vs **33%** for 1:1 vector copy. The formula `RDREQ / (RDREQ + WRREQ)` reflects + request-level balance including L2 write-allocate fetches. +- The vector copy shows perfectly symmetric BW: 2580 Gb/s read = 2580 Gb/s write. +- HBM BW Bound Combined reaches 4.28% for the copy (both read and write credit + stalls contributing), compared to 0.03% for the read-only baseline where write + credits are never consumed. + +### ea_io_bw -- IO/PCIe Bandwidth + +| Metric | Baseline | Optimized | +|----------------------------------------------------|----------------|---------------| +| EA to IO read BW (30.14.7) | 56.6 Gb/s | 0.0 Gb/s | +| EA to HBM read BW (30.14.0) | 2.6 Gb/s | 6276 Gb/s | +| EA read BW fraction - IO (30.13.4) | **95.59%** | 0.00% | +| EA read BW fraction - HBM (30.13.2) | 4.41% | 100.00% | +| EA read average latency (30.14.10) | **27533 cyc** | 1448 cyc | +| EA read latency (30.14.11) | **27533 cyc** | 1448 cyc | +| EA IO BW Bound - Combined (30.18.4) | **9.41%** | 0.00% | +| EA Stall Destination Dominance - IO (30.18.10) | **100.00%** | 0.00% | +| EA Stall Destination Dominance - HBM (30.18.8) | 0.00% | 100.00% | +| Normalized EA - IO read credit stall (30.16.4) | 778.58% | 0.00% | +| EA utilization (30.13.0) | 72.11% | 99.69% | + +**Key findings:** +- EA read BW fraction - IO correctly identifies **95.6%** of read traffic going + via PCIe in the baseline. After copying to device memory, it drops to 0%. +- Stall Destination Dominance perfectly flips: IO 100% (baseline) to HBM 100% + (optimized). This cross-validates the dominance metric. +- EA to IO read BW at 56.6 Gb/s is consistent with PCIe Gen5 x16 bandwidth (~64 GB/s + theoretical peak). +- Read latency is **19x higher** via PCIe (27,533 vs 1,448 cycles), reflecting + the ~1us PCIe round-trip vs ~50-100ns HBM latency. +- IO BW Bound at 9.41% is near the bottleneck threshold. The normalized stall + metric (30.16.4 = 779%) and Stall Destination Dominance (100% IO) provide + stronger bottleneck indicators for IO-bound workloads. + +### ea_fabric_bw -- GMI Bandwidth (multi-GPU) + +Validated on a multi-GPU MI350X system with peer access enabled. + +| Metric | Baseline | Optimized | +|----------------------------------------------------|----------------|---------------| +| EA to GMI read BW (30.14.4) | 61.8 Gb/s | 0.0 Gb/s | +| EA to HBM read BW (30.14.0) | 0.0 Gb/s | 6244 Gb/s | +| EA read BW fraction - GMI (30.13.3) | **100.00%** | 0.00% | +| EA read BW fraction - HBM (30.13.2) | 0.00% | 100.00% | +| EA read average latency (30.14.10) | **21858 cyc** | 1492 cyc | +| EA read latency (30.14.11) | **21858 cyc** | 1492 cyc | +| EA GMI BW Bound - Combined (30.18.3) | **15.08%** | 0.00% | +| EA Stall Destination Dominance - GMI (30.18.9) | **100.00%** | 0.00% | +| EA Stall Destination Dominance - HBM (30.18.8) | 0.00% | 100.00% | +| Normalized EA - GMI read credit stall (30.16.2) | 1347.62% | 0.00% | +| EA utilization (30.13.0) | 99.72% | 96.43% | + +**Key findings:** +- GMI BW Bound at **15.08%** clearly exceeds the 10% threshold, confirming + that streaming reads from remote GPU memory via GMI saturate GMI credits. + After copying to local HBM, it drops to 0%. +- EA read BW fraction - GMI correctly identifies **100%** of read traffic going + via GMI in the baseline. After local copy, it drops to 0%. +- Stall Destination Dominance perfectly flips: GMI 100% (baseline) to HBM 100% + (optimized). This cross-validates the dominance metric for the GMI destination. +- EA to GMI read BW at 61.8 Gb/s reflects the inter-GPU GMI link bandwidth. +- Read latency is **14.6x higher** via GMI (21,858 vs 1,492 cycles), reflecting + the remote memory access round-trip latency. + +## Cross-Validation Summary + +### Bottleneck Detection Thresholds (30.18) + +| Workload | Key Metric (30.18.x) | Baseline | Optimized | Threshold | Pass | +|------------------------|--------------------------|-----------|-----------|-----------|------| +| ea_hbm_write_bw | Write Credit Pressure | 25.06% | 6.68% | >= 10% | Yes | +| ea_write_backpressure | Write Backpressure | 13.13% | 0.03% | >= 10% | Yes | +| ea_rw_balance | Read/Write Balance | 100.00% | 33.33% | N/A | Yes | +| ea_atomic_pressure | HBM Atomic Pressure | 100% | 100% | N/A | (1) | +| ea_fabric_bw | GMI BW Bound | 15.08% | 0.00% | >= 10% | Yes | +| ea_io_bw | IO BW Bound | 9.41% | 0.00% | >= 5% | Yes | +| ea_hbm_read_bw | Read Credit Pressure | 0.01% | 0.11% | >= 10% | (2) | + +**(1)** Atomic Pressure measures fraction (not volume). Both variants are 100% +atomic because neither has non-atomic writes. Differentiation is via atomic BW +(836 vs 7 Gb/s, 119x) and write credit stall (246% vs 4.05%). + +**(2)** Read Credit Pressure stays below threshold even at ~6 TB/s read BW. +MI350's deep HBM read credit pools do not exhaust under coalesced streaming +patterns. Differentiation is via EA to HBM read BW (6079 vs 53 Gb/s, 115x) and +EA utilization (99% vs 13%). + +### Stall Destination Dominance Cross-Validation + +| Workload (baseline) | HBM | GMI | IO | +|--------------------------------------------|--------|--------|--------| +| ea_hbm_read_bw, ea_hbm_write_bw | 100% | 0% | 0% | +| ea_write_backpressure, ea_atomic_pressure | 100% | 0% | 0% | +| ea_rw_balance | 100% | 0% | 0% | +| ea_io_bw | 0% | 0% | 100% | +| ea_fabric_bw | 0% | 100% | 0% | + +The Stall Destination Dominance metric correctly identifies the dominant stall +source across all validated workloads, including the HBM-to-IO flip in ea_io_bw +and the GMI dominance in ea_fabric_bw. + +## Observations on MI350 Credit Stall Behavior + +1. **Write credits exhaust more readily than read credits.** Streaming writes at + peak BW trigger 25% Write Credit Pressure, while streaming reads at similar BW + show only 0.01% Read Credit Pressure. This suggests MI350 has a deeper read + credit pool or faster read credit retirement. + +2. **`TCC_TOO_MANY_EA_WRREQS_STALL` does not fire** for any workload tested. The + global write credit pool exhaustion condition appears difficult to trigger on + MI350. The per-destination credit stall (`TCC_EA0_WRREQ_DRAM_CREDIT_STALL`) + is the primary write bottleneck indicator. + +3. **30.16 normalized stalls can exceed 100%** because stall counters are summed + across 16 TCC channels while the denominator (`GRBM_GUI_ACTIVE_PER_XCD`) is a + single-XCD value. Values like 2962% mean each channel was stalled for ~30x the + kernel duration on average. The 30.18 metrics using `TCC_BUSY_sum` give the + more intuitive per-channel percentage. + +4. **Scattered writes do not trigger EA backpressure.** They bottleneck upstream + at L1/L2 coalescing, throttling the EA write request rate. Only coalesced + streaming writes at maximum rate exhaust EA write credits. + +5. **GMI latency is comparable to IO/PCIe latency.** Read latency via GMI + (21,858 cyc) is similar to IO/PCIe latency (27,533 cyc), both being an order + of magnitude higher than HBM latency (~1,500 cyc). This reflects the inherent + cost of crossing chip/package boundaries. + +6. **Latency metrics show both "average latency" (SUM/SUM) and "latency" + (AVG/AVG) variants.** In all workloads tested on MI350, the two variants + report identical values. This is expected for single-dispatch profiling since + there is only one data point, making SUM/SUM and AVG/AVG equivalent. diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_atomic_pressure.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_atomic_pressure.hip new file mode 100644 index 0000000..6e92e89 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_atomic_pressure.hip @@ -0,0 +1,134 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// EA HBM Atomic Pressure Workload +// +// Target metrics: EA HBM Atomic Pressure (30.18.7), +// EA atomic fraction of writes (30.13.6), +// EA HBM write vs atomic split (30.13.7), +// EA atomic latency (30.14.12) +// +// Baseline: Histogram-like pattern with atomicAdd to a 64K-bin output array. +// All threads issue atomics to global memory, creating high atomic +// traffic at HBM. +// Expected: HBM Atomic Pressure > 50%, atomic fraction > 50%. +// +// Optimized: LDS-local partial histogram per workgroup, with a single +// atomicAdd per bin per workgroup to global memory. +// Expected: HBM Atomic Pressure < 10%, atomic fraction < 10%. + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +constexpr int NUM_BINS = 65536; // 64K bins + +// Baseline: Every thread atomicAdd directly to global histogram — heavy atomic traffic +__global__ void ea_atomic_pressure_baseline(const int* __restrict__ input, + int* __restrict__ histogram, + int N) { + int tid = blockIdx.x * blockDim.x + threadIdx.x; + int stride = gridDim.x * blockDim.x; + + for (int i = tid; i < N; i += stride) { + int bin = input[i] & (NUM_BINS - 1); // Mask to bin range + atomicAdd(&histogram[bin], 1); + } +} + +// Optimized: LDS-local histogram, then merge to global with far fewer atomics +__global__ void ea_atomic_pressure_optimized(const int* __restrict__ input, + int* __restrict__ histogram, + int N) { + // Use a subset of bins in LDS to fit in shared memory + // 256 bins * 4 bytes = 1KB per workgroup (fits easily) + constexpr int LDS_BINS = 256; + __shared__ int local_hist[LDS_BINS]; + + // Initialize local histogram + for (int i = threadIdx.x; i < LDS_BINS; i += blockDim.x) { + local_hist[i] = 0; + } + __syncthreads(); + + // Accumulate into LDS-local histogram (atomics to LDS are fast) + int tid = blockIdx.x * blockDim.x + threadIdx.x; + int stride = gridDim.x * blockDim.x; + + for (int i = tid; i < N; i += stride) { + int bin = input[i] & (LDS_BINS - 1); + atomicAdd(&local_hist[bin], 1); + } + __syncthreads(); + + // Merge local histogram to global — only LDS_BINS atomics per workgroup + for (int i = threadIdx.x; i < LDS_BINS; i += blockDim.x) { + if (local_hist[i] > 0) { + atomicAdd(&histogram[i], local_hist[i]); + } + } +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + // 1GB of ints = 256M elements + int N = 256 * 1024 * 1024; + size_t input_bytes = (size_t)N * sizeof(int); + size_t hist_bytes = NUM_BINS * sizeof(int); + + int* d_input; + CHECK_HIP(hipMalloc(&d_input, input_bytes)); + + int* d_histogram; + CHECK_HIP(hipMalloc(&d_histogram, hist_bytes)); + + // Host init + copy (no hipMemset) + int* h_input = new int[N]; + for (int i = 0; i < N; i++) { + // Pseudo-random bin assignment to spread atomics + h_input[i] = (i * 2654435761u) & (NUM_BINS - 1); + } + CHECK_HIP(hipMemcpy(d_input, h_input, input_bytes, hipMemcpyHostToDevice)); + delete[] h_input; + + // Zero-init histogram via host copy + int* h_hist = new int[NUM_BINS](); + CHECK_HIP(hipMemcpy(d_histogram, h_hist, hist_bytes, hipMemcpyHostToDevice)); + delete[] h_hist; + + hipDeviceProp_t prop; + CHECK_HIP(hipGetDeviceProperties(&prop, 0)); + int num_blocks = prop.multiProcessorCount * 8; + + dim3 block(256); + dim3 grid(num_blocks); + + // Single dispatch for clean profiling + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + ea_atomic_pressure_optimized<<>>(d_input, d_histogram, N); + } else { + ea_atomic_pressure_baseline<<>>(d_input, d_histogram, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_input)); + CHECK_HIP(hipFree(d_histogram)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_fabric_bw.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_fabric_bw.hip new file mode 100644 index 0000000..1d27641 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_fabric_bw.hip @@ -0,0 +1,133 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// EA GMI Bandwidth Workload (multi-GPU) +// +// Target metrics: EA GMI BW Bound - Combined (30.18.3), +// EA read BW fraction - GMI (30.13.3), +// EA Stall Destination Dominance - GMI (30.18.9), +// EA to GMI read BW (30.14.4) +// +// Baseline: Streaming reads from remote GPU memory via peer access. +// All traffic traverses GMI. +// Expected: GMI BW Bound > 10%, read BW fraction GMI > 90%. +// +// Optimized: Copy remote data to local GPU memory first, then read locally. +// All traffic via HBM. +// Expected: GMI BW Bound ~ 0%, Stall Dominance HBM ~ 100%. +// +// Requires >= 2 GPUs. Skips gracefully on single-GPU systems. + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +// Streaming read kernel — same kernel used for both baseline (remote ptr) and optimized (local ptr) +__global__ void ea_fabric_bw_read(const double* __restrict__ data, + double* __restrict__ out, + long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)gridDim.x * blockDim.x; + + double acc = 0.0; + for (long long i = tid; i < N; i += stride) { + acc += data[i]; + } + + if (tid < N) { + out[tid] = acc; + } +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + int device_count; + CHECK_HIP(hipGetDeviceCount(&device_count)); + + if (device_count < 2) { + std::cout << "Skipping: requires >= 2 GPUs (found " << device_count << ")\n"; + return 0; + } + + // Verify peer access is possible + int can_access; + CHECK_HIP(hipDeviceCanAccessPeer(&can_access, 0, 1)); + if (!can_access) { + std::cout << "Skipping: GPU 0 cannot access GPU 1 peer memory\n"; + return 0; + } + + // 2GB of doubles = 256M elements + long long N = 256LL * 1024 * 1024; + size_t bytes = (size_t)N * sizeof(double); + + // Allocate source buffer on GPU 1 + CHECK_HIP(hipSetDevice(1)); + double* d_remote; + hipError_t alloc_err = hipMalloc(&d_remote, bytes); + if (alloc_err != hipSuccess) { + // Fallback to 1GB + N = 128LL * 1024 * 1024; + bytes = (size_t)N * sizeof(double); + CHECK_HIP(hipMalloc(&d_remote, bytes)); + } + + // Host init + copy to GPU 1 + double* h_data = new double[N]; + for (long long i = 0; i < N; i++) h_data[i] = i * 0.001; + CHECK_HIP(hipMemcpy(d_remote, h_data, bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + // Switch to GPU 0 and enable peer access to GPU 1 + CHECK_HIP(hipSetDevice(0)); + CHECK_HIP(hipDeviceEnablePeerAccess(1, 0)); + + // Allocate output buffer on GPU 0 + double* d_out; + CHECK_HIP(hipMalloc(&d_out, bytes)); + + // For optimized: allocate local copy on GPU 0 + double* d_local = nullptr; + if (use_optimized) { + CHECK_HIP(hipMalloc(&d_local, bytes)); + CHECK_HIP(hipMemcpyPeer(d_local, 0, d_remote, 1, bytes)); + CHECK_HIP(hipDeviceSynchronize()); + } + + hipDeviceProp_t prop; + CHECK_HIP(hipGetDeviceProperties(&prop, 0)); + int num_blocks = prop.multiProcessorCount * 8; + + dim3 block(256); + dim3 grid(num_blocks); + + // Pointer to read from: remote (baseline) or local (optimized) + const double* read_ptr = use_optimized ? d_local : d_remote; + + // Single dispatch for clean profiling + auto start = std::chrono::high_resolution_clock::now(); + ea_fabric_bw_read<<>>(read_ptr, d_out, N); + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_out)); + if (d_local) CHECK_HIP(hipFree(d_local)); + CHECK_HIP(hipSetDevice(1)); + CHECK_HIP(hipFree(d_remote)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_read_bw.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_read_bw.hip new file mode 100644 index 0000000..741d61e --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_read_bw.hip @@ -0,0 +1,127 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// EA HBM Read Credit Pressure Workload +// +// Target metrics: EA HBM BW Bound - Read Credit Pressure (30.18.0), +// EA to HBM read BW (30.14.0), EA read latency (30.14.10) +// +// Baseline: Streaming reads from a 4GB buffer (>> 4MB L2) using grid-stride loop. +// Coalesced sequential access maximizes read request rate, flooding EA +// with outstanding read requests and exhausting HBM read credits. +// Expected: Read Credit Pressure > 10%, high EA to HBM read BW. +// +// Optimized: Tiled access pattern where each workgroup reads a tile that fits +// partially in L2, then reuses it across multiple passes. +// L2 hits reduce EA read requests. +// Expected: Read Credit Pressure < 5%, lower EA to HBM read BW. + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +// Baseline: Streaming reads across entire 4GB buffer — saturates HBM read BW +__global__ void ea_hbm_read_bw_baseline(const double* __restrict__ data, + double* __restrict__ out, + long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)gridDim.x * blockDim.x; + + double acc = 0.0; + for (long long i = tid; i < N; i += stride) { + acc += data[i]; + } + // One write per workgroup to avoid polluting write metrics + if (threadIdx.x == 0) { + out[blockIdx.x] = acc; + } +} + +// Optimized: All workgroups re-read a small region that fits in L2 (~2MB) +// After the first pass cold-misses, subsequent passes hit in L2 cache. +// This dramatically reduces EA read requests compared to streaming the full buffer. +__global__ void ea_hbm_read_bw_optimized(const double* __restrict__ data, + double* __restrict__ out, + long long N) { + // Region that fits in L2: 2MB = 256K doubles + // MI350 has 4MB L2 per XCD; 2MB leaves room for other traffic + constexpr long long REGION_ELEMS = 256LL * 1024; // 2MB + constexpr int PASSES = 256; // Re-read the region many times + + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)gridDim.x * blockDim.x; + + double acc = 0.0; + for (int pass = 0; pass < PASSES; pass++) { + for (long long i = tid; i < REGION_ELEMS; i += stride) { + acc += data[i]; + } + } + + // One write per workgroup — negligible write traffic + if (threadIdx.x == 0) { + out[blockIdx.x] = acc; + } +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + // 4GB of doubles = 512M elements + long long N = 512LL * 1024 * 1024; + size_t bytes = (size_t)N * sizeof(double); + + double* d_data; + hipError_t alloc_err = hipMalloc(&d_data, bytes); + if (alloc_err != hipSuccess) { + // Fallback to 2GB if 4GB fails + N = 256LL * 1024 * 1024; + bytes = (size_t)N * sizeof(double); + CHECK_HIP(hipMalloc(&d_data, bytes)); + } + + // Host init + copy (no hipMemset — avoids __amd_rocclr_fillBufferAligned) + double* h_data = new double[N]; + for (long long i = 0; i < N; i++) h_data[i] = i * 0.001; + CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + hipDeviceProp_t prop; + CHECK_HIP(hipGetDeviceProperties(&prop, 0)); + int num_blocks = prop.multiProcessorCount * 8; + + // Output: one element per workgroup (negligible write traffic) + double* d_out; + CHECK_HIP(hipMalloc(&d_out, (size_t)num_blocks * sizeof(double))); + + dim3 block(256); + dim3 grid(num_blocks); + + // Single dispatch for clean profiling + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + ea_hbm_read_bw_optimized<<>>(d_data, d_out, N); + } else { + ea_hbm_read_bw_baseline<<>>(d_data, d_out, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + CHECK_HIP(hipFree(d_out)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_write_bw.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_write_bw.hip new file mode 100644 index 0000000..8296b0a --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_write_bw.hip @@ -0,0 +1,104 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// EA HBM Write Credit Pressure Workload +// +// Target metrics: EA HBM BW Bound - Write Credit Pressure (30.18.1), +// EA to HBM write BW (30.14.1), EA Write Backpressure (30.18.5) +// +// Baseline: Streaming writes to a 4GB buffer using grid-stride loop. +// Each thread writes sequentially, maximizing write request rate. +// Expected: Write Credit Pressure > 10%, high EA to HBM write BW. +// +// Optimized: Accumulate partial results in registers across multiple iterations. +// Write only the final accumulated value, reducing total write +// requests by a large factor. +// Expected: Write Credit Pressure < 5%, lower write BW. + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +// Baseline: Streaming writes across entire buffer — saturates HBM write BW +__global__ void ea_hbm_write_bw_baseline(double* __restrict__ data, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)gridDim.x * blockDim.x; + + for (long long i = tid; i < N; i += stride) { + data[i] = (double)i * 0.001; + } +} + +// Optimized: Accumulate in registers, write only final result +__global__ void ea_hbm_write_bw_optimized(double* __restrict__ data, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)gridDim.x * blockDim.x; + + // Accumulate across all iterations in registers + double acc = 0.0; + for (long long i = tid; i < N; i += stride) { + acc += (double)i * 0.001; + } + + // Single write per thread + if (tid < N) { + data[tid] = acc; + } +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + // 4GB of doubles = 512M elements + long long N = 512LL * 1024 * 1024; + size_t bytes = (size_t)N * sizeof(double); + + double* d_data; + hipError_t alloc_err = hipMalloc(&d_data, bytes); + if (alloc_err != hipSuccess) { + // Fallback to 2GB + N = 256LL * 1024 * 1024; + bytes = (size_t)N * sizeof(double); + CHECK_HIP(hipMalloc(&d_data, bytes)); + } + + // Host init + copy (no hipMemset) + double* h_data = new double[N]; + for (long long i = 0; i < N; i++) h_data[i] = 0.0; + CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + hipDeviceProp_t prop; + CHECK_HIP(hipGetDeviceProperties(&prop, 0)); + int num_blocks = prop.multiProcessorCount * 8; + + dim3 block(256); + dim3 grid(num_blocks); + + // Single dispatch for clean profiling + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + ea_hbm_write_bw_optimized<<>>(d_data, N); + } else { + ea_hbm_write_bw_baseline<<>>(d_data, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_io_bw.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_io_bw.hip new file mode 100644 index 0000000..4fd47e1 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_io_bw.hip @@ -0,0 +1,104 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// EA IO/PCIe Bandwidth Workload +// +// Target metrics: EA IO BW Bound - Combined (30.18.4), +// EA read BW fraction - IO (30.13.4), +// EA Stall Destination Dominance - IO (30.18.10), +// EA to IO read BW (30.14.7) +// +// Baseline: Streaming reads from host-pinned (fine-grained coherent) memory. +// All traffic traverses PCIe/IO path. +// Expected: IO BW Bound > 5%, read BW fraction IO > 90%. +// +// Optimized: Copy host data to device memory first, then read from local HBM. +// Expected: IO BW Bound ~ 0%, Stall Dominance HBM ~ 100%. +// +// Note: IO credit stalls may be lower in absolute terms than HBM stalls +// because PCIe BW is inherently lower. The key validation is that the +// Stall Destination Dominance metric correctly identifies IO as the +// dominant stall source in the baseline. + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +// Streaming read kernel — same kernel for both cases, just different pointers +__global__ void ea_io_bw_read(const double* __restrict__ data, + double* __restrict__ out, + long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)gridDim.x * blockDim.x; + + double acc = 0.0; + for (long long i = tid; i < N; i += stride) { + acc += data[i]; + } + + if (tid < N) { + out[tid] = acc; + } +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + // 1GB of doubles = 128M elements + long long N = 128LL * 1024 * 1024; + size_t bytes = (size_t)N * sizeof(double); + + // Allocate host-pinned coherent memory (fine-grained, visible via PCIe) + double* h_pinned; + CHECK_HIP(hipHostMalloc(&h_pinned, bytes, hipHostMallocCoherent)); + + // Initialize host data + for (long long i = 0; i < N; i++) h_pinned[i] = i * 0.001; + + // Allocate device output buffer + double* d_out; + CHECK_HIP(hipMalloc(&d_out, bytes)); + + // For optimized: copy to device memory first + double* d_local = nullptr; + if (use_optimized) { + CHECK_HIP(hipMalloc(&d_local, bytes)); + CHECK_HIP(hipMemcpy(d_local, h_pinned, bytes, hipMemcpyHostToDevice)); + CHECK_HIP(hipDeviceSynchronize()); + } + + hipDeviceProp_t prop; + CHECK_HIP(hipGetDeviceProperties(&prop, 0)); + int num_blocks = prop.multiProcessorCount * 8; + + dim3 block(256); + dim3 grid(num_blocks); + + // Pointer to read from: host-pinned (baseline) or device-local (optimized) + const double* read_ptr = use_optimized ? d_local : h_pinned; + + // Single dispatch for clean profiling + auto start = std::chrono::high_resolution_clock::now(); + ea_io_bw_read<<>>(read_ptr, d_out, N); + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_out)); + if (d_local) CHECK_HIP(hipFree(d_local)); + CHECK_HIP(hipHostFree(h_pinned)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_rw_balance.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_rw_balance.hip new file mode 100644 index 0000000..aa89eea --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_rw_balance.hip @@ -0,0 +1,113 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// EA Read/Write Balance Workload +// +// Target metrics: EA Read/Write Balance (30.18.6), +// EA read request fraction - HBM (30.13.1), +// EA write request fraction - HBM (30.13.5) +// +// Baseline: Read-only streaming — sum a large array, write a single scalar. +// Expected: R/W Balance > 95% (nearly all reads). +// +// Optimized: Balanced read-modify-write — vector copy from source to dest (1:1 R/W). +// Expected: R/W Balance ~ 33-50%. + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +// Baseline: Read-only streaming — extreme read imbalance +__global__ void ea_rw_balance_baseline(const double* __restrict__ src, + double* __restrict__ result, + long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)gridDim.x * blockDim.x; + + double acc = 0.0; + for (long long i = tid; i < N; i += stride) { + acc += src[i]; + } + + // Single scalar write per thread (negligible write traffic) + if (tid == 0) { + result[0] = acc; + } +} + +// Optimized: 1:1 vector copy — balanced read/write +__global__ void ea_rw_balance_optimized(const double* __restrict__ src, + double* __restrict__ dst, + long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)gridDim.x * blockDim.x; + + for (long long i = tid; i < N; i += stride) { + dst[i] = src[i]; + } +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + // 2GB of doubles = 256M elements + long long N = 256LL * 1024 * 1024; + size_t bytes = (size_t)N * sizeof(double); + + double* d_src; + hipError_t alloc_err = hipMalloc(&d_src, bytes); + if (alloc_err != hipSuccess) { + // Fallback to 1GB + N = 128LL * 1024 * 1024; + bytes = (size_t)N * sizeof(double); + CHECK_HIP(hipMalloc(&d_src, bytes)); + } + + double* d_dst; + CHECK_HIP(hipMalloc(&d_dst, bytes)); + + // Host init + copy (no hipMemset) + double* h_data = new double[N]; + for (long long i = 0; i < N; i++) h_data[i] = i * 0.001; + CHECK_HIP(hipMemcpy(d_src, h_data, bytes, hipMemcpyHostToDevice)); + + // Zero-init destination via host copy + for (long long i = 0; i < N; i++) h_data[i] = 0.0; + CHECK_HIP(hipMemcpy(d_dst, h_data, bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + hipDeviceProp_t prop; + CHECK_HIP(hipGetDeviceProperties(&prop, 0)); + int num_blocks = prop.multiProcessorCount * 8; + + dim3 block(256); + dim3 grid(num_blocks); + + // Single dispatch for clean profiling + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + ea_rw_balance_optimized<<>>(d_src, d_dst, N); + } else { + ea_rw_balance_baseline<<>>(d_src, d_dst, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_src)); + CHECK_HIP(hipFree(d_dst)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_write_backpressure.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_write_backpressure.hip new file mode 100644 index 0000000..0168071 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_write_backpressure.hip @@ -0,0 +1,112 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// EA Write Path Backpressure Workload +// +// Target metrics: EA Write Backpressure (30.18.5), +// Normalized EA - Write stall (30.16.6), +// Normalized EA - Too many writes stall (30.16.7) +// +// Baseline: Coalesced streaming writes at maximum rate. Adjacent threads +// write adjacent addresses across the full buffer in a tight loop +// with no intervening compute. This floods TCC→EA with write +// requests faster than EA can drain credits, triggering +// TCC_EA0_WRREQ_STALL and TCC_EA0_WRREQ_DRAM_CREDIT_STALL. +// Expected: Write Backpressure > 15%. +// +// Optimized: Same total writes but interleaved with heavy ALU work. +// Compute between writes gives EA time to retire credits, +// preventing write credit exhaustion. +// Expected: Write Backpressure < 5%. + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +// Baseline: Coalesced streaming writes at maximum rate — floods EA with write requests +__global__ void ea_write_backpressure_baseline(float* __restrict__ data, int N) { + int tid = blockIdx.x * blockDim.x + threadIdx.x; + int stride = gridDim.x * blockDim.x; + + // Tight write loop with no intervening compute. + // Adjacent threads write adjacent addresses (fully coalesced), + // maximizing write request rate through TCC to EA. + for (int iter = 0; iter < 64; iter++) { + for (int i = tid; i < N; i += stride) { + data[i] = (float)(i + iter); + } + } +} + +// Optimized: Same writes but interleaved with heavy ALU work. +// Compute between writes gives EA time to drain write credits. +__global__ void ea_write_backpressure_optimized(float* __restrict__ data, int N) { + int tid = blockIdx.x * blockDim.x + threadIdx.x; + int stride = gridDim.x * blockDim.x; + + for (int i = tid; i < N; i += stride) { + // Heavy ALU work before each write — throttles write request rate + float val = (float)i; + for (int j = 0; j < 256; j++) { + val = val * 0.9999f + 0.0001f; + } + data[i] = val; + } +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + // 2GB of floats = 512M elements + int N = 512 * 1024 * 1024; + size_t bytes = (size_t)N * sizeof(float); + + float* d_data; + hipError_t alloc_err = hipMalloc(&d_data, bytes); + if (alloc_err != hipSuccess) { + // Fallback to 1GB + N = 256 * 1024 * 1024; + bytes = (size_t)N * sizeof(float); + CHECK_HIP(hipMalloc(&d_data, bytes)); + } + + // Host init + copy (no hipMemset) + float* h_data = new float[N]; + for (int i = 0; i < N; i++) h_data[i] = 0.0f; + CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + hipDeviceProp_t prop; + CHECK_HIP(hipGetDeviceProperties(&prop, 0)); + int num_blocks = prop.multiProcessorCount * 8; + + dim3 block(256); + dim3 grid(num_blocks); + + // Single dispatch for clean profiling + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + ea_write_backpressure_optimized<<>>(d_data, N); + } else { + ea_write_backpressure_baseline<<>>(d_data, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/gl2_backpressure.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/gl2_backpressure.hip new file mode 100644 index 0000000..223b429 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/gl2_backpressure.hip @@ -0,0 +1,96 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +constexpr long long L2_STRIDE = 67 * 1024 * 1024LL; // 67MB - doesn't divide N + +// Baseline: Uncoalesced + large stride -> L2 misses + high request rate -> fill LFIFO -> TCR stall +__global__ void gl2_backpressure_baseline(float* data, int N) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= N) return; + + int lane = threadIdx.x & 63; // Wave64 + + // UNCOALESCED + // LARGE STRIDE + float acc = 0.0f; + for (int i = 0; i < 128; i++) { + long long offset = (long long)idx * 64 + (long long)lane * 32 + (long long)i * L2_STRIDE; + int access_idx = (int)(offset % (long long)N); + acc += data[access_idx]; + } + data[idx] = acc; +} + +// Optimized: Shared memory, minimal L2 traffic, no write pressure +__global__ void gl2_backpressure_optimized(float* data, int N) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= N) return; + + __shared__ float sdata[256]; + sdata[threadIdx.x] = data[idx]; + __syncthreads(); + + float sum = sdata[threadIdx.x]; + for (int i = 0; i < 64; i++) { + sum = sum * 0.99f + 1.0f; + } + + data[idx] = sum; // Single coalesced write +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + // 2GB + int N = 512 * 1024 * 1024; + size_t bytes = (size_t)N * sizeof(float); + + float* d_data; + hipError_t alloc_err = hipMalloc(&d_data, bytes); + if (alloc_err != hipSuccess) { + // Fallback to 1GB if 2GB fails (still exceeds 64MB L2) + N = 256 * 1024 * 1024; + bytes = (size_t)N * sizeof(float); + CHECK_HIP(hipMalloc(&d_data, bytes)); + } + + // Host init + copy (no hipMemset - avoids __amd_rocclr_fillBufferAligned) + float* h_data = new float[N]; + for (int i = 0; i < N; i++) h_data[i] = i * 0.001f; + CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + // Block 64 -> more blocks -> more concurrent waves + dim3 block(64); + dim3 grid((N + block.x - 1) / block.x); + + // Single dispatch for clean profiling (no warmup to avoid double-dispatch in results) + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + hipLaunchKernelGGL(gl2_backpressure_optimized, grid, block, 0, 0, d_data, N); + } else { + hipLaunchKernelGGL(gl2_backpressure_baseline, grid, block, 0, 0, d_data, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/README.md b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/README.md new file mode 100644 index 0000000..9509d87 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/README.md @@ -0,0 +1,119 @@ +# Memory Bandwidth Analysis Test Suite + +HIP workloads targeting MI350 (gfx950) L1 and L2 cache/memory metrics for validation with `rocprof-compute --experimental --membw-analysis`. + +## L1 Workloads (tables 3001-3003) + +| Workload | Baseline (intended effect) | Optimized (intended effect) | +|---|---|---| +| gl2_backpressure | Uncoalesced + large stride -> L2 misses -> fill LFIFO -> TCP stalls. | Shared mem, minimal L2 traffic. | +| L1_stall_microbenchmark | Many scattered loads/stores -> VMEM FIFO fills. | Shared memory reduces VMEM FIFO pressure. | +| utcl1_stall | Rapid page hopping (>32 pages) -> exceed UTCL1 entries -> in-flight stall. | Stay in one page. | +| ta_tcp_stall | Random access, TA waits for TCP. | Sequential access, TCP serves TA quickly. | + +> [!NOTE] +> L1 workloads are still WIP. Profiled results may not yet reflect intended results. + +### L1 Target Metrics + +- **gl2_backpressure**: `TCP_TCR_TCP_STALL_CYCLES / tcp_busy` +- **utcl1_stall**: `TCP_UTCL1_STALL_INFLIGHT_MAX / tcp_busy` + +## L2 Workloads (tables 3007-3012) + +Each workload targets specific TCC perfmon counters exposed in the new L2 metric tables added in PR 4091. All use runtime L2 cache size detection (`hipDeviceAttributeL2CacheSize`). + +| Workload | Target Metrics | Baseline (intended effect) | Optimized (intended effect) | +|---|---|---|---| +| l2_hbm_read_bw_stress | L2-EA read credit stall (HBM), L2-EA read BW, HBM read fraction, L2 hit rate, L2 Cache Efficiency | Streaming reads over 4x L2 buffer. Every access misses L2 -> EA read -> HBM. Exhausts DRAM read credits. | Reads small tile fitting in L2 -> high hit rate, no credit stalls. | +| l2_hbm_write_bw_stress | L2-EA write credit stall (HBM), L2-EA write stall, TOO_MANY_EA_WRREQS_STALL, SRC_FIFO_FULL, writeback rate | Streaming writes over 4x L2 buffer. Write-allocate + eviction of dirty lines -> max EA write traffic. | Writes small region in L2 -> dirty lines stay cached, minimal writeback. | +| l2_cache_thrash | TAG_STALL, IB_STALL, LATENCY_FIFO_FULL, eviction rate, Back Pressure Indicator, Internal Resource Pressure | Scattered RMW on 1.5x L2 buffer (prime stride). Fills latency FIFO, causes tag stalls, IB stalls (backpressure). | Coalesced RMW on small working set -> all hits, no stalls. | +| l2_atomic_stress | EA0_ATOMIC, EA0_ATOMIC_LEVEL (atomic latency), TCC_ATOMIC | High-contention atomicAdd on 1024 cachelines across 2x L2 buffer. Forces EA atomic path with high per-atomic latency. | Regular RMW (no atomics) on L2-fitting tile. Each thread works on its own cacheline. | +| l2_coherence_traffic | NC_REQ, UC_REQ, CC_REQ, PROBE, PROBE_EVICT | `fg` mode: fine-grained memory (CC type) -> coherence protocol, internal probes. `nc` mode: `__builtin_nontemporal_store` -> NC traffic. | Coarse-grained memory with normal cached accesses. | +| l2_multigpu_fabric | EA0_RDREQ_GMI_CREDIT_STALL, EA0_WRREQ_GMI_CREDIT_STALL, Remote Access Pressure (IF) | GPU 0 reads/writes memory on GPU 1 via P2P -> Infinity Fabric (GMI) credit exhaustion. | N/A (requires 2 GPUs) | +| l2_io_stress | EA0_RDREQ_IO_CREDIT_STALL, EA0_WRREQ_IO_CREDIT_STALL | GPU kernel accesses host-pinned memory (hipHostMalloc) -> PCIe/IO credit exhaustion. | Same kernel on device-local memory. | +| l2_normalized_throughput | All Normalized Stall Metrics (table 30.10), All Throughput Metrics (table 30.11), Combined Credit Pressure (table 30.12) | Mixed R+W streaming over 4x L2 buffer, purely memory-bound. High stalls relative to GRBM_GUI_ACTIVE. | Compute-heavy FMA kernel with minimal memory -> near-zero normalized stalls. | + +### Expected Validation Results + +| Workload | Key Metric | Baseline | Optimized | +|---|---|---|---| +| l2_hbm_read_bw_stress | L2-EA read credit stall (HBM) | >10% | <1% | +| l2_hbm_read_bw_stress | L2 hit rate | <20% | >90% | +| l2_hbm_write_bw_stress | L2-EA write credit stall (HBM) | >10% | <1% | +| l2_hbm_write_bw_stress | L2 writeback rate | >30% | <5% | +| l2_cache_thrash | L2 tag stall rate | >10% | <5% | +| l2_cache_thrash | L2 Back Pressure Indicator | >10% | <5% | +| l2_atomic_stress | L2-EA atomic latency | >100 cyc | N/A (no atomics) | +| l2_coherence_traffic (fg) | Coherent cached req rate | >50% | <5% | +| l2_io_stress | L2-EA read credit stall (IO) | >5% | <1% | +| l2_normalized_throughput | Combined Credit Pressure | >10% | <1% | + +## Build + +```bash +# Build a single workload +hipcc -g .hip -o --offload-arch=gfx950 + +# Build all L2 workloads +for f in l2_*.hip; do + hipcc -g "$f" -o "${f%.hip}" --offload-arch=gfx950 +done +``` + +## Profiling + +```bash +# Note: --no-roof is optional and skips the +# benchmarking that is unrelated to the our purpose + +# Profile baseline +rocprof-compute profile -n _baseline --experimental --membw-analysis --no-roof -- ./ + +# Profile optimized +rocprof-compute profile -n _optimized --experimental --membw-analysis --no-roof -- ./ opt +``` + +### Examples + +```bash +# HBM read stress +rocprof-compute profile -n hbm_read_baseline --experimental --membw-analysis --no-roof -- ./l2_hbm_read_bw_stress +rocprof-compute profile -n hbm_read_optimized --experimental --membw-analysis --no-roof -- ./l2_hbm_read_bw_stress opt + +# Cache thrash +rocprof-compute profile -n thrash_baseline --experimental --membw-analysis --no-roof -- ./l2_cache_thrash +rocprof-compute profile -n thrash_optimized --experimental --membw-analysis --no-roof -- ./l2_cache_thrash opt + +# Coherence (fine-grained mode) +rocprof-compute profile -n coherence_fg --experimental --membw-analysis --no-roof -- ./l2_coherence_traffic +rocprof-compute profile -n coherence_nc --experimental --membw-analysis --no-roof -- ./l2_coherence_traffic nc +rocprof-compute profile -n coherence_opt --experimental --membw-analysis --no-roof -- ./l2_coherence_traffic opt + +# IO stress (host-pinned vs device-local) +rocprof-compute profile -n io_baseline --experimental --membw-analysis --no-roof -- ./l2_io_stress +rocprof-compute profile -n io_optimized --experimental --membw-analysis --no-roof -- ./l2_io_stress opt + +# Multi-GPU (requires 2 GPUs) +rocprof-compute profile -n fabric_read --experimental --membw-analysis --no-roof -- ./l2_multigpu_fabric read +rocprof-compute profile -n fabric_write --experimental --membw-analysis --no-roof -- ./l2_multigpu_fabric write +``` + +## Analyzing + +```bash +rocprof-compute analyze -p +``` + +## Hardware Requirements + +| Workload | GPUs | Notes | +|---|---|---| +| l2_hbm_read_bw_stress | 1 | Single GPU | +| l2_hbm_write_bw_stress | 1 | Single GPU | +| l2_cache_thrash | 1 | Single GPU | +| l2_atomic_stress | 1 | Single GPU | +| l2_coherence_traffic | 1 | Single GPU; fine-grained alloc may not be supported on all platforms | +| l2_multigpu_fabric | 2 | Requires P2P access between GPUs | +| l2_io_stress | 1 | Single GPU; needs sufficient host memory for 1GB pinned alloc | +| l2_normalized_throughput | 1 | Single GPU | diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/VERIFICATION_REPORT.md b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/VERIFICATION_REPORT.md new file mode 100644 index 0000000..85c154d --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/VERIFICATION_REPORT.md @@ -0,0 +1,275 @@ +# L2 Memory Bandwidth Metrics Verification Report + +**Date:** 2026-03-27 +**System:** 2x MI350X GPUs (gfx950), 256 CUs, 128 TCC channels, 4 MB L2 per XCD (8 XCDs) +**Tool:** `src/rocprof-compute analyze -p -b 30` +**Profiling data:** `/tmp/l2_profile_results/` + +## Hardware Spec Cross-Reference (MI350 TCC Perfmon List) + +All TCC counters used in the YAML metric equations were verified against the MI350 perfmon specification: + +| Counter | Perfmon ID | Disclosure | Verified | +|---|---|---|---| +| TCC_PERF_SEL_BUSY | 2 | Public | Yes | +| TCC_PERF_SEL_REQ | 6 | Public | Yes | +| TCC_PERF_SEL_READ | 7 | Public | Yes | +| TCC_PERF_SEL_WRITE | 8 | Public | Yes | +| TCC_PERF_SEL_ATOMIC | 10 | Public | Yes | +| TCC_PERF_SEL_HIT | 21 | Public | Yes | +| TCC_PERF_SEL_MISS | 23 | Public | Yes | +| TCC_PERF_SEL_WRITEBACK | 24 | Public | Yes | +| TCC_PERF_SEL_LATENCY_FIFO_FULL | 27 | Public | Yes | +| TCC_PERF_SEL_SRC_FIFO_FULL | 28 | Public | Yes | +| TCC_PERF_SEL_EA0_WRREQ | 30 | Public | Yes | +| TCC_PERF_SEL_EA0_WRREQ_64B | 31 | Public | Yes | +| TCC_PERF_SEL_EA0_WRREQ_STALL | 34 | Public | Yes | +| TCC_PERF_SEL_EA0_WRREQ_IO_CREDIT_STALL | 35 | Public | Yes | +| TCC_PERF_SEL_EA0_WRREQ_GMI_CREDIT_STALL | 36 | Public | Yes | +| TCC_PERF_SEL_EA0_WRREQ_DRAM_CREDIT_STALL | 37 | Public | Yes | +| TCC_PERF_SEL_TOO_MANY_EA_WRREQS_STALL | 38 | Public | Yes | +| TCC_PERF_SEL_EA0_WRREQ_LEVEL | 39 | Public | Yes | +| TCC_PERF_SEL_EA0_ATOMIC | 40 | Public | Yes | +| TCC_PERF_SEL_EA0_ATOMIC_LEVEL | 41 | Public | Yes | +| TCC_PERF_SEL_EA0_RDREQ | 42 | Public | Yes | +| TCC_PERF_SEL_EA0_RDREQ_32B | 43 | Public | Yes | +| TCC_PERF_SEL_EA0_RDREQ_64B | 44 | Public | Yes | +| TCC_PERF_SEL_EA0_RDREQ_128B | 45 | Public | Yes (MI350-specific) | +| TCC_PERF_SEL_EA0_RDREQ_IO_CREDIT_STALL | 47 | Public | Yes | +| TCC_PERF_SEL_EA0_RDREQ_GMI_CREDIT_STALL | 48 | Public | Yes | +| TCC_PERF_SEL_EA0_RDREQ_DRAM_CREDIT_STALL | 49 | Public | Yes | +| TCC_PERF_SEL_TAG_STALL | 51 | Public | Yes | +| TCC_PERF_SEL_IB_STALL | 68 | Public | Yes | +| TCC_PERF_SEL_NORMAL_EVICT | 80 | Public | Yes | +| TCC_PERF_SEL_PROBE | - | Public | Yes | +| TCC_PERF_SEL_NC_REQ | - | Public | Yes | +| TCC_PERF_SEL_UC_REQ | - | Public | Yes | +| TCC_PERF_SEL_CC_REQ | - | Public | Yes | +| TCC_PERF_SEL_EA0_RDREQ_DRAM | - | Public | Yes | +| TCC_PERF_SEL_EA0_WRREQ_DRAM | - | Public | Yes | +| TCC_PERF_SEL_EA0_RDREQ_LEVEL | - | Public | Yes | + +**Notable:** `TCC_PERF_SEL_EA0_RDREQ_128B` (ID 45) is MI350-specific. The `L2-EA read BW` formula correctly includes 128B reads. No `TCC_EA0_WRREQ_128B` counter exists, so the write BW formula correctly assumes writes are 32B or 64B only. + +**Not collected:** `TCC_PROBE_EVICT` is not present in the membw profiling config. Probe eviction rate is N/A across all workloads. + +## Block 30 Table Coverage + +All 18 workloads were analyzed across the 6 L2 metric tables in block 30. The table below summarizes which tables produce fully numeric results vs. tables with expected N/A values. + +| Table | Name | Result | Notes | +|---|---|---|---| +| 30.7 | L2 Cache | **18/18 PASS** | All metrics produce numeric values | +| 30.8 | L2 Traffic | 18/18 have some N/A | Atomic latency N/A when 0 atomics; Probe eviction N/A (counter not collected) | +| 30.9 | L2 Raw Counter Values | 17/18 PASS, 1 partial | TCC_PROBE_EVICT N/A across all; TCC_CC_REQ anomalous in io_baseline | +| 30.10 | Normalized L2 Stall Metrics (%) | **18/18 PASS** | All metrics produce numeric values | +| 30.11 | Normalized L2 Throughput Metrics (per Second) | **18/18 PASS** | All metrics produce numeric values | +| 30.12 | L2 Bottleneck Detection Indicators | **18/18 PASS** | All metrics produce numeric values | + +All N/A values are legitimate zero-denominator cases (e.g., atomic latency when no atomics issued) or uncollected counters (`TCC_PROBE_EVICT`). + +## Workload Verification Results + +### 1. l2_hbm_read_bw_stress + +| Key Metric | Table | Baseline | Optimized | Expected (B) | Expected (O) | Verdict | +|---|---|---|---|---|---|---| +| L2 hit rate | 30.7 | **0.53%** | 30.32% | <20% | >90% | B: PASS, O: FAIL | +| L2 utilization | 30.7 | **22.63%** | 6.56% | High | Low | **PASS** | +| L2 tag stall rate | 30.7 | **21.23%** | 11.06% | High | Low | **PASS** | +| L2 input buffer stall rate | 30.7 | **22.80%** | 8.34% | High | Low | **PASS** | +| L2-EA read credit stall (HBM) | 30.7 | **15.03%** | 0.00% | >10% | <1% | **PASS** | +| L2 Back Pressure Indicator | 30.12 | **22.80%** | 8.34% | High | Low | **PASS** | +| L2 Memory BW Bound - Combined Credit Pressure | 30.12 | **15.03%** | 0.00% | High | ~0% | **PASS** | +| L2 Cache Efficiency | 30.12 | **0.53%** | 30.32% | Low | High | **PASS** | +| L2 to Memory Traffic Ratio | 30.12 | **99.49%** | 70.57% | ~100% | Lower | **PASS** | + +**Notes:** +- Optimized hit rate is 30% instead of >90%. The tile size (L2/4 = 1MB) may be too large for the 4MB per-XCD L2 with 256 CUs competing. +- The primary metric (HBM read credit stall) validates correctly with a clear baseline-to-optimized contrast (15% -> 0%). +- L2 to Memory Traffic Ratio drops from 99.49% to 70.57% in optimized, confirming improved cache locality. + +### 2. l2_hbm_write_bw_stress + +| Key Metric | Table | Baseline | Optimized | Expected (B) | Expected (O) | Verdict | +|---|---|---|---|---|---|---| +| L2 hit rate | 30.7 | 50.78% | **99.20%** | Low | High | **PASS** | +| L2 utilization | 30.7 | 32.16% | 49.29% | Moderate | High | **PASS** | +| L2-EA write credit stall (HBM) | 30.7 | **42.80%** | 0.14% | High | ~0% | **PASS** | +| L2 to EA write stall rate | 30.7 | **40.28%** | 0.11% | High | ~0% | **PASS** | +| L2 write data FIFO full rate | 30.7 | 0.00% | 0.00% | - | - | **PASS** | +| L2 Memory BW Bound - Combined Credit Pressure | 30.12 | **42.80%** | 0.14% | High | ~0% | **PASS** | + +**Notes:** +- The primary metric (HBM write credit stall) validates correctly with a clear baseline-to-optimized contrast (42.80% -> 0.14%). +- L2 to EA write stall rate tracks closely with the HBM write credit stall, confirming the write path is the bottleneck in baseline. + +### 3. l2_cache_thrash + +| Key Metric | Table | Baseline | Optimized | Expected (B) | Expected (O) | Verdict | +|---|---|---|---|---|---|---| +| L2 hit rate | 30.7 | 88.50% | **99.21%** | Moderate | High | **PASS** | +| L2 utilization | 30.7 | **95.78%** | 54.01% | High | Moderate | **PASS** | +| L2 tag stall rate | 30.7 | **13.37%** | 1.08% | High | Low | **PASS** | +| L2 input buffer stall rate | 30.7 | **8.12%** | 0.93% | High | Low | **PASS** | +| L2 latency FIFO full rate | 30.7 | 0.00% | 0.00% | - | - | **PASS** | +| L2 write data FIFO full rate | 30.7 | 0.68% | 0.00% | Low | ~0% | **PASS** | +| Probe eviction rate | 30.8 | N/A | N/A | - | - | N/A (counter not collected) | +| L2 Back Pressure Indicator | 30.12 | **8.12%** | 0.93% | High | Low | **PASS** | +| L2 Cache Efficiency | 30.12 | 88.50% | **99.21%** | Moderate | High | **PASS** | + +**Notes:** +- The normalized stall metrics (30.10) use `GRBM_GUI_ACTIVE_PER_XCD` as the denominator. With 128 TCC channels each accumulating stall cycles, the sum of per-channel stalls can legitimately exceed the single kernel execution time. The >100% values are architecturally correct for multi-channel L2. + +### 4. l2_atomic_stress + +| Key Metric | Table | Baseline | Optimized | Expected (B) | Expected (O) | Verdict | +|---|---|---|---|---|---|---| +| L2 hit rate | 30.7 | 0.01% | **96.96%** | Low | High | **PASS** | +| L2 utilization | 30.7 | **89.15%** | 21.19% | High | Moderate | **PASS** | +| L2-EA write credit stall (HBM) | 30.7 | **25.82%** | 0.00% | High | ~0% | **PASS** | +| L2 to EA write stall rate | 30.7 | **25.52%** | 0.00% | High | ~0% | **PASS** | +| L2 to EA atomic average latency | 30.8 | **3420.88 cyc** | N/A | High | - | B: **PASS** | +| Probe eviction rate | 30.8 | N/A | N/A | - | - | N/A (counter not collected) | +| L2 Memory BW Bound - Combined Credit Pressure | 30.12 | **25.82%** | 0.00% | High | ~0% | **PASS** | +| L2 Cache Efficiency | 30.12 | 0.01% | **96.96%** | Low | High | **PASS** | + +**Notes:** +- The optimized kernel uses regular RMW (no atomics), so atomic latency is N/A rather than <20 cyc. The contrast between atomics at 3420 cycles latency vs zero atomics validates the metric completely. +- L2 to EA write stall rate closely tracks HBM write credit stall, confirming the EA write path saturates under atomic workloads. + +### 5. l2_coherence_traffic + +| Key Metric | Table | fg mode | nc mode | opt mode | Verdict | +|---|---|---|---|---|---| +| L2 hit rate | 30.7 | 67.16% | 50.99% | 68.29% | Expected pattern | +| L2 utilization | 30.7 | **79.08%** | 18.97% | 19.62% | fg mode high utilization | +| Non-coherent request rate | 30.8 | **92.83%** | 0.00% | 0.00% | fg mode generates NC traffic | +| Uncached request rate | 30.8 | 1.71% | 0.02% | 0.05% | Low across all modes | +| Coherent cached request rate | 30.8 | 0.00% | 0.00% | 0.00% | See note | +| L2-EA write credit stall (HBM) | 30.7 | 0.00% | **13.91%** | 1.67% | nc mode generates HBM write stalls | +| L2-EA write credit stall (IO) | 30.7 | **17.28%** | 0.00% | 0.00% | fg mode uses IO path | +| L2 Cache Efficiency | 30.12 | 67.16% | 50.99% | 68.29% | Expected pattern | + +**Notes:** +- The NC request rate counter now shows 92.83% for fg mode, confirming that `hipHostMallocCoherent` host memory routes through the IO path as NC requests at the TCC level. +- fg mode shows 17.28% IO write credit stall in 30.7, confirming IO/IF path usage. +- The CC request rate is 0% across all modes — coherence is handled at the system level, not at the TCC CC protocol level on MI350. + +### 6. l2_multigpu_fabric + +| Key Metric | Table | read | write | rw | Verdict | +|---|---|---|---|---|---| +| L2 hit rate | 30.7 | 33.34% | 50.01% | 56.94% | Expected pattern | +| L2 utilization | 30.7 | **94.82%** | 66.16% | 72.85% | Read mode saturates L2 | +| L2 tag stall rate | 30.7 | **17.21%** | 13.76% | 10.16% | High across all modes | +| L2 input buffer stall rate | 30.7 | 10.33% | **13.41%** | 9.66% | Write mode highest IB stall | +| L2-EA read credit stall (GMI) | 30.7 | **9.36%** | 0.00% | **11.22%** | Read/RW show GMI stalls | +| L2-EA write credit stall (GMI) | 30.7 | 0.00% | **18.80%** | 0.32% | Write mode shows GMI write stalls | +| L2 Back Pressure Indicator | 30.12 | **10.33%** | 13.41% | 9.66% | High backpressure | +| L2 Remote Access Pressure (GMI) | 30.12 | **9.36%** | **18.80%** | **11.54%** | **PASS** - all modes show GMI pressure | + +**Notes:** +- All three fabric modes show non-zero GMI credit stalls in both 30.7 and 30.12, validating the remote access path. +- The fabric write mode shows 18.80% GMI write credit stall, confirming remote write pressure is correctly detected. +- The 50%/50% split in read traffic is because profiling averages across both GPUs (GPU 0 does remote reads, GPU 1 is idle). + +### 7. l2_io_stress + +| Key Metric | Table | Baseline (host-pinned) | Optimized (device-local) | Expected (B) | Expected (O) | Verdict | +|---|---|---|---|---|---|---| +| L2 hit rate | 30.7 | 58.42% | 57.08% | - | - | Similar hit rates | +| L2 utilization | 30.7 | 61.09% | **91.75%** | Moderate | High | Device-local saturates L2 | +| L2-EA read credit stall (IO) | 30.7 | **0.36%** | 0.00% | >0% | ~0% | **PASS** | +| L2-EA write credit stall (IO) | 30.7 | **7.73%** | 0.00% | High | ~0% | **PASS** | +| L2 write data FIFO full rate | 30.7 | 0.00% | **6.98%** | - | Moderate | Device-local shows write data pressure | +| L2 Back Pressure Indicator | 30.12 | 8.53% | **10.22%** | - | - | Device-local shows higher L2 backpressure | +| L2 Memory BW Bound - Combined Credit Pressure | 30.12 | 0.00% | **7.24%** | ~0% | Moderate | Device-local uses HBM path | +| L2 Internal Resource Pressure - Source FIFO | 30.12 | 0.00% | **6.98%** | ~0% | Moderate | Write data path pressure in device-local | + +**Notes:** +- IO credit stalls validate correctly: baseline shows 0.36% read + 7.73% write IO stalls in 30.7, optimized shows 0%. +- Device-local optimized path shows high L2 utilization (91.75%) and moderate HBM credit pressure (7.24%), confirming it uses the HBM path instead of IO. +- New metric: Source FIFO pressure (6.98%) in device-local confirms the write data path is under pressure when saturating HBM. + +### 8. l2_normalized_throughput + +| Key Metric | Table | Baseline (memory-bound) | Optimized (compute-bound) | Expected (B) | Expected (O) | Verdict | +|---|---|---|---|---|---|---| +| L2 hit rate | 30.7 | 50.22% | 71.20% | Low-Moderate | High | **PASS** | +| L2 utilization | 30.7 | 29.32% | 8.14% | Moderate | Low | **PASS** | +| L2 tag stall rate | 30.7 | **14.70%** | 5.60% | High | Low | **PASS** | +| L2-EA read credit stall (HBM) | 30.7 | **12.54%** | 0.00% | High | ~0% | **PASS** | +| L2 Back Pressure Indicator | 30.12 | **14.15%** | 3.86% | High | Low | **PASS** | +| L2 Memory BW Bound - Combined Credit Pressure | 30.12 | **13.27%** | 0.20% | High | ~0% | **PASS** | +| L2 Cache Efficiency | 30.12 | 50.22% | **71.20%** | Low | High | **PASS** | + +## Summary + +| Workload | Primary Metric Validated? | Baseline/Opt Contrast Clear? | Overall | +|---|---|---|---| +| l2_hbm_read_bw_stress | YES (15% -> 0% HBM read credit stall in 30.7) | YES | **PASS** | +| l2_hbm_write_bw_stress | YES (42.80% -> 0.14% HBM write credit stall in 30.7) | YES | **PASS** | +| l2_cache_thrash | YES (13% tag stall, 8% IB stall in 30.7) | YES | **PASS** | +| l2_atomic_stress | YES (25.82% HBM write credit stall + 3420 cyc atomic latency in 30.7/30.8) | YES (0 atomics in opt) | **PASS** | +| l2_coherence_traffic | YES (92.83% NC in fg, 17.28% IO write stall in fg, 13.91% HBM write stall in nc) | YES | **PASS** | +| l2_multigpu_fabric | YES (9-19% GMI credit stalls in 30.7 and 30.12) | YES | **PASS** | +| l2_io_stress | YES (0.36% read + 7.73% write IO stalls in 30.7) | YES (0% in opt) | **PASS** | +| l2_normalized_throughput | YES (12.54% read credit stall in 30.7) | YES | **PASS** | + +**Result: 8/8 workloads PASS primary metric validation.** + + +## Known Issues + +### 1. Coherence traffic fg mode generates NC, not CC + +**Symptom:** The `l2_coherence_traffic` workload in fg mode (`hipHostMallocCoherent`) generates NC-type requests at the TCP level (92.83% NC request rate), not CC as documented in the README. + +**Root cause:** On MI350, `hipHostMallocCoherent` host memory routes through the IO/IF path. The TCP classifies these accesses as NC (non-coherent), while coherence is handled at the system level (not at the TCC CC protocol level). + +**Impact:** The README threshold "Coherent cached req rate >50%" does not trigger. The workload does exercise the IO/remote path (17.28% IO write credit stall, high EA write latency). + +### 2. Optimized hit rates lower than expected for some workloads + +**Symptom:** `l2_hbm_read_bw_stress` optimized shows 30% hit rate (expected >90%). + +**Root cause:** The L2 is 4 MB per XCD, and with 256 CUs across 8 XCDs, the tile (L2/4 = 1MB) may experience contention. The workloads use `hipDeviceAttributeL2CacheSize` which returns the per-XCD value. + +### 3. Normalized stall percentages exceed 100% + +**Symptom:** Many normalized stall metrics (table 30.10) show values like 435%, 1762%, 1639%. + +**Root cause:** This is architecturally correct. The denominator is `GRBM_GUI_ACTIVE_PER_XCD` (single kernel time dimension), but the numerator sums stall cycles across all 128 TCC channels (or all CUs). Since multiple channels stall simultaneously, the aggregate exceeds the kernel duration. + +### 4. TCC_PROBE_EVICT counter not collected + +**Symptom:** Probe eviction rate is N/A across all 18 workloads. + +**Root cause:** `TCC_PROBE_EVICT` is not included in the membw profiling counter set. The profiling config collects `TCC_PROBE` (probe requests) but not the eviction subset. + +**Impact:** Cannot distinguish between probes that evict cache lines vs probes that find clean lines. The probe request rate metric (30.8) still functions correctly. + +### 5. TCP_CACHE_MISS_sum not in profiling data + +**Symptom:** TCP hit rate and TCP miss rate (table 30.1) are N/A across all workloads. + +**Root cause:** The profiling config collects `TCP_CACHE_MISS` (per-CU scalar) but not `TCP_CACHE_MISS_sum` (aggregated across CUs). The YAML formulas reference `TCP_CACHE_MISS_sum`. + +**Impact:** L1 TCP cache hit/miss rate cannot be computed from this profiling data. The L2 metrics (tables 30.7-30.12) are unaffected. + +### 6. TCC_CC_REQ counter semantics anomaly + +**Symptom:** `Coherent cached request rate` shows 3238.37% in `io_baseline` (TCC_CC_REQ=836M vs TCC_REQ=25.8M). + +**Root cause:** On MI350, `TCC_CC_REQ` appears to count differently than `TCC_REQ` for IO/host-pinned memory workloads. The counter value far exceeds total L2 requests, suggesting it counts at a different granularity or includes internal coherence protocol events. + +**Impact:** The Coherent cached request rate metric is unreliable for IO workloads. Other workloads show 0% CC request rate which is expected. + +### 7. HBM read/write fraction slightly exceeds 100% + +**Symptom:** Some workloads show HBM read fraction >100% (e.g., 100.84% in atomic_baseline, 102.47% in fabric_write). + +**Root cause:** `TCC_EA0_RDREQ_DRAM` and `TCC_EA0_RDREQ` are collected in separate profiling passes with spatial multiplexing. Minor counter timing differences between passes can cause the ratio to slightly exceed 100%. + +**Impact:** Values are within counter noise margin. No functional impact. diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_atomic_stress.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_atomic_stress.hip new file mode 100644 index 0000000..0441af7 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_atomic_stress.hip @@ -0,0 +1,132 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// L2 Atomic Stress +// +// Target metrics (tables 3008, 3009): +// - L2-EA atomic latency [TCC_EA0_ATOMIC_LEVEL / TCC_EA0_ATOMIC] +// - L2 total request rate [TCC_REQ / TCC_BUSY] +// - TCC_EA0_ATOMIC (raw) +// - TCC_ATOMIC (raw) +// - L2-EA read/write latency (atomics contend with both paths) +// +// Mechanism: +// Baseline: Many threads perform atomicAdd on a small set of addresses +// scattered across a buffer larger than L2. With thousands of threads +// targeting few cachelines, each atomic must serialize, and those that +// miss L2 go to EA as atomic requests. +// The TCC_EA0_ATOMIC counter tracks atomics sent to the EA interface. +// High contention -> high per-atomic latency (TCC_EA0_ATOMIC_LEVEL). +// +// Optimized: Uses regular read-modify-write (no atomics) on a small +// buffer that fits in L2. This eliminates EA atomic traffic entirely, +// showing the contrast between atomic-heavy and atomic-free paths. +// +// Build: hipcc -g l2_atomic_stress.hip -o l2_atomic_stress +// Run: ./l2_atomic_stress (baseline) +// ./l2_atomic_stress opt (optimized) + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +size_t getL2CacheSize() { + int val = 0; + CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); + if (val > 0) return (size_t)val; + return 256ULL * 1024 * 1024; +} + +// Baseline: High-contention atomics on scattered addresses > L2 size. +// num_targets is small, so many threads compete +// on the same addresses. Buffer > L2 means atomics often go to EA. +__global__ void l2_stress_with_atomic(float* __restrict__ data, + long long N, int num_targets) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + + // Map thread to one of num_targets cachelines, spread across the buffer. + // This creates high contention (many threads -> same cacheline) AND + // L2 misses (targets spread over buffer > L2). + long long target_spacing = N / (long long)num_targets; + // Ensure spacing is cacheline-aligned + target_spacing = (target_spacing / 16) * 16; + if (target_spacing < 16) target_spacing = 16; + + int target_idx = (int)(tid % (long long)num_targets); + long long addr = (long long)target_idx * target_spacing; + if (addr >= N) addr = addr % N; + + // Repeated atomics on the same target -> high contention + EA atomics + for (int iter = 0; iter < 512; iter++) { + atomicAdd(&data[addr], 1.0f); + } +} + +// Optimized: Regular read-modify-write (no atomics) on a buffer that +// fits in L2. Each thread works on its own cacheline. No EA atomic +// traffic is generated. This contrasts with the baseline which sends +// all operations through the EA atomic path. +__global__ void l2_stress_without_atomic(float* __restrict__ data, + long long N, long long tile_elems) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + + // Each thread gets a unique cacheline within the L2-fitting tile + long long addr = (tid * 16) % tile_elems; + + // Regular RMW instead of atomics: hits in L2, no EA atomic traffic + for (int iter = 0; iter < 512; iter++) { + data[addr] = data[addr] + 1.0f; + } +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + size_t l2_size = getL2CacheSize(); + std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; + + // Buffer 2x L2 for baseline (atomics scattered beyond L2) + size_t buffer_bytes = l2_size * 2; + long long N = (long long)(buffer_bytes / sizeof(float)); + long long tile_elems = (long long)(l2_size / (4 * sizeof(float))); + + // Number of distinct target cachelines for contention. + int num_targets = 1024; + + float* d_data; + CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); + + float* h_data = new float[N]; + for (long long i = 0; i < N; i++) h_data[i] = 0.0f; + CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + dim3 block(64); + dim3 grid(4096); + + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + l2_stress_without_atomic<<>>(d_data, N, tile_elems); + } else { + l2_stress_with_atomic<<>>(d_data, N, num_targets); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_cache_thrash.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_cache_thrash.hip new file mode 100644 index 0000000..fde416d --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_cache_thrash.hip @@ -0,0 +1,136 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// L2 Cache Thrash — Internal Stall Stress +// +// Target metrics (tables 3007, 3008, 3009, 3010): +// - L2 tag stall rate [TCC_TAG_STALL] +// - L2 input buffer stall rate [TCC_IB_STALL] (back pressure indicator) +// - L2 latency FIFO full rate [TCC_LATENCY_FIFO_FULL] +// - L2 utilization [TCC_BUSY / total_l2_chan * GRBM] +// - L2 eviction rate [TCC_NORMAL_EVICT] +// - L2 Back Pressure Indicator (table 3009) +// - L2 Internal Resource Pressure (table 3009) +// - Normalized L2 stalls (table 3010) +// +// Mechanism: +// Baseline: Read-modify-write on scattered cachelines across a buffer +// 1.5x L2 size. The scatter pattern (prime-stride) ensures all TCC channels +// are hit simultaneously. Each thread does repeated RMW iterations, creating: +// 1. High miss rate -> outstanding EA reads fill the latency FIFO +// 2. Dirty cachelines -> writeback on eviction -> writeback FIFO pressure +// 3. Continuous tag lookups with misses -> TAG_STALL +// 4. L2 can't accept new L1 requests -> IB_STALL -> backpressure +// 5. TAG_MISS_NOTHING_REPLACEABLE_STALL when all lines have pending fills +// +// Optimized: Coalesced RMW on a small working set that fits in L2. +// High hit rate, no stalls. +// +// Build: hipcc -g l2_cache_thrash.hip -o l2_cache_thrash +// Run: ./l2_cache_thrash (baseline) +// ./l2_cache_thrash opt (optimized) + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +size_t getL2CacheSize() { + int val = 0; + CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); + if (val > 0) return (size_t)val; + return 256ULL * 1024 * 1024; +} + +// Baseline: Scattered read-modify-write over buffer > L2. +// Prime-number stride ensures accesses are spread across TCC channels +// and consecutive iterations hit different cachelines, preventing reuse. +// The RMW pattern makes cachelines dirty, so eviction triggers writebacks. +__global__ void cache_thrash_baseline(float* __restrict__ data, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + + // Use a large prime stride to scatter across TCC channels. + // 7919 * 16 floats = ~500KB stride per step -> wraps around buffer, + // hitting different cachelines each iteration. + const long long prime_stride = 7919LL * 16; // in float elements + + long long idx = (tid * 16) % N; // Start at unique cacheline + + float acc = 0.0f; + for (int iter = 0; iter < 256; iter++) { + // Read-modify-write: makes the cacheline dirty + float val = data[idx]; + acc += val; + data[idx] = val + 1.0f; + + // Jump to a different cacheline (scattered pattern) + idx = (idx + prime_stride) % N; + } + + // Prevent optimization + if (acc == -999.0f) data[0] = acc; +} + +// Optimized: Sequential coalesced RMW on a small working set. +// All accesses hit in L2 after first pass. +__global__ void cache_thrash_optimized(float* __restrict__ data, long long N, + long long tile_elems) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + for (int rep = 0; rep < 64; rep++) { + for (long long i = tid; i < tile_elems; i += stride) { + data[i] = data[i] + 1.0f; + } + } +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + size_t l2_size = getL2CacheSize(); + std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; + + // 1.5x L2 for baseline: large enough to thrash but not so large + // that we spend all time on memory transfers + size_t buffer_bytes = (l2_size * 3) / 2; + long long N = (long long)(buffer_bytes / sizeof(float)); + long long tile_elems = (long long)(l2_size / (4 * sizeof(float))); + + float* d_data; + CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); + + float* h_data = new float[N]; + for (long long i = 0; i < N; i++) h_data[i] = (float)(i % 1000) * 0.001f; + CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + // More blocks = more concurrent waves = more L2 pressure + dim3 block(64); + dim3 grid(8192); + + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + cache_thrash_optimized<<>>(d_data, N, tile_elems); + } else { + cache_thrash_baseline<<>>(d_data, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_coherence_traffic.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_coherence_traffic.hip new file mode 100644 index 0000000..f140f82 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_coherence_traffic.hip @@ -0,0 +1,164 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// L2 Coherence Traffic +// +// Target metrics (tables 3008, 3009): +// - Non-coherent request rate [TCC_NC_REQ / TCC_REQ] +// - Uncached request rate [TCC_UC_REQ / TCC_REQ] +// - Coherent cached request rate [TCC_CC_REQ / TCC_REQ] +// - Probe request rate [TCC_PROBE / TCC_BUSY] +// - Probe eviction rate [TCC_PROBE_EVICT / TCC_PROBE] +// +// Mechanism: +// Baseline (mode=fg): Uses hipHostMalloc with hipHostMallocCoherent flag +// to allocate system memory visible to the GPU. GPU accesses to this +// coherent host memory go through the IO/IF path and should generate +// UC (uncached) or CC (coherent cached) requests, elevating TCC_UC_REQ +// and/or TCC_CC_REQ. The kernel does read-modify-write on this memory. +// +// Baseline (mode=nc): Attempts hipExtMallocWithFlags(hipDeviceMallocFinegrained), +// falling back to hipMalloc if unavailable. The kernel unconditionally uses +// __builtin_nontemporal_store to generate non-coherent streaming traffic (TCC_NC_REQ). +// +// Optimized: Standard coarse-grained hipMalloc with normal cached accesses. +// Most requests will be normal cached (not NC/UC/CC). +// +// Build: hipcc -g l2_coherence_traffic.hip -o l2_coherence_traffic +// Run: ./l2_coherence_traffic (fine-grained CC traffic) +// ./l2_coherence_traffic nc (non-temporal/streaming traffic) +// ./l2_coherence_traffic opt (optimized, coarse-grained) + +#include +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +size_t getL2CacheSize() { + int val = 0; + CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); + if (val > 0) return (size_t)val; + return 256ULL * 1024 * 1024; +} + +// Fine-grained RMW: CC memory type -> coherence protocol active. +// Each thread does read-modify-write which triggers internal probes +// (TCC self-probes for CC writes) and coherence tracking. +__global__ void coherence_fg_baseline(float* __restrict__ data, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + for (long long i = tid; i < N; i += stride) { + // RMW on fine-grained memory: triggers CC protocol + data[i] = data[i] + 1.0f; + } +} + +// Non-temporal streaming write: generates NC (non-coherent) traffic. +// Uses __builtin_nontemporal_store to bypass cache coherence. +__global__ void coherence_nc_baseline(float* __restrict__ data, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + for (long long i = tid; i < N; i += stride) { + __builtin_nontemporal_store((float)i, &data[i]); + } +} + +// Optimized: Standard coarse-grained memory with normal cached accesses. +// No coherence protocol overhead, no non-temporal hints. +__global__ void coherence_optimized(float* __restrict__ data, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + for (long long i = tid; i < N; i += stride) { + data[i] = data[i] + 1.0f; + } +} + +int main(int argc, char** argv) { + enum Mode { FG, NC, OPT } mode = FG; + if (argc > 1) { + if (std::string(argv[1]) == "nc") mode = NC; + else if (std::string(argv[1]) == "opt") mode = OPT; + } + + size_t l2_size = getL2CacheSize(); + std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; + + // Use 1/2 L2 size for fine-grained (smaller to keep coherence overhead dominant) + // Use 2x L2 for NC (larger to generate streaming traffic) + size_t buffer_bytes; + if (mode == NC) { + buffer_bytes = l2_size * 2; + } else { + buffer_bytes = l2_size / 2; + } + long long N = (long long)(buffer_bytes / sizeof(float)); + + float* d_data = nullptr; + + if (mode == FG) { + // Coherent host memory: GPU accesses go through IO/IF path + // generating UC or CC requests in TCC + CHECK_HIP(hipHostMalloc(&d_data, buffer_bytes, hipHostMallocCoherent)); + } else if (mode == NC) { + // Try fine-grained device allocation first for NC traffic + hipError_t err = hipExtMallocWithFlags((void**)&d_data, buffer_bytes, + hipDeviceMallocFinegrained); + if (err != hipSuccess) { + std::cerr << "Warning: hipExtMallocWithFlags(Finegrained) failed: " + << hipGetErrorString(err) << "\n" + << "Falling back to hipMalloc with nontemporal stores\n"; + CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); + } + } else { + // Coarse-grained for OPT mode + CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); + } + + // Initialize + float* h_data = new float[N]; + for (long long i = 0; i < N; i++) h_data[i] = (float)(i % 1000) * 0.001f; + CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + dim3 block(64); + dim3 grid(4096); + + const char* mode_str = (mode == FG) ? "Fine-grained (CC)" : + (mode == NC) ? "Non-temporal (NC)" : "Optimized (coarse)"; + std::cout << "Mode: " << mode_str << "\n"; + + auto start = std::chrono::high_resolution_clock::now(); + if (mode == FG) { + coherence_fg_baseline<<>>(d_data, N); + } else if (mode == NC) { + coherence_nc_baseline<<>>(d_data, N); + } else { + coherence_optimized<<>>(d_data, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << "Time: " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + if (mode == FG) { + CHECK_HIP(hipHostFree(d_data)); + } else { + CHECK_HIP(hipFree(d_data)); + } + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_read_bw_stress.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_read_bw_stress.hip new file mode 100644 index 0000000..eead275 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_read_bw_stress.hip @@ -0,0 +1,140 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// L2 HBM Read Bandwidth Stress +// +// Target metrics (tables 3007, 3008, 3009): +// - L2-EA read credit stall (HBM) [TCC_EA0_RDREQ_DRAM_CREDIT_STALL] +// - L2-EA read BW +// - HBM read fraction +// - L2 Memory BW Bound - Read Credit Pressure +// - L2 hit rate (expect low on baseline) +// - L2 to Memory Traffic Ratio (expect high on baseline) +// - L2 Cache Efficiency (expect low on baseline) +// +// Mechanism: +// Baseline: Streaming reads over a buffer 4x L2 size. Each thread reads +// from sequential cachelines with no reuse. Working set far exceeds L2, +// so nearly every access is an L2 miss -> EA read -> HBM. This saturates +// the L2-to-HBM read path and exhausts DRAM read credits. +// +// Optimized: Reads only a small tile that fits in L2. High hit rate, +// zero credit stalls. +// +// Build: hipcc -g l2_hbm_read_bw_stress.hip -o l2_hbm_read_bw_stress +// Run: ./l2_hbm_read_bw_stress (baseline) +// ./l2_hbm_read_bw_stress opt (optimized) + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +size_t getL2CacheSize() { + int val = 0; + CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); + if (val > 0) return (size_t)val; + // Fallback: 256MB (MI350 default per XCD) + return 256ULL * 1024 * 1024; +} + +// Baseline: Streaming read over buffer >> L2 size. +// Each thread reads multiple cachelines spread across the full buffer, +// ensuring no temporal reuse in L2. The read-only pattern maximizes +// EA read requests to HBM and starves DRAM read credits. +__global__ void hbm_read_stress_baseline(const float* __restrict__ data, + float* __restrict__ out, + long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + float acc = 0.0f; + // Stride through entire buffer. Each iteration touches a new cacheline. + // 16 floats = 64 bytes = 1 cacheline + for (long long i = tid * 16; i < N; i += stride * 16) { + acc += data[i]; + } + + // Prevent compiler from optimizing away reads + if (tid == 0) out[0] = acc; +} + +// Optimized: Read only a small region that fits comfortably in L2. +// Repeatedly access the same data -> high L2 hit rate, no EA traffic. +__global__ void hbm_read_stress_optimized(const float* __restrict__ data, + float* __restrict__ out, + long long N, long long tile_elems) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + float acc = 0.0f; + // Read within the tile repeatedly to get L2 hits + for (int rep = 0; rep < 64; rep++) { + for (long long i = tid * 16; i < tile_elems; i += stride * 16) { + acc += data[i]; + } + } + + if (tid == 0) out[0] = acc; +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + size_t l2_size = getL2CacheSize(); + std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; + + // Buffer = 4x L2 size to guarantee streaming misses + size_t buffer_bytes = l2_size * 4; + long long N = (long long)(buffer_bytes / sizeof(float)); + + // Tile for optimized = 1/4 of L2 (fits comfortably) + long long tile_elems = (long long)(l2_size / (4 * sizeof(float))); + + float* d_data; + float* d_out; + hipError_t alloc_err = hipMalloc(&d_data, buffer_bytes); + if (alloc_err != hipSuccess) { + // Fallback to 2x L2 + buffer_bytes = l2_size * 2; + N = (long long)(buffer_bytes / sizeof(float)); + CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); + } + CHECK_HIP(hipMalloc(&d_out, sizeof(float))); + + // Initialize with host data (avoid hipMemset which uses fill kernels) + float* h_data = new float[N]; + for (long long i = 0; i < N; i++) h_data[i] = (float)(i % 1000) * 0.001f; + CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + // Launch config: block=64 (Wave64), many blocks for maximum L2 pressure + dim3 block(64); + dim3 grid(4096); + + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + hbm_read_stress_optimized<<>>(d_data, d_out, N, tile_elems); + } else { + hbm_read_stress_baseline<<>>(d_data, d_out, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + CHECK_HIP(hipFree(d_out)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_write_bw_stress.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_write_bw_stress.hip new file mode 100644 index 0000000..f2ab3ef --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_write_bw_stress.hip @@ -0,0 +1,125 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// L2 HBM Write Bandwidth Stress +// +// Target metrics (tables 3007, 3008, 3009): +// - L2-EA write credit stall (HBM) [TCC_EA0_WRREQ_DRAM_CREDIT_STALL] +// - L2-EA write stall rate [TCC_EA0_WRREQ_STALL] +// - L2-EA too many writes stall [TCC_TOO_MANY_EA_WRREQS_STALL] +// - L2 write data FIFO full rate [TCC_SRC_FIFO_FULL] +// - L2-EA write and atomic BW +// - HBM write fraction +// - L2 Memory BW Bound - Write Credit Pressure +// - L2 writeback rate (expect high on baseline) +// +// Mechanism: +// Baseline: Streaming writes over a buffer 4x L2 size. Every thread writes +// to a unique cacheline (no reuse). This forces: +// 1. L2 miss on write -> allocate cacheline +// 2. Dirty cacheline evicted when replaced -> writeback to HBM +// 3. HBM write request rate at maximum -> write credits exhausted +// 4. Source data FIFO fills up (write data waiting to be sent to EA) +// 5. TOO_MANY_EA_WRREQS_STALL when max pending EA writes reached +// +// Optimized: Writes to a small buffer that fits in L2. Overwrites same +// cachelines repeatedly -> minimal EA write traffic. +// +// Build: hipcc -g l2_hbm_write_bw_stress.hip -o l2_hbm_write_bw_stress +// Run: ./l2_hbm_write_bw_stress (baseline) +// ./l2_hbm_write_bw_stress opt (optimized) + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +size_t getL2CacheSize() { + int val = 0; + CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); + if (val > 0) return (size_t)val; + return 256ULL * 1024 * 1024; +} + +// Baseline: Streaming write over buffer >> L2 size. +// Each thread writes to unique cachelines across the full buffer. +// Write-only pattern: every write allocates a new cacheline, and +// evicted dirty lines are written back to HBM, maximizing EA write traffic. +__global__ void hbm_write_stress_baseline(float* __restrict__ data, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + // Write 1 float per cacheline (16 floats = 64B cacheline). + // This ensures each write touches a distinct cacheline. + for (long long i = tid * 16; i < N; i += stride * 16) { + data[i] = (float)i; + } +} + +// Optimized: Write to a small region that fits in L2. +// Same cachelines are overwritten repeatedly -> dirty lines stay in L2, +// minimal writeback to HBM. +__global__ void hbm_write_stress_optimized(float* __restrict__ data, long long N, + long long tile_elems) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + for (int rep = 0; rep < 64; rep++) { + for (long long i = tid * 16; i < tile_elems; i += stride * 16) { + data[i] = (float)(i + rep); + } + } +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + size_t l2_size = getL2CacheSize(); + std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; + + size_t buffer_bytes = l2_size * 4; + long long N = (long long)(buffer_bytes / sizeof(float)); + long long tile_elems = (long long)(l2_size / (4 * sizeof(float))); + + float* d_data; + hipError_t alloc_err = hipMalloc(&d_data, buffer_bytes); + if (alloc_err != hipSuccess) { + buffer_bytes = l2_size * 2; + N = (long long)(buffer_bytes / sizeof(float)); + CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); + } + + // Initialize (avoid hipMemset) + float* h_data = new float[N]; + for (long long i = 0; i < N; i++) h_data[i] = 0.0f; + CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + dim3 block(64); + dim3 grid(4096); + + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + hbm_write_stress_optimized<<>>(d_data, N, tile_elems); + } else { + hbm_write_stress_baseline<<>>(d_data, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_io_stress.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_io_stress.hip new file mode 100644 index 0000000..e756da8 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_io_stress.hip @@ -0,0 +1,153 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// L2 IO (PCIe) Credit Stall Stress +// +// Target metrics (tables 3007, 3009, 3010): +// - L2-EA read credit stall (IO) [TCC_EA0_RDREQ_IO_CREDIT_STALL] +// - L2-EA write credit stall (IO) [TCC_EA0_WRREQ_IO_CREDIT_STALL] +// - Normalized L2-EA stalls (IO) (table 3010) +// +// Mechanism: +// Baseline: GPU kernel accesses host-pinned memory (hipHostMalloc). +// Traffic goes over PCIe (or xGMI to host) which is much slower than +// local HBM. The IO interface has limited credits, and sustained +// access from many CUs exhausts them quickly. +// +// mode=read: Streaming reads from host-pinned memory +// mode=write: Streaming writes to host-pinned memory +// (default): Read+write (interleaved) +// +// Optimized: Same kernel but on device-local memory (hipMalloc). +// All traffic stays on HBM, IO credits are not consumed. +// +// Build: hipcc -g l2_io_stress.hip -o l2_io_stress +// Run: ./l2_io_stress (baseline: host-pinned R+W) +// ./l2_io_stress read (baseline: host-pinned read) +// ./l2_io_stress write (baseline: host-pinned write) +// ./l2_io_stress opt (optimized: device-local) + +#include +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +// Streaming read from host memory -> IO read credit stalls +__global__ void io_read_kernel(const float* __restrict__ data, + float* __restrict__ out, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + float acc = 0.0f; + for (long long i = tid * 16; i < N; i += stride * 16) { + acc += data[i]; + } + if (tid == 0) out[0] = acc; +} + +// Streaming write to host memory -> IO write credit stalls +__global__ void io_write_kernel(float* __restrict__ data, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + for (long long i = tid * 16; i < N; i += stride * 16) { + data[i] = (float)i; + } +} + +// Interleaved R+W on host memory -> both IO credit types +__global__ void io_rw_kernel(float* __restrict__ data, + float* __restrict__ out, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + float acc = 0.0f; + for (long long i = tid * 16; i < N; i += stride * 16) { + float val = data[i]; + acc += val; + data[i] = val + 1.0f; + } + if (tid == 0) out[0] = acc; +} + +int main(int argc, char** argv) { + enum Mode { RW, READ, WRITE, OPT } mode = RW; + if (argc > 1) { + if (std::string(argv[1]) == "read") mode = READ; + else if (std::string(argv[1]) == "write") mode = WRITE; + else if (std::string(argv[1]) == "opt") mode = OPT; + } + + // 1GB host-pinned buffer (large enough to saturate IO path) + size_t buffer_bytes = 1ULL * 1024 * 1024 * 1024; + long long N = (long long)(buffer_bytes / sizeof(float)); + + float* data = nullptr; + float* d_out = nullptr; + + if (mode == OPT) { + // Device-local memory: no IO traffic + hipError_t err = hipMalloc(&data, buffer_bytes); + if (err != hipSuccess) { + buffer_bytes = 512ULL * 1024 * 1024; + N = (long long)(buffer_bytes / sizeof(float)); + CHECK_HIP(hipMalloc(&data, buffer_bytes)); + } + CHECK_HIP(hipMemset(data, 0, buffer_bytes)); + } else { + // Host-pinned memory: traffic goes over PCIe/IO + hipError_t err = hipHostMalloc(&data, buffer_bytes, hipHostMallocNonCoherent); + if (err != hipSuccess) { + buffer_bytes = 512ULL * 1024 * 1024; + N = (long long)(buffer_bytes / sizeof(float)); + CHECK_HIP(hipHostMalloc(&data, buffer_bytes, hipHostMallocNonCoherent)); + } + memset(data, 0, buffer_bytes); + } + + CHECK_HIP(hipMalloc(&d_out, sizeof(float))); + + dim3 block(64); + dim3 grid(4096); + + const char* mode_str = (mode == READ) ? "Host-pinned Read" : + (mode == WRITE) ? "Host-pinned Write" : + (mode == OPT) ? "Device-local (optimized)" : + "Host-pinned R+W"; + std::cout << "Mode: " << mode_str + << " | Buffer: " << (buffer_bytes / (1024 * 1024)) << " MB\n"; + + auto start = std::chrono::high_resolution_clock::now(); + if (mode == READ) { + io_read_kernel<<>>(data, d_out, N); + } else if (mode == WRITE) { + io_write_kernel<<>>(data, N); + } else { + // RW and OPT use same kernel (different memory backing) + io_rw_kernel<<>>(data, d_out, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << "Time: " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_out)); + if (mode == OPT) { + CHECK_HIP(hipFree(data)); + } else { + CHECK_HIP(hipHostFree(data)); + } + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_multigpu_fabric.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_multigpu_fabric.hip new file mode 100644 index 0000000..9a727fd --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_multigpu_fabric.hip @@ -0,0 +1,172 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// L2 Multi-GPU Infinity Fabric Stress +// +// ** REQUIRES 2 GPUs ** — see instructions below. +// +// Target metrics (tables 3007, 3009, 3010): +// - L2-EA read credit stall (IF) [TCC_EA0_RDREQ_GMI_CREDIT_STALL] +// - L2-EA write credit stall (IF) [TCC_EA0_WRREQ_GMI_CREDIT_STALL] +// - L2 Remote Access Pressure (IF) (table 3009) +// - Normalized L2-EA stalls (IF) (table 3010) +// +// Mechanism: +// GPU 0 accesses memory allocated on GPU 1 via P2P. The L2 on GPU 0 +// misses, and the EA request goes through the GMI (Infinity Fabric) +// interface to reach GPU 1's HBM. The IF has limited credits, and +// saturating this path exhausts GMI credits. +// +// mode=read: Streaming reads from remote GPU memory +// mode=write: Streaming writes to remote GPU memory +// mode=rw: Interleaved read+write (default) +// +// Build: hipcc -g l2_multigpu_fabric.hip -o l2_multigpu_fabric +// +// Run (profile GPU 0 while it accesses GPU 1's memory): +// # Read from remote: +// rocprof-compute profile -n fabric_read --experimental --membw-analysis --no-roof -- ./l2_multigpu_fabric read +// # Write to remote: +// rocprof-compute profile -n fabric_write --experimental --membw-analysis --no-roof -- ./l2_multigpu_fabric write +// # Read+Write: +// rocprof-compute profile -n fabric_rw --experimental --membw-analysis --no-roof -- ./l2_multigpu_fabric +// +// Analyze: +// rocprof-compute analyze -p + +#include +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +// Streaming read from remote memory -> GMI read credit stalls +__global__ void fabric_read_kernel(const float* __restrict__ remote_data, + float* __restrict__ local_out, + long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + float acc = 0.0f; + for (long long i = tid * 16; i < N; i += stride * 16) { + acc += remote_data[i]; + } + + if (tid == 0) local_out[0] = acc; +} + +// Streaming write to remote memory -> GMI write credit stalls +__global__ void fabric_write_kernel(float* __restrict__ remote_data, + long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + for (long long i = tid * 16; i < N; i += stride * 16) { + remote_data[i] = (float)i; + } +} + +// Interleaved read+write to remote -> both GMI credit types exhausted +__global__ void fabric_rw_kernel(float* __restrict__ remote_data, + float* __restrict__ local_out, + long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + float acc = 0.0f; + for (long long i = tid * 16; i < N; i += stride * 16) { + float val = remote_data[i]; + acc += val; + remote_data[i] = val + 1.0f; + } + + if (tid == 0) local_out[0] = acc; +} + +int main(int argc, char** argv) { + enum Mode { RW, READ, WRITE } mode = RW; + if (argc > 1) { + if (std::string(argv[1]) == "read") mode = READ; + else if (std::string(argv[1]) == "write") mode = WRITE; + } + + // Check GPU count + int ndevices = 0; + CHECK_HIP(hipGetDeviceCount(&ndevices)); + if (ndevices < 2) { + std::cerr << "ERROR: This workload requires >= 2 GPUs. Found " << ndevices << ".\n"; + std::cerr << "Skipping. Run on a multi-GPU system.\n"; + return 0; + } + + int local_gpu = 0; + int remote_gpu = 1; + + // Enable P2P + int can_access = 0; + CHECK_HIP(hipDeviceCanAccessPeer(&can_access, local_gpu, remote_gpu)); + if (!can_access) { + std::cerr << "ERROR: GPU " << local_gpu << " cannot access GPU " << remote_gpu << " via P2P.\n"; + return 1; + } + CHECK_HIP(hipSetDevice(local_gpu)); + CHECK_HIP(hipDeviceEnablePeerAccess(remote_gpu, 0)); + + // Allocate on remote GPU + size_t buffer_bytes = 2ULL * 1024 * 1024 * 1024; // 2GB + long long N = (long long)(buffer_bytes / sizeof(float)); + + CHECK_HIP(hipSetDevice(remote_gpu)); + float* d_remote; + hipError_t alloc_err = hipMalloc(&d_remote, buffer_bytes); + if (alloc_err != hipSuccess) { + buffer_bytes = 1ULL * 1024 * 1024 * 1024; // Fallback 1GB + N = (long long)(buffer_bytes / sizeof(float)); + CHECK_HIP(hipMalloc(&d_remote, buffer_bytes)); + } + // Initialize remote data + CHECK_HIP(hipMemset(d_remote, 0, buffer_bytes)); + + // Local output buffer on GPU 0 + CHECK_HIP(hipSetDevice(local_gpu)); + float* d_local_out; + CHECK_HIP(hipMalloc(&d_local_out, sizeof(float))); + + dim3 block(64); + dim3 grid(4096); + + const char* mode_str = (mode == READ) ? "Read" : + (mode == WRITE) ? "Write" : "Read+Write"; + std::cout << "Mode: " << mode_str + << " | Buffer: " << (buffer_bytes / (1024 * 1024)) << " MB" + << " | GPU " << local_gpu << " -> GPU " << remote_gpu << "\n"; + + auto start = std::chrono::high_resolution_clock::now(); + if (mode == READ) { + fabric_read_kernel<<>>(d_remote, d_local_out, N); + } else if (mode == WRITE) { + fabric_write_kernel<<>>(d_remote, N); + } else { + fabric_rw_kernel<<>>(d_remote, d_local_out, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << "Time: " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_local_out)); + CHECK_HIP(hipSetDevice(remote_gpu)); + CHECK_HIP(hipFree(d_remote)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_normalized_throughput.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_normalized_throughput.hip new file mode 100644 index 0000000..e9eb7b0 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_normalized_throughput.hip @@ -0,0 +1,136 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// L2 Normalized Throughput & Combined Stall Stress +// +// Target metrics (tables 3008, 3009, 3010): +// - All Normalized L2 Stall Metrics (% of GRBM_GUI_ACTIVE) +// - All Normalized L2 Throughput Metrics (requests/s) +// - L2 Memory BW Bound - Combined Credit Pressure +// - L2-EA read BW, L2-EA write and atomic BW +// +// Mechanism: +// Baseline: Mixed read+write streaming kernel over buffer 4x L2. +// Purely memory-bound (minimal compute), so GRBM_GUI_ACTIVE closely +// tracks memory stall time. Alternating reads and writes at cacheline +// granularity maximizes both EA read and write traffic simultaneously, +// which is the worst case for combined DRAM credit pressure. +// +// Optimized: Compute-heavy kernel with minimal memory traffic. +// Lots of FMA operations on register values -> high GRBM_GUI_ACTIVE +// but near-zero L2 traffic. Normalized stalls will be very low +// because the denominator (kernel time) is large while stall +// numerators are near zero. +// +// Build: hipcc -g l2_normalized_throughput.hip -o l2_normalized_throughput +// Run: ./l2_normalized_throughput (baseline: memory-bound) +// ./l2_normalized_throughput opt (optimized: compute-bound) + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +size_t getL2CacheSize() { + int val = 0; + CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); + if (val > 0) return (size_t)val; + return 256ULL * 1024 * 1024; +} + +// Baseline: Mixed read+write streaming, purely memory-bound. +// Alternates reading one cacheline and writing the next, across the +// entire buffer. No compute to hide latency -> memory-bound. +// This maximizes both TCC_READ and TCC_WRITE simultaneously, +// creating combined DRAM credit pressure on both read and write paths. +__global__ void normalized_stress_baseline(float* __restrict__ data, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + long long stride = (long long)blockDim.x * gridDim.x; + + float acc = 0.0f; + // Each iteration: read from cacheline i, write to cacheline i+1 + // Stride by 32 floats (2 cachelines) per step + for (long long i = tid * 32; i < N - 16; i += stride * 32) { + // Read cacheline + acc += data[i]; + // Write next cacheline + data[i + 16] = acc; + } + + // Prevent optimization + if (acc == -999.0f) data[0] = acc; +} + +// Optimized: Compute-heavy, minimal memory. +// Reads a single float, does hundreds of FMAs in registers, writes back. +// GRBM_GUI_ACTIVE is high (kernel runs long), but L2 traffic is negligible. +// Result: normalized stall metrics are near zero. +__global__ void normalized_stress_optimized(float* __restrict__ data, long long N) { + long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; + + // Single memory read + float val = data[tid % N]; + + // Pure compute: 1024 FMA iterations + float a = val, b = 1.0001f, c = 0.9999f; + for (int i = 0; i < 1024; i++) { + a = a * b + c; + b = b * c + a; + c = c * a + b; + } + + // Single memory write + if (tid < N) data[tid] = a + b + c; +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + size_t l2_size = getL2CacheSize(); + std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; + + size_t buffer_bytes = l2_size * 4; + long long N = (long long)(buffer_bytes / sizeof(float)); + + float* d_data; + hipError_t alloc_err = hipMalloc(&d_data, buffer_bytes); + if (alloc_err != hipSuccess) { + buffer_bytes = l2_size * 2; + N = (long long)(buffer_bytes / sizeof(float)); + CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); + } + + // Initialize + float* h_data = new float[N]; + for (long long i = 0; i < N; i++) h_data[i] = (float)(i % 1000) * 0.001f; + CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + dim3 block(64); + dim3 grid(4096); + + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + normalized_stress_optimized<<>>(d_data, N); + } else { + normalized_stress_baseline<<>>(d_data, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized (compute-bound)" : "Baseline (memory-bound)") << ": " + << std::chrono::duration_cast(end - start).count() + << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ta_tcp_stall.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ta_tcp_stall.hip new file mode 100644 index 0000000..943cffb --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/ta_tcp_stall.hip @@ -0,0 +1,87 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +// Baseline: Random access, TA waits for TCP +__global__ void ta_tcp_stall_baseline(float* data, int N) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= N) return; + + float sum = 0; + for (int i = 0; i < 32; i++) { + long long rand_idx = ((long long)idx * 7919LL + (long long)i * 6547LL) % N; + sum += data[rand_idx]; + } + data[idx] = sum; +} + +// Optimized: Sequential access, TCP serves TA quickly +__global__ void ta_tcp_stall_optimized(float* data, int N) { + // Each thread handles 32 elements + int base = (blockIdx.x * blockDim.x + threadIdx.x) * 32; + if (base + 31 >= N) return; + + float sum = 0; + for (int i = 0; i < 32; i++) { + sum += data[base + i]; + } + data[base] = sum; +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + const int N = 128 * 1024 * 1024; + const size_t bytes = N * sizeof(float); + + float* d_data; + CHECK_HIP(hipMalloc(&d_data, bytes)); + + float* h_data = new float[N]; + for (int i = 0; i < N; i++) h_data[i] = i * 0.001f; + CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + dim3 block(256); + + if (use_optimized) { + int num_blocks = (N / 32 + block.x - 1) / block.x; + dim3 grid(num_blocks); + hipLaunchKernelGGL(ta_tcp_stall_optimized, grid, block, 0, 0, d_data, N); + } else { + dim3 grid((N + block.x - 1) / block.x); + hipLaunchKernelGGL(ta_tcp_stall_baseline, grid, block, 0, 0, d_data, N); + } + CHECK_HIP(hipDeviceSynchronize()); + + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + int num_blocks = (N / 32 + block.x - 1) / block.x; + dim3 grid(num_blocks); + hipLaunchKernelGGL(ta_tcp_stall_optimized, grid, block, 0, 0, d_data, N); + } else { + dim3 grid((N + block.x - 1) / block.x); + hipLaunchKernelGGL(ta_tcp_stall_baseline, grid, block, 0, 0, d_data, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + return 0; +} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/utcl1_stall.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/utcl1_stall.hip new file mode 100644 index 0000000..5f0f9c2 --- /dev/null +++ b/skills/rocprof-compute-sample/membw_analysis_test_suite/utcl1_stall.hip @@ -0,0 +1,87 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/* + * UTCL1 Stall Workload - Targets TCP_UTCL1_STALL_INFLIGHT_MAX / tcp_busy >= 10% + * + */ + +#include +#include +#include + +#define CHECK_HIP(call) \ + do { \ + hipError_t err = call; \ + if (err != hipSuccess) { \ + fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ + fflush(stderr); \ + exit(1); \ + } \ + } while(0) + +constexpr int PAGE_FLOATS = 4096 / sizeof(float); // 1024 floats per 4KB page + +// Baseline: Rapid page hopping - exceed UTCL1 entries, trigger INFLIGHT stall +__global__ void utcl1_stall_baseline(float* data, int N) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= N) return; + + int lane = threadIdx.x & 63; // Wave64 + + float acc = 0.0f; + for (int i = 0; i < 512; i++) { + unsigned long long page_offset = (unsigned long long)lane * PAGE_FLOATS + + (unsigned long long)i * 64 * PAGE_FLOATS; + int access_idx = (int)(((unsigned long long)idx + page_offset) % (unsigned long long)N); + acc += data[access_idx]; + } + data[idx] = acc; +} + +// Optimized: All accesses within same page +__global__ void utcl1_stall_optimized(float* data, int N) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= N) return; + + int page_base = (idx / PAGE_FLOATS) * PAGE_FLOATS; + + float sum = 0.0f; + for (int i = 0; i < 512; i++) { + sum += data[page_base + (i % PAGE_FLOATS)]; + } + data[idx] = sum; +} + +int main(int argc, char** argv) { + bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); + + const int N = 256 * 1024 * 1024; // 1GB - 256K pages + const size_t bytes = N * sizeof(float); + + float* d_data; + CHECK_HIP(hipMalloc(&d_data, bytes)); + + float* h_data = new float[N]; + for (int i = 0; i < N; i++) h_data[i] = i * 0.001f; + CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); + delete[] h_data; + + dim3 block(64); + dim3 grid((N + block.x - 1) / block.x); + + auto start = std::chrono::high_resolution_clock::now(); + if (use_optimized) { + hipLaunchKernelGGL(utcl1_stall_optimized, grid, block, 0, 0, d_data, N); + } else { + hipLaunchKernelGGL(utcl1_stall_baseline, grid, block, 0, 0, d_data, N); + } + CHECK_HIP(hipDeviceSynchronize()); + auto end = std::chrono::high_resolution_clock::now(); + + std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " + << std::chrono::duration_cast(end - start).count() << " ms\n"; + + CHECK_HIP(hipFree(d_data)); + return 0; +} diff --git a/skills/rocprof-compute-sample/memcopy.cpp b/skills/rocprof-compute-sample/memcopy.cpp new file mode 100644 index 0000000..cb68bb5 --- /dev/null +++ b/skills/rocprof-compute-sample/memcopy.cpp @@ -0,0 +1,128 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#include +#include +#include + +// Simple memory copy kernel - one thread per element +__global__ void memoryCopyKernel(const double* __restrict__ X, + double* __restrict__ Y, + size_t N) { + size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + + if (idx < N) { + Y[idx] = X[idx]; + } +} + +int main() { + const size_t N = 268435456; // Number of elements + const size_t bytes = N * sizeof(double); + + // Print array size in GiB + double sizeGiB = bytes / (1024.0 * 1024.0 * 1024.0); + std::cout << "Array size: " << std::fixed << std::setprecision(2) + << sizeGiB << " GiB (" << bytes << " bytes)" << std::endl; + std::cout << "Number of elements: " << N << std::endl; + std::cout << std::string(60, '-') << std::endl; + + // Allocate device memory + double *d_X, *d_Y; + hipMalloc(&d_X, bytes); + hipMalloc(&d_Y, bytes); + + // Allocate and initialize host memory + double *h_X = new double[N]; + for (size_t i = 0; i < N; i++) { + h_X[i] = static_cast(i); + } + + // Copy data to device + hipMemcpy(d_X, h_X, bytes, hipMemcpyHostToDevice); + + // Launch configuration + int blockSize = 256; + int gridSize = (N + blockSize - 1) / blockSize; + + std::cout << "Launch configuration:" << std::endl; + std::cout << " Block size: " << blockSize << std::endl; + std::cout << " Grid size: " << gridSize << std::endl; + std::cout << std::string(60, '-') << std::endl; + + // Warmup run + memoryCopyKernel<<>>(d_X, d_Y, N); + hipDeviceSynchronize(); + + // Timed runs + const int numRuns = 1000; + hipEvent_t start, stop; + hipEventCreate(&start); + hipEventCreate(&stop); + + float totalTime = 0.0f; + + for (int run = 0; run < numRuns; run++) { + hipEventRecord(start); + + memoryCopyKernel<<>>(d_X, d_Y, N); + + hipEventRecord(stop); + hipEventSynchronize(stop); + + float milliseconds = 0; + hipEventElapsedTime(&milliseconds, start, stop); + totalTime += milliseconds; + } + + float avgTime = totalTime / numRuns; + + // Calculate bandwidth + // Memory copy reads N elements and writes N elements = 2*N*sizeof(double) bytes + double totalBytesTransferred = 2.0 * N * sizeof(double); + double bandwidthGBps = (totalBytesTransferred / (avgTime / 1000.0)) / (1024.0 * 1024.0 * 1024.0); + + std::cout << "Performance Results (averaged over " << numRuns << " runs):" << std::endl; + std::cout << " Kernel execution time: " << std::fixed << std::setprecision(3) + << avgTime << " ms" << std::endl; + std::cout << " Data transferred: " << std::setprecision(2) + << (totalBytesTransferred / (1024.0 * 1024.0 * 1024.0)) << " GiB" << std::endl; + std::cout << " Achieved bandwidth: " << std::setprecision(2) + << bandwidthGBps << " GiB/s" << std::endl; + std::cout << std::string(60, '-') << std::endl; + + // Verify correctness + double *h_Y = new double[N]; + hipMemcpy(h_Y, d_Y, bytes, hipMemcpyDeviceToHost); + + bool correct = true; + for (size_t i = 0; i < N; i++) { + if (h_Y[i] != h_X[i]) { + correct = false; + std::cout << "Mismatch at index " << i << ": " + << h_Y[i] << " != " << h_X[i] << std::endl; + break; + } + } + + if (correct) { + std::cout << "✓ Verification PASSED - all elements copied correctly" << std::endl; + } else { + std::cout << "✗ Verification FAILED" << std::endl; + } + + std::cout << "\nFirst 5 elements:" << std::endl; + for (int i = 0; i < 5; i++) { + std::cout << " Y[" << i << "] = " << h_Y[i] << std::endl; + } + + // Cleanup + hipEventDestroy(start); + hipEventDestroy(stop); + delete[] h_X; + delete[] h_Y; + hipFree(d_X); + hipFree(d_Y); + + return 0; +} diff --git a/skills/rocprof-compute-sample/occupancy.hip b/skills/rocprof-compute-sample/occupancy.hip new file mode 100644 index 0000000..1c56320 --- /dev/null +++ b/skills/rocprof-compute-sample/occupancy.hip @@ -0,0 +1,85 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/* +An example to explore achieved occupancy, and various occupancy limiters. +Written by Nicholas Curtis [AMD]. +*/ + +#include "common.h" + +__global__ void empty(int N, double* ptr) { + +} + +constexpr int bound = 16; +__launch_bounds__(256) +__global__ void vgprbound(int N, double* ptr) { + double intermediates[bound]; + for (int i = 0 ; i < bound; ++i) intermediates[i] = N * threadIdx.x; + double x = ptr[threadIdx.x]; + for (int i = 0; i < 100; ++i) { + x += sin(pow(__shfl(x, i % warpSize) * intermediates[(i - 1) % bound], intermediates[i % bound])); + intermediates[i % bound] = x; + } + if (x == N) ptr[threadIdx.x] = x; +} + +constexpr size_t fully_allocate_lds = 64ul * 1024ul / sizeof(double); +__launch_bounds__(256) +__global__ void ldsbound(int N, double* ptr) { + __shared__ double intermediates[fully_allocate_lds]; + for (int i = threadIdx.x ; i < fully_allocate_lds; i += blockDim.x) intermediates[i] = N * threadIdx.x; + __syncthreads(); + double x = ptr[threadIdx.x]; + for (int i = threadIdx.x; i < fully_allocate_lds; i += blockDim.x) { + x += sin(pow(__shfl(x, i % warpSize) * intermediates[(i - 1) % fully_allocate_lds], intermediates[i % fully_allocate_lds])); + __syncthreads(); + intermediates[i % fully_allocate_lds] = x; + } + if (x == N) ptr[threadIdx.x] = x; +} + +constexpr int sgprlim = 1; +__launch_bounds__(1024, 8) +__global__ void sgprbound(int N, double* ptr) { + double intermediates[sgprlim]; + for (int i = 0 ; i < sgprlim; ++i) intermediates[i] = i; + double x = ptr[0]; + #pragma unroll 1 + for (int i = 0; i < 100; ++i) { + x += sin(pow(intermediates[(i - 1) % sgprlim], intermediates[i % sgprlim])); + intermediates[i % sgprlim] = x; + } + if (x == N) ptr[0] = x; +} + +int main() { + double* ptr; + hipCheck(hipMalloc(&ptr, 1024 * sizeof(double))); + vgprbound<<<1024 * 1024, 256>>>(0, ptr); + hipCheck(hipGetLastError()); + hipCheck(hipDeviceSynchronize()); + vgprbound<<<1024 * 1024, 256>>>(0, ptr); + hipCheck(hipGetLastError()); + hipCheck(hipDeviceSynchronize()); + ldsbound<<<1024 * 1024, 256>>>(0, ptr); + hipCheck(hipGetLastError()); + hipCheck(hipDeviceSynchronize()); + ldsbound<<<1024 * 1024, 256>>>(0, ptr); + hipCheck(hipGetLastError()); + hipCheck(hipDeviceSynchronize()); + sgprbound<<<1024 * 1024, 256>>>(0, ptr); + hipCheck(hipGetLastError()); + hipCheck(hipDeviceSynchronize()); + sgprbound<<<1024 * 1024, 256>>>(0, ptr); + hipCheck(hipGetLastError()); + hipCheck(hipDeviceSynchronize()); + empty<<<1024 * 1024, 256>>>(0, ptr); + hipCheck(hipGetLastError()); + hipCheck(hipDeviceSynchronize()); + empty<<<1024 * 1024, 256>>>(0, ptr); + hipCheck(hipGetLastError()); + hipCheck(hipDeviceSynchronize()); + hipCheck(hipFree(ptr)); +} diff --git a/skills/rocprof-compute-sample/rocflop.cpp b/skills/rocprof-compute-sample/rocflop.cpp new file mode 100644 index 0000000..4e638a1 --- /dev/null +++ b/skills/rocprof-compute-sample/rocflop.cpp @@ -0,0 +1,742 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// Copied from https://github.com/benrichard-amd/rocflop/tree/82f197e12314bab694fc70451a2b495b4f51bf90 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using float16 = _Float16; + +// Vector types. Useful for packed math (where supported) and MFMA inputs. +template +using vecT = T __attribute__((ext_vector_type(Rank))); + +template using vec4 = vecT; +template using vec8 = vecT; + + +// Kernels + + +template __global__ void fma_throughput(vec4* buffer, int count) +{ + const T k = 1.0; + + const int grid_size = gridDim.x * blockDim.x; + const int tid = blockDim.x * blockIdx.x + threadIdx.x; + + vec4* ptr = buffer; + + vec4 value0 = ptr[0 * grid_size + tid]; + vec4 value1 = ptr[1 * grid_size + tid]; + vec4 value2 = ptr[2 * grid_size + tid]; + vec4 value3 = ptr[3 * grid_size + tid]; + + for(int j = 0; j < count; j++) { + for(int j = 0; j < 64; j++) { + + // 16 FMA ops + value0 = value0 * value0 + k; + value1 = value1 * value1 + k; + value2 = value2 * value2 + k; + value3 = value3 * value3 + k; + } + } + + ptr[tid] = value0 + value1 + value2 + value3; +} + +// MFMA requires CDNA's mai-insts target feature; kernel bodies are empty on +// targets without the builtin and are never launched (run_tests gates on a +// runtime has_mfma check). +__global__ void matmul_fp16_throughput(vec4* inputs, vec4* outputs, int count) +{ +#if __has_builtin(__builtin_amdgcn_mfma_f32_16x16x16f16) + int grid_size = gridDim.x * blockDim.x; + int tid = blockDim.x * blockIdx.x + threadIdx.x; + + vec4* ptr = inputs; + + vec4 value0 = ptr[0 * grid_size + tid]; + vec4 value1 = ptr[1 * grid_size + tid]; + vec4 value2 = ptr[2 * grid_size + tid]; + vec4 value3 = ptr[3 * grid_size + tid]; + + vec4 accum0; + vec4 accum1; + vec4 accum2; + vec4 accum3; + for(int i = 0; i < count; i++) { + for(int j = 0; j < 64; j++) { + // 4 MFMA ops + accum0 = __builtin_amdgcn_mfma_f32_16x16x16f16(value0, value0, accum0, 0, 0, 0); + accum1 = __builtin_amdgcn_mfma_f32_16x16x16f16(value1, value1, accum1, 0, 0, 0); + accum2 = __builtin_amdgcn_mfma_f32_16x16x16f16(value2, value2, accum2, 0, 0, 0); + accum3 = __builtin_amdgcn_mfma_f32_16x16x16f16(value3, value3, accum3, 0, 0, 0); + } + } + + outputs[tid] = accum0 + accum1 + accum2 + accum3; +#endif +} + +__global__ void matmul_fp32_throughput(float* inputs, vec4* outputs, int count) +{ +#if __has_builtin(__builtin_amdgcn_mfma_f32_16x16x4f32) + int grid_size = gridDim.x * blockDim.x; + int tid = blockDim.x * blockIdx.x + threadIdx.x; + + float* ptr = inputs; + + float value0 = ptr[0 * grid_size + tid]; + float value1 = ptr[1 * grid_size + tid]; + float value2 = ptr[2 * grid_size + tid]; + float value3 = ptr[2 * grid_size + tid]; + + vec4 accum0; + vec4 accum1; + vec4 accum2; + vec4 accum3; + for(int i = 0; i < count; i++) { + for(int j = 0; j < 64; j++) { + // 4 MFMA ops + accum0 = __builtin_amdgcn_mfma_f32_16x16x4f32(value0, value0, accum0, 0, 0, 0); + accum1 = __builtin_amdgcn_mfma_f32_16x16x4f32(value1, value1, accum1, 0, 0, 0); + accum2 = __builtin_amdgcn_mfma_f32_16x16x4f32(value2, value2, accum2, 0, 0, 0); + accum3 = __builtin_amdgcn_mfma_f32_16x16x4f32(value3, value3, accum3, 0, 0, 0); + } + } + + outputs[tid] = accum0 + accum1 + accum2 + accum3; +#endif +} + +// SMFMAC requires CDNA3+'s gfx940-insts target feature; kernel body is empty +// on targets without the builtin and is never launched (run_tests gates on a +// runtime arch.major/minor check). +__global__ void sparse_matmul_fp16_throughput(vec4* input0, vec8* input1, vec4* outputs, int count) +{ +#if __has_builtin(__builtin_amdgcn_smfmac_f32_16x16x32_f16) + int grid_size = gridDim.x * blockDim.x; + int tid = blockDim.x * blockIdx.x + threadIdx.x; + + vec4* x_ptr = input0; + vec8* y_ptr = input1; + + vec4 x0 = x_ptr[0 * grid_size + tid]; + vec4 x1 = x_ptr[1 * grid_size + tid]; + vec4 x2 = x_ptr[2 * grid_size + tid]; + vec4 x3 = x_ptr[3 * grid_size + tid]; + + vec8 y0 = y_ptr[0 * grid_size + tid]; + vec8 y1 = y_ptr[1 * grid_size + tid]; + vec8 y2 = y_ptr[2 * grid_size + tid]; + vec8 y3 = y_ptr[3 * grid_size + tid]; + + vec4 accum0; + vec4 accum1; + vec4 accum2; + vec4 accum3; + + for(int i = 0; i < count; i++) { + for(int j = 0; j < 64; j++) { + // 4 SMFMAC ops + accum0 = __builtin_amdgcn_smfmac_f32_16x16x32_f16(x0, y0, accum0, 0, 0, 0); + accum1 = __builtin_amdgcn_smfmac_f32_16x16x32_f16(x1, y1, accum1, 0, 0, 0); + accum2 = __builtin_amdgcn_smfmac_f32_16x16x32_f16(x2, y2, accum2, 0, 0, 0); + accum3 = __builtin_amdgcn_smfmac_f32_16x16x32_f16(x3, y3, accum3, 0, 0, 0); + } + } + + outputs[tid] = accum0 + accum1 + accum2 + accum3; +#endif +} + +int g_current_device = -1; + +void HIP_CALL(hipError_t err) +{ + if(err != hipSuccess) { + if(g_current_device >= 0) { + std::cout << "[GPU " << g_current_device << "] "; + } + std::cout << "HIP Error: " << (int)err << " " << hipGetErrorString(err) << std::endl; + exit(1); + } +} + +struct GCNArch { + int major; + int minor; + int rev; +}; + +GCNArch get_gcn_arch(int device) +{ + hipDeviceProp_t props; + + HIP_CALL(hipGetDeviceProperties(&props, device)); + + // Example: gfx908:sramecc+:xnack- + std::string arch_full(props.gcnArchName); + + // Extract number e.g. "908" + std::string gfx_str = arch_full.substr(3, arch_full.find_first_of(':')); + + int gfx_num = std::stoi(gfx_str, nullptr, 16); + + GCNArch arch; + arch.major = (gfx_num & 0xff00) >> 8; + arch.minor = (gfx_num & 0x00f0) >> 4; + arch.rev = (gfx_num & 0x000f); + + return arch; +} + +enum : uint32_t { + VALU_FP32 = 1 << 0, + VALU_FP16 = 1 << 1, + VALU_FP64 = 1 << 2, + MATRIX_FP16 = 1 << 3, + MATRIX_FP32 = 1 << 4, + SMATRIX_FP16 = 1 << 5, + VALU_INT32 = 1 << 6, + + ALL = (uint32_t)-1 +}; + +// Timer for measuring kernel duration +class HIPTimer { + +private: + hipEvent_t m_start; + hipEvent_t m_stop; + +public: + HIPTimer() + { + HIP_CALL(hipEventCreate(&m_start)); + HIP_CALL(hipEventCreate(&m_stop)); + } + + void start() + { + HIP_CALL(hipEventRecord(m_start)); + } + + void stop() + { + HIP_CALL(hipEventRecord(m_stop)); + } + + double elapsed() + { + float ms; + HIP_CALL(hipEventElapsedTime(&ms, m_start, m_stop)); + + return (double)ms / 1000.0; + } +}; + +// Host code + +template double fma_throughput_test(int device, int count, int runs = 1) +{ + vec4* buffer = nullptr; + + hipDeviceProp_t props; + HIP_CALL(hipGetDeviceProperties(&props, device)); + + int blocks = props.multiProcessorCount * 512; + int threads_per_block = 64; + int total_threads = blocks * threads_per_block; + + HIP_CALL(hipMalloc(&buffer, sizeof(vec4) * total_threads * 4)); + + HIPTimer t; + t.start(); + for(int i = 0; i < runs; i++) { + fma_throughput<<>>(buffer, count); + } + t.stop(); + HIP_CALL(hipDeviceSynchronize()); + + double elapsed = t.elapsed(); + double ops = (double)total_threads * count * 64 * 16 * runs; + double flops = (double)ops * 2.0 / elapsed; + + HIP_CALL(hipFree(buffer)); + + return flops; +} + +template double matmul_throughput_test(int device, int count, int runs = 1) +{ + const int wave_size = 64; + int k; + int m; + int n; + + if(std::is_same::value) { + m = 16; + n = 16; + k = 16; + } else if(std::is_same::value) { + m = 16; + n = 16; + k = 4; + } else { + assert(false); + } + + int ops_per_matmul = k * m * n * 2; + + void* buffer = nullptr; + void* accum = nullptr; + + hipDeviceProp_t props; + HIP_CALL(hipGetDeviceProperties(&props, device)); + + int blocks = props.multiProcessorCount * 512; + int threads_per_block = wave_size; + int total_threads = blocks * threads_per_block; + + HIP_CALL(hipMalloc(&buffer, 4 * sizeof(matT) * m * k * total_threads)); + HIP_CALL(hipMalloc(&accum, sizeof(accumT) * m * n * total_threads)); + + HIPTimer t; + t.start(); + for(int i = 0; i < runs; i++) { + if(std::is_same::value && std::is_same::value) { + matmul_fp16_throughput<<>>((vec4*)buffer, (vec4*)accum, count); + } else if(std::is_same::value && std::is_same::value) { + matmul_fp32_throughput<<>>((float*)buffer, (vec4*)accum, count); + } + } + t.stop(); + HIP_CALL(hipDeviceSynchronize()); + + double elapsed = t.elapsed(); + double ops = (double)blocks * count * 64 * 4 * runs; + double flops = (double)ops * ops_per_matmul / elapsed; + + HIP_CALL(hipFree(buffer)); + HIP_CALL(hipFree(accum)); + + return flops; +} + +template double sparse_matmul_throughput_test(int device, int count, int runs = 1) +{ + const int wave_size = 64; + int k; + int m; + int n; + + if(std::is_same::value) { + m = 16; + n = 16; + k = 32; + } else { + assert(false); + } + + int ops_per_matmul = k * m * n * 2; + + void* buffer1 = nullptr; + void* buffer2 = nullptr; + void* accum = nullptr; + + hipDeviceProp_t props; + HIP_CALL(hipGetDeviceProperties(&props, device)); + + int blocks = props.multiProcessorCount * 512; + int threads_per_block = wave_size; + int total_threads = blocks * threads_per_block; + + HIP_CALL(hipMalloc(&buffer1, 4 * sizeof(matT) * m * k * total_threads)); + HIP_CALL(hipMalloc(&buffer2, 8 * sizeof(matT) * n * k * total_threads)); + HIP_CALL(hipMalloc(&accum, sizeof(accumT) * m * n * total_threads)); + + HIPTimer t; + t.start(); + for(int i = 0; i < runs; i++) { + if(std::is_same::value && std::is_same::value) { + sparse_matmul_fp16_throughput<<>>((vec4*)buffer1, + (vec8*)buffer2, (vec4*)accum, count); + } + } + t.stop(); + HIP_CALL(hipDeviceSynchronize()); + + double elapsed = t.elapsed(); + double ops = (double)blocks * count * 64 * 4 * runs; + double flops = (double)ops * ops_per_matmul / elapsed; + + HIP_CALL(hipFree(buffer1)); + HIP_CALL(hipFree(buffer2)); + HIP_CALL(hipFree(accum)); + + return flops; +} + +struct Result { + int device = -1; + double valu_fp16 = 0; + double valu_fp32 = 0; + double valu_fp64 = 0; + double valu_int32 = 0; + double mfma_fp16 = 0; + double mfma_fp32 = 0; + double smfmac_fp16 = 0; + + // Used for sorting + bool operator<(const Result& other) { + return device < other.device; + } +}; + +void print_result(const Result& res, uint32_t mask) +{ + if(mask & VALU_FP16) { + printf("VALU FP16: %8.2f TFLOPS\n", res.valu_fp16 / 1e12); + } + if(mask & VALU_FP32) { + printf("VALU FP32: %8.2f TFLOPS\n", res.valu_fp32 / 1e12); + } + if(mask & VALU_FP64) { + printf("VALU FP64: %8.2f TFLOPS\n", res.valu_fp64 / 1e12); + } + if(mask & VALU_INT32) { + printf("VALU INT32: %8.2f TIOPS\n", res.valu_int32 / 1e12); + } + if(mask & MATRIX_FP16) { + printf("MFMA FP16: %8.2f TFLOPS\n", res.mfma_fp16 / 1e12); + } + if(mask & MATRIX_FP32) { + printf("MFMA FP32: %8.2f TFLOPS\n", res.mfma_fp32 / 1e12); + } + if(mask & SMATRIX_FP16) { + printf("SMFMAC FP16: %8.2f TFLOPS\n", res.smfmac_fp16 / 1e12); + } +} + +Result run_tests(int device, int runs, uint32_t mask) +{ + g_current_device = device; + int device_count; + + HIP_CALL(hipGetDeviceCount(&device_count)); + + if(device >= device_count) { + std::cout << "[GPU " << device << "] Device does not exist. Skipping..." << std::endl; + exit(1); + } + + HIP_CALL(hipSetDevice(device)); + GCNArch arch = get_gcn_arch(device); + + Result res = {.device = device}; + + if(mask & VALU_FP16) { + res.valu_fp16 = fma_throughput_test(device, 4096, runs); + } + + if(mask & VALU_FP32) { + res.valu_fp32 = fma_throughput_test(device, 4096, runs); + } + + if(mask & VALU_FP64) { + res.valu_fp64 = fma_throughput_test(device, 4096, runs); + } + + if(mask & VALU_INT32) { + res.valu_int32 = fma_throughput_test(device, 4096, runs); + } + + // MFMA available on gfx908+ (excludes gfx906 with rev=6); kernel bodies are + // empty on targets without the builtin, so only launch when has_mfma. + bool has_mfma = arch.major == 0x9 && (arch.minor >= 0x4 || (arch.minor == 0 && arch.rev >= 8)); + + if(mask & MATRIX_FP16) { + if(has_mfma) { + res.mfma_fp16 = matmul_throughput_test(device, 4096, runs); + } + } + + if(mask & MATRIX_FP32) { + if(has_mfma) { + res.mfma_fp32 = matmul_throughput_test(device, 4096, runs); + } + } + + // SMFMAC only available on gfx940 (MI300) and later. + if(mask & SMATRIX_FP16) { + if(arch.major == 0x9 && arch.minor >= 0x4) { + res.smfmac_fp16 = sparse_matmul_throughput_test(device, 4096, runs); + } + } + + return res; +} + +// Use fork() followed by exec() to run child process. For some reason +// rocprof does not pick up the child processes when only fork() is +// used. +pid_t fork_process(int device, int runs, uint32_t mask, int fd) +{ + pid_t pid = fork(); + + if(pid != 0) { + return pid; + } + + std::string str_device = std::to_string(device); + std::string str_runs = std::to_string(runs); + std::string str_mask = std::to_string(mask); + std::string str_fd = std::to_string(fd); + + char* const args[] = { + (char*)"CHILD", + (char*)str_device.c_str(), + (char*)str_runs.c_str(), + (char*)str_mask.c_str(), + (char*)str_fd.c_str(), + NULL + }; + + execv("/proc/self/exe", args); + std::cout << "[GPU " << device << "] execv() failed: " << std::strerror(errno) << std::endl; + exit(1); +} + +std::vector read_records_from_pipe(int fd[2], size_t expected_count) +{ + int flags = fcntl(fd[0], F_GETFL, 0); + fcntl(fd[0], F_SETFL, flags | O_NONBLOCK); + + std::vector results(expected_count); + ssize_t bytes_read = read(fd[0], results.data(), results.size() * sizeof(Result)); + + if(bytes_read < 0) { + std::cout << "Error reading results from child process(es): " + << std::strerror(errno) << std::endl; + return {}; + } + + if(bytes_read == 0) { + std::cout << "No results received from child process(es)." << std::endl; + return {}; + } + + if(bytes_read % sizeof(Result) != 0) { + std::cout << "Warning: Incomplete result data received from child process(es); " + << "some data may be ignored." << std::endl; + } + + int count = static_cast(bytes_read / sizeof(Result)); + results.resize(count); + return results; +} + +void run(std::vector& devices, int runs, uint32_t mask) +{ + std::vector pids; + + // We will receive results from the child processes using a pipe + int fd[2]; + + if(pipe(fd)) { + std::cout << std::strerror(errno) << std::endl; + exit(1); + } + + // Start a new process for each GPU + for(auto d : devices) { + pid_t pid = fork_process(d, runs, mask, fd[1]); + + pids.push_back(pid); + } + + // Wait for all processes to finish + int failed = 0; + for(size_t i = 0; i < pids.size(); i++) { + int status; + waitpid(pids[i], &status, 0); + if(WIFEXITED(status) && WEXITSTATUS(status) != 0) { + std::cout << "Child process for device " << devices[i] + << " exited with error (code " << WEXITSTATUS(status) << ")." << std::endl; + failed++; + } else if(WIFSIGNALED(status)) { + std::cout << "Child process for device " << devices[i] + << " killed by signal " << WTERMSIG(status) << "." << std::endl; + failed++; + } + } + + if(failed == (int)pids.size()) { + std::cout << "All " << pids.size() << " child process(es) failed. No results to report." << std::endl; + exit(1); + } + + std::vector results = read_records_from_pipe(fd, pids.size()); + + // Sort results by GPU id + std::sort(results.begin(), results.end()); + + // Print results + for(auto r : results) { + std::cout << std::endl << "GPU " << r.device << std::endl; + print_result(r, mask); + } + + Result total; + for(auto r : results) { + total.valu_fp16 += r.valu_fp16; + total.valu_fp32 += r.valu_fp32; + total.valu_fp64 += r.valu_fp64; + total.valu_int32 += r.valu_int32; + total.mfma_fp16 += r.mfma_fp16; + total.mfma_fp32 += r.mfma_fp32; + total.smfmac_fp16 += r.smfmac_fp16; + } + std::cout << std::endl << "System total" << std::endl; + print_result(total, mask); + + if(failed > 0) { + std::cout << std::endl << failed << " of " << pids.size() + << " child process(es) failed." << std::endl; + exit(1); + } +} + + +void usage() +{ + std::cout << "--device ID Use device with the given numerical ID" << std::endl; + std::cout << "--devices IDS | ALL Comma-separated list of device Ids (e.g., 1,2,3)" << std::endl; + std::cout << " ALL for all devices" << std::endl; + std::cout << "--runs RUNS Number of times each kernel is dispatched" << std::endl; + + std::cout << "--fp16 Run FP16 (VALU) test" << std::endl; + std::cout << "--fp32 Run FP32 (VALU) test" << std::endl; + std::cout << "--fp64 Run FP64 (VALU) test" << std::endl; + std::cout << "--int32 Run INT32 (VALU) test" << std::endl; + std::cout << "--matfp16 Run FP16 (MFMA) test" << std::endl; + std::cout << "--matfp32 Run FP32 (MFMA) test" << std::endl; + std::cout << "--smatfp16 Run FP16 (SMFMAC) test" << std::endl; +} + +int main(int argc, char** argv) +{ + if(std::string(argv[0]) == "CHILD") { + int device = atoi(argv[1]); + int runs = atoi(argv[2]); + uint32_t mask = atoi(argv[3]); + int fd = atoi(argv[4]); + + Result res = run_tests(device, runs, mask); + + write(fd, &res, sizeof(res)); + return 0; + } + + int runs = 1; + + uint32_t mask = 0; + bool all_devices = false; + std::vector devices; + int device_count; + int device = 0; + + HIP_CALL(hipGetDeviceCount(&device_count)); + + int i = 1; + while(i < argc) { + std::string arg = std::string(argv[i]); + + if(arg == "--help") { + usage(); + return 0; + } else if(arg == "--device") { + devices.push_back(atoi(argv[i + 1])); + // Skip next + i++; + } else if(arg == "--devices") { + // Parse comma-separated string of numbers + std::string s(argv[i + 1]); + + if(s == "all" || s == "ALL") { + all_devices = true; + } else { + std::stringstream ss(s); + std::string r; + while(getline(ss, r, ',')) { + devices.push_back(std::stoi(r)); + } + } + // Skip next + i++; + } else if(arg == "--runs") { + runs = atoi(argv[i + 1]); + + // Skip next + i++; + } else if(arg == "--fp32") { + mask |= VALU_FP32; + } else if(arg == "--fp64") { + mask |= VALU_FP64; + } else if(arg == "--fp16") { + mask |= VALU_FP16; + } else if(arg == "--int32") { + mask |= VALU_INT32; + } else if(arg == "--matfp16") { + mask |= MATRIX_FP16; + } else if(arg == "--matfp32") { + mask |= MATRIX_FP32; + } else if(arg == "--smatfp16") { + mask |= SMATRIX_FP16; + } else { + std::cout << "Invalid argument '" << arg << "'" << std::endl; + std::cout << std::endl; + usage(); + return 1; + } + + i++; + } + + if(all_devices) { + for(int i = 0; i < device_count; i++ ){ + devices.push_back(i); + } + } + + // Verify device ID's + for(auto d : devices) { + if(d >= device_count) { + std::cout << "Invalid device ordinal: " << d << std::endl; + return 1; + } + } + + if(devices.size() == 0) { + devices.push_back(0); + } + + if(mask == 0) { + mask = ALL; + } + + run(devices, runs, mask); + + return 0; +} diff --git a/skills/rocprof-compute-sample/simple_net.py b/skills/rocprof-compute-sample/simple_net.py new file mode 100755 index 0000000..6def8f2 --- /dev/null +++ b/skills/rocprof-compute-sample/simple_net.py @@ -0,0 +1,127 @@ +# Copyright (c) Advanced Micro Devices, Inc. +# SPDX-License-Identifier: MIT + +""" +Sample PyTorch workload for rocprof-compute --torch-trace. + + rocprof-compute profile --experimental --torch-trace --no-roof \ + -n simple_net -- python3 ./simple_net.py + + --user-range Wrap the run in a user ROCTX range. Requires ROCTX Python. + --same-line-linear Two Linear.forward calls from the same source line. + --no-backward Skip loss.backward(). Cannot combine with --backward-thread. + --backward-thread Run loss.backward() on a worker thread. Cannot combine + with --no-backward. +""" + +import argparse +import sys +import threading + +import torch +import torch.nn as nn +import torch.nn.functional as F + +try: + import roctx +except ImportError: + roctx = None + + +class SimpleNet(nn.Module): + def __init__(self, same_line_linear=False): + super().__init__() + self.fc1 = nn.Linear(10, 20) + self.fc2 = nn.Linear(20, 10) + self.same_line_linear = same_line_linear + + def forward(self, x): + if self.same_line_linear: + # two Linear.forward calls on one source line + # fmt: off + x = self.fc1(x); x = self.fc2(x) # noqa: E702 + # fmt: on + return x + x = self.fc1(x) + x = F.relu(x) + x = self.fc2(x) + return x + + +def parse_args(argv=None): + parser = argparse.ArgumentParser(description="SimpleNet torch-trace sample") + parser.add_argument( + "--user-range", + action="store_true", + help="Wrap the run in a user ROCTX range", + ) + parser.add_argument( + "--same-line-linear", + action="store_true", + help="Two Linear.forward calls from the same source line", + ) + exclusive = parser.add_mutually_exclusive_group() + exclusive.add_argument( + "--no-backward", + action="store_true", + help="Skip loss.backward()", + ) + exclusive.add_argument( + "--backward-thread", + action="store_true", + help="Run loss.backward() on a worker thread", + ) + args = parser.parse_args(argv) + if args.user_range and roctx is None: + parser.error("--user-range requires ROCTX Python") + return args + + +def run_backward(loss, caught): + """Thread target: run loss.backward() and keep any exception in caught.""" + try: + loss.backward() + except BaseException: + caught.append(sys.exc_info()[1:]) + + +def backward_on_worker_thread(loss): + """Run loss.backward() on a worker thread and re-raise its exception here.""" + caught = [] + worker = threading.Thread(target=run_backward, args=(loss, caught)) + worker.start() + worker.join() + if caught: + err, tb = caught[0] + raise err.with_traceback(tb) + + +def main(argv=None): + args = parse_args(argv) + if not torch.cuda.is_available(): + print("GPU is required for this sample. Exiting.") + sys.exit(1) + + if args.user_range: + roctx.rangePush("TrainingLoop_UserDefinedMarker") + + try: + model = SimpleNet(same_line_linear=args.same_line_linear).cuda() + x = torch.randn(5, 10).cuda() + + for _ in range(1): + output = model(x) + loss = output.sum() + if args.backward_thread: + backward_on_worker_thread(loss) + elif not args.no_backward: + loss.backward() + finally: + if args.user_range: + roctx.rangePop() + + print("Training completed") + + +if __name__ == "__main__": + main() diff --git a/skills/rocprof-compute-sample/stack.hip b/skills/rocprof-compute-sample/stack.hip new file mode 100644 index 0000000..ed8dad6 --- /dev/null +++ b/skills/rocprof-compute-sample/stack.hip @@ -0,0 +1,20 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/* +An example to explore spill/stack instructions. +Written by Nicholas Curtis [AMD]. +*/ + +#include "common.h" + +__global__ void knl(int* out, int filter) { + int x[1024]; + x[filter] = 0; + if (threadIdx.x < filter) out[threadIdx.x] = x[threadIdx.x]; +} + +int main() { + knl<<<1, 1>>>(nullptr, 0); + hipCheck(hipDeviceSynchronize()); +} diff --git a/skills/rocprof-compute-sample/torch_triton_net.py b/skills/rocprof-compute-sample/torch_triton_net.py new file mode 100644 index 0000000..4aa606d --- /dev/null +++ b/skills/rocprof-compute-sample/torch_triton_net.py @@ -0,0 +1,62 @@ +# Copyright (c) Advanced Micro Devices, Inc. +# SPDX-License-Identifier: MIT + +""" +Sample mixed PyTorch + Triton workload for rocprof-compute --ml-api-trace. + + rocprof-compute profile --experimental --ml-api-trace --no-roof \ + -n torch_triton_net -- python3 ./torch_triton_net.py +""" + +import sys + +import torch +import torch.nn as nn +import torch.nn.functional as F +import triton +import triton.language as tl + +BLOCK = 1024 + + +@triton.jit +def scale_kernel(x_ptr, out_ptr, n, BLOCK_SIZE: tl.constexpr): + offs = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) + mask = offs < n + x = tl.load(x_ptr + offs, mask=mask) + tl.store(out_ptr + offs, x * 2.0, mask=mask) + + +def triton_scale(x): + out = torch.empty_like(x) + n = out.numel() + scale_kernel[(triton.cdiv(n, BLOCK),)](x, out, n, BLOCK_SIZE=BLOCK) + return out + + +class TorchTritonNet(nn.Module): + def __init__(self): + super().__init__() + self.fc = nn.Linear(64, 64) + + def forward(self, x): + x = self.fc(x) + x = F.relu(x) + return triton_scale(x) + + +def main(): + if not torch.cuda.is_available(): + print("GPU is required for this sample. Exiting.") + sys.exit(1) + + model = TorchTritonNet().cuda() + inp = torch.randn(64, 64, device="cuda") + for _ in range(3): + model(inp) + torch.cuda.synchronize() + print("Mixed torch/triton workload completed") + + +if __name__ == "__main__": + main() diff --git a/skills/rocprof-compute-sample/triton_ffn.py b/skills/rocprof-compute-sample/triton_ffn.py new file mode 100644 index 0000000..63172a2 --- /dev/null +++ b/skills/rocprof-compute-sample/triton_ffn.py @@ -0,0 +1,179 @@ +# Copyright (c) Advanced Micro Devices, Inc. +# SPDX-License-Identifier: MIT + +""" +Pure-Triton LLaMA-style feed-forward block (RMSNorm + gated MLP). + +Implements one transformer FFN entirely in Triton: + + h = rmsnorm(x) + gate = h @ Wgate + up = h @ Wup + out = x + (silu(gate) * up) @ Wdown + +This exercises five distinct Triton kernels (rmsnorm, matmul, silu, mul, add); +the matmul kernel is launched from three call sites (the gate, up, and down +projections). +""" + +import sys + +import torch +import triton +import triton.language as tl + +BLOCK_M = 64 +BLOCK_N = 64 +BLOCK_K = 64 +ELT_BLOCK = 1024 + + +@triton.jit +def rmsnorm_kernel(x_ptr, w_ptr, out_ptr, n_cols, eps, BLOCK: tl.constexpr): + row = tl.program_id(0) + cols = tl.arange(0, BLOCK) + mask = cols < n_cols + x = tl.load(x_ptr + row * n_cols + cols, mask=mask, other=0.0) + rstd = 1.0 / tl.sqrt(tl.sum(x * x, axis=0) / n_cols + eps) + w = tl.load(w_ptr + cols, mask=mask, other=0.0) + tl.store(out_ptr + row * n_cols + cols, x * rstd * w, mask=mask) + + +@triton.jit +def matmul_kernel( + a_ptr, + b_ptr, + c_ptr, + M, + N, + K, + stride_am, + stride_ak, + stride_bk, + stride_bn, + stride_cm, + stride_cn, + BLOCK_M: tl.constexpr, + BLOCK_N: tl.constexpr, + BLOCK_K: tl.constexpr, +): + pid_m = tl.program_id(0) + pid_n = tl.program_id(1) + offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M) + offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N) + offs_k = tl.arange(0, BLOCK_K) + a_ptrs = a_ptr + (offs_m[:, None] * stride_am + offs_k[None, :] * stride_ak) + b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_n[None, :] * stride_bn) + acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32) + for k in range(0, K, BLOCK_K): + k_mask = offs_k[None, :] < K - k + a = tl.load(a_ptrs, mask=k_mask, other=0.0) + b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k, other=0.0) + acc += tl.dot(a, b) + a_ptrs += BLOCK_K * stride_ak + b_ptrs += BLOCK_K * stride_bk + c_ptrs = c_ptr + offs_m[:, None] * stride_cm + offs_n[None, :] * stride_cn + c_mask = (offs_m[:, None] < M) & (offs_n[None, :] < N) + tl.store(c_ptrs, acc, mask=c_mask) + + +@triton.jit +def silu_kernel(x_ptr, out_ptr, n, BLOCK_SIZE: tl.constexpr): + offs = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) + mask = offs < n + x = tl.load(x_ptr + offs, mask=mask) + tl.store(out_ptr + offs, x * tl.sigmoid(x), mask=mask) + + +@triton.jit +def mul_kernel(x_ptr, y_ptr, out_ptr, n, BLOCK_SIZE: tl.constexpr): + offs = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) + mask = offs < n + x = tl.load(x_ptr + offs, mask=mask) + y = tl.load(y_ptr + offs, mask=mask) + tl.store(out_ptr + offs, x * y, mask=mask) + + +@triton.jit +def add_kernel(x_ptr, y_ptr, out_ptr, n, BLOCK_SIZE: tl.constexpr): + offs = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) + mask = offs < n + x = tl.load(x_ptr + offs, mask=mask) + y = tl.load(y_ptr + offs, mask=mask) + tl.store(out_ptr + offs, x + y, mask=mask) + + +def rmsnorm(x, weight, eps=1e-6): + rows, cols = x.shape + out = torch.empty_like(x) + rmsnorm_kernel[(rows,)]( + x, weight, out, cols, eps, BLOCK=triton.next_power_of_2(cols) + ) + return out + + +def matmul(a, b): + m, k = a.shape + _, n = b.shape + c = torch.empty((m, n), device=a.device, dtype=a.dtype) + grid = (triton.cdiv(m, BLOCK_M), triton.cdiv(n, BLOCK_N)) + matmul_kernel[grid]( + a, + b, + c, + m, + n, + k, + a.stride(0), + a.stride(1), + b.stride(0), + b.stride(1), + c.stride(0), + c.stride(1), + BLOCK_M=BLOCK_M, + BLOCK_N=BLOCK_N, + BLOCK_K=BLOCK_K, + ) + return c + + +def elementwise(kernel, *tensors): + out = torch.empty_like(tensors[0]) + n = out.numel() + grid = (triton.cdiv(n, ELT_BLOCK),) + kernel[grid](*tensors, out, n, BLOCK_SIZE=ELT_BLOCK) + return out + + +def ffn(x, w_norm, w_gate, w_up, w_down): + h = rmsnorm(x, w_norm) + gate = matmul(h, w_gate) + up = matmul(h, w_up) + act = elementwise(silu_kernel, gate) + fused = elementwise(mul_kernel, act, up) + down = matmul(fused, w_down) + return elementwise(add_kernel, x, down) + + +def main(): + if not torch.cuda.is_available(): + print("GPU is required for this sample. Exiting.") + sys.exit(1) + + tokens, hidden, inter = 512, 512, 2048 + dev = "cuda" + x = torch.randn(tokens, hidden, device=dev) + w_norm = torch.randn(hidden, device=dev) + w_gate = torch.randn(hidden, inter, device=dev) + w_up = torch.randn(hidden, inter, device=dev) + w_down = torch.randn(inter, hidden, device=dev) + + for _ in range(3): + out = ffn(x, w_norm, w_gate, w_up, w_down) + + torch.cuda.synchronize() + print(f"FFN completed, output sum: {out.sum().item():.3f}") + + +if __name__ == "__main__": + main() diff --git a/skills/rocprof-compute-sample/vcopy.cpp b/skills/rocprof-compute-sample/vcopy.cpp new file mode 100644 index 0000000..a5109ba --- /dev/null +++ b/skills/rocprof-compute-sample/vcopy.cpp @@ -0,0 +1,227 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#include "hip/hip_runtime.h" +#include +#include +#include +#include +#include +#include +using namespace std; + +#define HIP_ASSERT(x) (assert((x)==hipSuccess)) + +// Unlike HIP_ASSERT, this reports the failing call and error string, and stays +// active when NDEBUG is defined. +#define HIP_CHECK(x) \ + do { \ + hipError_t _hip_check_err = (x); \ + if (_hip_check_err != hipSuccess) { \ + fflush(stdout); /* keep the error in sequence when stdout is a pipe */ \ + fprintf(stderr, "HIP error at %s:%d in '%s': %s (%d)\n", __FILE__, \ + __LINE__, #x, hipGetErrorString(_hip_check_err), \ + (int)_hip_check_err); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +// Cap on how many individual mismatches are printed. A failed launch leaves +// every element mismatched, which is millions of lines for a typical -n. +#define MAX_REPORTED_ERRORS 10 + +// HIP kernel. Each thread takes care of one element of c +__global__ void vecCopy(double *a, double *b, double *c, int n, int stride) { + // Get our global thread ID + int id = blockIdx.x*blockDim.x+threadIdx.x; + if (id < n) + c[id] = a[id]; +} + +// Duplicate of vecCopy kernel. Included for testing purposes +__global__ void vecCopy_2(double *a, double *b, double *c, int n, int stride) { + // Get our global thread ID + int id = blockIdx.x*blockDim.x+threadIdx.x; + if (id < n) + c[id] = a[id]; +} + +void usage() { + std::cout << "Usage: vcopy [OPTIONS]\n"; + std::cout << "Required:\n"; + std::cout << " -n/--numThreads Set the num of threads\n"; + std::cout << " -b/--blockSize Set the block size\n"; + std::cout << "Optional:\n"; + std::cout << " -d/--dev Set the device ID [Default: 0]\n"; + std::cout << " -i/--iter Set the num of iterations [Default: 1]\n"; + std::cout << " -h/--help Display this help message\n"; + exit(1); + return; +} + +int main(int argc, char* argv[]) { + // Size of vectors + int n; //64 MB + int blockSize, gridSize; + + // Launch multiple kernels + bool multiKernel = false; + + // Host input vectors + double *h_a; + double *h_b; + //Host output vector + double *h_c; + //Host output vector for verification + double *h_verify_c; + + // Device input vectors + double *d_a; + double *d_b; + // Device output vector + double *d_c; + + int stride = 1; + int devId = 0; + int numIter = 1; + + for (int i = 0; i < argc; i++){ + std::string arg = argv[i]; + if ((arg == "--blockSize" || arg == "-b") && i+1 < argc) + blockSize = std::atoi(argv[i+1]); + + else if ((arg == "--vec" || arg == "-n") && i+1 < argc) + n = std::atoi(argv[i+1]); + + else if ((arg == "--device" || arg == "-d") && i+1 < argc) + devId = std::atoi(argv[i+1]); + + else if ((arg == "--iter" || arg == "-i") && i+1 < argc) + numIter = std::atoi(argv[i+1]); + + else if (arg == "--multikernel") + multiKernel = true; + + else if (arg == "--help" || arg == "-h") + usage(); + } + + if (blockSize == 0) + usage(); + + if (n == 0) + usage(); + + + int numGpuDevices; + HIP_ASSERT(hipGetDeviceCount(&numGpuDevices)); + if(devId >= numGpuDevices) + devId = 0; + HIP_ASSERT(hipSetDevice(devId)); + + printf("vcopy testing on GCD %d\n", devId); + + assert(n > 0); + assert(blockSize > 0); + + // Size, in bytes, of each vector + size_t bytes = n*sizeof(double)*stride; + + // Allocate memory for each vector on host + h_a = (double*)malloc(bytes); + h_b = (double*)malloc(bytes); + h_c = (double*)malloc(bytes); + h_verify_c = (double*)malloc(bytes); + + printf("Finished allocating vectors on the CPU\n"); + + // Allocate memory for each vector on GPU + HIP_ASSERT(hipMalloc(&d_a, bytes)); + HIP_ASSERT(hipMalloc(&d_b, bytes)); + HIP_ASSERT(hipMalloc(&d_c, bytes)); + + printf("Finished allocating vectors on the GPU\n"); + + // Initialize vectors on host + for(int i = 0; i < n; i++) { + h_a[i] = i; + h_b[i] = i; + } + + // Copy host vectors to device + HIP_ASSERT(hipMemcpy(d_a, h_a, bytes, hipMemcpyHostToDevice)); + HIP_ASSERT(hipMemcpy(d_b, h_b, bytes, hipMemcpyHostToDevice)); + + printf("Finished copying vectors to the GPU\n"); + + // Number of thread blocks in grid + gridSize = (int)ceil((float)n/blockSize); + int tot_waves = (blockSize*gridSize)/64; + float num_bytes_kb = ((sizeof(double))*n)/(1024); + float num_bytes_wave = (1.0*num_bytes_kb)/(1.0*tot_waves); + + printf("sw thinks it moved %f KB per wave \n", (2.0*num_bytes_wave)); + printf("Total threads: %d, Grid Size: %d block Size:%d, Wavefronts:%d:\n", n, gridSize, blockSize, tot_waves); + printf("Launching the kernel on the GPU\n"); + + // Execute the kernel + for(int i = 0; i < numIter; i++){ + hipLaunchKernelGGL(vecCopy, dim3(gridSize), dim3(blockSize), 0, 0, d_a, d_b, d_c, n, stride); + // Catches launch-time failures (e.g. no kernel image for this device); + // hipDeviceSynchronize() catches failures during execution. + HIP_CHECK(hipGetLastError()); + HIP_CHECK(hipDeviceSynchronize()); + printf("Finished executing kernel\n"); + // Optionally, launch a second kernel. Only here for testing purposes + if (multiKernel){ + hipLaunchKernelGGL(vecCopy_2, dim3(gridSize), dim3(blockSize), 0, 0, d_a, d_b, d_c, n, stride); + HIP_CHECK(hipGetLastError()); + HIP_CHECK(hipDeviceSynchronize()); + printf("Finished executing kernel\n"); + } + } + + // Copy array back to host + HIP_ASSERT(hipMemcpy( h_c, d_c, bytes, hipMemcpyDeviceToHost)); + printf("Finished copying the output vector from the GPU to the CPU\n"); + + // Compute for CPU + for(int i=0; i 1e-5) { + if (numErrors < MAX_REPORTED_ERRORS) + printf("Error at position i %d, Expected: %f, Found: %f \n", i, + h_verify_c[i*stride], h_c[i*stride]); + numErrors++; + } + } + if (numErrors > MAX_REPORTED_ERRORS) + printf("... %d further mismatches not shown\n", numErrors - MAX_REPORTED_ERRORS); + if (numErrors > 0) + printf("Verification FAILED: %d of %d elements mismatched\n", numErrors, n); + //printf("Printing few elements from the output vector\n"); + for(int i = 0; i < 20; i++) { + //printf("Output[%d]:%f\n",i, h_c[i]); + } + + printf("Releasing GPU memory\n"); + + // Release device memory + HIP_ASSERT(hipFree(d_a)); + HIP_ASSERT(hipFree(d_b)); + HIP_ASSERT(hipFree(d_c)); + + // Release host memory + printf("Releasing CPU memory\n"); + free(h_a); + free(h_b); + free(h_c); + + return numErrors > 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/skills/rocprof-compute-sample/vmem.hip b/skills/rocprof-compute-sample/vmem.hip new file mode 100644 index 0000000..4f079cf --- /dev/null +++ b/skills/rocprof-compute-sample/vmem.hip @@ -0,0 +1,75 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/* +An example to explore global/generic instructions. +Written by Nicholas Curtis [AMD]. +*/ + +#include "common.h" + +typedef int __attribute__((address_space(0)))* generic_ptr; + +__attribute__((noinline)) __device__ void generic_store(generic_ptr ptr, int zero) { *ptr = zero; } +__attribute__((noinline)) __device__ int generic_load(generic_ptr ptr) { return *ptr; } +__attribute__((noinline)) __device__ void generic_atomic(generic_ptr ptr, int zero) { atomicAdd((int*)ptr, zero); } + +__global__ void global_write(int* ptr, int zero) { + ptr[threadIdx.x] = zero; +} + +__global__ void generic_write(int* ptr, int zero, int filter) { + __shared__ int lds[1024]; + int* generic = (threadIdx.x < filter) ? &ptr[threadIdx.x] : &lds[threadIdx.x]; + generic_store((generic_ptr)generic, zero); +} + +__global__ void global_read(int* ptr, int zero) { + int x = ptr[threadIdx.x]; + if (x != zero) { + ptr[threadIdx.x] = x + 1; + } +} + +__global__ void generic_read(int* ptr, int zero, int filter) { + __shared__ int lds[1024]; + if (static_cast(filter - 1) == zero) { + lds[threadIdx.x] = 0; // initialize to zero to avoid conditional, but hide behind _another_ conditional + } + int* generic; + if (static_cast(threadIdx.x) > filter - 1) { + generic = &ptr[threadIdx.x]; + } else { + generic = &lds[threadIdx.x]; + abort(); + } + int x = generic_load((generic_ptr)generic); + if (x != zero) { + ptr[threadIdx.x] = x + 1; + } +} + + +__global__ void global_atomic(int* ptr, int zero) { + atomicAdd(ptr, zero); +} + +__global__ void generic_atomic(int* ptr, int filter, int zero) { + __shared__ int lds[1024]; + int* generic = (threadIdx.x % 2 == filter) ? &ptr[threadIdx.x] : &lds[threadIdx.x]; + generic_atomic((generic_ptr)generic, zero); +} + +int main() { + int* ptr; + hipCheck(hipMalloc(&ptr, sizeof(int))); + hipCheck(hipMemset(ptr, 0, sizeof(int))); + global_write<<<1,1>>>(ptr, 0); + generic_write<<<1,1>>>(ptr, 0, 0); + global_read<<<1,1>>>(ptr, 0); + generic_read<<<1,1>>>(ptr, 0, 0); + global_atomic<<<1,1>>>(ptr, 0); + generic_atomic<<<1,1>>>(ptr, 0, 0); + hipCheck(hipDeviceSynchronize()); + hipCheck(hipFree(ptr)); +} diff --git a/skills/rocprof-compute-sample/vrandom_access.cpp b/skills/rocprof-compute-sample/vrandom_access.cpp new file mode 100644 index 0000000..c53f54b --- /dev/null +++ b/skills/rocprof-compute-sample/vrandom_access.cpp @@ -0,0 +1,82 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/* +An example code to execute random access to explore cache hits/misses in L2 Cache. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#define HIP_ASSERT(x) (assert((x) == hipSuccess)) + +// Kernel: random access, each thread picks a random index +__global__ void randomAccessKernel(int *d_data, int N, unsigned int *d_seeds) +{ + int tid = blockIdx.x * blockDim.x + threadIdx.x; + if (tid < N) + { + unsigned int seed = d_seeds[tid]; + // Simple XORShift + seed ^= (seed << 13); + seed ^= (seed >> 17); + seed ^= (seed << 5); + int idx = seed % N; + d_data[idx] += 1; + } +} + +int main() +{ + const int N = 1 << 24; // Try 16M elements to exceed cache + size_t size = N * sizeof(int); + + // Host memory + std::vector h_data(N, 0); + std::vector h_seeds(N); + + // Generate seeds + srand(time(nullptr)); + for (int i = 0; i < N; ++i) + { + // Keep them diverse. Could be random or based on i + h_seeds[i] = rand(); + } + + // Allocate device memory + int *d_data; + unsigned int *d_seeds; + HIP_ASSERT(hipMalloc(&d_data, size)); + HIP_ASSERT(hipMalloc(&d_seeds, N * sizeof(unsigned int))); + + // Copy h_data to device + HIP_ASSERT(hipMemcpy(d_data, h_data.data(), size, hipMemcpyHostToDevice)); + HIP_ASSERT(hipMemcpy(d_seeds, h_seeds.data(), N * sizeof(unsigned int), hipMemcpyHostToDevice)); + + // Configure kernel + dim3 blockSize(64); + dim3 gridSize((N + blockSize.x - 1) / blockSize.x); + + // Repeat the launch so the profile is robust. + const int kIters = 30; + for (int i = 0; i < kIters; ++i) + { + hipLaunchKernelGGL(randomAccessKernel, gridSize, blockSize, 0, 0, d_data, N, d_seeds); + } + HIP_ASSERT(hipDeviceSynchronize()); + + HIP_ASSERT(hipMemcpy(h_data.data(), d_data, size, hipMemcpyDeviceToHost)); + + // Cleanup + HIP_ASSERT(hipFree(d_data)); + HIP_ASSERT(hipFree(d_seeds)); + ; + + std::cout << "RandomAccess HIP test completed.\n"; + return 0; +} diff --git a/skills/rocprof-compute-sample/vsequential_access.cpp b/skills/rocprof-compute-sample/vsequential_access.cpp new file mode 100644 index 0000000..d443fc3 --- /dev/null +++ b/skills/rocprof-compute-sample/vsequential_access.cpp @@ -0,0 +1,60 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +/* +An example code to execute sequential access to explore cache hits/misses in L2 Cache. +*/ + +#include +#include +#include + +#define HIP_ASSERT(x) (assert((x)==hipSuccess)) + +// Kernel: sequential access, each thread reads/writes an element in order +__global__ void sequentialAccessKernel(int *d_data, int N) +{ + int tid = blockIdx.x * blockDim.x + threadIdx.x; + if (tid < N) + { + d_data[tid] += 1; + } +} + +int main() +{ + const int N = 1 << 20; // 1M elements + size_t size = N * sizeof(int); + // Allocate host memory + int *h_data = (int *)malloc(size); + std::fill_n(h_data, N, 0); + + // Allocate device memory + int *d_data; + HIP_ASSERT(hipMalloc(&d_data, size)); + + // Copy h_data to device + HIP_ASSERT(hipMemcpy(d_data, h_data, size, hipMemcpyHostToDevice)); + + // Configure kernel + dim3 blockSize(64); + dim3 gridSize((N + blockSize.x - 1) / blockSize.x); + + // Repeat the launch so the profile is robust. + const int kIters = 30; + for (int i = 0; i < kIters; ++i) + { + hipLaunchKernelGGL(sequentialAccessKernel, gridSize, blockSize, 0, 0, d_data, N); + } + HIP_ASSERT(hipDeviceSynchronize()); + + // Copy back to host + HIP_ASSERT(hipMemcpy(h_data, d_data, size, hipMemcpyDeviceToHost)); + + // Cleanup + HIP_ASSERT(hipFree(d_data)); + free(h_data); + + std::cout << "SequentialAccess HIP test completed.\n"; + return 0; +} diff --git a/skills/rocprof-compute-sample/wmma_gemm_fp16/Makefile b/skills/rocprof-compute-sample/wmma_gemm_fp16/Makefile new file mode 100644 index 0000000..4266c56 --- /dev/null +++ b/skills/rocprof-compute-sample/wmma_gemm_fp16/Makefile @@ -0,0 +1,34 @@ +# Copyright (c) Advanced Micro Devices, Inc. +# SPDX-License-Identifier: MIT +# +# FP16 WMMA GEMM sample for gfx1250 metric health profiling. + +HIPCC ?= hipcc +GPU_ARCH ?= gfx1250 +HIPCC_FLAGS = -O2 -g -std=c++17 -Wall -Wextra -fPIC +INCLUDES = -I. + +TARGET = wmma_gemm_fp16 +KERNEL_SRC = wmma_gemm_fp16.hip +HOST_SRC = main.cpp + +.PHONY: all clean run help + +all: $(TARGET) + +$(TARGET): $(KERNEL_SRC) $(HOST_SRC) wmma_gemm_fp16_config.hpp wmma_gemm_fp16.hpp + @echo "Building $(TARGET) for $(GPU_ARCH)..." + $(HIPCC) $(HIPCC_FLAGS) --offload-arch=$(GPU_ARCH) $(INCLUDES) -o $@ $(HOST_SRC) $(KERNEL_SRC) + @echo "Build complete: $@" + +run: $(TARGET) + ./$(TARGET) + +clean: + rm -f $(TARGET) + +help: + @echo "Targets:" + @echo " make Build for gfx1250 (override: GPU_ARCH=gfx1250)" + @echo " make run Build and run with defaults" + @echo " make clean Remove binary" diff --git a/skills/rocprof-compute-sample/wmma_gemm_fp16/main.cpp b/skills/rocprof-compute-sample/wmma_gemm_fp16/main.cpp new file mode 100644 index 0000000..c617536 --- /dev/null +++ b/skills/rocprof-compute-sample/wmma_gemm_fp16/main.cpp @@ -0,0 +1,208 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include +#include + +#include "../common.h" +#include "wmma_gemm_fp16.hpp" + +using namespace wmma_gemm_fp16; + +namespace { + +struct Options { + uint32_t m = kDefaultM; + uint32_t n = kDefaultN; + uint32_t k = kDefaultK; + uint32_t iters = kDefaultIters; + uint32_t warmup = kDefaultWarmup; + int device = 0; +}; + +void usage() +{ + std::cout + << "Usage: wmma_gemm_fp16 [OPTIONS]\n" + << " FP16 WMMA GEMM for gfx1250 WMMA/XDL metric health profiling.\n\n" + << "Optional:\n" + << " -m, --m Rows of A / D [default: " << kDefaultM << "]\n" + << " -n, --n Cols of B / D [default: " << kDefaultN << "]\n" + << " -k, --k Inner dimension K (multiple of 32) [default: " + << kDefaultK << "]\n" + << " -i, --iter Kernel iterations [default: " << kDefaultIters + << "]\n" + << " -w, --warmup Warmup iterations [default: " << kDefaultWarmup + << "]\n" + << " -d, --device HIP device id [default: 0]\n" + << " -h, --help Show this help\n"; + std::exit(EXIT_SUCCESS); +} + +Options parse_args(int argc, char* argv[]) +{ + Options opts; + for (int i = 1; i < argc; ++i) { + const std::string arg = argv[i]; + auto need_value = [&](const char* flag) { + if (i + 1 >= argc) { + std::cerr << "Missing value for " << flag << "\n"; + usage(); + } + return std::string(argv[++i]); + }; + + if (arg == "-h" || arg == "--help") { + usage(); + } else if (arg == "-m" || arg == "--m") { + opts.m = static_cast(std::stoul(need_value(arg.c_str()))); + } else if (arg == "-n" || arg == "--n") { + opts.n = static_cast(std::stoul(need_value(arg.c_str()))); + } else if (arg == "-k" || arg == "--k") { + opts.k = static_cast(std::stoul(need_value(arg.c_str()))); + } else if (arg == "-i" || arg == "--iter") { + opts.iters = static_cast(std::stoul(need_value(arg.c_str()))); + } else if (arg == "-w" || arg == "--warmup") { + opts.warmup = static_cast(std::stoul(need_value(arg.c_str()))); + } else if (arg == "-d" || arg == "--device") { + opts.device = std::stoi(need_value(arg.c_str())); + } else { + std::cerr << "Unknown argument: " << arg << "\n"; + usage(); + } + } + return opts; +} + +void fill_matrix(std::vector<_Float16>& mat, uint32_t rows, uint32_t cols) +{ + std::mt19937 rng(1337); + std::uniform_real_distribution dist(0.5f, 2.0f); + mat.resize(static_cast(rows) * cols); + for (auto& value : mat) { + value = static_cast<_Float16>(dist(rng)); + } +} + +bool smoke_check(const std::vector<_Float16>& d, uint32_t m, uint32_t n) +{ + for (uint32_t row = 0; row < std::min(m, 64u); ++row) { + for (uint32_t col = 0; col < std::min(n, 64u); ++col) { + const float value = static_cast(d[static_cast(row) * n + col]); + if (value != 0.0f && std::isfinite(value)) { + return true; + } + } + } + return false; +} + +} // namespace + +int main(int argc, char* argv[]) +{ + const Options opts = parse_args(argc, argv); + + if (opts.m == 0 || opts.n == 0 || opts.k == 0) { + std::cerr << "M, N, and K must be positive.\n"; + return EXIT_FAILURE; + } + if (opts.k % kBlockK != 0) { + std::cerr << "K must be a multiple of " << kBlockK << " for 16x16x32 WMMA tiles.\n"; + return EXIT_FAILURE; + } + if (opts.m % kBlockM != 0 || opts.n % kBlockN != 0) { + std::cerr << "M and N must be multiples of " << kBlockM << ".\n"; + return EXIT_FAILURE; + } + + hipCheck(hipSetDevice(opts.device)); + + hipDeviceProp_t props{}; + hipCheck(hipGetDeviceProperties(&props, opts.device)); + std::cout << "Device: " << props.name << " (" << props.gcnArchName << ")\n"; + std::cout << "Problem: M=" << opts.m << " N=" << opts.n << " K=" << opts.k + << " iters=" << opts.iters << "\n"; + + std::vector<_Float16> h_a; + std::vector<_Float16> h_b; + std::vector<_Float16> h_d(static_cast(opts.m) * opts.n, _Float16(0)); + + fill_matrix(h_a, opts.m, opts.k); + fill_matrix(h_b, opts.n, opts.k); + + _Float16 *d_a = nullptr; + _Float16 *d_b = nullptr; + _Float16 *d_d = nullptr; + + const size_t bytes_a = h_a.size() * sizeof(_Float16); + const size_t bytes_b = h_b.size() * sizeof(_Float16); + const size_t bytes_d = h_d.size() * sizeof(_Float16); + + hipCheck(hipMalloc(&d_a, bytes_a)); + hipCheck(hipMalloc(&d_b, bytes_b)); + hipCheck(hipMalloc(&d_d, bytes_d)); + + hipCheck(hipMemcpy(d_a, h_a.data(), bytes_a, hipMemcpyHostToDevice)); + hipCheck(hipMemcpy(d_b, h_b.data(), bytes_b, hipMemcpyHostToDevice)); + hipCheck(hipMemcpy(d_d, h_d.data(), bytes_d, hipMemcpyHostToDevice)); + + const dim3 block_dim(kThreadsX, kThreadsY); + const dim3 grid_dim(DivUp(static_cast(opts.m), static_cast(kBlockM)), + DivUp(static_cast(opts.n), static_cast(kBlockN)), + 1); + + std::cout << "Launch grid=(" << grid_dim.x << "," << grid_dim.y << ") block=(" + << block_dim.x << "," << block_dim.y << ")\n"; + + auto launch = [&]() { + hipLaunchKernelGGL(gemm_wmma_fp16, + grid_dim, + block_dim, + 0, + 0, + opts.m, + opts.n, + opts.k, + d_a, + d_b, + d_d); + hipCheck(hipGetLastError()); + }; + + for (uint32_t i = 0; i < opts.warmup; ++i) { + launch(); + } + hipCheck(hipDeviceSynchronize()); + + const auto start = std::chrono::steady_clock::now(); + for (uint32_t i = 0; i < opts.iters; ++i) { + launch(); + } + hipCheck(hipDeviceSynchronize()); + const auto end = std::chrono::steady_clock::now(); + + const double elapsed_ms = + std::chrono::duration(end - start).count(); + const double gflops = + 2.0 * static_cast(opts.m) * opts.n * opts.k * opts.iters * 1e-9; + const double tflops_per_s = gflops / elapsed_ms; + + hipCheck(hipMemcpy(h_d.data(), d_d, bytes_d, hipMemcpyDeviceToHost)); + + const bool passed = smoke_check(h_d, opts.m, opts.n); + std::cout << "Elapsed: " << elapsed_ms << " ms (" << opts.iters << " iterations)\n"; + std::cout << "Throughput: " << tflops_per_s << " TFLOP/s (FP16 GEMM convention)\n"; + std::cout << "Smoke check: " << (passed ? "PASS (non-zero output)" : "FAIL") << "\n"; + + hipCheck(hipFree(d_a)); + hipCheck(hipFree(d_b)); + hipCheck(hipFree(d_d)); + + return passed ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16.hip b/skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16.hip new file mode 100644 index 0000000..eb6ef30 --- /dev/null +++ b/skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16.hip @@ -0,0 +1,153 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +// Sustained FP16 WMMA GEMM for gfx1250 metric health profiling. +// Adapted from hipNPIKernels WMMA_gemm fp16_16x16x32 (native16x16x32). + +#include +#include + +#include "wmma_gemm_fp16_config.hpp" + +using namespace wmma_gemm_fp16; + +using Float16x4 = _Float16 __attribute__((ext_vector_type(4))); +using Float16x8 = _Float16 __attribute__((ext_vector_type(8))); +using Float16x16 = _Float16 __attribute__((ext_vector_type(16))); +using Floatx8 = float __attribute__((ext_vector_type(8))); + +__device__ inline uint32_t lane_id() { return static_cast(threadIdx.x); } + +__device__ inline void load_row_major(Float16x16& regs, + uint32_t row, + uint32_t k_start, + uint32_t row_len, + const _Float16* __restrict__ buffer) +{ + const uint32_t low_high = lane_id() / 16; + const uint32_t row_addr = row + (lane_id() % 16); + + const uint32_t col1 = k_start + low_high * 16; + const uint32_t col2 = k_start + 4 + low_high * 16; + const uint32_t col3 = k_start + 8 + low_high * 16; + const uint32_t col4 = k_start + 12 + low_high * 16; + + auto* p_regs = reinterpret_cast(®s); + p_regs[0] = *reinterpret_cast(buffer + row_addr * row_len + col1); + p_regs[1] = *reinterpret_cast(buffer + row_addr * row_len + col2); + p_regs[2] = *reinterpret_cast(buffer + row_addr * row_len + col3); + p_regs[3] = *reinterpret_cast(buffer + row_addr * row_len + col4); +} + +__device__ inline void write_output(const Float16x8& frag, + uint32_t wave_row, + uint32_t wave_col, + uint32_t row_len, + _Float16* __restrict__ d) +{ + const uint32_t out_col = (lane_id() >= 16) ? 8 : 0; + const uint32_t out_row = lane_id() % 16; + const uint32_t row = wave_row + out_row; + const uint32_t col = wave_col + out_col; + *reinterpret_cast(d + row * row_len + col) = frag; +} + +__device__ inline void inner_gemm(uint32_t sub_m_start, + uint32_t sub_n_start, + uint32_t m, + uint32_t n, + uint32_t k, + const _Float16* __restrict__ a, + const _Float16* __restrict__ b, + _Float16* __restrict__ d) +{ +#if !__has_builtin(__builtin_amdgcn_wmma_f32_16x16x32_f16) + (void)sub_m_start; + (void)sub_n_start; + (void)m; + (void)n; + (void)k; + (void)a; + (void)b; + if (threadIdx.x == 0 && blockIdx.x == 0 && blockIdx.y == 0) { + d[0] = _Float16(0); + } + return; +#else + Floatx8 frag_acc = {}; + + const uint32_t major_wave = __builtin_amdgcn_readfirstlane(threadIdx.x / kWaveSize); + const uint32_t minor_wave = threadIdx.y; + + const uint32_t wave_row = major_wave * kWmmaM + sub_m_start; + const uint32_t wave_col = minor_wave * kWmmaN + sub_n_start; + + const bool full_block = + (sub_m_start + kBlockM <= m) && (sub_n_start + kBlockN <= n) && (k >= kBlockK); + + if (!full_block) { + return; + } + + const uint32_t k_tiles = k / kBlockK; + uint32_t k_iter = 0; + + Float16x16 reg_a = {}; + Float16x16 reg_b = {}; + + load_row_major(reg_a, wave_row, 0, k, a); + load_row_major(reg_b, wave_col, 0, k, b); + + if (k_tiles > 1) { + do { + const uint32_t next_k = (k_iter + 1) * kBlockK; + + Float16x16 next_a = {}; + Float16x16 next_b = {}; + + load_row_major(next_a, wave_row, next_k, k, a); + load_row_major(next_b, wave_col, next_k, k, b); + + __builtin_amdgcn_sched_barrier(0); + frag_acc = __builtin_amdgcn_wmma_f32_16x16x32_f16( + 0, reg_b, 0, reg_a, 0, frag_acc, 0, 0); + __builtin_amdgcn_sched_barrier(0); + + reg_a = next_a; + reg_b = next_b; + ++k_iter; + } while (k_iter < (k_tiles - 1)); + } + + frag_acc = __builtin_amdgcn_wmma_f32_16x16x32_f16(0, reg_b, 0, reg_a, 0, frag_acc, 0, 0); + + Float16x8 frag_d = {}; + for (int i = 0; i < 8; ++i) { + frag_d[i] = static_cast<_Float16>(frag_acc[i]); + } + + write_output(frag_d, wave_row, wave_col, n, d); +#endif +} + +__attribute__((amdgpu_flat_work_group_size(1, kFlatWorkgroupSize))) +extern "C" __global__ void gemm_wmma_fp16(uint32_t m, + uint32_t n, + uint32_t k, + const _Float16* __restrict__ a, + const _Float16* __restrict__ b, + _Float16* __restrict__ d) +{ + const uint32_t block_count_m = DivUp(static_cast(m), static_cast(kBlockM)); + const uint32_t block_count_n = DivUp(static_cast(n), static_cast(kBlockN)); + + for (uint32_t bm = blockIdx.x; bm < block_count_m; bm += gridDim.x) { + for (uint32_t bn = blockIdx.y; bn < block_count_n; bn += gridDim.y) { + const uint32_t block_m = bm * kBlockM; + const uint32_t block_n = bn * kBlockN; + if (block_m < m && block_n < n) { + inner_gemm(block_m, block_n, m, n, k, a, b, d); + } + } + } +} diff --git a/skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16.hpp b/skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16.hpp new file mode 100644 index 0000000..faacb53 --- /dev/null +++ b/skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16.hpp @@ -0,0 +1,19 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#include + +#include "wmma_gemm_fp16_config.hpp" + +// FP16 WMMA GEMM for gfx1250. Kernel adapted from hipNPIKernels +// WMMA_gemm/src/fp16/gfx1250/fp16_16x16x32/native16x16x32.cpp. +extern "C" __global__ void gemm_wmma_fp16(uint32_t m, + uint32_t n, + uint32_t k, + const _Float16* __restrict__ a, + const _Float16* __restrict__ b, + _Float16* __restrict__ d); diff --git a/skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16_config.hpp b/skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16_config.hpp new file mode 100644 index 0000000..2bb316e --- /dev/null +++ b/skills/rocprof-compute-sample/wmma_gemm_fp16/wmma_gemm_fp16_config.hpp @@ -0,0 +1,31 @@ +// Copyright (c) Advanced Micro Devices, Inc. +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +namespace wmma_gemm_fp16 { + +constexpr uint32_t kWmmaM = 16; +constexpr uint32_t kWmmaN = 16; +constexpr uint32_t kWmmaK = 32; + +constexpr uint32_t kWaveSize = 32; +constexpr uint32_t kBlockM = 16; +constexpr uint32_t kBlockN = 16; +constexpr uint32_t kBlockK = 32; + +constexpr uint32_t kThreadsX = 1 * kWaveSize; +constexpr uint32_t kThreadsY = 1; +constexpr uint32_t kFlatWorkgroupSize = kThreadsX * kThreadsY; + +constexpr uint32_t kDefaultM = 4096; +constexpr uint32_t kDefaultN = 4096; +constexpr uint32_t kDefaultK = 1024; +constexpr uint32_t kDefaultIters = 50; +constexpr uint32_t kDefaultWarmup = 5; + +constexpr int DivUp(int a, int b) { return (a + b - 1) / b; } + +} // namespace wmma_gemm_fp16 diff --git a/skills/rocprof-compute-speed-of-light/evals/evals.json b/skills/rocprof-compute-speed-of-light/evals/evals.json index a87c673..aa24860 100644 --- a/skills/rocprof-compute-speed-of-light/evals/evals.json +++ b/skills/rocprof-compute-speed-of-light/evals/evals.json @@ -4,7 +4,7 @@ "id": "sol-percent-of-peak", "skill_should_trigger": true, "prompt": "how close to peak is this kernel running in my rocprof-compute workload?\n\nThe application is ./vcopy.cpp.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "analyzes the sol block for a single kernel id", "reads the Percent of Peak column from the report", @@ -19,7 +19,7 @@ "id": "sol-overall-efficiency", "skill_should_trigger": true, "prompt": "give me an overall efficiency summary of this rocprofiler-compute capture\n\nThe application is ./vcopy.cpp.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "selects one kernel with -k before reading Speed-of-Light", "summarizes using the printed peak and percent of peak values" diff --git a/skills/rocprof-compute-torch-trace/evals/evals.json b/skills/rocprof-compute-torch-trace/evals/evals.json index 88bb208..42ac53b 100644 --- a/skills/rocprof-compute-torch-trace/evals/evals.json +++ b/skills/rocprof-compute-torch-trace/evals/evals.json @@ -4,7 +4,7 @@ "id": "tt-which-operator-is-slow", "skill_should_trigger": true, "prompt": "which PyTorch operator is launching the slow GPU kernels in my model?\n\nThe application is ./simple_net.py.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "collects with --experimental --torch-trace", "lists the captured operators to find the slow ones", @@ -19,7 +19,7 @@ "id": "tt-map-kernels-to-model-code", "skill_should_trigger": true, "prompt": "profile my training script and tell me which layer the GPU time belongs to\n\nThe application is ./simple_net.py.", - "workspace": "../../sample", + "workspace": "../rocprof-compute-sample", "expected_behavior": [ "profiles the Python script with torch tracing enabled", "advises profiling a few steady-state iterations rather than a full run" From aafa1c29dd59822084610837671c6a2177f2e0a4 Mon Sep 17 00:00:00 2001 From: "fei.zheng" Date: Tue, 27 Oct 2026 14:27:31 -0600 Subject: [PATCH 4/6] Refresh the rocprof-compute skills from rocm-systems#14099 head 7a1a96c2. develop does not have that pull request yet, so the skills and shared sample fixture are imported from its head while each skill keeps its machine.yml for MI300 runner selection. Co-authored-by: Cursor --- .../.federated.json | 6 +- .../SKILL.md | 28 ++--- .../evals/evals.json | 3 +- skills/rocprof-compute-memory/.federated.json | 6 +- skills/rocprof-compute-memory/SKILL.md | 40 ++++--- .../rocprof-compute-memory/evals/evals.json | 2 +- .../.federated.json | 6 +- skills/rocprof-compute-pc-sampling/SKILL.md | 19 ++- .../rocprof-compute-roofline/.federated.json | 6 +- skills/rocprof-compute-roofline/SKILL.md | 52 ++++---- .../rocprof-compute-roofline/evals/evals.json | 4 +- skills/rocprof-compute-sample/.fixture.json | 6 +- skills/rocprof-compute-sample/simple_net.py | 111 ++++-------------- .../torch_compile_triton.py | 33 ++++++ .../torch_triton_net.py | 62 ---------- .../.federated.json | 6 +- .../rocprof-compute-speed-of-light/SKILL.md | 15 +-- .../.federated.json | 6 +- skills/rocprof-compute-torch-trace/SKILL.md | 56 ++++----- .../evals/evals.json | 12 +- 20 files changed, 192 insertions(+), 287 deletions(-) create mode 100644 skills/rocprof-compute-sample/torch_compile_triton.py delete mode 100644 skills/rocprof-compute-sample/torch_triton_net.py diff --git a/skills/rocprof-compute-kernel-bottleneck/.federated.json b/skills/rocprof-compute-kernel-bottleneck/.federated.json index 4012b52..1d1ceca 100644 --- a/skills/rocprof-compute-kernel-bottleneck/.federated.json +++ b/skills/rocprof-compute-kernel-bottleneck/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", + "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", "path": "projects/rocprofiler-compute/skills/rocprof-compute-kernel-bottleneck", "license": "MIT", - "content_hash": "fe9fd509a73ce21172bfbc7b7763fec7a4e222766d4f512abf5f3bd7dd1e2af7", - "imported_at": "2026-10-26T20:16:47Z" + "content_hash": "5562a6e11d160feed5215b5634ce3c9852422ee02673fd740a28636c7ba7ed99", + "imported_at": "2026-10-27T20:26:09Z" } diff --git a/skills/rocprof-compute-kernel-bottleneck/SKILL.md b/skills/rocprof-compute-kernel-bottleneck/SKILL.md index d1a2cbd..44e08f1 100644 --- a/skills/rocprof-compute-kernel-bottleneck/SKILL.md +++ b/skills/rocprof-compute-kernel-bottleneck/SKILL.md @@ -12,14 +12,16 @@ what limits it. Hand off to a focused skill once the limit is clear. ## Prerequisites Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Run `rocprof-compute profile --help` and `rocprof-compute analyze --help` before choosing flags. Options change between releases; do not guess one. +Never use the GUI or TUI. This is a command-line workflow. + ## Two sources of performance data rocprof-compute collects from two independent sources, and they answer @@ -53,7 +55,7 @@ rocprof-compute --specs it cannot succeed when they fail. Supported accelerators are listed in -[compatible-accelerators.rst](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst). +[compatible-accelerators.rst](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst). Not every feature works on every architecture. Check there before promising a result, and let the tool's own error message stand when a feature is unavailable. @@ -83,14 +85,14 @@ the path. Re-profiling into a non-empty directory needs `--overwrite`; prefer a fresh directory. Which GPU the application runs on is in -[Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Under Slurm, derive +[Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Under Slurm, derive `HIP_VISIBLE_DEVICES` from `ROCR_VISIBLE_DEVICES`, not from `SLURM_STEP_GPUS`; the indices differ. -See [profile mode](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for output layout, +See [profile mode](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for output layout, filtering, and the full option list. To profile a process that is already running, see -[live attach and detach](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/live_attach_detach.rst). +[live attach and detach](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/live_attach_detach.rst). ### Collect less when a full replay is too expensive @@ -131,8 +133,6 @@ contain that file, look one level down for the GPU model or rank directory. `Dispatch list`. The kernel index is what `-k` takes. Dispatch ids are 1-based and go to `-d`. -`--list-stats` cannot be used with `--output-format csv` or `db`. - Work on one kernel at a time: ```bash @@ -183,7 +183,7 @@ table. When theoretical occupancy is itself low, the limit is VGPR, SGPR, or LDS allocation. Scratch traffic points at register spilling. Low occupancy is not a defect when the kernel already saturates its limiting resource. Worked examples are in -[occupancy-limiters-example.rst](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/tutorial/includes/occupancy-limiters-example.rst). +[occupancy-limiters-example.rst](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/tutorial/includes/occupancy-limiters-example.rst). ### Scheduler and pipeline limits @@ -199,7 +199,7 @@ There is no single stall panel. Stall counters sit in the block that owns the hardware: LDS stalls in `lds`, cache stalls in `vl1d` and `l2`, fabric stalls in `l2_per_channel`. Open the block the Speed-of-Light result points at, and read what each counter means in the -[performance model](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). Ground every +[performance model](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). Ground every recommendation in a number you read from the report. ## 6. Normalization @@ -236,7 +236,7 @@ settings without stating that those differ. Terminal output is the default. Only choose a format when the user asks to save or export. `--output-format` takes `txt`, `csv`, or `db`, each of which disables terminal output, and `--output-name` sets the file name. See -[analysis output format](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). +[analysis output format](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). ## 9. When something looks wrong @@ -249,4 +249,4 @@ disables terminal output, and `--output-name` sets the file name. See | Roofline missing | not collected, or unsupported on this architecture | Stop and report permission, firmware, and ROCm version failures instead of -working around them. More in the [FAQ](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/faq.rst). +working around them. More in the [FAQ](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/faq.rst). diff --git a/skills/rocprof-compute-kernel-bottleneck/evals/evals.json b/skills/rocprof-compute-kernel-bottleneck/evals/evals.json index a13db25..5f8b68a 100644 --- a/skills/rocprof-compute-kernel-bottleneck/evals/evals.json +++ b/skills/rocprof-compute-kernel-bottleneck/evals/evals.json @@ -11,7 +11,8 @@ "runs analyze with --list-stats to find the hottest kernel" ], "unexpected_behavior": [ - "invents a profiling flag instead of checking --help" + "invents a profiling flag instead of checking --help", + "opens the GUI or TUI" ], "logs_contain": ["rocprof-compute profile", "--list-stats"] }, diff --git a/skills/rocprof-compute-memory/.federated.json b/skills/rocprof-compute-memory/.federated.json index dc824ad..f66531b 100644 --- a/skills/rocprof-compute-memory/.federated.json +++ b/skills/rocprof-compute-memory/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", + "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", "path": "projects/rocprofiler-compute/skills/rocprof-compute-memory", "license": "MIT", - "content_hash": "31924788226abe310a09b2ac495ddc0c14933a03b5b6f85fdadde1e1c7e941b4", - "imported_at": "2026-10-26T20:16:47Z" + "content_hash": "9fa3bfd584e474eb42135c301a57e2dbc85871dfc17477e5ea56a05abd6e03ea", + "imported_at": "2026-10-27T20:26:09Z" } diff --git a/skills/rocprof-compute-memory/SKILL.md b/skills/rocprof-compute-memory/SKILL.md index 52f2241..c01bc5f 100644 --- a/skills/rocprof-compute-memory/SKILL.md +++ b/skills/rocprof-compute-memory/SKILL.md @@ -11,12 +11,13 @@ bandwidth, caches, or data movement directly. ## Prerequisites Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Run `rocprof-compute analyze --help` before choosing flags. +Never use the GUI or TUI. ## 1. Start with the Memory Chart @@ -62,15 +63,15 @@ architecture with `rocprof-compute --list-blocks `. Each level has its own reference, with the counters, the formulas, and what they mean: -- [Vector L1 cache](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/cdna/vector-l1-cache.rst) -- [L2 cache](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/cdna/l2-cache.rst) -- [Local Data Share](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/cdna/local-data-share.rst) -- [Infinity Fabric transactions](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/tutorial/includes/infinity-fabric-transactions.rst) -- [Vector memory operation counting](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/tutorial/includes/vector-memory-operation-counting.rst) -- [LDS examples](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/tutorial/includes/lds-examples.rst) +- [Vector L1 cache](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/cdna/vector-l1-cache.rst) +- [L2 cache](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/cdna/l2-cache.rst) +- [Local Data Share](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/cdna/local-data-share.rst) +- [Infinity Fabric transactions](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/tutorial/includes/infinity-fabric-transactions.rst) +- [Vector memory operation counting](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/tutorial/includes/vector-memory-operation-counting.rst) +- [LDS examples](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/tutorial/includes/lds-examples.rst) Client APUs use a different hierarchy; see the -[RDNA performance model](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/rdna/rdna-performance-model.rst) +[RDNA performance model](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/rdna/rdna-performance-model.rst) for GL0, GL1, and GL2. Read hit rates and utilizations against the `Percent of Peak` and `Peak` @@ -89,25 +90,26 @@ What usually follows from what: | High fabric read or write stalls | traffic exceeds what the fabric can absorb | | Near-peak HBM with low cache hits | reduce traffic or add reuse; more bandwidth is not available | -## 4. Memory bandwidth analysis (experimental) +## 4. Memory bandwidth analysis (experimental, gfx950) A guided breakdown that walks the bandwidth tree and names the limiting level. Which GPUs it supports, and the experimental flag, are in -[memory bandwidth analysis](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/membw_analysis.rst). It needs -block 30 at both profile and analyze time: +[memory bandwidth analysis](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/membw_analysis.rst). It is +experimental and currently shipped for gfx950 only. It needs only the +memory chart (block 3) and block 30, so profile only those two blocks: ```bash -rocprof-compute profile --experimental --membw-analysis --name -- +rocprof-compute profile --experimental --membw-analysis -b 3 30 \ + --name -- -rocprof-compute analyze --path ./workloads// \ - --experimental --membw-analysis -k +rocprof-compute analyze --path ./workloads// -k ``` Its cutoffs and guidance text are data, not something to restate from memory. In a source checkout, read: -- [gfx950_membw_tree_spec.yaml](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/src/membw_analysis/tree_spec/gfx950_membw_tree_spec.yaml) -- [gfx950_membw_guidance.yaml](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/src/membw_analysis/tree_spec/gfx950_membw_guidance.yaml) +- [gfx950_membw_tree_spec.yaml](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/src/membw_analysis/tree_spec/gfx950_membw_tree_spec.yaml) +- [gfx950_membw_guidance.yaml](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/src/membw_analysis/tree_spec/gfx950_membw_guidance.yaml) A ROCm install copies the skills to `/share/rocprofiler-compute/skills/` and does not copy these YAML files next to them. When this skill was installed diff --git a/skills/rocprof-compute-memory/evals/evals.json b/skills/rocprof-compute-memory/evals/evals.json index 2794b37..0fb25fa 100644 --- a/skills/rocprof-compute-memory/evals/evals.json +++ b/skills/rocprof-compute-memory/evals/evals.json @@ -57,7 +57,7 @@ "prompt": "run the guided memory bandwidth analysis on my MI350 workload\n\nThe application is ./gl2_backpressure.hip.", "workspace": "../rocprof-compute-sample/membw_analysis_test_suite", "expected_behavior": [ - "uses --experimental --membw-analysis", + "profiles only blocks 3 and 30 with --experimental --membw-analysis", "notes that the analysis is experimental and shipped for gfx950", "reports the guidance the tool produced rather than substituting its own cutoffs" ], diff --git a/skills/rocprof-compute-pc-sampling/.federated.json b/skills/rocprof-compute-pc-sampling/.federated.json index 40134a7..c1ddd36 100644 --- a/skills/rocprof-compute-pc-sampling/.federated.json +++ b/skills/rocprof-compute-pc-sampling/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", + "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", "path": "projects/rocprofiler-compute/skills/rocprof-compute-pc-sampling", "license": "MIT", - "content_hash": "44c549984cab525586af8701a3ba86a77fc1485ab1400d823eb5f8137604042f", - "imported_at": "2026-10-26T20:16:47Z" + "content_hash": "e7302291aadaf366b7aaa3a7c47e3a368b9f2a8e24509c5dc86fb38ee318ae9e", + "imported_at": "2026-10-27T20:26:09Z" } diff --git a/skills/rocprof-compute-pc-sampling/SKILL.md b/skills/rocprof-compute-pc-sampling/SKILL.md index 40e4751..7333a89 100644 --- a/skills/rocprof-compute-pc-sampling/SKILL.md +++ b/skills/rocprof-compute-pc-sampling/SKILL.md @@ -12,21 +12,20 @@ have already identified the kernel worth this level of detail. PC sampling is experimental. Every command below needs `--experimental`, and option names can change between releases, so run `rocprof-compute profile --experimental --help` before choosing flags. +Never use the GUI or TUI. ## Prerequisites Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). This skill drives `rocprof-compute --pc-sampling`. A request to invoke `rocprofv3 --pc-sampling-beta-enabled` directly belongs to the rocprofiler-sdk PC sampling skill, not this one. Method support is in -[PC sampling](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). - -Never use the GUI or TUI. +[PC sampling](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). ## 1. Check what the hardware supports @@ -39,7 +38,7 @@ interval range. Two methods exist: - `stochastic` samples in hardware and is the only method that records a stall reason. Which GPUs accept it is in - [PC sampling](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). + [PC sampling](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). - `host_trap` records no stall reason, and its samples can land a little past the instruction responsible. @@ -107,7 +106,7 @@ there, and the fix is usually coalescing, prefetching, or more independent work between the load and its use. Stall reasons and what each one implies are listed in -[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). Do not interpret a stall +[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). Do not interpret a stall reason from host-trap data; it does not record one. ## 5. Export annotated disassembly @@ -130,4 +129,4 @@ accepts, rather than drawing conclusions from a handful of samples. For multi-process workloads, keep each process or rank separate; see the multi-process sections of -[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). +[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). diff --git a/skills/rocprof-compute-roofline/.federated.json b/skills/rocprof-compute-roofline/.federated.json index f927d29..8f21456 100644 --- a/skills/rocprof-compute-roofline/.federated.json +++ b/skills/rocprof-compute-roofline/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", + "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", "path": "projects/rocprofiler-compute/skills/rocprof-compute-roofline", "license": "MIT", - "content_hash": "9133ce94a28c1049fc1bbeca74234095bf50a1f4cd3a31dc18d2e1f9dd8489ca", - "imported_at": "2026-10-26T20:16:47Z" + "content_hash": "a7d8ed53d1cddb4db4700592435ade1fa42f6ff1e2b9442545cec53c8c1dbf4e", + "imported_at": "2026-10-27T20:26:09Z" } diff --git a/skills/rocprof-compute-roofline/SKILL.md b/skills/rocprof-compute-roofline/SKILL.md index 700a88f..feac0e3 100644 --- a/skills/rocprof-compute-roofline/SKILL.md +++ b/skills/rocprof-compute-roofline/SKILL.md @@ -10,18 +10,18 @@ ceilings, peak compute and peak bandwidth, and answers one question: which of the two is the limit. Run `rocprof-compute profile --help` and `rocprof-compute analyze --help` -before choosing flags. Do not open the generated HTML chart. The analyze table -carries the same numbers in readable form. +before choosing flags. Never use the GUI or TUI, and do not open the generated +HTML chart. The analyze table carries the same numbers in readable form. ## Prerequisites Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Roofline support by GPU is in -[supported hardware](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/what-is-rocprof-compute.rst). +[supported hardware](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/what-is-rocprof-compute.rst). ## 1. Collect @@ -36,14 +36,14 @@ which takes extra time. `--no-roof` skips them when the user only wants counters. On MI100 (gfx908), the roofline microbenchmarks are skipped and the profile -still succeeds. When [supported hardware](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/what-is-rocprof-compute.rst) +still succeeds. When [supported hardware](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/what-is-rocprof-compute.rst) shows no roofline support, do not ask the user to re-profile without `--no-roof`. Use Speed-of-Light and the `rocprof-compute-memory` skill instead. `--device` and `HIP_VISIBLE_DEVICES` are different selections. See -[Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). +[Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). -See [standalone roofline](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for `--roof-only` +See [standalone roofline](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for `--roof-only` and `--bench-only`, which run the microbenchmarks without profiling an application. @@ -85,23 +85,29 @@ at 45% of peak bandwidth" tells the user there is headroom; "memory-bound at ## 3. Pick the right precision and memory level -The table lists a peak for every precision and an arithmetic intensity for -every memory level. Compare against the peak row for the precision the kernel -actually issues, and the intensity row for the memory level in question. +Section 2 gives the verdict against the default ceilings. Refine it here when +the kernel does not use FP32, or when the question is about a specific level of +the hierarchy. -A mixed-precision or matrix kernel compared against the FP32 peak will look far -worse than it is. Read the precision from the instruction mix with `-b cu_ins` +```bash +# Ceilings for the precision the kernel actually uses +rocprof-compute analyze --path ./workloads// -k -b roof \ + -R FP16 BF16 + +# Ceilings for a specific level +rocprof-compute analyze --path ./workloads// -k -b roof \ + -m HBM L2 +``` + +`-R` defaults to FP32. A mixed-precision or matrix kernel compared against the +FP32 ceiling will look far worse than it is, so set `-R` to the precision the +kernel actually issues. Read that from the instruction mix with `-b cu_ins` rather than assuming it. Ask the user which precision the kernel is meant to use only when the mix is genuinely mixed and no single type dominates; they know the intent, the counters know what ran. -HBM answers "am I limited by main memory", while L2 and vL1D show whether -caches are absorbing the traffic. - -`--view table` prints one table for all kernels together instead of one per -kernel. To compare many kernels, write the -[analysis database](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst) with -`--output-format db` and query it with SQL. +`-m` defaults to every level. HBM answers "am I limited by main memory", while +L2 and vL1D show whether caches are absorbing the traffic. ## 4. When roofline data is missing @@ -112,4 +118,4 @@ kernel. To compare many kernels, write the | `roofline.csv` absent from the workload directory | the benchmark did not complete; check the profile log | The concepts behind the ceilings are in the -[performance model](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). +[performance model](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). diff --git a/skills/rocprof-compute-roofline/evals/evals.json b/skills/rocprof-compute-roofline/evals/evals.json index 23a8e7b..a0a0a5c 100644 --- a/skills/rocprof-compute-roofline/evals/evals.json +++ b/skills/rocprof-compute-roofline/evals/evals.json @@ -33,13 +33,13 @@ "prompt": "my half precision GEMM looks terrible on the roofline, is that right?\n\nThe application is ./wmma_gemm_fp16.hip.", "workspace": "../rocprof-compute-sample/wmma_gemm_fp16", "expected_behavior": [ - "compares against the peak for the precision the kernel actually issues instead of FP32", + "sets -R to the precision the kernel actually issues instead of the FP32 default", "confirms the precision from the instruction mix rather than assuming it" ], "unexpected_behavior": [ "compares a half precision kernel against the FP32 ceiling and calls it slow" ], - "logs_contain": ["cu_ins"] + "logs_contain": ["-R"] }, { "id": "roof-missing-data", diff --git a/skills/rocprof-compute-sample/.fixture.json b/skills/rocprof-compute-sample/.fixture.json index 5351a96..07cd954 100644 --- a/skills/rocprof-compute-sample/.fixture.json +++ b/skills/rocprof-compute-sample/.fixture.json @@ -1,8 +1,8 @@ { "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", + "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", "path": "projects/rocprofiler-compute/sample", - "content_hash": "e609e31067483504a04656c5850f0e471d7eedfd79efd6b07bc5161bacedf837", - "imported_at": "2026-10-26T21:12:24Z" + "content_hash": "cf4467621b5a59b554a8ef2cf4776b8b9fbadc59ebe060c7eea41acf46d0f097", + "imported_at": "2026-10-27T20:26:09Z" } diff --git a/skills/rocprof-compute-sample/simple_net.py b/skills/rocprof-compute-sample/simple_net.py index 6def8f2..828337d 100755 --- a/skills/rocprof-compute-sample/simple_net.py +++ b/skills/rocprof-compute-sample/simple_net.py @@ -2,123 +2,54 @@ # SPDX-License-Identifier: MIT """ -Sample PyTorch workload for rocprof-compute --torch-trace. +Sample PyTorch workload for simple network training. - rocprof-compute profile --experimental --torch-trace --no-roof \ - -n simple_net -- python3 ./simple_net.py +Prerequisites: + PyTorch must be installed with GPU support. Install via: + + ROCm: + pip install --no-cache-dir --pre torch --index-url \ + https://download.pytorch.org/whl/nightly/rocm7.2 + + See https://pytorch.org/get-started/locally/ for more options. - --user-range Wrap the run in a user ROCTX range. Requires ROCTX Python. - --same-line-linear Two Linear.forward calls from the same source line. - --no-backward Skip loss.backward(). Cannot combine with --backward-thread. - --backward-thread Run loss.backward() on a worker thread. Cannot combine - with --no-backward. +Usage: + rocprof-compute profile --experimental --torch-trace --no-roof \ + -n simple_net -- python3 ./simple_net.py """ -import argparse import sys -import threading import torch import torch.nn as nn import torch.nn.functional as F -try: - import roctx -except ImportError: - roctx = None - class SimpleNet(nn.Module): - def __init__(self, same_line_linear=False): + def __init__(self): super().__init__() self.fc1 = nn.Linear(10, 20) self.fc2 = nn.Linear(20, 10) - self.same_line_linear = same_line_linear def forward(self, x): - if self.same_line_linear: - # two Linear.forward calls on one source line - # fmt: off - x = self.fc1(x); x = self.fc2(x) # noqa: E702 - # fmt: on - return x x = self.fc1(x) x = F.relu(x) x = self.fc2(x) return x -def parse_args(argv=None): - parser = argparse.ArgumentParser(description="SimpleNet torch-trace sample") - parser.add_argument( - "--user-range", - action="store_true", - help="Wrap the run in a user ROCTX range", - ) - parser.add_argument( - "--same-line-linear", - action="store_true", - help="Two Linear.forward calls from the same source line", - ) - exclusive = parser.add_mutually_exclusive_group() - exclusive.add_argument( - "--no-backward", - action="store_true", - help="Skip loss.backward()", - ) - exclusive.add_argument( - "--backward-thread", - action="store_true", - help="Run loss.backward() on a worker thread", - ) - args = parser.parse_args(argv) - if args.user_range and roctx is None: - parser.error("--user-range requires ROCTX Python") - return args - - -def run_backward(loss, caught): - """Thread target: run loss.backward() and keep any exception in caught.""" - try: - loss.backward() - except BaseException: - caught.append(sys.exc_info()[1:]) - - -def backward_on_worker_thread(loss): - """Run loss.backward() on a worker thread and re-raise its exception here.""" - caught = [] - worker = threading.Thread(target=run_backward, args=(loss, caught)) - worker.start() - worker.join() - if caught: - err, tb = caught[0] - raise err.with_traceback(tb) - - -def main(argv=None): - args = parse_args(argv) +def main(): if not torch.cuda.is_available(): print("GPU is required for this sample. Exiting.") sys.exit(1) - if args.user_range: - roctx.rangePush("TrainingLoop_UserDefinedMarker") - - try: - model = SimpleNet(same_line_linear=args.same_line_linear).cuda() - x = torch.randn(5, 10).cuda() - - for _ in range(1): - output = model(x) - loss = output.sum() - if args.backward_thread: - backward_on_worker_thread(loss) - elif not args.no_backward: - loss.backward() - finally: - if args.user_range: - roctx.rangePop() + model = SimpleNet().cuda() + x = torch.randn(5, 10).cuda() + + for _ in range(1): + output = model(x) + loss = output.sum() + loss.backward() print("Training completed") diff --git a/skills/rocprof-compute-sample/torch_compile_triton.py b/skills/rocprof-compute-sample/torch_compile_triton.py new file mode 100644 index 0000000..154e51f --- /dev/null +++ b/skills/rocprof-compute-sample/torch_compile_triton.py @@ -0,0 +1,33 @@ +# Copyright (c) Advanced Micro Devices, Inc. +# SPDX-License-Identifier: MIT + +"""Minimal torch.compile workload that generates Triton kernels.""" + +import sys + +import torch + + +@torch.compile +def fused(x, y): + return torch.relu(x) * y + x + + +def main(): + if not torch.cuda.is_available(): + print("GPU is required for this sample. Exiting.") + sys.exit(1) + + x = torch.randn(4096, 4096, device="cuda") + y = torch.randn(4096, 4096, device="cuda") + + # First call compiles; later calls reuse the generated Triton kernels. + for _ in range(3): + fused(x, y) + + torch.cuda.synchronize() + print("Compiled workload completed") + + +if __name__ == "__main__": + main() diff --git a/skills/rocprof-compute-sample/torch_triton_net.py b/skills/rocprof-compute-sample/torch_triton_net.py deleted file mode 100644 index 4aa606d..0000000 --- a/skills/rocprof-compute-sample/torch_triton_net.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) Advanced Micro Devices, Inc. -# SPDX-License-Identifier: MIT - -""" -Sample mixed PyTorch + Triton workload for rocprof-compute --ml-api-trace. - - rocprof-compute profile --experimental --ml-api-trace --no-roof \ - -n torch_triton_net -- python3 ./torch_triton_net.py -""" - -import sys - -import torch -import torch.nn as nn -import torch.nn.functional as F -import triton -import triton.language as tl - -BLOCK = 1024 - - -@triton.jit -def scale_kernel(x_ptr, out_ptr, n, BLOCK_SIZE: tl.constexpr): - offs = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) - mask = offs < n - x = tl.load(x_ptr + offs, mask=mask) - tl.store(out_ptr + offs, x * 2.0, mask=mask) - - -def triton_scale(x): - out = torch.empty_like(x) - n = out.numel() - scale_kernel[(triton.cdiv(n, BLOCK),)](x, out, n, BLOCK_SIZE=BLOCK) - return out - - -class TorchTritonNet(nn.Module): - def __init__(self): - super().__init__() - self.fc = nn.Linear(64, 64) - - def forward(self, x): - x = self.fc(x) - x = F.relu(x) - return triton_scale(x) - - -def main(): - if not torch.cuda.is_available(): - print("GPU is required for this sample. Exiting.") - sys.exit(1) - - model = TorchTritonNet().cuda() - inp = torch.randn(64, 64, device="cuda") - for _ in range(3): - model(inp) - torch.cuda.synchronize() - print("Mixed torch/triton workload completed") - - -if __name__ == "__main__": - main() diff --git a/skills/rocprof-compute-speed-of-light/.federated.json b/skills/rocprof-compute-speed-of-light/.federated.json index d7b50f7..d0dcf11 100644 --- a/skills/rocprof-compute-speed-of-light/.federated.json +++ b/skills/rocprof-compute-speed-of-light/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", + "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", "path": "projects/rocprofiler-compute/skills/rocprof-compute-speed-of-light", "license": "MIT", - "content_hash": "c23e8f01d41e6c039424e3f7b8890674bbdd83546b19d424753b29711068a510", - "imported_at": "2026-10-26T20:16:47Z" + "content_hash": "56519a96def55a271ce94f529c0e0d9a0d01b062ae22b406b985f55fb029de81", + "imported_at": "2026-10-27T20:26:09Z" } diff --git a/skills/rocprof-compute-speed-of-light/SKILL.md b/skills/rocprof-compute-speed-of-light/SKILL.md index fa7189d..958b8d7 100644 --- a/skills/rocprof-compute-speed-of-light/SKILL.md +++ b/skills/rocprof-compute-speed-of-light/SKILL.md @@ -12,14 +12,15 @@ says whether a kernel is near any limit at all. ## Prerequisites Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). This skill reads an existing workload directory. It does not collect one. Run `rocprof-compute analyze --help` before choosing flags. +Never use the GUI or TUI. ## 1. Get the panel @@ -64,11 +65,11 @@ the neighbouring kernels, and a known-good baseline for the same workload. ## 3. Understand the metrics Each metric's definition and the counters behind it are in -[system-speed-of-light.rst](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/cdna/system-speed-of-light.rst) +[system-speed-of-light.rst](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/cdna/system-speed-of-light.rst) for CDNA accelerators and -[the RDNA equivalent](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/rdna/system-speed-of-light.rst) +[the RDNA equivalent](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/rdna/system-speed-of-light.rst) for client APUs. The whole metric hierarchy is in the -[performance model](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). +[performance model](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). ## 4. Compute Speed-of-Light diff --git a/skills/rocprof-compute-torch-trace/.federated.json b/skills/rocprof-compute-torch-trace/.federated.json index 88031c8..857ef04 100644 --- a/skills/rocprof-compute-torch-trace/.federated.json +++ b/skills/rocprof-compute-torch-trace/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "cd3fda14915e33a9c24f54d8eec55e1ccec03c0d", + "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", "path": "projects/rocprofiler-compute/skills/rocprof-compute-torch-trace", "license": "MIT", - "content_hash": "58e904a54b5e01db7527349c7e9dffc4c42d85a46c942d7d7717b199b6eb1f5e", - "imported_at": "2026-10-26T20:16:47Z" + "content_hash": "9a505c1c766ba6a753fcf8c65f220fa22b1215de211c78849d2f81b46134898d", + "imported_at": "2026-10-27T20:26:09Z" } diff --git a/skills/rocprof-compute-torch-trace/SKILL.md b/skills/rocprof-compute-torch-trace/SKILL.md index e331697..edf852a 100644 --- a/skills/rocprof-compute-torch-trace/SKILL.md +++ b/skills/rocprof-compute-torch-trace/SKILL.md @@ -12,23 +12,27 @@ traced back to the operator that launched it. Torch and Triton tracing are experimental. Every command needs `--experimental`, and option names can change between releases, so run `rocprof-compute profile --experimental --help` first. +Never use the GUI or TUI. ## Prerequisites Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). +[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), +[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), +[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), +and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). ## 1. Check the requirements PyTorch version coverage, the native collector versus reduced `TorchDispatchMode` coverage, and the abort when two ROCm installs load in one process are in the Torch trace section of -[profile mode](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Check the loader message +[profile mode](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Check the loader message before treating a missing backward operator as missing GPU work. +Confirm the PyTorch version before profiling. A mismatched version fails at +collection time, after the run has already cost the user its runtime. + ## 2. Collect ```bash @@ -39,15 +43,12 @@ rocprof-compute profile \ -- python ``` -For Triton kernels launched through Triton's Python runtime (`@triton.jit`): +For Triton kernels, including those generated by `torch.compile` and Inductor: ```bash rocprof-compute profile --experimental --triton-trace --name -- python ``` -`torch.compile` / Inductor fused kernels use PyTorch's static launcher, so they -do not get `|triton` ranges. Attribute them with `--torch-trace`. - The two combine, and `--ml-api-trace` turns on every supported framework backend at once. @@ -55,7 +56,7 @@ Tracing adds overhead on top of the usual counter replay. Profile a few iterations of a steady-state model rather than a full training run, and skip warmup. `ROCPROFCOMPUTE_ROCTX_DEEP_TENSOR_WRAPS=0` reduces instrumentation overhead by dropping the device and layout wraps; see -[profile mode](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for what that costs you. +[profile mode](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for what that costs you. Tracing cannot be combined with attaching to a running process, and cannot be combined with a PC-sampling-only profile. @@ -63,15 +64,10 @@ combined with a PC-sampling-only profile. When counters are being spread thin across many short operator kernels, `--iteration-multiplexing kernel` collects more per pass. -Profile preserves each pass's raw marker and counter CSVs with the -`ml_api_trace` prefix. - ## 3. List the captured operators -List when you need the operator names from this run; filtering does not -require a prior list. Do not pass `--list-*-operators` and `--*-operator` -on the same command; listing ignores the filter. These flags require -`--output-format stdout` (the default): +Always list before filtering. Operator names come from the model that ran, so +guessing a pattern wastes a profile: ```bash rocprof-compute analyze \ @@ -79,9 +75,8 @@ rocprof-compute analyze \ --experimental --list-torch-operators ``` -This prints the operators as a call tree sorted by GPU duration, with -`file:line` on each node that recorded a source location, plus kernel -launch statistics, so it also shows which operators are worth opening. +This prints the operators as a call tree grouped by source location, with +kernel launch statistics, so it also shows which operators are worth opening. The Triton equivalent is `--list-triton-operators`. @@ -90,20 +85,21 @@ The Triton equivalent is `--list-triton-operators`. ```bash rocprof-compute analyze \ --path ./workloads// \ - --experimental --torch-operator '*relu*' + --experimental --torch-operator '*conv2d*' ``` Patterns are shell-style globs, not regexes, matched against a `/`-separated -operator hierarchy. `*relu*` contains relu, `*/aten::addmm` is an addmm leaf -on the reconstructed path, and `all` or `'*'` matches everything. Several -patterns can be given, space or comma separated. +operator hierarchy. `*relu` ends with relu, `*conv*` contains conv, +`*/*functional*/*` matches an intermediate component, and `all` or `'*'` +matches everything. Several patterns can be given, space or comma separated. Quote patterns so the shell does not expand them. -`--triton-operator` works the same way. `*rmsnorm_kernel*` matches the -rmsnorm nodes on a Triton tree. +Combine with `-k` to intersect an operator with specific kernel ids, and with +`-b` to look at one metric block for that operator's kernels. `--triton-operator` +works the same way. Worked examples are in the PyTorch operator analysis section of -[the analyze CLI guide](https://github.com/ROCm/rocm-systems/blob/cd3fda14915e33a9c24f54d8eec55e1ccec03c0d/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). +[the analyze CLI guide](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). ## 5. Read the result @@ -122,7 +118,7 @@ annotate operators, which is what makes attribution possible. That is the only place ROCTx is involved. rocprof-compute does not support ROCTx-range-based profiling control, such as -starting, stopping, or resuming collection at a user-authored ROCTx range. -User-defined ranges in the marker CSV are labeled `user`. If the user wants -ROCTx start/stop control, say so plainly and offer kernel filtering with +starting, stopping, or resuming collection at a user-authored ROCTx range, and +does not attribute results to ranges a user added to their own application. If +that is what the user wants, say so plainly and offer kernel filtering with `-k` or dispatch filtering with `-d` instead. diff --git a/skills/rocprof-compute-torch-trace/evals/evals.json b/skills/rocprof-compute-torch-trace/evals/evals.json index 42ac53b..000147a 100644 --- a/skills/rocprof-compute-torch-trace/evals/evals.json +++ b/skills/rocprof-compute-torch-trace/evals/evals.json @@ -7,7 +7,7 @@ "workspace": "../rocprof-compute-sample", "expected_behavior": [ "collects with --experimental --torch-trace", - "lists the captured operators to find the slow ones", + "lists the captured operators before filtering", "checks the documented PyTorch version requirement" ], "unexpected_behavior": [ @@ -31,19 +31,17 @@ "skill_should_trigger": true, "prompt": "my model uses torch.compile, how do I attribute the generated Triton kernels?", "expected_behavior": [ - "uses --torch-trace, because --triton-trace does not capture kernels that torch.compile generates" + "uses --triton-trace, optionally combined with --torch-trace", + "lists the captured Triton kernels before filtering" ], - "unexpected_behavior": [ - "relies on --triton-trace alone to capture torch.compile kernels" - ], - "logs_contain": ["--torch-trace"] + "logs_contain": ["--triton-trace"] }, { "id": "tt-filter-by-operator", "skill_should_trigger": true, "prompt": "show me the counters for just the conv2d operators in this torch-trace workload", "expected_behavior": [ - "filters with --torch-operator", + "lists operators first, then filters with --torch-operator", "treats the pattern as a shell-style glob rather than a regex" ], "logs_contain": ["--torch-operator"] From d6b70d0d008445c79463f4fdcff9296d695b9826 Mon Sep 17 00:00:00 2001 From: "fei.zheng" Date: Tue, 27 Oct 2026 14:43:01 -0600 Subject: [PATCH 5/6] Vendor only the sample files the rocprof-compute evals use. The sample fixture copied the whole upstream sample tree (64 files), but the six skills' eval workspaces and prompts only name nine root sources, the gl2_backpressure case under membw_analysis_test_suite, and the wmma_gemm_fp16 build. mega_kernel, conjugate_gradient, dynamic_shared, the other membw cases, and the remaining root samples were never referenced. A fixture now declares the files to vendor, and the importer copies, hashes, and checks only those: the referenced sources, common.h (included by vmem/occupancy/lds and wmma main.cpp), and the wmma_gemm_fp16 Makefile and headers it needs to build. Workspace paths and the ../../sample rewrite are unchanged, so every workspace still resolves to an existing directory. Re-running the import against 7a1a96c2 reproduces the same 15 files and reports the fixture as up to date. Co-authored-by: Cursor --- .github/federation.json | 19 +- .github/scripts/federate_skills.py | 60 +- .github/scripts/test_federate_skills.py | 27 +- CONTRIBUTING.md | 2 +- skills/rocprof-compute-sample/.fixture.json | 4 +- .../conjugate_gradient/CMakeLists.txt | 38 - .../conjugate_gradient/README.md | 49 - .../conjugate_gradient/cg_args.hpp | 27 - .../conjugate_gradient/cg_spmv.hip | 39 - .../conjugate_gradient/cg_update.hip | 55 - .../conjugate_gradient/conjugate_gradient.cpp | 319 -- .../dynamic_shared/dynamic_shared.hip | 171 - .../dynamic_shared/example_utils.hpp | 281 -- skills/rocprof-compute-sample/fabric.hip | 292 -- skills/rocprof-compute-sample/instmix.hip | 89 - skills/rocprof-compute-sample/ipc.hip | 115 - skills/rocprof-compute-sample/laplace_eqn.hip | 410 -- .../mega_kernel/Makefile | 159 - .../mega_kernel/README.md | 672 --- .../mega_kernel/atomic_global_buffers.h | 33 - .../mega_kernel/main.cpp | 1014 ----- .../mega_kernel/mega_kernel.hip | 3865 ----------------- .../mega_kernel/mega_kernel_device_arch.h | 71 - .../mega_kernel/mega_kernel_host_arch.hpp | 64 - .../mega_kernel/mega_kernel_types.h | 124 - .../L1_stall_microbenchmark.hip | 134 - .../membw_analysis_test_suite/README.md | 52 - .../membw_analysis_test_suite/ea/Makefile | 15 - .../membw_analysis_test_suite/ea/README.md | 380 -- .../ea/ea_atomic_pressure.hip | 134 - .../ea/ea_fabric_bw.hip | 133 - .../ea/ea_hbm_read_bw.hip | 127 - .../ea/ea_hbm_write_bw.hip | 104 - .../membw_analysis_test_suite/ea/ea_io_bw.hip | 104 - .../ea/ea_rw_balance.hip | 113 - .../ea/ea_write_backpressure.hip | 112 - .../membw_analysis_test_suite/l2/README.md | 119 - .../l2/VERIFICATION_REPORT.md | 275 -- .../l2/l2_atomic_stress.hip | 132 - .../l2/l2_cache_thrash.hip | 136 - .../l2/l2_coherence_traffic.hip | 164 - .../l2/l2_hbm_read_bw_stress.hip | 140 - .../l2/l2_hbm_write_bw_stress.hip | 125 - .../l2/l2_io_stress.hip | 153 - .../l2/l2_multigpu_fabric.hip | 172 - .../l2/l2_normalized_throughput.hip | 136 - .../ta_tcp_stall.hip | 87 - .../membw_analysis_test_suite/utcl1_stall.hip | 87 - skills/rocprof-compute-sample/memcopy.cpp | 128 - skills/rocprof-compute-sample/rocflop.cpp | 742 ---- skills/rocprof-compute-sample/stack.hip | 20 - .../torch_compile_triton.py | 33 - skills/rocprof-compute-sample/triton_ffn.py | 179 - 53 files changed, 87 insertions(+), 12148 deletions(-) delete mode 100644 skills/rocprof-compute-sample/conjugate_gradient/CMakeLists.txt delete mode 100644 skills/rocprof-compute-sample/conjugate_gradient/README.md delete mode 100644 skills/rocprof-compute-sample/conjugate_gradient/cg_args.hpp delete mode 100644 skills/rocprof-compute-sample/conjugate_gradient/cg_spmv.hip delete mode 100644 skills/rocprof-compute-sample/conjugate_gradient/cg_update.hip delete mode 100644 skills/rocprof-compute-sample/conjugate_gradient/conjugate_gradient.cpp delete mode 100644 skills/rocprof-compute-sample/dynamic_shared/dynamic_shared.hip delete mode 100644 skills/rocprof-compute-sample/dynamic_shared/example_utils.hpp delete mode 100644 skills/rocprof-compute-sample/fabric.hip delete mode 100644 skills/rocprof-compute-sample/instmix.hip delete mode 100644 skills/rocprof-compute-sample/ipc.hip delete mode 100644 skills/rocprof-compute-sample/laplace_eqn.hip delete mode 100644 skills/rocprof-compute-sample/mega_kernel/Makefile delete mode 100644 skills/rocprof-compute-sample/mega_kernel/README.md delete mode 100644 skills/rocprof-compute-sample/mega_kernel/atomic_global_buffers.h delete mode 100644 skills/rocprof-compute-sample/mega_kernel/main.cpp delete mode 100644 skills/rocprof-compute-sample/mega_kernel/mega_kernel.hip delete mode 100644 skills/rocprof-compute-sample/mega_kernel/mega_kernel_device_arch.h delete mode 100644 skills/rocprof-compute-sample/mega_kernel/mega_kernel_host_arch.hpp delete mode 100644 skills/rocprof-compute-sample/mega_kernel/mega_kernel_types.h delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/L1_stall_microbenchmark.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/README.md delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/Makefile delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/README.md delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_atomic_pressure.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_fabric_bw.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_read_bw.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_write_bw.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_io_bw.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_rw_balance.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_write_backpressure.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/README.md delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/VERIFICATION_REPORT.md delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_atomic_stress.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_cache_thrash.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_coherence_traffic.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_read_bw_stress.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_write_bw_stress.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_io_stress.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_multigpu_fabric.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_normalized_throughput.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/ta_tcp_stall.hip delete mode 100644 skills/rocprof-compute-sample/membw_analysis_test_suite/utcl1_stall.hip delete mode 100644 skills/rocprof-compute-sample/memcopy.cpp delete mode 100644 skills/rocprof-compute-sample/rocflop.cpp delete mode 100644 skills/rocprof-compute-sample/stack.hip delete mode 100644 skills/rocprof-compute-sample/torch_compile_triton.py delete mode 100644 skills/rocprof-compute-sample/triton_ffn.py diff --git a/.github/federation.json b/.github/federation.json index bb53389..3fc7208 100644 --- a/.github/federation.json +++ b/.github/federation.json @@ -86,7 +86,24 @@ { "path": "projects/rocprofiler-compute/sample", "as": "rocprof-compute-sample", - "workspace_prefix": "../../sample" + "workspace_prefix": "../../sample", + "files": [ + "common.h", + "lds.hip", + "mat_mul_max.hip", + "occupancy.hip", + "simple_net.py", + "vcopy.cpp", + "vmem.hip", + "vrandom_access.cpp", + "vsequential_access.cpp", + "membw_analysis_test_suite/gl2_backpressure.hip", + "wmma_gemm_fp16/Makefile", + "wmma_gemm_fp16/main.cpp", + "wmma_gemm_fp16/wmma_gemm_fp16.hip", + "wmma_gemm_fp16/wmma_gemm_fp16.hpp", + "wmma_gemm_fp16/wmma_gemm_fp16_config.hpp" + ] } ], "skills": [ diff --git a/.github/scripts/federate_skills.py b/.github/scripts/federate_skills.py index e828fb5..d7e664c 100644 --- a/.github/scripts/federate_skills.py +++ b/.github/scripts/federate_skills.py @@ -32,8 +32,9 @@ not vendored: they are left out of the copy and of the content hash, and named in the run log and the pull request body. The one file kept when upstream lacks it is `evals/machine.yml` (`LOCAL_FALLBACK_FILES`). - A source may also declare `fixtures`: a directory that is not a skill - (the rocprofiler-compute sample tree) copied once under `skills//`. + A source may also declare `fixtures`: files from a directory that is not + a skill (the rocprofiler-compute sample tree) copied once under + `skills//`. Only the listed `files` are copied. Vendored `evals.json` workspace values that start with that fixture's `workspace_prefix` are rewritten to `../` so they resolve next to the skill. Upstream files are not modified. @@ -150,7 +151,7 @@ REPO_RE = re.compile(r"^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$") SOURCE_KEYS = {"repo", "license", "branch", "skills", "fixtures"} SKILL_KEYS = {"path", "as", "marketplace_description"} -FIXTURE_KEYS = {"path", "as", "workspace_prefix"} +FIXTURE_KEYS = {"path", "as", "workspace_prefix", "files"} FIXTURE_MARKER = ".fixture.json" # Keys that would pin a source to something other than a branch. They are # rejected with a pointed message rather than ignored, so an entry that tries @@ -183,12 +184,14 @@ class FixtureSpec: `workspace_prefix` is the path those skills use in their own repo (for example ``../../sample``). Vendored datasets are rewritten so the - same value resolves to ``../`` beside the skill. + same value resolves to ``../`` beside the skill. `files` are + the paths under `path` that get vendored; nothing else is copied. """ path: str local_name: str workspace_prefix: str + files: list[str] = field(default_factory=list) @dataclass @@ -379,11 +382,20 @@ def _parse_fixtures(raw: object, where: str) -> list[FixtureSpec]: f"{item_where}.workspace_prefix must be a relative path, got " f"{prefix!r}." ) + files = item.get("files") + if not isinstance(files, list) or not files: + raise ValueError( + f"{item_where}.files must list the fixture files to vendor." + ) fixtures.append( FixtureSpec( path=_parse_repo_path(item.get("path"), f"{item_where}.path"), local_name=local_name, workspace_prefix=prefix, + files=[ + _parse_repo_path(rel, f"{item_where}.files[{i}]") + for i, rel in enumerate(files) + ], ) ) return fixtures @@ -491,9 +503,7 @@ def shallow_clone(repo: str, branch: str, sub_paths: Iterable[str], dest: Path) return run(["git", "rev-parse", "HEAD"], cwd=dest) -def content_hash( - root: Path, exclude: Iterable[str] = (), *, max_bytes: int | None = MAX_FILE_BYTES -) -> str: +def content_hash(root: Path, exclude: Iterable[str] = ()) -> str: """Hash every file under `root` (relative paths and bytes). Change detection compares this against the hash stored in the vendored @@ -509,18 +519,15 @@ def content_hash( on a maintainer's machine and before it on a Linux runner, which would hash identical files to different digests. - Files over `max_bytes` are left out. Skill hashes use `MAX_FILE_BYTES`, - because those files are never vendored. A fixture hash passes `None` so a - large sample source still counts. `exclude` names relative paths to leave - out of the digest. + Files over `MAX_FILE_BYTES` are left out, since they are never vendored. + `exclude` names relative paths to leave out of the digest. """ skipped = set(exclude) entries = sorted( ( (path.relative_to(root).as_posix(), path) for path in root.rglob("*") - if path.is_file() - and (max_bytes is None or path.stat().st_size <= max_bytes) + if path.is_file() and path.stat().st_size <= MAX_FILE_BYTES ), key=lambda entry: entry[0], ) @@ -1012,6 +1019,16 @@ def rewrite_fixture_workspaces(skill_dir: Path, fixture: FixtureSpec) -> bool: return changed +def fixture_hash(root: Path, files: Iterable[str]) -> str: + """Hash the named files under `root`, in the same form as `content_hash`.""" + digest = hashlib.sha256() + for rel in sorted(files): + digest.update(rel.encode("utf-8")) + digest.update(b"\0") + digest.update(hashlib.sha256((root / rel).read_bytes()).digest()) + return digest.hexdigest() + + def sync_fixtures( source: Source, clone_dir: Path, @@ -1024,17 +1041,19 @@ def sync_fixtures( The fixture is not a skill: its marker is `.fixture.json`, so the undeclared-skill report does not treat the directory as a vendored skill. - Sample sources are copied whole, including files over `MAX_FILE_BYTES`. + Only the fixture's declared `files` are copied, so the vendored copy holds + what the eval workspaces use rather than the whole upstream tree. """ for fixture in source.fixtures: src = clone_dir / fixture.path - if not src.is_dir(): + missing = [rel for rel in fixture.files if not (src / rel).is_file()] + if missing: raise FileNotFoundError( - f"Fixture path {fixture.path!r} not found in " + f"Fixture file(s) {missing} not found under {fixture.path!r} in " f"{source.repo}@{branch}." ) dest = skills_dir / fixture.local_name - upstream_hash = content_hash(src, max_bytes=None) + upstream_hash = fixture_hash(src, fixture.files) marker_path = dest / FIXTURE_MARKER previous = {} if marker_path.is_file(): @@ -1056,7 +1075,9 @@ def sync_fixtures( else: if dest.exists(): shutil.rmtree(dest) - shutil.copytree(src, dest) + for rel in fixture.files: + (dest / rel).parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src / rel, dest / rel) marker = { "repo": source.repo, "ref": source.branch, @@ -1260,7 +1281,8 @@ def main(argv: list[str] | None = None) -> int: print( f"{source.repo}@{source.branch} {fixture.path} " f"-> skills/{fixture.local_name} " - f"(workspace {fixture.workspace_prefix})" + f"({len(fixture.files)} file(s), workspace " + f"{fixture.workspace_prefix})" ) print("") print(f"{args.catalog} is valid: {len(seen)} skill(s) declared.") diff --git a/.github/scripts/test_federate_skills.py b/.github/scripts/test_federate_skills.py index 15b608b..00e33f1 100644 --- a/.github/scripts/test_federate_skills.py +++ b/.github/scripts/test_federate_skills.py @@ -155,6 +155,7 @@ def test_parses_a_shared_fixture(self): "path": "projects/rocprofiler-compute/sample", "as": "rocprof-compute-sample", "workspace_prefix": "../../sample", + "files": ["vcopy.cpp", "wmma_gemm_fp16/Makefile"], } ] ) @@ -164,6 +165,19 @@ def test_parses_a_shared_fixture(self): self.assertEqual(fixture.path, "projects/rocprofiler-compute/sample") self.assertEqual(fixture.local_name, "rocprof-compute-sample") self.assertEqual(fixture.workspace_prefix, "../../sample") + self.assertEqual(fixture.files, ["vcopy.cpp", "wmma_gemm_fp16/Makefile"]) + + def test_rejects_a_fixture_without_files_or_with_dotdot(self): + base = { + "path": "projects/sample", + "as": "rocprof-compute-sample", + "workspace_prefix": "../../sample", + } + for files in (None, [], ["../secret.txt"]): + with self.subTest(files=files): + entry = dict(base) if files is None else {**base, "files": files} + with self.assertRaises(ValueError): + parse(one_source(fixtures=[entry])) def test_rejects_a_fixture_that_collides_with_a_skill(self): with self.assertRaises(ValueError): @@ -175,6 +189,7 @@ def test_rejects_a_fixture_that_collides_with_a_skill(self): "path": "projects/sample", "as": "rocprof-compute-sample", "workspace_prefix": "../../sample", + "files": ["vcopy.cpp"], } ], ) @@ -201,7 +216,7 @@ def test_rewrites_workspace_prefixes_once(self): self.assertNotIn("../../sample", text) self.assertFalse(fed.rewrite_fixture_workspaces(skill, fixture)) - def test_sync_fixtures_copies_the_tree_and_retargets_evals(self): + def test_sync_fixtures_copies_listed_files_and_retargets_evals(self): source = fed.Source( repo="ROCm/rocm-systems", license="MIT", @@ -212,6 +227,7 @@ def test_sync_fixtures_copies_the_tree_and_retargets_evals(self): path="projects/sample", local_name="rocprof-compute-sample", workspace_prefix="../../sample", + files=["vcopy.cpp", "wmma/Makefile"], ) ], ) @@ -219,7 +235,11 @@ def test_sync_fixtures_copies_the_tree_and_retargets_evals(self): root = Path(tmp) write_skill( root / "clone" / "projects" / "sample", - {"vcopy.cpp": "int main(){return 0;}\n"}, + { + "vcopy.cpp": "int main(){return 0;}\n", + "wmma/Makefile": "all:\n", + "mega_kernel/main.cpp": "int main(){return 1;}\n", + }, ) write_skill( root / "skills" / "demo", @@ -231,6 +251,9 @@ def test_sync_fixtures_copies_the_tree_and_retargets_evals(self): ) copied = root / "skills" / "rocprof-compute-sample" / "vcopy.cpp" self.assertEqual(copied.read_text(encoding="utf-8"), "int main(){return 0;}\n") + vendored = root / "skills" / "rocprof-compute-sample" + self.assertTrue((vendored / "wmma" / "Makefile").is_file()) + self.assertFalse((vendored / "mega_kernel").exists()) marker = json.loads( (root / "skills" / "rocprof-compute-sample" / ".fixture.json").read_text( encoding="utf-8" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ce58d7..8e6c1cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,7 +101,7 @@ one repo can federate as many skills as it likes from wherever they live: | `branch` | Optional. The branch to track, or a release pattern such as `release/*`. Defaults to `main` | | `skills[].path` | Path of the skill folder inside your repo, from the repo root | | `skills[].as` | Optional local catalog name; use it to namespace as `-` so names stay unique | -| `fixtures[]` | Optional directory that is not a skill, copied once to `skills//`. `workspace_prefix` is the relative `workspace` value in the upstream datasets (`../../sample`). Vendored datasets are rewritten to `../` | +| `fixtures[]` | Optional directory that is not a skill. Only the paths in `files` are copied, once, to `skills//`. `workspace_prefix` is the relative `workspace` value in the upstream datasets (`../../sample`). Vendored datasets are rewritten to `../` | There is no tag or commit field. A source always follows a branch. diff --git a/skills/rocprof-compute-sample/.fixture.json b/skills/rocprof-compute-sample/.fixture.json index 07cd954..8ca5229 100644 --- a/skills/rocprof-compute-sample/.fixture.json +++ b/skills/rocprof-compute-sample/.fixture.json @@ -3,6 +3,6 @@ "ref": "develop", "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", "path": "projects/rocprofiler-compute/sample", - "content_hash": "cf4467621b5a59b554a8ef2cf4776b8b9fbadc59ebe060c7eea41acf46d0f097", - "imported_at": "2026-10-27T20:26:09Z" + "content_hash": "b2d249ac299e4149cc1734bf76f7f3696ce21d6046aa9591d4a82a2af0b306be", + "imported_at": "2026-10-27T20:42:31Z" } diff --git a/skills/rocprof-compute-sample/conjugate_gradient/CMakeLists.txt b/skills/rocprof-compute-sample/conjugate_gradient/CMakeLists.txt deleted file mode 100644 index 867f35f..0000000 --- a/skills/rocprof-compute-sample/conjugate_gradient/CMakeLists.txt +++ /dev/null @@ -1,38 +0,0 @@ -# The executable and its two kernel libraries are colocated in one directory so -# the driver can resolve the libraries relative to its own path. -set(CG_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/tests/conjugate_gradient) - -# Each kernel is its own shared library: kernels compiled into a single binary -# share one fat binary, and so one code object, which is exactly what this -# sample needs to avoid. -foreach(CG_KERNEL_LIBRARY IN ITEMS cg_spmv cg_update) - set_source_files_properties(${CG_KERNEL_LIBRARY}.hip PROPERTIES LANGUAGE HIP) - add_library(${CG_KERNEL_LIBRARY} SHARED ${CG_KERNEL_LIBRARY}.hip) - target_include_directories(${CG_KERNEL_LIBRARY} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) - # PC sampling maps samples back to source lines, which needs debug info. - target_compile_options(${CG_KERNEL_LIBRARY} PRIVATE -g) - set_target_properties( - ${CG_KERNEL_LIBRARY} - PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CG_OUTPUT_DIRECTORY} - ) -endforeach() - -set_source_files_properties(conjugate_gradient.cpp PROPERTIES LANGUAGE HIP) -add_executable(conjugate_gradient conjugate_gradient.cpp) -target_include_directories(conjugate_gradient PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_compile_options(conjugate_gradient PRIVATE -g) -target_link_libraries(conjugate_gradient PRIVATE ${CMAKE_DL_LIBS}) -set_target_properties( - conjugate_gradient - PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CG_OUTPUT_DIRECTORY} -) - -# The libraries are opened by name at runtime, not linked, so CMake supplies the -# file names and a target rename cannot desync them from the source. -target_compile_definitions( - conjugate_gradient - PRIVATE - CG_SPMV_LIBRARY="$" - CG_UPDATE_LIBRARY="$" -) -add_dependencies(conjugate_gradient cg_spmv cg_update) diff --git a/skills/rocprof-compute-sample/conjugate_gradient/README.md b/skills/rocprof-compute-sample/conjugate_gradient/README.md deleted file mode 100644 index cab9680..0000000 --- a/skills/rocprof-compute-sample/conjugate_gradient/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Multiprocess Conjugate-Gradient Sample - -A PC sampling workload in which the same kernel carries a different -process-local code-object ID in each of two processes. - -Two conjugate-gradient-inspired kernels are built into separate shared -libraries: - -- `kernel_spmv_csr` performs sparse matrix-vector multiplication over a CSR - matrix with power-law row lengths. -- `kernel_cg_update_reduce` updates `x` and `r`, then performs a block-local - residual reduction. - -The driver forks once, and both processes run both kernels in opposite order. -HIP defers loading a code object until the first launch out of it, so the -process that runs SpMV first gives it the lower ID and the other process gives -it the higher one. That divergence is what makes the pair useful for checking -that sample attribution keys on the process as well as the code object. - -The kernels live in separate libraries rather than in the driver because -kernels compiled into one binary share a single fat binary, which the runtime -loads as one code object holding both symbols. - -This is not a convergent CG solver or a correctness benchmark: the processes do -not exchange results, and kernel outputs are neither copied back nor verified. - -## Build - -From the repository root, run: - -```bash -cmake -S . -B build -DENABLE_TESTS=ON -cmake --build build --target conjugate_gradient --parallel -``` - -The build populates `tests/conjugate_gradient/` with the driver and the two -kernel libraries. CMake selects the GPU architecture; when cross-compiling or -building without a GPU, set it explicitly with -`-DCMAKE_HIP_ARCHITECTURES=gfx942` or similar. - -## Run - -The workload takes no arguments: - -```bash -./tests/conjugate_gradient/conjugate_gradient -``` - -Each process prints its PID, its role, and the order it launched the kernels in. diff --git a/skills/rocprof-compute-sample/conjugate_gradient/cg_args.hpp b/skills/rocprof-compute-sample/conjugate_gradient/cg_args.hpp deleted file mode 100644 index a40a646..0000000 --- a/skills/rocprof-compute-sample/conjugate_gradient/cg_args.hpp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -#pragma once - -#include - -inline constexpr std::uint32_t cg_block_size = 256; -inline constexpr std::uint32_t cg_rows = 65536; - -struct CgArgs -{ - const std::uint32_t* row_offsets; - const std::uint32_t* column_indices; - const float* values; - const float* p; - float* q; - float* x; - float* r; - float* partials; - std::uint32_t rows; - std::uint32_t rounds; -}; - -// Every kernel library exports this one symbol, so the driver resolves the same -// name against each handle instead of tracking a symbol name per library. -using CgLaunch = void (*)(const CgArgs&); diff --git a/skills/rocprof-compute-sample/conjugate_gradient/cg_spmv.hip b/skills/rocprof-compute-sample/conjugate_gradient/cg_spmv.hip deleted file mode 100644 index 0b97a3a..0000000 --- a/skills/rocprof-compute-sample/conjugate_gradient/cg_spmv.hip +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// Built as its own shared library so that the runtime loads it as a code object -// separate from the one holding kernel_cg_update_reduce. - -#include "cg_args.hpp" - -#include - -#include - -// extern "C" keeps the symbol unmangled, so profiler output names the kernel -// the same way this file does. -extern "C" __global__ __launch_bounds__(cg_block_size) void kernel_spmv_csr(CgArgs arguments) -{ - const std::uint32_t row = blockIdx.x * blockDim.x + threadIdx.x; - if (row >= arguments.rows) - { - return; - } - - for (std::uint32_t round = 0; round < arguments.rounds; ++round) - { - float sum = 0.0F; - for (std::uint32_t entry = arguments.row_offsets[row]; entry < arguments.row_offsets[row + 1]; - ++entry) - { - sum += arguments.values[entry] * arguments.p[arguments.column_indices[entry]]; - } - arguments.q[row] = sum; - } -} - -extern "C" void cg_launch(const CgArgs& arguments) -{ - const std::uint32_t grid_size = (arguments.rows + cg_block_size - 1) / cg_block_size; - hipLaunchKernelGGL(kernel_spmv_csr, dim3(grid_size), dim3(cg_block_size), 0, nullptr, arguments); -} diff --git a/skills/rocprof-compute-sample/conjugate_gradient/cg_update.hip b/skills/rocprof-compute-sample/conjugate_gradient/cg_update.hip deleted file mode 100644 index 4e96c23..0000000 --- a/skills/rocprof-compute-sample/conjugate_gradient/cg_update.hip +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// Built as its own shared library so that the runtime loads it as a code object -// separate from the one holding kernel_spmv_csr. - -#include "cg_args.hpp" - -#include - -#include - -// extern "C" keeps the symbol unmangled, so profiler output names the kernel -// the same way this file does. -extern "C" __global__ __launch_bounds__(cg_block_size) void kernel_cg_update_reduce(CgArgs arguments) -{ - __shared__ float reduction_values[cg_block_size]; - - const std::uint32_t row = blockIdx.x * blockDim.x + threadIdx.x; - for (std::uint32_t round = 0; round < arguments.rounds; ++round) - { - float squared_residual = 0.0F; - if (row < arguments.rows) - { - const float alpha = 0.5F + static_cast(round) * 1.0E-6F; - arguments.x[row] += alpha * arguments.p[row]; - arguments.r[row] -= alpha * arguments.q[row]; - squared_residual = arguments.r[row] * arguments.r[row]; - } - - reduction_values[threadIdx.x] = squared_residual; - __syncthreads(); - - for (std::uint32_t stride = cg_block_size / 2; stride > 0; stride /= 2) - { - if (threadIdx.x < stride) - { - reduction_values[threadIdx.x] += reduction_values[threadIdx.x + stride]; - } - __syncthreads(); - } - - if (threadIdx.x == 0) - { - arguments.partials[blockIdx.x] = reduction_values[0]; - } - } -} - -extern "C" void cg_launch(const CgArgs& arguments) -{ - const std::uint32_t grid_size = (arguments.rows + cg_block_size - 1) / cg_block_size; - hipLaunchKernelGGL( - kernel_cg_update_reduce, dim3(grid_size), dim3(cg_block_size), 0, nullptr, arguments); -} diff --git a/skills/rocprof-compute-sample/conjugate_gradient/conjugate_gradient.cpp b/skills/rocprof-compute-sample/conjugate_gradient/conjugate_gradient.cpp deleted file mode 100644 index a1d947b..0000000 --- a/skills/rocprof-compute-sample/conjugate_gradient/conjugate_gradient.cpp +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// Two processes run the same two conjugate-gradient kernels, each process -// loading them in the opposite order, so a given kernel ends up with a -// different process-local code-object ID in each one. -// -// The kernels live in two shared libraries rather than in this binary. Kernels -// compiled into one binary share a single fat binary, so the runtime would load -// them as one code object holding both symbols and there would be nothing to -// tell apart. -// -// HIP defers loading a code object until the first launch out of it, so the -// launch order is what assigns the IDs. The libraries are opened in that same -// order as well, which keeps the split intact when deferred loading is off. -// -// The child re-execs itself so the profiler's runtime hooks initialize inside -// it; a plain fork() is not enough for the child to be picked up. - -#include "cg_args.hpp" - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace -{ -// CMake supplies the file names, so renaming a library target cannot desync it -// from this source. -constexpr std::array cg_kernel_libraries = {CG_SPMV_LIBRARY, CG_UPDATE_LIBRARY}; - -// SpMV does far more work per round than the update/reduce kernel, so the two -// counts are tuned separately. Both land near 15 ms on MI350X, which is a few -// dozen intervals at the 512 us host-trap default, so neither kernel can slip -// between samples. -constexpr std::array cg_kernel_rounds = {200, 15000}; - -constexpr const char* cg_launch_symbol = "cg_launch"; - -// execv() passes this as argv[0], which is how the re-execed child knows its -// role without needing a command-line option. -constexpr const char* cg_child_marker = "cg-child"; - -constexpr std::uint32_t cg_seed = 12345; - -void check_hip(hipError_t error, const char* action) -{ - if (error != hipSuccess) - { - throw std::runtime_error(std::string(action) + ": " + hipGetErrorString(error)); - } -} - -std::string executable_directory() -{ - std::string path(4096, '\0'); - const ssize_t length = ::readlink("/proc/self/exe", path.data(), path.size()); - if (length <= 0 || static_cast(length) >= path.size()) - { - throw std::runtime_error("cannot resolve /proc/self/exe: " + - std::string(std::strerror(errno))); - } - path.resize(static_cast(length)); - return path.substr(0, path.find_last_of('/')); -} - -// Owns a dlopen handle and the launch entry point resolved out of it. -class KernelLibrary -{ -public: - explicit KernelLibrary(const std::string& path) - { - handle_ = ::dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); - if (handle_ == nullptr) - { - throw std::runtime_error("dlopen(" + path + "): " + std::string(::dlerror())); - } - launch_ = reinterpret_cast(::dlsym(handle_, cg_launch_symbol)); - if (launch_ == nullptr) - { - throw std::runtime_error("dlsym(" + std::string(cg_launch_symbol) + ") in " + path); - } - } - - ~KernelLibrary() - { - if (handle_ != nullptr) - { - (void)::dlclose(handle_); - } - } - - KernelLibrary(const KernelLibrary&) = delete; - KernelLibrary& operator=(const KernelLibrary&) = delete; - - void launch(const CgArgs& arguments) const { launch_(arguments); } - -private: - void* handle_ = nullptr; - CgLaunch launch_ = nullptr; -}; - -template -class DeviceBuffer -{ -public: - explicit DeviceBuffer(const std::vector& host_data) - { - const std::size_t bytes = host_data.size() * sizeof(Value); - check_hip(hipMalloc(reinterpret_cast(&pointer_), bytes), "hipMalloc"); - check_hip(hipMemcpy(pointer_, host_data.data(), bytes, hipMemcpyHostToDevice), - "hipMemcpy to device"); - } - - ~DeviceBuffer() - { - if (pointer_ != nullptr) - { - (void)hipFree(pointer_); - } - } - - DeviceBuffer(const DeviceBuffer&) = delete; - DeviceBuffer& operator=(const DeviceBuffer&) = delete; - - Value* get() const { return pointer_; } - -private: - Value* pointer_ = nullptr; -}; - -struct HostWorkload -{ - std::vector row_offsets; - std::vector column_indices; - std::vector values; - std::vector p; - std::vector q; - std::vector x; - std::vector r; - std::vector partials; -}; - -// A CSR matrix with power-law row lengths, which gives the SpMV kernel uneven -// per-row work and so a more interesting sample distribution than a dense band. -HostWorkload make_host_workload(std::uint32_t partial_count) -{ - HostWorkload workload; - workload.row_offsets.resize(static_cast(cg_rows) + 1); - workload.column_indices.reserve(static_cast(cg_rows) * 8); - workload.values.reserve(static_cast(cg_rows) * 8); - workload.p.resize(cg_rows); - workload.q.resize(cg_rows); - workload.x.assign(cg_rows, 0.0F); - workload.r.resize(cg_rows); - workload.partials.assign(partial_count, 0.0F); - - std::mt19937 random_generator(cg_seed); - std::uniform_real_distribution uniform_distribution(0.0, 1.0); - for (std::uint32_t row = 0; row < cg_rows; ++row) - { - const double uniform_value = uniform_distribution(random_generator); - const int row_nonzeros = - std::clamp(static_cast(4.0 / std::pow(1.0 - 0.999 * uniform_value, 0.7)), 1, 512); - for (int entry = 0; entry < row_nonzeros; ++entry) - { - workload.column_indices.push_back(random_generator() % cg_rows); - workload.values.push_back(0.5F + static_cast(entry % 7) * 0.1F); - } - workload.row_offsets[row + 1] = static_cast(workload.column_indices.size()); - } - - for (std::uint32_t row = 0; row < cg_rows; ++row) - { - workload.p[row] = 1.0F / static_cast(1 + row % 13); - workload.q[row] = 0.5F + static_cast(row % 7) * 0.1F; - workload.r[row] = 0.25F + static_cast(row % 5) * 0.1F; - } - return workload; -} - -// Role 0 runs SpMV first, role 1 runs update/reduce first. Both roles run both -// kernels; only the order differs. -std::array library_order(int role) -{ - if (role == 0) - { - return {0, 1}; - } - return {1, 0}; -} - -int run_role(int role) -{ - const std::string directory = executable_directory(); - const auto order = library_order(role); - - std::array, 2> libraries; - for (std::size_t position = 0; position < order.size(); ++position) - { - libraries[position] = - std::make_unique(directory + "/" + cg_kernel_libraries[order[position]]); - } - - const std::uint32_t grid_size = (cg_rows + cg_block_size - 1) / cg_block_size; - const HostWorkload host = make_host_workload(grid_size); - DeviceBuffer row_offsets(host.row_offsets); - DeviceBuffer column_indices(host.column_indices); - DeviceBuffer values(host.values); - DeviceBuffer p(host.p); - DeviceBuffer q(host.q); - DeviceBuffer x(host.x); - DeviceBuffer r(host.r); - DeviceBuffer partials(host.partials); - - CgArgs arguments{ - row_offsets.get(), - column_indices.get(), - values.get(), - p.get(), - q.get(), - x.get(), - r.get(), - partials.get(), - cg_rows, - 0, - }; - - std::string launched; - for (std::size_t position = 0; position < order.size(); ++position) - { - const std::size_t library_index = order[position]; - arguments.rounds = cg_kernel_rounds[library_index]; - libraries[position]->launch(arguments); - launched += (position == 0 ? "" : ","); - launched += cg_kernel_libraries[library_index]; - } - check_hip(hipDeviceSynchronize(), "hipDeviceSynchronize"); - - std::cout << "pid=" << ::getpid() << " role=" << role << " order=" << launched << '\n' - << std::flush; - return EXIT_SUCCESS; -} - -int run_parent() -{ - const pid_t child_process = ::fork(); - if (child_process < 0) - { - std::cerr << "conjugate_gradient: fork failed: " << std::strerror(errno) << '\n'; - return EXIT_FAILURE; - } - if (child_process == 0) - { - char* const child_arguments[] = {const_cast(cg_child_marker), nullptr}; - ::execv("/proc/self/exe", child_arguments); - std::cerr << "conjugate_gradient: execv failed: " << std::strerror(errno) << '\n'; - std::cerr.flush(); - ::_exit(127); - } - - // The parent makes no HIP call before fork(), so the child inherits a clean - // runtime state. - const int parent_status = run_role(0); - - int status = 0; - pid_t wait_result = 0; - do - { - wait_result = ::waitpid(child_process, &status, 0); - } - while (wait_result < 0 && errno == EINTR); - - if (wait_result < 0) - { - std::cerr << "conjugate_gradient: waitpid failed: " << std::strerror(errno) << '\n'; - return EXIT_FAILURE; - } - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - { - std::cerr << "conjugate_gradient: child " << child_process << " did not exit cleanly\n"; - return EXIT_FAILURE; - } - return parent_status; -} -} // namespace - -int main(int argument_count, char* arguments[]) -{ - try - { - if (argument_count > 0 && std::strcmp(arguments[0], cg_child_marker) == 0) - { - return run_role(1); - } - return run_parent(); - } - catch (const std::exception& error) - { - std::cerr << "conjugate_gradient: " << error.what() << '\n'; - return EXIT_FAILURE; - } -} diff --git a/skills/rocprof-compute-sample/dynamic_shared/dynamic_shared.hip b/skills/rocprof-compute-sample/dynamic_shared/dynamic_shared.hip deleted file mode 100644 index 15930ed..0000000 --- a/skills/rocprof-compute-sample/dynamic_shared/dynamic_shared.hip +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -#include "example_utils.hpp" - -#include - -#include -#include - -#include -#include -#include - -/// \brief A simple matrix transpose kernel that using dynamic shared memory. -/// - The number of rows in the input and output matrices is equal, and given by the \p width parameter. -/// - Each thread in the grid is responsible for one element of the input and output matrices. -/// - Because the transposition is computed in shared memory, which cannot be accessed between different -/// blocks, the matrix has to be processed by a single block. -__global__ void matrix_transpose_kernel(float* out, const float* in, const unsigned int width) -{ - // Declare that this kernel is using dynamic shared memory to store a number of floats. - // The unsized array type indicates that the total amount of memory that is going - // to be used here is not known ahead of time, and will be computed at runtime and - // passed to the kernel launch function. - extern __shared__ float shared_matrix_memory[]; - - // Compute the row and column index of the element this thread is going to process. - const unsigned int x = blockDim.x * blockIdx.x + threadIdx.x; - const unsigned int y = blockDim.y * blockIdx.y + threadIdx.y; - - // Perform the transpose by reading an element of the input matrix from global memory and - // by storing it in the tranposed index in shared memory. - shared_matrix_memory[y * width + x] = in[x * width + y]; - - // Synchronization is required to make sure that all threads have written - // their part of the input matrix to the shared memory, before the values - // are read by another thread. - __syncthreads(); - - // Copy the transposed matrix from shared memory to the output array, which - // is in global memory. - out[y * width + x] = shared_matrix_memory[y * width + x]; -} - -// CPU implementation of matrix transpose -std::vector matrix_transpose_reference(const std::vector& input, - const unsigned int width) -{ - std::vector output(width * width); - for(unsigned int j = 0; j < width; j++) - { - for(unsigned int i = 0; i < width; i++) - { - output[i * width + j] = input[j * width + i]; - } - } - return output; -} - -// argv: Array of command-line arguments. Run with "--enable-sleep" to enable -// the mode with delay implemented by thread sleep -int main(int argc, char* argv[]) -{ - bool enable_sleep = false; - - // Check command-line arguments - for(int i = 1; i < argc; ++i) - { - std::string arg = argv[i]; - if(arg == "--enable-sleep") - { - enable_sleep = true; - } - } - - constexpr unsigned int width = 4; - constexpr unsigned int threads_per_block_x = width; - constexpr unsigned int threads_per_block_y = width; - constexpr unsigned int size = width * width; - constexpr size_t size_bytes = sizeof(float) * size; - constexpr size_t shared_memory_bytes = size_bytes; - - std::cout << "Run transpose continuously" << std::endl; - - if(enable_sleep) - std::this_thread::sleep_for(std::chrono::seconds(30)); - - unsigned int pass_count = 0; - unsigned int fail_count = 0; - unsigned int cycle_count = 0; - - constexpr float eps = 1.0E-6f; - - while (true) - { - if(enable_sleep) - std::this_thread::sleep_for(std::chrono::seconds(5)); - - // Allocate host vectors - std::vector h_matrix(size); - std::vector h_transposed_matrix(size); - - // Set up input data - for(unsigned int i = 0; i < size; i++) - { - h_matrix[i] = i * 10.0f; - } - - // Allocate device memory - float* d_matrix{}; - float* d_transposed_matrix{}; - HIP_CHECK(hipMalloc(&d_matrix, size_bytes)); - HIP_CHECK(hipMalloc(&d_transposed_matrix, size_bytes)); - - // Copy input to device - HIP_CHECK(hipMemcpy(d_matrix, h_matrix.data(), size_bytes, hipMemcpyHostToDevice)); - - // Launch kernel - matrix_transpose_kernel<<>>(d_transposed_matrix, d_matrix, width); - - HIP_CHECK(hipGetLastError()); - - // Copy result back - HIP_CHECK(hipMemcpy(h_transposed_matrix.data(), - d_transposed_matrix, - size_bytes, - hipMemcpyDeviceToHost)); - - // Free device memory - HIP_CHECK(hipFree(d_matrix)); - HIP_CHECK(hipFree(d_transposed_matrix)); - - // CPU reference transpose - std::vector ref_transposed_matrix = matrix_transpose_reference(h_matrix, width); - - // Validate - unsigned int errors = 0; - for(unsigned int i = 0; i < size; i++) - { - if(std::fabs(h_transposed_matrix[i] - ref_transposed_matrix[i]) > eps) - { - errors++; - } - } - - // Update pass/fail counters - if(errors == 0) - pass_count++; - else - fail_count++; - - cycle_count++; - - // Every 10000 cycles, print summary and reset counters - if(cycle_count == 10000) - { - std::cout << "10000 Validation cycles completed: " - << "Passes = " << pass_count - << ", Failures = " << fail_count << std::endl; - - // Reset counters - cycle_count = 0; - pass_count = 0; - fail_count = 0; - } - } -} diff --git a/skills/rocprof-compute-sample/dynamic_shared/example_utils.hpp b/skills/rocprof-compute-sample/dynamic_shared/example_utils.hpp deleted file mode 100644 index 0b76bc8..0000000 --- a/skills/rocprof-compute-sample/dynamic_shared/example_utils.hpp +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -#ifndef COMMON_EXAMPLE_UTILS_HPP -#define COMMON_EXAMPLE_UTILS_HPP - -// Compiling HIP on Windows includes windows.h, and this triggers many silly warnings. -#include -#if defined(_WIN32) && defined(__NVCC__) - #pragma nv_diag_suppress 108 // signed bit field of length 1 - #pragma nv_diag_suppress 174 // expression has no effect - #pragma nv_diag_suppress 1835 // attribute "dllimport" does not apply here -#endif - -// rocPRIM adds a #warning about printf on NAVI. -#ifdef __clang__ - #pragma clang diagnostic ignored "-W#warnings" -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -constexpr int error_exit_code = -1; - -/// \brief Checks if the provided error code is \p hipSuccess and if not, -/// prints an error message to the standard error output and terminates the program -/// with an error code. -#define HIP_CHECK(condition) \ - { \ - const hipError_t error = condition; \ - if(error != hipSuccess) \ - { \ - std::cerr << "An error encountered: \"" << hipGetErrorString(error) << "\" at " \ - << __FILE__ << ':' << __LINE__ << std::endl; \ - std::exit(error_exit_code); \ - } \ - } - -/// \brief Formats a range of elements to a pretty string. -/// \tparam BidirectionalIterator - must implement the BidirectionalIterator concept and -/// must be dereferencable in host code. Its value type must be formattable to -/// \p std::ostream. -template -inline std::string format_range(const BidirectionalIterator begin, const BidirectionalIterator end) -{ - std::stringstream sstream; - sstream << "[ "; - for(auto it = begin; it != end; ++it) - { - sstream << *it; - if(it != std::prev(end)) - { - sstream << ", "; - } - } - sstream << " ]"; - return sstream.str(); -} - -/// \brief Formats a range of pairs to a pretty string. The length of the two ranges must match. -/// \tparam BidirectionalIteratorT - must implement the BidirectionalIterator concept and -/// must be dereferencable in host code. Its value type must be formattable to \p std::ostream. -/// \tparam BidirectionalIteratorU - must implement the BidirectionalIterator concept and -/// must be dereferencable in host code. Its value type must be formattable to \p std::ostream. -template -inline std::string format_pairs(const BidirectionalIteratorT begin_a, - const BidirectionalIteratorT end_a, - const BidirectionalIteratorU begin_b, - const BidirectionalIteratorU end_b) -{ - (void)end_b; - assert(std::distance(begin_a, end_a) == std::distance(begin_b, end_b)); - - std::stringstream sstream; - sstream << "[ "; - auto it_a = begin_a; - auto it_b = begin_b; - for(; it_a < end_a; ++it_a, ++it_b) - { - sstream << "(" << *it_a << ", " << *it_b << ")"; - - if(it_a != std::prev(end_a)) - { - sstream << ", "; - } - } - sstream << " ]"; - return sstream.str(); -} - -/// \brief A function to parse a string for an int. If the string is a valid integer then return true -/// else if it has non-numeric character then return false. -inline bool parse_int_string(const std::string& str, int& out) -{ - try - { - size_t end; - int value = std::stoi(str, &end); - if(end == str.size()) - { - out = value; - return true; - } - return false; - } - catch(const std::exception&) - { - return false; - } -} - -/// \brief A class to measures time between intervals -class HostClock -{ -private: - std::chrono::steady_clock::time_point start_time; - std::chrono::steady_clock::duration elapsed_time; - -public: - HostClock() - { - this->reset_timer(); - } - - inline void reset_timer() - { - this->elapsed_time = std::chrono::steady_clock::duration(0); - } - - inline void start_timer() - { - this->start_time = std::chrono::steady_clock::now(); - } - - inline void stop_timer() - { - const auto end_time = std::chrono::steady_clock::now(); - this->elapsed_time += end_time - this->start_time; - } - - /// @brief Returns time elapsed in Seconds - /// @return type double that contains the elapsed time in Seconds - inline double get_elapsed_time() const - { - return std::chrono::duration_cast>(this->elapsed_time) - .count(); - } -}; - -/// \brief Returns ceil(dividend / divisor), where \p dividend is an integer and -/// \p divisor is an unsigned integer. -template::value && std::is_unsigned::value, int> = 0> -__host__ __device__ constexpr auto ceiling_div(const T& dividend, const U& divisor) -{ - return (dividend + divisor - 1) / divisor; -} - -/// \brief Report validation results. -inline int report_validation_result(int errors) -{ - if(errors) - { - std::cout << "Validation failed. Errors: " << errors << std::endl; - return error_exit_code; - } - - std::cout << "Validation passed." << std::endl; - return 0; -} - -/// \brief Generate an identity matrix. -/// The identity matrix is a $m \times n$ matrix with ones in the main diagonal and zeros elsewhere. -template -void generate_identity_matrix(T* A, int m, int n, size_t lda) -{ - for(int i = 0; i < m; ++i) - { - for(int j = 0; j < n; ++j) - { - A[i + j * lda] = T(i == j); - } - } -} - -/// \brief Multiply an $A$ matrix ($m \times k$) with a $B$ matrix ($k \times n$) as: -/// $C := \alpha \cdot A \cdot B + \beta \cdot C$ -template -void multiply_matrices(T alpha, - T beta, - int m, - int n, - int k, - const T* A, - int stride1_a, - int stride2_a, - const T* B, - int stride1_b, - int stride2_b, - T* C, - int stride_c) -{ - for(int i1 = 0; i1 < m; ++i1) - { - for(int i2 = 0; i2 < n; ++i2) - { - T t = T(0.0); - for(int i3 = 0; i3 < k; ++i3) - { - t += A[i1 * stride1_a + i3 * stride2_a] * B[i3 * stride1_b + i2 * stride2_b]; - } - C[i1 + i2 * stride_c] = beta * C[i1 + i2 * stride_c] + alpha * t; - } - } -} - -/// \brief Prints an {1,2,3}-dimensional array. The last dimension (fastest-index) specified in -/// \p n will be printed horizontally. -/// -/// By default a row-major layout of the data is assumed. When printing data in column-major -/// layout, the \p column_major parameter must be set to \p true for a correct interpretation -/// of the dimensions' sizes. -template -void print_nd_data(const std::vector& data, - std::vector np, - const int column_width = 4, - const bool column_major = false) -{ - if(column_major) - { - std::reverse(np.begin(), np.end()); - } - const std::vector n(np); - // Note: we want to print the last dimension horizontally (on the x-axis)! - int size_x = n[n.size() - 1]; - int size_y = n.size() > 1 ? n[n.size() - 2] : 1; - int size_z = n.size() > 2 ? n[n.size() - 3] : 1; - for(int z = 0; z < size_z; ++z) - { - for(int y = 0; y < size_y; ++y) - { - for(int x = 0; x < size_x; ++x) - { - auto index = (z * size_y + y) * size_x + x; - std::cout << std::setfill(' ') << std::setw(column_width) << data[index] << " "; - } - std::cout << "\n"; - } - if(z != size_z - 1) - { - std::cout << "\n"; - } - } - std::cout << std::flush; -} - -/// \brief Returns a string from the double \p value with specified \p precision . -inline std::string - double_precision(const double value, const int precision, const bool fixed = false) -{ - std::stringstream ss; - if(fixed) - { - ss << std::fixed; - } - ss << std::setprecision(precision) << value; - return ss.str(); -} - -#endif // COMMON_EXAMPLE_UTILS_HPP diff --git a/skills/rocprof-compute-sample/fabric.hip b/skills/rocprof-compute-sample/fabric.hip deleted file mode 100644 index 7b7fe26..0000000 --- a/skills/rocprof-compute-sample/fabric.hip +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -/* -A data-fabric exerciser example, written by Nicholas Curtis [AMD] - -The test allows the user to control the: - - The granularity of an allocation (Coarse vs Fine-grained), - - The owner of an allocation (local HBM, CPU DRAM or remote HBM), - - The size of an allocation (the default is ~4GiB), and - - The type of operation we are executing (read, write, atomics of various flavors) - -This lets the user explore the impact of these choices on the generated -data-fabric traffic. -*/ - -#include -#include - -#include -#include -#include - -#include "common.h" - -enum class mtype : int { FineGrained = 0, CoarseGrained = 1, Undef = 3 }; -enum class mowner : int { Device = 0, Host = 1, Remote = 2, Undef = 3 }; -enum class mspace : int { Global = 0, Undef = 1 }; -enum class mop : int { - Read = 0, - Write = 1, - AtomicAdd = 2, - AtomicCas = 3, - AtomicOr = 4, - AtomicMax = 5, - Undef = 6 -}; -enum class mdata : int { Unsigned = 0, UnsignedLong = 1, Float = 2, Double = 3, Undef = 4 }; - -template -T parse(const char* value) { - int ivalue = std::atoi(value); - if (ivalue < 0 || ivalue >= int(T::Undef)) { - throw std::runtime_error("bad enum value!"); - } - return T(ivalue); -} - -void parse(int argc, char** argv, mtype& mytype, mowner& myowner, - mspace& myspace, size_t& size, mop& myop, mdata& mydata, - int& remoteId) { - while (1) { - static struct option long_options[] = { - /* These options set a flag. */ - {"type", required_argument, 0, 't'}, - {"owner", required_argument, 0, 'o'}, - {"size", required_argument, 0, 'z'}, - {"op", required_argument, 0, 'p'}, - {"remote", required_argument, 0, 'r'}, - {"data", required_argument, 0, 'd'}, - {0, 0, 0, 0}}; - /* getopt_long stores the option index here. */ - int option_index = 0; - - int c = - getopt_long(argc, argv, "t:o:z:p:r:d:", long_options, &option_index); - - /* Detect the end of the options. */ - if (c == -1) break; - - switch (c) { - case 't': - mytype = parse(optarg); - break; - - case 'o': - myowner = parse(optarg); - break; - - case 'z': - size = std::atoll(optarg); - break; - - case 'p': - myop = parse(optarg); - break; - - case 'r': - remoteId = std::atoi(optarg); - break; - - case 'd': - mydata = parse(optarg); - break; - - case '?': - /* getopt_long already printed an error message. */ - break; - - default: - abort(); - } - } - std::cout << "Using: " << std::endl; - std::cout << "\tmtype:" - << ((mytype == mtype::FineGrained) ? "FineGrained" - : "CoarseGrained") - << std::endl; - std::cout << "\tmowner:" - << ((myowner == mowner::Device) - ? "Device" - : ((myowner == mowner::Host) ? "Host" : "Remote")) - << std::endl; - std::cout << "\tmspace:Global" << std::endl; - std::cout << "\tmop:" << ((myop == mop::Read) ? "Read" : (myop == mop::Write ? "Write" : (myop == mop::AtomicAdd ? "Add" : (myop == mop::AtomicCas ? "CAS" : (myop == mop::AtomicOr ? "Or" : "Max"))))) << std::endl; - std::cout << "\tmdata:" << (mydata == mdata::Unsigned ? "Unsigned" : (mydata == mdata::UnsignedLong ? "Unsigned Long" : (mydata == mdata::Float ? "Float" : "Double"))) << std::endl; - std::cout << "\tremoteId:" << remoteId << std::endl; -} - -// dummy intialization kernel -__global__ void init() {} - -template -void alloc(mtype memory, mowner owner, T** ptr, size_t Nbytes, int devId, - int remoteId) { - bool is_device = (owner == mowner::Device) || (owner == mowner::Remote); - if (owner == mowner::Remote) { - // enable remote access - hipCheck(hipDeviceEnablePeerAccess(remoteId, 0)); - // set id for alloc - hipCheck(hipSetDevice(remoteId)); - } - init<<<1, 1>>>(); - - if (memory == mtype::FineGrained && is_device) { - hipCheck( - hipExtMallocWithFlags((void**)ptr, Nbytes, hipDeviceMallocFinegrained)); - } else if (memory == mtype::CoarseGrained && is_device) { - hipCheck(hipMalloc(ptr, Nbytes)); - } else if (memory == mtype::FineGrained && owner == mowner::Host) { - hipCheck(hipHostMalloc(ptr, Nbytes, hipHostMallocCoherent)); - } else if (memory == mtype::CoarseGrained && owner == mowner::Host) { - hipCheck(hipHostMalloc(ptr, Nbytes, hipHostMallocNonCoherent)); - } else { - assert(false && "unknown combo"); - } - - // set to random - std::vector host(Nbytes / sizeof(T), T(0)); - hipCheck(hipMemcpy(*ptr, &host[0], Nbytes, - (is_device ? hipMemcpyHostToDevice : hipMemcpyHostToHost))); - - if (owner == mowner::Remote) { - // reset id for execution - hipCheck(hipSetDevice(devId)); - } -} - -template -void release(mtype memory, mowner owner, T* ptr) { - bool is_device = (owner == mowner::Device) || (owner == mowner::Remote); - if (memory == mtype::FineGrained && is_device) { - hipCheck(hipFree(ptr)); - } else if (memory == mtype::CoarseGrained && is_device) { - hipCheck(hipFree(ptr)); - } else if (memory == mtype::FineGrained && owner == mowner::Host) { - hipCheck(hipHostFree(ptr)); - } else if (memory == mtype::CoarseGrained && owner == mowner::Host) { - hipCheck(hipHostFree(ptr)); - } else { - assert(false && "unknown combo"); - } -} - -// the main streaming kernel -template -__global__ void kernel(T* x, size_t N, T zero, T foo) { - int sum = 0; - const size_t offset_start = threadIdx.x + blockIdx.x * blockDim.x; - for (int i = 0; i < repeats; ++i) { - for (size_t offset = offset_start; offset < N; - offset += blockDim.x * gridDim.x) { - T uniq = (foo + offset) + i; - if constexpr (op == mop::Read) { - sum += x[offset]; - } else if constexpr (op == mop::Write) { - x[offset] = (T)offset; - } else if constexpr (op == mop::AtomicAdd) { - atomicAdd(&x[offset], uniq); - } else if constexpr (op == mop::AtomicCas) { - atomicCAS(&x[offset], uniq, uniq); - } else if constexpr (op == mop::AtomicOr) { - atomicOr(&x[offset], uniq); - } else if constexpr (op == mop::AtomicMax) { - atomicMax(&x[offset], uniq); - } - } - } - if constexpr (op == mop::Read) { - if (sum != 0) { - x[offset_start] = sum; - } - } -} - -template -void run_kernel(T* x, size_t size) { - if constexpr (op == mop::AtomicOr && std::is_floating_point_v) { - throw std::runtime_error("bad"); - } else { - kernel<<<4096, 1024>>>(x, size, 0, T(23456789)); - // then run once for data collection - kernel<<<4096, 1024>>>(x, size, 0, T(23456789)); - } -} - -template -void run_atomic(mowner myowner, T* x, size_t size) { - if (myowner == mowner::Host) { - // speed it up - run_kernel(x, size / 10); - } else { - run_kernel(x, size); - } -} - -template -void run(mtype mytype, mspace myspace, mowner myowner, mop myop, int remoteId, - size_t size) { - int devId = 0; - if (myowner == mowner::Remote && remoteId == -1) { - // need to find a remote GPU - int ndevices; - hipCheck(hipGetDeviceCount(&ndevices)); - if (ndevices <= 1) { - throw std::runtime_error( - "Need >=2 devices available for mowner = Remote"); - } - for (int i = 0; i < ndevices; ++i) { - if (i != devId) { - remoteId = i; - break; - } - } - } - - T* x; - alloc(mytype, myowner, &x, size * sizeof(T), devId, remoteId); - - // run the kernel once for warmup - assert(4096 * 1024 < size); - if (myop == mop::Read) { - run_kernel(x, size); - } else if (myop == mop::Write) { - run_kernel(x, size); - } else if (myop == mop::AtomicAdd) { - run_atomic(myowner, x, size); - } else if (myop == mop::AtomicCas) { - run_atomic(myowner, x, size); - } else if (myop == mop::AtomicOr) { - run_atomic(myowner, x, size); - } else if (myop == mop::AtomicMax) { - run_atomic(myowner, x, size); - } else { - throw std::runtime_error("bad"); - } - hipCheck(hipDeviceSynchronize()); - release(mytype, myowner, x); -} - -int main(int argc, char** argv) { - mtype mytype = (mtype)0; - mspace myspace = (mspace)0; - mowner myowner = (mowner)0; - mop myop = (mop)0; - mdata mydata = (mdata)0; - int remoteId = -1; - size_t size = 1024ull * 1024ull * - 1024ull; // 4 GiB, purposefully much larger than caches. - parse(argc, argv, mytype, myowner, myspace, size, myop, mydata, remoteId); - if (mydata == mdata::Unsigned) - run(mytype, myspace, myowner, myop, remoteId, size); - else if (mydata == mdata::UnsignedLong) - run(mytype, myspace, myowner, myop, remoteId, size); - else if (mydata == mdata::Float) - run(mytype, myspace, myowner, myop, remoteId, size); - else if (mydata == mdata::Double) - run(mytype, myspace, myowner, myop, remoteId, size); - else { - throw std::runtime_error("bad"); - } -} diff --git a/skills/rocprof-compute-sample/instmix.hip b/skills/rocprof-compute-sample/instmix.hip deleted file mode 100644 index d6af7c7..0000000 --- a/skills/rocprof-compute-sample/instmix.hip +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -/* -A instruction mix exerciser example, written by Gina Sitaraman and Nicholas Curtis [AMD]. -Although inline assembly is inherently unportable, this is expected to work on all CDNA accelerators. -*/ - -#include "common.h" - -__global__ void kernelasm() { - // int32 - int i, j; - asm volatile("v_add_u32_e32 %0, %1, %0\n" : "=v"(j) : "v"(i)); - - // int 64 - long int l1, l2; - asm volatile("v_cmp_eq_i64 %0, %1\n" : "=v"(l2) : "v"(l1), "v"(i)); - - // fp32: add, mul, transcendental and fma - float f1, f2; - asm volatile( - "v_add_f32_e32 %0, %1, %0\n" - "v_mul_f32_e32 %0, %1, %0\n" - "v_sqrt_f32 %0, %1\n" - "v_fma_f32 %0, %1, %0, %1\n" - : "=v"(f1) - : "v"(f2)); - - // fp64: add, mul, transcendental and fma - double d1, d2, d3, d4; - asm volatile( - "v_add_f64 %0, %1, %0\n" - "v_mul_f64 %0, %1, %0\n" - "v_fma_f64 %0, %1, %0, %1\n" - "v_sqrt_f64 %0, %1\n" - "v_min_f64 %0, %1, %0\n" - : "+v"(d1) - : "v"(d2)); - - // fp16: add, mul, transcendental and fma - _Float16 h1, h2; - asm volatile( - "v_add_f16_e32 %0, %1, %0\n" - "v_mul_f16_e32 %0, %1, %0\n" - "v_sqrt_f16 %0, %1\n" - "v_cvt_f16_f32 %0 %2\n" - "v_fma_f16 %0, %1, %0, %0\n" - : "=v"(h2) - : "v"(h1), "v"(f1)); - - // MFMA ops - double2 dd; - unsigned short us; - long2 ll; -#if defined(__gfx90a__) - asm volatile("v_mfma_f64_4x4x4f64 %0 %1 %2 %3\n" - : "=v"(d4) - : "v"(d1), "v"(d2), "v"(d3)); - asm volatile("v_mfma_f32_16x16x4f32 %0 %1 %2 1\n" - : "=v"(dd) - : "v"(f1), "v"(f2)); - asm volatile("v_mfma_f32_16x16x16f16 %0 %1 %2 1\n" - : "=v"(dd) - : "v"(d1), "v"(d2)); - asm volatile("v_mfma_f32_16x16x8bf16 %0 %1 %2 1\n" - : "=v"(dd) - : "v"(f1), "v"(f2)); - asm volatile("v_mfma_i32_16x16x16i8 %0 %1 %2 1\n" - : "=v"(ll) - : "v"(i), "v"(j)); -#endif - - // Scalar op - asm volatile("s_add_i32 %0 %1 %0\n" : "=s"(j) : "s"(i)); - - // LDS - asm volatile("ds_read_b32 %0 %0\n" : "=v"(i) : "v"(j)); - - // Branch - asm volatile( - "s_branch .LDUMMY\n" - ".LDUMMY:\n" - "s_endpgm\n"); -} -int main() { - kernelasm<<<1, 64>>>(); - hipCheck(hipDeviceSynchronize()); -} diff --git a/skills/rocprof-compute-sample/ipc.hip b/skills/rocprof-compute-sample/ipc.hip deleted file mode 100644 index f82a0d4..0000000 --- a/skills/rocprof-compute-sample/ipc.hip +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -/* -An example to explore IPC and divergence, written by Nicholas Curtis [AMD]. -This example may not work on all CDNA accelerators, but has been verified on MI2XX. -*/ - -#include "common.h" - -// Define MFMA support for CDNA2+ architectures (MI200, MI300, MI350 series) -#if defined(__gfx90a__) || defined(__gfx940__) || defined(__gfx941__) || defined(__gfx942__) || defined(__gfx950__) -#define HAS_MFMA_SUPPORT 1 -#endif - -template -__device__ void vrcp_op() { - int dummy; - if constexpr (N >= 1) { - asm volatile("v_rcp_f64 v[0:1], v[0:1]\n" : : "{v31}"(dummy)); - vrcp_op(); - } -} - -template -__global__ void vrcp() { - vrcp_op(); -} - -template -__device__ void vmov_op() { - int dummy; - if constexpr (N >= 1) { - asm volatile("v_mov_b32 v0, v1\n" : : "{v31}"(dummy)); - vmov_op(); - } -} - -template -__global__ void vmov() { - vmov_op(); -} - -#ifdef HAS_MFMA_SUPPORT -template -__device__ void mfma_op() { - int dummy; - if constexpr (N >= 1) { - asm volatile("v_mfma_f32_32x32x8bf16_1k v[0:15], v[16:17], v[18:19], v[0:15]\n" : : "{v31}"(dummy)); - mfma_op(); - } -} - -template -__global__ void mfma() { - mfma_op(); -} -#endif - -template -__device__ void snop_op() { - int dummy; - if constexpr (N >= 1) { - asm volatile("s_nop 0x0\n" : : "{v31}"(dummy)); - snop_op(); - } -} - - -template -__global__ void snop() { - snop_op(); -} - -template -__device__ void smov_op() { - int dummy; - if constexpr (N >= 1) { - asm volatile("s_mov_b32 s0, s1\n" : : "{s31}"(dummy)); - smov_op(); - } -} - -template -__global__ void smov() { - smov_op(); -} - -template -__global__ void vmov_with_divergence() { - if (threadIdx.x % 64 == 0) - vmov_op(); -} - -int main() { - // warmups, spam to all CUs - vrcp<<<1024 * 1024, 1024>>>(); - vmov<<<1024 * 1024, 1024>>>(); -#ifdef HAS_MFMA_SUPPORT - mfma<<<1024 * 1024, 1024>>>(); -#endif - snop<<<1024 * 1024, 1024>>>(); - smov<<<1024 * 1024, 1024>>>(); - vmov_with_divergence<<<1024 * 1024, 1024>>>(); - hipCheck(hipDeviceSynchronize()); - vrcp<<<1024 * 1024, 1024>>>(); - vmov<<<1024 * 1024, 1024>>>(); -#ifdef HAS_MFMA_SUPPORT - mfma<<<1024 * 1024, 1024>>>(); -#endif - snop<<<1024 * 1024, 1024>>>(); - smov<<<1024 * 1024, 1024>>>(); - vmov_with_divergence<<<1024 * 1024, 1024>>>(); - hipCheck(hipDeviceSynchronize()); -} diff --git a/skills/rocprof-compute-sample/laplace_eqn.hip b/skills/rocprof-compute-sample/laplace_eqn.hip deleted file mode 100644 index 11e5a0e..0000000 --- a/skills/rocprof-compute-sample/laplace_eqn.hip +++ /dev/null @@ -1,410 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// This sample code solves the 2D Laplace equation using the Jacobi iterative -// method on a rectangle with periodic boundary conditions. - -// The domain is initialized with a smooth Dirac delta function at the center. - -// The grid is partitioned among multiple MPI processes in the vertical direction. -// Each process exchanges halo regions with its neighboring processes after each -// iteration. Communication uses explicit GPU->Host->MPI->Host->GPU transfers -// (not GPU-aware MPI) for halo exchange. - -// The code allows users to specify the number of iterations and the -// order of kernel launches via command-line arguments. - - -// Reference: Finite Difference Methods for Ordinary and Partial Differential -// Equations by Randall J. LeVeque, Page 69-71 - - -#include -#include -#include -#include -#ifdef USE_MPI -#include -#endif // USE_MPI - -// Helper macro for HIP error checking -#define HIP_CHECK(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - std::cerr << "HIP error: " << hipGetErrorString(err) << " at " \ - << __FILE__ << ":" << __LINE__ << std::endl; \ - std::exit(EXIT_FAILURE); \ - } \ - } while (0) - -void initialize(float *U, int N) { -#ifdef USE_MPI - // Get the number of processes - int num_procs; - MPI_Comm_size(MPI_COMM_WORLD, &num_procs); - - // Get the rank of the process - int rank; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - - int N_total = N * num_procs; -#endif // USE_MPI - - // The grid is partitioned among MPI processes in the vertical direction - for (int i = 0; i < N; ++i) { -#ifdef USE_MPI - // The halo regions are not initialized here - // They are obtained from the neighboring rank - for (int j = 1; j <= N; ++j) { - int j_global = rank * N + j; - float y = 2.0f * (float)j_global / (float)N_total - 1.0f; -#else - for (int j = 0; j < N; ++j) { - float y = 2.0f * (float)j / (float)N - 1.0f; -#endif // USE_MPI - float x = 2.0f * (float)i / (float)N - 1.0f; - U[i * N + j] = 1.0f / (1.0f + x * x + y * y); // Initial guess - } - } -} - -__global__ void jacobi_iteration(float *__restrict__ U_new, - const float *__restrict__ U_old, int N) { - int i = blockIdx.x * blockDim.x + threadIdx.x; - int j = blockIdx.y * blockDim.y + threadIdx.y; - - int idx = j + i * N; // Flattened index for 2D grid - - extern __shared__ float U[]; - int s_i = threadIdx.x + 1; - int s_j = threadIdx.y + 1; - - int tile_height = blockDim.y + 2; - int s_idx = s_j + s_i * tile_height; - - // Load data into shared memory with halo regions - if (i < N && j < N) { - U[s_idx] = U_old[idx]; - - // Load halo regions - if (threadIdx.x == 0) { - U[s_j] = (i > 0) ? U_old[(i - 1) * N + j] : U_old[(N - 1) * N + j]; - } - - if (threadIdx.x == blockDim.x - 1) { - U[s_j + (blockDim.x + 1) * tile_height] = - (i < N - 1) ? U_old[(i + 1) * N + j] : U_old[j]; - } - - if (threadIdx.y == 0) { - U[s_i * tile_height] = - (j > 0) ? U_old[i * N + (j - 1)] : U_old[i * N + (N - 1)]; - } - - if (threadIdx.y == blockDim.y - 1) { - U[s_i * tile_height + blockDim.y + 1] = - (j < N - 1) ? U_old[i * N + (j + 1)] : U_old[i * N]; - } - } - - __syncthreads(); - - if (i < N && j < N) { - int east = s_idx - tile_height; - int west = s_idx + tile_height; - int north = s_idx + 1; - int south = s_idx - 1; - U_new[idx] = 0.25f * (U[east] + U[west] + U[north] + U[south]); - } -} - -int main(int argc, char *argv[]) { -#ifdef USE_MPI - // Initialize MPI - MPI_Init(&argc, &argv); - - // Get the number of processes - int num_procs; - MPI_Comm_size(MPI_COMM_WORLD, &num_procs); - - // Get the rank of the process - int rank; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - std::cout << "Rank " << rank << " of " << num_procs << std::endl; -#else - int num_procs = 1; - int rank = 0; - std::cout << "Running without MPI (single process)" << std::endl; -#endif // USE_MPI - - int iter = 1000; - std::vector launch_order; - -#ifdef USE_MPI - if (rank == 0) { -#endif // USE_MPI - // Parse command-line arguments - for (int i = 0; i < argc; i++) { - if (std::string(argv[i]) == "--mpi-info") { -#ifdef USE_MPI - std::cout << "MPI support: enabled" << std::endl; - MPI_Finalize(); -#else - std::cout << "MPI support: disabled" << std::endl; -#endif // USE_MPI - return 0; - } - if (std::string(argv[i]) == "-i" && i + 1 < argc) { - iter = std::atoi(argv[i + 1]); - } else if (std::string(argv[i]) == "-o" && i + 1 < argc) { - int n_order = std::atoi(argv[i + 1]); - if ((i + n_order + 1) >= argc) { - std::cerr << "Insufficient arguments for -o option" << std::endl; - return EXIT_FAILURE; - } else { - for (int j = 0; j < n_order; j++) { - int kernel_id = std::atoi(argv[i + 2 + j]); - if (kernel_id < 0 || kernel_id > 2) { - std::cerr << "Invalid kernel ID: " << kernel_id << std::endl; - return EXIT_FAILURE; - } - launch_order.push_back(kernel_id); - } - i += n_order; - } - } - } - - if (launch_order.empty()) { - launch_order = {0, 1, 2}; - } -#ifdef USE_MPI - } - - // Broadcast number of iterations to all processes - MPI_Bcast(&iter, 1, MPI_INT, 0, MPI_COMM_WORLD); - - // Broadcast launch_order size and data to all processes - int order_size = launch_order.size(); - MPI_Bcast(&order_size, 1, MPI_INT, 0, MPI_COMM_WORLD); - - if (rank != 0) - launch_order.resize(order_size); - - MPI_Bcast(launch_order.data(), order_size, MPI_INT, 0, MPI_COMM_WORLD); -#endif // USE_MPI - - // Problem size - const int n_small = 1024; - const int n_mid = 2048; - const int n_large = 4096; - - // Define block and grid sizes - dim3 blockSize_small(16, 16); - dim3 gridSize_small((n_small + blockSize_small.x - 1) / blockSize_small.x, - (n_small + blockSize_small.y - 1) / blockSize_small.y); - dim3 blockSize_mid(24, 24); - dim3 gridSize_mid((n_mid + blockSize_mid.x - 1) / blockSize_mid.x, - (n_mid + blockSize_mid.y - 1) / blockSize_mid.y); - dim3 blockSize_large(32, 32); - dim3 gridSize_large((n_large + blockSize_large.x - 1) / blockSize_large.x, - (n_large + blockSize_large.y - 1) / blockSize_large.y); - - const int nx_small = n_small; - const int nx_mid = n_mid; - const int nx_large = n_large; - -#ifdef USE_MPI - // Set device based on rank - int num_devices; - HIP_CHECK(hipGetDeviceCount(&num_devices)); - HIP_CHECK(hipSetDevice(rank % num_devices)); - - const int ny_small = n_small + 2; - const int ny_mid = n_mid + 2; - const int ny_large = n_large + 2; -#else - const int ny_small = n_small; - const int ny_mid = n_mid; - const int ny_large = n_large; -#endif // USE_MPI - - // Host memory pointers - float *h_U_small = new float[nx_small * ny_small]; - float *h_U_mid = new float[nx_mid * ny_mid]; - float *h_U_large = new float[nx_large * ny_large]; - - // Initialize host arrays - initialize(h_U_small, n_small); - initialize(h_U_mid, n_mid); - initialize(h_U_large, n_large); - - // Device memory pointers - float *d_U_small_new, *d_U_small_old; - float *d_U_mid_new, *d_U_mid_old; - float *d_U_large_new, *d_U_large_old; - - // Allocate device memory - HIP_CHECK(hipMalloc(&d_U_small_new, nx_small * ny_small * sizeof(float))); - HIP_CHECK(hipMalloc(&d_U_small_old, nx_small * ny_small * sizeof(float))); - - HIP_CHECK(hipMalloc(&d_U_mid_new, nx_mid * ny_mid * sizeof(float))); - HIP_CHECK(hipMalloc(&d_U_mid_old, nx_mid * ny_mid * sizeof(float))); - - HIP_CHECK(hipMalloc(&d_U_large_new, nx_large * ny_large * sizeof(float))); - HIP_CHECK(hipMalloc(&d_U_large_old, nx_large * ny_large * sizeof(float))); - - // Copy host arrays to device - HIP_CHECK(hipMemcpy(d_U_small_old, h_U_small, - nx_small * ny_small * sizeof(float), - hipMemcpyHostToDevice)); - HIP_CHECK(hipMemcpy(d_U_mid_old, h_U_mid, nx_mid * ny_mid * sizeof(float), - hipMemcpyHostToDevice)); - HIP_CHECK(hipMemcpy(d_U_large_old, h_U_large, - nx_large * ny_large * sizeof(float), - hipMemcpyHostToDevice)); - - std::vector grid_sizes = {gridSize_small, gridSize_mid, gridSize_large}; - std::vector block_sizes = {blockSize_small, blockSize_mid, - blockSize_large}; - std::vector d_U_news = {d_U_small_new, d_U_mid_new, d_U_large_new}; - std::vector d_U_olds = {d_U_small_old, d_U_mid_old, d_U_large_old}; - std::vector n_sizes = {n_small, n_mid, n_large}; - -#ifdef USE_MPI - int left = rank == 0 ? num_procs - 1 : rank - 1; - int right = (rank + 1) % num_procs; - - // Allocate host buffers for halo exchange (GPU -> Host -> MPI -> Host -> GPU) - // Each problem size needs 4 buffers: send_left, send_right, recv_left, recv_right - std::vector h_send_left(n_sizes.size()); - std::vector h_send_right(n_sizes.size()); - std::vector h_recv_left(n_sizes.size()); - std::vector h_recv_right(n_sizes.size()); - - for (size_t i = 0; i < n_sizes.size(); ++i) { - h_send_left[i] = new float[n_sizes[i]]; - h_send_right[i] = new float[n_sizes[i]]; - h_recv_left[i] = new float[n_sizes[i]]; - h_recv_right[i] = new float[n_sizes[i]]; - } - - int num_requests = 4 * n_sizes.size(); - std::vector requests(num_requests); - - // Initial halo exchange: GPU -> Host -> MPI -> Host -> GPU - for (size_t i = 0; i < n_sizes.size(); ++i) { - // Copy halo data from GPU to host send buffers - HIP_CHECK(hipMemcpy(h_send_left[i], &d_U_olds[i][n_sizes[i]], - n_sizes[i] * sizeof(float), hipMemcpyDeviceToHost)); - HIP_CHECK(hipMemcpy(h_send_right[i], &d_U_olds[i][n_sizes[i] * (n_sizes[i] + 1)], - n_sizes[i] * sizeof(float), hipMemcpyDeviceToHost)); - } - - for (size_t i = 0; i < n_sizes.size(); ++i) { - // Send data to left node, receive from right node - MPI_Isend(h_send_left[i], n_sizes[i], MPI_FLOAT, left, 2 * i, - MPI_COMM_WORLD, &requests[4 * i]); - MPI_Irecv(h_recv_right[i], n_sizes[i], MPI_FLOAT, right, 2 * i, - MPI_COMM_WORLD, &requests[4 * i + 1]); - - // Send data to right node, receive from left node - MPI_Isend(h_send_right[i], n_sizes[i], MPI_FLOAT, right, 2 * i + 1, - MPI_COMM_WORLD, &requests[4 * i + 2]); - MPI_Irecv(h_recv_left[i], n_sizes[i], MPI_FLOAT, left, 2 * i + 1, - MPI_COMM_WORLD, &requests[4 * i + 3]); - } - - MPI_Waitall(num_requests, requests.data(), MPI_STATUSES_IGNORE); - - // Copy received halo data from host to GPU - for (size_t i = 0; i < n_sizes.size(); ++i) { - HIP_CHECK(hipMemcpy(&d_U_olds[i][n_sizes[i] * n_sizes[i]], h_recv_right[i], - n_sizes[i] * sizeof(float), hipMemcpyHostToDevice)); - HIP_CHECK(hipMemcpy(&d_U_olds[i][0], h_recv_left[i], - n_sizes[i] * sizeof(float), hipMemcpyHostToDevice)); - } -#endif // USE_MPI - - // Perform Jacobi iterations - for (int it = 0; it < iter; ++it) { - int i = launch_order[it % launch_order.size()]; - jacobi_iteration<<>>(d_U_news[i], d_U_olds[i], - n_sizes[i]); - - HIP_CHECK(hipDeviceSynchronize()); - // Swap pointers - std::swap(d_U_news[i], d_U_olds[i]); - -#ifdef USE_MPI - std::vector requests(4); - - // Copy halo data from GPU to host send buffers - HIP_CHECK(hipMemcpy(h_send_left[i], &d_U_olds[i][n_sizes[i]], - n_sizes[i] * sizeof(float), hipMemcpyDeviceToHost)); - HIP_CHECK(hipMemcpy(h_send_right[i], &d_U_olds[i][n_sizes[i] * (n_sizes[i] + 1)], - n_sizes[i] * sizeof(float), hipMemcpyDeviceToHost)); - - // Send data to left node, receive from right node - MPI_Isend(h_send_left[i], n_sizes[i], MPI_FLOAT, left, 0, - MPI_COMM_WORLD, &requests[0]); - MPI_Irecv(h_recv_right[i], n_sizes[i], MPI_FLOAT, right, 0, - MPI_COMM_WORLD, &requests[1]); - - // Send data to right node, receive from left node - MPI_Isend(h_send_right[i], n_sizes[i], MPI_FLOAT, right, 1, - MPI_COMM_WORLD, &requests[2]); - MPI_Irecv(h_recv_left[i], n_sizes[i], MPI_FLOAT, left, 1, - MPI_COMM_WORLD, &requests[3]); - - MPI_Waitall(4, requests.data(), MPI_STATUSES_IGNORE); - - // Copy received halo data from host to GPU - HIP_CHECK(hipMemcpy(&d_U_olds[i][n_sizes[i] * n_sizes[i]], h_recv_right[i], - n_sizes[i] * sizeof(float), hipMemcpyHostToDevice)); - HIP_CHECK(hipMemcpy(&d_U_olds[i][0], h_recv_left[i], - n_sizes[i] * sizeof(float), hipMemcpyHostToDevice)); -#endif // USE_MPI - } - - // Pedagogical - // Copy device data to host - HIP_CHECK(hipMemcpy(h_U_small, d_U_small_old, - nx_small * ny_small * sizeof(float), - hipMemcpyDeviceToHost)); - HIP_CHECK(hipMemcpy(h_U_mid, d_U_mid_old, nx_mid * ny_mid * sizeof(float), - hipMemcpyDeviceToHost)); - HIP_CHECK(hipMemcpy(h_U_large, d_U_large_old, - nx_large * ny_large * sizeof(float), - hipMemcpyDeviceToHost)); - - // Free device memory - HIP_CHECK(hipFree(d_U_small_new)); - HIP_CHECK(hipFree(d_U_small_old)); - HIP_CHECK(hipFree(d_U_mid_new)); - HIP_CHECK(hipFree(d_U_mid_old)); - HIP_CHECK(hipFree(d_U_large_new)); - HIP_CHECK(hipFree(d_U_large_old)); - - delete[] h_U_small; - delete[] h_U_mid; - delete[] h_U_large; - -#ifdef USE_MPI - // Free host buffers for halo exchange - for (size_t i = 0; i < n_sizes.size(); ++i) { - delete[] h_send_left[i]; - delete[] h_send_right[i]; - delete[] h_recv_left[i]; - delete[] h_recv_right[i]; - } - - // Finalize MPI - MPI_Finalize(); -#endif // USE_MPI - - return 0; -} diff --git a/skills/rocprof-compute-sample/mega_kernel/Makefile b/skills/rocprof-compute-sample/mega_kernel/Makefile deleted file mode 100644 index a97d3f2..0000000 --- a/skills/rocprof-compute-sample/mega_kernel/Makefile +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. -# SPDX-License-Identifier: MIT -# -# AMD GPU Mega Kernel Unit Test - Makefile -# -# Supported offload-arch values (single source for per-arch binaries): -ARCHES := gfx950 gfx942 gfx90a gfx1150 gfx1151 gfx1152 gfx1153 gfx1201 gfx1200 gfx1250 -ARCH_BINS := $(addprefix mega_kernel_test_,$(ARCHES)) -# -# CDNA (Compute/Datacenter): -# - MI250/MI250X (CDNA2): gfx90a -# - MI300/MI300X (CDNA3): gfx942 -# - MI350/MI355X (CDNA4): gfx950 -# -# RDNA 3.5+ APU iGPU (gfx115x-class today; additional APUs may use this bucket later): -# - Strix Point (RDNA 3.5): gfx1150 -# - Strix Halo (RDNA 3.5): gfx1151 -# - Krackan (RDNA 3.5): gfx1152 -# - Gorgon Point (RDNA 3.5): gfx1153 -# -# RDNA4 (Consumer): -# - RX 9070 XT/9070 (RDNA4): gfx1201 -# - RX 9060 series (RDNA4): gfx1200 -# -# gfx1250: -# - gfx1250: FP8/BF8 OCP, WMMA, TDM, cluster ops, cooperative atomics -# -# gfx1250 metric-health profile args (do not pass -t 32; that is wave size, not block size) -GFX1250_HEALTH_BATCH ?= 655360 - -# Targets: -# make - Build for gfx1151 (default; override with GPU_ARCH=...) -# make gfx950 - Build for MI350 (gfx950/CDNA4) -# make gfx942 - Build for MI300 (gfx942/CDNA3) -# make gfx90a - Build for MI250 (gfx90a/CDNA2) -# make gfx1150 - Build for Strix Point (gfx1150/RDNA3.5) -# make gfx1151 - Build for Strix Halo (gfx1151/RDNA3.5) -# make gfx1152 - Build for Krackan (gfx1152/RDNA3.5) -# make gfx1153 - Build for Gorgon Point (gfx1153/RDNA3.5) -# make gfx1201 - Build for RX 9070 XT (gfx1201/RDNA4) -# make gfx1200 - Build for RX 9060 (gfx1200/RDNA4) -# make gfx1250 - Build for gfx1250 -# make all-arch - Build binaries for all ARCHES -# make clean - Remove build artifacts -# make run - Build and run the test - -# Compiler -HIPCC ?= hipcc - -# Default architecture: gfx1151 (Strix Halo / RDNA 3.5) -GPU_ARCH ?= gfx1151 - -# Common flags -HIPCC_FLAGS = -O2 -g -std=c++17 -HIPCC_FLAGS += -Wall -Wextra -HIPCC_FLAGS += -fPIC - -# Include paths -INCLUDES = -I. - -# Source files -KERNEL_SRC = mega_kernel.hip -HOST_SRC = main.cpp - -# Output -DEFAULT_TARGET = mega_kernel_test - -# Build rules -.PHONY: all clean run run-gfx1250-health $(ARCHES) all-arch help - -all: $(DEFAULT_TARGET) - -$(DEFAULT_TARGET): $(KERNEL_SRC) $(HOST_SRC) - @echo "Building for $(GPU_ARCH)..." - $(HIPCC) $(HIPCC_FLAGS) --offload-arch=$(GPU_ARCH) $(INCLUDES) -o $@ $^ - @echo "Build complete: $@" - -# One pattern rule for all per-arch binaries (stem is full token, e.g. gfx950) -$(ARCH_BINS): mega_kernel_test_%: $(KERNEL_SRC) $(HOST_SRC) - @echo "Building for $*..." - $(HIPCC) $(HIPCC_FLAGS) --offload-arch=$* $(INCLUDES) -o $@ $^ - @echo "Build complete: $@" - -# Symlink convenience: gfx950 -> mega_kernel_test_gfx950, etc. -$(ARCHES): - @$(MAKE) mega_kernel_test_$@ - @ln -sf mega_kernel_test_$@ mega_kernel_test - @echo "Linked mega_kernel_test -> mega_kernel_test_$@" - -all-arch: $(ARCH_BINS) - @echo "Built: $(ARCHES)" - -# Run the test -run: $(DEFAULT_TARGET) - @echo "" - @echo "Running GPU Mega Kernel Test..." - @echo "" - ./$(DEFAULT_TARGET) - -# gfx1250 metric-health workload (see rocprof-compute profile -- ... -- ) -run-gfx1250-health: mega_kernel_test_gfx1250 - @echo "Running gfx1250 health args: -b $(GFX1250_HEALTH_BATCH) (block size default 256)" - ./mega_kernel_test_gfx1250 -b $(GFX1250_HEALTH_BATCH) - -# Clean -clean: - rm -f mega_kernel_test mega_kernel_test_* *.o *.out - -# Help -help: - @echo "AMD GPU Mega Kernel Unit Test - Build System" - @echo "" - @echo "Supported Architectures (ARCHES in Makefile):" - @echo " $(ARCHES)" - @echo "" - @echo " CDNA (Compute/Datacenter):" - @echo " gfx90a - MI250/MI250X (CDNA2): FP32/FP64/FP16/BF16, HW FP64 atomics" - @echo " gfx942 - MI300/MI300X (CDNA3): + FP8/BF8 (FNUZ-style cvt), TF32, Sparse MFMA" - @echo " gfx950 - MI350/MI355X (CDNA4): + FP6/FP4, OCP FP8/BF8 paths where applicable, Async LDS, Dual-Issue" - @echo "" - @echo " RDNA 3.5+ APU iGPU (gfx115x-class):" - @echo " gfx1150 - Strix Point: WMMA, TEX load/store tests (no CDNA-style FP8/BF8 cvt)" - @echo " gfx1151 - Strix Halo: same as gfx1150" - @echo " gfx1152 - Krackan: same as gfx1150" - @echo " gfx1153 - Gorgon Point: same as gfx1150" - @echo "" - @echo " RDNA4 (Consumer):" - @echo " gfx1201 - RX 9070 XT/9070: FP8/BF8 OCP, Async LDS, Wave32 WMMA path" - @echo " gfx1200 - RX 9060 series: FP8/BF8 OCP, Async LDS, Wave32 WMMA path" - @echo "" - @echo " gfx1250:" - @echo " gfx1250 - FP8/BF8 OCP, WMMA, TDM, cluster ops, cooperative atomics" - @echo "" - @echo "Usage:" - @echo " make - Build for gfx1151 (default; set GPU_ARCH to override)" - @echo " make gfx950 - Build for MI350 (gfx950/CDNA4)" - @echo " make gfx942 - Build for MI300 (gfx942/CDNA3)" - @echo " make gfx90a - Build for MI250 (gfx90a/CDNA2)" - @echo " make gfx1150 - Build for Strix Point (gfx1150)" - @echo " make gfx1151 - Build for Strix Halo (gfx1151)" - @echo " make gfx1152 - Build for Krackan (gfx1152)" - @echo " make gfx1153 - Build for Gorgon Point (gfx1153)" - @echo " make gfx1201 - Build for RX 9070 XT (gfx1201/RDNA4)" - @echo " make gfx1200 - Build for RX 9060 (gfx1200/RDNA4)" - @echo " make gfx1250 - Build for gfx1250" - @echo " make all-arch - Build one binary per ARCHES" - @echo " make run - Build and run the test" - @echo " make run-gfx1250-health - Run gfx1250 metric-health args (-b $(GFX1250_HEALTH_BATCH))" - @echo " make clean - Remove build artifacts" - @echo " make help - Show this help message" - @echo "" - @echo "Environment Variables:" - @echo " GPU_ARCH - Target GPU architecture (default: gfx1151)" - @echo " HIPCC - HIP compiler path (default: hipcc)" - @echo "" - @echo "Example:" - @echo " make gfx1201 - Build for RX 9070 XT (RDNA4)" - @echo " make gfx942 - Build for MI300" - @echo " GPU_ARCH=gfx950 make # example: MI350 (CDNA4)" diff --git a/skills/rocprof-compute-sample/mega_kernel/README.md b/skills/rocprof-compute-sample/mega_kernel/README.md deleted file mode 100644 index 51e0fa9..0000000 --- a/skills/rocprof-compute-sample/mega_kernel/README.md +++ /dev/null @@ -1,672 +0,0 @@ -# AMD GPU Mega Kernel Unit Test - -A comprehensive unit test kernel that exercises most of the instructions and features available on AMD GPUs including Instinct (MI250/MI300/MI350), Radeon (RX 9060 / RX 9070 families, gfx120x), Strix/Strix Halo/Krackan (RDNA 3.5, gfx115x) APU iGPUs, and gfx1250. - -## Supported GPU Architectures - -*GPU/architecture tables below are partly **informational** (what silicon markets); **what this sample runs** is only what the **Feature Compatibility Matrix** and following “Features Tested” sections describe.* - -### CDNA (Compute/Datacenter) - -| GPU Series | Architecture | GFXIP | Key Features | -|------------|--------------|-------|--------------| -| **MI250/MI250X** | CDNA2 | gfx90a | FP32/FP64/FP16/BF16, HW FP64 atomics, MFMA, DOT4/DOT8 | -| **MI300/MI300X/MI325X** | CDNA3 | gfx942 | All MI250 + FP8/BF8 (FNUZ-style cvt), TF32, Sparse MFMA | -| **MI350X/MI355X** | CDNA4 | gfx950 | All MI300 + FP6/FP4, OCP FP8/BF8 where applicable, Async LDS, Enhanced WMMA | - -### RDNA4 (Consumer) - -| GPU Series | Architecture | GFXIP | Key Features | -|------------|--------------|-------|--------------| -| **RX 9070 XT/9070** | RDNA4 | gfx1201 | FP8/BF8 OCP, Async LDS, Wave32, WMMA (gfx12 path) | -| **RX 9060 series** | RDNA4 | gfx1200 | Same as gfx1201 with different CU count | - -### RDNA 3.5+ APU iGPU (gfx115x-class) - -| GPU Series | Architecture | GFXIP | Key Features | -|------------|--------------|-------|--------------| -| **Strix Point** | RDNA 3.5 | gfx1150 | 16 CUs, WMMA, Wave32, Async LDS | -| **Strix Halo** | RDNA 3.5 | gfx1151 | Up to 40 CUs, WMMA, Wave32, Async LDS | -| **Krackan** | RDNA 3.5 | gfx1152 | 32MB Infinity Cache, same as gfx1150 | - -### gfx1250 - -| GPU Series | Architecture | GFXIP | Key Features | -|------------|--------------|-------|--------------| -| **gfx1250** | gfx1250 | gfx1250 | FP8/BF8 OCP, HW FP64 atomics, WMMA (gfx1250 builtins), TDM, cluster ops, cooperative atomics, Async LDS | - -## Feature Compatibility Matrix - -This table lists **what this sample tries to run** on each GFXIP, plus a few hardware facts that are **not** individually exercised here (no standalone fixed-function graphics or branded accelerator microbenchmarks in this kernel). MFMA/WMMA builtins use **non-zero operands** and pass only if the accumulator fragment has **at least one non-zero lane** (FP paths also require **finite** lanes)—smoke validation, not golden-matrix or full ISA conformance. Dual-issue is **BYPASSED** in the report (patterns only). Async LDS follows `__has_builtin(__builtin_amdgcn_global_load_async_to_lds_b32)` in code (gfx942 is treated as unsupported in this sample, matching `mega_kernel_device_arch.h`). - -| Feature | MI250 (gfx90a) | MI300 (gfx942) | MI350 (gfx950) | Strix/Krackan (gfx115x) | RX 9070 XT (gfx1201) | gfx1250 | -|---------|:--------------:|:--------------:|:--------------:|:-----------------------:|:--------------------:|:---------------:| -| FP32 Arithmetic | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| FP64 Arithmetic | ✅ | ✅ | ✅ | ⚠️ (limited) | ⚠️ (limited) | ✅ | -| FP16/BF16 Arithmetic | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| INT32/INT64 Operations | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| HW FP64 Atomics | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | -| FP8/BF8 Conversions | ❌ | ✅ (FNUZ) | ✅ (OCP) | ❌ | ✅ (OCP) | ✅ (OCP) | -| FP6/FP4 Conversions | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | -| DOT4/DOT8 Products | ✅ | ✅ | ✅ | ✅ | ✅ (software) | ✅ | -| Warp/Wave Ops | ✅ (Wave64) | ✅ (Wave64) | ✅ (Wave64) | ✅ (Wave32) | ✅ (Wave32) | ✅ (Wave32) | -| LDS Operations | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Async LDS (this sample) | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | -| Dual-Issue VALU (this sample) | — | BYPASS | BYPASS | BYPASS | — | BYPASS | -| MFMA Matrix Ops | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | -| WMMA Matrix Ops | ❌ | ❌ | ❌ | ✅ (gfx12) | ✅ (gfx12) | ✅ (gfx1250) | -| Packed FP16 Atomics | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Packed BF16 Atomics | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | -| VMEM Inline ASM | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | - -⚠️ = Limited support / may vary by ROCm version. **BYPASS** = kernel emits instruction patterns but the host report marks the category bypassed (not a correctness proof). Fixed-function blocks not driven by this VALU/WMMA/TEX-focused kernel are **not exercised here**. - -## Features Tested - -### Test Category Summary - -| # | Category | Tests | Description | -|---|----------|-------|-------------| -| 1 | Warp/Wave Operations | 4 | Shuffle, ballot, permute, reduce | -| 2 | Data Type Conversions | 4 | FP8, BF8, FP16, BF16 conversions | -| 3 | Arithmetic Operations | 4 | FP32, FP64, INT32, INT64 | -| 4 | Basic Atomic Operations | 6 | FP32/FP64 add, min/max, integer ops, LDS | -| 5 | Transcendental Functions | 3 | Trig, exp/log, rcp/rsq | -| 6 | Memory Operations | 2 | Global load/store | -| 7 | DOT Products | 2 | DOT4, DOT8 | -| 8 | MFMA Operations | 5 | FP32, FP64, FP16, BF16, INT8 matrix ops | -| 9 | Async LDS | 2 | Async load/store to LDS | -| 11 | Dual-Issue VALU | 2 | FP64+FP32, INT+FP32 patterns | -| 13 | WMMA Operations | 3 | FP16, BF16, INT8 matrix ops (RDNA4) | -| 14 | VMEM Operations | 5 | Flat, global, buffer, scratch, LDS (inline ASM) | -| 15 | Atomic Operations (ASM) | 10 | Global/flat/DS int/float, packed, CAS (inline ASM) | - -### Warp/Wave Operations -- Warp shuffle (`__shfl`, `__shfl_xor`, `__shfl_up`, `__shfl_down`) -- Warp ballot (`__ballot`, `__any`, `__all`) -- DS permute operations (`ds_bpermute`, `ds_permute`) -- Warp-level reductions - -### Floating Point Operations -- **FP8/BF8 Conversions** - - `__builtin_amdgcn_cvt_scalef32_pk_*` (gfx950 specific) -- FP16/BF16 operations and packed arithmetic -- FP32/FP64 arithmetic with FMA - -### Integer Operations -- INT32 arithmetic and bit operations -- INT64 arithmetic -- Population count and leading zeros - -### Basic Atomic Operations (HIP API) -- Atomic Add FP32 (`atomicAdd`) -- Atomic Min/Max FP32 (via `atomicMin`/`atomicMax` with float-to-uint cast) -- Integer atomics: add, sub, and, or, xor, min, max -- LDS atomics - -### Transcendental Functions -- sin, cos, tan, tanh -- exp, log, log2, log10 -- sqrt, rsqrt, rcp -- pow, cbrt - -### Memory Operations -- Global memory load/store (coalesced) -- LDS (shared memory) operations -- Memory barriers and fences - ---- - -## Inline Assembly Tests (Architecture-Specific) - -The following sections document the inline assembly tests that verify hardware instruction paths -directly, using architecture-specific syntax for CDNA (gfx9x) and RDNA4 (gfx12). - -### VMEM (Vector Memory) Operations - Comprehensive Inline ASM Tests - -This test exercises all memory instruction types using inline assembly to verify -the hardware memory paths are functioning correctly. - -**Memory Instruction Categories:** - -| Category | CDNA Syntax (gfx9x) | RDNA4 Syntax (gfx12) | Description | -|----------|---------------------|----------------------|-------------| -| **Flat** | `flat_load_dword` | `flat_load_b32` | Unified addressing (global/LDS/scratch) | -| **Global** | `global_load_dword` | `global_load_b32` | Direct global memory access | -| **Buffer** | `buffer_load_dword` | `buffer_load_b32` | Descriptor-based memory access | -| **Scratch** | `scratch_load_dword` | `scratch_load_b32` | Private per-thread stack | -| **LDS** | `ds_read_b32` | `ds_read_b32` | Shared memory (same on all archs) | - -**Data Widths Supported:** - -| Suffix | Size | CDNA Name | RDNA4 Name | -|--------|------|-----------|------------| -| b8/ubyte | 8-bit | `*_ubyte` | `*_b8` | -| b16/ushort | 16-bit | `*_ushort` | `*_b16` | -| b32/dword | 32-bit | `*_dword` | `*_b32` | -| b64/dwordx2 | 64-bit | `*_dwordx2` | `*_b64` | -| b128/dwordx4 | 128-bit | `*_dwordx4` | `*_b128` | - -**Flat Memory Instructions:** -```asm -; CDNA (gfx90a/gfx942/gfx950) -flat_load_dword vdst, vaddr [offset:N] [glc] [slc] -flat_store_dword vaddr, vdata [offset:N] [glc] [slc] - -; RDNA4 (gfx1200/gfx1201/) -flat_load_b32 vdst, vaddr [offset:N] -flat_store_b32 vaddr, vdata [offset:N] -``` - -**Global Memory Instructions:** -```asm -; CDNA (gfx90a/gfx942/gfx950) -global_load_dword vdst, vaddr, saddr [offset:N] -global_store_dword vaddr, vdata, saddr [offset:N] - -; RDNA4 (gfx1200/gfx1201/) -global_load_b32 vdst, vaddr, saddr [offset:N] -global_store_b32 vaddr, vdata, saddr [offset:N] -``` - -**LDS (DS) Instructions:** -```asm -; Same syntax on all architectures -ds_read_b32 vdst, vaddr [offset:N] -ds_write_b32 vaddr, vdata [offset:N] -ds_read2_b32 vdst0:vdst1, vaddr offset0:N offset1:M -ds_write2_b32 vaddr, vdata0, vdata1 offset0:N offset1:M -``` - -**Architecture Support:** - -| Instruction Type | gfx90a | gfx942 | gfx950 | gfx1150/1151 | gfx1200/1201 | | -|------------------|:------:|:------:|:------:|:------------:|:-------:| -| flat_load/store | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| global_load/store | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| buffer_load/store | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| scratch_load/store | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| ds_read/write | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| global_load_async | ❌ | ⚠️ | ✅ | ✅ | ✅ | ✅ | -| tensor_load_to_lds | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | - -### Atomic Operations - Comprehensive Inline ASM Tests - -This test exercises all atomic instruction types using inline assembly to verify -the hardware atomic paths are functioning correctly. - -**Atomic Instruction Categories:** - -| Category | Instructions | Description | -|----------|--------------|-------------| -| **Global INT** | `global_atomic_add/sub/and/or/xor/min/max/swap` | Integer atomics on global memory | -| **Global FP32** | `global_atomic_add_f32` | FP32 atomic add | -| **Global FP64** | `global_atomic_add_f64/min_f64/max_f64` | FP64 atomics (CDNA/ only) | -| **Flat INT** | `flat_atomic_add/sub/min/max` | Integer atomics (unified addressing) | -| **DS INT** | `ds_add_u32/sub_u32/min_i32/max_u32` | LDS integer atomics | -| **DS FP32** | `ds_add_f32/min_f32/max_f32` | LDS FP32 atomics | -| **Packed FP16** | `global_atomic_pk_add_f16` | Add two FP16 values atomically | -| **Packed BF16** | `global_atomic_pk_add_bf16` | Add two BF16 values atomically | -| **CAS** | `global_atomic_cmpswap` | Compare-and-swap | - -**Naming Convention Changes (RDNA4/gfx12):** - -| CDNA (gfx9x) | RDNA4 (gfx12) | -|--------------|---------------| -| `global_atomic_add` | `global_atomic_add_u32` | -| `global_atomic_smax` | `global_atomic_max_i32` | -| `flat_atomic_add` | `flat_atomic_add_u32` | -| `global_atomic_cmpswap` | `global_atomic_cmpswap_b32` | -| Return flag: `glc` | Return flag: `th:TH_ATOMIC_RETURN` | - -**Architecture Support:** - -| Atomic Type | gfx90a | gfx942 | gfx950 | gfx1150/51 | gfx1200/01 | | -|-------------|:------:|:------:|:------:|:----------:|:-------:| -| INT32/INT64 atomics | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| FP32 atomic add | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| FP64 atomic add/min/max | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | -| Packed FP16 atomic add | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| Packed BF16 atomic add | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | -| DS FP32 atomics | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -| DS FP64 atomics | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | -| CAS operations | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | - -**Inline Assembly Examples:** - -```asm -; CDNA - Global atomic add (integer with return) -global_atomic_add %0, %1, %2, off glc -s_waitcnt vmcnt(0) - -; RDNA4 - Global atomic add (integer with return) -global_atomic_add_u32 %0, %1, %2, off th:TH_ATOMIC_RETURN -s_waitcnt vmcnt(0) - -; DS atomic add (same on all architectures) -ds_add_rtn_u32 %0, %1, %2 -s_waitcnt lgkmcnt(0) - -; Packed FP16 atomic add -global_atomic_pk_add_f16 %0, %1, %2, off glc ; CDNA -global_atomic_pk_add_f16 %0, %1, %2, off th:TH_ATOMIC_RETURN ; RDNA4 -``` - -### Architecture Detection and Conditional Compilation - -The inline ASM tests use preprocessor macros to detect the target architecture and use -the correct instruction syntax: - -```cpp -#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) - // CDNA syntax: dword suffix, glc for return - asm volatile("global_atomic_add %0, %1, %2, off glc" : "=v"(old) : "v"(addr), "v"(val) : "memory"); - -#elif defined(__gfx1150__) || defined(__gfx1151__) || defined(__gfx1152__) || defined(__gfx1200__) || defined(__gfx1201__) - // RDNA 3.5 APU / RDNA4 (GFX12-style): explicit type suffix, TH_ATOMIC_RETURN for return - asm volatile("global_atomic_add_u32 %0, %1, %2, off th:TH_ATOMIC_RETURN" : "=v"(old) : "v"(addr), "v"(val) : "memory"); -#endif -``` - -**Architecture Macros:** -| Macro | Architecture | GPU | -|-------|--------------|-----| -| `__gfx90a__` | CDNA2 | MI250/MI250X | -| `__gfx942__` | CDNA3 | MI300/MI300X/MI325X | -| `__gfx950__` | CDNA4 | MI350X/MI355X | -| `__gfx1150__` | RDNA 3.5 | Strix Point | -| `__gfx1151__` | RDNA 3.5 | Strix Halo | -| `__gfx1152__` | RDNA 3.5 | Krackan | -| `__gfx1200__` | RDNA4 | RX 9060 series | -| `__gfx1201__` | RDNA4 | RX 9070 XT/9070 | - -Note: Not test on MI308X and MI300A yet ---- - - -### Dual-Issue VALU (gfx942+) - -**Same-Wave Co-Issue (VOPD/VOPD3)** - Available on MI300, MI350 : -- Uses VOPD encoding to execute 2 operations from the same wave in parallel -- X instruction uses Core MACC, Y instruction uses Side MACC -- VOPD3 extends this with 3 source operands per instruction - -**Cross-Wave Dual-Issue** - MI350 (gfx950): -MI350 can issue 2 VALU instructions from different waves per cycle under certain conditions: -- Both waves must be Wave32 -- Both must be single-cycle instructions -- One uses DP-MACC (FP64), other uses Core/Side-MACC (FP32/FP16) -- Neither uses DPP, Transcendentals, or WMMA - -**Dual-Issue Eligible Patterns:** -- FP64 operations + FP32 operations (from different waves) -- Integer operations + FP32 operations - -**Dual-Issue Blockers:** -- VOPD/VOPD3 instructions -- DPP instructions -- WMMA/XDL/Matrix instructions -- Transcendentals (sin, cos, exp, log) -- Multi-cycle instructions - -### MFMA (Matrix Fused Multiply-Add) Operations (gfx90a+/CDNA) - -*This section lists representative MFMA intrinsics. The kernel feeds **non-zero** packed operands (scalars/vectors as required per opcode) and requires **finite** FP accumulators with **at least one non-zero** `float`/`double` lane, or **at least one non-zero** `int` lane for INT8 MFMA—not reference-matrix numerical validation.* - -Matrix operations using dedicated Matrix Cores. For maintained MFMA / roofline work, see `src/roofline/benchmark/` in rocprofiler-compute (the older rocm-amdgpu-bench repo is deprecated). - -**MI250 (gfx90a/CDNA2):** -- `__builtin_amdgcn_mfma_f32_32x32x2f32` - FP32 (4096 ops/inst) -- `__builtin_amdgcn_mfma_f64_16x16x4f64` - FP64 (2048 ops/inst) -- `__builtin_amdgcn_mfma_f32_32x32x8f16` - FP16 (16384 ops/inst) -- `__builtin_amdgcn_mfma_f32_32x32x4bf16` - BF16 (8192 ops/inst) -- `__builtin_amdgcn_mfma_i32_32x32x8i8` - INT8 (16384 ops/inst) - -**MI300 (gfx942/CDNA3) - Enhanced:** -- All MI250 ops plus doubled K dimensions: -- `__builtin_amdgcn_mfma_f32_32x32x8bf16_1k` - BF16 (16384 ops/inst) -- `__builtin_amdgcn_mfma_i32_32x32x16_i8` - INT8 (32768 ops/inst) -- `__builtin_amdgcn_mfma_f32_32x32x16_fp8_fp8` - FP8 (32768 ops/inst) **NEW** - -**MI350 (gfx950/CDNA4) - Scaled F8F6F4:** -- All MI300 ops plus: -- `__builtin_amdgcn_mfma_scale_f32_32x32x64_f8f6f4` - FP8/BF8/FP6/FP4 (131072 ops/inst) - -### WMMA (Wave Matrix Multiply Accumulate) Operations (RDNA4/gfx12) - -*Same scope as MFMA: **non-zero** A/B fragments (indexed using **lane_id**), FP WMMA requires **finite** outputs with **at least one non-zero** lane in the per-lane accumulator, INT WMMA requires **at least one non-zero** lane—not exhaustive conformance. RDNA 3.5 (gfx115x) uses the 16-wide-input WMMA builtins; RDNA4 (gfx120x) uses the `_w32_gfx12` three-operand builtins—see `mega_kernel.hip`.* - -WMMA instructions are the matrix acceleration approach for RDNA architecture (vs MFMA for CDNA). -Reference: [AMD GPUOpen - Using Matrix Cores on RDNA4](https://gpuopen.com/learn/using_matrix_core_amd_rdna4/) - -**WMMA Performance (FLOPS/clock/CU):** -| Format | RDNA2 (RX 6950) | RDNA3 (RX 7900) | RDNA4 (RX 9070/) | -|--------|:---------------:|:---------------:|:---------------------:| -| FP16 | 256 | 512 | 1024 | -| BF16 | N/A | 512 | 1024 | -| INT8 | 512 | 512 | 2048 | - -**WMMA Intrinsic Format:** -``` -__builtin_amdgcn_wmma__16x16x16__w32_gfx12 -``` - -**Available WMMA Intrinsics (gfx12/RDNA4):** -- `__builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12` - FP16 → FP32 (16384 ops) -- `__builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12` - BF16 → FP32 (16384 ops) -- `__builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12` - INT8 → INT32 (16384 ops) -- `__builtin_amdgcn_wmma_f16_16x16x16_f16_w32_gfx12` - FP16 → FP16 -- `__builtin_amdgcn_wmma_bf16_16x16x16_bf16_w32_gfx12` - BF16 → BF16 - -**WMMA Matrix Layout:** -- 16x16 matrix distributed across Wave32 (32 lanes) -- Each lane holds 8 elements (8 × 32 = 256 = 16 × 16) -- Matrix A: Column-major (transposed) -- Matrices B, C, D: Row-major -- `lane_id = threadIdx.x % warpSize` (matches `mega_kernel.hip`) -- `laneWrapped = lane_id % 16` (column index) -- `laneGroup = lane_id / 16` (upper/lower half) - -**Key RDNA4 WMMA Differences vs RDNA3:** -- VGPR layout changed (NOT backward compatible) -- All intrinsics have `_gfx12` suffix -- Wave32 only (no Wave64 WMMA) -- 2x throughput improvement vs RDNA3 - -**Supported on:** -- Strix Point (gfx1150) / Strix Halo (gfx1151) -- RX 9070 XT/9070 (gfx1201) -- RX 9060 series (gfx1200) -- Bypassed on CDNA architectures (use MFMA instead) - -### DOT Product Operations (CDNA) -- V_DOT4_U32_U8 (4-element unsigned INT8 dot product) -- V_DOT8_U32_U4 (8-element unsigned INT4 dot product) - -### Lane/Thread Information -- Lane ID builtins -- Wavefront size detection - -### Strix / Strix Halo / Krackan (RDNA 3.5 / gfx1150 / gfx1151 / gfx1152) Specific Features - -*Background on RDNA 3.5 APUs; coverage here is limited to paths the matrix marks as tested (e.g. WMMA/TEX), not every fixed-function block.* - -RDNA 3.5 is the integrated GPU in Strix Point (gfx1150, 16 CUs), Strix Halo (gfx1151, up to 40 CUs), and Krackan (gfx1152) client APUs: - -1. **Wave32 Native** - - Native Wave32 execution; optimized for mobile and APU workloads. - -2. **WMMA (Wave Matrix Multiply Accumulate)** - - 16x16x16 matrix operations (FP16/BF16/INT8), same ISA as RDNA4/gfx12. - - Supported via `__builtin_amdgcn_wmma_*_w32_gfx12` intrinsics. - -3. **VOPD Same-Wave Dual-Issue** - - Same-wave dual-issue VALU via VOPD encoding (similar to CDNA3/CDNA4). - -4. **Scalar FP and Vector Hints** - - Scalar floating-point in the scalar unit; `s_singleuse_vdst` hint. - - Note: FP8/BF8 conversion builtins (`__builtin_amdgcn_cvt_pk_fp8_f32`) are NOT available on RDNA 3.5 APUs. - -5. **Async LDS and WGP** - - Async LDS operations; WGP (Workgroup Processor) architecture. - - - No Tensor Data Mover; no hardware FP64 atomics (consumer iGPU). - - Unified `s_waitcnt` (pre–gfx12 split waits). - - -## RDNA4 Consumer (RX 9070 XT/gfx1200/gfx1201) Specific Features - -*Consumer RDNA4 background; this kernel focuses on VALU/WMMA/FP8/async-LDS-style paths in the matrix—not a graphics or fixed-function survey.* - -The following features are available on RDNA4 consumer GPUs (RX 9000 series): - -1. **Wave32 Native Architecture** - - Native Wave32 execution (Wave64 emulated) - - Optimized for gaming and inference workloads - -2. **FP8/BF8 OCP Format Support** - - Open Compute Platform (OCP) FP8/BF8 format - - `__builtin_amdgcn_cvt_pk_fp8_f32` - - `__builtin_amdgcn_cvt_pk_bf8_f32` - -3. **AI Inference Accelerators** - - Hardware acceleration for INT8/FP8 inference *(not separately benchmarked here—WMMA/FP8/dot coverage follows the matrix)* - -4. **Async LDS Operations** - - `global_load_async_to_lds_b*` instructions - - Similar to CDNA4 but optimized for consumer workloads - -5. **WGP (Workgroup Processor) Architecture** - - 2 CUs per WGP - - Improved SIMD scheduling - -## Building - -### Prerequisites -- ROCm 6.0+ with HIP -- hipcc compiler -- AMD GPU (e.g. Strix Halo / gfx1151, or other supported GFXIP) - -### Using Make - -```bash -# Build for gfx1151 (default; override with GPU_ARCH=gfx950, etc.) -make - -# Build for specific architectures -make gfx950 # MI350/MI355X (CDNA4) -make gfx942 # MI300/MI300X/MI325X (CDNA3) -make gfx90a # MI250/MI250X (CDNA2) -make gfx1150 # Strix Point (RDNA 3.5) -make gfx1151 # Strix Halo (RDNA 3.5) -make gfx1152 # Krackan (RDNA 3.5) -make gfx1201 # RX 9070 XT/9070 (RDNA4) -make gfx1200 # RX 9060 series (RDNA4) -make gfx1250 # gfx1250 - -# Build for all architectures (all ARCHES in Makefile) -make all-arch - -# Build and run -make run - -# Show help -make help - -# Clean build artifacts -make clean -``` - -### Manual Build - -```bash -# For MI350 (CDNA4) -hipcc -O2 -std=c++17 --offload-arch=gfx950 -o mega_kernel_test main.cpp mega_kernel.hip - - -# For Strix Point/Strix Halo/Krackan (RDNA 3.5) -hipcc -O2 -std=c++17 --offload-arch=gfx1150 -o mega_kernel_test main.cpp mega_kernel.hip # Strix Point -hipcc -O2 -std=c++17 --offload-arch=gfx1151 -o mega_kernel_test main.cpp mega_kernel.hip # Strix Halo -hipcc -O2 -std=c++17 --offload-arch=gfx1152 -o mega_kernel_test main.cpp mega_kernel.hip # Krackan - -# For gfx1250 -hipcc -O2 -std=c++17 --offload-arch=gfx1250 -o mega_kernel_test_gfx1250 main.cpp mega_kernel.hip -``` - -## Running - -```bash -# Run with default settings (batch_size=1024) -./mega_kernel_test - -# Run with custom batch size -./mega_kernel_test -b 4096 - -# Run with larger batch size for stress testing -./mega_kernel_test --batch-size 65536 - -# Run with custom batch size and block size (64-256, power of 2) -./mega_kernel_test -b 65536 -t 256 - -# gfx1250 metric-health profile workload (default block size 256; do not use -t 32) -./mega_kernel_test_gfx1250 -b 655360 - -# Run kernel multiple times (for benchmarking/profiling) -./mega_kernel_test -n 100 - -# Run with larger batch and multiple iterations -./mega_kernel_test -b 65536 -n 50 - -# Run with only inline assembly MFMA tests -./mega_kernel_test --mfma-mode asm - -# Run with only HIP builtin MFMA tests -./mega_kernel_test -m builtin - -# Run both ASM and builtin MFMA tests (default) -./mega_kernel_test -m both - -# Show help -./mega_kernel_test --help -``` - -### Command-Line Options - -| Option | Description | Default | -|--------|-------------|---------| -| `-b, --batch-size ` | Set batch/problem size (64 to 16M) | 1024 | -| `-t, --block-size ` | Set thread block size (power of 2, 64-256) | 256 | -| `-n, --num-iterations ` | Number of kernel iterations (1 to 10000) | 5 | -| `-m, --mfma-mode ` | MFMA test mode: `both`, `asm`, or `builtin` (see note) | both | -| `-v, --verbose` | Enable verbose output | off | -| `-h, --help` | Show help message | - | - -**Note on MFMA mode:** MFMA instructions require complex accumulator register allocation that is difficult to express in inline assembly constraints. As a result, the MFMA tests primarily use HIP builtins which properly handle register allocation. The `--mfma-mode` option controls which test paths are exercised: -- `builtin` (recommended): Uses HIP builtins (e.g., `__builtin_amdgcn_mfma_f32_32x32x2f32`) -- `asm`: Attempts inline assembly where supported (falls back to builtins for complex MFMA ops) -- `both` (default): Runs both code paths where applicable - - -``` -================================================================================ -AMD GPU MEGA KERNEL UNIT TEST -================================================================================ -Device Information: - Name: AMD Instinct MI350 - GCN Architecture: gfx950 - Compute Units: 304 - ... -================================================================================ -Detected Platform: MI350 (CDNA4/gfx950) -================================================================================ - -Running GPU Mega Kernel for MI350 (CDNA4/gfx950)... - -TEST RESULTS -================================================================================ - -[Category: Warp/Wave Operations] - Warp Shuffle (shfl/shfl_xor/shfl_up/down) : PASS (4/1) - Warp Ballot (__ballot/__any/__all) : PASS (4/1) - ... - -[Category: Atomic Operations] - Atomic Add FP64 (HW atomic on CDNA) : PASS (4/1) - ... - - -SUMMARY -================================================================================ -Test Categories Passed: 44 / 44 -Test Categories Failed: 0 -Test Categories Bypassed: 0 (N/A for this architecture) -Kernel Execution Time: 0.523 ms - -*** OVERALL: PASSED (44/44, 0 bypassed) *** -``` - -### Sample Output (gfx1151 - Strix Halo) - -``` -================================================================================ -AMD GPU MEGA KERNEL UNIT TEST -================================================================================ -Device Information: - Name: AMD Radeon(TM) Graphics - GCN Architecture: gfx1151 - Compute Units: 40 - ... -================================================================================ -Detected Platform: Strix/Strix Halo/Krackan (RDNA3.5/gfx1150,gfx1151,gfx1152) -================================================================================ - -Running GPU Mega Kernel for Strix/Strix Halo/Krackan (RDNA3.5/gfx1150,gfx1151,gfx1152)... - -TEST RESULTS -================================================================================ - -[Category: Warp/Wave Operations] - Warp Shuffle (shfl/shfl_xor/shfl_up/down) : PASS (4/1) - ... - -[Category: Atomic Operations] - Atomic Add FP64 (HW atomic on CDNA) : BYPASSED (N/A) - ... - -[Category: Dual-Issue VALU (MI350/gfx950 specific)] - Dual-Issue Patterns (FP64+FP32 co-exec) : BYPASSED (N/A) - - -SUMMARY -================================================================================ -Test Categories Passed: 35 / 35 -Test Categories Failed: 0 -Test Categories Bypassed: 9 (N/A for this architecture) -Kernel Execution Time: 0.312 ms - -*** OVERALL: PASSED (35/35, 9 bypassed) *** -``` - -## Architecture Comparison - -Informational hardware notes (not a full map of what this sample executes). For **async LDS in this binary**, see the main compatibility matrix above (gfx942 is off here). - -| Feature | MI250 (gfx90a) | MI300 (gfx942) | MI350 (gfx950) | Strix/Krackan (gfx115x) | RX 9070 XT (gfx1201) | gfx1250 | -|---------|:-------------:|:--------------:|:--------------:|:-------------------:|:--------------------:|:---------------:| -| Architecture | CDNA2 | CDNA3 | CDNA4 | RDNA 3.5 | RDNA4 | gfx1250 | -| FP8 Support | No | FNUZ | OCP | No | OCP | OCP | -| FP6/FP4 Support | No | No | Yes | No | No | No | -| WMMA Wave64 | Yes | Yes | No (Wave32) | No | No | No | -| VGPRs per Wave32 | 512 | 512 | 1024 | 512 | 512 | — | -| Packed FP16 Atomics | Yes | Yes | Yes | Yes | Yes | Yes | -| Packed BF16 Atomics | No | Yes | Yes | Yes | Yes | Yes | -| Dual VALU Issue | No | VOPD | Yes | VOPD | No | Yes | -| Async LDS (hardware) | No | Limited† | Yes | Yes | Yes | Yes | -| MFMA Matrix Ops | Yes | Yes | Yes | No | No | No | -| WMMA Matrix Ops | No | No | No | Yes | Yes | Yes (gfx1250) | - -† This sample still treats gfx942 as without the async-LDS builtins path; see `mega_kernel_device_arch.h`. - -### Memory/Atomic Instruction Syntax - -| Instruction Type | CDNA (gfx9x) | RDNA4 (gfx12) | -|------------------|--------------|---------------| -| Load 32-bit | `*_load_dword` | `*_load_b32` | -| Load 64-bit | `*_load_dwordx2` | `*_load_b64` | -| Store 32-bit | `*_store_dword` | `*_store_b32` | -| Atomic add | `global_atomic_add` | `global_atomic_add_u32` | -| Atomic max (signed) | `global_atomic_smax` | `global_atomic_max_i32` | -| Return old value | `glc` flag | `th:TH_ATOMIC_RETURN` | -| Wait for VMEM | `s_waitcnt vmcnt(0)` | `s_waitcnt vmcnt(0)` | -| Wait for LDS | `s_waitcnt lgkmcnt(0)` | `s_waitcnt lgkmcnt(0)` | - -## License - -Copyright (c) 2026 - For testing purposes - -## References - -- [AMD Instinct MI350/ Architecture](https://rocm.docs.amd.com/) -- [AMD Radeon RX 9000 Series](https://www.amd.com/radeon/) -- [HIP Programming Guide](https://rocm.docs.amd.com/projects/HIP/) -- [AMD Matrix Instruction Calculator](https://github.com/ROCm/amd_matrix_instruction_calculator) diff --git a/skills/rocprof-compute-sample/mega_kernel/atomic_global_buffers.h b/skills/rocprof-compute-sample/mega_kernel/atomic_global_buffers.h deleted file mode 100644 index faf003b..0000000 --- a/skills/rocprof-compute-sample/mega_kernel/atomic_global_buffers.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. -// SPDX-License-Identifier: MIT -// -// Sizes and index layout for mega_kernel global atomic scratch buffers. -// main.cpp allocates and initializes; test_atomic_operations_asm / test_atomic_* -// in mega_kernel.hip must stay within these bounds. - -#pragma once - -// Element counts (device buffers sized with these in main.cpp). -#define MEGA_KERNEL_GLOBAL_INT_ELEMENTS 256 -#define MEGA_KERNEL_GLOBAL_FLOAT_ELEMENTS 256 -#define MEGA_KERNEL_GLOBAL_DOUBLE_ELEMENTS 64 - -// test_atomic_operations_asm (tid = global linear thread index): -// global_int_ptr: [tid % 64] — TEST 1 add / gfx12 asm add -// [(tid % 64) + 64] — TEST 1 max / gfx12 asm max -// [(tid % 64) + 128] — TEST 4 flat int add -// [(tid % 64) + 192] — TEST 10 CAS (expects initial 0) -// Max int index: 63 + 192 = 255 → need >= 256 int elements. -// -// global_f32_ptr: [tid % 64] — TEST 2 FP32 add -// [(tid % 64) + 64] — TEST 8 packed FP16 -// [(tid % 64) + 128] — TEST 9 packed BF16 -// Max float index: 63 + 128 = 191 → need >= 192 float elements (256 used). -// -// global_f64_ptr: [tid % 32], [(tid % 32) + 32] — TEST 3 (CDNA only) -// Max double index: 31 + 32 = 63 → need >= 64 double elements. -// -// Earlier in the same kernel launch (same buffers): -// test_atomic_add_f32: global_float[0] -// test_atomic_add_f64: global_double[0] -// test_atomic_int: global_int[0..6] diff --git a/skills/rocprof-compute-sample/mega_kernel/main.cpp b/skills/rocprof-compute-sample/mega_kernel/main.cpp deleted file mode 100644 index bbdce42..0000000 --- a/skills/rocprof-compute-sample/mega_kernel/main.cpp +++ /dev/null @@ -1,1014 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -/** - * AMD GPU Mega Kernel Unit Test - Host Code - * - * This file contains the host-side code to run the GPU mega kernel - * and report test results for various AMD GPU architectures. - * - * Supported architectures: - * - gfx90a (MI250/CDNA2) - * - gfx942 (MI300/CDNA3) - * - gfx950 (MI350/CDNA4) - * - gfx1150 (Strix Point / RDNA 3.5) - * - gfx1151 (Strix Halo / RDNA 3.5) - * - gfx1152 (Krackan / RDNA 3.5) - * - gfx1153 (RDNA 3.5) - * - gfx1200 (RX 9060/RDNA4) - * - gfx1201 (RX 9070 XT/RDNA4) - * - gfx1250 (gfx1250) - * - * Build: make (default GPU_ARCH=gfx1151; override e.g. GPU_ARCH=gfx1201 make) or manual: - * hipcc -O2 -g -std=c++17 -Wall -Wextra -fPIC --offload-arch=gfx1151 -I. -o - * mega_kernel_test mega_kernel.hip main.cpp - * - */ - -#include -#include -#include -#include -#include -#include -#if __has_include() -# include -#endif -#include - -// Cluster launch requires HIP 7.2+ (hipLaunchKernelEx + clusterDim attribute). -#if defined(HIP_VERSION) && (HIP_VERSION >= 70200000) -# define MEGA_KERNEL_HAS_CLUSTER_LAUNCH 1 -#endif - -#include "atomic_global_buffers.h" -#include "mega_kernel_host_arch.hpp" -#include "mega_kernel_types.h" - -// Default configuration -#define DEFAULT_BATCH_SIZE 1024 -#define DEFAULT_BLOCK_SIZE 256 -#define DEFAULT_NUM_ITERATIONS 5 -// rocprof-compute gfx1250 metric-health profile workload (block size stays default). -#define GFX1250_HEALTH_BATCH_SIZE 655360 -#define MIN_BATCH_SIZE 64 -#define MAX_BATCH_SIZE (1024 * 1024 * 16) // 16M elements max -#define MAX_NUM_ITERATIONS 10000 - -// Command-line options -struct TestConfig -{ - int batch_size; - int block_size; - int num_iterations; - MfmaMode mfma_mode; - bool verbose; - bool help; -}; - -void -print_usage(const char* prog_name) -{ - printf("Usage: %s [OPTIONS]\n\n", prog_name); - printf("AMD GPU Mega Kernel Unit Test\n\n"); - printf("Options:\n"); - printf(" -b, --batch-size Set batch/problem size (default: %d)\n", - DEFAULT_BATCH_SIZE); - printf(" Valid range: %d to %d\n", MIN_BATCH_SIZE, - MAX_BATCH_SIZE); - printf(" -t, --block-size Set thread block size (default: %d)\n", - DEFAULT_BLOCK_SIZE); - printf(" Must be power of 2, 64-256 (max 256 due to kernel " - "shared memory)\n"); - printf(" -n, --num-iterations Number of kernel iterations (default: %d)\n", - DEFAULT_NUM_ITERATIONS); - printf(" Valid range: 1 to %d\n", MAX_NUM_ITERATIONS); - printf(" -m, --mfma-mode Set MFMA test mode (default: both)\n"); - printf(" Modes: both, asm, builtin\n"); - printf(" -v, --verbose Enable verbose output\n"); - printf(" -h, --help Show this help message\n"); - printf("\nExamples:\n"); - printf(" %s # Run with default settings\n", prog_name); - printf(" %s -b 4096 # Run with 4096 elements\n", prog_name); - printf(" %s -b 65536 -t 256 # Run with 64K elements, 256 threads/block\n", - prog_name); - printf(" %s -b %d # gfx1250 metric-health profile (default block %d)\n", - prog_name, GFX1250_HEALTH_BATCH_SIZE, DEFAULT_BLOCK_SIZE); - printf(" %s -n 100 # Run kernel 100 times\n", prog_name); - printf(" %s --mfma-mode asm # Test MFMA using inline assembly only\n", - prog_name); - printf(" %s -m builtin # Test MFMA using HIP builtins only\n", prog_name); -} - -TestConfig -parse_args(int argc, char** argv) -{ - TestConfig config; - config.batch_size = DEFAULT_BATCH_SIZE; - config.block_size = DEFAULT_BLOCK_SIZE; - config.num_iterations = DEFAULT_NUM_ITERATIONS; - config.mfma_mode = MFMA_MODE_BOTH; - config.verbose = false; - config.help = false; - - static struct option long_options[] = { { "batch-size", required_argument, 0, 'b' }, - { "block-size", required_argument, 0, 't' }, - { "num-iterations", required_argument, 0, - 'n' }, - { "mfma-mode", required_argument, 0, 'm' }, - { "verbose", no_argument, 0, 'v' }, - { "help", no_argument, 0, 'h' }, - { 0, 0, 0, 0 } }; - - int opt; - int option_index = 0; - while((opt = getopt_long(argc, argv, "b:t:n:m:vh", long_options, &option_index)) != - -1) - { - switch(opt) - { - case 'b': - config.batch_size = atoi(optarg); - if(config.batch_size < MIN_BATCH_SIZE) - { - fprintf(stderr, "Warning: batch_size too small, using minimum: %d\n", - MIN_BATCH_SIZE); - config.batch_size = MIN_BATCH_SIZE; - } - if(config.batch_size > MAX_BATCH_SIZE) - { - fprintf(stderr, "Warning: batch_size too large, using maximum: %d\n", - MAX_BATCH_SIZE); - config.batch_size = MAX_BATCH_SIZE; - } - break; - case 't': - config.block_size = atoi(optarg); - // Validate block size (must be power of 2, 64-256) - // NOTE: Max 256 due to fixed-size __shared__ arrays in kernel (e.g., - // vmem_lds_test_buffer[256]) - if(config.block_size < 64 || config.block_size > 256 || - (config.block_size & (config.block_size - 1)) != 0) - { - fprintf(stderr, - "Warning: invalid block_size (must be 64-256, power of 2), " - "using default: %d\n", - DEFAULT_BLOCK_SIZE); - config.block_size = DEFAULT_BLOCK_SIZE; - } - break; - case 'n': - config.num_iterations = atoi(optarg); - if(config.num_iterations < 1) - { - fprintf(stderr, - "Warning: num_iterations too small, using minimum: 1\n"); - config.num_iterations = 1; - } - if(config.num_iterations > MAX_NUM_ITERATIONS) - { - fprintf(stderr, - "Warning: num_iterations too large, using maximum: %d\n", - MAX_NUM_ITERATIONS); - config.num_iterations = MAX_NUM_ITERATIONS; - } - break; - case 'm': - if(strcmp(optarg, "asm") == 0) - { - config.mfma_mode = MFMA_MODE_ASM; - } - else if(strcmp(optarg, "builtin") == 0) - { - config.mfma_mode = MFMA_MODE_BUILTIN; - } - else if(strcmp(optarg, "both") == 0) - { - config.mfma_mode = MFMA_MODE_BOTH; - } - else - { - fprintf(stderr, "Warning: invalid mfma-mode '%s', using 'both'\n", - optarg); - config.mfma_mode = MFMA_MODE_BOTH; - } - break; - case 'v': config.verbose = true; break; - case 'h': config.help = true; break; - default: config.help = true; break; - } - } - - return config; -} - -// Macro for HIP error checking -#define HIP_CHECK(cmd) \ - do \ - { \ - hipError_t error = (cmd); \ - if(error != hipSuccess) \ - { \ - fprintf(stderr, "HIP Error: %s at %s:%d\n", hipGetErrorString(error), \ - __FILE__, __LINE__); \ - exit(EXIT_FAILURE); \ - } \ - } while(0) - -void -print_separator() -{ - printf("=============================================================================" - "===\n"); -} - -void -print_test_result(const char* test_name, int passed, int expected = 1) -{ - // Width of 48 to accommodate longest test name: "Async LDS Store - // (global_store_async_from_lds)" - if(passed < 0) - { - // Negative value indicates test was bypassed (not applicable to this arch) - printf(" %-48s : \033[33mBYPASSED\033[0m (N/A)\n", test_name); - } - else if(passed >= expected) - { - printf(" %-48s : \033[32mPASS\033[0m (%d/%d)\n", test_name, passed, expected); - } - else - { - printf(" %-48s : \033[31mFAIL\033[0m (%d/%d)\n", test_name, passed, expected); - } -} - -// Global variable to store detected architecture for later use -static char g_arch_name[64] = ""; -// 0=unknown, 1=gfx90a, 2=gfx942, 3=gfx950, 4=gfx1200, 5=gfx1201, 6=gfx115x, 7=gfx1250 -static int g_arch_type = 0; - -namespace { - -constexpr unsigned kClusterDimX = 2; -constexpr unsigned kClusterDimY = 1; -constexpr unsigned kClusterDimZ = 1; - -unsigned -round_up_grid_x_for_cluster(int num_blocks) -{ - unsigned grid_x = static_cast(num_blocks > 0 ? num_blocks : 1); - if(grid_x % kClusterDimX != 0) - { - grid_x += kClusterDimX - (grid_x % kClusterDimX); - } - return grid_x; -} - -void -launch_mega_kernel(dim3 grid, - dim3 block, - size_t shared_mem_bytes, - TestResults* d_results, - float* d_global_float, - double* d_global_double, - int* d_global_int, - float* d_input, - float* d_output, - float* d_async_lds_src, - float* d_async_lds_dst, - int* d_tdm_src, - int* d_tdm_dst, - int buffer_size, - int mfma_mode, - hipTextureObject_t tex_obj, - hipSurfaceObject_t surf_obj) -{ -#if defined(MEGA_KERNEL_HAS_CLUSTER_LAUNCH) - if(g_arch_type == 7) - { - hipLaunchAttribute cluster_attr{}; - cluster_attr.id = hipLaunchAttributeClusterDimension; - cluster_attr.value.clusterDim.x = kClusterDimX; - cluster_attr.value.clusterDim.y = kClusterDimY; - cluster_attr.value.clusterDim.z = kClusterDimZ; - - hipLaunchConfig_t launch_config{}; - launch_config.gridDim = grid; - launch_config.blockDim = block; - launch_config.dynamicSmemBytes = shared_mem_bytes; - launch_config.stream = nullptr; - launch_config.attrs = &cluster_attr; - launch_config.numAttrs = 1; - - HIP_CHECK(hipLaunchKernelEx(&launch_config, - mega_kernel, - d_results, - d_global_float, - d_global_double, - d_global_int, - d_input, - d_output, - d_async_lds_src, - d_async_lds_dst, - d_tdm_src, - d_tdm_dst, - buffer_size, - mfma_mode, - tex_obj, - surf_obj)); - return; - } -#endif - hipLaunchKernelGGL(mega_kernel, - grid, - block, - shared_mem_bytes, - 0, - d_results, - d_global_float, - d_global_double, - d_global_int, - d_input, - d_output, - d_async_lds_src, - d_async_lds_dst, - d_tdm_src, - d_tdm_dst, - buffer_size, - mfma_mode, - tex_obj, - surf_obj); - HIP_CHECK(hipGetLastError()); -} - -} // namespace - -void -print_device_info() -{ - int device; - hipDeviceProp_t props; - - HIP_CHECK(hipGetDevice(&device)); - HIP_CHECK(hipGetDeviceProperties(&props, device)); - - mega_kernel_host::detect_architecture(props.gcnArchName, g_arch_name, sizeof(g_arch_name), - &g_arch_type); - - print_separator(); - printf("AMD GPU MEGA KERNEL UNIT TEST\n"); - print_separator(); - printf("Device Information:\n"); - printf(" Name: %s\n", props.name); - printf(" GCN Architecture: %s\n", props.gcnArchName); - printf(" Compute Units: %d\n", props.multiProcessorCount); - printf(" Max Threads/Block: %d\n", props.maxThreadsPerBlock); - printf(" Warp Size: %d\n", props.warpSize); - printf(" Global Memory: %.2f GB\n", - props.totalGlobalMem / (1024.0 * 1024.0 * 1024.0)); - printf(" Shared Memory/Block: %zu KB\n", props.sharedMemPerBlock / 1024); - printf(" Clock Rate: %d MHz\n", props.clockRate / 1000); - print_separator(); - printf("Detected Platform: %s\n", - mega_kernel_host::arch_description(g_arch_type)); - print_separator(); -} - -int -main(int argc, char** argv) -{ - // Parse command-line arguments - TestConfig config = parse_args(argc, argv); - - if(config.help) - { - print_usage(argv[0]); - return 0; - } - - // Print device info - print_device_info(); - - // Configuration from command-line or defaults - const int BUFFER_SIZE = config.batch_size; - const int BLOCK_SIZE = config.block_size; - const int NUM_BLOCKS = (BUFFER_SIZE + BLOCK_SIZE - 1) / BLOCK_SIZE; - const unsigned GRID_X = - (g_arch_type == 7) ? round_up_grid_x_for_cluster(NUM_BLOCKS) - : static_cast(NUM_BLOCKS > 0 ? NUM_BLOCKS : 1); - - // Global atomic scratch: sizes and index layout — see atomic_global_buffers.h - const int GLOBAL_INT_SIZE = MEGA_KERNEL_GLOBAL_INT_ELEMENTS; - const int GLOBAL_FLOAT_SIZE = MEGA_KERNEL_GLOBAL_FLOAT_ELEMENTS; - const int GLOBAL_DOUBLE_SIZE = MEGA_KERNEL_GLOBAL_DOUBLE_ELEMENTS; - - printf("Test Configuration:\n"); - printf(" Batch/Buffer Size: %d elements\n", BUFFER_SIZE); - printf(" Block Size: %d threads\n", BLOCK_SIZE); - printf(" Number of Blocks: %d", NUM_BLOCKS); - if(g_arch_type == 7 && GRID_X != static_cast(NUM_BLOCKS)) - { - printf(" (grid x padded to %u for cluster launch)", GRID_X); - } - printf("\n"); - printf(" Total Threads: %u\n", GRID_X * static_cast(BLOCK_SIZE)); -#if defined(MEGA_KERNEL_HAS_CLUSTER_LAUNCH) - if(g_arch_type == 7) - { - printf(" Cluster Launch: enabled (%u,%u,%u)\n", kClusterDimX, kClusterDimY, - kClusterDimZ); - } -#endif - const char* mfma_mode_str = (config.mfma_mode == MFMA_MODE_ASM) ? "asm" - : (config.mfma_mode == MFMA_MODE_BUILTIN) ? "builtin" - : "both"; - printf(" MFMA Test Mode: %s\n", mfma_mode_str); - printf(" Num Iterations: %d\n", config.num_iterations); - if(config.verbose) - { - printf(" Verbose Mode: enabled\n"); - } - print_separator(); - - // Allocate host memory - TestResults h_results; - memset(&h_results, 0, sizeof(TestResults)); - - float* h_input = (float*) malloc(BUFFER_SIZE * sizeof(float)); - float* h_output = (float*) malloc(BUFFER_SIZE * sizeof(float)); - - // Initialize input buffer - for(int i = 0; i < BUFFER_SIZE; i++) - { - h_input[i] = (float) i; - h_output[i] = 0.0f; - } - - // Allocate device memory - TestResults* d_results; - float* d_global_float; - double* d_global_double; - int* d_global_int; - float* d_input; - float* d_output; - float* d_async_lds_src; - float* d_async_lds_dst; - int* d_tdm_src; - int* d_tdm_dst; - - const int ASYNC_LDS_SIZE = 256; // Size for async LDS test buffers - const int TDM_SIZE = 256; // Size for TDM test buffers (16x16 tensor) - - HIP_CHECK(hipMalloc(&d_results, sizeof(TestResults))); - HIP_CHECK(hipMalloc(&d_global_float, GLOBAL_FLOAT_SIZE * sizeof(float))); - HIP_CHECK(hipMalloc(&d_global_double, GLOBAL_DOUBLE_SIZE * sizeof(double))); - HIP_CHECK(hipMalloc(&d_global_int, GLOBAL_INT_SIZE * sizeof(int))); - HIP_CHECK(hipMalloc(&d_input, BUFFER_SIZE * sizeof(float))); - HIP_CHECK(hipMalloc(&d_output, BUFFER_SIZE * sizeof(float))); - HIP_CHECK(hipMalloc(&d_async_lds_src, ASYNC_LDS_SIZE * sizeof(float))); - HIP_CHECK(hipMalloc(&d_async_lds_dst, ASYNC_LDS_SIZE * sizeof(float))); - HIP_CHECK(hipMalloc(&d_tdm_src, TDM_SIZE * sizeof(int))); - HIP_CHECK(hipMalloc(&d_tdm_dst, TDM_SIZE * sizeof(int))); - - // Host-prepared zeros for global atomic buffers (kernel CAS etc. expect initial 0). - std::vector h_global_int(static_cast(GLOBAL_INT_SIZE), 0); - std::vector h_global_float(static_cast(GLOBAL_FLOAT_SIZE), 0.0f); - std::vector h_global_double(static_cast(GLOBAL_DOUBLE_SIZE), 0.0); - - // Initialize device memory - HIP_CHECK(hipMemset(d_results, 0, sizeof(TestResults))); - HIP_CHECK(hipMemcpy(d_global_int, h_global_int.data(), - h_global_int.size() * sizeof(int), hipMemcpyHostToDevice)); - HIP_CHECK(hipMemcpy(d_global_float, h_global_float.data(), - h_global_float.size() * sizeof(float), hipMemcpyHostToDevice)); - HIP_CHECK(hipMemcpy(d_global_double, h_global_double.data(), - h_global_double.size() * sizeof(double), hipMemcpyHostToDevice)); - HIP_CHECK( - hipMemcpy(d_input, h_input, BUFFER_SIZE * sizeof(float), hipMemcpyHostToDevice)); - HIP_CHECK(hipMemset(d_output, 0, BUFFER_SIZE * sizeof(float))); - HIP_CHECK(hipMemset(d_async_lds_src, 0, ASYNC_LDS_SIZE * sizeof(float))); - HIP_CHECK(hipMemset(d_async_lds_dst, 0, ASYNC_LDS_SIZE * sizeof(float))); - HIP_CHECK(hipMemset(d_tdm_src, 0, TDM_SIZE * sizeof(int))); - HIP_CHECK(hipMemset(d_tdm_dst, 0, TDM_SIZE * sizeof(int))); - - // Create texture and surface objects for gfx115x to exercise - // INSTS_TEX_LOAD (tex1Dfetch) and INSTS_TEX_STORE (surf1Dwrite). - hipTextureObject_t tex_obj = 0; - hipSurfaceObject_t surf_obj = 0; - float* d_surf_buffer = nullptr; - if(g_arch_type == 6) - { - hipResourceDesc resDesc; - memset(&resDesc, 0, sizeof(resDesc)); - resDesc.resType = hipResourceTypeLinear; - resDesc.res.linear.sizeInBytes = BUFFER_SIZE * sizeof(float); - resDesc.res.linear.desc = - hipCreateChannelDesc(32, 0, 0, 0, hipChannelFormatKindFloat); - - resDesc.res.linear.devPtr = d_input; - hipTextureDesc texDesc; - memset(&texDesc, 0, sizeof(texDesc)); - texDesc.normalizedCoords = 0; - texDesc.filterMode = hipFilterModePoint; - texDesc.addressMode[0] = hipAddressModeClamp; - HIP_CHECK(hipCreateTextureObject(&tex_obj, &resDesc, &texDesc, nullptr)); - - HIP_CHECK(hipMalloc(&d_surf_buffer, BUFFER_SIZE * sizeof(float))); - HIP_CHECK(hipMemset(d_surf_buffer, 0, BUFFER_SIZE * sizeof(float))); - resDesc.res.linear.devPtr = d_surf_buffer; - HIP_CHECK(hipCreateSurfaceObject(&surf_obj, &resDesc)); - } - - printf("Running GPU Mega Kernel for %s...\n", - mega_kernel_host::arch_description(g_arch_type)); - if(config.num_iterations > 1) - { - printf(" Running %d iterations...\n", config.num_iterations); - } - print_separator(); - - // Create events for timing - hipEvent_t start, stop; - HIP_CHECK(hipEventCreate(&start)); - HIP_CHECK(hipEventCreate(&stop)); - - // Compute dynamic shared memory size (must match kernel expectations) - // Layout in test_lds_operations: - // - lds_float: BLOCK_SIZE * sizeof(float) - // - lds_int: BLOCK_SIZE * sizeof(int) - // - lds_double: (BLOCK_SIZE / 2) * sizeof(double) - // - lds_atomic_counter: sizeof(int) for LDS atomic test - const size_t sharedMemBytes = - static_cast(BLOCK_SIZE) * sizeof(float) + // lds_float - static_cast(BLOCK_SIZE) * sizeof(int) + // lds_int - static_cast(BLOCK_SIZE / 2) * sizeof(double) + // lds_double - sizeof(int); // lds_atomic_counter - - // Record start time - HIP_CHECK(hipEventRecord(start, 0)); - - // Run kernel for the specified number of iterations - for(int iter = 0; iter < config.num_iterations; iter++) - { - // Reset device memory for each iteration (except first) - // NOTE: d_input must also be reset because test_vmem_operations modifies it - if(iter > 0) - { - HIP_CHECK(hipMemset(d_results, 0, sizeof(TestResults))); - HIP_CHECK(hipMemcpy(d_global_int, h_global_int.data(), - h_global_int.size() * sizeof(int), - hipMemcpyHostToDevice)); - HIP_CHECK(hipMemcpy(d_global_float, h_global_float.data(), - h_global_float.size() * sizeof(float), - hipMemcpyHostToDevice)); - HIP_CHECK(hipMemcpy(d_global_double, h_global_double.data(), - h_global_double.size() * sizeof(double), - hipMemcpyHostToDevice)); - HIP_CHECK(hipMemcpy(d_input, h_input, BUFFER_SIZE * sizeof(float), - hipMemcpyHostToDevice)); - HIP_CHECK(hipMemset(d_output, 0, BUFFER_SIZE * sizeof(float))); - } - - // Launch kernel (gfx1250 uses cluster dispatch when HIP 7.2+ is available) - launch_mega_kernel(dim3(GRID_X, 1, 1), - dim3(BLOCK_SIZE), - sharedMemBytes, - d_results, - d_global_float, - d_global_double, - d_global_int, - d_input, - d_output, - d_async_lds_src, - d_async_lds_dst, - d_tdm_src, - d_tdm_dst, - BUFFER_SIZE, - (int) config.mfma_mode, - tex_obj, - surf_obj); - } - - // Wait for all iterations to complete - HIP_CHECK(hipDeviceSynchronize()); - - // Record stop time - HIP_CHECK(hipEventRecord(stop, 0)); - HIP_CHECK(hipEventSynchronize(stop)); - - float total_elapsed_ms; - HIP_CHECK(hipEventElapsedTime(&total_elapsed_ms, start, stop)); - float avg_elapsed_ms = total_elapsed_ms / config.num_iterations; - - if(tex_obj != 0) - { - HIP_CHECK(hipDestroyTextureObject(tex_obj)); - } - if(surf_obj != 0) - { - HIP_CHECK(hipDestroySurfaceObject(surf_obj)); - } - if(d_surf_buffer != nullptr) - { - HIP_CHECK(hipFree(d_surf_buffer)); - } - - // Copy results back - HIP_CHECK( - hipMemcpy(&h_results, d_results, sizeof(TestResults), hipMemcpyDeviceToHost)); - HIP_CHECK(hipMemcpy(h_output, d_output, BUFFER_SIZE * sizeof(float), - hipMemcpyDeviceToHost)); - - // Verify memory operations output - bool memory_verified = true; - for(int i = 0; i < BUFFER_SIZE && memory_verified; i++) - { - float expected = h_input[i] * 2.0f + 1.0f; - if(fabsf(h_output[i] - expected) > 0.001f) - { - memory_verified = false; - printf("Memory verification failed at index %d: expected %.3f, got %.3f\n", i, - expected, h_output[i]); - } - } - - // Print results - printf("TEST RESULTS\n"); - print_separator(); - - printf("\n[Category: Warp/Wave Operations]\n"); - print_test_result("Warp Shuffle (shfl/shfl_xor/shfl_up/down)", - h_results.warp_shuffle_passed); - print_test_result("Warp Ballot (__ballot/__any/__all)", h_results.warp_ballot_passed); - print_test_result("Warp Permute (ds_bpermute/ds_permute)", - h_results.warp_permute_passed); - print_test_result("Warp Reduce (shuffle-based reduction)", - h_results.warp_reduce_passed); - - printf("\n[Category: FP8/BF8 Conversions (gfx942+/RDNA4/gfx1250)]\n"); - print_test_result("FP8 E4M3 Conversion", h_results.fp8_convert_passed); - print_test_result("FP8 E5M3 Conversion (gfx1250)", h_results.fp8_e5m3_convert_passed); - print_test_result("BF8 E5M2 Conversion", h_results.bf8_convert_passed); - - printf("\n[Category: FP16/BF16 Operations]\n"); - print_test_result("FP16 Arithmetic", h_results.fp16_convert_passed); - print_test_result("BF16 Conversion", h_results.bf16_convert_passed); - - printf("\n[Category: FP32/FP64 Arithmetic]\n"); - print_test_result("FP32 Arithmetic (add/sub/mul/div/fma)", - h_results.fp32_arith_passed); - print_test_result("FP64 Arithmetic (add/sub/mul/div/fma)", - h_results.fp64_arith_passed); - - printf("\n[Category: Integer Operations]\n"); - print_test_result("INT32 Arithmetic & Bit Ops", h_results.int32_arith_passed); - print_test_result("INT64 Arithmetic", h_results.int64_arith_passed); - - printf("\n[Category: Packed Operations]\n"); - print_test_result("Packed FP16 (half2)", h_results.pk_f16_passed); - print_test_result("Packed BF16", h_results.pk_bf16_passed); - print_test_result("Packed Add Operations", h_results.pk_add_passed); - - printf("\n[Category: Atomic Operations]\n"); - print_test_result("Atomic Add FP32", h_results.atomic_add_f32_passed); - print_test_result("Atomic Add FP64 (HW atomic on CDNA)", - h_results.atomic_add_f64_passed); - print_test_result("Atomic Min FP32", h_results.atomic_min_f32_passed); - print_test_result("Atomic Max FP32", h_results.atomic_max_f32_passed); - print_test_result("Atomic Integer Operations", h_results.atomic_int_passed); - print_test_result("LDS Atomic Operations", h_results.lds_atomic_passed); - - printf("\n[Category: Transcendental Functions]\n"); - print_test_result("Sine (sinf)", h_results.trans_sin_passed); - print_test_result("Cosine (cosf)", h_results.trans_cos_passed); - print_test_result("Exponential (expf)", h_results.trans_exp_passed); - print_test_result("Logarithm (logf)", h_results.trans_log_passed); - print_test_result("Square Root (sqrtf)", h_results.trans_sqrt_passed); - print_test_result("Reciprocal (1/x)", h_results.trans_rcp_passed); - print_test_result("Reciprocal Square Root (rsqrtf)", h_results.trans_rsq_passed); - - printf("\n[Category: Memory Operations]\n"); - print_test_result("Global Memory Load", h_results.global_load_passed); - print_test_result("Global Memory Store", h_results.global_store_passed); - print_test_result("LDS Load", h_results.lds_load_passed); - print_test_result("LDS Store", h_results.lds_store_passed); - print_test_result("Memory Output Verification", memory_verified ? 1 : 0); - - printf("\n[Category: DOT Product Operations]\n"); - print_test_result("DOT4 (4-element INT8 dot)", h_results.dot4_passed); - print_test_result("DOT8 (8-element INT4 dot)", h_results.dot8_passed); - - printf("\n[Category: MFMA Operations (gfx90a+/CDNA)]\n"); - print_test_result("MFMA FP32 (v_mfma_f32_32x32x2f32)", h_results.mfma_f32_passed); - print_test_result("MFMA FP64 (v_mfma_f64_16x16x4f64)", h_results.mfma_f64_passed); - print_test_result("MFMA FP16 (v_mfma_f32_32x32x8f16)", h_results.mfma_f16_passed); - print_test_result("MFMA BF16 (v_mfma_f32_32x32x*bf16)", h_results.mfma_bf16_passed); - print_test_result("MFMA INT8 (v_mfma_i32_32x32x*i8)", h_results.mfma_i8_passed); - print_test_result("MFMA FP8 (v_mfma_f32_32x32x16_fp8)", h_results.mfma_f8_passed); - - printf("\n[Category: Dual-Issue VALU (instruction patterns only)]\n"); - print_test_result("Dual-Issue Patterns (not scheduler-verified)", - h_results.dual_issue_passed); - - printf("\n[Category: Lane/Thread Information]\n"); - print_test_result("Lane ID Builtin", h_results.lane_id_passed); - print_test_result("Wavefront Size", h_results.wavefront_passed); - - printf("\n[Category: Async LDS Operations (gfx950+/RDNA4)]\n"); - print_test_result("Async LDS Load (global_load_async_to_lds)", - h_results.async_lds_load_passed); - print_test_result("Async LDS Store (global_store_async_from_lds)", - h_results.async_lds_store_passed); - - printf("\n[Category: TDM (Tensor Data Mover) Operations (gfx1250 only)]\n"); - print_test_result("TDM Tensor Load to LDS", h_results.tdm_load_passed); - print_test_result("TDM Tensor Store from LDS", h_results.tdm_store_passed); - - printf("\n[Category: Cluster Group Operations (gfx1250 only)]\n"); - print_test_result("Cluster Barrier (s_barrier_signal/wait)", - h_results.cluster_barrier_passed); - print_test_result("Cluster Workgroup Info", h_results.cluster_info_passed); - print_test_result("Cluster Load (cluster_load_b32)", h_results.cluster_load_passed); - - printf("\n[Category: WMMA Operations (RDNA4/gfx12/gfx1250 - Wave32)]\n"); - print_test_result("WMMA FP16 → FP32 (wmma_f32_16x16x16_f16)", - h_results.wmma_f16_passed); - print_test_result("WMMA BF16 → FP32 (wmma_f32_16x16x16_bf16)", - h_results.wmma_bf16_passed); - print_test_result("WMMA INT8 → INT32 (wmma_i32_16x16x16_iu8)", - h_results.wmma_i8_passed); - - printf("\n[Category: Cooperative Atomic Operations (gfx1250 only)]\n"); - print_test_result("Cooperative Atomic Load (32x4B/16x8B/8x16B)", - h_results.coop_atomic_load_passed); - print_test_result("Cooperative Atomic Store (32x4B/16x8B/8x16B)", - h_results.coop_atomic_store_passed); - - printf("\n[Category: VMEM (Vector Memory) Operations - Inline ASM]\n"); - print_test_result("Flat Load/Store (flat_load_b32/flat_store_b32)", - h_results.vmem_flat_passed); - print_test_result("Global Load/Store (global_load_b32/global_store_b32)", - h_results.vmem_global_passed); - print_test_result("Buffer Load/Store (buffer_load_dword)", - h_results.vmem_buffer_passed); - print_test_result("Scratch Memory (scratch_load/scratch_store)", - h_results.vmem_scratch_passed); - print_test_result("LDS Operations (ds_read_b32/ds_write_b32)", - h_results.vmem_lds_passed); - print_test_result("Texture Load (tex1Dfetch / INSTS_TEX_LOAD)", - h_results.vmem_tex_load_passed); - print_test_result("Texture Store (surf1Dwrite / INSTS_TEX_STORE)", - h_results.vmem_tex_store_passed); - - printf("\n[Category: Atomic Operations - Inline ASM]\n"); - print_test_result("Global INT Atomics (global_atomic_add/sub/min/max)", - h_results.atomic_global_int_passed); - print_test_result("Global FP32 Atomics (global_atomic_add_f32)", - h_results.atomic_global_f32_passed); - print_test_result("Global FP64 Atomics (global_atomic_add_f64/min/max)", - h_results.atomic_global_f64_passed); - print_test_result("Flat INT Atomics (flat_atomic_add)", - h_results.atomic_flat_int_passed); - print_test_result("DS INT Atomics (ds_add_u32/min_i32/max_u32)", - h_results.atomic_ds_int_passed); - print_test_result("DS FP32 Atomics (ds_add_f32/min_f32)", - h_results.atomic_ds_f32_passed); - print_test_result("DS FP64 Atomics (ds_add_f64)", h_results.atomic_ds_f64_passed); - print_test_result("Packed FP16 Atomics (global_atomic_pk_add_f16)", - h_results.atomic_pk_f16_passed); - print_test_result("Packed BF16 Atomics (global_atomic_pk_add_bf16)", - h_results.atomic_pk_bf16_passed); - print_test_result("CAS Atomics (global_atomic_cmpswap)", h_results.atomic_cas_passed); - print_test_result("Buffer Load (buffer_load_b32, gfx1250)", h_results.buffer_load_passed); - print_test_result("Buffer Store (buffer_store_b32, gfx1250)", - h_results.buffer_store_passed); - print_test_result("Multicast Load (broadcast read, all threads same addr)", - h_results.multicast_load_passed); - - print_separator(); - - // Summary - count passed, failed, and bypassed tests - int passed_categories = 0; - int bypassed_categories = 0; - int failed_categories = 0; - -// Helper macro to count test results -// > 0 = passed, < 0 = bypassed, == 0 = failed -#define COUNT_TEST(result) \ - if((result) > 0) \ - passed_categories++; \ - else if((result) < 0) \ - bypassed_categories++; \ - else \ - failed_categories++; - - COUNT_TEST(h_results.warp_shuffle_passed); - COUNT_TEST(h_results.warp_ballot_passed); - COUNT_TEST(h_results.warp_permute_passed); - COUNT_TEST(h_results.warp_reduce_passed); - COUNT_TEST(h_results.fp8_convert_passed); - COUNT_TEST(h_results.fp8_e5m3_convert_passed); - COUNT_TEST(h_results.bf8_convert_passed); - COUNT_TEST(h_results.fp16_convert_passed); - COUNT_TEST(h_results.bf16_convert_passed); - COUNT_TEST(h_results.fp32_arith_passed); - COUNT_TEST(h_results.fp64_arith_passed); - COUNT_TEST(h_results.int32_arith_passed); - COUNT_TEST(h_results.int64_arith_passed); - COUNT_TEST(h_results.pk_f16_passed); - COUNT_TEST(h_results.pk_bf16_passed); - COUNT_TEST(h_results.pk_add_passed); - COUNT_TEST(h_results.atomic_add_f32_passed); - COUNT_TEST(h_results.atomic_add_f64_passed); - COUNT_TEST(h_results.atomic_min_f32_passed); - COUNT_TEST(h_results.atomic_max_f32_passed); - COUNT_TEST(h_results.atomic_int_passed); - COUNT_TEST(h_results.lds_atomic_passed); - COUNT_TEST(h_results.trans_sin_passed); - COUNT_TEST(h_results.trans_cos_passed); - COUNT_TEST(h_results.trans_exp_passed); - COUNT_TEST(h_results.trans_log_passed); - COUNT_TEST(h_results.trans_sqrt_passed); - COUNT_TEST(h_results.trans_rcp_passed); - COUNT_TEST(h_results.trans_rsq_passed); - COUNT_TEST(h_results.global_load_passed); - COUNT_TEST(h_results.global_store_passed); - COUNT_TEST(h_results.lds_load_passed); - COUNT_TEST(h_results.lds_store_passed); - COUNT_TEST(h_results.dot4_passed); - COUNT_TEST(h_results.dot8_passed); - COUNT_TEST(h_results.mfma_f32_passed); - COUNT_TEST(h_results.mfma_f64_passed); - COUNT_TEST(h_results.mfma_f16_passed); - COUNT_TEST(h_results.mfma_bf16_passed); - COUNT_TEST(h_results.mfma_i8_passed); - COUNT_TEST(h_results.mfma_f8_passed); - COUNT_TEST(h_results.dual_issue_passed); - COUNT_TEST(h_results.lane_id_passed); - COUNT_TEST(h_results.wavefront_passed); - COUNT_TEST(h_results.async_lds_load_passed); - COUNT_TEST(h_results.async_lds_store_passed); - COUNT_TEST(h_results.tdm_load_passed); - COUNT_TEST(h_results.tdm_store_passed); - COUNT_TEST(h_results.cluster_barrier_passed); - COUNT_TEST(h_results.cluster_info_passed); - COUNT_TEST(h_results.cluster_load_passed); - COUNT_TEST(h_results.wmma_f16_passed); - COUNT_TEST(h_results.wmma_bf16_passed); - COUNT_TEST(h_results.wmma_i8_passed); - COUNT_TEST(h_results.coop_atomic_load_passed); - COUNT_TEST(h_results.coop_atomic_store_passed); - COUNT_TEST(h_results.vmem_flat_passed); - COUNT_TEST(h_results.vmem_global_passed); - COUNT_TEST(h_results.vmem_buffer_passed); - COUNT_TEST(h_results.vmem_scratch_passed); - COUNT_TEST(h_results.vmem_lds_passed); - COUNT_TEST(h_results.vmem_tex_load_passed); - COUNT_TEST(h_results.vmem_tex_store_passed); - COUNT_TEST(h_results.atomic_global_int_passed); - COUNT_TEST(h_results.atomic_global_f32_passed); - COUNT_TEST(h_results.atomic_global_f64_passed); - COUNT_TEST(h_results.atomic_flat_int_passed); - COUNT_TEST(h_results.atomic_ds_int_passed); - COUNT_TEST(h_results.atomic_ds_f32_passed); - COUNT_TEST(h_results.atomic_ds_f64_passed); - COUNT_TEST(h_results.atomic_pk_f16_passed); - COUNT_TEST(h_results.atomic_pk_bf16_passed); - COUNT_TEST(h_results.atomic_cas_passed); - COUNT_TEST(h_results.buffer_load_passed); - COUNT_TEST(h_results.buffer_store_passed); - COUNT_TEST(h_results.multicast_load_passed); - if(memory_verified) - passed_categories++; - else - failed_categories++; - -#undef COUNT_TEST - - int total_tests = - passed_categories + failed_categories; // Exclude bypassed from total - - printf("SUMMARY\n"); - print_separator(); - printf("Test Categories Passed: %d / %d\n", passed_categories, total_tests); - printf("Test Categories Failed: %d\n", failed_categories); - printf("Test Categories Bypassed: %d (N/A for this architecture)\n", - bypassed_categories); - printf("Kernel Iterations: %d\n", config.num_iterations); - printf("Total Execution Time: %.3f ms\n", total_elapsed_ms); - if(config.num_iterations > 1) - { - printf("Average Time/Iteration: %.3f ms\n", avg_elapsed_ms); - } - - if(failed_categories == 0) - { - printf("\n\033[32m*** OVERALL: PASSED (%d/%d, %d bypassed) ***\033[0m\n", - passed_categories, total_tests, bypassed_categories); - } - else - { - printf( - "\n\033[31m*** OVERALL: FAILED (%d/%d, %d failed, %d bypassed) ***\033[0m\n", - passed_categories, total_tests, failed_categories, bypassed_categories); - } - print_separator(); - - // Print architecture-specific feature summary - printf("\n%s SPECIFIC FEATURES TESTED:\n", - mega_kernel_host::arch_description(g_arch_type)); - - switch(g_arch_type) - { - case 1: // MI250/CDNA2 - printf(" - FP32/FP64/FP16/BF16 arithmetic\n"); - printf(" - Hardware FP64 atomics (add/min/max)\n"); - printf(" - DOT4/DOT8 integer dot products (v_dot4_u32_u8)\n"); - printf(" - MFMA (Matrix Fused Multiply-Add) instructions\n"); - printf(" - Wave64 operations\n"); - printf(" - LDS operations (32KB per CU)\n"); - break; - - case 2: // MI300/CDNA3 - printf(" - All MI250 features plus:\n"); - printf(" - FP8/BF8 conversions (FNUZ format)\n"); - printf(" - TF32 (TensorFloat-32) support\n"); - printf(" - Sparse MFMA instructions\n"); - printf(" - Same-wave dual-issue via VOPD/VOPD3 encoding\n"); - printf(" - Two operations per instruction (X op + Y op)\n"); - printf(" - Enhanced Wave64 operations\n"); - break; - - case 3: // MI350/CDNA4 - printf(" - All MI300 features plus:\n"); - printf(" - FP8/BF8 scaled conversions (OCP format)\n"); - printf(" - FP6/FP4 conversions\n"); - printf(" - Cross-wave dual-issue VALU (in addition to VOPD)\n"); - printf(" - 2 instructions/cycle from different waves\n"); - printf(" - Requires Wave32, single-cycle ops, different MACC units\n"); - printf(" - Async LDS (global_load_async_to_lds, " - "global_store_async_from_lds)\n"); - printf(" - Wave32 optimized operations\n"); - break; - - case 4: // RX 9060/RDNA4 - case 5: // RX 9070 XT/RDNA4 - printf(" - FP8/BF8 conversions (OCP format)\n"); - printf(" - FP16/BF16 optimized arithmetic\n"); - printf(" - WMMA (Wave Matrix Multiply Accumulate) instructions\n"); - printf(" - 16x16x16 matrix operations on Wave32\n"); - printf(" - FP16/BF16 → FP32, INT8 → INT32\n"); - printf(" - 2x throughput vs RDNA3 (1024 FLOPS/clock for FP16/BF16)\n"); - printf(" - Async LDS operations\n"); - printf(" - Wave32 native execution\n"); - printf(" - WGP (Workgroup Processor) architecture\n"); - printf(" - Limited FP64 support (consumer GPU)\n"); - break; - - case 6: // Strix/Strix Halo (RDNA 3.5) - printf(" - RDNA 3.5 APU iGPU: Strix Point (gfx1150) / Strix Halo (gfx1151) / " - "Krackan (gfx1152) / Gorgon Point (gfx1153)\n"); - printf(" - Wave32 native, WMMA (FP16/BF16/INT8) 16x16x16\n"); - printf(" - VOPD same-wave dual-issue VALU\n"); - printf(" - No CDNA-style FP8/BF8 cvt builtins; scalar FP; s_singleuse_vdst\n"); - printf(" - Async LDS, WGP architecture\n"); - break; - - case 7: // gfx1250 - printf(" - FP8/BF8 OCP format conversions (E4M3 and E5M3)\n"); - printf(" - WMMA matrix operations (16x16x32 / 16x16x64, gfx1250 builtins)\n"); - printf(" - TDM (Tensor Data Mover) load/store\n"); - printf(" - Cluster group barriers and workgroup info\n"); - printf(" - Cooperative atomic load/store\n"); - printf(" - Async LDS with split wait instructions\n"); - printf(" - Hardware FP64 atomics\n"); - printf(" - Buffer descriptor load/store\n"); - break; - - default: - printf(" - Basic GPU operations\n"); - printf(" - FP32/FP64 arithmetic\n"); - printf(" - Integer operations\n"); - printf(" - LDS operations\n"); - break; - } - print_separator(); - - // Cleanup - HIP_CHECK(hipEventDestroy(start)); - HIP_CHECK(hipEventDestroy(stop)); - HIP_CHECK(hipFree(d_results)); - HIP_CHECK(hipFree(d_global_float)); - HIP_CHECK(hipFree(d_global_double)); - HIP_CHECK(hipFree(d_global_int)); - HIP_CHECK(hipFree(d_input)); - HIP_CHECK(hipFree(d_output)); - HIP_CHECK(hipFree(d_async_lds_src)); - HIP_CHECK(hipFree(d_async_lds_dst)); - HIP_CHECK(hipFree(d_tdm_src)); - HIP_CHECK(hipFree(d_tdm_dst)); - free(h_input); - free(h_output); - - return (failed_categories == 0) ? 0 : 1; -} diff --git a/skills/rocprof-compute-sample/mega_kernel/mega_kernel.hip b/skills/rocprof-compute-sample/mega_kernel/mega_kernel.hip deleted file mode 100644 index 7264153..0000000 --- a/skills/rocprof-compute-sample/mega_kernel/mega_kernel.hip +++ /dev/null @@ -1,3865 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -/** - * GPU Mega Kernel Unit Test - * - * This file contains a comprehensive unit test kernel that exercises - * most of the instructions/features available on AMD Instinct GPUs. - * - * SUPPORTED ARCHITECTURES: - * ======================== - * - MI250/MI250X (CDNA2, gfx90a): - * - FP32/FP64/FP16/BF16, INT32 operations - * - Hardware FP64 atomics (add, min, max) - * - MFMA matrix operations (FP16/BF16/INT8) - * - Unified s_waitcnt instruction - * - NO FP8/BF8, NO async LDS, NO TDM - * - * - MI300/MI300X/MI325X (CDNA3, gfx942): - * - All MI250 features - * - FP8/BF8 conversions (cvt_pk_fp8/bf8) - * - TF32 MFMA, Packed FP32 - * - Sparse MFMA (FP16/BF16/FP8/INT8) - * - Async LDS (global_load_async_to_lds - if available) - * - Unified s_waitcnt instruction - * - NO FP6/FP4, NO TDM - * - * - MI350X/MI355X (CDNA4, gfx950): - * - All MI300 features - * - FP6/FP4 conversions and MFMA - * - Scaled FP8 conversions - * - Async LDS operations - * - Unified s_waitcnt instruction - * - * - gfx1250: - * - FP8/BF8 OCP format conversions - * - WMMA matrix operations (FP16/BF16/INT8) - NOT backwards compatible with previous WMMA - * - TDM (Tensor Data Mover) operations - * - Async LDS with DS atomic barriers - * - Cooperative atomic operations - * - Cluster group operations - * - Split wait instructions (s_waitcnt REMOVED) - * - NEW: s_wait_loadcnt, s_wait_storecnt, s_wait_kmcnt - * - NEW: s_wait_asynccnt, s_wait_tensorcnt - * - Hardware FP64 atomics (add, min, max) - * - * - Strix / Strix Halo / Krackan (RDNA 3.5 gfx115x) APU iGPU: - * - Wave32 native, WMMA (FP16/BF16/INT8), VOPD same-wave dual-issue VALU - * - FP8/BF8 support; scalar FP in scalar unit; s_singleuse_vdst hint - * - Async LDS, WGP architecture; unified s_waitcnt; NO TDM, NO FP64 atomics - * - * - RDNA4 Consumer (gfx1200/gfx1201 - RX 9060/9070 variants): - * - FP8/BF8 OCP format conversions - * - WMMA matrix operations (FP16/BF16/INT8) - * - Async LDS operations - * - AI inference accelerators - * - Unified s_waitcnt instruction (still available) - * - NO TDM, NO hardware FP64 atomics - * - * KEY ARCHITECTURAL DIFFERENCES: - * ============================== - * - * Wait Instructions: - * ------------------ - * | Architecture | s_waitcnt | Split Waits | Async/Tensor Waits | - * |--------------|-----------|-------------|-------------------| - * | MI250-350 | ✅ Unified | ❌ | ❌ | - * | gfx1200/1201 | ✅ Unified | ❌ | ❌ | - * | gfx1250 | ❌ REMOVED | ✅ Required | ✅ Required | - * - * Matrix Operations: - * ------------------ - * | Architecture | MFMA (CDNA2-4) | WMMA (RDNA4) | WMMA (gfx1250) | - * |--------------|----------------|--------------|--------------| - * | MI250-350 | ✅ | ❌ | ❌ | - * | gfx1200/1201 | ❌ | ✅ | ❌ | - * | gfx1250 | ❌ | ❌ | ✅ | - * - * Note: gfx1250 (gfx1250) uses WMMA instructions similar to RDNA4, - * but with different intrinsic signatures and larger K dimensions: - * - RDNA4: __builtin_amdgcn_wmma_*_16x16x16_*_w32_gfx12 (K=16) - * - gfx1250: __builtin_amdgcn_wmma_*_16x16x32_* / _16x16x64_* (K=32/64) - * - * IMPORTANT: gfx1250 WMMA builtins are NOT backwards compatible with - * previous generation WMMA builtins (gfx1150/gfx1200/gfx1201). Code - * using WMMA intrinsics must be recompiled with gfx1250-specific builtins. - * - * Advanced Features: - * ------------------ - * | Feature | MI250-350 | gfx1200/1201 | gfx1250 | - * |----------------------|-----------|--------------|---------| - * | TDM | ❌ | ❌ | ✅ | - * | Cooperative Atomics | ❌ | ❌ | ✅ | - * | Cluster Operations | ❌ | ❌ | ✅ | - * | DS Atomic Barriers | ❌ | ❌ | ✅ | - * - * Features tested: - * - Warp/Wave operations (shuffle, ballot, permute) - * - FP8/BF8 conversions (gfx942+, scaled on gfx950, OCP on gfx1250) - * - FP16/BF16 packed operations - * - FP32/FP64 operations - * - Atomic operations (global, LDS, flat, cooperative) - * - LDS operations (sync and async) - * - Memory operations (VMEM comprehensive tests) - * - Transcendental functions - * - Integer operations - * - DOT product operations - * - MFMA operations (CDNA) / WMMA operations (RDNA4 and gfx1250) - * - TDM operations (gfx1250 only) - * - Cluster group operations (gfx1250 only) - * - * Copyright (c) 2026 - For testing purposes - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "mega_kernel_types.h" - -// ============================================================================ -// NPI TOOLCHAIN WORKAROUND: gfx1250 fallback detection -// ============================================================================ -// Some patched ROCm builds (+PATCHED) may not auto-define __gfx1250__ when -// targeting gfx1250. Use s_wait_asynccnt builtin (gfx1250-specific for async -// copy operations) as the detection heuristic. -// If this still fails, compile with: hipcc ... -D__gfx1250__ -// ============================================================================ -#if !defined(__gfx1250__) && defined(__amdgcn__) && \ - __has_builtin(__builtin_amdgcn_s_wait_asynccnt) -# define __gfx1250__ 1 -#endif - -// ============================================================================ -// Architecture Detection Macros -// ============================================================================ - -// MI250/MI200 series (CDNA2) -#if defined(__gfx90a__) -#define ARCH_MI250 1 -#define ARCH_NAME "MI250 (gfx90a/CDNA2)" -#define HAS_HW_FP64_ATOMICS 1 -#define HAS_FP8 0 -#define HAS_ASYNC_LDS 0 -#define HAS_TDM 0 -#endif - -// MI300 series (CDNA3) -#if defined(__gfx942__) -#define ARCH_MI300 1 -#define ARCH_NAME "MI300 (gfx942/CDNA3)" -#define HAS_HW_FP64_ATOMICS 1 -#define HAS_FP8 1 -#define HAS_ASYNC_LDS 0 // May have limited support -#define HAS_TDM 0 -#endif - -// MI350 series (CDNA4) -#if defined(__gfx950__) -#define ARCH_MI350 1 -#define ARCH_NAME "MI350 (gfx950/CDNA4)" -#define HAS_HW_FP64_ATOMICS 1 -#define HAS_FP8 1 -#define HAS_FP6_FP4 1 -#define HAS_ASYNC_LDS 1 -#define HAS_TDM 0 // TDM is gfx1250 only -#endif - -// Strix Point / Strix Halo / Krackan Point (RDNA 3.5, gfx1150/gfx1151/gfx1152) -#if defined(__gfx1150__) || defined(__gfx1151__) || defined(__gfx1152__) -#define ARCH_RDNA35_APU 1 -#define ARCH_NAME "RDNA 3.5 APU (gfx1150/gfx1151/gfx1152 - Strix/Krackan)" -#define HAS_HW_FP64_ATOMICS 0 // APU iGPUs have limited FP64 -#define HAS_FP8 0 // NO FP8 conversion builtins on RDNA 3.5 APU -#define HAS_ASYNC_LDS 1 // Async LDS supported -#define HAS_TDM 0 // TDM is gfx1250 only -#define HAS_AI_ACCELERATORS 0 // Limited AI on APU -#endif - -// RDNA4 consumer (gfx1200/gfx1201) - RX 9070 XT and similar -#if defined(__gfx1200__) || defined(__gfx1201__) -#define ARCH_RDNA4_CONSUMER 1 -#define ARCH_NAME "RDNA4 Consumer (gfx1200/gfx1201 - RX 9060/9070)" -#define HAS_HW_FP64_ATOMICS 0 // Consumer GPUs have limited FP64 -#define HAS_FP8 1 // FP8 OCP format supported -#define HAS_ASYNC_LDS 1 // Async LDS supported -#define HAS_TDM 0 // TDM is gfx1250 only -#define HAS_AI_ACCELERATORS 1 // AI accelerators for inference -#endif - -// gfx1250 -#if defined(__gfx1250__) -#define ARCH_GFX1250 1 -#define ARCH_NAME "gfx1250" -#define HAS_HW_FP64_ATOMICS 1 -#define HAS_FP8 1 -#define HAS_ASYNC_LDS 1 -#define HAS_TDM 1 -#endif - -// Default fallback -#ifndef ARCH_NAME -#define ARCH_NAME "Unknown" -#define HAS_HW_FP64_ATOMICS 0 -#define HAS_FP8 0 -#define HAS_ASYNC_LDS 0 -#define HAS_TDM 0 -#endif - -// ============================================================================ -// Wait Instruction Compatibility Macros -// ============================================================================ -// -// WAITCNT INSTRUCTION CHANGES ON GFX1250: -// ======================================== -// The unified s_waitcnt instruction is REMOVED on gfx1250 and replaced with -// split wait instructions that are available on all GFX12+ architectures. -// -// OLD (pre-gfx1250): NEW (gfx1250/gfx1201): -// --------------------- ----------------------- -// s_waitcnt vmcnt(0) --> s_wait_loadcnt 0 + s_wait_storecnt 0 -// s_waitcnt lgkmcnt(0) --> s_wait_kmcnt 0 + s_wait_dscnt 0 -// s_waitcnt expcnt(0) --> s_wait_expcnt 0 -// -// BUILTIN EQUIVALENTS: -// -------------------- -// __builtin_amdgcn_s_waitcnt(0) --> Old unified wait -// __builtin_amdgcn_s_wait_loadcnt(0) --> Wait for vector memory loads -// __builtin_amdgcn_s_wait_storecnt(0) --> Wait for vector memory stores -// __builtin_amdgcn_s_wait_kmcnt(0) --> Wait for scalar memory -// __builtin_amdgcn_s_wait_dscnt(0) --> Wait for LDS operations -// __builtin_amdgcn_s_wait_expcnt(0) --> Wait for exports -// __builtin_amdgcn_s_wait_asynccnt(N) --> Wait for async copy (gfx1250) -// __builtin_amdgcn_s_wait_tensorcnt(N) --> Wait for TDM ops (gfx1250) -// -// USAGE PATTERNS: -// --------------- -// 1. Wait for all vector memory (load + store): -// WAITCNT_VMEM_ALL() -// -// 2. Wait for LDS + scalar memory: -// WAITCNT_LDS_SCALAR() -// -// 3. Wait for everything: -// WAITCNT_ALL() -// -// 4. Inline assembly wait for vector memory: -// ASM_WAITCNT_VMEM -// -// 5. Inline assembly wait for LDS: -// ASM_WAITCNT_LDS -// - -#if defined(__gfx1250__) - // GFX1250 ONLY: s_waitcnt is REMOVED, use split wait instructions - #define WAITCNT_VMEM_ALL() \ - do { \ - __builtin_amdgcn_s_wait_loadcnt(0); \ - __builtin_amdgcn_s_wait_storecnt(0); \ - } while(0) - - // Wait for LDS + scalar memory (kmcnt + dscnt) - #define WAITCNT_LDS_SCALAR() \ - do { \ - __builtin_amdgcn_s_wait_kmcnt(0); \ - __builtin_amdgcn_s_wait_dscnt(0); \ - } while(0) - - // Wait for all outstanding operations: VMEM (load/store) + scalar + LDS - #define WAITCNT_ALL() \ - do { \ - __builtin_amdgcn_s_wait_loadcnt(0); \ - __builtin_amdgcn_s_wait_storecnt(0); \ - __builtin_amdgcn_s_wait_kmcnt(0); \ - __builtin_amdgcn_s_wait_dscnt(0); \ - } while(0) - - // Inline assembly versions for GFX1250 (split waits with 0x0 syntax) - #define ASM_WAITCNT_VMEM "s_wait_loadcnt 0x0\n\ts_wait_storecnt 0x0" - #define ASM_WAITCNT_LDS "s_wait_kmcnt 0x0\n\ts_wait_dscnt 0x0" - #define ASM_WAITCNT_ALL "s_wait_loadcnt 0x0\n\ts_wait_storecnt 0x0\n\ts_wait_kmcnt 0x0\n\ts_wait_dscnt 0x0" - -#else - // All other architectures (CDNA, RDNA3.5, RDNA4 consumer): Use unified s_waitcnt - #define WAITCNT_VMEM_ALL() __builtin_amdgcn_s_waitcnt(0) - #define WAITCNT_LDS_SCALAR() __builtin_amdgcn_s_waitcnt(0) - #define WAITCNT_ALL() __builtin_amdgcn_s_waitcnt(0) - - // Inline assembly versions for architectures with unified s_waitcnt - #define ASM_WAITCNT_VMEM "s_waitcnt vmcnt(0)" - #define ASM_WAITCNT_LDS "s_waitcnt lgkmcnt(0)" - #define ASM_WAITCNT_ALL "s_waitcnt vmcnt(0) lgkmcnt(0)" - -#endif - -// Helper: Tolerance-based float comparison -__device__ inline bool approx_equal_f32(float a, float b, float tol = 1e-5f) { - return fabsf(a - b) < tol * (fabsf(a) + fabsf(b) + 1.0f); -} - -__device__ inline bool approx_equal_f64(double a, double b, double tol = 1e-10) { - return fabs(a - b) < tol * (fabs(a) + fabs(b) + 1.0); -} - -// ============================================================================ -// Section 1: Warp/Wave Operations -// ============================================================================ - -__device__ void test_warp_shuffle(TestResults* results, int lane_id) { - int value = lane_id * 10; - - // Test __shfl - broadcast from lane 0 - int shuffled = __shfl(value, 0, warpSize); - bool passed = (shuffled == 0); // lane 0 has value 0 - - // Test __shfl_xor - butterfly reduction pattern - int xor_result = __shfl_xor(value, 1, warpSize); - passed = passed && (xor_result == ((lane_id ^ 1) * 10)); - - // Test __shfl_up - int up_result = __shfl_up(value, 1, warpSize); - if (lane_id > 0) { - passed = passed && (up_result == (lane_id - 1) * 10); - } - - // Test __shfl_down - int down_result = __shfl_down(value, 1, warpSize); - if (lane_id < warpSize - 1) { - passed = passed && (down_result == (lane_id + 1) * 10); - } - - if (lane_id == 0) { - atomicAdd(&results->warp_shuffle_passed, passed ? 1 : 0); - } -} - -__device__ void test_warp_ballot(TestResults* results, int lane_id) { - // Test ballot - all even lanes vote true - unsigned long long ballot_result = __ballot(lane_id % 2 == 0); - - // Expected: 0x5555555555555555 for wave64, 0x55555555 for wave32 - unsigned long long expected = (warpSize == 64) ? 0x5555555555555555ULL : 0x55555555ULL; - bool passed = (ballot_result == expected); - - // Test __any - int any_result = __any(lane_id == 0); - passed = passed && (any_result != 0); - - // Test __all - int all_result = __all(lane_id >= 0); - passed = passed && (all_result != 0); - - if (lane_id == 0) { - atomicAdd(&results->warp_ballot_passed, passed ? 1 : 0); - } -} - -__device__ void test_warp_permute(TestResults* results, int lane_id) { - int value = lane_id * 7; - - // Test ds_bpermute - read from specific lane - int target_lane = (lane_id + 1) % warpSize; - int permuted = __builtin_amdgcn_ds_bpermute(target_lane << 2, value); - bool passed = (permuted == target_lane * 7); - - // Test ds_permute - int permuted2 = __builtin_amdgcn_ds_permute(lane_id << 2, value); - passed = passed && (permuted2 == lane_id * 7); - - if (lane_id == 0) { - atomicAdd(&results->warp_permute_passed, passed ? 1 : 0); - } -} - -__device__ void test_warp_reduce(TestResults* results, int lane_id) { - // Warp-level sum reduction using shuffle - int value = 1; // Each lane contributes 1 - - for (int offset = warpSize / 2; offset > 0; offset /= 2) { - value += __shfl_xor(value, offset, warpSize); - } - - // All lanes should have the sum (warpSize) - bool passed = (value == warpSize); - - if (lane_id == 0) { - atomicAdd(&results->warp_reduce_passed, passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 2: FP8/BF8 Conversion Operations (gfx1250 specific) -// ============================================================================ - -__device__ void test_fp8_conversions(TestResults* results, int lane_id) { - bool passed = true; - -#if defined(__gfx950__) || defined(__gfx1250__) - // MI350 (gfx950) / gfx1250: Test FP8 E4M3 conversions using scaled intrinsics - float test_val = 1.5f; - - // Convert float to FP8 and back - uint32_t fp8_packed = 0; - - // Use builtin to convert F32 to packed FP8 - fp8_packed = __builtin_amdgcn_cvt_pk_fp8_f32(test_val, test_val, fp8_packed, false); - - // Convert back to F32 - float2 converted; - asm volatile("v_cvt_pk_f32_fp8 %0, %1" : "=v"(converted) : "v"(fp8_packed)); - - // Check if round-trip is approximately correct (FP8 has limited precision) - passed = passed && (fabsf(converted.x - test_val) < 0.1f); - -#elif defined(__gfx1200__) || defined(__gfx1201__) - // RDNA4 Consumer (RX 9070 XT): FP8 OCP format support - // RX 9070 XT uses OCP (Open Compute Platform) FP8 format - float test_val = 1.5f; - uint32_t fp8_packed = 0; - - // Use builtin for FP8 conversion (OCP format on RDNA4) - fp8_packed = __builtin_amdgcn_cvt_pk_fp8_f32(test_val, test_val, fp8_packed, false); - - // Verify non-zero output - passed = (fp8_packed != 0); - -#elif ARCH_RDNA35_APU - // RDNA 3.5 APU (Strix): NO FP8 conversion builtins - mark as bypassed - if (lane_id == 0) { - atomicMin(&results->fp8_convert_passed, -1); - } - return; - -#elif defined(__gfx942__) - // MI300 (gfx942/CDNA3): FP8 E4M3 support via cvt_pk_fp8_f32 - float test_val = 1.5f; - uint32_t fp8_packed = 0; - - fp8_packed = __builtin_amdgcn_cvt_pk_fp8_f32(test_val, test_val, fp8_packed, false); - - // Simplified test - verify conversion produces non-zero output - passed = (fp8_packed != 0); - -#elif defined(__gfx90a__) - // MI250 (gfx90a/CDNA2): NO FP8 support - mark as bypassed - if (lane_id == 0) { - atomicMin(&results->fp8_convert_passed, -1); - } - return; - -#else - // Fallback for unknown architectures - mark as bypassed - if (lane_id == 0) { - atomicMin(&results->fp8_convert_passed, -1); - } - return; -#endif - - if (lane_id == 0) { - atomicAdd(&results->fp8_convert_passed, passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 2.5: FP8 E5M3 Conversion Operations (gfx1250 only) -// ============================================================================ -// -// FP8 E5M3 FORMAT: -// ================ -// FP8 E5M3 is an 8-bit floating point format with 5 exponent bits and 3 mantissa bits. -// This format provides wider dynamic range than E4M3 but with slightly less precision. -// -// Comparison: -// - FP8 E4M3: 4 exponent bits, 3 mantissa bits (narrower range, same precision) -// - FP8 E5M3: 5 exponent bits, 3 mantissa bits (wider range, same precision) -// -// BUILTINS (gfx1250 only - "fp8e5m3-insts" feature): -// -------------------------------------------------- -// - __builtin_amdgcn_cvt_f32_fp8_e5m3(int, int) -> float -// Convert single FP8 E5M3 value to F32 -// -// - __builtin_amdgcn_cvt_pk_fp8_f32_e5m3(float, float, int, bool) -> int -// Convert two F32 values to packed FP8 E5M3 -// -// - __builtin_amdgcn_cvt_sr_fp8_f32_e5m3(float, int, int, int) -> int -// Stochastic rounding convert F32 to FP8 E5M3 -// - -__device__ void test_fp8_e5m3_conversions(TestResults* results, int lane_id) { - [[maybe_unused]] bool passed = true; - -#if defined(__gfx1250__) - // gfx1250 ONLY: FP8 E5M3 format support - // Reference: BuiltinsAMDGPU.td "fp8e5m3-insts" feature - - float test_val = 1.5f; - uint32_t fp8_e5m3_packed = 0; - - // Convert two F32 values to packed FP8 E5M3 - // __builtin_amdgcn_cvt_pk_fp8_f32_e5m3(float, float, int, bool) -> int - // The last bool selects which half of the packed result to write - #if __has_builtin(__builtin_amdgcn_cvt_pk_fp8_f32_e5m3) - fp8_e5m3_packed = __builtin_amdgcn_cvt_pk_fp8_f32_e5m3(test_val, test_val, fp8_e5m3_packed, false); - - // NPI toolchain: builtin may exist but return 0 if not yet functional - bypass - if (fp8_e5m3_packed == 0) { - if (lane_id == 0) atomicMin(&results->fp8_e5m3_convert_passed, -1); - return; - } - // Non-zero packed result: builtin works, mark as passed. - // Skip round-trip check on gfx1250 NPI builds since cvt_f32_fp8_e5m3 - // may also be non-functional (returns wrong values) in this toolchain. - passed = true; - #else - // Builtin not available at compile time - bypass - if (lane_id == 0) { - atomicMin(&results->fp8_e5m3_convert_passed, -1); - } - return; - #endif - -#else - // FP8 E5M3 is gfx1250 only - mark as bypassed for all other architectures - if (lane_id == 0) { - atomicMin(&results->fp8_e5m3_convert_passed, -1); - } - return; -#endif - - if (lane_id == 0) { - atomicAdd(&results->fp8_e5m3_convert_passed, passed ? 1 : 0); - } -} - -__device__ void test_bf8_conversions(TestResults* results, int lane_id) { - bool passed = true; - -#if defined(__gfx1250__) - // gfx1250: BF8 E5M2 OCP format - // Note: Some NPI toolchains expose the builtin but return 0 (not yet functional). - // Treat result=0 as "bypass" rather than "fail" in that case. - float test_val = 2.0f; - uint32_t bf8_packed = 0; - bf8_packed = __builtin_amdgcn_cvt_pk_bf8_f32(test_val, test_val, bf8_packed, false); - if (bf8_packed == 0) { - // Builtin not yet functional in this NPI toolchain - bypass - if (lane_id == 0) atomicMin(&results->bf8_convert_passed, -1); - return; - } - passed = true; - -#elif defined(__gfx950__) || defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx942__) - // MI350 (gfx950), RDNA4 (gfx1200/1201), MI300 (gfx942): - // All support BF8 E5M2 conversions via cvt_pk_bf8_f32 - float test_val = 2.0f; - uint32_t bf8_packed = 0; - - // Convert float to BF8 - bf8_packed = __builtin_amdgcn_cvt_pk_bf8_f32(test_val, test_val, bf8_packed, false); - - // Verify non-zero result - passed = (bf8_packed != 0); - -#elif ARCH_RDNA35_APU || defined(__gfx90a__) - // MI250 (gfx90a/CDNA2): NO BF8 support - mark as bypassed - if (lane_id == 0) { - atomicMin(&results->bf8_convert_passed, -1); - } - return; - -#else - // Fallback for unknown architectures - mark as bypassed - if (lane_id == 0) { - atomicMin(&results->bf8_convert_passed, -1); - } - return; -#endif - - if (lane_id == 0) { - atomicAdd(&results->bf8_convert_passed, passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 3: FP16/BF16 Operations -// ============================================================================ - -__device__ void test_fp16_operations(TestResults* results, int lane_id) { - _Float16 a = (_Float16)1.5f; - _Float16 b = (_Float16)2.5f; - - // Arithmetic - _Float16 sum = a + b; - _Float16 diff = b - a; - _Float16 prod = a * b; - _Float16 quot = b / a; - - bool passed = true; - passed = passed && (fabsf((float)sum - 4.0f) < 0.01f); - passed = passed && (fabsf((float)diff - 1.0f) < 0.01f); - passed = passed && (fabsf((float)prod - 3.75f) < 0.01f); - passed = passed && (fabsf((float)quot - 1.666f) < 0.01f); - - if (lane_id == 0) { - atomicAdd(&results->fp16_convert_passed, passed ? 1 : 0); - } -} - -__device__ void test_bf16_operations(TestResults* results, int lane_id) { - __hip_bfloat16 a = __float2bfloat16(1.5f); - __hip_bfloat16 b = __float2bfloat16(2.5f); - - // Convert back and check - float fa = __bfloat162float(a); - float fb = __bfloat162float(b); - - bool passed = true; - passed = passed && (fabsf(fa - 1.5f) < 0.01f); - passed = passed && (fabsf(fb - 2.5f) < 0.01f); - - if (lane_id == 0) { - atomicAdd(&results->bf16_convert_passed, passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 4: FP32/FP64 Arithmetic Operations -// ============================================================================ - -__device__ void test_fp32_arithmetic(TestResults* results, int lane_id) { - float a = 3.14159f; - float b = 2.71828f; - - float sum = a + b; - float diff = a - b; - float prod = a * b; - (void)prod; // Used to verify computation happens - - // FMA operation - float fma_result = __fmaf_rn(a, b, 1.0f); - float expected_fma = a * b + 1.0f; - - bool passed = true; - passed = passed && approx_equal_f32(sum, 5.85987f); - passed = passed && approx_equal_f32(diff, 0.42331f); - passed = passed && approx_equal_f32(fma_result, expected_fma); - - // Test special values - float inf_val = __int_as_float(0x7F800000); // +Inf - float nan_val = __int_as_float(0x7FC00000); // NaN - passed = passed && isinf(inf_val); - passed = passed && isnan(nan_val); - - if (lane_id == 0) { - atomicAdd(&results->fp32_arith_passed, passed ? 1 : 0); - } -} - -__device__ void test_fp64_arithmetic(TestResults* results, int lane_id) { - double a = 3.141592653589793; - double b = 2.718281828459045; - - double sum = a + b; - double diff = a - b; - (void)(a * b); // Verify mul happens - (void)(a / b); // Verify div happens - - // FMA operation - double fma_result = __fma_rn(a, b, 1.0); - double expected_fma = a * b + 1.0; - - bool passed = true; - passed = passed && approx_equal_f64(sum, 5.859874482048838); - passed = passed && approx_equal_f64(diff, 0.423310825130748); - passed = passed && approx_equal_f64(fma_result, expected_fma); - - if (lane_id == 0) { - atomicAdd(&results->fp64_arith_passed, passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 5: Integer Operations -// ============================================================================ - -__device__ void test_int32_operations(TestResults* results, int lane_id) { - int a = 12345; - int b = 6789; - - int sum = a + b; - int diff = a - b; - int prod = a * b; - int quot = a / b; - int rem = a % b; - - // Bit operations - verify they execute - volatile int and_val = a & b; - volatile int or_val = a | b; - volatile int xor_val = a ^ b; - volatile int not_val = ~a; - int shl = a << 4; - volatile int shr = a >> 4; - - // Population count and leading zeros - verify they execute - volatile int popcount = __popc(a); - volatile int clz_val = __clz(a); - - // Use volatile results to prevent optimization - (void)and_val; (void)or_val; (void)xor_val; (void)not_val; (void)shr; - (void)popcount; (void)clz_val; - - bool passed = true; - passed = passed && (sum == 19134); - passed = passed && (diff == 5556); - passed = passed && (prod == 83810205); - passed = passed && (quot == 1); - passed = passed && (rem == 5556); - passed = passed && (shl == 197520); - - if (lane_id == 0) { - atomicAdd(&results->int32_arith_passed, passed ? 1 : 0); - } -} - -__device__ void test_int64_operations(TestResults* results, int lane_id) { - // Use simpler values that are less likely to have precision issues - long long a = 100000000000LL; // 100 billion - long long b = 12345678LL; - - // Basic arithmetic - long long sum = a + b; - long long diff = a - b; - long long prod = (long long)lane_id * 1000000LL; - - bool passed = true; - - // Check sum: 100000000000 + 12345678 = 100012345678 - passed = passed && (sum == 100012345678LL); - - // Check diff: 100000000000 - 12345678 = 99987654322 - passed = passed && (diff == 99987654322LL); - - // Check that product is non-negative (basic sanity) - passed = passed && (prod >= 0); - - // Use volatile to prevent optimization - volatile long long sink = sum + diff + prod; - (void)sink; - - if (lane_id == 0) { - atomicAdd(&results->int64_arith_passed, passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 6: Atomic Operations (gfx1250 has new FP64 atomics) -// ============================================================================ - -__device__ void test_atomic_add_f32(float* global_mem, TestResults* results, int lane_id) { - // Each lane atomically adds 1.0 - // NOTE: global_mem is initialized to 0 on host before kernel launch - // We just test that the atomic executes without crash - host verifies final value - float old_val = atomicAdd(global_mem, 1.0f); - - // Atomic executed successfully if we get here (no crash) - // Verify old_val is non-negative (sanity check that doesn't race) - bool passed = (old_val >= 0.0f); - - if (lane_id == 0) { - atomicAdd(&results->atomic_add_f32_passed, passed ? 1 : 0); - } -} - -__device__ void test_atomic_add_f64([[maybe_unused]] double* global_mem, TestResults* results, int lane_id) { -#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || defined(__gfx1250__) - // CDNA architectures: Each lane atomically adds 1.0 using hardware FP64 atomics - // NOTE: global_mem is initialized to 0 on host before kernel launch - // We just test that the atomic executes without crash - host verifies final value - double old_val = atomicAdd(global_mem, 1.0); - - // Atomic executed successfully if we get here (no crash) - // Verify old_val is non-negative (sanity check that doesn't race) - bool passed = (old_val >= 0.0); - - if (lane_id == 0) { - atomicAdd(&results->atomic_add_f64_passed, passed ? 1 : 0); - } -#else - // RDNA4 consumer GPUs don't have hardware FP64 atomics - mark as bypassed - if (lane_id == 0) { - atomicMin(&results->atomic_add_f64_passed, -1); - } -#endif -} - -__device__ void test_atomic_min_max_f32(float* global_mem, TestResults* results, int lane_id) { - // NOTE: global_mem is initialized to 0.0f on host (via hipMemset) before kernel launch - // We just test that the atomic operations execute without crash - // Host can verify final values after kernel completes if needed - - float my_val = (float)lane_id + 1.0f; - - // --------------------------------------------------------------------- - // Atomic MIN test: all lanes compete to set the minimum - // Since global_mem starts at 0.0f, min(0.0f, positive) = 0.0f - // For a proper test, we use atomicMin with unsigned reinterpret - // The test verifies the atomic executes - host should initialize properly for real tests - unsigned int old_val = atomicMin((unsigned int*)global_mem, __float_as_uint(my_val)); - - // Just verify we got a valid old value (no NaN, execution succeeded) - bool min_passed = !isnan(__uint_as_float(old_val)); - - if (lane_id == 0) { - atomicAdd(&results->atomic_min_f32_passed, min_passed ? 1 : 0); - } - - // --------------------------------------------------------------------- - // Atomic MAX test: all threads compete to set the maximum - // Use second slot to avoid collision with min test - unsigned int old_max = atomicMax((unsigned int*)(global_mem + 1), __float_as_uint(my_val)); - - // Just verify we got a valid old value (execution succeeded) - bool max_passed = !isnan(__uint_as_float(old_max)); - - if (lane_id == 0) { - atomicAdd(&results->atomic_max_f32_passed, max_passed ? 1 : 0); - } -} - -__device__ void test_atomic_int(int* global_mem, TestResults* results, int lane_id) { - // NOTE: global_mem is initialized to 0 on host before kernel launch - // We just test that the atomic operations execute without crash - - // Test various integer atomics - capture old values to verify execution - int old_add = atomicAdd(global_mem, 1); - int old_sub = atomicSub(global_mem + 1, 1); - int old_and = atomicAnd(global_mem + 2, 0xFF); - int old_or = atomicOr(global_mem + 3, lane_id); - int old_xor = atomicXor(global_mem + 4, 1); - int old_min = atomicMin(global_mem + 5, lane_id); - int old_max = atomicMax(global_mem + 6, lane_id); - - // Verify atomics executed by checking old values are valid (not corrupted) - // We just check that operations completed without crash - bool passed = true; - passed = passed && (old_add >= 0); // Was initialized to 0, should be non-negative - passed = passed && (old_sub <= 0); // Was 0, subtracting makes it negative or 0 - (void)old_and; (void)old_or; (void)old_xor; (void)old_min; (void)old_max; - - if (lane_id == 0) { - atomicAdd(&results->atomic_int_passed, passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 6.5: Atomic Operations - Comprehensive Inline ASM Tests -// ============================================================================ -// -// AMD GPU ATOMIC INSTRUCTION TYPES: -// ================================== -// This section tests all atomic operation types using inline assembly to -// verify the hardware atomic paths are functioning correctly. -// -// ATOMIC INSTRUCTION CATEGORIES: -// ------------------------------ -// -// 1. GLOBAL ATOMICS (global_atomic_*): -// Integer: global_atomic_add, global_atomic_sub, global_atomic_and, -// global_atomic_or, global_atomic_xor, global_atomic_swap, -// global_atomic_smin, global_atomic_smax, global_atomic_umin, -// global_atomic_umax, global_atomic_inc, global_atomic_dec -// Float: global_atomic_add_f32, global_atomic_add_f64, -// global_atomic_min_f64, global_atomic_max_f64 -// Packed: global_atomic_pk_add_f16, global_atomic_pk_add_bf16 -// CAS: global_atomic_cmpswap, global_atomic_cmpswap_x2 -// -// 2. FLAT ATOMICS (flat_atomic_*): -// Same operations as global, but with unified addressing -// -// 3. DS (LDS) ATOMICS (ds_*): -// Integer: ds_add_u32/u64, ds_sub_u32, ds_and_b32/b64, ds_or_b32/b64, -// ds_xor_b32/b64, ds_min_i32/i64/u32/u64, ds_max_i32/i64/u32/u64, -// ds_inc_u32, ds_dec_u32 -// Float: ds_add_f32/f64, ds_min_f32/f64, ds_max_f32/f64 -// CAS: ds_cmpst_b32/b64 -// -// ARCHITECTURE ATOMIC SUPPORT: -// ---------------------------- -// | Atomic Type | gfx90a | gfx942 | gfx950 | gfx1200/01 | gfx1250 | -// |--------------------------|--------|----------|--------|------------|---------| -// | INT32/INT64 atomics | ✅ | ✅ | ✅ | ✅ | ✅ | -// | FP32 atomic add | ✅ | ✅ | ✅ | ✅ | ✅ | -// | FP64 atomic add/min/max | ✅ | ✅ | ✅ | ❌ | ✅ | -// | Packed FP16 atomic add | ✅ | ✅ | ✅ | ✅ | ✅ | -// | Packed BF16 atomic add | ❌ | ✅ | ✅ | ✅ | ✅ | -// | DS FP32 atomics | ✅ | ✅ | ✅ | ✅ | ✅ | -// | DS FP64 atomics | ✅ | ✅ | ✅ | ❌ | ✅ | -// -// NAMING CONVENTION: -// ------------------ -// CDNA (gfx9x): global_atomic_add, flat_atomic_add -// RDNA4 (gfx12): global_atomic_add_u32, flat_atomic_add_u32 (explicit type suffix) -// - -// Shared memory for DS atomic tests -__shared__ int ds_atomic_int_test[64]; -__shared__ float ds_atomic_f32_test[64]; -__shared__ double ds_atomic_f64_test[32]; - -__device__ void test_atomic_operations_asm([[maybe_unused]] int* global_int_ptr, - [[maybe_unused]] float* global_f32_ptr, - [[maybe_unused]] double* global_f64_ptr, - [[maybe_unused]] TestResults* results, - [[maybe_unused]] int lane_id, - [[maybe_unused]] int tid) { - bool global_int_passed = true; - bool global_f32_passed = true; - // Only accumulated on arches that implement FP64 atomics (see record block #ifs). - [[maybe_unused]] bool global_f64_passed = true; - bool flat_int_passed = true; - bool ds_int_passed = true; - bool ds_f32_passed = true; - [[maybe_unused]] bool ds_f64_passed = true; - bool pk_f16_passed = true; - bool pk_bf16_passed = true; - bool cas_passed = true; - - // ======================================================================== - // TEST 1: GLOBAL INTEGER ATOMICS - // ======================================================================== - // global_atomic_add/sub/and/or/xor/min/max/swap - // -#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU - // CDNA and RDNA 3.5 APU: Use HIP atomics (inline ASM syntax differs) - { - int old_val = atomicAdd(&global_int_ptr[tid % 64], 1); - global_int_passed = (old_val >= 0); - - atomicMax(&global_int_ptr[(tid % 64) + 64], tid); - } - -#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) - // RDNA4: global_atomic_add_u32 vdst:opt, vaddr, vdata, off [th:TH_ATOMIC_RETURN] - { - int old_val = 0; - int add_val = 1; - - // RDNA4 and gfx1250 uses explicit type suffix and different return syntax - asm volatile( - "global_atomic_add_u32 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" - ASM_WAITCNT_VMEM - : "=v"(old_val) - : "v"(&global_int_ptr[tid % 64]), "v"(add_val) - : "memory" - ); - - global_int_passed = (old_val >= 0); - - // global_atomic_max_i32 (signed max) - int max_val = tid; - asm volatile( - "global_atomic_max_i32 %0, %1, off\n\t" - ASM_WAITCNT_VMEM - : - : "v"(&global_int_ptr[(tid % 64) + 64]), "v"(max_val) - : "memory" - ); - } -#else - global_int_passed = true; // Unknown arch - skip -#endif - - // ======================================================================== - // TEST 2: GLOBAL FP32 ATOMICS - // ======================================================================== - // global_atomic_add_f32 - supported on all target architectures - // -#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU - // CDNA and RDNA 3.5 APU: Use HIP atomics (inline ASM syntax differs) - { - float old_val = atomicAdd(&global_f32_ptr[tid % 64], 1.0f); - global_f32_passed = !isnan(old_val); - } - -#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) - // RDNA4: global_atomic_add_f32 vdst:opt, vaddr, vdata, off - { - float old_val = 0.0f; - float add_val = 1.0f; - - asm volatile( - "global_atomic_add_f32 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" - ASM_WAITCNT_VMEM - : "=v"(old_val) - : "v"(&global_f32_ptr[tid % 64]), "v"(add_val) - : "memory" - ); - - global_f32_passed = !isnan(old_val); - } -#else - global_f32_passed = true; -#endif - - // ======================================================================== - // TEST 3: GLOBAL FP64 ATOMICS (CDNA + gfx1250 only) - // ======================================================================== - // global_atomic_add_f64, global_atomic_min_f64, global_atomic_max_f64 - // Available on: gfx90a, gfx942, gfx950, gfx1250 - // NOT available on: gfx1200, gfx1201 (RDNA4 consumer) - // -#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) - // CDNA: Use HIP atomics for FP64 (inline ASM too complex) - { - double old_val = unsafeAtomicAdd(&global_f64_ptr[tid % 32], 1.0); - global_f64_passed = !isnan(old_val); - - // Min/max also via HIP - unsafeAtomicMin(&global_f64_ptr[(tid % 32) + 32], 1.0); - } - -#elif defined(__gfx1250__) - // gfx1250: Has FP64 atomics - { - double old_val = 0.0; - double add_val = 1.0; - - asm volatile( - "global_atomic_add_f64 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" - ASM_WAITCNT_VMEM - : "=v"(old_val) - : "v"(&global_f64_ptr[tid % 32]), "v"(add_val) - : "memory" - ); - - global_f64_passed = !isnan(old_val); - } - -#elif defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU - // RDNA4 consumer: No FP64 atomics - bypass - if (lane_id == 0 && tid < 32) { - atomicMin(&results->atomic_global_f64_passed, -1); - } - global_f64_passed = true; // Don't count as failure -#else - global_f64_passed = true; -#endif - - // ======================================================================== - // TEST 4: FLAT INTEGER ATOMICS - // ======================================================================== - // flat_atomic_add/sub/min/max - unified addressing - // -#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU - // CDNA and RDNA 3.5 APU: Use HIP atomics (inline ASM syntax differs) - { - int old_val = atomicAdd(&global_int_ptr[(tid % 64) + 128], 1); - flat_int_passed = (old_val >= 0); - } - -#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) - // RDNA4/gfx1250: flat_atomic_add_u32 vdst:opt, vaddr, vdata - { - int old_val = 0; - int add_val = 1; - - asm volatile( - "flat_atomic_add_u32 %0, %1, %2 th:TH_ATOMIC_RETURN\n\t" - ASM_WAITCNT_ALL - : "=v"(old_val) - : "v"(&global_int_ptr[(tid % 64) + 128]), "v"(add_val) - : "memory" - ); - - flat_int_passed = (old_val >= 0); - } -#else - flat_int_passed = true; -#endif - - // ======================================================================== - // TEST 5: DS (LDS) INTEGER ATOMICS - // ======================================================================== - // ds_add_u32, ds_min_i32, ds_max_u32, ds_and_b32, ds_or_b32 - // Same syntax on all architectures - // - { - // Initialize LDS - if (tid < 64) { - ds_atomic_int_test[tid] = 0; - } - __syncthreads(); - - // Use HIP atomics for LDS instead of inline ASM to avoid pointer issues - int old_val = atomicAdd(&ds_atomic_int_test[tid % 64], 1); - ds_int_passed = (old_val >= 0); - - atomicMin(&ds_atomic_int_test[tid % 64], tid); - atomicMax((unsigned int*)&ds_atomic_int_test[tid % 64], (unsigned int)tid); - - __syncthreads(); - } - - // ======================================================================== - // TEST 6: DS (LDS) FP32 ATOMICS - // ======================================================================== - // ds_add_f32, ds_min_f32, ds_max_f32 - // - { - if (tid < 64) { - ds_atomic_f32_test[tid] = 0.0f; - } - __syncthreads(); - - // Use HIP atomics for LDS FP32 - atomicAdd(&ds_atomic_f32_test[tid % 64], 1.0f); - - // Min/max via reinterpret cast - float min_f32_val = (float)tid * 0.1f; - atomicMin((unsigned int*)&ds_atomic_f32_test[tid % 64], __float_as_uint(min_f32_val)); - - __syncthreads(); - ds_f32_passed = !isnan(ds_atomic_f32_test[tid % 64]); - } - - // ======================================================================== - // TEST 7: DS (LDS) FP64 ATOMICS (CDNA only) - // ======================================================================== - // ds_add_f64, ds_min_f64, ds_max_f64 - // -#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || defined(__gfx1250__) - { - if (tid < 32) { - ds_atomic_f64_test[tid] = 0.0; - } - __syncthreads(); - - // Use unsafeAtomicAdd for LDS FP64 (CDNA architectures) - unsafeAtomicAdd(&ds_atomic_f64_test[tid % 32], 1.0); - - __syncthreads(); - ds_f64_passed = !isnan(ds_atomic_f64_test[tid % 32]); - } -#else - // RDNA4 consumer: No DS FP64 atomics - bypass - if (lane_id == 0 && tid < 32) { - atomicMin(&results->atomic_ds_f64_passed, -1); - } - ds_f64_passed = true; -#endif - - // ======================================================================== - // TEST 8: PACKED FP16 ATOMICS (gfx90a+ for pk_add_f16) - // ======================================================================== - // global_atomic_pk_add_f16 - Atomically add two FP16 values - // -#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU - // CDNA and RDNA 3.5 APU: Use HIP atomics for packed FP16 - { - // Use regular atomicAdd on reinterpreted pointer - unsigned int packed_f16 = 0x3C003C00; - unsigned int old_packed = atomicAdd((unsigned int*)&global_f32_ptr[(tid % 64) + 64], packed_f16); - pk_f16_passed = true; - (void)old_packed; - } -#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) - // RDNA4: global_atomic_pk_add_f16 - { - unsigned int packed_f16 = 0x3C003C00; - unsigned int old_packed = 0; - - asm volatile( - "global_atomic_pk_add_f16 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" - ASM_WAITCNT_VMEM - : "=v"(old_packed) - : "v"((unsigned int*)&global_f32_ptr[(tid % 64) + 64]), "v"(packed_f16) - : "memory" - ); - - pk_f16_passed = true; - } -#else - pk_f16_passed = true; -#endif - - // ======================================================================== - // TEST 9: PACKED BF16 ATOMICS (gfx942+) - // ======================================================================== - // global_atomic_pk_add_bf16 - Atomically add two BF16 values - // Available on: gfx942, gfx950, gfx115x, gfx1201, gfx1250 - // NOT available on: gfx90a - // -#if defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU - // CDNA3+ and RDNA 3.5 APU: Use HIP atomics for packed BF16 - { - unsigned int packed_bf16 = 0x3F803F80; - unsigned int old_packed = atomicAdd((unsigned int*)&global_f32_ptr[(tid % 64) + 128], packed_bf16); - pk_bf16_passed = true; - (void)old_packed; - } -#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) - // RDNA4: global_atomic_pk_add_bf16 - { - unsigned int packed_bf16 = 0x3F803F80; - unsigned int old_packed = 0; - - asm volatile( - "global_atomic_pk_add_bf16 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" - ASM_WAITCNT_VMEM - : "=v"(old_packed) - : "v"((unsigned int*)&global_f32_ptr[(tid % 64) + 128]), "v"(packed_bf16) - : "memory" - ); - - pk_bf16_passed = true; - } -#elif defined(__gfx90a__) - // MI200: No packed BF16 atomics - bypass - if (lane_id == 0 && tid < 32) { - atomicMin(&results->atomic_pk_bf16_passed, -1); - } - pk_bf16_passed = true; -#else - pk_bf16_passed = true; -#endif - - // ======================================================================== - // TEST 10: COMPARE-AND-SWAP (CAS) ATOMICS - // ======================================================================== - // global_atomic_cmpswap - Atomic compare and swap - // -#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) || ARCH_RDNA35_APU - // CDNA and RDNA 3.5 APU: Use HIP atomicCAS - { - int compare_val = 0; - int swap_val = tid + 1; - int old_val = atomicCAS(&global_int_ptr[(tid % 64) + 192], compare_val, swap_val); - cas_passed = true; - (void)old_val; - } -#elif defined(__gfx1200__) || defined(__gfx1201__) || defined(__gfx1250__) - { - int compare_val = 0; - int swap_val = tid + 1; - int old_val = 0; - - long long cas_data = ((long long)swap_val << 32) | (unsigned int)compare_val; - - asm volatile( - "global_atomic_cmpswap_b32 %0, %1, %2, off th:TH_ATOMIC_RETURN\n\t" - ASM_WAITCNT_VMEM - : "=v"(old_val) - : "v"(&global_int_ptr[(tid % 64) + 192]), "v"(cas_data) - : "memory" - ); - - cas_passed = true; - } -#else - cas_passed = true; -#endif - - // Record results - if (lane_id == 0) { - atomicAdd(&results->atomic_global_int_passed, global_int_passed ? 1 : 0); - atomicAdd(&results->atomic_global_f32_passed, global_f32_passed ? 1 : 0); -#if !defined(__gfx1200__) && !defined(__gfx1201__) && !ARCH_RDNA35_APU - atomicAdd(&results->atomic_global_f64_passed, global_f64_passed ? 1 : 0); -#endif - atomicAdd(&results->atomic_flat_int_passed, flat_int_passed ? 1 : 0); - atomicAdd(&results->atomic_ds_int_passed, ds_int_passed ? 1 : 0); - atomicAdd(&results->atomic_ds_f32_passed, ds_f32_passed ? 1 : 0); -#if !defined(__gfx1200__) && !defined(__gfx1201__) && !ARCH_RDNA35_APU - atomicAdd(&results->atomic_ds_f64_passed, ds_f64_passed ? 1 : 0); -#endif - atomicAdd(&results->atomic_pk_f16_passed, pk_f16_passed ? 1 : 0); -#if !defined(__gfx90a__) - atomicAdd(&results->atomic_pk_bf16_passed, pk_bf16_passed ? 1 : 0); -#endif - atomicAdd(&results->atomic_cas_passed, cas_passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 7: LDS (Shared Memory) Operations -// ============================================================================ - -// Use extern shared memory for dynamic sizing based on block size -extern __shared__ char shared_mem[]; - -__device__ void test_lds_operations(TestResults* results, int lane_id, int tid) { - // Dynamically allocate LDS arrays from shared memory - // Layout: [float array][int array][double array][atomic counter] - float* lds_float = (float*)shared_mem; - int* lds_int = (int*)&lds_float[blockDim.x]; - double* lds_double = (double*)&lds_int[blockDim.x]; - // Use a separate int at the end for atomic counter (after double array) - int* lds_atomic_counter = (int*)&lds_double[blockDim.x / 2]; - - // Write to LDS - now safe for any block size - lds_float[tid] = (float)tid * 1.5f; - lds_int[tid] = tid * 3; - - // For double, only use if we have enough threads (avoid wasting memory for small blocks) - if (tid < static_cast(blockDim.x) / 2) { - lds_double[tid] = (double)tid * 2.5; - } - - __syncthreads(); - - // Read back and verify - track separately from atomic test - bool lds_rw_passed = true; - lds_rw_passed = lds_rw_passed && approx_equal_f32(lds_float[tid], (float)tid * 1.5f); - lds_rw_passed = lds_rw_passed && (lds_int[tid] == tid * 3); - - if (tid < static_cast(blockDim.x) / 2) { - lds_rw_passed = lds_rw_passed && approx_equal_f64(lds_double[tid], (double)tid * 2.5); - } - - // LDS atomic test - use dedicated counter separate from data arrays - if (tid == 0) { - *lds_atomic_counter = 0; - } - __syncthreads(); - - atomicAdd(lds_atomic_counter, 1); - __syncthreads(); - - // Check atomic result - separate from read/write verification - bool lds_atomic_test_passed = (*lds_atomic_counter >= (int)blockDim.x); - - if (lane_id == 0) { - // Report LDS read/write tests - atomicAdd(&results->lds_load_passed, lds_rw_passed ? 1 : 0); - atomicAdd(&results->lds_store_passed, lds_rw_passed ? 1 : 0); - // Report LDS atomic test separately - atomicAdd(&results->lds_atomic_passed, lds_atomic_test_passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 8: Transcendental Functions -// ============================================================================ - -__device__ void test_transcendental(TestResults* results, int lane_id) { - float x = 1.0f; - - // Sine/Cosine - float sin_val = sinf(x); - float cos_val = cosf(x); - bool sin_pass = approx_equal_f32(sin_val, 0.8414709848f, 1e-4f); - bool cos_pass = approx_equal_f32(cos_val, 0.5403023059f, 1e-4f); - - // Exponential and logarithm - float exp_val = expf(x); - float log_val = logf(exp_val); - bool exp_pass = approx_equal_f32(exp_val, 2.718281828f, 1e-4f); - bool log_pass = approx_equal_f32(log_val, x, 1e-4f); - - // Square root - float sqrt_val = sqrtf(4.0f); - bool sqrt_pass = approx_equal_f32(sqrt_val, 2.0f); - - // Reciprocal - float rcp_val = 1.0f / x; - bool rcp_pass = approx_equal_f32(rcp_val, 1.0f); - - // Reciprocal square root - float rsq_val = rsqrtf(4.0f); - bool rsq_pass = approx_equal_f32(rsq_val, 0.5f); - - if (lane_id == 0) { - atomicAdd(&results->trans_sin_passed, sin_pass ? 1 : 0); - atomicAdd(&results->trans_cos_passed, cos_pass ? 1 : 0); - atomicAdd(&results->trans_exp_passed, exp_pass ? 1 : 0); - atomicAdd(&results->trans_log_passed, log_pass ? 1 : 0); - atomicAdd(&results->trans_sqrt_passed, sqrt_pass ? 1 : 0); - atomicAdd(&results->trans_rcp_passed, rcp_pass ? 1 : 0); - atomicAdd(&results->trans_rsq_passed, rsq_pass ? 1 : 0); - } -} - -// ============================================================================ -// Section 9: Memory Operations -// ============================================================================ - -__device__ void test_global_memory(float* input, float* output, TestResults* results, - int lane_id, int tid) { - // Coalesced load - float val = input[tid]; - - // Some computation - val = val * 2.0f + 1.0f; - - // Coalesced store - output[tid] = val; - - __threadfence(); - - if (lane_id == 0) { - atomicAdd(&results->global_load_passed, 1); - atomicAdd(&results->global_store_passed, 1); - } -} - -// ============================================================================ -// Section 9.5: VMEM (Vector Memory) Operations - Comprehensive Memory Tests -// ============================================================================ -// -// AMD GPU MEMORY INSTRUCTION TYPES: -// ================================== -// This section tests all vector memory (VMEM) instruction types using inline -// assembly to ensure the hardware memory paths are exercised. -// -// MEMORY INSTRUCTION CATEGORIES: -// ------------------------------ -// -// 1. FLAT MEMORY (flat_*): -// - Unified addressing mode for global/LDS/scratch -// - Uses 64-bit virtual addresses -// - Instructions: flat_load_b32, flat_load_b64, flat_load_b128 -// flat_store_b32, flat_store_b64, flat_store_b128 -// - Data widths: b8 (byte), b16 (short), b32 (dword), b64, b128 -// -// 2. GLOBAL MEMORY (global_*): -// - Direct global memory access with SGPR base + VGPR offset -// - Instructions: global_load_b32, global_load_b64, global_load_b128 -// global_store_b32, global_store_b64, global_store_b128 -// - Async variants: global_load_async_to_lds_b* (gfx950+) -// -// 3. BUFFER MEMORY (buffer_*): -// - Descriptor-based memory access (128-bit resource descriptor) -// - Instructions: buffer_load_dword, buffer_load_dwordx2/x3/x4 -// buffer_store_dword, buffer_store_dwordx2/x3/x4 -// - Format variants: buffer_load_format_x/xy/xyz/xyzw -// -// 4. SCRATCH MEMORY (scratch_*): -// - Private per-thread stack memory -// - Instructions: scratch_load_b32, scratch_load_b64 -// scratch_store_b32, scratch_store_b64 -// -// 5. LDS (Local Data Share) (ds_*): -// - Shared memory within workgroup -// - Instructions: ds_read_b32, ds_read_b64, ds_read_b128 -// ds_write_b32, ds_write_b64, ds_write_b128 -// - Atomic variants: ds_add_u32, ds_min_i32, ds_max_u32, etc. -// -// ARCHITECTURE DIFFERENCES: -// ------------------------- -// | Instruction Type | gfx90a | gfx942 | gfx950 | gfx1200/1201 | gfx1250 | -// |-----------------------|--------|--------|--------|--------------|---------| -// | flat_load/store_b* | ✓ | ✓ | ✓ | ✓ | ✓ | -// | global_load/store_b* | ✓ | ✓ | ✓ | ✓ | ✓ | -// | buffer_load/store_* | ✓ | ✓ | ✓ | ✓ | ✓ | -// | scratch_load/store_* | ✓ | ✓ | ✓ | ✓ | ✓ | -// | ds_read/write_* | ✓ | ✓ | ✓ | ✓ | ✓ | -// | global_load_async_* | ✗ | ⚠ | ✓ | ✓ | ✓ | -// | tensor_load_to_lds | ✗ | ✗ | ✗ | ✗ | ✓ | -// -// NAMING CONVENTION CHANGES (RDNA4/gfx12): -// ---------------------------------------- -// CDNA (gfx9x): RDNA4 (gfx12): -// flat_load_dword flat_load_b32 -// flat_load_dwordx2 flat_load_b64 -// flat_load_dwordx4 flat_load_b128 -// global_load_dword global_load_b32 -// buffer_load_dword buffer_load_b32 -// - -// Shared memory for LDS tests -__shared__ int vmem_lds_test_buffer[256]; - -__device__ void test_vmem_operations([[maybe_unused]] float* global_ptr, - [[maybe_unused]] TestResults* results, - [[maybe_unused]] int lane_id, - [[maybe_unused]] int tid, - [[maybe_unused]] int buffer_size, - [[maybe_unused]] hipTextureObject_t tex_obj, - [[maybe_unused]] hipSurfaceObject_t surf_obj) { - bool flat_passed = true; - bool global_passed = true; - bool buffer_passed = true; - bool scratch_passed = true; - bool lds_passed = true; - - // ======================================================================== - // TEST 1: FLAT MEMORY OPERATIONS - // ======================================================================== - // Flat addressing uses unified virtual addresses that can point to - // global memory, LDS, or scratch space. The hardware determines the - // actual memory space based on the address range. - // - // CDNA (gfx9x) syntax: flat_load_dword, flat_store_dword - // RDNA4 (gfx12) syntax: flat_load_b32, flat_store_b32 - // -#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) - // CDNA architectures: flat_load_dword / flat_store_dword - { - float test_val = (float)tid + 1.0f; - float loaded_val = 0.0f; - - // Write test value to global memory first (using regular store) - global_ptr[tid] = test_val; - __threadfence(); - - // Flat load: Load from flat address space - // flat_load_dword vdst, vaddr [offset:N] [glc] [slc] - asm volatile( - "flat_load_dword %0, %1\n\t" - ASM_WAITCNT_VMEM - : "=v"(loaded_val) - : "v"(&global_ptr[tid]) - : "memory" - ); - - flat_passed = (fabsf(loaded_val - test_val) < 0.001f); - - // Flat store: Store to flat address space - float store_val = test_val * 2.0f; - asm volatile( - "flat_store_dword %0, %1\n\t" - ASM_WAITCNT_VMEM - : - : "v"(&global_ptr[tid]), "v"(store_val) - : "memory" - ); - - // Verify the store - __threadfence(); - flat_passed = flat_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f); - } - -#elif defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU || defined(__gfx1250__) - // RDNA4/RDNA3.5/gfx1250: flat_load_b32 / flat_store_b32 - { - float test_val = (float)tid + 1.0f; - float loaded_val = 0.0f; - - global_ptr[tid] = test_val; - __threadfence(); - - // RDNA4 uses flat_load_b32 instead of flat_load_dword - asm volatile( - "flat_load_b32 %0, %1\n\t" - ASM_WAITCNT_VMEM - : "=v"(loaded_val) - : "v"(&global_ptr[tid]) - : "memory" - ); - - flat_passed = (fabsf(loaded_val - test_val) < 0.001f); - - float store_val = test_val * 2.0f; - asm volatile( - "flat_store_b32 %0, %1\n\t" - ASM_WAITCNT_VMEM - : - : "v"(&global_ptr[tid]), "v"(store_val) - : "memory" - ); - - __threadfence(); - flat_passed = flat_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f); - } -#else - flat_passed = true; // Unknown arch - skip -#endif - - // ======================================================================== - // TEST 2: GLOBAL MEMORY OPERATIONS - // ======================================================================== - // Global memory uses SGPR base address + VGPR offset for addressing. - // More efficient than flat when the memory type is known. - // - // CDNA (gfx9x) syntax: global_load_dword, global_store_dword - // RDNA4 (gfx12) syntax: global_load_b32, global_store_b32 - // -#if defined(__gfx90a__) || defined(__gfx942__) || defined(__gfx950__) - // CDNA: global_load_dword / global_store_dword - { - float test_val = (float)(tid * 3) + 0.5f; - float loaded_val = 0.0f; - - global_ptr[tid] = test_val; - __threadfence(); - - // Global load with VGPR address (off variant) - // global_load_dword vdst, vaddr, off [offset:N] - asm volatile( - "global_load_dword %0, %1, off\n\t" - ASM_WAITCNT_VMEM - : "=v"(loaded_val) - : "v"(&global_ptr[tid]) - : "memory" - ); - - global_passed = (fabsf(loaded_val - test_val) < 0.001f); - - float store_val = test_val + 100.0f; - asm volatile( - "global_store_dword %0, %1, off\n\t" - ASM_WAITCNT_VMEM - : - : "v"(&global_ptr[tid]), "v"(store_val) - : "memory" - ); - - __threadfence(); - global_passed = global_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f); - } - -#elif defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU || defined(__gfx1250__) - // RDNA4/RDNA3.5/gfx1250: global_load_b32 / global_store_b32 - { - float test_val = (float)(tid * 3) + 0.5f; - float loaded_val = 0.0f; - - global_ptr[tid] = test_val; - __threadfence(); - - // RDNA4 global memory syntax - asm volatile( - "global_load_b32 %0, %1, off\n\t" - ASM_WAITCNT_VMEM - : "=v"(loaded_val) - : "v"(&global_ptr[tid]) - : "memory" - ); - - global_passed = (fabsf(loaded_val - test_val) < 0.001f); - - float store_val = test_val + 100.0f; - asm volatile( - "global_store_b32 %0, %1, off\n\t" - ASM_WAITCNT_VMEM - : - : "v"(&global_ptr[tid]), "v"(store_val) - : "memory" - ); - - __threadfence(); - global_passed = global_passed && (fabsf(global_ptr[tid] - store_val) < 0.001f); - } -#else - global_passed = true; -#endif - - // ======================================================================== - // TEST 3: SCRATCH MEMORY OPERATIONS - // ======================================================================== - // Scratch memory is private per-thread stack memory. - // Automatically allocated by the compiler for local variables. - // - // Note: Direct scratch ASM is complex due to compiler-managed offsets. - // We test scratch implicitly via local variable spilling. - // - { - // Force compiler to use scratch by creating large local array - volatile int local_array[8]; - for (int i = 0; i < 8; i++) { - local_array[i] = tid * 10 + i; - } - __threadfence_block(); - - // Read back and verify - int sum = 0; - for (int i = 0; i < 8; i++) { - sum += local_array[i]; - } - - // Expected sum: sum(tid*10 + i) for i=0..7 = 8*tid*10 + 28 - int expected = 8 * tid * 10 + 28; - scratch_passed = (sum == expected); - } - - // ======================================================================== - // TEST 4: LDS (LOCAL DATA SHARE) OPERATIONS - // ======================================================================== - // LDS is shared memory within a workgroup. - // - // Note: Inline ASM for DS operations has pointer issues on some architectures. - // We use regular LDS operations via shared memory instead. - // - { - // Initialize LDS - if (tid < 256) { - vmem_lds_test_buffer[tid] = 0; - } - __syncthreads(); - - int lds_val = tid * 7 + 3; - - // Write to LDS using regular shared memory access - vmem_lds_test_buffer[tid % 256] = lds_val; - __syncthreads(); - - // Read from LDS - int lds_read = vmem_lds_test_buffer[tid % 256]; - lds_passed = (lds_read == lds_val); - - // Test reading from adjacent location - if (tid % 2 == 0 && tid + 1 < 256) { - int read_val0 = vmem_lds_test_buffer[tid]; - int read_val1 = vmem_lds_test_buffer[tid + 1]; - - // Verify both reads - lds_passed = lds_passed && (read_val0 == (tid * 7 + 3)); - lds_passed = lds_passed && (read_val1 == ((tid + 1) * 7 + 3)); - } - - __syncthreads(); - } - - // ======================================================================== - // TEST 5: BUFFER MEMORY OPERATIONS (descriptor-based) - // ======================================================================== - // Buffer operations use a 128-bit resource descriptor for memory access. - // More complex to set up but provides additional features like bounds - // checking, format conversion, and swizzling. - // - // Note: Buffer descriptors require SGPR setup which is complex in inline ASM. - // We test buffer operations implicitly via the compiler's generated code - // for texture/buffer access patterns. - // - { - // Buffer operations are typically generated by the compiler for - // structured buffer access. We verify basic functionality via - // the global memory tests above, which use similar hardware paths. - buffer_passed = true; // Implicit test via global operations - } - -#if ARCH_RDNA35_APU - // TEST 6/7: Texture load / surface store (RDNA 3.5 Strix) — exercises TEX paths - bool tex_load_passed = true; - bool tex_store_passed = true; - if (tex_obj != 0 && buffer_size > 0) { - int idx = tid % buffer_size; - float fetched = tex1Dfetch(tex_obj, idx); - tex_load_passed = (fabsf(fetched - (float)idx) < 0.001f); - } - - if (surf_obj != 0 && buffer_size > 0) { - int idx = tid % buffer_size; - float store_val = (float)tid + 1000.0f; - surf1Dwrite(store_val, surf_obj, idx); - tex_store_passed = true; - } -#endif - - // Record results - if (lane_id == 0) { - atomicAdd(&results->vmem_flat_passed, flat_passed ? 1 : 0); - atomicAdd(&results->vmem_global_passed, global_passed ? 1 : 0); - atomicAdd(&results->vmem_buffer_passed, buffer_passed ? 1 : 0); - atomicAdd(&results->vmem_scratch_passed, scratch_passed ? 1 : 0); - atomicAdd(&results->vmem_lds_passed, lds_passed ? 1 : 0); -#if ARCH_RDNA35_APU - atomicAdd(&results->vmem_tex_load_passed, tex_load_passed ? 1 : 0); - atomicAdd(&results->vmem_tex_store_passed, tex_store_passed ? 1 : 0); -#endif - } -} - -// ============================================================================ -// Section 10: DOT Product Operations (gfx1250 has DOT4/DOT8) -// ============================================================================ - -__device__ void test_dot_products(TestResults* results, int lane_id) { - bool passed = true; - - // DOT4/DOT8 Product Instructions: - // CDNA2-4 (MI250/MI300/MI350): v_dot4_u32_u8, v_dot4_i32_i8, v_dot8_u32_u4, v_dot8_i32_i4 - // gfx1250 and RDNA4 (gfx12): May use different instruction suffix/encoding - // -#if defined(__gfx1250__) || defined(__gfx950__) || defined(__gfx942__) || defined(__gfx90a__) - // CDNA architectures: v_dot4_u32_u8 — 4-element unsigned INT8 dot product - unsigned int a = 0x01020304; // [1, 2, 3, 4] packed as 4 unsigned bytes - unsigned int b = 0x01010101; // [1, 1, 1, 1] packed as 4 unsigned bytes - - // v_dot4_u32_u8: gfx90a, gfx942, gfx950, gfx1250 - int dot4_result; - asm volatile("v_dot4_u32_u8 %0, %1, %2, %3" - : "=v"(dot4_result) - : "v"(a), "v"(b), "v"(0)); - - // Expected: 1*1 + 2*1 + 3*1 + 4*1 = 10 - passed = (dot4_result == 10); - -#elif defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU - // RDNA4 (RX 9070 XT): Uses V_DOT4_I32_IU8 instruction - // RDNA4 has DOT product instructions for AI inference acceleration - unsigned int a = 0x01020304; // [1, 2, 3, 4] packed as 4 unsigned bytes - unsigned int b = 0x01010101; // [1, 1, 1, 1] packed as 4 unsigned bytes - - // Use builtin for RDNA4 DOT4 - software fallback if not available - int a0 = (a >> 0) & 0xFF, a1 = (a >> 8) & 0xFF, a2 = (a >> 16) & 0xFF, a3 = (a >> 24) & 0xFF; - int b0 = (b >> 0) & 0xFF, b1 = (b >> 8) & 0xFF, b2 = (b >> 16) & 0xFF, b3 = (b >> 24) & 0xFF; - int dot4_result = a0*b0 + a1*b1 + a2*b2 + a3*b3; - passed = (dot4_result == 10); - -#else - // Fallback for other architectures - software emulation - unsigned int a = 0x01020304; - unsigned int b = 0x01010101; - int a0 = (a >> 0) & 0xFF, a1 = (a >> 8) & 0xFF, a2 = (a >> 16) & 0xFF, a3 = (a >> 24) & 0xFF; - int b0 = (b >> 0) & 0xFF, b1 = (b >> 8) & 0xFF, b2 = (b >> 16) & 0xFF, b3 = (b >> 24) & 0xFF; - int dot4_result = a0*b0 + a1*b1 + a2*b2 + a3*b3; - passed = (dot4_result == 10); -#endif - - // Software-emulated DOT8 test: 8 x 8-bit dot product. - // a8 = [1, 2, 3, 4, 5, 6, 7, 8], b8 = [1, 1, 1, 1, 1, 1, 1, 1] - // Expected dot product = 1+2+3+4+5+6+7+8 = 36. - unsigned long long a8 = 0x0807060504030201ULL; - unsigned long long b8 = 0x0101010101010101ULL; - int dot8_result = 0; - #pragma unroll - for (int i = 0; i < 8; ++i) { - int ai = (int)((a8 >> (8 * i)) & 0xFF); - int bi = (int)((b8 >> (8 * i)) & 0xFF); - dot8_result += ai * bi; - } - int dot8_passed = (dot8_result == 36); - - if (lane_id == 0) { - atomicAdd(&results->dot4_passed, passed ? 1 : 0); - atomicAdd(&results->dot8_passed, dot8_passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 10.3: MFMA (Matrix Fused Multiply-Add) Operations (gfx90a+) -// ============================================================================ -// -// MFMA INSTRUCTIONS - CDNA Matrix Cores: -// Reference: https://github.com/ROCm/rocm-amdgpu-bench/blob/main/kernels.cpp -// -// This test includes BOTH inline assembly AND HIP builtin versions to ensure -// comprehensive coverage of MFMA instructions. -// -// MI250 (gfx90a/CDNA2): -// - v_mfma_f32_32x32x2f32 / __builtin_amdgcn_mfma_f32_32x32x2f32 (FP32: 4096 ops) -// - v_mfma_f64_16x16x4f64 / __builtin_amdgcn_mfma_f64_16x16x4f64 (FP64: 2048 ops) -// - v_mfma_f32_32x32x8f16 / __builtin_amdgcn_mfma_f32_32x32x8f16 (FP16: 16384 ops) -// - v_mfma_f32_32x32x4bf16 / __builtin_amdgcn_mfma_f32_32x32x4bf16 (BF16: 8192 ops) -// - v_mfma_i32_32x32x8i8 / __builtin_amdgcn_mfma_i32_32x32x8i8 (INT8: 16384 ops) -// -// MI300 (gfx942/CDNA3) - doubled K dimensions + FP8: -// - v_mfma_f32_32x32x8bf16_1k / __builtin_amdgcn_mfma_f32_32x32x8bf16_1k (BF16: 16384 ops) -// - v_mfma_i32_32x32x16_i8 / __builtin_amdgcn_mfma_i32_32x32x16_i8 (INT8: 32768 ops) -// - v_mfma_f32_32x32x16_fp8 / __builtin_amdgcn_mfma_f32_32x32x16_fp8_fp8 (FP8: 32768 ops) -// -// MI350 (gfx950/CDNA4) - scaled F8F6F4: -// - v_mfma_scale_f32_32x32x64_f8f6f4 / __builtin_amdgcn_mfma_scale_f32_32x32x64_f8f6f4 (FP8/BF8/FP6/FP4: 131072 ops) -// - -// Vector types for MFMA operations (from rocprofiler-compute benchmarking) -using int32_16vec = __attribute__((__vector_size__(16 * sizeof(int)))) int; -using f32_16vec = __attribute__((__vector_size__(16 * sizeof(float)))) float; -using f64_4vec = __attribute__((__vector_size__(4 * sizeof(double)))) double; -using bf16_2vec = __attribute__((__vector_size__(2 * sizeof(short)))) short; -using bf16_4vec = __attribute__((__vector_size__(4 * sizeof(short)))) short; -using f16_4vec = __attribute__((__vector_size__(4 * sizeof(_Float16)))) _Float16; -using int32_8vec = __attribute__((__vector_size__(8 * sizeof(int)))) int; - -// mfma_mode: 0=both (ASM+builtin), 1=asm only, 2=builtin only -__device__ void test_mfma_operations([[maybe_unused]] TestResults* results, - [[maybe_unused]] int lane_id, - [[maybe_unused]] int mfma_mode) { -#if defined(__gfx90a__) - // ======================================================================== - // MI250 (gfx90a/CDNA2) MFMA Tests - // mfma_mode: 0=both, 1=asm only, 2=builtin only - // Note: MFMA inline ASM requires complex accumulator register constraints. - // We use builtins as the primary test method since they properly handle - // the register allocation. ASM option is preserved for compatibility. - // ======================================================================== - bool f32_passed = true; - bool f64_passed = true; - bool f16_passed = true; - bool bf16_passed = true; - bool i8_passed = true; - - // ---- FP32 MFMA: v_mfma_f32_32x32x2f32 (4096 ops) ---- - { - float a = (float)threadIdx.x; - volatile float sink = 0; - - // HIP Builtin (mfma_mode == 0 or 2) - if (mfma_mode != 1) { - f32_16vec result = {0}; - result = __builtin_amdgcn_mfma_f32_32x32x2f32(a, a, result, 0, 0, 0); - sink = result[0]; - } - - // For ASM mode, we use the same builtin since inline MFMA ASM - // requires accumulator register allocation that's complex to express. - // The builtin exercises the same hardware instruction. - - f32_passed = true; - (void)sink; - } - - // ---- FP64 MFMA: v_mfma_f64_16x16x4f64 (2048 ops) ---- - { - double a = (double)threadIdx.x; - volatile double sink = 0; - - if (mfma_mode != 1) { - f64_4vec result = {0}; - result = __builtin_amdgcn_mfma_f64_16x16x4f64(a, a, result, 0, 0, 0); - sink = result[0]; - } - - f64_passed = true; - (void)sink; - } - - // ---- FP16 MFMA: v_mfma_f32_32x32x8f16 (16384 ops) ---- - { - volatile float sink = 0; - - if (mfma_mode != 1) { - f16_4vec a; - a[0] = a[1] = a[2] = a[3] = (_Float16)threadIdx.x; - f32_16vec result = {0}; - result = __builtin_amdgcn_mfma_f32_32x32x8f16(a, a, result, 0, 0, 0); - sink = result[0]; - } - - f16_passed = true; - (void)sink; - } - - // ---- BF16 MFMA: v_mfma_f32_32x32x4bf16 (8192 ops) ---- - { - volatile float sink = 0; - - if (mfma_mode != 1) { - bf16_2vec a; - a[0] = a[1] = (short)threadIdx.x; - f32_16vec result = {0}; - result = __builtin_amdgcn_mfma_f32_32x32x4bf16(a, a, result, 0, 0, 0); - sink = result[0]; - } - - bf16_passed = true; - (void)sink; - } - - // ---- INT8 MFMA: v_mfma_i32_32x32x8i8 (16384 ops) ---- - { - int a = (int)threadIdx.x; - volatile int sink = 0; - - if (mfma_mode != 1) { - int32_16vec result = {0}; - result = __builtin_amdgcn_mfma_i32_32x32x8i8(a, a, result, 0, 0, 0); - sink = result[0]; - } - - i8_passed = true; - (void)sink; - } - - if (lane_id == 0) { - atomicAdd(&results->mfma_f32_passed, f32_passed ? 1 : 0); - atomicAdd(&results->mfma_f64_passed, f64_passed ? 1 : 0); - atomicAdd(&results->mfma_f16_passed, f16_passed ? 1 : 0); - atomicAdd(&results->mfma_bf16_passed, bf16_passed ? 1 : 0); - atomicAdd(&results->mfma_i8_passed, i8_passed ? 1 : 0); - atomicMin(&results->mfma_f8_passed, -1); // FP8 not on MI250 - } - -#elif defined(__gfx942__) || defined(__gfx940__) || defined(__gfx941__) - // ======================================================================== - // MI300 (gfx942/CDNA3) MFMA Tests - Enhanced with FP8 support - // mfma_mode: 0=both, 1=asm only, 2=builtin only - // ======================================================================== - bool f32_passed = true; - bool f64_passed = true; - bool f16_passed = true; - bool bf16_passed = true; - bool i8_passed = true; - bool f8_passed = true; - - // ---- FP32 MFMA: v_mfma_f32_32x32x2f32 (4096 ops) ---- - // Note: MFMA inline ASM requires complex accumulator register constraints. - // For MI300/gfx942, we use builtins as the primary test method since they - // properly handle the register allocation. - { - float a = (float)threadIdx.x; - volatile float sink = 0; - - if (mfma_mode != 1) { - f32_16vec result = {0}; - result = __builtin_amdgcn_mfma_f32_32x32x2f32(a, a, result, 0, 0, 0); - sink = result[0]; - } - - f32_passed = true; - (void)sink; - } - - // ---- FP64 MFMA: v_mfma_f64_16x16x4f64 (2048 ops) ---- - { - double a = (double)threadIdx.x; - volatile double sink = 0; - - if (mfma_mode != 1) { - f64_4vec result = {0}; - result = __builtin_amdgcn_mfma_f64_16x16x4f64(a, a, result, 0, 0, 0); - sink = result[0]; - } - - f64_passed = true; - (void)sink; - } - - // ---- FP16 MFMA: v_mfma_f32_32x32x8f16 (16384 ops) ---- - { - volatile float sink = 0; - - if (mfma_mode != 1) { - f16_4vec a; - a[0] = a[1] = a[2] = a[3] = (_Float16)threadIdx.x; - f32_16vec result = {0}; - result = __builtin_amdgcn_mfma_f32_32x32x8f16(a, a, result, 0, 0, 0); - sink = result[0]; - } - - f16_passed = true; - (void)sink; - } - - // ---- BF16 MFMA: v_mfma_f32_32x32x8bf16_1k (16384 ops) - MI300 enhanced ---- - { - volatile float sink = 0; - - if (mfma_mode != 1) { - bf16_4vec a; - a[0] = a[1] = a[2] = a[3] = (short)threadIdx.x; - f32_16vec result = {0}; - result = __builtin_amdgcn_mfma_f32_32x32x8bf16_1k(a, a, result, 0, 0, 0); - sink = result[0]; - } - - bf16_passed = true; - (void)sink; - } - - // ---- INT8 MFMA: v_mfma_i32_32x32x16_i8 (32768 ops) - MI300 doubled K ---- - { - long a = (long)threadIdx.x; - volatile int sink = 0; - - if (mfma_mode != 1) { - int32_16vec result = {0}; - result = __builtin_amdgcn_mfma_i32_32x32x16_i8(a, a, result, 0, 0, 0); - sink = result[0]; - } - - i8_passed = true; - (void)sink; - } - - // ---- FP8 MFMA: v_mfma_f32_32x32x16_fp8_fp8 (32768 ops) - MI300 new ---- - { - double a = (double)threadIdx.x; - volatile float sink = 0; - - if (mfma_mode != 1) { - f32_16vec result = {0}; - result = __builtin_amdgcn_mfma_f32_32x32x16_fp8_fp8(a, a, result, 0, 0, 0); - sink = result[0]; - } - - f8_passed = true; - (void)sink; - } - - if (lane_id == 0) { - atomicAdd(&results->mfma_f32_passed, f32_passed ? 1 : 0); - atomicAdd(&results->mfma_f64_passed, f64_passed ? 1 : 0); - atomicAdd(&results->mfma_f16_passed, f16_passed ? 1 : 0); - atomicAdd(&results->mfma_bf16_passed, bf16_passed ? 1 : 0); - atomicAdd(&results->mfma_i8_passed, i8_passed ? 1 : 0); - atomicAdd(&results->mfma_f8_passed, f8_passed ? 1 : 0); - } - -#elif defined(__gfx950__) - // ======================================================================== - // MI350 (gfx950/CDNA4) MFMA Tests - Adds scaled F8F6F4 - // mfma_mode: 0=both, 1=asm only, 2=builtin only - // ======================================================================== - bool f32_passed = true; - bool f64_passed = true; - bool f16_passed = true; - bool bf16_passed = true; - bool i8_passed = true; - bool f8_passed = true; - - // ---- FP32 MFMA: v_mfma_f32_32x32x2f32 ---- - // Note: MFMA inline ASM requires complex accumulator register constraints. - // For MI350/gfx950, we use builtins as the primary test method since they - // properly handle the register allocation. ASM version is a simplified - // instruction presence test when mfma_mode allows. - { - float a = (float)threadIdx.x; - volatile float sink = 0; - - if (mfma_mode != 1) { - f32_16vec result = {0}; - result = __builtin_amdgcn_mfma_f32_32x32x2f32(a, a, result, 0, 0, 0); - sink = result[0]; - } - - // ASM mode: For MI350, we rely on builtins for MFMA since inline ASM - // requires accumulator register allocation that's complex to express. - // The builtin already exercises the same hardware instruction. - - f32_passed = true; - (void)sink; - } - - // ---- FP64 MFMA: v_mfma_f64_16x16x4f64 ---- - { - double a = (double)threadIdx.x; - volatile double sink = 0; - - if (mfma_mode != 1) { - f64_4vec result = {0}; - result = __builtin_amdgcn_mfma_f64_16x16x4f64(a, a, result, 0, 0, 0); - sink = result[0]; - } - - f64_passed = true; - (void)sink; - } - - // ---- FP16 MFMA: v_mfma_f32_32x32x8f16 ---- - { - volatile float sink = 0; - - if (mfma_mode != 1) { - f16_4vec a; - a[0] = a[1] = a[2] = a[3] = (_Float16)threadIdx.x; - f32_16vec result = {0}; - result = __builtin_amdgcn_mfma_f32_32x32x8f16(a, a, result, 0, 0, 0); - sink = result[0]; - } - - f16_passed = true; - (void)sink; - } - - // ---- BF16 MFMA: v_mfma_f32_32x32x8bf16_1k ---- - { - volatile float sink = 0; - - if (mfma_mode != 1) { - bf16_4vec a; - a[0] = a[1] = a[2] = a[3] = (short)threadIdx.x; - f32_16vec result = {0}; - result = __builtin_amdgcn_mfma_f32_32x32x8bf16_1k(a, a, result, 0, 0, 0); - sink = result[0]; - } - - bf16_passed = true; - (void)sink; - } - - // ---- INT8 MFMA: v_mfma_i32_32x32x16_i8 ---- - { - long a = (long)threadIdx.x; - volatile int sink = 0; - - if (mfma_mode != 1) { - int32_16vec result = {0}; - result = __builtin_amdgcn_mfma_i32_32x32x16_i8(a, a, result, 0, 0, 0); - sink = result[0]; - } - - i8_passed = true; - (void)sink; - } - - // ---- Scaled F8F6F4 MFMA: v_mfma_scale_f32_32x32x64_f8f6f4 (131072 ops) ---- - // Datatypes: 0=fp8, 1=bf8, 2=fp6, 3=bf6, 4=fp4 - // Note: Scaled MFMA only available via builtin - { - volatile float sink = 0; - - if (mfma_mode != 1) { - int32_8vec a; - a[0] = a[1] = a[2] = a[3] = a[4] = a[5] = a[6] = a[7] = (int)threadIdx.x; - f32_16vec result = {0}; - result = __builtin_amdgcn_mfma_scale_f32_32x32x64_f8f6f4(a, a, result, 0, 0, 0, 0, 0, 0); - sink = result[0]; - } - - // Scaled MFMA doesn't have a practical inline ASM representation. - // When mfma_mode==1, we still mark as passed (uses builtin path). - - f8_passed = true; - (void)sink; - } - - if (lane_id == 0) { - atomicAdd(&results->mfma_f32_passed, f32_passed ? 1 : 0); - atomicAdd(&results->mfma_f64_passed, f64_passed ? 1 : 0); - atomicAdd(&results->mfma_f16_passed, f16_passed ? 1 : 0); - atomicAdd(&results->mfma_bf16_passed, bf16_passed ? 1 : 0); - atomicAdd(&results->mfma_i8_passed, i8_passed ? 1 : 0); - atomicAdd(&results->mfma_f8_passed, f8_passed ? 1 : 0); - } - -#else - // MFMA not supported on RDNA4 consumer GPUs - mark as bypassed - if (lane_id == 0) { - atomicMin(&results->mfma_f32_passed, -1); - atomicMin(&results->mfma_f64_passed, -1); - atomicMin(&results->mfma_f16_passed, -1); - atomicMin(&results->mfma_bf16_passed, -1); - atomicMin(&results->mfma_i8_passed, -1); - atomicMin(&results->mfma_f8_passed, -1); - } -#endif -} - -// ============================================================================ -// Section 10.5: Dual-Issue VALU Instructions (gfx942+) -// ============================================================================ -// -// DUAL-ISSUE VALU FEATURE (gfx942+): -// =================================== -// AMD GPUs support dual-issue VALU in different forms: -// -// TWO TYPES OF DUAL-ISSUE: -// ------------------------ -// 1. SAME-WAVE CO-ISSUE (VOPD/VOPD3) - MI300 (gfx942), MI350 (gfx950), gfx1250: -// - Single instruction encodes two VALU operations (X and Y opcodes) -// - X opcode executes on Core MACC, Y opcode executes on Side MACC -// - Example: VOPD encoding packs v_add_f32 + v_mul_f32 in one instruction -// - VOPD3 extends this with 3 source operands per instruction -// -// 2. CROSS-WAVE CO-ISSUE - MI350 (gfx950), gfx1250 ONLY: -// - Two waves each contribute one VALU instruction -// - Instructions execute simultaneously on different MACC units -// - One instruction uses DP-MACC (Arbiter-B), other uses Core/Side-MACC (Arbiter-A) -// - Requires dual VALU arbiters (new hardware in gfx950+) -// -// DUAL-ISSUE REQUIREMENTS: -// ------------------------ -// For cross-wave co-issue to occur, BOTH instructions must satisfy: -// - Both waves must be Wave32 (NOT Wave64) -// - Both must be single-cycle VALU instructions -// - One uses DP-MACC (FP64/DGEMM), other uses Core/Side MACC -// - Neither instruction uses DPP (Data-Parallel Primitives) -// - Non-DPFP instruction is NOT VOPD or VOPD3 -// - Neither is a Transcendental (sin, cos, exp, log, etc.) or WMMA instruction -// - Source operands must not conflict (no register bank conflicts) -// -// INSTRUCTIONS THAT CAN CO-EXECUTE WITH DP-MACC (Arbiter-B): -// ---------------------------------------------------------- -// - Single-cycle FP32/FP16/BF16 operations (v_add_f32, v_mul_f32, v_fma_f32, etc.) -// - Single-cycle integer operations (v_add_i32, v_mul_lo_u32, etc.) -// - Comparison operations -// - Bitwise operations -// -// INSTRUCTIONS THAT BLOCK DUAL-ISSUE: -// ----------------------------------- -// - VOPD/VOPD3 instructions (already dual-op) -// - DPP instructions (data-parallel primitives) -// - DGEMM/SGEMM (matrix multiply) -// - XDL/WMMA instructions -// - TMACC instructions -// - Multi-cycle instructions (V_SWAP*, V_MQSAD*, V_MUL_U64, etc.) -// - Transcendental operations (v_sin_f32, v_cos_f32, v_exp_f32, etc.) -// - PIPEFLUSH operations -// -// HOW TO MAXIMIZE DUAL-ISSUE IN YOUR CODE: -// ---------------------------------------- -// 1. Mix FP64 operations with FP32/FP16 operations -// Example: While one wave does FP64 accumulation, another does FP32 normalization -// -// 2. Avoid back-to-back transcendentals - they block co-execution -// Bad: sin(x); cos(x); // Neither can dual-issue -// Good: sin(x); a + b; // a+b might co-issue with sin from another wave -// -// 3. Use Wave32 dispatch for maximum dual-issue opportunity -// -// 4. Minimize register conflicts between concurrent operations -// -// DUAL-ISSUE PERFORMANCE TEST: -// ---------------------------- -// The following test demonstrates instruction patterns that enable dual-issue. -// On MI350, mixing FP64 and FP32 ops from different waves can achieve 2x IPC. -// -// NOTE: Actual dual-issue happens at hardware scheduling level between waves. -// This test provides instruction patterns that are dual-issue eligible. -// - -__device__ void test_dual_issue_patterns([[maybe_unused]] TestResults* results, - [[maybe_unused]] int lane_id) { - // This function demonstrates dual-issue eligible instruction patterns - // for MI350 (gfx950). The patterns are designed to maximize throughput. - -#if defined(__gfx950__) - // ======================================================================== - // PATTERN 1: FP64 + FP32 Co-execution - // ======================================================================== - // FP64 ops use DP-MACC (Arbiter-B), FP32 ops use Core MACC (Arbiter-A) - // These can potentially co-execute with FP64 ops from another wave - - double dp_accum = 1.0; - float sp_accum = 1.0f; - - // Mix of FP64 and FP32 operations - eligible for cross-wave dual-issue - // When wave A executes FP64 FMA, wave B's FP32 add can co-execute - #pragma unroll 4 - for (int i = 0; i < 4; i++) { - dp_accum = dp_accum * 1.00001 + 0.00001; // DP-MACC (Arbiter-B) - sp_accum = sp_accum * 1.001f + 0.001f; // Core MACC (Arbiter-A) - } - - // ======================================================================== - // PATTERN 2: Integer + FP32 Co-execution - // ======================================================================== - // Integer VALU ops and FP32 ops can potentially co-execute - - int int_accum = lane_id; - float fp_accum = (float)lane_id; - - #pragma unroll 4 - for (int i = 0; i < 4; i++) { - int_accum = int_accum * 3 + 1; // Integer MACC - fp_accum = fp_accum * 1.5f + 0.5f; // FP32 MACC - } - - // ======================================================================== - // PATTERN 3: Avoid Dual-Issue Blockers - // ======================================================================== - // Transcendentals CANNOT co-execute - avoid clustering them - - // BAD PATTERN (blocks dual-issue): - // float s = sinf(x); - // float c = cosf(x); // Both use Trans pipe, no co-execution - - // BETTER PATTERN (allows some dual-issue): - float x = (float)lane_id * 0.1f; - float s = sinf(x); // Trans pipe - no co-execute - float y = x * 2.0f + 1.0f; // Core MACC - might co-execute with other wave - float c = cosf(x); // Trans pipe - no co-execute - float z = y * 0.5f - 0.5f; // Core MACC - might co-execute with other wave - - // Use results to prevent optimization - volatile float sink = (float)dp_accum + sp_accum + (float)int_accum + fp_accum + s + c + y + z; - (void)sink; - -#elif defined(__gfx942__) - // ======================================================================== - // MI300 (gfx942): Same-Wave Co-Issue via VOPD/VOPD3 - // ======================================================================== - // MI300 supports VOPD encoding which executes 2 operations from the SAME - // wave in parallel. X instruction uses Core MACC, Y instruction uses Side MACC. - // - // VOPD Operations (executed as dual ops in one instruction): - // - V_DUAL_FMAC_F32, V_DUAL_MUL_F32, V_DUAL_ADD_F32, V_DUAL_SUB_F32 - // - V_DUAL_MOV_B32, V_DUAL_CNDMASK_B32 - // - V_DUAL_MAX_NUM_F32, V_DUAL_MIN_NUM_F32 - // - V_DUAL_ADD_NC_U32, V_DUAL_LSHLREV_B32, etc. - // - // VOPD3 extends this with 3 source operands per instruction. - - // Pattern: FP32 operations that compiler can potentially encode as VOPD - float a = (float)lane_id * 0.1f; - float b = (float)lane_id * 0.2f; - float c = 1.0f; - float d = 2.0f; - - // These paired operations may be encoded as VOPD by the compiler - #pragma unroll 4 - for (int i = 0; i < 4; i++) { - // Pair 1: Two independent FMAs - potential VOPD - c = c * a + 0.001f; // V_DUAL_FMAC_F32 (X op - Core MACC) - d = d * b + 0.002f; // V_DUAL_FMAC_F32 (Y op - Side MACC) - } - - // Pattern: Mixed integer and FP32 - potential VOPD - int e = lane_id; - float f = (float)lane_id; - - #pragma unroll 4 - for (int i = 0; i < 4; i++) { - e = e + 1; // V_DUAL_ADD_NC_U32 (X op) - f = f + 1.0f; // V_DUAL_ADD_F32 (Y op) - } - - // Use results to prevent optimization - volatile float sink = c + d + f + (float)e; - (void)sink; - -#elif defined(__gfx90a__) - // MI250 (gfx90a/CDNA2): Limited dual-issue capability - // Basic operations for compatibility - float accum = 1.0f; - #pragma unroll 4 - for (int i = 0; i < 4; i++) { - accum = accum * 1.001f + 0.001f; - } - volatile float sink = accum; - (void)sink; - -#else - // Other architectures - basic fallback - volatile float sink = 1.0f; - (void)sink; -#endif - - // Note: Dual-issue is a hardware scheduling decision. - // This test provides dual-issue ELIGIBLE patterns. - // Actual dual-issue depends on wave scheduling and register availability. - - if (lane_id == 0) { -#if defined(__gfx950__) || defined(__gfx942__) || defined(__gfx1250__) - // gfx950/gfx1250: Cross-wave dual-issue VALU - // gfx942: Same-wave dual-issue via VOPD/VOPD3 - // Both support some form of dual-issue - mark as passed - atomicAdd(&results->dual_issue_passed, 1); -#else - // Other architectures don't support dual-issue - mark as bypassed (-1) - // Only set once (use atomicMin to ensure we get -1) - atomicMin(&results->dual_issue_passed, -1); -#endif - } -} - -// ============================================================================ -// Section 11: Lane/Thread Info Operations -// ============================================================================ - -__device__ void test_lane_thread_info(TestResults* results, int lane_id) { - // Get lane ID using builtin - unsigned int hw_lane_id; - if (warpSize == 32) { - hw_lane_id = __builtin_amdgcn_mbcnt_lo(-1, 0); - } else { - hw_lane_id = __builtin_amdgcn_mbcnt_hi(-1, __builtin_amdgcn_mbcnt_lo(-1, 0)); - } - - bool passed = (hw_lane_id == (unsigned)lane_id); - - // Test wavefront size - int ws = __builtin_amdgcn_wavefrontsize(); - passed = passed && (ws == 32 || ws == 64); - - if (lane_id == 0) { - atomicAdd(&results->lane_id_passed, passed ? 1 : 0); - atomicAdd(&results->wavefront_passed, 1); - } -} - -// ============================================================================ -// Section 12: Packed FP16/BF16 Operations -// ============================================================================ - -__device__ void test_packed_operations(TestResults* results, int lane_id) { - bool f16_passed = true; - bool bf16_passed = true; - - // ======================================================================== - // TEST 1: Packed FP16 Operations - // ======================================================================== - // Create packed FP16 values (2 x FP16 in 32 bits) - __half2 h2_a = __float2half2_rn(1.5f); - __half2 h2_b = __float2half2_rn(2.0f); - - // Packed add - __half2 h2_sum = __hadd2(h2_a, h2_b); - - // Extract and verify - float low = __low2float(h2_sum); - float high = __high2float(h2_sum); - f16_passed = f16_passed && approx_equal_f32(low, 3.5f, 0.01f); - f16_passed = f16_passed && approx_equal_f32(high, 3.5f, 0.01f); - - // Packed multiply - __half2 h2_prod = __hmul2(h2_a, h2_b); - low = __low2float(h2_prod); - f16_passed = f16_passed && approx_equal_f32(low, 3.0f, 0.01f); - - // ======================================================================== - // TEST 2: Packed BF16 Operations - // ======================================================================== - // Create packed BF16 values (2 x BF16 in 32 bits) - // BF16 has same exponent range as FP32 but reduced mantissa precision - - // Convert floats to BF16 - __hip_bfloat16 bf16_a = __float2bfloat16(1.5f); - __hip_bfloat16 bf16_b = __float2bfloat16(2.5f); - - // Create packed BF16 (using __hip_bfloat162 if available) -#if defined(__HIP_PLATFORM_AMD__) - // Pack two BF16 values - __hip_bfloat162 bf16_2_a; - bf16_2_a.x = bf16_a; - bf16_2_a.y = bf16_a; - - __hip_bfloat162 bf16_2_b; - bf16_2_b.x = bf16_b; - bf16_2_b.y = bf16_b; - - // Packed BF16 add (if available) - __hip_bfloat162 bf16_2_sum = __hadd2(bf16_2_a, bf16_2_b); - - // Convert back to float and verify - float bf16_low = __bfloat162float(bf16_2_sum.x); - float bf16_high = __bfloat162float(bf16_2_sum.y); - - // Expected: 1.5 + 2.5 = 4.0 (with BF16 precision tolerance) - bf16_passed = bf16_passed && approx_equal_f32(bf16_low, 4.0f, 0.02f); - bf16_passed = bf16_passed && approx_equal_f32(bf16_high, 4.0f, 0.02f); - - // Packed BF16 multiply - __hip_bfloat162 bf16_2_prod = __hmul2(bf16_2_a, bf16_2_b); - bf16_low = __bfloat162float(bf16_2_prod.x); - - // Expected: 1.5 * 2.5 = 3.75 (with BF16 precision tolerance) - bf16_passed = bf16_passed && approx_equal_f32(bf16_low, 3.75f, 0.02f); -#else - // Fallback: Manual BF16 packed operations - // Convert to BF16, do arithmetic, convert back - float a_val = __bfloat162float(bf16_a); - float b_val = __bfloat162float(bf16_b); - - float sum_val = a_val + b_val; - float prod_val = a_val * b_val; - - bf16_passed = bf16_passed && approx_equal_f32(sum_val, 4.0f, 0.02f); - bf16_passed = bf16_passed && approx_equal_f32(prod_val, 3.75f, 0.02f); -#endif - - if (lane_id == 0) { - atomicAdd(&results->pk_f16_passed, f16_passed ? 1 : 0); - atomicAdd(&results->pk_bf16_passed, bf16_passed ? 1 : 0); - atomicAdd(&results->pk_add_passed, (f16_passed && bf16_passed) ? 1 : 0); - } -} - -// ============================================================================ -// Section 12.5: Cooperative Atomic Operations (gfx1250 specific) -// ============================================================================ -// -// COOPERATIVE ATOMIC OPERATIONS: -// ============================== -// Cooperative atomics allow multiple threads to cooperatively perform atomic -// operations on large data blocks. Available on gfx1250 only. -// -// COOPERATIVE ATOMIC BUILTINS (gfx1250): -// -------------------------------------- -// Load operations: -// - __builtin_amdgcn_cooperative_atomic_load_8x16B: Load 8 x 16-byte blocks -// - __builtin_amdgcn_cooperative_atomic_load_16x8B: Load 16 x 8-byte blocks -// - __builtin_amdgcn_cooperative_atomic_load_32x4B: Load 32 x 4-byte blocks -// -// Store operations: -// - __builtin_amdgcn_cooperative_atomic_store_8x16B: Store 8 x 16-byte blocks -// - __builtin_amdgcn_cooperative_atomic_store_16x8B: Store 16 x 8-byte blocks -// - __builtin_amdgcn_cooperative_atomic_store_32x4B: Store 32 x 4-byte blocks -// -// USAGE: -// Multiple threads cooperate to atomically load/store large data structures. -// This is useful for operations that need to atomically update large blocks -// of memory that exceed single atomic operation sizes. -// - -__device__ void test_cooperative_atomics([[maybe_unused]] int* global_mem, - [[maybe_unused]] TestResults* results, - [[maybe_unused]] int lane_id, - [[maybe_unused]] int tid) { -#if defined(__gfx1250__) - bool load_passed = true; - bool store_passed = true; - - // Test cooperative atomic load (32x4B variant - 32 x 4-byte = 128 bytes) - // This requires 32 threads to cooperatively load 128 bytes atomically - if (__has_builtin(__builtin_amdgcn_cooperative_atomic_load_32x4B)) { - // Initialize test data - if (tid < 32) { - global_mem[tid] = tid * 100; - } - __syncthreads(); - - // Cooperative atomic load - all 32 threads participate - // Each thread gets one 4-byte element - int loaded_val = 0; - if (tid < 32) { - // Note: Actual usage requires proper setup of cooperative group - // For now, we test that the builtin exists and can be called - // In production, this would be used with proper cooperative groups - loaded_val = global_mem[tid]; // Simplified test - load_passed = (loaded_val == tid * 100); - } - } - - // Test cooperative atomic store (32x4B variant) - if (__has_builtin(__builtin_amdgcn_cooperative_atomic_store_32x4B)) { - __syncthreads(); - - // Cooperative atomic store - all 32 threads participate - if (tid < 32) { - int store_val = tid * 200; - global_mem[tid + 64] = store_val; // Simplified test - store_passed = true; - } - - __syncthreads(); - } - - if (lane_id == 0) { - atomicAdd(&results->coop_atomic_load_passed, load_passed ? 1 : 0); - atomicAdd(&results->coop_atomic_store_passed, store_passed ? 1 : 0); - } - -#else - // Cooperative atomics are gfx1250 only - mark as bypassed - if (lane_id == 0) { - atomicMin(&results->coop_atomic_load_passed, -1); - atomicMin(&results->coop_atomic_store_passed, -1); - } -#endif -} - -// ============================================================================ -// Section 13: Async LDS Operations (MI350/gfx1250 specific - global_load/store_async) -// ============================================================================ -// -// Async LDS Support by Architecture: -// - MI250 (gfx90a): NO async LDS support -// - MI300 (gfx942): LIMITED async LDS support (may not have all builtins) -// - MI350 (gfx950): FULL async LDS support (global_load/store_async_to/from_lds) -// - gfx1250 (gfx1250): FULL async LDS support + DS atomic async barriers -// -// GFX1250-SPECIFIC DS ATOMIC BARRIER BUILTINS: -// -------------------------------------------- -// These are used for synchronization with async LDS operations: -// - __builtin_amdgcn_ds_atomic_async_barrier_arrive_b64: Signal barrier arrival -// - __builtin_amdgcn_ds_atomic_barrier_arrive_rtn_b64: Signal barrier with return value -// -// USAGE PATTERN: -// 1. Issue async LDS load/store operations -// 2. Signal barrier arrival using ds_atomic_async_barrier_arrive -// 3. Wait for barrier completion (all threads arrived) -// 4. Use s_wait_asynccnt + s_wait_dscnt to ensure completion -// - -// Shared memory for async LDS test -__shared__ int async_lds_buffer[64]; -#if defined(__gfx1250__) -__shared__ long async_barrier_counter; // Use 'long' for gfx1250 DS atomic barrier -#else -__shared__ long long async_barrier_counter; -#endif - -__device__ void test_async_lds_operations([[maybe_unused]] float* global_src, float* global_dst, - TestResults* results, int lane_id, int tid) { - bool passed = true; - -#if __has_builtin(__builtin_amdgcn_global_load_async_to_lds_b32) - // MI350 (gfx950) / gfx1250: Full async LDS load/store support - // global_load_async_to_lds_b8/b32/b64/b128: Load from global to LDS asynchronously - // global_store_async_from_lds_b8/b32/b64/b128: Store from LDS to global asynchronously - - // Initialize global source with test pattern - if (tid < 64) { - // First, copy test data to global memory - global_src[tid] = (float)(tid * 10 + 1); - } - - // Initialize async barrier counter per block for gfx1250 - if (threadIdx.x == 0) { - async_barrier_counter = 0; - } - __syncthreads(); - - // Perform async load from global to LDS - // Only first thread in each block issues the async load for this test - if (threadIdx.x == 0) { - // Cast pointers to appropriate address spaces - // address_space(1) = global, address_space(3) = local/LDS - typedef int __attribute__((ext_vector_type(4))) vint4; - - // Load 16 bytes (4 ints) asynchronously from global to LDS - __builtin_amdgcn_global_load_async_to_lds_b128( - ((__attribute__((address_space(1))) vint4*)global_src), - ((__attribute__((address_space(3))) vint4*)async_lds_buffer), - 0 /* offset */, 0 /* cache policy */); - } - -#if defined(__gfx1250__) - // GFX1250: Use DS atomic async barrier for synchronization - // This signals that this thread has reached the barrier point - // The barrier counter is atomically incremented - if (__has_builtin(__builtin_amdgcn_ds_atomic_async_barrier_arrive_b64)) { - __builtin_amdgcn_ds_atomic_async_barrier_arrive_b64( - ((__attribute__((address_space(3))) long*)&async_barrier_counter)); - } - - // Wait for async operations to complete - // On gfx1250, use s_wait_asynccnt for async LDS operations - __builtin_amdgcn_s_wait_asynccnt(0); // Wait for async copy operations -#else - // Other architectures: Use unified s_waitcnt - WAITCNT_ALL(); // Wait for all memory operations -#endif - __syncthreads(); - - // Verify the data was loaded correctly to LDS - if (tid < 4) { - int expected = (int)(tid * 10 + 1); - // Note: We loaded floats but stored as ints, reinterpret - float* lds_as_float = (float*)async_lds_buffer; - passed = passed && approx_equal_f32(lds_as_float[tid], (float)expected, 0.1f); - } - __syncthreads(); - - // Now test async store from LDS to global - if (tid == 0) { - // Modify LDS data - async_lds_buffer[0] = 12345; - async_lds_buffer[1] = 67890; - async_lds_buffer[2] = 11111; - async_lds_buffer[3] = 22222; - } - __syncthreads(); - - if (tid == 0) { - typedef int __attribute__((ext_vector_type(4))) vint4; - - // Store 16 bytes from LDS to global asynchronously - __builtin_amdgcn_global_store_async_from_lds_b128( - ((__attribute__((address_space(1))) vint4*)(global_dst)), - ((__attribute__((address_space(3))) vint4*)async_lds_buffer), - 0 /* offset */, 0 /* cache policy */); - } - -#if defined(__gfx1250__) - // GFX1250: Use DS atomic async barrier for store synchronization - if (__has_builtin(__builtin_amdgcn_ds_atomic_async_barrier_arrive_b64)) { - __builtin_amdgcn_ds_atomic_async_barrier_arrive_b64( - ((__attribute__((address_space(3))) long*)&async_barrier_counter)); - } - - // Wait for async store operations to complete - __builtin_amdgcn_s_wait_asynccnt(0); // Wait for async copy operations -#else - // Other architectures: Use unified s_waitcnt - WAITCNT_ALL(); -#endif - __syncthreads(); - - // Verify the store (on subsequent kernel or by reading back) - // For now, just check that no crash occurred - passed = passed && true; - -#else - // Async LDS builtins not available - mark as bypassed - (void)global_src; - (void)global_dst; - (void)tid; - (void)passed; - - if (lane_id == 0) { - atomicMin(&results->async_lds_load_passed, -1); - atomicMin(&results->async_lds_store_passed, -1); - } - return; -#endif - - if (lane_id == 0) { - atomicAdd(&results->async_lds_load_passed, passed ? 1 : 0); - atomicAdd(&results->async_lds_store_passed, passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 14: TDM (Tensor Data Mover) Operations (RDNA4/gfx1250 only) -// ============================================================================ -// -// TDM Support by Architecture: -// - MI250 (gfx90a): NO TDM - uses regular LDS operations -// - MI300 (gfx942): NO TDM - uses regular LDS operations -// - MI350 (gfx950): NO TDM - uses regular LDS operations (may have async LDS) -// - gfx1250: FULL TDM support (tensor_load/store_to/from_lds) -// -// TDM Features: -// - Dedicated DMA unit for tensor data movement -// - Up to 5-dimensional tensor support -// - Automatic address generation and bounds checking -// - -// TDM Descriptor structures for gfx1250 (forward declarations for compatibility) -#if defined(__gfx1250__) -// Include TDM header for gfx1250 -// These structures define tensor descriptors for multi-dimensional data movement -#include -#endif - -// Shared memory for TDM test -__shared__ int tdm_lds_buffer[16 * 16]; - -__device__ void test_tdm_operations(int* global_src, [[maybe_unused]] int* global_dst, - TestResults* results, int lane_id, int tid) { - bool passed = true; - -#if defined(__gfx1250__) - // gfx1250: FULL TDM support - // Reference: test_amd_gfx1250_TDM.cpp - - const int SIZE_X = 10; - const int SIZE_Y = 10; - - // Initialize source data (only first thread) - if (tid == 0) { - for (int i = 0; i < SIZE_X * SIZE_Y; i++) { - global_src[i] = i; - } - } - __syncthreads(); - - // Set up TDM descriptors (following reference pattern) - auto* pShmem = tdm_lds_buffer; - - gfx1250_TDM_GROUP0 group0; - group0.globalAddr((uintptr_t)global_src); - group0.ldsAddr((uintptr_t)pShmem); - - gfx1250_TDM_GROUP1 group1; - group1.dataSize(2); // 2 = 4 bytes (int) - group1.tensorDim0(SIZE_X); - group1.tensorDim1(SIZE_Y); - group1.tensorDim0Stride(SIZE_X); - group1.tensorDim1Stride(SIZE_Y); - group1.tileDim0(SIZE_X); - group1.tileDim1(SIZE_Y); - - // TDM Load: Load 2D tensor from global to LDS - // Guard with __has_builtin: NPI toolchains may not yet expose TDM d2 builtins -#if __has_builtin(__builtin_amdgcn_tensor_load_to_lds_d2) - __builtin_amdgcn_tensor_load_to_lds_d2(group0.m_bitfield, group1.m_bitfield, 0); - __builtin_amdgcn_s_wait_tensorcnt(0); - __syncthreads(); - - // Verify load (check a few elements) - if (tid < 10) { - passed = passed && (tdm_lds_buffer[tid] == tid); - } - __syncthreads(); - - // TDM Store: Write back to global - group0.globalAddr((uintptr_t)global_dst); - __builtin_amdgcn_tensor_store_from_lds_d2(group0.m_bitfield, group1.m_bitfield, 0); - __builtin_amdgcn_s_wait_tensorcnt(0); - __syncthreads(); -#else - // TDM d2 builtins not available in this toolchain - mark as bypassed - (void)group0; (void)group1; (void)passed; - if (lane_id == 0) { - atomicMin(&results->tdm_load_passed, -1); - atomicMin(&results->tdm_store_passed, -1); - } -#endif - -#else - // TDM is gfx1250 only - mark as bypassed for all other architectures - (void)global_src; - (void)global_dst; - (void)tid; - (void)passed; - - if (lane_id == 0) { - atomicMin(&results->tdm_load_passed, -1); - atomicMin(&results->tdm_store_passed, -1); - } - return; -#endif - - if (lane_id == 0) { - atomicAdd(&results->tdm_load_passed, passed ? 1 : 0); - atomicAdd(&results->tdm_store_passed, passed ? 1 : 0); - } -} - -// ============================================================================ -// Section 15: Cluster Group Operations (gfx950/gfx1250 specific) -// ============================================================================ -// -// WORKGROUP CLUSTER FEATURE: -// ================================ -// Workgroup clusters allow multiple workgroups to be launched to the same -// shader engine (SE) and execute cooperatively. This enables: -// -// 1. CLUSTER SYNCHRONIZATION: -// - Workgroups in a cluster can synchronize using cluster barriers -// - Uses split-barrier mechanism (signal/wait pattern) -// - Up to 15 clusters per SE, up to 12 workgroups per cluster -// -// 2. CLUSTER BROADCAST LOADS: -// - CLUSTER_LOAD_B{32,64,128}: Shared memory loads across cluster -// - Multiple workgroups request same data, coalesced into single request -// - Data broadcast to all workgroups in the cluster -// -// 3. CLUSTER IDENTIFICATION: -// - ClusterID: Which cluster (0 = not in cluster, 1-15 = in cluster) -// - WorkgroupInCluster: Logical workgroup ID within cluster (0..11) -// - waveInWG: Wave ID within workgroup -// -// CLUSTER BARRIER INSTRUCTIONS: -// ----------------------------- -// - S_BARRIER_SIGNAL(-3): Signal user cluster barrier -// - S_BARRIER_WAIT(-3): Wait on user cluster barrier -// - S_BARRIER_SIGNAL(-4): Signal trap cluster barrier (for CWSR) -// - S_BARRIER_WAIT(-4): Wait on trap cluster barrier -// -// CLUSTER BARRIER USAGE PATTERN: -// ------------------------------ -// 1. Each workgroup does internal workgroup barrier first -// 2. One wave per workgroup signals the cluster barrier -// 3. All waves wait on cluster barrier completion -// 4. Workgroups barrier again before continuing -// -// BUILTINS FOR CLUSTER: -// --------------------- -// - __builtin_amdgcn_s_barrier_signal(-3) // Signal cluster barrier -// - __builtin_amdgcn_s_barrier_wait(-3) // Wait on cluster barrier -// - __builtin_amdgcn_s_barrier_signal_isfirst(-1) // First wave detection -// - __builtin_amdgcn_cluster_workgroup_id_x/y/z() // Cluster workgroup IDs -// -// NOTE: Cluster operations require kernel launch with cluster dimensions. -// Standard kernel launches (hipLaunchKernelGGL) don't enable clustering. -// Use cooperative launch APIs with cluster parameters for actual cluster usage. -// - -__device__ void test_cluster_operations(int* global_int, TestResults* results, int lane_id, - int tid) { - bool passed = true; - bool cluster_dispatch = false; - bool cluster_load_passed = true; - -#if defined(__gfx1250__) - // gfx1250 binary is built with cluster_dims(2,1,1) and launched via cluster API. - cluster_dispatch = true; - - #if __has_builtin(__builtin_amdgcn_cluster_id_x) - const unsigned cluster_id_x = __builtin_amdgcn_cluster_id_x(); - const unsigned cluster_id_y = __builtin_amdgcn_cluster_id_y(); - const unsigned cluster_id_z = __builtin_amdgcn_cluster_id_z(); - (void)cluster_id_x; - (void)cluster_id_y; - (void)cluster_id_z; - #endif - - // Test 1: __builtin_amdgcn_s_cluster_barrier (gfx1250-specific) - #if __has_builtin(__builtin_amdgcn_s_cluster_barrier) - __builtin_amdgcn_s_cluster_barrier(); - passed = true; - #endif - - // Test 2: Split barrier builtins (workgroup and cluster scopes) - #if __has_builtin(__builtin_amdgcn_s_barrier_signal) && \ - __has_builtin(__builtin_amdgcn_s_barrier_wait) - - __syncthreads(); - __builtin_amdgcn_s_barrier_signal(-1); - __builtin_amdgcn_s_barrier_wait(-1); - - if(cluster_dispatch) { - #if __has_builtin(__builtin_amdgcn_s_barrier_signal_isfirst) - if(__builtin_amdgcn_s_barrier_signal_isfirst(-1)) { - __builtin_amdgcn_s_barrier_signal(-3); - } - #else - if(lane_id == 0) { - __builtin_amdgcn_s_barrier_signal(-3); - } - #endif - __builtin_amdgcn_s_barrier_wait(-3); - } - __syncthreads(); - passed = true; - - #else - passed = true; - #endif - - // Test 3: Cluster workgroup ID builtins - #if __has_builtin(__builtin_amdgcn_cluster_workgroup_id_x) - unsigned int cluster_wg_x = __builtin_amdgcn_cluster_workgroup_id_x(); - unsigned int cluster_wg_y = __builtin_amdgcn_cluster_workgroup_id_y(); - unsigned int cluster_wg_z = __builtin_amdgcn_cluster_workgroup_id_z(); - - if(cluster_dispatch) { - passed = passed && (cluster_wg_x < 12); - passed = passed && (cluster_wg_y < 12); - passed = passed && (cluster_wg_z < 12); - } else { - passed = passed && (cluster_wg_x == 0); - passed = passed && (cluster_wg_y == 0); - passed = passed && (cluster_wg_z == 0); - } - - volatile unsigned int sink = cluster_wg_x + cluster_wg_y + cluster_wg_z; - (void)sink; - #endif - - // Test 4: Cluster broadcast load (generates GL0-GL1 cluster requests) - #if __has_builtin(__builtin_amdgcn_cluster_load_b32) - if(cluster_dispatch && global_int != nullptr) { - // cluster_dims(2,1,1): pair blockIdx.x values share a cluster. Use a - // per-cluster slot (not global_int[0]) so concurrent clusters do not - // stomp each other; lead WG publishes, syncthreads, cluster barrier, - // then broadcast. - constexpr int kClusterLoadSlotBase = 128; - constexpr int kClusterLoadMagic = 0xC1A57EED; - const int cluster_slot = - kClusterLoadSlotBase + static_cast((blockIdx.x / 2u) & 127u); - int* const cluster_load_addr = global_int + cluster_slot; - - #if __has_builtin(__builtin_amdgcn_cluster_workgroup_id_x) - const unsigned cluster_wg_x = __builtin_amdgcn_cluster_workgroup_id_x(); - const unsigned cluster_wg_y = __builtin_amdgcn_cluster_workgroup_id_y(); - const unsigned cluster_wg_z = __builtin_amdgcn_cluster_workgroup_id_z(); - const bool cluster_lead_wg = - (cluster_wg_x == 0u && cluster_wg_y == 0u && cluster_wg_z == 0u); - if(cluster_lead_wg && tid == 0) { - cluster_load_addr[0] = kClusterLoadMagic; - } - #else - if(tid == 0) { - cluster_load_addr[0] = kClusterLoadMagic; - } - #endif - __syncthreads(); - - // Workgroup barrier alone is insufficient: cluster_load_b32 reads across - // the cluster, so the lead WG store must be visible before broadcast. - #if __has_builtin(__builtin_amdgcn_s_cluster_barrier) - __builtin_amdgcn_s_cluster_barrier(); - #elif __has_builtin(__builtin_amdgcn_s_barrier_signal) && \ - __has_builtin(__builtin_amdgcn_s_barrier_wait) - #if __has_builtin(__builtin_amdgcn_s_barrier_signal_isfirst) - if(__builtin_amdgcn_s_barrier_signal_isfirst(-1)) { - __builtin_amdgcn_s_barrier_signal(-3); - } - #else - if(lane_id == 0) { - __builtin_amdgcn_s_barrier_signal(-3); - } - #endif - __builtin_amdgcn_s_barrier_wait(-3); - __syncthreads(); - #endif - - constexpr int kClusterLoadCpol = 0; - constexpr int kClusterLoadMask = -1; - const int loaded = __builtin_amdgcn_cluster_load_b32( - cluster_load_addr, kClusterLoadCpol, kClusterLoadMask); - cluster_load_passed = (loaded == kClusterLoadMagic); - volatile int load_sink = loaded; - (void)load_sink; - } - #endif - -#else - // Cluster operations are gfx1250 only - mark as bypassed - (void)passed; - (void)cluster_dispatch; - - if (lane_id == 0) { - atomicMin(&results->cluster_barrier_passed, -1); - atomicMin(&results->cluster_info_passed, -1); - atomicMin(&results->cluster_load_passed, -1); - } - return; -#endif - - if (lane_id == 0) { - atomicAdd(&results->cluster_barrier_passed, passed ? 1 : 0); - atomicAdd(&results->cluster_info_passed, passed ? 1 : 0); - if(cluster_dispatch) { - atomicAdd(&results->cluster_load_passed, cluster_load_passed ? 1 : 0); - } else { - atomicMin(&results->cluster_load_passed, -1); - } - } -} - -// ============================================================================ -// Section 15.5: WMMA (Wave Matrix Multiply Accumulate) Operations (RDNA4/gfx1250) -// ============================================================================ -// -// WMMA INSTRUCTIONS - RDNA4 Matrix Cores: -// ======================================= -// Reference: https://gpuopen.com/learn/using_matrix_core_amd_rdna4/ -// -// gfx12/gfx1250 features 3rd-generation Matrix Cores with significantly -// improved GEMM performance compared to RDNA3: -// -// PERFORMANCE COMPARISON (FLOPS/clock/CU): -// ---------------------------------------- -// | Format | RDNA2 (RX 6950) | RDNA3 (RX 7900) | RDNA4 (RX 9070) | -// |--------|-----------------|-----------------|-----------------| -// | FP16 | 256 | 512 | 1024 | -// | BF16 | N/A | 512 | 1024 | -// | I8 | 512 | 512 | 2048 | -// -// WMMA INTRINSIC FORMAT: -// ---------------------- -// IMPORTANT: The _gfx12 suffix is ONLY for RDNA4 consumer GPUs (gfx1200/gfx1201). -// gfx1250 does NOT use the _gfx12 suffix. -// -// gfx1200/gfx1201 (RDNA4 consumer): __builtin_amdgcn_wmma_*_16x16x16_*_w32_gfx12 (3-arg) -// gfx1150/gfx1151/gfx1152 (RDNA 3.5 Strix): __builtin_amdgcn_wmma_*_16x16x16_*_w32 (GFX11, 3-arg) -// gfx1250 (gfx1250): __builtin_amdgcn_wmma_* (no suffix; different signatures per BuiltinsAMDGPU.td) -// -// Available for RDNA4 consumer (gfx1200/gfx1201 only): _w32_gfx12 suffix, 3 args (a, b, c) -// - __builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12 (FP16 → FP32) -// - __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12 (BF16 → FP32) -// - __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12 (INT8 → INT32) -// -// Available for gfx1250 (gfx1250 only, no _gfx12 suffix): 8-arg FP16/BF16, 7-arg INT8 -// - __builtin_amdgcn_wmma_f32_16x16x32_f16, __builtin_amdgcn_wmma_f32_16x16x32_bf16 -// - __builtin_amdgcn_wmma_i32_16x16x64_iu8 -// -// MATRIX LAYOUT IN VGPR: -// ---------------------- -// WMMA operates on 16x16 matrices distributed across a Wave32: -// - Each lane holds 8 elements (8 * 32 = 256 = 16 * 16 elements) -// - Matrix A is column-major (transposed) -// - Matrices B, C, D are row-major -// - laneWrapped = threadIdx.x % 16 (column index within matrix) -// - laneGroup = threadIdx.x / 16 (upper/lower half of matrix) -// -// WMMA USAGE PATTERN: -// ------------------- -// 1. Load matrix A (column-major) into fragment -// 2. Load matrix B (row-major) into fragment -// 3. Execute WMMA intrinsic with all lanes in wavefront -// 4. Store result matrix D (row-major) from fragment -// -// IMPORTANT NOTES: -// ---------------- -// - RDNA4 uses Wave32 ONLY for WMMA (no Wave64 WMMA) -// - VGPR layout changed vs RDNA3 - NOT backward compatible -// - All 32 lanes must participate in the WMMA operation -// - Matrix dimensions are fixed at 16x16 -// - -// Vector types for WMMA operations (RDNA4) -// Fragment types: 8 elements per lane for 16x16 matrix -#if defined(__gfx1250__) || defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU -using wmma_f16_frag = __attribute__((__vector_size__(8 * sizeof(_Float16)))) _Float16; -using wmma_bf16_frag = __attribute__((__vector_size__(8 * sizeof(short)))) short; -using wmma_f32_frag = __attribute__((__vector_size__(8 * sizeof(float)))) float; -using wmma_i32_frag = __attribute__((__vector_size__(8 * sizeof(int)))) int; -using wmma_i8_frag = __attribute__((__vector_size__(2 * sizeof(int)))) int; // 2 packed i32 for 8 i8 elements -#endif - -// ============================================================================ -// Section 18: Buffer Operations (gfx1250 — descriptor-based addressing) -// ============================================================================ -// -// buffer_load_b32 / buffer_store_b32 use a 128-bit buffer resource descriptor -// set up via __builtin_amdgcn_make_buffer_rsrc(ptr, stride, numrecords, flags). -// This exercises SQ_INSTS_BUFFER_LOAD_sum and SQ_INSTS_BUFFER_STORE_sum -// counters that are 0 when only flat/global instructions are used. -// - -__device__ void test_buffer_ops([[maybe_unused]] float* global_mem, - [[maybe_unused]] TestResults* results, - [[maybe_unused]] int lane_id, - [[maybe_unused]] int tid) { -#if defined(__gfx1250__) - // Build a buffer resource descriptor over global_mem (256 floats). - // stride=0: raw buffer mode — numrecords is in bytes, soffset is in bytes. - // addr = base + soffset. OOB if soffset >= numrecords. - // numrecords covers the whole buffer; byte_off is unique per global thread. - __amdgpu_buffer_rsrc_t rsrc = __builtin_amdgcn_make_buffer_rsrc( - global_mem, 0 /*stride=0 raw*/, 0x7FFFFFFF /*large: covers any buffer*/, 0); - - // Use a constant value: all threads write the same value to offset 0, - // so there is no race and the read-back is deterministic. - // "off" addressing: addr = base + soffset (SGPR). - // Using soffset=0 (constant, SGPR-safe) → all access same slot. - float wval = 42.0f; // constant — all threads write same value - float rval = 0.0f; - int soff = 0; // SGPR-safe constant soffset - - // buffer_store_b32 off: soffset=0 (SGPR). All threads store the same value, - // so the final result is deterministic regardless of execution order. - asm volatile("buffer_store_b32 %[v], off, %[r], %[o]\n\t" - "s_wait_storecnt 0x0\n\t" - : : [v]"v"(wval), [r]"s"(rsrc), [o]"s"(soff) : "memory"); - - // buffer_load_b32 off: read back — all threads read from offset 0. - asm volatile("buffer_load_b32 %[v], off, %[r], %[o]\n\t" - "s_wait_loadcnt 0x0\n\t" - : [v]"=v"(rval) : [r]"s"(rsrc), [o]"s"(soff) : "memory"); - - bool passed = (fabsf(rval - wval) < 0.01f); - if (lane_id == 0) { - atomicAdd(&results->buffer_load_passed, passed ? 1 : 0); - atomicAdd(&results->buffer_store_passed, passed ? 1 : 0); - } -#else - if (lane_id == 0) { - atomicMin(&results->buffer_load_passed, -1); - atomicMin(&results->buffer_store_passed, -1); - } -#endif -} - -// ============================================================================ -// Section 19: Multicast Load (broadcast read — all threads same address) -// ============================================================================ -// -// When all threads in a wavefront load from the SAME address the hardware can -// coalesce the request into a single fetch + broadcast (multicast), counted -// by SQ_INSTS_MULTICAST_LOAD_sum. -// - -__device__ void test_multicast_load(float* global_mem, - TestResults* results, - int lane_id, - int tid) { - // Thread 0 writes a known value to a shared broadcast location - if (tid == 0) { - global_mem[512] = 99.0f; // use slot 512 to avoid aliasing with other tests - } - __syncthreads(); - - // ALL threads read from the SAME address → potential GL0/GL1 multicast - // Use a volatile read so the compiler doesn't hoist or collapse loads - volatile float bval = ((volatile float*)global_mem)[512]; - - bool passed = (fabsf(bval - 99.0f) < 0.01f); - if (lane_id == 0) { - atomicAdd(&results->multicast_load_passed, passed ? 1 : 0); - } -} - -__device__ void test_wmma_operations([[maybe_unused]] TestResults* results, - [[maybe_unused]] int lane_id, - [[maybe_unused]] int tid) { -#if defined(__gfx1250__) - // ======================================================================== - // gfx1250 WMMA Tests - use gfx1250 builtins (no _gfx12 suffix) - // BuiltinsAMDGPU.td: __builtin_amdgcn_wmma_f32_16x16x32_f16, _bf16, i32_16x16x64_iu8 - // ======================================================================== - bool f16_passed = true; - bool bf16_passed = true; - bool i8_passed = true; - - const int laneWrapped = tid % 16; - const int laneGroup = tid / 16; - // gfx1250 FP16/BF16: 16 elements per lane for A and B; INT8: 8 int (32 i8) per lane - using wmma_f16_frag_gfx1250 = __attribute__((__vector_size__(16 * sizeof(_Float16)))) _Float16; - using wmma_bf16_frag_gfx1250 = __attribute__((__vector_size__(16 * sizeof(short)))) short; - using wmma_i8_frag_gfx1250 = __attribute__((__vector_size__(8 * sizeof(int)))) int; - - // ---- FP16: __builtin_amdgcn_wmma_f32_16x16x32_f16(transA, A_16, transB, B_16, scale, C_8, bool, bool) ---- - { - wmma_f16_frag_gfx1250 a_frag; - wmma_f16_frag_gfx1250 b_frag; - wmma_f32_frag c_frag = {0}; - for (int ele = 0; ele < 16; ele++) { - a_frag[ele] = (_Float16)((laneWrapped + (ele + laneGroup * 16) % 32) % 16 + 1) * 0.1f; - b_frag[ele] = (_Float16)(((ele + laneGroup * 16) % 32 + laneWrapped) % 16 + 1) * 0.1f; - } - for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; - #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x32_f16) - c_frag = __builtin_amdgcn_wmma_f32_16x16x32_f16(false, a_frag, false, b_frag, (short)0, c_frag, false, false); - #endif - volatile float sink = 0; - for (int i = 0; i < 8; i++) sink += c_frag[i]; - f16_passed = (sink != 0.0f || tid >= 32); - (void)sink; - } - - // ---- BF16: __builtin_amdgcn_wmma_f32_16x16x32_bf16(transA, A_16, transB, B_16, scale, C_8, bool, bool) ---- - { - wmma_bf16_frag_gfx1250 a_frag; - wmma_bf16_frag_gfx1250 b_frag; - wmma_f32_frag c_frag = {0}; - for (int ele = 0; ele < 16; ele++) { - float va = (float)((laneWrapped + ele) % 16 + 1) * 0.1f; - float vb = (float)((ele + laneWrapped) % 16 + 1) * 0.1f; - a_frag[ele] = (short)(*(unsigned int*)&va >> 16); - b_frag[ele] = (short)(*(unsigned int*)&vb >> 16); - } - for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; - #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x32_bf16) - c_frag = __builtin_amdgcn_wmma_f32_16x16x32_bf16(false, a_frag, false, b_frag, (short)0, c_frag, false, false); - #endif - volatile float sink = 0; - for (int i = 0; i < 8; i++) sink += c_frag[i]; - bf16_passed = (sink != 0.0f || tid >= 32); - (void)sink; - } - - // ---- INT8: __builtin_amdgcn_wmma_i32_16x16x64_iu8(negA, A_8i, negB, B_8i, C_8i, bool, bool) ---- - { - wmma_i8_frag_gfx1250 a_frag = {0}; - wmma_i8_frag_gfx1250 b_frag = {0}; - wmma_i32_frag c_frag = {0}; - for (int ele = 0; ele < 8; ele++) { - unsigned int pa = 0, pb = 0; - for (int i = 0; i < 4; i++) { - int ia = (laneWrapped + ele * 4 + i) % 16 + 1; - int ib = (ele * 4 + i + laneWrapped) % 16 + 1; - pa |= (ia & 0xFF) << (i * 8); - pb |= (ib & 0xFF) << (i * 8); - } - a_frag[ele] = (int)pa; - b_frag[ele] = (int)pb; - } - #if __has_builtin(__builtin_amdgcn_wmma_i32_16x16x64_iu8) - c_frag = __builtin_amdgcn_wmma_i32_16x16x64_iu8(false, a_frag, false, b_frag, c_frag, false, false); - #endif - volatile int sink = 0; - for (int i = 0; i < 8; i++) sink += c_frag[i]; - i8_passed = (sink != 0 || tid >= 32); - (void)sink; - } - - if (lane_id == 0) { - atomicAdd(&results->wmma_f16_passed, f16_passed ? 1 : 0); - atomicAdd(&results->wmma_bf16_passed, bf16_passed ? 1 : 0); - atomicAdd(&results->wmma_i8_passed, i8_passed ? 1 : 0); - } - -#elif ARCH_RDNA35_APU - // ======================================================================== - // RDNA 3.5 APU (gfx115x - Strix) WMMA Tests - // Uses GFX11 WMMA builtins (wmma-256b-insts) with _Vector<16> inputs - // These are DIFFERENT from GFX12 builtins which use _Vector<8> inputs - // ======================================================================== - bool f16_passed = true; - bool bf16_passed = true; - bool i8_passed = true; - - const int laneWrapped = tid % 16; - const int laneGroup = tid / 16; - - // GFX11 WMMA uses 16-element input vectors (wmma-256b-insts) - using wmma_f16_frag_gfx11 = __attribute__((__vector_size__(16 * sizeof(_Float16)))) _Float16; - using wmma_bf16_frag_gfx11 = __attribute__((__vector_size__(16 * sizeof(short)))) short; - using wmma_i8_frag_gfx11 = __attribute__((__vector_size__(4 * sizeof(int)))) int; // 4 x int32 for 16 x int8 packed - - // ---- FP16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_f16_w32 ---- - { - wmma_f16_frag_gfx11 a_frag; - wmma_f16_frag_gfx11 b_frag; - wmma_f32_frag c_frag = {0}; - - for (int ele = 0; ele < 16; ele++) { - a_frag[ele] = (_Float16)((laneWrapped + ele + laneGroup * 8) % 16 + 1) * 0.1f; - b_frag[ele] = (_Float16)((ele + laneGroup * 8 + laneWrapped) % 16 + 1) * 0.1f; - } - - for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; - - #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_f16_w32) - c_frag = __builtin_amdgcn_wmma_f32_16x16x16_f16_w32(a_frag, b_frag, c_frag); - #endif - - volatile float sink = 0; - for (int i = 0; i < 8; i++) sink += c_frag[i]; - f16_passed = (sink != 0.0f || tid >= 32); - (void)sink; - } - - // ---- BF16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32 ---- - { - wmma_bf16_frag_gfx11 a_frag; - wmma_bf16_frag_gfx11 b_frag; - wmma_f32_frag c_frag = {0}; - - for (int ele = 0; ele < 16; ele++) { - float val = (float)((laneWrapped + ele) % 16 + 1) * 0.1f; - a_frag[ele] = (short)(*(unsigned int*)&val >> 16); - b_frag[ele] = (short)(*(unsigned int*)&val >> 16); - } - - for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; - - #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_bf16_w32) - c_frag = __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32(a_frag, b_frag, c_frag); - #endif - - volatile float sink = 0; - for (int i = 0; i < 8; i++) sink += c_frag[i]; - bf16_passed = (sink != 0.0f || tid >= 32); - (void)sink; - } - - // ---- INT8 WMMA: __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32 ---- - { - wmma_i8_frag_gfx11 a_frag; // 4 x int32 for 16 x int8 packed - wmma_i8_frag_gfx11 b_frag; - wmma_i32_frag c_frag = {0}; - - // Pack 4 INT8 values into each of 4 int32 elements (16 total) - for (int ele = 0; ele < 4; ele++) { - unsigned int packed_a = 0; - unsigned int packed_b = 0; - for (int i = 0; i < 4; i++) { - int val_a = ((laneWrapped + ele * 4 + i) % 16 + 1); - int val_b = ((ele * 4 + i + laneWrapped) % 16 + 1); - packed_a |= (val_a & 0xFF) << (i * 8); - packed_b |= (val_b & 0xFF) << (i * 8); - } - a_frag[ele] = (int)packed_a; - b_frag[ele] = (int)packed_b; - } - - for (int i = 0; i < 8; i++) c_frag[i] = 0; - - #if __has_builtin(__builtin_amdgcn_wmma_i32_16x16x16_iu8_w32) - c_frag = __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32( - false, a_frag, false, b_frag, c_frag, false); - #endif - - volatile int sink = 0; - for (int i = 0; i < 8; i++) sink += c_frag[i]; - i8_passed = (sink != 0 || tid >= 32); - (void)sink; - } - - if (lane_id == 0) { - atomicAdd(&results->wmma_f16_passed, f16_passed ? 1 : 0); - atomicAdd(&results->wmma_bf16_passed, bf16_passed ? 1 : 0); - atomicAdd(&results->wmma_i8_passed, i8_passed ? 1 : 0); - } - -#elif defined(__gfx1200__) || defined(__gfx1201__) - // ======================================================================== - // RDNA4 Consumer (gfx1200/gfx1201 - RX 9070 XT) WMMA Tests - // Uses GFX12 WMMA builtins (wmma-128b-insts) with _Vector<8> inputs - // ======================================================================== - bool f16_passed = true; - bool bf16_passed = true; - bool i8_passed = true; - - const int laneWrapped = tid % 16; - const int laneGroup = tid / 16; - const int WMMA_DATA_WIDTH = 8; - - // ---- FP16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12 ---- - { - wmma_f16_frag a_frag; - wmma_f16_frag b_frag; - wmma_f32_frag c_frag = {0}; - - for (int ele = 0; ele < WMMA_DATA_WIDTH; ele++) { - a_frag[ele] = (_Float16)((laneWrapped + ele + laneGroup * WMMA_DATA_WIDTH) % 16 + 1) * 0.1f; - b_frag[ele] = (_Float16)((ele + laneGroup * WMMA_DATA_WIDTH + laneWrapped) % 16 + 1) * 0.1f; - } - - for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; - - #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12) - c_frag = __builtin_amdgcn_wmma_f32_16x16x16_f16_w32_gfx12(a_frag, b_frag, c_frag); - #endif - - volatile float sink = 0; - for (int i = 0; i < 8; i++) sink += c_frag[i]; - f16_passed = (sink != 0.0f || tid >= 32); - (void)sink; - } - - // ---- BF16 WMMA: __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12 ---- - { - wmma_bf16_frag a_frag; - wmma_bf16_frag b_frag; - wmma_f32_frag c_frag = {0}; - - for (int ele = 0; ele < WMMA_DATA_WIDTH; ele++) { - float val = (float)((laneWrapped + ele) % 16 + 1) * 0.1f; - a_frag[ele] = (short)(*(unsigned int*)&val >> 16); - b_frag[ele] = (short)(*(unsigned int*)&val >> 16); - } - - for (int i = 0; i < 8; i++) c_frag[i] = 0.0f; - - #if __has_builtin(__builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12) - c_frag = __builtin_amdgcn_wmma_f32_16x16x16_bf16_w32_gfx12(a_frag, b_frag, c_frag); - #endif - - volatile float sink = 0; - for (int i = 0; i < 8; i++) sink += c_frag[i]; - bf16_passed = (sink != 0.0f || tid >= 32); - (void)sink; - } - - // ---- INT8 WMMA: __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12 ---- - { - wmma_i8_frag a_frag; // 2 x int32 for 8 x int8 packed - wmma_i8_frag b_frag; - wmma_i32_frag c_frag = {0}; - - // Pack 4 INT8 values into each of 2 int32 elements - for (int ele = 0; ele < 2; ele++) { - unsigned int packed_a = 0; - unsigned int packed_b = 0; - for (int i = 0; i < 4; i++) { - int val_a = ((laneWrapped + ele * 4 + i) % 16 + 1); - int val_b = ((ele * 4 + i + laneWrapped) % 16 + 1); - packed_a |= (val_a & 0xFF) << (i * 8); - packed_b |= (val_b & 0xFF) << (i * 8); - } - a_frag[ele] = (int)packed_a; - b_frag[ele] = (int)packed_b; - } - - for (int i = 0; i < 8; i++) c_frag[i] = 0; - - #if __has_builtin(__builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12) - c_frag = __builtin_amdgcn_wmma_i32_16x16x16_iu8_w32_gfx12( - false, a_frag, false, b_frag, c_frag, false); - #endif - - volatile int sink = 0; - for (int i = 0; i < 8; i++) sink += c_frag[i]; - i8_passed = (sink != 0 || tid >= 32); - (void)sink; - } - - if (lane_id == 0) { - atomicAdd(&results->wmma_f16_passed, f16_passed ? 1 : 0); - atomicAdd(&results->wmma_bf16_passed, bf16_passed ? 1 : 0); - atomicAdd(&results->wmma_i8_passed, i8_passed ? 1 : 0); - } - -#else - // WMMA not available on CDNA architectures (use MFMA instead) - // Mark as bypassed - if (lane_id == 0) { - atomicMin(&results->wmma_f16_passed, -1); - atomicMin(&results->wmma_bf16_passed, -1); - atomicMin(&results->wmma_i8_passed, -1); - } -#endif -} - -// ============================================================================ -// Section 16: RDNA4 Consumer-Specific Features (RX 9070 XT / gfx1200/gfx1201) -// ============================================================================ -// -// RDNA4 CONSUMER GPU FEATURES (RX 9070 XT, RX 9070, RX 9060 series): -// ================================================================== -// -// ARCHITECTURE OVERVIEW: -// ---------------------- -// - GFX12 architecture (gfx1200/gfx1201) -// - WGP (Workgroup Processor) based - 2 CUs per WGP -// - Wave32 native, Wave64 emulated -// - Improved AI inference accelerators -// - FP8/BF8 OCP (Open Compute Platform) format support -// -// KEY DIFFERENCES FROM CDNA (MI series): -// -------------------------------------- -// - No hardware FP64 atomics (limited FP64 in general) -// - Graphics-focused instruction set (more pixel/vertex ops) -// - Different memory hierarchy (infinity cache) -// - WGP instead of CU-centric design -// -// RDNA4 SPECIFIC INSTRUCTIONS: -// ---------------------------- -// - FP8/BF8 conversions (OCP format) -// - Async LDS operations -// - AI inference dot products -// - Ray-box/ray-triangle intersection instructions -// -// This test verifies RDNA4-specific features when running on consumer GPUs. - -__device__ void test_rdna4_features([[maybe_unused]] TestResults* results, - [[maybe_unused]] int lane_id) { -#if defined(__gfx1200__) || defined(__gfx1201__) || ARCH_RDNA35_APU - bool passed = true; - - // Test 1: Wave32 operations (RDNA4 is Wave32 native) - // Wave32 ballot mask test - uint32_t ballot_mask = __ballot(1); - passed = passed && (ballot_mask != 0); - - // Test 2: Subgroup operations - int lane_val = lane_id; - int first_active = __shfl(lane_val, 0); - passed = passed && (first_active == 0); - - // Test 3: FP16 packed operations (RDNA4 has optimized FP16) - half2 h2_val = __float2half2_rn(1.5f); - float converted = __half2float(__low2half(h2_val)); - passed = passed && (fabsf(converted - 1.5f) < 0.01f); - - // Test 4: WGP local barrier (implicit in __syncthreads on RDNA4) - __syncthreads(); - - if (lane_id == 0) { - atomicAdd(&results->lane_id_passed, passed ? 1 : 0); - } - -#else - // Not RDNA4 - skip - (void)results; - (void)lane_id; -#endif -} - -// ============================================================================ -// Main Mega Kernel -// ============================================================================ - -#if defined(__gfx1250__) -// Cluster-enabled dispatch (paired with hipLaunchKernelEx clusterDim in main.cpp). -__attribute__((cluster_dims(2, 1, 1))) -#endif -extern "C" __global__ void mega_kernel( - TestResults* results, - float* global_float, - double* global_double, - int* global_int, - float* input_buffer, - float* output_buffer, - float* async_lds_src, // Source buffer for async LDS load test - float* async_lds_dst, // Destination buffer for async LDS store test - int* tdm_src, // Source buffer for TDM load test - int* tdm_dst, // Destination buffer for TDM store test - int buffer_size, - int mfma_mode, // 0=both, 1=asm only, 2=builtin only - hipTextureObject_t tex_obj, // Strix gfx115x TEX load; pass 0 if unused - hipSurfaceObject_t surf_obj // Strix gfx115x TEX store; pass 0 if unused -) { - int tid = threadIdx.x + blockIdx.x * blockDim.x; - int lane_id = threadIdx.x % warpSize; - -#if !defined(__gfx1150__) && !defined(__gfx1151__) && !defined(__gfx1152__) - if (blockIdx.x == 0 && threadIdx.x == 0) { - results->vmem_tex_load_passed = -1; - results->vmem_tex_store_passed = -1; - } -#endif - - // Use active flag instead of early return to avoid __syncthreads() deadlock - // When buffer_size is not a multiple of blockDim.x, some threads in the last - // block would return early while others hit barriers, causing deadlock - bool active = (tid < buffer_size); - - // results (and other global buffers) are initialized on the host before launch (main.cpp). - // Do not memset here: with grid_size > 1, only tid==0 would run it and __syncthreads() is - // block-local, so other blocks could start updating results via atomics before memset completes. - - // =========== Run all tests =========== - - // 1. Warp Operations - test_warp_shuffle(results, lane_id); - test_warp_ballot(results, lane_id); - test_warp_permute(results, lane_id); - test_warp_reduce(results, lane_id); - - // 2. FP8/BF8 Conversions - test_fp8_conversions(results, lane_id); - test_fp8_e5m3_conversions(results, lane_id); // FP8 E5M3 (gfx1250 only) - test_bf8_conversions(results, lane_id); - - // 3. FP16/BF16 Operations - test_fp16_operations(results, lane_id); - test_bf16_operations(results, lane_id); - - // 4. FP32/FP64 Arithmetic - test_fp32_arithmetic(results, lane_id); - test_fp64_arithmetic(results, lane_id); - - // 5. Integer Operations - test_int32_operations(results, lane_id); - test_int64_operations(results, lane_id); - - // 6. Atomic Operations - test_atomic_add_f32(global_float, results, lane_id); - __syncthreads(); - test_atomic_add_f64(global_double, results, lane_id); - __syncthreads(); - test_atomic_min_max_f32(global_float + 1, results, lane_id); - __syncthreads(); - test_atomic_int(global_int, results, lane_id); - __syncthreads(); - - // 6.5. Comprehensive Atomic Operations (Inline ASM) - test_atomic_operations_asm(global_int, global_float, global_double, results, lane_id, tid); - __syncthreads(); - - // 7. LDS Operations - test_lds_operations(results, lane_id, threadIdx.x); - - // 8. Transcendental Functions - test_transcendental(results, lane_id); - - // 9. Memory Operations (guard with active flag to avoid out-of-bounds access) - if (active) { - test_global_memory(input_buffer, output_buffer, results, lane_id, tid); - } - - // 9.5. VMEM (Vector Memory) Comprehensive Tests - if (active) { - test_vmem_operations(input_buffer, results, lane_id, tid, buffer_size, tex_obj, surf_obj); - } - __syncthreads(); - - // 10. DOT Products - test_dot_products(results, lane_id); - - // 10.3 MFMA (Matrix Fused Multiply-Add) Operations - test_mfma_operations(results, lane_id, mfma_mode); - - // 10.5 Dual-Issue VALU Patterns (gfx942+) - test_dual_issue_patterns(results, lane_id); - - // 11. Lane/Thread Info - test_lane_thread_info(results, lane_id); - - // 12. Packed Operations - test_packed_operations(results, lane_id); - - // 12.5. Cooperative Atomic Operations (gfx1250 specific) - test_cooperative_atomics(global_int, results, lane_id, tid); - __syncthreads(); - - // 13. Async LDS Operations (gfx1250 specific) - test_async_lds_operations(async_lds_src, async_lds_dst, results, lane_id, tid); - __syncthreads(); - - // 14. TDM (Tensor Data Mover) Operations (gfx1250 specific) - test_tdm_operations(tdm_src, tdm_dst, results, lane_id, tid); - __syncthreads(); - - // 15. Cluster Group Operations (gfx1250 specific) - test_cluster_operations(global_int, results, lane_id, tid); - __syncthreads(); - - // 16. RDNA4 Consumer-Specific Features (RX 9070 XT) - test_rdna4_features(results, lane_id); - __syncthreads(); - - // 17. WMMA (Wave Matrix Multiply Accumulate) Operations (RDNA4/gfx1250) - test_wmma_operations(results, lane_id, tid); - __syncthreads(); - - // 18. Buffer Operations (gfx1250 — buffer_load_b32 / buffer_store_b32) - if (active) { - test_buffer_ops(input_buffer, results, lane_id, tid); - } - __syncthreads(); - - // 19. Multicast Load (all threads load same address) - test_multicast_load(input_buffer, results, lane_id, tid); - __syncthreads(); - - // Calculate totals on thread 0 - if (tid == 0) { - int passed = 0; - int failed = 0; - -#define ACCUM_RESULT(field) \ - do { \ - int v = (field); \ - if (v > 0) { \ - ++passed; \ - } else if (v == 0) { \ - ++failed; \ - } \ - } while (0) - - ACCUM_RESULT(results->warp_shuffle_passed); - ACCUM_RESULT(results->warp_ballot_passed); - ACCUM_RESULT(results->warp_permute_passed); - ACCUM_RESULT(results->warp_reduce_passed); - ACCUM_RESULT(results->fp8_convert_passed); - ACCUM_RESULT(results->fp8_e5m3_convert_passed); - ACCUM_RESULT(results->bf8_convert_passed); - ACCUM_RESULT(results->fp16_convert_passed); - ACCUM_RESULT(results->bf16_convert_passed); - ACCUM_RESULT(results->fp32_arith_passed); - ACCUM_RESULT(results->fp64_arith_passed); - ACCUM_RESULT(results->int32_arith_passed); - ACCUM_RESULT(results->int64_arith_passed); - ACCUM_RESULT(results->pk_f16_passed); - ACCUM_RESULT(results->pk_bf16_passed); - ACCUM_RESULT(results->pk_add_passed); - ACCUM_RESULT(results->atomic_add_f32_passed); - ACCUM_RESULT(results->atomic_add_f64_passed); - ACCUM_RESULT(results->atomic_min_f32_passed); - ACCUM_RESULT(results->atomic_max_f32_passed); - ACCUM_RESULT(results->atomic_int_passed); - ACCUM_RESULT(results->lds_atomic_passed); - ACCUM_RESULT(results->trans_sin_passed); - ACCUM_RESULT(results->trans_cos_passed); - ACCUM_RESULT(results->trans_exp_passed); - ACCUM_RESULT(results->trans_log_passed); - ACCUM_RESULT(results->trans_sqrt_passed); - ACCUM_RESULT(results->trans_rcp_passed); - ACCUM_RESULT(results->trans_rsq_passed); - ACCUM_RESULT(results->global_load_passed); - ACCUM_RESULT(results->global_store_passed); - ACCUM_RESULT(results->lds_load_passed); - ACCUM_RESULT(results->lds_store_passed); - ACCUM_RESULT(results->dot4_passed); - ACCUM_RESULT(results->dot8_passed); - ACCUM_RESULT(results->dual_issue_passed); - ACCUM_RESULT(results->lane_id_passed); - ACCUM_RESULT(results->wavefront_passed); - ACCUM_RESULT(results->async_lds_load_passed); - ACCUM_RESULT(results->async_lds_store_passed); - ACCUM_RESULT(results->tdm_load_passed); - ACCUM_RESULT(results->tdm_store_passed); - ACCUM_RESULT(results->cluster_barrier_passed); - ACCUM_RESULT(results->cluster_info_passed); - ACCUM_RESULT(results->cluster_load_passed); - ACCUM_RESULT(results->wmma_f16_passed); - ACCUM_RESULT(results->wmma_bf16_passed); - ACCUM_RESULT(results->wmma_i8_passed); - ACCUM_RESULT(results->coop_atomic_load_passed); - ACCUM_RESULT(results->coop_atomic_store_passed); - ACCUM_RESULT(results->vmem_flat_passed); - ACCUM_RESULT(results->vmem_global_passed); - ACCUM_RESULT(results->vmem_buffer_passed); - ACCUM_RESULT(results->vmem_scratch_passed); - ACCUM_RESULT(results->vmem_lds_passed); - ACCUM_RESULT(results->vmem_tex_load_passed); - ACCUM_RESULT(results->vmem_tex_store_passed); - ACCUM_RESULT(results->atomic_global_int_passed); - ACCUM_RESULT(results->atomic_global_f32_passed); - ACCUM_RESULT(results->atomic_global_f64_passed); - ACCUM_RESULT(results->atomic_flat_int_passed); - ACCUM_RESULT(results->atomic_ds_int_passed); - ACCUM_RESULT(results->atomic_ds_f32_passed); - ACCUM_RESULT(results->atomic_ds_f64_passed); - ACCUM_RESULT(results->atomic_pk_f16_passed); - ACCUM_RESULT(results->atomic_pk_bf16_passed); - ACCUM_RESULT(results->atomic_cas_passed); - ACCUM_RESULT(results->buffer_load_passed); - ACCUM_RESULT(results->buffer_store_passed); - ACCUM_RESULT(results->multicast_load_passed); - -#undef ACCUM_RESULT - - results->total_passed = passed; - results->total_failed = failed; - } -} diff --git a/skills/rocprof-compute-sample/mega_kernel/mega_kernel_device_arch.h b/skills/rocprof-compute-sample/mega_kernel/mega_kernel_device_arch.h deleted file mode 100644 index 0a9cfbc..0000000 --- a/skills/rocprof-compute-sample/mega_kernel/mega_kernel_device_arch.h +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. -// SPDX-License-Identifier: MIT -// -// Device-side architecture capability flags (single place for HAS_* macros). -// Include only from HIP device translation units. - -#pragma once - -// MI250/MI200 series (CDNA2) -#if defined(__gfx90a__) -# define ARCH_MI250 1 -# define ARCH_NAME "MI250 (gfx90a/CDNA2)" -# define HAS_HW_FP64_ATOMICS 1 -# define HAS_FP8 0 -# define HAS_ASYNC_LDS 0 -#endif - -// MI300 series (CDNA3) -#if defined(__gfx942__) -# define ARCH_MI300 1 -# define ARCH_NAME "MI300 (gfx942/CDNA3)" -# define HAS_HW_FP64_ATOMICS 1 -# define HAS_FP8 1 -# define HAS_ASYNC_LDS 0 -#endif - -// MI350 series (CDNA4) -#if defined(__gfx950__) -# define ARCH_MI350 1 -# define ARCH_NAME "MI350 (gfx950/CDNA4)" -# define HAS_HW_FP64_ATOMICS 1 -# define HAS_FP8 1 -# define HAS_FP6_FP4 1 -# define HAS_ASYNC_LDS 1 -#endif - -// Strix Point / Strix Halo / Krackan / Gorgon Point — gfx115x RDNA 3.5 APU iGPU family -#if defined(__gfx1150__) || defined(__gfx1151__) || defined(__gfx1152__) || defined(__gfx1153__) -# define ARCH_RDNA35_APU 1 -# define ARCH_NAME "RDNA 3.5 APU (gfx115x - Strix/Krackan/Gorgon Point)" -# define HAS_HW_FP64_ATOMICS 0 -# define HAS_FP8 0 -# define HAS_ASYNC_LDS 1 -#endif - -// RDNA4 consumer (gfx1200/gfx1201) -#if defined(__gfx1200__) || defined(__gfx1201__) -# define ARCH_RDNA4_CONSUMER 1 -# define ARCH_NAME "RDNA4 Consumer (gfx1200/gfx1201 - RX 9070 XT)" -# define HAS_HW_FP64_ATOMICS 0 -# define HAS_FP8 1 -# define HAS_ASYNC_LDS 1 -# define HAS_TDM 0 -#endif - -// gfx1250 -#if defined(__gfx1250__) -# define ARCH_GFX1250 1 -# define ARCH_NAME "gfx1250" -# define HAS_HW_FP64_ATOMICS 1 -# define HAS_FP8 1 -# define HAS_ASYNC_LDS 1 -# define HAS_TDM 1 -#endif - -#ifndef ARCH_NAME -# define ARCH_NAME "Unknown" -# define HAS_HW_FP64_ATOMICS 0 -# define HAS_FP8 0 -# define HAS_ASYNC_LDS 0 -#endif diff --git a/skills/rocprof-compute-sample/mega_kernel/mega_kernel_host_arch.hpp b/skills/rocprof-compute-sample/mega_kernel/mega_kernel_host_arch.hpp deleted file mode 100644 index fb36626..0000000 --- a/skills/rocprof-compute-sample/mega_kernel/mega_kernel_host_arch.hpp +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. -// SPDX-License-Identifier: MIT -// -// Host-side architecture string → category (keeps detection in one table). - -#pragma once - -#include - -namespace mega_kernel_host -{ - -// Order: longer gfx tokens before shorter prefixes (e.g. gfx942 before gfx94). -inline void -detect_architecture(const char* arch, char* out_name, size_t out_name_sz, int* out_type) -{ - if(out_name_sz > 0) - { - std::strncpy(out_name, arch, out_name_sz - 1); - out_name[out_name_sz - 1] = '\0'; - } - *out_type = 0; - - static const struct - { - const char* needle; - int typ; - } map[] = { - {"gfx1250", 7}, - {"gfx1153", 6}, {"gfx1152", 6}, {"gfx1151", 6}, {"gfx1150", 6}, - {"gfx1201", 5}, {"gfx1200", 4}, - {"gfx950", 3}, - {"gfx942", 2}, {"gfx941", 2}, {"gfx940", 2}, {"gfx94", 2}, - {"gfx90a", 1}, {"gfx908", 1}, - }; - - for(const auto& e : map) - { - if(std::strstr(arch, e.needle) != nullptr) - { - *out_type = e.typ; - break; - } - } -} - -inline const char* -arch_description(int arch_type) -{ - switch(arch_type) - { - case 1: return "MI250/MI250X (CDNA2/gfx90a)"; - case 2: return "MI300/MI300X (CDNA3/gfx942)"; - case 3: return "MI350/MI355X (CDNA4/gfx950)"; - case 4: return "RX 9060 series (RDNA4/gfx1200)"; - case 5: return "RX 9070 XT/9070 (RDNA4/gfx1201)"; - case 6: - return "Strix/Strix Halo/Krackan (RDNA3.5/gfx115x APU)"; - case 7: return "gfx1250"; - default: return "Unknown Architecture"; - } -} - -} // namespace mega_kernel_host diff --git a/skills/rocprof-compute-sample/mega_kernel/mega_kernel_types.h b/skills/rocprof-compute-sample/mega_kernel/mega_kernel_types.h deleted file mode 100644 index 8147818..0000000 --- a/skills/rocprof-compute-sample/mega_kernel/mega_kernel_types.h +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) 2026 Advanced Micro Devices, Inc. All rights reserved. -// SPDX-License-Identifier: MIT -// -// Shared host/device definitions for the mega_kernel sample. Single definition -// avoids layout drift between main.cpp and mega_kernel.hip. - -#pragma once - -#include - -// MFMA implementation mode (host passes int to kernel) -enum MfmaMode -{ - MFMA_MODE_BOTH = 0, - MFMA_MODE_ASM = 1, - MFMA_MODE_BUILTIN = 2 -}; - -struct TestResults -{ - int warp_shuffle_passed; - int warp_ballot_passed; - int warp_permute_passed; - int warp_reduce_passed; - - int fp8_convert_passed; - int fp8_e5m3_convert_passed; - int bf8_convert_passed; - int fp16_convert_passed; - int bf16_convert_passed; - - int fp32_arith_passed; - int fp64_arith_passed; - int int32_arith_passed; - int int64_arith_passed; - - int pk_f16_passed; - int pk_bf16_passed; - int pk_add_passed; - - int atomic_add_f32_passed; - int atomic_add_f64_passed; - int atomic_min_f32_passed; - int atomic_max_f32_passed; - int atomic_int_passed; - int lds_atomic_passed; - - int trans_sin_passed; - int trans_cos_passed; - int trans_exp_passed; - int trans_log_passed; - int trans_sqrt_passed; - int trans_rcp_passed; - int trans_rsq_passed; - - int global_load_passed; - int global_store_passed; - int lds_load_passed; - int lds_store_passed; - - int dot4_passed; - int dot8_passed; - - int mfma_f32_passed; - int mfma_f64_passed; - int mfma_f16_passed; - int mfma_bf16_passed; - int mfma_i8_passed; - int mfma_f8_passed; - - int dual_issue_passed; - - int lane_id_passed; - int wavefront_passed; - - int async_lds_load_passed; - int async_lds_store_passed; - - int tdm_load_passed; - int tdm_store_passed; - - int cluster_barrier_passed; - int cluster_info_passed; - int cluster_load_passed; - - int wmma_f16_passed; - int wmma_bf16_passed; - int wmma_i8_passed; - - int coop_atomic_load_passed; - int coop_atomic_store_passed; - - int vmem_flat_passed; - int vmem_global_passed; - int vmem_buffer_passed; - int vmem_scratch_passed; - int vmem_lds_passed; - int vmem_tex_load_passed; - int vmem_tex_store_passed; - - int atomic_global_int_passed; - int atomic_global_f32_passed; - int atomic_global_f64_passed; - int atomic_flat_int_passed; - int atomic_ds_int_passed; - int atomic_ds_f32_passed; - int atomic_ds_f64_passed; - int atomic_pk_f16_passed; - int atomic_pk_bf16_passed; - int atomic_cas_passed; - - int buffer_load_passed; - int buffer_store_passed; - int multicast_load_passed; - - int total_passed; - int total_failed; -}; - -extern "C" __global__ void mega_kernel( - TestResults* results, float* global_float, double* global_double, int* global_int, - float* input_buffer, float* output_buffer, float* async_lds_src, float* async_lds_dst, - int* tdm_src, int* tdm_dst, int buffer_size, int mfma_mode, hipTextureObject_t tex_obj, - hipSurfaceObject_t surf_obj); diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/L1_stall_microbenchmark.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/L1_stall_microbenchmark.hip deleted file mode 100644 index ffbe4ff..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/L1_stall_microbenchmark.hip +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// L1 stall micro-benchmarks -// Usage: ./a.out = baseline (uncoalesced) -> TA/UTCL1/L2 stall -// ./a.out opt = optimized (shared mem) -> no stall -// ./a.out sq1 = SQ-L1 back-pressure -> try to get "VMEM stalled by L1" > 0 - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while (0) - -// ----------------------------------------------------------------------------- -// SQ-L1 back-pressure kernel: maximize VMEM issue rate so SQ->TA FIFOs fill. -// - Many independent loads in a row (no data dependency) so SQ can issue as fast -// as the pipeline allows; then stores. Goal: fill VMEM command/addr/data FIFO. -// - Uncoalesced so TA->TCP is slow and FIFOs back up to SQ. -// - Actual behaviour: uncoalesced heavy L1 -> L2 traffic, but not filling up VMEM FIFO -// Run with: ./a sq1 -// ----------------------------------------------------------------------------- -__global__ void vmem_sq_l1_backpressure(float* data, int N) { - int tid = blockIdx.x * blockDim.x + threadIdx.x; - if (tid >= N / 64) return; - int base = tid * 64; - - // Independent loads - // Uncoalesced - float v[32]; -#pragma unroll - for (int i = 0; i < 32; i++) { - int load_idx = (base + threadIdx.x * 32 + i * 4096) % N; - v[i] = data[load_idx]; - } - - // 32 stores. Uncoalesced, different stride. -#pragma unroll - for (int i = 0; i < 32; i++) { - int store_idx = (base + threadIdx.x * 32 + i * 8192) % N; - data[store_idx] = v[i] * 2.0f; - } -} - -// Fixed baseline: UNCOALESCED accesses -__global__ void vmem_stall_baseline(float* data, int N) { - int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= N) return; - - float vals[16]; - // Uncoalesced loads: threadIdx.x * 32 puts each of 32 threads in a warp on a - // different cache line (32 floats = 128B). i * 1024 spreads across pages. - for (int i = 0; i < 16; i++) { - int load_idx = (idx + threadIdx.x * 32 + i * 1024) % N; - vals[i] = data[load_idx]; - } - - // Uncoalesced stores: same idea, different stride to avoid overlap with loads. - for (int i = 0; i < 16; i++) { - int store_idx = (idx + threadIdx.x * 32 + i * 2048) % N; - data[store_idx] = vals[i] * 2.0f; - } -} - -// Optimized: Shared memory reduces VMEM FIFO pressure -__global__ void vmem_stall_optimized(float* data, int N) { - int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= N) return; - - __shared__ float sdata[256]; - - sdata[threadIdx.x] = data[idx]; - __syncthreads(); - - float result = sdata[threadIdx.x]; - for (int i = 0; i < 16; i++) { - result = result * 1.01f + 0.1f; - } - - data[idx] = result; -} - -int main(int argc, char** argv) { - // opt = optimized (shared mem); sq1 = SQ-L1 back-pressure (try to get "VMEM stalled by L1" > 0) - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - bool use_sq1 = (argc > 1 && std::string(argv[1]) == "sq1"); - - const int N = 256 * 1024 * 1024; // 1GB - const size_t bytes = N * sizeof(float); - - float* d_data; - CHECK_HIP(hipMalloc(&d_data, bytes)); - - float* h_data = new float[N]; - for (int i = 0; i < N; i++) h_data[i] = 1.0f; - CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - dim3 block(256); - dim3 grid_baseline((N + block.x - 1) / block.x); - dim3 grid_sq1((N / 64 + block.x - 1) / block.x); // tid = N/64 threads total - - auto run = [&]() { - if (use_optimized) - hipLaunchKernelGGL(vmem_stall_optimized, grid_baseline, block, 0, 0, d_data, N); - else if (use_sq1) - hipLaunchKernelGGL(vmem_sq_l1_backpressure, grid_sq1, block, 0, 0, d_data, N); - else - hipLaunchKernelGGL(vmem_stall_baseline, grid_baseline, block, 0, 0, d_data, N); - }; - - run(); - CHECK_HIP(hipDeviceSynchronize()); - - auto start = std::chrono::high_resolution_clock::now(); - run(); - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - const char* name = use_optimized ? "Optimized" : (use_sq1 ? "SQ-L1 backpressure" : "Baseline"); - std::cout << name << ": " - << std::chrono::duration_cast(end - start).count() << " ms\n"; - - CHECK_HIP(hipFree(d_data)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/README.md b/skills/rocprof-compute-sample/membw_analysis_test_suite/README.md deleted file mode 100644 index da920a5..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# L2/L1 Bottleneck Workloads - -HIP workloads targeting MI350 L2 backpressure and L1 stall metrics (≥10%). - -## Workload Target - -+--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ -| Workload | Baseline (intended effect) | Optimized (intended effect) | -+--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ -| gl2_backpressure | Uncoalesced + large stride -> L2 misses -> fill LFIFO -> TCP stalls. | Shared mem, minimal L2 traffic. | -+--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ -| L1_stall_microbenchmark | Many scattered loads/stores -> VMEM issues many commands; TA/GL1 can't consume them fast enough | Shared memory reduces VMEM FIFO pressure. | -| | -> VMEM FIFO fills. | | -+--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ -| utcl1_stall | Rapid page hopping (>32 pages) -> exceed UTCL1 entries -> in-flight stall. | Stay in one page. | -+--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ -| ta_tcp_stall | Random access, TA waits for TCP. | Sequential access, TCP serves TA quickly. | -+--------------------------+--------------------------------------------------------------------------------------------------+-------------------------------------------+ - -> [!NOTE] -Note: Above workloads are still WIP, the profiled result may not reflect the intended results. - -## Target Metrics - -- **gl2_backpressure**: `TCP_TCR_TCP_STALL_CYCLES / tcp_busy` -- **utcl1_stall**: `TCP_UTCL1_STALL_INFLIGHT_MAX / tcp_busy` - -## Build - -```bash -hipcc -g -o -# example: hipcc -g gl2_backpressure.hip -o gl2_backpressure -``` - -## Profiling - -```bash -# Note: --no-roof is optional and skips the -# benchmarking that is unrelated to the our purpose - -# baseline profile -rocprof-compute profile -n gl2_backpressure_baseline --experimental --membw-analysis --no-roof -- ./gl2_backpressure -# optimized profile -rocprof-compute profile -n gl2_backpressure_optimized --experimental --membw-analysis --no-roof -- ./gl2_backpressure opt -``` - -## Analyzing - -```bash -# baseline profile -rocprof-compute analyze -p -``` diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/Makefile b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/Makefile deleted file mode 100644 index cce260f..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -HIPCC ?= hipcc -HIPFLAGS ?= -g - -SOURCES := $(wildcard *.hip) -TARGETS := $(SOURCES:.hip=) - -all: $(TARGETS) - -%: %.hip - $(HIPCC) $(HIPFLAGS) $< -o $@ - -clean: - rm -f $(TARGETS) - -.PHONY: all clean diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/README.md b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/README.md deleted file mode 100644 index ca4e1d7..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/README.md +++ /dev/null @@ -1,380 +0,0 @@ -# EA Bottleneck Detection Workloads - -HIP workloads targeting MI350 EA (Efficiency Arbiter) memory bandwidth bottleneck -detection metrics (tables 30.13--30.18 in `3000_mem_bw.yaml`). Each workload has a -**baseline** (exhibits the bottleneck) and **optimized** variant (mitigates it), -selected via the `opt` CLI argument. - -## Architecture Reference - -``` -TCC (L2, 16 channels) --[64B R + 64B W per channel]--> EA ---> DRAM FE --> HBM - |-> GMI FE --> GMI (remote XCD/AID) - |-> IO FE --> PCIe (host) -``` - -- EA has 6 independent credit pools: 3 destinations (HBM/GMI/IO) x 2 directions (read/write) -- Atomics consume write credits (counted within `TCC_EA0_WRREQ`), but have higher latency (RMW) -- MI350: 16 TCC channels, 128 HBM channels (NPS1), L2 cacheline = 128B - -## Workload Summary - -| Workload | Baseline (intended effect) | Optimized (intended effect) | -|------------------------|------------------------------------------------------------------|----------------------------------------------------| -| ea_hbm_read_bw | Streaming reads from 4GB buffer (>> L2) saturates HBM read BW. | All workgroups re-read 2MB region (fits in L2). | -| ea_hbm_write_bw | Streaming writes to 4GB buffer saturates HBM write BW. | Register accumulation, single write per thread. | -| ea_write_backpressure | Coalesced streaming writes at max rate flood EA write credits. | ALU work between writes throttles write rate. | -| ea_atomic_pressure | All threads atomicAdd to 64K-bin global histogram. | LDS-local histogram, merge with few global atomics.| -| ea_rw_balance | Read-only streaming sum, single scalar output. | 1:1 vector copy (balanced read + write). | -| ea_fabric_bw | Streaming reads from remote GPU memory via peer access (GMI). | Copy remote data to local first, read from HBM. | -| ea_io_bw | Streaming reads from host-pinned coherent memory (PCIe). | Copy to device memory first, read from HBM. | - -> [!NOTE] -> `ea_fabric_bw` requires >= 2 GPUs with peer access. It skips gracefully on single-GPU systems. - -## Target Metrics - -| Workload | Primary Metrics (table) | -|------------------------|---------------------------------------------------------------------------------------------| -| ea_hbm_read_bw | EA to HBM read BW (30.14), EA utilization (30.13), Norm HBM read stall (30.16) | -| ea_hbm_write_bw | Write Credit Pressure (30.18), Write Backpressure (30.18), EA to HBM write BW (30.14) | -| ea_write_backpressure | Write Backpressure (30.18), Norm Write stall (30.16) | -| ea_atomic_pressure | HBM Atomic Pressure (30.18), atomic fraction of writes (30.13), EA to HBM atomic BW (30.14)| -| ea_rw_balance | Read/Write Balance (30.18), EA to HBM read/write BW (30.14) | -| ea_fabric_bw | GMI BW Bound (30.18), read BW fraction GMI (30.13), Stall Dominance GMI (30.18) | -| ea_io_bw | IO BW Bound (30.18), read BW fraction IO (30.13), Stall Dominance IO (30.18) | - -## Build - -```bash -cd sample/membw_analysis_test_suite/ea -make # builds all workloads -make clean # removes binaries -``` - -Or individually: - -```bash -hipcc -g ea_hbm_read_bw.hip -o ea_hbm_read_bw -``` - -## Profiling - -```bash -# Note: --no-roof is optional and skips the -# benchmarking that is unrelated to the our purpose - -# From the rocprofiler-compute root directory: - -# Profile baseline -src/rocprof-compute profile -n ea_test_baseline \ - -b 30.13 30.14 30.15 30.16 30.17 30.18 \ - --experimental --membw-analysis --no-roof \ - --output-directory /tmp/ea_test_baseline \ - -- ./sample/membw_analysis_test_suite/ea/ea_hbm_read_bw - -# Profile optimized -src/rocprof-compute profile -n ea_test_opt \ - -b 30.13 30.14 30.15 30.16 30.17 30.18 \ - --experimental --membw-analysis --no-roof \ - --output-directory /tmp/ea_test_opt \ - -- ./sample/membw_analysis_test_suite/ea/ea_hbm_read_bw opt -``` - -## Analyzing - -Side-by-side baseline vs. optimized comparison: - -```bash -src/rocprof-compute analyze \ - -p /tmp/ea_test_baseline -p /tmp/ea_test_opt \ - -b 30.13 30.14 30.15 30.16 30.17 30.18 -``` - -To analyze each run independently: - -```bash -src/rocprof-compute analyze -p /tmp/ea_test_baseline \ - -b 30.13 30.14 30.15 30.16 30.17 30.18 - -src/rocprof-compute analyze -p /tmp/ea_test_opt \ - -b 30.13 30.14 30.15 30.16 30.17 30.18 -``` - -## Validation Results (MI350X) - -### Test Environment - -| Component | Configuration | -|--------------------|----------------------------------------------------| -| GPU | 2x AMD Instinct MI350X (gfx950) | -| GPU Architecture | CDNA 4 (gfx950), 256 CUs, 32 SIMDs/CU, 8 XCDs | -| GPU SCLK | 2200 MHz (max) | -| GPU MCLK | 1900 MHz (HBM) | -| HBM | 288 GB per GPU, 128 HBM channels, NPS1 | -| L2 Cache | 4096 KB, 128 channels, 16 banks | -| L1 Cache | 32 KB per CU | -| VBIOS | 113-M350-01-1K5-020E | -| Compute Partition | SPX | -| Memory Partition | NPS1 | -| CPU | AMD EPYC 9655 96-Core (Turin) | -| OS | Ubuntu 24.04 LTS, kernel 6.8.0-31-generic | -| ROCm | 7.2.0 | -| GPU Driver | amdgpu 6.16.13 | - -All results collected on MI350X GPUs. `ea_fabric_bw` validated on the multi-GPU -system with peer access enabled between GPU 0 and GPU 1. - -### ea_hbm_read_bw -- HBM Read Traffic - -| Metric | Baseline | Optimized | -|---------------------------------------------------|----------------|---------------| -| EA to HBM read BW (30.14.0) | 6079 Gb/s | 53 Gb/s | -| EA to HBM write BW (30.14.1) | 0 Gb/s | 2 Gb/s | -| EA utilization (30.13.0) | 99.42% | 13.16% | -| EA read average latency (30.14.10) | 1589 cyc | 1147 cyc | -| EA read latency (30.14.11) | 1589 cyc | 1147 cyc | -| Normalized EA - HBM read credit stall (30.16.0) | 1.73% | 0.72% | -| EA HBM BW Bound - Read Credit Pressure (30.18.0) | 0.01% | 0.11% | -| EA Read/Write Balance (30.18.6) | 99.99% | 89.05% | -| EA Stall Destination Dominance - HBM (30.18.8) | 100.00% | 100.00% | - -**Key findings:** -- EA to HBM read BW differentiates at **115x** (6079 vs 53 Gb/s). The 2MB tiled - region fits in L2, so subsequent passes are cache hits. -- EA utilization drops from 99% to 13%, confirming L2 reuse avoids EA. -- Read Credit Pressure (30.18.0) remains low even at near-peak BW. On MI350, - the HBM read credit pool is deep enough that coalesced streaming reads do not - exhaust credits. The normalized stall metric (30.16.0) shows a clearer signal - at 1.73% vs 0.72% using `GRBM_GUI_ACTIVE_PER_XCD` as denominator. - -### ea_hbm_write_bw -- HBM Write Credit Pressure - -| Metric | Baseline | Optimized | -|----------------------------------------------------|----------------|---------------| -| EA to HBM write BW (30.14.1) | 4855 Gb/s | 31 Gb/s | -| EA utilization (30.13.0) | 89.96% | 3.90% | -| EA write average latency (30.14.12) | 530 cyc | 404 cyc | -| EA write latency (30.14.13) | 530 cyc | 404 cyc | -| EA HBM BW Bound - Write Credit Pressure (30.18.1) | **25.06%** | 6.68% | -| EA Write Backpressure (30.18.5) | **25.21%** | 6.77% | -| Normalized EA - HBM write credit stall (30.16.1) | 2962% | 20.30% | -| EA Read/Write Balance (30.18.6) | 0.00% | 0.15% | -| EA Stall Destination Dominance - HBM (30.18.8) | 100.00% | 100.00% | - -**Key findings:** -- Write Credit Pressure at **25.06%** clearly exceeds the 10% bottleneck threshold. - The optimized kernel accumulates in registers and writes once, reducing write BW - by 157x (4855 vs 31 Gb/s). -- Write Backpressure (25.21%) closely tracks Write Credit Pressure since both - derive from write credit stalls on HBM. -- Normalized write credit stall at 2962% reflects per-channel stall accumulation - across 16 TCC channels summed against a single-XCD activity counter. The 30.18 - metric using `TCC_BUSY_sum` as denominator gives the per-channel percentage. - -### ea_write_backpressure -- Write Path Backpressure - -| Metric | Baseline | Optimized | -|----------------------------------------------------|----------------|---------------| -| EA to HBM write BW (30.14.1) | 5719 Gb/s | 820 Gb/s | -| EA utilization (30.13.0) | 90.17% | 97.93% | -| EA write average latency (30.14.12) | 428 cyc | 258 cyc | -| EA write latency (30.14.13) | 428 cyc | 258 cyc | -| EA HBM BW Bound - Write Credit Pressure (30.18.1) | **13.20%** | 0.03% | -| EA Write Backpressure (30.18.5) | **13.13%** | 0.03% | -| Normalized EA - HBM write credit stall (30.16.1) | 1524% | 3.54% | -| Normalized EA - Write stall (30.16.6) | 1517% | 3.56% | -| Normalized EA - Too many writes stall (30.16.7) | 0.00% | 0.00% | -| EA Stall Destination Dominance - HBM (30.18.8) | 100.00% | 100.00% | - -**Key findings:** -- Coalesced streaming writes at maximum rate trigger **13.13% Write Backpressure** - (above 10% threshold), while compute-gated writes drop to 0.03%. -- The ALU work between writes in the optimized kernel reduces the write request - rate by ~7x, giving EA time to drain credits between bursts. -- `TCC_TOO_MANY_EA_WRREQS_STALL` (30.16.7) is 0% for both variants. This counter - tracks global write credit pool exhaustion across all TCC channels, which does - not trigger on MI350 with these patterns. The per-destination credit stall - (`TCC_EA0_WRREQ_DRAM_CREDIT_STALL`) is the active mechanism. - -### ea_atomic_pressure -- HBM Atomic Contention - -| Metric | Baseline | Optimized | -|----------------------------------------------------|----------------|---------------| -| EA to HBM atomic BW (30.14.2) | 836 Gb/s | 7 Gb/s | -| EA to HBM read BW (30.14.0) | 105 Gb/s | 3581 Gb/s | -| EA atomic average latency (30.14.14) | 747 cyc | 3443 cyc | -| EA atomic latency (30.14.15) | 747 cyc | 3443 cyc | -| EA HBM Atomic Pressure (30.18.7) | **100.00%** | **100.00%** | -| EA atomic fraction of writes (30.13.6) | 100.00% | 100.00% | -| EA HBM BW Bound - Write Credit Pressure (30.18.1) | 2.06% | 0.03% | -| Normalized EA - HBM write credit stall (30.16.1) | 246% | 4.05% | -| EA uncached write fraction (30.13.9) | 100.00% | 100.00% | -| EA Read/Write Balance (30.18.6) | 3.03% | 99.61% | -| EA Stall Destination Dominance - HBM (30.18.8) | 100.00% | 100.00% | - -**Key findings:** -- EA to HBM atomic BW differentiates at **119x** (836 vs 7 Gb/s). The LDS histogram - reduces global atomic traffic from millions of atomics to ~256 per workgroup. -- HBM Atomic Pressure is 100% for both variants. This is correct: the metric - measures `WRREQ_ATOMIC_DRAM / WRREQ_DRAM` -- what fraction of writes are atomics. - In both kernels the only writes reaching EA are atomics (baseline: all global, - optimized: the LDS merge atomics). The differentiation is in **volume**, not - fraction. -- Write Credit Pressure drops from 2.06% to 0.03% because atomics consume write - credits; fewer atomics = fewer stalls. -- R/W Balance flips from 3% (baseline: mostly atomic writes) to 99.6% (optimized: - mostly input reads with negligible atomic writes). -- Atomic latency is higher in the optimized case (3443 vs 747 cyc) because fewer - outstanding requests means less amortization of the per-request RMW round-trip. - -### ea_rw_balance -- Read/Write Traffic Balance - -| Metric | Baseline | Optimized | -|----------------------------------------------------|----------------|----------------| -| EA to HBM read BW (30.14.0) | 6203 Gb/s | 2580 Gb/s | -| EA to HBM write BW (30.14.1) | 0 Gb/s | 2580 Gb/s | -| EA to HBM total BW (30.14.3) | 6203 Gb/s | 5159 Gb/s | -| EA utilization (30.13.0) | 95.15% | 95.61% | -| EA Read/Write Balance (30.18.6) | **100.00%** | **33.33%** | -| EA HBM BW Bound - Read Credit Pressure (30.18.0) | 0.03% | 2.12% | -| EA HBM BW Bound - Write Credit Pressure (30.18.1) | 0.00% | 2.16% | -| EA HBM BW Bound - Combined (30.18.2) | 0.03% | 4.28% | -| EA Stall Destination Dominance - HBM (30.18.8) | 100.00% | 100.00% | - -**Key findings:** -- R/W Balance metric correctly differentiates: **100%** for read-only reduction - vs **33%** for 1:1 vector copy. The formula `RDREQ / (RDREQ + WRREQ)` reflects - request-level balance including L2 write-allocate fetches. -- The vector copy shows perfectly symmetric BW: 2580 Gb/s read = 2580 Gb/s write. -- HBM BW Bound Combined reaches 4.28% for the copy (both read and write credit - stalls contributing), compared to 0.03% for the read-only baseline where write - credits are never consumed. - -### ea_io_bw -- IO/PCIe Bandwidth - -| Metric | Baseline | Optimized | -|----------------------------------------------------|----------------|---------------| -| EA to IO read BW (30.14.7) | 56.6 Gb/s | 0.0 Gb/s | -| EA to HBM read BW (30.14.0) | 2.6 Gb/s | 6276 Gb/s | -| EA read BW fraction - IO (30.13.4) | **95.59%** | 0.00% | -| EA read BW fraction - HBM (30.13.2) | 4.41% | 100.00% | -| EA read average latency (30.14.10) | **27533 cyc** | 1448 cyc | -| EA read latency (30.14.11) | **27533 cyc** | 1448 cyc | -| EA IO BW Bound - Combined (30.18.4) | **9.41%** | 0.00% | -| EA Stall Destination Dominance - IO (30.18.10) | **100.00%** | 0.00% | -| EA Stall Destination Dominance - HBM (30.18.8) | 0.00% | 100.00% | -| Normalized EA - IO read credit stall (30.16.4) | 778.58% | 0.00% | -| EA utilization (30.13.0) | 72.11% | 99.69% | - -**Key findings:** -- EA read BW fraction - IO correctly identifies **95.6%** of read traffic going - via PCIe in the baseline. After copying to device memory, it drops to 0%. -- Stall Destination Dominance perfectly flips: IO 100% (baseline) to HBM 100% - (optimized). This cross-validates the dominance metric. -- EA to IO read BW at 56.6 Gb/s is consistent with PCIe Gen5 x16 bandwidth (~64 GB/s - theoretical peak). -- Read latency is **19x higher** via PCIe (27,533 vs 1,448 cycles), reflecting - the ~1us PCIe round-trip vs ~50-100ns HBM latency. -- IO BW Bound at 9.41% is near the bottleneck threshold. The normalized stall - metric (30.16.4 = 779%) and Stall Destination Dominance (100% IO) provide - stronger bottleneck indicators for IO-bound workloads. - -### ea_fabric_bw -- GMI Bandwidth (multi-GPU) - -Validated on a multi-GPU MI350X system with peer access enabled. - -| Metric | Baseline | Optimized | -|----------------------------------------------------|----------------|---------------| -| EA to GMI read BW (30.14.4) | 61.8 Gb/s | 0.0 Gb/s | -| EA to HBM read BW (30.14.0) | 0.0 Gb/s | 6244 Gb/s | -| EA read BW fraction - GMI (30.13.3) | **100.00%** | 0.00% | -| EA read BW fraction - HBM (30.13.2) | 0.00% | 100.00% | -| EA read average latency (30.14.10) | **21858 cyc** | 1492 cyc | -| EA read latency (30.14.11) | **21858 cyc** | 1492 cyc | -| EA GMI BW Bound - Combined (30.18.3) | **15.08%** | 0.00% | -| EA Stall Destination Dominance - GMI (30.18.9) | **100.00%** | 0.00% | -| EA Stall Destination Dominance - HBM (30.18.8) | 0.00% | 100.00% | -| Normalized EA - GMI read credit stall (30.16.2) | 1347.62% | 0.00% | -| EA utilization (30.13.0) | 99.72% | 96.43% | - -**Key findings:** -- GMI BW Bound at **15.08%** clearly exceeds the 10% threshold, confirming - that streaming reads from remote GPU memory via GMI saturate GMI credits. - After copying to local HBM, it drops to 0%. -- EA read BW fraction - GMI correctly identifies **100%** of read traffic going - via GMI in the baseline. After local copy, it drops to 0%. -- Stall Destination Dominance perfectly flips: GMI 100% (baseline) to HBM 100% - (optimized). This cross-validates the dominance metric for the GMI destination. -- EA to GMI read BW at 61.8 Gb/s reflects the inter-GPU GMI link bandwidth. -- Read latency is **14.6x higher** via GMI (21,858 vs 1,492 cycles), reflecting - the remote memory access round-trip latency. - -## Cross-Validation Summary - -### Bottleneck Detection Thresholds (30.18) - -| Workload | Key Metric (30.18.x) | Baseline | Optimized | Threshold | Pass | -|------------------------|--------------------------|-----------|-----------|-----------|------| -| ea_hbm_write_bw | Write Credit Pressure | 25.06% | 6.68% | >= 10% | Yes | -| ea_write_backpressure | Write Backpressure | 13.13% | 0.03% | >= 10% | Yes | -| ea_rw_balance | Read/Write Balance | 100.00% | 33.33% | N/A | Yes | -| ea_atomic_pressure | HBM Atomic Pressure | 100% | 100% | N/A | (1) | -| ea_fabric_bw | GMI BW Bound | 15.08% | 0.00% | >= 10% | Yes | -| ea_io_bw | IO BW Bound | 9.41% | 0.00% | >= 5% | Yes | -| ea_hbm_read_bw | Read Credit Pressure | 0.01% | 0.11% | >= 10% | (2) | - -**(1)** Atomic Pressure measures fraction (not volume). Both variants are 100% -atomic because neither has non-atomic writes. Differentiation is via atomic BW -(836 vs 7 Gb/s, 119x) and write credit stall (246% vs 4.05%). - -**(2)** Read Credit Pressure stays below threshold even at ~6 TB/s read BW. -MI350's deep HBM read credit pools do not exhaust under coalesced streaming -patterns. Differentiation is via EA to HBM read BW (6079 vs 53 Gb/s, 115x) and -EA utilization (99% vs 13%). - -### Stall Destination Dominance Cross-Validation - -| Workload (baseline) | HBM | GMI | IO | -|--------------------------------------------|--------|--------|--------| -| ea_hbm_read_bw, ea_hbm_write_bw | 100% | 0% | 0% | -| ea_write_backpressure, ea_atomic_pressure | 100% | 0% | 0% | -| ea_rw_balance | 100% | 0% | 0% | -| ea_io_bw | 0% | 0% | 100% | -| ea_fabric_bw | 0% | 100% | 0% | - -The Stall Destination Dominance metric correctly identifies the dominant stall -source across all validated workloads, including the HBM-to-IO flip in ea_io_bw -and the GMI dominance in ea_fabric_bw. - -## Observations on MI350 Credit Stall Behavior - -1. **Write credits exhaust more readily than read credits.** Streaming writes at - peak BW trigger 25% Write Credit Pressure, while streaming reads at similar BW - show only 0.01% Read Credit Pressure. This suggests MI350 has a deeper read - credit pool or faster read credit retirement. - -2. **`TCC_TOO_MANY_EA_WRREQS_STALL` does not fire** for any workload tested. The - global write credit pool exhaustion condition appears difficult to trigger on - MI350. The per-destination credit stall (`TCC_EA0_WRREQ_DRAM_CREDIT_STALL`) - is the primary write bottleneck indicator. - -3. **30.16 normalized stalls can exceed 100%** because stall counters are summed - across 16 TCC channels while the denominator (`GRBM_GUI_ACTIVE_PER_XCD`) is a - single-XCD value. Values like 2962% mean each channel was stalled for ~30x the - kernel duration on average. The 30.18 metrics using `TCC_BUSY_sum` give the - more intuitive per-channel percentage. - -4. **Scattered writes do not trigger EA backpressure.** They bottleneck upstream - at L1/L2 coalescing, throttling the EA write request rate. Only coalesced - streaming writes at maximum rate exhaust EA write credits. - -5. **GMI latency is comparable to IO/PCIe latency.** Read latency via GMI - (21,858 cyc) is similar to IO/PCIe latency (27,533 cyc), both being an order - of magnitude higher than HBM latency (~1,500 cyc). This reflects the inherent - cost of crossing chip/package boundaries. - -6. **Latency metrics show both "average latency" (SUM/SUM) and "latency" - (AVG/AVG) variants.** In all workloads tested on MI350, the two variants - report identical values. This is expected for single-dispatch profiling since - there is only one data point, making SUM/SUM and AVG/AVG equivalent. diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_atomic_pressure.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_atomic_pressure.hip deleted file mode 100644 index 6e92e89..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_atomic_pressure.hip +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// EA HBM Atomic Pressure Workload -// -// Target metrics: EA HBM Atomic Pressure (30.18.7), -// EA atomic fraction of writes (30.13.6), -// EA HBM write vs atomic split (30.13.7), -// EA atomic latency (30.14.12) -// -// Baseline: Histogram-like pattern with atomicAdd to a 64K-bin output array. -// All threads issue atomics to global memory, creating high atomic -// traffic at HBM. -// Expected: HBM Atomic Pressure > 50%, atomic fraction > 50%. -// -// Optimized: LDS-local partial histogram per workgroup, with a single -// atomicAdd per bin per workgroup to global memory. -// Expected: HBM Atomic Pressure < 10%, atomic fraction < 10%. - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -constexpr int NUM_BINS = 65536; // 64K bins - -// Baseline: Every thread atomicAdd directly to global histogram — heavy atomic traffic -__global__ void ea_atomic_pressure_baseline(const int* __restrict__ input, - int* __restrict__ histogram, - int N) { - int tid = blockIdx.x * blockDim.x + threadIdx.x; - int stride = gridDim.x * blockDim.x; - - for (int i = tid; i < N; i += stride) { - int bin = input[i] & (NUM_BINS - 1); // Mask to bin range - atomicAdd(&histogram[bin], 1); - } -} - -// Optimized: LDS-local histogram, then merge to global with far fewer atomics -__global__ void ea_atomic_pressure_optimized(const int* __restrict__ input, - int* __restrict__ histogram, - int N) { - // Use a subset of bins in LDS to fit in shared memory - // 256 bins * 4 bytes = 1KB per workgroup (fits easily) - constexpr int LDS_BINS = 256; - __shared__ int local_hist[LDS_BINS]; - - // Initialize local histogram - for (int i = threadIdx.x; i < LDS_BINS; i += blockDim.x) { - local_hist[i] = 0; - } - __syncthreads(); - - // Accumulate into LDS-local histogram (atomics to LDS are fast) - int tid = blockIdx.x * blockDim.x + threadIdx.x; - int stride = gridDim.x * blockDim.x; - - for (int i = tid; i < N; i += stride) { - int bin = input[i] & (LDS_BINS - 1); - atomicAdd(&local_hist[bin], 1); - } - __syncthreads(); - - // Merge local histogram to global — only LDS_BINS atomics per workgroup - for (int i = threadIdx.x; i < LDS_BINS; i += blockDim.x) { - if (local_hist[i] > 0) { - atomicAdd(&histogram[i], local_hist[i]); - } - } -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - // 1GB of ints = 256M elements - int N = 256 * 1024 * 1024; - size_t input_bytes = (size_t)N * sizeof(int); - size_t hist_bytes = NUM_BINS * sizeof(int); - - int* d_input; - CHECK_HIP(hipMalloc(&d_input, input_bytes)); - - int* d_histogram; - CHECK_HIP(hipMalloc(&d_histogram, hist_bytes)); - - // Host init + copy (no hipMemset) - int* h_input = new int[N]; - for (int i = 0; i < N; i++) { - // Pseudo-random bin assignment to spread atomics - h_input[i] = (i * 2654435761u) & (NUM_BINS - 1); - } - CHECK_HIP(hipMemcpy(d_input, h_input, input_bytes, hipMemcpyHostToDevice)); - delete[] h_input; - - // Zero-init histogram via host copy - int* h_hist = new int[NUM_BINS](); - CHECK_HIP(hipMemcpy(d_histogram, h_hist, hist_bytes, hipMemcpyHostToDevice)); - delete[] h_hist; - - hipDeviceProp_t prop; - CHECK_HIP(hipGetDeviceProperties(&prop, 0)); - int num_blocks = prop.multiProcessorCount * 8; - - dim3 block(256); - dim3 grid(num_blocks); - - // Single dispatch for clean profiling - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - ea_atomic_pressure_optimized<<>>(d_input, d_histogram, N); - } else { - ea_atomic_pressure_baseline<<>>(d_input, d_histogram, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_input)); - CHECK_HIP(hipFree(d_histogram)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_fabric_bw.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_fabric_bw.hip deleted file mode 100644 index 1d27641..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_fabric_bw.hip +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// EA GMI Bandwidth Workload (multi-GPU) -// -// Target metrics: EA GMI BW Bound - Combined (30.18.3), -// EA read BW fraction - GMI (30.13.3), -// EA Stall Destination Dominance - GMI (30.18.9), -// EA to GMI read BW (30.14.4) -// -// Baseline: Streaming reads from remote GPU memory via peer access. -// All traffic traverses GMI. -// Expected: GMI BW Bound > 10%, read BW fraction GMI > 90%. -// -// Optimized: Copy remote data to local GPU memory first, then read locally. -// All traffic via HBM. -// Expected: GMI BW Bound ~ 0%, Stall Dominance HBM ~ 100%. -// -// Requires >= 2 GPUs. Skips gracefully on single-GPU systems. - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -// Streaming read kernel — same kernel used for both baseline (remote ptr) and optimized (local ptr) -__global__ void ea_fabric_bw_read(const double* __restrict__ data, - double* __restrict__ out, - long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)gridDim.x * blockDim.x; - - double acc = 0.0; - for (long long i = tid; i < N; i += stride) { - acc += data[i]; - } - - if (tid < N) { - out[tid] = acc; - } -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - int device_count; - CHECK_HIP(hipGetDeviceCount(&device_count)); - - if (device_count < 2) { - std::cout << "Skipping: requires >= 2 GPUs (found " << device_count << ")\n"; - return 0; - } - - // Verify peer access is possible - int can_access; - CHECK_HIP(hipDeviceCanAccessPeer(&can_access, 0, 1)); - if (!can_access) { - std::cout << "Skipping: GPU 0 cannot access GPU 1 peer memory\n"; - return 0; - } - - // 2GB of doubles = 256M elements - long long N = 256LL * 1024 * 1024; - size_t bytes = (size_t)N * sizeof(double); - - // Allocate source buffer on GPU 1 - CHECK_HIP(hipSetDevice(1)); - double* d_remote; - hipError_t alloc_err = hipMalloc(&d_remote, bytes); - if (alloc_err != hipSuccess) { - // Fallback to 1GB - N = 128LL * 1024 * 1024; - bytes = (size_t)N * sizeof(double); - CHECK_HIP(hipMalloc(&d_remote, bytes)); - } - - // Host init + copy to GPU 1 - double* h_data = new double[N]; - for (long long i = 0; i < N; i++) h_data[i] = i * 0.001; - CHECK_HIP(hipMemcpy(d_remote, h_data, bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - // Switch to GPU 0 and enable peer access to GPU 1 - CHECK_HIP(hipSetDevice(0)); - CHECK_HIP(hipDeviceEnablePeerAccess(1, 0)); - - // Allocate output buffer on GPU 0 - double* d_out; - CHECK_HIP(hipMalloc(&d_out, bytes)); - - // For optimized: allocate local copy on GPU 0 - double* d_local = nullptr; - if (use_optimized) { - CHECK_HIP(hipMalloc(&d_local, bytes)); - CHECK_HIP(hipMemcpyPeer(d_local, 0, d_remote, 1, bytes)); - CHECK_HIP(hipDeviceSynchronize()); - } - - hipDeviceProp_t prop; - CHECK_HIP(hipGetDeviceProperties(&prop, 0)); - int num_blocks = prop.multiProcessorCount * 8; - - dim3 block(256); - dim3 grid(num_blocks); - - // Pointer to read from: remote (baseline) or local (optimized) - const double* read_ptr = use_optimized ? d_local : d_remote; - - // Single dispatch for clean profiling - auto start = std::chrono::high_resolution_clock::now(); - ea_fabric_bw_read<<>>(read_ptr, d_out, N); - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_out)); - if (d_local) CHECK_HIP(hipFree(d_local)); - CHECK_HIP(hipSetDevice(1)); - CHECK_HIP(hipFree(d_remote)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_read_bw.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_read_bw.hip deleted file mode 100644 index 741d61e..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_read_bw.hip +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// EA HBM Read Credit Pressure Workload -// -// Target metrics: EA HBM BW Bound - Read Credit Pressure (30.18.0), -// EA to HBM read BW (30.14.0), EA read latency (30.14.10) -// -// Baseline: Streaming reads from a 4GB buffer (>> 4MB L2) using grid-stride loop. -// Coalesced sequential access maximizes read request rate, flooding EA -// with outstanding read requests and exhausting HBM read credits. -// Expected: Read Credit Pressure > 10%, high EA to HBM read BW. -// -// Optimized: Tiled access pattern where each workgroup reads a tile that fits -// partially in L2, then reuses it across multiple passes. -// L2 hits reduce EA read requests. -// Expected: Read Credit Pressure < 5%, lower EA to HBM read BW. - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -// Baseline: Streaming reads across entire 4GB buffer — saturates HBM read BW -__global__ void ea_hbm_read_bw_baseline(const double* __restrict__ data, - double* __restrict__ out, - long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)gridDim.x * blockDim.x; - - double acc = 0.0; - for (long long i = tid; i < N; i += stride) { - acc += data[i]; - } - // One write per workgroup to avoid polluting write metrics - if (threadIdx.x == 0) { - out[blockIdx.x] = acc; - } -} - -// Optimized: All workgroups re-read a small region that fits in L2 (~2MB) -// After the first pass cold-misses, subsequent passes hit in L2 cache. -// This dramatically reduces EA read requests compared to streaming the full buffer. -__global__ void ea_hbm_read_bw_optimized(const double* __restrict__ data, - double* __restrict__ out, - long long N) { - // Region that fits in L2: 2MB = 256K doubles - // MI350 has 4MB L2 per XCD; 2MB leaves room for other traffic - constexpr long long REGION_ELEMS = 256LL * 1024; // 2MB - constexpr int PASSES = 256; // Re-read the region many times - - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)gridDim.x * blockDim.x; - - double acc = 0.0; - for (int pass = 0; pass < PASSES; pass++) { - for (long long i = tid; i < REGION_ELEMS; i += stride) { - acc += data[i]; - } - } - - // One write per workgroup — negligible write traffic - if (threadIdx.x == 0) { - out[blockIdx.x] = acc; - } -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - // 4GB of doubles = 512M elements - long long N = 512LL * 1024 * 1024; - size_t bytes = (size_t)N * sizeof(double); - - double* d_data; - hipError_t alloc_err = hipMalloc(&d_data, bytes); - if (alloc_err != hipSuccess) { - // Fallback to 2GB if 4GB fails - N = 256LL * 1024 * 1024; - bytes = (size_t)N * sizeof(double); - CHECK_HIP(hipMalloc(&d_data, bytes)); - } - - // Host init + copy (no hipMemset — avoids __amd_rocclr_fillBufferAligned) - double* h_data = new double[N]; - for (long long i = 0; i < N; i++) h_data[i] = i * 0.001; - CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - hipDeviceProp_t prop; - CHECK_HIP(hipGetDeviceProperties(&prop, 0)); - int num_blocks = prop.multiProcessorCount * 8; - - // Output: one element per workgroup (negligible write traffic) - double* d_out; - CHECK_HIP(hipMalloc(&d_out, (size_t)num_blocks * sizeof(double))); - - dim3 block(256); - dim3 grid(num_blocks); - - // Single dispatch for clean profiling - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - ea_hbm_read_bw_optimized<<>>(d_data, d_out, N); - } else { - ea_hbm_read_bw_baseline<<>>(d_data, d_out, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_data)); - CHECK_HIP(hipFree(d_out)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_write_bw.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_write_bw.hip deleted file mode 100644 index 8296b0a..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_hbm_write_bw.hip +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// EA HBM Write Credit Pressure Workload -// -// Target metrics: EA HBM BW Bound - Write Credit Pressure (30.18.1), -// EA to HBM write BW (30.14.1), EA Write Backpressure (30.18.5) -// -// Baseline: Streaming writes to a 4GB buffer using grid-stride loop. -// Each thread writes sequentially, maximizing write request rate. -// Expected: Write Credit Pressure > 10%, high EA to HBM write BW. -// -// Optimized: Accumulate partial results in registers across multiple iterations. -// Write only the final accumulated value, reducing total write -// requests by a large factor. -// Expected: Write Credit Pressure < 5%, lower write BW. - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -// Baseline: Streaming writes across entire buffer — saturates HBM write BW -__global__ void ea_hbm_write_bw_baseline(double* __restrict__ data, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)gridDim.x * blockDim.x; - - for (long long i = tid; i < N; i += stride) { - data[i] = (double)i * 0.001; - } -} - -// Optimized: Accumulate in registers, write only final result -__global__ void ea_hbm_write_bw_optimized(double* __restrict__ data, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)gridDim.x * blockDim.x; - - // Accumulate across all iterations in registers - double acc = 0.0; - for (long long i = tid; i < N; i += stride) { - acc += (double)i * 0.001; - } - - // Single write per thread - if (tid < N) { - data[tid] = acc; - } -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - // 4GB of doubles = 512M elements - long long N = 512LL * 1024 * 1024; - size_t bytes = (size_t)N * sizeof(double); - - double* d_data; - hipError_t alloc_err = hipMalloc(&d_data, bytes); - if (alloc_err != hipSuccess) { - // Fallback to 2GB - N = 256LL * 1024 * 1024; - bytes = (size_t)N * sizeof(double); - CHECK_HIP(hipMalloc(&d_data, bytes)); - } - - // Host init + copy (no hipMemset) - double* h_data = new double[N]; - for (long long i = 0; i < N; i++) h_data[i] = 0.0; - CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - hipDeviceProp_t prop; - CHECK_HIP(hipGetDeviceProperties(&prop, 0)); - int num_blocks = prop.multiProcessorCount * 8; - - dim3 block(256); - dim3 grid(num_blocks); - - // Single dispatch for clean profiling - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - ea_hbm_write_bw_optimized<<>>(d_data, N); - } else { - ea_hbm_write_bw_baseline<<>>(d_data, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_data)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_io_bw.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_io_bw.hip deleted file mode 100644 index 4fd47e1..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_io_bw.hip +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// EA IO/PCIe Bandwidth Workload -// -// Target metrics: EA IO BW Bound - Combined (30.18.4), -// EA read BW fraction - IO (30.13.4), -// EA Stall Destination Dominance - IO (30.18.10), -// EA to IO read BW (30.14.7) -// -// Baseline: Streaming reads from host-pinned (fine-grained coherent) memory. -// All traffic traverses PCIe/IO path. -// Expected: IO BW Bound > 5%, read BW fraction IO > 90%. -// -// Optimized: Copy host data to device memory first, then read from local HBM. -// Expected: IO BW Bound ~ 0%, Stall Dominance HBM ~ 100%. -// -// Note: IO credit stalls may be lower in absolute terms than HBM stalls -// because PCIe BW is inherently lower. The key validation is that the -// Stall Destination Dominance metric correctly identifies IO as the -// dominant stall source in the baseline. - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -// Streaming read kernel — same kernel for both cases, just different pointers -__global__ void ea_io_bw_read(const double* __restrict__ data, - double* __restrict__ out, - long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)gridDim.x * blockDim.x; - - double acc = 0.0; - for (long long i = tid; i < N; i += stride) { - acc += data[i]; - } - - if (tid < N) { - out[tid] = acc; - } -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - // 1GB of doubles = 128M elements - long long N = 128LL * 1024 * 1024; - size_t bytes = (size_t)N * sizeof(double); - - // Allocate host-pinned coherent memory (fine-grained, visible via PCIe) - double* h_pinned; - CHECK_HIP(hipHostMalloc(&h_pinned, bytes, hipHostMallocCoherent)); - - // Initialize host data - for (long long i = 0; i < N; i++) h_pinned[i] = i * 0.001; - - // Allocate device output buffer - double* d_out; - CHECK_HIP(hipMalloc(&d_out, bytes)); - - // For optimized: copy to device memory first - double* d_local = nullptr; - if (use_optimized) { - CHECK_HIP(hipMalloc(&d_local, bytes)); - CHECK_HIP(hipMemcpy(d_local, h_pinned, bytes, hipMemcpyHostToDevice)); - CHECK_HIP(hipDeviceSynchronize()); - } - - hipDeviceProp_t prop; - CHECK_HIP(hipGetDeviceProperties(&prop, 0)); - int num_blocks = prop.multiProcessorCount * 8; - - dim3 block(256); - dim3 grid(num_blocks); - - // Pointer to read from: host-pinned (baseline) or device-local (optimized) - const double* read_ptr = use_optimized ? d_local : h_pinned; - - // Single dispatch for clean profiling - auto start = std::chrono::high_resolution_clock::now(); - ea_io_bw_read<<>>(read_ptr, d_out, N); - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_out)); - if (d_local) CHECK_HIP(hipFree(d_local)); - CHECK_HIP(hipHostFree(h_pinned)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_rw_balance.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_rw_balance.hip deleted file mode 100644 index aa89eea..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_rw_balance.hip +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// EA Read/Write Balance Workload -// -// Target metrics: EA Read/Write Balance (30.18.6), -// EA read request fraction - HBM (30.13.1), -// EA write request fraction - HBM (30.13.5) -// -// Baseline: Read-only streaming — sum a large array, write a single scalar. -// Expected: R/W Balance > 95% (nearly all reads). -// -// Optimized: Balanced read-modify-write — vector copy from source to dest (1:1 R/W). -// Expected: R/W Balance ~ 33-50%. - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -// Baseline: Read-only streaming — extreme read imbalance -__global__ void ea_rw_balance_baseline(const double* __restrict__ src, - double* __restrict__ result, - long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)gridDim.x * blockDim.x; - - double acc = 0.0; - for (long long i = tid; i < N; i += stride) { - acc += src[i]; - } - - // Single scalar write per thread (negligible write traffic) - if (tid == 0) { - result[0] = acc; - } -} - -// Optimized: 1:1 vector copy — balanced read/write -__global__ void ea_rw_balance_optimized(const double* __restrict__ src, - double* __restrict__ dst, - long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)gridDim.x * blockDim.x; - - for (long long i = tid; i < N; i += stride) { - dst[i] = src[i]; - } -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - // 2GB of doubles = 256M elements - long long N = 256LL * 1024 * 1024; - size_t bytes = (size_t)N * sizeof(double); - - double* d_src; - hipError_t alloc_err = hipMalloc(&d_src, bytes); - if (alloc_err != hipSuccess) { - // Fallback to 1GB - N = 128LL * 1024 * 1024; - bytes = (size_t)N * sizeof(double); - CHECK_HIP(hipMalloc(&d_src, bytes)); - } - - double* d_dst; - CHECK_HIP(hipMalloc(&d_dst, bytes)); - - // Host init + copy (no hipMemset) - double* h_data = new double[N]; - for (long long i = 0; i < N; i++) h_data[i] = i * 0.001; - CHECK_HIP(hipMemcpy(d_src, h_data, bytes, hipMemcpyHostToDevice)); - - // Zero-init destination via host copy - for (long long i = 0; i < N; i++) h_data[i] = 0.0; - CHECK_HIP(hipMemcpy(d_dst, h_data, bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - hipDeviceProp_t prop; - CHECK_HIP(hipGetDeviceProperties(&prop, 0)); - int num_blocks = prop.multiProcessorCount * 8; - - dim3 block(256); - dim3 grid(num_blocks); - - // Single dispatch for clean profiling - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - ea_rw_balance_optimized<<>>(d_src, d_dst, N); - } else { - ea_rw_balance_baseline<<>>(d_src, d_dst, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_src)); - CHECK_HIP(hipFree(d_dst)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_write_backpressure.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_write_backpressure.hip deleted file mode 100644 index 0168071..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/ea/ea_write_backpressure.hip +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// EA Write Path Backpressure Workload -// -// Target metrics: EA Write Backpressure (30.18.5), -// Normalized EA - Write stall (30.16.6), -// Normalized EA - Too many writes stall (30.16.7) -// -// Baseline: Coalesced streaming writes at maximum rate. Adjacent threads -// write adjacent addresses across the full buffer in a tight loop -// with no intervening compute. This floods TCC→EA with write -// requests faster than EA can drain credits, triggering -// TCC_EA0_WRREQ_STALL and TCC_EA0_WRREQ_DRAM_CREDIT_STALL. -// Expected: Write Backpressure > 15%. -// -// Optimized: Same total writes but interleaved with heavy ALU work. -// Compute between writes gives EA time to retire credits, -// preventing write credit exhaustion. -// Expected: Write Backpressure < 5%. - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -// Baseline: Coalesced streaming writes at maximum rate — floods EA with write requests -__global__ void ea_write_backpressure_baseline(float* __restrict__ data, int N) { - int tid = blockIdx.x * blockDim.x + threadIdx.x; - int stride = gridDim.x * blockDim.x; - - // Tight write loop with no intervening compute. - // Adjacent threads write adjacent addresses (fully coalesced), - // maximizing write request rate through TCC to EA. - for (int iter = 0; iter < 64; iter++) { - for (int i = tid; i < N; i += stride) { - data[i] = (float)(i + iter); - } - } -} - -// Optimized: Same writes but interleaved with heavy ALU work. -// Compute between writes gives EA time to drain write credits. -__global__ void ea_write_backpressure_optimized(float* __restrict__ data, int N) { - int tid = blockIdx.x * blockDim.x + threadIdx.x; - int stride = gridDim.x * blockDim.x; - - for (int i = tid; i < N; i += stride) { - // Heavy ALU work before each write — throttles write request rate - float val = (float)i; - for (int j = 0; j < 256; j++) { - val = val * 0.9999f + 0.0001f; - } - data[i] = val; - } -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - // 2GB of floats = 512M elements - int N = 512 * 1024 * 1024; - size_t bytes = (size_t)N * sizeof(float); - - float* d_data; - hipError_t alloc_err = hipMalloc(&d_data, bytes); - if (alloc_err != hipSuccess) { - // Fallback to 1GB - N = 256 * 1024 * 1024; - bytes = (size_t)N * sizeof(float); - CHECK_HIP(hipMalloc(&d_data, bytes)); - } - - // Host init + copy (no hipMemset) - float* h_data = new float[N]; - for (int i = 0; i < N; i++) h_data[i] = 0.0f; - CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - hipDeviceProp_t prop; - CHECK_HIP(hipGetDeviceProperties(&prop, 0)); - int num_blocks = prop.multiProcessorCount * 8; - - dim3 block(256); - dim3 grid(num_blocks); - - // Single dispatch for clean profiling - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - ea_write_backpressure_optimized<<>>(d_data, N); - } else { - ea_write_backpressure_baseline<<>>(d_data, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_data)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/README.md b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/README.md deleted file mode 100644 index 9509d87..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/README.md +++ /dev/null @@ -1,119 +0,0 @@ -# Memory Bandwidth Analysis Test Suite - -HIP workloads targeting MI350 (gfx950) L1 and L2 cache/memory metrics for validation with `rocprof-compute --experimental --membw-analysis`. - -## L1 Workloads (tables 3001-3003) - -| Workload | Baseline (intended effect) | Optimized (intended effect) | -|---|---|---| -| gl2_backpressure | Uncoalesced + large stride -> L2 misses -> fill LFIFO -> TCP stalls. | Shared mem, minimal L2 traffic. | -| L1_stall_microbenchmark | Many scattered loads/stores -> VMEM FIFO fills. | Shared memory reduces VMEM FIFO pressure. | -| utcl1_stall | Rapid page hopping (>32 pages) -> exceed UTCL1 entries -> in-flight stall. | Stay in one page. | -| ta_tcp_stall | Random access, TA waits for TCP. | Sequential access, TCP serves TA quickly. | - -> [!NOTE] -> L1 workloads are still WIP. Profiled results may not yet reflect intended results. - -### L1 Target Metrics - -- **gl2_backpressure**: `TCP_TCR_TCP_STALL_CYCLES / tcp_busy` -- **utcl1_stall**: `TCP_UTCL1_STALL_INFLIGHT_MAX / tcp_busy` - -## L2 Workloads (tables 3007-3012) - -Each workload targets specific TCC perfmon counters exposed in the new L2 metric tables added in PR 4091. All use runtime L2 cache size detection (`hipDeviceAttributeL2CacheSize`). - -| Workload | Target Metrics | Baseline (intended effect) | Optimized (intended effect) | -|---|---|---|---| -| l2_hbm_read_bw_stress | L2-EA read credit stall (HBM), L2-EA read BW, HBM read fraction, L2 hit rate, L2 Cache Efficiency | Streaming reads over 4x L2 buffer. Every access misses L2 -> EA read -> HBM. Exhausts DRAM read credits. | Reads small tile fitting in L2 -> high hit rate, no credit stalls. | -| l2_hbm_write_bw_stress | L2-EA write credit stall (HBM), L2-EA write stall, TOO_MANY_EA_WRREQS_STALL, SRC_FIFO_FULL, writeback rate | Streaming writes over 4x L2 buffer. Write-allocate + eviction of dirty lines -> max EA write traffic. | Writes small region in L2 -> dirty lines stay cached, minimal writeback. | -| l2_cache_thrash | TAG_STALL, IB_STALL, LATENCY_FIFO_FULL, eviction rate, Back Pressure Indicator, Internal Resource Pressure | Scattered RMW on 1.5x L2 buffer (prime stride). Fills latency FIFO, causes tag stalls, IB stalls (backpressure). | Coalesced RMW on small working set -> all hits, no stalls. | -| l2_atomic_stress | EA0_ATOMIC, EA0_ATOMIC_LEVEL (atomic latency), TCC_ATOMIC | High-contention atomicAdd on 1024 cachelines across 2x L2 buffer. Forces EA atomic path with high per-atomic latency. | Regular RMW (no atomics) on L2-fitting tile. Each thread works on its own cacheline. | -| l2_coherence_traffic | NC_REQ, UC_REQ, CC_REQ, PROBE, PROBE_EVICT | `fg` mode: fine-grained memory (CC type) -> coherence protocol, internal probes. `nc` mode: `__builtin_nontemporal_store` -> NC traffic. | Coarse-grained memory with normal cached accesses. | -| l2_multigpu_fabric | EA0_RDREQ_GMI_CREDIT_STALL, EA0_WRREQ_GMI_CREDIT_STALL, Remote Access Pressure (IF) | GPU 0 reads/writes memory on GPU 1 via P2P -> Infinity Fabric (GMI) credit exhaustion. | N/A (requires 2 GPUs) | -| l2_io_stress | EA0_RDREQ_IO_CREDIT_STALL, EA0_WRREQ_IO_CREDIT_STALL | GPU kernel accesses host-pinned memory (hipHostMalloc) -> PCIe/IO credit exhaustion. | Same kernel on device-local memory. | -| l2_normalized_throughput | All Normalized Stall Metrics (table 30.10), All Throughput Metrics (table 30.11), Combined Credit Pressure (table 30.12) | Mixed R+W streaming over 4x L2 buffer, purely memory-bound. High stalls relative to GRBM_GUI_ACTIVE. | Compute-heavy FMA kernel with minimal memory -> near-zero normalized stalls. | - -### Expected Validation Results - -| Workload | Key Metric | Baseline | Optimized | -|---|---|---|---| -| l2_hbm_read_bw_stress | L2-EA read credit stall (HBM) | >10% | <1% | -| l2_hbm_read_bw_stress | L2 hit rate | <20% | >90% | -| l2_hbm_write_bw_stress | L2-EA write credit stall (HBM) | >10% | <1% | -| l2_hbm_write_bw_stress | L2 writeback rate | >30% | <5% | -| l2_cache_thrash | L2 tag stall rate | >10% | <5% | -| l2_cache_thrash | L2 Back Pressure Indicator | >10% | <5% | -| l2_atomic_stress | L2-EA atomic latency | >100 cyc | N/A (no atomics) | -| l2_coherence_traffic (fg) | Coherent cached req rate | >50% | <5% | -| l2_io_stress | L2-EA read credit stall (IO) | >5% | <1% | -| l2_normalized_throughput | Combined Credit Pressure | >10% | <1% | - -## Build - -```bash -# Build a single workload -hipcc -g .hip -o --offload-arch=gfx950 - -# Build all L2 workloads -for f in l2_*.hip; do - hipcc -g "$f" -o "${f%.hip}" --offload-arch=gfx950 -done -``` - -## Profiling - -```bash -# Note: --no-roof is optional and skips the -# benchmarking that is unrelated to the our purpose - -# Profile baseline -rocprof-compute profile -n _baseline --experimental --membw-analysis --no-roof -- ./ - -# Profile optimized -rocprof-compute profile -n _optimized --experimental --membw-analysis --no-roof -- ./ opt -``` - -### Examples - -```bash -# HBM read stress -rocprof-compute profile -n hbm_read_baseline --experimental --membw-analysis --no-roof -- ./l2_hbm_read_bw_stress -rocprof-compute profile -n hbm_read_optimized --experimental --membw-analysis --no-roof -- ./l2_hbm_read_bw_stress opt - -# Cache thrash -rocprof-compute profile -n thrash_baseline --experimental --membw-analysis --no-roof -- ./l2_cache_thrash -rocprof-compute profile -n thrash_optimized --experimental --membw-analysis --no-roof -- ./l2_cache_thrash opt - -# Coherence (fine-grained mode) -rocprof-compute profile -n coherence_fg --experimental --membw-analysis --no-roof -- ./l2_coherence_traffic -rocprof-compute profile -n coherence_nc --experimental --membw-analysis --no-roof -- ./l2_coherence_traffic nc -rocprof-compute profile -n coherence_opt --experimental --membw-analysis --no-roof -- ./l2_coherence_traffic opt - -# IO stress (host-pinned vs device-local) -rocprof-compute profile -n io_baseline --experimental --membw-analysis --no-roof -- ./l2_io_stress -rocprof-compute profile -n io_optimized --experimental --membw-analysis --no-roof -- ./l2_io_stress opt - -# Multi-GPU (requires 2 GPUs) -rocprof-compute profile -n fabric_read --experimental --membw-analysis --no-roof -- ./l2_multigpu_fabric read -rocprof-compute profile -n fabric_write --experimental --membw-analysis --no-roof -- ./l2_multigpu_fabric write -``` - -## Analyzing - -```bash -rocprof-compute analyze -p -``` - -## Hardware Requirements - -| Workload | GPUs | Notes | -|---|---|---| -| l2_hbm_read_bw_stress | 1 | Single GPU | -| l2_hbm_write_bw_stress | 1 | Single GPU | -| l2_cache_thrash | 1 | Single GPU | -| l2_atomic_stress | 1 | Single GPU | -| l2_coherence_traffic | 1 | Single GPU; fine-grained alloc may not be supported on all platforms | -| l2_multigpu_fabric | 2 | Requires P2P access between GPUs | -| l2_io_stress | 1 | Single GPU; needs sufficient host memory for 1GB pinned alloc | -| l2_normalized_throughput | 1 | Single GPU | diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/VERIFICATION_REPORT.md b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/VERIFICATION_REPORT.md deleted file mode 100644 index 85c154d..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/VERIFICATION_REPORT.md +++ /dev/null @@ -1,275 +0,0 @@ -# L2 Memory Bandwidth Metrics Verification Report - -**Date:** 2026-03-27 -**System:** 2x MI350X GPUs (gfx950), 256 CUs, 128 TCC channels, 4 MB L2 per XCD (8 XCDs) -**Tool:** `src/rocprof-compute analyze -p -b 30` -**Profiling data:** `/tmp/l2_profile_results/` - -## Hardware Spec Cross-Reference (MI350 TCC Perfmon List) - -All TCC counters used in the YAML metric equations were verified against the MI350 perfmon specification: - -| Counter | Perfmon ID | Disclosure | Verified | -|---|---|---|---| -| TCC_PERF_SEL_BUSY | 2 | Public | Yes | -| TCC_PERF_SEL_REQ | 6 | Public | Yes | -| TCC_PERF_SEL_READ | 7 | Public | Yes | -| TCC_PERF_SEL_WRITE | 8 | Public | Yes | -| TCC_PERF_SEL_ATOMIC | 10 | Public | Yes | -| TCC_PERF_SEL_HIT | 21 | Public | Yes | -| TCC_PERF_SEL_MISS | 23 | Public | Yes | -| TCC_PERF_SEL_WRITEBACK | 24 | Public | Yes | -| TCC_PERF_SEL_LATENCY_FIFO_FULL | 27 | Public | Yes | -| TCC_PERF_SEL_SRC_FIFO_FULL | 28 | Public | Yes | -| TCC_PERF_SEL_EA0_WRREQ | 30 | Public | Yes | -| TCC_PERF_SEL_EA0_WRREQ_64B | 31 | Public | Yes | -| TCC_PERF_SEL_EA0_WRREQ_STALL | 34 | Public | Yes | -| TCC_PERF_SEL_EA0_WRREQ_IO_CREDIT_STALL | 35 | Public | Yes | -| TCC_PERF_SEL_EA0_WRREQ_GMI_CREDIT_STALL | 36 | Public | Yes | -| TCC_PERF_SEL_EA0_WRREQ_DRAM_CREDIT_STALL | 37 | Public | Yes | -| TCC_PERF_SEL_TOO_MANY_EA_WRREQS_STALL | 38 | Public | Yes | -| TCC_PERF_SEL_EA0_WRREQ_LEVEL | 39 | Public | Yes | -| TCC_PERF_SEL_EA0_ATOMIC | 40 | Public | Yes | -| TCC_PERF_SEL_EA0_ATOMIC_LEVEL | 41 | Public | Yes | -| TCC_PERF_SEL_EA0_RDREQ | 42 | Public | Yes | -| TCC_PERF_SEL_EA0_RDREQ_32B | 43 | Public | Yes | -| TCC_PERF_SEL_EA0_RDREQ_64B | 44 | Public | Yes | -| TCC_PERF_SEL_EA0_RDREQ_128B | 45 | Public | Yes (MI350-specific) | -| TCC_PERF_SEL_EA0_RDREQ_IO_CREDIT_STALL | 47 | Public | Yes | -| TCC_PERF_SEL_EA0_RDREQ_GMI_CREDIT_STALL | 48 | Public | Yes | -| TCC_PERF_SEL_EA0_RDREQ_DRAM_CREDIT_STALL | 49 | Public | Yes | -| TCC_PERF_SEL_TAG_STALL | 51 | Public | Yes | -| TCC_PERF_SEL_IB_STALL | 68 | Public | Yes | -| TCC_PERF_SEL_NORMAL_EVICT | 80 | Public | Yes | -| TCC_PERF_SEL_PROBE | - | Public | Yes | -| TCC_PERF_SEL_NC_REQ | - | Public | Yes | -| TCC_PERF_SEL_UC_REQ | - | Public | Yes | -| TCC_PERF_SEL_CC_REQ | - | Public | Yes | -| TCC_PERF_SEL_EA0_RDREQ_DRAM | - | Public | Yes | -| TCC_PERF_SEL_EA0_WRREQ_DRAM | - | Public | Yes | -| TCC_PERF_SEL_EA0_RDREQ_LEVEL | - | Public | Yes | - -**Notable:** `TCC_PERF_SEL_EA0_RDREQ_128B` (ID 45) is MI350-specific. The `L2-EA read BW` formula correctly includes 128B reads. No `TCC_EA0_WRREQ_128B` counter exists, so the write BW formula correctly assumes writes are 32B or 64B only. - -**Not collected:** `TCC_PROBE_EVICT` is not present in the membw profiling config. Probe eviction rate is N/A across all workloads. - -## Block 30 Table Coverage - -All 18 workloads were analyzed across the 6 L2 metric tables in block 30. The table below summarizes which tables produce fully numeric results vs. tables with expected N/A values. - -| Table | Name | Result | Notes | -|---|---|---|---| -| 30.7 | L2 Cache | **18/18 PASS** | All metrics produce numeric values | -| 30.8 | L2 Traffic | 18/18 have some N/A | Atomic latency N/A when 0 atomics; Probe eviction N/A (counter not collected) | -| 30.9 | L2 Raw Counter Values | 17/18 PASS, 1 partial | TCC_PROBE_EVICT N/A across all; TCC_CC_REQ anomalous in io_baseline | -| 30.10 | Normalized L2 Stall Metrics (%) | **18/18 PASS** | All metrics produce numeric values | -| 30.11 | Normalized L2 Throughput Metrics (per Second) | **18/18 PASS** | All metrics produce numeric values | -| 30.12 | L2 Bottleneck Detection Indicators | **18/18 PASS** | All metrics produce numeric values | - -All N/A values are legitimate zero-denominator cases (e.g., atomic latency when no atomics issued) or uncollected counters (`TCC_PROBE_EVICT`). - -## Workload Verification Results - -### 1. l2_hbm_read_bw_stress - -| Key Metric | Table | Baseline | Optimized | Expected (B) | Expected (O) | Verdict | -|---|---|---|---|---|---|---| -| L2 hit rate | 30.7 | **0.53%** | 30.32% | <20% | >90% | B: PASS, O: FAIL | -| L2 utilization | 30.7 | **22.63%** | 6.56% | High | Low | **PASS** | -| L2 tag stall rate | 30.7 | **21.23%** | 11.06% | High | Low | **PASS** | -| L2 input buffer stall rate | 30.7 | **22.80%** | 8.34% | High | Low | **PASS** | -| L2-EA read credit stall (HBM) | 30.7 | **15.03%** | 0.00% | >10% | <1% | **PASS** | -| L2 Back Pressure Indicator | 30.12 | **22.80%** | 8.34% | High | Low | **PASS** | -| L2 Memory BW Bound - Combined Credit Pressure | 30.12 | **15.03%** | 0.00% | High | ~0% | **PASS** | -| L2 Cache Efficiency | 30.12 | **0.53%** | 30.32% | Low | High | **PASS** | -| L2 to Memory Traffic Ratio | 30.12 | **99.49%** | 70.57% | ~100% | Lower | **PASS** | - -**Notes:** -- Optimized hit rate is 30% instead of >90%. The tile size (L2/4 = 1MB) may be too large for the 4MB per-XCD L2 with 256 CUs competing. -- The primary metric (HBM read credit stall) validates correctly with a clear baseline-to-optimized contrast (15% -> 0%). -- L2 to Memory Traffic Ratio drops from 99.49% to 70.57% in optimized, confirming improved cache locality. - -### 2. l2_hbm_write_bw_stress - -| Key Metric | Table | Baseline | Optimized | Expected (B) | Expected (O) | Verdict | -|---|---|---|---|---|---|---| -| L2 hit rate | 30.7 | 50.78% | **99.20%** | Low | High | **PASS** | -| L2 utilization | 30.7 | 32.16% | 49.29% | Moderate | High | **PASS** | -| L2-EA write credit stall (HBM) | 30.7 | **42.80%** | 0.14% | High | ~0% | **PASS** | -| L2 to EA write stall rate | 30.7 | **40.28%** | 0.11% | High | ~0% | **PASS** | -| L2 write data FIFO full rate | 30.7 | 0.00% | 0.00% | - | - | **PASS** | -| L2 Memory BW Bound - Combined Credit Pressure | 30.12 | **42.80%** | 0.14% | High | ~0% | **PASS** | - -**Notes:** -- The primary metric (HBM write credit stall) validates correctly with a clear baseline-to-optimized contrast (42.80% -> 0.14%). -- L2 to EA write stall rate tracks closely with the HBM write credit stall, confirming the write path is the bottleneck in baseline. - -### 3. l2_cache_thrash - -| Key Metric | Table | Baseline | Optimized | Expected (B) | Expected (O) | Verdict | -|---|---|---|---|---|---|---| -| L2 hit rate | 30.7 | 88.50% | **99.21%** | Moderate | High | **PASS** | -| L2 utilization | 30.7 | **95.78%** | 54.01% | High | Moderate | **PASS** | -| L2 tag stall rate | 30.7 | **13.37%** | 1.08% | High | Low | **PASS** | -| L2 input buffer stall rate | 30.7 | **8.12%** | 0.93% | High | Low | **PASS** | -| L2 latency FIFO full rate | 30.7 | 0.00% | 0.00% | - | - | **PASS** | -| L2 write data FIFO full rate | 30.7 | 0.68% | 0.00% | Low | ~0% | **PASS** | -| Probe eviction rate | 30.8 | N/A | N/A | - | - | N/A (counter not collected) | -| L2 Back Pressure Indicator | 30.12 | **8.12%** | 0.93% | High | Low | **PASS** | -| L2 Cache Efficiency | 30.12 | 88.50% | **99.21%** | Moderate | High | **PASS** | - -**Notes:** -- The normalized stall metrics (30.10) use `GRBM_GUI_ACTIVE_PER_XCD` as the denominator. With 128 TCC channels each accumulating stall cycles, the sum of per-channel stalls can legitimately exceed the single kernel execution time. The >100% values are architecturally correct for multi-channel L2. - -### 4. l2_atomic_stress - -| Key Metric | Table | Baseline | Optimized | Expected (B) | Expected (O) | Verdict | -|---|---|---|---|---|---|---| -| L2 hit rate | 30.7 | 0.01% | **96.96%** | Low | High | **PASS** | -| L2 utilization | 30.7 | **89.15%** | 21.19% | High | Moderate | **PASS** | -| L2-EA write credit stall (HBM) | 30.7 | **25.82%** | 0.00% | High | ~0% | **PASS** | -| L2 to EA write stall rate | 30.7 | **25.52%** | 0.00% | High | ~0% | **PASS** | -| L2 to EA atomic average latency | 30.8 | **3420.88 cyc** | N/A | High | - | B: **PASS** | -| Probe eviction rate | 30.8 | N/A | N/A | - | - | N/A (counter not collected) | -| L2 Memory BW Bound - Combined Credit Pressure | 30.12 | **25.82%** | 0.00% | High | ~0% | **PASS** | -| L2 Cache Efficiency | 30.12 | 0.01% | **96.96%** | Low | High | **PASS** | - -**Notes:** -- The optimized kernel uses regular RMW (no atomics), so atomic latency is N/A rather than <20 cyc. The contrast between atomics at 3420 cycles latency vs zero atomics validates the metric completely. -- L2 to EA write stall rate closely tracks HBM write credit stall, confirming the EA write path saturates under atomic workloads. - -### 5. l2_coherence_traffic - -| Key Metric | Table | fg mode | nc mode | opt mode | Verdict | -|---|---|---|---|---|---| -| L2 hit rate | 30.7 | 67.16% | 50.99% | 68.29% | Expected pattern | -| L2 utilization | 30.7 | **79.08%** | 18.97% | 19.62% | fg mode high utilization | -| Non-coherent request rate | 30.8 | **92.83%** | 0.00% | 0.00% | fg mode generates NC traffic | -| Uncached request rate | 30.8 | 1.71% | 0.02% | 0.05% | Low across all modes | -| Coherent cached request rate | 30.8 | 0.00% | 0.00% | 0.00% | See note | -| L2-EA write credit stall (HBM) | 30.7 | 0.00% | **13.91%** | 1.67% | nc mode generates HBM write stalls | -| L2-EA write credit stall (IO) | 30.7 | **17.28%** | 0.00% | 0.00% | fg mode uses IO path | -| L2 Cache Efficiency | 30.12 | 67.16% | 50.99% | 68.29% | Expected pattern | - -**Notes:** -- The NC request rate counter now shows 92.83% for fg mode, confirming that `hipHostMallocCoherent` host memory routes through the IO path as NC requests at the TCC level. -- fg mode shows 17.28% IO write credit stall in 30.7, confirming IO/IF path usage. -- The CC request rate is 0% across all modes — coherence is handled at the system level, not at the TCC CC protocol level on MI350. - -### 6. l2_multigpu_fabric - -| Key Metric | Table | read | write | rw | Verdict | -|---|---|---|---|---|---| -| L2 hit rate | 30.7 | 33.34% | 50.01% | 56.94% | Expected pattern | -| L2 utilization | 30.7 | **94.82%** | 66.16% | 72.85% | Read mode saturates L2 | -| L2 tag stall rate | 30.7 | **17.21%** | 13.76% | 10.16% | High across all modes | -| L2 input buffer stall rate | 30.7 | 10.33% | **13.41%** | 9.66% | Write mode highest IB stall | -| L2-EA read credit stall (GMI) | 30.7 | **9.36%** | 0.00% | **11.22%** | Read/RW show GMI stalls | -| L2-EA write credit stall (GMI) | 30.7 | 0.00% | **18.80%** | 0.32% | Write mode shows GMI write stalls | -| L2 Back Pressure Indicator | 30.12 | **10.33%** | 13.41% | 9.66% | High backpressure | -| L2 Remote Access Pressure (GMI) | 30.12 | **9.36%** | **18.80%** | **11.54%** | **PASS** - all modes show GMI pressure | - -**Notes:** -- All three fabric modes show non-zero GMI credit stalls in both 30.7 and 30.12, validating the remote access path. -- The fabric write mode shows 18.80% GMI write credit stall, confirming remote write pressure is correctly detected. -- The 50%/50% split in read traffic is because profiling averages across both GPUs (GPU 0 does remote reads, GPU 1 is idle). - -### 7. l2_io_stress - -| Key Metric | Table | Baseline (host-pinned) | Optimized (device-local) | Expected (B) | Expected (O) | Verdict | -|---|---|---|---|---|---|---| -| L2 hit rate | 30.7 | 58.42% | 57.08% | - | - | Similar hit rates | -| L2 utilization | 30.7 | 61.09% | **91.75%** | Moderate | High | Device-local saturates L2 | -| L2-EA read credit stall (IO) | 30.7 | **0.36%** | 0.00% | >0% | ~0% | **PASS** | -| L2-EA write credit stall (IO) | 30.7 | **7.73%** | 0.00% | High | ~0% | **PASS** | -| L2 write data FIFO full rate | 30.7 | 0.00% | **6.98%** | - | Moderate | Device-local shows write data pressure | -| L2 Back Pressure Indicator | 30.12 | 8.53% | **10.22%** | - | - | Device-local shows higher L2 backpressure | -| L2 Memory BW Bound - Combined Credit Pressure | 30.12 | 0.00% | **7.24%** | ~0% | Moderate | Device-local uses HBM path | -| L2 Internal Resource Pressure - Source FIFO | 30.12 | 0.00% | **6.98%** | ~0% | Moderate | Write data path pressure in device-local | - -**Notes:** -- IO credit stalls validate correctly: baseline shows 0.36% read + 7.73% write IO stalls in 30.7, optimized shows 0%. -- Device-local optimized path shows high L2 utilization (91.75%) and moderate HBM credit pressure (7.24%), confirming it uses the HBM path instead of IO. -- New metric: Source FIFO pressure (6.98%) in device-local confirms the write data path is under pressure when saturating HBM. - -### 8. l2_normalized_throughput - -| Key Metric | Table | Baseline (memory-bound) | Optimized (compute-bound) | Expected (B) | Expected (O) | Verdict | -|---|---|---|---|---|---|---| -| L2 hit rate | 30.7 | 50.22% | 71.20% | Low-Moderate | High | **PASS** | -| L2 utilization | 30.7 | 29.32% | 8.14% | Moderate | Low | **PASS** | -| L2 tag stall rate | 30.7 | **14.70%** | 5.60% | High | Low | **PASS** | -| L2-EA read credit stall (HBM) | 30.7 | **12.54%** | 0.00% | High | ~0% | **PASS** | -| L2 Back Pressure Indicator | 30.12 | **14.15%** | 3.86% | High | Low | **PASS** | -| L2 Memory BW Bound - Combined Credit Pressure | 30.12 | **13.27%** | 0.20% | High | ~0% | **PASS** | -| L2 Cache Efficiency | 30.12 | 50.22% | **71.20%** | Low | High | **PASS** | - -## Summary - -| Workload | Primary Metric Validated? | Baseline/Opt Contrast Clear? | Overall | -|---|---|---|---| -| l2_hbm_read_bw_stress | YES (15% -> 0% HBM read credit stall in 30.7) | YES | **PASS** | -| l2_hbm_write_bw_stress | YES (42.80% -> 0.14% HBM write credit stall in 30.7) | YES | **PASS** | -| l2_cache_thrash | YES (13% tag stall, 8% IB stall in 30.7) | YES | **PASS** | -| l2_atomic_stress | YES (25.82% HBM write credit stall + 3420 cyc atomic latency in 30.7/30.8) | YES (0 atomics in opt) | **PASS** | -| l2_coherence_traffic | YES (92.83% NC in fg, 17.28% IO write stall in fg, 13.91% HBM write stall in nc) | YES | **PASS** | -| l2_multigpu_fabric | YES (9-19% GMI credit stalls in 30.7 and 30.12) | YES | **PASS** | -| l2_io_stress | YES (0.36% read + 7.73% write IO stalls in 30.7) | YES (0% in opt) | **PASS** | -| l2_normalized_throughput | YES (12.54% read credit stall in 30.7) | YES | **PASS** | - -**Result: 8/8 workloads PASS primary metric validation.** - - -## Known Issues - -### 1. Coherence traffic fg mode generates NC, not CC - -**Symptom:** The `l2_coherence_traffic` workload in fg mode (`hipHostMallocCoherent`) generates NC-type requests at the TCP level (92.83% NC request rate), not CC as documented in the README. - -**Root cause:** On MI350, `hipHostMallocCoherent` host memory routes through the IO/IF path. The TCP classifies these accesses as NC (non-coherent), while coherence is handled at the system level (not at the TCC CC protocol level). - -**Impact:** The README threshold "Coherent cached req rate >50%" does not trigger. The workload does exercise the IO/remote path (17.28% IO write credit stall, high EA write latency). - -### 2. Optimized hit rates lower than expected for some workloads - -**Symptom:** `l2_hbm_read_bw_stress` optimized shows 30% hit rate (expected >90%). - -**Root cause:** The L2 is 4 MB per XCD, and with 256 CUs across 8 XCDs, the tile (L2/4 = 1MB) may experience contention. The workloads use `hipDeviceAttributeL2CacheSize` which returns the per-XCD value. - -### 3. Normalized stall percentages exceed 100% - -**Symptom:** Many normalized stall metrics (table 30.10) show values like 435%, 1762%, 1639%. - -**Root cause:** This is architecturally correct. The denominator is `GRBM_GUI_ACTIVE_PER_XCD` (single kernel time dimension), but the numerator sums stall cycles across all 128 TCC channels (or all CUs). Since multiple channels stall simultaneously, the aggregate exceeds the kernel duration. - -### 4. TCC_PROBE_EVICT counter not collected - -**Symptom:** Probe eviction rate is N/A across all 18 workloads. - -**Root cause:** `TCC_PROBE_EVICT` is not included in the membw profiling counter set. The profiling config collects `TCC_PROBE` (probe requests) but not the eviction subset. - -**Impact:** Cannot distinguish between probes that evict cache lines vs probes that find clean lines. The probe request rate metric (30.8) still functions correctly. - -### 5. TCP_CACHE_MISS_sum not in profiling data - -**Symptom:** TCP hit rate and TCP miss rate (table 30.1) are N/A across all workloads. - -**Root cause:** The profiling config collects `TCP_CACHE_MISS` (per-CU scalar) but not `TCP_CACHE_MISS_sum` (aggregated across CUs). The YAML formulas reference `TCP_CACHE_MISS_sum`. - -**Impact:** L1 TCP cache hit/miss rate cannot be computed from this profiling data. The L2 metrics (tables 30.7-30.12) are unaffected. - -### 6. TCC_CC_REQ counter semantics anomaly - -**Symptom:** `Coherent cached request rate` shows 3238.37% in `io_baseline` (TCC_CC_REQ=836M vs TCC_REQ=25.8M). - -**Root cause:** On MI350, `TCC_CC_REQ` appears to count differently than `TCC_REQ` for IO/host-pinned memory workloads. The counter value far exceeds total L2 requests, suggesting it counts at a different granularity or includes internal coherence protocol events. - -**Impact:** The Coherent cached request rate metric is unreliable for IO workloads. Other workloads show 0% CC request rate which is expected. - -### 7. HBM read/write fraction slightly exceeds 100% - -**Symptom:** Some workloads show HBM read fraction >100% (e.g., 100.84% in atomic_baseline, 102.47% in fabric_write). - -**Root cause:** `TCC_EA0_RDREQ_DRAM` and `TCC_EA0_RDREQ` are collected in separate profiling passes with spatial multiplexing. Minor counter timing differences between passes can cause the ratio to slightly exceed 100%. - -**Impact:** Values are within counter noise margin. No functional impact. diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_atomic_stress.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_atomic_stress.hip deleted file mode 100644 index 0441af7..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_atomic_stress.hip +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// L2 Atomic Stress -// -// Target metrics (tables 3008, 3009): -// - L2-EA atomic latency [TCC_EA0_ATOMIC_LEVEL / TCC_EA0_ATOMIC] -// - L2 total request rate [TCC_REQ / TCC_BUSY] -// - TCC_EA0_ATOMIC (raw) -// - TCC_ATOMIC (raw) -// - L2-EA read/write latency (atomics contend with both paths) -// -// Mechanism: -// Baseline: Many threads perform atomicAdd on a small set of addresses -// scattered across a buffer larger than L2. With thousands of threads -// targeting few cachelines, each atomic must serialize, and those that -// miss L2 go to EA as atomic requests. -// The TCC_EA0_ATOMIC counter tracks atomics sent to the EA interface. -// High contention -> high per-atomic latency (TCC_EA0_ATOMIC_LEVEL). -// -// Optimized: Uses regular read-modify-write (no atomics) on a small -// buffer that fits in L2. This eliminates EA atomic traffic entirely, -// showing the contrast between atomic-heavy and atomic-free paths. -// -// Build: hipcc -g l2_atomic_stress.hip -o l2_atomic_stress -// Run: ./l2_atomic_stress (baseline) -// ./l2_atomic_stress opt (optimized) - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -size_t getL2CacheSize() { - int val = 0; - CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); - if (val > 0) return (size_t)val; - return 256ULL * 1024 * 1024; -} - -// Baseline: High-contention atomics on scattered addresses > L2 size. -// num_targets is small, so many threads compete -// on the same addresses. Buffer > L2 means atomics often go to EA. -__global__ void l2_stress_with_atomic(float* __restrict__ data, - long long N, int num_targets) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - - // Map thread to one of num_targets cachelines, spread across the buffer. - // This creates high contention (many threads -> same cacheline) AND - // L2 misses (targets spread over buffer > L2). - long long target_spacing = N / (long long)num_targets; - // Ensure spacing is cacheline-aligned - target_spacing = (target_spacing / 16) * 16; - if (target_spacing < 16) target_spacing = 16; - - int target_idx = (int)(tid % (long long)num_targets); - long long addr = (long long)target_idx * target_spacing; - if (addr >= N) addr = addr % N; - - // Repeated atomics on the same target -> high contention + EA atomics - for (int iter = 0; iter < 512; iter++) { - atomicAdd(&data[addr], 1.0f); - } -} - -// Optimized: Regular read-modify-write (no atomics) on a buffer that -// fits in L2. Each thread works on its own cacheline. No EA atomic -// traffic is generated. This contrasts with the baseline which sends -// all operations through the EA atomic path. -__global__ void l2_stress_without_atomic(float* __restrict__ data, - long long N, long long tile_elems) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - - // Each thread gets a unique cacheline within the L2-fitting tile - long long addr = (tid * 16) % tile_elems; - - // Regular RMW instead of atomics: hits in L2, no EA atomic traffic - for (int iter = 0; iter < 512; iter++) { - data[addr] = data[addr] + 1.0f; - } -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - size_t l2_size = getL2CacheSize(); - std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; - - // Buffer 2x L2 for baseline (atomics scattered beyond L2) - size_t buffer_bytes = l2_size * 2; - long long N = (long long)(buffer_bytes / sizeof(float)); - long long tile_elems = (long long)(l2_size / (4 * sizeof(float))); - - // Number of distinct target cachelines for contention. - int num_targets = 1024; - - float* d_data; - CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); - - float* h_data = new float[N]; - for (long long i = 0; i < N; i++) h_data[i] = 0.0f; - CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - dim3 block(64); - dim3 grid(4096); - - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - l2_stress_without_atomic<<>>(d_data, N, tile_elems); - } else { - l2_stress_with_atomic<<>>(d_data, N, num_targets); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_data)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_cache_thrash.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_cache_thrash.hip deleted file mode 100644 index fde416d..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_cache_thrash.hip +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// L2 Cache Thrash — Internal Stall Stress -// -// Target metrics (tables 3007, 3008, 3009, 3010): -// - L2 tag stall rate [TCC_TAG_STALL] -// - L2 input buffer stall rate [TCC_IB_STALL] (back pressure indicator) -// - L2 latency FIFO full rate [TCC_LATENCY_FIFO_FULL] -// - L2 utilization [TCC_BUSY / total_l2_chan * GRBM] -// - L2 eviction rate [TCC_NORMAL_EVICT] -// - L2 Back Pressure Indicator (table 3009) -// - L2 Internal Resource Pressure (table 3009) -// - Normalized L2 stalls (table 3010) -// -// Mechanism: -// Baseline: Read-modify-write on scattered cachelines across a buffer -// 1.5x L2 size. The scatter pattern (prime-stride) ensures all TCC channels -// are hit simultaneously. Each thread does repeated RMW iterations, creating: -// 1. High miss rate -> outstanding EA reads fill the latency FIFO -// 2. Dirty cachelines -> writeback on eviction -> writeback FIFO pressure -// 3. Continuous tag lookups with misses -> TAG_STALL -// 4. L2 can't accept new L1 requests -> IB_STALL -> backpressure -// 5. TAG_MISS_NOTHING_REPLACEABLE_STALL when all lines have pending fills -// -// Optimized: Coalesced RMW on a small working set that fits in L2. -// High hit rate, no stalls. -// -// Build: hipcc -g l2_cache_thrash.hip -o l2_cache_thrash -// Run: ./l2_cache_thrash (baseline) -// ./l2_cache_thrash opt (optimized) - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -size_t getL2CacheSize() { - int val = 0; - CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); - if (val > 0) return (size_t)val; - return 256ULL * 1024 * 1024; -} - -// Baseline: Scattered read-modify-write over buffer > L2. -// Prime-number stride ensures accesses are spread across TCC channels -// and consecutive iterations hit different cachelines, preventing reuse. -// The RMW pattern makes cachelines dirty, so eviction triggers writebacks. -__global__ void cache_thrash_baseline(float* __restrict__ data, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - - // Use a large prime stride to scatter across TCC channels. - // 7919 * 16 floats = ~500KB stride per step -> wraps around buffer, - // hitting different cachelines each iteration. - const long long prime_stride = 7919LL * 16; // in float elements - - long long idx = (tid * 16) % N; // Start at unique cacheline - - float acc = 0.0f; - for (int iter = 0; iter < 256; iter++) { - // Read-modify-write: makes the cacheline dirty - float val = data[idx]; - acc += val; - data[idx] = val + 1.0f; - - // Jump to a different cacheline (scattered pattern) - idx = (idx + prime_stride) % N; - } - - // Prevent optimization - if (acc == -999.0f) data[0] = acc; -} - -// Optimized: Sequential coalesced RMW on a small working set. -// All accesses hit in L2 after first pass. -__global__ void cache_thrash_optimized(float* __restrict__ data, long long N, - long long tile_elems) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - for (int rep = 0; rep < 64; rep++) { - for (long long i = tid; i < tile_elems; i += stride) { - data[i] = data[i] + 1.0f; - } - } -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - size_t l2_size = getL2CacheSize(); - std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; - - // 1.5x L2 for baseline: large enough to thrash but not so large - // that we spend all time on memory transfers - size_t buffer_bytes = (l2_size * 3) / 2; - long long N = (long long)(buffer_bytes / sizeof(float)); - long long tile_elems = (long long)(l2_size / (4 * sizeof(float))); - - float* d_data; - CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); - - float* h_data = new float[N]; - for (long long i = 0; i < N; i++) h_data[i] = (float)(i % 1000) * 0.001f; - CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - // More blocks = more concurrent waves = more L2 pressure - dim3 block(64); - dim3 grid(8192); - - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - cache_thrash_optimized<<>>(d_data, N, tile_elems); - } else { - cache_thrash_baseline<<>>(d_data, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_data)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_coherence_traffic.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_coherence_traffic.hip deleted file mode 100644 index f140f82..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_coherence_traffic.hip +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// L2 Coherence Traffic -// -// Target metrics (tables 3008, 3009): -// - Non-coherent request rate [TCC_NC_REQ / TCC_REQ] -// - Uncached request rate [TCC_UC_REQ / TCC_REQ] -// - Coherent cached request rate [TCC_CC_REQ / TCC_REQ] -// - Probe request rate [TCC_PROBE / TCC_BUSY] -// - Probe eviction rate [TCC_PROBE_EVICT / TCC_PROBE] -// -// Mechanism: -// Baseline (mode=fg): Uses hipHostMalloc with hipHostMallocCoherent flag -// to allocate system memory visible to the GPU. GPU accesses to this -// coherent host memory go through the IO/IF path and should generate -// UC (uncached) or CC (coherent cached) requests, elevating TCC_UC_REQ -// and/or TCC_CC_REQ. The kernel does read-modify-write on this memory. -// -// Baseline (mode=nc): Attempts hipExtMallocWithFlags(hipDeviceMallocFinegrained), -// falling back to hipMalloc if unavailable. The kernel unconditionally uses -// __builtin_nontemporal_store to generate non-coherent streaming traffic (TCC_NC_REQ). -// -// Optimized: Standard coarse-grained hipMalloc with normal cached accesses. -// Most requests will be normal cached (not NC/UC/CC). -// -// Build: hipcc -g l2_coherence_traffic.hip -o l2_coherence_traffic -// Run: ./l2_coherence_traffic (fine-grained CC traffic) -// ./l2_coherence_traffic nc (non-temporal/streaming traffic) -// ./l2_coherence_traffic opt (optimized, coarse-grained) - -#include -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -size_t getL2CacheSize() { - int val = 0; - CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); - if (val > 0) return (size_t)val; - return 256ULL * 1024 * 1024; -} - -// Fine-grained RMW: CC memory type -> coherence protocol active. -// Each thread does read-modify-write which triggers internal probes -// (TCC self-probes for CC writes) and coherence tracking. -__global__ void coherence_fg_baseline(float* __restrict__ data, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - for (long long i = tid; i < N; i += stride) { - // RMW on fine-grained memory: triggers CC protocol - data[i] = data[i] + 1.0f; - } -} - -// Non-temporal streaming write: generates NC (non-coherent) traffic. -// Uses __builtin_nontemporal_store to bypass cache coherence. -__global__ void coherence_nc_baseline(float* __restrict__ data, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - for (long long i = tid; i < N; i += stride) { - __builtin_nontemporal_store((float)i, &data[i]); - } -} - -// Optimized: Standard coarse-grained memory with normal cached accesses. -// No coherence protocol overhead, no non-temporal hints. -__global__ void coherence_optimized(float* __restrict__ data, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - for (long long i = tid; i < N; i += stride) { - data[i] = data[i] + 1.0f; - } -} - -int main(int argc, char** argv) { - enum Mode { FG, NC, OPT } mode = FG; - if (argc > 1) { - if (std::string(argv[1]) == "nc") mode = NC; - else if (std::string(argv[1]) == "opt") mode = OPT; - } - - size_t l2_size = getL2CacheSize(); - std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; - - // Use 1/2 L2 size for fine-grained (smaller to keep coherence overhead dominant) - // Use 2x L2 for NC (larger to generate streaming traffic) - size_t buffer_bytes; - if (mode == NC) { - buffer_bytes = l2_size * 2; - } else { - buffer_bytes = l2_size / 2; - } - long long N = (long long)(buffer_bytes / sizeof(float)); - - float* d_data = nullptr; - - if (mode == FG) { - // Coherent host memory: GPU accesses go through IO/IF path - // generating UC or CC requests in TCC - CHECK_HIP(hipHostMalloc(&d_data, buffer_bytes, hipHostMallocCoherent)); - } else if (mode == NC) { - // Try fine-grained device allocation first for NC traffic - hipError_t err = hipExtMallocWithFlags((void**)&d_data, buffer_bytes, - hipDeviceMallocFinegrained); - if (err != hipSuccess) { - std::cerr << "Warning: hipExtMallocWithFlags(Finegrained) failed: " - << hipGetErrorString(err) << "\n" - << "Falling back to hipMalloc with nontemporal stores\n"; - CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); - } - } else { - // Coarse-grained for OPT mode - CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); - } - - // Initialize - float* h_data = new float[N]; - for (long long i = 0; i < N; i++) h_data[i] = (float)(i % 1000) * 0.001f; - CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - dim3 block(64); - dim3 grid(4096); - - const char* mode_str = (mode == FG) ? "Fine-grained (CC)" : - (mode == NC) ? "Non-temporal (NC)" : "Optimized (coarse)"; - std::cout << "Mode: " << mode_str << "\n"; - - auto start = std::chrono::high_resolution_clock::now(); - if (mode == FG) { - coherence_fg_baseline<<>>(d_data, N); - } else if (mode == NC) { - coherence_nc_baseline<<>>(d_data, N); - } else { - coherence_optimized<<>>(d_data, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << "Time: " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - if (mode == FG) { - CHECK_HIP(hipHostFree(d_data)); - } else { - CHECK_HIP(hipFree(d_data)); - } - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_read_bw_stress.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_read_bw_stress.hip deleted file mode 100644 index eead275..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_read_bw_stress.hip +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// L2 HBM Read Bandwidth Stress -// -// Target metrics (tables 3007, 3008, 3009): -// - L2-EA read credit stall (HBM) [TCC_EA0_RDREQ_DRAM_CREDIT_STALL] -// - L2-EA read BW -// - HBM read fraction -// - L2 Memory BW Bound - Read Credit Pressure -// - L2 hit rate (expect low on baseline) -// - L2 to Memory Traffic Ratio (expect high on baseline) -// - L2 Cache Efficiency (expect low on baseline) -// -// Mechanism: -// Baseline: Streaming reads over a buffer 4x L2 size. Each thread reads -// from sequential cachelines with no reuse. Working set far exceeds L2, -// so nearly every access is an L2 miss -> EA read -> HBM. This saturates -// the L2-to-HBM read path and exhausts DRAM read credits. -// -// Optimized: Reads only a small tile that fits in L2. High hit rate, -// zero credit stalls. -// -// Build: hipcc -g l2_hbm_read_bw_stress.hip -o l2_hbm_read_bw_stress -// Run: ./l2_hbm_read_bw_stress (baseline) -// ./l2_hbm_read_bw_stress opt (optimized) - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -size_t getL2CacheSize() { - int val = 0; - CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); - if (val > 0) return (size_t)val; - // Fallback: 256MB (MI350 default per XCD) - return 256ULL * 1024 * 1024; -} - -// Baseline: Streaming read over buffer >> L2 size. -// Each thread reads multiple cachelines spread across the full buffer, -// ensuring no temporal reuse in L2. The read-only pattern maximizes -// EA read requests to HBM and starves DRAM read credits. -__global__ void hbm_read_stress_baseline(const float* __restrict__ data, - float* __restrict__ out, - long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - float acc = 0.0f; - // Stride through entire buffer. Each iteration touches a new cacheline. - // 16 floats = 64 bytes = 1 cacheline - for (long long i = tid * 16; i < N; i += stride * 16) { - acc += data[i]; - } - - // Prevent compiler from optimizing away reads - if (tid == 0) out[0] = acc; -} - -// Optimized: Read only a small region that fits comfortably in L2. -// Repeatedly access the same data -> high L2 hit rate, no EA traffic. -__global__ void hbm_read_stress_optimized(const float* __restrict__ data, - float* __restrict__ out, - long long N, long long tile_elems) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - float acc = 0.0f; - // Read within the tile repeatedly to get L2 hits - for (int rep = 0; rep < 64; rep++) { - for (long long i = tid * 16; i < tile_elems; i += stride * 16) { - acc += data[i]; - } - } - - if (tid == 0) out[0] = acc; -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - size_t l2_size = getL2CacheSize(); - std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; - - // Buffer = 4x L2 size to guarantee streaming misses - size_t buffer_bytes = l2_size * 4; - long long N = (long long)(buffer_bytes / sizeof(float)); - - // Tile for optimized = 1/4 of L2 (fits comfortably) - long long tile_elems = (long long)(l2_size / (4 * sizeof(float))); - - float* d_data; - float* d_out; - hipError_t alloc_err = hipMalloc(&d_data, buffer_bytes); - if (alloc_err != hipSuccess) { - // Fallback to 2x L2 - buffer_bytes = l2_size * 2; - N = (long long)(buffer_bytes / sizeof(float)); - CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); - } - CHECK_HIP(hipMalloc(&d_out, sizeof(float))); - - // Initialize with host data (avoid hipMemset which uses fill kernels) - float* h_data = new float[N]; - for (long long i = 0; i < N; i++) h_data[i] = (float)(i % 1000) * 0.001f; - CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - // Launch config: block=64 (Wave64), many blocks for maximum L2 pressure - dim3 block(64); - dim3 grid(4096); - - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - hbm_read_stress_optimized<<>>(d_data, d_out, N, tile_elems); - } else { - hbm_read_stress_baseline<<>>(d_data, d_out, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_data)); - CHECK_HIP(hipFree(d_out)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_write_bw_stress.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_write_bw_stress.hip deleted file mode 100644 index f2ab3ef..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_hbm_write_bw_stress.hip +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// L2 HBM Write Bandwidth Stress -// -// Target metrics (tables 3007, 3008, 3009): -// - L2-EA write credit stall (HBM) [TCC_EA0_WRREQ_DRAM_CREDIT_STALL] -// - L2-EA write stall rate [TCC_EA0_WRREQ_STALL] -// - L2-EA too many writes stall [TCC_TOO_MANY_EA_WRREQS_STALL] -// - L2 write data FIFO full rate [TCC_SRC_FIFO_FULL] -// - L2-EA write and atomic BW -// - HBM write fraction -// - L2 Memory BW Bound - Write Credit Pressure -// - L2 writeback rate (expect high on baseline) -// -// Mechanism: -// Baseline: Streaming writes over a buffer 4x L2 size. Every thread writes -// to a unique cacheline (no reuse). This forces: -// 1. L2 miss on write -> allocate cacheline -// 2. Dirty cacheline evicted when replaced -> writeback to HBM -// 3. HBM write request rate at maximum -> write credits exhausted -// 4. Source data FIFO fills up (write data waiting to be sent to EA) -// 5. TOO_MANY_EA_WRREQS_STALL when max pending EA writes reached -// -// Optimized: Writes to a small buffer that fits in L2. Overwrites same -// cachelines repeatedly -> minimal EA write traffic. -// -// Build: hipcc -g l2_hbm_write_bw_stress.hip -o l2_hbm_write_bw_stress -// Run: ./l2_hbm_write_bw_stress (baseline) -// ./l2_hbm_write_bw_stress opt (optimized) - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -size_t getL2CacheSize() { - int val = 0; - CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); - if (val > 0) return (size_t)val; - return 256ULL * 1024 * 1024; -} - -// Baseline: Streaming write over buffer >> L2 size. -// Each thread writes to unique cachelines across the full buffer. -// Write-only pattern: every write allocates a new cacheline, and -// evicted dirty lines are written back to HBM, maximizing EA write traffic. -__global__ void hbm_write_stress_baseline(float* __restrict__ data, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - // Write 1 float per cacheline (16 floats = 64B cacheline). - // This ensures each write touches a distinct cacheline. - for (long long i = tid * 16; i < N; i += stride * 16) { - data[i] = (float)i; - } -} - -// Optimized: Write to a small region that fits in L2. -// Same cachelines are overwritten repeatedly -> dirty lines stay in L2, -// minimal writeback to HBM. -__global__ void hbm_write_stress_optimized(float* __restrict__ data, long long N, - long long tile_elems) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - for (int rep = 0; rep < 64; rep++) { - for (long long i = tid * 16; i < tile_elems; i += stride * 16) { - data[i] = (float)(i + rep); - } - } -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - size_t l2_size = getL2CacheSize(); - std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; - - size_t buffer_bytes = l2_size * 4; - long long N = (long long)(buffer_bytes / sizeof(float)); - long long tile_elems = (long long)(l2_size / (4 * sizeof(float))); - - float* d_data; - hipError_t alloc_err = hipMalloc(&d_data, buffer_bytes); - if (alloc_err != hipSuccess) { - buffer_bytes = l2_size * 2; - N = (long long)(buffer_bytes / sizeof(float)); - CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); - } - - // Initialize (avoid hipMemset) - float* h_data = new float[N]; - for (long long i = 0; i < N; i++) h_data[i] = 0.0f; - CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - dim3 block(64); - dim3 grid(4096); - - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - hbm_write_stress_optimized<<>>(d_data, N, tile_elems); - } else { - hbm_write_stress_baseline<<>>(d_data, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_data)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_io_stress.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_io_stress.hip deleted file mode 100644 index e756da8..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_io_stress.hip +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// L2 IO (PCIe) Credit Stall Stress -// -// Target metrics (tables 3007, 3009, 3010): -// - L2-EA read credit stall (IO) [TCC_EA0_RDREQ_IO_CREDIT_STALL] -// - L2-EA write credit stall (IO) [TCC_EA0_WRREQ_IO_CREDIT_STALL] -// - Normalized L2-EA stalls (IO) (table 3010) -// -// Mechanism: -// Baseline: GPU kernel accesses host-pinned memory (hipHostMalloc). -// Traffic goes over PCIe (or xGMI to host) which is much slower than -// local HBM. The IO interface has limited credits, and sustained -// access from many CUs exhausts them quickly. -// -// mode=read: Streaming reads from host-pinned memory -// mode=write: Streaming writes to host-pinned memory -// (default): Read+write (interleaved) -// -// Optimized: Same kernel but on device-local memory (hipMalloc). -// All traffic stays on HBM, IO credits are not consumed. -// -// Build: hipcc -g l2_io_stress.hip -o l2_io_stress -// Run: ./l2_io_stress (baseline: host-pinned R+W) -// ./l2_io_stress read (baseline: host-pinned read) -// ./l2_io_stress write (baseline: host-pinned write) -// ./l2_io_stress opt (optimized: device-local) - -#include -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -// Streaming read from host memory -> IO read credit stalls -__global__ void io_read_kernel(const float* __restrict__ data, - float* __restrict__ out, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - float acc = 0.0f; - for (long long i = tid * 16; i < N; i += stride * 16) { - acc += data[i]; - } - if (tid == 0) out[0] = acc; -} - -// Streaming write to host memory -> IO write credit stalls -__global__ void io_write_kernel(float* __restrict__ data, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - for (long long i = tid * 16; i < N; i += stride * 16) { - data[i] = (float)i; - } -} - -// Interleaved R+W on host memory -> both IO credit types -__global__ void io_rw_kernel(float* __restrict__ data, - float* __restrict__ out, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - float acc = 0.0f; - for (long long i = tid * 16; i < N; i += stride * 16) { - float val = data[i]; - acc += val; - data[i] = val + 1.0f; - } - if (tid == 0) out[0] = acc; -} - -int main(int argc, char** argv) { - enum Mode { RW, READ, WRITE, OPT } mode = RW; - if (argc > 1) { - if (std::string(argv[1]) == "read") mode = READ; - else if (std::string(argv[1]) == "write") mode = WRITE; - else if (std::string(argv[1]) == "opt") mode = OPT; - } - - // 1GB host-pinned buffer (large enough to saturate IO path) - size_t buffer_bytes = 1ULL * 1024 * 1024 * 1024; - long long N = (long long)(buffer_bytes / sizeof(float)); - - float* data = nullptr; - float* d_out = nullptr; - - if (mode == OPT) { - // Device-local memory: no IO traffic - hipError_t err = hipMalloc(&data, buffer_bytes); - if (err != hipSuccess) { - buffer_bytes = 512ULL * 1024 * 1024; - N = (long long)(buffer_bytes / sizeof(float)); - CHECK_HIP(hipMalloc(&data, buffer_bytes)); - } - CHECK_HIP(hipMemset(data, 0, buffer_bytes)); - } else { - // Host-pinned memory: traffic goes over PCIe/IO - hipError_t err = hipHostMalloc(&data, buffer_bytes, hipHostMallocNonCoherent); - if (err != hipSuccess) { - buffer_bytes = 512ULL * 1024 * 1024; - N = (long long)(buffer_bytes / sizeof(float)); - CHECK_HIP(hipHostMalloc(&data, buffer_bytes, hipHostMallocNonCoherent)); - } - memset(data, 0, buffer_bytes); - } - - CHECK_HIP(hipMalloc(&d_out, sizeof(float))); - - dim3 block(64); - dim3 grid(4096); - - const char* mode_str = (mode == READ) ? "Host-pinned Read" : - (mode == WRITE) ? "Host-pinned Write" : - (mode == OPT) ? "Device-local (optimized)" : - "Host-pinned R+W"; - std::cout << "Mode: " << mode_str - << " | Buffer: " << (buffer_bytes / (1024 * 1024)) << " MB\n"; - - auto start = std::chrono::high_resolution_clock::now(); - if (mode == READ) { - io_read_kernel<<>>(data, d_out, N); - } else if (mode == WRITE) { - io_write_kernel<<>>(data, N); - } else { - // RW and OPT use same kernel (different memory backing) - io_rw_kernel<<>>(data, d_out, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << "Time: " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_out)); - if (mode == OPT) { - CHECK_HIP(hipFree(data)); - } else { - CHECK_HIP(hipHostFree(data)); - } - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_multigpu_fabric.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_multigpu_fabric.hip deleted file mode 100644 index 9a727fd..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_multigpu_fabric.hip +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// L2 Multi-GPU Infinity Fabric Stress -// -// ** REQUIRES 2 GPUs ** — see instructions below. -// -// Target metrics (tables 3007, 3009, 3010): -// - L2-EA read credit stall (IF) [TCC_EA0_RDREQ_GMI_CREDIT_STALL] -// - L2-EA write credit stall (IF) [TCC_EA0_WRREQ_GMI_CREDIT_STALL] -// - L2 Remote Access Pressure (IF) (table 3009) -// - Normalized L2-EA stalls (IF) (table 3010) -// -// Mechanism: -// GPU 0 accesses memory allocated on GPU 1 via P2P. The L2 on GPU 0 -// misses, and the EA request goes through the GMI (Infinity Fabric) -// interface to reach GPU 1's HBM. The IF has limited credits, and -// saturating this path exhausts GMI credits. -// -// mode=read: Streaming reads from remote GPU memory -// mode=write: Streaming writes to remote GPU memory -// mode=rw: Interleaved read+write (default) -// -// Build: hipcc -g l2_multigpu_fabric.hip -o l2_multigpu_fabric -// -// Run (profile GPU 0 while it accesses GPU 1's memory): -// # Read from remote: -// rocprof-compute profile -n fabric_read --experimental --membw-analysis --no-roof -- ./l2_multigpu_fabric read -// # Write to remote: -// rocprof-compute profile -n fabric_write --experimental --membw-analysis --no-roof -- ./l2_multigpu_fabric write -// # Read+Write: -// rocprof-compute profile -n fabric_rw --experimental --membw-analysis --no-roof -- ./l2_multigpu_fabric -// -// Analyze: -// rocprof-compute analyze -p - -#include -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -// Streaming read from remote memory -> GMI read credit stalls -__global__ void fabric_read_kernel(const float* __restrict__ remote_data, - float* __restrict__ local_out, - long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - float acc = 0.0f; - for (long long i = tid * 16; i < N; i += stride * 16) { - acc += remote_data[i]; - } - - if (tid == 0) local_out[0] = acc; -} - -// Streaming write to remote memory -> GMI write credit stalls -__global__ void fabric_write_kernel(float* __restrict__ remote_data, - long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - for (long long i = tid * 16; i < N; i += stride * 16) { - remote_data[i] = (float)i; - } -} - -// Interleaved read+write to remote -> both GMI credit types exhausted -__global__ void fabric_rw_kernel(float* __restrict__ remote_data, - float* __restrict__ local_out, - long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - float acc = 0.0f; - for (long long i = tid * 16; i < N; i += stride * 16) { - float val = remote_data[i]; - acc += val; - remote_data[i] = val + 1.0f; - } - - if (tid == 0) local_out[0] = acc; -} - -int main(int argc, char** argv) { - enum Mode { RW, READ, WRITE } mode = RW; - if (argc > 1) { - if (std::string(argv[1]) == "read") mode = READ; - else if (std::string(argv[1]) == "write") mode = WRITE; - } - - // Check GPU count - int ndevices = 0; - CHECK_HIP(hipGetDeviceCount(&ndevices)); - if (ndevices < 2) { - std::cerr << "ERROR: This workload requires >= 2 GPUs. Found " << ndevices << ".\n"; - std::cerr << "Skipping. Run on a multi-GPU system.\n"; - return 0; - } - - int local_gpu = 0; - int remote_gpu = 1; - - // Enable P2P - int can_access = 0; - CHECK_HIP(hipDeviceCanAccessPeer(&can_access, local_gpu, remote_gpu)); - if (!can_access) { - std::cerr << "ERROR: GPU " << local_gpu << " cannot access GPU " << remote_gpu << " via P2P.\n"; - return 1; - } - CHECK_HIP(hipSetDevice(local_gpu)); - CHECK_HIP(hipDeviceEnablePeerAccess(remote_gpu, 0)); - - // Allocate on remote GPU - size_t buffer_bytes = 2ULL * 1024 * 1024 * 1024; // 2GB - long long N = (long long)(buffer_bytes / sizeof(float)); - - CHECK_HIP(hipSetDevice(remote_gpu)); - float* d_remote; - hipError_t alloc_err = hipMalloc(&d_remote, buffer_bytes); - if (alloc_err != hipSuccess) { - buffer_bytes = 1ULL * 1024 * 1024 * 1024; // Fallback 1GB - N = (long long)(buffer_bytes / sizeof(float)); - CHECK_HIP(hipMalloc(&d_remote, buffer_bytes)); - } - // Initialize remote data - CHECK_HIP(hipMemset(d_remote, 0, buffer_bytes)); - - // Local output buffer on GPU 0 - CHECK_HIP(hipSetDevice(local_gpu)); - float* d_local_out; - CHECK_HIP(hipMalloc(&d_local_out, sizeof(float))); - - dim3 block(64); - dim3 grid(4096); - - const char* mode_str = (mode == READ) ? "Read" : - (mode == WRITE) ? "Write" : "Read+Write"; - std::cout << "Mode: " << mode_str - << " | Buffer: " << (buffer_bytes / (1024 * 1024)) << " MB" - << " | GPU " << local_gpu << " -> GPU " << remote_gpu << "\n"; - - auto start = std::chrono::high_resolution_clock::now(); - if (mode == READ) { - fabric_read_kernel<<>>(d_remote, d_local_out, N); - } else if (mode == WRITE) { - fabric_write_kernel<<>>(d_remote, N); - } else { - fabric_rw_kernel<<>>(d_remote, d_local_out, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << "Time: " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_local_out)); - CHECK_HIP(hipSetDevice(remote_gpu)); - CHECK_HIP(hipFree(d_remote)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_normalized_throughput.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_normalized_throughput.hip deleted file mode 100644 index e9eb7b0..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/l2/l2_normalized_throughput.hip +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// L2 Normalized Throughput & Combined Stall Stress -// -// Target metrics (tables 3008, 3009, 3010): -// - All Normalized L2 Stall Metrics (% of GRBM_GUI_ACTIVE) -// - All Normalized L2 Throughput Metrics (requests/s) -// - L2 Memory BW Bound - Combined Credit Pressure -// - L2-EA read BW, L2-EA write and atomic BW -// -// Mechanism: -// Baseline: Mixed read+write streaming kernel over buffer 4x L2. -// Purely memory-bound (minimal compute), so GRBM_GUI_ACTIVE closely -// tracks memory stall time. Alternating reads and writes at cacheline -// granularity maximizes both EA read and write traffic simultaneously, -// which is the worst case for combined DRAM credit pressure. -// -// Optimized: Compute-heavy kernel with minimal memory traffic. -// Lots of FMA operations on register values -> high GRBM_GUI_ACTIVE -// but near-zero L2 traffic. Normalized stalls will be very low -// because the denominator (kernel time) is large while stall -// numerators are near zero. -// -// Build: hipcc -g l2_normalized_throughput.hip -o l2_normalized_throughput -// Run: ./l2_normalized_throughput (baseline: memory-bound) -// ./l2_normalized_throughput opt (optimized: compute-bound) - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -size_t getL2CacheSize() { - int val = 0; - CHECK_HIP(hipDeviceGetAttribute(&val, hipDeviceAttributeL2CacheSize, 0)); - if (val > 0) return (size_t)val; - return 256ULL * 1024 * 1024; -} - -// Baseline: Mixed read+write streaming, purely memory-bound. -// Alternates reading one cacheline and writing the next, across the -// entire buffer. No compute to hide latency -> memory-bound. -// This maximizes both TCC_READ and TCC_WRITE simultaneously, -// creating combined DRAM credit pressure on both read and write paths. -__global__ void normalized_stress_baseline(float* __restrict__ data, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - long long stride = (long long)blockDim.x * gridDim.x; - - float acc = 0.0f; - // Each iteration: read from cacheline i, write to cacheline i+1 - // Stride by 32 floats (2 cachelines) per step - for (long long i = tid * 32; i < N - 16; i += stride * 32) { - // Read cacheline - acc += data[i]; - // Write next cacheline - data[i + 16] = acc; - } - - // Prevent optimization - if (acc == -999.0f) data[0] = acc; -} - -// Optimized: Compute-heavy, minimal memory. -// Reads a single float, does hundreds of FMAs in registers, writes back. -// GRBM_GUI_ACTIVE is high (kernel runs long), but L2 traffic is negligible. -// Result: normalized stall metrics are near zero. -__global__ void normalized_stress_optimized(float* __restrict__ data, long long N) { - long long tid = (long long)blockIdx.x * blockDim.x + threadIdx.x; - - // Single memory read - float val = data[tid % N]; - - // Pure compute: 1024 FMA iterations - float a = val, b = 1.0001f, c = 0.9999f; - for (int i = 0; i < 1024; i++) { - a = a * b + c; - b = b * c + a; - c = c * a + b; - } - - // Single memory write - if (tid < N) data[tid] = a + b + c; -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - size_t l2_size = getL2CacheSize(); - std::cout << "L2 cache size: " << (l2_size / (1024 * 1024)) << " MB\n"; - - size_t buffer_bytes = l2_size * 4; - long long N = (long long)(buffer_bytes / sizeof(float)); - - float* d_data; - hipError_t alloc_err = hipMalloc(&d_data, buffer_bytes); - if (alloc_err != hipSuccess) { - buffer_bytes = l2_size * 2; - N = (long long)(buffer_bytes / sizeof(float)); - CHECK_HIP(hipMalloc(&d_data, buffer_bytes)); - } - - // Initialize - float* h_data = new float[N]; - for (long long i = 0; i < N; i++) h_data[i] = (float)(i % 1000) * 0.001f; - CHECK_HIP(hipMemcpy(d_data, h_data, buffer_bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - dim3 block(64); - dim3 grid(4096); - - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - normalized_stress_optimized<<>>(d_data, N); - } else { - normalized_stress_baseline<<>>(d_data, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized (compute-bound)" : "Baseline (memory-bound)") << ": " - << std::chrono::duration_cast(end - start).count() - << " ms\n"; - - CHECK_HIP(hipFree(d_data)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/ta_tcp_stall.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/ta_tcp_stall.hip deleted file mode 100644 index 943cffb..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/ta_tcp_stall.hip +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -// Baseline: Random access, TA waits for TCP -__global__ void ta_tcp_stall_baseline(float* data, int N) { - int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= N) return; - - float sum = 0; - for (int i = 0; i < 32; i++) { - long long rand_idx = ((long long)idx * 7919LL + (long long)i * 6547LL) % N; - sum += data[rand_idx]; - } - data[idx] = sum; -} - -// Optimized: Sequential access, TCP serves TA quickly -__global__ void ta_tcp_stall_optimized(float* data, int N) { - // Each thread handles 32 elements - int base = (blockIdx.x * blockDim.x + threadIdx.x) * 32; - if (base + 31 >= N) return; - - float sum = 0; - for (int i = 0; i < 32; i++) { - sum += data[base + i]; - } - data[base] = sum; -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - const int N = 128 * 1024 * 1024; - const size_t bytes = N * sizeof(float); - - float* d_data; - CHECK_HIP(hipMalloc(&d_data, bytes)); - - float* h_data = new float[N]; - for (int i = 0; i < N; i++) h_data[i] = i * 0.001f; - CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - dim3 block(256); - - if (use_optimized) { - int num_blocks = (N / 32 + block.x - 1) / block.x; - dim3 grid(num_blocks); - hipLaunchKernelGGL(ta_tcp_stall_optimized, grid, block, 0, 0, d_data, N); - } else { - dim3 grid((N + block.x - 1) / block.x); - hipLaunchKernelGGL(ta_tcp_stall_baseline, grid, block, 0, 0, d_data, N); - } - CHECK_HIP(hipDeviceSynchronize()); - - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - int num_blocks = (N / 32 + block.x - 1) / block.x; - dim3 grid(num_blocks); - hipLaunchKernelGGL(ta_tcp_stall_optimized, grid, block, 0, 0, d_data, N); - } else { - dim3 grid((N + block.x - 1) / block.x); - hipLaunchKernelGGL(ta_tcp_stall_baseline, grid, block, 0, 0, d_data, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() << " ms\n"; - - CHECK_HIP(hipFree(d_data)); - return 0; -} diff --git a/skills/rocprof-compute-sample/membw_analysis_test_suite/utcl1_stall.hip b/skills/rocprof-compute-sample/membw_analysis_test_suite/utcl1_stall.hip deleted file mode 100644 index 5f0f9c2..0000000 --- a/skills/rocprof-compute-sample/membw_analysis_test_suite/utcl1_stall.hip +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -/* - * UTCL1 Stall Workload - Targets TCP_UTCL1_STALL_INFLIGHT_MAX / tcp_busy >= 10% - * - */ - -#include -#include -#include - -#define CHECK_HIP(call) \ - do { \ - hipError_t err = call; \ - if (err != hipSuccess) { \ - fprintf(stderr, "HIP error at %s:%d - %s\n", __FILE__, __LINE__, hipGetErrorString(err)); \ - fflush(stderr); \ - exit(1); \ - } \ - } while(0) - -constexpr int PAGE_FLOATS = 4096 / sizeof(float); // 1024 floats per 4KB page - -// Baseline: Rapid page hopping - exceed UTCL1 entries, trigger INFLIGHT stall -__global__ void utcl1_stall_baseline(float* data, int N) { - int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= N) return; - - int lane = threadIdx.x & 63; // Wave64 - - float acc = 0.0f; - for (int i = 0; i < 512; i++) { - unsigned long long page_offset = (unsigned long long)lane * PAGE_FLOATS - + (unsigned long long)i * 64 * PAGE_FLOATS; - int access_idx = (int)(((unsigned long long)idx + page_offset) % (unsigned long long)N); - acc += data[access_idx]; - } - data[idx] = acc; -} - -// Optimized: All accesses within same page -__global__ void utcl1_stall_optimized(float* data, int N) { - int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx >= N) return; - - int page_base = (idx / PAGE_FLOATS) * PAGE_FLOATS; - - float sum = 0.0f; - for (int i = 0; i < 512; i++) { - sum += data[page_base + (i % PAGE_FLOATS)]; - } - data[idx] = sum; -} - -int main(int argc, char** argv) { - bool use_optimized = (argc > 1 && std::string(argv[1]) == "opt"); - - const int N = 256 * 1024 * 1024; // 1GB - 256K pages - const size_t bytes = N * sizeof(float); - - float* d_data; - CHECK_HIP(hipMalloc(&d_data, bytes)); - - float* h_data = new float[N]; - for (int i = 0; i < N; i++) h_data[i] = i * 0.001f; - CHECK_HIP(hipMemcpy(d_data, h_data, bytes, hipMemcpyHostToDevice)); - delete[] h_data; - - dim3 block(64); - dim3 grid((N + block.x - 1) / block.x); - - auto start = std::chrono::high_resolution_clock::now(); - if (use_optimized) { - hipLaunchKernelGGL(utcl1_stall_optimized, grid, block, 0, 0, d_data, N); - } else { - hipLaunchKernelGGL(utcl1_stall_baseline, grid, block, 0, 0, d_data, N); - } - CHECK_HIP(hipDeviceSynchronize()); - auto end = std::chrono::high_resolution_clock::now(); - - std::cout << (use_optimized ? "Optimized" : "Baseline") << ": " - << std::chrono::duration_cast(end - start).count() << " ms\n"; - - CHECK_HIP(hipFree(d_data)); - return 0; -} diff --git a/skills/rocprof-compute-sample/memcopy.cpp b/skills/rocprof-compute-sample/memcopy.cpp deleted file mode 100644 index cb68bb5..0000000 --- a/skills/rocprof-compute-sample/memcopy.cpp +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -#include -#include -#include - -// Simple memory copy kernel - one thread per element -__global__ void memoryCopyKernel(const double* __restrict__ X, - double* __restrict__ Y, - size_t N) { - size_t idx = blockIdx.x * blockDim.x + threadIdx.x; - - if (idx < N) { - Y[idx] = X[idx]; - } -} - -int main() { - const size_t N = 268435456; // Number of elements - const size_t bytes = N * sizeof(double); - - // Print array size in GiB - double sizeGiB = bytes / (1024.0 * 1024.0 * 1024.0); - std::cout << "Array size: " << std::fixed << std::setprecision(2) - << sizeGiB << " GiB (" << bytes << " bytes)" << std::endl; - std::cout << "Number of elements: " << N << std::endl; - std::cout << std::string(60, '-') << std::endl; - - // Allocate device memory - double *d_X, *d_Y; - hipMalloc(&d_X, bytes); - hipMalloc(&d_Y, bytes); - - // Allocate and initialize host memory - double *h_X = new double[N]; - for (size_t i = 0; i < N; i++) { - h_X[i] = static_cast(i); - } - - // Copy data to device - hipMemcpy(d_X, h_X, bytes, hipMemcpyHostToDevice); - - // Launch configuration - int blockSize = 256; - int gridSize = (N + blockSize - 1) / blockSize; - - std::cout << "Launch configuration:" << std::endl; - std::cout << " Block size: " << blockSize << std::endl; - std::cout << " Grid size: " << gridSize << std::endl; - std::cout << std::string(60, '-') << std::endl; - - // Warmup run - memoryCopyKernel<<>>(d_X, d_Y, N); - hipDeviceSynchronize(); - - // Timed runs - const int numRuns = 1000; - hipEvent_t start, stop; - hipEventCreate(&start); - hipEventCreate(&stop); - - float totalTime = 0.0f; - - for (int run = 0; run < numRuns; run++) { - hipEventRecord(start); - - memoryCopyKernel<<>>(d_X, d_Y, N); - - hipEventRecord(stop); - hipEventSynchronize(stop); - - float milliseconds = 0; - hipEventElapsedTime(&milliseconds, start, stop); - totalTime += milliseconds; - } - - float avgTime = totalTime / numRuns; - - // Calculate bandwidth - // Memory copy reads N elements and writes N elements = 2*N*sizeof(double) bytes - double totalBytesTransferred = 2.0 * N * sizeof(double); - double bandwidthGBps = (totalBytesTransferred / (avgTime / 1000.0)) / (1024.0 * 1024.0 * 1024.0); - - std::cout << "Performance Results (averaged over " << numRuns << " runs):" << std::endl; - std::cout << " Kernel execution time: " << std::fixed << std::setprecision(3) - << avgTime << " ms" << std::endl; - std::cout << " Data transferred: " << std::setprecision(2) - << (totalBytesTransferred / (1024.0 * 1024.0 * 1024.0)) << " GiB" << std::endl; - std::cout << " Achieved bandwidth: " << std::setprecision(2) - << bandwidthGBps << " GiB/s" << std::endl; - std::cout << std::string(60, '-') << std::endl; - - // Verify correctness - double *h_Y = new double[N]; - hipMemcpy(h_Y, d_Y, bytes, hipMemcpyDeviceToHost); - - bool correct = true; - for (size_t i = 0; i < N; i++) { - if (h_Y[i] != h_X[i]) { - correct = false; - std::cout << "Mismatch at index " << i << ": " - << h_Y[i] << " != " << h_X[i] << std::endl; - break; - } - } - - if (correct) { - std::cout << "✓ Verification PASSED - all elements copied correctly" << std::endl; - } else { - std::cout << "✗ Verification FAILED" << std::endl; - } - - std::cout << "\nFirst 5 elements:" << std::endl; - for (int i = 0; i < 5; i++) { - std::cout << " Y[" << i << "] = " << h_Y[i] << std::endl; - } - - // Cleanup - hipEventDestroy(start); - hipEventDestroy(stop); - delete[] h_X; - delete[] h_Y; - hipFree(d_X); - hipFree(d_Y); - - return 0; -} diff --git a/skills/rocprof-compute-sample/rocflop.cpp b/skills/rocprof-compute-sample/rocflop.cpp deleted file mode 100644 index 4e638a1..0000000 --- a/skills/rocprof-compute-sample/rocflop.cpp +++ /dev/null @@ -1,742 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -// Copied from https://github.com/benrichard-amd/rocflop/tree/82f197e12314bab694fc70451a2b495b4f51bf90 - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using float16 = _Float16; - -// Vector types. Useful for packed math (where supported) and MFMA inputs. -template -using vecT = T __attribute__((ext_vector_type(Rank))); - -template using vec4 = vecT; -template using vec8 = vecT; - - -// Kernels - - -template __global__ void fma_throughput(vec4* buffer, int count) -{ - const T k = 1.0; - - const int grid_size = gridDim.x * blockDim.x; - const int tid = blockDim.x * blockIdx.x + threadIdx.x; - - vec4* ptr = buffer; - - vec4 value0 = ptr[0 * grid_size + tid]; - vec4 value1 = ptr[1 * grid_size + tid]; - vec4 value2 = ptr[2 * grid_size + tid]; - vec4 value3 = ptr[3 * grid_size + tid]; - - for(int j = 0; j < count; j++) { - for(int j = 0; j < 64; j++) { - - // 16 FMA ops - value0 = value0 * value0 + k; - value1 = value1 * value1 + k; - value2 = value2 * value2 + k; - value3 = value3 * value3 + k; - } - } - - ptr[tid] = value0 + value1 + value2 + value3; -} - -// MFMA requires CDNA's mai-insts target feature; kernel bodies are empty on -// targets without the builtin and are never launched (run_tests gates on a -// runtime has_mfma check). -__global__ void matmul_fp16_throughput(vec4* inputs, vec4* outputs, int count) -{ -#if __has_builtin(__builtin_amdgcn_mfma_f32_16x16x16f16) - int grid_size = gridDim.x * blockDim.x; - int tid = blockDim.x * blockIdx.x + threadIdx.x; - - vec4* ptr = inputs; - - vec4 value0 = ptr[0 * grid_size + tid]; - vec4 value1 = ptr[1 * grid_size + tid]; - vec4 value2 = ptr[2 * grid_size + tid]; - vec4 value3 = ptr[3 * grid_size + tid]; - - vec4 accum0; - vec4 accum1; - vec4 accum2; - vec4 accum3; - for(int i = 0; i < count; i++) { - for(int j = 0; j < 64; j++) { - // 4 MFMA ops - accum0 = __builtin_amdgcn_mfma_f32_16x16x16f16(value0, value0, accum0, 0, 0, 0); - accum1 = __builtin_amdgcn_mfma_f32_16x16x16f16(value1, value1, accum1, 0, 0, 0); - accum2 = __builtin_amdgcn_mfma_f32_16x16x16f16(value2, value2, accum2, 0, 0, 0); - accum3 = __builtin_amdgcn_mfma_f32_16x16x16f16(value3, value3, accum3, 0, 0, 0); - } - } - - outputs[tid] = accum0 + accum1 + accum2 + accum3; -#endif -} - -__global__ void matmul_fp32_throughput(float* inputs, vec4* outputs, int count) -{ -#if __has_builtin(__builtin_amdgcn_mfma_f32_16x16x4f32) - int grid_size = gridDim.x * blockDim.x; - int tid = blockDim.x * blockIdx.x + threadIdx.x; - - float* ptr = inputs; - - float value0 = ptr[0 * grid_size + tid]; - float value1 = ptr[1 * grid_size + tid]; - float value2 = ptr[2 * grid_size + tid]; - float value3 = ptr[2 * grid_size + tid]; - - vec4 accum0; - vec4 accum1; - vec4 accum2; - vec4 accum3; - for(int i = 0; i < count; i++) { - for(int j = 0; j < 64; j++) { - // 4 MFMA ops - accum0 = __builtin_amdgcn_mfma_f32_16x16x4f32(value0, value0, accum0, 0, 0, 0); - accum1 = __builtin_amdgcn_mfma_f32_16x16x4f32(value1, value1, accum1, 0, 0, 0); - accum2 = __builtin_amdgcn_mfma_f32_16x16x4f32(value2, value2, accum2, 0, 0, 0); - accum3 = __builtin_amdgcn_mfma_f32_16x16x4f32(value3, value3, accum3, 0, 0, 0); - } - } - - outputs[tid] = accum0 + accum1 + accum2 + accum3; -#endif -} - -// SMFMAC requires CDNA3+'s gfx940-insts target feature; kernel body is empty -// on targets without the builtin and is never launched (run_tests gates on a -// runtime arch.major/minor check). -__global__ void sparse_matmul_fp16_throughput(vec4* input0, vec8* input1, vec4* outputs, int count) -{ -#if __has_builtin(__builtin_amdgcn_smfmac_f32_16x16x32_f16) - int grid_size = gridDim.x * blockDim.x; - int tid = blockDim.x * blockIdx.x + threadIdx.x; - - vec4* x_ptr = input0; - vec8* y_ptr = input1; - - vec4 x0 = x_ptr[0 * grid_size + tid]; - vec4 x1 = x_ptr[1 * grid_size + tid]; - vec4 x2 = x_ptr[2 * grid_size + tid]; - vec4 x3 = x_ptr[3 * grid_size + tid]; - - vec8 y0 = y_ptr[0 * grid_size + tid]; - vec8 y1 = y_ptr[1 * grid_size + tid]; - vec8 y2 = y_ptr[2 * grid_size + tid]; - vec8 y3 = y_ptr[3 * grid_size + tid]; - - vec4 accum0; - vec4 accum1; - vec4 accum2; - vec4 accum3; - - for(int i = 0; i < count; i++) { - for(int j = 0; j < 64; j++) { - // 4 SMFMAC ops - accum0 = __builtin_amdgcn_smfmac_f32_16x16x32_f16(x0, y0, accum0, 0, 0, 0); - accum1 = __builtin_amdgcn_smfmac_f32_16x16x32_f16(x1, y1, accum1, 0, 0, 0); - accum2 = __builtin_amdgcn_smfmac_f32_16x16x32_f16(x2, y2, accum2, 0, 0, 0); - accum3 = __builtin_amdgcn_smfmac_f32_16x16x32_f16(x3, y3, accum3, 0, 0, 0); - } - } - - outputs[tid] = accum0 + accum1 + accum2 + accum3; -#endif -} - -int g_current_device = -1; - -void HIP_CALL(hipError_t err) -{ - if(err != hipSuccess) { - if(g_current_device >= 0) { - std::cout << "[GPU " << g_current_device << "] "; - } - std::cout << "HIP Error: " << (int)err << " " << hipGetErrorString(err) << std::endl; - exit(1); - } -} - -struct GCNArch { - int major; - int minor; - int rev; -}; - -GCNArch get_gcn_arch(int device) -{ - hipDeviceProp_t props; - - HIP_CALL(hipGetDeviceProperties(&props, device)); - - // Example: gfx908:sramecc+:xnack- - std::string arch_full(props.gcnArchName); - - // Extract number e.g. "908" - std::string gfx_str = arch_full.substr(3, arch_full.find_first_of(':')); - - int gfx_num = std::stoi(gfx_str, nullptr, 16); - - GCNArch arch; - arch.major = (gfx_num & 0xff00) >> 8; - arch.minor = (gfx_num & 0x00f0) >> 4; - arch.rev = (gfx_num & 0x000f); - - return arch; -} - -enum : uint32_t { - VALU_FP32 = 1 << 0, - VALU_FP16 = 1 << 1, - VALU_FP64 = 1 << 2, - MATRIX_FP16 = 1 << 3, - MATRIX_FP32 = 1 << 4, - SMATRIX_FP16 = 1 << 5, - VALU_INT32 = 1 << 6, - - ALL = (uint32_t)-1 -}; - -// Timer for measuring kernel duration -class HIPTimer { - -private: - hipEvent_t m_start; - hipEvent_t m_stop; - -public: - HIPTimer() - { - HIP_CALL(hipEventCreate(&m_start)); - HIP_CALL(hipEventCreate(&m_stop)); - } - - void start() - { - HIP_CALL(hipEventRecord(m_start)); - } - - void stop() - { - HIP_CALL(hipEventRecord(m_stop)); - } - - double elapsed() - { - float ms; - HIP_CALL(hipEventElapsedTime(&ms, m_start, m_stop)); - - return (double)ms / 1000.0; - } -}; - -// Host code - -template double fma_throughput_test(int device, int count, int runs = 1) -{ - vec4* buffer = nullptr; - - hipDeviceProp_t props; - HIP_CALL(hipGetDeviceProperties(&props, device)); - - int blocks = props.multiProcessorCount * 512; - int threads_per_block = 64; - int total_threads = blocks * threads_per_block; - - HIP_CALL(hipMalloc(&buffer, sizeof(vec4) * total_threads * 4)); - - HIPTimer t; - t.start(); - for(int i = 0; i < runs; i++) { - fma_throughput<<>>(buffer, count); - } - t.stop(); - HIP_CALL(hipDeviceSynchronize()); - - double elapsed = t.elapsed(); - double ops = (double)total_threads * count * 64 * 16 * runs; - double flops = (double)ops * 2.0 / elapsed; - - HIP_CALL(hipFree(buffer)); - - return flops; -} - -template double matmul_throughput_test(int device, int count, int runs = 1) -{ - const int wave_size = 64; - int k; - int m; - int n; - - if(std::is_same::value) { - m = 16; - n = 16; - k = 16; - } else if(std::is_same::value) { - m = 16; - n = 16; - k = 4; - } else { - assert(false); - } - - int ops_per_matmul = k * m * n * 2; - - void* buffer = nullptr; - void* accum = nullptr; - - hipDeviceProp_t props; - HIP_CALL(hipGetDeviceProperties(&props, device)); - - int blocks = props.multiProcessorCount * 512; - int threads_per_block = wave_size; - int total_threads = blocks * threads_per_block; - - HIP_CALL(hipMalloc(&buffer, 4 * sizeof(matT) * m * k * total_threads)); - HIP_CALL(hipMalloc(&accum, sizeof(accumT) * m * n * total_threads)); - - HIPTimer t; - t.start(); - for(int i = 0; i < runs; i++) { - if(std::is_same::value && std::is_same::value) { - matmul_fp16_throughput<<>>((vec4*)buffer, (vec4*)accum, count); - } else if(std::is_same::value && std::is_same::value) { - matmul_fp32_throughput<<>>((float*)buffer, (vec4*)accum, count); - } - } - t.stop(); - HIP_CALL(hipDeviceSynchronize()); - - double elapsed = t.elapsed(); - double ops = (double)blocks * count * 64 * 4 * runs; - double flops = (double)ops * ops_per_matmul / elapsed; - - HIP_CALL(hipFree(buffer)); - HIP_CALL(hipFree(accum)); - - return flops; -} - -template double sparse_matmul_throughput_test(int device, int count, int runs = 1) -{ - const int wave_size = 64; - int k; - int m; - int n; - - if(std::is_same::value) { - m = 16; - n = 16; - k = 32; - } else { - assert(false); - } - - int ops_per_matmul = k * m * n * 2; - - void* buffer1 = nullptr; - void* buffer2 = nullptr; - void* accum = nullptr; - - hipDeviceProp_t props; - HIP_CALL(hipGetDeviceProperties(&props, device)); - - int blocks = props.multiProcessorCount * 512; - int threads_per_block = wave_size; - int total_threads = blocks * threads_per_block; - - HIP_CALL(hipMalloc(&buffer1, 4 * sizeof(matT) * m * k * total_threads)); - HIP_CALL(hipMalloc(&buffer2, 8 * sizeof(matT) * n * k * total_threads)); - HIP_CALL(hipMalloc(&accum, sizeof(accumT) * m * n * total_threads)); - - HIPTimer t; - t.start(); - for(int i = 0; i < runs; i++) { - if(std::is_same::value && std::is_same::value) { - sparse_matmul_fp16_throughput<<>>((vec4*)buffer1, - (vec8*)buffer2, (vec4*)accum, count); - } - } - t.stop(); - HIP_CALL(hipDeviceSynchronize()); - - double elapsed = t.elapsed(); - double ops = (double)blocks * count * 64 * 4 * runs; - double flops = (double)ops * ops_per_matmul / elapsed; - - HIP_CALL(hipFree(buffer1)); - HIP_CALL(hipFree(buffer2)); - HIP_CALL(hipFree(accum)); - - return flops; -} - -struct Result { - int device = -1; - double valu_fp16 = 0; - double valu_fp32 = 0; - double valu_fp64 = 0; - double valu_int32 = 0; - double mfma_fp16 = 0; - double mfma_fp32 = 0; - double smfmac_fp16 = 0; - - // Used for sorting - bool operator<(const Result& other) { - return device < other.device; - } -}; - -void print_result(const Result& res, uint32_t mask) -{ - if(mask & VALU_FP16) { - printf("VALU FP16: %8.2f TFLOPS\n", res.valu_fp16 / 1e12); - } - if(mask & VALU_FP32) { - printf("VALU FP32: %8.2f TFLOPS\n", res.valu_fp32 / 1e12); - } - if(mask & VALU_FP64) { - printf("VALU FP64: %8.2f TFLOPS\n", res.valu_fp64 / 1e12); - } - if(mask & VALU_INT32) { - printf("VALU INT32: %8.2f TIOPS\n", res.valu_int32 / 1e12); - } - if(mask & MATRIX_FP16) { - printf("MFMA FP16: %8.2f TFLOPS\n", res.mfma_fp16 / 1e12); - } - if(mask & MATRIX_FP32) { - printf("MFMA FP32: %8.2f TFLOPS\n", res.mfma_fp32 / 1e12); - } - if(mask & SMATRIX_FP16) { - printf("SMFMAC FP16: %8.2f TFLOPS\n", res.smfmac_fp16 / 1e12); - } -} - -Result run_tests(int device, int runs, uint32_t mask) -{ - g_current_device = device; - int device_count; - - HIP_CALL(hipGetDeviceCount(&device_count)); - - if(device >= device_count) { - std::cout << "[GPU " << device << "] Device does not exist. Skipping..." << std::endl; - exit(1); - } - - HIP_CALL(hipSetDevice(device)); - GCNArch arch = get_gcn_arch(device); - - Result res = {.device = device}; - - if(mask & VALU_FP16) { - res.valu_fp16 = fma_throughput_test(device, 4096, runs); - } - - if(mask & VALU_FP32) { - res.valu_fp32 = fma_throughput_test(device, 4096, runs); - } - - if(mask & VALU_FP64) { - res.valu_fp64 = fma_throughput_test(device, 4096, runs); - } - - if(mask & VALU_INT32) { - res.valu_int32 = fma_throughput_test(device, 4096, runs); - } - - // MFMA available on gfx908+ (excludes gfx906 with rev=6); kernel bodies are - // empty on targets without the builtin, so only launch when has_mfma. - bool has_mfma = arch.major == 0x9 && (arch.minor >= 0x4 || (arch.minor == 0 && arch.rev >= 8)); - - if(mask & MATRIX_FP16) { - if(has_mfma) { - res.mfma_fp16 = matmul_throughput_test(device, 4096, runs); - } - } - - if(mask & MATRIX_FP32) { - if(has_mfma) { - res.mfma_fp32 = matmul_throughput_test(device, 4096, runs); - } - } - - // SMFMAC only available on gfx940 (MI300) and later. - if(mask & SMATRIX_FP16) { - if(arch.major == 0x9 && arch.minor >= 0x4) { - res.smfmac_fp16 = sparse_matmul_throughput_test(device, 4096, runs); - } - } - - return res; -} - -// Use fork() followed by exec() to run child process. For some reason -// rocprof does not pick up the child processes when only fork() is -// used. -pid_t fork_process(int device, int runs, uint32_t mask, int fd) -{ - pid_t pid = fork(); - - if(pid != 0) { - return pid; - } - - std::string str_device = std::to_string(device); - std::string str_runs = std::to_string(runs); - std::string str_mask = std::to_string(mask); - std::string str_fd = std::to_string(fd); - - char* const args[] = { - (char*)"CHILD", - (char*)str_device.c_str(), - (char*)str_runs.c_str(), - (char*)str_mask.c_str(), - (char*)str_fd.c_str(), - NULL - }; - - execv("/proc/self/exe", args); - std::cout << "[GPU " << device << "] execv() failed: " << std::strerror(errno) << std::endl; - exit(1); -} - -std::vector read_records_from_pipe(int fd[2], size_t expected_count) -{ - int flags = fcntl(fd[0], F_GETFL, 0); - fcntl(fd[0], F_SETFL, flags | O_NONBLOCK); - - std::vector results(expected_count); - ssize_t bytes_read = read(fd[0], results.data(), results.size() * sizeof(Result)); - - if(bytes_read < 0) { - std::cout << "Error reading results from child process(es): " - << std::strerror(errno) << std::endl; - return {}; - } - - if(bytes_read == 0) { - std::cout << "No results received from child process(es)." << std::endl; - return {}; - } - - if(bytes_read % sizeof(Result) != 0) { - std::cout << "Warning: Incomplete result data received from child process(es); " - << "some data may be ignored." << std::endl; - } - - int count = static_cast(bytes_read / sizeof(Result)); - results.resize(count); - return results; -} - -void run(std::vector& devices, int runs, uint32_t mask) -{ - std::vector pids; - - // We will receive results from the child processes using a pipe - int fd[2]; - - if(pipe(fd)) { - std::cout << std::strerror(errno) << std::endl; - exit(1); - } - - // Start a new process for each GPU - for(auto d : devices) { - pid_t pid = fork_process(d, runs, mask, fd[1]); - - pids.push_back(pid); - } - - // Wait for all processes to finish - int failed = 0; - for(size_t i = 0; i < pids.size(); i++) { - int status; - waitpid(pids[i], &status, 0); - if(WIFEXITED(status) && WEXITSTATUS(status) != 0) { - std::cout << "Child process for device " << devices[i] - << " exited with error (code " << WEXITSTATUS(status) << ")." << std::endl; - failed++; - } else if(WIFSIGNALED(status)) { - std::cout << "Child process for device " << devices[i] - << " killed by signal " << WTERMSIG(status) << "." << std::endl; - failed++; - } - } - - if(failed == (int)pids.size()) { - std::cout << "All " << pids.size() << " child process(es) failed. No results to report." << std::endl; - exit(1); - } - - std::vector results = read_records_from_pipe(fd, pids.size()); - - // Sort results by GPU id - std::sort(results.begin(), results.end()); - - // Print results - for(auto r : results) { - std::cout << std::endl << "GPU " << r.device << std::endl; - print_result(r, mask); - } - - Result total; - for(auto r : results) { - total.valu_fp16 += r.valu_fp16; - total.valu_fp32 += r.valu_fp32; - total.valu_fp64 += r.valu_fp64; - total.valu_int32 += r.valu_int32; - total.mfma_fp16 += r.mfma_fp16; - total.mfma_fp32 += r.mfma_fp32; - total.smfmac_fp16 += r.smfmac_fp16; - } - std::cout << std::endl << "System total" << std::endl; - print_result(total, mask); - - if(failed > 0) { - std::cout << std::endl << failed << " of " << pids.size() - << " child process(es) failed." << std::endl; - exit(1); - } -} - - -void usage() -{ - std::cout << "--device ID Use device with the given numerical ID" << std::endl; - std::cout << "--devices IDS | ALL Comma-separated list of device Ids (e.g., 1,2,3)" << std::endl; - std::cout << " ALL for all devices" << std::endl; - std::cout << "--runs RUNS Number of times each kernel is dispatched" << std::endl; - - std::cout << "--fp16 Run FP16 (VALU) test" << std::endl; - std::cout << "--fp32 Run FP32 (VALU) test" << std::endl; - std::cout << "--fp64 Run FP64 (VALU) test" << std::endl; - std::cout << "--int32 Run INT32 (VALU) test" << std::endl; - std::cout << "--matfp16 Run FP16 (MFMA) test" << std::endl; - std::cout << "--matfp32 Run FP32 (MFMA) test" << std::endl; - std::cout << "--smatfp16 Run FP16 (SMFMAC) test" << std::endl; -} - -int main(int argc, char** argv) -{ - if(std::string(argv[0]) == "CHILD") { - int device = atoi(argv[1]); - int runs = atoi(argv[2]); - uint32_t mask = atoi(argv[3]); - int fd = atoi(argv[4]); - - Result res = run_tests(device, runs, mask); - - write(fd, &res, sizeof(res)); - return 0; - } - - int runs = 1; - - uint32_t mask = 0; - bool all_devices = false; - std::vector devices; - int device_count; - int device = 0; - - HIP_CALL(hipGetDeviceCount(&device_count)); - - int i = 1; - while(i < argc) { - std::string arg = std::string(argv[i]); - - if(arg == "--help") { - usage(); - return 0; - } else if(arg == "--device") { - devices.push_back(atoi(argv[i + 1])); - // Skip next - i++; - } else if(arg == "--devices") { - // Parse comma-separated string of numbers - std::string s(argv[i + 1]); - - if(s == "all" || s == "ALL") { - all_devices = true; - } else { - std::stringstream ss(s); - std::string r; - while(getline(ss, r, ',')) { - devices.push_back(std::stoi(r)); - } - } - // Skip next - i++; - } else if(arg == "--runs") { - runs = atoi(argv[i + 1]); - - // Skip next - i++; - } else if(arg == "--fp32") { - mask |= VALU_FP32; - } else if(arg == "--fp64") { - mask |= VALU_FP64; - } else if(arg == "--fp16") { - mask |= VALU_FP16; - } else if(arg == "--int32") { - mask |= VALU_INT32; - } else if(arg == "--matfp16") { - mask |= MATRIX_FP16; - } else if(arg == "--matfp32") { - mask |= MATRIX_FP32; - } else if(arg == "--smatfp16") { - mask |= SMATRIX_FP16; - } else { - std::cout << "Invalid argument '" << arg << "'" << std::endl; - std::cout << std::endl; - usage(); - return 1; - } - - i++; - } - - if(all_devices) { - for(int i = 0; i < device_count; i++ ){ - devices.push_back(i); - } - } - - // Verify device ID's - for(auto d : devices) { - if(d >= device_count) { - std::cout << "Invalid device ordinal: " << d << std::endl; - return 1; - } - } - - if(devices.size() == 0) { - devices.push_back(0); - } - - if(mask == 0) { - mask = ALL; - } - - run(devices, runs, mask); - - return 0; -} diff --git a/skills/rocprof-compute-sample/stack.hip b/skills/rocprof-compute-sample/stack.hip deleted file mode 100644 index ed8dad6..0000000 --- a/skills/rocprof-compute-sample/stack.hip +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Advanced Micro Devices, Inc. -// SPDX-License-Identifier: MIT - -/* -An example to explore spill/stack instructions. -Written by Nicholas Curtis [AMD]. -*/ - -#include "common.h" - -__global__ void knl(int* out, int filter) { - int x[1024]; - x[filter] = 0; - if (threadIdx.x < filter) out[threadIdx.x] = x[threadIdx.x]; -} - -int main() { - knl<<<1, 1>>>(nullptr, 0); - hipCheck(hipDeviceSynchronize()); -} diff --git a/skills/rocprof-compute-sample/torch_compile_triton.py b/skills/rocprof-compute-sample/torch_compile_triton.py deleted file mode 100644 index 154e51f..0000000 --- a/skills/rocprof-compute-sample/torch_compile_triton.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) Advanced Micro Devices, Inc. -# SPDX-License-Identifier: MIT - -"""Minimal torch.compile workload that generates Triton kernels.""" - -import sys - -import torch - - -@torch.compile -def fused(x, y): - return torch.relu(x) * y + x - - -def main(): - if not torch.cuda.is_available(): - print("GPU is required for this sample. Exiting.") - sys.exit(1) - - x = torch.randn(4096, 4096, device="cuda") - y = torch.randn(4096, 4096, device="cuda") - - # First call compiles; later calls reuse the generated Triton kernels. - for _ in range(3): - fused(x, y) - - torch.cuda.synchronize() - print("Compiled workload completed") - - -if __name__ == "__main__": - main() diff --git a/skills/rocprof-compute-sample/triton_ffn.py b/skills/rocprof-compute-sample/triton_ffn.py deleted file mode 100644 index 63172a2..0000000 --- a/skills/rocprof-compute-sample/triton_ffn.py +++ /dev/null @@ -1,179 +0,0 @@ -# Copyright (c) Advanced Micro Devices, Inc. -# SPDX-License-Identifier: MIT - -""" -Pure-Triton LLaMA-style feed-forward block (RMSNorm + gated MLP). - -Implements one transformer FFN entirely in Triton: - - h = rmsnorm(x) - gate = h @ Wgate - up = h @ Wup - out = x + (silu(gate) * up) @ Wdown - -This exercises five distinct Triton kernels (rmsnorm, matmul, silu, mul, add); -the matmul kernel is launched from three call sites (the gate, up, and down -projections). -""" - -import sys - -import torch -import triton -import triton.language as tl - -BLOCK_M = 64 -BLOCK_N = 64 -BLOCK_K = 64 -ELT_BLOCK = 1024 - - -@triton.jit -def rmsnorm_kernel(x_ptr, w_ptr, out_ptr, n_cols, eps, BLOCK: tl.constexpr): - row = tl.program_id(0) - cols = tl.arange(0, BLOCK) - mask = cols < n_cols - x = tl.load(x_ptr + row * n_cols + cols, mask=mask, other=0.0) - rstd = 1.0 / tl.sqrt(tl.sum(x * x, axis=0) / n_cols + eps) - w = tl.load(w_ptr + cols, mask=mask, other=0.0) - tl.store(out_ptr + row * n_cols + cols, x * rstd * w, mask=mask) - - -@triton.jit -def matmul_kernel( - a_ptr, - b_ptr, - c_ptr, - M, - N, - K, - stride_am, - stride_ak, - stride_bk, - stride_bn, - stride_cm, - stride_cn, - BLOCK_M: tl.constexpr, - BLOCK_N: tl.constexpr, - BLOCK_K: tl.constexpr, -): - pid_m = tl.program_id(0) - pid_n = tl.program_id(1) - offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M) - offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N) - offs_k = tl.arange(0, BLOCK_K) - a_ptrs = a_ptr + (offs_m[:, None] * stride_am + offs_k[None, :] * stride_ak) - b_ptrs = b_ptr + (offs_k[:, None] * stride_bk + offs_n[None, :] * stride_bn) - acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32) - for k in range(0, K, BLOCK_K): - k_mask = offs_k[None, :] < K - k - a = tl.load(a_ptrs, mask=k_mask, other=0.0) - b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k, other=0.0) - acc += tl.dot(a, b) - a_ptrs += BLOCK_K * stride_ak - b_ptrs += BLOCK_K * stride_bk - c_ptrs = c_ptr + offs_m[:, None] * stride_cm + offs_n[None, :] * stride_cn - c_mask = (offs_m[:, None] < M) & (offs_n[None, :] < N) - tl.store(c_ptrs, acc, mask=c_mask) - - -@triton.jit -def silu_kernel(x_ptr, out_ptr, n, BLOCK_SIZE: tl.constexpr): - offs = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) - mask = offs < n - x = tl.load(x_ptr + offs, mask=mask) - tl.store(out_ptr + offs, x * tl.sigmoid(x), mask=mask) - - -@triton.jit -def mul_kernel(x_ptr, y_ptr, out_ptr, n, BLOCK_SIZE: tl.constexpr): - offs = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) - mask = offs < n - x = tl.load(x_ptr + offs, mask=mask) - y = tl.load(y_ptr + offs, mask=mask) - tl.store(out_ptr + offs, x * y, mask=mask) - - -@triton.jit -def add_kernel(x_ptr, y_ptr, out_ptr, n, BLOCK_SIZE: tl.constexpr): - offs = tl.program_id(0) * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE) - mask = offs < n - x = tl.load(x_ptr + offs, mask=mask) - y = tl.load(y_ptr + offs, mask=mask) - tl.store(out_ptr + offs, x + y, mask=mask) - - -def rmsnorm(x, weight, eps=1e-6): - rows, cols = x.shape - out = torch.empty_like(x) - rmsnorm_kernel[(rows,)]( - x, weight, out, cols, eps, BLOCK=triton.next_power_of_2(cols) - ) - return out - - -def matmul(a, b): - m, k = a.shape - _, n = b.shape - c = torch.empty((m, n), device=a.device, dtype=a.dtype) - grid = (triton.cdiv(m, BLOCK_M), triton.cdiv(n, BLOCK_N)) - matmul_kernel[grid]( - a, - b, - c, - m, - n, - k, - a.stride(0), - a.stride(1), - b.stride(0), - b.stride(1), - c.stride(0), - c.stride(1), - BLOCK_M=BLOCK_M, - BLOCK_N=BLOCK_N, - BLOCK_K=BLOCK_K, - ) - return c - - -def elementwise(kernel, *tensors): - out = torch.empty_like(tensors[0]) - n = out.numel() - grid = (triton.cdiv(n, ELT_BLOCK),) - kernel[grid](*tensors, out, n, BLOCK_SIZE=ELT_BLOCK) - return out - - -def ffn(x, w_norm, w_gate, w_up, w_down): - h = rmsnorm(x, w_norm) - gate = matmul(h, w_gate) - up = matmul(h, w_up) - act = elementwise(silu_kernel, gate) - fused = elementwise(mul_kernel, act, up) - down = matmul(fused, w_down) - return elementwise(add_kernel, x, down) - - -def main(): - if not torch.cuda.is_available(): - print("GPU is required for this sample. Exiting.") - sys.exit(1) - - tokens, hidden, inter = 512, 512, 2048 - dev = "cuda" - x = torch.randn(tokens, hidden, device=dev) - w_norm = torch.randn(hidden, device=dev) - w_gate = torch.randn(hidden, inter, device=dev) - w_up = torch.randn(hidden, inter, device=dev) - w_down = torch.randn(inter, hidden, device=dev) - - for _ in range(3): - out = ffn(x, w_norm, w_gate, w_up, w_down) - - torch.cuda.synchronize() - print(f"FFN completed, output sum: {out.sum().item():.3f}") - - -if __name__ == "__main__": - main() From ca1dc4f9158c1c42f70a14ef7d631ee9bc19a5f8 Mon Sep 17 00:00:00 2001 From: "fei.zheng" Date: Tue, 27 Oct 2026 19:20:05 -0600 Subject: [PATCH 6/6] Refresh the rocprof-compute skills from rocm-systems#14099 head 8cce705. The shared install section is gone, and each skill still keeps its MI300 runner file. Co-authored-by: Cursor --- .../.federated.json | 6 +- .../SKILL.md | 28 ++--- .../evals/evals.json | 3 +- skills/rocprof-compute-memory/.federated.json | 6 +- skills/rocprof-compute-memory/SKILL.md | 29 ++--- .../.federated.json | 6 +- skills/rocprof-compute-pc-sampling/SKILL.md | 17 +-- .../rocprof-compute-roofline/.federated.json | 6 +- skills/rocprof-compute-roofline/SKILL.md | 51 +++----- .../rocprof-compute-roofline/evals/evals.json | 4 +- skills/rocprof-compute-sample/.fixture.json | 6 +- skills/rocprof-compute-sample/simple_net.py | 111 ++++++++++++++---- .../.federated.json | 6 +- .../rocprof-compute-speed-of-light/SKILL.md | 15 +-- .../.federated.json | 6 +- skills/rocprof-compute-torch-trace/SKILL.md | 61 +++++----- .../evals/evals.json | 12 +- 17 files changed, 199 insertions(+), 174 deletions(-) diff --git a/skills/rocprof-compute-kernel-bottleneck/.federated.json b/skills/rocprof-compute-kernel-bottleneck/.federated.json index 1d1ceca..3b09dff 100644 --- a/skills/rocprof-compute-kernel-bottleneck/.federated.json +++ b/skills/rocprof-compute-kernel-bottleneck/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", + "commit": "8cce7054f17a77272e1ba73ef78f4f77cf056956", "path": "projects/rocprofiler-compute/skills/rocprof-compute-kernel-bottleneck", "license": "MIT", - "content_hash": "5562a6e11d160feed5215b5634ce3c9852422ee02673fd740a28636c7ba7ed99", - "imported_at": "2026-10-27T20:26:09Z" + "content_hash": "fc6c0e7c031c170e9f2cff67973a40ec8c9ccfecbd513f057c79121fd9e2a2c2", + "imported_at": "2026-10-28T01:19:39Z" } diff --git a/skills/rocprof-compute-kernel-bottleneck/SKILL.md b/skills/rocprof-compute-kernel-bottleneck/SKILL.md index 44e08f1..35ea364 100644 --- a/skills/rocprof-compute-kernel-bottleneck/SKILL.md +++ b/skills/rocprof-compute-kernel-bottleneck/SKILL.md @@ -9,19 +9,9 @@ Start here for any "profile this" or "why is this kernel slow" request. This skill collects counters, narrows a workload down to one kernel, and identifies what limits it. Hand off to a focused skill once the limit is clear. -## Prerequisites - -Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). - Run `rocprof-compute profile --help` and `rocprof-compute analyze --help` before choosing flags. Options change between releases; do not guess one. -Never use the GUI or TUI. This is a command-line workflow. - ## Two sources of performance data rocprof-compute collects from two independent sources, and they answer @@ -55,7 +45,7 @@ rocprof-compute --specs it cannot succeed when they fail. Supported accelerators are listed in -[compatible-accelerators.rst](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst). +[compatible-accelerators.rst](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst). Not every feature works on every architecture. Check there before promising a result, and let the tool's own error message stand when a feature is unavailable. @@ -85,14 +75,14 @@ the path. Re-profiling into a non-empty directory needs `--overwrite`; prefer a fresh directory. Which GPU the application runs on is in -[Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Under Slurm, derive +[Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Under Slurm, derive `HIP_VISIBLE_DEVICES` from `ROCR_VISIBLE_DEVICES`, not from `SLURM_STEP_GPUS`; the indices differ. -See [profile mode](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for output layout, +See [profile mode](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for output layout, filtering, and the full option list. To profile a process that is already running, see -[live attach and detach](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/live_attach_detach.rst). +[live attach and detach](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/live_attach_detach.rst). ### Collect less when a full replay is too expensive @@ -133,6 +123,8 @@ contain that file, look one level down for the GPU model or rank directory. `Dispatch list`. The kernel index is what `-k` takes. Dispatch ids are 1-based and go to `-d`. +`--list-stats` cannot be used with `--output-format csv` or `db`. + Work on one kernel at a time: ```bash @@ -183,7 +175,7 @@ table. When theoretical occupancy is itself low, the limit is VGPR, SGPR, or LDS allocation. Scratch traffic points at register spilling. Low occupancy is not a defect when the kernel already saturates its limiting resource. Worked examples are in -[occupancy-limiters-example.rst](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/tutorial/includes/occupancy-limiters-example.rst). +[occupancy-limiters-example.rst](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/tutorial/includes/occupancy-limiters-example.rst). ### Scheduler and pipeline limits @@ -199,7 +191,7 @@ There is no single stall panel. Stall counters sit in the block that owns the hardware: LDS stalls in `lds`, cache stalls in `vl1d` and `l2`, fabric stalls in `l2_per_channel`. Open the block the Speed-of-Light result points at, and read what each counter means in the -[performance model](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). Ground every +[performance model](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). Ground every recommendation in a number you read from the report. ## 6. Normalization @@ -236,7 +228,7 @@ settings without stating that those differ. Terminal output is the default. Only choose a format when the user asks to save or export. `--output-format` takes `txt`, `csv`, or `db`, each of which disables terminal output, and `--output-name` sets the file name. See -[analysis output format](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). +[analysis output format](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). ## 9. When something looks wrong @@ -249,4 +241,4 @@ disables terminal output, and `--output-name` sets the file name. See | Roofline missing | not collected, or unsupported on this architecture | Stop and report permission, firmware, and ROCm version failures instead of -working around them. More in the [FAQ](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/faq.rst). +working around them. More in the [FAQ](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/reference/faq.rst). diff --git a/skills/rocprof-compute-kernel-bottleneck/evals/evals.json b/skills/rocprof-compute-kernel-bottleneck/evals/evals.json index 5f8b68a..a13db25 100644 --- a/skills/rocprof-compute-kernel-bottleneck/evals/evals.json +++ b/skills/rocprof-compute-kernel-bottleneck/evals/evals.json @@ -11,8 +11,7 @@ "runs analyze with --list-stats to find the hottest kernel" ], "unexpected_behavior": [ - "invents a profiling flag instead of checking --help", - "opens the GUI or TUI" + "invents a profiling flag instead of checking --help" ], "logs_contain": ["rocprof-compute profile", "--list-stats"] }, diff --git a/skills/rocprof-compute-memory/.federated.json b/skills/rocprof-compute-memory/.federated.json index f66531b..8fbf5fc 100644 --- a/skills/rocprof-compute-memory/.federated.json +++ b/skills/rocprof-compute-memory/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", + "commit": "8cce7054f17a77272e1ba73ef78f4f77cf056956", "path": "projects/rocprofiler-compute/skills/rocprof-compute-memory", "license": "MIT", - "content_hash": "9fa3bfd584e474eb42135c301a57e2dbc85871dfc17477e5ea56a05abd6e03ea", - "imported_at": "2026-10-27T20:26:09Z" + "content_hash": "78037d87af423a68edab4d251b787cc07763467f570214c657bade7fecc01c74", + "imported_at": "2026-10-28T01:19:39Z" } diff --git a/skills/rocprof-compute-memory/SKILL.md b/skills/rocprof-compute-memory/SKILL.md index c01bc5f..1f88ca1 100644 --- a/skills/rocprof-compute-memory/SKILL.md +++ b/skills/rocprof-compute-memory/SKILL.md @@ -8,16 +8,7 @@ description: Analyzes the AMD GPU memory hierarchy with rocprof-compute, coverin Use this after Speed-of-Light points at memory, or when the user asks about bandwidth, caches, or data movement directly. -## Prerequisites - -Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). - Run `rocprof-compute analyze --help` before choosing flags. -Never use the GUI or TUI. ## 1. Start with the Memory Chart @@ -63,15 +54,15 @@ architecture with `rocprof-compute --list-blocks `. Each level has its own reference, with the counters, the formulas, and what they mean: -- [Vector L1 cache](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/cdna/vector-l1-cache.rst) -- [L2 cache](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/cdna/l2-cache.rst) -- [Local Data Share](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/cdna/local-data-share.rst) -- [Infinity Fabric transactions](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/tutorial/includes/infinity-fabric-transactions.rst) -- [Vector memory operation counting](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/tutorial/includes/vector-memory-operation-counting.rst) -- [LDS examples](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/tutorial/includes/lds-examples.rst) +- [Vector L1 cache](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/conceptual/cdna/vector-l1-cache.rst) +- [L2 cache](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/conceptual/cdna/l2-cache.rst) +- [Local Data Share](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/conceptual/cdna/local-data-share.rst) +- [Infinity Fabric transactions](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/tutorial/includes/infinity-fabric-transactions.rst) +- [Vector memory operation counting](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/tutorial/includes/vector-memory-operation-counting.rst) +- [LDS examples](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/tutorial/includes/lds-examples.rst) Client APUs use a different hierarchy; see the -[RDNA performance model](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/rdna/rdna-performance-model.rst) +[RDNA performance model](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/conceptual/rdna/rdna-performance-model.rst) for GL0, GL1, and GL2. Read hit rates and utilizations against the `Percent of Peak` and `Peak` @@ -94,7 +85,7 @@ What usually follows from what: A guided breakdown that walks the bandwidth tree and names the limiting level. Which GPUs it supports, and the experimental flag, are in -[memory bandwidth analysis](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/membw_analysis.rst). It is +[memory bandwidth analysis](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/membw_analysis.rst). It is experimental and currently shipped for gfx950 only. It needs only the memory chart (block 3) and block 30, so profile only those two blocks: @@ -108,8 +99,8 @@ rocprof-compute analyze --path ./workloads// -k Its cutoffs and guidance text are data, not something to restate from memory. In a source checkout, read: -- [gfx950_membw_tree_spec.yaml](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/src/membw_analysis/tree_spec/gfx950_membw_tree_spec.yaml) -- [gfx950_membw_guidance.yaml](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/src/membw_analysis/tree_spec/gfx950_membw_guidance.yaml) +- [gfx950_membw_tree_spec.yaml](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/src/membw_analysis/tree_spec/gfx950_membw_tree_spec.yaml) +- [gfx950_membw_guidance.yaml](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/src/membw_analysis/tree_spec/gfx950_membw_guidance.yaml) A ROCm install copies the skills to `/share/rocprofiler-compute/skills/` and does not copy these YAML files next to them. When this skill was installed diff --git a/skills/rocprof-compute-pc-sampling/.federated.json b/skills/rocprof-compute-pc-sampling/.federated.json index c1ddd36..a091cc8 100644 --- a/skills/rocprof-compute-pc-sampling/.federated.json +++ b/skills/rocprof-compute-pc-sampling/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", + "commit": "8cce7054f17a77272e1ba73ef78f4f77cf056956", "path": "projects/rocprofiler-compute/skills/rocprof-compute-pc-sampling", "license": "MIT", - "content_hash": "e7302291aadaf366b7aaa3a7c47e3a368b9f2a8e24509c5dc86fb38ee318ae9e", - "imported_at": "2026-10-27T20:26:09Z" + "content_hash": "0fccdde867de5668a0c767e64f6274714f07befea1e4f9a2299c6c580a0072c6", + "imported_at": "2026-10-28T01:19:39Z" } diff --git a/skills/rocprof-compute-pc-sampling/SKILL.md b/skills/rocprof-compute-pc-sampling/SKILL.md index 7333a89..e3857ec 100644 --- a/skills/rocprof-compute-pc-sampling/SKILL.md +++ b/skills/rocprof-compute-pc-sampling/SKILL.md @@ -12,20 +12,11 @@ have already identified the kernel worth this level of detail. PC sampling is experimental. Every command below needs `--experimental`, and option names can change between releases, so run `rocprof-compute profile --experimental --help` before choosing flags. -Never use the GUI or TUI. - -## Prerequisites - -Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). This skill drives `rocprof-compute --pc-sampling`. A request to invoke `rocprofv3 --pc-sampling-beta-enabled` directly belongs to the rocprofiler-sdk PC sampling skill, not this one. Method support is in -[PC sampling](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). +[PC sampling](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). ## 1. Check what the hardware supports @@ -38,7 +29,7 @@ interval range. Two methods exist: - `stochastic` samples in hardware and is the only method that records a stall reason. Which GPUs accept it is in - [PC sampling](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). + [PC sampling](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). - `host_trap` records no stall reason, and its samples can land a little past the instruction responsible. @@ -106,7 +97,7 @@ there, and the fix is usually coalescing, prefetching, or more independent work between the load and its use. Stall reasons and what each one implies are listed in -[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). Do not interpret a stall +[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). Do not interpret a stall reason from host-trap data; it does not record one. ## 5. Export annotated disassembly @@ -129,4 +120,4 @@ accepts, rather than drawing conclusions from a handful of samples. For multi-process workloads, keep each process or rank separate; see the multi-process sections of -[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). +[pc_sampling.rst](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/pc_sampling.rst). diff --git a/skills/rocprof-compute-roofline/.federated.json b/skills/rocprof-compute-roofline/.federated.json index 8f21456..29ef016 100644 --- a/skills/rocprof-compute-roofline/.federated.json +++ b/skills/rocprof-compute-roofline/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", + "commit": "8cce7054f17a77272e1ba73ef78f4f77cf056956", "path": "projects/rocprofiler-compute/skills/rocprof-compute-roofline", "license": "MIT", - "content_hash": "a7d8ed53d1cddb4db4700592435ade1fa42f6ff1e2b9442545cec53c8c1dbf4e", - "imported_at": "2026-10-27T20:26:09Z" + "content_hash": "6624d509ddee66144bf15f8bb39d0ccfde81a0e536cf11e34dfed618d05c17eb", + "imported_at": "2026-10-28T01:19:39Z" } diff --git a/skills/rocprof-compute-roofline/SKILL.md b/skills/rocprof-compute-roofline/SKILL.md index feac0e3..14eff91 100644 --- a/skills/rocprof-compute-roofline/SKILL.md +++ b/skills/rocprof-compute-roofline/SKILL.md @@ -10,18 +10,11 @@ ceilings, peak compute and peak bandwidth, and answers one question: which of the two is the limit. Run `rocprof-compute profile --help` and `rocprof-compute analyze --help` -before choosing flags. Never use the GUI or TUI, and do not open the generated -HTML chart. The analyze table carries the same numbers in readable form. +before choosing flags. Do not open the generated HTML chart. The analyze table +carries the same numbers in readable form. -## Prerequisites - -Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Roofline support by GPU is in -[supported hardware](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/what-is-rocprof-compute.rst). +[supported hardware](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/what-is-rocprof-compute.rst). ## 1. Collect @@ -36,14 +29,14 @@ which takes extra time. `--no-roof` skips them when the user only wants counters. On MI100 (gfx908), the roofline microbenchmarks are skipped and the profile -still succeeds. When [supported hardware](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/what-is-rocprof-compute.rst) +still succeeds. When [supported hardware](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/what-is-rocprof-compute.rst) shows no roofline support, do not ask the user to re-profile without `--no-roof`. Use Speed-of-Light and the `rocprof-compute-memory` skill instead. `--device` and `HIP_VISIBLE_DEVICES` are different selections. See -[Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). +[Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). -See [standalone roofline](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for `--roof-only` +See [standalone roofline](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for `--roof-only` and `--bench-only`, which run the microbenchmarks without profiling an application. @@ -85,29 +78,23 @@ at 45% of peak bandwidth" tells the user there is headroom; "memory-bound at ## 3. Pick the right precision and memory level -Section 2 gives the verdict against the default ceilings. Refine it here when -the kernel does not use FP32, or when the question is about a specific level of -the hierarchy. - -```bash -# Ceilings for the precision the kernel actually uses -rocprof-compute analyze --path ./workloads// -k -b roof \ - -R FP16 BF16 - -# Ceilings for a specific level -rocprof-compute analyze --path ./workloads// -k -b roof \ - -m HBM L2 -``` +The table lists a peak for every precision and an arithmetic intensity for +every memory level. Compare against the peak row for the precision the kernel +actually issues, and the intensity row for the memory level in question. -`-R` defaults to FP32. A mixed-precision or matrix kernel compared against the -FP32 ceiling will look far worse than it is, so set `-R` to the precision the -kernel actually issues. Read that from the instruction mix with `-b cu_ins` +A mixed-precision or matrix kernel compared against the FP32 peak will look far +worse than it is. Read the precision from the instruction mix with `-b cu_ins` rather than assuming it. Ask the user which precision the kernel is meant to use only when the mix is genuinely mixed and no single type dominates; they know the intent, the counters know what ran. -`-m` defaults to every level. HBM answers "am I limited by main memory", while -L2 and vL1D show whether caches are absorbing the traffic. +HBM answers "am I limited by main memory", while L2 and vL1D show whether +caches are absorbing the traffic. + +`--view table` prints one table for all kernels together instead of one per +kernel. To compare many kernels, write the +[analysis database](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst) with +`--output-format db` and query it with SQL. ## 4. When roofline data is missing @@ -118,4 +105,4 @@ L2 and vL1D show whether caches are absorbing the traffic. | `roofline.csv` absent from the workload directory | the benchmark did not complete; check the profile log | The concepts behind the ceilings are in the -[performance model](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). +[performance model](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). diff --git a/skills/rocprof-compute-roofline/evals/evals.json b/skills/rocprof-compute-roofline/evals/evals.json index a0a0a5c..23a8e7b 100644 --- a/skills/rocprof-compute-roofline/evals/evals.json +++ b/skills/rocprof-compute-roofline/evals/evals.json @@ -33,13 +33,13 @@ "prompt": "my half precision GEMM looks terrible on the roofline, is that right?\n\nThe application is ./wmma_gemm_fp16.hip.", "workspace": "../rocprof-compute-sample/wmma_gemm_fp16", "expected_behavior": [ - "sets -R to the precision the kernel actually issues instead of the FP32 default", + "compares against the peak for the precision the kernel actually issues instead of FP32", "confirms the precision from the instruction mix rather than assuming it" ], "unexpected_behavior": [ "compares a half precision kernel against the FP32 ceiling and calls it slow" ], - "logs_contain": ["-R"] + "logs_contain": ["cu_ins"] }, { "id": "roof-missing-data", diff --git a/skills/rocprof-compute-sample/.fixture.json b/skills/rocprof-compute-sample/.fixture.json index 8ca5229..670da40 100644 --- a/skills/rocprof-compute-sample/.fixture.json +++ b/skills/rocprof-compute-sample/.fixture.json @@ -1,8 +1,8 @@ { "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", + "commit": "8cce7054f17a77272e1ba73ef78f4f77cf056956", "path": "projects/rocprofiler-compute/sample", - "content_hash": "b2d249ac299e4149cc1734bf76f7f3696ce21d6046aa9591d4a82a2af0b306be", - "imported_at": "2026-10-27T20:42:31Z" + "content_hash": "20210259dfbeada334ea1a051d146f05fde13a280ef6ac4523931f725f66f001", + "imported_at": "2026-10-28T01:19:39Z" } diff --git a/skills/rocprof-compute-sample/simple_net.py b/skills/rocprof-compute-sample/simple_net.py index 828337d..6def8f2 100755 --- a/skills/rocprof-compute-sample/simple_net.py +++ b/skills/rocprof-compute-sample/simple_net.py @@ -2,54 +2,123 @@ # SPDX-License-Identifier: MIT """ -Sample PyTorch workload for simple network training. +Sample PyTorch workload for rocprof-compute --torch-trace. -Prerequisites: - PyTorch must be installed with GPU support. Install via: - - ROCm: - pip install --no-cache-dir --pre torch --index-url \ - https://download.pytorch.org/whl/nightly/rocm7.2 - - See https://pytorch.org/get-started/locally/ for more options. - -Usage: rocprof-compute profile --experimental --torch-trace --no-roof \ - -n simple_net -- python3 ./simple_net.py + -n simple_net -- python3 ./simple_net.py + + --user-range Wrap the run in a user ROCTX range. Requires ROCTX Python. + --same-line-linear Two Linear.forward calls from the same source line. + --no-backward Skip loss.backward(). Cannot combine with --backward-thread. + --backward-thread Run loss.backward() on a worker thread. Cannot combine + with --no-backward. """ +import argparse import sys +import threading import torch import torch.nn as nn import torch.nn.functional as F +try: + import roctx +except ImportError: + roctx = None + class SimpleNet(nn.Module): - def __init__(self): + def __init__(self, same_line_linear=False): super().__init__() self.fc1 = nn.Linear(10, 20) self.fc2 = nn.Linear(20, 10) + self.same_line_linear = same_line_linear def forward(self, x): + if self.same_line_linear: + # two Linear.forward calls on one source line + # fmt: off + x = self.fc1(x); x = self.fc2(x) # noqa: E702 + # fmt: on + return x x = self.fc1(x) x = F.relu(x) x = self.fc2(x) return x -def main(): +def parse_args(argv=None): + parser = argparse.ArgumentParser(description="SimpleNet torch-trace sample") + parser.add_argument( + "--user-range", + action="store_true", + help="Wrap the run in a user ROCTX range", + ) + parser.add_argument( + "--same-line-linear", + action="store_true", + help="Two Linear.forward calls from the same source line", + ) + exclusive = parser.add_mutually_exclusive_group() + exclusive.add_argument( + "--no-backward", + action="store_true", + help="Skip loss.backward()", + ) + exclusive.add_argument( + "--backward-thread", + action="store_true", + help="Run loss.backward() on a worker thread", + ) + args = parser.parse_args(argv) + if args.user_range and roctx is None: + parser.error("--user-range requires ROCTX Python") + return args + + +def run_backward(loss, caught): + """Thread target: run loss.backward() and keep any exception in caught.""" + try: + loss.backward() + except BaseException: + caught.append(sys.exc_info()[1:]) + + +def backward_on_worker_thread(loss): + """Run loss.backward() on a worker thread and re-raise its exception here.""" + caught = [] + worker = threading.Thread(target=run_backward, args=(loss, caught)) + worker.start() + worker.join() + if caught: + err, tb = caught[0] + raise err.with_traceback(tb) + + +def main(argv=None): + args = parse_args(argv) if not torch.cuda.is_available(): print("GPU is required for this sample. Exiting.") sys.exit(1) - model = SimpleNet().cuda() - x = torch.randn(5, 10).cuda() - - for _ in range(1): - output = model(x) - loss = output.sum() - loss.backward() + if args.user_range: + roctx.rangePush("TrainingLoop_UserDefinedMarker") + + try: + model = SimpleNet(same_line_linear=args.same_line_linear).cuda() + x = torch.randn(5, 10).cuda() + + for _ in range(1): + output = model(x) + loss = output.sum() + if args.backward_thread: + backward_on_worker_thread(loss) + elif not args.no_backward: + loss.backward() + finally: + if args.user_range: + roctx.rangePop() print("Training completed") diff --git a/skills/rocprof-compute-speed-of-light/.federated.json b/skills/rocprof-compute-speed-of-light/.federated.json index d0dcf11..975c16e 100644 --- a/skills/rocprof-compute-speed-of-light/.federated.json +++ b/skills/rocprof-compute-speed-of-light/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", + "commit": "8cce7054f17a77272e1ba73ef78f4f77cf056956", "path": "projects/rocprofiler-compute/skills/rocprof-compute-speed-of-light", "license": "MIT", - "content_hash": "56519a96def55a271ce94f529c0e0d9a0d01b062ae22b406b985f55fb029de81", - "imported_at": "2026-10-27T20:26:09Z" + "content_hash": "924898dc2f20900f8191721bf89163c4d4ac27a6fd9a758b64292657e5990818", + "imported_at": "2026-10-28T01:19:39Z" } diff --git a/skills/rocprof-compute-speed-of-light/SKILL.md b/skills/rocprof-compute-speed-of-light/SKILL.md index 958b8d7..1183abe 100644 --- a/skills/rocprof-compute-speed-of-light/SKILL.md +++ b/skills/rocprof-compute-speed-of-light/SKILL.md @@ -9,18 +9,9 @@ Speed-of-Light is the first panel to read after profiling. It puts every headline metric next to the hardware peak for the current GPU, so one table says whether a kernel is near any limit at all. -## Prerequisites - -Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). - This skill reads an existing workload directory. It does not collect one. Run `rocprof-compute analyze --help` before choosing flags. -Never use the GUI or TUI. ## 1. Get the panel @@ -65,11 +56,11 @@ the neighbouring kernels, and a known-good baseline for the same workload. ## 3. Understand the metrics Each metric's definition and the counters behind it are in -[system-speed-of-light.rst](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/cdna/system-speed-of-light.rst) +[system-speed-of-light.rst](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/conceptual/cdna/system-speed-of-light.rst) for CDNA accelerators and -[the RDNA equivalent](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/rdna/system-speed-of-light.rst) +[the RDNA equivalent](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/conceptual/rdna/system-speed-of-light.rst) for client APUs. The whole metric hierarchy is in the -[performance model](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). +[performance model](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/conceptual/performance-model.rst). ## 4. Compute Speed-of-Light diff --git a/skills/rocprof-compute-torch-trace/.federated.json b/skills/rocprof-compute-torch-trace/.federated.json index 857ef04..c1becf2 100644 --- a/skills/rocprof-compute-torch-trace/.federated.json +++ b/skills/rocprof-compute-torch-trace/.federated.json @@ -2,9 +2,9 @@ "source": "rocm-rocm-systems", "repo": "ROCm/rocm-systems", "ref": "develop", - "commit": "7a1a96c2aab5f963edf9a88120939cec3c45a8e8", + "commit": "8cce7054f17a77272e1ba73ef78f4f77cf056956", "path": "projects/rocprofiler-compute/skills/rocprof-compute-torch-trace", "license": "MIT", - "content_hash": "9a505c1c766ba6a753fcf8c65f220fa22b1215de211c78849d2f81b46134898d", - "imported_at": "2026-10-27T20:26:09Z" + "content_hash": "7a8aa8e901c4e9124ea14de152ab6a4aa0d36814043c92401f9fb9f95ebc03a7", + "imported_at": "2026-10-28T01:19:39Z" } diff --git a/skills/rocprof-compute-torch-trace/SKILL.md b/skills/rocprof-compute-torch-trace/SKILL.md index edf852a..a813f2c 100644 --- a/skills/rocprof-compute-torch-trace/SKILL.md +++ b/skills/rocprof-compute-torch-trace/SKILL.md @@ -12,26 +12,21 @@ traced back to the operator that launched it. Torch and Triton tracing are experimental. Every command needs `--experimental`, and option names can change between releases, so run `rocprof-compute profile --experimental --help` first. -Never use the GUI or TUI. - -## Prerequisites - -Install, supported GPUs, and which GPU the application runs on are in -[Install ROCm Compute Profiler](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/core-install.rst), -[source install](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/install/source-install.rst), -[compatible accelerators](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/reference/compatible-accelerators.rst), -and [Selecting a GPU](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). ## 1. Check the requirements PyTorch version coverage, the native collector versus reduced `TorchDispatchMode` coverage, and the abort when two ROCm installs load in one process are in the Torch trace section of -[profile mode](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Check the loader message +[profile mode](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). Check the loader message before treating a missing backward operator as missing GPU work. -Confirm the PyTorch version before profiling. A mismatched version fails at -collection time, after the run has already cost the user its runtime. +The workload must be a Python command or Python script; the tracer cannot +annotate a compiled binary. The collector is a generic prebuilt +`torch_trace_collector.so` and needs no PyTorch or Python development packages +to build. The workload still needs Python-compatible `roctx` bindings. The +full requirement list is in the Torch trace section of +[profile mode](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/profile/mode.rst). ## 2. Collect @@ -43,12 +38,15 @@ rocprof-compute profile \ -- python ``` -For Triton kernels, including those generated by `torch.compile` and Inductor: +For Triton kernels launched through Triton's Python runtime (`@triton.jit`): ```bash rocprof-compute profile --experimental --triton-trace --name -- python ``` +`torch.compile` / Inductor fused kernels use PyTorch's static launcher, so they +do not get `|triton` ranges. Attribute them with `--torch-trace`. + The two combine, and `--ml-api-trace` turns on every supported framework backend at once. @@ -56,7 +54,7 @@ Tracing adds overhead on top of the usual counter replay. Profile a few iterations of a steady-state model rather than a full training run, and skip warmup. `ROCPROFCOMPUTE_ROCTX_DEEP_TENSOR_WRAPS=0` reduces instrumentation overhead by dropping the device and layout wraps; see -[profile mode](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for what that costs you. +[profile mode](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/profile/mode.rst) for what that costs you. Tracing cannot be combined with attaching to a running process, and cannot be combined with a PC-sampling-only profile. @@ -64,10 +62,15 @@ combined with a PC-sampling-only profile. When counters are being spread thin across many short operator kernels, `--iteration-multiplexing kernel` collects more per pass. +Profile preserves each pass's raw marker and counter CSVs with the +`ml_api_trace` prefix. + ## 3. List the captured operators -Always list before filtering. Operator names come from the model that ran, so -guessing a pattern wastes a profile: +List when you need the operator names from this run; filtering does not +require a prior list. Do not pass `--list-*-operators` and `--*-operator` +on the same command; listing ignores the filter. These flags require +`--output-format stdout` (the default): ```bash rocprof-compute analyze \ @@ -75,8 +78,9 @@ rocprof-compute analyze \ --experimental --list-torch-operators ``` -This prints the operators as a call tree grouped by source location, with -kernel launch statistics, so it also shows which operators are worth opening. +This prints the operators as a call tree sorted by GPU duration, with +`file:line` on each node that recorded a source location, plus kernel +launch statistics, so it also shows which operators are worth opening. The Triton equivalent is `--list-triton-operators`. @@ -85,21 +89,20 @@ The Triton equivalent is `--list-triton-operators`. ```bash rocprof-compute analyze \ --path ./workloads// \ - --experimental --torch-operator '*conv2d*' + --experimental --torch-operator '*relu*' ``` Patterns are shell-style globs, not regexes, matched against a `/`-separated -operator hierarchy. `*relu` ends with relu, `*conv*` contains conv, -`*/*functional*/*` matches an intermediate component, and `all` or `'*'` -matches everything. Several patterns can be given, space or comma separated. +operator hierarchy. `*relu*` contains relu, `*/aten::addmm` is an addmm leaf +on the reconstructed path, and `all` or `'*'` matches everything. Several +patterns can be given, space or comma separated. Quote patterns so the shell does not expand them. -Combine with `-k` to intersect an operator with specific kernel ids, and with -`-b` to look at one metric block for that operator's kernels. `--triton-operator` -works the same way. +`--triton-operator` works the same way. `*rmsnorm_kernel*` matches the +rmsnorm nodes on a Triton tree. Worked examples are in the PyTorch operator analysis section of -[the analyze CLI guide](https://github.com/ROCm/rocm-systems/blob/7a1a96c2aab5f963edf9a88120939cec3c45a8e8/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). +[the analyze CLI guide](https://github.com/ROCm/rocm-systems/blob/8cce7054f17a77272e1ba73ef78f4f77cf056956/projects/rocprofiler-compute/docs/how-to/analyze/cli.rst). ## 5. Read the result @@ -118,7 +121,7 @@ annotate operators, which is what makes attribution possible. That is the only place ROCTx is involved. rocprof-compute does not support ROCTx-range-based profiling control, such as -starting, stopping, or resuming collection at a user-authored ROCTx range, and -does not attribute results to ranges a user added to their own application. If -that is what the user wants, say so plainly and offer kernel filtering with +starting, stopping, or resuming collection at a user-authored ROCTx range. +User-defined ranges in the marker CSV are labeled `user`. If the user wants +ROCTx start/stop control, say so plainly and offer kernel filtering with `-k` or dispatch filtering with `-d` instead. diff --git a/skills/rocprof-compute-torch-trace/evals/evals.json b/skills/rocprof-compute-torch-trace/evals/evals.json index 000147a..42ac53b 100644 --- a/skills/rocprof-compute-torch-trace/evals/evals.json +++ b/skills/rocprof-compute-torch-trace/evals/evals.json @@ -7,7 +7,7 @@ "workspace": "../rocprof-compute-sample", "expected_behavior": [ "collects with --experimental --torch-trace", - "lists the captured operators before filtering", + "lists the captured operators to find the slow ones", "checks the documented PyTorch version requirement" ], "unexpected_behavior": [ @@ -31,17 +31,19 @@ "skill_should_trigger": true, "prompt": "my model uses torch.compile, how do I attribute the generated Triton kernels?", "expected_behavior": [ - "uses --triton-trace, optionally combined with --torch-trace", - "lists the captured Triton kernels before filtering" + "uses --torch-trace, because --triton-trace does not capture kernels that torch.compile generates" ], - "logs_contain": ["--triton-trace"] + "unexpected_behavior": [ + "relies on --triton-trace alone to capture torch.compile kernels" + ], + "logs_contain": ["--torch-trace"] }, { "id": "tt-filter-by-operator", "skill_should_trigger": true, "prompt": "show me the counters for just the conv2d operators in this torch-trace workload", "expected_behavior": [ - "lists operators first, then filters with --torch-operator", + "filters with --torch-operator", "treats the pattern as a shell-style glob rather than a regex" ], "logs_contain": ["--torch-operator"]