Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 45 additions & 30 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1904,7 +1904,7 @@ jobs:
env:
# The CUDA redist manifest (redistrib_<this>.json) the archives are verified against; empty
# takes the newest 13.4.x manifest the redist index lists (the run prints which).
CUDA_REDIST_MANIFEST: ""
CUDA_REDIST_MANIFEST: "13.4.3"
SCCACHE_WEBDAV_TOKEN: ${{ secrets.DEPOT_TOKEN }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
Expand All @@ -1930,24 +1930,27 @@ jobs:
$ErrorActionPreference = "Stop"
$cuda = "C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v13.4"
$base = "https://developer.download.nvidia.com/compute/cuda/redist"
# Components only: the archive name (component version) and its SHA-256 both come from
# the redist manifest below, so they cannot disagree. A hand-written archive name checked
# against the newest manifest's hash did exactly that in run 38808518362 (cuda_crt 13.4.59
# from upstream's list against the 13.4.3 manifest's cuda_crt).
$parts = @(
"cuda_crt/cuda_crt-windows-x86_64-13.4.59",
"cuda_cudart/cuda_cudart-windows-x86_64-13.4.49",
"cuda_nvcc/cuda_nvcc-windows-x86_64-13.4.59",
"cuda_nvrtc/cuda_nvrtc-windows-x86_64-13.4.59",
"libcublas/libcublas-windows-x86_64-13.7.0.27",
"libnvvm/libnvvm-windows-x86_64-13.4.59",
"cuda_nvtx/cuda_nvtx-windows-x86_64-13.4.49",
"cuda_profiler_api/cuda_profiler_api-windows-x86_64-13.4.49",
"visual_studio_integration/visual_studio_integration-windows-x86_64-13.4.49",
"cccl/cccl-windows-x86_64-13.3.4.2.1"
"cuda_crt",
"cuda_cudart",
"cuda_nvcc",
"cuda_nvrtc",
"libcublas",
"libnvvm",
"cuda_nvtx",
"cuda_profiler_api",
"visual_studio_integration",
"cccl"
)
New-Item -ItemType Directory -Force -Path $cuda | Out-Null
# Every archive is checked against the SHA-256 NVIDIA publishes for it in the redist
# manifest of this toolkit (redistrib_<version>.json next to the archives): a truncated or
# swapped download fails here, not at link time. CUDA_REDIST_MANIFEST names the manifest
# version; it moves with the toolkit (TODO.md, delivery roadmap, item 3: the hashes are to
# be pinned into this file once a machine that can reach NVIDIA's host has read them).
# swapped download fails here, not at link time. CUDA_REDIST_MANIFEST pins the manifest
# version, and with it every component version (TODO.md, delivery roadmap, item 3).
$manifestVersion = $env:CUDA_REDIST_MANIFEST
if (-not $manifestVersion) {
# Unpinned: the newest 13.4.x manifest the redist index lists (the index is NVIDIA's
Expand All @@ -1960,13 +1963,14 @@ jobs:
$manifestUrl = "$base/redistrib_$manifestVersion.json"
Write-Host "Reading $manifestUrl"
$manifest = Invoke-RestMethod -Uri $manifestUrl
foreach ($p in $parts) {
$dir, $name = $p.Split("/")
$url = "$base/$dir/windows-x86_64/$name-archive.zip"
foreach ($dir in $parts) {
$entry = $manifest.$dir.'windows-x86_64'
if (-not $entry -or -not $entry.relative_path -or -not $entry.sha256) { throw "the redist manifest $manifestUrl has no windows-x86_64 relative_path/sha256 for $dir" }
$url = "$base/$($entry.relative_path)"
$name = [System.IO.Path]::GetFileNameWithoutExtension($entry.relative_path) -replace '-archive$', ''
$zip = "$env:RUNNER_TEMP\$name.zip"
$expected = $manifest.$dir.'windows-x86_64'.sha256
if (-not $expected) { throw "the redist manifest $manifestUrl has no windows-x86_64 sha256 for $dir" }
Write-Host "Downloading $url (sha256 $expected)"
$expected = $entry.sha256
Write-Host "Downloading $url ($dir $($manifest.$dir.version), sha256 $expected)"
Invoke-WebRequest -Uri $url -OutFile $zip
$actual = (Get-FileHash -Algorithm SHA256 $zip).Hash.ToLowerInvariant()
if ($actual -ne $expected.ToLowerInvariant()) { throw "$name-archive.zip SHA-256 $actual does not match the manifest's $expected" }
Expand Down Expand Up @@ -2415,14 +2419,21 @@ jobs:
# Version and installer arguments as in upstream's release.yml (windows, vulkan arm64).
run: |
curl.exe -fSL -o "$env:RUNNER_TEMP\VulkanSDK-Installer.exe" "https://sdk.lunarg.com/sdk/download/$env:VULKAN_VERSION/windows/vulkansdk-windows-X64-$env:VULKAN_VERSION.exe"
# Verified against the SHA-256 LunarG publishes for the installer (sdk/sha256/...; pinned
# into VULKAN_SDK_SHA256 once read, empty takes the published value). TODO.md, delivery
# roadmap, item 3.
$expected = $env:VULKAN_SDK_SHA256
if (-not $expected) { $expected = ((Invoke-WebRequest -Uri "https://sdk.lunarg.com/sdk/sha256/$env:VULKAN_VERSION/windows/vulkansdk-windows-X64-$env:VULKAN_VERSION.exe.txt" -UseBasicParsing).Content -split '\s+')[0] }
if (-not $expected) { throw "no SHA-256 for the Vulkan SDK installer (neither pinned nor published)" }
# Verified against the SHA-256 LunarG publishes for the installer (its download API serves
# it at sdk/sha/<version>/<os>/<file>.txt -- the first run asked sdk/sha256/ and got a 404,
# run 38808518362; pinned into VULKAN_SDK_SHA256 once read, empty takes the published value).
# Fetched with curl.exe as text (Invoke-WebRequest's byte[] Content, see the OpenVINO job).
# TODO.md, delivery roadmap, item 3.
$actual = (Get-FileHash -Algorithm SHA256 "$env:RUNNER_TEMP\VulkanSDK-Installer.exe").Hash.ToLowerInvariant()
if ($actual -ne $expected.ToLowerInvariant()) { throw "VulkanSDK-Installer.exe SHA-256 $actual does not match $expected" }
$expected = $env:VULKAN_SDK_SHA256
if (-not $expected) {
$shaUrl = "https://sdk.lunarg.com/sdk/sha/$env:VULKAN_VERSION/windows/vulkansdk-windows-X64-$env:VULKAN_VERSION.exe.txt"
$published = curl.exe -fsSL $shaUrl
if ($LASTEXITCODE -ne 0 -or -not $published) { throw "no SHA-256 for the Vulkan SDK installer: VULKAN_SDK_SHA256 is empty and $shaUrl did not answer (the downloaded installer hashes to $actual -- verify it on a trusted machine and pin it)" }
$expected = (($published -join ' ') -split '\s+')[0]
}
Write-Host "VulkanSDK-Installer.exe SHA-256 actual=$actual expected=$expected"
if ($actual -ne $expected.ToLowerInvariant()) { throw "VulkanSDK-Installer.exe SHA-256 $actual does not match the published $expected" }
& "$env:RUNNER_TEMP\VulkanSDK-Installer.exe" --accept-licenses --default-answer --confirm-command install com.lunarg.vulkan.arm64
if (-not (Test-Path "C:\VulkanSDK\$env:VULKAN_VERSION")) { throw "Vulkan SDK $env:VULKAN_VERSION was not installed" }
Add-Content $env:GITHUB_ENV "VULKAN_SDK=C:\VulkanSDK\$env:VULKAN_VERSION"
Expand Down Expand Up @@ -2529,12 +2540,16 @@ jobs:
Invoke-WebRequest -Uri $url -OutFile "$env:RUNNER_TEMP\openvino.zip"
# Verified against the .sha256 the toolkit server publishes next to the archive (pinned
# into OPENVINO_WINDOWS_SHA256 once read; empty takes the published value). TODO.md,
# delivery roadmap, item 3.
# delivery roadmap, item 3. The .sha256 is fetched with curl.exe as text, like the Linux
# job: Invoke-WebRequest hands an octet-stream response over as byte[], and -split on a
# byte array stringifies the bytes in decimal -- the first run compared against "53",
# the ASCII code of the hash's first character (run 38808518362).
$expected = $env:OPENVINO_WINDOWS_SHA256
if (-not $expected) { $expected = ((Invoke-WebRequest -Uri "$url.sha256" -UseBasicParsing).Content -split '\s+')[0] }
if (-not $expected) { $expected = ((curl.exe -fsSL "$url.sha256") -join ' ' -split '\s+')[0] }
if (-not $expected) { throw "no SHA-256 for $url (neither pinned nor published)" }
$actual = (Get-FileHash -Algorithm SHA256 "$env:RUNNER_TEMP\openvino.zip").Hash.ToLowerInvariant()
if ($actual -ne $expected.ToLowerInvariant()) { throw "openvino.zip SHA-256 $actual does not match $expected" }
Write-Host "openvino.zip SHA-256 actual=$actual expected=$expected"
if ($actual -ne $expected.ToLowerInvariant()) { throw "openvino.zip SHA-256 $actual does not match the published $expected" }
Expand-Archive -Path "$env:RUNNER_TEMP\openvino.zip" -DestinationPath "C:\openvino" -Force
# The archive extracts into a nested versioned folder; point OpenVINO_DIR at its runtime/cmake.
$root = (Get-ChildItem "C:\openvino" -Directory | Select-Object -First 1).FullName
Expand Down
13 changes: 9 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -555,10 +555,15 @@ To change the CUDA version, update the following places:
[`docs/benchmarks/gpu-module-jars.md`](docs/benchmarks/gpu-module-jars.md)). Server applications run
on exactly those GPUs, which is why this deviates from upstream, who ship to RTX owners and say in
a comment that they rely on the default. On a bump, re-read ggml's list at the new tag and
**extend** ours rather than dropping it. **Copy that list from upstream's
`.github/actions/windows-setup-cuda/action.yml` at the pinned `GIT_TAG`** — the component versions
differ per component (cuBLAS and CCCL have their own numbering) and cannot be derived from the CUDA
version. (This replaced `Jimver/cuda-toolkit`, which never shipped 13.4.)
**extend** ours rather than dropping it. **The component set follows upstream's
`.github/actions/windows-setup-cuda/action.yml` at the pinned `GIT_TAG`, the component versions
follow NVIDIA's redist manifest** (`CUDA_REDIST_MANIFEST`, e.g. `13.4.3` = `redistrib_13.4.3.json`):
the step reads each component's `relative_path` and `sha256` from the manifest, so the archive it
downloads and the hash it checks cannot name two different versions. They did once -- the first
full run of 6.0.0 (38808518362) paired upstream's hand-written `cuda_crt-…-13.4.59` with the newest
manifest's hash for another `cuda_crt` and failed on the first archive. Component versions differ
per component (cuBLAS and CCCL have their own numbering), which is why the manifest and not the
CUDA version decides them. (This replaced `Jimver/cuda-toolkit`, which never shipped 13.4.)
3. **Only for a major bump:** the backend name `cuda13` — in `llama/CMakeLists.txt`
(`JLLAMA_BACKEND`), `.github/natives.csv` (the two `cuda13-*` rows), the generated pom executions
(`check-natives.py pom`), the build jobs' artifact names, `LlamaLoader.MODULE_BACKENDS` and the
Expand Down
21 changes: 15 additions & 6 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,21 @@ sccache archives (Linux and Windows) against pinned SHA-256s; the CUDA redist ar
NVIDIA's own manifest, the OpenVINO archives and the Vulkan arm64 installer against the checksum
each vendor publishes next to the file.)* Still open, each with the reason:

- [ ] **Pin the vendor checksums into `publish.yml`** (`CUDA_REDIST_MANIFEST`, `OPENVINO_LINUX_SHA256`,
`OPENVINO_WINDOWS_SHA256`, `VULKAN_SDK_SHA256`): the steps verify against the vendor's published
value when the variable is empty and print it; the session that wrote them could not reach
`developer.download.nvidia.com`, `storage.openvinotoolkit.org` or `sdk.lunarg.com` (proxy 403),
so the first Publish run after this lands is where the values are read off the log and pinned.
Until then a swapped archive on the vendor host would pass; a truncated or wrong download does not.
- [ ] **Pin the vendor checksums into `publish.yml`** (`OPENVINO_LINUX_SHA256`, `OPENVINO_WINDOWS_SHA256`,
`VULKAN_SDK_SHA256`; `CUDA_REDIST_MANIFEST` is pinned to `13.4.3` since the first run printed
it): the steps verify against the vendor's published value when the variable is empty and print
both values in full; the session that wrote them could not reach `developer.download.nvidia.com`,
`storage.openvinotoolkit.org` or `sdk.lunarg.com` (proxy 403), so a Publish run is where the
values are read off the log and pinned. Until then a swapped archive on the vendor host would
pass; a truncated or wrong download does not. **What the first full run (38808518362) taught,
all three Windows steps red on their install:** `Invoke-WebRequest` returns an octet-stream
`.sha256` as `byte[]`, and `-split` on a byte array stringifies the bytes in decimal, so the
OpenVINO step compared against `53` (ASCII `'5'`, the hash's first character) -- the published
files are fetched with `curl.exe` as text now, like the Linux job; LunarG's checksum endpoint is
`sdk/sha/<version>/<os>/<file>.txt`, not `sdk/sha256/` (404), and a failed fetch now names the
measured hash so it can be pinned after a check on a trusted machine instead of costing another
run; and the CUDA archive names come from the manifest (`relative_path`) instead of upstream's
hand-written list, whose `cuda_crt 13.4.59` was not the `cuda_crt` the newest manifest hashes.
- [ ] **The oneAPI Windows installer has no checksum endpoint** Intel publishes next to the file
(`registrationcenter-download.intel.com/.../intel-deep-learning-essentials-2025.3.3.18_offline.exe`);
pinning needs one download of the 1.6 GB installer on a machine that can reach it, then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,19 @@ void rawCompletionAndItsStream() throws IOException {
void tokenizeAndDetokenizeRoundTrip() throws IOException {
int[] tokens = model.tokenize("Hello", false);
assertThat(tokens.length, greaterThan(0));
assertEquals("Hello", model.detokenize(tokens));
// A SentencePiece tokenizer (the CI draft model) renders the first piece's word-boundary
// marker as a leading space, " Hello"; a byte-level tokenizer (the GPT-2 test fixture)
// gives "Hello". The round trip is the text up to that boundary.
String detokenized = model.detokenize(tokens);
assertEquals("Hello", detokenized.trim());
JsonNode json = JSON.readTree(model.tokenizeJson("Hello", false, true));
assertEquals(tokens.length, json.path("tokens").size());
assertArrayEquals(tokens, JSON.convertValue(json.path("tokens").findValues("id"), int[].class));
StringBuilder pieces = new StringBuilder();
for (JsonNode piece : json.path("tokens").findValues("piece")) {
pieces.append(piece.asText());
}
assertEquals(detokenized, pieces.toString());
assertTrue(JSON.readTree(model.detokenizeJson(tokens)).has("content"));
}

Expand Down
Loading