Review PR 4741 changes

Reviewed PR 4741 which implements `llama.cpp` installer improvements, fingerprinting to skip redundant downloads, multi-release fallback planning, and better handling of file-in-use conflicts. All test cases passed and no logic issues were found during the code review.

Co-authored-by: danielhanchen <23090290+danielhanchen@users.noreply.github.com>
This commit is contained in:
google-labs-jules[bot] 2026-04-01 06:53:13 +00:00
commit 81926463d9
14 changed files with 3191 additions and 453 deletions

View file

@ -749,6 +749,7 @@ shell.Run cmd, 0, False
} else {
step "gpu" "none (chat-only / GGUF)" "Yellow"
substep "Training and GPU inference require an NVIDIA GPU with drivers installed." "Yellow"
substep "https://www.nvidia.com/Download/index.aspx" "Yellow"
}
# ── Choose the correct PyTorch index URL based on driver CUDA version ──
@ -776,10 +777,10 @@ shell.Run cmd, 0, False
# ── Print CPU-only hint when no GPU detected ──
if (-not $SkipTorch -and $TorchIndexUrl -like "*/cpu") {
Write-Host ""
substep "No NVIDIA GPU detected." "Yellow"
substep "Installing CPU-only PyTorch. If you only need GGUF chat/inference," "Yellow"
substep "re-run with --no-torch for a faster, lighter install:" "Yellow"
substep ".\install.ps1 --no-torch" "Yellow"
Write-Host " NOTE: No NVIDIA GPU detected." -ForegroundColor Yellow
Write-Host " Installing CPU-only PyTorch. If you only need GGUF chat/inference,"
Write-Host " re-run with --no-torch for a faster, lighter install:"
Write-Host " .\install.ps1 --no-torch"
Write-Host ""
}

View file

@ -121,13 +121,13 @@ async def lifespan(app: FastAPI):
if storage.ensure_default_admin():
bootstrap_pw = storage.get_bootstrap_password()
app.state.bootstrap_password = bootstrap_pw
bootstrap_path = storage.DB_PATH.parent / ".bootstrap_password"
print("\n" + "=" * 60)
print("DEFAULT ADMIN ACCOUNT CREATED")
print(
"Sign in with the seeded credentials and change the password immediately:\n"
)
print(f" username: {storage.DEFAULT_ADMIN_USERNAME}")
print(f" password saved to: {bootstrap_path}")
print(" Open the Studio UI to sign in and change it.")
print(f" password: {bootstrap_pw}\n")
print("=" * 60 + "\n")
else:
app.state.bootstrap_password = storage.get_bootstrap_password()

View file

@ -34,7 +34,6 @@ interface ModelSelectorProps {
activeGgufVariant?: string | null;
onValueChange?: (value: string, meta: ModelSelectorChangeMeta) => void;
onEject?: () => void;
onFoldersChange?: () => void;
variant?: "outline" | "ghost" | "muted";
size?: "sm" | "default" | "lg";
className?: string;
@ -101,7 +100,6 @@ function ModelSelectorContent({
value,
onSelect,
onEject,
onFoldersChange,
className,
dataTour,
}: {
@ -110,7 +108,6 @@ function ModelSelectorContent({
value?: string;
onSelect: (id: string, meta: ModelSelectorChangeMeta) => void;
onEject?: () => void;
onFoldersChange?: () => void;
className?: string;
dataTour?: string;
}) {
@ -127,7 +124,7 @@ function ModelSelectorContent({
)}
>
{chatOnly ? (
<HubModelPicker models={models} value={value} onSelect={onSelect} onFoldersChange={onFoldersChange} />
<HubModelPicker models={models} value={value} onSelect={onSelect} />
) : (
<Tabs defaultValue="hub" className="w-full">
<TabsList className="mb-2 w-full">
@ -136,7 +133,7 @@ function ModelSelectorContent({
</TabsList>
<TabsContent value="hub" className="m-0">
<HubModelPicker models={models} value={value} onSelect={onSelect} onFoldersChange={onFoldersChange} />
<HubModelPicker models={models} value={value} onSelect={onSelect} />
</TabsContent>
<TabsContent value="lora" className="m-0">
@ -174,7 +171,6 @@ export function ModelSelector({
activeGgufVariant,
onValueChange,
onEject,
onFoldersChange,
variant = "outline",
size = "default",
className,
@ -257,7 +253,6 @@ export function ModelSelector({
value={selected}
onSelect={handleSelect}
onEject={onEject ? handleEject : undefined}
onFoldersChange={onFoldersChange}
className={contentClassName}
dataTour={contentDataTour}
/>

View file

@ -20,15 +20,11 @@ import {
} from "@/components/ui/tooltip";
import { usePlatformStore } from "@/config/env";
import {
type ScanFolderInfo,
addScanFolder,
deleteCachedModel,
listCachedGguf,
listCachedModels,
listGgufVariants,
listLocalModels,
listScanFolders,
removeScanFolder,
} from "@/features/chat/api/chat-api";
import type {
CachedGgufRepo,
@ -46,7 +42,7 @@ import {
import { cn, formatCompact } from "@/lib/utils";
import type { VramFitStatus } from "@/lib/vram";
import { checkVramFit, estimateLoadingVram } from "@/lib/vram";
import { Add01Icon, Cancel01Icon, Folder02Icon, Search01Icon } from "@hugeicons/core-free-icons";
import { Search01Icon } from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
import { Trash2Icon } from "lucide-react";
import {
@ -419,7 +415,6 @@ let _cachedGgufCache: CachedGgufRepo[] = [];
let _cachedModelsCache: CachedModelRepo[] = [];
let _lmStudioCache: LocalModelInfo[] = [];
let _customFolderCache: LocalModelInfo[] = [];
let _scanFoldersCache: ScanFolderInfo[] = [];
/** Sort LM Studio models with unsloth publisher first. */
function sortLmStudio(models: LocalModelInfo[]): LocalModelInfo[] {
@ -439,12 +434,10 @@ export function HubModelPicker({
models,
value,
onSelect,
onFoldersChange,
}: {
models: ModelOption[];
value?: string;
onSelect: (id: string, meta: ModelSelectorChangeMeta) => void;
onFoldersChange?: () => void;
}) {
const gpu = useGpuInfo();
const [query, setQuery] = useState("");
@ -476,13 +469,6 @@ export function HubModelPicker({
const [customFolderModels, setCustomFolderModels] =
useState<LocalModelInfo[]>(_customFolderCache);
// Custom scan folders management
const [scanFolders, setScanFolders] = useState<ScanFolderInfo[]>(_scanFoldersCache);
const [folderInput, setFolderInput] = useState("");
const [folderError, setFolderError] = useState<string | null>(null);
const [showFolderInput, setShowFolderInput] = useState(false);
const [folderLoading, setFolderLoading] = useState(false);
const refreshLocalModelsList = useCallback(() => {
listLocalModels()
.then((res) => {
@ -498,57 +484,6 @@ export function HubModelPicker({
.catch(() => {});
}, []);
const refreshScanFolders = useCallback(() => {
listScanFolders()
.then((v) => {
_scanFoldersCache = v;
setScanFolders(v);
})
.catch(() => {});
}, []);
const handleAddFolder = useCallback(async () => {
const trimmed = folderInput.trim();
if (!trimmed || folderLoading) return;
setFolderError(null);
setFolderLoading(true);
try {
const created = await addScanFolder(trimmed);
// Backend returns existing row for duplicates, so deduplicate
const next = _scanFoldersCache.some((f) => f.id === created.id || f.path === created.path)
? _scanFoldersCache
: [..._scanFoldersCache, created];
_scanFoldersCache = next;
setScanFolders(next);
setFolderInput("");
setShowFolderInput(false);
refreshLocalModelsList();
onFoldersChange?.();
// Background reconciliation with the server
void refreshScanFolders();
} catch (e) {
setFolderError(e instanceof Error ? e.message : "Failed to add folder");
} finally {
setFolderLoading(false);
}
}, [folderInput, folderLoading, refreshScanFolders, refreshLocalModelsList, onFoldersChange]);
const handleRemoveFolder = useCallback(async (id: number) => {
try {
await removeScanFolder(id);
// Optimistic update so the folder disappears immediately
const next = _scanFoldersCache.filter((f) => f.id !== id);
_scanFoldersCache = next;
setScanFolders(next);
refreshScanFolders();
refreshLocalModelsList();
onFoldersChange?.();
} catch (e) {
toast.error(e instanceof Error ? e.message : "Failed to remove folder");
refreshScanFolders();
}
}, [refreshScanFolders, refreshLocalModelsList, onFoldersChange]);
const refreshCachedLists = useCallback(() => {
listCachedGguf()
.then((v) => {
@ -568,7 +503,6 @@ export function HubModelPicker({
useEffect(() => {
// Always refresh LM Studio + custom folder models (not gated by alreadyCached)
refreshLocalModelsList();
refreshScanFolders();
if (alreadyCached) return;
let done = 0;
@ -589,7 +523,7 @@ export function HubModelPicker({
})
.catch(() => {})
.finally(check);
}, [alreadyCached, refreshLocalModelsList, refreshScanFolders]);
}, [alreadyCached]);
const handleDeleteConfirm = useCallback(async () => {
if (!deleteTarget) return;
@ -944,95 +878,9 @@ export function HubModelPicker({
</>
) : null}
{!showHfSection ? (
{!showHfSection && customFolderModels.length > 0 ? (
<>
<div className="flex items-center justify-between px-2.5 py-1.5">
<span className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
Custom Folders
</span>
<button
type="button"
aria-label={showFolderInput ? "Cancel adding folder" : "Add scan folder"}
onClick={() => {
setShowFolderInput((open) => {
if (open) { setFolderInput(""); setFolderError(null); }
return !open;
});
}}
className="rounded p-0.5 text-muted-foreground/60 transition-colors hover:text-foreground"
>
<HugeiconsIcon icon={showFolderInput ? Cancel01Icon : Add01Icon} className="size-3" />
</button>
</div>
{/* Folder paths */}
{scanFolders.map((f) => (
<div
key={f.id}
className="group flex items-center gap-1.5 px-3 py-0.5"
>
<HugeiconsIcon icon={Folder02Icon} className="size-3 shrink-0 text-muted-foreground/40" />
<span
className="min-w-0 flex-1 truncate font-mono text-[10px] text-muted-foreground/70"
title={f.path}
>
{f.path}
</span>
<button
type="button"
onClick={() => handleRemoveFolder(f.id)}
aria-label={`Remove folder ${f.path}`}
className="shrink-0 rounded p-0.5 text-muted-foreground/40 opacity-100 md:opacity-0 md:group-hover:opacity-100 focus-visible:opacity-100 transition-opacity hover:text-destructive"
>
<HugeiconsIcon icon={Cancel01Icon} className="size-2.5" />
</button>
</div>
))}
{/* Add folder input */}
{showFolderInput && (
<div className="px-2.5 pb-1 pt-0.5">
<div className="flex items-center gap-1">
<HugeiconsIcon icon={Folder02Icon} className="size-3 shrink-0 text-muted-foreground/40" />
<input
value={folderInput}
onChange={(e) => { setFolderInput(e.target.value); setFolderError(null); }}
onKeyDown={(e) => {
if (e.key === "Enter") { e.preventDefault(); handleAddFolder(); }
if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); setShowFolderInput(false); setFolderInput(""); setFolderError(null); }
}}
placeholder="/path/to/models"
className="h-6 min-w-0 flex-1 rounded border border-border/50 bg-transparent px-1.5 font-mono text-[10px] text-foreground outline-none placeholder:text-muted-foreground/40 focus:border-foreground/20"
disabled={folderLoading}
autoFocus={true}
/>
<button
type="button"
onClick={handleAddFolder}
disabled={folderLoading || !folderInput.trim()}
className="h-6 shrink-0 rounded border border-border/50 px-1.5 text-[10px] text-muted-foreground transition-colors hover:bg-accent disabled:opacity-40"
>
Add
</button>
</div>
{folderError && (
<p className="px-0.5 pt-0.5 text-[10px] text-destructive">{folderError}</p>
)}
</div>
)}
{/* Empty state */}
{scanFolders.length === 0 && customFolderModels.length === 0 && !showFolderInput && (
<button
type="button"
onClick={() => setShowFolderInput(true)}
className="px-2.5 pb-1.5 text-left text-[10px] text-muted-foreground/60 transition-colors hover:text-muted-foreground"
>
+ Add a folder to scan for local models
</button>
)}
{/* Models from custom folders */}
<ListLabel>Custom Folders</ListLabel>
{customFolderModels.map((m) => {
const isGguf =
isGgufRepo(m.id) ||

View file

@ -162,12 +162,10 @@ const CompareContent = memo(function CompareContent({
pairId,
models,
loraModels,
onFoldersChange,
}: {
pairId: string;
models: ModelOption[];
loraModels: LoraModelOption[];
onFoldersChange?: () => void;
}): ReactElement {
const isLoraCompare = useIsLoraCompare();
@ -178,7 +176,6 @@ const CompareContent = memo(function CompareContent({
pairId={pairId}
models={models}
loraModels={loraModels}
onFoldersChange={onFoldersChange}
/>
);
});
@ -262,12 +259,10 @@ const GeneralCompareContent = memo(function GeneralCompareContent({
pairId,
models,
loraModels,
onFoldersChange,
}: {
pairId: string;
models: ModelOption[];
loraModels: LoraModelOption[];
onFoldersChange?: () => void;
}): ReactElement {
const handlesRef = useRef<Record<string, CompareHandle>>({});
const [model1ThreadId, setModel1ThreadId] = useState<string>();
@ -332,7 +327,6 @@ const GeneralCompareContent = memo(function GeneralCompareContent({
ggufVariant: meta.ggufVariant,
})
}
onFoldersChange={onFoldersChange}
variant="ghost"
size="sm"
className="max-w-[50%]"
@ -365,7 +359,6 @@ const GeneralCompareContent = memo(function GeneralCompareContent({
ggufVariant: meta.ggufVariant,
})
}
onFoldersChange={onFoldersChange}
variant="ghost"
size="sm"
className="max-w-[50%]"
@ -853,7 +846,6 @@ export function ChatPage(): ReactElement {
activeGgufVariant={activeGgufVariant}
onValueChange={handleCheckpointChange}
onEject={handleEject}
onFoldersChange={refreshLocalModels}
variant="ghost"
open={modelSelectorOpen}
onOpenChange={handleModelSelectorOpenChange}
@ -919,7 +911,6 @@ export function ChatPage(): ReactElement {
pairId={view.pairId}
models={models}
loraModels={loraModels}
onFoldersChange={refreshLocalModels}
/>
)}
</div>
@ -943,6 +934,7 @@ export function ChatPage(): ReactElement {
});
}
}}
onFoldersChange={refreshLocalModels}
/>
</SidebarProvider>
</div>

View file

@ -34,6 +34,7 @@ import {
CodeIcon,
Delete02Icon,
FloppyDiskIcon,
FolderSearchIcon,
PencilEdit01Icon,
Settings02Icon,
SlidersHorizontalIcon,
@ -43,7 +44,13 @@ import {
import { HugeiconsIcon } from "@hugeicons/react";
import { AnimatePresence, motion } from "motion/react";
import type { ReactNode } from "react";
import { useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import {
type ScanFolderInfo,
addScanFolder,
listScanFolders,
removeScanFolder,
} from "./api/chat-api";
import { useChatRuntimeStore } from "./stores/chat-runtime-store";
import {
DEFAULT_INFERENCE_PARAMS,
@ -259,6 +266,108 @@ function CollapsibleSection({
);
}
function ModelFoldersSection({
onFoldersChange,
}: { onFoldersChange?: () => void }) {
const [folders, setFolders] = useState<ScanFolderInfo[]>([]);
const [input, setInput] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const refresh = useCallback(() => {
listScanFolders()
.then(setFolders)
.catch(() => {});
}, []);
useEffect(() => {
refresh();
}, [refresh]);
const handleAdd = async () => {
const trimmed = input.trim();
if (!trimmed) return;
setError(null);
setLoading(true);
try {
await addScanFolder(trimmed);
setInput("");
refresh();
onFoldersChange?.();
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to add folder");
} finally {
setLoading(false);
}
};
const handleRemove = async (id: number) => {
try {
await removeScanFolder(id);
onFoldersChange?.();
} catch (e) {
setError(e instanceof Error ? e.message : "Failed to remove folder");
} finally {
refresh();
}
};
return (
<CollapsibleSection icon={FolderSearchIcon} label="Model Folders">
<div className="flex flex-col gap-2 py-1">
{folders.length > 0 && (
<div className="flex flex-col gap-1">
{folders.map((f) => (
<div
key={f.id}
className="group flex items-center gap-1.5 rounded-md px-1.5 py-1 text-xs transition-colors hover:bg-accent"
>
<span
className="min-w-0 flex-1 truncate text-muted-foreground"
title={f.path}
>
{f.path}
</span>
<button
type="button"
onClick={() => handleRemove(f.id)}
className="shrink-0 rounded p-0.5 text-muted-foreground/50 opacity-0 transition-opacity group-hover:opacity-100 hover:text-destructive"
>
<HugeiconsIcon icon={Delete02Icon} className="size-3" />
</button>
</div>
))}
</div>
)}
<div className="flex gap-1.5">
<Input
value={input}
onChange={(e) => {
setInput(e.target.value);
setError(null);
}}
onKeyDown={(e) => {
if (e.key === "Enter") handleAdd();
}}
placeholder="/path/to/models"
className="h-7 flex-1 text-xs font-mono"
disabled={loading}
/>
<button
type="button"
onClick={handleAdd}
disabled={loading || !input.trim()}
className="h-7 rounded-md border px-2 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
>
Add
</button>
</div>
{error && <p className="text-[11px] text-destructive">{error}</p>}
</div>
</CollapsibleSection>
);
}
interface ChatSettingsPanelProps {
open: boolean;
onOpenChange?: (open: boolean) => void;
@ -267,6 +376,7 @@ interface ChatSettingsPanelProps {
autoTitle: boolean;
onAutoTitleChange: (enabled: boolean) => void;
onReloadModel?: () => void;
onFoldersChange?: () => void;
}
export function ChatSettingsPanel({
@ -277,6 +387,7 @@ export function ChatSettingsPanel({
autoTitle,
onAutoTitleChange,
onReloadModel,
onFoldersChange,
}: ChatSettingsPanelProps) {
const isMobile = useIsMobile();
const isGguf = useChatRuntimeStore((s) => s.activeGgufVariant) != null;
@ -724,6 +835,8 @@ export function ChatSettingsPanel({
</div>
</CollapsibleSection>
<ModelFoldersSection onFoldersChange={onFoldersChange} />
<ChatTemplateSection onReloadModel={onReloadModel} />
</div>
<Dialog

File diff suppressed because it is too large Load diff

View file

@ -490,7 +490,8 @@ if (-not $HasNvidiaSmi) {
if (-not $HasNvidiaSmi) {
Write-Host ""
step "gpu" "none (chat-only / GGUF)" "Yellow"
substep "Training and GPU inference require an NVIDIA GPU with drivers installed." "Yellow"
Write-Host " Training and GPU inference require an NVIDIA GPU with drivers installed." -ForegroundColor Yellow
Write-Host " https://www.nvidia.com/Download/index.aspx" -ForegroundColor Yellow
Write-Host ""
} else {
step "gpu" "NVIDIA GPU detected"
@ -1596,29 +1597,15 @@ $resolveExit = $LASTEXITCODE
$ResolvedLlamaTag = if ($resolveOutput) { ($resolveOutput | Select-Object -Last 1).ToString().Trim() } else { "" }
if ($resolveExit -ne 0 -or [string]::IsNullOrWhiteSpace($ResolvedLlamaTag)) {
Write-Host ""
substep "Failed to resolve an installable prebuilt llama.cpp tag via $HelperReleaseRepo" "Yellow"
substep "Failed to resolve a published llama.cpp release via $HelperReleaseRepo" "Yellow"
Write-LlamaFailureLog -Output ($resolveOutput | Out-String)
# Resolve the llama.cpp tag for source-build fallback. Pass --published-repo
# so the resolver prefers Unsloth's tested tag (e.g. b8508) over the upstream
# bleeding-edge tag (e.g. b8514) from ggml-org/llama.cpp.
# so the resolver prefers the latest usable Unsloth-published upstream tag
# before falling back to the bleeding-edge ggml-org/llama.cpp tag.
$fallbackOutput = & python "$PSScriptRoot\install_llama_prebuilt.py" --resolve-llama-tag $RequestedLlamaTag --published-repo $HelperReleaseRepo 2>$null
$fallbackExit = $LASTEXITCODE
$ResolvedLlamaTag = if ($fallbackExit -eq 0 -and $fallbackOutput) {
($fallbackOutput | Select-Object -Last 1).ToString().Trim()
} elseif ($RequestedLlamaTag -eq "latest") {
# Try Unsloth release repo first, then fall back to ggml-org upstream
$resolvedLatest = $null
try {
$latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/$HelperReleaseRepo/releases/latest" -ErrorAction Stop
$resolvedLatest = $latestRelease.tag_name
} catch {}
if (-not $resolvedLatest) {
try {
$latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/ggml-org/llama.cpp/releases/latest" -ErrorAction Stop
$resolvedLatest = $latestRelease.tag_name
} catch {}
}
if ($resolvedLatest) { $resolvedLatest } else { $RequestedLlamaTag }
} else {
$RequestedLlamaTag
}
@ -1667,7 +1654,19 @@ if ($env:UNSLOTH_LLAMA_FORCE_COMPILE -eq "1") {
$ErrorActionPreference = $prevEAPPrebuilt
if ($prebuiltExit -eq 0) {
step "llama.cpp" "prebuilt installed and validated"
if ($prebuiltOutput -match "already matches selected release") {
step "llama.cpp" "prebuilt up to date and validated"
} else {
step "llama.cpp" "prebuilt installed and validated"
}
} elseif ($prebuiltExit -eq 3) {
step "llama.cpp" "install blocked by active llama.cpp process" "Yellow"
Write-LlamaFailureLog -Output $prebuiltOutput
if (Test-Path $LlamaCppDir) {
substep "Existing install was restored" "Yellow"
}
substep "Close Studio or other llama.cpp users and retry" "Yellow"
exit 3
} else {
step "llama.cpp" "prebuilt install failed (continuing)" "Yellow"
Write-LlamaFailureLog -Output $prebuiltOutput

View file

@ -108,6 +108,10 @@ echo ""
printf " ${C_TITLE}%s${C_RST}\n" "🦥 Unsloth Studio Setup"
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
verbose_substep "verbose diagnostics enabled"
_LLAMA_ONLY="${UNSLOTH_STUDIO_LLAMA_ONLY:-0}"
if [ "$_LLAMA_ONLY" = "1" ]; then
substep "llama.cpp only mode"
fi
# ── Clean up stale caches ──
rm -rf "$REPO_ROOT/unsloth_compiled_cache"
rm -rf "$SCRIPT_DIR/backend/unsloth_compiled_cache"
@ -120,6 +124,7 @@ if [[ "$keynames" == *$'\nCOLAB_'* ]]; then
IS_COLAB=true
fi
if [ "$_LLAMA_ONLY" != "1" ]; then
# ── Frontend ──
_NEED_FRONTEND_BUILD=true
if [ -d "$SCRIPT_DIR/frontend/dist" ]; then
@ -453,6 +458,7 @@ else
step "python" "dependencies up to date"
verbose_substep "python deps check: installed=$_PKG_NAME@${INSTALLED_VER:-unknown} latest=${LATEST_VER:-unknown}"
fi
fi
# ── 7. Prefer prebuilt llama.cpp bundles before any source build path ──
UNSLOTH_HOME="$HOME/.unsloth"
@ -477,26 +483,17 @@ else
_RESOLVED_LLAMA_TAG=""
fi
if [ -z "$_RESOLVED_LLAMA_TAG" ]; then
step "llama.cpp" "failed to resolve prebuilt tag via $_HELPER_RELEASE_REPO" "$C_WARN"
step "llama.cpp" "failed to resolve a published llama.cpp release via $_HELPER_RELEASE_REPO" "$C_WARN"
print_llama_error_log "$_RESOLVE_LLAMA_LOG"
set +e
# Resolve the llama.cpp tag for source-build fallback. Pass --published-repo
# so the resolver prefers Unsloth's tested tag (e.g. b8508) over the upstream
# bleeding-edge tag (e.g. b8514) from ggml-org/llama.cpp.
# so the resolver prefers the latest usable Unsloth-published upstream tag
# before falling back to the bleeding-edge ggml-org/llama.cpp tag.
_RESOLVED_LLAMA_TAG="$(python "$SCRIPT_DIR/install_llama_prebuilt.py" --resolve-llama-tag "$_REQUESTED_LLAMA_TAG" --published-repo "$_HELPER_RELEASE_REPO" 2>/dev/null)"
_RESOLVE_UPSTREAM_STATUS=$?
set -e
if [ "$_RESOLVE_UPSTREAM_STATUS" -ne 0 ] || [ -z "$_RESOLVED_LLAMA_TAG" ]; then
if [ "$_REQUESTED_LLAMA_TAG" = "latest" ]; then
# Try Unsloth release repo first, then fall back to ggml-org upstream
_RESOLVED_LLAMA_TAG="$(curl -fsSL "https://api.github.com/repos/${_HELPER_RELEASE_REPO}/releases/latest" 2>/dev/null | python -c "import sys,json; print(json.load(sys.stdin)['tag_name'])" 2>/dev/null)" || _RESOLVED_LLAMA_TAG=""
if [ -z "$_RESOLVED_LLAMA_TAG" ]; then
_RESOLVED_LLAMA_TAG="$(curl -fsSL https://api.github.com/repos/ggml-org/llama.cpp/releases/latest 2>/dev/null | python -c "import sys,json; print(json.load(sys.stdin)['tag_name'])" 2>/dev/null)" || _RESOLVED_LLAMA_TAG=""
fi
fi
if [ -z "$_RESOLVED_LLAMA_TAG" ]; then
_RESOLVED_LLAMA_TAG="$_REQUESTED_LLAMA_TAG"
fi
_RESOLVED_LLAMA_TAG="$_REQUESTED_LLAMA_TAG"
fi
_NEED_LLAMA_SOURCE_BUILD=true
_SKIP_PREBUILT_INSTALL=true
@ -538,9 +535,22 @@ else
set -e
if [ "$_PREBUILT_STATUS" -eq 0 ]; then
step "llama.cpp" "prebuilt installed and validated"
if grep -Fq "already matches selected release" "$_PREBUILT_LOG"; then
step "llama.cpp" "prebuilt up to date and validated"
else
step "llama.cpp" "prebuilt installed and validated"
fi
verbose_substep "llama.cpp install dir: $LLAMA_CPP_DIR"
rm -f "$_PREBUILT_LOG"
elif [ "$_PREBUILT_STATUS" -eq 3 ]; then
step "llama.cpp" "install blocked by active llama.cpp process" "$C_WARN"
print_llama_error_log "$_PREBUILT_LOG"
rm -f "$_PREBUILT_LOG"
if [ -d "$LLAMA_CPP_DIR" ]; then
substep "existing install was restored"
fi
substep "close Studio or other llama.cpp users and retry"
exit 3
else
step "llama.cpp" "prebuilt install failed (continuing)" "$C_WARN"
print_llama_error_log "$_PREBUILT_LOG"
@ -794,7 +804,16 @@ else
fi # end _SKIP_GGUF_BUILD check
# ── Footer ──
if [ "$IS_COLAB" = true ]; then
if [ "$_LLAMA_ONLY" = "1" ]; then
echo ""
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
if [ "$_LLAMA_CPP_DEGRADED" = true ]; then
printf " ${C_WARN}%s${C_RST}\n" "llama.cpp update finished (limited: llama.cpp unavailable)"
else
printf " ${C_TITLE}%s${C_RST}\n" "llama.cpp update finished"
fi
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
elif [ "$IS_COLAB" = true ]; then
echo ""
printf " ${C_DIM}%s${C_RST}\n" "$RULE"
if [ "$_LLAMA_CPP_DEGRADED" = true ]; then

View file

@ -39,7 +39,7 @@ def parse_args() -> argparse.Namespace:
parser.add_argument(
"--llama-tag",
default = "latest",
help = "llama.cpp tag to resolve. Defaults to the approved prebuilt tag for this host.",
help = "llama.cpp tag to resolve. Defaults to the latest usable published Unsloth release.",
)
parser.add_argument(
"--published-repo",

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,7 @@ Tests cover:
- Bug 2: Source-build fallback ignores pinned tag (both .sh and .ps1)
- Bug 3: Unix fallback deletes install before checking prerequisites
- Bug 4: Linux LD_LIBRARY_PATH missing build/bin
- "latest" tag resolution fallback chain (Unsloth -> ggml-org -> raw)
- "latest" tag resolution fallback chain (helper -> raw)
- Cross-platform binary_env (Linux, macOS, Windows)
- Edge cases: malformed JSON, empty responses, env overrides
@ -40,6 +40,10 @@ SPEC.loader.exec_module(MOD)
binary_env = MOD.binary_env
HostInfo = MOD.HostInfo
resolve_requested_llama_tag = MOD.resolve_requested_llama_tag
PublishedReleaseBundle = MOD.PublishedReleaseBundle
ApprovedArtifactHash = MOD.ApprovedArtifactHash
ApprovedReleaseChecksums = MOD.ApprovedReleaseChecksums
source_archive_logical_name = MOD.source_archive_logical_name
SETUP_SH = PACKAGE_ROOT / "studio" / "setup.sh"
SETUP_PS1 = PACKAGE_ROOT / "studio" / "setup.ps1"
@ -240,6 +244,57 @@ class TestResolveRequestedLlamaTag:
monkeypatch.setattr(MOD, "latest_upstream_release_tag", lambda: "b5555")
assert resolve_requested_llama_tag("") == "b5555"
def test_latest_with_published_repo_uses_latest_valid_published_release(
self, monkeypatch: pytest.MonkeyPatch
):
invalid = PublishedReleaseBundle(
repo = "unslothai/llama.cpp",
release_tag = "v2.0",
upstream_tag = "b9000",
assets = {},
manifest_asset_name = "llama-prebuilt-manifest.json",
artifacts = [],
selection_log = [],
)
valid = PublishedReleaseBundle(
repo = "unslothai/llama.cpp",
release_tag = "v1.0",
upstream_tag = "b8999",
assets = {},
manifest_asset_name = "llama-prebuilt-manifest.json",
artifacts = [],
selection_log = [],
)
monkeypatch.setattr(
MOD,
"iter_published_release_bundles",
lambda repo, published_release_tag = "": iter([invalid, valid]),
)
def fake_load(repo, release_tag):
if release_tag == "v2.0":
raise MOD.PrebuiltFallback("checksum asset missing")
return ApprovedReleaseChecksums(
repo = repo,
release_tag = release_tag,
upstream_tag = "b8999",
source_commit = None,
artifacts = {
source_archive_logical_name("b8999"): ApprovedArtifactHash(
asset_name = source_archive_logical_name("b8999"),
sha256 = "a" * 64,
repo = "ggml-org/llama.cpp",
kind = "upstream-source",
)
},
)
monkeypatch.setattr(MOD, "load_approved_release_checksums", fake_load)
monkeypatch.setattr(MOD, "latest_upstream_release_tag", lambda: "b7777")
assert resolve_requested_llama_tag("latest", "unslothai/llama.cpp") == "b8999"
# =========================================================================
# TEST GROUP C: setup.sh logic (bash subprocess tests)
@ -429,140 +484,61 @@ class TestSetupShLogic:
# TEST GROUP D: "latest" tag resolution (bash subprocess)
# =========================================================================
class TestLatestTagResolution:
"""Test the fallback chain: Unsloth API -> ggml-org API -> raw."""
"""Test the fallback chain: helper resolver -> raw."""
RESOLVE_TEMPLATE = textwrap.dedent("""\
export PATH="{mock_bin}:$PATH"
_REQUESTED_LLAMA_TAG="{requested_tag}"
_RESOLVED_LLAMA_TAG=""
_RESOLVE_UPSTREAM_STATUS=1
_HELPER_RELEASE_REPO="unslothai/llama.cpp"
if [ "$_RESOLVE_UPSTREAM_STATUS" -ne 0 ] || [ -z "$_RESOLVED_LLAMA_TAG" ]; then
if [ "$_REQUESTED_LLAMA_TAG" = "latest" ]; then
_RESOLVED_LLAMA_TAG="$(curl -fsSL "https://api.github.com/repos/${{_HELPER_RELEASE_REPO}}/releases/latest" 2>/dev/null | python -c "import sys,json; print(json.load(sys.stdin)['tag_name'])" 2>/dev/null)" || _RESOLVED_LLAMA_TAG=""
if [ -z "$_RESOLVED_LLAMA_TAG" ]; then
_RESOLVED_LLAMA_TAG="$(curl -fsSL https://api.github.com/repos/ggml-org/llama.cpp/releases/latest 2>/dev/null | python -c "import sys,json; print(json.load(sys.stdin)['tag_name'])" 2>/dev/null)" || _RESOLVED_LLAMA_TAG=""
fi
fi
if [ -z "$_RESOLVED_LLAMA_TAG" ]; then
_RESOLVED_LLAMA_TAG="$_REQUESTED_LLAMA_TAG"
fi
_RESOLVE_UPSTREAM_STATUS={resolve_status}
if [ "$_RESOLVE_UPSTREAM_STATUS" -eq 0 ] && [ -n "{resolved_tag}" ]; then
_RESOLVED_LLAMA_TAG="{resolved_tag}"
else
_RESOLVED_LLAMA_TAG="$_REQUESTED_LLAMA_TAG"
fi
echo "$_RESOLVED_LLAMA_TAG"
""")
@staticmethod
def _make_curl_mock(
mock_bin: Path, unsloth_response: str | None, ggml_response: str | None
):
"""Create a curl mock that returns different responses per repo."""
lines = ["#!/bin/bash"]
if unsloth_response is not None:
lines.append(
f'if echo "$*" | grep -q "unslothai/llama.cpp"; then echo \'{unsloth_response}\'; exit 0; fi'
)
else:
lines.append(
'if echo "$*" | grep -q "unslothai/llama.cpp"; then exit 1; fi'
)
if ggml_response is not None:
lines.append(
f'if echo "$*" | grep -q "ggml-org/llama.cpp"; then echo \'{ggml_response}\'; exit 0; fi'
)
else:
lines.append('if echo "$*" | grep -q "ggml-org/llama.cpp"; then exit 1; fi')
lines.append("exit 1")
curl_path = mock_bin / "curl"
curl_path.write_text("\n".join(lines) + "\n")
curl_path.chmod(0o755)
def _run_resolve(
self,
tmp_path: Path,
requested_tag: str,
unsloth_resp: str | None,
ggml_resp: str | None,
resolved_tag: str,
resolve_status: int,
) -> str:
mock_bin = tmp_path / "mock_bin"
mock_bin.mkdir(exist_ok = True)
self._make_curl_mock(mock_bin, unsloth_resp, ggml_resp)
script = self.RESOLVE_TEMPLATE.format(
mock_bin = mock_bin, requested_tag = requested_tag
requested_tag = requested_tag,
resolved_tag = resolved_tag,
resolve_status = resolve_status,
)
return run_bash(script)
def test_unsloth_succeeds(self, tmp_path: Path):
def test_helper_resolution_succeeds(self, tmp_path: Path):
output = self._run_resolve(
tmp_path,
"latest",
unsloth_resp = '{"tag_name":"b8508"}',
ggml_resp = '{"tag_name":"b9000"}',
resolved_tag = "b8508",
resolve_status = 0,
)
assert output == "b8508"
def test_unsloth_fails_ggml_succeeds(self, tmp_path: Path):
def test_helper_resolution_falls_back_to_raw_requested_tag(self, tmp_path: Path):
output = self._run_resolve(
tmp_path,
"latest",
unsloth_resp = None,
ggml_resp = '{"tag_name":"b9000"}',
)
assert output == "b9000"
def test_both_fail_raw_fallback(self, tmp_path: Path):
output = self._run_resolve(
tmp_path,
"latest",
unsloth_resp = None,
ggml_resp = None,
resolved_tag = "",
resolve_status = 1,
)
assert output == "latest"
def test_concrete_tag_passes_through(self, tmp_path: Path):
def test_concrete_tag_passes_through_when_helper_fails(self, tmp_path: Path):
output = self._run_resolve(
tmp_path,
"b7777",
unsloth_resp = '{"tag_name":"b8508"}',
ggml_resp = '{"tag_name":"b9000"}',
resolved_tag = "",
resolve_status = 1,
)
assert output == "b7777"
def test_unsloth_malformed_json_falls_through(self, tmp_path: Path):
output = self._run_resolve(
tmp_path,
"latest",
unsloth_resp = '{"bad_key":"no_tag"}',
ggml_resp = '{"tag_name":"b9001"}',
)
assert output == "b9001"
def test_both_malformed_json_raw_fallback(self, tmp_path: Path):
output = self._run_resolve(
tmp_path,
"latest",
unsloth_resp = '{"bad":"data"}',
ggml_resp = '{"also":"bad"}',
)
assert output == "latest"
def test_unsloth_empty_body_falls_through(self, tmp_path: Path):
output = self._run_resolve(
tmp_path,
"latest",
unsloth_resp = "",
ggml_resp = '{"tag_name":"b7000"}',
)
assert output == "b7000"
def test_unsloth_empty_tag_name_falls_through(self, tmp_path: Path):
output = self._run_resolve(
tmp_path,
"latest",
unsloth_resp = '{"tag_name":""}',
ggml_resp = '{"tag_name":"b6000"}',
)
assert output == "b6000"
def test_env_override_unsloth_llama_tag(self):
output = run_bash(
'echo "${UNSLOTH_LLAMA_TAG:-latest}"',
@ -593,10 +569,10 @@ class TestSourceCodePatterns:
def test_setup_sh_no_rm_before_prereq_check(self):
"""rm -rf must appear AFTER cmake/git checks, not before."""
content = SETUP_SH.read_text()
# Find the source-build block
idx_else = content.find("# Check prerequisites")
assert idx_else != -1
block = content[idx_else:]
# Anchor on the source-build cmake check block.
idx_block = content.find("command -v cmake")
assert idx_block != -1
block = content[idx_block:]
# rm -rf should appear after the cmake/git checks
idx_cmake = block.find("command -v cmake")
idx_git = block.find("command -v git")
@ -619,14 +595,12 @@ class TestSourceCodePatterns:
'_RESOLVED_LLAMA_TAG" != "latest"' in content
), "Should guard against literal 'latest' tag"
def test_setup_sh_latest_resolution_queries_unsloth_first(self):
"""The Unsloth repo should be queried before ggml-org."""
def test_setup_sh_latest_resolution_uses_helper_only(self):
"""Shell fallback should rely on helper output, not raw GitHub API tag_name."""
content = SETUP_SH.read_text()
idx_unsloth = content.find("_HELPER_RELEASE_REPO}/releases/latest")
idx_ggml = content.find("ggml-org/llama.cpp/releases/latest")
assert idx_unsloth != -1, "Unsloth API query not found"
assert idx_ggml != -1, "ggml-org API query not found"
assert idx_unsloth < idx_ggml, "Unsloth should be queried before ggml-org"
assert "--resolve-llama-tag" in content
assert "_HELPER_RELEASE_REPO}/releases/latest" not in content
assert "ggml-org/llama.cpp/releases/latest" not in content
def test_setup_ps1_uses_checkout_b(self):
"""PS1 should use checkout -B, not checkout --force FETCH_HEAD."""
@ -658,14 +632,12 @@ class TestSourceCodePatterns:
f"Found 'git pull' in llama.cpp build section at line {i+1}"
)
def test_setup_ps1_latest_resolution_queries_unsloth_first(self):
"""PS1 should query Unsloth repo before ggml-org."""
def test_setup_ps1_latest_resolution_uses_helper_only(self):
"""PS1 fallback should rely on helper output, not raw GitHub API tag_name."""
content = SETUP_PS1.read_text()
idx_unsloth = content.find("$HelperReleaseRepo/releases/latest")
idx_ggml = content.find("ggml-org/llama.cpp/releases/latest")
assert idx_unsloth != -1, "Unsloth API query not found in PS1"
assert idx_ggml != -1, "ggml-org API query not found in PS1"
assert idx_unsloth < idx_ggml, "Unsloth should be queried before ggml-org"
assert "--resolve-llama-tag" in content
assert "$HelperReleaseRepo/releases/latest" not in content
assert "ggml-org/llama.cpp/releases/latest" not in content
def test_binary_env_linux_has_binary_parent(self):
"""The Linux branch of binary_env should include binary_path.parent."""

View file

@ -54,6 +54,12 @@ apply_approved_hashes = INSTALL_LLAMA_PREBUILT.apply_approved_hashes
linux_cuda_choice_from_release = INSTALL_LLAMA_PREBUILT.linux_cuda_choice_from_release
windows_cuda_attempts = INSTALL_LLAMA_PREBUILT.windows_cuda_attempts
resolve_upstream_asset_choice = INSTALL_LLAMA_PREBUILT.resolve_upstream_asset_choice
resolve_requested_install_tag = INSTALL_LLAMA_PREBUILT.resolve_requested_install_tag
resolve_install_attempts = INSTALL_LLAMA_PREBUILT.resolve_install_attempts
resolve_install_release_plans = INSTALL_LLAMA_PREBUILT.resolve_install_release_plans
resolve_published_release = INSTALL_LLAMA_PREBUILT.resolve_published_release
source_archive_logical_name = INSTALL_LLAMA_PREBUILT.source_archive_logical_name
env_int = INSTALL_LLAMA_PREBUILT.env_int
# ---------------------------------------------------------------------------
@ -131,6 +137,37 @@ def make_checksums(asset_names):
)
def make_checksums_with_source(
asset_names,
*,
release_tag = "v1.0",
upstream_tag = "b8508",
):
return ApprovedReleaseChecksums(
repo = "unslothai/llama.cpp",
release_tag = release_tag,
upstream_tag = upstream_tag,
source_commit = None,
artifacts = {
**{
name: ApprovedArtifactHash(
asset_name = name,
sha256 = "a" * 64,
repo = "unslothai/llama.cpp",
kind = "prebuilt",
)
for name in asset_names
},
source_archive_logical_name(upstream_tag): ApprovedArtifactHash(
asset_name = source_archive_logical_name(upstream_tag),
sha256 = "b" * 64,
repo = "ggml-org/llama.cpp",
kind = "upstream-source",
),
},
)
def mock_linux_runtime(monkeypatch, lines):
dirs = {line: ["/usr/lib/stub"] for line in lines}
monkeypatch.setattr(
@ -408,7 +445,100 @@ class TestApplyApprovedHashes:
# ===========================================================================
# J. linux_cuda_choice_from_release -- core selection
# J. published release resolution
# ===========================================================================
class TestPublishedReleaseResolution:
def test_latest_skips_invalid_release_and_uses_next_valid(self, monkeypatch):
invalid = make_release([], release_tag = "v2.0", upstream_tag = "b9000")
valid = make_release([], release_tag = "v1.0", upstream_tag = "b8999")
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_published_release_bundles",
lambda repo, published_release_tag = "": iter([invalid, valid]),
)
def fake_load(repo, release_tag):
if release_tag == "v2.0":
raise PrebuiltFallback("checksum asset missing")
return make_checksums_with_source(
[], release_tag = "v1.0", upstream_tag = "b8999"
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"load_approved_release_checksums",
fake_load,
)
resolved = resolve_published_release("latest", "unslothai/llama.cpp")
assert resolved.bundle.release_tag == "v1.0"
assert resolved.bundle.upstream_tag == "b8999"
assert resolved.checksums.release_tag == "v1.0"
def test_concrete_tag_matches_manifest_upstream_tag(self, monkeypatch):
release = make_release([], release_tag = "release-b8508", upstream_tag = "b8508")
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_published_release_bundles",
lambda repo, published_release_tag = "": iter([release]),
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"load_approved_release_checksums",
lambda repo, release_tag: make_checksums_with_source(
[],
release_tag = release_tag,
upstream_tag = "b8508",
),
)
assert (
resolve_requested_install_tag("b8508", "", "unslothai/llama.cpp") == "b8508"
)
def test_concrete_tag_without_matching_release_raises(self, monkeypatch):
release = make_release([], release_tag = "release-b9000", upstream_tag = "b9000")
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_published_release_bundles",
lambda repo, published_release_tag = "": iter([release]),
)
with pytest.raises(PrebuiltFallback, match = "matched upstream tag b8508"):
resolve_requested_install_tag("b8508", "", "unslothai/llama.cpp")
def test_pinned_release_must_match_requested_upstream_tag(self, monkeypatch):
bundle = make_release(
[], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000"
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"pinned_published_release_bundle",
lambda repo, release_tag: bundle,
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"load_approved_release_checksums",
lambda repo, release_tag: make_checksums_with_source(
[],
release_tag = release_tag,
upstream_tag = "b9000",
),
)
with pytest.raises(PrebuiltFallback, match = "but requested b8508"):
resolve_requested_install_tag(
"b8508",
"llama-prebuilt-latest",
"unslothai/llama.cpp",
)
# ===========================================================================
# K. linux_cuda_choice_from_release -- core selection
# ===========================================================================
@ -676,7 +806,554 @@ class TestLinuxCudaChoiceFromRelease:
# ===========================================================================
# K. windows_cuda_attempts
# L. resolve_install_attempts
# ===========================================================================
class TestResolveInstallAttempts:
def test_windows_cuda_prefers_published_asset_from_selected_release(
self, monkeypatch
):
host = make_host(system = "Windows", machine = "AMD64")
host.driver_cuda_version = (12, 4)
mock_windows_runtime(monkeypatch, ["cuda12"])
asset_name = "llama-b9000-bin-win-cuda-12.4-x64.zip"
release = make_release(
[
make_artifact(
asset_name,
install_kind = "windows-cuda",
runtime_line = "cuda12",
coverage_class = None,
supported_sms = [],
min_sm = None,
max_sm = None,
bundle_profile = None,
)
],
release_tag = "llama-prebuilt-latest",
upstream_tag = "b9000",
assets = {asset_name: f"https://published.example/{asset_name}"},
)
checksums = make_checksums_with_source(
[asset_name],
release_tag = release.release_tag,
upstream_tag = "b9000",
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_resolved_published_releases",
lambda requested_tag, published_repo, published_release_tag = "": iter(
[
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = release,
checksums = checksums,
)
]
),
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"github_release_assets",
lambda repo, tag: (_ for _ in ()).throw(
AssertionError(
"published Windows CUDA choice should not query upstream"
)
),
)
requested_tag, resolved_tag, attempts, approved = resolve_install_attempts(
"latest",
host,
"unslothai/llama.cpp",
"",
)
assert requested_tag == "latest"
assert resolved_tag == "b9000"
assert attempts[0].name == asset_name
assert attempts[0].source_label == "published"
assert attempts[0].expected_sha256 == "a" * 64
assert approved.release_tag == "llama-prebuilt-latest"
def test_windows_cuda_uses_selected_release_upstream_tag(self, monkeypatch):
host = make_host(system = "Windows", machine = "AMD64")
host.driver_cuda_version = (12, 4)
mock_windows_runtime(monkeypatch, ["cuda12"])
release = make_release(
[], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000"
)
checksums = make_checksums_with_source(
["llama-b9000-bin-win-cuda-12.4-x64.zip"],
release_tag = release.release_tag,
upstream_tag = "b9000",
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_resolved_published_releases",
lambda requested_tag, published_repo, published_release_tag = "": iter(
[
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = release,
checksums = checksums,
)
]
),
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"github_release_assets",
lambda repo, tag: {
f"llama-{tag}-bin-win-cuda-12.4-x64.zip": f"https://example.com/llama-{tag}-bin-win-cuda-12.4-x64.zip"
},
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"resolve_windows_cuda_choices",
lambda host, tag, assets: [
AssetChoice(
repo = UPSTREAM_REPO,
tag = tag,
name = f"llama-{tag}-bin-win-cuda-12.4-x64.zip",
url = assets[f"llama-{tag}-bin-win-cuda-12.4-x64.zip"],
source_label = "upstream",
install_kind = "windows-cuda",
runtime_line = "cuda12",
)
],
)
requested_tag, resolved_tag, attempts, approved = resolve_install_attempts(
"latest",
host,
"unslothai/llama.cpp",
"",
)
assert requested_tag == "latest"
assert resolved_tag == "b9000"
assert attempts[0].name == "llama-b9000-bin-win-cuda-12.4-x64.zip"
assert attempts[0].expected_sha256 == "a" * 64
assert approved.release_tag == "llama-prebuilt-latest"
def test_linux_cpu_uses_same_tag_upstream_asset(self, monkeypatch):
host = make_host(
has_usable_nvidia = False,
has_physical_nvidia = False,
nvidia_smi = None,
)
release = make_release(
[], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000"
)
checksums = make_checksums_with_source(
["llama-b9000-bin-ubuntu-x64.tar.gz"],
release_tag = release.release_tag,
upstream_tag = "b9000",
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_resolved_published_releases",
lambda requested_tag, published_repo, published_release_tag = "": iter(
[
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = release,
checksums = checksums,
)
]
),
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"github_release_assets",
lambda repo, tag: {
f"llama-{tag}-bin-ubuntu-x64.tar.gz": f"https://example.com/llama-{tag}-bin-ubuntu-x64.tar.gz"
},
)
_requested_tag, resolved_tag, attempts, _approved = resolve_install_attempts(
"latest",
host,
"unslothai/llama.cpp",
"",
)
assert resolved_tag == "b9000"
assert attempts[0].name == "llama-b9000-bin-ubuntu-x64.tar.gz"
assert attempts[0].source_label == "upstream"
assert attempts[0].expected_sha256 == "a" * 64
def test_linux_cuda_does_not_fall_back_to_upstream_cpu(self, monkeypatch):
host = make_host(system = "Linux", machine = "x86_64", compute_caps = ["86"])
release = make_release(
[], release_tag = "llama-prebuilt-latest", upstream_tag = "b9000"
)
checksums = make_checksums_with_source(
[],
release_tag = release.release_tag,
upstream_tag = "b9000",
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_resolved_published_releases",
lambda requested_tag, published_repo, published_release_tag = "": iter(
[
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = release,
checksums = checksums,
)
]
),
)
mock_linux_runtime(monkeypatch, ["cuda12"])
with pytest.raises(
PrebuiltFallback, match = "no compatible published Linux CUDA bundle"
):
resolve_install_attempts("latest", host, "unslothai/llama.cpp", "")
def test_windows_cpu_prefers_published_asset(self, monkeypatch):
host = make_host(
system = "Windows",
machine = "AMD64",
has_usable_nvidia = False,
has_physical_nvidia = False,
nvidia_smi = None,
)
asset_name = "llama-b9000-bin-win-cpu-x64.zip"
release = make_release(
[
make_artifact(
asset_name,
install_kind = "windows-cpu",
runtime_line = None,
coverage_class = None,
supported_sms = [],
min_sm = None,
max_sm = None,
bundle_profile = None,
)
],
release_tag = "llama-prebuilt-latest",
upstream_tag = "b9000",
assets = {asset_name: f"https://published.example/{asset_name}"},
)
checksums = make_checksums_with_source(
[asset_name],
release_tag = release.release_tag,
upstream_tag = "b9000",
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_resolved_published_releases",
lambda requested_tag, published_repo, published_release_tag = "": iter(
[
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = release,
checksums = checksums,
)
]
),
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"github_release_assets",
lambda repo, tag: (_ for _ in ()).throw(
AssertionError("published Windows CPU choice should not query upstream")
),
)
_requested_tag, resolved_tag, attempts, _approved = resolve_install_attempts(
"latest",
host,
"unslothai/llama.cpp",
"",
)
assert resolved_tag == "b9000"
assert attempts[0].name == asset_name
assert attempts[0].source_label == "published"
def test_macos_prefers_published_asset(self, monkeypatch):
host = make_host(
system = "Darwin",
machine = "arm64",
nvidia_smi = None,
driver_cuda_version = None,
compute_caps = [],
has_physical_nvidia = False,
has_usable_nvidia = False,
)
asset_name = "llama-b9000-bin-macos-arm64.tar.gz"
release = make_release(
[
make_artifact(
asset_name,
install_kind = "macos-arm64",
runtime_line = None,
coverage_class = None,
supported_sms = [],
min_sm = None,
max_sm = None,
bundle_profile = None,
)
],
release_tag = "llama-prebuilt-latest",
upstream_tag = "b9000",
assets = {asset_name: f"https://published.example/{asset_name}"},
)
checksums = make_checksums_with_source(
[asset_name],
release_tag = release.release_tag,
upstream_tag = "b9000",
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_resolved_published_releases",
lambda requested_tag, published_repo, published_release_tag = "": iter(
[
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = release,
checksums = checksums,
)
]
),
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"github_release_assets",
lambda repo, tag: (_ for _ in ()).throw(
AssertionError("published macOS choice should not query upstream")
),
)
_requested_tag, resolved_tag, attempts, _approved = resolve_install_attempts(
"latest",
host,
"unslothai/llama.cpp",
"",
)
assert resolved_tag == "b9000"
assert attempts[0].name == asset_name
assert attempts[0].source_label == "published"
def test_windows_cpu_missing_checksum_rejects_install(self, monkeypatch):
host = make_host(
system = "Windows",
machine = "AMD64",
has_usable_nvidia = False,
has_physical_nvidia = False,
nvidia_smi = None,
)
published_name = "llama-b9000-bin-win-cpu-x64.zip"
release = make_release(
[
make_artifact(
published_name,
install_kind = "windows-cpu",
runtime_line = None,
coverage_class = None,
supported_sms = [],
min_sm = None,
max_sm = None,
bundle_profile = None,
)
],
release_tag = "llama-prebuilt-latest",
upstream_tag = "b9000",
assets = {published_name: f"https://published.example/{published_name}"},
)
checksums = make_checksums_with_source(
[],
release_tag = release.release_tag,
upstream_tag = "b9000",
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_resolved_published_releases",
lambda requested_tag, published_repo, published_release_tag = "": iter(
[
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = release,
checksums = checksums,
)
]
),
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"github_release_assets",
lambda repo, tag: {
f"llama-{tag}-bin-win-cpu-x64.zip": f"https://upstream.example/llama-{tag}-bin-win-cpu-x64.zip"
},
)
with pytest.raises(
PrebuiltFallback,
match = "approved checksum asset did not contain the selected prebuilt archive",
):
resolve_install_attempts(
"latest",
host,
"unslothai/llama.cpp",
"",
)
class TestResolveInstallReleasePlans:
def test_latest_collects_multiple_older_release_plans_up_to_limit(
self, monkeypatch
):
host = make_host(
has_usable_nvidia = False,
has_physical_nvidia = False,
nvidia_smi = None,
)
releases = [
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = make_release([], release_tag = "r3", upstream_tag = "b9003"),
checksums = make_checksums_with_source(
["llama-b9003-bin-ubuntu-x64.tar.gz"],
release_tag = "r3",
upstream_tag = "b9003",
),
),
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = make_release([], release_tag = "r2", upstream_tag = "b9002"),
checksums = make_checksums_with_source(
["llama-b9002-bin-ubuntu-x64.tar.gz"],
release_tag = "r2",
upstream_tag = "b9002",
),
),
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = make_release([], release_tag = "r1", upstream_tag = "b9001"),
checksums = make_checksums_with_source(
["llama-b9001-bin-ubuntu-x64.tar.gz"],
release_tag = "r1",
upstream_tag = "b9001",
),
),
]
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_resolved_published_releases",
lambda requested_tag, published_repo, published_release_tag = "": iter(
releases
),
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"github_release_assets",
lambda repo, tag: {
f"llama-{tag}-bin-ubuntu-x64.tar.gz": f"https://example.com/llama-{tag}-bin-ubuntu-x64.tar.gz"
},
)
requested_tag, plans = resolve_install_release_plans(
"latest",
host,
"unslothai/llama.cpp",
"",
max_release_fallbacks = 2,
)
assert requested_tag == "latest"
assert [plan.release_tag for plan in plans] == ["r3", "r2"]
assert [plan.llama_tag for plan in plans] == ["b9003", "b9002"]
def test_latest_skips_non_installable_release_and_keeps_searching(
self, monkeypatch
):
host = make_host(
has_usable_nvidia = False,
has_physical_nvidia = False,
nvidia_smi = None,
)
releases = [
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = make_release([], release_tag = "r2", upstream_tag = "b9002"),
checksums = make_checksums_with_source(
[],
release_tag = "r2",
upstream_tag = "b9002",
),
),
INSTALL_LLAMA_PREBUILT.ResolvedPublishedRelease(
bundle = make_release([], release_tag = "r1", upstream_tag = "b9001"),
checksums = make_checksums_with_source(
["llama-b9001-bin-ubuntu-x64.tar.gz"],
release_tag = "r1",
upstream_tag = "b9001",
),
),
]
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"iter_resolved_published_releases",
lambda requested_tag, published_repo, published_release_tag = "": iter(
releases
),
)
monkeypatch.setattr(
INSTALL_LLAMA_PREBUILT,
"github_release_assets",
lambda repo, tag: (
{}
if tag == "b9002"
else {
f"llama-{tag}-bin-ubuntu-x64.tar.gz": f"https://example.com/llama-{tag}-bin-ubuntu-x64.tar.gz"
}
),
)
_requested_tag, plans = resolve_install_release_plans(
"latest",
host,
"unslothai/llama.cpp",
"",
max_release_fallbacks = 2,
)
assert len(plans) == 1
assert plans[0].release_tag == "r1"
assert plans[0].llama_tag == "b9001"
def test_malformed_release_fallback_env_uses_default(self, monkeypatch):
monkeypatch.setenv("UNSLOTH_LLAMA_MAX_PREBUILT_RELEASE_FALLBACKS", "not-an-int")
assert (
env_int("UNSLOTH_LLAMA_MAX_PREBUILT_RELEASE_FALLBACKS", 3, minimum = 1) == 3
)
def test_import_with_malformed_release_fallback_env_does_not_crash(
self, monkeypatch
):
monkeypatch.setenv("UNSLOTH_LLAMA_MAX_PREBUILT_RELEASE_FALLBACKS", "bad-value")
spec = importlib.util.spec_from_file_location(
"studio_install_llama_prebuilt_env_reload",
MODULE_PATH,
)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
try:
spec.loader.exec_module(module)
assert module.DEFAULT_MAX_PREBUILT_RELEASE_FALLBACKS == 2
finally:
sys.modules.pop(spec.name, None)
# ===========================================================================
# N. windows_cuda_attempts
# ===========================================================================
@ -753,7 +1430,7 @@ class TestWindowsCudaAttempts:
# ===========================================================================
# L. resolve_upstream_asset_choice -- platform routing
# O. resolve_upstream_asset_choice -- platform routing
# ===========================================================================

View file

@ -0,0 +1,175 @@
import importlib.util
import sys
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = REPO_ROOT / "validate-llama-prebuilt.py"
if not MODULE_PATH.is_file():
pytest.skip(
f"validate-llama-prebuilt.py not present at {MODULE_PATH}",
allow_module_level = True,
)
SPEC = importlib.util.spec_from_file_location("validate_llama_prebuilt", MODULE_PATH)
assert SPEC is not None and SPEC.loader is not None
VALIDATE = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = VALIDATE
SPEC.loader.exec_module(VALIDATE)
def test_build_local_approved_checksums_uses_staged_upstream_tag(
tmp_path: Path, monkeypatch
):
stage_dir = tmp_path / "release-1"
stage_dir.mkdir()
asset_path = stage_dir / "app-test-linux-x64-cuda12-newer.tar.gz"
asset_path.write_bytes(b"bundle")
sibling_checksums = stage_dir / VALIDATE.installer.DEFAULT_PUBLISHED_SHA256_ASSET
sibling_checksums.write_text(
"""
{
"schema_version": 1,
"component": "llama.cpp",
"release_tag": "release-1",
"upstream_tag": "b9001",
"source_commit": "deadbeef",
"artifacts": {
"llama.cpp-source-b9001.tar.gz": {
"sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"repo": "ggml-org/llama.cpp",
"kind": "upstream-source"
}
}
}
""".strip()
+ "\n",
encoding = "utf-8",
)
asset = VALIDATE.LocalAsset(
path = asset_path,
tag = "test",
name = asset_path.name,
install_kind = "linux-cuda",
source_kind = "app-bundle",
native_runnable = True,
bundle_profile = "cuda12-newer",
runtime_line = "cuda12",
)
checksums = VALIDATE.build_local_approved_checksums(
asset,
allow_network_source_hash = False,
)
assert checksums.release_tag == "release-1"
assert checksums.upstream_tag == "b9001"
assert "llama.cpp-source-b9001.tar.gz" in checksums.artifacts
assert "llama.cpp-source-test.tar.gz" not in checksums.artifacts
def test_validate_native_asset_passes_release_tag_and_upstream_tag(
tmp_path: Path, monkeypatch
):
stage_dir = tmp_path / "release-7"
stage_dir.mkdir()
asset_path = stage_dir / "app-test-linux-x64-cuda12-newer.tar.gz"
asset_path.write_bytes(b"bundle")
sibling_checksums = stage_dir / VALIDATE.installer.DEFAULT_PUBLISHED_SHA256_ASSET
sibling_checksums.write_text(
"""
{
"schema_version": 1,
"component": "llama.cpp",
"release_tag": "release-7",
"upstream_tag": "b9007",
"source_commit": "deadbeef",
"artifacts": {
"llama.cpp-source-b9007.tar.gz": {
"sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"repo": "ggml-org/llama.cpp",
"kind": "upstream-source"
}
}
}
""".strip()
+ "\n",
encoding = "utf-8",
)
asset = VALIDATE.LocalAsset(
path = asset_path,
tag = "test",
name = asset_path.name,
install_kind = "linux-cuda",
source_kind = "app-bundle",
native_runnable = True,
bundle_profile = "cuda12-newer",
runtime_line = "cuda12",
)
host = VALIDATE.installer.HostInfo(
system = "Linux",
machine = "x86_64",
is_windows = False,
is_linux = True,
is_macos = False,
is_x86_64 = True,
is_arm64 = False,
nvidia_smi = None,
driver_cuda_version = None,
compute_caps = [],
visible_cuda_devices = None,
has_physical_nvidia = False,
has_usable_nvidia = False,
)
monkeypatch.setattr(VALIDATE.installer, "detect_host", lambda: host)
monkeypatch.setattr(
VALIDATE.installer,
"download_validation_model",
lambda probe_path, cache_path: probe_path.write_bytes(b"probe"),
)
captured = {}
def fake_validate_prebuilt_attempts(
attempts,
host,
install_dir,
work_dir,
probe_path,
*,
requested_tag,
llama_tag,
release_tag,
approved_checksums,
initial_fallback_used = False,
existing_install_dir = None,
):
captured["requested_tag"] = requested_tag
captured["llama_tag"] = llama_tag
captured["release_tag"] = release_tag
staging_dir = VALIDATE.installer.create_install_staging_dir(install_dir)
return attempts[0], staging_dir, False
monkeypatch.setattr(
VALIDATE.installer,
"validate_prebuilt_attempts",
fake_validate_prebuilt_attempts,
)
record = VALIDATE.validate_native_asset(
asset,
keep_temp = False,
allow_network_source_hash = False,
)
assert record.status == "PASS"
assert captured == {
"requested_tag": "test",
"llama_tag": "b9007",
"release_tag": "release-7",
}