- 博客(1188)
- 收藏
- 关注
原创 推理上下文借用跨越了哪些 Rust 安全边界
一个推理上下文通常包含 KV 状态、工作缓冲和当前序列位置。把它借给一次生成任务,看起来只是传入。但外部推理库可能保存输入指针,设备任务也可能在 Rust 函数返回后继续访问这些内存。此时,源码里已经结束的借用,不一定对应真正结束的使用。安全封装需要把运行时的使用区间映射到 Rust 生命周期。先分清模型、上下文、单次请求三层,再决定引用该放在哪里。
2026-11-07 18:33:46
331
原创 模型句柄所有权跨越了哪些 Rust 安全边界
把 C 推理库的model*包进 Rust 结构体,真正困难的部分往往还没开始。指针不是空值,并不代表模型仍然存活;结构体实现了Drop,也不能证明另一个线程没有同时调用释放函数。这里讨论一种明确的场景:外部库创建模型并交回一个句柄,调用方负责释放;推理上下文可能依赖该模型。目标是让安全 Rust 调用者不必重复处理这份外部契约。以下代码是绑定层骨架,外部符号须由所接入的运行时提供,不是一套可直接连接任意引擎的通用 API。
2026-11-07 00:06:22
349
原创 推理取消令牌需要模型运行时提供什么能力
取消与自然结束可能同时发生。假设最后一个 token 已经产生,但取消信号也刚到达,服务究竟返回成功还是取消?两种策略都能成立,前提是整个链路一致。可以让已经提交并确认的完整结果优先,或者让尚未发送的结果服从取消状态。不要在 HTTP 层采用一种规则、日志里采用另一种规则,否则成功率和取消率会互相打架。流式输出还要划清“已发送”和“待发送”。取消不能收回已经发给客户端的 token,却可以丢弃尚未发送的后续输出。输出队列需要有界,否则计算停止后,队列里仍可能保留大量结果和引用。
2026-11-07 00:05:45
320
原创 常量折叠成立需要哪些语义前提
表达式的输入都是常量,编译器就可以提前求值吗?对一个只有自然数加法的教学语言,答案很接近“可以”。换成带位宽、溢出规则、浮点异常和状态读取的真实推理图,这个判断就不够用了。常量折叠的目标是把计算换成一个等价常量。省掉一次运行时操作只是结果;等价性才是允许替换的依据。
2026-11-07 00:05:00
340
原创 分块矩阵乘法在哪些输入形状下容易出错
分块矩阵乘法在方阵、整齐倍数尺寸上很容易写对。真正能检验实现的,是三个维度同时不整齐的时候:输出最后一行、最后几列,以及归约维度不足一块的那段乘加,分别受不同边界控制。设行主序矩阵 A 的形状为 M×K,B 为 K×N,结果 C 为 M×N。本文只讨论二维、连续、无转置的乘法,先把数值正确性与索引边界说清,再考虑布局和并行优化。
2026-11-07 00:03:11
356
原创 推理缓存一致性如何区分随机差异与真实退化
本地推理里,“缓存”可能指已经分词的输入、提示前缀的 KV 状态,也可能是整段答案。它们的命中条件不同。本文重点讨论前缀 KV 缓存:复用已计算的前缀状态,让后续解码从正确位置继续。一个可用于回归的请求身份,至少应说明模型与权重版本、分词器及聊天模板、前缀 token ID、位置编码相关配置,以及会影响该状态的适配器或计算设置。具体键字段必须随运行时能力调整,不能只哈希可见文本。相同字符串经过不同模板,可能得到不同 token 序列;相同 token 在不同位置配置下,也不必拥有可互换的缓存。
2026-11-06 23:57:25
343
原创 给本地推理引擎实现推理错误转换
原生推理接口返回一个整数,Rust 服务需要返回一个能被调用者处理的错误。这两者之间不应该只有。没有阶段、类别和原始原因,调用方无法判断该修改输入、等待资源、结束请求,还是隔离实例。错误转换的目标,是把底层信息放进稳定的服务语义里,同时保留追查原因的入口。它不负责修复已经损坏的内存,也不能把一次不符合 ABI 的调用变成安全操作。
2026-11-05 14:31:34
315
原创 在 Rust 执行器实现生成任务有界接入
给推理接口加一个并发信号量,并不一定让服务有界。如果每条请求先创建任务,再在任务内部等待许可,运行中的推理数量虽然受控,等待中的任务、输入文本和会话对象仍可能持续增长。流量超过处理能力时,内存会先替队列承担压力。有界接入需要同时约束等待队列、正在执行的工作,以及每个请求可能占用的上下文资源。三个限制各自回答不同问题,不能只保留一个叫作的数字。
2026-11-05 14:30:20
366
原创 用 Rust 写出算子依赖排序最小实现
计算图中,节点在文件里的顺序不一定是可以执行的顺序。一个加法节点只有等两个输入都准备好,才有资格进入就绪队列。拓扑排序做的就是这件事:为一个有向无环图找到满足依赖的节点序列。本文实现最小的 Kahn 算法,输入是一组节点及其依赖编号,输出是一种合法顺序。它处理静态数据依赖,不负责设备异步完成、内存回收和跨请求调度。先把“先后关系”算对,再给执行器增加并发能力。
2026-11-05 14:29:06
374
原创 建立累加精度选择的参考实现
这个算子使用 FP16”并不足以描述它的数值行为。权重可以用 FP16 存储,乘法使用某种设备指令,部分和进入 FP32 累加器,最后再转回 FP16。另一个内核即使输入输出类型一样,中间舍入和归约顺序也可能不同。参考实现首先要把这些阶段拆开,明确每一步的类型和规则。本文给出选择与验证思路,不把任何一种累加器宣布为所有模型、所有设备上的最佳方案。
2026-11-05 14:27:48
301
原创 为本地推理服务搭建上下文长度边界
用户提交的文本没有超过字符限制,推理却在 prefill 或生成阶段失败,常见原因是服务把字符数当成了上下文长度。模型实际消费的是 token,而 token 还包含聊天模板、角色标记和特殊符号。只统计输入框里的文字,预算从一开始就少算了一部分。上下文边界应在请求准入时确定,并在执行时再次验证。它同时涉及模型语义、运行配置和资源容量,不能用模型介绍页上的一个最大长度替代全部检查。
2026-11-05 14:26:35
235
原创 给本地推理引擎实现生成回调生命周期
本地推理引擎通过 C 回调报告 token 或执行进度时,通常只接受函数指针和一个void*用户数据。Rust 闭包里的借用关系不会自动穿过这道 ABI。原生端保存了地址,编译器却不知道它何时再来访问;这正是回调封装最需要解释清楚的部分。设计可以从一个问题开始:注销函数返回时,是否保证所有正在执行的回调都已结束?若这个答案不明确,释放用户数据的时机就没有依据。函数指针能调用成功,只能证明连接已经建立,不能证明生命周期正确。
2026-11-04 20:56:53
245
原创 在 Rust 执行器实现推理实例预热
模型文件加载成功,只能说明权重和一部分运行时对象已经建立。首次执行还可能触发内核选择、图编译、设备缓冲分配和页面访问。如果实例一加载完就进入负载均衡,第一批请求会替服务完成这些工作,启动成本也会混进用户延迟。预热的目的,是在受控输入和资源预算下走过关键执行路径,并检查实例能否完成一次有效推理。它无法消除所有冷路径,也不应该被当作跳过容量验证的理由。
2026-11-04 20:55:40
339
原创 用 Rust 写出张量形状推断最小实现
形状推断常被写成几行向量拼接:拿到输入的最后两个维度,生成输出形状。这样做忽略了一个更基本的问题:输入是否满足算子契约?如果归约维不相等,正确结果是报告错误,而不是继续产生一个看起来合理的输出形状。本文实现二维矩阵乘法的保守形状推断。它支持已知和未知维度,显式保留尚未证明的约束,不覆盖广播批维、向量提升或完整的 ONNX MatMul 语义。
2026-11-04 20:54:29
370
原创 建立量化尺度广播的参考实现
量化权重已经解包成整数,接下来只要乘尺度,看起来没有太多可写。真正的麻烦在索引:这个尺度属于整个张量、某个通道,还是某个分块?一旦选错,代码仍然能生成长度正确的浮点数组,数值却已经失去原来的含义。本文建立一个范围明确的参考实现:行主序 int8 矩阵Q[M,N],每行一个尺度和零点,输出。它只覆盖这项自定义约定,不是完整 ONNX 算子或所有低比特模型的通用反量化器。
2026-11-04 20:53:18
234
原创 为本地推理服务搭建采样参数锁定
一次回归出现不同回答,先检查 seed,发现没有变,就把差异归到模型头上,这条排查路径往往太短。采样受温度、候选截断、惩罚项、采样器顺序、随机数状态和停止规则共同影响;模型与运行内核也会改变进入采样器的 logits。采样参数锁定需要让每条请求拥有一份经过验证、执行期间不再变化的有效配置。它能减少不必要的变量,帮助解释差异,但不能保证不同硬件和后端逐 token 一致。
2026-11-04 20:52:05
261
原创 给本地推理引擎实现分词缓冲区封装
分词接口看起来只是把字符串变成一串整数。真正需要封装的,是这串整数所在的内存:谁负责分配,失败后哪些位置仍可读取,下次分词能否复用,以及推理任务拿到的切片能活多久。把这些问题交给调用者,每个接入点都会重新写一遍边界判断。本文讨论同步分词接口的缓冲区设计。分词器可以来自 C 库,也可以是 Rust 实现;先把安全接口立住,再把具体 ABI 放进一个小适配层。这里不声称某个容量策略已经带来性能收益。
2026-11-03 23:47:20
303
原创 在 Rust 执行器实现会话上下文回收
本地推理服务通常希望保留模型权重,把上下文反复借给不同请求。问题出在“归还”二字:HTTP 请求已经结束,不等于生成循环已经退出;生成循环退出,也不等于 GPU 已经不再使用那块内存。若把这些时刻合并为一次drop,下一位租用者可能读到上一轮状态,甚至和尚未结束的计算同时修改上下文。回收应是一段由执行器负责完成的协议。请求侧只能提出结束意图,负责持有原生上下文的执行单元才有权宣告它可复用。
2026-11-03 23:46:11
293
原创 用 Rust 写出算子融合条件最小实现
计算图里出现Add → ReLU,并不意味着编译器可以直接把两个节点合成一个。中间结果可能还有别的用户,输入可能需要广播,后端也可能只支持某些布局。融合逻辑若只比较算子名字,错误会藏在那些“看起来差不多”的图中。本文限定一个可检查的小问题:在同一基本块内,把形状相同、类型相同的纯逐元素加法与 ReLU 替换为后端已经定义好的融合算子。它是合法性判断的最小起点,不是任意图融合器。
2026-11-03 23:44:57
368
原创 建立低比特权重解包的参考实现
看到“4 bit 权重”,容易先想到把一个字节拆成两个整数。真正容易出错的地方,是拆出的整数对应哪个权重、如何解释符号、该乘哪一个尺度,以及尾部填充是否应该参与计算。把这些约定写明白,标量解包代码才有资格成为 SIMD 或 GPU 内核的参考答案。本文定义一个用于讲解的自有格式:两个有符号 int4 使用二进制补码存进一个字节,低半字节保存较早的权重,每个数据块有一个正的有限f32尺度,反量化为。它不是 GGUF、Q4_0、GPTQ 或 AWQ 的格式说明。
2026-11-03 23:43:45
235
原创 为本地推理服务搭建量化版本对照
同一个提示词分别跑两个量化版本,看到回答都通顺,就认定精度没有变化;再各跑一次耗时,较快的那个成为“最优版本”。这样的对照很容易同时受到分词、随机采样、缓存状态和设备负载影响。最后比较的可能是两条不同的执行路径,而不只是量化误差。量化版本对照需要把问题拆开:模型输出发生了什么变化,运行成本发生了什么变化,以及这些变化是否符合当前服务的接受标准。本文只建立对照方法,不提供虚构的跑分或准确率。
2026-11-03 23:42:32
314
原创 给本地推理引擎实现推理上下文借用
推理上下文经常同时接触三类数据:长时间驻留的模型、一次调用传入的 token,以及由引擎暂存的 logits。它们的寿命不同,安全接口也不应把它们都包装成“随便拿着的指针”。一个实用的起点是按依赖方向拆开:上下文依赖模型;解码调用在限定时间内读取输入;输出视图借用上下文内部存储。先说明谁依赖谁,再决定哪些地方需要复制。
2026-11-02 12:42:36
1930
原创 在 Rust 执行器实现多请求长度分桶
把长短差很多的请求塞进同一个批次,可能浪费填充计算;把请求拆得太细,又可能让每个桶都凑不满。长度分桶要处理的就是这两个成本,而不是单纯把列表排个序。本文讨论需要按共同长度填充的稠密预填充批次。使用 ragged tensor、分页注意力或连续批处理的运行时,具体执行方式会不同,不能把“批次最大长度乘请求数”当成所有引擎通用的实际计算量。
2026-11-02 12:41:22
2040
原创 建立注意力掩码处理的参考实现
注意力结果不对时,掩码经常比矩阵乘法更值得先查。维度能广播、程序能运行,并不表示可见的位置就是预期位置。尤其加上 KV 缓存之后,query 的局部下标和它在完整序列中的位置会分开。参考实现的任务是把可见性规则写明白,再把它变成概率。这里使用约定:布尔值true表示允许参与注意力;不允许的位置概率为零。
2026-11-02 12:40:11
2402
原创 为本地推理服务搭建固定提示集校验
升级推理引擎、替换量化权重或调整采样参数之后,服务还能正常回答,并不能说明行为没有退化。随手输入几个问题,也很难分辨变化来自模型、模板还是执行路径。固定提示集的作用,是把一组有明确用途的输入保留下来,让不同版本在可核对的条件下运行。它是日常回归的证据来源,不是覆盖所有任务的能力榜单。
2026-11-02 12:39:01
2095
原创 用 Rust 写出中间缓冲复用最小实现
假设 A 产生一个张量,B 和 C 都读取它。即使 B 已经结束,只要 C 还会读取,A 的存储就不能交给另一个张量。按节点编号判断“前一个算子跑完了”,在有分支的图里不够可靠。可以在执行计划中记录每个值的使用者,等最后一个使用者完成后才归还存储。若操作有别名或视图,还需要追踪底层存储:张量对象没有了,不代表引用同一块内存的另一个视图也结束了。图中的“最后一次读取”还区分提交和完成。CPU 已经把最后一个读任务送入设备队列,不代表 GPU 已经读完。
2026-11-02 12:37:52
2254
原创 用 Rust 写出常量折叠最小实现
计算图里有一段只依赖常量的表达式,为什么还要等到推理时再算?常量折叠会在编译或准备阶段求出它的结果,用一个常量节点替换原来的计算节点。这个想法很短,实现却有一个不能省略的前提:编译阶段的求值规则必须和原程序一致。整数溢出怎么办,浮点舍入遵守什么规则,节点是否会读外部状态,都应先回答。下面用一个只含整数加法的小型表达式树,把这条边界写清楚。
2026-11-01 19:51:25
2147
原创 给本地推理引擎实现模型句柄所有权
模型一般持有权重及相关后端资源,上下文持有会话状态和工作缓冲。模型加载成功,不代表创建上下文也一定成功。反过来,创建出来的上下文也不必然拥有模型。图中三条线是封装必须维持的依赖关系。生成结束后才允许销毁上下文,最后才释放模型。虚线表示上下文借用模型,并没有复制出另一个模型所有者。图里的尤其值得留意:非空只排除了一个指针值,没有证明地址有效,更没有附送一次释放权限。以 llama.cpp 的接口为例,模型释放和上下文释放是不同操作。项目接入时应把所用提交固定下来,再核对llama_free等接口的契约。
2026-11-01 19:50:14
2198
原创 建立分块矩阵乘法的参考实现
推理引擎里的矩阵乘法一旦引入打包、向量指令或低精度内核,出错位置就很难从最终输出反推。先保留一份结构清楚的参考实现,可以把“计算语义是否正确”和“调度是否高效”分开检查。这里实现行主序、无转置的C = A × B:A 的形状是 M×K,B 是 K×N,C 是 M×N。它不覆盖完整 BLAS GEMM 的 alpha、beta、转置和 leading dimension 参数,也不承担最快内核的角色。
2026-11-01 19:49:00
2245
原创 在 Rust 执行器实现推理取消令牌
用户关掉生成窗口,HTTP 请求结束,推理线程却可能还在计算。把这三个时刻当成同一个时刻,是本地推理服务最容易出现的取消错误:前端已经返回“已停止”,后台仍然持有上下文;回收逻辑又把上下文放回池里,下一次请求就与上一段计算撞在一起。取消令牌负责传递停止意图。真正停止在哪个位置、谁确认退出、什么时候归还资源,仍然要由执行器明确安排。
2026-11-01 19:47:50
2262
原创 为本地推理服务搭建推理缓存一致性
两个请求的提示词看起来一样,就能共用推理缓存吗?需要先说明缓存存了什么。完整回答缓存保存的是输出;前缀 KV 缓存保存的是特定模型处理过一段 token 后的中间状态。它们的命中条件不同,失效条件也不同。这里讨论前缀 KV 缓存。目标是在满足相同计算语义的条件下复用已处理的前缀,而不是简单地用提示词字符串当键。
2026-11-01 19:46:35
2251
原创 量化网关该做哪些决定:格式、硬件和质量策略不能少一个
按层类型决定“FFN 一律 MXFP4、Attention 一律 FP8”,听起来整齐,却可能把不支持的硬件、缺失的尺度布局和任务质量要求藏起来。量化网关应解释为什么接收某条路径,以及不满足条件时怎样回退。2026 年 10 月列出多种格式与并行能力。把它落成自己的网关时,应该按具体版本建立能力表,不能把一个宣传列表变成无限通用的 bool。checkpoint 格式、尺度布局、硬件和质量策略共同决定接收与回退。图列出检查项,实际分支语义以下方代码为准;回退需要另有通过验证的产物。这里的。
2026-10-31 23:07:15
2235
原创 十月技术笔记的收口:留下能复用的反例,不虚报完成的引擎
这份月末笔记写给准备收拢工作的人。标题里的路线图,应该与已经实现的功能分开。回看推理优化,值得留下的是下一次仍能拿来用的证据:一个尾块反例、一种明确格式、一条回收规则、一份能复现的命令。月末归档保留证据、限制与下一项验证,而不是虚构业绩。
2026-10-31 23:06:07
2206
原创 跨平台 GEMM 后端:先共用测试,再分别谈 AVX 与 NEON
把 traits 里的类型名换成 AVX2、AVX-512、NEON,还不能得到跨平台 GEMM。不同指令的 lane 数、寄存器数量、对齐与尾部策略需要各自实现;未编译、未执行的后端应标成计划,不能出现在实测结果表里。说明了目标与别名条件对代码生成的影响。跨平台设计可以共享算子契约和参考结果,具体内核与运行时分派仍由目标负责。最早应该统一的是测试输入,而不是寄存器名字。多个 ISA 后端共用参考合同和验证输入,各自承担实现与测量。图为架构规划,方块不代表具体数据元素位宽,也不表示这些后端已经在本地运行。
2026-10-31 23:05:00
2256
原创 编译器 Pass 管线:每次改图之后,旧分析还有效吗
一个 Pass 返回 true,后面再打印“节点减少 68%”,并不表示编译器完成了优化。要证明变换发生,需要看真实 IR;要证明合法,需要看不变量;要证明有收益,还要执行目标程序。三种证据不能由一个硬编码布尔值代替。提供了分析与变换的具体职责。小型 AI 编译器可以先建立一个可信的解释器与验证器,再逐个加入变换,避免一开始就把解析、融合、内存池和代码生成混成一个不可检查的大函数。验证与分析伴随变换推进,改图后重新确认失效分析。图中的源码和机器码纸条仅是阶段符号,不是一组已经汇编验证的输入输出。
2026-10-31 23:03:55
2252
原创 Attention 与 SSM 混合架构:先算状态内存,再讨论检索能力
说 Attention 内存是 O(N),必须先说明是哪一部分。自回归解码保存的 KV 随上下文长度增长;朴素训练里的分数矩阵又是另一个对象。一次 decode 扫过已有 KV 的读取量,与生成整段序列的总读取量也不是同一口径。最初提交于 2023 年,使用选择性状态空间建模序列。固定维度的递推状态可以让单步状态存储不随历史长度增长,但这不等于可以无损保存任意长历史,也不等于所有检索任务都更好。Attention 的增长 KV 与递推模型的固定状态具有不同内存模式。
2026-10-31 23:02:45
2292
原创 MXFP4 的尺度字节:线格式是 17 字节,结构体未必是
32 个 FP4 元素需要 16 字节载荷,再加一个尺度字节,逻辑编码是 17 字节。若把这样的字段放进一个要求 16 字节对齐的结构体,数组步长却可能是 32 字节。压缩率应基于真实存储布局计算,不能只把字段大小相加。给出 MXFP4 的 32 元素块和 E8M0 共享尺度。E8M0 不能被简化成“存一个有符号 int8 指数”:格式有自己的编码与偏置,必须按规范解释。紧密线格式与对齐内存格式有不同步长,不能混算压缩率。保存下列代码为layout.rs,用验证当前目标的布局。这里选择。
2026-10-30 13:18:40
1959
原创 小 batch Attention 的并行:线程池生命周期也在成本里
单 query 面对很长 KV 时,可以沿 KV 维度分块,再合并各块统计量。这个思路到了 CPU,并不意味着每生成一个 token 都应新建一批线程。线程创建、调度与归约都要付成本;短上下文里,它们甚至可能比点积更重。讨论的是 GPU 上的分块与末级归约。把算法关系迁移到 CPU 是一个可研究方向,性能结论仍需重新测量。下面使用持久线程池展示任务生命周期,工作内容是普通整数分块求和,不是 Attention 内核。持久工作池供应分块任务,局部结果经正确归约形成输出。
2026-10-30 13:16:58
2164
原创 NUMA 优化别只绑线程:CPU 归属与内存策略是两份配置
把线程固定到编号相邻的 CPU,不足以证明线程都在同一个 NUMA 节点。把打印出来,也不会改变已经分配的内存来自哪里。线程亲和性和内存放置需要分别验证,再通过实际拓扑联系起来。说明内存策略决定从哪些节点分配页面,并区分任务、VMA 等范围;cpuset 约束还可能优先于策略。内存页何时被实际触碰和分配,同样会影响观察结果。线程亲和性与内存放置分别约束 CPU 和页面所在位置。图是机制示意;本地与远端必须相对实际访问 CPU 判断,不能据此推定机器的真实节点连线。
2026-10-30 13:14:36
2194
原创 SSA 的 Phi 合并:局部 load 转发还不是 Mem2Reg
一个基本块里,刚写入变量又读回来,可以用最近的值替换那次读取。跨过 if 分支后,变量可能有两种定义;这时只记“最近一次 store”就不够了。SSA 需要按控制流选择到达的定义,Phi 节点正是这个关系的表示。描述了可提升 alloca、支配边界放置 Phi 与重写访问。它不是“删掉所有 load/store”,也不等于物理寄存器永远不会溢出。寄存器分配发生在后续层次。Phi 根据实际到达的前驱选择定义,再进入后续使用。
2026-10-30 13:12:34
2198
空空如也
空空如也
TA创建的收藏夹 TA关注的收藏夹
TA关注的人
RSS订阅