全部文档/系列目录
inferserver 的数学基础
这一页写给这样的读者:本科数学学过,但计算过程已经记不清了。所以不假设你记得导数表、方差公式、矩阵乘法规则;每用一个概念,就在原地定义并推出来。
内容只覆盖这个仓库真正用到的数学:softmax、交叉熵 / KL、RMSNorm、线性层、SiLU / SwiGLU、残差、注意力、RoPE、GQA、KV cache、paged attention 的 online softmax、采样、batching 与浮点误差。每一节末尾都标出对应的 Go 代码。
第 15 节是补章:奇异值分解。它不属于每个 token 都要算的公式,但解释了 LoRA 的低秩增量、KV cache 压缩、谱范数误差估计背后共用的那条假设,所以单独讲一遍。
公式用本地 KaTeX 渲染(assets/katex),断网也能看。整个目录是静态 HTML,直接双击打开即可。
怎么读
- 第一次:从第 0 节顺序读到第 15 节。每节都先给定义,再给推导,最后给“容易写错的地方”。
- 配对代码看:读完一节,打开它末尾标出的 Go 文件;公式和代码行基本一一对应。
- 查公式:附录 A 是一页表格,列出所有公式和它们在仓库里的位置;附录 B 反过来按文件索引。
- 想快速建立直觉:先读第 1、7、10、11 节,这四节覆盖了推理服务里最核心的数学。
章节
-
只定义后面会用到的语言:内积、矩阵乘向量、指数对数、四条求导规则、期望与方差。
-
从“证据可加、相对概率只看差值”这个要求解函数方程,推出指数形式;再证明减最大值是恒等变形,并给出 Jacobian。含温度滑块 demo。
-
自信息 → 熵 → 交叉熵 → KL,Gibbs 不等式一行证完;困惑度就是损失的指数。
-
先验证 LayerNorm 的均值 0 / 方差 1,再说明 RMSNorm 省了什么;尺度不变性与 Jacobian;Qwen3 的 per-head QK-Norm。
-
矩阵乘向量的 FLOP 计数;把 Qwen3-0.6B 的 0.596B 参数、2.22 GiB fp32 常驻内存、每 token 1.19 GFLOP 逐项算出来。
-
sigmoid 的对数几率解释与导数;SiLU 求导;SwiGLU 的门控为什么是逐元素乘。
-
为什么残差能把梯度连乘展开成 2^L 条路径;Qwen3 两条残差的准确数据流。
-
点积的方差是 d,所以除以 √d;因果掩码为什么必须加在 softmax 之前;输出是 V 的凸组合;prefill 二次、decode 线性。
-
旋转矩阵保持长度;证明内积只依赖 n−m;频率的几何级数;HF 的 rotate_half 与劈半配对为什么等价。
-
h/G 的映射;省的是 KV 内存和 K/V 投影,不是注意力 FLOPs;224 KiB/token 的来历。
-
链式法则展开;无 cache 是 O(T³),有 cache 是 O(T²);三种 KV 实现的取舍与内存账。
-
块表地址公式;online softmax 的合并为什么数学上恒等;引用计数与写时复制。
-
温度的两种极限;top-k 等价于子集重归一化;top-p 的自适应截断;逆变换采样为什么按累积概率就对。
-
算术强度 B/2 说明 decode 是带宽受限;padding 浪费;chunked prefill 不减少总 FLOPs,优化的是公平性。
-
float32 的 epsilon;四类误差来源;怎么用相对误差判断“是舍入还是公式错了”。
-
矩阵 = 转一次 + 沿正交方向拉伸 + 再转一次;从几何图像推出 \(A=U\Sigma V^\top\);手算一个 \(3\times2\) 例子;Eckart–Young 与截断误差;PCA、伪逆、零空间都是它的推论。
-
一页表格:每个公式 + 它在仓库里的文件与函数。
-
反过来按代码文件索引数学概念,末尾是整条前向公式链。
符号约定
| 符号 | 含义 | 本项目的值(Qwen3-0.6B) |
|---|---|---|
| \(h\) | 隐藏维度 hidden_size | 1024 |
| \(L\) | 层数 | 28 |
| \(H,\ H_{kv}\) | Q 头数 / KV 头数 | 16 / 8 |
| \(d\) | 每头维度 head_dim | 128 |
| \(V\) | 词表大小 | 151936 |
| \(B\) | 每块 token 数(分页) | 基准代码用 16 |
| \(T\) | 序列长度 | — |
| \(\epsilon\) | 归一化的数值稳定项 | \(10^{-6}\) |
| \(\theta_{\text{base}}\) | RoPE 频率基数 | \(10^6\) |
数学公式里下标从 1 开始,Go 代码从 0 开始。看到 \(i\) 与 \(i+1\) 的差异时,先确认是哪种约定。