[{"data":1,"prerenderedAt":1801},["ShallowReactive",2],{"post-\u002Fposts\u002Fmini-llm-engine-deep-dive":3,"all-posts-nav":1532},{"id":4,"title":5,"body":6,"categories":1514,"date":1516,"description":1517,"draft":1518,"extension":1519,"hidden":1518,"meta":1520,"navigation":242,"path":1521,"published":1518,"seo":1522,"stem":1523,"tags":1524,"__hash__":1531},"posts\u002Fposts\u002Fmini-llm-engine-deep-dive.md","讲透 mini-llm-engine：从显存碎片到六大推理优化",{"type":7,"value":8,"toc":1496},"minimark",[9,19,28,31,36,44,47,52,60,70,73,77,83,86,91,93,97,100,106,121,123,127,131,137,159,342,345,379,386,388,392,398,401,407,413,416,517,523,525,529,535,547,553,564,641,648,679,682,684,688,694,699,780,791,868,871,877,885,894,896,900,903,917,998,1004,1011,1013,1017,1020,1032,1038,1041,1047,1057,1277,1280,1282,1286,1291,1301,1306,1320,1325,1328,1330,1334,1340,1342,1346,1349,1358,1381,1393,1401,1409,1411,1414,1492],[10,11,12,13,18],"p",{},"上一篇写了 ",[14,15,17],"a",{"href":16},"\u002Fposts\u002Fmini-llm-engine-from-scratch","mini-llm-engine 的实现过程","，有人问我能不能把原理再讲清楚一点。这篇就专门讲\"为什么\"——为什么要这么设计，每个优化到底解决了什么问题。",[10,20,21,22],{},"项目地址：",[14,23,27],{"href":24,"rel":25},"https:\u002F\u002Fgithub.com\u002Fliangqianxing\u002Fmini-llm-engine",[26],"nofollow","github.com\u002Fliangqianxing\u002Fmini-llm-engine",[29,30],"hr",{},[32,33,35],"h2",{"id":34},"一背景llm-推理的两个核心瓶颈","一、背景：LLM 推理的两个核心瓶颈",[10,37,38,39,43],{},"跑一个 LLM（比如 GPT-2、LLaMA）时，显存里有一个叫 ",[40,41,42],"strong",{},"KV Cache"," 的东西。",[10,45,46],{},"Transformer 每一层都有 Attention，计算时需要存当前 token 之前所有 token 的 Key 和 Value，这就是 KV Cache。它让 decode 每步只算当前 token，而不是重算整个序列。",[48,49,51],"h3",{"id":50},"瓶颈一显存碎片利用率只有-17","瓶颈一：显存碎片（利用率只有 17%）",[10,53,54,55,59],{},"传统框架一开始就给每个请求预留 ",[56,57,58],"code",{},"max_seq_len"," 个 token 的空间：",[61,62,67],"pre",{"className":63,"code":65,"language":66},[64],"language-text","请求进来，分配 128 token 的显存槽：\n[prompt:20tok | output:15tok | ......空的...... ]\n                                ↑ 93 个 token 的空间浪费了\n","text",[56,68,65],{"__ignoreMap":69},"",[10,71,72],{},"实际生成 35 个 token，却占了 128 个 token 的显存，利用率只有 27%。不同长度的请求导致显存碎片严重，多余内存无法被其他请求复用。",[48,74,76],{"id":75},"瓶颈二gpu-空转静态批处理","瓶颈二：GPU 空转（静态批处理）",[61,78,81],{"className":79,"code":80,"language":66},[64],"同时跑 A、B、C、D 四个请求，等最长的 A 跑完才能开始下一批：\n\nStep 1~10：[A][B][C][D]  都在跑\nStep 11   ：[A][B][ ][ ]  C、D 完了，两个槽空着等\nStep 30   ：[A][ ][ ][ ]  只剩 A，75% 算力浪费\nStep 40   ：A 完成，才能开始下一批\n",[56,82,80],{"__ignoreMap":69},[10,84,85],{},"短序列完成后，GPU 槽位空着等最长的序列，算力白白浪费。",[10,87,88],{},[40,89,90],{},"这两个问题，就是整个项目要解决的。",[29,92],{},[32,94,96],{"id":95},"二项目架构一览","二、项目架构一览",[10,98,99],{},"七个核心模块，从下往上：",[61,101,104],{"className":102,"code":103,"language":66},[64],"┌──────────────────────────────────────────────────┐\n│                   LLMEngine                       │  ← 对外 API\n├──────────────────────────────────────────────────┤\n│   Scheduler       │   KVCacheManager             │  ← 调度 + 内存\n├──────────────────────────────────────────────────┤\n│   BlockAllocator  │   PrefixCache │ SwapManager  │  ← 底层机制\n├──────────────────────────────────────────────────┤\n│   Sequence \u002F Block                               │  ← 基础数据结构\n└──────────────────────────────────────────────────┘\n",[56,105,103],{"__ignoreMap":69},[10,107,108,109,112,113,116,117,120],{},"另有：",[56,110,111],{},"ModelRunner","（推理后端）、",[56,114,115],{},"MetricsCollector","（性能统计）、",[56,118,119],{},"SpeculativeDecoder","（投机解码）。",[29,122],{},[32,124,126],{"id":125},"三六个优化逐一拆解","三、六个优化，逐一拆解",[48,128,130],{"id":129},"_31-paged-kv-cache-解决显存碎片","3.1 Paged KV Cache —— 解决显存碎片",[10,132,133,136],{},[40,134,135],{},"类比 OS 虚拟内存。"," 操作系统不会给每个进程分一块连续的物理内存，而是分成固定大小的\"页\"按需分配。这里完全照搬：",[138,139,140,147,153],"ul",{},[141,142,143,146],"li",{},[56,144,145],{},"PhysicalBlock","：一个物理块，存 16 个 token 的 KV Cache，相当于一页",[141,148,149,152],{},[56,150,151],{},"BlockAllocator","：空闲链表，O(1) 分配和回收",[141,154,155,158],{},[56,156,157],{},"block_table","：每个序列的\"页表\"，记录逻辑块 → 物理块的映射",[61,160,164],{"className":161,"code":162,"language":163,"meta":69,"style":69},"language-python shiki shiki-themes github-dark-dimmed github-light","@dataclass\nclass PhysicalBlock:\n    block_id: int        # GPU 显存中的索引\n    ref_count: int = 0   # 引用计数，>1 时触发 CoW\n    content_hash: int = None  # 内容哈希，用于 Prefix Cache\n\nclass BlockAllocator:\n    def allocate(self) -> PhysicalBlock:\n        return self._free_blocks.popleft()   # O(1)\n    \n    def free(self, block: PhysicalBlock) -> None:\n        block.ref_count -= 1\n        if block.ref_count == 0:\n            self._free_blocks.append(block)  # O(1)\n","python",[56,165,166,175,190,204,221,237,244,254,266,281,287,303,315,331],{"__ignoreMap":69},[167,168,171],"span",{"class":169,"line":170},"line",1,[167,172,174],{"class":173},"saVmf","@dataclass\n",[167,176,178,182,186],{"class":169,"line":177},2,[167,179,181],{"class":180},"s6PUj","class",[167,183,185],{"class":184},"sqRhv"," PhysicalBlock",[167,187,189],{"class":188},"ssh_m",":\n",[167,191,193,196,200],{"class":169,"line":192},3,[167,194,195],{"class":188},"    block_id: ",[167,197,199],{"class":198},"swcJU","int",[167,201,203],{"class":202},"sgHix","        # GPU 显存中的索引\n",[167,205,207,210,212,215,218],{"class":169,"line":206},4,[167,208,209],{"class":188},"    ref_count: ",[167,211,199],{"class":198},[167,213,214],{"class":180}," =",[167,216,217],{"class":198}," 0",[167,219,220],{"class":202},"   # 引用计数，>1 时触发 CoW\n",[167,222,224,227,229,231,234],{"class":169,"line":223},5,[167,225,226],{"class":188},"    content_hash: ",[167,228,199],{"class":198},[167,230,214],{"class":180},[167,232,233],{"class":198}," None",[167,235,236],{"class":202},"  # 内容哈希，用于 Prefix Cache\n",[167,238,240],{"class":169,"line":239},6,[167,241,243],{"emptyLinePlaceholder":242},true,"\n",[167,245,247,249,252],{"class":169,"line":246},7,[167,248,181],{"class":180},[167,250,251],{"class":184}," BlockAllocator",[167,253,189],{"class":188},[167,255,257,260,263],{"class":169,"line":256},8,[167,258,259],{"class":180},"    def",[167,261,262],{"class":173}," allocate",[167,264,265],{"class":188},"(self) -> PhysicalBlock:\n",[167,267,269,272,275,278],{"class":169,"line":268},9,[167,270,271],{"class":180},"        return",[167,273,274],{"class":198}," self",[167,276,277],{"class":188},"._free_blocks.popleft()   ",[167,279,280],{"class":202},"# O(1)\n",[167,282,284],{"class":169,"line":283},10,[167,285,286],{"class":188},"    \n",[167,288,290,292,295,298,301],{"class":169,"line":289},11,[167,291,259],{"class":180},[167,293,294],{"class":173}," free",[167,296,297],{"class":188},"(self, block: PhysicalBlock) -> ",[167,299,300],{"class":198},"None",[167,302,189],{"class":188},[167,304,306,309,312],{"class":169,"line":305},12,[167,307,308],{"class":188},"        block.ref_count ",[167,310,311],{"class":180},"-=",[167,313,314],{"class":198}," 1\n",[167,316,318,321,324,327,329],{"class":169,"line":317},13,[167,319,320],{"class":180},"        if",[167,322,323],{"class":188}," block.ref_count ",[167,325,326],{"class":180},"==",[167,328,217],{"class":198},[167,330,189],{"class":188},[167,332,334,337,340],{"class":169,"line":333},14,[167,335,336],{"class":198},"            self",[167,338,339],{"class":188},"._free_blocks.append(block)  ",[167,341,280],{"class":202},[10,343,344],{},"序列产生第 17 个 token 时，第一个 block 满了，立刻申请新块：",[61,346,348],{"className":161,"code":347,"language":163,"meta":69,"style":69},"if seq.needs_new_block():\n    new_block = allocator.allocate()\n    seq.block_table[new_idx] = new_block\n",[56,349,350,358,369],{"__ignoreMap":69},[167,351,352,355],{"class":169,"line":170},[167,353,354],{"class":180},"if",[167,356,357],{"class":188}," seq.needs_new_block():\n",[167,359,360,363,366],{"class":169,"line":177},[167,361,362],{"class":188},"    new_block ",[167,364,365],{"class":180},"=",[167,367,368],{"class":188}," allocator.allocate()\n",[167,370,371,374,376],{"class":169,"line":192},[167,372,373],{"class":188},"    seq.block_table[new_idx] ",[167,375,365],{"class":180},[167,377,378],{"class":188}," new_block\n",[10,380,381,382,385],{},"序列结束时，所有物理块立刻回到空闲链表。",[40,383,384],{},"内存利用率从 17% 提升到 45–65%","。",[29,387],{},[48,389,391],{"id":390},"_32-continuous-batching-解决-gpu-空转","3.2 Continuous Batching —— 解决 GPU 空转",[10,393,394,395],{},"核心思想：",[40,396,397],{},"每一步都重新调度，而不是等一批跑完再换。",[10,399,400],{},"调度器每步做三件事：",[61,402,405],{"className":403,"code":404,"language":66},[64],"Step N 结束后：\n1. 检查 running 里有没有完成的序列 → 释放它的 block\n2. 检查 waiting 队列里有没有新请求 → 立刻拉进来 prefill\n3. 剩余的继续 decode\n",[56,406,404],{"__ignoreMap":69},[61,408,411],{"className":409,"code":410,"language":66},[64],"Step N:   [A:decode] [B:decode] [C:decode]\nStep N+1: C 完成 → 立刻拉入 D\n          [A:decode] [B:decode] [D:prefill]\nStep N+2: [A:decode] [B:decode] [D:decode]\n",[56,412,410],{"__ignoreMap":69},[10,414,415],{},"核心代码：",[61,417,419],{"className":161,"code":418,"language":163,"meta":69,"style":69},"def schedule(self):\n    # 先给运行中的序列分配 decode slot\n    for seq in self.running:\n        self.kv_cache.append_slot(seq)\n        output.decode_seqs.append(seq)\n    \n    # 再从等待队列拉入新请求\n    while self.waiting:\n        if not self.kv_cache.can_allocate(next_seq):\n            break  # 内存不够就停\n        self.kv_cache.allocate(next_seq)\n        output.prefill_chunks.append(next_seq)\n",[56,420,421,432,437,453,461,466,470,475,485,497,505,512],{"__ignoreMap":69},[167,422,423,426,429],{"class":169,"line":170},[167,424,425],{"class":180},"def",[167,427,428],{"class":173}," schedule",[167,430,431],{"class":188},"(self):\n",[167,433,434],{"class":169,"line":177},[167,435,436],{"class":202},"    # 先给运行中的序列分配 decode slot\n",[167,438,439,442,445,448,450],{"class":169,"line":192},[167,440,441],{"class":180},"    for",[167,443,444],{"class":188}," seq ",[167,446,447],{"class":180},"in",[167,449,274],{"class":198},[167,451,452],{"class":188},".running:\n",[167,454,455,458],{"class":169,"line":206},[167,456,457],{"class":198},"        self",[167,459,460],{"class":188},".kv_cache.append_slot(seq)\n",[167,462,463],{"class":169,"line":223},[167,464,465],{"class":188},"        output.decode_seqs.append(seq)\n",[167,467,468],{"class":169,"line":239},[167,469,286],{"class":188},[167,471,472],{"class":169,"line":246},[167,473,474],{"class":202},"    # 再从等待队列拉入新请求\n",[167,476,477,480,482],{"class":169,"line":256},[167,478,479],{"class":180},"    while",[167,481,274],{"class":198},[167,483,484],{"class":188},".waiting:\n",[167,486,487,489,492,494],{"class":169,"line":268},[167,488,320],{"class":180},[167,490,491],{"class":180}," not",[167,493,274],{"class":198},[167,495,496],{"class":188},".kv_cache.can_allocate(next_seq):\n",[167,498,499,502],{"class":169,"line":283},[167,500,501],{"class":180},"            break",[167,503,504],{"class":202},"  # 内存不够就停\n",[167,506,507,509],{"class":169,"line":289},[167,508,457],{"class":198},[167,510,511],{"class":188},".kv_cache.allocate(next_seq)\n",[167,513,514],{"class":169,"line":305},[167,515,516],{"class":188},"        output.prefill_chunks.append(next_seq)\n",[10,518,519,522],{},[40,520,521],{},"实测：吞吐量 2.4 倍提升","（50 请求，2ms\u002Fstep 模拟延迟）。",[29,524],{},[48,526,528],{"id":527},"_33-chunked-prefill-长-prompt-不阻塞短请求","3.3 Chunked Prefill —— 长 prompt 不阻塞短请求",[10,530,531,534],{},[40,532,533],{},"问题","：一个 512 token 的长 prompt，prefill 要跑整整一步，这期间所有 decode 请求都被阻塞。",[10,536,537,540,541,546],{},[40,538,539],{},"解法","（来自 ",[14,542,545],{"href":543,"rel":544},"https:\u002F\u002Farxiv.org\u002Fabs\u002F2308.16369",[26],"Sarathi-Serve, OSDI 2024","）：把长 prompt 切成 chunk，和 decode 穿插着跑：",[61,548,551],{"className":549,"code":550,"language":66},[64],"没有 Chunked Prefill：\n│──── prefill(512 tok) ────│decode│decode│decode│...\n\n有 Chunked Prefill（chunk_size=64）：\n│chunk(64)│decode│chunk(64)│decode│...│\n",[56,552,550],{"__ignoreMap":69},[10,554,555,556,559,560,563],{},"实现：给序列加了状态 ",[56,557,558],{},"PREFILLING"," 和进度字段 ",[56,561,562],{},"num_prefilled_tokens","：",[61,565,567],{"className":161,"code":566,"language":163,"meta":69,"style":69},"class SequenceStatus(Enum):\n    WAITING    = auto()\n    PREFILLING = auto()  # 长 prompt 分批处理中\n    RUNNING    = auto()\n    SWAPPED    = auto()  # KV cache 已卸载到 CPU\n    FINISHED   = auto()\n",[56,568,569,586,597,610,619,631],{"__ignoreMap":69},[167,570,571,573,576,579,583],{"class":169,"line":170},[167,572,181],{"class":180},[167,574,575],{"class":184}," SequenceStatus",[167,577,578],{"class":188},"(",[167,580,582],{"class":581},"sr-dd","Enum",[167,584,585],{"class":188},"):\n",[167,587,588,591,594],{"class":169,"line":177},[167,589,590],{"class":198},"    WAITING",[167,592,593],{"class":180},"    =",[167,595,596],{"class":188}," auto()\n",[167,598,599,602,604,607],{"class":169,"line":192},[167,600,601],{"class":198},"    PREFILLING",[167,603,214],{"class":180},[167,605,606],{"class":188}," auto()  ",[167,608,609],{"class":202},"# 长 prompt 分批处理中\n",[167,611,612,615,617],{"class":169,"line":206},[167,613,614],{"class":198},"    RUNNING",[167,616,593],{"class":180},[167,618,596],{"class":188},[167,620,621,624,626,628],{"class":169,"line":223},[167,622,623],{"class":198},"    SWAPPED",[167,625,593],{"class":180},[167,627,606],{"class":188},[167,629,630],{"class":202},"# KV cache 已卸载到 CPU\n",[167,632,633,636,639],{"class":169,"line":239},[167,634,635],{"class":198},"    FINISHED",[167,637,638],{"class":180},"   =",[167,640,596],{"class":188},[10,642,643,644,647],{},"调度器每步只给这个序列分配 ",[56,645,646],{},"chunk_size"," 个 token，下步继续接着来：",[61,649,651],{"className":161,"code":650,"language":163,"meta":69,"style":69},"start, end = seq.get_next_prefill_range(chunk_size=64)\n# 本步处理 token[start:end]，下步继续 token[end:end+64]\n",[56,652,653,674],{"__ignoreMap":69},[167,654,655,658,660,663,666,668,671],{"class":169,"line":170},[167,656,657],{"class":188},"start, end ",[167,659,365],{"class":180},[167,661,662],{"class":188}," seq.get_next_prefill_range(",[167,664,646],{"class":665},"sNjOc",[167,667,365],{"class":180},[167,669,670],{"class":198},"64",[167,672,673],{"class":188},")\n",[167,675,676],{"class":169,"line":177},[167,677,678],{"class":202},"# 本步处理 token[start:end]，下步继续 token[end:end+64]\n",[10,680,681],{},"效果：短请求的 TTFT（首 token 延迟）不再受长 prompt 拖累。",[29,683],{},[48,685,687],{"id":686},"_34-prefix-caching-共享-system-prompt-的-kv-cache","3.4 Prefix Caching —— 共享 system prompt 的 KV Cache",[10,689,690,693],{},[40,691,692],{},"场景","：你的 API 服务里所有请求都有同一个 system prompt（比如\"你是一个有帮助的助手...\"）。每次都重新计算这 256 个 token 的 KV Cache 是纯粹的浪费。",[10,695,696,698],{},[40,697,539],{},"：把满块的内容哈希存到全局缓存，第二个请求直接引用同一个物理块：",[61,700,702],{"className":161,"code":701,"language":163,"meta":69,"style":69},"class PrefixCache:\n    _cache: Dict[int, PhysicalBlock]  # hash(token_ids) → PhysicalBlock\n    \n    def lookup(self, content_hash):\n        block = self._cache.get(content_hash)\n        if block:\n            block.ref_count += 1  # 共享了，引用计数 +1\n        return block\n",[56,703,704,713,726,730,740,752,759,773],{"__ignoreMap":69},[167,705,706,708,711],{"class":169,"line":170},[167,707,181],{"class":180},[167,709,710],{"class":184}," PrefixCache",[167,712,189],{"class":188},[167,714,715,718,720,723],{"class":169,"line":177},[167,716,717],{"class":188},"    _cache: Dict[",[167,719,199],{"class":198},[167,721,722],{"class":188},", PhysicalBlock]  ",[167,724,725],{"class":202},"# hash(token_ids) → PhysicalBlock\n",[167,727,728],{"class":169,"line":192},[167,729,286],{"class":188},[167,731,732,734,737],{"class":169,"line":206},[167,733,259],{"class":180},[167,735,736],{"class":173}," lookup",[167,738,739],{"class":188},"(self, content_hash):\n",[167,741,742,745,747,749],{"class":169,"line":223},[167,743,744],{"class":188},"        block ",[167,746,365],{"class":180},[167,748,274],{"class":198},[167,750,751],{"class":188},"._cache.get(content_hash)\n",[167,753,754,756],{"class":169,"line":239},[167,755,320],{"class":180},[167,757,758],{"class":188}," block:\n",[167,760,761,764,767,770],{"class":169,"line":246},[167,762,763],{"class":188},"            block.ref_count ",[167,765,766],{"class":180},"+=",[167,768,769],{"class":198}," 1",[167,771,772],{"class":202},"  # 共享了，引用计数 +1\n",[167,774,775,777],{"class":169,"line":256},[167,776,271],{"class":180},[167,778,779],{"class":188}," block\n",[10,781,782,783,786,787,790],{},"共享了怎么保证安全写入？",[40,784,785],{},"Copy-on-Write","：写入前检查 ",[56,788,789],{},"ref_count > 1","，是的话先 fork 出一个新块再写：",[61,792,794],{"className":161,"code":793,"language":163,"meta":69,"style":69},"def cow_if_needed(self, block):\n    if block.ref_count \u003C= 1:\n        return block, False   # 独占，直接写\n    new_block = allocator.allocate()   # fork 一份\n    block.ref_count -= 1               # 旧块减引用\n    return new_block, True\n",[56,795,796,806,820,833,845,857],{"__ignoreMap":69},[167,797,798,800,803],{"class":169,"line":170},[167,799,425],{"class":180},[167,801,802],{"class":173}," cow_if_needed",[167,804,805],{"class":188},"(self, block):\n",[167,807,808,811,813,816,818],{"class":169,"line":177},[167,809,810],{"class":180},"    if",[167,812,323],{"class":188},[167,814,815],{"class":180},"\u003C=",[167,817,769],{"class":198},[167,819,189],{"class":188},[167,821,822,824,827,830],{"class":169,"line":192},[167,823,271],{"class":180},[167,825,826],{"class":188}," block, ",[167,828,829],{"class":198},"False",[167,831,832],{"class":202},"   # 独占，直接写\n",[167,834,835,837,839,842],{"class":169,"line":206},[167,836,362],{"class":188},[167,838,365],{"class":180},[167,840,841],{"class":188}," allocator.allocate()   ",[167,843,844],{"class":202},"# fork 一份\n",[167,846,847,850,852,854],{"class":169,"line":223},[167,848,849],{"class":188},"    block.ref_count ",[167,851,311],{"class":180},[167,853,769],{"class":198},[167,855,856],{"class":202},"               # 旧块减引用\n",[167,858,859,862,865],{"class":169,"line":239},[167,860,861],{"class":180},"    return",[167,863,864],{"class":188}," new_block, ",[167,866,867],{"class":198},"True\n",[10,869,870],{},"这和 Linux 的 fork-on-write 是完全一样的思路。",[10,872,873,876],{},[40,874,875],{},"实测","（30 个请求，48 token 共享前缀 + 16 token 独有后缀）：",[138,878,879,882],{},[141,880,881],{},"缓存命中率：71.3%（最后一个不满的块必然 miss，这是设计上的取舍）",[141,883,884],{},"大量省去重复 prefill 计算，显存节省 ~75%",[886,887,888],"blockquote",{},[10,889,890,893],{},[40,891,892],{},"为什么只缓存\"满块\"？"," 不满的最后一块内容还会变化（decode 阶段还会往里追加 token），哈希不稳定，缓存了也没意义。只有满块内容固定，哈希才可靠。",[29,895],{},[48,897,899],{"id":898},"_35-cpu-swap-显存不够时的降级策略","3.5 CPU Swap —— 显存不够时的降级策略",[10,901,902],{},"内存不够必须抢占某个请求时，有两种选择：",[138,904,905,911],{},[141,906,907,910],{},[40,908,909],{},"Recompute","：丢掉它的 KV Cache，等下次轮到它重新 prefill（简单，但长序列代价大）",[141,912,913,916],{},[40,914,915],{},"Swap","：把 KV Cache 搬到 CPU RAM，等内存够了再搬回来（复杂，但避免重算）",[61,918,920],{"className":161,"code":919,"language":163,"meta":69,"style":69},"class SwapManager:\n    def swap_out(self, seq):\n        # 1. 记录 logical_block → cpu_slot 的映射\n        # 2. 释放 GPU 物理块（显存立刻还给其他请求）\n        seq.status = SequenceStatus.SWAPPED\n    \n    def swap_in(self, seq):\n        # 1. 重新分配 GPU 物理块\n        # 2. 恢复 block_table 映射\n        seq.status = SequenceStatus.RUNNING\n",[56,921,922,931,941,946,951,964,968,977,982,987],{"__ignoreMap":69},[167,923,924,926,929],{"class":169,"line":170},[167,925,181],{"class":180},[167,927,928],{"class":184}," SwapManager",[167,930,189],{"class":188},[167,932,933,935,938],{"class":169,"line":177},[167,934,259],{"class":180},[167,936,937],{"class":173}," swap_out",[167,939,940],{"class":188},"(self, seq):\n",[167,942,943],{"class":169,"line":192},[167,944,945],{"class":202},"        # 1. 记录 logical_block → cpu_slot 的映射\n",[167,947,948],{"class":169,"line":206},[167,949,950],{"class":202},"        # 2. 释放 GPU 物理块（显存立刻还给其他请求）\n",[167,952,953,956,958,961],{"class":169,"line":223},[167,954,955],{"class":188},"        seq.status ",[167,957,365],{"class":180},[167,959,960],{"class":188}," SequenceStatus.",[167,962,963],{"class":198},"SWAPPED\n",[167,965,966],{"class":169,"line":239},[167,967,286],{"class":188},[167,969,970,972,975],{"class":169,"line":246},[167,971,259],{"class":180},[167,973,974],{"class":173}," swap_in",[167,976,940],{"class":188},[167,978,979],{"class":169,"line":256},[167,980,981],{"class":202},"        # 1. 重新分配 GPU 物理块\n",[167,983,984],{"class":169,"line":268},[167,985,986],{"class":202},"        # 2. 恢复 block_table 映射\n",[167,988,989,991,993,995],{"class":169,"line":283},[167,990,955],{"class":188},[167,992,365],{"class":180},[167,994,960],{"class":188},[167,996,997],{"class":198},"RUNNING\n",[10,999,1000,1003],{},[40,1001,1002],{},"权衡","：PCIe 带宽（GPU↔CPU ~16 GB\u002Fs）远低于 GPU 显存带宽（~2 TB\u002Fs），swap 延迟约是 GPU 内部操作的 100 倍。但对长序列（512+ tokens）来说，swap 延迟 \u003C\u003C 重新 prefill 延迟，值得做。",[10,1005,1006,1007,1010],{},"这与 vLLM 的 ",[56,1008,1009],{},"preemption_mode=\"swap\""," 完全对应。",[29,1012],{},[48,1014,1016],{"id":1015},"_36-speculative-decoding-让-gpu-每步多生几个-token","3.6 Speculative Decoding —— 让 GPU 每步多生几个 token",[10,1018,1019],{},"标准 decode 每步只生 1 个 token，GPU 大部分时间在等显存读写（memory-bound，计算利用率低）。",[10,1021,1022,1025,1026,1031],{},[40,1023,1024],{},"投机解码","（",[14,1027,1030],{"href":1028,"rel":1029},"https:\u002F\u002Farxiv.org\u002Fabs\u002F2211.17192",[26],"Leviathan et al., ICML 2023","）：",[61,1033,1036],{"className":1034,"code":1035,"language":66},[64],"Step 1 (Draft)  ：小模型快速生成 K=4 个候选 token [t₁, t₂, t₃, t₄]\nStep 2 (Verify) ：大模型一次并行验证所有 4 个（等价于 1 次 prefill，并行快）\nStep 3 (Accept) ：t₁✓ t₂✓ t₃✗ → 接受 [t₁, t₂]，用大模型的纠正 token\n",[56,1037,1035],{"__ignoreMap":69},[10,1039,1040],{},"接受率 α 下，期望每步接受 token 数：",[61,1042,1045],{"className":1043,"code":1044,"language":66},[64],"E[accepted] = (1 - α^(K+1)) \u002F (1 - α)\n",[56,1046,1044],{"__ignoreMap":69},[10,1048,1049,1050,1053,1054,385],{},"当 α=0.7、K=4 时，期望 ",[40,1051,1052],{},"3.3 个 token\u002F步","，而不是 1 个，理论加速 ",[40,1055,1056],{},"3.3×",[61,1058,1060],{"className":161,"code":1059,"language":163,"meta":69,"style":69},"def step(self, seqs):\n    # Phase 1: Draft（K 步，小模型快）\n    draft_tokens = {}\n    for _ in range(self.K):\n        new_draft = self.draft_runner.step(decode_seqs=seqs)\n        for seq in seqs:\n            draft_tokens[seq.seq_id].append(new_draft[seq.seq_id])\n    \n    # Phase 2: Verify（1 步，大模型并行）\n    verify_tokens = self.target_runner.step(decode_seqs=seqs)\n    \n    # Phase 3: Accept\u002FReject（从左到右截断）\n    for seq in seqs:\n        accepted = []\n        for draft_tok in draft_tokens[seq.seq_id]:\n            if random() \u003C acceptance_rate:\n                accepted.append(draft_tok)        # draft 被接受\n            else:\n                accepted.append(verify_tokens[seq.seq_id])  # 用 target 纠正\n                break\n        else:\n            accepted.append(verify_tokens[seq.seq_id])      # bonus token\n",[56,1061,1062,1072,1077,1087,1107,1127,1139,1144,1148,1153,1171,1175,1180,1190,1200,1213,1228,1237,1245,1254,1260,1268],{"__ignoreMap":69},[167,1063,1064,1066,1069],{"class":169,"line":170},[167,1065,425],{"class":180},[167,1067,1068],{"class":173}," step",[167,1070,1071],{"class":188},"(self, seqs):\n",[167,1073,1074],{"class":169,"line":177},[167,1075,1076],{"class":202},"    # Phase 1: Draft（K 步，小模型快）\n",[167,1078,1079,1082,1084],{"class":169,"line":192},[167,1080,1081],{"class":188},"    draft_tokens ",[167,1083,365],{"class":180},[167,1085,1086],{"class":188}," {}\n",[167,1088,1089,1091,1094,1096,1099,1101,1104],{"class":169,"line":206},[167,1090,441],{"class":180},[167,1092,1093],{"class":188}," _ ",[167,1095,447],{"class":180},[167,1097,1098],{"class":198}," range",[167,1100,578],{"class":188},[167,1102,1103],{"class":198},"self",[167,1105,1106],{"class":188},".K):\n",[167,1108,1109,1112,1114,1116,1119,1122,1124],{"class":169,"line":223},[167,1110,1111],{"class":188},"        new_draft ",[167,1113,365],{"class":180},[167,1115,274],{"class":198},[167,1117,1118],{"class":188},".draft_runner.step(",[167,1120,1121],{"class":665},"decode_seqs",[167,1123,365],{"class":180},[167,1125,1126],{"class":188},"seqs)\n",[167,1128,1129,1132,1134,1136],{"class":169,"line":239},[167,1130,1131],{"class":180},"        for",[167,1133,444],{"class":188},[167,1135,447],{"class":180},[167,1137,1138],{"class":188}," seqs:\n",[167,1140,1141],{"class":169,"line":246},[167,1142,1143],{"class":188},"            draft_tokens[seq.seq_id].append(new_draft[seq.seq_id])\n",[167,1145,1146],{"class":169,"line":256},[167,1147,286],{"class":188},[167,1149,1150],{"class":169,"line":268},[167,1151,1152],{"class":202},"    # Phase 2: Verify（1 步，大模型并行）\n",[167,1154,1155,1158,1160,1162,1165,1167,1169],{"class":169,"line":283},[167,1156,1157],{"class":188},"    verify_tokens ",[167,1159,365],{"class":180},[167,1161,274],{"class":198},[167,1163,1164],{"class":188},".target_runner.step(",[167,1166,1121],{"class":665},[167,1168,365],{"class":180},[167,1170,1126],{"class":188},[167,1172,1173],{"class":169,"line":289},[167,1174,286],{"class":188},[167,1176,1177],{"class":169,"line":305},[167,1178,1179],{"class":202},"    # Phase 3: Accept\u002FReject（从左到右截断）\n",[167,1181,1182,1184,1186,1188],{"class":169,"line":317},[167,1183,441],{"class":180},[167,1185,444],{"class":188},[167,1187,447],{"class":180},[167,1189,1138],{"class":188},[167,1191,1192,1195,1197],{"class":169,"line":333},[167,1193,1194],{"class":188},"        accepted ",[167,1196,365],{"class":180},[167,1198,1199],{"class":188}," []\n",[167,1201,1203,1205,1208,1210],{"class":169,"line":1202},15,[167,1204,1131],{"class":180},[167,1206,1207],{"class":188}," draft_tok ",[167,1209,447],{"class":180},[167,1211,1212],{"class":188}," draft_tokens[seq.seq_id]:\n",[167,1214,1216,1219,1222,1225],{"class":169,"line":1215},16,[167,1217,1218],{"class":180},"            if",[167,1220,1221],{"class":188}," random() ",[167,1223,1224],{"class":180},"\u003C",[167,1226,1227],{"class":188}," acceptance_rate:\n",[167,1229,1231,1234],{"class":169,"line":1230},17,[167,1232,1233],{"class":188},"                accepted.append(draft_tok)        ",[167,1235,1236],{"class":202},"# draft 被接受\n",[167,1238,1240,1243],{"class":169,"line":1239},18,[167,1241,1242],{"class":180},"            else",[167,1244,189],{"class":188},[167,1246,1248,1251],{"class":169,"line":1247},19,[167,1249,1250],{"class":188},"                accepted.append(verify_tokens[seq.seq_id])  ",[167,1252,1253],{"class":202},"# 用 target 纠正\n",[167,1255,1257],{"class":169,"line":1256},20,[167,1258,1259],{"class":180},"                break\n",[167,1261,1263,1266],{"class":169,"line":1262},21,[167,1264,1265],{"class":180},"        else",[167,1267,189],{"class":188},[167,1269,1271,1274],{"class":169,"line":1270},22,[167,1272,1273],{"class":188},"            accepted.append(verify_tokens[seq.seq_id])      ",[167,1275,1276],{"class":202},"# bonus token\n",[10,1278,1279],{},"实践中，draft model 选同家族的小模型（如 Llama-3.2-1B 配 Llama-3-70B），接受率通常在 0.6–0.85 之间。",[29,1281],{},[32,1283,1285],{"id":1284},"四三个关键设计决策","四、三个关键设计决策",[10,1287,1288],{},[40,1289,1290],{},"为什么用 deque 做空闲链表？",[10,1292,1293,1296,1297,1300],{},[56,1294,1295],{},"popleft()"," 和 ",[56,1298,1299],{},"append()"," 都是 O(1)，比数组（O(n) 查找空闲）或 bitmap（需要扫描）更快。实际上 vLLM 的 block allocator 也是类似的空闲列表。",[10,1302,1303],{},[40,1304,1305],{},"ModelRunner 为什么抽象成接口？",[10,1307,1308,1311,1312,1315,1316,1319],{},[56,1309,1310],{},"MockModelRunner","（纯 Python，无 GPU）和 ",[56,1313,1314],{},"GPT2ModelRunner","（真实推理）实现同一个 ",[56,1317,1318],{},"step()"," 接口，调度器完全不知道背后用的什么模型。这样可以单独测试调度逻辑，不用 GPU 也能跑 benchmark，也方便 CI。",[10,1321,1322],{},[40,1323,1324],{},"Prefix Cache 为什么只缓存满块？",[10,1326,1327],{},"不满的最后一块在 decode 阶段还会往里追加 token，内容还没固定，哈希不稳定。只有满块内容不再变化，缓存才有意义。这是一个设计取舍：牺牲最后一个 block 的命中率，换取实现的简洁性。SGLang 的 RadixAttention 用 Radix Tree 能做到更精细的最长前缀匹配，但实现复杂很多。",[29,1329],{},[32,1331,1333],{"id":1332},"五整体数据流一张图","五、整体数据流（一张图）",[61,1335,1338],{"className":1336,"code":1337,"language":66},[64],"用户提交 \"Hello world\"\n  │\n  ▼\nLLMEngine.add_request()\n  │ 创建 Sequence（WAITING），加入 scheduler.waiting\n  │\n  ▼ ── 每步循环 ──────────────────────────────────────\nScheduler.schedule()\n  ├── 为 RUNNING 序列 append_slot（可能触发 CoW）\n  ├── 为 PREFILLING 序列推进 chunk 进度\n  ├── 尝试 swap_in SWAPPED 序列\n  └── 从 waiting 拉入新请求（分配物理块）\n  │\n  ▼ SchedulerOutput\n  │\nModelRunner.step(prefill_seqs, decode_seqs)\n  │ 返回 Dict[seq_id → new_token_id]\n  │\n  ▼\nScheduler.on_step_done()\n  │ 追加 token，检查 EOS \u002F max_tokens\n  │\n  ▼\nMetricsCollector.record_step()\n  │ 记录 KV util、队列深度、TTFT\n  │ ──────────────────────────────────────────────────\n  │\n  ▼ 序列完成时\nRequestOutput(latency, ttft, output_token_ids)\n",[56,1339,1337],{"__ignoreMap":69},[29,1341],{},[32,1343,1345],{"id":1344},"六这个项目能聊什么","六、这个项目能聊什么",[10,1347,1348],{},"做完这个项目，下面这些问题我能讲清楚：",[10,1350,1351,1354,1357],{},[40,1352,1353],{},"Q：vLLM 为什么比 HuggingFace 快 20 倍？",[1355,1356],"br",{},"\nA：两个核心：Continuous Batching 消除 GPU 空转，PagedAttention 消除显存碎片。GPU 利用率从 30% 提升到 90%+，显存释放后能跑更多并发请求，两者相乘放大了差距。",[10,1359,1360,1363,1365,1366,1369,1370,1373,1374,1376,1377,1380],{},[40,1361,1362],{},"Q：Prefix Cache 的 CoW 怎么实现的？",[1355,1364],{},"\nA：满块计算 ",[56,1367,1368],{},"hash(tuple(token_ids))"," 作为 key 存入全局字典。命中时 ",[56,1371,1372],{},"ref_count+1"," 直接共享物理块，不用重新分配。写入前检查 ",[56,1375,789],{},"，如果是共享块就先 fork 一个新块再写，旧块 ",[56,1378,1379],{},"ref_count-1","。完全照搬 Linux fork 的思路。",[10,1382,1383,1386,1388,1389,1392],{},[40,1384,1385],{},"Q：显存不够时怎么处理？",[1355,1387],{},"\nA：三个策略按优先级：① 先看能不能 swap out 到 CPU RAM（避免重算，有带宽代价）；② 不行就 recompute（丢 KV Cache，下次重新 prefill，长序列代价高）；③ 实在不行就让请求继续排队。vLLM 通过 ",[56,1390,1391],{},"preemption_mode"," 参数让用户选择。",[10,1394,1395,1398,1400],{},[40,1396,1397],{},"Q：Chunked Prefill 和 Continuous Batching 什么关系？",[1355,1399],{},"\nA：CB 解决\"谁来跑\"（批次动态变化，完成了立刻换人）；Chunked Prefill 解决\"怎么跑\"（长 prefill 拆碎，不独占一整步）。二者正交，可以同时开启，也是 vLLM 的默认配置。",[10,1402,1403,1406,1408],{},[40,1404,1405],{},"Q：Speculative Decoding 什么时候不适用？",[1355,1407],{},"\nA：draft 和 target 分布差异太大时（接受率 α \u003C 0.5），每步 K 次 draft 的时间开销超过多拿 token 的收益，不如直接跑 target。实践中要选同家族模型做 draft，比如 Llama-3.2-1B 配 Llama-3-70B，接受率能到 0.7+ 才合算。",[29,1410],{},[10,1412,1413],{},"代码在这里，可以直接跑：",[61,1415,1419],{"className":1416,"code":1417,"language":1418,"meta":69,"style":69},"language-bash shiki shiki-themes github-dark-dimmed github-light","git clone https:\u002F\u002Fgithub.com\u002Fliangqianxing\u002Fmini-llm-engine\ncd mini-llm-engine\npip install pytest\npytest tests\u002F -v                               # 67 tests, all pass\npython examples\u002Fbasic_usage.py                 # 完整 demo\npython run_all_benchmarks.py --fast --no-plot  # 5 个 benchmark\n","bash",[56,1420,1421,1433,1441,1452,1466,1476],{"__ignoreMap":69},[167,1422,1423,1426,1430],{"class":169,"line":170},[167,1424,1425],{"class":184},"git",[167,1427,1429],{"class":1428},"sXfbr"," clone",[167,1431,1432],{"class":1428}," https:\u002F\u002Fgithub.com\u002Fliangqianxing\u002Fmini-llm-engine\n",[167,1434,1435,1438],{"class":169,"line":177},[167,1436,1437],{"class":198},"cd",[167,1439,1440],{"class":1428}," mini-llm-engine\n",[167,1442,1443,1446,1449],{"class":169,"line":192},[167,1444,1445],{"class":184},"pip",[167,1447,1448],{"class":1428}," install",[167,1450,1451],{"class":1428}," pytest\n",[167,1453,1454,1457,1460,1463],{"class":169,"line":206},[167,1455,1456],{"class":184},"pytest",[167,1458,1459],{"class":1428}," tests\u002F",[167,1461,1462],{"class":198}," -v",[167,1464,1465],{"class":202},"                               # 67 tests, all pass\n",[167,1467,1468,1470,1473],{"class":169,"line":223},[167,1469,163],{"class":184},[167,1471,1472],{"class":1428}," examples\u002Fbasic_usage.py",[167,1474,1475],{"class":202},"                 # 完整 demo\n",[167,1477,1478,1480,1483,1486,1489],{"class":169,"line":239},[167,1479,163],{"class":184},[167,1481,1482],{"class":1428}," run_all_benchmarks.py",[167,1484,1485],{"class":198}," --fast",[167,1487,1488],{"class":198}," --no-plot",[167,1490,1491],{"class":202},"  # 5 个 benchmark\n",[1493,1494,1495],"style",{},"html pre.shiki code .saVmf, html code.shiki .saVmf{--shiki-default:#DCBDFB;--shiki-light:#6F42C1}html pre.shiki code .s6PUj, html code.shiki .s6PUj{--shiki-default:#F47067;--shiki-light:#D73A49}html pre.shiki code .sqRhv, html code.shiki .sqRhv{--shiki-default:#F69D50;--shiki-light:#6F42C1}html pre.shiki code .ssh_m, html code.shiki .ssh_m{--shiki-default:#ADBAC7;--shiki-light:#24292E}html pre.shiki code .swcJU, html code.shiki .swcJU{--shiki-default:#6CB6FF;--shiki-light:#005CC5}html pre.shiki code .sgHix, html code.shiki .sgHix{--shiki-default:#768390;--shiki-light:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html pre.shiki code .sr-dd, html code.shiki .sr-dd{--shiki-default:#6CB6FF;--shiki-light:#6F42C1}html pre.shiki code .sNjOc, html code.shiki .sNjOc{--shiki-default:#F69D50;--shiki-light:#E36209}html pre.shiki code .sXfbr, html code.shiki .sXfbr{--shiki-default:#96D0FF;--shiki-light:#032F62}",{"title":69,"searchDepth":177,"depth":192,"links":1497},[1498,1502,1503,1511,1512,1513],{"id":34,"depth":177,"text":35,"children":1499},[1500,1501],{"id":50,"depth":192,"text":51},{"id":75,"depth":192,"text":76},{"id":95,"depth":177,"text":96},{"id":125,"depth":177,"text":126,"children":1504},[1505,1506,1507,1508,1509,1510],{"id":129,"depth":192,"text":130},{"id":390,"depth":192,"text":391},{"id":527,"depth":192,"text":528},{"id":686,"depth":192,"text":687},{"id":898,"depth":192,"text":899},{"id":1015,"depth":192,"text":1016},{"id":1284,"depth":177,"text":1285},{"id":1332,"depth":177,"text":1333},{"id":1344,"depth":177,"text":1345},[1515],"AI","2026-06-30 14:00:00","上一篇写了 mini-llm-engine 的实现过程，有人问我能不能把原理再讲清楚一点。这篇就专门讲\"为什么\"——为什么要这么设计，每个优化到底解决了什么问题。",false,"md",{},"\u002Fposts\u002Fmini-llm-engine-deep-dive",{"title":5,"description":1517},"posts\u002Fmini-llm-engine-deep-dive",[1525,1526,1527,1528,42,1529,1024,1530],"LLM","AI Infra","vLLM","PagedAttention","推理优化","实习求职","wX0AJr1uxPADPTMPVFHdBCxwCu6D3w5Heiw4s-9Hw1s",[1533,1544,1547,1552,1564,1573,1582,1592,1602,1611,1621,1632,1645,1657,1665,1675,1687,1698,1708,1716,1727,1733,1739,1745,1753,1762,1770,1776,1784,1792],{"slug":1534,"path":1535,"title":1536,"date":1537,"tags":1538,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":268},"multimodal-rag-from-scratch","\u002Fposts\u002Fmultimodal-rag-from-scratch","从零实现多模态 RAG：BM25、Dense 检索、RRF 融合、MMR 重排全部手写","2026-06-30 18:00:00",[1539,1540,1526,1541,1542,1543,1530],"RAG","多模态","BM25","向量检索","混合检索",{"slug":1545,"path":1521,"title":5,"date":1516,"tags":1546,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":289},"mini-llm-engine-deep-dive",[1525,1526,1527,1528,42,1529,1024,1530],{"slug":1548,"path":16,"title":1549,"date":1550,"tags":1551,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":283},"mini-llm-engine-from-scratch","从零实现 LLM 推理引擎：深挖 vLLM 的六大核心优化","2026-06-30 10:00:00",[1525,1526,1527,1528,1529,1024,1530],{"slug":1553,"path":1554,"title":1555,"date":1556,"tags":1557,"description":1563,"draft":1518,"hidden":1518,"published":242,"readingTime":1239},"bytedance-recommendation-architecture-intern-interview","\u002Fposts\u002Fbytedance-recommendation-architecture-intern-interview","字节推荐架构实习生 Data 面试准备：推荐系统、实时特征与高并发八股","2026-06-09",[1558,1559,1560,1561,1562],"面试","推荐系统","后端架构","实时计算","字节跳动","面向字节跳动推荐架构团队实习岗位的八股准备清单，覆盖推荐系统链路、实时数据、特征服务、高并发后端、分布式系统与项目包装。",{"slug":1565,"path":1566,"title":1567,"date":1556,"tags":1568,"description":1572,"draft":1518,"hidden":1518,"published":242,"readingTime":305},"high-concurrency-rate-limiting-algorithms","\u002Fposts\u002Fhigh-concurrency-rate-limiting-algorithms","高并发限流算法：固定窗口、滑动窗口、漏桶与令牌桶",[1569,1570,1560,1571,1558],"高并发","限流","系统设计","面试和工程都常见的限流算法总结，讲清楚固定窗口、滑动窗口、漏桶、令牌桶、并发数限制以及分布式限流如何落地。",{"slug":1574,"path":1575,"title":1576,"date":1556,"tags":1577,"description":1581,"draft":1518,"hidden":1518,"published":242,"readingTime":256},"kafka-producer-broker-consumer","\u002Fposts\u002Fkafka-producer-broker-consumer","Kafka 入门：生产者、Broker、消费者和“消费”到底是什么意思",[1578,1579,1580,1558,1559],"Kafka","消息队列","后端","用推荐系统里的用户行为日志为例，讲清楚 Kafka 的作用、Producer、Broker、Consumer、Topic、Partition、Offset 和消费语义。",{"slug":1583,"path":1584,"title":1585,"date":1556,"tags":1586,"description":1591,"draft":1518,"hidden":1518,"published":242,"readingTime":239},"leetcode-lru-merge-k-reverse-list","\u002Fposts\u002Fleetcode-lru-merge-k-reverse-list","链表与缓存高频题：LRU Cache、合并 K 个有序链表、反转链表",[1587,1588,1589,1590,1558],"算法","链表","LRU","LeetCode","面试高频算法题速记，整理 LRU Cache、合并 K 个有序链表、反转链表的核心思路、复杂度和 C++ 代码。",{"slug":1593,"path":1594,"title":1595,"date":1596,"tags":1597,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":1215},"meddococr-interpreter-source-analysis","\u002Fposts\u002Fmeddococr-interpreter-source-analysis","MedDocOCR-Interpreter 源码导读：医疗文档 OCR、结构化抽取与报告解读原型","2026-05-22 10:30:00",[1598,1540,1599,1539,1600,1601],"OCR","医疗 AI","Python","源码分析",{"slug":1603,"path":1604,"title":1605,"date":1606,"tags":1607,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":283},"cet6-writing-model-essays","\u002Fposts\u002Fcet6-writing-model-essays","六级写作范文背诵包：10 个高频话题","2026-05-20 09:00:00",[1608,1609,1610],"English","CET6","Writing",{"slug":1612,"path":1613,"title":1614,"date":1615,"tags":1616,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":1620},"claude-code-context-management","\u002Fposts\u002Fclaude-code-context-management","Claude Code 上下文管理机制：从 Microcompact 到 Auto Compact","2026-05-19 10:00:00",[1617,1618,1525,1526,1619],"Claude Code","Agent","上下文工程",27,{"slug":1622,"path":1623,"title":1624,"date":1625,"tags":1626,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":1247},"nanotron-llm-pretraining-framework-analysis","\u002Fposts\u002Fnanotron-llm-pretraining-framework-analysis","Nanotron 项目详解：Hugging Face 的大模型预训练框架怎么做分布式训练","2026-05-10 12:10:00",[1525,1627,1628,1629,1630,1526,1631],"大模型训练","分布式训练","Nanotron","Hugging Face","PyTorch",{"slug":1633,"path":1634,"title":1635,"date":1636,"tags":1637,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":1644},"gofoundry-go-backend-foundation-framework","\u002Fposts\u002Fgofoundry-go-backend-foundation-framework","GoFoundry 项目详解：基于 Go 的后端基础框架套件设计","2026-05-10 11:20:00",[1638,1639,1640,1641,1642,1579,1643],"Go","后端框架","ORM","分布式缓存","分布式锁","项目架构",25,{"slug":1646,"path":1647,"title":1648,"date":1649,"tags":1650,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":1620},"cloudvault-go-cloud-storage-system","\u002Fposts\u002Fcloudvault-go-cloud-storage-system","CloudVault 项目详解：基于 Go 的云端存储与网盘系统架构设计","2026-05-10 10:30:00",[1638,1651,1652,1653,1643,1654,1655,1656],"云存储","网盘系统","分布式系统","Redis","RabbitMQ","Elasticsearch",{"slug":1658,"path":1659,"title":1660,"date":1661,"tags":1662,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":1256},"openclaw-source-code-analysis","\u002Fposts\u002Fopenclaw-source-code-analysis","OpenClaw 源码导读：个人 AI 助手的网关、通道、插件与运行时架构","2026-05-08 16:30:00",[1663,1618,1526,1664,1601],"OpenClaw","TypeScript",{"slug":1666,"path":1667,"title":1668,"date":1669,"tags":1670,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":283},"flow-matching-generative-models","\u002Fposts\u002Fflow-matching-generative-models","Flow Matching：从噪声到数据的连续流生成模型","2026-05-07 00:00:00",[1671,1672,1673,1674],"生成模型","Diffusion","Flow Matching","深度学习",{"slug":1676,"path":1677,"title":1678,"date":1679,"tags":1680,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":1202},"database-ai-week4","\u002Fposts\u002Fdatabase-ai-week4","Week 4：数据库速成——从 Storage、Index、Query Optimization 到 Vector DB 与 RAG","2026-05-05 12:00:00",[1681,1682,1683,1539,1684,1685,1686],"数据库","CMU 15-445","Vector DB","LLM Memory","Query Optimization","Caching",{"slug":1688,"path":1689,"title":1690,"date":1691,"tags":1692,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":1215},"distributed-systems-week3","\u002Fposts\u002Fdistributed-systems-week3","Week 3：分布式系统速成——MapReduce、Raft、容错与 Distributed KV Store","2026-05-05 11:00:00",[1653,1693,1694,1695,1696,1697,1618],"MIT 6.824","MapReduce","Raft","KV Store","Ray",{"slug":1699,"path":1700,"title":1701,"date":1702,"tags":1703,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":1202},"gpu-inference-acceleration-week2","\u002Fposts\u002Fgpu-inference-acceleration-week2","Week 2：GPU 与推理加速——从 Kernel、算子融合到 LLM Serving","2026-05-05 10:00:00",[1674,1704,1705,1525,1706,1527,1707],"GPU","推理加速","CMU 10-414","TensorRT",{"slug":1709,"path":1710,"title":1711,"date":1712,"tags":1713,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":1239},"dl-framework-autograd-mini","\u002Fposts\u002Fdl-framework-autograd-mini","Week 1：DL 框架与 Autograd——从计算图、反向传播到 Mini Autograd 实现","2026-05-05 09:00:00",[1674,1714,1631,1706,1715],"Autograd","Mini Framework",{"slug":1717,"path":1718,"title":1719,"date":1720,"tags":1721,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":305},"lock-free-concurrency-notes","\u002Fposts\u002Flock-free-concurrency-notes","无锁并发入门：从 CAS 到 Atomic Ring Buffer","2026-04-25",[1722,1723,1724,1725,1726],"C++","并发","无锁编程","性能优化","量化开发",{"slug":1728,"path":1729,"title":1730,"date":1731,"tags":1732,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":223},"agent-memory","\u002Fposts\u002Fagent-memory","Agent 对话记忆化：从原理到实现","2026-04-24",[1525,1618,1539,1558],{"slug":1734,"path":1735,"title":1736,"date":1731,"tags":1737,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":239},"llm-context-compression","\u002Fposts\u002Fllm-context-compression","LLM 上下文五层压缩机制详解",[1525,1618,1738,1558],"上下文压缩",{"slug":1740,"path":1741,"title":1742,"date":1743,"tags":1744,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":268},"cpp-concurrency-basics","\u002Fposts\u002Fcpp-concurrency-basics","C++ 并发编程入门：从数据竞争到线程池","2026-04-15",[1722,1723,1558,1726],{"slug":1746,"path":1747,"title":1748,"date":1749,"tags":1750,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":170},"travel-in-shenzhen","\u002Fposts\u002Ftravel-in-shenzhen","XCPC 深圳游记","2026-04-13",[1751,1722,1752],"XCPC","比赛",{"slug":1754,"path":1755,"title":1756,"date":1757,"tags":1758,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":246},"backend-stack-deep-dive","\u002Fposts\u002Fbackend-stack-deep-dive","后端五件套：FastAPI \u002F Node.js \u002F SQLAlchemy async \u002F PostgreSQL \u002F Docker 面试速通","2026-04-07",[1580,1759,1760,1761,1558],"FastAPI","PostgreSQL","Docker",{"slug":1763,"path":1764,"title":1765,"date":1766,"tags":1767,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":239},"deepscientist-tech-stack","\u002Fposts\u002Fdeepscientist-tech-stack","DeepScientist 技术栈全解析：一个 AI 科研平台的架构设计","2026-04-06",[1768,1759,1769,1760,1558],"全栈","Next.js",{"slug":1771,"path":1772,"title":1773,"date":1766,"tags":1774,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":256},"minicode-source-analysis","\u002Fposts\u002Fminicode-source-analysis","MiniCode 源码解析：用 5000 行 TypeScript 实现一个 AI 编程助手",[1664,1775,1525,1601,1558],"CLI",{"slug":1777,"path":1778,"title":1779,"date":1766,"tags":1780,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":223},"nova-theme-implementation","\u002Fposts\u002Fnova-theme-implementation","我是怎么从零实现 Nova 主题的",[1781,1782,1783],"Hexo","前端","开源",{"slug":1785,"path":1786,"title":1787,"date":1788,"tags":1789,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":177},"git-cheatsheet","\u002Fposts\u002Fgit-cheatsheet","Git 常用操作备忘","2026-04-05 14:00:00",[1790,1791],"Git","工具",{"slug":1793,"path":1794,"title":1795,"date":1796,"tags":1797,"description":69,"draft":1518,"hidden":1518,"published":242,"readingTime":192},"github-actions-intro","\u002Fposts\u002Fgithub-actions-intro","GitHub Actions 入门：自动化你的工作流","2026-04-04 09:00:00",[1798,1799,1800],"GitHub Actions","CI\u002FCD","自动化",1782796012461]