feat(ml)!: customizable ML settings (#3891)

* consolidated endpoints, added live configuration

* added ml settings to server

* added settings dashboard

* updated deps, fixed typos

* simplified modelconfig

updated tests

* Added ml setting accordion for admin page

updated tests

* merge `clipText` and `clipVision`

* added face distance setting

clarified setting

* add clip mode in request, dropdown for face models

* polished ml settings

updated descriptions

* update clip field on error

* removed unused import

* add description for image classification threshold

* pin safetensors for arm wheel

updated poetry lock

* moved dto

* set model type only in ml repository

* revert form-data package install

use fetch instead of axios

* added slotted description with link

updated facial recognition description

clarified effect of disabling tasks

* validation before model load

* removed unnecessary getconfig call

* added migration

* updated api

updated api

updated api

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Mert 2023-08-29 09:58:00 -04:00 committed by GitHub
parent 22f5e05060
commit bcc36d14a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
56 changed files with 2324 additions and 655 deletions

View file

@ -4,38 +4,45 @@
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import { api, SystemConfigDto } from '@api';
import { api, SystemConfigMachineLearningDto } from '@api';
import { isEqual } from 'lodash-es';
import { fade } from 'svelte/transition';
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import SettingSwitch from '../setting-switch.svelte';
import SettingAccordion from '../setting-accordion.svelte';
import SettingSelect from '../setting-select.svelte';
export let machineLearningConfig: SystemConfigMachineLearningDto; // this is the config that is being edited
export let disabled = false;
let config: SystemConfigDto;
let defaultConfig: SystemConfigDto;
let savedConfig: SystemConfigMachineLearningDto;
let defaultConfig: SystemConfigMachineLearningDto;
async function refreshConfig() {
[config, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data),
api.systemConfigApi.getDefaults().then((res) => res.data),
[savedConfig, defaultConfig] = await Promise.all([
api.systemConfigApi.getConfig().then((res) => res.data.machineLearning),
api.systemConfigApi.getDefaults().then((res) => res.data.machineLearning),
]);
}
async function reset() {
const { data: resetConfig } = await api.systemConfigApi.getConfig();
config = resetConfig;
machineLearningConfig = { ...resetConfig.machineLearning };
savedConfig = { ...resetConfig.machineLearning };
notificationController.show({ message: 'Reset to the last saved settings', type: NotificationType.Info });
}
async function saveSetting() {
try {
const { data: current } = await api.systemConfigApi.getConfig();
await api.systemConfigApi.updateConfig({
systemConfigDto: { ...current, machineLearning: config.machineLearning },
const result = await api.systemConfigApi.updateConfig({
systemConfigDto: { ...current, machineLearning: machineLearningConfig },
});
await refreshConfig();
machineLearningConfig = { ...result.data.machineLearning };
savedConfig = { ...result.data.machineLearning };
notificationController.show({ message: 'Settings saved', type: NotificationType.Info });
} catch (error) {
handleError(error, 'Unable to save settings');
@ -43,10 +50,7 @@
}
async function resetToDefault() {
await refreshConfig();
const { data: defaults } = await api.systemConfigApi.getDefaults();
config = defaults;
machineLearningConfig = { ...defaultConfig };
notificationController.show({ message: 'Reset settings to defaults', type: NotificationType.Info });
}
</script>
@ -54,52 +58,152 @@
<div class="mt-2">
{#await refreshConfig() then}
<div in:fade={{ duration: 500 }}>
<form autocomplete="off" on:submit|preventDefault class="mx-4 flex flex-col gap-4 py-4">
<SettingSwitch
title="Enabled"
subtitle="Use machine learning features"
{disabled}
bind:checked={config.machineLearning.enabled}
/>
<form autocomplete="off" on:submit|preventDefault class="mx-4 mt-4">
<div class="flex flex-col gap-4">
<SettingSwitch
title="ENABLED"
subtitle="If disabled, all ML features will be disabled regardless of the below settings."
{disabled}
bind:checked={machineLearningConfig.enabled}
/>
<hr />
<hr />
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="URL"
desc="URL of machine learning server"
bind:value={config.machineLearning.url}
required={true}
disabled={disabled || !config.machineLearning.enabled}
isEdited={!(config.machineLearning.url === config.machineLearning.url)}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="URL"
desc="URL of the machine learning server"
bind:value={machineLearningConfig.url}
required={true}
disabled={disabled || !machineLearningConfig.enabled}
isEdited={machineLearningConfig.url !== savedConfig.url}
/>
</div>
<SettingSwitch
title="SMART SEARCH"
subtitle="Extract CLIP embeddings for smart search"
bind:checked={config.machineLearning.clipEncodeEnabled}
disabled={disabled || !config.machineLearning.enabled}
/>
<SettingAccordion title="Image Tagging" subtitle="Tag and classify images with object labels">
<div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSwitch
title="ENABLED"
subtitle="If disabled, images will not be tagged. This affects the Things section in the Explore page as well as 'm:' searches."
bind:checked={machineLearningConfig.classification.enabled}
disabled={disabled || !machineLearningConfig.enabled}
/>
<SettingSwitch
title="FACIAL RECOGNITION"
subtitle="Recognize and group faces in photos"
disabled={disabled || !config.machineLearning.enabled}
bind:checked={config.machineLearning.facialRecognitionEnabled}
/>
<hr />
<SettingSwitch
title="IMAGE TAGGING"
subtitle="Tag and classify images"
disabled={disabled || !config.machineLearning.enabled}
bind:checked={config.machineLearning.tagImageEnabled}
/>
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="IMAGE CLASSIFICATION MODEL"
bind:value={machineLearningConfig.classification.modelName}
required={true}
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.classification.enabled}
isEdited={machineLearningConfig.classification.modelName !== savedConfig.classification.modelName}
>
<p slot="desc" class="immich-form-label pb-2 text-sm">
The name of an image classification model listed <a
href="https://huggingface.co/models?pipeline_tag=image-classification&sort=trending"><u>here</u></a
>. It must be tagged with the 'Image Classification' task and must support ONNX conversion.
</p>
</SettingInputField>
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
label="IMAGE CLASSIFICATION THRESHOLD"
desc="Minimum confidence score to add a particular object tag. Lower values will add more tags to images, but may result in more false positives. Will not have any effect until the Tag Objects job is re-run."
bind:value={machineLearningConfig.classification.minScore}
step="0.1"
min="0"
max="1"
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.classification.enabled}
isEdited={machineLearningConfig.classification.minScore !== savedConfig.classification.minScore}
/>
</div>
</SettingAccordion>
<SettingAccordion title="Smart Search" subtitle="Search for images semantically using CLIP embeddings">
<div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSwitch
title="ENABLED"
subtitle="If disabled, images will not be encoded for smart search."
bind:checked={machineLearningConfig.clip.enabled}
disabled={disabled || !machineLearningConfig.enabled}
/>
<hr />
<SettingInputField
inputType={SettingInputFieldType.TEXT}
label="CLIP MODEL"
bind:value={machineLearningConfig.clip.modelName}
required={true}
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.clip.enabled}
isEdited={machineLearningConfig.clip.modelName !== savedConfig.clip.modelName}
>
<p slot="desc" class="immich-form-label pb-2 text-sm">
The name of a CLIP model listed <a
href="https://clip-as-service.jina.ai/user-guides/benchmark/#size-and-efficiency"><u>here</u></a
>. Note that you must re-run the 'Encode CLIP' job for all images upon changing a model.
</p>
</SettingInputField>
</div>
</SettingAccordion>
<SettingAccordion title="Facial Recognition" subtitle="Detect, recognize and group faces in images">
<div class="ml-4 mt-4 flex flex-col gap-4">
<SettingSwitch
title="ENABLED"
subtitle="If disabled, images will not be encoded for facial recognition and will not populate the People section in the Explore page."
bind:checked={machineLearningConfig.facialRecognition.enabled}
disabled={disabled || !machineLearningConfig.enabled}
/>
<hr />
<SettingSelect
label="FACIAL RECOGNITION MODEL"
desc="Smaller models are faster and use less memory, but perform worse. Note that you must re-run the Recognize Faces job for all images upon changing a model."
name="facial-recognition-model"
bind:value={machineLearningConfig.facialRecognition.modelName}
options={[
{ value: 'buffalo_l', text: 'buffalo_l' },
{ value: 'buffalo_s', text: 'buffalo_s' },
]}
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.facialRecognition.enabled}
isEdited={machineLearningConfig.facialRecognition.modelName !== savedConfig.facialRecognition.modelName}
/>
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
label="MIN DETECTION SCORE"
desc="Minimum confidence score for a face to be detected from 0-1. Lower values will detect more faces but may result in false positives."
bind:value={machineLearningConfig.facialRecognition.minScore}
step="0.1"
min="0"
max="1"
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.facialRecognition.enabled}
isEdited={machineLearningConfig.facialRecognition.minScore !== savedConfig.facialRecognition.minScore}
/>
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
label="MAX RECOGNITION DISTANCE"
desc="Maximum distance between two faces to be considered the same person, ranging from 0-2. Lowering this can prevent labeling two people as the same person, while raising it can prevent labeling the same person as two different people. Note that it is easier to merge two people than to split one person in two, so err on the side of a lower threshold when possible."
bind:value={machineLearningConfig.facialRecognition.maxDistance}
step="0.1"
min="0"
max="2"
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.facialRecognition.enabled}
isEdited={machineLearningConfig.facialRecognition.maxDistance !==
savedConfig.facialRecognition.maxDistance}
/>
</div>
</SettingAccordion>
<SettingButtonsRow
on:reset={reset}
on:save={saveSetting}
on:reset-to-default={resetToDefault}
showResetToDefault={!isEqual(config, defaultConfig)}
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
{disabled}
/>
</form>