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,1424 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # This file contains Python non-CUDA fallback implementations for
4
+ # CUDA-specific operations.
5
+ #
6
+ # Standard
7
+ from concurrent.futures import ThreadPoolExecutor
8
+ from enum import Enum, IntEnum
9
+ from multiprocessing import shared_memory
10
+ from typing import Optional, Tuple
11
+ import ctypes
12
+ import ctypes.util
13
+
14
+ # Third Party
15
+ from numba import njit
16
+ import numpy as np
17
+ import torch
18
+
19
+ # Store the tensor objects in memory so that they can be accessed
20
+ # outside the scope of this file
21
+ _tensor_registry: dict[int, torch.Tensor] = {}
22
+ _shm_registry: dict[int, shared_memory.SharedMemory] = {}
23
+ _buf_registry: dict[int, ctypes.Array] = {}
24
+
25
+ # Cached copy library for lmcache_memcpy_async (lazy-initialized)
26
+ _copy_lib_NOT_LOADED = object()
27
+ _copy_lib: Optional[ctypes.CDLL] = _copy_lib_NOT_LOADED # type: ignore
28
+
29
+
30
+ def _get_copy_lib() -> Optional[ctypes.CDLL]:
31
+ """Lazily load and cache the CUDA/ROCm runtime library, or None for CPU fallback."""
32
+ global _copy_lib
33
+ if _copy_lib is _copy_lib_NOT_LOADED:
34
+ # Try to load GPU runtime libraries in priority order: CUDA first, then ROCm
35
+ # TODO: ROCm path to be validated on real device
36
+ for name, fallback in [
37
+ ("cudart", "libcudart.so"), # NVIDIA CUDA Runtime
38
+ ("amdhip64", "libamdhip64.so"), # AMD ROCm HIP Runtime
39
+ ]:
40
+ try:
41
+ path = ctypes.util.find_library(name)
42
+ if path:
43
+ _copy_lib = ctypes.CDLL(path)
44
+ else:
45
+ _copy_lib = ctypes.CDLL(fallback)
46
+ break # Successfully loaded, stop trying
47
+ except OSError:
48
+ continue # Current library not available, try next
49
+ else:
50
+ # All GPU libraries failed to load, fall back to CPU
51
+ _copy_lib = None
52
+ return _copy_lib
53
+
54
+
55
+ def _tensor_from_ptr(
56
+ ptr: int,
57
+ shape: tuple[int, ...],
58
+ dtype: torch.dtype,
59
+ device: torch.device | str | None = None,
60
+ ) -> torch.Tensor:
61
+ """
62
+ Create a tensor view over a raw pointer (zero-copy where possible).
63
+
64
+ Supports both CPU (pinned or regular) and CUDA device pointers.
65
+
66
+ Args:
67
+ ptr: Raw memory pointer as int (must be non-zero).
68
+ shape: Desired tensor shape.
69
+ dtype: Desired tensor dtype, must match the memory layout.
70
+ device: Where the pointer lives.
71
+ - None / "cpu" / torch.device("cpu") → CPU pointer
72
+ - "cuda" / "cuda:N" / torch.device("cuda", N) → CUDA pointer
73
+ If None and ptr looks like a CUDA ptr, pass device explicitly.
74
+
75
+ Returns:
76
+ A tensor that shares memory with the original pointer.
77
+ For CPU: always zero-copy via ctypes + torch.frombuffer.
78
+ For CUDA: zero-copy via torch._C._construct_storage_from_data_pointer
79
+ (PyTorch >= 2.0) or __cuda_array_interface__, with a
80
+ cudaMemcpy D2D fallback.
81
+
82
+ Raises:
83
+ ValueError: if ptr is 0.
84
+
85
+ Warning:
86
+ The caller is responsible for keeping the underlying memory alive
87
+ for the entire lifetime of the returned tensor.
88
+ """
89
+ if ptr == 0:
90
+ raise ValueError("Pointer must be non-zero")
91
+
92
+ # ------------------------------------------------------------------ #
93
+ # Normalise device #
94
+ # ------------------------------------------------------------------ #
95
+ if device is None:
96
+ device = torch.device("cpu")
97
+ elif not isinstance(device, torch.device):
98
+ device = torch.device(device)
99
+
100
+ assert isinstance(device, torch.device)
101
+ # ------------------------------------------------------------------ #
102
+ # Compute size #
103
+ # ------------------------------------------------------------------ #
104
+ numel = 1
105
+ for dim in shape:
106
+ numel *= int(dim)
107
+ element_size = torch.empty((), dtype=dtype).element_size()
108
+ total_bytes = numel * element_size
109
+
110
+ # ------------------------------------------------------------------ #
111
+ # CPU path #
112
+ # ------------------------------------------------------------------ #
113
+ if device.type == "cpu":
114
+ return _tensor_from_cpu_ptr(ptr, shape, dtype, numel, total_bytes)
115
+
116
+ # ------------------------------------------------------------------ #
117
+ # CUDA path #
118
+ # ------------------------------------------------------------------ #
119
+ if device.type == "cuda":
120
+ return _tensor_from_cuda_ptr(ptr, shape, dtype, device, numel, total_bytes)
121
+
122
+ raise ValueError(
123
+ f"Unsupported device type: {device.type!r}. Expected 'cpu' or 'cuda'."
124
+ )
125
+
126
+
127
+ # ====================================================================== #
128
+ # CPU implementation #
129
+ # ====================================================================== #
130
+
131
+
132
+ def _tensor_from_cpu_ptr(
133
+ ptr: int,
134
+ shape: tuple[int, ...],
135
+ dtype: torch.dtype,
136
+ numel: int,
137
+ total_bytes: int,
138
+ ) -> torch.Tensor:
139
+ """
140
+ Zero-copy CPU tensor from a raw host pointer via ctypes + torch.frombuffer.
141
+
142
+ """
143
+ buffer_type = ctypes.c_uint8 * total_bytes
144
+ buf = buffer_type.from_address(ptr)
145
+ # torch.frombuffer is zero-copy for contiguous byte buffers on CPU.
146
+ return torch.frombuffer(buf, dtype=dtype).view(*shape)
147
+
148
+
149
+ # ====================================================================== #
150
+ # CUDA implementation #
151
+ # ====================================================================== #
152
+ def _tensor_from_cuda_ptr(
153
+ ptr: int,
154
+ shape: tuple[int, ...],
155
+ dtype: torch.dtype,
156
+ device: torch.device,
157
+ numel: int,
158
+ total_bytes: int,
159
+ ) -> torch.Tensor:
160
+ """Zero-copy CUDA tensor from a raw device pointer."""
161
+
162
+ try:
163
+ _DTYPE_TO_TYPESTR = {
164
+ torch.float16: "<f2",
165
+ torch.float32: "<f4",
166
+ torch.float64: "<f8",
167
+ torch.int8: "|i1",
168
+ torch.int16: "<i2",
169
+ torch.int32: "<i4",
170
+ torch.int64: "<i8",
171
+ torch.uint8: "|u1",
172
+ torch.bool: "|b1",
173
+ }
174
+ is_bf16 = dtype == torch.bfloat16
175
+
176
+ # Determine the correct typestr, smuggle bfloat16 as int16
177
+ typestr = "<i2" if is_bf16 else _DTYPE_TO_TYPESTR.get(dtype, "|u1")
178
+
179
+ class _CudaArrayWrapper:
180
+ def __init__(self, ptr_int: int, shape_tuple: tuple, type_str: str):
181
+ self.__cuda_array_interface__ = {
182
+ "data": (ptr_int, False),
183
+ "shape": shape_tuple,
184
+ "typestr": type_str,
185
+ "version": 3,
186
+ }
187
+
188
+ t = torch.as_tensor(_CudaArrayWrapper(ptr, (numel,), typestr), device=device)
189
+ if is_bf16:
190
+ t = t.view(torch.bfloat16)
191
+
192
+ return t.view(*shape)
193
+ except Exception:
194
+ pass
195
+
196
+ # Strategy 2: cudaMemcpy Device-to-Device (Fallback)
197
+ libcudart = _get_copy_lib()
198
+ if libcudart is None:
199
+ raise RuntimeError("Failed to load libcudart/libamdhip")
200
+
201
+ cudaMemcpy = libcudart.cudaMemcpy
202
+ cudaMemcpy.restype = ctypes.c_int
203
+ cudaMemcpy.argtypes = [
204
+ ctypes.c_void_p,
205
+ ctypes.c_void_p,
206
+ ctypes.c_size_t,
207
+ ctypes.c_int,
208
+ ]
209
+ _MEMCPY_D2D = 3
210
+
211
+ dst = torch.empty(numel, dtype=dtype, device=device)
212
+
213
+ err = cudaMemcpy(
214
+ ctypes.c_void_p(dst.data_ptr()),
215
+ ctypes.c_void_p(ptr),
216
+ ctypes.c_size_t(total_bytes),
217
+ ctypes.c_int(_MEMCPY_D2D),
218
+ )
219
+ if err != 0:
220
+ raise RuntimeError(f"cudaMemcpy D2D failed with error code {err}.")
221
+
222
+ return dst.view(*shape)
223
+
224
+
225
+ def _copy_bytes_with_tensor(dst: int, src: int, num_bytes: int) -> None:
226
+ """Copy raw bytes between pointers using torch tensor semantics.
227
+
228
+ Note: This function only works for CPU-accessible memory. For device
229
+ memory (CUDA/XPU), use lmcache_memcpy_async with the appropriate runtime
230
+ library or PyTorch's tensor copy operations.
231
+ """
232
+ if num_bytes <= 0:
233
+ return
234
+
235
+ buffer_type = ctypes.c_uint8 * num_bytes
236
+ dst_tensor = torch.frombuffer(buffer_type.from_address(dst), dtype=torch.uint8)
237
+ src_tensor = torch.frombuffer(buffer_type.from_address(src), dtype=torch.uint8)
238
+ dst_tensor.copy_(src_tensor)
239
+
240
+
241
+ class TransferDirection(Enum):
242
+ """Specifies the direction of a memory transfer."""
243
+
244
+ H2D = 0
245
+ D2H = 1
246
+
247
+
248
+ class GPUKVFormat(IntEnum):
249
+ """Enumeration of different GPU KV cache memory layouts."""
250
+
251
+ # used by: vLLM CROSS_LAYER mode
252
+ NB_NL_TWO_BS_NH_HS = 0
253
+
254
+ # used by: vLLM non-MLA flash attention
255
+ NL_X_TWO_NB_BS_NH_HS = 1
256
+
257
+ # used by: vLLM non-MLA flash infer
258
+ NL_X_NB_TWO_BS_NH_HS = 2
259
+
260
+ # used by: vLLM MLA
261
+ NL_X_NB_BS_HS = 3
262
+
263
+ # used by: SGLang MHA (flash attention and flash infer)
264
+ TWO_X_NL_X_NBBS_NH_HS = 4
265
+
266
+ # used by: SGLang MLA
267
+ NL_X_NBBS_ONE_HS = 5
268
+
269
+ # used by: vLLM non-MLA flash attention (HND layout)
270
+ NL_X_TWO_NB_NH_BS_HS = 6
271
+
272
+ # used by: vLLM non-MLA flash infer (HND layout)
273
+ NL_X_NB_TWO_NH_BS_HS = 7
274
+
275
+
276
+ # On XPU (Intel GPU), PyTorch 2.4+ supports pin_memory=True via SYCL USM
277
+ # host allocation, enabling fast DMA for XPU<->CPU transfers.
278
+ _XPU_PIN_MEMORY = hasattr(torch, "xpu") and torch.xpu.is_available()
279
+
280
+
281
+ def alloc_pinned_numa_ptr(size: int, numa_id: int = 0) -> int:
282
+ """Non-CUDA equivalent of allocating pinned memory with NUMA awareness.
283
+ On XPU, uses pin_memory=True (SYCL USM host allocation) for fast transfers.
284
+ Note: NUMA node selection is not supported on non-CUDA."""
285
+
286
+ # Create a 1D uint8 CPU tensor, as uint8 == 1 byte
287
+ tensor = torch.empty(size, dtype=torch.uint8, pin_memory=_XPU_PIN_MEMORY)
288
+
289
+ # First-touch initialization (forces physical allocation)
290
+ tensor.fill_(0)
291
+
292
+ # Get a pointer to the start of the tensor object as this is what is
293
+ # returned by the CUDA equivalent function
294
+ ptr = tensor.data_ptr()
295
+
296
+ # Store the tensor so it can be accessed outide this function scope
297
+ _tensor_registry[ptr] = tensor
298
+
299
+ return ptr
300
+
301
+
302
+ def free_pinned_numa_ptr(ptr: int, size: int | None = None) -> None:
303
+ """Non-CUDA equivalent of freeing a previously allocated NUMA pointer."""
304
+
305
+ # Release the tensor object for that pointer reference
306
+ _tensor_registry.pop(ptr, None)
307
+
308
+
309
+ def alloc_pinned_ptr(size: int, device_id: int = 0) -> int:
310
+ """Non-CUDA equivalent of allocating pinned memory and returning pointer
311
+ to it. On XPU, uses pin_memory=True (SYCL USM host allocation) for
312
+ fast DMA transfers. On other non-CUDA platforms, pinning is not supported."""
313
+
314
+ # Create a 1D uint8 CPU tensor, as uint8 == 1 byte
315
+ tensor = torch.empty(size, dtype=torch.uint8, pin_memory=_XPU_PIN_MEMORY)
316
+
317
+ # First-touch initialization (forces physical allocation)
318
+ tensor.fill_(0)
319
+
320
+ # Get a pointer to the start of the tensor object as this is what is
321
+ # returned by the CUDA equivalent function
322
+ ptr = tensor.data_ptr()
323
+
324
+ # Store the tensor so it can be accessed outide this function scope
325
+ _tensor_registry[ptr] = tensor
326
+
327
+ return ptr
328
+
329
+
330
+ def free_pinned_ptr(ptr: int) -> None:
331
+ """Non-CUDA equivalent of freeing a previously allocated pinned pointer."""
332
+
333
+ # Release the tensor object for that pointer reference
334
+ _tensor_registry.pop(ptr, None)
335
+
336
+
337
+ def batched_memcpy(src_ptrs: list[int], dst_ptrs: list[int], sizes: list[int]) -> None:
338
+ """Non-CUDA equivalent of the native batched memcpy helper."""
339
+
340
+ if len(src_ptrs) != len(dst_ptrs) or len(src_ptrs) != len(sizes):
341
+ raise ValueError(
342
+ "batched_memcpy expects equally sized src_ptrs, dst_ptrs, and sizes"
343
+ )
344
+
345
+ for src_ptr, dst_ptr, size in zip(src_ptrs, dst_ptrs, sizes, strict=True):
346
+ if size <= 0:
347
+ continue
348
+ ctypes.memmove(
349
+ ctypes.c_void_p(dst_ptr),
350
+ ctypes.c_void_p(src_ptr),
351
+ size,
352
+ )
353
+
354
+
355
+ def alloc_shm_pinned_ptr(size: int, shm_name: str = "") -> int:
356
+ """Non-CUDA equivalent of allocating shared memory pinned pointer.
357
+ Uses multiprocessing.shared_memory for cross-platform POSIX shm."""
358
+
359
+ # Strip leading '/' for SharedMemory name
360
+ name = shm_name.lstrip("/") if shm_name else None
361
+
362
+ # Clean up stale shm segment if it exists
363
+ if name:
364
+ try:
365
+ stale = shared_memory.SharedMemory(name=name, create=False)
366
+ stale.close()
367
+ stale.unlink()
368
+ except FileNotFoundError:
369
+ pass
370
+
371
+ shm = shared_memory.SharedMemory(name=name, create=True, size=size)
372
+
373
+ array_type = ctypes.c_uint8 * size
374
+ buf = array_type.from_buffer(shm.buf)
375
+ ptr = ctypes.addressof(buf)
376
+
377
+ # Store references to keep them alive
378
+ tensor = torch.frombuffer(buf, dtype=torch.uint8)
379
+ _tensor_registry[ptr] = tensor
380
+ _buf_registry[ptr] = buf
381
+ _shm_registry[ptr] = shm
382
+ return ptr
383
+
384
+
385
+ def free_shm_pinned_ptr(ptr: int, size: int = 0, shm_name: str = "") -> None:
386
+ """Non-CUDA equivalent of freeing a shared memory
387
+ pinned pointer."""
388
+
389
+ # Release in order: tensor -> ctypes buf -> shm
390
+ _tensor_registry.pop(ptr, None)
391
+ _buf_registry.pop(ptr, None)
392
+ shm = _shm_registry.pop(ptr, None)
393
+ if shm is not None:
394
+ shm.close()
395
+ shm.unlink()
396
+
397
+
398
+ def alloc_numa_ptr(size: int, numa_id: int = 0) -> int:
399
+ """Non-CUDA equivalent of allocating numa memory and returning pointer
400
+ to it. Note: Numa memory is not supported on non-CUDA."""
401
+ return alloc_pinned_numa_ptr(size, numa_id)
402
+
403
+
404
+ def free_numa_ptr(ptr: int, size: int | None = None) -> None:
405
+ """Non-CUDA equivalent of freeing a previously allocated NUMA pointer."""
406
+ return free_pinned_numa_ptr(ptr, size)
407
+
408
+
409
+ def multi_layer_kv_transfer(
410
+ key_value: torch.Tensor,
411
+ key_value_ptrs: torch.Tensor | list[torch.Tensor],
412
+ slot_mapping: torch.Tensor,
413
+ paged_memory_device: torch.device,
414
+ page_buffer_size: int,
415
+ direction: TransferDirection,
416
+ gpu_kv_format: GPUKVFormat,
417
+ block_size: int = 0,
418
+ head_size: int = 0,
419
+ skip_prefix_n_tokens: int = 0,
420
+ ):
421
+ """
422
+ Fully vectorized Python fallback for multi_layer_kv_transfer.
423
+ Eliminates ALL token- and KV-level Python loops.
424
+ """
425
+ if not isinstance(key_value_ptrs, (torch.Tensor, list)):
426
+ raise TypeError(
427
+ f"Expected torch.Tensor or list, but got {type(key_value_ptrs).__name__}"
428
+ )
429
+
430
+ # TODO: Implement head_size support for HND layouts (NL_X_TWO_NB_NH_BS_HS,
431
+ # NL_X_NB_TWO_NH_BS_HS) as next step.
432
+ if gpu_kv_format in (
433
+ GPUKVFormat.NL_X_TWO_NB_NH_BS_HS,
434
+ GPUKVFormat.NL_X_NB_TWO_NH_BS_HS,
435
+ ):
436
+ raise NotImplementedError(
437
+ "HND layouts (NL_X_TWO_NB_NH_BS_HS, NL_X_NB_TWO_NH_BS_HS) "
438
+ "are not supported in the non-CUDA fallback. "
439
+ "head_size parameter is required but not implemented in this path."
440
+ )
441
+
442
+ # 1. Filter out invalid slots.
443
+ # valid_mask_kv: on key_value.device, used to index key_value
444
+ # valid_slots: on paged_memory_device, used to index paged_tensor
445
+ kv_device = key_value.device
446
+ slots_kv = slot_mapping.to(dtype=torch.long).to(kv_device)
447
+ valid_mask_kv = slots_kv >= 0
448
+ # Skip the first skip_prefix_n_tokens tokens from transfer.
449
+ # This matches the CUDA kernel semantics where the grid starts at
450
+ # token_id=0 but indexes key_value/slot_mapping at
451
+ # kv_token_id = token_id + skip_prefix_n_tokens.
452
+ # By masking them as invalid, the vectorized indexing via valid_mask_kv
453
+ # naturally skips them while keeping key_value indices aligned.
454
+ if skip_prefix_n_tokens > 0:
455
+ valid_mask_kv[:skip_prefix_n_tokens] = False
456
+ if not valid_mask_kv.any():
457
+ return
458
+
459
+ valid_slots = slots_kv[valid_mask_kv].to(paged_memory_device)
460
+
461
+ # 2. Determine architecture variant and tensor dimensions.
462
+ is_mla = gpu_kv_format in (
463
+ GPUKVFormat.NL_X_NB_BS_HS,
464
+ GPUKVFormat.NL_X_NBBS_ONE_HS,
465
+ )
466
+ is_flash_infer = gpu_kv_format == GPUKVFormat.NL_X_NB_TWO_BS_NH_HS
467
+
468
+ num_layers = key_value.size(1)
469
+ hidden_size = key_value.size(3)
470
+
471
+ # For the flash_infer interleaved layout, pre-compute block-level indices.
472
+ if is_flash_infer:
473
+ block_indices = valid_slots // block_size
474
+ block_offsets = valid_slots % block_size
475
+
476
+ # Determine the physical shape of the underlying paged tensor
477
+ # (used when wrapping a raw pointer).
478
+ layer_shape: Tuple[int, ...]
479
+
480
+ if is_mla:
481
+ layer_shape = (page_buffer_size, hidden_size)
482
+ elif is_flash_infer:
483
+ num_blocks = page_buffer_size // block_size
484
+ layer_shape = (num_blocks, 2, block_size, hidden_size)
485
+ else:
486
+ layer_shape = (2, page_buffer_size, hidden_size)
487
+
488
+ # 3. Iterate over layers — the only remaining Python-level loop.
489
+ for layer_id in range(num_layers):
490
+ # --- A. Obtain the physical device-memory view for this layer. ---
491
+ if isinstance(key_value_ptrs, list):
492
+ paged_tensor = key_value_ptrs[layer_id]
493
+ else:
494
+ ptr = int(key_value_ptrs[layer_id].item())
495
+ # Convert a raw device pointer into a PyTorch tensor view.
496
+ paged_tensor = _tensor_from_ptr(
497
+ ptr, layer_shape, key_value.dtype, paged_memory_device
498
+ )
499
+
500
+ # --- B. Vectorized bulk data transfer. ---
501
+ if is_mla:
502
+ # Paged layout : [page_buffer_size, hidden_size]
503
+ # key_value layout: [1, num_layers, num_tokens, hidden_size]
504
+ if direction == TransferDirection.H2D:
505
+ lmc_valid = key_value[0, layer_id, valid_mask_kv, :]
506
+ paged_tensor.index_copy_(
507
+ 0, valid_slots, lmc_valid.to(paged_tensor.device)
508
+ )
509
+ else:
510
+ gathered = paged_tensor.index_select(0, valid_slots)
511
+ key_value[0, layer_id, valid_mask_kv, :] = gathered.to(
512
+ kv_device, non_blocking=False
513
+ )
514
+ elif is_flash_infer:
515
+ # Paged layout : [num_blocks, 2, block_size, hidden_size]
516
+ # key_value layout: [2, num_layers, num_tokens, hidden_size]
517
+ if direction == TransferDirection.H2D:
518
+ lmc_valid = key_value[:, layer_id, valid_mask_kv, :]
519
+ src_data = lmc_valid.transpose(0, 1).to(paged_memory_device)
520
+ # src_data: [num_valid, 2, hidden_size]
521
+ paged_tensor[block_indices, :, block_offsets, :] = src_data
522
+ else:
523
+ gathered = paged_tensor[block_indices, :, block_offsets, :]
524
+ # gathered: [num_valid, 2, hidden_size]
525
+ key_value[:, layer_id, valid_mask_kv, :] = gathered.to(
526
+ kv_device, non_blocking=False
527
+ ).transpose(0, 1)
528
+ else:
529
+ # Paged layout : [2, page_buffer_size, hidden_size]
530
+ # key_value layout: [2, num_layers, num_tokens, hidden_size]
531
+ if direction == TransferDirection.H2D:
532
+ lmc_valid = key_value[:, layer_id, valid_mask_kv, :]
533
+ paged_tensor.index_copy_(
534
+ 1, valid_slots, lmc_valid.to(paged_memory_device)
535
+ )
536
+ else:
537
+ gathered = paged_tensor.index_select(1, valid_slots)
538
+ key_value[:, layer_id, valid_mask_kv, :] = gathered.to(
539
+ kv_device, non_blocking=False
540
+ )
541
+
542
+
543
+ def multi_layer_kv_transfer_unilateral(
544
+ key_value: torch.Tensor,
545
+ key_value_ptrs: torch.Tensor | list[torch.Tensor],
546
+ slot_mapping: torch.Tensor,
547
+ paged_memory_device: torch.device,
548
+ page_buffer_size: int,
549
+ direction: TransferDirection,
550
+ gpu_kv_format: GPUKVFormat,
551
+ ):
552
+ """
553
+ Python fallback for multi_layer_kv_transfer_unilateral
554
+
555
+ Handles SGLang MHA format where K and V paged buffers are stored separately:
556
+ ptrs = [K_layer0, K_layer1, ..., V_layer0, V_layer1, ...]
557
+ each buffer shape: [page_buffer_size, hidden_size]
558
+
559
+ For MLA, delegates to multi_layer_kv_transfer (same as C++ implementation).
560
+
561
+ key_value_ptrs:
562
+ - If torch.Tensor: int64 tensor containing raw memory pointers.
563
+ - If list[torch.Tensor]: list of tensor objects.
564
+
565
+ key_value layout:
566
+ - Standard: [2, num_layers, num_tokens, hidden_size]
567
+ - MLA: [1, num_layers, num_tokens, hidden_size]
568
+
569
+ direction:
570
+ H2D = LMCache -> PagedBuffer
571
+ D2H = PagedBuffer -> LMCache
572
+ """
573
+ is_mla = gpu_kv_format in (
574
+ GPUKVFormat.NL_X_NB_BS_HS,
575
+ GPUKVFormat.NL_X_NBBS_ONE_HS,
576
+ )
577
+
578
+ # MLA case collapses back to multi_layer_kv_transfer
579
+ # (vLLM and SGLang indexing are compatible)
580
+ if is_mla:
581
+ return multi_layer_kv_transfer(
582
+ key_value,
583
+ key_value_ptrs,
584
+ slot_mapping,
585
+ paged_memory_device,
586
+ page_buffer_size,
587
+ direction,
588
+ gpu_kv_format,
589
+ 0, # block_size unused for MLA formats
590
+ )
591
+
592
+ # ── Non-MLA path: unilateral (separate K/V buffers per layer) ──
593
+ num_layers = key_value.size(1)
594
+ hidden_size = key_value.size(3)
595
+ layer_shape = (page_buffer_size, hidden_size)
596
+
597
+ kv_device = key_value.device
598
+ slots_kv = slot_mapping.to(dtype=torch.long).to(kv_device)
599
+ valid_mask_kv = slots_kv >= 0
600
+ if not valid_mask_kv.any():
601
+ return
602
+
603
+ valid_slots = slots_kv[valid_mask_kv].to(paged_memory_device)
604
+
605
+ for layer_id in range(num_layers):
606
+ for kv_idx in range(2): # 0 = K, 1 = V
607
+ buffer_idx = layer_id + kv_idx * num_layers
608
+ if isinstance(key_value_ptrs, list):
609
+ paged_tensor = key_value_ptrs[buffer_idx]
610
+ else:
611
+ ptr = int(key_value_ptrs[buffer_idx].item())
612
+ paged_tensor = _tensor_from_ptr(
613
+ ptr, layer_shape, key_value.dtype, paged_memory_device
614
+ )
615
+
616
+ if direction == TransferDirection.H2D:
617
+ lmc_valid = key_value[kv_idx, layer_id, valid_mask_kv, :]
618
+ paged_tensor.index_copy_(
619
+ 0, valid_slots, lmc_valid.to(paged_memory_device)
620
+ )
621
+ else:
622
+ gathered = paged_tensor.index_select(0, valid_slots)
623
+ key_value[kv_idx, layer_id, valid_mask_kv, :] = gathered.to(kv_device)
624
+
625
+
626
+ def single_layer_kv_transfer(
627
+ lmc_key_value_cache: torch.Tensor,
628
+ vllm_key_value_cache: torch.Tensor,
629
+ slot_mapping: torch.Tensor,
630
+ direction: TransferDirection,
631
+ gpu_kv_format: GPUKVFormat,
632
+ token_major: bool = False,
633
+ ):
634
+ """
635
+ Vectorized Python fallback for single_layer_kv_transfer
636
+ (eliminates per-token loops).
637
+
638
+ Transfers KV data between LMCache buffer
639
+ and a single vLLM paged KV cache layer.
640
+
641
+ lmc_key_value_cache layout:
642
+ - MLA: [num_tokens, aligned_head_size]
643
+ - token_major=True: [num_tokens, 2, num_heads * head_size]
644
+ - token_major=False: [2, num_tokens, num_heads * head_size]
645
+
646
+ vllm_key_value_cache layout:
647
+ - NL_X_TWO_NB_BS_NH_HS (flash attn):
648
+ [2, num_blocks, block_size, num_heads, head_size]
649
+ - NL_X_NB_TWO_BS_NH_HS (flash infer):
650
+ [num_blocks, 2, block_size, num_heads, head_size]
651
+ - NL_X_NB_BS_HS (vLLM MLA):
652
+ [num_blocks, block_size, head_size]
653
+
654
+ direction:
655
+ H2D = LMCache -> vLLM GPU
656
+ D2H = vLLM GPU -> LMCache
657
+ """
658
+ kv_device = lmc_key_value_cache.device
659
+ paged_memory_device = vllm_key_value_cache.device
660
+ slots_kv = slot_mapping.to(dtype=torch.long).to(kv_device)
661
+ valid_mask_kv = slots_kv >= 0
662
+
663
+ if not valid_mask_kv.any():
664
+ return
665
+
666
+ valid_token_indices = torch.nonzero(valid_mask_kv, as_tuple=True)[0]
667
+ valid_slots = slots_kv[valid_mask_kv].to(paged_memory_device)
668
+
669
+ is_mla = gpu_kv_format in (
670
+ GPUKVFormat.NL_X_NB_BS_HS,
671
+ GPUKVFormat.NL_X_NBBS_ONE_HS,
672
+ )
673
+
674
+ if is_mla:
675
+ # ── MLA format ──
676
+ # vllm: [num_blocks, block_size, head_size]
677
+ # lmc: [num_tokens, aligned_head_size]
678
+ block_size = vllm_key_value_cache.size(1)
679
+ block_indices = valid_slots // block_size
680
+ block_offsets = valid_slots % block_size
681
+
682
+ if direction == TransferDirection.D2H:
683
+ # vLLM -> LMCache
684
+ lmc_key_value_cache[valid_token_indices] = vllm_key_value_cache[
685
+ block_indices, block_offsets
686
+ ].to(lmc_key_value_cache.device)
687
+ else:
688
+ # LMCache -> vLLM
689
+ vllm_key_value_cache[block_indices, block_offsets] = lmc_key_value_cache[
690
+ valid_token_indices
691
+ ].to(paged_memory_device)
692
+
693
+ else:
694
+ # ── Non-MLA format ──
695
+ # Determine vLLM layout and block_size
696
+ is_two_major = gpu_kv_format == GPUKVFormat.NL_X_TWO_NB_BS_NH_HS
697
+ # flash attn:
698
+ # [2, num_blocks, block_size, num_heads, head_size]
699
+ # -> dim2 = block_size
700
+ # flash infer:
701
+ # [num_blocks, 2, block_size, num_heads, head_size]
702
+ # -> dim2 = block_size
703
+ block_size = vllm_key_value_cache.size(2)
704
+ num_heads = vllm_key_value_cache.size(3)
705
+ head_size = vllm_key_value_cache.size(4)
706
+ block_indices = valid_slots // block_size
707
+ block_offsets = valid_slots % block_size
708
+
709
+ for kv in range(2):
710
+ if direction == TransferDirection.D2H:
711
+ if is_two_major:
712
+ gathered = vllm_key_value_cache[kv, block_indices, block_offsets]
713
+ else:
714
+ gathered = vllm_key_value_cache[block_indices, kv, block_offsets]
715
+
716
+ gathered_flat = gathered.reshape(-1, num_heads * head_size).to(
717
+ lmc_key_value_cache.device
718
+ )
719
+ if token_major:
720
+ lmc_key_value_cache[valid_token_indices, kv] = gathered_flat
721
+ else:
722
+ lmc_key_value_cache[kv, valid_token_indices] = gathered_flat
723
+ else:
724
+ if token_major:
725
+ lmc_src = lmc_key_value_cache[valid_token_indices, kv]
726
+ else:
727
+ lmc_src = lmc_key_value_cache[kv, valid_token_indices]
728
+ lmc_reshaped = lmc_src.reshape(-1, num_heads, head_size).to(
729
+ vllm_key_value_cache.device
730
+ )
731
+
732
+ if is_two_major:
733
+ vllm_key_value_cache[kv, block_indices, block_offsets] = (
734
+ lmc_reshaped
735
+ )
736
+ else:
737
+ vllm_key_value_cache[block_indices, kv, block_offsets] = (
738
+ lmc_reshaped
739
+ )
740
+
741
+
742
+ def single_layer_kv_transfer_sgl(
743
+ lmc_key_value_cache: torch.Tensor,
744
+ sgl_key_cache: torch.Tensor,
745
+ sgl_value_cache: torch.Tensor,
746
+ slot_mapping: torch.Tensor,
747
+ direction: TransferDirection,
748
+ token_major: bool = False,
749
+ ):
750
+ """
751
+ Python fallback implementation of single_layer_kv_transfer_sgl.
752
+
753
+ Args:
754
+ lmc_key_value_cache:
755
+ [num_tokens, 2, num_heads*head_size] or
756
+ [2, num_tokens, num_heads*head_size]
757
+ sgl_key_cache: [num_blocks, block_size, num_heads, head_size]
758
+ sgl_value_cache: [num_blocks, block_size, num_heads, head_size]
759
+ slot_mapping: [num_tokens] - maps each token to a global slot index
760
+ direction: False for LMCache -> SGLang, True for SGLang -> LMCache
761
+ token_major: Boolean to determine the layout of lmc_key_value_cache
762
+ """
763
+ kv_device = lmc_key_value_cache.device
764
+ paged_memory_device = sgl_key_cache.device
765
+ slots_kv = slot_mapping.to(dtype=torch.long).to(kv_device)
766
+ valid_mask_kv = slots_kv >= 0
767
+ if not valid_mask_kv.any():
768
+ return
769
+
770
+ # 1. Get basic dimensions
771
+ block_size = sgl_key_cache.size(1)
772
+ num_heads = sgl_key_cache.size(2)
773
+ head_size = sgl_key_cache.size(3)
774
+
775
+ # 2. Calculate block indices and offsets within the blocks from slot_mapping
776
+ # In SGLang/vLLM, slot_idx = block_idx * block_size + block_offset
777
+ valid_slots = slots_kv[valid_mask_kv].to(paged_memory_device)
778
+ block_indices = valid_slots // block_size
779
+ block_offsets = valid_slots % block_size
780
+
781
+ # 3. Prepare LMCache views for K and V
782
+ if token_major:
783
+ # Layout: [num_tokens, 2, hidden_size]
784
+ lmc_k = lmc_key_value_cache[:, 0, :]
785
+ lmc_v = lmc_key_value_cache[:, 1, :]
786
+ else:
787
+ # Layout: [2, num_tokens, hidden_size]
788
+ lmc_k = lmc_key_value_cache[0, :, :]
789
+ lmc_v = lmc_key_value_cache[1, :, :]
790
+
791
+ # 4. Perform the transfer
792
+ if direction == TransferDirection.H2D:
793
+ # --- Direction: LMCache to SGLang (Paged Buffer) ---
794
+ # Reshape LMC flat tensors to match SGL [num_heads, head_size]
795
+ src_k_reshaped = (
796
+ lmc_k[valid_mask_kv]
797
+ .reshape(-1, num_heads, head_size)
798
+ .to(paged_memory_device)
799
+ )
800
+ src_v_reshaped = (
801
+ lmc_v[valid_mask_kv]
802
+ .reshape(-1, num_heads, head_size)
803
+ .to(paged_memory_device)
804
+ )
805
+
806
+ # Advanced indexing: update specific slots in the paged cache
807
+ sgl_key_cache[block_indices, block_offsets] = src_k_reshaped
808
+ sgl_value_cache[block_indices, block_offsets] = src_v_reshaped
809
+
810
+ else:
811
+ # --- Direction: SGLang (Paged Buffer) to LMCache ---
812
+ # Gather tensors from paged cache based on mapping
813
+ sampled_k = sgl_key_cache[block_indices, block_offsets].to(kv_device)
814
+ sampled_v = sgl_value_cache[block_indices, block_offsets].to(kv_device)
815
+
816
+ # Flatten the head dimensions and copy into LMC tensors
817
+ lmc_k[valid_mask_kv] = sampled_k.reshape(-1, num_heads * head_size)
818
+ lmc_v[valid_mask_kv] = sampled_v.reshape(-1, num_heads * head_size)
819
+
820
+
821
+ def load_and_reshape_flash(
822
+ key_value: torch.Tensor,
823
+ # Destination (Dst): Pinned CPU Tensor [2, L, T, H]
824
+ key_cache: torch.Tensor,
825
+ # Source (Src): GPU Cache [Blocks, BlockSize, NumHeads, HeadSize]
826
+ value_cache: torch.Tensor, # Source (Src): GPU Cache
827
+ slot_mapping: torch.Tensor, # Mapping indices [num_tokens]
828
+ layer_idx: int,
829
+ ):
830
+ """
831
+ Python equivalent of load_and_reshape_flash.
832
+ Note: In the context of 'test_extract_and_load_back', this function performs
833
+ an EXTRACT operation (Reads from GPU Cache and writes to Pinned CPU memory).
834
+ """
835
+ # 1. Prepare indices on the target device
836
+ # Mapping must be on the same GPU as the cache to perform indexing
837
+ device = key_cache.device
838
+ slot_mapping = slot_mapping.to(device=device, dtype=torch.long)
839
+
840
+ block_size = key_cache.size(1)
841
+
842
+ # Calculate physical locations within the paged cache
843
+ block_indices = torch.div(slot_mapping, block_size, rounding_mode="floor")
844
+ block_offsets = slot_mapping % block_size
845
+
846
+ # 2. Extract data from Cache (Gather operation)
847
+ # The result k_out/v_out will be on the GPU
848
+ # Shape: [num_tokens, num_heads, head_size]
849
+ k_out = key_cache[block_indices, block_offsets]
850
+ v_out = value_cache[block_indices, block_offsets]
851
+
852
+ # 3. Write to the destination tensor (CPU Copy)
853
+ # Target shape: [2, num_layers, num_tokens, hidden_dim]
854
+
855
+ # Flatten heads into the hidden dimension: [T, NumHeads, HeadSize] -> [T, HiddenDim]
856
+ hidden_dim = k_out.shape[1] * k_out.shape[2]
857
+
858
+ # Assignment automatically handles the Device-to-Host (D2H) transfer
859
+ key_value[0, layer_idx] = k_out.view(-1, hidden_dim)
860
+ key_value[1, layer_idx] = v_out.view(-1, hidden_dim)
861
+
862
+
863
+ def reshape_and_cache_back_flash(
864
+ key_value: torch.Tensor,
865
+ # Source: [2, num_layer, num_tokens, num_heads * head_size]
866
+ # (Can be on CPU/Pinned Memory or GPU)
867
+ key_cache: torch.Tensor,
868
+ # Destination: [num_blocks, block_size, num_heads, head_size]
869
+ # (Must be on GPU)
870
+ value_cache: torch.Tensor, # Destination: (Must be on GPU)
871
+ slot_mapping: torch.Tensor, # Indices: [num_tokens]
872
+ layer_idx: int,
873
+ ):
874
+ """
875
+ Python implementation of reshape_and_cache_back_flash.
876
+
877
+ Operation:
878
+ Flat Tensor (Source) -> Paged Attention Cache (Destination)
879
+
880
+ Logic:
881
+ 1. Extract the specific layer's data from key_value.
882
+ 2. Move it to the GPU (if it's on CPU).
883
+ 3. Reshape it to match the cache's head structure.
884
+ 4. Scatter (write) it into the non-contiguous cache blocks using slot_mapping.
885
+ """
886
+
887
+ # 1. Setup Device & Dimensions
888
+ # The cache is on the GPU, so all indices and source data must eventually be there.
889
+ device = key_cache.device
890
+
891
+ block_size = key_cache.size(1)
892
+ num_heads = key_cache.size(2)
893
+ head_size = key_cache.size(3)
894
+
895
+ # 2. Prepare Indices
896
+ # slot_mapping might be on CPU, must move to GPU for indexing.
897
+ slot_mapping = slot_mapping.to(device=device, dtype=torch.long)
898
+
899
+ # Calculate physical block indices and offsets
900
+ block_indices = torch.div(slot_mapping, block_size, rounding_mode="floor")
901
+ block_offsets = slot_mapping % block_size
902
+
903
+ # 3. Process Source Data (Key)
904
+ # Step A: Slice the specific layer from the source tensor
905
+ # Source shape: [2, num_layers, num_tokens, hidden_dim] -> [num_tokens, hidden_dim]
906
+ k_src_flat = key_value[0, layer_idx]
907
+ v_src_flat = key_value[1, layer_idx]
908
+
909
+ # Step B: Reshape & Move to GPU
910
+ # .to(device) handles the CPU -> GPU transfer if key_value is in pinned memory.
911
+ # View shape: [num_tokens, num_heads, head_size]
912
+ k_src = k_src_flat.to(device).view(-1, num_heads, head_size)
913
+ v_src = v_src_flat.to(device).view(-1, num_heads, head_size)
914
+
915
+ # 4. Write to Cache (Scatter)
916
+ # Using Advanced Indexing to write data into specific blocks/offsets
917
+ key_cache[block_indices, block_offsets] = k_src
918
+ value_cache[block_indices, block_offsets] = v_src
919
+
920
+
921
+ def lmcache_memcpy_async(
922
+ dest: int | torch.Tensor,
923
+ src: int | torch.Tensor,
924
+ nbytes: int,
925
+ direction: TransferDirection,
926
+ host_buffer_offset: int,
927
+ host_buffer_alignments: int,
928
+ ):
929
+ """
930
+ Python fallback for lmcache_memcpy_async.
931
+
932
+ - Tensor mode (non-CUDA devices like HPU): uses .to(device) + copy_()
933
+ - Pointer mode with libcudart: uses synchronous cudaMemcpy (cudaMemcpyDefault)
934
+ - Pointer mode without libcudart: uses CPU tensor copy
935
+
936
+ Unlike the C++ version (which uses cudaMemcpyAsync and must split copies
937
+ at cudaHostRegister boundaries), this Python fallback does NOT need
938
+ alignment-based chunking because:
939
+ - cudaMemcpy (synchronous) handles cross-cudaHostRegister boundaries
940
+ internally via staging buffers
941
+ - CPU tensor copy has no alignment constraints
942
+ - Tensor mode bypasses raw pointers entirely
943
+
944
+ dest:
945
+ - If int: raw memory pointer (used for CUDA/CPU devices where we
946
+ work with pointers).
947
+ - If torch.Tensor: tensor object (used for non-CUDA/CPU devices
948
+ where we operate on tensor objects directly).
949
+
950
+ src:
951
+ - If int: raw memory pointer (used for CUDA/CPU devices where we
952
+ work with pointers).
953
+ - If torch.Tensor: tensor object (used for non-CUDA/CPU devices
954
+ where we operate on tensor objects directly).
955
+ """
956
+ # 1. Power of two check (kept for API compatibility)
957
+ if host_buffer_alignments <= 0 or (
958
+ host_buffer_alignments & (host_buffer_alignments - 1) != 0
959
+ ):
960
+ raise ValueError("host_buffer_alignments must be power of two")
961
+
962
+ # 2. Validate direction
963
+ if direction not in (TransferDirection.H2D, TransferDirection.D2H):
964
+ raise ValueError(f"Unsupported direction: {direction}")
965
+
966
+ # 3. Tensor-backed mode.
967
+ # Mixed pointer/tensor are not allowed
968
+ if isinstance(dest, torch.Tensor) or isinstance(src, torch.Tensor):
969
+ if not (isinstance(dest, torch.Tensor) and isinstance(src, torch.Tensor)):
970
+ raise TypeError(
971
+ "Mixed types are not allowed: both dest and src must be torch.Tensor "
972
+ "if either of them is a tensor."
973
+ )
974
+ if nbytes % dest.element_size() != 0:
975
+ raise ValueError("nbytes must align with tensor element size")
976
+
977
+ num_elements = nbytes // dest.element_size()
978
+
979
+ dest_slice = dest.flatten()[:num_elements]
980
+ src_slice = src.flatten()[:num_elements]
981
+
982
+ copied = src_slice.to(dest_slice.device)
983
+ dest_slice.copy_(copied)
984
+ return
985
+
986
+ # 4. Pointer mode
987
+ if not isinstance(dest, int) or not isinstance(src, int):
988
+ raise TypeError(
989
+ "dest and src must be both int (pointer mode) "
990
+ "or both torch.Tensor (tensor mode)"
991
+ )
992
+
993
+ libcudart = _get_copy_lib()
994
+ if libcudart is not None and hasattr(libcudart, "cudaMemcpy"):
995
+ try:
996
+ # Synchronous cudaMemcpy handles cross-cudaHostRegister boundaries
997
+ # internally — no manual alignment splitting needed.
998
+ ret = libcudart.cudaMemcpy(
999
+ ctypes.c_void_p(dest),
1000
+ ctypes.c_void_p(src),
1001
+ ctypes.c_size_t(nbytes),
1002
+ ctypes.c_int(4), # cudaMemcpyDefault
1003
+ )
1004
+ if ret != 0:
1005
+ raise RuntimeError(f"cudaMemcpy failed with error code {ret}")
1006
+ except AttributeError:
1007
+ raise
1008
+ else:
1009
+ # Pure CPU copy — no alignment constraints.
1010
+ _copy_bytes_with_tensor(dest, src, nbytes)
1011
+
1012
+
1013
+ @njit(cache=True)
1014
+ def _encode_single_channel(
1015
+ cdf_layer_c, # np.uint32 [lp]
1016
+ sym_channel, # np.uint8 [n_tokens]
1017
+ out_buf_lc, # np.uint8 [buffer_size]
1018
+ ):
1019
+ """Core arithmetic encoding for a single (layer, channel).
1020
+ Returns number of bytes written."""
1021
+ MASK32 = 0xFFFFFFFF
1022
+ precision = 16
1023
+ max_symbol = len(cdf_layer_c) - 2
1024
+ n_tokens = len(sym_channel)
1025
+
1026
+ low, high = 0, MASK32
1027
+ pending_bits = 0
1028
+ output_reg, output_reg_len = 0, 0
1029
+ ptr = 0
1030
+ buf_size = len(out_buf_lc)
1031
+
1032
+ # Inline flush_bit to avoid closure (numba does not support nonlocal)
1033
+ for token_idx in range(n_tokens):
1034
+ sym = int(sym_channel[token_idx])
1035
+ c_low = int(cdf_layer_c[sym])
1036
+ c_high = 0x10000 if sym == max_symbol else int(cdf_layer_c[sym + 1])
1037
+
1038
+ span = (high - low + 1) & MASK32
1039
+ if span == 0:
1040
+ span = 0x100000000
1041
+
1042
+ high = (low + ((span * c_high) >> precision) - 1) & MASK32
1043
+ low = (low + ((span * c_low) >> precision)) & MASK32
1044
+
1045
+ while True:
1046
+ if (high & 0x80000000) == (low & 0x80000000):
1047
+ # flush_bit(bit)
1048
+ bit = (high >> 31) & 1
1049
+ output_reg = (output_reg << 1) | bit
1050
+ output_reg_len += 1
1051
+ if output_reg_len == 8:
1052
+ if ptr < buf_size:
1053
+ out_buf_lc[ptr] = output_reg & 0xFF
1054
+ ptr += 1
1055
+ output_reg, output_reg_len = 0, 0
1056
+ # flush pending bits
1057
+ for _ in range(pending_bits):
1058
+ output_reg = (output_reg << 1) | (1 - bit)
1059
+ output_reg_len += 1
1060
+ if output_reg_len == 8:
1061
+ if ptr < buf_size:
1062
+ out_buf_lc[ptr] = output_reg & 0xFF
1063
+ ptr += 1
1064
+ output_reg, output_reg_len = 0, 0
1065
+ pending_bits = 0
1066
+ low = (low << 1) & MASK32
1067
+ high = ((high << 1) | 1) & MASK32
1068
+ elif (low & 0x40000000) != 0 and (high & 0x40000000) == 0:
1069
+ pending_bits += 1
1070
+ low = (low << 1) & 0x7FFFFFFF
1071
+ high = ((high << 1) | 0x80000001) & MASK32
1072
+ else:
1073
+ break
1074
+
1075
+ # Final flushing sequence
1076
+ pending_bits += 1
1077
+ bit = 1 if (low & 0x40000000) != 0 else 0
1078
+ output_reg = (output_reg << 1) | bit
1079
+ output_reg_len += 1
1080
+ if output_reg_len == 8:
1081
+ if ptr < buf_size:
1082
+ out_buf_lc[ptr] = output_reg & 0xFF
1083
+ ptr += 1
1084
+ output_reg, output_reg_len = 0, 0
1085
+ for _ in range(pending_bits):
1086
+ output_reg = (output_reg << 1) | (1 - bit)
1087
+ output_reg_len += 1
1088
+ if output_reg_len == 8:
1089
+ if ptr < buf_size:
1090
+ out_buf_lc[ptr] = output_reg & 0xFF
1091
+ ptr += 1
1092
+ output_reg, output_reg_len = 0, 0
1093
+ pending_bits = 0 # noqa: F841
1094
+
1095
+ if output_reg_len > 0:
1096
+ if ptr < buf_size:
1097
+ out_buf_lc[ptr] = (output_reg << (8 - output_reg_len)) & 0xFF
1098
+ ptr += 1
1099
+
1100
+ return ptr
1101
+
1102
+
1103
+ def encode_fast_new(cdf, input_sym, output_buffer, output_lengths):
1104
+ """
1105
+ Python equivalent of C++ Arithmetic Encoder.
1106
+ Strictly emulates 32-bit unsigned overflow for high/low.
1107
+ """
1108
+ cdf_np = cdf.cpu().numpy().view(np.uint16).astype(np.uint32)
1109
+ sym_np = input_sym.cpu().numpy().astype(np.uint8)
1110
+
1111
+ n_layers, n_tokens, n_channels = sym_np.shape
1112
+ out_buf_np = np.zeros(output_buffer.shape, dtype=np.uint8)
1113
+ out_len_np = np.zeros(output_lengths.shape, dtype=np.int32)
1114
+
1115
+ def encode_one(args):
1116
+ layer_idx, c = args
1117
+ length = _encode_single_channel(
1118
+ cdf_np[layer_idx, c],
1119
+ sym_np[layer_idx, :, c],
1120
+ out_buf_np[layer_idx, c],
1121
+ )
1122
+ out_len_np[layer_idx, c] = length
1123
+
1124
+ tasks = [(layer_idx, c) for layer_idx in range(n_layers) for c in range(n_channels)]
1125
+
1126
+ with ThreadPoolExecutor() as executor:
1127
+ list(executor.map(encode_one, tasks))
1128
+
1129
+ output_buffer.copy_(torch.from_numpy(out_buf_np))
1130
+ output_lengths.copy_(torch.from_numpy(out_len_np))
1131
+
1132
+
1133
+ @njit(cache=True)
1134
+ def _decode_single_channel(
1135
+ cdf_layer_c,
1136
+ bs_np,
1137
+ start_off,
1138
+ end_off,
1139
+ n_tokens,
1140
+ out_layer_c,
1141
+ ):
1142
+ MASK32 = 0xFFFFFFFF
1143
+ precision = 16
1144
+ max_symbol = len(cdf_layer_c) - 2
1145
+
1146
+ v_val = 0
1147
+ if start_off + 4 <= len(bs_np):
1148
+ v_val = (
1149
+ (int(bs_np[start_off]) << 24)
1150
+ | (int(bs_np[start_off + 1]) << 16)
1151
+ | (int(bs_np[start_off + 2]) << 8)
1152
+ | int(bs_np[start_off + 3])
1153
+ ) & MASK32
1154
+
1155
+ low, high = 0, MASK32
1156
+ byte_buffer_offset = start_off + 4
1157
+ bit_idx = 1
1158
+ byte_buffer = int(bs_np[byte_buffer_offset]) if byte_buffer_offset < end_off else 0
1159
+
1160
+ for i in range(n_tokens):
1161
+ span = (high - low + 1) & MASK32
1162
+ if span == 0:
1163
+ span = 0x100000000
1164
+
1165
+ v_minus_l = (v_val - low) & MASK32
1166
+ count = ((v_minus_l + 1) * 0x10000 - 1) // span
1167
+ count = count & 0xFFFF
1168
+
1169
+ left = 0
1170
+ right = max_symbol + 1
1171
+ while left + 1 < right:
1172
+ m = (left + right) // 2
1173
+ if int(cdf_layer_c[m]) < count:
1174
+ left = m
1175
+ elif int(cdf_layer_c[m]) > count:
1176
+ right = m
1177
+ else:
1178
+ left = m
1179
+ break
1180
+
1181
+ out_layer_c[i] = left
1182
+
1183
+ if i == n_tokens - 1:
1184
+ break
1185
+
1186
+ sym_i = left
1187
+ c_low = int(cdf_layer_c[sym_i])
1188
+ c_high = 0x10000 if sym_i == max_symbol else int(cdf_layer_c[sym_i + 1])
1189
+
1190
+ high = (low + ((span * c_high) >> precision) - 1) & MASK32
1191
+ low = (low + ((span * c_low) >> precision)) & MASK32
1192
+
1193
+ while True:
1194
+ if low >= 0x80000000 or high < 0x80000000:
1195
+ v_val = ((v_val << 1) | ((byte_buffer >> (8 - bit_idx)) & 1)) & MASK32
1196
+ low = (low << 1) & MASK32
1197
+ high = ((high << 1) | 1) & MASK32
1198
+ bit_idx += 1
1199
+ elif low >= 0x40000000 and high < 0xC0000000:
1200
+ v_val = (v_val - 0x40000000) & MASK32
1201
+ v_val = ((v_val << 1) | ((byte_buffer >> (8 - bit_idx)) & 1)) & MASK32
1202
+ low = (low << 1) & 0x7FFFFFFF
1203
+ high = ((high << 1) | 0x80000001) & MASK32
1204
+ bit_idx += 1
1205
+ else:
1206
+ break
1207
+
1208
+ if bit_idx == 9:
1209
+ bit_idx = 1
1210
+ byte_buffer_offset += 1
1211
+ byte_buffer = (
1212
+ int(bs_np[byte_buffer_offset])
1213
+ if byte_buffer_offset < end_off
1214
+ else 0
1215
+ )
1216
+
1217
+
1218
+ # Standard
1219
+
1220
+
1221
+ def decode_fast_new(cdf, bytestreams, lengths, output):
1222
+ """
1223
+ Python implementation of Arithmetic Decoding.
1224
+ Strictly aligned with CUDA decode_with_accessor_kernel.
1225
+ bytestreams shape: [nlayers, nchannels, buffer_size]
1226
+ """
1227
+ cdf_np = cdf.cpu().numpy().view(np.uint16).astype(np.uint32)
1228
+ bs_np = bytestreams.cpu().numpy().astype(np.uint8)
1229
+ len_np = lengths.cpu().numpy().astype(np.int32)
1230
+
1231
+ n_layers, n_tokens, n_channels = output.shape
1232
+ out_np = np.zeros(output.shape, dtype=np.uint8)
1233
+
1234
+ def decode_one(args):
1235
+ layer_idx, c = args
1236
+ curr_len = int(len_np[layer_idx, c])
1237
+ # For decode_fast_new, each channel has its own contiguous buffer,
1238
+ # so start_off=0 and end_off=curr_len within channel_bs
1239
+ channel_bs = bs_np[layer_idx, c] # shape [buffer_size]
1240
+ _decode_single_channel(
1241
+ cdf_np[layer_idx, c],
1242
+ channel_bs,
1243
+ 0,
1244
+ curr_len,
1245
+ n_tokens,
1246
+ out_np[layer_idx, :, c],
1247
+ )
1248
+
1249
+ tasks = [(layer_idx, c) for layer_idx in range(n_layers) for c in range(n_channels)]
1250
+
1251
+ with ThreadPoolExecutor() as executor:
1252
+ list(executor.map(decode_one, tasks))
1253
+
1254
+ if output is not None:
1255
+ output.copy_(torch.from_numpy(out_np))
1256
+
1257
+
1258
+ def decode_fast_prefsum(cdf, bytestreams, lengths_prefsum, output):
1259
+ """
1260
+ Python equivalent of C++ decode_fast_prefsum.
1261
+ bytestreams shape: [total_bytes] (1D, all channels packed)
1262
+ """
1263
+ cdf_np = cdf.cpu().numpy().view(np.uint16).astype(np.uint32)
1264
+ pref_np = lengths_prefsum.cpu().numpy().astype(np.int64).flatten()
1265
+
1266
+ # WA: CUDA kernel reads out-of-bound in two ways:
1267
+ # 1. max(prefsum) may equal len(bytestreams) (off-by-one on exclusive-end)
1268
+ # 2. v_val init reads 4 bytes starting at start_off, may exceed bytestreams
1269
+ # Pad with zeros to make all reads safe.
1270
+ max_prefsum = int(pref_np.max())
1271
+ pad_size = max(0, max_prefsum + 4 - bytestreams.shape[0])
1272
+ if pad_size > 0:
1273
+ bytestreams = torch.nn.functional.pad(bytestreams, (0, pad_size), value=0)
1274
+
1275
+ bs_np = bytestreams.cpu().numpy().astype(np.uint8) # must be after padding
1276
+
1277
+ n_layers, n_tokens, n_channels = output.shape
1278
+ out_np = np.zeros(output.shape, dtype=np.uint8)
1279
+
1280
+ def decode_one(args):
1281
+ layer_idx, c = args
1282
+ cid = layer_idx * n_channels + c
1283
+ start_off = 0 if cid == 0 else int(pref_np[cid - 1])
1284
+ end_off = int(pref_np[cid])
1285
+ _decode_single_channel(
1286
+ cdf_np[layer_idx, c],
1287
+ bs_np,
1288
+ start_off,
1289
+ end_off,
1290
+ n_tokens,
1291
+ out_np[layer_idx, :, c],
1292
+ )
1293
+
1294
+ tasks = [(layer_idx, c) for layer_idx in range(n_layers) for c in range(n_channels)]
1295
+
1296
+ with ThreadPoolExecutor() as executor:
1297
+ list(executor.map(decode_one, tasks))
1298
+
1299
+ output.copy_(torch.from_numpy(out_np))
1300
+
1301
+
1302
+ def calculate_cdf(input_tensor: torch.Tensor, num_bins: int) -> torch.Tensor:
1303
+ """Equivalent to CUDA calculate_cdf.
1304
+
1305
+ Calculates the CDF across tokens for each (layer, channel) pair.
1306
+
1307
+ Args:
1308
+ input_tensor: 3D tensor with shape [nlayers, ntokens, nchannels].
1309
+ num_bins: Maximum number of bins (i.e., Lp - 1).
1310
+
1311
+ Returns:
1312
+ int16 tensor with shape [nlayers, nchannels, num_bins + 1]
1313
+ containing normalized CDF values.
1314
+ """
1315
+ nlayers, ntokens, nchannels = input_tensor.shape
1316
+ device = input_tensor.device
1317
+
1318
+ # Compute per-(layer, channel) histogram via scatter_add.
1319
+ # Permute to [nlayers, nchannels, ntokens] then flatten first two dims.
1320
+ input_perm = input_tensor.permute(0, 2, 1).reshape(-1, ntokens).long()
1321
+ src = torch.ones_like(input_perm)
1322
+ counts = torch.zeros(nlayers * nchannels, num_bins, dtype=torch.long, device=device)
1323
+ counts.scatter_add_(1, input_perm.clamp(0, num_bins - 1), src)
1324
+ counts = counts.reshape(nlayers, nchannels, num_bins)
1325
+
1326
+ # Build CDF: cdf[..., 0] = 0, cdf[..., i] = sum(counts[..., 0:i])
1327
+ cdf = torch.zeros(nlayers, nchannels, num_bins + 1, dtype=torch.long, device=device)
1328
+ cdf[:, :, 1:] = torch.cumsum(counts, dim=2)
1329
+
1330
+ # Total count per (layer, channel)
1331
+ total = cdf[:, :, -1:] # [nlayers, nchannels, 1]
1332
+
1333
+ # Normalize: (0xFFFF - num_bins) * cdf / total + bin_index
1334
+ max_uint16_value = 0xFFFF - num_bins
1335
+ bin_offsets = torch.arange(num_bins + 1, dtype=torch.long, device=device)
1336
+
1337
+ safe_total = total.clamp(min=1)
1338
+ normalized = (max_uint16_value * cdf) // safe_total + bin_offsets
1339
+
1340
+ # Where total is 0, use just the bin offsets
1341
+ normalized = torch.where(
1342
+ total > 0, normalized, bin_offsets.unsqueeze(0).unsqueeze(0)
1343
+ )
1344
+
1345
+ return normalized.to(torch.int16)
1346
+
1347
+
1348
+ def rotary_embedding_k_fused(
1349
+ old_positions: torch.Tensor,
1350
+ new_positions: torch.Tensor,
1351
+ key: torch.Tensor,
1352
+ head_size: int,
1353
+ cos_sin_cache: torch.Tensor,
1354
+ is_neox: bool,
1355
+ ) -> None:
1356
+ """Apply fused rotary embedding undo/redo to key tensor in-place.
1357
+
1358
+ Reverses the rotary embedding at old_positions and applies the rotary
1359
+ embedding at new_positions. head_size is unused but kept for API
1360
+ compatibility with the CUDA equivalent.
1361
+
1362
+ Args:
1363
+ old_positions: Token positions whose rotary embedding to reverse.
1364
+ new_positions: Token positions whose rotary embedding to apply.
1365
+ key: Key tensor to update in-place.
1366
+ head_size: Head size (unused; kept for API compatibility).
1367
+ cos_sin_cache: Precomputed cosine/sine cache indexed by position.
1368
+ is_neox: If True, uses NeoX-style rotary (contiguous halves);
1369
+ otherwise uses GPT-J-style (interleaved).
1370
+ """
1371
+ rot_dim = cos_sin_cache.shape[1]
1372
+ half_rot = rot_dim // 2
1373
+
1374
+ old_cs = cos_sin_cache[old_positions]
1375
+ new_cs = cos_sin_cache[new_positions]
1376
+
1377
+ oc, os = old_cs[:, :half_rot].unsqueeze(1), old_cs[:, half_rot:].unsqueeze(1)
1378
+ nc, ns = new_cs[:, :half_rot].unsqueeze(1), new_cs[:, half_rot:].unsqueeze(1)
1379
+
1380
+ if is_neox:
1381
+ x = key[..., :half_rot]
1382
+ y = key[..., half_rot:rot_dim]
1383
+ else:
1384
+ x = key[..., :rot_dim:2]
1385
+ y = key[..., 1:rot_dim:2]
1386
+
1387
+ x_rev = x * oc + y * os
1388
+ y_rev = y * oc - x * os
1389
+
1390
+ x_out = x_rev * nc - y_rev * ns
1391
+ y_out = y_rev * nc + x_rev * ns
1392
+
1393
+ if is_neox:
1394
+ key[..., :half_rot] = x_out
1395
+ key[..., half_rot:rot_dim] = y_out
1396
+ else:
1397
+ key[..., :rot_dim:2] = x_out
1398
+ key[..., 1:rot_dim:2] = y_out
1399
+
1400
+
1401
+ def get_gpu_pci_bus_id(device_id: int = 0) -> str | None:
1402
+ """
1403
+ Get the PCI bus ID via CUDA/ROCm runtime.
1404
+ Other backends return None.
1405
+
1406
+ Args:
1407
+ device_id (int): CUDA/ROCm device index.
1408
+
1409
+ Returns:
1410
+ str | None: PCI bus ID (e.g., "0000:29:00.0") or None if unavailable.
1411
+ """
1412
+ try:
1413
+ if torch.cuda.is_available() and device_id < torch.cuda.device_count():
1414
+ props = torch.cuda.get_device_properties(device_id)
1415
+ # PCI function number is always 0 for GPUs
1416
+ bus_id = (
1417
+ f"{props.pci_domain_id:04x}:{props.pci_bus_id:02x}:"
1418
+ f"{props.pci_device_id:02x}.0"
1419
+ )
1420
+ return bus_id.upper()
1421
+ except Exception:
1422
+ pass
1423
+
1424
+ return None