project-nomad/admin/tests/unit/amd_hsa_override.spec.ts
jakeaturner a1b899f49a fix(gpu): keep an existing AMD HSA override across reinstall and update
Upgraded AMD iGPU installs sometimes carry a working
HSA_OVERRIDE_GFX_VERSION on nomad_ollama, set by hand or by an older
install path, but have no ai.amdHsaOverride KV and no .nomad-amd-gfx
marker. The CPU-fallback banner offered Fix: Reinstall, and the
reinstall rebuilt the container without the override, so a 780M
(gfx1103) stayed on CPU.

- Add pickAmdHsaOverride to utils/amd_hsa_override.ts with the
  resolution order KV, marker, existing container override, none.
  A marker still wins even when it maps to none. KV "none" still
  disables the override.
- forceReinstall inspects nomad_ollama before removing it and saves a
  container-only override to ai.amdHsaOverride. Saving it keeps the
  value through a reinstall that fails after the removal and shows it
  in the Set GFX Override modal.
- The update path passes the old container env to the resolver, so a
  routine Update keeps the override too.
- The GPU health check passes the container env, so
  diffAmdOllamaConfig compares against what a reinstall would build.
  On the reported box, the only drift is OLLAMA_IGPU_ENABLE=1.
- diagnoseAmdCpuFallback suggests the container's current override
  when one is set. With an override applied, Ollama logs the coerced
  target (gfx1100), which maps to none and would suggest removing a
  working value.
- Remove the unused _mapGfxToHsaOverride wrapper.

parseAmdGfxTargetFromLogs is unchanged. It still matches only
gpu_type=. The "dropping integrated GPU ... compute=gfx..." line needs
a full sample from an affected box before we can write a regex for it.
We'll address in a later PR.
2026-09-29 15:54:41 -07:00

92 lines
3.2 KiB
TypeScript

import * as assert from 'node:assert/strict'
import { test } from 'node:test'
import {
mapGfxToHsaOverride,
pickAmdHsaOverride,
readHsaOverrideFromEnv,
} 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)
})
const OVERRIDE_ENV = ['OLLAMA_NO_CLOUD=1', 'HSA_OVERRIDE_GFX_VERSION=11.0.0']
test('readHsaOverrideFromEnv reads the override or null', () => {
assert.equal(readHsaOverrideFromEnv(OVERRIDE_ENV), '11.0.0')
assert.equal(readHsaOverrideFromEnv(['OLLAMA_NO_CLOUD=1']), null)
assert.equal(readHsaOverrideFromEnv(null), null)
})
test('pickAmdHsaOverride: a KV version wins over the marker and container', () => {
assert.deepEqual(pickAmdHsaOverride({ manual: '10.3.0', markerGfx: 'gfx1103', containerEnv: OVERRIDE_ENV }), {
value: '10.3.0',
source: 'kv',
})
})
test('pickAmdHsaOverride: KV none disables even a container override', () => {
assert.deepEqual(pickAmdHsaOverride({ manual: 'none', markerGfx: null, containerEnv: OVERRIDE_ENV }), {
value: null,
source: 'kv-disabled',
})
})
test('pickAmdHsaOverride: a marker is authoritative even when it maps to none', () => {
assert.deepEqual(pickAmdHsaOverride({ manual: null, markerGfx: 'gfx1151', containerEnv: OVERRIDE_ENV }), {
value: null,
source: 'marker',
})
})
test('pickAmdHsaOverride keeps the container override with no KV or marker (#1377)', () => {
assert.deepEqual(pickAmdHsaOverride({ manual: null, markerGfx: null, containerEnv: OVERRIDE_ENV }), {
value: '11.0.0',
source: 'container',
})
assert.deepEqual(pickAmdHsaOverride({ manual: '', markerGfx: null, containerEnv: OVERRIDE_ENV }), {
value: '11.0.0',
source: 'container',
})
})
test('pickAmdHsaOverride ignores a malformed container override', () => {
assert.deepEqual(
pickAmdHsaOverride({ manual: null, markerGfx: null, containerEnv: ['HSA_OVERRIDE_GFX_VERSION=gfx1103'] }),
{ value: null, source: 'default' }
)
})
test('pickAmdHsaOverride skips an invalid KV value and reports it', () => {
assert.deepEqual(pickAmdHsaOverride({ manual: 'bogus', markerGfx: null, containerEnv: OVERRIDE_ENV }), {
value: '11.0.0',
source: 'container',
invalidManual: 'bogus',
})
})