// Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 // DeepSpeed Team #include "dequantization_utils.h" #include "memory_access_utils.h" #include "quantization_utils.h" #include "reduction_utils.h" using rop = reduce::ROpType; namespace swiz_quant { constexpr int max_threads = 512; constexpr int min_threads = 32; constexpr int step_granularity = 2; constexpr int h_per_step = step_granularity * quantize::h_per_load; } // namespace swiz_quant template __global__ void swizzled_quant_kernel(int8_t* quantized_data, float* quantized_scales, const __half* uncompressed_data, int elems_per_group, int nodes, int devices_per_node) { cg::thread_block tb = cg::this_thread_block(); cg::thread_block_tile warp = cg::tiled_partition(tb); // Indexing offsets, same as normal quantization for in-case const int block_rank = blockIdx.x + blockIdx.y * gridDim.x + blockIdx.z * gridDim.x * gridDim.y; const int block_offset = block_rank * elems_per_group; const int elem_offset = tb.thread_index().x * quantize::h_per_load; const int base_offset = block_offset + elem_offset; const int stride = tb.size() * quantize::h_per_load; const __half* input_base = uncompressed_data + base_offset; // Local buffer __half2 local_buffer[totalChunks * quantize::h2_per_load]; quantize::GroupStats stats; #pragma unroll for (int i = 0; i < totalChunks; i++) { __half2* iteration_buffer = local_buffer + i * quantize::h2_per_load; mem_access::load_global( iteration_buffer, input_base + i * stride, elem_offset + i * stride < elems_per_group); #pragma unroll for (int j = 0; j < quantize::h2_per_load; j++) { stats.update(iteration_buffer[j]); } } auto params = stats.template get_params(tb, warp); const int partition_id = blockIdx.z; const int partition_offset = partition_id / devices_per_node; const int partition_base = (partition_id % devices_per_node) * nodes; const int pipelining_offset = blockIdx.y * (devices_per_node * nodes); const int output_partition = (pipelining_offset + partition_base + partition_offset); constexpr int out_scalar_effect = 8 / numBits; const int out_block_rank = output_partition * gridDim.x + blockIdx.x; const int out_block_offset = out_block_rank * elems_per_group / out_scalar_effect; const int out_base_offset = out_block_offset + elem_offset / out_scalar_effect; int8_t* out_base = quantized_data + out_base_offset; const int out_stride = stride / out_scalar_effect; constexpr int num_int8_out = quantize::h_per_load / out_scalar_effect; if (tb.thread_index().x == 0) { params.store(quantized_scales, out_block_rank); } #pragma unroll for (int i = 0; i < totalChunks; i++) { if (i * stride + elem_offset < elems_per_group) { int8_t local_output[quantize::h_per_load / out_scalar_effect]; quantize::_chunk( local_output, local_buffer + i * quantize::h2_per_load, params); mem_access::store_global(out_base + i * out_stride, local_output); } } } #define LAUNCH_SWIZZLE_QUANT(total_chunks, threads) \ swizzled_quant_kernel<<>>( \ q_data, q_scales, input_data, elems_per_group, nodes, devices_per_node); /* Swizzled quantization reorganizes the quantized groups in order to better facilitate communication. As an example of the partitioning scheme we have the following example of 2 node, 4 device swizzling: --- --- --- --- --- --- --- --- | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | --- --- --- --- --- --- --- --- becomes --- --- --- --- --- --- --- --- | 0 | 4 | 1 | 5 | 2 | 6 | 3 | 7 | --- --- --- --- --- --- --- --- Multiple quantization groups may be mapped into a single partition. In order to better support later pipelining, we may also perform an additional slicing. In two-way slicing, for instance, the first halves of each partition are concatenated. */ template void launch_swizzled_quant_impl(int8_t* q_data, float* q_scales, const __half* input_data, int groups, int elems_per_group, int pipelining, int nodes, int devices_per_node, cudaStream_t stream) { const int one_step_threads = next_pow2((elems_per_group + swiz_quant::h_per_step - 1) / swiz_quant::h_per_step); const int max_threads = (one_step_threads < swiz_quant::max_threads) ? one_step_threads : swiz_quant::max_threads; const int threads = (max_threads < swiz_quant::min_threads) ? swiz_quant::min_threads : max_threads; dim3 block(threads); const int groups_per_partition = groups / (nodes * devices_per_node); assert(groups_per_partition % pipelining == 0); const int contiguous_groups = groups_per_partition / pipelining; const int partitions = nodes * devices_per_node; dim3 grid(contiguous_groups, pipelining, partitions); const int elems_per_step = threads * swiz_quant::h_per_step; const int external_unroll = ((elems_per_group + elems_per_step - 1) / elems_per_step); const int total_unroll = external_unroll * swiz_quant::step_granularity; assert(total_unroll % 2 == 0); if (threads == 32) { LAUNCH_SWIZZLE_QUANT(2, 32); } else if (threads == 64) { LAUNCH_SWIZZLE_QUANT(2, 64); } else if (threads == 128) { LAUNCH_SWIZZLE_QUANT(2, 128); } else if (threads == 256) { LAUNCH_SWIZZLE_QUANT(2, 256); } else if (threads == 512) { if (total_unroll == 2) { LAUNCH_SWIZZLE_QUANT(2, 512); } else if (total_unroll == 4) { LAUNCH_SWIZZLE_QUANT(4, 512); } else if (total_unroll == 6) { LAUNCH_SWIZZLE_QUANT(6, 512); } else if (total_unroll == 8) { LAUNCH_SWIZZLE_QUANT(8, 512); } else if (total_unroll == 10) { LAUNCH_SWIZZLE_QUANT(10, 512); } } } #define DISPATCH_SWIZZLE_QUANT(num_bits, qtype) \ launch_swizzled_quant_impl(q_data, \ q_scales, \ input_data, \ groups, \ elems_per_group, \ pipelining, \ nodes, \ devices_per_node, \ stream); void launch_swizzled_quant(int8_t* q_data, float* q_scales, const __half* input_data, int num_bits, quantize::Type q_type, int groups, int elems_per_group, int pipelining, int nodes, int devices_per_node, cudaStream_t stream) { if (num_bits == 4) { if (q_type == quantize::Type::Asymmetric) { DISPATCH_SWIZZLE_QUANT(4, quantize::Type::Asymmetric); } else if (q_type == quantize::Type::Symmetric) { DISPATCH_SWIZZLE_QUANT(4, quantize::Type::Symmetric); } } else if (num_bits == 8) { if (q_type == quantize::Type::Asymmetric) { DISPATCH_SWIZZLE_QUANT(8, quantize::Type::Asymmetric); } else if (q_type == quantize::Type::Symmetric) { DISPATCH_SWIZZLE_QUANT(8, quantize::Type::Symmetric); } } } template __global__ void loco_swizzled_quant_kernel(int8_t* quantized_data, float* quantized_scales, const __half* uncompressed_data, __half* error_feedback, const float err_beta, int groups, int elems_per_group, int pipelining, int nodes, int devices_per_node) { cg::thread_block tb = cg::this_thread_block(); cg::thread_block_tile warp = cg::tiled_partition(tb); // Indexing offsets, same as normal quantization for in-case const int block_rank_data = blockIdx.x + blockIdx.y * gridDim.x + blockIdx.z * gridDim.x * gridDim.y; const int block_offset_data = block_rank_data * elems_per_group; const int elem_offset = tb.thread_index().x * quantize::h_per_load; const int base_offset_data = block_offset_data + elem_offset; const int stride = tb.size() * quantize::h_per_load; const __half* uncompressed_data_base = uncompressed_data + base_offset_data; const int partition_id = blockIdx.z; const int partition_offset = partition_id / devices_per_node; const int partition_base = (partition_id % devices_per_node) * nodes; const int pipelining_offset = blockIdx.y * (devices_per_node * nodes); const int output_partition = (pipelining_offset + partition_base + partition_offset); const int block_rank_err = output_partition * gridDim.x + blockIdx.x; const int block_offset_err = block_rank_err * elems_per_group; const int base_offset_err = block_offset_err + elem_offset; __half* error_feedback_base = error_feedback + base_offset_err; __half2 local_buffer[totalChunks * quantize::h2_per_load]; __half2 err_buffer[totalChunks * quantize::h2_per_load]; quantize::GroupStats stats; #pragma unroll for (int i = 0; i < totalChunks; i++) { __half2* iteration_buffer = local_buffer + i * quantize::h2_per_load; __half2* iter_err_buffer = err_buffer + i * quantize::h2_per_load; const int i_stride = i * stride; bool do_loads = (elem_offset + i_stride) < elems_per_group; mem_access::load_global( iteration_buffer, uncompressed_data_base + i_stride, do_loads); mem_access::load_global( iter_err_buffer, error_feedback_base + i_stride, do_loads); #pragma unroll for (int j = 0; j < quantize::h2_per_load; j++) { iteration_buffer[j] = __hadd2(iteration_buffer[j], iter_err_buffer[j]); stats.update(iteration_buffer[j]); } } auto params = stats.template get_params(tb, warp); // Initialize dequantization parameters based on params auto de_params = params; de_params.scale = 1.0f / params.scale; if constexpr (quantType == quantize::Type::Asymmetric) { de_params.offset = params.offset; } if (threadIdx.x == 0) { params.store(quantized_scales, block_rank_err); } constexpr int out_scalar_effect = 8 / numBits; const int out_block_offset = block_rank_err * elems_per_group / out_scalar_effect; const int out_base_offset = out_block_offset + elem_offset / out_scalar_effect; int8_t* out_base = quantized_data + out_base_offset; const int out_stride = stride / out_scalar_effect; constexpr int num_int8_out = quantize::h_per_load / out_scalar_effect; #pragma unroll for (int i = 0; i < totalChunks; i++) { const int i_stride = i * stride; __half2* iteration_buffer = local_buffer + i * quantize::h2_per_load; __half2* iter_err_buffer = err_buffer + i * quantize::h2_per_load; if (i_stride + elem_offset < elems_per_group) { int8_t local_output[quantize::h_per_load / out_scalar_effect]; quantize::_chunk(local_output, iteration_buffer, params); mem_access::store_global(out_base + i * out_stride, local_output); // Dequantize the quantized output to compute the dequantized value __half2 dequant_buffer[quantize::h2_per_load]; dequantize::chunk(dequant_buffer, local_output, de_params); // Compute new error: sum - dequant_buffer #pragma unroll for (int k = 0; k < quantize::h2_per_load; k++) { // __half2 to float2 float2 iter_buf_f = __half22float2(iteration_buffer[k]); float2 dequant_buf_f = __half22float2(dequant_buffer[k]); // Update within float precision float2 new_error_f; new_error_f.x = iter_buf_f.x - dequant_buf_f.x; new_error_f.y = iter_buf_f.y - dequant_buf_f.y; float2 iter_err_buf_f = __half22float2(iter_err_buffer[k]); iter_err_buf_f.x = err_beta * iter_err_buf_f.x + (1.0f - err_beta) * new_error_f.x; iter_err_buf_f.y = err_beta * iter_err_buf_f.y + (1.0f - err_beta) * new_error_f.y; // float2 back to __half2 iter_err_buffer[k] = __float22half2_rn(iter_err_buf_f); } __half2* error_feedback_base_h2 = reinterpret_cast<__half2*>(error_feedback_base); mem_access::store_global(error_feedback_base_h2 + i_stride / 2, iter_err_buffer); } } } #define LAUNCH_LOCO_SWIZZLE_QUANT(total_chunks, threads) \ loco_swizzled_quant_kernel \ <<>>(output_data, \ params, \ input_data, \ error_feedback, \ err_beta, \ groups, \ elems_per_group, \ pipelining, \ nodes, \ devices_per_node); template void launch_loco_swizzled_quant_impl(int8_t* output_data, float* params, const __half* input_data, __half* error_feedback, const float err_beta, int groups, int elems_per_group, int pipelining, int nodes, int devices_per_node, cudaStream_t stream) { const int one_step_threads = next_pow2((elems_per_group + swiz_quant::h_per_step - 1) / swiz_quant::h_per_step); const int max_threads = (one_step_threads < swiz_quant::max_threads) ? one_step_threads : swiz_quant::max_threads; const int threads = (max_threads < swiz_quant::min_threads) ? swiz_quant::min_threads : max_threads; dim3 block(threads); const int groups_per_partition = groups / (nodes * devices_per_node); assert(groups_per_partition % pipelining == 0); const int contiguous_groups = groups_per_partition / pipelining; const int partitions = nodes * devices_per_node; dim3 grid(contiguous_groups, pipelining, partitions); const int elems_per_step = threads * swiz_quant::h_per_step; const int external_unroll = ((elems_per_group + elems_per_step - 1) / elems_per_step); const int total_unroll = external_unroll * swiz_quant::step_granularity; assert(total_unroll % 2 == 0); if (threads == 32) { LAUNCH_LOCO_SWIZZLE_QUANT(2, 32); } else if (threads == 64) { LAUNCH_LOCO_SWIZZLE_QUANT(2, 64); } else if (threads == 128) { LAUNCH_LOCO_SWIZZLE_QUANT(2, 128); } else if (threads == 256) { LAUNCH_LOCO_SWIZZLE_QUANT(2, 256); } else if (threads == 512) { if (total_unroll == 2) { LAUNCH_LOCO_SWIZZLE_QUANT(2, 512); } else if (total_unroll == 4) { LAUNCH_LOCO_SWIZZLE_QUANT(4, 512); } else if (total_unroll == 6) { LAUNCH_LOCO_SWIZZLE_QUANT(6, 512); } else if (total_unroll == 8) { LAUNCH_LOCO_SWIZZLE_QUANT(8, 512); } else if (total_unroll == 10) { LAUNCH_LOCO_SWIZZLE_QUANT(10, 512); } } } #define DISPATCH_LOCO_SWIZZLE_QUANT(num_bits, qtype) \ launch_loco_swizzled_quant_impl(output_data, \ params, \ input_data, \ error_feedback, \ err_beta, \ groups, \ elems_per_group, \ pipelining, \ nodes, \ devices_per_node, \ stream); void launch_loco_swizzled_quant(int8_t* output_data, float* params, const __half* input_data, __half* error_feedback, const float err_beta, int num_bits, quantize::Type q_type, int groups, int elems_per_group, int pipelining, int nodes, int devices_per_node, cudaStream_t stream) { if (num_bits == 4) { if (q_type == quantize::Type::Asymmetric) { DISPATCH_LOCO_SWIZZLE_QUANT(4, quantize::Type::Asymmetric); } else if (q_type == quantize::Type::Symmetric) { DISPATCH_LOCO_SWIZZLE_QUANT(4, quantize::Type::Symmetric); } } else if (num_bits == 8) { if (q_type == quantize::Type::Asymmetric) { DISPATCH_LOCO_SWIZZLE_QUANT(8, quantize::Type::Asymmetric); } else if (q_type == quantize::Type::Symmetric) { DISPATCH_LOCO_SWIZZLE_QUANT(8, quantize::Type::Symmetric); } } }