refinement: free-memory preflight for every level before any device work
Restores the PeriodicBlockStepper safeguard dropped in 8e1feff, for all
FixedPatchStepper levels: both G buffers, halo pairs and boundary columns
of every level are compared with cuda.mem_get_info() before the stream,
any module or any buffer exists, raising MemoryError('level buffers exceed
free device memory'). Transfer tables are not counted (lower bound).
Tests: the single-level MemoryError case is back beside the param-layout
check (no stream, no allocation); host test_single_level covers depth 1
and depth 2 (no stream, build or allocation) and the wait failure after a
good step (count stays 1). Driver-mocking fixtures report ample free memory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8c776309c9
commit
19db7d362f
@@ -159,11 +159,17 @@ class CpuContracts(unittest.TestCase):
|
||||
with self.assertRaisesRegex(ValueError, "launch limits"):
|
||||
single(context=self.forbidden_context())
|
||||
|
||||
def test_param_layout_rejected_before_allocation(self):
|
||||
def test_memory_and_param_layout_rejected_before_allocation(self):
|
||||
context = Mock(activate=nullcontext, sm_arch="sm_70")
|
||||
with patch.object(cuda, "mem_get_info", return_value=(1024, 1 << 30)), \
|
||||
patch.object(cuda, "Stream") as stream, patch.object(cuda, "mem_alloc") as alloc:
|
||||
with self.assertRaisesRegex(MemoryError, "exceed free device memory"):
|
||||
single(context=context, objects=[circle()])
|
||||
stream.assert_not_called()
|
||||
alloc.assert_not_called()
|
||||
module = Mock()
|
||||
module.get_global.return_value = (Mock(), 16)
|
||||
with patch.object(cuda, "Stream"), patch.object(compiler, "generate_config"), \
|
||||
with patch.object(cuda, "Stream"), patch.object(cuda, "mem_get_info", return_value=(1 << 40, 1 << 40)), patch.object(compiler, "generate_config"), \
|
||||
patch.object(compiler, "compile_kernel") as compile_kernel, \
|
||||
patch.object(compiler, "cleanup_build_dir"), \
|
||||
patch.object(compiler, "load_module", return_value=module), \
|
||||
|
||||
@@ -73,6 +73,7 @@ def mocked_backend(mode="periodic", fail_alloc=None):
|
||||
stack.enter_context(patch.object(compiler, name))
|
||||
stack.enter_context(patch.object(compiler, "load_module", return_value=module))
|
||||
stack.enter_context(patch.object(cuda, "Stream", return_value=stream))
|
||||
stack.enter_context(patch.object(cuda, "mem_get_info", return_value=(1 << 40, 1 << 40)))
|
||||
stack.enter_context(patch.object(cuda, "mem_alloc", side_effect=allocate))
|
||||
stack.enter_context(patch.object(cuda, "memcpy_htod",
|
||||
side_effect=lambda p, a: uploads.append((p, a))))
|
||||
|
||||
@@ -122,7 +122,7 @@ class BlockedFineCPU(unittest.TestCase):
|
||||
raise AssertionError("zero-byte device allocation")
|
||||
sizes.append(size)
|
||||
return Mock()
|
||||
with patch.object(cuda, "Stream"), patch.object(compiler, "generate_config"), \
|
||||
with patch.object(cuda, "Stream"), patch.object(cuda, "mem_get_info", return_value=(1 << 40, 1 << 40)), patch.object(compiler, "generate_config"), \
|
||||
patch.object(compiler, "compile_kernel"), patch.object(compiler, "cleanup_build_dir"), \
|
||||
patch.object(compiler, "load_module", return_value=module), \
|
||||
patch.object(cuda, "mem_alloc", side_effect=alloc), patch.object(cuda, "memcpy_htod"):
|
||||
@@ -172,7 +172,7 @@ class BlockedFineCPU(unittest.TestCase):
|
||||
return functions.setdefault(name, Mock(side_effect=lambda *a, **k: calls.append((name, a))))
|
||||
module = Mock(get_function=Mock(side_effect=get_function))
|
||||
module.get_global.return_value = (Mock(), 20)
|
||||
with patch.object(cuda, "Stream"), patch.object(compiler, "generate_config"), \
|
||||
with patch.object(cuda, "Stream"), patch.object(cuda, "mem_get_info", return_value=(1 << 40, 1 << 40)), patch.object(compiler, "generate_config"), \
|
||||
patch.object(compiler, "compile_kernel"), patch.object(compiler, "cleanup_build_dir"), \
|
||||
patch.object(compiler, "load_module", return_value=module), \
|
||||
patch.object(cuda, "mem_alloc", side_effect=lambda size: Mock()), \
|
||||
|
||||
@@ -166,7 +166,7 @@ class FaultGuardsCPU(unittest.TestCase):
|
||||
# A pair tau inconsistent with its level config is rejected twice over, both before
|
||||
# any device allocation or upload: first by the hierarchy chain check, and, if that
|
||||
# is bypassed, by the per-level omega/tau check in _build_level.
|
||||
with patch.object(cuda, "Stream"), bad, patch.object(cuda, "mem_alloc") as alloc, \
|
||||
with patch.object(cuda, "Stream"), patch.object(cuda, "mem_get_info", return_value=(1 << 40, 1 << 40)), bad, patch.object(cuda, "mem_alloc") as alloc, \
|
||||
patch.object(cuda, "memcpy_htod") as upload:
|
||||
with self.assertRaisesRegex(RuntimeError, "config chain is inconsistent"):
|
||||
PeriodicPatchStepper(cfg, context=context, refinements=[p.bounds])
|
||||
@@ -177,7 +177,7 @@ class FaultGuardsCPU(unittest.TestCase):
|
||||
upload.assert_not_called()
|
||||
module = Mock()
|
||||
module.get_global.return_value = (Mock(), 16)
|
||||
with patch.object(cuda, "Stream"), patch.object(compiler, "generate_config"), \
|
||||
with patch.object(cuda, "Stream"), patch.object(cuda, "mem_get_info", return_value=(1 << 40, 1 << 40)), patch.object(compiler, "generate_config"), \
|
||||
patch.object(compiler, "compile_kernel"), patch.object(compiler, "cleanup_build_dir"), \
|
||||
patch.object(compiler, "load_module", return_value=module), \
|
||||
patch.object(cuda, "memcpy_htod") as upload:
|
||||
|
||||
@@ -36,7 +36,7 @@ def mocked(objects, jets, fine_block):
|
||||
module = Mock(get_function=Mock(return_value=Mock()))
|
||||
module.get_global.return_value = (Mock(), 20)
|
||||
htod = Mock()
|
||||
with patch.object(cuda, "Stream"), patch.object(compiler, "generate_config"), \
|
||||
with patch.object(cuda, "Stream"), patch.object(cuda, "mem_get_info", return_value=(1 << 40, 1 << 40)), patch.object(compiler, "generate_config"), \
|
||||
patch.object(compiler, "compile_kernel"), patch.object(compiler, "cleanup_build_dir"), \
|
||||
patch.object(compiler, "load_module", return_value=module), \
|
||||
patch.object(cuda, "mem_alloc", side_effect=lambda size: Mock()), \
|
||||
|
||||
@@ -56,7 +56,7 @@ def mocked_child():
|
||||
context = Mock(activate=nullcontext, sm_arch="sm_70")
|
||||
module = Mock(get_function=Mock(side_effect=lambda name: Mock(name=name)))
|
||||
module.get_global.return_value = (Mock(), 20)
|
||||
with patch.object(cuda, "Stream"), patch.object(compiler, "generate_config"), \
|
||||
with patch.object(cuda, "Stream"), patch.object(cuda, "mem_get_info", return_value=(1 << 40, 1 << 40)), patch.object(compiler, "generate_config"), \
|
||||
patch.object(compiler, "compile_kernel"), patch.object(compiler, "cleanup_build_dir"), \
|
||||
patch.object(compiler, "load_module", return_value=module), \
|
||||
patch.object(cuda, "mem_alloc", side_effect=lambda size: Mock()), \
|
||||
|
||||
Reference in New Issue
Block a user