From 14c1b5bc552c5ebaacb5dc3b6867ce0f3b93bba6 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Mon, 14 Sep 2026 14:51:21 +0200 Subject: [PATCH] fix(zram): the cuda feature says why it cannot build, instead of a cascade (#3179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `--features cuda` has not compiled since APR-MONO. Three gaps, each verified by reading the tree rather than inferred: 1. `trueno_gpu::driver::PinnedBuffer` and `GpuBuffer::copy_{from,to}_pinned_async` do not exist and never did in this monorepo — `git log -S PinnedBuffer -- crates/aprender-gpu/` is empty. Only synchronous pageable copies exist. 2. src/gpu/batch/mod.rs:610 launches the entry point `lz4_compress_warp_shuffle` with 32 threads and `shared_mem: 0`. No kernel of that name exists. The one that does, `lz4_compress_warp`, is documented as 128 threads per block with shared memory. 3. `Lz4WarpDecompressKernel::build_ptx` is a STUB whose entire body is `ctx.label("L_exit")`. There is no GPU LZ4 decompressor in this workspace. So this is not one API away from working: it is wired to a kernel ABI removed at consolidation, and its decompressor does not exist. DECIDED BY QUORUM, two rounds. Round 1 (3 agy lanes) voted 2/3 to rewrite the path on the pageable API so it would compile. Facts 1-3 above were verified AFTER that vote, so round 2 put them to three fresh lanes: all three overturned round 1, unanimous that making a dead path compile to turn a matrix cell green is theater. Round 2 split 2/1 between deleting the path and this option. I took the minority option and the reason is a fact neither deleting lane had: this crate's DEFAULT build is green, and `gpu/batch` is live, compiling, public API that `gpu/hybrid.rs` uses. Only the cuda-gated arms are dead. Both deleting lanes argued from "a dark, unused feature"; the module is neither. Deleting it would remove live public types (`GpuBatchCompressor`, `BatchResult`, `BatchDecompressResult`, `GpuBatchConfig`) for no gain, which neither lane actually argued for. One of them said outright that this option "best serves someone who later wants to finish the GPU path". So: one `compile_error!` naming all three gaps, with the real gate moved to a private `__cuda-linked` feature (13+2 sites in batch/mod.rs, 7 in hybrid.rs) so the refusal is the ONLY diagnostic and nothing is deleted. This is the same shape already used in this tree for aprender-simulate's `z3-proofs` and aprender-core's `showcase-profile`. `--features cuda` now prints one named error instead of an unresolved-import cascade; the two selections stay RED in the nightly matrix, but red WITH A REASON, which is the honest state until a real decompress kernel exists. Default builds of both crates are clean, aprender-zram-core --lib is 411/411, and clippy is clean. Refs #3179, #3262 Pmat-Ticket: PMAT-1098 Co-Authored-By: Claude Opus 5 (1M context) --- crates/aprender-zram-core/Cargo.toml | 5 ++ .../aprender-zram-core/src/gpu/batch/mod.rs | 59 ++++++++++++++----- crates/aprender-zram-core/src/gpu/hybrid.rs | 14 ++--- 3 files changed, 56 insertions(+), 22 deletions(-) diff --git a/crates/aprender-zram-core/Cargo.toml b/crates/aprender-zram-core/Cargo.toml index 87c9133cc8..84d1a9bd69 100644 --- a/crates/aprender-zram-core/Cargo.toml +++ b/crates/aprender-zram-core/Cargo.toml @@ -44,6 +44,11 @@ std = [] nightly = [] # Enable CUDA GPU acceleration cuda = ["dep:cudarc", "dep:rayon", "dep:aprender-gpu"] +# Private. Carries the REAL gate on the GPU batch path in src/gpu/batch/mod.rs, +# so `--features cuda` reports the one named refusal in that file instead of a +# cascade of unresolved imports. Set it only once the three gaps that refusal +# names are closed. See #3179. +__cuda-linked = [] [lints.rust] unsafe_code = "allow" diff --git a/crates/aprender-zram-core/src/gpu/batch/mod.rs b/crates/aprender-zram-core/src/gpu/batch/mod.rs index 33c828408b..383006a79f 100644 --- a/crates/aprender-zram-core/src/gpu/batch/mod.rs +++ b/crates/aprender-zram-core/src/gpu/batch/mod.rs @@ -5,6 +5,35 @@ //! //! For 4KB pages with ~2:1 ratio, batch 1000+ pages to amortize PCIe overhead. +// #3179 — the GPU batch path cannot run, and has not compiled since APR-MONO. +// Three gaps, each verified by reading the tree rather than inferred: +// +// 1. `trueno_gpu::driver::PinnedBuffer` and `GpuBuffer::copy_{from,to}_pinned_async` +// do not exist and never did in this monorepo (`git log -S PinnedBuffer -- +// crates/aprender-gpu/` is empty). Only synchronous pageable copies exist. +// 2. This file launches the entry point `lz4_compress_warp_shuffle` with 32 +// threads and no shared memory. No kernel of that name exists; the one that +// does, `lz4_compress_warp`, wants 128 threads and shared memory. +// 3. `Lz4WarpDecompressKernel::build_ptx` is a STUB whose whole body is +// `ctx.label("L_exit")`. There is no GPU LZ4 decompressor in this workspace. +// +// So this is not one API away from working: it is wired to a kernel ABI removed +// at consolidation, and its decompressor does not exist. Making it merely +// COMPILE would hand back a feature that is green in the matrix and dead at +// runtime — the theater this repo refuses. The refusal names all three gaps and +// the private `__cuda-linked` feature carries the real gate, so nothing here is +// deleted and the diagnostic is one line instead of a cascade. +#[cfg(all(feature = "cuda", not(feature = "__cuda-linked")))] +compile_error!( + "aprender-zram-core's `cuda` feature cannot build: the GPU batch path needs \ + (1) trueno_gpu::driver::PinnedBuffer + GpuBuffer::copy_{from,to}_pinned_async, \ + which do not exist; (2) a kernel named `lz4_compress_warp_shuffle`, which does \ + not exist (aprender-gpu ships `lz4_compress_warp`, 128 threads + shared memory); \ + and (3) a real LZ4 decompress kernel — Lz4WarpDecompressKernel::build_ptx is a \ + stub that exits immediately. See #3179. The non-cuda build of this crate is \ + unaffected." +); + use crate::error::{Error, Result}; use crate::page::CompressedPage; use crate::{Algorithm, PAGE_SIZE}; @@ -43,7 +72,7 @@ impl Default for GpuBatchConfig { #[allow(dead_code)] // Fields used when CUDA kernels are fully implemented pub struct GpuBatchCompressor { config: GpuBatchConfig, - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] context: Option, /// Cached SIMD compressor for parallel CPU path (Arc for thread-safety) simd_compressor: std::sync::Arc>, @@ -64,7 +93,7 @@ impl std::fmt::Debug for GpuBatchCompressor { } } -#[cfg(feature = "cuda")] +#[cfg(feature = "__cuda-linked")] struct GpuContext { /// CUDA context handle (trueno-gpu driver). context: trueno_gpu::driver::CudaContext, @@ -92,7 +121,7 @@ struct GpuContext { max_batch_size: usize, } -#[cfg(feature = "cuda")] +#[cfg(feature = "__cuda-linked")] #[allow(clippy::missing_fields_in_debug)] impl std::fmt::Debug for GpuContext { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -208,7 +237,7 @@ impl GpuBatchCompressor { .build()?, ); - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] { // Initialize CUDA context with pre-allocated buffers for batch_size let context = Self::init_cuda(config.device_index, config.batch_size)?; @@ -223,7 +252,7 @@ impl GpuBatchCompressor { }) } - #[cfg(not(feature = "cuda"))] + #[cfg(not(feature = "__cuda-linked"))] { Ok(Self { config, @@ -236,7 +265,7 @@ impl GpuBatchCompressor { } } - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] fn init_cuda(device_index: u32, batch_size: usize) -> Result { use trueno_gpu::driver::{CudaContext, CudaModule, CudaStream, GpuBuffer, PinnedBuffer}; use trueno_gpu::kernels::lz4::{Lz4DecompressKernel, Lz4WarpShuffleKernel}; @@ -416,12 +445,12 @@ impl GpuBatchCompressor { }); } - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] { self.compress_batch_cuda(pages) } - #[cfg(not(feature = "cuda"))] + #[cfg(not(feature = "__cuda-linked"))] { Err(Error::GpuNotAvailable( "CUDA feature not enabled".to_string(), @@ -429,7 +458,7 @@ impl GpuBatchCompressor { } } - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] fn compress_batch_cuda(&mut self, pages: &[[u8; PAGE_SIZE]]) -> Result { use std::time::Instant; @@ -472,7 +501,7 @@ impl GpuBatchCompressor { }) } - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] fn transfer_to_device( &self, pages: &[[u8; PAGE_SIZE]], @@ -498,7 +527,7 @@ impl GpuBatchCompressor { Ok(device_buffer) } - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] fn execute_compression_kernel(&self, pages: &[[u8; PAGE_SIZE]]) -> Result>> { use std::ffi::c_void; use std::time::Instant; @@ -718,7 +747,7 @@ impl GpuBatchCompressor { } /// CPU fallback for compression when GPU is not available - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] fn execute_compression_kernel_cpu(&self, pages: &[[u8; PAGE_SIZE]]) -> Result>> { use rayon::prelude::*; use trueno_gpu::kernels::lz4::lz4_compress_block; @@ -737,7 +766,7 @@ impl GpuBatchCompressor { results } - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] fn transfer_from_device(&self, data: Vec>) -> Result> { // In the current hybrid approach, data is already on host. // When nvCOMP is integrated, this will do D2H transfer. @@ -786,7 +815,7 @@ impl GpuBatchCompressor { /// The kernel achieves 32 GB/s internally. /// /// G.119 COMPLIANT: Eliminates 350ms allocation overhead by reusing buffers. - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] pub fn decompress_batch_gpu( &mut self, compressed: &[Vec], @@ -1036,7 +1065,7 @@ impl GpuBatchCompressor { /// /// Zero pages are extremely common in memory (>30% typically) and compress /// to just a few bytes with LZ4's RLE-style encoding. -#[cfg(feature = "cuda")] +#[cfg(feature = "__cuda-linked")] #[allow(dead_code)] fn encode_lz4_zero_page() -> Vec { use trueno_gpu::kernels::lz4::lz4_compress_block; diff --git a/crates/aprender-zram-core/src/gpu/hybrid.rs b/crates/aprender-zram-core/src/gpu/hybrid.rs index 721bda18b0..aac637c61f 100644 --- a/crates/aprender-zram-core/src/gpu/hybrid.rs +++ b/crates/aprender-zram-core/src/gpu/hybrid.rs @@ -172,7 +172,7 @@ impl HybridScheduler { /// # Errors /// /// Returns error if decompression fails. - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] pub fn decompress_batch_gpu( &mut self, compressed: &[Vec], @@ -273,7 +273,7 @@ impl HybridScheduler { /// # Errors /// /// Returns error if decompression fails. - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] pub fn decompress_parallel( &mut self, compressed: &[Vec], @@ -338,7 +338,7 @@ impl HybridScheduler { /// # Errors /// /// Returns error if decompression fails or buffer is too small. - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] pub fn decompress_parallel_into( &mut self, compressed: &[Vec], @@ -500,7 +500,7 @@ mod tests { } #[test] - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] #[ignore] // SIGSEGV during large batch allocation - needs investigation fn test_hybrid_scheduler_creation() { let config = HybridConfig::default(); @@ -513,7 +513,7 @@ mod tests { } #[test] - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] #[ignore] // Depends on test_hybrid_scheduler_creation fn test_hybrid_scheduler_compress() { if !crate::gpu::gpu_available() { @@ -540,7 +540,7 @@ mod tests { } #[test] - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] #[ignore] // Depends on test_hybrid_scheduler_creation fn test_hybrid_scheduler_decompress_gpu() { if !crate::gpu::gpu_available() { @@ -585,7 +585,7 @@ mod tests { } #[test] - #[cfg(feature = "cuda")] + #[cfg(feature = "__cuda-linked")] fn test_hybrid_scheduler_g119_estimate() { let stats = HybridStats { pages_decompressed_gpu: 1_000_000, // 1M pages = 4GB