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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion content/blog/blog-20261104-ascend-support/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/design/page-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,避免返回循环。
Expand Down
5 changes: 4 additions & 1 deletion src/features/catalog/DetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import useIsBrowser from '@docusaurus/useIsBrowser';
import { useHistory, useLocation } from '@docusaurus/router';
import {
config,
hardwareText,
loadHistory,
loadYaml,
modelHref,
Expand Down Expand Up @@ -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: '模型与配方详情' }))
Expand Down
57 changes: 36 additions & 21 deletions src/features/catalog/RecipeOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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
Expand Down Expand Up @@ -101,28 +102,42 @@ export default function RecipeOverview({
aria-label={translate({ id: 'site.9770f40287d5', message: '配方概览' })}
>
<div className={styles.detailTop}>
<div>
<h1 className={styles.recipeTitle}>{variant.id}</h1>
<div className={styles.recipeBadges}>
{isDefault(recipe) && (
<span className={styles.badge}>
{translate({ id: 'site.bb88a59baf57', message: '默认配方' })}
<div className={styles.recipeIdentity}>
<div className={styles.recipeHeading}>
<h1 className={styles.recipeTitle}>{modelName}</h1>
<div className={styles.recipeBadges}>
{isDefault(recipe) && (
<span className={styles.badge}>
{translate({ id: 'site.bb88a59baf57', message: '默认配方' })}
</span>
)}
<span
className={
badge === 'optimized' ? styles.optimized : styles.badge
}
>
{badge === 'optimized'
? translate({ id: 'site.c805b06152c6', message: '已调优' })
: badge === 'baseline'
? translate({ id: 'site.a7ac587ab52d', message: '基线' })
: translate({
id: 'site.8be3a0d72f4d',
message: '部署配方',
})}
</span>
)}
<span
className={
badge === 'optimized' ? styles.optimized : styles.badge
}
>
{badge === 'optimized'
? translate({ id: 'site.c805b06152c6', message: '已调优' })
: badge === 'baseline'
? translate({ id: 'site.a7ac587ab52d', message: '基线' })
: translate({ id: 'site.8be3a0d72f4d', message: '部署配方' })}
</span>
</div>
</div>
<div
className={styles.recipeHardwareSummary}
role="group"
aria-label={translate({ id: 'site.a0e5308a1627', message: '硬件' })}
>
<Icon name="chip" />
<strong>{translateCopy(hardwareText(variant))}</strong>
</div>
<p>
{model.displayName ?? model.name}{' '}
<p className={styles.recipeMetadata}>
{translate({ id: 'catalog.recipeName', message: '配方' })}{' '}
{variant.id}
{translate({
id: 'site.c40529702643',
message: ' · 当前配方版本 ',
Expand Down
37 changes: 36 additions & 1 deletion src/features/catalog/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/catalog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/detail-loading.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
61 changes: 60 additions & 1 deletion tests/e2e/recipe-overview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Page['locator']>) =>
element.evaluate((node) => parseFloat(getComputedStyle(node).fontSize));
expect(await fontSize(title)).toBeGreaterThan(await fontSize(hardware));
expect(await fontSize(hardware)).toBeGreaterThan(
await fontSize(recipeName),
);
expect((await hardware.boundingBox())!.y).toBeGreaterThan(
(await title.boundingBox())!.y,
);
expect((await recipeName.boundingBox())!.y).toBeGreaterThan(
(await hardware.boundingBox())!.y,
);
await expect(
overview(page).getByText('默认配方', { exact: true }),
).toBeVisible();
await expect(
overview(page).getByText('已调优', { exact: true }),
).toBeVisible();
const titleBox = (await title.boundingBox())!;
const hardwareBox = (await hardware.boundingBox())!;
for (const label of ['默认配方', '已调优']) {
const badgeBox = (await overview(page)
.getByText(label, { exact: true })
.boundingBox())!;
expect(badgeBox.y + badgeBox.height).toBeLessThan(hardwareBox.y);
if (width >= 768) {
expect(badgeBox.x).toBeGreaterThan(titleBox.x + titleBox.width);
expect(badgeBox.y).toBeLessThan(titleBox.y + titleBox.height);
expect(badgeBox.y + badgeBox.height).toBeGreaterThan(titleBox.y);
}
}
await expect(fact(page, '版本')).toContainText('1.0.4');
await expect(fact(page, '版本')).toContainText('2026-10-12');
await expect(fact(page, '引擎')).toHaveText('sglang');
Expand Down Expand Up @@ -161,8 +200,16 @@ for (const width of [320, 1440])
});
await baselineLink.click();
await expect(page.getByRole('heading', { level: 1 })).toHaveText(
'sglang-tp8-b300-baseline',
'GLM 5.3 (NVFP4)',
);
await expect(
overview(page)
.getByRole('group', { name: '硬件', exact: true })
.locator('strong'),
).toHaveText('8 × B300');
await expect(overview(page).locator('p')).toContainText([
'sglang-tp8-b300-baseline',
]);
await expect(
overview(page).getByText('基线', { exact: true }),
).toBeVisible();
Expand All @@ -189,6 +236,8 @@ test('missing fields stay missing and a measured zero or regression is preserved
const catalog = parseCatalog(baseline);
const model = catalog.models.find((m) => m.name === 'glm5.3')!;
const recipe = model.variants.find((v) => v.id === id)!;
delete model.displayName;
recipe.requires = { gpuProduct: [] };
delete recipe.releaseDate;
delete recipe.image;
delete recipe.servedName;
Expand All @@ -205,6 +254,16 @@ test('missing fields stay missing and a measured zero or regression is preserved
];
await mount(page, catalog);
await page.goto(href);
await expect(overview(page).getByRole('heading', { level: 1 })).toHaveText(
'glm5.3',
);
await expect(
overview(page)
.getByRole('group', { name: '硬件', exact: true })
.locator('strong'),
).toHaveText('GPU 未指定');
await expect(fact(page, '硬件')).toHaveText('GPU 未指定');
await expect(page).toHaveTitle(/glm5\.3 · GPU 未指定 · 配方详情/);
await expect(fact(page, '版本')).toContainText('日期未提供');
for (const label of ['镜像', '服务名称', 'Chart'])
await expect(fact(page, label)).toHaveText('未提供');
Expand Down
4 changes: 3 additions & 1 deletion tests/pages/deployment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ test('catalog filters, recipe tabs, legacy redirects and return links preserve t
page.getByRole('tab', { name: '部署配置', selected: true }),
).toBeVisible();
await page.reload();
await expect(page.getByRole('heading', { level: 1 })).toHaveText(recipe.id);
await expect(page.getByRole('heading', { level: 1 })).toHaveText(
model.displayName?.trim() || model.name,
);
await expect(page.getByRole('tab')).toHaveCount(2);
await expect(page.getByRole('tab', { name: '版本历史' })).toHaveCount(0);
await page.getByRole('tab', { name: '性能对照' }).click();
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/blog-content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,19 @@ async function withArticle(
});
}
describe('blog publication boundary', () => {
it('publishes the Ascend support article in the formal release', async () => {
const result = await readBlog(process.cwd(), {
...release,
blogSource: 'content',
});
expect(result.articles.map((article) => article.meta)).toContainEqual(
expect.objectContaining({
id: 'blog-20261104-ascend-support',
kind: 'internal',
slug: 'ascend-support',
}),
);
});
it('publishes the selected example blog in an explicitly enabled release', async () => {
const mode = resolveBuildMode({
SITE_ORIGIN: 'https://example.test',
Expand Down
Loading