lmcache-cli 0.5.7rc2.dev0__py3-none-any.whl → 0.5.7rc4.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/_version.py +3 -3
- lmcache/cli/commands/bench/engine_bench/quality/alignment.py +328 -21
- lmcache/cli/commands/bench/engine_bench/quality/prompts.py +48 -3
- lmcache/cli/commands/bench/engine_bench/quality/scoring.py +10 -3
- lmcache/cli/commands/bench/quality_bench/__init__.py +34 -0
- lmcache/cli/commands/bench/quality_bench/analyze_command.py +44 -0
- lmcache/cli/commands/bench/quality_bench/core/__init__.py +7 -0
- lmcache/cli/commands/bench/quality_bench/core/analyze_stage.py +285 -0
- lmcache/cli/commands/bench/quality_bench/core/blend_counters.py +214 -0
- lmcache/cli/commands/bench/quality_bench/core/config.py +343 -0
- lmcache/cli/commands/bench/quality_bench/core/judge.py +492 -0
- lmcache/cli/commands/bench/quality_bench/core/plan.py +920 -0
- lmcache/cli/commands/bench/quality_bench/core/report.py +322 -0
- lmcache/cli/commands/bench/quality_bench/core/run_files.py +589 -0
- lmcache/cli/commands/bench/quality_bench/core/run_stage.py +518 -0
- lmcache/cli/commands/bench/quality_bench/core/runner.py +237 -0
- lmcache/cli/commands/bench/quality_bench/core/task.py +48 -0
- lmcache/cli/commands/bench/quality_bench/core/verdict.py +875 -0
- lmcache/cli/commands/bench/quality_bench/run_command.py +46 -0
- lmcache/cli/commands/bench/server_bench/__init__.py +21 -5
- lmcache/cli/commands/bench/server_bench/allocator.py +78 -0
- lmcache/cli/commands/bench/server_bench/arguments.py +236 -0
- lmcache/cli/commands/bench/server_bench/buffers.py +95 -32
- lmcache/cli/commands/bench/server_bench/cases/baseline.py +63 -41
- lmcache/cli/commands/bench/server_bench/client.py +326 -187
- lmcache/cli/commands/bench/server_bench/command.py +26 -240
- lmcache/cli/commands/bench/server_bench/config.py +14 -1
- lmcache/cli/commands/bench/server_bench/helpers.py +42 -130
- lmcache/cli/commands/server.py +1 -1
- lmcache/cli/commands/trace/_dispatch.py +50 -9
- lmcache/inprocess/integration/base_service_factory.py +2 -2
- lmcache/inprocess/integration/tensorrt_llm/utils.py +2 -2
- lmcache/inprocess/integration/vllm/utils.py +160 -0
- lmcache/inprocess/integration/vllm/vllm_ec_adapter.py +2 -2
- lmcache/inprocess/integration/vllm/vllm_service_factory.py +8 -8
- lmcache/inprocess/integration/vllm/vllm_v1_adapter.py +9 -9
- lmcache/inprocess/standalone/__main__.py +7 -4
- lmcache/inprocess/standalone/manager.py +2 -2
- lmcache/inprocess/standalone/standalone_service_factory.py +2 -2
- lmcache/inprocess/v1/api_server/__main__.py +1 -1
- lmcache/inprocess/v1/cache_controller/config.py +2 -2
- lmcache/inprocess/v1/cache_controller/full_sync_sender.py +2 -2
- lmcache/inprocess/v1/cache_controller/worker.py +2 -2
- lmcache/inprocess/v1/cache_engine.py +9 -10
- lmcache/inprocess/v1/check/check_mode_test_remote.py +5 -5
- lmcache/inprocess/v1/check/utils.py +4 -4
- lmcache/{v1 → inprocess/v1}/compute/attention/abstract.py +1 -1
- lmcache/{v1 → inprocess/v1}/compute/attention/flash_attn.py +4 -4
- lmcache/{v1 → inprocess/v1}/compute/attention/flash_infer_sparse.py +3 -3
- lmcache/{v1 → inprocess/v1}/compute/attention/metadata.py +1 -1
- lmcache/{v1 → inprocess/v1}/compute/attention/triton_sparse.py +4 -4
- lmcache/{v1 → inprocess/v1}/compute/attention/utils.py +1 -1
- lmcache/{v1 → inprocess/v1}/compute/blend/__init__.py +1 -1
- lmcache/{v1 → inprocess/v1}/compute/blend/blender.py +7 -4
- lmcache/{v1 → inprocess/v1}/compute/blend/utils.py +3 -3
- lmcache/{v1 → inprocess/v1}/compute/models/base.py +2 -2
- lmcache/{v1 → inprocess/v1}/compute/models/llama.py +1 -1
- lmcache/{v1 → inprocess/v1}/compute/models/qwen3.py +1 -1
- lmcache/{v1 → inprocess/v1}/compute/models/utils.py +3 -3
- lmcache/{v1 → inprocess/v1}/config.py +2 -23
- lmcache/inprocess/v1/ec_engine.py +3 -3
- lmcache/inprocess/v1/gpu_connector/__init__.py +2 -2
- lmcache/inprocess/v1/gpu_connector/gpu_connectors.py +2 -2
- lmcache/inprocess/v1/gpu_connector/hpu_connector.py +1 -1
- lmcache/inprocess/v1/gpu_connector/musa_connectors.py +3 -2
- lmcache/inprocess/v1/gpu_connector/utils.py +1 -1
- lmcache/inprocess/v1/gpu_connector/xpu_connectors.py +2 -2
- lmcache/inprocess/v1/health_monitor/base.py +5 -3
- lmcache/inprocess/v1/health_monitor/checks/remote_backend_check.py +6 -4
- lmcache/inprocess/v1/hidden_state_store.py +2 -2
- lmcache/inprocess/v1/internal_api_server/vllm/conf_api.py +2 -2
- lmcache/inprocess/v1/internal_api_server/vllm/load_fs_chunks_api.py +2 -2
- lmcache/inprocess/v1/lookup_client/chunk_statistics_lookup_client.py +2 -2
- lmcache/inprocess/v1/lookup_client/factory.py +2 -2
- lmcache/inprocess/v1/lookup_client/hit_limit_lookup_client.py +1 -1
- lmcache/inprocess/v1/lookup_client/lmcache_async_lookup_client.py +2 -2
- lmcache/inprocess/v1/lookup_client/lmcache_lookup_client.py +2 -2
- lmcache/inprocess/v1/lookup_client/lmcache_lookup_client_bypass.py +2 -2
- lmcache/inprocess/v1/lookup_client/mooncake_lookup_client.py +5 -5
- lmcache/inprocess/v1/manager.py +2 -2
- lmcache/inprocess/v1/memory_allocators/__init__.py +2 -0
- lmcache/{v1 → inprocess/v1}/storage_backend/__init__.py +19 -13
- lmcache/{v1 → inprocess/v1}/storage_backend/abstract_backend.py +3 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/audit_backend.py +3 -1
- lmcache/{v1 → inprocess/v1}/storage_backend/batched_message_sender.py +2 -2
- lmcache/{v1 → inprocess/v1}/storage_backend/cache_policy/__init__.py +7 -5
- lmcache/{v1 → inprocess/v1}/storage_backend/cache_policy/fifo.py +4 -1
- lmcache/{v1 → inprocess/v1}/storage_backend/cache_policy/lfu.py +4 -1
- lmcache/{v1 → inprocess/v1}/storage_backend/cache_policy/lru.py +4 -1
- lmcache/{v1 → inprocess/v1}/storage_backend/cache_policy/mru.py +4 -1
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/__init__.py +9 -7
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/audit_adapter.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/audit_connector.py +4 -2
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/azure_adapter.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/azure_connector.py +4 -2
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/base_connector.py +2 -2
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/bigtable_adapter.py +7 -2
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/bigtable_connector.py +12 -6
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/blackhole_adapter.py +7 -2
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/blackhole_connector.py +4 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/eic_adapter.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/eic_connector.py +7 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/external_adapter.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/fs_adapter.py +3 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/fs_connector.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/hf3fs_adapter.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/hf3fs_connector.py +2 -2
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/hfbucket_adapter.py +6 -4
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/hfbucket_connector.py +15 -203
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/infinistore_adapter.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/infinistore_connector.py +4 -2
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/instrumented_connector.py +3 -1
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/mock_adapter.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/mock_connector.py +7 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/mooncakestore_adapter.py +3 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/mooncakestore_connector.py +8 -7
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/redis_adapter.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/redis_connector.py +8 -4
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/s3_adapter.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/s3_connector.py +7 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/sagemaker_hyperpod_adapter.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/sagemaker_hyperpod_connector.py +7 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/valkey_adapter.py +6 -4
- lmcache/{v1 → inprocess/v1}/storage_backend/connector/valkey_connector.py +10 -6
- lmcache/{v1 → inprocess/v1}/storage_backend/gds_backend.py +13 -9
- lmcache/{v1 → inprocess/v1}/storage_backend/job_executor/pq_executor.py +3 -1
- lmcache/{storage_backend/serde → inprocess/v1/storage_backend/legacy_serde}/cachegen_decoder.py +6 -6
- lmcache/{storage_backend/serde → inprocess/v1/storage_backend/legacy_serde}/cachegen_encoder.py +6 -6
- lmcache/{v1 → inprocess/v1}/storage_backend/local_cpu_backend.py +13 -9
- lmcache/{v1 → inprocess/v1}/storage_backend/local_disk_backend.py +14 -10
- lmcache/{v1 → inprocess/v1}/storage_backend/maru_backend.py +5 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/naive_serde/__init__.py +14 -7
- lmcache/{v1 → inprocess/v1}/storage_backend/naive_serde/cachegen_basics.py +3 -1
- lmcache/{v1 → inprocess/v1}/storage_backend/naive_serde/cachegen_decoder.py +9 -7
- lmcache/{v1 → inprocess/v1}/storage_backend/naive_serde/cachegen_encoder.py +9 -5
- lmcache/{v1 → inprocess/v1}/storage_backend/naive_serde/kivi_serde.py +4 -1
- lmcache/{v1 → inprocess/v1}/storage_backend/naive_serde/naive_serde.py +4 -1
- lmcache/{v1 → inprocess/v1}/storage_backend/nixl_storage_backend.py +8 -6
- lmcache/{v1 → inprocess/v1}/storage_backend/p2p_backend.py +9 -7
- lmcache/{v1 → inprocess/v1}/storage_backend/pd_backend.py +8 -6
- lmcache/{v1 → inprocess/v1}/storage_backend/pd_backend_async.py +12 -8
- lmcache/{v1 → inprocess/v1}/storage_backend/plugins/dax_backend.py +5 -5
- lmcache/{v1 → inprocess/v1}/storage_backend/plugins/rust_raw_block_backend.py +3 -3
- lmcache/{v1 → inprocess/v1}/storage_backend/remote_backend.py +11 -7
- lmcache/{v1 → inprocess/v1}/storage_backend/resp_client.py +1 -1
- lmcache/{v1 → inprocess/v1}/storage_backend/storage_backend_listener.py +3 -1
- lmcache/{v1 → inprocess/v1}/storage_backend/storage_manager.py +8 -8
- lmcache/inprocess/v1/system_detection.py +116 -0
- lmcache/inprocess/v1/token_database.py +2 -2
- lmcache/integration/atom/multi_process_adapter.py +5 -4
- lmcache/integration/sglang/unified_lmcache_mp_connector.py +11 -10
- lmcache/integration/tensorrt_llm/tensorrt_mp_adapter.py +4 -4
- lmcache/integration/vllm/experimental/token_drop.py +18 -1
- lmcache/integration/vllm/experimental/token_drop_kv.py +55 -0
- lmcache/integration/vllm/lazy_offload_manager.py +14 -11
- lmcache/integration/vllm/lmcache_mp_connector.py +4 -4
- lmcache/integration/vllm/lmcache_mp_metadata.py +59 -6
- lmcache/integration/vllm/mp_lookup_state.py +2 -1
- lmcache/integration/vllm/mp_server_launcher.py +31 -15
- lmcache/integration/vllm/utils.py +5 -178
- lmcache/integration/vllm/vllm_multi_process_adapter.py +70 -90
- lmcache/observability.py +5 -29
- lmcache/sdk/context.py +2 -2
- lmcache/sdk/qringbuffer.py +3 -3
- lmcache/security/__init__.py +2 -0
- lmcache/security/api_key.py +207 -0
- lmcache/security/http.py +99 -0
- lmcache/tools/cache_simulator/trace_analyze.py +62 -0
- lmcache/usage_telemetry/context.py +2 -2
- lmcache/usage_telemetry/continuous.py +1 -1
- lmcache/usage_telemetry/messages.py +13 -12
- lmcache/v1/distributed/api.py +106 -51
- lmcache/v1/distributed/config.py +58 -149
- lmcache/v1/distributed/internal_api.py +14 -42
- lmcache/v1/distributed/key_codec.py +104 -0
- lmcache/v1/distributed/l1_manager/__init__.py +39 -0
- lmcache/v1/distributed/{l1_manager.py → l1_manager/base.py} +95 -267
- lmcache/v1/distributed/l1_manager/devdax.py +203 -0
- lmcache/v1/distributed/l1_manager/dram.py +39 -0
- lmcache/v1/distributed/l1_manager/factory.py +75 -0
- lmcache/v1/distributed/l1_manager/gds.py +73 -0
- lmcache/v1/distributed/l2_adapters/bigtable_key_encoder.py +5 -20
- lmcache/v1/distributed/l2_adapters/bigtable_l2_adapter.py +9 -21
- lmcache/v1/distributed/l2_adapters/dax_l2_adapter.py +5 -5
- lmcache/v1/distributed/l2_adapters/fs_l2_adapter.py +154 -124
- lmcache/v1/distributed/l2_adapters/hfbucket_client.py +193 -0
- lmcache/v1/distributed/l2_adapters/hfbucket_l2_adapter.py +5 -16
- lmcache/v1/distributed/l2_adapters/native_connector_l2_adapter.py +3 -24
- lmcache/v1/distributed/l2_adapters/nixl_store_agents/dynamic_nixl_store_agent.py +3 -6
- lmcache/v1/{storage_backend → distributed/l2_adapters}/raw_block/__init__.py +2 -2
- lmcache/v1/{storage_backend → distributed/l2_adapters}/raw_block/core.py +8 -3
- lmcache/v1/{storage_backend → distributed/l2_adapters}/raw_block/key_codec.py +5 -27
- lmcache/v1/distributed/l2_adapters/raw_block_l2_adapter.py +3 -3
- lmcache/v1/distributed/l2_adapters/s3_l2_adapter.py +80 -74
- lmcache/v1/distributed/l2_adapters/sagemaker_hyperpod_l2_adapter.py +3 -15
- lmcache/v1/{storage_backend → distributed/l2_adapters}/valkey/__init__.py +1 -1
- lmcache/v1/distributed/l2_adapters/valkey_l2_adapter.py +1 -1
- lmcache/v1/distributed/memory_manager/__init__.py +5 -3
- lmcache/v1/distributed/memory_manager/devdax_l1_memory_manager.py +12 -43
- lmcache/v1/distributed/memory_manager/gds_l1_memory_manager.py +2 -13
- lmcache/v1/distributed/memory_manager/l1_manager_protocol.py +3 -7
- lmcache/v1/distributed/memory_manager/l1_memory_manager.py +1 -12
- lmcache/v1/distributed/serde/turboquant/store_kernel.py +3 -0
- lmcache/v1/distributed/storage_controllers/prefetch_controller.py +38 -10
- lmcache/v1/distributed/storage_controllers/utils.py +2 -30
- lmcache/v1/distributed/storage_controllers/write_policy.py +104 -11
- lmcache/v1/distributed/storage_manager.py +302 -245
- lmcache/v1/distributed/transfer_channel/impl/mooncake_te_impl.py +16 -3
- lmcache/v1/gpu_connector/kv_format/specs/base.py +68 -2
- lmcache/v1/kv_layer_groups.py +23 -0
- lmcache/v1/memory_allocators/devdax_memory_allocator.py +26 -148
- lmcache/v1/memory_management.py +0 -19
- lmcache/v1/memory_orchestrator/_codec.py +15 -17
- lmcache/v1/mp_coordinator/app.py +27 -20
- lmcache/v1/mp_coordinator/background_loops.py +4 -2
- lmcache/v1/mp_coordinator/controllers/eviction_controller.py +5 -1
- lmcache/v1/mp_coordinator/controllers/eviction_http_api.py +56 -9
- lmcache/v1/mp_coordinator/controllers/prefetch_manager.py +192 -8
- lmcache/v1/mp_coordinator/http_apis/cache_api.py +1 -0
- lmcache/v1/mp_coordinator/http_apis/directory_api.py +53 -4
- lmcache/v1/mp_coordinator/ingest/kafka_event_source.py +13 -8
- lmcache/v1/mp_coordinator/ingest/stream_position.py +2 -3
- lmcache/v1/mp_coordinator/observability.py +327 -5
- lmcache/v1/mp_coordinator/persistence/checkpoint.py +172 -9
- lmcache/v1/mp_coordinator/persistence/metadata.py +47 -4
- lmcache/v1/mp_coordinator/utils/histogram.py +99 -0
- lmcache/v1/mp_coordinator/views/instance_registry.py +55 -4
- lmcache/v1/mp_coordinator/views/key_directory.py +73 -2
- lmcache/v1/mp_coordinator/views/usage_manager.py +27 -0
- lmcache/v1/mp_observability/README.md +1 -1
- lmcache/v1/mp_observability/otel_init.py +153 -5
- lmcache/v1/mp_observability/subscribers/logging/extra_stats.py +10 -4
- lmcache/v1/mp_observability/subscribers/metrics/l0_l1_throughput.py +15 -7
- lmcache/v1/mp_observability/subscribers/metrics/l1_eviction_loop.py +1 -1
- lmcache/v1/mp_observability/subscribers/tracing/mp_server.py +20 -8
- lmcache/v1/mp_observability/subscribers/tracing/span_registry.py +46 -17
- lmcache/v1/mp_observability/subscribers/tracing/transfer_phase.py +4 -2
- lmcache/v1/mp_observability/trace/format.py +4 -4
- lmcache/v1/mp_observability/trace/reader.py +8 -1
- lmcache/v1/multiprocess/cache_control/key_resolver.py +2 -2
- lmcache/v1/multiprocess/client_config.py +50 -0
- lmcache/v1/multiprocess/config.py +15 -15
- lmcache/v1/multiprocess/custom_types.py +103 -9
- lmcache/v1/multiprocess/engine_context.py +1 -1
- lmcache/v1/multiprocess/http_apis/common/env_api.py +1 -0
- lmcache/v1/multiprocess/http_auth.py +35 -123
- lmcache/v1/multiprocess/http_server.py +1 -1
- lmcache/v1/multiprocess/kv_event_subscriber.py +150 -0
- lmcache/v1/multiprocess/modules/blend/lookup.py +15 -10
- lmcache/v1/multiprocess/modules/blend/module.py +3 -2
- lmcache/v1/multiprocess/modules/blend/store.py +6 -3
- lmcache/v1/multiprocess/modules/engine_driven_transfer.py +388 -30
- lmcache/v1/multiprocess/modules/lmcache_driven_transfer.py +18 -16
- lmcache/v1/multiprocess/modules/lookup.py +10 -9
- lmcache/v1/multiprocess/modules/p2p_controller.py +28 -3
- lmcache/v1/multiprocess/modules/server_transfer.py +300 -112
- lmcache/v1/multiprocess/plugin_modules/qstore.py +4 -5
- lmcache/v1/multiprocess/session.py +23 -15
- lmcache/v1/multiprocess/token_codec.py +90 -0
- lmcache/v1/multiprocess/token_hasher.py +96 -9
- lmcache/v1/multiprocess/transfer_context/async_engine_driven.py +125 -0
- lmcache/v1/multiprocess/transfer_context/base.py +114 -25
- lmcache/v1/multiprocess/transfer_context/inline.py +53 -2
- lmcache/v1/multiprocess/transfer_context/shm.py +68 -0
- lmcache/v1/multiprocess/transfer_context/tensor_wire.py +164 -5
- lmcache/v1/multiprocess/transfer_context/worker_transfer.py +575 -24
- lmcache/v1/multiprocess/transport/auth.py +50 -0
- lmcache/v1/multiprocess/transport/base.py +4 -1
- lmcache/v1/multiprocess/transport/errors.py +39 -0
- lmcache/v1/multiprocess/transport/grpc_impl/_proto_gen/common_pb2.pyi +20 -6
- lmcache/v1/multiprocess/transport/grpc_impl/_proto_gen/p2p_service_pb2.pyi +5 -12
- lmcache/v1/multiprocess/transport/grpc_impl/client.py +143 -10
- lmcache/v1/multiprocess/transport/grpc_impl/native_metrics.py +115 -0
- lmcache/v1/multiprocess/transport/grpc_impl/native_server.py +458 -0
- lmcache/v1/multiprocess/transport/grpc_impl/protos/common.proto +10 -1
- lmcache/v1/multiprocess/transport/grpc_impl/protos/engine_driven_service.proto +23 -0
- lmcache/v1/multiprocess/transport/grpc_impl/protos/p2p_service.proto +3 -5
- lmcache/v1/multiprocess/transport/grpc_impl/server.py +6 -2
- lmcache/v1/multiprocess/transport/grpc_impl/server_base.py +4 -2
- lmcache/v1/multiprocess/transport/grpc_impl/server_builder.py +7 -2
- lmcache/v1/multiprocess/transport/grpc_impl/server_handlers.py +3 -0
- lmcache/v1/multiprocess/transport/zmq_impl/mq.py +38 -1
- lmcache/v1/multiprocess/transport/zmq_impl/wire.py +45 -0
- lmcache/v1/periodic_thread.py +1 -1
- lmcache/v1/platform/devices/mlu/__init__.py +76 -0
- lmcache/v1/platform/devices/mlu/device_ops.py +277 -0
- lmcache/v1/platform/devices/musa/cache_context.py +9 -4
- lmcache/v1/platform/devices/musa/device_ops.py +35 -33
- lmcache/v1/platform/devices/musa/format_capabilities.py +93 -0
- lmcache/v1/platform/devices/musa/native_kv_transfer.py +9 -25
- lmcache/v1/platform/native_block_transfer.py +6 -4
- lmcache/v1/system_detection.py +4 -113
- {lmcache_cli-0.5.7rc2.dev0.dist-info → lmcache_cli-0.5.7rc4.dev0.dist-info}/METADATA +3 -2
- {lmcache_cli-0.5.7rc2.dev0.dist-info → lmcache_cli-0.5.7rc4.dev0.dist-info}/RECORD +328 -287
- lmcache/inprocess/v1/pin_monitor.py +0 -260
- /lmcache/{v1 → inprocess/v1}/compute/__init__.py +0 -0
- /lmcache/{storage_backend/serde → inprocess/v1/compute/attention}/__init__.py +0 -0
- /lmcache/{v1 → inprocess/v1}/compute/attention/triton_kernels/__init__.py +0 -0
- /lmcache/{v1 → inprocess/v1}/compute/attention/triton_kernels/block_sparse_attention.py +0 -0
- /lmcache/{v1 → inprocess/v1}/compute/blend/metadata.py +0 -0
- /lmcache/{v1 → inprocess/v1}/compute/models/__init__.py +0 -0
- /lmcache/{v1 → inprocess/v1}/compute/positional_encoding.py +0 -0
- /lmcache/{v1 → inprocess/v1}/config_base.py +0 -0
- /lmcache/{v1 → inprocess/v1}/memory_allocators/cu_file_memory_allocator.py +0 -0
- /lmcache/{v1 → inprocess/v1}/memory_allocators/hip_file_memory_allocator.py +0 -0
- /lmcache/{v1 → inprocess/v1}/memory_allocators/paged_cpu_gpu_memory_allocator.py +0 -0
- /lmcache/{v1 → inprocess/v1}/metadata.py +0 -0
- /lmcache/{v1 → inprocess/v1}/storage_backend/cache_policy/base_policy.py +0 -0
- /lmcache/{v1 → inprocess/v1}/storage_backend/connector/bigtable_schema.py +0 -0
- /lmcache/{v1 → inprocess/v1}/storage_backend/hipfile_shim.py +0 -0
- /lmcache/{v1 → inprocess/v1}/storage_backend/job_executor/__init__.py +0 -0
- /lmcache/{v1 → inprocess/v1}/storage_backend/job_executor/base_executor.py +0 -0
- /lmcache/{v1/compute/attention → inprocess/v1/storage_backend/legacy_serde}/__init__.py +0 -0
- /lmcache/{storage_backend/serde → inprocess/v1/storage_backend/legacy_serde}/cachegen_basics.py +0 -0
- /lmcache/{storage_backend/serde → inprocess/v1/storage_backend/legacy_serde}/serde.py +0 -0
- /lmcache/{v1 → inprocess/v1}/storage_backend/naive_serde/serde.py +0 -0
- /lmcache/{v1 → inprocess/v1}/storage_backend/native_clients/aerospike_client.py +0 -0
- /lmcache/{v1 → inprocess/v1}/storage_backend/native_clients/connector_client_base.py +0 -0
- /lmcache/{v1 → inprocess/v1}/storage_backend/native_clients/resp_client.py +0 -0
- /lmcache/{v1 → inprocess/v1}/storage_backend/path_sharder.py +0 -0
- /lmcache/v1/{storage_backend/connector → distributed/l2_adapters}/bigtable_config.py +0 -0
- /lmcache/v1/{storage_backend/connector → distributed/l2_adapters}/bigtable_sharder.py +0 -0
- /lmcache/v1/{storage_backend → distributed/l2_adapters}/dax/__init__.py +0 -0
- /lmcache/v1/{storage_backend → distributed/l2_adapters}/dax/core.py +0 -0
- /lmcache/v1/{storage_backend → distributed/l2_adapters}/valkey/worker_pool.py +0 -0
- {lmcache_cli-0.5.7rc2.dev0.dist-info → lmcache_cli-0.5.7rc4.dev0.dist-info}/WHEEL +0 -0
- {lmcache_cli-0.5.7rc2.dev0.dist-info → lmcache_cli-0.5.7rc4.dev0.dist-info}/entry_points.txt +0 -0
- {lmcache_cli-0.5.7rc2.dev0.dist-info → lmcache_cli-0.5.7rc4.dev0.dist-info}/licenses/LICENSE +0 -0
- {lmcache_cli-0.5.7rc2.dev0.dist-info → lmcache_cli-0.5.7rc4.dev0.dist-info}/top_level.txt +0 -0
lmcache/_version.py
CHANGED
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.5.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 5, 7, '
|
|
21
|
+
__version__ = version = '0.5.7rc4.dev0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 5, 7, 'rc4', 'dev0')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g45a0bb420'
|
|
@@ -5,11 +5,27 @@ A passage cached on its own matches inside a longer prompt only if it starts
|
|
|
5
5
|
on a cache-chunk boundary in both places: chunks are content-addressed, so a
|
|
6
6
|
passage that lands off-phase shares no chunk with its cached copy and nothing
|
|
7
7
|
is reused, silently. :class:`ChunkAligner` pads the shared system block and
|
|
8
|
-
each passage so every block occupies whole chunks
|
|
8
|
+
each passage so every block occupies whole chunks, and
|
|
9
|
+
:meth:`ChunkAligner.place_blocks` checks, on the rendered chat prompt, that
|
|
10
|
+
they really do.
|
|
11
|
+
|
|
12
|
+
What fills the padding is the :class:`Filler`:
|
|
13
|
+
|
|
14
|
+
* ``words`` (legacy, and ``rag-qa-quality``'s) appends random single-token
|
|
15
|
+
words.
|
|
16
|
+
* ``dots`` / ``dashes`` wrap each passage as
|
|
17
|
+
``Passage <id>:\\n<text>\\n\\n . . … .\\nend of passage <id>`` -- a repeated
|
|
18
|
+
low-information token the model can read as a divider. The block starts
|
|
19
|
+
with a letter and ends with a digit, so no tokenizer merges it with its
|
|
20
|
+
neighbours, and no chat template trims it (see :func:`block_id`).
|
|
9
21
|
"""
|
|
10
22
|
|
|
11
23
|
# Standard
|
|
24
|
+
from collections.abc import Mapping
|
|
25
|
+
from dataclasses import dataclass
|
|
12
26
|
from typing import Any
|
|
27
|
+
import enum
|
|
28
|
+
import hashlib
|
|
13
29
|
import math
|
|
14
30
|
import random
|
|
15
31
|
|
|
@@ -31,14 +47,79 @@ _TEMPLATE_SENTINEL = "██SENTINEL██"
|
|
|
31
47
|
# Re-encode attempts per block; a merged boundary token shifts the estimate.
|
|
32
48
|
_PAD_ATTEMPTS = 8
|
|
33
49
|
|
|
50
|
+
# Chunk targets tried per block by the unit fillers before giving up: a
|
|
51
|
+
# boundary merge can make a target unreachable, never the next one too.
|
|
52
|
+
_PAD_TARGETS = 3
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Filler(enum.Enum):
|
|
56
|
+
"""What pads a block to a whole number of chunks."""
|
|
57
|
+
|
|
58
|
+
WORDS = "words"
|
|
59
|
+
"""Random single-token words (legacy; ``rag-qa-quality`` uses it).
|
|
60
|
+
Noisy for the model to read."""
|
|
61
|
+
|
|
62
|
+
DOTS = "dots"
|
|
63
|
+
"""Repeated ``" ."`` inside a ``Passage <id>: … end of passage <id>``
|
|
64
|
+
frame."""
|
|
65
|
+
|
|
66
|
+
DASHES = "dashes"
|
|
67
|
+
"""Repeated ``" -"`` inside the same frame as ``dots``."""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_PAD_UNITS = {Filler.DOTS: " .", Filler.DASHES: " -"}
|
|
71
|
+
|
|
72
|
+
# Ends the padded system block in the framed layouts: a fixed digit, so the
|
|
73
|
+
# first passage's "Passage" never merges into it.
|
|
74
|
+
_SYSTEM_TAIL = "\nend of instructions 0"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def block_id(text: str) -> str:
|
|
78
|
+
"""Return the id a framed block is labelled with.
|
|
79
|
+
|
|
80
|
+
Six hex characters of the text's digest plus one decimal digit, so the
|
|
81
|
+
id -- and the block, which ends with it -- always ends in a digit. Every
|
|
82
|
+
tokenizer checked splits a digit from a following letter or newline, while
|
|
83
|
+
a final letter merges into the next block's first word on some, a final
|
|
84
|
+
punctuation mark glues to the newlines after it on others, and final
|
|
85
|
+
whitespace is merged with its neighbours or stripped by chat templates.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
text: The raw passage text.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The id, e.g. ``"4f7a1c9"``. Identical for identical text.
|
|
92
|
+
"""
|
|
93
|
+
digest = hashlib.sha256(text.encode()).hexdigest()
|
|
94
|
+
return digest[:6] + str(int(digest, 16) % 10)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass(frozen=True)
|
|
98
|
+
class BlockPlacement:
|
|
99
|
+
"""Where one block sits in a rendered prompt.
|
|
100
|
+
|
|
101
|
+
Attributes:
|
|
102
|
+
block: The block text.
|
|
103
|
+
start_token: Its first token's index in the tokenized prompt, or
|
|
104
|
+
``-1`` when the block was not found verbatim.
|
|
105
|
+
tokens: Its token ids in this prompt.
|
|
106
|
+
problem: Why it cannot be reused, or ``""`` when it starts and ends
|
|
107
|
+
on chunk boundaries with no token straddling either.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
block: str
|
|
111
|
+
start_token: int
|
|
112
|
+
tokens: tuple[int, ...]
|
|
113
|
+
problem: str
|
|
114
|
+
|
|
34
115
|
|
|
35
116
|
class ChunkAligner:
|
|
36
117
|
"""Pads prompt blocks so each occupies a whole number of cache chunks.
|
|
37
118
|
|
|
38
|
-
Padding is deterministic: the same tokenizer, chunk size,
|
|
39
|
-
always yield the same padded block,
|
|
40
|
-
|
|
41
|
-
|
|
119
|
+
Padding is deterministic: the same tokenizer, chunk size, filler and text
|
|
120
|
+
always yield the same padded block (and, for ``words``, the same seed),
|
|
121
|
+
so two runs build byte-identical prompts. A passage shared by several
|
|
122
|
+
samples is padded once, identically.
|
|
42
123
|
"""
|
|
43
124
|
|
|
44
125
|
def __init__(
|
|
@@ -47,19 +128,25 @@ class ChunkAligner:
|
|
|
47
128
|
model_name: str,
|
|
48
129
|
align_tokens: int,
|
|
49
130
|
seed: int,
|
|
131
|
+
filler: Filler = Filler.WORDS,
|
|
132
|
+
template_kwargs: Mapping[str, Any] | None = None,
|
|
50
133
|
) -> None:
|
|
51
|
-
"""Build the
|
|
134
|
+
"""Build the aligner (and, for ``words``, the filler pool).
|
|
52
135
|
|
|
53
136
|
Args:
|
|
54
137
|
tokenizer: A HuggingFace tokenizer for the served model.
|
|
55
138
|
model_name: The model's name, used only in log messages.
|
|
56
139
|
align_tokens: Chunk size to align to. Should equal the
|
|
57
140
|
deployment's LMCache chunk size.
|
|
58
|
-
seed: Seed for filler selection.
|
|
141
|
+
seed: Seed for filler selection (``words`` only).
|
|
142
|
+
filler: What pads each block.
|
|
143
|
+
template_kwargs: Chat-template variables the engine renders
|
|
144
|
+
with; they can change the template's length ahead of the
|
|
145
|
+
content.
|
|
59
146
|
|
|
60
147
|
Raises:
|
|
61
148
|
ValueError: If *align_tokens* is not positive, or the tokenizer
|
|
62
|
-
cannot supply a single-token filler pool
|
|
149
|
+
cannot supply a single-token filler pool for ``words``.
|
|
63
150
|
"""
|
|
64
151
|
if align_tokens < 1:
|
|
65
152
|
raise ValueError(f"align_tokens must be >= 1, got {align_tokens}")
|
|
@@ -67,8 +154,25 @@ class ChunkAligner:
|
|
|
67
154
|
self._model_name = model_name
|
|
68
155
|
self._align_tokens = align_tokens
|
|
69
156
|
self._seed = seed
|
|
70
|
-
self.
|
|
71
|
-
self.
|
|
157
|
+
self._filler = filler
|
|
158
|
+
self._template_kwargs = dict(template_kwargs or {})
|
|
159
|
+
self._pool = (
|
|
160
|
+
build_single_token_pool(tokenizer, _FILLER_VOCAB_SIZE, seed=seed)
|
|
161
|
+
if filler is Filler.WORDS
|
|
162
|
+
else None
|
|
163
|
+
)
|
|
164
|
+
self._passage_blocks: dict[str, tuple[str, int]] = {}
|
|
165
|
+
self._system_pad_tokens = 0
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def filler(self) -> Filler:
|
|
169
|
+
"""What pads each block."""
|
|
170
|
+
return self._filler
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def system_pad_tokens(self) -> int:
|
|
174
|
+
"""Padding tokens in the last block :meth:`system_block` returned."""
|
|
175
|
+
return self._system_pad_tokens
|
|
72
176
|
|
|
73
177
|
def token_length(self, text: str) -> int:
|
|
74
178
|
"""Return the token length of *text*, without special tokens.
|
|
@@ -95,9 +199,16 @@ class ChunkAligner:
|
|
|
95
199
|
The padded system block.
|
|
96
200
|
"""
|
|
97
201
|
prefix_tokens = self._chat_prefix_tokens()
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
202
|
+
if self._filler is Filler.WORDS:
|
|
203
|
+
block = self._pad_to_multiple(
|
|
204
|
+
system_prompt, prefix_tokens, random.Random(self._seed)
|
|
205
|
+
)
|
|
206
|
+
pad = self.token_length(block) - self.token_length(system_prompt)
|
|
207
|
+
else:
|
|
208
|
+
block, pad = self._pad_framed(
|
|
209
|
+
"", system_prompt, _SYSTEM_TAIL, prefix_tokens
|
|
210
|
+
)
|
|
211
|
+
self._system_pad_tokens = pad
|
|
101
212
|
logger.info(
|
|
102
213
|
"System block: %d tokens after a %d-token chat-template prefix",
|
|
103
214
|
self.token_length(block),
|
|
@@ -114,12 +225,161 @@ class ChunkAligner:
|
|
|
114
225
|
Returns:
|
|
115
226
|
The padded passage; identical across calls for the same text.
|
|
116
227
|
"""
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
228
|
+
return self._padded_passage(passage)[0]
|
|
229
|
+
|
|
230
|
+
def pad_tokens(self, passage: str) -> int:
|
|
231
|
+
"""Return how many padding tokens :meth:`passage_block` adds.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
passage: Raw passage text.
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
Tokens of filler (``words``: the words; framed fillers: the
|
|
238
|
+
repeated unit), excluding the frame around the text.
|
|
239
|
+
"""
|
|
240
|
+
return self._padded_passage(passage)[1]
|
|
241
|
+
|
|
242
|
+
def framed(self, passage: str) -> str:
|
|
243
|
+
"""Return *passage* in the framed layout without padding.
|
|
244
|
+
|
|
245
|
+
``Passage <id>:\n<text>\n\nend of passage <id>``: the same label
|
|
246
|
+
and end marker as the ``dots`` / ``dashes`` blocks, for prompts that
|
|
247
|
+
are not chunk-aligned (``bench quality``'s ``concatenated``
|
|
248
|
+
scenario).
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
passage: Raw passage text.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
The framed passage.
|
|
255
|
+
"""
|
|
256
|
+
ident = block_id(passage)
|
|
257
|
+
return f"Passage {ident}:\n{passage}\n\nend of passage {ident}"
|
|
258
|
+
|
|
259
|
+
def render_and_encode(
|
|
260
|
+
self, messages: list[dict[str, str]]
|
|
261
|
+
) -> tuple[str, list[int]]:
|
|
262
|
+
"""Render *messages* with the chat template and tokenize the result.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
messages: Chat messages, as sent to the engine.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
The rendered prompt and its token ids, as the engine sees them.
|
|
269
|
+
"""
|
|
270
|
+
rendered = self._render(messages)
|
|
271
|
+
return rendered, self._encode(rendered)
|
|
272
|
+
|
|
273
|
+
def token_boundary(
|
|
274
|
+
self, rendered: str, char_index: int, ids: list[int]
|
|
275
|
+
) -> tuple[int, bool]:
|
|
276
|
+
"""Map a character position in a rendered prompt to a token index.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
rendered: A prompt from :meth:`render_and_encode`.
|
|
280
|
+
char_index: A character position in it.
|
|
281
|
+
ids: Its token ids.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
The number of tokens before *char_index*, and whether a token
|
|
285
|
+
boundary falls exactly there (the text before it tokenizes to a
|
|
286
|
+
prefix of the whole prompt's tokens).
|
|
287
|
+
"""
|
|
288
|
+
head = self._encode(rendered[:char_index])
|
|
289
|
+
return len(head), ids[: len(head)] == head
|
|
290
|
+
|
|
291
|
+
def place_blocks(
|
|
292
|
+
self, messages: list[dict[str, str]], blocks: list[str]
|
|
293
|
+
) -> tuple[int, list[BlockPlacement]]:
|
|
294
|
+
"""Locate *blocks* in the prompt the engine will tokenize.
|
|
295
|
+
|
|
296
|
+
Renders *messages* with the chat template (as the engine does),
|
|
297
|
+
tokenizes the result, and checks each block: it must appear
|
|
298
|
+
verbatim, in order, start and end on chunk boundaries, and no token
|
|
299
|
+
may straddle either boundary. Only then do its chunks hash the same
|
|
300
|
+
here as in any other prompt holding it -- which callers check by
|
|
301
|
+
comparing ``tokens`` across prompts.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
messages: The chat messages, as sent to the engine.
|
|
305
|
+
blocks: The padded blocks the messages contain, in order.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
The rendered prompt's token count, and one placement per block.
|
|
309
|
+
"""
|
|
310
|
+
rendered, ids = self.render_and_encode(messages)
|
|
311
|
+
placements: list[BlockPlacement] = []
|
|
312
|
+
cursor = 0
|
|
313
|
+
for block in blocks:
|
|
314
|
+
start = rendered.find(block, cursor)
|
|
315
|
+
if start < 0:
|
|
316
|
+
placements.append(
|
|
317
|
+
BlockPlacement(
|
|
318
|
+
block,
|
|
319
|
+
-1,
|
|
320
|
+
(),
|
|
321
|
+
"not found verbatim in the rendered prompt (the chat "
|
|
322
|
+
"template changed it, e.g. trimmed whitespace)",
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
continue
|
|
326
|
+
end = start + len(block)
|
|
327
|
+
cursor = end
|
|
328
|
+
first, clean_start = self.token_boundary(rendered, start, ids)
|
|
329
|
+
last, clean_end = self.token_boundary(rendered, end, ids)
|
|
330
|
+
problems = []
|
|
331
|
+
if not clean_start:
|
|
332
|
+
problems.append("a token straddles its start")
|
|
333
|
+
if not clean_end:
|
|
334
|
+
problems.append("a token straddles its end")
|
|
335
|
+
if clean_start and first % self._align_tokens:
|
|
336
|
+
problems.append(
|
|
337
|
+
f"starts at token {first}, not on a "
|
|
338
|
+
f"{self._align_tokens}-token chunk boundary"
|
|
339
|
+
)
|
|
340
|
+
if clean_start and clean_end and (last - first) % self._align_tokens:
|
|
341
|
+
problems.append(
|
|
342
|
+
f"spans {last - first} tokens, not a whole number of chunks"
|
|
343
|
+
)
|
|
344
|
+
placements.append(
|
|
345
|
+
BlockPlacement(
|
|
346
|
+
block, first, tuple(ids[first:last]), "; ".join(problems)
|
|
347
|
+
)
|
|
348
|
+
)
|
|
349
|
+
return len(ids), placements
|
|
350
|
+
|
|
351
|
+
def _padded_passage(self, passage: str) -> tuple[str, int]:
|
|
352
|
+
"""Pad *passage* once and cache the block with its padding count."""
|
|
353
|
+
cached = self._passage_blocks.get(passage)
|
|
354
|
+
if cached is None:
|
|
355
|
+
if self._filler is Filler.WORDS:
|
|
356
|
+
rng = random.Random(f"{self._seed}:{passage}")
|
|
357
|
+
block = self._pad_to_multiple(passage, 0, rng)
|
|
358
|
+
cached = (block, self.token_length(block) - self.token_length(passage))
|
|
359
|
+
else:
|
|
360
|
+
ident = block_id(passage)
|
|
361
|
+
cached = self._pad_framed(
|
|
362
|
+
f"Passage {ident}:\n", passage, f"\nend of passage {ident}", 0
|
|
363
|
+
)
|
|
364
|
+
self._passage_blocks[passage] = cached
|
|
365
|
+
return cached
|
|
366
|
+
|
|
367
|
+
def _render(self, messages: list[dict[str, str]]) -> str:
|
|
368
|
+
"""Render *messages* as the engine will, or their content without one."""
|
|
369
|
+
try:
|
|
370
|
+
return str(
|
|
371
|
+
self._tokenizer.apply_chat_template(
|
|
372
|
+
messages,
|
|
373
|
+
tokenize=False,
|
|
374
|
+
add_generation_prompt=True,
|
|
375
|
+
**self._template_kwargs,
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
except Exception: # noqa: BLE001 - no template: content only, as below
|
|
379
|
+
return "".join(m["content"] for m in messages)
|
|
380
|
+
|
|
381
|
+
def _encode(self, text: str) -> list[int]:
|
|
382
|
+
return list(self._tokenizer.encode(text, add_special_tokens=False))
|
|
123
383
|
|
|
124
384
|
def _chat_prefix_tokens(self) -> int:
|
|
125
385
|
"""Count the tokens the chat template inserts before the content.
|
|
@@ -133,6 +393,7 @@ class ChunkAligner:
|
|
|
133
393
|
[{"role": "user", "content": _TEMPLATE_SENTINEL}],
|
|
134
394
|
tokenize=False,
|
|
135
395
|
add_generation_prompt=True,
|
|
396
|
+
**self._template_kwargs,
|
|
136
397
|
)
|
|
137
398
|
except Exception as e: # noqa: BLE001 - a template failure is non-fatal
|
|
138
399
|
logger.warning(
|
|
@@ -146,8 +407,54 @@ class ChunkAligner:
|
|
|
146
407
|
head = str(rendered).split(_TEMPLATE_SENTINEL)[0]
|
|
147
408
|
return self.token_length(head)
|
|
148
409
|
|
|
410
|
+
def _pad_framed(
|
|
411
|
+
self, head: str, text: str, tail: str, offset: int
|
|
412
|
+
) -> tuple[str, int]:
|
|
413
|
+
"""Pad ``head + text + "\\n\\n" + unit*n + tail`` to a chunk boundary.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
head: Text before *text* (the passage label, or ``""``).
|
|
417
|
+
text: The passage or system prompt.
|
|
418
|
+
tail: Text after the padding (the end marker).
|
|
419
|
+
offset: Tokens already ahead of the block.
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
The padded block and its padding token count. When no count
|
|
423
|
+
lands on a boundary the closest attempt is returned; the
|
|
424
|
+
in-context check (:meth:`place_blocks`) then reports it.
|
|
425
|
+
"""
|
|
426
|
+
unit = _PAD_UNITS[self._filler]
|
|
427
|
+
align = self._align_tokens
|
|
428
|
+
|
|
429
|
+
def build(n: int) -> str:
|
|
430
|
+
return head + text + "\n\n" + unit * n + tail
|
|
431
|
+
|
|
432
|
+
base = self.token_length(build(0))
|
|
433
|
+
target = math.ceil((offset + base) / align) * align
|
|
434
|
+
block = build(0)
|
|
435
|
+
for _ in range(_PAD_TARGETS):
|
|
436
|
+
n = target - offset - base
|
|
437
|
+
for _ in range(_PAD_ATTEMPTS):
|
|
438
|
+
n = max(n, 0)
|
|
439
|
+
block = build(n)
|
|
440
|
+
actual = offset + self.token_length(block)
|
|
441
|
+
if actual == target:
|
|
442
|
+
return block, actual - offset - base
|
|
443
|
+
n += target - actual
|
|
444
|
+
target += align
|
|
445
|
+
logger.warning(
|
|
446
|
+
"Could not pad a block to a %d-token boundary with %r; cache "
|
|
447
|
+
"reuse will be partial",
|
|
448
|
+
align,
|
|
449
|
+
unit,
|
|
450
|
+
)
|
|
451
|
+
return block, self.token_length(block) - base
|
|
452
|
+
|
|
149
453
|
def _pad_to_multiple(self, text: str, offset: int, rng: random.Random) -> str:
|
|
150
454
|
"""Pad *text* so ``offset + len(text)`` is a whole chunk count."""
|
|
455
|
+
pool = self._pool
|
|
456
|
+
if pool is None:
|
|
457
|
+
raise RuntimeError("word padding needs the words filler")
|
|
151
458
|
align = self._align_tokens
|
|
152
459
|
total = offset + self.token_length(text)
|
|
153
460
|
target = math.ceil(total / align) * align
|
|
@@ -159,8 +466,8 @@ class ChunkAligner:
|
|
|
159
466
|
# Correct against a re-encode: a merged boundary token shifts the
|
|
160
467
|
# estimate, and an off-by-one phase error costs a whole chunk.
|
|
161
468
|
for _ in range(_PAD_ATTEMPTS):
|
|
162
|
-
words = [rng.choice(
|
|
163
|
-
padded = text + "\n" +
|
|
469
|
+
words = [rng.choice(pool.words) for _ in range(max(num_words, 0))]
|
|
470
|
+
padded = text + "\n" + pool.join(words)
|
|
164
471
|
actual = offset + self.token_length(padded)
|
|
165
472
|
if actual == target:
|
|
166
473
|
return padded
|
|
@@ -4,8 +4,19 @@
|
|
|
4
4
|
Kept in one module so every benchmark that scores answers over the same
|
|
5
5
|
passages asks byte-identical questions: a prompt difference between two runs
|
|
6
6
|
would read as a quality difference.
|
|
7
|
+
|
|
8
|
+
Two versions exist. :data:`ANSWER_TAG_PROMPT` (``bench quality``) asks for
|
|
9
|
+
``<answer>`` tags. :data:`QA_SYSTEM_PROMPT` / :data:`QA_QUESTION_TEMPLATE`
|
|
10
|
+
(``rag-qa-quality``) ask for ``<final_answer>`` tags, which gpt-oss trips
|
|
11
|
+
over: its response format names the answer channel ``final``, and a prompt
|
|
12
|
+
full of ``final_answer`` leads it to write ``<|channel|>final_answer>…``,
|
|
13
|
+
dropping the opening tag. ``extract_final_answer`` accepts both tags and that
|
|
14
|
+
form.
|
|
7
15
|
"""
|
|
8
16
|
|
|
17
|
+
# Standard
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
|
|
9
20
|
QA_SYSTEM_PROMPT = (
|
|
10
21
|
"Answer the question using only the given passages. You may reason "
|
|
11
22
|
"through the problem, but put only the concise final answer between "
|
|
@@ -20,16 +31,50 @@ QA_QUESTION_TEMPLATE = (
|
|
|
20
31
|
)
|
|
21
32
|
|
|
22
33
|
|
|
34
|
+
@dataclass(frozen=True)
|
|
35
|
+
class QAPrompt:
|
|
36
|
+
"""The instructions around a question's passages.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
system: Placed before the passages (then chunk-aligned).
|
|
40
|
+
question_template: Placed after them; ``{question}`` is filled in.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
system: str
|
|
44
|
+
question_template: str
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
ANSWER_TAG_PROMPT = QAPrompt(
|
|
48
|
+
system=(
|
|
49
|
+
"Answer the question using only the given passages. You may reason "
|
|
50
|
+
"through the problem, but put only the concise answer between "
|
|
51
|
+
"<answer> and </answer>. Always emit both tags.\n\n"
|
|
52
|
+
"The following are the given passages.\n"
|
|
53
|
+
),
|
|
54
|
+
question_template=(
|
|
55
|
+
"\n\nAnswer the question using only the passages above. End with "
|
|
56
|
+
"exactly one concise answer in this form: <answer>answer</answer>.\n\n"
|
|
57
|
+
"Question: {question}\nAnswer:"
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
"""The prompt with ``<answer>`` tags, which no checked chat format uses."""
|
|
61
|
+
|
|
62
|
+
|
|
23
63
|
def compose_qa_messages(
|
|
24
|
-
system_block: str,
|
|
64
|
+
system_block: str,
|
|
65
|
+
passage_blocks: list[str],
|
|
66
|
+
question: str,
|
|
67
|
+
question_template: str = QA_QUESTION_TEMPLATE,
|
|
25
68
|
) -> list[dict[str, str]]:
|
|
26
69
|
"""Build the chat messages for one question over its passages.
|
|
27
70
|
|
|
28
71
|
Args:
|
|
29
|
-
system_block: :
|
|
72
|
+
system_block: A :class:`QAPrompt` system text, chunk-aligned by
|
|
30
73
|
:meth:`ChunkAligner.system_block`.
|
|
31
74
|
passage_blocks: Chunk-aligned passages, in the order to present them.
|
|
32
75
|
question: The question text.
|
|
76
|
+
question_template: The text after the passages, with a
|
|
77
|
+
``{question}`` field; the system block's prompt version.
|
|
33
78
|
|
|
34
79
|
Returns:
|
|
35
80
|
A single user message:
|
|
@@ -38,7 +83,7 @@ def compose_qa_messages(
|
|
|
38
83
|
content = (
|
|
39
84
|
system_block
|
|
40
85
|
+ "".join(passage_blocks)
|
|
41
|
-
+
|
|
86
|
+
+ question_template.format(question=question)
|
|
42
87
|
)
|
|
43
88
|
return [{"role": "user", "content": content}]
|
|
44
89
|
|
|
@@ -17,8 +17,12 @@ _PUNCTUATION = str.maketrans("", "", string.punctuation)
|
|
|
17
17
|
|
|
18
18
|
# Lazy match, last region wins: reasoning models may echo an example answer
|
|
19
19
|
# before their own. Include an unfinished last region to avoid scoring the example.
|
|
20
|
+
# Opening tags: <final_answer> or <answer>, or a bare "final_answer>" -- gpt-oss
|
|
21
|
+
# opens its answer channel with "<|channel|>final" and, primed by the prompt,
|
|
22
|
+
# continues "_answer>" instead of "<|message|>", so the "<" never appears.
|
|
20
23
|
_FINAL_ANSWER = re.compile(
|
|
21
|
-
r"
|
|
24
|
+
r"(?:<(?:final_answer|answer)>|(?<![<\w/])final_answer>)"
|
|
25
|
+
r"\s*(.*?)\s*(</(?:final_answer|answer)>|$)",
|
|
22
26
|
re.IGNORECASE | re.DOTALL,
|
|
23
27
|
)
|
|
24
28
|
|
|
@@ -106,8 +110,11 @@ _ABSTENTION_ANSWERS = frozenset({"none", "n/a", "na", "null", "unknown"})
|
|
|
106
110
|
def extract_final_answer(output: str) -> str:
|
|
107
111
|
"""Extract the model's delimited final answer from a response.
|
|
108
112
|
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
Accepts ``<final_answer>…</final_answer>`` and ``<answer>…</answer>``
|
|
114
|
+
(either prompt version), and gpt-oss's ``final_answer>…</final_answer>``
|
|
115
|
+
with the opening ``<`` lost to its channel header. An unterminated region
|
|
116
|
+
counts as no answer: generation was cut off, so scoring the reasoning
|
|
117
|
+
before it would report an answer never produced.
|
|
111
118
|
|
|
112
119
|
Args:
|
|
113
120
|
output: The model's full response text.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""``lmcache bench quality`` command group.
|
|
3
|
+
|
|
4
|
+
Measures whether a serving engine change -- e.g. quantization, KV cache
|
|
5
|
+
compression, post-training, or another engine or model -- changes the
|
|
6
|
+
answers compared with a reference, in two stages:
|
|
7
|
+
|
|
8
|
+
* ``run`` sends byte-identical prompts to one or more arms (a reference, a
|
|
9
|
+
candidate under test, and optionally a control) and
|
|
10
|
+
saves their answers to a results directory.
|
|
11
|
+
* ``analyze`` scores the saved answers and reports per-question rows and a
|
|
12
|
+
verdict.
|
|
13
|
+
|
|
14
|
+
Arms can be run one at a time into the same directory, so they never need
|
|
15
|
+
to be served at once. The subcommands are auto-discovered from the
|
|
16
|
+
``*_command`` modules in this package; the runtime lives in ``core`` so that
|
|
17
|
+
discovery imports only these lightweight wrappers.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# First Party
|
|
21
|
+
from lmcache.cli.commands.base import CompositeCommand
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class QualityBenchCommand(CompositeCommand):
|
|
25
|
+
"""Compare a candidate serving stack's answers with a reference."""
|
|
26
|
+
|
|
27
|
+
def name(self) -> str:
|
|
28
|
+
return "quality"
|
|
29
|
+
|
|
30
|
+
def help(self) -> str:
|
|
31
|
+
return (
|
|
32
|
+
"Compare a candidate serving stack's answers with a reference "
|
|
33
|
+
"(e.g. a quantized model vs the original)."
|
|
34
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""``lmcache bench quality analyze`` subcommand."""
|
|
3
|
+
|
|
4
|
+
# Standard
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
# First Party
|
|
8
|
+
from lmcache.cli.commands.base import BaseCommand
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class QualityAnalyzeCommand(BaseCommand):
|
|
12
|
+
"""Score saved arm answers and reach the quality verdict."""
|
|
13
|
+
|
|
14
|
+
def name(self) -> str:
|
|
15
|
+
return "analyze"
|
|
16
|
+
|
|
17
|
+
def help(self) -> str:
|
|
18
|
+
return "Score the saved answers in a results directory and print a verdict."
|
|
19
|
+
|
|
20
|
+
def add_arguments(self, parser: argparse.ArgumentParser) -> None:
|
|
21
|
+
# First Party
|
|
22
|
+
from lmcache.cli.commands.bench.quality_bench.core.analyze_stage import (
|
|
23
|
+
add_analyze_arguments,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
add_analyze_arguments(parser)
|
|
27
|
+
|
|
28
|
+
def register(self, subparsers: argparse._SubParsersAction) -> None:
|
|
29
|
+
"""Register without the common output args (--format/--output).
|
|
30
|
+
|
|
31
|
+
Results are written into the results directory, so the generic
|
|
32
|
+
output flags do not apply.
|
|
33
|
+
"""
|
|
34
|
+
parser = subparsers.add_parser(self.name(), help=self.help())
|
|
35
|
+
self.add_arguments(parser)
|
|
36
|
+
parser.set_defaults(func=self.execute)
|
|
37
|
+
|
|
38
|
+
def execute(self, args: argparse.Namespace) -> None:
|
|
39
|
+
# First Party
|
|
40
|
+
from lmcache.cli.commands.bench.quality_bench.core.analyze_stage import (
|
|
41
|
+
run_quality_analyze,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
run_quality_analyze(args)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Runtime for ``lmcache bench quality``: plans, arm runs, scoring, verdict.
|
|
3
|
+
|
|
4
|
+
Kept in a sub-package so that command discovery, which imports every
|
|
5
|
+
module directly under ``quality_bench``, imports only the lightweight
|
|
6
|
+
command wrappers.
|
|
7
|
+
"""
|