Skip to content

Commit

Permalink
fix: set the max limit and display ui of resource numbers (#1918)
Browse files Browse the repository at this point in the history
* refactoring resource number UI of routing list page

* set the max value of the resource slider in the service launcher using config.toml

* fix typo

* display resource type icon in the service detail

* fix eslint

* display fixed number for memory

* fix: condition to determine whether to display the GPU slider

* change `somthing.slot` to `somthing.device`,  improve type definitions

* use `toFixed(2)` to display FGPU number

* add test code for iSizeToSize and update test snapshot

* display shmem on Endpoint detail

* fix: add shmem unit to the body of `/services` POST request
  • Loading branch information
yomybaby authored Sep 12, 2023
1 parent 1f53b03 commit 9ff6dcf
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 70 deletions.
154 changes: 154 additions & 0 deletions react/src/components/ResourceNumber.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { iSizeToSize } from '../helper';
import Flex from './Flex';
import { Tooltip, Typography, theme } from 'antd';
import React, { ReactElement } from 'react';
import { useTranslation } from 'react-i18next';

export type ResourceTypeKey =
| 'cpu'
| 'mem'
| 'cuda.device'
| 'cuda.shares'
| 'rocm.device'
| 'tpu.device'
| 'ipu.device'
| 'atom.device'
| 'warboy.device';

export type ResourceOpts = {
shmem: number;
};
interface Props {
type: ResourceTypeKey;
extra?: ReactElement;
opts?: ResourceOpts;
value: string;
}

type ResourceTypeInfo<V> = {
[key in ResourceTypeKey]: V;
};
const ResourceNumber: React.FC<Props> = ({
type,
value: amount,
extra,
opts,
}) => {
const { t } = useTranslation();
const { token } = theme.useToken();
const units: ResourceTypeInfo<string> = {
cpu: t('session.core'),
mem: 'GiB',
'cuda.device': 'GPU',
'cuda.shares': 'FGPU',
'rocm.device': 'GPU',
'tpu.device': 'TPU',
'ipu.device': 'IPU',
'atom.device': 'ATOM',
'warboy.device': 'Warboy',
};

return (
<Flex direction="row" gap="xxs">
<ResourceTypeIcon type={type} />
<Typography.Text>
{units[type] === 'GiB'
? iSizeToSize(amount + 'b', 'g', 2).numberFixed
: units[type] === 'FGPU'
? parseFloat(amount).toFixed(2)
: amount}
</Typography.Text>
<Typography.Text type="secondary">{units[type]}</Typography.Text>
{type === 'mem' && opts?.shmem && (
<Typography.Text
type="secondary"
style={{ fontSize: token.fontSizeSM }}
>
(SHM: {iSizeToSize(opts.shmem + 'b', 'g', 2).numberFixed}GiB)
</Typography.Text>
)}
{extra}
</Flex>
);
};

const MWCIconWrap: React.FC<{ size?: number; children: string }> = ({
size = 16,
children,
}) => {
return (
// @ts-ignore
<mwc-icon
style={{
'--mdc-icon-size': `${size + 2}px`,
width: size,
height: size,
}}
>
{children}
{/* @ts-ignore */}
</mwc-icon>
);
};
interface AccTypeIconProps
extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, 'src'> {
type: ResourceTypeKey;
showIcon?: boolean;
showUnit?: boolean;
showTooltip?: boolean;
size?: number;
}
export const ResourceTypeIcon: React.FC<AccTypeIconProps> = ({
type,
size = 16,
showIcon = true,
showUnit = true,
showTooltip = true,
...props
}) => {
const { t } = useTranslation();

const resourceTypeIconSrcMap: ResourceTypeInfo<
[ReactElement | string, string]
> = {
cpu: [
<MWCIconWrap size={size}>developer_board</MWCIconWrap>,
t('session.core'),
],
mem: [<MWCIconWrap size={size}>memory</MWCIconWrap>, 'GiB'],
'cuda.device': ['/resources/icons/file_type_cuda.svg', 'GPU'],
'cuda.shares': ['/resources/icons/file_type_cuda.svg', 'FGPU'],
'rocm.device': ['/resources/icons/ROCm.png', 'GPU'],
'tpu.device': [<MWCIconWrap size={size}>view_module</MWCIconWrap>, 'TPU'],
'ipu.device': [<MWCIconWrap size={size}>view_module</MWCIconWrap>, 'IPU'],
'atom.device': ['/resources/icons/rebel.svg', 'ATOM'],
'warboy.device': ['/resources/icons/furiosa.svg', 'Warboy'],
};

return (
<Tooltip
title={
showTooltip ? `${type} (${resourceTypeIconSrcMap[type][1]})` : undefined
}
>
{typeof resourceTypeIconSrcMap[type]?.[0] === 'string' ? (
<img
{...props}
style={{
height: size,
...(props.style || {}),
}}
// @ts-ignore
src={resourceTypeIconSrcMap[type]?.[0] || ''}
alt={type}
/>
) : (
<div style={{ width: 16, height: 16 }}>
{resourceTypeIconSrcMap[type]?.[0]}
</div>
)}
</Tooltip>
);
};

export default ResourceNumber;
25 changes: 0 additions & 25 deletions react/src/components/ResourcesNumbers.tsx

This file was deleted.

67 changes: 33 additions & 34 deletions react/src/components/ServiceLauncherModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ const ServiceLauncherModal: React.FC<ServiceLauncherProps> = ({
}
if (values.shmem && values.shmem > 0) {
body['config'].resource_opts = {
shmem: values.shmem,
shmem: values.shmem + 'G',
};
}
return baiSignedRequestWithPromise({
Expand Down Expand Up @@ -314,12 +314,7 @@ const ServiceLauncherModal: React.FC<ServiceLauncherProps> = ({
(i) => i?.key === 'cpu',
)?.min || '0',
)}
max={parseInt(
_.find(
currentImage?.resource_limits,
(i) => i?.key === 'cpu',
)?.max || '100',
)}
max={baiClient._config.maxCPUCoresPerContainer || 128}
inputNumberProps={{
addonAfter: t('session.launcher.Core'),
}}
Expand All @@ -336,7 +331,7 @@ const ServiceLauncherModal: React.FC<ServiceLauncherProps> = ({
tooltip={
<Trans i18nKey={'session.launcher.DescMemory'} />
}
max={64}
max={baiClient._config.maxMemoryPerContainer || 1536}
min={0}
inputNumberProps={{
addonAfter: 'GiB',
Expand Down Expand Up @@ -377,7 +372,7 @@ const ServiceLauncherModal: React.FC<ServiceLauncherProps> = ({
tooltip={
<Trans i18nKey={'session.launcher.DescSharedMemory'} />
}
max={64}
max={baiClient._config.maxShmPerContainer || 8}
min={0}
step={0.25}
inputNumberProps={{
Expand All @@ -390,31 +385,35 @@ const ServiceLauncherModal: React.FC<ServiceLauncherProps> = ({
},
]}
/>
{resourceSlots?.['cuda.device'] ||
(resourceSlots?.['cuda.shares'] && (
<SliderInputItem
style={{ marginBottom: 0 }}
name={'gpu'}
label={t('session.launcher.AIAccelerator')}
tooltip={
<Trans
i18nKey={'session.launcher.DescAIAccelerator'}
/>
}
max={30}
step={resourceSlots['cuda.shares'] ? 0.1 : 1}
inputNumberProps={{
//TODO: change unit based on resource limit
addonAfter: 'GPU',
}}
required
rules={[
{
required: true,
},
]}
/>
))}
{(resourceSlots?.['cuda.device'] ||
resourceSlots?.['cuda.shares']) && (
<SliderInputItem
style={{ marginBottom: 0 }}
name={'gpu'}
label={t('session.launcher.AIAccelerator')}
tooltip={
<Trans
i18nKey={'session.launcher.DescAIAccelerator'}
/>
}
max={
resourceSlots['cuda.shares']
? baiClient._config.maxCUDASharesPerContainer
: baiClient._config.maxCUDADevicesPerContainer
}
step={resourceSlots['cuda.shares'] ? 0.1 : 1}
inputNumberProps={{
//TODO: change unit based on resource limit
addonAfter: 'GPU',
}}
required
rules={[
{
required: true,
},
]}
/>
)}
</>
);
}}
Expand Down
68 changes: 68 additions & 0 deletions react/src/helper/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { iSizeToSize } from './index';

describe('iSizeToSize', () => {
it('should convert iSize to Size with default fixed value', () => {
const sizeWithUnit = '1K';
const targetSizeUnit = 'B';
const result = iSizeToSize(sizeWithUnit, targetSizeUnit);
expect(result).toEqual({
number: 1024,
numberFixed: '1024.00',
unit: 'B',
numberUnit: '1024.00B',
});
});

it('should convert iSize to Size with fixed value of 0', () => {
const sizeWithUnit = '1K';
const targetSizeUnit = 'B';
const fixed = 0;
const result = iSizeToSize(sizeWithUnit, targetSizeUnit, fixed);
expect(result).toEqual({
number: 1024,
numberFixed: '1024',
unit: 'B',
numberUnit: '1024B',
});
});

it('should convert iSize to Size with targetSizeUnit of "k"', () => {
const sizeWithUnit = '1M';
const targetSizeUnit = 'k';
const result = iSizeToSize(sizeWithUnit, targetSizeUnit);
expect(result).toEqual({
number: 1024,
numberFixed: '1024.00',
unit: 'k',
numberUnit: '1024.00k',
});
});

it('should convert iSize to Size with targetSizeUnit of "t"', () => {
const sizeWithUnit = '1P';
const targetSizeUnit = 't';
const result = iSizeToSize(sizeWithUnit, targetSizeUnit);
expect(result).toEqual({
number: 0.0009765625,
numberFixed: '0.00',
unit: 't',
numberUnit: '0.00t',
});
});

it('should throw an error if size format is invalid', () => {
const sizeWithUnit = 'invalid';
expect(() => iSizeToSize(sizeWithUnit)).toThrow('Invalid size format');
});

it('should use default targetSizeUnit and fixed values if not provided', () => {
const sizeWithUnit = '1K';
const result = iSizeToSize(sizeWithUnit);
expect(result).toEqual({
number: 1,
numberFixed: '1.00',
unit: 'K',
numberUnit: '1.00K',
});
});
});
22 changes: 18 additions & 4 deletions react/src/helper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,22 @@ export const bytesToGB = (
};

export function iSizeToSize(
size: string,
targetSizeUnit?: string,
sizeWithUnit: string,
targetSizeUnit?:
| 'B'
| 'K'
| 'M'
| 'G'
| 'T'
| 'P'
| 'E'
| 'b'
| 'k'
| 'm'
| 'g'
| 't'
| 'p'
| 'e',
fixed: number = 2,
): {
number: number;
Expand All @@ -143,8 +157,8 @@ export function iSizeToSize(
numberUnit: string;
} {
const sizes = ['B', 'K', 'M', 'G', 'T', 'P', 'E'];
const sizeUnit = size.slice(-1).toUpperCase();
const sizeValue = parseFloat(size.slice(0, -1));
const sizeUnit = sizeWithUnit.slice(-1).toUpperCase();
const sizeValue = parseFloat(sizeWithUnit.slice(0, -1));
const sizeIndex = sizes.indexOf(sizeUnit);
if (sizeIndex === -1 || isNaN(sizeValue)) {
throw new Error('Invalid size format');
Expand Down
Loading

0 comments on commit 9ff6dcf

Please sign in to comment.