均匀流形逼近与投影(UMAP)是一种广泛应用于可视化和特征提取的降维技术,涵盖探索性数据分析、主题建模和单细胞分析等多种应用场景。这些工作流程通常具有迭代性和探索性,需要用户在分析数据或调整参数时反复运行UMAP。随着数据集规模不断扩大,每次UMAP运算的成本大幅上升,使得交互式探索和迭代分析愈发困难。
UMAP算法的关键步骤是在数据集上构建全邻域kNN图,即为数据集中的每个向量找到k个最近邻。当数据集规模扩展至数千万乃至数亿个向量时,全邻域图的构建代价会急剧攀升。
此前发布的文章《借助NVIDIA cuML在GPU上实现更快、更可扩展的UMAP》介绍了如何通过核外计算方式扩展UMAP,使其能够处理单个GPU内存无法容纳的超大数据集。但训练阶段仍受限于单GPU,仅transform()步骤能够使用多个GPU。
本文介绍的是NVIDIA cuML和NVIDIA cuVS 25.06版本中发布的新特性——通过将耗费资源的全邻域图构建步骤分发到多个GPU上来突破这一限制,从而显著提升可扩展性并降低整体训练时间。
本文还将介绍如何利用cuML多GPU UMAP加速数千万乃至数亿个向量规模的数据集处理工作流程。在实践中,这一方法在超大规模数据集上能带来显著的端到端加速效果,可将数百GB数据的UMAP处理时间从数小时乃至数天压缩至数分钟。
实现核外扩展的核心思路
实现UMAP核外扩展的核心思路,是在不要求整个数据集同时加载进GPU内存的情况下完成全邻域kNN图的构建,这一点已在前文中有所介绍。具体做法是将数据集划分为均衡的簇,并在相邻簇之间进行向量重叠,以保留跨簇边界的最近邻关系。
每个簇的局部kNN图独立计算,再将这些局部图合并为一张全局全邻域图。这使UMAP得以在超出GPU内存容量的数据集上运行,同时保持嵌入质量。
自然延伸至多GPU场景
这一技术本身已将问题分解为相互独立的计算单元,因此可以自然地扩展至多GPU场景。每个簇可独立处理,局部kNN图的计算无需访问完整数据集,也无需与其他簇协调。因此,各簇可分发至不同GPU,每个GPU从CPU内存中独立获取所分配簇的数据。
每个GPU独立计算局部全邻域kNN图,并将结果合并至全局kNN图。这种独立计算局部kNN图的方式,避免了分布式全邻域kNN计算中通常存在的昂贵全量通信开销,在超大规模数据集上可带来显著的端到端性能提升。
关键超参数说明
在使用该特性之前,需要了解两个提供空间、时间与质量权衡的超参数。
cuML多GPU UMAP沿用单GPU UMAP实现的相同步骤,新增以下两个超参数:
knn_n_clusters:数据将被划分的簇数量。
knn_overlap_factor:每个数据点被分配到的最近邻簇的总数量。
knn_n_clusters所划分的簇大致均衡,即向量在各GPU上近似均匀分布以供处理。增大该值会减少每个簇中分配的向量数,从而降低每个GPU内存需要容纳的数据量。更多关于均衡k-means实现的详情,请参阅论文《GPU上的超大规模核外UMAP》。
knn_overlap_factor用于增大跨簇的向量重叠,从而保留更多跨簇边界的真实最近邻。增大该参数通常可以提升全邻域kNN图的质量,进而改善最终UMAP嵌入的质量。质量提升的代价是计算时间和内存占用的增加,因为每个簇需要处理更多向量。
knn_overlap_factor与knn_n_clusters共同提供了空间、时间与质量之间可控的权衡机制。
分布式全邻域图构建功能直接通过cuVS全邻域API暴露这些参数。cuML UMAP在内部使用这些参数,但对于需要独立构建全邻域图的应用程序,也可直接调用该API。
使用多GPU的cuVS全邻域API Python示例(示例1):
from cuvs.neighbors import all_neighbors
from cuvs.common import MultiGpuResources
params = all_neighbors.AllNeighborsParams(
algo="nn_descent",
n_clusters=32,
overlap_factor=2
)
# 使用系统上的所有GPU
res = MultiGpuResources()
indices, distances = all_neighbors.build(
data,
k,
params,
distances=cupy.empty((n_rows, k))
resources=res
)
其中n_clusters和overlap_factor对应cuML UMAP暴露的knn_n_clusters和knn_overlap_factor参数,cuML UMAP在构建图时会将这些参数转发至cuVS全邻域API。
参数调优建议
高质量嵌入的起点推荐使用knn_overlap_factor=2。实践中的建议如下:
较小规模的数据集适合以小幅递增方式调整knn_overlap_factor(2→3→4)。
knn_n_clusters较大(>100)的大规模数据集可能更适合以较大幅度递增(2→4→6)。
调整knn_overlap_factor时需谨慎,虽然更高的值会改善kNN召回率,但也会显著增加每个簇的计算时间。
若要在提升嵌入质量的同时控制内存开销,可在增大knn_overlap_factor的同时相应增大knn_n_clusters,从而使内存用量基本保持不变。
例如,将重叠因子翻倍会使每个簇的预期向量数翻倍,因此同时将簇数量翻倍即可维持每个簇的向量数不变。建议选取足够的knn_n_clusters,使每个局部图及其簇内向量能够较为轻松地放入GPU内存中。
内存估算方法
每个GPU所需的内存取决于数据集及待构建的局部kNN图。对于含N个D维向量的数据集,主要内存占用如下:
数据内存:M_data = N × (knn_overlap_factor / knn_n_clusters) × D × sizeof(data)
图内存:M_graph = N × k × (knn_overlap_factor / knn_n_clusters) × (sizeof(index) + sizeof(distance))
实际使用中,图构建过程中还需要额外的工作空间等开销,因此实际峰值内存可能高于上述估算值。建议选取knn_overlap_factor和knn_n_clusters,使预估内存占用明显低于GPU可用内存。
以使用80GB内存的GPU、N=1亿、D=1024的float32向量(共409GB)数据集为例,可选knn_overlap_factor=2、knn_n_clusters=24,此时每个簇的数据大小M_data约为34GB。使用int64索引类型(8字节)、float32距离类型(4字节)且k=15时,M_graph约为1.5GB,合计约35.5GB,为簇不均衡及kNN图构建过程中的其他开销留有足够余量。
内存占用与knn_overlap_factor成正比(决定每个向量的复制份数),与knn_n_clusters成反比。
cuML多GPU UMAP使用示例
在NVIDIA cuML中使用多GPU UMAP,只需在原有cuML UMAP配置的基础上增加少量参数。
以下Python示例(示例2)中,上方代码将使用所有可用GPU构建UMAP嵌入,下方代码仅使用ID为0、4、5的GPU:
from cuml.manifold import UMAP
# 使用系统上的所有GPU
umap = UMAP(
build_kwds={
"knn_n_clusters": 32,
"knn_overlap_factor": 2,
},
device_ids="all",
)
embedding = umap.fit_transform(data)
# 使用部分GPU
umap = UMAP(
build_kwds={
"knn_n_clusters": 32,
"knn_overlap_factor": 2,
},
device_ids=[0, 4, 5],
)
embedding = umap.fit_transform(data)
其中device_ids参数控制参与计算的GPU,knn_n_clusters和knn_overlap_factor控制全邻域kNN图的构建方式。
通常,增加GPU数量可通过将全邻域构建分散到多个设备上来缩短运行时间。
示例3展示了如何在cuML UMAP中使用示例1中由cuVS生成的预计算全邻域图:
from cuml.manifold.umap import UMAP as cuUMAP
# 使用示例1中由cuVS全邻域API计算的indices和distances
gpu_umap = cuUMAP(
precomputed_knn=(indices, distances),
)
gpu_embedding = gpu_umap.fit_transform(data)
这展示了通过precomputed_knn参数向UMAP提供预计算全邻域图的便捷性,CPU版UMAP同样支持该参数。
嵌入质量对比
图1对比了在106M×2048维的MIRACL数据集上,使用CPU参考实现(配合预计算GPU全邻域图)与cuML原生GPU UMAP实现生成的嵌入结果。由于在此规模下CPU计算全邻域图已不可行,采用了预计算方式。
值得注意的是,UMAP对缩放、平移和旋转具有不变性,因此尽管图像外观略有差异,这些嵌入结果在功能上是等价的。生成的嵌入展示出相近的全局结构,表明GPU全邻域图在超大规模下仍能保留产生高质量可视化所需的邻域关系。
性能基准测试
我们在超过单GPU内存容量的数据集(Wiki和MIRACL)上评估了多GPU UMAP的性能影响。所有基准测试均在搭载8块NVIDIA H100 GPU的NVIDIA DGX系统上执行,CPU为Intel Xeon 8480CL 224核处理器,内存为2TiB。
可信度分数(Trustworthiness Score)是衡量嵌入质量的常用指标,取值范围为0到1(越高越好),用于衡量UMAP低维嵌入空间中局部邻域结构相较于原始高维空间的保留程度。
图2a(上)展示了广泛使用的CPU参考实现在Wiki和MIRACL数据集逐渐增大的子集上的运行时间扩展行为。随着数据集规模增大,内存和计算成本导致运行时间急剧上升。在完整规模下,即使在2TiB内存的系统上,CPU实现也因内存耗尽而无法完成。
为估算此规模下的运行时间,通过对较小子集的实测扩展趋势进行外推来预测完整规模的CPU运行时间。详细方法见论文《GPU上的超大规模核外UMAP》。
图2b(下)将预估的CPU运行时间与在8块NVIDIA H100 GPU上运行的cuML多GPU UMAP实际运行时间进行对比。在1.06亿向量的MIRACL数据集上,cuML相较于预估CPU运行时间实现了最高74倍的端到端加速。
更重要的是,这使UMAP在以往无法处理的规模上变得切实可行——对1.06亿向量数据集的端到端处理仅需8分钟即可完成。
多GPU强扩展性
图3展示了cuML多GPU UMAP在Wiki和MIRACL数据集上,GPU数量从1块增加到8块H100 GPU时的扩展行为。在性能提升的同时,不同GPU配置下的嵌入质量保持相当。
关于多GPU全邻域UMAP实现的更多细节及更严格的评估,请参阅论文《GPU上的超大规模核外UMAP》。
总结
NVIDIA cuVS现已提供多GPU全邻域图构建能力,为NVIDIA cuML中的UMAP训练解锁了前所未有的规模与性能。这些能力使主题建模、单细胞分析等大规模可视化和嵌入工作流程更加实用,实现了更快速的迭代与探索。
如需开始使用cuML和cuVS中的多GPU UMAP,请参阅RAPIDS安装指南。有关NVIDIA cuVS中全邻域API的更多信息,请参阅cuVS API指南。
致谢:感谢Manas Singh和Mike Grauer对本文撰写与审阅的宝贵贡献,以及Leland McInnes创造并开源UMAP算法并持续支持GPU加速工作所做的贡献。
Q&A
Q1:cuML多GPU UMAP的knn_overlap_factor参数该怎么设置?
A:推荐从knn_overlap_factor=2开始。中等规模数据集可小幅递增(2→3→4);对于knn_n_clusters超过100的大规模数据集,可以较大幅度递增(2→4→6)。提高该参数能改善kNN召回率和嵌入质量,但会增加计算时间和内存占用。若要在提升质量的同时控制内存,可同步增大knn_n_clusters来平衡内存使用。
Q2:cuML多GPU UMAP在MIRACL数据集上的性能表现如何?
A:在含1.06亿个向量的MIRACL数据集上,使用8块NVIDIA H100 GPU的cuML多GPU UMAP相较于CPU参考实现实现了最高74倍的端到端加速,完整数据集的端到端处理仅需约8分钟。而CPU参考实现在同等规模下因内存耗尽(即使在2TiB内存系统上)无法完成,是通过对小规模子集的扩展趋势外推估算的。
Q3:cuML多GPU UMAP如何在代码中指定使用哪些GPU?
A:在创建UMAP对象时,通过device_ids参数指定。设置device_ids="all"可使用系统上所有可用GPU;也可传入具体的GPU ID列表,例如device_ids=[0, 4, 5],只使用指定的GPU参与计算。同时通过build_kwds字典传入knn_n_clusters和knn_overlap_factor参数来控制全邻域kNN图的构建方式。
