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.
- lmcache/__init__.py +84 -0
- lmcache/_version.py +24 -0
- lmcache/cli/__init__.py +1 -0
- lmcache/cli/commands/__init__.py +34 -0
- lmcache/cli/commands/base.py +157 -0
- lmcache/cli/commands/bench/__init__.py +557 -0
- lmcache/cli/commands/bench/engine_bench/__init__.py +1 -0
- lmcache/cli/commands/bench/engine_bench/config.py +245 -0
- lmcache/cli/commands/bench/engine_bench/interactive/__init__.py +274 -0
- lmcache/cli/commands/bench/engine_bench/interactive/config.json +10 -0
- lmcache/cli/commands/bench/engine_bench/interactive/schema.py +352 -0
- lmcache/cli/commands/bench/engine_bench/interactive/state.py +327 -0
- lmcache/cli/commands/bench/engine_bench/interactive/terminal.py +291 -0
- lmcache/cli/commands/bench/engine_bench/progress.py +145 -0
- lmcache/cli/commands/bench/engine_bench/request_sender.py +232 -0
- lmcache/cli/commands/bench/engine_bench/stats.py +275 -0
- lmcache/cli/commands/bench/engine_bench/workloads/__init__.py +153 -0
- lmcache/cli/commands/bench/engine_bench/workloads/base.py +122 -0
- lmcache/cli/commands/bench/engine_bench/workloads/long_doc_permutator.py +435 -0
- lmcache/cli/commands/bench/engine_bench/workloads/long_doc_qa.py +281 -0
- lmcache/cli/commands/bench/engine_bench/workloads/multi_round_chat.py +337 -0
- lmcache/cli/commands/bench/engine_bench/workloads/random_prefill.py +178 -0
- lmcache/cli/commands/describe.py +310 -0
- lmcache/cli/commands/kvcache.py +133 -0
- lmcache/cli/commands/mock.py +75 -0
- lmcache/cli/commands/ping.py +113 -0
- lmcache/cli/commands/query/__init__.py +155 -0
- lmcache/cli/commands/query/prompt.py +134 -0
- lmcache/cli/commands/query/request.py +357 -0
- lmcache/cli/commands/server.py +99 -0
- lmcache/cli/commands/tool/__init__.py +63 -0
- lmcache/cli/commands/tool/cache_simulator.py +113 -0
- lmcache/cli/commands/trace/__init__.py +505 -0
- lmcache/cli/commands/trace/dispatch.py +249 -0
- lmcache/cli/commands/trace/driver.py +372 -0
- lmcache/cli/commands/trace/stats.py +289 -0
- lmcache/cli/documents/lmcache.txt +11 -0
- lmcache/cli/main.py +42 -0
- lmcache/cli/metrics/__init__.py +29 -0
- lmcache/cli/metrics/formatter.py +171 -0
- lmcache/cli/metrics/handler.py +94 -0
- lmcache/cli/metrics/metrics.py +161 -0
- lmcache/cli/metrics/section.py +77 -0
- lmcache/connections.py +173 -0
- lmcache/integration/__init__.py +2 -0
- lmcache/integration/base_service_factory.py +165 -0
- lmcache/integration/request_telemetry/__init__.py +1 -0
- lmcache/integration/request_telemetry/base.py +51 -0
- lmcache/integration/request_telemetry/factory.py +113 -0
- lmcache/integration/request_telemetry/fastapi.py +109 -0
- lmcache/integration/request_telemetry/noop.py +35 -0
- lmcache/integration/sglang/__init__.py +2 -0
- lmcache/integration/sglang/sglang_adapter.py +326 -0
- lmcache/integration/sglang/utils.py +39 -0
- lmcache/integration/vllm/__init__.py +1 -0
- lmcache/integration/vllm/lmcache_connector_v1.py +213 -0
- lmcache/integration/vllm/lmcache_connector_v1_085.py +150 -0
- lmcache/integration/vllm/lmcache_mp_connector_0180.py +1072 -0
- lmcache/integration/vllm/tests/test_mm_hash_utils.py +112 -0
- lmcache/integration/vllm/utils.py +433 -0
- lmcache/integration/vllm/vllm_multi_process_adapter.py +1090 -0
- lmcache/integration/vllm/vllm_service_factory.py +339 -0
- lmcache/integration/vllm/vllm_v1_adapter.py +1713 -0
- lmcache/logging.py +107 -0
- lmcache/native_storage_ops.pyi +230 -0
- lmcache/non_cuda_equivalents.py +1424 -0
- lmcache/observability.py +1958 -0
- lmcache/storage_backend/serde/__init__.py +1 -0
- lmcache/storage_backend/serde/cachegen_basics.py +210 -0
- lmcache/storage_backend/serde/cachegen_decoder.py +207 -0
- lmcache/storage_backend/serde/cachegen_encoder.py +394 -0
- lmcache/storage_backend/serde/serde.py +75 -0
- lmcache/tools/__init__.py +1 -0
- lmcache/tools/cache_simulator/README.md +392 -0
- lmcache/tools/cache_simulator/__init__.py +1 -0
- lmcache/tools/cache_simulator/docs/simulate_example.png +0 -0
- lmcache/tools/cache_simulator/docs/sweep_example.png +0 -0
- lmcache/tools/cache_simulator/gen_bench_dataset.py +360 -0
- lmcache/tools/cache_simulator/lru_cache.py +124 -0
- lmcache/tools/cache_simulator/plot_hit_rate.py +231 -0
- lmcache/tools/cache_simulator/simulator.py +795 -0
- lmcache/tools/controller_benchmark/README.md +161 -0
- lmcache/tools/controller_benchmark/__init__.py +1 -0
- lmcache/tools/controller_benchmark/__main__.py +331 -0
- lmcache/tools/controller_benchmark/benchmark.py +660 -0
- lmcache/tools/controller_benchmark/config.py +44 -0
- lmcache/tools/controller_benchmark/constants.py +10 -0
- lmcache/tools/controller_benchmark/handlers/__init__.py +46 -0
- lmcache/tools/controller_benchmark/handlers/admit.py +52 -0
- lmcache/tools/controller_benchmark/handlers/base.py +47 -0
- lmcache/tools/controller_benchmark/handlers/deregister.py +49 -0
- lmcache/tools/controller_benchmark/handlers/evict.py +52 -0
- lmcache/tools/controller_benchmark/handlers/heartbeat.py +56 -0
- lmcache/tools/controller_benchmark/handlers/p2p_lookup.py +47 -0
- lmcache/tools/controller_benchmark/handlers/register.py +56 -0
- lmcache/tools/mp_status_viewer/__init__.py +1 -0
- lmcache/tools/mp_status_viewer/__main__.py +95 -0
- lmcache/usage_context.py +417 -0
- lmcache/utils.py +665 -0
- lmcache/v1/__init__.py +2 -0
- lmcache/v1/api_server/__init__.py +2 -0
- lmcache/v1/api_server/__main__.py +537 -0
- lmcache/v1/basic_check.py +112 -0
- lmcache/v1/cache_controller/__init__.py +9 -0
- lmcache/v1/cache_controller/commands/__init__.py +15 -0
- lmcache/v1/cache_controller/commands/base.py +35 -0
- lmcache/v1/cache_controller/commands/full_sync.py +49 -0
- lmcache/v1/cache_controller/config.py +176 -0
- lmcache/v1/cache_controller/controller_manager.py +535 -0
- lmcache/v1/cache_controller/controllers/__init__.py +11 -0
- lmcache/v1/cache_controller/controllers/full_sync_tracker.py +473 -0
- lmcache/v1/cache_controller/controllers/kv_controller.py +439 -0
- lmcache/v1/cache_controller/controllers/registration_controller.py +282 -0
- lmcache/v1/cache_controller/executor.py +463 -0
- lmcache/v1/cache_controller/frontend/static/css/style.css +201 -0
- lmcache/v1/cache_controller/frontend/static/img/logo.png +0 -0
- lmcache/v1/cache_controller/frontend/static/index.html +234 -0
- lmcache/v1/cache_controller/frontend/static/js/controller_app.js +660 -0
- lmcache/v1/cache_controller/full_sync_sender.py +475 -0
- lmcache/v1/cache_controller/locks.py +149 -0
- lmcache/v1/cache_controller/message.py +828 -0
- lmcache/v1/cache_controller/observability.py +208 -0
- lmcache/v1/cache_controller/utils.py +679 -0
- lmcache/v1/cache_controller/worker.py +665 -0
- lmcache/v1/cache_engine.py +2058 -0
- lmcache/v1/cache_interface.py +19 -0
- lmcache/v1/check/__init__.py +74 -0
- lmcache/v1/check/check_mode_gen.py +86 -0
- lmcache/v1/check/check_mode_test_l2_adapter.py +284 -0
- lmcache/v1/check/check_mode_test_remote.py +155 -0
- lmcache/v1/check/check_mode_test_storage_manager.py +142 -0
- lmcache/v1/check/utils.py +571 -0
- lmcache/v1/compute/__init__.py +2 -0
- lmcache/v1/compute/attention/__init__.py +0 -0
- lmcache/v1/compute/attention/abstract.py +39 -0
- lmcache/v1/compute/attention/flash_attn.py +129 -0
- lmcache/v1/compute/attention/flash_infer_sparse.py +284 -0
- lmcache/v1/compute/attention/metadata.py +85 -0
- lmcache/v1/compute/attention/utils.py +14 -0
- lmcache/v1/compute/blend/__init__.py +7 -0
- lmcache/v1/compute/blend/blender.py +168 -0
- lmcache/v1/compute/blend/metadata.py +34 -0
- lmcache/v1/compute/blend/utils.py +63 -0
- lmcache/v1/compute/models/__init__.py +0 -0
- lmcache/v1/compute/models/base.py +141 -0
- lmcache/v1/compute/models/llama.py +9 -0
- lmcache/v1/compute/models/qwen3.py +24 -0
- lmcache/v1/compute/models/utils.py +68 -0
- lmcache/v1/compute/positional_encoding.py +199 -0
- lmcache/v1/config.py +848 -0
- lmcache/v1/config_base.py +848 -0
- lmcache/v1/distributed/api.py +248 -0
- lmcache/v1/distributed/config.py +321 -0
- lmcache/v1/distributed/error.py +64 -0
- lmcache/v1/distributed/eviction.py +192 -0
- lmcache/v1/distributed/eviction_policy/__init__.py +21 -0
- lmcache/v1/distributed/eviction_policy/factory.py +27 -0
- lmcache/v1/distributed/eviction_policy/lru.py +244 -0
- lmcache/v1/distributed/eviction_policy/noop.py +50 -0
- lmcache/v1/distributed/internal_api.py +170 -0
- lmcache/v1/distributed/l1_manager.py +835 -0
- lmcache/v1/distributed/l2_adapters/__init__.py +67 -0
- lmcache/v1/distributed/l2_adapters/base.py +360 -0
- lmcache/v1/distributed/l2_adapters/config.py +385 -0
- lmcache/v1/distributed/l2_adapters/factory.py +205 -0
- lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +747 -0
- lmcache/v1/distributed/l2_adapters/fs_native_l2_adapter.py +167 -0
- lmcache/v1/distributed/l2_adapters/mock_l2_adapter.py +516 -0
- lmcache/v1/distributed/l2_adapters/mooncake_store_l2_adapter.py +135 -0
- lmcache/v1/distributed/l2_adapters/native_connector_l2_adapter.py +468 -0
- lmcache/v1/distributed/l2_adapters/native_plugin_l2_adapter.py +199 -0
- lmcache/v1/distributed/l2_adapters/nixl_store_dynamic_l2_adapter.py +831 -0
- lmcache/v1/distributed/l2_adapters/nixl_store_l2_adapter.py +983 -0
- lmcache/v1/distributed/l2_adapters/plugin_l2_adapter.py +210 -0
- lmcache/v1/distributed/l2_adapters/resp_l2_adapter.py +176 -0
- lmcache/v1/distributed/memory_manager.py +179 -0
- lmcache/v1/distributed/storage_controller.py +39 -0
- lmcache/v1/distributed/storage_controllers/__init__.py +43 -0
- lmcache/v1/distributed/storage_controllers/eviction_controller.py +242 -0
- lmcache/v1/distributed/storage_controllers/prefetch_controller.py +830 -0
- lmcache/v1/distributed/storage_controllers/prefetch_policy.py +193 -0
- lmcache/v1/distributed/storage_controllers/store_controller.py +452 -0
- lmcache/v1/distributed/storage_controllers/store_policy.py +213 -0
- lmcache/v1/distributed/storage_manager.py +532 -0
- lmcache/v1/event_manager.py +145 -0
- lmcache/v1/exceptions/__init__.py +16 -0
- lmcache/v1/gpu_connector/__init__.py +126 -0
- lmcache/v1/gpu_connector/gpu_connectors.py +1906 -0
- lmcache/v1/gpu_connector/gpu_ops.py +85 -0
- lmcache/v1/gpu_connector/hpu_connector.py +326 -0
- lmcache/v1/gpu_connector/mock_gpu_connector.py +67 -0
- lmcache/v1/gpu_connector/utils.py +890 -0
- lmcache/v1/gpu_connector/xpu_connectors.py +916 -0
- lmcache/v1/health_monitor/__init__.py +1 -0
- lmcache/v1/health_monitor/base.py +587 -0
- lmcache/v1/health_monitor/checks/__init__.py +1 -0
- lmcache/v1/health_monitor/checks/remote_backend_check.py +304 -0
- lmcache/v1/health_monitor/constants.py +36 -0
- lmcache/v1/internal_api_server/__init__.py +0 -0
- lmcache/v1/internal_api_server/api_registry.py +59 -0
- lmcache/v1/internal_api_server/api_server.py +120 -0
- lmcache/v1/internal_api_server/common/__init__.py +1 -0
- lmcache/v1/internal_api_server/common/env_api.py +22 -0
- lmcache/v1/internal_api_server/common/loglevel_api.py +57 -0
- lmcache/v1/internal_api_server/common/metrics_api.py +29 -0
- lmcache/v1/internal_api_server/common/periodic_thread_api.py +138 -0
- lmcache/v1/internal_api_server/common/run_script_api.py +73 -0
- lmcache/v1/internal_api_server/common/thread_api.py +63 -0
- lmcache/v1/internal_api_server/controller/__init__.py +1 -0
- lmcache/v1/internal_api_server/controller/key_stats_api.py +81 -0
- lmcache/v1/internal_api_server/controller/worker_info_api.py +136 -0
- lmcache/v1/internal_api_server/utils.py +43 -0
- lmcache/v1/internal_api_server/vllm/__init__.py +1 -0
- lmcache/v1/internal_api_server/vllm/backend_api.py +221 -0
- lmcache/v1/internal_api_server/vllm/bypass_api.py +204 -0
- lmcache/v1/internal_api_server/vllm/cache_api.py +895 -0
- lmcache/v1/internal_api_server/vllm/chunk_statistics_api.py +141 -0
- lmcache/v1/internal_api_server/vllm/conf_api.py +147 -0
- lmcache/v1/internal_api_server/vllm/freeze_api.py +172 -0
- lmcache/v1/internal_api_server/vllm/hot_cache_api.py +184 -0
- lmcache/v1/internal_api_server/vllm/inference_api.py +65 -0
- lmcache/v1/internal_api_server/vllm/load_fs_chunks_api.py +320 -0
- lmcache/v1/internal_api_server/vllm/lookup_api.py +145 -0
- lmcache/v1/internal_api_server/vllm/version_api.py +25 -0
- lmcache/v1/kv_layer_groups.py +267 -0
- lmcache/v1/lazy_memory_allocator.py +284 -0
- lmcache/v1/lookup_client/__init__.py +25 -0
- lmcache/v1/lookup_client/abstract_client.py +77 -0
- lmcache/v1/lookup_client/async_lookup_message.py +50 -0
- lmcache/v1/lookup_client/chunk_statistics_lookup_client.py +200 -0
- lmcache/v1/lookup_client/factory.py +251 -0
- lmcache/v1/lookup_client/hit_limit_lookup_client.py +86 -0
- lmcache/v1/lookup_client/lmcache_async_lookup_client.py +407 -0
- lmcache/v1/lookup_client/lmcache_lookup_client.py +285 -0
- lmcache/v1/lookup_client/lmcache_lookup_client_bypass.py +99 -0
- lmcache/v1/lookup_client/mooncake_lookup_client.py +87 -0
- lmcache/v1/lookup_client/record_strategies/__init__.py +77 -0
- lmcache/v1/lookup_client/record_strategies/base.py +327 -0
- lmcache/v1/lookup_client/record_strategies/file_hash.py +130 -0
- lmcache/v1/lookup_client/record_strategies/memory_bloom_filter.py +81 -0
- lmcache/v1/manager.py +539 -0
- lmcache/v1/memory_management.py +2619 -0
- lmcache/v1/metadata.py +114 -0
- lmcache/v1/mp_observability/AGENTS.override.md +21 -0
- lmcache/v1/mp_observability/README.md +204 -0
- lmcache/v1/mp_observability/config.py +340 -0
- lmcache/v1/mp_observability/event.py +100 -0
- lmcache/v1/mp_observability/event_bus.py +313 -0
- lmcache/v1/mp_observability/otel_init.py +145 -0
- lmcache/v1/mp_observability/subscribers/__init__.py +28 -0
- lmcache/v1/mp_observability/subscribers/logging/__init__.py +19 -0
- lmcache/v1/mp_observability/subscribers/logging/l1.py +56 -0
- lmcache/v1/mp_observability/subscribers/logging/l2.py +73 -0
- lmcache/v1/mp_observability/subscribers/logging/lookup_hash.py +209 -0
- lmcache/v1/mp_observability/subscribers/logging/mp_server.py +90 -0
- lmcache/v1/mp_observability/subscribers/logging/sm.py +59 -0
- lmcache/v1/mp_observability/subscribers/metrics/__init__.py +20 -0
- lmcache/v1/mp_observability/subscribers/metrics/l0_lifecycle.py +290 -0
- lmcache/v1/mp_observability/subscribers/metrics/l1.py +55 -0
- lmcache/v1/mp_observability/subscribers/metrics/l1_lifecycle.py +166 -0
- lmcache/v1/mp_observability/subscribers/metrics/l2.py +121 -0
- lmcache/v1/mp_observability/subscribers/metrics/sm.py +69 -0
- lmcache/v1/mp_observability/subscribers/tracing/__init__.py +12 -0
- lmcache/v1/mp_observability/subscribers/tracing/mp_server.py +333 -0
- lmcache/v1/mp_observability/subscribers/tracing/span_registry.py +148 -0
- lmcache/v1/mp_observability/trace/__init__.py +50 -0
- lmcache/v1/mp_observability/trace/codecs.py +255 -0
- lmcache/v1/mp_observability/trace/decorator.py +147 -0
- lmcache/v1/mp_observability/trace/format.py +132 -0
- lmcache/v1/mp_observability/trace/lifecycle.py +83 -0
- lmcache/v1/mp_observability/trace/reader.py +167 -0
- lmcache/v1/mp_observability/trace/recorder.py +300 -0
- lmcache/v1/multiprocess/__init__.py +0 -0
- lmcache/v1/multiprocess/affinity_pool.py +102 -0
- lmcache/v1/multiprocess/blend_server_v2.py +891 -0
- lmcache/v1/multiprocess/config.py +253 -0
- lmcache/v1/multiprocess/custom_types.py +281 -0
- lmcache/v1/multiprocess/futures.py +194 -0
- lmcache/v1/multiprocess/gpu_context.py +511 -0
- lmcache/v1/multiprocess/http_server.py +235 -0
- lmcache/v1/multiprocess/mp_runtime_plugin_launcher.py +130 -0
- lmcache/v1/multiprocess/mq.py +732 -0
- lmcache/v1/multiprocess/protocol.py +86 -0
- lmcache/v1/multiprocess/protocols/README.md +213 -0
- lmcache/v1/multiprocess/protocols/__init__.py +127 -0
- lmcache/v1/multiprocess/protocols/base.py +89 -0
- lmcache/v1/multiprocess/protocols/blend.py +109 -0
- lmcache/v1/multiprocess/protocols/blend_v2.py +57 -0
- lmcache/v1/multiprocess/protocols/controller.py +53 -0
- lmcache/v1/multiprocess/protocols/debug.py +34 -0
- lmcache/v1/multiprocess/protocols/engine.py +146 -0
- lmcache/v1/multiprocess/protocols/observability.py +39 -0
- lmcache/v1/multiprocess/server.py +1134 -0
- lmcache/v1/multiprocess/session.py +190 -0
- lmcache/v1/multiprocess/token_hasher.py +441 -0
- lmcache/v1/offload_server/__init__.py +17 -0
- lmcache/v1/offload_server/abstract_server.py +37 -0
- lmcache/v1/offload_server/message.py +30 -0
- lmcache/v1/offload_server/zmq_server.py +122 -0
- lmcache/v1/periodic_thread.py +579 -0
- lmcache/v1/pin_monitor.py +246 -0
- lmcache/v1/plugin/__init__.py +0 -0
- lmcache/v1/plugin/runtime_plugin_launcher.py +211 -0
- lmcache/v1/protocol.py +317 -0
- lmcache/v1/rpc/__init__.py +17 -0
- lmcache/v1/rpc/transport.py +105 -0
- lmcache/v1/rpc/zmq_transport.py +213 -0
- lmcache/v1/rpc_utils.py +165 -0
- lmcache/v1/server/__init__.py +2 -0
- lmcache/v1/server/__main__.py +170 -0
- lmcache/v1/server/storage_backend/__init__.py +21 -0
- lmcache/v1/server/storage_backend/abstract_backend.py +80 -0
- lmcache/v1/server/storage_backend/local_backend.py +75 -0
- lmcache/v1/server/utils.py +21 -0
- lmcache/v1/standalone/__init__.py +1 -0
- lmcache/v1/standalone/__main__.py +583 -0
- lmcache/v1/standalone/manager.py +80 -0
- lmcache/v1/standalone/standalone_service_factory.py +86 -0
- lmcache/v1/storage_backend/__init__.py +313 -0
- lmcache/v1/storage_backend/abstract_backend.py +445 -0
- lmcache/v1/storage_backend/audit_backend.py +233 -0
- lmcache/v1/storage_backend/batched_message_sender.py +222 -0
- lmcache/v1/storage_backend/cache_policy/__init__.py +45 -0
- lmcache/v1/storage_backend/cache_policy/base_policy.py +87 -0
- lmcache/v1/storage_backend/cache_policy/fifo.py +58 -0
- lmcache/v1/storage_backend/cache_policy/lfu.py +105 -0
- lmcache/v1/storage_backend/cache_policy/lru.py +81 -0
- lmcache/v1/storage_backend/cache_policy/mru.py +61 -0
- lmcache/v1/storage_backend/connector/__init__.py +443 -0
- lmcache/v1/storage_backend/connector/audit_adapter.py +77 -0
- lmcache/v1/storage_backend/connector/audit_connector.py +320 -0
- lmcache/v1/storage_backend/connector/base_connector.py +379 -0
- lmcache/v1/storage_backend/connector/blackhole_adapter.py +21 -0
- lmcache/v1/storage_backend/connector/blackhole_connector.py +37 -0
- lmcache/v1/storage_backend/connector/eic_adapter.py +31 -0
- lmcache/v1/storage_backend/connector/eic_connector.py +757 -0
- lmcache/v1/storage_backend/connector/external_adapter.py +79 -0
- lmcache/v1/storage_backend/connector/fs_adapter.py +51 -0
- lmcache/v1/storage_backend/connector/fs_connector.py +403 -0
- lmcache/v1/storage_backend/connector/infinistore_adapter.py +56 -0
- lmcache/v1/storage_backend/connector/infinistore_connector.py +177 -0
- lmcache/v1/storage_backend/connector/instrumented_connector.py +219 -0
- lmcache/v1/storage_backend/connector/lm_adapter.py +31 -0
- lmcache/v1/storage_backend/connector/lm_connector.py +176 -0
- lmcache/v1/storage_backend/connector/mock_adapter.py +57 -0
- lmcache/v1/storage_backend/connector/mock_connector.py +349 -0
- lmcache/v1/storage_backend/connector/mooncakestore_adapter.py +43 -0
- lmcache/v1/storage_backend/connector/mooncakestore_connector.py +614 -0
- lmcache/v1/storage_backend/connector/redis_adapter.py +181 -0
- lmcache/v1/storage_backend/connector/redis_connector.py +828 -0
- lmcache/v1/storage_backend/connector/s3_adapter.py +59 -0
- lmcache/v1/storage_backend/connector/s3_connector.py +699 -0
- lmcache/v1/storage_backend/connector/sagemaker_hyperpod_adapter.py +233 -0
- lmcache/v1/storage_backend/connector/sagemaker_hyperpod_connector.py +987 -0
- lmcache/v1/storage_backend/connector/valkey_adapter.py +114 -0
- lmcache/v1/storage_backend/connector/valkey_connector.py +627 -0
- lmcache/v1/storage_backend/gds_backend.py +1199 -0
- lmcache/v1/storage_backend/job_executor/__init__.py +0 -0
- lmcache/v1/storage_backend/job_executor/base_executor.py +34 -0
- lmcache/v1/storage_backend/job_executor/pq_executor.py +235 -0
- lmcache/v1/storage_backend/local_cpu_backend.py +810 -0
- lmcache/v1/storage_backend/local_disk_backend.py +656 -0
- lmcache/v1/storage_backend/maru_backend.py +734 -0
- lmcache/v1/storage_backend/naive_serde/__init__.py +50 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_basics.py +133 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_decoder.py +135 -0
- lmcache/v1/storage_backend/naive_serde/cachegen_encoder.py +83 -0
- lmcache/v1/storage_backend/naive_serde/kivi_serde.py +22 -0
- lmcache/v1/storage_backend/naive_serde/naive_serde.py +18 -0
- lmcache/v1/storage_backend/naive_serde/serde.py +37 -0
- lmcache/v1/storage_backend/native_clients/connector_client_base.py +165 -0
- lmcache/v1/storage_backend/native_clients/resp_client.py +35 -0
- lmcache/v1/storage_backend/nixl_storage_backend.py +1400 -0
- lmcache/v1/storage_backend/p2p_backend.py +788 -0
- lmcache/v1/storage_backend/path_sharder.py +117 -0
- lmcache/v1/storage_backend/pd_backend.py +646 -0
- lmcache/v1/storage_backend/plugins/dax_backend.py +1443 -0
- lmcache/v1/storage_backend/plugins/rust_raw_block_backend.py +1361 -0
- lmcache/v1/storage_backend/remote_backend.py +624 -0
- lmcache/v1/storage_backend/resp_client.py +227 -0
- lmcache/v1/storage_backend/storage_backend_listener.py +19 -0
- lmcache/v1/storage_backend/storage_manager.py +1352 -0
- lmcache/v1/system_detection.py +110 -0
- lmcache/v1/token_database.py +551 -0
- lmcache/v1/transfer_channel/__init__.py +83 -0
- lmcache/v1/transfer_channel/abstract.py +285 -0
- lmcache/v1/transfer_channel/mock_memory_channel.py +156 -0
- lmcache/v1/transfer_channel/nixl_channel.py +639 -0
- lmcache/v1/transfer_channel/py_socket_channel.py +260 -0
- lmcache/v1/transfer_channel/transfer_utils.py +63 -0
- lmcache/v1/utils/__init__.py +1 -0
- lmcache/v1/utils/bloom_filter.py +109 -0
- lmcache/v1/utils/cache_utils.py +125 -0
- lmcache_cli-0.4.5.dev0.dist-info/METADATA +185 -0
- lmcache_cli-0.4.5.dev0.dist-info/RECORD +399 -0
- lmcache_cli-0.4.5.dev0.dist-info/WHEEL +5 -0
- lmcache_cli-0.4.5.dev0.dist-info/entry_points.txt +2 -0
- lmcache_cli-0.4.5.dev0.dist-info/licenses/LICENSE +201 -0
- 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
|
+
)
|