基于CPU内存的v1 Transfer Connector
vLLM最近支持了外部加载Transfer Connector,基于LMCache给出的StorageSharedConnector的例子,我尝试实现了一个基于共享内存的Transfer Connector。
v1的接口是一个可以layer wise的实现。
实际上使用下来其实没有明确的区分Producer和Consumer的角色,在P2P的场景下可能比较明显,实际上谁生产kv cache谁消费kv cache其实没有明确规定。
这个的好处是Prefix Cache和KV Cache Transfer没有明确的区别了,Prefill和Worker之间唯一的区别就变成了max_token=1和max_token为真实值的区别了。
设想几个场景,Worker即是生成者,也可以是消费者,Prefill也可以即是生产者又是消费者:
- Worker 生成的对话可以存入一个中心化的缓存当中,在多轮对话的时候Prefill可以直接复用这个缓存,只需要计算新的用户对话。
- Prefill 基于新的对话可以生成一个缓存,被Worker使用,也可以被其他的Prefill在新的多轮对话中使用。
有一个比较hack的查看调用栈的方法就是在对应的接口函数抛出异常让程序崩溃,就能在stack trace上看到函数调用的路径了。
当然这个接口也可以实现P2P,毕竟他提供了对应的wait接口,无非是等中心化的缓存是否就绪还是Prefill的直接传输是否就绪区别了,这在提供的接口列表当中可以看到。
实现一个Connector需要关注几个接口。
Worker side
Layer Wise
vllm/vllm/attention/layer.py中可以看到。
| 1 | def unified_attention( | 
- 每一层会有 - wait_for_kv_layer_from_connector调用- connector.wait_for_layer_load。
- 在计算结束以后会有 - maybe_save_kv_layer_to_connector调用- connector.save_kv_layer。
他们分别对应了decode和prefill,其中wait是同步的,save是异步的。
Model Wise
在vllm/vllm/attention/layer.py中
| 1 | self.maybe_setup_kv_connector(scheduler_output) | 
- connector.start_load_kv来自于- maybe_setup_kv_connector,在decoder的model forward之前调用,用于异步启动kv的load。
- connector.wait_for_save来自于- maybe_wait_for_kv_save,在prefill的model forward之后调用,用于整体的save是同步的。
其他的一些接口包括:
- get_finished返回对于给定的request ids对应的已经完成的sending和recving的request ids。
- register_kv_caches用于connector提前注册kvcaches,在初始化kvcache的时候调用,这个应该是NIXL需要这样直接读取整个的kvcaches的显存地址做RDMA和注册。
- bind_connector_metadata,model forward之前bind metadata,这个数据结构是Metadata的数据结构是实现者自由定义的,在- start_load_kv之前调用。
- clear_connector_metadata,model forward之后clear。
换成prefiller和decoder的视角来看
Prefiller Connector
每一层调用save_kv_layer,这个可以是异步的,在model foward之后会调用wait_for_save保证kvcache被传输完,不然其中的kvcache可能会被之后的forward所覆盖。clear_connector_metadata可以帮助清理这次forward相关的metadata。
Decoder Connector
bind_connector_metadata帮助设置forward相关的metadata。
每一层调用start_load_kv,这个可以是异步的,在model forward之前调用,在每一层forward之前wait_for_layer_load,这个是同步的。
Scheduler Side
- get_num_new_matched_tokens,基于传入的- num_computed_tokens获取可以从外部加载的kvcache,这个是给scheduler用的,表明decoder需要加载的tokens。- computed_token代表已经计算过kvcache的token.
 调度器要额外分配一个- external_computed_tokens的slots给外部加载用并且把这部分也算在- computed_token,然后在根据- budget_token - computed_token分配- new_token。
- update_state_after_alloc在scheduler分配slots以后更新connector内部状态,比如用于告知connector是否要加载kvcache。
- build_connector_metadata用于构建connector metadata的相关输出,不能修改输入中的schedulerOutput。
- request_finished在request结束,blocks free之前被调用,可以帮助connector触发相关回调。
上面的接口比如register_kv_caches,bind_connector_metada和clear_connector_metadata不一定要实现,可以把他们理解为一些初始化路径,计算路径上的调用hook,我们希望在相关的hook上处理一些东西就实现这些接口。
CPUMemorySharedConnector
我实现了一个基于共享缓存的实现,Prefill只生成缓存,Worker只消费缓存。
主要是通过用layer和tokens hash做key创建SharedMemory。
完整的项目在这里。