Skill Tracer
Skills › Knowledge Work Plugins › scvi-tools › troubleshooting.md

troubleshooting.md

420 lines · bio-research/skills/scvi-tools/references/troubleshooting.md at 2574e29

Open on GitHub
1# Troubleshooting Guide for scvi-tools
2
3This reference provides a consolidated guide for diagnosing and resolving common issues across all scvi-tools models.
4

Troubleshooting Guide for scvi-tools

This reference provides a consolidated guide for diagnosing and resolving common issues across all scvi-tools models.

Raw lines 1 to 4
5## Quick Diagnosis
6
7| Symptom | Likely Cause | Quick Fix |
8|---------|--------------|-----------|
9| "X should contain integers" | Normalized data in X | Use `layer="counts"` in setup |
10| CUDA out of memory | GPU memory exhausted | Reduce `batch_size`, use smaller model |
11| Training loss is NaN | Bad data or learning rate | Check for all-zero cells/genes |
12| Batches not mixing | Too few shared features | Increase HVGs, check gene overlap |
13| Over-correction | Too aggressive integration | Use scANVI with labels |
14| Import error | Missing dependencies | `pip install scvi-tools[all]` |

Quick Diagnosis

SymptomLikely CauseQuick Fix
"X should contain integers"Normalized data in XUse layer="counts" in setup
CUDA out of memoryGPU memory exhaustedReduce batch_size, use smaller model
Training loss is NaNBad data or learning rateCheck for all-zero cells/genes
Batches not mixingToo few shared featuresIncrease HVGs, check gene overlap
Over-correctionToo aggressive integrationUse scANVI with labels
Import errorMissing dependenciespip install scvi-tools[all]
Raw lines 5 to 15
16## Data Format Issues

Data Format Issues

Raw lines 16 to 17
18### Issue: CITE-seq protein data from Seurat is CLR-normalized
20**Cause**: Seurat's `NormalizeData(normalization.method = "CLR")` transforms raw ADT counts. totalVI requires raw integer counts for protein data.
22**Symptoms**:
23- Protein values are not integers
24- Protein values contain negative numbers
25- Model training produces poor results
27**Solution**:
28```python
29# Check if protein data is normalized
30protein = adata.obsm["protein_expression"]
31print(f"Min value: {protein.min()}") # Should be 0 if raw counts
32print(f"Contains integers: {np.allclose(protein, protein.astype(int))}")
34# If importing from Seurat, use the raw counts assay, not the normalized one
35# In R/Seurat, export the RNA assay's counts slot, not the data slot
36# GetAssayData(seurat_obj, assay = "ADT", slot = "counts")
37```

Issue: CITE-seq protein data from Seurat is CLR-normalized

Cause: Seurat's NormalizeData(normalization.method = "CLR") transforms raw ADT counts. totalVI requires raw integer counts for protein data.

Symptoms:

  • Protein values are not integers
  • Protein values contain negative numbers
  • Model training produces poor results

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")
Raw lines 18 to 38
39### Issue: "layer not found" or "X should contain integers"
41**Cause**: scvi-tools requires raw integer counts, not normalized data.
43**Solution**:
44```python
45# Check if X contains integers
46import numpy as np
47print(f"X max: {adata.X.max()}")
48print(f"Contains integers: {np.allclose(adata.X.data, adata.X.data.astype(int))}")
50# If normalized, recover from raw
51if hasattr(adata, 'raw') and adata.raw is not None:
52 adata = adata.raw.to_adata()
54# Or use existing counts layer
55adata.layers["counts"] = adata.X.copy()
56scvi.model.SCVI.setup_anndata(adata, layer="counts")
57```

Issue: "layer not found" or "X should contain integers"

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")
Raw lines 39 to 58
59### Issue: Sparse matrix errors
61**Cause**: Incompatible sparse format or dense array expected.
63**Solution**:
64```python
65from scipy.sparse import csr_matrix
67# Convert to CSR format (most compatible)
68if hasattr(adata.X, 'toarray'):
69 adata.X = csr_matrix(adata.X)
71# Or convert to dense if small enough
72if adata.n_obs * adata.n_vars < 1e8:
73 adata.X = adata.X.toarray()
74```

Issue: Sparse matrix errors

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()
Raw lines 59 to 75
76### Issue: NaN or Inf values in data
78**Cause**: Missing values or corrupted data.
80**Solution**:
81```python
82import numpy as np
84# Check for issues
85X = adata.X.toarray() if hasattr(adata.X, 'toarray') else adata.X
86print(f"NaN count: {np.isnan(X).sum()}")
87print(f"Inf count: {np.isinf(X).sum()}")
88print(f"Negative count: {(X < 0).sum()}")
90# Replace NaN/Inf with 0
91X = np.nan_to_num(X, nan=0, posinf=0, neginf=0)
92X = np.clip(X, 0, None) # Ensure non-negative
93adata.X = csr_matrix(X)
94```

Issue: NaN or Inf values in data

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)
Raw lines 76 to 95
96### Issue: batch_key or labels_key not found
98**Cause**: Column name mismatch in adata.obs.
100**Solution**:
101```python
102# List available columns
103print(adata.obs.columns.tolist())
105# Check for similar names
106for col in adata.obs.columns:
107 if 'batch' in col.lower() or 'sample' in col.lower():
108 print(f"Potential batch column: {col}")
109```

Issue: batch_key or labels_key not found

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}")
Raw lines 96 to 110
111## GPU and Memory Issues

GPU and Memory Issues

Raw lines 111 to 112
113### Issue: CUDA out of memory
115**Cause**: Model or batch doesn't fit in GPU memory.
117**Solutions** (try in order):
119```python
120# 1. Reduce batch size
121model.train(batch_size=64) # Default is 128
123# 2. Use smaller model architecture
124model = scvi.model.SCVI(
125 adata,
126 n_latent=10, # Default is 10-30
127 n_layers=1 # Default is 1-2
130# 3. Subset to fewer genes
131sc.pp.highly_variable_genes(adata, n_top_genes=1500)
132adata = adata[:, adata.var['highly_variable']].copy()
134# 4. Clear GPU cache between models
135import torch
136torch.cuda.empty_cache()
138# 5. Use CPU if GPU is too small
139model.train(accelerator="cpu")
140```

Issue: CUDA out of memory

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")
Raw lines 113 to 141
142### Issue: No GPU detected
144**Cause**: CUDA not installed or version mismatch.
146**Diagnosis**:
147```python
148import torch
149print(f"CUDA available: {torch.cuda.is_available()}")
150print(f"PyTorch version: {torch.__version__}")
151print(f"CUDA version: {torch.version.cuda}")
152```
154**Solution**:
155```bash
156# Check system CUDA
157nvidia-smi
158nvcc --version
160# Reinstall PyTorch with matching CUDA
161pip install torch --index-url https://download.pytorch.org/whl/cu118 # For CUDA 11.8
162# Or
163pip install torch --index-url https://download.pytorch.org/whl/cu121 # For CUDA 12.1
164```

Issue: No GPU detected

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.1
Raw lines 142 to 165
166### Issue: Memory error with large datasets
168**Cause**: Dataset too large for system RAM.
170**Solutions**:
171```python
172# 1. Process in chunks (for very large data)
173# Subsample for initial exploration
174adata_sample = adata[np.random.choice(adata.n_obs, 50000, replace=False)].copy()
176# 2. Use backed mode for AnnData
177adata = sc.read_h5ad("large_data.h5ad", backed='r')
179# 3. Reduce gene count aggressively
180adata = adata[:, adata.var['highly_variable']].copy()
181```

Issue: Memory error with large datasets

Cause: 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()
Raw lines 166 to 182
183## Training Issues

Training Issues

Raw lines 183 to 184
185### Issue: Training loss is NaN
187**Cause**: Numerical instability, bad data, or learning rate issues.
189**Solutions**:
190```python
191# 1. Check for problematic cells/genes
192sc.pp.filter_cells(adata, min_genes=200)
193sc.pp.filter_genes(adata, min_cells=3)
195# 2. Remove cells with zero counts
196adata = adata[adata.X.sum(axis=1) > 0].copy()
198# 3. Use gradient clipping (built into scvi-tools)
199model.train(max_epochs=200, early_stopping=True)
200```

Issue: Training loss is NaN

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)
Raw lines 185 to 201
202### Issue: Training doesn't converge
204**Cause**: Insufficient epochs, poor hyperparameters, or data issues.
206**Solutions**:
207```python
208# 1. Train longer
209model.train(max_epochs=400)
211# 2. Check training curves
212import matplotlib.pyplot as plt
213plt.plot(model.history['elbo_train'])
214plt.plot(model.history['elbo_validation'])
215plt.xlabel('Epoch')
216plt.ylabel('ELBO')
217plt.legend(['Train', 'Validation'])
219# 3. Adjust model size for data size
220# Small data (<10k cells): smaller model
221model = scvi.model.SCVI(adata, n_latent=10, n_layers=1, dropout_rate=0.2)
223# Large data (>100k cells): can use larger model
224model = scvi.model.SCVI(adata, n_latent=30, n_layers=2)
225```

Issue: Training doesn't converge

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)
Raw lines 202 to 226
227### Issue: Overfitting (validation loss increases)
229**Cause**: Model too complex or trained too long.
231**Solutions**:
232```python
233# 1. Enable early stopping
234model.train(early_stopping=True, early_stopping_patience=10)
236# 2. Add regularization
237model = scvi.model.SCVI(adata, dropout_rate=0.2)
239# 3. Reduce model complexity
240model = scvi.model.SCVI(adata, n_layers=1)
241```

Issue: Overfitting (validation loss increases)

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)
Raw lines 227 to 242
243## Integration Issues

Integration Issues

Raw lines 243 to 244
245### Issue: Batches don't mix
247**Cause**: Too few shared features, strong biological differences, or technical issues.
249**Solutions**:
250```python
251# 1. Check gene overlap between batches
252for batch in adata.obs['batch'].unique():
253 batch_genes = adata[adata.obs['batch'] == batch].var_names
254 print(f"{batch}: {len(batch_genes)} genes")
256# 2. Use more HVGs
257sc.pp.highly_variable_genes(adata, n_top_genes=4000, batch_key="batch")
259# 3. Train longer
260model.train(max_epochs=400)
262# 4. Increase latent dimensions
263model = scvi.model.SCVI(adata, n_latent=50)
264```

Issue: Batches don't mix

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)
Raw lines 245 to 265
266### Issue: Over-correction (biological signal lost)
268**Cause**: Model removes too much variation.
270**Solutions**:
271```python
272# 1. Use scANVI with cell type labels
273scvi.model.SCANVI.from_scvi_model(scvi_model, labels_key="cell_type")
275# 2. Reduce model capacity
276model = scvi.model.SCVI(adata, n_latent=10)
278# 3. Use categorical covariates instead of batch_key
279scvi.model.SCVI.setup_anndata(
280 adata,
281 layer="counts",
282 categorical_covariate_keys=["batch"] # Less aggressive than batch_key
284```

Issue: Over-correction (biological signal lost)

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
)
Raw lines 266 to 285
286### Issue: One batch dominates clusters
288**Cause**: Unbalanced batch sizes or incomplete integration.
290**Solutions**:
291```python
292# 1. Check batch distribution
293print(adata.obs['batch'].value_counts())
295# 2. Subsample to balance
296from sklearn.utils import resample
297balanced = []
298min_size = adata.obs['batch'].value_counts().min()
299for batch in adata.obs['batch'].unique():
300 batch_data = adata[adata.obs['batch'] == batch]
301 balanced.append(batch_data[np.random.choice(len(batch_data), min_size, replace=False)])
302adata_balanced = sc.concat(balanced)
303```

Issue: One batch dominates clusters

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)
Raw lines 286 to 304
305## Model-Specific Issues

Model-Specific Issues

Raw lines 305 to 306
307### scANVI: Poor label transfer
309**Solutions**:
310```python
311# 1. Check label distribution
312print(adata.obs['cell_type'].value_counts())
314# 2. Use Unknown for low-confidence cells
315adata.obs.loc[adata.obs['prediction_score'] < 0.5, 'cell_type'] = 'Unknown'
317# 3. Train scVI longer before scANVI
318scvi_model.train(max_epochs=300)
319scanvi_model = scvi.model.SCANVI.from_scvi_model(scvi_model, labels_key="cell_type")
320scanvi_model.train(max_epochs=100)
321```

scANVI: Poor label transfer

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)
Raw lines 307 to 322
323### totalVI: Noisy protein signal
325**Solutions**:
326```python
327# 1. Use denoised protein values
328_, protein_denoised = model.get_normalized_expression(return_mean=True)
330# 2. Check isotype controls
331# Isotype controls should have low expression
332for i, name in enumerate(adata.uns["protein_names"]):
333 if 'isotype' in name.lower():
334 print(f"{name}: mean={adata.obsm['protein_expression'][:, i].mean():.1f}")
335```

totalVI: Noisy protein signal

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}")
Raw lines 323 to 336
337### PeakVI: Poor clustering
339**Solutions**:
340```python
341# 1. Use more variable peaks
342from sklearn.feature_selection import VarianceThreshold
343selector = VarianceThreshold(threshold=0.05)
344adata = adata[:, selector.fit(adata.X).get_support()].copy()
346# 2. Binarize data
347adata.X = (adata.X > 0).astype(np.float32)
348```

PeakVI: Poor clustering

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)
Raw lines 337 to 349
350### MultiVI: Different cell counts between modalities
352**Solutions**:
353```python
354# Ensure same cells in same order
355common_cells = adata_rna.obs_names.intersection(adata_atac.obs_names)
356adata_rna = adata_rna[common_cells].copy()
357adata_atac = adata_atac[common_cells].copy()
358```

MultiVI: Different cell counts between modalities

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()
Raw lines 350 to 359
360### DestVI: Poor deconvolution
362**Solutions**:
363```python
364# 1. Check gene overlap
365common_genes = adata_ref.var_names.intersection(adata_spatial.var_names)
366print(f"Common genes: {len(common_genes)}") # Should be >1000
368# 2. Use tissue-matched reference
369# Reference should contain all cell types expected in spatial data
371# 3. Check reference quality
372print(adata_ref.obs['cell_type'].value_counts())
373```

DestVI: Poor deconvolution

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())
Raw lines 360 to 374
375## Version Compatibility

Version Compatibility

Raw lines 375 to 376
377### scvi-tools 1.x vs 0.x API changes
379Key differences:
380```python
381# 0.x API
382scvi.data.setup_anndata(adata, ...)
384# 1.x API (current)
385scvi.model.SCVI.setup_anndata(adata, ...)
386```

scvi-tools 1.x vs 0.x API changes

Key differences:

# 0.x API
scvi.data.setup_anndata(adata, ...)

# 1.x API (current)
scvi.model.SCVI.setup_anndata(adata, ...)
Raw lines 377 to 387
388### Check versions
389```python
390import scvi
391import scanpy as sc
392import anndata
393import torch
395print(f"scvi-tools: {scvi.__version__}")
396print(f"scanpy: {sc.__version__}")
397print(f"anndata: {anndata.__version__}")
398print(f"torch: {torch.__version__}")
399```

Check versions

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__}")
Raw lines 388 to 400
401### Recommended versions (as of late 2024)
402```
403scvi-tools>=1.0.0
404scanpy>=1.9.0
405anndata>=0.9.0
406torch>=2.0.0
407```

Recommended versions (as of late 2024)

scvi-tools>=1.0.0
scanpy>=1.9.0
anndata>=0.9.0
torch>=2.0.0
Raw lines 401 to 408
409## Getting Help
4111. **Check documentation**: https://docs.scvi-tools.org/
4122. **GitHub issues**: https://github.com/scverse/scvi-tools/issues
4133. **Discourse forum**: https://discourse.scverse.org/
4144. **Tutorials**: https://docs.scvi-tools.org/en/stable/tutorials/index.html
416When reporting issues, include:
417- scvi-tools version (`scvi.__version__`)
418- Python version
419- Full error traceback
420- Minimal reproducible example

Getting Help

  1. Check documentation: https://docs.scvi-tools.org/
  2. GitHub issues: https://github.com/scverse/scvi-tools/issues
  3. Discourse forum: https://discourse.scverse.org/
  4. Tutorials: https://docs.scvi-tools.org/en/stable/tutorials/index.html

When reporting issues, include:

  • scvi-tools version (scvi.__version__)
  • Python version
  • Full error traceback
  • Minimal reproducible example
Raw lines 409 to 420