/******************************************************************************* * * MIT License * * Copyright (c) 2025 Advanced Micro Devices, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * *******************************************************************************/ #include #include #include #include "get_handle.hpp" #include "gtest_common.hpp" #include "random.hpp" #include "test_parameter_name_generator.hpp" // Run CPU emulations in hierarchical reduction mode. #define MIO_HEIRARCH_SEL 1 #define MIO_BN_TEST_EXPAVGFACTOR 0.1 #define MIO_BN_TEST_EPSILON 1e-5 // FLT_EPSILON #define MIO_BN_SP_TEST_DEBUG 0 #define MIO_BN_USE_MIX_PREC 1 #if MIO_BN_USE_MIX_PREC == 1 #define PREC_TYPE float #else #define PREC_TYPE T #endif namespace { using TestCase = NamedContainer>; //**************************************************** // FORWARD TRAIN //**************************************************** template struct verify_forward_train_3d_bn_spatial { const tensor input; const tensor scale; const tensor shift; std::tuple, tensor, tensor, tensor, tensor> cpu() const { #if(MIO_BN_TIME_EVERYTHING == 1) auto t_start = std::chrono::high_resolution_clock::now(); #endif double epsilon = MIO_BN_TEST_EPSILON; double expAvgFactor = MIO_BN_TEST_EXPAVGFACTOR; size_t n_batch, channels, depth, height, width; std::tie(n_batch, channels, depth, height, width) = miopen::tien<5>(input.desc.GetLengths()); size_t rs_n_batch, rs_channels, rs_depth, rs_height, rs_width; auto derivedBnDesc = miopen::TensorDescriptor{}; miopen::DeriveBNTensorDescriptor(derivedBnDesc, input.desc, miopenBNSpatial); std::tie(rs_n_batch, rs_channels, rs_depth, rs_height, rs_width) = miopen::tien<5>(derivedBnDesc.GetLengths()); tensor runMean; tensor runVar; if(input.desc.GetType() == miopenFloat) { runMean = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}.generate( tensor_elem_gen_integer{17}); runVar = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}.generate( tensor_elem_gen_integer{17}); } else { prng::reset_seed(); runMean = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}; runVar = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}; const double Data_scale = 0.001; for(size_t i = 0; i < runMean.desc.GetElementSize(); i++) { runMean[i] = prng::gen_descreet_uniform_sign(Data_scale, 100); runVar[i] = prng::gen_descreet_unsigned(Data_scale, 100); } } auto saveMean = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}; auto saveInvVar = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}; auto out = input; std::fill(out.begin(), out.end(), 0); const unsigned int in_cstride = depth * height * width; const auto ndhw = double(in_cstride * n_batch); miopen::par_for(channels, 1, [&](int cidx) { double elemStd = 0.; double variance_accum = 0.; double mean_accum = 0.; double invVar = 0.; double newRunMean = 0.; double adjust = 0.; #if(MIO_HEIRARCH_SEL == 1) std::vector variance_accum_arr(depth * height, 0.0); std::vector mean_accum_arr(depth * height, 0.0); std::vector dshift_accum_arr(depth * height, 0.0); std::vector dscale_accum_arr(depth * height, 0.0); #endif #if(MIO_HEIRARCH_SEL == 0) // process the batch per channel miopen::ford(n_batch, depth, height, width)( [&](size_t bidx, size_t didx, size_t row, size_t column) { // #1 calculate the mean // iterating through the stack of images in the mini_batch mean_accum += input(bidx, cidx, didx, row, column); }); #else miopen::ford(depth, height, width, n_batch)([&](size_t didx, size_t row, size_t column, size_t bidx) { mean_accum_arr[row] += input(bidx, cidx, didx, row, column); }); for(size_t i = 0; i(t_end - t_start).count() << " seconds." << std::endl; #endif return std::make_tuple(out, runMean, runVar, saveMean, saveInvVar); } std::tuple, tensor, tensor, tensor, tensor> gpu() const { #if(MIO_BN_TIME_EVERYTHING == 1) auto t_start = std::chrono::high_resolution_clock::now(); #endif auto&& handle = get_handle(); size_t n_batch, channels, depth, height, width; std::tie(n_batch, channels, depth, height, width) = miopen::tien<5>(input.desc.GetLengths()); auto out = input; std::fill(out.begin(), out.end(), 0); size_t rs_n_batch, rs_channels, rs_depth, rs_height, rs_width; auto derivedBnDesc = miopen::TensorDescriptor{}; miopen::DeriveBNTensorDescriptor(derivedBnDesc, input.desc, miopenBNSpatial); std::tie(rs_n_batch, rs_channels, rs_depth, rs_height, rs_width) = miopen::tien<5>(derivedBnDesc.GetLengths()); tensor runMean; tensor runVar; if(input.desc.GetType() == miopenFloat) { runMean = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}.generate( tensor_elem_gen_integer{17}); runVar = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}.generate( tensor_elem_gen_integer{17}); } else { prng::reset_seed(); runMean = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}; runVar = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}; const double Data_scale = 0.001; for(size_t i = 0; i < runMean.desc.GetElementSize(); i++) { runMean[i] = prng::gen_descreet_uniform_sign(Data_scale, 100); runVar[i] = prng::gen_descreet_unsigned(Data_scale, 100); } } auto saveMean = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}; auto saveInvVar = tensor{rs_n_batch, rs_channels, rs_depth, rs_height, rs_width}; // in buffers auto in_dev = handle.Write(input.data); auto scale_dev = handle.Write(scale.data); auto shift_dev = handle.Write(shift.data); // out buffers auto runMean_dev = handle.Write(runMean.data); auto runVar_dev = handle.Write(runVar.data); auto saveMean_dev = handle.Create(channels); auto saveInvVar_dev = handle.Create(channels); auto out_dev = handle.Create(n_batch * channels * depth * height * width); double epsilon = MIO_BN_TEST_EPSILON; double expAvgFactor = MIO_BN_TEST_EXPAVGFACTOR; float alpha = 1.0; float beta = 0.0; miopen::ActivationDescriptor actDesc(miopenActivationPASTHRU, 0.0f, 0.0f, 0.0f); miopen::BatchNormForwardTraining(handle, miopenBNSpatial, &alpha, &beta, miopen::BuildReshaped4DTensorDescriptor(input.desc), in_dev.get(), miopen::BuildReshaped4DTensorDescriptor(out.desc), out_dev.get(), miopen::BuildReshaped4DTensorDescriptor(scale.desc), miopen::BuildReshaped4DTensorDescriptor(shift.desc), miopen::BuildReshaped4DTensorDescriptor(shift.desc), miopen::BuildReshaped4DTensorDescriptor(shift.desc), scale_dev.get(), shift_dev.get(), expAvgFactor, runMean_dev.get(), runVar_dev.get(), epsilon, saveMean_dev.get(), saveInvVar_dev.get(), actDesc); saveMean.data = handle.Read(saveMean_dev, saveMean.data.size()); saveInvVar.data = handle.Read(saveInvVar_dev, saveInvVar.data.size()); runMean.data = handle.Read(runMean_dev, runMean.data.size()); runVar.data = handle.Read(runVar_dev, runVar.data.size()); out.data = handle.Read(out_dev, out.data.size()); #if(MIO_BN_TIME_EVERYTHING == 1) auto t_end = std::chrono::high_resolution_clock::now(); std::cout << "Wall clock: GPU forward_train_3d_bn_spatial pass time: " << std::chrono::duration(t_end - t_start).count() << " seconds." << std::endl; #endif return std::make_tuple(out, runMean, runVar, saveMean, saveInvVar); } void fail(int badtensor) const { std::cout << "Forward Train Spatial Batch Normalization: " << std::endl; std::cout << "Input tensor: " << input.desc.ToString() << std::endl; switch(badtensor) { case(0): std::cout << "Output tensor output failed verification." << std::endl; break; case(1): std::cout << "Running Mean output tensor failed verification." << std::endl; break; case(2): std::cout << "Running Variance output tensor failed verification." << std::endl; break; case(3): std::cout << "Saved Mean tensor failed verification." << std::endl; break; case(4): std::cout << "Saved Variance tensor failed verification." << std::endl; break; default: break; } } }; //**************************************************** // FORWARD INFERENCE //**************************************************** template struct verify_forward_infer_3d_bn_spatial_recalc { const tensor input; const tensor scale; const tensor shift; tensor cpu() const { #if(MIO_BN_TIME_EVERYTHING == 1) auto t_start = std::chrono::high_resolution_clock::now(); #endif double epsilon = MIO_BN_TEST_EPSILON; size_t n_batch, channels, depth, height, width; std::tie(n_batch, channels, depth, height, width) = miopen::tien<5>(input.desc.GetLengths()); auto out = input; std::fill(out.begin(), out.end(), 0); const unsigned int in_cstride = depth * height * width; const auto ndhw = double(in_cstride * n_batch); miopen::par_for(channels, 1, [&](int cidx) { double elemStd = 0.; double variance_accum = 0.; double mean_accum = 0.; double inhat = 0.; double invVar = 0.; // process the batch per channel miopen::ford(depth, height, width, n_batch)( [&](size_t didx, size_t row, size_t column, size_t bidx) { // iterating through the stack of images in the mini_batch mean_accum += input(bidx, cidx, didx, row, column); }); mean_accum /= ndhw; elemStd = 0.; variance_accum = 0.; // #2 calculate the variances // sigma^2 = (1/batch_mean) * sum( (x_i - batch_mean)^2 ) miopen::ford(depth, height, width, n_batch)( [&](size_t didx, size_t row, size_t column, size_t bidx) { // using out buffer as scratchpad out(bidx, cidx, didx, row, column) = elemStd = (input(bidx, cidx, didx, row, column) - mean_accum); // (x_i - mean) variance_accum += (elemStd * elemStd); // sum{ (x_i - mean)^2 } }); variance_accum /= ndhw; // (1/N)*sum{ (x_i - mean)^2 } // #3 add epsilon for numeric stability, sqr_root, and invert invVar = 1.0 / sqrt(variance_accum + epsilon); // #4 apply the normalization // x_hat = (x_i - mean) / sqrt(variance_accum - epsilon) miopen::ford(depth, height, width, n_batch)( [&](size_t didx, size_t row, size_t column, size_t bidx) { elemStd = out(bidx, cidx, didx, row, column); // using saved values from output tensor inhat = elemStd * invVar; // #5 Gamma and Beta adjust // y_i = gamma*x_hat + beta out(bidx, cidx, didx, row, column) = scale(0, cidx, 0, 0, 0) * inhat + shift(0, cidx, 0, 0, 0); }); }); #if(MIO_BN_TIME_EVERYTHING == 1) auto t_end = std::chrono::high_resolution_clock::now(); std::cout << "Wall clock: CPU forward_infer_3d_bn_spatial_recalc pass time: " << std::chrono::duration(t_end - t_start).count() << " seconds." << std::endl; #endif return out; } tensor gpu() const { #if(MIO_BN_TIME_EVERYTHING == 1) auto t_start = std::chrono::high_resolution_clock::now(); #endif auto&& handle = get_handle(); auto out = input; std::fill(out.begin(), out.end(), 0); auto in_dev = handle.Write(input.data); auto scale_dev = handle.Write(scale.data); auto shift_dev = handle.Write(shift.data); auto out_dev = handle.Write(out.data); float alpha = 1.0; float beta = 0.0; double epsilon = MIO_BN_TEST_EPSILON; miopen::ActivationDescriptor actDesc(miopenActivationPASTHRU, 0.0f, 0.0f, 0.0f); miopen::BatchNormForwardInference(handle, miopenBNSpatial, &alpha, &beta, miopen::BuildReshaped4DTensorDescriptor(input.desc), in_dev.get(), miopen::BuildReshaped4DTensorDescriptor(out.desc), out_dev.get(), miopen::BuildReshaped4DTensorDescriptor(scale.desc), miopen::BuildReshaped4DTensorDescriptor(shift.desc), miopen::BuildReshaped4DTensorDescriptor(shift.desc), miopen::BuildReshaped4DTensorDescriptor(shift.desc), scale_dev.get(), shift_dev.get(), nullptr, nullptr, epsilon, actDesc); out.data = handle.Read(out_dev, out.data.size()); #if(MIO_BN_TIME_EVERYTHING == 1) auto t_end = std::chrono::high_resolution_clock::now(); std::cout << "Wall clock: GPU forward_infer_3d_bn_spatial_recalc pass time: " << std::chrono::duration(t_end - t_start).count() << " seconds." << std::endl; #endif return out; } void fail(int) const { std::cout << "Forward Inference Spatial Batch Normalization Recalc: " << std::endl; std::cout << "Input tensor: " << input.desc.ToString() << std::endl; } }; template struct verify_forward_infer_3d_bn_spatial_use_est { const tensor input; const tensor scale; const tensor shift; const tensor estMean; const tensor estVar; tensor cpu() const { #if(MIO_BN_TIME_EVERYTHING == 1) auto t_start = std::chrono::high_resolution_clock::now(); #endif double epsilon = MIO_BN_TEST_EPSILON; size_t n_batch, channels, depth, height, width; std::tie(n_batch, channels, depth, height, width) = miopen::tien<5>(input.desc.GetLengths()); auto out = input; std::fill(out.begin(), out.end(), 0); miopen::par_for(channels, 1, [&](int cidx) { double elemStd = 0.; double variance = estVar(0, cidx, 0, 0, 0); double mean = estMean(0, cidx, 0, 0, 0); double inhat = 0.; double invVar = 1.0 / sqrt(variance + epsilon); // process the batch per channel miopen::ford(n_batch, depth, height, width)( [&](size_t bidx, size_t didx, size_t row, size_t column) { elemStd = input(bidx, cidx, didx, row, column) - mean; inhat = elemStd * invVar; out(bidx, cidx, didx, row, column) = scale(0, cidx, 0, 0, 0) * inhat + shift(0, cidx, 0, 0, 0); }); }); #if(MIO_BN_TIME_EVERYTHING == 1) auto t_end = std::chrono::high_resolution_clock::now(); std::cout << "Wall clock: CPU forward_infer_3d_bn_spatial_use_est pass time: " << std::chrono::duration(t_end - t_start).count() << " seconds." << std::endl; #endif return out; } tensor gpu() const { #if(MIO_BN_TIME_EVERYTHING == 1) auto t_start = std::chrono::high_resolution_clock::now(); #endif auto&& handle = get_handle(); auto out = input; std::fill(out.begin(), out.end(), 0); auto in_dev = handle.Write(input.data); auto estMean_dev = handle.Write(estMean.data); auto estVar_dev = handle.Write(estVar.data); auto scale_dev = handle.Write(scale.data); auto shift_dev = handle.Write(shift.data); auto out_dev = handle.Write(out.data); float alpha = 1.0; float beta = 0.0; double epsilon = MIO_BN_TEST_EPSILON; miopen::ActivationDescriptor actDesc(miopenActivationPASTHRU, 0.0f, 0.0f, 0.0f); miopen::BatchNormForwardInference(handle, miopenBNSpatial, &alpha, &beta, miopen::BuildReshaped4DTensorDescriptor(input.desc), in_dev.get(), miopen::BuildReshaped4DTensorDescriptor(out.desc), out_dev.get(), miopen::BuildReshaped4DTensorDescriptor(scale.desc), miopen::BuildReshaped4DTensorDescriptor(shift.desc), miopen::BuildReshaped4DTensorDescriptor(shift.desc), miopen::BuildReshaped4DTensorDescriptor(shift.desc), scale_dev.get(), shift_dev.get(), estMean_dev.get(), estVar_dev.get(), epsilon, actDesc); out.data = handle.Read(out_dev, out.data.size()); #if(MIO_BN_TIME_EVERYTHING == 1) auto t_end = std::chrono::high_resolution_clock::now(); std::cout << "Wall clock: GPU forward_infer_3d_bn_spatial_use_est pass time: " << std::chrono::duration(t_end - t_start).count() << " seconds." << std::endl; #endif return out; } void fail(int) const { std::cout << "Forward Inference Spatial Batch Normalization Use Estimated: " << std::endl; std::cout << "Input tensor: " << input.desc.ToString() << std::endl; } }; //**************************************************** // BACKWARDS PROPAGATION //**************************************************** template struct verify_backward_3d_bn_spatial_recalc { const tensor x_input; const tensor dy_input; const tensor scale; std::tuple, tensor, tensor> cpu() const { #if(MIO_BN_TIME_EVERYTHING == 1) auto t_start = std::chrono::high_resolution_clock::now(); #endif double epsilon = MIO_BN_TEST_EPSILON; size_t n_batch, channels, depth, height, width; std::tie(n_batch, channels, depth, height, width) = miopen::tien<5>(x_input.desc.GetLengths()); size_t ss_n_batch, ss_channels, ss_depth, ss_height, ss_width; auto derivedBnDesc = miopen::TensorDescriptor{}; miopen::DeriveBNTensorDescriptor(derivedBnDesc, x_input.desc, miopenBNSpatial); std::tie(ss_n_batch, ss_channels, ss_depth, ss_height, ss_width) = miopen::tien<5>(derivedBnDesc.GetLengths()); auto dx_out = tensor{n_batch, channels, depth, height, width}; std::fill(dx_out.begin(), dx_out.end(), 0); auto dscale = tensor{ss_n_batch, ss_channels, ss_depth, ss_height, ss_width}; std::fill(dscale.begin(), dscale.end(), 0); auto dshift = tensor{ss_n_batch, ss_channels, ss_depth, ss_height, ss_width}; std::fill(dshift.begin(), dshift.end(), 0); const unsigned int in_dstride = height * width; const unsigned int in_cstride = depth * in_dstride; const auto ndhw = double(in_cstride * n_batch); miopen::par_for(channels, 1, [&](int cidx) { double elemStd = 0.; unsigned int xhat_index; double mean = 0.; double invVar = 0.; double dyelem = 0.; double variance = 0.; std::vector xhat(n_batch * in_cstride, 0.0); #if(MIO_HEIRARCH_SEL == 1) std::vector variance_accum_arr(depth * height, 0.0); std::vector mean_accum_arr(depth * height, 0.0); std::vector dshift_accum_arr(depth * height, 0.0); std::vector dscale_accum_arr(depth * height, 0.0); #endif // process the batch per channel #if(MIO_HEIRARCH_SEL == 0) miopen::ford(depth, height, width, n_batch)( [&](size_t didx, size_t row, size_t column, size_t bidx) { // #1 calculate the mean mean += x_input(bidx, cidx, didx, row, column); }); #else miopen::ford(depth, height, width, n_batch)([&](size_t didx, size_t row, size_t column, size_t bidx) { mean_accum_arr[row] += x_input(bidx, cidx, didx, row, column); }); for(size_t i = 0; i(t_end - t_start).count() << " seconds." << std::endl; #endif return std::make_tuple(dx_out, dscale, dshift); } std::tuple, tensor, tensor> gpu() const { #if(MIO_BN_TIME_EVERYTHING == 1) auto t_start = std::chrono::high_resolution_clock::now(); #endif auto&& handle = get_handle(); size_t n_batch, channels, depth, height, width; std::tie(n_batch, channels, depth, height, width) = miopen::tien<5>(x_input.desc.GetLengths()); auto dx_out = tensor{n_batch, channels, depth, height, width}; std::fill(dx_out.begin(), dx_out.end(), 0); size_t ss_n_batch, ss_channels, ss_depth, ss_height, ss_width; auto derivedBnDesc = miopen::TensorDescriptor{}; miopen::DeriveBNTensorDescriptor(derivedBnDesc, x_input.desc, miopenBNSpatial); std::tie(ss_n_batch, ss_channels, ss_depth, ss_height, ss_width) = miopen::tien<5>(derivedBnDesc.GetLengths()); auto dscale = tensor{ss_n_batch, ss_channels, ss_depth, ss_height, ss_width}; std::fill(dscale.begin(), dscale.end(), 0); auto dshift = tensor{ss_n_batch, ss_channels, ss_depth, ss_height, ss_width}; std::fill(dshift.begin(), dshift.end(), 0); float alpha = 1.0; float beta = 0.0; auto xin_dev = handle.Write(x_input.data); auto dyin_dev = handle.Write(dy_input.data); auto scale_dev = handle.Write(scale.data); auto dscale_dev = handle.Write(dscale.data); auto dshift_dev = handle.Write(dshift.data); auto dx_out_dev = handle.Write(dx_out.data); double epsilon = MIO_BN_TEST_EPSILON; miopen::ActivationDescriptor actDesc(miopenActivationPASTHRU, 0.0f, 0.0f, 0.0f); miopen::BatchNormBackward(handle, miopenBNSpatial, &alpha, &beta, &alpha, &beta, miopen::BuildReshaped4DTensorDescriptor(x_input.desc), xin_dev.get(), miopen::BuildReshaped4DTensorDescriptor(dy_input.desc), dyin_dev.get(), miopen::BuildReshaped4DTensorDescriptor(dx_out.desc), dx_out_dev.get(), miopen::BuildReshaped4DTensorDescriptor(scale.desc), miopen::BuildReshaped4DTensorDescriptor(dshift.desc), miopen::BuildReshaped4DTensorDescriptor(dshift.desc), miopen::BuildReshaped4DTensorDescriptor(dshift.desc), scale_dev.get(), nullptr, dscale_dev.get(), dshift_dev.get(), epsilon, nullptr, nullptr, actDesc); dx_out.data = handle.Read(dx_out_dev, dx_out.data.size()); dscale.data = handle.Read(dscale_dev, dscale.data.size()); dshift.data = handle.Read(dshift_dev, dshift.data.size()); #if(MIO_BN_TIME_EVERYTHING == 1) auto t_end = std::chrono::high_resolution_clock::now(); std::cout << "Wall clock: GPU backward_3d_bn_spatial_recalc pass time: " << std::chrono::duration(t_end - t_start).count() << " seconds." << std::endl; #endif return std::make_tuple(dx_out, dscale, dshift); } void fail(int badtensor) const { std::cout << "Backward Batch Spatial Normalization Recalc Mean and Variance: " << std::endl; std::cout << "X Input tensor: " << x_input.desc.ToString() << std::endl; std::cout << "Delta Y Input tensor: " << dy_input.desc.ToString() << std::endl; switch(badtensor) { case(0): std::cout << "Delta X output tensor output failed verification." << std::endl; break; case(1): std::cout << "Delta scale output tensor failed verification." << std::endl; break; case(2): std::cout << "Delta shift output tensor failed verification." << std::endl; break; default: break; } } }; template struct verify_backward_3d_bn_spatial_use_saved { const tensor x_input; const tensor dy_input; const tensor scale; const tensor savedMean; const tensor savedInvVar; std::tuple, tensor, tensor> cpu() const { #if(MIO_BN_TIME_EVERYTHING == 1) auto t_start = std::chrono::high_resolution_clock::now(); #endif size_t n_batch, channels, depth, height, width; std::tie(n_batch, channels, depth, height, width) = miopen::tien<5>(x_input.desc.GetLengths()); auto dx_out = tensor{n_batch, channels, depth, height, width}; std::fill(dx_out.begin(), dx_out.end(), 0); size_t ss_n_batch, ss_channels, ss_depth, ss_height, ss_width; auto derivedBnDesc = miopen::TensorDescriptor{}; miopen::DeriveBNTensorDescriptor(derivedBnDesc, x_input.desc, miopenBNSpatial); std::tie(ss_n_batch, ss_channels, ss_depth, ss_height, ss_width) = miopen::tien<5>(derivedBnDesc.GetLengths()); auto dscale = tensor{ss_n_batch, ss_channels, ss_depth, ss_height, ss_width}; std::fill(dscale.begin(), dscale.end(), 0); auto dshift = tensor{ss_n_batch, ss_channels, ss_depth, ss_height, ss_width}; std::fill(dshift.begin(), dshift.end(), 0); const unsigned int in_dstride = height * width; const unsigned int in_cstride = depth * in_dstride; const auto ndhw = double(in_cstride * n_batch); miopen::par_for(channels, 1, [&](int cidx) { double elemStd = 0.; unsigned int xhat_index; double mean = savedMean(0, cidx, 0, 0, 0); // HxW elements double invVar = savedInvVar(0, cidx, 0, 0, 0); // HxW elements double dyelem = 0.; std::vector xhat(n_batch * in_cstride, 0.0); #if(MIO_HEIRARCH_SEL == 1) std::vector dshift_accum_arr(height * depth, 0.0); std::vector dscale_accum_arr(height * depth, 0.0); #endif // process the batch per channel dscale(0, cidx, 0, 0, 0) = 0.; #if(MIO_HEIRARCH_SEL == 0) miopen::ford(depth, height, width, n_batch)( [&](size_t didx, size_t row, size_t column, size_t bidx) { xhat_index = in_cstride * bidx + in_dstride * didx + width * row + column; // per (x-dims) channel load a block of data into LDS elemStd = x_input(bidx, cidx, didx, row, column) - mean; // (x_i - mean) xhat[xhat_index] = elemStd * invVar; dyelem = dy_input(bidx, cidx, didx, row, column); dshift(0, cidx, 0, 0, 0) += dyelem; dscale(0, cidx, 0, 0, 0) += xhat[xhat_index] * dyelem; }); #else miopen::ford(depth, height, width, n_batch)([&](size_t didx, size_t row, size_t column, size_t bidx) { xhat_index = in_cstride*bidx + in_dstride*didx + width*row + column; //per (x-dims) channel load a block of data into LDS elemStd = x_input(bidx,cidx,didx,row,column) - mean;// (x_i - mean) xhat[xhat_index] = elemStd*invVar; //printf("xhat[%d]: %lf\n",xhat_index,xhat[xhat_index]); dyelem = dy_input(bidx,cidx,didx, row,column); dshift_accum_arr[row] += dyelem; dscale_accum_arr[row] += xhat[xhat_index]*dyelem; //dscale_accum_arr[row] += 1.0;//DEBUG }); for(size_t i = 0; i(t_end - t_start).count() << " seconds." << std::endl; #endif return std::make_tuple(dx_out, dscale, dshift); } std::tuple, tensor, tensor> gpu() const { #if(MIO_BN_TIME_EVERYTHING == 1) auto t_start = std::chrono::high_resolution_clock::now(); #endif auto&& handle = get_handle(); size_t n_batch, channels, depth, height, width; std::tie(n_batch, channels, depth, height, width) = miopen::tien<5>(x_input.desc.GetLengths()); auto dx_out = tensor{n_batch, channels, depth, height, width}; std::fill(dx_out.begin(), dx_out.end(), 0); size_t ss_n_batch, ss_channels, ss_depth, ss_height, ss_width; auto derivedBnDesc = miopen::TensorDescriptor{}; miopen::DeriveBNTensorDescriptor(derivedBnDesc, x_input.desc, miopenBNSpatial); std::tie(ss_n_batch, ss_channels, ss_depth, ss_height, ss_width) = miopen::tien<5>(derivedBnDesc.GetLengths()); auto dscale = tensor{ss_n_batch, ss_channels, ss_depth, ss_height, ss_width}; std::fill(dscale.begin(), dscale.end(), 0); auto dshift = tensor{ss_n_batch, ss_channels, ss_depth, ss_height, ss_width}; std::fill(dshift.begin(), dshift.end(), 0); float alpha = 1.0; float beta = 0.0; auto xin_dev = handle.Write(x_input.data); auto dyin_dev = handle.Write(dy_input.data); auto scale_dev = handle.Write(scale.data); auto dscale_dev = handle.Write(dscale.data); auto dshift_dev = handle.Write(dshift.data); auto dx_out_dev = handle.Write(dx_out.data); auto savedMean_dev = handle.Write(savedMean.data); auto savedInvVar_dev = handle.Write(savedInvVar.data); double epsilon = MIO_BN_TEST_EPSILON; miopen::ActivationDescriptor actDesc(miopenActivationPASTHRU, 0.0f, 0.0f, 0.0f); miopen::BatchNormBackward(handle, miopenBNSpatial, &alpha, &beta, &alpha, &beta, miopen::BuildReshaped4DTensorDescriptor(x_input.desc), xin_dev.get(), miopen::BuildReshaped4DTensorDescriptor(dy_input.desc), dyin_dev.get(), miopen::BuildReshaped4DTensorDescriptor(dx_out.desc), dx_out_dev.get(), miopen::BuildReshaped4DTensorDescriptor(scale.desc), miopen::BuildReshaped4DTensorDescriptor(dshift.desc), miopen::BuildReshaped4DTensorDescriptor(dshift.desc), miopen::BuildReshaped4DTensorDescriptor(dshift.desc), scale_dev.get(), nullptr, dscale_dev.get(), dshift_dev.get(), epsilon, savedMean_dev.get(), savedInvVar_dev.get(), actDesc); dx_out.data = handle.Read(dx_out_dev, dx_out.data.size()); dscale.data = handle.Read(dscale_dev, dscale.data.size()); dshift.data = handle.Read(dshift_dev, dshift.data.size()); #if(MIO_BN_TIME_EVERYTHING == 1) auto t_end = std::chrono::high_resolution_clock::now(); std::cout << "Wall clock: GPU backward_3d_bn_spatial_use_saved pass time: " << std::chrono::duration(t_end - t_start).count() << " seconds." << std::endl; #endif return std::make_tuple(dx_out, dscale, dshift); } void fail(int badtensor) const { std::cout << "Backward Batch Spatial Normalization Use Saved Mean and Variance: " << std::endl; std::cout << "X Input tensor: " << x_input.desc.ToString() << std::endl; std::cout << "Delta Y Input tensor: " << dy_input.desc.ToString() << std::endl; switch(badtensor) { case(0): std::cout << "Delta X output tensor output failed verification." << std::endl; break; case(1): std::cout << "Delta scale output tensor failed verification." << std::endl; break; case(2): std::cout << "Delta shift output tensor failed verification." << std::endl; break; default: break; } } }; //====== DRIVERS =========================================== inline auto GenSmokeTestCases() { return testing::Values(NamedContainer>{"dims", {16, 32, 8, 8, 8}, "x"}); } inline auto GetSmokeTestCases() { static const auto cases = GenSmokeTestCases(); return cases; } inline auto GenFullTestCases() { return MakeNamedParameterCollectionValues>( "dims", get_3d_bn_spatial_inputs(4), "x"); } inline auto GetFullTestCases() { static const auto cases = GenFullTestCases(); return cases; } } // namespace template struct Bn3DSpatialTest : public testing::TestWithParam { static const constexpr uint64_t MaxValue{miopen_type{} == miopenHalf ? 5 : 17}; void SetUp() override { prng::reset_seed(); const std::vector dims = GetParam(); input = tensor{dims}.generate(tensor_elem_gen_integer{MaxValue}); tolerance = 4e-3 / std::numeric_limits::epsilon(); // ck solver has tolerance of 4e-3 } void Run() { #if(MIO_BN_TIME_EVERYTHING == 1) const auto t_start = std::chrono::high_resolution_clock::now(); #endif tensor scale; tensor shift; size_t n, c, d, h, w; std::tie(n, c, d, h, w) = miopen::tien<5>(input.desc.GetLengths()); // The condition is derived form bn_spatial_test.cpp as they are known failures if(n == 1) { std::cout << "(n=1) is not supported for BN operation." << std::endl; return; } if((h * w * d > 1024) && (input.desc.GetType() == miopenHalf) && (MIO_BN_USE_MIX_PREC == 0)) { std::cout << "(h*w*d > 1024) is not supported for BN operations " << "when half precision is used but mixed precision disabled." << std::endl; return; } size_t ssn, ssc, ssd, ssh, ssw; auto derivedBnDesc = miopen::TensorDescriptor{}; miopen::DeriveBNTensorDescriptor(derivedBnDesc, input.desc, miopenBNSpatial); std::tie(ssn, ssc, ssd, ssh, ssw) = miopen::tien<5>(derivedBnDesc.GetLengths()); scale = tensor{ssn, ssc, ssd, ssh, ssw}; shift = tensor{ssn, ssc, ssd, ssh, ssw}; const double data_scale = 1e-4; for(size_t i = 0; i < scale.desc.GetElementSize(); i++) { scale[i] = prng::gen_descreet_uniform_sign(data_scale, 100); shift[i] = prng::gen_descreet_uniform_sign(data_scale, 100); } for(size_t i = 0; i < input.desc.GetElementSize(); i++) { input[i] = prng::gen_descreet_uniform_sign(1e-5, 100); } // train #if(MIO_BN_SP_TEST_DEBUG == 1) std::cout << "Running forward train spatial with R and S set." << std::endl; #endif const auto outpair = Verify(verify_forward_train_3d_bn_spatial{input, scale, shift}); // returns: std::make_tuple(out,runMean,runVar,saveMean,saveInvVar); // inference recalc #if(MIO_BN_SP_TEST_DEBUG == 1) std::cout << "Running forward inference spatial recalc." << std::endl; #endif // tolerance = 80; // Debug values // std::fill(input.begin(), input.end(), 1); // std::fill(scale.begin(), scale.end(), 1); // std::fill(shift.begin(), shift.end(), 1); Verify(verify_forward_infer_3d_bn_spatial_recalc{input, scale, shift}); // inference use estimated running values const auto estMean = std::get<1>(outpair.second); const auto estVar = std::get<2>(outpair.second); #if(MIO_BN_SP_TEST_DEBUG == 1) std::cout << "Running forward inference spatial with R set." << std::endl; #endif Verify(verify_forward_infer_3d_bn_spatial_use_est{ input, scale, shift, estMean, estVar}); // backprop recalc auto dy_input = std::get<0>(outpair.second); miopen::ford(n, c, d, h, w)( [&](size_t bidx, size_t cidx, size_t didx, size_t row, size_t column) { dy_input(bidx, cidx, didx, row, column) *= 0.1; }); #if(MIO_BN_SP_TEST_DEBUG == 2) auto debugvals = Verify(verify_backward_3d_bn_spatial_recalc{input, dy_input, scale}); auto gpuout = std::get<0>(debugvals.second); auto cpuout = std::get<0>(debugvals.first); double maxdiff = 0.; int mn = 0; int mc = 0; int md = 0; int mh = 0; int mw = 0; miopen::ford( n, c, d, h, w)([&](size_t bidx, size_t cidx, size_t didx, size_t row, size_t column) { const double diff = fabs(gpuout(bidx, cidx, didx, row, column) - cpuout(bidx, cidx, didx, row, column)); if(diff > maxdiff) { maxdiff = diff; mn = bidx; mc = cidx; md = didx; mh = row; mw = column; } // if(diff > 1.) // { std::cout << "gpu[" << bidx << ", " << cidx << ", " << didx << ", " << row << ", " << column << "]: " << gpuout(bidx, cidx, didx, row, column) << " :: "; std::cout << "cpu[" << bidx << ", " << cidx << ", " << didx << ", " << row << ", " << column << "]: " << cpuout(bidx, cidx, didx, row, column) << " :: "; std::cout << "diff: " << diff << std::endl; // } }); if(maxdiff > 0) { std::cout << "Max diff: " << maxdiff << std::endl; std::cout << "gpu[" << mn << ", " << mc << ", " << md << ", " << mh << ", " << mw << "]: " << gpuout(mn, mc, md, mh, mw) << " :: "; std::cout << "cpu[" << mn << ", " << mc << ", " << md << ", " << mh << ", " << mw << "]: " << cpuout(mn, mc, md, mh, mw) << std::endl; } #else #if(MIO_BN_SP_TEST_DEBUG == 1) std::cout << "Running back propagation spatial recalc." << std::endl; #endif tolerance = 80 * input.desc.GetElementSize(); Verify(verify_backward_3d_bn_spatial_recalc{input, dy_input, scale}); #endif // backprop use saved values const auto savedMean = std::get<3>(outpair.second); const auto savedInvVar = std::get<4>(outpair.second); #if(MIO_BN_SP_TEST_DEBUG == 3) auto debugvals = Verify(verify_backward_3d_bn_spatial_use_saved{ input, dy_input, scale, savedMean, savedInvVar}); auto gpuout = std::get<0>(debugvals.second); auto cpuout = std::get<0>(debugvals.first); double maxdiff = 0.; int mn = 0; int mc = 0; int md = 0; int mh = 0; int mw = 0; miopen::ford( n, c, d, h, w)([&](size_t bidx, size_t cidx, size_t didx, size_t row, size_t column) { const double diff = fabs(gpuout(bidx, cidx, didx, row, column) - cpuout(bidx, cidx, didx, row, column)); if(diff > maxdiff) { maxdiff = diff; mn = bidx; mc = cidx; md = didx; mh = row; mw = column; } // if(diff > 1.) //{ std::cout << "gpu[" << bidx << ", " << cidx << ", " << didx << ", " << row << ", " << column << "]: " << gpuout(bidx, cidx, didx, row, column) << " :: "; std::cout << "cpu[" << bidx << ", " << cidx << ", " << didx << ", " << row << ", " << column << "]: " << cpuout(bidx, cidx, didx, row, column) << " :: "; std::cout << "diff: " << diff << std::endl; //} }); if(maxdiff > 0) { std::cout << "Max diff: " << maxdiff << std::endl; std::cout << "gpu[" << mn << ", " << mc << ", " << md << ", " << mh << ", " << mw << "]: " << gpuout(mn, mc, md, mh, mw) << " :: "; std::cout << "cpu[" << mn << ", " << mc << ", " << md << ", " << mh << ", " << mw << "]: " << cpuout(mn, mc, md, mh, mw) << std::endl; } #else #if(MIO_BN_SP_TEST_DEBUG == 1) std::cout << "Running back propagation spatial with S set." << std::endl; #endif Verify(verify_backward_3d_bn_spatial_use_saved{ input, dy_input, scale, savedMean, savedInvVar}); #endif #if(MIO_BN_TIME_EVERYTHING == 1) const auto t_end = std::chrono::high_resolution_clock::now(); std::cout << "Wall clock: full PER_ACTIVATION test pass time: " << std::chrono::duration(t_end - t_start).count() << " seconds." << std::endl; #endif } private: tensor input; double tolerance{0.0}; private: auto Verify(auto&& v) -> decltype(std::make_pair(v.cpu(), v.gpu())) { const auto cpu = v.cpu(); const auto gpu = v.gpu(); Compare(v, cpu, gpu); return std::make_pair(cpu, gpu); } template void Compare(auto&& v, const std::tuple& cpu, const std::tuple& gpu) { static_assert(sizeof...(CpuRanges) == sizeof...(GpuRanges), "CPU and GPU mismatch"); miopen::sequence([&](auto... is) { miopen::each_args( [&](auto i) { const auto& c = std::get(cpu); const auto& g = std::get(gpu); ASSERT_EQ(miopen::range_distance(c), miopen::range_distance(g)); using value_type = miopen::range_value; const double threshold = std::numeric_limits::epsilon() * tolerance; const double error = miopen::rms_range(c, g); EXPECT_LE(error, threshold); if(error > threshold) { v.fail(i); } }, is...); })(std::integral_constant{}); } template void Compare(auto&& v, const CpuRanges& cpu, const GpuRanges& gpu) { ASSERT_EQ(miopen::range_distance(cpu), miopen::range_distance(gpu)); using value_type = miopen::range_value; const double threshold = std::numeric_limits::epsilon() * tolerance; const double error = miopen::rms_range(cpu, gpu); EXPECT_LE(error, threshold); if(error > threshold) { v.fail(0); } } }; struct TestNameGenerator { std::string operator()(const auto& info) { const auto& dims = info.param; std::stringstream ss; std::string str; ss << "dims_" << GetRangeAsString(dims(), "x") << "_test_id_" << info.index; str = ss.str(); // Name format only supports letters, numbers and underscores. std::transform(str.begin(), str.end(), str.begin(), [](char c) { return (c == '.') ? 'p' : (std::isalnum(c) ? c : '_'); }); return str; } }; using GPU_Bn3DSpatial_FP32 = Bn3DSpatialTest; TEST_P(GPU_Bn3DSpatial_FP32, TestFloat32) { this->Run(); } INSTANTIATE_TEST_SUITE_P(Smoke, GPU_Bn3DSpatial_FP32, GetSmokeTestCases(), TestNameGenerator{}); INSTANTIATE_TEST_SUITE_P(Full, GPU_Bn3DSpatial_FP32, GetFullTestCases(), TestNameGenerator{});