0.8MB 跑通 Qwen|第 20-1 篇:极简 HTTP——推理引擎的 socket/bind/select 轮询

简介: 本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型,在RK3588芯片实测通过。核心亮点:单文件`vllm_http.c`实现socket监听、select轮询、有界线程池与SSE流式响应,不依赖任何第三方库,专为板端轻量服务而生。(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 篇 · 总纲(阿里云社区)

上一篇:19-3《视频帧与媒体模块——默认不启用的 H.264》| 下一篇:20-2《路由与自研最小 JSON》

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

一句话导读:推理引擎的极简 HTTP 服务:socket、bind、select 轮询与有界 worker 队列怎么撑起零外部库的自研服务器,板端实测 /health 往返,讲清自研与上框架在单机板端场景的成本取舍。

关键词:手搓 Qwen 推理引擎、千问大模型推理、Qwen3-VL、零依赖纯 C、HTTP、socket、select 轮询

导语:数据面收官后,引擎要自己当服务器:它在 vllm_http.c 一个文件里用 socket、bind、select 轮询与有界 worker 队列撑起零外部库的 HTTP 服务,并在 RK3588 上以 /health 往返实测,讲清自研与上框架在单机板端的成本取舍。

多模态与数据面收官,Day 20 进入服务化:引擎的 HTTP 服务在 src/serve/vllm_http.c,一个文件里装下了 socket 监听、自研 JSON 解析器、请求/响应解析、线程池、SSE 流式——零外部库,只依赖系统 socket API。这篇先回答"极简服务器怎么撑起来的",并用板端实测的 /health 往返时间把"自研 vs 上框架"的成本讲清楚。

1. 知识点:一个最小 HTTP 服务要几块砖

HTTP 服务最内核只有四件事:

socket() → bind() → listen() → accept() 循环
                                    │
              每来一个连接 → 读请求头/体 → 路由 → 写响应
  • listen:告诉内核"我在这端口等人连接",参数是积压队列长度;
  • accept:从内核拿一个已完成握手的连接;
  • select:一次等待多个 fd 就绪(本引擎用它在 accept 上等连接,附带超时做优雅停机轮询,vllm_http.c 854–868 行);
  • SO_RCVTIMEO:给每个已接受连接设接收超时(vnet_set_recv_timeout,511 行)——慢连接/半开连接会被超时踢掉,这是"不引库也能防呆"的典型写法。

引擎的取舍(文件头注释 4–9 行):accept 循环单线程串行 + 连接丢给有界队列 + 8 个 worker。HTTP 层并行度不归推理管——推理本身是单 SSE 流串行(Day 14 讲过"单机守则"),HTTP worker 只是把"慢客户端"与"推理线程"隔开。为什么不上 libevent/nginx? 板端场景的并发模型是"单模型 + 少量并发请求",select + 有界队列 + SO_RCVTIMEO 已经覆盖;少一个第三方依赖,就少一分供应链审计成本(这是 Day 23 起国密/签名体系能自洽的前提)。

2. 对应代码:vllm_http.c 的砖块顺序

监听与主循环(vhttp_serve_ex,804–868 行):

listener.fd = socket(AF_INET, SOCK_STREAM, 0);      // 812
setsockopt(listener.fd, SOL_SOCKET, SO_REUSEADDR, …);// 816
bind(listener.fd, (struct sockaddr *)&addr, sizeof(addr)); // 823
listen(listener.fd, 32);                            // 824
for (;;) {
   
    fd_set rfds; FD_SET(listener.fd, &rfds);
    select(listener.fd + 1, &rfds, NULL, NULL, &tv); // 860:等连接或超时
    int cf = accept(listener.fd, (struct sockaddr *)&ca, &calen); // 868
    queue_push(&ctx->q, conn);   // 连接进有界队列,worker 取走
}

配套的三块:

  1. 请求解析:read_request(554 行)+ find_header(519 行)——按 \r\n\r\n 切头、按 Content-Length 收体,不读 body 以外的字节;
  2. 响应写回:send_response(636 行),socket 无缓冲,vhttp_stream_write 每写一次就到线缆(683 行注释);
  3. worker:worker_main(791 行)从队列取连接、回调路由、关闭(serve_conn 765 行)。

"极简"的代价要在文档里说清(技术文档 §2.2):默认 --threads 8 的 worker 数只影响连接并发,推理仍串行;想要吞吐只能靠批处理而不是把 HTTP worker 堆到推理上。

3. 改动后果:板端实测 /health 往返

实测口径:RK3588 板 serve(model.vqf 已加载)+ x86 主机客户端,局域网 192.168.1.92:8801,2026-09-07。客户端记录的是 urlopen 完整往返(含 TCP 握手),不是服务端处理时间。

GET /health ×6(模型已加载、无排队)
42.6 ms / 29.3 ms / 16.8 ms / 27.8 ms / 7.2 ms / 22.5 ms
响应体: {"status":"ok","model":"qwen3-vl-8b","busy":false,"queued":0,...}

三个观察:

  1. 7–43 ms 的抖动主要是网络与调度:同一板子同一路径,/health 服务端只做一次 JSON 拼装,远小于网络 RTT 的抖动——这告诉我们:单看一次 HTTP 往返测不出服务端快慢,要测服务端就该看服务端日志或压测多次取中位(Day 28 方法论预告);
  2. busy/queued/active 字段让 /health 能当调度器探针用:queued 不为 0 说明请求在排队,这是比"进程活着"更有用的健康定义;
  3. 模型 id 是配置标签:响应里 "model":"qwen3-vl-8b" 是服务启动时的模型 id 串(非架构声明),多模型共存时客户端靠它区分后端——兼容协议里模型字段的语义在 Day 21 细讲。

4. 学员调试任务

  • A 档(板端/本地动手):
    1. 起 serve 后对 /health 连打 10 次,记录每次往返时间并取中位(对比本节的 7–43ms 量级);
    2. 用 strace -e trace=network 或 /proc/<pid>/fd 看监听 socket 与已建立连接的状态,确认 select/accept 在干什么(netstat -tlnp | grep 8801 也行);
    3. 读 vllm_http.c 812–868 行:把 listen(..., 32) 改成 1 再起服务,观察并发连接的表现差异(可在本机起两个 python 客户端同时连)。
  • B 档(纯读源码):回答:① 为什么 select 的超时对"优雅停机"是必要的(提示:g_vhttp_stop 标志 28–34 行 + vhttp_stop 在 vhttp_serve_ex 主循环哪里被检查)?② SO_RCVTIMEO 设多少秒合理、设成 0 会发生什么?③ 若把 queue_push 换成无界队列,worker 忙时连接会怎样堆积,内存上会出什么问题?

预期输出:自己板子的 /health 往返直方图 + 监听 socket 的 netstat 截图,并能说出 select 在这里"等的是什么"。

收尾

  • 本篇源码点名:vllm_http.c(socket/bind/listen 812–824、select 主循环 854–868、read_request 554、worker 队列 729–799)
  • 开源仓库:Kestrel-LLM (Gitee)(AGPL-3.0-or-later 或商业许可,二选一)
  • 下篇预告:连接进来了,URL 和 JSON 体怎么变成一次"对话"?20-2 讲路由表与自研最小 JSON 解析器,并用"改错字段名"实验演示兼容性断裂。
相关文章
|
2天前
|
JSON Java Linux
0.8MB 跑通 Qwen|第 3-3 篇:推理引擎的零依赖自研 util——"不引第三方也能活"的边界感
本文实测验证“零第三方依赖”的务实边界:OS已支持的(如Linux原生UTF-8路径)仅薄封装为宏;OS缺失且轻量关键的功能(FNV-1a校验、小端memcpy、平台工具宏)才自研。RK3588真机验证(2026-09),代码简洁、可移植、无冗余。
0.8MB 跑通 Qwen|第 3-3 篇:推理引擎的零依赖自研 util——"不引第三方也能活"的边界感
|
2天前
|
移动开发 编译器 Linux
0.8MB 跑通 Qwen|第 2-2 篇:推理引擎的平台层——一个头文件守住全部平台契约(vllm_platform.h)
本文实测于RK3588(2026-09),提出“平台层契约”设计:将NEON、mmap、绑核等平台依赖统一收口至`vllm_platform.h`,通过`ST_HAVE_NEON`等宏提供唯一真相,配合`#error`门闩实现错误前移——有守卫仅报1行错,无守卫则引发46处误导性编译失败。
0.8MB 跑通 Qwen|第 2-2 篇:推理引擎的平台层——一个头文件守住全部平台契约(vllm_platform.h)
|
1天前
|
调度 C++
0.8MB 跑通 Qwen|第 13-2 篇:草稿 + 验证——推理引擎里 spec decode 的实现
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测RK3588上运行Qwen3-VL多模态模型;详解speculative decode实现——基于n-gram草稿、批量验证与无损回放,确保输出位级一致,兼顾 correctness 与工程可控性。(239字)
|
1天前
|
编译器 C++
0.8MB 跑通 Qwen|第 6-3 篇:推理引擎的 4x4 asm 内核——从 llama.cpp 提取的 MIT 代码
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型,在RK3588上实测部署。核心采用llama.cpp中450行MIT许可NEON汇编内核,通过机械提取实现零转录风险,并严守许可署名规范,诠释开源合规与工程严谨的统一。(239字)
|
1天前
|
调度 C++ Python
0.8MB 跑通 Qwen|第 5-1 篇:为什么必须 Q4/Q8——边缘推理的带宽瓶颈
本系列手搓零依赖纯C推理引擎,仅0.8MB即可在RK3588上跑通Qwen3-VL/30B等大模型。本文聚焦“带宽瓶颈”:decode每生成一词需重读全部权重,推理时间≈权重字节数÷内存带宽。实测表明,Q4/Q8量化+紧凑布局可提升有效带宽3倍,是边缘部署的生存前提。(239字)
|
1天前
|
NoSQL C语言 C++
0.8MB 跑通 Qwen|第 15-1 篇:推理引擎入口架构——参数 → 分发(serve/自检)→ 退出
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,实测适配Qwen3-VL-2B/8B及30B-A3B模型,在RK3588板端完成真机验证。本文详解`main()`入口如何通过命令行参数分发至serve、离线转换或自检三种命运,并用GDB抓取真实调用链,厘清初始化与退出逻辑。(239字)
|
1天前
|
缓存 算法 API
0.8MB 跑通 Qwen|第 11-3 篇:推理引擎里 KV 缓存的一生——内存、容量与淘汰
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎开发,实测RK3588平台流畅运行Qwen3-VL-2B/8B等多版本。本文详解KV缓存“出生—续用/覆盖—死亡”三段式生命周期,揭示预分配、逻辑覆盖、进程级销毁本质,破除“命中即加速、淘汰即释放”认知误区。(239字)
|
1天前
|
机器学习/深度学习 区块链 C++
0.8MB 跑通 Qwen|第 10-1 篇:大模型推理的长上下文为什么必须稀疏——O(n²) 的注意力 vs O(n·k) 的"只看该看的"
本系列手搓0.8MB纯C推理引擎,零依赖跑通Qwen3-VL多模型。本文聚焦长上下文瓶颈:RK3588实测显示,注意力耗时占比从18%飙升至71%。提出块稀疏方案——将O(n²)注意力压至O(n·k),实现每词成本与上下文长度无关,为端侧大模型长文本推理提供关键路径。(239字)
|
1天前
|
缓存 C语言 内存技术
0.8MB 跑通 Qwen|第 8-2 篇:在线 softmax——推理引擎的注意力为什么不能先算完 e^x 再除
本系列手搓0.8MB零依赖纯C推理引擎,适配Qwen3-VL多模型,在RK3588(aarch64)真机实测。本文详解softmax溢出陷阱:fp32下exp&gt;88.7即inf,剖析朴素/稳定/在线三版实现,揭示“减max”数学等价性与在线rescale的流式优势。(239字)
|
23小时前
|
JSON 数据格式 Python
0.8MB 跑通 Qwen|第 18-2 篇:tokenizer.json → vocab.bin——推理引擎的 build_vocab_bin.py 在干嘛
本系列《0.8MB跑通Qwen》聚焦ARM端零依赖纯C推理引擎,适配Qwen3-VL多模态模型(2B/8B/30B),在RK3588平台实测通过。本文详解词表编译:将7MB tokenizer.json编译为1.95MB可审计vocab.bin,精准处理byte-level解码(如0xAD/0x7F等历史坑),实现板端逐位一致、冷启动仅约2秒。(239字)

热门文章

最新文章