【免费下载链接】jevjevos: an open-source alternative to Jev and Microsoft-Decision-1. Yes/no decisions, choices and scores with probabilities, on your laptops CPU.项目地址https://gitcode.com/gh_mirrors/je/jev点击查看免费下载jevOS 是 jev 开源项目的本地 LLM 决策引擎在笔记本 CPU 上运行一个小模型把一段文本和一个是否问题在 28–130 毫秒内回答成答案概率。本文拆解它的并发架构——单线程调度器 请求队列 token 预算批处理看它如何让多个并发 LLM 请求共享同一次前向传播既快又稳。整体架构三层流水线的线程分工jev serve的请求路径被切成三层每层有不同的线程策略层源码位置线程模型职责HTTP 层server.cpp每条连接一个线程cpp-httplib解析 JSON、校验、分词调度层scheduler.hpp单个 worker 线程 FIFO 队列攒批、按序调用模型模型层model.cpp只运行在调度线程上OpenVINO INT8 前向输出 P(yes)核心思想一句话模型只有一个用户。main.cpp 开头的注释 直接说明了这一点——所有模型调用都由一个调度线程发出而校验wire.cpp和分词跑在各自请求的线程上。并发没有丢只是被分配到了真正可以并行的地方。HTTP 层验证与分词并行跑在请求自己的线程上每个请求进来后在自己的线程上完成三件轻活解析 body、校验 wire 格式、把 state 和每个问题编码成 token。分词用的词表是只读的所以多个请求可以同时分词、互不加锁——server.cpp 中的注释 原话是一个请求在分词时另一个请求可以正在推理。分词结束后请求被压缩成一个 ScoreRequest它的所有问题提示词jobs、共享前缀prefix和长度估算tokens()。之后唯一的动作是scheduler.score()——把任务塞进队列阻塞等待结果。单线程调度器如何工作一个队列与一个条件变量Scheduler 类 总共只有一百多行结构非常干净入队调用线程把任务含一个promise压入队列唤醒条件变量然后阻塞在future上score()。攒批worker 线程醒来后从队首开始按 token 预算组装批次遇到第一个装不下的请求就停不跳过攒批循环保证 FIFO 顺序先来先服务。执行整批交给model.score_batch()一次前向传播把每个请求的结果概率 排队耗时 推理耗时通过 promise 回传。这个设计里有两个关键参数参数默认值上限含义--batch-tokens384CALL_TOKENS 1024一批请求合起来最多读多少 token--warmup384WARM_TOKENS 384空闲时预热到多长的 prompt定义见 model.hpp 的常量 与 main.cpp 的参数解析。token 预算批处理并发请求共享一次前向传播为什么一起读比分开读快因为模型是 stateless 前向批量让矩阵乘法的行数变多摊薄了每次调用的固定开销。main.cpp 里有一段实测注释4 个 64-token 请求合批后总耗时-11%3 个 120-token 的请求-16%但请求太大时合批反而变慢所以有预算上限。这也解释了单线程并发的本质它不是让请求并行执行而是让多个请求并行地占满一次执行的计算——这正是 vLLM 之类系统在 GPU 上做 continuous batching 的 CPU 精简版。空闲预热没有请求时调度器在做什么CPU 推理引擎按输入 shape 编译 kernel某个 prompt 长度第一次出现要多花 3–10 毫秒。如果把这个代价留给第一个用户首请求就会莫名变慢。调度器用了一个巧妙的机制scheduler.hpp 头部注释队列空且安静超过IDLE_GRACE500 毫秒后worker 不睡觉而是执行一次预热调用把还没预热过的 prompt 长度1–384逐个编译好 kernel预热期间来了真实请求就等这一次调用结束再处理等待通常小于 0.2 秒稳定流量自己就会把常用长度热掉因此不会被预热打断。代价是一次性、发生在没人的时候收益是所有常规长度的请求从第一次起就是全速。模型层缓存状态快照与分段读取调度器之外模型层还有两个复用优化进一步压缩了重复流量的实际计算量状态快照SnapshotIndex 按 token 前缀匹配缓存 state 的中间激活LRU 淘汰默认 16 份、共 8192 个 state token可用--state-cache调整。同一客户消息被追问多个问题时后续请求只读增量部分。分段读取超长 state 按 PIECE_TOKENS 2048 的块分段读兼顾精度与耗时分段大小的取舍分析在 model.hpp 的注释。实测数据并发客户端下的吞吐与延迟用 999 题集中的单问题请求压测参考机型Intel Core Ultra 7 255H16 线程早期 jevos 版本数据见 wiki 的吞吐分析页并发客户端数吞吐量延迟中位数18.7 req/s110 ms49.8 req/s390 ms810.1 req/s780 ms吞吐在 1 个客户端之后就基本不动延迟随队列线性增长——这正是排队论Littles law的预期表现模型已经是满负荷的更多请求只是排队而不是并行变快。要观测排队情况每个响应的Server-Timing响应头会拆分出parse / validate / tokenize / queue / inference / respond / total七段耗时server.cpptotal与inference的差值就是队列时间/health里的batched_requests计数器则记录有多少请求被合并读了。仓库自带压测脚本 bench_concurrent.py可直接复现上表。为什么单线程反而更快CPU 前向是算力密集型的同一模型多线程推理只会抢同一批核心收益远小于开销单线程 批量把算力利用率拉满。零锁、行为确定模型只有一个用户内部完全不需要互斥结果不受请求交错影响便于回归测试。并行留在可并行的地方验证与分词天然可并行就放在请求线程上不占用宝贵的模型线程时间。扩展靠横向单进程约 1 GB 内存一个进程饱和后标准做法是多进程或多机 负载均衡而不是在一个进程里加线程。顺带一提离线模式jev decide也复用了同一个调度器只是关掉批处理与预热main.cpp接口行为完全一致。关键源码导航想自己动手翻代码按这个顺序最省力调度器全貌队列、攒批、预热src/scheduler.hppHTTP API 与计时src/server.cpp批处理/分段/预热常量及取舍依据src/model.hpp请求单元结构src/tokens.hpp状态快照缓存src/snapshots.hpp并发压测脚本tests/bench_concurrent.py吞吐 vs 延迟的完整分析wiki/throughput-vs-latency-for-a-decision-server.mdjevOS 用不到两百行的调度器证明了一件事在 CPU 上跑本地 LLM 服务并发的关键不是更多线程而是把线程花在可并行的地方把算力花在攒批上。赞分享【免费下载链接】jevjevos: an open-source alternative to Jev and Microsoft-Decision-1. Yes/no decisions, choices and scores with probabilities, on your laptops CPU.项目地址https://gitcode.com/gh_mirrors/je/jev点击查看免费下载相关推荐Trippy多线程探测原理并发请求调度机制解析Trippy多线程探测原理并发请求调度机制解析 引言网络诊断工具的并发挑战 在现代网络诊断中传统的 traceroute 工具因单线程探测机制导致诊断效率网络CLI运维gitsome中的并发处理多线程API请求实现gitsome中的并发处理多线程API请求实现 在日常开发中你是否遇到过GitHub API请求缓慢、批量操作耗时过长的问题gitsome作为增强型Git开发工具版本控制devalue与JSON.stringify对比分析10个关键差异点详解devalue与JSON.stringify对比分析10个关键差异点详解 devalue 是一个轻量级 JavaScript 数据序列化库官方定位只有一句话创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考