291 lines
11 KiB
PowerShell
291 lines
11 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ReleaseRoot,
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidatePattern("^[a-f0-9]{64}$")]
|
|
[string]$ExpectedWheelSha256,
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidatePattern("^[A-Za-z0-9._-]{1,96}$")]
|
|
[string]$RunId,
|
|
[ValidateRange(0, 5000)]
|
|
[int]$MaximumImages = 0,
|
|
[string]$DatasetRoot = "D:\NDC_MISSIONCORE\datasets\coco-2017-val",
|
|
[string]$OutputRoot = "D:\NDC_MISSIONCORE\runtime\results\m48t-risk-quality"
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$ProgressPreference = "SilentlyContinue"
|
|
|
|
function Assert-LastExitCode([string]$Operation) {
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "$Operation failed with exit code $LASTEXITCODE"
|
|
}
|
|
}
|
|
|
|
function Get-Sha256([string]$Path) {
|
|
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
}
|
|
|
|
function Resolve-DDirectory([string]$Path, [string]$Label, [bool]$Create) {
|
|
if ($Create -and -not (Test-Path -LiteralPath $Path)) {
|
|
$null = New-Item -ItemType Directory -Path $Path
|
|
}
|
|
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
|
|
if (
|
|
-not $item.PSIsContainer -or
|
|
($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -or
|
|
[IO.Path]::GetPathRoot($item.FullName).TrimEnd("\") -ine "D:"
|
|
) {
|
|
throw "$Label must be a real D: directory"
|
|
}
|
|
return $item.FullName
|
|
}
|
|
|
|
function Assert-RegularFile([string]$Path, [string]$Label) {
|
|
$item = Get-Item -LiteralPath (Resolve-Path -LiteralPath $Path).Path -Force
|
|
if ($item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)) {
|
|
throw "$Label must be a regular file"
|
|
}
|
|
return $item.FullName
|
|
}
|
|
|
|
function Convert-ToDockerPath([string]$Path) {
|
|
return $Path.Replace("\", "/")
|
|
}
|
|
|
|
function Ensure-PinnedMirrorFile(
|
|
[string]$Path,
|
|
[string]$Url,
|
|
[long]$ExpectedLength,
|
|
[string]$ExpectedSha256,
|
|
[string]$Label
|
|
) {
|
|
if (Test-Path -LiteralPath $Path -PathType Leaf) {
|
|
if (
|
|
(Get-Item -LiteralPath $Path).Length -ne $ExpectedLength -or
|
|
(Get-Sha256 $Path) -cne $ExpectedSha256
|
|
) {
|
|
Remove-Item -LiteralPath $Path -Force
|
|
}
|
|
}
|
|
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
|
$partial = $Path + ".partial"
|
|
if (Test-Path -LiteralPath $partial) { Remove-Item -LiteralPath $partial -Force }
|
|
& curl.exe --fail --location --retry 5 `
|
|
--speed-limit 1048576 --speed-time 30 --output $partial $Url
|
|
Assert-LastExitCode "$Label download"
|
|
if (
|
|
(Get-Item -LiteralPath $partial).Length -ne $ExpectedLength -or
|
|
(Get-Sha256 $partial) -cne $ExpectedSha256
|
|
) {
|
|
throw "$Label length or SHA-256 changed"
|
|
}
|
|
Move-Item -LiteralPath $partial -Destination $Path
|
|
}
|
|
if (
|
|
(Get-Item -LiteralPath $Path).Length -ne $ExpectedLength -or
|
|
(Get-Sha256 $Path) -cne $ExpectedSha256
|
|
) {
|
|
throw "$Label length or SHA-256 changed"
|
|
}
|
|
}
|
|
|
|
function Get-Container([string]$Name) {
|
|
$rows = @((& docker inspect $Name) | ConvertFrom-Json)
|
|
Assert-LastExitCode "Docker inspection for $Name"
|
|
if ($rows.Count -ne 1) { throw "Container identity for $Name is not unique" }
|
|
return $rows[0]
|
|
}
|
|
|
|
if ($env:COMPUTERNAME -cne "DESKTOP-OPJ8J04") {
|
|
throw "M48T risk quality is pinned to DESKTOP-OPJ8J04"
|
|
}
|
|
|
|
$release = Resolve-DDirectory $ReleaseRoot "M48T release root" $false
|
|
$dataset = Resolve-DDirectory $DatasetRoot "M48T dataset root" $true
|
|
$output = Resolve-DDirectory $OutputRoot "M48T output root" $true
|
|
$runOutput = Join-Path $output $RunId
|
|
if (Test-Path -LiteralPath $runOutput) { throw "M48T run output already exists" }
|
|
$null = New-Item -ItemType Directory -Path $runOutput
|
|
$runOutput = Resolve-DDirectory $runOutput "M48T run output" $false
|
|
|
|
$wheel = Assert-RegularFile (
|
|
(Join-Path $release "nodedc_mission_core-0.1.0-py3-none-any.whl")
|
|
) "M48T wheel"
|
|
if ((Get-Sha256 $wheel) -cne $ExpectedWheelSha256) { throw "M48T wheel SHA-256 changed" }
|
|
$profile = Assert-RegularFile (
|
|
(Join-Path $release "m48t-risk-quality-temporal-v1.json")
|
|
) "M48T profile"
|
|
$runner = Assert-RegularFile (
|
|
(Join-Path $release "run_m48t_coco_risk_quality_worker.py")
|
|
) "M48T runner"
|
|
$runnerSha256 = Get-Sha256 $runner
|
|
|
|
$imagesArchive = Join-Path $dataset "val2017.zip"
|
|
$annotations = Join-Path $dataset "instances_val2017.json"
|
|
# These mirrors rehost the unmodified official COCO assets and publish pinned SHA-256 digests.
|
|
Ensure-PinnedMirrorFile $imagesArchive `
|
|
"https://huggingface.co/datasets/pcuenq/coco-2017-mirror/resolve/main/val2017.zip?download=true" `
|
|
815585330 `
|
|
"4f7e2ccb2866ec5041993c9cf2a952bbed69647b115d0f74da7ce8f4bef82f05" `
|
|
"COCO val2017"
|
|
Ensure-PinnedMirrorFile $annotations `
|
|
"https://huggingface.co/datasets/LibreYOLO/coco2017/resolve/main/instances_val2017.json?download=true" `
|
|
19987840 `
|
|
"e8c7f7908f1d7278341fae127d0da654f102f11bd7b21d8aeefa635b8c810b6f" `
|
|
"COCO instances_val2017"
|
|
$imagesArchive = Assert-RegularFile $imagesArchive "COCO images archive"
|
|
$annotations = Assert-RegularFile $annotations "COCO val2017 instances"
|
|
$imagesArchiveSha256 = Get-Sha256 $imagesArchive
|
|
$annotationsDocumentSha256 = Get-Sha256 $annotations
|
|
|
|
$imagesRoot = Join-Path $dataset "val2017"
|
|
if (-not (Test-Path -LiteralPath $imagesRoot -PathType Container)) {
|
|
& tar.exe -xf $imagesArchive -C $dataset
|
|
Assert-LastExitCode "COCO val2017 extraction"
|
|
}
|
|
$imagesRoot = Resolve-DDirectory $imagesRoot "COCO val2017 images" $false
|
|
if (@(Get-ChildItem -LiteralPath $imagesRoot -File -Filter "*.jpg").Count -ne 5000) {
|
|
throw "COCO val2017 image count changed"
|
|
}
|
|
|
|
$experimentRoot = Resolve-DDirectory (
|
|
"D:\NDC_MISSIONCORE\runtime\experiments\m48t-fixed-detector-20260825T095425Z"
|
|
) "M48T RF-DETR experiment root" $false
|
|
$modelRoot = Resolve-DDirectory (
|
|
(Join-Path $experimentRoot "triton-models")
|
|
) "M48T RF-DETR model root" $false
|
|
if ((Get-Sha256 (Assert-RegularFile (
|
|
(Join-Path $modelRoot "rf_detr_large\1\model.plan")
|
|
) "RF-DETR TensorRT engine")) -cne "986399ce706b7380472cf5e473232249fed6e628971d8007f6609e83128d46b8") {
|
|
throw "RF-DETR TensorRT engine SHA-256 changed"
|
|
}
|
|
|
|
$image = "nvcr.io/nvidia/tritonserver:26.06-py3@sha256:58df7489c3f2276f9591d500a012dee03e23d35543ce3c390b4c001e6bf90794"
|
|
& docker image inspect $image *> $null
|
|
Assert-LastExitCode "Pinned M48T image inspection"
|
|
$historicalTriton = Get-Container "ndc-mission-core-triton"
|
|
if (-not $historicalTriton.State.Running -or $historicalTriton.State.Health.Status -cne "healthy") {
|
|
throw "Historical Triton must remain healthy during M48T quality evaluation"
|
|
}
|
|
$historicalTritonId = [string]$historicalTriton.Id
|
|
$tritonName = "ndc-mission-core-m48t-risk-quality-triton"
|
|
$qualityName = "ndc-mission-core-m48t-risk-quality"
|
|
foreach ($name in @($tritonName, $qualityName)) {
|
|
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$name$") {
|
|
throw "M48T candidate container $name already exists"
|
|
}
|
|
}
|
|
|
|
$opencv = Resolve-DDirectory (
|
|
"D:\NDC_MISSIONCORE\runtime\derived\perception-e3-opencv413092-v1\packages"
|
|
) "OpenCV dependency" $false
|
|
$pillow = Resolve-DDirectory (
|
|
"D:\NDC_MISSIONCORE\runtime\derived\perception-p0-env-v1"
|
|
) "Pillow dependency" $false
|
|
|
|
try {
|
|
& docker create `
|
|
--name $tritonName `
|
|
--read-only `
|
|
--security-opt "no-new-privileges:true" `
|
|
--cap-drop ALL `
|
|
--pids-limit 512 `
|
|
--shm-size 1g `
|
|
--gpus all `
|
|
--tmpfs "/tmp:rw,noexec,nosuid,size=2g" `
|
|
--health-cmd "curl --fail --silent http://127.0.0.1:8000/v2/health/ready" `
|
|
--health-interval 5s `
|
|
--health-timeout 3s `
|
|
--health-start-period 20s `
|
|
--health-retries 24 `
|
|
-v ((Convert-ToDockerPath $modelRoot) + ":/models:ro") `
|
|
$image `
|
|
tritonserver `
|
|
--model-repository=/models `
|
|
--model-control-mode=explicit `
|
|
--load-model=rf_detr_large `
|
|
--disable-auto-complete-config `
|
|
--strict-readiness=true `
|
|
--exit-on-error=true `
|
|
--allow-http=true `
|
|
--allow-grpc=false `
|
|
--allow-metrics=false *> $null
|
|
Assert-LastExitCode "M48T Triton creation"
|
|
& docker start $tritonName *> $null
|
|
Assert-LastExitCode "M48T Triton start"
|
|
$ready = $false
|
|
foreach ($attempt in 1..60) {
|
|
Start-Sleep -Seconds 2
|
|
$candidate = Get-Container $tritonName
|
|
if (-not $candidate.State.Running) { throw "M48T Triton stopped during startup" }
|
|
if ($candidate.State.Health.Status -ceq "healthy") { $ready = $true; break }
|
|
}
|
|
if (-not $ready) { throw "M48T Triton did not become healthy" }
|
|
|
|
$maximumArguments = @()
|
|
if ($MaximumImages -gt 0) {
|
|
$maximumArguments = @("--maximum-images", ([string]$MaximumImages))
|
|
}
|
|
$arguments = @(
|
|
"run", "--name", $qualityName,
|
|
"--network", ("container:{0}" -f $tritonName),
|
|
"--read-only",
|
|
"--security-opt", "no-new-privileges:true",
|
|
"--cap-drop", "ALL",
|
|
"--pids-limit", "256",
|
|
"--gpus", "all",
|
|
"--tmpfs", "/tmp:rw,noexec,nosuid,size=2g",
|
|
"-e", "PYTHONDONTWRITEBYTECODE=1",
|
|
"-e", "PYTHONPATH=/release/nodedc_mission_core-0.1.0-py3-none-any.whl:/opt/opencv:/opt/pillow",
|
|
"-v", ((Convert-ToDockerPath $release) + ":/release:ro"),
|
|
"-v", ((Convert-ToDockerPath $imagesRoot) + ":/dataset/val2017:ro"),
|
|
"-v", ((Convert-ToDockerPath $annotations) + ":/dataset/instances_val2017.json:ro"),
|
|
"-v", ((Convert-ToDockerPath $runOutput) + ":/output:rw"),
|
|
"-v", ((Convert-ToDockerPath $opencv) + ":/opt/opencv:ro"),
|
|
"-v", ((Convert-ToDockerPath $pillow) + ":/opt/pillow:ro"),
|
|
"--entrypoint", "python3",
|
|
$image,
|
|
"/release/run_m48t_coco_risk_quality_worker.py",
|
|
"--profile", "/release/m48t-risk-quality-temporal-v1.json",
|
|
"--annotations", "/dataset/instances_val2017.json",
|
|
"--images-root", "/dataset/val2017",
|
|
"--triton-origin", "http://127.0.0.1:8000",
|
|
"--output", "/output/result.json",
|
|
"--predictions", "/output/predictions.jsonl",
|
|
"--failures", "/output/failures.jsonl",
|
|
"--progress", "/output/progress.jsonl",
|
|
"--review-root", "/output/review",
|
|
"--runtime-artifact-sha256", $ExpectedWheelSha256,
|
|
"--runner-sha256", $runnerSha256,
|
|
"--images-archive-sha256", $imagesArchiveSha256,
|
|
"--annotations-document-sha256", $annotationsDocumentSha256
|
|
) + $maximumArguments
|
|
& docker @arguments
|
|
Assert-LastExitCode "M48T COCO risk quality evaluation"
|
|
if (-not (Test-Path -LiteralPath (Join-Path $runOutput "result.json") -PathType Leaf)) {
|
|
throw "M48T result was not written"
|
|
}
|
|
} finally {
|
|
foreach ($name in @($qualityName, $tritonName)) {
|
|
if (& docker ps -a --format "{{.Names}}" --filter "name=^/$name$") {
|
|
& docker rm -f $name *> $null
|
|
}
|
|
}
|
|
$historicalAfter = Get-Container "ndc-mission-core-triton"
|
|
if (
|
|
$historicalAfter.Id -cne $historicalTritonId -or
|
|
-not $historicalAfter.State.Running -or
|
|
$historicalAfter.State.Health.Status -cne "healthy"
|
|
) {
|
|
throw "Historical Triton changed during M48T quality evaluation"
|
|
}
|
|
}
|
|
|
|
Write-Output ("M48T_RESULT={0}" -f (Join-Path $runOutput "result.json"))
|
|
Write-Output ("COCO_IMAGES_SHA256={0}" -f $imagesArchiveSha256)
|
|
Write-Output ("COCO_ANNOTATIONS_SHA256={0}" -f $annotationsDocumentSha256)
|
|
Write-Output "HISTORICAL_TRITON_ACTION=none"
|
|
Write-Output "PRODUCTION_ACCEPTED=false"
|