|
| 1 | +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +package main |
| 5 | + |
| 6 | +import ( |
| 7 | + "bytes" |
| 8 | + "context" |
| 9 | + "crypto/sha256" |
| 10 | + "encoding/hex" |
| 11 | + "encoding/json" |
| 12 | + "errors" |
| 13 | + "fmt" |
| 14 | + "io" |
| 15 | + "net/http" |
| 16 | + "os" |
| 17 | + "path/filepath" |
| 18 | + "reflect" |
| 19 | + "strings" |
| 20 | + "testing" |
| 21 | +) |
| 22 | + |
| 23 | +type windowsCUDAFixture struct { |
| 24 | + *llamaUpstreamFixture |
| 25 | + vendor *Fetch |
| 26 | + failArchive bool |
| 27 | + onArchive func() |
| 28 | +} |
| 29 | + |
| 30 | +// newWindowsCUDAFixture shapes the recipe like the bundled Windows x64 one: a |
| 31 | +// pinned vendor installer plus a pinned CUDA app and runtime bundle. |
| 32 | +func newWindowsCUDAFixture(t *testing.T, capabilities string, queryErr error, cuda llamaRuntimeFixture) *windowsCUDAFixture { |
| 33 | + t.Helper() |
| 34 | + upstream := newLlamaUpstreamFixture(t, llamaRuntimeFixture{Version: "version: 0.4.0-dev (build 10826, commit fixture)", Licenses: "vendor fixture license", Devices: "Available devices:\n Vulkan0: Fixture GPU"}) |
| 35 | + script := []byte(`$ErrorActionPreference='Stop' |
| 36 | +if ($env:LLAMA_VERSION -ne 'b10826' -or $env:SKIP_INSTALL -ne '1' -or $env:SKIP_CUDA -or $env:SKIP_VULKAN) {throw 'vendor selection was restricted'} |
| 37 | +$stagePath=Join-Path $env:USERPROFILE 'llama-app' |
| 38 | +New-Item -ItemType Directory -Path $stagePath -Force | Out-Null |
| 39 | +Copy-Item -LiteralPath $env:FAKE_LLAMA_BIN_SOURCE -Destination (Join-Path $stagePath 'llama.exe') |
| 40 | +Copy-Item -LiteralPath $env:FAKE_LLAMA_FIXTURE_SOURCE -Destination (Join-Path $stagePath '.llama-fixture.json') |
| 41 | +`) |
| 42 | + digest := sha256.Sum256(script) |
| 43 | + f := &windowsCUDAFixture{llamaUpstreamFixture: upstream, vendor: &Fetch{URL: "https://example.invalid/pinned-vendor.ps1", SHA256: hex.EncodeToString(digest[:])}} |
| 44 | + bin, err := os.ReadFile(fakeEngineBin) |
| 45 | + if err != nil { |
| 46 | + t.Fatal(err) |
| 47 | + } |
| 48 | + cudaJSON, _ := json.Marshal(cuda) |
| 49 | + bundles := [][]byte{llamaFixtureZip(t, map[string][]byte{"llama.exe": bin, ".llama-fixture.json": cudaJSON}), llamaFixtureZip(t, map[string][]byte{"cudart64_13.dll": []byte("fixture CUDA runtime")})} |
| 50 | + install := f.st.plat.Install |
| 51 | + install.UpstreamFirst, install.CPUFetch, install.Archives, install.Fetch, install.CUDAArchives = false, nil, nil, f.vendor, nil |
| 52 | + for i, bundle := range bundles { |
| 53 | + h := sha256.Sum256(bundle) |
| 54 | + install.CUDAArchives = append(install.CUDAArchives, Fetch{URL: fmt.Sprintf("https://github.com/ggml-org/llama.cpp/releases/download/b10826/fixture-cuda-%d.zip", i), SHA256: hex.EncodeToString(h[:])}) |
| 55 | + } |
| 56 | + f.e.nvidiaComputeQuery = func(context.Context, map[string]string) (string, error) { return capabilities, queryErr } |
| 57 | + f.e.client = &http.Client{Transport: llamaFixtureTransport(func(r *http.Request) (*http.Response, error) { |
| 58 | + url := r.URL.String() |
| 59 | + f.requests[url]++ |
| 60 | + body := []byte(nil) |
| 61 | + if url == f.vendor.URL { |
| 62 | + body = script |
| 63 | + } |
| 64 | + for i, fetch := range install.CUDAArchives { |
| 65 | + if fetch.URL != url { |
| 66 | + continue |
| 67 | + } |
| 68 | + if f.onArchive != nil { |
| 69 | + f.onArchive() |
| 70 | + } |
| 71 | + if f.failArchive { |
| 72 | + return nil, errors.New("fixture CUDA archive unavailable") |
| 73 | + } |
| 74 | + body = bundles[i] |
| 75 | + } |
| 76 | + if body == nil { |
| 77 | + return nil, errors.New("unexpected fixture request") |
| 78 | + } |
| 79 | + return &http.Response{StatusCode: http.StatusOK, ContentLength: -1, Body: io.NopCloser(bytes.NewReader(body))}, nil |
| 80 | + })} |
| 81 | + return f |
| 82 | +} |
| 83 | + |
| 84 | +func (f *windowsCUDAFixture) archiveRequests() int { |
| 85 | + n := 0 |
| 86 | + for _, fetch := range f.st.plat.Install.CUDAArchives { |
| 87 | + n += f.requests[fetch.URL] |
| 88 | + } |
| 89 | + return n |
| 90 | +} |
| 91 | + |
| 92 | +// Every NVIDIA GPU at 7.5 or newer installs the pinned CUDA build; anything that |
| 93 | +// rules CUDA out installs the vendor build and records why, without mixing the |
| 94 | +// two stages. |
| 95 | +func TestLlamaWindowsCUDASelection(t *testing.T) { |
| 96 | + noDevice := cudaLlamaFixture(10826) |
| 97 | + noDevice.Devices = "Available devices:\n CPU0: Fixture CPU" |
| 98 | + for _, tc := range []struct { |
| 99 | + name, capabilities, note string |
| 100 | + queryErr error |
| 101 | + cuda llamaRuntimeFixture |
| 102 | + failArchive bool |
| 103 | + archiveRequests int |
| 104 | + }{ |
| 105 | + {name: "turing-or-newer", capabilities: "8.9\n12.0\n", cuda: cudaLlamaFixture(10826), archiveRequests: 2}, |
| 106 | + {name: "no-nvidia", queryErr: errors.New("executable file not found"), cuda: cudaLlamaFixture(10826), note: "no NVIDIA GPU"}, |
| 107 | + {name: "pre-turing", capabilities: "8.9\n6.1\n", cuda: cudaLlamaFixture(10826), note: "below the 7.5"}, |
| 108 | + {name: "unreadable", capabilities: "[N/A]\n", cuda: cudaLlamaFixture(10826), note: "unreadable"}, |
| 109 | + {name: "no-cuda-device", capabilities: "8.9\n", cuda: noDevice, note: "CUDA device", archiveRequests: 2}, |
| 110 | + {name: "archive-unavailable", capabilities: "8.9\n", cuda: cudaLlamaFixture(10826), failArchive: true, note: "official CUDA archives", archiveRequests: 1}, |
| 111 | + } { |
| 112 | + t.Run(tc.name, func(t *testing.T) { |
| 113 | + f := newWindowsCUDAFixture(t, tc.capabilities, tc.queryErr, tc.cuda) |
| 114 | + f.failArchive = tc.failArchive |
| 115 | + if err := f.e.installLlamaApp(context.Background(), f.st); err != nil { |
| 116 | + t.Fatal(err) |
| 117 | + } |
| 118 | + receipt := f.receipt(t) |
| 119 | + if got := f.archiveRequests(); got != tc.archiveRequests { |
| 120 | + t.Fatalf("CUDA archive requests = %d, want %d", got, tc.archiveRequests) |
| 121 | + } |
| 122 | + _, runtimeErr := os.Stat(filepath.Join(f.st.installDir, "runtime", "cudart64_13.dll")) |
| 123 | + if tc.note == "" { |
| 124 | + capabilities, _ := receipt["compute_capabilities"].([]any) |
| 125 | + if receipt["source"] != "pinned-cuda-archives" || receipt["acceleration_policy"] != "cuda" || receipt["cuda_device_verified"] != true || |
| 126 | + len(capabilities) != 2 || receipt["recipe_sha256"] != llamaArchiveRecipeHash(f.st.plat.Install.CUDAArchives) { |
| 127 | + t.Fatalf("wrong CUDA provenance: %#v", receipt) |
| 128 | + } |
| 129 | + if f.requests[f.vendor.URL] != 0 || runtimeErr != nil { |
| 130 | + t.Fatal("CUDA install ran the vendor installer or lost the CUDA runtime") |
| 131 | + } |
| 132 | + return |
| 133 | + } |
| 134 | + note, _ := receipt["cuda_not_used"].(string) |
| 135 | + if receipt["installer_sha256"] != f.vendor.SHA256 || !strings.Contains(note, tc.note) { |
| 136 | + t.Fatalf("vendor install lacks its provenance or CUDA reason: %#v", receipt) |
| 137 | + } |
| 138 | + if f.requests[f.vendor.URL] != 1 || !os.IsNotExist(runtimeErr) { |
| 139 | + t.Fatal("vendor install skipped its installer or merged CUDA bytes") |
| 140 | + } |
| 141 | + }) |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +// A parent cancellation during the CUDA download ends the install cancelled; it |
| 146 | +// never authorizes the vendor installer. |
| 147 | +func TestLlamaWindowsCUDACancelDoesNotFallBack(t *testing.T) { |
| 148 | + f := newWindowsCUDAFixture(t, "8.9\n", nil, cudaLlamaFixture(10826)) |
| 149 | + model := f.retainModels(t) |
| 150 | + ctx, cancel := context.WithCancel(context.Background()) |
| 151 | + defer cancel() |
| 152 | + f.onArchive = cancel |
| 153 | + if err := f.e.installLlamaApp(ctx, f.st); !errors.Is(err, context.Canceled) { |
| 154 | + t.Fatalf("cancellation lost: %v", err) |
| 155 | + } |
| 156 | + if f.requests[f.vendor.URL] != 0 { |
| 157 | + t.Fatal("cancelled CUDA install ran the vendor installer") |
| 158 | + } |
| 159 | + f.assertNothingPromoted(t, model) |
| 160 | +} |
| 161 | + |
| 162 | +func TestLlamaWindowsCUDARecipe(t *testing.T) { |
| 163 | + mf, _ := buildRegistry("").Get("llamacpp") |
| 164 | + want := []Fetch{ |
| 165 | + {URL: "https://github.com/ggml-org/llama.cpp/releases/download/b10826/llama-b10826-bin-win-cuda-13.3-x64.zip", SHA256: "34549bd7c46717333277d7bfc4c8617a12be0fad33a5253748e59e1085ffda4f"}, |
| 166 | + {URL: "https://github.com/ggml-org/llama.cpp/releases/download/b10826/cudart-llama-bin-win-cuda-13.3-x64.zip", SHA256: "1462a050eb4c684921ba51dcc4cc488a036674c3e73e9945ee705b854808d03e"}, |
| 167 | + } |
| 168 | + if p := mf.Platforms["windows/amd64"]; !reflect.DeepEqual(p.Install.CUDAArchives, want) || p.Install.Fetch == nil { |
| 169 | + t.Fatalf("Windows x64 lost its pinned CUDA build or vendor fallback: %+v", p.Install) |
| 170 | + } |
| 171 | + for key, p := range mf.Platforms { |
| 172 | + if key != "windows/amd64" && len(p.Install.CUDAArchives) > 0 { |
| 173 | + t.Fatalf("%s acquired the Windows x64 CUDA recipe", key) |
| 174 | + } |
| 175 | + } |
| 176 | + if _, reason := llamaInstallSupport("windows", "amd64"); !strings.Contains(reason, "CUDA") { |
| 177 | + t.Fatalf("Windows x64 support reason omits the CUDA policy: %q", reason) |
| 178 | + } |
| 179 | + for _, mutation := range []string{"other-platform", "no-fallback", "unpinned-fallback", "unpinned-archive", "upstream-first"} { |
| 180 | + t.Run(mutation, func(t *testing.T) { |
| 181 | + fresh, _ := buildRegistry("").Get("llamacpp") |
| 182 | + p, key := fresh.Platforms["windows/amd64"], "windows/amd64" |
| 183 | + switch mutation { |
| 184 | + case "other-platform": |
| 185 | + key = "linux/amd64" |
| 186 | + case "no-fallback": |
| 187 | + p.Install.Fetch = nil |
| 188 | + case "unpinned-fallback": |
| 189 | + p.Install.Fetch = &Fetch{URL: p.Install.Fetch.URL} |
| 190 | + case "unpinned-archive": |
| 191 | + p.Install.CUDAArchives[0].SHA256 = "" |
| 192 | + case "upstream-first": |
| 193 | + p.Install.UpstreamFirst = true |
| 194 | + } |
| 195 | + if err := p.validate(key); err == nil { |
| 196 | + t.Fatal("invalid Windows x64 CUDA recipe accepted") |
| 197 | + } |
| 198 | + }) |
| 199 | + } |
| 200 | +} |
0 commit comments