diff --git a/__tests__/rntl/screens/ModelSettingsScreen.test.tsx b/__tests__/rntl/screens/ModelSettingsScreen.test.tsx index de00d047..f2c1131b 100644 --- a/__tests__/rntl/screens/ModelSettingsScreen.test.tsx +++ b/__tests__/rntl/screens/ModelSettingsScreen.test.tsx @@ -433,10 +433,10 @@ describe('ModelSettingsScreen', () => { // Performance Settings // ============================================================================ describe('performance settings', () => { - it('shows CPU Threads slider label and auto value when nThreads uses the auto sentinel', () => { - const { getByText } = renderWithSections('text'); + it('shows CPU Threads slider label and auto value when nThreads uses the auto sentinel', async () => { + const { getByText, findByText } = renderWithSections('text'); expect(getByText('CPU Threads')).toBeTruthy(); - expect(getByText('Auto')).toBeTruthy(); + await findByText(/^Auto \(\d+\)$/); }); it('shows Batch Size slider label and default value', () => { @@ -832,12 +832,12 @@ describe('ModelSettingsScreen', () => { }, }); - const { getByText } = renderWithSections('image', 'text'); + const { getByText, getAllByText } = renderWithSections('image', 'text'); // Verify fallback values are used expect(getByText('0.70')).toBeTruthy(); // temperature || 0.7 expect(getByText('0.90')).toBeTruthy(); // topP || 0.9 expect(getByText('1.10')).toBeTruthy(); // repeatPenalty || 1.1 - expect(getByText('6')).toBeTruthy(); // undefined still falls back to 6 + expect(getAllByText('1').length).toBeGreaterThan(0); // undefined falls back to cpuThreadsSliderValue (1) expect(getByText('8')).toBeTruthy(); // imageSteps || 8 expect(getByText('7.5')).toBeTruthy(); // imageGuidanceScale || 7.5 }); diff --git a/__tests__/unit/hooks/useTextGenerationAdvanced.test.ts b/__tests__/unit/hooks/useTextGenerationAdvanced.test.ts index 4c4e0e54..332f4b80 100644 --- a/__tests__/unit/hooks/useTextGenerationAdvanced.test.ts +++ b/__tests__/unit/hooks/useTextGenerationAdvanced.test.ts @@ -21,14 +21,16 @@ describe('useTextGenerationAdvanced', () => { expect(result.current.displayCacheType).toBe('f16'); }); - it('shows Auto for cpu threads when nThreads uses the auto sentinel', () => { + it('shows Auto (N) for cpu threads when nThreads uses the auto sentinel', async () => { act(() => { useAppStore.getState().updateSettings({ nThreads: 0 }); }); const { result } = renderHook(() => useTextGenerationAdvanced()); - expect(result.current.cpuThreadsDisplayValue).toBe('Auto'); + await act(async () => {}); + + expect(result.current.cpuThreadsDisplayValue).toMatch(/^Auto \(\d+\)$/); expect(result.current.cpuThreadsSliderValue).toBe(1); }); }); diff --git a/src/hooks/useTextGenerationAdvanced.ts b/src/hooks/useTextGenerationAdvanced.ts index 173acbda..c68887e1 100644 --- a/src/hooks/useTextGenerationAdvanced.ts +++ b/src/hooks/useTextGenerationAdvanced.ts @@ -1,6 +1,8 @@ +import { useState, useEffect } from 'react'; import { Platform } from 'react-native'; import { useAppStore } from '../stores'; import { CacheType, INFERENCE_BACKENDS } from '../types'; +import { hardwareService } from '../services/hardware'; /** Feature flag: Set to true to enable HTP/Hexagon NPU support. Currently disabled. */ const HTP_ENABLED = false; @@ -31,8 +33,17 @@ export function useTextGenerationAdvanced() { // OpenCL and HTP force f16 in the native loader, so lock the UI to match. const cacheDisabled = gpuForcesF16; const displayCacheType = cacheDisabled ? 'f16' : currentCacheType; + const [resolvedThreadCount, setResolvedThreadCount] = useState(null); + + useEffect(() => { + if (settings?.nThreads !== 0) return; + hardwareService.getRecommendedThreadCount().then(setResolvedThreadCount); + }, [settings?.nThreads]); + const cpuThreadsSliderValue = settings?.nThreads && settings.nThreads > 0 ? settings.nThreads : 1; - const cpuThreadsDisplayValue = settings?.nThreads === 0 ? 'Auto' : String(settings?.nThreads ?? 6); + const cpuThreadsDisplayValue = settings?.nThreads === 0 + ? (resolvedThreadCount != null ? `Auto (${resolvedThreadCount})` : 'Auto') + : String(cpuThreadsSliderValue); const handleFlashAttnToggle = (flashAttn: boolean) => { if (!flashAttn && isQuantizedCache) { diff --git a/src/stores/appStore.ts b/src/stores/appStore.ts index 58a8b901..e7339c58 100644 --- a/src/stores/appStore.ts +++ b/src/stores/appStore.ts @@ -178,11 +178,6 @@ function migratePersistedState(persistedState: any, currentState: AppState): App if (merged.checklistDismissed && merged.onboardingChecklist && !Object.values(merged.onboardingChecklist).every(Boolean)) merged.checklistDismissed = false; migrateEnabledTools(merged); - // nThreads: 4 was the old hard-coded default (before the 0 = auto sentinel). - // Migrate it to 0 so users get hardware-appropriate thread counts on next load. - if (merged.settings?.nThreads === 4) { - merged.settings = { ...merged.settings, nThreads: 0 }; - } return merged as AppState; }