Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ NVIDIA 还发布了量化权重版本 [`nvidia/GLM-5-NVFP4`](https://huggingface
- [sgl-project/sglang #21405](https://github.com/sgl-project/sglang/pull/21405) 为稀疏 MLA 启用了 **IndexCache**,在连续 decode 步骤间复用索引张量,在相同内核调用序列上带来 >10% 的 decode 吞吐量提升。
- [flashinfer-ai/flashinfer #2726](https://github.com/flashinfer-ai/flashinfer/pull/2726)(FlashInfer v0.6.6.post1)修复了一个间歇性 NVFP4 非法内存访问 bug,此前一直[阻塞](https://github.com/sgl-project/sglang/issues/19081) NVFP4 的功能验证签核;[flashinfer-ai/flashinfer #2836](https://github.com/flashinfer-ai/flashinfer/pull/2836)(v0.6.7)提升了 trtllm-gen 稀疏 MLA 的性能上限。

**MTP。** GLM-5 复用了 SGLang 为 DeepSeek V3.2 构建的 EAGLE 推测解码管线(`--speculative-algorithm EAGLE --speculative-num-steps 3 --speculative-eagle-topk 1 --speculative-num-draft-tokens 4`),并通过 `SGLANG_ENABLE_SPEC_V2=1` 启用 overlap 调度器。H200 和 B200 使用完全相同的参数集——两款 SKU 在下面方案中唯一的不同是模型检查点和注意力后端的选择。
**MTP。** GLM-5 复用了 SGLang 为 DeepSeek V3.2 构建的 EAGLE 投机解码管线(`--speculative-algorithm EAGLE --speculative-num-steps 3 --speculative-eagle-topk 1 --speculative-num-draft-tokens 4`),并通过 `SGLANG_ENABLE_SPEC_V2=1` 启用 overlap 调度器。H200 和 B200 使用完全相同的参数集——两款 SKU 在下面方案中唯一的不同是模型检查点和注意力后端的选择。

## 详细数据

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ tags:
- sglang
- vllm
- trtllm
- cann
---

_本文最初于 2026 年 6 月 9 日发布在 [SemiAnalysis 通讯](https://newsletter.semianalysis.com/p/deepseekv4-16t-day-0-to-day-43-performance)。_
Expand Down Expand Up @@ -179,7 +180,7 @@ ATOM 的几乎每条热路径也都走了 fallback:FP4 MoE 被迫使用 Triton

### NVIDIA TensorRT-LLM 的 Bug 及缺乏第 0 天 DeepSeekV4 Pro 支持

TensorRT 无法开箱即用地支持 DeepSeek v4,因为 `mhcFusedHcKernel.cu` 中有一个硬编码的 `FHC_HIDDEN = 4096` 常量。问题在于 SHAPE_K、residual/x TMA 描述符以及 MMA kernel 模板实例化都绑定在该隐藏维度大小上。所有之前的 DeepSeek 模型和 DeepSeek v4 flash 的隐藏维度均为 4096,因此暂时不出问题。但尝试对 DeepSeek v4 Pro 进行推理时,会触发 `"mhcFusedHcLaunch: hidden_size=7168 not supported (only 4096)"` 的保护错误。
TensorRT 无法开箱即用地支持 DeepSeek v4,因为 `mhcFusedHcKernel.cu` 中有一个硬编码的 `FHC_HIDDEN = 4096` 常量。问题在于 SHAPE_K、residual/x TMA 描述符以及 MMA kernel 模板实例化都绑定在该隐藏维度大小上。所有之前的 DeepSeek 模型和 DeepSeek v4 flash 的隐藏维度均为 4096,因此暂时不出问题。但尝试对 DeepSeek v4 Pro 进行推理时,会触发 `mhcFusedHcLaunch: hidden_size=7168 not supported (only 4096)` 的保护错误。

Nvidia 工程师也遇到了这个保护错误,但他们没有添加代码来支持 DeepSeek v4 Pro 的 7168 隐藏维度,而是直接[移除了保护检查](https://github.com/NVIDIA/TensorRT-LLM/commit/b3f45bb608aecca666a451ca5138b81470487f05)。毫不意外,错误确实消失了。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ GB200 的每 GPU 峰值吞吐量为 8,933,交互性为 15.3 tok/s/user。GB300
| 36 | 1,376 | 2,036 | 1.48x | $0.65 | $0.35 | 1.88x |
| 50 | 649 | 941 | 1.45x | $4.78 | $1.58 | 3.03x |

标题中 **2.83 倍每 GPU 吞吐量峰值出现在 27 tok/s/user(性价比 2.31 倍),位于曲线中段**而非峰值吞吐处。在 20 tok/s/user 以下,两套机架都运行足够宽的预填充批次,HBM 余量优势被抹平;在 36 tok/s/user 以上,两者都运行窄批次,没有哪套机架拥有宽 EP 能充分利用的配方。22–32 tok/s/user 区间是 GB300 的 1.5 倍 HBM 容量让其停留在一个更高 Pareto 节点上的地方(`conc=3072, 28 GPU 预填充, 32 GPU 解码 EP=16, 6,812 tok/s/GPU at 25.9 tok/s/user`),而 GB200 在同等交互性下没有等效配方——其最接近的配方是在 32-GPU 解码池上 conc=256 / 512,仅能提供 1,614–2,005 tok/s/GPU。
标题中 **2.83 倍每 GPU 吞吐量峰值出现在 27 tok/s/user(性价比 2.31 倍),位于曲线中段**而非峰值吞吐处。在 20 tok/s/user 以下,两套机架都运行足够宽的预填充批次,HBM 余量优势被抹平;在 36 tok/s/user 以上,两者都运行窄批次,没有哪套机架拥有宽 EP 能充分利用的配方。22–32 tok/s/user 区间是 GB300 的 1.5 倍 HBM 容量让其停留在一个更高 Pareto 节点上的地方(`conc=3072, 28 GPU prefill, 32 GPU decode EP=16, 6,812 tok/s/GPU at 25.9 tok/s/user`),而 GB200 在同等交互性下没有等效配方——其最接近的配方是在 32-GPU 解码池上 conc=256 / 512,仅能提供 1,614–2,005 tok/s/GPU。

50 tok/s/user 行显示成本比率(3.03x)再次扩大,因为两条曲线都进入了右侧的陡峭衰减区。这里的解读需要更谨慎——两套机架在该区域的 Pareto 覆盖都很薄(GB200 在约 33 tok/s/user 处各有一个节点,GB300 在约 37 tok/s/user 处各有一个节点,然后是到约 73 tok/s/user 的长尾),因此插值是在两个间隔较大的实测节点之间读取差距。22–32 tok/s/user 区间才是 GB300 优势的可靠甜蜜点;将 50 tok/s/user 行视为方向性参考。

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ OpenRouter 数据还显示 Nebius AI Studio (Fast) 以 167 tok/sec/user 的交
caption="来源:SemiAnalysis InferenceX"
/>

在如此高的交互性下,有必要采用 MTP 等推测解码技术来实现足够高的吞吐量,使推理具有经济可行性。幸运的是,MTP 能够在对模型精度影响极小的情况下提升吞吐量。我们将在文章后续部分进一步讨论 MTP 及其如何用于提升吞吐量/降低成本。
在如此高的交互性下,有必要采用 MTP 等投机解码技术来实现足够高的吞吐量,使推理具有经济可行性。幸运的是,MTP 能够在对模型精度影响极小的情况下提升吞吐量。我们将在文章后续部分进一步讨论 MTP 及其如何用于提升吞吐量/降低成本。

最后,我们再展示一张 FP8 DeepSeek 工作负载在 125 tok/s/user 下的图表。这是另一个低延迟工作负载,MTP 在其中显著改善了经济可行性。与前面的例子一样,我们注意到在这些较高交互性范围内,最便宜的配置都使用了 MTP。

Expand Down Expand Up @@ -519,7 +519,7 @@ AMD 有许多才华横溢的优秀工程师在 ATOM 上工作,我们鼓励 AMD

## 多 Token 预测(MTP)

推测解码通过使用一个小型、低成本的草稿模型提前提议多个 token 来降低自回归生成的成本。大模型然后在一次类似预填充计算的前向传播中验证所提议的 token。对于给定的输入序列长度,当输入多出 N 个 token 时,单次前向传播的耗时大致相同。推测解码利用这一特性,在小模型上运行推理生成多个 token 供主模型在一次前向传播中验证,在相似的时间预算内最多额外产出 N 个 token。
投机解码通过使用一个小型、低成本的草稿模型提前提议多个 token 来降低自回归生成的成本。大模型然后在一次类似预填充计算的前向传播中验证所提议的 token。对于给定的输入序列长度,当输入多出 N 个 token 时,单次前向传播的耗时大致相同。投机解码利用这一特性,在小模型上运行推理生成多个 token 供主模型在一次前向传播中验证,在相似的时间预算内最多额外产出 N 个 token。

<Figure
src="https://substack-post-media.s3.amazonaws.com/public/images/b2b2aa12-c308-4f4b-84f7-969228600ce5_2296x1126.png"
Expand All @@ -542,7 +542,7 @@ AMD 有许多才华横溢的优秀工程师在 ATOM 上工作,我们鼓励 AMD
caption="来源:SemiAnalysis InferenceX"
/>

在大批次下,推理机制相比小批次受内存带宽限制更少。由于推测解码(包括 MTP)的工作原理是用多余的计算换取更少的内存受限解码步骤,推测 token 带来的额外验证工作可能无法恰好利用空闲算力,导致在大批次下的改进幅度较小。
在大批次下,推理机制相比小批次受内存带宽限制更少。由于投机解码(包括 MTP)的工作原理是用多余的计算换取更少的内存受限解码步骤,投机 token 带来的额外验证工作可能无法恰好利用空闲算力,导致在大批次下的改进幅度较小。

从成本角度来看,MTP 能带来巨大的成本节省。在下表中,我们看到使用 Dynamo TRT 运行 FP4 的 DeepSeek-R1-0528 每百万总 token 成本为 $0.251,但启用 MTP 可将成本大幅降低至每百万总 token 仅 $0.057。

Expand Down Expand Up @@ -615,7 +615,7 @@ Anthropic 最近伴随 Opus 4.6 发布了"[fast mode](https://code.claude.com/do
caption="来源:SemiAnalysis InferenceX"
/>

此外,我们观察到推测解码等推理优化技术可以直接降低推理成本,无需新芯片。
此外,我们观察到投机解码等推理优化技术可以直接降低推理成本,无需新芯片。

以下面的例子为例,DeepSeek R1 FP4 在 8k/1k 工作负载上。在 150 tok/sec/user 的交互性水平下,基线 GB300 Dynamo TRT 的每百万 token 成本约为 $2.35,而启用 MTP 将价格降至约 $0.11。仅通过采用一种推理优化技术,就实现了该交互性水平下约 21 倍的价格降低。

Expand Down
38 changes: 38 additions & 0 deletions packages/app/cypress/component/blog-shared-chrome.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { HeadingLink } from '@/components/blog/heading-link';
import { registerAnalyticsClient } from '@/lib/analytics';

describe('Blog shared chrome', () => {
it('localizes heading-link feedback and tracks a Chinese copy interaction', () => {
const capture = cy.stub().as('capture');
const writeText = cy.stub();
writeText.resolves();
cy.wrap(writeText).as('writeText');
registerAnalyticsClient({ capture });
cy.window().then((win) => {
win.history.replaceState(null, '', '/zh/blog/example');
Object.defineProperty(win.navigator, 'clipboard', {
configurable: true,
value: { writeText },
});
});

cy.mount(<HeadingLink id="test-section" locale="zh" />);
cy.get('a[aria-label="复制本节链接"]').click();

cy.window().then((win) => {
cy.get('@writeText').should(
'have.been.calledWith',
`${win.location.origin}/zh/blog/example#test-section`,
);
});
cy.contains('链接已复制').should('be.visible');
cy.get('@capture').should('have.been.calledWith', 'blog_heading_link_clicked', {
id: 'test-section',
locale: 'zh',
});
cy.get('@capture').should('have.been.calledWith', 'blog_heading_link_copied', {
id: 'test-section',
locale: 'zh',
});
});
});
26 changes: 26 additions & 0 deletions packages/app/cypress/e2e/api-documentation.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const SITE_URL = 'https://inferencex.semianalysis.com';

describe('API documentation', () => {
it('exposes the localized reference and its OpenAPI contract', () => {
cy.viewport(1440, 900);
cy.visit('/api');

cy.get('[data-testid="api-reference"]')
Expand Down Expand Up @@ -78,4 +79,29 @@ describe('API documentation', () => {
.should('have.attr', 'href', '/zh/api')
.and('have.text', 'API 文档');
});

for (const [path, width] of [
['/api', 1440],
['/api', 375],
['/zh/api', 390],
] as const) {
it(`contains wide schema notes without page overflow at ${path} on ${width}px`, () => {
cy.viewport(width, 844);
cy.visit(path);
cy.get('[data-testid="api-schema-note"]').first().as('schemaNote').should('be.visible');
cy.get('@schemaNote').should('have.css', 'min-width', '0px');
cy.get('@schemaNote')
.find('.overflow-x-auto')
.then(($scrollers) => {
expect(
[...$scrollers].some((scroller) => scroller.scrollWidth > scroller.clientWidth),
).to.equal(true);
});
cy.document().then((document) => {
expect(document.documentElement.scrollWidth).to.be.at.most(
document.documentElement.clientWidth,
);
});
});
}
});
118 changes: 118 additions & 0 deletions packages/app/cypress/e2e/content-journeys.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
const CONTENT_SITE_URL = 'https://inferencex.semianalysis.com';
const BLOG_POST_COUNT = 31;
const GLOSSARY_ENTRY_COUNT = 118;

const BLOG_JOURNEY_MATRIX = [
{ basePath: '/blog', detailPrefix: '/blog/', alternateLocale: 'zh-CN', width: 1440 },
{ basePath: '/blog', detailPrefix: '/blog/', alternateLocale: 'zh-CN', width: 375 },
{ basePath: '/zh/blog', detailPrefix: '/zh/blog/', alternateLocale: 'en', width: 1440 },
{ basePath: '/zh/blog', detailPrefix: '/zh/blog/', alternateLocale: 'en', width: 390 },
] as const;

const GLOSSARY_JOURNEY_MATRIX = [
{ basePath: '/glossary', detailPrefix: '/glossary/', alternateLocale: 'zh-CN', width: 1440 },
{ basePath: '/glossary', detailPrefix: '/glossary/', alternateLocale: 'zh-CN', width: 375 },
{
basePath: '/zh/glossary',
detailPrefix: '/zh/glossary/',
alternateLocale: 'en',
width: 1440,
},
{
basePath: '/zh/glossary',
detailPrefix: '/zh/glossary/',
alternateLocale: 'en',
width: 390,
},
] as const;

function exerciseEveryBlogJourney({
basePath,
detailPrefix,
alternateLocale,
width,
}: (typeof BLOG_JOURNEY_MATRIX)[number]) {
cy.viewport(width, 900);
cy.visit(basePath);
cy.get('[data-testid="blog-post-card"]')
.should('have.length', BLOG_POST_COUNT)
.then(($cards) => {
const hrefs = [...$cards].map((card) => card.getAttribute('href'));
expect(hrefs.every((href): href is string => Boolean(href))).to.equal(true);
expect(new Set(hrefs).size).to.equal(BLOG_POST_COUNT);
expect(hrefs.every((href) => href?.startsWith(detailPrefix))).to.equal(true);

for (const href of hrefs as string[]) {
const slug = href.slice(detailPrefix.length);
const alternatePath = alternateLocale === 'zh-CN' ? `/zh${href}` : href.replace('/zh', '');
cy.get(`[data-testid="blog-post-card"][data-blog-slug="${slug}"]`).click();
cy.location('pathname').should('eq', href);
cy.get('[data-testid="blog-post-page"]')
.should('be.visible')
.and('have.attr', 'data-blog-slug', slug);
// Next.js streams head metadata after the body during client-side
// navigation, so match the fully-updated link instead of asserting on
// whichever (possibly stale) alternate is first in document order.
cy.get(
`link[rel="alternate"][hreflang="${alternateLocale}"][href="${CONTENT_SITE_URL}${alternatePath}"]`,
).should('exist');
cy.go('back');
cy.location('pathname').should('eq', basePath);
cy.get(`[data-testid="blog-post-card"][data-blog-slug="${slug}"]`).should('exist');
}
});
}

function exerciseEveryGlossaryJourney({
basePath,
detailPrefix,
alternateLocale,
width,
}: (typeof GLOSSARY_JOURNEY_MATRIX)[number]) {
cy.viewport(width, 900);
cy.visit(basePath);
cy.get('[data-testid="glossary-entry-link"]')
.should('have.length', GLOSSARY_ENTRY_COUNT)
.then(($links) => {
const hrefs = [...$links].map((link) => link.getAttribute('href'));
expect(hrefs.every((href): href is string => Boolean(href))).to.equal(true);
expect(new Set(hrefs).size).to.equal(GLOSSARY_ENTRY_COUNT);
expect(hrefs.every((href) => href?.startsWith(detailPrefix))).to.equal(true);

for (const href of hrefs as string[]) {
const slug = href.slice(detailPrefix.length);
const alternatePath = alternateLocale === 'zh-CN' ? `/zh${href}` : href.replace('/zh', '');
cy.get(`[data-testid="glossary-entry-link"][data-glossary-slug="${slug}"]`).click();
cy.location('pathname').should('eq', href);
cy.get('[data-testid="glossary-detail-page"]')
.should('be.visible')
.and('have.attr', 'data-glossary-slug', slug);
// See the blog journey above: retry until the streamed head metadata
// for this term has replaced the listing page's alternate link.
cy.get(
`link[rel="alternate"][hreflang="${alternateLocale}"][href="${CONTENT_SITE_URL}${alternatePath}"]`,
).should('exist');
cy.go('back');
cy.location('pathname').should('eq', basePath);
cy.get(`[data-testid="glossary-entry-link"][data-glossary-slug="${slug}"]`).should('exist');
}
});
}

describe('complete localized content journeys', () => {
describe('Blog listing to article', () => {
for (const scenario of BLOG_JOURNEY_MATRIX) {
it(`opens every ${scenario.basePath} article from the listing at ${scenario.width}px`, () => {
exerciseEveryBlogJourney(scenario);
});
}
});

describe('Glossary index to term', () => {
for (const scenario of GLOSSARY_JOURNEY_MATRIX) {
it(`opens every ${scenario.basePath} term from the index at ${scenario.width}px`, () => {
exerciseEveryGlossaryJourney(scenario);
});
}
});
});
Loading