install-cudnn.ps1 : version la plus récente lue dans l'index NVIDIA, archive cuda13 téléchargée avec reprise et vérifiée par SHA256, licence NVIDIA affichée et acceptée par l'utilisateur, installation sans droits admin dans %LOCALAPPDATA%\SEQUA\sdk\cudnn, variable utilisateur CUDNN_PATH. Refuse de remplacer un dossier de destination non vide qui n'est pas une installation cuDNN. Fonctions de téléchargement et d'empreinte factorisées dans scripts/lib/SequaDownload.ps1 (install-cuda.ps1 inchangé fonctionnellement). Écrit par un subagent (DeepSeek V4.1 Flash), relu et corrigé.
364 lines
15 KiB
PowerShell
364 lines
15 KiB
PowerShell
# SPDX-License-Identifier: MPL-2.0
|
|
# Messages en ASCII : compatible Windows PowerShell 5.1 sans BOM.
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Telecharge (avec reprise) et installe cuDNN NVIDIA pour Windows x86_64.
|
|
|
|
.DESCRIPTION
|
|
Utilise pour le prototype prototypes/gpu_interop (etape 3 : inference ONNX Runtime
|
|
sur GPU). cuDNN est un composant PROPRIETAIRE NVIDIA : il reste un plugin facultatif
|
|
de SEQUA (docs, par. 10.2). Le script n'accepte la licence qu'avec -AcceptLicense ou
|
|
apres confirmation interactive (O/N) ; il ne modifie jamais le PATH.
|
|
|
|
Etapes :
|
|
1. detecte la majeure CUDA via la variable machine CUDA_PATH (ou -CudaMajor) ;
|
|
2. lit l'index officiel NVIDIA et, si -Version est absent, prend la version la plus
|
|
recente (tri par version, pas par chaine : 9.28 > 9.9) ;
|
|
3. lit le manifeste redistrib_<version>.json et l'entree Windows x86_64 / cuda<maj> ;
|
|
4. telecharge l'archive (~1,3 Go) avec reprise : en cas de coupure, relancer le script
|
|
repart du dernier octet recu (curl -C -) ;
|
|
5. verifie le SHA256 (le fichier est supprime s'il est faux) ;
|
|
6. extrait dans un dossier temporaire a cote de la destination, affiche la licence
|
|
extraite et ses premieres lignes, puis demande confirmation (O/N) ;
|
|
7. installe en aplatissant le dossier racine de l'archive : <InstallDir>\bin,
|
|
\include, \lib ;
|
|
8. sauf -NoEnv, definit la variable d'environnement UTILISATEUR CUDNN_PATH (aucun
|
|
droit administrateur requis) et n'ajoute PAS au PATH.
|
|
|
|
A lancer dans une fenetre PowerShell externe (telechargement long).
|
|
|
|
.PARAMETER Version
|
|
Version cuDNN (ex. 9.28.0). Defaut : la plus recente de l'index NVIDIA.
|
|
|
|
.PARAMETER CudaMajor
|
|
Majeure CUDA du paquet a installer (12 ou 13). Defaut : deduite de CUDA_PATH machine.
|
|
|
|
.PARAMETER InstallDir
|
|
Dossier d'installation. Defaut : %LOCALAPPDATA%\SEQUA\sdk\cudnn\<version>_cuda<maj>.
|
|
|
|
.PARAMETER CacheDir
|
|
Dossier de telechargement. Defaut : %LOCALAPPDATA%\SEQUA\downloads\cudnn.
|
|
|
|
.PARAMETER ArchivePath
|
|
Installe depuis une archive locale deja telechargee, sans acces au manifeste. Aucune
|
|
empreinte connue : la verification SHA256 est clairement signalee comme ignoree.
|
|
|
|
.PARAMETER DryRun
|
|
Affiche ce qui serait fait (URL, SHA256, taille, destination, etat du cache) sans rien
|
|
telecharger, extraire ni installer.
|
|
|
|
.PARAMETER DownloadOnly
|
|
Telecharge et verifie l'archive sans l'extraire ni l'installer.
|
|
|
|
.PARAMETER AcceptLicense
|
|
Accepte la licence cuDNN sans demande interactive.
|
|
|
|
.PARAMETER NoEnv
|
|
N'ecrit pas la variable d'environnement utilisateur CUDNN_PATH (utile pour les tests).
|
|
|
|
.PARAMETER Force
|
|
Reinstalle meme si une DLL cudnn64_*.dll est deja presente dans la destination.
|
|
|
|
.PARAMETER Help
|
|
Affiche cette aide.
|
|
|
|
.EXAMPLE
|
|
powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1 -DryRun
|
|
|
|
.EXAMPLE
|
|
powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1 -DownloadOnly
|
|
|
|
.EXAMPLE
|
|
powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1
|
|
|
|
.EXAMPLE
|
|
powershell -ExecutionPolicy Bypass -File scripts\install-cudnn.ps1 -ArchivePath C:\cudnn.zip -AcceptLicense -NoEnv -InstallDir C:\tmp\cudnn
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$Version = "",
|
|
[int]$CudaMajor = 0,
|
|
[string]$InstallDir = "",
|
|
[string]$CacheDir = (Join-Path $env:LOCALAPPDATA "SEQUA\downloads\cudnn"),
|
|
[string]$ArchivePath = "",
|
|
[switch]$DryRun,
|
|
[switch]$DownloadOnly,
|
|
[switch]$AcceptLicense,
|
|
[switch]$NoEnv,
|
|
[switch]$Force,
|
|
[Alias("h", "?")]
|
|
[switch]$Help
|
|
)
|
|
|
|
if ($Help) {
|
|
Get-Help $PSCommandPath -Detailed
|
|
exit 0
|
|
}
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
. (Join-Path $PSScriptRoot "lib\SequaDownload.ps1")
|
|
$nvidiaBase = "https://developer.download.nvidia.com/compute/cudnn/redist"
|
|
$maxAttempts = 50
|
|
|
|
# Majeure CUDA depuis CUDA_PATH (machine d'abord : c'est la variable installee par le
|
|
# CUDA Toolkit ; le processus courant peut ne pas encore l'avoir heritee).
|
|
function Get-CudaMajor {
|
|
foreach ($scope in @("Machine", "Process")) {
|
|
$path = [System.Environment]::GetEnvironmentVariable("CUDA_PATH", $scope)
|
|
if ($path -and $path -match 'v([0-9]+)\.[0-9]+') { return [int]$Matches[1] }
|
|
}
|
|
return 0
|
|
}
|
|
|
|
# Versions cuDNN publiees dans l'index, triees par version (9.28 > 9.9), sans doublon.
|
|
function Get-CudnnVersions {
|
|
$index = & curl.exe -sL --fail "$nvidiaBase/"
|
|
if ($LASTEXITCODE -ne 0) { throw "Index cuDNN injoignable : $nvidiaBase/" }
|
|
$index |
|
|
ForEach-Object { if ($_ -match 'redistrib_([0-9]+\.[0-9]+\.[0-9]+)\.json') { $Matches[1] } } |
|
|
Sort-Object { [version]$_ } -Unique
|
|
}
|
|
|
|
# Manifeste officiel de la version : donne chemin relatif, sha256, md5, taille.
|
|
function Get-CudnnManifest([string]$version) {
|
|
$url = "$nvidiaBase/redistrib_$version.json"
|
|
$json = & curl.exe -sL --fail $url
|
|
if ($LASTEXITCODE -ne 0) { throw "Manifeste cuDNN introuvable : $url" }
|
|
($json -join "`n") | ConvertFrom-Json
|
|
}
|
|
|
|
# Extraction ZIP sans Expand-Archive (absent de certains Windows PowerShell 5.1) :
|
|
# .NET en premier, tar.exe de Windows 10+ en repli.
|
|
function Expand-ZipArchive([string]$archive, [string]$destination) {
|
|
New-Item -ItemType Directory -Force -Path $destination | Out-Null
|
|
try {
|
|
Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop
|
|
[System.IO.Compression.ZipFile]::ExtractToDirectory($archive, $destination)
|
|
return
|
|
}
|
|
catch {
|
|
Write-Host " Extraction .NET indisponible : repli sur tar.exe." -ForegroundColor Yellow
|
|
Remove-Item -Recurse -Force $destination -ErrorAction SilentlyContinue
|
|
New-Item -ItemType Directory -Force -Path $destination | Out-Null
|
|
}
|
|
& tar.exe -xf $archive -C $destination
|
|
if ($LASTEXITCODE -ne 0) { throw "Extraction impossible : $archive" }
|
|
}
|
|
|
|
# L'archive cuDNN contient un dossier racine unique : on le detecte pour l'aplatir.
|
|
function Get-ExtractedRoot([string]$dir) {
|
|
$entries = @(Get-ChildItem -Force -LiteralPath $dir)
|
|
if ($entries.Count -eq 1 -and $entries[0].PSIsContainer) { return $entries[0].FullName }
|
|
return $dir
|
|
}
|
|
|
|
# DLL cuDNN a la racine du dossier bin, ou dans un sous-dossier de version CUDA.
|
|
function Find-CudnnDll([string]$root) {
|
|
if (-not (Test-Path -LiteralPath $root)) { return @() }
|
|
@(Get-ChildItem -Path $root -Recurse -File -Filter "cudnn64_*.dll" -ErrorAction SilentlyContinue)
|
|
}
|
|
|
|
# --- Detection de CUDA ------------------------------------------------------
|
|
Write-Step "CUDA installe"
|
|
if ($CudaMajor -gt 0) { $cudaMajor = $CudaMajor } else { $cudaMajor = Get-CudaMajor }
|
|
if ($cudaMajor -gt 0) {
|
|
Write-Host " Majeure CUDA : $cudaMajor (CUDA_PATH)"
|
|
}
|
|
else {
|
|
Write-Host " Majeure CUDA inconnue (CUDA_PATH absent)." -ForegroundColor Yellow
|
|
}
|
|
|
|
$fromArchive = -not [string]::IsNullOrWhiteSpace($ArchivePath)
|
|
if ($fromArchive) {
|
|
if (-not (Test-Path -LiteralPath $ArchivePath -PathType Leaf)) { throw "Archive introuvable : $ArchivePath" }
|
|
$archive = (Resolve-Path -LiteralPath $ArchivePath).Path
|
|
}
|
|
|
|
# --- Version ----------------------------------------------------------------
|
|
if ([string]::IsNullOrWhiteSpace($Version)) {
|
|
if ($fromArchive) {
|
|
if ((Split-Path -Leaf $archive) -match '([0-9]+\.[0-9]+\.[0-9]+)') { $Version = $Matches[1] } else { $Version = "local" }
|
|
}
|
|
else {
|
|
Write-Step "Index cuDNN"
|
|
$versions = @(Get-CudnnVersions)
|
|
if ($versions.Count -eq 0) { throw "Aucune version cuDNN dans l'index $nvidiaBase/." }
|
|
$Version = $versions[-1]
|
|
Write-Host " Derniere version : $Version"
|
|
}
|
|
}
|
|
if ($Version -ne "local" -and $Version -notmatch '^[0-9]+\.[0-9]+\.[0-9]+$') {
|
|
throw "-Version attend une version x.y.z, ex. 9.28.0 (recu : $Version)."
|
|
}
|
|
|
|
# --- Destination ------------------------------------------------------------
|
|
if ([string]::IsNullOrWhiteSpace($InstallDir)) {
|
|
if ($cudaMajor -le 0) { throw "Destination indeterminable : fournir -CudaMajor (CUDA_PATH absent) ou -InstallDir." }
|
|
$InstallDir = Join-Path $env:LOCALAPPDATA "SEQUA\sdk\cudnn\${Version}_cuda${cudaMajor}"
|
|
}
|
|
|
|
# --- Deja installe ----------------------------------------------------------
|
|
$existing = @(Find-CudnnDll $InstallDir)
|
|
if ($existing.Count -gt 0 -and -not $Force) {
|
|
Write-Step "cuDNN deja installe"
|
|
Write-Host " Dossier : $InstallDir" -ForegroundColor Green
|
|
$existing | ForEach-Object { Write-Host " $($_.FullName)" }
|
|
Write-Host "Rien a faire (utiliser -Force pour reinstaller)." -ForegroundColor Green
|
|
exit 0
|
|
}
|
|
|
|
# --- Archive, manifeste, empreinte ------------------------------------------
|
|
if ($fromArchive) {
|
|
$sha256 = $null
|
|
$size = (Get-Item -LiteralPath $archive).Length
|
|
$url = $archive
|
|
$download = $false
|
|
}
|
|
else {
|
|
Write-Step "Manifeste cuDNN $Version"
|
|
$manifest = Get-CudnnManifest $Version
|
|
$key = "cuda$cudaMajor"
|
|
$entry = $manifest.cudnn.'windows-x86_64'.$key
|
|
if (-not $entry) {
|
|
$available = ((Get-Member -InputObject $manifest.cudnn.'windows-x86_64' -MemberType NoteProperty).Name) -join ", "
|
|
throw "Pas de binaire cuDNN Windows x86_64 pour $key (disponibles : $available)."
|
|
}
|
|
$url = "$nvidiaBase/$($entry.relative_path)"
|
|
$sha256 = $entry.sha256.ToUpperInvariant()
|
|
$size = [int64]$entry.size
|
|
$archive = Join-Path $CacheDir (Split-Path -Leaf $entry.relative_path)
|
|
$download = $true
|
|
}
|
|
|
|
Write-Step "cuDNN $Version (cuda$cudaMajor)"
|
|
Write-Host " URL : $url"
|
|
if ($sha256) { Write-Host " SHA256 : $sha256" }
|
|
else { Write-Host " SHA256 : inconnu (archive locale : verification d'empreinte ignoree)" -ForegroundColor Yellow }
|
|
Write-Host (" Taille : {0:N0} octets ({1:N1} Mo)" -f $size, ($size / 1MB))
|
|
Write-Host " Archive : $archive"
|
|
Write-Host " Destination: $InstallDir"
|
|
|
|
if ($DryRun) {
|
|
if ($download) {
|
|
$partial = "$archive.part"
|
|
if (Test-Path $archive) { Write-Host " Archive deja telechargee dans le cache." }
|
|
elseif (Test-Path $partial) { Write-Host (" Telechargement partiel : {0:N0} octets." -f (Get-Item $partial).Length) }
|
|
}
|
|
Write-Host "DryRun : rien n'a ete telecharge, extrait ni installe." -ForegroundColor Yellow
|
|
exit 0
|
|
}
|
|
|
|
# --- Telechargement et verification -----------------------------------------
|
|
if ($download) {
|
|
New-Item -ItemType Directory -Force -Path $CacheDir | Out-Null
|
|
if (Test-Path $archive) {
|
|
Write-Step "Archive deja presente dans le cache"
|
|
}
|
|
else {
|
|
Assert-FreeSpace -Path $CacheDir -Needed ($size * 3)
|
|
Write-Step "Telechargement avec reprise"
|
|
Invoke-ResumableDownload $url $archive $size $maxAttempts
|
|
}
|
|
|
|
Write-Step "Verification SHA256"
|
|
$hash = Get-FileDigest -Path $archive -Algorithm SHA256
|
|
if ($hash -ne $sha256) {
|
|
Remove-Item $archive -Force
|
|
throw "SHA256 incorrect ($hash) : fichier supprime, relancer le script pour le retelecharger."
|
|
}
|
|
Write-Host " OK"
|
|
}
|
|
|
|
if ($DownloadOnly) {
|
|
Write-Host "Archive prete : $archive" -ForegroundColor Green
|
|
exit 0
|
|
}
|
|
|
|
# --- Extraction dans un dossier temporaire a cote de la destination ---------
|
|
Write-Step "Extraction"
|
|
$parent = Split-Path -Parent $InstallDir
|
|
if (-not $parent) { $parent = "." }
|
|
New-Item -ItemType Directory -Force -Path $parent | Out-Null
|
|
$temp = Join-Path $parent ((Split-Path -Leaf $InstallDir) + ".tmp-" + [guid]::NewGuid().ToString("N").Substring(0, 8))
|
|
if (Test-Path $temp) { Remove-Item -Recurse -Force $temp }
|
|
Expand-ZipArchive $archive $temp
|
|
$root = Get-ExtractedRoot $temp
|
|
Write-Host " Archive : $archive"
|
|
Write-Host " Temporaire : $temp"
|
|
Write-Host " Racine archive : $root"
|
|
|
|
# --- Licence ----------------------------------------------------------------
|
|
Write-Step "Licence cuDNN (proprietaire NVIDIA)"
|
|
$license = Get-ChildItem -Path $temp -Recurse -File -Filter "LICENSE.txt" -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
if (-not $license) {
|
|
Remove-Item -Recurse -Force $temp
|
|
throw "LICENSE.txt introuvable dans l'archive extraite : installation refusee."
|
|
}
|
|
Write-Host " Licence : $($license.FullName)"
|
|
Get-Content -LiteralPath $license.FullName -TotalCount 8 | ForEach-Object { Write-Host " $_" }
|
|
Write-Host " cuDNN est proprietaire NVIDIA : son usage est regi par cette licence." -ForegroundColor Yellow
|
|
|
|
if (-not $AcceptLicense) {
|
|
$answer = Read-Host "Accepter la licence cuDNN et installer ? (O/N)"
|
|
if ($answer -notmatch '^(o|oui|y|yes)$') {
|
|
Remove-Item -Recurse -Force $temp
|
|
Write-Host "Licence refusee : aucune installation, dossier temporaire supprime." -ForegroundColor Yellow
|
|
exit 2
|
|
}
|
|
}
|
|
|
|
# --- Installation (aplatir le dossier racine de l'archive) -------------------
|
|
Write-Step "Installation"
|
|
# On ne remplace qu'un dossier vide ou une installation cuDNN precedente : jamais un
|
|
# dossier quelconque passe par erreur dans -InstallDir.
|
|
if (Test-Path -LiteralPath $InstallDir) {
|
|
$isEmpty = @(Get-ChildItem -Force -LiteralPath $InstallDir).Count -eq 0
|
|
$isCudnn = Test-Path -LiteralPath (Join-Path $InstallDir "include\cudnn.h")
|
|
if (-not $isEmpty -and -not $isCudnn) {
|
|
Remove-Item -Recurse -Force $temp
|
|
throw "$InstallDir existe, n'est pas vide et ne contient pas d'installation cuDNN : rien n'a ete supprime. Choisir un autre -InstallDir."
|
|
}
|
|
Remove-Item -Recurse -Force -LiteralPath $InstallDir
|
|
}
|
|
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
|
|
Get-ChildItem -Force -LiteralPath $root | ForEach-Object { Move-Item -LiteralPath $_.FullName -Destination $InstallDir }
|
|
Remove-Item -Recurse -Force $temp
|
|
Write-Host " Installe dans : $InstallDir"
|
|
|
|
if (-not $NoEnv) {
|
|
[System.Environment]::SetEnvironmentVariable("CUDNN_PATH", $InstallDir, "User")
|
|
Write-Host " CUDNN_PATH (utilisateur) = $InstallDir"
|
|
}
|
|
|
|
# --- Verification -----------------------------------------------------------
|
|
Write-Step "Verification"
|
|
$dlls = @(Find-CudnnDll $InstallDir)
|
|
$header = Join-Path $InstallDir "include\cudnn.h"
|
|
if ($dlls.Count -gt 0) {
|
|
Write-Host " DLL cudnn64_*.dll :" -ForegroundColor Green
|
|
$dlls | ForEach-Object { Write-Host " $($_.FullName)" }
|
|
}
|
|
else {
|
|
Write-Host " Aucune cudnn64_*.dll trouvee sous $InstallDir" -ForegroundColor Red
|
|
}
|
|
if (Test-Path $header) { Write-Host " En-tete : $header" -ForegroundColor Green }
|
|
else { Write-Host " En-tete include\cudnn.h introuvable." -ForegroundColor Red }
|
|
|
|
if ($dlls.Count -eq 0 -or -not (Test-Path $header)) {
|
|
Write-Host ""
|
|
Write-Host "Installation incomplete : verifier l'archive extraite." -ForegroundColor Red
|
|
exit 1
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "cuDNN $Version (cuda$cudaMajor) installe : $InstallDir" -ForegroundColor Green
|
|
if (-not $NoEnv) {
|
|
Write-Host "CUDNN_PATH (utilisateur) defini : ouvrir un NOUVEAU terminal pour l'utiliser."
|
|
}
|
|
else {
|
|
Write-Host "CUDNN_PATH non defini (-NoEnv)."
|
|
}
|
|
Write-Host "Ce script n'ajoute rien au PATH : pointer l'application sur bin\ (ou copier cudnn64_*.dll a cote de l'executable)."
|