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,1424 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#
|
|
3
|
+
# This file contains Python non-CUDA fallback implementations for
|
|
4
|
+
# CUDA-specific operations.
|
|
5
|
+
#
|
|
6
|
+
# Standard
|
|
7
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
8
|
+
from enum import Enum, IntEnum
|
|
9
|
+
from multiprocessing import shared_memory
|
|
10
|
+
from typing import Optional, Tuple
|
|
11
|
+
import ctypes
|
|
12
|
+
import ctypes.util
|
|
13
|
+
|
|
14
|
+
# Third Party
|
|
15
|
+
from numba import njit
|
|
16
|
+
import numpy as np
|
|
17
|
+
import torch
|
|
18
|
+
|
|
19
|
+
# Store the tensor objects in memory so that they can be accessed
|
|
20
|
+
# outside the scope of this file
|
|
21
|
+
_tensor_registry: dict[int, torch.Tensor] = {}
|
|
22
|
+
_shm_registry: dict[int, shared_memory.SharedMemory] = {}
|
|
23
|
+
_buf_registry: dict[int, ctypes.Array] = {}
|
|
24
|
+
|
|
25
|
+
# Cached copy library for lmcache_memcpy_async (lazy-initialized)
|
|
26
|
+
_copy_lib_NOT_LOADED = object()
|
|
27
|
+
_copy_lib: Optional[ctypes.CDLL] = _copy_lib_NOT_LOADED # type: ignore
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_copy_lib() -> Optional[ctypes.CDLL]:
|
|
31
|
+
"""Lazily load and cache the CUDA/ROCm runtime library, or None for CPU fallback."""
|
|
32
|
+
global _copy_lib
|
|
33
|
+
if _copy_lib is _copy_lib_NOT_LOADED:
|
|
34
|
+
# Try to load GPU runtime libraries in priority order: CUDA first, then ROCm
|
|
35
|
+
# TODO: ROCm path to be validated on real device
|
|
36
|
+
for name, fallback in [
|
|
37
|
+
("cudart", "libcudart.so"), # NVIDIA CUDA Runtime
|
|
38
|
+
("amdhip64", "libamdhip64.so"), # AMD ROCm HIP Runtime
|
|
39
|
+
]:
|
|
40
|
+
try:
|
|
41
|
+
path = ctypes.util.find_library(name)
|
|
42
|
+
if path:
|
|
43
|
+
_copy_lib = ctypes.CDLL(path)
|
|
44
|
+
else:
|
|
45
|
+
_copy_lib = ctypes.CDLL(fallback)
|
|
46
|
+
break # Successfully loaded, stop trying
|
|
47
|
+
except OSError:
|
|
48
|
+
continue # Current library not available, try next
|
|
49
|
+
else:
|
|
50
|
+
# All GPU libraries failed to load, fall back to CPU
|
|
51
|
+
_copy_lib = None
|
|
52
|
+
return _copy_lib
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _tensor_from_ptr(
|
|
56
|
+
ptr: int,
|
|
57
|
+
shape: tuple[int, ...],
|
|
58
|
+
dtype: torch.dtype,
|
|
59
|
+
device: torch.device | str | None = None,
|
|
60
|
+
) -> torch.Tensor:
|
|
61
|
+
"""
|
|
62
|
+
Create a tensor view over a raw pointer (zero-copy where possible).
|
|
63
|
+
|
|
64
|
+
Supports both CPU (pinned or regular) and CUDA device pointers.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
ptr: Raw memory pointer as int (must be non-zero).
|
|
68
|
+
shape: Desired tensor shape.
|
|
69
|
+
dtype: Desired tensor dtype, must match the memory layout.
|
|
70
|
+
device: Where the pointer lives.
|
|
71
|
+
- None / "cpu" / torch.device("cpu") → CPU pointer
|
|
72
|
+
- "cuda" / "cuda:N" / torch.device("cuda", N) → CUDA pointer
|
|
73
|
+
If None and ptr looks like a CUDA ptr, pass device explicitly.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
A tensor that shares memory with the original pointer.
|
|
77
|
+
For CPU: always zero-copy via ctypes + torch.frombuffer.
|
|
78
|
+
For CUDA: zero-copy via torch._C._construct_storage_from_data_pointer
|
|
79
|
+
(PyTorch >= 2.0) or __cuda_array_interface__, with a
|
|
80
|
+
cudaMemcpy D2D fallback.
|
|
81
|
+
|
|
82
|
+
Raises:
|
|
83
|
+
ValueError: if ptr is 0.
|
|
84
|
+
|
|
85
|
+
Warning:
|
|
86
|
+
The caller is responsible for keeping the underlying memory alive
|
|
87
|
+
for the entire lifetime of the returned tensor.
|
|
88
|
+
"""
|
|
89
|
+
if ptr == 0:
|
|
90
|
+
raise ValueError("Pointer must be non-zero")
|
|
91
|
+
|
|
92
|
+
# ------------------------------------------------------------------ #
|
|
93
|
+
# Normalise device #
|
|
94
|
+
# ------------------------------------------------------------------ #
|
|
95
|
+
if device is None:
|
|
96
|
+
device = torch.device("cpu")
|
|
97
|
+
elif not isinstance(device, torch.device):
|
|
98
|
+
device = torch.device(device)
|
|
99
|
+
|
|
100
|
+
assert isinstance(device, torch.device)
|
|
101
|
+
# ------------------------------------------------------------------ #
|
|
102
|
+
# Compute size #
|
|
103
|
+
# ------------------------------------------------------------------ #
|
|
104
|
+
numel = 1
|
|
105
|
+
for dim in shape:
|
|
106
|
+
numel *= int(dim)
|
|
107
|
+
element_size = torch.empty((), dtype=dtype).element_size()
|
|
108
|
+
total_bytes = numel * element_size
|
|
109
|
+
|
|
110
|
+
# ------------------------------------------------------------------ #
|
|
111
|
+
# CPU path #
|
|
112
|
+
# ------------------------------------------------------------------ #
|
|
113
|
+
if device.type == "cpu":
|
|
114
|
+
return _tensor_from_cpu_ptr(ptr, shape, dtype, numel, total_bytes)
|
|
115
|
+
|
|
116
|
+
# ------------------------------------------------------------------ #
|
|
117
|
+
# CUDA path #
|
|
118
|
+
# ------------------------------------------------------------------ #
|
|
119
|
+
if device.type == "cuda":
|
|
120
|
+
return _tensor_from_cuda_ptr(ptr, shape, dtype, device, numel, total_bytes)
|
|
121
|
+
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"Unsupported device type: {device.type!r}. Expected 'cpu' or 'cuda'."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ====================================================================== #
|
|
128
|
+
# CPU implementation #
|
|
129
|
+
# ====================================================================== #
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _tensor_from_cpu_ptr(
|
|
133
|
+
ptr: int,
|
|
134
|
+
shape: tuple[int, ...],
|
|
135
|
+
dtype: torch.dtype,
|
|
136
|
+
numel: int,
|
|
137
|
+
total_bytes: int,
|
|
138
|
+
) -> torch.Tensor:
|
|
139
|
+
"""
|
|
140
|
+
Zero-copy CPU tensor from a raw host pointer via ctypes + torch.frombuffer.
|
|
141
|
+
|
|
142
|
+
"""
|
|
143
|
+
buffer_type = ctypes.c_uint8 * total_bytes
|
|
144
|
+
buf = buffer_type.from_address(ptr)
|
|
145
|
+
# torch.frombuffer is zero-copy for contiguous byte buffers on CPU.
|
|
146
|
+
return torch.frombuffer(buf, dtype=dtype).view(*shape)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# ====================================================================== #
|
|
150
|
+
# CUDA implementation #
|
|
151
|
+
# ====================================================================== #
|
|
152
|
+
def _tensor_from_cuda_ptr(
|
|
153
|
+
ptr: int,
|
|
154
|
+
shape: tuple[int, ...],
|
|
155
|
+
dtype: torch.dtype,
|
|
156
|
+
device: torch.device,
|
|
157
|
+
numel: int,
|
|
158
|
+
total_bytes: int,
|
|
159
|
+
) -> torch.Tensor:
|
|
160
|
+
"""Zero-copy CUDA tensor from a raw device pointer."""
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
_DTYPE_TO_TYPESTR = {
|
|
164
|
+
torch.float16: "<f2",
|
|
165
|
+
torch.float32: "<f4",
|
|
166
|
+
torch.float64: "<f8",
|
|
167
|
+
torch.int8: "|i1",
|
|
168
|
+
torch.int16: "<i2",
|
|
169
|
+
torch.int32: "<i4",
|
|
170
|
+
torch.int64: "<i8",
|
|
171
|
+
torch.uint8: "|u1",
|
|
172
|
+
torch.bool: "|b1",
|
|
173
|
+
}
|
|
174
|
+
is_bf16 = dtype == torch.bfloat16
|
|
175
|
+
|
|
176
|
+
# Determine the correct typestr, smuggle bfloat16 as int16
|
|
177
|
+
typestr = "<i2" if is_bf16 else _DTYPE_TO_TYPESTR.get(dtype, "|u1")
|
|
178
|
+
|
|
179
|
+
class _CudaArrayWrapper:
|
|
180
|
+
def __init__(self, ptr_int: int, shape_tuple: tuple, type_str: str):
|
|
181
|
+
self.__cuda_array_interface__ = {
|
|
182
|
+
"data": (ptr_int, False),
|
|
183
|
+
"shape": shape_tuple,
|
|
184
|
+
"typestr": type_str,
|
|
185
|
+
"version": 3,
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
t = torch.as_tensor(_CudaArrayWrapper(ptr, (numel,), typestr), device=device)
|
|
189
|
+
if is_bf16:
|
|
190
|
+
t = t.view(torch.bfloat16)
|
|
191
|
+
|
|
192
|
+
return t.view(*shape)
|
|
193
|
+
except Exception:
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
# Strategy 2: cudaMemcpy Device-to-Device (Fallback)
|
|
197
|
+
libcudart = _get_copy_lib()
|
|
198
|
+
if libcudart is None:
|
|
199
|
+
raise RuntimeError("Failed to load libcudart/libamdhip")
|
|
200
|
+
|
|
201
|
+
cudaMemcpy = libcudart.cudaMemcpy
|
|
202
|
+
cudaMemcpy.restype = ctypes.c_int
|
|
203
|
+
cudaMemcpy.argtypes = [
|
|
204
|
+
ctypes.c_void_p,
|
|
205
|
+
ctypes.c_void_p,
|
|
206
|
+
ctypes.c_size_t,
|
|
207
|
+
ctypes.c_int,
|
|
208
|
+
]
|
|
209
|
+
_MEMCPY_D2D = 3
|
|
210
|
+
|
|
211
|
+
dst = torch.empty(numel, dtype=dtype, device=device)
|
|
212
|
+
|
|
213
|
+
err = cudaMemcpy(
|
|
214
|
+
ctypes.c_void_p(dst.data_ptr()),
|
|
215
|
+
ctypes.c_void_p(ptr),
|
|
216
|
+
ctypes.c_size_t(total_bytes),
|
|
217
|
+
ctypes.c_int(_MEMCPY_D2D),
|
|
218
|
+
)
|
|
219
|
+
if err != 0:
|
|
220
|
+
raise RuntimeError(f"cudaMemcpy D2D failed with error code {err}.")
|
|
221
|
+
|
|
222
|
+
return dst.view(*shape)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _copy_bytes_with_tensor(dst: int, src: int, num_bytes: int) -> None:
|
|
226
|
+
"""Copy raw bytes between pointers using torch tensor semantics.
|
|
227
|
+
|
|
228
|
+
Note: This function only works for CPU-accessible memory. For device
|
|
229
|
+
memory (CUDA/XPU), use lmcache_memcpy_async with the appropriate runtime
|
|
230
|
+
library or PyTorch's tensor copy operations.
|
|
231
|
+
"""
|
|
232
|
+
if num_bytes <= 0:
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
buffer_type = ctypes.c_uint8 * num_bytes
|
|
236
|
+
dst_tensor = torch.frombuffer(buffer_type.from_address(dst), dtype=torch.uint8)
|
|
237
|
+
src_tensor = torch.frombuffer(buffer_type.from_address(src), dtype=torch.uint8)
|
|
238
|
+
dst_tensor.copy_(src_tensor)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class TransferDirection(Enum):
|
|
242
|
+
"""Specifies the direction of a memory transfer."""
|
|
243
|
+
|
|
244
|
+
H2D = 0
|
|
245
|
+
D2H = 1
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class GPUKVFormat(IntEnum):
|
|
249
|
+
"""Enumeration of different GPU KV cache memory layouts."""
|
|
250
|
+
|
|
251
|
+
# used by: vLLM CROSS_LAYER mode
|
|
252
|
+
NB_NL_TWO_BS_NH_HS = 0
|
|
253
|
+
|
|
254
|
+
# used by: vLLM non-MLA flash attention
|
|
255
|
+
NL_X_TWO_NB_BS_NH_HS = 1
|
|
256
|
+
|
|
257
|
+
# used by: vLLM non-MLA flash infer
|
|
258
|
+
NL_X_NB_TWO_BS_NH_HS = 2
|
|
259
|
+
|
|
260
|
+
# used by: vLLM MLA
|
|
261
|
+
NL_X_NB_BS_HS = 3
|
|
262
|
+
|
|
263
|
+
# used by: SGLang MHA (flash attention and flash infer)
|
|
264
|
+
TWO_X_NL_X_NBBS_NH_HS = 4
|
|
265
|
+
|
|
266
|
+
# used by: SGLang MLA
|
|
267
|
+
NL_X_NBBS_ONE_HS = 5
|
|
268
|
+
|
|
269
|
+
# used by: vLLM non-MLA flash attention (HND layout)
|
|
270
|
+
NL_X_TWO_NB_NH_BS_HS = 6
|
|
271
|
+
|
|
272
|
+
# used by: vLLM non-MLA flash infer (HND layout)
|
|
273
|
+
NL_X_NB_TWO_NH_BS_HS = 7
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# On XPU (Intel GPU), PyTorch 2.4+ supports pin_memory=True via SYCL USM
|
|
277
|
+
# host allocation, enabling fast DMA for XPU<->CPU transfers.
|
|
278
|
+
_XPU_PIN_MEMORY = hasattr(torch, "xpu") and torch.xpu.is_available()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def alloc_pinned_numa_ptr(size: int, numa_id: int = 0) -> int:
|
|
282
|
+
"""Non-CUDA equivalent of allocating pinned memory with NUMA awareness.
|
|
283
|
+
On XPU, uses pin_memory=True (SYCL USM host allocation) for fast transfers.
|
|
284
|
+
Note: NUMA node selection is not supported on non-CUDA."""
|
|
285
|
+
|
|
286
|
+
# Create a 1D uint8 CPU tensor, as uint8 == 1 byte
|
|
287
|
+
tensor = torch.empty(size, dtype=torch.uint8, pin_memory=_XPU_PIN_MEMORY)
|
|
288
|
+
|
|
289
|
+
# First-touch initialization (forces physical allocation)
|
|
290
|
+
tensor.fill_(0)
|
|
291
|
+
|
|
292
|
+
# Get a pointer to the start of the tensor object as this is what is
|
|
293
|
+
# returned by the CUDA equivalent function
|
|
294
|
+
ptr = tensor.data_ptr()
|
|
295
|
+
|
|
296
|
+
# Store the tensor so it can be accessed outide this function scope
|
|
297
|
+
_tensor_registry[ptr] = tensor
|
|
298
|
+
|
|
299
|
+
return ptr
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def free_pinned_numa_ptr(ptr: int, size: int | None = None) -> None:
|
|
303
|
+
"""Non-CUDA equivalent of freeing a previously allocated NUMA pointer."""
|
|
304
|
+
|
|
305
|
+
# Release the tensor object for that pointer reference
|
|
306
|
+
_tensor_registry.pop(ptr, None)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def alloc_pinned_ptr(size: int, device_id: int = 0) -> int:
|
|
310
|
+
"""Non-CUDA equivalent of allocating pinned memory and returning pointer
|
|
311
|
+
to it. On XPU, uses pin_memory=True (SYCL USM host allocation) for
|
|
312
|
+
fast DMA transfers. On other non-CUDA platforms, pinning is not supported."""
|
|
313
|
+
|
|
314
|
+
# Create a 1D uint8 CPU tensor, as uint8 == 1 byte
|
|
315
|
+
tensor = torch.empty(size, dtype=torch.uint8, pin_memory=_XPU_PIN_MEMORY)
|
|
316
|
+
|
|
317
|
+
# First-touch initialization (forces physical allocation)
|
|
318
|
+
tensor.fill_(0)
|
|
319
|
+
|
|
320
|
+
# Get a pointer to the start of the tensor object as this is what is
|
|
321
|
+
# returned by the CUDA equivalent function
|
|
322
|
+
ptr = tensor.data_ptr()
|
|
323
|
+
|
|
324
|
+
# Store the tensor so it can be accessed outide this function scope
|
|
325
|
+
_tensor_registry[ptr] = tensor
|
|
326
|
+
|
|
327
|
+
return ptr
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def free_pinned_ptr(ptr: int) -> None:
|
|
331
|
+
"""Non-CUDA equivalent of freeing a previously allocated pinned pointer."""
|
|
332
|
+
|
|
333
|
+
# Release the tensor object for that pointer reference
|
|
334
|
+
_tensor_registry.pop(ptr, None)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def batched_memcpy(src_ptrs: list[int], dst_ptrs: list[int], sizes: list[int]) -> None:
|
|
338
|
+
"""Non-CUDA equivalent of the native batched memcpy helper."""
|
|
339
|
+
|
|
340
|
+
if len(src_ptrs) != len(dst_ptrs) or len(src_ptrs) != len(sizes):
|
|
341
|
+
raise ValueError(
|
|
342
|
+
"batched_memcpy expects equally sized src_ptrs, dst_ptrs, and sizes"
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
for src_ptr, dst_ptr, size in zip(src_ptrs, dst_ptrs, sizes, strict=True):
|
|
346
|
+
if size <= 0:
|
|
347
|
+
continue
|
|
348
|
+
ctypes.memmove(
|
|
349
|
+
ctypes.c_void_p(dst_ptr),
|
|
350
|
+
ctypes.c_void_p(src_ptr),
|
|
351
|
+
size,
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def alloc_shm_pinned_ptr(size: int, shm_name: str = "") -> int:
|
|
356
|
+
"""Non-CUDA equivalent of allocating shared memory pinned pointer.
|
|
357
|
+
Uses multiprocessing.shared_memory for cross-platform POSIX shm."""
|
|
358
|
+
|
|
359
|
+
# Strip leading '/' for SharedMemory name
|
|
360
|
+
name = shm_name.lstrip("/") if shm_name else None
|
|
361
|
+
|
|
362
|
+
# Clean up stale shm segment if it exists
|
|
363
|
+
if name:
|
|
364
|
+
try:
|
|
365
|
+
stale = shared_memory.SharedMemory(name=name, create=False)
|
|
366
|
+
stale.close()
|
|
367
|
+
stale.unlink()
|
|
368
|
+
except FileNotFoundError:
|
|
369
|
+
pass
|
|
370
|
+
|
|
371
|
+
shm = shared_memory.SharedMemory(name=name, create=True, size=size)
|
|
372
|
+
|
|
373
|
+
array_type = ctypes.c_uint8 * size
|
|
374
|
+
buf = array_type.from_buffer(shm.buf)
|
|
375
|
+
ptr = ctypes.addressof(buf)
|
|
376
|
+
|
|
377
|
+
# Store references to keep them alive
|
|
378
|
+
tensor = torch.frombuffer(buf, dtype=torch.uint8)
|
|
379
|
+
_tensor_registry[ptr] = tensor
|
|
380
|
+
_buf_registry[ptr] = buf
|
|
381
|
+
_shm_registry[ptr] = shm
|
|
382
|
+
return ptr
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def free_shm_pinned_ptr(ptr: int, size: int = 0, shm_name: str = "") -> None:
|
|
386
|
+
"""Non-CUDA equivalent of freeing a shared memory
|
|
387
|
+
pinned pointer."""
|
|
388
|
+
|
|
389
|
+
# Release in order: tensor -> ctypes buf -> shm
|
|
390
|
+
_tensor_registry.pop(ptr, None)
|
|
391
|
+
_buf_registry.pop(ptr, None)
|
|
392
|
+
shm = _shm_registry.pop(ptr, None)
|
|
393
|
+
if shm is not None:
|
|
394
|
+
shm.close()
|
|
395
|
+
shm.unlink()
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def alloc_numa_ptr(size: int, numa_id: int = 0) -> int:
|
|
399
|
+
"""Non-CUDA equivalent of allocating numa memory and returning pointer
|
|
400
|
+
to it. Note: Numa memory is not supported on non-CUDA."""
|
|
401
|
+
return alloc_pinned_numa_ptr(size, numa_id)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def free_numa_ptr(ptr: int, size: int | None = None) -> None:
|
|
405
|
+
"""Non-CUDA equivalent of freeing a previously allocated NUMA pointer."""
|
|
406
|
+
return free_pinned_numa_ptr(ptr, size)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def multi_layer_kv_transfer(
|
|
410
|
+
key_value: torch.Tensor,
|
|
411
|
+
key_value_ptrs: torch.Tensor | list[torch.Tensor],
|
|
412
|
+
slot_mapping: torch.Tensor,
|
|
413
|
+
paged_memory_device: torch.device,
|
|
414
|
+
page_buffer_size: int,
|
|
415
|
+
direction: TransferDirection,
|
|
416
|
+
gpu_kv_format: GPUKVFormat,
|
|
417
|
+
block_size: int = 0,
|
|
418
|
+
head_size: int = 0,
|
|
419
|
+
skip_prefix_n_tokens: int = 0,
|
|
420
|
+
):
|
|
421
|
+
"""
|
|
422
|
+
Fully vectorized Python fallback for multi_layer_kv_transfer.
|
|
423
|
+
Eliminates ALL token- and KV-level Python loops.
|
|
424
|
+
"""
|
|
425
|
+
if not isinstance(key_value_ptrs, (torch.Tensor, list)):
|
|
426
|
+
raise TypeError(
|
|
427
|
+
f"Expected torch.Tensor or list, but got {type(key_value_ptrs).__name__}"
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
# TODO: Implement head_size support for HND layouts (NL_X_TWO_NB_NH_BS_HS,
|
|
431
|
+
# NL_X_NB_TWO_NH_BS_HS) as next step.
|
|
432
|
+
if gpu_kv_format in (
|
|
433
|
+
GPUKVFormat.NL_X_TWO_NB_NH_BS_HS,
|
|
434
|
+
GPUKVFormat.NL_X_NB_TWO_NH_BS_HS,
|
|
435
|
+
):
|
|
436
|
+
raise NotImplementedError(
|
|
437
|
+
"HND layouts (NL_X_TWO_NB_NH_BS_HS, NL_X_NB_TWO_NH_BS_HS) "
|
|
438
|
+
"are not supported in the non-CUDA fallback. "
|
|
439
|
+
"head_size parameter is required but not implemented in this path."
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
# 1. Filter out invalid slots.
|
|
443
|
+
# valid_mask_kv: on key_value.device, used to index key_value
|
|
444
|
+
# valid_slots: on paged_memory_device, used to index paged_tensor
|
|
445
|
+
kv_device = key_value.device
|
|
446
|
+
slots_kv = slot_mapping.to(dtype=torch.long).to(kv_device)
|
|
447
|
+
valid_mask_kv = slots_kv >= 0
|
|
448
|
+
# Skip the first skip_prefix_n_tokens tokens from transfer.
|
|
449
|
+
# This matches the CUDA kernel semantics where the grid starts at
|
|
450
|
+
# token_id=0 but indexes key_value/slot_mapping at
|
|
451
|
+
# kv_token_id = token_id + skip_prefix_n_tokens.
|
|
452
|
+
# By masking them as invalid, the vectorized indexing via valid_mask_kv
|
|
453
|
+
# naturally skips them while keeping key_value indices aligned.
|
|
454
|
+
if skip_prefix_n_tokens > 0:
|
|
455
|
+
valid_mask_kv[:skip_prefix_n_tokens] = False
|
|
456
|
+
if not valid_mask_kv.any():
|
|
457
|
+
return
|
|
458
|
+
|
|
459
|
+
valid_slots = slots_kv[valid_mask_kv].to(paged_memory_device)
|
|
460
|
+
|
|
461
|
+
# 2. Determine architecture variant and tensor dimensions.
|
|
462
|
+
is_mla = gpu_kv_format in (
|
|
463
|
+
GPUKVFormat.NL_X_NB_BS_HS,
|
|
464
|
+
GPUKVFormat.NL_X_NBBS_ONE_HS,
|
|
465
|
+
)
|
|
466
|
+
is_flash_infer = gpu_kv_format == GPUKVFormat.NL_X_NB_TWO_BS_NH_HS
|
|
467
|
+
|
|
468
|
+
num_layers = key_value.size(1)
|
|
469
|
+
hidden_size = key_value.size(3)
|
|
470
|
+
|
|
471
|
+
# For the flash_infer interleaved layout, pre-compute block-level indices.
|
|
472
|
+
if is_flash_infer:
|
|
473
|
+
block_indices = valid_slots // block_size
|
|
474
|
+
block_offsets = valid_slots % block_size
|
|
475
|
+
|
|
476
|
+
# Determine the physical shape of the underlying paged tensor
|
|
477
|
+
# (used when wrapping a raw pointer).
|
|
478
|
+
layer_shape: Tuple[int, ...]
|
|
479
|
+
|
|
480
|
+
if is_mla:
|
|
481
|
+
layer_shape = (page_buffer_size, hidden_size)
|
|
482
|
+
elif is_flash_infer:
|
|
483
|
+
num_blocks = page_buffer_size // block_size
|
|
484
|
+
layer_shape = (num_blocks, 2, block_size, hidden_size)
|
|
485
|
+
else:
|
|
486
|
+
layer_shape = (2, page_buffer_size, hidden_size)
|
|
487
|
+
|
|
488
|
+
# 3. Iterate over layers — the only remaining Python-level loop.
|
|
489
|
+
for layer_id in range(num_layers):
|
|
490
|
+
# --- A. Obtain the physical device-memory view for this layer. ---
|
|
491
|
+
if isinstance(key_value_ptrs, list):
|
|
492
|
+
paged_tensor = key_value_ptrs[layer_id]
|
|
493
|
+
else:
|
|
494
|
+
ptr = int(key_value_ptrs[layer_id].item())
|
|
495
|
+
# Convert a raw device pointer into a PyTorch tensor view.
|
|
496
|
+
paged_tensor = _tensor_from_ptr(
|
|
497
|
+
ptr, layer_shape, key_value.dtype, paged_memory_device
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
# --- B. Vectorized bulk data transfer. ---
|
|
501
|
+
if is_mla:
|
|
502
|
+
# Paged layout : [page_buffer_size, hidden_size]
|
|
503
|
+
# key_value layout: [1, num_layers, num_tokens, hidden_size]
|
|
504
|
+
if direction == TransferDirection.H2D:
|
|
505
|
+
lmc_valid = key_value[0, layer_id, valid_mask_kv, :]
|
|
506
|
+
paged_tensor.index_copy_(
|
|
507
|
+
0, valid_slots, lmc_valid.to(paged_tensor.device)
|
|
508
|
+
)
|
|
509
|
+
else:
|
|
510
|
+
gathered = paged_tensor.index_select(0, valid_slots)
|
|
511
|
+
key_value[0, layer_id, valid_mask_kv, :] = gathered.to(
|
|
512
|
+
kv_device, non_blocking=False
|
|
513
|
+
)
|
|
514
|
+
elif is_flash_infer:
|
|
515
|
+
# Paged layout : [num_blocks, 2, block_size, hidden_size]
|
|
516
|
+
# key_value layout: [2, num_layers, num_tokens, hidden_size]
|
|
517
|
+
if direction == TransferDirection.H2D:
|
|
518
|
+
lmc_valid = key_value[:, layer_id, valid_mask_kv, :]
|
|
519
|
+
src_data = lmc_valid.transpose(0, 1).to(paged_memory_device)
|
|
520
|
+
# src_data: [num_valid, 2, hidden_size]
|
|
521
|
+
paged_tensor[block_indices, :, block_offsets, :] = src_data
|
|
522
|
+
else:
|
|
523
|
+
gathered = paged_tensor[block_indices, :, block_offsets, :]
|
|
524
|
+
# gathered: [num_valid, 2, hidden_size]
|
|
525
|
+
key_value[:, layer_id, valid_mask_kv, :] = gathered.to(
|
|
526
|
+
kv_device, non_blocking=False
|
|
527
|
+
).transpose(0, 1)
|
|
528
|
+
else:
|
|
529
|
+
# Paged layout : [2, page_buffer_size, hidden_size]
|
|
530
|
+
# key_value layout: [2, num_layers, num_tokens, hidden_size]
|
|
531
|
+
if direction == TransferDirection.H2D:
|
|
532
|
+
lmc_valid = key_value[:, layer_id, valid_mask_kv, :]
|
|
533
|
+
paged_tensor.index_copy_(
|
|
534
|
+
1, valid_slots, lmc_valid.to(paged_memory_device)
|
|
535
|
+
)
|
|
536
|
+
else:
|
|
537
|
+
gathered = paged_tensor.index_select(1, valid_slots)
|
|
538
|
+
key_value[:, layer_id, valid_mask_kv, :] = gathered.to(
|
|
539
|
+
kv_device, non_blocking=False
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def multi_layer_kv_transfer_unilateral(
|
|
544
|
+
key_value: torch.Tensor,
|
|
545
|
+
key_value_ptrs: torch.Tensor | list[torch.Tensor],
|
|
546
|
+
slot_mapping: torch.Tensor,
|
|
547
|
+
paged_memory_device: torch.device,
|
|
548
|
+
page_buffer_size: int,
|
|
549
|
+
direction: TransferDirection,
|
|
550
|
+
gpu_kv_format: GPUKVFormat,
|
|
551
|
+
):
|
|
552
|
+
"""
|
|
553
|
+
Python fallback for multi_layer_kv_transfer_unilateral
|
|
554
|
+
|
|
555
|
+
Handles SGLang MHA format where K and V paged buffers are stored separately:
|
|
556
|
+
ptrs = [K_layer0, K_layer1, ..., V_layer0, V_layer1, ...]
|
|
557
|
+
each buffer shape: [page_buffer_size, hidden_size]
|
|
558
|
+
|
|
559
|
+
For MLA, delegates to multi_layer_kv_transfer (same as C++ implementation).
|
|
560
|
+
|
|
561
|
+
key_value_ptrs:
|
|
562
|
+
- If torch.Tensor: int64 tensor containing raw memory pointers.
|
|
563
|
+
- If list[torch.Tensor]: list of tensor objects.
|
|
564
|
+
|
|
565
|
+
key_value layout:
|
|
566
|
+
- Standard: [2, num_layers, num_tokens, hidden_size]
|
|
567
|
+
- MLA: [1, num_layers, num_tokens, hidden_size]
|
|
568
|
+
|
|
569
|
+
direction:
|
|
570
|
+
H2D = LMCache -> PagedBuffer
|
|
571
|
+
D2H = PagedBuffer -> LMCache
|
|
572
|
+
"""
|
|
573
|
+
is_mla = gpu_kv_format in (
|
|
574
|
+
GPUKVFormat.NL_X_NB_BS_HS,
|
|
575
|
+
GPUKVFormat.NL_X_NBBS_ONE_HS,
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
# MLA case collapses back to multi_layer_kv_transfer
|
|
579
|
+
# (vLLM and SGLang indexing are compatible)
|
|
580
|
+
if is_mla:
|
|
581
|
+
return multi_layer_kv_transfer(
|
|
582
|
+
key_value,
|
|
583
|
+
key_value_ptrs,
|
|
584
|
+
slot_mapping,
|
|
585
|
+
paged_memory_device,
|
|
586
|
+
page_buffer_size,
|
|
587
|
+
direction,
|
|
588
|
+
gpu_kv_format,
|
|
589
|
+
0, # block_size unused for MLA formats
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
# ── Non-MLA path: unilateral (separate K/V buffers per layer) ──
|
|
593
|
+
num_layers = key_value.size(1)
|
|
594
|
+
hidden_size = key_value.size(3)
|
|
595
|
+
layer_shape = (page_buffer_size, hidden_size)
|
|
596
|
+
|
|
597
|
+
kv_device = key_value.device
|
|
598
|
+
slots_kv = slot_mapping.to(dtype=torch.long).to(kv_device)
|
|
599
|
+
valid_mask_kv = slots_kv >= 0
|
|
600
|
+
if not valid_mask_kv.any():
|
|
601
|
+
return
|
|
602
|
+
|
|
603
|
+
valid_slots = slots_kv[valid_mask_kv].to(paged_memory_device)
|
|
604
|
+
|
|
605
|
+
for layer_id in range(num_layers):
|
|
606
|
+
for kv_idx in range(2): # 0 = K, 1 = V
|
|
607
|
+
buffer_idx = layer_id + kv_idx * num_layers
|
|
608
|
+
if isinstance(key_value_ptrs, list):
|
|
609
|
+
paged_tensor = key_value_ptrs[buffer_idx]
|
|
610
|
+
else:
|
|
611
|
+
ptr = int(key_value_ptrs[buffer_idx].item())
|
|
612
|
+
paged_tensor = _tensor_from_ptr(
|
|
613
|
+
ptr, layer_shape, key_value.dtype, paged_memory_device
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
if direction == TransferDirection.H2D:
|
|
617
|
+
lmc_valid = key_value[kv_idx, layer_id, valid_mask_kv, :]
|
|
618
|
+
paged_tensor.index_copy_(
|
|
619
|
+
0, valid_slots, lmc_valid.to(paged_memory_device)
|
|
620
|
+
)
|
|
621
|
+
else:
|
|
622
|
+
gathered = paged_tensor.index_select(0, valid_slots)
|
|
623
|
+
key_value[kv_idx, layer_id, valid_mask_kv, :] = gathered.to(kv_device)
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def single_layer_kv_transfer(
|
|
627
|
+
lmc_key_value_cache: torch.Tensor,
|
|
628
|
+
vllm_key_value_cache: torch.Tensor,
|
|
629
|
+
slot_mapping: torch.Tensor,
|
|
630
|
+
direction: TransferDirection,
|
|
631
|
+
gpu_kv_format: GPUKVFormat,
|
|
632
|
+
token_major: bool = False,
|
|
633
|
+
):
|
|
634
|
+
"""
|
|
635
|
+
Vectorized Python fallback for single_layer_kv_transfer
|
|
636
|
+
(eliminates per-token loops).
|
|
637
|
+
|
|
638
|
+
Transfers KV data between LMCache buffer
|
|
639
|
+
and a single vLLM paged KV cache layer.
|
|
640
|
+
|
|
641
|
+
lmc_key_value_cache layout:
|
|
642
|
+
- MLA: [num_tokens, aligned_head_size]
|
|
643
|
+
- token_major=True: [num_tokens, 2, num_heads * head_size]
|
|
644
|
+
- token_major=False: [2, num_tokens, num_heads * head_size]
|
|
645
|
+
|
|
646
|
+
vllm_key_value_cache layout:
|
|
647
|
+
- NL_X_TWO_NB_BS_NH_HS (flash attn):
|
|
648
|
+
[2, num_blocks, block_size, num_heads, head_size]
|
|
649
|
+
- NL_X_NB_TWO_BS_NH_HS (flash infer):
|
|
650
|
+
[num_blocks, 2, block_size, num_heads, head_size]
|
|
651
|
+
- NL_X_NB_BS_HS (vLLM MLA):
|
|
652
|
+
[num_blocks, block_size, head_size]
|
|
653
|
+
|
|
654
|
+
direction:
|
|
655
|
+
H2D = LMCache -> vLLM GPU
|
|
656
|
+
D2H = vLLM GPU -> LMCache
|
|
657
|
+
"""
|
|
658
|
+
kv_device = lmc_key_value_cache.device
|
|
659
|
+
paged_memory_device = vllm_key_value_cache.device
|
|
660
|
+
slots_kv = slot_mapping.to(dtype=torch.long).to(kv_device)
|
|
661
|
+
valid_mask_kv = slots_kv >= 0
|
|
662
|
+
|
|
663
|
+
if not valid_mask_kv.any():
|
|
664
|
+
return
|
|
665
|
+
|
|
666
|
+
valid_token_indices = torch.nonzero(valid_mask_kv, as_tuple=True)[0]
|
|
667
|
+
valid_slots = slots_kv[valid_mask_kv].to(paged_memory_device)
|
|
668
|
+
|
|
669
|
+
is_mla = gpu_kv_format in (
|
|
670
|
+
GPUKVFormat.NL_X_NB_BS_HS,
|
|
671
|
+
GPUKVFormat.NL_X_NBBS_ONE_HS,
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
if is_mla:
|
|
675
|
+
# ── MLA format ──
|
|
676
|
+
# vllm: [num_blocks, block_size, head_size]
|
|
677
|
+
# lmc: [num_tokens, aligned_head_size]
|
|
678
|
+
block_size = vllm_key_value_cache.size(1)
|
|
679
|
+
block_indices = valid_slots // block_size
|
|
680
|
+
block_offsets = valid_slots % block_size
|
|
681
|
+
|
|
682
|
+
if direction == TransferDirection.D2H:
|
|
683
|
+
# vLLM -> LMCache
|
|
684
|
+
lmc_key_value_cache[valid_token_indices] = vllm_key_value_cache[
|
|
685
|
+
block_indices, block_offsets
|
|
686
|
+
].to(lmc_key_value_cache.device)
|
|
687
|
+
else:
|
|
688
|
+
# LMCache -> vLLM
|
|
689
|
+
vllm_key_value_cache[block_indices, block_offsets] = lmc_key_value_cache[
|
|
690
|
+
valid_token_indices
|
|
691
|
+
].to(paged_memory_device)
|
|
692
|
+
|
|
693
|
+
else:
|
|
694
|
+
# ── Non-MLA format ──
|
|
695
|
+
# Determine vLLM layout and block_size
|
|
696
|
+
is_two_major = gpu_kv_format == GPUKVFormat.NL_X_TWO_NB_BS_NH_HS
|
|
697
|
+
# flash attn:
|
|
698
|
+
# [2, num_blocks, block_size, num_heads, head_size]
|
|
699
|
+
# -> dim2 = block_size
|
|
700
|
+
# flash infer:
|
|
701
|
+
# [num_blocks, 2, block_size, num_heads, head_size]
|
|
702
|
+
# -> dim2 = block_size
|
|
703
|
+
block_size = vllm_key_value_cache.size(2)
|
|
704
|
+
num_heads = vllm_key_value_cache.size(3)
|
|
705
|
+
head_size = vllm_key_value_cache.size(4)
|
|
706
|
+
block_indices = valid_slots // block_size
|
|
707
|
+
block_offsets = valid_slots % block_size
|
|
708
|
+
|
|
709
|
+
for kv in range(2):
|
|
710
|
+
if direction == TransferDirection.D2H:
|
|
711
|
+
if is_two_major:
|
|
712
|
+
gathered = vllm_key_value_cache[kv, block_indices, block_offsets]
|
|
713
|
+
else:
|
|
714
|
+
gathered = vllm_key_value_cache[block_indices, kv, block_offsets]
|
|
715
|
+
|
|
716
|
+
gathered_flat = gathered.reshape(-1, num_heads * head_size).to(
|
|
717
|
+
lmc_key_value_cache.device
|
|
718
|
+
)
|
|
719
|
+
if token_major:
|
|
720
|
+
lmc_key_value_cache[valid_token_indices, kv] = gathered_flat
|
|
721
|
+
else:
|
|
722
|
+
lmc_key_value_cache[kv, valid_token_indices] = gathered_flat
|
|
723
|
+
else:
|
|
724
|
+
if token_major:
|
|
725
|
+
lmc_src = lmc_key_value_cache[valid_token_indices, kv]
|
|
726
|
+
else:
|
|
727
|
+
lmc_src = lmc_key_value_cache[kv, valid_token_indices]
|
|
728
|
+
lmc_reshaped = lmc_src.reshape(-1, num_heads, head_size).to(
|
|
729
|
+
vllm_key_value_cache.device
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
if is_two_major:
|
|
733
|
+
vllm_key_value_cache[kv, block_indices, block_offsets] = (
|
|
734
|
+
lmc_reshaped
|
|
735
|
+
)
|
|
736
|
+
else:
|
|
737
|
+
vllm_key_value_cache[block_indices, kv, block_offsets] = (
|
|
738
|
+
lmc_reshaped
|
|
739
|
+
)
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def single_layer_kv_transfer_sgl(
|
|
743
|
+
lmc_key_value_cache: torch.Tensor,
|
|
744
|
+
sgl_key_cache: torch.Tensor,
|
|
745
|
+
sgl_value_cache: torch.Tensor,
|
|
746
|
+
slot_mapping: torch.Tensor,
|
|
747
|
+
direction: TransferDirection,
|
|
748
|
+
token_major: bool = False,
|
|
749
|
+
):
|
|
750
|
+
"""
|
|
751
|
+
Python fallback implementation of single_layer_kv_transfer_sgl.
|
|
752
|
+
|
|
753
|
+
Args:
|
|
754
|
+
lmc_key_value_cache:
|
|
755
|
+
[num_tokens, 2, num_heads*head_size] or
|
|
756
|
+
[2, num_tokens, num_heads*head_size]
|
|
757
|
+
sgl_key_cache: [num_blocks, block_size, num_heads, head_size]
|
|
758
|
+
sgl_value_cache: [num_blocks, block_size, num_heads, head_size]
|
|
759
|
+
slot_mapping: [num_tokens] - maps each token to a global slot index
|
|
760
|
+
direction: False for LMCache -> SGLang, True for SGLang -> LMCache
|
|
761
|
+
token_major: Boolean to determine the layout of lmc_key_value_cache
|
|
762
|
+
"""
|
|
763
|
+
kv_device = lmc_key_value_cache.device
|
|
764
|
+
paged_memory_device = sgl_key_cache.device
|
|
765
|
+
slots_kv = slot_mapping.to(dtype=torch.long).to(kv_device)
|
|
766
|
+
valid_mask_kv = slots_kv >= 0
|
|
767
|
+
if not valid_mask_kv.any():
|
|
768
|
+
return
|
|
769
|
+
|
|
770
|
+
# 1. Get basic dimensions
|
|
771
|
+
block_size = sgl_key_cache.size(1)
|
|
772
|
+
num_heads = sgl_key_cache.size(2)
|
|
773
|
+
head_size = sgl_key_cache.size(3)
|
|
774
|
+
|
|
775
|
+
# 2. Calculate block indices and offsets within the blocks from slot_mapping
|
|
776
|
+
# In SGLang/vLLM, slot_idx = block_idx * block_size + block_offset
|
|
777
|
+
valid_slots = slots_kv[valid_mask_kv].to(paged_memory_device)
|
|
778
|
+
block_indices = valid_slots // block_size
|
|
779
|
+
block_offsets = valid_slots % block_size
|
|
780
|
+
|
|
781
|
+
# 3. Prepare LMCache views for K and V
|
|
782
|
+
if token_major:
|
|
783
|
+
# Layout: [num_tokens, 2, hidden_size]
|
|
784
|
+
lmc_k = lmc_key_value_cache[:, 0, :]
|
|
785
|
+
lmc_v = lmc_key_value_cache[:, 1, :]
|
|
786
|
+
else:
|
|
787
|
+
# Layout: [2, num_tokens, hidden_size]
|
|
788
|
+
lmc_k = lmc_key_value_cache[0, :, :]
|
|
789
|
+
lmc_v = lmc_key_value_cache[1, :, :]
|
|
790
|
+
|
|
791
|
+
# 4. Perform the transfer
|
|
792
|
+
if direction == TransferDirection.H2D:
|
|
793
|
+
# --- Direction: LMCache to SGLang (Paged Buffer) ---
|
|
794
|
+
# Reshape LMC flat tensors to match SGL [num_heads, head_size]
|
|
795
|
+
src_k_reshaped = (
|
|
796
|
+
lmc_k[valid_mask_kv]
|
|
797
|
+
.reshape(-1, num_heads, head_size)
|
|
798
|
+
.to(paged_memory_device)
|
|
799
|
+
)
|
|
800
|
+
src_v_reshaped = (
|
|
801
|
+
lmc_v[valid_mask_kv]
|
|
802
|
+
.reshape(-1, num_heads, head_size)
|
|
803
|
+
.to(paged_memory_device)
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
# Advanced indexing: update specific slots in the paged cache
|
|
807
|
+
sgl_key_cache[block_indices, block_offsets] = src_k_reshaped
|
|
808
|
+
sgl_value_cache[block_indices, block_offsets] = src_v_reshaped
|
|
809
|
+
|
|
810
|
+
else:
|
|
811
|
+
# --- Direction: SGLang (Paged Buffer) to LMCache ---
|
|
812
|
+
# Gather tensors from paged cache based on mapping
|
|
813
|
+
sampled_k = sgl_key_cache[block_indices, block_offsets].to(kv_device)
|
|
814
|
+
sampled_v = sgl_value_cache[block_indices, block_offsets].to(kv_device)
|
|
815
|
+
|
|
816
|
+
# Flatten the head dimensions and copy into LMC tensors
|
|
817
|
+
lmc_k[valid_mask_kv] = sampled_k.reshape(-1, num_heads * head_size)
|
|
818
|
+
lmc_v[valid_mask_kv] = sampled_v.reshape(-1, num_heads * head_size)
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
def load_and_reshape_flash(
|
|
822
|
+
key_value: torch.Tensor,
|
|
823
|
+
# Destination (Dst): Pinned CPU Tensor [2, L, T, H]
|
|
824
|
+
key_cache: torch.Tensor,
|
|
825
|
+
# Source (Src): GPU Cache [Blocks, BlockSize, NumHeads, HeadSize]
|
|
826
|
+
value_cache: torch.Tensor, # Source (Src): GPU Cache
|
|
827
|
+
slot_mapping: torch.Tensor, # Mapping indices [num_tokens]
|
|
828
|
+
layer_idx: int,
|
|
829
|
+
):
|
|
830
|
+
"""
|
|
831
|
+
Python equivalent of load_and_reshape_flash.
|
|
832
|
+
Note: In the context of 'test_extract_and_load_back', this function performs
|
|
833
|
+
an EXTRACT operation (Reads from GPU Cache and writes to Pinned CPU memory).
|
|
834
|
+
"""
|
|
835
|
+
# 1. Prepare indices on the target device
|
|
836
|
+
# Mapping must be on the same GPU as the cache to perform indexing
|
|
837
|
+
device = key_cache.device
|
|
838
|
+
slot_mapping = slot_mapping.to(device=device, dtype=torch.long)
|
|
839
|
+
|
|
840
|
+
block_size = key_cache.size(1)
|
|
841
|
+
|
|
842
|
+
# Calculate physical locations within the paged cache
|
|
843
|
+
block_indices = torch.div(slot_mapping, block_size, rounding_mode="floor")
|
|
844
|
+
block_offsets = slot_mapping % block_size
|
|
845
|
+
|
|
846
|
+
# 2. Extract data from Cache (Gather operation)
|
|
847
|
+
# The result k_out/v_out will be on the GPU
|
|
848
|
+
# Shape: [num_tokens, num_heads, head_size]
|
|
849
|
+
k_out = key_cache[block_indices, block_offsets]
|
|
850
|
+
v_out = value_cache[block_indices, block_offsets]
|
|
851
|
+
|
|
852
|
+
# 3. Write to the destination tensor (CPU Copy)
|
|
853
|
+
# Target shape: [2, num_layers, num_tokens, hidden_dim]
|
|
854
|
+
|
|
855
|
+
# Flatten heads into the hidden dimension: [T, NumHeads, HeadSize] -> [T, HiddenDim]
|
|
856
|
+
hidden_dim = k_out.shape[1] * k_out.shape[2]
|
|
857
|
+
|
|
858
|
+
# Assignment automatically handles the Device-to-Host (D2H) transfer
|
|
859
|
+
key_value[0, layer_idx] = k_out.view(-1, hidden_dim)
|
|
860
|
+
key_value[1, layer_idx] = v_out.view(-1, hidden_dim)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def reshape_and_cache_back_flash(
|
|
864
|
+
key_value: torch.Tensor,
|
|
865
|
+
# Source: [2, num_layer, num_tokens, num_heads * head_size]
|
|
866
|
+
# (Can be on CPU/Pinned Memory or GPU)
|
|
867
|
+
key_cache: torch.Tensor,
|
|
868
|
+
# Destination: [num_blocks, block_size, num_heads, head_size]
|
|
869
|
+
# (Must be on GPU)
|
|
870
|
+
value_cache: torch.Tensor, # Destination: (Must be on GPU)
|
|
871
|
+
slot_mapping: torch.Tensor, # Indices: [num_tokens]
|
|
872
|
+
layer_idx: int,
|
|
873
|
+
):
|
|
874
|
+
"""
|
|
875
|
+
Python implementation of reshape_and_cache_back_flash.
|
|
876
|
+
|
|
877
|
+
Operation:
|
|
878
|
+
Flat Tensor (Source) -> Paged Attention Cache (Destination)
|
|
879
|
+
|
|
880
|
+
Logic:
|
|
881
|
+
1. Extract the specific layer's data from key_value.
|
|
882
|
+
2. Move it to the GPU (if it's on CPU).
|
|
883
|
+
3. Reshape it to match the cache's head structure.
|
|
884
|
+
4. Scatter (write) it into the non-contiguous cache blocks using slot_mapping.
|
|
885
|
+
"""
|
|
886
|
+
|
|
887
|
+
# 1. Setup Device & Dimensions
|
|
888
|
+
# The cache is on the GPU, so all indices and source data must eventually be there.
|
|
889
|
+
device = key_cache.device
|
|
890
|
+
|
|
891
|
+
block_size = key_cache.size(1)
|
|
892
|
+
num_heads = key_cache.size(2)
|
|
893
|
+
head_size = key_cache.size(3)
|
|
894
|
+
|
|
895
|
+
# 2. Prepare Indices
|
|
896
|
+
# slot_mapping might be on CPU, must move to GPU for indexing.
|
|
897
|
+
slot_mapping = slot_mapping.to(device=device, dtype=torch.long)
|
|
898
|
+
|
|
899
|
+
# Calculate physical block indices and offsets
|
|
900
|
+
block_indices = torch.div(slot_mapping, block_size, rounding_mode="floor")
|
|
901
|
+
block_offsets = slot_mapping % block_size
|
|
902
|
+
|
|
903
|
+
# 3. Process Source Data (Key)
|
|
904
|
+
# Step A: Slice the specific layer from the source tensor
|
|
905
|
+
# Source shape: [2, num_layers, num_tokens, hidden_dim] -> [num_tokens, hidden_dim]
|
|
906
|
+
k_src_flat = key_value[0, layer_idx]
|
|
907
|
+
v_src_flat = key_value[1, layer_idx]
|
|
908
|
+
|
|
909
|
+
# Step B: Reshape & Move to GPU
|
|
910
|
+
# .to(device) handles the CPU -> GPU transfer if key_value is in pinned memory.
|
|
911
|
+
# View shape: [num_tokens, num_heads, head_size]
|
|
912
|
+
k_src = k_src_flat.to(device).view(-1, num_heads, head_size)
|
|
913
|
+
v_src = v_src_flat.to(device).view(-1, num_heads, head_size)
|
|
914
|
+
|
|
915
|
+
# 4. Write to Cache (Scatter)
|
|
916
|
+
# Using Advanced Indexing to write data into specific blocks/offsets
|
|
917
|
+
key_cache[block_indices, block_offsets] = k_src
|
|
918
|
+
value_cache[block_indices, block_offsets] = v_src
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def lmcache_memcpy_async(
|
|
922
|
+
dest: int | torch.Tensor,
|
|
923
|
+
src: int | torch.Tensor,
|
|
924
|
+
nbytes: int,
|
|
925
|
+
direction: TransferDirection,
|
|
926
|
+
host_buffer_offset: int,
|
|
927
|
+
host_buffer_alignments: int,
|
|
928
|
+
):
|
|
929
|
+
"""
|
|
930
|
+
Python fallback for lmcache_memcpy_async.
|
|
931
|
+
|
|
932
|
+
- Tensor mode (non-CUDA devices like HPU): uses .to(device) + copy_()
|
|
933
|
+
- Pointer mode with libcudart: uses synchronous cudaMemcpy (cudaMemcpyDefault)
|
|
934
|
+
- Pointer mode without libcudart: uses CPU tensor copy
|
|
935
|
+
|
|
936
|
+
Unlike the C++ version (which uses cudaMemcpyAsync and must split copies
|
|
937
|
+
at cudaHostRegister boundaries), this Python fallback does NOT need
|
|
938
|
+
alignment-based chunking because:
|
|
939
|
+
- cudaMemcpy (synchronous) handles cross-cudaHostRegister boundaries
|
|
940
|
+
internally via staging buffers
|
|
941
|
+
- CPU tensor copy has no alignment constraints
|
|
942
|
+
- Tensor mode bypasses raw pointers entirely
|
|
943
|
+
|
|
944
|
+
dest:
|
|
945
|
+
- If int: raw memory pointer (used for CUDA/CPU devices where we
|
|
946
|
+
work with pointers).
|
|
947
|
+
- If torch.Tensor: tensor object (used for non-CUDA/CPU devices
|
|
948
|
+
where we operate on tensor objects directly).
|
|
949
|
+
|
|
950
|
+
src:
|
|
951
|
+
- If int: raw memory pointer (used for CUDA/CPU devices where we
|
|
952
|
+
work with pointers).
|
|
953
|
+
- If torch.Tensor: tensor object (used for non-CUDA/CPU devices
|
|
954
|
+
where we operate on tensor objects directly).
|
|
955
|
+
"""
|
|
956
|
+
# 1. Power of two check (kept for API compatibility)
|
|
957
|
+
if host_buffer_alignments <= 0 or (
|
|
958
|
+
host_buffer_alignments & (host_buffer_alignments - 1) != 0
|
|
959
|
+
):
|
|
960
|
+
raise ValueError("host_buffer_alignments must be power of two")
|
|
961
|
+
|
|
962
|
+
# 2. Validate direction
|
|
963
|
+
if direction not in (TransferDirection.H2D, TransferDirection.D2H):
|
|
964
|
+
raise ValueError(f"Unsupported direction: {direction}")
|
|
965
|
+
|
|
966
|
+
# 3. Tensor-backed mode.
|
|
967
|
+
# Mixed pointer/tensor are not allowed
|
|
968
|
+
if isinstance(dest, torch.Tensor) or isinstance(src, torch.Tensor):
|
|
969
|
+
if not (isinstance(dest, torch.Tensor) and isinstance(src, torch.Tensor)):
|
|
970
|
+
raise TypeError(
|
|
971
|
+
"Mixed types are not allowed: both dest and src must be torch.Tensor "
|
|
972
|
+
"if either of them is a tensor."
|
|
973
|
+
)
|
|
974
|
+
if nbytes % dest.element_size() != 0:
|
|
975
|
+
raise ValueError("nbytes must align with tensor element size")
|
|
976
|
+
|
|
977
|
+
num_elements = nbytes // dest.element_size()
|
|
978
|
+
|
|
979
|
+
dest_slice = dest.flatten()[:num_elements]
|
|
980
|
+
src_slice = src.flatten()[:num_elements]
|
|
981
|
+
|
|
982
|
+
copied = src_slice.to(dest_slice.device)
|
|
983
|
+
dest_slice.copy_(copied)
|
|
984
|
+
return
|
|
985
|
+
|
|
986
|
+
# 4. Pointer mode
|
|
987
|
+
if not isinstance(dest, int) or not isinstance(src, int):
|
|
988
|
+
raise TypeError(
|
|
989
|
+
"dest and src must be both int (pointer mode) "
|
|
990
|
+
"or both torch.Tensor (tensor mode)"
|
|
991
|
+
)
|
|
992
|
+
|
|
993
|
+
libcudart = _get_copy_lib()
|
|
994
|
+
if libcudart is not None and hasattr(libcudart, "cudaMemcpy"):
|
|
995
|
+
try:
|
|
996
|
+
# Synchronous cudaMemcpy handles cross-cudaHostRegister boundaries
|
|
997
|
+
# internally — no manual alignment splitting needed.
|
|
998
|
+
ret = libcudart.cudaMemcpy(
|
|
999
|
+
ctypes.c_void_p(dest),
|
|
1000
|
+
ctypes.c_void_p(src),
|
|
1001
|
+
ctypes.c_size_t(nbytes),
|
|
1002
|
+
ctypes.c_int(4), # cudaMemcpyDefault
|
|
1003
|
+
)
|
|
1004
|
+
if ret != 0:
|
|
1005
|
+
raise RuntimeError(f"cudaMemcpy failed with error code {ret}")
|
|
1006
|
+
except AttributeError:
|
|
1007
|
+
raise
|
|
1008
|
+
else:
|
|
1009
|
+
# Pure CPU copy — no alignment constraints.
|
|
1010
|
+
_copy_bytes_with_tensor(dest, src, nbytes)
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
@njit(cache=True)
|
|
1014
|
+
def _encode_single_channel(
|
|
1015
|
+
cdf_layer_c, # np.uint32 [lp]
|
|
1016
|
+
sym_channel, # np.uint8 [n_tokens]
|
|
1017
|
+
out_buf_lc, # np.uint8 [buffer_size]
|
|
1018
|
+
):
|
|
1019
|
+
"""Core arithmetic encoding for a single (layer, channel).
|
|
1020
|
+
Returns number of bytes written."""
|
|
1021
|
+
MASK32 = 0xFFFFFFFF
|
|
1022
|
+
precision = 16
|
|
1023
|
+
max_symbol = len(cdf_layer_c) - 2
|
|
1024
|
+
n_tokens = len(sym_channel)
|
|
1025
|
+
|
|
1026
|
+
low, high = 0, MASK32
|
|
1027
|
+
pending_bits = 0
|
|
1028
|
+
output_reg, output_reg_len = 0, 0
|
|
1029
|
+
ptr = 0
|
|
1030
|
+
buf_size = len(out_buf_lc)
|
|
1031
|
+
|
|
1032
|
+
# Inline flush_bit to avoid closure (numba does not support nonlocal)
|
|
1033
|
+
for token_idx in range(n_tokens):
|
|
1034
|
+
sym = int(sym_channel[token_idx])
|
|
1035
|
+
c_low = int(cdf_layer_c[sym])
|
|
1036
|
+
c_high = 0x10000 if sym == max_symbol else int(cdf_layer_c[sym + 1])
|
|
1037
|
+
|
|
1038
|
+
span = (high - low + 1) & MASK32
|
|
1039
|
+
if span == 0:
|
|
1040
|
+
span = 0x100000000
|
|
1041
|
+
|
|
1042
|
+
high = (low + ((span * c_high) >> precision) - 1) & MASK32
|
|
1043
|
+
low = (low + ((span * c_low) >> precision)) & MASK32
|
|
1044
|
+
|
|
1045
|
+
while True:
|
|
1046
|
+
if (high & 0x80000000) == (low & 0x80000000):
|
|
1047
|
+
# flush_bit(bit)
|
|
1048
|
+
bit = (high >> 31) & 1
|
|
1049
|
+
output_reg = (output_reg << 1) | bit
|
|
1050
|
+
output_reg_len += 1
|
|
1051
|
+
if output_reg_len == 8:
|
|
1052
|
+
if ptr < buf_size:
|
|
1053
|
+
out_buf_lc[ptr] = output_reg & 0xFF
|
|
1054
|
+
ptr += 1
|
|
1055
|
+
output_reg, output_reg_len = 0, 0
|
|
1056
|
+
# flush pending bits
|
|
1057
|
+
for _ in range(pending_bits):
|
|
1058
|
+
output_reg = (output_reg << 1) | (1 - bit)
|
|
1059
|
+
output_reg_len += 1
|
|
1060
|
+
if output_reg_len == 8:
|
|
1061
|
+
if ptr < buf_size:
|
|
1062
|
+
out_buf_lc[ptr] = output_reg & 0xFF
|
|
1063
|
+
ptr += 1
|
|
1064
|
+
output_reg, output_reg_len = 0, 0
|
|
1065
|
+
pending_bits = 0
|
|
1066
|
+
low = (low << 1) & MASK32
|
|
1067
|
+
high = ((high << 1) | 1) & MASK32
|
|
1068
|
+
elif (low & 0x40000000) != 0 and (high & 0x40000000) == 0:
|
|
1069
|
+
pending_bits += 1
|
|
1070
|
+
low = (low << 1) & 0x7FFFFFFF
|
|
1071
|
+
high = ((high << 1) | 0x80000001) & MASK32
|
|
1072
|
+
else:
|
|
1073
|
+
break
|
|
1074
|
+
|
|
1075
|
+
# Final flushing sequence
|
|
1076
|
+
pending_bits += 1
|
|
1077
|
+
bit = 1 if (low & 0x40000000) != 0 else 0
|
|
1078
|
+
output_reg = (output_reg << 1) | bit
|
|
1079
|
+
output_reg_len += 1
|
|
1080
|
+
if output_reg_len == 8:
|
|
1081
|
+
if ptr < buf_size:
|
|
1082
|
+
out_buf_lc[ptr] = output_reg & 0xFF
|
|
1083
|
+
ptr += 1
|
|
1084
|
+
output_reg, output_reg_len = 0, 0
|
|
1085
|
+
for _ in range(pending_bits):
|
|
1086
|
+
output_reg = (output_reg << 1) | (1 - bit)
|
|
1087
|
+
output_reg_len += 1
|
|
1088
|
+
if output_reg_len == 8:
|
|
1089
|
+
if ptr < buf_size:
|
|
1090
|
+
out_buf_lc[ptr] = output_reg & 0xFF
|
|
1091
|
+
ptr += 1
|
|
1092
|
+
output_reg, output_reg_len = 0, 0
|
|
1093
|
+
pending_bits = 0 # noqa: F841
|
|
1094
|
+
|
|
1095
|
+
if output_reg_len > 0:
|
|
1096
|
+
if ptr < buf_size:
|
|
1097
|
+
out_buf_lc[ptr] = (output_reg << (8 - output_reg_len)) & 0xFF
|
|
1098
|
+
ptr += 1
|
|
1099
|
+
|
|
1100
|
+
return ptr
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
def encode_fast_new(cdf, input_sym, output_buffer, output_lengths):
|
|
1104
|
+
"""
|
|
1105
|
+
Python equivalent of C++ Arithmetic Encoder.
|
|
1106
|
+
Strictly emulates 32-bit unsigned overflow for high/low.
|
|
1107
|
+
"""
|
|
1108
|
+
cdf_np = cdf.cpu().numpy().view(np.uint16).astype(np.uint32)
|
|
1109
|
+
sym_np = input_sym.cpu().numpy().astype(np.uint8)
|
|
1110
|
+
|
|
1111
|
+
n_layers, n_tokens, n_channels = sym_np.shape
|
|
1112
|
+
out_buf_np = np.zeros(output_buffer.shape, dtype=np.uint8)
|
|
1113
|
+
out_len_np = np.zeros(output_lengths.shape, dtype=np.int32)
|
|
1114
|
+
|
|
1115
|
+
def encode_one(args):
|
|
1116
|
+
layer_idx, c = args
|
|
1117
|
+
length = _encode_single_channel(
|
|
1118
|
+
cdf_np[layer_idx, c],
|
|
1119
|
+
sym_np[layer_idx, :, c],
|
|
1120
|
+
out_buf_np[layer_idx, c],
|
|
1121
|
+
)
|
|
1122
|
+
out_len_np[layer_idx, c] = length
|
|
1123
|
+
|
|
1124
|
+
tasks = [(layer_idx, c) for layer_idx in range(n_layers) for c in range(n_channels)]
|
|
1125
|
+
|
|
1126
|
+
with ThreadPoolExecutor() as executor:
|
|
1127
|
+
list(executor.map(encode_one, tasks))
|
|
1128
|
+
|
|
1129
|
+
output_buffer.copy_(torch.from_numpy(out_buf_np))
|
|
1130
|
+
output_lengths.copy_(torch.from_numpy(out_len_np))
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
@njit(cache=True)
|
|
1134
|
+
def _decode_single_channel(
|
|
1135
|
+
cdf_layer_c,
|
|
1136
|
+
bs_np,
|
|
1137
|
+
start_off,
|
|
1138
|
+
end_off,
|
|
1139
|
+
n_tokens,
|
|
1140
|
+
out_layer_c,
|
|
1141
|
+
):
|
|
1142
|
+
MASK32 = 0xFFFFFFFF
|
|
1143
|
+
precision = 16
|
|
1144
|
+
max_symbol = len(cdf_layer_c) - 2
|
|
1145
|
+
|
|
1146
|
+
v_val = 0
|
|
1147
|
+
if start_off + 4 <= len(bs_np):
|
|
1148
|
+
v_val = (
|
|
1149
|
+
(int(bs_np[start_off]) << 24)
|
|
1150
|
+
| (int(bs_np[start_off + 1]) << 16)
|
|
1151
|
+
| (int(bs_np[start_off + 2]) << 8)
|
|
1152
|
+
| int(bs_np[start_off + 3])
|
|
1153
|
+
) & MASK32
|
|
1154
|
+
|
|
1155
|
+
low, high = 0, MASK32
|
|
1156
|
+
byte_buffer_offset = start_off + 4
|
|
1157
|
+
bit_idx = 1
|
|
1158
|
+
byte_buffer = int(bs_np[byte_buffer_offset]) if byte_buffer_offset < end_off else 0
|
|
1159
|
+
|
|
1160
|
+
for i in range(n_tokens):
|
|
1161
|
+
span = (high - low + 1) & MASK32
|
|
1162
|
+
if span == 0:
|
|
1163
|
+
span = 0x100000000
|
|
1164
|
+
|
|
1165
|
+
v_minus_l = (v_val - low) & MASK32
|
|
1166
|
+
count = ((v_minus_l + 1) * 0x10000 - 1) // span
|
|
1167
|
+
count = count & 0xFFFF
|
|
1168
|
+
|
|
1169
|
+
left = 0
|
|
1170
|
+
right = max_symbol + 1
|
|
1171
|
+
while left + 1 < right:
|
|
1172
|
+
m = (left + right) // 2
|
|
1173
|
+
if int(cdf_layer_c[m]) < count:
|
|
1174
|
+
left = m
|
|
1175
|
+
elif int(cdf_layer_c[m]) > count:
|
|
1176
|
+
right = m
|
|
1177
|
+
else:
|
|
1178
|
+
left = m
|
|
1179
|
+
break
|
|
1180
|
+
|
|
1181
|
+
out_layer_c[i] = left
|
|
1182
|
+
|
|
1183
|
+
if i == n_tokens - 1:
|
|
1184
|
+
break
|
|
1185
|
+
|
|
1186
|
+
sym_i = left
|
|
1187
|
+
c_low = int(cdf_layer_c[sym_i])
|
|
1188
|
+
c_high = 0x10000 if sym_i == max_symbol else int(cdf_layer_c[sym_i + 1])
|
|
1189
|
+
|
|
1190
|
+
high = (low + ((span * c_high) >> precision) - 1) & MASK32
|
|
1191
|
+
low = (low + ((span * c_low) >> precision)) & MASK32
|
|
1192
|
+
|
|
1193
|
+
while True:
|
|
1194
|
+
if low >= 0x80000000 or high < 0x80000000:
|
|
1195
|
+
v_val = ((v_val << 1) | ((byte_buffer >> (8 - bit_idx)) & 1)) & MASK32
|
|
1196
|
+
low = (low << 1) & MASK32
|
|
1197
|
+
high = ((high << 1) | 1) & MASK32
|
|
1198
|
+
bit_idx += 1
|
|
1199
|
+
elif low >= 0x40000000 and high < 0xC0000000:
|
|
1200
|
+
v_val = (v_val - 0x40000000) & MASK32
|
|
1201
|
+
v_val = ((v_val << 1) | ((byte_buffer >> (8 - bit_idx)) & 1)) & MASK32
|
|
1202
|
+
low = (low << 1) & 0x7FFFFFFF
|
|
1203
|
+
high = ((high << 1) | 0x80000001) & MASK32
|
|
1204
|
+
bit_idx += 1
|
|
1205
|
+
else:
|
|
1206
|
+
break
|
|
1207
|
+
|
|
1208
|
+
if bit_idx == 9:
|
|
1209
|
+
bit_idx = 1
|
|
1210
|
+
byte_buffer_offset += 1
|
|
1211
|
+
byte_buffer = (
|
|
1212
|
+
int(bs_np[byte_buffer_offset])
|
|
1213
|
+
if byte_buffer_offset < end_off
|
|
1214
|
+
else 0
|
|
1215
|
+
)
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
# Standard
|
|
1219
|
+
|
|
1220
|
+
|
|
1221
|
+
def decode_fast_new(cdf, bytestreams, lengths, output):
|
|
1222
|
+
"""
|
|
1223
|
+
Python implementation of Arithmetic Decoding.
|
|
1224
|
+
Strictly aligned with CUDA decode_with_accessor_kernel.
|
|
1225
|
+
bytestreams shape: [nlayers, nchannels, buffer_size]
|
|
1226
|
+
"""
|
|
1227
|
+
cdf_np = cdf.cpu().numpy().view(np.uint16).astype(np.uint32)
|
|
1228
|
+
bs_np = bytestreams.cpu().numpy().astype(np.uint8)
|
|
1229
|
+
len_np = lengths.cpu().numpy().astype(np.int32)
|
|
1230
|
+
|
|
1231
|
+
n_layers, n_tokens, n_channels = output.shape
|
|
1232
|
+
out_np = np.zeros(output.shape, dtype=np.uint8)
|
|
1233
|
+
|
|
1234
|
+
def decode_one(args):
|
|
1235
|
+
layer_idx, c = args
|
|
1236
|
+
curr_len = int(len_np[layer_idx, c])
|
|
1237
|
+
# For decode_fast_new, each channel has its own contiguous buffer,
|
|
1238
|
+
# so start_off=0 and end_off=curr_len within channel_bs
|
|
1239
|
+
channel_bs = bs_np[layer_idx, c] # shape [buffer_size]
|
|
1240
|
+
_decode_single_channel(
|
|
1241
|
+
cdf_np[layer_idx, c],
|
|
1242
|
+
channel_bs,
|
|
1243
|
+
0,
|
|
1244
|
+
curr_len,
|
|
1245
|
+
n_tokens,
|
|
1246
|
+
out_np[layer_idx, :, c],
|
|
1247
|
+
)
|
|
1248
|
+
|
|
1249
|
+
tasks = [(layer_idx, c) for layer_idx in range(n_layers) for c in range(n_channels)]
|
|
1250
|
+
|
|
1251
|
+
with ThreadPoolExecutor() as executor:
|
|
1252
|
+
list(executor.map(decode_one, tasks))
|
|
1253
|
+
|
|
1254
|
+
if output is not None:
|
|
1255
|
+
output.copy_(torch.from_numpy(out_np))
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
def decode_fast_prefsum(cdf, bytestreams, lengths_prefsum, output):
|
|
1259
|
+
"""
|
|
1260
|
+
Python equivalent of C++ decode_fast_prefsum.
|
|
1261
|
+
bytestreams shape: [total_bytes] (1D, all channels packed)
|
|
1262
|
+
"""
|
|
1263
|
+
cdf_np = cdf.cpu().numpy().view(np.uint16).astype(np.uint32)
|
|
1264
|
+
pref_np = lengths_prefsum.cpu().numpy().astype(np.int64).flatten()
|
|
1265
|
+
|
|
1266
|
+
# WA: CUDA kernel reads out-of-bound in two ways:
|
|
1267
|
+
# 1. max(prefsum) may equal len(bytestreams) (off-by-one on exclusive-end)
|
|
1268
|
+
# 2. v_val init reads 4 bytes starting at start_off, may exceed bytestreams
|
|
1269
|
+
# Pad with zeros to make all reads safe.
|
|
1270
|
+
max_prefsum = int(pref_np.max())
|
|
1271
|
+
pad_size = max(0, max_prefsum + 4 - bytestreams.shape[0])
|
|
1272
|
+
if pad_size > 0:
|
|
1273
|
+
bytestreams = torch.nn.functional.pad(bytestreams, (0, pad_size), value=0)
|
|
1274
|
+
|
|
1275
|
+
bs_np = bytestreams.cpu().numpy().astype(np.uint8) # must be after padding
|
|
1276
|
+
|
|
1277
|
+
n_layers, n_tokens, n_channels = output.shape
|
|
1278
|
+
out_np = np.zeros(output.shape, dtype=np.uint8)
|
|
1279
|
+
|
|
1280
|
+
def decode_one(args):
|
|
1281
|
+
layer_idx, c = args
|
|
1282
|
+
cid = layer_idx * n_channels + c
|
|
1283
|
+
start_off = 0 if cid == 0 else int(pref_np[cid - 1])
|
|
1284
|
+
end_off = int(pref_np[cid])
|
|
1285
|
+
_decode_single_channel(
|
|
1286
|
+
cdf_np[layer_idx, c],
|
|
1287
|
+
bs_np,
|
|
1288
|
+
start_off,
|
|
1289
|
+
end_off,
|
|
1290
|
+
n_tokens,
|
|
1291
|
+
out_np[layer_idx, :, c],
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1294
|
+
tasks = [(layer_idx, c) for layer_idx in range(n_layers) for c in range(n_channels)]
|
|
1295
|
+
|
|
1296
|
+
with ThreadPoolExecutor() as executor:
|
|
1297
|
+
list(executor.map(decode_one, tasks))
|
|
1298
|
+
|
|
1299
|
+
output.copy_(torch.from_numpy(out_np))
|
|
1300
|
+
|
|
1301
|
+
|
|
1302
|
+
def calculate_cdf(input_tensor: torch.Tensor, num_bins: int) -> torch.Tensor:
|
|
1303
|
+
"""Equivalent to CUDA calculate_cdf.
|
|
1304
|
+
|
|
1305
|
+
Calculates the CDF across tokens for each (layer, channel) pair.
|
|
1306
|
+
|
|
1307
|
+
Args:
|
|
1308
|
+
input_tensor: 3D tensor with shape [nlayers, ntokens, nchannels].
|
|
1309
|
+
num_bins: Maximum number of bins (i.e., Lp - 1).
|
|
1310
|
+
|
|
1311
|
+
Returns:
|
|
1312
|
+
int16 tensor with shape [nlayers, nchannels, num_bins + 1]
|
|
1313
|
+
containing normalized CDF values.
|
|
1314
|
+
"""
|
|
1315
|
+
nlayers, ntokens, nchannels = input_tensor.shape
|
|
1316
|
+
device = input_tensor.device
|
|
1317
|
+
|
|
1318
|
+
# Compute per-(layer, channel) histogram via scatter_add.
|
|
1319
|
+
# Permute to [nlayers, nchannels, ntokens] then flatten first two dims.
|
|
1320
|
+
input_perm = input_tensor.permute(0, 2, 1).reshape(-1, ntokens).long()
|
|
1321
|
+
src = torch.ones_like(input_perm)
|
|
1322
|
+
counts = torch.zeros(nlayers * nchannels, num_bins, dtype=torch.long, device=device)
|
|
1323
|
+
counts.scatter_add_(1, input_perm.clamp(0, num_bins - 1), src)
|
|
1324
|
+
counts = counts.reshape(nlayers, nchannels, num_bins)
|
|
1325
|
+
|
|
1326
|
+
# Build CDF: cdf[..., 0] = 0, cdf[..., i] = sum(counts[..., 0:i])
|
|
1327
|
+
cdf = torch.zeros(nlayers, nchannels, num_bins + 1, dtype=torch.long, device=device)
|
|
1328
|
+
cdf[:, :, 1:] = torch.cumsum(counts, dim=2)
|
|
1329
|
+
|
|
1330
|
+
# Total count per (layer, channel)
|
|
1331
|
+
total = cdf[:, :, -1:] # [nlayers, nchannels, 1]
|
|
1332
|
+
|
|
1333
|
+
# Normalize: (0xFFFF - num_bins) * cdf / total + bin_index
|
|
1334
|
+
max_uint16_value = 0xFFFF - num_bins
|
|
1335
|
+
bin_offsets = torch.arange(num_bins + 1, dtype=torch.long, device=device)
|
|
1336
|
+
|
|
1337
|
+
safe_total = total.clamp(min=1)
|
|
1338
|
+
normalized = (max_uint16_value * cdf) // safe_total + bin_offsets
|
|
1339
|
+
|
|
1340
|
+
# Where total is 0, use just the bin offsets
|
|
1341
|
+
normalized = torch.where(
|
|
1342
|
+
total > 0, normalized, bin_offsets.unsqueeze(0).unsqueeze(0)
|
|
1343
|
+
)
|
|
1344
|
+
|
|
1345
|
+
return normalized.to(torch.int16)
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
def rotary_embedding_k_fused(
|
|
1349
|
+
old_positions: torch.Tensor,
|
|
1350
|
+
new_positions: torch.Tensor,
|
|
1351
|
+
key: torch.Tensor,
|
|
1352
|
+
head_size: int,
|
|
1353
|
+
cos_sin_cache: torch.Tensor,
|
|
1354
|
+
is_neox: bool,
|
|
1355
|
+
) -> None:
|
|
1356
|
+
"""Apply fused rotary embedding undo/redo to key tensor in-place.
|
|
1357
|
+
|
|
1358
|
+
Reverses the rotary embedding at old_positions and applies the rotary
|
|
1359
|
+
embedding at new_positions. head_size is unused but kept for API
|
|
1360
|
+
compatibility with the CUDA equivalent.
|
|
1361
|
+
|
|
1362
|
+
Args:
|
|
1363
|
+
old_positions: Token positions whose rotary embedding to reverse.
|
|
1364
|
+
new_positions: Token positions whose rotary embedding to apply.
|
|
1365
|
+
key: Key tensor to update in-place.
|
|
1366
|
+
head_size: Head size (unused; kept for API compatibility).
|
|
1367
|
+
cos_sin_cache: Precomputed cosine/sine cache indexed by position.
|
|
1368
|
+
is_neox: If True, uses NeoX-style rotary (contiguous halves);
|
|
1369
|
+
otherwise uses GPT-J-style (interleaved).
|
|
1370
|
+
"""
|
|
1371
|
+
rot_dim = cos_sin_cache.shape[1]
|
|
1372
|
+
half_rot = rot_dim // 2
|
|
1373
|
+
|
|
1374
|
+
old_cs = cos_sin_cache[old_positions]
|
|
1375
|
+
new_cs = cos_sin_cache[new_positions]
|
|
1376
|
+
|
|
1377
|
+
oc, os = old_cs[:, :half_rot].unsqueeze(1), old_cs[:, half_rot:].unsqueeze(1)
|
|
1378
|
+
nc, ns = new_cs[:, :half_rot].unsqueeze(1), new_cs[:, half_rot:].unsqueeze(1)
|
|
1379
|
+
|
|
1380
|
+
if is_neox:
|
|
1381
|
+
x = key[..., :half_rot]
|
|
1382
|
+
y = key[..., half_rot:rot_dim]
|
|
1383
|
+
else:
|
|
1384
|
+
x = key[..., :rot_dim:2]
|
|
1385
|
+
y = key[..., 1:rot_dim:2]
|
|
1386
|
+
|
|
1387
|
+
x_rev = x * oc + y * os
|
|
1388
|
+
y_rev = y * oc - x * os
|
|
1389
|
+
|
|
1390
|
+
x_out = x_rev * nc - y_rev * ns
|
|
1391
|
+
y_out = y_rev * nc + x_rev * ns
|
|
1392
|
+
|
|
1393
|
+
if is_neox:
|
|
1394
|
+
key[..., :half_rot] = x_out
|
|
1395
|
+
key[..., half_rot:rot_dim] = y_out
|
|
1396
|
+
else:
|
|
1397
|
+
key[..., :rot_dim:2] = x_out
|
|
1398
|
+
key[..., 1:rot_dim:2] = y_out
|
|
1399
|
+
|
|
1400
|
+
|
|
1401
|
+
def get_gpu_pci_bus_id(device_id: int = 0) -> str | None:
|
|
1402
|
+
"""
|
|
1403
|
+
Get the PCI bus ID via CUDA/ROCm runtime.
|
|
1404
|
+
Other backends return None.
|
|
1405
|
+
|
|
1406
|
+
Args:
|
|
1407
|
+
device_id (int): CUDA/ROCm device index.
|
|
1408
|
+
|
|
1409
|
+
Returns:
|
|
1410
|
+
str | None: PCI bus ID (e.g., "0000:29:00.0") or None if unavailable.
|
|
1411
|
+
"""
|
|
1412
|
+
try:
|
|
1413
|
+
if torch.cuda.is_available() and device_id < torch.cuda.device_count():
|
|
1414
|
+
props = torch.cuda.get_device_properties(device_id)
|
|
1415
|
+
# PCI function number is always 0 for GPUs
|
|
1416
|
+
bus_id = (
|
|
1417
|
+
f"{props.pci_domain_id:04x}:{props.pci_bus_id:02x}:"
|
|
1418
|
+
f"{props.pci_device_id:02x}.0"
|
|
1419
|
+
)
|
|
1420
|
+
return bus_id.upper()
|
|
1421
|
+
except Exception:
|
|
1422
|
+
pass
|
|
1423
|
+
|
|
1424
|
+
return None
|