第 20 章:实时应用——150 毫秒预算
第 20 章:实时应用——150 毫秒预算
官方把实时应用的延迟预算定为 150 毫秒,因为这是人眼感知"即时响应"的一个舒适阈值。生成模型逐 token 写字的节奏天生跨不过这条线,而决策层只做一次判定,天然站在线内。本章讲怎么把 150 毫秒花在刀刃上,以及端侧、实时 UI、实时 Agent 三个方向上的真实做法。
150 毫秒是一条什么线
先说清这个数字为什么重要。用户敲下一个字、点下一个按钮、说出一句话,系统给出反馈的时间如果超过约 150 毫秒,人就会开始感觉到"卡"。对实时 UI、浏览器 agent、语音助手、自动驾驶这类场景来说,处理必须落在感知阈值以内,否则体验就崩了。
生成式模型的问题在于它的输出是序列:要生成 n 个 token,就得跑 n 次自回归步骤,每步都要等上一次的输出。哪怕单个 token 很快,累积起来也很容易越过 150 毫秒这条线,更别说推理型模型还会在输出前"想"很久。
决策层的输出是一次判定,不是一串文本。它要做的只有一件事:读入 state,在一次前向里给出选项上的概率分布。没有逐 token 的生成长尾,也没有"先想再写"的两段式,这就解释了为什么它能稳定落在实时区间——也解释了为什么 laya-mlx 这类本地实现能报出中位 13.4 毫秒的端到端延迟,并且输出 token 数为零。
拆开这 150 毫秒
预算是有限的,关键是别把时间花在不该花的地方。几条工程原则:
其一,把决策放在最近的算力上。 能端侧、浏览器内跑,就不要绕一圈云端。laya-mlx 用 MLX 把 Laya 权重原生跑在 Apple Silicon 上,短英文决策中位 13.4 毫秒、多语言检查点 7.4 毫秒,全程不依赖 PyTorch、Transformers 运行时或云 API。当延迟预算紧张到 150 毫秒,本地一次前向往往比一次网络往返更划算。
其二,并行,不要串行。 一组彼此独立的决策应该并发发出,而不是一个个等回来。
import asyncio
async def decide_batch(items):
# 同一个 state 里的多个独立问题并发发出,而不是排队
tasks = [client.systemone_async(state=s, questions=q) for s, q in items]
return await asyncio.gather(*tasks)其三,批处理替代逐条调用。 上游把多条 state 打成一个批次,一次请求里完成多次判定,省掉往返开销。第 22 章的 AI Map Reduce 会把这一点推到极致。
其四,避免串行大模型。 最慢的环节永远是"等一个大模型把整段话写完"。实时系统里要尽量让决策层承担判断,只在真正需要生成文本时才去叫语言模型——这正是下面 Jev Ultrafast 的核心策略。
详解一:Jev Ultrafast——只在要打字时才叫语言模型
Jev Ultrafast(browser-use/jev-ultrafast)是 browser-use 出品的超快浏览器 agent。它的分工非常清楚:每一步的"下一个动作"和"点哪个元素"由 Jev 决定,只有当任务确实需要输入文本时,才去调一次语言模型。
async def step(page):
d = client.systemone(
state=await page.dom_summary(), # 页面上可交互的元素
questions={
"action": {
"type": "choice",
"question": "下一步做什么?",
"choices": ["click", "type", "scroll", "done"],
},
"target": {
"type": "choice",
"question": "操作哪个元素?",
"choices": await page.candidate_ids(),
},
},
instructions="按当前页面状态推进用户目标;能点就不打字。",
)
if d["action"].choice == "click":
await page.click(d["target"].choice) # 决策即执行,无需生成
elif d["action"].choice == "type":
text = llm.complete(...) # 只有这里才需要语言模型关键在于把生成挤出热路径。浏览器操作的绝大多数步骤是"看页面、挑元素、点下去",这些全是判定,不是生成。把这一步交给决策层后,每步的延迟和成本都落到判定量级;语言模型只在"必须造出新文本"时登场。社区里同类的做法还有把点击交给 Jev、把思考和校验留给 Codex 的 jev-browser-use——它报告浏览器操作快了 5–10 倍。
详解二:laya-mlx——把决策搬进本地芯片
如果说 Ultrafast 讲的是"在云端把生成挤出热路径",那 laya-mlx 讲的是"干脆把决策做到本地"。它是 Laya 检查点的独立 MLX 移植,原生在 Apple Silicon 上跑类型化决策:短英文决策中位 13.4 毫秒,多语言检查点7.4 毫秒,输出 token 数为零,不需要 PyTorch、Transformers 运行时或云 API。
13.4 毫秒是什么概念?它比一次跨洲网络往返还快。这意味着决策可以贴着用户界面实时发生:你在输入框里每敲一个字,系统就能重新做一次判断,而用户毫无感知。
# 指向本地运行时,决策不再经过网络
client = TypesafeClient(base_url="http://localhost:8080")
# 语音转写每更新一个片段,就重新判定一次
for partial in transcript_stream():
d = client.systemone(
state=partial,
questions={
"intent": {"type": "choice", "question": "这是不是一条指令?",
"choices": ["指令", "闲聊"]},
},
)
if d["intent"].choice == "指令" and max(d["intent"].probabilities.values()) > 0.8:
act_on(partial)这类"本地、亚 20 毫秒、零生成"的路线,是实时场景里成本与延迟两条线同时降压的解法。社区里挂在这条路线上的还有一批开源决策模型:一份中文整理列出了五款值得一试的复刻——Laya 421M、Decider-2B、NanoJev 0.6B、Reflex、System-One 4B,其中两款对 Mac 友好。它们共同指向同一个结论:把决策放到本地、靠近芯片,是实时场景里同时压低延迟与成本的一条主路。
详解三:Jev for Physical AI——物理世界里的 p50 与单价
Jev for Physical AI(robokrunch)把决策层放进一个 10,000 台机器人的仓储车队里。它覆盖 41 套双语事件模板,公开的指标是:p50 延迟 0.527 秒、每百万次决策 24.57 美元、与模板标签的一致率 91.3%,并与一个自托管的 ModernBERT 做了交叉对比。
这组数据的价值在于它把"实时"和"规模"放在了一起。0.527 秒的 p50 显然不是人机对话级的 150 毫秒,因为仓库事件分诊的实时性要求本就不同——但它告诉我们另一件事:当决策要乘以百万次,单价就变成了首要约束。每百万次 24.57 美元,意味着你可以对这个车队里的每一次异常都做一次语义判定,而不必担心账单。
物理方向的邻居还有 OmniJev(shapsider):一个 Jev 风格的有限选项接口,把双摄像头图像与文本喂给自托管多模态模型,取回 MuJoCo 机械臂的下一个预设技能作为一次类型化选择。它的已发布片段显示,transfer、stack、barrier 任务都在 13 次决策、每次 39 个输出 token内完成;208/208 条非音频探针请求回答正确,遇到不可观测的输入会返回"证据不足"而不是乱猜;四个公开基准试跑中,准确率与直接短答持平,但决策延迟降低到 1/6.8 至 1/13.6,总 token 减少 51%–86%。
# 机器人:在一个封闭技能集上做类型化选择
skill = client.systemone(
state={"image": frame, "text": task_hint}, # 双摄像头 + 语言提示
questions={"next_skill": {"type": "choice", "question": "下一个技能?",
"choices": ["transfer", "stack", "barrier", "hold"]}},
)["next_skill"].choice
if skill == "hold":
return "insufficient evidence" # 不可观测时宁可不动实时 UI 的自适应
决策层不只服务于 agent,它本身就可以是界面的引擎。当一个决策能在十几毫秒内返回,"界面随上下文实时变形"就成立了。
- json-render(vercel-labs):一个生成式 UI 框架,在它的 compose 路径里用 Jev 决定一个渲染出的界面应该包含哪些组件和动作——界面结构本身成了一个决策。
- shapeshift(anishfn):一个输入框,随着你打字变形为合适的 UI,靠 Jev 判断这句话在要哪种控件,而且完全离线运行。
- unclutter(kitze)与 typesafe-adblock(realZachi):分别对页面元素做"是不是杂物""是不是广告"的逐元素判定,把清理与拦截变成一串类型化提问。
- PlotVeil(Dearest):每次用一条
Noul(20 条一批)判断 YouTube 评论是否剧透,由扩展自己持有 0.85 / 0.7 / 0.5 三档阈值,判定失败就继续遮挡。 - sift(bohutang):给 X 时间线上的每条帖子打上 substance / humour / chit-chat / promo / junk / AI-written 的标签。
- DWIM(rohit9mehta)与 jev-canvas(gaborishka):前者用
Noul逐个菜单项匹配自然语言请求、越过阈值才按下;后者在 tldraw 画布上边语音边指,每个不完整转写都回答八道类型化问题,每次决策 300–550 毫秒。
这些项目的共同点是:阈值由代码持有。decision 给分布,代码决定"到多少分才动手"。PlotVeil 的三档、jev-canvas 的门限都写在扩展里,而不是丢给模型——这正是"代码拥有控制流"在 UI 层的体现。
自适应界面还有两个实用的手段值得单独点出。一是按场景切换候选。 选项集应该跟着上下文走:一个词组输入框可能只需要"指令 / 闲聊"两个候选,而一个复杂界面的下一步动作可能是几十个候选。候选集越贴合当前场景,判断越准、也越快,因为模型在更小的空间里做选择——shapeshift 就是它的极端形态,句子还没敲完,"该出现哪种控件"的候选已经随上下文切换了。二是渐进增强。 先用一次低成本的即时判定让界面立刻响应,再在后台用更重的模型或更多候选做一次更精细的判断,结果回来时无缝替换上去。用户先拿到"够用"的反馈,最终质量由后到的判定兜底,感知延迟被压到最低。
实时 Agent 决策
把上述能力放回 agent 循环,就得到实时 agent 的两条主线:动作选择与意图识别。
- Eliza(elizaOS):多 agent 框架集成了 TypeSafe System One 决策服务,用于 sub-100ms 的意图分类、动作分派与置信度门控的工具执行。
- Jev Driver(reinhard-z):如上一章所述,浏览器里 Florence-2 写标题、Worker 问 Jev 三道
Choice,约 330 毫秒 median、每次决策 0.00004 美元。 - robo-harness(grmkris):一个 SO-101 机械臂工作台,Jev 决策 runner 在花费预算约束下,从类型化候选动作里挑选有界的关节步进。
- Jev Ultrafast 与 sift 则在浏览器侧证明:把判定与生成分离后,实时性是"设计出来的",不是"优化出来的"。
flowchart LR
subgraph 预算分配
E["端侧/浏览器
13–50ms"] --> P["并行/批处理
省往返"]
P --> D["决策层一次判定
无逐 token 生成"]
end
D -- 需要文本 --> G["语言模型
(仅打字时)"]
D -- 命中阈值 --> A["代码执行动作"]
D -- 低置信 --> H["升级/人工"]小结
- 官方的实时预算线是 150 毫秒;生成模型逐 token 的节奏跨不过它,决策层"一次判定、无生成长尾"的形态天生在线上。
- 花掉预算的原则:算力就近(端侧/浏览器)、并行而非串行、批处理替代逐条、避免串行大模型。
Jev Ultrafast把生成挤出热路径——每步动作由 Jev 选,只有真要打字时才调语言模型;laya-mlx用本地 MLX 把短英文决策做到中位 13.4 毫秒、7.4 毫秒(多语言)、零输出 token。Jev for Physical AI给出规模化实时决策的锚点:10,000 台机器人、0.527 秒 p50、每百万次 24.57 美元、一致率 91.3%;OmniJev用 13 次决策、39 token 完成机械臂任务,延迟降到 1/6.8–1/13.6。- 实时 UI 里阈值由代码持有(
PlotVeil的 0.85/0.7/0.5、jev-canvas的门限),json-render、shapeshift、unclutter则让界面结构随上下文实时变化。 - 实时 agent 的两条主线是动作选择(
Jev Ultrafast、robo-harness)与意图识别(Eliza的 sub-100ms、Jev Driver的 330ms / 0.00004 美元)。
下一章进入科学发现,看看这套"判定替代生成"的思路,如何落到文献筛选、假设排序与实验取舍上。