# 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 # ruff: noqa: E501 """Generator for CUTLASS attention kernels.""" from .library import substitute_template def instantiate_attention_template(attrs): """Return CUTLASS host code for fused multi head attention based on a template and the provided attribute map.""" bias_template = """ TVM_FFI_ICHECK(${bias}->ndim == 4); // B, N, S, S' p.attn_bias_ptr = reinterpret_cast(${bias}->data); p.bias_strideM = ${bias_strideM}; p.bias_strideH = ${bias_strideH}; p.bias_strideB = ${bias_strideB}; """ var_len_template = """ p.seqstart_q_ptr = (int32_t*)${seqstart_q}->data; p.seqstart_k_ptr = (int32_t*)${seqstart_k}->data; p.num_queries = ((int32_t*)${max_seqlen_q}->data)[0]; p.num_batches = ${seqstart_q}->shape[0] - 1; """ qkv_template = { "default": """ p.query_ptr = reinterpret_cast(${query}->data); p.key_ptr = reinterpret_cast(${key}->data); p.value_ptr = reinterpret_cast(${value}->data); TVM_FFI_ICHECK(${query}->ndim == 4); // B, S, N, H TVM_FFI_ICHECK(${key}->ndim == 4); // B, S', N, H TVM_FFI_ICHECK(${value}->ndim == 4); // B, S', N, H' // stride for N p.q_strideH = p.head_dim; // H p.k_strideH = p.head_dim; // H p.v_strideH = p.head_dim_value; // H' // stride for S p.q_strideM = p.q_strideH * p.num_heads; // H * N p.k_strideM = p.k_strideH * p.num_heads; // H * N p.v_strideM = p.v_strideH * p.num_heads; // H' * N // stride for B p.q_strideB = p.q_strideM * p.num_queries; // H * N * S p.k_strideB = p.k_strideM * p.num_keys; // H * N * S' p.v_strideB = p.v_strideM * p.num_keys; // H'* N * S' """, "qkv_stacked": """ p.query_ptr = reinterpret_cast(${qkv}->data); p.key_ptr = reinterpret_cast(${qkv}->data) + p.head_dim * p.num_heads; p.value_ptr = reinterpret_cast(${qkv}->data) + p.head_dim * p.num_heads * 2; TVM_FFI_ICHECK(${qkv}->ndim == 3); // B, S, NH + NH + NH' // stride for N p.q_strideH = p.head_dim; // H p.k_strideH = p.head_dim; // H p.v_strideH = p.head_dim_value; // H' // stride for S p.q_strideM = p.k_strideM = p.v_strideM = p.q_strideH * p.num_heads + p.k_strideH * p.num_heads + p.v_strideH * p.num_heads; // H * N + H * N + H * N' // stride for B p.q_strideB = p.k_strideB = p.v_strideB = p.q_strideM * p.num_queries; // (H * N + H * N + H * N') * S """, } template = """ using T = ${data_type}; using Attention = AttentionKernel; typename Attention::Params p; p.logsumexp_ptr = nullptr; p.output_ptr = reinterpret_cast(out0->data); p.output_accum_ptr = nullptr; uint64_t accumulator_buf_size = ${output_size} * sizeof(Attention::output_accum_t); bool accumulator_buf_allocated = false; if (Attention::kNeedsOutputAccumulatorBuffer) { if (accumulator_buf_size <= ${workspace}->shape[0]) { p.output_accum_ptr = static_cast(${workspace}->data); } else { accumulator_buf_allocated = true; cudaMalloc( &p.output_accum_ptr, accumulator_buf_size ); } } p.num_heads = ${num_heads}; // N p.num_batches = ${num_batches}; // B p.head_dim = ${head_dim}; // H p.head_dim_value = ${head_dim_value}; // H' p.num_queries = ${num_queries}; // S p.num_keys = ${num_keys}; // S' p.scale = ${scale}; p.custom_mask_type = ${custom_mask_type}; p.o_strideM = p.head_dim_value * p.num_heads; // H' * N TVM_FFI_ICHECK(out0->ndim == 4); // B, S, N, H' ${qkv_template} ${bias_template} ${var_len_template} constexpr auto kernel_fn = attention_kernel_batched_impl; int smem_bytes = sizeof(typename Attention::SharedStorage); if (smem_bytes > 0xc000) { static bool once = [&]() { cudaFuncSetAttribute( kernel_fn, cudaFuncAttributeMaxDynamicSharedMemorySize, smem_bytes); return true; }(); } TVM_FFI_ICHECK(Attention::check_supported(p)); cudaStream_t stream = static_cast(TVMFFIEnvGetStream(kDLCUDA, ${query}->device.device_id)); kernel_fn<<>>(p); if (accumulator_buf_allocated) { cudaFree(p.output_accum_ptr); } """ template = substitute_template( template, { "qkv_template": qkv_template[attrs["qkv_layout"]], "bias_template": bias_template if "bias" in attrs else "", "var_len_template": var_len_template if "seqstart_q" in attrs else "", }, ) return substitute_template(template, attrs) def instantiate_flash_attention_template(attrs): """Return host code for flash attention.""" template = """ int q_head_stride = ${head_dim}; int k_head_stride = ${head_dim}; int v_head_stride = ${head_dim}; int o_head_stride = ${head_dim}; int q_row_stride = q_head_stride * ${num_q_heads}; int k_row_stride = k_head_stride * ${num_kv_heads}; int v_row_stride = v_head_stride * ${num_kv_heads}; int o_row_stride = o_head_stride * ${num_q_heads}; int q_batch_stride = q_row_stride * ${num_queries}; int k_batch_stride = k_row_stride * ${num_keys}; int v_batch_stride = v_row_stride * ${num_keys}; int o_batch_stride = o_row_stride * ${num_queries}; cudaStream_t stream = static_cast(TVMFFIEnvGetStream(kDLCUDA, ${query}->device.device_id)); flash_attn::flash_attention_forward( static_cast(${query}->data), static_cast(${key}->data), static_cast(${value}->data), static_cast(out0->data), ${num_batches}, ${num_queries}, ${num_keys}, ${num_q_heads}, ${num_kv_heads}, ${head_dim}, q_batch_stride, k_batch_stride, v_batch_stride, o_batch_stride, q_head_stride, k_head_stride, v_head_stride, o_head_stride, q_row_stride, k_row_stride, v_row_stride, o_row_stride, ${scale}, ${is_causal}, ${window_size_left}, ${window_size_right}, stream); """ template_stacked = """ int q_head_stride = ${head_dim}; int k_head_stride = ${head_dim}; int v_head_stride = ${head_dim}; int o_head_stride = ${head_dim}; int row_stride = q_head_stride * ${num_q_heads} + k_head_stride * ${num_kv_heads} + v_head_stride * ${num_kv_heads}; int q_row_stride = row_stride; int k_row_stride = row_stride; int v_row_stride = row_stride; int o_row_stride = o_head_stride * ${num_q_heads}; int q_batch_stride = q_row_stride * ${num_queries}; int k_batch_stride = k_row_stride * ${num_keys}; int v_batch_stride = v_row_stride * ${num_keys}; int o_batch_stride = o_row_stride * ${num_queries}; cudaStream_t stream = static_cast(TVMFFIEnvGetStream(kDLCUDA, ${query}->device.device_id)); flash_attn::flash_attention_forward( static_cast(${qkv}->data), static_cast(${qkv}->data) + ${head_dim} * ${num_q_heads}, static_cast(${qkv}->data) + ${head_dim} * (${num_q_heads} + ${num_kv_heads}), static_cast(out0->data), ${num_batches}, ${num_queries}, ${num_keys}, ${num_q_heads}, ${num_kv_heads}, ${head_dim}, q_batch_stride, k_batch_stride, v_batch_stride, o_batch_stride, q_head_stride, k_head_stride, v_head_stride, o_head_stride, q_row_stride, k_row_stride, v_row_stride, o_row_stride, ${scale}, ${is_causal}, ${window_size_left}, ${window_size_right}, stream); """ if "qkv" in attrs: return substitute_template(template_stacked, attrs) return substitute_template(template, attrs) def instantiate_flash_attention_var_len_template(attrs): """Return host code for flash attention with variable sequence lengths.""" template = """ int _max_seqlen_q = ((int32_t*)${max_seqlen_q}->data)[0]; int _max_seqlen_k = ((int32_t*)${max_seqlen_k}->data)[0]; int batch_size = ${seqstart_q}->shape[0] - 1; int q_head_stride = ${head_dim}; int k_head_stride = ${head_dim}; int v_head_stride = ${head_dim}; int o_head_stride = ${head_dim}; int q_row_stride = q_head_stride * ${num_q_heads}; int k_row_stride = k_head_stride * ${num_kv_heads}; int v_row_stride = v_head_stride * ${num_kv_heads}; int o_row_stride = o_head_stride * ${num_q_heads}; cudaStream_t stream = static_cast(TVMFFIEnvGetStream(kDLCUDA, ${query}->device.device_id)); flash_attn::flash_attention_var_len_forward( static_cast(${query}->data), static_cast(${key}->data), static_cast(${value}->data), static_cast(${seqstart_q}->data), static_cast(${seqstart_k}->data), static_cast(out0->data), batch_size, _max_seqlen_q, _max_seqlen_k, ${num_q_heads}, ${num_kv_heads}, ${head_dim}, q_head_stride, k_head_stride, v_head_stride, o_head_stride, q_row_stride, k_row_stride, v_row_stride, o_row_stride, ${scale}, ${is_causal}, // For SWA, is_causal must be false. ${is_causal} ? _max_seqlen_k : ${window_size_left}, ${window_size_right}, stream); """ return substitute_template(template, attrs)