Blocked fine: fix INNER assertion, skip empty halo uploads, explicit fixture
- GPU test: after hop 2 some INNER_GHOST populations are NaN (pulled from OUTER poisoned on hop 1; 236 slots in pinned CPUREF); assert ACTIVE finite and OUTER NaN, keep the full dense-vs-blocked raw value/NaN-mask comparison. - A 1x1 non-periodic fine layout has zero halo pairs: skip the halo table uploads (CUDA rejects mem_alloc(0)); _fine_halo already skips launch. CPU guard test with driver doubles. - Drop the class-level fine_layout default; the followup fake backend now sets fine_layout = None explicitly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
dc5850c198
commit
59921a0181
3 files changed
+35
-6
No files matched your search
@@ -72,8 +72,6 @@ class FixedPatchStepper:
|
||||
only, ACTIVE collide); halos are gathered before each fine hop. Public
|
||||
arrays keep the dense (9, ny, nx) fine shape; only the allocation differs.
|
||||
"""
|
||||
fine_layout = None # dense fine storage unless fine_block is given
|
||||
|
||||
def __init__(self, cfg, bounds, *, context, coarse_boundary="periodic", fine_objects=(),
|
||||
fine_block=None):
|
||||
if HALO != 4:
|
||||
@@ -169,8 +167,9 @@ class FixedPatchStepper:
|
||||
else:
|
||||
self._children = self._upload(tables[1])
|
||||
self._kind = self._upload(tables[2])
|
||||
self._halo_dst = self._upload(layout.halo_dst)
|
||||
self._halo_src = self._upload(layout.halo_src)
|
||||
if len(layout.halo_dst): # a 1x1 non-periodic layout has no pairs
|
||||
self._halo_dst = self._upload(layout.halo_dst)
|
||||
self._halo_src = self._upload(layout.halo_src)
|
||||
module = self._levels[1].module
|
||||
self._explode = module.get_function("BlockExplode")
|
||||
self._halo_copy = module.get_function("BlockHaloCopy")
|
||||
|
||||
@@ -5,8 +5,9 @@ periodic and slot_reservoir modes: dense fine storage vs 1x1, 2x2 and 4x2
|
||||
fine blocks give bit-identical masked downloads AND bit-identical raw coarse
|
||||
and unpacked owner-core fine arrays, including patch-ghost values/NaN masks.
|
||||
"""
|
||||
from contextlib import nullcontext
|
||||
import unittest
|
||||
from unittest.mock import Mock
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import numpy as np
|
||||
import pycuda.driver as cuda
|
||||
@@ -14,6 +15,7 @@ import pycuda.driver as cuda
|
||||
from CelerisLab.body.geometry.circle import CircleGeometry
|
||||
from CelerisLab.body.objects import SimObject
|
||||
from CelerisLab.config import LBMConfig
|
||||
from CelerisLab.cuda import compiler_v2 as compiler
|
||||
from CelerisLab.lbm.blocks import BlockLayout2D
|
||||
from CelerisLab.lbm.descriptors import D2Q9_EX, D2Q9_EY
|
||||
from CelerisLab.lbm.refinement import ACTIVE, INNER_GHOST, OUTER_GHOST, FixedPatch2D
|
||||
@@ -67,6 +69,31 @@ class BlockedFineCPU(unittest.TestCase):
|
||||
with self.subTest(kw=kw), self.assertRaises(ValueError):
|
||||
FixedPatchStepper(config("periodic"), BOUNDS, context=self.forbidden_context(), **kw)
|
||||
|
||||
def test_single_block_layout_allocates_no_empty_halo_tables(self):
|
||||
# A 1x1 non-periodic fine layout has zero halo pairs; CUDA rejects mem_alloc(0).
|
||||
context = Mock(activate=nullcontext, sm_arch="sm_70")
|
||||
module = Mock()
|
||||
module.get_global.return_value = (Mock(), 20)
|
||||
sizes = []
|
||||
|
||||
def alloc(size):
|
||||
if size <= 0:
|
||||
raise AssertionError("zero-byte device allocation")
|
||||
sizes.append(size)
|
||||
return Mock()
|
||||
with patch.object(cuda, "Stream"), 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"):
|
||||
backend = FixedPatchStepper(config("periodic"), BOUNDS, context=context,
|
||||
fine_block=FINE_BLOCKS[0])
|
||||
self.assertEqual(len(backend.fine_layout.halo_dst), 0)
|
||||
self.assertIsNone(backend._halo_dst)
|
||||
backend._fine_halo(Mock()) # no launch without pairs
|
||||
module.get_function.return_value.assert_not_called()
|
||||
backend.close()
|
||||
self.assertTrue(sizes)
|
||||
|
||||
def test_tables_map_back_to_dense_slots_in_order(self):
|
||||
patch = FixedPatch2D.from_config(config("periodic"), BOUNDS)
|
||||
fy, fx = patch.fine_shape
|
||||
@@ -114,7 +141,9 @@ class BlockedFineGPU(unittest.TestCase):
|
||||
(dense_public, dense_raw), blocked = results[0], results[1:]
|
||||
kind = patch.fine_kind
|
||||
self.assertTrue(np.isnan(dense_raw[1][:, kind == OUTER_GHOST]).all(), mode)
|
||||
self.assertTrue(np.isfinite(dense_raw[1][:, kind != OUTER_GHOST]).all(), mode)
|
||||
# INNER populations may be NaN after hop 2 (pulled from poisoned OUTER);
|
||||
# their exact pattern is pinned by the dense-vs-blocked comparison below.
|
||||
self.assertTrue(np.isfinite(dense_raw[1][:, kind == ACTIVE]).all(), mode)
|
||||
for fine_block, (public, raw_arrays) in zip(FINE_BLOCKS, blocked):
|
||||
msg = f"{mode} fine_block={fine_block}"
|
||||
for a, b in zip(public + tuple(raw_arrays), dense_public + tuple(dense_raw)):
|
||||
|
||||
@@ -106,6 +106,7 @@ class FaultGuardsCPU(unittest.TestCase):
|
||||
|
||||
def fake_backend(self):
|
||||
backend = PeriodicPatchStepper.__new__(PeriodicPatchStepper)
|
||||
backend.fine_layout = None # dense fine storage
|
||||
backend.ctx = Mock(activate=nullcontext)
|
||||
backend._lock = RLock()
|
||||
backend._status = "ready"
|
||||
|
||||
Reference in new issue
Block a user