{"slug": "spir-v-on-rocm-a-portable-ir-for-amd-gpus", "title": "SPIR-V on ROCm: A Portable IR for AMD GPUs", "summary": "AMD's ROCm platform now supports SPIR-V as a portable intermediate representation for GPU code, shifting from ahead-of-time compilation per target to a compile-once, just-in-time specialize-on-device model. The change addresses scalability issues with the previous approach, which required enumerating every GPU architecture at build time, leading to long build times, large fat binaries, and no forward compatibility. AMD demonstrated the new model with a reproducible single-kernel HIP example on MI350X (CDNA4) and RX 9070 XT (RDNA4).", "body_md": "# SPIR-V on ROCm: A Portable IR for AMD GPUs[#](#spir-v-on-rocm-a-portable-ir-for-amd-gpus)\n\nROCm has compiled GPU code ahead of time (AOT), once per target\narchitecture, since its first release. CPU software shifted to runtime\njust-in-time (JIT) compilation in the 1990s — Java HotSpot, V8, LuaJIT,\n.NET RyuJIT, PyPy — once the matrix of *target × workload × deployment*\nbecame too large to enumerate at build time. With its adoption of SPIR-V,\nROCm now brings the compile-once, specialize-on-device model to AMD GPUs.\n\nThis post covers what SPIR-V is, why AMD adopted it, the compilation model and its trade-offs, and a reproducible single-kernel HIP example with measurements on MI350X (CDNA4), spot-checked on RX 9070 XT (RDNA4). A FAQ and status section at the end cover common questions and what is shipping, in flight, and not yet solved.\n\nA follow-up post will discuss a real-world SPIR-V deployment at scale: PyTorch.\n\n## The Problem: AOT-per-Target Does Not Scale[#](#the-problem-aot-per-target-does-not-scale)\n\nA ROCm application today is built by enumerating every GPU architecture\nit needs to run on. For each entry, the device-side code of every HIP\ntranslation unit is compiled once and the resulting code objects are\nbundled into a fat binary. With *N* targets, that is *N* device code\nobjects per TU — a substantial multiplier on build time and on the\nsize of the shipped artifacts. Host code is compiled once. Beyond the\nbuild system, developers themselves must be aware of every target: writing\nper-arch `#ifdef`\n\nblocks, testing on each architecture, and updating those\nlists whenever a new GPU ships.\n\nAn instance of this is PyTorch’s `PYTORCH_ROCM_ARCH`\n\n,\nwhose upstream default is currently fifteen targets spanning CDNA1/2/3\nand RDNA2/3/4:\n\n```\nPYTORCH_ROCM_ARCH=\"gfx900;gfx906;gfx908;gfx90a;gfx942;gfx950;\\\n                   gfx1030;gfx1100;gfx1101;gfx1102;gfx1103;\\\n                   gfx1150;gfx1151;gfx1200;gfx1201\"\n```\n\nEach entry expands into one `--offload-arch=gfxXXX`\n\nflag, and the\nconsequences cascade regardless of whether the build sits inside PyTorch,\na HIP application, or a downstream library:\n\n**Long build times.** The device pipeline runs N times.**Large fat binaries.** N native code objects per TU, all shipped together.**Build systems must enumerate every target.** The framework has to maintain an explicit arch list and update it whenever a new GPU ships.**No forward compatibility.** A binary built today cannot run on a GPU architecture that did not exist at build time — a new ASIC requires a new build.\n\nThis was tolerable when GPU architectures evolved on multi-year cycles. It strains under the current pace: new ASICs land frequently, cloud fleets mix generations, and frameworks ship precompiled artifacts that have to cover all of them.\n\nThe shift SPIR-V enables is from *ahead-of-time compilation per target*\nto *compile once, just-in-time specialize on device*. Figure 1 illustrates\nthe difference.\n\n## What SPIR-V Is, and What AMD’s Flavor Adds[#](#what-spir-v-is-and-what-amds-flavor-adds)\n\nSPIR-V — Standard Portable Intermediate Representation – V — is a binary,\nSSA-based IR defined by [the Khronos Group](https://registry.khronos.org/SPIR-V/). It originated as a\nportable IR for GPU programs (graphics shaders for Vulkan, compute kernels\nfor OpenCL) and has since broadened into a standardized, toolchain-friendly\nIR that can be lowered to a vendor’s concrete ISA at a later stage. We treat\nSPIR-V here as an intermediate representation (IR), not a source language.\n\nAMD’s AMDGCN-flavored SPIR-V carries vendor-specific constructs through two\nmechanisms. Inline assembly is encoded via the `SPV_INTEL_inline_assembly`\n\nextension, which wraps the raw assembly string in the SPIR-V binary. AMDGCN\nintrinsics and target-specific builtins, by contrast, are handled transparently\nby the compiler and translator pipeline: they are lowered to function\ndeclarations and reverse-translated accordingly, with no extension required.\nTogether, these mechanisms preserve expressiveness that a strictly-portable IR\nwould lose.\nThis variant is exposed through the `amdgcnspirv`\n\nabstract target in\n[LLVM/Clang](https://rocmdocs.amd.com/projects/llvm-project/en/latest/conceptual/spirv.html).\n\nIn ROCm, AMDGCN-flavored SPIR-V serves three roles:\n\nA\n\n**target-agnostic GPU IR** that can be produced once.A\n\n**late-binding abstraction** over AMD GPU architectures.A way to\n\n**decouple compilation from device-specific codegen**.\n\n### How AMD Got SPIR-V into Upstream LLVM[#](#how-amd-got-spir-v-into-upstream-llvm)\n\nThe `amdgcnspirv`\n\ntarget is not a fork. It is a multi-year effort, in\nupstream LLVM, that incrementally turned generic SPIR-V into a usable\n*flavor* of AMDGCN. The relevant pieces, listed in roughly the order\nthey were built because each layer depends on the one before it:\n\n**Target plumbing.** A new Clang target`SPIRV64AMDGCNTargetInfo`\n\n(`clang/lib/Basic/Targets/SPIR.h`\n\n), identified by the triple`spirv64-amd-amdhsa--amdgcnspirv`\n\n, that delegates feature/builtin decisions to the AMDGPU target rather than to vanilla SPIR-V. The driver side (`clang/lib/Driver/ToolChains/HIPSPV.cpp`\n\n) wires`-x hip --offload-arch=amdgcnspirv`\n\ninto the SPIR-V translation step and lets you mix`amdgcnspirv`\n\nwith concrete`gfxXXX`\n\ntargets in one invocation.**ABI alignment with AMDGPU**, so that calling conventions, aggregate passing, address spaces, and`__builtin_va_list`\n\nmatch what AMDGCN expects after lowering instead of what generic SPIR-V would default to. Without this step a SPIR-V binary lowered to AMDGCN at runtime would not be ABI-compatible with the host code that called it.**Intrinsic surface.** AMDGCN intrinsics (`__builtin_amdgcn_*`\n\n) and inline`asm volatile(...)`\n\nare reachable through the SPIR-V flow via the AMD extension; the in-tree backend’s`SPIRVInlineAsmLowering`\n\nand`SPIRVEmitIntrinsics`\n\ncarry the AMDGPU-specific encodings through to the binary.**Late-resolved feature predicates**, the capstone — discussed in the next section.\n\nThis is a portable IR built *from* a target compiler, not a portable\ncompiler retrofitted to a target. That distinction matters: every\ntarget-specific behavior the SPIR-V flavor preserves is one pull request\n(PR), with tests, in upstream LLVM.\n\n## Compilation Model[#](#compilation-model)\n\nLLVM IR is *not* target-agnostic — by the time it leaves the front end, the\nAST has already been typed against the chosen target. The portable artifact\nin this flow is the SPIR-V binary, not the IR. The full pipeline, with the\nbuild-time / run-time boundary made explicit, is shown in Figure 2:\n\nToday, the [SPIRV-LLVM Translator](https://github.com/KhronosGroup/SPIRV-LLVM-Translator) performs the LLVM-IR → SPIR-V\nstep. An in-tree LLVM SPIR-V backend is being hardened and is expected to\nbecome the default path.\n\n### Late-Resolved Feature Predicates (“ZCFS”)[#](#late-resolved-feature-predicates-zcfs)\n\nA design challenge with portable GPU IR is that hardware features vary\nacross GPU generations — and encoding those differences at compile time forces\nan ugly tradeoff: add runtime guards that penalize every dispatch, or multiply\nyour build matrix to cover each architecture. AMD’s *late-resolved feature\nidentifying predicates* work — informally called **ZCFS** (Zero-Cost\nFeature Selection), since it lets per-arch dispatch survive the trip\nthrough a portable IR without paying any runtime cost — landed via\n[LLVM PR #134016](https://github.com/llvm/llvm-project/pull/134016) and makes this concern largely go away.\nPredicates such as `__builtin_amdgcn_processor_is`\n\nand `__builtin_amdgcn_is_invocable`\n\nare resolved during SPIR-V → native\nlowering, so exactly one branch survives per arch and surrounding loops fold\nto per-arch constants. The result is a single SPIR-V binary that lowers to a\ndifferent ISA for each GPU at JIT time, with no runtime overhead from the\ndispatch itself. The interface is uniform: the same builtins\nwork whether the target is a concrete architecture like `--offload-arch=gfx950`\n\nor the abstract `--offload-arch=amdgcnspirv`\n\n, so no source forking is needed.\n\nThe mechanism, end-to-end, is worth understanding because it is the thing that makes portable AMD GPU IR not regress at runtime:\n\n**Sema**(`clang/lib/Sema/SemaAMDGPU.cpp`\n\n, +304 lines in PR #134016) constrains where`__builtin_amdgcn_processor_is`\n\nand`__builtin_amdgcn_is_invocable`\n\nmay appear — boolean control-flow contexts only — and gives them an opaque`__amdgpu_feature_predicate_t`\n\ntype (`clang/include/clang/Basic/AMDGPUTypes.def`\n\n) that cannot be stored, compared, or smuggled across function boundaries.**CodeGen**(`clang/lib/CodeGen/CGExprScalar.cpp`\n\n) lowers the predicate to`llvm.spv.named.boolean.spec.constant(i32 ID, metadata !\"gfx942\")`\n\nfor SPIR-V targets. The supporting intrinsic itself was added in[LLVM PR #187420](https://github.com/llvm/llvm-project/pull/187420)(commit`d8104bfc9e9d`\n\n, 2026-03-20) so the larger ZCFS work in PR #134016 (commit`18e695890306`\n\n, 2026-03-30) had something to lower into.**SPIR-V lowering**(`llvm/lib/Target/SPIRV/SPIRVPrepareGlobals.cpp`\n\n) assigns deterministic IDs to each unique predicate and emits`OpSpecConstantFalse`\n\nper ID — deliberately, so that an unspecialized predicate defaults to false and the controlled block is removed. The binary now contains a small table of predicates, each unresolved.**Runtime lowering**(the SPIRV-LLVM Translator invoked under`amd_comgr`\n\n) is told the actual`gfx`\n\ntarget of the device it is lowering for. It folds each spec-constant to a concrete`i1`\n\nand then runs a dedicated predicate-resolution pass that folds only the chains rooted at predicates. This pass is deterministic, runs at all optimization levels including debug, and is a hard failure if any predicate chain cannot be fully resolved — guaranteeing that exactly one branch survives per arch in the emitted AMDGCN ISA.\n\nFigure 3 shows the end-to-end pipeline.\n\nThe next section puts this mechanism into practice: the two builtins\nthat ride this lowering path — `__builtin_amdgcn_processor_is`\n\nand\n`__builtin_amdgcn_is_invocable`\n\n— used in a realistic kernel that\nreads as a runtime test but, after JIT, leaves exactly one branch\nalive on each device.\n\n## Late-Resolved Predicates in Practice[#](#late-resolved-predicates-in-practice)\n\nThe compilation model section above introduced the *mechanism* — how\npredicates flow from Clang through SPIR-V to the runtime JIT. This\nsection shows how to *use* them. Two builtins were introduced by\n[LLVM PR #134016](https://github.com/llvm/llvm-project/pull/134016); both are folded during SPIR-V → AMDGCN\nlowering, so exactly one branch survives in the emitted native code\nand the dispatch costs nothing at runtime.\n\n— asks`__builtin_amdgcn_processor_is(\"gfxXXX\")`\n\n*“is the device this is being lowered to a specific arch?”*Use it when the right answer truly varies per ASIC generation (e.g., a per-arch tuning constant).— asks`__builtin_amdgcn_is_invocable(builtin)`\n\n*“is this specific builtin callable on the target device’s ISA?”*Use it when the question is really*“does this hardware capability exist?”*; it survives ASIC additions and removals without a rewrite, where the processor-is check would need updating every time the supporting list changes.\n\nA realistic kernel picks the best implementation the device offers\nand falls back gracefully when nothing better is available. AMD’s\nmatrix accelerators come in two families — MFMA (matrix fused\nmultiply-add) on CDNA and WMMA on RDNA — and the two predicates\ncompose naturally for this dispatch: `__builtin_amdgcn_is_invocable`\n\nselects the *family* (MFMA? WMMA? neither?), and\n`__builtin_amdgcn_processor_is`\n\nnarrows *within* a family when a\nnewer ASIC adds a wider tile shape.\n\n```\n// One SPIR-V binary; one kernel; three paths. At JIT time exactly\n// one of these branches survives in the native code emitted for the\n// device. Adding a brand-new ASIC later requires no rebuild: the\n// kernel will pick the first capability it finds at JIT.\n__global__ void mma_kernel_16x16(const float* A, const float* B, float* C) {\n    if (__builtin_amdgcn_is_invocable(__builtin_amdgcn_mfma_f32_16x16x4f32)) {\n        // CDNA — matrix accelerator (MFMA family).\n        if (__builtin_amdgcn_processor_is(\"gfx950\")) {\n            // CDNA4 adds wider MFMA tiles; use them when present.\n            do_mma_mfma_wide(A, B, C);\n        } else {\n            // CDNA1/2/3 — baseline 16x16x4 fp32 MFMA.\n            do_mma_mfma(A, B, C);\n        }\n    } else if (__builtin_amdgcn_is_invocable(\n                   __builtin_amdgcn_wmma_f32_16x16x16_f16)) {\n        // RDNA3+ — matrix accelerator (WMMA family).\n        do_mma_wmma(A, B, C);\n    } else {\n        // No matrix accelerator on this device. Works on any AMDGCN,\n        // including older RDNA — the universal fallback.\n        do_mma_scalar(A, B, C);\n    }\n}\n```\n\nWhat each device sees after JIT:\n\n**MI300 / MI350 (CDNA3 / CDNA4)**— only the corresponding MFMA branch survives. Even the outer`is_invocable`\n\ntest on`mfma_f32_16x16x4f32`\n\nis folded to a literal`true`\n\n; the inner`processor_is(\"gfx950\")`\n\nis folded to`true`\n\non CDNA4 and`false`\n\non CDNA3.**RX 9070 XT (RDNA4) / RX 7900 (RDNA3)**— only the WMMA branch survives; the MFMA branch and its inner test are gone.** Anything older**— only`do_mma_scalar`\n\nsurvives.\n\nThis is what lets a single SPIR-V wheel ship for all of AMD — today and across GPU generations not yet announced:\n\n**Forward-compatible.** A binary built today will run on a GPU that ships next year. The runtime JITs it to whatever ISA the device reports; if that future ASIC introduces a*new*matrix family, adding it costs one branch in the`is_invocable`\n\nchain and none of the existing branches are touched.**Backward-compatible.** Older parts with no matrix unit at all still execute correctly via the scalar fallback.\n\n## What You Get[#](#what-you-get)\n\nProperty |\nSPIR-V flow |\nAOT-per-target flow |\n|---|---|---|\nBinary count |\n1 portable binary |\n1 per |\nForward compatibility |\nRuns on GPUs released after build |\nRequires rebuild for new ISAs |\nFat-binary size |\nFlat — see scaling section |\nGrows ~linearly with target count |\nBuild time |\nSingle device-codegen pass per TU |\nN device-codegen passes per TU |\nFramework awareness of HW |\nMinimal |\nBuild system enumerates targets |\nPer-arch dispatch (in-kernel) |\nLate-resolved at JIT, no runtime cost |\nCompile-time |\n\nThe forward-compatibility point is worth making concrete: a SPIR-V binary built today targets GPUs that did not exist when it was built. The runtime lowers it to the new ISA the first time the kernel runs.\n\n## What It Costs[#](#what-it-costs)\n\nTwo real costs that show up on any SPIR-V build, regardless of which framework or application is hosting it:\n\n**First-launch JIT latency.** SPIR-V → native lowering happens on the first kernel invocation on a given device. In the single-kernel benchmark below this adds roughly 70–100 ms. At framework scale the cost grows to*seconds per first-touched kernel*(numbers in a follow-up post). The cost is paid once per (TU, device) pair and then cached. The per-process JIT cache in`~/.cache/comgr/`\n\nalready mitigates this today: the cost is paid once per cache lifecycle. A planned further step is JIT-at-package-install — trading install time for zero startup latency, akin to shader pre-compilation in modern games.**Code that assumed compile-time arch macros has to be ported.** Patterns like`#if defined(__gfx942__)`\n\nneed to move to runtime predicates (`__builtin_amdgcn_processor_is`\n\n). With ZCFS-style late-resolved predicates this rewrite has no runtime overhead, but it is non-trivial work in places.\n\nA third cost is framework-specific: **library coverage**. ROCm’s\nper-arch device libraries do not yet all ship SPIR-V variants, so a\nSPIR-V application is bounded by its least-portable dependency. The\ncurrent state is covered in [Where things stand and what is\nnext](#where-things-stand-and-what-is-next) below.\n\nWith *What you get* and *What it costs* laid out conceptually, the\nnext section lets you measure both for yourself — a reproducible\nsingle-kernel benchmark covering build time, binary size, runtime,\nand a scalability comparison across target counts, on MI350X and\nspot-checked on RX 9070 XT.\n\n## Quick Start[#](#quick-start)\n\nThis section gets a SPIR-V binary in hand and confirms it produces\nthe same output as a per-`gfx`\n\nbuild. The deep dive that follows\nmeasures both: compile time, binary size, first-launch JIT cost, and\nscaling across target counts.\n\n**Bundled with this post.** A small set of files in the `src/`\n\ndirectory is everything you need to reproduce the numbers below:\n\n*Sources:*\n\n`custom_kernel_spirv.hip`\n\n— the SPIR-V variant (full source in`src/custom_kernel_spirv.hip`\n\n).`custom_kernel_standard.hip`\n\n— the per-`gfx`\n\nbaseline, identical except for one helper (shown in step 2).\n\n### Prerequisites[#](#prerequisites)\n\n**ROCm 7.2 or later.** All measurements in this post were captured inside the`rocm/dev-ubuntu-24.04:7.2.3`\n\ncontainer.A supported AMD GPU. All benchmark numbers use three compile modes — compiler\n\n**default**,, and`-O3`\n\n— via`-O3`\n\n+`--offload-compress`\n\n`BUILD_MODE`\n\n(see[FAQ](#faq)).A Linux environment with Docker.\n\nStart the container with GPU access:\n\n```\ndocker run -it --rm --device=/dev/kfd --device=/dev/dri \\\n    --group-add video -w /workspace \\\n    rocm/dev-ubuntu-24.04:7.2.3\n```\n\nVerify ROCm is visible inside the container:\n\n```\nrocminfo | grep gfx | head -1\n```\n\nOn a MI350 Series (MI350X/MI355X) node, you should see:\n\n```\nName:                    gfx950\n```\n\nThe ROCm LLVM toolchain (`clang++`\n\n, `llvm-objdump`\n\n) lives in\n`/opt/rocm/llvm/bin`\n\n. Put it on `PATH`\n\nbefore running the commands\nbelow:\n\n```\nexport PATH=/opt/rocm/llvm/bin:$PATH\n```\n\n### 1. Build a SPIR-V Binary[#](#build-a-spir-v-binary)\n\nUse the abstract `amdgcnspirv`\n\ntarget to generate AMDGCN-flavored SPIR-V:\n\n```\nclang++ -x hip --offload-arch=amdgcnspirv \\\n    -O3 custom_kernel_spirv.hip -o vecadd_spirv\n```\n\nYou can confirm no concrete target is embedded:\n\n```\nllvm-objdump --offloading ./vecadd_spirv\n# vecadd_spirv:   file format elf64-x86-64\n# Extracting offload bundle: vecadd_spirv.0.host-x86_64-unknown-linux-gnu-\n# Extracting offload bundle: vecadd_spirv.0.hip-spirv64-amd-amdhsa--amdgcnspirv\n```\n\n### 2. Build a Per-`gfx`\n\nBaseline[#](#build-a-per-gfx-baseline)\n\nThe `src/`\n\ndirectory already includes `custom_kernel_standard.hip`\n\n.\nBuild it targeting concrete GPUs (here the two CDNA datacenter parts\n`gfx942`\n\nand `gfx950`\n\n):\n\n```\nclang++ -x hip --offload-arch=gfx942,gfx950 \\\n    -O3 custom_kernel_standard.hip -o vecadd_fat\n```\n\nIn contrast to step 1, the fat binary embeds one native bundle per\nconcrete `gfx`\n\ntarget you asked for:\n\n```\nllvm-objdump --offloading ./vecadd_fat\n#vecadd_fat:     file format elf64-x86-64\n#Extracting offload bundle: vecadd_fat.0.host-x86_64-unknown-linux-gnu-\n#Extracting offload bundle: vecadd_fat.0.hipv4-amdgcn-amd-amdhsa--gfx942\n#Extracting offload bundle: vecadd_fat.0.hipv4-amdgcn-amd-amdhsa--gfx950\n```\n\nThe two source files are deliberately a minimal diff: same kernel,\nsame launches, same host logic. The *only* substantive difference is\nhow `items_per_thread_for_arch()`\n\nexpresses the per-arch dispatch —\ncompile-time `#if`\n\nmacros in the standard version, resolved per\n`--offload-arch`\n\ndevice-codegen pass at build time:\n\n```\n__device__ __forceinline__ int items_per_thread_for_arch() {\n#if defined(__gfx942__)\n    return 4;  // CDNA3\n#elif defined(__gfx950__)\n    return 4;  // CDNA4\n#elif defined(__gfx90a__)\n    return 4;  // CDNA2\n#elif defined(__gfx1201__)\n    return 2;  // RDNA4\n#elif defined(__gfx1100__)\n    return 2;  // RDNA3\n#else\n    return 1;\n#endif\n}\n```\n\nIn `custom_kernel_spirv.hip`\n\nit uses `__builtin_amdgcn_processor_is()`\n\n,\nresolved during SPIR-V → native lowering at JIT time:\n\n```\n__device__ __forceinline__ int items_per_thread_for_arch() {\n    if (__builtin_amdgcn_processor_is(\"gfx942\"))  return 4;  // CDNA3\n    if (__builtin_amdgcn_processor_is(\"gfx950\"))  return 4;  // CDNA4\n    if (__builtin_amdgcn_processor_is(\"gfx90a\"))  return 4;  // CDNA2\n    if (__builtin_amdgcn_processor_is(\"gfx1201\")) return 2;  // RDNA4\n    if (__builtin_amdgcn_processor_is(\"gfx1100\")) return 2;  // RDNA3\n    return 1;\n}\n```\n\nDifferent binding time, same end result — and that one-function diff is\nexactly what the benchmark numbers below are measuring. (The two\nbuiltin predicates used here — `__builtin_amdgcn_processor_is`\n\nand\n`__builtin_amdgcn_is_invocable`\n\n— are covered in detail in the\n[Late-resolved predicates in practice](#late-resolved-predicates-in-practice)\nsection above.)\n\n### 3. Run Both, Validate Functional Equivalence[#](#run-both-validate-functional-equivalence)\n\n```\n./vecadd_spirv\n```\n\nOutput:\n\n```\n=== SPIR-V HIP Vector Add (portable amdgcnspirv) ===\nBinary is portable -- native code generated at runtime\n\nDevice: AMD Radeon Graphics (gfx950:sramecc+:xnack-)\nBlock size: 256, 4 items/thread, grid: 1024 (chosen on host from gcnArchName)\n\nFirst launch (includes SPIR-V JIT compilation):\n  Kernel time: 83.122 ms  (includes JIT overhead)\nSecond launch (native code cached):\n  Kernel time: 0.010 ms  (native speed)\n\nResult: PASS\n```\n\nRun the fat:\n\n```\n./vecadd_fat\n```\n\nOutput:\n\n```\n=== Standard HIP Vector Add (per-gfx fat binary) ===\nBinary embeds native code for its --offload-arch targets\n\nDevice: AMD Radeon Graphics (gfx950:sramecc+:xnack-)\nBlock size: 256, 4 items/thread, grid: 1024 (chosen on host from gcnArchName)\n\nFirst launch (native code object from fat binary):\n  Kernel time: 0.556 ms\nSecond launch:\n  Kernel time: 0.007 ms  (native speed)\n\nResult: PASS\n```\n\nBoth printed `Result: PASS`\n\n, and displayed the first launch and second launch timing.\n\n### 4. Verify Portability on a Different GPU (e.g., gfx1201)[#](#verify-portability-on-a-different-gpu-e-g-gfx1201)\n\nThis step verifies the portability claim directly: `vecadd_spirv`\n\nstill runs on a GPU that wasn’t in the build, while `vecadd_fat`\n\nfails because its native bundles only target gfx942/gfx950.\n\nCopy both binaries to a non-CDNA system — we used a Radeon RX 9070\nXT (gfx1201) — and start the same container as in Prerequisites\n(`rocm/dev-ubuntu-24.04:7.2.3`\n\n).\n\nVerify ROCm sees the RDNA4 GPU inside the container:\n\n```\nrocminfo | grep gfx | head -1\n# Name:                    gfx1201\n```\n\nRun the SPIR-V binary:\n\n```\n./vecadd_spirv\n=== SPIR-V HIP Vector Add (portable amdgcnspirv) ===\nBinary is portable -- native code generated at runtime\n\nDevice: AMD Radeon RX 9070 XT (gfx1201)\nBlock size: 128, 2 items/thread, grid: 4096 (chosen on host from gcnArchName)\n\nFirst launch (includes SPIR-V JIT compilation):\n  Kernel time: 67.528 ms  (includes JIT overhead)\nSecond launch (native code cached):\n  Kernel time: 0.020 ms  (native speed)\n\nResult: PASS\n```\n\nThe same binary that ran on MI350X (gfx950) now runs on RDNA4 — no rebuild, JIT lowering on first launch.\n\nNow the fat binary:\n\n```\n./vecadd_fat\n# Segmentation fault (core dumped)\n```\n\nThe segfault is the surface symptom; the actual diagnostic comes from the HIP runtime log:\n\n```\nAMD_LOG_LEVEL=3 ./vecadd_fat\n:3:hip_module.cpp           :825 : 836675924502 us:   hipLaunchKernel ( 0x200ba0, {4096,1,1}, {128,1,1}, 0x7ffcca7d9b20, 0, char array:<null> )\n:3:hip_fatbin.cpp           :524 : 836675924584 us:  Forcing SPIRV: false\n:1:hip_fatbin.cpp           :687 : 836675924592 us:  No compatible code objects found for: gfx1201, value of HIP_FORCE_SPIRV_CODEOBJECT: 0\n```\n\nNo compatible code object for `gfx1201`\n\n— the fat binary only\ncontains gfx942 and gfx950 bundles, so the runtime has nothing to\nlaunch. The SPIR-V binary, by contrast, carries the portable IR and\nJIT-lowers to whatever ISA `rocminfo`\n\nreports.\n\n## Deep Dive: Benchmarking SPIR-V vs Fat[#](#deep-dive-benchmarking-spir-v-vs-fat)\n\nThe lead measurement is on a CDNA4 datacenter part (`gfx950`\n\n,\nMI350X), spot-checked on RDNA4. All numbers in this section come from\none pinned container image:\n\nComponent |\nVersion |\n|---|---|\nContainer image |\n|\nOS |\nUbuntu 24.04.4 LTS |\nROCm |\n7.2.3 |\nCompiler |\nAMD clang 22.0.0git ( |\nGPU under test |\nAMD Instinct MI350X ( |\n\n*Driver Scripts:*\n\n`benchmark.sh`\n\n— clears`~/.cache/comgr/`\n\n, rebuilds both binaries, and prints the per-arch comparison table. Takes optional`[runs]`\n\n,`GFX_TARGETS=…`\n\n, and`BUILD_MODE`\n\n(`default`\n\n,`O3`\n\n,`O3compress`\n\n).`run_singlekernel_scaling.sh`\n\n— sweeps 1/2/4/8/15 native targets plus`amdgcnspirv`\n\n, median of 3 trials per row (honours`BUILD_MODE`\n\n).`run_all_build_modes.sh`\n\n— runs both scripts for all three`BUILD_MODE`\n\nvalues and writes`artifacts/benchmark/results.csv`\n\nplus`artifacts/_singlekernel_scaling/data_{default,O3,O3compress}.csv`\n\n.\n\n### Cold-Cache Methodology[#](#cold-cache-methodology)\n\nThe runtime caches JIT output in `~/.cache/comgr/`\n\n; without clearing\nit, a SPIR-V “cold” run is actually warm. All three driver scripts\nused below (`benchmark.sh`\n\n, `run_singlekernel_scaling.sh`\n\n, and the\n`run_all_build_modes.sh`\n\nwrapper) clear `~/.cache/comgr/`\n\nand\n`/tmp/comgr-*`\n\nbefore each run so cold-start numbers reflect real\nfirst-launch JIT cost. Each builds the binaries, runs each N times,\nand prints compile time / binary size / first-launch / averages; the\nresult tables below are pasted directly from their output.\n\n### Build Flags: Default vs `-O3`\n\nvs `-O3`\n\n+ Compress[#](#build-flags-default-vs-o3-vs-o3-compress)\n\n**(1) Single-target build-flag sweep.** This is the first of three\nmeasurements. It isolates the effect of the two compile flags —\n`-O3`\n\nand `--offload-compress`\n\n— at a single target (`gfx950`\n\n),\ncomparing fat against `amdgcnspirv`\n\nacross the three `BUILD_MODE`\n\ncombinations exposed by the script (`default`\n\n, `O3`\n\n, `O3compress`\n\n).\n`BUILD_MODE`\n\nselects the compile flags: `default`\n\nuses the compiler’s defaults (no explicit `-O`\n\nflag), `O3`\n\nadds `-O3`\n\n, and `O3compress`\n\nadds `-O3 --offload-compress`\n\n.\nReproduce all three side by side:\n\n```\n./run_all_build_modes.sh\n```\n\nThe table below shows **binary size only** — the clearest single\ndimension to demonstrate flag impact. Figure 4 adds compile time\nand steady-state runtime across the same three modes:\n\n|\nfat ( |\n|\n|---|---|---|\n|\n56,256 B |\n44,864 B |\n|\n23,136 B |\n44,200 B |\n|\n14,784 B |\n19,304 B |\n\nAt **default**, SPIR-V can look smaller than single-target fat. At ** -O3**, fat shrinks ~2.4× while\nSPIR-V on-disk size is essentially unchanged.\n\n**shrinks both. Figure 4 shows all three dimensions side by side.**\n\n`--offload-compress`\n\nFigure 5 below is the output of `run_singlekernel_scaling.sh`\n\nrun\nacross all three `BUILD_MODE`\n\nvalues. It sweeps fat builds from 1 to\n15 targets (1, 2, 4, 8, 15) and compares each against `amdgcnspirv`\n\non both binary size and compile time — fat grows on both axes under\nevery flag setting, while SPIR-V stays flat.\n\n### Release Build (`BUILD_MODE=O3`\n\n) — Primary Comparison[#](#release-build-build-mode-o3-primary-comparison)\n\n**(2) Release build at 1 vs 2 targets.** Pinning the build mode to\nwhat frameworks actually ship (`-O3`\n\n), and stepping target count\nfrom one to two, to show what happens to compile time, binary size,\nand runtime as the arch list grows by one entry. `benchmark.sh`\n\nwith\n`BUILD_MODE=O3`\n\nand `GFX_TARGETS=…`\n\n:\n\n```\nBUILD_MODE=O3 GFX_TARGETS=gfx950          ./benchmark.sh 5\nBUILD_MODE=O3 GFX_TARGETS=\"gfx942 gfx950\" ./benchmark.sh 5\n```\n\n5 runs each, cache cleared between:\n\nWhat to look for.At one or two targets, SPIR-V does not undercut fat on absolute size or compile time — a single-arch fat binary is actually smaller here. SPIR-V’s wins areflat scaling(no growth with target count) andforward compatibility(the same binary lowers to GPUs that did not exist at build time). The 15-target sweep below makes that shape visible.\n\nfat ( |\nfat ( |\n|\n|\n|---|---|---|---|\nCompile time |\n733 ms |\n1,085 ms |\n712 ms |\nBinary size |\n23,136 B |\n31,328 B |\n44,200 B |\nFirst run (cold) |\n411 ms |\n413 ms |\n357 ms |\nAvg (excl. first) |\n263 ms |\n272 ms |\n281 ms |\n\nFigure 6 visualizes these numbers.\n\nReading the numbers:\n\n**Compile time is flat for SPIR-V**(~710 ms whether you ask for one arch or two) while the fat side scales: 733 ms for`gfx950`\n\nalone, 1,085 ms once`gfx942`\n\nis added. Every extra`gfxXXX`\n\nadds another device-codegen pass.**SPIR-V binary size is constant** at 44,200 B regardless of arch count; the fat binary grows 23,136 → 31,328 B going from one target to two. On this kernel a single-target native build is actually*smaller*than the SPIR-V artifact — the win is flat size as the arch list grows, not a smaller baseline.**First-launch JIT overhead is small**— ~74 ms above steady state here (351 ms first run vs 277 ms steady). Notably, fat-binary cold start (407–414 ms) is*larger*than SPIR-V’s (351 ms), because it is dominated by HIP runtime init rather than kernel codegen.**Steady state matches native within noise**— 266–268 ms (fat) vs 277 ms (SPIR-V), a few percent that is inside run-to-run variance on this kernel.\n\nThese are *uncompressed* bundle sizes — `--offload-compress`\n\nshrinks\nboth sides without changing the shape (see the scaling table below).\n\n### Scaling to 15 Targets[#](#scaling-to-15-targets)\n\n**(3) Full scaling sweep.** The release-build pattern from (2)\nextended across the full target-count range — 1, 2, 4, 8, and 15\n— to show that fat compile time and binary size grow with target\ncount while SPIR-V stays flat. `run_singlekernel_scaling.sh`\n\ndoes\nthis for `amdgcnspirv`\n\n(median of 3 trials). ** BUILD_MODE=O3** is\nthe release row used in Figure 6;\n\n**shows the unoptimized fat growth that makes SPIR-V look smaller at one target.**\n\n`default`\n\nTargets |\n|\n|\n|\n|---|---|---|---|\n1 ( |\n55.0 |\n22.6 |\n14.5 |\n2 ( |\n94.9 |\n30.6 |\n14.5 |\n15 (full default) |\n615.0 |\n134.6 |\n16.4 |\n\nSPIR-V (`n=0`\n\n) is flat per mode: 43.7 KiB (`default`\n\n), 43.1 KiB (`O3`\n\n),\n18.9 KiB (`O3compress`\n\n). Only the fat columns grow with target count.\nFigures 7 and 8 visualize this scaling.\n\nThree takeaways (release `-O3`\n\nrow):\n\n**Compile time scales linearly with arch count**— ~340 ms per added`--offload-arch`\n\n; SPIR-V stays ~678 ms.**Binary size**— fat grows 22.6 → 134.6 KiB (1 → 15 targets); SPIR-V stays ~43 KiB.** With**— fat-15 drops to 16.4 KiB (8.2× smaller than uncompressed fat-15); SPIR-V compresses to 18.9 KiB — the two converge. Compile-time flatness and forward compatibility remain the durable wins.`O3compress`\n\nWe spot-checked the same kernel and workflow on RX 9070 XT (RDNA4,\n`gfx1201`\n\n); compile-time flatness, fat scaling with target count, and\nsteady-state parity with native matched the CDNA pattern, as shown in\nFigure 9 below.\n\n## FAQ[#](#faq)\n\n**Q. How does -O3 affect the binary sizes in this post?**\n\nVery differently for fat and SPIR-V, because they ship different things.\n\nA **fat binary** embeds finished **native AMDGCN** code objects — one per\n`--offload-arch`\n\n. `-O3`\n\nruns the full device optimization pipeline before\nthose objects are sealed (dead-code elimination, inlining, loop unrolling,\nper-arch `#ifdef`\n\ncollapse), so each native slice shrinks dramatically. On\nthe MI350X demo kernel (`gfx950`\n\n), single-target fat went 56,256 B →\n23,136 B (`-O3`\n\n) → 14,784 B (`-O3`\n\n+ `--offload-compress`\n\n); SPIR-V went\n44,864 B → 44,200 B → 19,304 B. Run `./run_all_build_modes.sh`\n\nfor the\nfull matrix.\n\nA **SPIR-V binary** contains **portable IR**, not native ISA. The LLVM IR\nthat feeds SPIR-V generation is near-pristine Clang output — optimization\nis deliberately deferred so that at JIT time the AMDGPU backend can\nresume as if compilation were simply suspended after the front end.\nThis is why the on-disk SPIR-V artifact remains large even at `-O3`\n\n:\nthe size reduction shows up when the runtime lowers to native code on\nfirst launch, not in the portable artifact.\n\nThat is why, at default optimization, SPIR-V can look *smaller* than\nsingle-target fat, while at `-O3`\n\nsingle-target fat can be *smaller*\nthan SPIR-V — but multi-target fat still grows with every arch and\nSPIR-V stays flat. Frameworks ship release builds, so the primary\ncomparison tables use `BUILD_MODE=O3`\n\n; the three-mode sweep\n(`run_all_build_modes.sh`\n\n) and Figures 4 and 5 show default vs release\nvs release+compress side by side.\n\n**Q. What’s the relationship between clang++, amdclang++, and\nhipcc?**\n\nThey are layers over the *same* compiler, not alternatives:\n\n(`clang++`\n\n`/opt/rocm/llvm/bin/clang++`\n\n) is the compiler — a symlink to`clang-22`\n\n, AMD’s build of upstream LLVM/Clang shipped under the vanilla name (version string:`AMD clang 22.0.0git`\n\nin ROCm 7.2.3). HIP is first-class in mainline Clang, so`clang++ -x hip --offload-arch=…`\n\nis the whole toolchain with nothing above it.is AMD’s`amdclang++`\n\n*branded entry point*to that same compiler — a small (~115 KB) launcher (`amdllvm`\n\n) that applies ROCm configuration and execs`clang`\n\n. Same version string, same output. AMD recommends it so you explicitly select the ROCm compiler over a system`/usr/bin/clang++`\n\n.is a`hipcc`\n\n*convenience driver*one level up: it auto-adds HIP include paths, applies`-x hip`\n\n, adds device-library paths, derives`--offload-arch`\n\nfrom environment variables, links`libamdhip64`\n\n, then invokes`amdclang++`\n\n/`clang++`\n\nunderneath.\n\nThese three produce an identical object given matching flags:\n\n```\nhipcc      --offload-arch=amdgcnspirv -O3 k.hip -o k\namdclang++ -x hip --offload-arch=amdgcnspirv -O3 k.hip -o k\nclang++    -x hip --offload-arch=amdgcnspirv -O3 k.hip -o k\n```\n\nThe examples in this post call `clang++`\n\ndirectly because the SPIR-V\nexperiments need exact control over the offload flags\n(`--offload-arch=amdgcnspirv`\n\n, `--offload-compress`\n\n,\n`--offload-compression-level`\n\n). `hipcc`\n\nwould auto-inject an arch from\nthe environment, which fights that; `amdclang++`\n\nwould be identical.\n\n**Q. What happens if I pass both --offload-arch=amdgcnspirv and a\nconcrete --offload-arch=gfxXXX in the same compile?**\n\nThe compiler embeds *both* offload bundles in the output. At launch, the\nROCm runtime walks the bundles and picks the native `gfxXXX`\n\nslice if\nits arch matches the device; otherwise it falls back to the\n`amdgcnspirv`\n\nslice and JIT-lowers it. We confirmed this on a gfx950\nhost with `AMD_LOG_LEVEL=3`\n\n:\n\n``` bash\n$ clang++ -x hip --offload-arch=amdgcnspirv,gfx950 \\\n    --offload-compress -O3 vecadd.hip -o vecadd\n$ llvm-objdump --offloading vecadd\n  Extracting offload bundle: vecadd.0.host-x86_64-unknown-linux-gnu-\n  Extracting offload bundle: vecadd.0.hipv4-spirv64-amd-amdhsa-unknown-amdgcnspirv\n  Extracting offload bundle: vecadd.0.hipv4-amdgcn-amd-amdhsa--gfx950\n\n$ AMD_LOG_LEVEL=3 ./vecadd 2>&1 | grep \"Inserting bundle\"\n  Inserting bundle entry of amdgcn-amd-amdhsa--gfx950:sramecc+:xnack-\n```\n\nFirst-launch kernel time on gfx950 was **4.6 ms** (no JIT — used the\nnative bundle) vs **~105 ms** for a SPIR-V-only build on the same\ndevice (JIT path). So “native matches win, SPIR-V is the catch-all\nfallback” is the load-bearing rule. This makes the obvious production\nstrategy reasonable: ship native bundles for the architectures you have\nperformance-tuned for, plus an `amdgcnspirv`\n\nslice as the universal\nfallback for everything else.\n\n**Q. Which GPU architectures can run SPIR-V binaries today?**\n\nThe `amdgcnspirv`\n\nruntime JIT path in ROCm 7.2 supports the same set\nof architectures that ROCm itself supports — CDNA (gfx908, gfx90a,\ngfx942, gfx950), RDNA3 (gfx1100, gfx1101, gfx1102), and RDNA4\n(gfx1200, gfx1201). Older architectures that are no longer on the\nROCm support matrix (e.g., gfx900, gfx906) are not tested or\nguaranteed to work with the SPIR-V JIT path. The forward-compatibility\npromise applies to *future* architectures — a SPIR-V binary built\ntoday will run on a GPU that ships after the binary was built, as long\nas the ROCm runtime on that system supports the new architecture.\n\n**Q. How is the first-launch JIT cost mitigated?**\n\nTwo mitigations, one shipping and one under development:\n\n**Caching** is already implemented. The first SPIR-V → native lowering for a given (TU, device) pair is written to a per-process JIT cache, so every launch after the first reuses native code instead of re-lowering. The cost is paid once per cache lifecycle, not once per run.**Finalisation / JIT at package install** is under development. It moves the lowering to package-install time (`pip install`\n\n,`apt install`\n\n,`winget install`\n\n, …) so the artifact is already native by the time it first runs. This completely hides the run-time latency, trading it for a longer install — the same deal modern games make when they pre-compile shaders on install.\n\n**Q. Where is the JIT output cached?**\n\n`~/.cache/comgr/`\n\n(per-process, per-user). Removing this directory\nforces a cold-start JIT on the next launch — the measurement loop\nabove (`rm -rf ~/.cache/comgr/ /tmp/comgr-*`\n\n) does this between runs\nto capture honest first-launch cost. The JIT-at-package-install path\nbeing designed will populate this cache (or its successor) once per\npackage install, eliminating the per-machine cold start entirely.\n\n**Q. Is --offload-compress required to run SPIR-V, and is it worth\ntuning?**\n\nNo — `--offload-compress`\n\nis purely a binary-size optimization (zstd\ncompression of the offload bundles) and is not functionally required on\nany architecture or ROCm version. Frameworks that ship SPIR-V will\ntypically enable it unconditionally for the size win (PyTorch does, in\n[ cmake/Dependencies.cmake:1026](https://github.com/pytorch/pytorch/pull/143986)). The default compression\nlevel (zstd-3) is already near-optimal: SPIR-V compresses ~4.3× and\nnative AMDGCN ~3.6×, and higher levels yield diminishing returns. In\npractice: leave\n\n`--offload-compress`\n\non, leave the level at default,\nmove on.### Debugging SPIR-V Binaries (Today)[#](#debugging-spir-v-binaries-today)\n\nSPIR-V changes *when* device code materializes: the file you ship holds\nportable IR; the AMDGCN that actually runs is produced at first kernel\nlaunch (or reused from `~/.cache/comgr/`\n\nafter that). **Host debugging\nis unchanged** — compile the x86_64 side with `-g`\n\nand use `gdb`\n\nas usual.\n**Device debugging** goes through the same ROCm tools (`rocgdb`\n\n, etc.), but\nagainst the JIT’d native code object, not the SPIR-V bundle on disk. Debug-info\ncoverage on the SPIR-V path is still a subset of a native per-`gfx`\n\nbuild;\nthat gap is being closed, not a different debugging model.\n\nFor quick triage on this demo: `llvm-objdump --offloading`\n\nto see which\nbundles are embedded; `AMD_LOG_LEVEL=3`\n\nto watch the runtime pick native\nvs SPIR-V and JIT; `rm -rf ~/.cache/comgr/`\n\nto force a cold lowering.\nA dedicated follow-up post will walk through end-to-end SPIR-V debug\nworkflows — this one stays focused on the compile and portability story.\n\n## Where Things Stand and What Is Next[#](#where-things-stand-and-what-is-next)\n\nWhat is shipping today:\n\nThe\n\n`amdgcnspirv`\n\ntarget in Clang and the LLVM SPIR-V toolchain.The SPIRV-LLVM Translator path (production today).\n\nLate-resolved feature identifying predicates (\n\n[LLVM PR #134016](https://github.com/llvm/llvm-project/pull/134016)) and the supporting`llvm.spv.named.boolean.spec.constant`\n\nintrinsic ([LLVM PR #187420](https://github.com/llvm/llvm-project/pull/187420)).Per-process JIT caching at\n\n`~/.cache/comgr/`\n\n.\n\nWhat is in flight:\n\nThe in-tree LLVM SPIR-V backend, on track to replace the translator as the default lowering path.\n\nJIT-at-package-install, eliminating first-launch latency for shipped artifacts. The strongest argument for it shows up at framework scale, where first-launch JIT is in seconds, not the milliseconds the single-kernel demo suggests.\n\nRicher device debug-info through the SPIR-V → AMDGCN JIT path (see\n\n[Debugging SPIR-V binaries (today)](#debugging-spir-v-binaries-today)).\n\nWhat is *not* solved yet:\n\n**SPIR-V builds for the per-arch device libraries.** Coverage is partial and uneven, and remains the practical limit on portability for any real GPU workload built on top of ROCm. As of writing:*Building for*rocPRIM, rocThrust, rocRAND, rocBLAS.`amdgcnspirv`\n\ntoday:*In progress:*Composable Kernel ([PR #6304](https://github.com/ROCm/rocm-libraries/pull/6304)), rocFFT, and rocSPARSE.\n\nThese are AMD’s own libraries, so the fair question is why the rest are not further along. Sequencing and difficulty, not reluctance: enablement runs toolchain → framework → libraries, and the libraries are also the hardest code to port — the most per-arch-tuned part of the stack (MFMA/WMMA intrinsics, inline GCN assembly, per-arch kernel selection), since peak per-GPU performance is their whole purpose.\n\n## Summary[#](#summary)\n\nIn this post we explored how SPIR-V brings a compile-once, specialize-on-device model to AMD GPUs through the ROCm toolchain — turning the pain of building and shipping GPU code for every architecture into a single portable binary that specializes at runtime. Here is what that looks like in practice:\n\n**One binary, many GPUs**— a single`amdgcnspirv`\n\nartifact lowers to whichever AMDGCN ISA the device reports at first kernel launch.**Build time and binary size stay flat**— no device-codegen fan-out as your arch list grows. Add targets without paying for them at build time.** Forward compatibility comes for free**— the same binary runs on GPUs that did not exist when it was built.** Steady-state performance matches native**— the costs are a one-time first-launch JIT (~70–100 ms at single-kernel scale) and porting away from compile-time arch macros.**Library coverage is the practical ceiling today**— portability is bounded by the least-portable dependency, but that gap is closing: rocBLAS, Composable Kernel, rocFFT, and rocSPARSE are in progress.\n\nAll scripts and sources to reproduce the numbers in this post are included above. Follow-up posts will cover SPIR-V at PyTorch framework scale and end-to-end device debugging workflows — stay tuned.\n\nIf you have questions or feedback, please reach out on GitHub\n[Discussions](https://github.com/ROCm/rocm-blogs/discussions).\n\n## Additional Resources[#](#additional-resources)\n\nThe PRs and docs cited inline above, collected here for convenience.\n\n### Specs & docs[#](#specs-docs)\n\n### LLVM PRs[#](#llvm-prs)\n\n[#134016 — late-resolved feature identifying predicates](https://github.com/llvm/llvm-project/pull/134016)(commit`18e695890306`\n\n, 2026-03-30)[#187420 —](https://github.com/llvm/llvm-project/pull/187420)(commit`llvm.spv.named.boolean.spec.constant`\n\nintrinsic`d8104bfc9e9d`\n\n, 2026-03-20)\n\n### ROCm library PRs[#](#rocm-library-prs)\n\n[#2774 — rocBLAS: SPIR-V (](https://github.com/ROCm/rocm-libraries/pull/2774)(ROCm/rocm-libraries, open)`amdgcnspirv`\n\n) target support[#6304 — CK: add SPIR-V (](https://github.com/ROCm/rocm-libraries/pull/6304)(ROCm/rocm-libraries, open)`amdgcnspirv`\n\n) target support\n\n## Disclaimers[#](#disclaimers)\n\nThe information presented in this document is for informational purposes only and may contain technical inaccuracies, omissions, and typographical errors. The information contained herein is subject to change and may be rendered inaccurate for many reasons, including but not limited to product and roadmap changes, component and motherboard version changes, new model and/or product releases, product differences between differing manufacturers, software changes, BIOS flashes, firmware upgrades, or the like. Any computer system has risks of security vulnerabilities that cannot be completely prevented or mitigated. AMD assumes no obligation to update or otherwise correct or revise this information. However, AMD reserves the right to revise this information and to make changes from time to time to the content hereof without obligation of AMD to notify any person of such revisions or changes. THIS INFORMATION IS PROVIDED “AS IS.” AMD MAKES NO REPRESENTATIONS OR WARRANTIES WITH RESPECT TO THE CONTENTS HEREOF AND ASSUMES NO RESPONSIBILITY FOR ANY INACCURACIES, ERRORS, OR OMISSIONS THAT MAY APPEAR IN THIS INFORMATION. AMD SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR ANY PARTICULAR PURPOSE. IN NO EVENT WILL AMD BE LIABLE TO ANY PERSON FOR ANY RELIANCE, DIRECT, INDIRECT, SPECIAL, OR OTHER CONSEQUENTIAL DAMAGES ARISING FROM THE USE OF ANY INFORMATION CONTAINED HEREIN, EVEN IF AMD IS EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. AMD, the AMD Arrow logo, and combinations thereof are trademarks of Advanced Micro Devices, Inc. Other product names used in this publication are for identification purposes only and may be trademarks of their respective companies. © 2026 Advanced Micro Devices, Inc. All rights reserved.", "url": "https://wpnews.pro/news/spir-v-on-rocm-a-portable-ir-for-amd-gpus", "canonical_source": "https://rocm.blogs.amd.com/software-tools-optimization/spir-v-rocm/README.html", "published_at": "2026-07-20 00:00:00+00:00", "updated_at": "2026-07-20 15:47:59.654841+00:00", "lang": "en", "topics": ["developer-tools", "ai-infrastructure"], "entities": ["AMD", "ROCm", "SPIR-V", "MI350X", "RX 9070 XT", "CDNA4", "RDNA4", "PyTorch"], "alternates": {"html": "https://wpnews.pro/news/spir-v-on-rocm-a-portable-ir-for-amd-gpus", "markdown": "https://wpnews.pro/news/spir-v-on-rocm-a-portable-ir-for-amd-gpus.md", "text": "https://wpnews.pro/news/spir-v-on-rocm-a-portable-ir-for-amd-gpus.txt", "jsonld": "https://wpnews.pro/news/spir-v-on-rocm-a-portable-ir-for-amd-gpus.jsonld"}}