feat(builder): support remote machine learning (#771)

This commit is contained in:
bo0tzz
2026-09-03 14:22:35 +02:00
committed by GitHub
parent 0850d23a92
commit 73066127bf
5 changed files with 88 additions and 25 deletions
@@ -245,6 +245,16 @@ describe('buildCompose', () => {
expect(spec.services['immich-server'].depends_on).toEqual(['redis']);
});
it('remote machine learning drops the service, its model cache, and any hwaccel fragment', () => {
const config = structuredClone(DEFAULT_CONFIG);
config.machineLearning.external = true;
config.hwaccel.ml = 'cuda';
const spec = parse(buildCompose(config, VERSION));
expect(spec.services['immich-machine-learning']).toBeUndefined();
expect(spec.volumes).toBeUndefined();
expect(buildCompose(config, VERSION)).not.toContain('driver: nvidia');
});
it('external Redis drops the redis service and sets non-default REDIS_ vars', () => {
const config = structuredClone(DEFAULT_CONFIG);
config.redis.external = true;
+26 -15
View File
@@ -122,6 +122,23 @@ const build = (config: ImmichConfig, version: string): { spec: ComposeFile; fiel
);
const overrideKeys = overrides.map(({ key }) => key);
const bundledMl = !config.machineLearning.external;
const mlService: Record<string, ComposeService> = bundledMl
? {
'immich-machine-learning': deepmerge<ComposeService>(
{
container_name: containerName('immich_machine_learning'),
image: IMAGES.machineLearning(version + ML_BACKENDS[config.hwaccel.ml].tag),
volumes: rootlessVolumes('immich-machine-learning') ?? [`${NamedVolume.ModelCache}:/cache`],
restart: 'always',
healthcheck: { disable: false },
},
ML_BACKENDS[config.hwaccel.ml].fragment,
),
}
: {};
const services: Record<string, ComposeService> = {
'immich-server': deepmerge<ComposeService>(
{
@@ -142,16 +159,7 @@ const build = (config: ImmichConfig, version: string): { spec: ComposeFile; fiel
},
TRANSCODE_BACKENDS[config.hwaccel.transcoding].fragment,
),
'immich-machine-learning': deepmerge<ComposeService>(
{
container_name: containerName('immich_machine_learning'),
image: IMAGES.machineLearning(version + ML_BACKENDS[config.hwaccel.ml].tag),
volumes: rootlessVolumes('immich-machine-learning') ?? [`${NamedVolume.ModelCache}:/cache`],
restart: 'always',
healthcheck: { disable: false },
},
ML_BACKENDS[config.hwaccel.ml].fragment,
),
...mlService,
...backingServices,
};
@@ -194,6 +202,7 @@ const build = (config: ImmichConfig, version: string): { spec: ComposeFile; fiel
return value === undefined || value === '' ? [] : [['services', 'immich-server', 'environment', key]];
},
bundledDatabase: !config.database.external,
bundledMl,
databaseVolume,
network: externalNetwork
? [
@@ -217,11 +226,13 @@ const build = (config: ImmichConfig, version: string): { spec: ComposeFile; fiel
'immich-server',
key,
])
: Object.keys(ML_BACKENDS[config.hwaccel.ml].fragment).map((key) => [
'services',
'immich-machine-learning',
key,
]),
: bundledMl
? Object.keys(ML_BACKENDS[config.hwaccel.ml].fragment).map((key) => [
'services',
'immich-machine-learning',
key,
])
: [],
};
const fields: FieldPaths = Object.fromEntries(
+17 -5
View File
@@ -54,6 +54,9 @@ const immichConfigSchema = z.object({
transcoding: transcodeAccelSchema,
ml: mlAccelSchema,
}),
machineLearning: z.object({
external: z.boolean(),
}),
storage: z.object({
uploadLocation: required('Upload location is required.'),
customFolders: z.boolean(),
@@ -90,6 +93,7 @@ export type OutputContext = {
services: string[];
env: (key: string) => YamlPath[];
bundledDatabase: boolean;
bundledMl: boolean;
databaseVolume: YamlPath[];
network: YamlPath[];
rootless: YamlPath[];
@@ -110,10 +114,7 @@ const DATABASE = ['services', 'database'];
export const FIELDS = {
version: {
config: [],
output: ({ server, ml }) => [
[...server, 'image'],
[...ml, 'image'],
],
output: ({ server, ml, bundledMl }) => [[...server, 'image'], ...(bundledMl ? [[...ml, 'image']] : [])],
},
timezone: { config: ['timezone'], output: ({ env }) => env('TZ') },
port: { config: ['port'], advanced: true, output: ({ server }) => [[...server, 'ports', 0]] },
@@ -129,7 +130,15 @@ export const FIELDS = {
customFolders: { config: ['storage.customFolders'], advanced: true, output: ({ overrides }) => overrides },
externalLibraries: { config: ['storage.externalLibraries'], advanced: true, output: ({ libraries }) => libraries },
transcoding: { config: ['hwaccel.transcoding'], output: ({ fragment }) => fragment('transcoding') },
ml: { config: ['hwaccel.ml'], output: ({ ml, fragment }) => [[...ml, 'image'], ...fragment('ml')] },
ml: {
config: ['hwaccel.ml'],
output: ({ ml, fragment, bundledMl }) => (bundledMl ? [[...ml, 'image'], ...fragment('ml')] : []),
},
mlExternal: {
config: ['machineLearning.external'],
advanced: true,
output: ({ ml, bundledMl }) => (bundledMl ? [ml] : []),
},
databaseExternal: {
config: ['database.external'],
advanced: true,
@@ -216,6 +225,9 @@ export const DEFAULT_CONFIG: ImmichConfig = {
transcoding: 'cpu',
ml: 'cpu',
},
machineLearning: {
external: false,
},
storage: {
uploadLocation: './library',
customFolders: false,
@@ -61,6 +61,15 @@ const configs: [string, ImmichConfig][] = [
return config;
})(),
],
[
'remote machine learning',
(() => {
const config = structuredClone(DEFAULT_CONFIG);
config.machineLearning.external = true;
config.hwaccel.ml = 'openvino';
return config;
})(),
],
];
const everything = configs[1][1];
@@ -499,11 +499,32 @@
<CardTitle>Machine Learning</CardTitle>
</CardHeader>
<CardBody>
<Field label="Hardware Acceleration">
<div data-field="ml">
<Select bind:value={config.hwaccel.ml} options={ML_ACCELS} />
</div>
</Field>
<Stack gap={4}>
{#if advanced}
<Field label="Remote machine learning">
<Switch
bind:checked={config.machineLearning.external}
data-field="mlExternal"
class="flex justify-between gap-4"
/>
</Field>
{/if}
{#if advanced && config.machineLearning.external}
<Text size="small" color="muted">
Point your server at the remote instance in the admin settings. See the
<Link href="https://docs.immich.app/guides/remote-machine-learning"
>remote machine learning guide</Link
>.
</Text>
{/if}
{#if !config.machineLearning.external}
<Field label="Hardware Acceleration">
<div data-field="ml">
<Select bind:value={config.hwaccel.ml} options={ML_ACCELS} />
</div>
</Field>
{/if}
</Stack>
</CardBody>
</Card>