// Copyright © Advanced Micro Devices, Inc., or its affiliates. // SPDX-License-Identifier: MIT #include #include #include #include #include #include #include #include #include #include #include #include "../utils/Helpers.hpp" using namespace hipdnn_frontend; using namespace hipdnn_data_sdk; template bool SampleRunner::operator()(const TensorLayout& layout) { auto inputType = getDataTypeEnumFromType(); auto intermediateType = getDataTypeEnumFromType(); std::cout << "Running batch normalization training graph " << inputType << " [" << layout << "]" << (config.cpuValidation ? " (with CPU validation)" : ""); if(config.useRunningStats) { std::cout << " [FULL_TRAINING mode]...\n"; } else { std::cout << " [BATCH_STATS_ONLY mode]...\n"; } // Input dimensions const int64_t n = config.dims.size() > 0 ? config.dims[0] : 16; // BATCH SIZE const int64_t c = config.dims.size() > 1 ? config.dims[1] : 16; // CHANNELS (FEATURES) const int64_t h = config.dims.size() > 2 ? config.dims[2] : 16; // HEIGHT (SPATIAL DIMENSION) const int64_t w = config.dims.size() > 3 ? config.dims[3] : 16; // WIDTH (SPATIAL DIMENSION) auto graph = std::make_shared(); graph->set_io_data_type(inputType) .set_intermediate_data_type(intermediateType) .set_compute_data_type(hipdnn_frontend::DataType::FLOAT); setPreferredEngine(graph, config); auto x = createTensor({n, c, h, w}, inputType, layout); auto scale = createTensor({1, c, 1, 1}, intermediateType); auto bias = createTensor({1, c, 1, 1}, intermediateType); // Epsilon is a pass-by-value scalar, not a buffer auto epsilon = std::make_shared(); epsilon->set_value(utilities::BATCHNORM_DEFAULT_EPSILON); auto bnAttributes = graph::BatchnormAttributes(); bnAttributes.set_name("bn_training_node"); bnAttributes.set_epsilon(epsilon); std::shared_ptr prevRunningMean; std::shared_ptr prevRunningVar; const double momentumVal = 0.1; // Conditionally setup running statistics inputs if(config.useRunningStats) { prevRunningMean = createTensor({1, c, 1, 1}, intermediateType); prevRunningVar = createTensor({1, c, 1, 1}, intermediateType); // Momentum: use pass-by-value with double (matches MIOpen API) auto momentum = std::make_shared(); momentum->set_value(momentumVal); bnAttributes.set_previous_running_stats(prevRunningMean, prevRunningVar, momentum); } // API always returns 5 values regardless of whether running stats are used auto [y, savedMean, savedInvVariance, nextRunningMean, nextRunningVariance] = graph->batchnorm(x, scale, bias, bnAttributes); // Configure output tensors (always needed for BATCH_STATS_ONLY mode) y->set_output(true); savedMean->set_output(true).set_data_type(intermediateType); savedInvVariance->set_output(true).set_data_type(intermediateType); if(config.useRunningStats) { nextRunningMean->set_output(true).set_data_type(intermediateType); nextRunningVariance->set_output(true).set_data_type(intermediateType); } HIPDNN_FE_CHECK_SKIPPABLE(graph->build(handle)); std::cout << "Graph build successful.\n"; // Allocate tensors for BATCH_STATS_ONLY mode // Note: epsilon is pass-by-value, no buffer allocation needed utilities::Tensor xTensor(x->get_dim(), layout); utilities::Tensor scaleTensor(scale->get_dim()); utilities::Tensor biasTensor(bias->get_dim()); utilities::Tensor yTensor(y->get_dim(), layout); utilities::Tensor savedMeanTensor(savedMean->get_dim()); utilities::Tensor savedInvVarTensor(savedInvVariance->get_dim()); // Declare running statistics tensors at broader scope (conditionally initialized) utilities::Tensor prevMeanTensor( config.useRunningStats ? prevRunningMean->get_dim() : std::vector{1}); utilities::Tensor prevVarTensor( config.useRunningStats ? prevRunningVar->get_dim() : std::vector{1}); utilities::Tensor nextMeanTensor( config.useRunningStats ? nextRunningMean->get_dim() : std::vector{1}); utilities::Tensor nextVarTensor( config.useRunningStats ? nextRunningVariance->get_dim() : std::vector{1}); // Note: momentum would also be pass-by-value like epsilon // Initialize tensors xTensor.fillWithRandomValues(static_cast(-1.0f), static_cast(1.0f)); scaleTensor.fillWithRandomValues(static_cast(-2.0f), static_cast(2.0f)); biasTensor.fillWithRandomValues(static_cast(-2.0f), static_cast(2.0f)); if(config.useRunningStats) { prevMeanTensor.fillWithRandomValues(static_cast(-2.0f), static_cast(2.0f)); prevVarTensor.fillWithRandomValues(static_cast(-2.0f), static_cast(2.0f)); } // Build variant pack with batch statistics // Note: epsilon is pass-by-value, not included in variantPack std::unordered_map variantPack; variantPack[x->get_uid()] = xTensor.memory().deviceData(); variantPack[scale->get_uid()] = scaleTensor.memory().deviceData(); variantPack[bias->get_uid()] = biasTensor.memory().deviceData(); variantPack[y->get_uid()] = yTensor.memory().deviceData(); variantPack[savedMean->get_uid()] = savedMeanTensor.memory().deviceData(); variantPack[savedInvVariance->get_uid()] = savedInvVarTensor.memory().deviceData(); if(config.useRunningStats) { variantPack[prevRunningMean->get_uid()] = prevMeanTensor.memory().deviceData(); variantPack[prevRunningVar->get_uid()] = prevVarTensor.memory().deviceData(); variantPack[nextRunningMean->get_uid()] = nextMeanTensor.memory().deviceData(); variantPack[nextRunningVariance->get_uid()] = nextVarTensor.memory().deviceData(); // Note: momentum is also pass-by-value, not included in variantPack } HIPDNN_FE_CHECK(graph->execute(handle, variantPack, nullptr)); yTensor.memory().markDeviceModified(); savedMeanTensor.memory().markDeviceModified(); savedInvVarTensor.memory().markDeviceModified(); if(config.useRunningStats) { nextMeanTensor.memory().markDeviceModified(); nextVarTensor.memory().markDeviceModified(); } auto yHostPtr = yTensor.memory().hostData(); auto savedMeanHostPtr = savedMeanTensor.memory().hostData(); auto savedInvVarHostPtr = savedInvVarTensor.memory().hostData(); bool validationPassed = true; if(config.cpuValidation) { std::cout << "Running CPU reference validation...\n"; utilities::Tensor yRefTensor(y->get_dim(), layout); utilities::Tensor savedMeanRefTensor(savedMean->get_dim()); utilities::Tensor savedInvVarRefTensor(savedInvVariance->get_dim()); if(config.useRunningStats) { // FULL_TRAINING mode validation utilities::Tensor nextMeanRefTensor(nextRunningMean->get_dim()); utilities::Tensor nextVarRefTensor(nextRunningVariance->get_dim()); hipdnn_test_sdk::utilities::CpuFpReferenceBatchnorm::fwdTraining< InputType, // XDataType IntermediateType, // ScaleBiasDataType IntermediateType, // MeanVarianceDataType InputType // YDataType >(xTensor, scaleTensor, biasTensor, yRefTensor, utilities::BATCHNORM_DEFAULT_EPSILON, momentumVal, // momentum value used &savedMeanRefTensor, &savedInvVarRefTensor, &prevMeanTensor, // used &prevVarTensor, // used &nextMeanRefTensor, // used &nextVarRefTensor // used ); auto tolerance = hipdnn_test_sdk::utilities::batchnorm::getToleranceTraining(); auto floatTolerance = static_cast(tolerance); auto yValidator = hipdnn_test_sdk::utilities::CpuFpReferenceValidation( tolerance, tolerance); auto statsValidator = hipdnn_test_sdk::utilities::CpuFpReferenceValidation( static_cast(tolerance), static_cast(tolerance)); std::cout << "CPU reference validation:\n"; const bool yValid = hipdnn_test_sdk::utilities::validateAndReport( std::cout, "y", yValidator, yRefTensor, yTensor, floatTolerance, floatTolerance); const bool meanValid = hipdnn_test_sdk::utilities::validateAndReport( std::cout, "saved_mean", statsValidator, savedMeanRefTensor, savedMeanTensor, floatTolerance, floatTolerance); const bool invVarValid = hipdnn_test_sdk::utilities::validateAndReport( std::cout, "saved_inv_variance", statsValidator, savedInvVarRefTensor, savedInvVarTensor, floatTolerance, floatTolerance); const bool nextMeanValid = hipdnn_test_sdk::utilities::validateAndReport( std::cout, "next_running_mean", statsValidator, nextMeanRefTensor, nextMeanTensor, floatTolerance, floatTolerance); const bool nextVarValid = hipdnn_test_sdk::utilities::validateAndReport( std::cout, "next_running_var", statsValidator, nextVarRefTensor, nextVarTensor, floatTolerance, floatTolerance); validationPassed = yValid && meanValid && invVarValid && nextMeanValid && nextVarValid; } else { // BATCH_STATS_ONLY mode validation hipdnn_test_sdk::utilities::CpuFpReferenceBatchnorm::fwdTraining< InputType, // XDataType IntermediateType, // ScaleBiasDataType IntermediateType, // MeanVarianceDataType InputType // YDataType >(xTensor, scaleTensor, biasTensor, yRefTensor, utilities::BATCHNORM_DEFAULT_EPSILON, momentumVal, // momentum (not used in BATCH_STATS_ONLY mode but required by API) &savedMeanRefTensor, &savedInvVarRefTensor, nullptr, // prevRunningMean (not used) nullptr, // prevRunningVariance (not used) nullptr, // nextRunningMean (not used) nullptr // nextRunningVariance (not used) ); auto tolerance = hipdnn_test_sdk::utilities::batchnorm::getToleranceTraining(); auto floatTolerance = static_cast(tolerance); auto yValidator = hipdnn_test_sdk::utilities::CpuFpReferenceValidation( tolerance, tolerance); auto statsValidator = hipdnn_test_sdk::utilities::CpuFpReferenceValidation( static_cast(tolerance), static_cast(tolerance)); std::cout << "CPU reference validation:\n"; const bool yValid = hipdnn_test_sdk::utilities::validateAndReport( std::cout, "y", yValidator, yRefTensor, yTensor, floatTolerance, floatTolerance); const bool meanValid = hipdnn_test_sdk::utilities::validateAndReport( std::cout, "saved_mean", statsValidator, savedMeanRefTensor, savedMeanTensor, floatTolerance, floatTolerance); const bool invVarValid = hipdnn_test_sdk::utilities::validateAndReport( std::cout, "saved_inv_variance", statsValidator, savedInvVarRefTensor, savedInvVarTensor, floatTolerance, floatTolerance); validationPassed = yValid && meanValid && invVarValid; } } std::cout << "First 10 y values: "; for(int i = 0; i < 10; ++i) { std::cout << static_cast(yHostPtr[i]) << " "; } std::cout << "\nFirst 10 saved_mean values: "; for(int i = 0; i < 10; ++i) { std::cout << static_cast(savedMeanHostPtr[i]) << " "; } std::cout << "\nFirst 10 saved_inv_variance values: "; for(int i = 0; i < 10; ++i) { std::cout << static_cast(savedInvVarHostPtr[i]) << " "; } if(config.useRunningStats) { auto nextMeanHostPtr = nextMeanTensor.memory().hostData(); auto nextVarHostPtr = nextVarTensor.memory().hostData(); std::cout << "\nFirst 10 next_running_mean values: "; for(int i = 0; i < 10; ++i) { std::cout << static_cast(nextMeanHostPtr[i]) << " "; } std::cout << "\nFirst 10 next_running_variance values: "; for(int i = 0; i < 10; ++i) { std::cout << static_cast(nextVarHostPtr[i]) << " "; } } std::cout << '\n'; std::cout << "\nBatch normalization training graph execution complete for " << inputType << ".\n\n"; return validationPassed; } int main(int argc, char* argv[]) { try { RETURN_SUCCESS_IF_NO_DEVICE(); auto config = parseCommandLineArgs(argc, argv, SampleType::BN_TRAINING); auto [handle, handleError] = createHipdnnHandle(); HIPDNN_FE_CHECK(handleError); const bool allPassed = run(SampleRunner{*handle, config}); if(allPassed) { std::cout << "All batch normalization training runs completed successfully.\n"; return 0; } std::cout << "One or more batch normalization training runs failed validation.\n"; return 1; } catch(const std::exception& e) { std::fprintf(stderr, "Unhandled exception: %s\n", e.what()); return 1; } }