What Happens When You Increase num_warps in Triton — 寄存器压力的实证调查

动机

在编写 Triton kernel 时,num_warps 是最常调节的编译参数之一。直觉上,更多的 warp 意味着更高的 occupancy,应该提升性能。但在某些场景下,增加 num_warps 反而会导致显著的性能回退——非单调的寄存器压力崩溃

本文通过一系列实验,系统调查了以下问题:

  1. num_warps=16 到底比 num_warps=2 慢多少?
  2. 变慢的物理机制是什么?是传统的 local memory spill,还是别的什么?
  3. 这个现象是 cherry-pick 的巧合,还是普遍存在的?
  4. Ampere 和 Blackwell 两代架构有何差异?
  5. 有没有办法故意触发真正的 STL/LDL spill?

所有代码和实验均在以下环境完成:

  • GPU: NVIDIA GeForce RTX 3080 (SM 8.6, Ampere) + RTX 5060 Ti (SM 12.0, Blackwell)
  • PyTorch: 2.11.0 + CUDA 13.0
  • Triton: 3.6.0
  • ptxas: Triton 捆绑版 (CUDA 2025)

代码全文见文末。

Read More

macOS NT QQ 聊天记录解密

背景

最近出于兴趣研究一下 macOS 上新版 NT QQ 的聊天记录存储机制。发现其使用了加密的 SQLite 数据库来存储消息数据。

环境信息

  • 系统: macOS
  • 软件: NT QQ (新版腾讯 QQ)
  • 数据库类型: SQLCipher (加密 SQLite)
  • 加密算法: AES-256 + HMAC-SHA1

数据定位

NT QQ 的用户数据主要存储位置:

1
~/Library/Application\ Support/QQ/

在这个目录下可以找到两个关键的数据库文件:

  1. nt_msg.clean.db - 主消息数据库(约 2.8GB)
  2. profile_info.db - 用户配置信息(约 9.8MB)

密钥获取

NT QQ 将解密密钥存储在一个文本文件中,通常命名为类似 @qqkey 的文件。通过查看该文件可以找到数据库的解密密码。

注意: 不同版本的 QQ 可能使用不同的密钥存储方式和位置。

解密过程

1. 导出 SQL Dump

使用 sqlite3 命令行工具配合正确的加密参数导出数据:

1
2
3
4
5
6
7
8
9
sqlcipher nt_msg.clean.db <<EOF
PRAGMA key = '<实际密码已隐去>'; # 从密钥文件获取的密码
PRAGMA kdf_iter = 4096; # QQ 使用的 KDF 迭代次数
PRAGMA cipher_hmac_algorithm = HMAC_SHA1;
PRAGMA cipher_page_size = 4096; # 默认页面大小
.output raw_dump.sql
.dump
.exit
EOF

2. 修复 SQL Dump

由于数据库可能处于事务状态,导出的 SQL 文件会以 ROLLBACK 结尾,需要替换为 COMMIT

1
cat raw_dump.sql | sed -e 's|^ROLLBACK;\( -- due to errors\)*$|COMMIT;|g' | sqlite3 nt_msg_fixed.db

3. 读取解密后的数据

1
sqlite3 nt_msg_fixed.db ".tables"

可以看到多个数据表,主要包括:

1
2
3
4
5
6
c2c_msg_table              # 私聊消息表
group_msg_table # 群聊消息表
dataline_msg_table # 长消息
recent_contact_v3_table # 最近联系人
c2c_temp_msg_table # 临时会话消息
discuss_msg_table # 讨论组消息

注意事项

  1. PRAGMA kdf_iter: NT QQ 使用的是 4096 次迭代,而非 SQLCipher 的默认值 4000。这点很重要,如果参数不对会导致解密失败。
  2. 事务处理: 导出的 SQL dump 可能包含未提交事务,需要手动修复。
  3. 数据完整性: 建议先将解密后的数据库备份再进行查询操作。

结语

通过以上步骤,就可以成功解密 NT QQ 的聊天记录并导出数据。整个过程中最重要的是获取正确的加密密钥和配置参数。这个研究过程让我对 Qt 应用的数据存储机制有了更深的理解。


免责声明: 本文仅用于技术学习和个人数据备份,请勿用于非法用途或侵犯他人隐私。

cuasmrl 部署实录:用 RL 优化 CUDA Kernel 指令调度

cuasmrl 部署实录:用 RL 优化 CUDA Kernel 指令调度

背景

cuasmrl 是一个基于 TritonCuAssembler 的研究项目,核心思路是:

用强化学习(PPO)对 Triton 编译生成的 SASS 指令进行指令级重排序,通过调整 memory instruction 的相对位置来隐藏 latency,提升 kernel 的实际吞吐。

整个 pipeline 大致是:

1
2
3
Triton JIT 编译 → cubin → CuAssembler 反汇编为 SASS
→ 静态分析 (stall count / 依赖图) → RL agent 重排指令
→ CuAssembler 重新汇编为 cubin → 加载执行 → 测量 TFLOPS → reward

最近在一台双卡服务器上成功复现了这套流程,记录一下踩过的坑。

Read More

实验室单卡GPU虚拟化:K3s + Z2JH + HAMi 实现多用户GPU共享

背景

实验室有一台 GPU 服务器(RTX 3080 + RTX 5060 Ti,共两张卡),之前多个人要用 GPU 只能排队——一个人独占整张卡,其他人等着。这种粗放模式显然效率太低,尤其是跑 Jupyter Notebook 做实验的时候,大部分时间 GPU 根本跑不满。

目标很明确:

  • 一台物理机,两张消费级 GPU
  • 多个用户同时使用 JupyterHub 做 PyTorch 开发
  • 每个用户分到 4GB 显存 + 25% GPU 算力
  • 一张卡最多切 5 个 vGPU,总共支撑 10 个用户并发
  • 能可视化监控 GPU 使用情况

最终效果:一个 Helm values 文件 + 一套自动化安装脚本,在 K3s 上把 Z2JH、HAMi、HAMi-WebUI 全栈跑起来。


架构总览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
┌──────────────────────────────────────────────────────────────┐
│ Ubuntu 24.04 LTS │
│ K3s (轻量 Kubernetes) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ HAMi — GPU 虚拟化中间件 │ │
│ │ ├─ hami-scheduler # 自定义 GPU 感知调度器 │ │
│ │ ├─ hami-device-plugin # 注册 vGPU 资源到 K8s │ │
│ │ └─ webhook # 自动注入 GPU 环境变量 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Z2JH (JupyterHub) │ │
│ │ ├─ Hub # 用户认证 & 管理 │ │
│ │ ├─ Proxy # NodePort │ │
│ │ └─ User Pods # PyTorch CUDA Notebook │ │
│ │ # 每个 Pod: 4GB显存 / 25%算力 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ HAMi-WebUI (魔改版) │ │
│ │ ├─ 前端: Vue.js / NodePort │ │
│ │ ├─ 后端: Go (Kratos) │ │
│ │ └─ Prometheus + DCGM-Exporter 监控 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ GPU: RTX 3080 + RTX 5060 Ti │
└──────────────────────────────────────────────────────────────┘

核心技术选型

K3s 替代标准 K8s

标准 K8s 太重了——kube-apiserver、etcd、controller-manager、scheduler 一堆组件,单机跑起来吃掉好几 GB 内存。我们是单节点场景,用 K3s 完美替代:

  • 二进制只有 ~70MB,内存占用不到 500MB
  • 自带 containerd、Flannel CNI
  • 完全兼容标准 K8s API,Helm 直接可用
  • 禁用了 traefik ingress,用 NodePort 直出端口更简单

HAMi 做 GPU 虚拟化

HAMi(异构算力管理中间件,原 vGPU-device-plugin)是开源的 GPU 共享调度方案,核心能力:

功能 说明
显存切分 一张物理 GPU 按 MB 级别切分,deviceSplitCount: 10 最多切 10 份
算力共享 deviceCoreSharing: 1,按比例分配 GPU SM 算力
调度器 自定义 scheduler + admission webhook,binpack 策略优先塞满同一张卡
多厂商 除了 NVIDIA,还支持华为 Ascend、海光 DCU、寒武纪 MLU 等

用户 Pod 只需声明三个资源:

1
2
3
4
5
resources:
limits:
nvidia.com/gpu: "1" # 1 个 vGPU
nvidia.com/gpumem: "4000" # 4 GB 显存
nvidia.com/gpucores: "25" # 25% 算力

HAMi webhook 会自动注入 NVIDIA 可见设备环境变量,scheduler 把 Pod 调度到有足够剩余资源的 GPU 切片上。

Z2JH 做用户界面

JupyterHub 是学术界最流行的多用户 Notebook 方案,Z2JH 是它的 Helm Chart。关键配置:

1
2
3
4
5
6
7
8
9
10
singleuser:
extraResource:
limits:
nvidia.com/gpumem: "4000" # 4 GB 显存
nvidia.com/gpucores: "25" # 25% 算力
extraPodConfig:
runtimeClassName: nvidia # 指定 NVIDIA runtime
scheduling:
userScheduler:
enabled: false # 用 HAMi 调度器

认证插件使用 Dummy Authenticator,适合实验室内网环境。


魔改 HAMi-WebUI

原生的 HAMi-WebUI 有个 bug:GPU 使用率永远显示为 0。

Bug 根因

HAMi scheduler 在 Pod annotation 里编码 GPU 分配信息时,是按所有容器(包括 init containers)的顺序写入的。但 WebUI 解码时只遍历了 pod.Spec.Containers,忽略了 init containers,导致索引错位——应用容器拿到的永远是 init container(block-cloud-metadata)的空设备记录。

1
2
3
4
5
Scheduler 写入 annotation:
[init-container设备(空)] ; [主容器设备(GPU-xxx,NVIDIA,4000,25)]

WebUI 解码 (修复前):
i=0 → pod.Spec.Containers[0] → 错误地取了 init-container 的空记录 ❌

修复方案

修了三个文件:

1. util.go — 解码时考虑 init containers

1
2
3
4
5
// 修复前:只算 Containers
totalContainers := len(pod.Spec.Containers)

// 修复后:InitContainers + Containers
totalContainers := len(pod.Spec.InitContainers) + len(pod.Spec.Containers)

2. pod.go — 分配 device 索引时跳过 init containers

1
2
3
4
5
6
7
8
9
// 修复前:直接用 i 作为索引,错位
c.ContainerDevices = bizContainerDevices[i]

// 修复后:加上 initContainer 的偏移量
initContainerCount := len(pod.Spec.InitContainers)
deviceIdx := initContainerCount + i
if deviceIdx < len(bizContainerDevices) {
c.ContainerDevices = bizContainerDevices[deviceIdx]
}

3. node.go — 添加 nil 防护

当 POST 请求不带 filters 时 req.Filters 为 nil,会触发 panic。加上判空:

1
2
3
4
if filters != nil {
if filters.Ip != "" && filters.Ip != nodeReply.Ip { continue }
// ...
}

修复后 WebUI 正确显示各 GPU 使用率、显存和算力占用。


部署流程

整套部署压缩到三个脚本里:

1
2
3
4
5
6
7
8
# 1. 安装 K3s + Helm + 部署 Z2JH
./install-z2jh.sh

# 2. 查看状态
./status-z2jh.sh

# 3. 卸载(含数据清理)
./uninstall-z2jh.sh

HAMi 和 HAMi-WebUI 通过 Helm 单独部署:

1
2
helm install hami HAMi/charts/hami -n kube-system
helm install hami-webui HAMi-WebUI/charts/hami-webui -n kube-system

访问方式

服务 地址 说明
JupyterHub http://<服务器IP>:31080 按配置认证登录
HAMi-WebUI http://<服务器IP>:32361 GPU 资源监控面板

资源分配示意

1
2
3
4
5
6
7
8
RTX 3080 (10GB)          RTX 5060 Ti (16GB)
┌─────────────────┐ ┌─────────────────┐
│ vGPU 1: 4GB 25% │ │ vGPU 6: 4GB 25% │
│ vGPU 2: 4GB 25% │ │ vGPU 7: 4GB 25% │
│ vGPU 3: 2GB 25% │ │ vGPU 8: 4GB 25% │
│ │ │ vGPU 9: 4GB 25% │
└─────────────────┘ └─────────────────┘
5 个 vGPU 切片 5 个 vGPU 切片

每个 JupyterHub 用户 Pod 自动分配到一个 vGPU 切片,跑 PyTorch 和独占一张卡体验完全一致——torch.cuda.is_available() 返回 true,nvidia-smi 能看到 GPU,只不过显存上限是 4GB。


总结

这套方案的核心价值:

  1. 消费级 GPU 也能虚拟化——不需要 Tesla/V100 这类数据中心卡
  2. 资源利用率大幅提升——从一人占整卡到十人共享
  3. 开箱即用——K3s 单机一条脚本拉起全栈
  4. 可观测——WebUI 实时看 GPU 占用,Prometheus 留存历史数据
  5. 易维护——Helm 统一管理,卸载一条命令清理干净

适合实验室、小团队、教学机房等预算有限但需要多用户 GPU 环境的场景。

AMD uProf 性能分析:矩阵乘法优化之旅

背景

CS210 课程的第二次作业要求使用性能分析工具对矩阵乘法程序进行 profiling。由于使用的是 AMD CPU,我选择了 AMD uProf 的 data_access 模式来分析 L1/DTLB 性能变化。

环境

  • CPU: AMD (Family 0x17, Model 0x71, 24 核)
  • OS: Linux Ubuntu 25.04, Kernel 6.14.0-37-generic
  • 编译器: GCC 14.2.0, -g -O3 -march=native
  • 分析工具: AMD uProf 5.3.518 (data_access config)
  • 矩阵大小: 2048×2048

测试用例

使用 Intel oneAPI 的 matrix_multiply_c 示例,包含 5 个优化版本:

版本 函数 优化策略
0 multiply0 基础串行 (i-j-k 循环顺序)
1 multiply1 多线程 (pthreads, 同 i-j-k 顺序)
2 multiply2 循环交换 (i-k-j 顺序)
3 multiply3 循环交换 + 向量化提示 (#pragma ivdep)
4 multiply4 Cache blocking (64 元素块) + 循环展开

编译与分析

每个版本独立编译并使用 AMDuProfCLI 进行数据访问分析:

1
2
3
4
5
6
7
8
9
10
# 编译
gcc -g -O3 -march=native -DUSE_THR -c src/multiply.c -D_LINUX
gcc -g -O3 -march=native -DUSE_THR src/*.c -o matrix -lpthread -lm

# 使用 data_access 模式进行 profiling
AMDuProfCLI collect --config data_access -o profile_multiply0/multiply0-da ./matrix

# 生成 CSV 报告
AMDuProfCLI report -i profile_multiply0/multiply0-da \
--report-output profile_multiply0/multiply0-da-report.csv

结果

性能对比

版本 执行时间 (s) MFLOPS 加速比
multiply0 127.78 134
multiply1 6.89 2,495 18.5×
multiply2 0.77 22,320 166×
multiply3 0.73 23,605 175×
multiply4 0.15 112,524 836×

L1 数据缓存分析

版本 CPI L1 DC Miss Ratio L1 DTLB Miss Rate
multiply0 6.26 16.9% 0.106
multiply1 5.32 26.5% 0.112
multiply2 3.13 26.5% 0.001
multiply3 3.08 24.6% 0.001
multiply4 0.82 23.3% 0.019

关键发现

1. 循环交换是最关键的优化

原始代码中,内层循环访问 b[k][j],其中 k 变化时会跨越整行内存(步长为 2048 个 double = 16KB)。这导致:

  • 大量 L1 缓存未命中
  • 高 DTLB 未命中率(跨页访问)

循环交换为 i-k-j 顺序后,b[k][j] 变为连续内存访问,DTLB 未命中率从 0.106 降至 0.001。

2. Cache Blocking 在大矩阵下效果显著

当矩阵大小为 2048×2048 时(每个矩阵 32MB,共 96MB),工作集远超 L1 缓存但可放入 L2。Cache blocking 将计算限制在 64×64 的块内,使工作集完全驻留在 L2 中:

  • CPI 从 6.26 降至 0.82
  • DRAM refill 从 6.21% 降至 0.01%

3. 多线程不能解决缓存问题

multiply1 虽然通过 16 线程获得了 18.5× 加速,但 L1 DC miss ratio 反而从 16.9% 恶化到 26.5%,因为多线程间的缓存竞争加剧了问题。

AMD Zen3 架构启示

在 AMD Zen3 架构上,矩阵乘法的性能瓶颈主要是:

  1. L1 数据缓存未命中 - 由非连续内存访问模式引起
  2. DTLB 未命中 - 跨页访问导致 TLB 压力
  3. CPI 过高 - 内存停顿导致流水线空闲

最有效的优化策略是 Cache blocking + 循环交换,将工作集限制在 L2 缓存内,同时保证块内访问的连续性。

上海科技大学校园网 OpenWrt 配置指南

前言

上海科技大学(ShanghaiTech)的校园网采用 Web Portal 认证方式,设备需要先通过浏览器登录才能上网。对于希望使用 OpenWrt 路由器的用户来说,认证流程和 IPv6 配置都有不少坑。本文记录了完整的配置过程,包括:

  • 校园网 Web 认证的解决方案(MAC 地址克隆)
  • IPv6 配置(ISP 不下发前缀委派的处理)
  • OpenClash Fake-IP 模式下的 DNS 问题

Read More

Luckfox Pico Mini B/M开发板SD卡刷写与入门指南

Luckfox Pico Mini B/M是一款基于瑞芯微RV1103芯片的迷你Linux开发板,售价约$9,集成64MB DDR2内存、单核ARM Cortex-A7处理器和0.5TOPS NPU。由于其仅有128MB SPI Flash,日常开发推荐使用SD卡启动。本文记录从SD卡刷写到USB调试连接的完整过程。

硬件规格概览

项目 规格
处理器 ARM Cortex-A7 @ 1.2GHz + RISC-V
NPU 0.5TOPS,支持int4/int8/int16
内存 64MB DDR2
存储 128MB SPI Flash(板载)/ SD卡槽
USB USB 2.0 Host/Device
相机接口 MIPI CSI 2-lane
GPIO 17路GPIO

系统镜像获取

官方预编译镜像

Luckfox官方提供Ubuntu和Buildroot的预编译SD卡镜像:

下载链接: https://drive.google.com/drive/folders/14kFWY93MZ4Zga4ke2PVQgUs1y9xcMG0S

常用镜像包括:

  • Ubuntu_Luckfox_Pico_Mini_B_MicroSD_250313.zip - Ubuntu 22.04系统
  • Buildroot镜像(需自行从SDK编译或使用社区提供版本)

其他可用系统

系统 内存占用 适用场景
Buildroot 嵌入式最小化系统
Ubuntu 22.04 中等至高 全功能开发环境
Foxbuntu 中等 低功耗网络应用(Meshtastic、LoRa)
Alpine Linux 极低 安全导向极简系统

SD卡刷写方法

准备工作

  1. 下载镜像压缩包并解压到工作目录

  2. 确认SD卡设备路径,在Linux中运行:

    1
    $ lsblk

    插入SD卡后找到对应设备,通常为/dev/sdb或/dev/sdc

  3. 获取刷写工具 blkenvflash.py(官方镜像压缩包内已包含)

使用blkenvflash.py工具刷写

此工具专用于将多个.img分区文件按正确偏移量写入SD卡:

1
2
$ cd Ubuntu_Luckfox_Pico_Mini_B_MicroSD_250313
$ sudo python3 blkenvflash.py /dev/sdb

输出示例:

1
2
3
4
5
6
7
8
9
10
== blkenvflash.py 0.0.1 ==
writing to /dev/sdb
mmcblk1: env.img size:32,768/32K (offset:0/0B) imgsize:32,768 (32K)
mmcblk1: idblock.img size:524,288/512K (offset:32,768/32K) imgsize:188,416 (184K)
mmcblk1: uboot.img size:262,144/256K (offset:0/0B) imgsize:262,144 (256K)
mmcblk1: boot.img size:33,554,432/32M (offset:0/0B) imgsize:3,150,336 (3,150,336B)
mmcblk1: oem.img size:536,870,912/512M (offset:0/0B) imgsize:113,909,760 (111,240K)
mmcblk1: userdata.img size:268,435,456/256M (offset:0/0B) imgsize:9,999,360 (9,765K)
mmcblk1: rootfs.img size:6,442,450,944/6G (offset:0/0B) imgsize:1,136,914,432 (1,110,268K)
done.

⚠️ 重要提示:

  • 参数必须是SD卡设备路径(如/dev/sdb)而非分区(如/dev/sdb1)
  • 大文件刷写耗时较长,请耐心等待,进程卡住是正常的
  • 必须从包含所有.img文件的目录运行此脚本
  • SDK编译生成的镜像同样适用此工具刷写

SD卡分区结构说明

文件 大小 描述
idblock.img 184KB Rockchip Bootloader识别块
download.bin 263KB Rockchip专用底层引导程序
uboot.img 256KB U-Boot主引导加载程序
env.img 32KB U-Boot环境配置
boot.img 3.4MB Linux内核+设备树
rootfs.img 195MB 根文件系统
oem.img 43MB OEM厂商数据分区
userdata.img 9.6MB 用户数据分区

首次启动与连接

启动优先級

  • 有SD卡插入:从SD卡启动(推荐)
  • 无SD卡:从板载128MB SPI Flash启动

💡 实测性能:SD卡读取速度约21.87 MB/s,SPI Flash仅4.40 MB/s,强烈建议使用SD卡

USB RNDIS网络连接

Pico通过USB提供RNDIS网络接口实现调试连接:

1
2
3
4
# 插入USB后查看新接口
$ ifconfig
enxae935ce313b2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.32.0.100 netmask 255.255.0.0 ...

配置步骤:

  1. 将USB RNDIS接口IP设置为172.32.0.100/16(与Pico同一网段)
  2. Pico默认IP为172.32.0.70(Ubuntu镜像)或172.32.0.93
  3. 如需上网,在主机上开启网络共享给该接口
  4. 测试连接:
    1
    $ ping 172.32.0.70

SSH登录与文件传输

1
2
3
4
5
# SSH登录(Ubuntu镜像默认用户 pico)
$ ssh pico@172.32.0.70

# SFTP方式传输文件
$ scp file.txt pico@172.32.0.70:/home/pico/

ADB调试连接

1
2
$ adb device
$ adb shell

配置好了权限自动会连接。

参考链接: