OFERRY正在进入矩阵

RTX 3090 的张量核心被解锁:一个内核模块赶在驱动绑定前,把 FP16/BF16 发射速率放开到满速

同一块卡、同一个 220W 功耗上限、同一份 PyTorch:RTX 3080 20GB 的 FP16 稠密 GEMM 从 58.0 TFLOPS 涨到 88.2(+52%),BF16 从 59.1 涨到 93.8(+59%)。

而同一批测试里,INT8 矩阵乘从 158 掉到 155 —— 那不是提升,是噪声。

只看到第一行,你会以为有人超频了;看到第二行才明白,这次改的不是算力,是发射速率。

被限住的到底是哪一段#

专业卡和消费卡用同一颗 GPU 时,差距往往不在算力单元的数量上,而在「一条指令多久能被发射一次」。这次被放开的就是后者:累加器为 FP32 的 FP16/BF16 FMA 指令。

项目自己的描述很直白,Zagitta/gpu-unlock 的 README 抬头写的是 Unlock FP/BF16 instruction issue rate on Nvidia 3000’s cards to their professional equivalents,正文补了一句更精确的:会把 GA102、GA103、GA104、GA106 上「FP/BF16 进 FP32 运算」的指令发射速度翻倍。

  • 影响的芯片:GA102 / GA103 / GA104 / GA106(这是项目自述,IT之家 10 月 25 日 07:09 的报道与之一致)
  • 不受影响的是另一条链:INT8 走的是 IMLA 那套指令,所以它在所有报告里都是天然的对照组
  • 它不动的东西也值得记住:显存带宽、CUDA 核心数量、可用的显存容量,一项都没变

判断一条「解锁」消息是不是营销,最快的办法就是找这种对照组。如果 INT8 和标量 FP32 也一起涨了,那测的就不是这个寄存器。

它是怎么做的:initramfs、两次寄存器写、一次 FLR#

这套方案的思路是把 2018 年前后 CMP 挖矿卡解锁的老手艺(V67/PLM 那套定位方法)搬到消费级安培卡上,再换一个更早的时机执行:

  1. 编一个内核模块,放进 initramfs,让它排在 nvidia 之前加载
  2. 此时卡还没被驱动绑定,模块写两个速度选择寄存器:SS0 写 0x88888888(选满 BF16 速率),SS1 写 0x8(IMLA4,4-bit 寻址模式)
  3. 触一次 FLR(功能级复位),把卡按冷启动路径重新初始化
  4. 驱动随后正常绑定,之后一切照旧

关键是它既不是 UEFI 应用、也不是改过的驱动二进制。所以驱动升级不需要重新打补丁 —— 这正是它相对前一代方案的卖点,也是这个仓库能在几天内涨到 3000 多 star 的原因。

安装流程就是标准的 DKMS + initramfs 套路:

SHELL
git clone https://github.com/Zagitta/gpu-unlock
cd gpu-unlock

# 编译 DKMS 模块,并安装 initramfs / udev 集成
make install

# 默认是「关闭」,必须在内核命令行显式 opt-in
# Debian / Ubuntu (GRUB):编辑 /etc/default/grub,在 GRUB_CMDLINE_LINUX_DEFAULT 里追加
#   gpu_unlock.targets=all
# 也可以只解指定的卡,地址用 lspci -D 打出来的格式
#   gpu_unlock.targets=0000:01:00.0,0000:02:00.0
# Fedora 系用 grubby:
#   grubby --update-kernel=ALL --args="gpu_unlock.targets=all"
# 改完必须重建 initramfs(mkinitcpio / update-initramfs / dracut 三选一,看发行版)
READ / EXECUTE / UNDERSTANDUTF-8

重启之后,用模块自己的日志核对,而不是靠猜:

SHELL
# 模块每次解锁都会留日志
journalctl -k -b | grep gpu_unlock

# 看设备、驱动绑定、以及同一张卡上还有几个功能
lspci -nnk -s 01:00.0

# 想关掉:把 gpu_unlock.targets 从 cmdline 删除再重启,没有别的开关
READ / EXECUTE / UNDERSTANDUTF-8

几个 README 明确写出来、但很容易被忽略的约束:

  • 它要求冷启动。开机之后再手动 modprobe 没有意义 —— 驱动已经绑定了,寄存器写不进去。
  • 而且在冷启动之前不要先去碰 nvidia-smi、CUDA、NVML 或 nvidia-modprobe,一旦驱动抢了先,这轮就废了。
  • 睡眠/待机后会掉:挂起会把卡断电,速度选择器被复位。模块会在 resume 之前重放一次解锁(约 1 秒,依赖 NVreg_UseKernelSuspendNotifiers=1),不想付这 1 秒可以 gpu_unlock.resume=0 关掉。休眠(hibernation)不在覆盖范围内。
  • 固件状态在这一次开机里不会被重新上锁:模块会一直保留自己的 DMA 缓冲,直到断电。所以别强行卸载模块,也别指望热重试,想重来就断电重开。

数字要分三档读,混着读一定错#

这次的消息里同时流通着三种来源的数据,它们的口径并不一样:

第一档:项目自述(含 issue #1 的 3090 Ti 表,450W)

负载前后变化
FP16 GEMM87.46118.39+35%
BF16 GEMM87.50157.41+80%
TF32 GEMM42.5882.61+94%

同一张卡、同一个功耗上限,FP16 只涨 35% 而 BF16 涨 80% —— 这说明放开的主要是 BF16 那条指令链,而不是「所有张量运算一起翻倍」。

README 里那张 tested cards 表更要按型号读:3090 区间 +25%~67%、3080 10GB +70-73%、3080 20GB +56-59%、3070 +73-81%、3060 12GB 约 +80%;GA103 的卡当时没有任何报告;3070 Ti 只在 Windows UEFI 那条支线上成功过;3080 Ti 20GB OEM 版解锁流程能跑完,但 BF16 仍然只有半速。

而且 README 自己点明了最大的混淆项:最低的那个 +25% 来自一张被限到 225W 的卡。功耗墙会严重稀释收益,脱离功耗上限谈百分比没有意义。

第二档:社区的受控 A/B(issue #15,10 月 23 日)

两台改过的 RTX 3080 20GB(设备 ID 10de:2206),Debian 13.6 + Proxmox VE 9.2.18、内核 7.0.14-16-pve、驱动 615.71.09,两臂都封在 220W,稠密 GEMM 8192²,5 次预热 + 每窗 20 次 torch.mm、取 5 窗中位数:

负载卡 A 前→后卡 B 前→后
FP1658.0 → 88.2(+52%)58.4 → 89.6(+54%)
BF1659.1 → 93.8(+59%)59.0 → 92.2(+56%)
TF3228.8 → 42.9(+49%)28.7 → 43.8(+53%)
INT8158-160 → 155-160同左,无变化

同一份报告里还有两个值得记住的结果:交替压 FP16/BF16 连续跑了 11566 次迭代,平均吞吐从 56 → 84-86 TFLOPS,峰值 66℃,平均功耗约 211W;以及解锁前后的输出按位一致(bit-identical)。

另一个 GA10x 的报告(issue #60,联想 OEM 的 3060 12GB)给出了更整齐的一组数:FP16 26.84 → 51.40(+91.5%)、BF16 26.82 → 53.13(+98.1%)、TF32 13.32 → 25.95,FlashAttention 类负载 +47%~68%。同一份报告里的对照组更能说明问题:FP32 标量 GEMM 9.31 → 9.40,INT8 _int_mm 21.89 → 22.01。

第三档:媒体转述(IT之家 2026-10-25 07:09,标题写「实际性能提升可达 60%」)

它引用的数字包括:3080 Ti 在 315W 下 BF16 71.59 → 113.98 TFLOPS、TF32 35.65 → 65.28;3080 10GB 在 320W 下 FP16/BF16 大约 65 → 110-113 TFLOPS;实验版 Windows 实现(UEFI 那条支线)在 3090 上 BF16 约 +82.5%。

同篇还引了 Reddit 上 ComfyUI 用户的读数:MOSS-Image SFT BF16 +58.6%、Qwen-Image 2.1 Turbo BF16 +57.2%、Z-Image Turbo BF16 +56.1%,而 MOSS-Image SFT 的 INT8 版本只有 +0.3% —— 又一次印证「只放开特定指令链」。

60% 这个数本身没错,但它是稠密 GEMM 的中位数区间,不是「所有负载都 +60%」。所以引用收益时至少要交代三件事:卡的型号与设备 ID、功耗上限、以及测的是寄存器发射速率还是引擎端到端。

先把报告复算一遍,再决定要不要动手#

我写了个脚本把这些读数换算到同一口径(gain = after / before - 1),顺便核对「报告自称的区间」和实算是否一致。下面是它在本机的真实输出:

TEXT
① 报告读数 -> 统一口径复算(gain = after/before - 1)
报告 / 负载                                      前         后        实算          自称  判定
--------------------------------------------------------------------------------------------
#15 3080-20G @220W  FP16 GEMM 8192²      58.00     88.20     52.1%     +52-54%  一致
#15 3080-20G @220W  BF16 GEMM            59.10     93.80     58.7%     +56-59%  一致
#15 3080-20G @220W  TF32 GEMM            28.80     42.90     49.0%     +49-53%  一致
#15 3080-20G @220W  INT8 GEMM           158.00    155.00     -1.9%   no change  一致
#25 3090 vLLM prefill (tok/s)         12346.00  16165.00     30.9%        +33%  一致
#1  3090Ti @450W   FP16 GEMM             87.46    118.39     35.4%           —  自称留空
#1  3090Ti @450W   BF16 GEMM             87.50    157.41     79.9%     +68-80%  一致
#60 3060-12G        FP16 GEMM            26.84     51.40     91.5%       ~+80%  超出自称区间
#60 3060-12G        BF16 GEMM            26.82     53.13     98.1%       ~+80%  超出自称区间
#35 对照 fp32 标量 GEMM                       9.31      9.40      1.0%     control  一致
#35 对照 INT8 _int_mm                      21.89     22.01      0.5%     control  一致
READ / EXECUTE / UNDERSTANDUTF-8

两个具体结论:3060 那组实算是 +91.5% / +98.1%,比报告自称的「约 +80%」还高(README 的区间偏保守,引用时别把它当成天花板);3090 Ti 的 FP16 与 BF16 差得很远,任何「翻倍」的说法都要指明是 BF16 那条链。

为什么落到 LLM 推理上,加速会变得不均匀#

最干净的一份端到端数据是 issue #25:一张没改过 VBIOS 的普通 3090,驱动 615.71.09、内核 7.0.0-38-generic,用 vLLM 0.30.0 服务一个 27B 的 BF16 模型,2904 token 提示 + 148 token 补全,取 3 次中位数:

指标解锁前解锁后变化
prefill 吞吐12346 / 11857 tok/s16165 / 15992 tok/s+33%
decode 吞吐112.98 / 112.84 tok/s114.24 / 114.39 tok/s+1.2%(等于没动)
TTFT0.235-0.248 s0.176-0.190 s−24%

issue #15 里还多一层对比:同样是 vLLM,prefill 在 8K/32K/128K 三个长度上都拿到 +33%~36%、TTFT −25%~26%;而换成 Strata(一个 GGUF 引擎)之后,8K 长度只有 +0.5%~0.8%(在噪声里),32K/128K 才涨 +12%~13%、TTFT −11%~12%。另一个四卡 3060 的报告里,Strata 的 prefill 从 450 到 600、decode 几乎没变。

规律不复杂:prefill 是算力受限的,decode 是显存带宽受限的。这类改动只放开指令发射,带宽一点没动,所以 decode 纹丝不动是预期结果,不是失败。而同一个引擎换内核实现后,能不能吃到这条指令路径,还取决于它实际调用了哪些 kernel —— 那才是「同样是 vLLM,一个 +36% 一个 +0.8%」的来源。

判断自己的负载能不能受益,按三步走最省事:

  1. 你的瓶颈在 prefill 还是 decode?decode 为主的任务(长对话、流式输出)大概率没收益。
  2. 你的引擎有没有走到 FP16/BF16 进 FP32 累加的那条路?(vLLM 系有明显收益,GGUF/部分自定义 kernel 收益小)
  3. 你的卡功耗上限多少?被限在 225W 以下的卡,收益会被功耗墙吃掉一大块。

至于游戏侧,目前没有可靠证据。README 自己不提带宽和解锁之外的能力,IT之家也把 DLSS 与游戏帧率归到「尚无可靠数据」那一类。

一段能在你自己机器上跑的基准#

下面这份脚本要在有卡的机器上跑,本机没有 GPU,我只做了语法校验,没执行 —— 这一点我如实标出来。它跟社区报告的结构一致,并且强制带上 INT8 对照组:

PYTHON
# tensor_ab.py —— 解锁前后在同一张卡上跑同一份基准,两个方向都要测
import statistics
import torch

DEV = "cuda"
SHAPES = [(4096, 4096, 4096), (8192, 8192, 8192)]


def bench(dtype, iters=20, warmup=5, windows=5):
    out = {}
    for m, k, n in SHAPES:
        a = torch.randn(m, k, device=DEV, dtype=dtype)
        b = torch.randn(k, n, device=DEV, dtype=dtype)
        for _ in range(warmup):
            a @ b
        torch.cuda.synchronize()

        samples = []
        for _ in range(windows):
            start = torch.cuda.Event(enable_timing=True)
            end = torch.cuda.Event(enable_timing=True)
            start.record()
            for _ in range(iters):
                c = a @ b
            end.record()
            torch.cuda.synchronize()
            samples.append(start.elapsed_time(end) / iters)

        ms = statistics.median(samples)
        flops = 2 * m * k * n
        out[f"{m}x{k}x{n}"] = round(flops / (ms * 1e-3) / 1e12, 1)
    return out


def int8_control(iters=20):
    """INT8 是天然对照组:它要是也涨了,说明你测的根本不是这条指令链。"""
    a = torch.randint(-8, 8, (8192, 8192), device=DEV, dtype=torch.int8).contiguous()
    for _ in range(5):
        torch._int_mm(a, a)
    torch.cuda.synchronize()
    start, end = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True)
    start.record()
    for _ in range(iters):
        torch._int_mm(a, a)
    end.record()
    torch.cuda.synchronize()
    ops = 2 * 8192 ** 3 / (start.elapsed_time(end) / iters * 1e-3)
    return round(ops / 1e12, 1)


if __name__ == "__main__":
    for name, dtype in [("fp16", torch.float16), ("bf16", torch.bfloat16)]:
        print(name, bench(dtype))
    torch.backends.cuda.matmul.allow_tf32 = True
    print("tf32", bench(torch.float32))          # TF32 也要显式打开
    print("int8 TOP/s", int8_control())          # 对照组:解锁前后应当基本不变
READ / EXECUTE / UNDERSTANDUTF-8

配套的三条判据,缺一条都不能说「成功了」:

  • 寄存器读数:项目自己的 RM getter 会把 FMLA32/IMLA2/IMLA4 从半速读成满速 —— 这只是「设置生效」。
  • 吞吐读数:上面这份脚本的前后对比 —— 这才是「有没有收益」。
  • 数值一致性:同一批输入在解锁前后 torch.equal 应当为真(社区 A/B 报告的结论是按位一致)。对上游任务来说,这一条比增益更重要。

Ada 那边:寄存器翻倍≠应用翻倍#

10 月 23 日出现的分叉 JustAResearcher/gpu-unlock-ada 把同样思路搬到了 Ada 架构(4090 / 4070 Ti SUPER / 4060 Ti)。它的证据链比安培那边更完整,也更值得当反面教材看:

口径前后变化
4090 寄存器发射速率 FP16165.6331.6 TF/s+100%
4090 寄存器发射速率 BF16165.5330.6 TF/s+100%
4070 Ti SUPER FP16(240W)95.0189.9 TF/s+100%
4070 Ti SUPER INT8379.5379.5 TOP/s0
E4M3 FP8 CUTLASS GEMM59.32659.495 TF/s+0.3%

前两行写着 +100%,但同一次匹配跑的 cuBLAS GEMM 中位数只有:FP16 +12%~34%、BF16 两个 shape +16%~20%,4096³ 基本不变。寄存器翻倍,落到真实 kernel 上被压掉了大约三分之二。

这就是为什么我在开头先写 INT8 那个数字。发射速率是上限,不是收益;能拿到多少,取决于你的 kernel 有多少时间真的卡在那条指令的发射上。

顺带记两个细节:4060 Ti 16GB 的报告里,一次冷启动全过、驱动 595.99.02、getter 读回来也是满速,但没有同卡的 stock 对照 —— 所以那份报告只有「设置生效」,没有「吞吐收益」,引用时不能当性能数据用。另外 3070 Ti SUPER 在温热重启下也能成功,而 3090 那边 README 明确说不保证热重启。

还没被回答的问题#

核对一圈下来,有几件事目前没有可靠数据,写在这里免得被当成已知结论:

  • GA103 是零报告。README 的 tested cards 表里这颗芯片当时一条社区数据都没有。名单里有它,不代表有人在真机上验证过收益。
  • Turing(20 系)不在支持范围内。README 自述的芯片只有 GA102/GA103/GA104/GA106,Ada 那条支线另算;仓库里确实有人开 issue 求 Turing 支持,但没有被实现。
  • 笔记本端的支持是最近才补的:GA107 的 3050 6GB Laptop(设备 ID 10de:25ac)在 10 月 24 日才作为新设备被加进名单,也就是说这颗芯片上的收益数据几乎为零。
  • 功耗墙的代价没有系统研究。只有零散线索:README 里最低的 +25% 来自一张被限到 225W 的卡。到底多少瓦以上才「不亏」,没人给出曲线。
  • 长期稳定性只有两次像样的记录:一次是单卡 11566 次迭代的交替压测(峰值 66℃),一次是「一开机同时解锁多张卡」的成功记录 —— 安培那边有 4 卡与 7 卡同机解锁的报告,Ada 那边一次冷启动也能带过 4 张。几个月尺度的持续跑、以及解锁后的功耗与温度曲线,目前没有公开数据。
  • 「解锁完但没收益」的样本真实存在(3080 Ti 20GB OEM、4060 Ti 16GB),说明能不能吃到这条路径,跟芯片、VBIOS、以及你用的 kernel 实现都有关 —— 这也是为什么我建议先复算报告、再上自己机器测。

风险清单:都能在 issue 里翻到#

这类「改写厂商卡行为」的方案,风险跟收益一样具体:

  • 解锁失败会把卡留下来当砖。issue #23(已关闭)记录某个 commit 在非冷卡上返回 -ETIMEDOUT,并且让这些卡之后无法再被 nvidia 驱动使用。
  • 同一版本不是对每张卡都行。issue #20 里,同一台机器上的两张 3090 用旧 commit 解锁失败(等 GSP HALTED、WPR2 结束检查、SEC2 PRI 报错),换一个 commit 才过。
  • 「脚本说成功」不等于「有收益」。issue #28 的 3080 Ti 20GB OEM:解锁完成、运行时读回选择器确实是满速,但 BF16 吞吐仍是半速。README 的 tested cards 表里就写着这一条。
  • 还有卡直接报 firmware load failed (-2)(issue #67,GA104 的 3060 Ti LHR)。
  • 打包集成的坑比内核本身还多:Debian 包没装 dracut drop-in 时,Ubuntu 25.10+ 永远拿不到 initramfs 集成(#54);Arch 包缺 dracut 早加载(#44);initramfs 构型里 HOOKS 含 systemd 时会把模块名写成 gpu_unlock?,结果模块永不加载(#29);如果 initramfs 里有 nvidia.ko,它会抢在 gpu_unlock 之前绑定,解锁直接失败(#11)。
  • 许可要自己看清楚:README 说内核模块与打包部分是 GPL-2.0-only,但我在 10 月 25 日实测 GitHub API 对该仓库的 license 字段返回的是 None(Ada 分叉才明确标 GPL-2.0)。要拿去做分发或商用,这个差异值得先问清楚,别只看 README。
  • 最后一条与代码无关但更硬:在数据中心里用 GeForce 卡本身就有合规与保修层面的问题,本文只列风险面,不构成任何建议。

总结#

这次解锁改的是指令发射速率这一个旋钮,而不是算力、显存或带宽。证据链也相当清晰:稠密 GEMM 与 prefill 涨 50%~60%(部分 GA10x 报告接近翻倍),INT8、标量 FP32 与显存带宽受限的 decode 一动不动,输出还能保持按位一致。

但引用数字前请把口径补齐:卡的型号与设备 ID、功耗上限、以及测的是寄存器还是端到端。同一个仓库的 README 里,最低的收益(+25%)和最高的收益(+98%)之间,差的不是技术,是那张卡当时被限在多少瓦。

至于要不要在自己的机器上装:它是内核模块 + initramfs 集成 + 冷启动要求,失败案例里有把卡留成砖的记录,而且集成坑几乎都在打包层而不是内核层。想试的话,用一台可以承受重装的机器,先跑一份带 INT8 对照组的基准,把解锁前的数字留档。

参考与致谢

  • 项目仓库与 README:https://github.com/Zagitta/gpu-unlock(本机 2026-10-25 抓取的 main 分支 README)
  • Ada 分叉:https://github.com/JustAResearcher/gpu-unlock-ada
  • 社区受控报告:该仓库 issue #15、#25、#35、#55、#60
  • 媒体转述:IT之家《解锁英伟达 RTX 30 系显卡张量核心:实际性能提升可达 60%》,2026-10-25 07:09(作者远洋)
  • 未在本文使用的第三方说法:「所有游戏提升 xx%」类结论目前没有可靠来源,故未采纳。

RTX 3090 的张量核心被解锁:一个内核模块赶在驱动绑定前,把 FP16/BF16 发射速率放开到满速

https://www.oferry.com/posts/a493/
作者
摆渡
发布于
许可协议
CC BY-NC-SA 4.0

除另有声明外,本文采用上述许可协议。转载或改编请注明作者与原文地址,并遵守许可条款。