跳到正文
Hedon Garden

组件系统

所有组件共用一个接口:一个名字、一组有类型的参数、可选的 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。

内容:原文内容

步骤 steps

有先后顺序的过程,编号和竖线自动生成。

写法 · remark-directive 容器

:::steps
1. **Prefill**:一次性处理 prompt,算出并缓存全部 K/V,生成第一个 token。
2. **Decode**:把新 token 喂回去,只为它算 Q/K/V。
3. **追加**:新的 K/V 追加进 Cache,Q 只用于本步查询。
:::

渲染效果

  1. Prefill

    一次性处理 prompt,算出并缓存全部 K/V,生成第一个 token。

  2. Decode

    把新 token 喂回去,只为它算 Q/K/V。

  3. 追加

    新的 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 个用户并发
```

渲染效果

≈ 2GB
一个 4096 token 的请求
≈ 32GB
16 个用户并发
≈ 64GB
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 = 12 GB
B = 1632 GB
B = 3264 GB
L = 32、Hkv = 32、D = 128、T = 4096、BF16

内容:数字来自原文的计算示例

提示框 callout

沿用已定稿的六种提示框,语法就是 Typora 的警告框。

写法 · GitHub alerts

> [!NOTE]
> 上面说的是同一次请求内部的 K/V 复用(Decode 收益最大)。

渲染效果

提醒

上面说的是同一次请求内部的 K/V 复用(Decode 收益最大)。

内容:原文内容

可放大图片 figure

普通 Markdown 图片。点击或回车放大,Esc 关闭。

写法 · 标准 Markdown

![KV Cache Decoding Workflow](https://hedonspace…/workflow.png "KV Cache Decoding Workflow")

渲染效果

KV Cache Decoding Workflow
KV Cache Decoding Workflow

内容:原文内容

代码组 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 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。

内容:内容改写自原文

小测验 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。
```

渲染效果

公式 2 × L × T × Hkv × D × B × b 里,开头的 2 是什么?

内容:题目依据原文符号表

任意嵌套

标签页 → 步骤 → 提示框,另一个标签里放图表和警告。外层容器多一个冒号。

写法 · 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]
> 历史越长,读得越多。读得越多,读得越慢。
:::
::::

渲染效果

  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

    提醒

    而且随着生成,它还会继续膨胀。

内容:数字来自原文的计算示例 + 原文句子

可信 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>
```

渲染效果

内容:示意图,内容来自原文例句

沙箱块 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[] };
}