-
Notifications
You must be signed in to change notification settings - Fork 272
mimo v2.5 support #1351
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
mimo v2.5 support #1351
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| #include "rtp_llm/cpp/cache/HybridKVCacheAllocator.h" | ||
|
|
||
| #include <algorithm> | ||
| #include <cstdint> | ||
| #include <unordered_map> | ||
| #include <unordered_set> | ||
|
|
||
|
|
@@ -56,6 +57,32 @@ BlockIndicesType validBlocksAfter(const BlockIndicesType& blocks, size_t begin) | |
| return valid; | ||
| } | ||
|
|
||
| // Return the first canonical cache-key block needed by the SWA window when | ||
| // `end` is the last reused block. Cache keys are in canonical units under CP, | ||
| // so one key covers cp_scale raw blocks. A zero window keeps the historical | ||
| // single-tail behavior used by generic SWA groups that do not declare a | ||
| // prefix-reuse window; MiMo supplies the explicit window in its descriptor. | ||
| int swaMatchBegin(const KVCacheGroup& group, int end, int cp_scale) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] SWA 多块前缀复用(swaMatchBegin + CP 槽位映射)无任何单测覆盖 新逻辑含两处易错算术: 建议: 在 |
||
| const auto window_tokens = group.policy().prefix_reuse_window_tokens; | ||
| if (window_tokens == 0) { | ||
| return end; | ||
| } | ||
| if (window_tokens == 1) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P3] swaMatchBegin 中 window_tokens==1 的特例分支与通用公式完全等价
建议: 删除 Checklist: [6.1] DRY:重复非平凡逻辑被抽取或显式复用 |
||
| return end + 1; | ||
| } | ||
|
|
||
| const int block_tokens = std::max(1, group.seqSizePerBlock() * std::max(cp_scale, 1)); | ||
| const int64_t first_new_token = static_cast<int64_t>(end + 1) * block_tokens; | ||
| const int64_t history_tokens = static_cast<int64_t>(window_tokens - 1); | ||
| const int64_t history_start = std::max<int64_t>(0, first_new_token - history_tokens); | ||
| return std::max(0, static_cast<int>(history_start / block_tokens)); | ||
| } | ||
|
|
||
| struct SwaMatch { | ||
| int begin = 0; | ||
| BlockIndicesType blocks; | ||
| }; | ||
|
|
||
| } // namespace | ||
|
|
||
| bool HybridKVCacheAllocator::skipReuseCacheGroup(int gid) const { | ||
|
|
@@ -102,14 +129,14 @@ int HybridKVCacheAllocator::reuseCache(const CacheKeysType& cach | |
| full_matched_blocks[static_cast<size_t>(gid)] = std::move(match_result.block_indices); | ||
| } | ||
|
|
||
| int pos = min_full_reuse_blocks - 1; | ||
| std::vector<BlockIdxType> linear_tail_blocks(linear_group_ids_.size(), NULL_BLOCK_IDX); | ||
| std::vector<BlockIndicesType> swa_tail_blocks(swa_group_ids_.size()); | ||
| const bool has_tail_groups = !linear_group_ids_.empty() || !swa_group_ids_.empty(); | ||
| int pos = min_full_reuse_blocks - 1; | ||
| std::vector<BlockIdxType> linear_tail_blocks(linear_group_ids_.size(), NULL_BLOCK_IDX); | ||
| std::vector<SwaMatch> swa_tail_matches(swa_group_ids_.size()); | ||
| const bool has_tail_groups = !linear_group_ids_.empty() || !swa_group_ids_.empty(); | ||
| for (; pos >= 0 && has_tail_groups; --pos) { | ||
| bool all_tail_groups_matched = true; | ||
| std::vector<BlockIdxType> candidate_linear_tail_blocks(linear_group_ids_.size(), NULL_BLOCK_IDX); | ||
| std::vector<BlockIndicesType> candidate_swa_tail_blocks(swa_group_ids_.size()); | ||
| bool all_tail_groups_matched = true; | ||
| std::vector<BlockIdxType> candidate_linear_tail_blocks(linear_group_ids_.size(), NULL_BLOCK_IDX); | ||
| std::vector<SwaMatch> candidate_swa_tail_matches(swa_group_ids_.size()); | ||
| for (size_t i = 0; i < linear_group_ids_.size(); ++i) { | ||
| const int gid = linear_group_ids_[i]; | ||
| auto result = | ||
|
|
@@ -128,17 +155,28 @@ int HybridKVCacheAllocator::reuseCache(const CacheKeysType& cach | |
| if (skipReuseCacheGroup(gid)) { | ||
| continue; | ||
| } | ||
| auto result = | ||
| kv_cache_groups_[static_cast<size_t>(gid)]->matchSingleKey(cache_keys[static_cast<size_t>(pos)]); | ||
| if (result.block_indices.empty()) { | ||
| all_tail_groups_matched = false; | ||
| const auto& group = *kv_cache_groups_[static_cast<size_t>(gid)]; | ||
| const int begin = swaMatchBegin(group, pos, cp_scale); | ||
| auto& match = candidate_swa_tail_matches[i]; | ||
| match.begin = begin; | ||
| if (begin <= pos) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P3] 复用回退循环对同一 cache key 重复 matchSingleKey,最坏开销随声明窗口线性放大 外层 建议: 把已查询的 Checklist: [6.1] 边界 case 覆盖(空、单元素、最大值) |
||
| match.blocks.reserve(static_cast<size_t>(pos - begin + 1)); | ||
| for (int key_pos = begin; key_pos <= pos; ++key_pos) { | ||
| auto result = group.matchSingleKey(cache_keys[static_cast<size_t>(key_pos)]); | ||
| if (result.block_indices.empty()) { | ||
| all_tail_groups_matched = false; | ||
| break; | ||
| } | ||
| match.blocks.push_back(result.block_indices[0]); | ||
| } | ||
| } | ||
| if (!all_tail_groups_matched) { | ||
| break; | ||
| } | ||
| candidate_swa_tail_blocks[i].push_back(result.block_indices[0]); | ||
| } | ||
| if (all_tail_groups_matched) { | ||
| linear_tail_blocks = std::move(candidate_linear_tail_blocks); | ||
| swa_tail_blocks = std::move(candidate_swa_tail_blocks); | ||
| swa_tail_matches = std::move(candidate_swa_tail_matches); | ||
| break; | ||
| } | ||
| } | ||
|
|
@@ -176,10 +214,17 @@ int HybridKVCacheAllocator::reuseCache(const CacheKeysType& cach | |
| if (skipReuseCacheGroup(gid)) { | ||
| continue; | ||
| } | ||
| const size_t tail_begin = | ||
| static_cast<size_t>(std::max(group_reuse_len - static_cast<int>(swa_tail_blocks[i].size()), 0)); | ||
| for (size_t j = 0; j < swa_tail_blocks[i].size(); ++j) { | ||
| kv_resource.mutableBlockIds(0, gid).setAt(tail_begin + j, swa_tail_blocks[i][j]); | ||
| const auto& match = swa_tail_matches[i]; | ||
| for (size_t j = 0; j < match.blocks.size(); ++j) { | ||
| const int canonical_pos = match.begin + static_cast<int>(j); | ||
| // Compact-last-rank SWA uses one slot per canonical key. The | ||
| // non-compact layout keeps cp_size logical slots per key, and the | ||
| // canonical key owns the last slot in that group. | ||
| const int logical_pos = | ||
| cpCompactSwaGroup(gid, cp_mapper) ? canonical_pos : (canonical_pos + 1) * cp_scale - 1; | ||
| if (logical_pos >= 0 && logical_pos < group_reuse_len) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P3] SWA 复用的越界写入被静默丢弃,窗口全失配又会清零 FULL 复用且无日志或指标 两处:(1) 建议: 把 :225 的静默边界改为显式不变量断言( |
||
| kv_resource.mutableBlockIds(0, gid).setAt(static_cast<size_t>(logical_pos), match.blocks[j]); | ||
| } | ||
| } | ||
| } | ||
| return reuse_blocks_len; | ||
|
|
@@ -259,19 +304,15 @@ MallocResult HybridKVCacheAllocator::initMallocForCommonLen(const MallocInfo& ma | |
| original_sizes[static_cast<size_t>(gid)] = kv_resource->blocksNum(0, gid); | ||
| } | ||
| for (int gid = 0; gid < kv_resource->groupNums(); ++gid) { | ||
| auto& block_ids_0 = kv_resource->mutableBlockIds(0, gid); | ||
| const int group_seq_len = cpEffectiveSeqLenForGroup(cp_mapper, config_, gid, common_seq_len); | ||
| const auto& group = kv_cache_groups_[static_cast<size_t>(gid)]; | ||
| auto& block_ids_0 = kv_resource->mutableBlockIds(0, gid); | ||
| const int group_seq_len = cpEffectiveSeqLenForGroup(cp_mapper, config_, gid, common_seq_len); | ||
| const auto& group = kv_cache_groups_[static_cast<size_t>(gid)]; | ||
| // Snapshot the slot count before the call so a failure can report this | ||
| // group's exact physical request in the error_code=602 record. | ||
| const int blocks_before = static_cast<int>(block_ids_0.blocksNum()); | ||
| if (!group->malloc(block_ids_0, group_seq_len, malloc_info.reuse_cache, 0)) { | ||
| logMallocFailure(malloc_info, | ||
| "init_group_malloc", | ||
| 0, | ||
| gid, | ||
| false, | ||
| group->needBlocksNum(group_seq_len, blocks_before, 0)); | ||
| logMallocFailure( | ||
| malloc_info, "init_group_malloc", 0, gid, false, group->needBlocksNum(group_seq_len, blocks_before, 0)); | ||
| rollbackInitMalloc(*kv_resource, referenced_blocks, original_sizes); | ||
| return {false, 0}; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,10 +70,16 @@ void validateHybridPoolDescs(const ModelConfig& model_config, uint32_t kernel_to | |
| } | ||
| } | ||
|
|
||
| uint32_t mhaLocalKvHeadNum(const ModelConfig& model_config, const ParallelismConfig& parallelism_config) { | ||
| uint32_t mhaLocalKvHeadNum(const KVCacheSpecDesc& desc, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] local KV head 计算公式在 spec 与 pool config 两处重复实现,仅靠注释维持一致
建议: 收敛为单一入口,例如在 |
||
| const ModelConfig& model_config, | ||
| const ParallelismConfig& parallelism_config) { | ||
| const auto attn_tp = std::max<int64_t>(1, parallelism_config.get_attn_tp_size()); | ||
| const uint32_t tp = static_cast<uint32_t>(attn_tp); | ||
| const uint32_t kv = static_cast<uint32_t>(model_config.attn_config.kv_head_num); | ||
| // Must match MHAKVCacheSpec::build(): a per-desc override describes a layer kind whose | ||
| // KV head count differs from the model-wide one, and the group's local head count has | ||
| // to agree with the spec that sized its blocks. | ||
| const uint32_t kv = desc.kv_head_num_override != 0 ? desc.kv_head_num_override : | ||
| static_cast<uint32_t>(model_config.attn_config.kv_head_num); | ||
| RTP_LLM_CHECK_WITH_INFO(kv > 0, "local kv head num requires positive kv_head_num"); | ||
| return (kv % tp == 0) ? kv / tp : kv / std::gcd(kv, tp); | ||
| } | ||
|
|
@@ -98,7 +104,7 @@ uint32_t localKvHeadNumForDesc(const KVCacheSpecDesc& desc, | |
| const ParallelismConfig& parallelism_config) { | ||
| switch (desc.cache_type) { | ||
| case KVCacheSpecType::MultiHeadAttention: | ||
| return mhaLocalKvHeadNum(model_config, parallelism_config); | ||
| return mhaLocalKvHeadNum(desc, model_config, parallelism_config); | ||
| case KVCacheSpecType::LinearAttention: | ||
| return linearLocalKvHeadNum(model_config, parallelism_config); | ||
| case KVCacheSpecType::MultiHeadLatentAttention: | ||
|
|
@@ -239,7 +245,14 @@ void setupIndependentPoolSizes(CacheConfig& config, bool is_mtp) { | |
| group_kv_block_stride_bytes[gid] = kv_stride; | ||
| group_kv_scale_stride_bytes[gid] = scale_stride; | ||
| const auto type = config.typeForGroup(gid); | ||
| const bool is_paged_group = type == CacheGroupType::FULL || type == CacheGroupType::LINEAR; | ||
| // A sliding-window group counts too when it is an ordinary paged pool: it draws | ||
| // its block count from the same global budget (finalizeBlockNums gives it | ||
| // global_block_num / linear_step), so its bytes belong in the per-block cost. DSv4 | ||
| // instead backs its window with a fixed-allocation state cache, which is sized | ||
| // outside the paged budget and must stay excluded. | ||
| const bool is_state_cache = spec->type == KVCacheSpecType::OpaqueState; | ||
| const bool is_paged_group = type == CacheGroupType::FULL || type == CacheGroupType::LINEAR | ||
| || (type == CacheGroupType::SWA && !is_state_cache); | ||
| if (is_paged_group && !config.usesExplicitIndependentBlocks(gid)) { | ||
| total_kv_block_bytes += static_cast<size_t>(layer_count) * kv_stride; | ||
| total_scale_block_bytes += static_cast<size_t>(layer_count) * scale_stride; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📍 实际位置 [P2] SWA 组开启前缀复用后保留全长 KV,窗口收益消失且与注释、离线估算假设均矛盾
建议: 先确定取舍再统一两侧口径:若要保留窗口收益,为该 desc 提供独立于全局 Checklist: [6.1] 回滚路径:风险行为存在运维回滚手段 |
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2] 混合注意力 KV 显存估算有三处计量口径问题
(1)
ga_layers = sum(1 for t in pattern if t != SLIDING_WINDOW)(:278)把 LINEAR 层按全长 KV 计费,而仓库已有权威分类器rtp_llm/models/hybrid_kv_cache.py:22-25按== LINEAR三分;qwen3_next / kimi_linear 均置enable_hybrid_attention=True会命中该分支(数值与旧公式恒等故非回归,但 docstring 声称按层型精确计量并未对 LINEAR 兑现)。(2) 窗口层读swa_config.swa_kv_head_num(:285),全局层却读attn_config.kv_head_num(:295),而语义对应的ga_kv_head_num未被使用——build_layer_attn_configs:141等运行时路径用的正是后者,两者分叉时估算会静默偏离。(3)pattern为空时ga_layers=swa_layers=0,静默返回 0 且无日志。建议: 复用
hybrid_kv_cache.py的三分口径显式区分 LINEAR / SLIDING_WINDOW / 全局(LINEAR 计 0 或按linear_attention_config状态尺寸计费;若本次不修请在 docstring 写明已知偏差);GA 分支改用swa_config.ga_kv_head_num or self.attn_config.kv_head_num,或在_parse_swa_config中断言二者相等;并与apply_layer_num_override保持一致校验len(pattern) == self.num_layers,不满足时抛ValueError或退回同构公式并打 warning,不要静默产出 0。