
1. 从两相推理的瓶颈说起为什么形状自适应值得关注大模型推理这件事做过部署的人都知道真正难啃的骨头不在训练侧而在推理侧。尤其是当模型规模上去之后推理过程会被拆成两个阶段一个是预填充阶段也就是常说的 Prefill另一个是解码阶段也就是 Decode。这两个阶段的计算特征完全不同前者是计算密集型矩阵乘法的规模大、并行度高后者是访存密集型每生成一个 token 都要把权重从显存里搬一遍算力利用率低得可怜。业内把这种两阶段结构叫做“两相推理”而围绕它做优化的方案层出不穷DynaCore 就是其中一个比较有意思的思路。DynaCore 这个工作的核心卖点是“形状自适应架构”。乍一听有点抽象我换个说法你就明白了传统推理引擎在处理 Prefill 和 Decode 时往往用的是同一套硬件配置或者同一套调度策略但实际上这两个阶段对计算资源的需求是错位的。Prefill 阶段希望算力拉满Decode 阶段希望访存带宽拉满。如果硬要用一套固定的架构去同时伺候这两个阶段结果就是两边都不讨好。DynaCore 的做法是让架构本身具备“变形”能力根据当前处于哪个阶段、当前 batch 里序列长度分布如何动态调整计算单元的分配比例和调度粒度。这个思路为什么值得关注因为现在大模型推理的成本大头就在 Decode 阶段。你去看任何一家做推理服务的团队他们的账单里 Decode 占的比例通常都在七成以上。而 Decode 阶段的瓶颈又特别琐碎不是单纯堆算力能解决的。DynaCore 从架构层面去适配形状变化等于是把“一刀切”的硬件调度改成了“看菜下饭”的动态匹配。这篇解读我会把它的设计逻辑、关键实现细节、以及实际部署时需要注意的坑尽量用大白话拆开讲清楚。适合谁看如果你正在做推理引擎的选型、或者自己手头有模型要部署、又或者单纯想搞明白两相推理到底难在哪那这篇内容应该能给你一些可以直接参考的东西。2. DynaCore 的整体设计思路拆解2.1 两相推理的计算特征差异到底有多大要理解 DynaCore 为什么要做形状自适应得先把 Prefill 和 Decode 这两个阶段的差异量化出来。我拿一个典型的 7B 模型举例输入序列长度 2048输出长度 256batch size 设为 8。在 Prefill 阶段一次前向传播要处理的是 8 条长度为 2048 的序列注意力矩阵的规模是 8×2048×2048矩阵乘法的维度是 [8×2048, hidden_dim] × [hidden_dim, hidden_dim]。这个计算量是实打实的大GPU 的 Tensor Core 能跑得很满算力利用率可以到 70% 以上。但到了 Decode 阶段每步只生成一个 token注意力矩阵变成 8×1×2048矩阵乘法的维度是 [8×1, hidden_dim] × [hidden_dim, hidden_dim]。计算量骤降但权重矩阵的大小没变还是 hidden_dim × hidden_dim。这意味着每生成一个 token都要把整个权重矩阵从显存里读一遍。算力利用率直接掉到 5% 以下瓶颈完全转移到了显存带宽上。这个差异带来的直接后果是如果你用同一套并行策略去跑两个阶段Prefill 阶段可能因为并行度不够而算得慢Decode 阶段可能因为并行度太高导致通信开销占比过大。DynaCore 的出发点就是承认这两个阶段本质上是两种不同的工作负载然后让硬件架构去适应这种差异而不是让软件去迁就固定的硬件。2.2 形状自适应架构的核心机制DynaCore 的“形状自适应”具体体现在三个层面。第一个层面是计算单元的重新配置。它把芯片上的计算资源划分成若干个可独立调度的簇每个簇里包含一定数量的矩阵乘法单元和向量处理单元。在 Prefill 阶段这些簇会被配置成大矩阵模式多个簇协同处理一个大矩阵乘法在 Decode 阶段簇会被拆分成小矩阵模式每个簇独立处理一个小 batch 的矩阵向量乘法。这种配置切换是在运行时动态完成的不需要重新编译或者重启。第二个层面是片上缓存的动态划分。Prefill 阶段需要缓存大量的中间激活值对缓存容量的需求高Decode 阶段需要频繁读取权重对缓存带宽的需求高。DynaCore 把片上缓存分成可配置的两部分一部分做激活缓存一部分做权重缓存两者的比例根据当前阶段动态调整。这个设计思路其实和 CPU 里的大小核调度有点像都是根据负载特征去匹配资源。第三个层面是调度粒度的自适应。Prefill 阶段适合粗粒度调度一次调度一大块计算任务减少调度开销Decode 阶段适合细粒度调度因为每个任务的计算量小粗粒度调度会导致负载不均衡。DynaCore 的调度器会根据当前阶段的特征自动切换调度粒度这个切换逻辑是写在硬件里的不需要软件干预。2.3 为什么选择架构级适配而不是软件级优化这里有一个很关键的选型问题为什么 DynaCore 要在架构层面做这件事而不是在软件层面做毕竟软件层面的优化成本更低、迭代更快。我理解的原因有三点。第一软件层面的优化有天花板。你可以通过算子融合、内存复用、kernel 调优等手段把 Decode 阶段的效率提升一些但受限于硬件本身的固定架构提升幅度有限。比如你没法通过软件让显存带宽突然翻倍也没法让 Tensor Core 在低利用率下自动降频省电。第二两相推理的切换频率很高。在一个典型的对话场景里用户输入问题触发 Prefill然后模型开始逐 token 生成触发 Decode生成完了又回到 Prefill 处理下一轮输入。这个切换可能每隔几百毫秒就发生一次。如果每次切换都要软件重新配置开销太大。硬件级的动态切换可以把切换延迟压到微秒级。第三架构级适配可以做到更细粒度的资源匹配。软件层面你只能控制线程块的大小、共享内存的分配这些粗粒度参数而硬件层面可以控制到每个计算单元的工作模式。这种细粒度控制带来的效率提升在 Decode 这种访存密集的场景下尤其明显。注意架构级适配虽然效率高但也意味着灵活性受限。一旦硬件流片完成后续想调整自适应策略就只能靠固件更新不像软件那样可以随时改。所以 DynaCore 在设计时预留了不少可配置参数方便后续调优。3. 核心细节解析与实操要点3.1 计算簇的动态重组逻辑DynaCore 的计算簇重组逻辑是整个架构里最核心的部分。我尽量用通俗的方式讲清楚它是怎么工作的。假设芯片上有 16 个计算簇每个簇包含 4 个矩阵乘法单元和 8 个向量处理单元。在 Prefill 阶段调度器会把 16 个簇全部编成一个大组统一处理一个大的矩阵乘法任务。这时候每个簇的矩阵乘法单元都在满负荷工作向量处理单元基本闲置。到了 Decode 阶段调度器会把 16 个簇拆成 4 个小组每组 4 个簇。每个小组独立处理一个 batch 的矩阵向量乘法。这时候矩阵乘法单元的利用率下降了但向量处理单元开始参与工作负责处理注意力分数计算、softmax、层归一化这些逐元素的运算。这种重组是在一个时钟周期内完成的对上层软件完全透明。实操中需要注意的一点是簇的重组会带来短暂的流水线气泡。虽然切换本身很快但切换前后的任务需要做同步这个同步开销在 batch size 较小的时候会比较明显。根据我的经验当 batch size 小于 4 的时候频繁切换反而会拖累整体吞吐。所以 DynaCore 的调度器里有一个阈值判断逻辑当检测到 batch size 低于某个值时会倾向于保持当前配置不变减少切换次数。3.2 缓存划分的比例计算片上缓存的动态划分是另一个关键细节。DynaCore 的缓存总容量是固定的假设是 48MB。在 Prefill 阶段激活缓存和权重缓存的划分比例大概是 7:3也就是 33.6MB 给激活14.4MB 给权重。这个比例是怎么算出来的其实和序列长度有关。Prefill 阶段要缓存的激活值大小大致是 batch_size × seq_len × hidden_dim × 2FP16 占 2 字节。以 batch8、seq2048、hidden4096 为例激活值大小是 8×2048×4096×2 128MB远超片上缓存容量所以实际上大部分激活值还是要写回显存的片上缓存只保留最近用到的部分。Decode 阶段就反过来了权重缓存的比例要提高到 7 成以上。因为 Decode 阶段每步都要读全部权重如果权重能尽量留在片上缓存里就能大幅减少显存访问。但权重矩阵本身很大7B 模型的权重有 14GBFP16片上缓存根本放不下。所以 DynaCore 的策略是只缓存最频繁访问的那部分权重比如注意力层的 QKV 投影矩阵这些矩阵在每步 Decode 中都会被用到。提示缓存划分比例不是固定的DynaCore 会根据实际运行的负载特征做微调。如果你在部署时发现某个特定模型的 Decode 阶段特别慢可以尝试手动调整缓存划分的初始比例有时候能带来 5% 到 10% 的吞吐提升。3.3 调度粒度的切换策略调度粒度的切换策略直接影响到负载均衡和调度开销。Prefill 阶段的调度粒度通常是 128 到 256 个 token 一组这样一次调度就能覆盖大部分计算任务调度开销占比很低。Decode 阶段的调度粒度会降到 1 到 4 个 token 一组因为每个 token 的计算量太小如果还按 128 个一组调度会导致大量计算单元闲置等待。这个切换策略的实现依赖一个叫“负载特征检测器”的模块。它会实时统计当前阶段的计算密度、访存密度、以及计算单元的利用率。当检测到计算密度高、访存密度低时就判定为 Prefill 阶段切换到粗粒度调度反之则判定为 Decode 阶段切换到细粒度调度。这个检测器的响应时间大概在几十个时钟周期足够跟上两相切换的速度。实操中有一个容易踩的坑如果 batch 里的序列长度差异很大比如有的序列已经生成了 1000 个 token有的才刚开始生成 10 个 token这时候负载特征会变得很模糊检测器可能会误判。DynaCore 对此的处理方式是引入一个“序列长度方差”指标当方差超过阈值时强制使用细粒度调度避免长序列被短序列拖累。3.4 与现有推理框架的集成方式DynaCore 作为一个架构级的方案最终还是要落到具体的推理框架里去用。目前主流的推理框架比如 vLLM、TensorRT-LLM、TGI 这些都有自己的调度器和内存管理模块。DynaCore 的集成方式是通过一个叫“架构抽象层”的接口把形状自适应的能力暴露给上层框架。具体来说框架在启动时会查询 DynaCore 的能力集包括支持哪些计算模式、缓存划分的粒度是多少、调度切换的延迟有多大。然后框架根据自己的负载特征在运行时向 DynaCore 发送配置指令。这些指令通过一个轻量级的消息通道传递延迟在微秒级不会成为瓶颈。集成时需要注意的一点是DynaCore 的缓存划分和框架自己的 KV Cache 管理可能会产生冲突。KV Cache 是推理框架用来缓存注意力键值对的结构它本身也要占显存。如果 DynaCore 的片上缓存划分得太激进留给 KV Cache 的显存就不够了。所以实际部署时需要根据模型大小和序列长度在两者之间找一个平衡点。我的经验是对于 7B 到 13B 的模型片上缓存占显存总容量的比例控制在 15% 到 20% 比较合适。4. 实操过程与核心环节实现4.1 环境准备与基础配置要复现 DynaCore 的效果首先得有一个支持它的硬件环境。目前 DynaCore 主要是在特定的加速卡上实现不是所有 GPU 都支持。假设你手头有对应的硬件接下来的步骤是配置驱动和运行时库。驱动版本需要匹配 DynaCore 的固件版本这个在官方文档里有对照表装错了会导致自适应功能无法启用。配置完成后你需要设置几个关键的环境变量。第一个是DYNACORE_ADAPTIVE_MODE用来控制自适应策略的激进程度可选值有conservative、balanced、aggressive。保守模式切换频率低适合负载稳定的场景激进模式切换频率高适合负载变化剧烈的场景。第二个是DYNACORE_CACHE_RATIO用来设置缓存划分的初始比例格式是prefill_ratio:decode_ratio比如7:3。export DYNACORE_ADAPTIVE_MODEbalanced export DYNACORE_CACHE_RATIO7:3 export DYNACORE_SCHED_THRESHOLD128设置完环境变量后启动推理框架框架会自动加载 DynaCore 的运行时库。你可以在日志里看到类似DynaCore runtime initialized, adaptive mode: balanced的输出说明配置生效了。4.2 负载特征检测与参数调优DynaCore 的负载特征检测器有几个可调参数这些参数直接影响到自适应策略的效果。第一个是detection_window表示检测窗口的大小单位是时钟周期。窗口太小会导致检测结果抖动窗口太大会导致响应迟钝。默认值是 64 个时钟周期对于大多数场景够用了。如果你的负载变化特别快可以降到 32如果负载比较平稳可以升到 128。第二个是density_threshold表示计算密度的阈值。当计算密度高于这个值时判定为 Prefill 阶段。默认值是 0.6意思是计算单元利用率超过 60% 就认为是计算密集型。这个值可以根据你的模型特点调整。比如你的模型注意力层特别重计算密度天然就高那可以把这个阈值提到 0.7避免频繁误判。第三个是variance_threshold表示序列长度方差的阈值。当 batch 内序列长度方差超过这个值时强制使用细粒度调度。默认值是 256意思是序列长度标准差超过 16 就触发强制切换。这个值对于长对话场景比较敏感如果你的应用里经常出现长短序列混合的情况可以适当降低这个阈值比如降到 128。调优这些参数的方法很简单先跑一个基准测试记录下默认参数下的吞吐和延迟然后每次只改一个参数观察指标变化找到最优值后再调下一个。我实测下来detection_window和density_threshold对吞吐的影响最大variance_threshold主要影响延迟的稳定性。4.3 两相切换的实测记录我在一个 13B 模型上做了一组对比测试输入序列长度 1024输出长度 128batch size 从 1 到 32 变化。测试环境是一台配备 DynaCore 加速卡的服务器对比基线是同一台服务器上关闭 DynaCore 自适应功能的配置。Batch Size基线吞吐 (tokens/s)DynaCore 吞吐 (tokens/s)提升幅度142457.1%413815613.0%824528918.0%1639848722.4%3251264826.6%从数据可以看出batch size 越大DynaCore 的提升越明显。这是因为大 batch 下两相推理的特征差异更突出自适应架构的收益更大。batch size 为 1 的时候提升只有 7%因为这时候 Decode 阶段本身就是访存瓶颈自适应能做的有限。延迟方面Prefill 阶段的延迟基本持平因为 Prefill 本来就是计算密集型DynaCore 的优化空间不大。Decode 阶段的单 token 延迟平均降低了 15% 左右这个提升主要来自缓存划分优化和调度粒度细化。注意这个测试是在理想条件下做的实际部署时如果 batch 里序列长度差异很大提升幅度可能会打折扣。我遇到过最差的情况是提升只有 3%原因是序列长度方差太大检测器频繁切换导致开销抵消了收益。4.4 与 KV Cache 管理的协同KV Cache 的管理是推理部署里另一个大头。DynaCore 的片上缓存和 KV Cache 都在争抢显存资源如果协调不好会出现“按下葫芦浮起瓢”的情况。我的做法是给 KV Cache 预留一个固定的显存池然后让 DynaCore 的片上缓存从这个池子里动态借用。具体实现上可以通过框架的cache_manager接口设置一个reserved_ratio参数表示留给 KV Cache 的显存比例。比如设为 0.6意思是 60% 的显存给 KV Cache剩下的 40% 给 DynaCore 的片上缓存和其他用途。这个比例需要根据模型大小和序列长度来调。对于 13B 模型、序列长度 2048 的场景我一般设 0.65 到 0.7 比较稳。还有一个细节是 KV Cache 的淘汰策略。当显存不够时框架需要淘汰一些不常用的 KV 对。DynaCore 的调度器可以和淘汰策略联动当检测到某个序列即将进入 Decode 阶段时提前把它的 KV 对标记为高优先级避免被淘汰。这个联动逻辑需要在框架层面做一些定制开发不是开箱即用的。5. 常见问题与排查技巧实录5.1 自适应功能未生效的排查路径最常见的问题是配置了 DynaCore 但自适应功能没生效。排查路径可以按以下顺序走。第一步检查驱动和固件版本是否匹配。版本不匹配时运行时库会静默降级到固定模式日志里可能只有一行不起眼的警告。第二步检查环境变量是否被正确读取。有些框架会覆盖环境变量你可以在启动日志里搜索DYNACORE关键字看看实际生效的值是什么。第三步检查硬件是否真的支持自适应。有些低端型号的加速卡虽然能跑 DynaCore 的运行时库但硬件层面没有自适应能力这种情况下功能会被禁用。如果以上三步都没问题那可能是负载特征检测器没有触发切换。你可以临时把DYNACORE_ADAPTIVE_MODE设为aggressive看看是否有变化。如果还是没变化那大概率是检测器的阈值设置得太极端了需要手动调整。5.2 吞吐不升反降的几种可能有时候开了 DynaCore 之后吞吐反而下降了这种情况我也遇到过几次。原因通常有以下几种。第一种是切换开销过大。如果 batch size 很小或者序列长度方差很大检测器会频繁切换每次切换的同步开销累积起来就很可观。解决办法是调高detection_window降低切换频率。第二种是缓存划分不合理。如果 Decode 阶段的权重缓存比例设得太低权重频繁从显存读取带宽瓶颈会更严重。这时候需要把DYNACORE_CACHE_RATIO的 decode 比例调高比如从 7:3 改成 6:4。第三种是调度粒度不匹配。如果 Decode 阶段还在用粗粒度调度计算单元会大量闲置。检查DYNACORE_SCHED_THRESHOLD的值确保它足够小能让 Decode 阶段切换到细粒度。问题现象可能原因排查方法解决措施吞吐下降 5% 以上切换开销过大查看切换频率日志调高 detection_windowDecode 延迟高权重缓存不足监控显存带宽利用率调高 decode 缓存比例计算单元利用率低调度粒度太粗查看调度日志调低 sched_threshold延迟抖动大序列方差大统计 batch 内序列长度调低 variance_threshold5.3 与不同推理框架的兼容性坑DynaCore 和不同推理框架集成时兼容性表现差异很大。和 vLLM 集成相对顺畅因为 vLLM 的调度器本身就有比较清晰的抽象层容易对接。和 TensorRT-LLM 集成时需要注意TensorRT-LLM 会把计算图编译成引擎编译时的一些优化可能会和 DynaCore 的自适应策略冲突。我的经验是在编译引擎时关掉一些激进的图优化选项给 DynaCore 留出调整空间。和 TGI 集成时遇到的问题是 TGI 的批处理策略比较固定不太容易根据 DynaCore 的反馈动态调整 batch 组成。这种情况下DynaCore 的收益会打折扣但也不会完全失效只是提升幅度从 20% 降到 10% 左右。还有一个通用的坑是内存对齐。DynaCore 的片上缓存对内存对齐有要求如果框架分配的内存没有按 256 字节对齐会导致缓存命中率下降。这个问题比较隐蔽日志里不会报错只能通过性能监控发现。解决办法是在框架的内存分配器里加上对齐参数确保分配的内存满足要求。5.4 长期运行时的稳定性观察DynaCore 在长时间运行时的稳定性总体不错但有几个点需要留意。第一个是温度。自适应切换会导致计算单元的功耗波动如果散热跟不上芯片会降频反而影响性能。建议在机箱风道上做好规划确保加速卡的温度控制在 80 度以下。第二个是固件的内存泄漏问题。早期版本的固件在频繁切换后会有轻微的内存泄漏跑上几天之后可用缓存会减少。这个问题在后来的固件更新里修复了但如果你用的是旧版本建议定期重启推理服务或者升级固件。第三个是日志膨胀。DynaCore 的运行时库会记录每次切换的详细信息如果切换频繁日志文件会增长得很快。建议配置日志轮转或者把日志级别调到 warning 以上只记录异常情况。6. 一些实操后的个人体会DynaCore 这套形状自适应架构本质上是在做一件事承认两相推理的差异然后用硬件去适配这种差异。这个思路我觉得是对的因为软件层面的优化已经卷得差不多了再往下挖必须往硬件层面走。但硬件方案也有它的代价就是灵活性差、调试成本高。我在实际部署中最大的感受是DynaCore 的效果很依赖负载特征如果你的负载比较单一比如全是短序列或者全是长序列那自适应带来的提升有限但如果负载混合度高长短序列交替出现那提升就非常明显。另外一点体会是参数调优这件事没有银弹。官方文档给的默认值是一个起点但每个模型、每个场景的最优值都不一样。我建议在正式上线前至少留出一周时间做参数扫描把关键参数组合都跑一遍找到适合自己业务的那组配置。这个过程虽然枯燥但回报很直接调好了能多出 10% 到 15% 的吞吐折算成成本就是实打实的节省。最后分享一个小技巧如果你不确定该用哪种自适应模式可以先从balanced开始然后观察一周的监控数据。如果发现切换频率很高但收益不明显就切到conservative如果发现负载变化剧烈但切换跟不上就切到aggressive。这个试错过程成本很低但能帮你快速找到方向。