HiCache:如何适配CP、PP、投机解码与PD分离?
Agentic应用会反复向模型提交超长且高度相似的历史上下文。HiCache在Radix cache基础上把KV复用扩展到主机内存与外部存储,并进一步适配CP、PP、Speculative decoding和P/D分离等生产级推理架构。
Agentic负载里,请求反复带上的历史上下文往往很长,而且大部分内容不变。Radix cache能在GPU显存中复用前缀KV,HiCache把这种复用延伸到主机内存和外部存储。到了生产环境,缓存系统还得同时适配多种并行与分布式推理机制,涵盖Context Parallelism、Pipeline Parallelism、Speculative decoding以及prefill-decode disaggregation。
HiCache在这些场景里的核心设计可以这样梳理:Context Parallelism下维持统一的逻辑前缀;Pipeline Parallelism中让各stage做出相同的缓存决策;Speculative decoding中恢复draft模型状态;用统一KV布局跨并行配置共享缓存;P/D分离场景只传输缺失的KV。
生产实践中,科大讯飞Iflytek Astron MaaS团队把Pipeline Parallelism、prefill-decode disaggregation和MTP结合起来,服务GLM-5.1/5.2 CodingPlan工作负载。请求的输入token通常在30K到200K之间,少数能到100万token。没开HiCache时,缓存命中率大约30%,用户等待时间偏长;接入带外部存储的HiCache后,命中率升到95%,median TTFT降到3秒以内。
Context Parallelism × HiCache:基于页面的KV分片
Context Parallelism会把prefill计算分散到多个rank上,可KV cache若仍在各rank各存一份,显存就白白浪费了。HiCache的做法是按page对KV做sharding,重复副本被去掉,每个请求的逻辑视图仍然统一。
统一逻辑Radix tree与页面归属
CP场景下,每个rank都维护同一棵逻辑Radix tree,logical page ID也保持一致。拿两个rank举例,偶数page分给rank 0,奇数page分给rank 1。这样一来,A/B/C这三个前缀页的KV即使实际落在不同GPU上,任意rank命中A/B/C,认出的仍是同一组逻辑缓存页。
面对N个shard rank、page大小为p个token、逻辑槽位为x的情况,HiCache用简单算术映射来定位KV:q = floor(x / p)它表示logical cache page ID,r = q mod N它表示owner rank,l = floor(q / N) * p + (x mod p)它表示owner本地row。这套映射不用逐页查表。
请求复用A/B/C并接着追加page D时,allocator会顺着前缀的owner序列往下排:0、1、0之后轮到1。树结构把这种ownership rotation记下来,不兼容的页面轮转不会被混进同一前缀。
图1:两个CP rank共享同一逻辑Radix tree,logical page ID决定页面owner,扩展A/B/C前缀时D被分配到rank 1。
逐层读取分片KV:每层一次gather
KV分片之后,单个rank的本地cache只存着完整上下文的一部分。attention要读到完整前缀,每个rank就得在处理某一层之前,把自己手里的pages通过all-gather贡献出来,写进该层的临时buffer。
比如rank 0贡献A和C,rank 1贡献B和padding page。gather完的buffer按owner分组,attention借助page-table mapping按逻辑顺序A、B、C读取,物理上不用重排。系统用两个临时scratch buffer在层间轮换:当前层做attention计算时,下一层的gather可以同时跑,assembled KV多出来的显存就压在两个layer buffer以内。
在支持的配置上,gather走GPU copy engines,不用NCCL SM kernels,跟模型计算抢SM资源的情况少了。换句话说,前缀命中省掉了重复的prefill计算,但分布式KV gather这笔开销仍然要付。
图2:每个rank贡献本层所拥有的pages,all-gather生成相同的owner-ordered scratch buffer;双scratch slot让下一层gather与当前层attention重叠。
HiCache集成:先恢复各自shard,再执行gather
HiCache在GPU与host memory之间搬KV时,页面归属关系同样得保住。不然恢复出来的page可能落到错误的rank上,下次gather就没人贡献对应的KV。还是上面那三页,rank 0备份并恢复A和C,rank 1备份并恢复B;GPU物理地址就算变了,owner关系照样不变。
cache tree和transfer queues用的还是逻辑索引。真正拷贝时,每个rank挑出属于自己的source-destination page pairs,再翻译成本地地址。执行顺序是这样:先在transfer stream里恢复本地pages,然后gather stream等该层的restore-completion event,最后attention等gather buffer完成。
图3:HiCache备份与恢复时保持页面owner不变;每个rank按restore、gather、attention顺序推进。
生产流量下的性能结果
评测跑的是GLM-5.2-NVFP4,TP4/CP4、EAGLE MTP、static memory fraction 0.9。每个deployment里有2个prefill engine,每个engine跑在4张NVIDIA GB300 GPU上。对照组的做法是在GPU上复制KV,并在host memory里对MLA KV做去重;HiCache组则用CP-sharded KV。两组的每engine目标host-cache预算都是960GB:baseline是1个去重池,HiCache是4个shard rank各240GB。
3小时生产流量窗口里,每种配置处理约24.5K个带cache-tier归因的请求,平均输入长度136K至137K token,总输入量只差1.8%。结果是CP-sharded HiCache把L1命中率从42.83%拉高到83.31%,L2命中率从50.27%掉到9.62%,总命中率维持在约93%。同一时间,平均prefill排队时间从14.64秒降到6.42秒,forward时间从3.15秒降到2.16秒。
图4:CP-sharded HiCache明显抬高GPU侧L1命中率,prefill排队和forward耗时随之下降。
Pipeline Parallelism:各stage的缓存决策要一致
Pipeline Parallelism里,模型层被切到不同stage上。HiCache的规矩是:所有stage走同一套逻辑缓存操作序列,但KV tensor只留在各stage本地。请求在不同时间步穿过pipeline时,各stage执行的cache hit、restore、lock、unlock和eviction逻辑一模一样,对应的KV数据并不跨stage搬。
图5:4个pipeline stage在连续时间步处理同一个请求,各stage做相同的逻辑缓存操作,KV保持本地。
麻烦在于异步传输的完成时间未必一致。假如PP0只消费A,PP1却消费A和B,各stage的lock状态和eviction资格就会分叉,缓存一致性最终出问题。HiCache的办法是pp_sync同步消费长度:比如PP0选定n = 1并通过pp_sync往后传递,PP1就也只消费A,把B继续留在队列里;PP2则等本地A就绪再消费。
图6:不同PP stage一旦消费不同前缀,lock和eviction状态就会分叉;pp_sync强制各stage消费同一长度。
另外,后台线程会统计各rank的最小hit长度和已完成长度,再发布ACK,比如先发ACK(4)再发布ACK(6)。PP0消费ACK(4)之后就不再等,PP1靠pp_sync跟着PP0消费同一个ACK,而把ACK(6)这些缓存状态会继续留在队列里。异步restore时,pipeline各stage的缓存状态因此能保持一致。
图7:后台线程发布ACK,PP stage据此消费一致的确认长度,缓存决策不会漂移。pp_sync
Speculative decoding:draft模型状态也要一起恢复
在Speculative decoding或MTP中,系统要的不只是target model的KV cache,draft model相关状态同样缺不得。HiCache因此得在恢复target cache的同时,把draft模型所需的cache state一并恢复。
布局上有两种做法。一种是packed integration:draft layers直接追加到target KV和indexer host pools后面,形成一个统一池。另一种是sidecar integration:draft用独立pool,但继承target KV的索引关系。两种方式都能把host slots 7、12、19恢复到GPU slots 42、8、27,差别只在draft状态怎么组织和寻址。
图8:draft cache state可采用packed或sidecar布局,二者都能随target cache一起恢复。
统一KV布局:跨并行配置共享缓存
生产环境里,不同worker用的TP/PP配置可能各不相同。HiCache的做法是统一KV layout,把外部存储中的KV组织成可被兼容并行配置读取的对象,部署形态一变就cache失效的情况因此减少。
举个例子,TP4/PP2 writer会把每个rank的一段KV写成8个共享L3对象;换成TP2/PP1 reader读取时,一次能组装4个chunk。扩展后的对象把heads 4–5在layers 30–59的K和V组织为连续range,只要并行切分方式兼容,同一份存储KV就能复用。
图9:统一KV layout支持TP4/PP2写入、TP2/PP1读取,在兼容配置间共享外部KV对象。
Prefill-decode disaggregation:只传输缺失KV
在P/D分离架构中,prefill worker管prefill KV的计算,decode worker负责后续生成。decode侧如果已经持有部分前缀,HiCache会优先复用decode本地缓存,GPU resident cache和host cache都算在内,prefill侧只需通过P/D链路传输缺失的后缀。
拿12K-token prompt来说,可以拆成三个4K span:A已经在decode GPU上,B能从decode host pool恢复,只有缺失的C需要从prefill worker跨P/D链路传输。网络传输因此减少,prefill到decode的交接延迟也跟着降低。
图10:P/D分离下,decode侧优先复用本地前缀,prefill只传输缺失KV后缀。
调试:用rank_consensus_checker定位不一致
多并行系统里,最难排查的往往不是单个rank失败,而是不同rank对缓存状态的判断对不上。HiCache提供rank_consensus_checker来定位这类问题,包括hit length不一致、restore完成状态不一致、lock/unlock顺序不一致、eviction资格分叉,以及PP ACK消费不一致等。
总体来看,HiCache把单机Radix cache扩展为面向生产分布式推理的多层缓存系统:KV在GPU、host memory和外部存储之间迁移,同时保持CP、PP、Speculative decoding和P/D分离下的逻辑一致性。对长上下文Agentic应用来说,这种能力直接转化为更高命中率、更低TTFT和更稳定的服务成本。