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,289 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
|
|
3
|
+
"""Per-qualname latency statistics for trace replay.
|
|
4
|
+
|
|
5
|
+
The replay driver times every dispatched call. Timings feed into this
|
|
6
|
+
collector, which computes count + mean + percentiles (p50, p90, p99)
|
|
7
|
+
per qualname.
|
|
8
|
+
|
|
9
|
+
The shape is deliberately simpler than
|
|
10
|
+
:class:`lmcache.cli.commands.bench.engine_bench.stats.StatsCollector`
|
|
11
|
+
— that one is tailored to OpenAI-style streaming inference (TTFT,
|
|
12
|
+
decode speed, etc.), which is not applicable to in-process storage
|
|
13
|
+
replay. Sharing the computation code between the two would push a
|
|
14
|
+
small helper deep into the bench module; keeping this collector local
|
|
15
|
+
keeps the storage-replay code self-contained and easy to evolve.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# Future
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
# Standard
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
import csv
|
|
24
|
+
import json
|
|
25
|
+
import statistics
|
|
26
|
+
import threading
|
|
27
|
+
|
|
28
|
+
# First Party
|
|
29
|
+
from lmcache.logging import init_logger
|
|
30
|
+
|
|
31
|
+
logger = init_logger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class OpStats:
|
|
36
|
+
"""Aggregate timing stats for one qualname.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
qualname: The qualname these stats cover.
|
|
40
|
+
count: Number of successful replays for this qualname.
|
|
41
|
+
error_count: Number of replays that raised.
|
|
42
|
+
total_s: Total wall time spent replaying this qualname.
|
|
43
|
+
mean_ms: Mean per-call latency in milliseconds.
|
|
44
|
+
p50_ms: 50th-percentile latency in milliseconds.
|
|
45
|
+
p90_ms: 90th-percentile latency in milliseconds.
|
|
46
|
+
p99_ms: 99th-percentile latency in milliseconds.
|
|
47
|
+
min_ms: Minimum observed latency in milliseconds.
|
|
48
|
+
max_ms: Maximum observed latency in milliseconds.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
qualname: str
|
|
52
|
+
count: int
|
|
53
|
+
error_count: int
|
|
54
|
+
total_s: float
|
|
55
|
+
mean_ms: float
|
|
56
|
+
p50_ms: float
|
|
57
|
+
p90_ms: float
|
|
58
|
+
p99_ms: float
|
|
59
|
+
min_ms: float
|
|
60
|
+
max_ms: float
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class _Bucket:
|
|
65
|
+
"""Internal per-qualname sample bucket."""
|
|
66
|
+
|
|
67
|
+
latencies_ms: list[float] = field(default_factory=list)
|
|
68
|
+
errors: int = 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _percentile(sorted_values: list[float], pct: float) -> float:
|
|
72
|
+
"""Return the *pct*-th percentile from an already-sorted list.
|
|
73
|
+
|
|
74
|
+
Uses nearest-rank with no interpolation: for N samples, the
|
|
75
|
+
p-th percentile is the sample at index ``ceil(p/100 * N) - 1``.
|
|
76
|
+
Returns 0.0 for empty input.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
sorted_values: Ascending-sorted list of values.
|
|
80
|
+
pct: Percentile in ``[0, 100]``.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
The percentile value, or 0.0 for empty input.
|
|
84
|
+
"""
|
|
85
|
+
if not sorted_values:
|
|
86
|
+
return 0.0
|
|
87
|
+
if pct <= 0:
|
|
88
|
+
return sorted_values[0]
|
|
89
|
+
if pct >= 100:
|
|
90
|
+
return sorted_values[-1]
|
|
91
|
+
# bisect_left gives the insertion index; the nearest-rank formula
|
|
92
|
+
# maps p to ceil(p/100 * N) which equals floor((p/100 * N - eps) + 1).
|
|
93
|
+
n = len(sorted_values)
|
|
94
|
+
idx = max(0, min(n - 1, int((pct / 100.0) * n)))
|
|
95
|
+
return sorted_values[idx]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ReplayStatsCollector:
|
|
99
|
+
"""Thread-safe per-qualname latency collector.
|
|
100
|
+
|
|
101
|
+
The replay driver is single-threaded at the dispatcher boundary,
|
|
102
|
+
but the underlying StorageManager performs async work on helper
|
|
103
|
+
threads whose timings may eventually feed back here. A lock keeps
|
|
104
|
+
concurrent ``record()`` calls safe.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(self) -> None:
|
|
108
|
+
self._lock = threading.Lock()
|
|
109
|
+
self._buckets: dict[str, _Bucket] = {}
|
|
110
|
+
self._wall_start_s: float | None = None
|
|
111
|
+
self._wall_end_s: float | None = None
|
|
112
|
+
|
|
113
|
+
def mark_start(self, wall_time_s: float) -> None:
|
|
114
|
+
"""Record the wall-clock time when replay began.
|
|
115
|
+
|
|
116
|
+
Called once before the first ``record()``; replay-duration
|
|
117
|
+
metrics are derived from start/end marks.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
wall_time_s: ``time.time()`` at replay start.
|
|
121
|
+
"""
|
|
122
|
+
with self._lock:
|
|
123
|
+
self._wall_start_s = wall_time_s
|
|
124
|
+
|
|
125
|
+
def mark_end(self, wall_time_s: float) -> None:
|
|
126
|
+
"""Record the wall-clock time when replay finished.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
wall_time_s: ``time.time()`` at replay end.
|
|
130
|
+
"""
|
|
131
|
+
with self._lock:
|
|
132
|
+
self._wall_end_s = wall_time_s
|
|
133
|
+
|
|
134
|
+
def record(self, qualname: str, latency_s: float, failed: bool = False) -> None:
|
|
135
|
+
"""Record one replayed call.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
qualname: Qualified name of the replayed function.
|
|
139
|
+
latency_s: Elapsed seconds for the call.
|
|
140
|
+
failed: ``True`` if the call raised. Failed calls still
|
|
141
|
+
contribute to the count but do not add a latency
|
|
142
|
+
sample — the raising path's timing is not comparable
|
|
143
|
+
to successful calls.
|
|
144
|
+
"""
|
|
145
|
+
with self._lock:
|
|
146
|
+
bucket = self._buckets.get(qualname)
|
|
147
|
+
if bucket is None:
|
|
148
|
+
bucket = _Bucket()
|
|
149
|
+
self._buckets[qualname] = bucket
|
|
150
|
+
if failed:
|
|
151
|
+
bucket.errors += 1
|
|
152
|
+
return
|
|
153
|
+
# Append is O(1); the one-shot sort in :meth:`summary` is
|
|
154
|
+
# O(N log N), which beats the O(N) shift from keeping the
|
|
155
|
+
# list sorted on insert. For large traces (>1M records
|
|
156
|
+
# per qualname) the driver should sample or switch to an
|
|
157
|
+
# approximation; for now, exact percentiles suffice.
|
|
158
|
+
bucket.latencies_ms.append(latency_s * 1000.0)
|
|
159
|
+
|
|
160
|
+
def total_duration_s(self) -> float:
|
|
161
|
+
"""Return replay wall-clock duration in seconds.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
``mark_end - mark_start`` if both were set, else 0.0.
|
|
165
|
+
"""
|
|
166
|
+
with self._lock:
|
|
167
|
+
if self._wall_start_s is None or self._wall_end_s is None:
|
|
168
|
+
return 0.0
|
|
169
|
+
return max(0.0, self._wall_end_s - self._wall_start_s)
|
|
170
|
+
|
|
171
|
+
def summary(self) -> dict[str, OpStats]:
|
|
172
|
+
"""Return a per-qualname :class:`OpStats` snapshot.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
A dict keyed by qualname. A qualname with only errors
|
|
176
|
+
still appears, with zero latency stats.
|
|
177
|
+
"""
|
|
178
|
+
with self._lock:
|
|
179
|
+
result: dict[str, OpStats] = {}
|
|
180
|
+
for qualname, bucket in self._buckets.items():
|
|
181
|
+
# Sort once per summary call — ``record`` keeps the
|
|
182
|
+
# list unsorted (O(1) append) so the total work is
|
|
183
|
+
# O(N log N) per summary rather than O(N) per insert.
|
|
184
|
+
lats = sorted(bucket.latencies_ms)
|
|
185
|
+
if lats:
|
|
186
|
+
mean = statistics.fmean(lats)
|
|
187
|
+
total_s = sum(lats) / 1000.0
|
|
188
|
+
p50 = _percentile(lats, 50)
|
|
189
|
+
p90 = _percentile(lats, 90)
|
|
190
|
+
p99 = _percentile(lats, 99)
|
|
191
|
+
lo, hi = lats[0], lats[-1]
|
|
192
|
+
else:
|
|
193
|
+
mean = total_s = p50 = p90 = p99 = lo = hi = 0.0
|
|
194
|
+
result[qualname] = OpStats(
|
|
195
|
+
qualname=qualname,
|
|
196
|
+
count=len(lats),
|
|
197
|
+
error_count=bucket.errors,
|
|
198
|
+
total_s=total_s,
|
|
199
|
+
mean_ms=mean,
|
|
200
|
+
p50_ms=p50,
|
|
201
|
+
p90_ms=p90,
|
|
202
|
+
p99_ms=p99,
|
|
203
|
+
min_ms=lo,
|
|
204
|
+
max_ms=hi,
|
|
205
|
+
)
|
|
206
|
+
return result
|
|
207
|
+
|
|
208
|
+
def export_csv(self, path: str) -> None:
|
|
209
|
+
"""Write per-qualname stats to a CSV file.
|
|
210
|
+
|
|
211
|
+
Columns: ``qualname, count, errors, mean_ms, p50_ms, p90_ms,
|
|
212
|
+
p99_ms, min_ms, max_ms``. One row per qualname.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
path: File path to write. Overwritten if it exists.
|
|
216
|
+
"""
|
|
217
|
+
summary = self.summary()
|
|
218
|
+
with open(path, "w", newline="") as f:
|
|
219
|
+
w = csv.writer(f)
|
|
220
|
+
w.writerow(
|
|
221
|
+
[
|
|
222
|
+
"qualname",
|
|
223
|
+
"count",
|
|
224
|
+
"errors",
|
|
225
|
+
"mean_ms",
|
|
226
|
+
"p50_ms",
|
|
227
|
+
"p90_ms",
|
|
228
|
+
"p99_ms",
|
|
229
|
+
"min_ms",
|
|
230
|
+
"max_ms",
|
|
231
|
+
]
|
|
232
|
+
)
|
|
233
|
+
for qn in sorted(summary):
|
|
234
|
+
s = summary[qn]
|
|
235
|
+
w.writerow(
|
|
236
|
+
[
|
|
237
|
+
s.qualname,
|
|
238
|
+
s.count,
|
|
239
|
+
s.error_count,
|
|
240
|
+
f"{s.mean_ms:.6f}",
|
|
241
|
+
f"{s.p50_ms:.6f}",
|
|
242
|
+
f"{s.p90_ms:.6f}",
|
|
243
|
+
f"{s.p99_ms:.6f}",
|
|
244
|
+
f"{s.min_ms:.6f}",
|
|
245
|
+
f"{s.max_ms:.6f}",
|
|
246
|
+
]
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def export_json(self, path: str) -> None:
|
|
250
|
+
"""Write per-qualname stats + replay duration to a JSON file.
|
|
251
|
+
|
|
252
|
+
Schema::
|
|
253
|
+
|
|
254
|
+
{
|
|
255
|
+
"duration_s": <float>,
|
|
256
|
+
"ops": {
|
|
257
|
+
"<qualname>": {
|
|
258
|
+
"count": int, "errors": int,
|
|
259
|
+
"mean_ms": float, "p50_ms": float,
|
|
260
|
+
"p90_ms": float, "p99_ms": float,
|
|
261
|
+
"min_ms": float, "max_ms": float
|
|
262
|
+
},
|
|
263
|
+
...
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
path: File path to write. Overwritten if it exists.
|
|
269
|
+
"""
|
|
270
|
+
summary = self.summary()
|
|
271
|
+
payload = {
|
|
272
|
+
"duration_s": self.total_duration_s(),
|
|
273
|
+
"ops": {
|
|
274
|
+
qn: {
|
|
275
|
+
"count": s.count,
|
|
276
|
+
"errors": s.error_count,
|
|
277
|
+
"mean_ms": s.mean_ms,
|
|
278
|
+
"p50_ms": s.p50_ms,
|
|
279
|
+
"p90_ms": s.p90_ms,
|
|
280
|
+
"p99_ms": s.p99_ms,
|
|
281
|
+
"min_ms": s.min_ms,
|
|
282
|
+
"max_ms": s.max_ms,
|
|
283
|
+
}
|
|
284
|
+
for qn, s in summary.items()
|
|
285
|
+
},
|
|
286
|
+
}
|
|
287
|
+
with open(path, "w") as f:
|
|
288
|
+
json.dump(payload, f, indent=2, sort_keys=True)
|
|
289
|
+
f.write("\n")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LMCache is a high-performance key–value (KV) cache management system designed to accelerate large language model (LLM) inference by efficiently storing, transferring, and reusing intermediate attention states. As modern LLM serving increasingly becomes bottlenecked by memory bandwidth, redundant computation, and cross-device communication, LMCache provides a system-level solution that decouples KV cache storage from the model execution pipeline and enables scalable, low-latency reuse across requests, processes, and even distributed nodes.
|
|
2
|
+
|
|
3
|
+
At its core, LMCache targets one of the most expensive components of autoregressive inference: the KV cache generated during the prefill phase. In conventional serving systems, this cache is tightly coupled to a single process or GPU, making it difficult to reuse across requests or share between instances. As a result, repeated prompts or multi-turn conversations often trigger redundant computation, increasing both latency and resource consumption. LMCache addresses this limitation by introducing a unified KV cache abstraction that can be externally managed, retrieved asynchronously, and seamlessly reintegrated into the decoding pipeline.
|
|
4
|
+
|
|
5
|
+
A key design principle of LMCache is minimizing the impact of cache operations on the critical path of inference. By supporting asynchronous KV retrieval and background prefetching, LMCache allows decoding to proceed without blocking on cache transfers. This is particularly important in disaggregated or multi-process (MP) deployments, where different inference engines may collaborate through a shared cache backend. In such settings, LMCache enables one instance to reuse KV states computed by another, significantly reducing time-to-first-token (TTFT) and improving tail latency. Empirical results show substantial gains in multi-turn and stateful workloads, where reuse opportunities are abundant.
|
|
6
|
+
|
|
7
|
+
LMCache also provides flexible support for heterogeneous memory and storage backends, including GPU memory, host memory, persistent storage (e.g., SSD or DAX devices), and high-speed interconnects such as RDMA. Through pluggable connectors and configurable transfer policies, it can adapt to diverse deployment environments, from single-node setups to large-scale distributed clusters. This flexibility enables system designers to balance trade-offs between latency, capacity, and cost, while maintaining high throughput.
|
|
8
|
+
|
|
9
|
+
Beyond performance optimization, LMCache emphasizes observability and integration. It exposes detailed metrics for cache lookup, retrieval, and storage operations, allowing users to understand system behavior and diagnose bottlenecks. It is designed to integrate seamlessly with popular serving frameworks such as vLLM, requiring minimal changes to existing workflows while unlocking advanced caching capabilities.
|
|
10
|
+
|
|
11
|
+
In summary, LMCache rethinks KV cache management as a first-class system component for LLM serving. By enabling efficient reuse, asynchronous data movement, and cross-instance sharing, it addresses fundamental inefficiencies in current inference pipelines and paves the way for more scalable, responsive, and resource-efficient AI systems.
|
lmcache/cli/main.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""LMCache CLI entry point.
|
|
3
|
+
|
|
4
|
+
Subcommands are explicitly registered in
|
|
5
|
+
``lmcache.cli.commands.ALL_COMMANDS``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Standard
|
|
9
|
+
import argparse
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
# First Party
|
|
13
|
+
from lmcache.cli.commands import ALL_COMMANDS
|
|
14
|
+
from lmcache.logging import init_logger
|
|
15
|
+
|
|
16
|
+
logger = init_logger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> None:
|
|
20
|
+
"""CLI entry point registered as ``lmcache`` in *pyproject.toml*."""
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
prog="lmcache",
|
|
23
|
+
description="LMCache — KV cache management for LLM serving",
|
|
24
|
+
)
|
|
25
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
26
|
+
|
|
27
|
+
for cmd in ALL_COMMANDS:
|
|
28
|
+
cmd.register(subparsers)
|
|
29
|
+
|
|
30
|
+
args = parser.parse_args()
|
|
31
|
+
|
|
32
|
+
if not hasattr(args, "func"):
|
|
33
|
+
parser.print_help()
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
args.func(args)
|
|
38
|
+
except KeyboardInterrupt:
|
|
39
|
+
sys.exit(130)
|
|
40
|
+
except Exception: # noqa: BLE001
|
|
41
|
+
logger.exception("Command failed")
|
|
42
|
+
sys.exit(1)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Metrics package — collector, handlers, and formatters."""
|
|
3
|
+
|
|
4
|
+
# First Party
|
|
5
|
+
from lmcache.cli.metrics.formatter import (
|
|
6
|
+
JsonFormatter,
|
|
7
|
+
MetricsFormatter,
|
|
8
|
+
TerminalFormatter,
|
|
9
|
+
get_formatter,
|
|
10
|
+
)
|
|
11
|
+
from lmcache.cli.metrics.handler import (
|
|
12
|
+
FileHandler,
|
|
13
|
+
MetricsHandler,
|
|
14
|
+
StreamHandler,
|
|
15
|
+
)
|
|
16
|
+
from lmcache.cli.metrics.metrics import Metrics
|
|
17
|
+
from lmcache.cli.metrics.section import Section
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"FileHandler",
|
|
21
|
+
"JsonFormatter",
|
|
22
|
+
"get_formatter",
|
|
23
|
+
"Metrics",
|
|
24
|
+
"MetricsFormatter",
|
|
25
|
+
"MetricsHandler",
|
|
26
|
+
"Section",
|
|
27
|
+
"StreamHandler",
|
|
28
|
+
"TerminalFormatter",
|
|
29
|
+
]
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Metrics formatters — control *how* metrics are rendered.
|
|
3
|
+
|
|
4
|
+
A formatter converts a title + sections into a string (or dict).
|
|
5
|
+
Formatters are attached to handlers, separating rendering from destination.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Standard
|
|
9
|
+
from typing import Any
|
|
10
|
+
import abc
|
|
11
|
+
import inspect
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
# First Party
|
|
15
|
+
from lmcache.cli.metrics.section import Section, sections_to_dict
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MetricsFormatter(abc.ABC):
|
|
19
|
+
"""Base class for metrics formatters."""
|
|
20
|
+
|
|
21
|
+
@abc.abstractmethod
|
|
22
|
+
def format(self, title: str, sections: list[Section]) -> str:
|
|
23
|
+
"""Render metrics into a string.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
title: The report title.
|
|
27
|
+
sections: Ordered list of ``Section`` objects.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
The formatted string.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
# Formatter registry
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
_FORMATTER_REGISTRY: dict[str, type[MetricsFormatter]] = {}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def register_formatter(name: str):
|
|
42
|
+
"""Decorator that registers a ``MetricsFormatter`` subclass under *name*.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
name: The format name used for CLI lookup (e.g. ``"json"``).
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
A class decorator that registers the class and returns it unchanged.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def decorator(cls: type[MetricsFormatter]) -> type[MetricsFormatter]:
|
|
52
|
+
_FORMATTER_REGISTRY[name] = cls
|
|
53
|
+
return cls
|
|
54
|
+
|
|
55
|
+
return decorator
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_formatter(name: str, **kwargs: Any) -> MetricsFormatter:
|
|
59
|
+
"""Instantiate a formatter by its registered name.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
name: Registered format name (e.g. ``"terminal"``, ``"json"``).
|
|
63
|
+
**kwargs: Forwarded to the formatter constructor (e.g. ``width``).
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
A new formatter instance.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If *name* is not registered.
|
|
70
|
+
"""
|
|
71
|
+
cls = _FORMATTER_REGISTRY.get(name)
|
|
72
|
+
if cls is None:
|
|
73
|
+
available = ", ".join(sorted(_FORMATTER_REGISTRY))
|
|
74
|
+
raise ValueError(f"Unknown format {name!r}. Available: {available}")
|
|
75
|
+
# Only forward kwargs that the constructor accepts.
|
|
76
|
+
sig = inspect.signature(cls.__init__)
|
|
77
|
+
valid = {k: v for k, v in kwargs.items() if k in sig.parameters}
|
|
78
|
+
return cls(**valid)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# Built-in formatters
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _format_value(value: Any) -> str:
|
|
87
|
+
"""Format a metric value for terminal display."""
|
|
88
|
+
if value is None:
|
|
89
|
+
return "N/A"
|
|
90
|
+
if isinstance(value, float):
|
|
91
|
+
return f"{value:.2f}"
|
|
92
|
+
return str(value)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@register_formatter("terminal")
|
|
96
|
+
class TerminalFormatter(MetricsFormatter):
|
|
97
|
+
"""Plain ASCII table formatter for terminal output.
|
|
98
|
+
|
|
99
|
+
Title is centered in ``=`` borders, section headers are centered in
|
|
100
|
+
``-`` borders, key-value lines have left-aligned labels and
|
|
101
|
+
right-aligned values.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
width: Target total character width for the output.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(self, width: int = 48) -> None:
|
|
108
|
+
self._width = width
|
|
109
|
+
|
|
110
|
+
def format(self, title: str, sections: list[Section]) -> str:
|
|
111
|
+
"""Render metrics as an ASCII table.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
title: The report title.
|
|
115
|
+
sections: Ordered list of ``Section`` objects.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Rendered multi-line string.
|
|
119
|
+
"""
|
|
120
|
+
width = self._width
|
|
121
|
+
lines: list[str] = []
|
|
122
|
+
|
|
123
|
+
# Title bar
|
|
124
|
+
title_text = f" {title} "
|
|
125
|
+
lines.append(title_text.center(width, "="))
|
|
126
|
+
|
|
127
|
+
for section in sections:
|
|
128
|
+
# Section header (skip for unnamed section)
|
|
129
|
+
if section.label is not None:
|
|
130
|
+
header_text = f" {section.label} "
|
|
131
|
+
lines.append(header_text.center(width, "-"))
|
|
132
|
+
|
|
133
|
+
for _key, label, value in section.entries:
|
|
134
|
+
formatted = _format_value(value)
|
|
135
|
+
label_part = f"{label}:"
|
|
136
|
+
padding = width - len(label_part) - len(formatted)
|
|
137
|
+
if padding < 1:
|
|
138
|
+
padding = 1
|
|
139
|
+
lines.append(f"{label_part}{' ' * padding}{formatted}")
|
|
140
|
+
|
|
141
|
+
# Footer
|
|
142
|
+
lines.append("=" * width)
|
|
143
|
+
|
|
144
|
+
return "\n".join(lines)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@register_formatter("json")
|
|
148
|
+
class JsonFormatter(MetricsFormatter):
|
|
149
|
+
"""Renders metrics as a JSON string.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
indent: JSON indentation level.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(self, indent: int = 2) -> None:
|
|
156
|
+
self._indent = indent
|
|
157
|
+
|
|
158
|
+
def format(self, title: str, sections: list[Section]) -> str:
|
|
159
|
+
"""Render metrics as indented JSON.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
title: The report title.
|
|
163
|
+
sections: Ordered list of ``Section`` objects.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
JSON string.
|
|
167
|
+
"""
|
|
168
|
+
return json.dumps(
|
|
169
|
+
sections_to_dict(title, sections),
|
|
170
|
+
indent=self._indent,
|
|
171
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Metrics handlers — control *where* metrics are written.
|
|
3
|
+
|
|
4
|
+
Each handler pairs a destination (stream, file, …) with a
|
|
5
|
+
:class:`~lmcache.cli.metrics.formatter.MetricsFormatter` that controls
|
|
6
|
+
the rendering.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Standard
|
|
10
|
+
from typing import IO, Optional, Union
|
|
11
|
+
import abc
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
# First Party
|
|
16
|
+
from lmcache.cli.metrics.formatter import (
|
|
17
|
+
JsonFormatter,
|
|
18
|
+
MetricsFormatter,
|
|
19
|
+
TerminalFormatter,
|
|
20
|
+
)
|
|
21
|
+
from lmcache.cli.metrics.section import Section
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MetricsHandler(abc.ABC):
|
|
25
|
+
"""Base class for metrics handlers."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, formatter: MetricsFormatter) -> None:
|
|
28
|
+
self.formatter = formatter
|
|
29
|
+
|
|
30
|
+
@abc.abstractmethod
|
|
31
|
+
def emit(self, title: str, sections: list[Section]) -> None:
|
|
32
|
+
"""Format and write metrics to the destination.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
title: The report title.
|
|
36
|
+
sections: Ordered list of ``Section`` objects.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class StreamHandler(MetricsHandler):
|
|
41
|
+
"""Writes formatted metrics to a text stream.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
formatter: The formatter to use for rendering.
|
|
45
|
+
stream: Writable text stream. Defaults to ``sys.stdout``.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
formatter: Optional[MetricsFormatter] = None,
|
|
51
|
+
stream: Optional[IO[str]] = None,
|
|
52
|
+
) -> None:
|
|
53
|
+
super().__init__(formatter or TerminalFormatter())
|
|
54
|
+
self._stream = stream
|
|
55
|
+
|
|
56
|
+
def emit(self, title: str, sections: list[Section]) -> None:
|
|
57
|
+
"""Format and write metrics to the stream.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
title: The report title.
|
|
61
|
+
sections: Ordered list of ``Section`` objects.
|
|
62
|
+
"""
|
|
63
|
+
stream = self._stream or sys.stdout
|
|
64
|
+
stream.write(self.formatter.format(title, sections))
|
|
65
|
+
stream.write("\n")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class FileHandler(MetricsHandler):
|
|
69
|
+
"""Writes formatted metrics to a file.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
path: Destination file path.
|
|
73
|
+
formatter: The formatter to use for rendering. Defaults to
|
|
74
|
+
:class:`~lmcache.cli.metrics.formatter.JsonFormatter`.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
path: Union[str, os.PathLike],
|
|
80
|
+
formatter: Optional[MetricsFormatter] = None,
|
|
81
|
+
) -> None:
|
|
82
|
+
super().__init__(formatter or JsonFormatter())
|
|
83
|
+
self.path = path
|
|
84
|
+
|
|
85
|
+
def emit(self, title: str, sections: list[Section]) -> None:
|
|
86
|
+
"""Format and write metrics to the file.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
title: The report title.
|
|
90
|
+
sections: Ordered list of ``Section`` objects.
|
|
91
|
+
"""
|
|
92
|
+
with open(self.path, "w") as f:
|
|
93
|
+
f.write(self.formatter.format(title, sections))
|
|
94
|
+
f.write("\n")
|