Files
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

553 lines
21 KiB
Python

# 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.
# pylint: disable=invalid-name, unused-wildcard-import, wildcard-import
# ruff: noqa: E501, F403, F405
"""Generator for CUTLASS Conv2D kernels."""
from .library import *
class Conv2dOperation:
"""Describes various attributes for instantiating Conv2d kernels."""
def __init__(
self,
conv_kind,
iterator_algorithm,
arch,
tile_description,
A,
B,
C,
element_epilogue,
stride_support,
epilogue_functor=EpilogueFunctor.LinearCombination,
swizzling_functor=SwizzlingFunctor.Identity1,
split_k_slices=1,
):
self.operation_kind = OperationKind.Conv2d
self.arch = arch
self.tile_description = tile_description
self.conv_kind = conv_kind
self.A = A
self.B = B
self.C = C
self.element_epilogue = element_epilogue
self.epilogue_functor = epilogue_functor
self.iterator_algorithm = iterator_algorithm
self.stride_support = stride_support
self.swizzling_functor = swizzling_functor
self.split_k_slices = split_k_slices
def accumulator_type(self):
return self.tile_description.math_instruction.element_accumulator
def core_name(self):
"""The basic operation kind is prefixed with a letter indicating the accumulation type."""
intermediate_type = ""
if self.tile_description.math_instruction.opcode_class == OpcodeClass.TensorOp:
inst_shape = "{}{}{}".format(*self.tile_description.math_instruction.instruction_shape)
if (
self.tile_description.math_instruction.element_a != self.A.element
and self.tile_description.math_instruction.element_a != self.accumulator_type()
):
intermediate_type = DataTypeNames[self.tile_description.math_instruction.element_a]
else:
inst_shape = ""
return f"{ShortDataTypeNames[self.accumulator_type()]}{inst_shape}{intermediate_type}{ConvKindNames[self.conv_kind]}_{IteratorAlgorithmNames[self.iterator_algorithm]}"
def extended_name(self):
"""Append data types if they differ from compute type."""
if (
self.C.element != self.tile_description.math_instruction.element_accumulator
and self.A.element != self.tile_description.math_instruction.element_accumulator
):
extended_name = "${element_c}_${core_name}_${element_a}"
elif (
self.C.element == self.tile_description.math_instruction.element_accumulator
and self.A.element != self.tile_description.math_instruction.element_accumulator
):
extended_name = "${core_name}_${element_a}"
else:
extended_name = "${core_name}"
extended_name = substitute_template(
extended_name,
{
"element_a": DataTypeNames[self.A.element],
"element_c": DataTypeNames[self.C.element],
"core_name": self.core_name(),
},
)
return extended_name
def layout_name(self):
return f"{ShortLayoutTypeNames[self.A.layout]}"
def procedural_name(self):
"""
The full procedural name indicates architecture, extended name, tile size, and layout.
"""
opcode_class_name = OpcodeClassNames[self.tile_description.math_instruction.opcode_class]
threadblock = f"{self.tile_description.threadblock_shape[0]}x{self.tile_description.threadblock_shape[1]}_{self.tile_description.threadblock_shape[2]}x{self.tile_description.stages}"
if self.stride_support == StrideSupport.Unity:
configuration_name = (
"cutlass_${opcode_class}_${extended_name}_${threadblock}"
"_${layout}_align${alignment}_unity_stride"
)
else:
configuration_name = (
"cutlass_${opcode_class}_${extended_name}_${threadblock}"
"_${layout}_align${alignment}"
)
if self.split_k_slices > 1:
configuration_name += f"_splitk{self.split_k_slices}"
return substitute_template(
configuration_name,
{
"opcode_class": opcode_class_name,
"extended_name": self.extended_name(),
"threadblock": threadblock,
"layout": self.layout_name(),
"alignment": f"{self.A.alignment}",
},
)
class EmitConv2dInstance:
"""Responsible for emitting a CUTLASS template definition."""
def __init__(self):
self.epilogue_default = """
${epilogue_functor}<
${element_c},
${epilogue_vector_length},
${element_accumulator},
${element_epilogue}
>"""
self.epilogue_no_beta_scaling = """
${epilogue_functor}<
${element_c},
${epilogue_vector_length},
${element_accumulator},
${element_epilogue},
cutlass::epilogue::thread::ScaleType::NoBetaScaling
>"""
self.epilogue_residual_block = """
${epilogue_functor}<
${element_c},
${element_accumulator},
${element_epilogue},
${element_c},
${epilogue_vector_length},
${activation},
${binary_op},
${unary_op}
>"""
self.epilogue_wgrad = """
${epilogue_functor}<
${element_c},
4,
float,
float
>"""
self.template = """
// Conv2d${conv_kind_name} ${iterator_algorithm_name} kernel instance "${operation_name}"
using ${operation_name} =
typename cutlass::conv::kernel::DefaultConv2d${conv_kind_name}${conv_kernel_postfix}<
${element_a},
${layout_a},
${element_b},
${layout_b},
${element_c},
${layout_c},
${element_accumulator},
${opcode_class},
${arch},
cutlass::gemm::GemmShape<${threadblock_shape_m}, ${threadblock_shape_n}, ${threadblock_shape_k}>,
cutlass::gemm::GemmShape<${warp_shape_m}, ${warp_shape_n}, ${warp_shape_k} >,
cutlass::gemm::GemmShape<${instruction_shape_m}, ${instruction_shape_n}, ${instruction_shape_k}>,
${epilogue},
${swizzling_functor}, // cutlass::gemm::threadblock::GemmSplitKIdentityThreadblockSwizzle<>,
${stages},
${math_operator},
${iterator_algorithm},
${stride_support},
${align_a},
${align_b}
>::Kernel;
${reduction}
"""
self.reduction_template = """
using EpilogueOutputOp = ${epilogue};
using ReductionOp = cutlass::reduction::thread::ReduceAdd<
${element_accumulator},
${element_accumulator},
EpilogueOutputOp::kCount
>;
using ReductionKernel = cutlass::reduction::kernel::ReduceSplitK<
cutlass::MatrixShape<4, 32 * EpilogueOutputOp::kCount>,
EpilogueOutputOp,
ReductionOp
>;
using ReductionDevice = cutlass::reduction::device::ReduceSplitK<ReductionKernel>;
using ReductionStrideIndex = typename ReductionDevice::StrideIndex;
"""
def emit(
self, operation, no_beta_scaling=False, residual_block_info=False, emit_reduction=False
):
"""Instantiate a Conv2d kernel from given `operation`."""
warp_shape = [
int(
operation.tile_description.threadblock_shape[idx]
/ operation.tile_description.warp_count[idx]
)
for idx in range(3)
]
epilogue_vector_length = int(
min(operation.C.alignment * DataTypeSize[operation.C.element], 128)
/ DataTypeSize[operation.C.element]
)
element_c = operation.C.element
use_split_k_wgrad = operation.conv_kind == ConvKind.Wgrad and operation.split_k_slices > 1
# Gemm output always fp32 in wgrad with split k
element_c_gemm = DataType.f32 if use_split_k_wgrad else element_c
if emit_reduction:
epilogue_reduction = substitute_template(
self.epilogue_wgrad,
{
"epilogue_functor": EpilogueFunctorTag[operation.epilogue_functor],
"element_c": DataTypeTag[element_c],
},
)
reduction = substitute_template(
self.reduction_template,
{
"epilogue": epilogue_reduction,
"operation_name": operation.procedural_name(),
"element_accumulator": DataTypeTag[operation.accumulator_type()],
},
)
gemm_template = substitute_template(self.template, {"reduction": reduction})
else:
gemm_template = substitute_template(self.template, {"reduction": ""})
values = {
"operation_name": operation.procedural_name(),
"conv_kind": ConvKindTag[operation.conv_kind],
"conv_kind_name": ConvKindNames[operation.conv_kind].capitalize(),
"element_a": DataTypeTag[operation.A.element],
"layout_a": LayoutTag[operation.A.layout],
"element_b": DataTypeTag[operation.B.element],
"layout_b": LayoutTag[operation.B.layout],
"element_c": DataTypeTag[element_c_gemm],
"layout_c": LayoutTag[operation.C.layout],
"element_accumulator": DataTypeTag[operation.accumulator_type()],
"opcode_class": OpcodeClassTag[
operation.tile_description.math_instruction.opcode_class
],
"arch": f"cutlass::arch::Sm{operation.arch}",
"threadblock_shape_m": str(operation.tile_description.threadblock_shape[0]),
"threadblock_shape_n": str(operation.tile_description.threadblock_shape[1]),
"threadblock_shape_k": str(operation.tile_description.threadblock_shape[2]),
"warp_shape_m": str(warp_shape[0]),
"warp_shape_n": str(warp_shape[1]),
"warp_shape_k": str(warp_shape[2]),
"instruction_shape_m": str(
operation.tile_description.math_instruction.instruction_shape[0]
),
"instruction_shape_n": str(
operation.tile_description.math_instruction.instruction_shape[1]
),
"instruction_shape_k": str(
operation.tile_description.math_instruction.instruction_shape[2]
),
"epilogue_vector_length": str(epilogue_vector_length),
"epilogue_functor": EpilogueFunctorTag[operation.epilogue_functor],
"element_epilogue": str(DataTypeTag[operation.element_epilogue]),
"swizzling_functor": SwizzlingFunctorTag[operation.swizzling_functor],
"stages": str(operation.tile_description.stages),
"iterator_algorithm": IteratorAlgorithmTag[operation.iterator_algorithm],
"iterator_algorithm_name": IteratorAlgorithmNames[
operation.iterator_algorithm
].capitalize(),
"stride_support": StrideSupportTag[operation.stride_support],
"math_operator": MathOperationTag[
operation.tile_description.math_instruction.math_operation
],
"align_a": str(operation.A.alignment),
"align_b": str(operation.B.alignment),
"conv_kernel_postfix": "",
}
if use_split_k_wgrad:
# Even if the output is fp16, gemm output is always fp32 for split k wgrad.
epilogue_gemm = substitute_template(
self.epilogue_wgrad,
{
"epilogue_functor": EpilogueFunctorTag[operation.epilogue_functor],
"element_c": "float",
},
)
template = substitute_template(gemm_template, {"epilogue": epilogue_gemm})
elif residual_block_info:
template = substitute_template(
gemm_template, {"epilogue": self.epilogue_residual_block}
)
values.update(
{
"unary_op": residual_block_info["unary_op"],
"binary_op": residual_block_info["binary_op"],
"activation": residual_block_info["activation"],
"conv_kernel_postfix": "WithBroadcast",
}
)
elif no_beta_scaling:
template = substitute_template(
gemm_template, {"epilogue": self.epilogue_no_beta_scaling}
)
else:
template = substitute_template(gemm_template, {"epilogue": self.epilogue_default})
return substitute_template(template, values)
def instantiate_conv2d_template(attrs):
"""Return CUTLASS host code for conv2d based on a template and the provided attribute map."""
template = """
${cutlass_op_def}
using Conv2d = cutlass::conv::device::ImplicitGemmConvolution<${cutlass_op_name}>;
using ElementInputA = Conv2d::ElementA;
using ElementInputB = Conv2d::ElementB;
using ElementComputeEpilogue = Conv2d::ElementAccumulator;
int N = ${N};
int H = ${H};
int W = ${W};
int C = ${C};
int K = ${K};
int R = ${R};
int S = ${S};
int P = ${P};
int Q = ${Q};
int pad_h = ${pad_h};
int pad_w = ${pad_w};
int stride_h = ${stride_h};
int stride_w = ${stride_w};
int dilation_h = ${dilation_h};
int dilation_w = ${dilation_w};
int split_k_slices = ${split_k_slices};
cutlass::conv::Conv2dProblemSize problem_size(N, H, W, C, K, R, S, P, Q, pad_h, pad_w, stride_h, stride_w, dilation_h, dilation_w, cutlass::conv::Mode::kCrossCorrelation, split_k_slices);
const cutlass::conv::SplitKMode split_k_mode = cutlass::conv::SplitKMode::${split_k_mode};
void* ptr_a = (void*)(${data_arg}->data);
void* ptr_b = (void*)(${weight_arg}->data);
${bias_decl}
${residual_decl}
void* ptr_out = (void*)(out0->data);
ElementComputeEpilogue alpha = ElementComputeEpilogue(1);
ElementComputeEpilogue beta = ElementComputeEpilogue(${beta});
using cutlass::layout::TensorNHWC;
auto activation_shape = TensorNHWC::packed(cutlass::make_Coord(N, H, W, C));
auto weight_shape = TensorNHWC::packed(cutlass::make_Coord(K, R, S, C));
auto output_shape = TensorNHWC::packed(cutlass::make_Coord(N, P, Q, K));
${residual_shape_decl}
TensorNHWC layout_A(${A_shape});
TensorNHWC layout_B(${B_shape});
TensorNHWC layout_C(${C_shape});
TensorNHWC layout_D(${D_shape});
using ElementOutput = ${ElementOutput};
cutlass::TensorRef<ElementOutput, TensorNHWC> tensor_c{static_cast<ElementOutput*>(${tensor_c}), ${tensor_c_layout}};
cutlass::TensorRef<ElementOutput, TensorNHWC> tensor_d{static_cast<ElementOutput*>(ptr_out), layout_D};
typename Conv2d::Arguments arguments{
problem_size,
{static_cast<ElementInputA*>(ptr_a), layout_A},
{static_cast<ElementInputB*>(ptr_b), layout_B},
${tensor_c_arg},
${tensor_d_arg},
{${alpha_beta}},
split_k_mode
${additional_args}
};
Conv2d conv2d_op;
size_t workspace_size = conv2d_op.get_workspace_size(arguments);
cutlass::device_memory::allocation<uint8_t> workspace(workspace_size);
cutlass::Status status = conv2d_op.can_implement(arguments);
TVM_FFI_ICHECK(status == cutlass::Status::kSuccess);
${split_k_reset}
status = conv2d_op.initialize(arguments, workspace.get());
TVM_FFI_ICHECK(status == cutlass::Status::kSuccess);
${split_k_update}
cudaStream_t stream = static_cast<cudaStream_t>(TVMFFIEnvGetStream(kDLCUDA, ${data_arg}->device.device_id));
status = conv2d_op(stream);
TVM_FFI_ICHECK(status == cutlass::Status::kSuccess);
${split_k_reduction}
"""
split_k_reset = """
arguments.ref_D.reset(reinterpret_cast<ElementComputeEpilogue*>(workspace.get()), layout_D);
"""
split_k_update = """
arguments.output_op = {ElementComputeEpilogue(1), ElementComputeEpilogue(0)};
status = conv2d_op.update(arguments, workspace.get());
TVM_FFI_ICHECK(status == cutlass::Status::kSuccess);
"""
split_k_reduction = """
ReductionDevice reduction_op;
const static cutlass::conv::Operator kConvolutionalOperator = Conv2d::kConvolutionalOperator;
typename ReductionDevice::Arguments reduction_args(
cutlass::conv::implicit_gemm_problem_size(kConvolutionalOperator, problem_size).mn(),
problem_size.split_k_slices,
cutlass::conv::implicit_gemm_tensor_c_size(kConvolutionalOperator, problem_size),
{
reinterpret_cast<Conv2d::ElementAccumulator*> (workspace.get()),
ReductionStrideIndex(tensor_c.stride()[Conv2d::UnderlyingKernel::kTensorCStrideIdx])
},
{
tensor_d.data(),
ReductionStrideIndex(tensor_d.stride()[Conv2d::UnderlyingKernel::kTensorCStrideIdx])
},
{
tensor_c.data(),
ReductionStrideIndex(tensor_c.stride()[Conv2d::UnderlyingKernel::kTensorCStrideIdx])
},
{alpha, beta}
);
status = reduction_op.initialize(reduction_args, nullptr);
status = reduction_op();
"""
op_type = attrs["op_type"]
has_bias = "bias" in op_type
use_split_k = "splitk" in attrs["cutlass_op_name"]
is_wgrad = "backward_weight" in op_type
is_dgrad = "conv2d_transpose" in op_type
has_residual_block = "residual" in op_type
no_bias_scaling = op_type not in [
"cutlass.conv2d_bias_sigmoid",
"cutlass.conv2d_bias_silu",
"cutlass.conv2d_bias_hardswish",
]
aux_map = {}
if (not has_bias or no_bias_scaling) and not has_residual_block:
aux_map["beta"] = 0
else:
aux_map["beta"] = 1
if has_residual_block:
aux_map["bias_decl"] = "void* ptr_bias = (void*)(${bias_arg}->data);\n"
aux_map["residual_decl"] = "void* ptr_residual = (void*)(${residual_arg}->data);"
aux_map["tensor_c"] = "ptr_residual"
aux_map["tensor_c_layout"] = "layout_C"
elif has_bias:
aux_map["bias_decl"] = "void* ptr_c_bias = (void*)(${bias_arg}->data);\n"
aux_map["residual_decl"] = ""
aux_map["tensor_c"] = "ptr_c_bias"
aux_map["tensor_c_layout"] = "cutlass::layout::TensorNHWC::Stride(0)"
else:
aux_map["bias_decl"] = ""
aux_map["residual_decl"] = ""
aux_map["tensor_c"] = "ptr_out"
aux_map["tensor_c_layout"] = "layout_C"
if has_bias and no_bias_scaling and not has_residual_block:
aux_map["alpha_beta"] = "alpha"
else:
aux_map["alpha_beta"] = "alpha, beta"
if has_residual_block:
aux_map["additional_args"] = ", static_cast<ElementOutput*>(ptr_bias), nullptr, 0, K"
else:
aux_map["additional_args"] = ""
aux_map["residual_shape_decl"] = ""
if is_wgrad:
aux_map["A_shape"] = "output_shape"
aux_map["B_shape"] = "activation_shape"
aux_map["C_shape"] = "weight_shape"
aux_map["D_shape"] = "weight_shape"
elif is_dgrad:
aux_map["A_shape"] = "output_shape"
aux_map["B_shape"] = "weight_shape"
aux_map["C_shape"] = "activation_shape"
aux_map["D_shape"] = "activation_shape"
else:
aux_map["A_shape"] = "activation_shape"
aux_map["B_shape"] = "weight_shape"
aux_map["D_shape"] = "output_shape"
if has_residual_block:
res_shape = list(attrs.pop("residual_shape"))
shape_str = f"cutlass::make_Coord({res_shape[0]}, {res_shape[1]}, {res_shape[2]}, K)"
aux_map["residual_shape_decl"] = (
f"auto residual_shape = TensorNHWC::packed({shape_str});"
)
aux_map["C_shape"] = "residual_shape"
if res_shape == [int(attrs[c]) for c in ["N", "H", "W", "K"]]:
aux_map["tensor_c_layout"] = "layout_C"
else:
# bias-like residual input
aux_map["tensor_c_layout"] = "cutlass::layout::TensorNHWC::Stride(0)"
else:
aux_map["C_shape"] = "output_shape"
if use_split_k:
aux_map["ElementOutput"] = "EpilogueOutputOp::ElementOutput"
aux_map["tensor_c_arg"] = "{nullptr, TensorNHWC()}"
aux_map["tensor_d_arg"] = "{nullptr, TensorNHWC()}"
aux_map["split_k_reset"] = split_k_reset
aux_map["split_k_update"] = split_k_update
aux_map["split_k_reduction"] = split_k_reduction
else:
aux_map["ElementOutput"] = "Conv2d::ElementC"
aux_map["tensor_c_arg"] = "tensor_c"
aux_map["tensor_d_arg"] = "tensor_d"
aux_map["split_k_reset"] = aux_map["split_k_update"] = aux_map["split_k_reduction"] = ""
template = substitute_template(template, aux_map)
return substitute_template(template, attrs)