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,291 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Terminal UI primitives for interactive configuration.
3
+
4
+ Provides simple prompt functions for collecting user input:
5
+
6
+ - ``prompt_text`` — free-form text with optional default
7
+ - ``prompt_number`` — numeric input with type validation
8
+ - ``prompt_bool`` — Y/N confirmation
9
+ - ``prompt_choice`` — arrow-key selection (falls back to numbered list on non-TTY)
10
+ """
11
+
12
+ # Standard
13
+ import sys
14
+
15
+ # ---------------------------------------------------------------------------
16
+ # ANSI helpers
17
+ # ---------------------------------------------------------------------------
18
+
19
+ BOLD = "\033[1m"
20
+ CYAN = "\033[96m"
21
+ GREEN = "\033[92m"
22
+ YELLOW = "\033[93m"
23
+ DIM = "\033[2m"
24
+ RESET = "\033[0m"
25
+ CLEAR_LINE = "\033[2K"
26
+ CURSOR_UP = "\033[A"
27
+
28
+
29
+ def _is_tty() -> bool:
30
+ return hasattr(sys.stdin, "fileno") and sys.stdin.isatty()
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # prompt_text
35
+ # ---------------------------------------------------------------------------
36
+
37
+
38
+ def prompt_text(
39
+ label: str,
40
+ description: str = "",
41
+ default: str = "",
42
+ ) -> str:
43
+ """Prompt for free-form text input.
44
+
45
+ Args:
46
+ label: The config item name shown as a heading.
47
+ description: One-line explanation shown below the label.
48
+ default: Default value; shown in brackets, returned on empty Enter.
49
+
50
+ Returns:
51
+ The user's input, or the default if they pressed Enter.
52
+ """
53
+ print()
54
+ print(f"{BOLD}{label}{RESET}")
55
+ if description:
56
+ print(f" {description}")
57
+ if default:
58
+ prompt = f" {DIM}[default: {default}]{RESET} {GREEN}>{RESET} "
59
+ else:
60
+ prompt = f" {GREEN}>{RESET} "
61
+ value = input(prompt).strip()
62
+ return value if value else default
63
+
64
+
65
+ # ---------------------------------------------------------------------------
66
+ # prompt_number
67
+ # ---------------------------------------------------------------------------
68
+
69
+
70
+ def prompt_number(
71
+ label: str,
72
+ description: str = "",
73
+ default: float | int | None = None,
74
+ number_type: type = int,
75
+ ) -> float | int:
76
+ """Prompt for a numeric value with validation.
77
+
78
+ Args:
79
+ label: The config item name.
80
+ description: One-line explanation.
81
+ default: Default value; returned on empty Enter. None means required.
82
+ number_type: ``int`` or ``float``.
83
+
84
+ Returns:
85
+ The parsed number.
86
+ """
87
+ print()
88
+ print(f"{BOLD}{label}{RESET}")
89
+ if description:
90
+ print(f" {description}")
91
+
92
+ while True:
93
+ if default is not None:
94
+ prompt = f" {DIM}[default: {default}]{RESET} {GREEN}>{RESET} "
95
+ else:
96
+ prompt = f" {GREEN}>{RESET} "
97
+ raw = input(prompt).strip()
98
+ if not raw and default is not None:
99
+ return default
100
+ try:
101
+ return number_type(raw)
102
+ except (ValueError, TypeError):
103
+ type_name = "integer" if number_type is int else "number"
104
+ print(f" {YELLOW}Please enter a valid {type_name}.{RESET}")
105
+
106
+
107
+ # ---------------------------------------------------------------------------
108
+ # prompt_bool
109
+ # ---------------------------------------------------------------------------
110
+
111
+
112
+ def prompt_bool(
113
+ label: str,
114
+ description: str = "",
115
+ default: bool = True,
116
+ ) -> bool:
117
+ """Prompt for a yes/no confirmation.
118
+
119
+ Shows ``[Y/n]`` or ``[y/N]`` depending on the default.
120
+ Enter alone accepts the default.
121
+
122
+ Args:
123
+ label: The config item name.
124
+ description: One-line explanation.
125
+ default: Default value when user presses Enter.
126
+
127
+ Returns:
128
+ True for yes, False for no.
129
+ """
130
+ print()
131
+ print(f"{BOLD}{label}{RESET}")
132
+ if description:
133
+ print(f" {description}")
134
+
135
+ hint = "[default: Y] (Y/n)" if default else "[default: N] (y/N)"
136
+ while True:
137
+ raw = input(f" {DIM}{hint}{RESET} {GREEN}>{RESET} ").strip().lower()
138
+ if not raw:
139
+ return default
140
+ if raw in ("y", "yes"):
141
+ return True
142
+ if raw in ("n", "no"):
143
+ return False
144
+ print(f" {YELLOW}Please enter Y or N.{RESET}")
145
+
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # prompt_choice — arrow-key selection
149
+ # ---------------------------------------------------------------------------
150
+
151
+
152
+ def _read_key() -> str:
153
+ """Read a single keypress from stdin in raw mode.
154
+
155
+ Returns:
156
+ ``"up"``, ``"down"``, ``"enter"``, or the literal character.
157
+ """
158
+ # Standard
159
+ import termios
160
+ import tty
161
+
162
+ fd = sys.stdin.fileno()
163
+ old_settings = termios.tcgetattr(fd)
164
+ try:
165
+ tty.setraw(fd)
166
+ ch = sys.stdin.read(1)
167
+ if ch == "\r" or ch == "\n":
168
+ return "enter"
169
+ if ch == "\x1b":
170
+ seq = sys.stdin.read(2)
171
+ if seq == "[A":
172
+ return "up"
173
+ if seq == "[B":
174
+ return "down"
175
+ return "escape"
176
+ return ch
177
+ finally:
178
+ termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
179
+
180
+
181
+ def _render_choices(
182
+ choices: list[tuple[str, str]],
183
+ selected: int,
184
+ ) -> list[str]:
185
+ """Build display lines for the choice selector."""
186
+ lines: list[str] = []
187
+ for i, (value, desc) in enumerate(choices):
188
+ if i == selected:
189
+ marker = f"{CYAN}● {value}{RESET}"
190
+ else:
191
+ marker = f" {value}"
192
+ # Pad value to align descriptions
193
+ padding = max(0, 18 - len(value))
194
+ line = f" {marker}{' ' * padding} {DIM}{desc}{RESET}"
195
+ lines.append(line)
196
+ return lines
197
+
198
+
199
+ def prompt_choice(
200
+ label: str,
201
+ description: str = "",
202
+ choices: list[tuple[str, str]] = [], # noqa: B006
203
+ default: str = "",
204
+ ) -> str:
205
+ """Prompt user to select from a list using arrow keys.
206
+
207
+ Each choice is a ``(value, description)`` tuple. The description is
208
+ shown next to the value as a brief explanation.
209
+
210
+ Falls back to numbered input on non-TTY stdin.
211
+
212
+ Args:
213
+ label: The config item name.
214
+ description: One-line explanation.
215
+ choices: List of ``(value, one_line_description)`` tuples.
216
+ default: Pre-selected value. Defaults to the first choice.
217
+
218
+ Returns:
219
+ The selected value string.
220
+ """
221
+ if not choices:
222
+ raise ValueError("choices must not be empty")
223
+
224
+ # Find default index
225
+ selected = 0
226
+ for i, (val, _desc) in enumerate(choices):
227
+ if val == default:
228
+ selected = i
229
+ break
230
+
231
+ print()
232
+ print(f"{BOLD}{label}{RESET}")
233
+ if description:
234
+ print(f" {description}")
235
+
236
+ # Non-TTY fallback: numbered list
237
+ if not _is_tty():
238
+ return _prompt_choice_fallback(choices, selected)
239
+
240
+ print(f" {DIM}Use ↑↓ to navigate, Enter to select.{RESET}")
241
+ print()
242
+
243
+ # Initial render
244
+ lines = _render_choices(choices, selected)
245
+ for line in lines:
246
+ print(line)
247
+
248
+ while True:
249
+ key = _read_key()
250
+ if key == "up":
251
+ selected = (selected - 1) % len(choices)
252
+ elif key == "down":
253
+ selected = (selected + 1) % len(choices)
254
+ elif key == "enter":
255
+ # Clear and re-render final state
256
+ num_lines = len(choices)
257
+ sys.stdout.write(f"\r{CURSOR_UP * num_lines}")
258
+ lines = _render_choices(choices, selected)
259
+ for line in lines:
260
+ print(f"{CLEAR_LINE}{line}")
261
+ return choices[selected][0]
262
+ else:
263
+ continue
264
+
265
+ # Redraw
266
+ num_lines = len(choices)
267
+ sys.stdout.write(f"\r{CURSOR_UP * num_lines}")
268
+ lines = _render_choices(choices, selected)
269
+ for line in lines:
270
+ print(f"{CLEAR_LINE}{line}")
271
+
272
+
273
+ def _prompt_choice_fallback(
274
+ choices: list[tuple[str, str]],
275
+ default_index: int,
276
+ ) -> str:
277
+ """Numbered fallback for non-TTY environments."""
278
+ for i, (val, desc) in enumerate(choices):
279
+ marker = "*" if i == default_index else " "
280
+ print(f" {marker} {i + 1}) {val} — {desc}")
281
+ while True:
282
+ raw = input(f" [default: {default_index + 1}] {GREEN}>{RESET} ").strip()
283
+ if not raw:
284
+ return choices[default_index][0]
285
+ try:
286
+ idx = int(raw) - 1
287
+ if 0 <= idx < len(choices):
288
+ return choices[idx][0]
289
+ except ValueError:
290
+ pass
291
+ print(f" {YELLOW}Enter a number 1-{len(choices)}.{RESET}")
@@ -0,0 +1,145 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Real-time terminal display for ``lmcache bench engine``."""
3
+
4
+ # Standard
5
+ import threading
6
+ import time
7
+
8
+ # First Party
9
+ from lmcache.cli.commands.bench.engine_bench.stats import StatsCollector
10
+
11
+ # ANSI escape codes
12
+ CLEAR_LINE = "\033[2K"
13
+ CURSOR_UP = "\033[A"
14
+ BOLD = "\033[1m"
15
+ GREEN = "\033[92m"
16
+ YELLOW = "\033[93m"
17
+ CYAN = "\033[96m"
18
+ RED = "\033[91m"
19
+ RESET = "\033[0m"
20
+
21
+
22
+ class ProgressMonitor:
23
+ """Real-time terminal display for benchmark progress.
24
+
25
+ Runs a daemon thread that redraws stats every second using ANSI
26
+ cursor control for in-place updates. Reads aggregated metrics
27
+ from a ``StatsCollector`` and tracks in-flight count internally.
28
+ """
29
+
30
+ LOG_LINES = 5
31
+ DISPLAY_LINES = 9 + LOG_LINES # stats block + log lines
32
+
33
+ def __init__(
34
+ self,
35
+ stats_collector: StatsCollector,
36
+ quiet: bool = False,
37
+ ) -> None:
38
+ self._stats_collector = stats_collector
39
+ self._quiet = quiet
40
+
41
+ self._lock = threading.Lock()
42
+ self._in_flight: int = 0
43
+ self._log_messages: list[str] = []
44
+
45
+ self._running: bool = False
46
+ self._thread: threading.Thread = None # type: ignore[assignment]
47
+ self._first_draw: bool = True
48
+ self._start_time: float = time.monotonic()
49
+
50
+ # ------------------------------------------------------------------
51
+ # Public API
52
+ # ------------------------------------------------------------------
53
+
54
+ def start(self) -> None:
55
+ """Start the display daemon thread."""
56
+ if self._quiet:
57
+ return
58
+ self._start_time = time.monotonic()
59
+ self._running = True
60
+ self._first_draw = True
61
+ self._thread = threading.Thread(target=self._display_loop, daemon=True)
62
+ self._thread.start()
63
+
64
+ def stop(self) -> None:
65
+ """Stop the display thread and print final state."""
66
+ self._running = False
67
+ if self._thread is not None:
68
+ self._thread.join(timeout=3)
69
+ if not self._quiet:
70
+ self._draw()
71
+ print() # final newline
72
+
73
+ def on_request_sent(self, request_id: str) -> None:
74
+ """Called when a request is dispatched. Increments in-flight."""
75
+ with self._lock:
76
+ self._in_flight += 1
77
+
78
+ def on_request_finished(self, request_id: str, successful: bool) -> None:
79
+ """Called when a request completes. Decrements in-flight."""
80
+ with self._lock:
81
+ self._in_flight = max(self._in_flight - 1, 0)
82
+
83
+ def log_message(self, message: str) -> None:
84
+ """Add a log line to the display. Thread-safe."""
85
+ with self._lock:
86
+ self._log_messages.append(message)
87
+ if len(self._log_messages) > self.LOG_LINES:
88
+ self._log_messages = self._log_messages[-self.LOG_LINES :]
89
+
90
+ # ------------------------------------------------------------------
91
+ # Internal
92
+ # ------------------------------------------------------------------
93
+
94
+ def _display_loop(self) -> None:
95
+ """Background loop: redraw every second."""
96
+ while self._running:
97
+ self._draw()
98
+ time.sleep(1.0)
99
+
100
+ def _draw(self) -> None:
101
+ """Render the real-time stats display."""
102
+ # Read stats from collector (already thread-safe)
103
+ stats = self._stats_collector.get_current_stats()
104
+
105
+ # Read local state under lock
106
+ with self._lock:
107
+ in_flight = self._in_flight
108
+ log_messages = list(self._log_messages)
109
+
110
+ elapsed = time.monotonic() - self._start_time
111
+
112
+ # Move cursor up to overwrite previous display
113
+ if not self._first_draw:
114
+ print(f"\r{CURSOR_UP * self.DISPLAY_LINES}", end="")
115
+ self._first_draw = False
116
+
117
+ lines = [
118
+ f"{BOLD}{'─' * 50}{RESET}",
119
+ (f"{BOLD} Engine Benchmark{RESET} elapsed: {CYAN}{elapsed:.0f}s{RESET}"),
120
+ (
121
+ f" {GREEN}{stats.successful_requests}{RESET} successful"
122
+ f" {CYAN}{in_flight}{RESET} in-flight"
123
+ f" {RED}{stats.failed_requests}{RESET} failed"
124
+ ),
125
+ f" Avg TTFT: {YELLOW}{stats.mean_ttft_ms:.1f} ms{RESET}",
126
+ (f" Avg decode: {YELLOW}{stats.mean_decode_speed:.1f} tok/s{RESET}"),
127
+ f" Input tokens: {stats.total_input_tokens:,}",
128
+ f" Output tokens: {stats.total_output_tokens:,}",
129
+ (
130
+ f" Throughput: "
131
+ f"{stats.input_throughput:.1f} in "
132
+ f"/ {stats.output_throughput:.1f} out tok/s"
133
+ ),
134
+ f"{BOLD}{'─' * 50}{RESET}",
135
+ ]
136
+
137
+ # Pad log lines to fixed count
138
+ for i in range(self.LOG_LINES):
139
+ if i < len(log_messages):
140
+ lines.append(f" {YELLOW}[log] {log_messages[i]}{RESET}")
141
+ else:
142
+ lines.append("")
143
+
144
+ for line in lines:
145
+ print(f"{CLEAR_LINE}{line}")
@@ -0,0 +1,232 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Async streaming request sender for ``lmcache bench engine``."""
3
+
4
+ # Standard
5
+ from collections.abc import Callable
6
+ import collections.abc
7
+ import os
8
+ import time
9
+
10
+ # Third Party
11
+ from openai import AsyncOpenAI
12
+
13
+ # First Party
14
+ from lmcache.cli.commands.bench.engine_bench.stats import RequestResult
15
+ from lmcache.logging import init_logger
16
+
17
+ logger = init_logger(__name__)
18
+
19
+ # Callback signature: (result, response_text) -> None
20
+ OnFinishedCallback = Callable[[RequestResult, str], None]
21
+
22
+
23
+ def _normalize_url(engine_url: str) -> str:
24
+ """Ensure *engine_url* has a scheme and ends with ``/v1``."""
25
+ url = engine_url.rstrip("/")
26
+ if not url.startswith(("http://", "https://")):
27
+ url = f"http://{url}"
28
+ if not url.endswith("/v1"):
29
+ url += "/v1"
30
+ return url
31
+
32
+
33
+ def _extract_content(chunk: object, completions_mode: bool) -> str:
34
+ """Return text content from a streaming chunk, or ``""`` if none.
35
+
36
+ Ported from Tensormesh-Benchmark ``streaming_utils.py``.
37
+ """
38
+ choices = getattr(chunk, "choices", None)
39
+ if not choices:
40
+ return ""
41
+
42
+ choice = choices[0]
43
+
44
+ if completions_mode:
45
+ text = getattr(choice, "text", None)
46
+ return text if text is not None else ""
47
+
48
+ # Chat mode: delta.content, with fallback for reasoning_content
49
+ delta = getattr(choice, "delta", None)
50
+ if delta is None:
51
+ return ""
52
+ content = getattr(delta, "content", None)
53
+ if content is not None:
54
+ return content
55
+ # Fallback for reasoning models
56
+ for attr in ("reasoning_content", "reasoning"):
57
+ fallback = getattr(delta, attr, None)
58
+ if fallback is not None:
59
+ return fallback
60
+ return ""
61
+
62
+
63
+ class RequestSender:
64
+ """Async streaming request sender for inference engines.
65
+
66
+ Each ``send_request`` call is a self-contained coroutine.
67
+ Concurrency is controlled externally by the workload module.
68
+ """
69
+
70
+ def __init__(
71
+ self,
72
+ engine_url: str,
73
+ model: str,
74
+ completions_mode: bool = False,
75
+ on_finished: list[OnFinishedCallback] = [], # noqa: B006
76
+ ) -> None:
77
+ self._model = model
78
+ self._completions_mode = completions_mode
79
+ self._on_finished = list(on_finished)
80
+
81
+ base_url = _normalize_url(engine_url)
82
+ api_key = os.getenv("OPENAI_API_KEY", "")
83
+ if not api_key:
84
+ api_key = "sk-dummy"
85
+ logger.debug("API key source: default dummy key")
86
+ else:
87
+ logger.debug("API key source: OPENAI_API_KEY env var")
88
+
89
+ self._client = AsyncOpenAI(
90
+ base_url=base_url,
91
+ api_key=api_key,
92
+ timeout=None,
93
+ )
94
+
95
+ def add_on_finished_callback(self, callback: OnFinishedCallback) -> None:
96
+ """Register a callback to be invoked when a request finishes."""
97
+ self._on_finished.append(callback)
98
+
99
+ async def send_request(
100
+ self,
101
+ request_id: str,
102
+ messages: list[dict[str, str]],
103
+ max_tokens: int = 128,
104
+ ) -> RequestResult:
105
+ """Send a single streaming request and return the result.
106
+
107
+ Streams the response via SSE, measures TTFT, decode speed, and
108
+ total latency. Extracts token counts from server usage reports.
109
+ After collecting the result, invokes all registered
110
+ ``on_finished`` callbacks.
111
+ """
112
+ submit_time = time.time()
113
+ first_token_time = 0.0
114
+ tokens: list[str] = []
115
+ num_input_tokens = 0
116
+ num_output_tokens = 0
117
+
118
+ try:
119
+ response = await self._create_stream(messages, max_tokens)
120
+
121
+ async for chunk in response:
122
+ # Extract usage from final chunk
123
+ usage = getattr(chunk, "usage", None)
124
+ if usage is not None:
125
+ pt = getattr(usage, "prompt_tokens", 0)
126
+ ct = getattr(usage, "completion_tokens", 0)
127
+ if pt:
128
+ num_input_tokens = pt
129
+ if ct:
130
+ num_output_tokens = ct
131
+
132
+ content = _extract_content(chunk, self._completions_mode)
133
+ if content:
134
+ if not first_token_time:
135
+ first_token_time = time.time()
136
+ tokens.append(content)
137
+
138
+ finish_time = time.time()
139
+ successful = first_token_time > 0.0
140
+ ttft = (first_token_time - submit_time) if successful else -1.0
141
+ request_latency = finish_time - submit_time
142
+ decode_time = (finish_time - first_token_time) if successful else 0.0
143
+ num_output = num_output_tokens if num_output_tokens > 0 else len(tokens)
144
+ decode_speed = (num_output / decode_time) if decode_time > 0 else 0.0
145
+
146
+ result = RequestResult(
147
+ request_id=request_id,
148
+ successful=successful,
149
+ ttft=ttft,
150
+ request_latency=request_latency,
151
+ num_input_tokens=num_input_tokens,
152
+ num_output_tokens=num_output,
153
+ decode_speed=decode_speed,
154
+ submit_time=submit_time,
155
+ first_token_time=first_token_time,
156
+ finish_time=finish_time,
157
+ error="",
158
+ )
159
+ response_text = "".join(tokens)
160
+
161
+ except Exception as e:
162
+ finish_time = time.time()
163
+ result = RequestResult(
164
+ request_id=request_id,
165
+ successful=False,
166
+ ttft=-1.0,
167
+ request_latency=finish_time - submit_time,
168
+ num_input_tokens=0,
169
+ num_output_tokens=0,
170
+ decode_speed=0.0,
171
+ submit_time=submit_time,
172
+ first_token_time=0.0,
173
+ finish_time=finish_time,
174
+ error=str(e),
175
+ )
176
+ response_text = ""
177
+ logger.debug(
178
+ "Request %s failed: %s",
179
+ request_id,
180
+ e,
181
+ )
182
+
183
+ for cb in self._on_finished:
184
+ cb(result, response_text)
185
+
186
+ return result
187
+
188
+ async def send_warmup_request(
189
+ self,
190
+ request_id: str,
191
+ messages: list[dict[str, str]],
192
+ max_tokens: int = 1,
193
+ ) -> RequestResult:
194
+ """Send a warmup request (``max_tokens=1`` by default)."""
195
+ return await self.send_request(
196
+ request_id,
197
+ messages,
198
+ max_tokens=max_tokens,
199
+ )
200
+
201
+ async def close(self) -> None:
202
+ """Close the underlying HTTP client."""
203
+ await self._client.close()
204
+
205
+ # ------------------------------------------------------------------
206
+ # Internal
207
+ # ------------------------------------------------------------------
208
+
209
+ async def _create_stream(
210
+ self,
211
+ messages: list[dict[str, str]],
212
+ max_tokens: int,
213
+ ) -> collections.abc.AsyncIterator:
214
+ """Dispatch the streaming API call (chat or completions)."""
215
+ if self._completions_mode:
216
+ prompt = messages[0]["content"] if messages else ""
217
+ return await self._client.completions.create(
218
+ model=self._model,
219
+ prompt=prompt,
220
+ stream=True,
221
+ max_tokens=max_tokens,
222
+ temperature=0.0,
223
+ stream_options={"include_usage": True},
224
+ )
225
+ return await self._client.chat.completions.create(
226
+ model=self._model,
227
+ messages=messages,
228
+ stream=True,
229
+ max_tokens=max_tokens,
230
+ temperature=0.0,
231
+ stream_options={"include_usage": True},
232
+ )