feat: add extended_kv_cache

This commit is contained in:
jialin
2025-10-14 20:09:43 +08:00
parent 620c113fae
commit 83403a1a1a
13 changed files with 277 additions and 142 deletions
+12 -4
View File
@@ -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 {
+6 -1
View File
@@ -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'
};
+13 -2
View File
@@ -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 ==========
+12 -2
View File
@@ -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 ==========
+6 -1
View File
@@ -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': '远程存储地址'
};
+6
View File
@@ -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 {
+4 -2
View File
@@ -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"
+28
View File
@@ -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
+60
View File
@@ -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;
+2 -130
View File
@@ -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"
+124
View File
@@ -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;