Subchapter 4.12
references/troubleshooting.mdMarkdown11 KBView on GitHub
This reference provides a consolidated guide for diagnosing and resolving common issues across all scvi-tools models.
Scripts
Cluster Embed| Symptom | Likely Cause | Quick Fix |
|---|---|---|
| “X should contain integers” | Normalized data in X | Use layer="counts" in setup |
| CUDA out of memory | GPU memory exhausted | Reduce batch_size, use smaller model |
| Training loss is NaN | Bad data or learning rate | Check for all-zero cells/genes |
| Batches not mixing | Too few shared features | Increase HVGs, check gene overlap |
| Over-correction | Too aggressive integration | Use scANVI with labels |
| Import error | Missing dependencies | pip install scvi-tools[all] |
Cause: Seurat’s NormalizeData(normalization.method = "CLR") transforms raw ADT counts. totalVI requires raw integer counts for protein data.
Symptoms:
Solution:
# Check if protein data is normalized
protein = adata.obsm["protein_expression"]
print(f"Min value: {protein.min()}") # Should be 0 if raw counts
print(f"Contains integers: {np.allclose(protein, protein.astype(int))}")
# If importing from Seurat, use the raw counts assay, not the normalized one
# In R/Seurat, export the RNA assay's counts slot, not the data slot
# GetAssayData(seurat_obj, assay = "ADT", slot = "counts")Cause: scvi-tools requires raw integer counts, not normalized data.
Solution:
# Check if X contains integers
import numpy as np
print(f"X max: {adata.X.max()}")
print(f"Contains integers: {np.allclose(adata.X.data, adata.X.data.astype(int))}")
# If normalized, recover from raw
if hasattr(adata, 'raw') and adata.raw is not None:
adata = adata.raw.to_adata()
# Or use existing counts layer
adata.layers["counts"] = adata.X.copy()
scvi.model.SCVI.setup_anndata(adata, layer="counts")Cause: Incompatible sparse format or dense array expected.
Solution:
from scipy.sparse import csr_matrix
# Convert to CSR format (most compatible)
if hasattr(adata.X, 'toarray'):
adata.X = csr_matrix(adata.X)
# Or convert to dense if small enough
if adata.n_obs * adata.n_vars < 1e8:
adata.X = adata.X.toarray()Cause: Missing values or corrupted data.
Solution:
import numpy as np
# Check for issues
X = adata.X.toarray() if hasattr(adata.X, 'toarray') else adata.X
print(f"NaN count: {np.isnan(X).sum()}")
print(f"Inf count: {np.isinf(X).sum()}")
print(f"Negative count: {(X < 0).sum()}")
# Replace NaN/Inf with 0
X = np.nan_to_num(X, nan=0, posinf=0, neginf=0)
X = np.clip(X, 0, None) # Ensure non-negative
adata.X = csr_matrix(X)Cause: Column name mismatch in adata.obs.
Solution:
# List available columns
print(adata.obs.columns.tolist())
# Check for similar names
for col in adata.obs.columns:
if 'batch' in col.lower() or 'sample' in col.lower():
print(f"Potential batch column: {col}")Cause: Model or batch doesn’t fit in GPU memory.
Solutions (try in order):
# 1. Reduce batch size
model.train(batch_size=64) # Default is 128
# 2. Use smaller model architecture
model = scvi.model.SCVI(
adata,
n_latent=10, # Default is 10-30
n_layers=1 # Default is 1-2
)
# 3. Subset to fewer genes
sc.pp.highly_variable_genes(adata, n_top_genes=1500)
adata = adata[:, adata.var['highly_variable']].copy()
# 4. Clear GPU cache between models
import torch
torch.cuda.empty_cache()
# 5. Use CPU if GPU is too small
model.train(accelerator="cpu")Cause: CUDA not installed or version mismatch.
Diagnosis:
import torch
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"PyTorch version: {torch.__version__}")
print(f"CUDA version: {torch.version.cuda}")Solution:
# Check system CUDA
nvidia-smi
nvcc --version
# Reinstall PyTorch with matching CUDA
pip install torch --index-url https://download.pytorch.org/whl/cu118 # For CUDA 11.8
# Or
pip install torch --index-url https://download.pytorch.org/whl/cu121 # For CUDA 12.1Cause: Dataset too large for system RAM.
Solutions:
# 1. Process in chunks (for very large data)
# Subsample for initial exploration
adata_sample = adata[np.random.choice(adata.n_obs, 50000, replace=False)].copy()
# 2. Use backed mode for AnnData
adata = sc.read_h5ad("large_data.h5ad", backed='r')
# 3. Reduce gene count aggressively
adata = adata[:, adata.var['highly_variable']].copy()Cause: Numerical instability, bad data, or learning rate issues.
Solutions:
# 1. Check for problematic cells/genes
sc.pp.filter_cells(adata, min_genes=200)
sc.pp.filter_genes(adata, min_cells=3)
# 2. Remove cells with zero counts
adata = adata[adata.X.sum(axis=1) > 0].copy()
# 3. Use gradient clipping (built into scvi-tools)
model.train(max_epochs=200, early_stopping=True)Cause: Insufficient epochs, poor hyperparameters, or data issues.
Solutions:
# 1. Train longer
model.train(max_epochs=400)
# 2. Check training curves
import matplotlib.pyplot as plt
plt.plot(model.history['elbo_train'])
plt.plot(model.history['elbo_validation'])
plt.xlabel('Epoch')
plt.ylabel('ELBO')
plt.legend(['Train', 'Validation'])
# 3. Adjust model size for data size
# Small data (<10k cells): smaller model
model = scvi.model.SCVI(adata, n_latent=10, n_layers=1, dropout_rate=0.2)
# Large data (>100k cells): can use larger model
model = scvi.model.SCVI(adata, n_latent=30, n_layers=2)Cause: Model too complex or trained too long.
Solutions:
# 1. Enable early stopping
model.train(early_stopping=True, early_stopping_patience=10)
# 2. Add regularization
model = scvi.model.SCVI(adata, dropout_rate=0.2)
# 3. Reduce model complexity
model = scvi.model.SCVI(adata, n_layers=1)Cause: Too few shared features, strong biological differences, or technical issues.
Solutions:
# 1. Check gene overlap between batches
for batch in adata.obs['batch'].unique():
batch_genes = adata[adata.obs['batch'] == batch].var_names
print(f"{batch}: {len(batch_genes)} genes")
# 2. Use more HVGs
sc.pp.highly_variable_genes(adata, n_top_genes=4000, batch_key="batch")
# 3. Train longer
model.train(max_epochs=400)
# 4. Increase latent dimensions
model = scvi.model.SCVI(adata, n_latent=50)Cause: Model removes too much variation.
Solutions:
# 1. Use scANVI with cell type labels
scvi.model.SCANVI.from_scvi_model(scvi_model, labels_key="cell_type")
# 2. Reduce model capacity
model = scvi.model.SCVI(adata, n_latent=10)
# 3. Use categorical covariates instead of batch_key
scvi.model.SCVI.setup_anndata(
adata,
layer="counts",
categorical_covariate_keys=["batch"] # Less aggressive than batch_key
)Cause: Unbalanced batch sizes or incomplete integration.
Solutions:
# 1. Check batch distribution
print(adata.obs['batch'].value_counts())
# 2. Subsample to balance
from sklearn.utils import resample
balanced = []
min_size = adata.obs['batch'].value_counts().min()
for batch in adata.obs['batch'].unique():
batch_data = adata[adata.obs['batch'] == batch]
balanced.append(batch_data[np.random.choice(len(batch_data), min_size, replace=False)])
adata_balanced = sc.concat(balanced)Solutions:
# 1. Check label distribution
print(adata.obs['cell_type'].value_counts())
# 2. Use Unknown for low-confidence cells
adata.obs.loc[adata.obs['prediction_score'] < 0.5, 'cell_type'] = 'Unknown'
# 3. Train scVI longer before scANVI
scvi_model.train(max_epochs=300)
scanvi_model = scvi.model.SCANVI.from_scvi_model(scvi_model, labels_key="cell_type")
scanvi_model.train(max_epochs=100)Solutions:
# 1. Use denoised protein values
_, protein_denoised = model.get_normalized_expression(return_mean=True)
# 2. Check isotype controls
# Isotype controls should have low expression
for i, name in enumerate(adata.uns["protein_names"]):
if 'isotype' in name.lower():
print(f"{name}: mean={adata.obsm['protein_expression'][:, i].mean():.1f}")Solutions:
# 1. Use more variable peaks
from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold(threshold=0.05)
adata = adata[:, selector.fit(adata.X).get_support()].copy()
# 2. Binarize data
adata.X = (adata.X > 0).astype(np.float32)Solutions:
# Ensure same cells in same order
common_cells = adata_rna.obs_names.intersection(adata_atac.obs_names)
adata_rna = adata_rna[common_cells].copy()
adata_atac = adata_atac[common_cells].copy()Solutions:
# 1. Check gene overlap
common_genes = adata_ref.var_names.intersection(adata_spatial.var_names)
print(f"Common genes: {len(common_genes)}") # Should be >1000
# 2. Use tissue-matched reference
# Reference should contain all cell types expected in spatial data
# 3. Check reference quality
print(adata_ref.obs['cell_type'].value_counts())Key differences:
# 0.x API
scvi.data.setup_anndata(adata, ...)
# 1.x API (current)
scvi.model.SCVI.setup_anndata(adata, ...)import scvi
import scanpy as sc
import anndata
import torch
print(f"scvi-tools: {scvi.__version__}")
print(f"scanpy: {sc.__version__}")
print(f"anndata: {anndata.__version__}")
print(f"torch: {torch.__version__}")scvi-tools>=1.0.0
scanpy>=1.9.0
anndata>=0.9.0
torch>=2.0.0When reporting issues, include:
scvi.__version__)