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 应用的数据存储机制有了更深的理解。


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

双 V100 从源码部署 1Cat-vLLM:CUDA 12.8 + sm_70 踩坑与实测

手上有台双 V100(SXM2 16GB × 2)的机器,想跑 1Cat-vLLM——一个专门针对 SM70 / Volta 优化过的 vLLM 分支。它推荐的环境是 Python 3.12 + CUDA 12.8 + PyTorch 2.10,看起来照着装就行。

结果真正耗掉时间的,全是跟这个项目本身无关的环境问题:glibc 2.41 把 CUDA 12.8 的编译器识别直接干掉了、git 不认 NFS 挂载的属主、这台 NAS 上根本装不了 venv。三个坑都绕过去之后,编译 22 分钟,跑通。

这篇记录三件事:怎么装起来的、卡在哪儿、以及互联和编译的实测数字。

Read More

把 RX 7900 XTX 喂饱:不同精度、不同模型尺寸下的算存比实测

手里有一张 RX 7900 XTX(24 GiB / gfx1100 / RDNA3)。它能跑多大的模型、该用哪种量化、并发开到几才划算——这些问题最后都会收束到同一个量上:算存比(arithmetic intensity, AI)。

这篇文章把一天半里在 7900 XTX 上做的一批推理实验串成一根线:llama.cpp 与 vLLM 两套引擎、FP32 / FP16 / BF16 / INT8 / INT4 / FP8 六种精度、0.5B / 4B / 7B 三种模型尺寸、并发 N=1→16 的扫描,全部对着理论 roofline 校准。

一句话结论:这张卡在 7B 量级上,单请求 decode 能跑到实测 HBM 读带宽的约 91%;但即使是 N=16 的并发,离算力受限区仍然差 2~8 倍(取决于用 HBM 还是 IF 口径算 ridge)。 以及两个反直觉的点——算存比本身并不随模型变大而升高,变的是带宽利用率;还有 96 MiB Infinity Cache 让这张卡的带宽屋顶实际上有两档,笼统地按 960 GB/s 算会系统性低估小算子。

Read More

本地 27B Q4 + opencode 全程驱动:hami-learning-cloud 教学云更新实录(附模型自评)

五月的《实验室单卡GPU虚拟化:K3s + Z2JH + HAMi》把”K3s + Z2JH + HAMi”的骨架跑通了。这次我们把骨架长成了一个能用的教学云 hami-learning-cloud。

比内容更重要的是:这次更新的全部内容——代码审计、功能修复、镜像构建、部署验证、git 历史清洗——完全由本地部署的 Qwen3.8-27B(Q4_K_M 量化,2×RTX 3080 张量并行)+ opencode 完成,全程没有调用任何云模型。 文末让这个模型对自己的表现做了一次自评。

Read More

纸上得来终觉浅,绝知此事要躬行——llama.cpp 部署调优实录

纸上得来终觉浅,绝知此事要躬行。
——陆游《冬夜读书示子聿》

没有调查,就没有发言权。
——毛泽东《反对本本主义》


一、背景

我们用 2 张 RTX 3080 20G 跑 Qwen3.8-27B-GGUF(Q4_K_M 量化),Tensor Parallel 模式(-sm tensor),给 agent 提供推理服务。

网上关于 llama.cpp 的调优文章汗牛充栋,KV Cache 量化、Flash Attention、多 slot 并发——每个参数都被人写过”最佳实践”。但别人的最佳实践,放到你的硬件、你的模型、你的 workload 上,是不是还是最佳? 不试一下,谁也不知道。

于是我们决定:逐个参数实测,用数据说话。

二、初始配置:裸跑

最开始的启动脚本很简单:

1
2
3
4
5
6
llama-server \
-m Qwen3.8-27B-Q4_K_M.gguf \
-ngl 99 -sm tensor \
-c 262144 --parallel 1 \
--spec-type draft-mtp --spec-draft-n-max 2 \
--host 0.0.0.0 --port 8080
  • 没开 Flash Attention(-fa 默认 auto)
  • 没开 KV Cache 量化(-ctk/-ctv 默认 f16)
  • 单 slot(--parallel 1)

实测数据:

  • Prefill:~960 tok/s(大 prompt)
  • Generation:~60-70 t/s
  • 显存:17.3G / 20G(GPU 0),15.8G / 20G(GPU 1)

看起来还行?但问题出在多 agent 并发时——单 slot 意味着所有请求排队,一个 agent 在思考,其他 agent 只能干等。

三、第一刀:开 Flash Attention

网上都说 Flash Attention “无脑开就好”。真的吗?

1
llama-server ... -fa on

实测数据(单 slot):

  • Prefill:~967 tok/s(基本持平)
  • Generation:~60 t/s(基本持平)

结论:Flash Attention 在 RTX 3080 上对 prefill 和 generation 速度几乎没有提升。

为什么?因为 Flash Attention 的核心优势是降低 peak memory,而不是加速计算。在 20G 显存的卡上跑 Q4 量化模型,KV Cache 本身就不大(f16 下 262K context 约 2-4G),FA 省下来的那点内存,并没有转化为速度提升。

真正的价值: 如果你要跑更长的 context(比如512K+),FA 能防止 OOM。但在我们的场景下,它更像是一个”保险”,而不是”加速”。

四、第二刀:KV Cache 量化

这是最核心的调优。f16 的 KV Cache 每个元素 2 字节,Q8 量化后只要 1 字节,理论上 KV Cache 显存占用减半。

4.1 尝试 -ctk q8_0 -ctv q8_0

1
llama-server ... -fa on -ctk q8_0 -ctv q8_0

成功启动。 实测:

  • 显存占用变化不大(因为262K context 的 KV Cache 本身就不大)
  • Cache 命中率正常(90%+)
  • 无异常换入换出

4.2 尝试 -ctv q4_0(更激进的量化)

理论上 q4 比 q8 再省一半,我们试了:

1
llama-server ... -ctk q8_0 -ctv q4_0

崩了。 报错:

1
GGML_ASSERT(ret.axis != GGML_BACKEND_SPLIT_AXIS_UNKNOWN) failed

原因:q4_0 量化格式不支持 Tensor Parallel 的跨 GPU 分片。 GGML 后端无法将 q4 的 KV tensor 拆分到两张卡上。

4.3 尝试 -ctv q4_1

1
llama-server ... -ctk q8_0 -ctv q4_1

也崩了。 同样的错误。q4_1 也不支持 TP 分片。

结论:在 -sm tensor 模式下,KV Cache 量化的最低安全选项是 q8_0。q4_0/q4_1 虽然省内存,但和 TP 不兼容。

这是一个典型的”纸上得来”的坑——很多文章推荐 q4 量化 KV Cache,但它们跑的是单卡模式。多卡 TP 下,量化选项受限。

五、第三刀:多 Slot 并发

单 slot 排队太慢,我们加了 --parallel 4:

1
llama-server ... --parallel 4 --kv-unified

问题出现了: 两个 agent 同时发大 prompt(109K + 76K tokens),4 个 slot 的统一 KV Cache 开始频繁淘汰:

1
2
W srv alloc: making room for prompt cache entry, removing oldest entry (size = 599 MiB)
W srv alloc: making room for prompt cache entry, removing oldest entry (size = 2119 MiB)

Cache 命中率暴跌到 0%——每次请求都要重新 prefill 全部 tokens,因为之前的缓存被挤掉了。

这是”没有调查就没有发言权”的经典案例: 文档说 --parallel 是”总 context 除以 slot 数”,但没人告诉你:如果两个 agent 同时发大 prompt,总 KV Cache 不够用时会发生什么。答案是:互相挤兑,全部重算。

解决方案

降为 --parallel 2,减少并发 slot 数,给每个 agent 留足 KV Cache 空间:

1
2
llama-server ... --parallel 2 --kv-unified \
-ctk q8_0 -ctv q8_0 -fa on

实测数据:

  • Cache 命中率:98-99% ✅
  • Cache 淘汰:0 次 ✅
  • Generation:~61 t/s(单 slot 独占时)
  • 两个 slot 同时活跃时:~35 t/s(合理,显存带宽被瓜分)

六、最终配置与经验总结

最终启动脚本

1
2
3
4
5
6
7
8
9
10
11
llama-server \
-m Qwen3.8-27B-Q4_K_M.gguf \
--mmproj mmproj-BF16.gguf \
-ngl 99 -sm tensor \
-c 262144 --parallel 2 \
--spec-type draft-mtp --spec-draft-n-max 2 \
-fa on \
-ctk q8_0 -ctv q8_0 \
--image-min-tokens 1024 \
--kv-unified \
--host 0.0.0.0 --port 8080

参数调优对照表

参数 我们的尝试 结论
-fa on ✅ 开启 对速度无明显提升,但防止长 context OOM,建议开着
-ctk q8_0 ✅ 开启 KV Cache K 用 Q8,省 50% 显存,推荐
-ctv q8_0 ✅ 开启 KV Cache V 用 Q8,和 TP 兼容的最低选项
-ctv q4_0/q4_1 ❌ 崩溃 与 -sm tensor 不兼容,多卡勿用
--parallel 4 ⚠️ 挤兑 4 slot 在大 prompt 下互相淘汰,命中率暴跌
--parallel 2 ✅ 稳定 2 slot 是我们的最佳平衡点
--kv-unified ✅ 开启 统一 KV Cache 池,比 per-slot 更灵活

踩坑记录

  1. -fa 不需要显式 on? 错。-fa 单独用会把下一个参数当值解析(-fa -ctk 会把 -ctk 当成 flash-attn 的值),必须写 -fa on。

  2. --prompt-cache-all 不存在? 对。这个参数在某些文章里出现过,但我们的 build 版本没有。实际的 prompt caching 是 --cache-prompt,默认就是开启的。

  3. q4 量化 KV Cache 能省一半显存? 在单卡上可以。在多卡 TP 模式下不行,GGML 后端不支持对 q4 tensor 做跨 GPU 分片。

  4. 多 slot 就是好? 不一定。slot 越多,KV Cache 池被瓜分越厉害。大 prompt 场景下,少 slot 反而更稳。

七、方法论:没有调查就没有发言权

这次调优最大的收获不是参数配置,而是方法论:

1. 不要迷信”最佳实践”

每篇文章都说”开 Flash Attention”、”用 Q4 量化 KV Cache”。但在我们的硬件(2x RTX 3080)和模式(TP)下,这些”最佳实践”要么无效,要么直接崩溃。别人的结论,要在你自己的环境里验证。

2. 用数据说话,不要靠感觉

“感觉”开了 FA 会快一点,”感觉”q4 比 q8 好。但实测数据告诉你:FA 对速度几乎没影响,q4 在 TP 下根本跑不了。没有数据支撑的优化,就是自欺欺人。

3. 监控比调参更重要

调完参数不是结束,而是开始。我们用 /slots API 监控 cache 命中率、用 nvidia-smi 看显存、用 log 看淘汰事件。不监控的优化,等于盲人摸象。

4. 逐步调整,不要一次改太多

我们先加 FA,测一轮;再加 Q8,测一轮;再改 parallel,测一轮。如果一次改三个参数,出了问题你都不知道是哪个参数的锅。控制变量,逐步推进。

八、结语

纸上得来终觉浅,绝知此事要躬行。

写代码如此,调参数亦如此。llama.cpp 的每个参数背后都有复杂的行为,文档和文章只能给你一个起点,真正的理解必须来自你自己的实测。

这次调优从”裸跑”到”FA + Q8 + 2 slot”,每一步都踩过坑、看过数据、做过判断。最终的配置不一定是最优的,但它是我们调查过、验证过、理解过的。

这,才是”躬行”的意义。


本文记录了 2026 年 8 月在 2x RTX 3080 20G 上部署 Qwen3.8-27B 的 llama.cpp 调优过程。硬件、模型、workload 不同,结论可能不同。请以实测为准。

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

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

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

背景

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

用强化学习(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