Skip to content

Commit d7df7b7

Browse files
fix: select one discovered HTP session for Hexagon inference (#907)
* fix: select one discovered Hexagon device * fix: resolve saved Hexagon devices before model initialization * chore: cover exact Hexagon selection in Settings and benchmarks * chore: isolate device-rule fetching in store-dependent unit tests --------- Co-authored-by: a-ghorbani <ghorbani59@gmail.com>
1 parent ced4fad commit d7df7b7

9 files changed

Lines changed: 437 additions & 11 deletions

File tree

__mocks__/external/llama.rn.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ export const loadLlamaModelInfo = jest.fn();
5050

5151
export const initLlama = jest.fn();
5252

53+
export const getBackendDevicesInfo = jest.fn().mockResolvedValue([]);
54+
5355
export const toggleNativeLog = jest.fn().mockResolvedValue(undefined);
5456

5557
export const addNativeLogListener = jest.fn().mockReturnValue({

src/__automation__/screens/__tests__/BenchmarkRunnerScreen.test.tsx

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import {Platform} from 'react-native';
23
import {act} from 'react-test-renderer';
34

45
import {fireEvent, render, waitFor} from '../../../../jest/test-utils';
@@ -495,15 +496,68 @@ describe('BenchmarkRunnerScreen', () => {
495496
stubHexagonLogs();
496497
getDeviceOptions.mockResolvedValueOnce([
497498
{id: 'cpu', label: 'CPU', devices: ['CPU']},
498-
{id: 'hexagon', label: 'Hexagon', devices: ['HTP*']},
499+
{id: 'hexagon', label: 'Hexagon', devices: ['HTP0']},
499500
]);
500501
const cfg: BenchConfig = {...VALID_CONFIG, backends: ['hexagon']};
501502
await runMatrix(cfg, setStatus, setLastCell);
502503
const [paramsArg] = (initLlama as jest.Mock).mock.calls[0];
503-
expect(paramsArg.devices).toEqual(['HTP*']);
504+
expect(paramsArg.devices).toEqual(['HTP0']);
504505
expect(paramsArg.n_gpu_layers).toBe(99);
505506
});
506507

508+
it.each([['HTP0', 'HTP1', 'HTP2', 'HTP3', 'HTP4', 'HTP5'], ['HTP3']])(
509+
'uses real discovery for Hexagon sessions %j',
510+
async (...names) => {
511+
const originalOS = Platform.OS;
512+
Platform.OS = 'android';
513+
const {getBackendDevicesInfo} = require('llama.rn');
514+
const actualSelection = jest.requireActual(
515+
'../../../utils/deviceSelection',
516+
);
517+
getBackendDevicesInfo.mockResolvedValue(
518+
names.map(deviceName => ({
519+
deviceName,
520+
type: 'accel',
521+
backend: 'HTP',
522+
})),
523+
);
524+
getDeviceOptions.mockImplementationOnce(
525+
actualSelection.getDeviceOptions,
526+
);
527+
stubHexagonLogs();
528+
const saved = JSON.parse(JSON.stringify(modelStore.contextInitParams));
529+
try {
530+
const cfg: BenchConfig = {
531+
...VALID_CONFIG,
532+
backends: ['hexagon'],
533+
settings_axes: [{name: 'flash_attn_type', values: ['on']}],
534+
};
535+
await runMatrix(cfg, setStatus, setLastCell);
536+
const lastWrite =
537+
RNFS.writeFile.mock.calls[RNFS.writeFile.mock.calls.length - 1];
538+
const report = JSON.parse(lastWrite[1]);
539+
expect(initLlama).toHaveBeenCalledTimes(1);
540+
expect(initLlama.mock.calls[0][0]).toMatchObject({
541+
devices: [names[0]],
542+
n_gpu_layers: 99,
543+
flash_attn_type: 'on',
544+
});
545+
expect(report.runs[0]).toMatchObject({
546+
status: 'ok',
547+
effective_init_params: {
548+
devices: [names[0]],
549+
n_gpu_layers: 99,
550+
flash_attn_type: 'on',
551+
},
552+
});
553+
expect(modelStore.contextInitParams).toEqual(saved);
554+
} finally {
555+
Platform.OS = originalOS;
556+
getBackendDevicesInfo.mockReset().mockResolvedValue([]);
557+
}
558+
},
559+
);
560+
507561
// -------------------------------------------------------------------------
508562
// Per-cell context release (sole release site is the per-cell finally)
509563
// -------------------------------------------------------------------------
@@ -1326,10 +1380,10 @@ describe('BenchmarkRunnerScreen', () => {
13261380
filePath: '/mock/path/m.gguf',
13271381
base: DEFAULT_BENCH_BASE_PARAMS,
13281382
overrides: {} as any,
1329-
devices: ['HTP*'],
1383+
devices: ['HTP0'],
13301384
n_gpu_layers: 99,
13311385
});
1332-
expect(params.devices).toEqual(['HTP*']);
1386+
expect(params.devices).toEqual(['HTP0']);
13331387
expect(params.n_gpu_layers).toBe(99);
13341388
expect(params.model).toBe('/mock/path/m.gguf');
13351389
});

src/hooks/__tests__/useTheme.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import {observer} from 'mobx-react';
66

77
jest.unmock('../useTheme');
88
jest.unmock('../../store');
9+
10+
jest.mock('../../services/deviceRules/rules', () => ({
11+
fetchRules: jest.fn().mockResolvedValue(null),
12+
}));
913
import {useTheme} from '../useTheme';
1014

1115
import {uiStore} from '../../store';

src/screens/SettingsScreen/__tests__/SettingsScreen.test.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import {getBackendDevicesInfo} from 'llama.rn';
23
import {Platform, Keyboard} from 'react-native';
34
import {runInAction} from 'mobx';
45

@@ -33,6 +34,47 @@ describe('SettingsScreen', () => {
3334
jest.useRealTimers();
3435
});
3536

37+
it.each(['HTP*', 'HTP99'])(
38+
'shows saved %s as Hexagon and writes its discovered name when selected',
39+
async savedName => {
40+
const originalOS = Platform.OS;
41+
const originalSettings = {...modelStore.contextInitParams};
42+
Platform.OS = 'android';
43+
(getBackendDevicesInfo as jest.Mock).mockResolvedValue([
44+
{deviceName: 'HTP3', type: 'accel', backend: 'HTP'},
45+
{deviceName: 'HTP4', type: 'accel', backend: 'HTP'},
46+
]);
47+
runInAction(() => {
48+
modelStore.contextInitParams.devices = [savedName];
49+
modelStore.contextInitParams.flash_attn_type = 'off';
50+
});
51+
try {
52+
const {getByTestId} = render(<SettingsScreen />, {
53+
withSafeArea: true,
54+
withNavigation: true,
55+
});
56+
await waitFor(() => {
57+
expect(
58+
getByTestId('device-option-hexagon').props.accessibilityState
59+
.checked,
60+
).toBe(true);
61+
});
62+
act(() => {
63+
fireEvent.press(getByTestId('device-option-cpu'));
64+
fireEvent.press(getByTestId('device-option-hexagon'));
65+
});
66+
expect(modelStore.setDevices).toHaveBeenLastCalledWith(['HTP3']);
67+
expect(modelStore.setFlashAttnType).not.toHaveBeenCalled();
68+
} finally {
69+
Platform.OS = originalOS;
70+
runInAction(() => {
71+
modelStore.contextInitParams = originalSettings;
72+
});
73+
(getBackendDevicesInfo as jest.Mock).mockReset().mockResolvedValue([]);
74+
}
75+
},
76+
);
77+
3678
it('renders settings screen correctly', async () => {
3779
const {getByText, getByDisplayValue} = render(<SettingsScreen />, {
3880
withSafeArea: true,

src/store/ModelStore.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
unpairedDraftCandidate,
3232
} from './draftResolution';
3333
import {checkGpuSupport} from '../utils/deviceCapabilities';
34+
import {resolveDeviceSelection} from '../utils/deviceSelection';
3435
import {
3536
deepMerge,
3637
getSHA256Hash,
@@ -573,7 +574,7 @@ class ModelStore {
573574
cache_type_k: this.contextInitParams.cache_type_k,
574575
cache_type_v: this.contextInitParams.cache_type_v,
575576
n_gpu_layers: this.contextInitParams.n_gpu_layers ?? 99,
576-
devices: this.contextInitParams.devices, // NEW
577+
devices: this.contextInitParams.devices?.slice(),
577578
kv_unified: this.contextInitParams.kv_unified ?? true, // NEW (default true!)
578579
n_parallel: this.contextInitParams.n_parallel ?? 1, // NEW (1 for blocking mode only)
579580
use_mlock: this.contextInitParams.use_mlock,
@@ -617,6 +618,14 @@ class ModelStore {
617618
}
618619
}
619620

621+
Object.assign(
622+
params,
623+
await resolveDeviceSelection({
624+
devices: params.devices,
625+
n_gpu_layers: params.n_gpu_layers,
626+
}),
627+
);
628+
620629
// Remove undefined values from the params object
621630
return Object.fromEntries(
622631
Object.entries(params).filter(([_, value]) => value !== undefined),

src/store/__tests__/ModelStore.registerOnboardingPalModel.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
jest.unmock('../../store');
22

3+
jest.mock('../../services/deviceRules/rules', () => ({
4+
fetchRules: jest.fn().mockResolvedValue(null),
5+
}));
6+
37
import {downloadManager} from '../../services/downloads';
48
import {modelStore} from '..';
59
import {ModelOrigin} from '../../utils/types';

0 commit comments

Comments
 (0)