turbovec 2-bit 搜索性能爬山优化(Hill-Climb):目标度量、三道闸门与验证方法论
2026/9/13 22:43:18 网站建设 项目流程

turbovec 2-bit 搜索性能爬山优化(Hill-Climb):目标度量、三道闸门与验证方法论

【免费下载链接】turbovecA vector index built on TurboQuant, written in Rust with Python bindings项目地址: https://gitcode.com/GitHub_Trending/tu/turbovec

导读

本文以 GOAL_2bit.md 为骨架,系统讲解 turbovec 在2-bit 量化搜索bit_width=2)上的性能爬山(hill-climb)优化目标定义:8 个性能单元的调和均值评分、胜利判定条件、三道验证闸门(位级一致性、全量测试、边界扫描)、停止规则与配套工具链。结合仓库内benchmarks/hillclimb/下的评分器与闸门脚本源码,以及 LOG_2bit.md 中 5 个胜场与 30 余个被证伪假设的实测记录,读者可以完整复现这套"以测量为权威"的性能优化流程,并将其方法论迁移到其他性能敏感型项目。


一、目标定义:8 个性能单元与调和均值

GOAL_2bit.md开篇即为这次爬山优化定义了可判定的目标:

让 2-bit 搜索更快。评分 =bit_width=2时 8 个 per-cell 加速比的调和均值(harmonic mean)——{arm, x86} x {ST, MT} x {nq=1, nq=100}k=10N=200kdim=768,等权重,对照锁定在 climb HEAD 的基线。

1.1 单元矩阵的结构

维度取值含义
架构arm/x86两套真实硬件(见第二节"测试机")
线程模式ST/MT单线程与多线程
批大小nq=1/nq=100单查询与批量查询
固定量k=10N=200kdim=768近邻数、向量规模、维度

关键设计是nq=1 与 nq=100 是两个不同的内核形态,而非同一内核的缩放:批量内核(batched kernel)会在整批查询间摊薄 nibble 解包(unpack)成本,而 nq=1 没有任何可摊薄的对象。cells_2bit.py 的文档字符串明确写道:"A result at one width says nothing about the other"——一个宽度下的结论不能外推到另一个宽度。

1.2 为什么用调和均值而不是算术均值

score_cells.py 的模块注释给出了两层理由:

  • 调和均值(HM)而非算术均值:一个回退的 cell 会贡献很大的1/s,把整体分数拖下去,而不是被另外七个胜利平均掉——HM 天然惩罚"七个赢一个输"。
  • 用加速比(speedup)而非原始耗时:8 个 cell 的耗时横跨约 0.27 ms 到 149 ms(nq1_mt_armnq100_st_arm),对原始时间取均值会被最小的 cell 主导,失去等权重语义。

whm_2bit.py——整个爬山评分的唯一判定权威——用命名常量实现这套判定:

WIN_HM = 1.01 # 胜利:HM > 1.01 CELL_FLOOR = 0.99 # 任何 cell 不得低于 0.99 ARCHES = ("arm", "x86") CELLS = ("nq1_st", "nq1_mt", "nq100_st", "nq100_mt")

判定式即ok = hm > WIN_HM and sp[worst] >= CELL_FLOOR,并同时输出 arm / x86 各自的 4-cell HM,避免把单架构结果包装成双架构结果。


二、测试机、基线锁定与两个结构性事实

LOG_2bit.md记录了这次爬山的两台基准机:

  • armturbovec-bench-arm-search(GCP c4a,Axion,8 物理核,Neoverse V2 内核)。
  • x86turbovec-bench-search(GCP c3,Sapphire Rapids,4 核 8 线程,AVX-512)。

基线被锁定在climb HEAD = 262793f,每 cell 三个交错轮次取中位数。后续协议演进为预构建.so文件的平衡 ABBA/BAAB 交换——一次.so交换只需毫秒级,而一次全量重建约需 15 分钟,这让多轮平衡对比变得可负担(H9 协议修复)。

基线中位数(ms,来自LOG_2bit.md"Baseline" 一节):

cellarmx86
nq1_st1.9951.727
nq1_mt0.3060.487
nq100_st148.99183.086
nq100_mt18.42525.491

基线阶段就确立了两个结构性事实:

  1. 两个架构在 2-bit 上相距甚远:arm ST 在 nq=100 上比 x86 ST 慢 1.77 倍,而 arm MT 反而比 x86 MT 快 1.41 倍。
  2. 线程扩展差异悬殊:arm 8.07x(nq=100)对 x86 3.23x。P2 探针查明原因:c3-standard-8 只有4 个物理核、每核 2 线程,SMT 对端口受限(port-bound)的扫描几乎没有增益(4→8 线程仅 x1.04),而 arm 的 8.07x 来自 8 个真实核。两个架构的 MT 数字从来不可直接比较,也不是调度问题能弥合的。

三、三道闸门:一个候选如何才算赢

胜利 = HM > x1.01 且没有任何 cell 回退。闸门:分数、id 与平局顺序位级一致;cargo test -p turbovec全绿;nq 扫描(1..16, 32, 64)与 N 扫描(1k, 8k, 32k, 200k)中没有任何点回退超过 3%。

3.1 闸门一:位级一致性(bitwise parity)

parity_2bit.py 是正确性神谕。分数以原始 float 位做 SHA-256 哈希(而非带容差比较),id 序列按顺序哈希——日志记录过一个变体因调换两个 id 的顺序即被摘要e7e507e回退。平局是近似检查最容易漏掉的场景,因此夹具(fixture)刻意把最后 2,000 行重复为前 2,000 行DUPES),任何平局组内的重排都会改变摘要。

# 钉住参考摘要(在 arm / x86 机上分别执行) python parity_2bit.py --pin parity_base_arm.json python parity_2bit.py --pin parity_base_x86.json # 候选构建后核对(exit 1 表示漂移) python parity_2bit.py --check parity_base_arm.json

分数对精度敏感,通过牺牲精度换取速度是很容易的作弊路径,parity 闸门正是让加速比有意义的防线。日志中它多次抓到代码评审漏掉的真实错误:H19 的 GFNI 仿射 nibble 拆分因kpos常量推断错误(真实值set1_epi32(0x30201000),是逐字节斜坡而非常量 0x40),分数偏离约 3 倍、摘要aab9b863,被 parity 闸门当场拦下——"parity gate catches what code review missed, again"。

3.2 闸门二:测试套件全绿

每个候选必须通过:

cargo test -p turbovec

在 aarch64 上为 30 个测试套件(约 194 个测试),并通常附带cargo check --target x86_64-unknown-linux-gnu交叉检查。

3.3 闸门三:nq / N 边界扫描

sweep_2bit.py 实现边界扫描:nq 点1..16, 32, 64(21 个)+ N 点1k, 8k, 32k, 200k(4 个),MT/ST 双模式,共 88 个点:

python sweep_2bit.py --bits 2 --out sweep_cand.json

扫描的动机(sweep_2bit.py文档字符串):8 个目标 cell 只采样了两个查询宽度和一个索引规模,两个采样点之间可能藏着悬崖(cliff)——4-bit 登山曾被 P27 的小 N 并行度塌缩和 H90 的 nq=10 惨案各咬过一次。任何移动调度边界(batch 宽度、tile 下限、并行度闸门、arm 选择)的变更,只会在这里显现。输出按查询归一化的毫秒数,让悬崖以悬崖的形态呈现而非斜率为斜率。小且吵的点(nq <= 8n <= 32k)用 9 个子运行与 5 倍迭代,其余用 5 个子运行与 2 倍迭代。

重要的方法论转折(P4):探针证明这台测试机对无操作(no-op)构建都无法通过 3% 的逐点闸门——同一二进制自我对比时,88 点中最差达 x0.8199,23/88 的点无任何代码变更就超 3%。owner 随后裁定:逐点 sweep 下限从胜利条件中移除,sweep 降级为参考信息whm_2bit.pySWEEP_FLOOR = 0.97被保留但不再否决候选。裁定原则被日志原文强调:"放宽闸门来容纳自己的候选,就是爬山开始测量自身偏好的开始"——因此修改闸门是 owner 的职权,不是爬山者的。


四、4-bit 观测运行:记录但不设闸

4-bit 不设任何闸门。每次胜利时测量并记录它;绝不为 4-bit 放弃任何 2-bit 胜利。

cells_2bit.py 通过--bits 4产生 4-bit 观测运行,其结果只记录、从不参与判定whm_2bit.py--obs4-*参数打印"4-bit observation (recorded, never gated)")。

观测的价值在 P1 探针中充分体现。同机同构建的跨宽度对比:

cell2-bit4-bit4bit/2bit
arm nq1_st1.9333.712x1.920
arm nq100_st148.77099.557x0.669
x86 nq1_st1.6693.270x1.959
x86 nq100_st83.95865.750x0.783

两个结论:

  • nq=1 上 2-bit 比 4-bit 快约 2 倍——几乎精确贴合字节比(2-bit 代码字节是 4-bit 的一半),这是 4-bit 时代 P42 发现的"内存受限"签名被原样继承。
  • nq=100 上 2-bit 反而比 4-bit 慢 1.3–1.5 倍——尽管字节数减半。原因在代码路径:4-bit 在 nq=100 走 permute-dot/vm8 点积内核族(成本不随 nq 线性增长),而 2-bit 走 per-query TBL 经典内核,成本随 NQ 线性增长。"一半的内存流量,以指令数连本带利还了回去。"

这直接决定了整个登山的战略:nq=100 的 cell 是目标,nq=1 的 cell 已接近带宽上限、应当防守而非攻击。


五、停止规则与假设记录纪律

每个假设都必须连同测量与判定被记录,无论输赢。连续 20 个 non-win 时结束;一个 win 重置计数。

GOAL_2bit.md原文为"Done at 20 consecutive non-wins; a win resets the count",日志实际执行中计数窗口一度变为 25(LOG_2bit.md中多次出现 "non-win 1/25")。这条规则保证爬山不会无限空转,也让失败假设的机制价值被完整留存。

纪律的一个标志性实例:H7 首次尝试的区块头部曾写 "WIN",而权威脚本打印 "not a win"——因为arm nq1_mt读 x0.9938,字节相同的二进制(arm 侧改动只有注释)本不该移动。此后目标改为"脚本是唯一权威,散文永远不是",并在 capstone 中严格执行:一个 cell 读 x0.9991 时,尽管中位数/均值估计器都说候选领先,判定仍为 NOT A WIN——"在看过判定结果后切换估计器,正是让基准失效的做法"。


六、评分与判定工具链:可复现的工作流

6.1cells_2bit.py— 八单元测量器

python cells_2bit.py --bits 2 --reps 15 --out cand.json

要点(均来自源码文档字符串):

  • 用种子(seed 0)构建索引cells_{n}_{bits}bit.tvim并缓存到~/.cache/turbovec-hillclimb/——每个 box、每个候选都评分同一份数据
  • 每个 cell 独立进程测量:ST 设RAYON_NUM_THREADS=1,MT 设为os.cpu_count();索引加载后只测搜索内核本身(无加载、无进程启动开销),使内核改动以全振幅显现。
  • 每个 cell 9 个子运行取 min,而非中位数。原因(P16/P21 的实测):x86nq100_st单个进程内部就呈双峰(迭代落在约 82 或 98 ms),中位数会随机选中一个模式;min选择未受扰动的模式——"也就是内核改动会移动的那个模式"。9 次抽取把快模式的出现率从 3 次时的 ~70% 提升到 ~96%。
  • 所有原始样本保留在 JSON 的raw字段,modes()自动检测任何聚类成两簇的 cell 并上报到modes字段——P16 首次发现的"双峰"问题从此由工具自动报告,而非依赖人眼。
  • nq=1 的 cell 使用reps * 5次迭代,因为更小、更吵。

6.2whm_2bit.py— 唯一判定权威

python whm_2bit.py \ --base base_arm.json base_x86.json \ --cand cand_arm.json cand_x86.json \ --sweep-base sb_arm.json sb_x86.json --sweep-cand sc_arm.json sc_x86.json \ --obs4-base ob_arm.json ob_x86.json --obs4-cand oc_arm.json oc_x86.json

输出每 cell 的 arm/x86 加速比、两个 4-cell HM、8-cell HM 与最差 cell,最后一行VERDICT: WIN / NOT A WIN;仅当HM > 1.01且最差 cell ≥ 0.99 时退出码为 0(--sweep-*--obs4-*均为可选、参考性输入)。

仪器校正故事(日志最深刻的教训之一):脚本曾经写ok = hm > WIN and worst >= 1.0——一个字面量1.0,比目标书写的 x0.99 下限整整严一格,且没有任何命名常量。capstone 的最差 cell x0.9991 恰好落在这一格差上,导致一次 NOT A WIN。修正为命名常量CELL_FLOOR = 0.99后,同一份数据变为 WIN。日志结论:"一个从未与其规格对照过的权威,就不是权威——把某物命名为权威,恰恰让人停止阅读它。"修正的合法性仅在于它让脚本趋近书面目标而非背离:若差异方向相反,规则要求保持 NOT A WIN 不变。

6.3sweep_2bit.py— 边界闸门测量器

python sweep_2bit.py --bits 2 --reps 5 --out sweep_cand.json

nq 扫描1..16, 32, 64、N 扫描1k, 8k, 32k, 200k;输出按查询归一化的毫秒;子运行数与迭代数随点的噪声规模缩放(nq <= 8n <= 32k用 9 子运行、5 倍迭代)。


七、配套测量仪器:为这轮爬山专门构建的探针与基准

LOG_2bit.md记录了大量专门构建的仪器(均在benchmarks/hillclimb/下),它们是"测量优先"哲学的落地:

仪器用途与关键结论
mem_rates.c持续顺序读带宽(单/多线程),时钟在运行内推导。实测 arm 单核 33.1 GB/s 供应上限;验证 THP(匿名 vs file-backed)比值 1.00——"不是墙,是巧合"
isa_rates.c扫描内核依赖的 17 条指令实测速率表(tbl1 寄存器4.01/cyushr2.00/cyucvtf1.00/cysdot2.00、smmla2.00 等);曾被自身审计出 4 行乐观错误(P25),原因是单趟冷启动——现每行同时打印最快/最慢趟并标记分歧 >5% 的行
scan_probe.c独立转录的扫描循环消融工具(每变体两秒出结果);P30 证明其噪声带约 20%,无法分辨 <2% 的效应
probe_2bit_sdot.rsP5:arm 上 LUT / expand+SDOT / expand+SMMLA 三种公式化的实测定价(G(q.dim)/s)——LUT 每宽度全胜
probe_2bit_vnni.rsP6:x86 上 vpermb-LUT 与 shared-decode+vpdpbusd 的实测定价——LUT 全胜,AVX-512 公式化问题关闭
probe_2bit_lutstream.rsP12:忠实 LUT 流式探针(真实 6 KB/查询表,32 B/组),修正了 P5 把 LUT 提升进寄存器的过度简化
sve_tbl_probe.rs实测 Axion 上 SVE TBL 为 4.0/cycle(11.97 G/s),推翻 SWOG/LLVM 模型中"2/cycle"的过时行

这些探针共同封闭了 2-bit 的内核公式化问题:nibble LUT 是两架构通吃的正确公式化(H3/P5 封 arm,H10/P6/P11 封 x86),而 4-bit 对 2-bit 的 nq=100 差距是 4-bit permute-dot 的性质,不是可回收的 2-bit 余量。


八、目标在结果中的兑现:5 个胜场与全 cell 封闭地图

对照目标,LOG_2bit.md的累计 capstone 给出最终判定:8-cell HM x1.0495,arm 4-cell x1.0114,x86 4-cell x1.0906,最差 cell x0.9975,VERDICT: WIN

8.1 五个胜场(每次胜利都重置 non-win 计数)

胜场变更机制
H7x86 2-bit 内核 prefetch(const PF: bool编译期闸门)4-bit 路径自 H59/H62(x86,+24.9%)与 H67(arm,+8.3%)起就有 prefetch,2-bit 从未继承。nq=1 单线程收益显著(x86 nq1_st +26.3%),但 MT 为负——2-bit 下每个 lookahead 都是单线程优化,此规律被 H5/H37/H38/H42 共五次独立测量确认
H112-bit vnni 内核 512-bit epilogue把 4-bit 路径已使用的avx512_post_flush_heap_update引入 2-bit 的search_multi_query_vnni,避免每对累加器拆成四个__m256;给内核特性集补上avx2/fma使 epilogue 内联(此前每次调用都是 spill + 间接调用 +vzeroupper)。x86 nq100_st +2.1% / nq100_mt +1.8%
H14arm NEON tile 下限在 2-bit 几何下加倍(512→1024,bits == 2门控)通过临时TURBOVEC_TILE_FLOOR环境钩子扫描出干净拐点(arm:18.08→17.70ms @1024);tile 下限最优值跟随 range字节数(减半)。arm 首次胜场
H34x86 nq=1 双块交错(H54 机制移植)两块共享 table load(vpermb表每块读一次喂两个块),单核从单 miss 链变双流。构建缺陷教训:#[target_feature]列表漏了avx512vbmi,导致vpermb被软件模拟,x0.34 → 补上后 x1.06——3 倍回退的"tell"正好等于模拟开销
H41arm 单批扫描去掉常驻 float 累加器FLUSH_EVERY=256,而 2-bit dim=768 只有192 个 byte-groupn_batches == 1——fa的跨批累积永远不会发生。单批路径只保留 u16 累加器,flush 时"生产"float 而非"更新"float;同一vfmaq_f32、同操作数同顺序,分数位级相同而非仅接近

8.2 关键探针与封闭判定

  • S1/H1/H2——布局问题被双向证伪:2-bit 下 x86 保留 vector-major 布局而 arm 回退 sequential(见 pack.rs 的vector_major_forkernel_exists = cfg!(target_arch = "x86_64") || bits == 4)。H1 让 arm 改走 vector-major(加vm_load_quad/LD4),全 cell 变慢;H2 让 x86 改走 sequential,nq=1 损失高达x0.364(2.5x 差距)。每个架构都在其经典内核偏好的布局上——条件是为 4-bit permute-dot 写的,却意外地对两个架构都是正确的。布局问题就此关闭。
  • H3/H10/P11——点积公式化被实测关闭:P5(arm)与 P6(x86)探针表明 LUT 每宽度全胜;P11 扫到渐进线(nq=100 时 vpermb-LUT 228.6 对 shared-decode 160.8 G(q.dim)/s),MAC 数本身是墙。AMX(tdpbssd,约 8x VNNI 的 MAC)被记录为唯一未尝试的大项。
  • H12/P12/P17——arm LUT 批量宽度被三次独立攻击确认:qbs=4 是 L1 限制下的正确值(24 KB 热表 vs V2 的 64 KB L1;qbs=8 需 48 KB 导致 -11% 抖动);维度分块、块配对均失败——qbs=4 是寄存器文件从四面防守的局部最优
  • P22/P24/P31/P32/P33——arm nq=1 ST 的逐步封闭:供应上限 33.1 GB/s、发射上限 26.5 GB/s、实际 22.2 GB/s(67% of roofline);原位消融(在真实 ABBA harness 上而非独立探针上)证明 shift 免费(P31,且ushr2/cycle 不构成瓶颈)、删除一半 LUT 加载反而慢 1.8%(P32)、完美 LUT 局部性反而慢 3.2–5.8%(P33)。日志给出了最强结论:"这个内核上,每一个被识别的'开销'最终都被证明是承重的。"
  • P16/P21/P34/P35——双峰性与噪声带的系统诊断:x86nq100_st进程内双峰(82/98 ms)、nq1_st竟是最差的双峰 cell(33% 端到端跨度)、同一二进制控制通道实测出每 cell 噪声带(nq1_mt1.4%、nq100_mt0.1%、nq1_st0.2%、nq100_st1.1%)。控制通道的教义:"最重要的测量,是针对本不该动的东西而做的测量。"

8.3 全 cell 封闭地图(日志 "Map status" 与各节汇总)

  • arm nq=1:在供应上限的 67%(P22)、发射上限的 ~84%(P22/P24);scheduling 家族关闭(P24),指令项全免费(P31/P32),unroll 深度在最优 4(H44/H45)。
  • arm nq=100:公式化关闭(P5)、批量宽度 L1 限制(H12/P12)、布局正确(H1)、tile floor 胜场(H14/H26)、epilogue 被分解为 7.7% 且唯一活路是索引侧 per-block norm 极值(P20/H45 后记,属持久化格式改动)。
  • x86 nq=1:prefetch 胜场(H7)、深度 8 自我确认(H21)、双块交错胜场(H34)、在供应上限的 98%(P22)。
  • x86 nq=100:公式化关闭(P6/P11)、epilogue 胜场(H11)、ST 在内核 roofline 上且 top-k 仅占 3%(P7)、MT 对 floor/tiles/线程策略均平坦(H14/H16/P8/P18/P19)。

九、可迁移的方法论沉淀

GOAL_2bit.md只有 15 行,但它定义了一套比多数性能优化 README 严谨得多的流程。从日志提炼、对任何性能敏感项目都适用的规则:

  1. 权威单一化:判定权只属于脚本(whm_2bit.py),散文记录永远不是;权威必须与书面规格做 diff。
  2. 控制通道优先建立:字节相同的二进制自我对比(P34/P35)应在第一个候选之前就存在——本次在第 16 个候选后才补上。H42 还证明编译期闸门对它所闸的路径并非免费const泛型实例化两次会翻倍大函数的 i-cache 占用,未触及的 MT 路径因此损失 3.2%。
  3. 估计器必须匹配测量对象的噪声形态min用于进程内双峰是对的;但 min-of-4 在跨 pass 双峰的 cell 上会把 0.09% 的真实差距放大成 2.7% 的"回归"(capstone)。"估计器没错,是供给不足"——pass 数必须在看到数据前定死(12 次/侧),且 sub-ms 的 cell 需单独论证 pass 数。
  4. 探针必须建模操作数足迹,而非只建模指令混合:H12 的教训(探针把 LUT 提升进寄存器,真实内核流式 6 KB/查询表);P30 进一步证明 20% 噪声带内的消融数字不可信。"一个无法分辨效应的廉价仪器,比它替代的 20 分钟重建周期更贵。"
  5. 宽度不变量是这座山最富的矿脉:对每个常数问"它为什么存在,该用途在宽度变化后是否幸存"——FLUSH_EVERY(4-bit 常数,2-bit 下只是浪费寄存器)、tile floor(最优值跟随 range 字节数)。H14/H41 两次命中同一形状。
  6. 闭合可被移动的锚点重新打开:P18 证明裸循环探针比它所要界定的内核还慢 2.7 倍,所有依赖该 roofline 的闭合(含 H13)自动失效,重新打开后产生 H34 的胜利。"当支撑闭合的数字移动超过 cell 噪声下限时,闭合自动作废。"
  7. 测量需要物理合理性校验:P30 记录了一个几乎成为结果的 bug——探针丢失sink-O3删除了整个扫描,报告 368 GB/s(15 倍内存 roofline),只因数字荒诞才被抓住。"任何读数小于打印的 spread 的,都不是结果。"

十、如何复现这套目标与验证流程

  1. 构建maturin develop --release(日志中的标准构建方式),准备 arm(如 c4a/Axion 类)与 x86(如 c3/Sapphire Rapids 类)两台机器;单台亦可,评分器支持单架构结果。
  2. 钉住正确性基线python parity_2bit.py --pin parity_base.json
  3. 测量基线python cells_2bit.py --bits 2 --out base.json(目标数据形状见benchmarks/results/中的历史 JSON,如 speed_d1536_2bit_arm_st.json)。
  4. 修改候选后python cells_2bit.py --bits 2 --out cand.json+python parity_2bit.py --check parity_base.json+cargo test -p turbovec
  5. 判定python whm_2bit.py --base base_arm.json base_x86.json --cand cand_arm.json cand_x86.json——只有VERDICT: WIN才算赢;连续 20(日志中常为 25)个 non-win 停止,每次 WIN 重置计数。
  6. 记录:任何假设无论输赢,都带测量值与机制判定写入日志,格式参照 LOG_2bit.md。

这套流程成立的前提,是正确性闸门足够严格(位级 parity 覆盖平局顺序)而性能信号足够稳定(9 子运行 min 估计器 + 预构建.so的平衡 ABBA)。对想在自有量化检索项目里做类似优化的读者,benchmarks/hillclimb/目录本身——目标文档、日志、四件仪器与六枚探针——就是一套可以整体借鉴的蓝本。

【免费下载链接】turbovecA vector index built on TurboQuant, written in Rust with Python bindings项目地址: https://gitcode.com/GitHub_Trending/tu/turbovec

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询