studio: refresh GPU index space on backend swap; self-heal cold-cache pin stamp
Addresses two Codex P2s on the Vulkan GPU-pin machinery: - use-gpu-info caches /api/system forever, so after an in-session llama.cpp backend swap (update/rebuild) currentGpuIndexKind() kept reporting the pre-swap index space until a full reload, stranding physical pins on a now-Vulkan backend (or dropping Vulkan pins after a swap back). Add invalidateGpuInfoCache() and call it on llama.cpp update success, beside the existing refreshHardwareInfo(). - A /status hydration (or eager picker edit) that beats the first /api/system fetch stamped a live pick's index kind null; currentRuntimePerModelConfig then serialized it as an unstamped (legacy physical) pick, dropping a real Vulkan selection on the next reconcile. Add warmSelectedGpuIdsKind() to warm the cache and backfill the kind at the two cold-capable stamp sites (setSelectedGpuIds, the /status hydration).
This commit is contained in:
parent
4e7c0c2a1b
commit
81333e166d
4 changed files with 53 additions and 2 deletions
|
|
@ -15,6 +15,7 @@ import {
|
|||
loadedGpuMemoryFields,
|
||||
resolveToolsEnabledOnLoad,
|
||||
useChatRuntimeStore,
|
||||
warmSelectedGpuIdsKind,
|
||||
} from "../stores/chat-runtime-store";
|
||||
import {
|
||||
type InferenceStatusResponse,
|
||||
|
|
@ -338,6 +339,11 @@ export function applyActiveModelStatusToStore(
|
|||
}),
|
||||
});
|
||||
|
||||
// A /status hydration can beat the first /api/system fetch, stamping the
|
||||
// loaded pick's index kind null; backfill it once the GPU cache warms so a
|
||||
// subsequent rollback snapshot doesn't serialize it as a physical default.
|
||||
warmSelectedGpuIdsKind();
|
||||
|
||||
ensureActiveModelInStoreList(status, checkpointId);
|
||||
|
||||
if (
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
type GpuIndexKind,
|
||||
cachedPinnableGpuIndices,
|
||||
currentGpuIndexKind,
|
||||
ensureGpuDeviceCache,
|
||||
} from "@/hooks/use-gpu-info";
|
||||
import { toast } from "@/lib/toast";
|
||||
import { create } from "zustand";
|
||||
|
|
@ -701,6 +702,27 @@ export function loadedGpuMemoryFields(resp: {
|
|||
};
|
||||
}
|
||||
|
||||
/** Self-heal a cold-cache GPU-kind stamp. A live pick stamped while the
|
||||
* /api/system GPU cache was still cold (a /status hydration that beat the first
|
||||
* fetch, or an eager picker edit) lands with selectedGpuIdsKind === null, which
|
||||
* imperative snapshots (currentRuntimePerModelConfig) then serialize as an
|
||||
* unstamped -- i.e. legacy physical -- pick, dropping a real Vulkan selection on
|
||||
* the next reconcile. Warm the cache and stamp the now-known kind so any
|
||||
* snapshot taken after (a model-switch rollback) carries the true index space.
|
||||
* No-op when the pick is already stamped or cleared. */
|
||||
export function warmSelectedGpuIdsKind(): void {
|
||||
const { selectedGpuIds, selectedGpuIdsKind } = useChatRuntimeStore.getState();
|
||||
if (selectedGpuIds == null || selectedGpuIdsKind != null) return;
|
||||
void ensureGpuDeviceCache().then(() => {
|
||||
// Re-read: a swap/clear during the await gap must not resurrect a stamp for a
|
||||
// pick that is gone, and a pick already stamped by its own action is left be.
|
||||
const s = useChatRuntimeStore.getState();
|
||||
if (s.selectedGpuIds == null || s.selectedGpuIdsKind != null) return;
|
||||
const kind = currentGpuIndexKind();
|
||||
if (kind != null) useChatRuntimeStore.setState({ selectedGpuIdsKind: kind });
|
||||
});
|
||||
}
|
||||
|
||||
/** A pick is a GGUF: HF variant, native file, or a direct local .gguf. */
|
||||
export function hasGgufSource(x: {
|
||||
ggufVariant?: string;
|
||||
|
|
@ -1937,14 +1959,18 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set, get) => ({
|
|||
setGpuLayers: (gpuLayers) => set({ gpuLayers }),
|
||||
setNCpuMoe: (nCpuMoe) => set({ nCpuMoe }),
|
||||
setSplitRatio: (splitRatio) => set({ splitRatio }),
|
||||
setSelectedGpuIds: (selectedGpuIds) =>
|
||||
setSelectedGpuIds: (selectedGpuIds) => {
|
||||
set({
|
||||
selectedGpuIds,
|
||||
// A live picker selection is by definition in the current backend's index
|
||||
// space; stamp it so a later reconcile keeps it (and drops it only after
|
||||
// an actual backend swap). null pick carries no space.
|
||||
selectedGpuIdsKind: selectedGpuIds == null ? null : currentGpuIndexKind(),
|
||||
}),
|
||||
});
|
||||
// If the cache was cold the stamp above is null; warm and backfill it so a
|
||||
// later rollback snapshot serializes the true space, not a physical default.
|
||||
warmSelectedGpuIdsKind();
|
||||
},
|
||||
setExpandQuantizations: (expandQuantizations) => {
|
||||
saveBool(CHAT_EXPAND_QUANTIZATIONS_KEY, expandQuantizations);
|
||||
set({ expandQuantizations });
|
||||
|
|
|
|||
|
|
@ -77,6 +77,19 @@ export function currentGpuIndexKind(): GpuIndexKind | null {
|
|||
return systemGpuIndexKind(cachedSystem);
|
||||
}
|
||||
|
||||
/** Drop the cached /api/system snapshot so the next read refetches it. Call
|
||||
* after an in-session llama.cpp backend swap (update / MTP rebuild): the swap
|
||||
* flips the gpu_ids index space (physical CUDA/ROCm ids <-> ggml Vulkan
|
||||
* ordinals), and because this module caches the first fetch forever,
|
||||
* currentGpuIndexKind() and the load-boundary reconcile would otherwise keep
|
||||
* reporting the pre-swap space -- stranding stale physical pins on a now-Vulkan
|
||||
* backend, or dropping valid Vulkan pins after a swap back -- until a full page
|
||||
* reload. Invalidating here lets the next ensureGpuDeviceCache() re-probe. */
|
||||
export function invalidateGpuInfoCache(): void {
|
||||
cachedSystem = null;
|
||||
systemPromise = null;
|
||||
}
|
||||
|
||||
async function fetchSystemOnce(): Promise<SystemInfoResponse | null> {
|
||||
if (cachedSystem) return cachedSystem;
|
||||
if (systemPromise) return systemPromise;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import { authFetch, getAuthToken } from "@/features/auth";
|
||||
import { refreshHardwareInfo } from "@/hooks/use-hardware-info";
|
||||
import { invalidateGpuInfoCache } from "@/hooks/use-gpu-info";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
// Initial check plus hourly reminders until dismissed or applied.
|
||||
|
|
@ -212,6 +213,11 @@ export function useLlamaUpdateCheck({
|
|||
if (s.job.state === "success") {
|
||||
setVisible(false);
|
||||
void refreshHardwareInfo();
|
||||
// A llama.cpp update can swap the backend (e.g. ROCm -> Vulkan), which
|
||||
// flips the gpu_ids index space. Drop the separate /api/system GPU
|
||||
// cache too so currentGpuIndexKind()/the next reconcile re-probe the
|
||||
// new space instead of stranding pins stamped under the old one.
|
||||
invalidateGpuInfoCache();
|
||||
// The update unloads the running model server-side, so the chat
|
||||
// runtime still points at a model that now 400s on send. Let the
|
||||
// consumer drop the selector to "select model" instead of waiting for
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue