Skip to content

Sg/backend improvements - #32

Open
saurabhinfocusp wants to merge 15 commits into
mainfrom
sg/backend-improvements
Open

saurabhinfocusp wants to merge 15 commits into
mainfrom
sg/backend-improvements

Conversation

@saurabhinfocusp

@saurabhinfocusp saurabhinfocusp commented Sep 21, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Add a new "simple API" layer on top of the scaLR engine — scalr.train, scalr.annotate, scalr.load_model, scalr.validate, scalr.align_genes — for config-free, in-memory workflows (leakage-safe splitting, calibration, metrics, validation, gene alignment).
  • Layer in optional capabilities: hierarchical taxonomy annotation, cluster-refinement majority voting, doublet flagging, feature-selection stability scoring, and dataset benchmarking (quality + cost).
  • Add self-contained model artifacts (manifest.json, model.pt, configs, auto-generated model card) with a local model registry, and a scalr CLI (train, annotate, evaluate, validate, models list/info).
  • Add corresponding unit tests for all new modules.
  • Apply yapf/isort formatting fixes to satisfy CI style checks (no logic changes).
  • Update docs (README, CLAUDE.md), pyproject.toml, requirements.txt for the new modules/CLI entry point.

Test plan

  • pytest (new test_api.py, test_benchmark.py, test_cli.py, test_doublet.py, test_feature_stability.py, test_hierarchy.py, test_model_card.py, test_models.py, test_refinement.py, plus existing suites)
  • CLI-based training tested manually per latest commit message

*Other updates

  1. P2 — explainability & streaming inference (a6d45c0, caa8ceb, 65e9eef)
  • New scalr/explain.py explainability API.
  • Streaming/chunked predict() in artifact.py/api.py for datasets too large to hold in memory.
  • README documentation for both; CLI formatting fix.
  1. Hub integration & drift detection (15a0337)
  • Push/pull model artifacts to Hugging Face Hub (scalr/models.py, artifact.py, api.py, cli.py).
  • New scalr/drift.py for dataset drift detection.
  • Largest single commit on the branch (~970 lines), with matching test files (test_api.py, test_cli.py, test_drift.py, test_models.py).
  1. Training flexibility & performance (a9f86bc)
  • Custom/user-defined loss functions in scalr/nn/loss/_loss.py.
  • Parallelized feature scoring in feature_extraction_pipeline.py.
  1. Correctness fixes (044ba66, 4f85fa0, 83508e9, 8a61507)
  • Gene alignment bug when duplicate gene names exist (genes.py, feature_subsetting.py).
  • Multi-worker config propagation fix.
  • Lazy imports for scanpy/SHAP-backed submodules (faster base import, guarded by test_lazy_imports.py).
  • anndata bumped past a broken 0.11.x chunked-read regression; pydeseq2 np.matrix bug fixed in dge_pseudobulk.py.
  • AnnCollection dtype harmonization disabled — it was corrupting chunked .h5ad reads (most recent commit).

saurabh and others added 5 commits September 21, 2026 17:28
CI's yapf check failed because the new P0 files did not match the
project's .style.yapf (google style, 80 cols). Reformatted with
`yapf -ir --style .style.yapf` and `isort --settings-path .isort.cfg`;
no logic changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CI's yapf check was failing because add_argument calls exceeded the
80-col limit. Reformatted with `yapf -ir --style .style.yapf`; no
logic changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
saurabh and others added 8 commits September 24, 2026 16:49
Explainability: scalr/explain.py implements Grad x Input saliency
attribution, wired into AnnotationModel.explain()/explain_class() and
a new `scalr explain` CLI subcommand, to report which genes supported
or contradicted a prediction (per cell or per class).

Streaming predict: AnnotationModel.predict() gains streaming/chunk_size
params (implied by passing a file path); gene alignment and scoring now
happen chunk-by-chunk instead of materializing one aligned matrix for
the whole dataset, per the low-resource design goal. Forwarded through
scalr.annotate() and the `scalr annotate` CLI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add Python-API and CLI usage examples for model.explain()/explain_class()
and streaming/chunked model.predict(), added in a6d45c0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- scalr.models.download()/push() pull/publish model artifacts to the Hugging
  Face Hub (optional `hub` extra); scalr.load_model auto-downloads a
  "<namespace>/<name>"-shaped name not found locally. Adds `scalr models
  push|pull` CLI subcommands.
- scalr.detect_drift(adata, model) compares a query dataset's per-gene and
  library-size/detected-gene-count distributions against training-time
  reference stats recorded by scalr.train, to catch whole-cohort drift that
  per-cell is_unknown/is_possible_doublet flags can miss. Adds `scalr drift`
  CLI subcommand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- build_loss_fn resolves a CustomLossBase subclass registered under
  scalr.nn.loss by name (same name/params pattern as models/preprocessors/
  etc.), falling back to torch.nn built-ins.
- FeatureExtractionPipeline.feature_scoring() now scores chunked models in
  parallel (joblib, threading backend) using the existing num_workers
  setting, instead of serially.
- FeatureExtractionPipeline.set_data_and_targets() was silently dropping its
  sample_chunksize argument, breaking feature_subsetted_model_training()
  whenever data was set this way instead of via
  load_data_and_targets_from_config().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- align_genes kept the LAST occurrence of a duplicated gene name (a dict
  comprehension), contradicting scalr.validation's documented "only the
  first occurrence will be used" contract; query cells could silently score
  against the wrong column's expression values.
- FeatureSubsetting.train_chunked_models() resolved model/train configs by
  mutating `self` inside a joblib.Parallel(n_jobs>1) worker closure, which
  never propagates back to the parent process under the process-based
  backend; the pipeline's written-back config ended up stale/unresolved
  whenever num_workers > 1. Now sourced from each worker's return value.
- Fixed scalr.hierarchy.aggregate_to_broad's docstring: is_unknown is
  carried over unchanged from the fine-level result, not recomputed, per
  its own inline comment and existing test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
scalr.analysis (DgeLMEM et al.) and scalr.feature.scoring.ShapScorer pulled
in scanpy/statsmodels/shap eagerly on `import scalr`, adding ~1.5s and
noisy scanpy-internal deprecation FutureWarnings to every `scalr` CLI
invocation, including commands unrelated to analysis/SHAP (e.g. `scalr
models list`). Deferred via PEP 562 package __getattr__; getattr(module,
name) (the name/params builder pattern used by build_analyser/build_scorer)
triggers the lazy import transparently, same as an eager import would.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…trix bug

anndata==0.11.x (the whole previously-allowed >=0.11.2 floor) raises
AttributeError: 'backed_csr_matrix' object has no attribute
'_validate_indices' when constructing an AnnCollection over a chunked .h5ad
directory (used whenever num_workers > 1), breaking that path end-to-end.
Confirmed fixed in anndata>=0.12; bumped the floor accordingly (still within
the existing <0.13 ceiling).

That surfaced a second, pre-existing bug: DgePseudoBulk._make_design_matrix
built each pseudobulk sample's AnnData.X from a scipy sparse .sum(axis=0),
which returns np.matrix, not ndarray; anndata 0.11.x tolerated this
silently, but anndata>=0.12 no longer does, which broke pydeseq2's internal
genewise-dispersion masking. Fixed by converting to a plain ndarray.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
AnnCollection's default per-chunk dtype probing (harmonize_dtypes=True)
crashes on backed sparse chunks under some anndata versions/backends
(AttributeError: 'backed_csr_matrix' object has no attribute
'_validate_indices'). Chunks read here are always written by scalr's own
write_chunkwise_data from a single source, so their dtypes are already
consistent by construction — the probing is redundant work scaLR doesn't
need, on top of being unreliable across anndata versions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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