0.8MB 跑通 Qwen|第 4-3 篇:核绑定实验——推理引擎在 ARM 大小核上为什么叮嘱"勿设 8 线程"

简介: 本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588(4×A76+4×A55)上适配Qwen3-VL-2B/8B及30B-A3B模型。通过三组真机实验揭示“核多≠快”本质:仅绑4大核最优,设8线程反降效12%,验证大小核架构下线程配置需严守硬件特性。(239字)

系列:《0.8MB 跑通 Qwen:从零实现 ARM 零依赖纯 C 推理引擎》(30 天 × 90 篇) | 适配模型:Qwen3-VL-8B-Instruct(千问3_VL_8B_Instruct)· Qwen3-VL-2B-Instruct · Qwen3-30B-A3B | 测试设备:RK3588(4×Cortex-A76 + 4×Cortex-A55,aarch64)

系列总纲:《0.8MB 跑通 Qwen》30 天 90 篇 · 总纲(阿里云社区)

上一篇:4-2《vllm_tp_parfor:调用线程也干活》 | 下一篇:第 5 天《量化地基:Q8/Q4 从哪来》

真机实测通过:本文实验已在 RK3588 板端实测完成(2026-09;方法学与原始记录见仓库 docs 与《实验脚本》目录)

一句话导读:推理引擎的核绑定实验:从 RK3588 四颗 A76 大核加四颗 A55 小核的不对称架构出发,解释绑核目标只写大核簇的取舍,落点在纯计算、真实 prefill 与开绑核三组线程数对照实验,验证勿设 8 线程。

关键词:手搓 Qwen 推理引擎、千问大模型推理、核绑定、大小核、线程数、A76、RK3588、Qwen3-VL、零依赖纯 C

导语:RK3588 有 8 个核,默认却只用 4 个——因为它是 4 颗 A76 大核加 4 颗 A55 小核的大小核。本篇用三组实验钉死「核多≠快」:纯算下 8 线程最快、真实 prefill 打平、一开绑核反而慢约 12%,说清工程为何叮嘱「勿设 8 线程」。

4-1 里那行 nthreads = (nc > 4) ? 4 : nc 留下了悬念:RK3588 明明有 8 个逻辑核,为什么默认只用 4?因为它是 4×Cortex-A76(大核)+ 4×Cortex-A55(小核)的大小核——"核多"不等于"算得快"。这一篇我们用三组实验把这句话钉死。

1. 知识点:大小核不对称与"绑核"

A76 与 A55 是两种完全不同的核心:A76 主频高、乱序执行,是干活主力;A55 面积小、省电,性能约为 A76 的 1/3–1/2。推理引擎的 GEMM 是 A76 大核的菜;把线程扔到 A55 上,等于让主力在快车道、干活的却在小路。

所以线程池有"绑核"(affinity)机制:把每个 worker 钉到指定物理核,避免调度器乱丢。看 vllm_tp.c 第 115–125 行:

static void tp_bind_worker(int slot, int nthreads) {
   
    (void)nthreads;
    if (tp_bind_disabled()) return;
    long n_cpus = sysconf(_SC_NPROCESSORS_ONLN);
    if (n_cpus <= 0) n_cpus = 8;
    int core = (n_cpus > 4) ? (4 + (slot % 4)) : (slot % n_cpus); /* A76 cluster */
    cpu_set_t cs;
    CPU_ZERO(&cs);
    CPU_SET(core, &cs);
    sched_setaffinity(0, sizeof(cs), &cs);
}

注意两件事:

  1. 绑核目标只写 A76 集群(核 4–7),slot 对 4 取模——4 个 worker 各占一个大核;
  2. 但绑核默认是关的(VLLM_TP_BIND=1 才开启,第 105–113 行注释:x86 基准机实测不绑更快)。也就是说:默认情况下线程靠 Linux 调度器自由飘,8 线程就可能飘到 A55 上。

于是问题变成:8 线程在这块板上到底是赚是亏? 文档里有过明确警告(技术文档.md 环境变量表与 §11.4):VLLM_THREADS 默认 4、"绑定 4×A76 大核;勿设 8(A55 拖累,见 §11.4)",§11.4 记录同板实测 8 线程反而慢约 2 倍(图片编码 21.7→47s,文本 prefill 也变慢)。下面我们用当前代码在板端亲手验证,顺便看清"绑核开与关"两种世界。

2. 对应代码:线程数从哪来

回顾 4-1:线程数取 OMP_NUM_THREADS,否则取别名 VLLM_THREADS,再默认 nc > 4 ? 4 : nc。真实推理走 VLLM_THREADS=4(技术文档环境变量表 + 部署脚本 start.sh 均写死 4)。所以工程给这台板的出厂建议是 4 线程——本节的实验就是验证这个建议。

3. 改动后果:三组实验,看"线程更多"到底带来什么

实验 1:纯计算缩放(微基准,说明"更多核在纯算下有收益")

一个只做浮点运算的 parfor(4M 项),分别用 1/2/4/6/8 线程。板端实测(RK3588 / gcc 11.4 / 2026-09):

VLLM_THREADS=1 : 0.0784s
VLLM_THREADS=2 : 0.0704s
VLLM_THREADS=4 : 0.0428s
VLLM_THREADS=6 : 0.0280s
VLLM_THREADS=8 : 0.0255s

单调改善,8 线程最快——纯计算、无内存瓶颈的负载,核越多越好。这说明"A55 拖累"不是无条件的,它只出现在真实推理负载(GEMM/带宽受限)里。所以别用玩具程序下结论,要跑真负载。

实验 2:真实引擎 prefill(2B 模型,同一 prompt),默认不绑核

起服务 → 发一次约 5 秒的文本 prefill 请求,4 线程 vs 8 线程各两轮:

VLLM_THREADS=4 : 5.06s / 5.25s
VLLM_THREADS=8 : 4.97s / 4.97s

8 线程没有变慢,还略快 ~2–4%——为什么没复现文档的"慢 2 倍"?因为当前代码默认不绑核,调度器把多余线程放到 A55 上,但 4 个 A76 大核仍被核心工作占满,A55 分担了少量杂活,净效果接近打平。

实验 3:开绑核(VLLM_TP_BIND=1),同一请求

VLLM_THREADS=4 VLLM_TP_BIND=1 : 4.64s / 4.73s
VLLM_THREADS=8 VLLM_TP_BIND=1 : 5.24s / 5.33s

绑核后 8 线程比 4 线程慢约 12%(5.3s vs 4.7s)。为什么?绑核代码 4 + (slot % 4) 把 7 个 worker 全塞进 4 个大核——后 4 个 worker 与先 4 个双绑在同一物理核上互抢执行槽,反而添乱。这就复现了"勿设 8 线程"的机制:在这台板上,4 就是 A76 集群的容量上限,再多线程只是在同一个核上排队。

三组实验拼起来的结论:线程数的最优值取决于"负载是否饱和 A76"。玩具纯算饱和 8 核 → 8 线程好;真实推理的 GEMM 在 4 个大核上就接近饱和 → 8 线程无益;一旦绑核,超过 4 线程更是直接负优化。文档里那个"慢 2 倍"来自更重、更吃小核的负载与当时的测量口径——量级随负载浮动,方向一致:不要盲目设 8。

工程启示:为什么默认绑核是"关"的?因为绑核在 x86 基准机上是负优化(调度器比你懂),而在这块 ARM 板上又可能想要。所以做成开关(VLLM_TP_BIND),把"要不要绑"留给实测——这是"配置即诚实"的又一例:代码不替你赌。

4. 学员调试任务

  • A 档(板端动手):找你的板子(或有模型资产的环境)跑实验 2/3 的姿势:
    1. nproc 看核数,lscpu 或 /sys/devices/system/cpu/ 确认是否大小核;
    2. 同一 prompt 下,对比 VLLM_THREADS=4/8 与 VLLM_TP_BIND=1 开/关四组合,各自记录耗时;
    3. 说出你的板子"最优线程数"(不必等于 4,可能是 6/8——关键是你测出来的)。
  • B 档(纯读源码):读 vllm_tp.c 第 101–148 行(亲和三函数)与第 281–293 行(线程数策略),画一张"RK3588 8 核 → 4 worker + 调用线程"的分配图,标注绑核时 slot 4–7 会落在哪。

预期输出:你能解释"核多≠快"的大小核原因、"绑核默认关"的取舍,以及为什么实验必须跑真负载而不是玩具程序。

收尾

  • 本篇源码点名:vllm_tp.c(tp_bind_worker/tp_bind_caller/线程数策略)、技术文档.md(环境变量表与 §11.4 线程数)。
  • 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
  • 下篇预告:并行把"活"分下去了,可"活"本身是浮点数——4 字节存一个权重,2B 模型就要 8GB,板上根本放不下。第 5 天进入量化地基:Q4/Q8 是怎么把内存和带宽需求砍到 1/2、1/4 的。
相关文章
|
2天前
|
定位技术 Python
0.8MB 跑通 Qwen|第 3-2 篇:推理引擎的字节序与十六进制纪律——一个字节序错误,引擎当场翻脸
本文以RK3588真机实测为基础,深入剖析字节序陷阱:揭示VQF格式中小端落盘导致魔数“VQFW”在磁盘呈现为“FQFW”,并用篡改version字段的实验直观展示——小端机器误读大端数据会直接拒载(报错33554432≠2)。强调“先问字节序,再读数字”的十六进制读法铁律。(239字)
0.8MB 跑通 Qwen|第 3-2 篇:推理引擎的字节序与十六进制纪律——一个字节序错误,引擎当场翻脸
|
1天前
|
缓存 C语言 C++
0.8MB 跑通 Qwen|第 9-3 篇:推理引擎的 q8 KV 精度对照——量化进注意力,输出差多少
本系列《0.8MB跑通Qwen》手搓零依赖纯C推理引擎,适配Qwen3-VL多尺寸模型,在RK3588上实测q8 KV量化:输出误差仅≈0.01,端到端PPL损失<1%,带宽降为52%,精度与效率达成教科书级平衡。(239字)
|
1天前
|
JSON C语言 数据安全/隐私保护
0.8MB 跑通 Qwen|第 16-1 篇:VQF 的野心——推理引擎如何把量化与布局固化到文件
本系列手搓零依赖纯C推理引擎,仅0.8MB即可在RK3588上跑通Qwen3-VL多模态大模型。核心创新VQF格式将量化类型、内核布局与模型属性固化于2.17GB单文件中,实现mmap直挂、零转换加载,真正达成“加载即用”。
|
1天前
|
缓存 安全 API
0.8MB 跑通 Qwen|第 11-2 篇:大模型推理的前缀缓存键——怎么知道"两轮一样"?
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588上高效运行Qwen3-VL多模态模型。本文详解前缀缓存核心机制:以token序列最长公共前缀(LCP)为键,实现KV复用;并揭示“完全相同反不复用”的安全设计——确保prefill刷新logits,杜绝空响应。
|
1天前
0.8MB 跑通 Qwen|第 13-1 篇:推理引擎的 decode 为什么慢——逐词、带宽、不可并行
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型,在RK3588上实测decode瓶颈——逐词生成、内存带宽受限、无法并行。直击每词92ms硬地板,为推测解码(speculative decode)铺路,实现“一次前向多产出”。
|
1天前
|
JSON 自然语言处理 算法
0.8MB 跑通 Qwen|第 18-1 篇:BPE 入门与 tokenizer.json——为什么推理引擎不直接跑 BPE
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多尺寸模型,在RK3588上实测通过。本文详解分词器工程近似方案:以“字节流最长前缀+空格标记”替代官方BPE merge,三方对拍15条语料达成9/15一致,并精准归因差异为“贪心vs排序”与“空格丢失”两类机制,践行技术诚实。(239字)
|
1天前
|
C++ 芯片
0.8MB 跑通 Qwen|第 6-2 篇:推理引擎的 8x8 布局重排——把"取数"提前到转换期
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型,在RK3588芯片实测落地。核心创新是Q8权重的8×8内存重排,使宽矩阵GEMM提速约1.7倍,且位级结果一致,兼顾性能与精度。(239字)
|
1天前
|
NoSQL 调度 C++
0.8MB 跑通 Qwen|第 14-3 篇:推理引擎的批处理正确性边界——margin 0.71 vs 0.032
本篇精读Qwen3-VL系列推理引擎的“位级一致性”本质:批量与串行输出大多逐字节一致,但因浮点累加顺序差异,在near-tie(近平局)场景下存在翻盘风险——margin(如0.71 vs 0.032)决定是否一致。这是工程结果,非数学承诺。
|
1天前
|
编解码 应用服务中间件 API
# 0.8MB 跑通 Qwen|第 19-3 篇:视频帧与媒体模块——推理引擎默认不启用的 H.264 独立模块
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型(2B/8B/30B),在RK3588上实测。视频支持分两层:帧序列路径已落地;MP4/H.264解码为独立未完成模块,API清晰、默认不构建、不演示未验证功能,体现严谨工程边界。(239字)
|
1天前
|
缓存
0.8MB 跑通 Qwen|第 8-1 篇:大模型推理的自注意力数学——Q·K^T / softmax / V
本文精讲自注意力三步核心:Q·K^T打分、softmax归一化、加权V求和,逐行对照纯C引擎源码(vllm_transformer.c),剖析√d缩放、因果掩码与数值稳定性等工程地雷,聚焦Qwen3-VL系列在RK3588上的零依赖推理实现。(239字)

热门文章

最新文章