GraphRAG 论文里那本"省 97% token"的账,是"目标对齐"条件下才算得平的——彻底搞懂GraphRAG第七篇

10 阅读22分钟

30 秒判断:GraphRAG 全局问答最硬的卖点是成本——论文里根层社区摘要只要源文本的 2.3–2.6% 。但那个数字带着一个前提,而论文没有把它当成变量报出来:索引侧的提示词是按数据集领域定制的(论文 Figure 1 的图注里写了这句 prompts tailored to the domain of the dataset),评测侧的问题也是从语料描述出发生成的同一领域的高层题——两边都对齐了,但这个"对齐"值多少钱,论文没报。我做了个对照实验:同一份语料、同一套流程,把整套领域框定从"人写的六个新闻域"换成"prompt-tune 自动推断的技术域"——根层压缩比从 37.4% 变成 28.7% (全层 457% → 308%),实体 −20%、社区 −38%、报告总量 −33%。也就是说,这个数更像是"你目标的函数"。

一句话结论:GraphRAG 的成本优势是目标对齐条件下的条件性优势。它用"索引期就把目标定死"换来了省 token;而它的对照基线 TS 是"查询期再适配目标",代价是每次全量扫。拿这两者比成本、却不提这个交换,是那场对比真正缺的东西。

适合谁读:正在评估"要不要上图"的同学;也包括一直把"GraphRAG 省 token"默认理解成"比向量 RAG 省"的同学——第 4 节会把两个对手分开:成本上它比的是 TS(对全文做 map-reduce),质量上才比向量 RAG。也适合想搞清楚"索引期和查询期该怎么分工"的人——文末给了一条可以直接用的判据。

结构速览:1–2 节是那个对照实验;3 节拆"压缩比到底是谁的函数";4 节是论文对比缺的那句限定语;5 节给分工判据;6 节是边界(什么情况下图仍然必要)。

证据标记:✅ 实测(两条自洽的领域配置对照)· 📄 源码 · 📎 论文/官方文档 · 🤔 推断。


0. 接上一篇:那 2.6% 是在什么前提下测出来的

上一篇量清楚了"报告为什么比原文还长"。但还有一个更靠前的问题:论文那个 2.3–2.6% 是怎么来的?

先看两边差在哪。论文用的语料是 101 万 / 171 万 token,我用的最大一份只有 12.4 万;论文每个根社区能分到约 3 万 token,我只有三四千。这看起来像"语料太小"。

但把这条链拆开之后,会发现"语料大"并不是主控变量:

每根社区材料 = (源 token ÷ 实体数) × (每根社区平均实体数)

右边两个因子分别是抽取粒度和聚类粒度——它们都是"你的目标"决定的。同一份语料,你换个抽取的框定,图就变了,压缩比也就变了。

这篇就是那个对照实验。


1. 实验设计:同一份语料,换一套"领域框定"

语料固定:51 篇英文新闻(论文 News 数据集的同源子集——即 MultiHop-RAG,我们取全量 1,381,153 token 的约 9%)。按"索引实际读入的文本"计,124,330 token(含每篇的文件标题;文章正文合计 122,387)。跑两条各自自洽的臂:

臂领域框定(抽取提示词)描述摘要 / 报告提示词说明
B手工给域:"跨体育、科技、娱乐、商业、健康、科学的新闻",10 类中性实体B 自己的常见做法:领域靠人写
A2prompt-tune 自动推断(推出技术 + 体育向的 10 类)A2 自己的工具推断版:整套提示词都由 prompt-tune 产出

为什么是两条臂:要回答的问题只有一个——同一份语料、换一个领域,实体 / 社区 / 报告总量会怎么变。而"领域"在 GraphRAG 里是一组提示词(抽取 + 描述摘要 + 报告),两条臂各自是一套完整的领域配置:一条人写、一条工具推。真实使用中你只会选其中一套,不会拿 A 的抽取去配 B 的报告——所以对照就取这两套,不设第三臂。


2. 结果:换一个领域,三个量各变多少

2.1 主表

臂实体token/实体关系社区层级根社区每根覆盖根层报告÷原文全层报告÷原文
B(手工给域)3,09940.15,2467350–3373,36037.4%457.1%
A2(prompt-tune 自动域)2,48250.13,7434560–4274,60528.7%308.3%

(分母统一取"索引实际读入的文本"= 124,330 token。)

2.2 三个量:实体、社区、报告总量

量BA2变化
实体数3,0992,482−20%
社区数735456−38%
社区报告总量(全层)568,296 token(457.1%)383,331 token(308.3%)−33%

中间的换算量:token/实体 40.1 → 50.1(同样多的原文,抽出的实体少了,每个实体摊到的文本自然变多)、每根社区覆盖 3,360 → 4,605(根社区 37 → 27 变少了)。这两项合起来就是第 3 节那条链,也是压比从 37.4% 走到 28.7% (根层)、457% 走到 308% (全层)的原因。

顺带记一笔:换域也改"怎么写"——但这类比较必须按"每条 / 每份"来,不能看总量(总量差会被数量差吃掉)。两条都试一下就很清楚:

  • 每条实体描述几乎没变:均 37.1 → 36.5 token、中位 28 → 27。之前看总量"115,036 → 90,615(−21%)"像是写法变了,其实那只是实体数少了 20% 的镜像。
  • 每份社区报告确实变长了:均 773 → 841 token( +9% )、中位 734 → 813;逐层都长——L0 1,257 → 1,323、L1 857 → 866、L2 696 → 770、L3 667 → 786。

2.3 先把两边的类型表摆在一起:这是两套分类体系

要谈"实体数差 20%",得先看两边的类型表本身能不能对齐。左列是 B 的手写表,右列是 prompt-tune 自动推断的表:

B(手工给域)                          A2(自动域)
PERSON                       858      ATHLETE                     796
ORGANIZATION                 491      TECHNOLOGY PRODUCT          485
ENTERTAINMENT WORK           413      TECHNOLOGY COMPANY          362
TECHNOLOGY PLATFORM          343      SOFTWARE FEATURE            248
FINANCIAL METRIC             267      SPORTS TEAM                 184
LOCATION                     228      TECHNOLOGY PLATFORM         141
EVENT                        196      HARDWARE COMPONENT          101
SPORTS TEAM                  148      LIVE SPORTS EVENT            76
OCCUPATION                    80      BRAND                        62
BROADCAST MEDIA               69      PROFESSIONAL SPORTS LEAGUE   27
SPORT                          4
MARTIN SCORSESE                2

(末尾那两行是人写提示词的漏网:一个把"运动"当成实体类型,一个干脆把人名写成了类型。)

第一件事就很清楚:两张表几乎不相交——10 个类型里只有 SPORTS TEAM、TECHNOLOGY PLATFORM 两个同名;B 有 84% 的实体(2,608 / 3,099)落在 A2 根本没有的类型里,A2 有 87% (2,157 / 2,482)落在 B 没有的类型里。所以"实体 −20%"不是"同一批类型里抽得更少",而是换了一整套分类体系。

要对齐,只能用两种不依赖类型名的口径(脚本 _type_compare.py):

① 按实体名对账(同名即同一实体):共有 2,046(占 B 的 66%、占 A2 的 82%)|仅 B 1,053|仅 A2 436。仅 B 独有的那 1,053 个,大头正是"B 有槽位、A2 没槽位"的几类:FINANCIAL METRIC 239、LOCATION 166、EVENT 138、PERSON 118、ORGANIZATION 95、ENTERTAINMENT WORK 93。

② 按同名类型对:SPORTS TEAM 148 → 184(+24%) ;TECHNOLOGY PLATFORM 343 → 141(−59%) 。同一个类型名一边增一边减 ⇒ "数量差"高度依赖类型定义,不是简单的"抽多抽少"。

③ 把共有实体按两边类型交叉,就看得到窄表是怎么"改判"的:

B 的类A2 的类实体数
PERSONATHLETE728(B 的 858 个 PERSON 里 85% 被归成"运动员")
ORGANIZATIONTECHNOLOGY COMPANY307
ENTERTAINMENT WORKTECHNOLOGY PRODUCT233
ENTERTAINMENT WORKSOFTWARE FEATURE69
TECHNOLOGY PLATFORMTECHNOLOGY PRODUCT / PLATFORM / HARDWARE COMPONENT / SOFTWARE FEATURE116 / 57 / 42 / 33
EVENTLIVE SPORTS EVENT41
ORGANIZATIONBRAND29

⇒ 窄表做的是两件事:给域内实体换更细的标签(一个粗类 TECHNOLOGY PLATFORM 被拆进四个细类),以及把不属于它的实体硬塞进域内桶(娱乐作品 → 技术产品、事件 → 体育事件、85% 的人 → 运动员)。而它没有槽位的那些类就只能丢——这就是上面"仅 B 有 1,053 个"的来源。

那"匹配"还成不成立? 成立,但证据要落在槽位覆盖 vs 语料分布上:A2 的表在技术域给了 5 个细类、体育域 4 个,而这份语料里技术 + 体育占了 63% 的文档(11 + 21 / 51)——对得上;但它给娱乐 / 商业 / 健康 / 科学(合计 37% 的文档)一个槽位都没留。所以问题不在"窄",而在窄向了与语料分布不匹配的那一侧:域内标签细了,域外没有位置。


3. 于是"压缩比"到底是谁的函数

把这两臂的对照和上一篇的分解式合起来,就是一条责任链(从目标一路推到压缩比):

  1. 你的目标(谁问、问什么) 决定实体类型表——B 是人写的六域表,A2 是工具推断的技术 + 体育表;
  2. 实体类型表同时决定两件事:抽取粒度(同样多的原文抽出多少实体 ⇒ token/实体),以及图的形状,后者再决定聚类粒度(每根社区平均多少实体);
  3. 报告提示词的输出带宽决定每份报告的长度——而"带宽"是两头都管的:findings 是 5–10 条的范围(5 是下限,材料再少也得凑够;10 是上限,材料再多也被截住),另外还有一句长度上限(max_report_length,被 prompt-tune 丢掉了,见上一篇 5.2);
  4. 前两项合起来给出每根社区能分到多少源材料,它与每份报告长度相除,就是压缩比。

每根社区材料 = 抽取粒度 × 聚类粒度  |  压缩比 ≈ 每根社区材料 ÷ 每份报告长度

变量由谁决定实验里的证据
抽取粒度(token/实体)实体类型表 + 提示词 + 语料类型B 40.1 → 50.1 A2(同一语料、同一模型,只换领域框定)
聚类粒度(每根社区实体数)图的形状 + max_cluster_size=10根社区 37 → 27,社区总数 −38%
报告长度(分母)描述摘要 + 报告提示词(findings 5–10 条,下限撑、上限截 + max_report_length 长度上限)B → A2:每份报告 773 → 841 token(+9%) ,逐层都长(L0 1,257→1,323、L2 696→770、L3 667→786)——但报告提示词与抽取一起变,不可单独归因

而那些看起来很像主控变量的东西,其实都不是:

  • 语料总量:不在这条链里。它只通过"实体总数"和"初始划分"间接作用(我上一篇验过:语料从 3.2 万加到 12.4 万,每根社区材料几乎没动)。
  • 提示词(描述摘要 + 报告) :换域时它和抽取一起变,本文拆不开——只能说 "整套换域"的总效果是:实体 −20%、社区 −38%、报告总量 −33%。

真正决定成败的是最上面那一环——你的目标。 它决定类型表,类型表决定抽取粒度,抽取粒度决定每根社区能分到多少材料。这一环要是没定好,后面调什么都没用。


4. 论文和 TS 的那场对比,缺的正是这句限定语

现在回头看那场对比。论文的基线是 TS——把同一套 map-reduce 直接作用在源文本上。它的成本被定为 100%,而 GraphRAG 的根层是 2–3%。

顺手把两个对手分清(这是最容易混的一处)。论文的评测里有六个条件:GraphRAG 的 C0–C3、TS(把 map-reduce 直接作用在源文本上)、SS(vector RAG,"语义检索")。而那张成本表(Table 3)里只有 C0–C3 和 TS——C0 是 TS 的 2.3–2.6%,也就是"省 97%";SS 根本不在表里,因为它每次查询只读 top-k 个文本块,token 上比 GraphRAG 便宜得多。质量那一栏才是和 SS 比:论文写的是"global approaches 在全面性和多样性上显著优于 SS"(win rate 62%–83%);而"没有统计显著差异"说的是 C0–C3 彼此之间、以及它们和 TS 之间。一句话:成本赢 TS,质量赢 SS——别把两个对手并成一个"RAG"。

两者真正的差别不在"图 vs 文本",而在目标适配发生在什么时候:

目标适配发生在代价 / 收益
TS查询期——map 的每一块都是对着问题去读的保留对任意问题的适配性;代价是每次查询全量扫
GraphRAG索引期——报告的角色、评分口径、粒度那时就定死省 token;代价是放弃查询期的适配性

所以"2.6% "这个数字,缺的限定语是:

在"问题形状与索引期预设一致"的前提下。

这不是文字游戏,而是可以实测的——本文第 2 节就是那个"换领域"实验:同一份语料,把整套领域框定从"人写的六个新闻域"换成"prompt-tune 自动推断的技术域",根层压比 28.7% ⇄ 37.4% (全层 308% ⇄ 457%,实体 −20%、社区 −38%)。

GraphRAG 的压缩比是"目标的函数",不是"语料的函数"。 论文报了一个数,却没报这个函数。

需要说清的是,"不公平"这个批评的准确落点:不在于"它做了领域定制" ——一个方法当然有权为领域定制,那是它的设计。而在于它只在一句图注里交代了这个条件,却把结果当作通用能力来报——既没报定制这一步的代价,也没报"换个领域之后这个数会怎么变"。

4.1 顺带纠正一个常被默认的前提

"TS 是适配任何领域的通用方法"——这个说法也不对。TS 一样需要目标,只是它拿到问题之后才适配。 所以那场对比不是"专用 vs 通用",而是"两种不同的适配策略",却只报了其中一边的成本。

4.2 而论文自己的数据,把"质量"那根柱子也拆了

这一点更关键。论文报告了四个指标,而在它真正对标的那个基线(TS)上,GraphRAG 在 comprehensiveness(全面性)和 diversity(多样性)上没有统计显著差异——真正明显赢的只有成本(根层省 97%+)。

也就是说:在这组"目标对齐"的评测里,图的"质量贡献"没有被证明,剩下的立论只有成本——而那根成本柱子也带着条件,而且这个条件有两处:

  • 索引侧:提示词是按数据集领域定制的——论文 Figure 1 的图注原话是 "…LLM prompts tailored to the domain of the dataset" ,§3.1.2 还补了一句可以用 domain-specific few-shot 示例来定制;
  • 评测侧:问题是从语料描述出发生成的 persona → task → question,而且被限定为"需要理解整个语料"的高层题(Algorithm 1)——也就是问题形状与索引预设的领域是同一套。

所以严格说,这篇论文证明的是"在目标对齐的前提下,把语料压成社区摘要这条路很省";至于省下来的这笔账该记给"图"还是记给"领域定制",它没有对照组——实验里不存在"同样做了领域定制、但不建图"的那条臂。这也正是本文第 2 节要补的那一格:只换领域框定,压比就从 37.4% 摆到 28.7%。

(别读反:GraphRAG 在质量上确实显著赢向量 RAG(SS) ——那是另一个对手、另一次对比,见本节开头"两个对手分清"那段。这里说的"没有显著差异"只指 TS 那一栏、以及 C0–C3 彼此之间。)


5. 一条更普适的判据:索引期该物化什么,查询期该做什么

上面那个"适配时机"的交换,其实可以推广成一条设计判据。

5.1 先给分工表

与具体问题无关(可复用)与具体问题相关
索引期✅ 该物化:实体 / 关系 / 逐字证据、社区划分、覆盖度与连通性、社区规模❌ 不该冻结:报告视角、评分口径、抽象层次
查询期(没必要,重复算就是浪费)✅ 该做:选哪些、怎么排序、怎么组织成答案

一句话:索引期物化"问题无关的原料与结构",查询期做"问题相关的选择与渲染"。

5.2 但"判断放晚了"不是免费的

这一点必须说清,否则这条判据会被用成"越晚越好"。查询期做判断要每次调 LLM——GraphRAG 的 Dynamic 那条路就是实测代价:跨域场景 token +41% 、时间 +37% (三次采样均值),而且打分的抖动很大(同一个社区重复评,出现过 1→5、2→0 这种跨越式变化)。索引期做判断的好处恰好是这三条的反面:便宜(一次)、快(查表)、稳定(写死了) 。

所以真正的判据不是"越晚越好",而是:

把每个判断放在"它需要的上下文最便宜可得"的那一期。

  • 判断需要全语料的结构信息(谁和谁连、覆盖多少)⇒ 放索引期,查询期拿不到;
  • 判断需要具体问题(这问题在问什么)⇒ 放查询期,索引期不知道;
  • 两样都需要 ⇒ 索引期物化原料,查询期做判断。

5.3 用这条判据回看 GraphRAG:错配是单向的

环节它需要什么现在放在哪判定
实体 / 关系 / 证据抽取只需原文索引期✅ 分对
社区划分、度数、覆盖度全语料结构索引期✅ 分对
报告 rank(0–10)、size、level全语料结构索引期,且已物化进 parquet✅ 分对
报告视角、评分口径需要具体问题索引期❌ 在不知道问题的时期做了依赖问题的决定
层级粒度常数(max_cluster_size=10、findings 5–10 条)需要用户要什么粒度索引期❌ 同上,而且是代码常数、连提示词都改不动

所以它的错配是单向的:问题不在它没物化结构,而在它物化了一种只对某一个目标成立的抽象。

5.4 一个闭环推论

如果索引期不承担目标,那它就不知道该丢什么,也就压不出高压缩比;压不出来,就只剩下"把原文都带上、查询期现做一遍抽象"——那就是 TS。

而论文自己说:GraphRAG 对 TS 在全面性、多样性上没有统计显著差异。

索引期不承担目标 ⇒ 压不动 ⇒ 退化成 TS ⇒ 而论文承认它对 TS 没有质量优势 ⇒ 那还剩什么理由用它?

换句话说, "索引期要不要绑定目标"不是设计口味问题,而是这套方案立不立得住的问题。


6. 边界:"图的模块性"到底有多重要

说到这里很容易滑向一个过强的结论:"图没用"。这个结论我不认,因为有两处要卡住。

第一,它真正依赖的不是"模块性",而是一个更弱的假设。 压缩成立只需要:语料能被切成相对自包含的小块,且小块摘要能替代原文。这叫"可层级摘要性",是文本性质,不是图论性质。把假设从这里说清,可验证性好得多。

第二,"模块性不重要"只对某一类问题成立。 论文那 125 道评测题是 LLM 从语料描述(不是语料内容) 生成的 personas → tasks → questions,天然是"整体在讲什么"这种抽象题——恰好落在模块性最不重要的那一类。

而模块性真正可能值钱的是另一类问题:

  • 枚举:"哪些文档提到过 X?"
  • 跨源连接:"这两篇的观点在哪里冲突?"
  • 关系查询:"A 和 B 之间是什么关系?"

这类问题靠"摘要"是答不好的——摘要天然不保留枚举。所以更准确的说法是:

模块性对"全局摘要型问答"不重要(至少未被证明);对"枚举 / 连接 / 关系型问答"可能仍然重要。而论文的评测恰好全落在前者。

这也意味着:如果你的需求主要是全局摘要,你该认真考虑不要图;如果你的需求里有大量枚举和连接,图才有它自己的位置。


7. 落地建议:跑完抽取就停下来量一次

抽取本来就是整条链的第一步——所以这里没有"换顺序"这回事,要做的是在第一步和第二步之间插一个决策点:跑完抽取先量一次,再决定要不要往下建:

  1. 定提问者的角色:谁看这些答案、他会问哪一类顶层问题?→ 产出一份问题样本(这一步是设计起点,不该由语料推断);
  2. 由角色 + 问题定实体类型表(而不是让工具从样本里猜域);
  3. 只跑抽取链:load_input → base_text_units → final_documents → extract_graph → finalize_graph——不跑社区、不跑报告、不跑向量库;
  4. 量两个数:token/实体 和"预计根社区数"(实体数 ÷ 10 是个有前提的下界——得"拆得动"才成立,见上一篇 1.1);
  5. 算那个比值:
每根社区材料 ≈ (源 token ÷ 实体数) × 每根社区实体数
压缩比      ≈ 每根社区材料 ÷ 报告长度地板(约 1.5k token)

若这个比值已经接近或小于 1,别再往下跑了——报告会比原文还长,而且这不是调参能救的,得改语料或改目标。

第 4、5 步是我这次临时手算的,值得固定成一个脚本。它省下的不是"小钱 vs 大钱",而是"全链的一段" :抽取是必经步骤,先跑它不浪费;真正省下来的,是当你量完决定不往下走时的那部分后续开销。省多少取决于语料——下面是实测(严格说:拿各目录 stats.json 里各步骤自己的耗时拼的):

语料抽取报告向量化抽取占全链
51 篇新闻(12.4 万 token)9.1–14.9 分11.9–18.7 分未跑42%–49% (第三次只落了抽取 14.9 分、总分没记)
14 篇技术笔记(10.9 万 token)16.1 分43.0 分50.8 分15%
7 篇小语料(3.2 万 token)2.5 分5.7 分7.1 分16%

⇒ 语料越大、报告与向量化越重,"提前叫停"越划算;但 51 篇那套里抽取已经是全链的 4–5 成——别指望它便宜一个数量级。 另外这些时间随并发、模型、跑不跑向量化变化很大,别拿任何一个数当通用值。


8. 小结:三句话

  1. GraphRAG 省 token 的能力,取决于"索引期预设的目标"和"你实际要问的问题"对不对得上。 换个领域框定,这三个量全都变——同一份语料,实测根层压比 28.7% ⇄ 37.4% 、全层 308% ⇄ 457% 、实体 −20% 、社区 −38% 。这是目标的函数,不是语料的函数。
  2. 那场和 TS 的对比缺的正是这句限定语:TS 是"查询期适配目标",GraphRAG 是"索引期适配目标"。它俩比的不是通用 vs 专用,而是两种适配策略——却只报了其中一边的成本。
  3. 所以真正的设计问题不是"图的参数怎么调",而是"索引期和查询期该怎么分工" :索引期物化问题无关的原料与结构,查询期做问题相关的选择与渲染;把每个判断放在"它需要的上下文最便宜可得"的那一期。按这条看,GraphRAG 把依赖目标的判断冻结在了索引期——这是它成本优势的来源,也是它最脆弱的地方。

附:核对方式

  • 版本:graphrag 3.2.0(Python 3.11),DeepSeek deepseek-chat 作补全模型,本地 multilingual-e5-large 作嵌入
  • 语料:论文 News 数据集的同源子集——51 篇英文新闻(固定随机种子 42 分层抽样;全量 1,381,153 token,取到约 9%)。token 分母统一取索引实际读入的文本 = 124,330 token(含每篇文件标题;文章正文合计 122,387)
  • 论文与数据集(📎):原论文 From Local to Global §4 数据集说明(其 News 数据集取自 Tang & Yang, 2024);MultiHop-RAG(Tang, Y. & Yang, Y., 2024)
  • 两臂实验目录:graphrag-exp-multihop(B)/graphrag-exp-multihop-autodomain-selfconsistent(A2);语料、模型、分块、并发度一致,只有领域提示词(抽取 + 描述摘要 + 报告)不同
  • 本地脚本(都在开源仓库):_compare_arms.py(两臂主表:实体 / 关系 / 社区 / 层级 / 根社区 / 每根覆盖 / 报告总量)、_type_compare.py(2.3 的类型表对比、实体名对账、类型交叉表)、_entity_by_domain.py(备用口径:按来源文档类别给实体分域)、_compare_roots.py(根社区尺寸)、_check_example_consistency.py(提示词示例一致性)、_run_index.py(带显式 workflow 清单的索引 runner,可跳过向量化)
  • 论文与官方材料(📎):原论文 From Local to Global: A Graph RAG Approach to Query-Focused Summarization(§3.1.5 社区摘要规则、§4 评测设计与四个指标);官方 RAI 说明里的 Limitations 两条
  • 本文的完整实验记录与代码:github.com/lipeidonggz…

如果你算过"同一份语料、两个不同领域"下的这三个量(实体 / 社区 / 报告总量)的差异,欢迎贴出来——我手上只有一组(根层 28.7% ⇄ 37.4%,实体 −20%、社区 −38%),很想知道这个摆动在别的语料上有多大。