文献分享: BANG——DiskANN在单GPU上的实现与优化

文章目录

原论文: BANG: Billion-Scale Approximate Nearest Neighbor Search using a Single GPU


PART   \textbf{PART } PART Ⅰ: 导论与预备知识

1.   \textbf{1. } 1. 导论

1.1.   \textbf{1.1. } 1.1. 关于 ANN \textbf{ANN} ANN

1️⃣高维 k k k最邻近查询

  1. 精确查询 (NN) \text{(NN)} (NN)
    • 含义:找到与给定查询点最近的 k k k个数据点
    • 困难:由于维度诅咒 → \to 难以摆脱暴力扫描 O ( n ∗ dimension ) O(n*\text{dimension}) O(ndimension)的复杂度
  2. 近似查询 (ANN) \text{(ANN)} (ANN)
    • 核心:通过牺牲准确性来换取速度,以减轻维度诅咒
    • On GPU \text{On GPU} On GPU:大规模并行处理可以提高 ANN \text{ANN} ANN吞吐量(固定时间内的查询数量)
  3. 基于图的 ANN \text{ANN} ANN
    • 处理大规模数据最为高效的 ANN \text{ANN} ANN方法
    • Vamana/DiskANN \text{Vamana/DiskANN} Vamana/DiskANN是目前最先进的基于图的 ANN \text{ANN} ANN(详细的设计 Click Here \text{Click Here} Click Here)

1.2.   \textbf{1.2. } 1.2.  ANN \textbf{ANN} ANN GPU \textbf{GPU} GPU实现难点

1️⃣ GPU \text{GPU} GPU的内存有限

  1. 含义:目前主流 GPU \text{GPU} GPU内存有限,无法将构建好的图结构完整载入
  2. 现有方案:
    方案 描述 缺陷 文献
    分片 将图分片 → \to 不断在 CPU ⇆ GPU \text{CPU}\leftrightarrows{}\text{GPU} CPUGPU交换片以处理整个图 PCIe \text{PCIe} PCIe带宽不够 GGNN \text{GGNN} GGNN
    GPU \text{GPU} GPU 将图有效分割到所有 GPU \text{GPU} GPU上以容纳并处理整个图 硬件成本高 SONG \text{SONG} SONG/ FAISS \text{FAISS} FAISS
    压缩 压缩图数据维度使图结构能北方进 GPU \text{GPU} GPU内存 召回率下降(只适合小数据) GGNN \text{GGNN} GGNN

2️⃣最有硬件使用

  1. GPU ⇆ CPU \text{GPU}\leftrightarrows{}\text{CPU} GPUCPU负载平衡:确保二者持续并行工作不空闲,并且数据传输量不超过 PCIe \text{PCIe} PCIe极限
  2. 主存占用:基于 GPU \text{GPU} GPU ANN \text{ANN} ANN搜索占用的内存显著增加

1.3.   BANG \textbf{1.3. BANG} 1.3. BANG的总体优化思路

1️⃣硬件优化

  1. 总线优化:减少 CPU-GPU \text{CPU-GPU} CPU-GPU PCIe \text{PCIe} PCIe的通信量 → \to 提高吞吐
    优化思路 具体措施
    减少(总共的)总线传输次数 负载平衡,预取/流水线(让 CPU/GPU \text{CPU/GPU} CPU/GPU尽量没空闲时间)
    降低(一次的)总线传输量 传输 PQ \text{PQ} PQ压缩后的向量(而非原始向量)
  2. GPU \text{GPU} GPU内存优化:避免存放图结构 + + +只存放 PQ \text{PQ} PQ压缩后的向量

2️⃣计算优化

  1. 加速遍历/搜索:使用 Bloom \text{Bloom} Bloom过滤器,快速判断 a ∈ A a\text{∈}A aA式命题的真伪
  2. 加速距离计算:使用 PQ \text{PQ} PQ压缩后的向量计算距离

3️⃣软件优化:设立微内核,将距离计算/排序/更新表操作拆分成更原子化的操作,以提高并行化

2.   GPU \textbf{2. GPU} 2. GPU架构与 CUDA \textbf{CUDA} CUDA编程模型

2.1.   GPU \textbf{2.1. }\textbf{GPU} 2.1. GPU体系结构

1️⃣计算单元组织架构

image-20241030201057518
结构 功能
CUDA \text{CUDA} CUDA核心 类似 ALU \text{ALU} ALU(但远没 CPU \text{CPU} CPU的灵活),可执行浮点运算/张量运算/光线追踪(高级核心)
Warp \text{Warp} Warp 多核心共用一个取指/译码器,按 SIMT \text{SIMT} SIMT工作(所有线程指令相同/数据可不同)
SM \text{SM} SM 包含多组 Warps \text{Warps} Warps,所有 CUDA \text{CUDA} CUDA核心共用一套执行上下文(缓存) & \& &共享内存

2️⃣存储层次架构:

image-20241031001314018
  1. 不同 SM \text{SM} SM能够 Access \text{Access} Access相同的 L2 Cache \text{L2 Cache} L2 Cache
  2. 显存与缓存之间的带宽极高,但是相比 GPU \text{GPU} GPU的运算能力仍然有瓶颈

2.2.   \textbf{2.2. } 2.2.  CUDA \textbf{CUDA} CUDA编程模型

1️⃣ CUDA \text{CUDA} CUDA程序简述

  1. CUDA \text{CUDA} CUDA程序的两部分
    程序 运行位置 主要职责
    Host程序 CPU \text{CPU} CPU 任务管理/数据传输/启动 GPU \text{GPU} GPU内核
    Device程序 GPU \text{GPU} GPU 执行内核/处理数据
  2. Kernel \text{Kernel} Kernel即在 GPU \text{GPU} GPU上运行的函数,如下简单内核定义示例
    //通过__global__关键字声名内核函数
    __global__ void VecAdd(float* A, float* B, float* C)
    {
       int i = threadIdx.x;
       C[i] = A[i] + B[i];
    }
    int main()
    {
       //通过<<<...>>>中参数指定执行kernel的CUDA thread数量
       VecAdd<<<1, N>>>(A, B, C); 
    }
    

2️⃣线程并行执行架构

  1. 线程层次:
    结构 地位 功能
    Thread \text{Thread} Thread 并行执行最小单元 执行 Kernel \text{Kernel} Kernel的一段代码
    Warp(32Threads) \text{Warp(32Threads)} Warp(32Threads) 线程调度的基本单位 所有线程以 SIMD \text{SIMD} SIMD方式执行相同指令
    Block \text{Block} Block GPU \text{GPU} GPU执行线程基本单位 使块内线程内存共享/指令同步
    Grid \text{Grid} Grid 并行执行的最大单元 执行整个内核(启动内核时必启动整个 Grid \text{Grid} Grid)
  2. 线程在计算单元的映射:线程层次 ↔ 层次对应 GPU \xleftrightarrow{层次对应}\text{GPU} 层次对应 GPU物理架构
    image-20241030230857521
    • 注意 SM \text{SM} SM Block \text{Block} Block不必 1v1 \text{1v1} 1v1对应也可 Nv1 \text{Nv1} Nv1对应
  3. 线程在存储单元的映射
    线程结构 Access \textbf{Access} Access的内存结构 访问速度
    Thread \text{Thread} Thread 每线程唯一的 Local Memory \text{Local Memory} Local Memory 极快
    Block \text{Block} Block 每块唯一的 Shared Memory \text{Shared Memory} Shared Memory(块中每个线程都可访问) 较快
    所有线程 唯一且共享的 Global Memory \text{Global Memory} Global Memory 较慢

2.3.   CPU \textbf{2.3. CPU} 2.3. CPU GPU \textbf{GPU} GPU

image-20241030175627888

1️⃣ CPU/GPU \text{CPU/}\text{GPU} CPU/GPU结构对比

GPU \text{GPU} GPU CPU \text{CPU} CPU
ALU \text{ALU} ALU 功能强但数量少(只占 GPU \text{GPU} GPU小部),时钟频率极高 功能弱但数量大,时钟频率低
Cache \text{Cache} Cache 容量大并分级,缓存后续访问数据 容量很小,用于提高线程服务
控制 复杂串行逻辑,如流水/分支预测/乱序执行 简单(但大规模)并行逻辑

3️⃣ CPU ↔ 数据 / 指令传输 PCIe GPU \text{CPU} \xleftrightarrow[数据/指令传输]{\text{PCIe}} \text{GPU} CPUPCIe 数据/指令传输GPU交互

设备 逻辑地位 IO \textbf{IO} IO模块 任务分配
GPU \text{GPU} GPU 外设 IO Block \text{IO Block} IO Block(南桥) 控制逻辑和任务调度
CPU \text{CPU} CPU 主机 Copy Engine \text{Copy Engine} Copy Engine 执行大量并行计算任务

PART   \textbf{PART } PART Ⅱ: BANG \textbf{BANG} BANG的设计

1.   BANG \textbf{1. BANG} 1. BANG的总体设计

1.1.   BANG \textbf{1.1. BANG} 1.1. BANG的索引架构

1.1.1.   \textbf{1.1.1. } 1.1.1.  BANG \textbf{BANG} BANG索引(硬件)布局

结构 功能
RAM \text{RAM} RAM 存放 Vamana \text{Vamana} Vamana算法构建的图结构 + + +数据点
GPU \text{GPU} GPU内存 存放 Vamana \text{Vamana} Vamana算法构建的图中点经过 PQ \text{PQ} PQ压缩后的向量
CPU-GPU \text{CPU-GPU} CPU-GPU总线 传输压缩向量 & \& &协调并行

1.1.2.   BANG \textbf{1.1.2. BANG} 1.1.2. BANG索引构建算法: Vamana \textbf{Vamana} Vamana

1️⃣ Vamana \text{Vamana} Vamana图构建基本操作

  1. 图查询算法:贪心搜索 GreedySearch ( s , x q , k , L ) \text{GreedySearch} \left(s, \mathrm{x}_q, k, L\right) GreedySearch(s,xq,k,L)
    image-20241019000253117
  2. 图剪枝算法:健壮性剪枝 RobustPrune ( p , R , α , L ) \text{RobustPrune}(p, R, \alpha, L) RobustPrune(p,R,α,L)
    图片4

2️⃣ Vamana \text{Vamana} Vamana图构建总体流程

图片6

1.1.3.   BANG \textbf{1.1.3. BANG} 1.1.3. BANG索引构建方法: 类似 DiskANN \textbf{DiskANN} DiskANN架构

1️⃣构建步骤:面向面向内存空间的优化

  1. 划分:用 k -means k\text{-means} k-means P P P分为多簇(每簇有一中心),再将 P P P所有点分给 ℓ > 1 \ell\text{>}1 >1个中心以构成重叠簇
  2. 索引:在每个重叠簇中执行 Vamana \text{Vamana} Vamana算法,构建相应有向边
  3. 合并:将所有构建的有向边合并在一个图中,完成构建

2️⃣关于重叠分簇:为了保证图的连通性,以及后续搜索的 Navigable \text{Navigable} Navigable

1.2.   BANG \textbf{1.2. BANG} 1.2. BANG的查询架构

1.2.1.   \textbf{1.2.1. } 1.2.1. 第一阶段: 初始化 &PQ \textbf{\&PQ} &PQ表的构建

1️⃣执行的操作

  1. 并行化:为查询集 Q ρ Q_\rho Qρ中的每个查询 { q 1 , q 2 , . . . , q ρ } \{q_1,q_2,...,q_{\rho}\} { q1,q2,...,qρ}分配一个独立的 CUDA \text{CUDA} CUDA线程 Block \text{Block} Block
  2. 距离表:在每个线程块上为每个 q i q_i qi计算并构建 PQ \text{PQ} PQ距离子表,最终合并 ρ \rho ρ个子表为距离表
  3. 搜索起点:每个 q i q_i qi从图质心开始,即 CPU ← 传输给 WorkList ← 放入 u i ∗ (当前/候选点) ← 初始化 Centroid \text{CPU}\xleftarrow{传输给}\text{WorkList}\xleftarrow{放入}\textbf{u}_i^*\textbf{(当前/候选点)}\xleftarrow{初始化}\text{Centroid} CPU传输给 WorkList放入 ui(当前/候选点)初始化 Centroid

2️⃣ PQ \text{PQ} PQ表构建的时序逻辑

时期 操作
查询开始前 将查询点送入 GPU \text{GPU} GPU Copy \text{Copy} Copy引擎,在 CUDA \text{CUDA} CUDA核心上计算/构建/存储距离表
查询开始后 保留距离表在 GPU \text{GPU} GPU上直到查询结束

1.2.2.   \textbf{1.2.2. } 1.2.2. 第二阶段: 并行 GreedySearch \textbf{GreedySearch} GreedySearch主循环

图片xcxcxcxcxxx3

1️⃣前 CPU \text{CPU} CPU阶段: CPU \text{CPU} CPU从内存中获取当前在处理节点 u i ∗ u_i^* ui的邻居集 N i N_i Ni

🔁数据传输: CPU → 邻居集 N i GPU \text{CPU}\xrightarrow{邻居集N_i}\text{GPU} CPU邻居集Ni GPU

2️⃣中 GPU \text{GPU} GPU阶段:接收 u i ∗ u_i^* ui的邻居集 N i N_i Ni后,并行地执行内核 & \text{\&} &全精度向量的异步传输

  1. 执行内核:按顺序执行以下内核及操作
    步骤 操作 内核与否
    过滤邻居 Bloom \text{Bloom} Bloom并行检查 ∀ n ∈ N i \forall{}n\text{∈}N_i nNi中未被访问点 → \to 并放入 N i ′ {}N_i' Ni(未访问集) + \text{+} +更新 Bloom \text{Bloom} Bloom ✔️
    距离计算 PQ \text{PQ} PQ距离表并行计算所有未处理邻居 n k ∈ N i ′ {}n_k\text{∈}N_i' nkNi与查询点 q i q_i qi距离,并存在 D i [ k ] \mathcal{D}_i[k] Di[k] ✔️
    邻居排序 N i ′ {}N_i' Ni D i [ k ] \mathcal{D}_i[k] Di[k]按与 q i q_i qi的距离执行归并排序,得到排序后的距离 D i ′ \mathcal{D}_i' Di和节点 N i ′ \mathcal{N}_i' Ni ✔️
    合并列表 合并当前 WorkLisk ( L i ) \text{WorkLisk}(\mathcal{L}_i) WorkLisk(Li)与新排序的节点列表 N i ′ \mathcal{N}_i' Ni形成新的 L i \mathcal{L}_i Li ✔️
    更新节点 又将 L i \mathcal{L}_i Li排序后选取最近的未访问点 u i ∗ {}u_i^* ui作为下一个当前节点
  2. 异步传输:执行内核的同时, CPU \text{CPU} CPU u i ∗ u_i^* ui的全精度向量传输给 GPU \text{GPU} GPU → \to 以便后续重排

🔁数据传输: CPU ← 当前节点 u i ∗ GPU \text{CPU}\xleftarrow{当前节点u_i^*}\text{GPU} CPU当前节点ui GPU

3️⃣后 CPU \text{CPU} CPU阶段:若 L i \mathcal{L}_i Li中所有点都被访问过且 ∣ L i ∣ = t |\mathcal{L}_i|\text{=}t Li=t,则认为已经收敛 → \to 结束循环

1.2.3.   \textbf{1.2.3. } 1.2.3. 第三阶段: (搜索收敛后的)重排与输出

1️⃣重排与输出

  1. 重排的时序逻辑
    时间 操作 位置
    搜索过程中 用一个数据结构,存储每个 Iter \text{Iter} Iter中向 CPU \text{CPU} CPU发送的全精度候选点 CPU→GPU \text{CPU→GPU} CPU→GPU
    搜索完成后 计算所有候选点到查询点距离,按全精度距离排序后选取前若干 GPU \text{GPU} GPU
  2. 输出:选取重排后的 L i \mathcal{L}_i Li中,离 q i q_i qi最近的 k k k个节点 → \to 作为 k - k\text{-} k-最邻近返回

2️⃣重排的意义:用小成本(仅极小部分即候选点以全精度送往 GPU \text{GPU} GPU),补偿由压缩距离产生的误差

2.   BANG \textbf{2. BANG} 2. BANG的微内核设计与并行优化

2.0.   \textbf{2.0. } 2.0. 微内核总体设计概览

1️⃣设立独立微内核的操作:

阶段 有独立微内核的操作
第一阶段(建表) PQ \text{PQ} PQ表构建操作
第二阶段(主查询) 过滤邻居,距离计算,邻居(归并)排序,归并列表
第三阶段(重排) 重排操作

2️⃣动态线程块的优化:

  1. 每个查询分配到一线程块执行,查询过程会依次执行多个内核
  2. 执行不同内核时按经验调整线程块大小(如计算密集型内核的块更大),以保证 GPU \text{GPU} GPU的高占有

2.1.   \textbf{2.1. } 2.1. 第一阶段: PQ \textbf{PQ}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值