fix(amd): coerce gfx1103 (780M) to HSA_OVERRIDE 11.0.0 so it stays on GPU (#1134)

PR #1076 stopped forcing HSA_OVERRIDE_GFX_VERSION=11.0.0 on
"natively-supported" AMD iGPUs. That was correct for gfx1150/gfx1151
(Strix 890M / Strix Halo, which are in the bundled rocblas allowlist)
but wrong for gfx1103 (Phoenix/Hawk Point 780M/760M), which is NOT in
that list. Without the override, ollama drops the 780M with "no rocblas
support for gfx target" and falls back to CPU on a fresh AI provision.

Extract the gfx→HSA mapping into a pure, unit-tested util and map
gfx1103 → 11.0.0 (gfx1100 kernels), the value that worked on v1.33.0 and
that restores full GPU offload in the field. gfx1150/1151 stay native.

Also harden the installer's 780M detection (Hawk Point / "Radeon 780M/
760M" strings) so the gfx marker isn't silently deleted, and upgrade the
no-marker fallback log from info to warn since it can mask CPU fallback.
This commit is contained in:
Jake Turner 2026-07-22 11:07:42 -07:00 committed by jakeaturner
parent f5f2944516
commit 90489e06da
No known key found for this signature in database
GPG Key ID: B1072EBDEECE328D
4 changed files with 102 additions and 22 deletions

View File

@ -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)
}
/**

View File

@ -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
}

View File

@ -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)
})

View File

@ -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'