7. GPU 加速分析#
关键要点
rapids-singlecell 在 GPU 上复刻了 scanpy API,因此大多数预处理和聚类流程都可以通过把 sc 换成 rsc,并用 rsc.get.anndata_to_GPU 将 AnnData 移到 GPU 来完成迁移。
只选定一种分配器策略:如果数据能装进 VRAM,就用池分配器(pool allocator);如果装不下,则用托管(统一)内存。两者混用会导致内存碎片化,并削弱加速效果。
一旦某个步骤不再需要 GPU,就用 rsc.get.anndata_to_CPU 将数据移回主机内存、释放中间的 CuPy 数组,并调用 cp.get_default_memory_pool().free_all_blocks() 来释放你不再使用的 VRAM。
对于超出单块 GPU 显存的数据集,可结合 dask-cuda 和 AnnData 的 read_elem_lazy 读取器,将矩阵分块流式送入 GPU;QC、HVG 选择、缩放和 PCA 等步骤会偶尔触发同步并强制计算。
环境设置
安装 conda:
在创建环境之前,请确保 conda 已安装在您的系统中。
保存 yml 内容:
将 yml 选项卡中的内容复制到名为
environment.yml的文件中。
创建环境:
打开终端或命令提示符。
运行以下命令:
conda env create -f environment.yml
激活环境:
创建好环境后,使用以下命令激活它:
conda activate <environment_name>
替换
<environment_name>,名称就是在environment.yml文件中指定的那个。在 yml 文件里,它看起来像这样:name: <environment_name>
验证安装:
通过运行以下命令,检查环境是否创建成功:
conda env list
name: rapids_singlecell
channels:
- conda-forge
- bioconda
dependencies:
- python=3.14
- conda-forge::scanpy=1.12.1
- pip
- pip:
- --extra-index-url=https://pypi.nvidia.com
- rapids-singlecell-cu13[rapids]
- lamindb
获取数据和笔记本
本书使用 lamindb,并通过 theislab/sc-best-practices 实例 来存储、共享和加载数据集与笔记本。感谢 Lamin Labs 提供免费托管服务。
安装 lamindb
安装 lamindb Python 软件包:
pip install lamindb
可选择创建 lamin 账户
按照以下 说明 注册并登录。
验证你的设置
运行
lamin connect命令:
import lamindb as ln ln.Artifact.connect("theislab/sc-best-practices").df()
你现在应该看到最多100个存储的数据集。
访问数据集(Artifact)
在 Artifacts 页面 搜索该数据集。
加载一个 Artifact 及其对应的对象:
import lamindb as ln af = ln.Artifact.connect("theislab/sc-best-practices").get(key="key_of_dataset", is_latest=True) obj = af.load()
该对象现在已可在内存中访问,并可用于分析。请调整
ln.Artifact.connect("theislab/sc-best-practices").get("SOMEIDXXXX")后缀以获取相应的版本。访问笔记本(Transform)
在 Transforms 页面 搜索该笔记本。
加载笔记本:
lamin load <notebook url>
这会把笔记本下载到当前工作目录。与
Artifacts类似,你可以调整后缀 ID 来获取旧版本。
7.1. 动机#
本书的大多数章节都在 CPU 上运行,数据集规模为数万到数十万个细胞。如今的现代图谱已达到数百万个细胞,同一套 scanpy 风格的流程,在笔记本电脑上几分钟就能跑完,在工作站上却可能耗费数小时,甚至在跑完之前就因内存耗尽而崩溃。
rapids-singlecell (RSC)是一个 scverse 核心包,它在以下库的基础上,将 scanpy 的 API 移植到 GPU 上: RAPIDS、 CuPy 和 cuML [Dicks et al., 2026, Nolet et al., 2024]。它还提供了部分 squidpy [Palla et al., 2022], decoupler [Badia-i-Mompel et al., 2022]和 pertpy [Heumos et al., 2025] 等例程的 GPU 实现。相对于 CPU 版 scanpy,标准的预处理和聚类流程通常可获得 10–20× 的端到端加速,而 UMAP、t-SNE、Leiden 等单个步骤的运行速度可快 50–200×。
7.2. GPU 何时才划算?#
当计算任务规整、且规模大到足以摊薄主机到设备的传输成本时,GPU 才有帮助。对于单细胞数据,大致的经验法则是:
~50k 细胞以下:用 CPU 版 scanpy 即可,此时传输开销可能占主导。只有当你在方法开发过程中需要快速迭代时,才使用 GPU。
单块 GPU 上 50k–1M 个细胞:这是最佳区间。单块 24–80 GB 的 GPU 即可交互式地运行整条标准流程。
>1M 细胞,或存在内存压力:将 RSC 与 dask-cuda 结合,用于核外(out-of-core)或多 GPU 执行(见下文)。
单块 GPU 上的主要瓶颈是 VRAM,而不是计算能力。一个 1M 细胞 × 30k 基因的稠密 float32 表达矩阵约为 120 GB,根本无法放入显存。稀疏 CSR 格式让 GPU 上的单细胞分析变得可行;下面的大多数最佳实践,也都是围绕如何让矩阵尽可能久地保持稀疏,并停留在合适的内存位置。
7.3. 安装#
最近的 RSC 发行版提供了预编译的 wheel 包,覆盖 CUDA 12 和 13,以及从 Turing 到 Blackwell 的各代 GPU 架构。请使用 [rapids] 可选项进行安装,这样完整的 RAPIDS 堆栈(cuDF、cuML、cuGraph、RMM、CuPy)就会连同预编译的内核一起被拉取进来:
# CUDA 13
pip install 'rapids-singlecell-cu13[rapids]' --extra-index-url=https://pypi.nvidia.com
# CUDA 12
pip install 'rapids-singlecell-cu12[rapids]' --extra-index-url=https://pypi.nvidia.com
--extra-index-url 是必需的,因为 RAPIDS 的 wheel 包托管在 NVIDIA 的 PyPI 索引上,而不是公共索引上。在较旧的 RSC 版本中,你还必须额外安装 RAPIDS 的 conda 包;现在已不再需要这一步。
验证安装
在运行任何繁重任务之前,先确认 CuPy 能识别到你的 GPU:
import cupy as cp
print(cp.cuda.runtime.getDeviceCount(), cp.cuda.Device(0).name)
如果这里抛出 CUDARuntimeError,说明你的驱动版本早于 wheel 包内的 CUDA 运行时;此时应更新 NVIDIA 驱动,而不是 CUDA 工具包。
7.4. 用 RMM 管理内存#
RSC 的所有 GPU 内存分配都会经由 RAPIDS 内存管理器(RMM)[NVIDIA RAPIDS, 2024]。导入 rapids_singlecell 时,它已经把默认的 CuPy 分配器替换为基于 RMM 的分配器;但 RMM 的默认策略仍是普通 CUDA 分配器:每次分配都会调用 cudaMalloc,每次释放都会调用 cudaFree。单细胞流程会频繁分配内存(每个 scanpy 函数都会产生临时数组),因此这种默认策略会浪费大量性能。
有两种策略,并且应该只选择其中一种:
池分配器 ——RMM 会预先抓取一大块 VRAM,并从中满足后续的内存分配。这是迄今为止最快的方案,但它会把你限制在 VRAM 能容纳的范围内。只要你的数据集放得下,就使用此方案。
托管(统一)内存 ——分配由 CUDA 托管内存支持,因此驱动程序会在主机 RAM 与显存之间透明地换入换出数据。这正是你能在一块 40 GB GPU 上分析 200 GB 数据集的原因。它的单次操作较慢(缺页并非没有代价),但通常仍快于 CPU 版 scanpy。
不同时启用两者
把 pool_allocator=True 和 managed_memory=True 组合使用看起来很诱人,但内存池会建立在托管内存之上,两种策略会相互冲突。这样既要承担换页带来的延迟,又得不到内存池的速度优势;随着 notebook 运行时间变长,碎片化还会越来越严重。请只选其一。
请在 notebook 的 最顶部 、在创建任何 CuPy 数组之前,配置池分配器。在 CuPy 已经分配之后再重新初始化 RMM,会泄漏旧的分配。
import cupy as cp
import rmm
from rmm.allocators.cupy import rmm_cupy_allocator
rmm.reinitialize(
pool_allocator=True,
managed_memory=False,
initial_pool_size="4GB", # size to expected peak — growth events defeat the pool's purpose
maximum_pool_size="32GB", # leave headroom for plotting libraries, etc.
)
cp.cuda.set_allocator(rmm_cupy_allocator)
为什么要设定初始池大小 ?
当 pool_allocator=True 且 initial_pool_size=None 时,RMM 只会先抓取一半 VRAM,之后再继续扩张。每次扩张都会触发新的 cudaMalloc,这正是内存池本应避免的操作;而且新块与旧块不连续,还会造成内存池碎片化。把 initial_pool_size 设到接近预期峰值(专用 GPU 上通常为 VRAM 的 60–80%),可以让内存池不再扩张,或只扩张极少几次。maximum_pool_size 才是真正的上限,并且与初始大小相互独立;因此这不是在限制自己,只是在告诉 RMM 第一次调用时应先抓取多少显存。
如果你的数据集放不下,就改用托管内存。在搭载 Pascal 或更新架构 GPU 的 Linux 上,这是真正的统一内存,开箱即支持超额订阅(oversubscription);而在 WSL2 下的 Windows 上,它是模拟实现的,速度略慢一些。
rmm.reinitialize(
pool_allocator=False,
managed_memory=True,
)
cp.cuda.set_allocator(rmm_cupy_allocator)
配置好分配器之后,其余导入就和普通的 scanpy notebook 一样;按照惯例,rapids_singlecell 通常简写为 rsc。
import warnings
warnings.filterwarnings("ignore")
import anndata as ad
import pooch
import rapids_singlecell as rsc
import scanpy as sc
7.5. GPU 上的 scanpy 分析流程#
RSC 有意镜像了 scanpy 的模块布局: rsc.pp 用于预处理和 rsc.tl 用于工具。大多数 scanpy 调用都有一个签名相同、一一对应的 RSC 对应函数,因此移植流程基本上只是替换前缀的问题。
思路很简单:
像平常一样读入 AnnData(保留在 CPU 内存中,且
.X为稀疏矩阵)。用
rsc.get.anndata_to_GPU一次性移到 GPU。用
rsc.*运行计算量大的预处理和聚类步骤。用
rsc.get.anndata_to_CPU移回主机内存,用于绘图、保存或其他任何仅限 CPU 的操作。
我们使用与上游 RSC 教程相同的示例数据集,即约 90k 细胞的 DLI census 子集,因此这里的运行时间可以直接与 rapids-singlecell 文档中的报告结果比较。
path = pooch.retrieve(
url="https://exampledata.scverse.org/rapids-singlecell/dli_census.h5ad",
fname="dli_census.h5ad",
)
adata = ad.read_h5ad(path)
adata.var_names = adata.var.feature_name
adata
AnnData object with n_obs × n_vars = 216611 × 25430
obs: 'soma_joinid', 'dataset_id', 'assay', 'assay_ontology_term_id', 'cell_type', 'cell_type_ontology_term_id', 'development_stage', 'development_stage_ontology_term_id', 'disease', 'disease_ontology_term_id', 'donor_id', 'is_primary_data', 'observation_joinid', 'self_reported_ethnicity', 'self_reported_ethnicity_ontology_term_id', 'sex', 'sex_ontology_term_id', 'suspension_type', 'tissue', 'tissue_ontology_term_id', 'tissue_type', 'tissue_general', 'tissue_general_ontology_term_id', 'raw_sum', 'nnz', 'raw_mean_nnz', 'raw_variance_nnz', 'n_measured_vars'
var: 'soma_joinid', 'feature_id', 'feature_name', 'feature_type', 'feature_length', 'nnz', 'n_measured_obs', 'n_cells'
rsc.get.anndata_to_GPU 及其对应的 anndata_to_CPU 只作用于 .X 和 .layers;obs、var、obsm 和 varm 始终留在主机内存中。默认只移动 .X;传入 convert_all=True 可同时移动所有 layer,或用 layer="<name>" 移动指定 layer 而不是 .X。传输会就地把矩阵转换为 CuPy CSR(或稠密的 cupy.ndarray),后续 RSC 调用会从设备内存读取。
rsc.get.anndata_to_GPU(adata)
7.5.1. 质量控制#
QC 指标和基因过滤在 rsc.pp 中都有 GPU 实现。至于基因家族掩码本身,我们直接在 adata.var_names 上使用普通 pandas 的 startswith:无论 .X 位于何处,这个索引都是主机侧的 pd.Index,再套一层包装器并不会带来收益。
adata.var["MT"] = adata.var_names.str.startswith("MT-")
adata.var["RIBO"] = adata.var_names.str.startswith(("RPS", "RPL"))
rsc.pp.calculate_qc_metrics(adata, qc_vars=["MT", "RIBO"])
adata = adata[adata.obs["n_genes_by_counts"] < 5000]
adata = adata[adata.obs["pct_counts_MT"] < 20]
rsc.pp.filter_genes(adata, min_cells=3)
adata.shape
(213191, 25430)
7.5.2. 归一化、HVG、缩放#
这些函数是一对一移植自 scanpy,对应前文的 归一化 和 特征选择 章节。scanpy 的 highly_variable_genes 支持的所有 flavor= 选项(seurat、seurat_v3、seurat_v3_paper、cell_ranger、pearson_residuals)都可在 GPU 上使用。RSC 还额外提供 flavor="poisson_gene_selection",这是 M3Drop 解析式泊松基因选择(analytical Poisson gene selection)的 CUDA 内核移植版本,在 scanpy 中没有对应的 CPU 实现。
rsc.pp.normalize_total(adata, target_sum=1e4)
rsc.pp.log1p(adata)
rsc.pp.highly_variable_genes(adata, n_top_genes=5000, flavor="cell_ranger")
adata.raw = adata
rsc.pp.filter_highly_variable(adata)
rsc.pp.scale(adata, max_value=10)
暂存到 .raw 会使 VRAM 瞬时翻倍
把 HVG 之前的矩阵暂存到 .raw 的标准做法会复制一份 .X;在 GPU 上,这份副本也位于 VRAM 中,因此从 adata.raw = adata 到 rsc.pp.scale 之间,显存占用大致会翻倍。在 90k 细胞的示例中这通常不成问题;但在 100 万细胞的图谱上,这种瞬时 2× 峰值正是导致 OOM(内存溢出)的原因。如果后续不需要用 .raw 做 marker 分析,或不需要在 DE 中设置 use_raw=True,就直接删掉这一行。若确实需要,则应把 initial_pool_size 设到足以覆盖峰值,或者在移动到 GPU 之前,先在 CPU 上完成 normalize → HVG → raw 这一轮操作。
7.5.3. 主成分分析(PCA)、邻居、UMAP、聚类#
PCA 会根据输入类型分派到不同实现:稠密矩阵交给 cuML(zero_center=True 时使用 PCA,否则使用 TruncatedSVD),而 chunked=True 会使用 cuML 的 IncrementalPCA。对于其他输入(稀疏矩阵和 Dask 输入),RSC 使用自己的 CUDA 内核实现:feature 数 ≤8k 时走协方差特征分解路径,更宽的矩阵则走 Lanczos SVD。近邻默认使用暴力精确 KNN,同时也为超大数据集提供若干由 cuVS 支持的近似方法。UMAP 由 cuML 支持;Leiden 则通过 cuGraph 运行。
rsc.tl.pca(adata, n_comps=50)
rsc.pp.neighbors(adata, n_neighbors=15, n_pcs=40)
rsc.tl.umap(adata)
rsc.tl.leiden(adata, resolution=0.6)
近似 KNN 以精度换取可扩展性
rsc.pp.neighbors 通过以下方式暴露 cuVS 的近似最近邻后端: algorithm=:
brute(默认):精确 KNN,复杂度为 O(n²)。最适合约 50 万细胞以下的数据集;超过该规模后,二次方的代价将占主导,此时 ANN 后端更快。ivfflat:倒排文件 ANN。当brute变得太慢时,它是一个稳妥的默认选择:速度快、在聚类边界上表现良好,并且可通过n_lists/n_probes调参。all_neighbors:cuVS 的批量 / 聚类 ANN(底层使用nn_descent或ivf_pq),会把数据集划分到多个簇,并分布到多块 GPU 上。当数据集超出 VRAM,或有多块 GPU 可用时,这是合适的选择。ivfpq:类似ivfflat,但会通过乘积量化(product quantization)压缩索引,以一定精度为代价支持超大数据集。cagra和nn_descent:基于图的 ANN 后端,如果你想做比较可以使用;nn_descent尤其在极高维度下表现出色。
所有这些方法都会在边缘处改变聚类分配。如果你分别使用 brute 和某个 ANN 后端重新运行相同的工作流程,可以预期大部分细胞会落入相同的聚类,只有少数会发生变动——不要把这当作 bug。
7.5.4. 绘图仍交由 scanpy 完成#
RSC 刻意不重复 scanpy 的绘图 API。重计算步骤完成后,熟悉的 sc.pl.* 函数可以直接使用:obs、var 以及 obsm 中的 Embedding 已经位于主机内存中,因此按某个 obs 列或聚类标签给 UMAP 着色不需要额外传输。只有当图形需要从 .X 本身读取数值时,才需要 rsc.get.anndata_to_CPU,例如按某个基因的表达量着色,或绘制标记基因的 dotplot。序列化也是如此:write_h5ad 和 write_zarr 期望矩阵位于主机端,因此写入前应调用 rsc.get.anndata_to_CPU(adata);否则要么报错,要么保存出一个由 CuPy 支持的 .X,在只有 CPU 的机器上无法重新加载。
sc.pl.umap(adata, color=["leiden"], legend_loc="on data")
7.6. GPU 最佳实践#
上面的流程在小数据集上几秒钟即可跑完。一旦扩大规模,下面这些习惯,正是区分高效工作流与频繁拖垮 GPU 的工作流的关键。
7.6.1. 尽量减少主机↔设备之间的传输#
每个 anndata_to_GPU / anndata_to_CPU 是一次 PCIe 传输。一个 50 GB 的稀疏矩阵需要几秒钟才能移动,很容易就主导了那些本来很快的预处理步骤。
只移动一次,把所有 GPU 工作集中在一段连续的代码块中完成,然后再移动回来一次。避免在流程中间调用仅支持 CPU 的函数,从而强制触发一次隐式的数据迁移——如果必须混用,就把 CPU 端的工作批量处理。
7.6.2. 显式释放中间结果#
Python 的垃圾回收器并不会像你期望的那样积极地把 CuPy 内存归还给内存池。在一个内存开销较大的步骤之后(rsc.pp.scale 作用于稠密数据、 rsc.pp.regress_out、在大矩阵上做 PCA 等之后),请丢弃你不再需要的层引用,并让 CuPy 把已释放的内存池块归还给驱动。
import gc
# Drop references to large intermediates first.
if "counts" in adata.layers:
del adata.layers["counts"]
gc.collect()
# Then return any unused pool blocks to the driver.
cp.get_default_memory_pool().free_all_blocks()
cp.get_default_pinned_memory_pool().free_all_blocks()
7.6.3. 监视 VRAM 使用情况#
如果看不到设备上的情况,就很难为内存池设定合适的大小或诊断 OOM。有三种有用的查看方式:
# CuPy: what your process has allocated through the active pool.
pool = cp.get_default_memory_pool()
print(f"CuPy pool used: {pool.used_bytes() / 1e9:.2f} GB")
print(f"CuPy pool total: {pool.total_bytes() / 1e9:.2f} GB")
# CUDA: what the device reports as free vs total (across all processes).
free, total = cp.cuda.runtime.memGetInfo()
print(f"Device free: {free / 1e9:.2f} GB / {total / 1e9:.2f} GB")
CuPy pool used: 0.00 GB
CuPy pool total: 0.00 GB
Device free: 93.73 GB / 101.97 GB
从一个单独的终端 nvidia-smi --query-gpu=memory.used,memory.free,utilization.gpu --format=csv -l 1 会按进程、每秒给你一次反馈——在调试一个跑了三步后突然 OOM 的流程时,这是不可或缺的。
7.6.4. 留意 dtype(数据类型)#
来自 .h5ad 的 AnnData 对象通常是 float64。在 GPU 上, float64 会使你的内存占用翻倍,并且在消费级显卡上明显更慢(相比数据中心显卡,其 FP64 吞吐量受限)。
除非有特定的数值精度理由,否则请在移动到 GPU 之前先转换为 float32:
adata.X = adata.X.astype("float32")
rsc.get.anndata_to_GPU(adata)
即便输入是 float32,RSC 在内部也大多以 float64 进行归约计算,以在方差、PCA 等计算中保持数值稳定性。因此,你既能节省内存,又不会遭遇最坏情况下的精度损失。
7.6.5. 为非确定性做好准备#
GPU 的归约默认是非确定性的,因为浮点求和的顺序取决于线程调度。逐元素的差异极小,但可能让接近临界值的 HVG 排名发生翻转,或把某个细胞移到聚类边界的另一侧。如果精确的可复现性很重要,请设置 cp.random.seed(...) 并限制为单个 CUDA 流;但要接受这样一个事实:跨硬件得到逐比特一致的结果是不现实的。
7.6.6. 使 NVIDIA 驱动程序与 CUDA 运行时相匹配#
RSC 的 wheel 包固定了特定的 CUDA 运行时版本。你的 NVIDIA 驱动至少要和该运行时一样新;过旧的驱动会在加载 CuPy 时失败,并给出有误导性的错误。请更新驱动,而不是工具包——wheel 自带其工具包。
7.6.7. 了解哪些功能尚未移植#
严重依赖 R 包的工具(SoupX、scDblFinder、scran)以及那些并非瓶颈的工具(大多数绘图)都运行在 CPU 上。预期的模式是:在 GPU 上完成瓶颈步骤,其余则回到 CPU 处理。
7.7. 使用 dask-cuda 进行核外分析#
当数据集即使用托管内存也无法放入单块 GPU 时,RSC 会与 dask-cuda 整合,让矩阵分块流经一块或多块 GPU [Rocklin, 2015]。AnnData 的惰性读取器(anndata ≥0.12 中为 read_elem_lazy,此前为 read_elem_as_dask)会直接从 Zarr 或 HDF5 存储把 .X 加载为 Dask 数组,而不必在主机 RAM 中完整实例化。
流程很短,但每一步都很关键。
from dask.distributed import Client
from dask_cuda import LocalCUDACluster
cluster = LocalCUDACluster(
CUDA_VISIBLE_DEVICES="0",
threads_per_worker=8,
protocol="ucx", # zero-copy GPU↔GPU comms over NVLink/InfiniBand if available
rmm_pool_size="20GB",
rmm_maximum_pool_size="40GB",
rmm_allocator_external_lib_list="cupy",
)
client = Client(cluster)
from pathlib import Path
import zarr
from anndata.experimental import read_elem_lazy
# The lazy reader operates on a zarr store, so convert the cached h5ad once.
# In a real workflow you'd already have a zarr atlas on disk or in object storage.
zarr_path = Path(path).with_suffix(".zarr")
if not zarr_path.exists():
ad.read_h5ad(path).write_zarr(zarr_path)
store = zarr.open(zarr_path)
X_lazy = read_elem_lazy(store["X"], chunks=(20_000, store["X"].attrs["shape"][1]))
adata = ad.AnnData(
X=X_lazy,
obs=ad.io.read_elem(store["obs"]),
var=ad.io.read_elem(store["var"]),
)
rsc.get.anndata_to_GPU(adata)
第一次使用 Dask 路径时,最容易踩到下面几个实践细节:
大多数操作都是惰性的,但也有例外。 过滤和 log1p 会保持惰性,只是把任务排入队列。normalize_total 只有在显式传入 target_sum= 时才是惰性的;默认 target_sum=None 时,它必须计算每个细胞的总和以找出文库大小的中位数,因此会触发同步。calculate_qc_metrics、highly_variable_genes、scale 和 pca 都需要对整个矩阵做完整归约,也会触发同步;可以把它们视为 Dask 流程中的自然断点。
用布尔掩码过滤,而不要用 sc.pp.filter_cells.
内置过滤辅助函数会物化 Count 来决定保留哪些细胞,从而破坏惰性执行。应当从 QC 指标中一次性算好掩码,并直接用 .copy() 应用它们,以保持 CSR 格式而非视图:
adata = adata[adata.obs["n_genes_by_counts"].between(200, 10_000)].copy()
分块大小是你必须调节的一个旋钮。 分块太小,会让调度器被海量任务淹没;分块太大,又会在同步时撑爆 VRAM。对于 24–80 GB 的 GPU,每块 20–50k 个细胞是一个合理的起点;如果某一步比预期慢,可用 Dask 仪表板进行性能分析。
7.8. 超越预处理:在 GPU 上使用 decoupler、Squidpy 和 pertpy#
RSC 还封装了来自另外三个 scverse 包的部分例程的 GPU 实现,每个都在各自的命名空间下:
rsc.dcg.*— decoupler 功能分析(ulm、mlm、aucell、waggr、zscore)。在百万细胞图谱上,转录因子活性推断可从 CPU 上的数小时缩短到单块 GPU 上的几分钟。除命名空间不同外,CPU 与 GPU API 完全相同;参见通路和基因集分析章节。rsc.gr.*— Squidpy 空间分析(spatial_autocorr、co_occurrence、ligrec)。Moran / Geary 空间自相关以及配体–受体置换检验,是最能从 GPU 获益的例程;参见空间邻域章节了解这些方法的概念。邻域图本身仍然使用sq.gr.spatial_neighbors在 CPU 上构建。rsc.ptg.*— pertpy 扰动距离,通过Distance类实现。目前只移植了edistance,更多指标也即将推出。该 GPU 实现会即时重新计算距离,而不是实例化一个 n×n 的细胞距离矩阵,因此在涉及数百种扰动的筛选中,自助法(bootstrap)方差估计变得切实可行;参见扰动建模章节。
import decoupler as dc
net = dc.op.collectri(organism="human", license="academic")
rsc.dcg.ulm(adata, net=net, raw=False, bsize=10_000)
import squidpy as sq
sq.gr.spatial_neighbors(adata, coord_type="generic") # still CPU
rsc.gr.spatial_autocorr(adata, mode="moran")
distance = rsc.ptg.Distance(metric="edistance", obsm_key="X_pca")
df = distance.pairwise(adata, groupby="perturbation")
并非整个上游 API 都被移植——RSC 专注于那些既足够耗时、值得移植到 GPU,又足够“易并行”(embarrassingly parallel)、能保持数值忠实的例程。当前的覆盖范围请查阅 RSC 的 API 参考文档。
另见
rapids-singlecell 文档 ——API 参考与教程,包括 100 万细胞及核外(out-of-core)演示。
RAPIDS 内存管理器 ——更深入地介绍池分配器、托管分配器和竞技场(arena)分配器的背景。
dask-cuda ——多 GPU 与核外配置。
cuML 用户指南 ——为 PCA、UMAP、t-SNE 以及逻辑回归标记物检验提供支持的 GPU 机器学习库。
NVIDIA 单细胞分析博客文章 ——与 CPU 版 scanpy 的参照基准测试。
7.9. Quiz#
你有一个 80 GB 的 AnnData 对象和一块 40 GB 的 GPU。哪种 RMM 配置是合适的?
在基于 Dask 的 RSC 流程中,以下哪个步骤会触发同步(即不是惰性执行)?
7.10. 参考文献#
Pau Badia-i-Mompel, Jesús Vélez Santiago, Jana Braunger, Celina Geiss, Daniel Dimitrov, Sophia Müller-Dott, Petr Taus, Aurelien Dugourd, Christian H. Holland, Ricardo O. Ramirez Flores, and others. Decoupler: ensemble of computational methods to infer biological activities from omics data. Bioinformatics Advances, 2(1):vbac016, 2022.
Severin Dicks, Lukas Heumos, Lilly May, Sara Jimenez, Philipp Angerer, Ilan Gold, Isaac Virshup, Felix Fischer, Michelle Gill, Melanie Boerries, Corey J. Nolet, Tiffany J. Chen, and Fabian J. Theis. Gpu-accelerated single-cell analysis at scale with rapids-singlecell. arXiv preprint arXiv:2603.02402, 2026. URL: https://arxiv.org/abs/2603.02402, doi:10.48550/arXiv.2603.02402.
Lukas Heumos, Yuge Ji, Lilly May, Tessa D. Green, Stefan Peidli, Xinyue Zhang, Xichen Wu, Johannes Ostner, Antonia Schumacher, Karin Hrovatin, Michaela Müller, Faye Chong, Gregor Sturm, Alejandro Tejada, Emma Dann, Mingze Dong, Gonçalo Pinto, Mojtaba Bahrami, Ilan Gold, Sergei Rybakov, Altana Namsaraeva, Amir Ali Moinfar, Zihe Zheng, Eljas Roellin, Isra Mekki, Chris Sander, Mohammad Lotfollahi, Herbert B. Schiller, and Fabian J. Theis. Pertpy: an end-to-end framework for perturbation analysis. Nature Methods, 2025. URL: https://doi.org/10.1038/s41592-025-02909-7, doi:10.1038/s41592-025-02909-7.
Corey J. Nolet, Divye Gala, Alex Fender, Joe Eaton-Patrick Patro, Edward Raff, John Zedlewski, Brad Rees, and Tim Riedel. Cuml: a library for gpu accelerated machine learning. Software available from rapids.ai, 2024. URL: rapidsai/cuml.
Giovanni Palla, Hannah Spitzer, Michal Klein, David Fischer, Anna Christina Schaar, Louis Benedikt Kuemmerle, Sergei Rybakov, Ignacio L. Ibarra, Olle Holmberg, Isaac Virshup, Mohammad Lotfollahi, Sabrina Richter, and Fabian J. Theis. Squidpy: a scalable framework for spatial omics analysis. Nature Methods, 19(2):171–178, 2022. URL: https://doi.org/10.1038/s41592-021-01358-2, doi:10.1038/s41592-021-01358-2.
Matthew Rocklin. Dask: parallel computation with blocked algorithms and task scheduling. Proceedings of the 14th Python in Science Conference, pages 126–132, 2015. URL: https://doi.org/10.25080/Majora-7b98e3ed-013, doi:10.25080/Majora-7b98e3ed-013.
NVIDIA RAPIDS. RMM: RAPIDS memory manager. rapidsai/rmm, 2024. URL: rapidsai/rmm.
7.11. 贡献者#
我们衷心感谢以下人员的贡献:
7.11.2. 审阅者#
Severin Dicks