diff --git a/admin/app/services/docker_service.ts b/admin/app/services/docker_service.ts index 8a9aabf..bba8532 100644 --- a/admin/app/services/docker_service.ts +++ b/admin/app/services/docker_service.ts @@ -4,6 +4,7 @@ import logger from '@adonisjs/core/services/logger' import { inject } from '@adonisjs/core' import transmit from '@adonisjs/transmit/services/main' import { doResumableDownloadWithRetry } from '../utils/downloads.js' +import { mapGfxToHsaOverride } from '../utils/amd_hsa_override.js' import { join } from 'path' import os from 'node:os' import env from '#start/env' @@ -1477,10 +1478,9 @@ export class DockerService { * gfx1030 (RX 6800/6700/etc.), gfx1100/1101/1102 (RX 7900/7800/7600) are on AMD's * official ROCm allowlist — forcing an override on these breaks GPU discovery. * gfx1035 / gfx1036 (RDNA 2 iGPUs like 680M) need 10.3.0 to coerce to gfx1030. - * gfx1103 / gfx1150 / gfx1151 (RDNA 3/3.5 iGPUs like 780M / 890M / Strix Halo) are - * natively supported by the ROCm 7.2 the current ollama:rocm image bundles, so they - * need NO override. Forcing 11.0.0 coerces them to gfx1100's kernels — unnecessary on - * the 890M and a source of faults on the 780M (gfx1100 WMMA instructions it lacks). See #1056. + * gfx1150 / gfx1151 (RDNA 3.5 iGPUs like 890M / Strix Halo) ARE on the bundled rocblas + * allowlist, so they need NO override. gfx1103 (Phoenix 780M/760M) is NOT — it must be + * coerced to 11.0.0 (gfx1100 kernels) or ollama drops it to CPU. See ../utils/amd_hsa_override.ts. * * Resolution order: * 1. KV `ai.amdHsaOverride` — manual user override; accepts 'none' (disable) or a semver-style value. @@ -1536,27 +1536,18 @@ export class DockerService { // install_nomad.sh. Fall through to the default. } - logger.info('[DockerService] No AMD gfx marker; applying no HSA override (native ROCm discovery)') + logger.warn( + '[DockerService] AMD GPU configured but no gfx marker (/app/storage/.nomad-amd-gfx) and no ' + + 'ai.amdHsaOverride KV; relying on native ROCm discovery. iGPUs not on the bundled rocblas ' + + 'allowlist (e.g. 780M/gfx1103, 680M/gfx1035) will silently fall back to CPU. Set the ' + + 'ai.amdHsaOverride KV (e.g. 11.0.0 for a 780M) and force-reinstall the AI service if so.' + ) return null } private _mapGfxToHsaOverride(gfx: string): string | null { - // Officially supported by ROCm — no override needed - if (gfx === 'gfx1030' || gfx === 'gfx1100' || gfx === 'gfx1101' || gfx === 'gfx1102') { - return null - } - // RDNA 2 variants + iGPUs (gfx1031..gfx1036, e.g. Rembrandt 680M) — not natively - // supported, still need coercion to gfx1030. - if (/^gfx103[1-6]$/.test(gfx)) { - return '10.3.0' - } - // RDNA 3 / 3.5 mobile parts (Phoenix 780M = gfx1103, Strix 890M = gfx1150, Strix Halo = - // gfx1151) are native under ROCm 7.2 — no override (forcing 11.0.0 faults the 780M). See #1056. - if (gfx === 'gfx1103' || gfx === 'gfx1150' || gfx === 'gfx1151') { - return null - } - // Unknown/newer target: prefer native discovery over a coercion that's likely wrong. - return null + // Pure mapping lives in ../utils/amd_hsa_override.ts so it stays unit-testable. + return mapGfxToHsaOverride(gfx) } /** diff --git a/admin/app/utils/amd_hsa_override.ts b/admin/app/utils/amd_hsa_override.ts new file mode 100644 index 0000000..14b74ef --- /dev/null +++ b/admin/app/utils/amd_hsa_override.ts @@ -0,0 +1,52 @@ +/** + * Map an AMD GPU's gfx target to the `HSA_OVERRIDE_GFX_VERSION` value the ollama:rocm + * container needs, or `null` when the card is discovered natively and no override should + * be applied. + * + * This is intentionally a pure function so the mapping is unit-testable without + * constructing the Docker service or touching the container runtime. `DockerService` + * delegates its private `_mapGfxToHsaOverride` to this. + * + * The bundled `ollama/ollama:rocm` rocblas ships kernels for a fixed allowlist — as seen + * in ollama's own startup log: + * supported=[gfx1030, gfx1100/1101/1102, gfx1150/1151, gfx1200/1201, gfx908/90a/942/950] + * A target NOT in that list is dropped to CPU unless we coerce it onto a supported one via + * HSA_OVERRIDE_GFX_VERSION. + * + * Mapping: + * - gfx1030 / gfx1100 / gfx1101 / gfx1102 → none. Discrete RDNA 2/3 on the allowlist; + * forcing an override here breaks GPU discovery. + * - gfx1150 / gfx1151 (Strix 890M, Strix Halo) → none. RDNA 3.5 iGPUs that ARE on the + * allowlist under the bundled ROCm, so native discovery works. (#1076 got this right.) + * - gfx1103 (Phoenix/Hawk Point 780M/760M) → '11.0.0'. RDNA 3 iGPU that is NOT on the + * allowlist, so it must be coerced onto gfx1100's kernels. #1076 wrongly grouped it with + * gfx1150/1151 and dropped the override, silently sending the 780M to CPU (the very + * common iGPU this regression hit). 11.0.0 is the value that worked on v1.33.0 and that + * restores full GPU offload in the field; #1076's "gfx1100 WMMA fault" theory did not + * hold up. + * - gfx1031..gfx1036 (RDNA 2 iGPUs, e.g. Rembrandt 680M) → '10.3.0'. Not on the allowlist; + * coerce onto gfx1030. + * - anything else (unknown/newer target) → none. Prefer native discovery over a coercion + * that's likely wrong; a hardcoded default gets more wrong as ROCm adds native targets. + */ +export function mapGfxToHsaOverride(gfx: string): string | null { + // Officially supported by the bundled ROCm — no override needed. + if (gfx === 'gfx1030' || gfx === 'gfx1100' || gfx === 'gfx1101' || gfx === 'gfx1102') { + return null + } + // RDNA 3.5 iGPUs (Strix 890M = gfx1150, Strix Halo = gfx1151) — natively supported. + if (gfx === 'gfx1150' || gfx === 'gfx1151') { + return null + } + // RDNA 3 Phoenix/Hawk Point (780M/760M = gfx1103) — NOT on the rocblas allowlist; coerce + // to gfx1100 kernels or ollama drops it to CPU. + if (gfx === 'gfx1103') { + return '11.0.0' + } + // RDNA 2 variants + iGPUs (gfx1031..gfx1036, e.g. Rembrandt 680M) — coerce to gfx1030. + if (/^gfx103[1-6]$/.test(gfx)) { + return '10.3.0' + } + // Unknown/newer target: prefer native discovery over a coercion that's likely wrong. + return null +} diff --git a/admin/tests/unit/amd_hsa_override.spec.ts b/admin/tests/unit/amd_hsa_override.spec.ts new file mode 100644 index 0000000..c33c825 --- /dev/null +++ b/admin/tests/unit/amd_hsa_override.spec.ts @@ -0,0 +1,32 @@ +import * as assert from 'node:assert/strict' +import { test } from 'node:test' + +import { mapGfxToHsaOverride } from '../../app/utils/amd_hsa_override.js' + +test('gfx1103 (Phoenix 780M) coerces to 11.0.0 — the #1076 regression', () => { + // Not on the bundled rocblas allowlist; without this ollama drops it to CPU. + assert.equal(mapGfxToHsaOverride('gfx1103'), '11.0.0') +}) + +test('gfx1150 / gfx1151 (Strix 890M, Strix Halo) stay native — no override', () => { + // These ARE on the allowlist; forcing an override would needlessly coerce them. + assert.equal(mapGfxToHsaOverride('gfx1150'), null) + assert.equal(mapGfxToHsaOverride('gfx1151'), null) +}) + +test('RDNA 2 iGPUs (gfx1031..gfx1036, e.g. 680M) coerce to 10.3.0', () => { + assert.equal(mapGfxToHsaOverride('gfx1035'), '10.3.0') + assert.equal(mapGfxToHsaOverride('gfx1031'), '10.3.0') + assert.equal(mapGfxToHsaOverride('gfx1036'), '10.3.0') +}) + +test('discrete cards on the ROCm allowlist get no override', () => { + for (const gfx of ['gfx1030', 'gfx1100', 'gfx1101', 'gfx1102']) { + assert.equal(mapGfxToHsaOverride(gfx), null) + } +}) + +test('unknown / newer targets default to native discovery (no override)', () => { + assert.equal(mapGfxToHsaOverride('gfx9999'), null) + assert.equal(mapGfxToHsaOverride(''), null) +}) diff --git a/install/install_nomad.sh b/install/install_nomad.sh index 1650780..df34cb5 100644 --- a/install/install_nomad.sh +++ b/install/install_nomad.sh @@ -541,7 +541,12 @@ verify_gpu_setup() { amd_gfx_version='gfx1034' elif echo "${amd_devices}" | grep -iq 'Rembrandt'; then amd_gfx_version='gfx1035' - elif echo "${amd_devices}" | grep -iEq 'Phoenix1?|Phoenix2'; then + elif echo "${amd_devices}" | grep -iEq 'Phoenix[0-9]?|Hawk Point|Radeon (780M|760M)'; then + # Phoenix (Ryzen 7040) / Hawk Point (Ryzen 8040) — 780M & 760M are both gfx1103. + # lspci device strings vary (Phoenix1/Phoenix2/Phoenix3, "Hawk Point", or the bare + # "Radeon 780M Graphics" marketing name), so match all of them or the marker goes + # missing and the 780M silently drops to CPU. Kept before the Strix branches so a + # "Radeon 780M" string can't be miscaught. See gfx1103 regression. amd_gfx_version='gfx1103' elif echo "${amd_devices}" | grep -iEq 'Strix Halo'; then amd_gfx_version='gfx1151'