Skip to content

[seurat] fix SCTransform object bloat on qs2/saveRDS#3

Merged
ElliotXie merged 1 commit into
mainfrom
fix/sct-qs2-object-bloat
Jul 10, 2026
Merged

[seurat] fix SCTransform object bloat on qs2/saveRDS#3
ElliotXie merged 1 commit into
mainfrom
fix/sct-qs2-object-bloat

Conversation

@ElliotXie

Copy link
Copy Markdown
Owner

Problem

Community report: "after using autozyme the object saved with qs2 becomes very large." Reproduced — it is SCTransform-specific.

pbmc68k, 5,000-cell subsample, SCTransform(obj) defaults, qs2::qs_save:

arm qs2 file
baseline (no autozyme) 112.66 MB
autozyme (bug) 324.02 MB (2.88x)
autozyme (this fix) 112.18 MB

object.size is ~identical for both arms (215 MB) — it does not recurse into closure environments, but qs2/saveRDS serialize them in full.

Root cause

SCTModel@arguments$row_gmean = 212 MB on disk, 0.01 MB by object.size. The turbo SCT path temporarily swaps sctransform::row_gmean for a cached_row_gmean closure whose env is the whole fast_SCTransform frame (full UMI matrix). .seurat_sct_fast_vst does arguments <- as.list(environment()) and strips only umi/cell_attr, so the poisoned closure rides into arguments, which is persisted into SCTModel@arguments. Grows without bound in cell count. Standard Normalize→Scale→PCA→cluster pipeline is unaffected.

Fix

Drop function-valued entries from arguments before persisting (upstream sctransform stores only scalar params there).

Validation

  • End-to-end: fixed file 112.18 MB, 0 function args remain.
  • Numerically inert: scale.data bit-identical to pre-fix (all.equal == TRUE).
  • Speed preserved: SCT 4.05x; filter runs once per call outside hot path (~8 µs).

Follow-up (not in this PR)

Version bump + package rebuild needed for installed users.

.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 <noreply@anthropic.com>
@ElliotXie ElliotXie merged commit 718541d into main Jul 10, 2026
3 checks passed
@ElliotXie ElliotXie deleted the fix/sct-qs2-object-bloat branch July 10, 2026 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant