组件系统
所有组件共用一个接口:一个名字、一组有类型的参数、可选的 Markdown 子内容。写法只有三种,并且在 Typora 里都能读:围栏代码块放数据,:::容器放正文,> [!TYPE] 放提示。再留两个逃生口:可信的 html 块和隔离的 sandbox 块。
例子尽量取自《大模型推理:KV Cache 的底层原理、挑战与应对》;每个例子下方注明来源,非原文的都标为「示例」。
三种写法,一棵树
| 写法 | 放什么 | Typora 里看到的 | 例子 |
|---|---|---|---|
围栏代码块 ```garden:名字 | 数据驱动的叶子组件 | 一段高亮的 YAML,数据原样保留 | chart、stat、compare、quiz、playground |
容器 :::名字{参数} | 包着 Markdown 正文的组件 | 正文正常渲染,::: 行显示为普通文字 | tabs、steps、faq、code-group、grid |
警告框 > [!TYPE] | 提示 | Typora 原生警告框 | callout |
三种写法解析后都变成同一种节点 { name, props, children },由同一个注册表渲染,所以任何组件都能放进任何容器。
标签页 tabs
同一件事的几个并列视角。←/→ 键切换。
写法 · remark-directive 容器
::::tabs{label="两个阶段"}
:::tab{title="Prefill"}
模型一次性处理已有上下文 `The cat is very cute`,
算出并缓存这段的 K/V,同时生成第一个新 token `It`。
:::
:::tab{title="Decode"}
再把 `It` 喂回去,生成 `seems`,然后继续往后。
只要算最新输入 `It` 的 K/V,追加进缓存即可。
:::
::::渲染效果
模型一次性处理已有上下文 The cat is very cute,算出并缓存这段的 K/V,同时生成第一个新 token It。
再把 It 喂回去,生成 seems,然后继续往后。只要算最新输入 It 的 K/V,追加进缓存即可。
内容:原文内容
步骤 steps
有先后顺序的过程,编号和竖线自动生成。
写法 · remark-directive 容器
:::steps
1. **Prefill**:一次性处理 prompt,算出并缓存全部 K/V,生成第一个 token。
2. **Decode**:把新 token 喂回去,只为它算 Q/K/V。
3. **追加**:新的 K/V 追加进 Cache,Q 只用于本步查询。
:::渲染效果
Prefill
一次性处理 prompt,算出并缓存全部 K/V,生成第一个 token。
Decode
把新 token 喂回去,只为它算 Q/K/V。
追加
新的 K/V 追加进 Cache,Q 只用于本步查询。
内容:原文内容
对比卡片 compare
几个方案并排比较。窄屏自动变成一列。
写法 · 围栏代码块 + YAML
```garden:compare
- title: 显存不够用
tension: KV Cache 随序列长度近似线性增长
fix: GQA/MQA、MLA 压缩;FP8 存储;分页分配
- title: 越解码越慢
tension: 每一步都要读取全部历史 KV
fix: 减少每步加载的 KV 体积;高效注意力核
- title: 难管理
tension: 申请与释放造成内存碎片
fix: 像虚拟内存一样分页管理(PagedAttention)
```渲染效果
显存不够用
- 核心矛盾
- KV Cache 随序列长度近似线性增长
- 常见对策
- GQA/MQA、MLA 压缩;FP8 存储;分页分配
越解码越慢
- 核心矛盾
- 每一步都要读取全部历史 KV
- 常见对策
- 减少每步加载的 KV 体积;高效注意力核
难管理
- 核心矛盾
- 申请与释放造成内存碎片
- 常见对策
- 像虚拟内存一样分页管理(PagedAttention)
内容:原文「三个核心挑战」表格的精简
数字卡 stat
一眼要记住的数字。背景透明,只靠字号强调。
写法 · 围栏代码块 + YAML
```garden:stat
- value: 2
unit: GB
label: 一个 4096 token 的请求
- value: 32
unit: GB
label: 16 个用户并发
- value: 64
unit: GB
label: 32 个用户并发
```渲染效果
内容:数字来自原文的计算示例
数据图 chart
从数据生成图表,构建时出 SVG/HTML;附数据表给读屏与 RSS。
写法 · 围栏代码块 + YAML
```garden:chart
type: bar
title: KV Cache 随并发线性膨胀
unit: GB
data:
- { x: "B = 1", y: 2 }
- { x: "B = 16", y: 32 }
- { x: "B = 32", y: 64 }
caption: L = 32、Hkv = 32、D = 128、T = 4096、BF16
```渲染效果
KV Cache 随并发线性膨胀
查看数据表
| 并发 | KV Cache |
|---|---|
| B = 1 | 2 GB |
| B = 16 | 32 GB |
| B = 32 | 64 GB |
内容:数字来自原文的计算示例
提示框 callout
沿用已定稿的六种提示框,语法就是 Typora 的警告框。
写法 · GitHub alerts
> [!NOTE]
> 上面说的是同一次请求内部的 K/V 复用(Decode 收益最大)。渲染效果
提醒
上面说的是同一次请求内部的 K/V 复用(Decode 收益最大)。
内容:原文内容
可放大图片 figure
普通 Markdown 图片。点击或回车放大,Esc 关闭。
写法 · 标准 Markdown
渲染效果

内容:原文内容
代码组 code-group
同一段逻辑的多种语言写法。
写法 · remark-directive 容器
::::code-group
```python title="Python"
def kv_cache_bytes(L, T, H_kv, D, B=1, b=2): ...
```
```ts title="TypeScript"
const kvCacheBytes = (L, T, Hkv, D, B = 1, b = 2) => ...
```
::::渲染效果
def kv_cache_bytes(L, T, H_kv, D, B=1, b=2):
# 2:既有 K,又有 V
return 2 * L * T * H_kv * D * B * b
kv_cache_bytes(32, 4096, 32, 128) # 2147483648 ≈ 2 GBconst kvCacheBytes = (L: number, T: number, Hkv: number,
D: number, B = 1, b = 2) => 2 * L * T * Hkv * D * B * b;
kvCacheBytes(32, 4096, 32, 128); // 2147483648 ≈ 2 GB内容:示例写法,非原文(按原文公式写成)
代码差异 diff
改动前后对比。颜色之外还有 +/− 号和读屏文字。
写法 · 围栏代码块 + YAML
```diff
messages = [
- {"role": "system", "content": f"现在时间:{now}\n" + SYSTEM},
+ {"role": "system", "content": SYSTEM}, # 稳定前缀在前
*history,
+ {"role": "user", "content": f"现在时间:{now}\n" + q}, # 易变内容在后
]
```渲染效果
messages = [
{"role": "system", "content": f"现在时间:{now}\n" + SYSTEM},(删除)
{"role": "system", "content": SYSTEM}, # 稳定前缀在前(新增)
*history,
{"role": "user", "content": f"现在时间:{now}\n" + q}, # 易变内容在后(新增)
]
内容:示例写法,非原文(演示原文结论「稳定前缀在前,易变内容在后」)
参数游乐场 playground
拖动滑块看公式结果变化。在页面内水合,无需 iframe。
写法 · 围栏代码块 + YAML
```garden:playground
formula: 2 * L * t * Hkv * D * B * b
fixed: { L: 32, Hkv: 32, D: 128, b: 2 }
vary:
t: { label: 已有 token 数, values: [512, 1024, …, 65536] }
B: { label: 并发, min: 1, max: 32 }
```渲染效果
每步要读的历史 K/V ≈ 2 × L × t × Hkv × D × B × b (L = 32,Hkv = 32,D = 128,BF16)
≈ 2.00 GB
每多一个 token,每个请求的缓存就多 512 KB。
内容:公式与固定参数来自原文
折叠问答 faq
原生 details/summary,不依赖 JS,Typora 里也能折叠。
写法 · remark-directive 容器
:::faq
### KV Cache 和 Prefix/Prompt Cache 是一回事吗?
不是。KV Cache 说的是同一次请求内部的 K/V 复用……
### 为什么缓存 K/V,不缓存 Q?
每步只需为最新输入算新的 Q/K/V……
:::渲染效果
KV Cache 和 Prefix/Prompt Cache 是一回事吗?
不是。KV Cache 说的是同一次请求内部的 K/V 复用,Decode 收益最大。Prefix/Prompt Cache 是在此之上跨请求复用已有前缀的 K/V,主要省的是 Prefill。
为什么缓存 K/V,不缓存 Q?
每步只需为最新输入算新的 Q/K/V:新的 K/V 追加进 Cache 供后续步骤使用,Q 只用于本步查询,用完即弃。
KV Cache 的收益出现在哪个阶段?
Decode。预测 seems 时不必重算 The cat is very cute 的 K/V。
内容:内容改写自原文
链接卡片 link
单独一行的外链变成卡片,显示站点、标题、摘要和日期。
写法 · 标准 Markdown
<!-- 单独一行的裸链接自动变成卡片,元数据在构建时抓取 -->
https://huggingface.co/blog/not-lain/kv-caching内容:原文推荐阅读
小测验 quiz
读完检查自己是否理解。纯前端判分,不收集数据。
写法 · 围栏代码块 + YAML
```garden:quiz
question: 公式 2 × L × T × Hkv × D × B × b 里,开头的 2 是什么?
options:
- 既有 K,又有 V
- BF16 每个数占 2 字节
- Prefill 和 Decode 各存一份
answer: 0
explain: 2 是因为既有 K,又有 V;每个数的字节数是末尾的 b。
```渲染效果
内容:题目依据原文符号表
任意嵌套
标签页 → 步骤 → 提示框,另一个标签里放图表和警告。外层容器多一个冒号。
写法 · remark-directive 容器
::::tabs{label="单请求与并发"}
:::tab{title="单个请求"}
::steps
1. **数参数**:L = 32、Hkv = 32、D = 128、T = 4096、BF16
2. **代入公式**:2 × 32 × 4096 × 32 × 128 × 1 × 2
3. **得到** 2,147,483,648 字节 ≈ 2 GB
> [!NOTE]
> 而且随着生成,它还会继续膨胀。
::
:::
:::tab{title="并发"}
```garden:chart
title: KV Cache 随并发线性膨胀
data: [{x: "B = 1", y: 2}, {x: "B = 16", y: 32}, {x: "B = 32", y: 64}]
```
> [!WARNING]
> 历史越长,读得越多。读得越多,读得越慢。
:::
::::渲染效果
数参数
L = 32、Hkv = 32、D = 128、T = 4096、BF16
代入公式
2 × 32 × 4096 × 32 × 128 × 1 × 2得到
2,147,483,648 字节 ≈ 2 GB
提醒
而且随着生成,它还会继续膨胀。
KV Cache 随并发线性膨胀
查看数据表
| 并发 | KV Cache |
|---|---|
| B = 1 | 2 GB |
| B = 16 | 32 GB |
| B = 32 | 64 GB |
警告
历史越长,读得越多。读得越多,读得越慢。
内容:数字来自原文的计算示例 + 原文句子
可信 HTML 块 html
作者自己写的 HTML,直接内联进页面。用声明式 Shadow DOM 隔离样式,但仍然继承站点的颜色变量,所以深色模式自动生效。
写法 · 围栏代码块 + YAML
```html garden
<template shadowrootmode="open">
<style>.t{border:1px solid var(--rule-strong)} …</style>
<div class="row" role="img" aria-label="…">
<span class="t pre">The</span> … <span class="t new">It</span>
</div>
</template>
```渲染效果
蓝底:Prefill 一次算完并缓存 K/V;虚线:Decode 每步只为新 token 算 K/V。
内容:示意图,内容来自原文例句
沙箱块 sandbox
任意脚本:在 iframe srcdoc 里运行,sandbox="allow-scripts"、无同源权限、CSP 禁止联网。父页面把颜色变量发进去,iframe 回传高度自动撑开。
写法 · 围栏代码块 + YAML
```sandbox title="KV Cache 显存计算器" height=220
<label>层数 L <input id="L" type="number" value="32"></label>
…
<script>
// 计算并 postMessage({type:"garden:resize", height})
</script>
```渲染效果
内容:公式与默认参数来自原文
组件接口
每个组件都是一个注册项。ChatGPT 式的数据驱动组件就是只有 props、没有 children 的组件:LLM 按 JSON Schema 产出 YAML/JSON,校验通过才渲染,失败时显示 fallback 文本。
interface GardenComponent<P> {
name: string; // "tabs"、"chart"…
props: ZodType<P>; // 同时导出 JSON Schema 给 LLM
children?: "none" | "markdown" | { only: string[] };
render(props: P, children: ReactNode): ReactNode; // 构建时 SSR
hydrate?: () => Promise<Hydrator>; // 需要交互才加载
fallback(props: P, text: string): string; // RSS / 无 JS / 搜索
a11y: { role?: string; keyboard?: string[] };
}