From 4064117244d5248ac7686d44ec0874b4e2cb975d Mon Sep 17 00:00:00 2001 From: woutermans Date: Sun, 18 Oct 2026 23:03:58 +0200 Subject: [PATCH] zluda: skip compiling modules whose kernels are all native (0014) DLSS loads every module of a model at feature creation and ZLUDA compiled each one, even when the bridge had already linked a native kernel for every visible entry and the compiled code never ran. On RDNA2 the transformer layers emulate MMA and each took minutes in LLVM's register allocator, so install.sh's K test frame looked hung for about half an hour on a Steam Deck. Such a module (no globals, 64-bit) now loads the first native code object as a stand-in and keeps its PTX; it is compiled only if a function without a native replacement is requested. The K frame takes ~40 s on the Deck. --- docs/building.md | 7 +- packaging/SOURCES.txt | 2 +- ...ip-compiling-natively-served-modules.patch | 221 ++++++++++++++++++ scripts/build_release_glibc241.sh | 2 +- 4 files changed, 227 insertions(+), 5 deletions(-) create mode 100644 patches/zluda/0014-skip-compiling-natively-served-modules.patch diff --git a/docs/building.md b/docs/building.md index f9782de..324b77d 100644 --- a/docs/building.md +++ b/docs/building.md @@ -5,7 +5,7 @@ These steps produce the pieces the launcher needs: a patched ZLUDA, a patched vk | Component | Tested version | |---|---| -| ZLUDA | `ee2f25a` (upstream), plus `patches/zluda/0002` through `0013` in order | +| ZLUDA | `ee2f25a` (upstream), plus `patches/zluda/0002` through `0014` in order | | vkd3d-proton | `3dfc6f07` (the base GE-Proton11-3 ships), plus `patches/vkd3d-proton/0001` through `0003` in order | | ROCm | 7.2 (HIP runtime, clang, device libraries) | | Proton | GE-Proton11-3 (with its OptiScaler integration) | @@ -18,7 +18,7 @@ git clone https://github.com/vosen/ZLUDA zluda && cd zluda git checkout ee2f25a git submodule update --init --recursive git lfs pull -for p in 0002 0003 0004 0005 0006 0007 0008 0009 0010 0011 0012 0013; do git apply /path/to/d4r/patches/zluda/$p-*.patch; done +for p in 0002 0003 0004 0005 0006 0007 0008 0009 0010 0011 0012 0013 0014; do git apply /path/to/d4r/patches/zluda/$p-*.patch; done # rebuild the device helpers the patches changed (ptx/lib/zluda_ptx_impl*.bc) ZLUDA_SOURCE_ROOT=$PWD ROCM_ROOT=/opt/rocm /path/to/d4r/scripts/build_zluda_ptx_helpers.sh LIBRARY_PATH=/opt/rocm/lib cargo build --release -p zluda @@ -51,6 +51,7 @@ What the patches add: - **0011**: gfx12 native FP8 operands stay packed through the NVIDIA-to-WMMA lane gathers, removing fake-half splitting and repacking. Paired and unpaired operations preserve the previous K permutation, accumulation order and FP32-shadow scaling. Unpaired gathers execute in every source lane before zero-padding; masking EXEC around them loses valid source values. Rebuild both the device helpers and ZLUDA. - **0012**: coalesce complete accumulator-register overwrites into one shadow-layout conversion. Intervening reads, rewritten shadow accesses and basic-block boundaries prevent coalescing; partial writes retain read/modify/write behavior. Gfx12 native-FP8 functions request eight waves per SIMD to limit register pressure. Rebuild ZLUDA and `d4r_emit`. - **0013**: preserve signs through the native FP8 conversion instead of reconstructing them afterward. Explicitly clamp finite overflow and infinities, and map NaNs to signed infinity before conversion: native infinity produces the required `0x7f`/`0xff`, whereas native NaN always produces `0xff`. Plain and ReLU paths remain exhaustive-table identical to software. Rebuild device helpers and ZLUDA. +- **0014**: a module whose every visible kernel has a native replacement in `D4R_ZLUDA_NATIVE_DIR` (and that declares no globals) is not compiled when DLSS loads it; its PTX is kept and compiled only if a function without a native replacement is requested. DLSS loads every module at feature creation, and on RDNA2 each transformer layer (emulated MMA) took minutes to compile for code that never ran, so the first K frame took about half an hour on a Steam Deck. Rebuild ZLUDA. When linking on a system with ROCm libraries outside the default search path, include their library directory in `LIBRARY_PATH`. The build also needs the appropriate ROCm link libraries. `CARGO_BUILD_JOBS=8` caps parallel Rust compilation if memory is limited. @@ -178,7 +179,7 @@ Mount the repository at `/work`, ROCm's compiler/headers/device libraries at `/o | Variable | Input | |---|---| | `D4R_RELEASE_BUILD_ROOT` | writable directory under `/work/build/` | -| `D4R_ZLUDA_SRC` | isolated checkout inside that directory, with both pinned LLVM and HiGHS submodules, the real OCKL LFS payload, and patches `0002`–`0013` applied | +| `D4R_ZLUDA_SRC` | isolated checkout inside that directory, with both pinned LLVM and HiGHS submodules, the real OCKL LFS payload, and patches `0002`–`0014` applied | | `D4R_VKD3D_SRC` | isolated vkd3d-proton checkout inside that directory, with its submodules and patches `0001`–`0003` applied | | `CARGO_HOME` | writable build-local Cargo cache; prefetch the locked dependencies for an offline build | | `D4R_ROCM_DIR` | `/opt/rocm` | diff --git a/packaging/SOURCES.txt b/packaging/SOURCES.txt index 25253b0..ad366f6 100644 --- a/packaging/SOURCES.txt +++ b/packaging/SOURCES.txt @@ -19,7 +19,7 @@ Built from source by d4r (GPL-3.0-only, d4r/licenses/d4r-LICENSE.txt): Third-party software, unmodified or patched by d4r: d4r/zluda/libcuda.so ZLUDA (Apache License 2.0 or MIT), https://github.com/vosen/ZLUDA - commit @ZLUDA_COMMIT@ with d4r/source/patches/zluda/0002 to 0013 applied. + commit @ZLUDA_COMMIT@ with d4r/source/patches/zluda/0002 to 0014 applied. It contains LLVM (Apache License 2.0 with LLVM exceptions). Licenses: d4r/licenses/ZLUDA-LICENSE-*.txt, d4r/licenses/LLVM-LICENSE.txt d3d12.dll, d3d12core.dll diff --git a/patches/zluda/0014-skip-compiling-natively-served-modules.patch b/patches/zluda/0014-skip-compiling-natively-served-modules.patch new file mode 100644 index 0000000..27878c2 --- /dev/null +++ b/patches/zluda/0014-skip-compiling-natively-served-modules.patch @@ -0,0 +1,221 @@ +diff --git a/zluda/src/impl/library.rs b/zluda/src/impl/library.rs +index 98be33e6..f089f5a7 100644 +--- a/zluda/src/impl/library.rs ++++ b/zluda/src/impl/library.rs +@@ -21,8 +21,7 @@ impl Library { + let module_lock = self.modules.get(device).ok_or(CUerror::INVALID_DEVICE)?; + *module_lock.get_or_init(|| match self.data { + LibraryData::Lazy(lib) => { +- let (module, sm_version, meta32) = module::load_hip_module(lib)?; +- Ok(Module::new(module, sm_version, meta32).wrap()) ++ Ok(module::load_hip_module(lib)?.wrap()) + } + LibraryData::Eager(()) => Err(CUerror::NOT_SUPPORTED), + }) +diff --git a/zluda/src/impl/module.rs b/zluda/src/impl/module.rs +index 905b7d44..16deb701 100644 +--- a/zluda/src/impl/module.rs ++++ b/zluda/src/impl/module.rs +@@ -1,6 +1,6 @@ + use super::driver; + use crate::r#impl::function; +-use crate::r#impl::{driver::GlobalState, function::Function}; ++use crate::r#impl::function::Function; + use cuda_types::{cuda::*, dark_api::FatbinFileHeader}; + use dark_api::FunctionArgInfo; + use hip_runtime_sys::*; +@@ -24,6 +24,10 @@ pub(crate) struct Module { + + struct ModuleMutable { + functions: FxHashMap>, ++ // d4r: the PTX of a module loaded without compiling (native_placeholder), and the module compiled from ++ // it once a function without a native replacement is requested ++ deferred: Option, ++ compiled: Option, + } + + impl ModuleMutable { +@@ -40,6 +44,12 @@ impl ModuleMutable { + let (func_handle, prep, block_z, grid_x, prep_key, prep_slots, post) = match native_override(entry.key()) { + Some(native) => native, + None => { ++ if let Some(text) = self.deferred.as_deref() { ++ let (compiled, _, _) = compile_ptx(Some(text), ptx_parser::parse_module_unchecked(text))?; ++ self.compiled = Some(compiled); ++ self.deferred = None; ++ } ++ let module = self.compiled.unwrap_or(module); + let mut func_handle = unsafe { std::mem::zeroed() }; + unsafe { hipModuleGetFunction(&mut func_handle, module, entry.key().as_ptr()) }?; + (func_handle, None, 0, 0, None, 0, Vec::new()) +@@ -64,6 +74,57 @@ impl ModuleMutable { + } + } + ++fn read_native_image(path: &std::path::Path) -> Option<&'static [u8]> { ++ let image = fs::read(path).ok()?; ++ // HIP can crash on a short or truncated code object: an ELF's section headers (e_shoff, e_shentsize, e_shnum) must fit ++ let le = |at: usize, size: usize| image[at..at + size].iter().rev().fold(0u64, |v, &b| (v << 8) | u64::from(b)); ++ if image.len() < 64 ++ || (image.starts_with(b"\x7fELF") && le(40, 8).saturating_add(le(58, 2) * le(60, 2)) > image.len() as u64) ++ { ++ eprintln!("[d4r-native] {} is truncated ({} bytes)", path.display(), image.len()); ++ return None; ++ } ++ // HIP may reference the image after loading; the override lives for the whole process. ++ Some(Box::leak(image.into_boxed_slice())) ++} ++ ++/// d4r: a module whose every visible kernel has a native replacement (and that has no globals to look up) ++/// is not compiled when it is loaded: its kernels are served by native_override, and the PTX is compiled ++/// only if another function is requested. On RDNA2 the DLSS transformer layers each took minutes to ++/// compile (emulated MMA) for code that never ran. The returned module, the first kernel's native code ++/// object, stands in for the compiled one (unload, lookups). ++fn native_placeholder(ast: &ptx_parser::Module) -> Option { ++ let dir = std::env::var_os("D4R_ZLUDA_NATIVE_DIR")?; ++ if ast.invalid_directives != 0 || ast.address_size != 64 { ++ return None; ++ } ++ let mut first = None; ++ for directive in &ast.directives { ++ match directive { ++ ptx_parser::Directive::Variable(_, variable) => { ++ if variable.info.state_space != ptx_parser::StateSpace::Shared { ++ return None; ++ } ++ } ++ ptx_parser::Directive::Method(linking, function) => { ++ let name = &function.func_directive.name; ++ if !name.is_kernel() || !linking.contains(ptx_parser::LinkingDirective::VISIBLE) { ++ continue; ++ } ++ let path = std::path::Path::new(&dir).join(format!("{}.hsaco", name.text())); ++ if !path.is_file() { ++ return None; ++ } ++ first.get_or_insert(path); ++ } ++ } ++ } ++ let image = read_native_image(&first?)?; ++ let mut module = unsafe { mem::zeroed() }; ++ unsafe { hipModuleLoadData(&mut module, image.as_ptr().cast()) }.ok()?; ++ Some(module) ++} ++ + /// d4r: hand-written native replacement for a PTX kernel. With D4R_ZLUDA_NATIVE_DIR set, a kernel + /// NAME is served from DIR/NAME.hsaco when that file exists (same name, launch shape and parameter + /// block as the PTX kernel). An optional NAME_prep kernel in the same code object runs first on the +@@ -76,17 +137,7 @@ fn native_override( + ) -> Option<(hipFunction_t, Option<(hipFunction_t, u32)>, u32, u32, Option, u32, Vec<(hipFunction_t, u32, u32, u32, u32, u32)>)> { + let dir = std::env::var_os("D4R_ZLUDA_NATIVE_DIR")?; + let path = std::path::Path::new(&dir).join(format!("{}.hsaco", name.to_str().ok()?)); +- let image = fs::read(&path).ok()?; +- // HIP can crash on a short or truncated code object: an ELF's section headers (e_shoff, e_shentsize, e_shnum) must fit +- let le = |at: usize, size: usize| image[at..at + size].iter().rev().fold(0u64, |v, &b| (v << 8) | u64::from(b)); +- if image.len() < 64 +- || (image.starts_with(b"\x7fELF") && le(40, 8).saturating_add(le(58, 2) * le(60, 2)) > image.len() as u64) +- { +- eprintln!("[d4r-native] {} is truncated ({} bytes)", path.display(), image.len()); +- return None; +- } +- // HIP may reference the image after loading; the override lives for the whole process. +- let image: &'static [u8] = Box::leak(image.into_boxed_slice()); ++ let image = read_native_image(&path)?; + let mut native_module = unsafe { mem::zeroed() }; + if unsafe { hipModuleLoadData(&mut native_module, image.as_ptr().cast()) }.is_err() { + eprintln!("[d4r-native] failed to load {}", path.display()); +@@ -176,6 +227,9 @@ impl ZludaObject for Module { + type CudaHandle = CUmodule; + + fn drop_checked(&mut self) -> CUresult { ++ if let Some(compiled) = self.mutable.get_mut().ok().and_then(|m| m.compiled.take()) { ++ unsafe { hipModuleUnload(compiled) }?; ++ } + unsafe { hipModuleUnload(self.base) }?; + Ok(()) + } +@@ -263,6 +317,8 @@ impl Module { + sm_version, + mutable: Mutex::new(ModuleMutable { + functions: FxHashMap::default(), ++ deferred: None, ++ compiled: None, + }), + bit32, + } +@@ -281,10 +337,7 @@ impl Module { + } + } + +-fn get_best_ptx_and_compile( +- global_state: &GlobalState, +- image: CodeLibraryRef<'_>, +-) -> Result<(hipModule_t, u32, Option), CUerror> { ++fn get_best_ptx_and_compile(image: CodeLibraryRef<'_>) -> Result { + let mut ptx_modules = Vec::new(); + unsafe { + CodeLibraryRef::iterate_modules(image, |_, module| match module { +@@ -373,6 +426,20 @@ fn get_best_ptx_and_compile( + ) + } + }; ++ if let (Some(text), Some(placeholder)) = (text, native_placeholder(&module)) { ++ let loaded = Module::new(placeholder, module.sm_version, None); ++ loaded.mutable.lock().map_err(|_| CUerror::UNKNOWN)?.deferred = Some(text.to_string()); ++ return Ok(loaded); ++ } ++ let (hip_module, sm_version, zluda32) = compile_ptx(text.map(|text| &**text), module)?; ++ Ok(Module::new(hip_module, sm_version, zluda32)) ++} ++ ++fn compile_ptx( ++ text: Option<&str>, ++ module: ptx_parser::Module<'_>, ++) -> Result<(hipModule_t, u32, Option), CUerror> { ++ let global_state = driver::global_state()?; + // TODO: get this information on initialization + let hip_properties = get_hip_properties()?; + let gcn_arch = get_gcn_arch(&hip_properties)?; +@@ -389,7 +456,7 @@ fn get_best_ptx_and_compile( + let mut cache_with_key = match (text, global_state.cache_path.as_ref()) { + (Some(text), Some(p)) => (|| { + let cache = zluda_cache::ModuleCache::open(p)?; +- let key = get_cache_key(gcn_arch, &text, &attributes)?; ++ let key = get_cache_key(gcn_arch, text, &attributes)?; + Some((cache, key)) + })(), + _ => None, +@@ -410,11 +477,8 @@ fn cow_bytes_to_str<'a>(data: Cow<'a, [u8]>) -> Option> { + } + } + +-pub(crate) fn load_hip_module( +- library: CodeLibraryRef, +-) -> Result<(hipModule_t, u32, Option), CUerror> { +- let global_state = driver::global_state()?; +- get_best_ptx_and_compile(global_state, library) ++pub(crate) fn load_hip_module(library: CodeLibraryRef) -> Result { ++ get_best_ptx_and_compile(library) + } + + #[derive(serde::Serialize)] +@@ -605,16 +669,14 @@ pub(crate) fn load(module: &mut CUmodule, fname: &CStr) -> CUresult { + image.push(0); + let library = unsafe { CodeLibraryRef::try_load(image.as_ptr() as *const std::ffi::c_void) } + .map_err(|_| CUerror::NO_BINARY_FOR_GPU)?; +- let (hip_module, sm_version, meta32) = load_hip_module(library)?; +- *module = Module::new(hip_module, sm_version, meta32).wrap(); ++ *module = load_hip_module(library)?.wrap(); + Ok(()) + } + + pub(crate) fn load_data(module: &mut CUmodule, image: &std::ffi::c_void) -> CUresult { + let library = + unsafe { CodeLibraryRef::try_load(image) }.map_err(|_| CUerror::NO_BINARY_FOR_GPU)?; +- let (hip_module, sm_version, meta32) = load_hip_module(library)?; +- *module = Module::new(hip_module, sm_version, meta32).wrap(); ++ *module = load_hip_module(library)?.wrap(); + Ok(()) + } + diff --git a/scripts/build_release_glibc241.sh b/scripts/build_release_glibc241.sh index c947f7f..f2b7aad 100755 --- a/scripts/build_release_glibc241.sh +++ b/scripts/build_release_glibc241.sh @@ -5,7 +5,7 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" : "${D4R_RELEASE_BUILD_ROOT:?set the build output directory}" -: "${D4R_ZLUDA_SRC:?set a build-local ZLUDA checkout with patches 0002-0013 applied}" +: "${D4R_ZLUDA_SRC:?set a build-local ZLUDA checkout with patches 0002-0014 applied}" : "${D4R_VKD3D_SRC:?set a build-local vkd3d-proton checkout with patches 0001-0003 applied}" : "${D4R_ROCM_DIR:?set ROCm with clang and device libraries}" BUILD="$(realpath -m "$D4R_RELEASE_BUILD_ROOT")"