chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# isort: skip_file
|
||||
# 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.
|
||||
"""
|
||||
CPU-generic schedule rules.
|
||||
"""
|
||||
|
||||
from .gemv import GEMV
|
||||
from .reduction import Reduction
|
||||
@@ -0,0 +1,40 @@
|
||||
# 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.
|
||||
"""Base schedule rule for CPU operators."""
|
||||
|
||||
from tvm.target import Target
|
||||
|
||||
from ..base import ScheduleRule
|
||||
|
||||
|
||||
class CPUScheduleRule(ScheduleRule): # pylint: disable=too-few-public-methods
|
||||
"""The Schedule Rule specific to CPU targets, will return None if the target is not CPU."""
|
||||
|
||||
def is_target_available(self, target: Target) -> bool:
|
||||
"""Check whether the target is available for gpu rule.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
target : Target
|
||||
The compilation target to check.
|
||||
|
||||
Returns
|
||||
-------
|
||||
available : bool
|
||||
Whether the target is available for this rule.
|
||||
"""
|
||||
return super().is_target_available(target) and "llvm" == target.kind.name
|
||||
@@ -0,0 +1,131 @@
|
||||
# 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.
|
||||
"""A rule for GEMV and DecodeGEMV."""
|
||||
|
||||
from tvm import s_tir, tirx
|
||||
from tvm.target import Target
|
||||
|
||||
from ..analysis import SBlockInfo, normalize_prim_func
|
||||
from ..analysis.gemv import is_gemv, normalize
|
||||
from ..base import get_extent, try_inline_contiguous_spatial
|
||||
from .base import CPUScheduleRule
|
||||
|
||||
|
||||
class GEMV(CPUScheduleRule):
|
||||
"""A rule for GEMV and DecodeGEMV."""
|
||||
|
||||
def apply( # pylint: disable=too-many-locals,too-many-branches,too-many-return-statements, no-else-return
|
||||
self,
|
||||
func: tirx.PrimFunc,
|
||||
target: Target,
|
||||
_: bool,
|
||||
) -> None | s_tir.Schedule | list[s_tir.Schedule]:
|
||||
if not isinstance(func, tirx.PrimFunc) or not self.is_target_available(target):
|
||||
return None
|
||||
sch = s_tir.Schedule(func)
|
||||
block_infos = normalize_prim_func(sch)
|
||||
block_infos = try_inline_contiguous_spatial(sch, block_infos)
|
||||
if block_infos is None:
|
||||
return None
|
||||
if len(block_infos) == 1:
|
||||
epilogue = None
|
||||
elif len(block_infos) == 2:
|
||||
epilogue = block_infos[1]
|
||||
if not epilogue.is_injective():
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
block_info = block_infos[0]
|
||||
if len(block_info.iters) not in [2, 3]:
|
||||
# either [B, S, R] = [B, S, R] * [B, R]
|
||||
# or [S, R] = [S, R] * [R]
|
||||
return None
|
||||
block = block_info.block_rv
|
||||
vector_input_buffers = is_gemv(sch, block_info)
|
||||
if vector_input_buffers is None:
|
||||
return None
|
||||
|
||||
# Step 1. Normalize the block, merge spatial and reduction iters
|
||||
is_inner_reduction = normalize(sch, block_info)
|
||||
|
||||
# Step 2. Do the scheduling
|
||||
if is_inner_reduction is None:
|
||||
return None
|
||||
elif is_inner_reduction:
|
||||
return self.sch_inner_reduction(sch, target, block, vector_input_buffers, epilogue)
|
||||
else:
|
||||
# sch_outer reduction
|
||||
return None
|
||||
|
||||
def sch_inner_reduction( # pylint: disable=too-many-arguments, too-many-positional-arguments, invalid-name, unused-argument
|
||||
self,
|
||||
sch: s_tir.Schedule,
|
||||
target: Target,
|
||||
block: s_tir.schedule.SBlockRV,
|
||||
vector_input_buffers: list[tirx.Buffer],
|
||||
epilogue_info: SBlockInfo | None,
|
||||
):
|
||||
"""Schedule the inner reduction block."""
|
||||
|
||||
def apply( # pylint: disable=unused-variable, too-many-locals
|
||||
sch: s_tir.Schedule,
|
||||
gemv,
|
||||
vector_width: int = 8,
|
||||
parallel_threads: int = 8,
|
||||
unroll_factor: int = 256,
|
||||
):
|
||||
batch, s, r, c = sch.get_loops(block)
|
||||
len_batch, len_s, len_r, len_c = (
|
||||
get_extent(sch, batch),
|
||||
get_extent(sch, s),
|
||||
get_extent(sch, r),
|
||||
get_extent(sch, c),
|
||||
)
|
||||
len_S = len_batch * len_s
|
||||
len_R = len_r * len_c
|
||||
|
||||
if isinstance(len_S, int) and isinstance(len_R, int):
|
||||
if len_S > len_R:
|
||||
tile_s, tile_r = 128, 64 # Larger tiling for s-axis when len_S is larger
|
||||
else:
|
||||
tile_s, tile_r = 64, 128 # Larger tiling for r-axis when len_R is larger
|
||||
else:
|
||||
tile_s, tile_r = 64, 64 # Default tile sizes for unknown extents
|
||||
|
||||
tile_c = min(vector_width, len_c) # Ensure c-axis tiling aligns with SIMD vector width
|
||||
|
||||
# Apply loop tiling (improves cache locality)
|
||||
s_outer, s_inner = sch.split(s, factors=[None, tile_s])
|
||||
r_outer, r_inner = sch.split(r, factors=[None, tile_r])
|
||||
c_outer, c_inner = sch.split(c, factors=[None, tile_c])
|
||||
|
||||
# Apply vectorization (SIMD optimization)
|
||||
sch.vectorize(s_inner) # Vectorize computation along c-axis for AVX/NEON
|
||||
|
||||
# Enable parallel execution
|
||||
sch.parallel(s_outer) # Parallelize along the s-axis (major computation loop)
|
||||
|
||||
# Apply loop unrolling for better CPU performance
|
||||
sch.annotate(r_outer, "pragma_auto_unroll_max_step", unroll_factor)
|
||||
sch.annotate(r_outer, "pragma_unroll_explicit", 1)
|
||||
return sch
|
||||
|
||||
return apply(
|
||||
sch,
|
||||
gemv=block,
|
||||
)
|
||||
@@ -0,0 +1,153 @@
|
||||
# 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.
|
||||
"""CPU reduction rule for operators including softmax, layer norm, RMS norm, etc."""
|
||||
|
||||
from tvm import s_tir, tirx
|
||||
from tvm.target import Target
|
||||
from tvm.target.codegen import llvm_get_vector_width
|
||||
|
||||
from ..analysis import normalize_prim_func
|
||||
from ..base import get_extent
|
||||
from .base import CPUScheduleRule
|
||||
|
||||
|
||||
def _get_num_leading_s(dom_kind: str) -> int:
|
||||
"""Count leading spatial ('S') axes in a dom_kind string."""
|
||||
return len(dom_kind) - len(dom_kind.lstrip("S"))
|
||||
|
||||
|
||||
class Reduction(CPUScheduleRule):
|
||||
"""CPU reduction rule for softmax, layer norm, RMS norm, and similar operators.
|
||||
|
||||
Targets patterns with a mix of reduction (SR) and injective (SS) blocks,
|
||||
where all blocks share the same leading spatial axes.
|
||||
Example: softmax = maxelem(SR) -> exp(SS) -> expsum(SR) -> norm(SS).
|
||||
|
||||
Schedule strategy:
|
||||
1. Parallelize leading spatial axes (batch dimension).
|
||||
2. Move all blocks under the spatial loop via compute_at.
|
||||
3. Vectorize injective blocks (exp, delta, norm) on their inner axis.
|
||||
4. Split reduction inner axis to VLEN-sized chunks and annotate for
|
||||
LLVM unrolling, preventing harmful full-unroll by the backend.
|
||||
|
||||
Note: vectorized reduction via rfactor is not used here because TVM's
|
||||
rfactor primitive requires the reduction block to be the first child of
|
||||
its enclosing loop, which is incompatible with compute_at when multiple
|
||||
blocks share the same spatial loop. A follow-up using RVV reduction
|
||||
intrinsics (vfredmax/vfredusum) via tensorize can address this.
|
||||
"""
|
||||
|
||||
def apply( # pylint: disable=too-many-locals,too-many-return-statements,too-many-branches
|
||||
self,
|
||||
func: tirx.PrimFunc,
|
||||
target: Target,
|
||||
_: bool,
|
||||
) -> None | s_tir.Schedule | list[s_tir.Schedule]:
|
||||
if not isinstance(func, tirx.PrimFunc) or not self.is_target_available(target):
|
||||
return None
|
||||
|
||||
sch = s_tir.Schedule(func)
|
||||
block_infos = normalize_prim_func(sch)
|
||||
if block_infos is None or len(block_infos) < 2:
|
||||
return None
|
||||
|
||||
# Must have at least one reduction block and last block must be injective.
|
||||
if not any(not bi.is_injective() for bi in block_infos):
|
||||
return None
|
||||
if not block_infos[-1].is_injective():
|
||||
return None
|
||||
|
||||
# Every block must start with at least one spatial axis, and all blocks
|
||||
# must agree on the minimum number of leading spatial axes.
|
||||
num_leading_s = None
|
||||
for bi in block_infos:
|
||||
dk = bi.dom_kind()
|
||||
if not dk or dk[0] != "S":
|
||||
return None
|
||||
n = _get_num_leading_s(dk)
|
||||
num_leading_s = n if num_leading_s is None else min(num_leading_s, n)
|
||||
if not num_leading_s:
|
||||
return None
|
||||
|
||||
# Infer dtype from the last block's write buffer.
|
||||
last_block_stmt = sch.get(block_infos[-1].block_rv)
|
||||
dtype_bits = (
|
||||
last_block_stmt.writes[0].buffer.dtype.dtype.bits if last_block_stmt.writes else 32
|
||||
)
|
||||
|
||||
# Determine vector lanes from target VLEN.
|
||||
vlen_bits = llvm_get_vector_width(target)
|
||||
if vlen_bits <= 0:
|
||||
vlen_bits = 128
|
||||
vec_lanes = max(vlen_bits // dtype_bits, 2)
|
||||
|
||||
# --- Phase 1: Parallelize spatial on the last block ---
|
||||
last_block = block_infos[-1]
|
||||
loops = sch.get_loops(last_block.block_rv)
|
||||
if num_leading_s > 1:
|
||||
spatial = sch.fuse(*loops[:num_leading_s])
|
||||
else:
|
||||
spatial = loops[0]
|
||||
sch.parallel(spatial)
|
||||
|
||||
# --- Phase 2: Vectorize the last (injective) block ---
|
||||
self._vectorize_inner(sch, last_block.block_rv, vec_lanes)
|
||||
|
||||
# --- Phase 3: compute_at all preceding blocks under spatial ---
|
||||
for block_info in reversed(block_infos[:-1]):
|
||||
sch.compute_at(block_info.block_rv, spatial, preserve_unit_loops=True)
|
||||
|
||||
# --- Phase 4: Vectorize injective, split+unroll reduction blocks ---
|
||||
for block_info in block_infos[:-1]:
|
||||
if block_info.is_injective():
|
||||
self._vectorize_inner(sch, block_info.block_rv, vec_lanes)
|
||||
else:
|
||||
self._unroll_reduction_inner(sch, block_info.block_rv, vec_lanes)
|
||||
|
||||
return sch
|
||||
|
||||
@staticmethod
|
||||
def _vectorize_inner(sch, block_rv, vec_lanes):
|
||||
"""Split the innermost loop to vec_lanes and vectorize."""
|
||||
block_loops = sch.get_loops(block_rv)
|
||||
if len(block_loops) <= 1:
|
||||
return
|
||||
inner = block_loops[-1]
|
||||
extent = get_extent(sch, inner)
|
||||
if isinstance(extent, int):
|
||||
if extent > vec_lanes:
|
||||
_, vec_loop = sch.split(inner, factors=[None, vec_lanes])
|
||||
sch.vectorize(vec_loop)
|
||||
elif extent >= 2:
|
||||
sch.vectorize(inner)
|
||||
else:
|
||||
_, vec_loop = sch.split(inner, factors=[None, vec_lanes])
|
||||
sch.vectorize(vec_loop)
|
||||
|
||||
@staticmethod
|
||||
def _unroll_reduction_inner(sch, block_rv, vec_lanes):
|
||||
"""Split the reduction inner loop and annotate for unrolling."""
|
||||
block_loops = sch.get_loops(block_rv)
|
||||
if len(block_loops) <= 1:
|
||||
return
|
||||
inner = block_loops[-1]
|
||||
extent = get_extent(sch, inner)
|
||||
if isinstance(extent, int) and extent <= vec_lanes:
|
||||
return
|
||||
_, inner_loop = sch.split(inner, factors=[None, vec_lanes])
|
||||
sch.annotate(inner_loop, ann_key="pragma_auto_unroll_max_step", ann_val=vec_lanes)
|
||||
sch.annotate(inner_loop, ann_key="pragma_unroll_explicit", ann_val=1)
|
||||
Reference in New Issue
Block a user