Prhub

#32971 [unified-memory] Support MLA-hybrid-Mamba (Kimi-Linear) on the Triton backend

原始 PR 作者 ch-wan 合并时间 2026-07-31 13:10 文件变更 10 提交数 1 评论 1 代码增减 +1163 / -48

执行摘要

统一内存池支持 MLA 混合 Mamba(Kimi-Linear)Triton 后端

PR body 明确指出静态双池的痛点:一个池在空转、另一个池在收缩或 OOM(原文:one side idling while the other retracts or OOMs)。MHA-hybrid 的 unified memory 已合入上游,本 PR 补齐 MLA-hybrid 分支:此前 kv_cache_configurator.py 存在 assert not use_mla_backend,导致 Kimi-Linear 这类 MLA 全注意力 + KDA 线性注意力的模型无法启用 unified memory。作者选择 dense views 路线而非新建物理布局,理由是 MLA 各层行宽统一(kv_lora_rank + qk_rope_head_dim = 576),page-major 信封本身已是合法 dense paged pool,只需索引重编号即可复用上层引用与现有 MLA kernel。

值得精读。这个 PR 的索引抽象设计很有学习价值:把 page-major 信封直接当作 dense paged pool、用 torch.as_strided 构造重叠视图、以及 compaction 只管 virtual→physical 表从而让上层引用永不失效的思路,比引入新的物理布局或搬移数据优雅得多。阅读时建议重点对照三份文件:build_dense_mla_views(视图与寻址公式)、translate_kv_loc_dense(dense 空间约定)、HybridLinearKVPoolfull_loc 路由(写路径接线)。合并前请确认两件事:P1 的 FNUZ dtype 归一化是否已在后续 PR 修复,以及 rebase 后的 CI 状态。

讨论亮点

唯一的 review 评论来自 chatgpt-codex-connector[bot],标记为 P1:

在 gfx94/MI300 上使用 --kv-cache-dtype fp8_e4m3 时,dtype 解析会落到 torch.float8_e4m3fnuz,但 _store_dtype_for 没有把该 dtype 归一化为 uint8,因此新的 MLA spec 会构建 native-FNUZ 视图;而 MLATokenToKVPool 会把自己的 store_dtype 归一化为 uint8。继承的写路径会把输入按 uint8 重新解释再做数值转换进 FNUZ 目标,静默损坏缓存的 KV 值。建议把 torch.float8_e4m3fnuz 纳入归一化。

该评论出现在 unified_memory_pool.py 第 1060 行(assert not use_mla_backend 移除处附近),没有任何作者回复或修复记录,合并时疑似未解决。这是本 PR 最需要后续跟踪的风险点。

实现拆解

  1. 定义 MLA 子池规格与 dense 视图构建python/sglang/srt/mem_cache/layout/page_major.py 新增 mla_entry_bytesbuild_dense_mla_views,利用 MLA 各层统一行宽(kv_cache_dim = kv_lora_rank + qk_rope_head_dim,Kimi-Linear 为 576)把 page-major 信封整体视为合法 dense paged pool,用 torch.as_strided 构造逐层重叠但连续的视图,并对最后一层视图越界做硬校验。unified_memory_pool.py 新增 MLASubPoolSpec(V 是 latent 行前缀,无独立 V 区),UnifiedKVPool 增加 view_tail_pad_bytes 参数只扩展分配、不动 slot/watermark 数学,并修复 page_size > 1 时 sink 下限边界。

  2. 扩展双端分配器multi_ended_allocator.py 新增 kernel_page_multiplier 构造参数与 translate_kv_loc_dense 方法:dense id 为 (t // ps) * (ps * multiplier) + t % ps,multiplier 对 MLA 子池取层数;kernel_page_multiplier == 1 时回退到原 translate_kv_loc。文档化约定:compaction 与 in-flight 写集合必须继续使用物理语义的 translate_kv_loc,dense id 只面向 kernel,从而 compaction 重写 virtual→physical 表时上层引用永不失效。

  3. 写路径路由与接线memory_pool.pyHybridLinearKVPool 的 MLA 分支优先透传 KVWriteLoc.full_loc(unified 场景下即 dense loc),无则回退 locset_mla_kv_buffer / get_mla_kv_buffer_full_translate 且只翻译一次。新增 UnifiedMLATokenToKVPool 完成整页信封粒度的 move_kv_cachekv_cache_configurator.py 移除 assert not use_mla_backend 并转发 kv_lora_rank / qk_rope_head_dimtriton_backend.py 同步调整。Gating 保持保守:unified ⇒ page-major ⇒ full-attention 仅 Triton,linear/Mamba 侧仅 Triton,spec decode / PD disagg / hicache / DCP 仍关闭。

  4. 修复上游 FLA 内核寻址 bugpython/sglang/kernels/ops/attention/fla/chunk_delta_h.pychunk_gated_delta_rule_fwd_h 原先硬编码 linear-attention state 的 slot pitch 为 H · V · K,对 envelope-strided 池会错位寻址,改为 initial_state.stride(0) 并采用 int64 索引。该修复影响所有 KDA × page-major 配置,连续池行为不变。

  5. 测试与端到端验证:新增 test_unified_mla_views.py(CPU 纯 torch:dense 寻址、tail pad、整页 move、跨 compaction 的 dense translate)、test_unified_mla_gpu_parity.py(真实 K3 几何 L=24/D=576,覆盖 Triton fallback 与 TMA JIT 两条 set_mla_kv_buffer 路径以及 page_size 1/64)、test_full_loc_fast_path.py 的 MLA 侧 full_loc 路由测试。B300 单机 8×B300 SXM6、TP1 上,Kimi-Linear-48B-A3B-Instruct(27 层 = 20 KDA + 7 MLA)GSM8K 200 题,unified 与 static 双池同为 0.910,精度完全持平。

文件 模块 状态 重要度
python/sglang/srt/mem_cache/unified_memory_pool.py 统一内存池 modified 8.84
python/sglang/srt/mem_cache/multi_ended_allocator.py 双端分配器 modified 7.79
python/sglang/srt/mem_cache/layout/page_major.py 视图布局 modified 7.44
test/registered/unit/mem_cache/test_unified_mla_views.py 视图测试 added 7.76
test/registered/unit/mem_cache/test_unified_mla_gpu_parity.py GPU 对比 added 7.69
test/registered/unit/mem_cache/test_full_loc_fast_path.py 路由测试 modified 7.1
python/sglang/srt/mem_cache/memory_pool.py 内存池 modified 6.16
python/sglang/srt/layers/attention/triton_backend.py 注意力后端 modified 6.17
python/sglang/srt/mem_cache/kv_cache_configurator.py KV 缓存配置 modified 5.68
python/sglang/kernels/ops/attention/fla/chunk_delta_h.py FLA 内核 modified 4.48

关键符号

build_dense_mla_views translate_kv_loc_dense MLASubPoolSpec.entry_bytes MLASubPoolSpec.kv_cache_dim UnifiedKVPool.__init__ UnifiedMLATokenToKVPool.move_kv_cache KVWriteLoc.full_loc chunk_gated_delta_rule_fwd_h

关键源码片段

python/sglang/srt/mem_cache/unified_memory_pool.py core-logic

核心实现文件:新增 MLASubPoolSpec、UnifiedMLATokenToKVPool、view_tail_pad_bytes 与 sink 下限修复,移除对 MLA 的禁用断言,是整套 unified MLA 支持的枢纽。

# python/sglang/srt/mem_cache/unified_memory_pool.py
@dataclass(frozen=True, kw_only=True)
class MLASubPoolSpec(SubPoolSpec):
    """MLA 形态子池的逐 slot 布局。    每层每个 token 只有一条 latent 行(`kv_lora_rank + qk_rope_head_dim`),
    V 是该行的一个前缀切片,因此没有独立的 V 区域。它不是
    `MHASubPoolSpec` 的子类——MHA 的 K+V 字节运算和 `v_head_dim > 0`
    不变量在这里都不成立。
    """
​
    kv_lora_rank: int
    qk_rope_head_dim: int
    store_dtype: torch.dtype
​
    def __post_init__(self):
        super().__post_init__()
        assert self.kv_lora_rank > 0, f'kv_lora_rank 必须为正;当前为 {self.kv_lora_rank}'
        assert self.qk_rope_head_dim > 0, f'qk_rope_head_dim 必须为正;当前为 {self.qk_rope_head_dim}'
​
    @property
    def kv_cache_dim(self) -> int:
        # Kimi-Linear 为 512 + 64 = 576
        return self.kv_lora_rank + self.qk_rope_head_dim
​
    def entry_bytes(self) -> int:
        return self.layer_num * self.kv_cache_dim * self.store_dtype.itemsize
​
    def get_dtype(self) -> torch.dtype:
        return self.store_dtype
python/sglang/srt/mem_cache/multi_ended_allocator.py core-logic

新增 kernel_page_multiplier 与 translate_kv_loc_dense,是 dense 索引空间的核心定义处;明确划分物理语义(compaction 用)与 dense 语义(kernel 用)。

# python/sglang/srt/mem_cache/multi_ended_allocator.py
def translate_kv_loc_dense(
    self,
    virt_tokens: torch.Tensor,
    *,
    out: Optional[torch.Tensor] = None,
) -> torch.Tensor:
    """把 virtual token id 翻译为 DENSE(kernel 侧)id。    dense(t) = (t // ps) * (ps * kernel_page_multiplier) + t % ps,即
    `translate_kv_loc` 的页步长按 `kernel_page_multiplier`(对 dense-view 的
    MLA 子池等于层数 `layer_num`,见 `build_dense_mla_views`)放大后的版本。    关键约定:compaction、in-flight 写集合等内部机制必须继续使用
    `translate_kv_loc`,dense id 只允许交给 kernel——这样 compaction 重写
    virtual->physical 表时,上层持有的引用永远不失效。    tombstone 钳制把 -1 条目映射到 dense id 0(每层视图都在 page-0 保留
    sink 内)。支持 `out=` 以维持 cuda-graph 缓冲区稳定性。
    """
    if self.kernel_page_multiplier == 1:
        # 非 dense 子池(Mamba / MHA):退回原有物理语义
        return self.translate_kv_loc(virt_tokens, out=out)
​
    if out is not None:
        assert out.dtype == torch.int64, (
            f'translate_kv_loc_dense: out= 必须是 int64(与 v2p 一致),'
            f'当前为 {out.dtype}'
        )
        assert out.shape == virt_tokens.shape, (
            f'translate_kv_loc_dense: out= 形状 {tuple(out.shape)} 必须与 '
            f'virt_tokens 形状 {tuple(virt_tokens.shape)} 一致'
        )
​
    with record_function('MultiEndedAlloc.translate_kv_loc_dense'):
        dense_page_stride = self.page_size * self.kernel_page_multiplier
        if self.page_size == 1:
            # dense = phys * multiplier;tombstone -1 缩放为负值后钳到 0
            if out is not None:
                # canonical 调用方做 in-place `translate(ids, out=ids)`,
                # index_select(out=) 禁止索引 / 输出别名,故走临时张量 + copy_
                tmp = torch.index_select(self.virtual_to_physical, 0, virt_tokens)
                tmp = torch.clamp_min(tmp * dense_page_stride, 0)
                out.copy_(tmp)
                return out
            result = torch.index_select(self.virtual_to_physical, 0, virt_tokens)
            return torch.clamp_min(result * dense_page_stride, 0)
​
        # page_size > 1:先取页号与页内偏移,再按 dense 页步长缩放
        virt_pages = virt_tokens // self.page_size
        offsets = virt_tokens % self.page_size
        if out is not None:
            torch.index_select(self.virtual_to_physical, 0, virt_pages, out=out)
            out.mul_(dense_page_stride)
            out.add_(offsets)
            out.clamp_(min=0) # tombstoned 页:-1 * ps + offset 落在 [-ps, -1]
            return out
        phys_pages = self.virtual_to_physical[virt_pages]
        result = phys_pages * dense_page_stride + offsets
        return torch.clamp_min(result, 0)
python/sglang/srt/mem_cache/layout/page_major.py core-logic

新增 build_dense_mla_views 与 mla_entry_bytes,是整个 dense 视图方案的核心数学:重叠视图构造、统一 block table 与尾填充校验。

# python/sglang/srt/mem_cache/layout/page_major.py
def mla_entry_bytes(*, layer_num: int, kv_cache_dim: int, itemsize: int) -> int:
    """一个 MLA slot 跨所有层占用的字节数(单条 latent 行,无独立 V 区)。"""
    return layer_num * kv_cache_dim * itemsize
​
​
def build_dense_mla_views(
    raw: torch.Tensor,
    *,
    layer_num: int,
    kv_cache_dim: int,
    store_dtype: torch.dtype,
    page_size: int,
    num_pages: int,
    anchor_bytes: int = 0,
) -> List[torch.Tensor]:
    """在 page-major layout 的 `raw` 上构造逐层 DENSE 视图。    page 信封布局为 `[L0_latent * ps | L1_latent * ps | ...]`。因为 MLA 各层
    共享统一的行宽 `kv_cache_dim`,整个信封本身就是一个合法的 dense paged
    pool,只需重编号索引:把逐层偏移 `l * ps * kv_cache_dim` 折进每个视图的
    `storage_offset`,第 l 层视图就是普通的连续 `(num_pages * layer_num * ps,
    1, kv_cache_dim)` 张量,由层无关的 dense id 寻址:        dense(t) = (t // ps) * (ps * layer_num) + t % ps   # t 为物理 token id    这样所有层共享一张 block table(页号 = page * layer_num),要求
    `.view(-1, page_size, kv_cache_dim)` 的 kernel(trtllm / cutlass /
    flashmla)可以直接作用于这些视图。    视图之间互相重叠(视图 l+1 是视图 l 平移 ps 行):安全的前提是第 l 层
    永远只按 dense id 索引,而 dense id 相对视图 l 的起点总是落在第 l 层的
    字节区间内。最后一层视图会超出最后一个 page 信封 `(layer_num - 1) * ps`
    行,因此 `raw` 必须多分配一页信封的尾部填充
    (`UnifiedKVPool.view_tail_pad_bytes`)。
    """
    itemsize = store_dtype.itemsize
    row_bytes = kv_cache_dim * itemsize
    page_bytes = page_size * layer_num * row_bytes
    n_dense = num_pages * layer_num * page_size
    assert anchor_bytes % itemsize == 0
​
    # 越界硬校验:最后一层视图的末尾必须在 raw 容量内,否则报错提示分配 tail pad
    last_view_end = (
        anchor_bytes + (layer_num - 1) * page_size * row_bytes + n_dense * row_bytes
    )
    assert last_view_end <= raw.numel() * raw.itemsize, (
        f'build_dense_mla_views: layer {layer_num - 1} 的视图结束于字节 '
        f'{last_view_end},但 raw 只有 {raw.numel() * raw.itemsize} 字节;'
        f'请通过 view_tail_pad_bytes 预留一页信封({page_bytes} B)'
    )
​
    as_dtype_view = raw.view(store_dtype)
    views = []
    for layer in range(layer_num):
        base_bytes = anchor_bytes + layer * page_size * row_bytes
        assert base_bytes % itemsize == 0
        # torch.as_strided 制造重叠且连续的逐层视图,不改动底层存储
        views.append(
            torch.as_strided(
                as_dtype_view,
                size=(n_dense, 1, kv_cache_dim),
                stride=(kv_cache_dim, kv_cache_dim, 1),
                storage_offset=base_bytes // itemsize,
            )
        )
    return views

评论区精华

FNUZ dtype 未归一化为 uint8 导致 KV 静默损坏(P1) 正确性

Codex bot 在 unified_memory_pool.py 第 1060 行评论:gfx94/MI300 上 --kv-cache-dtype fp8_e4m3 时 dtype 解析为 torch.float8_e4m3fnuz,但 _store_dtype_for 未将其归一化为 uint8;新 MLA spec 构建 native-FNUZ 视图,而 MLATokenToKVPool 会将自己的 store_dtype 归一化为 uint8,继承的写路径会按 uint8 重新解释输入再做数值转换,静默损坏缓存 KV 值。建议把 torch.float8_e4m3fnuz 纳入归一化。

结论:无作者回复与修复记录,合并时该 P1 疑似未解决,建议后续 PR 跟踪修复。 · 待处理

风险与影响

  1. FNUZ dtype 数据损坏(P1,未解决)unified_memory_pool.py_store_dtype_for 未将 torch.float8_e4m3fnuz 归一化为 uint8,与 MLATokenToKVPool 的归一化行为不一致,在 AMD gfx94/MI300 上启用 --kv-cache-dtype fp8_e4m3 且使用 unified MLA 池时会静默损坏 KV 缓存。这是合并时遗留的开放问题。
  2. 核心内存路径变更unified_memory_pool.py / multi_ended_allocator.py / memory_pool.py 是 KV 缓存核心,改动会影响所有启用 unified memory 的部署;其中 sink 下限修复和 view_tail_pad_bytespage_size > 1 的 MHA 路径同样生效,需要回归验证。
  3. 上游内核修复影响面chunk_delta_h.pychunk_gated_delta_rule_fwd_h 的 stride 修改影响所有 KDA × page-major 配置,作者声明连续池行为不变,但缺少专门的回归测试佐证。
  4. 合并状态风险:PR body 自述基于 9f5655340,合入时 main 已前进约 24 个 commit,需要 rebase;CI 的 PR Test 与 PR Test (Extra) 双跑均为 ❌ 失败状态。
  5. 平台覆盖不足:新增 GPU byte-parity 测试只在 B300(CUDA 13.0)验证,未覆盖 AMD 平台,恰是 P1 问题暴露的环境。

用户侧:Kimi-Linear 等 MLA-hybrid-Mamba 模型现在可以配合 --enable-unified-memory + Triton 后端部署,缓解静态双池的空间空转/单侧 OOM 问题,GSM8K 精度与静态基线完全持平(0.910)。系统侧:这是统一内存池在 MLA 方向的第一个落地,gating 仍保守(full-attention 仅 Triton,linear/Mamba 仅 Triton,spec decode / PD disagg / hicache / DCP 关闭),不会影响未开启 unified memory 的默认路径。团队侧:virtual / physical / dense 三坐标系 + 逐层重叠 dense 视图 + kernel_page_multiplier 的抽象为后续开放 paged MLA backends(trtllm_mla、flashinfer prefill、cutedsl_mla、tokenspeed_mla)铺平了道路,是 unified-memory 功能线的重要里程碑。

FNUZ dtype 未归一化(P1 未解决) 核心 KV 缓存路径变更 需 rebase(基于旧 main) CI 双跑失败 上游内核修复缺专项回归

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论