feat: add extended_kv_cache
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import IconFont from '@/components/icon-font';
|
||||
import { isNotEmptyValue } from '@/utils/index';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import type { CascaderAutoProps } from 'antd';
|
||||
@@ -33,10 +34,11 @@ const renderTag = (props: any) => {
|
||||
|
||||
const OptionNodes = (props: {
|
||||
data: any;
|
||||
notFoundContent?: React.ReactNode;
|
||||
optionNode: React.FC<{ data: any }>;
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const { data, optionNode: OptionNode } = props;
|
||||
const { data, optionNode: OptionNode, notFoundContent } = props;
|
||||
if (data.value === '__EMPTY__') {
|
||||
return (
|
||||
<Empty
|
||||
@@ -48,9 +50,12 @@ const OptionNodes = (props: {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
description={intl.formatMessage({
|
||||
id: 'common.search.empty'
|
||||
})}
|
||||
description={
|
||||
notFoundContent ||
|
||||
intl.formatMessage({
|
||||
id: 'common.search.empty'
|
||||
})
|
||||
}
|
||||
></Empty>
|
||||
);
|
||||
}
|
||||
@@ -85,6 +90,7 @@ const SealCascader: React.FC<
|
||||
allowNull,
|
||||
isInFormItems = true,
|
||||
optionNode,
|
||||
notFoundContent,
|
||||
tagRender,
|
||||
...rest
|
||||
} = props;
|
||||
@@ -168,11 +174,13 @@ const SealCascader: React.FC<
|
||||
>
|
||||
<Cascader
|
||||
{...rest}
|
||||
suffixIcon={<IconFont type="icon-down"></IconFont>}
|
||||
optionRender={
|
||||
optionNode
|
||||
? (data) => (
|
||||
<OptionNodes
|
||||
data={data}
|
||||
notFoundContent={notFoundContent}
|
||||
optionNode={optionNode}
|
||||
></OptionNodes>
|
||||
)
|
||||
|
||||
@@ -158,6 +158,7 @@ const Inner = styled.div.attrs({
|
||||
className: '__inner__'
|
||||
})`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const Extra = styled.div`
|
||||
|
||||
@@ -10,6 +10,9 @@ const SliderWrapper = styled.div`
|
||||
.borderless {
|
||||
background-color: transparent;
|
||||
}
|
||||
.ant-slider {
|
||||
flex: 1;
|
||||
}
|
||||
padding-block: 0;
|
||||
padding-inline: 2px;
|
||||
input.ant-input-number-input {
|
||||
|
||||
@@ -182,5 +182,10 @@ export default {
|
||||
'models.table.users.all': 'All Users',
|
||||
'models.table.users.selected': 'Selected Users',
|
||||
'models.table.nouserFound': 'No users found',
|
||||
'models.form.performance': 'Performance'
|
||||
'models.form.performance': 'Performance',
|
||||
'models.form.gpus.notfound': 'No GPUs found',
|
||||
'models.form.extendedkvcache': 'Enable Extended KV Cache',
|
||||
'models.form.chunkSize': 'Size Of Cache Chunks',
|
||||
'models.form.maxCPUSize': 'Maximum CPU Cache Size (GiB)',
|
||||
'models.form.remoteURL': 'Remote Storage URL'
|
||||
};
|
||||
|
||||
@@ -179,7 +179,12 @@ export default {
|
||||
'models.table.users.all': 'All Users',
|
||||
'models.table.users.selected': 'Selected Users',
|
||||
'models.table.nouserFound': 'No users found',
|
||||
'models.form.performance': 'Performance'
|
||||
'models.form.performance': 'Performance',
|
||||
'models.form.gpus.notfound': 'No GPUs found',
|
||||
'models.form.extendedkvcache': 'Enable Extended KV Cache',
|
||||
'models.form.chunkSize': 'Size Of Cache Chunks',
|
||||
'models.form.maxCPUSize': 'Maximum CPU Cache Size (GiB)',
|
||||
'models.form.remoteURL': 'Remote Storage URL'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
@@ -202,5 +207,11 @@ export default {
|
||||
// 17. 'models.table.users.all': 'All Users',
|
||||
// 18. 'models.table.users.selected': 'Selected Users',
|
||||
// 19. 'models.table.nouserFound': 'No users found',
|
||||
// 20. 'models.form.performance': 'Performance'
|
||||
// 20. 'models.form.performance': 'Performance',
|
||||
// 21. 'models.form.gpus.notfound': 'No GPUs found',
|
||||
// 22. 'models.form.extendedkvcache': 'Enable Extended KV Cache',
|
||||
// 23. 'models.form.chunkSize': 'Size Of Cache Chunks',
|
||||
// 24. 'models.form.maxCPUSize': 'Maximum CPU Cache Size (GiB)',
|
||||
// 25. 'models.form.remoteURL': 'Remote Storage URL'
|
||||
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -182,7 +182,12 @@ export default {
|
||||
'models.table.users.all': 'All users',
|
||||
'models.table.users.selected': 'Selected users',
|
||||
'models.table.nouserFound': 'No users found',
|
||||
'models.form.performance': 'Performance'
|
||||
'models.form.performance': 'Performance',
|
||||
'models.form.gpus.notfound': 'No GPUs found',
|
||||
'models.form.extendedkvcache': 'Enable Extended KV Cache',
|
||||
'models.form.chunkSize': 'Size Of Cache Chunks',
|
||||
'models.form.maxCPUSize': 'Maximum CPU Cache Size (GiB)',
|
||||
'models.form.remoteURL': 'Remote Storage URL'
|
||||
};
|
||||
|
||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||
@@ -197,5 +202,10 @@ export default {
|
||||
// 9. 'models.table.users.all': 'All users',
|
||||
// 10. 'models.table.users.selected': 'Selected users',
|
||||
// 11. 'models.table.nouserFound': 'No users found',
|
||||
// 12. 'models.form.performance': 'Performance'
|
||||
// 12. 'models.form.performance': 'Performance',
|
||||
// 13. 'models.form.gpus.notfound': 'No GPUs found',
|
||||
// 14. 'models.form.extendedkvcache': 'Enable Extended KV Cache',
|
||||
// 15. 'models.form.chunkSize': 'Size Of Cache Chunks',
|
||||
// 16. 'models.form.maxCPUSize': 'Maximum CPU Cache Size (GiB)',
|
||||
// 17. 'models.form.remoteURL': 'Remote Storage URL'
|
||||
// ========== End of To-Do List ==========
|
||||
|
||||
@@ -172,5 +172,10 @@ export default {
|
||||
'models.table.users.all': '所有用户',
|
||||
'models.table.users.selected': '已选用户',
|
||||
'models.table.nouserFound': '未找到用户',
|
||||
'models.form.performance': '性能'
|
||||
'models.form.performance': '性能',
|
||||
'models.form.gpus.notfound': '未找到 GPU',
|
||||
'models.form.extendedkvcache': '启用扩展 KV 缓存',
|
||||
'models.form.chunkSize': '缓存分块大小',
|
||||
'models.form.maxCPUSize': 'CPU 缓存最大占用 (GiB)',
|
||||
'models.form.remoteURL': '远程存储地址'
|
||||
};
|
||||
|
||||
@@ -76,6 +76,12 @@ export interface FormData {
|
||||
optimize_long_prompt: boolean;
|
||||
enable_speculative_decoding: boolean;
|
||||
cluster_id: number;
|
||||
extended_kv_cache: {
|
||||
enabled: boolean;
|
||||
chunk_size: number;
|
||||
max_local_cpu_size: number;
|
||||
remote_url: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ComputedResourceClaim {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useFormContext } from '../config/form-context';
|
||||
import { FormData } from '../config/types';
|
||||
import Backend from '../forms/backend';
|
||||
import BackendParametersList from '../forms/backend-parameters-list';
|
||||
import ScheduleTypeForm from '../forms/schedule-type';
|
||||
|
||||
const placementStrategyTips = [
|
||||
{
|
||||
@@ -88,6 +89,7 @@ const AdvanceConfig = () => {
|
||||
options={modelCategories}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<ScheduleTypeForm></ScheduleTypeForm>
|
||||
<Backend></Backend>
|
||||
{scheduleType === ScheduleValueMap.Auto && (
|
||||
<>
|
||||
@@ -169,7 +171,7 @@ const AdvanceConfig = () => {
|
||||
[backendOptionsMap.vllm, backendOptionsMap.ascendMindie].includes(
|
||||
backend
|
||||
) && (
|
||||
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||
<div style={{ paddingBottom: 22 }}>
|
||||
<Form.Item<FormData>
|
||||
name="distributed_inference_across_workers"
|
||||
valuePropName="checked"
|
||||
@@ -187,7 +189,7 @@ const AdvanceConfig = () => {
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||
<div style={{ paddingBottom: 22 }}>
|
||||
<Form.Item<FormData>
|
||||
name="restart_on_error"
|
||||
valuePropName="checked"
|
||||
|
||||
@@ -191,6 +191,12 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
categories: null,
|
||||
restart_on_error: true,
|
||||
distributed_inference_across_workers: true,
|
||||
extended_kv_cache: {
|
||||
enabled: false,
|
||||
chunk_size: 256,
|
||||
max_local_cpu_size: 10,
|
||||
remote_url: ''
|
||||
},
|
||||
...initialValues
|
||||
}}
|
||||
>
|
||||
@@ -254,6 +260,28 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
|
||||
></SealSelect>
|
||||
}
|
||||
</Form.Item>
|
||||
<Form.Item<FormData>
|
||||
name="replicas"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('input', 'models.form.replicas')
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Number
|
||||
style={{ width: '100%' }}
|
||||
label={intl.formatMessage({
|
||||
id: 'models.form.replicas'
|
||||
})}
|
||||
required
|
||||
description={intl.formatMessage(
|
||||
{ id: 'models.form.replicas.tips' },
|
||||
{ api: `${window.location.origin}/v1` }
|
||||
)}
|
||||
min={0}
|
||||
></SealInput.Number>
|
||||
</Form.Item>
|
||||
<CatalogFrom></CatalogFrom>
|
||||
<Form.Item<FormData> name="description">
|
||||
<SealInput.TextArea
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import CheckboxField from '@/components/seal-form/checkbox-field';
|
||||
import SealInputNumber from '@/components/seal-form/input-number';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import { FormData } from '../config/types';
|
||||
|
||||
const KVCacheForm = () => {
|
||||
const intl = useIntl();
|
||||
const form = Form.useFormInstance();
|
||||
const kvCacheEnabled = Form.useWatch(['extended_kv_cache', 'enabled'], form);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ paddingBottom: 22 }}>
|
||||
<Form.Item<FormData>
|
||||
name={['extended_kv_cache', 'enabled']}
|
||||
valuePropName="checked"
|
||||
style={{ padding: '0 10px', marginBottom: 0 }}
|
||||
noStyle
|
||||
>
|
||||
<CheckboxField
|
||||
label={intl.formatMessage({ id: 'models.form.extendedkvcache' })}
|
||||
></CheckboxField>
|
||||
</Form.Item>
|
||||
</div>
|
||||
{kvCacheEnabled && (
|
||||
<>
|
||||
<Form.Item<FormData>
|
||||
name={['extended_kv_cache', 'max_local_cpu_size']}
|
||||
>
|
||||
<SealInputNumber
|
||||
label={intl.formatMessage({ id: 'models.form.maxCPUSize' })}
|
||||
min={0}
|
||||
step={1}
|
||||
precision={0}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name={['extended_kv_cache', 'chunk_size']}>
|
||||
<SealInputNumber
|
||||
label={intl.formatMessage({ id: 'models.form.chunkSize' })}
|
||||
min={0}
|
||||
step={1}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name={['extended_kv_cache', 'remote_url']}>
|
||||
<SealInput.Input
|
||||
label={intl.formatMessage({ id: 'models.form.remoteURL' })}
|
||||
min={0}
|
||||
step={1}
|
||||
placeholder="protocol://host:port"
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default KVCacheForm;
|
||||
@@ -1,143 +1,15 @@
|
||||
import SealCascader from '@/components/seal-form/seal-cascader';
|
||||
import SealInput from '@/components/seal-form/seal-input';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
import TooltipList from '@/components/tooltip-list';
|
||||
import useAppUtils from '@/hooks/use-app-utils';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import React from 'react';
|
||||
import GPUCard from '../components/gpu-card';
|
||||
import { scheduleList, ScheduleValueMap } from '../config';
|
||||
import { backendOptionsMap } from '../config/backend-parameters';
|
||||
import { useCatalogFormContext, useFormContext } from '../config/form-context';
|
||||
import { FormData } from '../config/types';
|
||||
|
||||
const scheduleTypeTips = [
|
||||
{
|
||||
title: {
|
||||
text: 'models.form.scheduletype.auto',
|
||||
locale: true
|
||||
},
|
||||
tips: 'models.form.scheduletype.auto.tips'
|
||||
},
|
||||
{
|
||||
title: {
|
||||
text: 'models.form.scheduletype.manual',
|
||||
locale: true
|
||||
},
|
||||
tips: 'models.form.scheduletype.manual.tips'
|
||||
}
|
||||
];
|
||||
import KVCacheForm from './kv-cache';
|
||||
|
||||
const Performance: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { onValuesChange, gpuOptions } = useFormContext();
|
||||
const { onQuantizationChange } = useCatalogFormContext();
|
||||
const { getRuleMessage } = useAppUtils();
|
||||
const form = Form.useFormInstance();
|
||||
const scheduleType = Form.useWatch('scheduleType', form);
|
||||
|
||||
const handleScheduleTypeChange = (value: string) => {
|
||||
if (value === ScheduleValueMap.Auto) {
|
||||
onValuesChange?.({}, form.getFieldsValue());
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnQuantizationChange = (val: any) => {
|
||||
onQuantizationChange?.(val);
|
||||
};
|
||||
|
||||
const handleBeforeGpuSelectorChange = (gpuIds: any[]) => {};
|
||||
|
||||
const handleGpuSelectorChange = (value: any[]) => {
|
||||
handleBeforeGpuSelectorChange(value);
|
||||
onValuesChange?.({}, form.getFieldsValue());
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item<FormData>
|
||||
name="replicas"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('input', 'models.form.replicas')
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealInput.Number
|
||||
style={{ width: '100%' }}
|
||||
label={intl.formatMessage({
|
||||
id: 'models.form.replicas'
|
||||
})}
|
||||
required
|
||||
description={intl.formatMessage(
|
||||
{ id: 'models.form.replicas.tips' },
|
||||
{ api: `${window.location.origin}/v1` }
|
||||
)}
|
||||
min={0}
|
||||
></SealInput.Number>
|
||||
</Form.Item>
|
||||
<Form.Item name="scheduleType">
|
||||
<SealSelect
|
||||
onChange={handleScheduleTypeChange}
|
||||
label={intl.formatMessage({ id: 'models.form.scheduletype' })}
|
||||
description={<TooltipList list={scheduleTypeTips}></TooltipList>}
|
||||
options={scheduleList}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
{scheduleType === ScheduleValueMap.SpecificGPUType && (
|
||||
<>
|
||||
<Form.Item name={['gpu_selector', 'gpu_type']}>
|
||||
<SealSelect
|
||||
label={intl.formatMessage({ id: 'models.form.gpuType' })}
|
||||
options={[]}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<Form.Item name={['gpu_selector', 'gpu_count']}>
|
||||
<SealSelect
|
||||
label={intl.formatMessage({ id: 'models.form.gpuCount' })}
|
||||
options={[]}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{scheduleType === ScheduleValueMap.Manual &&
|
||||
!form.getFieldValue('fix_gpu_type') && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['gpu_selector', 'gpu_ids']}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('select', 'models.form.gpuselector')
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealCascader
|
||||
required
|
||||
showSearch
|
||||
expandTrigger="hover"
|
||||
multiple={
|
||||
form.getFieldValue('backend') !== backendOptionsMap.voxBox
|
||||
}
|
||||
classNames={{
|
||||
popup: {
|
||||
root: 'cascader-popup-wrapper gpu-selector'
|
||||
}
|
||||
}}
|
||||
maxTagCount={1}
|
||||
label={intl.formatMessage({ id: 'models.form.gpuselector' })}
|
||||
options={gpuOptions}
|
||||
showCheckedStrategy="SHOW_CHILD"
|
||||
value={form.getFieldValue(['gpu_selector', 'gpu_ids'])}
|
||||
optionNode={GPUCard}
|
||||
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||
onChange={handleGpuSelectorChange}
|
||||
></SealCascader>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<KVCacheForm></KVCacheForm>
|
||||
{/* <div style={{ paddingBottom: 22, paddingLeft: 10 }}>
|
||||
<Form.Item<FormData>
|
||||
name="optimize_long_prompt"
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import SealCascader from '@/components/seal-form/seal-cascader';
|
||||
import SealSelect from '@/components/seal-form/seal-select';
|
||||
import TooltipList from '@/components/tooltip-list';
|
||||
import useAppUtils from '@/hooks/use-app-utils';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Form } from 'antd';
|
||||
import React from 'react';
|
||||
import GPUCard from '../components/gpu-card';
|
||||
import { scheduleList, ScheduleValueMap } from '../config';
|
||||
import { backendOptionsMap } from '../config/backend-parameters';
|
||||
import { useCatalogFormContext, useFormContext } from '../config/form-context';
|
||||
|
||||
const scheduleTypeTips = [
|
||||
{
|
||||
title: {
|
||||
text: 'models.form.scheduletype.auto',
|
||||
locale: true
|
||||
},
|
||||
tips: 'models.form.scheduletype.auto.tips'
|
||||
},
|
||||
{
|
||||
title: {
|
||||
text: 'models.form.scheduletype.manual',
|
||||
locale: true
|
||||
},
|
||||
tips: 'models.form.scheduletype.manual.tips'
|
||||
}
|
||||
];
|
||||
|
||||
const ScheduleTypeForm: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { onValuesChange, gpuOptions } = useFormContext();
|
||||
const { onQuantizationChange } = useCatalogFormContext();
|
||||
const { getRuleMessage } = useAppUtils();
|
||||
const form = Form.useFormInstance();
|
||||
const scheduleType = Form.useWatch('scheduleType', form);
|
||||
|
||||
const handleScheduleTypeChange = (value: string) => {
|
||||
if (value === ScheduleValueMap.Auto) {
|
||||
onValuesChange?.({}, form.getFieldsValue());
|
||||
}
|
||||
};
|
||||
|
||||
const handleOnQuantizationChange = (val: any) => {
|
||||
onQuantizationChange?.(val);
|
||||
};
|
||||
|
||||
const handleBeforeGpuSelectorChange = (gpuIds: any[]) => {};
|
||||
|
||||
const handleGpuSelectorChange = (value: any[]) => {
|
||||
handleBeforeGpuSelectorChange(value);
|
||||
onValuesChange?.({}, form.getFieldsValue());
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item name="scheduleType">
|
||||
<SealSelect
|
||||
onChange={handleScheduleTypeChange}
|
||||
label={intl.formatMessage({ id: 'models.form.scheduletype' })}
|
||||
description={<TooltipList list={scheduleTypeTips}></TooltipList>}
|
||||
options={scheduleList}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
{scheduleType === ScheduleValueMap.SpecificGPUType && (
|
||||
<>
|
||||
<Form.Item name={['gpu_selector', 'gpu_type']}>
|
||||
<SealSelect
|
||||
label={intl.formatMessage({ id: 'models.form.gpuType' })}
|
||||
options={[]}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
<Form.Item name={['gpu_selector', 'gpu_count']}>
|
||||
<SealSelect
|
||||
label={intl.formatMessage({ id: 'models.form.gpuCount' })}
|
||||
options={[]}
|
||||
></SealSelect>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{scheduleType === ScheduleValueMap.Manual &&
|
||||
!form.getFieldValue('fix_gpu_type') && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={['gpu_selector', 'gpu_ids']}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('select', 'models.form.gpuselector')
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealCascader
|
||||
required
|
||||
showSearch
|
||||
expandTrigger="hover"
|
||||
multiple={
|
||||
form.getFieldValue('backend') !== backendOptionsMap.voxBox
|
||||
}
|
||||
classNames={{
|
||||
popup: {
|
||||
root: 'cascader-popup-wrapper gpu-selector'
|
||||
}
|
||||
}}
|
||||
maxTagCount={1}
|
||||
label={intl.formatMessage({ id: 'models.form.gpuselector' })}
|
||||
options={gpuOptions}
|
||||
showCheckedStrategy="SHOW_CHILD"
|
||||
value={form.getFieldValue(['gpu_selector', 'gpu_ids'])}
|
||||
optionNode={GPUCard}
|
||||
getPopupContainer={(triggerNode) => triggerNode.parentNode}
|
||||
notFoundContent={intl.formatMessage({
|
||||
id: 'models.form.gpus.notfound'
|
||||
})}
|
||||
onChange={handleGpuSelectorChange}
|
||||
></SealCascader>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScheduleTypeForm;
|
||||
Reference in New Issue
Block a user