- 开源持续智能体推理基准测试——受万亿美元级吉瓦规模 Token 工厂运营者的信赖
+ 持续运行的开源智能体推理基准测试,受到万亿美元级、吉瓦规模 Token 工厂运营方的信赖
- 随着世界以指数级速度迈向
- AGI,软件开发和模型发布日新月异。现有基准测试因其静态性质而迅速过时,参与者往往提交专为基准测试定制的软件镜像,无法反映真实的线上推理性能。
+ 全球迈向 AGI
+ 的进程呈指数级加速,软件开发和模型发布也日新月异。静态基准测试很快就会过时;参与者提交的软件镜像往往是为基准测试专门构建的,无法反映真实应用场景中的性能。
InferenceX™ (原名
- InferenceMAX)是我们独立、厂商中立、可复现的基准测试平台。它测试固定序列推理服务和
- AgentX 长上下文多轮智能体编码工作负载,覆盖 ML 社区实际可用的各类 AI 加速器与服务栈。
+ InferenceMAX)是我们建立的独立、厂商中立且可复现的基准测试平台。它覆盖 ML
+ 社区实际可用的各类 AI 加速器和服务栈,测试固定序列推理服务以及 AgentX
+ 长上下文、多轮智能体编码工作负载。
- 我们的开放数据与洞察已被 ML 社区广泛采用,包括万亿美元级 Token 工厂和 AI
- 实验室的容量规划策略团队,以及多家数十亿美元级
- NeoCloud。了解更多详情请阅读我们的文章:{' '}
+ 我们的开放数据与洞察已被 ML 社区广泛采用,用户包括万亿美元级 Token 工厂和 AI
+ 实验室中负责容量规划与战略的团队,以及多家价值数十亿美元的
+ NeoCloud。更多详情请参阅以下文章:{' '}
- InferenceX v1(英文文章)
+ InferenceX v1
、{' '}
- InferenceX v2(英文文章)
+ InferenceX v2
。
@@ -84,9 +83,8 @@ export default function AboutPageZh() {
可复现性
- 仪表板上的每一个数据点均来自公开的 GitHub Actions
- 工作流运行。测试配方、日志、产物以及数据库记录端到端关联,任何人都可以审计、重新运行或
- fork 基准测试。
+ 仪表板上的每个数据点都由公开的 GitHub Actions
+ 工作流生成。测试配置、日志、产物及其对应的数据库记录彼此关联,任何人都可以核查结果来源、重新运行测试,或基于现有配置创建新的基准测试。
@@ -94,9 +92,9 @@ export default function AboutPageZh() {
1
- 配方提交至仓库。 {' '}
- 每种硬件、框架、模型和精度的组合都是一个提交到公开仓库的 shell
- 脚本。镜像、命令行和并行度均在源码中固定。
+ 测试配置已提交到仓库。 {' '}
+ 每组硬件、框架、模型和精度组合都对应一个提交到公开仓库的 shell
+ 脚本。脚本中固定了所用镜像、命令行参数和并行度。
@@ -125,10 +123,9 @@ export default function AboutPageZh() {
4
- 导入仪表板。 {' '}
- 成功的运行将被加载到数据库中并在此展示。每个图表 tooltip
- 都附带一个直接链接,指向生成该数据点的 GitHub Actions
- 运行。点击任意数据点即可审计其来源。
+ 将结果导入仪表板。 {' '}
+ 运行成功后,结果将写入数据库并在仪表板中展示。每个图表的提示框都提供对应的 GitHub
+ Actions 运行记录链接。点击任意数据点即可核查其来源。
@@ -139,7 +136,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"
>
- 浏览工作流运行
+ 查看工作流运行记录
- 查看基准测试配方
+ 查看测试配置
·
- {meta.readingTime} 分钟阅读
+ 预计阅读 {meta.readingTime} 分钟
·
阅读英文原文
@@ -135,7 +135,7 @@ export default async function ZhBlogPostPage({ params }: Props) {
{headings.length > 0 && (
-
+
)}
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/land-acknowledgement/page.tsx b/packages/app/src/app/zh/land-acknowledgement/page.tsx
index ddfcffc27..1bc6b5cff 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() {
{REGIONAL_ACKNOWLEDGEMENTS_ZH.map((entry) => (
- 致谢只是一个起点。我们怀着对原住民主权、历史和持续存在的社区的尊重分享这份声明,如果措辞需要改进,欢迎指正。
+ 传统领地声明只是一个起点。我们发布本声明,以表达对原住民族主权、历史和当代社群的尊重。如有任何措辞需要改进,欢迎指正。
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 8b247b103..d81a65542 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 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 成本(总计、输入、输出)——涵盖超大规模云、NeoCoud 和裸机租赁定价',
+ 'AgentX 场景的端到端延迟、token 间延迟(ITL)、输出吞吐量、prefix cache 行为以及会话与子智能体执行情况',
+ '每百万 token 成本(总计、输入、输出)——涵盖超大规模云、NeoCloud 和租赁定价',
'每 token 能耗(焦耳,总计、输入、输出)',
'用户自定义成本和功耗计算',
],
@@ -83,25 +83,25 @@ export const FAQ_ITEMS_ZH: readonly FaqItem[] = [
id: 'faq-benchmark-differences',
question: 'InferenceX 与其他 AI 基准测试有何不同?',
answer:
- 'InferenceX 在真实硬件上运行固定序列工作负载与 AgentX 长上下文多轮编码场景。测试配方保存在代码仓库中,每项结果均链接至对应的 GitHub Actions 运行。',
+ 'InferenceX 在真实硬件上运行固定序列工作负载和 AgentX 长上下文多轮编码场景。测试配置保存在代码仓库中,每项结果都链接到对应的 GitHub Actions 运行记录。',
},
{
id: 'faq-reproducibility',
- question: '结果如何实现可复现?',
+ question: '如何保证结果可复现?',
answer:
- '仪表板上的每一个数据点均由公开的 GitHub Actions 工作流运行产生。测试配方(模型、框架、精度、并行度、序列长度、并发数)已提交至仓库,在目标硬件上实际执行,产物(日志、指标、芯片追踪数据)上传至运行页面。用户可从任何图表的 tooltip 直接点击链接,跳转到生成该数据点的 GitHub Actions 运行。',
+ '仪表板上的每个数据点都由公开的 GitHub Actions 工作流生成。测试配置(模型、框架、精度、并行度、序列长度和并发数)保存在代码仓库中,并在对应的目标硬件上运行。日志、指标和芯片追踪数据等产物会上传到运行记录页面。每个图表的提示框都提供链接,可直接打开生成该数据点的 GitHub Actions 运行记录。',
},
{
id: 'faq-raw-logs',
question: '在哪里可以查看原始基准测试日志?',
answer:
- '在图表上点击任意数据点即可打开 tooltip。其中的"GitHub Actions Run"链接将直接跳转到生成该数据点的工作流运行。在那里您可以查看完整的任务日志、框架和驱动版本、命令行参数,以及下载原始产物(包括请求延迟、token 计数和芯片功耗遥测数据)。',
+ '点击图表中的任意数据点即可打开提示框。其中的“GitHub Actions 运行记录”链接会直接跳转到生成该数据点的工作流运行。您可以在那里查看完整的任务日志、框架和驱动版本、命令行参数,并下载原始产物,包括请求延迟、token 计数和芯片功耗遥测数据。',
},
{
id: 'faq-rerun-benchmark',
question: '我可以自己重新运行基准测试吗?',
answer:
- '可以。基准测试配方位于代码仓库的 /benchmarks 目录中,以独立的 shell 脚本形式存在。如果您拥有相同的硬件,可以 fork 仓库并直接运行脚本,或触发相同的 GitHub Actions 工作流来复现结果。',
+ '可以。基准测试配置以可独立运行的 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.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 4db1ee4ad..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,7 @@ export function ApiReferencePage({ locale }: { locale: ApiDocumentationLocale })
{documentation.schemaNotes.map((schema) => (
{schema.title}
diff --git a/packages/app/src/components/blog/blog-shared-chrome.test.tsx b/packages/app/src/components/blog/blog-shared-chrome.test.tsx
new file mode 100644
index 000000000..d28378724
--- /dev/null
+++ b/packages/app/src/components/blog/blog-shared-chrome.test.tsx
@@ -0,0 +1,26 @@
+import { renderToStaticMarkup } from 'react-dom/server';
+import { describe, expect, it } from 'vitest';
+
+import type { TocHeading } from '@/lib/blog';
+import { BlogToc } from './blog-toc';
+
+const headings = [{ id: 'results', text: 'Results', level: 2 }] satisfies TocHeading[];
+
+describe('shared Blog localization chrome', () => {
+ it('preserves the existing English table-of-contents copy', () => {
+ const toc = renderToStaticMarkup( );
+
+ expect(toc).toContain('aria-label="Table of contents"');
+ expect(toc).toContain('On this page');
+ expect(toc).toContain('(click to expand)');
+ });
+
+ it('renders the equivalent Chinese table of contents without English fallback copy', () => {
+ const toc = renderToStaticMarkup( );
+
+ expect(toc).toContain('aria-label="本页目录"');
+ expect(toc).toContain('(点击展开)');
+ expect(toc).not.toContain('Table of contents');
+ expect(toc).not.toContain('click to expand');
+ });
+});
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..565329527 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);
@@ -36,13 +43,13 @@ export function HeadingLink({ id }: { id: string }) {
{state === 'idle' ? (
) : (
- Link copied
+ {t.copied}
)}
);
diff --git a/packages/app/src/components/blog/mdx-components.tsx b/packages/app/src/components/blog/mdx-components.tsx
index b684c78e4..e422ed871 100644
--- a/packages/app/src/components/blog/mdx-components.tsx
+++ b/packages/app/src/components/blog/mdx-components.tsx
@@ -105,7 +105,7 @@ export function createMdxComponents(
return (
{props.children}
-
+
);
},
@@ -114,7 +114,7 @@ export function createMdxComponents(
return (
{props.children}
-
+
);
},
@@ -123,7 +123,7 @@ export function createMdxComponents(
return (
{props.children}
-
+
);
},
diff --git a/packages/app/src/lib/api-documentation.ts b/packages/app/src/lib/api-documentation.ts
index 4a22d16c5..ce5b6482e 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',
@@ -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: [
@@ -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',
),
],
@@ -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 8b660d5c7..ff11bb975 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,15 +37,11 @@ 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(),
+ );
}
/** Local `` and markdown `` references. */
@@ -55,8 +55,9 @@ function localImageRefs(raw: string): string[] {
const countFigures = (raw: string) => (raw.match(/ (raw.match(/^\$\$\s*$/gmu) ?? []).length;
-it('finds English posts to check', () => {
+it('checks every current locale pair in both directions', () => {
expect(enFiles.length).toBeGreaterThan(0);
+ 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 b5fe98d6c..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:
- 'Concurrency 表示活跃客户端或会话数量,同时存在的请求数会随时间变化。更快的系统更早完成轮次,因此会在同一个 profiling 窗口内发出更多请求。每条采样会话的推进速度取决于请求完成时间,实际请求组合可能有小幅变化。',
+ '并发量表示活跃客户端或会话的数量,但同时在途的请求数会随时间变化。系统越快,每一轮就完成得越早,因此在同一个 profiling 窗口内会发出更多请求。由于每条采样会话的进度取决于请求完成时间,实际请求组合可能略有不同。',
significance:
'这种负载模型符合交互式 agent 的运行方式,因为下一步动作依赖上一步结果。响应更快时,会话也会更快地产生后续工作,所以吞吐量与延迟相互关联。低并发运行的采样波动通常会比大型请求池更明显。',
benchmarkContext:
- 'AgentX 使用闭环 concurrency。该数值表示同时运行的 agent 客户端数量;request batch 会随着会话推进而变化。解读结果时需要结合吞吐量、首 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: {
@@ -309,7 +309,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 吞吐量',
@@ -344,7 +344,7 @@ const translations: Readonly> = {
explanation:
'每个新 token 都依赖此前 token,因此时间维度无法完全并行。模型会反复读取权重与该序列的 KV 缓存,使解码对内存带宽、批处理和通信尤其敏感。',
significance:
- '解码决定流式交互性,也常主导长输出成本。推测解码、MTP、量化和宽专家并行都试图减少每个有效 token 的工作量或耗时。',
+ '解码决定流式交互性,也常主导长输出成本。投机解码、MTP、量化和宽专家并行都试图减少每个有效 token 的工作量或耗时。',
benchmarkContext:
'InferenceX 用 tok/s/user 与总 tok/s/chip 展示不同并发下的解码性能。公平比较必须匹配输出长度、批形状、精度和并行策略。',
},
@@ -387,44 +387,44 @@ const translations: Readonly> = {
'InferenceX 中的 disagg 不是万能开关。应查看预填充/解码 world size、TP/EP 布局、框架、网络域,以及分离前沿真正领先的交互性区间。',
},
'speculative-decoding': {
- term: '推测解码',
+ term: '投机解码',
aliases: ['speculative decoding', '草稿与验证解码'],
plainEnglish:
- '推测解码让一个便宜的助手先起草多个 token,再由完整模型一次性审核,省去部分逐个生成步骤。',
+ '投机解码先让成本较低的辅助模块提前生成多个 token,再交给完整模型一次性验证,从而减少逐个生成的步骤。',
definition:
- '推测解码先以低成本提出多个未来 token,再由目标模型批量验证,从而减少昂贵的串行解码步数。',
+ '投机解码先以较低成本生成多个候选 token,再由目标模型批量验证,从而减少成本较高的串行 decode 步骤。',
explanation:
- '草稿模型或内置预测头生成候选,目标模型在一次批量验证中评估这些候选并接受有效前缀;严格实现时不会改变目标分布。',
+ 'draft model 或内置预测头会生成候选 token,目标模型再通过一次批量验证评估这些候选,并接受其中有效的连续前缀。只要算法实现严格,目标分布就不会改变。',
significance:
'加速取决于草稿 token 的接受数量,以及草稿与验证成本。稠密模型和 MoE 的表现可能不同,因为验证多个位置可能激活更多专家权重。',
benchmarkContext:
- '应在真实接受率下比较投机解码方案并验证模型质量。定长场景仍把投机解码作为曲线标识的一部分,因此开启和关闭 MTP 的方案会分开绘制;agentic 曲线则把它当作数据点级元数据并合并这些点,在 tooltip 中标明具体方式,因为 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 预测',
aliases: ['multi-token prediction', '多 token 预测头'],
plainEnglish: 'MTP 让模型一次猜测多个后续 token 并一起验证,从而减少缓慢的逐 token 步骤。',
definition:
- '多 token 预测(MTP)使用与主模型共同训练的辅助预测头,提出多个未来 token 供推测验证。',
+ '多 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'],
+ 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: '张量并行',
@@ -695,11 +695,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',
@@ -785,7 +785,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 offloading',
@@ -881,7 +881,7 @@ const translations: Readonly> = {
significance:
'芯片峰值规格无法描述服务性能,同一颗芯片在不同配置下可以相差数倍。把整套组合写清楚,结论才可核查:脱离配置的单个数字既无法复现,也无法与其他厂商公平比较。',
benchmarkContext:
- 'InferenceX 的测试配置主要跟随 vLLM 与 SGLang 官方 cookbook,并使用上游镜像,因此结果反映用户实际能部署的性能,而不是为基准测试特调过的镜像。数据点 tooltip 会展示背后的配置,并给出运行溯源链接。',
+ 'InferenceX 的测试配置主要跟随 vLLM 与 SGLang 官方 cookbook,并使用上游镜像,因此结果反映用户实际能部署的性能,而不是为基准测试特调过的镜像。数据点的提示框会展示背后的配置,并给出运行溯源链接。',
},
'tail-latency': {
term: '尾部延迟',
@@ -1025,7 +1025,7 @@ const translations: Readonly> = {
significance:
'对最大的那批模型来说,这首先是容量手段,其次才是提速手段。有些前沿模型根本装不进单个节点,流水线并行才让它们可服务;当某项竞争性优化拒绝与任何方案组合时,它甚至是唯一选项。',
benchmarkContext:
- 'InferenceX 在数据点 tooltip 和并行标签中与 TP、EP、DP 一起展示流水线并行度,且仅在大于 1 时显示。可组合性与并行度同样重要:一种会导致投机解码无法启用的阶段切分,代价可能超过它节省的显存。',
+ 'InferenceX 在数据点提示框和并行标签中与 TP、EP、DP 一起展示流水线并行度,且仅在大于 1 时显示。可组合性与并行度同样重要:一种会导致投机解码无法启用的阶段切分,代价可能超过它节省的显存。',
},
'dp-attention': {
term: '数据并行注意力',
@@ -1039,7 +1039,7 @@ const translations: Readonly> = {
significance:
'由于每个 rank 只拥有缓存池的私有一份,请求落在哪里就成了影响性能的关键:长会话一旦被路由到不持有其前缀的 rank,就要全部重算。此时实测命中率会远低于理论上限,而原因与缓存大小毫无关系。',
benchmarkContext:
- 'InferenceX 会在数据点 tooltip 的并行策略部分展示 DP attention。它是否有利取决于模型;当缓存局部性变成路由约束时,不启用它的配置有时反而占据前沿曲线。',
+ 'InferenceX 会在数据点提示框的并行策略部分展示 DP attention。它是否有利取决于模型;当缓存局部性变成路由约束时,不启用它的配置有时反而占据前沿曲线。',
},
int4: {
term: 'INT4',
diff --git a/packages/app/src/lib/glossary.test.ts b/packages/app/src/lib/glossary.test.ts
index e524c5805..88f8db199 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();