Files

9.3 KiB
Raw Permalink Blame History

Architecture repair — 2026-10-10

Scope: config loading/validation, isolated compilation, CUDA ownership, import boundary and package metadata. Parent owns Simulation integration; this agent additionally edited only its constructor exception handler and close() with explicit parent authorization. No generated source headers/PTX, default JSON, external repository or environment was edited by this agent. Existing user changes remain.

Reference and design constraints

Read .github/skills/lbm-ref-architecture-guard/SKILL.md, the repair BRIEF and prior audit before editing. These are lifecycle/configuration fixes, not new CFD algorithms: accepted physics are the existing low-Mach D2Q9 paths, with exact configuration preservation, independent instance ownership and no accidental configuration fallback as acceptance conditions.

  • ref/lbmpy_analysis.md §§10.2, 12.1–12.2 describes a configuration → generated kernel → compiled callable pipeline and configuration-dependent caching. The relevant lesson is to bind inputs and artifacts. Its recommendation to replace JSON is not adopted: JSON remains the user configuration authority, existing dataclasses remain the validation bridge.
  • The same reference distinguishes physics, execution and hardware configuration. This repair introduces only an allocation signature to reject incompatible recompilation; it does not import a new configuration hierarchy, plugin framework or disk cache.
  • PyCUDA Devices and Contexts: make_context() makes the new context current, push()/pop() manage current-thread activation, and detach() releases ownership. Thus closing A while B is current must not pop B.
  • Python Context Variables supports context-local legacy build convenience; explicit build_dir is the robust contract for interleaving independent builds in one context.
  • Historical constraints from the preceding study remain: keep the public orchestration style, pending-body discard and runtime dense IDs; no universal service framework and no AMR/jet implementation in this repair.

Disposition

Finding Result Concrete change / remaining limit
ARCH-01 shared generated headers/PTX Fixed Every generation creates a private kernel snapshot, writes headers there, and compiles a private PTX. Unique temporary output + atomic replace avoids partially visible PTX. Configuration, source digest, architecture/options and PTX digest accompany artifacts. Context-local defaults preserve sequential legacy calls; Simulation passes explicit paths and owns cleanup. No build cache introduced.
ARCH-02 density truncation Fixed Physics and method floats use round-trip literals rather than display precision (rho=1.04 stays 1.04; 0.04 stays nonzero).
ARCH-03 missing explicit config fallback Fixed Missing/empty explicit path raises FileNotFoundError; search fallback occurs only for None.
ARCH-04 CUDA ownership Fixed at orchestration boundary Constructor balances its implicit push. activate() balances operation scopes; close() detaches its owner without popping another. context_bound handles nested calls and permits low-level objects lacking an attached context. Root attaches field.ctx, scopes Simulation construction and GPU calls. Same-instance simultaneous caller operations remain outside the contract; this is not a thread-safe solver scheduler.
ARCH-05 incompatible recompilation Fixed with root integration Signature covers lattice/dimensions, computational type, storage precision, shifting and streaming. Simulation rejects structural changes while preserving allocations. Collision/block-size changes rebuild the stepper; changed viscosity refreshes runtime omega, unchanged viscosity preserves explicit runtime overrides. A mutable config still requires explicit recompile() for compile-time method changes.
ARCH-06 validation assertions/fixtures Root ownership No claim from these architecture tests that all validation experiments pass.
ARCH-07 Python floor Fixed declaration Existing evaluated union syntax requires Python 3.10; setup/pyproject minimum and classifiers now agree. Package version agrees at 0.5.1. No syntax backport.
Pure CPU import isolation Fixed Root/common/lbm/body package initializers load GPU facades lazily; compiler imports driver only when loading PTX. Config, equilibrium, geometry and artifact generation no longer require importing PyCUDA. GPU facade access still requires its dependency.
D3 advertised capability Explicitly restricted validate() keeps legitimate D3 schema/local-operator research. validate_runtime() rejects D3 Simulation before allocation/build because boundary/auxiliary/object paths remain incomplete. Numerical D3 local repairs do not imply a complete runtime.

Validation now explicitly rejects unknown configuration enums, nonpositive/nonintegral dimensions/block sizes, block sizes above 1024, nonfinite numerical values and invalid density/viscosity/guard/damping values. These checks remain active under optimized Python, unlike assertions. Device-specific limits beyond the CUDA block maximum remain a runtime/compiler concern.

Minimal API and changed files

build_dir = compiler.generate_config(cfg, n_objects)
ptx = compiler.compile_kernel(arch=arch, build_dir=build_dir)
module = compiler.load_module(ptx)
# caller later releases resources and their private source/artifact directory
compiler.cleanup_build_dir(build_dir)

with ctx.activate():
    # allocate/use/free GPU resources owned by this context
    ...
ctx.close()

generate_config(); compile_kernel(); load_module() remains available within one Python context. Interleaved callers should use returned paths. A failed compile clears implicit last-PTX selection while preserving an explicitly addressed older artifact; it cannot silently load previous code as the latest result. Cleanup refuses paths without the compiler marker and refuses the package kernel directory.

Files: config.py; cuda/compiler_v2.py, cuda/context.py, cuda/__init__.py; package/common/lbm/body __init__.py; pyproject.toml, setup.py; configs/CONFIG.md. Parent integrates simulation.py; the later explicitly delegated cleanup change adds preflight active-operation rejection, retry after synchronize/detach/directory-cleanup failures, and preservation of the original constructor exception if best-effort cleanup also fails. Constructor rollback cannot guarantee release after a driver failure; it preserves the useful primary error rather than replacing it.

Verification

Executed with PYTHONPATH=src and /home/frank14f/anaconda3/envs/pycuda_3_10/bin/python:

python -m pytest tests/unit/test_build_contracts.py \
    tests/unit/test_context_ownership.py tests/unit/test_simulation_lifecycle.py -q
18 passed, 8 subtests passed in 0.37s
  • Build tests read actual generated headers and simulate the compiler invocation to prove two simultaneous configurations remain isolated, explicit interleaving retains the intended inputs, source header hashes do not change, output traversal is refused, cleanup is owned/idempotent and failed compilation cannot implicitly select a previous PTX.
  • Config tests cover rho/method literal fidelity, explicit missing paths, invalid values, structural signature and D3 schema/runtime distinction. A subprocess explicitly blocks pycuda while importing config/equilibrium/circle/compiler.
  • Context tests execute the real context implementation against a driver-stack model: A/B nesting, non-LIFO close, active close refusal, exception unwinding, repeated close and nested/no-context decorators.
  • Lifecycle tests execute real Simulation cleanup with a CPU owner model: active close preserves resources; synchronize/detach/directory errors are retryable; constructor cleanup does not replace the original failure.

These are bounded CPU tests with explicitly modeled driver/compiler operations, not GPU validation or performance qualification. Parent performs isolated NVCC and actual V100 checks across repaired numerical/state paths. Source snapshotting adds construction/recompile I/O; it adds no per-lattice-cell work. Activation adds per-entry Python/driver overhead; its cost is not benchmarked here. Production fused kernels and module layout remain unchanged.

One cross-review of state integration

Reviewed checkpoint v2 geometry/projection/role validation, v1 limitations, host-control restoration, parameter-study viscosity handling, field snapshot parity, context decorators, pinned scratch ownership and manager action staging. Accepted the actual boundary: low-frequency scratch operations synchronize before freeing their host/device buffers; run() synchronizes before action staging can be reused. No blanket claim of arbitrary concurrent manager use.

Sent two actionable issues to owners: (1) parent run(checkpoint_interval) must increment the observation normalization count per completed batch before saving, and synchronize the selected stream before saving; (2) checkpoint scalar metadata should be fully coerced/validated before host/device writes, including malformed runtime parameters/observation counts. Owners handle these in their scopes. This is the single requested cross-review, not a new ledger or ongoing architecture expansion.