Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model Management: Enable update form #113

Merged
merged 4 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ function ModelActionButton (dispatch: ThunkDispatch<any, any, Action>, notificat
items.push({
text: 'Update',
id: 'editModel',
disabled: true,
disabledReason: 'This action will be available with LISA release 3.0.1',
disabled: !selectedModel,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non blocking: We don't technically need to do this since the parent dropdown is disabled if we don't have a model selected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I went back and forth on this. I liked this because if we change when the menu is disabled this would keep working. But it is superfluous now so I can remove it.

disabledReason: 'No model selected.',
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import { IAutoScalingConfig } from '../../../shared/model/model-management.model
import { Grid, Header, SpaceBetween } from '@cloudscape-design/components';
import Container from '@cloudscape-design/components/container';

export function AutoScalingConfig (props: FormProps<IAutoScalingConfig>) : ReactElement {
type AutoScalingConfigProps = FormProps<IAutoScalingConfig> & {
isEdit: boolean
};

export function AutoScalingConfig (props: AutoScalingConfigProps) : ReactElement {
return (
<SpaceBetween size={'s'}>
<Container
Expand All @@ -47,24 +51,35 @@ export function AutoScalingConfig (props: FormProps<IAutoScalingConfig>) : React
<span style={{lineHeight: '2.5em', paddingLeft: '0.5em'}}>instances</span>
</Grid>
</FormField>
{ props.isEdit &&
<FormField label='Desired Capacity' errorText={props.formErrors?.autoScalingConfig?.desiredCapacity}>
<Grid gridDefinition={[{colspan: 10}, {colspan: 2}]} disableGutters={true}>
<Input value={String(props.item.desiredCapacity)} type='number' inputMode='numeric' onBlur={() => props.touchFields(['autoScalingConfig.desiredCapacity'])} onChange={({ detail }) => {
props.setFields({ 'autoScalingConfig.desiredCapacity': detail.value.trim().length > 0 ? Number(detail.value) : undefined });
}}/>
<span style={{lineHeight: '2.5em', paddingLeft: '0.5em'}}>instances</span>
</Grid>
</FormField>
}
<FormField label='Cooldown' errorText={props.formErrors?.autoScalingConfig?.cooldown}>
<Grid gridDefinition={[{colspan: 10}, {colspan: 2}]} disableGutters={true}>
<Input value={props.item.cooldown.toString()} type='number' inputMode='numeric' onBlur={() => props.touchFields(['autoScalingConfig.cooldown'])} onChange={({ detail }) => {
props.setFields({ 'autoScalingConfig.Cooldown': Number(detail.value) });
}}/>
}} disabled={props.isEdit}/>
<span style={{lineHeight: '2.5em', paddingLeft: '0.5em'}}>seconds</span>
</Grid>
</FormField>
<FormField label='Default Instance Warmup' errorText={props.formErrors?.autoScalingConfig?.defaultInstanceWarmup}>
<Grid gridDefinition={[{colspan: 10}, {colspan: 2}]} disableGutters={true}>
<Input value={props.item.defaultInstanceWarmup.toString()} type='number' inputMode='numeric' onBlur={() => props.touchFields(['autoScalingConfig.defaultInstanceWarmup'])} onChange={({ detail }) => {
props.setFields({ 'autoScalingConfig.defaultInstanceWarmup': Number(detail.value) });
}}/>
}} disabled={props.isEdit}/>
<span style={{lineHeight: '2.5em', paddingLeft: '0.5em'}}>seconds</span>
</Grid>
</FormField>
</Container>
<Container

{ !props.isEdit && <Container
header={
<Header variant='h3'>Metric Config</Header>
}
Expand Down Expand Up @@ -97,7 +112,7 @@ export function AutoScalingConfig (props: FormProps<IAutoScalingConfig>) : React
</Grid>
</FormField>
</SpaceBetween>
</Container>
</Container>}
</SpaceBetween>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,28 @@ export function BaseModelConfig (props: FormProps<IModelRequest> & BaseModelConf
<FormField label='Model Name' errorText={props.formErrors?.modelName}>
<Input value={props.item.modelName} inputMode='text' onBlur={() => props.touchFields(['modelName'])} onChange={({ detail }) => {
props.setFields({ 'modelName': detail.value });
}}/>
}} disabled={props.isEdit}/>
</FormField>
<FormField label='Model URL' errorText={props.formErrors?.modelUrl}>
<Input value={props.item.modelUrl} inputMode='text' onBlur={() => props.touchFields(['modelUrl'])} onChange={({ detail }) => {
props.setFields({ 'modelUrl': detail.value });
}}/>
}} disabled={props.isEdit}/>
</FormField>
<FormField label='Model Type' errorText={props.formErrors?.modelType}>
<Select
selectedOption={{label: props.item.modelType.toUpperCase(), value: props.item.modelType}}
onChange={({ detail }) =>
props.setFields({
onChange={({ detail }) => {
const fields = {
'modelType': detail.selectedOption.value,
})
}
};

// turn off streaming for embedded models
if (fields.modelType === ModelType.embedding) {
fields['streaming'] = false;
}

props.setFields(fields);
}}
onBlur={() => props.touchFields(['modelType'])}
options={[
{ label: 'TEXTGEN', value: ModelType.textgen },
Expand All @@ -63,7 +70,7 @@ export function BaseModelConfig (props: FormProps<IModelRequest> & BaseModelConf
<FormField label='Instance Type' errorText={props.formErrors?.instanceType}>
<Input value={props.item.instanceType} inputMode='text' onBlur={() => props.touchFields(['instanceType'])} onChange={({ detail }) => {
props.setFields({ 'instanceType': detail.value });
}}/>
}} disabled={props.isEdit}/>
</FormField>
<FormField label='Inference Container' errorText={props.formErrors?.inferenceContainer}>
<Select
Expand All @@ -79,6 +86,7 @@ export function BaseModelConfig (props: FormProps<IModelRequest> & BaseModelConf
{ label: 'TEI', value: InferenceContainer.TEI },
{ label: 'VLLM', value: InferenceContainer.VLLM },
]}
disabled={props.isEdit}
/>
</FormField>
<Grid gridDefinition={[{ colspan: 6 }, { colspan: 6 }]}>
Expand All @@ -89,6 +97,7 @@ export function BaseModelConfig (props: FormProps<IModelRequest> & BaseModelConf
}
onBlur={() => props.touchFields(['lisaHostedModel'])}
checked={props.item.lisaHostedModel}
disabled={props.isEdit}
/>
</FormField>
<FormField label='Streaming' errorText={props.formErrors?.streaming}>
Expand All @@ -97,6 +106,7 @@ export function BaseModelConfig (props: FormProps<IModelRequest> & BaseModelConf
props.setFields({'streaming': detail.checked})
}
onBlur={() => props.touchFields(['streaming'])}
disabled={props.item.modelType === ModelType.embedding}
checked={props.item.streaming}
/>
</FormField>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,15 @@ export function CreateModelModal (props: CreateModelModalProps) : ReactElement {
if (isValid && !props.isEdit) {
createModelMutation(toSubmit);
} else if (isValid && props.isEdit) {
updateModelMutation(toSubmit);
// pick only the values we care about
updateModelMutation(_.pick({...changesDiff, modelId: props.selectedItems[0].modelId}, [
'modelId',
'streaming',
'enabled',
'modelType',
estohlmann marked this conversation as resolved.
Show resolved Hide resolved
'autoScalingConfig.minCapacity',
'autoScalingConfig.maxCapacity'
]));
}
}

Expand Down Expand Up @@ -189,6 +197,48 @@ export function CreateModelModal (props: CreateModelModalProps) : ReactElement {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isUpdateError, updateError, isUpdating, isUpdateSuccess]);

const steps = [
{
title: 'Base Model Configuration',
description: 'Define your model\'s configuration settings using these forms.',
content: (
<BaseModelConfig item={state.form} setFields={setFields} touchFields={touchFields} formErrors={errors} isEdit={props.isEdit} />
),
onEdit: true
},
{
title: 'Container Configuration',
content: (
<ContainerConfig item={state.form.containerConfig} setFields={setFields} touchFields={touchFields} formErrors={errors} />
),
isOptional: true
},
{
title: 'Auto Scaling Configuration',
content: (
<AutoScalingConfig item={state.form.autoScalingConfig} setFields={setFields} touchFields={touchFields} formErrors={errors} isEdit={props.isEdit} />
),
isOptional: true,
onEdit: true && state.form.lisaHostedModel
},
{
title: 'Load Balancer Configuration',
content: (
<LoadBalancerConfig item={state.form.loadBalancerConfig} setFields={setFields} touchFields={touchFields} formErrors={errors} />
),
isOptional: true
},
{
title: `Review and ${props.isEdit ? 'Update' : 'Create'}`,
description: `Review configuration ${props.isEdit ? 'changes' : ''} prior to submitting.`,
content: (
<ReviewModelChanges jsonDiff={changesDiff}/>
),
onEdit: state.form.lisaHostedModel
}
].filter((step) => props.isEdit ? step.onEdit : true);


return (
<Modal size={'large'} onDismiss={() => {
props.setVisible(false); props.setIsEdit(false); resetState();
Expand Down Expand Up @@ -242,43 +292,7 @@ export function CreateModelModal (props: CreateModelModalProps) : ReactElement {
activeStepIndex={state.activeStepIndex}
isLoadingNextStep={isCreating || isUpdating}
allowSkipTo
steps={[
{
title: 'Base Model Configuration',
description: 'Define your model\'s configuration settings using these forms.',
content: (
<BaseModelConfig item={state.form} setFields={setFields} touchFields={touchFields} formErrors={errors} isEdit={props.isEdit} />
)
},
{
title: 'Container Configuration',
content: (
<ContainerConfig item={state.form.containerConfig} setFields={setFields} touchFields={touchFields} formErrors={errors} />
),
isOptional: true
},
{
title: 'Auto Scaling Configuration',
content: (
<AutoScalingConfig item={state.form.autoScalingConfig} setFields={setFields} touchFields={touchFields} formErrors={errors} />
),
isOptional: true
},
{
title: 'Load Balancer Configuration',
content: (
<LoadBalancerConfig item={state.form.loadBalancerConfig} setFields={setFields} touchFields={touchFields} formErrors={errors} />
),
isOptional: true
},
{
title: `Review and ${props.isEdit ? 'Update' : 'Create'}`,
description: `Review configuration ${props.isEdit ? 'changes' : ''} prior to submitting.`,
content: (
<ReviewModelChanges jsonDiff={changesDiff}/>
)
}
]}
steps={steps}
/>
</Modal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export type ILoadBalancerConfig = {
export type IAutoScalingConfig = {
minCapacity: number;
maxCapacity: number;
desiredCapacity?: number;
cooldown: number;
defaultInstanceWarmup: number;
metricConfig: IMetricConfig;
Expand Down Expand Up @@ -162,9 +163,24 @@ export const loadBalancerConfigSchema = z.object({
export const autoScalingConfigSchema = z.object({
minCapacity: z.number().min(1).default(1),
maxCapacity: z.number().min(1).default(1),
desiredCapacity: z.number().optional(),
cooldown: z.number().min(1).default(420),
defaultInstanceWarmup: z.number().default(180),
metricConfig: metricConfigSchema.default(metricConfigSchema.parse({})),
}).superRefine((value, context) => {
// ensure the desired capacity stays between minCapacity/maxCapacity if not empty
if (value.desiredCapacity !== undefined && String(value.desiredCapacity).trim().length) {
const validator = z.number().min(Number(value.minCapacity)).max(Number(value.maxCapacity));
const result = validator.safeParse(value.desiredCapacity);
if (result.success === false) {
for (const error of result.error.errors) {
context.addIssue({
...error,
path: ['desiredCapacity']
});
}
}
}
});

export const containerConfigSchema = z.object({
Expand Down
Loading