[{"data":1,"prerenderedAt":1927},["ShallowReactive",2],{"post-\u002Fposts\u002Fmultimodal-rag-from-scratch":3,"all-posts-nav":1656},{"id":4,"title":5,"body":6,"categories":1637,"date":1639,"description":78,"draft":1640,"extension":1641,"hidden":1640,"meta":1642,"navigation":1643,"path":1644,"published":1640,"seo":1645,"stem":1646,"tags":1647,"__hash__":1655},"posts\u002Fposts\u002Fmultimodal-rag-from-scratch.md","从零实现多模态 RAG：BM25、Dense 检索、RRF 融合、MMR 重排全部手写",{"type":7,"value":8,"toc":1619},"minimark",[9,27,35,38,41,46,49,65,68,79,84,86,90,96,98,102,107,110,116,123,279,282,284,288,291,294,300,303,309,381,383,387,390,399,405,411,414,420,482,485,737,739,743,746,752,758,761,1056,1058,1062,1065,1071,1091,1304,1315,1317,1321,1325,1328,1334,1338,1341,1347,1350,1354,1357,1359,1363,1371,1379,1387,1395,1397,1401,1484,1486,1493,1573,1575,1578,1615],[10,11,12],"blockquote",{},[13,14,15,16,23,26],"p",{},"项目地址：",[17,18,22],"a",{"href":19,"rel":20},"https:\u002F\u002Fgithub.com\u002Fliangqianxing\u002Fmultimodal-rag",[21],"nofollow","github.com\u002Fliangqianxing\u002Fmultimodal-rag",[24,25],"br",{},"\n91 个单元测试，全部通过 ✅ · 纯 numpy + PIL，无需 GPU",[13,28,29,30,34],{},"上一个项目写了 LLM 推理引擎，这次做一个互补的方向：",[31,32,33],"strong",{},"多模态 RAG","。",[13,36,37],{},"两者的关系是：推理引擎负责\"怎么跑模型\"，RAG 负责\"给模型喂什么信息\"。面试时两个合在一起讲，能覆盖整个 LLM 应用栈。",[39,40],"hr",{},[42,43,45],"h2",{"id":44},"一rag-解决什么问题","一、RAG 解决什么问题？",[13,47,48],{},"LLM 有两个硬伤：",[50,51,52,59],"ol",{},[53,54,55,58],"li",{},[31,56,57],{},"知识截止","：训练数据有时效性，不知道最新信息",[53,60,61,64],{},[31,62,63],{},"幻觉","：对不确定的内容会瞎编",[13,66,67],{},"RAG 的解法：查询时先检索相关文档，把文档内容拼进 prompt，让模型\"有据可查\"而不是凭空生成。",[69,70,75],"pre",{"className":71,"code":73,"language":74},[72],"language-text","传统 LLM：\n  用户提问 → LLM 直接回答（靠训练时的记忆，可能幻觉）\n\nRAG 流程：\n  用户提问 → 检索相关文档 → 文档 + 提问拼接成 prompt → LLM 回答\n","text",[76,77,73],"code",{"__ignoreMap":78},"",[13,80,81,83],{},[31,82,33],{}," 进一步支持图像检索：用文字查图，用图查文字，或者图文混合检索。",[39,85],{},[42,87,89],{"id":88},"二系统架构","二、系统架构",[69,91,94],{"className":92,"code":93,"language":74},[72],"Documents (文本 + 图像)\n   │\n   ├── 文本 chunks → TF-IDF embedding\n   ├── 图像 chunks → 颜色直方图特征\n   │                    ↓\n   │              随机投影对齐 → 共享 embedding 空间\n   │\n   ▼\n┌─────────────────────────────────┐\n│  向量库 (cosine)  │  BM25 索引  │  ← 双路索引\n└─────────────────────────────────┘\n   │\n   ▼ 查询时\nDense 检索 + BM25 检索\n       ↓\n   RRF 融合\n       ↓\n   MMR 重排\n       ↓\n  Generator → 答案\n",[76,95,93],{"__ignoreMap":78},[39,97],{},[42,99,101],{"id":100},"三四个核心算法逐一拆解","三、四个核心算法，逐一拆解",[103,104,106],"h3",{"id":105},"_31-bm25-从零实现的关键词检索","3.1 BM25 —— 从零实现的关键词检索",[13,108,109],{},"BM25 Okapi 是搜索引擎领域最经典的排名函数，比简单 TF-IDF 更好的地方在于对长文档做了惩罚：",[69,111,114],{"className":112,"code":113,"language":74},[72],"BM25(q, d) = Σ IDF(tᵢ) · f(tᵢ,d)·(k₁+1) \u002F [f(tᵢ,d) + k₁·(1-b+b·|d|\u002Favgdl)]\n\n参数：k₁=1.5（词频饱和），b=0.75（文档长度惩罚）\n",[76,115,113],{"__ignoreMap":78},[13,117,118,119,122],{},"直觉：一篇 10000 词的文章里出现 5 次\"机器学习\"，和一篇 100 词的文章里出现 5 次，相关性应该不同。BM25 用 ",[76,120,121],{},"|d|\u002Favgdl"," 做了归一化。",[69,124,128],{"className":125,"code":126,"language":127,"meta":78,"style":78},"language-python shiki shiki-themes github-dark-dimmed github-light","class BM25:\n    def __init__(self, k1=1.5, b=0.75):\n        ...\n    \n    def fit(self, corpus: List[str]) -> None:\n        # 建立倒排索引：词 → 包含它的文档数（df）\n        # 计算每个词的 IDF\n        ...\n    \n    def get_top_k(self, query: str, k: int) -> List[Tuple[int, float]]:\n        # 分词 → 对每个词查 IDF → 计算 BM25 得分 → 排序\n        ...\n","python",[76,129,130,147,177,183,189,212,219,225,230,235,268,274],{"__ignoreMap":78},[131,132,135,139,143],"span",{"class":133,"line":134},"line",1,[131,136,138],{"class":137},"s6PUj","class",[131,140,142],{"class":141},"sqRhv"," BM25",[131,144,146],{"class":145},"ssh_m",":\n",[131,148,150,153,157,160,163,166,169,171,174],{"class":133,"line":149},2,[131,151,152],{"class":137},"    def",[131,154,156],{"class":155},"swcJU"," __init__",[131,158,159],{"class":145},"(self, k1",[131,161,162],{"class":137},"=",[131,164,165],{"class":155},"1.5",[131,167,168],{"class":145},", b",[131,170,162],{"class":137},[131,172,173],{"class":155},"0.75",[131,175,176],{"class":145},"):\n",[131,178,180],{"class":133,"line":179},3,[131,181,182],{"class":155},"        ...\n",[131,184,186],{"class":133,"line":185},4,[131,187,188],{"class":145},"    \n",[131,190,192,194,198,201,204,207,210],{"class":133,"line":191},5,[131,193,152],{"class":137},[131,195,197],{"class":196},"saVmf"," fit",[131,199,200],{"class":145},"(self, corpus: List[",[131,202,203],{"class":155},"str",[131,205,206],{"class":145},"]) -> ",[131,208,209],{"class":155},"None",[131,211,146],{"class":145},[131,213,215],{"class":133,"line":214},6,[131,216,218],{"class":217},"sgHix","        # 建立倒排索引：词 → 包含它的文档数（df）\n",[131,220,222],{"class":133,"line":221},7,[131,223,224],{"class":217},"        # 计算每个词的 IDF\n",[131,226,228],{"class":133,"line":227},8,[131,229,182],{"class":155},[131,231,233],{"class":133,"line":232},9,[131,234,188],{"class":145},[131,236,238,240,243,246,248,251,254,257,259,262,265],{"class":133,"line":237},10,[131,239,152],{"class":137},[131,241,242],{"class":196}," get_top_k",[131,244,245],{"class":145},"(self, query: ",[131,247,203],{"class":155},[131,249,250],{"class":145},", k: ",[131,252,253],{"class":155},"int",[131,255,256],{"class":145},") -> List[Tuple[",[131,258,253],{"class":155},[131,260,261],{"class":145},", ",[131,263,264],{"class":155},"float",[131,266,267],{"class":145},"]]:\n",[131,269,271],{"class":133,"line":270},11,[131,272,273],{"class":217},"        # 分词 → 对每个词查 IDF → 计算 BM25 得分 → 排序\n",[131,275,277],{"class":133,"line":276},12,[131,278,182],{"class":155},[13,280,281],{},"纯 Python，零依赖，支持增量添加文档。",[39,283],{},[103,285,287],{"id":286},"_32-dense-retrieval-嵌入空间的语义搜索","3.2 Dense Retrieval —— 嵌入空间的语义搜索",[13,289,290],{},"关键词检索的缺点：必须用相同的词才能匹配。\"汽车\"查不到\"automobile\"，\"机器学习\"查不到\"ML\"。",[13,292,293],{},"Dense Retrieval 把查询和文档都映射到同一个向量空间，用余弦相似度：",[69,295,298],{"className":296,"code":297,"language":74},[72],"similarity(q, d) = (q · d) \u002F (‖q‖ · ‖d‖)\n",[76,299,297],{"__ignoreMap":78},[13,301,302],{},"在嵌入空间里，语义相近的词\u002F句子距离也近，即使用词不同。",[13,304,305,308],{},[31,306,307],{},"本项目的 TF-IDF 嵌入器","（纯 numpy，无需 sentence-transformers）：",[69,310,312],{"className":125,"code":311,"language":127,"meta":78,"style":78},"class TFIDFEmbedder:\n    def fit(self, corpus: List[str]) -> None:\n        # 建立词表，计算 IDF 权重\n        ...\n    \n    def embed_texts(self, texts: List[str]) -> np.ndarray:\n        # 对每个文本：TF × IDF，L2 归一化\n        # 返回 shape (N, vocab_size) 的矩阵\n        ...\n",[76,313,314,323,339,344,348,352,367,372,377],{"__ignoreMap":78},[131,315,316,318,321],{"class":133,"line":134},[131,317,138],{"class":137},[131,319,320],{"class":141}," TFIDFEmbedder",[131,322,146],{"class":145},[131,324,325,327,329,331,333,335,337],{"class":133,"line":149},[131,326,152],{"class":137},[131,328,197],{"class":196},[131,330,200],{"class":145},[131,332,203],{"class":155},[131,334,206],{"class":145},[131,336,209],{"class":155},[131,338,146],{"class":145},[131,340,341],{"class":133,"line":179},[131,342,343],{"class":217},"        # 建立词表，计算 IDF 权重\n",[131,345,346],{"class":133,"line":185},[131,347,182],{"class":155},[131,349,350],{"class":133,"line":191},[131,351,188],{"class":145},[131,353,354,356,359,362,364],{"class":133,"line":214},[131,355,152],{"class":137},[131,357,358],{"class":196}," embed_texts",[131,360,361],{"class":145},"(self, texts: List[",[131,363,203],{"class":155},[131,365,366],{"class":145},"]) -> np.ndarray:\n",[131,368,369],{"class":133,"line":221},[131,370,371],{"class":217},"        # 对每个文本：TF × IDF，L2 归一化\n",[131,373,374],{"class":133,"line":227},[131,375,376],{"class":217},"        # 返回 shape (N, vocab_size) 的矩阵\n",[131,378,379],{"class":133,"line":232},[131,380,182],{"class":155},[39,382],{},[103,384,386],{"id":385},"_33-hybrid-retrieval-rrf-融合两路检索","3.3 Hybrid Retrieval —— RRF 融合两路检索",[13,388,389],{},"BM25 和 Dense 的优劣是互补的：",[391,392,393,396],"ul",{},[53,394,395],{},"BM25：精确关键词匹配，速度快，不依赖嵌入质量",[53,397,398],{},"Dense：语义理解，能处理同义词，但词表外词效果差",[13,400,401,404],{},[31,402,403],{},"Reciprocal Rank Fusion（RRF）"," 是最简单有效的融合方法，不需要分数归一化：",[69,406,409],{"className":407,"code":408,"language":74},[72],"RRF(d) = Σᵣ  1 \u002F (k + rankᵣ(d))    k = 60\n",[76,410,408],{"__ignoreMap":78},[13,412,413],{},"直觉：不关心绝对得分，只看在每个检索器的排名。第 1 名得 1\u002F(60+1)≈0.016，第 10 名得 1\u002F(60+10)≈0.014。把两路的分数加起来重新排序。",[13,415,416,419],{},[31,417,418],{},"实测结果","：",[421,422,423,439],"table",{},[424,425,426],"thead",{},[427,428,429,433,436],"tr",{},[430,431,432],"th",{},"策略",[430,434,435],{},"Recall@5",[430,437,438],{},"MRR@5",[440,441,442,454,465],"tbody",{},[427,443,444,448,451],{},[445,446,447],"td",{},"BM25 Only",[445,449,450],{},"0.42",[445,452,453],{},"0.35",[427,455,456,459,462],{},[445,457,458],{},"Dense Only",[445,460,461],{},"0.61",[445,463,464],{},"0.54",[427,466,467,472,477],{},[445,468,469],{},[31,470,471],{},"Hybrid (RRF)",[445,473,474],{},[31,475,476],{},"0.71",[445,478,479],{},[31,480,481],{},"0.63",[13,483,484],{},"混合检索比单一策略分别高出 +10pp 和 +17pp。",[69,486,488],{"className":125,"code":487,"language":127,"meta":78,"style":78},"class HybridRetriever:\n    def retrieve(self, query: str, k: int) -> List[Tuple[Chunk, float]]:\n        dense_results = self.dense.retrieve(query, k * 2)  # 候选池放大\n        bm25_results  = self.sparse.retrieve(query, k * 2)\n        \n        # RRF 融合\n        scores = {}\n        for rank, (chunk, _) in enumerate(dense_results):\n            scores[chunk.chunk_id] = scores.get(chunk.chunk_id, 0) + 1\u002F(self.rrf_k + rank + 1)\n        for rank, (chunk, _) in enumerate(bm25_results):\n            scores[chunk.chunk_id] = scores.get(chunk.chunk_id, 0) + 1\u002F(self.rrf_k + rank + 1)\n        \n        return sorted(results, key=lambda x: x[1], reverse=True)[:k]\n",[76,489,490,499,521,546,565,570,575,585,602,647,660,694,698],{"__ignoreMap":78},[131,491,492,494,497],{"class":133,"line":134},[131,493,138],{"class":137},[131,495,496],{"class":141}," HybridRetriever",[131,498,146],{"class":145},[131,500,501,503,506,508,510,512,514,517,519],{"class":133,"line":149},[131,502,152],{"class":137},[131,504,505],{"class":196}," retrieve",[131,507,245],{"class":145},[131,509,203],{"class":155},[131,511,250],{"class":145},[131,513,253],{"class":155},[131,515,516],{"class":145},") -> List[Tuple[Chunk, ",[131,518,264],{"class":155},[131,520,267],{"class":145},[131,522,523,526,528,531,534,537,540,543],{"class":133,"line":179},[131,524,525],{"class":145},"        dense_results ",[131,527,162],{"class":137},[131,529,530],{"class":155}," self",[131,532,533],{"class":145},".dense.retrieve(query, k ",[131,535,536],{"class":137},"*",[131,538,539],{"class":155}," 2",[131,541,542],{"class":145},")  ",[131,544,545],{"class":217},"# 候选池放大\n",[131,547,548,551,553,555,558,560,562],{"class":133,"line":185},[131,549,550],{"class":145},"        bm25_results  ",[131,552,162],{"class":137},[131,554,530],{"class":155},[131,556,557],{"class":145},".sparse.retrieve(query, k ",[131,559,536],{"class":137},[131,561,539],{"class":155},[131,563,564],{"class":145},")\n",[131,566,567],{"class":133,"line":191},[131,568,569],{"class":145},"        \n",[131,571,572],{"class":133,"line":214},[131,573,574],{"class":217},"        # RRF 融合\n",[131,576,577,580,582],{"class":133,"line":221},[131,578,579],{"class":145},"        scores ",[131,581,162],{"class":137},[131,583,584],{"class":145}," {}\n",[131,586,587,590,593,596,599],{"class":133,"line":227},[131,588,589],{"class":137},"        for",[131,591,592],{"class":145}," rank, (chunk, _) ",[131,594,595],{"class":137},"in",[131,597,598],{"class":155}," enumerate",[131,600,601],{"class":145},"(dense_results):\n",[131,603,604,607,609,612,615,618,621,624,627,630,633,636,638,641,643,645],{"class":133,"line":232},[131,605,606],{"class":145},"            scores[chunk.chunk_id] ",[131,608,162],{"class":137},[131,610,611],{"class":145}," scores.get(chunk.chunk_id, ",[131,613,614],{"class":155},"0",[131,616,617],{"class":145},") ",[131,619,620],{"class":137},"+",[131,622,623],{"class":155}," 1",[131,625,626],{"class":137},"\u002F",[131,628,629],{"class":145},"(",[131,631,632],{"class":155},"self",[131,634,635],{"class":145},".rrf_k ",[131,637,620],{"class":137},[131,639,640],{"class":145}," rank ",[131,642,620],{"class":137},[131,644,623],{"class":155},[131,646,564],{"class":145},[131,648,649,651,653,655,657],{"class":133,"line":237},[131,650,589],{"class":137},[131,652,592],{"class":145},[131,654,595],{"class":137},[131,656,598],{"class":155},[131,658,659],{"class":145},"(bm25_results):\n",[131,661,662,664,666,668,670,672,674,676,678,680,682,684,686,688,690,692],{"class":133,"line":270},[131,663,606],{"class":145},[131,665,162],{"class":137},[131,667,611],{"class":145},[131,669,614],{"class":155},[131,671,617],{"class":145},[131,673,620],{"class":137},[131,675,623],{"class":155},[131,677,626],{"class":137},[131,679,629],{"class":145},[131,681,632],{"class":155},[131,683,635],{"class":145},[131,685,620],{"class":137},[131,687,640],{"class":145},[131,689,620],{"class":137},[131,691,623],{"class":155},[131,693,564],{"class":145},[131,695,696],{"class":133,"line":276},[131,697,569],{"class":145},[131,699,701,704,707,710,714,717,720,723,726,729,731,734],{"class":133,"line":700},13,[131,702,703],{"class":137},"        return",[131,705,706],{"class":155}," sorted",[131,708,709],{"class":145},"(results, ",[131,711,713],{"class":712},"sNjOc","key",[131,715,716],{"class":137},"=lambda",[131,718,719],{"class":145}," x: x[",[131,721,722],{"class":155},"1",[131,724,725],{"class":145},"], ",[131,727,728],{"class":712},"reverse",[131,730,162],{"class":137},[131,732,733],{"class":155},"True",[131,735,736],{"class":145},")[:k]\n",[39,738],{},[103,740,742],{"id":741},"_34-mmr-最大边际相关性解决结果同质化","3.4 MMR —— 最大边际相关性，解决结果同质化",[13,744,745],{},"Top-5 结果全是说\"机器学习是数据驱动方法\"的不同说法，对 LLM 没有帮助。",[13,747,748,751],{},[31,749,750],{},"Maximal Marginal Relevance（MMR）"," 在选每个结果时，不仅考虑与 query 的相关度，还惩罚与已选结果的相似度：",[69,753,756],{"className":754,"code":755,"language":74},[72],"MMR(dᵢ) = argmax [ λ·sim(dᵢ, q) - (1-λ)·max_{dⱼ∈S} sim(dᵢ, dⱼ) ]\n\nλ=0.5：相关性和多样性各占一半\nS：已选结果集合\n",[76,757,755],{"__ignoreMap":78},[13,759,760],{},"实现：贪心迭代，每次选 MMR 分最高的文档加入结果集：",[69,762,764],{"className":125,"code":763,"language":127,"meta":78,"style":78},"class MMRRetriever:\n    def retrieve(self, query: str, k: int) -> List[Tuple[Chunk, float]]:\n        # 1. 先用 base_retriever 取候选池（k * 3 个）\n        candidates = self.base_retriever.retrieve(query, k * 3)\n        \n        # 2. 贪心 MMR 选取 k 个\n        selected = []\n        q_emb = self.embedder.embed_query(query)\n        \n        while len(selected) \u003C k and candidates:\n            best_chunk, best_score = None, -float(\"inf\")\n            for chunk, rel_score in candidates:\n                div_penalty = max(\n                    self._cosine(chunk.embedding, s.embedding)\n                    for s, _ in selected\n                ) if selected else 0.0\n                mmr_score = self.lambda_param * rel_score - (1 - self.lambda_param) * div_penalty\n                if mmr_score > best_score:\n                    best_chunk, best_score = chunk, mmr_score\n            selected.append((best_chunk, best_score))\n            candidates.remove((best_chunk, _))\n        \n        return selected\n",[76,765,766,775,795,800,819,823,828,838,850,854,877,902,914,927,936,950,968,1006,1021,1032,1038,1044,1049],{"__ignoreMap":78},[131,767,768,770,773],{"class":133,"line":134},[131,769,138],{"class":137},[131,771,772],{"class":141}," MMRRetriever",[131,774,146],{"class":145},[131,776,777,779,781,783,785,787,789,791,793],{"class":133,"line":149},[131,778,152],{"class":137},[131,780,505],{"class":196},[131,782,245],{"class":145},[131,784,203],{"class":155},[131,786,250],{"class":145},[131,788,253],{"class":155},[131,790,516],{"class":145},[131,792,264],{"class":155},[131,794,267],{"class":145},[131,796,797],{"class":133,"line":179},[131,798,799],{"class":217},"        # 1. 先用 base_retriever 取候选池（k * 3 个）\n",[131,801,802,805,807,809,812,814,817],{"class":133,"line":185},[131,803,804],{"class":145},"        candidates ",[131,806,162],{"class":137},[131,808,530],{"class":155},[131,810,811],{"class":145},".base_retriever.retrieve(query, k ",[131,813,536],{"class":137},[131,815,816],{"class":155}," 3",[131,818,564],{"class":145},[131,820,821],{"class":133,"line":191},[131,822,569],{"class":145},[131,824,825],{"class":133,"line":214},[131,826,827],{"class":217},"        # 2. 贪心 MMR 选取 k 个\n",[131,829,830,833,835],{"class":133,"line":221},[131,831,832],{"class":145},"        selected ",[131,834,162],{"class":137},[131,836,837],{"class":145}," []\n",[131,839,840,843,845,847],{"class":133,"line":227},[131,841,842],{"class":145},"        q_emb ",[131,844,162],{"class":137},[131,846,530],{"class":155},[131,848,849],{"class":145},".embedder.embed_query(query)\n",[131,851,852],{"class":133,"line":232},[131,853,569],{"class":145},[131,855,856,859,862,865,868,871,874],{"class":133,"line":237},[131,857,858],{"class":137},"        while",[131,860,861],{"class":155}," len",[131,863,864],{"class":145},"(selected) ",[131,866,867],{"class":137},"\u003C",[131,869,870],{"class":145}," k ",[131,872,873],{"class":137},"and",[131,875,876],{"class":145}," candidates:\n",[131,878,879,882,884,887,889,892,894,896,900],{"class":133,"line":270},[131,880,881],{"class":145},"            best_chunk, best_score ",[131,883,162],{"class":137},[131,885,886],{"class":155}," None",[131,888,261],{"class":145},[131,890,891],{"class":137},"-",[131,893,264],{"class":155},[131,895,629],{"class":145},[131,897,899],{"class":898},"sXfbr","\"inf\"",[131,901,564],{"class":145},[131,903,904,907,910,912],{"class":133,"line":276},[131,905,906],{"class":137},"            for",[131,908,909],{"class":145}," chunk, rel_score ",[131,911,595],{"class":137},[131,913,876],{"class":145},[131,915,916,919,921,924],{"class":133,"line":700},[131,917,918],{"class":145},"                div_penalty ",[131,920,162],{"class":137},[131,922,923],{"class":155}," max",[131,925,926],{"class":145},"(\n",[131,928,930,933],{"class":133,"line":929},14,[131,931,932],{"class":155},"                    self",[131,934,935],{"class":145},"._cosine(chunk.embedding, s.embedding)\n",[131,937,939,942,945,947],{"class":133,"line":938},15,[131,940,941],{"class":137},"                    for",[131,943,944],{"class":145}," s, _ ",[131,946,595],{"class":137},[131,948,949],{"class":145}," selected\n",[131,951,953,956,959,962,965],{"class":133,"line":952},16,[131,954,955],{"class":145},"                ) ",[131,957,958],{"class":137},"if",[131,960,961],{"class":145}," selected ",[131,963,964],{"class":137},"else",[131,966,967],{"class":155}," 0.0\n",[131,969,971,974,976,978,981,983,986,988,991,993,996,998,1001,1003],{"class":133,"line":970},17,[131,972,973],{"class":145},"                mmr_score ",[131,975,162],{"class":137},[131,977,530],{"class":155},[131,979,980],{"class":145},".lambda_param ",[131,982,536],{"class":137},[131,984,985],{"class":145}," rel_score ",[131,987,891],{"class":137},[131,989,990],{"class":145}," (",[131,992,722],{"class":155},[131,994,995],{"class":137}," -",[131,997,530],{"class":155},[131,999,1000],{"class":145},".lambda_param) ",[131,1002,536],{"class":137},[131,1004,1005],{"class":145}," div_penalty\n",[131,1007,1009,1012,1015,1018],{"class":133,"line":1008},18,[131,1010,1011],{"class":137},"                if",[131,1013,1014],{"class":145}," mmr_score ",[131,1016,1017],{"class":137},">",[131,1019,1020],{"class":145}," best_score:\n",[131,1022,1024,1027,1029],{"class":133,"line":1023},19,[131,1025,1026],{"class":145},"                    best_chunk, best_score ",[131,1028,162],{"class":137},[131,1030,1031],{"class":145}," chunk, mmr_score\n",[131,1033,1035],{"class":133,"line":1034},20,[131,1036,1037],{"class":145},"            selected.append((best_chunk, best_score))\n",[131,1039,1041],{"class":133,"line":1040},21,[131,1042,1043],{"class":145},"            candidates.remove((best_chunk, _))\n",[131,1045,1047],{"class":133,"line":1046},22,[131,1048,569],{"class":145},[131,1050,1052,1054],{"class":133,"line":1051},23,[131,1053,703],{"class":137},[131,1055,949],{"class":145},[39,1057],{},[42,1059,1061],{"id":1060},"四多模态嵌入不用-gpu-的跨模态对齐","四、多模态嵌入：不用 GPU 的跨模态对齐",[13,1063,1064],{},"CLIP 等模型能把文本和图像映射到同一空间，但需要 GPU 和大模型。",[13,1066,1067,1068,419],{},"本项目用",[31,1069,1070],{},"随机投影对齐",[50,1072,1073,1079,1085],{},[53,1074,1075,1078],{},[31,1076,1077],{},"文本","：TF-IDF 向量（vocab_size 维）",[53,1080,1081,1084],{},[31,1082,1083],{},"图像","：颜色直方图（R\u002FG\u002FB 各 64 bins + 灰度 32 bins = 224 维）",[53,1086,1087,1090],{},[31,1088,1089],{},"对齐","：固定随机矩阵 W（seed=42，可复现）将两种特征投影到同一 64 维空间",[69,1092,1094],{"className":125,"code":1093,"language":127,"meta":78,"style":78},"class MultimodalEmbedder:\n    def fit(self, text_corpus, image_samples):\n        # 拟合 TF-IDF 词表\n        self.text_emb.fit(text_corpus)\n        \n        # 随机投影矩阵（固定种子，确保复现）\n        rng = np.random.RandomState(42)\n        self.text_proj  = rng.randn(self.text_emb.dim, self.target_dim) \u002F np.sqrt(self.target_dim)\n        self.image_proj = rng.randn(self.image_emb.dim, self.target_dim) \u002F np.sqrt(self.target_dim)\n    \n    def embed_texts(self, texts):\n        raw = self.text_emb.embed_texts(texts)   # (N, vocab_size)\n        return self._l2_norm(raw @ self.text_proj)  # (N, target_dim)\n    \n    def embed_images(self, images):\n        raw = self.image_emb.embed_images(images) # (N, 224)\n        return self._l2_norm(raw @ self.image_proj) # (N, target_dim)\n",[76,1095,1096,1105,1114,1119,1127,1131,1136,1151,1183,1211,1215,1224,1239,1259,1263,1273,1287],{"__ignoreMap":78},[131,1097,1098,1100,1103],{"class":133,"line":134},[131,1099,138],{"class":137},[131,1101,1102],{"class":141}," MultimodalEmbedder",[131,1104,146],{"class":145},[131,1106,1107,1109,1111],{"class":133,"line":149},[131,1108,152],{"class":137},[131,1110,197],{"class":196},[131,1112,1113],{"class":145},"(self, text_corpus, image_samples):\n",[131,1115,1116],{"class":133,"line":179},[131,1117,1118],{"class":217},"        # 拟合 TF-IDF 词表\n",[131,1120,1121,1124],{"class":133,"line":185},[131,1122,1123],{"class":155},"        self",[131,1125,1126],{"class":145},".text_emb.fit(text_corpus)\n",[131,1128,1129],{"class":133,"line":191},[131,1130,569],{"class":145},[131,1132,1133],{"class":133,"line":214},[131,1134,1135],{"class":217},"        # 随机投影矩阵（固定种子，确保复现）\n",[131,1137,1138,1141,1143,1146,1149],{"class":133,"line":221},[131,1139,1140],{"class":145},"        rng ",[131,1142,162],{"class":137},[131,1144,1145],{"class":145}," np.random.RandomState(",[131,1147,1148],{"class":155},"42",[131,1150,564],{"class":145},[131,1152,1153,1155,1158,1160,1163,1165,1168,1170,1173,1175,1178,1180],{"class":133,"line":227},[131,1154,1123],{"class":155},[131,1156,1157],{"class":145},".text_proj  ",[131,1159,162],{"class":137},[131,1161,1162],{"class":145}," rng.randn(",[131,1164,632],{"class":155},[131,1166,1167],{"class":145},".text_emb.dim, ",[131,1169,632],{"class":155},[131,1171,1172],{"class":145},".target_dim) ",[131,1174,626],{"class":137},[131,1176,1177],{"class":145}," np.sqrt(",[131,1179,632],{"class":155},[131,1181,1182],{"class":145},".target_dim)\n",[131,1184,1185,1187,1190,1192,1194,1196,1199,1201,1203,1205,1207,1209],{"class":133,"line":232},[131,1186,1123],{"class":155},[131,1188,1189],{"class":145},".image_proj ",[131,1191,162],{"class":137},[131,1193,1162],{"class":145},[131,1195,632],{"class":155},[131,1197,1198],{"class":145},".image_emb.dim, ",[131,1200,632],{"class":155},[131,1202,1172],{"class":145},[131,1204,626],{"class":137},[131,1206,1177],{"class":145},[131,1208,632],{"class":155},[131,1210,1182],{"class":145},[131,1212,1213],{"class":133,"line":237},[131,1214,188],{"class":145},[131,1216,1217,1219,1221],{"class":133,"line":270},[131,1218,152],{"class":137},[131,1220,358],{"class":196},[131,1222,1223],{"class":145},"(self, texts):\n",[131,1225,1226,1229,1231,1233,1236],{"class":133,"line":276},[131,1227,1228],{"class":145},"        raw ",[131,1230,162],{"class":137},[131,1232,530],{"class":155},[131,1234,1235],{"class":145},".text_emb.embed_texts(texts)   ",[131,1237,1238],{"class":217},"# (N, vocab_size)\n",[131,1240,1241,1243,1245,1248,1251,1253,1256],{"class":133,"line":700},[131,1242,703],{"class":137},[131,1244,530],{"class":155},[131,1246,1247],{"class":145},"._l2_norm(raw ",[131,1249,1250],{"class":137},"@",[131,1252,530],{"class":155},[131,1254,1255],{"class":145},".text_proj)  ",[131,1257,1258],{"class":217},"# (N, target_dim)\n",[131,1260,1261],{"class":133,"line":929},[131,1262,188],{"class":145},[131,1264,1265,1267,1270],{"class":133,"line":938},[131,1266,152],{"class":137},[131,1268,1269],{"class":196}," embed_images",[131,1271,1272],{"class":145},"(self, images):\n",[131,1274,1275,1277,1279,1281,1284],{"class":133,"line":952},[131,1276,1228],{"class":145},[131,1278,162],{"class":137},[131,1280,530],{"class":155},[131,1282,1283],{"class":145},".image_emb.embed_images(images) ",[131,1285,1286],{"class":217},"# (N, 224)\n",[131,1288,1289,1291,1293,1295,1297,1299,1302],{"class":133,"line":970},[131,1290,703],{"class":137},[131,1292,530],{"class":155},[131,1294,1247],{"class":145},[131,1296,1250],{"class":137},[131,1298,530],{"class":155},[131,1300,1301],{"class":145},".image_proj) ",[131,1303,1258],{"class":217},[13,1305,1306,1307,1310,1311,1314],{},"效果比真实 CLIP 差，但完整展示了跨模态 RAG 的机制，且换成真实 encoder 只需替换 ",[76,1308,1309],{},"embed_texts"," 和 ",[76,1312,1313],{},"embed_images"," 两个方法。",[39,1316],{},[42,1318,1320],{"id":1319},"五评估指标","五、评估指标",[103,1322,1324],{"id":1323},"recallk","Recall@K",[13,1326,1327],{},"在前 K 个检索结果中，有多少比例的相关文档被找到：",[69,1329,1332],{"className":1330,"code":1331,"language":74},[72],"Recall@K = |retrieved[:K] ∩ relevant| \u002F |relevant|\n",[76,1333,1331],{"__ignoreMap":78},[103,1335,1337],{"id":1336},"mrrkmean-reciprocal-rank","MRR@K（Mean Reciprocal Rank）",[13,1339,1340],{},"第一个相关文档排在第几：",[69,1342,1345],{"className":1343,"code":1344,"language":74},[72],"MRR = 1 \u002F rank_of_first_relevant_doc\n",[76,1346,1344],{"__ignoreMap":78},[13,1348,1349],{},"第 1 名：MRR=1.0；第 2 名：MRR=0.5；第 5 名：MRR=0.2。",[103,1351,1353],{"id":1352},"ndcgk","NDCG@K",[13,1355,1356],{},"考虑相关文档的排序位置，排名越靠前得分越高。",[39,1358],{},[42,1360,1362],{"id":1361},"六面试高频考点","六、面试高频考点",[13,1364,1365,1368,1370],{},[31,1366,1367],{},"Q：RAG 和 Fine-tuning 的区别？什么时候用哪个？",[24,1369],{},"\nA：Fine-tuning 把知识\"烧进\"模型权重，适合固定领域、大量训练数据。RAG 在推理时动态检索，适合频繁更新的知识库、需要引用来源、或者不想训练成本的场景。实际上两者不互斥，可以先 RAG 再 fine-tune。",[13,1372,1373,1376,1378],{},[31,1374,1375],{},"Q：为什么 Hybrid 比单一检索好？",[24,1377],{},"\nA：BM25 和 Dense 的错误不相关。BM25 在精确匹配、专有名词上强，Dense 在语义理解、同义词上强。RRF 融合时，两路都认为相关的文档得分最高，是一种\"投票\"机制，比任何单一方法更鲁棒。",[13,1380,1381,1384,1386],{},[31,1382,1383],{},"Q：MMR 什么时候比 top-K 好？",[24,1385],{},"\nA：当 corpus 里有大量相似文档时，top-K 会全返回近似重复的内容。MMR 强制多样性，让 generator 拿到不同角度的信息。但当 corpus 天然多样时，MMR 反而可能降低 Recall（因为为了多样性牺牲了最相关的结果），所以 λ 是一个需要根据数据调的超参数。",[13,1388,1389,1392,1394],{},[31,1390,1391],{},"Q：如何扩展到亿级文档？",[24,1393],{},"\nA：本项目用 numpy 暴力计算，适合 ~50K chunks。生产中换成 FAISS（Facebook 的向量库，支持 IVF 分区 + PQ 压缩）或 Milvus\u002FWeaviate 等向量数据库。BM25 层可以换成 Elasticsearch。整体 pipeline 不变，只替换底层实现。",[39,1396],{},[42,1398,1400],{"id":1399},"七与-langchain-llamaindex-的对比","七、与 LangChain \u002F LlamaIndex 的对比",[421,1402,1403,1416],{},[424,1404,1405],{},[427,1406,1407,1410,1413],{},[430,1408,1409],{},"维度",[430,1411,1412],{},"本项目",[430,1414,1415],{},"LangChain \u002F LlamaIndex",[440,1417,1418,1429,1440,1451,1462,1473],{},[427,1419,1420,1423,1426],{},[445,1421,1422],{},"BM25 实现",[445,1424,1425],{},"从零，每行可读",[445,1427,1428],{},"封装的 rank-bm25 库",[427,1430,1431,1434,1437],{},[445,1432,1433],{},"RRF 融合",[445,1435,1436],{},"公式直接对应代码",[445,1438,1439],{},"可配置的抽象层",[427,1441,1442,1445,1448],{},[445,1443,1444],{},"MMR 重排",[445,1446,1447],{},"手写，λ 可调",[445,1449,1450],{},"内置 helper，内部隐藏",[427,1452,1453,1456,1459],{},[445,1454,1455],{},"多模态对齐",[445,1457,1458],{},"显式随机投影",[445,1460,1461],{},"依赖外部 encoder",[427,1463,1464,1467,1470],{},[445,1465,1466],{},"评估指标",[445,1468,1469],{},"内置 Recall\u002FMRR\u002FNDCG",[445,1471,1472],{},"需要外部评估框架",[427,1474,1475,1478,1481],{},[445,1476,1477],{},"学习价值",[445,1479,1480],{},"高，公式 = 代码",[445,1482,1483],{},"低，抽象遮蔽细节",[39,1485],{},[13,1487,1488,1489],{},"代码在 GitHub：",[17,1490,1492],{"href":19,"rel":1491},[21],"liangqianxing\u002Fmultimodal-rag",[69,1494,1498],{"className":1495,"code":1496,"language":1497,"meta":78,"style":78},"language-bash shiki shiki-themes github-dark-dimmed github-light","git clone https:\u002F\u002Fgithub.com\u002Fliangqianxing\u002Fmultimodal-rag\ncd multimodal-rag\npip install pytest numpy Pillow\npytest tests\u002F -v                    # 91 tests, all pass\npython examples\u002Fbasic_usage.py      # 端到端 demo\npython run_all_benchmarks.py --fast # 检索质量 + 延迟 benchmark\n","bash",[76,1499,1500,1511,1519,1536,1550,1560],{"__ignoreMap":78},[131,1501,1502,1505,1508],{"class":133,"line":134},[131,1503,1504],{"class":141},"git",[131,1506,1507],{"class":898}," clone",[131,1509,1510],{"class":898}," https:\u002F\u002Fgithub.com\u002Fliangqianxing\u002Fmultimodal-rag\n",[131,1512,1513,1516],{"class":133,"line":149},[131,1514,1515],{"class":155},"cd",[131,1517,1518],{"class":898}," multimodal-rag\n",[131,1520,1521,1524,1527,1530,1533],{"class":133,"line":179},[131,1522,1523],{"class":141},"pip",[131,1525,1526],{"class":898}," install",[131,1528,1529],{"class":898}," pytest",[131,1531,1532],{"class":898}," numpy",[131,1534,1535],{"class":898}," Pillow\n",[131,1537,1538,1541,1544,1547],{"class":133,"line":185},[131,1539,1540],{"class":141},"pytest",[131,1542,1543],{"class":898}," tests\u002F",[131,1545,1546],{"class":155}," -v",[131,1548,1549],{"class":217},"                    # 91 tests, all pass\n",[131,1551,1552,1554,1557],{"class":133,"line":191},[131,1553,127],{"class":141},[131,1555,1556],{"class":898}," examples\u002Fbasic_usage.py",[131,1558,1559],{"class":217},"      # 端到端 demo\n",[131,1561,1562,1564,1567,1570],{"class":133,"line":214},[131,1563,127],{"class":141},[131,1565,1566],{"class":898}," run_all_benchmarks.py",[131,1568,1569],{"class":155}," --fast",[131,1571,1572],{"class":217}," # 检索质量 + 延迟 benchmark\n",[39,1574],{},[13,1576,1577],{},"两个项目合起来，基本覆盖了 AI Infra 面试的核心考察点：",[421,1579,1580,1590],{},[424,1581,1582],{},[427,1583,1584,1587],{},[430,1585,1586],{},"项目",[430,1588,1589],{},"覆盖的面试考点",[440,1591,1592,1604],{},[427,1593,1594,1601],{},[445,1595,1596],{},[17,1597,1600],{"href":1598,"rel":1599},"https:\u002F\u002Fgithub.com\u002Fliangqianxing\u002Fmini-llm-engine",[21],"mini-llm-engine",[445,1602,1603],{},"KV Cache、Continuous Batching、GPU 显存管理、投机解码",[427,1605,1606,1612],{},[445,1607,1608],{},[17,1609,1611],{"href":19,"rel":1610},[21],"multimodal-rag",[445,1613,1614],{},"向量检索、BM25、混合检索、多模态对齐、评估指标",[1616,1617,1618],"style",{},"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 .saVmf, html code.shiki .saVmf{--shiki-default:#DCBDFB;--shiki-light:#6F42C1}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 .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":78,"searchDepth":149,"depth":179,"links":1620},[1621,1622,1623,1629,1630,1635,1636],{"id":44,"depth":149,"text":45},{"id":88,"depth":149,"text":89},{"id":100,"depth":149,"text":101,"children":1624},[1625,1626,1627,1628],{"id":105,"depth":179,"text":106},{"id":286,"depth":179,"text":287},{"id":385,"depth":179,"text":386},{"id":741,"depth":179,"text":742},{"id":1060,"depth":149,"text":1061},{"id":1319,"depth":149,"text":1320,"children":1631},[1632,1633,1634],{"id":1323,"depth":179,"text":1324},{"id":1336,"depth":179,"text":1337},{"id":1352,"depth":179,"text":1353},{"id":1361,"depth":149,"text":1362},{"id":1399,"depth":149,"text":1400},[1638],"AI","2026-06-30 18:00:00",false,"md",{},true,"\u002Fposts\u002Fmultimodal-rag-from-scratch",{"title":5,"description":78},"posts\u002Fmultimodal-rag-from-scratch",[1648,1649,1650,1651,1652,1653,1654],"RAG","多模态","AI Infra","BM25","向量检索","混合检索","实习求职","eJt1tYVJrs8ZR_KJpakfzIP5B7mA2yX-0pU640X23Dk",[1657,1660,1672,1678,1690,1699,1708,1718,1728,1737,1747,1758,1771,1783,1791,1801,1813,1824,1834,1842,1853,1859,1865,1871,1879,1888,1896,1902,1910,1918],{"slug":1658,"path":1644,"title":5,"date":1639,"tags":1659,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":232},"multimodal-rag-from-scratch",[1648,1649,1650,1651,1652,1653,1654],{"slug":1661,"path":1662,"title":1663,"date":1664,"tags":1665,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":270},"mini-llm-engine-deep-dive","\u002Fposts\u002Fmini-llm-engine-deep-dive","讲透 mini-llm-engine：从显存碎片到六大推理优化","2026-06-30 14:00:00",[1666,1650,1667,1668,1669,1670,1671,1654],"LLM","vLLM","PagedAttention","KV Cache","推理优化","投机解码",{"slug":1673,"path":1674,"title":1675,"date":1676,"tags":1677,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":237},"mini-llm-engine-from-scratch","\u002Fposts\u002Fmini-llm-engine-from-scratch","从零实现 LLM 推理引擎：深挖 vLLM 的六大核心优化","2026-06-30 10:00:00",[1666,1650,1667,1668,1670,1671,1654],{"slug":1679,"path":1680,"title":1681,"date":1682,"tags":1683,"description":1689,"draft":1640,"hidden":1640,"published":1643,"readingTime":1008},"bytedance-recommendation-architecture-intern-interview","\u002Fposts\u002Fbytedance-recommendation-architecture-intern-interview","字节推荐架构实习生 Data 面试准备：推荐系统、实时特征与高并发八股","2026-06-09",[1684,1685,1686,1687,1688],"面试","推荐系统","后端架构","实时计算","字节跳动","面向字节跳动推荐架构团队实习岗位的八股准备清单，覆盖推荐系统链路、实时数据、特征服务、高并发后端、分布式系统与项目包装。",{"slug":1691,"path":1692,"title":1693,"date":1682,"tags":1694,"description":1698,"draft":1640,"hidden":1640,"published":1643,"readingTime":276},"high-concurrency-rate-limiting-algorithms","\u002Fposts\u002Fhigh-concurrency-rate-limiting-algorithms","高并发限流算法：固定窗口、滑动窗口、漏桶与令牌桶",[1695,1696,1686,1697,1684],"高并发","限流","系统设计","面试和工程都常见的限流算法总结，讲清楚固定窗口、滑动窗口、漏桶、令牌桶、并发数限制以及分布式限流如何落地。",{"slug":1700,"path":1701,"title":1702,"date":1682,"tags":1703,"description":1707,"draft":1640,"hidden":1640,"published":1643,"readingTime":227},"kafka-producer-broker-consumer","\u002Fposts\u002Fkafka-producer-broker-consumer","Kafka 入门：生产者、Broker、消费者和“消费”到底是什么意思",[1704,1705,1706,1684,1685],"Kafka","消息队列","后端","用推荐系统里的用户行为日志为例，讲清楚 Kafka 的作用、Producer、Broker、Consumer、Topic、Partition、Offset 和消费语义。",{"slug":1709,"path":1710,"title":1711,"date":1682,"tags":1712,"description":1717,"draft":1640,"hidden":1640,"published":1643,"readingTime":214},"leetcode-lru-merge-k-reverse-list","\u002Fposts\u002Fleetcode-lru-merge-k-reverse-list","链表与缓存高频题：LRU Cache、合并 K 个有序链表、反转链表",[1713,1714,1715,1716,1684],"算法","链表","LRU","LeetCode","面试高频算法题速记，整理 LRU Cache、合并 K 个有序链表、反转链表的核心思路、复杂度和 C++ 代码。",{"slug":1719,"path":1720,"title":1721,"date":1722,"tags":1723,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":952},"meddococr-interpreter-source-analysis","\u002Fposts\u002Fmeddococr-interpreter-source-analysis","MedDocOCR-Interpreter 源码导读：医疗文档 OCR、结构化抽取与报告解读原型","2026-05-22 10:30:00",[1724,1649,1725,1648,1726,1727],"OCR","医疗 AI","Python","源码分析",{"slug":1729,"path":1730,"title":1731,"date":1732,"tags":1733,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":237},"cet6-writing-model-essays","\u002Fposts\u002Fcet6-writing-model-essays","六级写作范文背诵包：10 个高频话题","2026-05-20 09:00:00",[1734,1735,1736],"English","CET6","Writing",{"slug":1738,"path":1739,"title":1740,"date":1741,"tags":1742,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":1746},"claude-code-context-management","\u002Fposts\u002Fclaude-code-context-management","Claude Code 上下文管理机制：从 Microcompact 到 Auto Compact","2026-05-19 10:00:00",[1743,1744,1666,1650,1745],"Claude Code","Agent","上下文工程",27,{"slug":1748,"path":1749,"title":1750,"date":1751,"tags":1752,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":1023},"nanotron-llm-pretraining-framework-analysis","\u002Fposts\u002Fnanotron-llm-pretraining-framework-analysis","Nanotron 项目详解：Hugging Face 的大模型预训练框架怎么做分布式训练","2026-05-10 12:10:00",[1666,1753,1754,1755,1756,1650,1757],"大模型训练","分布式训练","Nanotron","Hugging Face","PyTorch",{"slug":1759,"path":1760,"title":1761,"date":1762,"tags":1763,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":1770},"gofoundry-go-backend-foundation-framework","\u002Fposts\u002Fgofoundry-go-backend-foundation-framework","GoFoundry 项目详解：基于 Go 的后端基础框架套件设计","2026-05-10 11:20:00",[1764,1765,1766,1767,1768,1705,1769],"Go","后端框架","ORM","分布式缓存","分布式锁","项目架构",25,{"slug":1772,"path":1773,"title":1774,"date":1775,"tags":1776,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":1746},"cloudvault-go-cloud-storage-system","\u002Fposts\u002Fcloudvault-go-cloud-storage-system","CloudVault 项目详解：基于 Go 的云端存储与网盘系统架构设计","2026-05-10 10:30:00",[1764,1777,1778,1779,1769,1780,1781,1782],"云存储","网盘系统","分布式系统","Redis","RabbitMQ","Elasticsearch",{"slug":1784,"path":1785,"title":1786,"date":1787,"tags":1788,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":1034},"openclaw-source-code-analysis","\u002Fposts\u002Fopenclaw-source-code-analysis","OpenClaw 源码导读：个人 AI 助手的网关、通道、插件与运行时架构","2026-05-08 16:30:00",[1789,1744,1650,1790,1727],"OpenClaw","TypeScript",{"slug":1792,"path":1793,"title":1794,"date":1795,"tags":1796,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":237},"flow-matching-generative-models","\u002Fposts\u002Fflow-matching-generative-models","Flow Matching：从噪声到数据的连续流生成模型","2026-05-07 00:00:00",[1797,1798,1799,1800],"生成模型","Diffusion","Flow Matching","深度学习",{"slug":1802,"path":1803,"title":1804,"date":1805,"tags":1806,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":938},"database-ai-week4","\u002Fposts\u002Fdatabase-ai-week4","Week 4：数据库速成——从 Storage、Index、Query Optimization 到 Vector DB 与 RAG","2026-05-05 12:00:00",[1807,1808,1809,1648,1810,1811,1812],"数据库","CMU 15-445","Vector DB","LLM Memory","Query Optimization","Caching",{"slug":1814,"path":1815,"title":1816,"date":1817,"tags":1818,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":952},"distributed-systems-week3","\u002Fposts\u002Fdistributed-systems-week3","Week 3：分布式系统速成——MapReduce、Raft、容错与 Distributed KV Store","2026-05-05 11:00:00",[1779,1819,1820,1821,1822,1823,1744],"MIT 6.824","MapReduce","Raft","KV Store","Ray",{"slug":1825,"path":1826,"title":1827,"date":1828,"tags":1829,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":938},"gpu-inference-acceleration-week2","\u002Fposts\u002Fgpu-inference-acceleration-week2","Week 2：GPU 与推理加速——从 Kernel、算子融合到 LLM Serving","2026-05-05 10:00:00",[1800,1830,1831,1666,1832,1667,1833],"GPU","推理加速","CMU 10-414","TensorRT",{"slug":1835,"path":1836,"title":1837,"date":1838,"tags":1839,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":1008},"dl-framework-autograd-mini","\u002Fposts\u002Fdl-framework-autograd-mini","Week 1：DL 框架与 Autograd——从计算图、反向传播到 Mini Autograd 实现","2026-05-05 09:00:00",[1800,1840,1757,1832,1841],"Autograd","Mini Framework",{"slug":1843,"path":1844,"title":1845,"date":1846,"tags":1847,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":276},"lock-free-concurrency-notes","\u002Fposts\u002Flock-free-concurrency-notes","无锁并发入门：从 CAS 到 Atomic Ring Buffer","2026-04-25",[1848,1849,1850,1851,1852],"C++","并发","无锁编程","性能优化","量化开发",{"slug":1854,"path":1855,"title":1856,"date":1857,"tags":1858,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":191},"agent-memory","\u002Fposts\u002Fagent-memory","Agent 对话记忆化：从原理到实现","2026-04-24",[1666,1744,1648,1684],{"slug":1860,"path":1861,"title":1862,"date":1857,"tags":1863,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":214},"llm-context-compression","\u002Fposts\u002Fllm-context-compression","LLM 上下文五层压缩机制详解",[1666,1744,1864,1684],"上下文压缩",{"slug":1866,"path":1867,"title":1868,"date":1869,"tags":1870,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":232},"cpp-concurrency-basics","\u002Fposts\u002Fcpp-concurrency-basics","C++ 并发编程入门：从数据竞争到线程池","2026-04-15",[1848,1849,1684,1852],{"slug":1872,"path":1873,"title":1874,"date":1875,"tags":1876,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":134},"travel-in-shenzhen","\u002Fposts\u002Ftravel-in-shenzhen","XCPC 深圳游记","2026-04-13",[1877,1848,1878],"XCPC","比赛",{"slug":1880,"path":1881,"title":1882,"date":1883,"tags":1884,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":221},"backend-stack-deep-dive","\u002Fposts\u002Fbackend-stack-deep-dive","后端五件套：FastAPI \u002F Node.js \u002F SQLAlchemy async \u002F PostgreSQL \u002F Docker 面试速通","2026-04-07",[1706,1885,1886,1887,1684],"FastAPI","PostgreSQL","Docker",{"slug":1889,"path":1890,"title":1891,"date":1892,"tags":1893,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":214},"deepscientist-tech-stack","\u002Fposts\u002Fdeepscientist-tech-stack","DeepScientist 技术栈全解析：一个 AI 科研平台的架构设计","2026-04-06",[1894,1885,1895,1886,1684],"全栈","Next.js",{"slug":1897,"path":1898,"title":1899,"date":1892,"tags":1900,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":227},"minicode-source-analysis","\u002Fposts\u002Fminicode-source-analysis","MiniCode 源码解析：用 5000 行 TypeScript 实现一个 AI 编程助手",[1790,1901,1666,1727,1684],"CLI",{"slug":1903,"path":1904,"title":1905,"date":1892,"tags":1906,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":191},"nova-theme-implementation","\u002Fposts\u002Fnova-theme-implementation","我是怎么从零实现 Nova 主题的",[1907,1908,1909],"Hexo","前端","开源",{"slug":1911,"path":1912,"title":1913,"date":1914,"tags":1915,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":149},"git-cheatsheet","\u002Fposts\u002Fgit-cheatsheet","Git 常用操作备忘","2026-04-05 14:00:00",[1916,1917],"Git","工具",{"slug":1919,"path":1920,"title":1921,"date":1922,"tags":1923,"description":78,"draft":1640,"hidden":1640,"published":1643,"readingTime":179},"github-actions-intro","\u002Fposts\u002Fgithub-actions-intro","GitHub Actions 入门：自动化你的工作流","2026-04-04 09:00:00",[1924,1925,1926],"GitHub Actions","CI\u002FCD","自动化",1782796012491]