2026 年 9 月 10 日,DeepSeek 官方组织在 GitHub 上发布了仓库 deepseek-ai/DeepSelect,同一天还放出了 v1.0.0 与一份中英双语的算法深度解析文档。截至发布当天,仓库拿到 152 颗星,主语言是 CUDA,创建并首次推送都发生在 9 月 10 日,属于当日活跃的新项目。它不是又一个"又训了个模型"的故事,而是一个更底层、也更容易被忽视的东西:高性能 TopK 算子与其配套的采样器。
值得注意的呼应在同一天发生。DeepSeek 同日开源了 V4.1 Flash,而 DeepSeek Sparse Attention(简称 DSA,下文统一用 DSA)正是 V4.1 Flash 底层的稀疏注意力机制——本库就是 DSA 所依赖的算子底座。换句话说,你看到的"模型开源"只是水面上的那部分,DeepSelect 是水面下让稀疏注意力跑得动、跑得快的算子实现。本批我们同时发了一篇 V4.1 Flash 的热点解读(点此阅读),可以把两篇对照着看:那篇讲"模型对外是什么",这篇讲"它靠什么算子撑起来"。
一、项目是什么:DSA 稀疏注意力的 TopK 算子底座
先说清楚 DeepSelect 的定位。它不是一个通用 TopK 库,而是「DeepSeek Sparse Attention(DSA)所用 TopK kernel 的高性能实现 + sampler」。DSA 被用在 DeepSeek V3.2、DeepSeek V4 和 DeepSeek V4.1 三代模型里。在 DSA 内部,需要用 TopK 算子从大量 context token 中选出最相关的位置;在采样阶段,也需要用 TopK 从一个较大的词表里选出候选 token。DeepSelect 干的事,就是为这两类「选最大的 k 个」需求提供比原生 torch.topk 快得多的实现,官方给出的实测加速区间是 2 到 20 倍(README 原文 "2 ~ 20x speedup")。
把关系捋一遍:用户调用的是 DeepSeek 的模型(V3.2 / V4 / V4.1),模型内部跑 DSA 做稀疏注意力,DSA 内部频繁调用 TopK,而 DeepSelect 就是把这一层 TopK 换成了自己手写的高性能 CUDA kernel。所以 DeepSelect 的价值首先体现在「它让 DeepSeek 系模型在推理时更省算力、更快」,而不是直接给用户一个能随便套在任何任务上的 TopK 工具。
这也是为什么我们对它的判断要冷静:它的第一性是为 DeepSeek 系模型服务。这一点放到第四节和第六节再展开。
二、为什么 TopK 会卡住注意力:被忽视的瓶颈
很多做工程的同学对注意力的直觉集中在「矩阵乘」上,以为瓶颈永远是 GEMM。但在稀疏注意力的语境下,TopK 反而可能成为端到端延迟里不可忽略的一块。原因很直接:DSA 要从一长串 context token 里挑出最相关的那几个位置,这个「挑选」动作本身就是一次 TopK;而采样的候选 token 筛选,又是一次对大词表(约 12.8 万量级)的 TopK。这两类操作在每一层、每一步解码里都可能反复发生。
原生 torch.topk 是通用实现,它要为各种 dtype、各种 shape、各种 topk 大小都留余量,常数因子做不到极致。而当 TopK 的输入是一个几十万长度的行、且只需要取其中很小的 k(比如 512)时,通用实现的「把整行排序再取前 k」思路就会做大量无用功。DeepSelect 的出发点正是:既然 DSA 和采样这两类 workload 的输入分布、dtype、topk 大小高度可预期,就可以为它们量身定制算法,把不经济的全排序替换成「一次扫描 + 阈值收敛」的思路,从而吃掉那 2 到 20 倍的加速空间。
这也解释了官方文档反复强调的一点:TopK 的 workload 千差万别,最快的算法与实现高度依赖输入 dtype、batch_size、vocab_size 和 topk。DeepSelect 明确只聚焦两类场景,而不是号称「通吃一切」。这种克制,恰恰是它能做快的前提。
三、RadixSelect 算法:一遍扫描 + 随机块 + 阈值收敛
DeepSelect 的算法核心是一个名为 DeepSelectTopk 的流程。它维护一个初值为 -inf 的 top-k 阈值 T,然后循环执行三个简单步骤:
- 扫描(Scan):按照随机顺序,每次扫描输入序列中大小为
B的一块。 - 筛选(Filter):根据当前阈值
T筛选,只把仍然可能进入 top-k 的元素(也就是大于T的元素)追加进候选缓冲区topk_candidate。 - 压缩(Compact):当候选缓冲区变大(或算法结束时),在 shared memory 里执行一次基于 radix-select 的 TopK(也可以用别的 TopK 算法),把候选集重新精简回
k个元素,并把T更新为这k个元素里最小的那个值。
伪代码(原文逐字)是这样的:
DeepSelectTopk(x[0:N), k, B, B2):
require 1 <= k <= N and B >= 1 and B2 >= 1
topk_candidate = [] # shared memory
topk_threshold = -inf # current top-k threshold
p = random_permutation(ceil_div(N, B)) # independent of x
for block in p:
lo, hi = block * B, min((block + 1) * B, N)
# Keep only elements that may still enter the top-k.
for j in range(lo, hi):
if x[j] > topk_threshold:
topk_candidate.append((x[j], j))
# Periodically compact the candidate buffer.
if len(topk_candidate) >= k + B2:
topk_candidate = RadixSelectTopK(topk_candidate, k)
topk_threshold = min(v for v, _ in topk_candidate)
return RadixSelectTopK(topk_candidate, k)初始时阈值是 -inf,所以在第一次压缩之前,所有元素都会被接受。此后 topk_threshold 表示当前第 k 大的元素值,并且随着扫描推进只会单调增大。于是后续元素通过阈值筛选的概率逐渐降低,候选缓冲区的增长速度也越来越慢。文档给出一个对 k=512 较合理的参数配置:B = B2 = 1024。
几个工程上很关键的性质:任意时刻都有 len(topk_candidate) <= k + B2 + B;x 里的每个元素恰好被读取一次,而且以大小为 B 的连续块方式访问(这对显存带宽友好);除去随机排列,算法额外所需的空间只有 O(k + B + B2),小到可以放在速度更快的 shared memory 里。
这里还要点出一个设计动机:为了避免极端输入下的性能退化,块的处理顺序必须是随机的。这一点不是拍脑袋,下一节会用期望上界来给它一个严格保证。
四、"总处理元素数期望上界"到底意味着什么
这是整篇算法解析里最硬核、也最值得读懂的一段。问题背景是:固定输入,唯一随机性来自一个跟输入独立的均匀随机块排列。我们想知道——在最坏的输入分布下,所有 RadixSelectTopK 调用一共要处理多少元素?
记 m = ceil(N / B),L = k + B + B2,H_m = Σ(1/i)(i 从 1 到 m,也就是第 m 个调和数)。文档证明了:在给定前 i 个已处理块集合 U_i 的条件下,第 i 个块加入候选的元素数量的期望 E[A_i | U_i] <= L / i;再由期望的线性性得到所有被追加元素总数 E[A] <= L · H_m。令 R 为循环内部调用 TopK 的次数,每次至少删掉 B2 个元素,所以 R · B2 <= A;而每次被追加的元素进一次输入、每次循环内调用保留的 k 个元素之后还会再被处理一次,于是总处理元素数 W = A + kR。最终得到:
E[W] <= (1 + k / B2) · L · H_m
= (1 + k / B2) · L · (ln m + O(1))这个式子对工程师意味着什么?它说的是:在随机块顺序下,RadixSelect 阶段「被反复处理的冗余元素总量」的期望被一个关于输入规模的对数项 ln(N/B) 所控制,而不是线性地跟随 N 增长。换句话说,即便输入有几十万、上百万个元素,真正需要进 radix-select 反复比较的量也只是 O((1 + k/B2)(k + B + B2) · log(N/B))。
文档的性能综述进一步给出:当 B, B2 = Θ(k) 时,TopK 部分额外的计算量只有 O(k · log(N/k)),远小于输入规模 N。这正是「一遍扫描 + 阈值收敛」相比「全行排序」的本质优势:你不再为每一个元素都付出排序代价,而只为那一小撮真正可能进 top-k 的元素付出对数级的代价。经过针对硬件的细致优化后,它在 DSA 场景下相对 torch.topk 取得显著加速,也就不意外了。
五、为什么用"有效内存带宽"而不是 FLOP 来度量
README 把性能度量方式写得非常坦率:用 tests/test.py(python3 tests/test.py --perf-only)跑基准,指标是同一输入下相对 torch.topk 的加速比;而核心度量指标是「有效内存带宽」(effective memory bandwidth)。理由是:TopK 不做浮点运算,所以报一个 FLOP 速率在这里没有意义。
这句话值得每个做算子优化的人记住。很多同学习惯性地用 TFLOPS 衡量一切 kernel 快慢,但 TopK 的本质是「把数据从全局显存搬进来、比较、选出」,它吃的是显存带宽而不是算力。一个 TopK kernel 优不优,要看它把 HBM 带宽用到了多少(官方图表里把 Lightning Indexer 场景画在共享的 0 到 7 TB/s 坐标轴上),而不是看它跑了多少 FLOP。这也反向说明了 DeepSelect 的优化重心为什么落在「减少全局内存读取次数、连续块访问、把候选集塞进 shared memory、用位运算和 PTX 挤压常数因子」上——因为这些都在为带宽服务。
具体基准设置:Lightning Indexer 场景用 bfloat16、topk = 512,每个 batch size 一张子图;Sampling 场景用 float32、vocab_size = 129280、topk = 512。
六、使用场景与边界:它不做什么,比能做什么更重要
对一个极早期的算子库,最有价值的信息往往不是「它能做什么」,而是它的硬约束。DeepSelect 明确只支持两类场景,且都有刚性限制。
| 维度 | Lightning Indexer 场景 | Sampling 场景 |
|---|---|---|
| 输入 dtype | torch.bfloat16 | torch.float32 |
batch_size | 1 ~ +∞(大小 batch 都优化) | 1 ~ +∞ |
vocab_size | 1 ~ +∞(大小词表都优化) | 约 128K |
topk | 必须 ≤ 4096(更大不支持) | 必须 ≤ 4096(更大不支持) |
其余边界同样要记牢:
- topk 硬上限 4096:大于 4096 的请求直接不在支持范围。这是算法与实现对特定 workload 的取舍,不是疏漏。
- dtype 严格区分:Lightning Indexer 只吃 bfloat16,Sampling 只吃 float32,别混用。
- 只对特定 shape 优化:仓库只针对上述两类 workload 的输入分布调优,并非对任意 shape 都快。
- 行步长对齐要求:输入张量
x的 row stride 必须对齐到deep_select.get_stride_requirement()[0]字节,且最后一维必须连续;未对齐的输入需要 padding。两个输出由调用方分配,步长对齐到get_stride_requirement()[1]字节(可能不连续);也可传output_idx=写入自己持有的缓冲,该缓冲也要满足同样的步长要求。 - 变长行:用
end设每行(不含)的上界,短于 topk 的行用value_oob_fill_value/idx_oob_fill_value填充。 - NaN 处理始终开启:默认
abort_when_nan_found=True会让 kernel 调用trap()并中止;长度<= topk的行不做 NaN 检查。
另外两条调参建议是 README 原文直接给的,能直接省性能:除非输出必须按 index 或 value 排序,否则关闭 sorted_index(开启任一种排序都付出性能);不需要取值时设 return_value=False,这会跳过 value 输出、更快(约 10%)。
顺带一提长上下文成本这个更宏观的话题——我们本批另一篇横评(点此阅读)系统性比较了不同长上下文方案的账单,而 DeepSelect 这类底层算子,正是把「长上下文稀疏化」从论文变成可落地推理的关键一环。如果你打算把 V4.1 Flash 接进自己的系统,本批的集成 SOP(点此阅读)也值得一并看。
七、冷思考:152 颗星与它的真实价值
最后泼一点冷水,帮读者建立正确预期。
第一,152 颗星意味着它处于极早期。发布当天就拿到这个星数,更多反映的是「DeepSeek 官方出品」的关注度,而不是社区已经大规模验证过它在生产里的稳定性。引用信息里作者署名为 Yi Qian、Shengyu Liu、Yichen Li(2026),项目还非常新,接口、边界、性能都可能在后续版本里变化。
第二,它对非 DeepSeek 系模型的直接用处有限。DeepSelect 是为 DSA(V3.2 / V4 / V4.1)和对应采样 workload 量身定制的,输入 dtype、topk 上限、shape 假设都绑定在 DeepSeek 的推理路径上。你拿一个完全不相关的任务、随便什么 dtype 和 shape 去套,既不在它的优化范围,也可能直接触发不支持的限制。把它当成「通用 TopK 加速库」来用,是误会了它的定位。
第三,它的价值更偏向生态层面而非通用层面。对 DeepSeek 系模型的用户和研究者,它把 DSA 的算子底座开放出来,意味着你可以审计、改写、针对自己的硬件再榨性能;对整个社区,它是一份「为特定 workload 手写 CUDA TopK」的高质量参考实现,里面的 PTX 与位运算技巧(下面第八节细说)本身就是教材。换句话说,它的意义不在于「让所有人的 TopK 都变快」,而在于「让 DeepSeek 的稀疏注意力真正跑得动、跑得省」,并顺手给算子优化者留下一份可学习的范本。
常见问题
Q1:DeepSelect 和普通的 torch.topk 有什么区别?
A1:核心区别在两点。一是定位:torch.topk 是通用实现,要为各种 dtype、shape、topk 大小留余量;DeepSelect 只聚焦 DSA 的 Lightning Indexer 与采样两类 workload,为它们定制了「一次扫描 + 阈值收敛」的 RadixSelect 算法。二是性能:官方实测相对 torch.topk 取得 2 到 20 倍加速,且度量指标是有效内存带宽而非 FLOP。如果你的输入正好是它支持的两类场景,收益明显;否则它不支持或不保证更快。
Q2:为什么 DeepSelect 的 topk 必须小于等于 4096? A2:这是算法与实现对特定 workload 的明确取舍,不是遗漏。DSA 的 Lightning Indexer 与采样场景里,实际需要的 k(如基准中的 512)远小于 4096,DeepSelect 的候选缓冲区、压缩触发与 shared memory 布局都围绕「小 topk」设计。大于 4096 的请求不在支持范围,仓库不会保证正确性或性能。
Q3:DeepSelect 支持哪些输入数据类型?
A3:只支持两类,且严格区分:Lightning Indexer 场景输入 dtype 为 torch.bfloat16,Sampling 场景输入 dtype 为 torch.float32。此外输入张量的 row stride 必须对齐到 deep_select.get_stride_requirement()[0] 字节、最后一维必须连续;未对齐的输入需要 padding。两个输出由调用分配,步长对齐到 get_stride_requirement()[1] 字节。
Q4:我能在自己的非 DeepSeek 模型里直接用 DeepSelect 吗? A4:可以调用,但要先确认你的 workload 落在它支持的两类场景内(bfloat16 的 Lightning Indexer,或 float32、词表约 128K 的 Sampling),且 topk ≤ 4096、输入满足步长与连续性要求。如果你的模型并不是 DeepSeek 系、输入分布与这两类场景不符,DeepSelect 不在优化范围,可能不更快甚至不被支持。它的第一性是为 DSA(V3.2 / V4 / V4.1)服务。
Q5:如何验证 DeepSelect 在我机器上的加速比?
A5:用仓库自带基准:python3 tests/test.py --perf-only,它会报同一输入下相对 torch.topk 的加速比,指标是有效内存带宽。Lightning Indexer 场景基准用 bfloat16、topk=512(每个 batch size 一张子图),Sampling 场景用 float32、vocab_size=129280、topk=512。若要进一步省性能,记得按 README 建议关闭非必须的 sorted_index,并在不需要取值时设 return_value=False。
参考来源
- DeepSeek DeepSelect GitHub 仓库(152 星,CUDA,DeepSeek 官方组织,创建并 push 于 2026-09-10):https://github.com/deepseek-ai/DeepSelect
- 官方 README(定位、支持场景、性能度量、安装与使用):https://github.com/deepseek-ai/DeepSelect/blob/main/README.md
- 官方中文算法解析 docs/DeepSelect-deep-dive.zh.md(算法设计背景 / 算法 / 期望上界 / 性能综述 / 实现)
- 官方英文算法解析 docs/DeepSelect-deep-dive.md
- GitHub API 实测数据(2026-09-10):仓库 152 星、标签 v1.0.0、同日发布中英双语算法文档