lmcache-cli 0.4.5.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (399) hide show
  1. lmcache/__init__.py +84 -0
  2. lmcache/_version.py +24 -0
  3. lmcache/cli/__init__.py +1 -0
  4. lmcache/cli/commands/__init__.py +34 -0
  5. lmcache/cli/commands/base.py +157 -0
  6. lmcache/cli/commands/bench/__init__.py +557 -0
  7. lmcache/cli/commands/bench/engine_bench/__init__.py +1 -0
  8. lmcache/cli/commands/bench/engine_bench/config.py +245 -0
  9. lmcache/cli/commands/bench/engine_bench/interactive/__init__.py +274 -0
  10. lmcache/cli/commands/bench/engine_bench/interactive/config.json +10 -0
  11. lmcache/cli/commands/bench/engine_bench/interactive/schema.py +352 -0
  12. lmcache/cli/commands/bench/engine_bench/interactive/state.py +327 -0
  13. lmcache/cli/commands/bench/engine_bench/interactive/terminal.py +291 -0
  14. lmcache/cli/commands/bench/engine_bench/progress.py +145 -0
  15. lmcache/cli/commands/bench/engine_bench/request_sender.py +232 -0
  16. lmcache/cli/commands/bench/engine_bench/stats.py +275 -0
  17. lmcache/cli/commands/bench/engine_bench/workloads/__init__.py +153 -0
  18. lmcache/cli/commands/bench/engine_bench/workloads/base.py +122 -0
  19. lmcache/cli/commands/bench/engine_bench/workloads/long_doc_permutator.py +435 -0
  20. lmcache/cli/commands/bench/engine_bench/workloads/long_doc_qa.py +281 -0
  21. lmcache/cli/commands/bench/engine_bench/workloads/multi_round_chat.py +337 -0
  22. lmcache/cli/commands/bench/engine_bench/workloads/random_prefill.py +178 -0
  23. lmcache/cli/commands/describe.py +310 -0
  24. lmcache/cli/commands/kvcache.py +133 -0
  25. lmcache/cli/commands/mock.py +75 -0
  26. lmcache/cli/commands/ping.py +113 -0
  27. lmcache/cli/commands/query/__init__.py +155 -0
  28. lmcache/cli/commands/query/prompt.py +134 -0
  29. lmcache/cli/commands/query/request.py +357 -0
  30. lmcache/cli/commands/server.py +99 -0
  31. lmcache/cli/commands/tool/__init__.py +63 -0
  32. lmcache/cli/commands/tool/cache_simulator.py +113 -0
  33. lmcache/cli/commands/trace/__init__.py +505 -0
  34. lmcache/cli/commands/trace/dispatch.py +249 -0
  35. lmcache/cli/commands/trace/driver.py +372 -0
  36. lmcache/cli/commands/trace/stats.py +289 -0
  37. lmcache/cli/documents/lmcache.txt +11 -0
  38. lmcache/cli/main.py +42 -0
  39. lmcache/cli/metrics/__init__.py +29 -0
  40. lmcache/cli/metrics/formatter.py +171 -0
  41. lmcache/cli/metrics/handler.py +94 -0
  42. lmcache/cli/metrics/metrics.py +161 -0
  43. lmcache/cli/metrics/section.py +77 -0
  44. lmcache/connections.py +173 -0
  45. lmcache/integration/__init__.py +2 -0
  46. lmcache/integration/base_service_factory.py +165 -0
  47. lmcache/integration/request_telemetry/__init__.py +1 -0
  48. lmcache/integration/request_telemetry/base.py +51 -0
  49. lmcache/integration/request_telemetry/factory.py +113 -0
  50. lmcache/integration/request_telemetry/fastapi.py +109 -0
  51. lmcache/integration/request_telemetry/noop.py +35 -0
  52. lmcache/integration/sglang/__init__.py +2 -0
  53. lmcache/integration/sglang/sglang_adapter.py +326 -0
  54. lmcache/integration/sglang/utils.py +39 -0
  55. lmcache/integration/vllm/__init__.py +1 -0
  56. lmcache/integration/vllm/lmcache_connector_v1.py +213 -0
  57. lmcache/integration/vllm/lmcache_connector_v1_085.py +150 -0
  58. lmcache/integration/vllm/lmcache_mp_connector_0180.py +1072 -0
  59. lmcache/integration/vllm/tests/test_mm_hash_utils.py +112 -0
  60. lmcache/integration/vllm/utils.py +433 -0
  61. lmcache/integration/vllm/vllm_multi_process_adapter.py +1090 -0
  62. lmcache/integration/vllm/vllm_service_factory.py +339 -0
  63. lmcache/integration/vllm/vllm_v1_adapter.py +1713 -0
  64. lmcache/logging.py +107 -0
  65. lmcache/native_storage_ops.pyi +230 -0
  66. lmcache/non_cuda_equivalents.py +1424 -0
  67. lmcache/observability.py +1958 -0
  68. lmcache/storage_backend/serde/__init__.py +1 -0
  69. lmcache/storage_backend/serde/cachegen_basics.py +210 -0
  70. lmcache/storage_backend/serde/cachegen_decoder.py +207 -0
  71. lmcache/storage_backend/serde/cachegen_encoder.py +394 -0
  72. lmcache/storage_backend/serde/serde.py +75 -0
  73. lmcache/tools/__init__.py +1 -0
  74. lmcache/tools/cache_simulator/README.md +392 -0
  75. lmcache/tools/cache_simulator/__init__.py +1 -0
  76. lmcache/tools/cache_simulator/docs/simulate_example.png +0 -0
  77. lmcache/tools/cache_simulator/docs/sweep_example.png +0 -0
  78. lmcache/tools/cache_simulator/gen_bench_dataset.py +360 -0
  79. lmcache/tools/cache_simulator/lru_cache.py +124 -0
  80. lmcache/tools/cache_simulator/plot_hit_rate.py +231 -0
  81. lmcache/tools/cache_simulator/simulator.py +795 -0
  82. lmcache/tools/controller_benchmark/README.md +161 -0
  83. lmcache/tools/controller_benchmark/__init__.py +1 -0
  84. lmcache/tools/controller_benchmark/__main__.py +331 -0
  85. lmcache/tools/controller_benchmark/benchmark.py +660 -0
  86. lmcache/tools/controller_benchmark/config.py +44 -0
  87. lmcache/tools/controller_benchmark/constants.py +10 -0
  88. lmcache/tools/controller_benchmark/handlers/__init__.py +46 -0
  89. lmcache/tools/controller_benchmark/handlers/admit.py +52 -0
  90. lmcache/tools/controller_benchmark/handlers/base.py +47 -0
  91. lmcache/tools/controller_benchmark/handlers/deregister.py +49 -0
  92. lmcache/tools/controller_benchmark/handlers/evict.py +52 -0
  93. lmcache/tools/controller_benchmark/handlers/heartbeat.py +56 -0
  94. lmcache/tools/controller_benchmark/handlers/p2p_lookup.py +47 -0
  95. lmcache/tools/controller_benchmark/handlers/register.py +56 -0
  96. lmcache/tools/mp_status_viewer/__init__.py +1 -0
  97. lmcache/tools/mp_status_viewer/__main__.py +95 -0
  98. lmcache/usage_context.py +417 -0
  99. lmcache/utils.py +665 -0
  100. lmcache/v1/__init__.py +2 -0
  101. lmcache/v1/api_server/__init__.py +2 -0
  102. lmcache/v1/api_server/__main__.py +537 -0
  103. lmcache/v1/basic_check.py +112 -0
  104. lmcache/v1/cache_controller/__init__.py +9 -0
  105. lmcache/v1/cache_controller/commands/__init__.py +15 -0
  106. lmcache/v1/cache_controller/commands/base.py +35 -0
  107. lmcache/v1/cache_controller/commands/full_sync.py +49 -0
  108. lmcache/v1/cache_controller/config.py +176 -0
  109. lmcache/v1/cache_controller/controller_manager.py +535 -0
  110. lmcache/v1/cache_controller/controllers/__init__.py +11 -0
  111. lmcache/v1/cache_controller/controllers/full_sync_tracker.py +473 -0
  112. lmcache/v1/cache_controller/controllers/kv_controller.py +439 -0
  113. lmcache/v1/cache_controller/controllers/registration_controller.py +282 -0
  114. lmcache/v1/cache_controller/executor.py +463 -0
  115. lmcache/v1/cache_controller/frontend/static/css/style.css +201 -0
  116. lmcache/v1/cache_controller/frontend/static/img/logo.png +0 -0
  117. lmcache/v1/cache_controller/frontend/static/index.html +234 -0
  118. lmcache/v1/cache_controller/frontend/static/js/controller_app.js +660 -0
  119. lmcache/v1/cache_controller/full_sync_sender.py +475 -0
  120. lmcache/v1/cache_controller/locks.py +149 -0
  121. lmcache/v1/cache_controller/message.py +828 -0
  122. lmcache/v1/cache_controller/observability.py +208 -0
  123. lmcache/v1/cache_controller/utils.py +679 -0
  124. lmcache/v1/cache_controller/worker.py +665 -0
  125. lmcache/v1/cache_engine.py +2058 -0
  126. lmcache/v1/cache_interface.py +19 -0
  127. lmcache/v1/check/__init__.py +74 -0
  128. lmcache/v1/check/check_mode_gen.py +86 -0
  129. lmcache/v1/check/check_mode_test_l2_adapter.py +284 -0
  130. lmcache/v1/check/check_mode_test_remote.py +155 -0
  131. lmcache/v1/check/check_mode_test_storage_manager.py +142 -0
  132. lmcache/v1/check/utils.py +571 -0
  133. lmcache/v1/compute/__init__.py +2 -0
  134. lmcache/v1/compute/attention/__init__.py +0 -0
  135. lmcache/v1/compute/attention/abstract.py +39 -0
  136. lmcache/v1/compute/attention/flash_attn.py +129 -0
  137. lmcache/v1/compute/attention/flash_infer_sparse.py +284 -0
  138. lmcache/v1/compute/attention/metadata.py +85 -0
  139. lmcache/v1/compute/attention/utils.py +14 -0
  140. lmcache/v1/compute/blend/__init__.py +7 -0
  141. lmcache/v1/compute/blend/blender.py +168 -0
  142. lmcache/v1/compute/blend/metadata.py +34 -0
  143. lmcache/v1/compute/blend/utils.py +63 -0
  144. lmcache/v1/compute/models/__init__.py +0 -0
  145. lmcache/v1/compute/models/base.py +141 -0
  146. lmcache/v1/compute/models/llama.py +9 -0
  147. lmcache/v1/compute/models/qwen3.py +24 -0
  148. lmcache/v1/compute/models/utils.py +68 -0
  149. lmcache/v1/compute/positional_encoding.py +199 -0
  150. lmcache/v1/config.py +848 -0
  151. lmcache/v1/config_base.py +848 -0
  152. lmcache/v1/distributed/api.py +248 -0
  153. lmcache/v1/distributed/config.py +321 -0
  154. lmcache/v1/distributed/error.py +64 -0
  155. lmcache/v1/distributed/eviction.py +192 -0
  156. lmcache/v1/distributed/eviction_policy/__init__.py +21 -0
  157. lmcache/v1/distributed/eviction_policy/factory.py +27 -0
  158. lmcache/v1/distributed/eviction_policy/lru.py +244 -0
  159. lmcache/v1/distributed/eviction_policy/noop.py +50 -0
  160. lmcache/v1/distributed/internal_api.py +170 -0
  161. lmcache/v1/distributed/l1_manager.py +835 -0
  162. lmcache/v1/distributed/l2_adapters/__init__.py +67 -0
  163. lmcache/v1/distributed/l2_adapters/base.py +360 -0
  164. lmcache/v1/distributed/l2_adapters/config.py +385 -0
  165. lmcache/v1/distributed/l2_adapters/factory.py +205 -0
  166. lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +747 -0
  167. lmcache/v1/distributed/l2_adapters/fs_native_l2_adapter.py +167 -0
  168. lmcache/v1/distributed/l2_adapters/mock_l2_adapter.py +516 -0
  169. lmcache/v1/distributed/l2_adapters/mooncake_store_l2_adapter.py +135 -0
  170. lmcache/v1/distributed/l2_adapters/native_connector_l2_adapter.py +468 -0
  171. lmcache/v1/distributed/l2_adapters/native_plugin_l2_adapter.py +199 -0
  172. lmcache/v1/distributed/l2_adapters/nixl_store_dynamic_l2_adapter.py +831 -0
  173. lmcache/v1/distributed/l2_adapters/nixl_store_l2_adapter.py +983 -0
  174. lmcache/v1/distributed/l2_adapters/plugin_l2_adapter.py +210 -0
  175. lmcache/v1/distributed/l2_adapters/resp_l2_adapter.py +176 -0
  176. lmcache/v1/distributed/memory_manager.py +179 -0
  177. lmcache/v1/distributed/storage_controller.py +39 -0
  178. lmcache/v1/distributed/storage_controllers/__init__.py +43 -0
  179. lmcache/v1/distributed/storage_controllers/eviction_controller.py +242 -0
  180. lmcache/v1/distributed/storage_controllers/prefetch_controller.py +830 -0
  181. lmcache/v1/distributed/storage_controllers/prefetch_policy.py +193 -0
  182. lmcache/v1/distributed/storage_controllers/store_controller.py +452 -0
  183. lmcache/v1/distributed/storage_controllers/store_policy.py +213 -0
  184. lmcache/v1/distributed/storage_manager.py +532 -0
  185. lmcache/v1/event_manager.py +145 -0
  186. lmcache/v1/exceptions/__init__.py +16 -0
  187. lmcache/v1/gpu_connector/__init__.py +126 -0
  188. lmcache/v1/gpu_connector/gpu_connectors.py +1906 -0
  189. lmcache/v1/gpu_connector/gpu_ops.py +85 -0
  190. lmcache/v1/gpu_connector/hpu_connector.py +326 -0
  191. lmcache/v1/gpu_connector/mock_gpu_connector.py +67 -0
  192. lmcache/v1/gpu_connector/utils.py +890 -0
  193. lmcache/v1/gpu_connector/xpu_connectors.py +916 -0
  194. lmcache/v1/health_monitor/__init__.py +1 -0
  195. lmcache/v1/health_monitor/base.py +587 -0
  196. lmcache/v1/health_monitor/checks/__init__.py +1 -0
  197. lmcache/v1/health_monitor/checks/remote_backend_check.py +304 -0
  198. lmcache/v1/health_monitor/constants.py +36 -0
  199. lmcache/v1/internal_api_server/__init__.py +0 -0
  200. lmcache/v1/internal_api_server/api_registry.py +59 -0
  201. lmcache/v1/internal_api_server/api_server.py +120 -0
  202. lmcache/v1/internal_api_server/common/__init__.py +1 -0
  203. lmcache/v1/internal_api_server/common/env_api.py +22 -0
  204. lmcache/v1/internal_api_server/common/loglevel_api.py +57 -0
  205. lmcache/v1/internal_api_server/common/metrics_api.py +29 -0
  206. lmcache/v1/internal_api_server/common/periodic_thread_api.py +138 -0
  207. lmcache/v1/internal_api_server/common/run_script_api.py +73 -0
  208. lmcache/v1/internal_api_server/common/thread_api.py +63 -0
  209. lmcache/v1/internal_api_server/controller/__init__.py +1 -0
  210. lmcache/v1/internal_api_server/controller/key_stats_api.py +81 -0
  211. lmcache/v1/internal_api_server/controller/worker_info_api.py +136 -0
  212. lmcache/v1/internal_api_server/utils.py +43 -0
  213. lmcache/v1/internal_api_server/vllm/__init__.py +1 -0
  214. lmcache/v1/internal_api_server/vllm/backend_api.py +221 -0
  215. lmcache/v1/internal_api_server/vllm/bypass_api.py +204 -0
  216. lmcache/v1/internal_api_server/vllm/cache_api.py +895 -0
  217. lmcache/v1/internal_api_server/vllm/chunk_statistics_api.py +141 -0
  218. lmcache/v1/internal_api_server/vllm/conf_api.py +147 -0
  219. lmcache/v1/internal_api_server/vllm/freeze_api.py +172 -0
  220. lmcache/v1/internal_api_server/vllm/hot_cache_api.py +184 -0
  221. lmcache/v1/internal_api_server/vllm/inference_api.py +65 -0
  222. lmcache/v1/internal_api_server/vllm/load_fs_chunks_api.py +320 -0
  223. lmcache/v1/internal_api_server/vllm/lookup_api.py +145 -0
  224. lmcache/v1/internal_api_server/vllm/version_api.py +25 -0
  225. lmcache/v1/kv_layer_groups.py +267 -0
  226. lmcache/v1/lazy_memory_allocator.py +284 -0
  227. lmcache/v1/lookup_client/__init__.py +25 -0
  228. lmcache/v1/lookup_client/abstract_client.py +77 -0
  229. lmcache/v1/lookup_client/async_lookup_message.py +50 -0
  230. lmcache/v1/lookup_client/chunk_statistics_lookup_client.py +200 -0
  231. lmcache/v1/lookup_client/factory.py +251 -0
  232. lmcache/v1/lookup_client/hit_limit_lookup_client.py +86 -0
  233. lmcache/v1/lookup_client/lmcache_async_lookup_client.py +407 -0
  234. lmcache/v1/lookup_client/lmcache_lookup_client.py +285 -0
  235. lmcache/v1/lookup_client/lmcache_lookup_client_bypass.py +99 -0
  236. lmcache/v1/lookup_client/mooncake_lookup_client.py +87 -0
  237. lmcache/v1/lookup_client/record_strategies/__init__.py +77 -0
  238. lmcache/v1/lookup_client/record_strategies/base.py +327 -0
  239. lmcache/v1/lookup_client/record_strategies/file_hash.py +130 -0
  240. lmcache/v1/lookup_client/record_strategies/memory_bloom_filter.py +81 -0
  241. lmcache/v1/manager.py +539 -0
  242. lmcache/v1/memory_management.py +2619 -0
  243. lmcache/v1/metadata.py +114 -0
  244. lmcache/v1/mp_observability/AGENTS.override.md +21 -0
  245. lmcache/v1/mp_observability/README.md +204 -0
  246. lmcache/v1/mp_observability/config.py +340 -0
  247. lmcache/v1/mp_observability/event.py +100 -0
  248. lmcache/v1/mp_observability/event_bus.py +313 -0
  249. lmcache/v1/mp_observability/otel_init.py +145 -0
  250. lmcache/v1/mp_observability/subscribers/__init__.py +28 -0
  251. lmcache/v1/mp_observability/subscribers/logging/__init__.py +19 -0
  252. lmcache/v1/mp_observability/subscribers/logging/l1.py +56 -0
  253. lmcache/v1/mp_observability/subscribers/logging/l2.py +73 -0
  254. lmcache/v1/mp_observability/subscribers/logging/lookup_hash.py +209 -0
  255. lmcache/v1/mp_observability/subscribers/logging/mp_server.py +90 -0
  256. lmcache/v1/mp_observability/subscribers/logging/sm.py +59 -0
  257. lmcache/v1/mp_observability/subscribers/metrics/__init__.py +20 -0
  258. lmcache/v1/mp_observability/subscribers/metrics/l0_lifecycle.py +290 -0
  259. lmcache/v1/mp_observability/subscribers/metrics/l1.py +55 -0
  260. lmcache/v1/mp_observability/subscribers/metrics/l1_lifecycle.py +166 -0
  261. lmcache/v1/mp_observability/subscribers/metrics/l2.py +121 -0
  262. lmcache/v1/mp_observability/subscribers/metrics/sm.py +69 -0
  263. lmcache/v1/mp_observability/subscribers/tracing/__init__.py +12 -0
  264. lmcache/v1/mp_observability/subscribers/tracing/mp_server.py +333 -0
  265. lmcache/v1/mp_observability/subscribers/tracing/span_registry.py +148 -0
  266. lmcache/v1/mp_observability/trace/__init__.py +50 -0
  267. lmcache/v1/mp_observability/trace/codecs.py +255 -0
  268. lmcache/v1/mp_observability/trace/decorator.py +147 -0
  269. lmcache/v1/mp_observability/trace/format.py +132 -0
  270. lmcache/v1/mp_observability/trace/lifecycle.py +83 -0
  271. lmcache/v1/mp_observability/trace/reader.py +167 -0
  272. lmcache/v1/mp_observability/trace/recorder.py +300 -0
  273. lmcache/v1/multiprocess/__init__.py +0 -0
  274. lmcache/v1/multiprocess/affinity_pool.py +102 -0
  275. lmcache/v1/multiprocess/blend_server_v2.py +891 -0
  276. lmcache/v1/multiprocess/config.py +253 -0
  277. lmcache/v1/multiprocess/custom_types.py +281 -0
  278. lmcache/v1/multiprocess/futures.py +194 -0
  279. lmcache/v1/multiprocess/gpu_context.py +511 -0
  280. lmcache/v1/multiprocess/http_server.py +235 -0
  281. lmcache/v1/multiprocess/mp_runtime_plugin_launcher.py +130 -0
  282. lmcache/v1/multiprocess/mq.py +732 -0
  283. lmcache/v1/multiprocess/protocol.py +86 -0
  284. lmcache/v1/multiprocess/protocols/README.md +213 -0
  285. lmcache/v1/multiprocess/protocols/__init__.py +127 -0
  286. lmcache/v1/multiprocess/protocols/base.py +89 -0
  287. lmcache/v1/multiprocess/protocols/blend.py +109 -0
  288. lmcache/v1/multiprocess/protocols/blend_v2.py +57 -0
  289. lmcache/v1/multiprocess/protocols/controller.py +53 -0
  290. lmcache/v1/multiprocess/protocols/debug.py +34 -0
  291. lmcache/v1/multiprocess/protocols/engine.py +146 -0
  292. lmcache/v1/multiprocess/protocols/observability.py +39 -0
  293. lmcache/v1/multiprocess/server.py +1134 -0
  294. lmcache/v1/multiprocess/session.py +190 -0
  295. lmcache/v1/multiprocess/token_hasher.py +441 -0
  296. lmcache/v1/offload_server/__init__.py +17 -0
  297. lmcache/v1/offload_server/abstract_server.py +37 -0
  298. lmcache/v1/offload_server/message.py +30 -0
  299. lmcache/v1/offload_server/zmq_server.py +122 -0
  300. lmcache/v1/periodic_thread.py +579 -0
  301. lmcache/v1/pin_monitor.py +246 -0
  302. lmcache/v1/plugin/__init__.py +0 -0
  303. lmcache/v1/plugin/runtime_plugin_launcher.py +211 -0
  304. lmcache/v1/protocol.py +317 -0
  305. lmcache/v1/rpc/__init__.py +17 -0
  306. lmcache/v1/rpc/transport.py +105 -0
  307. lmcache/v1/rpc/zmq_transport.py +213 -0
  308. lmcache/v1/rpc_utils.py +165 -0
  309. lmcache/v1/server/__init__.py +2 -0
  310. lmcache/v1/server/__main__.py +170 -0
  311. lmcache/v1/server/storage_backend/__init__.py +21 -0
  312. lmcache/v1/server/storage_backend/abstract_backend.py +80 -0
  313. lmcache/v1/server/storage_backend/local_backend.py +75 -0
  314. lmcache/v1/server/utils.py +21 -0
  315. lmcache/v1/standalone/__init__.py +1 -0
  316. lmcache/v1/standalone/__main__.py +583 -0
  317. lmcache/v1/standalone/manager.py +80 -0
  318. lmcache/v1/standalone/standalone_service_factory.py +86 -0
  319. lmcache/v1/storage_backend/__init__.py +313 -0
  320. lmcache/v1/storage_backend/abstract_backend.py +445 -0
  321. lmcache/v1/storage_backend/audit_backend.py +233 -0
  322. lmcache/v1/storage_backend/batched_message_sender.py +222 -0
  323. lmcache/v1/storage_backend/cache_policy/__init__.py +45 -0
  324. lmcache/v1/storage_backend/cache_policy/base_policy.py +87 -0
  325. lmcache/v1/storage_backend/cache_policy/fifo.py +58 -0
  326. lmcache/v1/storage_backend/cache_policy/lfu.py +105 -0
  327. lmcache/v1/storage_backend/cache_policy/lru.py +81 -0
  328. lmcache/v1/storage_backend/cache_policy/mru.py +61 -0
  329. lmcache/v1/storage_backend/connector/__init__.py +443 -0
  330. lmcache/v1/storage_backend/connector/audit_adapter.py +77 -0
  331. lmcache/v1/storage_backend/connector/audit_connector.py +320 -0
  332. lmcache/v1/storage_backend/connector/base_connector.py +379 -0
  333. lmcache/v1/storage_backend/connector/blackhole_adapter.py +21 -0
  334. lmcache/v1/storage_backend/connector/blackhole_connector.py +37 -0
  335. lmcache/v1/storage_backend/connector/eic_adapter.py +31 -0
  336. lmcache/v1/storage_backend/connector/eic_connector.py +757 -0
  337. lmcache/v1/storage_backend/connector/external_adapter.py +79 -0
  338. lmcache/v1/storage_backend/connector/fs_adapter.py +51 -0
  339. lmcache/v1/storage_backend/connector/fs_connector.py +403 -0
  340. lmcache/v1/storage_backend/connector/infinistore_adapter.py +56 -0
  341. lmcache/v1/storage_backend/connector/infinistore_connector.py +177 -0
  342. lmcache/v1/storage_backend/connector/instrumented_connector.py +219 -0
  343. lmcache/v1/storage_backend/connector/lm_adapter.py +31 -0
  344. lmcache/v1/storage_backend/connector/lm_connector.py +176 -0
  345. lmcache/v1/storage_backend/connector/mock_adapter.py +57 -0
  346. lmcache/v1/storage_backend/connector/mock_connector.py +349 -0
  347. lmcache/v1/storage_backend/connector/mooncakestore_adapter.py +43 -0
  348. lmcache/v1/storage_backend/connector/mooncakestore_connector.py +614 -0
  349. lmcache/v1/storage_backend/connector/redis_adapter.py +181 -0
  350. lmcache/v1/storage_backend/connector/redis_connector.py +828 -0
  351. lmcache/v1/storage_backend/connector/s3_adapter.py +59 -0
  352. lmcache/v1/storage_backend/connector/s3_connector.py +699 -0
  353. lmcache/v1/storage_backend/connector/sagemaker_hyperpod_adapter.py +233 -0
  354. lmcache/v1/storage_backend/connector/sagemaker_hyperpod_connector.py +987 -0
  355. lmcache/v1/storage_backend/connector/valkey_adapter.py +114 -0
  356. lmcache/v1/storage_backend/connector/valkey_connector.py +627 -0
  357. lmcache/v1/storage_backend/gds_backend.py +1199 -0
  358. lmcache/v1/storage_backend/job_executor/__init__.py +0 -0
  359. lmcache/v1/storage_backend/job_executor/base_executor.py +34 -0
  360. lmcache/v1/storage_backend/job_executor/pq_executor.py +235 -0
  361. lmcache/v1/storage_backend/local_cpu_backend.py +810 -0
  362. lmcache/v1/storage_backend/local_disk_backend.py +656 -0
  363. lmcache/v1/storage_backend/maru_backend.py +734 -0
  364. lmcache/v1/storage_backend/naive_serde/__init__.py +50 -0
  365. lmcache/v1/storage_backend/naive_serde/cachegen_basics.py +133 -0
  366. lmcache/v1/storage_backend/naive_serde/cachegen_decoder.py +135 -0
  367. lmcache/v1/storage_backend/naive_serde/cachegen_encoder.py +83 -0
  368. lmcache/v1/storage_backend/naive_serde/kivi_serde.py +22 -0
  369. lmcache/v1/storage_backend/naive_serde/naive_serde.py +18 -0
  370. lmcache/v1/storage_backend/naive_serde/serde.py +37 -0
  371. lmcache/v1/storage_backend/native_clients/connector_client_base.py +165 -0
  372. lmcache/v1/storage_backend/native_clients/resp_client.py +35 -0
  373. lmcache/v1/storage_backend/nixl_storage_backend.py +1400 -0
  374. lmcache/v1/storage_backend/p2p_backend.py +788 -0
  375. lmcache/v1/storage_backend/path_sharder.py +117 -0
  376. lmcache/v1/storage_backend/pd_backend.py +646 -0
  377. lmcache/v1/storage_backend/plugins/dax_backend.py +1443 -0
  378. lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py +1361 -0
  379. lmcache/v1/storage_backend/remote_backend.py +624 -0
  380. lmcache/v1/storage_backend/resp_client.py +227 -0
  381. lmcache/v1/storage_backend/storage_backend_listener.py +19 -0
  382. lmcache/v1/storage_backend/storage_manager.py +1352 -0
  383. lmcache/v1/system_detection.py +110 -0
  384. lmcache/v1/token_database.py +551 -0
  385. lmcache/v1/transfer_channel/__init__.py +83 -0
  386. lmcache/v1/transfer_channel/abstract.py +285 -0
  387. lmcache/v1/transfer_channel/mock_memory_channel.py +156 -0
  388. lmcache/v1/transfer_channel/nixl_channel.py +639 -0
  389. lmcache/v1/transfer_channel/py_socket_channel.py +260 -0
  390. lmcache/v1/transfer_channel/transfer_utils.py +63 -0
  391. lmcache/v1/utils/__init__.py +1 -0
  392. lmcache/v1/utils/bloom_filter.py +109 -0
  393. lmcache/v1/utils/cache_utils.py +125 -0
  394. lmcache_cli-0.4.5.dev0.dist-info/METADATA +185 -0
  395. lmcache_cli-0.4.5.dev0.dist-info/RECORD +399 -0
  396. lmcache_cli-0.4.5.dev0.dist-info/WHEEL +5 -0
  397. lmcache_cli-0.4.5.dev0.dist-info/entry_points.txt +2 -0
  398. lmcache_cli-0.4.5.dev0.dist-info/licenses/LICENSE +201 -0
  399. lmcache_cli-0.4.5.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1906 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Standard
3
+ from typing import List, Optional, Tuple, Union
4
+ import abc
5
+
6
+ # Third Party
7
+ import torch
8
+
9
+ # First Party
10
+ from lmcache.logging import init_logger
11
+ from lmcache.utils import EngineType, _lmcache_nvtx_annotate
12
+ from lmcache.v1.compute.blend.utils import LMCBlenderBuilder
13
+ from lmcache.v1.gpu_connector.utils import (
14
+ LayoutHints,
15
+ assert_is_vllm_flash_attn_or_flash_infer,
16
+ assert_is_vllm_mla_or_flash_attn_or_flash_infer,
17
+ discover_gpu_kv_format,
18
+ ensure_contiguous_kv_caches,
19
+ get_block_size,
20
+ get_elements_per_layer,
21
+ get_head_size,
22
+ get_num_blocks,
23
+ get_page_buffer_size,
24
+ get_tokens_per_layer,
25
+ permute_kv_caches_to_contiguous,
26
+ )
27
+ from lmcache.v1.memory_management import GPUMemoryAllocator # noqa: E501
28
+ from lmcache.v1.memory_management import MemoryFormat, MemoryObj
29
+ from lmcache.v1.metadata import LMCacheMetadata
30
+ import lmcache.c_ops as lmc_ops
31
+
32
+ logger = init_logger(__name__)
33
+
34
+
35
+ class GPUConnectorInterface(metaclass=abc.ABCMeta):
36
+ @abc.abstractmethod
37
+ def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
38
+ # FIXME (Yihua): We shouldn't put start and end here since
39
+ # it's not the responsibility of the GPUConnector to know
40
+ # the token-sequence-related information.
41
+ """Store the data in the memory object into a GPU buffer.
42
+ Sub-classes should define the format of the kwargs.
43
+
44
+ :param MemoryObj memory_obj: The memory object to be copied into GPU.
45
+ :param int start: The starting index of the data in the corresponding
46
+ token sequence.
47
+ :param int end: The ending index of the data in the corresponding
48
+ token sequence.
49
+ """
50
+ raise NotImplementedError
51
+
52
+ @abc.abstractmethod
53
+ def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
54
+ # FIXME (Yihua): We shouldn't put start and end here since
55
+ # it's not the responsibility of the GPUConnector to know
56
+ # the token-sequence-related information.
57
+ """Load the data from a GPU buffer into the memory object.
58
+ Sub-classes should define the format of the kwargs.
59
+
60
+ :param MemoryObj memory_obj: The memory object to store the data from
61
+ GPU.
62
+ :param int start: The starting index of the data in the corresponding
63
+ token sequence.
64
+ :param int end: The ending index of the data in the corresponding
65
+ token sequence.
66
+ """
67
+ raise NotImplementedError
68
+
69
+ @abc.abstractmethod
70
+ def batched_from_gpu(
71
+ self,
72
+ memory_objs: Union[List[List[MemoryObj]], List[MemoryObj]],
73
+ starts: List[int],
74
+ ends: List[int],
75
+ **kwargs,
76
+ ):
77
+ """
78
+ Batched load the data from a GPU memory into the memory objects.
79
+ Sub-classes should define the format of the kwargs.
80
+
81
+ :param Union[List[List[MemoryObj]], List[MemoryObj]] memory_obj:
82
+ The memory objects to store the data from GPU.
83
+ :param List[int] starts: The starting indices of the data in the corresponding
84
+ token sequence.
85
+ :param List[int] ends: The ending indices of the data in the corresponding
86
+ token sequence.
87
+ """
88
+ raise NotImplementedError
89
+
90
+ @abc.abstractmethod
91
+ def batched_to_gpu(
92
+ self,
93
+ memory_objs: Union[
94
+ List[List[MemoryObj]], List[MemoryObj], List[int], None
95
+ ] = None,
96
+ starts: Optional[List[int]] = None,
97
+ ends: Optional[List[int]] = None,
98
+ **kwargs,
99
+ ):
100
+ """
101
+ Batched store the data from the memory objects to GPU kv cache.
102
+ Sub-classes should define the format of the kwargs.
103
+
104
+ For non-layerwise connectors:
105
+ :param Union[List[List[MemoryObj]], List[MemoryObj]] memory_obj:
106
+ The memory objects to store the data to GPU.
107
+ :param List[int] starts: The starting indices of the data in the corresponding
108
+ token sequence.
109
+ :param List[int] ends: The ending indices of the data in the corresponding
110
+ token sequence.
111
+
112
+ For layerwise connectors (generator pattern):
113
+ :param List[int] memory_objs: Actually the starts list
114
+ (positional compatibility)
115
+ :param List[int] starts: Actually the ends list
116
+ (positional compatibility)
117
+ Note: Layerwise connectors receive memory objects
118
+ via generator.send()
119
+ """
120
+ raise NotImplementedError
121
+
122
+ @abc.abstractmethod
123
+ def get_shape(self, num_tokens: int) -> torch.Size:
124
+ """Get the shape of the data given the number of tokens."""
125
+ raise NotImplementedError
126
+
127
+ def initialize_kvcaches_ptr(self, **kwargs):
128
+ """Initialize the kvcaches pointers if not already initialized."""
129
+ if "kvcaches" in kwargs:
130
+ self.kvcaches = kwargs["kvcaches"]
131
+ # Ensure contiguity on every call. HND tensors from vLLM have a
132
+ # non-contiguous logical view (NHD) that must be permuted back to
133
+ # the physical (HND) shape for correct kernel indexing.
134
+ # permute_kv_caches_to_contiguous is a no-op when already contiguous.
135
+ self.kvcaches = permute_kv_caches_to_contiguous(self.kvcaches)
136
+
137
+
138
+ class VLLMPagedMemGPUConnectorV2(GPUConnectorInterface):
139
+ """
140
+ The GPU KV cache should be a nested tuple of K and V tensors.
141
+ More specifically, we have:
142
+ - GPUTensor = Tuple[KVLayer, ...]
143
+ - KVLayer = Tuple[Tensor, Tensor]
144
+ - Tensor: [num_blocks, block_size, num_heads, head_size]
145
+
146
+ It will produce / consume memory object with KV_2LTD format
147
+ """
148
+
149
+ def __init__(
150
+ self,
151
+ hidden_dim_size: int,
152
+ num_layers: int,
153
+ use_gpu: bool = False,
154
+ **kwargs,
155
+ ):
156
+ """
157
+ If use_gpu is true, it will create a gpu intermediate buffer. In this
158
+ case, it requires the following kwargs:
159
+ - chunk_size: The MAX size of the chunk to be copied to GPU.
160
+ - dtype: The data type of the intermediate buffer.
161
+ """
162
+ self.hidden_dim_size = hidden_dim_size
163
+ self.num_layers = num_layers
164
+ self.kv_cache_pointers = torch.empty(
165
+ num_layers, dtype=torch.int64, device="cpu"
166
+ )
167
+ # Not sure we need a dict here. Maybe a single GPU connector always
168
+ # works with a single device?
169
+ self.kv_cache_pointers_on_gpu: dict[int, torch.Tensor] = {}
170
+
171
+ self.kvcaches: Optional[List[torch.Tensor]] = None
172
+
173
+ self.gpu_buffer: Optional[torch.Tensor] = None
174
+ self.use_mla = "use_mla" in kwargs and kwargs["use_mla"]
175
+ self.layout_hints: LayoutHints = (
176
+ kwargs.get( # type: ignore[assignment]
177
+ "layout_hints"
178
+ )
179
+ or {}
180
+ )
181
+ if use_gpu:
182
+ assert "chunk_size" in kwargs, (
183
+ "chunk_size should be provided to create a GPU buffer."
184
+ )
185
+ assert "dtype" in kwargs, "dtype should be provided to create a GPU buffer."
186
+ assert "device" in kwargs, (
187
+ "device should be provided to create a GPU buffer."
188
+ )
189
+ shape = self.get_shape(kwargs["chunk_size"])
190
+ self.gpu_buffer = torch.empty(
191
+ shape, dtype=kwargs["dtype"], device=kwargs["device"]
192
+ )
193
+
194
+ self.store_stream = torch.cuda.Stream()
195
+ self.load_stream = torch.cuda.Stream()
196
+
197
+ @classmethod
198
+ def from_metadata(
199
+ cls,
200
+ metadata: LMCacheMetadata,
201
+ use_gpu: bool = False,
202
+ device: Optional[torch.device] = None,
203
+ layout_hints: Optional[LayoutHints] = None,
204
+ ) -> "VLLMPagedMemGPUConnectorV2":
205
+ """Create a connector from LMCacheMetadata.
206
+
207
+ Args:
208
+ metadata: The LMCache engine metadata containing model configuration.
209
+ use_gpu: Whether to use GPU intermediate buffer.
210
+ device: The device to use for the connector.
211
+ layout_hints: Optional hints about KV cache layout from the
212
+ serving engine.
213
+
214
+ Returns:
215
+ A new instance of VLLMPagedMemGPUConnectorV2.
216
+ """
217
+ # Extract parameters from metadata
218
+ # kv_shape: (num_layer, 2 or 1, chunk_size, num_kv_head, head_size)
219
+ num_layers = metadata.kv_shape[0]
220
+ chunk_size = metadata.kv_shape[2]
221
+ num_kv_head = metadata.kv_shape[3]
222
+ head_size = metadata.kv_shape[4]
223
+ hidden_dim_size = num_kv_head * head_size
224
+
225
+ return cls(
226
+ hidden_dim_size=hidden_dim_size,
227
+ num_layers=num_layers,
228
+ use_gpu=use_gpu,
229
+ chunk_size=chunk_size,
230
+ dtype=metadata.kv_dtype,
231
+ device=device,
232
+ use_mla=metadata.use_mla,
233
+ layout_hints=layout_hints,
234
+ )
235
+
236
+ def _initialize_pointers(self, kv_caches: List[torch.Tensor]) -> torch.Tensor:
237
+ self.device = kv_caches[0].device
238
+ assert self.device.type == "cuda", "The device should be CUDA."
239
+ idx = self.device.index
240
+ if idx in self.kv_cache_pointers_on_gpu:
241
+ return self.kv_cache_pointers_on_gpu[idx]
242
+
243
+ # contiguous before pointer capture or format discovery
244
+ kv_caches = ensure_contiguous_kv_caches(
245
+ kv_caches, kv_layout=self.layout_hints.get("kv_layout")
246
+ )
247
+
248
+ self.kv_cache_pointers.numpy()[:] = [t.data_ptr() for t in kv_caches]
249
+ self.kv_cache_pointers_on_gpu[idx] = torch.empty(
250
+ self.num_layers, dtype=torch.int64, device=self.device
251
+ )
252
+ self.kv_cache_pointers_on_gpu[idx].copy_(self.kv_cache_pointers)
253
+
254
+ self.gpu_kv_format = discover_gpu_kv_format(
255
+ kv_caches, EngineType.VLLM, layout_hints=self.layout_hints
256
+ )
257
+ self.num_blocks = get_num_blocks(kv_caches, self.gpu_kv_format)
258
+ self.block_size = get_block_size(kv_caches, self.gpu_kv_format)
259
+ self.page_buffer_size = self.num_blocks * self.block_size
260
+ self.head_size = get_head_size(kv_caches, self.gpu_kv_format)
261
+
262
+ return self.kv_cache_pointers_on_gpu[idx]
263
+
264
+ @_lmcache_nvtx_annotate
265
+ def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
266
+ """Expect a kwarg 'kvcaches' which is a nested tuple of K and V tensors.
267
+ The kvcaches should correspond to the "WHOLE token sequence".
268
+
269
+ Note:
270
+ 1. This function expects the 'slot_mapping' is a "full slot mapping"
271
+ where it's length is the same as the whole token sequence.
272
+ 2. In the case that there is prefix caching, slot_mapping will starts
273
+ with -1s until the end of the matched prefix. The start and end
274
+ should NEVER overlap with the prefix caching (which means the
275
+ underlying CUDA kernel will never see -1 in slot_mapping)
276
+
277
+
278
+ :raises ValueError: If 'kvcaches' is not provided in kwargs.
279
+ :raises AssertionError: If the memory object does not have a tensor.
280
+ :raises ValueError: If 'slot_mapping' is not provided in kwargs.
281
+ """
282
+ assert memory_obj.tensor is not None
283
+
284
+ self.initialize_kvcaches_ptr(**kwargs)
285
+
286
+ assert self.kvcaches is not None, (
287
+ "kvcaches should be provided in kwargs or initialized beforehand."
288
+ )
289
+
290
+ if self.use_mla:
291
+ if memory_obj.metadata.fmt != MemoryFormat.KV_MLA_FMT:
292
+ raise ValueError(
293
+ "The memory object should be in KV_MLA_FMT format in"
294
+ " order to be processed by VLLMPagedMemGPUConnector"
295
+ )
296
+ else:
297
+ if memory_obj.metadata.fmt != MemoryFormat.KV_2LTD:
298
+ raise ValueError(
299
+ "The memory object should be in KV_2LTD format in"
300
+ " order to be processed by VLLMPagedMemGPUConnector"
301
+ )
302
+
303
+ if "slot_mapping" not in kwargs:
304
+ raise ValueError("'slot_mapping' should be provided in kwargs.")
305
+
306
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
307
+
308
+ kv_cache_pointers = self._initialize_pointers(self.kvcaches)
309
+
310
+ # avoid read/write stream race condition for shared block
311
+ # this will only be potentially non-zero for the first
312
+ # block lmcache is transferring back
313
+ vllm_cached = kwargs.get("vllm_cached_tokens", 0)
314
+ skip_prefix_n_tokens = min(end - start, max(0, vllm_cached - start))
315
+
316
+ lmc_ops.multi_layer_kv_transfer(
317
+ memory_obj.tensor,
318
+ kv_cache_pointers,
319
+ slot_mapping[start:end],
320
+ self.device,
321
+ self.page_buffer_size,
322
+ lmc_ops.TransferDirection.H2D,
323
+ self.gpu_kv_format,
324
+ block_size=self.block_size,
325
+ head_size=self.head_size,
326
+ skip_prefix_n_tokens=skip_prefix_n_tokens,
327
+ )
328
+
329
+ @_lmcache_nvtx_annotate
330
+ def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
331
+ """Expect a kwarg 'kvcaches' which is a nested tuple of K and V tensors.
332
+ The kvcaches should correspond to the "WHOLE token sequence".
333
+
334
+ Will set the memory_obj.metadata.fmt to MemoryFormat.KV_2LTD.
335
+
336
+ Note:
337
+ 1. This function expects the 'slot_mapping' is a "full slot mapping"
338
+ where it's length is the same as the whole token sequence.
339
+ 2. In the case that there is prefix caching, slot_mapping will starts
340
+ with -1s until the end of the matched prefix. The start and end
341
+ should NEVER overlap with the prefix caching (which means the
342
+ underlying CUDA kernel will never see -1 in slot_mapping)
343
+
344
+ :raises ValueError: If 'kvcaches' is not provided in kwargs,
345
+ :raises AssertionError: If the memory object does not have a tensor.
346
+ :raises ValueError: If 'slot_mapping' is not provided in kwargs.
347
+ """
348
+ assert memory_obj.tensor is not None
349
+
350
+ self.initialize_kvcaches_ptr(**kwargs)
351
+ assert self.kvcaches is not None, (
352
+ "kvcaches should be provided in kwargs or initialized beforehand."
353
+ )
354
+
355
+ if "slot_mapping" not in kwargs:
356
+ raise ValueError("'slot_mapping' should be provided in kwargs.")
357
+
358
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
359
+
360
+ kv_cache_pointers = self._initialize_pointers(self.kvcaches)
361
+
362
+ with torch.cuda.stream(self.store_stream):
363
+ if self.gpu_buffer is None or end - start != self.gpu_buffer.shape[2]:
364
+ lmc_ops.multi_layer_kv_transfer(
365
+ memory_obj.tensor,
366
+ kv_cache_pointers,
367
+ slot_mapping[start:end],
368
+ self.kvcaches[0].device,
369
+ self.page_buffer_size,
370
+ lmc_ops.TransferDirection.D2H,
371
+ self.gpu_kv_format,
372
+ block_size=self.block_size,
373
+ head_size=self.head_size,
374
+ )
375
+ else:
376
+ # kvcaches -> gpu_buffer -> memobj
377
+ assert self.gpu_buffer.device == self.kvcaches[0].device
378
+ tmp_gpu_buffer = self.gpu_buffer[:, :, : end - start, :]
379
+ lmc_ops.multi_layer_kv_transfer(
380
+ tmp_gpu_buffer,
381
+ kv_cache_pointers,
382
+ slot_mapping[start:end],
383
+ self.kvcaches[0].device,
384
+ self.page_buffer_size,
385
+ lmc_ops.TransferDirection.D2H,
386
+ self.gpu_kv_format,
387
+ block_size=self.block_size,
388
+ head_size=self.head_size,
389
+ )
390
+ memory_obj.tensor.copy_(tmp_gpu_buffer, non_blocking=True)
391
+
392
+ if not memory_obj.tensor.is_cuda:
393
+ # Force a synchronize if the target buffer is NOT CUDA device
394
+ # NOTE: for better performance, we may not want to sync for every
395
+ # memory object
396
+ self.store_stream.synchronize()
397
+
398
+ if self.use_mla:
399
+ memory_obj.metadata.fmt = MemoryFormat.KV_MLA_FMT
400
+
401
+ # TODO(Jiayi): need to optimize to enable real batching
402
+ def batched_to_gpu(self, memory_objs, starts, ends, **kwargs):
403
+ with torch.cuda.stream(self.load_stream):
404
+ for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
405
+ self.to_gpu(memory_obj, start, end, **kwargs)
406
+ self.load_stream.synchronize()
407
+
408
+ # TODO(Jiayi): need to optimize to enable real batching
409
+ def batched_from_gpu(self, memory_objs, starts, ends, **kwargs):
410
+ for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
411
+ self.from_gpu(memory_obj, start, end, **kwargs)
412
+
413
+ def get_shape(self, num_tokens: int) -> torch.Size:
414
+ kv_size = 1 if self.use_mla else 2
415
+ return torch.Size([kv_size, self.num_layers, num_tokens, self.hidden_dim_size])
416
+
417
+
418
+ class VLLMPagedMemGPUConnectorV3(GPUConnectorInterface):
419
+ def __init__(
420
+ self,
421
+ metadata: LMCacheMetadata,
422
+ device: torch.device,
423
+ use_gpu: bool = False,
424
+ layout_hints: Optional[LayoutHints] = None,
425
+ ):
426
+ assert device.type == "cuda", "The device should be CUDA."
427
+ self.metadata = metadata
428
+ self.device = device
429
+ self.use_mla = metadata.use_mla
430
+ self.chunk_size = metadata.chunk_size
431
+ self.use_gpu = use_gpu
432
+ self.layout_hints: LayoutHints = layout_hints or {}
433
+ self.kvcaches: Optional[List[torch.Tensor]] = None
434
+
435
+ self.init = False
436
+ self.group_kv_cache_pointers_on_gpu: Optional[list[torch.Tensor]] = None
437
+ self.group_tmp_buffer: Optional[list[torch.Tensor]] = None
438
+
439
+ self.store_stream = torch.cuda.Stream()
440
+ self.load_stream = torch.cuda.Stream()
441
+
442
+ @classmethod
443
+ def from_metadata(
444
+ cls,
445
+ metadata: LMCacheMetadata,
446
+ use_gpu: bool = False,
447
+ device: Optional[torch.device] = None,
448
+ layout_hints: Optional[LayoutHints] = None,
449
+ ) -> "VLLMPagedMemGPUConnectorV3":
450
+ assert device is not None
451
+ return cls(metadata, device, use_gpu, layout_hints=layout_hints)
452
+
453
+ def _initialize_kv_cache_pointers(self):
454
+ if self.init:
455
+ return
456
+ assert self.metadata.kv_layer_groups_manager.kv_layer_groups
457
+
458
+ # permute to contiguous before capturing pointers or doing format discovery
459
+ self.kvcaches = ensure_contiguous_kv_caches(
460
+ self.kvcaches, kv_layout=self.layout_hints.get("kv_layout")
461
+ )
462
+
463
+ if self.use_gpu:
464
+ # init tmp buffer
465
+ tmp_buf_shapes = self.metadata.get_shapes(self.chunk_size)
466
+ tmp_buf_dtypes = self.metadata.get_dtypes()
467
+ assert len(tmp_buf_shapes) == len(tmp_buf_dtypes)
468
+ self.group_tmp_buffer = [
469
+ torch.empty(tmp_buf_shape, dtype=tmp_buf_dtype, device=self.device)
470
+ for tmp_buf_shape, tmp_buf_dtype in zip(
471
+ tmp_buf_shapes, tmp_buf_dtypes, strict=True
472
+ )
473
+ ]
474
+ self.group_kv_cache_pointers_on_gpu = []
475
+ for group in self.metadata.kv_layer_groups_manager.kv_layer_groups:
476
+ # init kv cache pointers
477
+ num_layers = group.num_layers
478
+ kv_cache_pointers = torch.empty(num_layers, dtype=torch.int64, device="cpu")
479
+ kv_cache_pointers.numpy()[:] = [
480
+ t.data_ptr()
481
+ for i, t in enumerate(self.kvcaches)
482
+ if i in group.layer_indices
483
+ ]
484
+ kv_cache_pointers_on_gpu = torch.empty(
485
+ num_layers, dtype=torch.int64, device=self.device
486
+ )
487
+ kv_cache_pointers_on_gpu.copy_(kv_cache_pointers)
488
+ self.group_kv_cache_pointers_on_gpu.append(kv_cache_pointers_on_gpu)
489
+
490
+ self.gpu_kv_format = discover_gpu_kv_format(
491
+ self.kvcaches, EngineType.VLLM, layout_hints=self.layout_hints
492
+ )
493
+ self.num_blocks = get_num_blocks(self.kvcaches, self.gpu_kv_format)
494
+ self.block_size = get_block_size(self.kvcaches, self.gpu_kv_format)
495
+ self.page_buffer_size = self.num_blocks * self.block_size
496
+ self.head_size = get_head_size(self.kvcaches, self.gpu_kv_format)
497
+
498
+ self.init = True
499
+ logger.info("init kv cache pointers success in VLLMPagedMemGPUConnectorV3")
500
+
501
+ @_lmcache_nvtx_annotate
502
+ def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
503
+ assert memory_obj.raw_tensor is not None
504
+ assert "slot_mapping" in kwargs
505
+ if self.use_mla:
506
+ assert memory_obj.metadata.fmt == MemoryFormat.KV_MLA_FMT
507
+ else:
508
+ assert memory_obj.metadata.fmt == MemoryFormat.KV_2LTD
509
+
510
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
511
+ self.initialize_kvcaches_ptr(**kwargs)
512
+ assert self.kvcaches is not None
513
+ assert self.kvcaches[0].device == self.device
514
+ self._initialize_kv_cache_pointers()
515
+ assert self.group_kv_cache_pointers_on_gpu is not None
516
+
517
+ # avoid read/write stream race condition for shared block
518
+ # this will only be potentially non-zero for the first
519
+ # block lmcache is transferring back
520
+ vllm_cached = kwargs.get("vllm_cached_tokens", 0)
521
+ skip_prefix_n_tokens = min(end - start, max(0, vllm_cached - start))
522
+
523
+ for i, kv_cache_pointer in enumerate(self.group_kv_cache_pointers_on_gpu):
524
+ memory_obj_tensor = memory_obj.get_tensor(i)
525
+ assert memory_obj_tensor is not None
526
+ lmc_ops.multi_layer_kv_transfer(
527
+ memory_obj_tensor,
528
+ kv_cache_pointer,
529
+ slot_mapping[start:end],
530
+ self.device,
531
+ self.page_buffer_size,
532
+ lmc_ops.TransferDirection.H2D,
533
+ self.gpu_kv_format,
534
+ block_size=self.block_size,
535
+ head_size=self.head_size,
536
+ skip_prefix_n_tokens=skip_prefix_n_tokens,
537
+ )
538
+
539
+ @_lmcache_nvtx_annotate
540
+ def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
541
+ assert memory_obj.raw_tensor is not None
542
+ assert "slot_mapping" in kwargs
543
+
544
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
545
+ self.initialize_kvcaches_ptr(**kwargs)
546
+ assert self.kvcaches is not None
547
+ assert self.kvcaches[0].device == self.device
548
+ self._initialize_kv_cache_pointers()
549
+ assert self.group_kv_cache_pointers_on_gpu is not None
550
+ with torch.cuda.stream(self.store_stream):
551
+ if not self.use_gpu or end - start != self.chunk_size:
552
+ for i, kv_cache_pointer in enumerate(
553
+ self.group_kv_cache_pointers_on_gpu
554
+ ):
555
+ memory_obj_tensor = memory_obj.get_tensor(i)
556
+ assert memory_obj_tensor is not None
557
+ lmc_ops.multi_layer_kv_transfer(
558
+ memory_obj_tensor,
559
+ kv_cache_pointer,
560
+ slot_mapping[start:end],
561
+ self.device,
562
+ self.page_buffer_size,
563
+ lmc_ops.TransferDirection.D2H,
564
+ self.gpu_kv_format,
565
+ block_size=self.block_size,
566
+ head_size=self.head_size,
567
+ )
568
+ else:
569
+ # kvcaches -> gpu_buffer -> memobj
570
+ assert self.group_tmp_buffer is not None
571
+ for i, kv_cache_pointer in enumerate(
572
+ self.group_kv_cache_pointers_on_gpu
573
+ ):
574
+ tmp_gpu_buffer = self.group_tmp_buffer[i][:, :, : end - start, :]
575
+ lmc_ops.multi_layer_kv_transfer(
576
+ tmp_gpu_buffer,
577
+ kv_cache_pointer,
578
+ slot_mapping[start:end],
579
+ self.device,
580
+ self.page_buffer_size,
581
+ lmc_ops.TransferDirection.D2H,
582
+ self.gpu_kv_format,
583
+ block_size=self.block_size,
584
+ head_size=self.head_size,
585
+ )
586
+ memory_obj_tensor = memory_obj.get_tensor(i)
587
+ assert memory_obj_tensor is not None
588
+ memory_obj_tensor.copy_(tmp_gpu_buffer, non_blocking=True)
589
+
590
+ if not memory_obj.raw_tensor.is_cuda:
591
+ # Force a synchronize if the target buffer is NOT CUDA device
592
+ # NOTE: for better performance, we may not want to sync for every
593
+ # memory object
594
+ self.store_stream.synchronize()
595
+
596
+ if self.use_mla:
597
+ memory_obj.metadata.fmt = MemoryFormat.KV_MLA_FMT
598
+
599
+ def batched_to_gpu(self, memory_objs, starts, ends, **kwargs):
600
+ with torch.cuda.stream(self.load_stream):
601
+ for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
602
+ self.to_gpu(memory_obj, start, end, **kwargs)
603
+ self.load_stream.synchronize()
604
+
605
+ def batched_from_gpu(self, memory_objs, starts, ends, **kwargs):
606
+ for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
607
+ self.from_gpu(memory_obj, start, end, **kwargs)
608
+
609
+ def get_shape(self, num_tokens: int) -> torch.Size:
610
+ raise NotImplementedError
611
+
612
+
613
+ class VLLMBufferLayerwiseGPUConnector(GPUConnectorInterface):
614
+ def __init__(
615
+ self,
616
+ hidden_dim_size: int,
617
+ num_layers: int,
618
+ use_gpu: bool = False,
619
+ use_double_buffer: bool = True,
620
+ **kwargs,
621
+ ):
622
+ self.hidden_dim_size = hidden_dim_size
623
+ self.num_layers = num_layers
624
+
625
+ self.kvcaches: Optional[List[torch.Tensor]] = None
626
+ self.layout_hints: LayoutHints = (
627
+ kwargs.get( # type: ignore[assignment]
628
+ "layout_hints"
629
+ )
630
+ or {}
631
+ )
632
+
633
+ # TODO(Jiayi): remove this hardcode
634
+ self.cache_positions = True
635
+
636
+ self.fused_rotary_emb = None
637
+
638
+ assert use_gpu, "use_gpu must be true in VLLMBufferLayerwiseGPUConnector"
639
+ assert "dtype" in kwargs, "dtype should be provided to create a GPU buffer."
640
+ assert "device" in kwargs, "device should be provided to create a GPU buffer."
641
+
642
+ self.dtype = kwargs["dtype"]
643
+ self.device = kwargs["device"]
644
+
645
+ self.load_stream = torch.cuda.Stream()
646
+ self.store_stream = torch.cuda.Stream()
647
+
648
+ self.buffer_mapping: dict[int, MemoryObj] = {}
649
+
650
+ # track gap positions between blended chunks
651
+ self.current_gap_positions = None
652
+
653
+ self.use_gpu = use_gpu
654
+ self.gpu_buffer_allocator = None
655
+ self.element_size = torch.tensor([], dtype=self.dtype).element_size()
656
+
657
+ @classmethod
658
+ def from_metadata(
659
+ cls,
660
+ metadata: LMCacheMetadata,
661
+ use_gpu: bool = False,
662
+ device: Optional[torch.device] = None,
663
+ layout_hints: Optional[LayoutHints] = None,
664
+ ) -> "VLLMBufferLayerwiseGPUConnector":
665
+ """Create a connector from LMCacheMetadata.
666
+
667
+ Args:
668
+ metadata: The LMCache engine metadata containing model configuration.
669
+ use_gpu: Whether to use GPU intermediate buffer.
670
+ device: The device to use for the connector.
671
+ layout_hints: Optional hints about KV cache layout from the
672
+ serving engine.
673
+
674
+ Returns:
675
+ A new instance of VLLMBufferLayerwiseGPUConnector.
676
+ """
677
+ # Extract parameters from metadata
678
+ # kv_shape: (num_layer, 2 or 1, chunk_size, num_kv_head, head_size)
679
+ num_layers = metadata.kv_shape[0]
680
+ num_kv_head = metadata.kv_shape[3]
681
+ head_size = metadata.kv_shape[4]
682
+ hidden_dim_size = num_kv_head * head_size
683
+
684
+ return cls(
685
+ hidden_dim_size=hidden_dim_size,
686
+ num_layers=num_layers,
687
+ use_gpu=use_gpu,
688
+ dtype=metadata.kv_dtype,
689
+ device=device,
690
+ layout_hints=layout_hints,
691
+ )
692
+
693
+ def _lazy_initialize_buffer(self, kv_caches):
694
+ """
695
+ Lazily initialize the GPU buffer allocator if it is not initialized yet.
696
+ Currently, we use the `kv_caches` (kv cache pointer) to determine
697
+ the gpu buffer size in gpu connector.
698
+ Also, the first request might be a bit slower due to buffer creation.
699
+ """
700
+ if self.use_gpu and self.gpu_buffer_allocator is None:
701
+ logger.info("Lazily initializing GPU buffer.")
702
+ # NOTE (Jiayi): We use the first layer to determine the gpu buffer size.
703
+ # NOTE (Jiayi): Using the exact number of tokens in the first layer
704
+ # is okay since fragmentation shouldn't exist in the `gpu_buffer_allocator`
705
+ # in layerwise mode.
706
+
707
+ kv_caches = ensure_contiguous_kv_caches(
708
+ kv_caches, kv_layout=self.layout_hints.get("kv_layout")
709
+ )
710
+ self.kvcaches = kv_caches
711
+ self.gpu_kv_format = discover_gpu_kv_format(
712
+ kv_caches, EngineType.VLLM, layout_hints=self.layout_hints
713
+ )
714
+ assert_is_vllm_flash_attn_or_flash_infer(self.gpu_kv_format)
715
+ self.tokens_per_layer = get_tokens_per_layer(kv_caches, self.gpu_kv_format)
716
+ self.elements_per_layer = get_elements_per_layer(
717
+ kv_caches, self.gpu_kv_format
718
+ )
719
+ logger.info(
720
+ f"Lazily initializing GPU buffer (max tokens={self.tokens_per_layer})."
721
+ )
722
+ gpu_buffer_size = self.elements_per_layer * self.element_size
723
+ self.gpu_buffer_allocator = GPUMemoryAllocator(
724
+ gpu_buffer_size, device=self.device
725
+ )
726
+
727
+ def get_kv(self, layer_id: int) -> Tuple[torch.Tensor, torch.Tensor]:
728
+ """
729
+ Get the KV cache for the given layer ID.
730
+ This function is used to get the KV cache from the GPU buffer.
731
+ """
732
+ if layer_id not in self.buffer_mapping:
733
+ raise ValueError(f"Layer {layer_id} is not loaded into GPU buffer.")
734
+
735
+ gpu_buffer = self.buffer_mapping[layer_id].tensor
736
+ assert gpu_buffer is not None
737
+ return gpu_buffer[0], gpu_buffer[1]
738
+
739
+ def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
740
+ """ """
741
+
742
+ raise NotImplementedError
743
+
744
+ def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
745
+ """ """
746
+
747
+ raise NotImplementedError
748
+
749
+ @_lmcache_nvtx_annotate
750
+ def batched_to_gpu(self, starts: List[int], ends: List[int], **kwargs):
751
+ """
752
+ This function is a generator that moves the KV cache from the memory
753
+ objects to buffer GPU memory. In each iteration i, it (1) loads the KV
754
+ cache of layer i from CPU -> GPU buffer, (2) recovers the positional
755
+ encoding of the layer i-1's KV cache in the GPU buffer, and (3)
756
+ moves the KV cache of layer i-2 from GPU buffer to paged GPU memory.
757
+ In total, this the generator will yield num_layers + 2 times.
758
+
759
+ :param starts: The starting indices of the KV cache in the corresponding
760
+ token sequence.
761
+
762
+ :param ends: The ending indices of the KV cache in the corresponding
763
+ token sequence.
764
+ """
765
+
766
+ self.initialize_kvcaches_ptr(**kwargs)
767
+ assert self.kvcaches is not None, (
768
+ "kvcaches should be provided in kwargs or initialized beforehand."
769
+ )
770
+
771
+ if "slot_mapping" not in kwargs:
772
+ raise ValueError("'slot_mapping' should be provided in kwargs.")
773
+
774
+ if self.fused_rotary_emb is None and self.cache_positions:
775
+ # TODO(Jiayi): Make this more elegant
776
+ # First Party
777
+ from lmcache.integration.vllm.utils import ENGINE_NAME
778
+
779
+ self.lmc_model = LMCBlenderBuilder.get(ENGINE_NAME).layerwise_model
780
+ self.fused_rotary_emb = self.lmc_model.fused_rotary_emb
781
+
782
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
783
+
784
+ self._lazy_initialize_buffer(self.kvcaches)
785
+
786
+ num_all_tokens = ends[-1] - starts[0]
787
+ slot_mapping_full = slot_mapping[starts[0] : ends[-1]]
788
+
789
+ # compute gap positions
790
+ gap_mask = torch.ones(
791
+ num_all_tokens, dtype=torch.bool, device=slot_mapping_full.device
792
+ )
793
+ buf_offset = starts[0]
794
+
795
+ for start, end in zip(starts, ends, strict=False):
796
+ gap_mask[start - buf_offset : end - buf_offset] = False
797
+
798
+ self.current_gap_positions = torch.where(gap_mask)[0]
799
+
800
+ buf_offset = starts[0]
801
+ if self.cache_positions:
802
+ new_positions_full = torch.arange(
803
+ starts[0], ends[-1], dtype=torch.int64, device=self.kvcaches[0].device
804
+ )
805
+
806
+ buffer_shape = self.get_shape(num_all_tokens)
807
+ assert self.gpu_buffer_allocator is not None
808
+ compute_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
809
+ buffer_shape, self.dtype, MemoryFormat.KV_2TD
810
+ )
811
+ load_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
812
+ buffer_shape, self.dtype, MemoryFormat.KV_2TD
813
+ )
814
+ assert compute_gpu_buffer_obj is not None, (
815
+ "Failed to allocate GPU buffer in GPUConnector"
816
+ )
817
+ assert load_gpu_buffer_obj is not None, (
818
+ "Failed to allocate GPU buffer in GPUConnector"
819
+ )
820
+ assert compute_gpu_buffer_obj.tensor is not None
821
+ assert load_gpu_buffer_obj.tensor is not None
822
+
823
+ # current_stream = torch.cuda.current_stream()
824
+
825
+ if self.cache_positions:
826
+ old_positions_full = torch.zeros(
827
+ (num_all_tokens,), dtype=torch.int64, device=self.kvcaches[0].device
828
+ )
829
+ for layer_id in range(self.num_layers + 2):
830
+ if layer_id > 1:
831
+ lmc_ops.single_layer_kv_transfer(
832
+ self.buffer_mapping[layer_id - 2].tensor,
833
+ self.kvcaches[layer_id - 2],
834
+ slot_mapping_full,
835
+ lmc_ops.TransferDirection.H2D,
836
+ self.gpu_kv_format,
837
+ token_major=False, # shape is [2, num_tokens, hidden_dim]
838
+ )
839
+ del self.buffer_mapping[layer_id - 2]
840
+
841
+ logger.debug(f"Finished loading layer {layer_id - 2} into paged memory")
842
+
843
+ if layer_id > 0 and layer_id <= self.num_layers:
844
+ # NOTE: wait until both compute and load streams are done
845
+ torch.cuda.synchronize()
846
+
847
+ # ping-pong the buffers
848
+ compute_gpu_buffer_obj, load_gpu_buffer_obj = (
849
+ load_gpu_buffer_obj,
850
+ compute_gpu_buffer_obj,
851
+ )
852
+
853
+ if self.cache_positions:
854
+ assert compute_gpu_buffer_obj.tensor is not None
855
+
856
+ compute_gpu_buffer_obj.tensor[0] = self.fused_rotary_emb(
857
+ old_positions_full,
858
+ new_positions_full,
859
+ compute_gpu_buffer_obj.tensor[0],
860
+ )
861
+
862
+ # gap zeroing after RoPE
863
+ if self.current_gap_positions.numel():
864
+ compute_gpu_buffer_obj.tensor[:, self.current_gap_positions] = 0.0
865
+
866
+ self.buffer_mapping[layer_id - 1] = compute_gpu_buffer_obj
867
+
868
+ logger.debug(f"Finished loading layer {layer_id - 1} into buffer")
869
+
870
+ if layer_id < self.num_layers:
871
+ memory_objs_layer = yield
872
+
873
+ # memobj -> gpu_buffer
874
+ with torch.cuda.stream(self.load_stream):
875
+ for start, end, memory_obj in zip(
876
+ starts, ends, memory_objs_layer, strict=False
877
+ ):
878
+ assert memory_obj.metadata.fmt == MemoryFormat.KV_2TD
879
+ assert load_gpu_buffer_obj.tensor is not None
880
+ load_gpu_buffer_obj.tensor[0][
881
+ start - buf_offset : end - buf_offset
882
+ ].copy_(memory_obj.tensor[0], non_blocking=True)
883
+
884
+ load_gpu_buffer_obj.tensor[1][
885
+ start - buf_offset : end - buf_offset
886
+ ].copy_(memory_obj.tensor[1], non_blocking=True)
887
+
888
+ if self.cache_positions and layer_id == 0:
889
+ old_positions_full[
890
+ start - buf_offset : end - buf_offset
891
+ ] = memory_obj.metadata.cached_positions
892
+
893
+ elif layer_id == self.num_layers:
894
+ yield
895
+
896
+ # free the buffer memory
897
+ load_gpu_buffer_obj.ref_count_down()
898
+ compute_gpu_buffer_obj.ref_count_down()
899
+
900
+ assert len(self.buffer_mapping) == 0, (
901
+ "There are still layers in the buffer mapping after "
902
+ "releasing the GPU buffers."
903
+ )
904
+
905
+ yield
906
+
907
+ # TODO(Jiayi): Reduce repetitive operations in `batched_to_gpu`
908
+ # and `batched_from_gpu`.
909
+ @_lmcache_nvtx_annotate
910
+ def batched_from_gpu(
911
+ self,
912
+ memory_objs: Union[List[List[MemoryObj]], List[MemoryObj]],
913
+ starts: List[int],
914
+ ends: List[int],
915
+ **kwargs,
916
+ ):
917
+ """
918
+ This function is a generator that moves the KV cache from the paged GPU
919
+ memory to the memory objects. The first iteration will prepare some
920
+ related metadata and initiate the transfer in the first layer. In each
921
+ of the following iterations, it will first wait until the storing of
922
+ previous layer finishes, and then initiate string the KV cache of the
923
+ current layer one. The storing process of the KV cache is paged GPU
924
+ memory -> GPU buffer -> memory objects. The last iteration simply waits
925
+ for the last layer to finish.
926
+ In total, this the generator will yield num_layers + 1 times.
927
+
928
+ :param memory_objs: The memory objects to store the KV cache. The first
929
+ dimension is the number of layers, and the second dimension is the
930
+ number of memory objects (i.e., number of chunks) for each layer.
931
+
932
+ :param starts: The starting indices of the KV cache in the corresponding
933
+ token sequence.
934
+
935
+ :param ends: The ending indices of the KV cache in the corresponding
936
+ token sequence.
937
+
938
+ :raises ValueError: If 'kvcaches' is not provided in kwargs.
939
+
940
+ :raises ValueError: If 'slot_mapping' is not provided in kwargs.
941
+ """
942
+
943
+ self.initialize_kvcaches_ptr(**kwargs)
944
+ assert self.kvcaches is not None, (
945
+ "kvcaches should be provided in kwargs or initialized beforehand."
946
+ )
947
+
948
+ if "slot_mapping" not in kwargs:
949
+ raise ValueError("'slot_mapping' should be provided in kwargs.")
950
+
951
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
952
+
953
+ self._lazy_initialize_buffer(self.kvcaches)
954
+
955
+ buf_start = 0
956
+ slot_mapping_chunks = []
957
+ buf_starts_ends = []
958
+ old_positions_chunks = []
959
+ for start, end in zip(starts, ends, strict=False):
960
+ buf_end = buf_start + end - start
961
+ buf_starts_ends.append((buf_start, buf_end))
962
+ slot_mapping_chunks.append(slot_mapping[start:end])
963
+ buf_start = buf_end
964
+ if self.cache_positions:
965
+ old_positions_chunks.append(
966
+ torch.arange(
967
+ start, end, device=self.kvcaches[0].device, dtype=torch.int64
968
+ )
969
+ )
970
+
971
+ slot_mapping_full = torch.cat(slot_mapping_chunks, dim=0)
972
+
973
+ num_tokens = len(slot_mapping_full)
974
+ buffer_shape = self.get_shape(num_tokens)
975
+ assert self.gpu_buffer_allocator is not None
976
+ tmp_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
977
+ buffer_shape, self.dtype, MemoryFormat.KV_2TD
978
+ )
979
+ assert tmp_gpu_buffer_obj is not None, (
980
+ "Failed to allocate GPU buffer in GPUConnector"
981
+ )
982
+ assert tmp_gpu_buffer_obj.tensor is not None
983
+
984
+ current_stream = torch.cuda.current_stream()
985
+
986
+ for layer_id in range(self.num_layers):
987
+ memory_objs_layer = memory_objs[layer_id]
988
+ # kvcaches -> gpu_buffer -> memobj
989
+ with torch.cuda.stream(self.store_stream):
990
+ self.store_stream.wait_stream(current_stream)
991
+ lmc_ops.single_layer_kv_transfer(
992
+ tmp_gpu_buffer_obj.tensor,
993
+ self.kvcaches[layer_id],
994
+ slot_mapping_full,
995
+ lmc_ops.TransferDirection.D2H,
996
+ self.gpu_kv_format,
997
+ token_major=False, # shape is [2, num_tokens, hidden_dim]
998
+ )
999
+ for (buf_start, buf_end), memory_obj, old_positions in zip(
1000
+ buf_starts_ends,
1001
+ memory_objs_layer,
1002
+ old_positions_chunks,
1003
+ strict=False,
1004
+ ):
1005
+ assert memory_obj.tensor is not None
1006
+ memory_obj.tensor[0].copy_(
1007
+ tmp_gpu_buffer_obj.tensor[0][buf_start:buf_end],
1008
+ non_blocking=True,
1009
+ )
1010
+ memory_obj.tensor[1].copy_(
1011
+ tmp_gpu_buffer_obj.tensor[1][buf_start:buf_end],
1012
+ non_blocking=True,
1013
+ )
1014
+ if self.cache_positions:
1015
+ memory_obj.metadata.cached_positions = old_positions
1016
+
1017
+ yield
1018
+ self.store_stream.synchronize()
1019
+ logger.debug(f"Finished offloading layer {layer_id}")
1020
+
1021
+ # free the buffer memory
1022
+ tmp_gpu_buffer_obj.ref_count_down()
1023
+ yield
1024
+
1025
+ def get_shape(self, num_tokens: int) -> torch.Size:
1026
+ return torch.Size([2, num_tokens, self.hidden_dim_size])
1027
+
1028
+
1029
+ class VLLMPagedMemLayerwiseGPUConnector(GPUConnectorInterface):
1030
+ """ """
1031
+
1032
+ def __init__(
1033
+ self,
1034
+ hidden_dim_size: int,
1035
+ num_layers: int,
1036
+ use_gpu: bool = False,
1037
+ **kwargs,
1038
+ ):
1039
+ self.hidden_dim_size = hidden_dim_size
1040
+ self.num_layers = num_layers
1041
+ self.use_gpu = use_gpu
1042
+ self.layout_hints: LayoutHints = (
1043
+ kwargs.get( # type: ignore[assignment]
1044
+ "layout_hints"
1045
+ )
1046
+ or {}
1047
+ )
1048
+
1049
+ self.gpu_buffer_allocator = None
1050
+
1051
+ assert "chunk_size" in kwargs, (
1052
+ "chunk_size should be provided to create a GPU buffer."
1053
+ )
1054
+ assert "dtype" in kwargs, "dtype should be provided to create a GPU buffer."
1055
+ assert "device" in kwargs, "device should be provided to create a GPU buffer."
1056
+
1057
+ self.dtype = kwargs["dtype"]
1058
+ self.device = kwargs["device"]
1059
+
1060
+ self.kvcaches: Optional[List[torch.Tensor]] = None
1061
+
1062
+ # All sizes are in bytes
1063
+ self.element_size = torch.tensor([], dtype=self.dtype).element_size()
1064
+
1065
+ self.load_stream = torch.cuda.Stream()
1066
+ self.store_stream = torch.cuda.Stream()
1067
+
1068
+ self.use_mla = "use_mla" in kwargs and kwargs["use_mla"]
1069
+
1070
+ @classmethod
1071
+ def from_metadata(
1072
+ cls,
1073
+ metadata: LMCacheMetadata,
1074
+ use_gpu: bool = False,
1075
+ device: Optional[torch.device] = None,
1076
+ layout_hints: Optional[LayoutHints] = None,
1077
+ ) -> "VLLMPagedMemLayerwiseGPUConnector":
1078
+ """Create a connector from LMCacheMetadata.
1079
+
1080
+ Args:
1081
+ metadata: The LMCache engine metadata containing model configuration.
1082
+ use_gpu: Whether to use GPU intermediate buffer.
1083
+ device: The device to use for the connector.
1084
+ layout_hints: Optional hints about KV cache layout from the
1085
+ serving engine.
1086
+
1087
+ Returns:
1088
+ A new instance of VLLMPagedMemLayerwiseGPUConnector.
1089
+ """
1090
+ # Extract parameters from metadata
1091
+ # kv_shape: (num_layer, 2 or 1, chunk_size, num_kv_head, head_size)
1092
+ num_layers = metadata.kv_shape[0]
1093
+ chunk_size = metadata.kv_shape[2]
1094
+ num_kv_head = metadata.kv_shape[3]
1095
+ head_size = metadata.kv_shape[4]
1096
+ hidden_dim_size = num_kv_head * head_size
1097
+
1098
+ return cls(
1099
+ hidden_dim_size=hidden_dim_size,
1100
+ num_layers=num_layers,
1101
+ use_gpu=use_gpu,
1102
+ chunk_size=chunk_size,
1103
+ dtype=metadata.kv_dtype,
1104
+ device=device,
1105
+ use_mla=metadata.use_mla,
1106
+ layout_hints=layout_hints,
1107
+ )
1108
+
1109
+ def _lazy_initialize_buffer(self, kv_caches):
1110
+ """
1111
+ Lazily initialize the GPU buffer allocator if it is not initialized yet.
1112
+ Currently, we use the `kv_caches` (kv cache pointer) to determine
1113
+ the gpu buffer size in gpu connector.
1114
+ Also, the first request might be a bit slower due to buffer creation.
1115
+ """
1116
+ if self.use_gpu and self.gpu_buffer_allocator is None:
1117
+ logger.info("Lazily initializing GPU buffer.")
1118
+ # NOTE (Jiayi): We use the first layer to determine the gpu buffer size.
1119
+ # NOTE (Jiayi): Using the exact number of tokens in the first layer
1120
+ # is okay since fragmentation shouldn't exist in the `gpu_buffer_allocator`
1121
+ # in layerwise mode.
1122
+
1123
+ kv_caches = ensure_contiguous_kv_caches(
1124
+ kv_caches, kv_layout=self.layout_hints.get("kv_layout")
1125
+ )
1126
+ self.kvcaches = kv_caches
1127
+ self.gpu_kv_format = discover_gpu_kv_format(
1128
+ kv_caches, EngineType.VLLM, layout_hints=self.layout_hints
1129
+ )
1130
+ assert_is_vllm_mla_or_flash_attn_or_flash_infer(self.gpu_kv_format)
1131
+ self.tokens_per_layer = get_tokens_per_layer(kv_caches, self.gpu_kv_format)
1132
+ self.elements_per_layer = get_elements_per_layer(
1133
+ kv_caches, self.gpu_kv_format
1134
+ )
1135
+ logger.info(
1136
+ f"Lazily initializing GPU buffer (max tokens={self.tokens_per_layer})."
1137
+ )
1138
+ gpu_buffer_size = self.elements_per_layer * self.element_size
1139
+ self.gpu_buffer_allocator = GPUMemoryAllocator(
1140
+ gpu_buffer_size, device=self.device
1141
+ )
1142
+
1143
+ def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
1144
+ """ """
1145
+
1146
+ raise NotImplementedError
1147
+
1148
+ def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
1149
+ """ """
1150
+
1151
+ raise NotImplementedError
1152
+
1153
+ @_lmcache_nvtx_annotate
1154
+ def batched_to_gpu(self, starts: List[int], ends: List[int], **kwargs):
1155
+ """
1156
+ This function is a generator that moves the KV cache from the memory
1157
+ objects to paged GPU memory. The first iteration will prepare some
1158
+ related metadata. In each of the following iterations, it will first
1159
+ wait until the loading of the previous layer finish, and then load
1160
+ one layer of KV cache from the memory objects -> GPU buffer ->
1161
+ paged GPU memory. The last iteration simply waits for the last layer
1162
+ to finish.
1163
+ In total, this the generator will yield num_layers + 2 times.
1164
+
1165
+ :param starts: The starting indices of the KV cache in the corresponding
1166
+ token sequence.
1167
+
1168
+ :param ends: The ending indices of the KV cache in the corresponding
1169
+ token sequence.
1170
+
1171
+ :raises ValueError: If 'slot_mapping' is not provided in kwargs.
1172
+ """
1173
+
1174
+ self.initialize_kvcaches_ptr(**kwargs)
1175
+ assert self.kvcaches is not None, (
1176
+ "kvcaches should be provided in kwargs or initialized beforehand."
1177
+ )
1178
+
1179
+ if "slot_mapping" not in kwargs:
1180
+ raise ValueError("'slot_mapping' should be provided in kwargs.")
1181
+
1182
+ if "sync" not in kwargs:
1183
+ raise ValueError("'sync' should be provided in kwargs.")
1184
+
1185
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
1186
+ sync: bool = kwargs["sync"]
1187
+
1188
+ self._lazy_initialize_buffer(self.kvcaches)
1189
+
1190
+ slot_mapping_chunks = []
1191
+ for start, end in zip(starts, ends, strict=False):
1192
+ slot_mapping_chunks.append(slot_mapping[start:end])
1193
+
1194
+ # TODO(Jiayi): Optimize away this `cat`
1195
+ slot_mapping_full = torch.cat(slot_mapping_chunks, dim=0)
1196
+
1197
+ num_tokens = len(slot_mapping_full)
1198
+
1199
+ mem_fmt = MemoryFormat.KV_MLA_FMT if self.use_mla else MemoryFormat.KV_T2D
1200
+
1201
+ tmp_gpu_buffer_obj: Optional[MemoryObj] = None
1202
+ if self.use_gpu:
1203
+ buffer_shape = self.get_shape(num_tokens)
1204
+ assert self.gpu_buffer_allocator is not None
1205
+ tmp_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
1206
+ buffer_shape, self.dtype, mem_fmt
1207
+ )
1208
+ assert tmp_gpu_buffer_obj is not None, (
1209
+ "Failed to allocate GPU buffer in GPUConnector"
1210
+ )
1211
+ assert tmp_gpu_buffer_obj.tensor is not None
1212
+
1213
+ offset = starts[0]
1214
+ current_stream = torch.cuda.current_stream()
1215
+
1216
+ for layer_id in range(self.num_layers):
1217
+ memory_objs_layer = yield
1218
+ if sync:
1219
+ current_stream.wait_stream(self.load_stream)
1220
+ if layer_id > 0:
1221
+ logger.debug(f"Finished loading layer {layer_id - 1}")
1222
+
1223
+ # memobj -> gpu_buffer -> kvcaches
1224
+ with torch.cuda.stream(self.load_stream):
1225
+ for start, end, memory_obj in zip(
1226
+ starts, ends, memory_objs_layer, strict=False
1227
+ ):
1228
+ # Validate memory format
1229
+ if self.use_mla:
1230
+ assert memory_obj.metadata.fmt == MemoryFormat.KV_MLA_FMT, (
1231
+ f"Expected memory format {MemoryFormat.KV_MLA_FMT}, "
1232
+ f"got {memory_obj.metadata.fmt}"
1233
+ )
1234
+ else:
1235
+ assert memory_obj.metadata.fmt == MemoryFormat.KV_T2D, (
1236
+ f"Expected memory format {MemoryFormat.KV_T2D}, "
1237
+ f"got {memory_obj.metadata.fmt}"
1238
+ )
1239
+ if self.use_gpu:
1240
+ tmp_gpu_buffer_obj.tensor[start - offset : end - offset].copy_(
1241
+ memory_obj.tensor, non_blocking=True
1242
+ )
1243
+ else:
1244
+ lmc_ops.single_layer_kv_transfer(
1245
+ memory_obj.tensor,
1246
+ self.kvcaches[layer_id],
1247
+ slot_mapping_full,
1248
+ lmc_ops.TransferDirection.H2D,
1249
+ self.gpu_kv_format,
1250
+ token_major=True,
1251
+ )
1252
+
1253
+ if self.use_gpu:
1254
+ lmc_ops.single_layer_kv_transfer(
1255
+ tmp_gpu_buffer_obj.tensor,
1256
+ self.kvcaches[layer_id],
1257
+ slot_mapping_full,
1258
+ lmc_ops.TransferDirection.H2D,
1259
+ self.gpu_kv_format,
1260
+ token_major=True,
1261
+ )
1262
+ yield
1263
+
1264
+ # synchronize the last layer
1265
+ if sync:
1266
+ current_stream.wait_stream(self.load_stream)
1267
+
1268
+ # free the buffer memory
1269
+ if tmp_gpu_buffer_obj is not None:
1270
+ tmp_gpu_buffer_obj.ref_count_down()
1271
+
1272
+ logger.debug(f"Finished loading all {self.num_layers} layers.")
1273
+ yield
1274
+
1275
+ @_lmcache_nvtx_annotate
1276
+ def batched_from_gpu(
1277
+ self,
1278
+ memory_objs: Union[List[List[MemoryObj]]],
1279
+ starts: List[int],
1280
+ ends: List[int],
1281
+ **kwargs,
1282
+ ):
1283
+ """
1284
+ This function is a generator that moves the KV cache from the paged GPU
1285
+ memory to the memory objects. The first iteration will prepare some
1286
+ related metadata and initiate the transfer in the first layer. In each
1287
+ of the following iterations, it will first wait until the storing of
1288
+ previous layer finishes, and then initiate string the KV cache of the
1289
+ current layer one. The storing process of the KV cache is paged GPU
1290
+ memory -> GPU buffer -> memory objects. The last iteration simply waits
1291
+ for the last layer to finish.
1292
+ In total, this the generator will yield num_layers + 1 times.
1293
+
1294
+ :param memory_objs: The memory objects to store the KV cache. The first
1295
+ dimension is the number of layers, and the second dimension is the
1296
+ number of memory objects (i.e., number of chunks) for each layer.
1297
+
1298
+ :param starts: The starting indices of the KV cache in the corresponding
1299
+ token sequence.
1300
+
1301
+ :param ends: The ending indices of the KV cache in the corresponding
1302
+ token sequence.
1303
+
1304
+ :raises ValueError: If 'slot_mapping' is not provided in kwargs.
1305
+ """
1306
+
1307
+ self.initialize_kvcaches_ptr(**kwargs)
1308
+ assert self.kvcaches is not None, (
1309
+ "kvcaches should be provided in kwargs or initialized beforehand."
1310
+ )
1311
+
1312
+ if "slot_mapping" not in kwargs:
1313
+ raise ValueError("'slot_mapping' should be provided in kwargs.")
1314
+
1315
+ if "sync" not in kwargs:
1316
+ raise ValueError("'sync' should be provided in kwargs.")
1317
+
1318
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
1319
+ sync: bool = kwargs["sync"]
1320
+
1321
+ self._lazy_initialize_buffer(self.kvcaches)
1322
+
1323
+ slot_mapping_chunks = []
1324
+ for start, end in zip(starts, ends, strict=False):
1325
+ slot_mapping_chunks.append(slot_mapping[start:end])
1326
+
1327
+ slot_mapping_full = torch.cat(slot_mapping_chunks, dim=0)
1328
+
1329
+ num_tokens = len(slot_mapping_full)
1330
+
1331
+ mem_fmt = MemoryFormat.KV_MLA_FMT if self.use_mla else MemoryFormat.KV_T2D
1332
+
1333
+ tmp_gpu_buffer_obj: Optional[MemoryObj] = None
1334
+ if self.use_gpu:
1335
+ buffer_shape = self.get_shape(num_tokens)
1336
+ assert self.gpu_buffer_allocator is not None
1337
+ tmp_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
1338
+ buffer_shape, self.dtype, mem_fmt
1339
+ )
1340
+ assert tmp_gpu_buffer_obj is not None, (
1341
+ "Failed to allocate GPU buffer in GPUConnector"
1342
+ )
1343
+ assert tmp_gpu_buffer_obj.tensor is not None
1344
+
1345
+ offset = starts[0]
1346
+ current_stream = torch.cuda.current_stream()
1347
+
1348
+ for layer_id in range(self.num_layers):
1349
+ memory_objs_layer = memory_objs[layer_id]
1350
+ # kvcaches -> gpu_buffer -> memobj
1351
+ with torch.cuda.stream(self.store_stream):
1352
+ self.store_stream.wait_stream(current_stream)
1353
+ if self.use_gpu:
1354
+ lmc_ops.single_layer_kv_transfer(
1355
+ tmp_gpu_buffer_obj.tensor,
1356
+ self.kvcaches[layer_id],
1357
+ slot_mapping_full,
1358
+ lmc_ops.TransferDirection.D2H,
1359
+ self.gpu_kv_format,
1360
+ token_major=True,
1361
+ )
1362
+ for start, end, memory_obj in zip(
1363
+ starts, ends, memory_objs_layer, strict=False
1364
+ ):
1365
+ assert memory_obj.tensor is not None
1366
+ if self.use_gpu:
1367
+ memory_obj.tensor.copy_(
1368
+ tmp_gpu_buffer_obj.tensor[start - offset : end - offset],
1369
+ non_blocking=True,
1370
+ )
1371
+ else:
1372
+ lmc_ops.single_layer_kv_transfer(
1373
+ memory_obj.tensor,
1374
+ self.kvcaches[layer_id],
1375
+ slot_mapping[start:end],
1376
+ lmc_ops.TransferDirection.D2H,
1377
+ self.gpu_kv_format,
1378
+ token_major=True,
1379
+ )
1380
+ # Set metadata format
1381
+ if self.use_mla:
1382
+ memory_obj.metadata.fmt = MemoryFormat.KV_MLA_FMT
1383
+
1384
+ yield
1385
+ if sync:
1386
+ self.store_stream.synchronize()
1387
+ logger.debug(f"Finished offloading layer {layer_id}")
1388
+
1389
+ # free the buffer memory
1390
+ if tmp_gpu_buffer_obj is not None:
1391
+ tmp_gpu_buffer_obj.ref_count_down()
1392
+
1393
+ yield
1394
+
1395
+ def get_shape(self, num_tokens: int) -> torch.Size:
1396
+ if self.use_mla:
1397
+ # MLA format: [num_tokens, hidden_dim_size]
1398
+ return torch.Size([num_tokens, self.hidden_dim_size])
1399
+ else:
1400
+ # Standard format: [num_tokens, 2, hidden_dim_size]
1401
+ return torch.Size([num_tokens, 2, self.hidden_dim_size])
1402
+
1403
+
1404
+ class SGLangGPUConnector(GPUConnectorInterface):
1405
+ """
1406
+ The GPU KV cache should be a list of tensors, one for each layer,
1407
+ with separate key and value pointers.
1408
+ More specifically, we have:
1409
+ - kvcaches: Tuple[List[Tensor], List[Tensor]]
1410
+ - The first element is a list of key tensors, one per layer.
1411
+ - The second element is a list of value tensors, one per layer.
1412
+ - Each tensor: [page_buffer_size, head_num, head_size]
1413
+
1414
+ The connector manages the transfer of KV cache data between CPU and GPU
1415
+ memory for SGLang using pointer arrays for efficient access.
1416
+ It will produce/consume memory objects with KV_2LTD format.
1417
+ """
1418
+
1419
+ def __init__(
1420
+ self, hidden_dim_size: int, num_layers: int, use_gpu: bool = False, **kwargs
1421
+ ):
1422
+ self.hidden_dim_size = hidden_dim_size
1423
+ self.num_layers = num_layers
1424
+
1425
+ self.kv_cache_pointers_on_gpu: dict[int, torch.Tensor] = {}
1426
+ self.page_buffer_size = 0
1427
+
1428
+ self.gpu_buffer: Optional[torch.Tensor] = None
1429
+ self.use_mla = "use_mla" in kwargs and kwargs["use_mla"]
1430
+
1431
+ self.num_kv_cache = num_layers if self.use_mla else num_layers * 2
1432
+ self.kv_cache_pointers = torch.empty(
1433
+ self.num_kv_cache, dtype=torch.int64, device="cpu"
1434
+ )
1435
+
1436
+ if use_gpu:
1437
+ assert "chunk_size" in kwargs, (
1438
+ "chunk_size should be provided to create a GPU buffer."
1439
+ )
1440
+ assert "device" in kwargs, (
1441
+ "device should be provided to create a GPU buffer."
1442
+ )
1443
+ shape = self.get_shape(kwargs["chunk_size"])
1444
+ self.gpu_buffer = torch.empty(
1445
+ shape, dtype=kwargs["dtype"], device=kwargs["device"]
1446
+ )
1447
+ logger.info(f"GPU buffer: {self.gpu_buffer.shape}")
1448
+
1449
+ def _initialize_pointers(self, kv_caches: List[torch.Tensor]) -> torch.Tensor:
1450
+ # Discover format first to handle flattening correctly
1451
+ self.gpu_kv_format = discover_gpu_kv_format(kv_caches, EngineType.SGLANG)
1452
+
1453
+ # For TWO_X_NL_X_NBBS_NH_HS format, kv_caches is [[k_list], [v_list]]
1454
+ # We need to flatten it to [k0, k1, ..., v0, v1, ...]
1455
+ if self.gpu_kv_format == lmc_ops.GPUKVFormat.TWO_X_NL_X_NBBS_NH_HS:
1456
+ flat_kv_caches = kv_caches[0] + kv_caches[1] # [k_list] + [v_list]
1457
+ device = flat_kv_caches[0].device
1458
+ else:
1459
+ flat_kv_caches = kv_caches
1460
+ device = flat_kv_caches[0].device
1461
+
1462
+ assert len(flat_kv_caches) == self.num_kv_cache, (
1463
+ f"Expected {self.num_kv_cache} KV caches, got {len(flat_kv_caches)}"
1464
+ )
1465
+
1466
+ self.kv_cache_pointers.numpy()[:] = [t.data_ptr() for t in flat_kv_caches]
1467
+ assert device.type == "cuda", "The device should be CUDA."
1468
+ idx = device.index
1469
+ if idx not in self.kv_cache_pointers_on_gpu:
1470
+ self.kv_cache_pointers_on_gpu[idx] = torch.empty(
1471
+ self.num_kv_cache, dtype=torch.int64, device=device
1472
+ )
1473
+ self.kv_cache_pointers_on_gpu[idx].copy_(self.kv_cache_pointers)
1474
+
1475
+ # sglang MLA kv_caches[0].shape: [num_pages * page_size, 1, head_size]
1476
+ # sglang MHA kv_caches: [[k_list], [v_list]]
1477
+ # each with shape [num_pages * page_size, num_heads, head_size]
1478
+ self.page_buffer_size = get_page_buffer_size(kv_caches, self.gpu_kv_format)
1479
+ return self.kv_cache_pointers_on_gpu[idx]
1480
+
1481
+ @_lmcache_nvtx_annotate
1482
+ def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
1483
+ """Expect a kwarg 'kvcaches' which is a nested tuple of K and V tensors.
1484
+ The kvcaches should correspond to the "WHOLE token sequence".
1485
+
1486
+ Note:
1487
+ 1. This function expects the 'slot_mapping' is a "partial slot mapping"
1488
+ where its length is the same as the uncached token sequence.
1489
+ 2. In the case that there is prefix caching, slot_mapping will starts
1490
+ with -1s until the end of the matched prefix. The start and end
1491
+ should NEVER overlap with the prefix caching (which means the
1492
+ underlying CUDA kernel will never see -1 in slot_mapping)
1493
+
1494
+
1495
+ :raises ValueError: If 'kvcaches' is not provided in kwargs.
1496
+ :raises AssertionError: If the memory object does not have a tensor.
1497
+ :raises ValueError: If 'slot_mapping' is not provided in kwargs.
1498
+ """
1499
+ assert memory_obj.tensor is not None
1500
+
1501
+ if self.use_mla:
1502
+ if memory_obj.metadata.fmt != MemoryFormat.KV_MLA_FMT:
1503
+ raise ValueError(
1504
+ "The memory object should be in KV_MLA_FMT format in"
1505
+ f" order to be processed by {self.__class__.__name__}"
1506
+ )
1507
+ else:
1508
+ if memory_obj.metadata.fmt != MemoryFormat.KV_2LTD:
1509
+ raise ValueError(
1510
+ "The memory object should be in KV_2LTD format in"
1511
+ f" order to be processed by {self.__class__.__name__}"
1512
+ )
1513
+
1514
+ if "kvcaches" not in kwargs:
1515
+ raise ValueError("'kvcaches' should be provided in kwargs.")
1516
+
1517
+ if "slot_mapping" not in kwargs:
1518
+ raise ValueError("'slot_mapping' should be provided in kwargs.")
1519
+
1520
+ offset = kwargs.get("offset", 0)
1521
+
1522
+ kvcaches: List[torch.Tensor] = kwargs["kvcaches"]
1523
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
1524
+
1525
+ kv_cache_pointers = self._initialize_pointers(kvcaches)
1526
+ lmc_ops.multi_layer_kv_transfer_unilateral(
1527
+ memory_obj.tensor,
1528
+ kv_cache_pointers,
1529
+ slot_mapping[start - offset : end - offset],
1530
+ kvcaches[0][0].device,
1531
+ self.page_buffer_size,
1532
+ lmc_ops.TransferDirection.H2D,
1533
+ self.gpu_kv_format,
1534
+ )
1535
+
1536
+ @_lmcache_nvtx_annotate
1537
+ def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
1538
+ """Expect a kwarg 'kvcaches' which is a nested tuple of K and V tensors.
1539
+ The kvcaches should correspond to the "WHOLE token sequence".
1540
+
1541
+ Will set the memory_obj.metadata.fmt to MemoryFormat.KV_2LTD.
1542
+
1543
+ Note:
1544
+ 1. This function expects the 'slot_mapping' is a "partial slot mapping"
1545
+ where its length is the same as the uncached token sequence.
1546
+ 2. In the case that there is prefix caching, slot_mapping will starts
1547
+ with -1s until the end of the matched prefix. The start and end
1548
+ should NEVER overlap with the prefix caching (which means the
1549
+ underlying CUDA kernel will never see -1 in slot_mapping)
1550
+
1551
+ :raises ValueError: If 'kvcaches' is not provided in kwargs,
1552
+ :raises AssertionError: If the memory object does not have a tensor.
1553
+ :raises ValueError: If 'slot_mapping' is not provided in kwargs.
1554
+ """
1555
+ assert memory_obj.tensor is not None
1556
+
1557
+ if "kvcaches" not in kwargs:
1558
+ raise ValueError("'kvcaches' should be provided in kwargs.")
1559
+
1560
+ if "slot_mapping" not in kwargs:
1561
+ raise ValueError("'slot_mapping' should be provided in kwargs.")
1562
+
1563
+ kvcaches: List[torch.Tensor] = kwargs["kvcaches"]
1564
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
1565
+
1566
+ kv_cache_pointers = self._initialize_pointers(kvcaches)
1567
+
1568
+ if self.gpu_buffer is None or end - start != self.gpu_buffer.shape[2]:
1569
+ lmc_ops.multi_layer_kv_transfer_unilateral(
1570
+ memory_obj.tensor,
1571
+ kv_cache_pointers,
1572
+ slot_mapping[start:end],
1573
+ kvcaches[0][0].device,
1574
+ self.page_buffer_size,
1575
+ lmc_ops.TransferDirection.D2H,
1576
+ self.gpu_kv_format,
1577
+ )
1578
+ else:
1579
+ # kvcaches -> gpu_buffer -> memobj
1580
+ assert self.gpu_buffer.device == kvcaches[0][0].device
1581
+ tmp_gpu_buffer = self.gpu_buffer[:, :, : end - start, :]
1582
+ lmc_ops.multi_layer_kv_transfer_unilateral(
1583
+ tmp_gpu_buffer,
1584
+ kv_cache_pointers,
1585
+ slot_mapping[start:end],
1586
+ kvcaches[0][0].device,
1587
+ self.page_buffer_size,
1588
+ lmc_ops.TransferDirection.D2H,
1589
+ self.gpu_kv_format,
1590
+ )
1591
+ memory_obj.tensor.copy_(tmp_gpu_buffer, non_blocking=True)
1592
+
1593
+ if not memory_obj.tensor.is_cuda:
1594
+ # Force a synchronize if the target buffer is NOT CUDA device
1595
+ # NOTE: for better performance, we may not want to sync for every
1596
+ # memory object
1597
+ torch.cuda.synchronize()
1598
+
1599
+ if self.use_mla:
1600
+ memory_obj.metadata.fmt = MemoryFormat.KV_MLA_FMT
1601
+
1602
+ def get_shape(self, num_tokens: int) -> torch.Size:
1603
+ return torch.Size([2, self.num_layers, num_tokens, self.hidden_dim_size])
1604
+
1605
+ # TODO(Jiayi): need to optimize to enable real batching
1606
+ def batched_to_gpu(self, memory_objs, starts, ends, **kwargs):
1607
+ for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
1608
+ self.to_gpu(memory_obj, start, end, **kwargs)
1609
+
1610
+ # TODO(Yuwei): need to optimize to enable real batching
1611
+ def batched_from_gpu(self, memory_objs, starts, ends, **kwargs):
1612
+ for memory_obj, start, end in zip(memory_objs, starts, ends, strict=False):
1613
+ self.from_gpu(memory_obj, start, end, **kwargs)
1614
+
1615
+
1616
+ # TODO: support MLA
1617
+ class SGLangLayerwiseGPUConnector(GPUConnectorInterface):
1618
+ """
1619
+ The GPU KV cache should be a list of tensors, one for each layer,
1620
+ with separate key and value pointers.
1621
+ More specifically, we have:
1622
+ - kvcaches: Tuple[List[Tensor], List[Tensor]]
1623
+ - The first element is a list of key tensors, one per layer.
1624
+ - The second element is a list of value tensors, one per layer.
1625
+ - Each tensor: [page_buffer_size, head_num, head_size]
1626
+
1627
+ The connector manages the transfer of KV cache data between CPU and GPU
1628
+ memory for SGLang using pointer arrays for efficient access.
1629
+ It will produce/consume memory objects with KV_2LTD format.
1630
+ """
1631
+
1632
+ def __init__(
1633
+ self, hidden_dim_size: int, num_layers: int, use_gpu: bool = False, **kwargs
1634
+ ):
1635
+ assert "dtype" in kwargs, "dtype should be provided to create a GPU buffer."
1636
+ self.dtype = kwargs["dtype"]
1637
+ assert "device" in kwargs, "device should be provided to create a GPU buffer."
1638
+ self.device = kwargs["device"]
1639
+
1640
+ self.hidden_dim_size = hidden_dim_size
1641
+ self.num_layers = num_layers
1642
+
1643
+ self.kv_cache_pointers_on_gpu: dict[int, torch.Tensor] = {}
1644
+ self.page_buffer_size = 0
1645
+
1646
+ self.gpu_buffer: Optional[torch.Tensor] = None
1647
+ self.use_mla = "use_mla" in kwargs and kwargs["use_mla"]
1648
+
1649
+ self.num_kv_cache = num_layers if self.use_mla else num_layers * 2
1650
+ self.element_size = torch.tensor([], dtype=self.dtype).element_size()
1651
+ self.kv_cache_pointers = torch.empty(
1652
+ self.num_kv_cache, dtype=torch.int64, device="cpu"
1653
+ )
1654
+ self.use_gpu = use_gpu
1655
+ self.gpu_buffer_allocator: Optional[GPUMemoryAllocator] = None
1656
+
1657
+ def _lazy_initialize_buffer(self, kv_caches):
1658
+ """
1659
+ Lazily initialize the GPU buffer allocator if it is not initialized yet.
1660
+ Currently, we use the `kv_caches` (kv cache pointer) to determine
1661
+ the gpu buffer size in gpu connector.
1662
+ Also, the first request might be a bit slower due to buffer creation.
1663
+ """
1664
+ if self.use_gpu and self.gpu_buffer_allocator is None:
1665
+ self.gpu_kv_format = discover_gpu_kv_format(kv_caches, EngineType.SGLANG)
1666
+ self.tokens_per_layer = get_tokens_per_layer(kv_caches, self.gpu_kv_format)
1667
+ self.elements_per_layer = get_elements_per_layer(
1668
+ kv_caches, self.gpu_kv_format
1669
+ )
1670
+ logger.info(
1671
+ f"Lazily initializing GPU buffer (max tokens={self.tokens_per_layer})."
1672
+ )
1673
+ gpu_buffer_size = self.elements_per_layer * self.element_size
1674
+ logger.info(
1675
+ f"Lazily initializing GPU buffer (gpu buffer size={gpu_buffer_size})."
1676
+ )
1677
+ self.gpu_buffer_allocator = GPUMemoryAllocator(
1678
+ gpu_buffer_size, device=self.device
1679
+ )
1680
+
1681
+ def to_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
1682
+ raise NotImplementedError
1683
+
1684
+ def from_gpu(self, memory_obj: MemoryObj, start: int, end: int, **kwargs):
1685
+ raise NotImplementedError
1686
+
1687
+ @_lmcache_nvtx_annotate
1688
+ def batched_to_gpu(self, starts: List[int], ends: List[int], **kwargs):
1689
+ """
1690
+ This function is a generator that moves the KV cache from the memory
1691
+ objects to paged GPU memory. The first iteration will prepare some
1692
+ related metadata. In each of the following iterations, it will first
1693
+ wait until the loading of the previous layer finish, and then load
1694
+ one layer of KV cache from the memory objects -> GPU buffer ->
1695
+ paged GPU memory. The last iteration simply waits for the last layer
1696
+ to finish.
1697
+ In total, this the generator will yield num_layers + 2 times.
1698
+
1699
+ :param starts: The starting indices of the KV cache in the corresponding
1700
+ token sequence.
1701
+
1702
+ :param ends: The ending indices of the KV cache in the corresponding
1703
+ token sequence.
1704
+
1705
+ :raises ValueError: If 'slot_mapping' is not provided in kwargs.
1706
+ """
1707
+
1708
+ self.initialize_kvcaches_ptr(**kwargs)
1709
+ assert self.kvcaches is not None, (
1710
+ "kvcaches should be provided in kwargs or initialized beforehand."
1711
+ )
1712
+
1713
+ if "slot_mapping" not in kwargs:
1714
+ raise ValueError("'slot_mapping' should be provided in kwargs.")
1715
+
1716
+ if "sync" not in kwargs:
1717
+ raise ValueError("'sync' should be provided in kwargs.")
1718
+
1719
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
1720
+
1721
+ self._lazy_initialize_buffer(self.kvcaches)
1722
+
1723
+ slot_mapping_chunks = []
1724
+ for start, end in zip(starts, ends, strict=False):
1725
+ slot_mapping_chunks.append(slot_mapping[start:end])
1726
+
1727
+ slot_mapping_full = torch.cat(slot_mapping_chunks, dim=0)
1728
+
1729
+ num_tokens = len(slot_mapping_full)
1730
+
1731
+ if self.use_gpu:
1732
+ buffer_shape = self.get_shape(num_tokens)
1733
+
1734
+ assert self.gpu_buffer_allocator is not None, (
1735
+ "GPU buffer allocator should be initialized"
1736
+ )
1737
+ tmp_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
1738
+ buffer_shape, self.dtype, MemoryFormat.KV_T2D
1739
+ )
1740
+ assert tmp_gpu_buffer_obj is not None, (
1741
+ "Failed to allocate GPU buffer in GPUConnector"
1742
+ )
1743
+ assert tmp_gpu_buffer_obj.tensor is not None
1744
+
1745
+ offset = starts[0]
1746
+
1747
+ for layer_id in range(self.num_layers):
1748
+ memory_objs_layer = yield
1749
+ if layer_id > 0:
1750
+ logger.debug(f"Finished loading layer {layer_id - 1}")
1751
+
1752
+ # memobj -> gpu_buffer -> kvcaches
1753
+ for start, end, memory_obj in zip(
1754
+ starts, ends, memory_objs_layer, strict=False
1755
+ ):
1756
+ assert memory_obj.metadata.fmt == MemoryFormat.KV_T2D
1757
+ if self.use_gpu:
1758
+ tmp_gpu_buffer_obj.tensor[start - offset : end - offset].copy_(
1759
+ memory_obj.tensor, non_blocking=True
1760
+ )
1761
+ else:
1762
+ lmc_ops.single_layer_kv_transfer_sgl(
1763
+ memory_obj.tensor,
1764
+ self.kvcaches[0][layer_id],
1765
+ self.kvcaches[1][layer_id],
1766
+ slot_mapping[start:end],
1767
+ lmc_ops.TransferDirection.H2D,
1768
+ token_major=True,
1769
+ )
1770
+
1771
+ if self.use_gpu:
1772
+ t, h, d = self.kvcaches[0][layer_id].shape
1773
+
1774
+ lmc_ops.single_layer_kv_transfer_sgl(
1775
+ tmp_gpu_buffer_obj.tensor,
1776
+ self.kvcaches[0][layer_id].view(t, 1, h, d),
1777
+ self.kvcaches[1][layer_id].view(t, 1, h, d),
1778
+ slot_mapping_full,
1779
+ lmc_ops.TransferDirection.H2D,
1780
+ token_major=True,
1781
+ )
1782
+
1783
+ # free the buffer memory
1784
+ if self.use_gpu:
1785
+ tmp_gpu_buffer_obj.ref_count_down()
1786
+
1787
+ logger.debug(f"Finished loading layer {layer_id}")
1788
+ yield
1789
+
1790
+ @_lmcache_nvtx_annotate
1791
+ def batched_from_gpu(
1792
+ self,
1793
+ memory_objs: Union[List[List[MemoryObj]]],
1794
+ starts: List[int],
1795
+ ends: List[int],
1796
+ **kwargs,
1797
+ ):
1798
+ """
1799
+ This function is a generator that moves the KV cache from the paged GPU
1800
+ memory to the memory objects. The first iteration will prepare some
1801
+ related metadata and initiate the transfer in the first layer. In each
1802
+ of the following iterations, it will first wait until the storing of
1803
+ previous layer finishes, and then initiate string the KV cache of the
1804
+ current layer one. The storing process of the KV cache is paged GPU
1805
+ memory -> GPU buffer -> memory objects. The last iteration simply waits
1806
+ for the last layer to finish.
1807
+ In total, this the generator will yield num_layers + 1 times.
1808
+
1809
+ :param memory_objs: The memory objects to store the KV cache. The first
1810
+ dimension is the number of layers, and the second dimension is the
1811
+ number of memory objects (i.e., number of chunks) for each layer.
1812
+
1813
+ :param starts: The starting indices of the KV cache in the corresponding
1814
+ token sequence.
1815
+
1816
+ :param ends: The ending indices of the KV cache in the corresponding
1817
+ token sequence.
1818
+
1819
+ :raises ValueError: If 'slot_mapping' is not provided in kwargs.
1820
+ """
1821
+
1822
+ self.initialize_kvcaches_ptr(**kwargs)
1823
+ assert self.kvcaches is not None, (
1824
+ "kvcaches should be provided in kwargs or initialized beforehand."
1825
+ )
1826
+
1827
+ if "slot_mapping" not in kwargs:
1828
+ raise ValueError("'slot_mapping' should be provided in kwargs.")
1829
+
1830
+ if "sync" not in kwargs:
1831
+ raise ValueError("'sync' should be provided in kwargs.")
1832
+
1833
+ slot_mapping: torch.Tensor = kwargs["slot_mapping"]
1834
+
1835
+ self._lazy_initialize_buffer(self.kvcaches)
1836
+
1837
+ slot_mapping_chunks = []
1838
+ for start, end in zip(starts, ends, strict=False):
1839
+ slot_mapping_chunks.append(slot_mapping[start:end])
1840
+
1841
+ slot_mapping_full = torch.cat(slot_mapping_chunks, dim=0)
1842
+
1843
+ num_tokens = len(slot_mapping_full)
1844
+
1845
+ if self.use_gpu:
1846
+ buffer_shape = self.get_shape(num_tokens)
1847
+
1848
+ assert self.gpu_buffer_allocator is not None, (
1849
+ "GPU buffer allocator should be initialized"
1850
+ )
1851
+ tmp_gpu_buffer_obj = self.gpu_buffer_allocator.allocate(
1852
+ buffer_shape, self.dtype, MemoryFormat.KV_T2D
1853
+ )
1854
+ assert tmp_gpu_buffer_obj is not None, (
1855
+ "Failed to allocate GPU buffer in GPUConnector"
1856
+ )
1857
+ assert tmp_gpu_buffer_obj.tensor is not None
1858
+
1859
+ for layer_id in range(self.num_layers):
1860
+ memory_objs_layer = memory_objs[layer_id]
1861
+ # kvcaches -> gpu_buffer -> memobj
1862
+ if self.use_gpu:
1863
+ t, h, d = self.kvcaches[0][layer_id].shape
1864
+ lmc_ops.single_layer_kv_transfer_sgl(
1865
+ tmp_gpu_buffer_obj.tensor,
1866
+ self.kvcaches[0][layer_id].view(t, 1, h, d),
1867
+ self.kvcaches[1][layer_id].view(t, 1, h, d),
1868
+ slot_mapping_full,
1869
+ lmc_ops.TransferDirection.D2H,
1870
+ token_major=True,
1871
+ )
1872
+
1873
+ start_idx = 0
1874
+
1875
+ for start, end, memory_obj in zip(
1876
+ starts, ends, memory_objs_layer, strict=False
1877
+ ):
1878
+ assert memory_obj.tensor is not None
1879
+ if self.use_gpu:
1880
+ chunk_len = memory_obj.tensor.shape[0]
1881
+ memory_obj.tensor.copy_(
1882
+ tmp_gpu_buffer_obj.tensor[start_idx : start_idx + chunk_len],
1883
+ non_blocking=True,
1884
+ )
1885
+ start_idx += chunk_len
1886
+ else:
1887
+ lmc_ops.single_layer_kv_transfer_sgl(
1888
+ memory_obj.tensor,
1889
+ self.kvcaches[0][layer_id],
1890
+ self.kvcaches[1][layer_id],
1891
+ slot_mapping[start:end],
1892
+ lmc_ops.TransferDirection.D2H,
1893
+ token_major=True,
1894
+ )
1895
+
1896
+ yield
1897
+ logger.debug(f"Finished offloading layer {layer_id}")
1898
+
1899
+ # free the buffer memory
1900
+ if self.use_gpu:
1901
+ tmp_gpu_buffer_obj.ref_count_down()
1902
+ yield
1903
+
1904
+ def get_shape(self, num_tokens: int) -> torch.Size:
1905
+ # TODO: support MLA
1906
+ return torch.Size([num_tokens, 2, self.hidden_dim_size])