Skip to content

Commit 09d2b01

Browse files
authored
[optimizer] add LiteHit: single-pass multi-capacity LRU prefix hit-rate analyzer (#242)
* [optimizer] add LiteHit multi-capacity LRU prefix hit-rate analyzer core * [optimizer] move OnlineOptimizerManager under manager/online_runtime * [optimizer] switch online TraceQuery to LiteHit token-based prefix hit rate * [optimizer] add LiteHit offline runner with streaming trace loader and lite_hit_main * [docs] document LiteHit algorithm and refresh optimizer architecture * [optimizer] commit LiteHit requests tail-to-head to match leaf-first eviction * [optimizer] add LiteHit hit-curve facts primitives Introduce the capacity-independent building blocks of the LiteHit facts refactor: HitCurveSegment/RequestFact with a stateless HitCurveProjector, shared request preprocessing (authoritative input length validation plus a rolling Jenkins prefix hash bit-compatible with the Python trace producer), and the facts CSV row codec and post-hoc capacity query used by offline replay. * [optimizer] rework LiteHit to capacity-independent facts * [optimizer] move enable_prefix_hash from instance to instance group * [optimizer] support LiteHit trace re-blocking and multi-instance fanout * [optimizer] clean up online trace query response semantics Remove redundant slot-0 scalars from TraceQueryResult, unify the -1 sentinel for disabled theoretical max cache across TraceQuery and ListInstances, rename avg_bytes_per_block to bytes_per_block, and report full-attention kv_cache_usage_bytes under infinite-capacity semantics. * [docs] consolidate LiteHit docs into the README Document why request commit is hit/miss-agnostic (writes also touch) and the read/write-separation path to decode support, retire REQUIREMENTS.md in favor of the README, and index LiteHit in the optimizer README. * [optimizer] address LiteHit PR review feedback - keep TraceQueryResponse wire numbers (capacity_results=3, theoretical_result=4) and add input_token_len as new tag 5 - reject duplicate instance_id in offline lane setup so fanout cannot route one request twice into the same lane - guard trace block_size==0 on the programmatic-setter path (modulo divisor in re-blocking check) - distinguish I/O errors from EOF (in.bad()) in trace loader and facts query instead of silently treating truncated input as complete - docs: rewrite the stale LiteHit section in optimizer_architecture.md (drop nonexistent assume_time_sorted and the old single-CSV schema, describe the two-step facts flow) and state that write trace events are recognized and ignored by the facts replay
1 parent b75bdc4 commit 09d2b01

45 files changed

Lines changed: 3670 additions & 223 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/design/module_architecture.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
|---|---|---|
1717
| **KVCache Manager** | `kv_cache_manager/` | 核心系统:全局 KVCache 元数据管理服务,以及配套的客户端 SDK 与推理框架连接器。本文档的主体。 |
1818
| **HiSim** | `hisim/` | 独立的 LLM 推理仿真系统,通过回放 trace 预测 TTFT/TPOT/吞吐等指标,不依赖 Manager 运行时。 |
19-
| **Optimizer** | `kv_cache_manager/optimizer/` | 缓存仿真与优化:回放 KVCache 访问 trace,模拟命中率与容量消耗,指导逐出策略与容量参数调优。在线化能力正在开发中,后续会与现有 optimizer 合并|
19+
| **Optimizer** | `kv_cache_manager/optimizer/` | 缓存仿真与优化:既支持离线回放 KVCache 访问 trace,也提供在线 TraceQuery 服务。完整 optimizer 用于策略/容量分析;LiteHit 用最小状态精确计算多容量 full-attention LRU 命中率|
2020

2121
KVCache Manager 采用中心化部署,负责 KVCache 的全局元数据管理(查询、写入、容量管理),推理引擎通过 Client/Connector 接入。
2222

@@ -97,7 +97,7 @@ service → manager → meta → config → data_storage → common
9797
### 3.3 客户端与 Optimizer
9898

9999
- **client** 是独立的对外分支,仅共享 `common``config``data_storage``protocol` 以及 `service/util:manager_message_proto_util`**py_connector** 通过 pybind 位于 client 之上。核心服务端不依赖 client。运行时,元数据面经 gRPC(C++ `MetaClient`)或 HTTP(py_connector 的 Python `KvCacheManagerClient`)调用 KVCM 服务,数据面经 C++ `TransferClient` 直接读写存储后端——这几条链路是理解端到端流程的关键(见第 4 节)。
100-
- **optimizer** 负责 KVCache 访问 trace 的仿真与优化(命中率/容量分析、逐出与容量参数调优)。目前通过 `meta:cache_location` 类型与 `event` 的 optimizer 事件与核心关联;在线化能力正在开发中,后续会与现有 optimizer 合并
100+
- **optimizer** 负责 KVCache 访问 trace 的仿真与优化(命中率/容量分析、逐出与容量参数调优),并通过独立 online runtime/service 提供实时 TraceQuery。full-attention LRU 的在线多容量统计复用 LiteHit;它通过 `meta:cache_location` 类型与 `event` 的 optimizer 事件与核心关联。
101101

102102
### 3.4 模块关系图
103103

kv_cache_manager/optimizer/BUILD

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,38 @@ cc_binary(
1313
"//kv_cache_manager/common:logger"
1414
],
1515
)
16-
cc_library(
17-
name = "config",
18-
hdrs = [
19-
"advanced_cache_reclaimer.h",
20-
"advanced_data_storage_selector.h",
21-
],
22-
srcs = [
2316

17+
cc_binary(
18+
name = "lite_hit_main",
19+
srcs = [
20+
"lite_hit_main.cc",
21+
],
22+
deps = [
23+
"//kv_cache_manager/optimizer/manager:lite_hit_offline_runner",
24+
"//kv_cache_manager/optimizer/config:optimizer_lite_hit_config",
25+
"//kv_cache_manager/common:logger",
2426
],
2527
)
2628

27-
cc_library(
28-
name = "online_optimizer_manager",
29+
cc_binary(
30+
name = "lite_hit_facts_query_main",
2931
srcs = [
30-
"online_runtime/online_optimizer_manager.cc",
32+
"lite_hit_facts_query_main.cc",
3133
],
34+
deps = [
35+
"//kv_cache_manager/optimizer/liteHit:facts_query",
36+
"//kv_cache_manager/common:logger",
37+
],
38+
)
39+
40+
cc_library(
41+
name = "config",
3242
hdrs = [
33-
"online_runtime/online_optimizer_manager.h",
43+
"advanced_cache_reclaimer.h",
44+
"advanced_data_storage_selector.h",
3445
],
35-
deps = [
36-
"//kv_cache_manager/common",
37-
"//kv_cache_manager/optimizer/config:online_optimizer_config",
38-
"//kv_cache_manager/optimizer/config:optimizer_registry_manager",
39-
"//kv_cache_manager/optimizer/index:cache_indexer",
40-
"//kv_cache_manager/optimizer/index:cache_indexer_factory",
41-
"//kv_cache_manager/optimizer/index:ttl_cache_indexer_wrapper",
46+
srcs = [
47+
4248
],
4349
)
4450

@@ -65,7 +71,7 @@ cc_library(
6571
"service/metrics/optimizer_metrics_reporter.h",
6672
],
6773
deps = [
68-
":online_optimizer_manager",
74+
"//kv_cache_manager/optimizer/manager:online_optimizer_manager",
6975
":optimizer_metrics_collector",
7076
"//kv_cache_manager/common",
7177
"//kv_cache_manager/common:common_util",
@@ -100,7 +106,7 @@ cc_library(
100106
"service/optimizer_service_impl.h",
101107
],
102108
deps = [
103-
":online_optimizer_manager",
109+
"//kv_cache_manager/optimizer/manager:online_optimizer_manager",
104110
":optimizer_call_guard",
105111
":optimizer_metrics_collector",
106112
":optimizer_metrics_reporter",
@@ -181,7 +187,7 @@ cc_library(
181187
"-fcoroutines",
182188
],
183189
deps = [
184-
":online_optimizer_manager",
190+
"//kv_cache_manager/optimizer/manager:online_optimizer_manager",
185191
":online_optimizer_server_config",
186192
":optimizer_metrics_reporter",
187193
":optimizer_service_grpc",

kv_cache_manager/optimizer/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ KVCacheManager Optimizer 是一个独立的缓存优化分析模块,通过回
99
- 多种驱逐策略的模拟和对比
1010
- 缓存命中率的实时统计和分析
1111
- Radix Tree 索引结构的可视化
12+
- LiteHit:full-attention 场景的容量无关命中率分析(一次回放产出事实,任意 LRU 容量事后投影,支持重分块与多 block size fanout),见 [liteHit/README.md](liteHit/README.md)
1213

1314
## 动机
1415

@@ -31,7 +32,7 @@ KVCacheManager Optimizer 是一个独立的缓存优化分析模块,通过回
3132
- **灵活配置**:通过 JSON 配置文件灵活配置实例、存储和策略
3233
- **可视化分析**:支持 Radix Tree 可视化和命中率图表生成
3334

34-
标准策略配置、multi-instance replay、trace schema 和命中率口径见 [docs/strategy_config.md](docs/strategy_config.md)。标准版中 `HitRate` 统一表示整体 token hit rate,即 `HitTokens / InputTokens`;local/remote 只作为 trace `block_mask` 与 optimizer 模拟命中的诊断拆分,不作为标准结论维度。传入 optimizer config 的 Python 入口统一使用配置中的 `output_result_path``multi_instance_replay` 不读取完整 config,使用显式 `--output-dir`。标准 `get` trace 必须包含 `input_len`;外部只有请求级日志时可使用 `type=request`,optimizer 会按 `trace_replay.write_delay_ns` 在内部调度 delayed write;已经拆分好的 `get` / `write` trace 仍然支持。
35+
标准策略配置、multi-instance replay、trace schema 和命中率口径见 [docs/strategy_config.md](docs/strategy_config.md)。标准版中 `HitRate` 统一表示整体 token hit rate,即 `HitTokens / InputTokens`;local/remote 只作为 trace `block_mask` 与 optimizer 模拟命中的诊断拆分,不作为标准结论维度。传入 optimizer config 的 Python 入口统一使用配置中的 `output_result_path``multi_instance_replay` 不读取完整 config,使用显式 `--output-dir`。标准 `get` trace 必须包含 `input_len`;外部只有请求级日志时可使用 `type=request`,optimizer 会按 `trace_replay.write_delay_ns` 在内部调度 delayed write;已经拆分好的 `get` / `write` trace 仍然支持(仅限 replay 路径;LiteHit facts 回放会识别并忽略 `write` 事件,`get` 提交即视为写回完成)
3536

3637
### 架构设计
3738

kv_cache_manager/optimizer/config/BUILD

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,21 @@ cc_library(
4545
],
4646
)
4747

48+
cc_library(
49+
name = "optimizer_lite_hit_config",
50+
srcs = [
51+
"optimizer_lite_hit_config.cc",
52+
],
53+
hdrs = [
54+
"optimizer_lite_hit_config.h",
55+
],
56+
deps = [
57+
":online_optimizer_config",
58+
"//kv_cache_manager/common",
59+
"//kv_cache_manager/common:jsonizable",
60+
],
61+
)
62+
4863
cc_library(
4964
name = "config",
5065
deps = [

kv_cache_manager/optimizer/config/optimizer_instance_group.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ bool OptimizerInstanceGroup::FromRapidValue(const rapidjson::Value &rapid_value)
1919
KVCM_JSON_GET_DEFAULT_MACRO(rapid_value, "shared_group_quota", shared_group_quota_, false);
2020
KVCM_JSON_GET_DEFAULT_MACRO(rapid_value, "enable_theoretical_max_cache", enable_theoretical_max_cache_, false);
2121
KVCM_JSON_GET_DEFAULT_MACRO(rapid_value, "ttl_seconds", ttl_seconds_, int64_t(0));
22+
KVCM_JSON_GET_DEFAULT_MACRO(rapid_value, "enable_prefix_hash", enable_prefix_hash_, false);
2223
SortCapacities();
2324
return true;
2425
}
@@ -32,6 +33,7 @@ void OptimizerInstanceGroup::ToRapidWriter(rapidjson::Writer<rapidjson::StringBu
3233
Put(writer, "shared_group_quota", shared_group_quota_);
3334
Put(writer, "enable_theoretical_max_cache", enable_theoretical_max_cache_);
3435
Put(writer, "ttl_seconds", ttl_seconds_);
36+
Put(writer, "enable_prefix_hash", enable_prefix_hash_);
3537
}
3638

3739
bool OptimizerInstanceGroup::ValidateRequiredFields(std::string &invalid_fields) const {

kv_cache_manager/optimizer/config/optimizer_instance_group.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ class OptimizerInstanceGroup : public Jsonizable {
2525
bool shared_group_quota() const { return shared_group_quota_; }
2626
bool enable_theoretical_max_cache() const { return enable_theoretical_max_cache_; }
2727
int64_t ttl_seconds() const { return ttl_seconds_; }
28+
// When true, incoming block keys of every instance in this group are
29+
// per-block raw hashes and are converted to rolling prefix-chained keys
30+
// before analysis. Key shape follows the model deployment, so it lives at
31+
// group granularity for both online and offline.
32+
bool enable_prefix_hash() const { return enable_prefix_hash_; }
2833

2934
void set_name(const std::string &v) { name_ = v; }
3035
void set_capacity_gb(const std::vector<double> &v) {
@@ -35,6 +40,7 @@ class OptimizerInstanceGroup : public Jsonizable {
3540
void set_shared_group_quota(bool v) { shared_group_quota_ = v; }
3641
void set_enable_theoretical_max_cache(bool v) { enable_theoretical_max_cache_ = v; }
3742
void set_ttl_seconds(int64_t v) { ttl_seconds_ = v; }
43+
void set_enable_prefix_hash(bool v) { enable_prefix_hash_ = v; }
3844

3945
private:
4046
void SortCapacities();
@@ -45,6 +51,7 @@ class OptimizerInstanceGroup : public Jsonizable {
4551
bool shared_group_quota_ = false;
4652
bool enable_theoretical_max_cache_ = false;
4753
int64_t ttl_seconds_ = 0;
54+
bool enable_prefix_hash_ = false;
4855
};
4956

5057
} // namespace kv_cache_manager
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#include "kv_cache_manager/optimizer/config/optimizer_lite_hit_config.h"
2+
3+
#include "kv_cache_manager/common/logger.h"
4+
5+
namespace kv_cache_manager {
6+
7+
bool OptimizerLiteHitConfig::FromRapidValue(const rapidjson::Value &rapid_value) {
8+
KVCM_JSON_GET_MACRO(rapid_value, "trace_file_path", trace_file_path_);
9+
KVCM_JSON_GET_MACRO(rapid_value, "output_result_path", output_result_path_);
10+
KVCM_JSON_GET_MACRO(rapid_value, "instance_groups", instance_groups_);
11+
KVCM_JSON_GET_MACRO(rapid_value, "instances", instances_);
12+
KVCM_JSON_GET_DEFAULT_MACRO(rapid_value, "override_instance_id", override_instance_id_, std::string());
13+
KVCM_JSON_GET_DEFAULT_MACRO(rapid_value, "block_size", block_size_, uint64_t(256));
14+
KVCM_JSON_GET_DEFAULT_MACRO(rapid_value, "fanout_all_instances", fanout_all_instances_, false);
15+
KVCM_JSON_GET_DEFAULT_MACRO(rapid_value, "pipeline_worker_count", pipeline_worker_count_, int32_t(1));
16+
17+
if (trace_file_path_.empty()) {
18+
KVCM_LOG_ERROR("lite_hit config: trace_file_path must not be empty");
19+
return false;
20+
}
21+
if (output_result_path_.empty()) {
22+
KVCM_LOG_ERROR("lite_hit config: output_result_path must not be empty");
23+
return false;
24+
}
25+
if (instance_groups_.empty()) {
26+
KVCM_LOG_ERROR("lite_hit config: instance_groups must not be empty");
27+
return false;
28+
}
29+
if (instances_.empty()) {
30+
KVCM_LOG_ERROR("lite_hit config: instances must not be empty");
31+
return false;
32+
}
33+
if (block_size_ == 0) {
34+
KVCM_LOG_ERROR("lite_hit config: block_size (trace granularity) must be positive");
35+
return false;
36+
}
37+
if (fanout_all_instances_ && !override_instance_id_.empty()) {
38+
KVCM_LOG_ERROR("lite_hit config: fanout_all_instances and override_instance_id are mutually exclusive");
39+
return false;
40+
}
41+
return true;
42+
}
43+
44+
void OptimizerLiteHitConfig::ToRapidWriter(rapidjson::Writer<rapidjson::StringBuffer> &writer) const noexcept {
45+
Put(writer, "trace_file_path", trace_file_path_);
46+
Put(writer, "output_result_path", output_result_path_);
47+
Put(writer, "instance_groups", instance_groups_);
48+
Put(writer, "instances", instances_);
49+
Put(writer, "override_instance_id", override_instance_id_);
50+
Put(writer, "block_size", block_size_);
51+
Put(writer, "fanout_all_instances", fanout_all_instances_);
52+
Put(writer, "pipeline_worker_count", pipeline_worker_count_);
53+
}
54+
55+
} // namespace kv_cache_manager
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <vector>
5+
6+
#include "kv_cache_manager/common/jsonizable.h"
7+
#include "kv_cache_manager/optimizer/config/optimizer_instance_group.h"
8+
#include "kv_cache_manager/optimizer/config/optimizer_instance_info.h"
9+
10+
namespace kv_cache_manager {
11+
12+
// Configuration for the offline LiteHit entry (lite_hit_main / LiteHitOfflineRunner).
13+
//
14+
// It deliberately reuses the SAME config objects as the online service
15+
// (OptimizerInstanceGroup + OptimizerInstanceInfo): instance registration is
16+
// validated by OnlineOptimizerManager exactly like online. The replay itself
17+
// produces one capacity-independent facts CSV
18+
// (${output_result_path}/litehit_facts.csv); capacities are applied
19+
// afterwards by the facts query tool.
20+
class OptimizerLiteHitConfig : public Jsonizable {
21+
public:
22+
OptimizerLiteHitConfig() = default;
23+
~OptimizerLiteHitConfig() override = default;
24+
25+
bool FromRapidValue(const rapidjson::Value &rapid_value) override;
26+
void ToRapidWriter(rapidjson::Writer<rapidjson::StringBuffer> &writer) const noexcept override;
27+
28+
[[nodiscard]] const std::string &trace_file_path() const { return trace_file_path_; }
29+
// Output DIRECTORY. The replay atomically publishes
30+
// ${output_result_path}/litehit_facts.csv on success.
31+
[[nodiscard]] const std::string &output_result_path() const { return output_result_path_; }
32+
[[nodiscard]] const std::vector<OptimizerInstanceGroup> &instance_groups() const { return instance_groups_; }
33+
[[nodiscard]] const std::vector<OptimizerInstanceInfo> &instances() const { return instances_; }
34+
// When non-empty, every replayed request is attributed to this single
35+
// instance_id (the trace's own instance_id is ignored). Use it to pool a
36+
// whole per-service trace into ONE global cache, e.g. instance_id=pod files
37+
// replayed as a single service-wide cache. The instances() list must define
38+
// exactly this id.
39+
[[nodiscard]] const std::string &override_instance_id() const { return override_instance_id_; }
40+
// Native granularity (tokens per block) the trace keys were produced at.
41+
// Every instance's block_size must be an exact multiple of it; requests
42+
// are re-blocked (coarsening only) per lane during preprocessing.
43+
[[nodiscard]] uint64_t block_size() const { return block_size_; }
44+
// When true, every request is replayed into EVERY configured instance
45+
// (the trace's own instance_id is ignored). Combined with instances of
46+
// different block_size this sweeps several granularities over one trace
47+
// in a single run. Mutually exclusive with override_instance_id.
48+
[[nodiscard]] bool fanout_all_instances() const { return fanout_all_instances_; }
49+
// The only public parallelism knob of the offline pipeline. Values < 1 are
50+
// clamped to 1; queue/window sizes are derived internally.
51+
[[nodiscard]] int32_t pipeline_worker_count() const { return pipeline_worker_count_; }
52+
53+
void set_trace_file_path(const std::string &path) { trace_file_path_ = path; }
54+
void set_output_result_path(const std::string &path) { output_result_path_ = path; }
55+
void set_instance_groups(const std::vector<OptimizerInstanceGroup> &groups) { instance_groups_ = groups; }
56+
void set_instances(const std::vector<OptimizerInstanceInfo> &instances) { instances_ = instances; }
57+
void set_override_instance_id(const std::string &id) { override_instance_id_ = id; }
58+
void set_block_size(uint64_t block_size) { block_size_ = block_size; }
59+
void set_fanout_all_instances(bool fanout) { fanout_all_instances_ = fanout; }
60+
void set_pipeline_worker_count(int32_t count) { pipeline_worker_count_ = count; }
61+
62+
private:
63+
std::string trace_file_path_;
64+
std::string output_result_path_;
65+
std::vector<OptimizerInstanceGroup> instance_groups_;
66+
std::vector<OptimizerInstanceInfo> instances_;
67+
std::string override_instance_id_;
68+
uint64_t block_size_ = 256;
69+
bool fanout_all_instances_ = false;
70+
int32_t pipeline_worker_count_ = 1;
71+
};
72+
73+
} // namespace kv_cache_manager

0 commit comments

Comments
 (0)