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,557 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """``lmcache bench`` command — sustained performance benchmarking."""
3
+
4
+ # Standard
5
+ import argparse
6
+ import asyncio
7
+ import os
8
+ import sys
9
+
10
+ # First Party
11
+ from lmcache.cli.commands.base import BaseCommand
12
+ from lmcache.cli.commands.bench.engine_bench.config import (
13
+ EngineBenchConfig,
14
+ parse_args_to_config,
15
+ )
16
+ from lmcache.cli.commands.bench.engine_bench.interactive import run_interactive
17
+ from lmcache.cli.commands.bench.engine_bench.interactive.state import (
18
+ InteractiveState,
19
+ )
20
+ from lmcache.cli.commands.bench.engine_bench.progress import ProgressMonitor
21
+ from lmcache.cli.commands.bench.engine_bench.request_sender import (
22
+ RequestSender,
23
+ )
24
+ from lmcache.cli.commands.bench.engine_bench.stats import (
25
+ FinalStats,
26
+ StatsCollector,
27
+ )
28
+ from lmcache.cli.commands.bench.engine_bench.workloads import create_workload
29
+ from lmcache.logging import init_logger
30
+
31
+ logger = init_logger(__name__)
32
+
33
+
34
+ class BenchCommand(BaseCommand):
35
+ """CLI command for sustained performance benchmarking."""
36
+
37
+ def name(self) -> str:
38
+ return "bench"
39
+
40
+ def help(self) -> str:
41
+ return "Run sustained performance benchmarks."
42
+
43
+ def add_arguments(self, _parser: argparse.ArgumentParser) -> None:
44
+ pass # args registered in register() via subparsers
45
+
46
+ def register(self, subparsers: argparse._SubParsersAction) -> None:
47
+ parser = subparsers.add_parser(
48
+ self.name(),
49
+ help=self.help(),
50
+ description="Run sustained performance benchmarks.",
51
+ )
52
+ inner = parser.add_subparsers(
53
+ dest="bench_target",
54
+ required=True,
55
+ metavar="{engine}",
56
+ )
57
+ self._register_engine(inner)
58
+
59
+ def _register_engine(
60
+ self,
61
+ subparsers: argparse._SubParsersAction,
62
+ ) -> None:
63
+ parser = subparsers.add_parser(
64
+ "engine",
65
+ help="Benchmark an inference engine.",
66
+ )
67
+
68
+ # --- Config file ---
69
+ parser.add_argument(
70
+ "--config",
71
+ default=None,
72
+ metavar="FILE",
73
+ help="Load configuration from a JSON file (skips interactive mode).",
74
+ )
75
+
76
+ # --- General args ---
77
+ parser.add_argument(
78
+ "--engine-url",
79
+ default=None,
80
+ help=(
81
+ "Inference engine URL (e.g., http://localhost:8000). "
82
+ "Set OPENAI_API_KEY env var if authentication is needed."
83
+ ),
84
+ )
85
+ parser.add_argument(
86
+ "--lmcache-url",
87
+ default=None,
88
+ help="LMCache MP server URL for auto-detecting tokens per GB.",
89
+ )
90
+ parser.add_argument(
91
+ "--model",
92
+ default=None,
93
+ help="Model name (auto-detected from engine if omitted).",
94
+ )
95
+ parser.add_argument(
96
+ "--workload",
97
+ default=None,
98
+ choices=[
99
+ "long-doc-permutator",
100
+ "long-doc-qa",
101
+ "multi-round-chat",
102
+ "random-prefill",
103
+ ],
104
+ help="Workload type.",
105
+ )
106
+ parser.add_argument(
107
+ "--kv-cache-volume",
108
+ type=float,
109
+ default=100.0,
110
+ help="Target active KV cache in GB (default: 100).",
111
+ )
112
+ parser.add_argument(
113
+ "--tokens-per-gb-kvcache",
114
+ type=int,
115
+ default=None,
116
+ help=("Tokens per GB of KV cache (required if --lmcache-url is not set)."),
117
+ )
118
+ parser.add_argument(
119
+ "--seed",
120
+ type=int,
121
+ default=42,
122
+ help="Random seed (default: 42).",
123
+ )
124
+ parser.add_argument(
125
+ "--output-dir",
126
+ default=".",
127
+ help="Directory for output files (default: current).",
128
+ )
129
+ parser.add_argument(
130
+ "--no-csv",
131
+ action="store_true",
132
+ help="Skip CSV export.",
133
+ )
134
+ parser.add_argument(
135
+ "--json",
136
+ action="store_true",
137
+ help="Export JSON summary.",
138
+ )
139
+ parser.add_argument(
140
+ "-q",
141
+ "--quiet",
142
+ action="store_true",
143
+ help="Suppress real-time progress display.",
144
+ )
145
+ parser.add_argument(
146
+ "--no-interactive",
147
+ action="store_true",
148
+ help=(
149
+ "Disable interactive mode. Errors if required arguments are missing."
150
+ ),
151
+ )
152
+ parser.add_argument(
153
+ "--export-config",
154
+ default=None,
155
+ metavar="FILE",
156
+ help=(
157
+ "Export resolved configuration to a JSON file and exit. "
158
+ "Does not run the benchmark or enter interactive mode."
159
+ ),
160
+ )
161
+
162
+ # --- Long-doc-permutator workload args ---
163
+ ldp_group = parser.add_argument_group("long-doc-permutator workload options")
164
+ ldp_group.add_argument(
165
+ "--ldp-num-contexts",
166
+ type=int,
167
+ default=5,
168
+ help="Number of unique context documents (default: 5).",
169
+ )
170
+ ldp_group.add_argument(
171
+ "--ldp-context-length",
172
+ type=int,
173
+ default=5000,
174
+ help="Token length of each context (default: 5000).",
175
+ )
176
+ ldp_group.add_argument(
177
+ "--ldp-system-prompt-length",
178
+ type=int,
179
+ default=1000,
180
+ help="Token length of the shared system prompt (default: 1000). "
181
+ "Use 0 for no system prompt.",
182
+ )
183
+ ldp_group.add_argument(
184
+ "--ldp-num-permutations",
185
+ type=int,
186
+ default=10,
187
+ help="Number of distinct permutations to send (default: 10). "
188
+ "Capped at N! where N = --ldp-num-contexts.",
189
+ )
190
+ ldp_group.add_argument(
191
+ "--ldp-num-inflight-requests",
192
+ type=int,
193
+ default=1,
194
+ help="Max concurrent in-flight requests (default: 1).",
195
+ )
196
+
197
+ # --- Long-doc-qa workload args ---
198
+ group = parser.add_argument_group("long-doc-qa workload options")
199
+ group.add_argument(
200
+ "--ldqa-document-length",
201
+ type=int,
202
+ default=10000,
203
+ help="Token length per document (default: 10000).",
204
+ )
205
+ group.add_argument(
206
+ "--ldqa-query-per-document",
207
+ type=int,
208
+ default=2,
209
+ help="Questions per document (default: 2).",
210
+ )
211
+ group.add_argument(
212
+ "--ldqa-shuffle-policy",
213
+ default="random",
214
+ choices=["random", "tile"],
215
+ help="Request ordering (default: random).",
216
+ )
217
+ group.add_argument(
218
+ "--ldqa-num-inflight-requests",
219
+ type=int,
220
+ default=3,
221
+ help="Max concurrent in-flight requests (default: 3).",
222
+ )
223
+
224
+ # --- Multi-round-chat workload args ---
225
+ mrc_group = parser.add_argument_group(
226
+ "multi-round-chat workload options",
227
+ )
228
+ mrc_group.add_argument(
229
+ "--mrc-shared-prompt-length",
230
+ type=int,
231
+ default=2000,
232
+ help="System prompt token length (default: 2000).",
233
+ )
234
+ mrc_group.add_argument(
235
+ "--mrc-chat-history-length",
236
+ type=int,
237
+ default=10000,
238
+ help="Pre-filled chat history token length (default: 10000).",
239
+ )
240
+ mrc_group.add_argument(
241
+ "--mrc-user-input-length",
242
+ type=int,
243
+ default=50,
244
+ help="Tokens per user query (default: 50).",
245
+ )
246
+ mrc_group.add_argument(
247
+ "--mrc-output-length",
248
+ type=int,
249
+ default=200,
250
+ help="Max tokens to generate per response (default: 200).",
251
+ )
252
+ mrc_group.add_argument(
253
+ "--mrc-qps",
254
+ type=float,
255
+ default=1.0,
256
+ help="Queries per second (default: 1.0).",
257
+ )
258
+ mrc_group.add_argument(
259
+ "--mrc-duration",
260
+ type=float,
261
+ default=60.0,
262
+ help="Benchmark duration in seconds (default: 60).",
263
+ )
264
+
265
+ # --- Random-prefill workload args ---
266
+ rp_group = parser.add_argument_group(
267
+ "random-prefill workload options",
268
+ )
269
+ rp_group.add_argument(
270
+ "--rp-request-length",
271
+ type=int,
272
+ default=10000,
273
+ help="Token length per request (default: 10000).",
274
+ )
275
+ rp_group.add_argument(
276
+ "--rp-num-requests",
277
+ type=int,
278
+ default=50,
279
+ help="Number of requests to send (default: 50).",
280
+ )
281
+
282
+ parser.set_defaults(func=self.execute)
283
+
284
+ def execute(self, args: argparse.Namespace) -> None:
285
+ handlers = {"engine": self._bench_engine}
286
+ handler = handlers.get(args.bench_target)
287
+ if handler is None:
288
+ print(
289
+ f"Unknown bench target: {args.bench_target}",
290
+ file=sys.stderr,
291
+ )
292
+ sys.exit(1)
293
+ handler(args)
294
+
295
+ # ------------------------------------------------------------------
296
+ # Engine benchmark orchestrator
297
+ # ------------------------------------------------------------------
298
+
299
+ def _get_missing_args(self, args: argparse.Namespace) -> list[str]:
300
+ """Return list of missing required CLI flags."""
301
+ missing: list[str] = []
302
+ if args.engine_url is None:
303
+ missing.append("--engine-url")
304
+ if args.workload is None:
305
+ missing.append("--workload")
306
+ if (
307
+ args.tokens_per_gb_kvcache is None
308
+ and getattr(args, "lmcache_url", None) is None
309
+ ):
310
+ missing.append("--tokens-per-gb-kvcache or --lmcache-url")
311
+ return missing
312
+
313
+ def _needs_interactive(self, args: argparse.Namespace) -> bool:
314
+ """Check whether interactive mode should be triggered."""
315
+ if getattr(args, "config", None):
316
+ return False
317
+ return len(self._get_missing_args(args)) > 0
318
+
319
+ def _resolve_args(self, args: argparse.Namespace) -> argparse.Namespace:
320
+ """Resolve args via config file, interactive mode, or pass through."""
321
+ # Case 1: --config file
322
+ config_path = getattr(args, "config", None)
323
+ if config_path:
324
+ state = InteractiveState.load_json(config_path)
325
+ state.merge_cli_args(args)
326
+ resolved = state.to_namespace()
327
+ # Carry over output flags from CLI
328
+ for attr in (
329
+ "output_dir",
330
+ "seed",
331
+ "no_csv",
332
+ "json",
333
+ "quiet",
334
+ "format",
335
+ "output",
336
+ ):
337
+ cli_val = getattr(args, attr, None)
338
+ if cli_val is not None:
339
+ setattr(resolved, attr, cli_val)
340
+ return resolved
341
+
342
+ # Case 2: --no-interactive or --export-config — error if missing
343
+ no_interactive = getattr(args, "no_interactive", False)
344
+ export_config = getattr(args, "export_config", None)
345
+ if no_interactive or export_config:
346
+ missing = self._get_missing_args(args)
347
+ if missing:
348
+ flag = "--export-config" if export_config else "--no-interactive"
349
+ raise SystemExit(
350
+ "Missing required arguments: "
351
+ + ", ".join(missing)
352
+ + f". Provide them or remove {flag} "
353
+ "for guided setup."
354
+ )
355
+ return args
356
+
357
+ # Case 3: Interactive mode
358
+ if self._needs_interactive(args):
359
+ return run_interactive(args)
360
+
361
+ # Case 4: All required args present — run directly
362
+ return args
363
+
364
+ def _export_config(
365
+ self,
366
+ config: EngineBenchConfig,
367
+ args: argparse.Namespace,
368
+ path: str,
369
+ ) -> None:
370
+ """Export resolved config to JSON and exit.
371
+
372
+ Builds a standalone config dict from the resolved
373
+ ``EngineBenchConfig`` and workload-specific CLI args.
374
+ Environment-specific keys (``engine_url``, ``lmcache_url``)
375
+ are excluded by ``InteractiveState.to_json()`` so the exported
376
+ config is portable.
377
+ """
378
+ # Standard
379
+ import json as json_mod
380
+
381
+ state = InteractiveState()
382
+ state.set("engine_url", config.engine_url)
383
+ state.set("model", config.model)
384
+ state.set("workload", config.workload)
385
+ state.set("kv_cache_volume", config.kv_cache_volume_gb)
386
+ state.set("tokens_per_gb_kvcache", config.tokens_per_gb_kvcache)
387
+
388
+ # Workload-specific args from namespace
389
+ for item in state.get_workload_items():
390
+ value = getattr(args, item.key, item.default)
391
+ if value is not None:
392
+ state.set(item.key, value)
393
+
394
+ # to_json() handles filtering out engine_url, lmcache_url, etc.
395
+ data = state.to_json()
396
+
397
+ with open(path, "w") as f:
398
+ json_mod.dump(data, f, indent=2)
399
+ f.write("\n")
400
+
401
+ print(f"Configuration exported to {path}")
402
+ print(
403
+ f"\033[1mReplay with:\033[0m \033[96mlmcache bench engine "
404
+ f"--engine-url <URL> --config {path}\033[0m"
405
+ )
406
+
407
+ def _bench_engine(self, args: argparse.Namespace) -> None:
408
+ """Centralized orchestrator: create all modules and run benchmark."""
409
+ # 0. Resolve args (config file / interactive / pass-through)
410
+ args = self._resolve_args(args)
411
+
412
+ # 1. Parse config
413
+ config = parse_args_to_config(args)
414
+
415
+ # 1b. --export-config: save resolved config and exit
416
+ export_path = getattr(args, "export_config", None)
417
+ if export_path:
418
+ self._export_config(config, args, export_path)
419
+ return
420
+
421
+ logger.info(
422
+ "Benchmark config: workload=%s, model=%s, "
423
+ "kv_cache=%.1f GB, tokens_per_gb=%d",
424
+ config.workload,
425
+ config.model,
426
+ config.kv_cache_volume_gb,
427
+ config.tokens_per_gb_kvcache,
428
+ )
429
+
430
+ # 2. Create shared modules
431
+ stats_collector = StatsCollector()
432
+ progress_monitor = ProgressMonitor(
433
+ stats_collector,
434
+ quiet=config.quiet,
435
+ )
436
+
437
+ # 3. Create request sender (callbacks wired after workload creation)
438
+ request_sender = RequestSender(config.engine_url, config.model)
439
+
440
+ # 4. Create workload
441
+ workload = create_workload(
442
+ config,
443
+ args,
444
+ request_sender,
445
+ stats_collector,
446
+ progress_monitor,
447
+ )
448
+
449
+ # 5. Wire callbacks on sender
450
+ request_sender.add_on_finished_callback(
451
+ lambda result, _text: stats_collector.on_request_finished(result),
452
+ )
453
+ request_sender.add_on_finished_callback(
454
+ lambda result, _text: progress_monitor.on_request_finished(
455
+ result.request_id,
456
+ result.successful,
457
+ ),
458
+ )
459
+ request_sender.add_on_finished_callback(workload.request_finished)
460
+
461
+ # 6. Log config and run benchmark
462
+ workload.log_config()
463
+ progress_monitor.start()
464
+ try:
465
+ workload.run()
466
+ finally:
467
+ progress_monitor.stop()
468
+ asyncio.run(request_sender.close())
469
+
470
+ # 7. Final metrics
471
+ final = stats_collector.get_final_stats()
472
+ self._emit_final_metrics(config, final, args)
473
+
474
+ # 8. Export
475
+ if config.export_csv:
476
+ csv_path = os.path.join(config.output_dir, "bench_results.csv")
477
+ stats_collector.export_csv(csv_path)
478
+ logger.info("CSV results written to %s", csv_path)
479
+ if config.export_json:
480
+ json_path = os.path.join(
481
+ config.output_dir,
482
+ "bench_summary.json",
483
+ )
484
+ stats_collector.export_json(json_path, config)
485
+ logger.info("JSON summary written to %s", json_path)
486
+
487
+ # 9. Exit code
488
+ if final.failed_requests > 0:
489
+ sys.exit(1)
490
+
491
+ def _emit_final_metrics(
492
+ self,
493
+ config: EngineBenchConfig,
494
+ final: FinalStats,
495
+ args: argparse.Namespace,
496
+ ) -> None:
497
+ """Emit final benchmark summary using the CLI metrics system."""
498
+ title = f"Engine Benchmark Result ({config.workload})"
499
+ metrics = self.create_metrics(title, args, width=56)
500
+
501
+ cfg_section = metrics.add_section("config", "Configuration")
502
+ cfg_section.add("engine_url", "Engine URL", config.engine_url)
503
+ cfg_section.add("model", "Model", config.model)
504
+ cfg_section.add("workload", "Workload", config.workload)
505
+
506
+ results = metrics.add_section("results", "Results")
507
+ results.add(
508
+ "successful",
509
+ "Successful requests",
510
+ final.successful_requests,
511
+ )
512
+ results.add("failed", "Failed requests", final.failed_requests)
513
+ results.add(
514
+ "duration",
515
+ "Benchmark duration (s)",
516
+ round(final.elapsed_time, 2),
517
+ )
518
+ results.add(
519
+ "input_tokens",
520
+ "Total input tokens",
521
+ final.total_input_tokens,
522
+ )
523
+ results.add(
524
+ "output_tokens",
525
+ "Total output tokens",
526
+ final.total_output_tokens,
527
+ )
528
+ results.add(
529
+ "input_tput",
530
+ "Input throughput (tok/s)",
531
+ round(final.input_throughput, 2),
532
+ )
533
+ results.add(
534
+ "output_tput",
535
+ "Output throughput (tok/s)",
536
+ round(final.output_throughput, 2),
537
+ )
538
+
539
+ ttft = metrics.add_section("ttft", "Time to First Token")
540
+ ttft.add("mean", "Mean TTFT (ms)", round(final.mean_ttft_ms, 2))
541
+ ttft.add("p50", "P50 TTFT (ms)", round(final.p50_ttft_ms, 2))
542
+ ttft.add("p90", "P90 TTFT (ms)", round(final.p90_ttft_ms, 2))
543
+ ttft.add("p99", "P99 TTFT (ms)", round(final.p99_ttft_ms, 2))
544
+
545
+ decode = metrics.add_section("decode", "Decoding Speed")
546
+ decode.add(
547
+ "mean",
548
+ "Mean decode (tok/s)",
549
+ round(final.mean_decode_speed, 2),
550
+ )
551
+ decode.add(
552
+ "p99",
553
+ "P99 decode (tok/s)",
554
+ round(final.p99_decode_speed, 2),
555
+ )
556
+
557
+ metrics.emit()
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0