-
Notifications
You must be signed in to change notification settings - Fork 300
Backward weight v4r4r2 with xdlops #18
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
Merged
Merged
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
59ac6f8
start
ltqin 0bf754e
modify transformat
ltqin 6a963f9
modify device convolutiion
ltqin 7e0e97d
modify host
ltqin 16d78f5
added host conv bwd and wrw
9465cf3
remove bwd, seperate wrw
4910398
clean
55aec0e
Merge branch 'add_host_conv_bwd_wrw' into backward_weight_v4r4r2_xdlops
ltqin fe26049
hacall k to zero
ltqin c27a57d
out log
ltqin 115c5bf
fixed
fca3500
fixed
95ce725
Merge branch 'add_host_conv_bwd_wrw' into backward_weight_v4r4r2_xdlops
ltqin 252d271
change to (out in wei)
ltqin 2d194c5
input hack
ltqin 1e9c511
hack to out
ltqin 5cfd01f
format
ltqin e6d9dd2
fix by comments
ltqin 7cacac2
Merge branch 'develop' into backward_weight_v4r4r2_xdlops
ltqin a34c34a
change wei hacks(wei transform has not merge)
ltqin b2ea9aa
fix program once issue
ltqin 0e08b3b
fix review comment
7bc4254
fix vector load issue
ltqin f74cf52
Merge branch 'develop' into backward_weight_v4r4r2_xdlops
ltqin fd30df2
tweak
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
129 changes: 129 additions & 0 deletions
129
...oblem_transform/transform_backward_weight_convolution_into_gemm_v4r4r2_nchw_kcyx_nkhw.hpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| #ifndef CK_TRANSFORM_BACKWARD_WEIGHT_CONVOLUTION_INTO_GEMM_V4R4R2_NCHW_KCYX_NKHW_HPP | ||
| #define CK_TRANSFORM_BACKWARD_WEIGHT_CONVOLUTION_INTO_GEMM_V4R4R2_NCHW_KCYX_NKHW_HPP | ||
|
|
||
| #include "common_header.hpp" | ||
| #include "tensor_descriptor.hpp" | ||
| #include "tensor_descriptor_helper.hpp" | ||
|
|
||
| namespace ck { | ||
|
|
||
| // GemmM = K | ||
| // GemmK = N * Ho * Wo | ||
| // GemmN = C * Y * X | ||
| template <typename... Wei, | ||
| typename... In, | ||
| typename... Out, | ||
| typename ConvStrides, | ||
| typename ConvDilations, | ||
| typename InLeftPads, | ||
| typename InRightPads, | ||
| index_t GemmK1Value> | ||
| __host__ __device__ constexpr auto | ||
| transform_backward_weight_convolution_into_gemm_v4r4r2_nchw_kcyx_nkhw_pad( | ||
| const TensorDescriptor<Wei...>& wei_k_c_y_x_grid_desc, | ||
| const TensorDescriptor<In...>& in_n_c_hi_wi_grid_desc, | ||
| const TensorDescriptor<Out...>& out_n_k_ho_wo_grid_desc, | ||
| const ConvStrides& conv_strides, | ||
| const ConvDilations& conv_dilations, | ||
| const InLeftPads& in_left_pads, | ||
| const InRightPads& in_right_pads, | ||
| Number<GemmK1Value>) | ||
| { | ||
| constexpr auto I0 = Number<0>{}; | ||
| constexpr auto I1 = Number<1>{}; | ||
| constexpr auto I2 = Number<2>{}; | ||
| constexpr auto I3 = Number<3>{}; | ||
|
|
||
| constexpr auto GemmK1 = Number<GemmK1Value>{}; | ||
|
|
||
| const auto N = in_n_c_hi_wi_grid_desc.GetLength(I0); | ||
| const auto C = in_n_c_hi_wi_grid_desc.GetLength(I1); | ||
| const auto K = out_n_k_ho_wo_grid_desc.GetLength(I1); | ||
|
|
||
| const auto Hi = in_n_c_hi_wi_grid_desc.GetLength(I2); | ||
| const auto Wi = in_n_c_hi_wi_grid_desc.GetLength(I3); | ||
|
|
||
| const auto Ho = out_n_k_ho_wo_grid_desc.GetLength(I2); | ||
| const auto Wo = out_n_k_ho_wo_grid_desc.GetLength(I3); | ||
|
|
||
| const auto Y = wei_k_c_y_x_grid_desc.GetLength(I2); | ||
| const auto X = wei_k_c_y_x_grid_desc.GetLength(I3); | ||
|
|
||
| const auto ConvStrideH = conv_strides[I0]; | ||
| const auto ConvStrideW = conv_strides[I1]; | ||
|
|
||
| const auto ConvDilationH = conv_dilations[I0]; | ||
| const auto ConvDilationW = conv_dilations[I1]; | ||
|
|
||
| const auto InLeftPadH = in_left_pads[I0]; | ||
| const auto InLeftPadW = in_left_pads[I1]; | ||
|
|
||
| const auto InRightPadH = in_right_pads[I0]; | ||
| const auto InRightPadW = in_right_pads[I1]; | ||
|
|
||
| const auto GemmM = K; | ||
| const auto GemmN = C * Y * X; | ||
| const auto GemmK = N * Ho * Wo; | ||
| const auto GemmK0 = GemmK / GemmK1; | ||
|
|
||
| // weight tensor | ||
| const auto wei_gemmm_gemmn_grid_desc = transform_tensor_descriptor( | ||
| make_naive_tensor_descriptor_packed(make_tuple(K, C * Y * X)), | ||
| make_tuple(make_pass_through_transform(K), make_pass_through_transform(C * Y * X)), | ||
| make_tuple(Sequence<0>{}, Sequence<1>{}), | ||
| make_tuple(Sequence<0>{}, Sequence<1>{})); | ||
|
|
||
| // input tensor | ||
| const auto in_n_c_hip_wip_grid_desc = transform_tensor_descriptor( | ||
| in_n_c_hi_wi_grid_desc, | ||
| make_tuple(make_pass_through_transform(N), | ||
| make_pass_through_transform(C), | ||
| make_pad_transform(Hi, InLeftPadH, InRightPadH), | ||
| make_pad_transform(Wi, InLeftPadW, InRightPadW)), | ||
| make_tuple(Sequence<0>{}, Sequence<1>{}, Sequence<2>{}, Sequence<3>{}), | ||
| make_tuple(Sequence<0>{}, Sequence<1>{}, Sequence<2>{}, Sequence<3>{})); | ||
|
|
||
| const auto in_n_c_y_ho_x_wo_grid_desc = transform_tensor_descriptor( | ||
| in_n_c_hip_wip_grid_desc, | ||
| make_tuple(make_pass_through_transform(N), | ||
| make_pass_through_transform(C), | ||
| make_embed_transform(make_tuple(Y, Ho), make_tuple(ConvDilationH, ConvStrideH)), | ||
| make_embed_transform(make_tuple(X, Wo), make_tuple(ConvDilationW, ConvStrideW))), | ||
| make_tuple(Sequence<0>{}, Sequence<1>{}, Sequence<2>{}, Sequence<3>{}), | ||
| make_tuple(Sequence<0>{}, Sequence<1>{}, Sequence<2, 3>{}, Sequence<4, 5>{})); | ||
|
|
||
| const auto in_gemmk_gemmn_grid_desc = | ||
| transform_tensor_descriptor(in_n_c_y_ho_x_wo_grid_desc, | ||
| make_tuple(make_merge_transform(make_tuple(C, Y, X)), | ||
| make_merge_transform(make_tuple(N, Ho, Wo))), | ||
| make_tuple(Sequence<1, 2, 4>{}, Sequence<0, 3, 5>{}), | ||
| make_tuple(Sequence<1>{}, Sequence<0>{})); | ||
|
|
||
| const auto in_gemmk0_gemmn_gemmk1_grid_desc = | ||
| transform_tensor_descriptor(in_gemmk_gemmn_grid_desc, | ||
| make_tuple(make_unmerge_transform(make_tuple(GemmK0, GemmK1)), | ||
| make_pass_through_transform(GemmN)), | ||
| make_tuple(Sequence<0>{}, Sequence<1>{}), | ||
| make_tuple(Sequence<0, 2>{}, Sequence<1>{})); | ||
|
|
||
| // output tensor | ||
| const auto out_gemmk_gemmm_grid_desc = transform_tensor_descriptor( | ||
| make_naive_tensor_descriptor_packed(make_tuple(N, K, Ho * Wo)), | ||
| make_tuple(make_pass_through_transform(K), make_merge_transform(make_tuple(N, Ho * Wo))), | ||
| make_tuple(Sequence<1>{}, Sequence<0, 2>{}), | ||
| make_tuple(Sequence<1>{}, Sequence<0>{})); | ||
|
|
||
| const auto out_gemmk0_gemmm_gemmk1_grid_desc = | ||
| transform_tensor_descriptor(out_gemmk_gemmm_grid_desc, | ||
| make_tuple(make_unmerge_transform(make_tuple(GemmK0, GemmK1)), | ||
| make_pass_through_transform(GemmM)), | ||
| make_tuple(Sequence<0>{}, Sequence<1>{}), | ||
| make_tuple(Sequence<0, 2>{}, Sequence<1>{})); | ||
|
|
||
| return make_tuple(out_gemmk0_gemmm_gemmk1_grid_desc, | ||
| in_gemmk0_gemmn_gemmk1_grid_desc, | ||
| wei_gemmm_gemmn_grid_desc); | ||
| } | ||
|
|
||
| } // namespace ck | ||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
197 changes: 197 additions & 0 deletions
197
...include/device_convolution_backward_weight_implicit_gemm_v4r4r2_xdlops_nchw_kcyx_nkhw.hpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| #include <unistd.h> | ||
| #include "device.hpp" | ||
| #include "host_tensor.hpp" | ||
| #include "transform_backward_weight_convolution_into_gemm_v4r4r2_nchw_kcyx_nkhw.hpp" | ||
| #include "driver_gemm_xdlops_v2r3.hpp" | ||
|
|
||
| template <typename TInWei, | ||
| typename TAcc, | ||
| typename TOut, | ||
| typename InLengths, | ||
| typename WeiLengths, | ||
| typename OutLengths, | ||
| typename ConvStrides, | ||
| typename ConvDilations, | ||
| typename InLeftPads, | ||
| typename InRightPads> | ||
| void device_convolution_backward_weight_implicit_gemm_v4r4r2_xdlops_nchw_kcyx_nkhw( | ||
| const InLengths& in_n_c_hi_wi_lengths, | ||
| const WeiLengths& wei_k_c_y_x_lengths, | ||
| const OutLengths& out_n_k_ho_wo_lengths, | ||
| const ConvStrides& conv_strides, | ||
| const ConvDilations& conv_dilations, | ||
| const InLeftPads& in_left_pads, | ||
| const InRightPads& in_right_pads, | ||
| const Tensor<TInWei>& in_n_c_hi_wi, | ||
| Tensor<TInWei>& wei_k_c_y_x, | ||
| const Tensor<TOut>& out_n_k_ho_wo, | ||
| ck::index_t nrepeat) | ||
| { | ||
| using namespace ck; | ||
|
|
||
| std::cout << __func__ << std::endl; | ||
|
|
||
| constexpr auto I0 = Number<0>{}; | ||
| constexpr auto I1 = Number<1>{}; | ||
| constexpr auto I2 = Number<2>{}; | ||
|
|
||
| DeviceMem in_n_c_hi_wi_device_buf(sizeof(TInWei) * in_n_c_hi_wi.mDesc.GetElementSpace()); | ||
| DeviceMem wei_k_c_y_x_device_buf(sizeof(TInWei) * wei_k_c_y_x.mDesc.GetElementSpace()); | ||
| DeviceMem out_n_k_ho_wo_device_buf(sizeof(TOut) * out_n_k_ho_wo.mDesc.GetElementSpace()); | ||
|
|
||
| in_n_c_hi_wi_device_buf.ToDevice(in_n_c_hi_wi.mData.data()); | ||
| wei_k_c_y_x_device_buf.ToDevice(wei_k_c_y_x.mData.data()); | ||
| out_n_k_ho_wo_device_buf.ToDevice(out_n_k_ho_wo.mData.data()); | ||
|
|
||
| const auto in_n_c_hi_wi_desc = make_naive_tensor_descriptor_packed(in_n_c_hi_wi_lengths); | ||
| const auto wei_k_c_y_x_desc = make_naive_tensor_descriptor_packed(wei_k_c_y_x_lengths); | ||
| const auto out_n_k_ho_wo_desc = make_naive_tensor_descriptor_packed(out_n_k_ho_wo_lengths); | ||
|
|
||
| #if 1 | ||
| // [M, N, K0, K1] = [128, 128, 4, 8] for fp16 | ||
| constexpr index_t BlockSize = 256; | ||
|
|
||
| constexpr index_t GemmMPerBlock = 128; | ||
| constexpr index_t GemmNPerBlock = 128; | ||
| constexpr index_t GemmKPerBlock = 4; | ||
|
|
||
| constexpr index_t GemmMPerWave = 32; | ||
| constexpr index_t GemmNPerWave = 32; | ||
| constexpr index_t GemmK1 = 8; | ||
|
|
||
| constexpr index_t MRepeat = 2; | ||
| constexpr index_t NRepeat = 2; | ||
|
|
||
| using GemmABlockTransferThreadSliceLengths_GemmK0_GemmM_GemmK1 = Sequence<1, 2, 8>; | ||
| using GemmABlockTransferThreadClusterLengths_GemmK0_GemmM_GemmK1 = Sequence<4, 64, 1>; | ||
|
|
||
| constexpr index_t GemmABlockTransferSrcScalarPerVector_GemmK1 = 8; | ||
| constexpr index_t GemmABlockTransferDstScalarPerVector_GemmK1 = 8; | ||
|
|
||
| using GemmBBlockTransferThreadSliceLengths_GemmK0_GemmN_GemmK1 = Sequence<1, 2, 8>; | ||
| using GemmBBlockTransferThreadClusterLengths_GemmK0_GemmN_GemmK1 = Sequence<4, 64, 1>; | ||
|
|
||
| constexpr index_t GemmBBlockTransferSrcScalarPerVector_GemmN = 1; | ||
| constexpr index_t GemmBBlockTransferDstScalarPerVector_GemmK1 = 8; | ||
|
|
||
| constexpr index_t GemmCThreadTransferDstScalarPerVector = 1; | ||
| #endif | ||
|
|
||
| const auto descs = transform_backward_weight_convolution_into_gemm_v4r4r2_nchw_kcyx_nkhw_pad( | ||
| wei_k_c_y_x_desc, | ||
| in_n_c_hi_wi_desc, | ||
| out_n_k_ho_wo_desc, | ||
| conv_strides, | ||
| conv_dilations, | ||
| in_left_pads, | ||
| in_right_pads, | ||
| Number<GemmK1>{}); | ||
|
|
||
| const auto out_gemmk0_gemmm_gemmk1_grid_desc = descs[I0]; | ||
| const auto in_gemmk0_gemmn_gemmk1_grid_desc = descs[I1]; | ||
| const auto wei_gemmm_gemmn_grid_desc = descs[I2]; | ||
|
|
||
| // HACK: hacks that control index calculation when iterating over A, B, C matrix | ||
| constexpr auto out_gemmk0_gemmm_gemmk1_grid_step_hacks = make_tuple( | ||
| make_tuple(Sequence<0, 0, 1, 0, 0>{}, Sequence<0, 0, 0, 0, 0>{}, Sequence<0, 0, 1, 0, 0>{}), | ||
| make_tuple( | ||
| Sequence<0, 0, 2, 0, 0>{}, Sequence<0, 0, 0, 0, 0>{}, Sequence<0, 0, 2, 0, 0>{})); | ||
|
|
||
| constexpr auto in_gemmk0_gemmn_gemmk1_grid_step_hacks = | ||
| make_tuple(make_tuple(Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0>{}), | ||
| make_tuple(Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0>{})); | ||
|
|
||
| constexpr auto wei_m0_n0_m1_n1_m2_m3_m4_n2_grid_step_hacks = | ||
| make_tuple(make_tuple(Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}), | ||
| make_tuple(Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{}, | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>{})); | ||
|
|
||
| constexpr auto out_gemmk0_gemmm_gemmk1_grid_move_slice_window_step_hacks = | ||
| Sequence<0, 0, 1, 0, 0>{}; | ||
|
|
||
| constexpr auto in_gemmk0_gemmn_gemmk1_grid_move_slice_window_step_hacks = | ||
| Sequence<0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0, 0>{}; | ||
|
|
||
| for(index_t i = 0; i < 5; ++i) | ||
| { | ||
| float ave_time = driver_gemm_xdlops_v2r3< | ||
| BlockSize, | ||
| TInWei, | ||
| TAcc, | ||
| TOut, | ||
| InMemoryDataOperationEnum_t::Set, | ||
| decltype(out_gemmk0_gemmm_gemmk1_grid_desc), | ||
| decltype(in_gemmk0_gemmn_gemmk1_grid_desc), | ||
| decltype(wei_gemmm_gemmn_grid_desc), | ||
| GemmMPerBlock, | ||
| GemmNPerBlock, | ||
| GemmKPerBlock, | ||
| GemmMPerWave, | ||
| GemmNPerWave, | ||
| GemmK1, | ||
| MRepeat, | ||
| NRepeat, | ||
| GemmABlockTransferThreadSliceLengths_GemmK0_GemmM_GemmK1, | ||
| GemmABlockTransferThreadClusterLengths_GemmK0_GemmM_GemmK1, | ||
| Sequence<1, 0, 2>, | ||
| Sequence<1, 0, 2>, | ||
| 2, | ||
| GemmABlockTransferSrcScalarPerVector_GemmK1, | ||
| GemmABlockTransferDstScalarPerVector_GemmK1, | ||
| false, // don't move back src coordinate after threadwise copy | ||
| GemmBBlockTransferThreadSliceLengths_GemmK0_GemmN_GemmK1, | ||
| GemmBBlockTransferThreadClusterLengths_GemmK0_GemmN_GemmK1, | ||
| Sequence<1, 0, 2>, | ||
| Sequence<1, 0, 2>, | ||
| 2, | ||
| GemmBBlockTransferSrcScalarPerVector_GemmN, | ||
| GemmBBlockTransferDstScalarPerVector_GemmK1, | ||
| false, // don't move back src coordinate after threadwise copy | ||
| Sequence<3, 0, 1, 2, 7, 5, 4, 6>, | ||
| 7, | ||
| GemmCThreadTransferDstScalarPerVector, | ||
| decltype(out_gemmk0_gemmm_gemmk1_grid_step_hacks), | ||
| decltype(in_gemmk0_gemmn_gemmk1_grid_step_hacks), | ||
| decltype(wei_m0_n0_m1_n1_m2_m3_m4_n2_grid_step_hacks), | ||
| decltype(out_gemmk0_gemmm_gemmk1_grid_move_slice_window_step_hacks), | ||
| decltype(in_gemmk0_gemmn_gemmk1_grid_move_slice_window_step_hacks), | ||
| false>(static_cast<TOut*>(out_n_k_ho_wo_device_buf.GetDeviceBuffer()), | ||
| static_cast<TInWei*>(in_n_c_hi_wi_device_buf.GetDeviceBuffer()), | ||
| static_cast<TInWei*>(wei_k_c_y_x_device_buf.GetDeviceBuffer()), | ||
| out_gemmk0_gemmm_gemmk1_grid_desc, | ||
| in_gemmk0_gemmn_gemmk1_grid_desc, | ||
| wei_gemmm_gemmn_grid_desc, | ||
| out_gemmk0_gemmm_gemmk1_grid_step_hacks, | ||
| in_gemmk0_gemmn_gemmk1_grid_step_hacks, | ||
| wei_m0_n0_m1_n1_m2_m3_m4_n2_grid_step_hacks, | ||
| out_gemmk0_gemmm_gemmk1_grid_move_slice_window_step_hacks, | ||
| in_gemmk0_gemmn_gemmk1_grid_move_slice_window_step_hacks, | ||
| nrepeat); | ||
|
|
||
| float perf = static_cast<float>(calculate_convolution_flops( | ||
| in_n_c_hi_wi_desc, wei_k_c_y_x_desc, out_n_k_ho_wo_desc)) / | ||
| (std::size_t(1000) * 1000 * 1000) / ave_time; | ||
|
|
||
| std::cout << "Average time : " << ave_time << " ms, " << perf << " TFlop/s" << std::endl; | ||
| } | ||
|
|
||
| // copy result back to host | ||
| wei_k_c_y_x_device_buf.FromDevice(wei_k_c_y_x.mData.data()); | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.