| 1 | /****************************************************************************** |
| 2 | * Copyright (c) 2023, Tri Dao. |
| 3 | ******************************************************************************/ |
| 4 | // clang-format off |
| 5 | // adapted from https://github.com/state-spaces/mamba/blob/main/csrc/selective_scan/selective_scan.h |
| 6 | |
| 7 | #pragma once |
| 8 | |
| 9 | #ifndef USE_ROCM |
| 10 | #include <cuda_bf16.h> |
| 11 | #else |
| 12 | #include <hip/hip_bf16.h> |
| 13 | #endif |
| 14 | #include <cuda_fp16.h> |
| 15 | |
| 16 | #include <torch/headeronly/util/Half.h> |
| 17 | #include <torch/headeronly/util/BFloat16.h> |
| 18 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 19 | |
| 20 | struct SSMParamsBase { |
| 21 | using index_t = size_t; |
| 22 | |
| 23 | int batch, dim, seqlen, dstate, n_groups; |
| 24 | int dim_ngroups_ratio; |
| 25 | bool is_variable_B; |
| 26 | bool is_variable_C; |
| 27 | int64_t null_block_id; |
| 28 | |
| 29 | bool delta_softplus; |
| 30 | bool cache_enabled; |
| 31 | int block_size; |
| 32 | |
| 33 | index_t A_d_stride; |
| 34 | index_t A_dstate_stride; |
| 35 | index_t B_batch_stride; |
| 36 | index_t B_d_stride; |
| 37 | index_t B_dstate_stride; |
| 38 | index_t B_group_stride; |
| 39 | index_t C_batch_stride; |
| 40 | index_t C_d_stride; |
| 41 | index_t C_dstate_stride; |
| 42 | index_t C_group_stride; |
| 43 | index_t u_batch_stride; |
| 44 | index_t u_d_stride; |
| 45 | index_t delta_batch_stride; |
| 46 | index_t delta_d_stride; |
| 47 | index_t z_batch_stride; |
| 48 | index_t z_d_stride; |
| 49 | index_t out_batch_stride; |
| 50 | index_t out_d_stride; |
| 51 | index_t out_z_batch_stride; |
| 52 | index_t out_z_d_stride; |
| 53 | index_t ssm_states_batch_stride; |
| 54 | index_t ssm_states_dim_stride; |
| 55 | index_t ssm_states_dstate_stride; |
| 56 | index_t cache_indices_stride; |
| 57 | |
| 58 | // Common data pointers. |
| 59 | void *__restrict__ A_ptr; |
| 60 | void *__restrict__ B_ptr; |
| 61 | void *__restrict__ C_ptr; |
| 62 | void *__restrict__ D_ptr; |
| 63 | void *__restrict__ u_ptr; |
| 64 | void *__restrict__ delta_ptr; |
| 65 | void *__restrict__ delta_bias_ptr; |
| 66 | void *__restrict__ out_ptr; |
| 67 | void *__restrict__ ssm_states_ptr; |
| 68 | void *__restrict__ z_ptr; |
| 69 | void *__restrict__ out_z_ptr; |
| 70 | |
| 71 | void *__restrict__ query_start_loc_ptr; |
| 72 | void *__restrict__ cache_indices_ptr; |
| 73 | void *__restrict__ has_initial_state_ptr; |
| 74 | |
| 75 | void *__restrict__ block_idx_first_scheduled_token_ptr; // (batch,) - first block to write |
| 76 | void *__restrict__ block_idx_last_scheduled_token_ptr; // (batch,) - last block to write |
| 77 | void *__restrict__ initial_state_idx_ptr; // (batch,) - index of the initial state to use |
| 78 | void *__restrict__ cu_chunk_seqlen_ptr; // (nchunks+1,) - cumulative chunk token offsets |
| 79 | void *__restrict__ last_chunk_indices_ptr; // (batch,) - index of last chunk per sequence |
| 80 | }; |
| 81 | |
| 82 | |
| 83 | |
| 84 | |
| 85 | #ifndef USE_ROCM |
| 86 | |
| 87 | constexpr size_t custom_max(std::initializer_list<size_t> ilist) |
| 88 | { |
| 89 | return std::max(ilist); |
| 90 | } |
| 91 | |
| 92 | template<typename T> |
| 93 | constexpr T constexpr_min(T a, T b) { |
| 94 | return std::min(a, b); |
| 95 | } |
| 96 | |
| 97 | #else |
| 98 | constexpr size_t custom_max(std::initializer_list<size_t> ilist) |
| 99 | { |
| 100 | return *std::max_element(ilist.begin(), ilist.end()); |
| 101 | } |
| 102 | |
| 103 | template<typename T> |
| 104 | constexpr T constexpr_min(T a, T b) { |
| 105 | return a < b ? a : b; |
| 106 | } |
| 107 | #endif |
| 108 | |
| 109 | |
| 110 | #define MAX_DSTATE 256 |
| 111 | |
| 112 | |
| 113 | inline __device__ float2 operator+(const float2 & a, const float2 & b){ |
| 114 | return {a.x + b.x, a.y + b.y}; |
| 115 | } |
| 116 | |
| 117 | inline __device__ float3 operator+(const float3 &a, const float3 &b) { |
| 118 | return {a.x + b.x, a.y + b.y, a.z + b.z}; |
| 119 | } |
| 120 | |
| 121 | inline __device__ float4 operator+(const float4 & a, const float4 & b){ |
| 122 | return {a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w}; |
| 123 | } |
| 124 | |
| 125 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 126 | |
| 127 | template<int BYTES> struct BytesToType {}; |
| 128 | |
| 129 | template<> struct BytesToType<16> { |
| 130 | using Type = uint4; |
| 131 | static_assert(sizeof(Type) == 16); |
| 132 | }; |
| 133 | |
| 134 | template<> struct BytesToType<8> { |
| 135 | using Type = uint64_t; |
| 136 | static_assert(sizeof(Type) == 8); |
| 137 | }; |
| 138 | |
| 139 | template<> struct BytesToType<4> { |
| 140 | using Type = uint32_t; |
| 141 | static_assert(sizeof(Type) == 4); |
| 142 | }; |
| 143 | |
| 144 | template<> struct BytesToType<2> { |
| 145 | using Type = uint16_t; |
| 146 | static_assert(sizeof(Type) == 2); |
| 147 | }; |
| 148 | |
| 149 | template<> struct BytesToType<1> { |
| 150 | using Type = uint8_t; |
| 151 | static_assert(sizeof(Type) == 1); |
| 152 | }; |
| 153 | |
| 154 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 155 | |
| 156 | template<typename scalar_t, int N> |
| 157 | struct Converter{ |
| 158 | static inline __device__ void to_float(const scalar_t (&src)[N], float (&dst)[N]) { |
| 159 | #pragma unroll |
| 160 | for (int i = 0; i < N; ++i) { dst[i] = src[i]; } |
| 161 | } |
| 162 | }; |
| 163 | |
| 164 | template<int N> |
| 165 | struct Converter<torch::headeronly::Half, N>{ |
| 166 | static inline __device__ void to_float(const torch::headeronly::Half (&src)[N], float (&dst)[N]) { |
| 167 | static_assert(N % 2 == 0); |
| 168 | auto &src2 = reinterpret_cast<const half2 (&)[N / 2]>(src); |
| 169 | auto &dst2 = reinterpret_cast<float2 (&)[N / 2]>(dst); |
| 170 | #pragma unroll |
| 171 | for (int i = 0; i < N / 2; ++i) { dst2[i] = __half22float2(src2[i]); } |
| 172 | } |
| 173 | }; |
| 174 | |
| 175 | #if __CUDA_ARCH__ >= 800 |
| 176 | template<int N> |
| 177 | struct Converter<torch::headeronly::BFloat16, N>{ |
| 178 | static inline __device__ void to_float(const torch::headeronly::BFloat16 (&src)[N], float (&dst)[N]) { |
| 179 | static_assert(N % 2 == 0); |
| 180 | auto &src2 = reinterpret_cast<const nv_bfloat162 (&)[N / 2]>(src); |
| 181 | auto &dst2 = reinterpret_cast<float2 (&)[N / 2]>(dst); |
| 182 | #pragma unroll |
| 183 | for (int i = 0; i < N / 2; ++i) { dst2[i] = __bfloat1622float2(src2[i]); } |
| 184 | } |
| 185 | }; |
| 186 | #endif |
| 187 | |
| 188 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 189 | |
| 190 | |
| 191 | template<typename scalar_t> struct SSMScanOp; |
| 192 | |
| 193 | template<> |
| 194 | struct SSMScanOp<float> { |
| 195 | __device__ __forceinline__ float2 operator()(const float2 &ab0, const float2 &ab1) const { |
| 196 | return make_float2(ab1.x * ab0.x, ab1.x * ab0.y + ab1.y); |
| 197 | } |
| 198 | }; |
| 199 | |
| 200 | // A stateful callback functor that maintains a running prefix to be applied |
| 201 | // during consecutive scan operations. |
| 202 | template <typename scalar_t> struct SSMScanPrefixCallbackOp { |
| 203 | using scan_t = std::conditional_t<std::is_same_v<scalar_t, float>, float2, float4>; |
| 204 | scan_t running_prefix; |
| 205 | // Constructor |
| 206 | __device__ SSMScanPrefixCallbackOp(scan_t running_prefix_) : running_prefix(running_prefix_) {} |
| 207 | // Callback operator to be entered by the first warp of threads in the block. |
| 208 | // Thread-0 is responsible for returning a value for seeding the block-wide scan. |
| 209 | __device__ scan_t operator()(scan_t block_aggregate) { |
| 210 | scan_t old_prefix = running_prefix; |
| 211 | running_prefix = SSMScanOp<scalar_t>()(running_prefix, block_aggregate); |
| 212 | return old_prefix; |
| 213 | } |
| 214 | }; |
| 215 | |
| 216 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 217 | |
| 218 | template<typename Ktraits> |
| 219 | inline __device__ void load_input(typename Ktraits::input_t *u, |
| 220 | typename Ktraits::input_t (&u_vals)[Ktraits::kNItems], |
| 221 | typename Ktraits::BlockLoadT::TempStorage &smem_load, |
| 222 | int seqlen) { |
| 223 | if constexpr (Ktraits::kIsEvenLen && !Ktraits::kVarlen) { |
| 224 | auto& smem_load_vec = reinterpret_cast<typename Ktraits::BlockLoadVecT::TempStorage&>(smem_load); |
| 225 | using vec_t = typename Ktraits::vec_t; |
| 226 | typename Ktraits::BlockLoadVecT(smem_load_vec).Load( |
| 227 | reinterpret_cast<vec_t*>(u), |
| 228 | reinterpret_cast<vec_t(&)[Ktraits::kNLoads]>(u_vals) |
| 229 | #ifdef USE_ROCM |
| 230 | , Ktraits::kNThreads * Ktraits::kNLoads |
| 231 | #endif |
| 232 | |
| 233 | ); |
| 234 | } else { |
| 235 | typename Ktraits::BlockLoadT(smem_load).Load(u, u_vals, seqlen, 0.f); |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | |
| 240 | template<typename Ktraits> |
| 241 | inline __device__ void load_weight(typename Ktraits::input_t *Bvar, |
| 242 | typename Ktraits::weight_t (&B_vals)[Ktraits::kNItems], |
| 243 | typename Ktraits::BlockLoadWeightT::TempStorage &smem_load_weight, |
| 244 | int seqlen) { |
| 245 | constexpr int kNItems = Ktraits::kNItems; |
| 246 | typename Ktraits::input_t B_vals_load[kNItems]; |
| 247 | if constexpr (Ktraits::kIsEvenLen && !Ktraits::kVarlen) { |
| 248 | auto& smem_load_weight_vec = reinterpret_cast<typename Ktraits::BlockLoadWeightVecT::TempStorage&>(smem_load_weight); |
| 249 | using vec_t = typename Ktraits::vec_t; |
| 250 | typename Ktraits::BlockLoadWeightVecT(smem_load_weight_vec).Load( |
| 251 | reinterpret_cast<vec_t*>(Bvar), |
| 252 | reinterpret_cast<vec_t(&)[Ktraits::kNLoads]>(B_vals_load) |
| 253 | ); |
| 254 | } else { |
| 255 | typename Ktraits::BlockLoadWeightT(smem_load_weight).Load(Bvar, B_vals_load, seqlen, 0.f); |
| 256 | } |
| 257 | // #pragma unroll |
| 258 | // for (int i = 0; i < kNItems; ++i) { B_vals[i] = B_vals_load[i]; } |
| 259 | Converter<typename Ktraits::input_t, kNItems>::to_float(B_vals_load, B_vals); |
| 260 | } |
| 261 | |
| 262 | template<typename Ktraits> |
| 263 | inline __device__ void store_output(typename Ktraits::input_t *out, |
| 264 | const float (&out_vals)[Ktraits::kNItems], |
| 265 | typename Ktraits::BlockStoreT::TempStorage &smem_store, |
| 266 | int seqlen) { |
| 267 | typename Ktraits::input_t write_vals[Ktraits::kNItems]; |
| 268 | #pragma unroll |
| 269 | for (int i = 0; i < Ktraits::kNItems; ++i) { write_vals[i] = out_vals[i]; } |
| 270 | if constexpr (Ktraits::kIsEvenLen && !Ktraits::kVarlen) { |
| 271 | auto& smem_store_vec = reinterpret_cast<typename Ktraits::BlockStoreVecT::TempStorage&>(smem_store); |
| 272 | using vec_t = typename Ktraits::vec_t; |
| 273 | typename Ktraits::BlockStoreVecT(smem_store_vec).Store( |
| 274 | reinterpret_cast<vec_t*>(out), |
| 275 | reinterpret_cast<vec_t(&)[Ktraits::kNLoads]>(write_vals) |
| 276 | ); |
| 277 | } else { |
| 278 | typename Ktraits::BlockStoreT(smem_store).Store(out, write_vals, seqlen); |
| 279 | } |
| 280 | } |