# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # licensed to the apache software foundation (asf) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. """Dlight Adreno Fallback Schedules""" from tvm import s_tir, tirx from tvm.target import Target from .. import analysis from .base import AdrenoScheduleRule def _assert_gpu_target(target: Target): if "gpu" not in target.keys: raise ValueError(f"Expect a GPU target, but got {target}") def get_max_threads_per_block(target: Target) -> int: _assert_gpu_target(target) max_threads_per_block = None for name in ["max_threads_per_block", "max_num_threads"]: if max_threads_per_block is None: max_threads_per_block = target.attrs.get(name, None) if max_threads_per_block is None: max_threads_per_block = 64 return int(max_threads_per_block) # pylint: disable=invalid-name,missing-function-docstring,unused-variable,unused-import class Fallback(AdrenoScheduleRule): """Texture Based Fallback Schedule(s) for Adreno""" @staticmethod def schedule_inline_blocks( sch: s_tir.Schedule, blocks: list[s_tir.schedule.SBlockRV] ) -> list[s_tir.schedule.SBlockRV]: """ Auto Inlines Injective and Element-wise Operations while trying to omit data pad blocks... """ if blocks is None: root_blk = analysis.get_root_block(sch) blocks = sch.get_child_blocks(root_blk) remaining_blocks = [] for blk in blocks: block_info = analysis.get_sblock_info(sch, blk) if block_info.is_injective() and not block_info.is_data_pad(sch): if len(sch.get_consumers(blk)) == 1: try: sch.compute_inline(blk) except Exception: # pylint: disable=broad-exception-caught remaining_blocks.append(blk) elif len(sch.get_producers(blk)) == 1: inlined_once = False try: # Would cause an issue inlining to producer with multiple consumers while ( len(sch.get_producers(blk)) == 1 and len(sch.get_consumers(sch.get_producers(blk)[0])) == 1 ): sch.reverse_compute_inline(blk) inlined_once = True except Exception: # pylint: disable=broad-exception-caught break if not inlined_once: remaining_blocks.append(blk) else: remaining_blocks.append(blk) else: remaining_blocks.append(blk) return remaining_blocks @staticmethod def schedule_default(sch: s_tir.Schedule, blk: s_tir.schedule.SBlockRV): block_info = analysis.get_sblock_info(sch, blk) s_loops, r_loops, o_loops = [], [], [] v_loop = block_info.write_bufs(sch)[0].assoc_lps[-1] for iter_info in block_info.iters: if sch.get(iter_info.loop_rv) == sch.get(v_loop): continue {"S": s_loops, "R": r_loops, "O": o_loops}.get(iter_info.kind).append(iter_info.loop_rv) iter_vars = analysis.collect_block_iter_vars_used_in_access_region( sch.get(blk), block_info.write_bufs(sch)[0].buf_region.region ) o_outer = [lp for lp in o_loops if sch.get(lp).var in iter_vars] o_inner = [lp for lp in o_loops if sch.get(lp).var not in iter_vars] # Can't change loop order for opaque loops if o_loops != o_outer + o_inner: return o_outer.append(v_loop) sch.reorder(*s_loops, *o_outer, *r_loops, *o_inner) assert s_loops tgt = Target.current(allow_none=True) b = sch.fuse(*s_loops) tx_extent = get_max_threads_per_block(tgt) if tgt is not None else 256 bx, tx = sch.split(b, [None, tx_extent]) sch.bind(bx, "blockIdx.x") sch.bind(tx, "threadIdx.x") if len(r_loops) > 1: lp = [*s_loops, *o_outer][-1] init_block = sch.decompose_reduction(blk, lp) wblk = sch.cache_write(blk, 0, "local") sch.compute_at(wblk, lp) if v_loop: sch.vectorize(sch.get_loops(init_block)[-1]) sch.vectorize(sch.get_loops(wblk)[-1]) elif v_loop is not None: sch.vectorize(v_loop) @staticmethod def schedule_fallback(sch): root_block = analysis.get_root_block(sch) blocks = sch.get_child_blocks(root_block) schedule_blocks = [ blk for blk in blocks if analysis.get_sblock_info(sch, blk).is_reduction() or analysis.get_sblock_info(sch, blk).is_data_pad(sch) ] remaining_blocks = [blk for blk in blocks if blk not in schedule_blocks] for blk in schedule_blocks: Fallback.schedule_default(sch, blk) remaining_blocks = Fallback.schedule_inline_blocks(sch, remaining_blocks) # TODO: Analyze unscheduled blocks to schedule instead of relying on remaining for blk in remaining_blocks: Fallback.schedule_default(sch, blk) def apply( # pylint: disable=too-many-locals self, func: tirx.PrimFunc, target: Target, _: bool, ) -> None | s_tir.Schedule | list[s_tir.Schedule]: # pylint: disable=invalid-name if not isinstance(func, tirx.PrimFunc) or not self.is_target_available(target): return None sch = s_tir.Schedule(func) root_block = analysis.get_root_block(sch) blocks = sch.get_child_blocks(root_block) if any(len(sch.get_child_blocks(block)) != 0 for block in blocks): return None block_infos = [analysis.get_sblock_info(sch, block) for block in blocks] if not any("texture" in block.write_bufs(sch)[0].get_scope() for block in block_infos): return None Fallback.schedule_fallback(sch) return sch