From 8f00bf51b5e13eab18a39329d854ca89e5a98a69 Mon Sep 17 00:00:00 2001 From: ElliotXie <123660672+ElliotXie@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:57:47 -0500 Subject: [PATCH] [seurat] fix SCTransform object bloat on qs2/saveRDS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .seurat_sct_fast_vst built its returned `arguments` list via as.list(environment()), capturing local helper fns. The turbo path temporarily swaps sctransform::row_gmean for a cached_row_gmean closure whose enclosing env is the whole fast_SCTransform frame (full umi matrix + CSR intermediates). That list is persisted into SCTModel@arguments, so qs2/saveRDS of the SCT object walks the closure env and drags the matrix in — the saved file balloons ~3x (324MB vs 112MB baseline on a 5k-cell set, scaling with cell count) while object.size stays blind (does not recurse into closure envs). Upstream sctransform stores only scalar params here. Drop function-valued entries from `arguments` before it is persisted. Output unchanged: scale.data bit-identical to pre-fix; qs2 back to baseline size; SCT speedup preserved (4.05x); filter runs once per call (~8us). Co-Authored-By: Claude Opus 4.8 --- autozyme_r/inst/patches/seurat/patch.R | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/autozyme_r/inst/patches/seurat/patch.R b/autozyme_r/inst/patches/seurat/patch.R index 2567777..d30a39c 100644 --- a/autozyme_r/inst/patches/seurat/patch.R +++ b/autozyme_r/inst/patches/seurat/patch.R @@ -1931,6 +1931,16 @@ if (requireNamespace("Seurat", quietly = TRUE) && } arguments <- as.list(environment()) arguments <- arguments[!names(arguments) %in% c("umi", "cell_attr")] + # as.list(environment()) also captured the local helper fns (make_cell_attr, + # row_gmean, get_model_pars, ...). This `arguments` list is persisted into + # SCTModel@arguments, and the turbo path temporarily swaps sctransform's + # row_gmean for a `cached_row_gmean` closure whose enclosing env is the whole + # fast_SCTransform frame (full umi matrix + CSR intermediates). Serializing + # the model (qs2/saveRDS) then walks that env and drags the matrix in — the + # saved object balloons (~200 MB extra on a 5k-cell set) while object.size, + # which does not recurse into closure envs, reports it as tiny. Upstream + # sctransform stores only scalar params here, so drop the function entries. + arguments <- arguments[!vapply(arguments, is.function, logical(1))] if (startsWith(method, "offset")) { cell_attr <- NULL; latent_var <- c("log_umi"); batch_var <- NULL latent_var_nonreg <- NULL; n_genes <- NULL; n_cells <- NULL