From 5076ba10900e69880c11bb8a7a050b9309cb53e0 Mon Sep 17 00:00:00 2001 From: Fabio Luporini Date: Tue, 17 Dec 2024 08:19:44 +0000 Subject: [PATCH 01/11] misc: Turn not-enough-memory warning into a MemoryError --- devito/types/dense.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devito/types/dense.py b/devito/types/dense.py index 7afcf6f12b..5ba24f48a1 100644 --- a/devito/types/dense.py +++ b/devito/types/dense.py @@ -1374,10 +1374,10 @@ def __init_finalize__(self, *args, **kwargs): available_mem = virtual_memory().available required_mem = np.dtype(self.dtype).itemsize * self.size if required_mem > available_mem: - warning("Trying to allocate more memory (%s) " - % humanbytes(required_mem) + "for symbol %s " % self.name + - "than available (%s) " % humanbytes(available_mem) + - "on physical device, this will start swapping") + raise MemoryError( + "Trying to allocate more memory (%s) for `%s` than available (%s)" + % (humanbytes(required_mem), self.name, humanbytes(available_mem)) + ) if not isinstance(self.time_order, int): raise TypeError("`time_order` must be int") From 3c09e4d3ffa71f06fd9fee0fc57d779440ea722a Mon Sep 17 00:00:00 2001 From: Fabio Luporini Date: Tue, 17 Dec 2024 09:14:26 +0000 Subject: [PATCH 02/11] misc: Document exceptions --- devito/exceptions.py | 52 ++++++++++++++++++++++++++++++++------- devito/ir/iet/visitors.py | 4 +-- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/devito/exceptions.py b/devito/exceptions.py index fa5619d4ca..2c93d457cf 100644 --- a/devito/exceptions.py +++ b/devito/exceptions.py @@ -1,22 +1,56 @@ class DevitoError(Exception): - pass + """ + Base class for all Devito-related exceptions. + """ class CompilationError(DevitoError): - pass + """ + Raised by the JIT compiler when the generated code cannot be compiled, + typically due to a syntax error. + + These errors typically stem by one of the following: + + * A flaw in the user-provided equations; + * An issue with the user-provided compiler options, not compatible + with the given equations and/or backend; + * A bug or a limitation in the Devito compiler itself. + """ class InvalidArgument(DevitoError): - pass + """ + Raised by the runtime system when an `op.apply(...)` argument, either a + default argument or a user-provided one ("override"), is not valid. + These are typically user-level errors, such as passing an incorrect + type of argument, or passing an argument with an incorrect value. + """ -class InvalidOperator(DevitoError): - pass +class InvalidOperator(DevitoError): + """ + Raised by the runtime system when an `Operator` cannot be constructed. -class ExecutionError(DevitoError): - pass + This generally occurs when an invalid combination of arguments is supplied to + `Operator(...)` (e.g., a GPU-only optimization option is provided, while the + Operator is being generated for the CPU). + """ -class VisitorException(DevitoError): - pass +class ExecutionError(DevitoError): + """ + Raised after `op.apply(...)` if a runtime error occurred during the execution + of the Operator is detected. + + The nature of these errors can be various, for example: + + * Unstable numerical behavior (e.g., NaNs); + * Out-of-bound accesses to arrays, which in turn can be caused by: + * Incorrect user-provided equations (e.g., abuse of the "indexed notation"); + * A buggy optimization pass; + * Running out of resources: + * Memory (e.g., too many temporaries in the generated code); + * Device shared memory or registers (e.g., too many threads per block); + * etc. + """ diff --git a/devito/ir/iet/visitors.py b/devito/ir/iet/visitors.py index 505fe2e001..cd405833f5 100644 --- a/devito/ir/iet/visitors.py +++ b/devito/ir/iet/visitors.py @@ -13,7 +13,7 @@ from sympy import IndexedBase from sympy.core.function import Application -from devito.exceptions import VisitorException +from devito.exceptions import CompilationError from devito.ir.iet.nodes import (Node, Iteration, Expression, ExpressionBundle, Call, Lambda, BlankLine, Section, ListMajor) from devito.ir.support.space import Backward @@ -1188,7 +1188,7 @@ def visit_Node(self, o, **kwargs): elif isinstance(handle, Iterable): # Iterable -> inject `handle` into `o`'s children if not o.children: - raise VisitorException + raise CompilationError("Cannot inject nodes in a leaf node") if self.nested: children = [self._visit(i, **kwargs) for i in o.children] else: From e5759bc63395bf07139d8ee852efb608d0ea3df1 Mon Sep 17 00:00:00 2001 From: Fabio Luporini Date: Tue, 17 Dec 2024 09:29:31 +0000 Subject: [PATCH 03/11] misc: Change exception type where necessary --- devito/core/operator.py | 10 +++++----- devito/ir/clusters/algorithms.py | 6 +++--- devito/operator/operator.py | 5 +++-- devito/passes/clusters/aliases.py | 8 +++++--- devito/passes/clusters/buffering.py | 10 +++++----- devito/passes/iet/orchestration.py | 4 ++-- tests/test_buffering.py | 4 ++-- 7 files changed, 25 insertions(+), 22 deletions(-) diff --git a/devito/core/operator.py b/devito/core/operator.py index 39c7a61fef..e6bfd18916 100644 --- a/devito/core/operator.py +++ b/devito/core/operator.py @@ -2,7 +2,7 @@ from functools import cached_property from devito.core.autotuning import autotune -from devito.exceptions import InvalidArgument, InvalidOperator +from devito.exceptions import InvalidOperator from devito.ir import FindSymbols from devito.logger import warning from devito.mpi.routines import mpi_registry @@ -170,15 +170,15 @@ def _check_kwargs(cls, **kwargs): raise InvalidOperator("Unsupported MPI mode `%s`" % oo['mpi']) if oo['cse-algo'] not in ('basic', 'smartsort', 'advanced'): - raise InvalidArgument("Illegal `cse-algo` value") + raise InvalidOperator("Illegal `cse-algo` value") if oo['deriv-schedule'] not in ('basic', 'smart'): - raise InvalidArgument("Illegal `deriv-schedule` value") + raise InvalidOperator("Illegal `deriv-schedule` value") if oo['deriv-unroll'] not in (False, 'inner', 'full'): - raise InvalidArgument("Illegal `deriv-unroll` value") + raise InvalidOperator("Illegal `deriv-unroll` value") if oo['errctl'] not in (None, False, 'basic', 'max'): - raise InvalidArgument("Illegal `errctl` value") + raise InvalidOperator("Illegal `errctl` value") def _autotune(self, args, setup): if setup in [False, 'off']: diff --git a/devito/ir/clusters/algorithms.py b/devito/ir/clusters/algorithms.py index 1a05f0842a..5323539dd4 100644 --- a/devito/ir/clusters/algorithms.py +++ b/devito/ir/clusters/algorithms.py @@ -5,7 +5,7 @@ import numpy as np import sympy -from devito.exceptions import InvalidOperator +from devito.exceptions import CompilationError from devito.finite_differences.elementary import Max, Min from devito.ir.support import (Any, Backward, Forward, IterationSpace, erange, pull_dims, null_ispace) @@ -306,8 +306,8 @@ def callback(self, clusters, prefix): elif len(sis) == 1: si = sis.pop() else: - raise InvalidOperator("Cannot use multiple SteppingDimensions " - "to index into a Function") + raise CompilationError("Cannot use multiple SteppingDimensions " + "to index into a Function") size = i.function.shape_allocated[d] assert is_integer(size) diff --git a/devito/operator/operator.py b/devito/operator/operator.py index 90e827de84..15dc6d1c8a 100644 --- a/devito/operator/operator.py +++ b/devito/operator/operator.py @@ -11,7 +11,7 @@ from devito.arch import ANYCPU, Device, compiler_registry, platform_registry from devito.data import default_allocator -from devito.exceptions import InvalidOperator, ExecutionError +from devito.exceptions import CompilationError, ExecutionError from devito.logger import debug, info, perf, warning, is_log_enabled_for, switch_log_level from devito.ir.equations import LoweredEq, lower_exprs, concretize_subdims from devito.ir.clusters import ClusterGroup, clusterize @@ -188,7 +188,8 @@ def _sanitize_exprs(cls, expressions, **kwargs): for i in expressions: if not isinstance(i, Evaluable): - raise InvalidOperator("`%s` is not an `Evaluable` object" % str(i)) + raise CompilationError("`%s` is not an Evaluable object, " + "check again your Equation" % str(i)) return expressions diff --git a/devito/passes/clusters/aliases.py b/devito/passes/clusters/aliases.py index ca5343c9ac..e04b2bc67f 100644 --- a/devito/passes/clusters/aliases.py +++ b/devito/passes/clusters/aliases.py @@ -5,6 +5,7 @@ import numpy as np import sympy +from devito.exceptions import CompilationError from devito.finite_differences import EvalDerivative, IndexDerivative, Weights from devito.ir import (SEQUENTIAL, PARALLEL_IF_PVT, SEPARABLE, Forward, IterationSpace, Interval, Cluster, ExprGeometry, Queue, @@ -372,9 +373,10 @@ def _select(self, variants): try: return variants[self.opt_schedule_strategy] except IndexError: - raise ValueError("Illegal schedule %d; " - "generated %d schedules in total" - % (self.opt_schedule_strategy, len(variants))) + raise CompilationError( + "Illegal schedule %d; generated %d schedules in total" + % (self.opt_schedule_strategy, len(variants)) + ) return pick_best(variants) diff --git a/devito/passes/clusters/buffering.py b/devito/passes/clusters/buffering.py index 5a6cddb87e..68d2c7d95e 100644 --- a/devito/passes/clusters/buffering.py +++ b/devito/passes/clusters/buffering.py @@ -8,7 +8,7 @@ from devito.ir import (Cluster, Backward, Forward, GuardBound, Interval, IntervalGroup, IterationSpace, Properties, Queue, Vector, InitArray, lower_exprs, vmax, vmin) -from devito.exceptions import InvalidOperator +from devito.exceptions import CompilationError from devito.logger import warning from devito.passes.clusters.utils import is_memcpy from devito.symbolics import IntDiv, retrieve_functions, uxreplace @@ -312,8 +312,8 @@ def generate_buffers(clusters, key, sregistry, options, **kwargs): dims = [d for d in f.dimensions if d not in bdims] if len(dims) != 1: - raise InvalidOperator("Unsupported multi-dimensional `buffering` " - "required by `%s`" % f) + raise CompilationError("Unsupported multi-dimensional `buffering` " + "required by `%s`" % f) dim = dims.pop() if is_buffering(exprs): @@ -397,8 +397,8 @@ def __init__(self, f, b, clusters): ispaces = {i.lift(self.bdims, v=stamp) for i in ispaces} if len(ispaces) > 1: - raise InvalidOperator("Unsupported `buffering` over different " - "IterationSpaces") + raise CompilationError("Unsupported `buffering` over different " + "IterationSpaces") assert len(ispaces) == 1, "Unexpected form of `buffering`" self.ispace = ispaces.pop() diff --git a/devito/passes/iet/orchestration.py b/devito/passes/iet/orchestration.py index 952e1cbb6f..b807fd561b 100644 --- a/devito/passes/iet/orchestration.py +++ b/devito/passes/iet/orchestration.py @@ -3,7 +3,7 @@ from sympy import Or -from devito.exceptions import InvalidOperator +from devito.exceptions import CompilationError from devito.ir.iet import (Call, Callable, List, SyncSpot, FindNodes, Transformer, BlankLine, BusyWait, DummyExpr, AsyncCall, AsyncCallable, make_callable, derive_parameters) @@ -156,7 +156,7 @@ def process(self, iet): layers = {infer_layer(s.function) for s in sync_ops} if len(layers) != 1: - raise InvalidOperator("Unsupported streaming case") + raise CompilationError("Unsupported streaming case") layer = layers.pop() n1, v = callbacks[t](subs.get(n0, n0), sync_ops, layer) diff --git a/tests/test_buffering.py b/tests/test_buffering.py index a7472ae22c..aebde40eff 100644 --- a/tests/test_buffering.py +++ b/tests/test_buffering.py @@ -6,7 +6,7 @@ SubDomain, ConditionalDimension, configuration, switchconfig) from devito.arch.archinfo import AppleArm from devito.ir import FindSymbols, retrieve_iteration_tree -from devito.exceptions import InvalidOperator +from devito.exceptions import CompilationError def test_read_write(): @@ -353,7 +353,7 @@ def define(self, dimensions): try: Operator(eqns, opt='buffering') - except InvalidOperator: + except CompilationError: assert True except: assert False From 587ecfb5cff26d3396403e4a7997e767b3dfc489 Mon Sep 17 00:00:00 2001 From: Fabio Luporini Date: Tue, 17 Dec 2024 09:34:46 +0000 Subject: [PATCH 04/11] misc: Replace ValueError with InvalidArgument where necessary --- devito/operator/operator.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/devito/operator/operator.py b/devito/operator/operator.py index 15dc6d1c8a..f56f6b93fc 100644 --- a/devito/operator/operator.py +++ b/devito/operator/operator.py @@ -11,7 +11,8 @@ from devito.arch import ANYCPU, Device, compiler_registry, platform_registry from devito.data import default_allocator -from devito.exceptions import CompilationError, ExecutionError +from devito.exceptions import (CompilationError, ExecutionError, InvalidArgument, + InvalidOperator) from devito.logger import debug, info, perf, warning, is_log_enabled_for, switch_log_level from devito.ir.equations import LoweredEq, lower_exprs, concretize_subdims from devito.ir.clusters import ClusterGroup, clusterize @@ -553,7 +554,7 @@ def _prepare_arguments(self, autotune=None, **kwargs): if not configuration['ignore-unknowns']: for k, v in kwargs.items(): if k not in self._known_arguments: - raise ValueError("Unrecognized argument %s=%s" % (k, v)) + raise InvalidArgument("Unrecognized argument %s=%s" % (k, v)) # Pre-process Dimension overrides. This may help ruling out ambiguities # when processing the `defaults` arguments. A topological sorting is used @@ -583,8 +584,10 @@ def _prepare_arguments(self, autotune=None, **kwargs): try: args.reduce_inplace() except ValueError: - raise ValueError("Override `%s` is incompatible with overrides `%s`" % - (p, [i for i in overrides if i.name in args])) + raise InvalidArgument( + "Override `%s` is incompatible with overrides `%s`" % + (p, [i for i in overrides if i.name in args]) + ) # Process data-carrier defaults for p in defaults: @@ -604,10 +607,11 @@ def _prepare_arguments(self, autotune=None, **kwargs): # `fact` is supplied w/o overriding `usave`; that's legal pass elif is_integer(args[k]) and not contains_val(args[k], v): - raise ValueError("Default `%s` is incompatible with other args as " - "`%s=%s`, while `%s=%s` is expected. Perhaps you " - "forgot to override `%s`?" % - (p, k, v, k, args[k], p)) + raise InvalidArgument( + "Default `%s` is incompatible with other args as `%s=%s`, " + "while `%s=%s` is expected. Perhaps you forgot to override " + "`%s`?" % (p, k, v, k, args[k], p) + ) args = kwargs['args'] = args.reduce_all() @@ -726,7 +730,7 @@ def arguments(self, **kwargs): # Check all arguments are present for p in self.parameters: if args.get(p.name) is None: - raise ValueError("No value found for parameter %s" % p.name) + raise InvalidArgument("No value found for parameter %s" % p.name) return args # Code generation and JIT compilation From 02dc1698e49e16a9c2f3881ffd56fc7e3d9c2064 Mon Sep 17 00:00:00 2001 From: Fabio Luporini Date: Tue, 17 Dec 2024 09:37:02 +0000 Subject: [PATCH 05/11] misc: Inherit from ValueError for back compat --- devito/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devito/exceptions.py b/devito/exceptions.py index 2c93d457cf..b15c5fd32f 100644 --- a/devito/exceptions.py +++ b/devito/exceptions.py @@ -18,7 +18,7 @@ class CompilationError(DevitoError): """ -class InvalidArgument(DevitoError): +class InvalidArgument(ValueError, DevitoError): """ Raised by the runtime system when an `op.apply(...)` argument, either a default argument or a user-provided one ("override"), is not valid. From cb89186175663c37d8e7beea750f4c6daef1322d Mon Sep 17 00:00:00 2001 From: Fabio Luporini Date: Tue, 17 Dec 2024 11:00:03 +0000 Subject: [PATCH 06/11] misc: Support more types of errors --- devito/operator/operator.py | 12 ++++++++++++ devito/passes/iet/errors.py | 2 ++ 2 files changed, 14 insertions(+) diff --git a/devito/operator/operator.py b/devito/operator/operator.py index f56f6b93fc..dd65b44d23 100644 --- a/devito/operator/operator.py +++ b/devito/operator/operator.py @@ -697,6 +697,18 @@ def _postprocess_errors(self, retval): raise ExecutionError("Detected nan/inf in some output Functions") elif retval == error_mapper['KernelLaunch']: raise ExecutionError("Kernel launch failed") + elif retval == error_mapper['KernelLaunchOutOfResources']: + raise ExecutionError( + "Kernel launch failed due to insufficient resources. This may be " + "due to excessive register pressure in one of the Operator " + "kernels. Try supplying a smaller `par-tile` value." + ) + elif retval == error_mapper['KernelLaunchUnknown']: + raise ExecutionError( + "Kernel launch failed due to an unknown error. This might " + "simply indicate memory corruption, but also, in a more unlikely " + "case, a hardware issue. Please report this issue to the " + "Devito team.") else: raise ExecutionError("An error occurred during execution") diff --git a/devito/passes/iet/errors.py b/devito/passes/iet/errors.py index 6d8f60526a..13f1101a3f 100644 --- a/devito/passes/iet/errors.py +++ b/devito/passes/iet/errors.py @@ -108,4 +108,6 @@ class Retval(LocalObject, Expr): error_mapper = { 'Stability': 100, 'KernelLaunch': 200, + 'KernelLaunchOutOfResources': 201, + 'KernelLaunchUnknown': 202, } From 11292e5b75fc061996a66f5370688c64fe43ff4c Mon Sep 17 00:00:00 2001 From: Fabio Luporini Date: Tue, 17 Dec 2024 13:58:54 +0000 Subject: [PATCH 07/11] misc: Convert into f-strings --- devito/operator/operator.py | 18 +++++++++--------- devito/passes/clusters/aliases.py | 4 ++-- devito/passes/clusters/buffering.py | 4 ++-- devito/types/dense.py | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/devito/operator/operator.py b/devito/operator/operator.py index dd65b44d23..13c86047cd 100644 --- a/devito/operator/operator.py +++ b/devito/operator/operator.py @@ -189,8 +189,8 @@ def _sanitize_exprs(cls, expressions, **kwargs): for i in expressions: if not isinstance(i, Evaluable): - raise CompilationError("`%s` is not an Evaluable object, " - "check again your Equation" % str(i)) + raise CompilationError(f"`{i!s}` is not an Evaluable object; " + "check your equation again") return expressions @@ -554,7 +554,7 @@ def _prepare_arguments(self, autotune=None, **kwargs): if not configuration['ignore-unknowns']: for k, v in kwargs.items(): if k not in self._known_arguments: - raise InvalidArgument("Unrecognized argument %s=%s" % (k, v)) + raise InvalidArgument(f"Unrecognized argument `{k}={v}`") # Pre-process Dimension overrides. This may help ruling out ambiguities # when processing the `defaults` arguments. A topological sorting is used @@ -584,9 +584,9 @@ def _prepare_arguments(self, autotune=None, **kwargs): try: args.reduce_inplace() except ValueError: + v = [i for i in overrides if i.name in args] raise InvalidArgument( - "Override `%s` is incompatible with overrides `%s`" % - (p, [i for i in overrides if i.name in args]) + f"Override `{p}` is incompatible with overrides `{v}`" ) # Process data-carrier defaults @@ -608,9 +608,9 @@ def _prepare_arguments(self, autotune=None, **kwargs): pass elif is_integer(args[k]) and not contains_val(args[k], v): raise InvalidArgument( - "Default `%s` is incompatible with other args as `%s=%s`, " - "while `%s=%s` is expected. Perhaps you forgot to override " - "`%s`?" % (p, k, v, k, args[k], p) + f"Default `{p}` is incompatible with other args as " + f"`{k}={v}`, while `{k}={args[k]}` is expected. Perhaps " + f"you forgot to override `{p}`?" ) args = kwargs['args'] = args.reduce_all() @@ -742,7 +742,7 @@ def arguments(self, **kwargs): # Check all arguments are present for p in self.parameters: if args.get(p.name) is None: - raise InvalidArgument("No value found for parameter %s" % p.name) + raise InvalidArgument(f"No value found for parameter `{p.name}`") return args # Code generation and JIT compilation diff --git a/devito/passes/clusters/aliases.py b/devito/passes/clusters/aliases.py index e04b2bc67f..8f03246f30 100644 --- a/devito/passes/clusters/aliases.py +++ b/devito/passes/clusters/aliases.py @@ -374,8 +374,8 @@ def _select(self, variants): return variants[self.opt_schedule_strategy] except IndexError: raise CompilationError( - "Illegal schedule %d; generated %d schedules in total" - % (self.opt_schedule_strategy, len(variants)) + f"Illegal schedule {self.opt_schedule_strategy}; " + f"generated {len(variants)} schedules in total" ) return pick_best(variants) diff --git a/devito/passes/clusters/buffering.py b/devito/passes/clusters/buffering.py index 68d2c7d95e..88a07816f3 100644 --- a/devito/passes/clusters/buffering.py +++ b/devito/passes/clusters/buffering.py @@ -312,8 +312,8 @@ def generate_buffers(clusters, key, sregistry, options, **kwargs): dims = [d for d in f.dimensions if d not in bdims] if len(dims) != 1: - raise CompilationError("Unsupported multi-dimensional `buffering` " - "required by `%s`" % f) + raise CompilationError(f"Unsupported multi-dimensional `buffering` " + f"required by `{f}`") dim = dims.pop() if is_buffering(exprs): diff --git a/devito/types/dense.py b/devito/types/dense.py index 5ba24f48a1..b05beb656c 100644 --- a/devito/types/dense.py +++ b/devito/types/dense.py @@ -1375,8 +1375,8 @@ def __init_finalize__(self, *args, **kwargs): required_mem = np.dtype(self.dtype).itemsize * self.size if required_mem > available_mem: raise MemoryError( - "Trying to allocate more memory (%s) for `%s` than available (%s)" - % (humanbytes(required_mem), self.name, humanbytes(available_mem)) + f"Trying to allocate more memory ({humanbytes(required_mem)}) " + f"for `{self.name}` than available ({humanbytes(available_mem)})" ) if not isinstance(self.time_order, int): raise TypeError("`time_order` must be int") From bcac0449b4034b766a60a4fd995d5833b1b81b75 Mon Sep 17 00:00:00 2001 From: Fabio Luporini Date: Tue, 17 Dec 2024 14:20:58 +0000 Subject: [PATCH 08/11] tests: Switch to pytest.raises --- devito/operator/operator.py | 2 +- tests/test_buffering.py | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/devito/operator/operator.py b/devito/operator/operator.py index 13c86047cd..ccfa5a249f 100644 --- a/devito/operator/operator.py +++ b/devito/operator/operator.py @@ -742,7 +742,7 @@ def arguments(self, **kwargs): # Check all arguments are present for p in self.parameters: if args.get(p.name) is None: - raise InvalidArgument(f"No value found for parameter `{p.name}`") + raise InvalidArgument(f"No value found for parameter {p.name}") return args # Code generation and JIT compilation diff --git a/tests/test_buffering.py b/tests/test_buffering.py index aebde40eff..c1196466e3 100644 --- a/tests/test_buffering.py +++ b/tests/test_buffering.py @@ -351,12 +351,8 @@ def define(self, dimensions): eqns = [Eq(u.forward, u + 1, subdomain=s_d0), Eq(u.forward, u.forward + 1, subdomain=s_d1)] - try: + with pytest.raises(CompilationError): Operator(eqns, opt='buffering') - except CompilationError: - assert True - except: - assert False @pytest.mark.xfail(reason="Cannot deal with non-overlapping SubDimensions yet") From 2c7be2f32f7ab5121148c3014ab835cd8cf5e5f7 Mon Sep 17 00:00:00 2001 From: Fabio Luporini Date: Thu, 19 Dec 2024 08:20:37 +0000 Subject: [PATCH 09/11] arch: Relax numa_domains --- devito/arch/archinfo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devito/arch/archinfo.py b/devito/arch/archinfo.py index ca3c52dd63..9681135871 100644 --- a/devito/arch/archinfo.py +++ b/devito/arch/archinfo.py @@ -734,7 +734,7 @@ def simd_items_per_reg(self, dtype): def numa_domains(self): try: return int(lscpu()['NUMA node(s)']) - except KeyError: + except (ValueError, TypeError, KeyError): warning("NUMA domain count autodetection failed") return 1 From d26ee157a97a1d1a22650987681c9a783a3ec033 Mon Sep 17 00:00:00 2001 From: Fabio Luporini Date: Fri, 20 Dec 2024 14:04:16 +0000 Subject: [PATCH 10/11] compiler: Comment key DataManager methods --- devito/passes/iet/definitions.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/devito/passes/iet/definitions.py b/devito/passes/iet/definitions.py index b5e761e28e..3532169754 100644 --- a/devito/passes/iet/definitions.py +++ b/devito/passes/iet/definitions.py @@ -168,11 +168,7 @@ def _alloc_mapped_array_on_high_bw_mem(self, site, obj, storage, *args): """ decl = Definition(obj) - # Allocating a mapped Array on the high bandwidth memory requires - # multiple statements, hence we implement it as a generic Callable - # to minimize code size, since different arrays will ultimately be - # able to reuse the same abstract Callable - + # Allocate the Array struct memptr = VOID(Byref(obj._C_symbol), '**') alignment = obj._data_alignment nbytes = SizeOf(obj._C_typedata) @@ -181,10 +177,12 @@ def _alloc_mapped_array_on_high_bw_mem(self, site, obj, storage, *args): nbytes_param = Symbol(name='nbytes', dtype=np.uint64, is_const=True) nbytes_arg = SizeOf(obj.indexed._C_typedata)*obj.size + # Allocate the underlying host data ffp0 = FieldFromPointer(obj._C_field_data, obj._C_symbol) memptr = VOID(Byref(ffp0), '**') allocs.append(self.lang['host-alloc-pin'](memptr, alignment, nbytes_param)) + # Initialize the Array struct ffp1 = FieldFromPointer(obj._C_field_nbytes, obj._C_symbol) init0 = DummyExpr(ffp1, nbytes_param) ffp2 = FieldFromPointer(obj._C_field_size, obj._C_symbol) @@ -193,8 +191,7 @@ def _alloc_mapped_array_on_high_bw_mem(self, site, obj, storage, *args): frees = [self.lang['host-free-pin'](ffp0), self.lang['host-free'](obj._C_symbol)] - # Not all backends require explicit allocation/deallocation of the - # `dmap` field + # Allocate the underlying device data, if required by the backend alloc, free = self._make_dmap_allocfree(obj, nbytes_param) # Chain together all allocs and frees @@ -203,6 +200,8 @@ def _alloc_mapped_array_on_high_bw_mem(self, site, obj, storage, *args): ret = Return(obj._C_symbol) + # Wrap everything in a Callable so that we can reuse the same code + # for equivalent Array structs name = self.sregistry.make_name(prefix='alloc') body = (decl, *allocs, init0, init1, ret) efunc0 = make_callable(name, body, retval=obj) @@ -210,6 +209,7 @@ def _alloc_mapped_array_on_high_bw_mem(self, site, obj, storage, *args): args[args.index(nbytes_param)] = nbytes_arg alloc = Call(name, args, retobj=obj) + # Same story for the frees name = self.sregistry.make_name(prefix='free') efunc1 = make_callable(name, frees) free = Call(name, efunc1.parameters) @@ -222,6 +222,7 @@ def _alloc_bundle_struct_on_high_bw_mem(self, site, obj, storage): """ decl = Definition(obj) + # Allocate the Bundle struct memptr = VOID(Byref(obj._C_symbol), '**') alignment = obj._data_alignment nbytes = SizeOf(obj._C_typedata) @@ -230,6 +231,7 @@ def _alloc_bundle_struct_on_high_bw_mem(self, site, obj, storage): nbytes_param = Symbol(name='nbytes', dtype=np.uint64, is_const=True) nbytes_arg = SizeOf(obj.indexed._C_typedata)*obj.size + # Initialize the Bundle struct ffp1 = FieldFromPointer(obj._C_field_nbytes, obj._C_symbol) init0 = DummyExpr(ffp1, nbytes_param) ffp2 = FieldFromPointer(obj._C_field_size, obj._C_symbol) @@ -239,6 +241,8 @@ def _alloc_bundle_struct_on_high_bw_mem(self, site, obj, storage): ret = Return(obj._C_symbol) + # Wrap everything in a Callable so that we can reuse the same code + # for equivalent Bundle structs name = self.sregistry.make_name(prefix='alloc') body = (decl, alloc, init0, init1, ret) efunc0 = make_callable(name, body, retval=obj) From 76651c8ec337e98d250bb6b5b811a6b4ebed8fab Mon Sep 17 00:00:00 2001 From: Fabio Luporini Date: Mon, 23 Dec 2024 13:55:30 +0000 Subject: [PATCH 11/11] compiler: Add AbstractFunction.is_persistent --- devito/builtins/utils.py | 2 +- devito/types/basic.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/devito/builtins/utils.py b/devito/builtins/utils.py index df7c7ddca3..67aef28ba0 100644 --- a/devito/builtins/utils.py +++ b/devito/builtins/utils.py @@ -148,7 +148,7 @@ def wrapper(*args, **kwargs): for i in args: try: - if i.is_transient: + if not i.is_persistent: raise ValueError(f"Cannot apply `{func.__name__}` to transient " f"function `{i.name}` on backend `{platform}`") except AttributeError: diff --git a/devito/types/basic.py b/devito/types/basic.py index e70a105296..9bdd6bc8dd 100644 --- a/devito/types/basic.py +++ b/devito/types/basic.py @@ -984,10 +984,10 @@ def __init_finalize__(self, *args, **kwargs): assert self._space in ['local', 'mapped', 'host'] # If True, the AbstractFunction is treated by the compiler as a "transient - # field", meaning that its content is only useful within an Operator - # execution, but the final data is not expected to be read back in - # Python-land by the user. This allows the compiler/run-time to apply - # certain optimizations, such as avoiding memory copies + # field", meaning that its content cannot be accessed by the user in + # Python-land. This allows the compiler/run-time to apply certain + # optimizations, such as avoiding memory copies across different Operator + # executions self._is_transient = kwargs.get('is_transient', False) # Averaging mode for off the grid evaluation @@ -1274,6 +1274,16 @@ def is_const(self): def is_transient(self): return self._is_transient + @property + def is_persistent(self): + """ + True if the AbstractFunction is persistent, i.e., its data is guaranteed + to exist across multiple Operator invocations, False otherwise. + By default, transient AbstractFunctions are not persistent. However, + subclasses may override this behavior. + """ + return not self.is_transient + @cached_property def properties(self): return frozendict([(i, getattr(self, i)) for i in self.__properties__])