Skip to content

feat: FCLayers residual and forward kwargs - #4019

Open
moinfar wants to merge 8 commits into
scverse:mainfrom
moinfar:fclayers_residual_and_forward_kwargs
Open

moinfar wants to merge 8 commits into
scverse:mainfrom
moinfar:fclayers_residual_and_forward_kwargs

Conversation

@moinfar

@moinfar moinfar commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Dear scvi-tools team,

I am making this PR to add two small, backwards-compatible extensions to FCLayers. This allows easier extension of DRVI code without requiring to reimplement/repeat big chunks of code. Both are off/unused by default, so existing callers are unaffected.

  • residual option. Wraps every block except the first in a skip connection, whenever that block's input and output widths match. The first block is always excluded, so the flag needs n_layers >= 2 to do anything.
  • forward(**kwargs). Extra per-call context is threaded to the _apply_layer and _apply_batch_norm hooks. The base layers ignore unknown keywords; a subclass can branch on them (e.g. to change behavior between encode/decode calls without a separate code path).

SplitFCLayers (scvi.external.drvi), the one FCLayers subclass in the repo, picks up **kwargs on its own _apply_layer / _apply_batch_norm overrides so it isn't broken by the new signature.

Testing

tests/nn/test_fclayers.py adds a residual test that checks the full forward against a hand-built reference with skip connections on same-width blocks only.

moinfar and others added 8 commits September 11, 2026 19:26
Three additions to `FCLayers`, all independent of any particular model:

- `residual`: optionally add a skip connection around each hidden block whose
  input and output share a width. Block 0 changes width and is always excluded,
  so the flag needs `n_layers >= 2` to have any effect.
- `forward(**kwargs)`: extra per-call context is threaded to the
  `_apply_layer` / `_apply_batch_norm` hooks so subclasses can branch on it.
  The base layers ignore it.
- `_build_cov_list`: extract the continuous + one-hot categorical covariate
  assembly out of `forward` into an overridable method.

Covered by tests in `tests/nn/test_fclayers.py`, including the two residual
guards (`i > 0` and the shape match), kwargs reaching both hooks, and the
covariate-list seam being the source `forward` actually uses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `residual` docstring and release note said "each hidden block", but the
last block gets a skip too when `n_hidden == n_out`, and the first block is
excluded by the `i > 0` guard whether or not it changes width. Reword both to
"every block except the first, whenever its input and output widths match".

`SplitFCLayers` is the only `FCLayers` subclass in the repo and overrides
`_apply_layer` / `_apply_batch_norm`, so give those `**kwargs` as well;
otherwise passing per-call context through a `SplitFCLayers` raises TypeError.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Pruning the added tests truncated the file two lines early and took the tail of
`test_gradient_hook_preserves_categorical_grad_only` with it, so the test no
longer checked that the categorical columns keep a non-zero gradient. The file
is now byte-identical to main up to the tests this branch appends.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The covariate assembly moves back inline into `FCLayers.forward`: nothing
overrides or calls `_build_cov_list`, in scvi-tools or downstream, so the
seam was speculative. `residual` and the `**kwargs` threading stay -- both
have real subclass users.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.46%. Comparing base (73b28e4) to head (049c006).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4019   +/-   ##
=======================================
  Coverage   89.46%   89.46%           
=======================================
  Files         247      247           
  Lines       24389    24393    +4     
=======================================
+ Hits        21820    21824    +4     
  Misses       2569     2569           
Flag Coverage Δ
integration 75.96% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/scvi/external/drvi/_base_components.py 91.83% <100.00%> (ø)
src/scvi/nn/_base_components.py 95.36% <100.00%> (+0.06%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@moinfar moinfar changed the title Fclayers residual and forward kwargs feat: Fclayers residual and forward kwargs Sep 14, 2026
@moinfar moinfar changed the title feat: Fclayers residual and forward kwargs feat: FCLayers residual and forward kwargs Sep 14, 2026
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