Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependency finding #704

Draft
wants to merge 55 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
9f097a3
Sketch out where precise dependencies might go in the IR
inducer Nov 7, 2022
779c2f0
New branch
a-alveyblanc Nov 8, 2022
365781d
Merge branch 'inducer:main' into dependencies-wip
a-alveyblanc Nov 14, 2022
6a58e1d
Added draft of new version of dependency finding
a-alveyblanc Jan 13, 2023
ba3fbf5
Added some documentation
a-alveyblanc Jan 13, 2023
990624b
Started implementation of add_lexicographic_...
a-alveyblanc Jan 20, 2023
93b4126
Update gitignore to ignore DS_STORE
a-alveyblanc Jan 20, 2023
01d3688
Sketch out add_lexicographic_happens_after
inducer Jan 20, 2023
51c460d
Add relation to HappensAfter data structure
a-alveyblanc Jan 21, 2023
f6b75dd
New version of data dependency finding, still WIP
a-alveyblanc Jan 29, 2023
8327c70
added read-after-write, write-after-read, write-after-write dep findi…
a-alveyblanc Feb 4, 2023
999446f
update documentation
a-alveyblanc Feb 6, 2023
f110b30
Added first implementation of dependency finding for non-successive i…
a-alveyblanc Feb 12, 2023
a671762
Minor fixes for some failing tests
a-alveyblanc Feb 14, 2023
34f5b90
Slight refactor to improve readability and remove repeated code
a-alveyblanc Feb 16, 2023
adb924c
Merge branch 'main' into dependencies-wip
a-alveyblanc Feb 16, 2023
a7eb364
More changes to fix failing CI
a-alveyblanc Feb 16, 2023
c23eff4
Some slight changes to fix failing CI and improve readability
a-alveyblanc Feb 16, 2023
1647d5a
More fixes for CI
a-alveyblanc Feb 16, 2023
055d7e3
Add knl.id_to_insn to find instructions from ids instead of looping t…
a-alveyblanc Feb 25, 2023
888291f
Change the way the new happens_after is updated when finding dependen…
a-alveyblanc Feb 28, 2023
b785738
Temporary changes for how to handle cases in which happens_after and …
a-alveyblanc Mar 6, 2023
457cb6f
Revert a change to a test that was not supposed to be changed
a-alveyblanc Mar 6, 2023
096e68b
pushing
a-alveyblanc Mar 20, 2023
1b9f186
Revert "pushing"
a-alveyblanc Mar 21, 2023
5f3a249
Refactor dependency finding code to eliminate bugs.
a-alveyblanc Mar 23, 2023
169e808
Attempt at fixing CI failures due to replacing happens_after with dep…
a-alveyblanc Mar 24, 2023
faa5f23
Remove HappensAfter import from where it never should have been
a-alveyblanc Mar 24, 2023
d3224d1
Minor tweaks. Add a function to print dependency info of a knl
a-alveyblanc Apr 6, 2023
a215511
Refactored code. Much shorter with better access map finding.
a-alveyblanc Apr 10, 2023
72ab7e7
Remove giant todo list from top of file, change printing code
a-alveyblanc Apr 10, 2023
590a8cf
Minor grammatical fix
a-alveyblanc Apr 10, 2023
5208fb6
Minor print function change
a-alveyblanc Apr 10, 2023
cf4b9bd
Implement map_sub_array_ref in AccessMapFinder
a-alveyblanc Apr 11, 2023
faff25c
Read-after-read dependency finding avoided
a-alveyblanc Apr 11, 2023
d967949
Change back to long version of dependency finding. Switch to using pmap
a-alveyblanc Apr 12, 2023
307aa83
Nested array access finding
a-alveyblanc Apr 12, 2023
808cef6
Allow depends_on to be passed as an argument for legacy purposes
a-alveyblanc Apr 17, 2023
8a28e7f
Fix issues found by mypy and pylint
a-alveyblanc Apr 17, 2023
5731025
Fix documentation issues, handle cases where depends_on and happens_a…
a-alveyblanc Apr 18, 2023
e745336
Fix dependency cylce introduced by interaction between add_lexicograp…
a-alveyblanc Apr 21, 2023
b9cc691
Add a possible solution for supporting scalars in dependency finding …
a-alveyblanc Apr 25, 2023
d361167
Pushing [skip ci]
a-alveyblanc Apr 25, 2023
16be472
Precompute coarse-grained dependencies, add traces of transitive depe…
a-alveyblanc May 1, 2023
8e0e384
Use fine-grain dependency information that exists in the kernel when …
a-alveyblanc May 8, 2023
69265b2
Fix failing pylint
a-alveyblanc May 8, 2023
8c42f69
Remove file that was accidentally created
a-alveyblanc May 8, 2023
970feb0
Some unimportant changes
a-alveyblanc Jun 8, 2023
5b98c84
Remove unnecessary tests
a-alveyblanc Jun 9, 2023
2043233
Fix failing doctests
a-alveyblanc Jun 13, 2023
1cea1b3
Remove some (currently) unnecessary changes
a-alveyblanc Jun 13, 2023
9131461
Remove accidentally added files
a-alveyblanc Jun 13, 2023
290c91e
Remove more (temporarily) unnecessary code to prep for compatibility …
a-alveyblanc Jun 13, 2023
7cd7ea0
Fix failing doctest
a-alveyblanc Jun 13, 2023
c1a553a
Merge branch 'inducer:main' into dependencies-wip
a-alveyblanc Sep 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ htmlcov
lextab.py
yacctab.py
.pytest_cache/*
.DS_STORE

loopy/_git_rev.py

Expand Down
5 changes: 5 additions & 0 deletions doc/ref_kernel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ Instructions

.. {{{

.. autoclass:: HappensAfter
.. autoclass:: InstructionBase

.. _assignments:
Expand Down Expand Up @@ -459,6 +460,10 @@ Loopy's expressions are a slight superset of the expressions supported by
TODO: Functions
TODO: Reductions

Dependencies
^^^^^^^^^^^^
.. automodule:: loopy.kernel.dependency

Function Call Instructions
^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down
4 changes: 4 additions & 0 deletions doc/ref_transform.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Wrangling inames

.. automodule:: loopy.transform.iname

Precise Dependency Finding
--------------------------
.. automodule:: loopy.transform.dependency

Dealing with Substitution Rules
-------------------------------

Expand Down
2 changes: 2 additions & 0 deletions loopy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
# {{{ imported user interface

from loopy.kernel.instruction import (
HappensAfter,
LegacyStringInstructionTag, UseStreamingStoreTag,
MemoryOrdering,
MemoryScope,
Expand Down Expand Up @@ -175,6 +176,7 @@
"LoopKernel",
"KernelState",

"HappensAfter",
"LegacyStringInstructionTag", "UseStreamingStoreTag",
"MemoryOrdering",
"MemoryScope",
Expand Down
1 change: 0 additions & 1 deletion loopy/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -1058,7 +1058,6 @@ def _check_variable_access_ordered_inner(kernel):
depends_on = {insn.id: set() for insn in kernel.instructions}
# rev_depends: mapping from insn_ids to their reverse deps.
rev_depends = {insn.id: set() for insn in kernel.instructions}

for insn in kernel.instructions:
depends_on[insn.id].update(insn.depends_on)
for dep in insn.depends_on:
Expand Down
82 changes: 34 additions & 48 deletions loopy/kernel/creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def subst_func(var):

def get_default_insn_options_dict():
return {
"depends_on": frozenset(),
"happens_after": frozenset(),
"depends_on_is_final": False,
"no_sync_with": frozenset(),
"groups": frozenset(),
Expand Down Expand Up @@ -289,14 +289,15 @@ def parse_nosync_option(opt_value):
result["depends_on_is_final"] = True
opt_value = (opt_value[1:]).strip()

result["depends_on"] = result["depends_on"].union(frozenset(
result["happens_after"] = result["happens_after"].union(frozenset(
intern(dep.strip()) for dep in opt_value.split(":")
if dep.strip()))

elif opt_key == "dep_query" and opt_value is not None:
from loopy.match import parse_match
match = parse_match(opt_value)
result["depends_on"] = result["depends_on"].union(frozenset([match]))
result["happens_after"] = result["happens_after"].union(
frozenset([match]))

elif opt_key == "nosync" and opt_value is not None:
if is_with_block:
Expand Down Expand Up @@ -684,6 +685,7 @@ def _count_open_paren_symbols(s):


def parse_instructions(instructions, defines):

if isinstance(instructions, str):
instructions = [instructions]

Expand Down Expand Up @@ -716,8 +718,8 @@ def intern_if_str(s):

copy_args = {
"id": intern_if_str(insn.id),
"depends_on": frozenset(intern_if_str(dep)
for dep in insn.depends_on),
"happens_after": frozenset(intern_if_str(dep)
for dep in insn.happens_after),
"groups": frozenset(checked_intern(grp) for grp in insn.groups),
"conflicts_with_groups": frozenset(
checked_intern(grp) for grp in insn.conflicts_with_groups),
Expand Down Expand Up @@ -834,11 +836,11 @@ def intern_if_str(s):
# If it's inside a for/with block, then it's
# final now.
bool(local_w_inames)),
depends_on=(
happens_after=(
(insn.depends_on
| insn_options_stack[-1]["depends_on"])
if insn_options_stack[-1]["depends_on"] is not None
else insn.depends_on),
| insn_options_stack[-1]["happens_after"])
if insn_options_stack[-1]["happens_after"] is not None
else insn.happens_after),
tags=(
insn.tags
| insn_options_stack[-1]["tags"]),
Expand Down Expand Up @@ -1441,32 +1443,6 @@ def add_assignment(base_name, expr, dtype, additional_inames):
# }}}


# {{{ add_sequential_dependencies

def add_sequential_dependencies(knl):
new_insns = []
prev_insn = None
for insn in knl.instructions:
depon = insn.depends_on
if depon is None:
depon = frozenset()

if prev_insn is not None:
depon = depon | frozenset((prev_insn.id,))

insn = insn.copy(
depends_on=depon,
depends_on_is_final=True)

new_insns.append(insn)

prev_insn = insn

return knl.copy(instructions=new_insns)

# }}}


# {{{ temporary variable creation

def create_temporaries(knl, default_order):
Expand Down Expand Up @@ -1816,18 +1792,20 @@ def resolve_dependencies(knl):
new_insns = []

for insn in knl.instructions:
depends_on = _resolve_dependencies(
"a dependency", knl, insn, insn.depends_on)
happens_after = _resolve_dependencies(
"a dependency", knl, insn, insn.happens_after)
no_sync_with = frozenset(
(resolved_insn_id, nosync_scope)
for nosync_dep, nosync_scope in insn.no_sync_with
for resolved_insn_id in
_resolve_dependencies("nosync", knl, insn, (nosync_dep,)))

if depends_on == insn.depends_on and no_sync_with == insn.no_sync_with:
if happens_after == insn.happens_after and \
no_sync_with == insn.no_sync_with:
new_insn = insn
else:
new_insn = insn.copy(depends_on=depends_on, no_sync_with=no_sync_with)
new_insn = insn.copy(happens_after=happens_after,
no_sync_with=no_sync_with)
new_insns.append(new_insn)

return knl.copy(instructions=new_insns)
Expand Down Expand Up @@ -1921,13 +1899,17 @@ def apply_single_writer_depencency_heuristic(kernel, warn_if_used=True,

# }}}

depends_on = insn.depends_on
if depends_on is None:
depends_on = frozenset()
happens_after = insn.happens_after

if not isinstance(happens_after, frozenset):
happens_after = frozenset(happens_after)

new_deps = frozenset(auto_deps) | depends_on
if happens_after is None:
happens_after = frozenset()

if new_deps != depends_on:
new_deps = frozenset(auto_deps) | frozenset(happens_after)

if new_deps != happens_after:
msg = (
"The single-writer dependency heuristic added dependencies "
"on instruction ID(s) '%s' to instruction ID '%s' after "
Expand All @@ -1936,13 +1918,13 @@ def apply_single_writer_depencency_heuristic(kernel, warn_if_used=True,
"To fix this, ensure that instruction dependencies "
"are added/resolved as soon as possible, ideally at kernel "
"creation time."
% (", ".join(new_deps - depends_on), insn.id))
% (", ".join(new_deps - happens_after), insn.id))
if warn_if_used:
warn_with_kernel(kernel, "single_writer_after_creation", msg)
if error_if_used:
raise LoopyError(msg)

insn = insn.copy(depends_on=new_deps)
insn = insn.copy(happens_after=new_deps)
changed = True

new_insns.append(insn)
Expand Down Expand Up @@ -2523,7 +2505,8 @@ def make_function(domains, instructions, kernel_data=None, **kwargs):
check_for_duplicate_insn_ids(knl)

if seq_dependencies:
knl = add_sequential_dependencies(knl)
from loopy.kernel.dependency import add_lexicographic_happens_after
knl = add_lexicographic_happens_after(knl)

assert len(knl.instructions) == len(inames_to_dup)

Expand Down Expand Up @@ -2565,7 +2548,10 @@ def make_function(domains, instructions, kernel_data=None, **kwargs):
knl = guess_arg_shape_if_requested(knl, default_order)
knl = apply_default_order_to_args(knl, default_order)
knl = resolve_dependencies(knl)
knl = apply_single_writer_depencency_heuristic(knl, warn_if_used=False)

# precise dependency semantics should not rely on this
if not seq_dependencies:
knl = apply_single_writer_depencency_heuristic(knl, warn_if_used=False)

# -------------------------------------------------------------------------
# Ordering dependency:
Expand Down
122 changes: 122 additions & 0 deletions loopy/kernel/dependency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""
.. autofunction:: add_lexicographic_happens_after
"""

__copyright__ = "Copyright (C) 2023 Addison Alvey-Blanco"

__license__ = """
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""

import islpy as isl
from islpy import dim_type

from loopy import LoopKernel
from loopy.kernel.instruction import HappensAfter
from loopy.translation_unit import for_each_kernel


@for_each_kernel
def add_lexicographic_happens_after(knl: LoopKernel) -> LoopKernel:
"""Construct a sequential dependency specification between each instruction
and the instruction immediately before it. This dependency information
contains a lexicographic map which acts as a description of the precise,
statement-instance level dependencies between statements.
"""

new_insns = []

for iafter, insn_after in enumerate(knl.instructions):

if iafter == 0:
new_insns.append(insn_after)

else:

insn_before = knl.instructions[iafter - 1]
shared_inames = insn_after.within_inames & insn_before.within_inames

domain_before = knl.get_inames_domain(insn_before.within_inames)
domain_after = knl.get_inames_domain(insn_after.within_inames)
happens_before = isl.Map.from_domain_and_range(
domain_before, domain_after
)

for idim in range(happens_before.dim(dim_type.out)):
happens_before = happens_before.set_dim_name(
dim_type.out, idim,
happens_before.get_dim_name(dim_type.out, idim) + "'"
)

n_inames_before = happens_before.dim(dim_type.in_)
happens_before_set = happens_before.move_dims(
dim_type.out, 0,
dim_type.in_, 0,
n_inames_before).range()

shared_inames_order_before = [
domain_before.get_dim_name(dim_type.out, idim)
for idim in range(domain_before.dim(dim_type.out))
if domain_before.get_dim_name(dim_type.out, idim)
in shared_inames
]
shared_inames_order_after = [
domain_after.get_dim_name(dim_type.out, idim)
for idim in range(domain_after.dim(dim_type.out))
if domain_after.get_dim_name(dim_type.out, idim)
in shared_inames
]
assert shared_inames_order_after == shared_inames_order_before
shared_inames_order = shared_inames_order_after
Comment on lines +85 to +86
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a big unresolved question here, in terms of what nesting order we should use for the shared inames. Right now, this uses the axis order in the domains, however we also already do have LoopKernel.loop_priority to indicate nesting order during code generation). If nothing else, this should make sure that the order produced is consistent with that. But there's also the option of using/introducing a different mechanism entirely for this.

@kaushikcfd, got an opinion?


affs = isl.affs_from_space(happens_before_set.space)

lex_set = isl.Set.empty(happens_before_set.space)
for iinnermost, innermost_iname in enumerate(shared_inames_order):

innermost_set = affs[innermost_iname].lt_set(
affs[innermost_iname+"'"]
)

for outer_iname in shared_inames_order[:iinnermost]:
innermost_set = innermost_set & (
affs[outer_iname].eq_set(affs[outer_iname + "'"])
)

lex_set = lex_set | innermost_set

lex_map = isl.Map.from_range(lex_set).move_dims(
dim_type.in_, 0,
dim_type.out, 0,
n_inames_before)

happens_before = happens_before & lex_map

new_happens_after = {
insn_before.id: HappensAfter(None, happens_before)
}

insn_after = insn_after.copy(happens_after=new_happens_after)

new_insns.append(insn_after)

return knl.copy(instructions=new_insns)


# vim: foldmethod=marker
Loading
Loading