Add ROCm build variant + small inference-config wins - #147
Draft
laziukdavid wants to merge 3 commits into
Draft
Conversation
The CPU EP uses ORT's default intra-op thread count, which is Runtime.availableProcessors() (every logical core on the host). On systems with 16+ logical cores this is a measurable regression for this workload — the 2 GB base U-Net is memory-bandwidth-bound and additional threads add cache contention. Benchmarking on a 32-thread Strix Halo found 16 threads optimal end-to-end, roughly 10-15% faster than the default. Adds a new inference.cpu_intra_threads config key (0 = ORT default, anything else pins the intra-op count and forces inter-op to 1). Default remains 0 so existing users see no behavior change; the override is a free win for tuning.
WorldPipeline.cacheLimitBytes was hardcoded to 100 MB and LocalTerrainProvider.MAX_CACHE_SIZE to 64 heightmap regions. These are both reasonable defaults for low-RAM clients but leave performance on the table for users with plenty of memory — and for dedicated servers in particular, where Chunky pre-gen revisits adjacent windows constantly. Two new config keys: inference.cache_limit_mb (default 100, 0 = unlimited) and inference.heightmap_cache_size (default 64). Existing behavior is unchanged because both defaults match the previous hardcoded values.
The CPU build is the only path the upstream ships for Linux machines without an NVIDIA
GPU. On any AMD-on-Linux setup — including the increasingly common ROCm-supported APUs
like Strix Halo / Ryzen AI Max+ — this leaves a large amount of inference throughput on
the table: in benchmarks on a Strix Halo (gfx1151), the CPU build gets ~290 chunks/sec
at world-scale 6 while a ROCm build of the same pipeline gets ~2050 c/s, a ~7x speedup.
Key bits of this patch:
- New -PuseRocm=true gradle property and matching buildRocm task. Mirrors the existing
-PuseCuda / -PuseDml / -PuseCpu wiring.
- libs/onnxruntime-rocm.jar bundled in this commit (300 MB). The jar combines:
* Java bindings from Maven Central onnxruntime:1.21.0
* libonnxruntime4j_jni.so recompiled from upstream onnxruntime v1.21.0 sources with
-DUSE_ROCM=1 (the Maven-published jar is built without it, so addROCM() throws
"This binary was not compiled with ROCM support.")
* Native libonnxruntime.so + provider .so files from AMD's official onnxruntime-rocm
Python wheel (matched version)
* The auditwheel-bundled rocm-smi dependency that the ROCm provider needs at runtime
scripts/build-onnxruntime-rocm-jar.sh reproduces this jar from versioned upstream
artifacts, intended for CI / users building from source.
- OnnxModel.addGpuProvider() gains a ROCm branch (between DirectML and CoreML in the
fallback chain) and a static helper that pre-loads librocm_smi64-*.so.7.7.60404 from
the classpath into a tempdir + System.load()s it. Without the pre-load, the ROCm
provider library dlopen()s its dependency by SONAME and fails since the auditwheel-
renamed file isn't on any standard search path.
- ROCM_INSTALL.md documents the user-facing setup: install ROCm 6.4+ system packages,
add user to render/video groups, and (only on gfx1151 / Strix Halo iGPUs) set
HSA_OVERRIDE_GFX_VERSION=11.0.0 so MIOpen picks up gfx1100 kernels. The override
becomes unnecessary once ROCm gains first-class gfx1151 support.
- README updated to advertise the new build variant alongside the existing three.
Verified on Strix Halo (Fedora 43, ROCm 6.4.4, Java 21): td-explore + Chunky pre-gen
both produce identical terrain to the CPU build with ~7x throughput improvement.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add ROCm build variant + small inference-config wins
Adds a
-rocmbuild variant for AMD GPUs and APUs on Linux, alongside two smaller fixes that benefit all build variants. The big change is the ROCm variant: on a Strix Halo (Ryzen AI Max+ 395, gfx1151) it produces ~2050 c/s in synthetic inference benchmarks vs the CPU build's ~290 c/s, and ~1100 c/s in a real deployment with C2ME/Chunky.What this PR does
1. New
-rocmbuild variant —./gradlew build -PuseRocm=trueproducesterrain-diffusion-mc-<ver>-rocm+<mc>.jar. The wiring mirrors the existing-cuda/-windows/-cpuvariants:useRocmbuild propertybuildRocmgradle task added to the existingbuildAllumbrellaaddROCM(0)provider call added toOnnxModel.addGpuProvider()between DirectML and CoreML in the fallback chainsetResolvedProviderOnce("ROCm")so the log lineTerrain diffusion inference: ROCmmakes it obvious which backend is activeROCM_INSTALL.mduser-facing setup doc parallel to the existingCUDA_INSTALL.md2. CPU intra-op thread cap — adds
inference.cpu_intra_threadsconfig key. Defaults to 0 (= ORT default = all logical cores), preserving existing behavior. Operators on 16+ core machines can pin this to a lower value to recover 10-15% throughput; on the test box (32 logical cores) 16 threads beats the default by 12%.3. Configurable cache sizes —
inference.cache_limit_mb(was hardcoded100L * 1024 * 1024in WorldPipeline) andinference.heightmap_cache_size(was hardcoded64in LocalTerrainProvider). Defaults match the previous hardcoded values, so existing users see no behavior change. Dedicated servers with plenty of RAM can raise the cache limit to keep more windows resident during Chunky pre-gen and exploration.Why ROCm needs both an EP wire-up and a hybrid jar
The upstream ONNX Runtime Java package on Maven Central is built with the CPU EP only. Even though the Java API has
OrtSession.SessionOptions.addROCM(int), the JNI lib that backs it is conditionally compiled with#ifdef USE_ROCM, and the Maven build doesn't set that flag. CallingaddROCM(0)against the Maven jar throwsOrtException: This binary was not compiled with ROCM support.AMD publishes the ROCm-built
libonnxruntime.so+libonnxruntime_providers_rocm.soonly as part of their Python wheel (onnxruntime-rocmonrepo.radeon.com). There is no Java distribution.This PR ships a small build script (
scripts/build-onnxruntime-rocm-jar.sh) that assembles a hybrid jar from versioned upstream artifacts:com.microsoft.onnxruntime:onnxruntime:1.21.0on Maven Centrallibonnxruntime4j_jni.sorecompiled from the matching upstream source tag with-DUSE_ROCM=1onnxruntime-rocm-1.21.0-cp312-cp312-manylinux*wheellibrocm_smi64-*.so.7.7.60404that the ROCm provider library NEEDs at runtimeThe resulting
libs/onnxruntime-rocm-1.21.0.jaris ~290 MB, dominated by the ROCm provider library which statically links MIOpen kernel binaries. The jar itself is NOT committed (too large for normal git, and it's a reproducible derivative of upstream artifacts) —libs/is already in.gitignore. Users running-PuseRocm=truefor the first time run the build script once before invoking gradle.At mod init we pre-load the auditwheel-renamed
librocm_smi64-*.sofrom the classpath usingOnnxModel.preloadBundledRocmSmiOnce(): extract to a tempdir,System.load(). Without this,libonnxruntime_providers_rocm.sodlopens by SONAME and ld.so cannot find the file (it has an auditwheel-injected hash in its filename and is not in any standard library path). The pre-load registers it in ld.so's loaded-libraries cache so the subsequent dlopen succeeds.User-facing setup
Documented in
ROCM_INSTALL.md. Summary:amdgpu-install --usecase=rocmon Ubuntu,dnf install rocm-runtime hipblas hipfft miopen rocblason Fedora, etc.)renderandvideogroups for/dev/kfdand/dev/dri/renderD*accessHSA_OVERRIDE_GFX_VERSION=11.0.0in the launch environment. ROCm 6.4.x doesn't ship MIOpen kernels for gfx1151; the override routes them to gfx1100 which has working kernels for this workload. The override becomes unnecessary once ROCm gains first-class gfx1151 support.-rocmjar inmods/.Benchmarks
Method: standalone Python harness that loads the same .onnx files the mod uses, runs the WorldPipeline call pattern (20 coarse + 2 base + 1 decoder per 256-block tile) with synthetic inputs, measures wall-clock end-to-end at world-scale 6. Reproduce with:
Results on Strix Halo (Ryzen AI Max+ 395, 128 GB unified, gfx1151 iGPU, Fedora 43, ROCm 6.4.4):
-cpubuild (intra=default 32)-cpubuild (intra=16, with this PR'sinference.cpu_intra_threads=16)-rocmbuild, batch=4 latentThe deployment number is lower than the harness because the harness measures pure ONNX forward-pass time while the deployment also pays for Java glue (tile blending, MemoryTileStore HashMap synchronization, JNI tensor allocation) and C2ME's chunk-worker queueing. Both rates are stable at scale: 200 tiles ≈ 50k chunks shows the same per-tile cost as 4 tiles.
Per-stage breakdown of the synthetic harness ROCm run:
What this PR does not do
getOrCreateBatched(4), but the coarse stage usesgetOrCreateand processes its 20 diffusion steps sequentially per tile. Restructuring to batch coarse across adjacent tiles would yield ~5-7% more on ROCm (462 sps at batch=2 vs 376 sps at batch=1), but hurts CPU (74 → 63 sps), so it would need to be EP-conditional. Deferred — it's a non-trivial refactor and the gain is small relative to this PR's scope.Verification
./gradlew build -PuseCpu=true✓./gradlew build -PuseDml=true✓./gradlew build -PuseCuda=true✓./gradlew build -PuseRocm=true✓ (after runningscripts/build-onnxruntime-rocm-jar.shto assemble libs/onnxruntime-rocm-1.21.0.jar)-cpu,-cuda, and-windowsbuild artifacts are byte-identical to a build before this PR (verifiedjarcontents are unchanged for those variants — the only changed files are commit 1 and commit 2's adjustments which only affect runtime behavior when the new config keys are set).-rocmjar to a Fabric server on Strix Halo, ran Chunky pre-gen at scale=6 over multiple tiles: terrain generates correctly, journal showsTerrain diffusion inference: ROCm, no crashes (after bumpingmax-tick-timein server.properties to tolerate first-call MIOpen JIT compilation — documented in ROCM_INSTALL.md).Outstanding concerns / questions
libs/onnxruntime-rocm-*.jarship as a release asset on GitHub? The 290 MB jar is too big for git LFS-free workflows and ill-suited to be a committed artifact. Currently users build it locally via the script. An alternative: attach the jar to each GitHub release alongside the existing-windows/-cuda/-cpumod jars, and have the build script optionally download instead of build.-cpuand-cudabuilds use 1.20.0. I went to 1.21.0 for ROCm because AMD's ROCm 6.4.4 wheel is 1.21.0. If you'd prefer everything pinned to the same ORT version, the simplest path is to bump everything to 1.21.0 in a separate PR before this one.Happy to split this into three smaller PRs (config-only / CPU thread tuning / ROCm variant) if that's easier to review.