端侧 AI · 15 天工程路线
目录首页 English
DAY 13

LLM Runtime

把模型包、生成循环与异构 backend 组织成可观测执行路径

建议阅读:约 20 分钟

学习目标

理解 runtime 如何加载模型、管理会话、调度 Prefill/Decode、执行采样与流式取消,并能用分层验收矩阵比较 CPU、GPU、NPU backend、编译方式、子图分区和 fallback。

本章关键词

关键词解释ESP32 工程类比
Context一次推理会话持有的 token 和 KV 状态。像一个连接会话的协议状态块。
Cancellation中止已失效请求并释放资源的机制。像关闭 socket 后回收 DMA 与任务资源。
Backend把 runtime 请求映射到特定硬件的实现层。像统一驱动接口下的具体外设驱动。
Offload把部分层或子图交给加速器执行。像将计算任务卸载给协处理器。

承上:回顾与定位

前六天建立传统端侧模型执行链,Day 7–12 又补齐 LLM 的输入、架构、Attention、KV 与模型包。本章把这些静态契约放进真实 runtime,请求调度与异构 backend 从此成为一条可追踪的数据和控制路径。

从源头看今天

历史发展脉络

LLM runtime 与端侧框架共同承接模型语义和硬件现实:前者管理生成状态与请求生命周期,后者通过图变换、编译和 delegate 把算子映射到异构设备。两条历史最终汇合为一条必须可观测、可回退的执行路径。

2017

Transformer 奠定并行 Prefill 与自回归 Decode 的结构基础

《Attention Is All You Need》用纯注意力架构替代循环结构,使整段输入可以高度并行处理;生成端仍按已生成前缀逐步产生下一个 token。现代 runtime 因而天然分成一次吞吐导向的 prefill 与多次低延迟 decode。

Transformer 原始论文 ↗
2023

llama.cpp 推动通用硬件上的本地 LLM 运行

llama.cpp 以轻量 C/C++ 实现把模型加载、量化 kernel、CPU/GPU 混合执行和命令行生成循环放到同一工程里。本地推理由研究脚本变为可嵌入进程,也让 mmap、线程数、context 和后端选择成为普通部署参数。

llama.cpp 官方仓库 ↗
2024—至今

本地 Runtime 逐渐具备完整服务语义

现代 llama-server 已把并行解码、连续批处理、流式接口、监控、结构化输出与取消等能力放进服务层。工程重点从“能生成一句话”转向资源隔离、可观测性、协议兼容和不可信工具调用的安全边界。

llama.cpp Server 官方文档 ↗
2017

ONNX 推动模型交换与算子版本契约

ONNX 用图、节点、initializer、类型与 opset 描述模型,使训练框架和执行器可以围绕共同 IR 协作。它解决的是“表达什么”,而非自动保证目标芯片上有高效实现;这种分层后来成为比较各种 runtime 的第一把尺子。

ONNX 官方概念文档 ↗
2023

llama.cpp 证明专用轻量 Runtime 的端侧价值

面向自回归 LLM 的 llama.cpp 把 GGUF、量化 kernel、KV cache 和多种 CPU/GPU backend 紧密结合,以较少依赖覆盖桌面与边缘设备。它代表另一条路线:针对主工作负载做深度垂直整合,而不是追求任意训练图通吃。

llama.cpp 官方仓库 ↗
至今

框架竞争转向编译产物、异构分区与可观测性

MLC LLM 等系统将权重转换与目标模型库编译分开,按 WebGPU、移动 GPU 或本地平台生成推理逻辑。工程比较也从 API 风格转向:支持哪些图、何时编译、跨分区复制多少、能否回溯 fallback,以及升级后能否复现实测。

MLC LLM 官方编译文档 ↗
今天为什么仍然重要: 先用 CPU 与金输入固定语义,再逐层打开量化模型包、编译变换、子图分区和硬件 delegate;每增加一层优化,都要留下实际执行节点、复制边界、版本和 fallback 证据。
借熟悉的系统建立直觉

类比图解

把 Runtime 想成一间不断接单的面馆

模型权重像挂在墙上的固定配方,加载一次即可复用;每位客人的 prompt 是一张新订单,prefill 像厨师一次读完所有加料要求并备好案台,KV cache 是这桌专属的半成品托盘。随后 decode 每次只做一小步、端出一个 token;采样器决定从几种合格调味中怎样选择,流式输出则让客人不必等整桌菜齐才开吃。若客人离店,取消信号必须立刻让后厨停火并归还托盘。

固定配方 模型权重与 tokenizer 在进程启动时加载并校验
订单入队 请求校验、模板化、tokenize 与 context 分配
专属托盘 每个会话独立持有并持续追加 KV cache
叫停铃 断连、超时、EOS 或用户取消触发资源回收

类比的边界: 面馆类比会在并行细节上失效:GPU batch 不是多个厨师各做一碗,而是把不同请求的同类矩阵运算拼成一次执行;token 也不是独立菜品,它会改变下一步全部概率。类比只帮助理解生命周期与所有权,不能用来推导吞吐、显存布局或采样数学。

本章讲解

先分清进程级资源与会话级状态

权重映射、backend、线程池通常属于进程,可跨请求复用;token 序列、KV cache、采样器状态和停止条件属于会话,必须隔离。实现时为两类对象分别画生命周期:进程退出才释放权重,请求结束就归还 context。若把会话指针塞进全局单例,并发时会串写历史;若每次请求重载权重,TTFT 又会被初始化成本吞没。

采样器是一条可复现的决策管线

模型输出 logits 后,重复惩罚、温度缩放、top-k、top-p 与随机抽样按既定顺序改变候选分布;顺序或随机种子不同,输出就可能分叉。调试正确性时先使用贪心或固定种子,保存采样参数和首若干步 logits 摘要。面向工具调用还应使用语法或 schema 约束,但约束只保证结构可解析,不能证明命令被授权。

流式返回必须与取消共用控制路径

SSE、WebSocket 或分块 HTTP 只是输出通道;真正困难的是客户端断开后,decode 任务能否在下一安全点观察取消标志,停止排队并释放 KV。设计时让请求拥有明确状态机:排队、prefill、decode、完成、取消、失败,并保证每个终态只回收一次。用慢客户端、半包、超时和重复取消做故障注入,检查没有悬挂线程与内存泄漏。

用四层模型拆解框架宣传语

先分别写出模型格式、runtime、backend 与 kernel:GGUF 或导出图负责表达;runtime 管生命周期与调度;backend 接入 CPU/GPU/NPU;kernel 执行具体布局和 dtype。框架声称“支持某芯片”时,要追问哪些模型架构、哪些算子和量化类型真的走专用 kernel。只要其中一层版本不匹配,就可能加载失败、静默 fallback 或产生额外转换。

Partition 的价值取决于边界而非节点占比

delegate 通常选择连续且受支持的子图,边界处可能发生 device copy、layout transform、量化与反量化以及同步等待。即使 90% 节点被标记到 NPU,若剩余节点夹在每层中间,来回搬运仍会主导时延。评估时记录分区数量、每区输入输出字节和执行时间,并人工关闭 delegate 做对照;节点覆盖率只能作为线索,不能作为性能结论。

AOT 与 JIT 是部署责任的重新分配

AOT 在发布前完成 lowering、融合和目标代码生成,能缩小设备 runtime、减少启动抖动,却要求按芯片与形状管理产物;JIT 在现场适配更多动态情况,但带来编译时延、缓存与工具链依赖。固件或离线产品通常偏向 AOT,桌面应用可接受 JIT。无论哪种,都要把编译器版本、目标特性和生成配置纳入产物清单。

框架选择应落到可维护的验收矩阵

为候选框架列出目标 OS/芯片、模型架构、最大 context、量化格式、包体、冷启动、峰值内存、PP/TG、功耗、许可证和调试能力。再准备正常、边界与失败模型:含一个不支持算子、动态 shape 和低内存场景,观察报错与 fallback 是否可见。版本升级后重跑同一矩阵;能持续解释回归的第二名,常比一次基准领先却不可诊断的方案更适合产品。

让数据真正跑起来

动态过程演示

模型图如何被切分到 CPU、GPU 与 NPU

逐步播放器把抽象的“硬件加速”展开为能力查询、分区、lowering、跨边界搬运和回退证据。

步骤 1 / 6

读取图契约 · ops + shape + dtype + layout + quant params

验证模型版本与 I/O,并建立 CPU 可运行基线

观察点

格式可读不等于每个节点都有目标后端实现

循环 / 返回条件: 若某分区收益为负,回到“形成分区”尝试扩大连续子图、消除布局转换或干脆让该段留在 CPU;若结果不一致,回到“读取图契约”逐边界比对。每次只改变一个 backend 或编译选项。

查看静态全景图
model package → loader/compiler → runtime request loop
                                  │
       CPU baseline ↔ GPU delegate ↔ NPU partition
 queue → prefill → decode → sample → stream / cancel / reclaim

代码或命令示例

engine = load(model_package, backend_policy)
for request in scheduler:
    state = prefill(request.tokens)
    while not request.done:
        logits, state = decode_one(state)
        request.stream(sampler(logits))
    reclaim(state)  # cancel/error 也走同一释放路径

动手实验

选择一个本地 LLM 和两个可用执行配置。固定模型、GGUF、prompt、线程、context 与输出长度,先建立 CPU 正确性基线,再开启 GPU/NPU delegate 或 offload。记录模型加载时间、TTFT、ITL、峰值 RSS、实际算子/层分区、边界复制、fallback 和取消后的资源回收;用相同金提示比较输出 token 与日志,形成一页框架验收矩阵。
实验记录与导出

工程陷阱

避免误判: 把能加载模型当成框架适配完成,或只比较品牌与峰值 tokens/s。必须证明 tokenizer/模板一致、算子实际落到目标 backend、边界复制可解释、fallback 可见,且取消与异常能完整回收会话状态。

核心测试

完成 3 道单选题后提交;提交前不会显示答案。

1. TTFT 主要包含哪些阶段?
2. ONNX Runtime Execution Provider 的职责是?
3. 为什么子图切分可能降低端到端性能?

一起把本章讲清楚

在 GitHub 上讨论本章

登录 GitHub 后提问、补充实测或分享你的实现;评论会保存在本课程的 GitHub Discussions 中。

评论区需要 JavaScript;也可以直接打开 GitHub 讨论区: 打开 GitHub 讨论区 ↗

延伸阅读

启下:下一章如何使用本章能力

下一章从单机 benchmark 扩展到芯片、互连、并行训练、在线服务与 SLO,建立跨尺度性能证据。

下一天: Day 14 · LLM 性能与 Infra