From 6c5da4625e55812652c9eef78b9987a318b95bec Mon Sep 17 00:00:00 2001 From: Wenyao Gao Date: Sun, 23 Aug 2026 05:41:30 -0700 Subject: [PATCH 01/10] fix(zh): polish explanatory content pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct localized routes and terminology, preserve source scope, contain API schema overflow on mobile, and add focused desktop/mobile regression coverage. 中文:修正本地化路由与术语,保持英文原文的语义范围,解决 API 数据结构在移动端的横向溢出,并补齐桌面端与移动端回归覆盖。 --- .../app/cypress/e2e/api-documentation.cy.ts | 23 ++++ packages/app/cypress/e2e/zh-pages.cy.ts | 100 ++++++++++++++++++ packages/app/src/app/zh/about/page.tsx | 10 +- .../src/app/zh/land-acknowledgement/page.tsx | 44 ++++---- .../app/src/components/about/faq-data-zh.ts | 2 +- .../app/src/components/about/faq-data.test.ts | 29 +++++ .../api-documentation/api-reference-page.tsx | 2 +- .../app/src/lib/api-documentation.test.ts | 19 ++++ packages/app/src/lib/api-documentation.ts | 12 +-- packages/app/src/lib/glossary-zh.ts | 26 ++--- packages/app/src/lib/glossary.test.ts | 7 ++ 11 files changed, 225 insertions(+), 49 deletions(-) create mode 100644 packages/app/src/components/about/faq-data.test.ts create mode 100644 packages/app/src/lib/api-documentation.test.ts diff --git a/packages/app/cypress/e2e/api-documentation.cy.ts b/packages/app/cypress/e2e/api-documentation.cy.ts index f22f9e9b5..a646f015a 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,26 @@ describe('API documentation', () => { .should('have.attr', 'href', '/zh/api') .and('have.text', 'API 文档'); }); + + for (const [path, width] of [ + ['/api', 375], + ['/zh/api', 390], + ] as const) { + it(`keeps wide API data reachable without page overflow at ${path} on ${width}px`, () => { + cy.viewport(width, 844); + cy.visit(path); + cy.get('[data-testid="api-endpoint-list-benchmarks"] summary').click(); + cy.get('[data-testid="api-endpoint-list-benchmarks"] .overflow-x-auto') + .first() + .should('be.visible') + .then(($scroller) => { + expect($scroller[0].scrollWidth).to.be.greaterThan($scroller[0].clientWidth); + }); + cy.document().then((document) => { + expect(document.documentElement.scrollWidth).to.be.at.most( + document.documentElement.clientWidth, + ); + }); + }); + } }); diff --git a/packages/app/cypress/e2e/zh-pages.cy.ts b/packages/app/cypress/e2e/zh-pages.cy.ts index 7e114d37c..ecb6827c3 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'); diff --git a/packages/app/src/app/zh/about/page.tsx b/packages/app/src/app/zh/about/page.tsx index e34141ffb..6213e8b72 100644 --- a/packages/app/src/app/zh/about/page.tsx +++ b/packages/app/src/app/zh/about/page.tsx @@ -59,19 +59,17 @@ export default function AboutPageZh() { 实验室的容量规划策略团队,以及多家数十亿美元级 NeoCloud。了解更多详情请阅读我们的文章:{' '} - InferenceX v1(英文文章) + InferenceX v1 、{' '} - InferenceX v2(英文文章) + InferenceX v2 。

diff --git a/packages/app/src/app/zh/land-acknowledgement/page.tsx b/packages/app/src/app/zh/land-acknowledgement/page.tsx index ddfcffc27..5d3acc9eb 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..616d9b141 100644 --- a/packages/app/src/components/about/faq-data-zh.ts +++ b/packages/app/src/components/about/faq-data-zh.ts @@ -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 能耗(焦耳,总计、输入、输出)', '用户自定义成本和功耗计算', ], 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..68de50116 --- /dev/null +++ b/packages/app/src/components/about/faq-data.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from 'vitest'; + +import { FAQ_ITEMS_ZH } from './faq-data-zh'; +import { FAQ_ITEMS } from './faq-data'; + +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'); + }); +}); 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..9c8773713 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,7 @@ export function ApiReferencePage({ locale }: { locale: ApiDocumentationLocale }) {documentation.schemaNotes.map((schema) => (
{schema.title}
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/glossary-zh.ts b/packages/app/src/lib/glossary-zh.ts index cbbb159ad..a03b69e1b 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: @@ -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: { @@ -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,12 +382,12 @@ const translations: Readonly> = { 'InferenceX 中的 disagg 不是万能开关。应查看预填充/解码 world size、TP/EP 布局、框架、网络域,以及分离前沿真正领先的交互性区间。', }, 'speculative-decoding': { - term: '推测解码', + term: '投机解码', aliases: ['speculative decoding', '草稿与验证解码'], plainEnglish: - '推测解码让一个便宜的助手先起草多个 token,再由完整模型一次性审核,省去部分逐个生成步骤。', + '投机解码让一个便宜的助手先起草多个 token,再由完整模型一次性审核,省去部分逐个生成步骤。', definition: - '推测解码先以低成本提出多个未来 token,再由目标模型批量验证,从而减少昂贵的串行解码步数。', + '投机解码先以低成本提出多个未来 token,再由目标模型批量验证,从而减少昂贵的串行解码步数。', explanation: '草稿模型或内置预测头生成候选,目标模型在一次批量验证中评估这些候选并接受有效前缀;严格实现时不会改变目标分布。', significance: @@ -400,7 +400,7 @@ const translations: Readonly> = { 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', diff --git a/packages/app/src/lib/glossary.test.ts b/packages/app/src/lib/glossary.test.ts index e524c5805..760c019ce 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,12 @@ 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('sglang')?.benchmarkContext).toContain('回归或提升'); + }); }); describe('glossary navigation', () => { From b95d088c427978d50f88bec407ee230615f0de41 Mon Sep 17 00:00:00 2001 From: Wenyao Gao Date: Sun, 23 Aug 2026 05:41:48 -0700 Subject: [PATCH 02/10] fix(zh): preserve audited article terminology MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the established speculative-decoding term and keep the audited DeepSeek V4 and GB300 inline code byte-identical to their English sources. 中文:统一使用既定的“投机解码”术语,并确保 DeepSeek V4 与 GB300 审核段落中的内联代码与英文原文逐字节一致。 --- ...nvfp4-vs-h200-fp8-3-6x-perf-per-dollar.mdx | 2 +- ...seekv4-16t-day-0-to-day-43-performance.mdx | 2 +- ...nvl72-vs-gb200-nvl72-dsv4-pro-vllm-fp4.mdx | 2 +- ...x-v2-nvidia-blackwell-vs-amd-vs-hopper.mdx | 8 ++--- packages/app/src/lib/blog-content.test.ts | 33 +++++++++++++++++++ 5 files changed, 40 insertions(+), 7 deletions(-) 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..49c04a597 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 @@ -179,7 +179,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/src/lib/blog-content.test.ts b/packages/app/src/lib/blog-content.test.ts index 8b660d5c7..2563644b1 100644 --- a/packages/app/src/lib/blog-content.test.ts +++ b/packages/app/src/lib/blog-content.test.ts @@ -55,6 +55,15 @@ function localImageRefs(raw: string): string[] { const countFigures = (raw: string) => (raw.match(/ (raw.match(/^\$\$\s*$/gmu) ?? []).length; +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('finds English posts to check', () => { expect(enFiles.length).toBeGreaterThan(0); }); @@ -102,3 +111,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); + } +}); From 3ad1629fbb05773bf148b28c76241f394c698361 Mon Sep 17 00:00:00 2001 From: Wenyao Gao Date: Sun, 23 Aug 2026 06:11:09 -0700 Subject: [PATCH 03/10] fix(zh): close content review gaps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Parse multiline blog tags and restore complete locale metadata parity. Cover API schema containment plus every localized Blog and Glossary listing-to-detail journey at desktop and mobile widths. Correct the TCO glossary translation to preserve software and workload scope. 中文:修复博客多行标签解析并恢复中英文 metadata 完整一致;覆盖 API 数据结构布局,以及 Blog 和 Glossary 在桌面端与移动端的全部双语列表到详情页点击链路;同时修正 TCO 术语页中软件实现与工作负载范围的翻译。 --- ...seekv4-16t-day-0-to-day-43-performance.mdx | 1 + .../app/cypress/e2e/api-documentation.cy.ts | 17 ++--- packages/app/cypress/e2e/blog.cy.ts | 54 ++++++++++++++++ packages/app/cypress/e2e/zh-pages.cy.ts | 62 +++++++++++++++++++ packages/app/src/app/blog/[slug]/page.tsx | 2 +- packages/app/src/app/glossary/[slug]/page.tsx | 2 +- packages/app/src/app/zh/blog/[slug]/page.tsx | 2 +- .../app/src/app/zh/glossary/[slug]/page.tsx | 2 +- .../api-documentation/api-reference-page.tsx | 1 + .../src/components/blog/blog-post-card.tsx | 2 + .../components/glossary/glossary-browser.tsx | 2 + packages/app/src/lib/blog-content.test.ts | 38 ++++++++---- packages/app/src/lib/glossary-zh.ts | 2 +- packages/app/src/lib/glossary.test.ts | 6 ++ 14 files changed, 170 insertions(+), 23 deletions(-) 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 49c04a597..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)。_ diff --git a/packages/app/cypress/e2e/api-documentation.cy.ts b/packages/app/cypress/e2e/api-documentation.cy.ts index a646f015a..ca11fe531 100644 --- a/packages/app/cypress/e2e/api-documentation.cy.ts +++ b/packages/app/cypress/e2e/api-documentation.cy.ts @@ -81,18 +81,21 @@ describe('API documentation', () => { }); for (const [path, width] of [ + ['/api', 1440], ['/api', 375], ['/zh/api', 390], ] as const) { - it(`keeps wide API data reachable without page overflow at ${path} on ${width}px`, () => { + 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-endpoint-list-benchmarks"] summary').click(); - cy.get('[data-testid="api-endpoint-list-benchmarks"] .overflow-x-auto') - .first() - .should('be.visible') - .then(($scroller) => { - expect($scroller[0].scrollWidth).to.be.greaterThan($scroller[0].clientWidth); + 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( diff --git a/packages/app/cypress/e2e/blog.cy.ts b/packages/app/cypress/e2e/blog.cy.ts index 351f37fef..fefe484a5 100644 --- a/packages/app/cypress/e2e/blog.cy.ts +++ b/packages/app/cypress/e2e/blog.cy.ts @@ -1,4 +1,58 @@ +const BLOG_SITE_URL = 'https://inferencex.semianalysis.com'; +const BLOG_POST_COUNT = 19; + +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; + +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); + cy.get(`link[rel="alternate"][hreflang="${alternateLocale}"]`).should( + 'have.attr', + 'href', + `${BLOG_SITE_URL}${alternatePath}`, + ); + cy.go('back'); + cy.location('pathname').should('eq', basePath); + cy.get(`[data-testid="blog-post-card"][data-blog-slug="${slug}"]`).should('exist'); + } + }); +} + describe('Blog', () => { + describe('complete localized listing-to-article journeys', () => { + for (const scenario of BLOG_JOURNEY_MATRIX) { + it(`opens every ${scenario.basePath} article from the listing at ${scenario.width}px`, () => { + exerciseEveryBlogJourney(scenario); + }); + } + }); + describe('Blog listing page', () => { before(() => { cy.visit('/blog'); diff --git a/packages/app/cypress/e2e/zh-pages.cy.ts b/packages/app/cypress/e2e/zh-pages.cy.ts index ecb6827c3..8b23bee3b 100644 --- a/packages/app/cypress/e2e/zh-pages.cy.ts +++ b/packages/app/cypress/e2e/zh-pages.cy.ts @@ -1,3 +1,59 @@ +const GLOSSARY_SITE_URL = 'https://inferencex.semianalysis.com'; +const GLOSSARY_ENTRY_COUNT = 54; + +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 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); + cy.get(`link[rel="alternate"][hreflang="${alternateLocale}"]`).should( + 'have.attr', + 'href', + `${GLOSSARY_SITE_URL}${alternatePath}`, + ); + cy.go('back'); + cy.location('pathname').should('eq', basePath); + cy.get(`[data-testid="glossary-entry-link"][data-glossary-slug="${slug}"]`).should('exist'); + } + }); +} + describe('Chinese (/zh) pages', () => { describe('zh landing page', () => { before(() => { @@ -156,6 +212,12 @@ describe('Chinese (/zh) pages', () => { }); describe('Glossary pages', () => { + for (const scenario of GLOSSARY_JOURNEY_MATRIX) { + it(`opens every ${scenario.basePath} term from the index at ${scenario.width}px`, () => { + exerciseEveryGlossaryJourney(scenario); + }); + } + it('supports search, category filtering, empty recovery, and a Chinese term click path', () => { cy.viewport(1440, 900); cy.visit('/zh/glossary'); 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/blog/[slug]/page.tsx b/packages/app/src/app/zh/blog/[slug]/page.tsx index 5b6deabe6..f7cce1af3 100644 --- a/packages/app/src/app/zh/blog/[slug]/page.tsx +++ b/packages/app/src/app/zh/blog/[slug]/page.tsx @@ -88,7 +88,7 @@ export default async function ZhBlogPostPage({ params }: Props) { const breadcrumbJsonLd = buildBlogBreadcrumbJsonLdZh(slug, meta.title); return ( -
+
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/components/api-documentation/api-reference-page.tsx b/packages/app/src/components/api-documentation/api-reference-page.tsx index 9c8773713..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,6 +305,7 @@ 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/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/blog-content.test.ts b/packages/app/src/lib/blog-content.test.ts index 2563644b1..2cad30d7a 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 [ @@ -64,8 +79,9 @@ function inlineCodeSpans(raw: string): string[] { ); } -it('finds English posts to check', () => { - expect(enFiles.length).toBeGreaterThan(0); +it('checks all 19 current locale pairs in both directions', () => { + expect(enFiles).toHaveLength(19); + expect(zhFiles).toEqual(enFiles); }); describe.each(enFiles)('%s', (file) => { diff --git a/packages/app/src/lib/glossary-zh.ts b/packages/app/src/lib/glossary-zh.ts index a03b69e1b..bf6c0e4bf 100644 --- a/packages/app/src/lib/glossary-zh.ts +++ b/packages/app/src/lib/glossary-zh.ts @@ -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 吞吐量', diff --git a/packages/app/src/lib/glossary.test.ts b/packages/app/src/lib/glossary.test.ts index 760c019ce..5a80eeb73 100644 --- a/packages/app/src/lib/glossary.test.ts +++ b/packages/app/src/lib/glossary.test.ts @@ -139,6 +139,12 @@ 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('回归或提升'); }); }); From 6ebc42b7f6d3c384c0d914071b21c298afc883c0 Mon Sep 17 00:00:00 2001 From: Wenyao Gao Date: Sun, 23 Aug 2026 06:28:14 -0700 Subject: [PATCH 04/10] fix(zh): preserve land acknowledgement scope MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translate each infrastructure relationship as operational rather than constructional, with a focused fidelity guard. Move exhaustive localized Blog and Glossary navigation matrices into a dedicated integration spec so smoke coverage stays lean. 中文:将三处基础设施与传统领地的关系准确表达为“运行于”,避免误写成建设关系,并加入聚焦语义范围的回归测试;同时把 Blog 与 Glossary 的完整双语导航矩阵移入独立集成测试,保持 smoke 测试轻量。 --- packages/app/cypress/e2e/blog.cy.ts | 54 -------- .../app/cypress/e2e/content-journeys.cy.ts | 117 ++++++++++++++++++ packages/app/cypress/e2e/zh-pages.cy.ts | 62 ---------- .../src/app/zh/land-acknowledgement/page.tsx | 6 +- .../lib/land-acknowledgement-content.test.ts | 30 +++++ 5 files changed, 150 insertions(+), 119 deletions(-) create mode 100644 packages/app/cypress/e2e/content-journeys.cy.ts create mode 100644 packages/app/src/lib/land-acknowledgement-content.test.ts diff --git a/packages/app/cypress/e2e/blog.cy.ts b/packages/app/cypress/e2e/blog.cy.ts index fefe484a5..351f37fef 100644 --- a/packages/app/cypress/e2e/blog.cy.ts +++ b/packages/app/cypress/e2e/blog.cy.ts @@ -1,58 +1,4 @@ -const BLOG_SITE_URL = 'https://inferencex.semianalysis.com'; -const BLOG_POST_COUNT = 19; - -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; - -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); - cy.get(`link[rel="alternate"][hreflang="${alternateLocale}"]`).should( - 'have.attr', - 'href', - `${BLOG_SITE_URL}${alternatePath}`, - ); - cy.go('back'); - cy.location('pathname').should('eq', basePath); - cy.get(`[data-testid="blog-post-card"][data-blog-slug="${slug}"]`).should('exist'); - } - }); -} - describe('Blog', () => { - describe('complete localized listing-to-article journeys', () => { - for (const scenario of BLOG_JOURNEY_MATRIX) { - it(`opens every ${scenario.basePath} article from the listing at ${scenario.width}px`, () => { - exerciseEveryBlogJourney(scenario); - }); - } - }); - describe('Blog listing page', () => { before(() => { cy.visit('/blog'); 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..d9f603e99 --- /dev/null +++ b/packages/app/cypress/e2e/content-journeys.cy.ts @@ -0,0 +1,117 @@ +const CONTENT_SITE_URL = 'https://inferencex.semianalysis.com'; +const BLOG_POST_COUNT = 19; +const GLOSSARY_ENTRY_COUNT = 54; + +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); + cy.get(`link[rel="alternate"][hreflang="${alternateLocale}"]`).should( + 'have.attr', + 'href', + `${CONTENT_SITE_URL}${alternatePath}`, + ); + 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); + cy.get(`link[rel="alternate"][hreflang="${alternateLocale}"]`).should( + 'have.attr', + 'href', + `${CONTENT_SITE_URL}${alternatePath}`, + ); + 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 8b23bee3b..ecb6827c3 100644 --- a/packages/app/cypress/e2e/zh-pages.cy.ts +++ b/packages/app/cypress/e2e/zh-pages.cy.ts @@ -1,59 +1,3 @@ -const GLOSSARY_SITE_URL = 'https://inferencex.semianalysis.com'; -const GLOSSARY_ENTRY_COUNT = 54; - -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 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); - cy.get(`link[rel="alternate"][hreflang="${alternateLocale}"]`).should( - 'have.attr', - 'href', - `${GLOSSARY_SITE_URL}${alternatePath}`, - ); - cy.go('back'); - cy.location('pathname').should('eq', basePath); - cy.get(`[data-testid="glossary-entry-link"][data-glossary-slug="${slug}"]`).should('exist'); - } - }); -} - describe('Chinese (/zh) pages', () => { describe('zh landing page', () => { before(() => { @@ -212,12 +156,6 @@ describe('Chinese (/zh) pages', () => { }); describe('Glossary pages', () => { - for (const scenario of GLOSSARY_JOURNEY_MATRIX) { - it(`opens every ${scenario.basePath} term from the index at ${scenario.width}px`, () => { - exerciseEveryGlossaryJourney(scenario); - }); - } - it('supports search, category filtering, empty recovery, and a Chinese term click path', () => { cy.viewport(1440, 900); cy.visit('/zh/glossary'); diff --git a/packages/app/src/app/zh/land-acknowledgement/page.tsx b/packages/app/src/app/zh/land-acknowledgement/page.tsx index 5d3acc9eb..944c78fce 100644 --- a/packages/app/src/app/zh/land-acknowledgement/page.tsx +++ b/packages/app/src/app/zh/land-acknowledgement/page.tsx @@ -9,20 +9,20 @@ const REGIONAL_ACKNOWLEDGEMENTS_ZH = [ region: 'San Jose', peoples: 'Muwekma Ohlone Tribe', acknowledgement: - '我们位于 San Jose 地区的基准测试基础设施,建在旧金山湾区 Muwekma Ohlone Tribe 尚未割让的祖居地上。', + '我们位于 San Jose 地区的基准测试基础设施运行于旧金山湾区 Muwekma Ohlone Tribe 尚未割让的祖居地上。', }, { region: 'Los Angeles', peoples: 'Tongva、Tataviam、Serrano、Kizh 和 Chumash 原住民族', acknowledgement: - '我们位于 Los Angeles 地区的基准测试基础设施,建在 Tongva、Tataviam、Serrano、Kizh 和 Chumash 原住民族最早居住、至今仍在生活并守护的土地上。', + '我们位于 Los Angeles 地区的基准测试基础设施运行于 Tongva、Tataviam、Serrano、Kizh 和 Chumash 原住民族最早居住、至今仍在生活并守护的土地上。', }, { region: 'Chicago', peoples: 'Council of the Three Fires、Illinois Confederacy、Miami、Ho-Chunk、Menominee、Fox 和 Sac 原住民族', acknowledgement: - '我们位于 Chicago 地区的基准测试基础设施,建在 Council of the Three Fires(Ojibwe、Odawa 和 Potawatomi Nations)、Illinois Confederacy 以及 Miami、Ho-Chunk、Menominee、Fox 和 Sac 等其他原住民族世代守护的土地上。', + '我们位于 Chicago 地区的基准测试基础设施运行于 Council of the Three Fires(Ojibwe、Odawa 和 Potawatomi Nations)、Illinois Confederacy 以及 Miami、Ho-Chunk、Menominee、Fox 和 Sac 等其他原住民族世代守护的土地上。', }, ]; 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('建在'); + } + }); +}); From 0748d30c32c1a541e5d84f76a9730a76884a68fa Mon Sep 17 00:00:00 2001 From: Wenyao Gao Date: Sun, 23 Aug 2026 07:13:33 -0700 Subject: [PATCH 05/10] feat(zh): localize Blog navigation chrome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Localize Blog table-of-contents and heading-link controls, preserve the English rendering, and cover mobile layout plus copy analytics in content-owned tests. 中文:完善 Blog 目录与标题链接控件的中文显示,保持英文界面不变,并在内容分支覆盖移动端布局和复制操作埋点。 --- .../component/blog-shared-chrome.cy.tsx | 38 +++++++++++++++++++ packages/app/cypress/e2e/zh-pages.cy.ts | 17 +++++++++ packages/app/src/app/zh/blog/[slug]/page.tsx | 2 +- .../blog/blog-shared-chrome.test.tsx | 32 ++++++++++++++++ packages/app/src/components/blog/blog-toc.tsx | 28 +++++++++++--- .../app/src/components/blog/heading-link.tsx | 18 ++++++--- .../src/components/blog/mdx-components.tsx | 6 +-- 7 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 packages/app/cypress/component/blog-shared-chrome.cy.tsx create mode 100644 packages/app/src/components/blog/blog-shared-chrome.test.tsx 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/zh-pages.cy.ts b/packages/app/cypress/e2e/zh-pages.cy.ts index ecb6827c3..4340674d0 100644 --- a/packages/app/cypress/e2e/zh-pages.cy.ts +++ b/packages/app/cypress/e2e/zh-pages.cy.ts @@ -236,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/zh/blog/[slug]/page.tsx b/packages/app/src/app/zh/blog/[slug]/page.tsx index f7cce1af3..631bee7b4 100644 --- a/packages/app/src/app/zh/blog/[slug]/page.tsx +++ b/packages/app/src/app/zh/blog/[slug]/page.tsx @@ -135,7 +135,7 @@ export default async function ZhBlogPostPage({ params }: Props) { {headings.length > 0 && (
- +
)}
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} - +

); }, From 675eb6657b2fffd07457b58925666e792ff10411 Mon Sep 17 00:00:00 2001 From: edwingao28 Date: Fri, 28 Aug 2026 23:30:25 +0000 Subject: [PATCH 06/10] fix(tests): refresh content counts after rebase onto master Master grew the blog to 31 locale pairs and the glossary to 118 entries, so the hardcoded 19/54 counts in blog-content.test.ts and content-journeys.cy.ts were stale after the rebase. Also register the new content-journeys.cy.ts spec in timings.json (and restore its sort order) so the cypress-split baseline test passes. --- packages/app/cypress/e2e/content-journeys.cy.ts | 4 ++-- packages/app/src/lib/blog-content.test.ts | 4 ++-- packages/app/timings.json | 12 ++++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/app/cypress/e2e/content-journeys.cy.ts b/packages/app/cypress/e2e/content-journeys.cy.ts index d9f603e99..25918d38c 100644 --- a/packages/app/cypress/e2e/content-journeys.cy.ts +++ b/packages/app/cypress/e2e/content-journeys.cy.ts @@ -1,6 +1,6 @@ const CONTENT_SITE_URL = 'https://inferencex.semianalysis.com'; -const BLOG_POST_COUNT = 19; -const GLOSSARY_ENTRY_COUNT = 54; +const BLOG_POST_COUNT = 31; +const GLOSSARY_ENTRY_COUNT = 118; const BLOG_JOURNEY_MATRIX = [ { basePath: '/blog', detailPrefix: '/blog/', alternateLocale: 'zh-CN', width: 1440 }, diff --git a/packages/app/src/lib/blog-content.test.ts b/packages/app/src/lib/blog-content.test.ts index 2cad30d7a..359c7bbd4 100644 --- a/packages/app/src/lib/blog-content.test.ts +++ b/packages/app/src/lib/blog-content.test.ts @@ -79,8 +79,8 @@ function inlineCodeSpans(raw: string): string[] { ); } -it('checks all 19 current locale pairs in both directions', () => { - expect(enFiles).toHaveLength(19); +it('checks all 31 current locale pairs in both directions', () => { + expect(enFiles).toHaveLength(31); expect(zhFiles).toEqual(enFiles); }); diff --git a/packages/app/timings.json b/packages/app/timings.json index 3207a42db..6cc117157 100644 --- a/packages/app/timings.json +++ b/packages/app/timings.json @@ -44,10 +44,6 @@ "spec": "cypress/e2e/blog.cy.ts", "duration": 3125 }, - { - "spec": "cypress/e2e/fleet-lifecycle.cy.ts", - "duration": 18000 - }, { "spec": "cypress/e2e/calculator-overlay.cy.ts", "duration": 9124 @@ -88,6 +84,10 @@ "spec": "cypress/e2e/compare-table.cy.ts", "duration": 1597 }, + { + "spec": "cypress/e2e/content-journeys.cy.ts", + "duration": 150000 + }, { "spec": "cypress/e2e/csv-export-overlay.cy.ts", "duration": 1116 @@ -124,6 +124,10 @@ "spec": "cypress/e2e/fixed-sequence-logs.cy.ts", "duration": 1528 }, + { + "spec": "cypress/e2e/fleet-lifecycle.cy.ts", + "duration": 18000 + }, { "spec": "cypress/e2e/gpu-compare-agentic-detail.cy.ts", "duration": 1789 From 57cbbd418a170f43f313433a00bbf5420a7af41f Mon Sep 17 00:00:00 2001 From: edwingao28 Date: Fri, 28 Aug 2026 23:39:07 +0000 Subject: [PATCH 07/10] fix(e2e): tolerate streamed head metadata in content journeys After a client-side navigation Next.js updates the document body before the streamed tags replace the listing page's, so asserting have.attr on the first matching alternate raced the head update and flaked on the first article/term of a journey. Query for the fully-updated link (hreflang + href) and retry until it exists. --- .../app/cypress/e2e/content-journeys.cy.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/app/cypress/e2e/content-journeys.cy.ts b/packages/app/cypress/e2e/content-journeys.cy.ts index 25918d38c..19a3603a4 100644 --- a/packages/app/cypress/e2e/content-journeys.cy.ts +++ b/packages/app/cypress/e2e/content-journeys.cy.ts @@ -50,11 +50,13 @@ function exerciseEveryBlogJourney({ cy.get('[data-testid="blog-post-page"]') .should('be.visible') .and('have.attr', 'data-blog-slug', slug); - cy.get(`link[rel="alternate"][hreflang="${alternateLocale}"]`).should( - 'have.attr', - 'href', - `${CONTENT_SITE_URL}${alternatePath}`, - ); + // 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}"]`, + { timeout: 15000 }, + ).should('exist'); cy.go('back'); cy.location('pathname').should('eq', basePath); cy.get(`[data-testid="blog-post-card"][data-blog-slug="${slug}"]`).should('exist'); @@ -86,11 +88,12 @@ function exerciseEveryGlossaryJourney({ cy.get('[data-testid="glossary-detail-page"]') .should('be.visible') .and('have.attr', 'data-glossary-slug', slug); - cy.get(`link[rel="alternate"][hreflang="${alternateLocale}"]`).should( - 'have.attr', - 'href', - `${CONTENT_SITE_URL}${alternatePath}`, - ); + // 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}"]`, + { timeout: 15000 }, + ).should('exist'); cy.go('back'); cy.location('pathname').should('eq', basePath); cy.get(`[data-testid="glossary-entry-link"][data-glossary-slug="${slug}"]`).should('exist'); From 9ac9f6cc4a5fff788dbaa33379f04fee209492af Mon Sep 17 00:00:00 2001 From: Wenyao Gao Date: Fri, 28 Aug 2026 20:15:23 -0700 Subject: [PATCH 08/10] fix(zh): refine explanatory page copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 中文:优化关于页面、常见问题与闭环并发术语,并移除不符合测试规范的内联超时。 --- .../app/cypress/e2e/content-journeys.cy.ts | 2 -- packages/app/src/app/zh/about/page.tsx | 6 ++--- .../app/src/components/about/faq-data-zh.ts | 6 ++--- .../app/src/components/about/faq-data.test.ts | 27 +++++++++++++++++++ packages/app/src/lib/glossary-zh.ts | 4 +-- packages/app/src/lib/glossary.test.ts | 11 ++++++++ 6 files changed, 46 insertions(+), 10 deletions(-) diff --git a/packages/app/cypress/e2e/content-journeys.cy.ts b/packages/app/cypress/e2e/content-journeys.cy.ts index 19a3603a4..3acfdb13e 100644 --- a/packages/app/cypress/e2e/content-journeys.cy.ts +++ b/packages/app/cypress/e2e/content-journeys.cy.ts @@ -55,7 +55,6 @@ function exerciseEveryBlogJourney({ // whichever (possibly stale) alternate is first in document order. cy.get( `link[rel="alternate"][hreflang="${alternateLocale}"][href="${CONTENT_SITE_URL}${alternatePath}"]`, - { timeout: 15000 }, ).should('exist'); cy.go('back'); cy.location('pathname').should('eq', basePath); @@ -92,7 +91,6 @@ function exerciseEveryGlossaryJourney({ // for this term has replaced the listing page's alternate link. cy.get( `link[rel="alternate"][hreflang="${alternateLocale}"][href="${CONTENT_SITE_URL}${alternatePath}"]`, - { timeout: 15000 }, ).should('exist'); cy.go('back'); cy.location('pathname').should('eq', basePath); diff --git a/packages/app/src/app/zh/about/page.tsx b/packages/app/src/app/zh/about/page.tsx index 6213e8b72..1038f5838 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™(原名 diff --git a/packages/app/src/components/about/faq-data-zh.ts b/packages/app/src/components/about/faq-data-zh.ts index 616d9b141..7acdebd84 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, }, { @@ -95,7 +95,7 @@ export const FAQ_ITEMS_ZH: readonly FaqItem[] = [ id: 'faq-raw-logs', question: '在哪里可以查看原始基准测试日志?', answer: - '在图表上点击任意数据点即可打开 tooltip。其中的"GitHub Actions Run"链接将直接跳转到生成该数据点的工作流运行。在那里您可以查看完整的任务日志、框架和驱动版本、命令行参数,以及下载原始产物(包括请求延迟、token 计数和芯片功耗遥测数据)。', + '点击图表中的任意数据点即可打开提示框。其中的“GitHub Actions 运行记录”链接会直接跳转到生成该数据点的工作流运行。您可以在那里查看完整的任务日志、框架和驱动版本、命令行参数,并下载原始产物,包括请求延迟、token 计数和芯片功耗遥测数据。', }, { id: 'faq-rerun-benchmark', diff --git a/packages/app/src/components/about/faq-data.test.ts b/packages/app/src/components/about/faq-data.test.ts index 68de50116..a6f7b5b73 100644 --- a/packages/app/src/components/about/faq-data.test.ts +++ b/packages/app/src/components/about/faq-data.test.ts @@ -1,8 +1,15 @@ +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', +); + describe('localized About FAQ', () => { it('keeps every English FAQ item and optional field represented in Chinese', () => { expect(FAQ_ITEMS_ZH).toHaveLength(FAQ_ITEMS.length); @@ -26,4 +33,24 @@ describe('localized About FAQ', () => { 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"'); + }); }); diff --git a/packages/app/src/lib/glossary-zh.ts b/packages/app/src/lib/glossary-zh.ts index bf6c0e4bf..5a326a8db 100644 --- a/packages/app/src/lib/glossary-zh.ts +++ b/packages/app/src/lib/glossary-zh.ts @@ -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: '子智能体', diff --git a/packages/app/src/lib/glossary.test.ts b/packages/app/src/lib/glossary.test.ts index 5a80eeb73..3dcb72358 100644 --- a/packages/app/src/lib/glossary.test.ts +++ b/packages/app/src/lib/glossary.test.ts @@ -147,6 +147,17 @@ describe('Chinese glossary content', () => { ); 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'); + }); }); describe('glossary navigation', () => { From c39785108648e5c3c6a6be1a49e7c872cd92a4f5 Mon Sep 17 00:00:00 2001 From: Wenyao Gao Date: Fri, 28 Aug 2026 20:41:34 -0700 Subject: [PATCH 09/10] fix(zh): refine content provenance copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 中文:优化 About 可复现性说明、FAQ 测试配置术语和 Glossary 提示框用词。 --- packages/app/src/app/zh/about/page.tsx | 22 +++++++------- .../app/src/components/about/faq-data-zh.ts | 6 ++-- .../app/src/components/about/faq-data.test.ts | 30 +++++++++++++++++++ packages/app/src/lib/glossary-zh.ts | 10 +++---- packages/app/src/lib/glossary.test.ts | 16 ++++++++++ 5 files changed, 64 insertions(+), 20 deletions(-) diff --git a/packages/app/src/app/zh/about/page.tsx b/packages/app/src/app/zh/about/page.tsx index 1038f5838..29050de0c 100644 --- a/packages/app/src/app/zh/about/page.tsx +++ b/packages/app/src/app/zh/about/page.tsx @@ -82,9 +82,8 @@ export default function AboutPageZh() {

可复现性

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

  1. @@ -92,9 +91,9 @@ export default function AboutPageZh() { 1
    - 配方提交至仓库。{' '} - 每种硬件、框架、模型和精度的组合都是一个提交到公开仓库的 shell - 脚本。镜像、命令行和并行度均在源码中固定。 + 测试配置已提交到仓库。{' '} + 每组硬件、框架、模型和精度组合都对应一个提交到公开仓库的 shell + 脚本。脚本中固定了所用镜像、命令行参数和并行度。
  2. @@ -123,10 +122,9 @@ export default function AboutPageZh() { 4
    - 导入仪表板。{' '} - 成功的运行将被加载到数据库中并在此展示。每个图表 tooltip - 都附带一个直接链接,指向生成该数据点的 GitHub Actions - 运行。点击任意数据点即可审计其来源。 + 结果写入仪表板。{' '} + 运行成功后,结果将写入数据库并在仪表板中展示。每个图表的提示框都提供对应的 GitHub + Actions 运行记录链接。点击任意数据点即可核查其来源。
@@ -137,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" > - 浏览工作流运行 + 查看工作流运行记录 - 查看基准测试配方 + 查看测试配置 { it('keeps every English FAQ item and optional field represented in Chinese', () => { @@ -53,4 +54,33 @@ describe('localized About FAQ', () => { 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/lib/glossary-zh.ts b/packages/app/src/lib/glossary-zh.ts index 5a326a8db..2f689d413 100644 --- a/packages/app/src/lib/glossary-zh.ts +++ b/packages/app/src/lib/glossary-zh.ts @@ -393,7 +393,7 @@ const translations: Readonly> = { 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 预测', @@ -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 3dcb72358..4e5903941 100644 --- a/packages/app/src/lib/glossary.test.ts +++ b/packages/app/src/lib/glossary.test.ts @@ -158,6 +158,22 @@ describe('Chinese glossary content', () => { 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', () => { From c9243a387daab4e3e1248cb40476b465a90c20fc Mon Sep 17 00:00:00 2001 From: Wenyao Gao Date: Wed, 2 Sep 2026 00:27:28 -0700 Subject: [PATCH 10/10] fix(zh): refine content copy and trim redundant tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply the maintainer-approved Chinese copy across About, AgentX FAQ, land acknowledgement, API reference, glossary, and selected blog passages. Keep focused locale, parity, and browser coverage while removing exhaustive navigation and exact-copy tests. 中文:完善关于页面、AgentX FAQ、原住民传统领地声明、API 参考文档、术语表及选定博客段落中经维护者确认的中文文案;保留聚焦的语言版本、配对和浏览器行为覆盖,并移除穷举式导航与逐字文案测试。 --- ...nvfp4-vs-h200-fp8-3-6x-perf-per-dollar.mdx | 2 +- ...seekv4-16t-day-0-to-day-43-performance.mdx | 2 +- ...nvl72-vs-gb200-nvl72-dsv4-pro-vllm-fp4.mdx | 2 +- ...x-v2-nvidia-blackwell-vs-amd-vs-hopper.mdx | 6 +- .../component/blog-shared-chrome.cy.tsx | 5 - .../app/cypress/e2e/api-documentation.cy.ts | 39 +++--- .../app/cypress/e2e/content-journeys.cy.ts | 118 ------------------ packages/app/cypress/e2e/zh-pages.cy.ts | 70 +++-------- packages/app/src/app/blog/[slug]/page.tsx | 2 +- packages/app/src/app/glossary/[slug]/page.tsx | 2 +- packages/app/src/app/zh/about/page.tsx | 19 +-- .../app/zh/blog/[slug]/opengraph-image.tsx | 2 +- packages/app/src/app/zh/blog/[slug]/page.tsx | 4 +- packages/app/src/app/zh/blog/page.tsx | 4 +- .../app/src/app/zh/glossary/[slug]/page.tsx | 2 +- .../src/app/zh/land-acknowledgement/page.tsx | 16 +-- .../app/src/components/about/agentx-faq.tsx | 44 +++---- .../app/src/components/about/faq-data-zh.ts | 18 +-- .../app/src/components/about/faq-data.test.ts | 86 ------------- packages/app/src/components/about/faq.test.ts | 11 ++ .../api-documentation/api-reference-page.tsx | 5 +- .../src/components/blog/blog-post-card.tsx | 2 - .../blog/blog-shared-chrome.test.tsx | 10 +- .../app/src/components/blog/heading-link.tsx | 5 +- .../components/glossary/glossary-browser.tsx | 2 - .../app/src/lib/api-documentation.test.ts | 19 --- packages/app/src/lib/api-documentation.ts | 44 +++---- packages/app/src/lib/blog-content.test.ts | 52 +------- packages/app/src/lib/glossary-zh.ts | 34 ++--- packages/app/src/lib/glossary.test.ts | 39 ------ .../lib/land-acknowledgement-content.test.ts | 30 ----- packages/app/timings.json | 4 - 32 files changed, 157 insertions(+), 543 deletions(-) delete mode 100644 packages/app/cypress/e2e/content-journeys.cy.ts delete mode 100644 packages/app/src/components/about/faq-data.test.ts delete mode 100644 packages/app/src/lib/api-documentation.test.ts delete mode 100644 packages/app/src/lib/land-acknowledgement-content.test.ts 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 2034f99e1..8c98f3801 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 d404b5aae..6b6234c34 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 @@ -180,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 a94028942..bfa57e208 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 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。 +标题中的每 GPU 吞吐量 2.83 倍优势,出现在曲线中段的 27 tok/s/user(此时性价比为 2.31 倍),而不是吞吐量最高点。低于 20 tok/s/user 时,两套机架的预填充批次都足够大,GB300 的 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 在相同交互性下没有对应的测试配置。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 48fc4ddb8..93630c370 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。 diff --git a/packages/app/cypress/component/blog-shared-chrome.cy.tsx b/packages/app/cypress/component/blog-shared-chrome.cy.tsx index 7210a4225..b51236de0 100644 --- a/packages/app/cypress/component/blog-shared-chrome.cy.tsx +++ b/packages/app/cypress/component/blog-shared-chrome.cy.tsx @@ -26,13 +26,8 @@ describe('Blog shared chrome', () => { ); }); 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 ca11fe531..68ef83aef 100644 --- a/packages/app/cypress/e2e/api-documentation.cy.ts +++ b/packages/app/cypress/e2e/api-documentation.cy.ts @@ -2,7 +2,6 @@ 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"]') @@ -80,28 +79,22 @@ describe('API documentation', () => { .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, - ); + it('keeps wide schemas locally scrollable without overflowing the Chinese mobile page', () => { + cy.viewport(375, 844); + cy.visit('/zh/api'); + + cy.get('section[aria-labelledby="api-schemas-heading"] > dl > div') + .should('be.visible') + .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 deleted file mode 100644 index 3acfdb13e..000000000 --- a/packages/app/cypress/e2e/content-journeys.cy.ts +++ /dev/null @@ -1,118 +0,0 @@ -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 6c8312035..aca9302b8 100644 --- a/packages/app/cypress/e2e/zh-pages.cy.ts +++ b/packages/app/cypress/e2e/zh-pages.cy.ts @@ -123,24 +123,10 @@ 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', () => { + it('keeps both Chinese article links inside /zh and follows one to its translation', () => { 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'); @@ -156,11 +142,15 @@ describe('Chinese (/zh) pages', () => { }); describe('Glossary pages', () => { - it('supports search, category filtering, empty recovery, and a Chinese term click path', () => { - cy.viewport(1440, 900); + it('supports a mobile Chinese search, filter, and term navigation journey without overflow', () => { + cy.viewport(375, 844); cy.visit('/zh/glossary'); - cy.get('link[rel="alternate"][hreflang="en"]').should('exist'); + cy.document().then((document) => { + expect(document.documentElement.scrollWidth).to.be.at.most( + document.documentElement.clientWidth, + ); + }); 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'); @@ -174,31 +164,10 @@ describe('Chinese (/zh) pages', () => { 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', () => { + it('keeps every region and nation visible on mobile', () => { cy.viewport(375, 812); cy.visit('/zh/land-acknowledgement'); @@ -207,12 +176,15 @@ describe('Chinese (/zh) pages', () => { '原住民传统领地声明', ); 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'); + for (const [testId, region, nation] of [ + ['land-acknowledgement-san-jose', 'San Jose', 'Muwekma Ohlone'], + ['land-acknowledgement-los-angeles', 'Los Angeles', 'Tongva'], + ['land-acknowledgement-chicago', 'Chicago', 'Potawatomi'], + ] as const) { + cy.get(`[data-testid="${testId}"]`) + .should('contain.text', region) + .and('contain.text', nation); + } cy.get('link[rel="alternate"][hreflang="en"]').should('exist'); cy.document().then((document) => { expect(document.documentElement.scrollWidth).to.be.at.most( @@ -229,7 +201,7 @@ describe('Chinese (/zh) pages', () => { it('renders translated content with Chinese chrome', () => { cy.get('article.prose').should('exist'); - cy.contains('分钟阅读').should('exist'); + cy.contains('预计阅读').should('exist'); cy.get('a[href="/zh/blog"]').should('exist'); }); @@ -242,13 +214,11 @@ describe('Chinese (/zh) pages', () => { cy.get('details[aria-label="本页目录"]') .should('be.visible') .find('summary') - .should('contain.text', '点击展开') - .and('not.contain.text', 'click to expand'); + .should('contain.text', '点击展开'); 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); }); diff --git a/packages/app/src/app/blog/[slug]/page.tsx b/packages/app/src/app/blog/[slug]/page.tsx index e0c28ecc4..4fa7a7bc2 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 dbe7adf24..91722f144 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 29050de0c..0e383709b 100644 --- a/packages/app/src/app/zh/about/page.tsx +++ b/packages/app/src/app/zh/about/page.tsx @@ -13,7 +13,7 @@ import { GITHUB_OWNER, GITHUB_REPO, SITE_URL } from '@semianalysisai/inferencex- const faqJsonLd = buildFaqJsonLd(FAQ_ITEMS_ZH, ZH_LANG_TAG); const ABOUT_DESCRIPTION = - 'InferenceX 对比各类加速器与服务栈的智能体推理和固定序列 AI 推理性能。AgentX 是其长上下文多轮编码场景。'; + 'InferenceX 对各类加速器和服务栈上的智能体推理与固定序列 AI 推理进行基准测试。AgentX 是其中的长上下文、多轮编码场景。'; export const metadata: Metadata = { title: '关于', @@ -46,18 +46,19 @@ export default function AboutPageZh() { 持续运行的开源智能体推理基准测试,受到万亿美元级、吉瓦规模 Token 工厂运营方的信赖

- 随着全球迈向 AGI - 的进程呈指数级加速,软件开发和模型发布也日新月异。静态基准测试很快就会过时;参与者提交的镜像往往专为基准测试打造,无法反映真实环境中的性能。 + 全球迈向 AGI + 的进程呈指数级加速,软件开发和模型发布也日新月异。静态基准测试很快就会过时;参与者提交的软件镜像往往是为基准测试专门构建的,无法反映真实应用场景中的性能。

InferenceX™(原名 - InferenceMAX)是我们独立、厂商中立、可复现的基准测试平台。它测试固定序列推理服务和 - AgentX 长上下文多轮智能体编码工作负载,覆盖 ML 社区实际可用的各类 AI 加速器与服务栈。 + InferenceMAX)是我们建立的独立、厂商中立且可复现的基准测试平台。它覆盖 ML + 社区实际可用的各类 AI 加速器和服务栈,测试固定序列推理服务以及 AgentX + 长上下文、多轮智能体编码工作负载。

- 我们的开放数据与洞察已被 ML 社区广泛采用,包括万亿美元级 Token 工厂和 AI - 实验室的容量规划策略团队,以及多家数十亿美元级 - NeoCloud。了解更多详情请阅读我们的文章:{' '} + 我们的开放数据与洞察已被 ML 社区广泛采用,用户包括万亿美元级 Token 工厂和 AI + 实验室中负责容量规划与战略的团队,以及多家价值数十亿美元的 + NeoCloud。更多详情请参阅以下文章:{' '}

- 结果写入仪表板。{' '} + 将结果导入仪表板。{' '} 运行成功后,结果将写入数据库并在仪表板中展示。每个图表的提示框都提供对应的 GitHub Actions 运行记录链接。点击任意数据点即可核查其来源。
diff --git a/packages/app/src/app/zh/blog/[slug]/opengraph-image.tsx b/packages/app/src/app/zh/blog/[slug]/opengraph-image.tsx index 378444bf4..ef08d09bf 100644 --- a/packages/app/src/app/zh/blog/[slug]/opengraph-image.tsx +++ b/packages/app/src/app/zh/blog/[slug]/opengraph-image.tsx @@ -7,7 +7,7 @@ import { getAllPosts, getPostBySlug } from '@/lib/blog'; // article card. Swapping in a CJK-capable font is a known follow-up. import { renderOgImage, size } from '../../../blog/[slug]/og-image-render'; -export const alt = 'InferenceX Articles'; +export const alt = 'InferenceX 文章'; export { size }; export const contentType = 'image/png'; diff --git a/packages/app/src/app/zh/blog/[slug]/page.tsx b/packages/app/src/app/zh/blog/[slug]/page.tsx index 631bee7b4..984e6bc62 100644 --- a/packages/app/src/app/zh/blog/[slug]/page.tsx +++ b/packages/app/src/app/zh/blog/[slug]/page.tsx @@ -88,7 +88,7 @@ export default async function ZhBlogPostPage({ params }: Props) { const breadcrumbJsonLd = buildBlogBreadcrumbJsonLdZh(slug, meta.title); return ( -
+
@@ -112,7 +112,7 @@ export default async function ZhBlogPostPage({ params }: Props) { })} · - {meta.readingTime} 分钟阅读 + 预计阅读 {meta.readingTime} 分钟 · 阅读英文原文 diff --git a/packages/app/src/app/zh/blog/page.tsx b/packages/app/src/app/zh/blog/page.tsx index 4e040d32b..024c0122d 100644 --- a/packages/app/src/app/zh/blog/page.tsx +++ b/packages/app/src/app/zh/blog/page.tsx @@ -11,7 +11,7 @@ import { SITE_URL, SITE_NAME, AUTHOR_NAME } from '@semianalysisai/inferencex-con export const metadata: Metadata = { title: '文章', - description: `${SITE_NAME} by ${AUTHOR_NAME} 发布关于智能体推理基准测试、AgentX 结果、芯片性能与 ML 基础设施的技术文章。`, + description: `${AUTHOR_NAME} 通过 ${SITE_NAME} 发布技术文章,涵盖智能体推理基准测试、AgentX 结果、芯片性能和 ML 基础设施。`, alternates: zhAlternates('/blog'), openGraph: { title: `文章 | ${SITE_NAME} by ${AUTHOR_NAME}`, @@ -102,7 +102,7 @@ export default async function ZhBlogPage({ })} · - {post.readingTime} 分钟阅读 + 预计阅读 {post.readingTime} 分钟

{post.title} diff --git a/packages/app/src/app/zh/glossary/[slug]/page.tsx b/packages/app/src/app/zh/glossary/[slug]/page.tsx index 01337b307..3cc736db0 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 944c78fce..1bc6b5cff 100644 --- a/packages/app/src/app/zh/land-acknowledgement/page.tsx +++ b/packages/app/src/app/zh/land-acknowledgement/page.tsx @@ -9,25 +9,25 @@ const REGIONAL_ACKNOWLEDGEMENTS_ZH = [ region: 'San Jose', peoples: 'Muwekma Ohlone Tribe', acknowledgement: - '我们位于 San Jose 地区的基准测试基础设施运行于旧金山湾区 Muwekma Ohlone Tribe 尚未割让的祖居地上。', + '我们在 San Jose 地区运行的基准测试基础设施,位于旧金山湾区 Muwekma Ohlone Tribe 从未割让的祖居地上。', }, { region: 'Los Angeles', peoples: 'Tongva、Tataviam、Serrano、Kizh 和 Chumash 原住民族', acknowledgement: - '我们位于 Los Angeles 地区的基准测试基础设施运行于 Tongva、Tataviam、Serrano、Kizh 和 Chumash 原住民族最早居住、至今仍在生活并守护的土地上。', + '我们在 Los Angeles 地区运行的基准测试基础设施,位于 Tongva、Tataviam、Serrano、Kizh 和 Chumash 原住民族最早居住、至今仍生活并守护的土地上。', }, { region: 'Chicago', peoples: 'Council of the Three Fires、Illinois Confederacy、Miami、Ho-Chunk、Menominee、Fox 和 Sac 原住民族', acknowledgement: - '我们位于 Chicago 地区的基准测试基础设施运行于 Council of the Three Fires(Ojibwe、Odawa 和 Potawatomi Nations)、Illinois Confederacy 以及 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 美国基准测试集群所在的原住民族与传统领地所作的声明。'; + 'InferenceX 在 San Jose、Los Angeles 和 Chicago 设有美国基准测试集群;本声明向这些地区的原住民族及其传统领地致意。'; export const metadata: Metadata = { title: '原住民传统领地声明', @@ -55,11 +55,11 @@ export default function LandAcknowledgementPageZh() { 原住民传统领地声明

- 我们承认并尊重美国基础设施所在地的原住民传统领地。 + 我们承认美国基础设施所在地的原住民传统领地。

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

@@ -90,7 +90,7 @@ export default function LandAcknowledgementPageZh() {

- 承认这些传统领地只是起点。我们怀着对原住民族主权、历史及延续至今的社群的尊重作出本声明;如有措辞需要改进,欢迎指正。 + 传统领地声明只是一个起点。我们发布本声明,以表达对原住民族主权、历史和当代社群的尊重。如有任何措辞需要改进,欢迎指正。

diff --git a/packages/app/src/components/about/agentx-faq.tsx b/packages/app/src/components/about/agentx-faq.tsx index 2220a3b63..fc4f9640a 100644 --- a/packages/app/src/components/about/agentx-faq.tsx +++ b/packages/app/src/components/about/agentx-faq.tsx @@ -168,17 +168,17 @@ export AIPERF_SERVICE_PROFILE_CONFIGURE_TIMEOUT=1800`, 'Thanks to the AIPerf team for implementing and documenting the scenario, and to Callan Fox and Weka for the underlying agentic-coding trace work.', }, zh: { - title: 'AgentX:基准测试如何工作', + title: 'AgentX:基准测试的工作原理', intro: [ - 'AgentX 按照编码智能体实际使用模型的方式衡量推理性能:长程多轮会话、共享前缀、轮次间停顿、并行子智能体以及持续的 KV cache 复用。它是 AIPerf 中的一套场景,基于公开的智能体编码轨迹与固定回放规则,使不同服务栈的结果能够在同一工作负载下进行比较。', - '这份 FAQ 介绍基准测试的运行机制、保障可比性的配置,以及最容易影响测试结果的部署细节。', + 'AgentX 按照编码智能体实际使用模型的方式衡量推理性能。这类会话持续时间长、轮次多,会共享前缀并在轮次之间停顿;多个子智能体还可能并行运行,KV cache 也会被反复复用。AgentX 是 AIPerf 中的一种测试场景:它按照固定配置回放公开的智能体编码轨迹,使不同服务栈能够在同一工作负载下进行比较。', + '本 FAQ 介绍该基准测试的运行方式、确保结果可比的配置,以及最容易影响测试结果的部署细节。', ], sections: [ { title: 'AgentX 衡量什么?', paragraphs: [ '传统负载测试通常以固定请求速率发送互不相关的单轮 prompt。AgentX 则维持指定数量的完整会话树。根编码会话可以创建子会话、等待子会话结束,再继续执行。每一轮都会携带累计的消息历史,从而重现真实智能体系统中的长共享前缀与突发式 fan-out。', - 'Prompt 文本是合成的,但工作负载形态来自真实会话记录:输入与输出长度、前缀共享关系、子智能体拓扑以及轮次间时间间隔。这样既不会回放用户的私密内容,又保留了对调度器、路由器和 prefix cache 真正重要的特征。', + 'prompt 文本是合成的,但工作负载形态来自真实会话记录:输入与输出长度、前缀共享关系、子智能体拓扑以及轮次间时间间隔。这样既不会回放用户的私密内容,又保留了对调度器、路由器和 prefix cache 真正重要的特征。', ], }, { @@ -206,7 +206,7 @@ export AIPERF_SERVICE_PROFILE_CONFIGURE_TIMEOUT=1800`, { title: '最简运行命令是什么?', paragraphs: [ - '请根据部署替换 URL、模型、语料和 concurrency。AIPerf 会补齐场景默认值,并将结果写入 ./artifacts/。固定 --random-seed 可精确复现采样并复用重建后的数据集 cache;当模型名称无法解析为 Hugging Face tokenizer 时,请额外传入 --tokenizer。', + '请根据部署情况替换 URL、模型、语料和 --concurrency 的值。AIPerf 会补齐场景默认值,并将结果写入 ./artifacts/。固定 --random-seed 可精确复现采样并复用重建后的数据集 cache;当模型名称无法解析为 Hugging Face tokenizer 时,请额外传入 --tokenizer。', ], code: `aiperf profile \\ --scenario inferencex-agentx-mvp \\ @@ -224,46 +224,46 @@ export AIPERF_SERVICE_PROFILE_CONFIGURE_TIMEOUT=1800`, ], }, { - title: 'Concurrency 表示什么?', + title: '并发数表示什么?', paragraphs: [ - 'Concurrency 表示同时存活的会话树数量,而不是正在进行的 HTTP 请求数。一个会话可能 fan-out 为多个子会话,因此瞬时请求并发数可以高于配置值。', - '该场景有意不提供请求速率控制。负载由活跃会话数量、记录的思考时间以及 fan-out 模式共同产生,因此 --concurrency 是主要负载调节项。', + '并发数表示同时存活的会话树数量,而不是正在处理的 HTTP 请求数。一个会话可能 fan-out 为多个子会话,因此瞬时请求并发数可以高于配置值。', + '该场景有意不提供请求速率控制。负载由活跃会话数、记录的思考时间和 fan-out 模式共同决定,因此 --concurrency 是主要的负载调节参数。', ], }, { title: '为什么第一次运行可能很久?', paragraphs: [ - '发送流量前,AIPerf 会下载语料,并将其重建为完成 token 化且带有 cache 结构的会话树。结果会存入 memory-mapped 磁盘 cache。只要语料、tokenizer、重建配置、条目数量和 seed 相同,后续测试通常可以在数秒内恢复准备好的数据集。', - '冷启动时应同时提高两个配置超时。随后依次执行数据集配置、warmup、定时 profiling 和 drain/export。Benchmark duration 仅覆盖 profiling,因此总耗时会更长。', + '发送流量前,AIPerf 会下载语料,并将其重建为完成 token 化且带有 cache 结构的会话树。结果会存入采用内存映射的磁盘 cache。只要语料、tokenizer、重建配置、条目数量和 seed 相同,后续测试通常可以在数秒内恢复准备好的数据集。', + '冷启动时应同时提高两个配置阶段的超时时间。随后依次执行数据集配置、warmup、定时 profiling 和 drain/export。基准测试时长只涵盖 profiling 阶段,因此总耗时会更长。', ], code: `export AIPERF_DATASET_CONFIGURATION_TIMEOUT=1800 export AIPERF_SERVICE_PROFILE_CONFIGURE_TIMEOUT=1800`, }, { - title: '可以先跑一个短 smoke test 吗?', + title: '可以先运行一次简短的冒烟测试(smoke test)吗?', paragraphs: [ - '有效测试不能短于 900 秒。低成本有效检查可以降低 concurrency,但仍需保留 900 秒下限。--num-dataset-entries 能减少重建工作量,但会改变工作负载,不应将这种结果用于正式比较或提交。', - '如需在几分钟内检查连通性,可配合短 duration 使用 --unsafe-override,并接受 invalid 标记。保留完整语料并固定 seed,可让后续有效测试复用重建后的数据集 cache。', + '有效测试不能短于 900 秒。低成本有效检查可以降低并发数,但仍需保留 900 秒下限。--num-dataset-entries 能减少重建工作量,但会改变工作负载,不应将这种结果用于正式比较或提交。', + '如需在几分钟内检查连通性,可使用 --unsafe-override 并缩短测试时长,同时接受 invalid 标记。保留完整语料并固定 seed,可让后续有效测试复用重建后的数据集 cache。', ], }, { - title: '如何确认负载生成器不是瓶颈?', + title: '如何确认负载生成器(load generator)不是瓶颈?', paragraphs: [ - 'AIPerf 会将工作分配到多个 worker process,并每两秒上报健康状态。应将高 CPU 警告视为客户端饱和信号;在信任结果前,需要增加客户端 CPU core 或负载生成器主机。', - '--show-trace-timing 可以区分等待连接池中空闲连接的时间与等待服务器返回首字节的时间。Worker CPU 正常且连接池阻塞很少,才有充分依据认为瓶颈位于服务器端。', + 'AIPerf 会将工作分配到多个 worker 进程,并每两秒上报 worker 健康状态。应将高 CPU 警告视为客户端饱和信号;在信任结果前,需要增加客户端 CPU 核心数或负载生成器主机。', + '--show-trace-timing 可以区分等待连接池中空闲连接的时间与等待服务器返回首字节的时间。worker 的 CPU 使用率正常且连接池阻塞很少,才有充分依据认为瓶颈位于服务器端。', ], }, { - title: '为什么运行中途会出现 connection reset?', + title: '为什么运行过程中会出现连接重置(connection reset)?', paragraphs: [ '客户端与服务器的 keep-alive 配置可能不一致。如果 AIPerf 保留空闲连接的时间比服务器更长,客户端可能复用已经关闭的 socket。应将客户端 keep-alive 设置为低于服务器值。该问题通常出现在 profiling 阶段,因为 warmup 连接的空闲时间较短。', ], code: 'export AIPERF_HTTP_KEEPALIVE_TIMEOUT=4', }, { - title: '多 replica 应如何路由?', + title: '多副本应如何路由?', paragraphs: [ - '当路由器连接多个 replica 时,必须使用会话感知路由。如果同一会话的各轮落在不同 worker 上,prefix cache 复用会大幅下降,基准测试最终衡量的是路由分散程度,而不是服务栈的 cache 能力。', + '当路由器连接多个副本时,必须使用会话感知路由。如果同一会话的各轮落在不同 worker 上,prefix cache 复用会大幅下降,基准测试最终衡量的是路由分散程度,而不是服务栈的 cache 能力。', 'AIPerf 会为每个会话分配稳定的 correlation ID;子智能体则拥有各自的稳定 ID。这些配置改变请求落点而非请求内容,因此不会被场景锁定。', ], bullets: [ @@ -286,7 +286,7 @@ export AIPERF_SERVICE_PROFILE_CONFIGURE_TIMEOUT=1800`, '保持 cache busting 开启;关闭后,循环使用轨迹会夸大 cache-hit 结果。', '保留记录的时序;压缩单条轨迹的延迟会改变 cache TTL 行为和会话重叠程度。', '在判断结果受服务器限制前,先监控客户端 CPU 和连接池等待时间。', - '所有多 replica 部署都应使用会话感知路由。', + '所有多副本部署都应使用会话感知路由。', '不要解读合成 prompt 的语义;基准测试保留的是 token 与 cache 结构,而非文本含义。', ], }, @@ -295,9 +295,9 @@ export AIPERF_SERVICE_PROFILE_CONFIGURE_TIMEOUT=1800`, adapted: '本文改编自 AIPerf AgentX 文档。上游源文件由 NVIDIA 维护,并采用 Apache-2.0 许可证。', authority: 'AgentX 是使用 AIPerf 实现的 SemiAnalysis InferenceX 基准测试。关于当前 CLI、场景锁定规则、环境变量和产物 schema,请以 AIPerf 的文档与实现为准。', - sourceLabel: 'SemiAnalysis AgentX:基准测试如何工作(FAQ)— Nvidia AIPerf 文档', + sourceLabel: 'SemiAnalysis AgentX:基准测试的工作原理(FAQ)— NVIDIA AIPerf 文档', upstreamLabel: 'AgentX MVP 教程源文件 — ai-dynamo/aiperf', - repositoryLabel: 'Nvidia AIPerf repository', + repositoryLabel: 'NVIDIA AIPerf 代码仓库', thanks: '感谢 AIPerf 团队实现并记录这一场景,也感谢 Callan Fox 与 Weka 为底层智能体编码轨迹所做的工作。', }, diff --git a/packages/app/src/components/about/faq-data-zh.ts b/packages/app/src/components/about/faq-data-zh.ts index bf3e0d088..d81a65542 100644 --- a/packages/app/src/components/about/faq-data-zh.ts +++ b/packages/app/src/components/about/faq-data-zh.ts @@ -26,7 +26,7 @@ export const FAQ_ITEMS_ZH: readonly FaqItem[] = [ id: 'faq-models', question: '测试了哪些 AI 模型?', answer: - '各模型会在相应的固定序列配置(1k/1k、1k/8k、8k/1k tokens)和多个并发级别下接受测试;如果已有对应数据,还会运行 AgentX 长上下文多轮智能体编码场景。', + '各模型会在其支持的固定序列配置(1k/1k、1k/8k 和 8k/1k token)和多个并发级别下进行测试。对于支持 AgentX 且有相应数据的模型,现有结果还涵盖 AgentX 长上下文、多轮智能体编程测试。', list: GENERATED_FAQ_DATA.modelNames, }, { @@ -34,10 +34,10 @@ export const FAQ_ITEMS_ZH: readonly FaqItem[] = [ question: '测试了哪些推理框架和配置?', answer: '', list: [ - `框架:${GENERATED_FAQ_DATA.frameworkNames.join(', ')}`, - `精度:${GENERATED_FAQ_DATA.precisionNames.join(', ')}`, + `框架:${GENERATED_FAQ_DATA.frameworkNames.join('、')}`, + `精度:${GENERATED_FAQ_DATA.precisionNames.join('、')}`, '运行时:CUDA、ROCm', - '分离式推理(Disaggregated serving,独立的 prefill/decode 芯片池)', + '分离式推理(独立的 prefill/decode 芯片池)', '多 token 预测(MTP)', '面向 MoE 模型的宽专家并行(Wide Expert Parallelism)', ], @@ -52,8 +52,8 @@ export const FAQ_ITEMS_ZH: readonly FaqItem[] = [ '每芯片输入和输出吞吐量', '每兆瓦 token 吞吐量(tok/s/MW)', 'P99 首 token 延迟(TTFT)', - 'AgentX 场景的端到端延迟、token 间延迟(ITL)、输出吞吐量、prefix cache 行为以及会话与 subagent 执行情况', - '每百万 token 成本(总计、输入、输出)——涵盖超大规模云、NeoCloud 和裸机租赁定价', + 'AgentX 场景的端到端延迟、token 间延迟(ITL)、输出吞吐量、prefix cache 行为以及会话与子智能体执行情况', + '每百万 token 成本(总计、输入、输出)——涵盖超大规模云、NeoCloud 和租赁定价', '每 token 能耗(焦耳,总计、输入、输出)', '用户自定义成本和功耗计算', ], @@ -87,7 +87,7 @@ export const FAQ_ITEMS_ZH: readonly FaqItem[] = [ }, { id: 'faq-reproducibility', - question: '结果如何实现可复现?', + question: '如何保证结果可复现?', answer: '仪表板上的每个数据点都由公开的 GitHub Actions 工作流生成。测试配置(模型、框架、精度、并行度、序列长度和并发数)保存在代码仓库中,并在对应的目标硬件上运行。日志、指标和芯片追踪数据等产物会上传到运行记录页面。每个图表的提示框都提供链接,可直接打开生成该数据点的 GitHub Actions 运行记录。', }, @@ -101,7 +101,7 @@ export const FAQ_ITEMS_ZH: readonly FaqItem[] = [ id: 'faq-rerun-benchmark', question: '我可以自己重新运行基准测试吗?', answer: - '可以。基准测试脚本位于代码仓库的 /benchmarks 目录中,可以独立运行。如果您拥有相同的硬件,可以 fork 仓库并直接运行脚本,也可以触发相同的 GitHub Actions 工作流来复现结果。', + '可以。基准测试配置以可独立运行的 shell 脚本形式保存在代码仓库的 /benchmarks 目录中。如果您拥有相同的硬件,可以 fork 仓库并直接运行脚本,也可以触发相同的 GitHub Actions 工作流来复现结果。', }, { id: 'faq-old-runs', @@ -111,7 +111,7 @@ export const FAQ_ITEMS_ZH: readonly FaqItem[] = [ }, { id: 'faq-data-use', - question: '我可以使用 InferenceX 的数据进行自己的分析吗?', + question: '我可以使用 InferenceX 数据自行分析吗?', answer: '可以。所有数据均可自由获取。仪表板支持按芯片、模型、框架和日期范围筛选,您也可以直接从任何图表导出原始 CSV 数据。', }, diff --git a/packages/app/src/components/about/faq-data.test.ts b/packages/app/src/components/about/faq-data.test.ts deleted file mode 100644 index 9478d45f1..000000000 --- a/packages/app/src/components/about/faq-data.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -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/about/faq.test.ts b/packages/app/src/components/about/faq.test.ts index 465a3ac6b..b894498e4 100644 --- a/packages/app/src/components/about/faq.test.ts +++ b/packages/app/src/components/about/faq.test.ts @@ -3,6 +3,13 @@ import { describe, expect, it } from 'vitest'; import { FAQ_ITEMS } from '@/components/about/faq-data'; import { FAQ_ITEMS_ZH } from '@/components/about/faq-data-zh'; +const faqContentShape = (items: typeof FAQ_ITEMS) => + items.map((item) => ({ + hasAnswer: Boolean(item.answer), + listLength: item.list?.length ?? 0, + linkHref: item.link?.href ?? null, + })); + describe('FAQ anchors', () => { it('gives every item a unique anchor', () => { const ids = FAQ_ITEMS.map((item) => item.id); @@ -15,4 +22,8 @@ describe('FAQ anchors', () => { it('keeps English and Chinese anchors in exact parity', () => { expect(FAQ_ITEMS_ZH.map((item) => item.id)).toEqual(FAQ_ITEMS.map((item) => item.id)); }); + + it('keeps optional content fields structurally aligned across locales', () => { + expect(faqContentShape(FAQ_ITEMS_ZH)).toEqual(faqContentShape(FAQ_ITEMS)); + }); }); 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 7c670d951..5074a448e 100644 --- a/packages/app/src/components/api-documentation/api-reference-page.tsx +++ b/packages/app/src/components/api-documentation/api-reference-page.tsx @@ -59,7 +59,7 @@ const UI_COPY = { }, openApiKicker: '机器可读契约', openApiTitle: 'OpenAPI 3.1 JSON', - openApiDescription: '查看标准数据结构,或将其直接传入开发工具。', + openApiDescription: '查看标准 schema,或直接将其传入工具链。', openApiAction: '打开 OpenAPI JSON', quickstart: '快速入门', quickstartDescription: '只需几步,即可从查看契约到获得真实响应。', @@ -68,7 +68,7 @@ const UI_COPY = { schemas: 'BenchmarkRow 与指标', schemasDescription: '理解主要基准测试响应数据及其中的实测字段。', endpoints: '端点参考', - endpointsDescription: '展开操作,查看参数、状态码与完整示例。', + endpointsDescription: '展开任一操作,即可查看参数、状态码和完整示例。', operation: '项操作', operations: '项操作', parameters: '参数', @@ -305,7 +305,6 @@ 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 369f38fc5..4e3363582 100644 --- a/packages/app/src/components/blog/blog-post-card.tsx +++ b/packages/app/src/components/blog/blog-post-card.tsx @@ -17,8 +17,6 @@ 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 index e7f448989..d28378724 100644 --- a/packages/app/src/components/blog/blog-shared-chrome.test.tsx +++ b/packages/app/src/components/blog/blog-shared-chrome.test.tsx @@ -3,30 +3,24 @@ 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', () => { + it('preserves the existing English table-of-contents 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', () => { + it('renders the equivalent Chinese table of contents 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/heading-link.tsx b/packages/app/src/components/blog/heading-link.tsx index 662fe8628..565329527 100644 --- a/packages/app/src/components/blog/heading-link.tsx +++ b/packages/app/src/components/blog/heading-link.tsx @@ -19,11 +19,10 @@ export function HeadingLink({ id, locale = 'en' }: { id: string; locale?: Locale (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, locale }); + track('blog_heading_link_copied', { id }); setState('copied'); timerRef.current = setTimeout(() => { setState('fading'); @@ -35,7 +34,7 @@ export function HeadingLink({ id, locale = 'en' }: { id: string; locale?: Locale }, ); }, - [id, locale], + [id], ); const visible = state !== 'idle'; diff --git a/packages/app/src/components/glossary/glossary-browser.tsx b/packages/app/src/components/glossary/glossary-browser.tsx index dcaede952..72f6a2624 100644 --- a/packages/app/src/components/glossary/glossary-browser.tsx +++ b/packages/app/src/components/glossary/glossary-browser.tsx @@ -252,8 +252,6 @@ export function GlossaryBrowser({
diff --git a/packages/app/src/lib/api-documentation.test.ts b/packages/app/src/lib/api-documentation.test.ts deleted file mode 100644 index 89c5c50d9..000000000 --- a/packages/app/src/lib/api-documentation.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -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 470ad3233..ce5b6482e 100644 --- a/packages/app/src/lib/api-documentation.ts +++ b/packages/app/src/lib/api-documentation.ts @@ -590,7 +590,7 @@ export const apiOperations: readonly ApiOperation[] = [ summary: text('Read benchmark results', '读取基准结果'), description: text( 'Returns raw benchmark rows for a display model. Use date for an as-of snapshot, exact=true for that exact date, runId to constrain the latest lookup, or exactRun=true with a numeric runId to return only that workflow run. The page-owned calculator view is not part of this public contract.', - '按展示模型返回原始基准行。可用 date 获取截至该日的快照,exact=true 限定该日,runId 约束最新查询,或将 exactRun=true 与数字 runId 组合以仅返回该工作流运行。页面专用的计算器视图不属于此公开契约。', + '返回指定展示模型的原始基准测试数据行。使用 date 可获取截至指定日期的快照;exact=true 仅返回该日期的数据;runId 用于限定最新结果的查询范围;将 exactRun=true 与数值型 runId 搭配使用,则只返回该工作流运行的数据。页面内部使用的 calculator 视图不属于此公开契约。', ), audience: 'public', stability: 'stable', @@ -677,7 +677,7 @@ export const apiOperations: readonly ApiOperation[] = [ summary: text('Read benchmark history', '读取基准历史'), description: text( 'Returns every dated benchmark row for one model and either a fixed input/output token pair or Agentic Traces.', - '返回某个模型以及固定输入/输出 token 组合或 Agentic Traces 的全部历史基准行。', + '返回指定模型在固定输入/输出 token 组合或 Agentic Traces 条件下的全部历史基准测试数据行。', ), audience: 'public', stability: 'stable', @@ -756,10 +756,10 @@ export const apiOperations: readonly ApiOperation[] = [ group: 'core', method: 'GET', path: '/api/v1/workflow-info', - summary: text('Read workflow provenance', '读取工作流来源'), + summary: text('Read workflow provenance', '读取工作流溯源信息'), description: text( 'Returns workflow runs, changelogs, available configurations, and per-run configuration coverage. Omit date for all dates.', - '返回工作流运行、变更记录、可用配置以及每次运行的配置覆盖。省略 date 可读取全部日期。', + '返回工作流运行、变更记录、可用配置,以及各次运行涵盖的配置。省略 date 时返回所有日期的数据。', ), audience: 'public', stability: 'stable', @@ -788,7 +788,7 @@ export const apiOperations: readonly ApiOperation[] = [ responses: [ success( 'Workflow provenance grouped into four arrays.', - '按四个数组组织的工作流来源数据。', + '工作流溯源信息,分为四个数组。', workflowInfoSchema, { runs: [ @@ -912,7 +912,7 @@ export const apiOperations: readonly ApiOperation[] = [ summary: text('Compute a TCO feed', '计算 TCO 数据源'), description: text( 'Computes Pareto-frontier throughput points or weighted scores for spreadsheet TCO models. Every scoring assumption is encoded in the URL. CSV returns the same selected view as a flat table.', - '为电子表格 TCO 模型计算帕累托前沿吞吐点或加权分数。所有评分假设都编码在 URL 中。CSV 会将同一所选视图返回为平面表格。', + '为电子表格中的 TCO 模型计算帕累托前沿上的吞吐量数据点或加权分数。所有评分假设都编码在 URL 中。CSV 会以扁平表格形式返回当前所选视图中的相同数据。', ), audience: 'public', stability: 'stable', @@ -973,7 +973,7 @@ export const apiOperations: readonly ApiOperation[] = [ false, 'enum', 'points returns one row per hardware, workload, and tier. scores returns one row per hardware.', - 'points 为每个硬件、负载和档位返回一行。scores 为每个硬件返回一行。', + 'points 针对每个硬件、工作负载与档位的组合返回一行;scores 针对每个硬件返回一行。', { type: 'string', enum: ['points', 'scores'], default: 'points' }, 'points', ), @@ -993,7 +993,7 @@ export const apiOperations: readonly ApiOperation[] = [ false, 'CSV number list', 'scores only. One non-negative weight per workload, normalized to sum to 1. Defaults to equal weights.', - '仅用于 scores。每个负载一个非负权重,并归一化为总和 1。默认等权。', + '仅用于 scores。每个工作负载对应一个非负权重;所有权重归一化后总和为 1,默认使用等权重。', { type: 'string' }, '0.5,0.5', ), @@ -1011,7 +1011,7 @@ export const apiOperations: readonly ApiOperation[] = [ responses: [ success( 'The selected points or scores envelope.', - '所选 points 或 scores 数据包。', + '所选 points 或 scores 视图的响应对象。', tcoFeedSchema, { model: 'dsv4', @@ -1292,7 +1292,7 @@ export const apiOperations: readonly ApiOperation[] = [ false, 'integer', 'Page size, clamped to 1 through 200.', - '每页数量,限制在 1 到 200。', + '每页数量;小于 1 时按 1 处理,大于 200 时按 200 处理。', { type: 'integer', minimum: 1, maximum: 200, default: 50 }, 50, ), @@ -1383,7 +1383,7 @@ export const apiOperations: readonly ApiOperation[] = [ true, 'string', 'Conversation ID exactly as listed by the conversation index.', - '会话索引中列出的原始会话 ID。', + '会话 ID,必须与会话索引中列出的值完全一致。', { type: 'string', minLength: 1 }, 'trace-018', ), @@ -1447,7 +1447,7 @@ export const apiOperations: readonly ApiOperation[] = [ responses: [ success( 'Latest CollectiveX run, coverage, series, and optional KV cases.', - '最新 CollectiveX 运行、覆盖、序列和可选 KV 案例。', + '最新的 CollectiveX 运行记录、覆盖情况、序列,以及可能存在的 KV 测试用例。', collectiveXDatasetSchema, { version: 1, @@ -1903,7 +1903,7 @@ export const apiOperations: readonly ApiOperation[] = [ summary: text('Read a request timeline', '读取请求时间线'), description: text( 'Returns a versioned benchmark window and per-request replay identity, source provenance, dispatch, acknowledgement, completion, token, phase, worker, and cancellation timing.', - '返回带版本的基准窗口,以及每个请求的重放标识、来源溯源、调度、确认、完成、token、阶段、worker 和取消时间。', + '返回带版本号的基准测试时间窗口,以及每个请求的重放标识、来源追溯信息、调度/确认/完成/取消时间、token 指标、阶段和 worker 信息。', ), audience: 'public', stability: 'beta', @@ -2011,7 +2011,7 @@ export const apiOperations: readonly ApiOperation[] = [ summary: text('Read a benchmark server log', '读取基准服务器日志'), description: text( 'Returns one stored .log/.out file for a benchmark result ID. Use file with a name from server-log-files. Add offset or limit for a bounded chunk; add download=1 to stream the complete selected file as a text attachment.', - '返回某个基准结果 ID 已存储的一个 .log/.out 文件。file 应使用 server-log-files 返回的文件名。可添加 offset 或 limit 读取有界分块;添加 download=1 可将完整的当前文件作为文本附件流式下载。', + '返回某个基准测试结果 ID 对应的一份已存储的 .log/.out 文件。file 应使用 server-log-files 返回的文件名。可添加 offset 或 limit,按指定范围分块读取;添加 download=1 可将完整的所选文件作为文本附件流式下载。', ), audience: 'public', stability: 'beta', @@ -2062,7 +2062,7 @@ export const apiOperations: readonly ApiOperation[] = [ false, 'integer', 'Set to 1 to stream the complete selected file as a text attachment. Cannot be combined with offset or limit.', - '设为 1 时,将完整的当前文件作为文本附件流式下载。不能与 offset 或 limit 同时使用。', + '设为 1 时,将完整的所选文件作为文本附件流式下载;不能与 offset 或 limit 同时使用。', { type: 'integer', enum: [1] }, 1, ), @@ -2121,7 +2121,7 @@ export const apiOperations: readonly ApiOperation[] = [ summary: text('Search complete benchmark logs', '搜索完整基准测试日志'), description: text( 'Runs a literal, case-insensitive search across every stored .log/.out file for one benchmark result. The bounded response contains contextual snippets without transferring complete log files.', - '对某个基准结果已存储的全部 .log/.out 文件执行不区分大小写的文本搜索。响应数量有上限,并返回上下文片段,无需传输完整日志文件。', + '在某个基准测试结果对应的所有已存储 .log/.out 文件中,按字面值执行不区分大小写的搜索。响应仅返回数量受限的上下文片段,无需传输完整日志文件。', ), audience: 'public', stability: 'beta', @@ -2142,7 +2142,7 @@ export const apiOperations: readonly ApiOperation[] = [ true, 'string', 'Literal search text, from 1 to 256 characters.', - '搜索文本,长度为 1 到 256 个字符,按原文匹配。', + '按字面值匹配的搜索文本,长度为 1 到 256 个字符。', { type: 'string', minLength: 1, maxLength: 256 }, 'router ready', ), @@ -2409,7 +2409,7 @@ export const apiOperations: readonly ApiOperation[] = [ summary: text('Read trace server metrics', '读取跟踪服务器指标'), description: text( 'Returns point metadata and chart-ready aggregate time series for cache usage, queue depth, prefill and decode throughput, and prompt-token sources. metricSources contains source descriptors; source-specific arrays are loaded by the point-detail UI only when selected.', - '返回点元数据,以及缓存使用率、队列深度、预填充和解码吞吐、提示 token 来源的图表聚合时间序列。metricSources 仅包含来源描述信息;详情页只会在用户选中某个来源时加载其专属数组。', + '返回数据点元数据,以及可直接用于图表的聚合时间序列,涵盖缓存使用率、队列深度、prefill/decode 吞吐量和 prompt token 来源。metricSources 包含各来源的描述信息;仅当用户在数据点详情界面选择某个来源时,才会加载该来源对应的数组。', ), audience: 'public', stability: 'beta', @@ -2502,7 +2502,7 @@ const overview = { eyebrow: text('Public data API', '公开数据 API'), description: text( 'Read benchmark, provenance, dataset, CollectiveX, and diagnostic data from the same sources that power InferenceX.', - '读取为 InferenceX 提供数据的基准、来源、数据集、CollectiveX 和诊断信息。', + '从 InferenceX 使用的同一组数据源中,读取基准测试数据、溯源信息、数据集、CollectiveX 数据和诊断数据。', ), auth: { title: text('Authentication', '身份验证'), @@ -2550,7 +2550,7 @@ const overview = { title: text('BenchmarkRow', 'BenchmarkRow'), description: text( 'Configuration fields sit beside a metrics map. Metric keys evolve independently; values are numbers, time metrics are seconds, and throughput metrics use tokens per second per GPU unless their name states otherwise.', - '配置字段与 metrics 映射并列。指标键可独立演进;值为数字,时间指标单位为秒,吞吐指标默认为每 GPU 每秒 token,除非名称另有说明。', + '配置字段与 metrics 映射位于同一层级。metrics 的键可独立扩展,各项指标值均为数字;时间指标以秒为单位。除非指标名另有说明,吞吐量指标均以 token/s/GPU 为单位。', ), shape: 'BenchmarkRows', example: benchmarkExample[0], @@ -2580,10 +2580,10 @@ const overview = { const quickstartOperationIds = ['get-availability', 'list-benchmarks'] as const; const quickstartText = { 'get-availability': { - title: text('Discover configurations', '发现可用配置'), + title: text('Discover configurations', '查询可用配置'), description: text( 'Start with availability to choose real model, hardware, framework, and sequence values.', - '先读取可用配置,以选择真实的模型、硬件、框架和序列值。', + '先查询 availability,选择实际可用的模型、硬件、框架和序列取值。', ), }, 'list-benchmarks': { diff --git a/packages/app/src/lib/blog-content.test.ts b/packages/app/src/lib/blog-content.test.ts index 359c7bbd4..ff11bb975 100644 --- a/packages/app/src/lib/blog-content.test.ts +++ b/packages/app/src/lib/blog-content.test.ts @@ -44,21 +44,6 @@ function tagList(raw: string): string[] { ); } -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 [ @@ -70,17 +55,8 @@ function localImageRefs(raw: string): string[] { const countFigures = (raw: string) => (raw.match(/ (raw.match(/^\$\$\s*$/gmu) ?? []).length; -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); +it('checks every current locale pair in both directions', () => { + expect(enFiles.length).toBeGreaterThan(0); expect(zhFiles).toEqual(enFiles); }); @@ -127,27 +103,3 @@ 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 3669d6fb1..18fe9c024 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: @@ -110,11 +110,11 @@ const translations: Readonly> = { definition: '闭环基准测试中的客户端会在前一个依赖请求完成后生成新工作,同时遵循工作负载记录的等待时间和分支结构。', explanation: - '并发量表示活跃客户端或会话的数量,同时在途的请求数会随时间变化。系统越快,完成轮次越早,因此在同一个 profiling 窗口内发出的请求也越多。由于每条采样会话的推进取决于请求完成时间,实际请求组合可能略有变化。', + '并发量表示活跃客户端或会话的数量,但同时在途的请求数会随时间变化。系统越快,每一轮就完成得越早,因此在同一个 profiling 窗口内会发出更多请求。由于每条采样会话的进度取决于请求完成时间,实际请求组合可能略有不同。', significance: '这种负载模型符合交互式 agent 的运行方式,因为下一步动作依赖上一步结果。响应更快时,会话也会更快地产生后续工作,所以吞吐量与延迟相互关联。低并发运行的采样波动通常会比大型请求池更明显。', benchmarkContext: - 'AgentX 采用闭环并发。并发量表示同时运行的 agent 客户端数量;随着会话推进,请求批次大小会不断变化。解读结果时,应结合吞吐量、首 token 延迟和交互性。', + 'AgentX 采用闭环并发。这里的并发量指同时运行的 agent 客户端数量;随着会话推进,每个批次中的请求数也会变化。解读结果时,应结合吞吐量、首 token 延迟和交互性。', }, subagent: { term: '子智能体', @@ -226,7 +226,7 @@ const translations: Readonly> = { '批处理就像让多名乘客坐同一辆巴士:芯片一次处理多个请求,让每趟计算完成更多有效工作。', definition: '批处理将多个请求的工作组合起来,使加速器能够一起处理它们的 token。', explanation: - '大型矩阵运算比大量微小运算更能发挥芯片效率。现代推理引擎采用连续批处理,请求到达和结束时动态加入或退出,无需等待固定批次全部完成。由此形成的批次形状会在预填充和解码过程中不断变化。', + '大型矩阵运算比大量微小运算更能发挥芯片效率。现代推理引擎采用连续批处理;随着请求到达或完成,序列会动态加入或退出,无需等待固定批次全部处理完。因此,batch 形状会在 prefill 和 decode 过程中不断变化。', significance: '批处理是吞吐量与延迟核心权衡的来源。更大的有效批次能摊薄权重读取和内核启动开销,但通常会增加每位用户的 token 间隔。', benchmarkContext: @@ -279,7 +279,7 @@ const translations: Readonly> = { explanation: 'InferenceX 根据每小时总体拥有成本和实测 token 吞吐量计算该指标。它可能按总 token 报告,也可能区分输入和输出 token,因此比较前必须确认分母。', significance: - '该指标把系统性能转化为服务经济性,但仍受工作负载、交互性、利用率、缓存命中和成本假设影响;低吞吐量的离线运行点与高交互性实时端点属于不同的运行区间,不能直接比较。', + '工作负载形状、交互性、利用率、缓存行为和成本假设,共同决定两个数值能否直接比较。低吞吐量的离线运行点与曲线高交互性一端的运行点属于不同运行区间,不能直接比较。', benchmarkContext: '成本曲线使用与吞吐曲线相同的并发扫描。在等交互性下,更低的 $/M 表示以更少建模成本提供相同流式体验。', measurement: { @@ -390,15 +390,15 @@ const translations: Readonly> = { term: '投机解码', aliases: ['speculative decoding', '草稿与验证解码'], plainEnglish: - '投机解码让一个便宜的助手先起草多个 token,再由完整模型一次性审核,省去部分逐个生成步骤。', + '投机解码先让成本较低的辅助模块提前生成多个 token,再交给完整模型一次性验证,从而减少逐个生成的步骤。', definition: - '投机解码先以低成本提出多个未来 token,再由目标模型批量验证,从而减少昂贵的串行解码步数。', + '投机解码先以较低成本生成多个候选 token,再由目标模型批量验证,从而减少成本较高的串行 decode 步骤。', explanation: - '草稿模型或内置预测头生成候选,目标模型在一次批量验证中评估这些候选并接受有效前缀;严格实现时不会改变目标分布。', + 'draft model 或内置预测头会生成候选 token,目标模型再通过一次批量验证评估这些候选,并接受其中有效的连续前缀。只要算法实现严格,目标分布就不会改变。', significance: '加速取决于草稿 token 的接受数量,以及草稿与验证成本。稠密模型和 MoE 的表现可能不同,因为验证多个位置可能激活更多专家权重。', benchmarkContext: - '应在真实接受率下比较投机解码方案并验证模型质量。定长场景仍把投机解码作为曲线标识的一部分,因此开启和关闭 MTP 的方案会分开绘制;agentic 曲线则把它当作数据点级元数据并合并这些点,在提示框中标明具体方式,因为 AgentX 按模型、芯片 SKU 和引擎给出可获得的最佳曲线。由于 AgentX 回放的内容是合成的,speculator 接受的 draft token 数会失真,因此运行时会套用一套按模型、speculator、draft 长度和思考模式在外部 agentic 编码数据集上采集的接受长度。', + '在固定序列场景中,投机解码属于曲线标识的一部分,因此启用和未启用 MTP 的测试配置会分别绘制。Agentic 曲线则将投机解码作为数据点级元数据,并将这些点合并到同一条曲线中;每个提示框仍会注明具体方式。这是因为 AgentX 按模型、芯片 SKU 和引擎展示可达到的最佳曲线。由于 AgentX 回放使用合成内容,speculator 接受的 draft token 数并不具有代表性,因此运行时会使用从外部 agentic 编码数据集采集的接受长度,并按模型、speculator、draft 长度和思考模式分别配置。', }, 'multi-token-prediction': { term: '多 token 预测', @@ -407,24 +407,24 @@ const translations: Readonly> = { definition: '多 token 预测(MTP)使用与主模型共同训练的辅助预测头,提出多个未来 token 供投机验证。', explanation: - 'MTP 不需要独立草稿模型,候选来自目标模型自身表示,因此分布更一致、部署也更简单;但它要求检查点包含兼容 MTP 模块,且推理引擎支持验证路径。', + 'MTP 不需要单独的 draft model;候选来自目标模型自身的表示,因此通常更贴合目标模型,也更易部署。不过,checkpoint 必须包含兼容的 MTP 模块,推理引擎也必须支持对应的验证路径。', significance: - 'MTP 可用额外计算换取更少的内存受限解码步。草稿接受率高且验证能利用空闲计算时收益最大;大批次下额外工作可能减少优势。', + 'MTP 可以利用原本闲置的计算资源,减少受显存带宽限制的 decode 步骤。draft token 接受率较高、且验证能够利用可用算力时,收益最大;batch 较大时,额外计算带来的收益可能下降。', benchmarkContext: - 'InferenceX 将 MTP 作为方案维度。把基准收益迁移到生产时,必须考虑接受率/长度、工作负载分布、数值质量检查与匹配交互性。', + 'InferenceX 将 MTP 作为测试配置中的一个维度。评估基准测试中的提升能否在生产环境复现时,需要同时考虑接受率或接受长度、工作负载分布、数值质量检查和匹配交互性。', }, eagle: { term: 'EAGLE', aliases: ['EAGLE 投机解码', 'EAGLE-3'], plainEnglish: 'EAGLE 是一种为主模型起草多个可能后续 token 的方法,可让答案流式输出得更快。', definition: - 'EAGLE 是一组投机解码方法:利用与目标语言模型相关的特征预测草稿序列,再由目标模型验证。', + 'EAGLE 是一类投机解码方法:它根据与目标语言模型相关的特征预测 draft 序列,再交由目标模型验证。', explanation: - '推理框架通常通过投机步数、草稿 token 数和候选宽度等参数暴露 EAGLE。模型检查点、草稿组件与引擎实现必须匹配。', + '推理框架通常提供投机步数、draft token 数和候选宽度等参数来配置 EAGLE。模型 checkpoint、draft 组件和引擎实现必须相互匹配。', significance: - 'EAGLE 能提高每个目标模型步接受的 token 数,但结果依赖工作负载;接受行为、草稿开销、模型架构和批大小共同决定端到端收益。', + 'EAGLE 可以增加目标模型每一步接受的 token 数,但实际效果取决于工作负载。接受情况、draft 开销、模型架构和 batch 大小,共同决定这条额外路径能否提升端到端服务性能。', benchmarkContext: - '部分 InferenceX 曲线标注 MTP,是因为模型提供多 token 预测头,而引擎使用 EAGLE 风格管线。应查看方案参数与检查点细节,不能假设所有 MTP 曲线实现相同。', + '部分 InferenceX 曲线将这一功能标记为 MTP,因为模型提供多 token 预测头,而引擎使用的是 EAGLE 风格的投机解码管线。具体实现以测试配置中的 flags 和 checkpoint 详情为准。', }, 'tensor-parallelism': { term: '张量并行', @@ -699,7 +699,7 @@ const translations: Readonly> = { significance: 'SGLang 快速迭代的版本和模型专用内核可在硬件不变时显著改变吞吐量;低并发受调度开销影响,其他区间则由注意力、MoE 与通信内核主导。', benchmarkContext: - 'InferenceX 持续重跑版本固定的 SGLang 方案。对比不同版本的曲线,可以看出改动如何影响完整运行区间,并发现单个峰值点掩盖的回归或提升。', + 'InferenceX 会持续重跑固定版本的 SGLang 测试配置。对比不同版本的曲线,可以看出改动对整个运行区间的影响,也能发现单看峰值时容易被掩盖的回归或提升。', }, 'tensorrt-llm': { term: 'TensorRT-LLM', diff --git a/packages/app/src/lib/glossary.test.ts b/packages/app/src/lib/glossary.test.ts index 4e5903941..88f8db199 100644 --- a/packages/app/src/lib/glossary.test.ts +++ b/packages/app/src/lib/glossary.test.ts @@ -135,45 +135,6 @@ 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 deleted file mode 100644 index 19f807e7f..000000000 --- a/packages/app/src/lib/land-acknowledgement-content.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -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 6ec8cfc57..033c50b34 100644 --- a/packages/app/timings.json +++ b/packages/app/timings.json @@ -88,10 +88,6 @@ "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