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,281 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Long-document Q&A workload for ``lmcache bench engine``."""
3
+
4
+ # Standard
5
+ from dataclasses import dataclass
6
+ import asyncio
7
+ import random
8
+
9
+ # First Party
10
+ from lmcache.cli.commands.bench.engine_bench.progress import ProgressMonitor
11
+ from lmcache.cli.commands.bench.engine_bench.request_sender import (
12
+ RequestSender,
13
+ )
14
+ from lmcache.cli.commands.bench.engine_bench.stats import StatsCollector
15
+ from lmcache.cli.commands.bench.engine_bench.workloads.base import BaseWorkload
16
+ from lmcache.logging import init_logger
17
+
18
+ logger = init_logger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class LongDocQAConfig:
23
+ """Workload-specific config for the long-doc-qa workload."""
24
+
25
+ document_length: int = 10000
26
+ query_per_document: int = 2
27
+ num_documents: int = 1
28
+ shuffle_policy: str = "random"
29
+ num_inflight_requests: int = 3
30
+
31
+ def __post_init__(self) -> None:
32
+ if self.document_length <= 0:
33
+ raise ValueError(
34
+ f"document_length must be positive, got {self.document_length}"
35
+ )
36
+ if self.query_per_document < 1:
37
+ raise ValueError(
38
+ f"query_per_document must be >= 1, got {self.query_per_document}"
39
+ )
40
+ if self.num_inflight_requests < 1:
41
+ raise ValueError(
42
+ f"num_inflight_requests must be >= 1, got {self.num_inflight_requests}"
43
+ )
44
+ if self.shuffle_policy not in ("random", "tile"):
45
+ raise ValueError(
46
+ f"shuffle_policy must be 'random' or 'tile', "
47
+ f"got {self.shuffle_policy!r}"
48
+ )
49
+
50
+ @classmethod
51
+ def resolve(
52
+ cls,
53
+ kv_cache_volume_gb: float,
54
+ tokens_per_gb_kvcache: int,
55
+ document_length: int = 10000,
56
+ query_per_document: int = 2,
57
+ shuffle_policy: str = "random",
58
+ num_inflight_requests: int = 3,
59
+ ) -> "LongDocQAConfig":
60
+ """Create a config with ``num_documents`` computed from KV cache budget.
61
+
62
+ Args:
63
+ kv_cache_volume_gb: Target active KV cache volume in GB.
64
+ tokens_per_gb_kvcache: Tokens fitting in 1 GB of KV cache.
65
+ document_length: Token length of each document.
66
+ query_per_document: Number of questions per document.
67
+ shuffle_policy: Request ordering — ``"random"`` or ``"tile"``.
68
+ num_inflight_requests: Max concurrent in-flight requests.
69
+
70
+ Returns:
71
+ A fully-resolved LongDocQAConfig with computed num_documents.
72
+ """
73
+ num_documents = max(
74
+ int(kv_cache_volume_gb * tokens_per_gb_kvcache / document_length),
75
+ 1,
76
+ )
77
+ logger.debug(
78
+ "Computed num_documents=%d from kv_cache_volume_gb=%.1f, "
79
+ "tokens_per_gb_kvcache=%d, document_length=%d",
80
+ num_documents,
81
+ kv_cache_volume_gb,
82
+ tokens_per_gb_kvcache,
83
+ document_length,
84
+ )
85
+ return cls(
86
+ document_length=document_length,
87
+ query_per_document=query_per_document,
88
+ num_documents=num_documents,
89
+ shuffle_policy=shuffle_policy,
90
+ num_inflight_requests=num_inflight_requests,
91
+ )
92
+
93
+
94
+ _QUESTIONS = [
95
+ "What is this document about?",
96
+ "Summarize the key points.",
97
+ "What is the main topic discussed?",
98
+ "Provide a brief overview.",
99
+ ]
100
+
101
+
102
+ class LongDocQAWorkload(BaseWorkload):
103
+ """Workload that simulates repeated Q&A over long documents.
104
+
105
+ Generates synthetic documents, builds a request schedule, warms up
106
+ the KV cache by sending each document once, then dispatches benchmark
107
+ requests with semaphore-controlled concurrency.
108
+ """
109
+
110
+ def __init__(
111
+ self,
112
+ config: LongDocQAConfig,
113
+ request_sender: RequestSender,
114
+ stats_collector: StatsCollector,
115
+ progress_monitor: ProgressMonitor,
116
+ seed: int = 42,
117
+ ) -> None:
118
+ super().__init__(request_sender, stats_collector, progress_monitor)
119
+ self._config = config
120
+ self._seed = seed
121
+
122
+ self._documents = self._generate_documents()
123
+ self._schedule = self._build_schedule()
124
+ self._schedule_index = 0
125
+
126
+ self._semaphore = asyncio.Semaphore(config.num_inflight_requests)
127
+ self._pending_tasks: set[asyncio.Task] = set()
128
+
129
+ def log_config(self) -> None:
130
+ """Log key workload config before the benchmark starts."""
131
+ c = self._config
132
+ B = "\033[1m" # bold
133
+ C = "\033[96m" # cyan
134
+ Y = "\033[93m" # yellow
135
+ R = "\033[0m" # reset
136
+ total = c.num_documents * c.query_per_document
137
+ print(
138
+ f"{B}{'═' * 50}{R}\n"
139
+ f"{B} Workload: {C}long-doc-qa{R}\n"
140
+ f"{B}{'─' * 50}{R}\n"
141
+ f" Documents: {Y}{c.num_documents}{R}\n"
142
+ f" Queries/doc: {Y}{c.query_per_document}{R}\n"
143
+ f" Total requests: {Y}{total}{R}\n"
144
+ f" Document length: {Y}{c.document_length}{R} tokens\n"
145
+ f" Max inflight: {Y}{c.num_inflight_requests}{R}\n"
146
+ f" Shuffle policy: {Y}{c.shuffle_policy}{R}\n"
147
+ f"{B}{'═' * 50}{R}"
148
+ )
149
+
150
+ # ------------------------------------------------------------------
151
+ # Data generation
152
+ # ------------------------------------------------------------------
153
+
154
+ def _generate_documents(self) -> list[str]:
155
+ """Generate synthetic documents of approximately ``document_length`` tokens."""
156
+ documents = []
157
+ for doc_id in range(self._config.num_documents):
158
+ prefix = f"Document {doc_id}: "
159
+ body = " ".join(["hi"] * max(self._config.document_length - 10, 1))
160
+ documents.append(prefix + body)
161
+ logger.debug(
162
+ "Generated %d documents of ~%d tokens each",
163
+ len(documents),
164
+ self._config.document_length,
165
+ )
166
+ return documents
167
+
168
+ def _build_schedule(self) -> list[tuple[int, int]]:
169
+ """Build the request schedule as ``(doc_index, query_index)`` pairs.
170
+
171
+ Tile policy uses query-major order: all documents for query 0,
172
+ then all documents for query 1, etc. Random policy shuffles
173
+ the same pairs with a seeded RNG.
174
+ """
175
+ schedule: list[tuple[int, int]] = []
176
+ for q_idx in range(self._config.query_per_document):
177
+ for doc_idx in range(self._config.num_documents):
178
+ schedule.append((doc_idx, q_idx))
179
+
180
+ if self._config.shuffle_policy == "random":
181
+ rng = random.Random(self._seed)
182
+ rng.shuffle(schedule)
183
+
184
+ logger.debug(
185
+ "Built schedule with %d requests (policy=%s)",
186
+ len(schedule),
187
+ self._config.shuffle_policy,
188
+ )
189
+ return schedule
190
+
191
+ def _build_messages(
192
+ self,
193
+ doc_index: int,
194
+ query_index: int,
195
+ ) -> list[dict[str, str]]:
196
+ """Build chat messages for a benchmark request."""
197
+ document = self._documents[doc_index]
198
+ question = _QUESTIONS[query_index % len(_QUESTIONS)]
199
+ content = f"{document}\n\nQuestion {query_index}: {question}"
200
+ return [{"role": "user", "content": content}]
201
+
202
+ # ------------------------------------------------------------------
203
+ # Warmup
204
+ # ------------------------------------------------------------------
205
+
206
+ async def warmup(self) -> None:
207
+ """Send each document once with ``max_tokens=1`` to populate KV cache."""
208
+ num_docs = self._config.num_documents
209
+ for doc_idx in range(num_docs):
210
+ request_id = f"warmup_doc{doc_idx}"
211
+ messages = [{"role": "user", "content": self._documents[doc_idx]}]
212
+ self._progress_monitor.log_message(f"Warmup {doc_idx + 1}/{num_docs}")
213
+ self._progress_monitor.on_request_sent(request_id)
214
+ result = await self._request_sender.send_warmup_request(
215
+ request_id,
216
+ messages,
217
+ )
218
+ if not result.successful:
219
+ self._progress_monitor.log_message(
220
+ f"Warmup {request_id} failed: {result.error}"
221
+ )
222
+ self._progress_monitor.log_message(
223
+ f"Warmup complete: {num_docs} documents sent",
224
+ )
225
+
226
+ # ------------------------------------------------------------------
227
+ # Benchmark dispatch
228
+ # ------------------------------------------------------------------
229
+
230
+ async def step(self, time_offset: float) -> float:
231
+ """Dispatch the next request if semaphore allows."""
232
+ if self._schedule_index < len(self._schedule):
233
+ await self._semaphore.acquire()
234
+ doc_idx, q_idx = self._schedule[self._schedule_index]
235
+ self._schedule_index += 1
236
+
237
+ task = asyncio.create_task(self._dispatch(doc_idx, q_idx))
238
+ self._pending_tasks.add(task)
239
+ task.add_done_callback(self._on_task_done)
240
+ return 0.0 # immediate re-call
241
+
242
+ # All dispatched — wait for pending tasks.
243
+ if self._pending_tasks:
244
+ await asyncio.wait(
245
+ self._pending_tasks,
246
+ return_when=asyncio.FIRST_COMPLETED,
247
+ )
248
+ return 0.0
249
+
250
+ return -1.0 # all done
251
+
252
+ async def _dispatch(
253
+ self,
254
+ doc_index: int,
255
+ query_index: int,
256
+ ) -> None:
257
+ """Send a single benchmark request, then release the semaphore."""
258
+ request_id = f"doc{doc_index}_q{query_index}"
259
+ messages = self._build_messages(doc_index, query_index)
260
+ self._progress_monitor.on_request_sent(request_id)
261
+ self._progress_monitor.log_message(
262
+ f"Dispatched request {request_id} (doc {doc_index}, query {query_index})"
263
+ )
264
+ try:
265
+ await self._request_sender.send_request(
266
+ request_id,
267
+ messages,
268
+ )
269
+ finally:
270
+ self._semaphore.release()
271
+
272
+ def _on_task_done(self, task: asyncio.Task) -> None:
273
+ """Clean up completed tasks and log unexpected errors."""
274
+ self._pending_tasks.discard(task)
275
+ if not task.cancelled():
276
+ exc = task.exception()
277
+ if exc is not None:
278
+ self._progress_monitor.log_message(f"Dispatch task failed: {exc}")
279
+
280
+ def on_request_finished(self, request_id: str, output: str) -> None:
281
+ """No-op — this workload is stateless."""
@@ -0,0 +1,337 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Multi-round chat workload for ``lmcache bench engine``."""
3
+
4
+ # Standard
5
+ from dataclasses import dataclass, field
6
+ import asyncio
7
+
8
+ # First Party
9
+ from lmcache.cli.commands.bench.engine_bench.progress import ProgressMonitor
10
+ from lmcache.cli.commands.bench.engine_bench.request_sender import (
11
+ RequestSender,
12
+ )
13
+ from lmcache.cli.commands.bench.engine_bench.stats import StatsCollector
14
+ from lmcache.cli.commands.bench.engine_bench.workloads.base import BaseWorkload
15
+ from lmcache.logging import init_logger
16
+
17
+ logger = init_logger(__name__)
18
+
19
+
20
+ @dataclass
21
+ class MultiRoundChatConfig:
22
+ """Workload-specific config for the multi-round-chat workload."""
23
+
24
+ shared_prompt_length: int = 2000
25
+ chat_history_length: int = 10000
26
+ user_input_length: int = 50
27
+ output_length: int = 200
28
+ qps: float = 1.0
29
+ duration: float = 60.0
30
+ num_concurrent_users: int = 1
31
+
32
+ def __post_init__(self) -> None:
33
+ if self.shared_prompt_length <= 0:
34
+ raise ValueError(
35
+ f"shared_prompt_length must be positive, "
36
+ f"got {self.shared_prompt_length}"
37
+ )
38
+ if self.chat_history_length <= 0:
39
+ raise ValueError(
40
+ f"chat_history_length must be positive, got {self.chat_history_length}"
41
+ )
42
+ if self.user_input_length < 1:
43
+ raise ValueError(
44
+ f"user_input_length must be >= 1, got {self.user_input_length}"
45
+ )
46
+ if self.output_length < 1:
47
+ raise ValueError(f"output_length must be >= 1, got {self.output_length}")
48
+ if self.qps <= 0:
49
+ raise ValueError(f"qps must be positive, got {self.qps}")
50
+ if self.duration <= 0:
51
+ raise ValueError(f"duration must be positive, got {self.duration}")
52
+ if self.num_concurrent_users < 1:
53
+ raise ValueError(
54
+ f"num_concurrent_users must be >= 1, got {self.num_concurrent_users}"
55
+ )
56
+
57
+ @classmethod
58
+ def resolve(
59
+ cls,
60
+ kv_cache_volume_gb: float,
61
+ tokens_per_gb_kvcache: int,
62
+ shared_prompt_length: int = 2000,
63
+ chat_history_length: int = 10000,
64
+ user_input_length: int = 50,
65
+ output_length: int = 200,
66
+ qps: float = 1.0,
67
+ duration: float = 60.0,
68
+ ) -> "MultiRoundChatConfig":
69
+ """Create a config with ``num_concurrent_users`` computed from KV cache budget.
70
+
71
+ Args:
72
+ kv_cache_volume_gb: Target active KV cache volume in GB.
73
+ tokens_per_gb_kvcache: Tokens fitting in 1 GB of KV cache.
74
+ shared_prompt_length: Token length of the system prompt.
75
+ chat_history_length: Token length of pre-filled history.
76
+ user_input_length: Token length per user query.
77
+ output_length: Max tokens to generate per response.
78
+ qps: Queries per second.
79
+ duration: Benchmark duration in seconds.
80
+
81
+ Returns:
82
+ A fully-resolved MultiRoundChatConfig.
83
+ """
84
+ tokens_per_session = shared_prompt_length + chat_history_length
85
+ total_tokens = kv_cache_volume_gb * tokens_per_gb_kvcache
86
+ num_users = max(1, int(total_tokens / tokens_per_session))
87
+ logger.debug(
88
+ "Computed num_concurrent_users=%d from kv_cache_volume_gb=%.1f, "
89
+ "tokens_per_gb_kvcache=%d, tokens_per_session=%d",
90
+ num_users,
91
+ kv_cache_volume_gb,
92
+ tokens_per_gb_kvcache,
93
+ tokens_per_session,
94
+ )
95
+ return cls(
96
+ shared_prompt_length=shared_prompt_length,
97
+ chat_history_length=chat_history_length,
98
+ user_input_length=user_input_length,
99
+ output_length=output_length,
100
+ qps=qps,
101
+ duration=duration,
102
+ num_concurrent_users=num_users,
103
+ )
104
+
105
+
106
+ @dataclass
107
+ class Session:
108
+ """A single stateful chat session.
109
+
110
+ Maintains a system prompt, pre-filled history, and accumulated
111
+ Q&A exchanges. The ``in_flight`` flag tracks whether a request
112
+ is currently pending for this session.
113
+ """
114
+
115
+ session_id: int
116
+ system_prompt: str
117
+ history_text: str
118
+ exchanges: list[tuple[str, str]] = field(default_factory=list)
119
+ in_flight: bool = False
120
+
121
+ def build_messages(self, query: str) -> list[dict[str, str]]:
122
+ """Construct OpenAI-format messages for a request."""
123
+ messages: list[dict[str, str]] = [
124
+ {"role": "system", "content": self.system_prompt},
125
+ ]
126
+ if self.history_text:
127
+ messages.append(
128
+ {"role": "user", "content": self.history_text},
129
+ )
130
+ messages.append(
131
+ {
132
+ "role": "assistant",
133
+ "content": "Understood, I have read the context above.",
134
+ },
135
+ )
136
+ for q, a in self.exchanges:
137
+ messages.append({"role": "user", "content": q})
138
+ messages.append({"role": "assistant", "content": a})
139
+ messages.append({"role": "user", "content": query})
140
+ return messages
141
+
142
+ def record_answer(self, query: str, answer: str) -> None:
143
+ """Record a completed exchange and mark session as ready."""
144
+ self.exchanges.append((query, answer))
145
+ self.in_flight = False
146
+
147
+
148
+ class MultiRoundChatWorkload(BaseWorkload):
149
+ """Workload that simulates multi-round chat with stateful sessions.
150
+
151
+ Creates multiple concurrent user sessions, dispatches requests at a
152
+ fixed QPS rate using round-robin scheduling, and records responses
153
+ in session history so subsequent queries include prior context.
154
+ """
155
+
156
+ def __init__(
157
+ self,
158
+ config: MultiRoundChatConfig,
159
+ request_sender: RequestSender,
160
+ stats_collector: StatsCollector,
161
+ progress_monitor: ProgressMonitor,
162
+ seed: int = 42,
163
+ ) -> None:
164
+ super().__init__(request_sender, stats_collector, progress_monitor)
165
+ self._config = config
166
+ self._seed = seed
167
+
168
+ self._sessions = self._create_sessions()
169
+ self._global_index = 0
170
+ self._interval = 1.0 / config.qps
171
+ self._pending_info: dict[str, tuple[int, str]] = {}
172
+ self._pending_tasks: set[asyncio.Task] = set()
173
+
174
+ def log_config(self) -> None:
175
+ """Log key workload config before the benchmark starts."""
176
+ c = self._config
177
+ B = "\033[1m" # bold
178
+ C = "\033[96m" # cyan
179
+ Y = "\033[93m" # yellow
180
+ R = "\033[0m" # reset
181
+ print(
182
+ f"{B}{'═' * 50}{R}\n"
183
+ f"{B} Workload: {C}multi-round-chat{R}\n"
184
+ f"{B}{'─' * 50}{R}\n"
185
+ f" Concurrent users: {Y}{c.num_concurrent_users}{R}\n"
186
+ f" Prompt length: {Y}{c.shared_prompt_length}{R} tokens\n"
187
+ f" History length: {Y}{c.chat_history_length}{R} tokens\n"
188
+ f" Query length: {Y}{c.user_input_length}{R} tokens\n"
189
+ f" Output length: {Y}{c.output_length}{R} tokens\n"
190
+ f" QPS: {Y}{c.qps}{R}\n"
191
+ f" Duration: {Y}{c.duration}s{R}\n"
192
+ f"{B}{'═' * 50}{R}"
193
+ )
194
+
195
+ # ------------------------------------------------------------------
196
+ # Session creation
197
+ # ------------------------------------------------------------------
198
+
199
+ def _create_sessions(self) -> list[Session]:
200
+ """Create sessions with synthetic prompts and history."""
201
+ sessions: list[Session] = []
202
+ for i in range(self._config.num_concurrent_users):
203
+ system_prompt = self._generate_system_prompt(i)
204
+ history = self._generate_history(i)
205
+ sessions.append(
206
+ Session(
207
+ session_id=i,
208
+ system_prompt=system_prompt,
209
+ history_text=history,
210
+ )
211
+ )
212
+ logger.debug(
213
+ "Created %d sessions (prompt=%d, history=%d tokens each)",
214
+ len(sessions),
215
+ self._config.shared_prompt_length,
216
+ self._config.chat_history_length,
217
+ )
218
+ return sessions
219
+
220
+ def _generate_system_prompt(self, session_id: int) -> str:
221
+ """Generate a system prompt of approximately ``shared_prompt_length`` tokens."""
222
+ prefix = f"Session {session_id}. You are a helpful assistant. "
223
+ remaining = max(0, self._config.shared_prompt_length - len(prefix.split()))
224
+ return prefix + " ".join(["help"] * remaining)
225
+
226
+ def _generate_history(self, session_id: int) -> str:
227
+ """
228
+ Generate pre-filled history of approximately ``chat_history_length`` tokens.
229
+ """
230
+ prefix = f"[Session {session_id} history] "
231
+ remaining = max(0, self._config.chat_history_length - len(prefix.split()))
232
+ return prefix + " ".join(["hi"] * remaining)
233
+
234
+ def _generate_query(self) -> str:
235
+ """Generate a user query of approximately ``user_input_length`` tokens."""
236
+ return " ".join(["tell"] * self._config.user_input_length)
237
+
238
+ # ------------------------------------------------------------------
239
+ # Warmup
240
+ # ------------------------------------------------------------------
241
+
242
+ async def warmup(self) -> None:
243
+ """Send one warmup request per session with ``max_tokens=1``."""
244
+ num_sessions = len(self._sessions)
245
+ for session in self._sessions:
246
+ request_id = f"warmup_s{session.session_id}"
247
+ messages = session.build_messages("Hello")
248
+ self._progress_monitor.log_message(
249
+ f"Warmup {session.session_id + 1}/{num_sessions}"
250
+ )
251
+ self._progress_monitor.on_request_sent(request_id)
252
+ result = await self._request_sender.send_warmup_request(
253
+ request_id,
254
+ messages,
255
+ )
256
+ if not result.successful:
257
+ self._progress_monitor.log_message(
258
+ f"Warmup session {session.session_id} failed: {result.error}"
259
+ )
260
+ self._progress_monitor.log_message(f"Warmup complete: {num_sessions} sessions")
261
+
262
+ # ------------------------------------------------------------------
263
+ # Benchmark dispatch
264
+ # ------------------------------------------------------------------
265
+
266
+ async def step(self, time_offset: float) -> float:
267
+ """Dispatch the next request at the QPS-controlled rate.
268
+
269
+ Returns:
270
+ Next wakeup time offset, or negative when done.
271
+ """
272
+ # Check duration — stop dispatching new requests
273
+ if time_offset >= self._config.duration:
274
+ if self._pending_tasks:
275
+ await asyncio.wait(
276
+ self._pending_tasks,
277
+ return_when=asyncio.FIRST_COMPLETED,
278
+ )
279
+ return 0.0
280
+ return -1.0
281
+
282
+ # Find ready session via round-robin
283
+ target_idx = self._global_index % len(self._sessions)
284
+ session = self._sessions[target_idx]
285
+
286
+ if session.in_flight:
287
+ # Session busy — sleep briefly, let loop drain queue
288
+ return time_offset + 0.01
289
+
290
+ # Dispatch request
291
+ query = self._generate_query()
292
+ request_id = f"s{session.session_id}_r{self._global_index}"
293
+ messages = session.build_messages(query)
294
+
295
+ session.in_flight = True
296
+ self._pending_info[request_id] = (session.session_id, query)
297
+ self._progress_monitor.on_request_sent(request_id)
298
+ self._progress_monitor.log_message(
299
+ f"Session {session.session_id} dispatched request {self._global_index}"
300
+ )
301
+
302
+ task = asyncio.create_task(
303
+ self._dispatch(request_id, messages),
304
+ )
305
+ self._pending_tasks.add(task)
306
+ task.add_done_callback(self._on_task_done)
307
+
308
+ self._global_index += 1
309
+ return self._global_index * self._interval
310
+
311
+ async def _dispatch(
312
+ self,
313
+ request_id: str,
314
+ messages: list[dict[str, str]],
315
+ ) -> None:
316
+ """Send a single benchmark request."""
317
+ await self._request_sender.send_request(
318
+ request_id,
319
+ messages,
320
+ max_tokens=self._config.output_length,
321
+ )
322
+
323
+ def _on_task_done(self, task: asyncio.Task) -> None:
324
+ """Clean up completed tasks and log unexpected errors."""
325
+ self._pending_tasks.discard(task)
326
+ if not task.cancelled():
327
+ exc = task.exception()
328
+ if exc is not None:
329
+ self._progress_monitor.log_message(f"Dispatch task failed: {exc}")
330
+
331
+ def on_request_finished(self, request_id: str, output: str) -> None:
332
+ """Record the response in the session's conversation history."""
333
+ info = self._pending_info.pop(request_id, None)
334
+ if info is None:
335
+ return # warmup request or already processed
336
+ session_id, query = info
337
+ self._sessions[session_id].record_answer(query, output)