-
Notifications
You must be signed in to change notification settings - Fork 26.3k
Enable a fast path for (static) qlinear for AArch64 through ACL directly. #147337
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
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/147337
Note: Links to docs will display an error until the docs builds have been completed. ✅ No FailuresAs of commit 0b06ae1 with merge base 6c3492b ( This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@pytorchbot label "module: arm" |
|
@pytorchbot label "ciflow/linux-aarch64" |
Among many things, this version of ACL fixes the redundant declaration warning that we're blocked on in (#145942, #146620, #147337) and introduces better scheduling heuristics for GEMMs Fixes #ISSUE_NUMBER Pull Request resolved: #147454 Approved by: https://github.com/malfet
bccb8ed to
457a00b
Compare
|
@pytorchbot label "arm priority" |
3310ef2 to
4c484f2
Compare
digantdesai
left a comment
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.
Looks good at a high level. Left some comments.
Do we have existing python tests and CI setups which guarantee we test these new impls?
| } | ||
| } | ||
|
|
||
| at::Tensor PackedLinearWeightsACL::apply( |
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.
This will be yet another option in addition to existing alternatives to run this quantized operator on Arm CPUs. What are your thoughts, at a high level from maintenance point of view, on consolidating some of these options? Or even directly using Kleidi kernels here with at::parallel and other native utils?
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.
The motivation for this approach is to address the overheads from the incompatibilities between the oneDNN and ACL lowp_gemm APIs in the current path (without this PR): PyTorch -> ideep -> oneDNN -> ACL (See motivation on #145942).
oneDNN's API is stateless, while ACL's lowp_gemm API is stateful. The current path utilizes ACL's lowp_gemm operators through oneDNN. Given that oneDNN is stateless, ACL's lowp_gemm cannot have state and will need to run (redundant) weights pre-transpositions and reductions on every forward call. This PR enables ACL's lowp_gemm directly from PyTorch, which allows ACL to cache pre-transpositions and reductions for each layer, instead of recomputing them on every forward call.
What are your thoughts, at a high level from maintenance point of view, on consolidating some of these options? Or even directly using Kleidi kernels here with at::parallel and other native utils?
Unfortunately, KleidiAI currently has minimal support for int8 GEMMs in general, and no kernels for int8 statically quantized GEMMs.
Once those kernels are in KleidiAI they will be part of ACL anyway and will be utilized by the path this PR enables.
We will then benchmark the (direct) PyTorch -> KleidiAI path against the PyTorch -> ACL -> KleidiAI path. If it turns out that ACL is adding overhead, we'll integrate KleidiAI kernels directly here.
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.
I guess my concern was around maintainability, so many different ways to run this going through build and runtime selection just for Arm CPUs. While I understand the rationale, we should also aim to actively trim dead paths.
For example, I saw somewhere that it is possible to fallback to onednn when ACL path isn't valid, can we support those through ACL and remove onednn path.
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.
we should also aim to actively trim dead paths.
Yes, I agree with you.
In this PR, we're extending the existing PackedLinearWeightsOneDNN with our new PackedLinearWeightsACL rather than replacing it entirely. This is because ACL's support for per-channel quantized weights is still incomplete. As you can see here, we only prepack to PackedLinearWeightsACL when weights are per-tensor affine quantized. Once ACL provides full support for per-channel quantized weights, we will drop the dependency on PackedLinearWeightsOneDNN and make ACL the default (and only) option for this path - i.e. PackedLinearWeightsACL extends LinearPackedParamsBase directly
it is possible to fallback to onednn when ACL path isn't valid, can we support those through ACL and remove onednn path.
Regarding the fallback to PackedLinearWeightsOneDNN::apply or PackedLinearWeightsOneDNN::apply_dynamic, it's not strictly needed as I haven’t encountered any workload causing ACL's validation to fail. I included these fallbacks purely as a precaution. Since our implementation extends PackedLinearWeightsOneDNN, I wanted to guarantee that the behavior remains exactly the same as the previous implementation in all cases (plus the perf improvements)
4c484f2 to
08f16da
Compare
Yes, current tests exercise this impl when |
CMakeLists.txt
Outdated
| if(USE_MKLDNN_ACL) | ||
| find_package(ACL REQUIRED) | ||
| if(ACL_FOUND) | ||
| include_directories(${ACL_INCLUDE_DIRS}) |
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.
This is a really bad pattern, what's wrong with target_include_directories(torch_cpu PRIVATE $(ACL_INCLUDE_DIRS})?
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.
Ack, I moved this from the root CMakeLists.txt to the caffe2 one where torch_cpu is defined.
I removed the redundant if(ACL_FOUND) check and used target_include_directories(torch_cpu PRIVATE $(ACL_INCLUDE_DIRS}) instead of include_directories(${ACL_INCLUDE_DIRS}) as you suggested.
Does it look better now?
08f16da to
460ed57
Compare
|
Thanks for your feedback @digantdesai , @malfet cc: @milpuz01 |
ACL is already built with PyTorch as a shared library when USE_MKLDNN_ACL is set. Currently, it is only used indirectly in ATen via oneDNN for AArch64 targets. However there are cases where it makes sense to utilize ACL directly without oneDNN as an intermediary - e.g. quantization. See pytorch#145942, pytorch#147337, pytorch#146620. This patch enables such use cases by exposing ACL to ATen
This enables a fast path for eager mode dynamic quantization for AArch64 through Arm Compute Library (ACL) directly. Context: PR pytorch#126687 enabled an optimized implementation for qlinear_dynamic for aarch64 through ideep → oneDNN → ACL which improved performance by ~10x compared to the previous implementation. However, the current qlinear_dynamic path (ideep → oneDNN → ACL) suffers from high overhead due to the API friction between the stateless oneDNN API and the stateful ACL low-precision GEMM (lowp_gemm) API - for example, ACL's lowp_gemm objects cache information like weights reduction or weights in optimized memory format which oneDNN does not allow due to its stateless nature. Hence, ACL currently runs a (redundant) sum of columns and pre-transposition (to the gemm kerne's optimal format) for each GEMM operation. This PR addresses the sub-optimalities above by integrating ACL directly with qlinear_dynamic. This approach yields an average speedup (averaged over context_lengths of 2^3 up to 2^9) of ~ 50% for bert-base-uncased, bert-large-uncased, roberta-base, distilbert-base-uncased with 16 threads on a Neoverse-V1 (with transformers==4.48). To achieve this we introduce PackedLinearWeightsACL (as a subclasses of PackedLinearWeightsOnednn ) with an implementation of qlinear_dynamic that uses ACL directly, while qlinear still follows the oneDNN path.
…tly. This enables a fast path for eager mode static quantization for AArch64 through Arm Compute Library (ACL) directly. PR pytorch#145942 addressed the high overhead in qlinear_dynamic on AArch64 (due to redundant weight pretranspositions and reductions) by enabling a path that calls ACL directly. This does the same thing but for (static) qlinear.
460ed57 to
0b06ae1
Compare
ACL is already built with PyTorch as a shared library when USE_MKLDNN_ACL is set. Currently, it is only used indirectly in ATen via oneDNN for AArch64 targets. However there are cases where it makes sense to utilize ACL directly without oneDNN as an intermediary - e.g. quantization. See #145942, #147337, #146620. This patch enables such use cases by exposing ACL to ATen ghstack-source-id: 266c621 Pull Request resolved: #148581
ACL is already built with PyTorch as a shared library when USE_MKLDNN_ACL is set. Currently, it is only used indirectly in ATen via oneDNN for AArch64 targets. However there are cases where it makes sense to utilize ACL directly without oneDNN as an intermediary - e.g. quantization. See #145942, #147337, #146620. This patch enables such use cases by exposing ACL to ATen Pull Request resolved: #148584 Approved by: https://github.com/malfet
|
Closing in favor of ghstack PR #148585 which has all comments addressed |
This enables a fast path for eager mode statically quantized matmuls for AArch64 through Arm Compute Library (ACL) directly.
PR #145942 addressed the high overhead in
qlinear_dynamicon AArch64 (due to redundant weight pre-transpositions and reductions) by enabling a path that calls ACL directly.This does the same thing and addresses the same overheads for (static)
qlinear.I benchmarked this PR (ACL direct integration for static quantization in ATen) against the current state of PyTorch (with #147498 which updates oneDNN to v3.7 included because it's a much stronger baseline than the current oneDNN version in PyTorch which is v3.5.3). See benchmarking script below.
My benchmark runs statically quantized linears for all combinations of
M = [8, 16, ..., 512],K = [768, 1024, 2048, 4096],N = [768, 1024, 2048, 4096].This PR gives an average speedup of 2x for signed activations (
s8s8s8) and 95x for unsigned activations (u8s8u8) on a Neoverse-V1 with 16 threads.The astronomical speedup for unsigned activation is because oneDNN v3.7 does not have an optimized implementation for
u8s8u8on AArch64.cc @jgong5 @mingfeima @XiaobingSuper @sanchitintel @ashokei @jingxu10 @malfet @snadampal @milpuz01