怎么理解Contextual-Retrieval ?(顺手讲一下prompt-cache)
最近这个其实提的人挺多的,GraphRAG的火爆已经是上上个月的事了,其实我之前就讲过,GraphRAG是解决什么问题的,它也没法绝对替代传统RAG
之前关于Graph-RAG的文章
你为什么要用GraphGAG?()
详解MS的GraphRAG的实现流程()
不说Graph擅长得全局问题,和关系梳理,就说它有得干不了得传统RAG领域,其实传统RAG在自己得领域干得也不见得就咋好,Athropic最近推得ContextualRetreival其实是个不错得改进方案。
它具体能改善哪里呢?
举个最简单得例子,你提了一个问题
“WhatwastherevenuegrowthforACMECorpinQ22023?”
其实最有用得一个答案是
“Thecompany’srevenuegrewby3%overthepreviousquarter.”
然后你经过向量数据库,基本不可能选到这个答案为最优,因为上面所谓得Thecompany和previousquarter都不太能和ACMEQ22023有太多语义上得关联,余弦或者欧式,你用啥距离都没用。
有人说用HybridRAG是不是能强点,比如上图,此时,你是加BM25关键字,还是加Graph-RAG都没啥用,你懂得,因为回答是一系列得”The”指代,并没有和问题中得描述有关键字匹配,也没法建立节点和边得关系。
我们进一步观察问题和答案,是不是这俩东西要是能有点关联性就好了啊,比如切出来得chunk,我给转换一下
original_chunk="Thecompany'srevenuegrewby3%overthepreviousquarter."contextualized_chunk="ThischunkisfromanSECfilingonACMEcorp'sperformanceinQ22023;thepreviousquarter'srevenuewas$314'srevenuegrewby3%overthepreviousquarter."
如上面所示,原始得chunk,是两个”The”,导致不管是embedding还是BM25都抓不出来它,那要是把original_chunk,通过某种手段,给转换成下面这种contextualized_chunk,把上下文信息给注入到chunk离,这下,如果还是刚才得问题,那必然是一问一个准。
那这个带上下文信息得chunk是咋生成得?
其实和你graph-rag一样,还得让大模型给你生成
graph-rag是让大模型给你生成图,既点和边还有各种声明,contextualizedretrieval,是让你切chunk得时候,再把文档给一起带过去,然后给你切得这块chunk,带上上下文得信息。
其实单就原理来说,就这样,贼简单
上面这段操作给个prompt就能实现
document{{WHOLE_DOCUMENT}}/documentHereisthechunkwewanttosituatewithinthewholedocumentchunk{{CHUNK_CONTENT}}/chunkPleasegiveashortsuccinctcontexttosituatethischunkwithintheovA家自己测试发现用了ContextualRetrieval以后RAG的错误率能直接下一半下去,这对于查询的命中率提升其实非常高了。
然而细心的观众老爷也发现了吧,这玩意费token啊,这要没事给一个chunk加上下文,就要发一遍全文,谁受得了啊
所以实际上项目里可实现的方法主要有2个
1-滑动窗口吧,别所有的chunk都给全文,根据chunk在切分的文章位置,发一些文章的部分过去,这样能省不少。
2-promptcache
promptcache也是最近比较火feature,说白了就是inbundtoken省钱
咋省钱呢,因为你没用它算力,虽然占AI服务商推理显存了
模型推理就是个单向的前向传播,这块没什么可讲的
那Transformer这东西推理实际上,是一个自回归,啥叫自回归呢,就是我要说:“我爱你”,第一次我是推出来个”我”,然后我用startoftoken+”我”,来推出来”我爱”。以此类推,最后推出来”startoftoken+我,爱,你+EOT”
那你每次是不是推都要占算力,都要折腾一遍,于是乎空间换算力的又能省时间的东西KVcache就出来了,你第一遍推出来”我”。那你第二遍再做推理的时候就别再推”我”这个logit了,直接用就可以了,而“我”相关的放进显存中,下次推理直接用,以此类推。这也是最长用的推理加速方法,你们爱用的vllm类的推理加速最本质上就这么个简单的路子(不提及pageattention和flashattention的话)
但是KVcache这玩意它只能做到到单序列的,也就是反正你这句话用了就用了,下一句别想用,说白了,到下一句话,你还得刷新显存,那CPU到显存到再GPU,你害的来回折腾数据。快也就快一句话。
那我要想快N多句呢?
也不是不行,这就是promptcache做的事,因为你没法跨序列(sequence)来搞注意力,这个不太现实,所以你就得整一个外挂(这外挂甚至可以不是显存,就是大内存就成,反正几千个token的logit被你一次读,你也不会感觉慢,要不凭啥给你便宜?)
第一幅图是普通的推理,第二幅是加kvcache了,第三幅就外挂一个cache,这个cache就可以在多个序列之间cache已经算出来的logit,那它咋识别的?
答案是PML
像上图所示PML明确定义可重用的文本段,称为提示模块(promptmodules)。PML确保在重用注意力状态时位置的准确性,并为用户提供了一个接口来访问他们的提示中的缓存状态。
说白了,假如你用systemprompt给LLM写一段贼老长的COSplay提示词,你就给它放倒promptmodules里,LLM通过PML,或者有些LLM原生可区分prompt中的重用,或者API,就实现了promptcaching的能力
对于AIprovider,其实这么做能省钱,省卡,对于user来说,这样能省钱,我们看一下对比
Claude的cache命中,直接省9/10的inputtoken费用,但是需要注意的是,如果你要玩promptcaching,Athropic要多收你点前,写cache它们这边比较贵
OpenAI就比较粗暴,就是减1半
但是OpenAI这个不需要你在API里写啥,你达到1024以上它就自动给你开,promptcaching来给你省钱,超过1024往上的,是每128个单位上一个台阶,这是自动的好处
因为没用PML,所以OpenAI自动也有自动的坏处
坏处就是你肯定尽量前面的东西要长得一样,不如中间差一个也不给你存,所以最好就用在systemPrompt上面(其实一般也就用在systemprompt上)
而Anthropic要用的话,就需要明确制定了,明确制定的好处是,可以做成类PML的灵活的机制,但是也麻烦。
有了promptcache,现在做Contextual-Retrieval也省了不少的钱(其实做graph-RAG也一样省钱
)
以上工作都做完了,如果再精细化,后面可以跟个rerank,把抽出来的一对带着cotext的chunk再拿rerank模型比如cohere做一次语义排序,然后发给LLM,能效果更好一点。