4411 lines
138 KiB
Metal
4411 lines
138 KiB
Metal
// MLX 0.32.2 GPU support, Copyright Apple Inc., MIT; see MLX-LICENSE.txt.
|
|
// Flattened only the GPU header closure used by MTPLX 9882703 row-map gather.
|
|
// Copyright © 2023-2024 Apple Inc.
|
|
|
|
|
|
#include <metal_math>
|
|
|
|
// Copyright © 2023 Apple Inc.
|
|
|
|
|
|
#include <metal_stdlib>
|
|
|
|
using namespace metal;
|
|
|
|
typedef bfloat bfloat16_t;
|
|
inline uint16_t bfloat16_to_uint16(const bfloat16_t x) {
|
|
return as_type<uint16_t>(x);
|
|
}
|
|
|
|
inline bfloat16_t uint16_to_bfloat16(const uint16_t x) {
|
|
return as_type<bfloat16_t>(x);
|
|
}
|
|
// Copyright © 2023 Apple Inc.
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Metal math for bfloat16
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
|
|
Following the Metal Shading Language Specification (Metal 3.1)
|
|
|
|
"bfloat is an extended itypeing point type that only allows implicit conversion
|
|
to a type of greater itypeing point rank. While bfloat can be implicitly
|
|
converted to itype, it cannot be implicitly converted to half, and neither
|
|
itype nor half can be implicitly converted to bfloat."
|
|
|
|
Further, as far as I can tell, the stdlib math/simd functions are not defined
|
|
for bfloat and calling with an argument of type bfloat will result in that
|
|
argument getting implicitly converted to itype which then returns an output
|
|
that is (likely) a itype which cannot be implicitly converted into a bfloat
|
|
|
|
This leads to situations where
|
|
bfloat a = 5.0bf;
|
|
bfloat b = metal::abs(a); // this will throw an error since abs return itype
|
|
bfloat c = static_cast<bfloat>(metal::abs(a)); // this is fine
|
|
|
|
For the moment, I will be adding overloaded instantiations of the math
|
|
functions to accordingly automatically handle the casting
|
|
|
|
*/
|
|
|
|
#define instantiate_metal_math_funcs(itype, otype, ctype, mfast) \
|
|
\
|
|
METAL_FUNC otype abs(itype x) { \
|
|
return static_cast<otype>(__metal_fabs(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype acos(itype x) { \
|
|
return static_cast<otype>(__metal_acos(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype acosh(itype x) { \
|
|
return static_cast<otype>(__metal_acosh(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype asin(itype x) { \
|
|
return static_cast<otype>(__metal_asin(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype asinh(itype x) { \
|
|
return static_cast<otype>(__metal_asinh(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype atan(itype y_over_x) { \
|
|
return static_cast<otype>( \
|
|
__metal_atan(static_cast<ctype>(y_over_x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype atan2(itype y, itype x) { \
|
|
return static_cast<otype>( \
|
|
__metal_atan2(static_cast<ctype>(y), static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype atanh(itype x) { \
|
|
return static_cast<otype>(__metal_atanh(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype ceil(itype x) { \
|
|
return static_cast<otype>(__metal_ceil(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype cos(itype x) { \
|
|
return static_cast<otype>(__metal_cos(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype cosh(itype x) { \
|
|
return static_cast<otype>(__metal_cosh(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype cospi(itype x) { \
|
|
return static_cast<otype>(__metal_cospi(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype divide(itype x, itype y) { \
|
|
return static_cast<otype>( \
|
|
__metal_divide(static_cast<ctype>(x), static_cast<ctype>(y), mfast)); \
|
|
} \
|
|
METAL_FUNC otype exp(itype x) { \
|
|
return static_cast<otype>(__metal_exp(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype exp10(itype x) { \
|
|
return static_cast<otype>(__metal_exp10(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype exp2(itype x) { \
|
|
return static_cast<otype>(__metal_exp2(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype fabs(itype x) { \
|
|
return static_cast<otype>(__metal_fabs(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype fdim(itype x, itype y) { \
|
|
ctype t = static_cast<ctype>(x - y); \
|
|
return static_cast<otype>(select(t, ctype(0), t < ctype(0) || x == y)); \
|
|
} \
|
|
METAL_FUNC otype floor(itype x) { \
|
|
return static_cast<otype>(__metal_floor(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype fma(itype x, itype y, itype z) { \
|
|
return static_cast<otype>(__metal_fma( \
|
|
static_cast<ctype>(x), static_cast<ctype>(y), static_cast<ctype>(z))); \
|
|
} \
|
|
METAL_FUNC otype fmax(itype x, itype y) { \
|
|
return static_cast<otype>( \
|
|
__metal_fmax(static_cast<ctype>(x), static_cast<ctype>(y), mfast)); \
|
|
} \
|
|
METAL_FUNC otype fmax3(itype x, itype y, itype z) { \
|
|
return static_cast<otype>(__metal_fmax3( \
|
|
static_cast<ctype>(x), \
|
|
static_cast<ctype>(y), \
|
|
static_cast<ctype>(z), \
|
|
mfast)); \
|
|
} \
|
|
METAL_FUNC otype fmedian3(itype x, itype y, itype z) { \
|
|
return static_cast<otype>(__metal_fmedian3( \
|
|
static_cast<ctype>(x), \
|
|
static_cast<ctype>(y), \
|
|
static_cast<ctype>(z), \
|
|
mfast)); \
|
|
} \
|
|
METAL_FUNC otype fmin(itype x, itype y) { \
|
|
return static_cast<otype>( \
|
|
__metal_fmin(static_cast<ctype>(x), static_cast<ctype>(y), mfast)); \
|
|
} \
|
|
METAL_FUNC otype fmin3(itype x, itype y, itype z) { \
|
|
return static_cast<otype>(__metal_fmin3( \
|
|
static_cast<ctype>(x), \
|
|
static_cast<ctype>(y), \
|
|
static_cast<ctype>(z), \
|
|
mfast)); \
|
|
} \
|
|
METAL_FUNC otype fmod(itype x, itype y) { \
|
|
return static_cast<otype>( \
|
|
__metal_fmod(static_cast<ctype>(x), static_cast<ctype>(y), mfast)); \
|
|
} \
|
|
METAL_FUNC otype fract(itype x) { \
|
|
return static_cast<otype>(__metal_fract(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype frexp(itype x, thread int& exp) { \
|
|
return static_cast<otype>(__metal_frexp(static_cast<ctype>(x), &exp)); \
|
|
} \
|
|
METAL_FUNC otype ldexp(itype x, int k) { \
|
|
return static_cast<otype>(__metal_ldexp(static_cast<ctype>(x), k, mfast)); \
|
|
} \
|
|
METAL_FUNC otype log(itype x) { \
|
|
return static_cast<otype>(__metal_log(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype log10(itype x) { \
|
|
return static_cast<otype>(__metal_log10(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype log2(itype x) { \
|
|
return static_cast<otype>(__metal_log2(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype max(itype x, itype y) { \
|
|
return static_cast<otype>( \
|
|
__metal_fmax(static_cast<ctype>(x), static_cast<ctype>(y), mfast)); \
|
|
} \
|
|
METAL_FUNC otype max3(itype x, itype y, itype z) { \
|
|
return static_cast<otype>(__metal_fmax3( \
|
|
static_cast<ctype>(x), \
|
|
static_cast<ctype>(y), \
|
|
static_cast<ctype>(z), \
|
|
mfast)); \
|
|
} \
|
|
METAL_FUNC otype median3(itype x, itype y, itype z) { \
|
|
return static_cast<otype>(__metal_fmedian3( \
|
|
static_cast<ctype>(x), \
|
|
static_cast<ctype>(y), \
|
|
static_cast<ctype>(z), \
|
|
mfast)); \
|
|
} \
|
|
METAL_FUNC otype min(itype x, itype y) { \
|
|
return static_cast<otype>( \
|
|
__metal_fmin(static_cast<ctype>(x), static_cast<ctype>(y), mfast)); \
|
|
} \
|
|
METAL_FUNC otype min3(itype x, itype y, itype z) { \
|
|
return static_cast<otype>(__metal_fmin3( \
|
|
static_cast<ctype>(x), \
|
|
static_cast<ctype>(y), \
|
|
static_cast<ctype>(z), \
|
|
mfast)); \
|
|
} \
|
|
METAL_FUNC otype nextafter(itype x, itype y) { \
|
|
return static_cast<otype>( \
|
|
__metal_nextafter(static_cast<ctype>(x), static_cast<ctype>(y))); \
|
|
} \
|
|
METAL_FUNC otype pow(itype x, itype y) { \
|
|
return static_cast<otype>( \
|
|
__metal_pow(static_cast<ctype>(x), static_cast<ctype>(y), mfast)); \
|
|
} \
|
|
METAL_FUNC otype powr(itype x, itype y) { \
|
|
return static_cast<otype>( \
|
|
__metal_powr(static_cast<ctype>(x), static_cast<ctype>(y), mfast)); \
|
|
} \
|
|
METAL_FUNC otype rint(itype x) { \
|
|
return static_cast<otype>(__metal_rint(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype round(itype x) { \
|
|
return static_cast<otype>(__metal_round(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype rsqrt(itype x) { \
|
|
return static_cast<otype>(__metal_rsqrt(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype sin(itype x) { \
|
|
return static_cast<otype>(__metal_sin(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype sinh(itype x) { \
|
|
return static_cast<otype>(__metal_sinh(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype sinpi(itype x) { \
|
|
return static_cast<otype>(__metal_sinpi(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype sqrt(itype x) { \
|
|
return static_cast<otype>(__metal_sqrt(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype tan(itype x) { \
|
|
return static_cast<otype>(__metal_tan(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype tanh(itype x) { \
|
|
return static_cast<otype>(__metal_tanh(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype tanpi(itype x) { \
|
|
return static_cast<otype>(__metal_tanpi(static_cast<ctype>(x), mfast)); \
|
|
} \
|
|
METAL_FUNC otype trunc(itype x) { \
|
|
return static_cast<otype>(__metal_trunc(static_cast<ctype>(x), mfast)); \
|
|
}
|
|
|
|
namespace metal {
|
|
|
|
instantiate_metal_math_funcs(
|
|
bfloat16_t,
|
|
bfloat16_t,
|
|
float,
|
|
__METAL_MAYBE_FAST_MATH__);
|
|
|
|
namespace fast {
|
|
|
|
instantiate_metal_math_funcs(
|
|
bfloat16_t,
|
|
bfloat16_t,
|
|
float,
|
|
__METAL_FAST_MATH__);
|
|
|
|
} // namespace fast
|
|
|
|
namespace precise {
|
|
|
|
instantiate_metal_math_funcs(
|
|
bfloat16_t,
|
|
bfloat16_t,
|
|
float,
|
|
__METAL_PRECISE_MATH__);
|
|
|
|
} // namespace precise
|
|
|
|
} // namespace metal
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Metal simd for bfloat16
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define instantiate_metal_simd_comm_funcs( \
|
|
itype, otype, ctype, itype_to_ctype, ctype_to_otype) \
|
|
\
|
|
METAL_FUNC otype simd_broadcast(itype data, ushort broadcast_lane_id) { \
|
|
return ctype_to_otype( \
|
|
__metal_simd_broadcast(itype_to_ctype(data), broadcast_lane_id)); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_shuffle(itype data, ushort simd_lane_id) { \
|
|
return ctype_to_otype( \
|
|
__metal_simd_shuffle(itype_to_ctype(data), simd_lane_id)); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_shuffle_and_fill_down( \
|
|
itype data, itype filling_data, ushort delta, ushort modulo) { \
|
|
return ctype_to_otype(__metal_simd_shuffle_and_fill_down( \
|
|
itype_to_ctype(data), itype_to_ctype(filling_data), delta, modulo)); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_shuffle_and_fill_down( \
|
|
itype data, itype filling_data, ushort delta) { \
|
|
return ctype_to_otype(__metal_simd_shuffle_and_fill_down( \
|
|
itype_to_ctype(data), \
|
|
itype_to_ctype(filling_data), \
|
|
delta, \
|
|
__metal_get_simdgroup_size(ushort()))); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_shuffle_and_fill_up( \
|
|
itype data, itype filling_data, ushort delta, ushort modulo) { \
|
|
return ctype_to_otype(__metal_simd_shuffle_and_fill_up( \
|
|
itype_to_ctype(data), itype_to_ctype(filling_data), delta, modulo)); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_shuffle_and_fill_up( \
|
|
itype data, itype filling_data, ushort delta) { \
|
|
return ctype_to_otype(__metal_simd_shuffle_and_fill_up( \
|
|
itype_to_ctype(data), \
|
|
itype_to_ctype(filling_data), \
|
|
delta, \
|
|
__metal_get_simdgroup_size(ushort()))); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_shuffle_down(itype data, ushort delta) { \
|
|
return ctype_to_otype( \
|
|
__metal_simd_shuffle_down(itype_to_ctype(data), delta)); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_shuffle_rotate_down(itype data, ushort delta) { \
|
|
return ctype_to_otype( \
|
|
__metal_simd_shuffle_rotate_down(itype_to_ctype(data), delta)); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_shuffle_rotate_up(itype data, ushort delta) { \
|
|
return ctype_to_otype( \
|
|
__metal_simd_shuffle_rotate_up(itype_to_ctype(data), delta)); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_shuffle_up(itype data, ushort delta) { \
|
|
return ctype_to_otype( \
|
|
__metal_simd_shuffle_up(itype_to_ctype(data), delta)); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_shuffle_xor(itype data, ushort mask) { \
|
|
return ctype_to_otype( \
|
|
__metal_simd_shuffle_xor(itype_to_ctype(data), mask)); \
|
|
}
|
|
|
|
#define instantiate_metal_simd_reduction_funcs(itype, otype, ctype) \
|
|
\
|
|
METAL_FUNC otype simd_max(itype data) { \
|
|
return static_cast<otype>(__metal_simd_max(static_cast<ctype>(data))); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_min(itype data) { \
|
|
return static_cast<otype>(__metal_simd_min(static_cast<ctype>(data))); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_prefix_exclusive_product(itype data) { \
|
|
return static_cast<otype>( \
|
|
__metal_simd_prefix_exclusive_product(static_cast<ctype>(data))); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_prefix_exclusive_sum(itype data) { \
|
|
return static_cast<otype>( \
|
|
__metal_simd_prefix_exclusive_sum(static_cast<ctype>(data))); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_prefix_inclusive_product(itype data) { \
|
|
return static_cast<otype>( \
|
|
__metal_simd_prefix_inclusive_product(static_cast<ctype>(data))); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_prefix_inclusive_sum(itype data) { \
|
|
return static_cast<otype>( \
|
|
__metal_simd_prefix_inclusive_sum(static_cast<ctype>(data))); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_product(itype data) { \
|
|
return static_cast<otype>(__metal_simd_product(static_cast<ctype>(data))); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_sum(itype data) { \
|
|
return static_cast<otype>(__metal_simd_sum(static_cast<ctype>(data))); \
|
|
} \
|
|
\
|
|
METAL_FUNC otype simd_xor(itype data) { \
|
|
return static_cast<otype>(__metal_simd_xor(static_cast<ctype>(data))); \
|
|
}
|
|
|
|
namespace metal {
|
|
|
|
instantiate_metal_simd_comm_funcs(
|
|
bfloat16_t,
|
|
bfloat16_t,
|
|
uint16_t,
|
|
bfloat16_to_uint16,
|
|
uint16_to_bfloat16);
|
|
instantiate_metal_simd_reduction_funcs(bfloat16_t, bfloat16_t, float);
|
|
|
|
} // namespace metal
|
|
// Copyright © 2023 Apple Inc.
|
|
|
|
|
|
#include <metal_stdlib>
|
|
|
|
|
|
using namespace metal;
|
|
|
|
template <typename T>
|
|
struct complex_t;
|
|
|
|
template <typename T>
|
|
static constexpr constant bool is_complex_v = false;
|
|
|
|
template <typename T>
|
|
static constexpr constant bool is_complex_v<complex_t<T>> = true;
|
|
|
|
// Metal accepts explicit bfloat casts that is_convertible_v reports as false.
|
|
template <typename From, typename To>
|
|
static constexpr constant bool is_lane_convertible_v =
|
|
is_convertible_v<From, To> ||
|
|
(is_same_v<To, bfloat16_t> && is_convertible_v<From, float>) ||
|
|
(is_same_v<From, bfloat16_t> && is_convertible_v<float, To>);
|
|
|
|
template <typename T>
|
|
struct complex_t {
|
|
using value_type = T;
|
|
|
|
T real;
|
|
T imag;
|
|
|
|
// Constructors
|
|
constexpr complex_t(T real, T imag) thread : real(real), imag(imag) {};
|
|
constexpr complex_t() thread : real(0), imag(0) {};
|
|
constexpr complex_t() threadgroup : real(0), imag(0) {};
|
|
|
|
// Conversions from scalar types
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_complex_v<U> && is_lane_convertible_v<U, T>>::type>
|
|
constexpr complex_t(U x) thread : real(static_cast<T>(x)),
|
|
imag(static_cast<T>(0)) {}
|
|
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_complex_v<U> && is_lane_convertible_v<U, T>>::type>
|
|
constexpr complex_t(U x) threadgroup : real(static_cast<T>(x)),
|
|
imag(static_cast<T>(0)) {}
|
|
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_complex_v<U> && is_lane_convertible_v<U, T>>::type>
|
|
constexpr complex_t(U x) device : real(static_cast<T>(x)),
|
|
imag(static_cast<T>(0)) {}
|
|
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_complex_v<U> && is_lane_convertible_v<U, T>>::type>
|
|
constexpr complex_t(U x) constant : real(static_cast<T>(x)),
|
|
imag(static_cast<T>(0)) {}
|
|
|
|
// Conversions between complex types
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_same_v<U, T> && is_lane_convertible_v<U, T>>::type>
|
|
constexpr complex_t(complex_t<U> x) thread : real(static_cast<T>(x.real)),
|
|
imag(static_cast<T>(x.imag)) {}
|
|
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_same_v<U, T> && is_lane_convertible_v<U, T>>::type>
|
|
constexpr complex_t(complex_t<U> x) threadgroup
|
|
: real(static_cast<T>(x.real)),
|
|
imag(static_cast<T>(x.imag)) {}
|
|
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_same_v<U, T> && is_lane_convertible_v<U, T>>::type>
|
|
constexpr complex_t(complex_t<U> x) device : real(static_cast<T>(x.real)),
|
|
imag(static_cast<T>(x.imag)) {}
|
|
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_same_v<U, T> && is_lane_convertible_v<U, T>>::type>
|
|
constexpr complex_t(complex_t<U> x) constant : real(static_cast<T>(x.real)),
|
|
imag(static_cast<T>(x.imag)) {}
|
|
|
|
// Conversions to and from two-lane vectors (the FFT lane representation)
|
|
constexpr complex_t(vec<T, 2> v) thread : real(v.x), imag(v.y) {};
|
|
constexpr complex_t(vec<T, 2> v) threadgroup : real(v.x), imag(v.y) {};
|
|
constexpr complex_t(vec<T, 2> v) device : real(v.x), imag(v.y) {};
|
|
constexpr complex_t(vec<T, 2> v) constant : real(v.x), imag(v.y) {};
|
|
|
|
constexpr operator vec<T, 2>() const thread {
|
|
return vec<T, 2>(real, imag);
|
|
}
|
|
|
|
constexpr operator vec<T, 2>() const threadgroup {
|
|
return vec<T, 2>(real, imag);
|
|
}
|
|
|
|
constexpr operator vec<T, 2>() const device {
|
|
return vec<T, 2>(real, imag);
|
|
}
|
|
|
|
constexpr operator vec<T, 2>() const constant {
|
|
return vec<T, 2>(real, imag);
|
|
}
|
|
|
|
// Conversions to scalar types
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_complex_v<U> && is_lane_convertible_v<T, U>>::type>
|
|
constexpr operator U() const thread {
|
|
return static_cast<U>(real);
|
|
}
|
|
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_complex_v<U> && is_lane_convertible_v<T, U>>::type>
|
|
constexpr operator U() const threadgroup {
|
|
return static_cast<U>(real);
|
|
}
|
|
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_complex_v<U> && is_lane_convertible_v<T, U>>::type>
|
|
constexpr operator U() const device {
|
|
return static_cast<U>(real);
|
|
}
|
|
|
|
template <
|
|
typename U,
|
|
typename = typename enable_if<
|
|
!is_complex_v<U> && is_lane_convertible_v<T, U>>::type>
|
|
constexpr operator U() const constant {
|
|
return static_cast<U>(real);
|
|
}
|
|
};
|
|
|
|
using complex32_t = complex_t<half>;
|
|
using complex64_t = complex_t<float>;
|
|
|
|
static_assert(sizeof(complex32_t) == 2 * sizeof(half));
|
|
static_assert(sizeof(complex64_t) == 2 * sizeof(float));
|
|
static_assert(sizeof(complex_t<bfloat16_t>) == 2 * sizeof(bfloat16_t));
|
|
|
|
template <typename T>
|
|
constexpr complex_t<T> operator-(complex_t<T> x) {
|
|
return {-x.real, -x.imag};
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr bool operator>=(complex_t<T> a, complex_t<T> b) {
|
|
return (a.real > b.real) || (a.real == b.real && a.imag >= b.imag);
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr bool operator>(complex_t<T> a, complex_t<T> b) {
|
|
return (a.real > b.real) || (a.real == b.real && a.imag > b.imag);
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr bool operator<=(complex_t<T> a, complex_t<T> b) {
|
|
return operator>=(b, a);
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr bool operator<(complex_t<T> a, complex_t<T> b) {
|
|
return operator>(b, a);
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr bool operator==(complex_t<T> a, complex_t<T> b) {
|
|
return a.real == b.real && a.imag == b.imag;
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr complex_t<T> operator+(complex_t<T> a, complex_t<T> b) {
|
|
return {a.real + b.real, a.imag + b.imag};
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr thread complex_t<T>& operator+=(
|
|
thread complex_t<T>& a,
|
|
complex_t<T> b) {
|
|
a.real += b.real;
|
|
a.imag += b.imag;
|
|
return a;
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr threadgroup complex_t<T>& operator+=(
|
|
threadgroup complex_t<T>& a,
|
|
complex_t<T> b) {
|
|
a.real += b.real;
|
|
a.imag += b.imag;
|
|
return a;
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr device complex_t<T>& operator+=(
|
|
device complex_t<T>& a,
|
|
complex_t<T> b) {
|
|
a.real += b.real;
|
|
a.imag += b.imag;
|
|
return a;
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename U,
|
|
enable_if_t<!is_complex_v<U> && is_lane_convertible_v<U, T>, bool> = true>
|
|
constexpr complex_t<T> operator+(U a, complex_t<T> b) {
|
|
return {static_cast<T>(a) + b.real, b.imag};
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename U,
|
|
enable_if_t<!is_complex_v<U> && is_lane_convertible_v<U, T>, bool> = true>
|
|
constexpr complex_t<T> operator+(complex_t<T> a, U b) {
|
|
return {a.real + static_cast<T>(b), a.imag};
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr complex_t<T> operator-(complex_t<T> a, complex_t<T> b) {
|
|
return {a.real - b.real, a.imag - b.imag};
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename U,
|
|
enable_if_t<!is_complex_v<U> && is_lane_convertible_v<U, T>, bool> = true>
|
|
constexpr complex_t<T> operator-(U a, complex_t<T> b) {
|
|
return {static_cast<T>(a) - b.real, -b.imag};
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename U,
|
|
enable_if_t<!is_complex_v<U> && is_lane_convertible_v<U, T>, bool> = true>
|
|
constexpr complex_t<T> operator-(complex_t<T> a, U b) {
|
|
return {a.real - static_cast<T>(b), a.imag};
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr complex_t<T> operator*(complex_t<T> a, complex_t<T> b) {
|
|
return {a.real * b.real - a.imag * b.imag, a.real * b.imag + a.imag * b.real};
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr complex_t<T> operator/(complex_t<T> a, complex_t<T> b) {
|
|
auto denom = b.real * b.real + b.imag * b.imag;
|
|
auto x = a.real * b.real + a.imag * b.imag;
|
|
auto y = a.imag * b.real - a.real * b.imag;
|
|
return {x / denom, y / denom};
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename U,
|
|
enable_if_t<!is_complex_v<U> && is_lane_convertible_v<U, T>, bool> = true>
|
|
constexpr complex_t<T> operator/(U a, complex_t<T> b) {
|
|
auto scalar = static_cast<T>(a);
|
|
auto denom = b.real * b.real + b.imag * b.imag;
|
|
auto x = scalar * b.real;
|
|
auto y = -scalar * b.imag;
|
|
return {x / denom, y / denom};
|
|
}
|
|
|
|
template <typename T>
|
|
constexpr complex_t<T> operator%(complex_t<T> a, complex_t<T> b) {
|
|
auto real = a.real - (b.real * static_cast<int64_t>(a.real / b.real));
|
|
auto imag = a.imag - (b.imag * static_cast<int64_t>(a.imag / b.imag));
|
|
if (real != 0 && (real < 0 != b.real < 0)) {
|
|
real += b.real;
|
|
}
|
|
if (imag != 0 && (imag < 0 != b.imag < 0)) {
|
|
imag += b.imag;
|
|
}
|
|
return {real, imag};
|
|
}
|
|
|
|
static_assert(
|
|
(complex_t<half>{1.0h, 2.0h} * complex_t<half>{3.0h, 4.0h}).real == -5.0h);
|
|
static_assert(
|
|
(complex_t<bfloat16_t>{bfloat16_t(1.0f), bfloat16_t(2.0f)} *
|
|
complex_t<bfloat16_t>{bfloat16_t(3.0f), bfloat16_t(4.0f)})
|
|
.real == bfloat16_t(-5.0f));
|
|
// Copyright © 2023 Apple Inc.
|
|
|
|
|
|
#if defined __METAL__ || defined MLX_METAL_JIT
|
|
#define MTL_CONST constant
|
|
#else
|
|
#define MTL_CONST
|
|
#endif
|
|
|
|
static MTL_CONST constexpr int MAX_REDUCE_SPECIALIZED_DIMS = 4;
|
|
static MTL_CONST constexpr int REDUCE_N_READS = 4;
|
|
static MTL_CONST constexpr int REDUCE_N_WRITES = 4;
|
|
static MTL_CONST constexpr int SOFTMAX_N_READS = 4;
|
|
static MTL_CONST constexpr int RMS_N_READS = 4;
|
|
static MTL_CONST constexpr int RMS_LOOPED_LIMIT = 4096;
|
|
|
|
// Instantiate a templated kernel.
|
|
// Extra args are used as template parameters:
|
|
// e.g. instantiate_kernel(binary_int, binary, a, b) ->
|
|
// [[host_name(binary_int)]] [kernel] binary<a, b>
|
|
#define instantiate_kernel(name, func, ...) \
|
|
template [[host_name( \
|
|
name)]] [[kernel]] decltype(func<__VA_ARGS__>) func<__VA_ARGS__>;
|
|
// Copyright © 2025 Apple Inc.
|
|
|
|
|
|
#if defined(__METAL_VERSION__) && (__METAL_VERSION__ >= 320)
|
|
#include <metal_logging>
|
|
|
|
namespace mlx {
|
|
using os_log = metal::os_log;
|
|
} // namespace mlx
|
|
|
|
#else
|
|
|
|
namespace mlx {
|
|
struct os_log {
|
|
constexpr os_log(constant char*, constant char*) constant {}
|
|
|
|
template <typename... Args>
|
|
void log_debug(constant char*, Args...) const thread {}
|
|
|
|
template <typename... Args>
|
|
void log_debug(constant char*, Args...) const constant {}
|
|
};
|
|
} // namespace mlx
|
|
|
|
#endif
|
|
|
|
typedef half float16_t;
|
|
|
|
// Work per thread values for different types. The values here are expected to
|
|
// match get_work_per_thread in mlx/backend/metal/utils.h
|
|
template <typename U>
|
|
struct WorkPerThread {
|
|
static_assert(sizeof(U) <= 8, "Type too large");
|
|
static constexpr int constant n = 8 / sizeof(U);
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Type limits utils
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template <typename U>
|
|
struct Limits {
|
|
static const constant U max = metal::numeric_limits<U>::max();
|
|
static const constant U min = metal::numeric_limits<U>::min();
|
|
static const constant U finite_max = metal::numeric_limits<U>::max();
|
|
static const constant U finite_min = metal::numeric_limits<U>::min();
|
|
};
|
|
|
|
#define instantiate_default_limit(type) \
|
|
template <> \
|
|
struct Limits<type> { \
|
|
static constexpr constant type max = metal::numeric_limits<type>::max(); \
|
|
static constexpr constant type min = metal::numeric_limits<type>::min(); \
|
|
static constexpr constant type finite_max = \
|
|
metal::numeric_limits<type>::max(); \
|
|
static constexpr constant type finite_min = \
|
|
metal::numeric_limits<type>::min(); \
|
|
};
|
|
|
|
instantiate_default_limit(uint8_t);
|
|
instantiate_default_limit(uint16_t);
|
|
instantiate_default_limit(uint32_t);
|
|
instantiate_default_limit(uint64_t);
|
|
instantiate_default_limit(int8_t);
|
|
instantiate_default_limit(int16_t);
|
|
instantiate_default_limit(int32_t);
|
|
instantiate_default_limit(int64_t);
|
|
|
|
#define instantiate_float_limit(type) \
|
|
template <> \
|
|
struct Limits<type> { \
|
|
static constexpr constant type max = \
|
|
metal::numeric_limits<type>::infinity(); \
|
|
static constexpr constant type min = \
|
|
-metal::numeric_limits<type>::infinity(); \
|
|
static constexpr constant type finite_max = \
|
|
metal::numeric_limits<type>::max(); \
|
|
static constexpr constant type finite_min = \
|
|
-metal::numeric_limits<type>::max(); \
|
|
};
|
|
|
|
instantiate_float_limit(half);
|
|
instantiate_float_limit(float);
|
|
instantiate_float_limit(bfloat16_t);
|
|
|
|
template <>
|
|
struct Limits<bool> {
|
|
static constexpr constant bool max = true;
|
|
static constexpr constant bool min = false;
|
|
};
|
|
|
|
template <typename T>
|
|
struct Limits<complex_t<T>> {
|
|
inline static constexpr constant complex_t<T> max = complex_t<T>(
|
|
metal::numeric_limits<T>::infinity(),
|
|
metal::numeric_limits<T>::infinity());
|
|
inline static constexpr constant complex_t<T> min = complex_t<T>(
|
|
-metal::numeric_limits<T>::infinity(),
|
|
-metal::numeric_limits<T>::infinity());
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Indexing utils
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define MLX_MTL_PRAGMA_UNROLL _Pragma("clang loop unroll(full)")
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Single Array with generic dims
|
|
|
|
template <typename IdxT = int64_t>
|
|
METAL_FUNC IdxT elem_to_loc(
|
|
IdxT elem,
|
|
constant const int* shape,
|
|
constant const int64_t* strides,
|
|
int ndim) {
|
|
IdxT loc = 0;
|
|
for (int i = ndim - 1; i >= 0 && elem > 0; --i) {
|
|
loc += (elem % shape[i]) * IdxT(strides[i]);
|
|
elem /= shape[i];
|
|
}
|
|
return loc;
|
|
}
|
|
|
|
// Non templated version to handle arbitrary dims
|
|
template <typename IdxT = int64_t>
|
|
METAL_FUNC IdxT elem_to_loc(
|
|
uint3 elem,
|
|
constant const int* shape,
|
|
constant const int64_t* strides,
|
|
int ndim) {
|
|
IdxT loc =
|
|
elem.x * IdxT(strides[ndim - 1]) + elem.y * IdxT(strides[ndim - 2]);
|
|
for (int d = ndim - 3; d >= 0; --d) {
|
|
loc += (elem.z % shape[d]) * IdxT(strides[d]);
|
|
elem.z /= shape[d];
|
|
}
|
|
return loc;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Single Array with fixed N dims
|
|
|
|
template <typename IdxT = int64_t>
|
|
METAL_FUNC IdxT elem_to_loc_1(uint elem, constant const int64_t& stride) {
|
|
return elem * IdxT(stride);
|
|
}
|
|
|
|
template <typename IdxT = int64_t>
|
|
METAL_FUNC IdxT elem_to_loc_2(uint2 elem, constant const int64_t strides[2]) {
|
|
return elem.x * IdxT(strides[1]) + elem.y * IdxT(strides[0]);
|
|
}
|
|
|
|
template <typename IdxT = int64_t>
|
|
METAL_FUNC IdxT elem_to_loc_3(uint3 elem, constant const int64_t strides[3]) {
|
|
return elem.x * IdxT(strides[2]) + elem.y * IdxT(strides[1]) +
|
|
elem.z * IdxT(strides[0]);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Multiple Arrays with generic dims
|
|
|
|
template <typename IdxT = int64_t>
|
|
METAL_FUNC vec<IdxT, 2> elem_to_loc_2_nd(
|
|
uint3 elem,
|
|
constant const int* shape,
|
|
constant const int64_t* a_strides,
|
|
constant const int64_t* b_strides,
|
|
int ndim) {
|
|
vec<IdxT, 2> loc = {
|
|
IdxT(
|
|
elem.x * IdxT(a_strides[ndim - 1]) +
|
|
IdxT(elem.y) * IdxT(a_strides[ndim - 2])),
|
|
IdxT(
|
|
elem.x * IdxT(b_strides[ndim - 1]) +
|
|
elem.y * IdxT(b_strides[ndim - 2]))};
|
|
for (int d = ndim - 3; d >= 0; --d) {
|
|
uint l = elem.z % shape[d];
|
|
loc.x += l * IdxT(a_strides[d]);
|
|
loc.y += l * IdxT(b_strides[d]);
|
|
elem.z /= shape[d];
|
|
}
|
|
return loc;
|
|
}
|
|
|
|
template <typename IdxT = int64_t>
|
|
METAL_FUNC vec<IdxT, 3> elem_to_loc_3_nd(
|
|
uint3 elem,
|
|
constant const int* shape,
|
|
constant const int64_t* a_strides,
|
|
constant const int64_t* b_strides,
|
|
constant const int64_t* c_strides,
|
|
int ndim) {
|
|
vec<IdxT, 3> loc = {
|
|
IdxT(elem.x * IdxT(a_strides[ndim - 1])) +
|
|
IdxT(elem.y * IdxT(a_strides[ndim - 2])),
|
|
IdxT(elem.x * IdxT(b_strides[ndim - 1])) +
|
|
IdxT(elem.y * IdxT(b_strides[ndim - 2])),
|
|
IdxT(elem.x * IdxT(c_strides[ndim - 1])) +
|
|
IdxT(elem.y * IdxT(c_strides[ndim - 2]))};
|
|
for (int d = ndim - 3; d >= 0; --d) {
|
|
uint l = elem.z % shape[d];
|
|
loc.x += l * IdxT(a_strides[d]);
|
|
loc.y += l * IdxT(b_strides[d]);
|
|
loc.z += l * IdxT(c_strides[d]);
|
|
elem.z /= shape[d];
|
|
}
|
|
return loc;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Elem to loc in a loop utils
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template <int DIM, typename OffsetT = size_t, bool General = true>
|
|
struct LoopedElemToLoc {
|
|
int dim;
|
|
LoopedElemToLoc<DIM - 1, OffsetT, General> inner_looper;
|
|
OffsetT offset{0};
|
|
int index{0};
|
|
|
|
LoopedElemToLoc(int dim) thread : dim(dim), inner_looper(dim - 1) {}
|
|
|
|
void next(const constant int* shape, const constant int64_t* strides) thread {
|
|
if (dim == 0) {
|
|
return;
|
|
}
|
|
index++;
|
|
offset += OffsetT(strides[dim - 1]);
|
|
if (index >= shape[dim - 1]) {
|
|
index = 0;
|
|
inner_looper.next(shape, strides);
|
|
offset = inner_looper.offset;
|
|
}
|
|
}
|
|
|
|
void next(int n, const constant int* shape, const constant int64_t* strides)
|
|
thread {
|
|
if (dim == 0) {
|
|
return;
|
|
}
|
|
index += n;
|
|
offset += n * OffsetT(strides[dim - 1]);
|
|
|
|
if (index >= shape[dim - 1]) {
|
|
int extra = index - shape[dim - 1];
|
|
if (extra >= shape[dim - 1]) {
|
|
inner_looper.next(1 + extra / shape[dim - 1], shape, strides);
|
|
extra = extra % shape[dim - 1];
|
|
} else {
|
|
inner_looper.next(shape, strides);
|
|
}
|
|
index = 0;
|
|
offset = inner_looper.offset;
|
|
if (extra > 0) {
|
|
next(extra, shape, strides);
|
|
}
|
|
}
|
|
}
|
|
|
|
OffsetT location() thread {
|
|
return offset;
|
|
}
|
|
};
|
|
|
|
template <typename OffsetT>
|
|
struct LoopedElemToLoc<1, OffsetT, true> {
|
|
int dim;
|
|
OffsetT offset{0};
|
|
uint index{0};
|
|
|
|
LoopedElemToLoc(int dim) thread : dim(dim) {}
|
|
|
|
void next(const constant int* shape, const constant int64_t* strides) thread {
|
|
index++;
|
|
if (dim > 1) {
|
|
offset = elem_to_loc<OffsetT>(index, shape, strides, dim);
|
|
} else {
|
|
offset += OffsetT(strides[0]);
|
|
}
|
|
}
|
|
|
|
void next(int n, const constant int* shape, const constant int64_t* strides)
|
|
thread {
|
|
index += n;
|
|
if (dim > 1) {
|
|
offset = elem_to_loc<OffsetT>(index, shape, strides, dim);
|
|
} else {
|
|
offset = index * OffsetT(strides[0]);
|
|
}
|
|
}
|
|
|
|
OffsetT location() thread {
|
|
return offset;
|
|
}
|
|
};
|
|
|
|
template <typename OffsetT>
|
|
struct LoopedElemToLoc<1, OffsetT, false> {
|
|
OffsetT offset{0};
|
|
|
|
LoopedElemToLoc(int) thread {}
|
|
|
|
void next(const constant int*, const constant int64_t* strides) thread {
|
|
offset += OffsetT(strides[0]);
|
|
}
|
|
|
|
void next(int n, const constant int*, const constant int64_t* strides)
|
|
thread {
|
|
offset += n * OffsetT(strides[0]);
|
|
}
|
|
|
|
OffsetT location() thread {
|
|
return offset;
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Calculation utils
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/** Compute ceil((float)N/(float)M) */
|
|
template <typename T, typename U>
|
|
inline T ceildiv(T N, U M) {
|
|
return (N + M - 1) / M;
|
|
}
|
|
|
|
// https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1202
|
|
inline float log1p(float x) {
|
|
float xp1 = 1.0f + x;
|
|
if (xp1 == Limits<float>::max) {
|
|
return Limits<float>::max;
|
|
}
|
|
if (xp1 == 1.0f) {
|
|
return x;
|
|
}
|
|
|
|
return x * (metal::log(xp1) / (xp1 - 1.0f));
|
|
}
|
|
|
|
inline bfloat16_t log1p(bfloat16_t x) {
|
|
float xp1 = 1.0f + static_cast<float>(x);
|
|
if (xp1 == Limits<float>::max) {
|
|
return Limits<bfloat16_t>::max;
|
|
}
|
|
if (xp1 == 1.0f) {
|
|
return x;
|
|
}
|
|
|
|
return bfloat16_t(x * (metal::log(xp1) / (xp1 - 1.0f)));
|
|
}
|
|
|
|
inline complex64_t log1p(complex64_t in) {
|
|
float x = in.real;
|
|
float y = in.imag;
|
|
float zabs = metal::precise::sqrt(x * x + y * y);
|
|
float theta = metal::atan2(y, x + 1);
|
|
if (zabs < 0.5f) {
|
|
float r = x * (2 + x) + y * y;
|
|
if (r == 0) { // handle underflow
|
|
return {x, theta};
|
|
}
|
|
return {0.5f * log1p(r), theta};
|
|
} else {
|
|
auto z0 = metal::sqrt((x + 1) * (x + 1) + y * y);
|
|
return {metal::log(z0), theta};
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// SIMD shuffle ops
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
inline uint64_t simd_shuffle_down(uint64_t data, uint16_t delta) {
|
|
return as_type<uint64_t>(
|
|
metal::simd_shuffle_down(as_type<uint2>(data), delta));
|
|
}
|
|
|
|
inline int64_t simd_shuffle_down(int64_t data, uint16_t delta) {
|
|
return as_type<int64_t>(
|
|
metal::simd_shuffle_down(as_type<uint2>(data), delta));
|
|
}
|
|
|
|
inline bool simd_shuffle_down(bool data, uint16_t delta) {
|
|
return simd_shuffle_down(static_cast<uint32_t>(data), delta);
|
|
}
|
|
|
|
inline complex64_t simd_shuffle_down(complex64_t data, uint16_t delta) {
|
|
return complex64_t(
|
|
simd_shuffle_down(data.real, delta), simd_shuffle_down(data.imag, delta));
|
|
}
|
|
|
|
inline uint64_t simd_shuffle_up(uint64_t data, uint16_t delta) {
|
|
return as_type<uint64_t>(metal::simd_shuffle_up(as_type<uint2>(data), delta));
|
|
}
|
|
|
|
inline int64_t simd_shuffle_up(int64_t data, uint16_t delta) {
|
|
return as_type<int64_t>(metal::simd_shuffle_up(as_type<uint2>(data), delta));
|
|
}
|
|
|
|
inline bool simd_shuffle_up(bool data, uint16_t delta) {
|
|
return simd_shuffle_up(static_cast<uint32_t>(data), delta);
|
|
}
|
|
|
|
inline complex64_t simd_shuffle_up(complex64_t data, uint16_t delta) {
|
|
return complex64_t(
|
|
simd_shuffle_up(data.real, delta), simd_shuffle_up(data.imag, delta));
|
|
}
|
|
|
|
inline uint64_t
|
|
simd_shuffle_and_fill_up(uint64_t data, uint64_t filling, uint16_t delta) {
|
|
return as_type<uint64_t>(metal::simd_shuffle_and_fill_up(
|
|
as_type<uint2>(data), as_type<uint2>(filling), delta));
|
|
}
|
|
|
|
inline int64_t
|
|
simd_shuffle_and_fill_up(int64_t data, int64_t filling, uint16_t delta) {
|
|
return as_type<int64_t>(metal::simd_shuffle_and_fill_up(
|
|
as_type<uint2>(data), as_type<uint2>(filling), delta));
|
|
}
|
|
|
|
inline bool simd_shuffle_and_fill_up(bool data, bool filling, uint16_t delta) {
|
|
return simd_shuffle_and_fill_up(
|
|
static_cast<uint32_t>(data), static_cast<uint32_t>(filling), delta);
|
|
}
|
|
|
|
inline complex64_t simd_shuffle_and_fill_up(
|
|
complex64_t data,
|
|
complex64_t filling,
|
|
uint16_t delta) {
|
|
return complex64_t(
|
|
simd_shuffle_and_fill_up(data.real, filling.real, delta),
|
|
simd_shuffle_and_fill_up(data.imag, filling.imag, delta));
|
|
}
|
|
|
|
inline uint64_t simd_shuffle(uint64_t data, uint16_t lane) {
|
|
return as_type<uint64_t>(metal::simd_shuffle(as_type<uint2>(data), lane));
|
|
}
|
|
|
|
inline int64_t simd_shuffle(int64_t data, uint16_t lane) {
|
|
return as_type<int64_t>(metal::simd_shuffle(as_type<uint2>(data), lane));
|
|
}
|
|
|
|
inline bool simd_shuffle(bool data, uint16_t lane) {
|
|
return simd_shuffle(static_cast<uint32_t>(data), lane);
|
|
}
|
|
|
|
inline complex64_t simd_shuffle(complex64_t data, uint16_t lane) {
|
|
return complex64_t(
|
|
simd_shuffle(data.real, lane), simd_shuffle(data.imag, lane));
|
|
}
|
|
|
|
// std::conditional is not included with Metal
|
|
template <bool condition, typename T, typename U>
|
|
struct ConditionalType {
|
|
using type = U;
|
|
};
|
|
|
|
template <typename T, typename U>
|
|
struct ConditionalType<true, T, U> {
|
|
using type = T;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Type casting utils
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template <typename U, typename T>
|
|
inline U cast_to(T val) {
|
|
return static_cast<U>(val);
|
|
}
|
|
|
|
template <>
|
|
inline bool cast_to<bool, float>(float val) {
|
|
return (as_type<uint32_t>(val) & 0x7FFFFFFF) != 0;
|
|
}
|
|
|
|
template <>
|
|
inline bool cast_to<bool, bfloat16_t>(bfloat16_t val) {
|
|
return (as_type<uint16_t>(val) & 0x7FFF) != 0;
|
|
}
|
|
|
|
template <>
|
|
inline bool cast_to<bool, complex64_t>(complex64_t val) {
|
|
return cast_to<bool, float>(val.real) || cast_to<bool, float>(val.imag);
|
|
}
|
|
// Copyright © 2025 Apple Inc.
|
|
|
|
|
|
// Copyright © 2025 Apple Inc.
|
|
|
|
|
|
#include <metal_simdgroup>
|
|
#include <metal_simdgroup_matrix>
|
|
#include <metal_stdlib>
|
|
|
|
// Copyright © 2024 Apple Inc.
|
|
|
|
|
|
#define STEEL_CONST static constant constexpr const
|
|
#define STEEL_PRAGMA_UNROLL _Pragma("clang loop unroll(full)")
|
|
#define STEEL_PRAGMA_NO_UNROLL _Pragma("clang loop unroll(disable)")
|
|
// Copyright © 2024 Apple Inc.
|
|
|
|
|
|
#include <metal_stdlib>
|
|
// Copyright © 2024 Apple Inc.
|
|
|
|
|
|
#include <metal_stdlib>
|
|
|
|
#pragma METAL internals : enable
|
|
|
|
namespace metal {
|
|
|
|
template <typename T>
|
|
struct is_empty : metal::bool_constant<__is_empty(T)> {};
|
|
|
|
#ifdef __cpp_variable_templates
|
|
template <typename T>
|
|
constexpr constant bool is_empty_v = is_empty<T>::value;
|
|
#endif
|
|
|
|
template <typename... Ts>
|
|
struct make_void {
|
|
typedef void type;
|
|
};
|
|
|
|
template <typename... Ts>
|
|
using void_t = typename make_void<Ts...>::type;
|
|
|
|
template <class T>
|
|
struct is_static : metal::bool_constant<is_empty<remove_cv_t<T>>::value> {};
|
|
|
|
template <typename T>
|
|
struct pointer_element {};
|
|
|
|
template <typename T>
|
|
struct pointer_element<thread T*> {
|
|
using type = remove_cv_t<T>;
|
|
};
|
|
template <typename T>
|
|
struct pointer_element<device T*> {
|
|
using type = remove_cv_t<T>;
|
|
};
|
|
template <typename T>
|
|
struct pointer_element<constant T*> {
|
|
using type = remove_cv_t<T>;
|
|
};
|
|
template <typename T>
|
|
struct pointer_element<threadgroup T*> {
|
|
using type = remove_cv_t<T>;
|
|
};
|
|
|
|
template <typename T>
|
|
using pointer_element_t = typename pointer_element<remove_cv_t<T>>::type;
|
|
|
|
} // namespace metal
|
|
|
|
#pragma METAL internals : disable
|
|
|
|
#pragma METAL internals : enable
|
|
|
|
namespace mlx {
|
|
namespace steel {
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Integral constant with casting
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template <typename T, T v>
|
|
struct integral_constant {
|
|
static constexpr constant T value = v;
|
|
using value_type = T;
|
|
using type = integral_constant;
|
|
|
|
METAL_FUNC constexpr operator value_type() const thread noexcept {
|
|
return value;
|
|
}
|
|
};
|
|
|
|
template <bool B>
|
|
using bool_constant = integral_constant<bool, B>;
|
|
using true_type = bool_constant<true>;
|
|
using false_type = bool_constant<false>;
|
|
|
|
template <class T>
|
|
struct is_integral : bool_constant<metal::is_integral<T>::value> {};
|
|
|
|
template <class T, T v>
|
|
struct is_integral<integral_constant<T, v>>
|
|
: bool_constant<metal::is_integral<T>::value> {};
|
|
|
|
template <typename T>
|
|
constexpr constant bool is_integral_v = is_integral<T>::value;
|
|
|
|
template <int val>
|
|
using Int = integral_constant<int, val>;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Binary Operators on Integral constants
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#define integral_const_binop(__op__, __operator__) \
|
|
template <typename T, T tv, typename U, U uv> \
|
|
METAL_FUNC constexpr auto __operator__( \
|
|
integral_constant<T, tv>, integral_constant<U, uv>) { \
|
|
constexpr auto res = tv __op__ uv; \
|
|
using res_t = metal::remove_addrspace_t<decltype(res)>; \
|
|
return integral_constant<res_t, res>{}; \
|
|
}
|
|
|
|
integral_const_binop(+, operator+);
|
|
integral_const_binop(-, operator-);
|
|
integral_const_binop(*, operator*);
|
|
integral_const_binop(/, operator/);
|
|
|
|
integral_const_binop(==, operator==);
|
|
integral_const_binop(!=, operator!=);
|
|
integral_const_binop(<, operator<);
|
|
integral_const_binop(>, operator>);
|
|
integral_const_binop(<=, operator<=);
|
|
integral_const_binop(>=, operator>=);
|
|
|
|
integral_const_binop(&&, operator&&);
|
|
integral_const_binop(||, operator||);
|
|
|
|
template <typename T, typename = metal::enable_if_t<!is_integral_v<T>>>
|
|
METAL_FUNC constexpr auto operator||(true_type, T) {
|
|
return true_type{};
|
|
}
|
|
template <typename T, typename = metal::enable_if_t<!is_integral_v<T>>>
|
|
METAL_FUNC constexpr auto operator||(T, true_type) {
|
|
return true_type{};
|
|
}
|
|
|
|
template <typename T, typename = metal::enable_if_t<!is_integral_v<T>>>
|
|
METAL_FUNC constexpr auto operator&&(false_type, T) {
|
|
return false_type{};
|
|
}
|
|
|
|
template <typename T, typename = metal::enable_if_t<!is_integral_v<T>>>
|
|
METAL_FUNC constexpr auto operator&&(T, false_type) {
|
|
return false_type{};
|
|
}
|
|
|
|
// Dispatch utilities
|
|
template <typename F>
|
|
void dispatch_bool(bool v, F f) {
|
|
if (v) {
|
|
f(true_type{});
|
|
} else {
|
|
f(false_type{});
|
|
}
|
|
}
|
|
|
|
template <int start, int stop, int step, typename F>
|
|
constexpr void const_for_loop(F f) {
|
|
if constexpr (start < stop) {
|
|
constexpr auto idx = Int<start>{};
|
|
f(idx);
|
|
const_for_loop<start + step, stop, step, F>(f);
|
|
}
|
|
}
|
|
|
|
#undef integral_const_binop
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Reduction operators
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template <typename T>
|
|
METAL_FUNC constexpr T sum(T x) {
|
|
return x;
|
|
}
|
|
|
|
template <typename T, typename... Us>
|
|
METAL_FUNC constexpr auto sum(T x, Us... us) {
|
|
return x + sum(us...);
|
|
}
|
|
|
|
} // namespace steel
|
|
} // namespace mlx
|
|
|
|
#pragma METAL internals : disable
|
|
|
|
#include <MetalPerformancePrimitives/MetalPerformancePrimitives.h>
|
|
|
|
using namespace metal;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// MMA helper
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace mlx {
|
|
namespace steel {
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// NAX Steel with new tiles
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct BaseNAXFrag {
|
|
STEEL_CONST short kFragRows = 16;
|
|
STEEL_CONST short kFragCols = 16;
|
|
|
|
STEEL_CONST short kElemsPerFrag = (kFragRows * kFragCols) / 32;
|
|
|
|
STEEL_CONST short kElemRows = 2;
|
|
STEEL_CONST short kElemCols = 4;
|
|
|
|
STEEL_CONST short kElemRowsJump = 8;
|
|
|
|
static_assert(
|
|
kElemRows * kElemCols == kElemsPerFrag,
|
|
"MMAFrag shape is not consistent with MMAFrag size");
|
|
|
|
template <typename U>
|
|
using dtype_frag_t = typename metal::vec<U, kElemsPerFrag>;
|
|
|
|
METAL_FUNC static short2 get_coord() {
|
|
const ushort simd_lane_id = __metal_get_thread_index_in_simdgroup(ushort());
|
|
const short qid = simd_lane_id >> 2;
|
|
const short fm = ((qid & 4) | ((simd_lane_id >> 1) & 3));
|
|
const short fn = ((qid & 2) | (simd_lane_id & 1)) * 4;
|
|
return short2{fn, fm};
|
|
}
|
|
|
|
METAL_FUNC static short2 get_coord(short idx) {
|
|
const ushort simd_lane_id = __metal_get_thread_index_in_simdgroup(ushort());
|
|
const short qid = simd_lane_id >> 2;
|
|
const short fm = ((qid & 4) | ((simd_lane_id >> 1) & 3)) + (idx >> 2) * 8;
|
|
const short fn = ((qid & 2) | (simd_lane_id & 1)) * 4 + idx % 4;
|
|
return short2{fn, fm};
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename SrcPtrType,
|
|
typename StrX,
|
|
typename StrY,
|
|
typename OffX = Int<0>,
|
|
typename OffY = Int<0>>
|
|
METAL_FUNC static constexpr void load(
|
|
thread dtype_frag_t<T>& dst,
|
|
SrcPtrType src,
|
|
StrX str_x,
|
|
StrY str_y,
|
|
OffX off_x = {},
|
|
OffY off_y = {}) {
|
|
const short2 sc = get_coord();
|
|
src += sc.y * str_x + sc.x * str_y;
|
|
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemRows; i++) {
|
|
const auto r = off_x + i * kElemRowsJump;
|
|
const auto c = off_y;
|
|
|
|
if constexpr (metal::is_same_v<StrY, Int<1>>) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
dst[i * kElemCols + j] = static_cast<T>(src[r * str_x + c + j]);
|
|
}
|
|
} else {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
dst[i * kElemCols + j] =
|
|
static_cast<T>(src[r * str_x + (c + j) * str_y]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename SrcPtrType,
|
|
typename StrX,
|
|
typename StrY,
|
|
typename LimX,
|
|
typename OffX = Int<0>,
|
|
typename OffY = Int<0>>
|
|
METAL_FUNC static constexpr void load_rows(
|
|
thread dtype_frag_t<T>& dst,
|
|
SrcPtrType src,
|
|
StrX str_x,
|
|
StrY str_y,
|
|
LimX lim_x,
|
|
OffX off_x = {},
|
|
OffY off_y = {}) {
|
|
const short2 sc = get_coord();
|
|
src += sc.y * str_x + sc.x * str_y;
|
|
auto lx = lim_x - sc.y;
|
|
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemRows; i++) {
|
|
const auto r = off_x + i * kElemRowsJump;
|
|
const auto c = off_y;
|
|
|
|
if (r < lx) {
|
|
if constexpr (metal::is_same_v<StrY, Int<1>>) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
dst[i * kElemCols + j] = static_cast<T>(src[r * str_x + (c + j)]);
|
|
}
|
|
} else {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
dst[i * kElemCols + j] =
|
|
static_cast<T>(src[r * str_x + (c + j) * str_y]);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
dst[i * kElemCols + j] = T(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename SrcPtrType,
|
|
typename StrX,
|
|
typename StrY,
|
|
typename LimX,
|
|
typename LimY,
|
|
typename OffX = Int<0>,
|
|
typename OffY = Int<0>>
|
|
METAL_FUNC static constexpr void load_safe(
|
|
thread dtype_frag_t<T>& dst,
|
|
SrcPtrType src,
|
|
StrX str_x,
|
|
StrY str_y,
|
|
LimX lim_x,
|
|
LimY lim_y,
|
|
OffX off_x = {},
|
|
OffY off_y = {}) {
|
|
const short2 sc = get_coord();
|
|
src += sc.y * str_x + sc.x * str_y;
|
|
auto lx = lim_x - sc.y;
|
|
auto ly = lim_y - sc.x;
|
|
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemRows; i++) {
|
|
const auto r = off_x + i * kElemRowsJump;
|
|
const auto c = off_y;
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
if ((r < lx) && ((c + j) < ly)) {
|
|
dst[i * kElemCols + j] =
|
|
static_cast<T>(src[r * str_x + (c + j) * str_y]);
|
|
} else {
|
|
dst[i * kElemCols + j] = T(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename DstPtrType,
|
|
typename StrX,
|
|
typename StrY,
|
|
typename OffX = Int<0>,
|
|
typename OffY = Int<0>>
|
|
METAL_FUNC static constexpr void store(
|
|
const thread dtype_frag_t<T>& src,
|
|
DstPtrType dst,
|
|
StrX str_x,
|
|
StrY str_y,
|
|
OffX off_x = {},
|
|
OffY off_y = {}) {
|
|
using U = pointer_element_t<DstPtrType>;
|
|
|
|
const short2 sc = get_coord();
|
|
dst += sc.y * str_x + sc.x * str_y;
|
|
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemRows; i++) {
|
|
const auto r = off_x + i * kElemRowsJump;
|
|
const auto c = off_y;
|
|
|
|
if constexpr (metal::is_same_v<StrY, Int<1>>) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
dst[r * str_x + c + j] = static_cast<U>(src[i * kElemCols + j]);
|
|
}
|
|
} else {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
dst[r * str_x + (c + j) * str_y] =
|
|
static_cast<U>(src[i * kElemCols + j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename DstPtrType,
|
|
typename StrX,
|
|
typename StrY,
|
|
typename LimX,
|
|
typename OffX = Int<0>,
|
|
typename OffY = Int<0>>
|
|
METAL_FUNC static constexpr void store_rows(
|
|
const thread dtype_frag_t<T>& src,
|
|
DstPtrType dst,
|
|
StrX str_x,
|
|
StrY str_y,
|
|
LimX lim_x,
|
|
OffX off_x = {},
|
|
OffY off_y = {}) {
|
|
using U = pointer_element_t<DstPtrType>;
|
|
|
|
const short2 sc = get_coord();
|
|
dst += sc.y * str_x + sc.x * str_y;
|
|
auto lx = lim_x - sc.y;
|
|
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemRows; i++) {
|
|
const auto r = off_x + i * kElemRowsJump;
|
|
const auto c = off_y;
|
|
|
|
if (r < lx) {
|
|
if constexpr (metal::is_same_v<StrY, Int<1>>) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
dst[r * str_x + c + j] = static_cast<U>(src[i * kElemCols + j]);
|
|
}
|
|
} else {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
dst[r * str_x + (c + j) * str_y] =
|
|
static_cast<U>(src[i * kElemCols + j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename DstPtrType,
|
|
typename StrX,
|
|
typename StrY,
|
|
typename LimX,
|
|
typename LimY,
|
|
typename OffX = Int<0>,
|
|
typename OffY = Int<0>>
|
|
METAL_FUNC static constexpr void store_safe(
|
|
const thread dtype_frag_t<T>& src,
|
|
DstPtrType dst,
|
|
StrX str_x,
|
|
StrY str_y,
|
|
LimX lim_x,
|
|
LimY lim_y,
|
|
OffX off_x = {},
|
|
OffY off_y = {}) {
|
|
using U = pointer_element_t<DstPtrType>;
|
|
|
|
const short2 sc = get_coord();
|
|
dst += sc.y * str_x + sc.x * str_y;
|
|
auto lx = lim_x - sc.y;
|
|
auto ly = lim_y - sc.x;
|
|
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemRows; i++) {
|
|
const auto r = off_x + i * kElemRowsJump;
|
|
const auto c = off_y;
|
|
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
if (r < lx && (c + j) < ly) {
|
|
dst[r * str_x + (c + j) * str_y] =
|
|
static_cast<U>(src[i * kElemCols + j]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
typename DstPtrType,
|
|
typename StrX,
|
|
typename StrY,
|
|
typename StartX,
|
|
typename StopX,
|
|
typename StartY,
|
|
typename StopY,
|
|
typename OffX = Int<0>,
|
|
typename OffY = Int<0>>
|
|
METAL_FUNC static constexpr void store_slice(
|
|
const thread dtype_frag_t<T>& src,
|
|
DstPtrType dst,
|
|
StrX str_x,
|
|
StrY str_y,
|
|
StartX start_x,
|
|
StopX stop_x,
|
|
StartY start_y,
|
|
StopY stop_y,
|
|
OffX off_x = Int<0>{},
|
|
OffY off_y = Int<0>{}) {
|
|
using U = pointer_element_t<DstPtrType>;
|
|
|
|
const short2 sc = get_coord();
|
|
|
|
const_for_loop<0, kElemRows, 1>([&](auto idx_row) {
|
|
const auto r = off_x + idx_row * Int<kElemRowsJump>{};
|
|
if (r >= stop_x - sc.y || r < start_x - sc.y) {
|
|
return;
|
|
}
|
|
|
|
const_for_loop<0, kElemCols, 1>([&](auto idx_col) {
|
|
const auto c = off_y + idx_col;
|
|
if (c >= stop_y - sc.x || c < start_y - sc.x) {
|
|
return;
|
|
}
|
|
|
|
const auto src_idx = idx_row * Int<kElemCols>{} + idx_col;
|
|
dst[(r + sc.y) * str_x + (c + sc.x) * str_y] =
|
|
static_cast<U>(src[src_idx]);
|
|
});
|
|
});
|
|
}
|
|
|
|
template <typename Op, typename T>
|
|
METAL_FUNC static constexpr void row_reduce(
|
|
thread const dtype_frag_t<T>& inp_vals,
|
|
thread T* reduced_vals) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemRows; i++) {
|
|
T thr_reduce = Op::apply(
|
|
Op::apply(inp_vals[i * kElemCols + 0], inp_vals[i * kElemCols + 1]),
|
|
Op::apply(inp_vals[i * kElemCols + 2], inp_vals[i * kElemCols + 3]));
|
|
|
|
T qgr_reduce = simd_shuffle_xor(thr_reduce, ushort(1));
|
|
qgr_reduce = Op::apply(thr_reduce, qgr_reduce);
|
|
|
|
T sgr_reduce = simd_shuffle_xor(qgr_reduce, ushort(8));
|
|
sgr_reduce = Op::apply(qgr_reduce, sgr_reduce);
|
|
|
|
reduced_vals[i] = Op::apply(reduced_vals[i], sgr_reduce);
|
|
}
|
|
}
|
|
|
|
template <typename Op, typename T>
|
|
METAL_FUNC static constexpr void row_bin_op(
|
|
thread dtype_frag_t<T>& inp_vals,
|
|
thread T* row_vals) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemRows; i++) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kElemCols; j++) {
|
|
inp_vals[i * kElemCols + j] =
|
|
Op::apply(inp_vals[i * kElemCols + j], row_vals[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <
|
|
typename CType,
|
|
typename AType,
|
|
typename BType,
|
|
bool transpose_a = false,
|
|
bool transpose_b = false>
|
|
METAL_FUNC static constexpr void mma(
|
|
thread dtype_frag_t<CType>& Cn0,
|
|
thread dtype_frag_t<CType>& Cn1,
|
|
const thread dtype_frag_t<AType>& A,
|
|
metal::bool_constant<transpose_a>,
|
|
const thread dtype_frag_t<BType>& Bn0,
|
|
const thread dtype_frag_t<BType>& Bn1,
|
|
metal::bool_constant<transpose_b>) {
|
|
constexpr auto desc = mpp::tensor_ops::matmul2d_descriptor(
|
|
16,
|
|
32,
|
|
16,
|
|
transpose_a,
|
|
transpose_b,
|
|
true,
|
|
mpp::tensor_ops::matmul2d_descriptor::mode::multiply_accumulate);
|
|
|
|
// Create matmul op
|
|
mpp::tensor_ops::matmul2d<desc, metal::execution_simdgroup> gemm_op;
|
|
|
|
// Create matmul operands in registers
|
|
auto ct_a =
|
|
gemm_op
|
|
.template get_left_input_cooperative_tensor<AType, BType, CType>();
|
|
auto ct_b =
|
|
gemm_op
|
|
.template get_right_input_cooperative_tensor<AType, BType, CType>();
|
|
|
|
// Create matmul output in register
|
|
auto ct_c = gemm_op.template get_destination_cooperative_tensor<
|
|
metal::remove_addrspace_t<decltype(ct_a)>,
|
|
metal::remove_addrspace_t<decltype(ct_b)>,
|
|
CType>();
|
|
|
|
// Load A in to left operand registers
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemsPerFrag; i++) {
|
|
ct_a[i] = A[i];
|
|
}
|
|
|
|
// Load B into right operand registers
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemsPerFrag; i++) {
|
|
ct_b[i] = Bn0[i];
|
|
ct_b[kElemsPerFrag + i] = Bn1[i];
|
|
}
|
|
|
|
// Load C into output registers (op handles accumulation)
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemsPerFrag; i++) {
|
|
ct_c[i] = Cn0[i];
|
|
ct_c[kElemsPerFrag + i] = Cn1[i];
|
|
}
|
|
|
|
// Do matmul
|
|
gemm_op.run(ct_a, ct_b, ct_c);
|
|
|
|
// Copy out results
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemsPerFrag; i++) {
|
|
Cn0[i] = ct_c[i];
|
|
Cn1[i] = ct_c[kElemsPerFrag + i];
|
|
}
|
|
}
|
|
|
|
template <
|
|
typename CType,
|
|
typename AType,
|
|
typename BType,
|
|
bool transpose_a = false,
|
|
bool transpose_b = false>
|
|
METAL_FUNC static constexpr void mma(
|
|
thread dtype_frag_t<CType>& Cm0,
|
|
thread dtype_frag_t<CType>& Cm1,
|
|
const thread dtype_frag_t<AType>& Am0,
|
|
const thread dtype_frag_t<AType>& Am1,
|
|
metal::bool_constant<transpose_a>,
|
|
const thread dtype_frag_t<BType>& B,
|
|
metal::bool_constant<transpose_b>) {
|
|
// Create Matmul descriptor
|
|
constexpr auto desc = mpp::tensor_ops::matmul2d_descriptor(
|
|
16,
|
|
32,
|
|
16,
|
|
transpose_a,
|
|
transpose_b,
|
|
true,
|
|
mpp::tensor_ops::matmul2d_descriptor::mode::multiply_accumulate);
|
|
|
|
// Create matmul op
|
|
mpp::tensor_ops::matmul2d<desc, metal::execution_simdgroup> gemm_op;
|
|
|
|
// Create matmul operands in registers
|
|
auto ct_a =
|
|
gemm_op
|
|
.template get_left_input_cooperative_tensor<AType, BType, CType>();
|
|
auto ct_b =
|
|
gemm_op
|
|
.template get_right_input_cooperative_tensor<AType, BType, CType>();
|
|
|
|
// Create matmul output in register
|
|
auto ct_c = gemm_op.template get_destination_cooperative_tensor<
|
|
metal::remove_addrspace_t<decltype(ct_a)>,
|
|
metal::remove_addrspace_t<decltype(ct_b)>,
|
|
CType>();
|
|
|
|
// Load A in to left operand registers
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemsPerFrag; i++) {
|
|
ct_a[i] = Am0[i];
|
|
ct_a[kElemsPerFrag + i] = Am1[i];
|
|
}
|
|
|
|
// Load B into right operand registers
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemsPerFrag; i++) {
|
|
ct_b[i] = B[i];
|
|
}
|
|
|
|
// Load C into output registers (op handles accumulation)
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemsPerFrag; i++) {
|
|
ct_c[i] = Cm0[i];
|
|
ct_c[kElemsPerFrag + i] = Cm1[i];
|
|
}
|
|
|
|
// Do matmul
|
|
gemm_op.run(ct_a, ct_b, ct_c);
|
|
|
|
// Copy out results
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kElemsPerFrag; i++) {
|
|
Cm0[i] = ct_c[i];
|
|
Cm1[i] = ct_c[kElemsPerFrag + i];
|
|
}
|
|
}
|
|
};
|
|
|
|
template <
|
|
typename T,
|
|
short kTileRows_,
|
|
short kTileCols_,
|
|
class NAXFrag_ = BaseNAXFrag>
|
|
struct NAXTile {
|
|
using NAXFrag_t = NAXFrag_;
|
|
using elem_type = T;
|
|
|
|
STEEL_CONST short kFragRows = NAXFrag_t::kFragRows;
|
|
STEEL_CONST short kFragCols = NAXFrag_t::kFragCols;
|
|
STEEL_CONST short kElemsPerFrag = NAXFrag_t::kElemsPerFrag;
|
|
|
|
STEEL_CONST short kTileRows = kTileRows_;
|
|
STEEL_CONST short kTileCols = kTileCols_;
|
|
|
|
STEEL_CONST short kRows = kTileRows * kFragRows;
|
|
STEEL_CONST short kCols = kTileCols * kFragCols;
|
|
|
|
STEEL_CONST short kNumFrags = kTileRows * kTileCols;
|
|
STEEL_CONST short kElemsPerTile = kNumFrags * kElemsPerFrag;
|
|
|
|
STEEL_CONST short kFragThrRows = NAXFrag_t::kElemRows;
|
|
STEEL_CONST short kFragThrCols = NAXFrag_t::kElemCols;
|
|
STEEL_CONST short kFragRowsJump = NAXFrag_t::kElemRowsJump;
|
|
|
|
STEEL_CONST short kRowsPerThread = kTileRows * NAXFrag_t::kElemRows;
|
|
STEEL_CONST short kColsPerThread = kTileCols * NAXFrag_t::kElemCols;
|
|
|
|
typedef typename NAXFrag_t::template dtype_frag_t<T> frag_type;
|
|
|
|
frag_type val_frags[kNumFrags]; // = {frag_type(0)};
|
|
|
|
METAL_FUNC NAXTile() thread {}
|
|
|
|
METAL_FUNC constexpr void clear() thread {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kNumFrags; ++i) {
|
|
val_frags[i] = frag_type(0);
|
|
}
|
|
}
|
|
|
|
METAL_FUNC constexpr thread frag_type& frag_at(const short i, const short j)
|
|
thread {
|
|
return val_frags[i * kTileCols + j];
|
|
}
|
|
|
|
METAL_FUNC constexpr const thread frag_type& frag_at(
|
|
const short i,
|
|
const short j) const thread {
|
|
return val_frags[i * kTileCols + j];
|
|
}
|
|
|
|
template <int i, int j>
|
|
METAL_FUNC constexpr thread frag_type& frag_at() thread {
|
|
return val_frags[i * kTileCols + j];
|
|
}
|
|
|
|
template <int i, int j>
|
|
METAL_FUNC constexpr const thread frag_type& frag_at() const thread {
|
|
return val_frags[i * kTileCols + j];
|
|
}
|
|
|
|
template <bool transpose>
|
|
METAL_FUNC constexpr thread frag_type& frag_at(
|
|
const short i,
|
|
const short j,
|
|
metal::bool_constant<transpose>) thread {
|
|
if constexpr (transpose) {
|
|
return frag_at(j, i);
|
|
} else {
|
|
return frag_at(i, j);
|
|
}
|
|
}
|
|
|
|
template <bool transpose>
|
|
METAL_FUNC constexpr const thread frag_type& frag_at(
|
|
const short i,
|
|
const short j,
|
|
metal::bool_constant<transpose>) const thread {
|
|
if constexpr (transpose) {
|
|
return frag_at(j, i);
|
|
} else {
|
|
return frag_at(i, j);
|
|
}
|
|
}
|
|
|
|
template <int i, int j, bool transpose>
|
|
METAL_FUNC constexpr thread frag_type& frag_at() thread {
|
|
if constexpr (transpose) {
|
|
return frag_at<j, i>();
|
|
} else {
|
|
return frag_at<i, j>();
|
|
}
|
|
}
|
|
|
|
template <int i, int j, bool transpose>
|
|
METAL_FUNC constexpr const thread frag_type& frag_at() const thread {
|
|
if constexpr (transpose) {
|
|
return frag_at<j, i>();
|
|
} else {
|
|
return frag_at<i, j>();
|
|
}
|
|
}
|
|
|
|
METAL_FUNC thread elem_type* elems() thread {
|
|
return reinterpret_cast<thread elem_type*>(val_frags);
|
|
}
|
|
|
|
METAL_FUNC const thread elem_type* elems() const thread {
|
|
return reinterpret_cast<const thread elem_type*>(val_frags);
|
|
}
|
|
|
|
template <typename Op>
|
|
METAL_FUNC void row_reduce(
|
|
thread metal::vec<T, kRowsPerThread>& vals) const thread {
|
|
auto vptr = (thread T*)(&vals);
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kTileRows; ++i) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kTileCols; ++j) {
|
|
NAXFrag_t::template row_reduce<Op>(
|
|
frag_at(i, j), &vptr[i * kFragThrRows]);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Op>
|
|
METAL_FUNC void row_bin_op(
|
|
thread metal::vec<T, kRowsPerThread>& vals) thread {
|
|
auto vptr = (thread T*)(&vals);
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short i = 0; i < kTileRows; ++i) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short j = 0; j < kTileCols; ++j) {
|
|
NAXFrag_t::template row_bin_op<Op>(
|
|
frag_at(i, j), &vptr[i * kFragThrRows]);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename U, int str_x, int str_y>
|
|
METAL_FUNC void load(const threadgroup U* src) thread {
|
|
const_for_loop<0, kTileRows, 1>([&](auto idx_row) {
|
|
const_for_loop<0, kTileCols, 1>([&](auto idx_col) {
|
|
NAXFrag_t::load(
|
|
frag_at<idx_row.value, idx_col.value>(),
|
|
src,
|
|
Int<str_x>{},
|
|
Int<str_y>{},
|
|
idx_row * Int<kFragRows>{},
|
|
idx_col * Int<kFragCols>{});
|
|
});
|
|
});
|
|
}
|
|
|
|
template <typename U, int str_x, int str_y>
|
|
METAL_FUNC void store(threadgroup U* dst) const thread {
|
|
const_for_loop<0, kTileRows, 1>([&](auto idx_row) {
|
|
const_for_loop<0, kTileCols, 1>([&](auto idx_col) {
|
|
NAXFrag_t::store(
|
|
frag_at<idx_row.value, idx_col.value>(),
|
|
dst,
|
|
Int<str_x>{},
|
|
Int<str_y>{},
|
|
idx_row * Int<kFragRows>{},
|
|
idx_col * Int<kFragCols>{});
|
|
});
|
|
});
|
|
}
|
|
|
|
template <typename U>
|
|
METAL_FUNC void load(const device U* src, const int ld) thread {
|
|
const_for_loop<0, kTileRows, 1>([&](auto idx_row) {
|
|
const_for_loop<0, kTileCols, 1>([&](auto idx_col) {
|
|
NAXFrag_t::load(
|
|
frag_at<idx_row.value, idx_col.value>(),
|
|
src,
|
|
ld,
|
|
Int<1>{},
|
|
idx_row * Int<kFragRows>{},
|
|
idx_col * Int<kFragCols>{});
|
|
});
|
|
});
|
|
}
|
|
|
|
template <typename U>
|
|
METAL_FUNC void store(device U* dst, const int ld) const thread {
|
|
const_for_loop<0, kTileRows, 1>([&](auto idx_row) {
|
|
const_for_loop<0, kTileCols, 1>([&](auto idx_col) {
|
|
NAXFrag_t::store(
|
|
frag_at<idx_row.value, idx_col.value>(),
|
|
dst,
|
|
ld,
|
|
Int<1>{},
|
|
idx_row * Int<kFragRows>{},
|
|
idx_col * Int<kFragCols>{});
|
|
});
|
|
});
|
|
}
|
|
|
|
template <typename U>
|
|
METAL_FUNC void
|
|
load_rows(const device U* src, const int ld, const short n_rows) thread {
|
|
const_for_loop<0, kTileRows, 1>([&](auto idx_row) {
|
|
const_for_loop<0, kTileCols, 1>([&](auto idx_col) {
|
|
NAXFrag_t::load_rows(
|
|
frag_at<idx_row.value, idx_col.value>(),
|
|
src,
|
|
ld,
|
|
Int<1>{},
|
|
n_rows,
|
|
idx_row * Int<kFragRows>{},
|
|
idx_col * Int<kFragCols>{});
|
|
});
|
|
});
|
|
}
|
|
|
|
template <typename U>
|
|
METAL_FUNC void load_safe(
|
|
const device U* src,
|
|
const int ld,
|
|
const short2 src_tile_dims) thread {
|
|
const_for_loop<0, kTileRows, 1>([&](auto idx_row) {
|
|
const_for_loop<0, kTileCols, 1>([&](auto idx_col) {
|
|
NAXFrag_t::load_safe(
|
|
frag_at<idx_row.value, idx_col.value>(),
|
|
src,
|
|
ld,
|
|
Int<1>{},
|
|
src_tile_dims.y,
|
|
src_tile_dims.x,
|
|
idx_row * Int<kFragRows>{},
|
|
idx_col * Int<kFragCols>{});
|
|
});
|
|
});
|
|
}
|
|
|
|
template <typename U>
|
|
METAL_FUNC void store_rows(device U* dst, const int ld, const short n_rows)
|
|
const thread {
|
|
const_for_loop<0, kTileRows, 1>([&](auto idx_row) {
|
|
const_for_loop<0, kTileCols, 1>([&](auto idx_col) {
|
|
NAXFrag_t::store_rows(
|
|
frag_at<idx_row.value, idx_col.value>(),
|
|
dst,
|
|
ld,
|
|
Int<1>{},
|
|
n_rows,
|
|
idx_row * Int<kFragRows>{},
|
|
idx_col * Int<kFragCols>{});
|
|
});
|
|
});
|
|
}
|
|
|
|
template <typename U>
|
|
METAL_FUNC void store_safe(
|
|
device U* dst,
|
|
const int ld,
|
|
const short2 dst_tile_dims) const thread {
|
|
const_for_loop<0, kTileRows, 1>([&](auto idx_row) {
|
|
const_for_loop<0, kTileCols, 1>([&](auto idx_col) {
|
|
NAXFrag_t::store_safe(
|
|
frag_at<idx_row.value, idx_col.value>(),
|
|
dst,
|
|
ld,
|
|
Int<1>{},
|
|
dst_tile_dims.y,
|
|
dst_tile_dims.x,
|
|
idx_row * Int<kFragRows>{},
|
|
idx_col * Int<kFragCols>{});
|
|
});
|
|
});
|
|
}
|
|
|
|
template <typename U>
|
|
METAL_FUNC void store_slice(
|
|
device U* dst,
|
|
const int ld,
|
|
const short2 start,
|
|
const short2 stop) const thread {
|
|
const_for_loop<0, kTileRows, 1>([&](auto idx_row) {
|
|
const_for_loop<0, kTileCols, 1>([&](auto idx_col) {
|
|
NAXFrag_t::store_slice(
|
|
frag_at<idx_row.value, idx_col.value>(),
|
|
dst,
|
|
ld,
|
|
Int<1>{},
|
|
start.y,
|
|
stop.y,
|
|
start.x,
|
|
stop.x,
|
|
idx_row * Int<kFragRows>{},
|
|
idx_col * Int<kFragCols>{});
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
template <
|
|
class CTile,
|
|
class ATile,
|
|
class BTile,
|
|
bool transpose_a,
|
|
bool transpose_b>
|
|
METAL_FUNC void tile_matmad_nax(
|
|
thread CTile& C,
|
|
thread ATile& A,
|
|
metal::bool_constant<transpose_a>,
|
|
thread BTile& B,
|
|
metal::bool_constant<transpose_b>) {
|
|
// Static checks
|
|
constexpr short TMa = transpose_a ? ATile::kTileCols : ATile::kTileRows;
|
|
constexpr short TM = CTile::kTileRows;
|
|
static_assert(TMa == TM, "MXU tile matmul: M dimensions do not match");
|
|
|
|
constexpr short TNb = transpose_b ? BTile::kTileRows : BTile::kTileCols;
|
|
constexpr short TN = CTile::kTileCols;
|
|
static_assert(TNb == TN, "MXU tile matmul: N dimensions do not match");
|
|
|
|
constexpr short TKa = transpose_a ? ATile::kTileRows : ATile::kTileCols;
|
|
constexpr short TK = transpose_b ? BTile::kTileCols : BTile::kTileRows;
|
|
static_assert(TKa == TK, "MXU tile matmul: K dimensions do not match");
|
|
|
|
constexpr auto ta = metal::bool_constant<transpose_a>{};
|
|
constexpr auto tb = metal::bool_constant<transpose_b>{};
|
|
|
|
if constexpr (TN == 1 && TM % 2 == 0) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short mm = 0; mm < TM; mm += 2) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short nn = 0; nn < TN; ++nn) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short kk = 0; kk < TK; ++kk) {
|
|
CTile::NAXFrag_t::mma(
|
|
C.frag_at(mm, nn),
|
|
C.frag_at(mm + 1, nn),
|
|
A.frag_at(mm, kk, ta),
|
|
A.frag_at(mm + 1, kk, ta),
|
|
metal::bool_constant<transpose_a>{},
|
|
B.frag_at(kk, nn, tb),
|
|
metal::bool_constant<transpose_b>{});
|
|
}
|
|
}
|
|
}
|
|
} else if constexpr (TN % 2 == 0) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short mm = 0; mm < TM; ++mm) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short nn = 0; nn < TN; nn += 2) {
|
|
STEEL_PRAGMA_UNROLL
|
|
for (short kk = 0; kk < TK; ++kk) {
|
|
CTile::NAXFrag_t::mma(
|
|
C.frag_at(mm, nn),
|
|
C.frag_at(mm, nn + 1),
|
|
A.frag_at(mm, kk, ta),
|
|
metal::bool_constant<transpose_a>{},
|
|
B.frag_at(kk, nn, tb),
|
|
B.frag_at(kk, nn + 1, tb),
|
|
metal::bool_constant<transpose_b>{});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace steel
|
|
} // namespace mlx
|
|
// Copyright © 2024 Apple Inc.
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// GEMM param classes
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace mlx {
|
|
namespace steel {
|
|
|
|
struct GEMMParams {
|
|
const int M;
|
|
const int N;
|
|
const int K;
|
|
|
|
const int lda;
|
|
const int ldb;
|
|
const int ldd;
|
|
|
|
const int tiles_n;
|
|
const int tiles_m;
|
|
|
|
const int64_t batch_stride_a;
|
|
const int64_t batch_stride_b;
|
|
const int64_t batch_stride_d;
|
|
|
|
const int swizzle_log;
|
|
const int gemm_k_iterations_aligned;
|
|
|
|
const int batch_ndim;
|
|
};
|
|
|
|
struct GEMMSpiltKParams {
|
|
const int M;
|
|
const int N;
|
|
const int K;
|
|
|
|
const int lda;
|
|
const int ldb;
|
|
const int ldc;
|
|
|
|
const int tiles_n;
|
|
const int tiles_m;
|
|
|
|
const int split_k_partitions;
|
|
const int split_k_partition_stride;
|
|
const int split_k_partition_size;
|
|
|
|
const int swizzle_log;
|
|
const int gemm_k_iterations_aligned;
|
|
};
|
|
|
|
struct GEMMAddMMParams {
|
|
const int ldc;
|
|
const int fdc;
|
|
|
|
const int64_t batch_stride_c;
|
|
|
|
const float alpha;
|
|
const float beta;
|
|
};
|
|
|
|
} // namespace steel
|
|
} // namespace mlx
|
|
// Copyright © 2024 Apple Inc.
|
|
|
|
|
|
// Copyright © 2024 Apple Inc.
|
|
|
|
|
|
#include <metal_stdlib>
|
|
|
|
METAL_FUNC ulong2 elem_to_loc_broadcast(
|
|
uint elem,
|
|
constant const int* shape,
|
|
constant const int64_t* a_strides,
|
|
constant const int64_t* b_strides,
|
|
int ndim) {
|
|
ulong loc_a{0};
|
|
ulong loc_b{0};
|
|
for (int i = ndim - 1; i >= 0 && elem > 0; --i) {
|
|
int pos_in_dim = (elem % shape[i]);
|
|
elem /= shape[i];
|
|
loc_a += pos_in_dim * a_strides[i];
|
|
loc_b += pos_in_dim * b_strides[i];
|
|
}
|
|
return ulong2(loc_a, loc_b);
|
|
}
|
|
|
|
METAL_FUNC ulong3 elem_to_loc_broadcast(
|
|
uint elem,
|
|
constant const int* shape,
|
|
constant const int64_t* a_strides,
|
|
constant const int64_t* b_strides,
|
|
constant const int64_t* c_strides,
|
|
int ndim) {
|
|
ulong loc_a{0};
|
|
ulong loc_b{0};
|
|
ulong loc_c{0};
|
|
for (int i = ndim - 1; i >= 0 && elem > 0; --i) {
|
|
int pos_in_dim = (elem % shape[i]);
|
|
elem /= shape[i];
|
|
loc_a += pos_in_dim * a_strides[i];
|
|
loc_b += pos_in_dim * b_strides[i];
|
|
loc_c += pos_in_dim * c_strides[i];
|
|
}
|
|
return ulong3(loc_a, loc_b, loc_c);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Transforms and Epilogues
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace mlx {
|
|
namespace steel {
|
|
|
|
template <typename OutT, typename InT>
|
|
struct TransformNone {
|
|
static METAL_FUNC OutT apply(InT x) {
|
|
return static_cast<OutT>(x);
|
|
}
|
|
|
|
static METAL_FUNC OutT apply(InT x, OutT) {
|
|
return static_cast<OutT>(x);
|
|
}
|
|
};
|
|
|
|
template <typename OutT, typename InT>
|
|
struct TransformAdd {
|
|
TransformAdd(const float, const float) thread {}
|
|
|
|
static METAL_FUNC OutT apply(InT x) {
|
|
return static_cast<OutT>(x);
|
|
}
|
|
|
|
static METAL_FUNC OutT apply(InT x, OutT c) {
|
|
return static_cast<OutT>(x) + c;
|
|
}
|
|
};
|
|
|
|
template <typename OutT, typename InT>
|
|
struct TransformAxpby {
|
|
const float alpha;
|
|
const float beta;
|
|
|
|
TransformAxpby(const float alpha_, const float beta_) thread : alpha(alpha_),
|
|
beta(beta_) {}
|
|
|
|
static METAL_FUNC OutT apply(InT x) {
|
|
return static_cast<OutT>(x);
|
|
}
|
|
|
|
METAL_FUNC OutT apply(InT x, OutT c) const thread {
|
|
return static_cast<OutT>(
|
|
x * static_cast<InT>(alpha) + (static_cast<OutT>(beta) * c));
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct AccumHelper {
|
|
typedef float accum_type;
|
|
};
|
|
|
|
struct BlockSwizzle {
|
|
static METAL_FUNC int2
|
|
swizzle(uint3 tid [[threadgroup_position_in_grid]], const int swizzle_log) {
|
|
const int tid_x = (tid.x) >> swizzle_log;
|
|
const int tid_y =
|
|
((tid.y) << swizzle_log) + ((tid.x) & ((1 << swizzle_log) - 1));
|
|
return int2(tid_x, tid_y);
|
|
}
|
|
};
|
|
|
|
} // namespace steel
|
|
} // namespace mlx
|
|
|
|
using namespace metal;
|
|
|
|
namespace mlx::steel {
|
|
|
|
template <
|
|
typename T,
|
|
short SM,
|
|
short SN,
|
|
short SK,
|
|
short BK,
|
|
bool transpose_a,
|
|
bool transpose_b,
|
|
bool kAlignedM,
|
|
bool kAlignedN,
|
|
bool kAlignedK,
|
|
typename AccumType = float>
|
|
auto gemm_loop(
|
|
const device T* A,
|
|
const device T* B,
|
|
int lda,
|
|
int ldb,
|
|
int K,
|
|
int gemm_k_iterations_aligned,
|
|
const short sgp_sm,
|
|
const short sgp_sn) {
|
|
constexpr short TM = SM / 16;
|
|
constexpr short TN = SN / 16;
|
|
constexpr short TK = SK / 16;
|
|
|
|
constexpr int RA = transpose_a ? TK : TM;
|
|
constexpr int CA = transpose_a ? TM : TK;
|
|
|
|
constexpr int RB = transpose_b ? TN : TK;
|
|
constexpr int CB = transpose_b ? TK : TN;
|
|
|
|
NAXTile<AccumType, TM, TN> Dtile;
|
|
Dtile.clear();
|
|
|
|
const bool has_output = sgp_sm > 0 && sgp_sn > 0;
|
|
|
|
int gemm_k_iterations_ = gemm_k_iterations_aligned;
|
|
|
|
STEEL_PRAGMA_NO_UNROLL
|
|
for (int kk0 = 0; kk0 < gemm_k_iterations_; kk0++) {
|
|
threadgroup_barrier(mem_flags::mem_none);
|
|
if constexpr (!kAlignedM || !kAlignedN) {
|
|
if (!has_output)
|
|
continue;
|
|
}
|
|
|
|
STEEL_PRAGMA_NO_UNROLL
|
|
for (int kk1 = 0; kk1 < BK; kk1 += SK) {
|
|
NAXTile<T, RA, CA> Atile;
|
|
NAXTile<T, RB, CB> Btile;
|
|
const int k = kk1;
|
|
|
|
volatile int compiler_barrier;
|
|
|
|
const int A_offset = transpose_a ? k * lda : k;
|
|
const int B_offset = transpose_b ? k : k * ldb;
|
|
|
|
if constexpr (kAlignedM) {
|
|
Atile.load(A + A_offset, lda);
|
|
} else {
|
|
const short rmax = transpose_a ? SK : sgp_sm;
|
|
const short cmax = transpose_a ? sgp_sm : SK;
|
|
Atile.load_safe(A + A_offset, lda, short2(cmax, rmax));
|
|
}
|
|
|
|
if constexpr (kAlignedN) {
|
|
Btile.load(B + B_offset, ldb);
|
|
} else {
|
|
const short rmax = transpose_b ? sgp_sn : SK;
|
|
const short cmax = transpose_b ? SK : sgp_sn;
|
|
Btile.load_safe(B + B_offset, ldb, short2(cmax, rmax));
|
|
}
|
|
|
|
tile_matmad_nax(
|
|
Dtile,
|
|
Atile,
|
|
metal::bool_constant<transpose_a>{},
|
|
Btile,
|
|
metal::bool_constant<transpose_b>{});
|
|
|
|
(void)compiler_barrier;
|
|
}
|
|
|
|
A += transpose_a ? (BK * lda) : BK;
|
|
B += transpose_b ? BK : (BK * ldb);
|
|
}
|
|
|
|
if constexpr (!kAlignedK) {
|
|
simdgroup_barrier(mem_flags::mem_none);
|
|
if constexpr (!kAlignedM || !kAlignedN) {
|
|
if (!has_output)
|
|
return Dtile;
|
|
}
|
|
|
|
const short rem_bk = K - gemm_k_iterations_ * BK;
|
|
|
|
STEEL_PRAGMA_NO_UNROLL
|
|
for (int kk1 = 0; kk1 < rem_bk; kk1 += SK) {
|
|
NAXTile<T, RA, CA> Atile;
|
|
NAXTile<T, RB, CB> Btile;
|
|
|
|
const int k = kk1;
|
|
const short psk = max(0, rem_bk - k);
|
|
|
|
const short2 Aklims =
|
|
transpose_a ? short2(sgp_sm, psk) : short2(psk, sgp_sm);
|
|
const short2 Bklims =
|
|
transpose_b ? short2(psk, sgp_sn) : short2(sgp_sn, psk);
|
|
|
|
const int A_offset = transpose_a ? k * lda : k;
|
|
const int B_offset = transpose_b ? k : k * ldb;
|
|
|
|
Atile.load_safe(A + A_offset, lda, Aklims);
|
|
Btile.load_safe(B + B_offset, ldb, Bklims);
|
|
|
|
tile_matmad_nax(
|
|
Dtile,
|
|
Atile,
|
|
metal::bool_constant<transpose_a>{},
|
|
Btile,
|
|
metal::bool_constant<transpose_b>{});
|
|
}
|
|
}
|
|
|
|
return Dtile;
|
|
}
|
|
|
|
} // namespace mlx::steel
|
|
// Copyright © 2023-2024 Apple Inc.
|
|
|
|
#include <metal_simdgroup>
|
|
#include <metal_stdlib>
|
|
|
|
template <typename T, typename mma_t, typename loader_a_t, typename loader_b_t>
|
|
METAL_FUNC void gemm_loop_aligned(
|
|
threadgroup T* As,
|
|
threadgroup T* Bs,
|
|
thread mma_t& mma_op,
|
|
thread loader_a_t& loader_a,
|
|
thread loader_b_t& loader_b,
|
|
const int k_iterations) {
|
|
for (int k = 0; k < k_iterations; k++) {
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
|
|
// Load elements into threadgroup memory
|
|
loader_a.load_unsafe();
|
|
loader_b.load_unsafe();
|
|
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
|
|
// Multiply and accumulate threadgroup elements
|
|
mma_op.mma(As, Bs);
|
|
|
|
// Prepare for next iteration
|
|
loader_a.next();
|
|
loader_b.next();
|
|
}
|
|
}
|
|
|
|
template <
|
|
bool rows_aligned,
|
|
bool cols_aligned,
|
|
bool transpose,
|
|
typename T,
|
|
typename mma_t,
|
|
typename loader_a_t,
|
|
typename loader_b_t>
|
|
METAL_FUNC void gemm_loop_unaligned(
|
|
threadgroup T* As,
|
|
threadgroup T* Bs,
|
|
thread mma_t& mma_op,
|
|
thread loader_a_t& loader_a,
|
|
thread loader_b_t& loader_b,
|
|
const int k_iterations,
|
|
const short tgp_bm,
|
|
const short tgp_bn,
|
|
const short tgp_bk) {
|
|
for (int k = 0; k < k_iterations; k++) {
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
|
|
// Load elements into threadgroup memory
|
|
if (rows_aligned) {
|
|
loader_a.load_unsafe();
|
|
} else {
|
|
loader_a.load_safe(short2(tgp_bk, tgp_bm));
|
|
}
|
|
if (cols_aligned) {
|
|
loader_b.load_unsafe();
|
|
} else {
|
|
loader_b.load_safe(
|
|
transpose ? short2(tgp_bk, tgp_bn) : short2(tgp_bn, tgp_bk));
|
|
}
|
|
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
|
|
// Multiply and accumulate threadgroup elements
|
|
mma_op.mma(As, Bs);
|
|
|
|
// Prepare for next iteration
|
|
loader_a.next();
|
|
loader_b.next();
|
|
}
|
|
}
|
|
|
|
template <typename T, typename mma_t, typename loader_a_t, typename loader_b_t>
|
|
METAL_FUNC void gemm_loop_finalize(
|
|
threadgroup T* As,
|
|
threadgroup T* Bs,
|
|
thread mma_t& mma_op,
|
|
thread loader_a_t& loader_a,
|
|
thread loader_b_t& loader_b,
|
|
const short2 tile_a,
|
|
const short2 tile_b) {
|
|
loader_a.load_safe(tile_a);
|
|
loader_b.load_safe(tile_b);
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
mma_op.mma(As, Bs);
|
|
}
|
|
// Copyright © 2023-2024 Apple Inc.
|
|
|
|
#include <metal_simdgroup>
|
|
#include <metal_stdlib>
|
|
|
|
using namespace metal;
|
|
using namespace mlx::steel;
|
|
|
|
constant bool align_M [[function_constant(200)]];
|
|
constant bool align_N [[function_constant(201)]];
|
|
constant bool align_K [[function_constant(202)]];
|
|
|
|
using namespace metal;
|
|
|
|
#define MLX_MTL_CONST static constant constexpr const
|
|
|
|
MLX_MTL_CONST int SIMD_SIZE = 32;
|
|
MLX_MTL_CONST int QUAD_SIZE = 4;
|
|
|
|
template <int bits, int wsize = 8>
|
|
inline constexpr short get_pack_factor() {
|
|
return (bits == 3 || bits == 5) ? 8 : (bits == 6 ? 4 : wsize / bits);
|
|
}
|
|
|
|
template <int bits, int wsize = 8>
|
|
inline constexpr short get_bytes_per_pack() {
|
|
constexpr int power_of_2_bits = (bits & (bits - 1)) == 0;
|
|
return power_of_2_bits ? (wsize / 8) : (bits == 5 ? 5 : 3);
|
|
}
|
|
|
|
template <typename T, typename U, int values_per_thread, int bits>
|
|
inline U load_vector(const device T* x, thread U* x_thread) {
|
|
static_assert(
|
|
bits == 2 || bits == 3 || bits == 4 || bits == 5 || bits == 6 ||
|
|
bits == 8,
|
|
"Template undefined for bits not in {2, 3, 4, 5, 6, 8}");
|
|
|
|
U sum = 0;
|
|
|
|
if (bits == 2) {
|
|
for (int i = 0; i < values_per_thread; i += 4) {
|
|
sum += x[i] + x[i + 1] + x[i + 2] + x[i + 3];
|
|
x_thread[i] = x[i];
|
|
x_thread[i + 1] = x[i + 1] / 4.0f;
|
|
x_thread[i + 2] = x[i + 2] / 16.0f;
|
|
x_thread[i + 3] = x[i + 3] / 64.0f;
|
|
}
|
|
}
|
|
|
|
else if (bits == 3) {
|
|
for (int i = 0; i < values_per_thread; i += 8) {
|
|
sum += x[i] + x[i + 1] + x[i + 2] + x[i + 3] + x[i + 4] + x[i + 5] +
|
|
x[i + 6] + x[i + 7];
|
|
x_thread[i] = x[i];
|
|
x_thread[i + 1] = x[i + 1] / 8.0f;
|
|
x_thread[i + 2] = x[i + 2] / 64.0f;
|
|
x_thread[i + 3] = x[i + 3] / 2.0f;
|
|
x_thread[i + 4] = x[i + 4] / 16.0f;
|
|
x_thread[i + 5] = x[i + 5] / 128.0f;
|
|
x_thread[i + 6] = x[i + 6] / 4.0f;
|
|
x_thread[i + 7] = x[i + 7] / 32.0f;
|
|
}
|
|
}
|
|
|
|
else if (bits == 4) {
|
|
for (int i = 0; i < values_per_thread; i += 4) {
|
|
sum += x[i] + x[i + 1] + x[i + 2] + x[i + 3];
|
|
x_thread[i] = x[i];
|
|
x_thread[i + 1] = x[i + 1] / 16.0f;
|
|
x_thread[i + 2] = x[i + 2] / 256.0f;
|
|
x_thread[i + 3] = x[i + 3] / 4096.0f;
|
|
}
|
|
}
|
|
|
|
else if (bits == 5) {
|
|
for (int i = 0; i < values_per_thread; i += 8) {
|
|
sum += x[i] + x[i + 1] + x[i + 2] + x[i + 3] + x[i + 4] + x[i + 5] +
|
|
x[i + 6] + x[i + 7];
|
|
x_thread[i] = x[i];
|
|
x_thread[i + 1] = x[i + 1] / 32.0f;
|
|
x_thread[i + 2] = x[i + 2] / 4.0f;
|
|
x_thread[i + 3] = x[i + 3] / 128.0f;
|
|
x_thread[i + 4] = x[i + 4] / 16.0f;
|
|
x_thread[i + 5] = x[i + 5] / 2.0f;
|
|
x_thread[i + 6] = x[i + 6] / 64.0f;
|
|
x_thread[i + 7] = x[i + 7] / 8.0f;
|
|
}
|
|
}
|
|
|
|
else if (bits == 6) {
|
|
for (int i = 0; i < values_per_thread; i += 4) {
|
|
sum += x[i] + x[i + 1] + x[i + 2] + x[i + 3];
|
|
x_thread[i] = x[i];
|
|
x_thread[i + 1] = x[i + 1] / 64.0f;
|
|
x_thread[i + 2] = x[i + 2] / 16.0f;
|
|
x_thread[i + 3] = x[i + 3] / 4.0f;
|
|
}
|
|
}
|
|
|
|
else if (bits == 8) {
|
|
for (int i = 0; i < values_per_thread; i++) {
|
|
sum += x[i];
|
|
x_thread[i] = x[i];
|
|
}
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
template <typename T, typename U, int values_per_thread, int bits>
|
|
inline U load_vector_safe(const device T* x, thread U* x_thread, int N) {
|
|
static_assert(
|
|
bits == 2 || bits == 3 || bits == 4 || bits == 5 || bits == 6 ||
|
|
bits == 8,
|
|
"Template undefined for bits not in {2, 3, 4, 5, 6, 8}");
|
|
|
|
U sum = 0;
|
|
|
|
if (bits == 2) {
|
|
for (int i = 0; i < N; i += 4) {
|
|
sum += x[i] + x[i + 1] + x[i + 2] + x[i + 3];
|
|
x_thread[i] = x[i];
|
|
x_thread[i + 1] = x[i + 1] / 4.0f;
|
|
x_thread[i + 2] = x[i + 2] / 16.0f;
|
|
x_thread[i + 3] = x[i + 3] / 64.0f;
|
|
}
|
|
}
|
|
|
|
else if (bits == 3) {
|
|
for (int i = 0; i < N; i += 8) {
|
|
sum += x[i] + x[i + 1] + x[i + 2] + x[i + 3] + x[i + 4] + x[i + 5] +
|
|
x[i + 6] + x[i + 7];
|
|
|
|
x_thread[i] = x[i];
|
|
x_thread[i + 1] = x[i + 1] / 8.0f;
|
|
x_thread[i + 2] = x[i + 2] / 64.0f;
|
|
x_thread[i + 3] = x[i + 3] / 2.0f;
|
|
x_thread[i + 4] = x[i + 4] / 16.0f;
|
|
x_thread[i + 5] = x[i + 5] / 128.0f;
|
|
x_thread[i + 6] = x[i + 6] / 4.0f;
|
|
x_thread[i + 7] = x[i + 7] / 32.0f;
|
|
}
|
|
}
|
|
|
|
else if (bits == 4) {
|
|
for (int i = 0; i < N; i += 4) {
|
|
sum += x[i] + x[i + 1] + x[i + 2] + x[i + 3];
|
|
x_thread[i] = x[i];
|
|
x_thread[i + 1] = x[i + 1] / 16.0f;
|
|
x_thread[i + 2] = x[i + 2] / 256.0f;
|
|
x_thread[i + 3] = x[i + 3] / 4096.0f;
|
|
}
|
|
}
|
|
|
|
else if (bits == 5) {
|
|
for (int i = 0; i < N; i += 8) {
|
|
sum += x[i] + x[i + 1] + x[i + 2] + x[i + 3] + x[i + 4] + x[i + 5] +
|
|
x[i + 6] + x[i + 7];
|
|
x_thread[i] = x[i];
|
|
x_thread[i + 1] = x[i + 1] / 32.0f;
|
|
x_thread[i + 2] = x[i + 2] / 4.0f;
|
|
x_thread[i + 3] = x[i + 3] / 128.0f;
|
|
x_thread[i + 4] = x[i + 4] / 16.0f;
|
|
x_thread[i + 5] = x[i + 5] / 2.0f;
|
|
x_thread[i + 6] = x[i + 6] / 64.0f;
|
|
x_thread[i + 7] = x[i + 7] / 8.0f;
|
|
}
|
|
}
|
|
|
|
else if (bits == 6) {
|
|
for (int i = 0; i < N; i += 4) {
|
|
sum += x[i] + x[i + 1] + x[i + 2] + x[i + 3];
|
|
x_thread[i] = x[i];
|
|
x_thread[i + 1] = x[i + 1] / 64.0f;
|
|
x_thread[i + 2] = x[i + 2] / 16.0f;
|
|
x_thread[i + 3] = x[i + 3] / 4.0f;
|
|
}
|
|
}
|
|
|
|
else if (bits == 8) {
|
|
for (int i = 0; i < N; i++) {
|
|
sum += x[i];
|
|
x_thread[i] = x[i];
|
|
}
|
|
}
|
|
|
|
for (int i = N; i < values_per_thread; i++) {
|
|
x_thread[i] = 0;
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
template <typename U, int values_per_thread, int bits>
|
|
inline U qdot(
|
|
const device uint8_t* w,
|
|
const thread U* x_thread,
|
|
U scale,
|
|
U bias,
|
|
U sum) {
|
|
static_assert(
|
|
bits == 2 || bits == 3 || bits == 4 || bits == 5 || bits == 6 ||
|
|
bits == 8,
|
|
"Template undefined for bits not in {2, 3, 4, 5, 6, 8}");
|
|
|
|
U accum = 0;
|
|
|
|
if (bits == 2) {
|
|
for (int i = 0; i < (values_per_thread / 4); i++) {
|
|
accum +=
|
|
(x_thread[4 * i] * (w[i] & 0x03) +
|
|
x_thread[4 * i + 1] * (w[i] & 0x0c) +
|
|
x_thread[4 * i + 2] * (w[i] & 0x30) +
|
|
x_thread[4 * i + 3] * (w[i] & 0xc0));
|
|
}
|
|
}
|
|
|
|
else if (bits == 3) {
|
|
for (int i = 0; i < (values_per_thread / 8); i++) {
|
|
x_thread += 8 * i;
|
|
w += 3 * i;
|
|
|
|
accum += (w[0] & 0x07) * x_thread[0];
|
|
accum += (w[0] & 0x38) * x_thread[1];
|
|
accum += (w[0] & 0xc0) * x_thread[2];
|
|
accum += (w[1] & 0x01) * (x_thread[2] * 256.0f);
|
|
|
|
accum += (w[1] & 0x0e) * x_thread[3];
|
|
accum += (w[1] & 0x70) * x_thread[4];
|
|
accum += (w[1] & 0x80) * x_thread[5];
|
|
accum += (w[2] & 0x03) * (x_thread[5] * 256.0f);
|
|
|
|
accum += (w[2] & 0x1c) * x_thread[6];
|
|
accum += (w[2] & 0xe0) * x_thread[7];
|
|
}
|
|
}
|
|
|
|
else if (bits == 4) {
|
|
const device uint16_t* ws = (const device uint16_t*)w;
|
|
for (int i = 0; i < (values_per_thread / 4); i++) {
|
|
accum +=
|
|
(x_thread[4 * i] * (ws[i] & 0x000f) +
|
|
x_thread[4 * i + 1] * (ws[i] & 0x00f0) +
|
|
x_thread[4 * i + 2] * (ws[i] & 0x0f00) +
|
|
x_thread[4 * i + 3] * (ws[i] & 0xf000));
|
|
}
|
|
}
|
|
|
|
else if (bits == 5) {
|
|
for (int i = 0; i < (values_per_thread / 8); i++) {
|
|
x_thread += 8 * i;
|
|
w += 5 * i;
|
|
|
|
accum += (w[0] & 0x1f) * x_thread[0];
|
|
accum += (w[0] & 0xe0) * x_thread[1];
|
|
accum += (w[1] & 0x3) * (x_thread[1] * 256.0f);
|
|
accum += (w[1] & 0x7c) * x_thread[2];
|
|
accum += (w[1] & 0x80) * x_thread[3];
|
|
accum += (w[2] & 0xf) * (x_thread[3] * 256.0f);
|
|
accum += (w[2] & 0xf0) * x_thread[4];
|
|
accum += (w[3] & 0x1) * (x_thread[4] * 256.0f);
|
|
accum += (w[3] & 0x3e) * x_thread[5];
|
|
accum += (w[3] & 0xc0) * x_thread[6];
|
|
accum += (w[4] & 0x7) * (x_thread[6] * 256.0f);
|
|
accum += (w[4] & 0xf8) * x_thread[7];
|
|
}
|
|
}
|
|
|
|
else if (bits == 6) {
|
|
for (int i = 0; i < (values_per_thread / 4); i++) {
|
|
x_thread += 4 * i;
|
|
w += 3 * i;
|
|
|
|
accum += (w[0] & 0x3f) * x_thread[0];
|
|
|
|
accum += (w[0] & 0xc0) * x_thread[1];
|
|
accum += (w[1] & 0x0f) * (x_thread[1] * 256.0f);
|
|
|
|
accum += (w[1] & 0xf0) * x_thread[2];
|
|
accum += (w[2] & 0x03) * (x_thread[2] * 256.0f);
|
|
|
|
accum += (w[2] & 0xfc) * x_thread[3];
|
|
}
|
|
}
|
|
|
|
else if (bits == 8) {
|
|
for (int i = 0; i < values_per_thread; i++) {
|
|
accum += x_thread[i] * w[i];
|
|
}
|
|
}
|
|
|
|
return scale * accum + sum * bias;
|
|
}
|
|
|
|
template <typename U, int values_per_thread, int bits>
|
|
inline U qdot_safe(
|
|
const device uint8_t* w,
|
|
const thread U* x_thread,
|
|
U scale,
|
|
U bias,
|
|
U sum,
|
|
int N) {
|
|
static_assert(
|
|
bits == 2 || bits == 3 || bits == 4 || bits == 5 || bits == 6 ||
|
|
bits == 8,
|
|
"Template undefined for bits not in {2, 3, 4, 5, 6, 8}");
|
|
|
|
U accum = 0;
|
|
|
|
if (bits == 2) {
|
|
for (int i = 0; i < (N / 4); i++) {
|
|
accum +=
|
|
(x_thread[4 * i] * (w[i] & 0x03) +
|
|
x_thread[4 * i + 1] * (w[i] & 0x0c) +
|
|
x_thread[4 * i + 2] * (w[i] & 0x30) +
|
|
x_thread[4 * i + 3] * (w[i] & 0xc0));
|
|
}
|
|
}
|
|
|
|
else if (bits == 3) {
|
|
for (int i = 0; i < (N / 8); i++) {
|
|
x_thread += 8 * i;
|
|
w += 3 * i;
|
|
|
|
accum += (w[0] & 0x07) * x_thread[0];
|
|
accum += (w[0] & 0x38) * x_thread[1];
|
|
accum += (w[0] & 0xc0) * x_thread[2];
|
|
accum += (w[1] & 0x01) * (x_thread[2] * 256.0f);
|
|
|
|
accum += (w[1] & 0x0e) * x_thread[3];
|
|
accum += (w[1] & 0x70) * x_thread[4];
|
|
accum += (w[1] & 0x80) * x_thread[5];
|
|
accum += (w[2] & 0x03) * (x_thread[5] * 256.0f);
|
|
|
|
accum += (w[2] & 0x1c) * x_thread[6];
|
|
accum += (w[2] & 0xe0) * x_thread[7];
|
|
}
|
|
}
|
|
|
|
else if (bits == 4) {
|
|
const device uint16_t* ws = (const device uint16_t*)w;
|
|
for (int i = 0; i < (N / 4); i++) {
|
|
accum +=
|
|
(x_thread[4 * i] * (ws[i] & 0x000f) +
|
|
x_thread[4 * i + 1] * (ws[i] & 0x00f0) +
|
|
x_thread[4 * i + 2] * (ws[i] & 0x0f00) +
|
|
x_thread[4 * i + 3] * (ws[i] & 0xf000));
|
|
}
|
|
}
|
|
|
|
else if (bits == 5) {
|
|
for (int i = 0; i < (N / 8); i++) {
|
|
x_thread += 8 * i;
|
|
w += 5 * i;
|
|
|
|
accum += (w[0] & 0x1f) * x_thread[0];
|
|
accum += (w[0] & 0xe0) * x_thread[1];
|
|
accum += (w[1] & 0x3) * (x_thread[1] * 256.0f);
|
|
accum += (w[1] & 0x7c) * x_thread[2];
|
|
accum += (w[1] & 0x80) * x_thread[3];
|
|
accum += (w[2] & 0xf) * (x_thread[3] * 256.0f);
|
|
accum += (w[2] & 0xf0) * x_thread[4];
|
|
accum += (w[3] & 0x1) * (x_thread[4] * 256.0f);
|
|
accum += (w[3] & 0x3e) * x_thread[5];
|
|
accum += (w[3] & 0xc0) * x_thread[6];
|
|
accum += (w[4] & 0x7) * (x_thread[6] * 256.0f);
|
|
accum += (w[4] & 0xf8) * x_thread[7];
|
|
}
|
|
}
|
|
|
|
else if (bits == 6) {
|
|
for (int i = 0; i < (N / 4); i++) {
|
|
x_thread += 4 * i;
|
|
w += 3 * i;
|
|
|
|
accum += (w[0] & 0x3f) * x_thread[0];
|
|
|
|
accum += (w[0] & 0xc0) * x_thread[1];
|
|
accum += (w[1] & 0x0f) * (x_thread[1] * 256.0f);
|
|
|
|
accum += (w[1] & 0xf0) * x_thread[2];
|
|
accum += (w[2] & 0x03) * (x_thread[2] * 256.0f);
|
|
|
|
accum += (w[2] & 0xfc) * x_thread[3];
|
|
}
|
|
}
|
|
|
|
else if (bits == 8) {
|
|
for (int i = 0; i < N; i++) {
|
|
accum += x_thread[i] * w[i];
|
|
}
|
|
}
|
|
|
|
return scale * accum + sum * bias;
|
|
}
|
|
|
|
template <typename U, int values_per_thread, int bits>
|
|
inline void
|
|
qouter(const thread uint8_t* w, U x, U scale, U bias, thread U* result) {
|
|
static_assert(
|
|
bits == 2 || bits == 3 || bits == 4 || bits == 5 || bits == 6 ||
|
|
bits == 8,
|
|
"Template undefined for bits not in {2, 3, 4, 5, 6, 8}");
|
|
|
|
if (bits == 2) {
|
|
U s[4] = {scale, scale / 4.0f, scale / 16.0f, scale / 64.0f};
|
|
for (int i = 0; i < (values_per_thread / 4); i++) {
|
|
result[4 * i] += x * (s[0] * (w[i] & 0x03) + bias);
|
|
result[4 * i + 1] += x * (s[1] * (w[i] & 0x0c) + bias);
|
|
result[4 * i + 2] += x * (s[2] * (w[i] & 0x30) + bias);
|
|
result[4 * i + 3] += x * (s[3] * (w[i] & 0xc0) + bias);
|
|
}
|
|
}
|
|
|
|
else if (bits == 3) {
|
|
for (int i = 0; i < (values_per_thread / 8); i++) {
|
|
uint8_t w0 = w[3 * i];
|
|
uint8_t w1 = w[3 * i + 1];
|
|
uint8_t w2 = w[3 * i + 2];
|
|
|
|
result[8 * i] += x * ((w0 & 0x7) * scale + bias);
|
|
result[8 * i + 1] += x * (((w0 & 0x38) >> 3) * scale + bias);
|
|
result[8 * i + 2] +=
|
|
x * ((((w0 & 0xc0) >> 6) + ((w1 & 0x1) << 2)) * scale + bias);
|
|
result[8 * i + 3] += x * (((w1 & 0xe) >> 1) * scale + bias);
|
|
result[8 * i + 4] += x * (((w1 & 0x70) >> 4) * scale + bias);
|
|
result[8 * i + 5] +=
|
|
x * ((((w1 & 0x80) >> 7) + ((w2 & 0x3) << 1)) * scale + bias);
|
|
result[8 * i + 6] += x * (((w2 & 0x1c) >> 2) * scale + bias);
|
|
result[8 * i + 7] += x * (((w2 & 0xe0) >> 5) * scale + bias);
|
|
}
|
|
}
|
|
|
|
else if (bits == 4) {
|
|
U s[2] = {scale, scale / 16.0f};
|
|
for (int i = 0; i < (values_per_thread / 2); i++) {
|
|
result[2 * i] += x * (s[0] * (w[i] & 0x0f) + bias);
|
|
result[2 * i + 1] += x * (s[1] * (w[i] & 0xf0) + bias);
|
|
}
|
|
}
|
|
|
|
else if (bits == 5) {
|
|
for (int i = 0; i < (values_per_thread / 8); i++) {
|
|
uint8_t w0 = w[5 * i];
|
|
uint8_t w1 = w[5 * i + 1];
|
|
uint8_t w2 = w[5 * i + 2];
|
|
uint8_t w3 = w[5 * i + 3];
|
|
uint8_t w4 = w[5 * i + 4];
|
|
result[8 * i] += x * ((w0 & 0x1f) * scale + bias);
|
|
result[8 * i + 1] +=
|
|
x * ((((w0 & 0xe0) >> 5) + ((w1 & 0x3) << 3)) * scale + bias);
|
|
result[8 * i + 2] += x * (((w1 & 0x7c) >> 2) * scale + bias);
|
|
result[8 * i + 3] +=
|
|
x * ((((w1 & 0x80) >> 7) + ((w2 & 0xf) << 1)) * scale + bias);
|
|
result[8 * i + 4] +=
|
|
x * ((((w2 & 0xf0) >> 4) + ((w3 & 0x1) << 4)) * scale + bias);
|
|
result[8 * i + 5] += x * (((w3 & 0x3e) >> 1) * scale + bias);
|
|
result[8 * i + 6] +=
|
|
x * ((((w3 & 0xc0) >> 6) + ((w4 & 0x7) << 2)) * scale + bias);
|
|
result[8 * i + 7] += x * (((w4 & 0xf8) >> 3) * scale + bias);
|
|
}
|
|
}
|
|
|
|
else if (bits == 6) {
|
|
for (int i = 0; i < (values_per_thread / 4); i++) {
|
|
uint8_t w0 = w[3 * i];
|
|
uint8_t w1 = w[3 * i + 1];
|
|
uint8_t w2 = w[3 * i + 2];
|
|
|
|
result[4 * i] += x * ((w0 & 0x3f) * scale + bias);
|
|
result[4 * i + 1] +=
|
|
x * ((((w0 >> 6) & 0x03) + ((w1 & 0x0f) << 2)) * scale + bias);
|
|
result[4 * i + 2] +=
|
|
x * ((((w1 >> 4) & 0x0f) + ((w2 & 0x03) << 4)) * scale + bias);
|
|
result[4 * i + 3] += x * (((w2 >> 2) & 0x3f) * scale + bias);
|
|
}
|
|
}
|
|
|
|
else if (bits == 8) {
|
|
for (int i = 0; i < values_per_thread; i++) {
|
|
result[i] += x * (scale * w[i] + bias);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename U, int N, int bits>
|
|
inline void
|
|
dequantize(const device uint8_t* w, U scale, U bias, threadgroup U* w_local) {
|
|
static_assert(
|
|
bits == 2 || bits == 3 || bits == 4 || bits == 5 || bits == 6 ||
|
|
bits == 8,
|
|
"Template undefined for bits not in {2, 3, 4, 5, 6, 8}");
|
|
|
|
const float s = float(scale);
|
|
const float b = float(bias);
|
|
|
|
if (bits == 2) {
|
|
float sc[4] = {s, s / 4.0f, s / 16.0f, s / 64.0f};
|
|
for (int i = 0; i < (N / 4); i++) {
|
|
w_local[4 * i] = static_cast<U>(sc[0] * (w[i] & 0x03) + b);
|
|
w_local[4 * i + 1] = static_cast<U>(sc[1] * (w[i] & 0x0c) + b);
|
|
w_local[4 * i + 2] = static_cast<U>(sc[2] * (w[i] & 0x30) + b);
|
|
w_local[4 * i + 3] = static_cast<U>(sc[3] * (w[i] & 0xc0) + b);
|
|
}
|
|
}
|
|
|
|
else if (bits == 3) {
|
|
for (int i = 0; i < (N / 8); i++) {
|
|
w_local += 8 * i;
|
|
w += 3 * i;
|
|
|
|
w_local[0] = static_cast<U>((w[0] & 0x7) * s + b);
|
|
w_local[1] = static_cast<U>(((w[0] & 0x38) >> 3) * s + b);
|
|
w_local[2] =
|
|
static_cast<U>((((w[0] & 0xc0) >> 6) + ((w[1] & 0x1) << 2)) * s + b);
|
|
w_local[3] = static_cast<U>(((w[1] & 0xe) >> 1) * s + b);
|
|
w_local[4] = static_cast<U>(((w[1] & 0x70) >> 4) * s + b);
|
|
w_local[5] =
|
|
static_cast<U>((((w[1] & 0x80) >> 7) + ((w[2] & 0x3) << 1)) * s + b);
|
|
w_local[6] = static_cast<U>(((w[2] & 0x1c) >> 2) * s + b);
|
|
w_local[7] = static_cast<U>(((w[2] & 0xe0) >> 5) * s + b);
|
|
}
|
|
}
|
|
|
|
else if (bits == 4) {
|
|
float sc[2] = {s, s / 16.0f};
|
|
for (int i = 0; i < (N / 2); i++) {
|
|
w_local[2 * i] = static_cast<U>(sc[0] * (w[i] & 0x0f) + b);
|
|
w_local[2 * i + 1] = static_cast<U>(sc[1] * (w[i] & 0xf0) + b);
|
|
}
|
|
}
|
|
|
|
else if (bits == 5) {
|
|
for (int i = 0; i < (N / 8); i++) {
|
|
w_local += 8 * i;
|
|
w += 5 * i;
|
|
|
|
w_local[0] = static_cast<U>((w[0] & 0x1f) * s + b);
|
|
w_local[1] =
|
|
static_cast<U>((((w[0] & 0xe0) >> 5) + ((w[1] & 0x3) << 3)) * s + b);
|
|
w_local[2] = static_cast<U>(((w[1] & 0x7c) >> 2) * s + b);
|
|
w_local[3] =
|
|
static_cast<U>((((w[1] & 0x80) >> 7) + ((w[2] & 0xf) << 1)) * s + b);
|
|
w_local[4] =
|
|
static_cast<U>((((w[2] & 0xf0) >> 4) + ((w[3] & 0x1) << 4)) * s + b);
|
|
w_local[5] = static_cast<U>(((w[3] & 0x3e) >> 1) * s + b);
|
|
w_local[6] =
|
|
static_cast<U>((((w[3] & 0xc0) >> 6) + ((w[4] & 0x7) << 2)) * s + b);
|
|
w_local[7] = static_cast<U>(((w[4] & 0xf8) >> 3) * s + b);
|
|
}
|
|
}
|
|
|
|
else if (bits == 6) {
|
|
for (int i = 0; i < (N / 4); i++) {
|
|
w_local += 4 * i;
|
|
w += 3 * i;
|
|
w_local[0] = static_cast<U>((w[0] & 0x3f) * s + b);
|
|
w_local[1] =
|
|
static_cast<U>((((w[0] >> 6) & 0x03) + ((w[1] & 0x0f) << 2)) * s + b);
|
|
w_local[2] =
|
|
static_cast<U>((((w[1] >> 4) & 0x0f) + ((w[2] & 0x03) << 4)) * s + b);
|
|
w_local[3] = static_cast<U>(((w[2] >> 2) & 0x3f) * s + b);
|
|
}
|
|
}
|
|
|
|
else if (bits == 8) {
|
|
for (int i = 0; i < N; i++) {
|
|
w_local[i] = static_cast<U>(s * w[i] + b);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
short BROWS,
|
|
short BCOLS,
|
|
short dst_ld,
|
|
short reduction_dim,
|
|
short tgp_size,
|
|
short group_size,
|
|
short bits>
|
|
struct QuantizedBlockLoader {
|
|
static_assert(
|
|
BCOLS <= group_size,
|
|
"The group size should be larger than the columns");
|
|
static_assert(
|
|
group_size % BCOLS == 0,
|
|
"The group size should be divisible by the columns");
|
|
static_assert(
|
|
bits == 2 || bits == 3 || bits == 4 || bits == 5 || bits == 6 ||
|
|
bits == 8,
|
|
"Template undefined for bits not in {2, 3, 4, 5, 6, 8}");
|
|
|
|
MLX_MTL_CONST short pack_factor = get_pack_factor<bits, 8>();
|
|
MLX_MTL_CONST short bytes_per_pack = get_bytes_per_pack<bits>();
|
|
MLX_MTL_CONST short BCOLS_PACKED = BCOLS / pack_factor;
|
|
MLX_MTL_CONST short n_reads =
|
|
(BCOLS_PACKED * BROWS < tgp_size) ? 1 : (BCOLS_PACKED * BROWS) / tgp_size;
|
|
MLX_MTL_CONST short group_steps = group_size / BCOLS;
|
|
|
|
const int src_ld;
|
|
const int tile_stride;
|
|
short group_step_cnt;
|
|
const int group_stride;
|
|
|
|
const short thread_idx;
|
|
const short bi;
|
|
const short bj;
|
|
|
|
threadgroup T* dst;
|
|
const device uint8_t* src;
|
|
const device T* scales;
|
|
const device T* biases;
|
|
|
|
QuantizedBlockLoader(
|
|
const device uint8_t* src_,
|
|
const device T* scales_,
|
|
const device T* biases_,
|
|
const int src_ld_,
|
|
threadgroup T* dst_,
|
|
ushort simd_group_id [[simdgroup_index_in_threadgroup]],
|
|
ushort simd_lane_id [[thread_index_in_simdgroup]]) thread
|
|
: src_ld(src_ld_),
|
|
tile_stride(
|
|
reduction_dim ? BCOLS_PACKED* bytes_per_pack
|
|
: BROWS * src_ld * bytes_per_pack / pack_factor),
|
|
group_step_cnt(0),
|
|
group_stride(BROWS* src_ld / group_size),
|
|
thread_idx(simd_group_id * 32 + simd_lane_id),
|
|
bi(n_reads* thread_idx / BCOLS_PACKED),
|
|
bj((n_reads * thread_idx) % BCOLS_PACKED),
|
|
dst(dst_ + bi * dst_ld + bj * pack_factor),
|
|
src(src_ + bi * src_ld * bytes_per_pack / pack_factor +
|
|
bj * bytes_per_pack),
|
|
scales(scales_ + bi * src_ld / group_size),
|
|
biases(biases_ + bi * src_ld / group_size) {}
|
|
|
|
void load_unsafe() const thread {
|
|
if (BCOLS_PACKED * BROWS < tgp_size && bi >= BROWS) {
|
|
return;
|
|
}
|
|
|
|
T scale = *scales;
|
|
T bias = *biases;
|
|
for (int i = 0; i < n_reads; i++) {
|
|
dequantize<T, pack_factor, bits>(
|
|
src + i * bytes_per_pack, scale, bias, dst + i * pack_factor);
|
|
}
|
|
}
|
|
|
|
void load_safe(short2 src_tile_dim) const thread {
|
|
if (BCOLS_PACKED * BROWS < tgp_size && bi >= BROWS) {
|
|
return;
|
|
}
|
|
|
|
if (reduction_dim == 1 && bi >= src_tile_dim.x) {
|
|
for (int i = 0; i < n_reads * pack_factor; i++) {
|
|
dst[i] = T(0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (reduction_dim == 0 && bi >= src_tile_dim.y) {
|
|
for (int i = 0; i < n_reads * pack_factor; i++) {
|
|
dst[i] = T(0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
T scale = *scales;
|
|
T bias = *biases;
|
|
for (int i = 0; i < n_reads; i++) {
|
|
dequantize<T, pack_factor, bits>(
|
|
(device uint8_t*)(src + i * bytes_per_pack),
|
|
scale,
|
|
bias,
|
|
dst + i * pack_factor);
|
|
}
|
|
}
|
|
|
|
void next() thread {
|
|
src += tile_stride;
|
|
if (reduction_dim == 1) {
|
|
if (group_steps > 1) {
|
|
group_step_cnt++;
|
|
if (group_step_cnt == group_steps) {
|
|
group_step_cnt = 0;
|
|
scales++;
|
|
biases++;
|
|
}
|
|
} else {
|
|
scales++;
|
|
biases++;
|
|
}
|
|
} else {
|
|
scales += group_stride;
|
|
biases += group_stride;
|
|
}
|
|
}
|
|
};
|
|
|
|
template <
|
|
typename T,
|
|
short BROWS,
|
|
short BCOLS,
|
|
short dst_ld,
|
|
short reduction_dim,
|
|
short tgp_size,
|
|
short bits>
|
|
struct QuantizedBlockLoader<
|
|
T,
|
|
BROWS,
|
|
BCOLS,
|
|
dst_ld,
|
|
reduction_dim,
|
|
tgp_size,
|
|
32,
|
|
bits> {
|
|
MLX_MTL_CONST short group_size = 32;
|
|
|
|
static_assert(
|
|
BCOLS % group_size == 0,
|
|
"The group size should be divisible by the columns");
|
|
static_assert(
|
|
bits == 2 || bits == 3 || bits == 4 || bits == 5 || bits == 6 ||
|
|
bits == 8,
|
|
"Template undefined for bits not in {2, 3, 4, 5, 6, 8}");
|
|
|
|
MLX_MTL_CONST short pack_factor = get_pack_factor<bits, 8>();
|
|
MLX_MTL_CONST short bytes_per_pack = get_bytes_per_pack<bits>();
|
|
MLX_MTL_CONST short BCOLS_PACKED = BCOLS / pack_factor;
|
|
MLX_MTL_CONST short n_reads =
|
|
(BCOLS_PACKED * BROWS < tgp_size) ? 1 : (BCOLS_PACKED * BROWS) / tgp_size;
|
|
MLX_MTL_CONST short n_groups = BCOLS / group_size;
|
|
|
|
static_assert(
|
|
(BCOLS_PACKED / n_reads) == n_groups,
|
|
"Other configurations are not yet supported");
|
|
|
|
const int src_ld;
|
|
const int tile_stride;
|
|
const int group_stride;
|
|
|
|
const short thread_idx;
|
|
const short bi;
|
|
const short bj;
|
|
|
|
const short group_id;
|
|
|
|
threadgroup T* dst;
|
|
const device uint8_t* src;
|
|
const device T* scales;
|
|
const device T* biases;
|
|
|
|
QuantizedBlockLoader(
|
|
const device uint8_t* src_,
|
|
const device T* scales_,
|
|
const device T* biases_,
|
|
const int src_ld_,
|
|
threadgroup T* dst_,
|
|
ushort simd_group_id [[simdgroup_index_in_threadgroup]],
|
|
ushort simd_lane_id [[thread_index_in_simdgroup]]) thread
|
|
: src_ld(src_ld_),
|
|
tile_stride(
|
|
reduction_dim ? BCOLS_PACKED* bytes_per_pack
|
|
: BROWS * src_ld * bytes_per_pack / pack_factor),
|
|
group_stride(BROWS* src_ld / group_size),
|
|
thread_idx(simd_group_id * 32 + simd_lane_id),
|
|
bi(n_reads* thread_idx / BCOLS_PACKED),
|
|
bj((n_reads * thread_idx) % BCOLS_PACKED),
|
|
group_id((bj * pack_factor) / group_size),
|
|
dst(dst_ + bi * dst_ld + bj * pack_factor),
|
|
src(src_ + bi * src_ld * bytes_per_pack / pack_factor +
|
|
bj * bytes_per_pack),
|
|
scales(scales_ + bi * src_ld / group_size + group_id),
|
|
biases(biases_ + bi * src_ld / group_size + group_id) {}
|
|
|
|
void load_unsafe() const thread {
|
|
if (BCOLS_PACKED * BROWS < tgp_size && bi >= BROWS) {
|
|
return;
|
|
}
|
|
|
|
T scale = *scales;
|
|
T bias = *biases;
|
|
for (int i = 0; i < n_reads; i++) {
|
|
dequantize<T, pack_factor, bits>(
|
|
src + i * bytes_per_pack, scale, bias, dst + i * pack_factor);
|
|
}
|
|
}
|
|
|
|
void load_safe(short2 src_tile_dim) const thread {
|
|
if (BCOLS_PACKED * BROWS < tgp_size && bi >= BROWS) {
|
|
return;
|
|
}
|
|
|
|
if (reduction_dim == 1 && bi >= src_tile_dim.x) {
|
|
for (int i = 0; i < n_reads * pack_factor; i++) {
|
|
dst[i] = T(0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (reduction_dim == 0 && bi >= src_tile_dim.y) {
|
|
for (int i = 0; i < n_reads * pack_factor; i++) {
|
|
dst[i] = T(0);
|
|
}
|
|
return;
|
|
}
|
|
|
|
T scale = *scales;
|
|
T bias = *biases;
|
|
for (int i = 0; i < n_reads; i++) {
|
|
dequantize<T, pack_factor, bits>(
|
|
(device uint8_t*)(src + i * bytes_per_pack),
|
|
scale,
|
|
bias,
|
|
dst + i * pack_factor);
|
|
}
|
|
}
|
|
|
|
void next() thread {
|
|
src += tile_stride;
|
|
if (reduction_dim == 1) {
|
|
// if (group_steps > 1) {
|
|
// group_step_cnt++;
|
|
// if (group_step_cnt == group_steps) {
|
|
// group_step_cnt = 0;
|
|
// scales++;
|
|
// biases++;
|
|
// }
|
|
// } else {
|
|
scales += n_groups;
|
|
biases += n_groups;
|
|
// }
|
|
} else {
|
|
scales += group_stride;
|
|
biases += group_stride;
|
|
}
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
METAL_FUNC void adjust_matrix_offsets(
|
|
const device T*& x,
|
|
const device uint32_t*& w,
|
|
const device T*& scales,
|
|
const device T*& biases,
|
|
device T*& y,
|
|
int output_stride,
|
|
const constant int& x_batch_ndims,
|
|
const constant int* x_shape,
|
|
const constant int64_t* x_strides,
|
|
const constant int& w_batch_ndims,
|
|
const constant int* w_shape,
|
|
const constant int64_t* w_strides,
|
|
const constant int64_t* s_strides,
|
|
const constant int64_t* b_strides,
|
|
uint3 tid [[threadgroup_position_in_grid]]) {
|
|
// Set the input/output matrices
|
|
uint32_t x_idx = tid.z;
|
|
uint32_t w_idx = tid.z;
|
|
if (x_batch_ndims == 1) {
|
|
x += x_idx * x_strides[0];
|
|
} else {
|
|
x += elem_to_loc(x_idx, x_shape, x_strides, x_batch_ndims);
|
|
}
|
|
if (w_batch_ndims == 1) {
|
|
w += w_idx * w_strides[0];
|
|
scales += w_idx * s_strides[0];
|
|
biases += w_idx * b_strides[0];
|
|
} else {
|
|
ulong3 idx = elem_to_loc_broadcast(
|
|
w_idx, w_shape, w_strides, s_strides, b_strides, w_batch_ndims);
|
|
w += idx.x;
|
|
scales += idx.y;
|
|
biases += idx.z;
|
|
}
|
|
y += tid.z * output_stride;
|
|
}
|
|
|
|
template <typename T>
|
|
METAL_FUNC void adjust_matrix_offsets(
|
|
const device T*& x,
|
|
const device uint32_t*& w,
|
|
const device T*& scales,
|
|
const device T*& biases,
|
|
const device uint32_t* lhs_indices,
|
|
const device uint32_t* rhs_indices,
|
|
device T*& y,
|
|
int output_stride,
|
|
const constant int& batch_ndims,
|
|
const constant int* batch_shape,
|
|
const constant int64_t* lhs_strides,
|
|
const constant int64_t* rhs_strides,
|
|
const constant int& x_batch_ndims,
|
|
const constant int* x_shape,
|
|
const constant int64_t* x_strides,
|
|
const constant int& w_batch_ndims,
|
|
const constant int* w_shape,
|
|
const constant int64_t* w_strides,
|
|
const constant int64_t* s_strides,
|
|
const constant int64_t* b_strides,
|
|
uint3 tid [[threadgroup_position_in_grid]]) {
|
|
// Set the input/output matrices
|
|
uint32_t x_idx;
|
|
uint32_t w_idx;
|
|
if (batch_ndims == 1) {
|
|
x_idx = lhs_indices[tid.z * lhs_strides[0]];
|
|
w_idx = rhs_indices[tid.z * rhs_strides[0]];
|
|
} else {
|
|
ulong2 idx = elem_to_loc_broadcast(
|
|
tid.z, batch_shape, lhs_strides, rhs_strides, batch_ndims);
|
|
x_idx = lhs_indices[idx.x];
|
|
w_idx = rhs_indices[idx.y];
|
|
}
|
|
if (x_batch_ndims == 1) {
|
|
x += x_idx * x_strides[0];
|
|
} else {
|
|
x += elem_to_loc(x_idx, x_shape, x_strides, x_batch_ndims);
|
|
}
|
|
if (w_batch_ndims == 1) {
|
|
w += w_idx * w_strides[0];
|
|
scales += w_idx * s_strides[0];
|
|
biases += w_idx * b_strides[0];
|
|
} else {
|
|
ulong3 idx = elem_to_loc_broadcast(
|
|
w_idx, w_shape, w_strides, s_strides, b_strides, w_batch_ndims);
|
|
w += idx.x;
|
|
scales += idx.y;
|
|
biases += idx.z;
|
|
}
|
|
y += tid.z * output_stride;
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
const int group_size,
|
|
const int bits,
|
|
const bool aligned_N,
|
|
const int BM = 64,
|
|
const int BK = 64,
|
|
const int BN = 64,
|
|
const int WM = 2,
|
|
const int WN = 2>
|
|
METAL_FUNC void qmm_t_nax_tgp_impl(
|
|
const device uint32_t* w,
|
|
const device T* scales,
|
|
const device T* biases,
|
|
const device T* x,
|
|
device T* y,
|
|
threadgroup T* Ws,
|
|
const constant int& K,
|
|
const constant int& N,
|
|
const constant int& M,
|
|
uint3 tid [[threadgroup_position_in_grid]],
|
|
uint lid [[thread_index_in_threadgroup]],
|
|
uint simd_gid [[simdgroup_index_in_threadgroup]],
|
|
uint simd_lid [[thread_index_in_simdgroup]]) {
|
|
static_assert(BK >= SIMD_SIZE, "BK should be larger than SIMD_SIZE");
|
|
static_assert(BK % SIMD_SIZE == 0, "BK should be divisible by SIMD_SIZE");
|
|
|
|
(void)lid;
|
|
|
|
constexpr int pack_factor = get_pack_factor<bits, 8>();
|
|
constexpr int bytes_per_pack = get_bytes_per_pack<bits>();
|
|
|
|
constexpr int BK_padded = (BK + 16 / sizeof(T));
|
|
|
|
using loader_w_t = QuantizedBlockLoader<
|
|
T,
|
|
BN,
|
|
BK,
|
|
BK_padded,
|
|
1,
|
|
WM * WN * SIMD_SIZE,
|
|
group_size,
|
|
bits>;
|
|
|
|
// Set the block
|
|
const int K_w = K * bytes_per_pack / pack_factor;
|
|
const int K_g = K / group_size;
|
|
const int y_row = tid.y * BM;
|
|
const int y_col = tid.x * BN;
|
|
|
|
auto wl = (const device uint8_t*)w;
|
|
|
|
x += y_row * static_cast<int64_t>(K);
|
|
wl += y_col * K_w;
|
|
scales += y_col * K_g;
|
|
biases += y_col * K_g;
|
|
y += y_row * static_cast<int64_t>(N) + y_col;
|
|
|
|
// Make the weight loader
|
|
loader_w_t loader_w(wl, scales, biases, K, Ws, simd_gid, simd_lid);
|
|
|
|
constexpr short SM = BM / WM;
|
|
constexpr short SN = BN / WN;
|
|
constexpr short SK = 32;
|
|
|
|
constexpr short TM = SM / 16;
|
|
constexpr short TN = SN / 16;
|
|
constexpr short TK = SK / 16;
|
|
|
|
const short tm = SM * (simd_gid / WN);
|
|
const short tn = SN * (simd_gid % WN);
|
|
|
|
constexpr bool transpose_a = false;
|
|
constexpr bool transpose_b = true;
|
|
|
|
const short sgp_sm = min(int(SM), M - (y_row + tm));
|
|
const bool is_unaligned_sm = (sgp_sm != SM);
|
|
|
|
const short sgp_sn = aligned_N ? SN : min(int(SN), N - (y_col + tn));
|
|
|
|
const short tgp_bn = aligned_N ? BN : min(BN, int(N - (y_col)));
|
|
const bool is_unaligned_bn = aligned_N ? false : (tgp_bn != BN);
|
|
|
|
using AccumType = float;
|
|
|
|
NAXTile<AccumType, TM, TN> Dtile;
|
|
Dtile.clear();
|
|
|
|
x += tm * K;
|
|
|
|
dispatch_bool(!is_unaligned_sm, [&](auto kAlignedM) {
|
|
dispatch_bool(aligned_N || !is_unaligned_bn, [&](auto kAlignedN) {
|
|
for (int k = 0; k < K; k += BK) {
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
if constexpr (kAlignedN.value) {
|
|
loader_w.load_unsafe();
|
|
} else {
|
|
loader_w.load_safe(short2(BK, tgp_bn));
|
|
}
|
|
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
|
|
STEEL_PRAGMA_NO_UNROLL
|
|
for (int kk1 = 0; kk1 < BK; kk1 += SK) {
|
|
NAXTile<T, TM, TK> Atile;
|
|
NAXTile<T, TN, TK> Btile;
|
|
|
|
volatile int compiler_barrier;
|
|
|
|
if constexpr (kAlignedM.value) {
|
|
Atile.load(x + kk1, K);
|
|
} else {
|
|
Atile.load_safe(x + kk1, K, short2(SK, sgp_sm));
|
|
}
|
|
|
|
Btile.template load<T, BK_padded, 1>(Ws + tn * BK_padded + kk1);
|
|
|
|
tile_matmad_nax(
|
|
Dtile,
|
|
Atile,
|
|
metal::bool_constant<transpose_a>{},
|
|
Btile,
|
|
metal::bool_constant<transpose_b>{});
|
|
|
|
(void)compiler_barrier;
|
|
}
|
|
|
|
x += BK;
|
|
loader_w.next();
|
|
}
|
|
|
|
// Store results to device memory
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
|
|
if constexpr (kAlignedM.value && kAlignedN.value) {
|
|
Dtile.store(y + tm * N + tn, N);
|
|
} else if (kAlignedM.value && sgp_sn == SN) {
|
|
Dtile.store(y + tm * N + tn, N);
|
|
} else {
|
|
Dtile.store_safe(y + tm * N + tn, N, short2(sgp_sn, sgp_sm));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
const int group_size,
|
|
const int bits,
|
|
const int BM = 64,
|
|
const int BK = 64,
|
|
const int BN = 64,
|
|
const int WM = 2,
|
|
const int WN = 2>
|
|
METAL_FUNC void qmm_n_nax_tgp_impl(
|
|
const device uint32_t* w,
|
|
const device T* scales,
|
|
const device T* biases,
|
|
const device T* x,
|
|
device T* y,
|
|
threadgroup T* Ws,
|
|
const constant int& K,
|
|
const constant int& N,
|
|
const constant int& M,
|
|
uint3 tid [[threadgroup_position_in_grid]],
|
|
uint lid [[thread_index_in_threadgroup]],
|
|
uint simd_gid [[simdgroup_index_in_threadgroup]],
|
|
uint simd_lid [[thread_index_in_simdgroup]]) {
|
|
(void)lid;
|
|
|
|
static_assert(BK >= SIMD_SIZE, "BK should be larger than SIMD_SIZE");
|
|
static_assert(BK % SIMD_SIZE == 0, "BK should be divisible by SIMD_SIZE");
|
|
|
|
constexpr int pack_factor = get_pack_factor<bits, 8>();
|
|
constexpr int bytes_per_pack = get_bytes_per_pack<bits>();
|
|
|
|
constexpr int BN_padded = (BN + 16 / sizeof(T));
|
|
|
|
using loader_w_t = QuantizedBlockLoader<
|
|
T,
|
|
BK,
|
|
BN,
|
|
BN_padded,
|
|
0,
|
|
WM * WN * SIMD_SIZE,
|
|
group_size,
|
|
bits>;
|
|
|
|
// Set the block
|
|
const int y_row = tid.y * BM;
|
|
const int y_col = tid.x * BN;
|
|
|
|
auto wl = (const device uint8_t*)w;
|
|
|
|
// Here w is [K, N]: packed and group-quantized along N, with row stride N.
|
|
x += y_row * static_cast<int64_t>(K);
|
|
wl += y_col * bytes_per_pack / pack_factor;
|
|
scales += y_col / group_size;
|
|
biases += y_col / group_size;
|
|
y += y_row * static_cast<int64_t>(N) + y_col;
|
|
|
|
// Make the weight loader
|
|
loader_w_t loader_w(wl, scales, biases, N, Ws, simd_gid, simd_lid);
|
|
|
|
constexpr short SM = BM / WM;
|
|
constexpr short SN = BN / WN;
|
|
constexpr short SK = 32;
|
|
|
|
constexpr short TM = SM / 16;
|
|
constexpr short TN = SN / 16;
|
|
constexpr short TK = SK / 16;
|
|
|
|
const short tm = SM * (simd_gid / WN);
|
|
const short tn = SN * (simd_gid % WN);
|
|
|
|
const short sgp_sm = min(int(SM), M - (y_row + tm));
|
|
|
|
const short ldb_tgp = BN_padded;
|
|
|
|
constexpr bool transpose_a = false;
|
|
constexpr bool transpose_b = false;
|
|
|
|
using AccumType = float;
|
|
|
|
NAXTile<AccumType, TM, TN> Dtile;
|
|
Dtile.clear();
|
|
|
|
x += tm * K;
|
|
|
|
for (int k = 0; k < K; k += BK) {
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
loader_w.load_unsafe();
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
|
|
STEEL_PRAGMA_NO_UNROLL
|
|
for (int kk1 = 0; kk1 < BK; kk1 += SK) {
|
|
NAXTile<T, TM, TK> Atile;
|
|
NAXTile<T, TK, TN> Btile;
|
|
|
|
volatile int compiler_barrier;
|
|
|
|
if (sgp_sm == SM) {
|
|
Atile.load(x + kk1, K);
|
|
} else {
|
|
Atile.load_safe(x + kk1, K, short2(SK, sgp_sm));
|
|
}
|
|
|
|
Btile.template load<T, BN_padded, 1>(Ws + tn + kk1 * ldb_tgp);
|
|
|
|
tile_matmad_nax(
|
|
Dtile,
|
|
Atile,
|
|
metal::bool_constant<transpose_a>{},
|
|
Btile,
|
|
metal::bool_constant<transpose_b>{});
|
|
|
|
(void)compiler_barrier;
|
|
}
|
|
|
|
x += BK;
|
|
loader_w.next();
|
|
}
|
|
|
|
// Store results to device memory
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
|
|
if (sgp_sm == SM) {
|
|
Dtile.store(y + tm * N + tn, N);
|
|
} else {
|
|
Dtile.store_safe(y + tm * N + tn, N, short2(SN, sgp_sm));
|
|
}
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
const int group_size,
|
|
const int bits,
|
|
const bool aligned_N,
|
|
const bool batched,
|
|
const int BM = 64,
|
|
const int BK = 32,
|
|
const int BN = 64,
|
|
const int WM = 2,
|
|
const int WN = 2>
|
|
[[kernel]] void affine_qmm_t_nax(
|
|
const device uint32_t* w [[buffer(0)]],
|
|
const device T* scales [[buffer(1)]],
|
|
const device T* biases [[buffer(2)]],
|
|
const device T* x [[buffer(3)]],
|
|
device T* y [[buffer(4)]],
|
|
const constant int& K [[buffer(5)]],
|
|
const constant int& N [[buffer(6)]],
|
|
const constant int& M [[buffer(7)]],
|
|
const constant int& x_batch_ndims [[buffer(8)]],
|
|
const constant int* x_shape [[buffer(9)]],
|
|
const constant int64_t* x_strides [[buffer(10)]],
|
|
const constant int& w_batch_ndims [[buffer(11)]],
|
|
const constant int* w_shape [[buffer(12)]],
|
|
const constant int64_t* w_strides [[buffer(13)]],
|
|
const constant int64_t* s_strides [[buffer(14)]],
|
|
const constant int64_t* b_strides [[buffer(15)]],
|
|
uint3 tid [[threadgroup_position_in_grid]],
|
|
uint lid [[thread_index_in_threadgroup]],
|
|
uint simd_gid [[simdgroup_index_in_threadgroup]],
|
|
uint simd_lid [[thread_index_in_simdgroup]]) {
|
|
(void)lid;
|
|
|
|
constexpr int BK_padded = (BK + 16 / sizeof(T));
|
|
|
|
threadgroup T Ws[BN * BK_padded];
|
|
|
|
if (batched) {
|
|
adjust_matrix_offsets<T>(
|
|
x,
|
|
w,
|
|
scales,
|
|
biases,
|
|
y,
|
|
M * N,
|
|
x_batch_ndims,
|
|
x_shape,
|
|
x_strides,
|
|
w_batch_ndims,
|
|
w_shape,
|
|
w_strides,
|
|
s_strides,
|
|
b_strides,
|
|
tid);
|
|
}
|
|
qmm_t_nax_tgp_impl<T, group_size, bits, aligned_N, BM, BK, BN, WM, WN>(
|
|
w, scales, biases, x, y, Ws, K, N, M, tid, lid, simd_gid, simd_lid);
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
const int group_size,
|
|
const int bits,
|
|
const bool batched,
|
|
const int BM = 64,
|
|
const int BK = 64,
|
|
const int BN = 64,
|
|
const int WM = 2,
|
|
const int WN = 2>
|
|
[[kernel]] void affine_qmm_n_nax(
|
|
const device uint32_t* w [[buffer(0)]],
|
|
const device T* scales [[buffer(1)]],
|
|
const device T* biases [[buffer(2)]],
|
|
const device T* x [[buffer(3)]],
|
|
device T* y [[buffer(4)]],
|
|
const constant int& K [[buffer(5)]],
|
|
const constant int& N [[buffer(6)]],
|
|
const constant int& M [[buffer(7)]],
|
|
const constant int& x_batch_ndims [[buffer(8)]],
|
|
const constant int* x_shape [[buffer(9)]],
|
|
const constant int64_t* x_strides [[buffer(10)]],
|
|
const constant int& w_batch_ndims [[buffer(11)]],
|
|
const constant int* w_shape [[buffer(12)]],
|
|
const constant int64_t* w_strides [[buffer(13)]],
|
|
const constant int64_t* s_strides [[buffer(14)]],
|
|
const constant int64_t* b_strides [[buffer(15)]],
|
|
uint3 tid [[threadgroup_position_in_grid]],
|
|
uint lid [[thread_index_in_threadgroup]],
|
|
uint simd_gid [[simdgroup_index_in_threadgroup]],
|
|
uint simd_lid [[thread_index_in_simdgroup]]) {
|
|
(void)lid;
|
|
|
|
constexpr int BN_padded = (BN + 16 / sizeof(T));
|
|
|
|
threadgroup T Ws[BK * BN_padded];
|
|
|
|
if (batched) {
|
|
adjust_matrix_offsets<T>(
|
|
x,
|
|
w,
|
|
scales,
|
|
biases,
|
|
y,
|
|
M * N,
|
|
x_batch_ndims,
|
|
x_shape,
|
|
x_strides,
|
|
w_batch_ndims,
|
|
w_shape,
|
|
w_strides,
|
|
s_strides,
|
|
b_strides,
|
|
tid);
|
|
}
|
|
|
|
qmm_n_nax_tgp_impl<T, group_size, bits, BM, BK, BN, WM, WN>(
|
|
w, scales, biases, x, y, Ws, K, N, M, tid, lid, simd_gid, simd_lid);
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
const int group_size,
|
|
const int bits,
|
|
const bool aligned_N,
|
|
const int BM = 64,
|
|
const int BK = 64,
|
|
const int BN = 64,
|
|
const int WM = 2,
|
|
const int WN = 2>
|
|
[[kernel]] void affine_gather_qmm_t_nax(
|
|
const device uint32_t* w [[buffer(0)]],
|
|
const device T* scales [[buffer(1)]],
|
|
const device T* biases [[buffer(2)]],
|
|
const device T* x [[buffer(3)]],
|
|
const device uint32_t* lhs_indices [[buffer(4)]],
|
|
const device uint32_t* rhs_indices [[buffer(5)]],
|
|
device T* y [[buffer(6)]],
|
|
const constant int& K [[buffer(7)]],
|
|
const constant int& N [[buffer(8)]],
|
|
const constant int& M [[buffer(9)]],
|
|
const constant int& x_batch_ndims [[buffer(10)]],
|
|
const constant int* x_shape [[buffer(11)]],
|
|
const constant int64_t* x_strides [[buffer(12)]],
|
|
const constant int& w_batch_ndims [[buffer(13)]],
|
|
const constant int* w_shape [[buffer(14)]],
|
|
const constant int64_t* w_strides [[buffer(15)]],
|
|
const constant int64_t* s_strides [[buffer(16)]],
|
|
const constant int64_t* b_strides [[buffer(17)]],
|
|
const constant int& batch_ndims [[buffer(18)]],
|
|
const constant int* batch_shape [[buffer(19)]],
|
|
const constant int64_t* lhs_strides [[buffer(20)]],
|
|
const constant int64_t* rhs_strides [[buffer(21)]],
|
|
uint3 tid [[threadgroup_position_in_grid]],
|
|
uint lid [[thread_index_in_threadgroup]],
|
|
uint simd_gid [[simdgroup_index_in_threadgroup]],
|
|
uint simd_lid [[thread_index_in_simdgroup]]) {
|
|
(void)lid;
|
|
|
|
constexpr int BK_padded = (BK + 16 / sizeof(T));
|
|
|
|
threadgroup T Ws[BN * BK_padded];
|
|
|
|
adjust_matrix_offsets<T>(
|
|
x,
|
|
w,
|
|
scales,
|
|
biases,
|
|
lhs_indices,
|
|
rhs_indices,
|
|
y,
|
|
M * N,
|
|
batch_ndims,
|
|
batch_shape,
|
|
lhs_strides,
|
|
rhs_strides,
|
|
x_batch_ndims,
|
|
x_shape,
|
|
x_strides,
|
|
w_batch_ndims,
|
|
w_shape,
|
|
w_strides,
|
|
s_strides,
|
|
b_strides,
|
|
tid);
|
|
qmm_t_nax_tgp_impl<T, group_size, bits, aligned_N, BM, BK, BN, WM, WN>(
|
|
w, scales, biases, x, y, Ws, K, N, M, tid, lid, simd_gid, simd_lid);
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
const int group_size,
|
|
const int bits,
|
|
const int BM = 64,
|
|
const int BK = 64,
|
|
const int BN = 64,
|
|
const int WM = 2,
|
|
const int WN = 2>
|
|
[[kernel]] void affine_gather_qmm_n_nax(
|
|
const device uint32_t* w [[buffer(0)]],
|
|
const device T* scales [[buffer(1)]],
|
|
const device T* biases [[buffer(2)]],
|
|
const device T* x [[buffer(3)]],
|
|
const device uint32_t* lhs_indices [[buffer(4)]],
|
|
const device uint32_t* rhs_indices [[buffer(5)]],
|
|
device T* y [[buffer(6)]],
|
|
const constant int& K [[buffer(7)]],
|
|
const constant int& N [[buffer(8)]],
|
|
const constant int& M [[buffer(9)]],
|
|
const constant int& x_batch_ndims [[buffer(10)]],
|
|
const constant int* x_shape [[buffer(11)]],
|
|
const constant int64_t* x_strides [[buffer(12)]],
|
|
const constant int& w_batch_ndims [[buffer(13)]],
|
|
const constant int* w_shape [[buffer(14)]],
|
|
const constant int64_t* w_strides [[buffer(15)]],
|
|
const constant int64_t* s_strides [[buffer(16)]],
|
|
const constant int64_t* b_strides [[buffer(17)]],
|
|
const constant int& batch_ndims [[buffer(18)]],
|
|
const constant int* batch_shape [[buffer(19)]],
|
|
const constant int64_t* lhs_strides [[buffer(20)]],
|
|
const constant int64_t* rhs_strides [[buffer(21)]],
|
|
uint3 tid [[threadgroup_position_in_grid]],
|
|
uint lid [[thread_index_in_threadgroup]],
|
|
uint simd_gid [[simdgroup_index_in_threadgroup]],
|
|
uint simd_lid [[thread_index_in_simdgroup]]) {
|
|
(void)lid;
|
|
|
|
constexpr int BN_padded = (BN + 16 / sizeof(T));
|
|
|
|
threadgroup T Ws[BK * BN_padded];
|
|
|
|
adjust_matrix_offsets<T>(
|
|
x,
|
|
w,
|
|
scales,
|
|
biases,
|
|
lhs_indices,
|
|
rhs_indices,
|
|
y,
|
|
M * N,
|
|
batch_ndims,
|
|
batch_shape,
|
|
lhs_strides,
|
|
rhs_strides,
|
|
x_batch_ndims,
|
|
x_shape,
|
|
x_strides,
|
|
w_batch_ndims,
|
|
w_shape,
|
|
w_strides,
|
|
s_strides,
|
|
b_strides,
|
|
tid);
|
|
qmm_n_nax_tgp_impl<T, group_size, bits, BM, BK, BN, WM, WN>(
|
|
w, scales, biases, x, y, Ws, K, N, M, tid, lid, simd_gid, simd_lid);
|
|
}
|
|
|
|
template <
|
|
typename T,
|
|
int group_size,
|
|
int bits,
|
|
int BM,
|
|
int BN,
|
|
int BK,
|
|
int WM,
|
|
int WN,
|
|
bool transpose>
|
|
[[kernel]] void affine_gather_qmm_rhs_nax(
|
|
const device T* x [[buffer(0)]],
|
|
const device uint32_t* w [[buffer(1)]],
|
|
const device T* scales [[buffer(2)]],
|
|
const device T* biases [[buffer(3)]],
|
|
const device uint32_t* indices [[buffer(4)]],
|
|
device T* y [[buffer(5)]],
|
|
const constant int& M [[buffer(6)]],
|
|
const constant int& N [[buffer(7)]],
|
|
const constant int& K [[buffer(8)]],
|
|
uint3 tid [[threadgroup_position_in_grid]],
|
|
uint simd_group_id [[simdgroup_index_in_threadgroup]],
|
|
uint simd_lane_id [[thread_index_in_simdgroup]]) {
|
|
constexpr int pack_factor = get_pack_factor<bits, 8>();
|
|
constexpr int bytes_per_pack = get_bytes_per_pack<bits>();
|
|
constexpr int BK_padded = (BK + 16 / sizeof(T));
|
|
constexpr int BN_padded = (BN + 16 / sizeof(T));
|
|
|
|
using loader_w_t = QuantizedBlockLoader<
|
|
T,
|
|
transpose ? BN : BK,
|
|
transpose ? BK : BN,
|
|
transpose ? BK_padded : BN_padded,
|
|
transpose,
|
|
WM * WN * SIMD_SIZE,
|
|
group_size,
|
|
bits>;
|
|
|
|
threadgroup T Ws[transpose ? BN * BK_padded : BK * BN_padded];
|
|
|
|
// Compute the block
|
|
const int K_w = K * bytes_per_pack / pack_factor;
|
|
const int K_g = K / group_size;
|
|
const int N_w = N * bytes_per_pack / pack_factor;
|
|
const int N_g = N / group_size;
|
|
const int K_it = K / BK;
|
|
const size_t stride_w = transpose ? N * K_w : K * N_w;
|
|
const size_t stride_s = transpose ? N * K_g : K * N_g;
|
|
const int y_row = tid.y * BM;
|
|
const int y_col = tid.x * BN;
|
|
const size_t y_row_long = size_t(y_row);
|
|
const size_t y_col_long = size_t(y_col);
|
|
|
|
// Prepare threadgroup bounds
|
|
const short tgp_bm = align_M ? BM : short(min(BM, M - y_row));
|
|
const short tgp_bn = align_N ? BN : short(min(BN, N - y_col));
|
|
|
|
// Calculate the final tiles in the case that K is not aligned
|
|
const int k_remain = K - K_it * BK;
|
|
const short2 tile_w =
|
|
transpose ? short2(k_remain, tgp_bn) : short2(tgp_bn, k_remain);
|
|
|
|
// Move x and output to the correct block
|
|
auto wl = (const device uint8_t*)w;
|
|
x += y_row_long * K;
|
|
y += y_row_long * N + y_col_long;
|
|
wl += transpose ? y_col_long * K_w : y_col * bytes_per_pack / pack_factor;
|
|
scales += transpose ? y_col_long * K_g : y_col / group_size;
|
|
biases += transpose ? y_col_long * K_g : y_col / group_size;
|
|
|
|
constexpr short SM = BM / WM;
|
|
constexpr short SN = BN / WN;
|
|
constexpr short SK = 32;
|
|
|
|
constexpr short TM = SM / 16;
|
|
constexpr short TN = SN / 16;
|
|
constexpr short TK = SK / 16;
|
|
|
|
const short tm = SM * (simd_group_id / WN);
|
|
const short tn = SN * (simd_group_id % WN);
|
|
|
|
const short sgp_sm =
|
|
align_M ? SM : min(SM, short(max(0, (M - (y_row + tm)))));
|
|
const short sgp_sn =
|
|
align_N ? SN : min(SN, short(max(0, (N - (y_col + tn)))));
|
|
|
|
const bool is_unaligned_sm = align_M ? false : (sgp_sm != SM);
|
|
const bool is_unaligned_bn = align_N ? false : (tgp_bn != BN);
|
|
|
|
constexpr short BR = transpose ? TN : TK;
|
|
constexpr short BC = transpose ? TK : TN;
|
|
|
|
using AccumType = float;
|
|
|
|
// Do as many matmuls as necessary
|
|
uint32_t index;
|
|
short offset;
|
|
uint32_t index_next = indices[y_row];
|
|
short offset_next = 0;
|
|
int n = 0;
|
|
while (n < tgp_bm) {
|
|
n++;
|
|
offset = offset_next;
|
|
index = index_next;
|
|
offset_next = tgp_bm;
|
|
for (; n < tgp_bm; n++) {
|
|
if (indices[y_row + n] != index) {
|
|
offset_next = n;
|
|
index_next = indices[y_row + n];
|
|
break;
|
|
}
|
|
}
|
|
threadgroup_barrier(mem_flags::mem_none);
|
|
|
|
const short m_lo_lim = min(int(sgp_sm), max(0, offset - tm));
|
|
const short m_hi_lim = min(int(sgp_sm), max(0, offset_next - tm));
|
|
const bool sg_active = m_hi_lim > m_lo_lim;
|
|
|
|
NAXTile<AccumType, TM, TN> Dtile;
|
|
Dtile.clear();
|
|
|
|
const device T* xn = x + tm * K;
|
|
|
|
// Prepare threadgroup loading operations
|
|
thread loader_w_t loader_w(
|
|
wl + index * stride_w,
|
|
scales + index * stride_s,
|
|
biases + index * stride_s,
|
|
transpose ? K : N,
|
|
Ws,
|
|
simd_group_id,
|
|
simd_lane_id);
|
|
|
|
dispatch_bool(align_M || !is_unaligned_sm, [&](auto kAlignedM) {
|
|
dispatch_bool(align_N || !is_unaligned_bn, [&](auto kAlignedN) {
|
|
for (int k = 0; k < K_it; k++) {
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
if constexpr (kAlignedN.value) {
|
|
loader_w.load_unsafe();
|
|
} else {
|
|
loader_w.load_safe(
|
|
transpose ? short2(BK, tgp_bn) : short2(tgp_bn, BK));
|
|
}
|
|
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
|
|
STEEL_PRAGMA_NO_UNROLL
|
|
for (int kk1 = 0; kk1 < BK; kk1 += SK) {
|
|
if (sg_active) {
|
|
NAXTile<T, TM, TK> Atile;
|
|
NAXTile<T, BR, BC> Btile;
|
|
|
|
volatile int compiler_barrier;
|
|
|
|
if constexpr (kAlignedM.value) {
|
|
Atile.load(xn + kk1, K);
|
|
} else {
|
|
Atile.load_safe(xn + kk1, K, short2(SK, sgp_sm));
|
|
}
|
|
|
|
if constexpr (transpose) {
|
|
Btile.template load<T, BK_padded, 1>(Ws + tn * BK_padded + kk1);
|
|
} else {
|
|
Btile.template load<T, BN_padded, 1>(Ws + tn + kk1 * BN_padded);
|
|
}
|
|
|
|
tile_matmad_nax(
|
|
Dtile,
|
|
Atile,
|
|
metal::bool_constant<false>{},
|
|
Btile,
|
|
metal::bool_constant<transpose>{});
|
|
|
|
(void)compiler_barrier;
|
|
}
|
|
}
|
|
|
|
xn += BK;
|
|
loader_w.next();
|
|
}
|
|
|
|
if (!align_K) {
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
loader_w.load_safe(tile_w);
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
|
|
STEEL_PRAGMA_NO_UNROLL
|
|
for (int kk1 = 0; kk1 < BK; kk1 += SK) {
|
|
if (sg_active) {
|
|
NAXTile<T, TM, TK> Atile;
|
|
NAXTile<T, BR, BC> Btile;
|
|
|
|
volatile int compiler_barrier;
|
|
|
|
const short psk = min(int(SK), max(0, (BK - kk1)));
|
|
Atile.load_safe(xn + kk1, K, short2(psk, sgp_sm));
|
|
|
|
if constexpr (transpose) {
|
|
Btile.template load<T, BK_padded, 1>(Ws + tn * BK_padded + kk1);
|
|
} else {
|
|
Btile.template load<T, BN_padded, 1>(Ws + tn + kk1 * BN_padded);
|
|
}
|
|
|
|
tile_matmad_nax(
|
|
Dtile,
|
|
Atile,
|
|
metal::bool_constant<false>{},
|
|
Btile,
|
|
metal::bool_constant<transpose>{});
|
|
|
|
(void)compiler_barrier;
|
|
}
|
|
}
|
|
}
|
|
|
|
threadgroup_barrier(mem_flags::mem_threadgroup);
|
|
|
|
// Store results to device memory
|
|
if constexpr (kAlignedN.value) {
|
|
if (m_lo_lim == 0 && m_hi_lim == SM) {
|
|
Dtile.store(y + tm * N + tn, N);
|
|
} else {
|
|
Dtile.store_slice(
|
|
y + tm * N + tn, N, short2(0, m_lo_lim), short2(SN, m_hi_lim));
|
|
}
|
|
} else {
|
|
Dtile.store_slice(
|
|
y + tm * N + tn,
|
|
N,
|
|
short2(0, m_lo_lim),
|
|
short2(sgp_sn, m_hi_lim));
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
struct Sigmoid {
|
|
template <typename T>
|
|
T operator()(T x) thread {
|
|
auto y = 1 / (1 + metal::exp(metal::abs(x)));
|
|
return (x < 0) ? y : 1 - y;
|
|
}
|
|
};
|
|
struct Multiply {
|
|
template <typename T>
|
|
T operator()(T x, T y) thread {
|
|
return x * y;
|
|
}
|
|
};
|
|
using namespace mlx::steel;
|