diff --git a/pykokkos/core/compiler.py b/pykokkos/core/compiler.py index cf3f5c95..e37e12fb 100644 --- a/pykokkos/core/compiler.py +++ b/pykokkos/core/compiler.py @@ -8,7 +8,7 @@ from typing import Dict, List, Optional from pykokkos.core.parsers import Parser, PyKokkosEntity, PyKokkosStyles from pykokkos.core.translators import PyKokkosMembers, StaticTranslator -from pykokkos.interface import ExecutionSpace, UpdatedTypes +from pykokkos.interface import ExecutionSpace, UpdatedTypes, UpdatedDecorator import pykokkos.kokkos_manager as km from .cpp_setup import CppSetup from .module_setup import EntityMetadata, ModuleSetup @@ -51,7 +51,9 @@ def compile_object( module_setup: ModuleSetup, space: ExecutionSpace, force_uvm: bool, + updated_decorator: UpdatedDecorator, updated_types: Optional[UpdatedTypes] = None, + types_signature: Optional[str] = None ) -> Optional[PyKokkosMembers]: """ Compile an entity object for a single execution space @@ -59,17 +61,19 @@ def compile_object( :param entity_object: the module_setup object containing module info :param space: the execution space to compile for :param force_uvm: whether CudaUVMSpace is enabled + :param updated_decorator: Object for decorator specifiers :param updated_types: Object with with inferred types :returns: the PyKokkos members obtained during translation """ metadata = module_setup.metadata parser = self.get_parser(metadata.path) - types_signature = None if updated_types is None else updated_types.types_signature + hash: str = self.members_hash(metadata.path, metadata.name, types_signature) entity: PyKokkosEntity = parser.get_entity(metadata.name) types_inferred: bool = updated_types is not None + decorator_inferred: bool = updated_decorator is not None if types_inferred and entity.style is not PyKokkosStyles.workunit: raise Exception(f"Types are required for style: {entity.style}") @@ -78,6 +82,8 @@ def compile_object( if hash not in self.members: # True if pre-compiled if types_inferred: entity.AST = parser.fix_types(entity, updated_types) + if decorator_inferred: + entity.AST = parser.fix_decorator(entity, updated_decorator) self.members[hash] = self.extract_members(metadata) return self.members[hash] @@ -88,7 +94,9 @@ def compile_object( if types_inferred: entity.AST = parser.fix_types(entity, updated_types) - + if decorator_inferred: + entity.AST = parser.fix_decorator(entity, updated_decorator) + if hash in self.members: # True if compiled with another execution space members = self.members[hash] else: diff --git a/pykokkos/core/module_setup.py b/pykokkos/core/module_setup.py index a95fbd1a..bb9cba33 100644 --- a/pykokkos/core/module_setup.py +++ b/pykokkos/core/module_setup.py @@ -82,6 +82,7 @@ def __init__( ModuleSetup constructor :param entity: the functor/workunit/workload + :param types_signature: hash/string to identify workunit signature against types """ self.metadata: EntityMetadata @@ -129,7 +130,7 @@ def get_output_dir( :param main: the path to the main file in the current PyKokkos application :param metadata: the metadata of the entity being compiled :param space: the execution space to compile for - :param types_signature: optional identifier string for inferred types of parameters + :param types_signature: optional identifier/hash string for types of parameters :returns: the path to the output directory for a specific execution space """ diff --git a/pykokkos/core/parsers/parser.py b/pykokkos/core/parsers/parser.py index 5a34c2d0..324b8700 100644 --- a/pykokkos/core/parsers/parser.py +++ b/pykokkos/core/parsers/parser.py @@ -1,12 +1,11 @@ import ast import inspect -import copy from dataclasses import dataclass from enum import Enum, auto from typing import Callable, Dict, List, Tuple, Union from pykokkos.core import cppast -from pykokkos.interface import Decorator, UpdatedTypes, get_type_str +from pykokkos.interface import Decorator, UpdatedTypes, UpdatedDecorator, get_type_str class PyKokkosStyles(Enum): """ @@ -157,11 +156,6 @@ def fix_types(self, entity: PyKokkosEntity, updated_types: UpdatedTypes) -> ast. if needs_reset: entity_tree = self.reset_entity_tree(entity_tree, updated_types) - # if modifications to layout decorator is needed, do not change original inferences - if len(updated_types.layout_change): - inferred_layouts = copy.deepcopy(updated_types.layout_change) - entity_tree.decorator_list = self.fix_view_layout(entity_tree, inferred_layouts) - for arg_obj in entity_tree.args.args: # Type already provided by the user if arg_obj.arg not in updated_types.inferred_types: @@ -182,23 +176,22 @@ def check_self(self, entity_tree: ast.AST) -> bool: :returns: True if a 'self' argument exists, False otherwise ''' - for arg in entity_tree.args.args: - if arg.arg == "self": - return True + if entity_tree.args.args[0].arg == "self": + return True return False - def reset_entity_tree(self, entity_tree: ast.AST, updated_types: UpdatedTypes) -> ast.AST: + def reset_entity_tree(self, entity_tree: ast.AST, updated_obj: Union[UpdatedTypes, UpdatedDecorator]) -> ast.AST: ''' - Remove type annotations and self argument from the entity tree. This allows + Remove the inferred type annotations and self argument from the entity tree. This allows the types to be inserted again if they change dynamically - :param entity_tree: Ast of pykokkos entity being resiet - :param updated_types: inferred types information + :param entity_tree: Ast of pykokkos entity being reset + :param updated_obj: inferred types/decorator object that must have original inspect param list :returns: updated entity ast as it would be in the first run ''' args_list: List[ast.arg] = [] - param_list = updated_types.param_list + param_list = updated_obj.param_list for param in param_list: arg_obj = ast.arg(arg=param.name) if param.annotation is not inspect._empty: @@ -286,44 +279,38 @@ def get_annotation_node(self, type: str) -> ast.AST: return annotation_node - def fix_view_layout(self, node : ast.AST, layout_change: Dict[str, str]) -> List[ast.Call]: + def fix_decorator(self, entity : PyKokkosEntity, updated_decorator: UpdatedDecorator) -> ast.AST: ''' - Construct the decorator list (as in AST) with the missing layout decorators for pykokkos views + Add the decorator list with the specifiers for pykokkos views to the workunit AST :param node: ast object for the entity - :param layout_change: Dict that maps [view -> layout] + :param updated_decorator: Object with dict that maps view to its layout, space and trait :returns: decorator list ''' - assert len(node.decorator_list), f"Decorator cannot be missing for pykokkos workunit {node.name}" - # Decorator list will have ast.Call object as first element if user has provided layout decorators - is_layout_given: bool = isinstance(node.decorator_list[0], ast.Call) - - if is_layout_given: - # Filter out layouts already given by user - layout_change = self.filter_layout_change(node, layout_change) - - if len(layout_change): - call_obj = None - - if is_layout_given: # preserve the existing call object - call_obj = node.decorator_list[0] - else: - call_obj= ast.Call() - call_obj.func = ast.Attribute(value=ast.Name(id=self.pk_import, ctx=ast.Load()), attr='workunit', ctx=ast.Load()) - call_obj.args = [] - call_obj.keywords = [] - - for view, layout in layout_change.items(): - call_obj.keywords.append(self.get_keyword_node(view, layout)) - - return [call_obj] - - # no change needed - return node.decorator_list + entity_tree = entity.AST + needs_reset: bool = self.check_self(entity_tree) + if needs_reset: + entity_tree = self.reset_entity_tree(entity_tree, updated_decorator) + assert len(entity_tree.decorator_list), f"Decorator cannot be missing for pykokkos workunit {entity_tree.name}" + + if not len(updated_decorator.inferred_decorator): + # no change needed + return entity_tree.decorator_list + + call_obj= ast.Call() + call_obj.func = ast.Attribute(value=ast.Name(id=self.pk_import, ctx=ast.Load()), attr='workunit', ctx=ast.Load()) + call_obj.args = [] + call_obj.keywords = [] + + for view, specifier_dict in updated_decorator.inferred_decorator.items(): + call_obj.keywords.append(self.get_keyword_node(view, specifier_dict)) + + entity_tree.decorator_list = [call_obj] + return entity_tree - def get_keyword_node(self, view_name: str, layout: str) -> ast.keyword: + def get_keyword_node(self, view_name: str, specifiers: Dict[str, str]) -> ast.keyword: ''' Make the ast.keyword node to be added to the decorator list @@ -332,6 +319,23 @@ def get_keyword_node(self, view_name: str, layout: str) -> ast.keyword: :returns: corresponding ast.keyword node that can be added to decorator list ''' + skip_space: bool = False if specifiers['trait'] == "Unmanaged" else True + keywords_list: List[ast.keyword] = [] + attr_names = {'layout' : 'Layout', 'space' : 'MemorySpace', 'trait' : 'Trait'} + for specifier, value in specifiers.items(): + if specifier == "space" and skip_space: + continue + keywords_list.append( + ast.keyword( + arg=specifier, + value=ast.Attribute( + value=ast.Attribute( + value=ast.Name(id=self.pk_import, ctx=ast.Load()), + attr=attr_names[specifier], ctx=ast.Load()), + attr=value, ctx=ast.Load() + ) + ) + ) return ast.keyword( arg=view_name, value=ast.Call( @@ -340,38 +344,10 @@ def get_keyword_node(self, view_name: str, layout: str) -> ast.keyword: attr='ViewTypeInfo', ctx=ast.Load() ), args=[], - keywords=[ - ast.keyword( - arg='layout', - value=ast.Attribute( - value=ast.Attribute( - value=ast.Name(id=self.pk_import, ctx=ast.Load()), - attr='Layout', ctx=ast.Load()), - attr= layout, ctx=ast.Load() - ) - ) - ] + keywords= keywords_list ) ) - @staticmethod - def filter_layout_change(node: ast.AST, working_dict: Dict[str, str]) -> Dict[str, str]: - ''' - Filter out (from the working dict) the views whose layouts decorators user has already provided - - :param node: AST of pykokkos entity to work with - :param working_dict: map of view -> layout - :returns updated working dict - ''' - - call_obj = node.decorator_list[0] - # iterating over view layout decorators in signature (of workunit) - for keyword_obj in call_obj.keywords: - if keyword_obj.arg in working_dict: - # user provided layout decorator for this view, remove from working dict - del working_dict[keyword_obj.arg] - - return working_dict @staticmethod def is_classtype(node: ast.stmt, pk_import: str) -> bool: diff --git a/pykokkos/core/runtime.py b/pykokkos/core/runtime.py index 9ce28663..86880843 100644 --- a/pykokkos/core/runtime.py +++ b/pykokkos/core/runtime.py @@ -11,8 +11,8 @@ from pykokkos.core.visitors import visitors_util from pykokkos.interface import ( DataType, ExecutionPolicy, ExecutionSpace, MemorySpace, - RandomPool, RangePolicy, TeamPolicy, View, ViewType, UpdatedTypes, - is_host_execution_space + RandomPool, RangePolicy, TeamPolicy, View, ViewType, UpdatedTypes, UpdatedDecorator, + is_host_execution_space, get_types_signature ) import pykokkos.kokkos_manager as km @@ -57,18 +57,25 @@ def precompile_workunit( self, workunit: Callable[..., None], space: ExecutionSpace, - updated_types: Optional[UpdatedTypes] = None - ) -> Optional[PyKokkosMembers]: + updated_decorator: UpdatedDecorator, + updated_types: Optional[UpdatedTypes] = None, + types_signature: Optional[str] = None, + ) -> Optional[PyKokkosMembers]: """ precompile the workunit :param workunit: the workunit function object + :param updated_decorator: Object for decorator specifier + :param updated_types: Object with type inference information :param space: the ExecutionSpace for which the bindings are generated :returns: the members the functor is containing """ - module_setup: ModuleSetup = self.get_module_setup(workunit, space, updated_types) - members: Optional[PyKokkosMembers] = self.compiler.compile_object(module_setup, space, km.is_uvm_enabled(), updated_types) + module_setup: ModuleSetup = self.get_module_setup(workunit, space, types_signature) + members: Optional[PyKokkosMembers] = self.compiler.compile_object(module_setup, + space, km.is_uvm_enabled(), + updated_decorator, + updated_types, types_signature) return members @@ -92,6 +99,7 @@ def run_workunit( name: Optional[str], policy: ExecutionPolicy, workunit: Callable[..., None], + updated_decorator: UpdatedDecorator, updated_types: Optional[UpdatedTypes] = None, operation: Optional[str] = None, initial_value: Union[float, int] = 0, @@ -104,6 +112,7 @@ def run_workunit( :param policy: the execution policy of the operation :param workunit: the workunit function object :param kwargs: the keyword arguments passed to the workunit + :param updated_decorator: Object with decorator specifier information :param updated_types: UpdatedTypes object with type inferrence information :param operation: the name of the operation "for", "reduce", or "scan" :param initial_value: the initial value of the accumulator @@ -117,11 +126,12 @@ def run_workunit( raise RuntimeError("ERROR: operation cannot be None for Debug") return run_workunit_debug(policy, workunit, operation, initial_value, **kwargs) - members: Optional[PyKokkosMembers] = self.precompile_workunit(workunit, execution_space, updated_types) + types_signature: str = get_types_signature(updated_types, updated_decorator, execution_space) + members: Optional[PyKokkosMembers] = self.precompile_workunit(workunit, execution_space, updated_decorator, updated_types, types_signature) if members is None: raise RuntimeError("ERROR: members cannot be none") - module_setup: ModuleSetup = self.get_module_setup(workunit, execution_space, updated_types) + module_setup: ModuleSetup = self.get_module_setup(workunit, execution_space, types_signature) return self.execute(workunit, module_setup, members, execution_space, policy=policy, name=name, **kwargs) def is_debug(self, space: ExecutionSpace) -> bool: @@ -429,19 +439,18 @@ def get_module_setup( self, entity: Union[object, Callable[..., None]], space: ExecutionSpace, - updated_types: Optional[UpdatedTypes] = None + types_signature: Optional[str] = None ) -> ModuleSetup: """ Get the compiled module setup information unique to an entity + space :param entity: the workload or workunit object :param space: the execution space - :updated_types: Object with information about inferred types (if any) + :types_signature: Hash/identifer string for workunit module against data types :returns: the ModuleSetup object """ space: ExecutionSpace = km.get_default_space() if space is ExecutionSpace.Debug else space - types_signature: str = None if updated_types is None else updated_types.types_signature module_setup_id = self.get_module_setup_id(entity, space, types_signature) @@ -467,7 +476,7 @@ def get_module_setup_id( :param entity: the workload or workunit object :param space: the execution space - :param types_signature: optional identifier string for inferred types of parameters + :param types_signature: optional identifier/hash string for types of parameters against workunit module :returns: a unique tuple per entity and space """ diff --git a/pykokkos/interface/__init__.py b/pykokkos/interface/__init__.py index 9c299bc6..0616df5d 100644 --- a/pykokkos/interface/__init__.py +++ b/pykokkos/interface/__init__.py @@ -53,7 +53,7 @@ ) from .ext_module import compile_into_module -from .args_type_inference import UpdatedTypes, get_annotations, get_type_str +from .args_type_inference import UpdatedTypes, UpdatedDecorator, get_annotations, get_type_str, get_types_signature def fence(): pass diff --git a/pykokkos/interface/args_type_inference.py b/pykokkos/interface/args_type_inference.py index 98d597e4..c6fddc79 100644 --- a/pykokkos/interface/args_type_inference.py +++ b/pykokkos/interface/args_type_inference.py @@ -1,12 +1,14 @@ import inspect from dataclasses import dataclass -from typing import Callable, Dict, Optional, Tuple, Union, List, Any +from typing import Callable, Dict, Optional, Tuple, Union, List +import hashlib + import pykokkos.kokkos_manager as km from .execution_policy import MDRangePolicy, TeamPolicy, TeamThreadRange, RangePolicy, ExecutionPolicy, ExecutionSpace -from .views import View, ViewType -from .layout import Layout, get_default_layout +from .views import View, ViewType, Trait from .data_types import DataType, DataTypeClass + @dataclass class HandledArgs: """ @@ -30,9 +32,16 @@ class UpdatedTypes: workunit: Callable inferred_types: Dict[str, str] # type information stored as string: identifier -> type param_list: List[str] - layout_change: Dict[str, str] # layout for views - types_signature: str # unique string identifer for inferred paramater types +@dataclass +class UpdatedDecorator: + """ + Class for storing inferred decorator specifiers + (Making Pykokkos more pythonic by automatically inferring types) + """ + + inferred_decorator: Dict[str, Dict[str, str]] # against each view (first dict) values for layout, space, and trait + param_list: List[str] # Original params needed to reset AST incase user provides all annotations # DataType class has all supported pk datatypes, we ignore class members starting with __, add enum duplicate aliases SUPPORTED_NP_DTYPES = [attr for attr in dir(DataType) if not attr.startswith("__")] + ["float64", "float32"] @@ -112,8 +121,12 @@ def get_annotations(parallel_type: str, handled_args: HandledArgs, *args, passed ''' param_list = list(inspect.signature(handled_args.workunit).parameters.values()) - args_list = list(*args) - updated_types = UpdatedTypes(workunit=handled_args.workunit, inferred_types={}, param_list=param_list, layout_change={}, types_signature=None) + + updated_types = UpdatedTypes( + workunit=handled_args.workunit, + inferred_types={}, + param_list=param_list, + ) policy_params: int = len(handled_args.policy.begin) if isinstance(handled_args.policy, MDRangePolicy) else 1 # check if all annotations are already provided @@ -121,6 +134,7 @@ def get_annotations(parallel_type: str, handled_args: HandledArgs, *args, passed for param in param_list: if param.annotation is inspect._empty: missing = True + break if not missing: return None # accumulator @@ -134,33 +148,54 @@ def get_annotations(parallel_type: str, handled_args: HandledArgs, *args, passed updated_types = infer_policy_args(param_list, policy_params, handled_args.policy, parallel_type, updated_types) # Policy parameters are the only parameters - if len(param_list) == policy_params: + if not len(passed_kwargs): if not len(updated_types.inferred_types): return None return updated_types - # Handle keyword args, make sure they are treated by queuing them in args - if len(passed_kwargs): - # add value to arguments list so the value can be assessed - for param in param_list[policy_params:]: - if param.name in passed_kwargs: - args_list.append(passed_kwargs[param.name]) + # Additional keyword arguments that may have been passed + updated_types = infer_other_args(param_list, passed_kwargs, updated_types) - # Handling arguments other than policy args, they begin at value_idx in args list - # e.g idx=3 -> parallel_for("label", policy, workunit, ...) or if name ("label") is missing: 2 - value_idx: int = 3 if handled_args.name != None else 2 + if not len(updated_types.inferred_types): return None - assert (len(param_list) - policy_params) == len(args_list) - value_idx, f"Unannotated arguments mismatch {len(param_list) - policy_params} != {len(args_list) - value_idx}" + return updated_types - # At this point there must more arguments to the workunit that may not have their types annotated - # These parameters may also not have raw values associated in the stand alone format -> infer types from the argument list - updated_types = infer_other_args(param_list, policy_params, args_list, value_idx, handled_args.policy.space, updated_types) +def get_views_decorator(handled_args: HandledArgs, passed_kwargs) -> UpdatedDecorator: + ''' + Extract the layout, space, trait information against view: will be used to construct decorator + specifiers + + :param handled_args: Processed arguments passed to the dispatch + :param passed_kwargs: Keyword arguments passed to parallel dispatch (has views) + :returns: UpdatedDecorator object + ''' - if not len(updated_types.inferred_types) and not len(updated_types.layout_change): return None + param_list = list(inspect.signature(handled_args.workunit).parameters.values()) - updated_types.types_signature = get_types_sig(updated_types.inferred_types, updated_types.layout_change) + updated_decorator = UpdatedDecorator( + inferred_decorator = {}, + param_list=param_list + ) + + for kwarg in passed_kwargs: + if kwarg not in [param.name for param in param_list ]: + raise Exception(f"Unknown kwarg: {kwarg} passed") + + value = passed_kwargs[kwarg] + if not isinstance(value, View): + continue + + if kwarg not in updated_decorator.inferred_decorator: + updated_decorator.inferred_decorator[kwarg] = {} + updated_decorator.inferred_decorator[kwarg]['trait'] = str(value.trait).split(".")[1] + updated_decorator.inferred_decorator[kwarg]['layout'] = str(value.layout).split(".")[1] + updated_decorator.inferred_decorator[kwarg]['space'] = str(value.space).split(".")[1] + + if not len(updated_decorator.inferred_decorator): + return None + + return updated_decorator - return updated_types def infer_policy_args( param_list: List[inspect.Parameter], @@ -214,11 +249,8 @@ def infer_policy_args( def infer_other_args( - param_list: List[inspect.Parameter], - policy_params: int, - args_list: List[Any], - start_idx: int, - space: ExecutionSpace, + param_list: List[inspect.Parameter], + passed_kwargs, updated_types: UpdatedTypes ) -> UpdatedTypes: ''' @@ -232,13 +264,13 @@ def infer_other_args( :returns: Updated UpdatedTypes object with inferred types ''' - for i in range(policy_params , len(param_list)): - param = param_list[i] - value = args_list[start_idx + i - policy_params] + for name, value in passed_kwargs.items(): + param = None + for iparam in param_list: + if name == iparam.name: param = iparam - if isinstance(value, View): - inferred_layout = value.layout if value.layout is not Layout.LayoutDefault else get_default_layout(space) - updated_types.layout_change[param.name] = "LayoutRight" if inferred_layout == Layout.LayoutRight else "LayoutLeft" + if param is None: + continue if param.annotation is not inspect._empty: continue @@ -276,6 +308,8 @@ def infer_other_args( def get_pk_datatype(view_dtype): ''' + Infer the dataype of view e.g pk.View1D[] + :param view_dtype: view.dtype whose datatype is to be determined as string :returns: the type of custom pkDataType as string ''' @@ -293,42 +327,38 @@ def get_pk_datatype(view_dtype): return dtype -def get_types_sig(inferred_types: Dict[str, str], inferred_layouts: Dict[str, str]) -> str: +def get_types_signature(updated_types: UpdatedTypes, updated_decorator: UpdatedDecorator, execution_space: ExecutionSpace) -> str: ''' + Generates a signature/hash to represent the signature of the workunit: used for module setup + :param inferred_types: Dict that stores arg name against its inferred type - :param inferred_layouts: Dict that stores view name against its inferred layout + :param inferred_decorator: Dict that stores the layout of view name against its inferred layout + :param execution_space: The execution space of the workunit :returns: a string representing inferred types ''' - if not len(inferred_layouts) and not len(inferred_types): - return None - signature:str = "" - for name, i_type in inferred_types.items(): - signature += i_type - if "View" in i_type and name in inferred_layouts: - signature += inferred_layouts[name] + if updated_types is not None: + for name, i_type in updated_types.inferred_types.items(): + signature += i_type + + if updated_decorator is not None: + for name in updated_decorator.inferred_decorator: + space_str = str(execution_space) + layout_str = updated_decorator.inferred_decorator[name]['layout'] + trait_str = updated_decorator.inferred_decorator[name]['trait'] + + signature += layout_str + space_str + trait_str - # if there were no inferred types but only layouts if signature == "": - for name, l_type in inferred_layouts.items(): - signature += name + l_type + return None # Compacting - signature = signature.replace("View", "") - signature = signature.replace("Acc:", "" ) - signature = signature.replace("TeamMember", "T") - signature = signature.replace("numpy:", "np") - signature = signature.replace("LayoutRight", "R") - signature = signature.replace("LayoutLeft", "L") - signature = signature.replace(":", "") - signature = signature.replace("double", "d") - signature = signature.replace("int", "i") - signature = signature.replace("bool", "b") - signature = signature.replace("float", "f") + signature = hashlib.md5(signature.encode()).hexdigest() return signature + def get_type_str(inspect_type: inspect.Parameter.annotation) -> str: ''' Given a user provided inspect.annotation string return the equivalent type inferrence string (used internally). diff --git a/pykokkos/interface/parallel_dispatch.py b/pykokkos/interface/parallel_dispatch.py index 66f2625f..aec80554 100644 --- a/pykokkos/interface/parallel_dispatch.py +++ b/pykokkos/interface/parallel_dispatch.py @@ -7,7 +7,7 @@ from .execution_policy import ExecutionPolicy from .execution_space import ExecutionSpace -from .args_type_inference import UpdatedTypes, HandledArgs, get_annotations, handle_args +from .args_type_inference import UpdatedTypes, UpdatedDecorator, HandledArgs, get_annotations, get_views_decorator, handle_args workunit_cache: Dict[int, Callable] = {} @@ -100,11 +100,13 @@ def parallel_for(*args, **kwargs) -> None: handled_args: HandledArgs = handle_args(True, args) updated_types: UpdatedTypes = get_annotations("parallel_for", handled_args, args, passed_kwargs=kwargs) + updated_decorator: UpdatedDecorator = get_views_decorator(handled_args, passed_kwargs=kwargs) func, args = runtime_singleton.runtime.run_workunit( handled_args.name, handled_args.policy, handled_args.workunit, + updated_decorator, updated_types, "for", **kwargs) @@ -147,11 +149,13 @@ def reduce_body(operation: str, *args, **kwargs) -> Union[float, int]: #* Inferring missing data types updated_types: UpdatedTypes = get_annotations("parallel_"+operation, handled_args, args, passed_kwargs=kwargs) + updated_decorator: UpdatedDecorator = get_views_decorator(handled_args, passed_kwargs=kwargs) func, args = runtime_singleton.runtime.run_workunit( handled_args.name, handled_args.policy, handled_args.workunit, + updated_decorator, updated_types, operation, **kwargs) diff --git a/tests/test_typeinference.py b/tests/test_typeinference.py index 7f18979c..d44bb006 100644 --- a/tests/test_typeinference.py +++ b/tests/test_typeinference.py @@ -1,6 +1,12 @@ import unittest import numpy as np import pykokkos as pk +import pytest +try: + import cupy as cp + HAS_CUDA = True +except ImportError: + HAS_CUDA = False # workunits @pk.workunit @@ -99,6 +105,9 @@ def add_all_init(i, view, i8, i16, i32, i64): def add_two_init(i, view, v1, v2): view[i] = v1 + v2 +@pk.workunit +def no_view(i: int, acc: pk.Acc[pk.double], n): + acc=acc + n; class TestTypeInference(unittest.TestCase): def setUp(self): @@ -119,6 +128,13 @@ def setUp(self): self.view2D: pk.View2D[pk.int32] = pk.View([self.threads, self.threads], pk.int32) self.view3D: pk.View3D[pk.int32] = pk.View([self.threads, self.threads, self.threads], pk.int32) + if HAS_CUDA: + self.range_policy_cuda = pk.RangePolicy(pk.ExecutionSpace.Cuda, 0, self.threads) + self.view1D_cuda: pk.View1D[pk.int32] = pk.View([self.threads], pk.int32, pk.CudaSpace, pk.LayoutLeft) + self.view2D_cuda: pk.View1D[pk.int32] = pk.View([self.threads, self.threads], pk.int32, pk.CudaSpace, pk.LayoutLeft) + self.view3D_cuda: pk.View3D[pk.int32] = pk.View([self.threads, self.threads, self.threads], pk.int32, pk.CudaSpace, pk.LayoutLeft) + + def test_simple_parallelfor(self): expected_result: float = 1.0 n=1.0 @@ -126,6 +142,16 @@ def test_simple_parallelfor(self): for i in range(0, self.threads): self.assertEqual(expected_result, self.view1D[i]) + @pytest.mark.skipif(not HAS_CUDA, reason="CUDA/cupy not available") + def test_simple_parallelfor_cuda(self): + if not HAS_CUDA: + return + expected_result: float = 1.0 + n=1.0 + pk.parallel_for(self.range_policy_cuda, init_view, view=self.view1D_cuda, init=n) + for i in range(0, self.threads): + self.assertEqual(expected_result, self.view1D_cuda[i]) + def test_simple_parallelreduce(self): expect_result: float = self.threads n = 1 @@ -133,6 +159,16 @@ def test_simple_parallelreduce(self): result = pk.parallel_reduce(self.range_policy, reduce, view=self.view1D) self.assertEqual(expect_result, result) + @pytest.mark.skipif(not HAS_CUDA, reason="CUDA/cupy not available") + def test_simple_parallelreduce_cuda(self): + if not HAS_CUDA: + return + expect_result: float = self.threads + n = 1 + pk.parallel_for(self.range_policy_cuda, init_view, view=self.view1D_cuda, init=n) + result = pk.parallel_reduce(self.range_policy_cuda, reduce, view=self.view1D_cuda) + self.assertEqual(expect_result, result) + def test_simple_parallelscan(self): expect_result: float = np.cumsum(np.ones(self.threads)) n = 1 @@ -142,6 +178,18 @@ def test_simple_parallelscan(self): for i in range(0, self.threads): self.assertEqual(expect_result[i], self.view1D[i]) + @pytest.mark.skipif(not HAS_CUDA, reason="CUDA/cupy not available") + def test_simple_parallelscan_cuda(self): + if not HAS_CUDA: + return + expect_result: float = np.cumsum(np.ones(self.threads)) + n = 1 + pk.parallel_for(self.range_policy_cuda, init_view, view=self.view1D_cuda, init=n) + result = pk.parallel_scan(self.range_policy_cuda, scan, view=self.view1D_cuda) + self.assertEqual(expect_result[self.threads-1], result) + for i in range(0, self.threads): + self.assertEqual(expect_result[i], self.view1D_cuda[i]) + def test_reduceandfor_labels(self): # reduce and scan share the same dispatch expect_result: float = self.threads @@ -198,12 +246,20 @@ def test_layout_switchL(self): self.assertEqual(int64_view.layout, pk.Layout.LayoutLeft) self.assertEqual(int64_view[0], self.np_i64) - def test_layout_switchR(self): int64_view = pk.View([self.threads], pk.int64, layout=pk.Layout.LayoutRight) pk.parallel_for(self.range_policy, init_view, view=int64_view, init=self.np_i64) self.assertEqual(int64_view.layout, pk.Layout.LayoutRight) self.assertEqual(int64_view[0], self.np_i64) + @pytest.mark.skipif(not HAS_CUDA, reason="CUDA/cupy not available") + def test_cuda_switch(self): + if not HAS_CUDA: + return + int64_view = pk.View([self.threads], pk.int64, space=pk.MemorySpace.CudaSpace, layout=pk.Layout.LayoutLeft) + pk.parallel_for(self.range_policy_cuda, init_view, view=int64_view, init=self.np_i64) + self.assertEqual(int64_view.layout, pk.Layout.LayoutLeft) + self.assertEqual(int64_view[0], self.np_i64) + def test_cache_read(self): self.test_simple_parallelfor() self.test_simple_parallelreduce() @@ -268,6 +324,19 @@ def test_all_Ds(self): for k in range(self.threads): self.assertEqual(self.view3D[i][j][k], expect_result) + @pytest.mark.skipif(not HAS_CUDA, reason="CUDA/cupy not available") + def test_all_Ds_cuda(self): + if not HAS_CUDA: + return + expect_result = 1 + pk.parallel_for(self.range_policy_cuda, init_all_views, view1D=self.view1D_cuda, view2D=self.view2D_cuda, view3D=self.view3D_cuda, max_dim=self.threads, init=expect_result) + for i in range(self.threads): + self.assertEqual(self.view1D_cuda[i], expect_result) + for j in range(self.threads): + self.assertEqual(self.view2D_cuda[i][j], expect_result) + for k in range(self.threads): + self.assertEqual(self.view3D_cuda[i][j][k], expect_result) + def test_team_policy(self): # running team policy example y: pk.View1D = pk.View([self.threads], pk.double) @@ -298,10 +367,10 @@ def test_layout_decorated(self): pk.parallel_for(self.range_policy, init_view_layout, view=l_view, init=1) self.assertEqual(l_view.layout, pk.Layout.LayoutLeft) - # def test_only_layoutL(self): - # l_view = pk.View([self.threads], pk.int32, layout=pk.Layout.LayoutLeft) - # pk.parallel_for(self.range_policy, init_view_annotated, view=l_view, init=self.np_i32) - # self.assertEqual(l_view.layout, pk.Layout.LayoutLeft) + def test_only_layoutL(self): + l_view = pk.View([self.threads], pk.int32, layout=pk.Layout.LayoutLeft) + pk.parallel_for(self.range_policy, init_view_annotated, view=l_view, init=self.np_i32) + self.assertEqual(l_view.layout, pk.Layout.LayoutLeft) def test_only_layoutR(self): r_view = pk.View([self.threads], pk.int32, layout=pk.Layout.LayoutRight) @@ -356,6 +425,10 @@ def test_resetting_team(self): result = pk.parallel_reduce(p, team_reduce_mixed, M=self.threads, y=new_view, x=x, A=A) self.assertEqual(result, expected_result) + def test_no_view(self): + pk.parallel_reduce(self.range_policy, no_view, n = 1); + pk.parallel_reduce(self.range_policy, no_view, n = 2.1); + if __name__ == "__main__": unittest.main()