跳转至

CloudMatrix384 LLM Serving

导言

一台加速卡跑不下、几百张卡又容易互相等待时,问题就不再只是“算力够不够”,而是谁负责算、谁负责搬、状态放在哪里、下一步在等什么。本文以知乎学习笔记为线索,回到原始论文逐项核验,用“超级厨房”“专家快递”和“共享图书馆”三个直觉,解释 CloudMatrix384 服务 DeepSeek-R1 的四个关键机制。

证据身份

原始论文是 arXiv 2506.12708v3 技术报告/预印本。文中的系统结构、公式和实验数字来自该版本;它们不是本文复现结果,也不等同于经过同行评审的普适结论。

一句话先懂

如果把大模型推理看成一家爆单餐厅,CloudMatrix384 的思路不是让一位厨师跑得飞快,而是:

  1. 把长提示词的预处理、逐 token 生成、缓存保管拆成不同工位。
  2. 让每个 token 去找自己需要的 MoE 专家,而不是把所有专家复制到每张卡。
  3. 把矩阵计算、向量整理和数据搬运交错起来,少让硬件空等。
  4. 把可复用的 KV Cache 放进共享内存池,下次只计算新来的后缀。

这里的关键不是某一个神奇算子,而是把系统里不同形状的等待,交给不同资源处理

PDC 超级厨房隐喻

原创认知插图:预填负责“备菜”,解码负责“一盘盘出菜”,共享缓存保管 KV 卡片;高速通道让分工不等于割裂。

先把地图摊开

论文中的 CloudMatrix384 包含 384 个 Ascend 910 NPU package192 个鲲鹏 CPU。一个 package 内含两个 die,论文又常以 die 作为通信 rank,因此你会同时看到“160 个 NPU package”和“320 路 EP”这样的数字。

package、die、rank

可以把 package 想成一套双间办公室,die 是其中一间,rank 是参与一次并行协作时的编号。物理封装数和并行 rank 数不是一回事。

系统也不是“所有数据都走同一根线”。论文描述的当前实现仍区分:

  • UB(UnifiedBus):NPU 间及 NPU 到共享内存池的高带宽、低时延通路。
  • RDMA 网络:预填集群向解码集群传完整 KV Cache 时使用。
  • VPC 网络:通用网络路径,也是论文缓存实验中的对照路径。

论文实际性能评估使用其中 256 个 NPU:96 个用于预填,160 个用于解码,并使用 32 个节点的 CPU DRAM。模型是 DeepSeek-R1 671B 的 INT8 版本。所以“CM384 的系统设计”与“256 NPU 的实验切片”也不能混为一谈。

PDC:三间工坊

PDC 是 Prefill/Decode/Caching Disaggregation(预填、解码、缓存解耦)

  • Prefill 一次读完整段提示词,计算首 token,并生成初始 KV Cache,计算更密集。
  • Decode 每轮只生成一个或少数 token,却要不断读取历史 KV Cache,更吃内存带宽与低时延。
  • Caching 用 CPU DRAM 汇聚上下文缓存与模型缓存,为多个 NPU 提供共享容量。

如果强迫同一种实例同时兼顾三者,就像让备菜师一边切一百斤菜,一边每秒端一只盘子,还要自己看守仓库。PDC 允许三类资源按流量分别扩缩。

运行过程

下面是语义伪代码,用来说明对象与完成条件,不是论文公开 API:

def serve_request(request):
    prefix_kv, reuse_len = cache.lookup_longest_prefix(request.tokens)
    prefill = scheduler.choose_prefill(request, reuse_len)
    decode = scheduler.choose_decode(request.output_limit)
    decode_slot = decode.allocate_kv_slot(request.id)

    new_kv, first_token = prefill.run(
        tokens=request.tokens,
        reused_prefix=prefix_kv,
        reuse_len=reuse_len,
    )

    transfer_done = rdma.copy_async(
        source=new_kv,
        destination=decode_slot,
    )
    transfer_done.wait()
    decode.mark_ready(request.id, first_token)

    token = first_token
    while token != request.eos_token and decode.length(request.id) < request.output_limit:
        request.emit(token)
        token = decode.step(request.id)

    request.emit_if_not_eos(token)
    cache.store_context_async(request.tokens, new_kv)
    decode.release_kv_slot(request.id)

论文给出的典型布局是:

  • 预填实例用 16 个 NPU package,也就是 32 个 die/rank,做 EP32。
  • 解码实例用 160 个 NPU package,也就是 320 个 die/rank,做 EP320。
  • 预填完成后,后台线程先在解码侧分配目标 KV 地址,再通过 RDMA 搬一次完整 KV;解码的 MoE 高频通信则尽量留在 UB。

PDC 五视图技术图

原创五视图技术图,语义依据 arXiv 2506.12708v3 §4.1:从逻辑、运行过程、rank 序列、张量数据流和内存生命周期观察 PDC。它解释机制,不声称对应公开源码行;为可读性省略输出上限、故障恢复和异步缓存回填。

论文 Figure 9 的 PDC 架构

论文 Figure 9(逻辑证据):证明作者描述了预填、解码、缓存的分离架构与 KV 传输关系;它本身不证明性能提升。

代价与边界

PDC 没有消灭 KV Cache,而是改变它的所有权和搬运时机。新增代价包括目标地址预留、RDMA 传输、完成通知、容量调度和故障恢复。只有当分池后的利用率收益,大于跨池交接成本时,解耦才值得。

“无状态调度”不是没有状态

论文说调度不必强绑定本地缓存亲和性,是因为共享层级更平坦。请求的 token 序列、KV 地址、完成事件和容量配额仍然是状态;只是状态不再锁死某一台解码实例

EP320:token 去找专家

DeepSeek-R1 的 MoE(Mixture of Experts,专家混合)层不会让每个 token 经过全部专家。门控网络为每个 token 选择 topK=8 个路由专家。CloudMatrix-Infer 把专家铺到 320 个 rank 上,让 token 主动“寄快递”到专家所在处。

困难是:一个 MoE 层通常要经历发路由信息、发 token、做专家 FFN、把结果寄回。小批量解码时,每个包裹很小,通信启动和同步反而可能比计算更显眼。

论文的 LEP(Large-scale Expert Parallelism,大规模专家并行)用两个融合算子解决:

  • FusedDispatch:尽早把 BF16 hidden 量化为 INT8 加 scale,附上来源信息,直接写入目标 rank 的静态缓冲区。
  • FusedCombine:专家算完后按来源信息写回,等待贡献齐全,再反量化并按门控权重求和。

运行过程

def fused_moe(hidden, gate, topology, buffers):
    expert_ids, expert_weights = gate.topk(hidden, k=8)

    expected = buffers.begin_dispatch()
    for batch_slot in range(hidden.rows):
        q_token, scale = quantize_int8(hidden[batch_slot])
        for route_slot in range(8):
            expert_id = expert_ids[batch_slot, route_slot]
            target_rank = topology.owner(expert_id)
            message = {
                "q_token": q_token,
                "scale": scale,
                "source_rank": topology.local_rank,
                "batch_slot": batch_slot,
                "route_slot": route_slot,
            }
            buffers.remote_write_dispatch(target_rank, message)
            expected[target_rank] += 1

    buffers.publish_dispatch_counts(expected)
    buffers.wait_dispatch_ready(expected)
    expert_inputs = buffers.assemble_expert_inputs()
    expert_outputs = run_local_experts(expert_inputs)

    returned = buffers.begin_combine(hidden.rows, routes_per_token=8)
    for output in expert_outputs:
        buffers.remote_write_combine(
            target_rank=output.source_rank,
            batch_slot=output.batch_slot,
            route_slot=output.route_slot,
            value=output.value,
        )
        buffers.publish_combine_ready(
            target_rank=output.source_rank,
            batch_slot=output.batch_slot,
        )

    buffers.wait_combine_ready(returned)
    result = zeros_like(hidden)
    for batch_slot in range(hidden.rows):
        for route_slot in range(8):
            value = buffers.read_combine(batch_slot, route_slot)
            result[batch_slot] += expert_weights[batch_slot, route_slot] * value
    return result

图里最值得盯住的是两件事:token 被量化后搬走,scale 与来源元数据必须一起活着;返回结果必须等八路贡献齐全才能相加。

LEP 五视图技术图

原创五视图技术图,语义依据 arXiv 2506.12708v3 §4.2.1–4.2.2:展示路由、远端写、专家计算、返回与缓冲区生命周期。它是论文语义重建,不是公开源码拓扑;为可读性把 320 个 peer 折叠为少数示例 rank,并省略共享专家支路。

缓冲区公式

静态缓冲区避免每轮临时分配,但必须按最坏情况预留:

\[ \text{max\_tokens} = \text{local\_batch} \times \min(\text{topK}, \text{experts\_per\_die}) \]
\[ \text{buffer\_size} = \text{rank\_num} \times \text{max\_tokens} \times \text{msg\_size} \]

在论文给出的 320 rank、local batch 96、每 die 一个专家配置下,dispatch 缓冲区约 225 MB,combine 缓冲区约 420 MB,合计约 645 MB/die。双缓冲还要避免 combine 覆盖仍在使用的 dispatch 数据。

融合不等于免费

收益来自减少启动、动态分配和中间往返;代价是固定缓冲、最坏情况容量、同步 flag,以及提早 INT8 量化的精度边界。这个 645 MB 是论文配置的结果,不能直接套到别的 batch、topK 或专家布局。

微批流水:少让硬件等待

Ascend NPU 中,AIC 更擅长矩阵计算,AIV 更擅长向量整理和部分通信控制,SDMA 负责大块数据搬运。若严格串行执行,常出现“矩阵单元算完了,等通信;通信单元忙完了,又等下一次矩阵”。

CloudMatrix-Infer 把 batch 切成 microbatch(微批),让两个微批在不同阶段交错。论文的解码示例中,Attention 路径与 MoE 路径各约 600 μs,因此有机会像接力一样重叠。

三轨接力隐喻

原创认知插图:微批并不让单个算子凭空加速,而是把矩阵、向量和搬运的空档互相填上。

运行过程

def run_layer_with_two_streams(microbatches, attention_stream, moe_stream):
    attention_done = {}
    moe_done = {}

    for index, microbatch in enumerate(microbatches):
        attention_done[index] = attention_stream.submit(
            operation="attention_path",
            inputs=microbatch,
        )

        if index > 0:
            previous = index - 1
            attention_done[previous].wait_on(moe_stream)
            moe_done[previous] = moe_stream.submit(
                operation="moe_path",
                inputs=microbatches[previous],
            )

    last = len(microbatches) - 1
    attention_done[last].wait_on(moe_stream)
    moe_done[last] = moe_stream.submit(
        operation="moe_path",
        inputs=microbatches[last],
    )

    outputs = []
    for index in range(len(microbatches)):
        moe_done[index].wait()
        outputs.append(moe_done[index].result())
    return restore_request_order(outputs)

预填阶段的搭配略不同:AIC 主跑 Attention/MLP,AIV 跑 Dispatch/Combine 的计算部分,SDMA 做大块 all-to-all;两个微批再次交错。长序列的 MLA 还采用 SP-TP-SP:先按序列切分,再按头做张量并行,最后切回序列并行,以两次集合通信换更均匀的负载。

微批流水五视图技术图

原创五视图技术图,语义依据 arXiv 2506.12708v3 §4.2.3 与 §4.3.2:重点观察同一微批的依赖边和不同微批的重叠窗口;为可读性省略具体算子 tiling、SP-TP-SP 集合通信和动态资源比例数值。

适用边界

微批不是越小越好:

  • 太大:可重叠的窗口少,尾部等待长。
  • 太小:算子启动、同步和碎片化开销上升。
  • KV 长度变化:Attention 时间会随上下文增长,固定切分可能失衡。
  • 内存压力:同时活着的微批更多,中间张量和 workspace 的峰值可能上升。

论文同系统消融中,解码吞吐提升约 5.8%–9.4%,预填在所测设置中提升约 23%–31%。这些数字说明“重叠在该配置有效”,不保证迁移到任意模型和序列长度仍有相同比例。

EMS:共享 KV 图书馆

EMS(Elastic Memory Storage,弹性内存存储)把多个节点的 CPU DRAM 汇成共享池,并用 SSD 作为更冷的层级。上下文缓存按 128–512 token 切块,用“前缀哈希 + 当前 token 块”形成链式身份。

想象很多人都问:“请总结同一份 4000 字报告,但关注点不同。”如果前 3500 字相同,就不必每次重算。缓存系统先找到最长的相同前缀,只计算新后缀。

运行过程

def prefill_with_ems(tokens, block_size, ems, prefill_engine):
    blocks = split_into_blocks(tokens, block_size)
    prefix_hash = ems.root_hash()
    matched_kv = []
    first_missing = len(blocks)

    for index, token_block in enumerate(blocks):
        block_hash = ems.hash_block(prefix_hash, token_block)
        entry = ems.lookup(block_hash)
        if entry is None:
            first_missing = index
            break
        matched_kv.append(ems.read_to_npu(entry))
        prefix_hash = block_hash

    suffix_tokens = concatenate(blocks[first_missing:])
    suffix_kv = prefill_engine.compute_suffix(
        tokens=suffix_tokens,
        reused_prefix=matched_kv,
    )

    produced = split_kv_like_blocks(suffix_kv, blocks[first_missing:])
    for offset, kv_block in enumerate(produced):
        token_block = blocks[first_missing + offset]
        block_hash = ems.hash_block(prefix_hash, token_block)
        ems.write_async(block_hash, kv_block, preferred_tier="dram")
        prefix_hash = block_hash

    return concatenate(matched_kv + produced)

EMS 五视图技术图

原创五视图技术图,语义依据 arXiv 2506.12708v3 §4.4.1–4.4.2:从哈希链、NPU 读取、DRAM/SSD 分层和 KV 生命周期理解共享缓存;为可读性省略控制器恢复、负载均衡和模型缓存支路。

命中率边界

论文在 4K 输入、总 batch 为每 NPU 16K token 的设置下报告:

  • 上下文复用率达到 90% 时,相比不使用 EMS,吞吐达到 2.28×
  • 通过 UB 访问缓存,相比 VPC 路径最高达到 1.52×

但复用率为零时,缓存不能替你省计算,还会多一次查询。并且 DeepSeek-R1 的推理链包含生成的 reasoning token 与位置变化,论文通常不把 decode 新生成的 KV 当作跨请求上下文缓存。EMS 最适合稳定、重复、块对齐的长前缀,不是所有对话的万能记忆。

三种最后一公里

四个主机制之外,论文还叠加了三类优化。它们更像“把剩余缝隙再挤一遍”,不要和前面的系统分工混成一个概念。本节只帮助你辨认作用方向与证据边界,不把它们冒充成量化、MLA 或推测解码的完整实现教程。

INT8:少搬一些

论文采用训练后、分层混合精度:计算密集的矩阵乘尽量用 INT8,归一化、门控等敏感位置保留 BF16/FP32;权重使用 per-channel scale,激活使用 per-token scale,并处理离群值。

这里有两个不同层次的 INT8:

  1. 模型量化减少选定权重和激活的计算/存储开销。
  2. Dispatch 提前量化专门减少 MoE 跨 rank 消息,从 BF16 hidden 变为 INT8 加 scale。

论文在 16 个基准上报告总体可比,但比较中可能存在 API、采样参数和评分器差异,因此准确说法是“在作者设置中总体保持”,不是“INT8 永不掉点”。

MLA 融合:少落几次地

MLA(Multi-head Latent Attention,多头潜在注意力)相关路径把 RMSNorm、QKV、RoPE 合进 MLAProlog,把 FlashAttention 与拼接/切片合并,并让 KV Cache 直接保持更适合 NPU 的 NZ 布局。

它减少中间张量写回和布局转换,但融合算子仍需要 workspace、寄存器与片上缓存;输入形状变化也会影响 tiling。少一次中间落地,不等于峰值显存按同样比例下降。

MTP:一次多猜一点

MTP(Multi-Token Prediction,多 token 预测)让一次迭代猜多个后续 token,再验证可接受的前缀。论文假设一个推测 token 的接受率为 70%,平均每轮接受约 1.7 个 token。

吞吐收益取决于接受率和 batch;验证分支又增加了本轮计算。论文报告的提升范围约 6%–49%,同时 batch 96 的单层迭代时延增加约 44%。所以 MTP 的核心是“用更多单轮工作换更少迭代轮数”,不是单轮更快。

数字应该怎样读

论文 Table 3 解码吞吐对比

论文 Table 3(定量证据):支持 CM384 在所列 DeepSeek-R1、batch 96、4K KV Cache 条件下报告 1943 tokens/s/NPU 与 49.4 ms TPOT;不同硬件、batch、软件来源和 MTP 假设并不统一,不能据此下“硬件普遍更快”的结论。

先认识三个指标:

  • TTFT(Time To First Token):从请求到首 token,主要看预填。
  • TPOT(Time Per Output Token):后续每个输出 token 的间隔,主要看解码。
  • tokens/s/NPU:每个 NPU 的吞吐,仍受 batch、精度、缓存长度和软件实现影响。

论文 Table 2 的默认预填结果是 5655 tokens/s/NPU6688 对应的是 Perfect EPLB,也就是理想化专家负载均衡,不应写成默认实测。

Table 3 中,CM384 解码行是 1943 tokens/s/NPU、49.4 ms TPOT、batch 96、4K KV Cache。同表的 H800/H100 数据来自博客、profile 或模拟,batch 和软件也不同。tokens/s/TFLOPS 提供了一种归一化视角,但仍不是总成本、能耗或生产成熟度的完整比较。

读系统论文的四问

  1. 同一模型吗?
  2. 同一精度、batch、上下文长度吗?
  3. 实测、模拟还是理想投影?
  4. 比较的是延迟、吞吐、单位算力,还是总成本?

内存账本

一篇系统文章最容易制造的错觉,是“某个对象搬走了,所以内存消失了”。更稳妥的峰值账本是:

\[ M_{\text{peak}} = M_{\text{model state}} + M_{\text{saved activations}} + M_{\text{operator workspace}} + M_{\text{communication buffers}} + M_{\text{live I/O}} + M_{\text{allocator reserved unused}} + M_{\text{runtime margin}} \]

在推理中,saved activations 通常比训练少,但其他项仍然存在:

  • PDC:把部分 KV 与模型缓存从 NPU HBM 移到 CPU DRAM;增加传输目标、完成状态与在途数据。
  • LEP:减少每 rank 的专家权重压力;增加约束于配置的 dispatch/combine 静态缓冲。
  • 微批:提高硬件重叠;可能让更多中间张量和 workspace 同时活着。
  • EMS:扩大共享缓存容量;增加 DRAM/SSD 层级、哈希元数据和注册内存。
  • INT8:减少选定权重与消息;增加 scale、量化/反量化临时对象和误差边界。

因此,正确的结论是内存被重排、分层和设上界,而不是凭空消失。

小白检查清单

读完后,不妨试着回答:

  1. 为什么 prefill 和 decode 的最佳硬件配比不同?
  2. PDC 的 KV Cache 在什么时候、从哪里、搬到哪里?
  3. EP320 为什么能省专家权重复制,却要付通信缓冲区?
  4. 微批流水提升的是单算子速度,还是系统重叠率?
  5. EMS 的 2.28× 为什么必须同时写上“90% 复用率”和测试条件?
  6. Table 3 为什么不能直接变成 Ascend、H800、H100 的排行榜?

如果这些问题能用自己的话讲清,你已经抓住了这篇论文最值得迁移到其他系统的部分。

总结

CloudMatrix384 的故事可以压缩成一句话:先承认大模型推理不是一种工作,再为每种工作安排合适的资源、通路与状态所有权。

  • PDC 解决不同阶段的资源错配。
  • LEP 解决超大 MoE 专家如何分布与低时延交换。
  • 微批流水解决异构计算单元互相等待。
  • EMS 解决长前缀重复计算与 NPU HBM 容量。
  • INT8、算子融合与 MTP 再分别压缩数据、减少落地和减少迭代轮数。

真正可复用的不是某个数字,而是这套对象—路径—完成条件—峰值账本—证据边界的分析方法。

参考资料

  1. Huawei & SiliconFlow, Serving Large Language Models on Huawei CloudMatrix384, arXiv 2506.12708v3, 2025.
  2. 想飞的石头,《Serving Large Language Models on Huawei CloudMatrix384》学习笔记,2025-06-22.

评论