-
Notifications
You must be signed in to change notification settings - Fork 2k
[CUDA] Use GEMM with epilogue instead of AddMM #2569
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,6 +11,7 @@ | |
| #include <numeric> | ||
|
|
||
| namespace mlx::core { | ||
|
|
||
| namespace { | ||
|
|
||
| std::tuple<bool, int64_t, array> | ||
|
|
@@ -28,41 +29,20 @@ check_transpose(cu::CommandEncoder& enc, const Stream& s, const array& arr) { | |
| } | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| void Matmul::eval_gpu(const std::vector<array>& inputs, array& out) { | ||
| nvtx3::scoped_range r("Matmul::eval_gpu"); | ||
| auto& s = stream(); | ||
| auto& encoder = cu::get_command_encoder(s); | ||
|
|
||
| assert(inputs.size() == 2); | ||
| auto& a_pre = inputs[0]; | ||
| auto& b_pre = inputs[1]; | ||
| // Return 0s if either input is empty. | ||
| if (a_pre.size() == 0 || b_pre.size() == 0) { | ||
| array zero(0, a_pre.dtype()); | ||
| encoder.add_temporary(zero); | ||
| fill_gpu(zero, out, s); | ||
| return; | ||
| } | ||
|
|
||
| out.set_data(allocator::malloc(out.nbytes())); | ||
|
|
||
| ///////////////////////////////////////////////////////////////////////////// | ||
| // Init checks and prep | ||
|
|
||
| int M = a_pre.shape(-2); | ||
| int N = b_pre.shape(-1); | ||
| int K = a_pre.shape(-1); | ||
|
|
||
| // Keep a vector with copies to be cleared in the completed buffer to release | ||
| // the arrays | ||
| auto [a_transposed, lda, a] = check_transpose(encoder, s, a_pre); | ||
| auto [b_transposed, ldb, b] = check_transpose(encoder, s, b_pre); | ||
|
|
||
| ///////////////////////////////////////////////////////////////////////////// | ||
| void gemm_and_bias( | ||
| cu::CommandEncoder& encoder, | ||
| int M, | ||
| int N, | ||
| int K, | ||
| bool a_transposed, | ||
| int64_t lda, | ||
| bool b_transposed, | ||
| int64_t ldb, | ||
| array& out, | ||
| const array& a, | ||
| const array& b, | ||
| void* bias = nullptr) { | ||
| // Check and collapse batch dimensions | ||
|
|
||
| auto [batch_shape, a_batch_strides, b_batch_strides] = collapse_batches(a, b); | ||
|
|
||
| auto batch_count = out.size() / (M * N); | ||
|
|
@@ -79,7 +59,8 @@ void Matmul::eval_gpu(const std::vector<array>& inputs, array& out) { | |
| batch_shape = {1}; | ||
| } | ||
|
|
||
| if (cu::can_use_gemv(M, N, K, a_transposed, b_transposed)) { | ||
| // Use gemmv when possible | ||
| if (!bias && cu::can_use_gemv(M, N, K, a_transposed, b_transposed)) { | ||
| cu::gemv( | ||
| a, | ||
| b, | ||
|
|
@@ -95,10 +76,9 @@ void Matmul::eval_gpu(const std::vector<array>& inputs, array& out) { | |
| return; | ||
| } | ||
|
|
||
| ///////////////////////////////////////////////////////////////////////////// | ||
| // Invoke cublasLt | ||
| CublasGemm gemm( | ||
| cu::device(s.device), | ||
| encoder.device(), | ||
| a.dtype(), | ||
| a_transposed, | ||
| M, | ||
|
|
@@ -111,9 +91,45 @@ void Matmul::eval_gpu(const std::vector<array>& inputs, array& out) { | |
| batch_shape.back(), | ||
| a_batch_strides.back(), | ||
| b_batch_strides.back()); | ||
| if (bias) { | ||
| gemm.set_bias(bias); | ||
| } | ||
| gemm.run(encoder, out, a, b, batch_shape, a_batch_strides, b_batch_strides); | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| void Matmul::eval_gpu(const std::vector<array>& inputs, array& out) { | ||
| nvtx3::scoped_range r("Matmul::eval_gpu"); | ||
| auto& s = stream(); | ||
| auto& encoder = cu::get_command_encoder(s); | ||
|
|
||
| assert(inputs.size() == 2); | ||
| auto& a_pre = inputs[0]; | ||
| auto& b_pre = inputs[1]; | ||
| // Return 0s if either input is empty. | ||
| if (a_pre.size() == 0 || b_pre.size() == 0) { | ||
| array zero(0, a_pre.dtype()); | ||
| encoder.add_temporary(zero); | ||
| fill_gpu(zero, out, s); | ||
| return; | ||
| } | ||
|
|
||
| out.set_data(allocator::malloc(out.nbytes())); | ||
|
|
||
| int M = a_pre.shape(-2); | ||
| int N = b_pre.shape(-1); | ||
| int K = a_pre.shape(-1); | ||
|
|
||
| // Keep a vector with copies to be cleared in the completed buffer to release | ||
| // the arrays | ||
| auto [a_transposed, lda, a] = check_transpose(encoder, s, a_pre); | ||
| auto [b_transposed, ldb, b] = check_transpose(encoder, s, b_pre); | ||
|
|
||
| gemm_and_bias( | ||
| encoder, M, N, K, a_transposed, lda, b_transposed, ldb, out, a, b); | ||
| } | ||
|
|
||
| void AddMM::eval_gpu(const std::vector<array>& inputs, array& out) { | ||
| nvtx3::scoped_range r("AddMM::eval_gpu"); | ||
| auto& s = stream(); | ||
|
|
@@ -136,6 +152,27 @@ void AddMM::eval_gpu(const std::vector<array>& inputs, array& out) { | |
| auto [a_transposed, lda, a] = check_transpose(encoder, s, a_pre); | ||
| auto [b_transposed, ldb, b] = check_transpose(encoder, s, b_pre); | ||
|
|
||
| ///////////////////////////////////////////////////////////////////////////// | ||
| // Dispatch to GEMM with epilogue or AddMM | ||
|
|
||
| if (beta_ == 1 && c.strides(-1) == 1 && c.data_size() == out.shape(-1)) { | ||
| out.set_data(allocator::malloc(out.nbytes())); | ||
| gemm_and_bias( | ||
| encoder, | ||
| M, | ||
| N, | ||
| K, | ||
| a_transposed, | ||
| lda, | ||
| b_transposed, | ||
| ldb, | ||
| out, | ||
| a, | ||
| b, | ||
| c.data<void>()); | ||
| return; | ||
|
Comment on lines
+158
to
+173
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we simply setup the right cublas matmul without adding a
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The epilogue bias requires the input to be a vector of size |
||
| } | ||
|
|
||
| int64_t ldc; | ||
| { | ||
| auto stx = c.strides()[c.ndim() - 2]; | ||
|
|
@@ -177,7 +214,7 @@ void AddMM::eval_gpu(const std::vector<array>& inputs, array& out) { | |
| } | ||
|
|
||
| ///////////////////////////////////////////////////////////////////////////// | ||
| // Invoke cublasLt | ||
| // Invoke cublasLt with AddMM settings | ||
|
|
||
| CublasGemm gemm( | ||
| cu::device(s.device), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I missing something here or is the return statement removed a bug?
It looks like it will encode the gemv and the full gemm right now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was a mistake, thanks for noticing it!