diff --git a/src/components/collapse-container/headless-collapse.tsx b/src/components/collapse-container/headless-collapse.tsx new file mode 100644 index 00000000..a41e644b --- /dev/null +++ b/src/components/collapse-container/headless-collapse.tsx @@ -0,0 +1,57 @@ +import React, { useEffect, useRef, useState } from 'react'; + +interface CollapseProps { + open: boolean; + children: React.ReactNode; + duration?: number; + minHeight?: number; +} + +export default function Collapse({ + open, + children, + minHeight = 0, + duration = 200 +}: CollapseProps) { + const ref = useRef(null); + const [height, setHeight] = useState(minHeight); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + if (open) { + const h = el.scrollHeight; + setHeight(h); + + const timer = setTimeout(() => { + setHeight('auto'); + }, duration); + + return () => clearTimeout(timer); + } else { + const h = el.scrollHeight; + setHeight(h); + + requestAnimationFrame(() => { + requestAnimationFrame(() => { + setHeight(0); + }); + }); + } + return undefined; + }, [open, duration]); + + return ( +
+ {children} +
+ ); +} diff --git a/src/pages/benchmark/components/configure/index.tsx b/src/pages/benchmark/components/configure/index.tsx deleted file mode 100644 index b167b59a..00000000 --- a/src/pages/benchmark/components/configure/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import { useDetailContext } from '../../config/detail-context'; -import Benchmark from './benchmark'; -import Instance from './instance'; - -const Container = styled.div` - display: flex; - flex-direction: column; - gap: 16px; -`; - -const Configure: React.FC = () => { - const { detailData } = useDetailContext(); - return ( - - - - - ); -}; - -export default Configure; diff --git a/src/pages/benchmark/components/detail-content.tsx b/src/pages/benchmark/components/detail-content.tsx index 160be161..c3e3041b 100644 --- a/src/pages/benchmark/components/detail-content.tsx +++ b/src/pages/benchmark/components/detail-content.tsx @@ -6,7 +6,6 @@ import React, { useEffect, useState } from 'react'; import DetailContext from '../config/detail-context'; import { BenchmarkListItem } from '../config/types'; import useQueryDetail from '../services/use-query-detail'; -import Configure from './configure'; import Environment from './environment'; import Summary from './summary'; @@ -31,12 +30,6 @@ const Details: React.FC<{ currentData?: BenchmarkListItem }> = ({ children: , icon: }, - { - key: 'configure', - label: intl.formatMessage({ id: 'benchmark.detail.configure.title' }), - children: , - icon: - }, { key: 'environment', label: intl.formatMessage({ id: 'benchmark.detail.environment.title' }), diff --git a/src/pages/benchmark/components/summary/basic.tsx b/src/pages/benchmark/components/summary/basic.tsx index 999e6780..e40095fc 100644 --- a/src/pages/benchmark/components/summary/basic.tsx +++ b/src/pages/benchmark/components/summary/basic.tsx @@ -6,7 +6,6 @@ import _, { round } from 'lodash'; import React from 'react'; import styled from 'styled-components'; import { useDetailContext } from '../../config/detail-context'; -import PercentileResult from './percentile-result'; import Section from './section'; const Container = styled.div` @@ -239,7 +238,6 @@ const Summary: React.FC = () => { > - ); }; diff --git a/src/pages/benchmark/components/configure/benchmark.tsx b/src/pages/benchmark/components/summary/benchmark.tsx similarity index 94% rename from src/pages/benchmark/components/configure/benchmark.tsx rename to src/pages/benchmark/components/summary/benchmark.tsx index 9d12e623..9f985c0c 100644 --- a/src/pages/benchmark/components/configure/benchmark.tsx +++ b/src/pages/benchmark/components/summary/benchmark.tsx @@ -1,7 +1,7 @@ import { Descriptions, DescriptionsProps } from 'antd'; import React from 'react'; import { useDetailContext } from '../../config/detail-context'; -import Section from '../summary/section'; +import Section from './section'; const Benchmark: React.FC = () => { const { detailData } = useDetailContext(); @@ -19,7 +19,7 @@ const Benchmark: React.FC = () => { }, { key: '3', - label: 'Token Length (In/Out)', + label: 'Token Length (Prompt/Out)', children: ( {detailData?.dataset_prompt_tokens || '-'} /{' '} diff --git a/src/pages/benchmark/components/summary/index.tsx b/src/pages/benchmark/components/summary/index.tsx index 457ca25a..816ff6cc 100644 --- a/src/pages/benchmark/components/summary/index.tsx +++ b/src/pages/benchmark/components/summary/index.tsx @@ -1,19 +1,23 @@ import React from 'react'; import styled from 'styled-components'; -import BasicInfo from './basic'; -import MetricsInfo from './metrics'; +import BenchMark from './benchmark'; +import Instance from './instance'; +import MetricsResult from './metrics-result'; +import PercentileResult from './percentile-result'; const Container = styled.div` display: flex; flex-direction: column; - gap: 24px; + gap: 16px; `; const Summary: React.FC = () => { return ( - - + + + + ); }; diff --git a/src/pages/benchmark/components/configure/instance.tsx b/src/pages/benchmark/components/summary/instance.tsx similarity index 75% rename from src/pages/benchmark/components/configure/instance.tsx rename to src/pages/benchmark/components/summary/instance.tsx index 3c2cd11e..eff1d588 100644 --- a/src/pages/benchmark/components/configure/instance.tsx +++ b/src/pages/benchmark/components/summary/instance.tsx @@ -1,11 +1,8 @@ -import StatusTag from '@/components/status-tag'; -import { InstanceStatusMapValue, status } from '@/pages/llmodels/config'; -import { convertFileSize } from '@/utils'; import { Descriptions, Flex, Tag } from 'antd'; import _ from 'lodash'; import React, { useMemo } from 'react'; import { useDetailContext } from '../../config/detail-context'; -import Section from '../summary/section'; +import Section from './section'; const calcTotalVram = (vram: Record) => { return _.sum(_.values(vram)); @@ -17,21 +14,16 @@ const Instance: React.FC = () => { const items = useMemo(() => { const { snapshot } = detailData; const [instanceName, instanceData] = - Object.entries(snapshot.instances || {})[0] || []; + Object.entries(snapshot?.instances || {})[0] || []; return [ { key: '1', label: 'Instance Name', children: (
- {instanceName} - + + {detailData?.model_name}/{detailData?.model_instance_name} +
) }, @@ -39,42 +31,18 @@ const Instance: React.FC = () => { key: '2', label: 'Worker', children: instanceData?.worker_name || '-' - // children: - // instanceData?.ports?.length > 0 - // ? `${instanceData.worker_ip}:${instanceData.ports[0]}` - // : instanceData.worker_ip || '-' - }, - { - key: '3', - label: 'GPU Indexes', - children: - _.join( - instanceData.gpu_indexes?.sort?.((a, b) => a - b), - ',' - ) || '-' }, { key: '6', label: 'GPU Type', children: instanceData?.gpu_type || '-' }, - { - key: '4', - label: 'Allocated VRAM', - children: - convertFileSize( - instanceData.computed_resource_claim?.vram - ? calcTotalVram(instanceData.computed_resource_claim?.vram) - : 0, - 1 - ) || '-' - }, { key: '5', label: 'Backend', children: `${instanceData?.backend || '-'} ${ - instanceData.backend_version - ? `(${instanceData.backend_version})` + instanceData?.backend_version + ? `(${instanceData?.backend_version})` : '' }` } @@ -84,14 +52,14 @@ const Instance: React.FC = () => { const paramsItems = useMemo(() => { const { snapshot } = detailData; const [instanceName, instanceData] = - Object.entries(snapshot.instances || {})[0] || []; + Object.entries(snapshot?.instances || {})[0] || []; return [ { key: '1', label: 'Backend Parameters', children: ( - {instanceData?.backend_parameters.map( + {instanceData?.backend_parameters?.map( (param: string, index: number) => ( {param} @@ -180,7 +148,7 @@ const Instance: React.FC = () => { 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', + path: 'total_requests', + unit: '', + render: (value: number) => round(value, 0) + }, + { + title: 'Success Requests', + dataIndex: 'successful_requests', + path: ['raw_metrics', 'benchmarks', '0'], + unit: '', + render: (value: number) => { + return ( + + {_.get(value, 'metrics.request_totals.successful')} + + ); + } + }, + { + title: 'Failed Requests', + dataIndex: 'failed_requests', + path: ['raw_metrics', 'benchmarks', '0'], + unit: '', + render: (value: number) => { + return ( + + {_.get(value, 'metrics.request_totals.errored')} + + ); + } + }, + { + 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: 'Request token throughput (t/s)', + dataIndex: 'prompt_tokens_per_second_mean', + path: 'prompt_tokens_per_second_mean', + unit: 't/s', + render: (value: number) => round(value, 2) + }, + { + title: 'Average Request Latency (ms)', + dataIndex: 'request_latency_mean', + path: 'request_latency_mean', + unit: 'ms', + render: (value: number) => round(value, 2) + }, + { + title: 'Average Time To First Token (ms)', + dataIndex: 'time_to_first_token_mean', + path: 'time_to_first_token_mean', + unit: 'ms', + render: (value: number) => round(value, 2) + }, + { + title: 'Average Time Per Output Token (ms)', + dataIndex: 'time_per_output_token_mean', + path: 'time_per_output_token_mean', + unit: 'ms', + render: (value: number) => round(value, 2) + }, + { + title: '', + dataIndex: '', + path: '', + unit: '', + render: (_: number) => '' + } +]; + +const PERCENTILES = [ + { key: 'metrics', label: 'Metrics', title: 'N/A' } +] as const; + +const PercentileResult: React.FC = () => { + const { detailData } = useDetailContext(); + const metrics = detailData?.raw_metrics?.benchmarks?.[0]?.metrics || {}; + + const buildPercentileTable = (metrics: any) => { + return PERCENTILES.map(({ key, title }) => { + const row: any = { metrics: title }; + + [...columns, ...columnsSub].forEach(({ dataIndex, path }) => { + row[dataIndex] = _.get(detailData, path) ?? 0; + }); + + return row; + }); + }; + + const items = [...columns, ...columnsSub].map( + ({ title, dataIndex, path, render, unit }) => ({ + key: dataIndex, + label: title, + children: unit + ? render(_.get(detailData, path) ?? 0) + ` (${unit})` + : render(_.get(detailData, path) ?? 0) + }) + ); + + return ( +
+ {/*
*/} + +
+ ); +}; + +export default PercentileResult; diff --git a/src/pages/benchmark/components/summary/metrics.tsx b/src/pages/benchmark/components/summary/metrics.tsx deleted file mode 100644 index df79310a..00000000 --- a/src/pages/benchmark/components/summary/metrics.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { useDetailContext } from '../../config/detail-context'; - -const fields = [ - 'requests_per_second_mean', - 'request_latency_mean', - 'time_per_output_token_mean', - 'inter_token_latency_mean', - 'time_to_first_token_mean', - 'tokens_per_second_mean', - 'output_tokens_per_second_mean', - 'prompt_tokens_per_second_mean' -]; - -const Summary: React.FC = () => { - const { detailData } = useDetailContext(); - return
; -}; - -export default Summary; diff --git a/src/pages/benchmark/components/summary/percentile-result.tsx b/src/pages/benchmark/components/summary/percentile-result.tsx index 88aa1c00..df26ec4a 100644 --- a/src/pages/benchmark/components/summary/percentile-result.tsx +++ b/src/pages/benchmark/components/summary/percentile-result.tsx @@ -1,107 +1,92 @@ import { Table } from 'antd'; +import { round } from 'lodash'; import React from 'react'; import { useDetailContext } from '../../config/detail-context'; import Section from './section'; -// Define table columns: Percentile、 TTFT (ms) 、 ITL (ms) 、TPOT (ms)、Latency (ms)、Input Tokens (t/s)、 Output Tokens (t/s)、 Output (t/s)、Total (t/s) - const columns = [ - { - title: 'Percentile', - dataIndex: 'percentile', - key: 'percentile' - }, { title: 'TTFT (ms)', - dataIndex: 'ttft', - key: 'ttft' + dataIndex: 'time_to_first_token_ms', + render: (value: number) => round(value, 2) }, { title: 'ITL (ms)', - dataIndex: 'itl', - key: 'itl' + dataIndex: 'inter_token_latency_ms', + render: (value: number) => round(value, 2) }, { title: 'TPOT (ms)', - dataIndex: 'tpot', - key: 'tpot' + dataIndex: 'time_per_output_token_ms', + render: (value: number) => round(value, 2) }, { - title: 'Latency (ms)', - dataIndex: 'latency', - key: 'latency' + title: 'Latency (s)', + dataIndex: 'request_latency', + render: (value: number) => round(value, 2) }, { - title: 'Input Tokens (t/s)', - dataIndex: 'inputTokens', - key: 'inputTokens' + title: 'Input tokens', + dataIndex: 'prompt_token_count', + render: (value: number) => round(value, 0) }, { - title: 'Output Tokens (t/s)', - dataIndex: 'outputTokens', - key: 'outputTokens' + title: 'Output tokens', + dataIndex: 'output_token_count', + render: (value: number) => round(value, 0) + }, + { + title: 'Input (t/s)', + dataIndex: 'prompt_tokens_per_second', + render: (value: number) => round(value, 2) }, { title: 'Output (t/s)', - dataIndex: 'output', - key: 'output' + dataIndex: 'output_tokens_per_second', + render: (value: number) => round(value, 2) }, { title: 'Total (t/s)', - dataIndex: 'total', - key: 'total' + dataIndex: 'tokens_per_second', + render: (value: number) => round(value, 2) } ]; -// mock data example: three rows of percentiles: 50th, 90th, 99th -const data = [ - { - key: '1', - percentile: '50%', - ttft: 100, - itl: 50, - tpot: 20, - latency: 200, - inputTokens: 300, - outputTokens: 400, - output: 500, - total: 600 - }, - { - key: '2', - percentile: '90%', - ttft: 150, - itl: 70, - tpot: 30, - latency: 250, - inputTokens: 350, - outputTokens: 450, - output: 550, - total: 650 - }, - { - key: '3', - percentile: '99%', - ttft: 200, - itl: 90, - tpot: 40, - latency: 300, - inputTokens: 400, - outputTokens: 500, - output: 600, - total: 700 - } -]; +const PERCENTILES = [ + { key: 'p50', label: '50%' }, + { key: 'p90', label: '90%' }, + { key: 'p99', label: '99%' } +] as const; const PercentileResult: React.FC = () => { - const { detailData, id } = useDetailContext(); + const { detailData } = useDetailContext(); + const metrics = detailData?.raw_metrics?.benchmarks?.[0]?.metrics || {}; + + const buildPercentileTable = (metrics: any) => { + return PERCENTILES.map(({ key, label }) => { + const row: any = { percentile: label }; + + columns.forEach(({ dataIndex }) => { + row[dataIndex] = + metrics?.[dataIndex]?.successful?.percentiles?.[key] ?? 0; + }); + + return row; + }); + }; return (
= ({ children, title, styles }) => { +}> = ({ children, title, styles, minHeight = 100 }) => { + const [open, setOpen] = useState(true); return ( - {title && {title}} - {children} + {title && ( + setOpen(!open)}> + {title} + <IconFont + type={'icon-down'} + rotate={open ? 0 : -90} + style={{ + transition: 'transform 0.2s' + }} + ></IconFont> + + )} + + {children} + ); }; diff --git a/src/pages/benchmark/config/detail-types.ts b/src/pages/benchmark/config/detail-types.ts index cf1c36cb..08bc636d 100644 --- a/src/pages/benchmark/config/detail-types.ts +++ b/src/pages/benchmark/config/detail-types.ts @@ -67,6 +67,12 @@ export interface GPUData { export interface BenchmarkDetail { profile: string; seed: number; + raw_metrics: { + benchmarks: Array<{ + metrics: Record; + }>; + [key: string]: any; + }; requests_per_second_mean: number; request_latency_mean: number; time_per_output_token_mean: number; diff --git a/src/pages/benchmark/forms/model-instance.tsx b/src/pages/benchmark/forms/model-instance.tsx index 9f19fb01..fe1c0b1c 100644 --- a/src/pages/benchmark/forms/model-instance.tsx +++ b/src/pages/benchmark/forms/model-instance.tsx @@ -132,6 +132,7 @@ const ModelInstanceForm: React.FC = () => { > { } = useTableFetch({ fetchAPI: queryBenchmarkList, deleteAPI: deleteBenchmark, + API: BENCHMARKS_API, + watch: true, contentForDelete: 'menu.models.benchmark' }); const intl = useIntl();