Preserve exact float32 via explicit nvcc compilation and audited binary loading
This commit is contained in:
@@ -6,12 +6,15 @@ import importlib.util
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
|
||||
from gpu_backend import (CUDA_OPTIONS, CUDA_SOURCE, COMPARTMENTS, CudaEligibility, cpu_reference,
|
||||
from gpu_backend import (CUDA_OPTIONS, CUDA_SOURCE, COMPARTMENTS, CudaEligibility, _nvcc_cubin, cpu_reference,
|
||||
prepare_events)
|
||||
|
||||
|
||||
@@ -229,5 +232,84 @@ class HostMathTests(unittest.TestCase):
|
||||
self.assertNotIn("__expf", CUDA_SOURCE)
|
||||
|
||||
|
||||
class CompilerCacheTests(unittest.TestCase):
|
||||
"""Host-side compiler invocation/evidence checks; these do not compile CUDA."""
|
||||
|
||||
def test_explicit_flags_authenticated_cache_and_no_global_environment_mutation(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
compiler = root / "nvcc"
|
||||
compiler.write_bytes(b"host-test-compiler-placeholder")
|
||||
calls = []
|
||||
|
||||
def run(argv, **kwargs):
|
||||
if argv[-1] == "--version":
|
||||
return subprocess.CompletedProcess(argv, 0, stdout="nvcc test version", stderr="")
|
||||
calls.append((argv, kwargs))
|
||||
self.assertIn("--ftz=false", argv)
|
||||
self.assertIn("--fmad=false", argv)
|
||||
self.assertIn("-arch=sm_70", argv)
|
||||
self.assertNotIn("--use_fast_math", argv)
|
||||
self.assertNotIn("-ftz=true", argv)
|
||||
Path(argv[argv.index("-o") + 1]).write_bytes(b"test cubin, not executable CUDA")
|
||||
kwargs["stdout"].write(b"mock nvcc stdout\n")
|
||||
kwargs["stderr"].write(b"mock nvcc stderr\n")
|
||||
return subprocess.CompletedProcess(argv, 0)
|
||||
|
||||
with mock.patch.dict(os.environ, {"NVCC_PREPEND_FLAGS": "", "NVCC_APPEND_FLAGS": ""}), \
|
||||
mock.patch("gpu_backend.subprocess.run", side_effect=run):
|
||||
before = dict(os.environ)
|
||||
cubin, evidence, timing = _nvcc_cubin(root / "cache", "70", compiler)
|
||||
self.assertFalse(evidence["cache_hit"])
|
||||
self.assertTrue(evidence["complete"])
|
||||
self.assertIn("eligibility.cubin", evidence["files_sha256"])
|
||||
self.assertIn("compiler.stdout.log", evidence["files_sha256"])
|
||||
self.assertIn("compiler.stderr.log", evidence["files_sha256"])
|
||||
self.assertEqual(before, dict(os.environ))
|
||||
again, cached, second_timing = _nvcc_cubin(root / "cache", "70", compiler)
|
||||
self.assertEqual(cubin, again)
|
||||
self.assertTrue(cached["cache_hit"])
|
||||
self.assertEqual(second_timing["nvcc_compile_seconds"], 0)
|
||||
self.assertEqual(len(calls), 1)
|
||||
self.assertEqual(evidence["receipt_sha256"], cached["receipt_sha256"])
|
||||
cubin.write_bytes(b"corrupted test cache")
|
||||
with self.assertRaisesRegex(ValueError, "artifact changed"):
|
||||
_nvcc_cubin(root / "cache", "70", compiler)
|
||||
|
||||
def test_failed_compile_evidence_is_retained_and_not_overwritten(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
compiler = root / "nvcc"
|
||||
compiler.write_bytes(b"host-test-compiler-placeholder")
|
||||
|
||||
def run(argv, **kwargs):
|
||||
if argv[-1] == "--version":
|
||||
return subprocess.CompletedProcess(argv, 0, stdout="nvcc test version", stderr="")
|
||||
kwargs["stderr"].write(b"mock compiler failure\n")
|
||||
return subprocess.CompletedProcess(argv, 1)
|
||||
|
||||
with mock.patch.dict(os.environ, {"NVCC_PREPEND_FLAGS": "", "NVCC_APPEND_FLAGS": ""}), \
|
||||
mock.patch("gpu_backend.subprocess.run", side_effect=run):
|
||||
with self.assertRaisesRegex(RuntimeError, "evidence retained"):
|
||||
_nvcc_cubin(root / "cache", "70", compiler)
|
||||
receipts = list((root / "cache").rglob("build-receipt.json"))
|
||||
self.assertEqual(len(receipts), 1)
|
||||
before = receipts[0].read_bytes()
|
||||
with self.assertRaisesRegex(ValueError, "failed or mismatched"):
|
||||
_nvcc_cubin(root / "cache", "70", compiler)
|
||||
self.assertEqual(receipts[0].read_bytes(), before)
|
||||
|
||||
def test_injected_compiler_flags_rejected_without_clearing_global_environment(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
compiler = root / "nvcc"
|
||||
compiler.write_bytes(b"host-test-compiler-placeholder")
|
||||
with mock.patch.dict(os.environ, {"NVCC_APPEND_FLAGS": "--ftz=true"}):
|
||||
with self.assertRaisesRegex(ValueError, "injected flags"):
|
||||
_nvcc_cubin(root / "cache", "70", compiler)
|
||||
self.assertEqual(os.environ["NVCC_APPEND_FLAGS"], "--ftz=true")
|
||||
self.assertFalse((root / "cache").exists())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user