From 293bc247b930e6d0e11b04c3d1fdc26eab6695ed Mon Sep 17 00:00:00 2001 From: Seongyong Park Date: Tue, 16 Jun 2026 21:33:38 -0400 Subject: [PATCH 1/2] Migrate optional accelerator to FlashReg (replaces RidgeFast + RidgeCuda) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FlashReg is a single optional package providing both the C+OpenMP CPU backend and the (optional) CUDA GPU backend. It replaces the historical pair of RidgeFast (CPU) and RidgeCuda (GPU) packages — same kernel sources, same MT19937 permutation seam, same bit-equivalence guarantees. Changes: - DESCRIPTION: Suggests drops RidgeFast + RidgeCuda, adds FlashReg. - R/ridge.R: .resolve_backend / .ridge_dispatch / .ridge_batch_dispatch now delegate to FlashReg::ridge() with backend mapping gpu -> cuda_native cpu-fast -> omp cpu-pure -> in-house .ridge_pureR (unchanged) Legacy SecAct backend names are preserved for backward compatibility. .ridge_pureR_batch gains an injectable 'kernel_fn' argument so the HDF5-streaming column-batch loop is shared between FlashReg and pure-R. - R/activity.R: docstring updated to recommend FlashReg. - tests/testthat/test-backend-parity.R: skip_if_not_installed('FlashReg'). - vignettes/accelerator.Rmd: replaces RidgeFast + RidgeCuda links with FlashReg link and explanatory note. Verified with FlashReg 0.1.0 installed: SecAct backend='cpu-fast' (-> FlashReg backend='omp') vs backend='cpu-pure': max|delta beta| = 3.6e-17, max|delta z| = 6.7e-16, p exact. --- DESCRIPTION | 3 +- R/activity.R | 8 +- R/ridge.R | 105 +++++++++++++++++---------- tests/testthat/test-backend-parity.R | 2 +- vignettes/accelerator.Rmd | 7 +- 5 files changed, 76 insertions(+), 49 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 6cd4dd8..bfee245 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -26,8 +26,7 @@ Imports: ComplexHeatmap, RANN Suggests: - RidgeFast, - RidgeCuda, + FlashReg, rhdf5, shiny, shinyWidgets, diff --git a/R/activity.R b/R/activity.R index 2beaff1..93858b8 100644 --- a/R/activity.R +++ b/R/activity.R @@ -13,8 +13,12 @@ #' @param nrand Number of randomization in the permutation test, with a default value of 1000. #' @param ncores Number of threads for accelerator backends (ignored by pure R). Default 1. #' @param backend One of \code{"auto"}, \code{"gpu"}, \code{"cpu-fast"}, \code{"cpu-pure"}. -#' \code{"auto"} picks GPU (RidgeCuda) > CPU-fast (RidgeFast) > CPU-pure -#' depending on what is installed. Default \code{"auto"}. +#' \code{"auto"} picks the best FlashReg backend available: +#' GPU (\code{FlashReg::ridge(backend="cuda_native")}) > CPU-fast +#' (\code{FlashReg::ridge(backend="omp")}) > CPU-pure (in-house loop) +#' depending on what FlashReg detects at install / load time. Default +#' \code{"auto"}. Legacy backend names \code{"gpu"} / \code{"cpu-fast"} +#' are kept for backward compatibility. #' @param rng_method RNG for permutations. \code{"mt19937"} (default) is #' GSL-compatible MT19937 seed 0 — bit-identical across backends when #' \code{ncores=1}. Accelerators may support \code{"srand"} for faster, diff --git a/R/ridge.R b/R/ridge.R index 1cd95be..831e8fc 100644 --- a/R/ridge.R +++ b/R/ridge.R @@ -49,30 +49,43 @@ list(beta = beta, se = se, zscore = zscore, pvalue = pvalue) } -#' Resolve backend based on installed packages and GPU availability +#' Resolve backend based on installed FlashReg and GPU availability +#' +#' Legacy backend names ("gpu", "cpu-fast", "cpu-pure") are still +#' accepted and remap onto the corresponding FlashReg backends so +#' downstream code keeps working without changes. #' @keywords internal .resolve_backend <- function(backend = c("auto", "gpu", "cpu-fast", "cpu-pure")) { backend <- match.arg(backend) if (backend != "auto") return(backend) - if (requireNamespace("RidgeCuda", quietly = TRUE)) { - gpu_ok <- tryCatch( - RidgeCuda::check_cuda_available(), - error = function(e) FALSE - ) + if (requireNamespace("FlashReg", quietly = TRUE)) { + gpu_ok <- tryCatch(FlashReg::cuda_available(), error = function(e) FALSE) if (isTRUE(gpu_ok)) return("gpu") + return("cpu-fast") } - if (requireNamespace("RidgeFast", quietly = TRUE)) return("cpu-fast") "cpu-pure" } -#' Dispatch ridge+permutation call to installed backend +# Map SecAct's legacy backend name onto FlashReg's backend name. +.secact_to_flashreg_backend <- function(chosen) { + switch(chosen, + gpu = "cuda_native", + `cpu-fast` = "omp", + `cpu-pure` = "pure_r", + stop("internal: unknown SecAct backend label '", chosen, "'")) +} + +#' Dispatch ridge+permutation call to FlashReg or the in-house pure-R loop +#' +#' Picks GPU (\code{FlashReg::ridge(backend="cuda_native")}) > CPU-fast +#' (\code{FlashReg::ridge(backend="omp")}) > CPU-pure (this package's +#' \code{.ridge_pureR}). FlashReg replaces the historical pair of +#' optional packages (RidgeFast for CPU, RidgeCuda for GPU); legacy +#' backend names are kept as aliases for backward compatibility. #' -#' Picks GPU (RidgeCuda) > CPU-fast (RidgeFast) > CPU-pure (this -#' package) based on \code{backend}. All three accelerators share the -#' API \code{ridge(X, Y, lambda, nrand, ncores, rng_method)}. -#' With \code{rng_method="mt19937"} and \code{ncores=1}, results are -#' bit-identical across backends. +#' With \code{rng_method="mt19937"} and \code{ncores=1}, FlashReg's +#' backends are bit-identical to the pure-R loop. #' #' @keywords internal .ridge_dispatch <- function(X, Y, lambda, nrand, @@ -86,15 +99,18 @@ ", ncores=", ncores, ")") } - if (chosen == "gpu") { - RidgeCuda::ridge(X = X, Y = Y, lambda = lambda, nrand = nrand, - ncores = ncores, rng_method = rng_method) - } else if (chosen == "cpu-fast") { - RidgeFast::ridge(X = X, Y = Y, lambda = lambda, nrand = nrand, - ncores = ncores, rng_method = rng_method) + if (chosen %in% c("gpu", "cpu-fast")) { + if (!requireNamespace("FlashReg", quietly = TRUE)) { + stop("backend='", chosen, "' requires the FlashReg package. ", + "Install it from https://github.com/data2intelligence/FlashReg ", + "or set backend='cpu-pure' to use the in-house pure-R loop.") + } + FlashReg::ridge(X = X, Y = Y, lambda = lambda, nrand = nrand, + backend = .secact_to_flashreg_backend(chosen), + ncores = ncores, rng_method = rng_method) } else { if (rng_method != "mt19937") { - stop("rng_method='", rng_method, "' requires RidgeFast or RidgeCuda; ", + stop("rng_method='", rng_method, "' requires FlashReg; ", "pure-R supports only 'mt19937'.") } .ridge_pureR(X, Y, lambda, nrand) @@ -111,9 +127,11 @@ #' Dispatch ridge+permutation over Y-column batches #' #' Memory-efficient version of \code{\link{.ridge_dispatch}}. Processes -#' \code{Y} in column-batches, forwarding to the chosen backend's -#' \code{ridge_batch()} (RidgeCuda > RidgeFast) or to the in-house -#' pure-R batched path. Supports HDF5 Y input and streaming HDF5 output. +#' \code{Y} in column-batches, calling the resolved backend on each +#' batch. With FlashReg installed, the per-batch kernel is the +#' \code{FlashReg::ridge()} dispatcher (GPU or C+OpenMP CPU); +#' otherwise the pure-R loop is used. The same column-batching code +#' handles HDF5 Y input and streaming HDF5 output for all backends. #' #' @keywords internal .ridge_batch_dispatch <- function(X, Y, lambda, nrand, @@ -130,26 +148,31 @@ " (batch_size=", batch_size, ", rng_method=", rng_method, ")") } - if (chosen == "gpu") { - RidgeCuda::ridge_batch(X = X, Y = Y, lambda = lambda, nrand = nrand, - ncores = ncores, rng_method = rng_method, - batch_size = batch_size, reader = reader, - n_samples = n_samples, output_h5 = output_h5) - } else if (chosen == "cpu-fast") { - RidgeFast::ridge_batch(X = X, Y = Y, lambda = lambda, nrand = nrand, - ncores = ncores, rng_method = rng_method, - batch_size = batch_size, reader = reader, - n_samples = n_samples, output_h5 = output_h5) + if (chosen %in% c("gpu", "cpu-fast")) { + if (!requireNamespace("FlashReg", quietly = TRUE)) { + stop("backend='", chosen, "' requires the FlashReg package. ", + "Install it from https://github.com/data2intelligence/FlashReg ", + "or set backend='cpu-pure' to use the in-house pure-R loop.") + } + flashreg_backend <- .secact_to_flashreg_backend(chosen) + kernel_fn <- function(X, Y_batch, lambda, nrand) { + FlashReg::ridge(X = X, Y = Y_batch, lambda = lambda, nrand = nrand, + backend = flashreg_backend, + ncores = ncores, rng_method = rng_method) + } } else { if (rng_method != "mt19937") { - stop("rng_method='", rng_method, "' requires RidgeFast or RidgeCuda; ", + stop("rng_method='", rng_method, "' requires FlashReg; ", "pure-R supports only 'mt19937'.") } - .ridge_pureR_batch(X, Y, lambda, nrand, - batch_size = batch_size, - reader = reader, n_samples = n_samples, - output_h5 = output_h5) + kernel_fn <- .ridge_pureR } + + .ridge_pureR_batch(X, Y, lambda, nrand, + batch_size = batch_size, + reader = reader, n_samples = n_samples, + output_h5 = output_h5, + kernel_fn = kernel_fn) } #' Pure-R batched ridge+permutation @@ -164,7 +187,9 @@ .ridge_pureR_batch <- function(X, Y, lambda, nrand, batch_size = 5000L, reader = NULL, n_samples = NULL, - output_h5 = NULL) { + output_h5 = NULL, + kernel_fn = NULL) { + if (is.null(kernel_fn)) kernel_fn <- .ridge_pureR if (!is.matrix(X)) X <- as.matrix(X) storage.mode(X) <- "double" n <- nrow(X); p <- ncol(X) @@ -209,7 +234,7 @@ storage.mode(Y_batch) <- "double" if (is.null(colnames(Y_batch))) colnames(Y_batch) <- samp_names[s:e] - res <- .ridge_pureR(X, Y_batch, lambda, nrand) + res <- kernel_fn(X, Y_batch, lambda, nrand) if (h5_out) { for (nm in c("beta", "se", "zscore", "pvalue")) { diff --git a/tests/testthat/test-backend-parity.R b/tests/testthat/test-backend-parity.R index 766a7a2..8891fce 100644 --- a/tests/testthat/test-backend-parity.R +++ b/tests/testthat/test-backend-parity.R @@ -1,5 +1,5 @@ test_that("cpu-pure and cpu-fast backends produce identical z-scores in mt19937 mode", { - skip_if_not_installed("RidgeFast") + skip_if_not_installed("FlashReg") set.seed(7) n <- 60; p <- 10; m <- 4 diff --git a/vignettes/accelerator.Rmd b/vignettes/accelerator.Rmd index 740d957..3aba7c2 100644 --- a/vignettes/accelerator.Rmd +++ b/vignettes/accelerator.Rmd @@ -25,16 +25,15 @@ After installing the `SecAct` R package, you can complete all analyses in the tu ## Installation -Once either package is installed, no additional configuration is required—the activity inference functions will automatically use the available accelerator. +Once FlashReg is installed, no additional configuration is required—the activity inference functions will automatically use the available accelerator. -* [RidgeFast](https://github.com/data2intelligence/RidgeFast) — multi-threaded CPU (GSL + OpenMP) -* [RidgeCuda](https://github.com/data2intelligence/RidgeCuda) — NVIDIA GPU (CUDA) +* [FlashReg](https://github.com/data2intelligence/FlashReg) — one R package, both CPU (C+OpenMP) and GPU (NVIDIA CUDA) backends, GSL required and CUDA toolkit optional. FlashReg replaces the historical pair of accelerator packages (RidgeFast for CPU, RidgeCuda for GPU). ## Example -With either installed, `SecAct.activity.inference(..., backend = "auto")` (the default) picks GPU > CPU-fast > pure-R automatically. At `rng_method = "mt19937"` (default) and `ncores = 1`, output is bit-identical across all three backends. +With FlashReg installed, `SecAct.activity.inference(..., backend = "auto")` (the default) picks GPU > CPU-fast > pure-R automatically — FlashReg detects CUDA availability at runtime. At `rng_method = "mt19937"` (default) and `ncores = 1`, output is bit-identical across all three backends. ``` r # Memory-bounded inference on 100k samples From 9accf068f7e747aadfc989e726e693fd8d1b936a Mon Sep 17 00:00:00 2001 From: Seongyong Park Date: Fri, 3 Jul 2026 14:53:42 -0400 Subject: [PATCH 2/2] docs(readme): note SecAct's ridge core can be accelerated by FlashReg/FlashRegPy --- README.Rmd | 2 ++ README.md | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/README.Rmd b/README.Rmd index 0bc4440..c201ffe 100644 --- a/README.Rmd +++ b/README.Rmd @@ -20,6 +20,8 @@ knitr::opts_chunk$set( SecAct is an R package designed for inferring the intercellular signaling activity of secreted proteins from gene expression profiles. Users can input multiple modalities of expression data, including spatial, single-cell, or bulk transcriptomics data. The outputs are the inferred signaling activities of 1,170 secreted proteins for each spatial spot, individual cell, or sample, depending on the input data type. Based on the inferred activities, SecAct provides multiple downstream application modules. For spatial data, SecAct can infer the signaling pattern and signaling velocity for secreted proteins. For single-cell data, SecAct can infer the intercellular communication network and signaling flow from source cells to receiver cells. For bulk data, SecAct can infer secreted protein risk scores for a large cohort linked to clinical data, and can infer secreted protein activities that are differentially regulated between two phenotypes. Check the tutorials below for details on how to use the package. +> **Fast regression engine.** SecAct's ridge-regression + permutation-testing core can be accelerated by [FlashReg](https://github.com/data2intelligence/FlashReg) (R) / [FlashRegPy](https://github.com/data2intelligence/FlashRegPy) (Python) — an ultra-fast, GPU-capable ridge engine — which makes atlas-scale (millions of cells) activity inference tractable: the permutation nulls that project to days–months on naïve refit-per-permutation solvers run in minutes on a single GPU. +

diff --git a/README.md b/README.md index b9075a8..aca62ea 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,15 @@ infer secreted protein activities that are differentially regulated between two phenotypes. Check the tutorials below for details on how to use the package. +> **Fast regression engine.** SecAct’s ridge-regression + permutation-testing +> core can be accelerated by +> [FlashReg](https://github.com/data2intelligence/FlashReg) (R) / +> [FlashRegPy](https://github.com/data2intelligence/FlashRegPy) (Python) — an +> ultra-fast, GPU-capable ridge engine — which makes atlas-scale (millions of +> cells) activity inference tractable: the permutation nulls that project to +> days–months on naïve refit-per-permutation solvers run in minutes on a single +> GPU. +