diff --git a/packages/app/content/blog/zh/b200-glm5-nvfp4-vs-h200-fp8-3-6x-perf-per-dollar.mdx b/packages/app/content/blog/zh/b200-glm5-nvfp4-vs-h200-fp8-3-6x-perf-per-dollar.mdx index ec6db67ae..2034f99e1 100644 --- a/packages/app/content/blog/zh/b200-glm5-nvfp4-vs-h200-fp8-3-6x-perf-per-dollar.mdx +++ b/packages/app/content/blog/zh/b200-glm5-nvfp4-vs-h200-fp8-3-6x-perf-per-dollar.mdx @@ -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 在下面方案中唯一的不同是模型检查点和注意力后端的选择。 ## 详细数据 diff --git a/packages/app/content/blog/zh/deepseekv4-16t-day-0-to-day-43-performance.mdx b/packages/app/content/blog/zh/deepseekv4-16t-day-0-to-day-43-performance.mdx index bceea89d9..d404b5aae 100644 --- a/packages/app/content/blog/zh/deepseekv4-16t-day-0-to-day-43-performance.mdx +++ b/packages/app/content/blog/zh/deepseekv4-16t-day-0-to-day-43-performance.mdx @@ -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)。_ @@ -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)。毫不意外,错误确实消失了。 diff --git a/packages/app/content/blog/zh/gb300-nvl72-vs-gb200-nvl72-dsv4-pro-vllm-fp4.mdx b/packages/app/content/blog/zh/gb300-nvl72-vs-gb200-nvl72-dsv4-pro-vllm-fp4.mdx index 9970710b7..a94028942 100644 --- a/packages/app/content/blog/zh/gb300-nvl72-vs-gb200-nvl72-dsv4-pro-vllm-fp4.mdx +++ b/packages/app/content/blog/zh/gb300-nvl72-vs-gb200-nvl72-dsv4-pro-vllm-fp4.mdx @@ -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 行视为方向性参考。 diff --git a/packages/app/content/blog/zh/inferencex-v2-nvidia-blackwell-vs-amd-vs-hopper.mdx b/packages/app/content/blog/zh/inferencex-v2-nvidia-blackwell-vs-amd-vs-hopper.mdx index e9051378a..48fc4ddb8 100644 --- a/packages/app/content/blog/zh/inferencex-v2-nvidia-blackwell-vs-amd-vs-hopper.mdx +++ b/packages/app/content/blog/zh/inferencex-v2-nvidia-blackwell-vs-amd-vs-hopper.mdx @@ -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。 @@ -519,7 +519,7 @@ AMD 有许多才华横溢的优秀工程师在 ATOM 上工作,我们鼓励 AMD ## 多 Token 预测(MTP) -推测解码通过使用一个小型、低成本的草稿模型提前提议多个 token 来降低自回归生成的成本。大模型然后在一次类似预填充计算的前向传播中验证所提议的 token。对于给定的输入序列长度,当输入多出 N 个 token 时,单次前向传播的耗时大致相同。推测解码利用这一特性,在小模型上运行推理生成多个 token 供主模型在一次前向传播中验证,在相似的时间预算内最多额外产出 N 个 token。 +投机解码通过使用一个小型、低成本的草稿模型提前提议多个 token 来降低自回归生成的成本。大模型然后在一次类似预填充计算的前向传播中验证所提议的 token。对于给定的输入序列长度,当输入多出 N 个 token 时,单次前向传播的耗时大致相同。投机解码利用这一特性,在小模型上运行推理生成多个 token 供主模型在一次前向传播中验证,在相似的时间预算内最多额外产出 N 个 token。
-在大批次下,推理机制相比小批次受内存带宽限制更少。由于推测解码(包括 MTP)的工作原理是用多余的计算换取更少的内存受限解码步骤,推测 token 带来的额外验证工作可能无法恰好利用空闲算力,导致在大批次下的改进幅度较小。 +在大批次下,推理机制相比小批次受内存带宽限制更少。由于投机解码(包括 MTP)的工作原理是用多余的计算换取更少的内存受限解码步骤,投机 token 带来的额外验证工作可能无法恰好利用空闲算力,导致在大批次下的改进幅度较小。 从成本角度来看,MTP 能带来巨大的成本节省。在下表中,我们看到使用 Dynamo TRT 运行 FP4 的 DeepSeek-R1-0528 每百万总 token 成本为 $0.251,但启用 MTP 可将成本大幅降低至每百万总 token 仅 $0.057。 @@ -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 倍的价格降低。 diff --git a/packages/app/cypress/component/blog-shared-chrome.cy.tsx b/packages/app/cypress/component/blog-shared-chrome.cy.tsx new file mode 100644 index 000000000..7210a4225 --- /dev/null +++ b/packages/app/cypress/component/blog-shared-chrome.cy.tsx @@ -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(); + 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', + }); + }); +}); diff --git a/packages/app/cypress/e2e/api-documentation.cy.ts b/packages/app/cypress/e2e/api-documentation.cy.ts index f22f9e9b5..ca11fe531 100644 --- a/packages/app/cypress/e2e/api-documentation.cy.ts +++ b/packages/app/cypress/e2e/api-documentation.cy.ts @@ -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"]') @@ -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, + ); + }); + }); + } }); diff --git a/packages/app/cypress/e2e/content-journeys.cy.ts b/packages/app/cypress/e2e/content-journeys.cy.ts new file mode 100644 index 000000000..3acfdb13e --- /dev/null +++ b/packages/app/cypress/e2e/content-journeys.cy.ts @@ -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); + }); + } + }); +}); diff --git a/packages/app/cypress/e2e/zh-pages.cy.ts b/packages/app/cypress/e2e/zh-pages.cy.ts index 7e114d37c..4340674d0 100644 --- a/packages/app/cypress/e2e/zh-pages.cy.ts +++ b/packages/app/cypress/e2e/zh-pages.cy.ts @@ -122,6 +122,106 @@ describe('Chinese (/zh) pages', () => { }); }); + describe('About pages', () => { + it('keeps English article links on the English click path', () => { + cy.viewport(1440, 900); + cy.visit('/about'); + + cy.get('link[rel="alternate"][hreflang="zh-CN"]').should('exist'); + cy.contains('a', 'InferenceX v1') + .should('have.attr', 'href', '/blog/inferencemax-open-source-inference-benchmarking') + .click(); + cy.location('pathname').should('eq', '/blog/inferencemax-open-source-inference-benchmarking'); + }); + + it('keeps both Chinese article links inside /zh and preserves the FAQ structure', () => { + cy.viewport(390, 844); + cy.visit('/zh/about'); + + cy.get('link[rel="alternate"][hreflang="en"]').should('exist'); + cy.contains('NeoCloud').should('exist'); + cy.contains('dt', '什么是 InferenceX?').should('exist'); + cy.contains('a', 'InferenceX v1') + .should('have.attr', 'href', '/zh/blog/inferencemax-open-source-inference-benchmarking') + .and('not.have.attr', 'hreflang', 'en'); + cy.contains('a', 'InferenceX v2') + .should('have.attr', 'href', '/zh/blog/inferencex-v2-nvidia-blackwell-vs-amd-vs-hopper') + .and('not.have.attr', 'hreflang', 'en') + .click(); + cy.location('pathname').should( + 'eq', + '/zh/blog/inferencex-v2-nvidia-blackwell-vs-amd-vs-hopper', + ); + }); + }); + + describe('Glossary pages', () => { + it('supports search, category filtering, empty recovery, and a Chinese term click path', () => { + cy.viewport(1440, 900); + cy.visit('/zh/glossary'); + + cy.get('link[rel="alternate"][hreflang="en"]').should('exist'); + cy.get('input[placeholder="搜索 MTP、延迟、FP4…"]').as('search').type('MTP'); + cy.get('a[href="/zh/glossary/multi-token-prediction"]').should('be.visible'); + cy.get('@search').clear().type('不存在的术语-xyz'); + cy.contains('h2', '未找到相关术语').should('be.visible'); + cy.contains('button', '显示全部术语').click(); + cy.get('@search').should('have.value', ''); + + cy.contains('button', '智能体推理').click().should('have.attr', 'aria-pressed', 'true'); + cy.get('a[href="/zh/glossary/agentx"]').click(); + cy.location('pathname').should('eq', '/zh/glossary/agentx'); + cy.contains('a', 'AI 推理术语表').click(); + cy.location('pathname').should('eq', '/zh/glossary'); + }); + + for (const width of [375, 390]) { + it(`keeps the Chinese glossary inside the ${width}px viewport`, () => { + cy.viewport(width, 844); + cy.visit('/zh/glossary'); + cy.document().then((document) => { + expect(document.documentElement.scrollWidth).to.be.at.most( + document.documentElement.clientWidth, + ); + }); + cy.get('input[placeholder="搜索 MTP、延迟、FP4…"]').should('be.visible'); + cy.contains('button', '智能体推理').should('be.visible'); + }); + } + }); + + describe('Land acknowledgement pages', () => { + it('renders the English source and its Chinese hreflang at 1440px', () => { + cy.viewport(1440, 900); + cy.visit('/land-acknowledgement'); + cy.contains('h1', 'Indigenous homelands').should('be.visible'); + cy.get('link[rel="alternate"][hreflang="zh-CN"]').should('exist'); + }); + + it('uses the approved Chinese page term and keeps every nation visible on mobile', () => { + cy.viewport(375, 812); + cy.visit('/zh/land-acknowledgement'); + + cy.get('[data-testid="land-acknowledgement-page"]').should( + 'contain.text', + '原住民传统领地声明', + ); + cy.title().should('contain', '原住民传统领地声明'); + cy.get('[data-testid="land-acknowledgement-san-jose"]').should( + 'contain.text', + 'Muwekma Ohlone', + ); + cy.get('[data-testid="land-acknowledgement-los-angeles"]').should('contain.text', 'Tongva'); + cy.get('[data-testid="land-acknowledgement-chicago"]').should('contain.text', 'Potawatomi'); + cy.get('link[rel="alternate"][hreflang="en"]').should('exist'); + cy.document().then((document) => { + expect(document.documentElement.scrollWidth).to.be.at.most( + document.documentElement.clientWidth, + ); + }); + }); + }); + describe('zh blog post page', () => { before(() => { cy.visit('/zh/blog/inferencemax-open-source-inference-benchmarking'); @@ -136,6 +236,23 @@ describe('Chinese (/zh) pages', () => { it('links to the English original', () => { cy.get('a[href="/blog/inferencemax-open-source-inference-benchmarking"]').should('exist'); }); + + it('localizes the table of contents and heading-link controls at mobile widths', () => { + cy.viewport(390, 844); + cy.get('details[aria-label="本页目录"]') + .should('be.visible') + .find('summary') + .should('contain.text', '点击展开') + .and('not.contain.text', 'click to expand'); + cy.get('article.prose a[aria-label="复制本节链接"]') + .first() + .should('have.attr', 'href') + .and('match', /^#/u); + cy.get('article.prose a[aria-label="Copy link to section"]').should('not.exist'); + cy.document().then((doc) => { + expect(doc.documentElement.scrollWidth).to.be.at.most(doc.documentElement.clientWidth); + }); + }); }); describe('zh blog post with math', () => { diff --git a/packages/app/src/app/blog/[slug]/page.tsx b/packages/app/src/app/blog/[slug]/page.tsx index 4fa7a7bc2..e0c28ecc4 100644 --- a/packages/app/src/app/blog/[slug]/page.tsx +++ b/packages/app/src/app/blog/[slug]/page.tsx @@ -92,7 +92,7 @@ export default async function BlogPostPage({ params }: Props) { const breadcrumbJsonLd = buildBlogBreadcrumbJsonLd(slug, meta.title); return ( -
+
diff --git a/packages/app/src/app/glossary/[slug]/page.tsx b/packages/app/src/app/glossary/[slug]/page.tsx index 91722f144..dbe7adf24 100644 --- a/packages/app/src/app/glossary/[slug]/page.tsx +++ b/packages/app/src/app/glossary/[slug]/page.tsx @@ -119,7 +119,7 @@ export default async function GlossaryTermPage({ params }: Props) { }; return ( -
+
diff --git a/packages/app/src/app/zh/about/page.tsx b/packages/app/src/app/zh/about/page.tsx index e34141ffb..29050de0c 100644 --- a/packages/app/src/app/zh/about/page.tsx +++ b/packages/app/src/app/zh/about/page.tsx @@ -43,11 +43,11 @@ export default function AboutPageZh() {

- 开源持续智能体推理基准测试——受万亿美元级吉瓦规模 Token 工厂运营者的信赖 + 持续运行的开源智能体推理基准测试,受到万亿美元级、吉瓦规模 Token 工厂运营方的信赖

- 随着世界以指数级速度迈向 - AGI,软件开发和模型发布日新月异。现有基准测试因其静态性质而迅速过时,参与者往往提交专为基准测试定制的软件镜像,无法反映真实的线上推理性能。 + 随着全球迈向 AGI + 的进程呈指数级加速,软件开发和模型发布也日新月异。静态基准测试很快就会过时;参与者提交的镜像往往专为基准测试打造,无法反映真实环境中的性能。

InferenceX™(原名 @@ -59,19 +59,17 @@ export default function AboutPageZh() { 实验室的容量规划策略团队,以及多家数十亿美元级 NeoCloud。了解更多详情请阅读我们的文章:{' '} - InferenceX v1(英文文章) + InferenceX v1 、{' '} - InferenceX v2(英文文章) + InferenceX v2 。

@@ -84,9 +82,8 @@ export default function AboutPageZh() {

可复现性

- 仪表板上的每一个数据点均来自公开的 GitHub Actions - 工作流运行。测试配方、日志、产物以及数据库记录端到端关联,任何人都可以审计、重新运行或 - fork 基准测试。 + 仪表板上的每个数据点都由公开的 GitHub Actions + 工作流生成。测试配置、日志、产物及其对应的数据库记录彼此关联,任何人都可以核查结果来源、重新运行测试,或基于现有配置创建新的基准测试。

  1. @@ -94,9 +91,9 @@ export default function AboutPageZh() { 1
    - 配方提交至仓库。{' '} - 每种硬件、框架、模型和精度的组合都是一个提交到公开仓库的 shell - 脚本。镜像、命令行和并行度均在源码中固定。 + 测试配置已提交到仓库。{' '} + 每组硬件、框架、模型和精度组合都对应一个提交到公开仓库的 shell + 脚本。脚本中固定了所用镜像、命令行参数和并行度。
  2. @@ -125,10 +122,9 @@ export default function AboutPageZh() { 4
    - 导入仪表板。{' '} - 成功的运行将被加载到数据库中并在此展示。每个图表 tooltip - 都附带一个直接链接,指向生成该数据点的 GitHub Actions - 运行。点击任意数据点即可审计其来源。 + 结果写入仪表板。{' '} + 运行成功后,结果将写入数据库并在仪表板中展示。每个图表的提示框都提供对应的 GitHub + Actions 运行记录链接。点击任意数据点即可核查其来源。
@@ -139,7 +135,7 @@ export default function AboutPageZh() { rel="noopener noreferrer" className="inline-flex items-center gap-1.5 rounded-md border border-border px-3 py-1.5 hover:bg-accent transition-colors" > - 浏览工作流运行 + 查看工作流运行记录 - 查看基准测试配方 + 查看测试配置 +
@@ -135,7 +135,7 @@ export default async function ZhBlogPostPage({ params }: Props) { {headings.length > 0 && (
- +
)}
diff --git a/packages/app/src/app/zh/glossary/[slug]/page.tsx b/packages/app/src/app/zh/glossary/[slug]/page.tsx index 3cc736db0..01337b307 100644 --- a/packages/app/src/app/zh/glossary/[slug]/page.tsx +++ b/packages/app/src/app/zh/glossary/[slug]/page.tsx @@ -123,7 +123,7 @@ export default async function ZhGlossaryTermPage({ params }: Props) { }; return ( -
+
diff --git a/packages/app/src/app/zh/land-acknowledgement/page.tsx b/packages/app/src/app/zh/land-acknowledgement/page.tsx index ddfcffc27..944c78fce 100644 --- a/packages/app/src/app/zh/land-acknowledgement/page.tsx +++ b/packages/app/src/app/zh/land-acknowledgement/page.tsx @@ -7,40 +7,41 @@ import { SITE_URL } from '@semianalysisai/inferencex-constants'; const REGIONAL_ACKNOWLEDGEMENTS_ZH = [ { region: 'San Jose', - peoples: 'Muwekma Ohlone 部落', + peoples: 'Muwekma Ohlone Tribe', acknowledgement: - '我们位于 San Jose 地区的基准测试基础设施运行在旧金山湾区 Muwekma Ohlone 部落未被让渡的祖传家园之上。', + '我们位于 San Jose 地区的基准测试基础设施运行于旧金山湾区 Muwekma Ohlone Tribe 尚未割让的祖居地上。', }, { region: 'Los Angeles', - peoples: 'Tongva、Tataviam、Serrano、Kizh 和 Chumash 族群', + peoples: 'Tongva、Tataviam、Serrano、Kizh 和 Chumash 原住民族', acknowledgement: - '我们位于 Los Angeles 地区的基准测试基础设施运行在 Tongva、Tataviam、Serrano、Kizh 和 Chumash 族群最初居住并至今仍在守护的土地之上。', + '我们位于 Los Angeles 地区的基准测试基础设施运行于 Tongva、Tataviam、Serrano、Kizh 和 Chumash 原住民族最早居住、至今仍在生活并守护的土地上。', }, { region: 'Chicago', - peoples: '三火议会、Illinois 联盟、Miami、Ho-Chunk、Menominee、Fox 和 Sac 族群', + peoples: + 'Council of the Three Fires、Illinois Confederacy、Miami、Ho-Chunk、Menominee、Fox 和 Sac 原住民族', acknowledgement: - '我们位于 Chicago 地区的基准测试基础设施运行在由三火议会(Ojibwe、Odawa 和 Potawatomi 部落)、Illinois 联盟以及包括 Miami、Ho-Chunk、Menominee、Fox 和 Sac 在内的众多原住民族群守护的土地之上。', + '我们位于 Chicago 地区的基准测试基础设施运行于 Council of the Three Fires(Ojibwe、Odawa 和 Potawatomi Nations)、Illinois Confederacy 以及 Miami、Ho-Chunk、Menominee、Fox 和 Sac 等其他原住民族世代守护的土地上。', }, ]; +const LAND_ACKNOWLEDGEMENT_DESCRIPTION = + 'InferenceX 就 San Jose、Los Angeles 和 Chicago 美国基准测试集群所在的原住民族与传统领地所作的声明。'; + export const metadata: Metadata = { - title: '土地致谢', - description: - '对与 InferenceX 美国基准测试集群(San Jose、Los Angeles 和 Chicago)所在土地相关的原住民族群和家园的致谢。', + title: '原住民传统领地声明', + description: LAND_ACKNOWLEDGEMENT_DESCRIPTION, alternates: zhAlternates('/land-acknowledgement'), openGraph: { - title: '土地致谢 | InferenceX', - description: - '对与 InferenceX 美国基准测试集群(San Jose、Los Angeles 和 Chicago)所在土地相关的原住民族群和家园的致谢。', + title: '原住民传统领地声明 | InferenceX', + description: LAND_ACKNOWLEDGEMENT_DESCRIPTION, url: `${SITE_URL}/zh/land-acknowledgement`, locale: ZH_OG_LOCALE, }, twitter: { - title: '土地致谢 | InferenceX', - description: - '对与 InferenceX 美国基准测试集群(San Jose、Los Angeles 和 Chicago)所在土地相关的原住民族群和家园的致谢。', + title: '原住民传统领地声明 | InferenceX', + description: LAND_ACKNOWLEDGEMENT_DESCRIPTION, }, }; @@ -51,22 +52,21 @@ export default function LandAcknowledgementPageZh() {

- 土地致谢 + 原住民传统领地声明

- 我们致敬与我们美国基础设施所在土地相关的原住民家园。 + 我们承认并尊重美国基础设施所在地的原住民传统领地。

- InferenceX 基准测试集群为多个地区提供服务。本页聚焦于我们在美国的 San Jose、Los - Angeles 和 Chicago - 站点,并向世代守护这些土地、至今仍在延续这一使命的原住民族群致以敬意。 + InferenceX 基准测试集群服务多个地区。本页聚焦 San Jose、Los Angeles 和 Chicago + 三个美国站点,向世代守护这些土地并延续至今的原住民族致意。

{REGIONAL_ACKNOWLEDGEMENTS_ZH.map((entry) => (

- 致谢只是一个起点。我们怀着对原住民主权、历史和持续存在的社区的尊重分享这份声明,如果措辞需要改进,欢迎指正。 + 承认这些传统领地只是起点。我们怀着对原住民族主权、历史及延续至今的社群的尊重作出本声明;如有措辞需要改进,欢迎指正。

diff --git a/packages/app/src/components/about/faq-data-zh.ts b/packages/app/src/components/about/faq-data-zh.ts index 8b247b103..bf3e0d088 100644 --- a/packages/app/src/components/about/faq-data-zh.ts +++ b/packages/app/src/components/about/faq-data-zh.ts @@ -19,14 +19,14 @@ export const FAQ_ITEMS_ZH: readonly FaqItem[] = [ { id: 'faq-chips', question: 'InferenceX 测试了哪些芯片?', - answer: '我们会在新加速器可用时持续添加。', + answer: '新加速器可用后,我们会持续将其纳入基准测试。', list: GENERATED_FAQ_DATA.gpuGroups, }, { id: 'faq-models', question: '测试了哪些 AI 模型?', answer: - '各模型会在其已有数据所覆盖的固定序列配置(1k/1k、1k/8k、8k/1k tokens)与多个并发级别下进行测试。具备对应数据的模型还包含 AgentX 长上下文多轮智能体编码运行。', + '各模型会在相应的固定序列配置(1k/1k、1k/8k、8k/1k tokens)和多个并发级别下接受测试;如果已有对应数据,还会运行 AgentX 长上下文多轮智能体编码场景。', list: GENERATED_FAQ_DATA.modelNames, }, { @@ -53,7 +53,7 @@ export const FAQ_ITEMS_ZH: readonly FaqItem[] = [ '每兆瓦 token 吞吐量(tok/s/MW)', 'P99 首 token 延迟(TTFT)', 'AgentX 场景的端到端延迟、token 间延迟(ITL)、输出吞吐量、prefix cache 行为以及会话与 subagent 执行情况', - '每百万 token 成本(总计、输入、输出)——涵盖超大规模云、NeoCoud 和裸机租赁定价', + '每百万 token 成本(总计、输入、输出)——涵盖超大规模云、NeoCloud 和裸机租赁定价', '每 token 能耗(焦耳,总计、输入、输出)', '用户自定义成本和功耗计算', ], @@ -83,25 +83,25 @@ export const FAQ_ITEMS_ZH: readonly FaqItem[] = [ id: 'faq-benchmark-differences', question: 'InferenceX 与其他 AI 基准测试有何不同?', answer: - 'InferenceX 在真实硬件上运行固定序列工作负载与 AgentX 长上下文多轮编码场景。测试配方保存在代码仓库中,每项结果均链接至对应的 GitHub Actions 运行。', + 'InferenceX 在真实硬件上运行固定序列工作负载和 AgentX 长上下文多轮编码场景。测试配置保存在代码仓库中,每项结果都链接到对应的 GitHub Actions 运行记录。', }, { id: 'faq-reproducibility', question: '结果如何实现可复现?', answer: - '仪表板上的每一个数据点均由公开的 GitHub Actions 工作流运行产生。测试配方(模型、框架、精度、并行度、序列长度、并发数)已提交至仓库,在目标硬件上实际执行,产物(日志、指标、芯片追踪数据)上传至运行页面。用户可从任何图表的 tooltip 直接点击链接,跳转到生成该数据点的 GitHub Actions 运行。', + '仪表板上的每个数据点都由公开的 GitHub Actions 工作流生成。测试配置(模型、框架、精度、并行度、序列长度和并发数)保存在代码仓库中,并在对应的目标硬件上运行。日志、指标和芯片追踪数据等产物会上传到运行记录页面。每个图表的提示框都提供链接,可直接打开生成该数据点的 GitHub Actions 运行记录。', }, { id: 'faq-raw-logs', question: '在哪里可以查看原始基准测试日志?', answer: - '在图表上点击任意数据点即可打开 tooltip。其中的"GitHub Actions Run"链接将直接跳转到生成该数据点的工作流运行。在那里您可以查看完整的任务日志、框架和驱动版本、命令行参数,以及下载原始产物(包括请求延迟、token 计数和芯片功耗遥测数据)。', + '点击图表中的任意数据点即可打开提示框。其中的“GitHub Actions 运行记录”链接会直接跳转到生成该数据点的工作流运行。您可以在那里查看完整的任务日志、框架和驱动版本、命令行参数,并下载原始产物,包括请求延迟、token 计数和芯片功耗遥测数据。', }, { id: 'faq-rerun-benchmark', question: '我可以自己重新运行基准测试吗?', answer: - '可以。基准测试配方位于代码仓库的 /benchmarks 目录中,以独立的 shell 脚本形式存在。如果您拥有相同的硬件,可以 fork 仓库并直接运行脚本,或触发相同的 GitHub Actions 工作流来复现结果。', + '可以。基准测试脚本位于代码仓库的 /benchmarks 目录中,可以独立运行。如果您拥有相同的硬件,可以 fork 仓库并直接运行脚本,也可以触发相同的 GitHub Actions 工作流来复现结果。', }, { id: 'faq-old-runs', diff --git a/packages/app/src/components/about/faq-data.test.ts b/packages/app/src/components/about/faq-data.test.ts new file mode 100644 index 000000000..9478d45f1 --- /dev/null +++ b/packages/app/src/components/about/faq-data.test.ts @@ -0,0 +1,86 @@ +import { readFileSync } from 'node:fs'; + +import { describe, expect, it } from 'vitest'; + +import { FAQ_ITEMS_ZH } from './faq-data-zh'; +import { FAQ_ITEMS } from './faq-data'; + +const aboutPageSource = readFileSync( + new URL('../../app/zh/about/page.tsx', import.meta.url), + 'utf8', +); +const normalizedAboutPageSource = aboutPageSource.replaceAll(/\s+/gu, ' '); + +describe('localized About FAQ', () => { + it('keeps every English FAQ item and optional field represented in Chinese', () => { + expect(FAQ_ITEMS_ZH).toHaveLength(FAQ_ITEMS.length); + expect( + FAQ_ITEMS_ZH.map((item) => ({ + hasAnswer: Boolean(item.answer), + listLength: item.list?.length ?? 0, + linkHref: item.link?.href ?? null, + })), + ).toEqual( + FAQ_ITEMS.map((item) => ({ + hasAnswer: Boolean(item.answer), + listLength: item.list?.length ?? 0, + linkHref: item.link?.href ?? null, + })), + ); + }); + + it('spells the NeoCloud pricing category correctly', () => { + const metrics = FAQ_ITEMS_ZH.find((item) => item.question === 'InferenceX 测量哪些指标?'); + expect(metrics?.list?.join('\n')).toContain('NeoCloud'); + expect(metrics?.list?.join('\n')).not.toContain('NeoCoud'); + }); + + it('uses natural Chinese for the About introduction without changing its claims', () => { + expect(aboutPageSource).toContain( + '持续运行的开源智能体推理基准测试,受到万亿美元级、吉瓦规模 Token 工厂运营方的信赖', + ); + expect(aboutPageSource).toContain('参与者提交的镜像往往专为基准测试打造'); + expect(aboutPageSource).not.toContain('开源持续智能体推理基准测试'); + }); + + it('uses natural, interface-consistent Chinese in audited FAQ answers', () => { + const chips = FAQ_ITEMS_ZH.find((item) => item.id === 'faq-chips'); + const models = FAQ_ITEMS_ZH.find((item) => item.id === 'faq-models'); + const logs = FAQ_ITEMS_ZH.find((item) => item.id === 'faq-raw-logs'); + + expect(chips?.answer).toBe('新加速器可用后,我们会持续将其纳入基准测试。'); + expect(models?.answer).toContain('如果已有对应数据,还会运行 AgentX'); + expect(logs?.answer).toContain('“GitHub Actions 运行记录”'); + expect(logs?.answer).not.toContain('tooltip'); + expect(logs?.answer).not.toContain('"GitHub Actions Run"'); + }); + + it('uses formal, natural Chinese for benchmark provenance', () => { + expect(normalizedAboutPageSource).toContain( + '测试配置、日志、产物及其对应的数据库记录彼此关联,任何人都可以核查结果来源、重新运行测试,或基于现有配置创建新的基准测试。', + ); + expect(normalizedAboutPageSource).toContain('测试配置已提交到仓库。'); + expect(normalizedAboutPageSource).toContain('运行成功后,结果将写入数据库并在仪表板中展示。'); + expect(normalizedAboutPageSource).toContain('每个图表的提示框'); + expect(normalizedAboutPageSource).toContain('查看工作流运行记录'); + expect(normalizedAboutPageSource).toContain('查看测试配置'); + expect(normalizedAboutPageSource).not.toContain('成功的运行将被加载到数据库中'); + expect(normalizedAboutPageSource).not.toContain('图表 tooltip'); + }); + + it('uses test configuration and run-record terminology in reproducibility answers', () => { + const differences = FAQ_ITEMS_ZH.find((item) => item.id === 'faq-benchmark-differences'); + const reproducibility = FAQ_ITEMS_ZH.find((item) => item.id === 'faq-reproducibility'); + const rerun = FAQ_ITEMS_ZH.find((item) => item.id === 'faq-rerun-benchmark'); + + expect(differences?.answer).toContain('测试配置保存在代码仓库中'); + expect(differences?.answer).toContain('GitHub Actions 运行记录'); + expect(reproducibility?.answer).toContain( + '测试配置(模型、框架、精度、并行度、序列长度和并发数)', + ); + expect(reproducibility?.answer).toContain('每个图表的提示框都提供链接'); + expect(reproducibility?.answer).not.toContain('tooltip'); + expect(rerun?.answer).toContain('基准测试脚本位于代码仓库的 /benchmarks 目录中'); + expect(rerun?.answer).not.toContain('基准测试配方'); + }); +}); diff --git a/packages/app/src/components/api-documentation/api-reference-page.tsx b/packages/app/src/components/api-documentation/api-reference-page.tsx index 4db1ee4ad..7c670d951 100644 --- a/packages/app/src/components/api-documentation/api-reference-page.tsx +++ b/packages/app/src/components/api-documentation/api-reference-page.tsx @@ -305,7 +305,8 @@ export function ApiReferencePage({ locale }: { locale: ApiDocumentationLocale }) {documentation.schemaNotes.map((schema) => (
{schema.title}
diff --git a/packages/app/src/components/blog/blog-post-card.tsx b/packages/app/src/components/blog/blog-post-card.tsx index 4e3363582..369f38fc5 100644 --- a/packages/app/src/components/blog/blog-post-card.tsx +++ b/packages/app/src/components/blog/blog-post-card.tsx @@ -17,6 +17,8 @@ export function BlogPostCard({ slug, title, basePath = '/blog', children }: Blog return ( track('blog_post_clicked', { slug, title })} > diff --git a/packages/app/src/components/blog/blog-shared-chrome.test.tsx b/packages/app/src/components/blog/blog-shared-chrome.test.tsx new file mode 100644 index 000000000..e7f448989 --- /dev/null +++ b/packages/app/src/components/blog/blog-shared-chrome.test.tsx @@ -0,0 +1,32 @@ +import { renderToStaticMarkup } from 'react-dom/server'; +import { describe, expect, it } from 'vitest'; + +import type { TocHeading } from '@/lib/blog'; +import { BlogToc } from './blog-toc'; +import { HeadingLink } from './heading-link'; + +const headings = [{ id: 'results', text: 'Results', level: 2 }] satisfies TocHeading[]; + +describe('shared Blog localization chrome', () => { + it('preserves the existing English table-of-contents and heading-link copy', () => { + const toc = renderToStaticMarkup(); + const link = renderToStaticMarkup(); + + expect(toc).toContain('aria-label="Table of contents"'); + expect(toc).toContain('On this page'); + expect(toc).toContain('(click to expand)'); + expect(link).toContain('aria-label="Copy link to section"'); + }); + + it('renders the equivalent Chinese controls without English fallback copy', () => { + const toc = renderToStaticMarkup(); + const link = renderToStaticMarkup(); + + expect(toc).toContain('aria-label="本页目录"'); + expect(toc).toContain('(点击展开)'); + expect(toc).not.toContain('Table of contents'); + expect(toc).not.toContain('click to expand'); + expect(link).toContain('aria-label="复制本节链接"'); + expect(link).not.toContain('Copy link to section'); + }); +}); diff --git a/packages/app/src/components/blog/blog-toc.tsx b/packages/app/src/components/blog/blog-toc.tsx index 591b9da3d..98fb76226 100644 --- a/packages/app/src/components/blog/blog-toc.tsx +++ b/packages/app/src/components/blog/blog-toc.tsx @@ -4,11 +4,26 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { track } from '@/lib/analytics'; import type { TocHeading } from '@/lib/blog'; +import type { Locale } from '@/lib/i18n'; + +const STRINGS = { + en: { + defaultLabel: 'On this page', + tableOfContents: 'Table of contents', + clickToExpand: '(click to expand)', + }, + zh: { + defaultLabel: '本页目录', + tableOfContents: '本页目录', + clickToExpand: '(点击展开)', + }, +} as const; interface BlogTocProps { headings: TocHeading[]; /** Heading label, e.g. '本页目录' on Chinese pages. */ label?: string; + locale?: Locale; } function handleClick(heading: TocHeading) { @@ -19,7 +34,9 @@ function handleClick(heading: TocHeading) { window.scrollTo({ top, behavior: 'smooth' }); } -export function BlogToc({ headings, label = 'On this page' }: BlogTocProps) { +export function BlogToc({ headings, label, locale = 'en' }: BlogTocProps) { + const t = STRINGS[locale]; + const displayLabel = label ?? t.defaultLabel; const [activeId, setActiveId] = useState(''); const [showSidebar, setShowSidebar] = useState(false); const observerRef = useRef(null); @@ -141,9 +158,10 @@ export function BlogToc({ headings, label = 'On this page' }: BlogTocProps) { <> {/* Inline: when sidebar doesn't fit */} {!showSidebar && ( -
+
- {label} (click to expand) + {displayLabel}{' '} + {t.clickToExpand}
{list}
@@ -160,9 +178,9 @@ export function BlogToc({ headings, label = 'On this page' }: BlogTocProps) { top: Math.max(32, sectionTopRef.current - window.scrollY), scrollbarWidth: 'none', }} - aria-label="Table of contents" + aria-label={t.tableOfContents} > -

{label}

+

{displayLabel}

{list} , document.body, diff --git a/packages/app/src/components/blog/heading-link.tsx b/packages/app/src/components/blog/heading-link.tsx index c72296e14..662fe8628 100644 --- a/packages/app/src/components/blog/heading-link.tsx +++ b/packages/app/src/components/blog/heading-link.tsx @@ -3,8 +3,15 @@ import { useCallback, useRef, useState } from 'react'; import { LinkIcon } from 'lucide-react'; import { track } from '@/lib/analytics'; +import type { Locale } from '@/lib/i18n'; -export function HeadingLink({ id }: { id: string }) { +const STRINGS = { + en: { copy: 'Copy link to section', copied: 'Link copied' }, + zh: { copy: '复制本节链接', copied: '链接已复制' }, +} as const; + +export function HeadingLink({ id, locale = 'en' }: { id: string; locale?: Locale }) { + const t = STRINGS[locale]; const [state, setState] = useState<'idle' | 'copied' | 'fading'>('idle'); const timerRef = useRef>(undefined); @@ -12,10 +19,11 @@ export function HeadingLink({ id }: { id: string }) { (e: React.MouseEvent) => { e.preventDefault(); clearTimeout(timerRef.current); + track('blog_heading_link_clicked', { id, locale }); const url = `${window.location.origin}${window.location.pathname}#${id}`; navigator.clipboard.writeText(url).then( () => { - track('blog_heading_link_copied', { id }); + track('blog_heading_link_copied', { id, locale }); setState('copied'); timerRef.current = setTimeout(() => { setState('fading'); @@ -27,7 +35,7 @@ export function HeadingLink({ id }: { id: string }) { }, ); }, - [id], + [id, locale], ); const visible = state !== 'idle'; @@ -36,13 +44,13 @@ export function HeadingLink({ id }: { id: string }) { {state === 'idle' ? ( ) : ( - Link copied + {t.copied} )} ); diff --git a/packages/app/src/components/blog/mdx-components.tsx b/packages/app/src/components/blog/mdx-components.tsx index b684c78e4..e422ed871 100644 --- a/packages/app/src/components/blog/mdx-components.tsx +++ b/packages/app/src/components/blog/mdx-components.tsx @@ -105,7 +105,7 @@ export function createMdxComponents( return (

{props.children} - +

); }, @@ -114,7 +114,7 @@ export function createMdxComponents( return (

{props.children} - +

); }, @@ -123,7 +123,7 @@ export function createMdxComponents( return (

{props.children} - +

); }, diff --git a/packages/app/src/components/glossary/glossary-browser.tsx b/packages/app/src/components/glossary/glossary-browser.tsx index 72f6a2624..dcaede952 100644 --- a/packages/app/src/components/glossary/glossary-browser.tsx +++ b/packages/app/src/components/glossary/glossary-browser.tsx @@ -252,6 +252,8 @@ export function GlossaryBrowser({
diff --git a/packages/app/src/lib/api-documentation.test.ts b/packages/app/src/lib/api-documentation.test.ts new file mode 100644 index 000000000..89c5c50d9 --- /dev/null +++ b/packages/app/src/lib/api-documentation.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from 'vitest'; + +import { getApiDocumentation } from './api-documentation'; + +describe('Chinese API documentation copy', () => { + it('uses the established evaluation and speculative-decoding terminology', () => { + const documentation = getApiDocumentation('zh'); + const availability = documentation.groups + .flatMap((group) => group.operations) + .find((operation) => operation.id === 'get-availability'); + const evaluations = documentation.groups + .flatMap((group) => group.operations) + .find((operation) => operation.id === 'list-evaluations'); + + expect(availability?.description).toContain('投机解码方式'); + expect(evaluations?.summary).toContain('评估'); + expect(evaluations?.description).toContain('评估结果'); + }); +}); diff --git a/packages/app/src/lib/api-documentation.ts b/packages/app/src/lib/api-documentation.ts index 4a22d16c5..470ad3233 100644 --- a/packages/app/src/lib/api-documentation.ts +++ b/packages/app/src/lib/api-documentation.ts @@ -506,7 +506,7 @@ export const apiDocumentationGroups: readonly ApiDocumentationGroup[] = [ title: text('Core benchmark data', '核心基准数据'), description: text( 'Benchmark results, availability, workflow provenance, evaluations, and reliability.', - '基准结果、可用配置、工作流来源、评测与可靠性数据。', + '基准结果、可用配置、工作流来源、评估与可靠性数据。', ), }, { @@ -552,7 +552,7 @@ export const apiOperations: readonly ApiOperation[] = [ summary: text('List available benchmark configurations', '列出可用的基准配置'), description: text( 'Returns model, sequence, precision, hardware, framework, speculative method, benchmark type, and date combinations that have benchmark data.', - '返回已有基准数据的模型、序列、精度、硬件、框架、推测方法、基准类型和日期组合。', + '返回已有基准数据的模型、序列、精度、硬件、框架、投机解码方式、基准类型和日期组合。', ), audience: 'public', stability: 'stable', @@ -829,16 +829,16 @@ export const apiOperations: readonly ApiOperation[] = [ group: 'core', method: 'GET', path: '/api/v1/evaluations', - summary: text('List evaluation aggregates', '列出评测汇总'), + summary: text('List evaluation aggregates', '列出评估汇总'), description: text( 'Returns latest-attempt evaluation results with configuration, task, provenance, and metric values.', - '返回最新尝试的评测结果,包含配置、任务、来源和指标值。', + '返回最新尝试的评估结果,包含配置、任务、来源和指标值。', ), audience: 'public', stability: 'stable', parameters: [], responses: [ - success('Evaluation result rows.', '评测结果行。', evaluationsSchema, [ + success('Evaluation result rows.', '评估结果行。', evaluationsSchema, [ { id: 72, config_id: 11, @@ -870,7 +870,7 @@ export const apiOperations: readonly ApiOperation[] = [ errorResponse( '500', 'The evaluation query failed.', - '评测查询失败。', + '评估查询失败。', 'Internal server error', ), ], diff --git a/packages/app/src/lib/blog-content.test.ts b/packages/app/src/lib/blog-content.test.ts index 8b660d5c7..359c7bbd4 100644 --- a/packages/app/src/lib/blog-content.test.ts +++ b/packages/app/src/lib/blog-content.test.ts @@ -21,6 +21,10 @@ const enFiles = fs .readdirSync(CONTENT_DIR) .filter((f) => f.endsWith('.mdx')) .toSorted(); +const zhFiles = fs + .readdirSync(ZH_DIR) + .filter((f) => f.endsWith('.mdx')) + .toSorted(); const read = (file: string) => fs.readFileSync(file, 'utf8'); @@ -33,17 +37,28 @@ function frontmatterField(raw: string, field: string): string | null { function tagList(raw: string): string[] { const fm = raw.split('---')[1] ?? ''; - const after = fm.split(/^tags:\s*$/mu)[1]; - if (!after) return []; - const tags: string[] = []; - for (const line of after.split('\n')) { - const item = /^\s+-\s+(?.*)$/u.exec(line); - if (!item) break; - tags.push(item.groups!.tag.trim()); - } - return tags; + const match = /^tags:\s*\n(?(?:[ \t]+-[^\n]*(?:\n|$))+)/mu.exec(fm); + if (!match?.groups) return []; + return [...match.groups.items.matchAll(/^\s+-\s+(?.*)$/gmu)].map((item) => + item.groups!.tag.trim(), + ); } +describe('frontmatter tag parsing', () => { + it('reads every item from a multiline tags field before the next frontmatter key', () => { + const frontmatter = `--- +title: Example +tags: + - benchmark + - cann +publishDate: '2026-08-23' +--- +`; + + expect(tagList(frontmatter)).toEqual(['benchmark', 'cann']); + }); +}); + /** Local `
` and markdown `![alt](/images/...)` references. */ function localImageRefs(raw: string): string[] { return [ @@ -55,8 +70,18 @@ function localImageRefs(raw: string): string[] { const countFigures = (raw: string) => (raw.match(/ (raw.match(/^\$\$\s*$/gmu) ?? []).length; -it('finds English posts to check', () => { - expect(enFiles.length).toBeGreaterThan(0); +function inlineCodeSpans(raw: string): string[] { + const withoutStructuredContent = raw + .replaceAll(/\{`[\s\S]*?`\}<\/JsonLd>/gu, '') + .replaceAll(/```[\s\S]*?```/gu, ''); + return [...withoutStructuredContent.matchAll(/(?[^`\n]+)`(?!`)/gu)].map( + (match) => match.groups!.code, + ); +} + +it('checks all 31 current locale pairs in both directions', () => { + expect(enFiles).toHaveLength(31); + expect(zhFiles).toEqual(enFiles); }); describe.each(enFiles)('%s', (file) => { @@ -102,3 +127,27 @@ describe.each(enFiles)('%s', (file) => { } }); }); + +it('preserves protected inline code in the audited Chinese article passages', () => { + const auditedSpans = [ + { + file: 'deepseekv4-16t-day-0-to-day-43-performance.mdx', + code: '“mhcFusedHcLaunch: hidden_size=7168 not supported (only 4096)”', + }, + { + file: 'gb300-nvl72-vs-gb200-nvl72-dsv4-pro-vllm-fp4.mdx', + code: 'conc=3072, 28 GPU prefill, 32 GPU decode EP=16, 6,812 tok/s/GPU at 25.9 tok/s/user', + }, + ] as const; + + for (const { file, code } of auditedSpans) { + expect( + inlineCodeSpans(read(path.join(CONTENT_DIR, file))), + `${file}: English source`, + ).toContain(code); + expect( + inlineCodeSpans(read(path.join(ZH_DIR, file))), + `${file}: Chinese translation`, + ).toContain(code); + } +}); diff --git a/packages/app/src/lib/glossary-zh.ts b/packages/app/src/lib/glossary-zh.ts index cbbb159ad..2f689d413 100644 --- a/packages/app/src/lib/glossary-zh.ts +++ b/packages/app/src/lib/glossary-zh.ts @@ -36,7 +36,7 @@ const translations: Readonly> = { definition: 'AI 推理是使用已经训练好的模型处理新输入并生成输出的过程;对大语言模型而言,通常就是处理提示词并生成 token。', explanation: - '训练阶段会更新模型权重,推理阶段则使用这些权重。生产系统还需要推理引擎负责调度请求、管理内存、合并批次,并在一个或多个加速器上执行内核。相同模型在不同软硬件栈上的表现可能相差数倍。', + '训练阶段会更新模型权重,推理阶段则使用这些权重。生产系统还需要推理引擎负责调度请求、管理内存、合并批次,并在一个或多个加速器上执行内核。周边软硬件栈不同,性能也会随之变化。', significance: '推理既是模型问题,也是系统问题。用户体验取决于延迟和交互性,运营成本则取决于吞吐量、利用率、功耗与硬件成本;只优化其中一个维度,往往会牺牲另一个维度。', benchmarkContext: @@ -105,11 +105,11 @@ const translations: Readonly> = { definition: '闭环基准测试中的客户端会在前一个依赖请求完成后生成新工作,同时遵循工作负载记录的等待时间和分支结构。', explanation: - 'Concurrency 表示活跃客户端或会话数量,同时存在的请求数会随时间变化。更快的系统更早完成轮次,因此会在同一个 profiling 窗口内发出更多请求。每条采样会话的推进速度取决于请求完成时间,实际请求组合可能有小幅变化。', + '并发量表示活跃客户端或会话的数量,同时在途的请求数会随时间变化。系统越快,完成轮次越早,因此在同一个 profiling 窗口内发出的请求也越多。由于每条采样会话的推进取决于请求完成时间,实际请求组合可能略有变化。', significance: '这种负载模型符合交互式 agent 的运行方式,因为下一步动作依赖上一步结果。响应更快时,会话也会更快地产生后续工作,所以吞吐量与延迟相互关联。低并发运行的采样波动通常会比大型请求池更明显。', benchmarkContext: - 'AgentX 使用闭环 concurrency。该数值表示同时运行的 agent 客户端数量;request batch 会随着会话推进而变化。解读结果时需要结合吞吐量、首 token 延迟与交互性。', + 'AgentX 采用闭环并发。并发量表示同时运行的 agent 客户端数量;随着会话推进,请求批次大小会不断变化。解读结果时,应结合吞吐量、首 token 延迟和交互性。', }, subagent: { term: '子智能体', @@ -221,7 +221,7 @@ const translations: Readonly> = { '批处理就像让多名乘客坐同一辆巴士:芯片一次处理多个请求,让每趟计算完成更多有效工作。', definition: '批处理将多个请求的工作组合起来,使加速器能够一起处理它们的 token。', explanation: - '大型矩阵运算比大量微小运算更能发挥芯片效率。现代推理引擎采用连续批处理,请求到达和结束时动态加入或退出,无需等待固定批次全部完成。', + '大型矩阵运算比大量微小运算更能发挥芯片效率。现代推理引擎采用连续批处理,请求到达和结束时动态加入或退出,无需等待固定批次全部完成。由此形成的批次形状会在预填充和解码过程中不断变化。', significance: '批处理是吞吐量与延迟核心权衡的来源。更大的有效批次能摊薄权重读取和内核启动开销,但通常会增加每位用户的 token 间隔。', benchmarkContext: @@ -274,7 +274,7 @@ const translations: Readonly> = { explanation: 'InferenceX 根据每小时总体拥有成本和实测 token 吞吐量计算该指标。它可能按总 token 报告,也可能区分输入和输出 token,因此比较前必须确认分母。', significance: - '该指标把系统性能转化为服务经济性,但仍受工作负载、交互性、利用率、缓存命中和成本假设影响;离线低交互点不能直接与实时端点比较。', + '该指标把系统性能转化为服务经济性,但仍受工作负载、交互性、利用率、缓存命中和成本假设影响;低吞吐量的离线运行点与高交互性实时端点属于不同的运行区间,不能直接比较。', benchmarkContext: '成本曲线使用与吞吐曲线相同的并发扫描。在等交互性下,更低的 $/M 表示以更少建模成本提供相同流式体验。', measurement: { @@ -304,7 +304,7 @@ const translations: Readonly> = { significance: 'TCO 比标价更适合跨系统经济性比较,尤其是网络与电力基础设施不同的机架级产品;但它仍是模型,必须连同假设一起阅读。', benchmarkContext: - 'InferenceX 将 SemiAnalysis AI Cloud TCO 输入与实测 tok/s/chip 结合,从而区分每小时系统成本和决定该小时 token 产出的软硬件行为。', + 'InferenceX 将 SemiAnalysis AI Cloud 的 TCO 输入与实测 tok/s/chip 结合,从而把系统每小时成本与决定这一小时 token 产出的软件实现及工作负载特征分开考察。', }, 'tokens-per-megawatt': { term: '每兆瓦 token 吞吐量', @@ -339,7 +339,7 @@ const translations: Readonly> = { explanation: '每个新 token 都依赖此前 token,因此时间维度无法完全并行。模型会反复读取权重与该序列的 KV 缓存,使解码对内存带宽、批处理和通信尤其敏感。', significance: - '解码决定流式交互性,也常主导长输出成本。推测解码、MTP、量化和宽专家并行都试图减少每个有效 token 的工作量或耗时。', + '解码决定流式交互性,也常主导长输出成本。投机解码、MTP、量化和宽专家并行都试图减少每个有效 token 的工作量或耗时。', benchmarkContext: 'InferenceX 用 tok/s/user 与总 tok/s/chip 展示不同并发下的解码性能。公平比较必须匹配输出长度、批形状、精度和并行策略。', }, @@ -382,25 +382,25 @@ const translations: Readonly> = { 'InferenceX 中的 disagg 不是万能开关。应查看预填充/解码 world size、TP/EP 布局、框架、网络域,以及分离前沿真正领先的交互性区间。', }, 'speculative-decoding': { - term: '推测解码', + term: '投机解码', aliases: ['speculative decoding', '草稿与验证解码'], plainEnglish: - '推测解码让一个便宜的助手先起草多个 token,再由完整模型一次性审核,省去部分逐个生成步骤。', + '投机解码让一个便宜的助手先起草多个 token,再由完整模型一次性审核,省去部分逐个生成步骤。', definition: - '推测解码先以低成本提出多个未来 token,再由目标模型批量验证,从而减少昂贵的串行解码步数。', + '投机解码先以低成本提出多个未来 token,再由目标模型批量验证,从而减少昂贵的串行解码步数。', explanation: '草稿模型或内置预测头生成候选,目标模型在一次批量验证中评估这些候选并接受有效前缀;严格实现时不会改变目标分布。', significance: '加速取决于草稿 token 的接受数量,以及草稿与验证成本。稠密模型和 MoE 的表现可能不同,因为验证多个位置可能激活更多专家权重。', benchmarkContext: - '应在真实接受率下比较投机解码方案并验证模型质量。定长场景仍把投机解码作为曲线标识的一部分,因此开启和关闭 MTP 的方案会分开绘制;agentic 曲线则把它当作数据点级元数据并合并这些点,在 tooltip 中标明具体方式,因为 AgentX 按模型、芯片 SKU 和引擎给出可获得的最佳曲线。由于 AgentX 回放的内容是合成的,speculator 接受的 draft token 数会失真,因此运行时会套用一套按模型、speculator、draft 长度和思考模式在外部 agentic 编码数据集上采集的接受长度。', + '应在真实接受率下比较投机解码方案并验证模型质量。定长场景仍把投机解码作为曲线标识的一部分,因此开启和关闭 MTP 的方案会分开绘制;agentic 曲线则把它当作数据点级元数据并合并这些点,在提示框中标明具体方式,因为 AgentX 按模型、芯片 SKU 和引擎给出可获得的最佳曲线。由于 AgentX 回放的内容是合成的,speculator 接受的 draft token 数会失真,因此运行时会套用一套按模型、speculator、draft 长度和思考模式在外部 agentic 编码数据集上采集的接受长度。', }, 'multi-token-prediction': { term: '多 token 预测', aliases: ['multi-token prediction', '多 token 预测头'], plainEnglish: 'MTP 让模型一次猜测多个后续 token 并一起验证,从而减少缓慢的逐 token 步骤。', definition: - '多 token 预测(MTP)使用与主模型共同训练的辅助预测头,提出多个未来 token 供推测验证。', + '多 token 预测(MTP)使用与主模型共同训练的辅助预测头,提出多个未来 token 供投机验证。', explanation: 'MTP 不需要独立草稿模型,候选来自目标模型自身表示,因此分布更一致、部署也更简单;但它要求检查点包含兼容 MTP 模块,且推理引擎支持验证路径。', significance: @@ -410,12 +410,12 @@ const translations: Readonly> = { }, eagle: { term: 'EAGLE', - aliases: ['EAGLE 推测解码', 'EAGLE-3'], + aliases: ['EAGLE 投机解码', 'EAGLE-3'], plainEnglish: 'EAGLE 是一种为主模型起草多个可能后续 token 的方法,可让答案流式输出得更快。', definition: - 'EAGLE 是一组推测解码方法:利用与目标语言模型相关的特征预测草稿序列,再由目标模型验证。', + 'EAGLE 是一组投机解码方法:利用与目标语言模型相关的特征预测草稿序列,再由目标模型验证。', explanation: - '推理框架通常通过推测步数、草稿 token 数和候选宽度等参数暴露 EAGLE。模型检查点、草稿组件与引擎实现必须匹配。', + '推理框架通常通过投机步数、草稿 token 数和候选宽度等参数暴露 EAGLE。模型检查点、草稿组件与引擎实现必须匹配。', significance: 'EAGLE 能提高每个目标模型步接受的 token 数,但结果依赖工作负载;接受行为、草稿开销、模型架构和批大小共同决定端到端收益。', benchmarkContext: @@ -690,11 +690,11 @@ const translations: Readonly> = { 'SGLang 是用于快速服务语言模型的开源软件,提供面向复杂 AI 工作负载的调度和优化功能。', definition: 'SGLang 是面向高性能 LLM 与多模态推理的开源服务引擎和语言模型编程系统。', explanation: - '服务运行时包含连续批处理、前缀感知调度、分布式并行、推测解码,以及面向 NVIDIA/AMD 芯片的多种注意力和 MoE 内核后端。', + '服务运行时包含连续批处理、前缀感知调度、分布式并行、投机解码,以及面向 NVIDIA/AMD 芯片的多种注意力和 MoE 内核后端。', significance: 'SGLang 快速迭代的版本和模型专用内核可在硬件不变时显著改变吞吐量;低并发受调度开销影响,其他区间则由注意力、MoE 与通信内核主导。', benchmarkContext: - 'InferenceX 持续重跑固定版本的 SGLang 方案。跨版本曲线会保留改动对完整性能区间的影响。', + 'InferenceX 持续重跑版本固定的 SGLang 方案。对比不同版本的曲线,可以看出改动如何影响完整运行区间,并发现单个峰值点掩盖的回归或提升。', }, 'tensorrt-llm': { term: 'TensorRT-LLM', @@ -779,7 +779,7 @@ const translations: Readonly> = { significance: '张量并行会在每个 rank 上复制完整 KV cache,数据并行注意力则把会话绑定在持有其分片的 rank 上,两者在上下文达到几十万 token 时都难以扩展。上下文并行直接针对这一点,而且收益随输入长度增长,而不是随 batch 大小增长。', benchmarkContext: - 'InferenceX 在数据点 tooltip 和并行标签中与 TP、EP、DP 一起展示 DCP 与 PCP 的并行度。各厂商支持程度并不均衡:在 AgentX 1.0 结果发布时,vLLM 支持矩阵中 AMD 的注意力后端仍标为不支持,因此该技术仍构成 CUDA 实际优势的一部分。', + 'InferenceX 在数据点提示框和并行标签中与 TP、EP、DP 一起展示 DCP 与 PCP 的并行度。各厂商支持程度并不均衡:在 AgentX 1.0 结果发布时,vLLM 支持矩阵中 AMD 的注意力后端仍标为不支持,因此该技术仍构成 CUDA 实际优势的一部分。', }, 'kv-cache-offload': { term: 'KV cache offload', @@ -847,7 +847,7 @@ const translations: Readonly> = { significance: '芯片峰值规格无法描述服务性能,同一颗芯片在不同配置下可以相差数倍。把整套组合写清楚,结论才可核查:脱离配置的单个数字既无法复现,也无法与其他厂商公平比较。', benchmarkContext: - 'InferenceX 的测试配置主要跟随 vLLM 与 SGLang 官方 cookbook,并使用上游镜像,因此结果反映用户实际能部署的性能,而不是为基准测试特调过的镜像。数据点 tooltip 会展示背后的配置,并给出运行溯源链接。', + 'InferenceX 的测试配置主要跟随 vLLM 与 SGLang 官方 cookbook,并使用上游镜像,因此结果反映用户实际能部署的性能,而不是为基准测试特调过的镜像。数据点的提示框会展示背后的配置,并给出运行溯源链接。', }, 'tail-latency': { term: '尾部延迟', @@ -991,7 +991,7 @@ const translations: Readonly> = { significance: '对最大的那批模型来说,这首先是容量手段,其次才是提速手段。有些前沿模型根本装不进单个节点,流水线并行才让它们可服务;当某项竞争性优化拒绝与任何方案组合时,它甚至是唯一选项。', benchmarkContext: - 'InferenceX 在数据点 tooltip 和并行标签中与 TP、EP、DP 一起展示流水线并行度,且仅在大于 1 时显示。可组合性与并行度同样重要:一种会导致投机解码无法启用的阶段切分,代价可能超过它节省的显存。', + 'InferenceX 在数据点提示框和并行标签中与 TP、EP、DP 一起展示流水线并行度,且仅在大于 1 时显示。可组合性与并行度同样重要:一种会导致投机解码无法启用的阶段切分,代价可能超过它节省的显存。', }, 'dp-attention': { term: '数据并行注意力', @@ -1005,7 +1005,7 @@ const translations: Readonly> = { significance: '由于每个 rank 只拥有缓存池的私有一份,请求落在哪里就成了影响性能的关键:长会话一旦被路由到不持有其前缀的 rank,就要全部重算。此时实测命中率会远低于理论上限,而原因与缓存大小毫无关系。', benchmarkContext: - 'InferenceX 会在数据点 tooltip 的并行策略部分展示 DP attention。它是否有利取决于模型;当缓存局部性变成路由约束时,不启用它的配置有时反而占据前沿曲线。', + 'InferenceX 会在数据点提示框的并行策略部分展示 DP attention。它是否有利取决于模型;当缓存局部性变成路由约束时,不启用它的配置有时反而占据前沿曲线。', }, int4: { term: 'INT4', diff --git a/packages/app/src/lib/glossary.test.ts b/packages/app/src/lib/glossary.test.ts index e524c5805..4e5903941 100644 --- a/packages/app/src/lib/glossary.test.ts +++ b/packages/app/src/lib/glossary.test.ts @@ -123,6 +123,7 @@ describe('Chinese glossary content', () => { expect(getZhGlossaryEntry('trace-replay')?.term).toBe('轨迹回放'); expect(getZhGlossaryEntry('closed-loop-benchmark')?.term).toBe('闭环基准测试'); expect(getZhGlossaryEntry('subagent')?.term).toBe('子智能体'); + expect(getZhGlossaryEntry('speculative-decoding')?.term).toBe('投机解码'); expect(getZhGlossaryEntry('multi-token-prediction')?.term).toBe('多 token 预测'); expect(getZhGlossaryEntry('not-a-real-term')).toBeUndefined(); @@ -134,6 +135,45 @@ describe('Chinese glossary content', () => { }); } }); + + it('preserves the source scope in audited Chinese definitions', () => { + expect(getZhGlossaryEntry('ai-inference')?.explanation).not.toContain('相差数倍'); + expect(getZhGlossaryEntry('cost-per-million-tokens')?.significance).toContain('低吞吐量'); + expect(getZhGlossaryEntry('total-cost-of-ownership')?.benchmarkContext).toContain( + '软件实现及工作负载特征', + ); + expect(getZhGlossaryEntry('total-cost-of-ownership')?.benchmarkContext).not.toContain( + '软硬件行为', + ); + expect(getZhGlossaryEntry('sglang')?.benchmarkContext).toContain('回归或提升'); + }); + + it('explains closed-loop concurrency in natural Chinese', () => { + const entry = getZhGlossaryEntry('closed-loop-benchmark'); + + expect(entry?.explanation).toContain('并发量表示活跃客户端或会话的数量'); + expect(entry?.benchmarkContext).toContain('AgentX 采用闭环并发'); + expect(entry?.benchmarkContext).toContain('请求批次大小会不断变化'); + expect(entry?.explanation).not.toContain('Concurrency'); + expect(entry?.benchmarkContext).not.toContain('concurrency'); + expect(entry?.benchmarkContext).not.toContain('request batch'); + }); + + it('uses the established Chinese term for point tooltips in audited entries', () => { + const slugs = [ + 'speculative-decoding', + 'context-parallelism', + 'recipe', + 'pipeline-parallelism', + 'dp-attention', + ]; + + for (const slug of slugs) { + const benchmarkContext = getZhGlossaryEntry(slug)?.benchmarkContext; + expect(benchmarkContext, slug).toContain('提示框'); + expect(benchmarkContext, slug).not.toContain('tooltip'); + } + }); }); describe('glossary navigation', () => { diff --git a/packages/app/src/lib/land-acknowledgement-content.test.ts b/packages/app/src/lib/land-acknowledgement-content.test.ts new file mode 100644 index 000000000..19f807e7f --- /dev/null +++ b/packages/app/src/lib/land-acknowledgement-content.test.ts @@ -0,0 +1,30 @@ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +import { describe, expect, it } from 'vitest'; + +const englishPage = readFileSync( + resolve(import.meta.dirname, '../app/land-acknowledgement/page.tsx'), + 'utf8', +); +const chinesePage = readFileSync( + resolve(import.meta.dirname, '../app/zh/land-acknowledgement/page.tsx'), + 'utf8', +); + +describe('Land Acknowledgement translation fidelity', () => { + it('keeps all three infrastructure relationships operational rather than constructional', () => { + const englishOperationalRelationships = + englishPage.match(/benchmark infrastructure operates on/gu) ?? []; + const chineseAcknowledgements = [ + ...chinesePage.matchAll(/acknowledgement:\s*\n\s*'(?[^']+)'/gu), + ].map((match) => match.groups!.copy); + + expect(englishOperationalRelationships).toHaveLength(3); + expect(chineseAcknowledgements).toHaveLength(englishOperationalRelationships.length); + for (const acknowledgement of chineseAcknowledgements) { + expect(acknowledgement).toContain('基准测试基础设施运行于'); + expect(acknowledgement).not.toContain('建在'); + } + }); +}); diff --git a/packages/app/timings.json b/packages/app/timings.json index 8d86f0979..87b70c1f4 100644 --- a/packages/app/timings.json +++ b/packages/app/timings.json @@ -84,6 +84,10 @@ "spec": "cypress/e2e/compare-table.cy.ts", "duration": 1751 }, + { + "spec": "cypress/e2e/content-journeys.cy.ts", + "duration": 150000 + }, { "spec": "cypress/e2e/csv-export-overlay.cy.ts", "duration": 1213