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,275 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Stats collection, aggregation, and export for ``lmcache bench engine``."""
3
+
4
+ # Standard
5
+ from dataclasses import asdict, dataclass
6
+ from pathlib import Path
7
+ import csv
8
+ import json
9
+ import threading
10
+ import time
11
+
12
+ # First Party
13
+ from lmcache.cli.commands.bench.engine_bench.config import EngineBenchConfig
14
+ from lmcache.logging import init_logger
15
+
16
+ logger = init_logger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class RequestResult:
21
+ """Raw per-request result collected by the request sender."""
22
+
23
+ request_id: str
24
+ successful: bool
25
+ ttft: float # time to first token (seconds)
26
+ request_latency: float # total request time (seconds)
27
+ num_input_tokens: int # from server usage report
28
+ num_output_tokens: int # tokens generated
29
+ decode_speed: float # output tokens / decode time (tok/s)
30
+ submit_time: float # absolute timestamp
31
+ first_token_time: float # absolute timestamp
32
+ finish_time: float # absolute timestamp
33
+ error: str # empty string if successful
34
+
35
+
36
+ @dataclass
37
+ class AggregatedStats:
38
+ """Snapshot of aggregated statistics (running totals)."""
39
+
40
+ total_requests: int
41
+ successful_requests: int
42
+ failed_requests: int
43
+ elapsed_time: float # seconds since benchmark start
44
+
45
+ mean_ttft_ms: float
46
+ mean_decode_speed: float # tok/s
47
+ mean_request_latency_ms: float
48
+
49
+ input_throughput: float # total input tokens / elapsed time
50
+ output_throughput: float # total output tokens / elapsed time
51
+
52
+ total_input_tokens: int
53
+ total_output_tokens: int
54
+
55
+
56
+ @dataclass
57
+ class FinalStats(AggregatedStats):
58
+ """Final statistics with percentiles. Extends AggregatedStats."""
59
+
60
+ p50_ttft_ms: float = 0.0
61
+ p90_ttft_ms: float = 0.0
62
+ p99_ttft_ms: float = 0.0
63
+ p50_decode_speed: float = 0.0
64
+ p90_decode_speed: float = 0.0
65
+ p99_decode_speed: float = 0.0
66
+ p50_request_latency_ms: float = 0.0
67
+ p90_request_latency_ms: float = 0.0
68
+ p99_request_latency_ms: float = 0.0
69
+
70
+
71
+ class StatsCollector:
72
+ """Thread-safe stats aggregation for benchmark results.
73
+
74
+ Receives ``RequestResult`` objects from the request sender,
75
+ maintains running totals, and produces final summaries.
76
+ """
77
+
78
+ def __init__(self) -> None:
79
+ self._lock = threading.Lock()
80
+ self._results: list[RequestResult] = []
81
+ self._start_time: float = time.monotonic()
82
+
83
+ # Running accumulators (updated under lock)
84
+ self._successful: int = 0
85
+ self._failed: int = 0
86
+ self._sum_ttft: float = 0.0
87
+ self._sum_decode_speed: float = 0.0
88
+ self._sum_request_latency: float = 0.0
89
+ self._total_input_tokens: int = 0
90
+ self._total_output_tokens: int = 0
91
+
92
+ def on_request_finished(self, result: RequestResult) -> None:
93
+ """Record a completed request. Thread-safe."""
94
+ with self._lock:
95
+ self._results.append(result)
96
+ if result.successful:
97
+ self._successful += 1
98
+ self._sum_ttft += result.ttft
99
+ self._sum_decode_speed += result.decode_speed
100
+ self._sum_request_latency += result.request_latency
101
+ else:
102
+ self._failed += 1
103
+ self._total_input_tokens += result.num_input_tokens
104
+ self._total_output_tokens += result.num_output_tokens
105
+ logger.debug(
106
+ "Recorded result for %s (successful=%s)",
107
+ result.request_id,
108
+ result.successful,
109
+ )
110
+
111
+ def reset(self) -> None:
112
+ """Clear all accumulated results and restart the timer.
113
+
114
+ Used between warmup and benchmark phases so warmup stats
115
+ don't pollute benchmark results. Thread-safe.
116
+ """
117
+ with self._lock:
118
+ self._results.clear()
119
+ self._start_time = time.monotonic()
120
+ self._successful = 0
121
+ self._failed = 0
122
+ self._sum_ttft = 0.0
123
+ self._sum_decode_speed = 0.0
124
+ self._sum_request_latency = 0.0
125
+ self._total_input_tokens = 0
126
+ self._total_output_tokens = 0
127
+ logger.debug("Stats collector reset")
128
+
129
+ def get_current_stats(self) -> AggregatedStats:
130
+ """Return current aggregated stats snapshot. Thread-safe."""
131
+ with self._lock:
132
+ successful = self._successful
133
+ failed = self._failed
134
+ elapsed = time.monotonic() - self._start_time
135
+ sum_ttft = self._sum_ttft
136
+ sum_decode = self._sum_decode_speed
137
+ sum_latency = self._sum_request_latency
138
+ total_in = self._total_input_tokens
139
+ total_out = self._total_output_tokens
140
+
141
+ safe_successful = max(successful, 1)
142
+
143
+ return AggregatedStats(
144
+ total_requests=successful + failed,
145
+ successful_requests=successful,
146
+ failed_requests=failed,
147
+ elapsed_time=elapsed,
148
+ mean_ttft_ms=(sum_ttft / safe_successful) * 1000.0,
149
+ mean_decode_speed=sum_decode / safe_successful,
150
+ mean_request_latency_ms=(sum_latency / safe_successful) * 1000.0,
151
+ input_throughput=total_in / max(elapsed, 1e-9),
152
+ output_throughput=total_out / max(elapsed, 1e-9),
153
+ total_input_tokens=total_in,
154
+ total_output_tokens=total_out,
155
+ )
156
+
157
+ def get_final_stats(self) -> FinalStats:
158
+ """Compute and return final stats with percentiles.
159
+
160
+ Should be called once after the benchmark completes.
161
+ """
162
+ with self._lock:
163
+ results = list(self._results)
164
+
165
+ successful_results = [r for r in results if r.successful]
166
+ current = self.get_current_stats()
167
+
168
+ if not successful_results:
169
+ return FinalStats(
170
+ total_requests=current.total_requests,
171
+ successful_requests=current.successful_requests,
172
+ failed_requests=current.failed_requests,
173
+ elapsed_time=current.elapsed_time,
174
+ mean_ttft_ms=current.mean_ttft_ms,
175
+ mean_decode_speed=current.mean_decode_speed,
176
+ mean_request_latency_ms=current.mean_request_latency_ms,
177
+ input_throughput=current.input_throughput,
178
+ output_throughput=current.output_throughput,
179
+ total_input_tokens=current.total_input_tokens,
180
+ total_output_tokens=current.total_output_tokens,
181
+ )
182
+
183
+ ttfts = sorted(r.ttft * 1000.0 for r in successful_results)
184
+ decode_speeds = sorted(r.decode_speed for r in successful_results)
185
+ latencies = sorted(r.request_latency * 1000.0 for r in successful_results)
186
+
187
+ return FinalStats(
188
+ total_requests=current.total_requests,
189
+ successful_requests=current.successful_requests,
190
+ failed_requests=current.failed_requests,
191
+ elapsed_time=current.elapsed_time,
192
+ mean_ttft_ms=current.mean_ttft_ms,
193
+ mean_decode_speed=current.mean_decode_speed,
194
+ mean_request_latency_ms=current.mean_request_latency_ms,
195
+ input_throughput=current.input_throughput,
196
+ output_throughput=current.output_throughput,
197
+ total_input_tokens=current.total_input_tokens,
198
+ total_output_tokens=current.total_output_tokens,
199
+ p50_ttft_ms=_percentile(ttfts, 50),
200
+ p90_ttft_ms=_percentile(ttfts, 90),
201
+ p99_ttft_ms=_percentile(ttfts, 99),
202
+ p50_decode_speed=_percentile(decode_speeds, 50),
203
+ p90_decode_speed=_percentile(decode_speeds, 90),
204
+ p99_decode_speed=_percentile(decode_speeds, 99),
205
+ p50_request_latency_ms=_percentile(latencies, 50),
206
+ p90_request_latency_ms=_percentile(latencies, 90),
207
+ p99_request_latency_ms=_percentile(latencies, 99),
208
+ )
209
+
210
+ def get_all_results(self) -> list[RequestResult]:
211
+ """Return all raw results for CSV export."""
212
+ with self._lock:
213
+ return list(self._results)
214
+
215
+ def export_csv(self, path: str) -> None:
216
+ """Write per-request results to a CSV file."""
217
+ results = self.get_all_results()
218
+ fieldnames = [
219
+ "request_id",
220
+ "successful",
221
+ "ttft",
222
+ "request_latency",
223
+ "num_input_tokens",
224
+ "num_output_tokens",
225
+ "decode_speed",
226
+ "submit_time",
227
+ "first_token_time",
228
+ "finish_time",
229
+ "error",
230
+ ]
231
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
232
+ with open(path, "w", newline="") as f:
233
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
234
+ writer.writeheader()
235
+ for result in results:
236
+ writer.writerow(asdict(result))
237
+ logger.debug("Exported %d results to CSV: %s", len(results), path)
238
+
239
+ def export_json(self, path: str, config: EngineBenchConfig) -> None:
240
+ """Write summary JSON with config and aggregated metrics."""
241
+ final = self.get_final_stats()
242
+ output = {
243
+ "config": asdict(config),
244
+ "results": asdict(final),
245
+ }
246
+ with open(path, "w") as f:
247
+ json.dump(output, f, indent=2)
248
+ logger.debug("Exported JSON summary to: %s", path)
249
+
250
+
251
+ def _percentile(sorted_data: list[float], p: float) -> float:
252
+ """Compute the p-th percentile using linear interpolation.
253
+
254
+ Uses the Tensormesh-Benchmark V1 method:
255
+ ``k = (len(sorted_data) - 1) * p / 100``
256
+
257
+ Args:
258
+ sorted_data: Pre-sorted list of values.
259
+ p: Percentile value (0-100).
260
+
261
+ Returns:
262
+ Interpolated percentile value. Returns 0.0 for empty data.
263
+ """
264
+ if not sorted_data:
265
+ return 0.0
266
+ n = len(sorted_data)
267
+ if n == 1:
268
+ return sorted_data[0]
269
+ k = (n - 1) * p / 100.0
270
+ floor_k = int(k)
271
+ ceil_k = min(floor_k + 1, n - 1)
272
+ fraction = k - floor_k
273
+ return sorted_data[floor_k] + fraction * (
274
+ sorted_data[ceil_k] - sorted_data[floor_k]
275
+ )
@@ -0,0 +1,153 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Workload definitions and factory for ``lmcache bench engine``.
3
+
4
+ Each workload module defines its own config dataclass and workload
5
+ class. The ``create_workload`` factory selects the right workload
6
+ based on ``EngineBenchConfig.workload``, resolves the workload-specific
7
+ config from CLI args, and returns the workload instance.
8
+ """
9
+
10
+ # Standard
11
+ import argparse
12
+
13
+ # First Party
14
+ from lmcache.cli.commands.bench.engine_bench.config import EngineBenchConfig
15
+ from lmcache.cli.commands.bench.engine_bench.progress import ProgressMonitor
16
+ from lmcache.cli.commands.bench.engine_bench.request_sender import (
17
+ RequestSender,
18
+ )
19
+ from lmcache.cli.commands.bench.engine_bench.stats import StatsCollector
20
+ from lmcache.cli.commands.bench.engine_bench.workloads.base import BaseWorkload
21
+ from lmcache.cli.commands.bench.engine_bench.workloads.long_doc_permutator import (
22
+ LongDocPermutatorConfig,
23
+ LongDocPermutatorWorkload,
24
+ )
25
+ from lmcache.cli.commands.bench.engine_bench.workloads.long_doc_qa import (
26
+ LongDocQAConfig,
27
+ LongDocQAWorkload,
28
+ )
29
+ from lmcache.cli.commands.bench.engine_bench.workloads.multi_round_chat import (
30
+ MultiRoundChatConfig,
31
+ MultiRoundChatWorkload,
32
+ )
33
+ from lmcache.cli.commands.bench.engine_bench.workloads.random_prefill import (
34
+ RandomPrefillConfig,
35
+ RandomPrefillWorkload,
36
+ )
37
+
38
+ __all__ = [
39
+ "BaseWorkload",
40
+ "LongDocPermutatorConfig",
41
+ "LongDocPermutatorWorkload",
42
+ "LongDocQAConfig",
43
+ "LongDocQAWorkload",
44
+ "MultiRoundChatConfig",
45
+ "MultiRoundChatWorkload",
46
+ "RandomPrefillConfig",
47
+ "RandomPrefillWorkload",
48
+ "create_workload",
49
+ ]
50
+
51
+ _WORKLOAD_NAMES = (
52
+ "long-doc-permutator",
53
+ "long-doc-qa",
54
+ "multi-round-chat",
55
+ "random-prefill",
56
+ )
57
+
58
+
59
+ def create_workload(
60
+ config: EngineBenchConfig,
61
+ args: argparse.Namespace,
62
+ request_sender: RequestSender,
63
+ stats_collector: StatsCollector,
64
+ progress_monitor: ProgressMonitor,
65
+ ) -> BaseWorkload:
66
+ """Resolve workload-specific config and create the workload instance.
67
+
68
+ Dispatches on ``config.workload`` to the appropriate workload module,
69
+ resolves the workload-specific config from ``args`` and ``config``,
70
+ and returns the workload instance ready to ``run()``.
71
+
72
+ Args:
73
+ config: Fully-resolved general benchmark config.
74
+ args: Raw CLI args namespace (contains workload-specific flags).
75
+ request_sender: Shared request sender instance.
76
+ stats_collector: Shared stats collector instance.
77
+ progress_monitor: Shared progress monitor instance.
78
+
79
+ Returns:
80
+ A concrete BaseWorkload instance.
81
+
82
+ Raises:
83
+ ValueError: If the workload name is not recognized.
84
+ """
85
+ if config.workload == "long-doc-permutator":
86
+ ldp_workload_config = LongDocPermutatorConfig.resolve(
87
+ num_contexts=args.ldp_num_contexts,
88
+ context_length=args.ldp_context_length,
89
+ system_prompt_length=args.ldp_system_prompt_length,
90
+ num_permutations=args.ldp_num_permutations,
91
+ vocab_size=8000,
92
+ num_inflight_requests=args.ldp_num_inflight_requests,
93
+ )
94
+ return LongDocPermutatorWorkload(
95
+ config=ldp_workload_config,
96
+ request_sender=request_sender,
97
+ stats_collector=stats_collector,
98
+ progress_monitor=progress_monitor,
99
+ seed=config.seed,
100
+ )
101
+
102
+ if config.workload == "long-doc-qa":
103
+ ld_workload_config = LongDocQAConfig.resolve(
104
+ kv_cache_volume_gb=config.kv_cache_volume_gb,
105
+ tokens_per_gb_kvcache=config.tokens_per_gb_kvcache,
106
+ document_length=args.ldqa_document_length,
107
+ query_per_document=args.ldqa_query_per_document,
108
+ shuffle_policy=args.ldqa_shuffle_policy,
109
+ num_inflight_requests=args.ldqa_num_inflight_requests,
110
+ )
111
+ return LongDocQAWorkload(
112
+ config=ld_workload_config,
113
+ request_sender=request_sender,
114
+ stats_collector=stats_collector,
115
+ progress_monitor=progress_monitor,
116
+ seed=config.seed,
117
+ )
118
+
119
+ if config.workload == "multi-round-chat":
120
+ mr_workload_config = MultiRoundChatConfig.resolve(
121
+ kv_cache_volume_gb=config.kv_cache_volume_gb,
122
+ tokens_per_gb_kvcache=config.tokens_per_gb_kvcache,
123
+ shared_prompt_length=args.mrc_shared_prompt_length,
124
+ chat_history_length=args.mrc_chat_history_length,
125
+ user_input_length=args.mrc_user_input_length,
126
+ output_length=args.mrc_output_length,
127
+ qps=args.mrc_qps,
128
+ duration=args.mrc_duration,
129
+ )
130
+ return MultiRoundChatWorkload(
131
+ config=mr_workload_config,
132
+ request_sender=request_sender,
133
+ stats_collector=stats_collector,
134
+ progress_monitor=progress_monitor,
135
+ seed=config.seed,
136
+ )
137
+
138
+ if config.workload == "random-prefill":
139
+ rp_workload_config = RandomPrefillConfig.resolve(
140
+ request_length=args.rp_request_length,
141
+ num_requests=args.rp_num_requests,
142
+ )
143
+ return RandomPrefillWorkload(
144
+ config=rp_workload_config,
145
+ request_sender=request_sender,
146
+ stats_collector=stats_collector,
147
+ progress_monitor=progress_monitor,
148
+ seed=config.seed,
149
+ )
150
+
151
+ raise ValueError(
152
+ f"Unknown workload {config.workload!r}. Available: {', '.join(_WORKLOAD_NAMES)}"
153
+ )
@@ -0,0 +1,122 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Abstract base class for engine benchmark workloads."""
3
+
4
+ # Standard
5
+ from abc import ABC, abstractmethod
6
+ import asyncio
7
+ import queue
8
+ import time
9
+
10
+ # First Party
11
+ from lmcache.cli.commands.bench.engine_bench.progress import ProgressMonitor
12
+ from lmcache.cli.commands.bench.engine_bench.request_sender import RequestSender
13
+ from lmcache.cli.commands.bench.engine_bench.stats import StatsCollector
14
+ from lmcache.logging import init_logger
15
+
16
+ logger = init_logger(__name__)
17
+
18
+
19
+ class BaseWorkload(ABC):
20
+ """Abstract base class for all engine benchmark workloads.
21
+
22
+ Owns the internal dispatch loop that calls ``step()`` on the
23
+ concrete workload. Provides a thread-safe ``request_finished``
24
+ callback that enqueues completed requests for the loop to drain.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ request_sender: RequestSender,
30
+ stats_collector: StatsCollector,
31
+ progress_monitor: ProgressMonitor,
32
+ ) -> None:
33
+ self._request_sender = request_sender
34
+ self._stats_collector = stats_collector
35
+ self._progress_monitor = progress_monitor
36
+ self._finished_queue: queue.Queue[tuple[str, str]] = queue.Queue()
37
+
38
+ # ------------------------------------------------------------------
39
+ # Abstract methods
40
+ # ------------------------------------------------------------------
41
+
42
+ @abstractmethod
43
+ async def warmup(self) -> None:
44
+ """Run warmup requests. Blocks until all warmup is done."""
45
+
46
+ @abstractmethod
47
+ async def step(self, time_offset: float) -> float:
48
+ """Execute one step of the workload.
49
+
50
+ Args:
51
+ time_offset: seconds since benchmark start.
52
+
53
+ Returns:
54
+ Next wakeup time offset (absolute, from benchmark start).
55
+ Return a negative value to signal that the workload is done.
56
+ """
57
+
58
+ @abstractmethod
59
+ def log_config(self) -> None:
60
+ """Log key workload config before the benchmark starts."""
61
+
62
+ @abstractmethod
63
+ def on_request_finished(self, request_id: str, output: str) -> None:
64
+ """Called when a request finishes (from the loop thread).
65
+
66
+ Stateless workloads can implement this as a no-op.
67
+ Stateful workloads use it to record responses in session history.
68
+ """
69
+
70
+ # ------------------------------------------------------------------
71
+ # Concrete methods
72
+ # ------------------------------------------------------------------
73
+
74
+ def run(self) -> None:
75
+ """Run the full workload: warmup, then the benchmark loop.
76
+
77
+ Blocks until the workload is complete.
78
+ """
79
+ asyncio.run(self._run_async())
80
+
81
+ async def _run_async(self) -> None:
82
+ """Internal async implementation of the run loop."""
83
+ self._progress_monitor.log_message("Starting warmup phase")
84
+ await self.warmup()
85
+
86
+ self._progress_monitor.log_message(
87
+ "Warmup complete, starting benchmark",
88
+ )
89
+ self._stats_collector.reset()
90
+ self._drain_finished_queue() # discard warmup completions
91
+
92
+ start_time = time.monotonic()
93
+
94
+ while True:
95
+ self._drain_finished_queue()
96
+ time_offset = time.monotonic() - start_time
97
+ next_wakeup = await self.step(time_offset)
98
+ if next_wakeup < 0:
99
+ break
100
+ sleep_duration = max(0.0, next_wakeup - (time.monotonic() - start_time))
101
+ if sleep_duration > 0:
102
+ await asyncio.sleep(sleep_duration)
103
+
104
+ self._drain_finished_queue() # final drain
105
+ self._progress_monitor.log_message("Benchmark complete")
106
+
107
+ def request_finished(self, result, response_text: str) -> None:
108
+ """Thread-safe callback matching ``OnFinishedCallback`` signature.
109
+
110
+ Enqueues ``(request_id, response_text)`` for the loop to drain.
111
+ Registered on ``RequestSender.on_finished`` by the orchestrator.
112
+ """
113
+ self._finished_queue.put((result.request_id, response_text))
114
+
115
+ def _drain_finished_queue(self) -> None:
116
+ """Drain all completed requests and call ``on_request_finished``."""
117
+ while True:
118
+ try:
119
+ request_id, output = self._finished_queue.get_nowait()
120
+ except queue.Empty:
121
+ break
122
+ self.on_request_finished(request_id, output)