From 667751dc5f214c0c9d5fdc8f92f0cbb8f4674200 Mon Sep 17 00:00:00 2001 From: jialin Date: Tue, 27 Jan 2026 21:33:30 +0800 Subject: [PATCH] refactor: env detail --- .../seal-table/components/header-prefix.tsx | 37 ++- src/components/seal-table/index.tsx | 2 +- src/hooks/use-query-data-list.ts | 9 +- src/locales/en-US/benchmark.ts | 6 +- src/locales/ja-JP/benchmark.ts | 6 +- src/locales/ru-RU/benchmark.ts | 6 +- src/locales/zh-CN/benchmark.ts | 6 +- .../components/environment/index.tsx | 234 ++++++++++++++---- .../components/summary/benchmark.tsx | 2 +- .../benchmark/components/summary/instance.tsx | 2 +- .../components/summary/metrics-result.tsx | 73 +++--- .../components/summary/percentile-result.tsx | 13 +- src/pages/benchmark/config/types.ts | 2 +- src/pages/benchmark/forms/dataset.tsx | 60 ++--- src/pages/benchmark/forms/index.tsx | 2 +- src/pages/benchmark/forms/random-settings.tsx | 32 ++- .../benchmark/hooks/use-benchmark-columns.tsx | 62 +++-- .../benchmark/services/use-query-dataset.ts | 10 +- 18 files changed, 377 insertions(+), 187 deletions(-) diff --git a/src/components/seal-table/components/header-prefix.tsx b/src/components/seal-table/components/header-prefix.tsx index 245f5ca6..e324e5fe 100644 --- a/src/components/seal-table/components/header-prefix.tsx +++ b/src/components/seal-table/components/header-prefix.tsx @@ -1,5 +1,4 @@ import IconFont from '@/components/icon-font'; -import { RightOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Button, Checkbox } from 'antd'; import _ from 'lodash'; @@ -88,14 +87,34 @@ const HeaderPrefix: React.FC = (props) => { } if (expandable) { return ( -
- {_.isBoolean(expandable) ? ( - - ) : ( - expandable - )} +
+ + {_.isBoolean(expandable) ? ( + + ) : ( + expandable + )} +
); } diff --git a/src/components/seal-table/index.tsx b/src/components/seal-table/index.tsx index 8b67d5d9..f91dad45 100644 --- a/src/components/seal-table/index.tsx +++ b/src/components/seal-table/index.tsx @@ -27,7 +27,7 @@ const Wrapper = styled.div<{ $token: any }>` props.$token.tableHeaderIconHoverColor}; `; -const SealTable: React.FC = ( +const SealTable: React.FC = ( props ) => { const { diff --git a/src/hooks/use-query-data-list.ts b/src/hooks/use-query-data-list.ts index 799d7215..19cb3560 100644 --- a/src/hooks/use-query-data-list.ts +++ b/src/hooks/use-query-data-list.ts @@ -84,11 +84,12 @@ export function useQueryDataList(option: { export function useQueryData(option: { key: string; + delay?: number; fetchDetail: (params: Params, options?: any) => Promise; getData?: (response: Detail) => any; errorMsg?: string; }) { - const { key, fetchDetail, getData, errorMsg } = option; + const { key, fetchDetail, getData, errorMsg, delay } = option; const axiosTokenRef = useRef(null); const [detailData, setDetailData] = useState({} as Detail); @@ -105,6 +106,12 @@ export function useQueryData(option: { ...(extra || {}) }); + if (delay) { + await new Promise((resolve) => { + setTimeout(resolve, delay); + }); + } + setDetailData(getData ? getData(res) : res); return res; diff --git a/src/locales/en-US/benchmark.ts b/src/locales/en-US/benchmark.ts index 5911b793..f515d968 100644 --- a/src/locales/en-US/benchmark.ts +++ b/src/locales/en-US/benchmark.ts @@ -7,9 +7,9 @@ export default { 'benchmark.table.dataset': 'Dataset', 'benchmark.table.requestRate': 'Request Rate', 'benchmark.table.gpu': 'GPU ', - 'benchmark.table.tpot': 'TPOT (ms)', - 'benchmark.table.itl': 'ITL (ms)', - 'benchmark.table.ttft': 'TTFT (ms)', + 'benchmark.table.tpot': 'TPOT', + 'benchmark.table.itl': 'ITL', + 'benchmark.table.ttft': 'TTFT', 'benchmark.table.rps': 'Requests/s', 'benchmark.table.tps': 'Tokens/s', 'benchmark.table.inputLen': 'Input Length', diff --git a/src/locales/ja-JP/benchmark.ts b/src/locales/ja-JP/benchmark.ts index 5911b793..f515d968 100644 --- a/src/locales/ja-JP/benchmark.ts +++ b/src/locales/ja-JP/benchmark.ts @@ -7,9 +7,9 @@ export default { 'benchmark.table.dataset': 'Dataset', 'benchmark.table.requestRate': 'Request Rate', 'benchmark.table.gpu': 'GPU ', - 'benchmark.table.tpot': 'TPOT (ms)', - 'benchmark.table.itl': 'ITL (ms)', - 'benchmark.table.ttft': 'TTFT (ms)', + 'benchmark.table.tpot': 'TPOT', + 'benchmark.table.itl': 'ITL', + 'benchmark.table.ttft': 'TTFT', 'benchmark.table.rps': 'Requests/s', 'benchmark.table.tps': 'Tokens/s', 'benchmark.table.inputLen': 'Input Length', diff --git a/src/locales/ru-RU/benchmark.ts b/src/locales/ru-RU/benchmark.ts index 5911b793..f515d968 100644 --- a/src/locales/ru-RU/benchmark.ts +++ b/src/locales/ru-RU/benchmark.ts @@ -7,9 +7,9 @@ export default { 'benchmark.table.dataset': 'Dataset', 'benchmark.table.requestRate': 'Request Rate', 'benchmark.table.gpu': 'GPU ', - 'benchmark.table.tpot': 'TPOT (ms)', - 'benchmark.table.itl': 'ITL (ms)', - 'benchmark.table.ttft': 'TTFT (ms)', + 'benchmark.table.tpot': 'TPOT', + 'benchmark.table.itl': 'ITL', + 'benchmark.table.ttft': 'TTFT', 'benchmark.table.rps': 'Requests/s', 'benchmark.table.tps': 'Tokens/s', 'benchmark.table.inputLen': 'Input Length', diff --git a/src/locales/zh-CN/benchmark.ts b/src/locales/zh-CN/benchmark.ts index aaac3307..5860e71d 100644 --- a/src/locales/zh-CN/benchmark.ts +++ b/src/locales/zh-CN/benchmark.ts @@ -7,9 +7,9 @@ export default { 'benchmark.table.dataset': '数据集', 'benchmark.table.requestRate': '请求率', 'benchmark.table.gpu': 'GPU ', - 'benchmark.table.tpot': 'TPOT (ms)', - 'benchmark.table.itl': 'ITL (ms)', - 'benchmark.table.ttft': 'TTFT (ms)', + 'benchmark.table.tpot': 'TPOT', + 'benchmark.table.itl': 'ITL', + 'benchmark.table.ttft': 'TTFT', 'benchmark.table.rps': '请求数/秒', 'benchmark.table.tps': 'Tokens/秒', 'benchmark.table.inputLen': '输入长度', diff --git a/src/pages/benchmark/components/environment/index.tsx b/src/pages/benchmark/components/environment/index.tsx index 998199cd..d416ea75 100644 --- a/src/pages/benchmark/components/environment/index.tsx +++ b/src/pages/benchmark/components/environment/index.tsx @@ -1,8 +1,13 @@ -import { Tag } from 'antd'; +import AutoTooltip from '@/components/auto-tooltip'; +import RowChildren from '@/components/seal-table/components/row-children'; +import SealTable from '@/components/seal-table/index'; +import useExpandedRowKeys from '@/hooks/use-expanded-row-keys'; +import { convertFileSize } from '@/utils'; +import useMemoizedFn from 'ahooks/lib/useMemoizedFn'; +import { Col, Row } from 'antd'; import React, { useMemo } from 'react'; import styled from 'styled-components'; import { useDetailContext } from '../../config/detail-context'; -import WorkerData from './worker-data'; const Container = styled.div` display: flex; @@ -14,74 +19,209 @@ const Environment: React.FC = () => { const { detailData } = useDetailContext(); const { snapshot } = detailData; + const { handleExpandChange, handleExpandAll, expandedRowKeys } = + useExpandedRowKeys(); + const mainWorker = useMemo(() => { // get workers data + const [[workerName, workerInfo]] = Object.entries(snapshot.workers); const gpuData = Object.values(snapshot.gpus).filter( (gpu) => gpu.worker_name === workerName ); return { - workerData: workerInfo, - gpuData: [...gpuData, ...gpuData] + ...workerInfo, + isMain: true, + children: gpuData }; }, [snapshot]); - const subWorkers = useMemo(() => { - const [[mainWorkerName, mainWorkerInfo]] = Object.entries(snapshot.workers); + // const subWorkers = useMemo(() => { + // const [[mainWorkerName, mainWorkerInfo]] = Object.entries(snapshot.workers); - const subOrdinaryWorkers = Object.values(snapshot.instances).filter( - (instance) => instance.worker_name === mainWorkerName + // const subOrdinaryWorkers = Object.values(snapshot.instances).filter( + // (instance) => instance.worker_name === mainWorkerName + // ); + + // return subOrdinaryWorkers.map((worker) => { + // const gpuData = Object.values(snapshot.gpus).filter( + // (gpu) => gpu.worker_name === worker.worker_name + // ); + + // return { + // ...worker, + // isMain: false, + // children: gpuData + // }; + // }); + // }, [snapshot, mainWorker]); + + const GPUColumns = [ + { + title: 'GPU Name', + dataIndex: 'name', + key: 'name', + span: 6, + colStyle: { paddingLeft: 16 }, + render: (value: string, record: any) => ( + {value} + ) + }, + { + title: 'Vendor', + dataIndex: 'vendor', + key: 'vendor', + span: 4, + colStyle: { paddingLeft: 46 } + }, + { + title: 'VRAM', + dataIndex: 'memory_total', + key: 'memory_total', + label: 'VRAM', + span: 3, + render: (value: number, record: any) => convertFileSize(value) + }, + { + title: 'Cores', + dataIndex: 'core_total', + key: 'core_total', + label: 'Cores', + span: 3 + }, + { + title: 'Runtime Version', + dataIndex: 'runtime_version', + key: 'runtime_version', + span: 4 + }, + { + title: 'Driver Version', + dataIndex: 'driver_version', + key: 'driver_version', + span: 4, + colStyle: { paddingLeft: 35 } + } + ]; + + const columns = [ + { + title: 'Worker Name', + dataIndex: 'name', + key: 'name', + span: 6 + }, + { + title: 'System', + dataIndex: 'os', + key: 'system', + span: 4, + render: (os: { name: string; version: string }, record: any) => { + return `${record.os.name}`; + } + }, + { + title: 'Version', + dataIndex: 'version', + key: 'version', + span: 6, + render: (val: any, record: any) => { + return {record.os.version}; + } + }, + { + title: 'CPU Count', + dataIndex: 'cpu_total', + key: 'cpu_total', + span: 4 + }, + { + title: 'Memory', + dataIndex: 'memory_total', + key: 'memory_total', + span: 4, + render: (value: number) => convertFileSize(value) + } + ]; + + const dataList = useMemo(() => { + return [mainWorker]; + }, [mainWorker]); + + const handleToggleExpandAll = useMemoizedFn((expanded: boolean) => { + const keys = dataList?.map((item) => item.id); + handleExpandAll(expanded, keys); + }); + + const loadChildren = useMemoizedFn(async (record: any) => { + return record.children || []; + }); + + const renderChildren = useMemoizedFn((list: any[]) => { + return ( +
+ + {list.map((gpu) => ( + + {GPUColumns.map((col) => ( + + + + {col.title} + + {col.render + ? col.render((gpu as any)[col.dataIndex], gpu) + : (gpu as any)[col.dataIndex]} + + + ))} + + ))} + +
); - - return subOrdinaryWorkers.map((worker) => { - const gpuData = Object.values(snapshot.gpus).filter( - (gpu) => gpu.worker_name === worker.worker_name - ); - - return { - workerData: worker, - gpuData: gpuData - }; - }); - }, [snapshot, mainWorker]); + }); return ( - Main {mainWorker?.workerData?.name}
} - > - - - Sub - - {mainWorker?.workerData?.name} - - } - > - {/* {subWorkers?.map?.((worker, index) => ( - - ))} */} + > */} + ); }; diff --git a/src/pages/benchmark/components/summary/benchmark.tsx b/src/pages/benchmark/components/summary/benchmark.tsx index 9f985c0c..b18b6bd5 100644 --- a/src/pages/benchmark/components/summary/benchmark.tsx +++ b/src/pages/benchmark/components/summary/benchmark.tsx @@ -45,7 +45,7 @@ const Benchmark: React.FC = () => { ]; return ( -
+
{ }, [detailData]); return ( -
+
round(value, 2) }, - { - title: 'Concurrency', - dataIndex: 'request_concurrency', - path: ['raw_metrics', 'benchmarks', '0'], - unit: '', - render: (value: number) => { - return round( - _.get(value, 'metrics.request_concurrency.successful.mean'), - 0 - ); - } - }, + { title: 'Total Requests', dataIndex: 'total_requests', @@ -31,6 +20,13 @@ const columns = [ unit: '', render: (value: number) => round(value, 0) }, + { + title: 'Total token throughput', + dataIndex: 'tokens_per_second_mean', + path: 'tokens_per_second_mean', + unit: 'Tokens/s', + render: (value: number) => round(value, 2) + }, { title: 'Success Requests', dataIndex: 'successful_requests', @@ -44,6 +40,17 @@ const columns = [ ); } }, + + { + title: 'Output token throughput', + dataIndex: 'output_tokens_per_second_mean', + path: 'output_tokens_per_second_mean', + unit: 'Tokens/s', + render: (value: number) => round(value, 2) + } +]; + +const columnsSub = [ { title: 'Failed Requests', dataIndex: 'failed_requests', @@ -58,27 +65,22 @@ const columns = [ } }, { - title: 'Output token throughput (t/s)', - dataIndex: 'output_tokens_per_second_mean', - path: 'output_tokens_per_second_mean', - unit: 't/s', - render: (value: number) => round(value, 2) - } -]; - -const columnsSub = [ - { - title: 'Total token throughput (t/s)', - dataIndex: 'tokens_per_second_mean', - path: 'tokens_per_second_mean', - unit: 't/s', - render: (value: number) => round(value, 2) + title: 'Concurrency', + dataIndex: 'request_concurrency', + path: ['raw_metrics', 'benchmarks', '0'], + unit: '', + render: (value: number) => { + return round( + _.get(value, 'metrics.request_concurrency.successful.mean'), + 0 + ); + } }, { - title: 'Request token throughput (t/s)', + title: 'Request token throughput ', dataIndex: 'prompt_tokens_per_second_mean', path: 'prompt_tokens_per_second_mean', - unit: 't/s', + unit: 'Tokens/s', render: (value: number) => round(value, 2) }, { @@ -135,14 +137,19 @@ const PercentileResult: React.FC = () => { ({ title, dataIndex, path, render, unit }) => ({ key: dataIndex, label: title, - children: unit - ? render(_.get(detailData, path) ?? 0) + ` (${unit})` - : render(_.get(detailData, path) ?? 0) + children: unit ? ( + + {render(_.get(detailData, path) ?? 0)}{' '} + ({unit}) + + ) : ( + render(_.get(detailData, path) ?? 0) + ) }) ); return ( -
+
{/* { }; return ( -
+
{ rowKey="percentile" pagination={false} styles={{ + // header: { + // row: { + // background: 'none', + // borderBottom: '1px solid var(--ant-color-split)' + // }, + // cell: { + // background: 'none', + // paddingBottom: 0, + // borderBottom: '1px solid var(--ant-color-split)' + // } + // }, body: { cell: { height: 54 diff --git a/src/pages/benchmark/config/types.ts b/src/pages/benchmark/config/types.ts index d7f67b49..bdd5b1e5 100644 --- a/src/pages/benchmark/config/types.ts +++ b/src/pages/benchmark/config/types.ts @@ -81,7 +81,7 @@ export interface FormData { dataset_output_tokens: number; total_requests: number; request_rate: number; - seed: number; + dataset_seed: number; model_instance?: string; } diff --git a/src/pages/benchmark/forms/dataset.tsx b/src/pages/benchmark/forms/dataset.tsx index 92b194d2..cad02053 100644 --- a/src/pages/benchmark/forms/dataset.tsx +++ b/src/pages/benchmark/forms/dataset.tsx @@ -1,4 +1,3 @@ -import AutoComplete from '@/components/seal-form/auto-complete'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import useAppUtils from '@/hooks/use-app-utils'; @@ -32,30 +31,17 @@ const DatasetForm: React.FC = () => { } = useQueryProfiles(); const handleOnDataSetChange = (value: any, option: any) => { - if (value === 'Custom') { - form.setFieldsValue({ - profile: ProfileValueMap.Custom, - dataset_id: null - }); - } else { - form.setFieldsValue({ - profile: ProfileValueMap.Custom, - dataset_id: option?.data?.id, - dataset_prompt_tokens: option?.prompt_tokens, - dataset_output_tokens: option?.output_tokens, - request_rate: null, - total_requests: null - }); - } + form.setFieldsValue({ + dataset_id: option?.data?.id, + dataset_prompt_tokens: option?.prompt_tokens, + dataset_output_tokens: option?.output_tokens, + request_rate: null, + total_requests: null + }); }; const handleProfileChange = (value: string, option: any) => { - if (value === ProfileValueMap.Custom) { - form.setFieldsValue({ - dataset_name: 'Custom', - dataset_id: null - }); - } else { + if (value !== ProfileValueMap.Custom) { const dataset_id = datasetList.find( (item) => item.label === option.config?.dataset_name )?.value; @@ -97,31 +83,17 @@ const DatasetForm: React.FC = () => { required > - - name="dataset_name" - rules={[ - { - required: true, - message: getRuleMessage('select', 'benchmark.table.dataset') - } - ]} - > - ({ - ...item, - label: item.label, - value: item.label - }))} - loading={datasetLoading} - onChange={handleOnDataSetChange} - label={intl.formatMessage({ id: 'benchmark.table.dataset' })} - required - > - + hidden name="dataset_id"> - {profile === 'Custom' && } + {profile === 'Custom' && ( + + )} ); }; diff --git a/src/pages/benchmark/forms/index.tsx b/src/pages/benchmark/forms/index.tsx index 9bddb965..20ff6b40 100644 --- a/src/pages/benchmark/forms/index.tsx +++ b/src/pages/benchmark/forms/index.tsx @@ -130,7 +130,7 @@ const ProviderForm: React.FC = forwardRef((props, ref) => { dataset_output_tokens: null, total_requests: null, request_rate: null, - seed: null + dataset_seed: null }} > diff --git a/src/pages/benchmark/forms/random-settings.tsx b/src/pages/benchmark/forms/random-settings.tsx index 4d667d62..f76f7a6f 100644 --- a/src/pages/benchmark/forms/random-settings.tsx +++ b/src/pages/benchmark/forms/random-settings.tsx @@ -1,11 +1,17 @@ import SealInputNumber from '@/components/seal-form/input-number'; +import SealSelect from '@/components/seal-form/seal-select'; import useAppUtils from '@/hooks/use-app-utils'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React from 'react'; import { FormData } from '../config/types'; -const RandomSettingsForm: React.FC = () => { +const RandomSettingsForm: React.FC<{ + datasetList: Global.BaseOption[]; + datasetLoading: boolean; + handleOnDataSetChange: (value: any, option: any) => void; +}> = (props) => { + const { datasetList, datasetLoading, handleOnDataSetChange } = props; const intl = useIntl(); const form = Form.useFormInstance(); const profile = Form.useWatch('profile', form); @@ -15,6 +21,28 @@ const RandomSettingsForm: React.FC = () => { return ( <> + + name="dataset_name" + rules={[ + { + required: true, + message: getRuleMessage('select', 'benchmark.table.dataset') + } + ]} + > + ({ + ...item, + label: item.label, + value: item.label + }))} + loading={datasetLoading} + onChange={handleOnDataSetChange} + label={intl.formatMessage({ id: 'benchmark.table.dataset' })} + required + > + name="dataset_prompt_tokens" rules={[ @@ -53,7 +81,7 @@ const RandomSettingsForm: React.FC = () => { > - name="seed" + name="dataset_seed" getValueProps={(value) => ({ value: value || null })} > ) }, - { - title: intl.formatMessage({ id: 'benchmark.table.requestRate' }), - dataIndex: 'request_rate', - sorter: tableSorter(1), - render: (text: string) => ( - - {text} - - ) - }, + // { + // title: intl.formatMessage({ id: 'benchmark.table.requestRate' }), + // dataIndex: 'request_rate', + // sorter: tableSorter(1), + // render: (text: string) => ( + // + // {text} + // + // ) + // }, { title: intl.formatMessage({ id: 'benchmark.table.gpu' }), dataIndex: 'gpu_summary', @@ -91,7 +90,12 @@ const useBenchmarkColumns = ( ) }, { - title: intl.formatMessage({ id: 'benchmark.table.itl' }), + title: ( + + {intl.formatMessage({ id: 'benchmark.table.itl' })} + avg (ms) + + ), dataIndex: 'inter_token_latency_mean', sorter: tableSorter(1), render: (text: string) => ( @@ -101,7 +105,12 @@ const useBenchmarkColumns = ( ) }, { - title: intl.formatMessage({ id: 'benchmark.table.tpot' }), + title: ( + + {intl.formatMessage({ id: 'benchmark.table.tpot' })} + avg (ms) + + ), dataIndex: 'time_per_output_token_mean', sorter: tableSorter(1), render: (text: string) => ( @@ -111,7 +120,12 @@ const useBenchmarkColumns = ( ) }, { - title: intl.formatMessage({ id: 'benchmark.table.ttft' }), + title: ( + + {intl.formatMessage({ id: 'benchmark.table.ttft' })} + avg (ms) + + ), dataIndex: 'time_to_first_token_mean', sorter: tableSorter(1), render: (text: string) => ( @@ -140,16 +154,16 @@ const useBenchmarkColumns = ( ) }, - { - title: intl.formatMessage({ id: 'common.table.createTime' }), - dataIndex: 'created_at', - sorter: tableSorter(3), - render: (value: string) => ( - - {dayjs(value).format('YYYY-MM-DD HH:mm:ss')} - - ) - }, + // { + // title: intl.formatMessage({ id: 'common.table.createTime' }), + // dataIndex: 'created_at', + // sorter: tableSorter(3), + // render: (value: string) => ( + // + // {dayjs(value).format('YYYY-MM-DD HH:mm:ss')} + // + // ) + // }, { title: intl.formatMessage({ id: 'common.table.operation' }), dataIndex: 'operations', diff --git a/src/pages/benchmark/services/use-query-dataset.ts b/src/pages/benchmark/services/use-query-dataset.ts index d1e1b8dd..4fbd313e 100644 --- a/src/pages/benchmark/services/use-query-dataset.ts +++ b/src/pages/benchmark/services/use-query-dataset.ts @@ -1,11 +1,9 @@ import { useQueryDataList } from '@/hooks/use-query-data-list'; -import { useIntl } from '@umijs/max'; import { useState } from 'react'; import { queryDatasetList } from '../apis'; import { DatasetListItem } from '../config/types'; const useQueryDataset = () => { - const intl = useIntl(); const { dataList, loading, fetchData, cancelRequest } = useQueryDataList< DatasetListItem, Global.SearchParams @@ -29,13 +27,7 @@ const useQueryDataset = () => { value: item.id })) || []; - setDatasetList([ - ...list, - { - label: intl.formatMessage({ id: 'backend.custom' }), - value: 'Custom' - } - ]); + setDatasetList([...list]); }; return {