☰
从源码拆 DigUp:4天1146星,它凭什么让开发者连夜围观?
2026/10/20 4:12:42 网站建设 项目流程

从源码拆 DigUp:4天1146星,它凭什么让开发者连夜围观?

【免费下载链接】DigUpSearch your computer by what's in your files: photos, PDFs, documents, recordings, videos and code. On-device on Mac, Windows and Linux, built on Google DeepMind's EmbeddingGemma 2.项目地址: https://gitcode.com/gh_mirrors/di/DigUp

一款 macOS 应用,在开源社区以"4 天 1146 星"的速度被开发者围观,靠的不是营销文案,而是三个词:按内容搜索你的文件。DigUp 做的事情,用一句话说就是——在视频里搜"斑马"能直接定位到那一帧,在播客里搜"他们聊睡眠的那一分钟"能跳到对应时间点,在合同 PDF 里搜"关于宠物的条款"能标出那一页,而且全部在本机运行、无账号、无遥测。

它的技术底座是 Google DeepMind 于 2026 年 10 月开源的 EmbeddingGemma 2(Apache 2.0、740M 参数、768 维、支持 100+ 语言),推理走 llama.cpp 的 Metal 后端,索引与检索由作者手写的 Swift 代码完成。这篇文章不讨论"它有多火",而是直接拆仓库源码,看这套"多模态语义检索 + 本地文件索引"的工程到底怎么落地:架构怎么分层、检索怎么混合、选型怎么取舍、早期项目又埋了哪些坑。

一、先看仓库布局:一个"小而美"的 SwiftPM 工程

整个仓库是一份标准 Swift Package,顶层只有Sources/、Tests/、evals/、scripts/、worker/五个目录,外部依赖只有一个 Sparkle(应用内更新)。工程被刻意切成四层:

  • DigUpKit:纯引擎,无任何 UI。爬取、提取、索引、检索、评估全部在这里;
  • LlamaRuntime + CLlama:llama.cpp 的 C 绑定与 EmbeddingGemma 2 推理封装;
  • digup:开发者 CLI,同时也是应用内部使用的"索引助手"与"查询编码器";
  • DigUpApp:菜单栏 + 热键的 SwiftUI/AppKit 壳。

Package.swift里有一处非常典型的工程细节:可执行产物故意不叫 "DigUp" 而叫DigUpApp,注释写得很直白——"在大小写不敏感的磁盘上,它会和digupCLI 二进制撞名"。这种对 macOS 文件系统大小写不敏感的敏感,是这个仓库里无处不在的工程气质的缩影。

Sources/DigUpKit下的文件职责异常清晰:Rules.swift 管"什么该索引、什么绝不碰",Crawler.swift 管目录遍历,Extractors.swift 与 MediaExtractors.swift 管多模态提取,IndexStore.swift 管 SQLite 存储,Searcher.swift 管混合检索。加上 evals/ 里 code、multilingual、synthetic 三份评测查询集,以及 scripts/testbed/ 生成测试语料的脚本——这是一个把"评测驱动开发"写进仓库结构的项目。

二、索引流水线:从文件到向量,每一类文件都有专门的拆解方式

DigUp 的核心不是"把整张图/整个 PDF 扔给模型",而是把每种文件拆成模型能吃的最小语义单元,再逐一嵌入。这一层(Extractors.swift、MediaExtractors.swift、CodeExtractor.swift)是全项目工程量最大的部分。

图片与截图:ImageIO 直接解码到缩略图尺寸("一张 48MP 的照片永远不会被全分辨率解码"),截图额外走 Apple Vision OCR(recognitionLevel = .accurate),把屏幕文字喂给关键词索引。这里有一个很反直觉的设计决策——截图文字只进关键词索引,不进语义向量。代码注释给出的原因是:给 OCR 文字也做向量会让截图在评测里"碾压更好的语义匹配"(IndexStore.swift 的migrateScreenshotText),所以迁移时直接把ocr段的向量清空。

PDF:按页处理,文本层少于 200 字符的页面直接渲染成图片交给视觉编码器;超过 50 页的长文件第一遍只读前 30 页,其余等所有文件都处理完再补。更细的功夫在"文本层坏了"的检测上:PageTextProblem 能识别两类常见故障——孟加拉语/印地语被按字形顺序读出(元音符号跑到辅音前面)、阿拉伯语因 lam 连字丢失字母。前者改用"渲染成图 + 乱序文本向量"的组合,后者用 OCR 重新识别。

音频:解码成 16kHz 单声道,按 30 秒窗口、25 秒步长切成 WAV 片段,静音片段(RMS 低于约 −50dBFS)直接丢弃。模型对单段音频超过 30 秒的输入会静默截断,所以窗口必须压在 30 秒内——"一条 3 小时的播客,内存始终只占一个窗口"。

视频:每 3 秒抽一帧,用 8×8 平均哈希做帧间去重(4 bit 内视为同一镜头,只保留一帧),音轨按音频同样处理。抽帧和音轨各自入索引,于是"视频里出现斑马"与"播客里聊睡眠"才能被同一个查询命中。

代码:按整行切"stretch"(默认 1800 字符),Jupyter notebook 按 cell 切;读取时直接丢弃"DO NOT EDIT"、@generated标记的生成代码、minified 文件和含私钥的文件(CodeExtractor.swift)。每个 stretch 的标题是"仓库名 + 仓库内路径"(如billing/payments/retry.py),实测比只用文件名在真实代码评测里 MRR 从 0.69 提到 0.79。

这些提取器全部运行在独立的 helper 进程里,而不是 App 主进程——因为 Vision OCR 单是模型就要吃约 100MB 内存。主 App 保持小而空闲,索引助手索引完一分钟内自动退出(Engine.swift)。

三、混合检索:语义、关键词、类型提示怎么"拧"成一个分数

IndexStore.swift 做了一个看起来很"反潮流"的决定:不用向量数据库,一个 SQLite 文件装下一切。files、segments、fts5 三张表,向量以 fp16 BLOB 与行数据同库存储,"崩溃永远不会让向量和描述它的行脱节"。20k 个向量约 30MB,内存里全量暴力检索只要几毫秒——注释里直接写了 "no vector database needed"。

Vectors.swift 则展示了极致的省内存纪律:向量在内存里保持 fp16,用mmap分配(70k 向量只占 108MB,对比 fp32 的 215MB),查询时按 1024 行分块转成 fp32 再用vDSP_mmul做矩阵-向量积;munmap保证释放时真正归还内存,而不是留在 malloc 的缓存里(作者实测malloc_zone_pressure_relief都拿不回来)。配合releaseVectors()在查询编码器空闲退出时同步卸载向量、以及returnFreedMemory()的malloc_zone_pressure_relief调用——这是把内存占用当成一等公民来设计的教科书案例。

检索侧的混合逻辑在 Searcher.swift,核心是三路信号融合:

  1. 语义:查询向量与全量矩阵求余弦相似度,但直接比相似度是有偏差的——文字、图像、音频三类向量的得分水平天然不同(实测文本 0.75–0.86,图像音频 0.67–0.78)。所以作者先按 modality 分组去均值,再对所有得分做 z-score 标准化,只有 z ≥ 2.0 的段才算"在统计意义上脱颖而出"。这套校准让长文件的后续页不再拉低整组均值(否则"一本书的几百页会把每次查询的文字均值都拉低,文档盖过截图")。
  2. 关键词:SQLite FTS5 + Porter 词干 + bm25,覆盖文件名、文件夹名、OCR、页面文本和文档文本。分词器被定制成categories 'L* N* Co M*',让孟加拉语元音符号和阿拉伯语变音符号留在词内(否则 SQLite 默认会把"কলকাতায়"切成残片)。
  3. 融合打分:score = meaningWeight × max(z, 0) + wordsWeight × coverage^1.5 + 类型提示加分。关键参数全部写死在代码里且带调优注释:wordWeight = 2.0、codeWordWeight = 1.0(代码里关键词到处都是,"retry" 这种词权重过高会盖过语义匹配)、zGate = 2.0。对"发票号、错误码、长数字"这类查询(isLookup),权重会反转成"词 ×2、语义 ×0.5"——因为 embedding 永远记不住一个精确的订单号。

融合方案在注释里留下了完整评测轨迹:这套"z 分数 + coverage 加权"在 synthetic 评测上 R@1 0.88 vs 0.73、在真实评测上 0.86 vs 0.82,都压过了 reciprocal-rank fusion。strongCount按"不低于最高分 0.5 倍"把弱匹配折叠起来,把平均每查询 26.2 个结果收敛到 5.7 个,同时保证不丢正确答案——这是产品层面的"诚实反馈"设计。

四、选型拆解:为什么是 SQLite + 暴力检索,而不是向量数据库

这可能是开发者围观 DigUp 时争议最大的一点,也是最体现判断力的一点。向量数据库(Milvus 一类)解决的是百万级以上向量的 ANNS 检索,而个人 Mac 的文件索引量级是"数万"——Searcher.swift 里写得很清楚:20k 向量 30MB,全量暴力检索几毫秒;即使膨胀到 70k 向量,fp16 也只要 108MB,查询代价多约 2ms。在这个量级上引入 ANNS 是在为不存在的规模问题牺牲精确召回,而全量暴力检索反而保证了 100% 召回。

与常规 RAG 管线对比,DigUp 的取舍更鲜明:多数 RAG 项目的工程量集中在"向量检索"这一步,而 DigUp 把大部分工程投入放在了提取与切分(多模态分段)、评分校准(modality z-score)和精确词兜底(FTS5 全字段覆盖)上——这些恰恰是检索质量差异的真正来源。它连查询编码器都做了两套进程策略:索引时用"文本+图像+音频"全量模型(约 1GB),查询时只启动 text-only 模型(约 0.25GB),面板关闭后空闲约 1 分钟自动退出,向量随之卸载。

另一个容易被忽略的选型是提示词工程:Model.swift 里把 EmbeddingGemma 2 的任务提示词直接写进代码——查询是task: search result | query: ...,代码检索是task: code retrieval | query: ...,文档是title: ... | text: ...。实测"code retrieval"提示词比"search result"在真实代码评测里多命中 2 个 top5、MRR 0.79 vs 0.75。可见多模态 embedding 时代,检索质量的上限有一半写在提示词里。

至于和 Spotlight 的关系,README 的 FAQ 一句带过但非常清晰:Spotlight 按文件名、文本和元数据找文件,DigUp 按内容含义找文件并定位到页面或时刻,两者可以并存。这本质上是在语义搜索与系统级索引之间做了能力切分,而不是替代。

五、冷静看短板:热度背面,早期项目的真实代价

围观归围观,从源码能看出这个"4 天 1146 星"的项目仍处于非常早期、且带有明确取舍的阶段:

平台门槛极窄。仅支持 Apple Silicon + macOS 14+(README 注明在 macOS 26 上测试)。M 系列芯片之外的 Mac 用户直接被排除,Windows/Linux 完全无缘。热度高、受众窄,是它必须面对的现实。

首次建库成本不低。几百个文件要几分钟,一个大的 Downloads 文件夹可能耗时一小时;长 PDF 最多读 5000 页,文档上限约 800 万字符。模型下载 865MB(文本 310MB + 编码器 555MB),索引期间模型常驻约 1GB。README 也承认"只有第一遍重,之后只读新文件"——但第一遍的门槛对普通用户是真实的。

对中文用户有明确的边界。这是仓库里白纸黑字写着的:Apple Vision OCR 只认识约 25 种语言;而不带空格的文字(中文、日文、泰语)不会被切分成词——意味着这类语言没有精确词匹配,语义检索完全依赖向量,中文搜索体验和英文不是一个量级。孟加拉语 PDF 走"渲染成图 + 乱序文本向量"的兜底,但 OCR 本身读不了孟加拉文。纯数字文本(数据导出表)干脆只做关键词、不做向量——因为"数字的向量匹配不了任何人会描述它的方式"。

迭代激进,稳定性待验证。代码注释里到处是 2026-10-07、2026-10-08、2026-10-09 的调优记录,schema 已经演进到第 5 版;模型或设置一变,整个索引会因为 fingerprint 机制全量重建(Indexer.swift 的adopt/resetAll)——向量来自不同模型的索引不能互相比较,这是严谨,但也是升级成本的来源。迁移期间数据库要设 60 秒的 busy_timeout,说明作者预见到了双进程并发打开同一个索引的现实。

检索粒度的物理上限。768 维单向量承载一个 30 秒音频窗口或一整页 PDF,细粒度定位靠的是"分段足够细",而不是模型有多聪明。strongCount的 0.5 阈值折叠虽然减少了噪音结果,但"一刀切"的阈值在跨类型混合查询里未必总是最优。

结语:热度之外,这是一堂工程判断课

DigUp 之所以能让开发者"连夜围观",不是因为它发明了什么模型——EmbeddingGemma 2 是 Google 的,llama.cpp 是社区开源的。它展示的是把这些组件产品化的完整工程判断:规模意识到位(量级决定要不要向量数据库)、内存纪律苛刻(fp16 + mmap + helper 进程)、多模态拆解细致(每一类文件都有专门的语义单元切法)、检索校准严谨(modality z-score + 关键词兜底 + 评测驱动调参),以及一份坦诚的 FAQ 明说局限。

对于想学习"本地多模态检索怎么落地"的开发者,这份仓库本身就是最好的教材;而对于普通 Mac 用户,最值得记住的可能只是那句 FAQ——它和 Spotlight 并存,一个按内容找,一个按名字找。4 天 1146 星的背后,是"终于有人把多模态搜索做成了一件本机就能跑、拿来就能用的东西",而这份源码把"为什么能做到"回答得很彻底。

【免费下载链接】DigUpSearch your computer by what's in your files: photos, PDFs, documents, recordings, videos and code. On-device on Mac, Windows and Linux, built on Google DeepMind's EmbeddingGemma 2.项目地址: https://gitcode.com/gh_mirrors/di/DigUp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询