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