diff --git a/content/blog/blog-20261104-ascend-support/index.md b/content/blog/blog-20261104-ascend-support/index.md index 1f2ea73..4cbb51a 100644 --- a/content/blog/blog-20261104-ascend-support/index.md +++ b/content/blog/blog-20261104-ascend-support/index.md @@ -5,7 +5,6 @@ summary: ModelSphere 模型目录新增昇腾部署方案。本文以 GLM-5.3-Fl kind: internal slug: ascend-support publishedAt: 2026-11-06 -draft: true updatedAt: 2026-11-06T17:19:00+08:00 evidenceIds: [] categoryId: performance diff --git a/docs/design/page-contracts.md b/docs/design/page-contracts.md index 290ee11..14f87d5 100644 --- a/docs/design/page-contracts.md +++ b/docs/design/page-contracts.md @@ -147,7 +147,7 @@ Catalog 卡片桌面最多 4 列、平板 2 列、手机 1 列;硬件使用 ch - `sort` 默认 `improvement-desc`,本期只比较已明确标注的源记录最佳负载百分比,保留条件和测量版本,不解释为统一吞吐性能排名;另支持 name-asc/name-desc/updated-desc。无值置后,同值按稳定 id。模型视图按“调优配方最佳提升”排序并展示对应 baseline、负载和版本,模型卡片仅显示可用配方数量,完整配方在模型详情和表格中展示,不复用默认配方卡片。 - 配方卡片:模型名称为主标题并进入模型详情,下方依次显示配方名称、算力卡型号和数量;后两项使用相同的小字号与弱化颜色,配方名称进入配方详情,禁止从配方名称推断硬件配置。 - `page`:默认 1,每页 12 条;先过滤再排序和分页。初始 HTML 提供完整精简列表与原生详情链接,增强成功后接管交互。 -- 配方页:`tab` 为 deployment / performance,缺省、旧 history 或未知值显示部署配置。切换保留配方身份、`version`、`dataVersion` 与 `returnTo`,支持分享、刷新及前进后退;方向键和 Home/End 移动 Tab 焦点,Enter/Space 激活。 +- 配方页:主标题显示模型名称(缺失时回退模型 ID),紧随其后突出目录提供的硬件型号与数量;配方自定义名称和当前版本降为辅助信息,浏览器标题同步包含模型与硬件,不从配方名称推断显卡配置。`tab` 为 deployment / performance,缺省、旧 history 或未知值显示部署配置。切换保留配方身份、`version`、`dataVersion` 与 `returnTo`,支持分享、刷新及前进后退;方向键和 Home/End 移动 Tab 焦点,Enter/Space 激活。 - 性能 Tab:`modelId`、`recipeId` 定位优化配方,`dataVersion` 固定目录提交;展示该目录中配方最新测量版本关联的首份报告,不支持任意配方比较或历史报告选择。参数不完整、记录不存在、无关联报告和目录版本不可用分别提示,不替换为其他记录。 - `returnTo`:模型、配方和报告页传递原目录查询,仅允许站内 `/models` 地址,保留搜索、筛选、视图、排序及页码;缺省或无效时返回 `/models`。报告的 `dataVersion` 不写回原目录查询。 - 旧 `/models/report?…`、`/models?benchmark={modelId}/{recipeId}…#benchmark` 和 `/models/recipe?…#benchmark` 链接以 history.replace 转入配方性能 Tab;移除返回地址中的旧 benchmark,避免返回循环。 diff --git a/src/features/catalog/DetailPage.tsx b/src/features/catalog/DetailPage.tsx index 4b5facb..d03fc93 100644 --- a/src/features/catalog/DetailPage.tsx +++ b/src/features/catalog/DetailPage.tsx @@ -9,6 +9,7 @@ import useIsBrowser from '@docusaurus/useIsBrowser'; import { useHistory, useLocation } from '@docusaurus/router'; import { config, + hardwareText, loadHistory, loadYaml, modelHref, @@ -202,7 +203,9 @@ function DetailContent({ kind }: { kind: 'model' | 'recipe' }) { kind === 'recipe' && recipe ? translate( { id: 'site.fc599ecea048', message: '{v0} · 配方详情' }, - { v0: recipe.variant.id }, + { + v0: `${recipe.model.displayName?.trim() || recipe.model.name} · ${translateCopy(hardwareText(recipe.variant))}`, + }, ) : (model?.displayName ?? translate({ id: 'site.f7121092ad68', message: '模型与配方详情' })) diff --git a/src/features/catalog/RecipeOverview.tsx b/src/features/catalog/RecipeOverview.tsx index e24e277..98cd56a 100644 --- a/src/features/catalog/RecipeOverview.tsx +++ b/src/features/catalog/RecipeOverview.tsx @@ -13,7 +13,7 @@ import { topologyLabel, type Recipe, } from './data'; -import { SourceLinks } from './components'; +import { Icon, SourceLinks } from './components'; import styles from './styles.module.css'; import GainMeter from './GainMeter'; @@ -40,6 +40,7 @@ export default function RecipeOverview({ i18n: { currentLocale }, } = useDocusaurusContext(); const { model, variant } = recipe; + const modelName = model.displayName?.trim() || model.name; const badge = role(recipe); const image = variant.image; const imageText = image @@ -101,28 +102,42 @@ export default function RecipeOverview({ aria-label={translate({ id: 'site.9770f40287d5', message: '配方概览' })} >
- {model.displayName ?? model.name}{' '} +
+ {translate({ id: 'catalog.recipeName', message: '配方' })}{' '}
+ {variant.id}
{translate({
id: 'site.c40529702643',
message: ' · 当前配方版本 ',
diff --git a/src/features/catalog/styles.module.css b/src/features/catalog/styles.module.css
index dd76d45..d3524b3 100644
--- a/src/features/catalog/styles.module.css
+++ b/src/features/catalog/styles.module.css
@@ -1322,16 +1322,51 @@
.recipeOverview {
margin-top: 0;
}
+.recipeIdentity {
+ flex: 1;
+ min-width: 0;
+ max-width: 100%;
+}
+.recipeHeading {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 8px 16px;
+}
.recipeTitle {
+ min-width: 0;
margin: 0;
font: 700 clamp(22px, 3vw, 32px) / 1.4 var(--font-heading);
overflow-wrap: anywhere;
}
+.recipeHardwareSummary {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ margin: 8px 0 12px;
+ color: var(--color-text-primary);
+ font: 600 clamp(18px, 2vw, 22px) / 1.4 var(--font-heading);
+}
+.recipeHardwareSummary > svg {
+ flex: none;
+ width: 1em;
+ height: 1em;
+ margin-top: 0.2em;
+ color: var(--color-text-muted);
+}
+.recipeHardwareSummary > strong {
+ min-width: 0;
+ font: inherit;
+ overflow-wrap: anywhere;
+}
+.recipeMetadata {
+ overflow-wrap: anywhere;
+}
.recipeBadges {
display: flex;
flex-wrap: wrap;
gap: 6px;
- margin: 8px 0;
+ margin: 0;
}
.recipeBadges > span {
font-size: 12px;
diff --git a/tests/e2e/catalog.spec.ts b/tests/e2e/catalog.spec.ts
index 551536e..39e80cc 100644
--- a/tests/e2e/catalog.spec.ts
+++ b/tests/e2e/catalog.spec.ts
@@ -846,7 +846,7 @@ for (const layout of ['cards', 'table'])
baseline.commit,
);
await expect(page.getByRole('heading', { level: 1 })).toHaveText(
- variant.id,
+ m.displayName?.trim() || m.name,
);
if (layout === 'cards') {
await expect(
diff --git a/tests/e2e/detail-loading.spec.ts b/tests/e2e/detail-loading.spec.ts
index f8c2eb4..1856764 100644
--- a/tests/e2e/detail-loading.spec.ts
+++ b/tests/e2e/detail-loading.spec.ts
@@ -111,7 +111,7 @@ for (const kind of ['model', 'recipe'] as const)
);
await expect(loading(page)).toHaveCount(0);
await expect(page.getByRole('heading', { level: 1 })).toHaveText(
- kind === 'model' ? (model.displayName ?? model.name) : recipe.id,
+ model.displayName?.trim() || model.name,
);
expect(await page.locator('main').innerText()).not.toContain(
'JavaScript',
diff --git a/tests/e2e/recipe-overview.spec.ts b/tests/e2e/recipe-overview.spec.ts
index 08ecf4c..b4615ea 100644
--- a/tests/e2e/recipe-overview.spec.ts
+++ b/tests/e2e/recipe-overview.spec.ts
@@ -88,12 +88,51 @@ for (const width of [320, 1440])
await expect(
page.getByRole('tabpanel', { name: '部署配置' }).locator('pre'),
).toBeVisible();
+ const title = overview(page).getByRole('heading', { level: 1 });
+ const hardware = overview(page)
+ .getByRole('group', { name: '硬件', exact: true })
+ .locator('strong');
+ const recipeName = overview(page)
+ .locator('p')
+ .filter({ hasText: '当前配方版本' });
+ await expect(title).toHaveText('GLM 5.3 (NVFP4)');
+ await expect(hardware).toHaveText('4 × B300');
+ await expect(recipeName).toContainText(id);
+ await expect(recipeName).toContainText('1.0.4');
+ await expect(page).toHaveTitle(
+ /GLM 5\.3 \(NVFP4\) · 4 × B300 · 配方详情/,
+ );
+ const fontSize = (element: ReturnType