想象你管理着一座十万平米的百货仓库:上千排货架整整齐齐,但绝大多数货位空空如也,只有几十个角落稀稀拉拉摆着商品。每次总部要盘点,如果拿着整层楼的平面图一个格子一个格子地核验,你就会把 99% 的时间浪费在“确认这里有货吗?没有”上——对着空气干活。真实世界的矩阵,绝大多数也是这种“空仓库”:推荐系统里用户与商品的交互矩阵,上亿商品中一个用户只逛过几十个;社交网络里用户好友关系矩阵,数亿用户每人只认识几百人。我们把这种“零元素占绝对多数”的矩阵称为稀疏矩阵。cuSPARSE 就是 NVIDIA 为这类“空仓库”定制的物流手账系统:它不保存任何空货位的信息,只记录“哪排哪列有货、货是什么”,让 GPU 这张超级流水线只拣有货的货架,从此告别“对着零做乘法”的尴尬。
掌握 cuSPARSE,你才能在推荐系统、图神经网络这类高稀疏真实场景中写出真正跑满 GPU 算力的代码——数据稀疏是常态,稠密才是特例。
可视化演示
动态展示CSR存储格式、SpMV稀疏矩阵向量乘、2:4结构化稀疏与格式对比四个视角,直观理解GPU稀疏计算核心机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
1. 稀疏世界
假设你管理着一座十万平米的百货仓库,货架有十万个货位,但真正摆上货的只有几十个位置。每次盘点,你若拿着整层楼的平面图一个格子一个格子地核验,绝大多数时间都在对着空气确认“这里没有货”。真实世界的运算数据,很多时候就是这种空仓库:推荐系统里用户与商品的交互矩阵,上亿商品中用户只浏览过几十个;社交网络里用户的好友关系矩阵,数亿用户每人只认识几百人;自然语言处理里的词共现矩阵,词典里绝大多数词对从未同时出现。这些矩阵的共同特征——零元素占了绝对多数——就是稀疏。
如果用稠密方式存储,一个 1 亿行 × 1 亿列的交互矩阵要存 \(10^{16}\) 个元素,按 8 字节计就是 8 万万亿字节,远超任何 GPU 的显存。这就像把整层楼的平面图打印成工程蓝图,99.99% 的格子里写着一个“空”字——这张纸的费用,就是我们白白消耗的存储空间与搬运时间。GPU 的算力再强,也经不起这种“空转”。
为了量化稀疏程度,我们引入填充率(fill-in ratio)指标:
填充率只有万分之一甚至更低的矩阵,若还按稠密算法去跑,GPU 的计算单元全在做“零乘任意数”的无用功。cuSPARSE 正是为这种空仓库而生的管理系统——它只登记和搬运有货的货位,让 GPU 的每一条流水线都花在刀刃上。仓库越空,这套手账的价值就越大。
- nnz 含义nnz(A) 表示矩阵 A 中非零元素的总个数,如同仓库手账上登记的货物总件数。
- 总货位数m 行乘 n 列得到矩阵的全部元素个数,也就是仓库的全部货位。
- 比值含义两者相除得到有货货位占比。当 ρ 小于 1%,矩阵就是典型的“空仓库”。
2. 三种格式
仓库管理员不喜欢画平面图,他更愿意记手账。针对同一批货物,有三种记法:最简单的是每件货物贴一张便签,上面写着“第几排第几列 + 货名”,一叠便签就是一本清单,这叫 COO 格式;更专业的是按“排”整理成台账——先写下每一排的货物从第几号便签开始,再罗列每张便签的列号和货名,这叫 CSR 格式;还有一种按“列”整理的台账,叫 CSC 格式。cuSPARSE 三种都支持,就像集团统一配发了三种手账模板。
COO 格式用三个数组:行号数组 rows、列号数组 cols、数值数组 values,一一对应,直观得就像一叠便签。CSR 格式进一步压缩:用 row_ptr 记录每一行第一个非零元素在 values 中的起始下标,再用 col_idx 和 values 分别记录列号与数值。CSC 则是把“行”换成“列”,适合按列查询。cuSPARSE 的 API 通过 cusparseCreateCsr 等函数接收这些数组,并支持三者互转,如同把便签重新装订成行台账或列台账。
CSR 存储中,“第 i 行有哪些非零”可以用一个区间来精确描述:
三种手账各有长短:COO 记录直观但查找慢,CSR 查整排飞快但改货位麻烦,CSC 适合按列做统计。实际工程中,推荐系统嵌入表常用 CSR,图神经网络消息传递常用 COO 建图再转 CSR,cuSPARSE 提供 cusparseCreateCoo 与 cusparseCsr2coo 等转换 API,让管理员随时换手账。
- 行指针 row_ptrrow_ptr[i] 存放第 i 行第一个非零元素在 values 中的下标。
- 半开区间左闭右开区间 [row_ptr[i], row_ptr[i+1]) 表示第 i 行全部非零的编号范围。
- 列号取值范围内每个 k 对应的 col_idx[k] 就是该非零元素的列号。
3. SpMV 向量
总部要求:每个排口统计货物总额,每件货物的“价值”等于毛重乘以系数。仓库管理员翻开 CSR 台账,从第一排第一件开始,找到列号、取出系数、乘完累加,逐件处理,完全不用去空货架上逛一圈。这个动作在数学上叫 SpMV(稀疏矩阵乘向量):给定稀疏矩阵 A 和稠密向量 x,计算结果向量 y = Ax。
CSR 格式下的 SpMV 可以精确写成求和式:第 i 行的结果,等于该行所有非零元素 values[k] 与向量 x 中对应列号取值 x[col_idx[k]] 的乘积之和。
GPU 上实现 SpMV 时,最自然的做法是每个线程负责一行:线程 t 只翻第 t 排的台账,逐件乘加。稠密算法算 100 个货位的排要 100 次乘法,稀疏手账可能只需要 3 次——省下的不仅是乘法,更是从显存取回大量零元素的内存带宽。这就是 SpMV 在推荐系统、科学计算迭代求解器中被反复调用的根本原因。
- 行下标 iy 的第 i 行由矩阵 A 的第 i 行决定,对应仓库第 i 排货架的统计任务。
- 求和区间k 从 row_ptr[i] 到 row_ptr[i+1]-1,恰好是台账上第 i 排的货物编号范围。
- 取数相乘values[k] 是货物毛重,x[col_idx[k]] 是按列号从系数表 x 中取出的对应系数。
4. SpGEMM 矩阵
公司并购了隔壁仓库,要把两家货合并统计“组合价值”:A 仓库第 k 列有货、B 仓库第 k 行也有货,两件货才能组合成一件新产品。这比单排拣货复杂得多——需要同时翻两本手账,找“列号”和“行号”都能对上的货位。这个操作叫 SpGEMM(稀疏矩阵乘稀疏矩阵),计算 C = A × B。
对于 C 的每个元素,必须满足“中间维度 k 在 A 的第 i 行非零、同时在 B 的第 k 行非零”,才有一个乘积项需要累加:
SpGEMM 是稀疏计算中最难啃的骨头:组合后的仓库可能比原来任何一个都拥挤(中间结果膨胀),也可能更空(大量乘积对相互抵消)。更麻烦的是,输出矩阵 C 的稀疏度在计算前难以预测,需要先扫一遍估算非零数量、做好内存预算,再正式计算。cuSPARSE 提供 cusparseSpGEMM 的两阶段接口(估算 + 执行),正是为了解决“新仓库到底要准备多少货架”的问题。
- 乘积项条件A_{i,k} 非零且 B_{k,j} 非零,两个条件同时满足才有一个非零的中间乘积。
- 只累加非零对求和范围被限制在满足条件的 k 上,跳过任何一边为空货位的组合。
- 乘积累加把 A_{i,k} 与 B_{k,j} 的乘积累加到 C_{i,j} 中。
5. GPU 并行
新仓库请了几百个搬货工人,每个工人负责一条货架排。问题立刻暴露:第 3 排有 50 件货,第 7 排只有 1 件货,干完活的工人只能站在一旁等待,负责 3 排的工人累得满头大汗。GPU 上跑 SpMV 也是同一个道理:每个线程处理一行,行非零数量差异悬殊时,整个 warp(32 个线程的小组)的执行时间被最重的那一行拖死。
衡量这种不均衡程度,我们定义一个负载不均衡度 SU(Skewness of Utilization):
除了负载不均衡,GPU 访存还有两个坑:合并访问要求相邻线程读相邻内存,否则一次内存事务只搬运一个有用数据;bank 冲突则发生在 warp 内多个线程同时访问共享内存的同一分区,如同多个工人抢同一个货梯。cuSPARSE 在选择内部算法时,会根据稀疏模式自适应地切换到行并行、非零并行或分段扫描等策略,尽量让每个线程的货量均匀、访存连续。
- 分子所有行中非零数量最多的那一行的工作量,相当于最重的一排货架。
- 分母所有行非零数量的平均值,相当于全仓库的平均货量。
- 比值解释两者相除得到“最忙线程相对平均水平的倍数”,倍数越大,越多人干等。
6. API 流程
仓库引入了标准化作业流程,cuSPARSE 的编程套路也像一张标准作业单:先领一个工作许可证,再提交仓库结构图,然后备好货物清单,最后喊一句通用口令干活。具体来说,第一步 cusparseCreate 创建句柄 handle 作为许可证;第二步用 cusparseCreateCsr 创建矩阵描述符,把 CSR 的三个数组交给库;第三步是执行运算,比如 cusparseSpMV;第四步收工后 cusparseDestroy 销毁句柄和描述符。
cuSPARSE 提供的通用 SpMV 语义比裸的 y=Ax 更灵活,支持缩放和转置:
这套流程与 cuBLAS 的句柄设计如出一辙:handle 管理上下文,描述符描述数据布局,alpha/beta 控制累积语义。学会 cuSPARSE 之后再学 cuDNN/cuBLAS,会发现 API 风格高度统一,如同集团旗下所有仓库用同一套作业标准书。
- op 转置op(A) 表示对 A 做原样(N)、转置(T)或共轭转置(H),如同按“排”或按“列”翻台账。
- α 缩放整个乘积乘以系数 α,实现 y = αAx 的缩放。
- β 累加加上 β 倍的旧 y,支持原地累积计算,避免反复分配临时数组。
7. 优化策略
干了二十年的老仓管员有三条秘诀:第一,提前把台账本订够页数,别等记到一半发现没纸了——对应 内存预分配,cuSPARSE 的 SpGEMM 先用估算阶段算出输出非零数量,一次性分配好显存;第二,把货物按列号重新排队,让同一趟送货路线上的货位挨在一起——对应 非零排序与重排,提高缓存命中率;第三,换一把更快的电子秤——对应 混合精度,在不损失精度的场景用 FP16 替换 FP32,让 Tensor Core 跑得更快。
存储格式本身也值得优化。CSR 的压缩效果可以用压缩比来量化:稠密存储需要 mn 个元素,而 CSR 需要 row_ptr(m+1 个)、col_idx(nnz 个)、values(nnz 个):
最后的优化是按场景选格式:行访问密集选 CSR,列访问密集选 CSC,图动态建边选 COO,结构化稀疏(如 2:4 剪枝)用 cuSPARSE 的块稀疏格式配合 Tensor Core。优化没有银弹——本质是让 GPU 的缓存、访存单元和流水线都尽量满负荷运转。
- 总货位分子 m×n 是稠密矩阵的全部元素个数,即平面图上的所有格子。
- 台账开销分母 2nnz+m+1 是 CSV 三个数组的总长度:values 和 col_idx 各 nnz 个,row_ptr 有 m+1 个。
- 压缩倍数两者相除得到压缩比,例如 η=1000 表示手账只用了平面图千分之一的纸。
8. 工业应用
这套“仓库手账”如今是全集团的标配。在推荐系统里,用户-物品交互矩阵就是一座巨型空仓库,ctr 预估模型需要查询用户点击过的几百个物品的嵌入向量——这正是 SpMV:用稀疏交互矩阵乘嵌入矩阵,只取非零位置的嵌入。在图神经网络中,社交网络的邻接矩阵同样极度稀疏,GCN 的每一层都在做“邻居消息聚合”,本质就是一个稀疏矩阵乘特征矩阵(SpMM),cuSPARSE 让亿级节点的图也能在 GPU 上训练。
GNN 消息传递的数学形式,与 SpMM 完全对应:
科学计算同样是 cuSPARSE 的主战场:有限元分析、天气预报、油气勘探都要反复求解大规模稀疏线性方程组,每一次 Krylov 迭代都调用一次 SpMV。cuSPARSE 与 cuDNN/cuBLAS 协同工作,前者管稀疏、后者管稠密,共同支撑起从深度学习训练到科学模拟的完整 GPU 生态。稀疏不是特例,而是 AI 处理真实世界数据的默认姿势。
- 邻居集合N(i) 是节点 i 的邻居,对应稀疏邻接矩阵第 i 行所有非零位置。
- 求和聚合对每个邻居变换后的特征求和,累加顺序与 CSR 存储的求和区间完全一致。
- 激活函数σ 是激活函数,例如 ReLU,相当于对统计结果做一次“审批盖章”。
AI 应用场景
推荐系统 Embedded 查询
阶段:特征工程与模型推理
解决的问题:传统方式把用户-物品交互矩阵当稠密矩阵处理,上亿商品对每个用户都做全量打分,计算量和显存都不可承受。利用 cuSPARSE 的 SpMV,只看用户实际交互过的几十个商品,把嵌入表查询变成稀疏矩阵乘向量,计算量直降几个数量级。
具体案例:某短视频推荐平台的双塔模型维护一张 10 亿 × 128 的嵌入表,用户交互序列构成稀疏矩阵。线上推理时用 cuSPARSE 的 cusparseSpMV 对交互矩阵与嵌入表做稀疏乘,单次请求嵌入查询延迟从毫秒级降到亚毫秒级,GPU 利用率提升 3 倍以上。
图神经网络消息传递
阶段:模型训练与推理
解决的问题:GCN/GAT 每层都要做邻居特征聚合,邻接矩阵稀疏度常超过 99.99%。朴素实现按稠密矩阵乘,显存和算力都被零元素浪费。cuSPARSE 的 SpMM 把聚合计算严格限定在邻接矩阵非零位置,让亿级节点图训练成为可能。
具体案例:某社交平台的欺诈检测系统构建 3 亿节点、30 亿边的用户关系图,GCN 训练时用 cuSPARSE 的 cusparseSpMM 做消息聚合,相比稠密实现显存占用降到 1/50,单轮训练时间从小时级压缩到分钟级。
科学计算迭代求解器
阶段:数值模拟与仿真
解决的问题:有限元分析、流体仿真中的偏微分方程离散后产生巨型稀疏方程组,每次迭代都要做 SpMV。传统 CPU 实现受内存带宽限制,cuSPARSE 让迭代求解器的主循环直接在 GPU 上运行,配合预条件子大幅加速。
具体案例:某汽车企业的碰撞仿真中,网格矩阵规模 5000 万×5000 万、非零约 3 亿。使用 cuSPARSE 的 SpMV 驱动共轭梯度法,单次迭代时间从 120ms 降到 9ms,整场仿真提速 13 倍,工程师能在一晚完成原本一周的迭代设计。
Python 演示
# -*- coding: utf-8 -*-"""CSR 稀疏矩阵与 SpMV 的极简实现, 提前演练 cuSPARSE 的核心逻辑""" import numpy as np def dense_to_csr(A): """把稠密矩阵 A 压缩成 CSR 三个数组: row_ptr, col_idx, values""" m, n = A.shape row_ptr = [0] col_idx = [] values = [] for i in range(m): for j in range(n): if A[i, j] != 0: col_idx.append(j) values.append(A[i, j]) row_ptr.append(len(values)) return (np.array(row_ptr, dtype=np.int32), np.array(col_idx, dtype=np.int32), np.array(values, dtype=np.float64)) def csr_spmv(row_ptr, col_idx, values, x): """y = A * x, 按 CSR 手账逐行逐件拣货, 跳过所有空货位""" m = len(row_ptr) - 1 y = np.zeros(m) for i in range(m): for k in range(row_ptr[i], row_ptr[i + 1]): y[i] += values[k] * x[col_idx[k]] return y if __name__ == "__main__": A = np.array([[0, 0, 5, 0], [8, 0, 0, 0], [0, 0, 0, 3], [0, 6, 0, 0]], dtype=np.float64) x = np.array([1.0, 2.0, 3.0, 4.0]) row_ptr, col_idx, values = dense_to_csr(A) print("row_ptr =", row_ptr) print("col_idx =", col_idx) print("values =", values) y = csr_spmv(row_ptr, col_idx, values, x) print("y =", y) print("numpy 校验:", A @ x)
if A[i, j] != 0:跳过空货位,只把有货货位的列号和值收进手账。row_ptr.append(len(values))记下每一排第一件货的编号,这是 CSR 的灵魂。for k in range(row_ptr[i], row_ptr[i + 1]):只遍历第 i 排台账上的货物,空货位根本不出现。y[i] += values[k] * x[col_idx[k]]用列号 col_idx[k] 从向量 x 里取系数,乘完累加。print("numpy 校验:", A @ x)与稠密矩阵乘法结果对账,验证手账没算错。
常见误区
误区cuSPARSE 既然叫“稀疏库”,那我只要把完整的稠密矩阵交给它,它自动就知道跳过零元素了吧?我的代码里已经有个 numpy 数组了,直接传进去不就行了?
正解cuSPARSE 要求你必须先完成稀疏压缩——显式提供 values、col_idx、row_ptr 三个数组,并通过 cusparseCreateCsr 描述稀疏结构。它看到的只有压缩后的非零数据,根本没有“完整矩阵”的概念。就像仓库手账只记录有货货位,库管员不会拿到整层楼的平面图再自己找货。如果传输稠密矩阵,显存带宽在数据搬运阶段就已经被零元素耗尽了。
为何常见:cuBLAS 等稠密库都是直接接收完整数据,初学者容易把 cuSPARSE 想成“自动压缩”的魔法盒,忽略了稀疏格式是使用的前提而非库的附加能力。
误区我听说稀疏计算性能提升巨大,那是不是只要矩阵里有零,用 cuSPARSE 就一定比稠密计算快?那我把所有矩阵都声明成稀疏的好了。
正解当填充率较高(比如超过 30%)时,稀疏格式的索引访问、分支判断、随机访存开销会超过省下的乘法。此时 CSR 的 col_idx 间接寻址反而让 GPU 访存变得支离破碎,稠密计算配合共享内存的矩阵分块反而更快。判断标准是“空仓库值得不值得换手账”——货位利用率高了,平面图直接看反而方便。
为何常见:宣传材料总强调“稀疏加速 XX 倍”,让人误以为稀疏格式天生优越。实际上 cuSPARSE 内部也有稠密回退策略,高填充率下它自己都会建议用 cuBLAS。
误区我学会了 CSR 格式,那以后所有稀疏矩阵都统一用 CSR 存储吧,反正 cuSPARSE 支持它,一劳永逸。
正解CSR 优化的是“按行访问”:逐行遍历、行的连续区间都在内存中挨着。但如果你需要频繁按列操作——比如计算每列的和、提取某一列做特征——CSC 才是正确选择。COO 在动态建图(频繁插入新边)时最方便,因为只需 append 新便签;而图神经网络常用 COO 建图再转 CSR 训练。cuSPARSE 提供格式互转 API,正式原因就是没有万能格式。
为何常见:教程通常拿 CSR 做最详细的讲解,学习者产生“最重要的就是最好的”的错觉,忽略了格式设计与访问模式强相关的本质。
误区SpGEMM 不就是把稠密矩阵乘法的循环里加个 if 判断,遇到零就跳过吗?流程完全一样,写起来也不难。
正解稠密矩阵乘法 C = A×B 的三重循环是固定执行计划;SpGEMM 必须先做“行-列非零匹配”——A 的第 i 行非零列号集合与 B 的第 j 行非零行号集合取交集,交集中的 k 才参与计算。更麻烦的是输出 C 的稀疏模式未知:乘积可能累加成新的非零,也可能相互抵消,需要两阶段(估算+执行)或动态内存管理。这是 cuSPARSE 提供 cusparseSpGEMM 而不是简单封装的原因。
为何常见:许多人把“稀疏矩阵乘法”理解成“能跳过零的稠密乘法”,低估了输出稀疏度未知带来的内存与调度复杂度。
误区填充率从 1% 降到 0.1%,非零少了十倍,那 SpMV 应该也快十倍吧?GPU 不就是靠省乘法来加速的?
正解GPU 性能远非线性地取决于计算量。非零少但分布不均时,负载不均衡让大量线程空转;非零列号跳跃时,x[col_idx[k]] 的随机访存让缓存命中率暴跌;共享内存上的 bank 冲突进一步放大损失。cuSPARSE 的优化策略是排序重排、行合并、分块稀疏等手段,改善的是访存与负载模式,而非单纯减少计算。手账记的货少了,但不代表送货路线一定更顺。
为何常见:CPU 上的稀疏加速主要来自省计算量,人们把 CPU 经验迁移到 GPU,忽视了带宽、合并访问、线程调度这些 GPU 特有的性能瓶颈。
面试题
Q1. CSR 格式中 row_ptr 数组的作用是?
- A.记录每行非零元素的起始位置← 正确答案
- B.记录每列非零元素的个数
- C.存储矩阵全部的行列数值
- D.标识矩阵的行数与列数
答案:A。row_ptr[i] 表示第 i 行第一个非零元素在 values/col_idx 中的下标,row_ptr[i+1]-row_ptr[i] 正是该行非零个数。它如同仓库台账上“每排货物从第几号便签开始”的索引,记录的是行区间起点。B 混淆了 CSR 与 CSC,C 混淆了 values 与 col_idx 的功能,D 是矩阵形状信息而非存储索引。
Q2. 填充率 ρ 的定义是?
- A.非零元素数与总元素数的比值← 正确答案
- B.矩阵行数与列数的比值
- C.非零行数与总行数的比值
- D.已用显存与总显存的比值
答案:A。填充率 ρ = nnz(A) / (m×n),衡量矩阵中有货货位占总货位的比例。B 是矩阵宽高比,C 只统计了含非零的行,忽略了行内零元素,D 是显存使用率,都与稀疏度的定义无关。ρ 越小,cuSPARSE 手账的价值越大。
Q3. 一个 1 亿行 × 1 亿列的稠密矩阵按 8 字节存储,需要多少空间?
- A.8×10^16 字节← 正确答案
- B.1×10^16 字节
- C.8×10^8 字节
- D.4×10^12 字节
答案:A。1 亿×1 亿 = 10^8×10^8 = 10^16 个元素,每个元素 8 字节,共 8×10^16 字节,远超任何 GPU 显存。这正是“空仓库平面图”的浪费:明明没有货,图纸上的空格子却占了全部空间。其他选项要么只算了元素个数没乘 8,要么位数错误。
Q4. SpMV 中访问稠密向量 x 的方式是?
- A.按 col_idx 给出的列号取值← 正确答案
- B.顺序遍历 x 的全部元素
- C.按 row_idx 分组取出
- D.完全不访问向量 x
答案:A。CSR 格式下 SpMV 的核心是 values[k] × x[col_idx[k]],col_idx[k] 记录了货位列号,线程必须按这个列号去 x 中取系数,属于间接随机访问。B 是稠密乘法风格,C 混淆了行与列,D 显然错误——乘积少不了一方因子。
Q5. 下面哪个场景最适合使用 cuSPARSE?
- A.用户物品交互矩阵的嵌入查询← 正确答案
- B.全连接层的稠密矩阵乘法
- C.卷积层的特征图池化
- D.向量之间的余弦相似度
答案:A。用户-物品交互矩阵极度稀疏,嵌入查询本质是 SpMV/SpMM,是 cuSPARSE 的主场。全连接层是稠密矩阵乘法,用 cuBLAS;池化是规约,余弦相似度是向量运算,都不涉及稀疏存储结构。就像手账只适用于货位极空的仓库,满仓时直接用平面图。
Q6. SpGEMM 与稠密矩阵乘法最关键的区别是?
- A.需要行列非零匹配及中间累积← 正确答案
- B.乘法执行顺序完全相同
- C.无需考虑输出稀疏度变化
- D.只能使用单精度浮点数
答案:A。SpGEMM 要求 A 的第 i 行非零列号与 B 的第 j 行非零行号求交集,交集外的 k 直接跳过,且输出稀疏度未知需要两阶段处理。B 忽略了匹配与剪枝流程,C 恰恰相反——输出稀疏度是最大难点,D 无中生有,cuSPARSE 支持多精度。
Q7. 关于 SpGEMM 的中间结果,下列说法正确的是?
- A.可能膨胀出大量冗余项需要后续压缩← 正确答案
- B.会在 GPU 显存中被自动压缩存储
- C.被 cuSPARSE 强制转换为稠密矩阵
- D.因为稀疏度为零而完全消失
答案:A。A 的非零匹配会产生大量中间乘积,这些乘积累加后可能合并为更少输出,也可能保持膨胀状态,因此需要估算阶段预留内存,这是 SpGEMM 的经典挑战。B 没有“自动压缩”,C 会摧毁稀疏优势,D 与事实相反——中间结果通常非零且不可忽略。
Q8. GPU 上 SpMV 负载不均衡的主要原因是?
- A.各行非零元素数量差异悬殊← 正确答案
- B.矩阵总行数超过线程总数
- C.非零元素的数值类型不同
- D.GPU 核心的时钟频率不同
答案:A。行并行 SpMV 中每个线程负责一行,行间非零数量差异大(如一行 50 个非零、一行 1 个)时,最重的线程决定 warp 的执行时间,其他人干等。B 只是任务分批问题,C 数值类型影响的是算力而非均衡,D 硬件频率相同且已由调度器管理。
Q9. cuSPARSE 使用的基本流程包含哪些步骤?
- A.cusparseCreate 建立句柄← 正确答案
- B.cusparseCreateCsr 描述矩阵← 正确答案
- C.cusparseSpMV 执行乘加← 正确答案
- D.cudaFree 释放全部显存
答案:ABC。标准流程是“领证、画图、干活、销证”:Create 建句柄,CreateCsr 描述 CSR 结构,SpMV 执行 y=αop(A)x+βy。D 错误——释放句柄和描述符应使用 cusparseDestroy 系列函数,cudaFree 只释放用户自己分配的数据数组,且不在固定流程内。
Q10. 影响稀疏矩阵 GPU 计算效率的因素有哪些?
- A.非零元素在行间的分布← 正确答案
- B.内存访问是否合并连续← 正确答案
- C.warp 内线程是否发生 bank 冲突← 正确答案
- D.非零元素数值的大小
答案:ABC。行间分布决定负载均衡,访问连续性决定合并访问效率,bank 冲突影响共享内存吞吐,三者是 GPU 稀疏性能的关键。数值大小只影响浮点运算本身,不影响存储布局与访存模式,因此 D 不是效率因素。优化策略——排序、重排、分段扫描——都是在改善 A/B/C 这三个维度。