Repository: ollama/ollama
Pull request: ci: fix missing windows zip file

Use 7z compression (better compression rate) if found in path.  That alone isn't sufficient to get us under 2G, so MLX is now split out as a discrete download.  Fix CI so it will fail if artifacts fail to upload.

Changed files:
filename: .github/workflows/release.yaml
patch: @@ -583,11 +583,19 @@ jobs:
           for payload in dist/*.txt dist/*.zip dist/*.tgz dist/*.tar.zst dist/*.exe dist/*.dmg dist/*.ps1 dist/*.sh ; do
             echo "Uploading $payload"
             gh release upload ${GITHUB_REF_NAME} $payload --clobber &
-            pids[$!]=$!
+            pids+=($!)
             sleep 1
           done
           echo "Waiting for uploads to complete"
-          for pid in "${pids[*]}"; do
-            wait $pid
+          failed=0
+          for pid in "${pids[@]}"; do
+            if ! wait $pid; then
+              echo "::error::Upload failed (pid $pid)"
+              failed=1
+            fi
           done
+          if [ $failed -ne 0 ]; then
+            echo "One or more uploads failed"
+            exit 1
+          fi
           echo "done"

filename: docs/windows.mdx
patch: @@ -80,9 +80,13 @@ help you keep up to date.
 
 If you'd like to install or integrate Ollama as a service, a standalone
 `ollama-windows-amd64.zip` zip file is available containing only the Ollama CLI
-and GPU library dependencies for Nvidia. If you have an AMD GPU, also download
-and extract the additional ROCm package `ollama-windows-amd64-rocm.zip` into the
-same directory. This allows for embedding Ollama in existing applications, or
+and GPU library dependencies for Nvidia. Depending on your hardware, you may also
+need to download and extract additional packages into the same directory:
+
+- **AMD GPU**: `ollama-windows-amd64-rocm.zip`
+- **MLX (CUDA)**: `ollama-windows-amd64-mlx.zip`
+
+This allows for embedding Ollama in existing applications, or
 running it as a system service via `ollama serve` with tools such as
 [NSSM](https://nssm.cc/).
 

filename: scripts/build_windows.ps1
patch: @@ -425,28 +425,93 @@ function installer {
     if ($LASTEXITCODE -ne 0) { exit($LASTEXITCODE)}
 }
 
-function zip {
-    if (Test-Path -Path "${script:SRC_DIR}\dist\windows-amd64") {
-        if (Test-Path -Path "${script:SRC_DIR}\dist\windows-amd64\lib\ollama\rocm") {
-            Write-Output "Generating stand-alone distribution zip file ${script:SRC_DIR}\dist\ollama-windows-amd64-rocm.zip"
-            # Temporarily adjust paths so we can retain the same directory structure
-            Remove-Item -ea 0 -r "${script:SRC_DIR}\dist\windows-amd64-rocm"
-            mkdir -Force -path "${script:SRC_DIR}\dist\windows-amd64-rocm\lib\ollama"
-            Write-Output "Extract this ROCm zip file to the same location where you extracted ollama-windows-amd64.zip" > "${script:SRC_DIR}\dist\windows-amd64-rocm\README.txt"
-            Move-Item -path "${script:SRC_DIR}\dist\windows-amd64\lib\ollama\rocm" -destination "${script:SRC_DIR}\dist\windows-amd64-rocm\lib\ollama" -ErrorAction Stop
-            Compress-Archive -CompressionLevel Optimal -Path "${script:SRC_DIR}\dist\windows-amd64-rocm\*" -DestinationPath "${script:SRC_DIR}\dist\ollama-windows-amd64-rocm.zip" -Force
+function newZipJob($sourceDir, $destZip) {
+    $use7z = [bool](Get-Command 7z -ErrorAction SilentlyContinue)
+    Start-Job -ScriptBlock {
+        param($src, $dst, $use7z)
+        if ($use7z) {
+            & 7z a -tzip -mx=9 -mmt=on $dst "${src}\*"
+            if ($LASTEXITCODE -ne 0) { throw "7z failed with exit code $LASTEXITCODE" }
+        } else {
+            Compress-Archive -CompressionLevel Optimal -Path "${src}\*" -DestinationPath $dst -Force
+        }
+    } -ArgumentList $sourceDir, $destZip, $use7z
+}
+
+function stageComponents($mainDir, $stagingDir, $pattern, $readmePrefix) {
+    $components = Get-ChildItem -Path "${mainDir}\lib\ollama" -Directory -Filter $pattern -ErrorAction SilentlyContinue
+    if ($components) {
+        Remove-Item -ea 0 -r $stagingDir
+        mkdir -Force -path "${stagingDir}\lib\ollama" | Out-Null
+        Write-Output "Extract this ${readmePrefix} zip file to the same location where you extracted ollama-windows-amd64.zip" > "${stagingDir}\README_${readmePrefix}.txt"
+        foreach ($dir in $components) {
+            Write-Output "  Staging $($dir.Name)"
+            Move-Item -path $dir.FullName -destination "${stagingDir}\lib\ollama\$($dir.Name)"
         }
+        return $true
+    }
+    return $false
+}
 
-        Write-Output "Generating stand-alone distribution zip file ${script:SRC_DIR}\dist\ollama-windows-amd64.zip"
-        Compress-Archive -CompressionLevel Optimal -Path "${script:SRC_DIR}\dist\windows-amd64\*" -DestinationPath "${script:SRC_DIR}\dist\ollama-windows-amd64.zip" -Force
-        if (Test-Path -Path "${script:SRC_DIR}\dist\windows-amd64-rocm") {
-            Move-Item -destination "${script:SRC_DIR}\dist\windows-amd64\lib\ollama\rocm" -path "${script:SRC_DIR}\dist\windows-amd64-rocm\lib\ollama\rocm" -ErrorAction Stop
+function restoreComponents($mainDir, $stagingDir) {
+    if (Test-Path -Path "${stagingDir}\lib\ollama") {
+        foreach ($dir in (Get-ChildItem -Path "${stagingDir}\lib\ollama" -Directory)) {
+            Move-Item -path $dir.FullName -destination "${mainDir}\lib\ollama\$($dir.Name)"
         }
     }
+    Remove-Item -ea 0 -r $stagingDir
+}
+
+function zip {
+    $jobs = @()
+    $distDir = "${script:SRC_DIR}\dist"
+    $amd64Dir = "${distDir}\windows-amd64"
+
+    # Remove any stale zip files before starting
+    Remove-Item -ea 0 "${distDir}\ollama-windows-*.zip"
+
+    try {
+        if (Test-Path -Path $amd64Dir) {
+            # Stage ROCm into its own directory for independent compression
+            if (stageComponents $amd64Dir "${distDir}\windows-amd64-rocm" "rocm*" "ROCm") {
+                Write-Output "Generating ${distDir}\ollama-windows-amd64-rocm.zip"
+                $jobs += newZipJob "${distDir}\windows-amd64-rocm" "${distDir}\ollama-windows-amd64-rocm.zip"
+            }
+
+            # Stage MLX into its own directory for independent compression
+            if (stageComponents $amd64Dir "${distDir}\windows-amd64-mlx" "mlx_*" "MLX") {
+                Write-Output "Generating ${distDir}\ollama-windows-amd64-mlx.zip"
+                $jobs += newZipJob "${distDir}\windows-amd64-mlx" "${distDir}\ollama-windows-amd64-mlx.zip"
+            }
+
+            # Compress the main amd64 zip (without rocm/mlx)
+            Write-Output "Generating ${distDir}\ollama-windows-amd64.zip"
+            $jobs += newZipJob $amd64Dir "${distDir}\ollama-windows-amd64.zip"
+        }
 
-    if (Test-Path -Path "${script:SRC_DIR}\dist\windows-arm64") {
-        Write-Output "Generating stand-alone distribution zip file ${script:SRC_DIR}\dist\ollama-windows-arm64.zip"
-        Compress-Archive -CompressionLevel Optimal -Path "${script:SRC_DIR}\dist\windows-arm64\*" -DestinationPath "${script:SRC_DIR}\dist\ollama-windows-arm64.zip" -Force
+        if (Test-Path -Path "${distDir}\windows-arm64") {
+            Write-Output "Generating ${distDir}\ollama-windows-arm64.zip"
+            $jobs += newZipJob "${distDir}\windows-arm64" "${distDir}\ollama-windows-arm64.zip"
+        }
+
+        if ($jobs.Count -gt 0) {
+            Write-Output "Waiting for $($jobs.Count) parallel zip jobs..."
+            $jobs | Wait-Job | Out-Null
+            $failed = $false
+            foreach ($job in $jobs) {
+                if ($job.State -eq 'Failed') {
+                    Write-Error "Zip job failed: $($job.ChildJobs[0].JobStateInfo.Reason)"
+                    $failed = $true
+                }
+                Receive-Job $job
+                Remove-Job $job
+            }
+            if ($failed) { throw "One or more zip jobs failed" }
+        }
+    } finally {
+        # Always restore staged components back into the main tree
+        restoreComponents $amd64Dir "${distDir}\windows-amd64-rocm"
+        restoreComponents $amd64Dir "${distDir}\windows-amd64-mlx"
     }
 }